summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Pawlowski <jpawlowski@google.com>2017-04-05 09:22:29 -0700
committerJakub Pawlowski <jpawlowski@google.com>2018-10-12 19:25:59 +0200
commite280f12834190c543be1ad4fddf6a642f65b998a (patch)
treea398a922d546c95123c3cc6e78091bef5378969b
parent23b27ba5c54bf6368980117d91bc51c4495e2c50 (diff)
downloadlibchrome-e280f12834190c543be1ad4fddf6a642f65b998a.tar.gz
Uprev libchrome to r576279 (1/many)
This patch brings the latest and greatest features of libchrome to android. It contains ~2600 patches. Reason for uprev: libbluetooth want to use some of the most recent features avaliable. Test: libchrome_test Change-Id: Iccdec267948daab29e6328694f4c7d2f71ea26ca Merged-In: Iccdec267948daab29e6328694f4c7d2f71ea26ca
-rw-r--r--Android.bp282
-rw-r--r--base/DEPS16
-rw-r--r--base/PRESUBMIT.py49
-rw-r--r--base/allocator/README.md196
-rw-r--r--base/allocator/allocator_extension.cc6
-rw-r--r--base/allocator/allocator_shim.cc18
-rw-r--r--base/allocator/allocator_shim.h2
-rw-r--r--base/allocator/allocator_shim_default_dispatch_to_linker_wrapped_symbols.cc3
-rw-r--r--base/allocator/allocator_shim_override_cpp_symbols.h8
-rw-r--r--base/allocator/buildflags.h5
-rw-r--r--base/allocator/features.h15
-rw-r--r--base/android/base_jni_onload.h23
-rw-r--r--base/android/build_info.cc79
-rw-r--r--base/android/build_info.h72
-rw-r--r--base/android/context_utils.cc53
-rw-r--r--base/android/context_utils.h26
-rw-r--r--base/android/java/src/org/chromium/base/BuildConfig.java21
-rw-r--r--base/android/java/src/org/chromium/base/BuildInfo.java244
-rw-r--r--base/android/java/src/org/chromium/base/ContextUtils.java85
-rw-r--r--base/android/java/src/org/chromium/base/DiscardableReferencePool.java90
-rw-r--r--base/android/java/src/org/chromium/base/JavaExceptionReporter.java65
-rw-r--r--base/android/java/src/org/chromium/base/PackageUtils.java19
-rw-r--r--base/android/java/src/org/chromium/base/StrictModeContext.java85
-rw-r--r--base/android/java/src/org/chromium/base/Supplier.java18
-rw-r--r--base/android/java/src/org/chromium/base/ThreadUtils.java268
-rw-r--r--base/android/java/src/org/chromium/base/TimezoneUtils.java36
-rw-r--r--base/android/java/src/org/chromium/base/annotations/CalledByNative.java2
-rw-r--r--base/android/java/src/org/chromium/base/annotations/MainDex.java5
-rw-r--r--base/android/java/src/org/chromium/base/annotations/SuppressFBWarnings.java20
-rw-r--r--base/android/java_exception_reporter.cc70
-rw-r--r--base/android/java_exception_reporter.h33
-rw-r--r--base/android/javatests/src/org/chromium/base/AssertsTest.java39
-rw-r--r--base/android/javatests/src/org/chromium/base/AsyncTaskTest.java128
-rw-r--r--base/android/jni_android.cc77
-rw-r--r--base/android/jni_android.h27
-rw-r--r--base/android/jni_array.cc311
-rw-r--r--base/android/jni_array.h134
-rw-r--r--base/android/jni_generator/AndroidManifest.xml13
-rw-r--r--base/android/jni_generator/PRESUBMIT.py37
-rw-r--r--base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java90
-rw-r--r--base/android/jni_generator/jni_exception_list.gni13
-rwxr-xr-xbase/android/jni_generator/jni_generator.py869
-rw-r--r--base/android/jni_generator/jni_generator_helper.h24
-rwxr-xr-xbase/android/jni_generator/jni_generator_tests.py223
-rwxr-xr-xbase/android/jni_generator/jni_registration_generator.py340
-rw-r--r--base/android/jni_generator/sample_entry_point.cc27
-rw-r--r--base/android/jni_generator/sample_for_tests.cc40
-rw-r--r--base/android/jni_generator/sample_for_tests.h42
-rw-r--r--base/android/jni_generator/testCalledByNatives.golden510
-rw-r--r--base/android/jni_generator/testConstantsFromJavaP.golden2010
-rw-r--r--base/android/jni_generator/testFromJavaP.golden262
-rw-r--r--base/android/jni_generator/testFromJavaPGenerics.golden67
-rw-r--r--base/android/jni_generator/testInnerClassNatives.golden75
-rw-r--r--base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden100
-rw-r--r--base/android/jni_generator/testInnerClassNativesBothInnerAndOuterRegistrations.golden109
-rw-r--r--base/android/jni_generator/testInnerClassNativesMultiple.golden120
-rw-r--r--base/android/jni_generator/testMultipleJNIAdditionalImport.golden93
-rw-r--r--base/android/jni_generator/testNatives.golden308
-rw-r--r--base/android/jni_generator/testNativesLong.golden57
-rw-r--r--base/android/jni_generator/testNativesRegistrations.golden175
-rw-r--r--base/android/jni_generator/testSingleJNIAdditionalImport.golden89
-rw-r--r--base/android/jni_generator/testTracing.golden99
-rw-r--r--base/android/junit/src/org/chromium/base/DiscardableReferencePoolTest.java80
-rw-r--r--base/android/library_loader/README.md10
-rw-r--r--base/android/library_loader/anchor_functions.cc79
-rw-r--r--base/android/library_loader/anchor_functions.h32
-rw-r--r--base/android/library_loader/anchor_functions.lds7
-rw-r--r--base/android/orderfile/BUILD.gn34
-rw-r--r--base/android/orderfile/orderfile_instrumentation.cc328
-rw-r--r--base/android/orderfile/orderfile_instrumentation.h56
-rw-r--r--base/android/orderfile/orderfile_instrumentation_perftest.cc135
-rw-r--r--base/android/proguard/disable_all_obfuscation.flags8
-rw-r--r--base/android/proguard/disable_chromium_obfuscation.flags8
-rw-r--r--base/android/proguard/enable_obfuscation.flags8
-rw-r--r--base/android/scoped_hardware_buffer_handle.cc114
-rw-r--r--base/android/scoped_hardware_buffer_handle.h87
-rw-r--r--base/android/scoped_java_ref.h28
-rw-r--r--base/android/timezone_utils.cc23
-rw-r--r--base/android/timezone_utils.h22
-rw-r--r--base/at_exit.cc4
-rw-r--r--base/at_exit.h5
-rw-r--r--base/at_exit_unittest.cc16
-rw-r--r--base/atomic_ref_count.h87
-rw-r--r--base/atomic_sequence_num.h49
-rw-r--r--base/atomicops.h4
-rw-r--r--base/atomicops_internals_x86_msvc.h26
-rw-r--r--base/auto_reset.h8
-rw-r--r--base/barrier_closure.cc51
-rw-r--r--base/barrier_closure.h28
-rw-r--r--base/base64_decode_fuzzer.cc15
-rw-r--r--base/base64_encode_fuzzer.cc20
-rw-r--r--base/base_paths.cc12
-rw-r--r--base/base_paths.h5
-rw-r--r--base/base_paths_posix.cc6
-rw-r--r--base/base_switches.cc45
-rw-r--r--base/base_switches.h16
-rw-r--r--base/big_endian.cc105
-rw-r--r--base/big_endian.h106
-rw-r--r--base/big_endian_unittest.cc116
-rw-r--r--base/bind.h405
-rw-r--r--base/bind_helpers.cc14
-rw-r--r--base/bind_helpers.h572
-rw-r--r--base/bind_internal.h710
-rw-r--r--base/bind_unittest.cc125
-rw-r--r--base/bits.h168
-rw-r--r--base/bits_unittest.cc142
-rw-r--r--base/callback.h143
-rw-r--r--base/callback_forward.h35
-rw-r--r--base/callback_helpers.cc24
-rw-r--r--base/callback_helpers.h68
-rw-r--r--base/callback_helpers_unittest.cc19
-rw-r--r--base/callback_internal.cc49
-rw-r--r--base/callback_internal.h54
-rw-r--r--base/callback_list.h39
-rw-r--r--base/callback_list_unittest.cc2
-rw-r--r--base/callback_unittest.cc4
-rw-r--r--base/cancelable_callback.h96
-rw-r--r--base/cancelable_callback_unittest.cc4
-rw-r--r--base/cfi_buildflags.h7
-rw-r--r--base/command_line.cc67
-rw-r--r--base/command_line.h20
-rw-r--r--base/command_line_unittest.cc8
-rw-r--r--base/compiler_specific.h58
-rw-r--r--base/component_export.h87
-rw-r--r--base/component_export_unittest.cc82
-rw-r--r--base/containers/README.md295
-rw-r--r--base/containers/circular_deque.h1111
-rw-r--r--base/containers/circular_deque_unittest.cc881
-rw-r--r--base/containers/flat_map.h362
-rw-r--r--base/containers/flat_set.h140
-rw-r--r--base/containers/flat_tree.h1004
-rw-r--r--base/containers/linked_list.h22
-rw-r--r--base/containers/mru_cache.h22
-rw-r--r--base/containers/queue.h23
-rw-r--r--base/containers/ring_buffer.h133
-rw-r--r--base/containers/small_map.h278
-rw-r--r--base/containers/span.h453
-rw-r--r--base/containers/span_unittest.cc1170
-rw-r--r--base/containers/span_unittest.nc167
-rw-r--r--base/containers/stack.h23
-rw-r--r--base/containers/stack_container.h55
-rw-r--r--base/containers/vector_buffer.h163
-rw-r--r--base/containers/vector_buffer_unittest.cc89
-rw-r--r--base/cpu.cc69
-rw-r--r--base/cpu.h3
-rw-r--r--base/cpu_unittest.cc9
-rw-r--r--base/debug/activity_tracker.cc322
-rw-r--r--base/debug/activity_tracker.h123
-rw-r--r--base/debug/activity_tracker_unittest.cc159
-rw-r--r--base/debug/alias.cc4
-rw-r--r--base/debug/alias.h24
-rw-r--r--base/debug/alias_unittest.cc28
-rw-r--r--base/debug/crash_logging.cc54
-rw-r--r--base/debug/crash_logging.h104
-rw-r--r--base/debug/debugger_posix.cc9
-rw-r--r--base/debug/debugging_buildflags.h12
-rw-r--r--base/debug/debugging_flags.h8
-rw-r--r--base/debug/dump_without_crashing.cc8
-rw-r--r--base/debug/dump_without_crashing.h10
-rw-r--r--base/debug/elf_reader_linux.cc132
-rw-r--r--base/debug/elf_reader_linux.h28
-rw-r--r--base/debug/elf_reader_linux_unittest.cc70
-rw-r--r--base/debug/leak_tracker.h2
-rw-r--r--base/debug/proc_maps_linux.h3
-rw-r--r--base/debug/profiler.cc49
-rw-r--r--base/debug/profiler.h3
-rw-r--r--base/debug/stack_trace.cc35
-rw-r--r--base/debug/stack_trace.h35
-rw-r--r--base/debug/stack_trace_posix.cc166
-rw-r--r--base/debug/task_annotator.cc101
-rw-r--r--base/debug/task_annotator.h32
-rw-r--r--base/debug/task_annotator_unittest.cc343
-rw-r--r--base/debug/thread_heap_usage_tracker.h4
-rw-r--r--base/environment.cc52
-rw-r--r--base/environment.h4
-rw-r--r--base/environment_unittest.cc34
-rw-r--r--base/export_template.h163
-rw-r--r--base/feature_list.cc133
-rw-r--r--base/feature_list.h30
-rw-r--r--base/feature_list_unittest.cc5
-rw-r--r--base/files/dir_reader_linux.h2
-rw-r--r--base/files/dir_reader_posix.h4
-rw-r--r--base/files/dir_reader_posix_unittest.cc8
-rw-r--r--base/files/file.cc32
-rw-r--r--base/files/file.h78
-rw-r--r--base/files/file_descriptor_watcher_posix.cc57
-rw-r--r--base/files/file_descriptor_watcher_posix.h17
-rw-r--r--base/files/file_descriptor_watcher_posix_unittest.cc12
-rw-r--r--base/files/file_enumerator.cc8
-rw-r--r--base/files/file_enumerator.h62
-rw-r--r--base/files/file_enumerator_posix.cc160
-rw-r--r--base/files/file_enumerator_unittest.cc312
-rw-r--r--base/files/file_path.cc109
-rw-r--r--base/files/file_path.h49
-rw-r--r--base/files/file_path_unittest.cc14
-rw-r--r--base/files/file_path_watcher.cc2
-rw-r--r--base/files/file_path_watcher_linux.cc121
-rw-r--r--base/files/file_path_watcher_unittest.cc73
-rw-r--r--base/files/file_posix.cc77
-rw-r--r--base/files/file_unittest.cc87
-rw-r--r--base/files/file_util.cc61
-rw-r--r--base/files/file_util.h75
-rw-r--r--base/files/file_util_posix.cc567
-rw-r--r--base/files/important_file_writer.cc165
-rw-r--r--base/files/important_file_writer.h35
-rw-r--r--base/files/important_file_writer_unittest.cc132
-rw-r--r--base/files/memory_mapped_file.cc34
-rw-r--r--base/files/memory_mapped_file.h6
-rw-r--r--base/files/memory_mapped_file_posix.cc114
-rw-r--r--base/files/platform_file.h43
-rw-r--r--base/files/scoped_file.cc21
-rw-r--r--base/files/scoped_file.h4
-rw-r--r--base/files/scoped_temp_dir.cc21
-rw-r--r--base/files/scoped_temp_dir.h16
-rw-r--r--base/format_macros.h40
-rw-r--r--base/gmock_unittest.cc12
-rw-r--r--base/gtest_prod_util.h2
-rw-r--r--base/guid.cc15
-rw-r--r--base/guid.h8
-rw-r--r--base/hash.cc94
-rw-r--r--base/hash.h95
-rw-r--r--base/i18n/rtl.h16
-rw-r--r--base/json/json_correctness_fuzzer.cc63
-rw-r--r--base/json/json_file_value_serializer.cc8
-rw-r--r--base/json/json_parser.cc541
-rw-r--r--base/json/json_parser.h121
-rw-r--r--base/json/json_parser_unittest.cc166
-rw-r--r--base/json/json_reader.cc55
-rw-r--r--base/json/json_reader.h28
-rw-r--r--base/json/json_reader_fuzzer.cc29
-rw-r--r--base/json/json_reader_unittest.cc1001
-rw-r--r--base/json/json_string_value_serializer.cc4
-rw-r--r--base/json/json_value_converter.h75
-rw-r--r--base/json/json_value_converter_unittest.cc6
-rw-r--r--base/json/json_value_serializer_unittest.cc4
-rw-r--r--base/json/json_writer.cc15
-rw-r--r--base/json/json_writer_unittest.cc30
-rw-r--r--base/json/string_escape.cc16
-rw-r--r--base/json/string_escape.h21
-rw-r--r--base/json/string_escape_fuzzer.cc37
-rw-r--r--base/json/string_escape_unittest.cc16
-rw-r--r--base/lazy_instance.cc54
-rw-r--r--base/lazy_instance.h103
-rw-r--r--base/lazy_instance_helpers.cc64
-rw-r--r--base/lazy_instance_helpers.h101
-rw-r--r--base/lazy_instance_unittest.cc165
-rw-r--r--base/location.cc113
-rw-r--r--base/location.h135
-rw-r--r--base/logging.cc288
-rw-r--r--base/logging.h243
-rw-r--r--base/logging_unittest.cc357
-rw-r--r--base/macros.h60
-rw-r--r--base/memory/aligned_memory.cc4
-rw-r--r--base/memory/aligned_memory.h91
-rw-r--r--base/memory/aligned_memory_unittest.cc73
-rw-r--r--base/memory/linked_ptr_unittest.cc18
-rw-r--r--base/memory/manual_constructor.h75
-rw-r--r--base/memory/platform_shared_memory_region.cc37
-rw-r--r--base/memory/platform_shared_memory_region.h229
-rw-r--r--base/memory/platform_shared_memory_region_mac.cc233
-rw-r--r--base/memory/platform_shared_memory_region_posix.cc328
-rw-r--r--base/memory/platform_shared_memory_region_unittest.cc347
-rw-r--r--base/memory/protected_memory.cc17
-rw-r--r--base/memory/protected_memory.h276
-rw-r--r--base/memory/protected_memory_buildflags.h7
-rw-r--r--base/memory/protected_memory_cfi.h86
-rw-r--r--base/memory/protected_memory_posix.cc79
-rw-r--r--base/memory/protected_memory_unittest.cc126
-rw-r--r--base/memory/ptr_util.h51
-rw-r--r--base/memory/raw_scoped_refptr_mismatch_checker.h36
-rw-r--r--base/memory/read_only_shared_memory_region.cc97
-rw-r--r--base/memory/read_only_shared_memory_region.h122
-rw-r--r--base/memory/ref_counted.cc47
-rw-r--r--base/memory/ref_counted.h420
-rw-r--r--base/memory/ref_counted_delete_on_sequence.h82
-rw-r--r--base/memory/ref_counted_memory.cc75
-rw-r--r--base/memory/ref_counted_memory.h93
-rw-r--r--base/memory/ref_counted_memory_unittest.cc90
-rw-r--r--base/memory/ref_counted_unittest.cc203
-rw-r--r--base/memory/scoped_refptr.h337
-rw-r--r--base/memory/scoped_vector.h153
-rw-r--r--base/memory/scoped_vector_unittest.cc338
-rw-r--r--base/memory/shared_memory.h166
-rw-r--r--base/memory/shared_memory_android.cc36
-rw-r--r--base/memory/shared_memory_handle.cc23
-rw-r--r--base/memory/shared_memory_handle.h239
-rw-r--r--base/memory/shared_memory_handle_android.cc115
-rw-r--r--base/memory/shared_memory_handle_posix.cc71
-rw-r--r--base/memory/shared_memory_helper.cc81
-rw-r--r--base/memory/shared_memory_helper.h13
-rw-r--r--base/memory/shared_memory_mapping.cc117
-rw-r--r--base/memory/shared_memory_mapping.h144
-rw-r--r--base/memory/shared_memory_posix.cc277
-rw-r--r--base/memory/shared_memory_region_unittest.cc280
-rw-r--r--base/memory/shared_memory_unittest.cc366
-rw-r--r--base/memory/singleton.cc34
-rw-r--r--base/memory/singleton.h93
-rw-r--r--base/memory/singleton_unittest.cc34
-rw-r--r--base/memory/unsafe_shared_memory_region.cc76
-rw-r--r--base/memory/unsafe_shared_memory_region.h118
-rw-r--r--base/memory/weak_ptr.cc39
-rw-r--r--base/memory/weak_ptr.h92
-rw-r--r--base/memory/weak_ptr_unittest.cc66
-rw-r--r--base/memory/weak_ptr_unittest.nc23
-rw-r--r--base/memory/writable_shared_memory_region.cc93
-rw-r--r--base/memory/writable_shared_memory_region.h109
-rw-r--r--base/message_loop/incoming_task_queue.cc340
-rw-r--r--base/message_loop/incoming_task_queue.h252
-rw-r--r--base/message_loop/message_loop.cc811
-rw-r--r--base/message_loop/message_loop.h445
-rw-r--r--base/message_loop/message_loop_current.cc248
-rw-r--r--base/message_loop/message_loop_current.h297
-rw-r--r--base/message_loop/message_loop_io_posix_unittest.cc418
-rw-r--r--base/message_loop/message_loop_perftest.cc254
-rw-r--r--base/message_loop/message_loop_task_runner.cc18
-rw-r--r--base/message_loop/message_loop_task_runner.h10
-rw-r--r--base/message_loop/message_loop_task_runner_perftest.cc191
-rw-r--r--base/message_loop/message_loop_task_runner_unittest.cc84
-rw-r--r--base/message_loop/message_loop_test.cc1041
-rw-r--r--base/message_loop/message_loop_test.h130
-rw-r--r--base/message_loop/message_loop_unittest.cc1904
-rw-r--r--base/message_loop/message_pump.cc6
-rw-r--r--base/message_loop/message_pump.h6
-rw-r--r--base/message_loop/message_pump_default.cc27
-rw-r--r--base/message_loop/message_pump_default.h4
-rw-r--r--base/message_loop/message_pump_for_io.h44
-rw-r--r--base/message_loop/message_pump_for_ui.h57
-rw-r--r--base/message_loop/message_pump_glib.cc10
-rw-r--r--base/message_loop/message_pump_glib_unittest.cc234
-rw-r--r--base/message_loop/message_pump_libevent.cc70
-rw-r--r--base/message_loop/message_pump_libevent.h84
-rw-r--r--base/message_loop/watchable_io_message_pump_posix.cc16
-rw-r--r--base/message_loop/watchable_io_message_pump_posix.h88
-rw-r--r--base/metrics/bucket_ranges.cc39
-rw-r--r--base/metrics/bucket_ranges.h24
-rw-r--r--base/metrics/dummy_histogram.cc102
-rw-r--r--base/metrics/dummy_histogram.h61
-rw-r--r--base/metrics/field_trial.cc429
-rw-r--r--base/metrics/field_trial.h118
-rw-r--r--base/metrics/field_trial_param_associator.cc12
-rw-r--r--base/metrics/field_trial_param_associator.h5
-rw-r--r--base/metrics/field_trial_params_unittest.nc47
-rw-r--r--base/metrics/field_trial_unittest.cc394
-rw-r--r--base/metrics/histogram.cc533
-rw-r--r--base/metrics/histogram.h195
-rw-r--r--base/metrics/histogram_base.cc149
-rw-r--r--base/metrics/histogram_base.h84
-rw-r--r--base/metrics/histogram_base_unittest.cc197
-rw-r--r--base/metrics/histogram_delta_serialization.cc54
-rw-r--r--base/metrics/histogram_delta_serialization.h9
-rw-r--r--base/metrics/histogram_flattener.h20
-rw-r--r--base/metrics/histogram_functions.cc110
-rw-r--r--base/metrics/histogram_functions.h158
-rw-r--r--base/metrics/histogram_macros.h131
-rw-r--r--base/metrics/histogram_macros_internal.h124
-rw-r--r--base/metrics/histogram_macros_local.h18
-rw-r--r--base/metrics/histogram_macros_unittest.cc12
-rw-r--r--base/metrics/histogram_samples.cc248
-rw-r--r--base/metrics/histogram_samples.h157
-rw-r--r--base/metrics/histogram_samples_unittest.cc84
-rw-r--r--base/metrics/histogram_snapshot_manager.cc67
-rw-r--r--base/metrics/histogram_snapshot_manager.h32
-rw-r--r--base/metrics/histogram_snapshot_manager_unittest.cc35
-rw-r--r--base/metrics/histogram_unittest.cc170
-rw-r--r--base/metrics/persistent_histogram_allocator.cc461
-rw-r--r--base/metrics/persistent_histogram_allocator.h97
-rw-r--r--base/metrics/persistent_histogram_allocator_unittest.cc115
-rw-r--r--base/metrics/persistent_histogram_storage.cc103
-rw-r--r--base/metrics/persistent_histogram_storage.h68
-rw-r--r--base/metrics/persistent_histogram_storage_unittest.cc78
-rw-r--r--base/metrics/persistent_memory_allocator.cc182
-rw-r--r--base/metrics/persistent_memory_allocator.h108
-rw-r--r--base/metrics/persistent_memory_allocator_unittest.cc135
-rw-r--r--base/metrics/persistent_sample_map.cc66
-rw-r--r--base/metrics/persistent_sample_map_unittest.cc16
-rw-r--r--base/metrics/record_histogram_checker.h27
-rw-r--r--base/metrics/sample_map.cc22
-rw-r--r--base/metrics/sample_map_unittest.cc8
-rw-r--r--base/metrics/sample_vector.cc378
-rw-r--r--base/metrics/sample_vector.h118
-rw-r--r--base/metrics/sample_vector_unittest.cc301
-rw-r--r--base/metrics/single_sample_metrics.cc77
-rw-r--r--base/metrics/single_sample_metrics.h104
-rw-r--r--base/metrics/single_sample_metrics_unittest.cc124
-rw-r--r--base/metrics/sparse_histogram.cc69
-rw-r--r--base/metrics/sparse_histogram.h10
-rw-r--r--base/metrics/sparse_histogram_unittest.cc108
-rw-r--r--base/metrics/statistics_recorder.cc582
-rw-r--r--base/metrics/statistics_recorder.h343
-rw-r--r--base/metrics/statistics_recorder_unittest.cc328
-rw-r--r--base/metrics/user_metrics.cc2
-rw-r--r--base/metrics/user_metrics_action.h2
-rw-r--r--base/native_library.cc15
-rw-r--r--base/native_library.h22
-rw-r--r--base/native_library_posix.cc10
-rw-r--r--base/no_destructor.h99
-rw-r--r--base/no_destructor_unittest.cc76
-rw-r--r--base/numerics/BUILD.gn28
-rw-r--r--base/numerics/README.md409
-rw-r--r--base/numerics/checked_math.h393
-rw-r--r--base/numerics/checked_math_impl.h567
-rw-r--r--base/numerics/clamped_math.h262
-rw-r--r--base/numerics/clamped_math_impl.h341
-rw-r--r--base/numerics/math_constants.h15
-rw-r--r--base/numerics/ranges.h27
-rw-r--r--base/numerics/safe_conversions.h216
-rw-r--r--base/numerics/safe_conversions_arm_impl.h51
-rw-r--r--base/numerics/safe_conversions_impl.h168
-rw-r--r--base/numerics/safe_math.h504
-rw-r--r--base/numerics/safe_math_arm_impl.h122
-rw-r--r--base/numerics/safe_math_clang_gcc_impl.h157
-rw-r--r--base/numerics/safe_math_impl.h641
-rw-r--r--base/numerics/safe_math_shared_impl.h237
-rw-r--r--base/numerics/safe_numerics_unittest.cc1186
-rw-r--r--base/numerics/saturated_arithmetic.h107
-rw-r--r--base/numerics/saturated_arithmetic_arm.h108
-rw-r--r--base/observer_list.h438
-rw-r--r--base/observer_list_threadsafe.cc16
-rw-r--r--base/observer_list_threadsafe.h309
-rw-r--r--base/observer_list_unittest.cc474
-rw-r--r--base/optional.h922
-rw-r--r--base/optional_unittest.cc887
-rw-r--r--base/optional_unittest.nc65
-rw-r--r--base/path_service.cc32
-rw-r--r--base/path_service.h3
-rw-r--r--base/pending_task.cc33
-rw-r--r--base/pending_task.h35
-rw-r--r--base/pickle.cc111
-rw-r--r--base/pickle.h76
-rw-r--r--base/pickle_unittest.cc181
-rw-r--r--base/posix/eintr_wrapper.h7
-rw-r--r--base/posix/file_descriptor_shuffle.h2
-rw-r--r--base/posix/global_descriptors.cc8
-rw-r--r--base/posix/safe_strerror.cc2
-rw-r--r--base/posix/unix_domain_socket.cc288
-rw-r--r--base/posix/unix_domain_socket.h111
-rw-r--r--base/posix/unix_domain_socket_linux.cc245
-rw-r--r--base/posix/unix_domain_socket_linux.h104
-rw-r--r--base/posix/unix_domain_socket_linux_unittest.cc163
-rw-r--r--base/posix/unix_domain_socket_unittest.cc183
-rw-r--r--base/power_monitor/power_monitor.h5
-rw-r--r--base/power_monitor/power_monitor_device_source.h2
-rw-r--r--base/power_monitor/power_monitor_source.h11
-rw-r--r--base/power_monitor/power_observer.h2
-rw-r--r--base/process/internal_aix.cc155
-rw-r--r--base/process/internal_aix.h84
-rw-r--r--base/process/internal_linux.cc5
-rw-r--r--base/process/internal_linux.h22
-rw-r--r--base/process/kill.cc38
-rw-r--r--base/process/kill.h59
-rw-r--r--base/process/kill_posix.cc106
-rw-r--r--base/process/launch.h186
-rw-r--r--base/process/launch_posix.cc257
-rw-r--r--base/process/memory.cc2
-rw-r--r--base/process/memory.h2
-rw-r--r--base/process/memory_linux.cc114
-rw-r--r--base/process/process.h34
-rw-r--r--base/process/process_handle.cc2
-rw-r--r--base/process/process_handle.h26
-rw-r--r--base/process/process_handle_linux.cc9
-rw-r--r--base/process/process_info.h12
-rw-r--r--base/process/process_info_unittest.cc6
-rw-r--r--base/process/process_iterator.cc9
-rw-r--r--base/process/process_iterator.h10
-rw-r--r--base/process/process_iterator_linux.cc2
-rw-r--r--base/process/process_metrics.cc97
-rw-r--r--base/process/process_metrics.h399
-rw-r--r--base/process/process_metrics_iocounters.h37
-rw-r--r--base/process/process_metrics_linux.cc786
-rw-r--r--base/process/process_metrics_posix.cc42
-rw-r--r--base/process/process_metrics_unittest.cc550
-rw-r--r--base/process/process_posix.cc105
-rw-r--r--base/profiler/tracked_time.cc68
-rw-r--r--base/profiler/tracked_time.h71
-rw-r--r--base/profiler/tracked_time_unittest.cc105
-rw-r--r--base/rand_util.cc6
-rw-r--r--base/rand_util.h40
-rw-r--r--base/rand_util_posix.cc15
-rw-r--r--base/rand_util_unittest.cc16
-rw-r--r--base/run_loop.cc279
-rw-r--r--base/run_loop.h262
-rw-r--r--base/sampling_heap_profiler/benchmark-octane.js58
-rw-r--r--base/sampling_heap_profiler/lock_free_address_hash_set.cc72
-rw-r--r--base/sampling_heap_profiler/lock_free_address_hash_set.h152
-rw-r--r--base/sampling_heap_profiler/lock_free_address_hash_set_unittest.cc183
-rw-r--r--base/sampling_heap_profiler/sampling_heap_profiler.cc481
-rw-r--r--base/sampling_heap_profiler/sampling_heap_profiler.h119
-rw-r--r--base/sampling_heap_profiler/sampling_heap_profiler_unittest.cc165
-rw-r--r--base/scoped_generic.h7
-rw-r--r--base/scoped_native_library.cc43
-rw-r--r--base/scoped_native_library.h55
-rw-r--r--base/security_unittest.cc37
-rw-r--r--base/sequence_checker.h82
-rw-r--r--base/sequence_checker_impl.cc26
-rw-r--r--base/sequence_checker_unittest.cc139
-rw-r--r--base/sequence_token.cc4
-rw-r--r--base/sequence_token_unittest.cc8
-rw-r--r--base/sequenced_task_runner.cc12
-rw-r--r--base/sequenced_task_runner.h33
-rw-r--r--base/single_thread_task_runner.h8
-rw-r--r--base/stl_util.h160
-rw-r--r--base/stl_util_unittest.cc239
-rw-r--r--base/strings/char_traits.h92
-rw-r--r--base/strings/char_traits_unittest.cc32
-rw-r--r--base/strings/nullable_string16.cc22
-rw-r--r--base/strings/nullable_string16.h29
-rw-r--r--base/strings/old_utf_string_conversions.cc262
-rw-r--r--base/strings/old_utf_string_conversions.h64
-rw-r--r--base/strings/pattern.cc202
-rw-r--r--base/strings/pattern.h13
-rw-r--r--base/strings/pattern_unittest.cc16
-rw-r--r--base/strings/safe_sprintf.cc8
-rw-r--r--base/strings/safe_sprintf.h4
-rw-r--r--base/strings/safe_sprintf_unittest.cc12
-rw-r--r--base/strings/strcat.cc81
-rw-r--r--base/strings/strcat.h99
-rw-r--r--base/strings/strcat_unittest.cc67
-rw-r--r--base/strings/string16.cc11
-rw-r--r--base/strings/string16.h21
-rw-r--r--base/strings/string16_unittest.nc25
-rw-r--r--base/strings/string_number_conversions.cc130
-rw-r--r--base/strings/string_number_conversions.h89
-rw-r--r--base/strings/string_number_conversions_fuzzer.cc67
-rw-r--r--base/strings/string_number_conversions_unittest.cc51
-rw-r--r--base/strings/string_piece.cc3
-rw-r--r--base/strings/string_piece.h86
-rw-r--r--base/strings/string_piece_forward.h24
-rw-r--r--base/strings/string_piece_unittest.cc156
-rw-r--r--base/strings/string_tokenizer.h24
-rw-r--r--base/strings/string_tokenizer_fuzzer.cc56
-rw-r--r--base/strings/string_util.cc355
-rw-r--r--base/strings/string_util.h42
-rw-r--r--base/strings/string_util_unittest.cc225
-rw-r--r--base/strings/string_util_win.h44
-rw-r--r--base/strings/stringprintf_unittest.cc2
-rw-r--r--base/strings/sys_string_conversions.h7
-rw-r--r--base/strings/sys_string_conversions_posix.cc13
-rw-r--r--base/strings/utf_string_conversion_utils.cc7
-rw-r--r--base/strings/utf_string_conversions.cc336
-rw-r--r--base/strings/utf_string_conversions.h6
-rw-r--r--base/strings/utf_string_conversions_fuzzer.cc56
-rw-r--r--base/strings/utf_string_conversions_regression_fuzzer.cc105
-rw-r--r--base/strings/utf_string_conversions_unittest.cc4
-rw-r--r--base/sync_socket.h9
-rw-r--r--base/sync_socket_posix.cc63
-rw-r--r--base/sync_socket_unittest.cc155
-rw-r--r--base/synchronization/atomic_flag_unittest.cc26
-rw-r--r--base/synchronization/condition_variable.h18
-rw-r--r--base/synchronization/condition_variable_posix.cc7
-rw-r--r--base/synchronization/condition_variable_unittest.cc22
-rw-r--r--base/synchronization/lock.h12
-rw-r--r--base/synchronization/lock_impl.h27
-rw-r--r--base/synchronization/lock_impl_posix.cc73
-rw-r--r--base/synchronization/lock_unittest.cc42
-rw-r--r--base/synchronization/read_write_lock.h105
-rw-r--r--base/synchronization/read_write_lock_posix.cc40
-rw-r--r--base/synchronization/synchronization_buildflags.h4
-rw-r--r--base/synchronization/waitable_event.h95
-rw-r--r--base/synchronization/waitable_event_perftest.cc178
-rw-r--r--base/synchronization/waitable_event_posix.cc14
-rw-r--r--base/synchronization/waitable_event_unittest.cc32
-rw-r--r--base/synchronization/waitable_event_watcher.h55
-rw-r--r--base/synchronization/waitable_event_watcher_posix.cc39
-rw-r--r--base/sys_info.cc57
-rw-r--r--base/sys_info.h40
-rw-r--r--base/sys_info_chromeos.cc22
-rw-r--r--base/sys_info_internal.h2
-rw-r--r--base/sys_info_linux.cc4
-rw-r--r--base/sys_info_posix.cc21
-rw-r--r--base/sys_info_unittest.cc51
-rw-r--r--base/task/README.md12
-rw-r--r--base/task/cancelable_task_tracker.cc83
-rw-r--r--base/task/cancelable_task_tracker.h68
-rw-r--r--base/task/cancelable_task_tracker_unittest.cc76
-rw-r--r--base/task/sequence_manager/enqueue_order.cc17
-rw-r--r--base/task/sequence_manager/enqueue_order.h71
-rw-r--r--base/task/sequence_manager/graceful_queue_shutdown_helper.cc42
-rw-r--r--base/task/sequence_manager/graceful_queue_shutdown_helper.h50
-rw-r--r--base/task/sequence_manager/intrusive_heap.h229
-rw-r--r--base/task/sequence_manager/intrusive_heap_unittest.cc378
-rw-r--r--base/task/sequence_manager/lazily_deallocated_deque.h364
-rw-r--r--base/task/sequence_manager/lazily_deallocated_deque_unittest.cc364
-rw-r--r--base/task/sequence_manager/lazy_now.cc36
-rw-r--r--base/task/sequence_manager/lazy_now.h41
-rw-r--r--base/task/sequence_manager/moveable_auto_lock.h41
-rw-r--r--base/task/sequence_manager/real_time_domain.cc48
-rw-r--r--base/task/sequence_manager/real_time_domain.h37
-rw-r--r--base/task/sequence_manager/sequence_manager.cc26
-rw-r--r--base/task/sequence_manager/sequence_manager.h132
-rw-r--r--base/task/sequence_manager/sequence_manager_impl.cc724
-rw-r--r--base/task/sequence_manager/sequence_manager_impl.h341
-rw-r--r--base/task/sequence_manager/sequence_manager_impl_unittest.cc3260
-rw-r--r--base/task/sequence_manager/sequence_manager_perftest.cc306
-rw-r--r--base/task/sequence_manager/sequenced_task_source.h37
-rw-r--r--base/task/sequence_manager/task_queue.cc289
-rw-r--r--base/task/sequence_manager/task_queue.h368
-rw-r--r--base/task/sequence_manager/task_queue_impl.cc1016
-rw-r--r--base/task/sequence_manager/task_queue_impl.h471
-rw-r--r--base/task/sequence_manager/task_queue_selector.cc407
-rw-r--r--base/task/sequence_manager/task_queue_selector.h225
-rw-r--r--base/task/sequence_manager/task_queue_selector_logic.h37
-rw-r--r--base/task/sequence_manager/task_queue_selector_unittest.cc885
-rw-r--r--base/task/sequence_manager/task_time_observer.h32
-rw-r--r--base/task/sequence_manager/test/fake_task.cc35
-rw-r--r--base/task/sequence_manager/test/fake_task.h31
-rw-r--r--base/task/sequence_manager/test/lazy_thread_controller_for_test.cc123
-rw-r--r--base/task/sequence_manager/test/lazy_thread_controller_for_test.h53
-rw-r--r--base/task/sequence_manager/test/mock_time_domain.cc39
-rw-r--r--base/task/sequence_manager/test/mock_time_domain.h38
-rw-r--r--base/task/sequence_manager/test/sequence_manager_for_test.cc79
-rw-r--r--base/task/sequence_manager/test/sequence_manager_for_test.h46
-rw-r--r--base/task/sequence_manager/test/test_task_queue.cc23
-rw-r--r--base/task/sequence_manager/test/test_task_queue.h33
-rw-r--r--base/task/sequence_manager/test/test_task_time_observer.h23
-rw-r--r--base/task/sequence_manager/thread_controller.h85
-rw-r--r--base/task/sequence_manager/thread_controller_impl.cc269
-rw-r--r--base/task/sequence_manager/thread_controller_impl.h130
-rw-r--r--base/task/sequence_manager/thread_controller_with_message_pump_impl.cc205
-rw-r--r--base/task/sequence_manager/thread_controller_with_message_pump_impl.h109
-rw-r--r--base/task/sequence_manager/time_domain.cc136
-rw-r--r--base/task/sequence_manager/time_domain.h139
-rw-r--r--base/task/sequence_manager/time_domain_unittest.cc324
-rw-r--r--base/task/sequence_manager/work_queue.cc236
-rw-r--r--base/task/sequence_manager/work_queue.h152
-rw-r--r--base/task/sequence_manager/work_queue_sets.cc172
-rw-r--r--base/task/sequence_manager/work_queue_sets.h102
-rw-r--r--base/task/sequence_manager/work_queue_sets_unittest.cc328
-rw-r--r--base/task/sequence_manager/work_queue_unittest.cc475
-rw-r--r--base/task_runner.cc17
-rw-r--r--base/task_runner.h40
-rw-r--r--base/task_runner_util.h35
-rw-r--r--base/task_runner_util_unittest.cc1
-rw-r--r--base/task_scheduler/can_schedule_sequence_observer.h27
-rw-r--r--base/task_scheduler/environment_config.cc45
-rw-r--r--base/task_scheduler/environment_config.h53
-rw-r--r--base/task_scheduler/lazy_task_runner.cc122
-rw-r--r--base/task_scheduler/lazy_task_runner.h218
-rw-r--r--base/task_scheduler/lazy_task_runner_unittest.cc199
-rw-r--r--base/task_scheduler/post_task.cc132
-rw-r--r--base/task_scheduler/post_task.h225
-rw-r--r--base/task_scheduler/scheduler_worker_observer.h27
-rw-r--r--base/task_scheduler/scheduler_worker_pool.cc219
-rw-r--r--base/task_scheduler/scheduler_worker_pool_unittest.cc343
-rw-r--r--base/task_scheduler/sequence.cc31
-rw-r--r--base/task_scheduler/sequence.h29
-rw-r--r--base/task_scheduler/sequence_sort_key.h3
-rw-r--r--base/task_scheduler/sequence_unittest.cc265
-rw-r--r--base/task_scheduler/service_thread.cc93
-rw-r--r--base/task_scheduler/service_thread.h60
-rw-r--r--base/task_scheduler/service_thread_unittest.cc111
-rw-r--r--base/task_scheduler/single_thread_task_runner_thread_mode.h25
-rw-r--r--base/task_scheduler/task.cc49
-rw-r--r--base/task_scheduler/task.h15
-rw-r--r--base/task_scheduler/task_scheduler.h248
-rw-r--r--base/task_scheduler/task_traits.cc33
-rw-r--r--base/task_scheduler/task_traits.h193
-rw-r--r--base/task_scheduler/task_traits_details.h128
-rw-r--r--base/task_scheduler/task_traits_unittest.cc175
-rw-r--r--base/task_scheduler/task_traits_unittest.nc31
-rw-r--r--base/task_scheduler/test_utils.cc45
-rw-r--r--base/task_scheduler/test_utils.h32
-rw-r--r--base/task_scheduler/tracked_ref.h171
-rw-r--r--base/task_scheduler/tracked_ref_unittest.cc150
-rw-r--r--base/template_util.h90
-rw-r--r--base/template_util_unittest.cc44
-rw-r--r--base/test/DEPS3
-rw-r--r--base/test/android/java_handler_thread_helpers.cc39
-rw-r--r--base/test/android/java_handler_thread_helpers.h42
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/params/ParameterProvider.java11
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/AnnotationProcessingUtils.java259
-rw-r--r--base/test/android/javatests/src/org/chromium/base/test/util/AnnotationRule.java139
-rw-r--r--base/test/android/junit/src/org/chromium/base/test/util/AnnotationProcessingUtilsTest.java377
-rw-r--r--base/test/android/url_utils.cc24
-rw-r--r--base/test/android/url_utils.h23
-rw-r--r--base/test/bind_test_util.h36
-rw-r--r--base/test/copy_only_int.h55
-rw-r--r--base/test/data/file_util/.gitattributes2
-rw-r--r--base/test/data/json/bom_feff.json18
-rw-r--r--base/test/fontconfig_util_linux.cc423
-rw-r--r--base/test/fontconfig_util_linux.h18
-rw-r--r--base/test/generate_fontconfig_caches.cc24
-rw-r--r--base/test/gtest_util.cc5
-rw-r--r--base/test/gtest_util.h18
-rw-r--r--base/test/metrics/histogram_enum_reader.cc156
-rw-r--r--base/test/metrics/histogram_enum_reader.h31
-rw-r--r--base/test/metrics/histogram_enum_reader_unittest.cc31
-rw-r--r--base/test/metrics/histogram_tester.cc231
-rw-r--r--base/test/metrics/histogram_tester.h179
-rw-r--r--base/test/mock_entropy_provider.cc2
-rw-r--r--base/test/move_only_int.h68
-rw-r--r--base/test/multiprocess_test.cc23
-rw-r--r--base/test/multiprocess_test.h26
-rw-r--r--base/test/multiprocess_test_android.cc28
-rw-r--r--base/test/opaque_ref_counted.cc59
-rw-r--r--base/test/opaque_ref_counted.h29
-rw-r--r--base/test/scoped_environment_variable_override.cc33
-rw-r--r--base/test/scoped_environment_variable_override.h40
-rw-r--r--base/test/scoped_feature_list.cc214
-rw-r--r--base/test/scoped_feature_list.h93
-rw-r--r--base/test/scoped_feature_list_unittest.cc308
-rw-r--r--base/test/scoped_locale.cc6
-rw-r--r--base/test/scoped_task_environment.cc346
-rw-r--r--base/test/scoped_task_environment.h177
-rw-r--r--base/test/scoped_task_environment_unittest.cc324
-rw-r--r--base/test/sequenced_worker_pool_owner.cc65
-rw-r--r--base/test/sequenced_worker_pool_owner.h71
-rw-r--r--base/test/simple_test_clock.cc6
-rw-r--r--base/test/simple_test_clock.h4
-rw-r--r--base/test/simple_test_tick_clock.cc6
-rw-r--r--base/test/simple_test_tick_clock.h4
-rw-r--r--base/test/test_child_process.cc43
-rw-r--r--base/test/test_file_util.cc5
-rw-r--r--base/test/test_file_util_linux.cc31
-rw-r--r--base/test/test_file_util_posix.cc8
-rw-r--r--base/test/test_io_thread.cc6
-rw-r--r--base/test/test_io_thread.h3
-rw-r--r--base/test/test_mock_time_task_runner.cc332
-rw-r--r--base/test/test_mock_time_task_runner.h136
-rw-r--r--base/test/test_mock_time_task_runner_unittest.cc290
-rw-r--r--base/test/test_pending_task.cc4
-rw-r--r--base/test/test_pending_task.h4
-rw-r--r--base/test/test_shared_memory_util.cc187
-rw-r--r--base/test/test_shared_memory_util.h56
-rw-r--r--base/test/test_simple_task_runner.cc25
-rw-r--r--base/test/test_simple_task_runner.h13
-rw-r--r--base/test/test_switches.cc9
-rw-r--r--base/test/test_switches.h2
-rw-r--r--base/test/test_timeouts.cc97
-rw-r--r--base/test/test_timeouts.h9
-rw-r--r--base/third_party/icu/LICENSE50
-rw-r--r--base/third_party/icu/README.chromium15
-rw-r--r--base/third_party/icu/icu_utf.cc260
-rw-r--r--base/third_party/icu/icu_utf.h294
-rw-r--r--base/thread_annotations.h238
-rw-r--r--base/thread_annotations_unittest.cc58
-rw-r--r--base/thread_annotations_unittest.nc71
-rw-r--r--base/threading/non_thread_safe.h62
-rw-r--r--base/threading/non_thread_safe_impl.cc23
-rw-r--r--base/threading/non_thread_safe_impl.h39
-rw-r--r--base/threading/non_thread_safe_unittest.cc148
-rw-r--r--base/threading/platform_thread.h38
-rw-r--r--base/threading/platform_thread_linux.cc14
-rw-r--r--base/threading/platform_thread_posix.cc28
-rw-r--r--base/threading/platform_thread_unittest.cc40
-rw-r--r--base/threading/post_task_and_reply_impl.cc137
-rw-r--r--base/threading/post_task_and_reply_impl.h23
-rw-r--r--base/threading/scoped_blocking_call.cc72
-rw-r--r--base/threading/scoped_blocking_call.h140
-rw-r--r--base/threading/scoped_blocking_call_unittest.cc134
-rw-r--r--base/threading/sequence_local_storage_map.cc105
-rw-r--r--base/threading/sequence_local_storage_map.h90
-rw-r--r--base/threading/sequence_local_storage_map_unittest.cc117
-rw-r--r--base/threading/sequence_local_storage_slot.cc26
-rw-r--r--base/threading/sequence_local_storage_slot.h105
-rw-r--r--base/threading/sequence_local_storage_slot_unittest.cc143
-rw-r--r--base/threading/sequenced_task_runner_handle.cc58
-rw-r--r--base/threading/sequenced_task_runner_handle.h5
-rw-r--r--base/threading/sequenced_worker_pool.cc1680
-rw-r--r--base/threading/sequenced_worker_pool.h418
-rw-r--r--base/threading/simple_thread.cc35
-rw-r--r--base/threading/simple_thread.h49
-rw-r--r--base/threading/simple_thread_unittest.cc4
-rw-r--r--base/threading/thread.cc14
-rw-r--r--base/threading/thread.h17
-rw-r--r--base/threading/thread_checker.h116
-rw-r--r--base/threading/thread_checker_impl.h6
-rw-r--r--base/threading/thread_checker_unittest.cc62
-rw-r--r--base/threading/thread_collision_warner.h6
-rw-r--r--base/threading/thread_collision_warner_unittest.cc2
-rw-r--r--base/threading/thread_id_name_manager.cc35
-rw-r--r--base/threading/thread_id_name_manager.h16
-rw-r--r--base/threading/thread_local_storage.cc104
-rw-r--r--base/threading/thread_local_storage.h123
-rw-r--r--base/threading/thread_local_storage_posix.cc4
-rw-r--r--base/threading/thread_local_storage_unittest.cc158
-rw-r--r--base/threading/thread_local_unittest.cc27
-rw-r--r--base/threading/thread_restrictions.cc145
-rw-r--r--base/threading/thread_restrictions.h379
-rw-r--r--base/threading/thread_restrictions_unittest.cc137
-rw-r--r--base/threading/thread_task_runner_handle.cc43
-rw-r--r--base/threading/thread_task_runner_handle.h6
-rw-r--r--base/threading/thread_unittest.cc60
-rw-r--r--base/threading/worker_pool.cc125
-rw-r--r--base/threading/worker_pool.h59
-rw-r--r--base/threading/worker_pool_posix.cc190
-rw-r--r--base/threading/worker_pool_posix.h90
-rw-r--r--base/threading/worker_pool_posix_unittest.cc250
-rw-r--r--base/threading/worker_pool_unittest.cc118
-rw-r--r--base/time/clock.cc2
-rw-r--r--base/time/clock.h2
-rw-r--r--base/time/default_clock.cc12
-rw-r--r--base/time/default_clock.h5
-rw-r--r--base/time/default_tick_clock.cc12
-rw-r--r--base/time/default_tick_clock.h6
-rw-r--r--base/time/pr_time_unittest.cc7
-rw-r--r--base/time/tick_clock.cc2
-rw-r--r--base/time/tick_clock.h2
-rw-r--r--base/time/time.cc117
-rw-r--r--base/time/time.h340
-rw-r--r--base/time/time_android.cc26
-rw-r--r--base/time/time_conversion_posix.cc67
-rw-r--r--base/time/time_exploded_posix.cc300
-rw-r--r--base/time/time_now_posix.cc123
-rw-r--r--base/time/time_override.cc45
-rw-r--r--base/time/time_override.h74
-rw-r--r--base/time/time_posix.cc429
-rw-r--r--base/time/time_to_iso8601.cc20
-rw-r--r--base/time/time_to_iso8601.h20
-rw-r--r--base/time/time_unittest.cc803
-rw-r--r--base/timer/elapsed_timer.cc8
-rw-r--r--base/timer/elapsed_timer.h3
-rw-r--r--base/timer/hi_res_timer_manager.h12
-rw-r--r--base/timer/hi_res_timer_manager_posix.cc7
-rw-r--r--base/timer/hi_res_timer_manager_unittest.cc5
-rw-r--r--base/timer/mock_timer.cc92
-rw-r--r--base/timer/mock_timer.h77
-rw-r--r--base/timer/mock_timer_unittest.cc17
-rw-r--r--base/timer/timer.cc165
-rw-r--r--base/timer/timer.h268
-rw-r--r--base/timer/timer_unittest.cc214
-rw-r--r--base/trace_event/common/trace_event_common.h45
-rw-r--r--base/trace_event/heap_profiler.h30
-rw-r--r--base/trace_event/trace_event.h91
-rw-r--r--base/tracked_objects.cc1085
-rw-r--r--base/tracked_objects.h898
-rw-r--r--base/tracked_objects_unittest.cc1375
-rw-r--r--base/tracking_info.cc28
-rw-r--r--base/tracking_info.h58
-rw-r--r--base/tuple.h67
-rw-r--r--base/tuple_unittest.cc35
-rw-r--r--base/unguessable_token.cc4
-rw-r--r--base/unguessable_token.h17
-rw-r--r--base/value_iterators.cc228
-rw-r--r--base/value_iterators.h194
-rw-r--r--base/value_iterators_unittest.cc335
-rw-r--r--base/values.cc807
-rw-r--r--base/values.h429
-rw-r--r--base/values_unittest.cc1560
-rw-r--r--base/version.cc9
-rw-r--r--base/version_unittest.cc31
-rw-r--r--base/vlog.cc4
-rw-r--r--build/android/gyp/util/build_utils.py226
-rw-r--r--build/android/pylib/constants/__init__.py145
-rw-r--r--build/android/pylib/constants/host_paths.py55
-rw-r--r--build/build_config.h51
-rw-r--r--build/gn_helpers.py2
-rw-r--r--components/timers/DEPS10
-rw-r--r--components/timers/alarm_timer_chromeos.cc112
-rw-r--r--components/timers/alarm_timer_chromeos.h54
-rw-r--r--crypto/apple_keychain.h70
-rw-r--r--crypto/ec_private_key.h3
-rw-r--r--crypto/hmac.cc18
-rw-r--r--crypto/hmac.h16
-rw-r--r--crypto/nss_crypto_module_delegate.h18
-rw-r--r--crypto/nss_util.cc317
-rw-r--r--crypto/nss_util.h31
-rw-r--r--crypto/nss_util_internal.h15
-rw-r--r--crypto/openssl_util.cc6
-rw-r--r--crypto/openssl_util.h7
-rw-r--r--crypto/p224.cc2
-rw-r--r--crypto/p224.h2
-rw-r--r--crypto/p224_spake.cc9
-rw-r--r--crypto/p224_spake.h5
-rw-r--r--crypto/rsa_private_key.cc7
-rw-r--r--crypto/scoped_test_nss_db.cc13
-rw-r--r--crypto/secure_hash.cc4
-rw-r--r--crypto/secure_hash.h3
-rw-r--r--crypto/secure_hash_unittest.cc25
-rw-r--r--crypto/sha2.cc6
-rw-r--r--crypto/sha2.h7
-rw-r--r--crypto/signature_creator_unittest.cc24
-rw-r--r--crypto/signature_verifier.h54
-rw-r--r--crypto/signature_verifier_unittest.cc1094
-rw-r--r--crypto/symmetric_key.cc5
-rw-r--r--crypto/symmetric_key.h12
-rw-r--r--crypto/symmetric_key_unittest.cc33
-rw-r--r--dbus/DEPS3
-rw-r--r--dbus/bus.cc194
-rw-r--r--dbus/bus.h15
-rw-r--r--dbus/exported_object.cc33
-rw-r--r--dbus/message.cc208
-rw-r--r--dbus/message.h41
-rw-r--r--dbus/mock_bus.cc3
-rw-r--r--dbus/mock_bus.h12
-rw-r--r--dbus/mock_exported_object.cc3
-rw-r--r--dbus/mock_exported_object.h2
-rw-r--r--dbus/mock_object_manager.cc18
-rw-r--r--dbus/mock_object_manager.h41
-rw-r--r--dbus/mock_object_proxy.cc32
-rw-r--r--dbus/mock_object_proxy.h87
-rw-r--r--dbus/object_manager.cc22
-rw-r--r--dbus/object_manager.h22
-rw-r--r--dbus/object_proxy.cc404
-rw-r--r--dbus/object_proxy.h137
-rw-r--r--dbus/property.cc89
-rw-r--r--dbus/property.h25
-rw-r--r--dbus/util.h7
-rw-r--r--dbus/values_util.cc60
-rw-r--r--device/bluetooth/bluetooth_advertisement.cc9
-rw-r--r--device/bluetooth/bluetooth_advertisement.h4
-rw-r--r--device/bluetooth/bluetooth_uuid.cc36
-rw-r--r--device/bluetooth/bluetooth_uuid.h18
-rw-r--r--device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.cc7
-rw-r--r--gen/mojo/common/common_custom_types__type_mappings193
-rw-r--r--ipc/ipc.mojom27
-rw-r--r--ipc/ipc_buildflags.h8
-rw-r--r--ipc/ipc_channel.cc51
-rw-r--r--ipc/ipc_channel.h266
-rw-r--r--ipc/ipc_channel_common.cc78
-rw-r--r--ipc/ipc_channel_factory.cc56
-rw-r--r--ipc/ipc_channel_factory.h38
-rw-r--r--ipc/ipc_channel_mojo.cc351
-rw-r--r--ipc/ipc_channel_mojo.h143
-rw-r--r--ipc/ipc_channel_mojo_unittest.cc1791
-rw-r--r--ipc/ipc_channel_nacl.cc396
-rw-r--r--ipc/ipc_channel_nacl.h114
-rw-r--r--ipc/ipc_channel_proxy.cc583
-rw-r--r--ipc/ipc_channel_proxy.h425
-rw-r--r--ipc/ipc_channel_proxy_unittest.cc437
-rw-r--r--ipc/ipc_channel_proxy_unittest_messages.h46
-rw-r--r--ipc/ipc_channel_reader.cc215
-rw-r--r--ipc/ipc_channel_reader.h167
-rw-r--r--ipc/ipc_channel_reader_unittest.cc249
-rw-r--r--ipc/ipc_cpu_perftest.cc417
-rw-r--r--ipc/ipc_export.h34
-rw-r--r--ipc/ipc_fuzzing_tests.cc364
-rw-r--r--ipc/ipc_listener.h8
-rw-r--r--ipc/ipc_logging.cc313
-rw-r--r--ipc/ipc_logging.h128
-rw-r--r--ipc/ipc_message.cc22
-rw-r--r--ipc/ipc_message.h25
-rw-r--r--ipc/ipc_message_attachment.cc145
-rw-r--r--ipc/ipc_message_attachment.h21
-rw-r--r--ipc/ipc_message_attachment_set.cc12
-rw-r--r--ipc/ipc_message_attachment_set.h8
-rw-r--r--ipc/ipc_message_attachment_set_posix_unittest.cc152
-rw-r--r--ipc/ipc_message_macros.h560
-rw-r--r--ipc/ipc_message_null_macros.h27
-rw-r--r--ipc/ipc_message_pipe_reader.cc129
-rw-r--r--ipc/ipc_message_pipe_reader.h114
-rw-r--r--ipc/ipc_message_protobuf_utils.h67
-rw-r--r--ipc/ipc_message_protobuf_utils_unittest.cc170
-rw-r--r--ipc/ipc_message_start.h55
-rw-r--r--ipc/ipc_message_support_export.h31
-rw-r--r--ipc/ipc_message_templates.h269
-rw-r--r--ipc/ipc_message_templates_impl.h112
-rw-r--r--ipc/ipc_message_unittest.cc281
-rw-r--r--ipc/ipc_message_utils.cc863
-rw-r--r--ipc/ipc_message_utils.h560
-rw-r--r--ipc/ipc_message_utils_unittest.cc240
-rw-r--r--ipc/ipc_mojo_bootstrap.cc1035
-rw-r--r--ipc/ipc_mojo_bootstrap.h65
-rw-r--r--ipc/ipc_mojo_bootstrap_unittest.cc207
-rw-r--r--ipc/ipc_mojo_handle_attachment.cc3
-rw-r--r--ipc/ipc_mojo_handle_attachment.h5
-rw-r--r--ipc/ipc_mojo_message_helper.cc5
-rw-r--r--ipc/ipc_mojo_message_helper.h4
-rw-r--r--ipc/ipc_mojo_param_traits.cc58
-rw-r--r--ipc/ipc_mojo_param_traits.h17
-rw-r--r--ipc/ipc_mojo_perftest.cc864
-rw-r--r--ipc/ipc_param_traits.h11
-rw-r--r--ipc/ipc_perftest_messages.cc7
-rw-r--r--ipc/ipc_perftest_messages.h16
-rw-r--r--ipc/ipc_perftest_util.cc145
-rw-r--r--ipc/ipc_perftest_util.h119
-rw-r--r--ipc/ipc_platform_file.cc76
-rw-r--r--ipc/ipc_platform_file.h86
-rw-r--r--ipc/ipc_platform_file_attachment_posix.cc3
-rw-r--r--ipc/ipc_platform_file_attachment_posix.h5
-rw-r--r--ipc/ipc_security_test_util.cc25
-rw-r--r--ipc/ipc_security_test_util.h40
-rw-r--r--ipc/ipc_send_fds_test.cc218
-rw-r--r--ipc/ipc_sender.h28
-rw-r--r--ipc/ipc_sync_channel.cc730
-rw-r--r--ipc/ipc_sync_channel.h263
-rw-r--r--ipc/ipc_sync_channel_unittest.cc1884
-rw-r--r--ipc/ipc_sync_message.cc116
-rw-r--r--ipc/ipc_sync_message.h7
-rw-r--r--ipc/ipc_sync_message_filter.cc198
-rw-r--r--ipc/ipc_sync_message_filter.h101
-rw-r--r--ipc/ipc_sync_message_unittest.cc317
-rw-r--r--ipc/ipc_sync_message_unittest.h120
-rw-r--r--ipc/ipc_test_base.cc80
-rw-r--r--ipc/ipc_test_base.h111
-rw-r--r--ipc/ipc_test_channel_listener.cc63
-rw-r--r--ipc/ipc_test_channel_listener.h46
-rw-r--r--ipc/ipc_test_message_generator.cc33
-rw-r--r--ipc/ipc_test_message_generator.h7
-rw-r--r--ipc/ipc_test_messages.h38
-rw-r--r--ipc/ipc_test_sink.cc86
-rw-r--r--ipc/ipc_test_sink.h141
-rw-r--r--ipc/message_filter.cc37
-rw-r--r--ipc/message_filter.h69
-rw-r--r--ipc/message_filter_router.cc96
-rw-r--r--ipc/message_filter_router.h42
-rw-r--r--ipc/message_mojom_traits.cc40
-rw-r--r--ipc/message_mojom_traits.h31
-rw-r--r--ipc/message_router.cc59
-rw-r--r--ipc/message_router.h74
-rw-r--r--ipc/message_view.cc30
-rw-r--r--ipc/message_view.h56
-rw-r--r--ipc/native_handle_type_converters.cc49
-rw-r--r--ipc/native_handle_type_converters.h30
-rw-r--r--ipc/param_traits_log_macros.h52
-rw-r--r--ipc/param_traits_macros.h65
-rw-r--r--ipc/param_traits_read_macros.h47
-rw-r--r--ipc/param_traits_write_macros.h40
-rw-r--r--ipc/run_all_perftests.cc25
-rw-r--r--ipc/run_all_unittests.cc35
-rw-r--r--ipc/struct_constructor_macros.h21
-rw-r--r--ipc/struct_destructor_macros.h17
-rw-r--r--ipc/sync_socket_unittest.cc306
-rwxr-xr-xlibchrome_tools/jni_registration_generator_helper.sh40
-rwxr-xr-xlibchrome_tools/mojom_source_generator.sh144
-rw-r--r--libchrome_tools/patch/580fcef.patch112
-rw-r--r--libchrome_tools/patch/8fbafc9.patch25
-rw-r--r--libchrome_tools/patch/ContextUtils.patch18
-rw-r--r--libchrome_tools/patch/ThreadLocalStorage-Add-a-function-to-destroy-pthread.patch (renamed from ThreadLocalStorage-Add-a-function-to-destroy-pthread.patch)0
-rw-r--r--libchrome_tools/patch/allocator_shim.patch2
-rw-r--r--libchrome_tools/patch/buildflag_header.patch95
-rw-r--r--libchrome_tools/patch/c7ce19d.patch28
-rw-r--r--libchrome_tools/patch/dmg_fp.patch74
-rw-r--r--libchrome_tools/patch/file_path_mojom.patch19
-rw-r--r--libchrome_tools/patch/file_posix.patch11
-rw-r--r--libchrome_tools/patch/handle_table.patch176
-rw-r--r--libchrome_tools/patch/hash.patch17
-rw-r--r--libchrome_tools/patch/jni_registration_generator.patch13
-rw-r--r--libchrome_tools/patch/lazy_instance.patch18
-rw-r--r--libchrome_tools/patch/logging.patch20
-rw-r--r--libchrome_tools/patch/macros.patch45
-rw-r--r--libchrome_tools/patch/memory_linux.patch17
-rw-r--r--libchrome_tools/patch/message_loop.patch32
-rw-r--r--libchrome_tools/patch/message_loop_unittest.patch125
-rw-r--r--libchrome_tools/patch/message_pump_for_ui.patch28
-rw-r--r--libchrome_tools/patch/mojo-Add-a-way-to-handle-unhandled-RuntimeExceptions.patch129
-rw-r--r--libchrome_tools/patch/mojo-Avoid-a-crash-when-NodeController-pending-invit.patch44
-rw-r--r--libchrome_tools/patch/mojo.patch507
-rw-r--r--libchrome_tools/patch/mojom_disable_trace_and_mem_dump.patch299
-rw-r--r--libchrome_tools/patch/observer_list_unittest.patch65
-rw-r--r--libchrome_tools/patch/path_service.patch13
-rw-r--r--libchrome_tools/patch/shared_memory_handle.patch22
-rw-r--r--libchrome_tools/patch/shared_memory_mapping.patch33
-rw-r--r--libchrome_tools/patch/shared_memory_posix.patch74
-rw-r--r--libchrome_tools/patch/ssl.patch46
-rw-r--r--libchrome_tools/patch/statfs_f_type.patch2
-rw-r--r--libchrome_tools/patch/subprocess.patch36
-rw-r--r--libchrome_tools/patch/task_annotator.patch12
-rw-r--r--libchrome_tools/patch/task_scheduler.patch121
-rw-r--r--libchrome_tools/patch/time.patch19
-rw-r--r--libchrome_tools/patch/trace_event.patch28
-rw-r--r--libchrome_tools/patch/values.patch63
-rw-r--r--libchrome_tools/update_libchrome.py20
-rw-r--r--mojo/BUILD.gn50
-rw-r--r--mojo/DEPS7
-rw-r--r--mojo/README.md135
-rw-r--r--mojo/android/BUILD.gn155
-rw-r--r--mojo/android/DEPS3
-rw-r--r--mojo/android/javatests/AndroidManifest.xml29
-rw-r--r--mojo/android/javatests/DEPS4
-rw-r--r--mojo/android/javatests/init_library.cc50
-rw-r--r--mojo/android/javatests/mojo_test_case.cc70
-rw-r--r--mojo/android/javatests/mojo_test_case.h20
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/HandleMock.java226
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/MojoTestCase.java66
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/TestUtils.java32
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java55
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTest.java241
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java108
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java211
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java59
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java108
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java104
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java129
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java284
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java69
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java109
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java231
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/bindings/SerializationTest.java175
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTest.java237
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java68
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java151
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java31
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java545
-rw-r--r--mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java255
-rw-r--r--mojo/android/javatests/validation_test_util.cc54
-rw-r--r--mojo/android/javatests/validation_test_util.h20
-rw-r--r--mojo/android/system/base_run_loop.cc82
-rw-r--r--mojo/android/system/base_run_loop.h20
-rw-r--r--mojo/android/system/core_impl.cc310
-rw-r--r--mojo/android/system/core_impl.h20
-rw-r--r--mojo/android/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java74
-rw-r--r--mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java522
-rw-r--r--mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java72
-rw-r--r--mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java64
-rw-r--r--mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java140
-rw-r--r--mojo/android/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java58
-rw-r--r--mojo/android/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java62
-rw-r--r--mojo/android/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java72
-rw-r--r--mojo/android/system/watcher_impl.cc109
-rw-r--r--mojo/android/system/watcher_impl.h20
-rw-r--r--mojo/common/BUILD.gn93
-rw-r--r--mojo/common/DEPS6
-rw-r--r--mojo/common/common_custom_types_struct_traits.cc108
-rw-r--r--mojo/common/common_custom_types_struct_traits.h85
-rw-r--r--mojo/common/common_custom_types_unittest.cc433
-rw-r--r--mojo/common/data_pipe_drainer.cc50
-rw-r--r--mojo/common/data_pipe_drainer.h49
-rw-r--r--mojo/common/data_pipe_utils.cc96
-rw-r--r--mojo/common/data_pipe_utils.h32
-rw-r--r--mojo/common/file.mojom10
-rw-r--r--mojo/common/file.typemap13
-rw-r--r--mojo/common/file_path.mojom8
-rw-r--r--mojo/common/file_path.typemap12
-rw-r--r--mojo/common/mojo_common_export.h32
-rw-r--r--mojo/common/string16.mojom12
-rw-r--r--mojo/common/string16.typemap12
-rw-r--r--mojo/common/struct_traits_unittest.cc57
-rw-r--r--mojo/common/test_common_custom_types.mojom61
-rw-r--r--mojo/common/text_direction.mojom12
-rw-r--r--mojo/common/text_direction.typemap12
-rw-r--r--mojo/common/time.mojom21
-rw-r--r--mojo/common/time.typemap20
-rw-r--r--mojo/common/time_struct_traits.h55
-rw-r--r--mojo/common/traits_test_service.mojom14
-rw-r--r--mojo/common/typemaps.gni14
-rw-r--r--mojo/common/unguessable_token.mojom11
-rw-r--r--mojo/common/unguessable_token.typemap12
-rw-r--r--mojo/common/values.mojom32
-rw-r--r--mojo/common/values.typemap25
-rw-r--r--mojo/common/values_struct_traits.cc109
-rw-r--r--mojo/common/values_struct_traits.h257
-rw-r--r--mojo/common/version.mojom10
-rw-r--r--mojo/common/version.typemap12
-rw-r--r--mojo/core/BUILD.gn325
-rw-r--r--mojo/core/atomic_flag.h53
-rw-r--r--mojo/core/broker.h55
-rw-r--r--mojo/core/broker_host.cc180
-rw-r--r--mojo/core/broker_host.h73
-rw-r--r--mojo/core/broker_messages.h97
-rw-r--r--mojo/core/broker_posix.cc148
-rw-r--r--mojo/core/broker_win.cc162
-rw-r--r--mojo/core/channel.cc738
-rw-r--r--mojo/core/channel.h370
-rw-r--r--mojo/core/channel_fuchsia.cc466
-rw-r--r--mojo/core/channel_posix.cc768
-rw-r--r--mojo/core/channel_unittest.cc278
-rw-r--r--mojo/core/channel_win.cc377
-rw-r--r--mojo/core/configuration.cc15
-rw-r--r--mojo/core/configuration.h25
-rw-r--r--mojo/core/connection_params.cc31
-rw-r--r--mojo/core/connection_params.h49
-rw-r--r--mojo/core/core.cc1513
-rw-r--r--mojo/core/core.h385
-rw-r--r--mojo/core/core_test_base.cc250
-rw-r--r--mojo/core/core_test_base.h93
-rw-r--r--mojo/core/core_unittest.cc459
-rw-r--r--mojo/core/data_pipe_consumer_dispatcher.cc594
-rw-r--r--mojo/core/data_pipe_consumer_dispatcher.h128
-rw-r--r--mojo/core/data_pipe_control_message.cc37
-rw-r--r--mojo/core/data_pipe_control_message.h42
-rw-r--r--mojo/core/data_pipe_producer_dispatcher.cc538
-rw-r--r--mojo/core/data_pipe_producer_dispatcher.h119
-rw-r--r--mojo/core/data_pipe_unittest.cc2047
-rw-r--r--mojo/core/dispatcher.cc198
-rw-r--r--mojo/core/dispatcher.h233
-rw-r--r--mojo/core/embedder/BUILD.gn29
-rw-r--r--mojo/core/embedder/README.md86
-rw-r--r--mojo/core/embedder/configuration.h47
-rw-r--r--mojo/core/embedder/embedder.cc50
-rw-r--r--mojo/core/embedder/embedder.h65
-rw-r--r--mojo/core/embedder/process_error_callback.h21
-rw-r--r--mojo/core/embedder/scoped_ipc_support.cc48
-rw-r--r--mojo/core/embedder/scoped_ipc_support.h118
-rw-r--r--mojo/core/embedder_unittest.cc429
-rw-r--r--mojo/core/entrypoints.cc414
-rw-r--r--mojo/core/entrypoints.h25
-rw-r--r--mojo/core/export_only_thunks_api.lst12
-rw-r--r--mojo/core/handle_signals_state.h111
-rw-r--r--mojo/core/handle_table.cc204
-rw-r--r--mojo/core/handle_table.h89
-rw-r--r--mojo/core/handle_table_unittest.cc73
-rw-r--r--mojo/core/invitation_dispatcher.cc78
-rw-r--r--mojo/core/invitation_dispatcher.h49
-rw-r--r--mojo/core/invitation_unittest.cc874
-rw-r--r--mojo/core/mach_port_relay.cc200
-rw-r--r--mojo/core/mach_port_relay.h90
-rw-r--r--mojo/core/message_pipe_dispatcher.cc426
-rw-r--r--mojo/core/message_pipe_dispatcher.h116
-rw-r--r--mojo/core/message_pipe_perftest.cc161
-rw-r--r--mojo/core/message_pipe_unittest.cc556
-rw-r--r--mojo/core/message_unittest.cc956
-rw-r--r--mojo/core/mojo_core.cc42
-rw-r--r--mojo/core/mojo_core.def10
-rw-r--r--mojo/core/mojo_core_unittest.cc39
-rw-r--r--mojo/core/multiprocess_message_pipe_unittest.cc1339
-rw-r--r--mojo/core/node_channel.cc731
-rw-r--r--mojo/core/node_channel.h188
-rw-r--r--mojo/core/node_controller.cc1272
-rw-r--r--mojo/core/node_controller.h346
-rw-r--r--mojo/core/options_validation.h97
-rw-r--r--mojo/core/options_validation_unittest.cc134
-rw-r--r--mojo/core/platform_handle_dispatcher.cc96
-rw-r--r--mojo/core/platform_handle_dispatcher.h61
-rw-r--r--mojo/core/platform_handle_dispatcher_unittest.cc125
-rw-r--r--mojo/core/platform_handle_in_transit.cc156
-rw-r--r--mojo/core/platform_handle_in_transit.h107
-rw-r--r--mojo/core/platform_handle_utils.cc67
-rw-r--r--mojo/core/platform_handle_utils.h35
-rw-r--r--mojo/core/platform_shared_memory_mapping.cc104
-rw-r--r--mojo/core/platform_shared_memory_mapping.h60
-rw-r--r--mojo/core/platform_wrapper_unittest.cc249
-rw-r--r--mojo/core/ports/BUILD.gn59
-rw-r--r--mojo/core/ports/event.cc383
-rw-r--r--mojo/core/ports/event.h284
-rw-r--r--mojo/core/ports/message_filter.h29
-rw-r--r--mojo/core/ports/message_queue.cc82
-rw-r--r--mojo/core/ports/message_queue.h86
-rw-r--r--mojo/core/ports/name.cc26
-rw-r--r--mojo/core/ports/name.h76
-rw-r--r--mojo/core/ports/name_unittest.cc75
-rw-r--r--mojo/core/ports/node.cc1388
-rw-r--r--mojo/core/ports/node.h250
-rw-r--r--mojo/core/ports/node_delegate.h38
-rw-r--r--mojo/core/ports/port.cc24
-rw-r--r--mojo/core/ports/port.h175
-rw-r--r--mojo/core/ports/port_locker.cc74
-rw-r--r--mojo/core/ports/port_locker.h86
-rw-r--r--mojo/core/ports/port_ref.cc30
-rw-r--r--mojo/core/ports/port_ref.h49
-rw-r--r--mojo/core/ports/ports_unittest.cc1644
-rw-r--r--mojo/core/ports/user_data.h25
-rw-r--r--mojo/core/ports/user_message.cc25
-rw-r--r--mojo/core/ports/user_message.h56
-rw-r--r--mojo/core/quota_unittest.cc314
-rw-r--r--mojo/core/request_context.cc116
-rw-r--r--mojo/core/request_context.h108
-rw-r--r--mojo/core/run_all_core_unittests.cc18
-rw-r--r--mojo/core/scoped_process_handle.cc90
-rw-r--r--mojo/core/scoped_process_handle.h65
-rw-r--r--mojo/core/shared_buffer_dispatcher.cc445
-rw-r--r--mojo/core/shared_buffer_dispatcher.h124
-rw-r--r--mojo/core/shared_buffer_dispatcher_unittest.cc340
-rw-r--r--mojo/core/shared_buffer_unittest.cc323
-rw-r--r--mojo/core/signals_unittest.cc222
-rw-r--r--mojo/core/system_impl_export.h33
-rw-r--r--mojo/core/test/BUILD.gn88
-rw-r--r--mojo/core/test/mojo_test_base.cc308
-rw-r--r--mojo/core/test/mojo_test_base.h232
-rw-r--r--mojo/core/test/multiprocess_test_helper.cc276
-rw-r--r--mojo/core/test/multiprocess_test_helper.h102
-rw-r--r--mojo/core/test/run_all_perftests.cc26
-rw-r--r--mojo/core/test/run_all_unittests.cc50
-rw-r--r--mojo/core/test/test_support_impl.cc82
-rw-r--r--mojo/core/test/test_support_impl.h38
-rw-r--r--mojo/core/test/test_utils.cc33
-rw-r--r--mojo/core/test/test_utils.h30
-rw-r--r--mojo/core/test/test_utils_win.cc49
-rw-r--r--mojo/core/test_utils.cc74
-rw-r--r--mojo/core/test_utils.h59
-rw-r--r--mojo/core/trap_unittest.cc1763
-rw-r--r--mojo/core/user_message_impl.cc686
-rw-r--r--mojo/core/user_message_impl.h218
-rw-r--r--mojo/core/watch.cc90
-rw-r--r--mojo/core/watch.h127
-rw-r--r--mojo/core/watcher_dispatcher.cc263
-rw-r--r--mojo/core/watcher_dispatcher.h102
-rw-r--r--mojo/core/watcher_set.cc82
-rw-r--r--mojo/core/watcher_set.h70
-rw-r--r--mojo/edk/DEPS14
-rw-r--r--mojo/edk/embedder/BUILD.gn147
-rw-r--r--mojo/edk/embedder/README.md346
-rw-r--r--mojo/edk/embedder/configuration.h65
-rw-r--r--mojo/edk/embedder/connection_params.cc28
-rw-r--r--mojo/edk/embedder/connection_params.h34
-rw-r--r--mojo/edk/embedder/embedder.cc158
-rw-r--r--mojo/edk/embedder/embedder.h173
-rw-r--r--mojo/edk/embedder/embedder_internal.h44
-rw-r--r--mojo/edk/embedder/embedder_unittest.cc603
-rw-r--r--mojo/edk/embedder/entrypoints.cc283
-rw-r--r--mojo/edk/embedder/entrypoints.h22
-rw-r--r--mojo/edk/embedder/named_platform_channel_pair.h73
-rw-r--r--mojo/edk/embedder/named_platform_channel_pair_win.cc89
-rw-r--r--mojo/edk/embedder/named_platform_handle.h51
-rw-r--r--mojo/edk/embedder/named_platform_handle_utils.h56
-rw-r--r--mojo/edk/embedder/named_platform_handle_utils_posix.cc141
-rw-r--r--mojo/edk/embedder/named_platform_handle_utils_win.cc95
-rw-r--r--mojo/edk/embedder/pending_process_connection.cc50
-rw-r--r--mojo/edk/embedder/pending_process_connection.h124
-rw-r--r--mojo/edk/embedder/platform_channel_pair.cc34
-rw-r--r--mojo/edk/embedder/platform_channel_pair.h106
-rw-r--r--mojo/edk/embedder/platform_channel_pair_posix.cc172
-rw-r--r--mojo/edk/embedder/platform_channel_pair_posix_unittest.cc261
-rw-r--r--mojo/edk/embedder/platform_channel_pair_win.cc123
-rw-r--r--mojo/edk/embedder/platform_channel_utils_posix.cc282
-rw-r--r--mojo/edk/embedder/platform_channel_utils_posix.h87
-rw-r--r--mojo/edk/embedder/platform_handle.cc74
-rw-r--r--mojo/edk/embedder/platform_handle.h90
-rw-r--r--mojo/edk/embedder/platform_handle_utils.h33
-rw-r--r--mojo/edk/embedder/platform_handle_utils_posix.cc24
-rw-r--r--mojo/edk/embedder/platform_handle_utils_win.cc28
-rw-r--r--mojo/edk/embedder/platform_handle_vector.h35
-rw-r--r--mojo/edk/embedder/platform_shared_buffer.cc325
-rw-r--r--mojo/edk/embedder/platform_shared_buffer.h178
-rw-r--r--mojo/edk/embedder/platform_shared_buffer_unittest.cc227
-rw-r--r--mojo/edk/embedder/scoped_ipc_support.cc39
-rw-r--r--mojo/edk/embedder/scoped_ipc_support.h118
-rw-r--r--mojo/edk/embedder/scoped_platform_handle.h63
-rw-r--r--mojo/edk/embedder/test_embedder.cc46
-rw-r--r--mojo/edk/embedder/test_embedder.h28
-rw-r--r--mojo/edk/js/BUILD.gn35
-rw-r--r--mojo/edk/js/core.cc454
-rw-r--r--mojo/edk/js/core.h25
-rw-r--r--mojo/edk/js/drain_data.cc129
-rw-r--r--mojo/edk/js/drain_data.h65
-rw-r--r--mojo/edk/js/handle.cc85
-rw-r--r--mojo/edk/js/handle.h107
-rw-r--r--mojo/edk/js/handle_close_observer.h24
-rw-r--r--mojo/edk/js/handle_unittest.cc92
-rw-r--r--mojo/edk/js/js_export.h32
-rw-r--r--mojo/edk/js/mojo_runner_delegate.cc80
-rw-r--r--mojo/edk/js/mojo_runner_delegate.h36
-rw-r--r--mojo/edk/js/support.cc77
-rw-r--r--mojo/edk/js/support.h25
-rw-r--r--mojo/edk/js/tests/BUILD.gn68
-rw-r--r--mojo/edk/js/tests/js_to_cpp.mojom54
-rw-r--r--mojo/edk/js/tests/js_to_cpp_tests.cc455
-rw-r--r--mojo/edk/js/tests/js_to_cpp_tests.js223
-rw-r--r--mojo/edk/js/tests/run_js_unittests.cc61
-rw-r--r--mojo/edk/js/threading.cc49
-rw-r--r--mojo/edk/js/threading.h28
-rw-r--r--mojo/edk/js/waiting_callback.cc95
-rw-r--r--mojo/edk/js/waiting_callback.h67
-rw-r--r--mojo/edk/system/BUILD.gn205
-rw-r--r--mojo/edk/system/atomic_flag.h57
-rw-r--r--mojo/edk/system/broker.h52
-rw-r--r--mojo/edk/system/broker_host.cc153
-rw-r--r--mojo/edk/system/broker_host.h64
-rw-r--r--mojo/edk/system/broker_messages.h80
-rw-r--r--mojo/edk/system/broker_posix.cc125
-rw-r--r--mojo/edk/system/broker_win.cc155
-rw-r--r--mojo/edk/system/channel.cc683
-rw-r--r--mojo/edk/system/channel.h303
-rw-r--r--mojo/edk/system/channel_posix.cc572
-rw-r--r--mojo/edk/system/channel_unittest.cc177
-rw-r--r--mojo/edk/system/channel_win.cc360
-rw-r--r--mojo/edk/system/configuration.cc25
-rw-r--r--mojo/edk/system/configuration.h29
-rw-r--r--mojo/edk/system/core.cc1019
-rw-r--r--mojo/edk/system/core.h297
-rw-r--r--mojo/edk/system/core_test_base.cc272
-rw-r--r--mojo/edk/system/core_test_base.h94
-rw-r--r--mojo/edk/system/core_unittest.cc971
-rw-r--r--mojo/edk/system/data_pipe_consumer_dispatcher.cc562
-rw-r--r--mojo/edk/system/data_pipe_consumer_dispatcher.h123
-rw-r--r--mojo/edk/system/data_pipe_control_message.cc35
-rw-r--r--mojo/edk/system/data_pipe_control_message.h43
-rw-r--r--mojo/edk/system/data_pipe_producer_dispatcher.cc507
-rw-r--r--mojo/edk/system/data_pipe_producer_dispatcher.h123
-rw-r--r--mojo/edk/system/data_pipe_unittest.cc2034
-rw-r--r--mojo/edk/system/dispatcher.cc198
-rw-r--r--mojo/edk/system/dispatcher.h245
-rw-r--r--mojo/edk/system/handle_signals_state.h13
-rw-r--r--mojo/edk/system/handle_table.cc135
-rw-r--r--mojo/edk/system/handle_table.h75
-rw-r--r--mojo/edk/system/mach_port_relay.cc248
-rw-r--r--mojo/edk/system/mach_port_relay.h94
-rw-r--r--mojo/edk/system/mapping_table.cc48
-rw-r--r--mojo/edk/system/mapping_table.h57
-rw-r--r--mojo/edk/system/message_for_transit.cc136
-rw-r--r--mojo/edk/system/message_for_transit.h115
-rw-r--r--mojo/edk/system/message_pipe_dispatcher.cc554
-rw-r--r--mojo/edk/system/message_pipe_dispatcher.h115
-rw-r--r--mojo/edk/system/message_pipe_perftest.cc167
-rw-r--r--mojo/edk/system/message_pipe_unittest.cc699
-rw-r--r--mojo/edk/system/multiprocess_message_pipe_unittest.cc1366
-rw-r--r--mojo/edk/system/node_channel.cc905
-rw-r--r--mojo/edk/system/node_channel.h219
-rw-r--r--mojo/edk/system/node_controller.cc1475
-rw-r--r--mojo/edk/system/node_controller.h378
-rw-r--r--mojo/edk/system/options_validation.h97
-rw-r--r--mojo/edk/system/options_validation_unittest.cc134
-rw-r--r--mojo/edk/system/platform_handle_dispatcher.cc104
-rw-r--r--mojo/edk/system/platform_handle_dispatcher.h61
-rw-r--r--mojo/edk/system/platform_handle_dispatcher_unittest.cc123
-rw-r--r--mojo/edk/system/platform_wrapper_unittest.cc212
-rw-r--r--mojo/edk/system/ports/BUILD.gn46
-rw-r--r--mojo/edk/system/ports/event.cc46
-rw-r--r--mojo/edk/system/ports/event.h111
-rw-r--r--mojo/edk/system/ports/message.cc100
-rw-r--r--mojo/edk/system/ports/message.h93
-rw-r--r--mojo/edk/system/ports/message_filter.h29
-rw-r--r--mojo/edk/system/ports/message_queue.cc87
-rw-r--r--mojo/edk/system/ports/message_queue.h73
-rw-r--r--mojo/edk/system/ports/name.cc26
-rw-r--r--mojo/edk/system/ports/name.h74
-rw-r--r--mojo/edk/system/ports/node.cc1385
-rw-r--r--mojo/edk/system/ports/node.h228
-rw-r--r--mojo/edk/system/ports/node_delegate.h48
-rw-r--r--mojo/edk/system/ports/port.cc24
-rw-r--r--mojo/edk/system/ports/port.h60
-rw-r--r--mojo/edk/system/ports/port_ref.cc36
-rw-r--r--mojo/edk/system/ports/port_ref.h41
-rw-r--r--mojo/edk/system/ports/ports_unittest.cc1478
-rw-r--r--mojo/edk/system/ports/user_data.h25
-rw-r--r--mojo/edk/system/ports_message.cc62
-rw-r--r--mojo/edk/system/ports_message.h69
-rw-r--r--mojo/edk/system/request_context.cc110
-rw-r--r--mojo/edk/system/request_context.h107
-rw-r--r--mojo/edk/system/shared_buffer_dispatcher.cc339
-rw-r--r--mojo/edk/system/shared_buffer_dispatcher.h127
-rw-r--r--mojo/edk/system/shared_buffer_dispatcher_unittest.cc312
-rw-r--r--mojo/edk/system/shared_buffer_unittest.cc318
-rw-r--r--mojo/edk/system/signals_unittest.cc76
-rw-r--r--mojo/edk/system/system_impl_export.h29
-rw-r--r--mojo/edk/system/test_utils.cc76
-rw-r--r--mojo/edk/system/test_utils.h59
-rw-r--r--mojo/edk/system/watch.cc83
-rw-r--r--mojo/edk/system/watch.h124
-rw-r--r--mojo/edk/system/watcher_dispatcher.cc232
-rw-r--r--mojo/edk/system/watcher_dispatcher.h101
-rw-r--r--mojo/edk/system/watcher_set.cc82
-rw-r--r--mojo/edk/system/watcher_set.h71
-rw-r--r--mojo/edk/system/watcher_unittest.cc1637
-rw-r--r--mojo/edk/test/BUILD.gn131
-rw-r--r--mojo/edk/test/mojo_test_base.cc327
-rw-r--r--mojo/edk/test/mojo_test_base.h249
-rw-r--r--mojo/edk/test/multiprocess_test_helper.cc263
-rw-r--r--mojo/edk/test/multiprocess_test_helper.h110
-rw-r--r--mojo/edk/test/multiprocess_test_helper_unittest.cc165
-rw-r--r--mojo/edk/test/run_all_perftests.cc26
-rw-r--r--mojo/edk/test/run_all_unittests.cc49
-rw-r--r--mojo/edk/test/test_support_impl.cc84
-rw-r--r--mojo/edk/test/test_support_impl.h38
-rw-r--r--mojo/edk/test/test_utils.h55
-rw-r--r--mojo/edk/test/test_utils_posix.cc94
-rw-r--r--mojo/edk/test/test_utils_win.cc115
-rw-r--r--mojo/public/BUILD.gn7
-rw-r--r--mojo/public/DEPS11
-rw-r--r--mojo/public/c/system/BUILD.gn35
-rw-r--r--mojo/public/c/system/README.md507
-rw-r--r--mojo/public/c/system/buffer.h210
-rw-r--r--mojo/public/c/system/core.h4
-rw-r--r--mojo/public/c/system/data_pipe.h325
-rw-r--r--mojo/public/c/system/functions.h33
-rw-r--r--mojo/public/c/system/invitation.h456
-rw-r--r--mojo/public/c/system/macros.h9
-rw-r--r--mojo/public/c/system/message_pipe.h650
-rw-r--r--mojo/public/c/system/platform_handle.h277
-rw-r--r--mojo/public/c/system/quota.h126
-rw-r--r--mojo/public/c/system/set_thunks_for_app.cc20
-rw-r--r--mojo/public/c/system/tests/BUILD.gn4
-rw-r--r--mojo/public/c/system/tests/core_api_unittest.cc327
-rw-r--r--mojo/public/c/system/tests/core_perftest.cc46
-rw-r--r--mojo/public/c/system/tests/core_unittest.cc322
-rw-r--r--mojo/public/c/system/tests/core_unittest_pure_c.c23
-rw-r--r--mojo/public/c/system/thunks.cc533
-rw-r--r--mojo/public/c/system/thunks.h242
-rw-r--r--mojo/public/c/system/trap.h322
-rw-r--r--mojo/public/c/system/types.h76
-rw-r--r--mojo/public/c/system/watcher.h184
-rw-r--r--mojo/public/cpp/base/BUILD.gn85
-rw-r--r--mojo/public/cpp/base/README.md5
-rw-r--r--mojo/public/cpp/base/big_buffer.cc155
-rw-r--r--mojo/public/cpp/base/big_buffer.h179
-rw-r--r--mojo/public/cpp/base/big_buffer.typemap12
-rw-r--r--mojo/public/cpp/base/big_buffer_mojom_traits.cc146
-rw-r--r--mojo/public/cpp/base/big_buffer_mojom_traits.h64
-rw-r--r--mojo/public/cpp/base/big_buffer_unittest.cc69
-rw-r--r--mojo/public/cpp/base/big_string.typemap17
-rw-r--r--mojo/public/cpp/base/big_string_mojom_traits.cc33
-rw-r--r--mojo/public/cpp/base/big_string_mojom_traits.h27
-rw-r--r--mojo/public/cpp/base/big_string_unittest.cc43
-rw-r--r--mojo/public/cpp/base/file.typemap14
-rw-r--r--mojo/public/cpp/base/file_error.typemap12
-rw-r--r--mojo/public/cpp/base/file_error_mojom_traits.h120
-rw-r--r--mojo/public/cpp/base/file_info.typemap13
-rw-r--r--mojo/public/cpp/base/file_info_mojom_traits.cc28
-rw-r--r--mojo/public/cpp/base/file_info_mojom_traits.h49
-rw-r--r--mojo/public/cpp/base/file_mojom_traits.cc30
-rw-r--r--mojo/public/cpp/base/file_mojom_traits.h28
-rw-r--r--mojo/public/cpp/base/file_path.typemap12
-rw-r--r--mojo/public/cpp/base/file_path_mojom_traits.cc28
-rw-r--r--mojo/public/cpp/base/file_path_mojom_traits.h38
-rw-r--r--mojo/public/cpp/base/file_path_unittest.cc24
-rw-r--r--mojo/public/cpp/base/file_unittest.cc73
-rw-r--r--mojo/public/cpp/base/logfont_win.typemap18
-rw-r--r--mojo/public/cpp/base/logfont_win_mojom_traits.cc39
-rw-r--r--mojo/public/cpp/base/logfont_win_mojom_traits.h30
-rw-r--r--mojo/public/cpp/base/memory_allocator_dump_cross_process_uid.typemap13
-rw-r--r--mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_mojom_traits.cc22
-rw-r--r--mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_mojom_traits.h29
-rw-r--r--mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_unittest.cc42
-rw-r--r--mojo/public/cpp/base/process_id.typemap14
-rw-r--r--mojo/public/cpp/base/process_id_mojom_traits.cc16
-rw-r--r--mojo/public/cpp/base/process_id_mojom_traits.h27
-rw-r--r--mojo/public/cpp/base/process_id_unittest.cc23
-rw-r--r--mojo/public/cpp/base/read_only_buffer.typemap13
-rw-r--r--mojo/public/cpp/base/read_only_buffer_mojom_traits.cc23
-rw-r--r--mojo/public/cpp/base/read_only_buffer_mojom_traits.h26
-rw-r--r--mojo/public/cpp/base/read_only_buffer_unittest.cc37
-rw-r--r--mojo/public/cpp/base/ref_counted_memory.typemap17
-rw-r--r--mojo/public/cpp/base/ref_counted_memory_mojom_traits.cc46
-rw-r--r--mojo/public/cpp/base/ref_counted_memory_mojom_traits.h34
-rw-r--r--mojo/public/cpp/base/ref_counted_memory_unittest.cc40
-rw-r--r--mojo/public/cpp/base/shared_memory.typemap24
-rw-r--r--mojo/public/cpp/base/shared_memory_mojom_traits.cc105
-rw-r--r--mojo/public/cpp/base/shared_memory_mojom_traits.h56
-rw-r--r--mojo/public/cpp/base/shared_memory_unittest.cc53
-rw-r--r--mojo/public/cpp/base/string16.typemap19
-rw-r--r--mojo/public/cpp/base/string16_mojom_traits.cc44
-rw-r--r--mojo/public/cpp/base/string16_mojom_traits.h51
-rw-r--r--mojo/public/cpp/base/string16_unittest.cc60
-rw-r--r--mojo/public/cpp/base/text_direction.typemap15
-rw-r--r--mojo/public/cpp/base/text_direction_mojom_traits.cc43
-rw-r--r--mojo/public/cpp/base/text_direction_mojom_traits.h25
-rw-r--r--mojo/public/cpp/base/text_direction_unittest.cc31
-rw-r--r--mojo/public/cpp/base/thread_priority.typemap13
-rw-r--r--mojo/public/cpp/base/thread_priority_mojom_traits.cc48
-rw-r--r--mojo/public/cpp/base/thread_priority_mojom_traits.h24
-rw-r--r--mojo/public/cpp/base/thread_priority_unittest.cc31
-rw-r--r--mojo/public/cpp/base/time.typemap17
-rw-r--r--mojo/public/cpp/base/time_mojom_traits.cc49
-rw-r--r--mojo/public/cpp/base/time_mojom_traits.h42
-rw-r--r--mojo/public/cpp/base/time_unittest.cc38
-rw-r--r--mojo/public/cpp/base/typemaps.gni24
-rw-r--r--mojo/public/cpp/base/unguessable_token.typemap13
-rw-r--r--mojo/public/cpp/base/unguessable_token_mojom_traits.cc25
-rw-r--r--mojo/public/cpp/base/unguessable_token_mojom_traits.h37
-rw-r--r--mojo/public/cpp/base/unguessable_token_unittest.cc23
-rw-r--r--mojo/public/cpp/base/values.typemap16
-rw-r--r--mojo/public/cpp/base/values_mojom_traits.cc94
-rw-r--r--mojo/public/cpp/base/values_mojom_traits.h130
-rw-r--r--mojo/public/cpp/base/values_unittest.cc160
-rw-r--r--mojo/public/cpp/bindings/BUILD.gn208
-rw-r--r--mojo/public/cpp/bindings/DEPS3
-rw-r--r--mojo/public/cpp/bindings/README.md577
-rw-r--r--mojo/public/cpp/bindings/array_traits.h10
-rw-r--r--mojo/public/cpp/bindings/array_traits_carray.h76
-rw-r--r--mojo/public/cpp/bindings/array_traits_span.h47
-rw-r--r--mojo/public/cpp/bindings/array_traits_wtf_vector.h31
-rw-r--r--mojo/public/cpp/bindings/associated_binding.h66
-rw-r--r--mojo/public/cpp/bindings/associated_group.h4
-rw-r--r--mojo/public/cpp/bindings/associated_group_controller.h16
-rw-r--r--mojo/public/cpp/bindings/associated_interface_ptr.h83
-rw-r--r--mojo/public/cpp/bindings/associated_interface_ptr_info.h2
-rw-r--r--mojo/public/cpp/bindings/associated_interface_request.h22
-rw-r--r--mojo/public/cpp/bindings/binding.h133
-rw-r--r--mojo/public/cpp/bindings/binding_set.h120
-rw-r--r--mojo/public/cpp/bindings/callback_helpers.h124
-rw-r--r--mojo/public/cpp/bindings/clone_traits.h8
-rw-r--r--mojo/public/cpp/bindings/connection_error_callback.h11
-rw-r--r--mojo/public/cpp/bindings/connector.h109
-rw-r--r--mojo/public/cpp/bindings/enum_traits.h8
-rw-r--r--mojo/public/cpp/bindings/equals_traits.h98
-rw-r--r--mojo/public/cpp/bindings/filter_chain.h5
-rw-r--r--mojo/public/cpp/bindings/interface_endpoint_client.h34
-rw-r--r--mojo/public/cpp/bindings/interface_endpoint_controller.h4
-rw-r--r--mojo/public/cpp/bindings/interface_id.h4
-rw-r--r--mojo/public/cpp/bindings/interface_ptr.h85
-rw-r--r--mojo/public/cpp/bindings/interface_ptr_info.h7
-rw-r--r--mojo/public/cpp/bindings/interface_ptr_set.h63
-rw-r--r--mojo/public/cpp/bindings/interface_request.h50
-rw-r--r--mojo/public/cpp/bindings/lib/array_internal.h57
-rw-r--r--mojo/public/cpp/bindings/lib/array_serialization.h156
-rw-r--r--mojo/public/cpp/bindings/lib/associated_binding.cc16
-rw-r--r--mojo/public/cpp/bindings/lib/associated_interface_ptr.cc8
-rw-r--r--mojo/public/cpp/bindings/lib/associated_interface_ptr_state.cc81
-rw-r--r--mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h143
-rw-r--r--mojo/public/cpp/bindings/lib/binding_state.cc32
-rw-r--r--mojo/public/cpp/bindings/lib/binding_state.h28
-rw-r--r--mojo/public/cpp/bindings/lib/bindings_internal.h25
-rw-r--r--mojo/public/cpp/bindings/lib/buffer.cc136
-rw-r--r--mojo/public/cpp/bindings/lib/buffer.h129
-rw-r--r--mojo/public/cpp/bindings/lib/connector.cc173
-rw-r--r--mojo/public/cpp/bindings/lib/control_message_handler.cc22
-rw-r--r--mojo/public/cpp/bindings/lib/control_message_handler.h2
-rw-r--r--mojo/public/cpp/bindings/lib/control_message_proxy.cc43
-rw-r--r--mojo/public/cpp/bindings/lib/equals_traits.h94
-rw-r--r--mojo/public/cpp/bindings/lib/fixed_buffer.cc16
-rw-r--r--mojo/public/cpp/bindings/lib/fixed_buffer.h15
-rw-r--r--mojo/public/cpp/bindings/lib/handle_interface_serialization.h181
-rw-r--r--mojo/public/cpp/bindings/lib/handle_serialization.h35
-rw-r--r--mojo/public/cpp/bindings/lib/interface_endpoint_client.cc91
-rw-r--r--mojo/public/cpp/bindings/lib/interface_ptr_state.cc94
-rw-r--r--mojo/public/cpp/bindings/lib/interface_ptr_state.h214
-rw-r--r--mojo/public/cpp/bindings/lib/interface_serialization.h139
-rw-r--r--mojo/public/cpp/bindings/lib/map_data_internal.h34
-rw-r--r--mojo/public/cpp/bindings/lib/map_serialization.h63
-rw-r--r--mojo/public/cpp/bindings/lib/may_auto_lock.h5
-rw-r--r--mojo/public/cpp/bindings/lib/message.cc462
-rw-r--r--mojo/public/cpp/bindings/lib/message_buffer.cc52
-rw-r--r--mojo/public/cpp/bindings/lib/message_buffer.h43
-rw-r--r--mojo/public/cpp/bindings/lib/message_builder.cc69
-rw-r--r--mojo/public/cpp/bindings/lib/message_builder.h45
-rw-r--r--mojo/public/cpp/bindings/lib/message_dumper.cc96
-rw-r--r--mojo/public/cpp/bindings/lib/message_header_validator.cc11
-rw-r--r--mojo/public/cpp/bindings/lib/message_internal.cc45
-rw-r--r--mojo/public/cpp/bindings/lib/message_internal.h14
-rw-r--r--mojo/public/cpp/bindings/lib/multiplex_router.cc346
-rw-r--r--mojo/public/cpp/bindings/lib/multiplex_router.h100
-rw-r--r--mojo/public/cpp/bindings/lib/native_struct.cc34
-rw-r--r--mojo/public/cpp/bindings/lib/native_struct_data.cc22
-rw-r--r--mojo/public/cpp/bindings/lib/native_struct_data.h38
-rw-r--r--mojo/public/cpp/bindings/lib/native_struct_serialization.cc122
-rw-r--r--mojo/public/cpp/bindings/lib/native_struct_serialization.h109
-rw-r--r--mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc1
-rw-r--r--mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc19
-rw-r--r--mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc25
-rw-r--r--mojo/public/cpp/bindings/lib/sequence_local_sync_event_watcher.cc286
-rw-r--r--mojo/public/cpp/bindings/lib/serialization.h140
-rw-r--r--mojo/public/cpp/bindings/lib/serialization_context.cc78
-rw-r--r--mojo/public/cpp/bindings/lib/serialization_context.h91
-rw-r--r--mojo/public/cpp/bindings/lib/serialization_forward.h38
-rw-r--r--mojo/public/cpp/bindings/lib/serialization_util.h25
-rw-r--r--mojo/public/cpp/bindings/lib/string_serialization.h32
-rw-r--r--mojo/public/cpp/bindings/lib/string_traits_string16.cc42
-rw-r--r--mojo/public/cpp/bindings/lib/string_traits_wtf.cc13
-rw-r--r--mojo/public/cpp/bindings/lib/sync_call_restrictions.cc86
-rw-r--r--mojo/public/cpp/bindings/lib/sync_event_watcher.cc32
-rw-r--r--mojo/public/cpp/bindings/lib/sync_handle_registry.cc151
-rw-r--r--mojo/public/cpp/bindings/lib/sync_handle_watcher.cc6
-rw-r--r--mojo/public/cpp/bindings/lib/task_runner_helper.cc24
-rw-r--r--mojo/public/cpp/bindings/lib/task_runner_helper.h28
-rw-r--r--mojo/public/cpp/bindings/lib/template_util.h5
-rw-r--r--mojo/public/cpp/bindings/lib/union_accessor.h33
-rw-r--r--mojo/public/cpp/bindings/lib/unserialized_message_context.cc24
-rw-r--r--mojo/public/cpp/bindings/lib/unserialized_message_context.h63
-rw-r--r--mojo/public/cpp/bindings/lib/validation_context.h4
-rw-r--r--mojo/public/cpp/bindings/lib/validation_errors.h34
-rw-r--r--mojo/public/cpp/bindings/lib/validation_util.cc45
-rw-r--r--mojo/public/cpp/bindings/lib/validation_util.h109
-rw-r--r--mojo/public/cpp/bindings/lib/wtf_clone_equals_util.h24
-rw-r--r--mojo/public/cpp/bindings/lib/wtf_hash_util.h36
-rw-r--r--mojo/public/cpp/bindings/map.h19
-rw-r--r--mojo/public/cpp/bindings/map_traits.h10
-rw-r--r--mojo/public/cpp/bindings/map_traits_flat_map.h56
-rw-r--r--mojo/public/cpp/bindings/map_traits_stl.h30
-rw-r--r--mojo/public/cpp/bindings/map_traits_wtf_hash_map.h4
-rw-r--r--mojo/public/cpp/bindings/message.h179
-rw-r--r--mojo/public/cpp/bindings/message_dumper.h43
-rw-r--r--mojo/public/cpp/bindings/message_header_validator.h6
-rw-r--r--mojo/public/cpp/bindings/mojo_buildflags.h6
-rw-r--r--mojo/public/cpp/bindings/native_struct.h51
-rw-r--r--mojo/public/cpp/bindings/native_struct_data_view.h36
-rw-r--r--mojo/public/cpp/bindings/pipe_control_message_handler.h2
-rw-r--r--mojo/public/cpp/bindings/pipe_control_message_proxy.h4
-rw-r--r--mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h12
-rw-r--r--mojo/public/cpp/bindings/sequence_local_sync_event_watcher.h69
-rw-r--r--mojo/public/cpp/bindings/string_traits.h8
-rw-r--r--mojo/public/cpp/bindings/string_traits_string16.h37
-rw-r--r--mojo/public/cpp/bindings/string_traits_wtf.h4
-rw-r--r--mojo/public/cpp/bindings/strong_associated_binding.h30
-rw-r--r--mojo/public/cpp/bindings/strong_associated_binding_set.h25
-rw-r--r--mojo/public/cpp/bindings/strong_binding.h37
-rw-r--r--mojo/public/cpp/bindings/strong_binding_set.h12
-rw-r--r--mojo/public/cpp/bindings/struct_ptr.h11
-rw-r--r--mojo/public/cpp/bindings/struct_traits.h32
-rw-r--r--mojo/public/cpp/bindings/sync_call_restrictions.h87
-rw-r--r--mojo/public/cpp/bindings/sync_event_watcher.h21
-rw-r--r--mojo/public/cpp/bindings/sync_handle_registry.h44
-rw-r--r--mojo/public/cpp/bindings/sync_handle_watcher.h16
-rw-r--r--mojo/public/cpp/bindings/tests/BUILD.gn18
-rw-r--r--mojo/public/cpp/bindings/tests/associated_interface_unittest.cc118
-rw-r--r--mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc15
-rw-r--r--mojo/public/cpp/bindings/tests/binding_set_unittest.cc293
-rw-r--r--mojo/public/cpp/bindings/tests/binding_unittest.cc139
-rw-r--r--mojo/public/cpp/bindings/tests/bindings_perftest.cc14
-rw-r--r--mojo/public/cpp/bindings/tests/bindings_test_base.cc40
-rw-r--r--mojo/public/cpp/bindings/tests/bindings_test_base.h51
-rw-r--r--mojo/public/cpp/bindings/tests/buffer_unittest.cc68
-rw-r--r--mojo/public/cpp/bindings/tests/callback_helpers_unittest.cc202
-rw-r--r--mojo/public/cpp/bindings/tests/connector_unittest.cc117
-rw-r--r--mojo/public/cpp/bindings/tests/constant_unittest.cc2
-rw-r--r--mojo/public/cpp/bindings/tests/data_view_unittest.cc45
-rw-r--r--mojo/public/cpp/bindings/tests/e2e_perftest.cc32
-rw-r--r--mojo/public/cpp/bindings/tests/handle_passing_unittest.cc78
-rw-r--r--mojo/public/cpp/bindings/tests/hash_unittest.cc8
-rw-r--r--mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc199
-rw-r--r--mojo/public/cpp/bindings/tests/lazy_serialization_unittest.cc166
-rw-r--r--mojo/public/cpp/bindings/tests/map_unittest.cc6
-rw-r--r--mojo/public/cpp/bindings/tests/message_queue.h5
-rw-r--r--mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc35
-rw-r--r--mojo/public/cpp/bindings/tests/native_struct_unittest.cc98
-rw-r--r--mojo/public/cpp/bindings/tests/pickle_unittest.cc32
-rw-r--r--mojo/public/cpp/bindings/tests/pickled_types_blink.cc10
-rw-r--r--mojo/public/cpp/bindings/tests/pickled_types_blink.h2
-rw-r--r--mojo/public/cpp/bindings/tests/pickled_types_chromium.cc10
-rw-r--r--mojo/public/cpp/bindings/tests/pickled_types_chromium.h2
-rw-r--r--mojo/public/cpp/bindings/tests/report_bad_message_unittest.cc39
-rw-r--r--mojo/public/cpp/bindings/tests/request_response_unittest.cc17
-rw-r--r--mojo/public/cpp/bindings/tests/router_test_util.cc21
-rw-r--r--mojo/public/cpp/bindings/tests/router_test_util.h2
-rw-r--r--mojo/public/cpp/bindings/tests/sample_service_unittest.cc17
-rw-r--r--mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc30
-rw-r--r--mojo/public/cpp/bindings/tests/struct_traits_unittest.cc26
-rw-r--r--mojo/public/cpp/bindings/tests/struct_unittest.cc157
-rw-r--r--mojo/public/cpp/bindings/tests/struct_with_traits.typemap3
-rw-r--r--mojo/public/cpp/bindings/tests/struct_with_traits_impl.cc22
-rw-r--r--mojo/public/cpp/bindings/tests/struct_with_traits_impl.h48
-rw-r--r--mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc30
-rw-r--r--mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h57
-rw-r--r--mojo/public/cpp/bindings/tests/sync_handle_registry_unittest.cc258
-rw-r--r--mojo/public/cpp/bindings/tests/sync_method_unittest.cc541
-rw-r--r--mojo/public/cpp/bindings/tests/test_helpers_unittest.cc128
-rw-r--r--mojo/public/cpp/bindings/tests/test_native_types.cc99
-rw-r--r--mojo/public/cpp/bindings/tests/test_native_types.h89
-rw-r--r--mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap8
-rw-r--r--mojo/public/cpp/bindings/tests/union_unittest.cc796
-rw-r--r--mojo/public/cpp/bindings/tests/validation_unittest.cc28
-rw-r--r--mojo/public/cpp/bindings/tests/variant_test_util.h4
-rw-r--r--mojo/public/cpp/bindings/tests/versioning_test_service.cc25
-rw-r--r--mojo/public/cpp/bindings/tests/wtf_hash_unittest.cc22
-rw-r--r--mojo/public/cpp/bindings/tests/wtf_types_unittest.cc100
-rw-r--r--mojo/public/cpp/bindings/thread_safe_interface_ptr.h96
-rw-r--r--mojo/public/cpp/bindings/union_traits.h8
-rw-r--r--mojo/public/cpp/bindings/unique_ptr_impl_ref_traits.h4
-rw-r--r--mojo/public/cpp/platform/BUILD.gn50
-rw-r--r--mojo/public/cpp/platform/README.md75
-rw-r--r--mojo/public/cpp/platform/named_platform_channel.cc59
-rw-r--r--mojo/public/cpp/platform/named_platform_channel.h122
-rw-r--r--mojo/public/cpp/platform/named_platform_channel_fuchsia.cc26
-rw-r--r--mojo/public/cpp/platform/named_platform_channel_posix.cc151
-rw-r--r--mojo/public/cpp/platform/named_platform_channel_win.cc110
-rw-r--r--mojo/public/cpp/platform/platform_channel.cc282
-rw-r--r--mojo/public/cpp/platform/platform_channel.h117
-rw-r--r--mojo/public/cpp/platform/platform_channel_endpoint.cc30
-rw-r--r--mojo/public/cpp/platform/platform_channel_endpoint.h45
-rw-r--r--mojo/public/cpp/platform/platform_channel_server_endpoint.cc31
-rw-r--r--mojo/public/cpp/platform/platform_channel_server_endpoint.h46
-rw-r--r--mojo/public/cpp/platform/platform_handle.cc252
-rw-r--r--mojo/public/cpp/platform/platform_handle.h189
-rw-r--r--mojo/public/cpp/platform/socket_utils_posix.cc194
-rw-r--r--mojo/public/cpp/platform/socket_utils_posix.h80
-rw-r--r--mojo/public/cpp/platform/tests/BUILD.gn19
-rw-r--r--mojo/public/cpp/platform/tests/platform_handle_unittest.cc262
-rw-r--r--mojo/public/cpp/system/BUILD.gn22
-rw-r--r--mojo/public/cpp/system/README.md224
-rw-r--r--mojo/public/cpp/system/buffer.cc20
-rw-r--r--mojo/public/cpp/system/buffer.h8
-rw-r--r--mojo/public/cpp/system/data_pipe.h118
-rw-r--r--mojo/public/cpp/system/data_pipe_drainer.cc50
-rw-r--r--mojo/public/cpp/system/data_pipe_drainer.h47
-rw-r--r--mojo/public/cpp/system/data_pipe_utils.cc95
-rw-r--r--mojo/public/cpp/system/data_pipe_utils.h30
-rw-r--r--mojo/public/cpp/system/file_data_pipe_producer.cc289
-rw-r--r--mojo/public/cpp/system/file_data_pipe_producer.h114
-rw-r--r--mojo/public/cpp/system/handle.h20
-rw-r--r--mojo/public/cpp/system/handle_signal_tracker.cc70
-rw-r--r--mojo/public/cpp/system/handle_signal_tracker.h69
-rw-r--r--mojo/public/cpp/system/handle_signals_state.h39
-rw-r--r--mojo/public/cpp/system/invitation.cc286
-rw-r--r--mojo/public/cpp/system/invitation.h186
-rw-r--r--mojo/public/cpp/system/isolated_connection.cc41
-rw-r--r--mojo/public/cpp/system/isolated_connection.h60
-rw-r--r--mojo/public/cpp/system/message.cc13
-rw-r--r--mojo/public/cpp/system/message.h46
-rw-r--r--mojo/public/cpp/system/message_pipe.cc88
-rw-r--r--mojo/public/cpp/system/message_pipe.h79
-rw-r--r--mojo/public/cpp/system/platform_handle.cc343
-rw-r--r--mojo/public/cpp/system/platform_handle.h120
-rw-r--r--mojo/public/cpp/system/scope_to_message_pipe.cc46
-rw-r--r--mojo/public/cpp/system/scope_to_message_pipe.h65
-rw-r--r--mojo/public/cpp/system/simple_watcher.cc160
-rw-r--r--mojo/public/cpp/system/simple_watcher.h90
-rw-r--r--mojo/public/cpp/system/string_data_pipe_producer.cc138
-rw-r--r--mojo/public/cpp/system/string_data_pipe_producer.h90
-rw-r--r--mojo/public/cpp/system/tests/BUILD.gn12
-rw-r--r--mojo/public/cpp/system/tests/core_unittest.cc157
-rw-r--r--mojo/public/cpp/system/tests/data_pipe_drainer_unittest.cc62
-rw-r--r--mojo/public/cpp/system/tests/file_data_pipe_producer_unittest.cc338
-rw-r--r--mojo/public/cpp/system/tests/handle_signal_tracker_unittest.cc116
-rw-r--r--mojo/public/cpp/system/tests/handle_signals_state_unittest.cc8
-rw-r--r--mojo/public/cpp/system/tests/invitation_unittest.cc318
-rw-r--r--mojo/public/cpp/system/tests/scope_to_message_pipe_unittest.cc69
-rw-r--r--mojo/public/cpp/system/tests/simple_watcher_unittest.cc61
-rw-r--r--mojo/public/cpp/system/tests/string_data_pipe_producer_unittest.cc208
-rw-r--r--mojo/public/cpp/system/tests/wait_set_unittest.cc17
-rw-r--r--mojo/public/cpp/system/tests/wait_unittest.cc24
-rw-r--r--mojo/public/cpp/system/trap.cc20
-rw-r--r--mojo/public/cpp/system/trap.h36
-rw-r--r--mojo/public/cpp/system/wait.cc98
-rw-r--r--mojo/public/cpp/system/wait.h28
-rw-r--r--mojo/public/cpp/system/wait_set.cc106
-rw-r--r--mojo/public/cpp/system/wait_set.h5
-rw-r--r--mojo/public/cpp/system/watcher.cc20
-rw-r--r--mojo/public/cpp/system/watcher.h37
-rw-r--r--mojo/public/cpp/test_support/BUILD.gn1
-rw-r--r--mojo/public/cpp/test_support/lib/test_utils.cc57
-rw-r--r--mojo/public/cpp/test_support/test_utils.h14
-rw-r--r--mojo/public/interfaces/bindings/BUILD.gn29
-rw-r--r--mojo/public/interfaces/bindings/native_struct.mojom26
-rw-r--r--mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom67
-rw-r--r--mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom46
-rw-r--r--mojo/public/interfaces/bindings/tests/BUILD.gn66
-rw-r--r--mojo/public/interfaces/bindings/tests/echo.mojom2
-rw-r--r--mojo/public/interfaces/bindings/tests/echo_import/echo_import.mojom (renamed from mojo/public/interfaces/bindings/tests/echo_import.mojom)0
-rw-r--r--mojo/public/interfaces/bindings/tests/ping_service.mojom7
-rw-r--r--mojo/public/interfaces/bindings/tests/sample_factory.mojom2
-rw-r--r--mojo/public/interfaces/bindings/tests/sample_import.mojom5
-rw-r--r--mojo/public/interfaces/bindings/tests/sample_import2.mojom2
-rw-r--r--mojo/public/interfaces/bindings/tests/sample_service.mojom4
-rw-r--r--mojo/public/interfaces/bindings/tests/struct_with_traits.mojom29
-rw-r--r--mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom15
-rw-r--r--mojo/public/interfaces/bindings/tests/test_name_generator.mojom27
-rw-r--r--mojo/public/interfaces/bindings/tests/test_native_types.mojom12
-rw-r--r--mojo/public/interfaces/bindings/tests/test_structs.mojom4
-rw-r--r--mojo/public/interfaces/bindings/tests/test_unions.mojom10
-rw-r--r--mojo/public/interfaces/bindings/tests/test_wtf_types.mojom6
-rw-r--r--mojo/public/java/BUILD.gn14
-rw-r--r--mojo/public/java/base/src/org/chromium/mojo_base/BigBufferUtil.java55
-rw-r--r--mojo/public/java/bindings/README.md4
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java13
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java27
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java2
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java7
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java41
-rw-r--r--mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java13
-rw-r--r--mojo/public/java/system/BUILD.gn177
-rw-r--r--mojo/public/java/system/README.md4
-rw-r--r--mojo/public/java/system/base_run_loop.cc77
-rw-r--r--mojo/public/java/system/core_impl.cc343
-rw-r--r--mojo/public/java/system/javatests/AndroidManifest.xml30
-rw-r--r--mojo/public/java/system/javatests/apk/.empty (renamed from mojo/android/javatests/apk/.empty)0
-rw-r--r--mojo/public/java/system/javatests/init_library.cc17
-rw-r--r--mojo/public/java/system/javatests/mojo_test_rule.cc73
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/HandleMock.java220
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/MojoTestRule.java81
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/TestUtils.java30
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java60
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsTest.java228
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java109
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java223
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java66
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java119
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java115
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java140
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java296
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java74
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/NameGeneratorTest.java80
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java123
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/RouterTest.java241
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/SerializationTest.java186
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTest.java247
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java67
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java150
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java29
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java529
-rw-r--r--mojo/public/java/system/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java271
-rw-r--r--mojo/public/java/system/javatests/validation_test_util.cc48
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java5
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java70
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java74
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/impl/CoreImpl.java515
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java70
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java62
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/impl/HandleBase.java137
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java55
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java60
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java70
-rw-r--r--mojo/public/java/system/src/org/chromium/mojo/system/impl/WatcherImpl.java (renamed from mojo/android/system/src/org/chromium/mojo/system/impl/WatcherImpl.java)0
-rw-r--r--mojo/public/java/system/watcher_impl.cc104
-rw-r--r--mojo/public/js/BUILD.gn91
-rw-r--r--mojo/public/js/README.md285
-rw-r--r--mojo/public/js/base.js134
-rw-r--r--mojo/public/js/bindings.js352
-rw-r--r--mojo/public/js/buffer.js156
-rw-r--r--mojo/public/js/codec.js926
-rw-r--r--mojo/public/js/connector.js113
-rw-r--r--mojo/public/js/constants.cc33
-rw-r--r--mojo/public/js/constants.h30
-rw-r--r--mojo/public/js/core.js304
-rw-r--r--mojo/public/js/interface_types.js63
-rw-r--r--mojo/public/js/lib/buffer.js (renamed from mojo/public/js/new_bindings/buffer.js)0
-rw-r--r--mojo/public/js/lib/codec.js1139
-rw-r--r--mojo/public/js/lib/connector.js169
-rw-r--r--mojo/public/js/lib/control_message_handler.js90
-rw-r--r--mojo/public/js/lib/control_message_proxy.js67
-rw-r--r--mojo/public/js/lib/interface_endpoint_client.js75
-rw-r--r--mojo/public/js/lib/interface_endpoint_handle.js62
-rw-r--r--mojo/public/js/lib/pipe_control_message_handler.js39
-rw-r--r--mojo/public/js/lib/pipe_control_message_proxy.js36
-rw-r--r--mojo/public/js/lib/router.js317
-rw-r--r--mojo/public/js/lib/unicode.js51
-rw-r--r--mojo/public/js/lib/validator.js678
-rw-r--r--mojo/public/js/mojo_bindings_resources.grd21
-rw-r--r--mojo/public/js/new_bindings/base.js111
-rw-r--r--mojo/public/js/new_bindings/bindings.js275
-rw-r--r--mojo/public/js/new_bindings/codec.js917
-rw-r--r--mojo/public/js/new_bindings/connector.js104
-rw-r--r--mojo/public/js/new_bindings/interface_types.js46
-rw-r--r--mojo/public/js/new_bindings/lib/control_message_handler.js106
-rw-r--r--mojo/public/js/new_bindings/lib/control_message_proxy.js97
-rw-r--r--mojo/public/js/new_bindings/router.js190
-rw-r--r--mojo/public/js/new_bindings/unicode.js51
-rw-r--r--mojo/public/js/new_bindings/validator.js511
-rw-r--r--mojo/public/js/router.js269
-rw-r--r--mojo/public/js/support.js53
-rw-r--r--mojo/public/js/tests/core_unittest.js223
-rw-r--r--mojo/public/js/tests/validation_test_input_parser.js299
-rw-r--r--mojo/public/js/tests/validation_unittest.js334
-rw-r--r--mojo/public/js/threading.js21
-rw-r--r--mojo/public/js/unicode.js51
-rw-r--r--mojo/public/js/validator.js560
-rw-r--r--mojo/public/mojom/base/BUILD.gn45
-rw-r--r--mojo/public/mojom/base/big_buffer.mojom18
-rw-r--r--mojo/public/mojom/base/big_string.mojom19
-rw-r--r--mojo/public/mojom/base/file.mojom11
-rw-r--r--mojo/public/mojom/base/file_error.mojom27
-rw-r--r--mojo/public/mojom/base/file_info.mojom17
-rw-r--r--mojo/public/mojom/base/file_path.mojom24
-rw-r--r--mojo/public/mojom/base/logfont_win.mojom11
-rw-r--r--mojo/public/mojom/base/memory_allocator_dump_cross_process_uid.mojom10
-rw-r--r--mojo/public/mojom/base/process_id.mojom12
-rw-r--r--mojo/public/mojom/base/read_only_buffer.mojom12
-rw-r--r--mojo/public/mojom/base/ref_counted_memory.mojom14
-rw-r--r--mojo/public/mojom/base/shared_memory.mojom25
-rw-r--r--mojo/public/mojom/base/string16.mojom27
-rw-r--r--mojo/public/mojom/base/text_direction.mojom12
-rw-r--r--mojo/public/mojom/base/thread_priority.mojom17
-rw-r--r--mojo/public/mojom/base/time.mojom21
-rw-r--r--mojo/public/mojom/base/unguessable_token.mojom11
-rw-r--r--mojo/public/mojom/base/values.mojom38
-rw-r--r--mojo/public/tools/bindings/BUILD.gn11
-rw-r--r--mojo/public/tools/bindings/README.md81
-rw-r--r--mojo/public/tools/bindings/blink_bindings_configuration.gni17
-rw-r--r--mojo/public/tools/bindings/chromium_bindings_configuration.gni70
-rw-r--r--mojo/public/tools/bindings/gen_data_files_list.py48
-rwxr-xr-xmojo/public/tools/bindings/generate_type_mappings.py22
-rw-r--r--mojo/public/tools/bindings/generators/__init__.py0
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl22
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl26
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl421
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl168
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl4
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl2
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl2
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl2
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl20
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/module-shared-message-ids.h.tmpl35
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl1
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl20
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl9
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl55
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl4
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl31
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl4
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl80
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl32
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/struct_unserialized_message_context.tmpl33
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl39
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl2
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl69
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/union_traits_definition.tmpl28
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/validation_macros.tmpl19
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl54
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl2
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_template_definition.tmpl4
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl39
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl69
-rw-r--r--mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_template_definition.tmpl4
-rw-r--r--mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl177
-rw-r--r--mojo/public/tools/bindings/generators/java_templates/header.java.tmpl3
-rw-r--r--mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl7
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/externs/interface_definition.tmpl34
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/externs/module.externs.tmpl37
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/externs/struct_definition.tmpl8
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/fuzzing.tmpl125
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl130
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl54
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl9
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl57
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/union_definition.tmpl66
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl30
-rw-r--r--mojo/public/tools/bindings/generators/mojom_cpp_generator.py1120
-rw-r--r--mojo/public/tools/bindings/generators/mojom_java_generator.py150
-rw-r--r--mojo/public/tools/bindings/generators/mojom_js_generator.py783
-rw-r--r--mojo/public/tools/bindings/mojom.gni777
-rwxr-xr-xmojo/public/tools/bindings/mojom_bindings_generator.py353
-rw-r--r--mojo/public/tools/bindings/mojom_bindings_generator_unittest.py30
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/generate/generator.py217
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/generate/generator_unittest.py24
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/generate/module.py352
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/generate/pack.py3
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py23
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/generate/translate.py180
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/parse/ast.py74
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/parse/conditional_features.py80
-rw-r--r--mojo/public/tools/bindings/pylib/mojom/parse/parser.py27
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/generate/generator_unittest.py12
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/parse/conditional_features_unittest.py232
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py5
-rw-r--r--mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py66
-rw-r--r--mojo/public/tools/fuzzers/BUILD.gn67
-rw-r--r--mojo/public/tools/fuzzers/fuzz.mojom78
-rw-r--r--mojo/public/tools/fuzzers/fuzz_impl.cc45
-rw-r--r--mojo/public/tools/fuzzers/fuzz_impl.h45
-rw-r--r--mojo/public/tools/fuzzers/message_corpus/message_0.mojomsgbin0 -> 32 bytes
-rw-r--r--mojo/public/tools/fuzzers/message_corpus/message_1.mojomsgbin0 -> 40 bytes
-rw-r--r--mojo/public/tools/fuzzers/message_corpus/message_10.mojomsgbin0 -> 32 bytes
-rw-r--r--mojo/public/tools/fuzzers/message_corpus/message_11.mojomsgbin0 -> 72 bytes
-rw-r--r--mojo/public/tools/fuzzers/message_corpus/message_2.mojomsgbin0 -> 40 bytes
-rw-r--r--mojo/public/tools/fuzzers/message_corpus/message_3.mojomsgbin0 -> 328 bytes
-rw-r--r--mojo/public/tools/fuzzers/message_corpus/message_4.mojomsgbin0 -> 1736 bytes
-rw-r--r--mojo/public/tools/fuzzers/message_corpus/message_5.mojomsgbin0 -> 1744 bytes
-rw-r--r--mojo/public/tools/fuzzers/message_corpus/message_6.mojomsgbin0 -> 1744 bytes
-rw-r--r--mojo/public/tools/fuzzers/message_corpus/message_7.mojomsgbin0 -> 1744 bytes
-rw-r--r--mojo/public/tools/fuzzers/message_corpus/message_8.mojomsgbin0 -> 1744 bytes
-rw-r--r--mojo/public/tools/fuzzers/message_corpus/message_9.mojomsgbin0 -> 80 bytes
-rw-r--r--mojo/public/tools/fuzzers/mojo_fuzzer.proto7
-rw-r--r--mojo/public/tools/fuzzers/mojo_fuzzer_message_dump.cc266
-rw-r--r--mojo/public/tools/fuzzers/mojo_parse_message_fuzzer.cc61
-rw-r--r--mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/07775ad8fdb79599024caefbe7889501dfee9e061
-rw-r--r--mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/2ce2f91669a46921ebf4e47679c86dd2bf5b14961
-rw-r--r--mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/32a65dcd84debde03d51f8b8ace2cdcc87461d341
-rw-r--r--mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/7cbf9144ec3980eb121eedc679ebc56a3ddd22a61
-rw-r--r--mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/9ccc6b5c0a61672816dc252194c3d722c18107bc1
-rw-r--r--mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/9e0a62bdd4b08cb777bee9449a22b3ad6702b1061
-rw-r--r--mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/a74241101f97704b96c9ba11b4781651e236ad8f1
-rw-r--r--mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/be66c5d078fbf574388b7b1d25a29ff2d16df67e1
-rw-r--r--mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/e4be6bde72d04c5cda7d4939a80e5890c5c013741
-rw-r--r--mojo/public/tools/fuzzers/mojo_parse_message_proto_fuzzer.cc70
-rw-r--r--soong/bindings_generator.go26
-rw-r--r--third_party/catapult/LICENSE27
-rw-r--r--third_party/catapult/devil/PRESUBMIT.py81
-rw-r--r--third_party/catapult/devil/README.md37
-rwxr-xr-xthird_party/catapult/devil/bin/generate_md_docs45
-rwxr-xr-xthird_party/catapult/devil/bin/run_py_devicetests32
-rwxr-xr-xthird_party/catapult/devil/bin/run_py_tests22
-rw-r--r--third_party/catapult/devil/devil/__init__.py7
-rw-r--r--third_party/catapult/devil/devil/android/__init__.py3
-rw-r--r--third_party/catapult/devil/devil/android/apk_helper.py164
-rwxr-xr-xthird_party/catapult/devil/devil/android/apk_helper_test.py169
-rw-r--r--third_party/catapult/devil/devil/android/app_ui.py243
-rw-r--r--third_party/catapult/devil/devil/android/app_ui_test.py191
-rw-r--r--third_party/catapult/devil/devil/android/battery_utils.py699
-rwxr-xr-xthird_party/catapult/devil/devil/android/battery_utils_test.py694
-rw-r--r--third_party/catapult/devil/devil/android/constants/__init__.py3
-rw-r--r--third_party/catapult/devil/devil/android/constants/chrome.py57
-rw-r--r--third_party/catapult/devil/devil/android/constants/file_system.py5
-rw-r--r--third_party/catapult/devil/devil/android/decorators.py176
-rw-r--r--third_party/catapult/devil/devil/android/decorators_test.py332
-rw-r--r--third_party/catapult/devil/devil/android/device_blacklist.py80
-rw-r--r--third_party/catapult/devil/devil/android/device_blacklist_test.py38
-rw-r--r--third_party/catapult/devil/devil/android/device_errors.py180
-rwxr-xr-xthird_party/catapult/devil/devil/android/device_errors_test.py72
-rw-r--r--third_party/catapult/devil/devil/android/device_list.py52
-rw-r--r--third_party/catapult/devil/devil/android/device_signal.py41
-rw-r--r--third_party/catapult/devil/devil/android/device_temp_file.py63
-rw-r--r--third_party/catapult/devil/devil/android/device_test_case.py54
-rw-r--r--third_party/catapult/devil/devil/android/device_utils.py2640
-rwxr-xr-xthird_party/catapult/devil/devil/android/device_utils_devicetest.py229
-rwxr-xr-xthird_party/catapult/devil/devil/android/device_utils_test.py2900
-rw-r--r--third_party/catapult/devil/devil/android/fastboot_utils.py256
-rwxr-xr-xthird_party/catapult/devil/devil/android/fastboot_utils_test.py375
-rw-r--r--third_party/catapult/devil/devil/android/flag_changer.py300
-rw-r--r--third_party/catapult/devil/devil/android/flag_changer_devicetest.py88
-rwxr-xr-xthird_party/catapult/devil/devil/android/flag_changer_test.py135
-rw-r--r--third_party/catapult/devil/devil/android/forwarder.py464
-rw-r--r--third_party/catapult/devil/devil/android/install_commands.py57
-rw-r--r--third_party/catapult/devil/devil/android/logcat_monitor.py255
-rwxr-xr-xthird_party/catapult/devil/devil/android/logcat_monitor_test.py230
-rw-r--r--third_party/catapult/devil/devil/android/md5sum.py120
-rwxr-xr-xthird_party/catapult/devil/devil/android/md5sum_test.py237
-rw-r--r--third_party/catapult/devil/devil/android/perf/__init__.py3
-rw-r--r--third_party/catapult/devil/devil/android/perf/cache_control.py15
-rw-r--r--third_party/catapult/devil/devil/android/perf/perf_control.py210
-rw-r--r--third_party/catapult/devil/devil/android/perf/perf_control_devicetest.py38
-rw-r--r--third_party/catapult/devil/devil/android/perf/surface_stats_collector.py186
-rw-r--r--third_party/catapult/devil/devil/android/perf/thermal_throttle.py135
-rw-r--r--third_party/catapult/devil/devil/android/ports.py178
-rw-r--r--third_party/catapult/devil/devil/android/sdk/__init__.py6
-rw-r--r--third_party/catapult/devil/devil/android/sdk/aapt.py43
-rw-r--r--third_party/catapult/devil/devil/android/sdk/adb_compatibility_devicetest.py230
-rw-r--r--third_party/catapult/devil/devil/android/sdk/adb_wrapper.py917
-rwxr-xr-xthird_party/catapult/devil/devil/android/sdk/adb_wrapper_devicetest.py118
-rwxr-xr-xthird_party/catapult/devil/devil/android/sdk/adb_wrapper_test.py59
-rw-r--r--third_party/catapult/devil/devil/android/sdk/build_tools.py51
-rw-r--r--third_party/catapult/devil/devil/android/sdk/dexdump.py31
-rw-r--r--third_party/catapult/devil/devil/android/sdk/fastboot.py98
-rw-r--r--third_party/catapult/devil/devil/android/sdk/gce_adb_wrapper.py154
-rw-r--r--third_party/catapult/devil/devil/android/sdk/intent.py129
-rw-r--r--third_party/catapult/devil/devil/android/sdk/keyevent.py63
-rw-r--r--third_party/catapult/devil/devil/android/sdk/shared_prefs.py420
-rwxr-xr-xthird_party/catapult/devil/devil/android/sdk/shared_prefs_test.py171
-rw-r--r--third_party/catapult/devil/devil/android/sdk/split_select.py63
-rw-r--r--third_party/catapult/devil/devil/android/sdk/test/data/push_directory/push_directory_contents.txt1
-rw-r--r--third_party/catapult/devil/devil/android/sdk/test/data/push_file.txt1
-rw-r--r--third_party/catapult/devil/devil/android/sdk/version_codes.py20
-rw-r--r--third_party/catapult/devil/devil/android/settings.py273
-rw-r--r--third_party/catapult/devil/devil/android/tools/__init__.py3
-rwxr-xr-xthird_party/catapult/devil/devil/android/tools/adb_run_shell_cmd.py61
-rwxr-xr-xthird_party/catapult/devil/devil/android/tools/cpufreq.py87
-rwxr-xr-xthird_party/catapult/devil/devil/android/tools/device_monitor.py231
-rwxr-xr-xthird_party/catapult/devil/devil/android/tools/device_monitor_test.py168
-rwxr-xr-xthird_party/catapult/devil/devil/android/tools/device_recovery.py208
-rwxr-xr-xthird_party/catapult/devil/devil/android/tools/device_status.py313
-rwxr-xr-xthird_party/catapult/devil/devil/android/tools/flash_device.py70
-rwxr-xr-xthird_party/catapult/devil/devil/android/tools/keyboard.py129
-rwxr-xr-xthird_party/catapult/devil/devil/android/tools/provision_devices.py637
-rwxr-xr-xthird_party/catapult/devil/devil/android/tools/screenshot.py59
-rw-r--r--third_party/catapult/devil/devil/android/tools/script_common.py29
-rwxr-xr-xthird_party/catapult/devil/devil/android/tools/script_common_test.py58
-rwxr-xr-xthird_party/catapult/devil/devil/android/tools/video_recorder.py175
-rwxr-xr-xthird_party/catapult/devil/devil/android/tools/wait_for_devices.py50
-rw-r--r--third_party/catapult/devil/devil/android/valgrind_tools/__init__.py21
-rw-r--r--third_party/catapult/devil/devil/android/valgrind_tools/base_tool.py53
-rw-r--r--third_party/catapult/devil/devil/base_error.py24
-rw-r--r--third_party/catapult/devil/devil/constants/__init__.py3
-rw-r--r--third_party/catapult/devil/devil/constants/exit_codes.py9
-rw-r--r--third_party/catapult/devil/devil/devil_dependencies.json127
-rw-r--r--third_party/catapult/devil/devil/devil_env.py194
-rwxr-xr-xthird_party/catapult/devil/devil/devil_env_test.py63
-rw-r--r--third_party/catapult/devil/devil/utils/__init__.py23
-rwxr-xr-xthird_party/catapult/devil/devil/utils/battor_device_mapping.py309
-rw-r--r--third_party/catapult/devil/devil/utils/cmd_helper.py394
-rwxr-xr-xthird_party/catapult/devil/devil/utils/cmd_helper_test.py262
-rw-r--r--third_party/catapult/devil/devil/utils/file_utils.py31
-rwxr-xr-xthird_party/catapult/devil/devil/utils/find_usb_devices.py532
-rwxr-xr-xthird_party/catapult/devil/devil/utils/find_usb_devices_test.py379
-rw-r--r--third_party/catapult/devil/devil/utils/geometry.py75
-rw-r--r--third_party/catapult/devil/devil/utils/geometry_test.py61
-rw-r--r--third_party/catapult/devil/devil/utils/host_utils.py16
-rw-r--r--third_party/catapult/devil/devil/utils/lazy/__init__.py5
-rw-r--r--third_party/catapult/devil/devil/utils/lazy/weak_constant.py29
-rw-r--r--third_party/catapult/devil/devil/utils/lsusb.py174
-rwxr-xr-xthird_party/catapult/devil/devil/utils/lsusb_test.py250
-rwxr-xr-xthird_party/catapult/devil/devil/utils/markdown.py320
-rwxr-xr-xthird_party/catapult/devil/devil/utils/markdown_test.py121
-rw-r--r--third_party/catapult/devil/devil/utils/mock_calls.py180
-rwxr-xr-xthird_party/catapult/devil/devil/utils/mock_calls_test.py173
-rw-r--r--third_party/catapult/devil/devil/utils/parallelizer.py238
-rw-r--r--third_party/catapult/devil/devil/utils/parallelizer_test.py162
-rw-r--r--third_party/catapult/devil/devil/utils/reraiser_thread.py228
-rw-r--r--third_party/catapult/devil/devil/utils/reraiser_thread_unittest.py117
-rwxr-xr-xthird_party/catapult/devil/devil/utils/reset_usb.py111
-rw-r--r--third_party/catapult/devil/devil/utils/run_tests_helper.py44
-rw-r--r--third_party/catapult/devil/devil/utils/signal_handler.py48
-rw-r--r--third_party/catapult/devil/devil/utils/test/data/test_serial_map.json1
-rw-r--r--third_party/catapult/devil/devil/utils/timeout_retry.py175
-rwxr-xr-xthird_party/catapult/devil/devil/utils/timeout_retry_unittest.py79
-rwxr-xr-xthird_party/catapult/devil/devil/utils/update_mapping.py47
-rw-r--r--third_party/catapult/devil/devil/utils/usb_hubs.py165
-rw-r--r--third_party/catapult/devil/devil/utils/watchdog_timer.py47
-rw-r--r--third_party/catapult/devil/devil/utils/zip_utils.py33
-rw-r--r--third_party/catapult/devil/docs/adb_wrapper.md388
-rw-r--r--third_party/catapult/devil/docs/device_blacklist.md59
-rw-r--r--third_party/catapult/devil/docs/device_utils.md1041
-rw-r--r--third_party/catapult/devil/docs/markdown.md139
-rw-r--r--third_party/catapult/devil/docs/persistent_device_list.md41
-rw-r--r--third_party/catapult/devil/pylintrc68
-rw-r--r--third_party/jinja2/AUTHORS1
-rw-r--r--third_party/jinja2/Jinja2-2.10.tar.gz.md51
-rw-r--r--third_party/jinja2/Jinja2-2.10.tar.gz.sha5121
-rw-r--r--third_party/jinja2/Jinja2-2.8.tar.gz.md51
-rw-r--r--third_party/jinja2/Jinja2-2.8.tar.gz.sha5121
-rw-r--r--third_party/jinja2/README.chromium15
-rw-r--r--third_party/jinja2/__init__.py21
-rw-r--r--third_party/jinja2/_compat.py22
-rw-r--r--third_party/jinja2/_identifier.py2
-rw-r--r--third_party/jinja2/_stringdefs.py132
-rw-r--r--third_party/jinja2/asyncfilters.py146
-rw-r--r--third_party/jinja2/asyncsupport.py256
-rw-r--r--third_party/jinja2/bccache.py6
-rw-r--r--third_party/jinja2/compiler.py1135
-rw-r--r--third_party/jinja2/constants.py2
-rw-r--r--third_party/jinja2/debug.py42
-rw-r--r--third_party/jinja2/defaults.py19
-rw-r--r--third_party/jinja2/environment.py177
-rw-r--r--third_party/jinja2/exceptions.py2
-rw-r--r--third_party/jinja2/ext.py55
-rw-r--r--third_party/jinja2/filters.py416
-rwxr-xr-xthird_party/jinja2/get_jinja2.sh18
-rw-r--r--third_party/jinja2/idtracking.py286
-rw-r--r--third_party/jinja2/jinja2.gni31
-rw-r--r--third_party/jinja2/lexer.py57
-rw-r--r--third_party/jinja2/loaders.py6
-rw-r--r--third_party/jinja2/meta.py11
-rw-r--r--third_party/jinja2/nativetypes.py220
-rw-r--r--third_party/jinja2/nodes.py184
-rw-r--r--third_party/jinja2/optimizer.py25
-rw-r--r--third_party/jinja2/parser.py142
-rw-r--r--third_party/jinja2/runtime.py288
-rw-r--r--third_party/jinja2/sandbox.py128
-rw-r--r--third_party/jinja2/tests.py52
-rw-r--r--third_party/jinja2/utils.py140
-rw-r--r--third_party/jinja2/visitor.py2
-rw-r--r--ui/gfx/geometry/insets_f.h5
-rw-r--r--ui/gfx/geometry/mojo/BUILD.gn2
-rw-r--r--ui/gfx/geometry/mojo/DEPS4
-rw-r--r--ui/gfx/geometry/mojo/geometry.mojom12
-rw-r--r--ui/gfx/geometry/mojo/geometry.typemap2
-rw-r--r--ui/gfx/geometry/mojo/geometry_struct_traits.h13
-rw-r--r--ui/gfx/geometry/mojo/geometry_struct_traits_unittest.cc50
-rw-r--r--ui/gfx/geometry/point.h18
-rw-r--r--ui/gfx/geometry/rect.cc18
-rw-r--r--ui/gfx/geometry/rect.h39
-rw-r--r--ui/gfx/geometry/rect_f.cc52
-rw-r--r--ui/gfx/geometry/scroll_offset.cc15
-rw-r--r--ui/gfx/geometry/scroll_offset.h129
-rw-r--r--ui/gfx/geometry/size.cc6
-rw-r--r--ui/gfx/geometry/vector2d.cc10
-rw-r--r--ui/gfx/geometry/vector2d.h9
-rw-r--r--ui/gfx/geometry/vector2d_f.h6
-rw-r--r--ui/gfx/range/BUILD.gn4
-rw-r--r--ui/gfx/range/mojo/DEPS4
-rw-r--r--ui/gfx/range/mojo/range_struct_traits_unittest.cc15
-rw-r--r--ui/gfx/range/range.h5
2207 files changed, 172922 insertions, 120173 deletions
diff --git a/Android.bp b/Android.bp
index 8f10867ba9..ec721a70c0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -96,12 +96,13 @@ cc_defaults {
libchromeCommonSrc = [
"base/at_exit.cc",
+ "base/barrier_closure.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/big_endian.cc",
"base/build_time.cc",
"base/callback_helpers.cc",
"base/callback_internal.cc",
@@ -109,6 +110,7 @@ libchromeCommonSrc = [
"base/cpu.cc",
"base/debug/activity_tracker.cc",
"base/debug/alias.cc",
+ "base/debug/crash_logging.cc",
"base/debug/debugger.cc",
"base/debug/debugger_posix.cc",
"base/debug/dump_without_crashing.cc",
@@ -143,30 +145,41 @@ libchromeCommonSrc = [
"base/json/json_value_converter.cc",
"base/json/json_writer.cc",
"base/json/string_escape.cc",
- "base/lazy_instance.cc",
+ "base/lazy_instance_helpers.cc",
"base/location.cc",
"base/logging.cc",
"base/md5.cc",
"base/memory/aligned_memory.cc",
+ "base/memory/platform_shared_memory_region.cc",
+ "base/memory/platform_shared_memory_region_posix.cc",
+ "base/memory/read_only_shared_memory_region.cc",
"base/memory/ref_counted.cc",
"base/memory/ref_counted_memory.cc",
+ "base/memory/shared_memory_handle.cc",
+ "base/memory/shared_memory_handle_posix.cc",
"base/memory/shared_memory_helper.cc",
- "base/memory/singleton.cc",
+ "base/memory/shared_memory_mapping.cc",
+ "base/memory/unsafe_shared_memory_region.cc",
"base/memory/weak_ptr.cc",
+ "base/memory/writable_shared_memory_region.cc",
"base/message_loop/incoming_task_queue.cc",
"base/message_loop/message_loop.cc",
+ "base/message_loop/message_loop_current.cc",
"base/message_loop/message_loop_task_runner.cc",
"base/message_loop/message_pump.cc",
"base/message_loop/message_pump_default.cc",
"base/message_loop/message_pump_libevent.cc",
+ "base/message_loop/watchable_io_message_pump_posix.cc",
"base/metrics/bucket_ranges.cc",
+ "base/metrics/dummy_histogram.cc",
"base/metrics/field_trial.cc",
"base/metrics/field_trial_param_associator.cc",
- "base/metrics/metrics_hashes.cc",
- "base/metrics/histogram_base.cc",
"base/metrics/histogram.cc",
+ "base/metrics/histogram_base.cc",
+ "base/metrics/histogram_functions.cc",
"base/metrics/histogram_samples.cc",
"base/metrics/histogram_snapshot_manager.cc",
+ "base/metrics/metrics_hashes.cc",
"base/metrics/persistent_histogram_allocator.cc",
"base/metrics/persistent_memory_allocator.cc",
"base/metrics/persistent_sample_map.cc",
@@ -174,11 +187,14 @@ libchromeCommonSrc = [
"base/metrics/sample_vector.cc",
"base/metrics/sparse_histogram.cc",
"base/metrics/statistics_recorder.cc",
+ "base/native_library.cc",
+ "base/native_library_posix.cc",
+ "base/observer_list_threadsafe.cc",
"base/path_service.cc",
"base/pending_task.cc",
"base/pickle.cc",
- "base/posix/global_descriptors.cc",
"base/posix/file_descriptor_shuffle.cc",
+ "base/posix/global_descriptors.cc",
"base/posix/safe_strerror.cc",
"base/process/kill.cc",
"base/process/kill_posix.cc",
@@ -191,32 +207,32 @@ libchromeCommonSrc = [
"base/process/process_metrics.cc",
"base/process/process_metrics_posix.cc",
"base/process/process_posix.cc",
- "base/profiler/tracked_time.cc",
"base/rand_util.cc",
"base/rand_util_posix.cc",
"base/run_loop.cc",
+ "base/scoped_native_library.cc",
"base/sequence_checker_impl.cc",
"base/sequence_token.cc",
"base/sequenced_task_runner.cc",
"base/sha1.cc",
+ "base/strings/nullable_string16.cc",
"base/strings/pattern.cc",
"base/strings/safe_sprintf.cc",
"base/strings/string16.cc",
"base/strings/string_number_conversions.cc",
"base/strings/string_piece.cc",
- "base/strings/stringprintf.cc",
"base/strings/string_split.cc",
"base/strings/string_util.cc",
"base/strings/string_util_constants.cc",
- "base/strings/utf_string_conversions.cc",
+ "base/strings/stringprintf.cc",
"base/strings/utf_string_conversion_utils.cc",
+ "base/strings/utf_string_conversions.cc",
+ "base/sync_socket_posix.cc",
"base/synchronization/atomic_flag.cc",
"base/synchronization/condition_variable_posix.cc",
"base/synchronization/lock.cc",
"base/synchronization/lock_impl_posix.cc",
- "base/synchronization/read_write_lock_posix.cc",
"base/synchronization/waitable_event_posix.cc",
- "base/sync_socket_posix.cc",
"base/sys_info.cc",
"base/sys_info_posix.cc",
"base/task/cancelable_task_tracker.cc",
@@ -230,11 +246,12 @@ libchromeCommonSrc = [
"base/third_party/dynamic_annotations/dynamic_annotations.c",
"base/third_party/icu/icu_utf.cc",
"base/third_party/nspr/prtime.cc",
- "base/threading/non_thread_safe_impl.cc",
"base/threading/platform_thread_posix.cc",
"base/threading/post_task_and_reply_impl.cc",
+ "base/threading/scoped_blocking_call.cc",
+ "base/threading/sequence_local_storage_map.cc",
+ "base/threading/sequence_local_storage_slot.cc",
"base/threading/sequenced_task_runner_handle.cc",
- "base/threading/sequenced_worker_pool.cc",
"base/threading/simple_thread.cc",
"base/threading/thread.cc",
"base/threading/thread_checker_impl.cc",
@@ -244,19 +261,19 @@ libchromeCommonSrc = [
"base/threading/thread_local_storage_posix.cc",
"base/threading/thread_restrictions.cc",
"base/threading/thread_task_runner_handle.cc",
- "base/threading/worker_pool.cc",
- "base/threading/worker_pool_posix.cc",
"base/time/clock.cc",
"base/time/default_clock.cc",
"base/time/default_tick_clock.cc",
"base/time/tick_clock.cc",
"base/time/time.cc",
- "base/time/time_posix.cc",
+ "base/time/time_conversion_posix.cc",
+ "base/time/time_exploded_posix.cc",
+ "base/time/time_now_posix.cc",
+ "base/time/time_override.cc",
"base/timer/elapsed_timer.cc",
"base/timer/timer.cc",
- "base/tracked_objects.cc",
- "base/tracking_info.cc",
"base/unguessable_token.cc",
+ "base/value_iterators.cc",
"base/values.cc",
"base/version.cc",
"base/vlog.cc",
@@ -283,7 +300,7 @@ libchromeLinuxSrc = [
"base/files/file_path_watcher_linux.cc",
"base/files/file_util_linux.cc",
"base/memory/shared_memory_posix.cc",
- "base/posix/unix_domain_socket_linux.cc",
+ "base/posix/unix_domain_socket.cc",
"base/process/internal_linux.cc",
"base/process/memory_linux.cc",
"base/process/process_handle_linux.cc",
@@ -325,8 +342,8 @@ cc_library {
"libevent",
],
static_libs: [
- "libmodpb64",
"libgtest_prod",
+ "libmodpb64",
],
target: {
linux: {
@@ -338,8 +355,8 @@ cc_library {
android: {
srcs: libchromeAndroidSrc,
shared_libs: [
- "liblog",
"libcutils",
+ "liblog",
],
},
},
@@ -424,6 +441,7 @@ cc_test {
"base/atomicops_unittest.cc",
"base/base64_unittest.cc",
"base/base64url_unittest.cc",
+ "base/big_endian_unittest.cc",
"base/bind_unittest.cc",
"base/bits_unittest.cc",
"base/build_time_unittest.cc",
@@ -436,12 +454,12 @@ cc_test {
"base/debug/activity_tracker_unittest.cc",
"base/debug/debugger_unittest.cc",
"base/debug/leak_tracker_unittest.cc",
- "base/debug/task_annotator_unittest.cc",
"base/environment_unittest.cc",
"base/files/dir_reader_posix_unittest.cc",
"base/files/file_descriptor_watcher_posix_unittest.cc",
- "base/files/file_path_watcher_unittest.cc",
+ "base/files/file_enumerator_unittest.cc",
"base/files/file_path_unittest.cc",
+ "base/files/file_path_watcher_unittest.cc",
"base/files/file_unittest.cc",
"base/files/important_file_writer_unittest.cc",
"base/files/scoped_temp_dir_unittest.cc",
@@ -460,19 +478,17 @@ cc_test {
"base/memory/linked_ptr_unittest.cc",
"base/memory/ref_counted_memory_unittest.cc",
"base/memory/ref_counted_unittest.cc",
- "base/memory/scoped_vector_unittest.cc",
"base/memory/singleton_unittest.cc",
"base/memory/weak_ptr_unittest.cc",
- "base/message_loop/message_loop_test.cc",
"base/message_loop/message_loop_task_runner_unittest.cc",
"base/message_loop/message_loop_unittest.cc",
"base/metrics/bucket_ranges_unittest.cc",
"base/metrics/field_trial_unittest.cc",
- "base/metrics/metrics_hashes_unittest.cc",
"base/metrics/histogram_base_unittest.cc",
"base/metrics/histogram_macros_unittest.cc",
"base/metrics/histogram_snapshot_manager_unittest.cc",
"base/metrics/histogram_unittest.cc",
+ "base/metrics/metrics_hashes_unittest.cc",
"base/metrics/persistent_histogram_allocator_unittest.cc",
"base/metrics/persistent_memory_allocator_unittest.cc",
"base/metrics/persistent_sample_map_unittest.cc",
@@ -480,15 +496,13 @@ cc_test {
"base/metrics/sample_vector_unittest.cc",
"base/metrics/sparse_histogram_unittest.cc",
"base/metrics/statistics_recorder_unittest.cc",
- "base/numerics/safe_numerics_unittest.cc",
"base/observer_list_unittest.cc",
"base/optional_unittest.cc",
"base/pickle_unittest.cc",
"base/posix/file_descriptor_shuffle_unittest.cc",
- "base/posix/unix_domain_socket_linux_unittest.cc",
+ "base/posix/unix_domain_socket_unittest.cc",
"base/process/process_info_unittest.cc",
"base/process/process_metrics_unittest.cc",
- "base/profiler/tracked_time_unittest.cc",
"base/rand_util_unittest.cc",
"base/scoped_clear_errno_unittest.cc",
"base/scoped_generic_unittest.cc",
@@ -501,16 +515,16 @@ cc_test {
"base/strings/string16_unittest.cc",
"base/strings/string_number_conversions_unittest.cc",
"base/strings/string_piece_unittest.cc",
- "base/strings/stringprintf_unittest.cc",
"base/strings/string_split_unittest.cc",
"base/strings/string_util_unittest.cc",
+ "base/strings/stringprintf_unittest.cc",
"base/strings/sys_string_conversions_unittest.cc",
"base/strings/utf_string_conversions_unittest.cc",
+ "base/sync_socket_unittest.cc",
"base/synchronization/atomic_flag_unittest.cc",
"base/synchronization/condition_variable_unittest.cc",
"base/synchronization/lock_unittest.cc",
"base/synchronization/waitable_event_unittest.cc",
- "base/sync_socket_unittest.cc",
"base/sys_info_unittest.cc",
"base/task/cancelable_task_tracker_unittest.cc",
"base/task_runner_util_unittest.cc",
@@ -520,22 +534,22 @@ cc_test {
"base/task_scheduler/sequence_unittest.cc",
"base/task_scheduler/task_traits.cc",
"base/template_util_unittest.cc",
+ "base/test/metrics/histogram_tester.cc",
"base/test/mock_entropy_provider.cc",
"base/test/multiprocess_test.cc",
- "base/test/opaque_ref_counted.cc",
"base/test/scoped_feature_list.cc",
"base/test/scoped_locale.cc",
- "base/test/sequenced_worker_pool_owner.cc",
+ "base/test/simple_test_tick_clock.cc",
"base/test/test_file_util.cc",
"base/test/test_file_util_linux.cc",
"base/test/test_file_util_posix.cc",
"base/test/test_io_thread.cc",
"base/test/test_mock_time_task_runner.cc",
"base/test/test_pending_task.cc",
+ "base/test/test_shared_memory_util.cc",
"base/test/test_simple_task_runner.cc",
"base/test/test_switches.cc",
"base/test/test_timeouts.cc",
- "base/threading/non_thread_safe_unittest.cc",
"base/threading/platform_thread_unittest.cc",
"base/threading/simple_thread_unittest.cc",
"base/threading/thread_checker_unittest.cc",
@@ -544,13 +558,10 @@ cc_test {
"base/threading/thread_local_storage_unittest.cc",
"base/threading/thread_local_unittest.cc",
"base/threading/thread_unittest.cc",
- "base/threading/worker_pool_posix_unittest.cc",
- "base/threading/worker_pool_unittest.cc",
"base/time/pr_time_unittest.cc",
"base/time/time_unittest.cc",
"base/timer/hi_res_timer_manager_unittest.cc",
- "base/timer/timer_unittest.cc",
- "base/tracked_objects_unittest.cc",
+ "base/timer/mock_timer.cc",
"base/tuple_unittest.cc",
"base/values_unittest.cc",
"base/version_unittest.cc",
@@ -587,16 +598,25 @@ 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/native_struct.mojom",
"mojo/public/interfaces/bindings/pipe_control_messages.mojom",
+ "mojo/public/mojom/base/big_buffer.mojom",
+ "mojo/public/mojom/base/big_string.mojom",
+ "mojo/public/mojom/base/file.mojom",
+ "mojo/public/mojom/base/file_error.mojom",
+ "mojo/public/mojom/base/file_info.mojom",
+ "mojo/public/mojom/base/file_path.mojom",
+ "mojo/public/mojom/base/process_id.mojom",
+ "mojo/public/mojom/base/read_only_buffer.mojom",
+ "mojo/public/mojom/base/ref_counted_memory.mojom",
+ "mojo/public/mojom/base/shared_memory.mojom",
+ "mojo/public/mojom/base/string16.mojom",
+ "mojo/public/mojom/base/text_direction.mojom",
+ "mojo/public/mojom/base/thread_priority.mojom",
+ "mojo/public/mojom/base/time.mojom",
+ "mojo/public/mojom/base/unguessable_token.mojom",
+ "mojo/public/mojom/base/values.mojom",
"ui/gfx/geometry/mojo/geometry.mojom",
"ui/gfx/range/mojo/range.mojom",
],
@@ -616,29 +636,35 @@ filegroup {
// No WTF support.
"mojo/public/cpp/bindings/lib/string_traits_wtf.cc",
- // Exclude windows/mac/ios files.
+ // Exclude windows/mac/ios/fuchsia files.
"**/*_win.cc",
- "mojo/edk/system/mach_port_relay.cc",
+ "**/*_fuchsia.cc",
+ "mojo/core/mach_port_relay.*",
+ "mojo/public/cpp/base/logfont_win*",
+ "mojo/public/mojom/base/logfont_win*",
// 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/core/core_test_base.*",
+ "mojo/core/test/*",
+ "mojo/core/test_utils.*",
"mojo/public/c/system/tests/**/*",
"mojo/public/cpp/bindings/tests/**/*",
"mojo/public/cpp/system/tests/**/*",
"mojo/public/cpp/test_support/**/*",
+ "mojo/public/java/system/javatests/**/*",
"mojo/public/tests/**/*",
+
+ // Exclude memory allocator unsupported feature
+ "mojo/public/cpp/base/memory_allocator_dump_cross_process_uid*",
+
+ // Exclude fuzzers
+ "mojo/public/tools/fuzzers/**/*",
],
}
@@ -661,7 +687,17 @@ python_binary_host {
srcs: [
"base/android/jni_generator/jni_generator.py",
"build/**/*.py",
- "third_party/catapult/devil/devil/**/*.py",
+ ],
+ defaults: ["libmojo_scripts"],
+}
+
+python_binary_host {
+ name: "jni_registration_generator",
+ main: "base/android/jni_generator/jni_registration_generator.py",
+ srcs: [
+ "base/android/jni_generator/jni_generator.py",
+ "base/android/jni_generator/jni_registration_generator.py",
+ "build/**/*.py",
],
defaults: ["libmojo_scripts"],
}
@@ -670,9 +706,8 @@ 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",
+ "mojo/public/tools/bindings/**/*.py",
"third_party/jinja2/**/*.py",
"third_party/markupsafe/**/*.py",
"third_party/ply/**/*.py",
@@ -685,18 +720,11 @@ python_binary_host {
defaults: ["libmojo_scripts"],
}
-// TODO(lhchavez): Delete this once all other projects have been migrated.
-cc_prebuilt_binary {
- name: "mojom_source_generator_sh",
- srcs: ["libchrome_tools/mojom_source_generator.sh"],
- host_supported: true,
-}
-
genrule {
name: "libmojo_mojom_templates",
cmd: "$(location mojom_bindings_generator)" +
- " --use_bundled_pylibs precompile" +
- " -o $(genDir)",
+ " --use_bundled_pylibs precompile" +
+ " -o $(genDir)",
tools: [
"mojom_bindings_generator",
@@ -716,6 +744,10 @@ python_binary_host {
"build/gn_helpers.py",
"libchrome_tools/mojom_generate_type_mappings.py",
"mojo/public/tools/bindings/generate_type_mappings.py",
+ "mojo/public/tools/bindings/pylib/mojom/fileutil.py",
+ "mojo/public/tools/bindings/pylib/mojom/generate/generator.py",
+ "mojo/public/tools/bindings/pylib/mojom/generate/module.py",
+ "mojo/public/tools/bindings/pylib/mojom/generate/pack.py",
],
defaults: ["libmojo_scripts"],
}
@@ -723,20 +755,30 @@ python_binary_host {
genrule {
name: "libmojo_common_custom_types__type_mappings",
cmd: "$(location mojom_generate_type_mappings)" +
- " --output=$(out)" +
- " $(in)",
+ " --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",
+ "mojo/public/cpp/base/big_buffer.typemap",
+ "mojo/public/cpp/base/big_string.typemap",
+ "mojo/public/cpp/base/file.typemap",
+ "mojo/public/cpp/base/file_error.typemap",
+ "mojo/public/cpp/base/file_info.typemap",
+ "mojo/public/cpp/base/file_path.typemap",
+ "mojo/public/cpp/base/process_id.typemap",
+ "mojo/public/cpp/base/read_only_buffer.typemap",
+ "mojo/public/cpp/base/ref_counted_memory.typemap",
+ "mojo/public/cpp/base/shared_memory.typemap",
+ "mojo/public/cpp/base/string16.typemap",
+ "mojo/public/cpp/base/text_direction.typemap",
+ "mojo/public/cpp/base/thread_priority.typemap",
+ "mojo/public/cpp/base/time.typemap",
+ "mojo/public/cpp/base/unguessable_token.typemap",
+ "mojo/public/cpp/base/values.typemap",
+ "ui/gfx/geometry/mojo/geometry.typemap",
+ "ui/gfx/range/mojo/range.typemap",
],
out: ["common_custom_types__type_mappings"],
}
@@ -764,17 +806,14 @@ generate_mojom_srcs {
typemaps: [":libmojo_common_custom_types__type_mappings"],
}
-// 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)",
+ " --jni_generator=$(location jni_generator)" +
+ " --output_dir=$(genDir)/jni" +
+ " --includes=base/android/jni_generator/jni_generator_helper.h" +
+ " --ptr_type=long" +
+ " $(in)",
tools: [
"jni_generator",
@@ -786,21 +825,51 @@ genrule {
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",
+ "base/android/java/src/org/chromium/base/JavaExceptionReporter.java",
+ "base/android/java/src/org/chromium/base/ThreadUtils.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/impl/CoreImpl.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/impl/WatcherImpl.java",
],
out: [
- "jni/BuildInfo_jni.h",
- "jni/ContextUtils_jni.h",
"jni/BaseRunLoop_jni.h",
+ "jni/BuildInfo_jni.h",
"jni/CoreImpl_jni.h",
+ "jni/JavaExceptionReporter_jni.h",
"jni/WatcherImpl_jni.h",
],
}
+genrule {
+ name: "libmojo_jni_registration_headers",
+ cmd: "$(location libchrome_tools/jni_registration_generator_helper.sh)" +
+ " --jni_generator=$(location jni_registration_generator)" +
+ " --output=$(genDir)/jni/libmojo_jni_registrations.h" +
+ " $(in)",
+
+ tools: [
+ "jni_registration_generator",
+ ],
+
+ tool_files: [
+ "libchrome_tools/jni_registration_generator_helper.sh",
+ ],
+
+ srcs: [
+ "base/android/java/src/org/chromium/base/BuildInfo.java",
+ "base/android/java/src/org/chromium/base/JavaExceptionReporter.java",
+ "base/android/java/src/org/chromium/base/ThreadUtils.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/impl/CoreImpl.java",
+ "mojo/public/java/system/src/org/chromium/mojo/system/impl/WatcherImpl.java",
+ ],
+
+ out: [
+ "jni/libmojo_jni_registrations.h",
+ ],
+}
+
cc_library_shared {
name: "libmojo",
vendor_available: true,
@@ -808,17 +877,20 @@ cc_library_shared {
generated_sources: ["libmojo_mojom_srcs"],
generated_headers: [
"libmojo_jni_headers",
+ "libmojo_jni_registration_headers",
"libmojo_mojom_headers",
],
export_generated_headers: [
- "libmojo_jni_headers",
+ "libmojo_jni_registration_headers",
"libmojo_mojom_headers",
],
srcs: [
+ ":libmojo_mojo_sources",
"base/android/build_info.cc",
- "base/android/context_utils.cc",
+ "base/android/java_exception_reporter.cc",
"base/android/jni_android.cc",
+ "base/android/jni_array.cc",
"base/android/jni_string.cc",
"base/android/scoped_java_ref.cc",
"ipc/ipc_message.cc",
@@ -829,32 +901,32 @@ cc_library_shared {
"ipc/ipc_mojo_message_helper.cc",
"ipc/ipc_mojo_param_traits.cc",
"ipc/ipc_platform_file_attachment_posix.cc",
- ":libmojo_mojo_sources",
+ "ipc/native_handle_type_converters.cc",
],
cflags: [
+ "-DMOJO_CORE_LEGACY_PROTOCOL",
"-Wall",
"-Werror",
- "-Wno-unused-parameter",
"-Wno-missing-field-initializers",
- "-DMOJO_EDK_LEGACY_PROTOCOL",
+ "-Wno-unused-parameter",
],
// 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",
+ "-Wno-extra",
+ "-Wno-ignored-qualifiers",
+ "-Wno-non-virtual-dtor",
+ "-Wno-sign-promo",
],
shared_libs: [
- "libevent",
- "liblog",
"libchrome",
"libchrome-crypto",
+ "libevent",
+ "liblog",
],
header_libs: ["jni_headers"],
@@ -879,7 +951,11 @@ java_library {
":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",
+ "mojo/public/java/system/src/**/*.java",
+ ],
+
+ static_libs: [
+ "android-support-annotations",
],
}
diff --git a/base/DEPS b/base/DEPS
deleted file mode 100644
index 4b25f3f310..0000000000
--- a/base/DEPS
+++ /dev/null
@@ -1,16 +0,0 @@
-include_rules = [
- "+jni",
- "+third_party/ashmem",
- "+third_party/apple_apsl",
- "+third_party/ced",
- "+third_party/lss",
- "+third_party/modp_b64",
- "+third_party/tcmalloc",
-
- # These are implicitly brought in from the root, and we don't want them.
- "-ipc",
- "-url",
-
- # ICU dependendencies must be separate from the rest of base.
- "-i18n",
-]
diff --git a/base/PRESUBMIT.py b/base/PRESUBMIT.py
deleted file mode 100644
index 7fc8107658..0000000000
--- a/base/PRESUBMIT.py
+++ /dev/null
@@ -1,49 +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.
-
-"""Chromium presubmit script for src/base.
-
-See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
-for more details on the presubmit API built into depot_tools.
-"""
-
-def _CheckNoInterfacesInBase(input_api, output_api):
- """Checks to make sure no files in libbase.a have |@interface|."""
- pattern = input_api.re.compile(r'^\s*@interface', input_api.re.MULTILINE)
- files = []
- for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
- if (f.LocalPath().startswith('base/') and
- not "/ios/" in f.LocalPath() and
- not "/test/" in f.LocalPath() and
- not f.LocalPath().endswith('_unittest.mm') and
- not f.LocalPath().endswith('mac/sdk_forward_declarations.h')):
- contents = input_api.ReadFile(f)
- if pattern.search(contents):
- files.append(f)
-
- if len(files):
- return [ output_api.PresubmitError(
- 'Objective-C interfaces or categories are forbidden in libbase. ' +
- 'See http://groups.google.com/a/chromium.org/group/chromium-dev/' +
- 'browse_thread/thread/efb28c10435987fd',
- files) ]
- return []
-
-
-def _CommonChecks(input_api, output_api):
- """Checks common to both upload and commit."""
- results = []
- results.extend(_CheckNoInterfacesInBase(input_api, output_api))
- return results
-
-def CheckChangeOnUpload(input_api, output_api):
- results = []
- results.extend(_CommonChecks(input_api, output_api))
- return results
-
-
-def CheckChangeOnCommit(input_api, output_api):
- results = []
- results.extend(_CommonChecks(input_api, output_api))
- return results
diff --git a/base/allocator/README.md b/base/allocator/README.md
deleted file mode 100644
index a211732c3f..0000000000
--- a/base/allocator/README.md
+++ /dev/null
@@ -1,196 +0,0 @@
-This document describes how malloc / new calls are routed in the various Chrome
-platforms.
-
-Bare in mind that the chromium codebase does not always just use `malloc()`.
-Some examples:
- - Large parts of the renderer (Blink) use two home-brewed allocators,
- PartitionAlloc and BlinkGC (Oilpan).
- - Some subsystems, such as the V8 JavaScript engine, handle memory management
- autonomously.
- - Various parts of the codebase use abstractions such as `SharedMemory` or
- `DiscardableMemory` which, similarly to the above, have their own page-level
- memory management.
-
-Background
-----------
-The `allocator` target defines at compile-time the platform-specific choice of
-the allocator and extra-hooks which services calls to malloc/new. The relevant
-build-time flags involved are `use_allocator` and `win_use_allocator_shim`.
-
-The default choices are as follows:
-
-**Windows**
-`use_allocator: winheap`, the default Windows heap.
-Additionally, `static_library` (i.e. non-component) builds have a shim
-layer wrapping malloc/new, which is controlled by `win_use_allocator_shim`.
-The shim layer provides extra security features, such as preventing large
-allocations that can hit signed vs. unsigned bugs in third_party code.
-
-**Linux Desktop / CrOS**
-`use_allocator: tcmalloc`, a forked copy of tcmalloc which resides in
-`third_party/tcmalloc/chromium`. Setting `use_allocator: none` causes the build
-to fall back to the system (Glibc) symbols.
-
-**Android**
-`use_allocator: none`, always use the allocator symbols coming from Android's
-libc (Bionic). As it is developed as part of the OS, it is considered to be
-optimized for small devices and more memory-efficient than other choices.
-The actual implementation backing malloc symbols in Bionic is up to the board
-config and can vary (typically *dlmalloc* or *jemalloc* on most Nexus devices).
-
-**Mac/iOS**
-`use_allocator: none`, we always use the system's allocator implementation.
-
-In addition, when building for `asan` / `msan` / `syzyasan` `valgrind`, the
-both the allocator and the shim layer are disabled.
-
-Layering and build deps
------------------------
-The `allocator` target provides both the source files for tcmalloc (where
-applicable) and the linker flags required for the Windows shim layer.
-The `base` target is (almost) the only one depending on `allocator`. No other
-targets should depend on it, with the exception of the very few executables /
-dynamic libraries that don't depend, either directly or indirectly, on `base`
-within the scope of a linker unit.
-
-More importantly, **no other place outside of `/base` should depend on the
-specific allocator** (e.g., directly include `third_party/tcmalloc`).
-If such a functional dependency is required that should be achieved using
-abstractions in `base` (see `/base/allocator/allocator_extension.h` and
-`/base/memory/`)
-
-**Why `base` depends on `allocator`?**
-Because it needs to provide services that depend on the actual allocator
-implementation. In the past `base` used to pretend to be allocator-agnostic
-and get the dependencies injected by other layers. This ended up being an
-inconsistent mess.
-See the [allocator cleanup doc][url-allocator-cleanup] for more context.
-
-Linker unit targets (executables and shared libraries) that depend in some way
-on `base` (most of the targets in the codebase) get automatically the correct
-set of linker flags to pull in tcmalloc or the Windows shim-layer.
-
-
-Source code
------------
-This directory contains just the allocator (i.e. shim) layer that switches
-between the different underlying memory allocation implementations.
-
-The tcmalloc library originates outside of Chromium and exists in
-`../../third_party/tcmalloc` (currently, the actual location is defined in the
-allocator.gyp file). The third party sources use a vendor-branch SCM pattern to
-track Chromium-specific changes independently from upstream changes.
-
-The general intent is to push local changes upstream so that over
-time we no longer need any forked files.
-
-
-Unified allocator shim
-----------------------
-On most platform, Chrome overrides the malloc / operator new symbols (and
-corresponding free / delete and other variants). This is to enforce security
-checks and lately to enable the
-[memory-infra heap profiler][url-memory-infra-heap-profiler].
-Historically each platform had its special logic for defining the allocator
-symbols in different places of the codebase. The unified allocator shim is
-a project aimed to unify the symbol definition and allocator routing logic in
-a central place.
-
- - Full documentation: [Allocator shim design doc][url-allocator-shim].
- - Current state: Available and enabled by default on Linux, CrOS and Android.
- - Tracking bug: [https://crbug.com/550886][crbug.com/550886].
- - Build-time flag: `use_experimental_allocator_shim`.
-
-**Overview of the unified allocator shim**
-The allocator shim consists of three stages:
-```
-+-------------------------+ +-----------------------+ +----------------+
-| malloc & friends | -> | shim layer | -> | Routing to |
-| symbols definition | | implementation | | allocator |
-+-------------------------+ +-----------------------+ +----------------+
-| - libc symbols (malloc, | | - Security checks | | - tcmalloc |
-| calloc, free, ...) | | - Chain of dispatchers| | - glibc |
-| - C++ symbols (operator | | that can intercept | | - Android |
-| new, delete, ...) | | and override | | bionic |
-| - glibc weak symbols | | allocations | | - WinHeap |
-| (__libc_malloc, ...) | +-----------------------+ +----------------+
-+-------------------------+
-```
-
-**1. malloc symbols definition**
-This stage takes care of overriding the symbols `malloc`, `free`,
-`operator new`, `operator delete` and friends and routing those calls inside the
-allocator shim (next point).
-This is taken care of by the headers in `allocator_shim_override_*`.
-
-*On Linux/CrOS*: the allocator symbols are defined as exported global symbols
-in `allocator_shim_override_libc_symbols.h` (for `malloc`, `free` and friends)
-and in `allocator_shim_override_cpp_symbols.h` (for `operator new`,
-`operator delete` and friends).
-This enables proper interposition of malloc symbols referenced by the main
-executable and any third party libraries. Symbol resolution on Linux is a breadth first search that starts from the root link unit, that is the executable
-(see EXECUTABLE AND LINKABLE FORMAT (ELF) - Portable Formats Specification).
-Additionally, when tcmalloc is the default allocator, some extra glibc symbols
-are also defined in `allocator_shim_override_glibc_weak_symbols.h`, for subtle
-reasons explained in that file.
-The Linux/CrOS shim was introduced by
-[crrev.com/1675143004](https://crrev.com/1675143004).
-
-*On Android*: load-time symbol interposition (unlike the Linux/CrOS case) is not
-possible. This is because Android processes are `fork()`-ed from the Android
-zygote, which pre-loads libc.so and only later native code gets loaded via
-`dlopen()` (symbols from `dlopen()`-ed libraries get a different resolution
-scope).
-In this case, the approach instead of wrapping symbol resolution at link time
-(i.e. during the build), via the `--Wl,-wrap,malloc` linker flag.
-The use of this wrapping flag causes:
- - All references to allocator symbols in the Chrome codebase to be rewritten as
- references to `__wrap_malloc` and friends. The `__wrap_malloc` symbols are
- defined in the `allocator_shim_override_linker_wrapped_symbols.h` and
- route allocator calls inside the shim layer.
- - The reference to the original `malloc` symbols (which typically is defined by
- the system's libc.so) are accessible via the special `__real_malloc` and
- friends symbols (which will be relocated, at load time, against `malloc`).
-
-In summary, this approach is transparent to the dynamic loader, which still sees
-undefined symbol references to malloc symbols.
-These symbols will be resolved against libc.so as usual.
-More details in [crrev.com/1719433002](https://crrev.com/1719433002).
-
-**2. Shim layer implementation**
-This stage contains the actual shim implementation. This consists of:
-- A singly linked list of dispatchers (structs with function pointers to `malloc`-like functions). Dispatchers can be dynamically inserted at runtime
-(using the `InsertAllocatorDispatch` API). They can intercept and override
-allocator calls.
-- The security checks (suicide on malloc-failure via `std::new_handler`, etc).
-This happens inside `allocator_shim.cc`
-
-**3. Final allocator routing**
-The final element of the aforementioned dispatcher chain is statically defined
-at build time and ultimately routes the allocator calls to the actual allocator
-(as described in the *Background* section above). This is taken care of by the
-headers in `allocator_shim_default_dispatch_to_*` files.
-
-
-Appendixes
-----------
-**How does the Windows shim layer replace the malloc symbols?**
-The mechanism for hooking LIBCMT in Windows is rather tricky. The core
-problem is that by default, the Windows library does not declare malloc and
-free as weak symbols. Because of this, they cannot be overridden. To work
-around this, we start with the LIBCMT.LIB, and manually remove all allocator
-related functions from it using the visual studio library tool. Once removed,
-we can now link against the library and provide custom versions of the
-allocator related functionality.
-See the script `preb_libc.py` in this folder.
-
-Related links
--------------
-- [Unified allocator shim doc - Feb 2016][url-allocator-shim]
-- [Allocator cleanup doc - Jan 2016][url-allocator-cleanup]
-- [Proposal to use PartitionAlloc as default allocator](https://crbug.com/339604)
-- [Memory-Infra: Tools to profile memory usage in Chrome](/docs/memory-infra/README.md)
-
-[url-allocator-cleanup]: https://docs.google.com/document/d/1V77Kgp_4tfaaWPEZVxNevoD02wXiatnAv7Ssgr0hmjg/edit?usp=sharing
-[url-memory-infra-heap-profiler]: /docs/memory-infra/heap_profiler.md
-[url-allocator-shim]: https://docs.google.com/document/d/1yKlO1AO4XjpDad9rjcBOI15EKdAGsuGO_IeZy0g0kxo/edit?usp=sharing
diff --git a/base/allocator/allocator_extension.cc b/base/allocator/allocator_extension.cc
index 9a3d114f72..b6ddbaa872 100644
--- a/base/allocator/allocator_extension.cc
+++ b/base/allocator/allocator_extension.cc
@@ -7,9 +7,9 @@
#include "base/logging.h"
#if defined(USE_TCMALLOC)
-#include "third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h"
-#include "third_party/tcmalloc/chromium/src/gperftools/malloc_extension.h"
-#include "third_party/tcmalloc/chromium/src/gperftools/malloc_hook.h"
+#include "third_party/tcmalloc/gperftools-2.0/chromium/src/gperftools/heap-profiler.h"
+#include "third_party/tcmalloc/gperftools-2.0/chromium/src/gperftools/malloc_extension.h"
+#include "third_party/tcmalloc/gperftools-2.0/chromium/src/gperftools/malloc_hook.h"
#endif
namespace base {
diff --git a/base/allocator/allocator_shim.cc b/base/allocator/allocator_shim.cc
index 0caeb73490..faffae22d8 100644
--- a/base/allocator/allocator_shim.cc
+++ b/base/allocator/allocator_shim.cc
@@ -41,10 +41,6 @@ subtle::AtomicWord g_chain_head = reinterpret_cast<subtle::AtomicWord>(
bool g_call_new_handler_on_malloc_failure = false;
-#if !defined(OS_WIN)
-subtle::Atomic32 g_new_handler_lock = 0;
-#endif
-
inline size_t GetCachedPageSize() {
static size_t pagesize = 0;
if (!pagesize)
@@ -58,17 +54,7 @@ bool CallNewHandler(size_t size) {
#if defined(OS_WIN)
return base::allocator::WinCallNewHandler(size);
#else
- // TODO(primiano): C++11 has introduced ::get_new_handler() which is supposed
- // to be thread safe and would avoid the spinlock boilerplate here. However
- // it doesn't seem to be available yet in the Linux chroot headers yet.
- std::new_handler nh;
- {
- while (subtle::Acquire_CompareAndSwap(&g_new_handler_lock, 0, 1))
- PlatformThread::YieldCurrentThread();
- nh = std::set_new_handler(0);
- ignore_result(std::set_new_handler(nh));
- subtle::Release_Store(&g_new_handler_lock, 0);
- }
+ std::new_handler nh = std::get_new_handler();
if (!nh)
return false;
(*nh)();
@@ -345,6 +331,6 @@ void InitializeAllocatorShim() {
#endif
#if (defined(__GNUC__) && defined(__EXCEPTIONS)) || \
- (defined(_HAS_EXCEPTIONS) && _HAS_EXCEPTIONS)
+ (defined(_MSC_VER) && defined(_CPPUNWIND))
#error This code cannot be used when exceptions are turned on.
#endif
diff --git a/base/allocator/allocator_shim.h b/base/allocator/allocator_shim.h
index 65ac1eb7de..527e414ded 100644
--- a/base/allocator/allocator_shim.h
+++ b/base/allocator/allocator_shim.h
@@ -13,7 +13,7 @@
namespace base {
namespace allocator {
-// Allocator Shim API. Allows to to:
+// Allocator Shim API. Allows to:
// - Configure the behavior of the allocator (what to do on OOM failures).
// - Install new hooks (AllocatorDispatch) in the allocator chain.
diff --git a/base/allocator/allocator_shim_default_dispatch_to_linker_wrapped_symbols.cc b/base/allocator/allocator_shim_default_dispatch_to_linker_wrapped_symbols.cc
index e33754a443..c351a7c925 100644
--- a/base/allocator/allocator_shim_default_dispatch_to_linker_wrapped_symbols.cc
+++ b/base/allocator/allocator_shim_default_dispatch_to_linker_wrapped_symbols.cc
@@ -9,6 +9,9 @@
#if defined(OS_ANDROID) && __ANDROID_API__ < 17
#include <dlfcn.h>
+// This is defined in malloc.h on other platforms. We just need the definition
+// for the decltype(malloc_usable_size)* call to work.
+size_t malloc_usable_size(const void*);
#endif
// This translation unit defines a default dispatch for the allocator shim which
diff --git a/base/allocator/allocator_shim_override_cpp_symbols.h b/base/allocator/allocator_shim_override_cpp_symbols.h
index 3313687250..b1e6ee2509 100644
--- a/base/allocator/allocator_shim_override_cpp_symbols.h
+++ b/base/allocator/allocator_shim_override_cpp_symbols.h
@@ -49,3 +49,11 @@ SHIM_ALWAYS_EXPORT void operator delete[](void* p,
const std::nothrow_t&) __THROW {
ShimCppDelete(p);
}
+
+SHIM_ALWAYS_EXPORT void operator delete(void* p, size_t) __THROW {
+ ShimCppDelete(p);
+}
+
+SHIM_ALWAYS_EXPORT void operator delete[](void* p, size_t) __THROW {
+ ShimCppDelete(p);
+}
diff --git a/base/allocator/buildflags.h b/base/allocator/buildflags.h
new file mode 100644
index 0000000000..c547e2dd73
--- /dev/null
+++ b/base/allocator/buildflags.h
@@ -0,0 +1,5 @@
+#ifndef BASE_ALLOCATOR_BUILDFLAGS_H_
+#define BASE_ALLOCATOR_BUILDFLAGS_H_
+#include "build/buildflag.h"
+#define BUILDFLAG_INTERNAL_USE_ALLOCATOR_SHIM() (0)
+#endif // BASE_ALLOCATOR_BUILDFLAGS_H_
diff --git a/base/allocator/features.h b/base/allocator/features.h
deleted file mode 100644
index ea479cd5a0..0000000000
--- a/base/allocator/features.h
+++ /dev/null
@@ -1,15 +0,0 @@
-// 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_
diff --git a/base/android/base_jni_onload.h b/base/android/base_jni_onload.h
new file mode 100644
index 0000000000..5d7cc0ad21
--- /dev/null
+++ b/base/android/base_jni_onload.h
@@ -0,0 +1,23 @@
+// 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_BASE_JNI_ONLOAD_H_
+#define BASE_ANDROID_BASE_JNI_ONLOAD_H_
+
+#include <jni.h>
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/callback.h"
+
+namespace base {
+namespace android {
+
+// Returns whether initialization succeeded.
+BASE_EXPORT bool OnJNIOnLoadInit();
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_BASE_JNI_ONLOAD_H_
diff --git a/base/android/build_info.cc b/base/android/build_info.cc
index 869c703f3f..bebf901608 100644
--- a/base/android/build_info.cc
+++ b/base/android/build_info.cc
@@ -7,28 +7,39 @@
#include <string>
#include "base/android/jni_android.h"
-#include "base/android/jni_string.h"
+#include "base/android/jni_array.h"
#include "base/android/scoped_java_ref.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
+#include "base/strings/string_number_conversions.h"
#include "jni/BuildInfo_jni.h"
+namespace base {
+namespace android {
+
namespace {
// We are leaking these strings.
-const char* StrDupJString(const base::android::JavaRef<jstring>& java_string) {
- std::string str = ConvertJavaStringToUTF8(java_string);
- return strdup(str.c_str());
+const char* StrDupParam(const std::vector<std::string>& params, int index) {
+ return strdup(params[index].c_str());
}
-} // namespace
+int GetIntParam(const std::vector<std::string>& params, int index) {
+ int ret = 0;
+ bool success = StringToInt(params[index], &ret);
+ DCHECK(success);
+ return ret;
+}
-namespace base {
-namespace android {
+} // namespace
struct BuildInfoSingletonTraits {
static BuildInfo* New() {
- return new BuildInfo(AttachCurrentThread());
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobjectArray> params_objs = Java_BuildInfo_getAll(env);
+ std::vector<std::string> params;
+ AppendJavaStringArrayToStringVector(env, params_objs.obj(), &params);
+ return new BuildInfo(params);
}
static void Delete(BuildInfo* x) {
@@ -42,39 +53,35 @@ struct BuildInfoSingletonTraits {
#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) {}
+BuildInfo::BuildInfo(const std::vector<std::string>& params)
+ : brand_(StrDupParam(params, 0)),
+ device_(StrDupParam(params, 1)),
+ android_build_id_(StrDupParam(params, 2)),
+ manufacturer_(StrDupParam(params, 3)),
+ model_(StrDupParam(params, 4)),
+ sdk_int_(GetIntParam(params, 5)),
+ build_type_(StrDupParam(params, 6)),
+ board_(StrDupParam(params, 7)),
+ host_package_name_(StrDupParam(params, 8)),
+ host_version_code_(StrDupParam(params, 9)),
+ host_package_label_(StrDupParam(params, 10)),
+ package_name_(StrDupParam(params, 11)),
+ package_version_code_(StrDupParam(params, 12)),
+ package_version_name_(StrDupParam(params, 13)),
+ android_build_fp_(StrDupParam(params, 14)),
+ gms_version_code_(StrDupParam(params, 15)),
+ installer_package_name_(StrDupParam(params, 16)),
+ abi_name_(StrDupParam(params, 17)),
+ firebase_app_id_(StrDupParam(params, 18)),
+ custom_themes_(StrDupParam(params, 19)),
+ resources_version_(StrDupParam(params, 20)),
+ extracted_file_suffix_(params[21]),
+ is_at_least_p_(GetIntParam(params, 22)) {}
// static
BuildInfo* BuildInfo::GetInstance() {
return Singleton<BuildInfo, BuildInfoSingletonTraits >::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
index cce74f4e52..bfe4db2499 100644
--- a/base/android/build_info.h
+++ b/base/android/build_info.h
@@ -8,6 +8,7 @@
#include <jni.h>
#include <string>
+#include <vector>
#include "base/base_export.h"
#include "base/macros.h"
@@ -27,15 +28,14 @@ enum SdkVersion {
SDK_VERSION_LOLLIPOP = 21,
SDK_VERSION_LOLLIPOP_MR1 = 22,
SDK_VERSION_MARSHMALLOW = 23,
- SDK_VERSION_NOUGAT = 24
+ SDK_VERSION_NOUGAT = 24,
+ SDK_VERSION_NOUGAT_MR1 = 25,
+ SDK_VERSION_OREO = 26,
};
// 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:
@@ -79,6 +79,12 @@ class BASE_EXPORT BuildInfo {
return gms_version_code_;
}
+ const char* host_package_name() const { return host_package_name_; }
+
+ const char* host_version_code() const { return host_version_code_; }
+
+ const char* host_package_label() const { return host_package_label_; }
+
const char* package_version_code() const {
return package_version_code_;
}
@@ -87,54 +93,68 @@ class BASE_EXPORT BuildInfo {
return package_version_name_;
}
- const char* package_label() const {
- return package_label_;
- }
-
const char* package_name() const {
return package_name_;
}
+ // Will be empty string if no app id is assigned.
+ const char* firebase_app_id() const { return firebase_app_id_; }
+
+ const char* custom_themes() const { return custom_themes_; }
+
+ const char* resources_version() const { return resources_version_; }
+
const char* build_type() const {
return build_type_;
}
+ const char* board() const { return board_; }
+
+ const char* installer_package_name() const { return installer_package_name_; }
+
+ const char* abi_name() const { return abi_name_; }
+
+ std::string extracted_file_suffix() const { return extracted_file_suffix_; }
+
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();
+ bool is_at_least_p() const { return is_at_least_p_; }
private:
friend struct BuildInfoSingletonTraits;
- explicit BuildInfo(JNIEnv* env);
+ explicit BuildInfo(const std::vector<std::string>& params);
// 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 brand_;
const char* const device_;
+ const char* const android_build_id_;
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 int sdk_int_;
+ const char* const build_type_;
+ const char* const board_;
+ const char* const host_package_name_;
+ const char* const host_version_code_;
+ const char* const host_package_label_;
+ const char* const package_name_;
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_;
+ const char* const android_build_fp_;
+ const char* const gms_version_code_;
+ const char* const installer_package_name_;
+ const char* const abi_name_;
+ const char* const firebase_app_id_;
+ const char* const custom_themes_;
+ const char* const resources_version_;
+ // Not needed by breakpad.
+ const std::string extracted_file_suffix_;
+ const int is_at_least_p_;
DISALLOW_COPY_AND_ASSIGN(BuildInfo);
};
diff --git a/base/android/context_utils.cc b/base/android/context_utils.cc
deleted file mode 100644
index e2c4ed0b4b..0000000000
--- a/base/android/context_utils.cc
+++ /dev/null
@@ -1,53 +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/android/context_utils.h"
-
-#include <jni.h>
-
-#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<base::android::ScopedJavaGlobalRef<jobject>>::Leaky
- g_application_context = LAZY_INSTANCE_INITIALIZER;
-
-void SetNativeApplicationContext(JNIEnv* env, const JavaRef<jobject>& 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<jobject>& GetApplicationContext() {
- DCHECK(!g_application_context.Get().is_null());
- return g_application_context.Get();
-}
-
-static void InitNativeSideApplicationContext(
- JNIEnv* env,
- const JavaParamRef<jclass>& clazz,
- const JavaParamRef<jobject>& 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
deleted file mode 100644
index c5289f1d5e..0000000000
--- a/base/android/context_utils.h
+++ /dev/null
@@ -1,26 +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_ANDROID_CONTEXT_UTILS_H_
-#define BASE_ANDROID_CONTEXT_UTILS_H_
-
-#include <jni.h>
-
-#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<jobject>& 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/BuildConfig.java b/base/android/java/src/org/chromium/base/BuildConfig.java
new file mode 100644
index 0000000000..b8fee950b9
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/BuildConfig.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;
+
+/**
+ * Build configuration. Generated on a per-target basis.
+ */
+public class BuildConfig {
+
+
+ public static final String FIREBASE_APP_ID = "";
+
+ public static final boolean DCHECK_IS_ON = false;
+
+ // The ID of the android string resource that stores the product version.
+ // This layer of indirection is necessary to make the resource dependency
+ // optional for android_apk targets/base_java (ex. for cronet).
+ public static final int R_STRING_PRODUCT_VERSION = 0;
+}
diff --git a/base/android/java/src/org/chromium/base/BuildInfo.java b/base/android/java/src/org/chromium/base/BuildInfo.java
index de4ad08a83..111bca8d01 100644
--- a/base/android/java/src/org/chromium/base/BuildInfo.java
+++ b/base/android/java/src/org/chromium/base/BuildInfo.java
@@ -5,12 +5,12 @@
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 android.os.Build.VERSION;
+import android.text.TextUtils;
import org.chromium.base.annotations.CalledByNative;
@@ -22,122 +22,144 @@ 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;
- }
+ private static PackageInfo sBrowserPackageInfo;
+ private static boolean sInitialized;
+
+ /** The application name (e.g. "Chrome"). For WebView, this is name of the embedding app. */
+ public final String hostPackageLabel;
+ /** By default: same as versionCode. For WebView: versionCode of the embedding app. */
+ public final int hostVersionCode;
+ /** The packageName of Chrome/WebView. Use application context for host app packageName. */
+ public final String packageName;
+ /** The versionCode of the apk. */
+ public final int versionCode;
+ /** The versionName of Chrome/WebView. Use application context for host app versionName. */
+ public final String versionName;
+ /** Result of PackageManager.getInstallerPackageName(). Never null, but may be "". */
+ public final String installerPackageName;
+ /** The versionCode of Play Services (for crash reporting). */
+ public final String gmsVersionCode;
+ /** Formatted ABI string (for crash reporting). */
+ public final String abiString;
+ /** Truncated version of Build.FINGERPRINT (for crash reporting). */
+ public final String androidBuildFingerprint;
+ /** A string that is different each time the apk changes. */
+ public final String extractedFileSuffix;
+ /** Whether or not the device has apps installed for using custom themes. */
+ public final String customThemes;
+ /** Product version as stored in Android resources. */
+ public final String resourcesVersion;
+
+ private static class Holder { private static BuildInfo sInstance = new BuildInfo(); }
@CalledByNative
- public static String getBrand() {
- return Build.BRAND;
+ private static String[] getAll() {
+ BuildInfo buildInfo = getInstance();
+ String hostPackageName = ContextUtils.getApplicationContext().getPackageName();
+ return new String[] {
+ Build.BRAND, Build.DEVICE, Build.ID, Build.MANUFACTURER, Build.MODEL,
+ String.valueOf(Build.VERSION.SDK_INT), Build.TYPE, Build.BOARD, hostPackageName,
+ String.valueOf(buildInfo.hostVersionCode), buildInfo.hostPackageLabel,
+ buildInfo.packageName, String.valueOf(buildInfo.versionCode), buildInfo.versionName,
+ buildInfo.androidBuildFingerprint, buildInfo.gmsVersionCode,
+ buildInfo.installerPackageName, buildInfo.abiString, BuildConfig.FIREBASE_APP_ID,
+ buildInfo.customThemes, buildInfo.resourcesVersion, buildInfo.extractedFileSuffix,
+ isAtLeastP() ? "1" : "0",
+ };
}
- @CalledByNative
- public static String getAndroidBuildId() {
- return Build.ID;
+ private static String nullToEmpty(CharSequence seq) {
+ return seq == null ? "" : seq.toString();
}
/**
- * @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.
+ * @param packageInfo Package for Chrome/WebView (as opposed to host app).
*/
- @CalledByNative
- public static String getAndroidBuildFingerprint() {
- return Build.FINGERPRINT.substring(
- 0, Math.min(Build.FINGERPRINT.length(), MAX_FINGERPRINT_LENGTH));
+ public static void setBrowserPackageInfo(PackageInfo packageInfo) {
+ assert !sInitialized;
+ sBrowserPackageInfo = packageInfo;
}
- @CalledByNative
- public static String getDeviceManufacturer() {
- return Build.MANUFACTURER;
- }
-
- @CalledByNative
- public static String getDeviceModel() {
- return Build.MODEL;
+ public static BuildInfo getInstance() {
+ return Holder.sInstance;
}
- @CalledByNative
- public static String getGMSVersionCode() {
- String msg = "gms versionCode not available.";
+ private BuildInfo() {
+ sInitialized = true;
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;
- }
+ Context appContext = ContextUtils.getApplicationContext();
+ String hostPackageName = appContext.getPackageName();
+ PackageManager pm = appContext.getPackageManager();
+ PackageInfo pi = pm.getPackageInfo(hostPackageName, 0);
+ hostVersionCode = pi.versionCode;
+ if (sBrowserPackageInfo != null) {
+ packageName = sBrowserPackageInfo.packageName;
+ versionCode = sBrowserPackageInfo.versionCode;
+ versionName = nullToEmpty(sBrowserPackageInfo.versionName);
+ sBrowserPackageInfo = null;
+ } else {
+ packageName = hostPackageName;
+ versionCode = hostVersionCode;
+ versionName = nullToEmpty(pi.versionName);
+ }
- @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);
+ hostPackageLabel = nullToEmpty(pm.getApplicationLabel(pi.applicationInfo));
+ installerPackageName = nullToEmpty(pm.getInstallerPackageName(packageName));
+
+ PackageInfo gmsPackageInfo = null;
+ try {
+ gmsPackageInfo = pm.getPackageInfo("com.google.android.gms", 0);
+ } catch (NameNotFoundException e) {
+ Log.d(TAG, "GMS package is not found.", e);
}
- } catch (NameNotFoundException e) {
- Log.d(TAG, msg);
- }
- return msg;
- }
+ gmsVersionCode = gmsPackageInfo != null ? String.valueOf(gmsPackageInfo.versionCode)
+ : "gms versionCode not available.";
+
+ String hasCustomThemes = "true";
+ try {
+ // Substratum is a theme engine that enables users to use custom themes provided
+ // by theme apps. Sometimes these can cause crashs if not installed correctly.
+ // These crashes can be difficult to debug, so knowing if the theme manager is
+ // present on the device is useful (http://crbug.com/820591).
+ pm.getPackageInfo("projekt.substratum", 0);
+ } catch (NameNotFoundException e) {
+ hasCustomThemes = "false";
+ }
+ customThemes = hasCustomThemes;
+
+ String currentResourcesVersion = "Not Enabled";
+ // Controlled by target specific build flags.
+ if (BuildConfig.R_STRING_PRODUCT_VERSION != 0) {
+ try {
+ // This value can be compared with the actual product version to determine if
+ // corrupted resources were the cause of a crash. This can happen if the app
+ // loads resources from the outdated package during an update
+ // (http://crbug.com/820591).
+ currentResourcesVersion = ContextUtils.getApplicationContext().getString(
+ BuildConfig.R_STRING_PRODUCT_VERSION);
+ } catch (Exception e) {
+ currentResourcesVersion = "Not found";
+ }
+ }
+ resourcesVersion = currentResourcesVersion;
- @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;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ abiString = TextUtils.join(", ", Build.SUPPORTED_ABIS);
+ } else {
+ abiString = String.format("ABI1: %s, ABI2: %s", Build.CPU_ABI, Build.CPU_ABI2);
}
- } 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);
- }
- }
+ // Use lastUpdateTime when developing locally, since versionCode does not normally
+ // change in this case.
+ long version = versionCode > 10 ? versionCode : pi.lastUpdateTime;
+ extractedFileSuffix = String.format("@%x", version);
- @CalledByNative
- public static String getPackageName() {
- if (ContextUtils.getApplicationContext() == null) {
- return "";
+ // The value is truncated, as this is used for crash and UMA reporting.
+ androidBuildFingerprint = Build.FINGERPRINT.substring(
+ 0, Math.min(Build.FINGERPRINT.length(), MAX_FINGERPRINT_LENGTH));
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
}
- return ContextUtils.getApplicationContext().getPackageName();
- }
-
- @CalledByNative
- public static String getBuildType() {
- return Build.TYPE;
}
/**
@@ -147,25 +169,25 @@ public class BuildInfo {
return "eng".equals(Build.TYPE) || "userdebug".equals(Build.TYPE);
}
- @CalledByNative
- public static int getSdkInt() {
- return Build.VERSION.SDK_INT;
- }
+ // The markers Begin:BuildCompat and End:BuildCompat delimit code
+ // that is autogenerated from Android sources.
+ // Begin:BuildCompat P
/**
- * @return Whether the current device is running Android O release or newer.
+ * Checks if the device is running on a pre-release version of Android P or newer.
+ * <p>
+ * @return {@code true} if P APIs are available for use, {@code false} otherwise
*/
- public static boolean isAtLeastO() {
- return !"REL".equals(Build.VERSION.CODENAME)
- && ("O".equals(Build.VERSION.CODENAME) || Build.VERSION.CODENAME.startsWith("OMR"));
+ public static boolean isAtLeastP() {
+ return VERSION.SDK_INT >= 28;
}
/**
- * @return Whether the current app targets the SDK for at least O
+ * Checks if the application targets at least released SDK P
*/
- public static boolean targetsAtLeastO(Context appContext) {
- return isAtLeastO()
- && appContext.getApplicationInfo().targetSdkVersion
- == Build.VERSION_CODES.CUR_DEVELOPMENT;
+ public static boolean targetsAtLeastP() {
+ return ContextUtils.getApplicationContext().getApplicationInfo().targetSdkVersion >= 28;
}
+
+ // End:BuildCompat
}
diff --git a/base/android/java/src/org/chromium/base/ContextUtils.java b/base/android/java/src/org/chromium/base/ContextUtils.java
index 448eff9b6a..c648e01ff9 100644
--- a/base/android/java/src/org/chromium/base/ContextUtils.java
+++ b/base/android/java/src/org/chromium/base/ContextUtils.java
@@ -4,8 +4,12 @@
package org.chromium.base;
+import android.app.Application;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.SharedPreferences;
+import android.content.res.AssetManager;
+import android.os.Process;
import android.preference.PreferenceManager;
import org.chromium.base.annotations.JNINamespace;
@@ -15,10 +19,11 @@ 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;
+ // TODO(agrieve): Remove sProcessName caching when we stop supporting JB.
+ private static String sProcessName;
/**
* Initialization-on-demand holder. This exists for thread-safe lazy initialization.
@@ -52,6 +57,7 @@ public class ContextUtils {
*
* @param appContext The application context.
*/
+ @MainDex // TODO(agrieve): Could add to whole class if not for ApplicationStatus.initialize().
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.
@@ -62,16 +68,6 @@ public class ContextUtils {
}
/**
- * 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.
@@ -100,6 +96,14 @@ public class ContextUtils {
*/
@VisibleForTesting
public static void initApplicationContextForTests(Context appContext) {
+ // ApplicationStatus.initialize should be called to setup activity tracking for tests
+ // that use Robolectric and set the application context manually. Instead of changing all
+ // tests that do so, the call was put here instead.
+ // TODO(mheikal): Require param to be of type Application
+ // Disabled on libchrome
+ // if (appContext instanceof Application) {
+ // ApplicationStatus.initialize((Application) appContext);
+ // }
initJavaSideApplicationContext(appContext);
Holder.sSharedPreferences = fetchAppSharedPreferences();
}
@@ -111,5 +115,62 @@ public class ContextUtils {
sApplicationContext = appContext;
}
- private static native void nativeInitNativeSideApplicationContext(Context appContext);
+ /**
+ * In most cases, {@link Context#getAssets()} can be used directly. Modified resources are
+ * used downstream and are set up on application startup, and this method provides access to
+ * regular assets before that initialization is complete.
+ *
+ * This method should ONLY be used for accessing files within the assets folder.
+ *
+ * @return Application assets.
+ */
+ public static AssetManager getApplicationAssets() {
+ Context context = getApplicationContext();
+ while (context instanceof ContextWrapper) {
+ context = ((ContextWrapper) context).getBaseContext();
+ }
+ return context.getAssets();
+ }
+
+ /**
+ * @return Whether the process is isolated.
+ */
+ public static boolean isIsolatedProcess() {
+ try {
+ return (Boolean) Process.class.getMethod("isIsolated").invoke(null);
+ } catch (Exception e) { // No multi-catch below API level 19 for reflection exceptions.
+ // If fallback logic is ever needed, refer to:
+ // https://chromium-review.googlesource.com/c/chromium/src/+/905563/1
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** @return The name of the current process. E.g. "org.chromium.chrome:privileged_process0". */
+ public static String getProcessName() {
+ // Once we drop support JB, this method can be simplified to not cache sProcessName and call
+ // ActivityThread.currentProcessName().
+ if (sProcessName != null) {
+ return sProcessName;
+ }
+ try {
+ // An even more convenient ActivityThread.currentProcessName() exists, but was not added
+ // until JB MR2.
+ Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
+ Object activityThread =
+ activityThreadClazz.getMethod("currentActivityThread").invoke(null);
+ // Before JB MR2, currentActivityThread() returns null when called on a non-UI thread.
+ // Cache the name to allow other threads to access it.
+ sProcessName =
+ (String) activityThreadClazz.getMethod("getProcessName").invoke(activityThread);
+ return sProcessName;
+ } catch (Exception e) { // No multi-catch below API level 19 for reflection exceptions.
+ // If fallback logic is ever needed, refer to:
+ // https://chromium-review.googlesource.com/c/chromium/src/+/905563/1
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static boolean isMainProcess() {
+ return !getProcessName().contains(":");
+ }
}
diff --git a/base/android/java/src/org/chromium/base/DiscardableReferencePool.java b/base/android/java/src/org/chromium/base/DiscardableReferencePool.java
new file mode 100644
index 0000000000..566df70a0b
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/DiscardableReferencePool.java
@@ -0,0 +1,90 @@
+// 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.
+
+package org.chromium.base;
+
+import android.support.annotation.Nullable;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+/**
+ * A DiscardableReferencePool allows handing out typed references to objects ("payloads") that can
+ * be dropped in one batch ("drained"), e.g. under memory pressure. In contrast to {@link
+ * java.lang.ref.WeakReference}s, which drop their referents when they get garbage collected, a
+ * reference pool gives more precise control over when exactly it is drained.
+ *
+ * <p>Internally it uses a {@link WeakHashMap} with the reference itself as a key to allow the
+ * payloads to be garbage collected regularly when the last reference goes away before the pool is
+ * drained.
+ *
+ * <p>This class and its references are not thread-safe and should not be used simultaneously by
+ * multiple threads.
+ */
+public class DiscardableReferencePool {
+ /**
+ * The underlying data storage. The wildcard type parameter allows using a single pool for
+ * references of any type.
+ */
+ private final Set<DiscardableReference<?>> mPool;
+
+ public DiscardableReferencePool() {
+ WeakHashMap<DiscardableReference<?>, Boolean> map = new WeakHashMap<>();
+ mPool = Collections.newSetFromMap(map);
+ }
+
+ /**
+ * A reference to an object in the pool. Will be nulled out when the pool is drained.
+ * @param <T> The type of the object.
+ */
+ public static class DiscardableReference<T> {
+ @Nullable
+ private T mPayload;
+
+ private DiscardableReference(T payload) {
+ assert payload != null;
+ mPayload = payload;
+ }
+
+ /**
+ * @return The referent, or null if the pool has been drained.
+ */
+ @Nullable
+ public T get() {
+ return mPayload;
+ }
+
+ /**
+ * Clear the referent.
+ */
+ private void discard() {
+ assert mPayload != null;
+ mPayload = null;
+ }
+ }
+
+ /**
+ * @param <T> The type of the object.
+ * @param payload The payload to add to the pool.
+ * @return A new reference to the {@code payload}.
+ */
+ public <T> DiscardableReference<T> put(T payload) {
+ assert payload != null;
+ DiscardableReference<T> reference = new DiscardableReference<>(payload);
+ mPool.add(reference);
+ return reference;
+ }
+
+ /**
+ * Drains the pool, removing all references to objects in the pool and therefore allowing them
+ * to be garbage collected.
+ */
+ public void drain() {
+ for (DiscardableReference<?> ref : mPool) {
+ ref.discard();
+ }
+ mPool.clear();
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/JavaExceptionReporter.java b/base/android/java/src/org/chromium/base/JavaExceptionReporter.java
new file mode 100644
index 0000000000..f192f78c10
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/JavaExceptionReporter.java
@@ -0,0 +1,65 @@
+// 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.support.annotation.UiThread;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.MainDex;
+
+/**
+ * This UncaughtExceptionHandler will create a breakpad minidump when there is an uncaught
+ * exception.
+ *
+ * The exception's stack trace will be added to the minidump's data. This allows java-only crashes
+ * to be reported in the same way as other native crashes.
+ */
+@JNINamespace("base::android")
+@MainDex
+public class JavaExceptionReporter implements Thread.UncaughtExceptionHandler {
+ private final Thread.UncaughtExceptionHandler mParent;
+ private final boolean mCrashAfterReport;
+ private boolean mHandlingException;
+
+ private JavaExceptionReporter(
+ Thread.UncaughtExceptionHandler parent, boolean crashAfterReport) {
+ mParent = parent;
+ mCrashAfterReport = crashAfterReport;
+ }
+
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ if (!mHandlingException) {
+ mHandlingException = true;
+ nativeReportJavaException(mCrashAfterReport, e);
+ }
+ if (mParent != null) {
+ mParent.uncaughtException(t, e);
+ }
+ }
+
+ /**
+ * Report and upload the stack trace as if it was a crash. This is very expensive and should
+ * be called rarely and only on the UI thread to avoid corrupting other crash uploads. Ideally
+ * only called in idle handlers.
+ *
+ * @param stackTrace The stack trace to report.
+ */
+ @UiThread
+ public static void reportStackTrace(String stackTrace) {
+ assert ThreadUtils.runningOnUiThread();
+ nativeReportJavaStackTrace(stackTrace);
+ }
+
+ @CalledByNative
+ private static void installHandler(boolean crashAfterReport) {
+ Thread.setDefaultUncaughtExceptionHandler(new JavaExceptionReporter(
+ Thread.getDefaultUncaughtExceptionHandler(), crashAfterReport));
+ }
+
+ private static native void nativeReportJavaException(boolean crashAfterReport, Throwable e);
+ private static native void nativeReportJavaStackTrace(String stackTrace);
+}
diff --git a/base/android/java/src/org/chromium/base/PackageUtils.java b/base/android/java/src/org/chromium/base/PackageUtils.java
index ab554cdc45..a8e487b35e 100644
--- a/base/android/java/src/org/chromium/base/PackageUtils.java
+++ b/base/android/java/src/org/chromium/base/PackageUtils.java
@@ -7,6 +7,9 @@ package org.chromium.base;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
/**
* This class provides package checking related methods.
@@ -31,6 +34,22 @@ public class PackageUtils {
return versionCode;
}
+ /**
+ * Decodes into a Bitmap an Image resource stored in another package.
+ * @param otherPackage The package containing the resource.
+ * @param resourceId The id of the resource.
+ * @return A Bitmap containing the resource or null if the package could not be found.
+ */
+ public static Bitmap decodeImageResource(String otherPackage, int resourceId) {
+ PackageManager packageManager = ContextUtils.getApplicationContext().getPackageManager();
+ try {
+ Resources resources = packageManager.getResourcesForApplication(otherPackage);
+ return BitmapFactory.decodeResource(resources, resourceId);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
private PackageUtils() {
// Hide constructor
}
diff --git a/base/android/java/src/org/chromium/base/StrictModeContext.java b/base/android/java/src/org/chromium/base/StrictModeContext.java
new file mode 100644
index 0000000000..beaaac0ad5
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/StrictModeContext.java
@@ -0,0 +1,85 @@
+// 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.
+
+package org.chromium.base;
+
+import android.os.StrictMode;
+
+import java.io.Closeable;
+
+/**
+ * Enables try-with-resources compatible StrictMode violation whitelisting.
+ *
+ * Example:
+ * <pre>
+ * try (StrictModeContext unused = StrictModeContext.allowDiskWrites()) {
+ * return Example.doThingThatRequiresDiskWrites();
+ * }
+ * </pre>
+ *
+ */
+public final class StrictModeContext implements Closeable {
+ private final StrictMode.ThreadPolicy mThreadPolicy;
+ private final StrictMode.VmPolicy mVmPolicy;
+
+ private StrictModeContext(StrictMode.ThreadPolicy threadPolicy, StrictMode.VmPolicy vmPolicy) {
+ mThreadPolicy = threadPolicy;
+ mVmPolicy = vmPolicy;
+ }
+
+ private StrictModeContext(StrictMode.ThreadPolicy threadPolicy) {
+ this(threadPolicy, null);
+ }
+
+ private StrictModeContext(StrictMode.VmPolicy vmPolicy) {
+ this(null, vmPolicy);
+ }
+
+ /**
+ * Convenience method for disabling all VM-level StrictMode checks with try-with-resources.
+ * Includes everything listed here:
+ * https://developer.android.com/reference/android/os/StrictMode.VmPolicy.Builder.html
+ */
+ public static StrictModeContext allowAllVmPolicies() {
+ StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy();
+ StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX);
+ return new StrictModeContext(oldPolicy);
+ }
+
+ /**
+ * Convenience method for disabling StrictMode for disk-writes with try-with-resources.
+ */
+ public static StrictModeContext allowDiskWrites() {
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+ return new StrictModeContext(oldPolicy);
+ }
+
+ /**
+ * Convenience method for disabling StrictMode for disk-reads with try-with-resources.
+ */
+ public static StrictModeContext allowDiskReads() {
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+ return new StrictModeContext(oldPolicy);
+ }
+
+ /**
+ * Convenience method for disabling StrictMode for slow calls with try-with-resources.
+ */
+ public static StrictModeContext allowSlowCalls() {
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+ StrictMode.setThreadPolicy(
+ new StrictMode.ThreadPolicy.Builder(oldPolicy).permitCustomSlowCalls().build());
+ return new StrictModeContext(oldPolicy);
+ }
+
+ @Override
+ public void close() {
+ if (mThreadPolicy != null) {
+ StrictMode.setThreadPolicy(mThreadPolicy);
+ }
+ if (mVmPolicy != null) {
+ StrictMode.setVmPolicy(mVmPolicy);
+ }
+ }
+} \ No newline at end of file
diff --git a/base/android/java/src/org/chromium/base/Supplier.java b/base/android/java/src/org/chromium/base/Supplier.java
new file mode 100644
index 0000000000..350da578a7
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/Supplier.java
@@ -0,0 +1,18 @@
+// 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.base;
+
+/**
+ * Based on Java 8's java.util.function.Supplier.
+ * Same as Callable<T>, but without a checked Exception.
+ *
+ * @param <T> Return type.
+ */
+public interface Supplier<T> {
+ /**
+ * Returns a value.
+ */
+ T get();
+}
diff --git a/base/android/java/src/org/chromium/base/ThreadUtils.java b/base/android/java/src/org/chromium/base/ThreadUtils.java
new file mode 100644
index 0000000000..61872a0171
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/ThreadUtils.java
@@ -0,0 +1,268 @@
+// 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.os.Handler;
+import android.os.Looper;
+import android.os.Process;
+
+import org.chromium.base.annotations.CalledByNative;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+
+/**
+ * Helper methods to deal with threading related tasks.
+ */
+public class ThreadUtils {
+
+ private static final Object sLock = new Object();
+
+ private static boolean sWillOverride;
+
+ private static Handler sUiThreadHandler;
+
+ private static boolean sThreadAssertsDisabled;
+
+ public static void setWillOverrideUiThread() {
+ synchronized (sLock) {
+ sWillOverride = true;
+ }
+ }
+
+ public static void setUiThread(Looper looper) {
+ synchronized (sLock) {
+ if (looper == null) {
+ // Used to reset the looper after tests.
+ sUiThreadHandler = null;
+ return;
+ }
+ if (sUiThreadHandler != null && sUiThreadHandler.getLooper() != looper) {
+ throw new RuntimeException("UI thread looper is already set to "
+ + sUiThreadHandler.getLooper() + " (Main thread looper is "
+ + Looper.getMainLooper() + "), cannot set to new looper " + looper);
+ } else {
+ sUiThreadHandler = new Handler(looper);
+ }
+ }
+ }
+
+ public static Handler getUiThreadHandler() {
+ synchronized (sLock) {
+ if (sUiThreadHandler == null) {
+ if (sWillOverride) {
+ throw new RuntimeException("Did not yet override the UI thread");
+ }
+ sUiThreadHandler = new Handler(Looper.getMainLooper());
+ }
+ return sUiThreadHandler;
+ }
+ }
+
+ /**
+ * Run the supplied Runnable on the main thread. The method will block until the Runnable
+ * completes.
+ *
+ * @param r The Runnable to run.
+ */
+ public static void runOnUiThreadBlocking(final Runnable r) {
+ if (runningOnUiThread()) {
+ r.run();
+ } else {
+ FutureTask<Void> task = new FutureTask<Void>(r, null);
+ postOnUiThread(task);
+ try {
+ task.get();
+ } catch (Exception e) {
+ throw new RuntimeException("Exception occurred while waiting for runnable", e);
+ }
+ }
+ }
+
+ /**
+ * Run the supplied Callable on the main thread, wrapping any exceptions in a RuntimeException.
+ * The method will block until the Callable completes.
+ *
+ * @param c The Callable to run
+ * @return The result of the callable
+ */
+ @VisibleForTesting
+ public static <T> T runOnUiThreadBlockingNoException(Callable<T> c) {
+ try {
+ return runOnUiThreadBlocking(c);
+ } catch (ExecutionException e) {
+ throw new RuntimeException("Error occurred waiting for callable", e);
+ }
+ }
+
+ /**
+ * Run the supplied Callable on the main thread, The method will block until the Callable
+ * completes.
+ *
+ * @param c The Callable to run
+ * @return The result of the callable
+ * @throws ExecutionException c's exception
+ */
+ public static <T> T runOnUiThreadBlocking(Callable<T> c) throws ExecutionException {
+ FutureTask<T> task = new FutureTask<T>(c);
+ runOnUiThread(task);
+ try {
+ return task.get();
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interrupted waiting for callable", e);
+ }
+ }
+
+ /**
+ * Run the supplied FutureTask on the main thread. The method will block only if the current
+ * thread is the main thread.
+ *
+ * @param task The FutureTask to run
+ * @return The queried task (to aid inline construction)
+ */
+ public static <T> FutureTask<T> runOnUiThread(FutureTask<T> task) {
+ if (runningOnUiThread()) {
+ task.run();
+ } else {
+ postOnUiThread(task);
+ }
+ return task;
+ }
+
+ /**
+ * Run the supplied Callable on the main thread. The method will block only if the current
+ * thread is the main thread.
+ *
+ * @param c The Callable to run
+ * @return A FutureTask wrapping the callable to retrieve results
+ */
+ public static <T> FutureTask<T> runOnUiThread(Callable<T> c) {
+ return runOnUiThread(new FutureTask<T>(c));
+ }
+
+ /**
+ * Run the supplied Runnable on the main thread. The method will block only if the current
+ * thread is the main thread.
+ *
+ * @param r The Runnable to run
+ */
+ public static void runOnUiThread(Runnable r) {
+ if (runningOnUiThread()) {
+ r.run();
+ } else {
+ getUiThreadHandler().post(r);
+ }
+ }
+
+ /**
+ * Post the supplied FutureTask to run on the main thread. The method will not block, even if
+ * called on the UI thread.
+ *
+ * @param task The FutureTask to run
+ * @return The queried task (to aid inline construction)
+ */
+ public static <T> FutureTask<T> postOnUiThread(FutureTask<T> task) {
+ getUiThreadHandler().post(task);
+ return task;
+ }
+
+ /**
+ * Post the supplied Runnable to run on the main thread. The method will not block, even if
+ * called on the UI thread.
+ *
+ * @param task The Runnable to run
+ */
+ public static void postOnUiThread(Runnable task) {
+ getUiThreadHandler().post(task);
+ }
+
+ /**
+ * Post the supplied Runnable to run on the main thread after the given amount of time. The
+ * method will not block, even if called on the UI thread.
+ *
+ * @param task The Runnable to run
+ * @param delayMillis The delay in milliseconds until the Runnable will be run
+ */
+ @VisibleForTesting
+ public static void postOnUiThreadDelayed(Runnable task, long delayMillis) {
+ getUiThreadHandler().postDelayed(task, delayMillis);
+ }
+
+ /**
+ * Throw an exception (when DCHECKs are enabled) if currently not running on the UI thread.
+ *
+ * Can be disabled by setThreadAssertsDisabledForTesting(true).
+ */
+ public static void assertOnUiThread() {
+ if (sThreadAssertsDisabled) return;
+
+ assert runningOnUiThread() : "Must be called on the UI thread.";
+ }
+
+ /**
+ * Throw an exception (regardless of build) if currently not running on the UI thread.
+ *
+ * Can be disabled by setThreadAssertsEnabledForTesting(false).
+ *
+ * @see #assertOnUiThread()
+ */
+ public static void checkUiThread() {
+ if (!sThreadAssertsDisabled && !runningOnUiThread()) {
+ throw new IllegalStateException("Must be called on the UI thread.");
+ }
+ }
+
+ /**
+ * Throw an exception (when DCHECKs are enabled) if currently running on the UI thread.
+ *
+ * Can be disabled by setThreadAssertsDisabledForTesting(true).
+ */
+ public static void assertOnBackgroundThread() {
+ if (sThreadAssertsDisabled) return;
+
+ assert !runningOnUiThread() : "Must be called on a thread other than UI.";
+ }
+
+ /**
+ * Disables thread asserts.
+ *
+ * Can be used by tests where code that normally runs multi-threaded is going to run
+ * single-threaded for the test (otherwise asserts that are valid in production would fail in
+ * those tests).
+ */
+ public static void setThreadAssertsDisabledForTesting(boolean disabled) {
+ sThreadAssertsDisabled = disabled;
+ }
+
+ /**
+ * @return true iff the current thread is the main (UI) thread.
+ */
+ public static boolean runningOnUiThread() {
+ return getUiThreadHandler().getLooper() == Looper.myLooper();
+ }
+
+ public static Looper getUiThreadLooper() {
+ return getUiThreadHandler().getLooper();
+ }
+
+ /**
+ * Set thread priority to audio.
+ */
+ @CalledByNative
+ public static void setThreadPriorityAudio(int tid) {
+ Process.setThreadPriority(tid, Process.THREAD_PRIORITY_AUDIO);
+ }
+
+ /**
+ * Checks whether Thread priority is THREAD_PRIORITY_AUDIO or not.
+ * @param tid Thread id.
+ * @return true for THREAD_PRIORITY_AUDIO and false otherwise.
+ */
+ @CalledByNative
+ private static boolean isThreadPriorityAudio(int tid) {
+ return Process.getThreadPriority(tid) == Process.THREAD_PRIORITY_AUDIO;
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/TimezoneUtils.java b/base/android/java/src/org/chromium/base/TimezoneUtils.java
new file mode 100644
index 0000000000..cddd3d9d2a
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/TimezoneUtils.java
@@ -0,0 +1,36 @@
+// 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.
+
+package org.chromium.base;
+
+import android.os.StrictMode;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.annotations.MainDex;
+
+import java.util.TimeZone;
+
+@JNINamespace("base::android")
+@MainDex
+class TimezoneUtils {
+ /**
+ * Guards this class from being instantiated.
+ */
+
+ private TimezoneUtils() {}
+
+ /**
+ * @return the Olson timezone ID of the current system time zone.
+ */
+ @CalledByNative
+ private static String getDefaultTimeZoneId() {
+ // On Android N or earlier, getting the default timezone requires the disk
+ // access when a device set up is skipped.
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+ String timezoneID = TimeZone.getDefault().getID();
+ StrictMode.setThreadPolicy(oldPolicy);
+ return timezoneID;
+ }
+}
diff --git a/base/android/java/src/org/chromium/base/annotations/CalledByNative.java b/base/android/java/src/org/chromium/base/annotations/CalledByNative.java
index 94ef3fab48..52f5b7e59f 100644
--- a/base/android/java/src/org/chromium/base/annotations/CalledByNative.java
+++ b/base/android/java/src/org/chromium/base/annotations/CalledByNative.java
@@ -13,7 +13,7 @@ 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)
+@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface CalledByNative {
/*
diff --git a/base/android/java/src/org/chromium/base/annotations/MainDex.java b/base/android/java/src/org/chromium/base/annotations/MainDex.java
index 0b35ade8ee..56aab743fc 100644
--- a/base/android/java/src/org/chromium/base/annotations/MainDex.java
+++ b/base/android/java/src/org/chromium/base/annotations/MainDex.java
@@ -15,7 +15,6 @@ import java.lang.annotation.Target;
* This generally means it's used by renderer processes, which can't load secondary dexes
* on K and below.
*/
-@Target(ElementType.TYPE)
+@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
-public @interface MainDex {
-}
+public @interface MainDex {}
diff --git a/base/android/java/src/org/chromium/base/annotations/SuppressFBWarnings.java b/base/android/java/src/org/chromium/base/annotations/SuppressFBWarnings.java
deleted file mode 100644
index 89068ac941..0000000000
--- a/base/android/java/src/org/chromium/base/annotations/SuppressFBWarnings.java
+++ /dev/null
@@ -1,20 +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.
-
-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_exception_reporter.cc b/base/android/java_exception_reporter.cc
new file mode 100644
index 0000000000..96eb38e581
--- /dev/null
+++ b/base/android/java_exception_reporter.cc
@@ -0,0 +1,70 @@
+// 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_exception_reporter.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/debug/dump_without_crashing.h"
+#include "jni/JavaExceptionReporter_jni.h"
+
+using base::android::JavaParamRef;
+
+namespace base {
+namespace android {
+
+namespace {
+
+void (*g_java_exception_callback)(const char*);
+
+} // namespace
+
+void InitJavaExceptionReporter() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ constexpr bool crash_after_report = false;
+ Java_JavaExceptionReporter_installHandler(env, crash_after_report);
+}
+
+void InitJavaExceptionReporterForChildProcess() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ constexpr bool crash_after_report = true;
+ Java_JavaExceptionReporter_installHandler(env, crash_after_report);
+}
+
+void SetJavaExceptionCallback(void (*callback)(const char*)) {
+ DCHECK(!g_java_exception_callback);
+ g_java_exception_callback = callback;
+}
+
+void SetJavaException(const char* exception) {
+ DCHECK(g_java_exception_callback);
+ g_java_exception_callback(exception);
+}
+
+void JNI_JavaExceptionReporter_ReportJavaException(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& jcaller,
+ jboolean crash_after_report,
+ const JavaParamRef<jthrowable>& e) {
+ std::string exception_info = base::android::GetJavaExceptionInfo(env, e);
+ SetJavaException(exception_info.c_str());
+ if (crash_after_report) {
+ LOG(ERROR) << exception_info;
+ LOG(FATAL) << "Uncaught exception";
+ }
+ base::debug::DumpWithoutCrashing();
+ SetJavaException(nullptr);
+}
+
+void JNI_JavaExceptionReporter_ReportJavaStackTrace(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& jcaller,
+ const JavaParamRef<jstring>& stackTrace) {
+ SetJavaException(ConvertJavaStringToUTF8(stackTrace).c_str());
+ base::debug::DumpWithoutCrashing();
+ SetJavaException(nullptr);
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/java_exception_reporter.h b/base/android/java_exception_reporter.h
new file mode 100644
index 0000000000..c0a7ea6394
--- /dev/null
+++ b/base/android/java_exception_reporter.h
@@ -0,0 +1,33 @@
+// 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_EXCEPTION_REPORTER_H_
+#define BASE_ANDROID_JAVA_EXCEPTION_REPORTER_H_
+
+#include <jni.h>
+
+#include "base/base_export.h"
+
+namespace base {
+namespace android {
+
+// Install the exception handler. This should only be called once per process.
+BASE_EXPORT void InitJavaExceptionReporter();
+
+// Same as above except the handler ensures child process exists immediately
+// after an unhandled exception. This is used for child processes because
+// DumpWithoutCrashing does not work for child processes on Android.
+BASE_EXPORT void InitJavaExceptionReporterForChildProcess();
+
+// Sets a callback to be called with the contents of a Java exception, which may
+// be nullptr.
+BASE_EXPORT void SetJavaExceptionCallback(void (*)(const char* exception));
+
+// Calls the Java exception callback, if any, with exception.
+void SetJavaException(const char* exception);
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_JAVA_EXCEPTION_REPORTER_H_
diff --git a/base/android/javatests/src/org/chromium/base/AssertsTest.java b/base/android/javatests/src/org/chromium/base/AssertsTest.java
new file mode 100644
index 0000000000..37e3b40844
--- /dev/null
+++ b/base/android/javatests/src/org/chromium/base/AssertsTest.java
@@ -0,0 +1,39 @@
+// 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.base;
+
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+
+/**
+ * Test that ensures Java asserts are working.
+ *
+ * Not a robolectric test because we want to make sure asserts are enabled after dexing.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class AssertsTest {
+ @Test
+ @SmallTest
+ @SuppressWarnings("UseCorrectAssertInTests")
+ public void testAssertsWorkAsExpected() {
+ if (BuildConfig.DCHECK_IS_ON) {
+ try {
+ assert false;
+ } catch (AssertionError e) {
+ // When DCHECK is on, asserts should throw AssertionErrors.
+ return;
+ }
+ Assert.fail("Java assert unexpectedly didn't fire.");
+ } else {
+ // When DCHECK isn't on, asserts should be removed by proguard.
+ assert false : "Java assert unexpectedly fired.";
+ }
+ }
+}
diff --git a/base/android/javatests/src/org/chromium/base/AsyncTaskTest.java b/base/android/javatests/src/org/chromium/base/AsyncTaskTest.java
new file mode 100644
index 0000000000..2fd92beced
--- /dev/null
+++ b/base/android/javatests/src/org/chromium/base/AsyncTaskTest.java
@@ -0,0 +1,128 @@
+// 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.base;
+
+import android.support.annotation.NonNull;
+import android.support.test.filters.SmallTest;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for our AsyncTask modifications
+ *
+ * Not a robolectric test because the reflection doesn't work with ShadowAsyncTask.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class AsyncTaskTest {
+ private static class SpecialChromeAsyncTask extends AsyncTask<Void, Void, Void> {
+ @Override
+ protected Void doInBackground(Void... params) {
+ return null;
+ }
+ }
+
+ private static class SpecialOsAsyncTask extends android.os.AsyncTask<Void, Void, Void> {
+ @Override
+ protected Void doInBackground(Void... params) {
+ return null;
+ }
+ }
+
+ private static class SpecialRunnable implements Runnable {
+ @Override
+ public void run() {}
+ }
+
+ private static final int QUEUE_SIZE = 40;
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ /**
+ * Test filling the queue with basic Runnables, then add a final AsyncTask to overfill it, and
+ * ensure the Runnable is the one blamed in the exception message.
+ */
+ @Test
+ @SmallTest
+ public void testChromeThreadPoolExecutorRunnables() {
+ Executor executor = new AsyncTask.ChromeThreadPoolExecutor(1, 1, 1, TimeUnit.SECONDS,
+ new ArrayBlockingQueue<Runnable>(QUEUE_SIZE), new ThreadFactory() {
+ @Override
+ public Thread newThread(@NonNull Runnable r) {
+ return null;
+ }
+ });
+ for (int i = 0; i < QUEUE_SIZE; i++) {
+ executor.execute(new SpecialRunnable());
+ }
+ thrown.expect(RejectedExecutionException.class);
+ thrown.expectMessage(
+ CoreMatchers.containsString("org.chromium.base.AsyncTaskTest$SpecialRunnable"));
+ thrown.expectMessage(
+ CoreMatchers.not(CoreMatchers.containsString("SpecialChromeAsyncTask")));
+ new SpecialChromeAsyncTask().executeOnExecutor(executor);
+ }
+
+ /**
+ * Test filling the queue with Chrome AsyncTasks, then add a final OS AsyncTask to
+ * overfill it and ensure the Chrome AsyncTask is the one blamed in the exception message.
+ */
+ @Test
+ @SmallTest
+ public void testChromeThreadPoolExecutorChromeAsyncTask() {
+ Executor executor = new AsyncTask.ChromeThreadPoolExecutor(1, 1, 1, TimeUnit.SECONDS,
+ new ArrayBlockingQueue<Runnable>(QUEUE_SIZE), new ThreadFactory() {
+ @Override
+ public Thread newThread(@NonNull Runnable r) {
+ return null;
+ }
+ });
+ for (int i = 0; i < QUEUE_SIZE; i++) {
+ new SpecialChromeAsyncTask().executeOnExecutor(executor);
+ }
+ thrown.expect(RejectedExecutionException.class);
+ thrown.expectMessage(CoreMatchers.containsString(
+ "org.chromium.base.AsyncTaskTest$SpecialChromeAsyncTask"));
+ thrown.expectMessage(CoreMatchers.not(CoreMatchers.containsString("SpecialOsAsyncTask")));
+ new SpecialOsAsyncTask().executeOnExecutor(executor);
+ }
+
+ /**
+ * Test filling the queue with android.os.AsyncTasks, then add a final ChromeAsyncTask to
+ * overfill it and ensure the OsAsyncTask is the one blamed in the exception message.
+ */
+ @Test
+ @SmallTest
+ public void testChromeThreadPoolExecutorOsAsyncTask() {
+ Executor executor = new AsyncTask.ChromeThreadPoolExecutor(1, 1, 1, TimeUnit.SECONDS,
+ new ArrayBlockingQueue<Runnable>(QUEUE_SIZE), new ThreadFactory() {
+ @Override
+ public Thread newThread(@NonNull Runnable r) {
+ return null;
+ }
+ });
+ for (int i = 0; i < QUEUE_SIZE; i++) {
+ new SpecialOsAsyncTask().executeOnExecutor(executor);
+ }
+ thrown.expect(RejectedExecutionException.class);
+ thrown.expectMessage(
+ CoreMatchers.containsString("org.chromium.base.AsyncTaskTest$SpecialOsAsyncTask"));
+ thrown.expectMessage(
+ CoreMatchers.not(CoreMatchers.containsString("SpecialChromeAsyncTask")));
+ new SpecialChromeAsyncTask().executeOnExecutor(executor);
+ }
+}
diff --git a/base/android/jni_android.cc b/base/android/jni_android.cc
index 2b5869f06b..3c17c39e4d 100644
--- a/base/android/jni_android.cc
+++ b/base/android/jni_android.cc
@@ -5,14 +5,13 @@
#include "base/android/jni_android.h"
#include <stddef.h>
+#include <sys/prctl.h>
#include <map>
-#include "base/android/build_info.h"
+#include "base/android/java_exception_reporter.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/debug/debugging_buildflags.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/threading/thread_local.h"
@@ -22,37 +21,45 @@ 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<base::android::ScopedJavaGlobalRef<jobject>>::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
+#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
base::LazyInstance<base::ThreadLocalPointer<void>>::Leaky
g_stack_frame_pointer = LAZY_INSTANCE_INITIALIZER;
#endif
+bool g_fatal_exception_occurred = false;
+
} // 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);
+ JNIEnv* env = nullptr;
+ jint ret = g_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_2);
+ if (ret == JNI_EDETACHED || !env) {
+ JavaVMAttachArgs args;
+ args.version = JNI_VERSION_1_2;
+ args.group = nullptr;
+
+ // 16 is the maximum size for thread names on Android.
+ char thread_name[16];
+ int err = prctl(PR_GET_NAME, thread_name);
+ if (err < 0) {
+ DPLOG(ERROR) << "prctl(PR_GET_NAME)";
+ args.name = nullptr;
+ } else {
+ args.name = thread_name;
+ }
+
+ ret = g_jvm->AttachCurrentThread(&env, &args);
+ DCHECK_EQ(JNI_OK, ret);
+ }
return env;
}
@@ -227,28 +234,28 @@ 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));
+ if (g_fatal_exception_occurred) {
+ // Another exception (probably OOM) occurred during GetJavaExceptionInfo.
+ base::android::SetJavaException(
+ "Java OOM'ed in exception handling, check logcat");
+ } else {
+ g_fatal_exception_occurred = true;
+ // RVO should avoid any extra copies of the exception string.
+ base::android::SetJavaException(
+ GetJavaExceptionInfo(env, java_throwable).c_str());
+ }
}
// 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;
+ if (java_throwable)
+ LOG(FATAL) << GetJavaExceptionInfo(env, java_throwable);
else
LOG(FATAL) << "Unhandled exception";
}
@@ -274,6 +281,7 @@ std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) {
ScopedJavaLocalRef<jobject> bytearray_output_stream(env,
env->NewObject(bytearray_output_stream_clazz.obj(),
bytearray_output_stream_constructor));
+ CheckException(env);
// Create an instance of PrintStream.
ScopedJavaLocalRef<jclass> printstream_clazz =
@@ -285,21 +293,24 @@ std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) {
ScopedJavaLocalRef<jobject> printstream(env,
env->NewObject(printstream_clazz.obj(), printstream_constructor,
bytearray_output_stream.obj()));
+ CheckException(env);
// Call Throwable.printStackTrace(PrintStream)
env->CallVoidMethod(java_throwable, throwable_printstacktrace,
printstream.obj());
+ CheckException(env);
// Call ByteArrayOutputStream.toString()
ScopedJavaLocalRef<jstring> exception_string(
env, static_cast<jstring>(
env->CallObjectMethod(bytearray_output_stream.obj(),
bytearray_output_stream_tostring)));
+ CheckException(env);
return ConvertJavaStringToUTF8(exception_string);
}
-#if BUILDFLAG(ENABLE_PROFILING) && HAVE_TRACE_STACK_FRAME_POINTERS
+#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
JNIStackFrameSaver::JNIStackFrameSaver(void* current_fp) {
previous_fp_ = g_stack_frame_pointer.Pointer()->Get();
@@ -314,7 +325,7 @@ void* JNIStackFrameSaver::SavedFrame() {
return g_stack_frame_pointer.Pointer()->Get();
}
-#endif // ENABLE_PROFILING && HAVE_TRACE_STACK_FRAME_POINTERS
+#endif // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
} // namespace android
} // namespace base
diff --git a/base/android/jni_android.h b/base/android/jni_android.h
index de53c10f6d..fba6113cd4 100644
--- a/base/android/jni_android.h
+++ b/base/android/jni_android.h
@@ -14,10 +14,11 @@
#include "base/atomicops.h"
#include "base/base_export.h"
#include "base/compiler_specific.h"
+#include "base/debug/debugging_buildflags.h"
#include "base/debug/stack_trace.h"
#include "base/macros.h"
-#if HAVE_TRACE_STACK_FRAME_POINTERS
+#if BUILDFLAG(CAN_UNWIND_WITH_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
@@ -46,7 +47,7 @@
#define JNI_SAVE_FRAME_POINTER
#define JNI_LINK_SAVED_FRAME_POINTER
-#endif // HAVE_TRACE_STACK_FRAME_POINTERS
+#endif // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
namespace base {
namespace android {
@@ -54,24 +55,6 @@ 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;
@@ -166,7 +149,7 @@ BASE_EXPORT void CheckException(JNIEnv* env);
BASE_EXPORT std::string GetJavaExceptionInfo(JNIEnv* env,
jthrowable java_throwable);
-#if HAVE_TRACE_STACK_FRAME_POINTERS
+#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
// Saves caller's PC and stack frame in a thread-local variable.
// Implemented only when profiling is enabled (enable_profiling=true).
@@ -182,7 +165,7 @@ class BASE_EXPORT JNIStackFrameSaver {
DISALLOW_COPY_AND_ASSIGN(JNIStackFrameSaver);
};
-#endif // HAVE_TRACE_STACK_FRAME_POINTERS
+#endif // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
} // namespace android
} // namespace base
diff --git a/base/android/jni_array.cc b/base/android/jni_array.cc
new file mode 100644
index 0000000000..52b1679bbd
--- /dev/null
+++ b/base/android/jni_array.cc
@@ -0,0 +1,311 @@
+// 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_array.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/logging.h"
+
+namespace base {
+namespace android {
+namespace {
+
+// As |GetArrayLength| makes no guarantees about the returned value (e.g., it
+// may be -1 if |array| is not a valid Java array), provide a safe wrapper
+// that always returns a valid, non-negative size.
+template <typename JavaArrayType>
+size_t SafeGetArrayLength(JNIEnv* env, JavaArrayType jarray) {
+ DCHECK(jarray);
+ jsize length = env->GetArrayLength(jarray);
+ DCHECK_GE(length, 0) << "Invalid array length: " << length;
+ return static_cast<size_t>(std::max(0, length));
+}
+
+} // namespace
+
+ScopedJavaLocalRef<jbyteArray> ToJavaByteArray(JNIEnv* env,
+ const uint8_t* bytes,
+ size_t len) {
+ jbyteArray byte_array = env->NewByteArray(len);
+ CheckException(env);
+ DCHECK(byte_array);
+
+ env->SetByteArrayRegion(
+ byte_array, 0, len, reinterpret_cast<const jbyte*>(bytes));
+ CheckException(env);
+
+ return ScopedJavaLocalRef<jbyteArray>(env, byte_array);
+}
+
+ScopedJavaLocalRef<jbyteArray> ToJavaByteArray(
+ JNIEnv* env,
+ const std::vector<uint8_t>& bytes) {
+ return ToJavaByteArray(env, bytes.data(), bytes.size());
+}
+
+ScopedJavaLocalRef<jbooleanArray> ToJavaBooleanArray(JNIEnv* env,
+ const bool* bools,
+ size_t len) {
+ jbooleanArray boolean_array = env->NewBooleanArray(len);
+ CheckException(env);
+ DCHECK(boolean_array);
+
+ env->SetBooleanArrayRegion(boolean_array, 0, len,
+ reinterpret_cast<const jboolean*>(bools));
+ CheckException(env);
+
+ return ScopedJavaLocalRef<jbooleanArray>(env, boolean_array);
+}
+
+ScopedJavaLocalRef<jintArray> ToJavaIntArray(
+ JNIEnv* env, const int* ints, size_t len) {
+ jintArray int_array = env->NewIntArray(len);
+ CheckException(env);
+ DCHECK(int_array);
+
+ env->SetIntArrayRegion(
+ int_array, 0, len, reinterpret_cast<const jint*>(ints));
+ CheckException(env);
+
+ return ScopedJavaLocalRef<jintArray>(env, int_array);
+}
+
+ScopedJavaLocalRef<jintArray> ToJavaIntArray(
+ JNIEnv* env, const std::vector<int>& ints) {
+ return ToJavaIntArray(env, ints.data(), ints.size());
+}
+
+ScopedJavaLocalRef<jlongArray> ToJavaLongArray(JNIEnv* env,
+ const int64_t* longs,
+ size_t len) {
+ jlongArray long_array = env->NewLongArray(len);
+ CheckException(env);
+ DCHECK(long_array);
+
+ env->SetLongArrayRegion(
+ long_array, 0, len, reinterpret_cast<const jlong*>(longs));
+ CheckException(env);
+
+ return ScopedJavaLocalRef<jlongArray>(env, long_array);
+}
+
+// Returns a new Java long array converted from the given int64_t array.
+BASE_EXPORT ScopedJavaLocalRef<jlongArray> ToJavaLongArray(
+ JNIEnv* env,
+ const std::vector<int64_t>& longs) {
+ return ToJavaLongArray(env, longs.data(), longs.size());
+}
+
+// Returns a new Java float array converted from the given C++ float array.
+BASE_EXPORT ScopedJavaLocalRef<jfloatArray> ToJavaFloatArray(
+ JNIEnv* env, const float* floats, size_t len) {
+ jfloatArray float_array = env->NewFloatArray(len);
+ CheckException(env);
+ DCHECK(float_array);
+
+ env->SetFloatArrayRegion(
+ float_array, 0, len, reinterpret_cast<const jfloat*>(floats));
+ CheckException(env);
+
+ return ScopedJavaLocalRef<jfloatArray>(env, float_array);
+}
+
+BASE_EXPORT ScopedJavaLocalRef<jfloatArray> ToJavaFloatArray(
+ JNIEnv* env,
+ const std::vector<float>& floats) {
+ return ToJavaFloatArray(env, floats.data(), floats.size());
+}
+
+ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfByteArray(
+ JNIEnv* env, const std::vector<std::string>& v) {
+ ScopedJavaLocalRef<jclass> byte_array_clazz = GetClass(env, "[B");
+ jobjectArray joa = env->NewObjectArray(v.size(),
+ byte_array_clazz.obj(), NULL);
+ CheckException(env);
+
+ for (size_t i = 0; i < v.size(); ++i) {
+ ScopedJavaLocalRef<jbyteArray> byte_array = ToJavaByteArray(
+ env, reinterpret_cast<const uint8_t*>(v[i].data()), v[i].length());
+ env->SetObjectArrayElement(joa, i, byte_array.obj());
+ }
+ return ScopedJavaLocalRef<jobjectArray>(env, joa);
+}
+
+ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings(
+ JNIEnv* env, const std::vector<std::string>& v) {
+ ScopedJavaLocalRef<jclass> string_clazz = GetClass(env, "java/lang/String");
+ jobjectArray joa = env->NewObjectArray(v.size(), string_clazz.obj(), NULL);
+ CheckException(env);
+
+ for (size_t i = 0; i < v.size(); ++i) {
+ ScopedJavaLocalRef<jstring> item = ConvertUTF8ToJavaString(env, v[i]);
+ env->SetObjectArrayElement(joa, i, item.obj());
+ }
+ return ScopedJavaLocalRef<jobjectArray>(env, joa);
+}
+
+ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings(
+ JNIEnv* env, const std::vector<string16>& v) {
+ ScopedJavaLocalRef<jclass> string_clazz = GetClass(env, "java/lang/String");
+ jobjectArray joa = env->NewObjectArray(v.size(), string_clazz.obj(), NULL);
+ CheckException(env);
+
+ for (size_t i = 0; i < v.size(); ++i) {
+ ScopedJavaLocalRef<jstring> item = ConvertUTF16ToJavaString(env, v[i]);
+ env->SetObjectArrayElement(joa, i, item.obj());
+ }
+ return ScopedJavaLocalRef<jobjectArray>(env, joa);
+}
+
+void AppendJavaStringArrayToStringVector(JNIEnv* env,
+ jobjectArray array,
+ std::vector<string16>* out) {
+ DCHECK(out);
+ if (!array)
+ return;
+ size_t len = SafeGetArrayLength(env, array);
+ size_t back = out->size();
+ out->resize(back + len);
+ for (size_t i = 0; i < len; ++i) {
+ ScopedJavaLocalRef<jstring> str(env,
+ static_cast<jstring>(env->GetObjectArrayElement(array, i)));
+ ConvertJavaStringToUTF16(env, str.obj(), out->data() + back + i);
+ }
+}
+
+void AppendJavaStringArrayToStringVector(JNIEnv* env,
+ jobjectArray array,
+ std::vector<std::string>* out) {
+ DCHECK(out);
+ if (!array)
+ return;
+ size_t len = SafeGetArrayLength(env, array);
+ size_t back = out->size();
+ out->resize(back + len);
+ for (size_t i = 0; i < len; ++i) {
+ ScopedJavaLocalRef<jstring> str(env,
+ static_cast<jstring>(env->GetObjectArrayElement(array, i)));
+ ConvertJavaStringToUTF8(env, str.obj(), out->data() + back + i);
+ }
+}
+
+void AppendJavaByteArrayToByteVector(JNIEnv* env,
+ jbyteArray byte_array,
+ std::vector<uint8_t>* out) {
+ DCHECK(out);
+ if (!byte_array)
+ return;
+ size_t len = SafeGetArrayLength(env, byte_array);
+ if (!len)
+ return;
+ size_t back = out->size();
+ out->resize(back + len);
+ env->GetByteArrayRegion(byte_array, 0, len,
+ reinterpret_cast<int8_t*>(out->data() + back));
+}
+
+void JavaByteArrayToByteVector(JNIEnv* env,
+ jbyteArray byte_array,
+ std::vector<uint8_t>* out) {
+ DCHECK(out);
+ DCHECK(byte_array);
+ out->clear();
+ AppendJavaByteArrayToByteVector(env, byte_array, out);
+}
+
+void JavaBooleanArrayToBoolVector(JNIEnv* env,
+ jbooleanArray boolean_array,
+ std::vector<bool>* out) {
+ DCHECK(out);
+ if (!boolean_array)
+ return;
+ size_t len = SafeGetArrayLength(env, boolean_array);
+ if (!len)
+ return;
+ out->resize(len);
+ // It is not possible to get bool* out of vector<bool>.
+ jboolean* values = env->GetBooleanArrayElements(boolean_array, nullptr);
+ for (size_t i = 0; i < len; ++i) {
+ out->at(i) = static_cast<bool>(values[i]);
+ }
+}
+
+void JavaIntArrayToIntVector(JNIEnv* env,
+ jintArray int_array,
+ std::vector<int>* out) {
+ DCHECK(out);
+ size_t len = SafeGetArrayLength(env, int_array);
+ out->resize(len);
+ if (!len)
+ return;
+ env->GetIntArrayRegion(int_array, 0, len, out->data());
+}
+
+void JavaLongArrayToInt64Vector(JNIEnv* env,
+ jlongArray long_array,
+ std::vector<int64_t>* out) {
+ DCHECK(out);
+ std::vector<jlong> temp;
+ JavaLongArrayToLongVector(env, long_array, &temp);
+ out->resize(0);
+ out->insert(out->begin(), temp.begin(), temp.end());
+}
+
+void JavaLongArrayToLongVector(JNIEnv* env,
+ jlongArray long_array,
+ std::vector<jlong>* out) {
+ DCHECK(out);
+ size_t len = SafeGetArrayLength(env, long_array);
+ out->resize(len);
+ if (!len)
+ return;
+ env->GetLongArrayRegion(long_array, 0, len, out->data());
+}
+
+void JavaFloatArrayToFloatVector(JNIEnv* env,
+ jfloatArray float_array,
+ std::vector<float>* out) {
+ DCHECK(out);
+ size_t len = SafeGetArrayLength(env, float_array);
+ out->resize(len);
+ if (!len)
+ return;
+ env->GetFloatArrayRegion(float_array, 0, len, out->data());
+}
+
+void JavaArrayOfByteArrayToStringVector(
+ JNIEnv* env,
+ jobjectArray array,
+ std::vector<std::string>* out) {
+ DCHECK(out);
+ size_t len = SafeGetArrayLength(env, array);
+ out->resize(len);
+ for (size_t i = 0; i < len; ++i) {
+ ScopedJavaLocalRef<jbyteArray> bytes_array(
+ env, static_cast<jbyteArray>(
+ env->GetObjectArrayElement(array, i)));
+ jsize bytes_len = env->GetArrayLength(bytes_array.obj());
+ jbyte* bytes = env->GetByteArrayElements(bytes_array.obj(), nullptr);
+ (*out)[i].assign(reinterpret_cast<const char*>(bytes), bytes_len);
+ env->ReleaseByteArrayElements(bytes_array.obj(), bytes, JNI_ABORT);
+ }
+}
+
+void JavaArrayOfIntArrayToIntVector(
+ JNIEnv* env,
+ jobjectArray array,
+ std::vector<std::vector<int>>* out) {
+ DCHECK(out);
+ size_t len = SafeGetArrayLength(env, array);
+ out->resize(len);
+ for (size_t i = 0; i < len; ++i) {
+ ScopedJavaLocalRef<jintArray> int_array(
+ env, static_cast<jintArray>(env->GetObjectArrayElement(array, i)));
+ JavaIntArrayToIntVector(env, int_array.obj(), &out->at(i));
+ }
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/jni_array.h b/base/android/jni_array.h
new file mode 100644
index 0000000000..66c56ef309
--- /dev/null
+++ b/base/android/jni_array.h
@@ -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.
+
+#ifndef BASE_ANDROID_JNI_ARRAY_H_
+#define BASE_ANDROID_JNI_ARRAY_H_
+
+#include <jni.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/strings/string16.h"
+
+namespace base {
+namespace android {
+
+// Returns a new Java byte array converted from the given bytes array.
+BASE_EXPORT ScopedJavaLocalRef<jbyteArray> ToJavaByteArray(JNIEnv* env,
+ const uint8_t* bytes,
+ size_t len);
+
+BASE_EXPORT ScopedJavaLocalRef<jbyteArray> ToJavaByteArray(
+ JNIEnv* env,
+ const std::vector<uint8_t>& bytes);
+
+// Returns a new Java boolean array converted from the given bool array.
+BASE_EXPORT ScopedJavaLocalRef<jbooleanArray>
+ToJavaBooleanArray(JNIEnv* env, const bool* bools, size_t len);
+
+// Returns a new Java int array converted from the given int array.
+BASE_EXPORT ScopedJavaLocalRef<jintArray> ToJavaIntArray(
+ JNIEnv* env, const int* ints, size_t len);
+
+BASE_EXPORT ScopedJavaLocalRef<jintArray> ToJavaIntArray(
+ JNIEnv* env, const std::vector<int>& ints);
+
+// Returns a new Java long array converted from the given int64_t array.
+BASE_EXPORT ScopedJavaLocalRef<jlongArray> ToJavaLongArray(JNIEnv* env,
+ const int64_t* longs,
+ size_t len);
+
+BASE_EXPORT ScopedJavaLocalRef<jlongArray> ToJavaLongArray(
+ JNIEnv* env,
+ const std::vector<int64_t>& longs);
+
+// Returns a new Java float array converted from the given C++ float array.
+BASE_EXPORT ScopedJavaLocalRef<jfloatArray> ToJavaFloatArray(
+ JNIEnv* env, const float* floats, size_t len);
+
+BASE_EXPORT ScopedJavaLocalRef<jfloatArray> ToJavaFloatArray(
+ JNIEnv* env,
+ const std::vector<float>& floats);
+
+// Returns a array of Java byte array converted from |v|.
+BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfByteArray(
+ JNIEnv* env, const std::vector<std::string>& v);
+
+BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings(
+ JNIEnv* env, const std::vector<std::string>& v);
+
+BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings(
+ JNIEnv* env, const std::vector<string16>& v);
+
+// Converts a Java string array to a native array.
+BASE_EXPORT void AppendJavaStringArrayToStringVector(
+ JNIEnv* env,
+ jobjectArray array,
+ std::vector<string16>* out);
+
+BASE_EXPORT void AppendJavaStringArrayToStringVector(
+ JNIEnv* env,
+ jobjectArray array,
+ std::vector<std::string>* out);
+
+// Appends the Java bytes in |bytes_array| onto the end of |out|.
+BASE_EXPORT void AppendJavaByteArrayToByteVector(JNIEnv* env,
+ jbyteArray byte_array,
+ std::vector<uint8_t>* out);
+
+// Replaces the content of |out| with the Java bytes in |bytes_array|.
+BASE_EXPORT void JavaByteArrayToByteVector(JNIEnv* env,
+ jbyteArray byte_array,
+ std::vector<uint8_t>* out);
+
+// Replaces the content of |out| with the Java booleans in |boolean_array|.
+BASE_EXPORT void JavaBooleanArrayToBoolVector(JNIEnv* env,
+ jbooleanArray boolean_array,
+ std::vector<bool>* out);
+
+// Replaces the content of |out| with the Java ints in |int_array|.
+BASE_EXPORT void JavaIntArrayToIntVector(
+ JNIEnv* env,
+ jintArray int_array,
+ std::vector<int>* out);
+
+// Replaces the content of |out| with the Java longs in |long_array|.
+BASE_EXPORT void JavaLongArrayToInt64Vector(JNIEnv* env,
+ jlongArray long_array,
+ std::vector<int64_t>* out);
+
+// Replaces the content of |out| with the Java longs in |long_array|.
+BASE_EXPORT void JavaLongArrayToLongVector(
+ JNIEnv* env,
+ jlongArray long_array,
+ std::vector<jlong>* out);
+
+// Replaces the content of |out| with the Java floats in |float_array|.
+BASE_EXPORT void JavaFloatArrayToFloatVector(
+ JNIEnv* env,
+ jfloatArray float_array,
+ std::vector<float>* out);
+
+// Assuming |array| is an byte[][] (array of byte arrays), replaces the
+// content of |out| with the corresponding vector of strings. No UTF-8
+// conversion is performed.
+BASE_EXPORT void JavaArrayOfByteArrayToStringVector(
+ JNIEnv* env,
+ jobjectArray array,
+ std::vector<std::string>* out);
+
+// Assuming |array| is an int[][] (array of int arrays), replaces the
+// contents of |out| with the corresponding vectors of ints.
+BASE_EXPORT void JavaArrayOfIntArrayToIntVector(
+ JNIEnv* env,
+ jobjectArray array,
+ std::vector<std::vector<int>>* out);
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_JNI_ARRAY_H_
diff --git a/base/android/jni_generator/AndroidManifest.xml b/base/android/jni_generator/AndroidManifest.xml
new file mode 100644
index 0000000000..ec28ff5d37
--- /dev/null
+++ b/base/android/jni_generator/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.jni.generator">
+
+ <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="24" />
+ <application></application>
+
+</manifest>
diff --git a/base/android/jni_generator/PRESUBMIT.py b/base/android/jni_generator/PRESUBMIT.py
new file mode 100644
index 0000000000..bc76d5bb97
--- /dev/null
+++ b/base/android/jni_generator/PRESUBMIT.py
@@ -0,0 +1,37 @@
+# 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.
+
+"""Presubmit script for android buildbot.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for
+details on the presubmit API built into depot_tools.
+"""
+
+
+def CommonChecks(input_api, output_api):
+ base_android_jni_generator_dir = input_api.PresubmitLocalPath()
+
+ env = dict(input_api.environ)
+ env.update({
+ 'PYTHONPATH': base_android_jni_generator_dir,
+ 'PYTHONDONTWRITEBYTECODE': '1',
+ })
+
+ return input_api.canned_checks.RunUnitTests(
+ input_api,
+ output_api,
+ unit_tests=[
+ input_api.os_path.join(
+ base_android_jni_generator_dir, 'jni_generator_tests.py')
+ ],
+ env=env,
+ )
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return CommonChecks(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return CommonChecks(input_api, output_api)
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
index 42d8e56bbf..ba3abe7e53 100644
--- 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
@@ -15,84 +15,27 @@ import org.chromium.base.annotations.NativeClassQualifiedName;
import java.util.ArrayList;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
// 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.
+// * Generate a header file containing registration methods required to use C++ methods from 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
+ // Classes can store their C++ pointer counterpart as an int that is normally initialized by
// calling out a nativeInit() function. Replace "CPPClass" with your particular class name!
long mNativeCPPObject;
@@ -145,6 +88,16 @@ class SampleForTests {
void packagePrivateJavaMethod() {
}
+ // Method signature with generics in params.
+ @CalledByNative
+ public void methodWithGenericParams(
+ Map<String, Map<String, String>> foo, LinkedList<Integer> bar) {}
+
+ // Constructors will be exported to C++ as:
+ // Java_SampleForTests_Constructor(JNIEnv* env, jint foo, jint bar)
+ @CalledByNative
+ public SampleForTests(int foo, int bar) {}
+
// 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.
@@ -315,4 +268,17 @@ class SampleForTests {
@NativeCall("InnerClass")
private static native int nativeGetInnerIntFunction();
}
+
+ interface InnerInterface {}
+ enum InnerEnum {}
+
+ @CalledByNative
+ static InnerInterface getInnerInterface() {
+ return null;
+ }
+
+ @CalledByNative
+ static InnerEnum getInnerEnum() {
+ return null;
+ }
}
diff --git a/base/android/jni_generator/jni_exception_list.gni b/base/android/jni_generator/jni_exception_list.gni
new file mode 100644
index 0000000000..31d027cab2
--- /dev/null
+++ b/base/android/jni_generator/jni_exception_list.gni
@@ -0,0 +1,13 @@
+# 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.
+
+import("//device/vr/buildflags/buildflags.gni")
+
+jni_exception_files =
+ [ "//base/android/java/src/org/chromium/base/library_loader/Linker.java" ]
+
+# Exclude it from JNI registration if VR is not enabled.
+if (!enable_vr) {
+ jni_exception_files += [ "//chrome/android/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java" ]
+}
diff --git a/base/android/jni_generator/jni_generator.py b/base/android/jni_generator/jni_generator.py
index 99d8b424f0..ef676e26e1 100755
--- a/base/android/jni_generator/jni_generator.py
+++ b/base/android/jni_generator/jni_generator.py
@@ -11,7 +11,6 @@ import errno
import optparse
import os
import re
-import string
from string import Template
import subprocess
import sys
@@ -28,6 +27,35 @@ sys.path.append(BUILD_ANDROID_GYP)
from util import build_utils
+# Match single line comments, multiline comments, character literals, and
+# double-quoted strings.
+_COMMENT_REMOVER_REGEX = re.compile(
+ r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
+ re.DOTALL | re.MULTILINE)
+
+_EXTRACT_NATIVES_REGEX = re.compile(
+ r'(@NativeClassQualifiedName'
+ r'\(\"(?P<native_class_name>.*?)\"\)\s+)?'
+ r'(@NativeCall(\(\"(?P<java_class_name>.*?)\"\))\s+)?'
+ r'(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*native '
+ r'(?P<return_type>\S*) '
+ r'(?P<name>native\w+)\((?P<params>.*?)\);')
+
+_MAIN_DEX_REGEX = re.compile(
+ r'^\s*(?:@(?:\w+\.)*\w+\s+)*@MainDex\b',
+ re.MULTILINE)
+
+# Use 100 columns rather than 80 because it makes many lines more readable.
+_WRAP_LINE_LENGTH = 100
+# WrapOutput() is fairly slow. Pre-creating TextWrappers helps a bit.
+_WRAPPERS_BY_INDENT = [
+ textwrap.TextWrapper(width=_WRAP_LINE_LENGTH, expand_tabs=False,
+ replace_whitespace=False,
+ subsequent_indent=' ' * (indent + 4),
+ break_long_words=False)
+ for indent in xrange(50)] # 50 chosen experimentally.
+
+
class ParseError(Exception):
"""Exception thrown when we can't parse the input file."""
@@ -113,12 +141,14 @@ def JavaDataTypeToC(java_type):
java_type_map = {
'void': 'void',
'String': 'jstring',
+ 'Class': 'jclass',
'Throwable': 'jthrowable',
'java/lang/String': 'jstring',
'java/lang/Class': 'jclass',
'java/lang/Throwable': 'jthrowable',
}
+ java_type = _StripGenerics(java_type)
if java_type in java_pod_type_map:
return java_pod_type_map[java_type]
elif java_type in java_type_map:
@@ -127,10 +157,6 @@ def JavaDataTypeToC(java_type):
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'
@@ -143,7 +169,7 @@ def WrapCTypeForDeclaration(c_type):
return c_type
-def JavaDataTypeToCForDeclaration(java_type):
+def _JavaDataTypeToCForDeclaration(java_type):
"""Returns a JavaRef-wrapped C datatype for the given java type."""
return WrapCTypeForDeclaration(JavaDataTypeToC(java_type))
@@ -155,7 +181,7 @@ def JavaDataTypeToCForCalledByNativeParam(java_type):
else:
c_type = JavaDataTypeToC(java_type)
if re.match(RE_SCOPED_JNI_TYPES, c_type):
- return 'const base::android::JavaRefOrBare<' + c_type + '>&'
+ return 'const base::android::JavaRef<' + c_type + '>&'
else:
return c_type
@@ -176,65 +202,96 @@ def JavaReturnValueToC(java_type):
return java_pod_type_map.get(java_type, 'NULL')
-class JniParams(object):
- _imports = []
- _fully_qualified_class = ''
- _package = ''
- _inner_classes = []
- _implicit_imports = []
+def _GetJNIFirstParamType(native):
+ if native.type == 'function' and native.static:
+ return 'jclass'
+ return 'jobject'
- @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]
+def _GetJNIFirstParam(native, for_declaration):
+ c_type = _GetJNIFirstParamType(native)
+ if for_declaration:
+ c_type = WrapCTypeForDeclaration(c_type)
+ return [c_type + ' jcaller']
- @staticmethod
- def ExtractImportsAndInnerClasses(contents):
- if not JniParams._package:
- raise RuntimeError('SetFullyQualifiedClass must be called before '
- 'ExtractImportsAndInnerClasses')
+
+def _GetParamsInDeclaration(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(_GetJNIFirstParam(native, True) +
+ [_JavaDataTypeToCForDeclaration(param.datatype) + ' ' +
+ param.name
+ for param in native.params])
+
+
+def GetParamsInStub(native):
+ """Returns the params for the stub declaration.
+
+ Args:
+ native: the native dictionary describing the method.
+
+ Returns:
+ A string containing the params.
+ """
+ params = [JavaDataTypeToC(p.datatype) + ' ' + p.name for p in native.params]
+ return ',\n '.join(_GetJNIFirstParam(native, False) + params)
+
+
+def _StripGenerics(value):
+ """Strips Java generics from a string."""
+ nest_level = 0 # How deeply we are nested inside the generics.
+ start_index = 0 # Starting index of the last non-generic region.
+ out = []
+
+ for i, c in enumerate(value):
+ if c == '<':
+ if nest_level == 0:
+ out.append(value[start_index:i])
+ nest_level += 1
+ elif c == '>':
+ start_index = i + 1
+ nest_level -= 1
+ out.append(value[start_index:])
+
+ return ''.join(out)
+
+
+class JniParams(object):
+ """Get JNI related parameters."""
+
+ def __init__(self, fully_qualified_class):
+ self._fully_qualified_class = 'L' + fully_qualified_class
+ self._package = '/'.join(fully_qualified_class.split('/')[:-1])
+ self._imports = []
+ self._inner_classes = []
+ self._implicit_imports = []
+
+ def ExtractImportsAndInnerClasses(self, contents):
contents = contents.replace('\n', '')
re_import = re.compile(r'import.*?(?P<class>\S*?);')
for match in re.finditer(re_import, contents):
- JniParams._imports += ['L' + match.group('class').replace('.', '/')]
+ self._imports += ['L' + match.group('class').replace('.', '/')]
- re_inner = re.compile(r'(class|interface)\s+?(?P<name>\w+?)\W')
+ re_inner = re.compile(r'(class|interface|enum)\s+?(?P<name>\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 + '$' +
+ if not self._fully_qualified_class.endswith(inner):
+ self._inner_classes += [self._fully_qualified_class + '$' +
inner]
re_additional_imports = re.compile(
r'@JNIAdditionalImport\(\s*{?(?P<class_names>.*?)}?\s*\)')
for match in re.finditer(re_additional_imports, contents):
for class_name in match.group('class_names').split(','):
- JniParams.AddAdditionalImport(class_name.strip())
+ self._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):
+ def JavaToJni(self, param):
"""Converts a java param into a JNI signature type."""
pod_param_map = {
'int': 'I',
@@ -274,8 +331,7 @@ class JniParams(object):
return prefix + 'L' + param + ';'
for qualified_name in (object_param_list +
- [JniParams._fully_qualified_class] +
- JniParams._inner_classes):
+ [self._fully_qualified_class] + self._inner_classes):
if (qualified_name.endswith('/' + param) or
qualified_name.endswith('$' + param.replace('.', '$')) or
qualified_name == 'L' + param):
@@ -284,7 +340,7 @@ class JniParams(object):
# 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:
+ for qualified_name in self._imports:
if qualified_name.endswith('/' + param):
# Ensure it's not an inner class.
components = qualified_name.split('/')
@@ -301,32 +357,43 @@ class JniParams(object):
components = param.split('.')
outer = '/'.join(components[:-1])
inner = components[-1]
- for qualified_name in JniParams._imports:
+ for qualified_name in self._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('/', '.'),
+ (param, self._package.replace('/', '.'),
outer.replace('/', '.')))
- JniParams._CheckImplicitImports(param)
+ self._CheckImplicitImports(param)
# Type not found, falling back to same package as this class.
- return (prefix + 'L' + JniParams._package + '/' + param + ';')
+ return (prefix + 'L' + self._package + '/' + param + ';')
- @staticmethod
- def _CheckImplicitImports(param):
+ def _AddAdditionalImport(self, 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' % (self._package, raw_class_name)
+ if new_import in self._imports:
+ raise SyntaxError('Do not use JNIAdditionalImport on an already '
+ 'imported class: %s' % (new_import.replace('/', '.')))
+ self._imports += [new_import]
+
+ def _CheckImplicitImports(self, param):
# Ensure implicit imports, such as java.lang.*, are not being treated
# as being in the same package.
- if not JniParams._implicit_imports:
+ if not self._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:
+ self._implicit_imports = f.readlines()
+ for implicit_import in self._implicit_imports:
implicit_import = implicit_import.strip().replace('.class', '')
implicit_import = implicit_import.replace('/', '.')
if implicit_import.endswith('.' + param):
@@ -335,18 +402,22 @@ class JniParams(object):
'import %s;' %
(param, implicit_import))
-
- @staticmethod
- def Signature(params, returns, wrap):
+ def Signature(self, params, returns):
"""Returns the JNI signature for the given datatypes."""
items = ['(']
- items += [JniParams.JavaToJni(param.datatype) for param in params]
+ items += [self.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) + '"'
+ items += [self.JavaToJni(returns)]
+ return '"{}"'.format(''.join(items))
+
+ @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 Parse(params):
@@ -354,8 +425,9 @@ class JniParams(object):
if not params:
return []
ret = []
- for p in [p.strip() for p in params.split(',')]:
- items = p.split(' ')
+ params = _StripGenerics(params)
+ for p in params.split(','):
+ items = p.split()
# Remove @Annotations from parameters.
while items[0].startswith('@'):
@@ -393,13 +465,7 @@ 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<native_class_name>.*?)\"\)\s+)?'
- '(@NativeCall(\(\"(?P<java_class_name>.*?)\"\))\s+)?'
- '(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*native '
- '(?P<return_type>\S*) '
- '(?P<name>native\w+)\((?P<params>.*?)\);')
- for match in re.finditer(re_native, contents):
+ for match in _EXTRACT_NATIVES_REGEX.finditer(contents):
native = NativeMethod(
static='static' in match.group('qualifiers'),
java_class_name=match.group('java_class_name'),
@@ -413,21 +479,32 @@ def ExtractNatives(contents, ptr_type):
def IsMainDexJavaClass(contents):
- """Returns "true" if the class is annotated with "@MainDex", "false" if not.
+ """Returns True if the class or any of its methods are annotated as @MainDex.
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'
+ return bool(_MAIN_DEX_REGEX.search(contents))
+
+
+def GetBinaryClassName(fully_qualified_class):
+ """Returns a string concatenating the Java package and class."""
+ escaped = fully_qualified_class.replace('_', '_1')
+ return escaped.replace('/', '_').replace('$', '_00024')
+
+
+def GetRegistrationFunctionName(fully_qualified_class):
+ """Returns the register name with a given class."""
+ return 'RegisterNative_' + GetBinaryClassName(fully_qualified_class)
def GetStaticCastForReturnType(return_type):
type_map = { 'String' : 'jstring',
'java/lang/String' : 'jstring',
+ 'Class': 'jclass',
+ 'java/lang/Class': 'jclass',
'Throwable': 'jthrowable',
'java/lang/Throwable': 'jthrowable',
'boolean[]': 'jbooleanArray',
@@ -438,6 +515,7 @@ def GetStaticCastForReturnType(return_type):
'long[]': 'jlongArray',
'float[]': 'jfloatArray',
'double[]': 'jdoubleArray' }
+ return_type = _StripGenerics(return_type)
ret = type_map.get(return_type, None)
if ret:
return ret
@@ -481,13 +559,14 @@ def GetMangledParam(datatype):
return ret
-def GetMangledMethodName(name, params, return_type):
+def GetMangledMethodName(jni_params, 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:
+ jni_params: JniParams object.
name: string.
params: list of Param.
return_type: string.
@@ -497,13 +576,13 @@ def GetMangledMethodName(name, params, return_type):
"""
mangled_items = []
for datatype in [return_type] + [x.datatype for x in params]:
- mangled_items += [GetMangledParam(JniParams.JavaToJni(datatype))]
+ mangled_items += [GetMangledParam(jni_params.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):
+def MangleCalledByNatives(jni_params, called_by_natives):
"""Mangles all the overloads from the call_by_natives list."""
method_counts = collections.defaultdict(
lambda: collections.defaultdict(lambda: 0))
@@ -516,7 +595,7 @@ def MangleCalledByNatives(called_by_natives):
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,
+ method_id_var_name = GetMangledMethodName(jni_params, method_name,
called_by_native.params,
called_by_native.return_type)
called_by_native.method_id_var_name = method_id_var_name
@@ -529,23 +608,26 @@ 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>(Unchecked)*?)(?:\("(?P<annotation>.*)"\))?'
- '\s+(?P<prefix>[\w ]*?)'
- '(:?\s*@\w+)?' # Ignore annotations in return types.
- '\s*(?P<return_type>\S+?)'
- '\s+(?P<name>\w+)'
- '\s*\((?P<params>[^\)]*)\)')
-
+ r'@CalledByNative(?P<Unchecked>(?:Unchecked)?)(?:\("(?P<annotation>.*)"\))?'
+ r'(?:\s+@\w+(?:\(.*\))?)*' # Ignore any other annotations.
+ r'\s+(?P<prefix>('
+ r'(private|protected|public|static|abstract|final|default|synchronized)'
+ r'\s*)*)'
+ r'(?:\s*@\w+)?' # Ignore annotations in return types.
+ r'\s*(?P<return_type>\S*?)'
+ r'\s*(?P<name>\w+)'
+ r'\s*\((?P<params>[^\)]*)\)')
# 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):
+def ExtractCalledByNatives(jni_params, contents):
"""Parses all methods annotated with @CalledByNative.
Args:
+ jni_params: JniParams object.
contents: the contents of the java file.
Returns:
@@ -557,13 +639,23 @@ def ExtractCalledByNatives(contents):
"""
called_by_natives = []
for match in re.finditer(RE_CALLED_BY_NATIVE, contents):
+ return_type = match.group('return_type')
+ name = match.group('name')
+ if not return_type:
+ is_constructor = True
+ return_type = name
+ name = "Constructor"
+ else:
+ is_constructor = False
+
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'),
+ return_type=return_type,
+ name=name,
+ is_constructor=is_constructor,
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')
@@ -571,7 +663,25 @@ def ExtractCalledByNatives(contents):
if '@CalledByNative' in line1:
raise ParseError('could not parse @CalledByNative method signature',
line1, line2)
- return MangleCalledByNatives(called_by_natives)
+ return MangleCalledByNatives(jni_params, called_by_natives)
+
+
+def RemoveComments(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 _COMMENT_REMOVER_REGEX.sub(replacer, contents)
class JNIFromJavaP(object):
@@ -591,7 +701,7 @@ class JNIFromJavaP(object):
# Java 7's javap includes type parameters in output, like HashSet<T>. 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.jni_params = JniParams(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
@@ -628,8 +738,8 @@ class JNIFromJavaP(object):
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.called_by_natives = MangleCalledByNatives(self.jni_params,
+ self.called_by_natives)
self.constant_fields = []
re_constant_field = re.compile('.*?public static final int (?P<name>.*?);')
re_constant_field_value = re.compile(
@@ -648,7 +758,7 @@ class JNIFromJavaP(object):
self.inl_header_file_generator = InlHeaderFileGenerator(
self.namespace, self.fully_qualified_class, [], self.called_by_natives,
- self.constant_fields, options)
+ self.constant_fields, self.jni_params, options)
def GetContent(self):
return self.inl_header_file_generator.GetContent()
@@ -669,46 +779,21 @@ class JNIFromJavaP(object):
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)
+ contents = RemoveComments(contents)
+ self.jni_params = JniParams(fully_qualified_class)
+ self.jni_params.ExtractImportsAndInnerClasses(contents)
jni_namespace = ExtractJNINamespace(contents) or options.namespace
natives = ExtractNatives(contents, options.ptr_type)
- called_by_natives = ExtractCalledByNatives(contents)
- maindex = IsMainDexJavaClass(contents)
+ called_by_natives = ExtractCalledByNatives(self.jni_params, 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.jni_params, options)
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
@@ -720,11 +805,97 @@ class JNIFromJavaSource(object):
return JNIFromJavaSource(contents, fully_qualified_class, options)
+class HeaderFileGeneratorHelper(object):
+ """Include helper methods for header generators."""
+
+ def __init__(self, class_name, fully_qualified_class):
+ self.class_name = class_name
+ self.fully_qualified_class = fully_qualified_class
+
+ 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
+ if native.java_class_name:
+ java_name += '$' + native.java_class_name
+
+ values = {'NAME': native.name,
+ 'JAVA_NAME': GetBinaryClassName(java_name)}
+ 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 GetClassPathLines(self, classes, declare_only=False):
+ """Returns the ClassPath constants."""
+ ret = []
+ if declare_only:
+ template = Template("""
+extern const char kClassPath_${JAVA_CLASS}[];
+""")
+ else:
+ template = Template("""
+JNI_REGISTRATION_EXPORT extern const char kClassPath_${JAVA_CLASS}[];
+const char kClassPath_${JAVA_CLASS}[] = \
+"${JNI_CLASS_PATH}";
+""")
+
+ for full_clazz in classes.itervalues():
+ values = {
+ 'JAVA_CLASS': GetBinaryClassName(full_clazz),
+ 'JNI_CLASS_PATH': full_clazz,
+ }
+ ret += [template.substitute(values)]
+
+ class_getter = """\
+#ifndef ${JAVA_CLASS}_clazz_defined
+#define ${JAVA_CLASS}_clazz_defined
+inline jclass ${JAVA_CLASS}_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_${JAVA_CLASS}, \
+&g_${JAVA_CLASS}_clazz);
+}
+#endif
+"""
+ if declare_only:
+ template = Template("""\
+extern base::subtle::AtomicWord g_${JAVA_CLASS}_clazz;
+""" + class_getter)
+ else:
+ template = Template("""\
+// Leaking this jclass as we cannot use LazyInstance from some threads.
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_${JAVA_CLASS}_clazz = 0;
+""" + class_getter)
+
+ for full_clazz in classes.itervalues():
+ values = {
+ 'JAVA_CLASS': GetBinaryClassName(full_clazz),
+ }
+ ret += [template.substitute(values)]
+
+ return ''.join(ret)
+
+
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'):
+ called_by_natives, constant_fields, jni_params, options):
self.namespace = namespace
self.fully_qualified_class = fully_qualified_class
self.class_name = self.fully_qualified_class.split('/')[-1]
@@ -732,8 +903,10 @@ class InlHeaderFileGenerator(object):
self.called_by_natives = called_by_natives
self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI'
self.constant_fields = constant_fields
- self.maindex = maindex
+ self.jni_params = jni_params
self.options = options
+ self.helper = HeaderFileGeneratorHelper(
+ self.class_name, fully_qualified_class)
def GetContent(self):
@@ -756,26 +929,16 @@ class InlHeaderFileGenerator(object):
${INCLUDES}
-#include "base/android/jni_int_wrapper.h"
-
-// Step 1: forward declarations.
-namespace {
+// Step 1: Forward declarations.
$CLASS_PATH_DEFINITIONS
-} // namespace
-
-$OPEN_NAMESPACE
+// Step 2: Constants (optional).
-$CONSTANT_FIELDS
+$CONSTANT_FIELDS\
-// Step 2: method stubs.
+// Step 3: Method stubs.
$METHOD_STUBS
-// Step 3: RegisterNatives.
-$JNI_NATIVE_METHODS
-$REGISTER_NATIVES
-$CLOSE_NAMESPACE
-
#endif // ${HEADER_GUARD}
""")
values = {
@@ -784,21 +947,26 @@ $CLOSE_NAMESPACE
'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'] == ''))
+ open_namespace = self.GetOpenNamespaceString()
+ if open_namespace:
+ close_namespace = self.GetCloseNamespaceString()
+ values['METHOD_STUBS'] = '\n'.join([
+ open_namespace, values['METHOD_STUBS'], close_namespace])
+
+ constant_fields = values['CONSTANT_FIELDS']
+ if constant_fields:
+ values['CONSTANT_FIELDS'] = '\n'.join([
+ open_namespace, constant_fields, close_namespace])
+
return WrapOutput(template.substitute(values))
def GetClassPathDefinitionsString(self):
- ret = []
- ret += [self.GetClassPathDefinitions()]
- return '\n'.join(ret)
+ classes = self.helper.GetUniqueClasses(self.called_by_natives)
+ classes.update(self.helper.GetUniqueClasses(self.natives))
+ return self.helper.GetClassPathLines(classes)
def GetConstantFieldsString(self):
if not self.constant_fields:
@@ -806,7 +974,7 @@ $CLOSE_NAMESPACE
ret = ['enum Java_%s_constant_fields {' % self.class_name]
for c in self.constant_fields:
ret += [' %s = %s,' % (c.name, c.value)]
- ret += ['};']
+ ret += ['};', '']
return '\n'.join(ret)
def GetMethodStubsString(self):
@@ -825,92 +993,13 @@ $CLOSE_NAMESPACE
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)
+ return '\n'.join('#include "%s"' % x for x in includes) + '\n'
def GetOpenNamespaceString(self):
if self.namespace:
all_namespaces = ['namespace %s {' % ns
for ns in self.namespace.split('::')]
- return '\n'.join(all_namespaces)
+ return '\n'.join(all_namespaces) + '\n'
return ''
def GetCloseNamespaceString(self):
@@ -918,77 +1007,15 @@ ${NATIVES}
all_namespaces = ['} // namespace %s' % ns
for ns in self.namespace.split('::')]
all_namespaces.reverse()
- return '\n'.join(all_namespaces) + '\n'
+ return '\n' + '\n'.join(all_namespaces)
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({
@@ -997,9 +1024,16 @@ ${NATIVES}
})
def GetJNIFirstParamForCall(self, native):
- c_type = self.GetJNIFirstParamType(native)
+ c_type = _GetJNIFirstParamType(native)
return [self.GetJavaParamRefForCall(c_type, 'jcaller')]
+ def GetImplementationMethodName(self, native):
+ class_name = self.class_name
+ if native.java_class_name is not None:
+ # Inner class
+ class_name = native.java_class_name
+ return "JNI_%s_%s" % (class_name, native.name)
+
def GetNativeStub(self, native):
is_method = native.type == 'method'
@@ -1024,19 +1058,22 @@ ${NATIVES}
'>')
profiling_entered_native = ''
if self.options.enable_profiling:
- profiling_entered_native = 'JNI_LINK_SAVED_FRAME_POINTER;'
+ profiling_entered_native = ' JNI_LINK_SAVED_FRAME_POINTER;\n'
values = {
'RETURN': return_type,
'RETURN_DECLARATION': return_declaration,
'NAME': native.name,
- 'PARAMS': self.GetParamsInDeclaration(native),
- 'PARAMS_IN_STUB': self.GetParamsInStub(native),
+ 'IMPL_METHOD_NAME': self.GetImplementationMethodName(native),
+ 'PARAMS': _GetParamsInDeclaration(native),
+ 'PARAMS_IN_STUB': GetParamsInStub(native),
'PARAMS_IN_CALL': params_in_call,
'POST_CALL': post_call,
- 'STUB_NAME': self.GetStubName(native),
+ 'STUB_NAME': self.helper.GetStubName(native),
'PROFILING_ENTERED_NATIVE': profiling_entered_native,
+ 'TRACE_EVENT': '',
}
+ namespace_qual = self.namespace + '::' if self.namespace else ''
if is_method:
optional_error_return = JavaReturnValueToC(native.return_type)
if optional_error_return:
@@ -1046,21 +1083,33 @@ ${NATIVES}
'PARAM0_NAME': native.params[0].name,
'P0_TYPE': native.p0_type,
})
+ if self.options.enable_tracing:
+ values['TRACE_EVENT'] = self.GetTraceEventForNameTemplate(
+ namespace_qual + '${P0_TYPE}::${NAME}', values);
template = Template("""\
-JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS_IN_STUB}) {
- ${PROFILING_ENTERED_NATIVE}
+JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(
+ JNIEnv* env,
+ ${PARAMS_IN_STUB}) {
+${PROFILING_ENTERED_NATIVE}\
+${TRACE_EVENT}\
${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};
+ if self.options.enable_tracing:
+ values['TRACE_EVENT'] = self.GetTraceEventForNameTemplate(
+ namespace_qual + '${IMPL_METHOD_NAME}', values)
+ template = Template("""\
+static ${RETURN_DECLARATION} ${IMPL_METHOD_NAME}(JNIEnv* env, ${PARAMS});
+
+JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(
+ JNIEnv* env,
+ ${PARAMS_IN_STUB}) {
+${PROFILING_ENTERED_NATIVE}\
+${TRACE_EVENT}\
+ return ${IMPL_METHOD_NAME}(${PARAMS_IN_CALL})${POST_CALL};
}
""")
@@ -1080,13 +1129,17 @@ JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS_IN_STUB}) {
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
+ java_class_only = called_by_native.java_class_name or self.class_name
+ java_class = self.fully_qualified_class
+ if called_by_native.java_class_name:
+ java_class += '$' + called_by_native.java_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)
+ first_param_in_call = ('%s_clazz(env)' % GetBinaryClassName(java_class))
else:
first_param_in_declaration = (
- ', const base::android::JavaRefOrBare<jobject>& obj')
+ ', const base::android::JavaRef<jobject>& obj')
first_param_in_call = 'obj.obj()'
params_in_declaration = self.GetCalledByNativeParamsInDeclaration(
called_by_native)
@@ -1119,9 +1172,21 @@ JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS_IN_STUB}) {
return_clause = 'return ret;'
profiling_leaving_native = ''
if self.options.enable_profiling:
- profiling_leaving_native = 'JNI_SAVE_FRAME_POINTER;'
+ profiling_leaving_native = ' JNI_SAVE_FRAME_POINTER;\n'
+ jni_name = called_by_native.name
+ jni_return_type = called_by_native.return_type
+ if called_by_native.is_constructor:
+ jni_name = '<init>'
+ jni_return_type = 'void'
+ if called_by_native.signature:
+ jni_signature = called_by_native.signature
+ else:
+ jni_signature = self.jni_params.Signature(
+ called_by_native.params, jni_return_type)
+ java_name_full = java_class.replace('/', '.') + '.' + jni_name
return {
- 'JAVA_CLASS': java_class,
+ 'JAVA_CLASS_ONLY': java_class_only,
+ 'JAVA_CLASS': GetBinaryClassName(java_class),
'RETURN_TYPE': return_type,
'OPTIONAL_ERROR_RETURN': optional_error_return,
'RETURN_DECLARATION': return_declaration,
@@ -1133,17 +1198,19 @@ JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS_IN_STUB}) {
'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,
+ 'JNI_NAME': jni_name,
+ 'JNI_SIGNATURE': jni_signature,
+ 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name,
+ 'METHOD_ID_TYPE': 'STATIC' if called_by_native.static else 'INSTANCE',
+ 'JAVA_NAME_FULL': java_name_full,
}
-
def GetLazyCalledByNativeMethodStub(self, called_by_native):
"""Returns a string."""
function_signature_template = Template("""\
-static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\
+static ${RETURN_TYPE} Java_${JAVA_CLASS_ONLY}_${METHOD_ID_VAR_NAME}(\
JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""")
function_header_template = Template("""\
${FUNCTION_SIGNATURE} {""")
@@ -1155,9 +1222,15 @@ 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}
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_${METHOD_ID_TYPE}>(
+ env, ${JAVA_CLASS}_clazz(env),
+ "${JNI_NAME}",
+ ${JNI_SIGNATURE},
+ &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME});
+
+${TRACE_EVENT}\
+${PROFILING_LEAVING_NATIVE}\
${RETURN_DECLARATION}
${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL},
method_id${PARAMS_IN_CALL})${POST_CALL};
@@ -1172,109 +1245,30 @@ ${FUNCTION_HEADER}
function_header_with_unused_template.substitute(values))
else:
values['FUNCTION_HEADER'] = function_header_template.substitute(values)
+ if self.options.enable_tracing:
+ values['TRACE_EVENT'] = self.GetTraceEventForNameTemplate(
+ '${JAVA_NAME_FULL}', values)
+ else:
+ values['TRACE_EVENT'] = ''
return RemoveIndentedEmptyLines(template.substitute(values))
- def GetKMethodArrayEntry(self, native):
- template = Template(' { "native${NAME}", ${JNI_SIGNATURE}, ' +
- 'reinterpret_cast<void*>(${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 = '<init>'
- 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 GetTraceEventForNameTemplate(self, name_template, values):
+ name = Template(name_template).substitute(values)
+ return ' TRACE_EVENT0("jni", "%s");' % name
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)
+ # Do not wrap preprocessor directives or comments.
+ if len(line) < _WRAP_LINE_LENGTH or line[0] == '#' or line.startswith('//'):
+ ret.append(line)
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)]
+ # Assumes that the line is not already indented as a continuation line,
+ # which is not always true (oh well).
+ first_line_indent = (len(line) - len(line.lstrip()))
+ wrapper = _WRAPPERS_BY_INDENT[first_line_indent]
+ ret.extend(wrapper.wrap(line))
ret += ['']
return '\n'.join(ret)
@@ -1321,19 +1315,21 @@ def GenerateJNIHeader(input_file, output_file, options):
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)
+ WriteOutput(output_file, content)
else:
print content
+def WriteOutput(output_file, content):
+ if os.path.exists(output_file):
+ with open(output_file) as f:
+ existing_content = f.read()
+ if existing_content == content:
+ return
+ with open(output_file, 'w') as f:
+ f.write(content)
+
+
def GetScriptName():
script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
base_index = 0
@@ -1370,10 +1366,6 @@ See SampleForTests.java for more details.
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.')
@@ -1389,11 +1381,10 @@ See SampleForTests.java for more details.
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.')
+ option_parser.add_option('--enable_tracing', action='store_true',
+ help='Add TRACE_EVENTs to generated functions.')
options, args = option_parser.parse_args(argv)
if options.jar_file:
input_file = ExtractJarInputFile(options.jar_file, options.input_file,
diff --git a/base/android/jni_generator/jni_generator_helper.h b/base/android/jni_generator/jni_generator_helper.h
index 3062806a85..3347fee1fe 100644
--- a/base/android/jni_generator/jni_generator_helper.h
+++ b/base/android/jni_generator/jni_generator_helper.h
@@ -8,8 +8,10 @@
#include <jni.h>
#include "base/android/jni_android.h"
+#include "base/android/jni_int_wrapper.h"
#include "base/android/scoped_java_ref.h"
#include "base/logging.h"
+#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
// Project-specific macros used by the header files generated by
@@ -30,6 +32,13 @@
#define JNI_GENERATOR_EXPORT extern "C" __attribute__((visibility("default")))
#endif
+// Used to export JNI registration functions.
+#if defined(COMPONENT_BUILD)
+#define JNI_REGISTRATION_EXPORT __attribute__((visibility("default")))
+#else
+#define JNI_REGISTRATION_EXPORT
+#endif
+
namespace jni_generator {
inline void HandleRegistrationError(JNIEnv* env,
@@ -42,21 +51,6 @@ 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
index c0c82388dc..12812a5c54 100755
--- a/base/android/jni_generator/jni_generator_tests.py
+++ b/base/android/jni_generator/jni_generator_tests.py
@@ -18,7 +18,11 @@ import os
import sys
import unittest
import jni_generator
-from jni_generator import CalledByNative, JniParams, NativeMethod, Param
+import jni_registration_generator
+from jni_generator import CalledByNative
+from jni_generator import IsMainDexJavaClass
+from jni_generator import NativeMethod
+from jni_generator import Param
SCRIPT_NAME = 'base/android/jni_generator/jni_generator.py'
@@ -42,6 +46,7 @@ class TestOptions(object):
self.javap = 'javap'
self.native_exports_optional = True
self.enable_profiling = False
+ self.enable_tracing = False
class TestGenerator(unittest.TestCase):
def assertObjEquals(self, first, second):
@@ -96,14 +101,14 @@ class TestGenerator(unittest.TestCase):
with file(golden_file, 'r') as f:
return f.read()
- def assertGoldenTextEquals(self, generated_text):
+ def assertGoldenTextEquals(self, generated_text, suffix=''):
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_file = os.path.join(script_dir, '%s%s.golden' % (caller, suffix))
golden_text = self._ReadGoldenFile(golden_file)
if os.environ.get(REBASELINE_ENV):
if golden_text != generated_text:
@@ -120,6 +125,9 @@ class TestGenerator(unittest.TestCase):
def testNatives(self):
test_data = """"
+ import android.graphics.Bitmap;
+ import android.view.View;
+
interface OnFrameAvailableListener {}
private native int nativeInit();
private native void nativeDestroy(int nativeChromeBrowserProvider);
@@ -148,9 +156,9 @@ class TestGenerator(unittest.TestCase):
double alpha, double beta, double gamma);
private static native Throwable nativeMessWithJavaException(Throwable e);
"""
- jni_generator.JniParams.SetFullyQualifiedClass(
+ jni_params = jni_generator.JniParams(
'org/chromium/example/jni_generator/SampleForTests')
- jni_generator.JniParams.ExtractImportsAndInnerClasses(test_data)
+ jni_params.ExtractImportsAndInnerClasses(test_data)
natives = jni_generator.ExtractNatives(test_data, 'int')
golden_natives = [
NativeMethod(return_type='int', static=False,
@@ -277,9 +285,20 @@ class TestGenerator(unittest.TestCase):
type='function')
]
self.assertListEquals(golden_natives, natives)
- h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni',
- natives, [], [], TestOptions())
- self.assertGoldenTextEquals(h.GetContent())
+ h1 = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni',
+ natives, [], [], jni_params,
+ TestOptions())
+ self.assertGoldenTextEquals(h1.GetContent())
+ h2 = jni_registration_generator.HeaderGenerator(
+ '', 'org/chromium/TestJni', natives, jni_params, True)
+ content = h2.Generate()
+ for k in jni_registration_generator.MERGEABLE_KEYS:
+ content[k] = content.get(k, '')
+
+ self.assertGoldenTextEquals(
+ jni_registration_generator.CreateFromDict(content),
+ suffix='Registrations')
+
def testInnerClassNatives(self):
test_data = """
@@ -296,8 +315,10 @@ class TestGenerator(unittest.TestCase):
type='function')
]
self.assertListEquals(golden_natives, natives)
+ jni_params = jni_generator.JniParams('')
h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni',
- natives, [], [], TestOptions())
+ natives, [], [], jni_params,
+ TestOptions())
self.assertGoldenTextEquals(h.GetContent())
def testInnerClassNativesMultiple(self):
@@ -323,8 +344,10 @@ class TestGenerator(unittest.TestCase):
type='function')
]
self.assertListEquals(golden_natives, natives)
+ jni_params = jni_generator.JniParams('')
h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni',
- natives, [], [], TestOptions())
+ natives, [], [], jni_params,
+ TestOptions())
self.assertGoldenTextEquals(h.GetContent())
def testInnerClassNativesBothInnerAndOuter(self):
@@ -349,10 +372,22 @@ class TestGenerator(unittest.TestCase):
type='function')
]
self.assertListEquals(golden_natives, natives)
+ jni_params = jni_generator.JniParams('')
h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni',
- natives, [], [], TestOptions())
+ natives, [], [], jni_params,
+ TestOptions())
self.assertGoldenTextEquals(h.GetContent())
+ h2 = jni_registration_generator.HeaderGenerator(
+ '', 'org/chromium/TestJni', natives, jni_params, True)
+ content = h2.Generate()
+ for k in jni_registration_generator.MERGEABLE_KEYS:
+ content[k] = content.get(k, '')
+
+ self.assertGoldenTextEquals(
+ jni_registration_generator.CreateFromDict(content),
+ suffix='Registrations')
+
def testCalledByNatives(self):
test_data = """"
import android.graphics.Bitmap;
@@ -363,7 +398,9 @@ class TestGenerator(unittest.TestCase):
class InnerClass {}
@CalledByNative
- InnerClass showConfirmInfoBar(int nativeInfoBar,
+ @SomeOtherA
+ @SomeOtherB
+ public InnerClass showConfirmInfoBar(int nativeInfoBar,
String buttonOk, String buttonCancel, String title, Bitmap icon) {
InfoBar infobar = new ConfirmInfoBar(nativeInfoBar, mContext,
buttonOk, buttonCancel,
@@ -443,9 +480,10 @@ class TestGenerator(unittest.TestCase):
@CalledByNative
public List<Bitmap.CompressFormat> getCompressFormatList();
"""
- jni_generator.JniParams.SetFullyQualifiedClass('org/chromium/Foo')
- jni_generator.JniParams.ExtractImportsAndInnerClasses(test_data)
- called_by_natives = jni_generator.ExtractCalledByNatives(test_data)
+ jni_params = jni_generator.JniParams('org/chromium/Foo')
+ jni_params.ExtractImportsAndInnerClasses(test_data)
+ called_by_natives = jni_generator.ExtractCalledByNatives(jni_params,
+ test_data)
golden_called_by_natives = [
CalledByNative(
return_type='InnerClass',
@@ -673,14 +711,15 @@ class TestGenerator(unittest.TestCase):
),
]
self.assertListEquals(golden_called_by_natives, called_by_natives)
- h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni',
- [], called_by_natives, [],
- TestOptions())
+ h = jni_generator.InlHeaderFileGenerator(
+ '', 'org/chromium/TestJni', [], called_by_natives, [], jni_params,
+ TestOptions())
self.assertGoldenTextEquals(h.GetContent())
def testCalledByNativeParseError(self):
try:
- jni_generator.ExtractCalledByNatives("""
+ jni_params = jni_generator.JniParams('')
+ jni_generator.ExtractCalledByNatives(jni_params, """
@CalledByNative
public static int foo(); // This one is fine
@@ -712,10 +751,11 @@ import org.chromium.base.BuildInfo;
'com/foo/Bar', 'no PACKAGE line')
def testMethodNameMangling(self):
+ jni_params = jni_generator.JniParams('')
self.assertEquals('closeV',
- jni_generator.GetMangledMethodName('close', [], 'void'))
+ jni_generator.GetMangledMethodName(jni_params, 'close', [], 'void'))
self.assertEquals('readI_AB_I_I',
- jni_generator.GetMangledMethodName('read',
+ jni_generator.GetMangledMethodName(jni_params, 'read',
[Param(name='p1',
datatype='byte[]'),
Param(name='p2',
@@ -724,7 +764,7 @@ import org.chromium.base.BuildInfo;
datatype='int'),],
'int'))
self.assertEquals('openJIIS_JLS',
- jni_generator.GetMangledMethodName('open',
+ jni_generator.GetMangledMethodName(jni_params, 'open',
[Param(name='p1',
datatype='java/lang/String'),],
'java/io/InputStream'))
@@ -734,12 +774,14 @@ import org.chromium.base.BuildInfo;
public abstract class java.util.HashSet<T> extends java.util.AbstractSet<E>
implements java.util.Set<E>, java.lang.Cloneable, java.io.Serializable {
public void dummy();
- Signature: ()V
+ Signature: ()V
+ public java.lang.Class<?> getClass();
+ Signature: ()Ljava/lang/Class<*>;
}
"""
jni_from_javap = jni_generator.JNIFromJavaP(contents.split('\n'),
TestOptions())
- self.assertEquals(1, len(jni_from_javap.called_by_natives))
+ self.assertEquals(2, len(jni_from_javap.called_by_natives))
self.assertGoldenTextEquals(jni_from_javap.GetContent())
def testSnippnetJavap6_7_8(self):
@@ -905,28 +947,27 @@ class Foo {
}
}
"""
- jni_generator.JniParams.SetFullyQualifiedClass(
- 'org/chromium/content/app/Foo')
- jni_generator.JniParams.ExtractImportsAndInnerClasses(import_header)
+ jni_params = jni_generator.JniParams('org/chromium/content/app/Foo')
+ jni_params.ExtractImportsAndInnerClasses(import_header)
self.assertTrue('Lorg/chromium/content/common/ISandboxedProcessService' in
- jni_generator.JniParams._imports)
+ jni_params._imports)
self.assertTrue('Lorg/chromium/Bar/Zoo' in
- jni_generator.JniParams._imports)
+ jni_params._imports)
self.assertTrue('Lorg/chromium/content/app/Foo$BookmarkNode' in
- jni_generator.JniParams._inner_classes)
+ jni_params._inner_classes)
self.assertTrue('Lorg/chromium/content/app/Foo$PasswordListObserver' in
- jni_generator.JniParams._inner_classes)
+ jni_params._inner_classes)
self.assertEquals('Lorg/chromium/content/app/ContentMain$Inner;',
- jni_generator.JniParams.JavaToJni('ContentMain.Inner'))
+ jni_params.JavaToJni('ContentMain.Inner'))
self.assertRaises(SyntaxError,
- jni_generator.JniParams.JavaToJni,
- 'AnException')
+ jni_params.JavaToJni, 'AnException')
def testJniParamsJavaToJni(self):
- self.assertTextEquals('I', JniParams.JavaToJni('int'))
- self.assertTextEquals('[B', JniParams.JavaToJni('byte[]'))
+ jni_params = jni_generator.JniParams('')
+ self.assertTextEquals('I', jni_params.JavaToJni('int'))
+ self.assertTextEquals('[B', jni_params.JavaToJni('byte[]'))
self.assertTextEquals(
- '[Ljava/nio/ByteBuffer;', JniParams.JavaToJni('java/nio/ByteBuffer[]'))
+ '[Ljava/nio/ByteBuffer;', jni_params.JavaToJni('java/nio/ByteBuffer[]'))
def testNativesLong(self):
test_options = TestOptions()
@@ -934,7 +975,8 @@ class Foo {
test_data = """"
private native void nativeDestroy(long nativeChromeBrowserProvider);
"""
- jni_generator.JniParams.ExtractImportsAndInnerClasses(test_data)
+ jni_params = jni_generator.JniParams('')
+ jni_params.ExtractImportsAndInnerClasses(test_data)
natives = jni_generator.ExtractNatives(test_data, test_options.ptr_type)
golden_natives = [
NativeMethod(return_type='void', static=False, name='Destroy',
@@ -947,35 +989,54 @@ class Foo {
]
self.assertListEquals(golden_natives, natives)
h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni',
- natives, [], [], test_options)
+ natives, [], [], jni_params,
+ 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 testMainDexAnnotation(self):
+ mainDexEntries = [
+ '@MainDex public class Test {',
+ '@MainDex public class Test{',
+ """@MainDex
+ public class Test {
+ """,
+ """@MainDex public class Test
+ {
+ """,
+ '@MainDex /* This class is a test */ public class Test {',
+ '@MainDex public class Test implements java.io.Serializable {',
+ '@MainDex public class Test implements java.io.Serializable, Bidule {',
+ '@MainDex public class Test extends BaseTest {',
+ """@MainDex
+ public class Test extends BaseTest implements Bidule {
+ """,
+ """@MainDex
+ public class Test extends BaseTest implements Bidule, Machin, Chose {
+ """,
+ """@MainDex
+ public class Test implements Testable<java.io.Serializable> {
+ """,
+ '@MainDex public class Test implements Testable<java.io.Serializable> {',
+ '@a.B @MainDex @C public class Test extends Testable<Serializable> {',
+ """public class Test extends Testable<java.io.Serializable> {
+ @MainDex void func() {}
+ """,
+ ]
+ for entry in mainDexEntries:
+ self.assertEquals(True, IsMainDexJavaClass(entry), entry)
+
+ def testNoMainDexAnnotation(self):
+ noMainDexEntries = [
+ 'public class Test {',
+ '@NotMainDex public class Test {',
+ '// @MainDex public class Test {',
+ '/* @MainDex */ public class Test {',
+ 'public class Test implements java.io.Serializable {',
+ '@MainDexNot public class Test {',
+ 'public class Test extends BaseTest {'
+ ]
+ for entry in noMainDexEntries:
+ self.assertEquals(False, IsMainDexJavaClass(entry))
def testNativeExportsOnlyOption(self):
test_data = """
@@ -1070,6 +1131,31 @@ class Foo {
TestOptions())
self.assertGoldenTextEquals(jni_from_java.GetContent())
+ def testTracing(self):
+ test_data = """
+ package org.chromium.foo;
+
+ @JNINamespace("org::chromium_foo")
+ class Foo {
+
+ @CalledByNative
+ Foo();
+
+ @CalledByNative
+ void callbackFromNative();
+
+ native void nativeInstanceMethod(long nativeInstance);
+
+ static native void nativeStaticMethod();
+ }
+ """
+ options_with_tracing = TestOptions()
+ options_with_tracing.enable_tracing = True
+ jni_from_java = jni_generator.JNIFromJavaSource(test_data,
+ 'org/chromium/foo/Foo',
+ options_with_tracing)
+ self.assertGoldenTextEquals(jni_from_java.GetContent())
+
def TouchStamp(stamp_path):
dir_name = os.path.dirname(stamp_path)
@@ -1083,9 +1169,14 @@ def TouchStamp(stamp_path):
def main(argv):
parser = optparse.OptionParser()
parser.add_option('--stamp', help='Path to touch on success.')
+ parser.add_option('--verbose', action="store_true",
+ help='Whether to output details.')
options, _ = parser.parse_args(argv[1:])
- test_result = unittest.main(argv=argv[0:1], exit=False)
+ test_result = unittest.main(
+ argv=argv[0:1],
+ exit=False,
+ verbosity=(2 if options.verbose else 1))
if test_result.result.wasSuccessful() and options.stamp:
TouchStamp(options.stamp)
diff --git a/base/android/jni_generator/jni_registration_generator.py b/base/android/jni_generator/jni_registration_generator.py
new file mode 100755
index 0000000000..8c545f6d1b
--- /dev/null
+++ b/base/android/jni_generator/jni_registration_generator.py
@@ -0,0 +1,340 @@
+#!/usr/bin/env python
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Generate JNI registration entry points
+
+Creates a header file with two static functions: RegisterMainDexNatives() and
+RegisterNonMainDexNatives(). Together, these will use manual JNI registration
+to register all native methods that exist within an application."""
+
+import argparse
+import jni_generator
+import multiprocessing
+import string
+import sys
+from util import build_utils
+
+
+# All but FULL_CLASS_NAME, which is used only for sorting.
+MERGEABLE_KEYS = [
+ 'CLASS_PATH_DECLARATIONS',
+ 'FORWARD_DECLARATIONS',
+ 'JNI_NATIVE_METHOD',
+ 'JNI_NATIVE_METHOD_ARRAY',
+ 'REGISTER_MAIN_DEX_NATIVES',
+ 'REGISTER_NON_MAIN_DEX_NATIVES',
+]
+
+
+def GenerateJNIHeader(java_file_paths, output_file, args):
+ """Generate a header file including two registration functions.
+
+ Forward declares all JNI registration functions created by jni_generator.py.
+ Calls the functions in RegisterMainDexNatives() if they are main dex. And
+ calls them in RegisterNonMainDexNatives() if they are non-main dex.
+
+ Args:
+ java_file_paths: A list of java file paths.
+ output_file: A relative path to output file.
+ args: All input arguments.
+ """
+ # Without multiprocessing, script takes ~13 seconds for chrome_public_apk
+ # on a z620. With multiprocessing, takes ~2 seconds.
+ pool = multiprocessing.Pool()
+ paths = (p for p in java_file_paths if p not in args.no_register_java)
+ results = [d for d in pool.imap_unordered(_DictForPath, paths) if d]
+ pool.close()
+
+ # Sort to make output deterministic.
+ results.sort(key=lambda d: d['FULL_CLASS_NAME'])
+
+ combined_dict = {}
+ for key in MERGEABLE_KEYS:
+ combined_dict[key] = ''.join(d.get(key, '') for d in results)
+
+ header_content = CreateFromDict(combined_dict)
+ if output_file:
+ jni_generator.WriteOutput(output_file, header_content)
+ else:
+ print header_content
+
+
+def _DictForPath(path):
+ with open(path) as f:
+ contents = jni_generator.RemoveComments(f.read())
+ natives = jni_generator.ExtractNatives(contents, 'long')
+ if len(natives) == 0:
+ return None
+ namespace = jni_generator.ExtractJNINamespace(contents)
+ fully_qualified_class = jni_generator.ExtractFullyQualifiedJavaClassName(
+ path, contents)
+ jni_params = jni_generator.JniParams(fully_qualified_class)
+ jni_params.ExtractImportsAndInnerClasses(contents)
+ main_dex = jni_generator.IsMainDexJavaClass(contents)
+ header_generator = HeaderGenerator(
+ namespace, fully_qualified_class, natives, jni_params, main_dex)
+ return header_generator.Generate()
+
+
+def CreateFromDict(registration_dict):
+ """Returns the content of the header file."""
+
+ template = string.Template("""\
+// 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.
+
+
+// This file is autogenerated by
+// base/android/jni_generator/jni_registration_generator.py
+// Please do not change its content.
+
+#ifndef HEADER_GUARD
+#define HEADER_GUARD
+
+#include <jni.h>
+
+#include "base/android/jni_generator/jni_generator_helper.h"
+#include "base/android/jni_int_wrapper.h"
+
+
+// Step 1: Forward declarations (classes).
+${CLASS_PATH_DECLARATIONS}
+
+// Step 2: Forward declarations (methods).
+
+${FORWARD_DECLARATIONS}
+
+// Step 3: Method declarations.
+
+${JNI_NATIVE_METHOD_ARRAY}
+${JNI_NATIVE_METHOD}
+// Step 4: Main dex and non-main dex registration functions.
+
+bool RegisterMainDexNatives(JNIEnv* env) {
+${REGISTER_MAIN_DEX_NATIVES}
+ return true;
+}
+
+bool RegisterNonMainDexNatives(JNIEnv* env) {
+${REGISTER_NON_MAIN_DEX_NATIVES}
+ return true;
+}
+
+#endif // HEADER_GUARD
+""")
+ if len(registration_dict['FORWARD_DECLARATIONS']) == 0:
+ return ''
+
+ return template.substitute(registration_dict)
+
+
+class HeaderGenerator(object):
+ """Generates an inline header file for JNI registration."""
+
+ def __init__(self, namespace, fully_qualified_class, natives, jni_params,
+ main_dex):
+ self.namespace = namespace
+ self.natives = natives
+ self.fully_qualified_class = fully_qualified_class
+ self.jni_params = jni_params
+ self.class_name = self.fully_qualified_class.split('/')[-1]
+ self.main_dex = main_dex
+ self.helper = jni_generator.HeaderFileGeneratorHelper(
+ self.class_name, fully_qualified_class)
+ self.registration_dict = None
+
+ def Generate(self):
+ self.registration_dict = {'FULL_CLASS_NAME': self.fully_qualified_class}
+ self._AddClassPathDeclarations()
+ self._AddForwardDeclaration()
+ self._AddJNINativeMethodsArrays()
+ self._AddRegisterNativesCalls()
+ self._AddRegisterNativesFunctions()
+ return self.registration_dict
+
+ def _SetDictValue(self, key, value):
+ self.registration_dict[key] = jni_generator.WrapOutput(value)
+
+ def _AddClassPathDeclarations(self):
+ classes = self.helper.GetUniqueClasses(self.natives)
+ self._SetDictValue('CLASS_PATH_DECLARATIONS',
+ self.helper.GetClassPathLines(classes, declare_only=True))
+
+ def _AddForwardDeclaration(self):
+ """Add the content of the forward declaration to the dictionary."""
+ template = string.Template("""\
+JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(
+ JNIEnv* env,
+ ${PARAMS_IN_STUB});
+""")
+ forward_declaration = ''
+ for native in self.natives:
+ value = {
+ 'RETURN': jni_generator.JavaDataTypeToC(native.return_type),
+ 'STUB_NAME': self.helper.GetStubName(native),
+ 'PARAMS_IN_STUB': jni_generator.GetParamsInStub(native),
+ }
+ forward_declaration += template.substitute(value)
+ self._SetDictValue('FORWARD_DECLARATIONS', forward_declaration)
+
+ def _AddRegisterNativesCalls(self):
+ """Add the body of the RegisterNativesImpl method to the dictionary."""
+ template = string.Template("""\
+ if (!${REGISTER_NAME}(env))
+ return false;
+""")
+ value = {
+ 'REGISTER_NAME':
+ jni_generator.GetRegistrationFunctionName(
+ self.fully_qualified_class)
+ }
+ register_body = template.substitute(value)
+ if self.main_dex:
+ self._SetDictValue('REGISTER_MAIN_DEX_NATIVES', register_body)
+ else:
+ self._SetDictValue('REGISTER_NON_MAIN_DEX_NATIVES', register_body)
+
+ def _AddJNINativeMethodsArrays(self):
+ """Returns the implementation of the array of native methods."""
+ template = string.Template("""\
+static const JNINativeMethod kMethods_${JAVA_CLASS}[] = {
+${KMETHODS}
+};
+
+""")
+ open_namespace = ''
+ close_namespace = ''
+ if self.namespace:
+ parts = self.namespace.split('::')
+ all_namespaces = ['namespace %s {' % ns for ns in parts]
+ open_namespace = '\n'.join(all_namespaces) + '\n'
+ all_namespaces = ['} // namespace %s' % ns for ns in parts]
+ all_namespaces.reverse()
+ close_namespace = '\n'.join(all_namespaces) + '\n\n'
+
+ body = self._SubstituteNativeMethods(template)
+ self._SetDictValue('JNI_NATIVE_METHOD_ARRAY',
+ ''.join((open_namespace, body, close_namespace)))
+
+ 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 _GetKMethodArrayEntry(self, native):
+ template = string.Template(' { "native${NAME}", ${JNI_SIGNATURE}, ' +
+ 'reinterpret_cast<void*>(${STUB_NAME}) },')
+ values = {
+ 'NAME': native.name,
+ 'JNI_SIGNATURE': self.jni_params.Signature(
+ native.params, native.return_type),
+ 'STUB_NAME': self.helper.GetStubName(native)
+ }
+ return template.substitute(values)
+
+ def _SubstituteNativeMethods(self, template):
+ """Substitutes NAMESPACE, JAVA_CLASS and KMETHODS in the provided
+ template."""
+ ret = []
+ all_classes = self.helper.GetUniqueClasses(self.natives)
+ all_classes[self.class_name] = self.fully_qualified_class
+ for clazz, full_clazz in all_classes.iteritems():
+ kmethods = self._GetKMethodsString(clazz)
+ namespace_str = ''
+ if self.namespace:
+ namespace_str = self.namespace + '::'
+ if kmethods:
+ values = {'NAMESPACE': namespace_str,
+ 'JAVA_CLASS': jni_generator.GetBinaryClassName(full_clazz),
+ 'KMETHODS': kmethods}
+ ret += [template.substitute(values)]
+ if not ret: return ''
+ return '\n'.join(ret)
+
+ def GetJNINativeMethodsString(self):
+ """Returns the implementation of the array of native methods."""
+ template = string.Template("""\
+static const JNINativeMethod kMethods_${JAVA_CLASS}[] = {
+${KMETHODS}
+
+};
+""")
+ return self._SubstituteNativeMethods(template)
+
+ def _AddRegisterNativesFunctions(self):
+ """Returns the code for RegisterNatives."""
+ natives = self._GetRegisterNativesImplString()
+ if not natives:
+ return ''
+ template = string.Template("""\
+JNI_REGISTRATION_EXPORT bool ${REGISTER_NAME}(JNIEnv* env) {
+${NATIVES}\
+ return true;
+}
+
+""")
+ values = {
+ 'REGISTER_NAME': jni_generator.GetRegistrationFunctionName(
+ self.fully_qualified_class),
+ 'NATIVES': natives
+ }
+ self._SetDictValue('JNI_NATIVE_METHOD', template.substitute(values))
+
+ def _GetRegisterNativesImplString(self):
+ """Returns the shared implementation for RegisterNatives."""
+ template = string.Template("""\
+ const int kMethods_${JAVA_CLASS}Size =
+ arraysize(${NAMESPACE}kMethods_${JAVA_CLASS});
+ if (env->RegisterNatives(
+ ${JAVA_CLASS}_clazz(env),
+ ${NAMESPACE}kMethods_${JAVA_CLASS},
+ kMethods_${JAVA_CLASS}Size) < 0) {
+ jni_generator::HandleRegistrationError(env,
+ ${JAVA_CLASS}_clazz(env),
+ __FILE__);
+ return false;
+ }
+
+""")
+ return self._SubstituteNativeMethods(template)
+
+
+def main(argv):
+ arg_parser = argparse.ArgumentParser()
+ build_utils.AddDepfileOption(arg_parser)
+
+ arg_parser.add_argument('--sources_files',
+ help='A list of .sources files which contain Java '
+ 'file paths. Must be used with --output.')
+ arg_parser.add_argument('--output',
+ help='The output file path.')
+ arg_parser.add_argument('--no_register_java',
+ help='A list of Java files which should be ignored '
+ 'by the parser.', default=[])
+ args = arg_parser.parse_args(build_utils.ExpandFileArgs(argv[1:]))
+ args.sources_files = build_utils.ParseGnList(args.sources_files)
+
+ if not args.sources_files:
+ print '\nError: Must specify --sources_files.'
+ return 1
+
+ java_file_paths = []
+ for f in args.sources_files:
+ # java_file_paths stores each Java file path as a string.
+ java_file_paths += build_utils.ReadSourcesList(f)
+ output_file = args.output
+ GenerateJNIHeader(java_file_paths, output_file, args)
+
+ if args.depfile:
+ build_utils.WriteDepfile(args.depfile, output_file,
+ args.sources_files + java_file_paths)
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/base/android/jni_generator/sample_entry_point.cc b/base/android/jni_generator/sample_entry_point.cc
new file mode 100644
index 0000000000..86f7e48081
--- /dev/null
+++ b/base/android/jni_generator/sample_entry_point.cc
@@ -0,0 +1,27 @@
+// 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/android/jni_android.h"
+#include "base/android/jni_generator/sample_jni_registration.h"
+#include "base/android/jni_utils.h"
+
+// This is called by the VM when the shared library is first loaded.
+JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ // By default, all JNI methods are registered. However, since render processes
+ // don't need very much Java code, we enable selective JNI registration on the
+ // Java side and only register a subset of JNI methods.
+ base::android::InitVM(vm);
+ JNIEnv* env = base::android::AttachCurrentThread();
+
+ if (!base::android::IsSelectiveJniRegistrationEnabled(env)) {
+ if (!RegisterNonMainDexNatives(env)) {
+ return -1;
+ }
+ }
+
+ if (!RegisterMainDexNatives(env)) {
+ return -1;
+ }
+ return JNI_VERSION_1_4;
+}
diff --git a/base/android/jni_generator/sample_for_tests.cc b/base/android/jni_generator/sample_for_tests.cc
index 42b2143e98..890103eea1 100644
--- a/base/android/jni_generator/sample_for_tests.cc
+++ b/base/android/jni_generator/sample_for_tests.cc
@@ -35,10 +35,6 @@ CPPClass::~CPPClass() {
}
// static
-bool CPPClass::RegisterJNI(JNIEnv* env) {
- return RegisterNativesImpl(env); // Generated in SampleForTests_jni.h
-}
-
void CPPClass::Destroy(JNIEnv* env, const JavaParamRef<jobject>& caller) {
delete this;
}
@@ -76,31 +72,36 @@ ScopedJavaLocalRef<jstring> CPPClass::ReturnAString(
}
// Static free functions declared and called directly from java.
-static jlong Init(JNIEnv* env,
- const JavaParamRef<jobject>& caller,
- const JavaParamRef<jstring>& param) {
+static jlong JNI_SampleForTests_Init(JNIEnv* env,
+ const JavaParamRef<jobject>& caller,
+ const JavaParamRef<jstring>& param) {
return 0;
}
-static jdouble GetDoubleFunction(JNIEnv*, const JavaParamRef<jobject>&) {
+static jdouble JNI_SampleForTests_GetDoubleFunction(
+ JNIEnv*,
+ const JavaParamRef<jobject>&) {
return 0;
}
-static jfloat GetFloatFunction(JNIEnv*, const JavaParamRef<jclass>&) {
+static jfloat JNI_SampleForTests_GetFloatFunction(JNIEnv*,
+ const JavaParamRef<jclass>&) {
return 0;
}
-static void SetNonPODDatatype(JNIEnv*,
- const JavaParamRef<jobject>&,
- const JavaParamRef<jobject>&) {}
+static void JNI_SampleForTests_SetNonPODDatatype(JNIEnv*,
+ const JavaParamRef<jobject>&,
+ const JavaParamRef<jobject>&) {
+}
-static ScopedJavaLocalRef<jobject> GetNonPODDatatype(
+static ScopedJavaLocalRef<jobject> JNI_SampleForTests_GetNonPODDatatype(
JNIEnv*,
const JavaParamRef<jobject>&) {
return ScopedJavaLocalRef<jobject>();
}
-static jint GetInnerIntFunction(JNIEnv*, const JavaParamRef<jclass>&) {
+static jint JNI_InnerClass_GetInnerIntFunction(JNIEnv*,
+ const JavaParamRef<jclass>&) {
return 0;
}
@@ -121,6 +122,13 @@ int main() {
int bar = base::android::Java_SampleForTests_javaMethod(
env, my_java_object, 1, 2);
+ base::android::Java_SampleForTests_methodWithGenericParams(
+ env, my_java_object, nullptr, nullptr);
+
+ // This is how you call a java constructor method from C++.
+ ScopedJavaLocalRef<jobject> my_created_object =
+ base::android::Java_SampleForTests_Constructor(env, 1, 2);
+
std::cout << foo << bar;
for (int i = 0; i < 10; ++i) {
@@ -138,5 +146,9 @@ int main() {
my_java_object);
base::android::Java_SampleForTests_javaMethodWithAnnotatedParam(
env, my_java_object, 42);
+
+ base::android::Java_SampleForTests_getInnerInterface(env);
+ base::android::Java_SampleForTests_getInnerEnum(env);
+
return 0;
}
diff --git a/base/android/jni_generator/sample_for_tests.h b/base/android/jni_generator/sample_for_tests.h
index a9cf7b05e9..6414158a3a 100644
--- a/base/android/jni_generator/sample_for_tests.h
+++ b/base/android/jni_generator/sample_for_tests.h
@@ -19,8 +19,9 @@ namespace android {
// - 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
+// Methods are called directly from Java. More documentation in
+// SampleForTests.java. See BUILD.gn for the build rules necessary for JNI
+// to be used in an APK.
//
// For C++ to access Java methods:
// - GN Build must be configured to generate bindings:
@@ -56,39 +57,26 @@ namespace android {
// ]
// }
// }
+// The build rules above are generally that that's needed when adding new
+// JNI methods/files. For a full GN example, see
+// base/android/jni_generator/BUILD.gn
//
// 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.
+// - The Java class must be part of an android_apk target that depends on
+// a generate_jni_registration target. This generate_jni_registration target
+// automatically generates all necessary registration functions. The
+// generated header file exposes two functions that should be called when a
+// library is first loaded:
+// 1) RegisterMainDexNatives()
+// - Registers all methods that are used outside the browser process
+// 2) RegisterNonMainDexNatives()
+// - Registers all methods used in the browser process
//
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.
diff --git a/base/android/jni_generator/testCalledByNatives.golden b/base/android/jni_generator/testCalledByNatives.golden
index ac86b2e801..f0673f65fc 100644
--- a/base/android/jni_generator/testCalledByNatives.golden
+++ b/base/android/jni_generator/testCalledByNatives.golden
@@ -2,6 +2,7 @@
// 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
@@ -14,246 +15,209 @@
#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";
+// Step 1: Forward declarations.
+
+JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni[];
+const char kClassPath_org_chromium_TestJni[] = "org/chromium/TestJni";
+
+JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni_00024InfoBar[];
+const char kClassPath_org_chromium_TestJni_00024InfoBar[] = "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)
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_clazz = 0;
+#ifndef org_chromium_TestJni_clazz_defined
+#define org_chromium_TestJni_clazz_defined
+inline jclass org_chromium_TestJni_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni,
+ &g_org_chromium_TestJni_clazz);
+}
+#endif
// 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)
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_00024InfoBar_clazz = 0;
+#ifndef org_chromium_TestJni_00024InfoBar_clazz_defined
+#define org_chromium_TestJni_00024InfoBar_clazz_defined
+inline jclass org_chromium_TestJni_00024InfoBar_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni_00024InfoBar,
+ &g_org_chromium_TestJni_00024InfoBar_clazz);
+}
+#endif
+
-} // namespace
+// Step 2: Constants (optional).
-// Step 2: method stubs.
-static base::subtle::AtomicWord g_TestJni_showConfirmInfoBar = 0;
-static base::android::ScopedJavaLocalRef<jobject>
- Java_TestJni_showConfirmInfoBar(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper nativeInfoBar,
- const base::android::JavaRefOrBare<jstring>& buttonOk,
- const base::android::JavaRefOrBare<jstring>& buttonCancel,
- const base::android::JavaRefOrBare<jstring>& title,
- const base::android::JavaRefOrBare<jobject>& icon) {
+// Step 3: Method stubs.
+
+static base::subtle::AtomicWord g_org_chromium_TestJni_showConfirmInfoBar = 0;
+static base::android::ScopedJavaLocalRef<jobject> Java_TestJni_showConfirmInfoBar(JNIEnv* env, const
+ base::android::JavaRef<jobject>& obj, JniIntWrapper nativeInfoBar,
+ const base::android::JavaRef<jstring>& buttonOk,
+ const base::android::JavaRef<jstring>& buttonCancel,
+ const base::android::JavaRef<jstring>& title,
+ const base::android::JavaRef<jobject>& icon) {
CHECK_CLAZZ(env, obj.obj(),
- TestJni_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ org_chromium_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);
+ env, org_chromium_TestJni_clazz(env),
+ "showConfirmInfoBar",
+"(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/graphics/Bitmap;)Lorg/chromium/Foo$InnerClass;",
+ &g_org_chromium_TestJni_showConfirmInfoBar);
jobject ret =
env->CallObjectMethod(obj.obj(),
- method_id, as_jint(nativeInfoBar), buttonOk.obj(), buttonCancel.obj(),
- title.obj(), icon.obj());
+ method_id, as_jint(nativeInfoBar), buttonOk.obj(), buttonCancel.obj(), title.obj(),
+ icon.obj());
jni_generator::CheckException(env);
return base::android::ScopedJavaLocalRef<jobject>(env, ret);
}
-static base::subtle::AtomicWord g_TestJni_showAutoLoginInfoBar = 0;
-static base::android::ScopedJavaLocalRef<jobject>
- Java_TestJni_showAutoLoginInfoBar(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper nativeInfoBar,
- const base::android::JavaRefOrBare<jstring>& realm,
- const base::android::JavaRefOrBare<jstring>& account,
- const base::android::JavaRefOrBare<jstring>& args) {
+static base::subtle::AtomicWord g_org_chromium_TestJni_showAutoLoginInfoBar = 0;
+static base::android::ScopedJavaLocalRef<jobject> Java_TestJni_showAutoLoginInfoBar(JNIEnv* env,
+ const base::android::JavaRef<jobject>& obj, JniIntWrapper nativeInfoBar,
+ const base::android::JavaRef<jstring>& realm,
+ const base::android::JavaRef<jstring>& account,
+ const base::android::JavaRef<jstring>& args) {
CHECK_CLAZZ(env, obj.obj(),
- TestJni_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ org_chromium_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);
+ env, org_chromium_TestJni_clazz(env),
+ "showAutoLoginInfoBar",
+ "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lorg/chromium/Foo$InnerClass;",
+ &g_org_chromium_TestJni_showAutoLoginInfoBar);
jobject ret =
env->CallObjectMethod(obj.obj(),
- method_id, as_jint(nativeInfoBar), realm.obj(), account.obj(),
- args.obj());
+ method_id, as_jint(nativeInfoBar), realm.obj(), account.obj(), args.obj());
jni_generator::CheckException(env);
return base::android::ScopedJavaLocalRef<jobject>(env, ret);
}
-static base::subtle::AtomicWord g_InfoBar_dismiss = 0;
-static void Java_InfoBar_dismiss(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_org_chromium_TestJni_00024InfoBar_dismiss = 0;
+static void Java_InfoBar_dismiss(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- InfoBar_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ org_chromium_TestJni_00024InfoBar_clazz(env));
+ jmethodID method_id = base::android::MethodID::LazyGet<
base::android::MethodID::TYPE_INSTANCE>(
- env, InfoBar_clazz(env),
- "dismiss",
-"("
-")"
-"V",
- &g_InfoBar_dismiss);
+ env, org_chromium_TestJni_00024InfoBar_clazz(env),
+ "dismiss",
+ "()V",
+ &g_org_chromium_TestJni_00024InfoBar_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<jobject>& view,
- const base::android::JavaRefOrBare<jstring>& realm,
- const base::android::JavaRefOrBare<jstring>& account,
- const base::android::JavaRefOrBare<jstring>& args) {
- CHECK_CLAZZ(env, TestJni_clazz(env),
- TestJni_clazz(env), false);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+static base::subtle::AtomicWord g_org_chromium_TestJni_shouldShowAutoLogin = 0;
+static jboolean Java_TestJni_shouldShowAutoLogin(JNIEnv* env, const base::android::JavaRef<jobject>&
+ view,
+ const base::android::JavaRef<jstring>& realm,
+ const base::android::JavaRef<jstring>& account,
+ const base::android::JavaRef<jstring>& args) {
+ CHECK_CLAZZ(env, org_chromium_TestJni_clazz(env),
+ org_chromium_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);
+ env, org_chromium_TestJni_clazz(env),
+ "shouldShowAutoLogin",
+ "(Landroid/view/View;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
+ &g_org_chromium_TestJni_shouldShowAutoLogin);
jboolean ret =
- env->CallStaticBooleanMethod(TestJni_clazz(env),
+ env->CallStaticBooleanMethod(org_chromium_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<jobject> Java_TestJni_openUrl(JNIEnv*
- env, const base::android::JavaRefOrBare<jstring>& url) {
- CHECK_CLAZZ(env, TestJni_clazz(env),
- TestJni_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+static base::subtle::AtomicWord g_org_chromium_TestJni_openUrl = 0;
+static base::android::ScopedJavaLocalRef<jobject> Java_TestJni_openUrl(JNIEnv* env, const
+ base::android::JavaRef<jstring>& url) {
+ CHECK_CLAZZ(env, org_chromium_TestJni_clazz(env),
+ org_chromium_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);
+ env, org_chromium_TestJni_clazz(env),
+ "openUrl",
+ "(Ljava/lang/String;)Ljava/io/InputStream;",
+ &g_org_chromium_TestJni_openUrl);
jobject ret =
- env->CallStaticObjectMethod(TestJni_clazz(env),
+ env->CallStaticObjectMethod(org_chromium_TestJni_clazz(env),
method_id, url.obj());
jni_generator::CheckException(env);
return base::android::ScopedJavaLocalRef<jobject>(env, ret);
}
-static base::subtle::AtomicWord g_TestJni_activateHardwareAcceleration = 0;
+static base::subtle::AtomicWord g_org_chromium_TestJni_activateHardwareAcceleration = 0;
static void Java_TestJni_activateHardwareAcceleration(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, jboolean activated,
+ base::android::JavaRef<jobject>& 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<
+ org_chromium_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, org_chromium_TestJni_clazz(env),
+ "activateHardwareAcceleration",
+ "(ZIIII)V",
+ &g_org_chromium_TestJni_activateHardwareAcceleration);
env->CallVoidMethod(obj.obj(),
- method_id, activated, as_jint(iPid), as_jint(iType),
- as_jint(iPrimaryID), as_jint(iSecondaryID));
+ 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 base::subtle::AtomicWord g_org_chromium_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<
+ CHECK_CLAZZ(env, org_chromium_TestJni_clazz(env),
+ org_chromium_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);
+ env, org_chromium_TestJni_clazz(env),
+ "updateStatus",
+ "(I)I",
+ &g_org_chromium_TestJni_updateStatus);
jint ret =
- env->CallStaticIntMethod(TestJni_clazz(env),
+ env->CallStaticIntMethod(org_chromium_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<jobject>& obj, JniIntWrapper iParam) {
+static base::subtle::AtomicWord g_org_chromium_TestJni_uncheckedCall = 0;
+static void Java_TestJni_uncheckedCall(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper iParam) {
CHECK_CLAZZ(env, obj.obj(),
- TestJni_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ org_chromium_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, org_chromium_TestJni_clazz(env),
+ "uncheckedCall",
+ "(I)V",
+ &g_org_chromium_TestJni_uncheckedCall);
env->CallVoidMethod(obj.obj(),
method_id, as_jint(iParam));
}
-static base::subtle::AtomicWord g_TestJni_returnByteArray = 0;
-static base::android::ScopedJavaLocalRef<jbyteArray>
- Java_TestJni_returnByteArray(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_org_chromium_TestJni_returnByteArray = 0;
+static base::android::ScopedJavaLocalRef<jbyteArray> Java_TestJni_returnByteArray(JNIEnv* env, const
+ base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- TestJni_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ org_chromium_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);
+ env, org_chromium_TestJni_clazz(env),
+ "returnByteArray",
+ "()[B",
+ &g_org_chromium_TestJni_returnByteArray);
jbyteArray ret =
static_cast<jbyteArray>(env->CallObjectMethod(obj.obj(),
@@ -262,21 +226,17 @@ static base::android::ScopedJavaLocalRef<jbyteArray>
return base::android::ScopedJavaLocalRef<jbyteArray>(env, ret);
}
-static base::subtle::AtomicWord g_TestJni_returnBooleanArray = 0;
-static base::android::ScopedJavaLocalRef<jbooleanArray>
- Java_TestJni_returnBooleanArray(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_org_chromium_TestJni_returnBooleanArray = 0;
+static base::android::ScopedJavaLocalRef<jbooleanArray> Java_TestJni_returnBooleanArray(JNIEnv* env,
+ const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- TestJni_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ org_chromium_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);
+ env, org_chromium_TestJni_clazz(env),
+ "returnBooleanArray",
+ "()[Z",
+ &g_org_chromium_TestJni_returnBooleanArray);
jbooleanArray ret =
static_cast<jbooleanArray>(env->CallObjectMethod(obj.obj(),
@@ -285,21 +245,17 @@ static base::android::ScopedJavaLocalRef<jbooleanArray>
return base::android::ScopedJavaLocalRef<jbooleanArray>(env, ret);
}
-static base::subtle::AtomicWord g_TestJni_returnCharArray = 0;
-static base::android::ScopedJavaLocalRef<jcharArray>
- Java_TestJni_returnCharArray(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_org_chromium_TestJni_returnCharArray = 0;
+static base::android::ScopedJavaLocalRef<jcharArray> Java_TestJni_returnCharArray(JNIEnv* env, const
+ base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- TestJni_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ org_chromium_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);
+ env, org_chromium_TestJni_clazz(env),
+ "returnCharArray",
+ "()[C",
+ &g_org_chromium_TestJni_returnCharArray);
jcharArray ret =
static_cast<jcharArray>(env->CallObjectMethod(obj.obj(),
@@ -308,21 +264,17 @@ static base::android::ScopedJavaLocalRef<jcharArray>
return base::android::ScopedJavaLocalRef<jcharArray>(env, ret);
}
-static base::subtle::AtomicWord g_TestJni_returnShortArray = 0;
-static base::android::ScopedJavaLocalRef<jshortArray>
- Java_TestJni_returnShortArray(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_org_chromium_TestJni_returnShortArray = 0;
+static base::android::ScopedJavaLocalRef<jshortArray> Java_TestJni_returnShortArray(JNIEnv* env,
+ const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- TestJni_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ org_chromium_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);
+ env, org_chromium_TestJni_clazz(env),
+ "returnShortArray",
+ "()[S",
+ &g_org_chromium_TestJni_returnShortArray);
jshortArray ret =
static_cast<jshortArray>(env->CallObjectMethod(obj.obj(),
@@ -331,21 +283,17 @@ static base::android::ScopedJavaLocalRef<jshortArray>
return base::android::ScopedJavaLocalRef<jshortArray>(env, ret);
}
-static base::subtle::AtomicWord g_TestJni_returnIntArray = 0;
-static base::android::ScopedJavaLocalRef<jintArray>
- Java_TestJni_returnIntArray(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_org_chromium_TestJni_returnIntArray = 0;
+static base::android::ScopedJavaLocalRef<jintArray> Java_TestJni_returnIntArray(JNIEnv* env, const
+ base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- TestJni_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ org_chromium_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);
+ env, org_chromium_TestJni_clazz(env),
+ "returnIntArray",
+ "()[I",
+ &g_org_chromium_TestJni_returnIntArray);
jintArray ret =
static_cast<jintArray>(env->CallObjectMethod(obj.obj(),
@@ -354,21 +302,17 @@ static base::android::ScopedJavaLocalRef<jintArray>
return base::android::ScopedJavaLocalRef<jintArray>(env, ret);
}
-static base::subtle::AtomicWord g_TestJni_returnLongArray = 0;
-static base::android::ScopedJavaLocalRef<jlongArray>
- Java_TestJni_returnLongArray(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_org_chromium_TestJni_returnLongArray = 0;
+static base::android::ScopedJavaLocalRef<jlongArray> Java_TestJni_returnLongArray(JNIEnv* env, const
+ base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- TestJni_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ org_chromium_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);
+ env, org_chromium_TestJni_clazz(env),
+ "returnLongArray",
+ "()[J",
+ &g_org_chromium_TestJni_returnLongArray);
jlongArray ret =
static_cast<jlongArray>(env->CallObjectMethod(obj.obj(),
@@ -377,21 +321,17 @@ static base::android::ScopedJavaLocalRef<jlongArray>
return base::android::ScopedJavaLocalRef<jlongArray>(env, ret);
}
-static base::subtle::AtomicWord g_TestJni_returnDoubleArray = 0;
-static base::android::ScopedJavaLocalRef<jdoubleArray>
- Java_TestJni_returnDoubleArray(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_org_chromium_TestJni_returnDoubleArray = 0;
+static base::android::ScopedJavaLocalRef<jdoubleArray> Java_TestJni_returnDoubleArray(JNIEnv* env,
+ const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- TestJni_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ org_chromium_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);
+ env, org_chromium_TestJni_clazz(env),
+ "returnDoubleArray",
+ "()[D",
+ &g_org_chromium_TestJni_returnDoubleArray);
jdoubleArray ret =
static_cast<jdoubleArray>(env->CallObjectMethod(obj.obj(),
@@ -400,21 +340,17 @@ static base::android::ScopedJavaLocalRef<jdoubleArray>
return base::android::ScopedJavaLocalRef<jdoubleArray>(env, ret);
}
-static base::subtle::AtomicWord g_TestJni_returnObjectArray = 0;
-static base::android::ScopedJavaLocalRef<jobjectArray>
- Java_TestJni_returnObjectArray(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_org_chromium_TestJni_returnObjectArray = 0;
+static base::android::ScopedJavaLocalRef<jobjectArray> Java_TestJni_returnObjectArray(JNIEnv* env,
+ const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- TestJni_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ org_chromium_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);
+ env, org_chromium_TestJni_clazz(env),
+ "returnObjectArray",
+ "()[Ljava/lang/Object;",
+ &g_org_chromium_TestJni_returnObjectArray);
jobjectArray ret =
static_cast<jobjectArray>(env->CallObjectMethod(obj.obj(),
@@ -423,21 +359,17 @@ static base::android::ScopedJavaLocalRef<jobjectArray>
return base::android::ScopedJavaLocalRef<jobjectArray>(env, ret);
}
-static base::subtle::AtomicWord g_TestJni_returnArrayOfByteArray = 0;
-static base::android::ScopedJavaLocalRef<jobjectArray>
- Java_TestJni_returnArrayOfByteArray(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_org_chromium_TestJni_returnArrayOfByteArray = 0;
+static base::android::ScopedJavaLocalRef<jobjectArray> Java_TestJni_returnArrayOfByteArray(JNIEnv*
+ env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- TestJni_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ org_chromium_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);
+ env, org_chromium_TestJni_clazz(env),
+ "returnArrayOfByteArray",
+ "()[[B",
+ &g_org_chromium_TestJni_returnArrayOfByteArray);
jobjectArray ret =
static_cast<jobjectArray>(env->CallObjectMethod(obj.obj(),
@@ -446,21 +378,17 @@ static base::android::ScopedJavaLocalRef<jobjectArray>
return base::android::ScopedJavaLocalRef<jobjectArray>(env, ret);
}
-static base::subtle::AtomicWord g_TestJni_getCompressFormat = 0;
-static base::android::ScopedJavaLocalRef<jobject>
- Java_TestJni_getCompressFormat(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_org_chromium_TestJni_getCompressFormat = 0;
+static base::android::ScopedJavaLocalRef<jobject> Java_TestJni_getCompressFormat(JNIEnv* env, const
+ base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- TestJni_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ org_chromium_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);
+ env, org_chromium_TestJni_clazz(env),
+ "getCompressFormat",
+ "()Landroid/graphics/Bitmap$CompressFormat;",
+ &g_org_chromium_TestJni_getCompressFormat);
jobject ret =
env->CallObjectMethod(obj.obj(),
@@ -469,21 +397,17 @@ static base::android::ScopedJavaLocalRef<jobject>
return base::android::ScopedJavaLocalRef<jobject>(env, ret);
}
-static base::subtle::AtomicWord g_TestJni_getCompressFormatList = 0;
-static base::android::ScopedJavaLocalRef<jobject>
- Java_TestJni_getCompressFormatList(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_org_chromium_TestJni_getCompressFormatList = 0;
+static base::android::ScopedJavaLocalRef<jobject> Java_TestJni_getCompressFormatList(JNIEnv* env,
+ const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- TestJni_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ org_chromium_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);
+ env, org_chromium_TestJni_clazz(env),
+ "getCompressFormatList",
+ "()Ljava/util/List;",
+ &g_org_chromium_TestJni_getCompressFormatList);
jobject ret =
env->CallObjectMethod(obj.obj(),
@@ -492,6 +416,4 @@ static base::android::ScopedJavaLocalRef<jobject>
return base::android::ScopedJavaLocalRef<jobject>(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
index b16956f7f5..9cb39b7441 100644
--- a/base/android/jni_generator/testConstantsFromJavaP.golden
+++ b/base/android/jni_generator/testConstantsFromJavaP.golden
@@ -2,6 +2,7 @@
// 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
@@ -14,16 +15,23 @@
#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";
+// Step 1: Forward declarations.
+
+JNI_REGISTRATION_EXPORT extern const char kClassPath_android_view_MotionEvent[];
+const char kClassPath_android_view_MotionEvent[] = "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)
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_android_view_MotionEvent_clazz = 0;
+#ifndef android_view_MotionEvent_clazz_defined
+#define android_view_MotionEvent_clazz_defined
+inline jclass android_view_MotionEvent_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_android_view_MotionEvent,
+ &g_android_view_MotionEvent_clazz);
+}
+#endif
-} // namespace
+
+// Step 2: Constants (optional).
namespace JNI_MotionEvent {
@@ -110,22 +118,24 @@ enum Java_MotionEvent_constant_fields {
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<jobject>& obj) __attribute__ ((unused));
-static void Java_MotionEvent_finalize(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+} // namespace JNI_MotionEvent
+// Step 3: Method stubs.
+namespace JNI_MotionEvent {
+
+
+static base::subtle::AtomicWord g_android_view_MotionEvent_finalize = 0;
+static void Java_MotionEvent_finalize(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static void Java_MotionEvent_finalize(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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, android_view_MotionEvent_clazz(env),
+ "finalize",
+ "()V",
+ &g_android_view_MotionEvent_finalize);
env->CallVoidMethod(obj.obj(),
method_id);
@@ -133,15 +143,14 @@ static void Java_MotionEvent_finalize(JNIEnv* env, const
}
static base::subtle::AtomicWord
- g_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I = 0;
+ g_android_view_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I = 0;
static base::android::ScopedJavaLocalRef<jobject>
- Java_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I(JNIEnv*
- env, jlong p0,
+ 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<jobjectArray>& p4,
- const base::android::JavaRefOrBare<jobjectArray>& p5,
+ const base::android::JavaRef<jobjectArray>& p4,
+ const base::android::JavaRef<jobjectArray>& p5,
JniIntWrapper p6,
JniIntWrapper p7,
jfloat p8,
@@ -151,13 +160,12 @@ static base::android::ScopedJavaLocalRef<jobject>
JniIntWrapper p12,
JniIntWrapper p13) __attribute__ ((unused));
static base::android::ScopedJavaLocalRef<jobject>
- Java_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I(JNIEnv*
- env, jlong p0,
+ 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<jobjectArray>& p4,
- const base::android::JavaRefOrBare<jobjectArray>& p5,
+ const base::android::JavaRef<jobjectArray>& p4,
+ const base::android::JavaRef<jobjectArray>& p5,
JniIntWrapper p6,
JniIntWrapper p7,
jfloat p8,
@@ -166,35 +174,32 @@ static base::android::ScopedJavaLocalRef<jobject>
JniIntWrapper p11,
JniIntWrapper p12,
JniIntWrapper p13) {
- CHECK_CLAZZ(env, MotionEvent_clazz(env),
- MotionEvent_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env),
+ android_view_MotionEvent_clazz(env), NULL);
+ jmethodID method_id = base::android::MethodID::LazyGet<
base::android::MethodID::TYPE_STATIC>(
- env, MotionEvent_clazz(env),
- "obtain",
+ env, android_view_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);
+ &g_android_view_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));
+ env->CallStaticObjectMethod(android_view_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<jobject>(env, ret);
}
static base::subtle::AtomicWord
- g_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I = 0;
+ g_android_view_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I = 0;
static base::android::ScopedJavaLocalRef<jobject>
- Java_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I(JNIEnv* env,
- jlong p0,
+ 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<jintArray>& p4,
- const base::android::JavaRefOrBare<jobjectArray>& p5,
+ const base::android::JavaRef<jintArray>& p4,
+ const base::android::JavaRef<jobjectArray>& p5,
JniIntWrapper p6,
jfloat p7,
jfloat p8,
@@ -203,13 +208,12 @@ static base::android::ScopedJavaLocalRef<jobject>
JniIntWrapper p11,
JniIntWrapper p12) __attribute__ ((unused));
static base::android::ScopedJavaLocalRef<jobject>
- Java_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I(JNIEnv* env,
- jlong p0,
+ 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<jintArray>& p4,
- const base::android::JavaRefOrBare<jobjectArray>& p5,
+ const base::android::JavaRef<jintArray>& p4,
+ const base::android::JavaRef<jobjectArray>& p5,
JniIntWrapper p6,
jfloat p7,
jfloat p8,
@@ -217,27 +221,24 @@ static base::android::ScopedJavaLocalRef<jobject>
JniIntWrapper p10,
JniIntWrapper p11,
JniIntWrapper p12) {
- CHECK_CLAZZ(env, MotionEvent_clazz(env),
- MotionEvent_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env),
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "obtain",
+ "(JJII[I[Landroid/view/MotionEvent$PointerCoords;IFFIIII)Landroid/view/MotionEvent;",
+ &g_android_view_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));
+ env->CallStaticObjectMethod(android_view_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<jobject>(env, ret);
}
-static base::subtle::AtomicWord g_MotionEvent_obtainAVME_J_J_I_F_F_F_F_I_F_F_I_I
- = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_obtainAVME_J_J_I_F_F_F_F_I_F_F_I_I = 0;
static base::android::ScopedJavaLocalRef<jobject>
Java_MotionEvent_obtainAVME_J_J_I_F_F_F_F_I_F_F_I_I(JNIEnv* env, jlong p0,
jlong p1,
@@ -264,26 +265,24 @@ static base::android::ScopedJavaLocalRef<jobject>
jfloat p9,
JniIntWrapper p10,
JniIntWrapper p11) {
- CHECK_CLAZZ(env, MotionEvent_clazz(env),
- MotionEvent_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env),
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "obtain",
+ "(JJIFFFFIFFII)Landroid/view/MotionEvent;",
+ &g_android_view_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));
+ env->CallStaticObjectMethod(android_view_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<jobject>(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::subtle::AtomicWord g_android_view_MotionEvent_obtainAVME_J_J_I_I_F_F_F_F_I_F_F_I_I = 0;
static base::android::ScopedJavaLocalRef<jobject>
Java_MotionEvent_obtainAVME_J_J_I_I_F_F_F_F_I_F_F_I_I(JNIEnv* env, jlong p0,
jlong p1,
@@ -312,138 +311,126 @@ static base::android::ScopedJavaLocalRef<jobject>
jfloat p10,
JniIntWrapper p11,
JniIntWrapper p12) {
- CHECK_CLAZZ(env, MotionEvent_clazz(env),
- MotionEvent_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env),
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "obtain",
+ "(JJIIFFFFIFFII)Landroid/view/MotionEvent;",
+ &g_android_view_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));
+ env->CallStaticObjectMethod(android_view_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<jobject>(env, ret);
}
-static base::subtle::AtomicWord g_MotionEvent_obtainAVME_J_J_I_F_F_I = 0;
-static base::android::ScopedJavaLocalRef<jobject>
- Java_MotionEvent_obtainAVME_J_J_I_F_F_I(JNIEnv* env, jlong p0,
+static base::subtle::AtomicWord g_android_view_MotionEvent_obtainAVME_J_J_I_F_F_I = 0;
+static base::android::ScopedJavaLocalRef<jobject> 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<jobject>
- Java_MotionEvent_obtainAVME_J_J_I_F_F_I(JNIEnv* env, jlong p0,
+static base::android::ScopedJavaLocalRef<jobject> 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<
+ CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env),
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "obtain",
+ "(JJIFFI)Landroid/view/MotionEvent;",
+ &g_android_view_MotionEvent_obtainAVME_J_J_I_F_F_I);
jobject ret =
- env->CallStaticObjectMethod(MotionEvent_clazz(env),
+ env->CallStaticObjectMethod(android_view_MotionEvent_clazz(env),
method_id, p0, p1, as_jint(p2), p3, p4, as_jint(p5));
jni_generator::CheckException(env);
return base::android::ScopedJavaLocalRef<jobject>(env, ret);
}
-static base::subtle::AtomicWord g_MotionEvent_obtainAVME_AVME = 0;
-static base::android::ScopedJavaLocalRef<jobject>
- Java_MotionEvent_obtainAVME_AVME(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& p0) __attribute__ ((unused));
-static base::android::ScopedJavaLocalRef<jobject>
- Java_MotionEvent_obtainAVME_AVME(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& p0) {
- CHECK_CLAZZ(env, MotionEvent_clazz(env),
- MotionEvent_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+static base::subtle::AtomicWord g_android_view_MotionEvent_obtainAVME_AVME = 0;
+static base::android::ScopedJavaLocalRef<jobject> Java_MotionEvent_obtainAVME_AVME(JNIEnv* env,
+ const base::android::JavaRef<jobject>& p0) __attribute__ ((unused));
+static base::android::ScopedJavaLocalRef<jobject> Java_MotionEvent_obtainAVME_AVME(JNIEnv* env,
+ const base::android::JavaRef<jobject>& p0) {
+ CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env),
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "obtain",
+ "(Landroid/view/MotionEvent;)Landroid/view/MotionEvent;",
+ &g_android_view_MotionEvent_obtainAVME_AVME);
jobject ret =
- env->CallStaticObjectMethod(MotionEvent_clazz(env),
+ env->CallStaticObjectMethod(android_view_MotionEvent_clazz(env),
method_id, p0.obj());
jni_generator::CheckException(env);
return base::android::ScopedJavaLocalRef<jobject>(env, ret);
}
-static base::subtle::AtomicWord g_MotionEvent_obtainNoHistory = 0;
-static base::android::ScopedJavaLocalRef<jobject>
- Java_MotionEvent_obtainNoHistory(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& p0) __attribute__ ((unused));
-static base::android::ScopedJavaLocalRef<jobject>
- Java_MotionEvent_obtainNoHistory(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& p0) {
- CHECK_CLAZZ(env, MotionEvent_clazz(env),
- MotionEvent_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+static base::subtle::AtomicWord g_android_view_MotionEvent_obtainNoHistory = 0;
+static base::android::ScopedJavaLocalRef<jobject> Java_MotionEvent_obtainNoHistory(JNIEnv* env,
+ const base::android::JavaRef<jobject>& p0) __attribute__ ((unused));
+static base::android::ScopedJavaLocalRef<jobject> Java_MotionEvent_obtainNoHistory(JNIEnv* env,
+ const base::android::JavaRef<jobject>& p0) {
+ CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env),
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "obtainNoHistory",
+ "(Landroid/view/MotionEvent;)Landroid/view/MotionEvent;",
+ &g_android_view_MotionEvent_obtainNoHistory);
jobject ret =
- env->CallStaticObjectMethod(MotionEvent_clazz(env),
+ env->CallStaticObjectMethod(android_view_MotionEvent_clazz(env),
method_id, p0.obj());
jni_generator::CheckException(env);
return base::android::ScopedJavaLocalRef<jobject>(env, ret);
}
-static base::subtle::AtomicWord g_MotionEvent_recycle = 0;
-static void Java_MotionEvent_recycle(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static void Java_MotionEvent_recycle(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_recycle = 0;
+static void Java_MotionEvent_recycle(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static void Java_MotionEvent_recycle(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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, android_view_MotionEvent_clazz(env),
+ "recycle",
+ "()V",
+ &g_android_view_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<jobject>& obj) __attribute__ ((unused));
-static jint Java_MotionEvent_getDeviceId(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getDeviceId = 0;
+static jint Java_MotionEvent_getDeviceId(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jint Java_MotionEvent_getDeviceId(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getDeviceId",
+ "()I",
+ &g_android_view_MotionEvent_getDeviceId);
jint ret =
env->CallIntMethod(obj.obj(),
@@ -452,20 +439,18 @@ static jint Java_MotionEvent_getDeviceId(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getSource = 0;
-static jint Java_MotionEvent_getSource(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jint Java_MotionEvent_getSource(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getSource = 0;
+static jint Java_MotionEvent_getSource(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jint Java_MotionEvent_getSource(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getSource",
+ "()I",
+ &g_android_view_MotionEvent_getSource);
jint ret =
env->CallIntMethod(obj.obj(),
@@ -474,41 +459,37 @@ static jint Java_MotionEvent_getSource(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_setSource = 0;
-static void Java_MotionEvent_setSource(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static void Java_MotionEvent_setSource(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_setSource = 0;
+static void Java_MotionEvent_setSource(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper p0) __attribute__ ((unused));
+static void Java_MotionEvent_setSource(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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, android_view_MotionEvent_clazz(env),
+ "setSource",
+ "(I)V",
+ &g_android_view_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<jobject>& obj) __attribute__ ((unused));
-static jint Java_MotionEvent_getAction(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getAction = 0;
+static jint Java_MotionEvent_getAction(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jint Java_MotionEvent_getAction(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getAction",
+ "()I",
+ &g_android_view_MotionEvent_getAction);
jint ret =
env->CallIntMethod(obj.obj(),
@@ -517,20 +498,19 @@ static jint Java_MotionEvent_getAction(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getActionMasked = 0;
-static jint Java_MotionEvent_getActionMasked(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jint Java_MotionEvent_getActionMasked(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getActionMasked = 0;
+static jint Java_MotionEvent_getActionMasked(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) __attribute__ ((unused));
+static jint Java_MotionEvent_getActionMasked(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getActionMasked",
+ "()I",
+ &g_android_view_MotionEvent_getActionMasked);
jint ret =
env->CallIntMethod(obj.obj(),
@@ -539,20 +519,19 @@ static jint Java_MotionEvent_getActionMasked(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getActionIndex = 0;
-static jint Java_MotionEvent_getActionIndex(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jint Java_MotionEvent_getActionIndex(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getActionIndex = 0;
+static jint Java_MotionEvent_getActionIndex(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jint Java_MotionEvent_getActionIndex(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getActionIndex",
+ "()I",
+ &g_android_view_MotionEvent_getActionIndex);
jint ret =
env->CallIntMethod(obj.obj(),
@@ -561,20 +540,18 @@ static jint Java_MotionEvent_getActionIndex(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getFlags = 0;
-static jint Java_MotionEvent_getFlags(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jint Java_MotionEvent_getFlags(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getFlags = 0;
+static jint Java_MotionEvent_getFlags(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jint Java_MotionEvent_getFlags(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getFlags",
+ "()I",
+ &g_android_view_MotionEvent_getFlags);
jint ret =
env->CallIntMethod(obj.obj(),
@@ -583,20 +560,18 @@ static jint Java_MotionEvent_getFlags(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getDownTime = 0;
-static jlong Java_MotionEvent_getDownTime(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jlong Java_MotionEvent_getDownTime(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getDownTime = 0;
+static jlong Java_MotionEvent_getDownTime(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jlong Java_MotionEvent_getDownTime(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getDownTime",
+ "()J",
+ &g_android_view_MotionEvent_getDownTime);
jlong ret =
env->CallLongMethod(obj.obj(),
@@ -605,20 +580,19 @@ static jlong Java_MotionEvent_getDownTime(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getEventTime = 0;
-static jlong Java_MotionEvent_getEventTime(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jlong Java_MotionEvent_getEventTime(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getEventTime = 0;
+static jlong Java_MotionEvent_getEventTime(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jlong Java_MotionEvent_getEventTime(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getEventTime",
+ "()J",
+ &g_android_view_MotionEvent_getEventTime);
jlong ret =
env->CallLongMethod(obj.obj(),
@@ -627,20 +601,18 @@ static jlong Java_MotionEvent_getEventTime(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getXF = 0;
-static jfloat Java_MotionEvent_getXF(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jfloat Java_MotionEvent_getXF(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getXF = 0;
+static jfloat Java_MotionEvent_getXF(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jfloat Java_MotionEvent_getXF(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getX",
+ "()F",
+ &g_android_view_MotionEvent_getXF);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -649,20 +621,18 @@ static jfloat Java_MotionEvent_getXF(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getYF = 0;
-static jfloat Java_MotionEvent_getYF(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jfloat Java_MotionEvent_getYF(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getYF = 0;
+static jfloat Java_MotionEvent_getYF(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jfloat Java_MotionEvent_getYF(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getY",
+ "()F",
+ &g_android_view_MotionEvent_getYF);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -671,20 +641,19 @@ static jfloat Java_MotionEvent_getYF(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getPressureF = 0;
-static jfloat Java_MotionEvent_getPressureF(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jfloat Java_MotionEvent_getPressureF(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getPressureF = 0;
+static jfloat Java_MotionEvent_getPressureF(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jfloat Java_MotionEvent_getPressureF(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getPressure",
+ "()F",
+ &g_android_view_MotionEvent_getPressureF);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -693,20 +662,18 @@ static jfloat Java_MotionEvent_getPressureF(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getSizeF = 0;
-static jfloat Java_MotionEvent_getSizeF(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jfloat Java_MotionEvent_getSizeF(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getSizeF = 0;
+static jfloat Java_MotionEvent_getSizeF(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jfloat Java_MotionEvent_getSizeF(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getSize",
+ "()F",
+ &g_android_view_MotionEvent_getSizeF);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -715,20 +682,19 @@ static jfloat Java_MotionEvent_getSizeF(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getTouchMajorF = 0;
-static jfloat Java_MotionEvent_getTouchMajorF(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jfloat Java_MotionEvent_getTouchMajorF(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getTouchMajorF = 0;
+static jfloat Java_MotionEvent_getTouchMajorF(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getTouchMajorF(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getTouchMajor",
+ "()F",
+ &g_android_view_MotionEvent_getTouchMajorF);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -737,20 +703,19 @@ static jfloat Java_MotionEvent_getTouchMajorF(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getTouchMinorF = 0;
-static jfloat Java_MotionEvent_getTouchMinorF(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jfloat Java_MotionEvent_getTouchMinorF(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getTouchMinorF = 0;
+static jfloat Java_MotionEvent_getTouchMinorF(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getTouchMinorF(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getTouchMinor",
+ "()F",
+ &g_android_view_MotionEvent_getTouchMinorF);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -759,20 +724,19 @@ static jfloat Java_MotionEvent_getTouchMinorF(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getToolMajorF = 0;
-static jfloat Java_MotionEvent_getToolMajorF(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jfloat Java_MotionEvent_getToolMajorF(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getToolMajorF = 0;
+static jfloat Java_MotionEvent_getToolMajorF(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getToolMajorF(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getToolMajor",
+ "()F",
+ &g_android_view_MotionEvent_getToolMajorF);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -781,20 +745,19 @@ static jfloat Java_MotionEvent_getToolMajorF(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getToolMinorF = 0;
-static jfloat Java_MotionEvent_getToolMinorF(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jfloat Java_MotionEvent_getToolMinorF(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getToolMinorF = 0;
+static jfloat Java_MotionEvent_getToolMinorF(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getToolMinorF(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getToolMinor",
+ "()F",
+ &g_android_view_MotionEvent_getToolMinorF);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -803,20 +766,19 @@ static jfloat Java_MotionEvent_getToolMinorF(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getOrientationF = 0;
-static jfloat Java_MotionEvent_getOrientationF(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jfloat Java_MotionEvent_getOrientationF(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getOrientationF = 0;
+static jfloat Java_MotionEvent_getOrientationF(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getOrientationF(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getOrientation",
+ "()F",
+ &g_android_view_MotionEvent_getOrientationF);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -825,21 +787,19 @@ static jfloat Java_MotionEvent_getOrientationF(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getAxisValueF_I = 0;
-static jfloat Java_MotionEvent_getAxisValueF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static jfloat Java_MotionEvent_getAxisValueF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getAxisValueF_I = 0;
+static jfloat Java_MotionEvent_getAxisValueF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getAxisValueF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getAxisValue",
+ "(I)F",
+ &g_android_view_MotionEvent_getAxisValueF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -848,20 +808,19 @@ static jfloat Java_MotionEvent_getAxisValueF_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getPointerCount = 0;
-static jint Java_MotionEvent_getPointerCount(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jint Java_MotionEvent_getPointerCount(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getPointerCount = 0;
+static jint Java_MotionEvent_getPointerCount(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) __attribute__ ((unused));
+static jint Java_MotionEvent_getPointerCount(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getPointerCount",
+ "()I",
+ &g_android_view_MotionEvent_getPointerCount);
jint ret =
env->CallIntMethod(obj.obj(),
@@ -870,21 +829,19 @@ static jint Java_MotionEvent_getPointerCount(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getPointerId = 0;
-static jint Java_MotionEvent_getPointerId(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static jint Java_MotionEvent_getPointerId(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getPointerId = 0;
+static jint Java_MotionEvent_getPointerId(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper p0) __attribute__ ((unused));
+static jint Java_MotionEvent_getPointerId(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getPointerId",
+ "(I)I",
+ &g_android_view_MotionEvent_getPointerId);
jint ret =
env->CallIntMethod(obj.obj(),
@@ -893,21 +850,19 @@ static jint Java_MotionEvent_getPointerId(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getToolType = 0;
-static jint Java_MotionEvent_getToolType(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static jint Java_MotionEvent_getToolType(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getToolType = 0;
+static jint Java_MotionEvent_getToolType(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper p0) __attribute__ ((unused));
+static jint Java_MotionEvent_getToolType(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getToolType",
+ "(I)I",
+ &g_android_view_MotionEvent_getToolType);
jint ret =
env->CallIntMethod(obj.obj(),
@@ -916,21 +871,19 @@ static jint Java_MotionEvent_getToolType(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_findPointerIndex = 0;
-static jint Java_MotionEvent_findPointerIndex(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static jint Java_MotionEvent_findPointerIndex(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_findPointerIndex = 0;
+static jint Java_MotionEvent_findPointerIndex(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) __attribute__ ((unused));
+static jint Java_MotionEvent_findPointerIndex(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "findPointerIndex",
+ "(I)I",
+ &g_android_view_MotionEvent_findPointerIndex);
jint ret =
env->CallIntMethod(obj.obj(),
@@ -939,21 +892,19 @@ static jint Java_MotionEvent_findPointerIndex(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getXF_I = 0;
-static jfloat Java_MotionEvent_getXF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static jfloat Java_MotionEvent_getXF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getXF_I = 0;
+static jfloat Java_MotionEvent_getXF_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper p0) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getXF_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getX",
+ "(I)F",
+ &g_android_view_MotionEvent_getXF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -962,21 +913,19 @@ static jfloat Java_MotionEvent_getXF_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getYF_I = 0;
-static jfloat Java_MotionEvent_getYF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static jfloat Java_MotionEvent_getYF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getYF_I = 0;
+static jfloat Java_MotionEvent_getYF_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper p0) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getYF_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getY",
+ "(I)F",
+ &g_android_view_MotionEvent_getYF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -985,21 +934,19 @@ static jfloat Java_MotionEvent_getYF_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getPressureF_I = 0;
-static jfloat Java_MotionEvent_getPressureF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static jfloat Java_MotionEvent_getPressureF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getPressureF_I = 0;
+static jfloat Java_MotionEvent_getPressureF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getPressureF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getPressure",
+ "(I)F",
+ &g_android_view_MotionEvent_getPressureF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1008,21 +955,19 @@ static jfloat Java_MotionEvent_getPressureF_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getSizeF_I = 0;
-static jfloat Java_MotionEvent_getSizeF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static jfloat Java_MotionEvent_getSizeF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getSizeF_I = 0;
+static jfloat Java_MotionEvent_getSizeF_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper p0) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getSizeF_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getSize",
+ "(I)F",
+ &g_android_view_MotionEvent_getSizeF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1031,21 +976,19 @@ static jfloat Java_MotionEvent_getSizeF_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getTouchMajorF_I = 0;
-static jfloat Java_MotionEvent_getTouchMajorF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static jfloat Java_MotionEvent_getTouchMajorF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getTouchMajorF_I = 0;
+static jfloat Java_MotionEvent_getTouchMajorF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getTouchMajorF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getTouchMajor",
+ "(I)F",
+ &g_android_view_MotionEvent_getTouchMajorF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1054,21 +997,19 @@ static jfloat Java_MotionEvent_getTouchMajorF_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getTouchMinorF_I = 0;
-static jfloat Java_MotionEvent_getTouchMinorF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static jfloat Java_MotionEvent_getTouchMinorF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getTouchMinorF_I = 0;
+static jfloat Java_MotionEvent_getTouchMinorF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getTouchMinorF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getTouchMinor",
+ "(I)F",
+ &g_android_view_MotionEvent_getTouchMinorF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1077,21 +1018,19 @@ static jfloat Java_MotionEvent_getTouchMinorF_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getToolMajorF_I = 0;
-static jfloat Java_MotionEvent_getToolMajorF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static jfloat Java_MotionEvent_getToolMajorF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getToolMajorF_I = 0;
+static jfloat Java_MotionEvent_getToolMajorF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getToolMajorF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getToolMajor",
+ "(I)F",
+ &g_android_view_MotionEvent_getToolMajorF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1100,21 +1039,19 @@ static jfloat Java_MotionEvent_getToolMajorF_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getToolMinorF_I = 0;
-static jfloat Java_MotionEvent_getToolMinorF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static jfloat Java_MotionEvent_getToolMinorF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getToolMinorF_I = 0;
+static jfloat Java_MotionEvent_getToolMinorF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getToolMinorF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getToolMinor",
+ "(I)F",
+ &g_android_view_MotionEvent_getToolMinorF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1123,21 +1060,19 @@ static jfloat Java_MotionEvent_getToolMinorF_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getOrientationF_I = 0;
-static jfloat Java_MotionEvent_getOrientationF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static jfloat Java_MotionEvent_getOrientationF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getOrientationF_I = 0;
+static jfloat Java_MotionEvent_getOrientationF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getOrientationF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getOrientation",
+ "(I)F",
+ &g_android_view_MotionEvent_getOrientationF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1146,22 +1081,21 @@ static jfloat Java_MotionEvent_getOrientationF_I(JNIEnv* env, const
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<jobject>& obj, JniIntWrapper p0,
+static base::subtle::AtomicWord g_android_view_MotionEvent_getAxisValueF_I_I = 0;
+static jfloat Java_MotionEvent_getAxisValueF_I_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0,
JniIntWrapper p1) __attribute__ ((unused));
-static jfloat Java_MotionEvent_getAxisValueF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+static jfloat Java_MotionEvent_getAxisValueF_I_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0,
JniIntWrapper p1) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getAxisValue",
+ "(II)F",
+ &g_android_view_MotionEvent_getAxisValueF_I_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1170,64 +1104,60 @@ static jfloat Java_MotionEvent_getAxisValueF_I_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getPointerCoords = 0;
-static void Java_MotionEvent_getPointerCoords(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
- const base::android::JavaRefOrBare<jobject>& p1) __attribute__ ((unused));
-static void Java_MotionEvent_getPointerCoords(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
- const base::android::JavaRefOrBare<jobject>& p1) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getPointerCoords = 0;
+static void Java_MotionEvent_getPointerCoords(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0,
+ const base::android::JavaRef<jobject>& p1) __attribute__ ((unused));
+static void Java_MotionEvent_getPointerCoords(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0,
+ const base::android::JavaRef<jobject>& p1) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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, android_view_MotionEvent_clazz(env),
+ "getPointerCoords",
+ "(ILandroid/view/MotionEvent$PointerCoords;)V",
+ &g_android_view_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 base::subtle::AtomicWord g_android_view_MotionEvent_getPointerProperties = 0;
static void Java_MotionEvent_getPointerProperties(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
- const base::android::JavaRefOrBare<jobject>& p1) __attribute__ ((unused));
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
+ const base::android::JavaRef<jobject>& p1) __attribute__ ((unused));
static void Java_MotionEvent_getPointerProperties(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
- const base::android::JavaRefOrBare<jobject>& p1) {
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
+ const base::android::JavaRef<jobject>& p1) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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, android_view_MotionEvent_clazz(env),
+ "getPointerProperties",
+ "(ILandroid/view/MotionEvent$PointerProperties;)V",
+ &g_android_view_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<jobject>& obj) __attribute__ ((unused));
-static jint Java_MotionEvent_getMetaState(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getMetaState = 0;
+static jint Java_MotionEvent_getMetaState(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jint Java_MotionEvent_getMetaState(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getMetaState",
+ "()I",
+ &g_android_view_MotionEvent_getMetaState);
jint ret =
env->CallIntMethod(obj.obj(),
@@ -1236,20 +1166,19 @@ static jint Java_MotionEvent_getMetaState(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getButtonState = 0;
-static jint Java_MotionEvent_getButtonState(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jint Java_MotionEvent_getButtonState(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getButtonState = 0;
+static jint Java_MotionEvent_getButtonState(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jint Java_MotionEvent_getButtonState(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getButtonState",
+ "()I",
+ &g_android_view_MotionEvent_getButtonState);
jint ret =
env->CallIntMethod(obj.obj(),
@@ -1258,20 +1187,18 @@ static jint Java_MotionEvent_getButtonState(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getRawX = 0;
-static jfloat Java_MotionEvent_getRawX(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jfloat Java_MotionEvent_getRawX(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getRawX = 0;
+static jfloat Java_MotionEvent_getRawX(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jfloat Java_MotionEvent_getRawX(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getRawX",
+ "()F",
+ &g_android_view_MotionEvent_getRawX);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1280,20 +1207,18 @@ static jfloat Java_MotionEvent_getRawX(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getRawY = 0;
-static jfloat Java_MotionEvent_getRawY(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jfloat Java_MotionEvent_getRawY(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getRawY = 0;
+static jfloat Java_MotionEvent_getRawY(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jfloat Java_MotionEvent_getRawY(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getRawY",
+ "()F",
+ &g_android_view_MotionEvent_getRawY);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1302,20 +1227,19 @@ static jfloat Java_MotionEvent_getRawY(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getXPrecision = 0;
-static jfloat Java_MotionEvent_getXPrecision(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jfloat Java_MotionEvent_getXPrecision(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getXPrecision = 0;
+static jfloat Java_MotionEvent_getXPrecision(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getXPrecision(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getXPrecision",
+ "()F",
+ &g_android_view_MotionEvent_getXPrecision);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1324,20 +1248,19 @@ static jfloat Java_MotionEvent_getXPrecision(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getYPrecision = 0;
-static jfloat Java_MotionEvent_getYPrecision(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jfloat Java_MotionEvent_getYPrecision(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getYPrecision = 0;
+static jfloat Java_MotionEvent_getYPrecision(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getYPrecision(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getYPrecision",
+ "()F",
+ &g_android_view_MotionEvent_getYPrecision);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1346,20 +1269,19 @@ static jfloat Java_MotionEvent_getYPrecision(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistorySize = 0;
-static jint Java_MotionEvent_getHistorySize(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jint Java_MotionEvent_getHistorySize(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistorySize = 0;
+static jint Java_MotionEvent_getHistorySize(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jint Java_MotionEvent_getHistorySize(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistorySize",
+ "()I",
+ &g_android_view_MotionEvent_getHistorySize);
jint ret =
env->CallIntMethod(obj.obj(),
@@ -1368,21 +1290,19 @@ static jint Java_MotionEvent_getHistorySize(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalEventTime = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalEventTime = 0;
static jlong Java_MotionEvent_getHistoricalEventTime(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused));
static jlong Java_MotionEvent_getHistoricalEventTime(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalEventTime",
+ "(I)J",
+ &g_android_view_MotionEvent_getHistoricalEventTime);
jlong ret =
env->CallLongMethod(obj.obj(),
@@ -1391,21 +1311,19 @@ static jlong Java_MotionEvent_getHistoricalEventTime(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalXF_I = 0;
-static jfloat Java_MotionEvent_getHistoricalXF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static jfloat Java_MotionEvent_getHistoricalXF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalXF_I = 0;
+static jfloat Java_MotionEvent_getHistoricalXF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getHistoricalXF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalX",
+ "(I)F",
+ &g_android_view_MotionEvent_getHistoricalXF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1414,21 +1332,19 @@ static jfloat Java_MotionEvent_getHistoricalXF_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalYF_I = 0;
-static jfloat Java_MotionEvent_getHistoricalYF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static jfloat Java_MotionEvent_getHistoricalYF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalYF_I = 0;
+static jfloat Java_MotionEvent_getHistoricalYF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) __attribute__ ((unused));
+static jfloat Java_MotionEvent_getHistoricalYF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj, JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalY",
+ "(I)F",
+ &g_android_view_MotionEvent_getHistoricalYF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1437,21 +1353,19 @@ static jfloat Java_MotionEvent_getHistoricalYF_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalPressureF_I = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalPressureF_I = 0;
static jfloat Java_MotionEvent_getHistoricalPressureF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused));
static jfloat Java_MotionEvent_getHistoricalPressureF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalPressure",
+ "(I)F",
+ &g_android_view_MotionEvent_getHistoricalPressureF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1460,21 +1374,19 @@ static jfloat Java_MotionEvent_getHistoricalPressureF_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalSizeF_I = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalSizeF_I = 0;
static jfloat Java_MotionEvent_getHistoricalSizeF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused));
static jfloat Java_MotionEvent_getHistoricalSizeF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalSize",
+ "(I)F",
+ &g_android_view_MotionEvent_getHistoricalSizeF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1483,21 +1395,19 @@ static jfloat Java_MotionEvent_getHistoricalSizeF_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalTouchMajorF_I = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalTouchMajorF_I = 0;
static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused));
static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalTouchMajor",
+ "(I)F",
+ &g_android_view_MotionEvent_getHistoricalTouchMajorF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1506,21 +1416,19 @@ static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalTouchMinorF_I = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalTouchMinorF_I = 0;
static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused));
static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalTouchMinor",
+ "(I)F",
+ &g_android_view_MotionEvent_getHistoricalTouchMinorF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1529,21 +1437,19 @@ static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalToolMajorF_I = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalToolMajorF_I = 0;
static jfloat Java_MotionEvent_getHistoricalToolMajorF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused));
static jfloat Java_MotionEvent_getHistoricalToolMajorF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalToolMajor",
+ "(I)F",
+ &g_android_view_MotionEvent_getHistoricalToolMajorF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1552,21 +1458,19 @@ static jfloat Java_MotionEvent_getHistoricalToolMajorF_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalToolMinorF_I = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalToolMinorF_I = 0;
static jfloat Java_MotionEvent_getHistoricalToolMinorF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused));
static jfloat Java_MotionEvent_getHistoricalToolMinorF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalToolMinor",
+ "(I)F",
+ &g_android_view_MotionEvent_getHistoricalToolMinorF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1575,21 +1479,19 @@ static jfloat Java_MotionEvent_getHistoricalToolMinorF_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalOrientationF_I = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalOrientationF_I = 0;
static jfloat Java_MotionEvent_getHistoricalOrientationF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused));
static jfloat Java_MotionEvent_getHistoricalOrientationF_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalOrientation",
+ "(I)F",
+ &g_android_view_MotionEvent_getHistoricalOrientationF_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1598,22 +1500,21 @@ static jfloat Java_MotionEvent_getHistoricalOrientationF_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalAxisValueF_I_I = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalAxisValueF_I_I = 0;
static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) __attribute__ ((unused));
static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalAxisValue",
+ "(II)F",
+ &g_android_view_MotionEvent_getHistoricalAxisValueF_I_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1622,22 +1523,21 @@ static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalXF_I_I = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalXF_I_I = 0;
static jfloat Java_MotionEvent_getHistoricalXF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) __attribute__ ((unused));
static jfloat Java_MotionEvent_getHistoricalXF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalX",
+ "(II)F",
+ &g_android_view_MotionEvent_getHistoricalXF_I_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1646,22 +1546,21 @@ static jfloat Java_MotionEvent_getHistoricalXF_I_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalYF_I_I = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalYF_I_I = 0;
static jfloat Java_MotionEvent_getHistoricalYF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) __attribute__ ((unused));
static jfloat Java_MotionEvent_getHistoricalYF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalY",
+ "(II)F",
+ &g_android_view_MotionEvent_getHistoricalYF_I_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1670,22 +1569,21 @@ static jfloat Java_MotionEvent_getHistoricalYF_I_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalPressureF_I_I = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalPressureF_I_I = 0;
static jfloat Java_MotionEvent_getHistoricalPressureF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) __attribute__ ((unused));
static jfloat Java_MotionEvent_getHistoricalPressureF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalPressure",
+ "(II)F",
+ &g_android_view_MotionEvent_getHistoricalPressureF_I_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1694,22 +1592,21 @@ static jfloat Java_MotionEvent_getHistoricalPressureF_I_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalSizeF_I_I = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalSizeF_I_I = 0;
static jfloat Java_MotionEvent_getHistoricalSizeF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) __attribute__ ((unused));
static jfloat Java_MotionEvent_getHistoricalSizeF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalSize",
+ "(II)F",
+ &g_android_view_MotionEvent_getHistoricalSizeF_I_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1718,22 +1615,21 @@ static jfloat Java_MotionEvent_getHistoricalSizeF_I_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalTouchMajorF_I_I = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalTouchMajorF_I_I = 0;
static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) __attribute__ ((unused));
static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalTouchMajor",
+ "(II)F",
+ &g_android_view_MotionEvent_getHistoricalTouchMajorF_I_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1742,22 +1638,21 @@ static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalTouchMinorF_I_I = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalTouchMinorF_I_I = 0;
static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) __attribute__ ((unused));
static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalTouchMinor",
+ "(II)F",
+ &g_android_view_MotionEvent_getHistoricalTouchMinorF_I_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1766,22 +1661,21 @@ static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalToolMajorF_I_I = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalToolMajorF_I_I = 0;
static jfloat Java_MotionEvent_getHistoricalToolMajorF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) __attribute__ ((unused));
static jfloat Java_MotionEvent_getHistoricalToolMajorF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalToolMajor",
+ "(II)F",
+ &g_android_view_MotionEvent_getHistoricalToolMajorF_I_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1790,22 +1684,21 @@ static jfloat Java_MotionEvent_getHistoricalToolMajorF_I_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalToolMinorF_I_I = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalToolMinorF_I_I = 0;
static jfloat Java_MotionEvent_getHistoricalToolMinorF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) __attribute__ ((unused));
static jfloat Java_MotionEvent_getHistoricalToolMinorF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalToolMinor",
+ "(II)F",
+ &g_android_view_MotionEvent_getHistoricalToolMinorF_I_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1814,22 +1707,21 @@ static jfloat Java_MotionEvent_getHistoricalToolMinorF_I_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalOrientationF_I_I = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalOrientationF_I_I = 0;
static jfloat Java_MotionEvent_getHistoricalOrientationF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) __attribute__ ((unused));
static jfloat Java_MotionEvent_getHistoricalOrientationF_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalOrientation",
+ "(II)F",
+ &g_android_view_MotionEvent_getHistoricalOrientationF_I_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1838,24 +1730,23 @@ static jfloat Java_MotionEvent_getHistoricalOrientationF_I_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalAxisValueF_I_I_I = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalAxisValueF_I_I_I = 0;
static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1,
JniIntWrapper p2) __attribute__ ((unused));
static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1,
JniIntWrapper p2) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getHistoricalAxisValue",
+ "(III)F",
+ &g_android_view_MotionEvent_getHistoricalAxisValueF_I_I_I);
jfloat ret =
env->CallFloatMethod(obj.obj(),
@@ -1864,44 +1755,41 @@ static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_getHistoricalPointerCoords = 0;
+static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalPointerCoords = 0;
static void Java_MotionEvent_getHistoricalPointerCoords(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1,
- const base::android::JavaRefOrBare<jobject>& p2) __attribute__ ((unused));
+ const base::android::JavaRef<jobject>& p2) __attribute__ ((unused));
static void Java_MotionEvent_getHistoricalPointerCoords(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0,
+ base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
JniIntWrapper p1,
- const base::android::JavaRefOrBare<jobject>& p2) {
+ const base::android::JavaRef<jobject>& p2) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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, android_view_MotionEvent_clazz(env),
+ "getHistoricalPointerCoords",
+ "(IILandroid/view/MotionEvent$PointerCoords;)V",
+ &g_android_view_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<jobject>& obj) __attribute__ ((unused));
-static jint Java_MotionEvent_getEdgeFlags(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_getEdgeFlags = 0;
+static jint Java_MotionEvent_getEdgeFlags(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jint Java_MotionEvent_getEdgeFlags(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "getEdgeFlags",
+ "()I",
+ &g_android_view_MotionEvent_getEdgeFlags);
jint ret =
env->CallIntMethod(obj.obj(),
@@ -1910,184 +1798,170 @@ static jint Java_MotionEvent_getEdgeFlags(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_MotionEvent_setEdgeFlags = 0;
-static void Java_MotionEvent_setEdgeFlags(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static void Java_MotionEvent_setEdgeFlags(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_setEdgeFlags = 0;
+static void Java_MotionEvent_setEdgeFlags(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper p0) __attribute__ ((unused));
+static void Java_MotionEvent_setEdgeFlags(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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, android_view_MotionEvent_clazz(env),
+ "setEdgeFlags",
+ "(I)V",
+ &g_android_view_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<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static void Java_MotionEvent_setAction(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_setAction = 0;
+static void Java_MotionEvent_setAction(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper p0) __attribute__ ((unused));
+static void Java_MotionEvent_setAction(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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, android_view_MotionEvent_clazz(env),
+ "setAction",
+ "(I)V",
+ &g_android_view_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<jobject>& obj, jfloat p0,
+static base::subtle::AtomicWord g_android_view_MotionEvent_offsetLocation = 0;
+static void Java_MotionEvent_offsetLocation(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ jfloat p0,
jfloat p1) __attribute__ ((unused));
-static void Java_MotionEvent_offsetLocation(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, jfloat p0,
+static void Java_MotionEvent_offsetLocation(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ jfloat p0,
jfloat p1) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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, android_view_MotionEvent_clazz(env),
+ "offsetLocation",
+ "(FF)V",
+ &g_android_view_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<jobject>& obj, jfloat p0,
+static base::subtle::AtomicWord g_android_view_MotionEvent_setLocation = 0;
+static void Java_MotionEvent_setLocation(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ jfloat p0,
jfloat p1) __attribute__ ((unused));
-static void Java_MotionEvent_setLocation(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, jfloat p0,
+static void Java_MotionEvent_setLocation(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ jfloat p0,
jfloat p1) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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, android_view_MotionEvent_clazz(env),
+ "setLocation",
+ "(FF)V",
+ &g_android_view_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<jobject>& obj, const
- base::android::JavaRefOrBare<jobject>& p0) __attribute__ ((unused));
-static void Java_MotionEvent_transform(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, const
- base::android::JavaRefOrBare<jobject>& p0) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_transform = 0;
+static void Java_MotionEvent_transform(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ const base::android::JavaRef<jobject>& p0) __attribute__ ((unused));
+static void Java_MotionEvent_transform(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ const base::android::JavaRef<jobject>& p0) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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, android_view_MotionEvent_clazz(env),
+ "transform",
+ "(Landroid/graphics/Matrix;)V",
+ &g_android_view_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 base::subtle::AtomicWord g_android_view_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<jobject>& obj, jlong p0,
+ base::android::JavaRef<jobject>& 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<jobject>& obj, jlong p0,
+ base::android::JavaRef<jobject>& 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<
+ android_view_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, android_view_MotionEvent_clazz(env),
+ "addBatch",
+ "(JFFFFI)V",
+ &g_android_view_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 base::subtle::AtomicWord g_android_view_MotionEvent_addBatchV_J_LAVMEPC_I = 0;
static void Java_MotionEvent_addBatchV_J_LAVMEPC_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, jlong p0,
- const base::android::JavaRefOrBare<jobjectArray>& p1,
+ base::android::JavaRef<jobject>& obj, jlong p0,
+ const base::android::JavaRef<jobjectArray>& p1,
JniIntWrapper p2) __attribute__ ((unused));
static void Java_MotionEvent_addBatchV_J_LAVMEPC_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, jlong p0,
- const base::android::JavaRefOrBare<jobjectArray>& p1,
+ base::android::JavaRef<jobject>& obj, jlong p0,
+ const base::android::JavaRef<jobjectArray>& p1,
JniIntWrapper p2) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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, android_view_MotionEvent_clazz(env),
+ "addBatch",
+ "(J[Landroid/view/MotionEvent$PointerCoords;I)V",
+ &g_android_view_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<jstring>
- Java_MotionEvent_toString(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static base::android::ScopedJavaLocalRef<jstring>
- Java_MotionEvent_toString(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_android_view_MotionEvent_toString = 0;
+static base::android::ScopedJavaLocalRef<jstring> Java_MotionEvent_toString(JNIEnv* env, const
+ base::android::JavaRef<jobject>& obj) __attribute__ ((unused));
+static base::android::ScopedJavaLocalRef<jstring> Java_MotionEvent_toString(JNIEnv* env, const
+ base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "toString",
+ "()Ljava/lang/String;",
+ &g_android_view_MotionEvent_toString);
jstring ret =
static_cast<jstring>(env->CallObjectMethod(obj.obj(),
@@ -2096,100 +1970,90 @@ static base::android::ScopedJavaLocalRef<jstring>
return base::android::ScopedJavaLocalRef<jstring>(env, ret);
}
-static base::subtle::AtomicWord g_MotionEvent_actionToString = 0;
-static base::android::ScopedJavaLocalRef<jstring>
- Java_MotionEvent_actionToString(JNIEnv* env, JniIntWrapper p0) __attribute__
- ((unused));
-static base::android::ScopedJavaLocalRef<jstring>
- Java_MotionEvent_actionToString(JNIEnv* env, JniIntWrapper p0) {
- CHECK_CLAZZ(env, MotionEvent_clazz(env),
- MotionEvent_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+static base::subtle::AtomicWord g_android_view_MotionEvent_actionToString = 0;
+static base::android::ScopedJavaLocalRef<jstring> Java_MotionEvent_actionToString(JNIEnv* env,
+ JniIntWrapper p0) __attribute__ ((unused));
+static base::android::ScopedJavaLocalRef<jstring> Java_MotionEvent_actionToString(JNIEnv* env,
+ JniIntWrapper p0) {
+ CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env),
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "actionToString",
+ "(I)Ljava/lang/String;",
+ &g_android_view_MotionEvent_actionToString);
jstring ret =
- static_cast<jstring>(env->CallStaticObjectMethod(MotionEvent_clazz(env),
+ static_cast<jstring>(env->CallStaticObjectMethod(android_view_MotionEvent_clazz(env),
method_id, as_jint(p0)));
jni_generator::CheckException(env);
return base::android::ScopedJavaLocalRef<jstring>(env, ret);
}
-static base::subtle::AtomicWord g_MotionEvent_axisToString = 0;
-static base::android::ScopedJavaLocalRef<jstring>
- Java_MotionEvent_axisToString(JNIEnv* env, JniIntWrapper p0) __attribute__
- ((unused));
-static base::android::ScopedJavaLocalRef<jstring>
- Java_MotionEvent_axisToString(JNIEnv* env, JniIntWrapper p0) {
- CHECK_CLAZZ(env, MotionEvent_clazz(env),
- MotionEvent_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+static base::subtle::AtomicWord g_android_view_MotionEvent_axisToString = 0;
+static base::android::ScopedJavaLocalRef<jstring> Java_MotionEvent_axisToString(JNIEnv* env,
+ JniIntWrapper p0) __attribute__ ((unused));
+static base::android::ScopedJavaLocalRef<jstring> Java_MotionEvent_axisToString(JNIEnv* env,
+ JniIntWrapper p0) {
+ CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env),
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "axisToString",
+ "(I)Ljava/lang/String;",
+ &g_android_view_MotionEvent_axisToString);
jstring ret =
- static_cast<jstring>(env->CallStaticObjectMethod(MotionEvent_clazz(env),
+ static_cast<jstring>(env->CallStaticObjectMethod(android_view_MotionEvent_clazz(env),
method_id, as_jint(p0)));
jni_generator::CheckException(env);
return base::android::ScopedJavaLocalRef<jstring>(env, ret);
}
-static base::subtle::AtomicWord g_MotionEvent_axisFromString = 0;
-static jint Java_MotionEvent_axisFromString(JNIEnv* env, const
- base::android::JavaRefOrBare<jstring>& p0) __attribute__ ((unused));
-static jint Java_MotionEvent_axisFromString(JNIEnv* env, const
- base::android::JavaRefOrBare<jstring>& p0) {
- CHECK_CLAZZ(env, MotionEvent_clazz(env),
- MotionEvent_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+static base::subtle::AtomicWord g_android_view_MotionEvent_axisFromString = 0;
+static jint Java_MotionEvent_axisFromString(JNIEnv* env, const base::android::JavaRef<jstring>& p0)
+ __attribute__ ((unused));
+static jint Java_MotionEvent_axisFromString(JNIEnv* env, const base::android::JavaRef<jstring>& p0)
+ {
+ CHECK_CLAZZ(env, android_view_MotionEvent_clazz(env),
+ android_view_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);
+ env, android_view_MotionEvent_clazz(env),
+ "axisFromString",
+ "(Ljava/lang/String;)I",
+ &g_android_view_MotionEvent_axisFromString);
jint ret =
- env->CallStaticIntMethod(MotionEvent_clazz(env),
+ env->CallStaticIntMethod(android_view_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<jobject>& obj, const
- base::android::JavaRefOrBare<jobject>& p0,
+static base::subtle::AtomicWord g_android_view_MotionEvent_writeToParcel = 0;
+static void Java_MotionEvent_writeToParcel(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ const base::android::JavaRef<jobject>& p0,
JniIntWrapper p1) __attribute__ ((unused));
-static void Java_MotionEvent_writeToParcel(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, const
- base::android::JavaRefOrBare<jobject>& p0,
+static void Java_MotionEvent_writeToParcel(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ const base::android::JavaRef<jobject>& p0,
JniIntWrapper p1) {
CHECK_CLAZZ(env, obj.obj(),
- MotionEvent_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ android_view_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, android_view_MotionEvent_clazz(env),
+ "writeToParcel",
+ "(Landroid/os/Parcel;I)V",
+ &g_android_view_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
index 18a9430fef..ca5b050fbf 100644
--- a/base/android/jni_generator/testFromJavaP.golden
+++ b/base/android/jni_generator/testFromJavaP.golden
@@ -2,6 +2,7 @@
// 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
@@ -14,35 +15,41 @@
#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";
+// Step 1: Forward declarations.
+
+JNI_REGISTRATION_EXPORT extern const char kClassPath_java_io_InputStream[];
+const char kClassPath_java_io_InputStream[] = "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)
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_java_io_InputStream_clazz = 0;
+#ifndef java_io_InputStream_clazz_defined
+#define java_io_InputStream_clazz_defined
+inline jclass java_io_InputStream_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_java_io_InputStream,
+ &g_java_io_InputStream_clazz);
+}
+#endif
-} // namespace
+// Step 2: Constants (optional).
+
+
+// Step 3: Method stubs.
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<jobject>& obj) __attribute__ ((unused));
-static jint Java_InputStream_available(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_java_io_InputStream_available = 0;
+static jint Java_InputStream_available(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jint Java_InputStream_available(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- InputStream_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ java_io_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);
+ env, java_io_InputStream_clazz(env),
+ "available",
+ "()I",
+ &g_java_io_InputStream_available);
jint ret =
env->CallIntMethod(obj.obj(),
@@ -51,61 +58,56 @@ static jint Java_InputStream_available(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_InputStream_close = 0;
-static void Java_InputStream_close(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static void Java_InputStream_close(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_java_io_InputStream_close = 0;
+static void Java_InputStream_close(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static void Java_InputStream_close(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- InputStream_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ java_io_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, java_io_InputStream_clazz(env),
+ "close",
+ "()V",
+ &g_java_io_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<jobject>& obj, JniIntWrapper p0) __attribute__
- ((unused));
-static void Java_InputStream_mark(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, JniIntWrapper p0) {
+static base::subtle::AtomicWord g_java_io_InputStream_mark = 0;
+static void Java_InputStream_mark(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper p0) __attribute__ ((unused));
+static void Java_InputStream_mark(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ JniIntWrapper p0) {
CHECK_CLAZZ(env, obj.obj(),
- InputStream_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ java_io_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, java_io_InputStream_clazz(env),
+ "mark",
+ "(I)V",
+ &g_java_io_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<jobject>& obj) __attribute__ ((unused));
-static jboolean Java_InputStream_markSupported(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_java_io_InputStream_markSupported = 0;
+static jboolean Java_InputStream_markSupported(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) __attribute__ ((unused));
+static jboolean Java_InputStream_markSupported(JNIEnv* env, const base::android::JavaRef<jobject>&
+ obj) {
CHECK_CLAZZ(env, obj.obj(),
- InputStream_clazz(env), false);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ java_io_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);
+ env, java_io_InputStream_clazz(env),
+ "markSupported",
+ "()Z",
+ &g_java_io_InputStream_markSupported);
jboolean ret =
env->CallBooleanMethod(obj.obj(),
@@ -114,20 +116,18 @@ static jboolean Java_InputStream_markSupported(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_InputStream_readI = 0;
-static jint Java_InputStream_readI(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static jint Java_InputStream_readI(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_java_io_InputStream_readI = 0;
+static jint Java_InputStream_readI(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static jint Java_InputStream_readI(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- InputStream_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ java_io_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);
+ env, java_io_InputStream_clazz(env),
+ "read",
+ "()I",
+ &g_java_io_InputStream_readI);
jint ret =
env->CallIntMethod(obj.obj(),
@@ -136,22 +136,19 @@ static jint Java_InputStream_readI(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_InputStream_readI_AB = 0;
-static jint Java_InputStream_readI_AB(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, const
- base::android::JavaRefOrBare<jbyteArray>& p0) __attribute__ ((unused));
-static jint Java_InputStream_readI_AB(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, const
- base::android::JavaRefOrBare<jbyteArray>& p0) {
+static base::subtle::AtomicWord g_java_io_InputStream_readI_AB = 0;
+static jint Java_InputStream_readI_AB(JNIEnv* env, const base::android::JavaRef<jobject>& obj, const
+ base::android::JavaRef<jbyteArray>& p0) __attribute__ ((unused));
+static jint Java_InputStream_readI_AB(JNIEnv* env, const base::android::JavaRef<jobject>& obj, const
+ base::android::JavaRef<jbyteArray>& p0) {
CHECK_CLAZZ(env, obj.obj(),
- InputStream_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ java_io_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);
+ env, java_io_InputStream_clazz(env),
+ "read",
+ "([B)I",
+ &g_java_io_InputStream_readI_AB);
jint ret =
env->CallIntMethod(obj.obj(),
@@ -160,26 +157,23 @@ static jint Java_InputStream_readI_AB(JNIEnv* env, const
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<jobject>& obj, const
- base::android::JavaRefOrBare<jbyteArray>& p0,
+static base::subtle::AtomicWord g_java_io_InputStream_readI_AB_I_I = 0;
+static jint Java_InputStream_readI_AB_I_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ const base::android::JavaRef<jbyteArray>& p0,
JniIntWrapper p1,
JniIntWrapper p2) __attribute__ ((unused));
-static jint Java_InputStream_readI_AB_I_I(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, const
- base::android::JavaRefOrBare<jbyteArray>& p0,
+static jint Java_InputStream_readI_AB_I_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
+ const base::android::JavaRef<jbyteArray>& p0,
JniIntWrapper p1,
JniIntWrapper p2) {
CHECK_CLAZZ(env, obj.obj(),
- InputStream_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ java_io_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);
+ env, java_io_InputStream_clazz(env),
+ "read",
+ "([BII)I",
+ &g_java_io_InputStream_readI_AB_I_I);
jint ret =
env->CallIntMethod(obj.obj(),
@@ -188,41 +182,37 @@ static jint Java_InputStream_readI_AB_I_I(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_InputStream_reset = 0;
-static void Java_InputStream_reset(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) __attribute__ ((unused));
-static void Java_InputStream_reset(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_java_io_InputStream_reset = 0;
+static void Java_InputStream_reset(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static void Java_InputStream_reset(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- InputStream_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ java_io_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, java_io_InputStream_clazz(env),
+ "reset",
+ "()V",
+ &g_java_io_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<jobject>& obj, jlong p0) __attribute__
- ((unused));
-static jlong Java_InputStream_skip(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj, jlong p0) {
+static base::subtle::AtomicWord g_java_io_InputStream_skip = 0;
+static jlong Java_InputStream_skip(JNIEnv* env, const base::android::JavaRef<jobject>& obj, jlong
+ p0) __attribute__ ((unused));
+static jlong Java_InputStream_skip(JNIEnv* env, const base::android::JavaRef<jobject>& obj, jlong
+ p0) {
CHECK_CLAZZ(env, obj.obj(),
- InputStream_clazz(env), 0);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ java_io_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);
+ env, java_io_InputStream_clazz(env),
+ "skip",
+ "(J)J",
+ &g_java_io_InputStream_skip);
jlong ret =
env->CallLongMethod(obj.obj(),
@@ -231,30 +221,26 @@ static jlong Java_InputStream_skip(JNIEnv* env, const
return ret;
}
-static base::subtle::AtomicWord g_InputStream_Constructor = 0;
-static base::android::ScopedJavaLocalRef<jobject>
- Java_InputStream_Constructor(JNIEnv* env) __attribute__ ((unused));
-static base::android::ScopedJavaLocalRef<jobject>
- Java_InputStream_Constructor(JNIEnv* env) {
- CHECK_CLAZZ(env, InputStream_clazz(env),
- InputStream_clazz(env), NULL);
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+static base::subtle::AtomicWord g_java_io_InputStream_Constructor = 0;
+static base::android::ScopedJavaLocalRef<jobject> Java_InputStream_Constructor(JNIEnv* env)
+ __attribute__ ((unused));
+static base::android::ScopedJavaLocalRef<jobject> Java_InputStream_Constructor(JNIEnv* env) {
+ CHECK_CLAZZ(env, java_io_InputStream_clazz(env),
+ java_io_InputStream_clazz(env), NULL);
+ jmethodID method_id = base::android::MethodID::LazyGet<
base::android::MethodID::TYPE_INSTANCE>(
- env, InputStream_clazz(env),
- "<init>",
- "()V",
- &g_InputStream_Constructor);
+ env, java_io_InputStream_clazz(env),
+ "<init>",
+ "()V",
+ &g_java_io_InputStream_Constructor);
jobject ret =
- env->NewObject(InputStream_clazz(env),
+ env->NewObject(java_io_InputStream_clazz(env),
method_id);
jni_generator::CheckException(env);
return base::android::ScopedJavaLocalRef<jobject>(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
index c076c39c36..25c5e0ea22 100644
--- a/base/android/jni_generator/testFromJavaPGenerics.golden
+++ b/base/android/jni_generator/testFromJavaPGenerics.golden
@@ -2,6 +2,7 @@
// 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
@@ -14,42 +15,66 @@
#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";
+// Step 1: Forward declarations.
+
+JNI_REGISTRATION_EXPORT extern const char kClassPath_java_util_HashSet[];
+const char kClassPath_java_util_HashSet[] = "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)
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_java_util_HashSet_clazz = 0;
+#ifndef java_util_HashSet_clazz_defined
+#define java_util_HashSet_clazz_defined
+inline jclass java_util_HashSet_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_java_util_HashSet, &g_java_util_HashSet_clazz);
+}
+#endif
+
+
+// Step 2: Constants (optional).
-} // namespace
+// Step 3: Method stubs.
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<jobject>& obj) __attribute__ ((unused));
-static void Java_HashSet_dummy(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& obj) {
+static base::subtle::AtomicWord g_java_util_HashSet_dummy = 0;
+static void Java_HashSet_dummy(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
+ __attribute__ ((unused));
+static void Java_HashSet_dummy(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
CHECK_CLAZZ(env, obj.obj(),
- HashSet_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+ java_util_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, java_util_HashSet_clazz(env),
+ "dummy",
+ "()V",
+ &g_java_util_HashSet_dummy);
env->CallVoidMethod(obj.obj(),
method_id);
jni_generator::CheckException(env);
}
-// Step 3: RegisterNatives.
+static base::subtle::AtomicWord g_java_util_HashSet_getClass = 0;
+static base::android::ScopedJavaLocalRef<jclass> Java_HashSet_getClass(JNIEnv* env, const
+ base::android::JavaRef<jobject>& obj) __attribute__ ((unused));
+static base::android::ScopedJavaLocalRef<jclass> Java_HashSet_getClass(JNIEnv* env, const
+ base::android::JavaRef<jobject>& obj) {
+ CHECK_CLAZZ(env, obj.obj(),
+ java_util_HashSet_clazz(env), NULL);
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_INSTANCE>(
+ env, java_util_HashSet_clazz(env),
+ "getClass",
+ "()Ljava/lang/Class<*>;",
+ &g_java_util_HashSet_getClass);
+
+ jclass ret =
+ static_cast<jclass>(env->CallObjectMethod(obj.obj(),
+ method_id));
+ jni_generator::CheckException(env);
+ return base::android::ScopedJavaLocalRef<jclass>(env, ret);
+}
} // namespace JNI_HashSet
diff --git a/base/android/jni_generator/testInnerClassNatives.golden b/base/android/jni_generator/testInnerClassNatives.golden
index 20b8830301..2c89de9b8b 100644
--- a/base/android/jni_generator/testInnerClassNatives.golden
+++ b/base/android/jni_generator/testInnerClassNatives.golden
@@ -2,6 +2,7 @@
// 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
@@ -14,58 +15,46 @@
#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.
+// Step 1: Forward declarations.
-static jint Init(JNIEnv* env, const base::android::JavaParamRef<jobject>&
- jcaller);
+JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni[];
+const char kClassPath_org_chromium_TestJni[] = "org/chromium/TestJni";
-JNI_GENERATOR_EXPORT jint
- Java_org_chromium_TestJni_00024MyInnerClass_nativeInit(JNIEnv* env, jobject
- jcaller) {
- return Init(env, base::android::JavaParamRef<jobject>(env, jcaller));
+JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni_00024MyInnerClass[];
+const char kClassPath_org_chromium_TestJni_00024MyInnerClass[] =
+ "org/chromium/TestJni$MyInnerClass";
+// Leaking this jclass as we cannot use LazyInstance from some threads.
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_clazz = 0;
+#ifndef org_chromium_TestJni_clazz_defined
+#define org_chromium_TestJni_clazz_defined
+inline jclass org_chromium_TestJni_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni,
+ &g_org_chromium_TestJni_clazz);
}
+#endif
+// Leaking this jclass as we cannot use LazyInstance from some threads.
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_00024MyInnerClass_clazz = 0;
+#ifndef org_chromium_TestJni_00024MyInnerClass_clazz_defined
+#define org_chromium_TestJni_00024MyInnerClass_clazz_defined
+inline jclass org_chromium_TestJni_00024MyInnerClass_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni_00024MyInnerClass,
+ &g_org_chromium_TestJni_00024MyInnerClass_clazz);
+}
+#endif
-// Step 3: RegisterNatives.
-
-static const JNINativeMethod kMethodsMyInnerClass[] = {
- { "nativeInit",
-"("
-")"
-"I",
- reinterpret_cast<void*>(Java_org_chromium_TestJni_00024MyInnerClass_nativeInit)
- },
-};
-static bool RegisterNativesImpl(JNIEnv* env) {
- if (jni_generator::ShouldSkipJniRegistration(false))
- return true;
+// Step 2: Constants (optional).
- const int kMethodsMyInnerClassSize = arraysize(kMethodsMyInnerClass);
- if (env->RegisterNatives(MyInnerClass_clazz(env),
- kMethodsMyInnerClass,
- kMethodsMyInnerClassSize) < 0) {
- jni_generator::HandleRegistrationError(
- env, MyInnerClass_clazz(env), __FILE__);
- return false;
- }
+// Step 3: Method stubs.
+static jint JNI_MyInnerClass_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller);
- return true;
+JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_00024MyInnerClass_nativeInit(
+ JNIEnv* env,
+ jobject jcaller) {
+ return JNI_MyInnerClass_Init(env, base::android::JavaParamRef<jobject>(env, jcaller));
}
+
#endif // org_chromium_TestJni_JNI
diff --git a/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden b/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden
index 67352e71c4..0623b37825 100644
--- a/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden
+++ b/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden
@@ -2,6 +2,7 @@
// 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
@@ -14,85 +15,56 @@
#include "base/android/jni_generator/jni_generator_helper.h"
-#include "base/android/jni_int_wrapper.h"
-// Step 1: forward declarations.
-namespace {
-const char kMyOtherInnerClassClassPath[] =
+// Step 1: Forward declarations.
+
+JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni_00024MyOtherInnerClass[];
+const char kClassPath_org_chromium_TestJni_00024MyOtherInnerClass[] =
"org/chromium/TestJni$MyOtherInnerClass";
-const char kTestJniClassPath[] = "org/chromium/TestJni";
+
+JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni[];
+const char kClassPath_org_chromium_TestJni[] = "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)
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_00024MyOtherInnerClass_clazz
+ = 0;
+#ifndef org_chromium_TestJni_00024MyOtherInnerClass_clazz_defined
+#define org_chromium_TestJni_00024MyOtherInnerClass_clazz_defined
+inline jclass org_chromium_TestJni_00024MyOtherInnerClass_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni_00024MyOtherInnerClass,
+ &g_org_chromium_TestJni_00024MyOtherInnerClass_clazz);
+}
+#endif
// 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)
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_clazz = 0;
+#ifndef org_chromium_TestJni_clazz_defined
+#define org_chromium_TestJni_clazz_defined
+inline jclass org_chromium_TestJni_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni,
+ &g_org_chromium_TestJni_clazz);
+}
+#endif
-} // namespace
-// Step 2: method stubs.
+// Step 2: Constants (optional).
-static jint Init(JNIEnv* env, const base::android::JavaParamRef<jobject>&
- jcaller);
-JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeInit(JNIEnv* env,
+// Step 3: Method stubs.
+static jint JNI_TestJni_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller);
+
+JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeInit(
+ JNIEnv* env,
jobject jcaller) {
- return Init(env, base::android::JavaParamRef<jobject>(env, jcaller));
+ return JNI_TestJni_Init(env, base::android::JavaParamRef<jobject>(env, jcaller));
}
-static jint Init(JNIEnv* env, const base::android::JavaParamRef<jobject>&
+static jint JNI_MyOtherInnerClass_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>&
jcaller);
-JNI_GENERATOR_EXPORT jint
- Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit(JNIEnv* env,
+JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit(
+ JNIEnv* env,
jobject jcaller) {
- return Init(env, base::android::JavaParamRef<jobject>(env, jcaller));
+ return JNI_MyOtherInnerClass_Init(env, base::android::JavaParamRef<jobject>(env, jcaller));
}
-// Step 3: RegisterNatives.
-
-static const JNINativeMethod kMethodsMyOtherInnerClass[] = {
- { "nativeInit",
-"("
-")"
-"I",
- reinterpret_cast<void*>(Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit)
- },
-};
-
-static const JNINativeMethod kMethodsTestJni[] = {
- { "nativeInit",
-"("
-")"
-"I", reinterpret_cast<void*>(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/testInnerClassNativesBothInnerAndOuterRegistrations.golden b/base/android/jni_generator/testInnerClassNativesBothInnerAndOuterRegistrations.golden
new file mode 100644
index 0000000000..3f91cf1fff
--- /dev/null
+++ b/base/android/jni_generator/testInnerClassNativesBothInnerAndOuterRegistrations.golden
@@ -0,0 +1,109 @@
+// 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.
+
+
+// This file is autogenerated by
+// base/android/jni_generator/jni_registration_generator.py
+// Please do not change its content.
+
+#ifndef HEADER_GUARD
+#define HEADER_GUARD
+
+#include <jni.h>
+
+#include "base/android/jni_generator/jni_generator_helper.h"
+#include "base/android/jni_int_wrapper.h"
+
+
+// Step 1: Forward declarations (classes).
+
+extern const char kClassPath_org_chromium_TestJni_00024MyOtherInnerClass[];
+
+extern const char kClassPath_org_chromium_TestJni[];
+extern base::subtle::AtomicWord g_org_chromium_TestJni_00024MyOtherInnerClass_clazz;
+#ifndef org_chromium_TestJni_00024MyOtherInnerClass_clazz_defined
+#define org_chromium_TestJni_00024MyOtherInnerClass_clazz_defined
+inline jclass org_chromium_TestJni_00024MyOtherInnerClass_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni_00024MyOtherInnerClass,
+ &g_org_chromium_TestJni_00024MyOtherInnerClass_clazz);
+}
+#endif
+extern base::subtle::AtomicWord g_org_chromium_TestJni_clazz;
+#ifndef org_chromium_TestJni_clazz_defined
+#define org_chromium_TestJni_clazz_defined
+inline jclass org_chromium_TestJni_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni,
+ &g_org_chromium_TestJni_clazz);
+}
+#endif
+
+
+// Step 2: Forward declarations (methods).
+
+JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeInit(
+ JNIEnv* env,
+ jobject jcaller);
+JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit(
+ JNIEnv* env,
+ jobject jcaller);
+
+
+// Step 3: Method declarations.
+
+static const JNINativeMethod kMethods_org_chromium_TestJni_00024MyOtherInnerClass[] = {
+ { "nativeInit", "()I",
+ reinterpret_cast<void*>(Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit) },
+};
+
+
+static const JNINativeMethod kMethods_org_chromium_TestJni[] = {
+ { "nativeInit", "()I", reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeInit) },
+};
+
+
+JNI_REGISTRATION_EXPORT bool RegisterNative_org_chromium_TestJni(JNIEnv* env) {
+ const int kMethods_org_chromium_TestJni_00024MyOtherInnerClassSize =
+ arraysize(kMethods_org_chromium_TestJni_00024MyOtherInnerClass);
+ if (env->RegisterNatives(
+ org_chromium_TestJni_00024MyOtherInnerClass_clazz(env),
+ kMethods_org_chromium_TestJni_00024MyOtherInnerClass,
+ kMethods_org_chromium_TestJni_00024MyOtherInnerClassSize) < 0) {
+ jni_generator::HandleRegistrationError(env,
+ org_chromium_TestJni_00024MyOtherInnerClass_clazz(env),
+ __FILE__);
+ return false;
+ }
+
+
+ const int kMethods_org_chromium_TestJniSize =
+ arraysize(kMethods_org_chromium_TestJni);
+ if (env->RegisterNatives(
+ org_chromium_TestJni_clazz(env),
+ kMethods_org_chromium_TestJni,
+ kMethods_org_chromium_TestJniSize) < 0) {
+ jni_generator::HandleRegistrationError(env,
+ org_chromium_TestJni_clazz(env),
+ __FILE__);
+ return false;
+ }
+
+ return true;
+}
+
+
+// Step 4: Main dex and non-main dex registration functions.
+
+bool RegisterMainDexNatives(JNIEnv* env) {
+ if (!RegisterNative_org_chromium_TestJni(env))
+ return false;
+
+ return true;
+}
+
+bool RegisterNonMainDexNatives(JNIEnv* env) {
+
+ return true;
+}
+
+#endif // HEADER_GUARD
diff --git a/base/android/jni_generator/testInnerClassNativesMultiple.golden b/base/android/jni_generator/testInnerClassNativesMultiple.golden
index 7807efa400..a7eff72ba0 100644
--- a/base/android/jni_generator/testInnerClassNativesMultiple.golden
+++ b/base/android/jni_generator/testInnerClassNativesMultiple.golden
@@ -2,6 +2,7 @@
// 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
@@ -14,92 +15,69 @@
#include "base/android/jni_generator/jni_generator_helper.h"
-#include "base/android/jni_int_wrapper.h"
-// Step 1: forward declarations.
-namespace {
-const char kMyOtherInnerClassClassPath[] =
+// Step 1: Forward declarations.
+
+JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni_00024MyOtherInnerClass[];
+const char kClassPath_org_chromium_TestJni_00024MyOtherInnerClass[] =
"org/chromium/TestJni$MyOtherInnerClass";
-const char kTestJniClassPath[] = "org/chromium/TestJni";
-const char kMyInnerClassClassPath[] = "org/chromium/TestJni$MyInnerClass";
+
+JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni[];
+const char kClassPath_org_chromium_TestJni[] = "org/chromium/TestJni";
+
+JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni_00024MyInnerClass[];
+const char kClassPath_org_chromium_TestJni_00024MyInnerClass[] =
+ "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)
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_00024MyOtherInnerClass_clazz
+ = 0;
+#ifndef org_chromium_TestJni_00024MyOtherInnerClass_clazz_defined
+#define org_chromium_TestJni_00024MyOtherInnerClass_clazz_defined
+inline jclass org_chromium_TestJni_00024MyOtherInnerClass_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni_00024MyOtherInnerClass,
+ &g_org_chromium_TestJni_00024MyOtherInnerClass_clazz);
+}
+#endif
// 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)
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_clazz = 0;
+#ifndef org_chromium_TestJni_clazz_defined
+#define org_chromium_TestJni_clazz_defined
+inline jclass org_chromium_TestJni_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni,
+ &g_org_chromium_TestJni_clazz);
+}
+#endif
// 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)
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_00024MyInnerClass_clazz = 0;
+#ifndef org_chromium_TestJni_00024MyInnerClass_clazz_defined
+#define org_chromium_TestJni_00024MyInnerClass_clazz_defined
+inline jclass org_chromium_TestJni_00024MyInnerClass_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni_00024MyInnerClass,
+ &g_org_chromium_TestJni_00024MyInnerClass_clazz);
+}
+#endif
-} // namespace
-// Step 2: method stubs.
+// Step 2: Constants (optional).
-static jint Init(JNIEnv* env, const base::android::JavaParamRef<jobject>&
- jcaller);
-JNI_GENERATOR_EXPORT jint
- Java_org_chromium_TestJni_00024MyInnerClass_nativeInit(JNIEnv* env, jobject
- jcaller) {
- return Init(env, base::android::JavaParamRef<jobject>(env, jcaller));
+// Step 3: Method stubs.
+static jint JNI_MyInnerClass_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller);
+
+JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_00024MyInnerClass_nativeInit(
+ JNIEnv* env,
+ jobject jcaller) {
+ return JNI_MyInnerClass_Init(env, base::android::JavaParamRef<jobject>(env, jcaller));
}
-static jint Init(JNIEnv* env, const base::android::JavaParamRef<jobject>&
+static jint JNI_MyOtherInnerClass_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>&
jcaller);
-JNI_GENERATOR_EXPORT jint
- Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit(JNIEnv* env,
+JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit(
+ JNIEnv* env,
jobject jcaller) {
- return Init(env, base::android::JavaParamRef<jobject>(env, jcaller));
+ return JNI_MyOtherInnerClass_Init(env, base::android::JavaParamRef<jobject>(env, jcaller));
}
-// Step 3: RegisterNatives.
-
-static const JNINativeMethod kMethodsMyOtherInnerClass[] = {
- { "nativeInit",
-"("
-")"
-"I",
- reinterpret_cast<void*>(Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit)
- },
-};
-
-static const JNINativeMethod kMethodsMyInnerClass[] = {
- { "nativeInit",
-"("
-")"
-"I",
- reinterpret_cast<void*>(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/testMultipleJNIAdditionalImport.golden b/base/android/jni_generator/testMultipleJNIAdditionalImport.golden
index 0eecb5aeb1..40f3d6beea 100644
--- a/base/android/jni_generator/testMultipleJNIAdditionalImport.golden
+++ b/base/android/jni_generator/testMultipleJNIAdditionalImport.golden
@@ -2,6 +2,7 @@
// 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
@@ -14,82 +15,56 @@
#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";
+// Step 1: Forward declarations.
+
+JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_foo_Foo[];
+const char kClassPath_org_chromium_foo_Foo[] = "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)
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_foo_Foo_clazz = 0;
+#ifndef org_chromium_foo_Foo_clazz_defined
+#define org_chromium_foo_Foo_clazz_defined
+inline jclass org_chromium_foo_Foo_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_org_chromium_foo_Foo,
+ &g_org_chromium_foo_Foo_clazz);
+}
+#endif
+
-} // namespace
+// Step 2: Constants (optional).
-// Step 2: method stubs.
-static void DoSomething(JNIEnv* env, const base::android::JavaParamRef<jclass>&
- jcaller,
+// Step 3: Method stubs.
+static void JNI_Foo_DoSomething(JNIEnv* env, const base::android::JavaParamRef<jclass>& jcaller,
const base::android::JavaParamRef<jobject>& callback1,
const base::android::JavaParamRef<jobject>& callback2);
-JNI_GENERATOR_EXPORT void Java_org_chromium_foo_Foo_nativeDoSomething(JNIEnv*
- env, jclass jcaller,
+JNI_GENERATOR_EXPORT void Java_org_chromium_foo_Foo_nativeDoSomething(
+ JNIEnv* env,
+ jclass jcaller,
jobject callback1,
jobject callback2) {
- return DoSomething(env, base::android::JavaParamRef<jclass>(env, jcaller),
+ return JNI_Foo_DoSomething(env, base::android::JavaParamRef<jclass>(env, jcaller),
base::android::JavaParamRef<jobject>(env, callback1),
base::android::JavaParamRef<jobject>(env, callback2));
}
-static base::subtle::AtomicWord g_Foo_calledByNative = 0;
-static void Java_Foo_calledByNative(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& callback1,
- const base::android::JavaRefOrBare<jobject>& callback2) {
- CHECK_CLAZZ(env, Foo_clazz(env),
- Foo_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+
+static base::subtle::AtomicWord g_org_chromium_foo_Foo_calledByNative = 0;
+static void Java_Foo_calledByNative(JNIEnv* env, const base::android::JavaRef<jobject>& callback1,
+ const base::android::JavaRef<jobject>& callback2) {
+ CHECK_CLAZZ(env, org_chromium_foo_Foo_clazz(env),
+ org_chromium_foo_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),
+ env, org_chromium_foo_Foo_clazz(env),
+ "calledByNative",
+ "(Lorg/chromium/foo/Bar1$Callback;Lorg/chromium/foo/Bar2$Callback;)V",
+ &g_org_chromium_foo_Foo_calledByNative);
+
+ env->CallStaticVoidMethod(org_chromium_foo_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<void*>(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
index 3362c92841..1178f19a0e 100644
--- a/base/android/jni_generator/testNatives.golden
+++ b/base/android/jni_generator/testNatives.golden
@@ -2,6 +2,7 @@
// 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
@@ -14,39 +15,47 @@
#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";
+// Step 1: Forward declarations.
+
+JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni[];
+const char kClassPath_org_chromium_TestJni[] = "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)
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_clazz = 0;
+#ifndef org_chromium_TestJni_clazz_defined
+#define org_chromium_TestJni_clazz_defined
+inline jclass org_chromium_TestJni_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni,
+ &g_org_chromium_TestJni_clazz);
+}
+#endif
+
-} // namespace
+// Step 2: Constants (optional).
-// Step 2: method stubs.
-static jint Init(JNIEnv* env, const base::android::JavaParamRef<jobject>&
- jcaller);
+// Step 3: Method stubs.
+static jint JNI_TestJni_Init(JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller);
-JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeInit(JNIEnv* env,
+JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeInit(
+ JNIEnv* env,
jobject jcaller) {
- return Init(env, base::android::JavaParamRef<jobject>(env, jcaller));
+ return JNI_TestJni_Init(env, base::android::JavaParamRef<jobject>(env, jcaller));
}
-JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeDestroy(JNIEnv* env,
+JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeDestroy(
+ JNIEnv* env,
jobject jcaller,
jint nativeChromeBrowserProvider) {
ChromeBrowserProvider* native =
reinterpret_cast<ChromeBrowserProvider*>(nativeChromeBrowserProvider);
CHECK_NATIVE_PTR(env, jcaller, native, "Destroy");
- return native->Destroy(env, base::android::JavaParamRef<jobject>(env,
- jcaller));
+ return native->Destroy(env, base::android::JavaParamRef<jobject>(env, jcaller));
}
-JNI_GENERATOR_EXPORT jlong Java_org_chromium_TestJni_nativeAddBookmark(JNIEnv*
- env, jobject jcaller,
+JNI_GENERATOR_EXPORT jlong Java_org_chromium_TestJni_nativeAddBookmark(
+ JNIEnv* env,
+ jobject jcaller,
jint nativeChromeBrowserProvider,
jstring url,
jstring title,
@@ -55,79 +64,76 @@ JNI_GENERATOR_EXPORT jlong Java_org_chromium_TestJni_nativeAddBookmark(JNIEnv*
ChromeBrowserProvider* native =
reinterpret_cast<ChromeBrowserProvider*>(nativeChromeBrowserProvider);
CHECK_NATIVE_PTR(env, jcaller, native, "AddBookmark", 0);
- return native->AddBookmark(env, base::android::JavaParamRef<jobject>(env,
- jcaller), base::android::JavaParamRef<jstring>(env, url),
- base::android::JavaParamRef<jstring>(env, title), isFolder, parentId);
+ return native->AddBookmark(env, base::android::JavaParamRef<jobject>(env, jcaller),
+ base::android::JavaParamRef<jstring>(env, url), base::android::JavaParamRef<jstring>(env,
+ title), isFolder, parentId);
}
-static base::android::ScopedJavaLocalRef<jstring> GetDomainAndRegistry(JNIEnv*
- env, const base::android::JavaParamRef<jclass>& jcaller,
+static base::android::ScopedJavaLocalRef<jstring> JNI_TestJni_GetDomainAndRegistry(JNIEnv* env,
+ const base::android::JavaParamRef<jclass>& jcaller,
const base::android::JavaParamRef<jstring>& url);
-JNI_GENERATOR_EXPORT jstring
- Java_org_chromium_TestJni_nativeGetDomainAndRegistry(JNIEnv* env, jclass
- jcaller,
+JNI_GENERATOR_EXPORT jstring Java_org_chromium_TestJni_nativeGetDomainAndRegistry(
+ JNIEnv* env,
+ jclass jcaller,
jstring url) {
- return GetDomainAndRegistry(env, base::android::JavaParamRef<jclass>(env,
- jcaller), base::android::JavaParamRef<jstring>(env, url)).Release();
+ return JNI_TestJni_GetDomainAndRegistry(env, base::android::JavaParamRef<jclass>(env, jcaller),
+ base::android::JavaParamRef<jstring>(env, url)).Release();
}
-static void CreateHistoricalTabFromState(JNIEnv* env, const
+static void JNI_TestJni_CreateHistoricalTabFromState(JNIEnv* env, const
base::android::JavaParamRef<jclass>& jcaller,
const base::android::JavaParamRef<jbyteArray>& state,
jint tab_index);
-JNI_GENERATOR_EXPORT void
- Java_org_chromium_TestJni_nativeCreateHistoricalTabFromState(JNIEnv* env,
+JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeCreateHistoricalTabFromState(
+ JNIEnv* env,
jclass jcaller,
jbyteArray state,
jint tab_index) {
- return CreateHistoricalTabFromState(env,
- base::android::JavaParamRef<jclass>(env, jcaller),
- base::android::JavaParamRef<jbyteArray>(env, state), tab_index);
+ return JNI_TestJni_CreateHistoricalTabFromState(env, base::android::JavaParamRef<jclass>(env,
+ jcaller), base::android::JavaParamRef<jbyteArray>(env, state), tab_index);
}
-static base::android::ScopedJavaLocalRef<jbyteArray> GetStateAsByteArray(JNIEnv*
- env, const base::android::JavaParamRef<jobject>& jcaller,
+static base::android::ScopedJavaLocalRef<jbyteArray> JNI_TestJni_GetStateAsByteArray(JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jobject>& view);
-JNI_GENERATOR_EXPORT jbyteArray
- Java_org_chromium_TestJni_nativeGetStateAsByteArray(JNIEnv* env, jobject
- jcaller,
+JNI_GENERATOR_EXPORT jbyteArray Java_org_chromium_TestJni_nativeGetStateAsByteArray(
+ JNIEnv* env,
+ jobject jcaller,
jobject view) {
- return GetStateAsByteArray(env, base::android::JavaParamRef<jobject>(env,
- jcaller), base::android::JavaParamRef<jobject>(env, view)).Release();
+ return JNI_TestJni_GetStateAsByteArray(env, base::android::JavaParamRef<jobject>(env, jcaller),
+ base::android::JavaParamRef<jobject>(env, view)).Release();
}
-static base::android::ScopedJavaLocalRef<jobjectArray>
- GetAutofillProfileGUIDs(JNIEnv* env, const
- base::android::JavaParamRef<jclass>& jcaller);
+static base::android::ScopedJavaLocalRef<jobjectArray> JNI_TestJni_GetAutofillProfileGUIDs(JNIEnv*
+ env, const base::android::JavaParamRef<jclass>& jcaller);
-JNI_GENERATOR_EXPORT jobjectArray
- Java_org_chromium_TestJni_nativeGetAutofillProfileGUIDs(JNIEnv* env, jclass
- jcaller) {
- return GetAutofillProfileGUIDs(env, base::android::JavaParamRef<jclass>(env,
+JNI_GENERATOR_EXPORT jobjectArray Java_org_chromium_TestJni_nativeGetAutofillProfileGUIDs(
+ JNIEnv* env,
+ jclass jcaller) {
+ return JNI_TestJni_GetAutofillProfileGUIDs(env, base::android::JavaParamRef<jclass>(env,
jcaller)).Release();
}
-static void SetRecognitionResults(JNIEnv* env, const
+static void JNI_TestJni_SetRecognitionResults(JNIEnv* env, const
base::android::JavaParamRef<jobject>& jcaller,
jint sessionId,
const base::android::JavaParamRef<jobjectArray>& results);
-JNI_GENERATOR_EXPORT void
- Java_org_chromium_TestJni_nativeSetRecognitionResults(JNIEnv* env, jobject
- jcaller,
+JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeSetRecognitionResults(
+ JNIEnv* env,
+ jobject jcaller,
jint sessionId,
jobjectArray results) {
- return SetRecognitionResults(env, base::android::JavaParamRef<jobject>(env,
- jcaller), sessionId, base::android::JavaParamRef<jobjectArray>(env,
- results));
+ return JNI_TestJni_SetRecognitionResults(env, base::android::JavaParamRef<jobject>(env, jcaller),
+ sessionId, base::android::JavaParamRef<jobjectArray>(env, results));
}
-JNI_GENERATOR_EXPORT jlong
- Java_org_chromium_TestJni_nativeAddBookmarkFromAPI(JNIEnv* env, jobject
- jcaller,
+JNI_GENERATOR_EXPORT jlong Java_org_chromium_TestJni_nativeAddBookmarkFromAPI(
+ JNIEnv* env,
+ jobject jcaller,
jint nativeChromeBrowserProvider,
jstring url,
jobject created,
@@ -139,39 +145,38 @@ JNI_GENERATOR_EXPORT jlong
ChromeBrowserProvider* native =
reinterpret_cast<ChromeBrowserProvider*>(nativeChromeBrowserProvider);
CHECK_NATIVE_PTR(env, jcaller, native, "AddBookmarkFromAPI", 0);
- return native->AddBookmarkFromAPI(env,
- base::android::JavaParamRef<jobject>(env, jcaller),
- base::android::JavaParamRef<jstring>(env, url),
- base::android::JavaParamRef<jobject>(env, created),
- base::android::JavaParamRef<jobject>(env, isBookmark),
- base::android::JavaParamRef<jobject>(env, date),
- base::android::JavaParamRef<jbyteArray>(env, favicon),
- base::android::JavaParamRef<jstring>(env, title),
+ return native->AddBookmarkFromAPI(env, base::android::JavaParamRef<jobject>(env, jcaller),
+ base::android::JavaParamRef<jstring>(env, url), base::android::JavaParamRef<jobject>(env,
+ created), base::android::JavaParamRef<jobject>(env, isBookmark),
+ base::android::JavaParamRef<jobject>(env, date), base::android::JavaParamRef<jbyteArray>(env,
+ favicon), base::android::JavaParamRef<jstring>(env, title),
base::android::JavaParamRef<jobject>(env, visits));
}
-static jint FindAll(JNIEnv* env, const base::android::JavaParamRef<jobject>&
- jcaller,
+static jint JNI_TestJni_FindAll(JNIEnv* env, const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jstring>& find);
-JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeFindAll(JNIEnv* env,
+JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeFindAll(
+ JNIEnv* env,
jobject jcaller,
jstring find) {
- return FindAll(env, base::android::JavaParamRef<jobject>(env, jcaller),
+ return JNI_TestJni_FindAll(env, base::android::JavaParamRef<jobject>(env, jcaller),
base::android::JavaParamRef<jstring>(env, find));
}
-static base::android::ScopedJavaLocalRef<jobject> GetInnerClass(JNIEnv* env,
- const base::android::JavaParamRef<jclass>& jcaller);
+static base::android::ScopedJavaLocalRef<jobject> JNI_TestJni_GetInnerClass(JNIEnv* env, const
+ base::android::JavaParamRef<jclass>& jcaller);
-JNI_GENERATOR_EXPORT jobject
- Java_org_chromium_TestJni_nativeGetInnerClass(JNIEnv* env, jclass jcaller) {
- return GetInnerClass(env, base::android::JavaParamRef<jclass>(env,
+JNI_GENERATOR_EXPORT jobject Java_org_chromium_TestJni_nativeGetInnerClass(
+ JNIEnv* env,
+ jclass jcaller) {
+ return JNI_TestJni_GetInnerClass(env, base::android::JavaParamRef<jclass>(env,
jcaller)).Release();
}
-JNI_GENERATOR_EXPORT jobject Java_org_chromium_TestJni_nativeQueryBitmap(JNIEnv*
- env, jobject jcaller,
+JNI_GENERATOR_EXPORT jobject Java_org_chromium_TestJni_nativeQueryBitmap(
+ JNIEnv* env,
+ jobject jcaller,
jint nativeChromeBrowserProvider,
jobjectArray projection,
jstring selection,
@@ -180,15 +185,16 @@ JNI_GENERATOR_EXPORT jobject Java_org_chromium_TestJni_nativeQueryBitmap(JNIEnv*
ChromeBrowserProvider* native =
reinterpret_cast<ChromeBrowserProvider*>(nativeChromeBrowserProvider);
CHECK_NATIVE_PTR(env, jcaller, native, "QueryBitmap", NULL);
- return native->QueryBitmap(env, base::android::JavaParamRef<jobject>(env,
- jcaller), base::android::JavaParamRef<jobjectArray>(env, projection),
+ return native->QueryBitmap(env, base::android::JavaParamRef<jobject>(env, jcaller),
+ base::android::JavaParamRef<jobjectArray>(env, projection),
base::android::JavaParamRef<jstring>(env, selection),
base::android::JavaParamRef<jobjectArray>(env, selectionArgs),
base::android::JavaParamRef<jstring>(env, sortOrder)).Release();
}
-JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeGotOrientation(JNIEnv*
- env, jobject jcaller,
+JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeGotOrientation(
+ JNIEnv* env,
+ jobject jcaller,
jint nativeDataFetcherImplAndroid,
jdouble alpha,
jdouble beta,
@@ -196,145 +202,21 @@ JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeGotOrientation(JNIEnv*
DataFetcherImplAndroid* native =
reinterpret_cast<DataFetcherImplAndroid*>(nativeDataFetcherImplAndroid);
CHECK_NATIVE_PTR(env, jcaller, native, "GotOrientation");
- return native->GotOrientation(env, base::android::JavaParamRef<jobject>(env,
- jcaller), alpha, beta, gamma);
+ return native->GotOrientation(env, base::android::JavaParamRef<jobject>(env, jcaller), alpha,
+ beta, gamma);
}
-static base::android::ScopedJavaLocalRef<jthrowable>
- MessWithJavaException(JNIEnv* env, const
- base::android::JavaParamRef<jclass>& jcaller,
+static base::android::ScopedJavaLocalRef<jthrowable> JNI_TestJni_MessWithJavaException(JNIEnv* env,
+ const base::android::JavaParamRef<jclass>& jcaller,
const base::android::JavaParamRef<jthrowable>& e);
-JNI_GENERATOR_EXPORT jthrowable
- Java_org_chromium_TestJni_nativeMessWithJavaException(JNIEnv* env, jclass
- jcaller,
+JNI_GENERATOR_EXPORT jthrowable Java_org_chromium_TestJni_nativeMessWithJavaException(
+ JNIEnv* env,
+ jclass jcaller,
jthrowable e) {
- return MessWithJavaException(env, base::android::JavaParamRef<jclass>(env,
- jcaller), base::android::JavaParamRef<jthrowable>(env, e)).Release();
+ return JNI_TestJni_MessWithJavaException(env, base::android::JavaParamRef<jclass>(env, jcaller),
+ base::android::JavaParamRef<jthrowable>(env, e)).Release();
}
-// Step 3: RegisterNatives.
-
-static const JNINativeMethod kMethodsTestJni[] = {
- { "nativeInit",
-"("
-")"
-"I", reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeInit) },
- { "nativeDestroy",
-"("
-"I"
-")"
-"V", reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeDestroy) },
- { "nativeAddBookmark",
-"("
-"I"
-"Ljava/lang/String;"
-"Ljava/lang/String;"
-"Z"
-"J"
-")"
-"J", reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeAddBookmark) },
- { "nativeGetDomainAndRegistry",
-"("
-"Ljava/lang/String;"
-")"
-"Ljava/lang/String;",
- reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeGetDomainAndRegistry)
- },
- { "nativeCreateHistoricalTabFromState",
-"("
-"[B"
-"I"
-")"
-"V",
- reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeCreateHistoricalTabFromState)
- },
- { "nativeGetStateAsByteArray",
-"("
-"Landroid/view/View;"
-")"
-"[B",
- reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeGetStateAsByteArray)
- },
- { "nativeGetAutofillProfileGUIDs",
-"("
-")"
-"[Ljava/lang/String;",
- reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeGetAutofillProfileGUIDs)
- },
- { "nativeSetRecognitionResults",
-"("
-"I"
-"[Ljava/lang/String;"
-")"
-"V",
- reinterpret_cast<void*>(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<void*>(Java_org_chromium_TestJni_nativeAddBookmarkFromAPI)
- },
- { "nativeFindAll",
-"("
-"Ljava/lang/String;"
-")"
-"I", reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeFindAll) },
- { "nativeGetInnerClass",
-"("
-")"
-"Lorg/chromium/example/jni_generator/SampleForTests$OnFrameAvailableListener;",
- reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeGetInnerClass) },
- { "nativeQueryBitmap",
-"("
-"I"
-"[Ljava/lang/String;"
-"Ljava/lang/String;"
-"[Ljava/lang/String;"
-"Ljava/lang/String;"
-")"
-"Landroid/graphics/Bitmap;",
- reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeQueryBitmap) },
- { "nativeGotOrientation",
-"("
-"I"
-"D"
-"D"
-"D"
-")"
-"V", reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeGotOrientation) },
- { "nativeMessWithJavaException",
-"("
-"Ljava/lang/Throwable;"
-")"
-"Ljava/lang/Throwable;",
- reinterpret_cast<void*>(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
index ec029ce306..b3115eff28 100644
--- a/base/android/jni_generator/testNativesLong.golden
+++ b/base/android/jni_generator/testNativesLong.golden
@@ -2,6 +2,7 @@
// 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
@@ -14,53 +15,35 @@
#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";
+// Step 1: Forward declarations.
+
+JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni[];
+const char kClassPath_org_chromium_TestJni[] = "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)
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_clazz = 0;
+#ifndef org_chromium_TestJni_clazz_defined
+#define org_chromium_TestJni_clazz_defined
+inline jclass org_chromium_TestJni_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni,
+ &g_org_chromium_TestJni_clazz);
+}
+#endif
-} // namespace
-// Step 2: method stubs.
-JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeDestroy(JNIEnv* env,
+// Step 2: Constants (optional).
+
+
+// Step 3: Method stubs.
+JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeDestroy(
+ JNIEnv* env,
jobject jcaller,
jlong nativeChromeBrowserProvider) {
ChromeBrowserProvider* native =
reinterpret_cast<ChromeBrowserProvider*>(nativeChromeBrowserProvider);
CHECK_NATIVE_PTR(env, jcaller, native, "Destroy");
- return native->Destroy(env, base::android::JavaParamRef<jobject>(env,
- jcaller));
+ return native->Destroy(env, base::android::JavaParamRef<jobject>(env, jcaller));
}
-// Step 3: RegisterNatives.
-
-static const JNINativeMethod kMethodsTestJni[] = {
- { "nativeDestroy",
-"("
-"J"
-")"
-"V", reinterpret_cast<void*>(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/testNativesRegistrations.golden b/base/android/jni_generator/testNativesRegistrations.golden
new file mode 100644
index 0000000000..1dae786aae
--- /dev/null
+++ b/base/android/jni_generator/testNativesRegistrations.golden
@@ -0,0 +1,175 @@
+// 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.
+
+
+// This file is autogenerated by
+// base/android/jni_generator/jni_registration_generator.py
+// Please do not change its content.
+
+#ifndef HEADER_GUARD
+#define HEADER_GUARD
+
+#include <jni.h>
+
+#include "base/android/jni_generator/jni_generator_helper.h"
+#include "base/android/jni_int_wrapper.h"
+
+
+// Step 1: Forward declarations (classes).
+
+extern const char kClassPath_org_chromium_TestJni[];
+extern base::subtle::AtomicWord g_org_chromium_TestJni_clazz;
+#ifndef org_chromium_TestJni_clazz_defined
+#define org_chromium_TestJni_clazz_defined
+inline jclass org_chromium_TestJni_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_org_chromium_TestJni,
+ &g_org_chromium_TestJni_clazz);
+}
+#endif
+
+
+// Step 2: Forward declarations (methods).
+
+JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeInit(
+ JNIEnv* env,
+ jobject jcaller);
+JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeDestroy(
+ JNIEnv* env,
+ jobject jcaller,
+ jint nativeChromeBrowserProvider);
+JNI_GENERATOR_EXPORT jlong Java_org_chromium_TestJni_nativeAddBookmark(
+ JNIEnv* env,
+ jobject jcaller,
+ jint nativeChromeBrowserProvider,
+ jstring url,
+ jstring title,
+ jboolean isFolder,
+ jlong parentId);
+JNI_GENERATOR_EXPORT jstring Java_org_chromium_TestJni_nativeGetDomainAndRegistry(
+ JNIEnv* env,
+ jclass jcaller,
+ jstring url);
+JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeCreateHistoricalTabFromState(
+ JNIEnv* env,
+ jclass jcaller,
+ jbyteArray state,
+ jint tab_index);
+JNI_GENERATOR_EXPORT jbyteArray Java_org_chromium_TestJni_nativeGetStateAsByteArray(
+ JNIEnv* env,
+ jobject jcaller,
+ jobject view);
+JNI_GENERATOR_EXPORT jobjectArray Java_org_chromium_TestJni_nativeGetAutofillProfileGUIDs(
+ JNIEnv* env,
+ jclass jcaller);
+JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeSetRecognitionResults(
+ JNIEnv* env,
+ jobject jcaller,
+ jint sessionId,
+ jobjectArray 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);
+JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeFindAll(
+ JNIEnv* env,
+ jobject jcaller,
+ jstring find);
+JNI_GENERATOR_EXPORT jobject Java_org_chromium_TestJni_nativeGetInnerClass(
+ JNIEnv* env,
+ jclass jcaller);
+JNI_GENERATOR_EXPORT jobject Java_org_chromium_TestJni_nativeQueryBitmap(
+ JNIEnv* env,
+ jobject jcaller,
+ jint nativeChromeBrowserProvider,
+ jobjectArray projection,
+ jstring selection,
+ jobjectArray selectionArgs,
+ jstring sortOrder);
+JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeGotOrientation(
+ JNIEnv* env,
+ jobject jcaller,
+ jint nativeDataFetcherImplAndroid,
+ jdouble alpha,
+ jdouble beta,
+ jdouble gamma);
+JNI_GENERATOR_EXPORT jthrowable Java_org_chromium_TestJni_nativeMessWithJavaException(
+ JNIEnv* env,
+ jclass jcaller,
+ jthrowable e);
+
+
+// Step 3: Method declarations.
+
+static const JNINativeMethod kMethods_org_chromium_TestJni[] = {
+ { "nativeInit", "()I", reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeInit) },
+ { "nativeDestroy", "(I)V", reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeDestroy) },
+ { "nativeAddBookmark", "(ILjava/lang/String;Ljava/lang/String;ZJ)J",
+ reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeAddBookmark) },
+ { "nativeGetDomainAndRegistry", "(Ljava/lang/String;)Ljava/lang/String;",
+ reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeGetDomainAndRegistry) },
+ { "nativeCreateHistoricalTabFromState", "([BI)V",
+ reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeCreateHistoricalTabFromState) },
+ { "nativeGetStateAsByteArray", "(Landroid/view/View;)[B",
+ reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeGetStateAsByteArray) },
+ { "nativeGetAutofillProfileGUIDs", "()[Ljava/lang/String;",
+ reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeGetAutofillProfileGUIDs) },
+ { "nativeSetRecognitionResults", "(I[Ljava/lang/String;)V",
+ reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeSetRecognitionResults) },
+ { "nativeAddBookmarkFromAPI",
+ "(ILjava/lang/String;Ljava/lang/Long;Ljava/lang/Boolean;Ljava/lang/Long;[BLjava/lang/String;Ljava/lang/Integer;)J",
+ reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeAddBookmarkFromAPI) },
+ { "nativeFindAll", "(Ljava/lang/String;)I",
+ reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeFindAll) },
+ { "nativeGetInnerClass",
+ "()Lorg/chromium/example/jni_generator/SampleForTests$OnFrameAvailableListener;",
+ reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeGetInnerClass) },
+ { "nativeQueryBitmap",
+ "(I[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/graphics/Bitmap;",
+ reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeQueryBitmap) },
+ { "nativeGotOrientation", "(IDDD)V",
+ reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeGotOrientation) },
+ { "nativeMessWithJavaException", "(Ljava/lang/Throwable;)Ljava/lang/Throwable;",
+ reinterpret_cast<void*>(Java_org_chromium_TestJni_nativeMessWithJavaException) },
+};
+
+
+JNI_REGISTRATION_EXPORT bool RegisterNative_org_chromium_TestJni(JNIEnv* env) {
+ const int kMethods_org_chromium_TestJniSize =
+ arraysize(kMethods_org_chromium_TestJni);
+ if (env->RegisterNatives(
+ org_chromium_TestJni_clazz(env),
+ kMethods_org_chromium_TestJni,
+ kMethods_org_chromium_TestJniSize) < 0) {
+ jni_generator::HandleRegistrationError(env,
+ org_chromium_TestJni_clazz(env),
+ __FILE__);
+ return false;
+ }
+
+ return true;
+}
+
+
+// Step 4: Main dex and non-main dex registration functions.
+
+bool RegisterMainDexNatives(JNIEnv* env) {
+ if (!RegisterNative_org_chromium_TestJni(env))
+ return false;
+
+ return true;
+}
+
+bool RegisterNonMainDexNatives(JNIEnv* env) {
+
+ return true;
+}
+
+#endif // HEADER_GUARD
diff --git a/base/android/jni_generator/testSingleJNIAdditionalImport.golden b/base/android/jni_generator/testSingleJNIAdditionalImport.golden
index ef618da80a..4b3eccdc94 100644
--- a/base/android/jni_generator/testSingleJNIAdditionalImport.golden
+++ b/base/android/jni_generator/testSingleJNIAdditionalImport.golden
@@ -2,6 +2,7 @@
// 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
@@ -14,76 +15,52 @@
#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";
+// Step 1: Forward declarations.
+
+JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_foo_Foo[];
+const char kClassPath_org_chromium_foo_Foo[] = "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)
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_foo_Foo_clazz = 0;
+#ifndef org_chromium_foo_Foo_clazz_defined
+#define org_chromium_foo_Foo_clazz_defined
+inline jclass org_chromium_foo_Foo_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_org_chromium_foo_Foo,
+ &g_org_chromium_foo_Foo_clazz);
+}
+#endif
+
-} // namespace
+// Step 2: Constants (optional).
-// Step 2: method stubs.
-static void DoSomething(JNIEnv* env, const base::android::JavaParamRef<jclass>&
- jcaller,
+// Step 3: Method stubs.
+static void JNI_Foo_DoSomething(JNIEnv* env, const base::android::JavaParamRef<jclass>& jcaller,
const base::android::JavaParamRef<jobject>& callback);
-JNI_GENERATOR_EXPORT void Java_org_chromium_foo_Foo_nativeDoSomething(JNIEnv*
- env, jclass jcaller,
+JNI_GENERATOR_EXPORT void Java_org_chromium_foo_Foo_nativeDoSomething(
+ JNIEnv* env,
+ jclass jcaller,
jobject callback) {
- return DoSomething(env, base::android::JavaParamRef<jclass>(env, jcaller),
+ return JNI_Foo_DoSomething(env, base::android::JavaParamRef<jclass>(env, jcaller),
base::android::JavaParamRef<jobject>(env, callback));
}
-static base::subtle::AtomicWord g_Foo_calledByNative = 0;
-static void Java_Foo_calledByNative(JNIEnv* env, const
- base::android::JavaRefOrBare<jobject>& callback) {
- CHECK_CLAZZ(env, Foo_clazz(env),
- Foo_clazz(env));
- jmethodID method_id =
- base::android::MethodID::LazyGet<
+
+static base::subtle::AtomicWord g_org_chromium_foo_Foo_calledByNative = 0;
+static void Java_Foo_calledByNative(JNIEnv* env, const base::android::JavaRef<jobject>& callback) {
+ CHECK_CLAZZ(env, org_chromium_foo_Foo_clazz(env),
+ org_chromium_foo_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),
+ env, org_chromium_foo_Foo_clazz(env),
+ "calledByNative",
+ "(Lorg/chromium/foo/Bar$Callback;)V",
+ &g_org_chromium_foo_Foo_calledByNative);
+
+ env->CallStaticVoidMethod(org_chromium_foo_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<void*>(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/testTracing.golden b/base/android/jni_generator/testTracing.golden
new file mode 100644
index 0000000000..97010278bc
--- /dev/null
+++ b/base/android/jni_generator/testTracing.golden
@@ -0,0 +1,99 @@
+// 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 <jni.h>
+
+#include "base/android/jni_generator/jni_generator_helper.h"
+
+
+// Step 1: Forward declarations.
+
+JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_foo_Foo[];
+const char kClassPath_org_chromium_foo_Foo[] = "org/chromium/foo/Foo";
+// Leaking this jclass as we cannot use LazyInstance from some threads.
+JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_foo_Foo_clazz = 0;
+#ifndef org_chromium_foo_Foo_clazz_defined
+#define org_chromium_foo_Foo_clazz_defined
+inline jclass org_chromium_foo_Foo_clazz(JNIEnv* env) {
+ return base::android::LazyGetClass(env, kClassPath_org_chromium_foo_Foo,
+ &g_org_chromium_foo_Foo_clazz);
+}
+#endif
+
+
+// Step 2: Constants (optional).
+
+
+// Step 3: Method stubs.
+namespace org {
+namespace chromium_foo {
+
+JNI_GENERATOR_EXPORT void Java_org_chromium_foo_Foo_nativeInstanceMethod(
+ JNIEnv* env,
+ jobject jcaller,
+ jlong nativeInstance) {
+ TRACE_EVENT0("jni", "org::chromium_foo::Instance::InstanceMethod"); Instance* native =
+ reinterpret_cast<Instance*>(nativeInstance);
+ CHECK_NATIVE_PTR(env, jcaller, native, "InstanceMethod");
+ return native->InstanceMethod(env, base::android::JavaParamRef<jobject>(env, jcaller));
+}
+
+static void JNI_Foo_StaticMethod(JNIEnv* env, const base::android::JavaParamRef<jclass>& jcaller);
+
+JNI_GENERATOR_EXPORT void Java_org_chromium_foo_Foo_nativeStaticMethod(
+ JNIEnv* env,
+ jclass jcaller) {
+ TRACE_EVENT0("jni", "org::chromium_foo::JNI_Foo_StaticMethod"); return JNI_Foo_StaticMethod(env,
+ base::android::JavaParamRef<jclass>(env, jcaller));
+}
+
+
+static base::subtle::AtomicWord g_org_chromium_foo_Foo_Constructor = 0;
+static base::android::ScopedJavaLocalRef<jobject> Java_Foo_Constructor(JNIEnv* env) {
+ CHECK_CLAZZ(env, org_chromium_foo_Foo_clazz(env),
+ org_chromium_foo_Foo_clazz(env), NULL);
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_INSTANCE>(
+ env, org_chromium_foo_Foo_clazz(env),
+ "<init>",
+ "()V",
+ &g_org_chromium_foo_Foo_Constructor);
+
+ TRACE_EVENT0("jni", "org.chromium.foo.Foo.<init>"); jobject ret =
+ env->NewObject(org_chromium_foo_Foo_clazz(env),
+ method_id);
+ jni_generator::CheckException(env);
+ return base::android::ScopedJavaLocalRef<jobject>(env, ret);
+}
+
+static base::subtle::AtomicWord g_org_chromium_foo_Foo_callbackFromNative = 0;
+static void Java_Foo_callbackFromNative(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
+ CHECK_CLAZZ(env, obj.obj(),
+ org_chromium_foo_Foo_clazz(env));
+ jmethodID method_id = base::android::MethodID::LazyGet<
+ base::android::MethodID::TYPE_INSTANCE>(
+ env, org_chromium_foo_Foo_clazz(env),
+ "callbackFromNative",
+ "()V",
+ &g_org_chromium_foo_Foo_callbackFromNative);
+
+ TRACE_EVENT0("jni", "org.chromium.foo.Foo.callbackFromNative");
+ env->CallVoidMethod(obj.obj(),
+ method_id);
+ jni_generator::CheckException(env);
+}
+
+} // namespace chromium_foo
+} // namespace org
+
+#endif // org_chromium_foo_Foo_JNI
diff --git a/base/android/junit/src/org/chromium/base/DiscardableReferencePoolTest.java b/base/android/junit/src/org/chromium/base/DiscardableReferencePoolTest.java
new file mode 100644
index 0000000000..5eff6c9488
--- /dev/null
+++ b/base/android/junit/src/org/chromium/base/DiscardableReferencePoolTest.java
@@ -0,0 +1,80 @@
+// 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.
+
+package org.chromium.base;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.DiscardableReferencePool.DiscardableReference;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.RetryOnFailure;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Tests for {@link DiscardableReferencePool}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class DiscardableReferencePoolTest {
+ /**
+ * Tests that draining the pool clears references and allows objects to be garbage collected.
+ */
+ @Test
+ public void testDrain() {
+ DiscardableReferencePool pool = new DiscardableReferencePool();
+
+ Object object = new Object();
+ WeakReference<Object> weakReference = new WeakReference<>(object);
+
+ DiscardableReference<Object> discardableReference = pool.put(object);
+ Assert.assertEquals(object, discardableReference.get());
+
+ // Drop reference to the object itself, to allow it to be garbage-collected.
+ object = null;
+
+ pool.drain();
+
+ // The discardable reference should be null now.
+ Assert.assertNull(discardableReference.get());
+
+ // The object is not (strongly) reachable anymore, so the weak reference may or may not be
+ // null (it could be if a GC has happened since the pool was drained).
+ // After an explicit GC call it definitely should be null.
+ Runtime.getRuntime().gc();
+
+ Assert.assertNull(weakReference.get());
+ }
+
+ /**
+ * Tests that dropping the (last) discardable reference to an object allows it to be regularly
+ * garbage collected.
+ */
+ @Test
+ @RetryOnFailure
+ public void testReferenceGCd() {
+ DiscardableReferencePool pool = new DiscardableReferencePool();
+
+ Object object = new Object();
+ WeakReference<Object> weakReference = new WeakReference<>(object);
+
+ DiscardableReference<Object> discardableReference = pool.put(object);
+ Assert.assertEquals(object, discardableReference.get());
+
+ // Drop reference to the object itself and to the discardable reference, allowing the object
+ // to be garbage-collected.
+ object = null;
+ discardableReference = null;
+
+ // The object is not (strongly) reachable anymore, so the weak reference may or may not be
+ // null (it could be if a GC has happened since the pool was drained).
+ // After an explicit GC call it definitely should be null.
+ Runtime.getRuntime().gc();
+
+ Assert.assertNull(weakReference.get());
+ }
+}
diff --git a/base/android/library_loader/README.md b/base/android/library_loader/README.md
new file mode 100644
index 0000000000..7773b32b08
--- /dev/null
+++ b/base/android/library_loader/README.md
@@ -0,0 +1,10 @@
+# //base/android/library_loader
+
+Native code is split between this directory and:
+ * [//third_party/android_crazy_linker](../../../third_party/android_crazy_linker/README.chromium)
+
+Java code lives at:
+ * [//base/android/java/src/org/chromium/base/library_loader/](../java/src/org/chromium/base/library_loader/)
+
+A high-level guide to native code on Android exists at:
+ * [//docs/android_native_libraries.md](../../../docs/android_native_libraries.md)
diff --git a/base/android/library_loader/anchor_functions.cc b/base/android/library_loader/anchor_functions.cc
new file mode 100644
index 0000000000..0865d9d869
--- /dev/null
+++ b/base/android/library_loader/anchor_functions.cc
@@ -0,0 +1,79 @@
+// 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/android/library_loader/anchor_functions.h"
+
+#include "base/logging.h"
+#include "build/build_config.h"
+
+#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
+
+// These functions are here to delimit the start and end of the ordered part of
+// .text. They require a suitably constructed orderfile, with these functions at
+// the beginning and end.
+//
+// These functions are weird: this is due to ICF (Identical Code Folding).
+// The linker merges functions that have the same code, which would be the case
+// if these functions were empty, or simple.
+// Gold's flag --icf=safe will *not* alias functions when their address is used
+// in code, but as of November 2017, we use the default setting that
+// deduplicates function in this case as well.
+//
+// Thus these functions are made to be unique, using inline .word in assembly.
+//
+// Note that code |CheckOrderingSanity()| below will make sure that these
+// functions are not aliased, in case the toolchain becomes really clever.
+extern "C" {
+
+// These functions have a well-defined ordering in this file, see the comment
+// in |IsOrderingSane()|.
+void dummy_function_end_of_ordered_text() {
+ asm(".word 0x21bad44d");
+ asm(".word 0xb815c5b0");
+}
+
+void dummy_function_start_of_ordered_text() {
+ asm(".word 0xe4a07375");
+ asm(".word 0x66dda6dc");
+}
+
+// These two symbols are defined by anchor_functions.lds and delimit the start
+// and end of .text.
+void linker_script_start_of_text();
+void linker_script_end_of_text();
+
+} // extern "C"
+
+namespace base {
+namespace android {
+
+const size_t kStartOfText =
+ reinterpret_cast<size_t>(linker_script_start_of_text);
+const size_t kEndOfText = reinterpret_cast<size_t>(linker_script_end_of_text);
+const size_t kStartOfOrderedText =
+ reinterpret_cast<size_t>(dummy_function_start_of_ordered_text);
+const size_t kEndOfOrderedText =
+ reinterpret_cast<size_t>(dummy_function_end_of_ordered_text);
+
+bool IsOrderingSane() {
+ size_t here = reinterpret_cast<size_t>(&IsOrderingSane);
+ // The symbols linker_script_start_of_text and linker_script_end_of_text
+ // should cover all of .text, and dummy_function_start_of_ordered_text and
+ // dummy_function_end_of_ordered_text should cover the ordered part of it.
+ // This check is intended to catch the lack of ordering.
+ //
+ // Ordered text can start at the start of text, but should not cover the
+ // entire range. Most addresses are distinct nonetheless as the symbols are
+ // different, but linker-defined symbols have zero size and therefore the
+ // start address could be the same as the address of
+ // dummy_function_start_of_ordered_text.
+ return kStartOfText < here && here < kEndOfText &&
+ kStartOfOrderedText < kEndOfOrderedText &&
+ kStartOfText <= kStartOfOrderedText && kEndOfOrderedText < kEndOfText;
+}
+
+} // namespace android
+} // namespace base
+
+#endif // BUILDFLAG(SUPPORTS_CODE_ORDERING)
diff --git a/base/android/library_loader/anchor_functions.h b/base/android/library_loader/anchor_functions.h
new file mode 100644
index 0000000000..9894583a52
--- /dev/null
+++ b/base/android/library_loader/anchor_functions.h
@@ -0,0 +1,32 @@
+// 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_ANDROID_LIBRARY_LOADER_ANCHOR_FUNCTIONS_H_
+#define BASE_ANDROID_LIBRARY_LOADER_ANCHOR_FUNCTIONS_H_
+
+#include <cstdint>
+#include "base/android/library_loader/anchor_functions_buildflags.h"
+
+#include "base/base_export.h"
+
+#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
+
+namespace base {
+namespace android {
+
+// Start and end of .text, respectively.
+BASE_EXPORT extern const size_t kStartOfText;
+BASE_EXPORT extern const size_t kEndOfText;
+// Start and end of the ordered part of .text, respectively.
+BASE_EXPORT extern const size_t kStartOfOrderedText;
+BASE_EXPORT extern const size_t kEndOfOrderedText;
+
+// Returns true if the ordering looks sane.
+BASE_EXPORT bool IsOrderingSane();
+
+} // namespace android
+} // namespace base
+#endif // BUILDFLAG(SUPPORTS_CODE_ORDERING)
+
+#endif // BASE_ANDROID_LIBRARY_LOADER_ANCHOR_FUNCTIONS_H_
diff --git a/base/android/library_loader/anchor_functions.lds b/base/android/library_loader/anchor_functions.lds
new file mode 100644
index 0000000000..cdeaaa2a37
--- /dev/null
+++ b/base/android/library_loader/anchor_functions.lds
@@ -0,0 +1,7 @@
+# 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.
+
+# Define symbols that point to the start and end of the .text section.
+PROVIDE_HIDDEN(linker_script_start_of_text = ADDR(.text));
+PROVIDE_HIDDEN(linker_script_end_of_text = ADDR(.text) + SIZEOF(.text));
diff --git a/base/android/orderfile/BUILD.gn b/base/android/orderfile/BUILD.gn
new file mode 100644
index 0000000000..ff0bfff147
--- /dev/null
+++ b/base/android/orderfile/BUILD.gn
@@ -0,0 +1,34 @@
+# 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.
+
+import("//build/config/android/config.gni")
+
+if (use_order_profiling && target_cpu == "arm") {
+ static_library("orderfile_instrumentation") {
+ sources = [
+ "orderfile_instrumentation.cc",
+ "orderfile_instrumentation.h",
+ ]
+ deps = [
+ "//base",
+ ]
+ }
+
+ executable("orderfile_instrumentation_perftest") {
+ testonly = true
+
+ sources = [
+ "orderfile_instrumentation_perftest.cc",
+ ]
+
+ deps = [
+ ":orderfile_instrumentation",
+ "//base",
+ "//testing/gtest",
+ "//testing/perf",
+ ]
+
+ configs -= [ "//build/config/android:default_orderfile_instrumentation" ]
+ }
+}
diff --git a/base/android/orderfile/orderfile_instrumentation.cc b/base/android/orderfile/orderfile_instrumentation.cc
new file mode 100644
index 0000000000..f06cc20bed
--- /dev/null
+++ b/base/android/orderfile/orderfile_instrumentation.cc
@@ -0,0 +1,328 @@
+// Copyright (c) 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/android/orderfile/orderfile_instrumentation.h"
+
+#include <time.h>
+#include <unistd.h>
+
+#include <atomic>
+#include <cstdio>
+#include <cstring>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include "base/android/library_loader/anchor_functions.h"
+#include "base/android/orderfile/orderfile_buildflags.h"
+#include "base/files/file.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+
+#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
+#include <sstream>
+
+#include "base/command_line.h"
+#include "base/time/time.h"
+#include "base/trace_event/memory_dump_manager.h"
+#include "base/trace_event/memory_dump_provider.h"
+#endif // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
+
+#if !defined(ARCH_CPU_ARMEL)
+#error Only supported on ARM.
+#endif // !defined(ARCH_CPU_ARMEL)
+
+// Must be applied to all functions within this file.
+#define NO_INSTRUMENT_FUNCTION __attribute__((no_instrument_function))
+
+namespace base {
+namespace android {
+namespace orderfile {
+
+namespace {
+// Constants used for StartDelayedDump().
+constexpr int kDelayInSeconds = 30;
+constexpr int kInitialDelayInSeconds = kPhases == 1 ? kDelayInSeconds : 5;
+
+#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
+// This is defined in content/public/common/content_switches.h, which is not
+// accessible in ::base.
+constexpr const char kProcessTypeSwitch[] = "type";
+#endif // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
+
+// These are large overestimates, which is not an issue, as the data is
+// allocated in .bss, and on linux doesn't take any actual memory when it's not
+// touched.
+constexpr size_t kBitfieldSize = 1 << 22;
+constexpr size_t kMaxTextSizeInBytes = kBitfieldSize * (4 * 32);
+constexpr size_t kMaxElements = 1 << 20;
+
+// Data required to log reached offsets.
+struct LogData {
+ std::atomic<uint32_t> offsets[kBitfieldSize];
+ std::atomic<size_t> ordered_offsets[kMaxElements];
+ std::atomic<size_t> index;
+};
+
+LogData g_data[kPhases];
+std::atomic<int> g_data_index;
+
+#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
+// Dump offsets when a memory dump is requested. Used only if
+// switches::kDevtoolsInstrumentationDumping is set.
+class OrderfileMemoryDumpHook : public base::trace_event::MemoryDumpProvider {
+ NO_INSTRUMENT_FUNCTION bool OnMemoryDump(
+ const base::trace_event::MemoryDumpArgs& args,
+ base::trace_event::ProcessMemoryDump* pmd) override {
+ // Disable instrumentation now to cut down on orderfile pollution.
+ if (!Disable()) {
+ return true; // A dump has already been started.
+ }
+ std::stringstream process_type_str;
+ Dump(base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ kProcessTypeSwitch));
+ return true; // If something goes awry, a fatal error will be created
+ // internally.
+ }
+};
+#endif // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
+
+// |RecordAddress()| adds an element to a concurrent bitset and to a concurrent
+// append-only list of offsets.
+//
+// Ordering:
+// Two consecutive calls to |RecordAddress()| from the same thread will be
+// ordered in the same way in the result, as written by
+// |StopAndDumpToFile()|. The result will contain exactly one instance of each
+// unique offset relative to |kStartOfText| passed to |RecordAddress()|.
+//
+// Implementation:
+// The "set" part is implemented with a bitfield, |g_offset|. The insertion
+// order is recorded in |g_ordered_offsets|.
+// This is not a class to make sure there isn't a static constructor, as it
+// would cause issue with an instrumented static constructor calling this code.
+//
+// Limitations:
+// - Only records offsets to addresses between |kStartOfText| and |kEndOfText|.
+// - Capacity of the set is limited by |kMaxElements|.
+// - Some insertions at the end of collection may be lost.
+
+// Records that |address| has been reached, if recording is enabled.
+// To avoid infinite recursion, this *must* *never* call any instrumented
+// function, unless |Disable()| is called first.
+template <bool for_testing>
+__attribute__((always_inline, no_instrument_function)) void RecordAddress(
+ size_t address) {
+ int index = g_data_index.load(std::memory_order_relaxed);
+ if (index >= kPhases)
+ return;
+
+ const size_t start =
+ for_testing ? kStartOfTextForTesting : base::android::kStartOfText;
+ const size_t end =
+ for_testing ? kEndOfTextForTesting : base::android::kEndOfText;
+ if (UNLIKELY(address < start || address > end)) {
+ Disable();
+ // If the start and end addresses are set incorrectly, this code path is
+ // likely happening during a static initializer. Logging at this time is
+ // prone to deadlock. By crashing immediately we at least have a chance to
+ // get a stack trace from the system to give some clue about the nature of
+ // the problem.
+ IMMEDIATE_CRASH();
+ }
+
+ size_t offset = address - start;
+ static_assert(sizeof(int) == 4,
+ "Collection and processing code assumes that sizeof(int) == 4");
+ size_t offset_index = offset / 4;
+
+ auto* offsets = g_data[index].offsets;
+ // Atomically set the corresponding bit in the array.
+ std::atomic<uint32_t>* element = offsets + (offset_index / 32);
+ // First, a racy check. This saves a CAS if the bit is already set, and
+ // allows the cache line to remain shared acoss CPUs in this case.
+ uint32_t value = element->load(std::memory_order_relaxed);
+ uint32_t mask = 1 << (offset_index % 32);
+ if (value & mask)
+ return;
+
+ auto before = element->fetch_or(mask, std::memory_order_relaxed);
+ if (before & mask)
+ return;
+
+ // We were the first one to set the element, record it in the ordered
+ // elements list.
+ // Use relaxed ordering, as the value is not published, or used for
+ // synchronization.
+ auto* ordered_offsets = g_data[index].ordered_offsets;
+ auto& ordered_offsets_index = g_data[index].index;
+ size_t insertion_index =
+ ordered_offsets_index.fetch_add(1, std::memory_order_relaxed);
+ if (UNLIKELY(insertion_index >= kMaxElements)) {
+ Disable();
+ LOG(FATAL) << "Too many reached offsets";
+ }
+ ordered_offsets[insertion_index].store(offset, std::memory_order_relaxed);
+}
+
+NO_INSTRUMENT_FUNCTION bool DumpToFile(const base::FilePath& path,
+ const LogData& data) {
+ auto file =
+ base::File(path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+ if (!file.IsValid()) {
+ PLOG(ERROR) << "Could not open " << path;
+ return false;
+ }
+
+ if (data.index == 0) {
+ LOG(ERROR) << "No entries to dump";
+ return false;
+ }
+
+ size_t count = data.index - 1;
+ for (size_t i = 0; i < count; i++) {
+ // |g_ordered_offsets| is initialized to 0, so a 0 in the middle of it
+ // indicates a case where the index was incremented, but the write is not
+ // visible in this thread yet. Safe to skip, also because the function at
+ // the start of text is never called.
+ auto offset = data.ordered_offsets[i].load(std::memory_order_relaxed);
+ if (!offset)
+ continue;
+ auto offset_str = base::StringPrintf("%" PRIuS "\n", offset);
+ if (file.WriteAtCurrentPos(offset_str.c_str(),
+ static_cast<int>(offset_str.size())) < 0) {
+ // If the file could be opened, but writing has failed, it's likely that
+ // data was partially written. Producing incomplete profiling data would
+ // lead to a poorly performing orderfile, but might not be otherwised
+ // noticed. So we crash instead.
+ LOG(FATAL) << "Error writing profile data";
+ }
+ }
+ return true;
+}
+
+// Stops recording, and outputs the data to |path|.
+NO_INSTRUMENT_FUNCTION void StopAndDumpToFile(int pid,
+ uint64_t start_ns_since_epoch,
+ const std::string& tag) {
+ Disable();
+
+ for (int phase = 0; phase < kPhases; phase++) {
+ std::string tag_str;
+ if (!tag.empty())
+ tag_str = base::StringPrintf("%s-", tag.c_str());
+ auto path = base::StringPrintf(
+ "/data/local/tmp/chrome/orderfile/profile-hitmap-%s%d-%" PRIu64
+ ".txt_%d",
+ tag_str.c_str(), pid, start_ns_since_epoch, phase);
+ if (!DumpToFile(base::FilePath(path), g_data[phase])) {
+ LOG(ERROR) << "Problem with dump " << phase << " (" << tag << ")";
+ }
+ }
+}
+
+} // namespace
+
+NO_INSTRUMENT_FUNCTION bool Disable() {
+ auto old_phase = g_data_index.exchange(kPhases, std::memory_order_relaxed);
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+ return old_phase != kPhases;
+}
+
+NO_INSTRUMENT_FUNCTION void SanityChecks() {
+ CHECK_LT(base::android::kEndOfText - base::android::kStartOfText,
+ kMaxTextSizeInBytes);
+ CHECK(base::android::IsOrderingSane());
+}
+
+NO_INSTRUMENT_FUNCTION bool SwitchToNextPhaseOrDump(
+ int pid,
+ uint64_t start_ns_since_epoch) {
+ int before = g_data_index.fetch_add(1, std::memory_order_relaxed);
+ if (before + 1 == kPhases) {
+ StopAndDumpToFile(pid, start_ns_since_epoch, "");
+ return true;
+ }
+ return false;
+}
+
+NO_INSTRUMENT_FUNCTION void StartDelayedDump() {
+ // Using std::thread and not using base::TimeTicks() in order to to not call
+ // too many base:: symbols that would pollute the reached symbol dumps.
+ struct timespec ts;
+ if (clock_gettime(CLOCK_MONOTONIC, &ts))
+ PLOG(FATAL) << "clock_gettime.";
+ uint64_t start_ns_since_epoch =
+ static_cast<uint64_t>(ts.tv_sec) * 1000 * 1000 * 1000 + ts.tv_nsec;
+ int pid = getpid();
+
+#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
+ static auto* g_orderfile_memory_dump_hook = new OrderfileMemoryDumpHook();
+ base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
+ g_orderfile_memory_dump_hook, "Orderfile", nullptr);
+#endif // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
+
+ std::thread([pid, start_ns_since_epoch]() {
+ sleep(kInitialDelayInSeconds);
+#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
+ SwitchToNextPhaseOrDump(pid, start_ns_since_epoch);
+// Return, letting devtools tracing handle any post-startup phases.
+#else
+ while (!SwitchToNextPhaseOrDump(pid, start_ns_since_epoch))
+ sleep(kDelayInSeconds);
+#endif // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
+ })
+ .detach();
+}
+
+NO_INSTRUMENT_FUNCTION void Dump(const std::string& tag) {
+ // As profiling has been disabled, none of the uses of ::base symbols below
+ // will enter the symbol dump.
+ StopAndDumpToFile(
+ getpid(), (base::Time::Now() - base::Time::UnixEpoch()).InNanoseconds(),
+ tag);
+}
+
+NO_INSTRUMENT_FUNCTION void ResetForTesting() {
+ Disable();
+ g_data_index = 0;
+ for (int i = 0; i < kPhases; i++) {
+ memset(reinterpret_cast<uint32_t*>(g_data[i].offsets), 0,
+ sizeof(uint32_t) * kBitfieldSize);
+ memset(reinterpret_cast<uint32_t*>(g_data[i].ordered_offsets), 0,
+ sizeof(uint32_t) * kMaxElements);
+ g_data[i].index.store(0);
+ }
+}
+
+NO_INSTRUMENT_FUNCTION void RecordAddressForTesting(size_t address) {
+ return RecordAddress<true>(address);
+}
+
+NO_INSTRUMENT_FUNCTION std::vector<size_t> GetOrderedOffsetsForTesting() {
+ std::vector<size_t> result;
+ size_t max_index = g_data[0].index.load(std::memory_order_relaxed);
+ for (size_t i = 0; i < max_index; ++i) {
+ auto value = g_data[0].ordered_offsets[i].load(std::memory_order_relaxed);
+ if (value)
+ result.push_back(value);
+ }
+ return result;
+}
+
+} // namespace orderfile
+} // namespace android
+} // namespace base
+
+extern "C" {
+
+NO_INSTRUMENT_FUNCTION void __cyg_profile_func_enter_bare() {
+ base::android::orderfile::RecordAddress<false>(
+ reinterpret_cast<size_t>(__builtin_return_address(0)));
+}
+
+} // extern "C"
diff --git a/base/android/orderfile/orderfile_instrumentation.h b/base/android/orderfile/orderfile_instrumentation.h
new file mode 100644
index 0000000000..8db1943037
--- /dev/null
+++ b/base/android/orderfile/orderfile_instrumentation.h
@@ -0,0 +1,56 @@
+// 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_ANDROID_ORDERFILE_ORDERFILE_INSTRUMENTATION_H_
+#define BASE_ANDROID_ORDERFILE_ORDERFILE_INSTRUMENTATION_H_
+
+#include <cstdint>
+#include <vector>
+
+#include "base/android/orderfile/orderfile_buildflags.h"
+
+namespace base {
+namespace android {
+namespace orderfile {
+#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
+constexpr int kPhases = 2;
+#else
+constexpr int kPhases = 1;
+#endif // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
+
+constexpr size_t kStartOfTextForTesting = 1000;
+constexpr size_t kEndOfTextForTesting = kStartOfTextForTesting + 1000 * 1000;
+
+// Stop recording. Returns false if recording was already disabled.
+bool Disable();
+
+// CHECK()s that the offsets are correctly set up.
+void SanityChecks();
+
+// Switches to the next recording phase. If called from the last phase, dumps
+// the data to disk, and returns |true|. |pid| is the current process pid, and
+// |start_ns_since_epoch| the process start timestamp.
+bool SwitchToNextPhaseOrDump(int pid, uint64_t start_ns_since_epoch);
+
+// Starts a thread to dump instrumentation after a delay.
+void StartDelayedDump();
+
+// Dumps all information for the current process, annotating the dump file name
+// with the given tag. Will disable instrumentation. Instrumentation must be
+// disabled before this is called.
+void Dump(const std::string& tag);
+
+// Record an |address|, if recording is enabled. Only for testing.
+void RecordAddressForTesting(size_t address);
+
+// Resets the state. Only for testing.
+void ResetForTesting();
+
+// Returns an ordered list of reached offsets. Only for testing.
+std::vector<size_t> GetOrderedOffsetsForTesting();
+} // namespace orderfile
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_ORDERFILE_ORDERFILE_INSTRUMENTATION_H_
diff --git a/base/android/orderfile/orderfile_instrumentation_perftest.cc b/base/android/orderfile/orderfile_instrumentation_perftest.cc
new file mode 100644
index 0000000000..e1a69c9b7b
--- /dev/null
+++ b/base/android/orderfile/orderfile_instrumentation_perftest.cc
@@ -0,0 +1,135 @@
+// 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/android/orderfile/orderfile_instrumentation.h"
+
+#include <thread>
+
+#include "base/android/library_loader/anchor_functions.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_test.h"
+
+namespace base {
+namespace android {
+namespace orderfile {
+
+namespace {
+
+// Records |addresses_count| distinct addresses |iterations| times, in
+// |threads|.
+void RunBenchmark(int iterations, int addresses_count, int threads) {
+ ResetForTesting();
+ auto iterate = [iterations, addresses_count]() {
+ for (int i = 0; i < iterations; i++) {
+ for (size_t addr = kStartOfTextForTesting;
+ addr < static_cast<size_t>(addresses_count); addr += sizeof(int)) {
+ RecordAddressForTesting(addr);
+ }
+ }
+ };
+ if (threads != 1) {
+ for (int i = 0; i < threads - 1; ++i)
+ std::thread(iterate).detach();
+ }
+ auto tick = base::TimeTicks::Now();
+ iterate();
+ auto tock = base::TimeTicks::Now();
+ double nanos = static_cast<double>((tock - tick).InNanoseconds());
+ auto ns_per_call =
+ nanos / (iterations * static_cast<double>(addresses_count));
+ auto modifier =
+ base::StringPrintf("_%d_%d_%d", iterations, addresses_count, threads);
+ perf_test::PrintResult("RecordAddressCostPerCall", modifier, "", ns_per_call,
+ "ns", true);
+}
+
+} // namespace
+
+class OrderfileInstrumentationTest : public ::testing::Test {
+ // Any tests need to run ResetForTesting() when they start. Because this
+ // perftest is built with instrumentation enabled, all code including
+ // ::testing::Test is instrumented. If ResetForTesting() is called earlier,
+ // for example in setUp(), any test harness code between setUp() and the
+ // actual test will change the instrumentation offset record in unpredictable
+ // ways and make these tests unreliable.
+};
+
+TEST_F(OrderfileInstrumentationTest, RecordOffset) {
+ ResetForTesting();
+ size_t first = 1234, second = 1456;
+ RecordAddressForTesting(first);
+ RecordAddressForTesting(second);
+ RecordAddressForTesting(first); // No duplicates.
+ RecordAddressForTesting(first + 1); // 4 bytes granularity.
+ Disable();
+
+ auto reached = GetOrderedOffsetsForTesting();
+ EXPECT_EQ(2UL, reached.size());
+ EXPECT_EQ(first - kStartOfTextForTesting, reached[0]);
+ EXPECT_EQ(second - kStartOfTextForTesting, reached[1]);
+}
+
+TEST_F(OrderfileInstrumentationTest, RecordingStops) {
+ ResetForTesting();
+ size_t first = 1234, second = 1456, third = 1789;
+ RecordAddressForTesting(first);
+ RecordAddressForTesting(second);
+ Disable();
+ RecordAddressForTesting(third);
+
+ auto reached = GetOrderedOffsetsForTesting();
+ ASSERT_EQ(2UL, reached.size());
+ ASSERT_EQ(first - kStartOfTextForTesting, reached[0]);
+ ASSERT_EQ(second - kStartOfTextForTesting, reached[1]);
+}
+
+TEST_F(OrderfileInstrumentationTest, OutOfBounds) {
+ ResetForTesting();
+ EXPECT_DEATH(RecordAddressForTesting(kEndOfTextForTesting + 100), "");
+ EXPECT_DEATH(RecordAddressForTesting(kStartOfTextForTesting - 100), "");
+}
+
+TEST(OrderfileInstrumentationPerfTest, RecordAddress_10_10000) {
+ RunBenchmark(10, 10000, 1);
+}
+
+TEST(OrderfileInstrumentationPerfTest, RecordAddress_100_10000) {
+ RunBenchmark(100, 10000, 1);
+}
+
+TEST(OrderfileInstrumentationPerfTest, RecordAddress_10_100000) {
+ RunBenchmark(10, 100000, 1);
+}
+
+TEST(OrderfileInstrumentationPerfTest, RecordAddress_100_100000) {
+ RunBenchmark(100, 100000, 1);
+}
+
+TEST(OrderfileInstrumentationPerfTest, RecordAddress_1000_100000_2) {
+ RunBenchmark(1000, 100000, 2);
+}
+
+TEST(OrderfileInstrumentationPerfTest, RecordAddress_1000_100000_3) {
+ RunBenchmark(1000, 100000, 3);
+}
+
+TEST(OrderfileInstrumentationPerfTest, RecordAddress_1000_100000_4) {
+ RunBenchmark(1000, 100000, 4);
+}
+
+TEST(OrderfileInstrumentationPerfTest, RecordAddress_1000_100000_6) {
+ RunBenchmark(1000, 100000, 6);
+}
+
+} // namespace orderfile
+} // namespace android
+} // namespace base
+
+// Custom runner implementation since base's one requires JNI on Android.
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/base/android/proguard/disable_all_obfuscation.flags b/base/android/proguard/disable_all_obfuscation.flags
new file mode 100644
index 0000000000..deca250c40
--- /dev/null
+++ b/base/android/proguard/disable_all_obfuscation.flags
@@ -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.
+
+# Disables obfuscation while still allowing optimizations.
+-keepnames,allowoptimization class *** {
+ *;
+}
diff --git a/base/android/proguard/disable_chromium_obfuscation.flags b/base/android/proguard/disable_chromium_obfuscation.flags
new file mode 100644
index 0000000000..b410239eab
--- /dev/null
+++ b/base/android/proguard/disable_chromium_obfuscation.flags
@@ -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.
+
+# Disables obfuscation for chromium packages.
+-keepnames,allowoptimization class com.google.android.apps.chrome.**,org.chromium.** {
+ *;
+}
diff --git a/base/android/proguard/enable_obfuscation.flags b/base/android/proguard/enable_obfuscation.flags
new file mode 100644
index 0000000000..11bc240f54
--- /dev/null
+++ b/base/android/proguard/enable_obfuscation.flags
@@ -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.
+
+# As of August 11, 2016, obfuscation was found to save 660kb on our .dex size
+# and 53kb memory/process (through shrinking method/string counts).
+-renamesourcefileattribute PG
+-repackageclasses ""
diff --git a/base/android/scoped_hardware_buffer_handle.cc b/base/android/scoped_hardware_buffer_handle.cc
new file mode 100644
index 0000000000..55b0a70272
--- /dev/null
+++ b/base/android/scoped_hardware_buffer_handle.cc
@@ -0,0 +1,114 @@
+// 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.
+
+#include "base/android/scoped_hardware_buffer_handle.h"
+
+#include "base/android/android_hardware_buffer_compat.h"
+#include "base/logging.h"
+#include "base/posix/unix_domain_socket.h"
+
+namespace base {
+namespace android {
+
+ScopedHardwareBufferHandle::ScopedHardwareBufferHandle() = default;
+
+ScopedHardwareBufferHandle::ScopedHardwareBufferHandle(
+ ScopedHardwareBufferHandle&& other) {
+ *this = std::move(other);
+}
+
+ScopedHardwareBufferHandle::~ScopedHardwareBufferHandle() {
+ reset();
+}
+
+// static
+ScopedHardwareBufferHandle ScopedHardwareBufferHandle::Adopt(
+ AHardwareBuffer* buffer) {
+ return ScopedHardwareBufferHandle(buffer);
+}
+
+ScopedHardwareBufferHandle& ScopedHardwareBufferHandle::operator=(
+ ScopedHardwareBufferHandle&& other) {
+ reset();
+ std::swap(buffer_, other.buffer_);
+ return *this;
+}
+
+bool ScopedHardwareBufferHandle::is_valid() const {
+ return buffer_ != nullptr;
+}
+
+AHardwareBuffer* ScopedHardwareBufferHandle::get() const {
+ return buffer_;
+}
+
+void ScopedHardwareBufferHandle::reset() {
+ if (buffer_) {
+ AndroidHardwareBufferCompat::GetInstance().Release(buffer_);
+ buffer_ = nullptr;
+ }
+}
+
+AHardwareBuffer* ScopedHardwareBufferHandle::Take() {
+ AHardwareBuffer* buffer = nullptr;
+ std::swap(buffer, buffer_);
+ return buffer;
+}
+
+ScopedHardwareBufferHandle ScopedHardwareBufferHandle::Clone() const {
+ DCHECK(buffer_);
+ AndroidHardwareBufferCompat::GetInstance().Acquire(buffer_);
+ return ScopedHardwareBufferHandle(buffer_);
+}
+
+ScopedFD ScopedHardwareBufferHandle::SerializeAsFileDescriptor() const {
+ DCHECK(is_valid());
+
+ ScopedFD reader, writer;
+ if (!CreateSocketPair(&reader, &writer)) {
+ PLOG(ERROR) << "socketpair";
+ return ScopedFD();
+ }
+
+ // NOTE: SendHandleToUnixSocket does NOT acquire or retain a reference to the
+ // buffer object. The caller is therefore responsible for ensuring that the
+ // buffer remains alive through the lifetime of this file descriptor.
+ int result =
+ AndroidHardwareBufferCompat::GetInstance().SendHandleToUnixSocket(
+ buffer_, writer.get());
+ if (result < 0) {
+ PLOG(ERROR) << "send";
+ return ScopedFD();
+ }
+
+ return reader;
+}
+
+// static
+ScopedHardwareBufferHandle
+ScopedHardwareBufferHandle::DeserializeFromFileDescriptor(ScopedFD fd) {
+ DCHECK(fd.is_valid());
+ DCHECK(AndroidHardwareBufferCompat::IsSupportAvailable());
+ AHardwareBuffer* buffer = nullptr;
+
+ // NOTE: Upon success, RecvHandleFromUnixSocket acquires a new reference to
+ // the AHardwareBuffer.
+ int result =
+ AndroidHardwareBufferCompat::GetInstance().RecvHandleFromUnixSocket(
+ fd.get(), &buffer);
+ if (result < 0) {
+ PLOG(ERROR) << "recv";
+ return ScopedHardwareBufferHandle();
+ }
+
+ return ScopedHardwareBufferHandle(buffer);
+}
+
+ScopedHardwareBufferHandle::ScopedHardwareBufferHandle(AHardwareBuffer* buffer)
+ : buffer_(buffer) {
+ DCHECK(AndroidHardwareBufferCompat::IsSupportAvailable());
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/scoped_hardware_buffer_handle.h b/base/android/scoped_hardware_buffer_handle.h
new file mode 100644
index 0000000000..b8121ae811
--- /dev/null
+++ b/base/android/scoped_hardware_buffer_handle.h
@@ -0,0 +1,87 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ANDROID_SCOPED_HARDWARE_BUFFER_HANDLE_H_
+#define BASE_ANDROID_SCOPED_HARDWARE_BUFFER_HANDLE_H_
+
+#include "base/base_export.h"
+#include "base/files/scoped_file.h"
+#include "base/macros.h"
+
+extern "C" typedef struct AHardwareBuffer AHardwareBuffer;
+
+namespace base {
+namespace android {
+
+// Owns a single reference to an AHardwareBuffer object.
+class BASE_EXPORT ScopedHardwareBufferHandle {
+ public:
+ ScopedHardwareBufferHandle();
+
+ // Takes ownership of |other|'s buffer reference. Does NOT acquire a new one.
+ ScopedHardwareBufferHandle(ScopedHardwareBufferHandle&& other);
+
+ // Releases this handle's reference to the underlying buffer object if still
+ // valid.
+ ~ScopedHardwareBufferHandle();
+
+ // Assumes ownership of an existing reference to |buffer|. This does NOT
+ // acquire a new reference.
+ static ScopedHardwareBufferHandle Adopt(AHardwareBuffer* buffer);
+
+ // Takes ownership of |other|'s buffer reference. Does NOT acquire a new one.
+ ScopedHardwareBufferHandle& operator=(ScopedHardwareBufferHandle&& other);
+
+ bool is_valid() const;
+
+ AHardwareBuffer* get() const;
+
+ // Releases this handle's reference to the underlying buffer object if still
+ // valid. Invalidates this handle.
+ void reset();
+
+ // Passes implicit ownership of this handle's reference over to the caller,
+ // invalidating |this|. Returns the raw buffer handle.
+ //
+ // The caller is responsible for eventually releasing this reference to the
+ // buffer object.
+ AHardwareBuffer* Take() WARN_UNUSED_RESULT;
+
+ // Creates a new handle with its own newly acquired reference to the
+ // underlying buffer object. |this| must be a valid handle.
+ ScopedHardwareBufferHandle Clone() const;
+
+ // Consumes a handle and returns a file descriptor which can be used to
+ // transmit the handle over IPC. A subsequent receiver may use
+ // |DeserializeFromFileDescriptor()| to recover the buffer handle.
+ //
+ // NOTE: The returned file descriptor DOES NOT own a reference to the
+ // underlying AHardwareBuffer. When using this for IPC, the caller is
+ // responsible for retaining at least one reference to the buffer object to
+ // keep it alive while the descriptor is in transit.
+ ScopedFD SerializeAsFileDescriptor() const;
+
+ // Consumes the supplied single-use file descriptor (which must have been
+ // returned by a previous call to |SerializeAsFileDescriptor()|, perhaps in
+ // a different process), and recovers an AHardwareBuffer object from it.
+ //
+ // This acquires a new reference to the AHardwareBuffer, with ownership passed
+ // to the caller via the returned ScopedHardwareBufferHandle.
+ static ScopedHardwareBufferHandle DeserializeFromFileDescriptor(ScopedFD fd)
+ WARN_UNUSED_RESULT;
+
+ private:
+ // Assumes ownership of an existing reference to |buffer|. This does NOT
+ // acquire a new reference.
+ explicit ScopedHardwareBufferHandle(AHardwareBuffer* buffer);
+
+ AHardwareBuffer* buffer_ = nullptr;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedHardwareBufferHandle);
+};
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_SCOPED_HARDWARE_BUFFER_HANDLE_H_
diff --git a/base/android/scoped_java_ref.h b/base/android/scoped_java_ref.h
index 6d728e9695..8bb18d4a4e 100644
--- a/base/android/scoped_java_ref.h
+++ b/base/android/scoped_java_ref.h
@@ -45,12 +45,12 @@ template<>
class BASE_EXPORT JavaRef<jobject> {
public:
// Initializes a null reference. Don't add anything else here; it's inlined.
- JavaRef() : obj_(nullptr) {}
+ constexpr 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() {}
+ constexpr JavaRef(std::nullptr_t) : JavaRef() {}
// Public to allow destruction of null JavaRef objects.
// Don't add anything else here; it's inlined.
@@ -146,8 +146,8 @@ class JavaParamRef : public JavaRef<T> {
template<typename T>
class ScopedJavaLocalRef : public JavaRef<T> {
public:
- ScopedJavaLocalRef() : env_(nullptr) {}
- ScopedJavaLocalRef(std::nullptr_t) : env_(nullptr) {}
+ constexpr ScopedJavaLocalRef() : env_(nullptr) {}
+ constexpr 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.
@@ -234,8 +234,8 @@ class ScopedJavaLocalRef : public JavaRef<T> {
template<typename T>
class ScopedJavaGlobalRef : public JavaRef<T> {
public:
- ScopedJavaGlobalRef() {}
- ScopedJavaGlobalRef(std::nullptr_t) {}
+ constexpr ScopedJavaGlobalRef() {}
+ constexpr ScopedJavaGlobalRef(std::nullptr_t) {}
ScopedJavaGlobalRef(const ScopedJavaGlobalRef<T>& other) {
this->Reset(other);
@@ -279,22 +279,6 @@ class ScopedJavaGlobalRef : public JavaRef<T> {
}
};
-// Temporary type for parameters to Java functions, to allow incremental
-// migration from bare jobject to JavaRef. Don't use outside JNI generator.
-template <typename T>
-class JavaRefOrBare {
- public:
- JavaRefOrBare(std::nullptr_t) : obj_(nullptr) {}
- JavaRefOrBare(const JavaRef<T>& 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
diff --git a/base/android/timezone_utils.cc b/base/android/timezone_utils.cc
new file mode 100644
index 0000000000..5243cdc56c
--- /dev/null
+++ b/base/android/timezone_utils.cc
@@ -0,0 +1,23 @@
+// 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/android/timezone_utils.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/strings/string16.h"
+#include "jni/TimezoneUtils_jni.h"
+
+namespace base {
+namespace android {
+
+base::string16 GetDefaultTimeZoneId() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> timezone_id =
+ Java_TimezoneUtils_getDefaultTimeZoneId(env);
+ return ConvertJavaStringToUTF16(timezone_id);
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/timezone_utils.h b/base/android/timezone_utils.h
new file mode 100644
index 0000000000..f78ef85d76
--- /dev/null
+++ b/base/android/timezone_utils.h
@@ -0,0 +1,22 @@
+// 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_ANDROID_TIMEZONE_UTILS_H_
+#define BASE_ANDROID_TIMEZONE_UTILS_H_
+
+#include <jni.h>
+
+#include "base/base_export.h"
+#include "base/strings/string16.h"
+
+namespace base {
+namespace android {
+
+// Return an ICU timezone created from the host timezone.
+BASE_EXPORT base::string16 GetDefaultTimeZoneId();
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_TIMEZONE_UTILS_H_
diff --git a/base/at_exit.cc b/base/at_exit.cc
index e0025ea0d3..52c2151398 100644
--- a/base/at_exit.cc
+++ b/base/at_exit.cc
@@ -20,7 +20,7 @@ namespace base {
// version of the constructor, and if we are building a dynamic library we may
// end up with multiple AtExitManagers on the same process. We don't protect
// this for thread-safe access, since it will only be modified in testing.
-static AtExitManager* g_top_manager = NULL;
+static AtExitManager* g_top_manager = nullptr;
static bool g_disable_managers = false;
@@ -74,7 +74,7 @@ void AtExitManager::ProcessCallbacksNow() {
// Callbacks may try to add new callbacks, so run them without holding
// |lock_|. This is an error and caught by the DCHECK in RegisterTask(), but
// handle it gracefully in release builds so we don't deadlock.
- std::stack<base::Closure> tasks;
+ base::stack<base::Closure> tasks;
{
AutoLock lock(g_top_manager->lock_);
tasks.swap(g_top_manager->stack_);
diff --git a/base/at_exit.h b/base/at_exit.h
index 6bf3f50350..e74de8d78f 100644
--- a/base/at_exit.h
+++ b/base/at_exit.h
@@ -5,10 +5,9 @@
#ifndef BASE_AT_EXIT_H_
#define BASE_AT_EXIT_H_
-#include <stack>
-
#include "base/base_export.h"
#include "base/callback.h"
+#include "base/containers/stack.h"
#include "base/macros.h"
#include "base/synchronization/lock.h"
@@ -62,7 +61,7 @@ class BASE_EXPORT AtExitManager {
private:
base::Lock lock_;
- std::stack<base::Closure> stack_;
+ base::stack<base::Closure> stack_;
bool processing_callbacks_;
AtExitManager* next_manager_; // Stack of managers to allow shadowing.
diff --git a/base/at_exit_unittest.cc b/base/at_exit_unittest.cc
index cda73403fb..3de061f6a7 100644
--- a/base/at_exit_unittest.cc
+++ b/base/at_exit_unittest.cc
@@ -30,7 +30,7 @@ void ExpectCounter1IsZero(void* unused) {
}
void ExpectParamIsNull(void* param) {
- EXPECT_EQ(static_cast<void*>(NULL), param);
+ EXPECT_EQ(nullptr, param);
}
void ExpectParamIsCounter(void* param) {
@@ -48,9 +48,9 @@ class AtExitTest : public testing::Test {
TEST_F(AtExitTest, Basic) {
ZeroTestCounters();
- base::AtExitManager::RegisterCallback(&IncrementTestCounter1, NULL);
- base::AtExitManager::RegisterCallback(&IncrementTestCounter2, NULL);
- base::AtExitManager::RegisterCallback(&IncrementTestCounter1, NULL);
+ base::AtExitManager::RegisterCallback(&IncrementTestCounter1, nullptr);
+ base::AtExitManager::RegisterCallback(&IncrementTestCounter2, nullptr);
+ base::AtExitManager::RegisterCallback(&IncrementTestCounter1, nullptr);
EXPECT_EQ(0, g_test_counter_1);
EXPECT_EQ(0, g_test_counter_2);
@@ -61,9 +61,9 @@ TEST_F(AtExitTest, Basic) {
TEST_F(AtExitTest, LIFOOrder) {
ZeroTestCounters();
- base::AtExitManager::RegisterCallback(&IncrementTestCounter1, NULL);
- base::AtExitManager::RegisterCallback(&ExpectCounter1IsZero, NULL);
- base::AtExitManager::RegisterCallback(&IncrementTestCounter2, NULL);
+ base::AtExitManager::RegisterCallback(&IncrementTestCounter1, nullptr);
+ base::AtExitManager::RegisterCallback(&ExpectCounter1IsZero, nullptr);
+ base::AtExitManager::RegisterCallback(&IncrementTestCounter2, nullptr);
EXPECT_EQ(0, g_test_counter_1);
EXPECT_EQ(0, g_test_counter_2);
@@ -73,7 +73,7 @@ TEST_F(AtExitTest, LIFOOrder) {
}
TEST_F(AtExitTest, Param) {
- base::AtExitManager::RegisterCallback(&ExpectParamIsNull, NULL);
+ base::AtExitManager::RegisterCallback(&ExpectParamIsNull, nullptr);
base::AtExitManager::RegisterCallback(&ExpectParamIsCounter,
&g_test_counter_1);
base::AtExitManager::ProcessCallbacksNow();
diff --git a/base/atomic_ref_count.h b/base/atomic_ref_count.h
index 93c1f0dfd4..3ffa017acd 100644
--- a/base/atomic_ref_count.h
+++ b/base/atomic_ref_count.h
@@ -8,58 +8,59 @@
#ifndef BASE_ATOMIC_REF_COUNT_H_
#define BASE_ATOMIC_REF_COUNT_H_
-#include "base/atomicops.h"
+#include <atomic>
namespace base {
-typedef subtle::AtomicWord AtomicRefCount;
+class AtomicRefCount {
+ public:
+ constexpr AtomicRefCount() : ref_count_(0) {}
+ explicit constexpr AtomicRefCount(int initial_value)
+ : ref_count_(initial_value) {}
-// Increment a reference count by "increment", which must exceed 0.
-inline void AtomicRefCountIncN(volatile AtomicRefCount *ptr,
- AtomicRefCount increment) {
- subtle::NoBarrier_AtomicIncrement(ptr, increment);
-}
+ // Increment a reference count.
+ void Increment() { Increment(1); }
-// Decrement a reference count by "decrement", which must exceed 0,
-// and return whether the result is non-zero.
-// Insert barriers to ensure that state written before the reference count
-// became zero will be visible to a thread that has just made the count zero.
-inline bool AtomicRefCountDecN(volatile AtomicRefCount *ptr,
- AtomicRefCount decrement) {
- bool res = (subtle::Barrier_AtomicIncrement(ptr, -decrement) != 0);
- return res;
-}
+ // Increment a reference count by "increment", which must exceed 0.
+ void Increment(int increment) {
+ ref_count_.fetch_add(increment, std::memory_order_relaxed);
+ }
-// Increment a reference count by 1.
-inline void AtomicRefCountInc(volatile AtomicRefCount *ptr) {
- base::AtomicRefCountIncN(ptr, 1);
-}
+ // Decrement a reference count, and return whether the result is non-zero.
+ // Insert barriers to ensure that state written before the reference count
+ // became zero will be visible to a thread that has just made the count zero.
+ bool Decrement() {
+ // TODO(jbroman): Technically this doesn't need to be an acquire operation
+ // unless the result is 1 (i.e., the ref count did indeed reach zero).
+ // However, there are toolchain issues that make that not work as well at
+ // present (notably TSAN doesn't like it).
+ return ref_count_.fetch_sub(1, std::memory_order_acq_rel) != 1;
+ }
-// Decrement a reference count by 1 and return whether the result is non-zero.
-// Insert barriers to ensure that state written before the reference count
-// became zero will be visible to a thread that has just made the count zero.
-inline bool AtomicRefCountDec(volatile AtomicRefCount *ptr) {
- return base::AtomicRefCountDecN(ptr, 1);
-}
+ // Return whether the reference count is one. If the reference count is used
+ // in the conventional way, a refrerence count of 1 implies that the current
+ // thread owns the reference and no other thread shares it. This call
+ // performs the test for a reference count of one, and performs the memory
+ // barrier needed for the owning thread to act on the object, knowing that it
+ // has exclusive access to the object.
+ bool IsOne() const { return ref_count_.load(std::memory_order_acquire) == 1; }
-// Return whether the reference count is one. If the reference count is used
-// in the conventional way, a refrerence count of 1 implies that the current
-// thread owns the reference and no other thread shares it. This call performs
-// the test for a reference count of one, and performs the memory barrier
-// needed for the owning thread to act on the object, knowing that it has
-// exclusive access to the object.
-inline bool AtomicRefCountIsOne(volatile AtomicRefCount *ptr) {
- bool res = (subtle::Acquire_Load(ptr) == 1);
- return res;
-}
+ // Return whether the reference count is zero. With conventional object
+ // referencing counting, the object will be destroyed, so the reference count
+ // should never be zero. Hence this is generally used for a debug check.
+ bool IsZero() const {
+ return ref_count_.load(std::memory_order_acquire) == 0;
+ }
-// Return whether the reference count is zero. With conventional object
-// referencing counting, the object will be destroyed, so the reference count
-// should never be zero. Hence this is generally used for a debug check.
-inline bool AtomicRefCountIsZero(volatile AtomicRefCount *ptr) {
- bool res = (subtle::Acquire_Load(ptr) == 0);
- return res;
-}
+ // Returns the current reference count (with no barriers). This is subtle, and
+ // should be used only for debugging.
+ int SubtleRefCountForDebug() const {
+ return ref_count_.load(std::memory_order_relaxed);
+ }
+
+ private:
+ std::atomic_int ref_count_;
+};
} // namespace base
diff --git a/base/atomic_sequence_num.h b/base/atomic_sequence_num.h
index 59b0d2551a..717e37a60b 100644
--- a/base/atomic_sequence_num.h
+++ b/base/atomic_sequence_num.h
@@ -5,53 +5,26 @@
#ifndef BASE_ATOMIC_SEQUENCE_NUM_H_
#define BASE_ATOMIC_SEQUENCE_NUM_H_
-#include "base/atomicops.h"
+#include <atomic>
+
#include "base/macros.h"
namespace base {
-class AtomicSequenceNumber;
-
-// Static (POD) AtomicSequenceNumber that MUST be used in global scope (or
-// non-function scope) ONLY. This implementation does not generate any static
-// initializer. Note that it does not implement any constructor which means
-// that its fields are not initialized except when it is stored in the global
-// data section (.data in ELF). If you want to allocate an atomic sequence
-// number on the stack (or heap), please use the AtomicSequenceNumber class
-// declared below.
-class StaticAtomicSequenceNumber {
- public:
- inline int GetNext() {
- return static_cast<int>(
- base::subtle::NoBarrier_AtomicIncrement(&seq_, 1) - 1);
- }
-
- private:
- friend class AtomicSequenceNumber;
-
- inline void Reset() {
- base::subtle::Release_Store(&seq_, 0);
- }
-
- base::subtle::Atomic32 seq_;
-};
-
-// AtomicSequenceNumber that can be stored and used safely (i.e. its fields are
-// always initialized as opposed to StaticAtomicSequenceNumber declared above).
-// Please use StaticAtomicSequenceNumber if you want to declare an atomic
-// sequence number in the global scope.
+// AtomicSequenceNumber is a thread safe increasing sequence number generator.
+// Its constructor doesn't emit a static initializer, so it's safe to use as a
+// global variable or static member.
class AtomicSequenceNumber {
public:
- AtomicSequenceNumber() {
- seq_.Reset();
- }
+ constexpr AtomicSequenceNumber() = default;
- inline int GetNext() {
- return seq_.GetNext();
- }
+ // Returns an increasing sequence number starts from 0 for each call.
+ // This function can be called from any thread without data race.
+ inline int GetNext() { return seq_.fetch_add(1, std::memory_order_relaxed); }
private:
- StaticAtomicSequenceNumber seq_;
+ std::atomic_int seq_{0};
+
DISALLOW_COPY_AND_ASSIGN(AtomicSequenceNumber);
};
diff --git a/base/atomicops.h b/base/atomicops.h
index 3428fe87ab..4d8510e89e 100644
--- a/base/atomicops.h
+++ b/base/atomicops.h
@@ -145,8 +145,8 @@ Atomic64 Release_Load(volatile const Atomic64* ptr);
} // namespace base
#if defined(OS_WIN)
-// TODO(jfb): The MSVC header includes windows.h, which other files end up
-// relying on. Fix this as part of crbug.com/559247.
+// TODO(jfb): Try to use base/atomicops_internals_portable.h everywhere.
+// https://crbug.com/559247.
# include "base/atomicops_internals_x86_msvc.h"
#else
# include "base/atomicops_internals_portable.h"
diff --git a/base/atomicops_internals_x86_msvc.h b/base/atomicops_internals_x86_msvc.h
index 9f05b7e78d..ee9043e968 100644
--- a/base/atomicops_internals_x86_msvc.h
+++ b/base/atomicops_internals_x86_msvc.h
@@ -7,7 +7,7 @@
#ifndef BASE_ATOMICOPS_INTERNALS_X86_MSVC_H_
#define BASE_ATOMICOPS_INTERNALS_X86_MSVC_H_
-#include <windows.h>
+#include "base/win/windows_types.h"
#include <intrin.h>
@@ -61,8 +61,10 @@ inline void MemoryBarrier() {
// See #undef and note at the top of this file.
__faststorefence();
#else
- // We use MemoryBarrier from WinNT.h
- ::MemoryBarrier();
+ // We use the implementation of MemoryBarrier from WinNT.h
+ LONG barrier;
+
+ _InterlockedOr(&barrier, 0);
#endif
}
@@ -115,25 +117,25 @@ static_assert(sizeof(Atomic64) == sizeof(PVOID), "atomic word is atomic");
inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr,
Atomic64 old_value,
Atomic64 new_value) {
- PVOID result = InterlockedCompareExchangePointer(
- reinterpret_cast<volatile PVOID*>(ptr),
- reinterpret_cast<PVOID>(new_value), reinterpret_cast<PVOID>(old_value));
+ PVOID result = _InterlockedCompareExchangePointer(
+ reinterpret_cast<volatile PVOID*>(ptr),
+ reinterpret_cast<PVOID>(new_value), reinterpret_cast<PVOID>(old_value));
return reinterpret_cast<Atomic64>(result);
}
inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr,
Atomic64 new_value) {
- PVOID result = InterlockedExchangePointer(
- reinterpret_cast<volatile PVOID*>(ptr),
- reinterpret_cast<PVOID>(new_value));
+ PVOID result =
+ _InterlockedExchangePointer(reinterpret_cast<volatile PVOID*>(ptr),
+ reinterpret_cast<PVOID>(new_value));
return reinterpret_cast<Atomic64>(result);
}
inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr,
Atomic64 increment) {
- return InterlockedExchangeAdd64(
- reinterpret_cast<volatile LONGLONG*>(ptr),
- static_cast<LONGLONG>(increment)) + increment;
+ return _InterlockedExchangeAdd64(reinterpret_cast<volatile LONGLONG*>(ptr),
+ static_cast<LONGLONG>(increment)) +
+ increment;
}
inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr,
diff --git a/base/auto_reset.h b/base/auto_reset.h
index 9116537bfb..8515fe9cd7 100644
--- a/base/auto_reset.h
+++ b/base/auto_reset.h
@@ -5,6 +5,8 @@
#ifndef BASE_AUTO_RESET_H_
#define BASE_AUTO_RESET_H_
+#include <utility>
+
#include "base/macros.h"
// base::AutoReset<> is useful for setting a variable to a new value only within
@@ -23,11 +25,11 @@ class AutoReset {
public:
AutoReset(T* scoped_variable, T new_value)
: scoped_variable_(scoped_variable),
- original_value_(*scoped_variable) {
- *scoped_variable_ = new_value;
+ original_value_(std::move(*scoped_variable)) {
+ *scoped_variable_ = std::move(new_value);
}
- ~AutoReset() { *scoped_variable_ = original_value_; }
+ ~AutoReset() { *scoped_variable_ = std::move(original_value_); }
private:
T* scoped_variable_;
diff --git a/base/barrier_closure.cc b/base/barrier_closure.cc
new file mode 100644
index 0000000000..4426bb9d37
--- /dev/null
+++ b/base/barrier_closure.cc
@@ -0,0 +1,51 @@
+// 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/barrier_closure.h"
+
+#include <utility>
+
+#include "base/atomic_ref_count.h"
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+
+namespace base {
+namespace {
+
+// Maintains state for a BarrierClosure.
+class BarrierInfo {
+ public:
+ BarrierInfo(int num_callbacks_left, OnceClosure done_closure);
+ void Run();
+
+ private:
+ AtomicRefCount num_callbacks_left_;
+ OnceClosure done_closure_;
+};
+
+BarrierInfo::BarrierInfo(int num_callbacks, OnceClosure done_closure)
+ : num_callbacks_left_(num_callbacks),
+ done_closure_(std::move(done_closure)) {}
+
+void BarrierInfo::Run() {
+ DCHECK(!num_callbacks_left_.IsZero());
+ if (!num_callbacks_left_.Decrement())
+ std::move(done_closure_).Run();
+}
+
+} // namespace
+
+RepeatingClosure BarrierClosure(int num_callbacks_left,
+ OnceClosure done_closure) {
+ DCHECK_GE(num_callbacks_left, 0);
+
+ if (num_callbacks_left == 0)
+ std::move(done_closure).Run();
+
+ return BindRepeating(
+ &BarrierInfo::Run,
+ Owned(new BarrierInfo(num_callbacks_left, std::move(done_closure))));
+}
+
+} // namespace base
diff --git a/base/barrier_closure.h b/base/barrier_closure.h
new file mode 100644
index 0000000000..282aa39b92
--- /dev/null
+++ b/base/barrier_closure.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 BASE_BARRIER_CLOSURE_H_
+#define BASE_BARRIER_CLOSURE_H_
+
+#include "base/base_export.h"
+#include "base/callback.h"
+
+namespace base {
+
+// BarrierClosure executes |done_closure| after it has been invoked
+// |num_closures| times.
+//
+// If |num_closures| is 0, |done_closure| is executed immediately.
+//
+// BarrierClosure is thread-safe - the count of remaining closures is
+// maintained as a base::AtomicRefCount. |done_closure| will be run on
+// the thread that calls the final Run() on the returned closures.
+//
+// |done_closure| is also cleared on the final calling thread.
+BASE_EXPORT RepeatingClosure BarrierClosure(int num_closures,
+ OnceClosure done_closure);
+
+} // namespace base
+
+#endif // BASE_BARRIER_CLOSURE_H_
diff --git a/base/base64_decode_fuzzer.cc b/base/base64_decode_fuzzer.cc
new file mode 100644
index 0000000000..3716f727c6
--- /dev/null
+++ b/base/base64_decode_fuzzer.cc
@@ -0,0 +1,15 @@
+// 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 <string>
+
+#include "base/base64.h"
+#include "base/strings/string_piece.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ std::string decode_output;
+ base::StringPiece data_piece(reinterpret_cast<const char*>(data), size);
+ base::Base64Decode(data_piece, &decode_output);
+ return 0;
+}
diff --git a/base/base64_encode_fuzzer.cc b/base/base64_encode_fuzzer.cc
new file mode 100644
index 0000000000..c324be08b7
--- /dev/null
+++ b/base/base64_encode_fuzzer.cc
@@ -0,0 +1,20 @@
+// 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 <string>
+
+#include "base/base64.h"
+#include "base/logging.h"
+#include "base/strings/string_piece.h"
+
+// Encode some random data, and then decode it.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ std::string encode_output;
+ std::string decode_output;
+ base::StringPiece data_piece(reinterpret_cast<const char*>(data), size);
+ base::Base64Encode(data_piece, &encode_output);
+ CHECK(base::Base64Decode(encode_output, &decode_output));
+ CHECK_EQ(data_piece, decode_output);
+ return 0;
+}
diff --git a/base/base_paths.cc b/base/base_paths.cc
index 31bc55401a..e3f322e72a 100644
--- a/base/base_paths.cc
+++ b/base/base_paths.cc
@@ -15,17 +15,19 @@ bool PathProvider(int key, FilePath* result) {
switch (key) {
case DIR_EXE:
- PathService::Get(FILE_EXE, result);
+ if (!PathService::Get(FILE_EXE, result))
+ return false;
*result = result->DirName();
return true;
case DIR_MODULE:
- PathService::Get(FILE_MODULE, result);
+ if (!PathService::Get(FILE_MODULE, result))
+ return false;
*result = result->DirName();
return true;
+ case DIR_ASSETS:
+ return PathService::Get(DIR_MODULE, result);
case DIR_TEMP:
- if (!GetTempDir(result))
- return false;
- return true;
+ return GetTempDir(result);
case base::DIR_HOME:
*result = GetHomeDir();
return true;
diff --git a/base/base_paths.h b/base/base_paths.h
index ef6aa82836..2a163f48d4 100644
--- a/base/base_paths.h
+++ b/base/base_paths.h
@@ -18,7 +18,7 @@
#include "base/base_paths_android.h"
#endif
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
#include "base/base_paths_posix.h"
#endif
@@ -30,6 +30,7 @@ enum BasePathKey {
DIR_CURRENT, // Current directory.
DIR_EXE, // Directory containing FILE_EXE.
DIR_MODULE, // Directory containing FILE_MODULE.
+ DIR_ASSETS, // Directory that contains application assets.
DIR_TEMP, // Temporary directory.
DIR_HOME, // User's root home directory. On Windows this will look
// like "C:\Users\<user>" which isn't necessarily a great
@@ -44,7 +45,7 @@ enum BasePathKey {
// should not be used outside of test code.
DIR_USER_DESKTOP, // The current user's Desktop.
- DIR_TEST_DATA, // Used only for testing.
+ DIR_TEST_DATA, // Used only for testing.
PATH_END
};
diff --git a/base/base_paths_posix.cc b/base/base_paths_posix.cc
index 37d646cd26..5c4dd57f1d 100644
--- a/base/base_paths_posix.cc
+++ b/base/base_paths_posix.cc
@@ -28,14 +28,13 @@
#if defined(OS_FREEBSD)
#include <sys/param.h>
#include <sys/sysctl.h>
-#elif defined(OS_SOLARIS)
+#elif defined(OS_SOLARIS) || defined(OS_AIX)
#include <stdlib.h>
#endif
namespace base {
bool PathProviderPosix(int key, FilePath* result) {
- FilePath path;
switch (key) {
case FILE_EXE:
case FILE_MODULE: { // TODO(evanm): is this correct?
@@ -68,7 +67,7 @@ bool PathProviderPosix(int key, FilePath* result) {
}
*result = FilePath(bin_dir);
return true;
-#elif defined(OS_OPENBSD)
+#elif defined(OS_OPENBSD) || defined(OS_AIX)
// There is currently no way to get the executable path on OpenBSD
char* cpath;
if ((cpath = getenv("CHROME_EXE_PATH")) != NULL)
@@ -85,6 +84,7 @@ bool PathProviderPosix(int key, FilePath* result) {
// tree configurations (sub-project builds, gyp --output_dir, etc.)
std::unique_ptr<Environment> env(Environment::Create());
std::string cr_source_root;
+ FilePath path;
if (env->GetVar("CR_SOURCE_ROOT", &cr_source_root)) {
path = FilePath(cr_source_root);
if (PathExists(path)) {
diff --git a/base/base_switches.cc b/base/base_switches.cc
index e8aa5cbc4d..13c710b19e 100644
--- a/base/base_switches.cc
+++ b/base/base_switches.cc
@@ -7,28 +7,22 @@
namespace switches {
+// Delays execution of base::TaskPriority::BACKGROUND tasks until shutdown.
+const char kDisableBackgroundTasks[] = "disable-background-tasks";
+
// Disables the crash reporting.
const char kDisableBreakpad[] = "disable-breakpad";
+// Comma-separated list of feature names to disable. See also kEnableFeatures.
+const char kDisableFeatures[] = "disable-features";
+
// Indicates that crash reporting should be enabled. On platforms where helper
// processes cannot access to files needed to make this decision, this flag is
// generated internally.
const char kEnableCrashReporter[] = "enable-crash-reporter";
-// Makes memory allocators keep track of their allocations and context, so a
-// detailed breakdown of memory usage can be presented in chrome://tracing when
-// the memory-infra category is enabled.
-const char kEnableHeapProfiling[] = "enable-heap-profiling";
-
-// Report native (walk the stack) allocation traces. By default pseudo stacks
-// derived from trace events are reported.
-const char kEnableHeapProfilingModeNative[] = "native";
-
-// Report per-task heap usage and churn in the task profiler.
-// Does not keep track of individual allocations unlike the default and native
-// mode. Keeps only track of summarized churn stats in the task profiler
-// (chrome://profiler).
-const char kEnableHeapProfilingTaskProfiler[] = "task-profiler";
+// Comma-separated list of feature names to enable. See also kDisableFeatures.
+const char kEnableFeatures[] = "enable-features";
// Generates full memory crash dump.
const char kFullMemoryCrashReport[] = "full-memory-crash-report";
@@ -87,14 +81,6 @@ const char kTraceToFile[] = "trace-to-file";
// go to a default file name.
const char kTraceToFileName[] = "trace-to-file-name";
-// Configure whether chrome://profiler will contain timing information. This
-// option is enabled by default. A value of "0" will disable profiler timing,
-// while all other values will enable it.
-const char kProfilerTiming[] = "profiler-timing";
-// Value of the --profiler-timing flag that will disable timing information for
-// chrome://profiler.
-const char kProfilerTimingDisabledValue[] = "0";
-
// Specifies a location for profiling output. This will only work if chrome has
// been built with the gyp variable profiling=1 or gn arg enable_profiling=true.
//
@@ -110,6 +96,14 @@ const char kProfilingFile[] = "profiling-file";
const char kDisableUsbKeyboardDetect[] = "disable-usb-keyboard-detect";
#endif
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// The /dev/shm partition is too small in certain VM environments, causing
+// Chrome to fail or crash (see http://crbug.com/715363). Use this flag to
+// work-around this issue (a temporary directory will always be used to create
+// anonymous shared memory files).
+const char kDisableDevShmUsage[] = "disable-dev-shm-usage";
+#endif
+
#if defined(OS_POSIX)
// Used for turning on Breakpad crash reporting in a debug environment where
// crash reporting is typically compiled but disabled.
@@ -117,4 +111,11 @@ const char kEnableCrashReporterForTesting[] =
"enable-crash-reporter-for-testing";
#endif
+#if defined(OS_ANDROID)
+// Optimizes memory layout of the native library using the orderfile symbols
+// given in base/android/library_loader/anchor_functions.h, via madvise and
+// changing the library prefetch behavior.
+const char kOrderfileMemoryOptimization[] = "orderfile-memory-optimization";
+#endif
+
} // namespace switches
diff --git a/base/base_switches.h b/base/base_switches.h
index 04b0773057..4ef070d3f5 100644
--- a/base/base_switches.h
+++ b/base/base_switches.h
@@ -11,18 +11,16 @@
namespace switches {
+extern const char kDisableBackgroundTasks[];
extern const char kDisableBreakpad[];
+extern const char kDisableFeatures[];
extern const char kDisableLowEndDeviceMode[];
extern const char kEnableCrashReporter[];
-extern const char kEnableHeapProfiling[];
-extern const char kEnableHeapProfilingModeNative[];
-extern const char kEnableHeapProfilingTaskProfiler[];
+extern const char kEnableFeatures[];
extern const char kEnableLowEndDeviceMode[];
extern const char kForceFieldTrials[];
extern const char kFullMemoryCrashReport[];
extern const char kNoErrorDialogs[];
-extern const char kProfilerTiming[];
-extern const char kProfilerTimingDisabledValue[];
extern const char kProfilingFile[];
extern const char kTestChildProcess[];
extern const char kTestDoNotInitializeIcu[];
@@ -36,10 +34,18 @@ extern const char kWaitForDebugger[];
extern const char kDisableUsbKeyboardDetect[];
#endif
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+extern const char kDisableDevShmUsage[];
+#endif
+
#if defined(OS_POSIX)
extern const char kEnableCrashReporterForTesting[];
#endif
+#if defined(OS_ANDROID)
+extern const char kOrderfileMemoryOptimization[];
+#endif
+
} // namespace switches
#endif // BASE_BASE_SWITCHES_H_
diff --git a/base/big_endian.cc b/base/big_endian.cc
new file mode 100644
index 0000000000..514581fe41
--- /dev/null
+++ b/base/big_endian.cc
@@ -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.
+
+#include "base/big_endian.h"
+
+#include "base/strings/string_piece.h"
+
+namespace base {
+
+BigEndianReader::BigEndianReader(const char* buf, size_t len)
+ : ptr_(buf), end_(ptr_ + len) {}
+
+bool BigEndianReader::Skip(size_t len) {
+ if (ptr_ + len > end_)
+ return false;
+ ptr_ += len;
+ return true;
+}
+
+bool BigEndianReader::ReadBytes(void* out, size_t len) {
+ if (ptr_ + len > end_)
+ return false;
+ memcpy(out, ptr_, len);
+ ptr_ += len;
+ return true;
+}
+
+bool BigEndianReader::ReadPiece(base::StringPiece* out, size_t len) {
+ if (ptr_ + len > end_)
+ return false;
+ *out = base::StringPiece(ptr_, len);
+ ptr_ += len;
+ return true;
+}
+
+template<typename T>
+bool BigEndianReader::Read(T* value) {
+ if (ptr_ + sizeof(T) > end_)
+ return false;
+ ReadBigEndian<T>(ptr_, value);
+ ptr_ += sizeof(T);
+ return true;
+}
+
+bool BigEndianReader::ReadU8(uint8_t* value) {
+ return Read(value);
+}
+
+bool BigEndianReader::ReadU16(uint16_t* value) {
+ return Read(value);
+}
+
+bool BigEndianReader::ReadU32(uint32_t* value) {
+ return Read(value);
+}
+
+bool BigEndianReader::ReadU64(uint64_t* value) {
+ return Read(value);
+}
+
+BigEndianWriter::BigEndianWriter(char* buf, size_t len)
+ : ptr_(buf), end_(ptr_ + len) {}
+
+bool BigEndianWriter::Skip(size_t len) {
+ if (ptr_ + len > end_)
+ return false;
+ ptr_ += len;
+ return true;
+}
+
+bool BigEndianWriter::WriteBytes(const void* buf, size_t len) {
+ if (ptr_ + len > end_)
+ return false;
+ memcpy(ptr_, buf, len);
+ ptr_ += len;
+ return true;
+}
+
+template<typename T>
+bool BigEndianWriter::Write(T value) {
+ if (ptr_ + sizeof(T) > end_)
+ return false;
+ WriteBigEndian<T>(ptr_, value);
+ ptr_ += sizeof(T);
+ return true;
+}
+
+bool BigEndianWriter::WriteU8(uint8_t value) {
+ return Write(value);
+}
+
+bool BigEndianWriter::WriteU16(uint16_t value) {
+ return Write(value);
+}
+
+bool BigEndianWriter::WriteU32(uint32_t value) {
+ return Write(value);
+}
+
+bool BigEndianWriter::WriteU64(uint64_t value) {
+ return Write(value);
+}
+
+} // namespace base
diff --git a/base/big_endian.h b/base/big_endian.h
new file mode 100644
index 0000000000..5684c6758d
--- /dev/null
+++ b/base/big_endian.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 BASE_BIG_ENDIAN_H_
+#define BASE_BIG_ENDIAN_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/base_export.h"
+#include "base/strings/string_piece.h"
+
+namespace base {
+
+// Read an integer (signed or unsigned) from |buf| in Big Endian order.
+// Note: this loop is unrolled with -O1 and above.
+// NOTE(szym): glibc dns-canon.c use ntohs(*(uint16_t*)ptr) which is
+// potentially unaligned.
+// This would cause SIGBUS on ARMv5 or earlier and ARMv6-M.
+template<typename T>
+inline void ReadBigEndian(const char buf[], T* out) {
+ *out = buf[0];
+ for (size_t i = 1; i < sizeof(T); ++i) {
+ *out <<= 8;
+ // Must cast to uint8_t to avoid clobbering by sign extension.
+ *out |= static_cast<uint8_t>(buf[i]);
+ }
+}
+
+// Write an integer (signed or unsigned) |val| to |buf| in Big Endian order.
+// Note: this loop is unrolled with -O1 and above.
+template<typename T>
+inline void WriteBigEndian(char buf[], T val) {
+ for (size_t i = 0; i < sizeof(T); ++i) {
+ buf[sizeof(T)-i-1] = static_cast<char>(val & 0xFF);
+ val >>= 8;
+ }
+}
+
+// Specializations to make clang happy about the (dead code) shifts above.
+template <>
+inline void ReadBigEndian<uint8_t>(const char buf[], uint8_t* out) {
+ *out = buf[0];
+}
+
+template <>
+inline void WriteBigEndian<uint8_t>(char buf[], uint8_t val) {
+ buf[0] = static_cast<char>(val);
+}
+
+// Allows reading integers in network order (big endian) while iterating over
+// an underlying buffer. All the reading functions advance the internal pointer.
+class BASE_EXPORT BigEndianReader {
+ public:
+ BigEndianReader(const char* buf, size_t len);
+
+ const char* ptr() const { return ptr_; }
+ int remaining() const { return end_ - ptr_; }
+
+ bool Skip(size_t len);
+ bool ReadBytes(void* out, size_t len);
+ // Creates a StringPiece in |out| that points to the underlying buffer.
+ bool ReadPiece(base::StringPiece* out, size_t len);
+ bool ReadU8(uint8_t* value);
+ bool ReadU16(uint16_t* value);
+ bool ReadU32(uint32_t* value);
+ bool ReadU64(uint64_t* value);
+
+ private:
+ // Hidden to promote type safety.
+ template<typename T>
+ bool Read(T* v);
+
+ const char* ptr_;
+ const char* end_;
+};
+
+// Allows writing integers in network order (big endian) while iterating over
+// an underlying buffer. All the writing functions advance the internal pointer.
+class BASE_EXPORT BigEndianWriter {
+ public:
+ BigEndianWriter(char* buf, size_t len);
+
+ char* ptr() const { return ptr_; }
+ int remaining() const { return end_ - ptr_; }
+
+ bool Skip(size_t len);
+ bool WriteBytes(const void* buf, size_t len);
+ bool WriteU8(uint8_t value);
+ bool WriteU16(uint16_t value);
+ bool WriteU32(uint32_t value);
+ bool WriteU64(uint64_t value);
+
+ private:
+ // Hidden to promote type safety.
+ template<typename T>
+ bool Write(T v);
+
+ char* ptr_;
+ char* end_;
+};
+
+} // namespace base
+
+#endif // BASE_BIG_ENDIAN_H_
diff --git a/base/big_endian_unittest.cc b/base/big_endian_unittest.cc
new file mode 100644
index 0000000000..4e1e7cea30
--- /dev/null
+++ b/base/big_endian_unittest.cc
@@ -0,0 +1,116 @@
+// 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/big_endian.h"
+
+#include <stdint.h>
+
+#include "base/strings/string_piece.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+TEST(BigEndianReaderTest, ReadsValues) {
+ char data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+ 0x1A, 0x2B, 0x3C, 0x4D, 0x5E };
+ char buf[2];
+ uint8_t u8;
+ uint16_t u16;
+ uint32_t u32;
+ uint64_t u64;
+ base::StringPiece piece;
+ BigEndianReader reader(data, sizeof(data));
+
+ EXPECT_TRUE(reader.Skip(2));
+ EXPECT_EQ(data + 2, reader.ptr());
+ EXPECT_EQ(reader.remaining(), static_cast<int>(sizeof(data)) - 2);
+ EXPECT_TRUE(reader.ReadBytes(buf, sizeof(buf)));
+ EXPECT_EQ(0x2, buf[0]);
+ EXPECT_EQ(0x3, buf[1]);
+ EXPECT_TRUE(reader.ReadU8(&u8));
+ EXPECT_EQ(0x4, u8);
+ EXPECT_TRUE(reader.ReadU16(&u16));
+ EXPECT_EQ(0x0506, u16);
+ EXPECT_TRUE(reader.ReadU32(&u32));
+ EXPECT_EQ(0x0708090Au, u32);
+ EXPECT_TRUE(reader.ReadU64(&u64));
+ EXPECT_EQ(0x0B0C0D0E0F1A2B3Cllu, u64);
+ base::StringPiece expected(reader.ptr(), 2);
+ EXPECT_TRUE(reader.ReadPiece(&piece, 2));
+ EXPECT_EQ(2u, piece.size());
+ EXPECT_EQ(expected.data(), piece.data());
+}
+
+TEST(BigEndianReaderTest, RespectsLength) {
+ char data[8];
+ char buf[2];
+ uint8_t u8;
+ uint16_t u16;
+ uint32_t u32;
+ uint64_t u64;
+ base::StringPiece piece;
+ BigEndianReader reader(data, sizeof(data));
+ // 8 left
+ EXPECT_FALSE(reader.Skip(9));
+ EXPECT_TRUE(reader.Skip(1));
+ // 7 left
+ EXPECT_FALSE(reader.ReadU64(&u64));
+ EXPECT_TRUE(reader.Skip(4));
+ // 3 left
+ EXPECT_FALSE(reader.ReadU32(&u32));
+ EXPECT_FALSE(reader.ReadPiece(&piece, 4));
+ EXPECT_TRUE(reader.Skip(2));
+ // 1 left
+ EXPECT_FALSE(reader.ReadU16(&u16));
+ EXPECT_FALSE(reader.ReadBytes(buf, 2));
+ EXPECT_TRUE(reader.Skip(1));
+ // 0 left
+ EXPECT_FALSE(reader.ReadU8(&u8));
+ EXPECT_EQ(0, reader.remaining());
+}
+
+TEST(BigEndianWriterTest, WritesValues) {
+ char expected[] = { 0, 0, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE,
+ 0xF, 0x1A, 0x2B, 0x3C };
+ char data[sizeof(expected)];
+ char buf[] = { 0x2, 0x3 };
+ memset(data, 0, sizeof(data));
+ BigEndianWriter writer(data, sizeof(data));
+
+ EXPECT_TRUE(writer.Skip(2));
+ EXPECT_TRUE(writer.WriteBytes(buf, sizeof(buf)));
+ EXPECT_TRUE(writer.WriteU8(0x4));
+ EXPECT_TRUE(writer.WriteU16(0x0506));
+ EXPECT_TRUE(writer.WriteU32(0x0708090A));
+ EXPECT_TRUE(writer.WriteU64(0x0B0C0D0E0F1A2B3Cllu));
+ EXPECT_EQ(0, memcmp(expected, data, sizeof(expected)));
+}
+
+TEST(BigEndianWriterTest, RespectsLength) {
+ char data[8];
+ char buf[2];
+ uint8_t u8 = 0;
+ uint16_t u16 = 0;
+ uint32_t u32 = 0;
+ uint64_t u64 = 0;
+ BigEndianWriter writer(data, sizeof(data));
+ // 8 left
+ EXPECT_FALSE(writer.Skip(9));
+ EXPECT_TRUE(writer.Skip(1));
+ // 7 left
+ EXPECT_FALSE(writer.WriteU64(u64));
+ EXPECT_TRUE(writer.Skip(4));
+ // 3 left
+ EXPECT_FALSE(writer.WriteU32(u32));
+ EXPECT_TRUE(writer.Skip(2));
+ // 1 left
+ EXPECT_FALSE(writer.WriteU16(u16));
+ EXPECT_FALSE(writer.WriteBytes(buf, 2));
+ EXPECT_TRUE(writer.Skip(1));
+ // 0 left
+ EXPECT_FALSE(writer.WriteU8(u8));
+ EXPECT_EQ(0, writer.remaining());
+}
+
+} // namespace base
diff --git a/base/bind.h b/base/bind.h
index ce717972e2..66d5d82dd0 100644
--- a/base/bind.h
+++ b/base/bind.h
@@ -5,14 +5,46 @@
#ifndef BASE_BIND_H_
#define BASE_BIND_H_
+#include <utility>
+
#include "base/bind_internal.h"
+#include "base/compiler_specific.h"
+#include "build/build_config.h"
+
+#if defined(OS_MACOSX) && !HAS_FEATURE(objc_arc)
+#include "base/mac/scoped_block.h"
+#endif
// -----------------------------------------------------------------------------
// Usage documentation
// -----------------------------------------------------------------------------
//
-// See //docs/callback.md for documentation.
+// Overview:
+// base::BindOnce() and base::BindRepeating() are helpers for creating
+// base::OnceCallback and base::RepeatingCallback objects respectively.
+//
+// For a runnable object of n-arity, the base::Bind*() family allows partial
+// application of the first m arguments. The remaining n - m arguments must be
+// passed when invoking the callback with Run().
+//
+// // The first argument is bound at callback creation; the remaining
+// // two must be passed when calling Run() on the callback object.
+// base::OnceCallback<void(int, long)> cb = base::BindOnce(
+// [](short x, int y, long z) { return x * y * z; }, 42);
+//
+// When binding to a method, the receiver object must also be specified at
+// callback creation time. When Run() is invoked, the method will be invoked on
+// the specified receiver object.
+//
+// class C : public base::RefCounted<C> { void F(); };
+// auto instance = base::MakeRefCounted<C>();
+// auto cb = base::BindOnce(&C::F, instance);
+// cb.Run(); // Identical to instance->F()
//
+// base::Bind is currently a type alias for base::BindRepeating(). In the
+// future, we expect to flip this to default to base::BindOnce().
+//
+// See //docs/callback.md for the full documentation.
//
// -----------------------------------------------------------------------------
// Implementation notes
@@ -24,10 +56,154 @@
namespace base {
+namespace internal {
+
+// IsOnceCallback<T> is a std::true_type if |T| is a OnceCallback.
+template <typename T>
+struct IsOnceCallback : std::false_type {};
+
+template <typename Signature>
+struct IsOnceCallback<OnceCallback<Signature>> : std::true_type {};
+
+// Helper to assert that parameter |i| of type |Arg| can be bound, which means:
+// - |Arg| can be retained internally as |Storage|.
+// - |Arg| can be forwarded as |Unwrapped| to |Param|.
+template <size_t i,
+ typename Arg,
+ typename Storage,
+ typename Unwrapped,
+ typename Param>
+struct AssertConstructible {
+ private:
+ static constexpr bool param_is_forwardable =
+ std::is_constructible<Param, Unwrapped>::value;
+ // Unlike the check for binding into storage below, the check for
+ // forwardability drops the const qualifier for repeating callbacks. This is
+ // to try to catch instances where std::move()--which forwards as a const
+ // reference with repeating callbacks--is used instead of base::Passed().
+ static_assert(
+ param_is_forwardable ||
+ !std::is_constructible<Param, std::decay_t<Unwrapped>&&>::value,
+ "Bound argument |i| is move-only but will be forwarded by copy. "
+ "Ensure |Arg| is bound using base::Passed(), not std::move().");
+ static_assert(
+ param_is_forwardable,
+ "Bound argument |i| of type |Arg| cannot be forwarded as "
+ "|Unwrapped| to the bound functor, which declares it as |Param|.");
+
+ static constexpr bool arg_is_storable =
+ std::is_constructible<Storage, Arg>::value;
+ static_assert(arg_is_storable ||
+ !std::is_constructible<Storage, std::decay_t<Arg>&&>::value,
+ "Bound argument |i| is move-only but will be bound by copy. "
+ "Ensure |Arg| is mutable and bound using std::move().");
+ static_assert(arg_is_storable,
+ "Bound argument |i| of type |Arg| cannot be converted and "
+ "bound as |Storage|.");
+};
+
+// Takes three same-length TypeLists, and applies AssertConstructible for each
+// triples.
+template <typename Index,
+ typename Args,
+ typename UnwrappedTypeList,
+ typename ParamsList>
+struct AssertBindArgsValidity;
+
+template <size_t... Ns,
+ typename... Args,
+ typename... Unwrapped,
+ typename... Params>
+struct AssertBindArgsValidity<std::index_sequence<Ns...>,
+ TypeList<Args...>,
+ TypeList<Unwrapped...>,
+ TypeList<Params...>>
+ : AssertConstructible<Ns, Args, std::decay_t<Args>, Unwrapped, Params>... {
+ static constexpr bool ok = true;
+};
+
+// The implementation of TransformToUnwrappedType below.
+template <bool is_once, typename T>
+struct TransformToUnwrappedTypeImpl;
+
+template <typename T>
+struct TransformToUnwrappedTypeImpl<true, T> {
+ using StoredType = std::decay_t<T>;
+ using ForwardType = StoredType&&;
+ using Unwrapped = decltype(Unwrap(std::declval<ForwardType>()));
+};
+
+template <typename T>
+struct TransformToUnwrappedTypeImpl<false, T> {
+ using StoredType = std::decay_t<T>;
+ using ForwardType = const StoredType&;
+ using Unwrapped = decltype(Unwrap(std::declval<ForwardType>()));
+};
+
+// Transform |T| into `Unwrapped` type, which is passed to the target function.
+// Example:
+// In is_once == true case,
+// `int&&` -> `int&&`,
+// `const int&` -> `int&&`,
+// `OwnedWrapper<int>&` -> `int*&&`.
+// In is_once == false case,
+// `int&&` -> `const int&`,
+// `const int&` -> `const int&`,
+// `OwnedWrapper<int>&` -> `int* const &`.
+template <bool is_once, typename T>
+using TransformToUnwrappedType =
+ typename TransformToUnwrappedTypeImpl<is_once, T>::Unwrapped;
+
+// Transforms |Args| into `Unwrapped` types, and packs them into a TypeList.
+// If |is_method| is true, tries to dereference the first argument to support
+// smart pointers.
+template <bool is_once, bool is_method, typename... Args>
+struct MakeUnwrappedTypeListImpl {
+ using Type = TypeList<TransformToUnwrappedType<is_once, Args>...>;
+};
+
+// Performs special handling for this pointers.
+// Example:
+// int* -> int*,
+// std::unique_ptr<int> -> int*.
+template <bool is_once, typename Receiver, typename... Args>
+struct MakeUnwrappedTypeListImpl<is_once, true, Receiver, Args...> {
+ using UnwrappedReceiver = TransformToUnwrappedType<is_once, Receiver>;
+ using Type = TypeList<decltype(&*std::declval<UnwrappedReceiver>()),
+ TransformToUnwrappedType<is_once, Args>...>;
+};
+
+template <bool is_once, bool is_method, typename... Args>
+using MakeUnwrappedTypeList =
+ typename MakeUnwrappedTypeListImpl<is_once, is_method, Args...>::Type;
+
+} // namespace internal
+
// Bind as OnceCallback.
template <typename Functor, typename... Args>
inline OnceCallback<MakeUnboundRunType<Functor, Args...>>
BindOnce(Functor&& functor, Args&&... args) {
+ static_assert(!internal::IsOnceCallback<std::decay_t<Functor>>() ||
+ (std::is_rvalue_reference<Functor&&>() &&
+ !std::is_const<std::remove_reference_t<Functor>>()),
+ "BindOnce requires non-const rvalue for OnceCallback binding."
+ " I.e.: base::BindOnce(std::move(callback)).");
+
+ // This block checks if each |args| matches to the corresponding params of the
+ // target function. This check does not affect the behavior of Bind, but its
+ // error message should be more readable.
+ using Helper = internal::BindTypeHelper<Functor, Args...>;
+ using FunctorTraits = typename Helper::FunctorTraits;
+ using BoundArgsList = typename Helper::BoundArgsList;
+ using UnwrappedArgsList =
+ internal::MakeUnwrappedTypeList<true, FunctorTraits::is_method,
+ Args&&...>;
+ using BoundParamsList = typename Helper::BoundParamsList;
+ static_assert(internal::AssertBindArgsValidity<
+ std::make_index_sequence<Helper::num_bounds>, BoundArgsList,
+ UnwrappedArgsList, BoundParamsList>::ok,
+ "The bound args need to be convertible to the target params.");
+
using BindState = internal::MakeBindStateType<Functor, Args...>;
using UnboundRunType = MakeUnboundRunType<Functor, Args...>;
using Invoker = internal::Invoker<BindState, UnboundRunType>;
@@ -50,6 +226,25 @@ BindOnce(Functor&& functor, Args&&... args) {
template <typename Functor, typename... Args>
inline RepeatingCallback<MakeUnboundRunType<Functor, Args...>>
BindRepeating(Functor&& functor, Args&&... args) {
+ static_assert(
+ !internal::IsOnceCallback<std::decay_t<Functor>>(),
+ "BindRepeating cannot bind OnceCallback. Use BindOnce with std::move().");
+
+ // This block checks if each |args| matches to the corresponding params of the
+ // target function. This check does not affect the behavior of Bind, but its
+ // error message should be more readable.
+ using Helper = internal::BindTypeHelper<Functor, Args...>;
+ using FunctorTraits = typename Helper::FunctorTraits;
+ using BoundArgsList = typename Helper::BoundArgsList;
+ using UnwrappedArgsList =
+ internal::MakeUnwrappedTypeList<false, FunctorTraits::is_method,
+ Args&&...>;
+ using BoundParamsList = typename Helper::BoundParamsList;
+ static_assert(internal::AssertBindArgsValidity<
+ std::make_index_sequence<Helper::num_bounds>, BoundArgsList,
+ UnwrappedArgsList, BoundParamsList>::ok,
+ "The bound args need to be convertible to the target params.");
+
using BindState = internal::MakeBindStateType<Functor, Args...>;
using UnboundRunType = MakeUnboundRunType<Functor, Args...>;
using Invoker = internal::Invoker<BindState, UnboundRunType>;
@@ -74,10 +269,214 @@ BindRepeating(Functor&& functor, Args&&... args) {
template <typename Functor, typename... Args>
inline Callback<MakeUnboundRunType<Functor, Args...>>
Bind(Functor&& functor, Args&&... args) {
- return BindRepeating(std::forward<Functor>(functor),
- std::forward<Args>(args)...);
+ return base::BindRepeating(std::forward<Functor>(functor),
+ std::forward<Args>(args)...);
+}
+
+// Special cases for binding to a base::Callback without extra bound arguments.
+template <typename Signature>
+OnceCallback<Signature> BindOnce(OnceCallback<Signature> closure) {
+ return closure;
+}
+
+template <typename Signature>
+RepeatingCallback<Signature> BindRepeating(
+ RepeatingCallback<Signature> closure) {
+ return closure;
+}
+
+template <typename Signature>
+Callback<Signature> Bind(Callback<Signature> closure) {
+ return closure;
+}
+
+// Unretained() allows Bind() to bind a non-refcounted class, and to disable
+// refcounting on arguments that are refcounted objects.
+//
+// EXAMPLE OF Unretained():
+//
+// class Foo {
+// public:
+// void func() { cout << "Foo:f" << endl; }
+// };
+//
+// // In some function somewhere.
+// Foo foo;
+// Closure foo_callback =
+// Bind(&Foo::func, Unretained(&foo));
+// foo_callback.Run(); // Prints "Foo:f".
+//
+// Without the Unretained() wrapper on |&foo|, the above call would fail
+// to compile because Foo does not support the AddRef() and Release() methods.
+template <typename T>
+static inline internal::UnretainedWrapper<T> Unretained(T* o) {
+ return internal::UnretainedWrapper<T>(o);
}
+// RetainedRef() accepts a ref counted object and retains a reference to it.
+// When the callback is called, the object is passed as a raw pointer.
+//
+// EXAMPLE OF RetainedRef():
+//
+// void foo(RefCountedBytes* bytes) {}
+//
+// scoped_refptr<RefCountedBytes> bytes = ...;
+// Closure callback = Bind(&foo, base::RetainedRef(bytes));
+// callback.Run();
+//
+// Without RetainedRef, the scoped_refptr would try to implicitly convert to
+// a raw pointer and fail compilation:
+//
+// Closure callback = Bind(&foo, bytes); // ERROR!
+template <typename T>
+static inline internal::RetainedRefWrapper<T> RetainedRef(T* o) {
+ return internal::RetainedRefWrapper<T>(o);
+}
+template <typename T>
+static inline internal::RetainedRefWrapper<T> RetainedRef(scoped_refptr<T> o) {
+ return internal::RetainedRefWrapper<T>(std::move(o));
+}
+
+// ConstRef() allows binding a constant reference to an argument rather
+// than a copy.
+//
+// EXAMPLE OF ConstRef():
+//
+// void foo(int arg) { cout << arg << endl }
+//
+// int n = 1;
+// Closure no_ref = Bind(&foo, n);
+// Closure has_ref = Bind(&foo, ConstRef(n));
+//
+// no_ref.Run(); // Prints "1"
+// has_ref.Run(); // Prints "1"
+//
+// n = 2;
+// no_ref.Run(); // Prints "1"
+// has_ref.Run(); // Prints "2"
+//
+// Note that because ConstRef() takes a reference on |n|, |n| must outlive all
+// its bound callbacks.
+template <typename T>
+static inline internal::ConstRefWrapper<T> ConstRef(const T& o) {
+ return internal::ConstRefWrapper<T>(o);
+}
+
+// Owned() transfers ownership of an object to the Callback resulting from
+// bind; the object will be deleted when the Callback is deleted.
+//
+// EXAMPLE OF Owned():
+//
+// void foo(int* arg) { cout << *arg << endl }
+//
+// int* pn = new int(1);
+// Closure foo_callback = Bind(&foo, Owned(pn));
+//
+// foo_callback.Run(); // Prints "1"
+// foo_callback.Run(); // Prints "1"
+// *n = 2;
+// foo_callback.Run(); // Prints "2"
+//
+// foo_callback.Reset(); // |pn| is deleted. Also will happen when
+// // |foo_callback| goes out of scope.
+//
+// Without Owned(), someone would have to know to delete |pn| when the last
+// reference to the Callback is deleted.
+template <typename T>
+static inline internal::OwnedWrapper<T> Owned(T* o) {
+ return internal::OwnedWrapper<T>(o);
+}
+
+// Passed() is for transferring movable-but-not-copyable types (eg. unique_ptr)
+// through a Callback. Logically, this signifies a destructive transfer of
+// the state of the argument into the target function. Invoking
+// Callback::Run() twice on a Callback that was created with a Passed()
+// argument will CHECK() because the first invocation would have already
+// transferred ownership to the target function.
+//
+// Note that Passed() is not necessary with BindOnce(), as std::move() does the
+// same thing. Avoid Passed() in favor of std::move() with BindOnce().
+//
+// EXAMPLE OF Passed():
+//
+// void TakesOwnership(std::unique_ptr<Foo> arg) { }
+// std::unique_ptr<Foo> CreateFoo() { return std::make_unique<Foo>();
+// }
+//
+// auto f = std::make_unique<Foo>();
+//
+// // |cb| is given ownership of Foo(). |f| is now NULL.
+// // You can use std::move(f) in place of &f, but it's more verbose.
+// Closure cb = Bind(&TakesOwnership, Passed(&f));
+//
+// // Run was never called so |cb| still owns Foo() and deletes
+// // it on Reset().
+// cb.Reset();
+//
+// // |cb| is given a new Foo created by CreateFoo().
+// cb = Bind(&TakesOwnership, Passed(CreateFoo()));
+//
+// // |arg| in TakesOwnership() is given ownership of Foo(). |cb|
+// // no longer owns Foo() and, if reset, would not delete Foo().
+// cb.Run(); // Foo() is now transferred to |arg| and deleted.
+// cb.Run(); // This CHECK()s since Foo() already been used once.
+//
+// We offer 2 syntaxes for calling Passed(). The first takes an rvalue and
+// is best suited for use with the return value of a function or other temporary
+// rvalues. The second takes a pointer to the scoper and is just syntactic sugar
+// to avoid having to write Passed(std::move(scoper)).
+//
+// Both versions of Passed() prevent T from being an lvalue reference. The first
+// via use of enable_if, and the second takes a T* which will not bind to T&.
+template <typename T,
+ std::enable_if_t<!std::is_lvalue_reference<T>::value>* = nullptr>
+static inline internal::PassedWrapper<T> Passed(T&& scoper) {
+ return internal::PassedWrapper<T>(std::move(scoper));
+}
+template <typename T>
+static inline internal::PassedWrapper<T> Passed(T* scoper) {
+ return internal::PassedWrapper<T>(std::move(*scoper));
+}
+
+// IgnoreResult() is used to adapt a function or Callback with a return type to
+// one with a void return. This is most useful if you have a function with,
+// say, a pesky ignorable bool return that you want to use with PostTask or
+// something else that expect a Callback with a void return.
+//
+// EXAMPLE OF IgnoreResult():
+//
+// int DoSomething(int arg) { cout << arg << endl; }
+//
+// // Assign to a Callback with a void return type.
+// Callback<void(int)> cb = Bind(IgnoreResult(&DoSomething));
+// cb->Run(1); // Prints "1".
+//
+// // Prints "1" on |ml|.
+// ml->PostTask(FROM_HERE, Bind(IgnoreResult(&DoSomething), 1);
+template <typename T>
+static inline internal::IgnoreResultHelper<T> IgnoreResult(T data) {
+ return internal::IgnoreResultHelper<T>(std::move(data));
+}
+
+#if defined(OS_MACOSX) && !HAS_FEATURE(objc_arc)
+
+// RetainBlock() is used to adapt an Objective-C block when Automated Reference
+// Counting (ARC) is disabled. This is unnecessary when ARC is enabled, as the
+// BindOnce and BindRepeating already support blocks then.
+//
+// EXAMPLE OF RetainBlock():
+//
+// // Wrap the block and bind it to a callback.
+// Callback<void(int)> cb = Bind(RetainBlock(^(int n) { NSLog(@"%d", n); }));
+// cb.Run(1); // Logs "1".
+template <typename R, typename... Args>
+base::mac::ScopedBlock<R (^)(Args...)> RetainBlock(R (^block)(Args...)) {
+ return base::mac::ScopedBlock<R (^)(Args...)>(block,
+ base::scoped_policy::RETAIN);
+}
+
+#endif // defined(OS_MACOSX) && !HAS_FEATURE(objc_arc)
+
} // namespace base
#endif // BASE_BIND_H_
diff --git a/base/bind_helpers.cc b/base/bind_helpers.cc
deleted file mode 100644
index f1fe46da47..0000000000
--- a/base/bind_helpers.cc
+++ /dev/null
@@ -1,14 +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/bind_helpers.h"
-
-#include "base/callback.h"
-
-namespace base {
-
-void DoNothing() {
-}
-
-} // namespace base
diff --git a/base/bind_helpers.h b/base/bind_helpers.h
index 7b3d7d3474..15961e6059 100644
--- a/base/bind_helpers.h
+++ b/base/bind_helpers.h
@@ -2,161 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// This defines a set of argument wrappers and related factory methods that
-// can be used specify the refcounting and reference semantics of arguments
-// that are bound by the Bind() function in base/bind.h.
-//
-// It also defines a set of simple functions and utilities that people want
-// when using Callback<> and Bind().
-//
-//
-// ARGUMENT BINDING WRAPPERS
-//
-// The wrapper functions are base::Unretained(), base::Owned(), base::Passed(),
-// base::ConstRef(), and base::IgnoreResult().
-//
-// Unretained() allows Bind() to bind a non-refcounted class, and to disable
-// refcounting on arguments that are refcounted objects.
-//
-// Owned() transfers ownership of an object to the Callback resulting from
-// bind; the object will be deleted when the Callback is deleted.
-//
-// Passed() is for transferring movable-but-not-copyable types (eg. unique_ptr)
-// through a Callback. Logically, this signifies a destructive transfer of
-// the state of the argument into the target function. Invoking
-// Callback::Run() twice on a Callback that was created with a Passed()
-// argument will CHECK() because the first invocation would have already
-// transferred ownership to the target function.
-//
-// RetainedRef() accepts a ref counted object and retains a reference to it.
-// When the callback is called, the object is passed as a raw pointer.
-//
-// ConstRef() allows binding a constant reference to an argument rather
-// than a copy.
-//
-// IgnoreResult() is used to adapt a function or Callback with a return type to
-// one with a void return. This is most useful if you have a function with,
-// say, a pesky ignorable bool return that you want to use with PostTask or
-// something else that expect a Callback with a void return.
-//
-// EXAMPLE OF Unretained():
-//
-// class Foo {
-// public:
-// void func() { cout << "Foo:f" << endl; }
-// };
-//
-// // In some function somewhere.
-// Foo foo;
-// Closure foo_callback =
-// Bind(&Foo::func, Unretained(&foo));
-// foo_callback.Run(); // Prints "Foo:f".
-//
-// Without the Unretained() wrapper on |&foo|, the above call would fail
-// to compile because Foo does not support the AddRef() and Release() methods.
-//
-//
-// EXAMPLE OF Owned():
-//
-// void foo(int* arg) { cout << *arg << endl }
-//
-// int* pn = new int(1);
-// Closure foo_callback = Bind(&foo, Owned(pn));
-//
-// foo_callback.Run(); // Prints "1"
-// foo_callback.Run(); // Prints "1"
-// *n = 2;
-// foo_callback.Run(); // Prints "2"
-//
-// foo_callback.Reset(); // |pn| is deleted. Also will happen when
-// // |foo_callback| goes out of scope.
-//
-// Without Owned(), someone would have to know to delete |pn| when the last
-// reference to the Callback is deleted.
-//
-// EXAMPLE OF RetainedRef():
-//
-// void foo(RefCountedBytes* bytes) {}
-//
-// scoped_refptr<RefCountedBytes> bytes = ...;
-// Closure callback = Bind(&foo, base::RetainedRef(bytes));
-// callback.Run();
-//
-// Without RetainedRef, the scoped_refptr would try to implicitly convert to
-// a raw pointer and fail compilation:
-//
-// Closure callback = Bind(&foo, bytes); // ERROR!
-//
-//
-// EXAMPLE OF ConstRef():
-//
-// void foo(int arg) { cout << arg << endl }
-//
-// int n = 1;
-// Closure no_ref = Bind(&foo, n);
-// Closure has_ref = Bind(&foo, ConstRef(n));
-//
-// no_ref.Run(); // Prints "1"
-// has_ref.Run(); // Prints "1"
-//
-// n = 2;
-// no_ref.Run(); // Prints "1"
-// has_ref.Run(); // Prints "2"
-//
-// Note that because ConstRef() takes a reference on |n|, |n| must outlive all
-// its bound callbacks.
-//
-//
-// EXAMPLE OF IgnoreResult():
-//
-// int DoSomething(int arg) { cout << arg << endl; }
-//
-// // Assign to a Callback with a void return type.
-// Callback<void(int)> cb = Bind(IgnoreResult(&DoSomething));
-// cb->Run(1); // Prints "1".
-//
-// // Prints "1" on |ml|.
-// ml->PostTask(FROM_HERE, Bind(IgnoreResult(&DoSomething), 1);
-//
-//
-// EXAMPLE OF Passed():
-//
-// void TakesOwnership(std::unique_ptr<Foo> arg) { }
-// std::unique_ptr<Foo> CreateFoo() { return std::unique_ptr<Foo>(new Foo());
-// }
-//
-// std::unique_ptr<Foo> f(new Foo());
-//
-// // |cb| is given ownership of Foo(). |f| is now NULL.
-// // You can use std::move(f) in place of &f, but it's more verbose.
-// Closure cb = Bind(&TakesOwnership, Passed(&f));
-//
-// // Run was never called so |cb| still owns Foo() and deletes
-// // it on Reset().
-// cb.Reset();
-//
-// // |cb| is given a new Foo created by CreateFoo().
-// cb = Bind(&TakesOwnership, Passed(CreateFoo()));
-//
-// // |arg| in TakesOwnership() is given ownership of Foo(). |cb|
-// // no longer owns Foo() and, if reset, would not delete Foo().
-// cb.Run(); // Foo() is now transferred to |arg| and deleted.
-// cb.Run(); // This CHECK()s since Foo() already been used once.
-//
-// Passed() is particularly useful with PostTask() when you are transferring
-// ownership of an argument into a task, but don't necessarily know if the
-// task will always be executed. This can happen if the task is cancellable
-// or if it is posted to a TaskRunner.
-//
-//
-// SIMPLE FUNCTIONS AND UTILITIES.
-//
-// DoNothing() - Useful for creating a Closure that does nothing when called.
-// DeletePointer<T>() - Useful for creating a Closure that will delete a
-// pointer when invoked. Only use this when necessary.
-// In most cases MessageLoop::DeleteSoon() is a better
-// fit.
-
#ifndef BASE_BIND_HELPERS_H_
#define BASE_BIND_HELPERS_H_
@@ -165,406 +10,59 @@
#include <type_traits>
#include <utility>
+#include "base/bind.h"
#include "base/callback.h"
#include "base/memory/weak_ptr.h"
#include "build/build_config.h"
-namespace base {
-
-template <typename T>
-struct IsWeakReceiver;
-
-template <typename>
-struct BindUnwrapTraits;
-
-namespace internal {
-
-template <typename Functor, typename SFINAE = void>
-struct FunctorTraits;
-
-template <typename T>
-class UnretainedWrapper {
- public:
- explicit UnretainedWrapper(T* o) : ptr_(o) {}
- T* get() const { return ptr_; }
- private:
- T* ptr_;
-};
-
-template <typename T>
-class ConstRefWrapper {
- public:
- explicit ConstRefWrapper(const T& o) : ptr_(&o) {}
- const T& get() const { return *ptr_; }
- private:
- const T* ptr_;
-};
-
-template <typename T>
-class RetainedRefWrapper {
- public:
- explicit RetainedRefWrapper(T* o) : ptr_(o) {}
- explicit RetainedRefWrapper(scoped_refptr<T> o) : ptr_(std::move(o)) {}
- T* get() const { return ptr_.get(); }
- private:
- scoped_refptr<T> ptr_;
-};
-
-template <typename T>
-struct IgnoreResultHelper {
- explicit IgnoreResultHelper(T functor) : functor_(std::move(functor)) {}
- explicit operator bool() const { return !!functor_; }
+// This defines a set of simple functions and utilities that people want when
+// using Callback<> and Bind().
- T functor_;
-};
+namespace base {
-// An alternate implementation is to avoid the destructive copy, and instead
-// specialize ParamTraits<> for OwnedWrapper<> to change the StorageType to
-// a class that is essentially a std::unique_ptr<>.
-//
-// The current implementation has the benefit though of leaving ParamTraits<>
-// fully in callback_internal.h as well as avoiding type conversions during
-// storage.
-template <typename T>
-class OwnedWrapper {
+// Creates a null callback.
+class BASE_EXPORT NullCallback {
public:
- explicit OwnedWrapper(T* o) : ptr_(o) {}
- ~OwnedWrapper() { delete ptr_; }
- T* get() const { return ptr_; }
- OwnedWrapper(OwnedWrapper&& other) {
- ptr_ = other.ptr_;
- other.ptr_ = NULL;
+ template <typename R, typename... Args>
+ operator RepeatingCallback<R(Args...)>() const {
+ return RepeatingCallback<R(Args...)>();
}
-
- private:
- mutable T* ptr_;
-};
-
-// PassedWrapper is a copyable adapter for a scoper that ignores const.
-//
-// It is needed to get around the fact that Bind() takes a const reference to
-// all its arguments. Because Bind() takes a const reference to avoid
-// unnecessary copies, it is incompatible with movable-but-not-copyable
-// types; doing a destructive "move" of the type into Bind() would violate
-// the const correctness.
-//
-// This conundrum cannot be solved without either C++11 rvalue references or
-// a O(2^n) blowup of Bind() templates to handle each combination of regular
-// types and movable-but-not-copyable types. Thus we introduce a wrapper type
-// that is copyable to transmit the correct type information down into
-// BindState<>. Ignoring const in this type makes sense because it is only
-// created when we are explicitly trying to do a destructive move.
-//
-// Two notes:
-// 1) PassedWrapper supports any type that has a move constructor, however
-// the type will need to be specifically whitelisted in order for it to be
-// bound to a Callback. We guard this explicitly at the call of Passed()
-// to make for clear errors. Things not given to Passed() will be forwarded
-// and stored by value which will not work for general move-only types.
-// 2) is_valid_ is distinct from NULL because it is valid to bind a "NULL"
-// scoper to a Callback and allow the Callback to execute once.
-template <typename T>
-class PassedWrapper {
- public:
- explicit PassedWrapper(T&& scoper)
- : is_valid_(true), scoper_(std::move(scoper)) {}
- PassedWrapper(PassedWrapper&& other)
- : is_valid_(other.is_valid_), scoper_(std::move(other.scoper_)) {}
- T Take() const {
- CHECK(is_valid_);
- is_valid_ = false;
- return std::move(scoper_);
+ template <typename R, typename... Args>
+ operator OnceCallback<R(Args...)>() const {
+ return OnceCallback<R(Args...)>();
}
-
- private:
- mutable bool is_valid_;
- mutable T scoper_;
-};
-
-template <typename T>
-using Unwrapper = BindUnwrapTraits<typename std::decay<T>::type>;
-
-template <typename T>
-auto Unwrap(T&& o) -> decltype(Unwrapper<T>::Unwrap(std::forward<T>(o))) {
- return Unwrapper<T>::Unwrap(std::forward<T>(o));
-}
-
-// IsWeakMethod is a helper that determine if we are binding a WeakPtr<> to a
-// method. It is used internally by Bind() to select the correct
-// InvokeHelper that will no-op itself in the event the WeakPtr<> for
-// the target object is invalidated.
-//
-// The first argument should be the type of the object that will be received by
-// the method.
-template <bool is_method, typename... Args>
-struct IsWeakMethod : std::false_type {};
-
-template <typename T, typename... Args>
-struct IsWeakMethod<true, T, Args...> : IsWeakReceiver<T> {};
-
-// Packs a list of types to hold them in a single type.
-template <typename... Types>
-struct TypeList {};
-
-// Used for DropTypeListItem implementation.
-template <size_t n, typename List>
-struct DropTypeListItemImpl;
-
-// Do not use enable_if and SFINAE here to avoid MSVC2013 compile failure.
-template <size_t n, typename T, typename... List>
-struct DropTypeListItemImpl<n, TypeList<T, List...>>
- : DropTypeListItemImpl<n - 1, TypeList<List...>> {};
-
-template <typename T, typename... List>
-struct DropTypeListItemImpl<0, TypeList<T, List...>> {
- using Type = TypeList<T, List...>;
-};
-
-template <>
-struct DropTypeListItemImpl<0, TypeList<>> {
- using Type = TypeList<>;
-};
-
-// A type-level function that drops |n| list item from given TypeList.
-template <size_t n, typename List>
-using DropTypeListItem = typename DropTypeListItemImpl<n, List>::Type;
-
-// Used for TakeTypeListItem implementation.
-template <size_t n, typename List, typename... Accum>
-struct TakeTypeListItemImpl;
-
-// Do not use enable_if and SFINAE here to avoid MSVC2013 compile failure.
-template <size_t n, typename T, typename... List, typename... Accum>
-struct TakeTypeListItemImpl<n, TypeList<T, List...>, Accum...>
- : TakeTypeListItemImpl<n - 1, TypeList<List...>, Accum..., T> {};
-
-template <typename T, typename... List, typename... Accum>
-struct TakeTypeListItemImpl<0, TypeList<T, List...>, Accum...> {
- using Type = TypeList<Accum...>;
-};
-
-template <typename... Accum>
-struct TakeTypeListItemImpl<0, TypeList<>, Accum...> {
- using Type = TypeList<Accum...>;
-};
-
-// A type-level function that takes first |n| list item from given TypeList.
-// E.g. TakeTypeListItem<3, TypeList<A, B, C, D>> is evaluated to
-// TypeList<A, B, C>.
-template <size_t n, typename List>
-using TakeTypeListItem = typename TakeTypeListItemImpl<n, List>::Type;
-
-// Used for ConcatTypeLists implementation.
-template <typename List1, typename List2>
-struct ConcatTypeListsImpl;
-
-template <typename... Types1, typename... Types2>
-struct ConcatTypeListsImpl<TypeList<Types1...>, TypeList<Types2...>> {
- using Type = TypeList<Types1..., Types2...>;
-};
-
-// A type-level function that concats two TypeLists.
-template <typename List1, typename List2>
-using ConcatTypeLists = typename ConcatTypeListsImpl<List1, List2>::Type;
-
-// Used for MakeFunctionType implementation.
-template <typename R, typename ArgList>
-struct MakeFunctionTypeImpl;
-
-template <typename R, typename... Args>
-struct MakeFunctionTypeImpl<R, TypeList<Args...>> {
- // MSVC 2013 doesn't support Type Alias of function types.
- // Revisit this after we update it to newer version.
- typedef R Type(Args...);
-};
-
-// A type-level function that constructs a function type that has |R| as its
-// return type and has TypeLists items as its arguments.
-template <typename R, typename ArgList>
-using MakeFunctionType = typename MakeFunctionTypeImpl<R, ArgList>::Type;
-
-// Used for ExtractArgs and ExtractReturnType.
-template <typename Signature>
-struct ExtractArgsImpl;
-
-template <typename R, typename... Args>
-struct ExtractArgsImpl<R(Args...)> {
- using ReturnType = R;
- using ArgsList = TypeList<Args...>;
};
-// A type-level function that extracts function arguments into a TypeList.
-// E.g. ExtractArgs<R(A, B, C)> is evaluated to TypeList<A, B, C>.
-template <typename Signature>
-using ExtractArgs = typename ExtractArgsImpl<Signature>::ArgsList;
-
-// A type-level function that extracts the return type of a function.
-// E.g. ExtractReturnType<R(A, B, C)> is evaluated to R.
-template <typename Signature>
-using ExtractReturnType = typename ExtractArgsImpl<Signature>::ReturnType;
-
-} // namespace internal
-
-template <typename T>
-static inline internal::UnretainedWrapper<T> Unretained(T* o) {
- return internal::UnretainedWrapper<T>(o);
-}
-
-template <typename T>
-static inline internal::RetainedRefWrapper<T> RetainedRef(T* o) {
- return internal::RetainedRefWrapper<T>(o);
-}
-
-template <typename T>
-static inline internal::RetainedRefWrapper<T> RetainedRef(scoped_refptr<T> o) {
- return internal::RetainedRefWrapper<T>(std::move(o));
-}
-
-template <typename T>
-static inline internal::ConstRefWrapper<T> ConstRef(const T& o) {
- return internal::ConstRefWrapper<T>(o);
-}
-
-template <typename T>
-static inline internal::OwnedWrapper<T> Owned(T* o) {
- return internal::OwnedWrapper<T>(o);
-}
-
-// We offer 2 syntaxes for calling Passed(). The first takes an rvalue and
-// is best suited for use with the return value of a function or other temporary
-// rvalues. The second takes a pointer to the scoper and is just syntactic sugar
-// to avoid having to write Passed(std::move(scoper)).
-//
-// Both versions of Passed() prevent T from being an lvalue reference. The first
-// via use of enable_if, and the second takes a T* which will not bind to T&.
-template <typename T,
- typename std::enable_if<!std::is_lvalue_reference<T>::value>::type* =
- nullptr>
-static inline internal::PassedWrapper<T> Passed(T&& scoper) {
- return internal::PassedWrapper<T>(std::move(scoper));
-}
-template <typename T>
-static inline internal::PassedWrapper<T> Passed(T* scoper) {
- return internal::PassedWrapper<T>(std::move(*scoper));
-}
-
-template <typename T>
-static inline internal::IgnoreResultHelper<T> IgnoreResult(T data) {
- return internal::IgnoreResultHelper<T>(std::move(data));
-}
-
-BASE_EXPORT void DoNothing();
-
-template<typename T>
-void DeletePointer(T* obj) {
- delete obj;
-}
-
-// An injection point to control |this| pointer behavior on a method invocation.
-// If IsWeakReceiver<> is true_type for |T| and |T| is used for a receiver of a
-// method, base::Bind cancels the method invocation if the receiver is tested as
-// false.
-// E.g. Foo::bar() is not called:
-// struct Foo : base::SupportsWeakPtr<Foo> {
-// void bar() {}
-// };
-//
-// WeakPtr<Foo> oo = nullptr;
-// base::Bind(&Foo::bar, oo).Run();
-template <typename T>
-struct IsWeakReceiver : std::false_type {};
-
-template <typename T>
-struct IsWeakReceiver<internal::ConstRefWrapper<T>> : IsWeakReceiver<T> {};
-
-template <typename T>
-struct IsWeakReceiver<WeakPtr<T>> : std::true_type {};
-
-// An injection point to control how bound objects passed to the target
-// function. BindUnwrapTraits<>::Unwrap() is called for each bound objects right
-// before the target function is invoked.
-template <typename>
-struct BindUnwrapTraits {
- template <typename T>
- static T&& Unwrap(T&& o) { return std::forward<T>(o); }
-};
-
-template <typename T>
-struct BindUnwrapTraits<internal::UnretainedWrapper<T>> {
- static T* Unwrap(const internal::UnretainedWrapper<T>& o) {
- return o.get();
+// Creates a callback that does nothing when called.
+class BASE_EXPORT DoNothing {
+ public:
+ template <typename... Args>
+ operator RepeatingCallback<void(Args...)>() const {
+ return Repeatedly<Args...>();
}
-};
-
-template <typename T>
-struct BindUnwrapTraits<internal::ConstRefWrapper<T>> {
- static const T& Unwrap(const internal::ConstRefWrapper<T>& o) {
- return o.get();
+ template <typename... Args>
+ operator OnceCallback<void(Args...)>() const {
+ return Once<Args...>();
}
-};
-
-template <typename T>
-struct BindUnwrapTraits<internal::RetainedRefWrapper<T>> {
- static T* Unwrap(const internal::RetainedRefWrapper<T>& o) {
- return o.get();
+ // Explicit way of specifying a specific callback type when the compiler can't
+ // deduce it.
+ template <typename... Args>
+ static RepeatingCallback<void(Args...)> Repeatedly() {
+ return BindRepeating([](Args... args) {});
}
-};
-
-template <typename T>
-struct BindUnwrapTraits<internal::OwnedWrapper<T>> {
- static T* Unwrap(const internal::OwnedWrapper<T>& o) {
- return o.get();
+ template <typename... Args>
+ static OnceCallback<void(Args...)> Once() {
+ return BindOnce([](Args... args) {});
}
};
+// Useful for creating a Closure that will delete a pointer when invoked. Only
+// use this when necessary. In most cases MessageLoop::DeleteSoon() is a better
+// fit.
template <typename T>
-struct BindUnwrapTraits<internal::PassedWrapper<T>> {
- static T Unwrap(const internal::PassedWrapper<T>& o) {
- return o.Take();
- }
-};
-
-// CallbackCancellationTraits allows customization of Callback's cancellation
-// semantics. By default, callbacks are not cancellable. A specialization should
-// set is_cancellable = true and implement an IsCancelled() that returns if the
-// callback should be cancelled.
-template <typename Functor, typename BoundArgsTuple, typename SFINAE = void>
-struct CallbackCancellationTraits {
- static constexpr bool is_cancellable = false;
-};
-
-// Specialization for method bound to weak pointer receiver.
-template <typename Functor, typename... BoundArgs>
-struct CallbackCancellationTraits<
- Functor,
- std::tuple<BoundArgs...>,
- typename std::enable_if<
- internal::IsWeakMethod<internal::FunctorTraits<Functor>::is_method,
- BoundArgs...>::value>::type> {
- static constexpr bool is_cancellable = true;
-
- template <typename Receiver, typename... Args>
- static bool IsCancelled(const Functor&,
- const Receiver& receiver,
- const Args&...) {
- return !receiver;
- }
-};
-
-// Specialization for a nested bind.
-template <typename Signature,
- typename... BoundArgs,
- internal::CopyMode copy_mode,
- internal::RepeatMode repeat_mode>
-struct CallbackCancellationTraits<Callback<Signature, copy_mode, repeat_mode>,
- std::tuple<BoundArgs...>> {
- static constexpr bool is_cancellable = true;
-
- template <typename Functor>
- static bool IsCancelled(const Functor& functor, const BoundArgs&...) {
- return functor.IsCancelled();
- }
-};
+void DeletePointer(T* obj) {
+ delete obj;
+}
} // namespace base
diff --git a/base/bind_internal.h b/base/bind_internal.h
index 8988bdca22..4ebecfaf77 100644
--- a/base/bind_internal.h
+++ b/base/bind_internal.h
@@ -7,19 +7,19 @@
#include <stddef.h>
-#include <tuple>
#include <type_traits>
+#include <utility>
-#include "base/bind_helpers.h"
#include "base/callback_internal.h"
+#include "base/compiler_specific.h"
#include "base/memory/raw_scoped_refptr_mismatch_checker.h"
#include "base/memory/weak_ptr.h"
#include "base/template_util.h"
-#include "base/tuple.h"
#include "build/build_config.h"
-namespace base {
-namespace internal {
+#if defined(OS_MACOSX) && !HAS_FEATURE(objc_arc)
+#include "base/mac/scoped_block.h"
+#endif
// See base/callback.h for user documentation.
//
@@ -46,25 +46,257 @@ namespace internal {
// BindState<> -- Stores the curried parameters, and is the main entry point
// into the Bind() system.
-template <typename...>
-struct make_void {
- using type = void;
+namespace base {
+
+template <typename T>
+struct IsWeakReceiver;
+
+template <typename>
+struct BindUnwrapTraits;
+
+template <typename Functor, typename BoundArgsTuple, typename SFINAE = void>
+struct CallbackCancellationTraits;
+
+namespace internal {
+
+template <typename Functor, typename SFINAE = void>
+struct FunctorTraits;
+
+template <typename T>
+class UnretainedWrapper {
+ public:
+ explicit UnretainedWrapper(T* o) : ptr_(o) {}
+ T* get() const { return ptr_; }
+
+ private:
+ T* ptr_;
+};
+
+template <typename T>
+class ConstRefWrapper {
+ public:
+ explicit ConstRefWrapper(const T& o) : ptr_(&o) {}
+ const T& get() const { return *ptr_; }
+
+ private:
+ const T* ptr_;
+};
+
+template <typename T>
+class RetainedRefWrapper {
+ public:
+ explicit RetainedRefWrapper(T* o) : ptr_(o) {}
+ explicit RetainedRefWrapper(scoped_refptr<T> o) : ptr_(std::move(o)) {}
+ T* get() const { return ptr_.get(); }
+
+ private:
+ scoped_refptr<T> ptr_;
+};
+
+template <typename T>
+struct IgnoreResultHelper {
+ explicit IgnoreResultHelper(T functor) : functor_(std::move(functor)) {}
+ explicit operator bool() const { return !!functor_; }
+
+ T functor_;
+};
+
+// An alternate implementation is to avoid the destructive copy, and instead
+// specialize ParamTraits<> for OwnedWrapper<> to change the StorageType to
+// a class that is essentially a std::unique_ptr<>.
+//
+// The current implementation has the benefit though of leaving ParamTraits<>
+// fully in callback_internal.h as well as avoiding type conversions during
+// storage.
+template <typename T>
+class OwnedWrapper {
+ public:
+ explicit OwnedWrapper(T* o) : ptr_(o) {}
+ ~OwnedWrapper() { delete ptr_; }
+ T* get() const { return ptr_; }
+ OwnedWrapper(OwnedWrapper&& other) {
+ ptr_ = other.ptr_;
+ other.ptr_ = NULL;
+ }
+
+ private:
+ mutable T* ptr_;
+};
+
+// PassedWrapper is a copyable adapter for a scoper that ignores const.
+//
+// It is needed to get around the fact that Bind() takes a const reference to
+// all its arguments. Because Bind() takes a const reference to avoid
+// unnecessary copies, it is incompatible with movable-but-not-copyable
+// types; doing a destructive "move" of the type into Bind() would violate
+// the const correctness.
+//
+// This conundrum cannot be solved without either C++11 rvalue references or
+// a O(2^n) blowup of Bind() templates to handle each combination of regular
+// types and movable-but-not-copyable types. Thus we introduce a wrapper type
+// that is copyable to transmit the correct type information down into
+// BindState<>. Ignoring const in this type makes sense because it is only
+// created when we are explicitly trying to do a destructive move.
+//
+// Two notes:
+// 1) PassedWrapper supports any type that has a move constructor, however
+// the type will need to be specifically whitelisted in order for it to be
+// bound to a Callback. We guard this explicitly at the call of Passed()
+// to make for clear errors. Things not given to Passed() will be forwarded
+// and stored by value which will not work for general move-only types.
+// 2) is_valid_ is distinct from NULL because it is valid to bind a "NULL"
+// scoper to a Callback and allow the Callback to execute once.
+template <typename T>
+class PassedWrapper {
+ public:
+ explicit PassedWrapper(T&& scoper)
+ : is_valid_(true), scoper_(std::move(scoper)) {}
+ PassedWrapper(PassedWrapper&& other)
+ : is_valid_(other.is_valid_), scoper_(std::move(other.scoper_)) {}
+ T Take() const {
+ CHECK(is_valid_);
+ is_valid_ = false;
+ return std::move(scoper_);
+ }
+
+ private:
+ mutable bool is_valid_;
+ mutable T scoper_;
+};
+
+template <typename T>
+using Unwrapper = BindUnwrapTraits<std::decay_t<T>>;
+
+template <typename T>
+decltype(auto) Unwrap(T&& o) {
+ return Unwrapper<T>::Unwrap(std::forward<T>(o));
+}
+
+// IsWeakMethod is a helper that determine if we are binding a WeakPtr<> to a
+// method. It is used internally by Bind() to select the correct
+// InvokeHelper that will no-op itself in the event the WeakPtr<> for
+// the target object is invalidated.
+//
+// The first argument should be the type of the object that will be received by
+// the method.
+template <bool is_method, typename... Args>
+struct IsWeakMethod : std::false_type {};
+
+template <typename T, typename... Args>
+struct IsWeakMethod<true, T, Args...> : IsWeakReceiver<T> {};
+
+// Packs a list of types to hold them in a single type.
+template <typename... Types>
+struct TypeList {};
+
+// Used for DropTypeListItem implementation.
+template <size_t n, typename List>
+struct DropTypeListItemImpl;
+
+// Do not use enable_if and SFINAE here to avoid MSVC2013 compile failure.
+template <size_t n, typename T, typename... List>
+struct DropTypeListItemImpl<n, TypeList<T, List...>>
+ : DropTypeListItemImpl<n - 1, TypeList<List...>> {};
+
+template <typename T, typename... List>
+struct DropTypeListItemImpl<0, TypeList<T, List...>> {
+ using Type = TypeList<T, List...>;
+};
+
+template <>
+struct DropTypeListItemImpl<0, TypeList<>> {
+ using Type = TypeList<>;
};
-// A clone of C++17 std::void_t.
-// Unlike the original version, we need |make_void| as a helper struct to avoid
-// a C++14 defect.
-// ref: http://en.cppreference.com/w/cpp/types/void_t
-// ref: http://open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1558
-template <typename... Ts>
-using void_t = typename make_void<Ts...>::type;
+// A type-level function that drops |n| list item from given TypeList.
+template <size_t n, typename List>
+using DropTypeListItem = typename DropTypeListItemImpl<n, List>::Type;
+
+// Used for TakeTypeListItem implementation.
+template <size_t n, typename List, typename... Accum>
+struct TakeTypeListItemImpl;
+
+// Do not use enable_if and SFINAE here to avoid MSVC2013 compile failure.
+template <size_t n, typename T, typename... List, typename... Accum>
+struct TakeTypeListItemImpl<n, TypeList<T, List...>, Accum...>
+ : TakeTypeListItemImpl<n - 1, TypeList<List...>, Accum..., T> {};
+
+template <typename T, typename... List, typename... Accum>
+struct TakeTypeListItemImpl<0, TypeList<T, List...>, Accum...> {
+ using Type = TypeList<Accum...>;
+};
+
+template <typename... Accum>
+struct TakeTypeListItemImpl<0, TypeList<>, Accum...> {
+ using Type = TypeList<Accum...>;
+};
+
+// A type-level function that takes first |n| list item from given TypeList.
+// E.g. TakeTypeListItem<3, TypeList<A, B, C, D>> is evaluated to
+// TypeList<A, B, C>.
+template <size_t n, typename List>
+using TakeTypeListItem = typename TakeTypeListItemImpl<n, List>::Type;
+
+// Used for ConcatTypeLists implementation.
+template <typename List1, typename List2>
+struct ConcatTypeListsImpl;
+
+template <typename... Types1, typename... Types2>
+struct ConcatTypeListsImpl<TypeList<Types1...>, TypeList<Types2...>> {
+ using Type = TypeList<Types1..., Types2...>;
+};
+
+// A type-level function that concats two TypeLists.
+template <typename List1, typename List2>
+using ConcatTypeLists = typename ConcatTypeListsImpl<List1, List2>::Type;
+
+// Used for MakeFunctionType implementation.
+template <typename R, typename ArgList>
+struct MakeFunctionTypeImpl;
+
+template <typename R, typename... Args>
+struct MakeFunctionTypeImpl<R, TypeList<Args...>> {
+ // MSVC 2013 doesn't support Type Alias of function types.
+ // Revisit this after we update it to newer version.
+ typedef R Type(Args...);
+};
+
+// A type-level function that constructs a function type that has |R| as its
+// return type and has TypeLists items as its arguments.
+template <typename R, typename ArgList>
+using MakeFunctionType = typename MakeFunctionTypeImpl<R, ArgList>::Type;
+
+// Used for ExtractArgs and ExtractReturnType.
+template <typename Signature>
+struct ExtractArgsImpl;
+
+template <typename R, typename... Args>
+struct ExtractArgsImpl<R(Args...)> {
+ using ReturnType = R;
+ using ArgsList = TypeList<Args...>;
+};
+
+// A type-level function that extracts function arguments into a TypeList.
+// E.g. ExtractArgs<R(A, B, C)> is evaluated to TypeList<A, B, C>.
+template <typename Signature>
+using ExtractArgs = typename ExtractArgsImpl<Signature>::ArgsList;
+
+// A type-level function that extracts the return type of a function.
+// E.g. ExtractReturnType<R(A, B, C)> is evaluated to R.
+template <typename Signature>
+using ExtractReturnType = typename ExtractArgsImpl<Signature>::ReturnType;
template <typename Callable,
typename Signature = decltype(&Callable::operator())>
struct ExtractCallableRunTypeImpl;
template <typename Callable, typename R, typename... Args>
-struct ExtractCallableRunTypeImpl<Callable, R(Callable::*)(Args...) const> {
+struct ExtractCallableRunTypeImpl<Callable, R (Callable::*)(Args...)> {
+ using Type = R(Args...);
+};
+
+template <typename Callable, typename R, typename... Args>
+struct ExtractCallableRunTypeImpl<Callable, R (Callable::*)(Args...) const> {
using Type = R(Args...);
};
@@ -78,27 +310,23 @@ template <typename Callable>
using ExtractCallableRunType =
typename ExtractCallableRunTypeImpl<Callable>::Type;
-// IsConvertibleToRunType<Functor> is std::true_type if |Functor| has operator()
-// and convertible to the corresponding function pointer. Otherwise, it's
-// std::false_type.
+// IsCallableObject<Functor> is std::true_type if |Functor| has operator().
+// Otherwise, it's std::false_type.
// Example:
-// IsConvertibleToRunType<void(*)()>::value is false.
+// IsCallableObject<void(*)()>::value is false.
//
// struct Foo {};
-// IsConvertibleToRunType<void(Foo::*)()>::value is false.
-//
-// auto f = []() {};
-// IsConvertibleToRunType<decltype(f)>::value is true.
+// IsCallableObject<void(Foo::*)()>::value is false.
//
// int i = 0;
-// auto g = [i]() {};
-// IsConvertibleToRunType<decltype(g)>::value is false.
+// auto f = [i]() {};
+// IsCallableObject<decltype(f)>::value is false.
template <typename Functor, typename SFINAE = void>
-struct IsConvertibleToRunType : std::false_type {};
+struct IsCallableObject : std::false_type {};
template <typename Callable>
-struct IsConvertibleToRunType<Callable, void_t<decltype(&Callable::operator())>>
- : std::is_convertible<Callable, ExtractCallableRunType<Callable>*> {};
+struct IsCallableObject<Callable, void_t<decltype(&Callable::operator())>>
+ : std::true_type {};
// HasRefCountedTypeAsRawPtr selects true_type when any of the |Args| is a raw
// pointer to a RefCounted type.
@@ -112,9 +340,9 @@ struct HasRefCountedTypeAsRawPtr : std::false_type {};
// parameters recursively.
template <typename T, typename... Args>
struct HasRefCountedTypeAsRawPtr<T, Args...>
- : std::conditional<NeedsScopedRefptrButGetsRawPtr<T>::value,
- std::true_type,
- HasRefCountedTypeAsRawPtr<Args...>>::type {};
+ : std::conditional_t<NeedsScopedRefptrButGetsRawPtr<T>::value,
+ std::true_type,
+ HasRefCountedTypeAsRawPtr<Args...>> {};
// ForceVoidReturn<>
//
@@ -133,22 +361,37 @@ struct ForceVoidReturn<R(Args...)> {
template <typename Functor, typename SFINAE>
struct FunctorTraits;
-// For a callable type that is convertible to the corresponding function type.
+// For empty callable types.
// This specialization is intended to allow binding captureless lambdas by
-// base::Bind(), based on the fact that captureless lambdas can be convertible
-// to the function type while capturing lambdas can't.
+// base::Bind(), based on the fact that captureless lambdas are empty while
+// capturing lambdas are not. This also allows any functors as far as it's an
+// empty class.
+// Example:
+//
+// // Captureless lambdas are allowed.
+// []() {return 42;};
+//
+// // Capturing lambdas are *not* allowed.
+// int x;
+// [x]() {return x;};
+//
+// // Any empty class with operator() is allowed.
+// struct Foo {
+// void operator()() const {}
+// // No non-static member variable and no virtual functions.
+// };
template <typename Functor>
-struct FunctorTraits<
- Functor,
- typename std::enable_if<IsConvertibleToRunType<Functor>::value>::type> {
+struct FunctorTraits<Functor,
+ std::enable_if_t<IsCallableObject<Functor>::value &&
+ std::is_empty<Functor>::value>> {
using RunType = ExtractCallableRunType<Functor>;
static constexpr bool is_method = false;
static constexpr bool is_nullable = false;
- template <typename... RunArgs>
- static ExtractReturnType<RunType>
- Invoke(const Functor& functor, RunArgs&&... args) {
- return functor(std::forward<RunArgs>(args)...);
+ template <typename RunFunctor, typename... RunArgs>
+ static ExtractReturnType<RunType> Invoke(RunFunctor&& functor,
+ RunArgs&&... args) {
+ return std::forward<RunFunctor>(functor)(std::forward<RunArgs>(args)...);
}
};
@@ -159,9 +402,9 @@ struct FunctorTraits<R (*)(Args...)> {
static constexpr bool is_method = false;
static constexpr bool is_nullable = true;
- template <typename... RunArgs>
- static R Invoke(R (*function)(Args...), RunArgs&&... args) {
- return function(std::forward<RunArgs>(args)...);
+ template <typename Function, typename... RunArgs>
+ static R Invoke(Function&& function, RunArgs&&... args) {
+ return std::forward<Function>(function)(std::forward<RunArgs>(args)...);
}
};
@@ -195,6 +438,61 @@ struct FunctorTraits<R(__fastcall*)(Args...)> {
#endif // defined(OS_WIN) && !defined(ARCH_CPU_X86_64)
+#if defined(OS_MACOSX)
+
+// Support for Objective-C blocks. There are two implementation depending
+// on whether Automated Reference Counting (ARC) is enabled. When ARC is
+// enabled, then the block itself can be bound as the compiler will ensure
+// its lifetime will be correctly managed. Otherwise, require the block to
+// be wrapped in a base::mac::ScopedBlock (via base::RetainBlock) that will
+// correctly manage the block lifetime.
+//
+// The two implementation ensure that the One Definition Rule (ODR) is not
+// broken (it is not possible to write a template base::RetainBlock that would
+// work correctly both with ARC enabled and disabled).
+
+#if HAS_FEATURE(objc_arc)
+
+template <typename R, typename... Args>
+struct FunctorTraits<R (^)(Args...)> {
+ using RunType = R(Args...);
+ static constexpr bool is_method = false;
+ static constexpr bool is_nullable = true;
+
+ template <typename BlockType, typename... RunArgs>
+ static R Invoke(BlockType&& block, RunArgs&&... args) {
+ // According to LLVM documentation (§ 6.3), "local variables of automatic
+ // storage duration do not have precise lifetime." Use objc_precise_lifetime
+ // to ensure that the Objective-C block is not deallocated until it has
+ // finished executing even if the Callback<> is destroyed during the block
+ // execution.
+ // https://clang.llvm.org/docs/AutomaticReferenceCounting.html#precise-lifetime-semantics
+ __attribute__((objc_precise_lifetime)) R (^scoped_block)(Args...) = block;
+ return scoped_block(std::forward<RunArgs>(args)...);
+ }
+};
+
+#else // HAS_FEATURE(objc_arc)
+
+template <typename R, typename... Args>
+struct FunctorTraits<base::mac::ScopedBlock<R (^)(Args...)>> {
+ using RunType = R(Args...);
+ static constexpr bool is_method = false;
+ static constexpr bool is_nullable = true;
+
+ template <typename BlockType, typename... RunArgs>
+ static R Invoke(BlockType&& block, RunArgs&&... args) {
+ // Copy the block to ensure that the Objective-C block is not deallocated
+ // until it has finished executing even if the Callback<> is destroyed
+ // during the block execution.
+ base::mac::ScopedBlock<R (^)(Args...)> scoped_block(block);
+ return scoped_block.get()(std::forward<RunArgs>(args)...);
+ }
+};
+
+#endif // HAS_FEATURE(objc_arc)
+#endif // defined(OS_MACOSX)
+
// For methods.
template <typename R, typename Receiver, typename... Args>
struct FunctorTraits<R (Receiver::*)(Args...)> {
@@ -202,16 +500,11 @@ struct FunctorTraits<R (Receiver::*)(Args...)> {
static constexpr bool is_method = true;
static constexpr bool is_nullable = true;
- template <typename ReceiverPtr, typename... RunArgs>
- static R Invoke(R (Receiver::*method)(Args...),
+ template <typename Method, typename ReceiverPtr, typename... RunArgs>
+ static R Invoke(Method method,
ReceiverPtr&& receiver_ptr,
RunArgs&&... args) {
- // Clang skips CV qualifier check on a method pointer invocation when the
- // receiver is a subclass. Store the receiver into a const reference to
- // T to ensure the CV check works.
- // https://llvm.org/bugs/show_bug.cgi?id=27037
- Receiver& receiver = *receiver_ptr;
- return (receiver.*method)(std::forward<RunArgs>(args)...);
+ return ((*receiver_ptr).*method)(std::forward<RunArgs>(args)...);
}
};
@@ -222,19 +515,31 @@ struct FunctorTraits<R (Receiver::*)(Args...) const> {
static constexpr bool is_method = true;
static constexpr bool is_nullable = true;
- template <typename ReceiverPtr, typename... RunArgs>
- static R Invoke(R (Receiver::*method)(Args...) const,
+ template <typename Method, typename ReceiverPtr, typename... RunArgs>
+ static R Invoke(Method method,
ReceiverPtr&& receiver_ptr,
RunArgs&&... args) {
- // Clang skips CV qualifier check on a method pointer invocation when the
- // receiver is a subclass. Store the receiver into a const reference to
- // T to ensure the CV check works.
- // https://llvm.org/bugs/show_bug.cgi?id=27037
- const Receiver& receiver = *receiver_ptr;
- return (receiver.*method)(std::forward<RunArgs>(args)...);
+ return ((*receiver_ptr).*method)(std::forward<RunArgs>(args)...);
}
};
+#ifdef __cpp_noexcept_function_type
+// noexcept makes a distinct function type in C++17.
+// I.e. `void(*)()` and `void(*)() noexcept` are same in pre-C++17, and
+// different in C++17.
+template <typename R, typename... Args>
+struct FunctorTraits<R (*)(Args...) noexcept> : FunctorTraits<R (*)(Args...)> {
+};
+
+template <typename R, typename Receiver, typename... Args>
+struct FunctorTraits<R (Receiver::*)(Args...) noexcept>
+ : FunctorTraits<R (Receiver::*)(Args...)> {};
+
+template <typename R, typename Receiver, typename... Args>
+struct FunctorTraits<R (Receiver::*)(Args...) const noexcept>
+ : FunctorTraits<R (Receiver::*)(Args...) const> {};
+#endif
+
// For IgnoreResults.
template <typename T>
struct FunctorTraits<IgnoreResultHelper<T>> : FunctorTraits<T> {
@@ -250,10 +555,9 @@ struct FunctorTraits<IgnoreResultHelper<T>> : FunctorTraits<T> {
}
};
-// For Callbacks.
-template <typename R, typename... Args,
- CopyMode copy_mode, RepeatMode repeat_mode>
-struct FunctorTraits<Callback<R(Args...), copy_mode, repeat_mode>> {
+// For OnceCallbacks.
+template <typename R, typename... Args>
+struct FunctorTraits<OnceCallback<R(Args...)>> {
using RunType = R(Args...);
static constexpr bool is_method = false;
static constexpr bool is_nullable = true;
@@ -266,6 +570,24 @@ struct FunctorTraits<Callback<R(Args...), copy_mode, repeat_mode>> {
}
};
+// For RepeatingCallbacks.
+template <typename R, typename... Args>
+struct FunctorTraits<RepeatingCallback<R(Args...)>> {
+ using RunType = R(Args...);
+ static constexpr bool is_method = false;
+ static constexpr bool is_nullable = true;
+
+ template <typename CallbackType, typename... RunArgs>
+ static R Invoke(CallbackType&& callback, RunArgs&&... args) {
+ DCHECK(!callback.is_null());
+ return std::forward<CallbackType>(callback).Run(
+ std::forward<RunArgs>(args)...);
+ }
+};
+
+template <typename Functor>
+using MakeFunctorTraits = FunctorTraits<std::decay_t<Functor>>;
+
// InvokeHelper<>
//
// There are 2 logical InvokeHelper<> specializations: normal, WeakCalls.
@@ -281,7 +603,7 @@ template <typename ReturnType>
struct InvokeHelper<false, ReturnType> {
template <typename Functor, typename... RunArgs>
static inline ReturnType MakeItSo(Functor&& functor, RunArgs&&... args) {
- using Traits = FunctorTraits<typename std::decay<Functor>::type>;
+ using Traits = MakeFunctorTraits<Functor>;
return Traits::Invoke(std::forward<Functor>(functor),
std::forward<RunArgs>(args)...);
}
@@ -301,7 +623,7 @@ struct InvokeHelper<true, ReturnType> {
RunArgs&&... args) {
if (!weak_ptr)
return;
- using Traits = FunctorTraits<typename std::decay<Functor>::type>;
+ using Traits = MakeFunctorTraits<Functor>;
Traits::Invoke(std::forward<Functor>(functor),
std::forward<BoundWeakPtr>(weak_ptr),
std::forward<RunArgs>(args)...);
@@ -316,7 +638,8 @@ struct Invoker;
template <typename StorageType, typename R, typename... UnboundArgs>
struct Invoker<StorageType, R(UnboundArgs...)> {
- static R RunOnce(BindStateBase* base, UnboundArgs&&... unbound_args) {
+ static R RunOnce(BindStateBase* base,
+ PassingType<UnboundArgs>... unbound_args) {
// Local references to make debugger stepping easier. If in a debugger,
// you really want to warp ahead and step through the
// InvokeHelper<>::MakeItSo() call below.
@@ -325,20 +648,19 @@ struct Invoker<StorageType, R(UnboundArgs...)> {
std::tuple_size<decltype(storage->bound_args_)>::value;
return RunImpl(std::move(storage->functor_),
std::move(storage->bound_args_),
- MakeIndexSequence<num_bound_args>(),
+ std::make_index_sequence<num_bound_args>(),
std::forward<UnboundArgs>(unbound_args)...);
}
- static R Run(BindStateBase* base, UnboundArgs&&... unbound_args) {
+ static R Run(BindStateBase* base, PassingType<UnboundArgs>... unbound_args) {
// Local references to make debugger stepping easier. If in a debugger,
// you really want to warp ahead and step through the
// InvokeHelper<>::MakeItSo() call below.
const StorageType* storage = static_cast<StorageType*>(base);
static constexpr size_t num_bound_args =
std::tuple_size<decltype(storage->bound_args_)>::value;
- return RunImpl(storage->functor_,
- storage->bound_args_,
- MakeIndexSequence<num_bound_args>(),
+ return RunImpl(storage->functor_, storage->bound_args_,
+ std::make_index_sequence<num_bound_args>(),
std::forward<UnboundArgs>(unbound_args)...);
}
@@ -346,44 +668,60 @@ struct Invoker<StorageType, R(UnboundArgs...)> {
template <typename Functor, typename BoundArgsTuple, size_t... indices>
static inline R RunImpl(Functor&& functor,
BoundArgsTuple&& bound,
- IndexSequence<indices...>,
+ std::index_sequence<indices...>,
UnboundArgs&&... unbound_args) {
- static constexpr bool is_method =
- FunctorTraits<typename std::decay<Functor>::type>::is_method;
+ static constexpr bool is_method = MakeFunctorTraits<Functor>::is_method;
- using DecayedArgsTuple = typename std::decay<BoundArgsTuple>::type;
+ using DecayedArgsTuple = std::decay_t<BoundArgsTuple>;
static constexpr bool is_weak_call =
IsWeakMethod<is_method,
- typename std::tuple_element<
- indices,
- DecayedArgsTuple>::type...>::value;
+ std::tuple_element_t<indices, DecayedArgsTuple>...>();
return InvokeHelper<is_weak_call, R>::MakeItSo(
std::forward<Functor>(functor),
- Unwrap(base::get<indices>(std::forward<BoundArgsTuple>(bound)))...,
+ Unwrap(std::get<indices>(std::forward<BoundArgsTuple>(bound)))...,
std::forward<UnboundArgs>(unbound_args)...);
}
};
-// Used to implement MakeUnboundRunType.
+// Extracts necessary type info from Functor and BoundArgs.
+// Used to implement MakeUnboundRunType, BindOnce and BindRepeating.
template <typename Functor, typename... BoundArgs>
-struct MakeUnboundRunTypeImpl {
- using RunType =
- typename FunctorTraits<typename std::decay<Functor>::type>::RunType;
+struct BindTypeHelper {
+ static constexpr size_t num_bounds = sizeof...(BoundArgs);
+ using FunctorTraits = MakeFunctorTraits<Functor>;
+
+ // Example:
+ // When Functor is `double (Foo::*)(int, const std::string&)`, and BoundArgs
+ // is a template pack of `Foo*` and `int16_t`:
+ // - RunType is `double(Foo*, int, const std::string&)`,
+ // - ReturnType is `double`,
+ // - RunParamsList is `TypeList<Foo*, int, const std::string&>`,
+ // - BoundParamsList is `TypeList<Foo*, int>`,
+ // - UnboundParamsList is `TypeList<const std::string&>`,
+ // - BoundArgsList is `TypeList<Foo*, int16_t>`,
+ // - UnboundRunType is `double(const std::string&)`.
+ using RunType = typename FunctorTraits::RunType;
using ReturnType = ExtractReturnType<RunType>;
- using Args = ExtractArgs<RunType>;
- using UnboundArgs = DropTypeListItem<sizeof...(BoundArgs), Args>;
- using Type = MakeFunctionType<ReturnType, UnboundArgs>;
+
+ using RunParamsList = ExtractArgs<RunType>;
+ using BoundParamsList = TakeTypeListItem<num_bounds, RunParamsList>;
+ using UnboundParamsList = DropTypeListItem<num_bounds, RunParamsList>;
+
+ using BoundArgsList = TypeList<BoundArgs...>;
+
+ using UnboundRunType = MakeFunctionType<ReturnType, UnboundParamsList>;
};
+
template <typename Functor>
-typename std::enable_if<FunctorTraits<Functor>::is_nullable, bool>::type
-IsNull(const Functor& functor) {
+std::enable_if_t<FunctorTraits<Functor>::is_nullable, bool> IsNull(
+ const Functor& functor) {
return !functor;
}
template <typename Functor>
-typename std::enable_if<!FunctorTraits<Functor>::is_nullable, bool>::type
-IsNull(const Functor&) {
+std::enable_if_t<!FunctorTraits<Functor>::is_nullable, bool> IsNull(
+ const Functor&) {
return false;
}
@@ -391,9 +729,9 @@ IsNull(const Functor&) {
template <typename Functor, typename BoundArgsTuple, size_t... indices>
bool ApplyCancellationTraitsImpl(const Functor& functor,
const BoundArgsTuple& bound_args,
- IndexSequence<indices...>) {
+ std::index_sequence<indices...>) {
return CallbackCancellationTraits<Functor, BoundArgsTuple>::IsCancelled(
- functor, base::get<indices>(bound_args)...);
+ functor, std::get<indices>(bound_args)...);
}
// Relays |base| to corresponding CallbackCancellationTraits<>::Run(). Returns
@@ -403,25 +741,9 @@ bool ApplyCancellationTraits(const BindStateBase* base) {
const BindStateType* storage = static_cast<const BindStateType*>(base);
static constexpr size_t num_bound_args =
std::tuple_size<decltype(storage->bound_args_)>::value;
- return ApplyCancellationTraitsImpl(storage->functor_, storage->bound_args_,
- MakeIndexSequence<num_bound_args>());
-};
-
-// Template helpers to detect using Bind() on a base::Callback without any
-// additional arguments. In that case, the original base::Callback object should
-// just be directly used.
-template <typename Functor, typename... BoundArgs>
-struct BindingCallbackWithNoArgs {
- static constexpr bool value = false;
-};
-
-template <typename Signature,
- typename... BoundArgs,
- CopyMode copy_mode,
- RepeatMode repeat_mode>
-struct BindingCallbackWithNoArgs<Callback<Signature, copy_mode, repeat_mode>,
- BoundArgs...> {
- static constexpr bool value = sizeof...(BoundArgs) == 0;
+ return ApplyCancellationTraitsImpl(
+ storage->functor_, storage->bound_args_,
+ std::make_index_sequence<num_bound_args>());
};
// BindState<>
@@ -444,12 +766,7 @@ struct BindState final : BindStateBase {
: BindState(IsCancellable{},
invoke_func,
std::forward<ForwardFunctor>(functor),
- std::forward<ForwardBoundArgs>(bound_args)...) {
- static_assert(!BindingCallbackWithNoArgs<Functor, BoundArgs...>::value,
- "Attempting to bind a base::Callback with no additional "
- "arguments: save a heap allocation and use the original "
- "base::Callback object");
- }
+ std::forward<ForwardBoundArgs>(bound_args)...) {}
Functor functor_;
std::tuple<BoundArgs...> bound_args_;
@@ -479,7 +796,7 @@ struct BindState final : BindStateBase {
DCHECK(!IsNull(functor_));
}
- ~BindState() {}
+ ~BindState() = default;
static void Destroy(const BindStateBase* self) {
delete static_cast<const BindState*>(self);
@@ -492,51 +809,162 @@ struct MakeBindStateTypeImpl;
template <typename Functor, typename... BoundArgs>
struct MakeBindStateTypeImpl<false, Functor, BoundArgs...> {
- static_assert(!HasRefCountedTypeAsRawPtr<BoundArgs...>::value,
+ static_assert(!HasRefCountedTypeAsRawPtr<std::decay_t<BoundArgs>...>::value,
"A parameter is a refcounted type and needs scoped_refptr.");
- using Type = BindState<typename std::decay<Functor>::type,
- typename std::decay<BoundArgs>::type...>;
+ using Type = BindState<std::decay_t<Functor>, std::decay_t<BoundArgs>...>;
};
template <typename Functor>
struct MakeBindStateTypeImpl<true, Functor> {
- using Type = BindState<typename std::decay<Functor>::type>;
+ using Type = BindState<std::decay_t<Functor>>;
};
template <typename Functor, typename Receiver, typename... BoundArgs>
struct MakeBindStateTypeImpl<true, Functor, Receiver, BoundArgs...> {
+ private:
+ using DecayedReceiver = std::decay_t<Receiver>;
+
+ static_assert(!std::is_array<std::remove_reference_t<Receiver>>::value,
+ "First bound argument to a method cannot be an array.");
static_assert(
- !std::is_array<typename std::remove_reference<Receiver>::type>::value,
- "First bound argument to a method cannot be an array.");
- static_assert(!HasRefCountedTypeAsRawPtr<BoundArgs...>::value,
+ !std::is_pointer<DecayedReceiver>::value ||
+ IsRefCountedType<std::remove_pointer_t<DecayedReceiver>>::value,
+ "Receivers may not be raw pointers. If using a raw pointer here is safe"
+ " and has no lifetime concerns, use base::Unretained() and document why"
+ " it's safe.");
+ static_assert(!HasRefCountedTypeAsRawPtr<std::decay_t<BoundArgs>...>::value,
"A parameter is a refcounted type and needs scoped_refptr.");
- private:
- using DecayedReceiver = typename std::decay<Receiver>::type;
-
public:
using Type = BindState<
- typename std::decay<Functor>::type,
- typename std::conditional<
- std::is_pointer<DecayedReceiver>::value,
- scoped_refptr<typename std::remove_pointer<DecayedReceiver>::type>,
- DecayedReceiver>::type,
- typename std::decay<BoundArgs>::type...>;
+ std::decay_t<Functor>,
+ std::conditional_t<std::is_pointer<DecayedReceiver>::value,
+ scoped_refptr<std::remove_pointer_t<DecayedReceiver>>,
+ DecayedReceiver>,
+ std::decay_t<BoundArgs>...>;
};
template <typename Functor, typename... BoundArgs>
-using MakeBindStateType = typename MakeBindStateTypeImpl<
- FunctorTraits<typename std::decay<Functor>::type>::is_method,
- Functor,
- BoundArgs...>::Type;
+using MakeBindStateType =
+ typename MakeBindStateTypeImpl<MakeFunctorTraits<Functor>::is_method,
+ Functor,
+ BoundArgs...>::Type;
} // namespace internal
+// An injection point to control |this| pointer behavior on a method invocation.
+// If IsWeakReceiver<> is true_type for |T| and |T| is used for a receiver of a
+// method, base::Bind cancels the method invocation if the receiver is tested as
+// false.
+// E.g. Foo::bar() is not called:
+// struct Foo : base::SupportsWeakPtr<Foo> {
+// void bar() {}
+// };
+//
+// WeakPtr<Foo> oo = nullptr;
+// base::Bind(&Foo::bar, oo).Run();
+template <typename T>
+struct IsWeakReceiver : std::false_type {};
+
+template <typename T>
+struct IsWeakReceiver<internal::ConstRefWrapper<T>> : IsWeakReceiver<T> {};
+
+template <typename T>
+struct IsWeakReceiver<WeakPtr<T>> : std::true_type {};
+
+// An injection point to control how bound objects passed to the target
+// function. BindUnwrapTraits<>::Unwrap() is called for each bound objects right
+// before the target function is invoked.
+template <typename>
+struct BindUnwrapTraits {
+ template <typename T>
+ static T&& Unwrap(T&& o) {
+ return std::forward<T>(o);
+ }
+};
+
+template <typename T>
+struct BindUnwrapTraits<internal::UnretainedWrapper<T>> {
+ static T* Unwrap(const internal::UnretainedWrapper<T>& o) { return o.get(); }
+};
+
+template <typename T>
+struct BindUnwrapTraits<internal::ConstRefWrapper<T>> {
+ static const T& Unwrap(const internal::ConstRefWrapper<T>& o) {
+ return o.get();
+ }
+};
+
+template <typename T>
+struct BindUnwrapTraits<internal::RetainedRefWrapper<T>> {
+ static T* Unwrap(const internal::RetainedRefWrapper<T>& o) { return o.get(); }
+};
+
+template <typename T>
+struct BindUnwrapTraits<internal::OwnedWrapper<T>> {
+ static T* Unwrap(const internal::OwnedWrapper<T>& o) { return o.get(); }
+};
+
+template <typename T>
+struct BindUnwrapTraits<internal::PassedWrapper<T>> {
+ static T Unwrap(const internal::PassedWrapper<T>& o) { return o.Take(); }
+};
+
+// CallbackCancellationTraits allows customization of Callback's cancellation
+// semantics. By default, callbacks are not cancellable. A specialization should
+// set is_cancellable = true and implement an IsCancelled() that returns if the
+// callback should be cancelled.
+template <typename Functor, typename BoundArgsTuple, typename SFINAE>
+struct CallbackCancellationTraits {
+ static constexpr bool is_cancellable = false;
+};
+
+// Specialization for method bound to weak pointer receiver.
+template <typename Functor, typename... BoundArgs>
+struct CallbackCancellationTraits<
+ Functor,
+ std::tuple<BoundArgs...>,
+ std::enable_if_t<
+ internal::IsWeakMethod<internal::FunctorTraits<Functor>::is_method,
+ BoundArgs...>::value>> {
+ static constexpr bool is_cancellable = true;
+
+ template <typename Receiver, typename... Args>
+ static bool IsCancelled(const Functor&,
+ const Receiver& receiver,
+ const Args&...) {
+ return !receiver;
+ }
+};
+
+// Specialization for a nested bind.
+template <typename Signature, typename... BoundArgs>
+struct CallbackCancellationTraits<OnceCallback<Signature>,
+ std::tuple<BoundArgs...>> {
+ static constexpr bool is_cancellable = true;
+
+ template <typename Functor>
+ static bool IsCancelled(const Functor& functor, const BoundArgs&...) {
+ return functor.IsCancelled();
+ }
+};
+
+template <typename Signature, typename... BoundArgs>
+struct CallbackCancellationTraits<RepeatingCallback<Signature>,
+ std::tuple<BoundArgs...>> {
+ static constexpr bool is_cancellable = true;
+
+ template <typename Functor>
+ static bool IsCancelled(const Functor& functor, const BoundArgs&...) {
+ return functor.IsCancelled();
+ }
+};
+
// Returns a RunType of bound functor.
// E.g. MakeUnboundRunType<R(A, B, C), A, B> is evaluated to R(C).
template <typename Functor, typename... BoundArgs>
using MakeUnboundRunType =
- typename internal::MakeUnboundRunTypeImpl<Functor, BoundArgs...>::Type;
+ typename internal::BindTypeHelper<Functor, BoundArgs...>::UnboundRunType;
} // namespace base
diff --git a/base/bind_unittest.cc b/base/bind_unittest.cc
index 0de9294894..870132bb28 100644
--- a/base/bind_unittest.cc
+++ b/base/bind_unittest.cc
@@ -13,6 +13,7 @@
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
+#include "base/test/bind_test_util.h"
#include "base/test/gtest_util.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
@@ -31,7 +32,7 @@ class IncompleteType;
class NoRef {
public:
- NoRef() {}
+ NoRef() = default;
MOCK_METHOD0(VoidMethod0, void());
MOCK_CONST_METHOD0(VoidConstMethod0, void());
@@ -49,7 +50,7 @@ class NoRef {
class HasRef : public NoRef {
public:
- HasRef() {}
+ HasRef() = default;
MOCK_CONST_METHOD0(AddRef, void());
MOCK_CONST_METHOD0(Release, bool());
@@ -61,7 +62,7 @@ class HasRef : public NoRef {
class HasRefPrivateDtor : public HasRef {
private:
- ~HasRefPrivateDtor() {}
+ ~HasRefPrivateDtor() = default;
};
static const int kParentValue = 1;
@@ -202,11 +203,8 @@ class CopyCounter {
public:
CopyCounter(int* copies, int* assigns)
: counter_(copies, assigns, nullptr, nullptr) {}
- CopyCounter(const CopyCounter& other) : counter_(other.counter_) {}
- CopyCounter& operator=(const CopyCounter& other) {
- counter_ = other.counter_;
- return *this;
- }
+ CopyCounter(const CopyCounter& other) = default;
+ CopyCounter& operator=(const CopyCounter& other) = default;
explicit CopyCounter(const DerivedCopyMoveCounter& other) : counter_(other) {}
@@ -319,6 +317,10 @@ void TakesACallback(const Closure& callback) {
callback.Run();
}
+int Noexcept() noexcept {
+ return 42;
+}
+
class BindTest : public ::testing::Test {
public:
BindTest() {
@@ -327,14 +329,15 @@ class BindTest : public ::testing::Test {
static_func_mock_ptr = &static_func_mock_;
}
- virtual ~BindTest() {
- }
+ ~BindTest() override = default;
static void VoidFunc0() {
static_func_mock_ptr->VoidMethod0();
}
static int IntFunc0() { return static_func_mock_ptr->IntMethod0(); }
+ int NoexceptMethod() noexcept { return 42; }
+ int ConstNoexceptMethod() const noexcept { return 42; }
protected:
StrictMock<NoRef> no_ref_;
@@ -809,8 +812,8 @@ TYPED_TEST(BindVariantsTest, FunctionTypeSupport) {
EXPECT_EQ(&no_ref, std::move(normal_non_refcounted_cb).Run());
ClosureType method_cb = TypeParam::Bind(&HasRef::VoidMethod0, &has_ref);
- ClosureType method_refptr_cb = TypeParam::Bind(&HasRef::VoidMethod0,
- make_scoped_refptr(&has_ref));
+ ClosureType method_refptr_cb =
+ TypeParam::Bind(&HasRef::VoidMethod0, WrapRefCounted(&has_ref));
ClosureType const_method_nonconst_obj_cb =
TypeParam::Bind(&HasRef::VoidConstMethod0, &has_ref);
ClosureType const_method_const_obj_cb =
@@ -852,7 +855,7 @@ TYPED_TEST(BindVariantsTest, ReturnValues) {
.WillOnce(Return(41337))
.WillOnce(Return(51337));
EXPECT_CALL(has_ref, UniquePtrMethod0())
- .WillOnce(Return(ByMove(MakeUnique<int>(42))));
+ .WillOnce(Return(ByMove(std::make_unique<int>(42))));
CallbackType<TypeParam, int()> normal_cb = TypeParam::Bind(&IntFunc0);
CallbackType<TypeParam, int()> method_cb =
@@ -1238,17 +1241,17 @@ TEST_F(BindTest, ArgumentCopiesAndMoves) {
}
TEST_F(BindTest, CapturelessLambda) {
- EXPECT_FALSE(internal::IsConvertibleToRunType<void>::value);
- EXPECT_FALSE(internal::IsConvertibleToRunType<int>::value);
- EXPECT_FALSE(internal::IsConvertibleToRunType<void(*)()>::value);
- EXPECT_FALSE(internal::IsConvertibleToRunType<void(NoRef::*)()>::value);
+ EXPECT_FALSE(internal::IsCallableObject<void>::value);
+ EXPECT_FALSE(internal::IsCallableObject<int>::value);
+ EXPECT_FALSE(internal::IsCallableObject<void (*)()>::value);
+ EXPECT_FALSE(internal::IsCallableObject<void (NoRef::*)()>::value);
auto f = []() {};
- EXPECT_TRUE(internal::IsConvertibleToRunType<decltype(f)>::value);
+ EXPECT_TRUE(internal::IsCallableObject<decltype(f)>::value);
int i = 0;
auto g = [i]() { (void)i; };
- EXPECT_FALSE(internal::IsConvertibleToRunType<decltype(g)>::value);
+ EXPECT_TRUE(internal::IsCallableObject<decltype(g)>::value);
auto h = [](int, double) { return 'k'; };
EXPECT_TRUE((std::is_same<
@@ -1267,6 +1270,36 @@ TEST_F(BindTest, CapturelessLambda) {
EXPECT_EQ(42, x);
}
+TEST_F(BindTest, EmptyFunctor) {
+ struct NonEmptyFunctor {
+ int operator()() const { return x; }
+ int x = 42;
+ };
+
+ struct EmptyFunctor {
+ int operator()() { return 42; }
+ };
+
+ struct EmptyFunctorConst {
+ int operator()() const { return 42; }
+ };
+
+ EXPECT_TRUE(internal::IsCallableObject<NonEmptyFunctor>::value);
+ EXPECT_TRUE(internal::IsCallableObject<EmptyFunctor>::value);
+ EXPECT_TRUE(internal::IsCallableObject<EmptyFunctorConst>::value);
+ EXPECT_EQ(42, BindOnce(EmptyFunctor()).Run());
+ EXPECT_EQ(42, BindOnce(EmptyFunctorConst()).Run());
+ EXPECT_EQ(42, BindRepeating(EmptyFunctorConst()).Run());
+}
+
+TEST_F(BindTest, CapturingLambdaForTesting) {
+ int x = 6;
+ EXPECT_EQ(42, BindLambdaForTesting([=](int y) { return x * y; }).Run(7));
+
+ auto f = [x](std::unique_ptr<int> y) { return x * *y; };
+ EXPECT_EQ(42, BindLambdaForTesting(f).Run(std::make_unique<int>(7)));
+}
+
TEST_F(BindTest, Cancellation) {
EXPECT_CALL(no_ref_, VoidMethodWithIntArg(_)).Times(2);
@@ -1376,8 +1409,9 @@ TEST_F(BindTest, OnceCallback) {
cb = std::move(cb2);
- OnceCallback<void(int)> cb4 = BindOnce(
- &VoidPolymorphic<std::unique_ptr<int>, int>::Run, MakeUnique<int>(0));
+ OnceCallback<void(int)> cb4 =
+ BindOnce(&VoidPolymorphic<std::unique_ptr<int>, int>::Run,
+ std::make_unique<int>(0));
BindOnce(std::move(cb4), 1).Run();
}
@@ -1408,6 +1442,55 @@ TEST_F(BindTest, WindowsCallingConventions) {
}
#endif
+// Test unwrapping the various wrapping functions.
+
+TEST_F(BindTest, UnwrapUnretained) {
+ int i = 0;
+ auto unretained = Unretained(&i);
+ EXPECT_EQ(&i, internal::Unwrap(unretained));
+ EXPECT_EQ(&i, internal::Unwrap(std::move(unretained)));
+}
+
+TEST_F(BindTest, UnwrapConstRef) {
+ int p = 0;
+ auto const_ref = ConstRef(p);
+ EXPECT_EQ(&p, &internal::Unwrap(const_ref));
+ EXPECT_EQ(&p, &internal::Unwrap(std::move(const_ref)));
+}
+
+TEST_F(BindTest, UnwrapRetainedRef) {
+ auto p = MakeRefCounted<RefCountedData<int>>();
+ auto retained_ref = RetainedRef(p);
+ EXPECT_EQ(p.get(), internal::Unwrap(retained_ref));
+ EXPECT_EQ(p.get(), internal::Unwrap(std::move(retained_ref)));
+}
+
+TEST_F(BindTest, UnwrapOwned) {
+ int* p = new int;
+ auto owned = Owned(p);
+ EXPECT_EQ(p, internal::Unwrap(owned));
+ EXPECT_EQ(p, internal::Unwrap(std::move(owned)));
+}
+
+TEST_F(BindTest, UnwrapPassed) {
+ int* p = new int;
+ auto passed = Passed(WrapUnique(p));
+ EXPECT_EQ(p, internal::Unwrap(passed).get());
+
+ p = new int;
+ EXPECT_EQ(p, internal::Unwrap(Passed(WrapUnique(p))).get());
+}
+
+TEST_F(BindTest, BindNoexcept) {
+ EXPECT_EQ(42, base::BindOnce(&Noexcept).Run());
+ EXPECT_EQ(
+ 42,
+ base::BindOnce(&BindTest::NoexceptMethod, base::Unretained(this)).Run());
+ EXPECT_EQ(
+ 42, base::BindOnce(&BindTest::ConstNoexceptMethod, base::Unretained(this))
+ .Run());
+}
+
// Test null callbacks cause a DCHECK.
TEST(BindDeathTest, NullCallback) {
base::Callback<void(int)> null_cb;
diff --git a/base/bits.h b/base/bits.h
index d101cb731a..a1c8b5de59 100644
--- a/base/bits.h
+++ b/base/bits.h
@@ -12,6 +12,7 @@
#include "base/compiler_specific.h"
#include "base/logging.h"
+#include "build/build_config.h"
#if defined(COMPILER_MSVC)
#include <intrin.h>
@@ -20,91 +21,162 @@
namespace base {
namespace bits {
-// Returns the integer i such as 2^i <= n < 2^(i+1)
-inline int Log2Floor(uint32_t n) {
- if (n == 0)
- return -1;
- int log = 0;
- uint32_t value = n;
- for (int i = 4; i >= 0; --i) {
- int shift = (1 << i);
- uint32_t x = value >> shift;
- if (x != 0) {
- value = x;
- log += shift;
- }
- }
- DCHECK_EQ(value, 1u);
- return log;
-}
-
-// Returns the integer i such as 2^(i-1) < n <= 2^i
-inline int Log2Ceiling(uint32_t n) {
- if (n == 0) {
- return -1;
- } else {
- // Log2Floor returns -1 for 0, so the following works correctly for n=1.
- return 1 + Log2Floor(n - 1);
- }
+// Returns true iff |value| is a power of 2.
+template <typename T,
+ typename = typename std::enable_if<std::is_integral<T>::value>>
+constexpr inline bool IsPowerOfTwo(T value) {
+ // From "Hacker's Delight": Section 2.1 Manipulating Rightmost Bits.
+ //
+ // Only positive integers with a single bit set are powers of two. If only one
+ // bit is set in x (e.g. 0b00000100000000) then |x-1| will have that bit set
+ // to zero and all bits to its right set to 1 (e.g. 0b00000011111111). Hence
+ // |x & (x-1)| is 0 iff x is a power of two.
+ return value > 0 && (value & (value - 1)) == 0;
}
// Round up |size| to a multiple of alignment, which must be a power of two.
inline size_t Align(size_t size, size_t alignment) {
- DCHECK_EQ(alignment & (alignment - 1), 0u);
+ DCHECK(IsPowerOfTwo(alignment));
return (size + alignment - 1) & ~(alignment - 1);
}
-// These functions count the number of leading zeros in a binary value, starting
-// with the most significant bit. C does not have an operator to do this, but
-// fortunately the various compilers have built-ins that map to fast underlying
-// processor instructions.
+// CountLeadingZeroBits(value) returns the number of zero bits following the
+// most significant 1 bit in |value| if |value| is non-zero, otherwise it
+// returns {sizeof(T) * 8}.
+// Example: 00100010 -> 2
+//
+// CountTrailingZeroBits(value) returns the number of zero bits preceding the
+// least significant 1 bit in |value| if |value| is non-zero, otherwise it
+// returns {sizeof(T) * 8}.
+// Example: 00100010 -> 1
+//
+// C does not have an operator to do this, but fortunately the various
+// compilers have built-ins that map to fast underlying processor instructions.
#if defined(COMPILER_MSVC)
-ALWAYS_INLINE uint32_t CountLeadingZeroBits32(uint32_t x) {
+template <typename T, unsigned bits = sizeof(T) * 8>
+ALWAYS_INLINE
+ typename std::enable_if<std::is_unsigned<T>::value && sizeof(T) <= 4,
+ unsigned>::type
+ CountLeadingZeroBits(T x) {
+ static_assert(bits > 0, "invalid instantiation");
+ unsigned long index;
+ return LIKELY(_BitScanReverse(&index, static_cast<uint32_t>(x)))
+ ? (31 - index - (32 - bits))
+ : bits;
+}
+
+template <typename T, unsigned bits = sizeof(T) * 8>
+ALWAYS_INLINE
+ typename std::enable_if<std::is_unsigned<T>::value && sizeof(T) == 8,
+ unsigned>::type
+ CountLeadingZeroBits(T x) {
+ static_assert(bits > 0, "invalid instantiation");
unsigned long index;
- return LIKELY(_BitScanReverse(&index, x)) ? (31 - index) : 32;
+ return LIKELY(_BitScanReverse64(&index, static_cast<uint64_t>(x)))
+ ? (63 - index)
+ : 64;
+}
+
+template <typename T, unsigned bits = sizeof(T) * 8>
+ALWAYS_INLINE
+ typename std::enable_if<std::is_unsigned<T>::value && sizeof(T) <= 4,
+ unsigned>::type
+ CountTrailingZeroBits(T x) {
+ static_assert(bits > 0, "invalid instantiation");
+ unsigned long index;
+ return LIKELY(_BitScanForward(&index, static_cast<uint32_t>(x))) ? index
+ : bits;
+}
+
+template <typename T, unsigned bits = sizeof(T) * 8>
+ALWAYS_INLINE
+ typename std::enable_if<std::is_unsigned<T>::value && sizeof(T) == 8,
+ unsigned>::type
+ CountTrailingZeroBits(T x) {
+ static_assert(bits > 0, "invalid instantiation");
+ unsigned long index;
+ return LIKELY(_BitScanForward64(&index, static_cast<uint64_t>(x))) ? index
+ : 64;
+}
+
+ALWAYS_INLINE uint32_t CountLeadingZeroBits32(uint32_t x) {
+ return CountLeadingZeroBits(x);
}
#if defined(ARCH_CPU_64_BITS)
// MSVC only supplies _BitScanForward64 when building for a 64-bit target.
ALWAYS_INLINE uint64_t CountLeadingZeroBits64(uint64_t x) {
- unsigned long index;
- return LIKELY(_BitScanReverse64(&index, x)) ? (63 - index) : 64;
+ return CountLeadingZeroBits(x);
}
#endif
#elif defined(COMPILER_GCC)
-// This is very annoying. __builtin_clz has undefined behaviour for an input of
-// 0, even though there's clearly a return value that makes sense, and even
-// though some processor clz instructions have defined behaviour for 0. We could
-// drop to raw __asm__ to do better, but we'll avoid doing that unless we see
-// proof that we need to.
+// __builtin_clz has undefined behaviour for an input of 0, even though there's
+// clearly a return value that makes sense, and even though some processor clz
+// instructions have defined behaviour for 0. We could drop to raw __asm__ to
+// do better, but we'll avoid doing that unless we see proof that we need to.
+template <typename T, unsigned bits = sizeof(T) * 8>
+ALWAYS_INLINE
+ typename std::enable_if<std::is_unsigned<T>::value && sizeof(T) <= 8,
+ unsigned>::type
+ CountLeadingZeroBits(T value) {
+ static_assert(bits > 0, "invalid instantiation");
+ return LIKELY(value)
+ ? bits == 64
+ ? __builtin_clzll(static_cast<uint64_t>(value))
+ : __builtin_clz(static_cast<uint32_t>(value)) - (32 - bits)
+ : bits;
+}
+
+template <typename T, unsigned bits = sizeof(T) * 8>
+ALWAYS_INLINE
+ typename std::enable_if<std::is_unsigned<T>::value && sizeof(T) <= 8,
+ unsigned>::type
+ CountTrailingZeroBits(T value) {
+ return LIKELY(value) ? bits == 64
+ ? __builtin_ctzll(static_cast<uint64_t>(value))
+ : __builtin_ctz(static_cast<uint32_t>(value))
+ : bits;
+}
+
ALWAYS_INLINE uint32_t CountLeadingZeroBits32(uint32_t x) {
- return LIKELY(x) ? __builtin_clz(x) : 32;
+ return CountLeadingZeroBits(x);
}
+#if defined(ARCH_CPU_64_BITS)
+
ALWAYS_INLINE uint64_t CountLeadingZeroBits64(uint64_t x) {
- return LIKELY(x) ? __builtin_clzll(x) : 64;
+ return CountLeadingZeroBits(x);
}
#endif
-#if defined(ARCH_CPU_64_BITS)
+#endif
ALWAYS_INLINE size_t CountLeadingZeroBitsSizeT(size_t x) {
- return CountLeadingZeroBits64(x);
+ return CountLeadingZeroBits(x);
}
-#else
+ALWAYS_INLINE size_t CountTrailingZeroBitsSizeT(size_t x) {
+ return CountTrailingZeroBits(x);
+}
-ALWAYS_INLINE size_t CountLeadingZeroBitsSizeT(size_t x) {
- return CountLeadingZeroBits32(x);
+// Returns the integer i such as 2^i <= n < 2^(i+1)
+inline int Log2Floor(uint32_t n) {
+ return 31 - CountLeadingZeroBits(n);
}
-#endif
+// Returns the integer i such as 2^(i-1) < n <= 2^i
+inline int Log2Ceiling(uint32_t n) {
+ // When n == 0, we want the function to return -1.
+ // When n == 0, (n - 1) will underflow to 0xFFFFFFFF, which is
+ // why the statement below starts with (n ? 32 : -1).
+ return (n ? 32 : -1) - CountLeadingZeroBits(n - 1);
+}
} // namespace bits
} // namespace base
diff --git a/base/bits_unittest.cc b/base/bits_unittest.cc
index 270b8ef7d3..98b9c08c47 100644
--- a/base/bits_unittest.cc
+++ b/base/bits_unittest.cc
@@ -5,6 +5,7 @@
// This file contains the unit tests for the bit utilities.
#include "base/bits.h"
+#include "build/build_config.h"
#include <stddef.h>
@@ -61,24 +62,135 @@ TEST(BitsTest, Align) {
EXPECT_EQ(kSizeTMax / 2 + 1, Align(1, kSizeTMax / 2 + 1));
}
-TEST(BitsTest, CLZWorks) {
- EXPECT_EQ(32u, CountLeadingZeroBits32(0u));
- EXPECT_EQ(31u, CountLeadingZeroBits32(1u));
- EXPECT_EQ(1u, CountLeadingZeroBits32(1u << 30));
- EXPECT_EQ(0u, CountLeadingZeroBits32(1u << 31));
+TEST(BitsTest, CountLeadingZeroBits8) {
+ EXPECT_EQ(8u, CountLeadingZeroBits(uint8_t{0}));
+ EXPECT_EQ(7u, CountLeadingZeroBits(uint8_t{1}));
+ for (uint8_t shift = 0; shift <= 7; shift++) {
+ EXPECT_EQ(7u - shift,
+ CountLeadingZeroBits(static_cast<uint8_t>(1 << shift)));
+ }
+ EXPECT_EQ(4u, CountLeadingZeroBits(uint8_t{0x0f}));
+}
+
+TEST(BitsTest, CountLeadingZeroBits16) {
+ EXPECT_EQ(16u, CountLeadingZeroBits(uint16_t{0}));
+ EXPECT_EQ(15u, CountLeadingZeroBits(uint16_t{1}));
+ for (uint16_t shift = 0; shift <= 15; shift++) {
+ EXPECT_EQ(15u - shift,
+ CountLeadingZeroBits(static_cast<uint16_t>(1 << shift)));
+ }
+ EXPECT_EQ(4u, CountLeadingZeroBits(uint16_t{0x0f0f}));
+}
+
+TEST(BitsTest, CountLeadingZeroBits32) {
+ EXPECT_EQ(32u, CountLeadingZeroBits(uint32_t{0}));
+ EXPECT_EQ(31u, CountLeadingZeroBits(uint32_t{1}));
+ for (uint32_t shift = 0; shift <= 31; shift++) {
+ EXPECT_EQ(31u - shift, CountLeadingZeroBits(uint32_t{1} << shift));
+ }
+ EXPECT_EQ(4u, CountLeadingZeroBits(uint32_t{0x0f0f0f0f}));
+}
+
+TEST(BitsTest, CountTrailingeZeroBits8) {
+ EXPECT_EQ(8u, CountTrailingZeroBits(uint8_t{0}));
+ EXPECT_EQ(7u, CountTrailingZeroBits(uint8_t{128}));
+ for (uint8_t shift = 0; shift <= 7; shift++) {
+ EXPECT_EQ(shift, CountTrailingZeroBits(static_cast<uint8_t>(1 << shift)));
+ }
+ EXPECT_EQ(4u, CountTrailingZeroBits(uint8_t{0xf0}));
+}
+
+TEST(BitsTest, CountTrailingeZeroBits16) {
+ EXPECT_EQ(16u, CountTrailingZeroBits(uint16_t{0}));
+ EXPECT_EQ(15u, CountTrailingZeroBits(uint16_t{32768}));
+ for (uint16_t shift = 0; shift <= 15; shift++) {
+ EXPECT_EQ(shift, CountTrailingZeroBits(static_cast<uint16_t>(1 << shift)));
+ }
+ EXPECT_EQ(4u, CountTrailingZeroBits(uint16_t{0xf0f0}));
+}
+
+TEST(BitsTest, CountTrailingeZeroBits32) {
+ EXPECT_EQ(32u, CountTrailingZeroBits(uint32_t{0}));
+ EXPECT_EQ(31u, CountTrailingZeroBits(uint32_t{1} << 31));
+ for (uint32_t shift = 0; shift <= 31; shift++) {
+ EXPECT_EQ(shift, CountTrailingZeroBits(uint32_t{1} << shift));
+ }
+ EXPECT_EQ(4u, CountTrailingZeroBits(uint32_t{0xf0f0f0f0}));
+}
#if defined(ARCH_CPU_64_BITS)
- EXPECT_EQ(64u, CountLeadingZeroBitsSizeT(0ull));
- EXPECT_EQ(63u, CountLeadingZeroBitsSizeT(1ull));
- EXPECT_EQ(32u, CountLeadingZeroBitsSizeT(1ull << 31));
- EXPECT_EQ(1u, CountLeadingZeroBitsSizeT(1ull << 62));
- EXPECT_EQ(0u, CountLeadingZeroBitsSizeT(1ull << 63));
+
+TEST(BitsTest, CountLeadingZeroBits64) {
+ EXPECT_EQ(64u, CountLeadingZeroBits(uint64_t{0}));
+ EXPECT_EQ(63u, CountLeadingZeroBits(uint64_t{1}));
+ for (uint64_t shift = 0; shift <= 63; shift++) {
+ EXPECT_EQ(63u - shift, CountLeadingZeroBits(uint64_t{1} << shift));
+ }
+ EXPECT_EQ(4u, CountLeadingZeroBits(uint64_t{0x0f0f0f0f0f0f0f0f}));
+}
+
+TEST(BitsTest, CountTrailingeZeroBits64) {
+ EXPECT_EQ(64u, CountTrailingZeroBits(uint64_t{0}));
+ EXPECT_EQ(63u, CountTrailingZeroBits(uint64_t{1} << 63));
+ for (uint64_t shift = 0; shift <= 31; shift++) {
+ EXPECT_EQ(shift, CountTrailingZeroBits(uint64_t{1} << shift));
+ }
+ EXPECT_EQ(4u, CountTrailingZeroBits(uint64_t{0xf0f0f0f0f0f0f0f0}));
+}
+
+#endif // ARCH_CPU_64_BITS
+
+TEST(BitsTest, CountLeadingZeroBitsSizeT) {
+#if defined(ARCH_CPU_64_BITS)
+ EXPECT_EQ(64u, CountLeadingZeroBitsSizeT(size_t{0}));
+ EXPECT_EQ(63u, CountLeadingZeroBitsSizeT(size_t{1}));
+ EXPECT_EQ(32u, CountLeadingZeroBitsSizeT(size_t{1} << 31));
+ EXPECT_EQ(1u, CountLeadingZeroBitsSizeT(size_t{1} << 62));
+ EXPECT_EQ(0u, CountLeadingZeroBitsSizeT(size_t{1} << 63));
#else
- EXPECT_EQ(32u, CountLeadingZeroBitsSizeT(0u));
- EXPECT_EQ(31u, CountLeadingZeroBitsSizeT(1u));
- EXPECT_EQ(1u, CountLeadingZeroBitsSizeT(1u << 30));
- EXPECT_EQ(0u, CountLeadingZeroBitsSizeT(1u << 31));
-#endif
+ EXPECT_EQ(32u, CountLeadingZeroBitsSizeT(size_t{0}));
+ EXPECT_EQ(31u, CountLeadingZeroBitsSizeT(size_t{1}));
+ EXPECT_EQ(1u, CountLeadingZeroBitsSizeT(size_t{1} << 30));
+ EXPECT_EQ(0u, CountLeadingZeroBitsSizeT(size_t{1} << 31));
+#endif // ARCH_CPU_64_BITS
+}
+
+TEST(BitsTest, CountTrailingZeroBitsSizeT) {
+#if defined(ARCH_CPU_64_BITS)
+ EXPECT_EQ(64u, CountTrailingZeroBitsSizeT(size_t{0}));
+ EXPECT_EQ(63u, CountTrailingZeroBitsSizeT(size_t{1} << 63));
+ EXPECT_EQ(31u, CountTrailingZeroBitsSizeT(size_t{1} << 31));
+ EXPECT_EQ(1u, CountTrailingZeroBitsSizeT(size_t{2}));
+ EXPECT_EQ(0u, CountTrailingZeroBitsSizeT(size_t{1}));
+#else
+ EXPECT_EQ(32u, CountTrailingZeroBitsSizeT(size_t{0}));
+ EXPECT_EQ(31u, CountTrailingZeroBitsSizeT(size_t{1} << 31));
+ EXPECT_EQ(1u, CountTrailingZeroBitsSizeT(size_t{2}));
+ EXPECT_EQ(0u, CountTrailingZeroBitsSizeT(size_t{1}));
+#endif // ARCH_CPU_64_BITS
+}
+
+TEST(BitsTest, PowerOfTwo) {
+ EXPECT_FALSE(IsPowerOfTwo(-1));
+ EXPECT_FALSE(IsPowerOfTwo(0));
+ EXPECT_TRUE(IsPowerOfTwo(1));
+ EXPECT_TRUE(IsPowerOfTwo(2));
+ // Unsigned 64 bit cases.
+ for (uint32_t i = 2; i < 64; i++) {
+ const uint64_t val = uint64_t{1} << i;
+ EXPECT_FALSE(IsPowerOfTwo(val - 1));
+ EXPECT_TRUE(IsPowerOfTwo(val));
+ EXPECT_FALSE(IsPowerOfTwo(val + 1));
+ }
+ // Signed 64 bit cases.
+ for (uint32_t i = 2; i < 63; i++) {
+ const int64_t val = int64_t{1} << i;
+ EXPECT_FALSE(IsPowerOfTwo(val - 1));
+ EXPECT_TRUE(IsPowerOfTwo(val));
+ EXPECT_FALSE(IsPowerOfTwo(val + 1));
+ }
+ // Signed integers with only the last bit set are negative, not powers of two.
+ EXPECT_FALSE(IsPowerOfTwo(int64_t{1} << 63));
}
} // namespace bits
diff --git a/base/callback.h b/base/callback.h
index c91e1a88d3..bcda5af587 100644
--- a/base/callback.h
+++ b/base/callback.h
@@ -1,80 +1,129 @@
// 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.
+//
+// NOTE: Header files that do not require the full definition of Callback or
+// Closure should #include "base/callback_forward.h" instead of this file.
#ifndef BASE_CALLBACK_H_
#define BASE_CALLBACK_H_
+#include <stddef.h>
+
#include "base/callback_forward.h"
#include "base/callback_internal.h"
-// NOTE: Header files that do not require the full definition of Callback or
-// Closure should #include "base/callback_forward.h" instead of this file.
-
// -----------------------------------------------------------------------------
// Usage documentation
// -----------------------------------------------------------------------------
//
-// See //docs/callback.md for documentation.
+// Overview:
+// A callback is similar in concept to a function pointer: it wraps a runnable
+// object such as a function, method, lambda, or even another callback, allowing
+// the runnable object to be invoked later via the callback object.
+//
+// Unlike function pointers, callbacks are created with base::BindOnce() or
+// base::BindRepeating() and support partial function application.
+//
+// A base::OnceCallback may be Run() at most once; a base::RepeatingCallback may
+// be Run() any number of times. |is_null()| is guaranteed to return true for a
+// moved-from callback.
+//
+// // The lambda takes two arguments, but the first argument |x| is bound at
+// // callback creation.
+// base::OnceCallback<int(int)> cb = base::BindOnce([] (int x, int y) {
+// return x + y;
+// }, 1);
+// // Run() only needs the remaining unbound argument |y|.
+// printf("1 + 2 = %d\n", std::move(cb).Run(2)); // Prints 3
+// printf("cb is null? %s\n",
+// cb.is_null() ? "true" : "false"); // Prints true
+// std::move(cb).Run(2); // Crashes since |cb| has already run.
+//
+// Callbacks also support cancellation. A common use is binding the receiver
+// object as a WeakPtr<T>. If that weak pointer is invalidated, calling Run()
+// will be a no-op. Note that |is_cancelled()| and |is_null()| are distinct:
+// simply cancelling a callback will not also make it null.
+//
+// base::Callback is currently a type alias for base::RepeatingCallback. In the
+// future, we expect to flip this to default to base::OnceCallback.
+//
+// See //docs/callback.md for the full documentation.
namespace base {
-namespace internal {
-
-template <typename From, typename To>
-struct IsCallbackConvertible : std::false_type {};
-
-template <typename Signature>
-struct IsCallbackConvertible<RepeatingCallback<Signature>,
- OnceCallback<Signature>> : std::true_type {};
+template <typename R, typename... Args>
+class OnceCallback<R(Args...)> : public internal::CallbackBase {
+ public:
+ using RunType = R(Args...);
+ using PolymorphicInvoke = R (*)(internal::BindStateBase*,
+ internal::PassingType<Args>...);
-} // namespace internal
+ constexpr OnceCallback() = default;
+ OnceCallback(std::nullptr_t) = delete;
-template <typename R,
- typename... Args,
- internal::CopyMode copy_mode,
- internal::RepeatMode repeat_mode>
-class Callback<R(Args...), copy_mode, repeat_mode>
- : public internal::CallbackBase<copy_mode> {
- public:
- static_assert(repeat_mode != internal::RepeatMode::Once ||
- copy_mode == internal::CopyMode::MoveOnly,
- "OnceCallback must be MoveOnly.");
+ explicit OnceCallback(internal::BindStateBase* bind_state)
+ : internal::CallbackBase(bind_state) {}
- using RunType = R(Args...);
- using PolymorphicInvoke = R (*)(internal::BindStateBase*, Args&&...);
+ OnceCallback(const OnceCallback&) = delete;
+ OnceCallback& operator=(const OnceCallback&) = delete;
- Callback() : internal::CallbackBase<copy_mode>(nullptr) {}
+ OnceCallback(OnceCallback&&) noexcept = default;
+ OnceCallback& operator=(OnceCallback&&) noexcept = default;
- explicit Callback(internal::BindStateBase* bind_state)
- : internal::CallbackBase<copy_mode>(bind_state) {
- }
+ OnceCallback(RepeatingCallback<RunType> other)
+ : internal::CallbackBase(std::move(other)) {}
- template <typename OtherCallback,
- typename = typename std::enable_if<
- internal::IsCallbackConvertible<OtherCallback, Callback>::value
- >::type>
- Callback(OtherCallback other)
- : internal::CallbackBase<copy_mode>(std::move(other)) {}
-
- template <typename OtherCallback,
- typename = typename std::enable_if<
- internal::IsCallbackConvertible<OtherCallback, Callback>::value
- >::type>
- Callback& operator=(OtherCallback other) {
- static_cast<internal::CallbackBase<copy_mode>&>(*this) = std::move(other);
+ OnceCallback& operator=(RepeatingCallback<RunType> other) {
+ static_cast<internal::CallbackBase&>(*this) = std::move(other);
return *this;
}
- bool Equals(const Callback& other) const {
- return this->EqualsInternal(other);
- }
+ bool Equals(const OnceCallback& other) const { return EqualsInternal(other); }
R Run(Args... args) const & {
- static_assert(repeat_mode == internal::RepeatMode::Repeating,
+ static_assert(!sizeof(*this),
"OnceCallback::Run() may only be invoked on a non-const "
"rvalue, i.e. std::move(callback).Run().");
+ NOTREACHED();
+ }
+ R Run(Args... args) && {
+ // Move the callback instance into a local variable before the invocation,
+ // that ensures the internal state is cleared after the invocation.
+ // It's not safe to touch |this| after the invocation, since running the
+ // bound function may destroy |this|.
+ OnceCallback cb = std::move(*this);
+ PolymorphicInvoke f =
+ reinterpret_cast<PolymorphicInvoke>(cb.polymorphic_invoke());
+ return f(cb.bind_state_.get(), std::forward<Args>(args)...);
+ }
+};
+
+template <typename R, typename... Args>
+class RepeatingCallback<R(Args...)> : public internal::CallbackBaseCopyable {
+ public:
+ using RunType = R(Args...);
+ using PolymorphicInvoke = R (*)(internal::BindStateBase*,
+ internal::PassingType<Args>...);
+
+ constexpr RepeatingCallback() = default;
+ RepeatingCallback(std::nullptr_t) = delete;
+
+ explicit RepeatingCallback(internal::BindStateBase* bind_state)
+ : internal::CallbackBaseCopyable(bind_state) {}
+
+ // Copyable and movable.
+ RepeatingCallback(const RepeatingCallback&) = default;
+ RepeatingCallback& operator=(const RepeatingCallback&) = default;
+ RepeatingCallback(RepeatingCallback&&) noexcept = default;
+ RepeatingCallback& operator=(RepeatingCallback&&) noexcept = default;
+
+ bool Equals(const RepeatingCallback& other) const {
+ return EqualsInternal(other);
+ }
+
+ R Run(Args... args) const & {
PolymorphicInvoke f =
reinterpret_cast<PolymorphicInvoke>(this->polymorphic_invoke());
return f(this->bind_state_.get(), std::forward<Args>(args)...);
@@ -85,7 +134,7 @@ class Callback<R(Args...), copy_mode, repeat_mode>
// that ensures the internal state is cleared after the invocation.
// It's not safe to touch |this| after the invocation, since running the
// bound function may destroy |this|.
- Callback cb = std::move(*this);
+ RepeatingCallback cb = std::move(*this);
PolymorphicInvoke f =
reinterpret_cast<PolymorphicInvoke>(cb.polymorphic_invoke());
return f(cb.bind_state_.get(), std::forward<Args>(args)...);
diff --git a/base/callback_forward.h b/base/callback_forward.h
index 13eed0eb0d..f1851c4fbb 100644
--- a/base/callback_forward.h
+++ b/base/callback_forward.h
@@ -6,42 +6,21 @@
#define BASE_CALLBACK_FORWARD_H_
namespace base {
-namespace internal {
-// CopyMode is used to control the copyablity of a Callback.
-// MoveOnly indicates the Callback is not copyable but movable, and Copyable
-// indicates it is copyable and movable.
-enum class CopyMode {
- MoveOnly,
- Copyable,
-};
-
-enum class RepeatMode {
- Once,
- Repeating,
-};
+template <typename Signature>
+class OnceCallback;
-} // namespace internal
+template <typename Signature>
+class RepeatingCallback;
-template <typename Signature,
- internal::CopyMode copy_mode = internal::CopyMode::Copyable,
- internal::RepeatMode repeat_mode = internal::RepeatMode::Repeating>
-class Callback;
+template <typename Signature>
+using Callback = RepeatingCallback<Signature>;
// Syntactic sugar to make Callback<void()> easier to declare since it
// will be used in a lot of APIs with delayed execution.
-using Closure = Callback<void()>;
-
-template <typename Signature>
-using OnceCallback = Callback<Signature,
- internal::CopyMode::MoveOnly,
- internal::RepeatMode::Once>;
-template <typename Signature>
-using RepeatingCallback = Callback<Signature,
- internal::CopyMode::Copyable,
- internal::RepeatMode::Repeating>;
using OnceClosure = OnceCallback<void()>;
using RepeatingClosure = RepeatingCallback<void()>;
+using Closure = Callback<void()>;
} // namespace base
diff --git a/base/callback_helpers.cc b/base/callback_helpers.cc
index 838e6c8d84..90867310c8 100644
--- a/base/callback_helpers.cc
+++ b/base/callback_helpers.cc
@@ -4,18 +4,16 @@
#include "base/callback_helpers.h"
-#include "base/callback.h"
-
namespace base {
-ScopedClosureRunner::ScopedClosureRunner() {}
+ScopedClosureRunner::ScopedClosureRunner() = default;
-ScopedClosureRunner::ScopedClosureRunner(const Closure& closure)
- : closure_(closure) {}
+ScopedClosureRunner::ScopedClosureRunner(OnceClosure closure)
+ : closure_(std::move(closure)) {}
ScopedClosureRunner::~ScopedClosureRunner() {
if (!closure_.is_null())
- closure_.Run();
+ std::move(closure_).Run();
}
ScopedClosureRunner::ScopedClosureRunner(ScopedClosureRunner&& other)
@@ -28,19 +26,15 @@ ScopedClosureRunner& ScopedClosureRunner::operator=(
}
void ScopedClosureRunner::RunAndReset() {
- Closure old_closure = Release();
- if (!old_closure.is_null())
- old_closure.Run();
+ std::move(closure_).Run();
}
-void ScopedClosureRunner::ReplaceClosure(const Closure& closure) {
- closure_ = closure;
+void ScopedClosureRunner::ReplaceClosure(OnceClosure closure) {
+ closure_ = std::move(closure);
}
-Closure ScopedClosureRunner::Release() {
- Closure result = closure_;
- closure_.Reset();
- return result;
+OnceClosure ScopedClosureRunner::Release() {
+ return std::move(closure_);
}
} // namespace base
diff --git a/base/callback_helpers.h b/base/callback_helpers.h
index 6e0aee8882..0cdda6de4f 100644
--- a/base/callback_helpers.h
+++ b/base/callback_helpers.h
@@ -6,36 +6,76 @@
// are implemented using templates, with a class per callback signature, adding
// methods to Callback<> itself is unattractive (lots of extra code gets
// generated). Instead, consider adding methods here.
-//
-// ResetAndReturn(&cb) is like cb.Reset() but allows executing a callback (via a
-// move or copy) after the original callback is Reset(). This can be handy if
-// Run() reads/writes the variable holding the Callback.
#ifndef BASE_CALLBACK_HELPERS_H_
#define BASE_CALLBACK_HELPERS_H_
+#include <utility>
+
+#include "base/atomicops.h"
+#include "base/bind.h"
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/macros.h"
+#include "base/memory/ptr_util.h"
namespace base {
-template <typename Signature,
- internal::CopyMode copy_mode,
- internal::RepeatMode repeat_mode>
-base::Callback<Signature, copy_mode, repeat_mode> ResetAndReturn(
- base::Callback<Signature, copy_mode, repeat_mode>* cb) {
- base::Callback<Signature, copy_mode, repeat_mode> ret(std::move(*cb));
+// Prefer std::move() over ResetAndReturn().
+template <typename CallbackType>
+CallbackType ResetAndReturn(CallbackType* cb) {
+ CallbackType ret(std::move(*cb));
DCHECK(!*cb);
return ret;
}
+namespace internal {
+
+template <typename... Args>
+class AdaptCallbackForRepeatingHelper final {
+ public:
+ explicit AdaptCallbackForRepeatingHelper(OnceCallback<void(Args...)> callback)
+ : callback_(std::move(callback)) {
+ DCHECK(callback_);
+ }
+
+ void Run(Args... args) {
+ if (subtle::NoBarrier_AtomicExchange(&has_run_, 1))
+ return;
+ DCHECK(callback_);
+ std::move(callback_).Run(std::forward<Args>(args)...);
+ }
+
+ private:
+ volatile subtle::Atomic32 has_run_ = 0;
+ base::OnceCallback<void(Args...)> callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(AdaptCallbackForRepeatingHelper);
+};
+
+} // namespace internal
+
+// Wraps the given OnceCallback into a RepeatingCallback that relays its
+// invocation to the original OnceCallback on the first invocation. The
+// following invocations are just ignored.
+//
+// Note that this deliberately subverts the Once/Repeating paradigm of Callbacks
+// but helps ease the migration from old-style Callbacks. Avoid if possible; use
+// if necessary for migration. TODO(tzik): Remove it. https://crbug.com/730593
+template <typename... Args>
+RepeatingCallback<void(Args...)> AdaptCallbackForRepeating(
+ OnceCallback<void(Args...)> callback) {
+ using Helper = internal::AdaptCallbackForRepeatingHelper<Args...>;
+ return base::BindRepeating(&Helper::Run,
+ std::make_unique<Helper>(std::move(callback)));
+}
+
// ScopedClosureRunner is akin to std::unique_ptr<> for Closures. It ensures
// that the Closure is executed no matter how the current scope exits.
class BASE_EXPORT ScopedClosureRunner {
public:
ScopedClosureRunner();
- explicit ScopedClosureRunner(const Closure& closure);
+ explicit ScopedClosureRunner(OnceClosure closure);
~ScopedClosureRunner();
ScopedClosureRunner(ScopedClosureRunner&& other);
@@ -48,13 +88,13 @@ class BASE_EXPORT ScopedClosureRunner {
void RunAndReset();
// Replaces closure with the new one releasing the old one without calling it.
- void ReplaceClosure(const Closure& closure);
+ void ReplaceClosure(OnceClosure closure);
// Releases the Closure without calling.
- Closure Release() WARN_UNUSED_RESULT;
+ OnceClosure Release() WARN_UNUSED_RESULT;
private:
- Closure closure_;
+ OnceClosure closure_;
DISALLOW_COPY_AND_ASSIGN(ScopedClosureRunner);
};
diff --git a/base/callback_helpers_unittest.cc b/base/callback_helpers_unittest.cc
index 6c48d7ce4e..1c1102db91 100644
--- a/base/callback_helpers_unittest.cc
+++ b/base/callback_helpers_unittest.cc
@@ -43,14 +43,14 @@ TEST(CallbackHelpersTest, TestScopedClosureRunnerExitScope) {
TEST(CallbackHelpersTest, TestScopedClosureRunnerRelease) {
int run_count = 0;
- base::Closure c;
+ base::OnceClosure c;
{
base::ScopedClosureRunner runner(base::Bind(&Increment, &run_count));
c = runner.Release();
EXPECT_EQ(0, run_count);
}
EXPECT_EQ(0, run_count);
- c.Run();
+ std::move(c).Run();
EXPECT_EQ(1, run_count);
}
@@ -109,4 +109,19 @@ TEST(CallbackHelpersTest, TestScopedClosureRunnerMoveAssignment) {
EXPECT_EQ(1, run_count_2);
}
+TEST(CallbackHelpersTest, TestAdaptCallbackForRepeating) {
+ int count = 0;
+ base::OnceCallback<void(int*)> cb =
+ base::BindOnce([](int* count) { ++*count; });
+
+ base::RepeatingCallback<void(int*)> wrapped =
+ base::AdaptCallbackForRepeating(std::move(cb));
+
+ EXPECT_EQ(0, count);
+ wrapped.Run(&count);
+ EXPECT_EQ(1, count);
+ wrapped.Run(&count);
+ EXPECT_EQ(1, count);
+}
+
} // namespace
diff --git a/base/callback_internal.cc b/base/callback_internal.cc
index a760f0664c..dd000ca8e2 100644
--- a/base/callback_internal.cc
+++ b/base/callback_internal.cc
@@ -33,73 +33,52 @@ BindStateBase::BindStateBase(InvokeFuncStorage polymorphic_invoke,
destructor_(destructor),
is_cancelled_(is_cancelled) {}
-CallbackBase<CopyMode::MoveOnly>::CallbackBase(CallbackBase&& c) = default;
-
-CallbackBase<CopyMode::MoveOnly>&
-CallbackBase<CopyMode::MoveOnly>::operator=(CallbackBase&& c) = default;
-
-CallbackBase<CopyMode::MoveOnly>::CallbackBase(
- const CallbackBase<CopyMode::Copyable>& c)
+CallbackBase& CallbackBase::operator=(CallbackBase&& c) noexcept = default;
+CallbackBase::CallbackBase(const CallbackBaseCopyable& c)
: bind_state_(c.bind_state_) {}
-CallbackBase<CopyMode::MoveOnly>& CallbackBase<CopyMode::MoveOnly>::operator=(
- const CallbackBase<CopyMode::Copyable>& c) {
+CallbackBase& CallbackBase::operator=(const CallbackBaseCopyable& c) {
bind_state_ = c.bind_state_;
return *this;
}
-CallbackBase<CopyMode::MoveOnly>::CallbackBase(
- CallbackBase<CopyMode::Copyable>&& c)
+CallbackBase::CallbackBase(CallbackBaseCopyable&& c) noexcept
: bind_state_(std::move(c.bind_state_)) {}
-CallbackBase<CopyMode::MoveOnly>& CallbackBase<CopyMode::MoveOnly>::operator=(
- CallbackBase<CopyMode::Copyable>&& c) {
+CallbackBase& CallbackBase::operator=(CallbackBaseCopyable&& c) noexcept {
bind_state_ = std::move(c.bind_state_);
return *this;
}
-void CallbackBase<CopyMode::MoveOnly>::Reset() {
+void CallbackBase::Reset() {
// NULL the bind_state_ last, since it may be holding the last ref to whatever
// object owns us, and we may be deleted after that.
bind_state_ = nullptr;
}
-bool CallbackBase<CopyMode::MoveOnly>::IsCancelled() const {
+bool CallbackBase::IsCancelled() const {
DCHECK(bind_state_);
return bind_state_->IsCancelled();
}
-bool CallbackBase<CopyMode::MoveOnly>::EqualsInternal(
- const CallbackBase& other) const {
+bool CallbackBase::EqualsInternal(const CallbackBase& other) const {
return bind_state_ == other.bind_state_;
}
-CallbackBase<CopyMode::MoveOnly>::CallbackBase(BindStateBase* bind_state)
- : bind_state_(bind_state ? AdoptRef(bind_state) : nullptr) {
- DCHECK(!bind_state_.get() || bind_state_->HasOneRef());
-}
-
-CallbackBase<CopyMode::MoveOnly>::~CallbackBase() {}
+CallbackBase::~CallbackBase() = default;
-CallbackBase<CopyMode::Copyable>::CallbackBase(
- const CallbackBase& c)
- : CallbackBase<CopyMode::MoveOnly>(nullptr) {
+CallbackBaseCopyable::CallbackBaseCopyable(const CallbackBaseCopyable& c) {
bind_state_ = c.bind_state_;
}
-CallbackBase<CopyMode::Copyable>::CallbackBase(CallbackBase&& c) = default;
-
-CallbackBase<CopyMode::Copyable>&
-CallbackBase<CopyMode::Copyable>::operator=(const CallbackBase& c) {
+CallbackBaseCopyable& CallbackBaseCopyable::operator=(
+ const CallbackBaseCopyable& c) {
bind_state_ = c.bind_state_;
return *this;
}
-CallbackBase<CopyMode::Copyable>&
-CallbackBase<CopyMode::Copyable>::operator=(CallbackBase&& c) = default;
-
-template class CallbackBase<CopyMode::MoveOnly>;
-template class CallbackBase<CopyMode::Copyable>;
+CallbackBaseCopyable& CallbackBaseCopyable::operator=(
+ CallbackBaseCopyable&& c) noexcept = default;
} // namespace internal
} // namespace base
diff --git a/base/callback_internal.h b/base/callback_internal.h
index 29b07c23bd..1215e3e870 100644
--- a/base/callback_internal.h
+++ b/base/callback_internal.h
@@ -19,8 +19,8 @@ struct FakeBindState;
namespace internal {
-template <CopyMode copy_mode>
class CallbackBase;
+class CallbackBaseCopyable;
class BindStateBase;
@@ -31,6 +31,9 @@ struct BindStateBaseRefCountTraits {
static void Destruct(const BindStateBase*);
};
+template <typename T>
+using PassingType = std::conditional_t<std::is_scalar<T>::value, T, T&&>;
+
// BindStateBase is used to provide an opaque handle that the Callback
// class can use to represent a function object with bound arguments. It
// behaves as an existential type that is used by a corresponding
@@ -61,8 +64,8 @@ class BASE_EXPORT BindStateBase
friend struct BindStateBaseRefCountTraits;
friend class RefCountedThreadSafe<BindStateBase, BindStateBaseRefCountTraits>;
- template <CopyMode copy_mode>
friend class CallbackBase;
+ friend class CallbackBaseCopyable;
// Whitelist subclasses that access the destructor of BindStateBase.
template <typename Functor, typename... BoundArgs>
@@ -90,17 +93,16 @@ class BASE_EXPORT BindStateBase
// template bloat.
// CallbackBase<MoveOnly> is a direct base class of MoveOnly callbacks, and
// CallbackBase<Copyable> uses CallbackBase<MoveOnly> for its implementation.
-template <>
-class BASE_EXPORT CallbackBase<CopyMode::MoveOnly> {
+class BASE_EXPORT CallbackBase {
public:
- CallbackBase(CallbackBase&& c);
- CallbackBase& operator=(CallbackBase&& c);
+ inline CallbackBase(CallbackBase&& c) noexcept;
+ CallbackBase& operator=(CallbackBase&& c) noexcept;
- explicit CallbackBase(const CallbackBase<CopyMode::Copyable>& c);
- CallbackBase& operator=(const CallbackBase<CopyMode::Copyable>& c);
+ explicit CallbackBase(const CallbackBaseCopyable& c);
+ CallbackBase& operator=(const CallbackBaseCopyable& c);
- explicit CallbackBase(CallbackBase<CopyMode::Copyable>&& c);
- CallbackBase& operator=(CallbackBase<CopyMode::Copyable>&& c);
+ explicit CallbackBase(CallbackBaseCopyable&& c) noexcept;
+ CallbackBase& operator=(CallbackBaseCopyable&& c) noexcept;
// Returns true if Callback is null (doesn't refer to anything).
bool is_null() const { return !bind_state_; }
@@ -119,9 +121,11 @@ class BASE_EXPORT CallbackBase<CopyMode::MoveOnly> {
// Returns true if this callback equals |other|. |other| may be null.
bool EqualsInternal(const CallbackBase& other) const;
+ constexpr inline CallbackBase();
+
// Allow initializing of |bind_state_| via the constructor to avoid default
// initialization of the scoped_refptr.
- explicit CallbackBase(BindStateBase* bind_state);
+ explicit inline CallbackBase(BindStateBase* bind_state);
InvokeFuncStorage polymorphic_invoke() const {
return bind_state_->polymorphic_invoke_;
@@ -135,24 +139,26 @@ class BASE_EXPORT CallbackBase<CopyMode::MoveOnly> {
scoped_refptr<BindStateBase> bind_state_;
};
+constexpr CallbackBase::CallbackBase() = default;
+CallbackBase::CallbackBase(CallbackBase&&) noexcept = default;
+CallbackBase::CallbackBase(BindStateBase* bind_state)
+ : bind_state_(AdoptRef(bind_state)) {}
+
// CallbackBase<Copyable> is a direct base class of Copyable Callbacks.
-template <>
-class BASE_EXPORT CallbackBase<CopyMode::Copyable>
- : public CallbackBase<CopyMode::MoveOnly> {
+class BASE_EXPORT CallbackBaseCopyable : public CallbackBase {
public:
- CallbackBase(const CallbackBase& c);
- CallbackBase(CallbackBase&& c);
- CallbackBase& operator=(const CallbackBase& c);
- CallbackBase& operator=(CallbackBase&& c);
+ CallbackBaseCopyable(const CallbackBaseCopyable& c);
+ CallbackBaseCopyable(CallbackBaseCopyable&& c) noexcept = default;
+ CallbackBaseCopyable& operator=(const CallbackBaseCopyable& c);
+ CallbackBaseCopyable& operator=(CallbackBaseCopyable&& c) noexcept;
+
protected:
- explicit CallbackBase(BindStateBase* bind_state)
- : CallbackBase<CopyMode::MoveOnly>(bind_state) {}
- ~CallbackBase() {}
+ constexpr CallbackBaseCopyable() = default;
+ explicit CallbackBaseCopyable(BindStateBase* bind_state)
+ : CallbackBase(bind_state) {}
+ ~CallbackBaseCopyable() = default;
};
-extern template class CallbackBase<CopyMode::MoveOnly>;
-extern template class CallbackBase<CopyMode::Copyable>;
-
} // namespace internal
} // namespace base
diff --git a/base/callback_list.h b/base/callback_list.h
index 7ab79dd8e1..f455c65730 100644
--- a/base/callback_list.h
+++ b/base/callback_list.h
@@ -15,10 +15,10 @@
// OVERVIEW:
//
-// A container for a list of callbacks. Unlike a normal STL vector or list,
-// this container can be modified during iteration without invalidating the
-// iterator. It safely handles the case of a callback removing itself
-// or another callback from the list while callbacks are being run.
+// A container for a list of (repeating) callbacks. Unlike a normal vector or
+// list, this container can be modified during iteration without invalidating
+// the iterator. It safely handles the case of a callback removing itself or
+// another callback from the list while callbacks are being run.
//
// TYPICAL USAGE:
//
@@ -26,10 +26,8 @@
// public:
// ...
//
-// typedef base::Callback<void(const Foo&)> OnFooCallback;
-//
// std::unique_ptr<base::CallbackList<void(const Foo&)>::Subscription>
-// RegisterCallback(const OnFooCallback& cb) {
+// RegisterCallback(const base::RepeatingCallback<void(const Foo&)>& cb) {
// return callback_list_.Add(cb);
// }
//
@@ -48,7 +46,7 @@
// public:
// MyWidgetListener::MyWidgetListener() {
// foo_subscription_ = MyWidget::GetCurrent()->RegisterCallback(
-// base::Bind(&MyWidgetListener::OnFoo, this)));
+// base::BindRepeating(&MyWidgetListener::OnFoo, this)));
// }
//
// MyWidgetListener::~MyWidgetListener() {
@@ -104,12 +102,12 @@ class CallbackListBase {
// CallbackList is destroyed.
std::unique_ptr<Subscription> Add(const CallbackType& cb) WARN_UNUSED_RESULT {
DCHECK(!cb.is_null());
- return std::unique_ptr<Subscription>(
- new Subscription(this, callbacks_.insert(callbacks_.end(), cb)));
+ return std::make_unique<Subscription>(
+ this, callbacks_.insert(callbacks_.end(), cb));
}
// Sets a callback which will be run when a subscription list is changed.
- void set_removal_callback(const Closure& callback) {
+ void set_removal_callback(const RepeatingClosure& callback) {
removal_callback_ = callback;
}
@@ -146,7 +144,7 @@ class CallbackListBase {
while ((list_iter_ != list_->callbacks_.end()) && list_iter_->is_null())
++list_iter_;
- CallbackType* cb = NULL;
+ CallbackType* cb = nullptr;
if (list_iter_ != list_->callbacks_.end()) {
cb = &(*list_iter_);
++list_iter_;
@@ -172,10 +170,10 @@ class CallbackListBase {
return Iterator(this);
}
- // Compact the list: remove any entries which were NULLed out during
+ // Compact the list: remove any entries which were nulled out during
// iteration.
void Compact() {
- typename std::list<CallbackType>::iterator it = callbacks_.begin();
+ auto it = callbacks_.begin();
bool updated = false;
while (it != callbacks_.end()) {
if ((*it).is_null()) {
@@ -193,7 +191,7 @@ class CallbackListBase {
private:
std::list<CallbackType> callbacks_;
int active_iterator_count_;
- Closure removal_callback_;
+ RepeatingClosure removal_callback_;
DISALLOW_COPY_AND_ASSIGN(CallbackListBase);
};
@@ -204,18 +202,17 @@ template <typename Sig> class CallbackList;
template <typename... Args>
class CallbackList<void(Args...)>
- : public internal::CallbackListBase<Callback<void(Args...)> > {
+ : public internal::CallbackListBase<RepeatingCallback<void(Args...)>> {
public:
- typedef Callback<void(Args...)> CallbackType;
+ using CallbackType = RepeatingCallback<void(Args...)>;
- CallbackList() {}
+ CallbackList() = default;
template <typename... RunArgs>
void Notify(RunArgs&&... args) {
- typename internal::CallbackListBase<CallbackType>::Iterator it =
- this->GetIterator();
+ auto it = this->GetIterator();
CallbackType* cb;
- while ((cb = it.GetNext()) != NULL) {
+ while ((cb = it.GetNext()) != nullptr) {
cb->Run(args...);
}
}
diff --git a/base/callback_list_unittest.cc b/base/callback_list_unittest.cc
index 62081e9a72..6eb5ff7f85 100644
--- a/base/callback_list_unittest.cc
+++ b/base/callback_list_unittest.cc
@@ -310,7 +310,7 @@ TEST(CallbackList, RemovalCallback) {
Bind(&Counter::Increment, Unretained(&remove_count)));
std::unique_ptr<CallbackList<void(void)>::Subscription> subscription =
- cb_reg.Add(Bind(&DoNothing));
+ cb_reg.Add(DoNothing());
// Removing a subscription outside of iteration signals the callback.
EXPECT_EQ(0, remove_count.value());
diff --git a/base/callback_unittest.cc b/base/callback_unittest.cc
index f76adbcdd2..c07d3ee20e 100644
--- a/base/callback_unittest.cc
+++ b/base/callback_unittest.cc
@@ -25,7 +25,7 @@ struct FakeBindState : internal::BindStateBase {
FakeBindState() : BindStateBase(&NopInvokeFunc, &Destroy, &IsCancelled) {}
private:
- ~FakeBindState() {}
+ ~FakeBindState() = default;
static void Destroy(const internal::BindStateBase* self) {
delete static_cast<const FakeBindState*>(self);
}
@@ -41,7 +41,7 @@ class CallbackTest : public ::testing::Test {
CallbackTest()
: callback_a_(new FakeBindState()), callback_b_(new FakeBindState()) {}
- ~CallbackTest() override {}
+ ~CallbackTest() override = default;
protected:
Callback<void()> callback_a_;
diff --git a/base/cancelable_callback.h b/base/cancelable_callback.h
index 13cbd0c213..a98101a162 100644
--- a/base/cancelable_callback.h
+++ b/base/cancelable_callback.h
@@ -56,28 +56,24 @@
#include "base/memory/weak_ptr.h"
namespace base {
+namespace internal {
-template <typename Sig>
-class CancelableCallback;
-
-template <typename... A>
-class CancelableCallback<void(A...)> {
+template <typename CallbackType>
+class CancelableCallbackImpl {
public:
- CancelableCallback() : weak_factory_(this) {}
+ CancelableCallbackImpl() : weak_ptr_factory_(this) {}
// |callback| must not be null.
- explicit CancelableCallback(const base::Callback<void(A...)>& callback)
- : callback_(callback), weak_factory_(this) {
- DCHECK(!callback.is_null());
- InitializeForwarder();
+ explicit CancelableCallbackImpl(CallbackType callback)
+ : callback_(std::move(callback)), weak_ptr_factory_(this) {
+ DCHECK(callback_);
}
- ~CancelableCallback() {}
+ ~CancelableCallbackImpl() = default;
// Cancels and drops the reference to the wrapped callback.
void Cancel() {
- weak_factory_.InvalidateWeakPtrs();
- forwarder_.Reset();
+ weak_ptr_factory_.InvalidateWeakPtrs();
callback_.Reset();
}
@@ -88,48 +84,72 @@ class CancelableCallback<void(A...)> {
// Sets |callback| as the closure that may be cancelled. |callback| may not
// be null. Outstanding and any previously wrapped callbacks are cancelled.
- void Reset(const base::Callback<void(A...)>& callback) {
- DCHECK(!callback.is_null());
-
+ void Reset(CallbackType callback) {
+ DCHECK(callback);
// Outstanding tasks (e.g., posted to a message loop) must not be called.
Cancel();
-
- // |forwarder_| is no longer valid after Cancel(), so re-bind.
- InitializeForwarder();
-
- callback_ = callback;
+ callback_ = std::move(callback);
}
// Returns a callback that can be disabled by calling Cancel().
- const base::Callback<void(A...)>& callback() const {
- return forwarder_;
+ CallbackType callback() const {
+ if (!callback_)
+ return CallbackType();
+ CallbackType forwarder;
+ MakeForwarder(&forwarder);
+ return forwarder;
}
private:
- void Forward(A... args) const {
- callback_.Run(std::forward<A>(args)...);
+ template <typename... Args>
+ void MakeForwarder(RepeatingCallback<void(Args...)>* out) const {
+ using ForwarderType = void (CancelableCallbackImpl::*)(Args...);
+ ForwarderType forwarder = &CancelableCallbackImpl::ForwardRepeating;
+ *out = BindRepeating(forwarder, weak_ptr_factory_.GetWeakPtr());
}
- // Helper method to bind |forwarder_| using a weak pointer from
- // |weak_factory_|.
- void InitializeForwarder() {
- forwarder_ = base::Bind(&CancelableCallback<void(A...)>::Forward,
- weak_factory_.GetWeakPtr());
+ template <typename... Args>
+ void MakeForwarder(OnceCallback<void(Args...)>* out) const {
+ using ForwarderType = void (CancelableCallbackImpl::*)(Args...);
+ ForwarderType forwarder = &CancelableCallbackImpl::ForwardOnce;
+ *out = BindOnce(forwarder, weak_ptr_factory_.GetWeakPtr());
}
- // The wrapper closure.
- base::Callback<void(A...)> forwarder_;
+ template <typename... Args>
+ void ForwardRepeating(Args... args) {
+ callback_.Run(std::forward<Args>(args)...);
+ }
- // The stored closure that may be cancelled.
- base::Callback<void(A...)> callback_;
+ template <typename... Args>
+ void ForwardOnce(Args... args) {
+ weak_ptr_factory_.InvalidateWeakPtrs();
+ std::move(callback_).Run(std::forward<Args>(args)...);
+ }
- // Used to ensure Forward() is not run when this object is destroyed.
- base::WeakPtrFactory<CancelableCallback<void(A...)>> weak_factory_;
+ // The stored closure that may be cancelled.
+ CallbackType callback_;
+ mutable base::WeakPtrFactory<CancelableCallbackImpl> weak_ptr_factory_;
- DISALLOW_COPY_AND_ASSIGN(CancelableCallback);
+ DISALLOW_COPY_AND_ASSIGN(CancelableCallbackImpl);
};
-typedef CancelableCallback<void(void)> CancelableClosure;
+} // namespace internal
+
+// Consider using base::WeakPtr directly instead of base::CancelableCallback for
+// the task cancellation.
+template <typename Signature>
+using CancelableOnceCallback =
+ internal::CancelableCallbackImpl<OnceCallback<Signature>>;
+using CancelableOnceClosure = CancelableOnceCallback<void()>;
+
+template <typename Signature>
+using CancelableRepeatingCallback =
+ internal::CancelableCallbackImpl<RepeatingCallback<Signature>>;
+using CancelableRepeatingClosure = CancelableOnceCallback<void()>;
+
+template <typename Signature>
+using CancelableCallback = CancelableRepeatingCallback<Signature>;
+using CancelableClosure = CancelableCallback<void()>;
} // namespace base
diff --git a/base/cancelable_callback_unittest.cc b/base/cancelable_callback_unittest.cc
index 23b6c1c244..373498cbde 100644
--- a/base/cancelable_callback_unittest.cc
+++ b/base/cancelable_callback_unittest.cc
@@ -11,6 +11,7 @@
#include "base/location.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/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
@@ -22,7 +23,8 @@ namespace {
class TestRefCounted : public RefCountedThreadSafe<TestRefCounted> {
private:
friend class RefCountedThreadSafe<TestRefCounted>;
- ~TestRefCounted() {};
+ ~TestRefCounted() = default;
+ ;
};
void Increment(int* count) { (*count)++; }
diff --git a/base/cfi_buildflags.h b/base/cfi_buildflags.h
new file mode 100644
index 0000000000..be8270e3d9
--- /dev/null
+++ b/base/cfi_buildflags.h
@@ -0,0 +1,7 @@
+// Generated by build/write_buildflag_header.py
+// From "base_debugging_flags"
+#ifndef BASE_CFI_BUILDFLAGS_H_
+#define BASE_CFI_BUILDFLAGS_H_
+#include "build/buildflag.h"
+#define BUILDFLAG_INTERNAL_CFI_ICALL_CHECK() (0)
+#endif // BASE_CFI_BUILDFLAGS_H_
diff --git a/base/command_line.cc b/base/command_line.cc
index 873da81348..aec89f5633 100644
--- a/base/command_line.cc
+++ b/base/command_line.cc
@@ -10,6 +10,7 @@
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/macros.h"
+#include "base/stl_util.h"
#include "base/strings/string_split.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/string_util.h"
@@ -23,7 +24,7 @@
namespace base {
-CommandLine* CommandLine::current_process_commandline_ = NULL;
+CommandLine* CommandLine::current_process_commandline_ = nullptr;
namespace {
@@ -37,7 +38,7 @@ const CommandLine::CharType kSwitchValueSeparator[] = FILE_PATH_LITERAL("=");
// value by changing the value of switch_prefix_count to be one less than
// the array size.
const CommandLine::CharType* const kSwitchPrefixes[] = {L"--", L"-", L"/"};
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
// Unixes don't use slash as a switch.
const CommandLine::CharType* const kSwitchPrefixes[] = {"--", "-"};
#endif
@@ -78,7 +79,7 @@ void AppendSwitchesAndArguments(CommandLine* command_line,
CommandLine::StringType arg = argv[i];
#if defined(OS_WIN)
TrimWhitespace(arg, TRIM_ALL, &arg);
-#else
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
TrimWhitespaceASCII(arg, TRIM_ALL, &arg);
#endif
@@ -89,8 +90,10 @@ void AppendSwitchesAndArguments(CommandLine* command_line,
#if defined(OS_WIN)
command_line->AppendSwitchNative(UTF16ToASCII(switch_string),
switch_value);
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
command_line->AppendSwitchNative(switch_string, switch_value);
+#else
+#error Unsupported platform
#endif
} else {
command_line->AppendArgNative(arg);
@@ -173,23 +176,11 @@ CommandLine::CommandLine(const StringVector& argv)
InitFromArgv(argv);
}
-CommandLine::CommandLine(const CommandLine& other)
- : argv_(other.argv_),
- switches_(other.switches_),
- begin_args_(other.begin_args_) {
- ResetStringPieces();
-}
+CommandLine::CommandLine(const CommandLine& other) = default;
-CommandLine& CommandLine::operator=(const CommandLine& other) {
- argv_ = other.argv_;
- switches_ = other.switches_;
- begin_args_ = other.begin_args_;
- ResetStringPieces();
- return *this;
-}
+CommandLine& CommandLine::operator=(const CommandLine& other) = default;
-CommandLine::~CommandLine() {
-}
+CommandLine::~CommandLine() = default;
#if defined(OS_WIN)
// static
@@ -223,8 +214,10 @@ bool CommandLine::Init(int argc, const char* const* argv) {
current_process_commandline_ = new CommandLine(NO_PROGRAM);
#if defined(OS_WIN)
current_process_commandline_->ParseFromString(::GetCommandLineW());
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
current_process_commandline_->InitFromArgv(argc, argv);
+#else
+#error Unsupported platform
#endif
return true;
@@ -234,7 +227,7 @@ bool CommandLine::Init(int argc, const char* const* argv) {
void CommandLine::Reset() {
DCHECK(current_process_commandline_);
delete current_process_commandline_;
- current_process_commandline_ = NULL;
+ current_process_commandline_ = nullptr;
}
// static
@@ -268,7 +261,6 @@ void CommandLine::InitFromArgv(int argc,
void CommandLine::InitFromArgv(const StringVector& argv) {
argv_ = StringVector(1);
switches_.clear();
- switches_by_stringpiece_.clear();
begin_args_ = 1;
SetProgram(argv.empty() ? FilePath() : FilePath(argv[0]));
AppendSwitchesAndArguments(this, argv);
@@ -281,15 +273,16 @@ FilePath CommandLine::GetProgram() const {
void CommandLine::SetProgram(const FilePath& program) {
#if defined(OS_WIN)
TrimWhitespace(program.value(), TRIM_ALL, &argv_[0]);
-#else
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
TrimWhitespaceASCII(program.value(), TRIM_ALL, &argv_[0]);
+#else
+#error Unsupported platform
#endif
}
bool CommandLine::HasSwitch(const base::StringPiece& switch_string) const {
DCHECK_EQ(ToLowerASCII(switch_string), switch_string);
- return switches_by_stringpiece_.find(switch_string) !=
- switches_by_stringpiece_.end();
+ return ContainsKey(switches_, switch_string);
}
bool CommandLine::HasSwitch(const char switch_constant[]) const {
@@ -305,7 +298,7 @@ std::string CommandLine::GetSwitchValueASCII(
}
#if defined(OS_WIN)
return UTF16ToASCII(value);
-#else
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
return value;
#endif
}
@@ -318,9 +311,8 @@ FilePath CommandLine::GetSwitchValuePath(
CommandLine::StringType CommandLine::GetSwitchValueNative(
const base::StringPiece& switch_string) const {
DCHECK_EQ(ToLowerASCII(switch_string), switch_string);
- auto result = switches_by_stringpiece_.find(switch_string);
- return result == switches_by_stringpiece_.end() ? StringType()
- : *(result->second);
+ auto result = switches_.find(switch_string);
+ return result == switches_.end() ? StringType() : result->second;
}
void CommandLine::AppendSwitch(const std::string& switch_string) {
@@ -337,7 +329,7 @@ void CommandLine::AppendSwitchNative(const std::string& switch_string,
#if defined(OS_WIN)
const std::string switch_key = ToLowerASCII(switch_string);
StringType combined_switch_string(ASCIIToUTF16(switch_key));
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
const std::string& switch_key = switch_string;
StringType combined_switch_string(switch_key);
#endif
@@ -346,7 +338,6 @@ void CommandLine::AppendSwitchNative(const std::string& switch_string,
switches_.insert(make_pair(switch_key.substr(prefix_length), value));
if (!insertion.second)
insertion.first->second = value;
- switches_by_stringpiece_[insertion.first->first] = &(insertion.first->second);
// Preserve existing switch prefixes in |argv_|; only append one if necessary.
if (prefix_length == 0)
combined_switch_string = kSwitchPrefixes[0] + combined_switch_string;
@@ -360,8 +351,10 @@ void CommandLine::AppendSwitchASCII(const std::string& switch_string,
const std::string& value_string) {
#if defined(OS_WIN)
AppendSwitchNative(switch_string, ASCIIToUTF16(value_string));
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
AppendSwitchNative(switch_string, value_string);
+#else
+#error Unsupported platform
#endif
}
@@ -389,8 +382,10 @@ void CommandLine::AppendArg(const std::string& value) {
#if defined(OS_WIN)
DCHECK(IsStringUTF8(value));
AppendArgNative(UTF8ToWide(value));
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
AppendArgNative(value);
+#else
+#error Unsupported platform
#endif
}
@@ -489,10 +484,4 @@ CommandLine::StringType CommandLine::GetArgumentsStringInternal(
return params;
}
-void CommandLine::ResetStringPieces() {
- switches_by_stringpiece_.clear();
- for (const auto& entry : switches_)
- switches_by_stringpiece_[entry.first] = &(entry.second);
-}
-
} // namespace base
diff --git a/base/command_line.h b/base/command_line.h
index 3d29f8fee7..25fd7d9193 100644
--- a/base/command_line.h
+++ b/base/command_line.h
@@ -34,14 +34,13 @@ class BASE_EXPORT CommandLine {
#if defined(OS_WIN)
// The native command line string type.
using StringType = string16;
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
using StringType = std::string;
#endif
using CharType = StringType::value_type;
using StringVector = std::vector<StringType>;
- using SwitchMap = std::map<std::string, StringType>;
- using StringPieceSwitchMap = std::map<StringPiece, const StringType*>;
+ using SwitchMap = std::map<std::string, StringType, std::less<>>;
// A constructor for CommandLines that only carry switches and arguments.
enum NoProgram { NO_PROGRAM };
@@ -205,7 +204,7 @@ class BASE_EXPORT CommandLine {
void AppendArguments(const CommandLine& other, bool include_program);
// Insert a command before the current command.
- // Common for debuggers, like "valgrind" or "gdb --args".
+ // Common for debuggers, like "gdb --args".
void PrependWrapper(const StringType& wrapper);
#if defined(OS_WIN)
@@ -216,7 +215,7 @@ class BASE_EXPORT CommandLine {
private:
// Disallow default constructor; a program name must be explicitly specified.
- CommandLine();
+ CommandLine() = delete;
// Allow the copy constructor. A common pattern is to copy of the current
// process's command line and then add some flags to it. For example:
// CommandLine cl(*CommandLine::ForCurrentProcess());
@@ -230,11 +229,6 @@ class BASE_EXPORT CommandLine {
// also quotes parts with '%' in them.
StringType GetArgumentsStringInternal(bool quote_placeholders) const;
- // Reconstruct |switches_by_stringpiece| to be a mirror of |switches|.
- // |switches_by_stringpiece| only contains pointers to objects owned by
- // |switches|.
- void ResetStringPieces();
-
// The singleton CommandLine representing the current process's command line.
static CommandLine* current_process_commandline_;
@@ -244,12 +238,6 @@ class BASE_EXPORT CommandLine {
// Parsed-out switch keys and values.
SwitchMap switches_;
- // A mirror of |switches_| with only references to the actual strings.
- // The StringPiece internally holds a pointer to a key in |switches_| while
- // the mapped_type points to a value in |switches_|.
- // Used for allocation-free lookups.
- StringPieceSwitchMap switches_by_stringpiece_;
-
// The index after the program and switches, any arguments start here.
size_t begin_args_;
};
diff --git a/base/command_line_unittest.cc b/base/command_line_unittest.cc
index 79c9aecc2a..3718cd999c 100644
--- a/base/command_line_unittest.cc
+++ b/base/command_line_unittest.cc
@@ -176,7 +176,7 @@ TEST(CommandLineTest, EmptyString) {
EXPECT_EQ(1U, cl_from_string.argv().size());
EXPECT_TRUE(cl_from_string.GetArgs().empty());
#endif
- CommandLine cl_from_argv(0, NULL);
+ CommandLine cl_from_argv(0, nullptr);
EXPECT_TRUE(cl_from_argv.GetCommandLineString().empty());
EXPECT_TRUE(cl_from_argv.GetProgram().empty());
EXPECT_EQ(1U, cl_from_argv.argv().size());
@@ -208,7 +208,7 @@ TEST(CommandLineTest, GetArgumentsString) {
CommandLine::StringType expected_third_arg(UTF8ToUTF16(kThirdArgName));
CommandLine::StringType expected_fourth_arg(UTF8ToUTF16(kFourthArgName));
CommandLine::StringType expected_fifth_arg(UTF8ToUTF16(kFifthArgName));
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
CommandLine::StringType expected_first_arg(kFirstArgName);
CommandLine::StringType expected_second_arg(kSecondArgName);
CommandLine::StringType expected_third_arg(kThirdArgName);
@@ -382,9 +382,9 @@ TEST(CommandLineTest, ProgramQuotes) {
TEST(CommandLineTest, Init) {
// Call Init without checking output once so we know it's been called
// whether or not the test runner does so.
- CommandLine::Init(0, NULL);
+ CommandLine::Init(0, nullptr);
CommandLine* initial = CommandLine::ForCurrentProcess();
- EXPECT_FALSE(CommandLine::Init(0, NULL));
+ EXPECT_FALSE(CommandLine::Init(0, nullptr));
CommandLine* current = CommandLine::ForCurrentProcess();
EXPECT_EQ(initial, current);
}
diff --git a/base/compiler_specific.h b/base/compiler_specific.h
index 327e3fabc0..a930f5d6cf 100644
--- a/base/compiler_specific.h
+++ b/base/compiler_specific.h
@@ -48,21 +48,6 @@
#define MSVC_DISABLE_OPTIMIZE() __pragma(optimize("", off))
#define MSVC_ENABLE_OPTIMIZE() __pragma(optimize("", on))
-// Allows exporting a class that inherits from a non-exported base class.
-// This uses suppress instead of push/pop because the delimiter after the
-// declaration (either "," or "{") has to be placed before the pop macro.
-//
-// Example usage:
-// class EXPORT_API Foo : NON_EXPORTED_BASE(public Bar) {
-//
-// MSVC Compiler warning C4275:
-// non dll-interface class 'Bar' used as base for dll-interface class 'Foo'.
-// Note that this is intended to be used only when no access to the base class'
-// static data is done through derived classes or inline methods. For more info,
-// see http://msdn.microsoft.com/en-us/library/3tdb471s(VS.80).aspx
-#define NON_EXPORTED_BASE(code) MSVC_SUPPRESS_WARNING(4275) \
- code
-
#else // Not MSVC
#define _Printf_format_string_
@@ -72,18 +57,16 @@
#define MSVC_POP_WARNING()
#define MSVC_DISABLE_OPTIMIZE()
#define MSVC_ENABLE_OPTIMIZE()
-#define NON_EXPORTED_BASE(code) code
#endif // COMPILER_MSVC
-
// Annotate a variable indicating it's ok if the variable is not used.
// (Typically used to silence a compiler warning when the assignment
// is important for some other reason.)
// Use like:
// int x = ...;
// ALLOW_UNUSED_LOCAL(x);
-#define ALLOW_UNUSED_LOCAL(x) false ? (void)x : (void)0
+#define ALLOW_UNUSED_LOCAL(x) (void)x
// Annotate a typedef or function indicating it's ok if it's not used.
// Use like:
@@ -117,21 +100,29 @@
// Use like:
// class ALIGNAS(16) MyClass { ... }
// ALIGNAS(16) int array[4];
+//
+// In most places you can use the C++11 keyword "alignas", which is preferred.
+//
+// But compilers have trouble mixing __attribute__((...)) syntax with
+// alignas(...) syntax.
+//
+// Doesn't work in clang or gcc:
+// struct alignas(16) __attribute__((packed)) S { char c; };
+// Works in clang but not gcc:
+// struct __attribute__((packed)) alignas(16) S2 { char c; };
+// Works in clang and gcc:
+// struct alignas(16) S3 { char c; } __attribute__((packed));
+//
+// There are also some attributes that must be specified *before* a class
+// definition: visibility (used for exporting functions/classes) is one of
+// these attributes. This means that it is not possible to use alignas() with a
+// class that is marked as exported.
#if defined(COMPILER_MSVC)
#define ALIGNAS(byte_alignment) __declspec(align(byte_alignment))
#elif defined(COMPILER_GCC)
#define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment)))
#endif
-// Return the byte alignment of the given type (available at compile time).
-// Use like:
-// ALIGNOF(int32_t) // this would be 4
-#if defined(COMPILER_MSVC)
-#define ALIGNOF(type) __alignof(type)
-#elif defined(COMPILER_GCC)
-#define ALIGNOF(type) __alignof__(type)
-#endif
-
// Annotate a function indicating the caller must examine the return value.
// Use like:
// int foo() WARN_UNUSED_RESULT;
@@ -148,7 +139,7 @@
// |dots_param| is the one-based index of the "..." parameter.
// For v*printf functions (which take a va_list), pass 0 for dots_param.
// (This is undocumented but matches what the system C headers do.)
-#if defined(COMPILER_GCC)
+#if defined(COMPILER_GCC) || defined(__clang__)
#define PRINTF_FORMAT(format_param, dots_param) \
__attribute__((format(printf, format_param, dots_param)))
#else
@@ -212,7 +203,7 @@
// Macro for hinting that an expression is likely to be false.
#if !defined(UNLIKELY)
-#if defined(COMPILER_GCC)
+#if defined(COMPILER_GCC) || defined(__clang__)
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
#else
#define UNLIKELY(x) (x)
@@ -220,7 +211,7 @@
#endif // !defined(UNLIKELY)
#if !defined(LIKELY)
-#if defined(COMPILER_GCC)
+#if defined(COMPILER_GCC) || defined(__clang__)
#define LIKELY(x) __builtin_expect(!!(x), 1)
#else
#define LIKELY(x) (x)
@@ -235,4 +226,11 @@
#define HAS_FEATURE(FEATURE) 0
#endif
+// Macro for telling -Wimplicit-fallthrough that a fallthrough is intentional.
+#if defined(__clang__)
+#define FALLTHROUGH [[clang::fallthrough]]
+#else
+#define FALLTHROUGH
+#endif
+
#endif // BASE_COMPILER_SPECIFIC_H_
diff --git a/base/component_export.h b/base/component_export.h
new file mode 100644
index 0000000000..b5cb364f0d
--- /dev/null
+++ b/base/component_export.h
@@ -0,0 +1,87 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_COMPONENT_EXPORT_H_
+#define BASE_COMPONENT_EXPORT_H_
+
+#include "build/build_config.h"
+
+// Used to annotate symbols which are exported by the component named
+// |component|. Note that this only does the right thing if the corresponding
+// component target's sources are compiled with |IS_$component_IMPL| defined
+// as 1. For example:
+//
+// class COMPONENT_EXPORT(FOO) Bar {};
+//
+// If IS_FOO_IMPL=1 at compile time, then Bar will be annotated using the
+// COMPONENT_EXPORT_ANNOTATION macro defined below. Otherwise it will be
+// annotated using the COMPONENT_IMPORT_ANNOTATION macro.
+#define COMPONENT_EXPORT(component) \
+ COMPONENT_MACRO_CONDITIONAL_(IS_##component##_IMPL, \
+ COMPONENT_EXPORT_ANNOTATION, \
+ COMPONENT_IMPORT_ANNOTATION)
+
+// Indicates whether the current compilation unit is being compiled as part of
+// the implementation of the component named |component|. Expands to |1| if
+// |IS_$component_IMPL| is defined as |1|; expands to |0| otherwise.
+//
+// Note in particular that if |IS_$component_IMPL| is not defined at all, it is
+// still fine to test INSIDE_COMPONENT_IMPL(component), which expands to |0| as
+// expected.
+#define INSIDE_COMPONENT_IMPL(component) \
+ COMPONENT_MACRO_CONDITIONAL_(IS_##component##_IMPL, 1, 0)
+
+// Compiler-specific macros to annotate for export or import of a symbol. No-op
+// in non-component builds. These should not see much if any direct use.
+// Instead use the COMPONENT_EXPORT macro defined above.
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+#define COMPONENT_EXPORT_ANNOTATION __declspec(dllexport)
+#define COMPONENT_IMPORT_ANNOTATION __declspec(dllimport)
+#else // defined(WIN32)
+#define COMPONENT_EXPORT_ANNOTATION __attribute__((visibility("default")))
+#define COMPONENT_IMPORT_ANNOTATION
+#endif // defined(WIN32)
+#else // defined(COMPONENT_BUILD)
+#define COMPONENT_EXPORT_ANNOTATION
+#define COMPONENT_IMPORT_ANNOTATION
+#endif // defined(COMPONENT_BUILD)
+
+// Below this point are several internal utility macros used for the
+// implementation of the above macros. Not intended for external use.
+
+// Helper for conditional expansion to one of two token strings. If |condition|
+// expands to |1| then this macro expands to |consequent|; otherwise it expands
+// to |alternate|.
+#define COMPONENT_MACRO_CONDITIONAL_(condition, consequent, alternate) \
+ COMPONENT_MACRO_SELECT_THIRD_ARGUMENT_( \
+ COMPONENT_MACRO_CONDITIONAL_COMMA_(condition), consequent, alternate)
+
+// Expands to a comma (,) iff its first argument expands to |1|. Used in
+// conjunction with |COMPONENT_MACRO_SELECT_THIRD_ARGUMENT_()|, as the presence
+// or absense of an extra comma can be used to conditionally shift subsequent
+// argument positions and thus influence which argument is selected.
+#define COMPONENT_MACRO_CONDITIONAL_COMMA_(...) \
+ COMPONENT_MACRO_CONDITIONAL_COMMA_IMPL_(__VA_ARGS__,)
+#define COMPONENT_MACRO_CONDITIONAL_COMMA_IMPL_(x, ...) \
+ COMPONENT_MACRO_CONDITIONAL_COMMA_##x##_
+#define COMPONENT_MACRO_CONDITIONAL_COMMA_1_ ,
+
+// Helper which simply selects its third argument. Used in conjunction with
+// |COMPONENT_MACRO_CONDITIONAL_COMMA_()| above to implement conditional macro
+// expansion.
+#define COMPONENT_MACRO_SELECT_THIRD_ARGUMENT_(...) \
+ COMPONENT_MACRO_EXPAND_( \
+ COMPONENT_MACRO_SELECT_THIRD_ARGUMENT_IMPL_(__VA_ARGS__))
+#define COMPONENT_MACRO_SELECT_THIRD_ARGUMENT_IMPL_(a, b, c, ...) c
+
+// Helper to work around MSVC quirkiness wherein a macro expansion like |,|
+// within a parameter list will be treated as a single macro argument. This is
+// needed to ensure that |COMPONENT_MACRO_CONDITIONAL_COMMA_()| above can expand
+// to multiple separate positional arguments in the affirmative case, thus
+// elliciting the desired conditional behavior with
+// |COMPONENT_MACRO_SELECT_THIRD_ARGUMENT_()|.
+#define COMPONENT_MACRO_EXPAND_(x) x
+
+#endif // BASE_COMPONENT_EXPORT_H_
diff --git a/base/component_export_unittest.cc b/base/component_export_unittest.cc
new file mode 100644
index 0000000000..e9943537c1
--- /dev/null
+++ b/base/component_export_unittest.cc
@@ -0,0 +1,82 @@
+// 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.
+
+#include "base/component_export.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+using ComponentExportTest = testing::Test;
+
+#define IS_TEST_COMPONENT_A_IMPL 1
+#define IS_TEST_COMPONENT_B_IMPL
+#define IS_TEST_COMPONENT_C_IMPL 0
+#define IS_TEST_COMPONENT_D_IMPL 2
+#define IS_TEST_COMPONENT_E_IMPL xyz
+
+TEST(ComponentExportTest, ImportExport) {
+ // Defined as 1. Treat as export.
+ EXPECT_EQ(1, INSIDE_COMPONENT_IMPL(TEST_COMPONENT_A));
+
+ // Defined, but empty. Treat as import.
+ EXPECT_EQ(0, INSIDE_COMPONENT_IMPL(TEST_COMPONENT_B));
+
+ // Defined, but 0. Treat as import.
+ EXPECT_EQ(0, INSIDE_COMPONENT_IMPL(TEST_COMPONENT_C));
+
+ // Defined, but some other arbitrary thing that isn't 1. Treat as import.
+ EXPECT_EQ(0, INSIDE_COMPONENT_IMPL(TEST_COMPONENT_D));
+ EXPECT_EQ(0, INSIDE_COMPONENT_IMPL(TEST_COMPONENT_E));
+
+ // Undefined. Treat as import.
+ EXPECT_EQ(0, INSIDE_COMPONENT_IMPL(TEST_COMPONENT_F));
+
+ // And just for good measure, ensure that the macros evaluate properly in the
+ // context of preprocessor #if blocks.
+#if INSIDE_COMPONENT_IMPL(TEST_COMPONENT_A)
+ EXPECT_TRUE(true);
+#else
+ EXPECT_TRUE(false);
+#endif
+
+#if !INSIDE_COMPONENT_IMPL(TEST_COMPONENT_B)
+ EXPECT_TRUE(true);
+#else
+ EXPECT_TRUE(false);
+#endif
+
+#if !INSIDE_COMPONENT_IMPL(TEST_COMPONENT_C)
+ EXPECT_TRUE(true);
+#else
+ EXPECT_TRUE(false);
+#endif
+
+#if !INSIDE_COMPONENT_IMPL(TEST_COMPONENT_D)
+ EXPECT_TRUE(true);
+#else
+ EXPECT_TRUE(false);
+#endif
+
+#if !INSIDE_COMPONENT_IMPL(TEST_COMPONENT_E)
+ EXPECT_TRUE(true);
+#else
+ EXPECT_TRUE(false);
+#endif
+
+#if !INSIDE_COMPONENT_IMPL(TEST_COMPONENT_F)
+ EXPECT_TRUE(true);
+#else
+ EXPECT_TRUE(false);
+#endif
+}
+
+#undef IS_TEST_COMPONENT_A_IMPL
+#undef IS_TEST_COMPONENT_B_IMPL
+#undef IS_TEST_COMPONENT_C_IMPL
+#undef IS_TEST_COMPONENT_D_IMPL
+#undef IS_TEST_COMPONENT_E_IMPL
+
+} // namespace
+} // namespace base
diff --git a/base/containers/README.md b/base/containers/README.md
new file mode 100644
index 0000000000..092a264c47
--- /dev/null
+++ b/base/containers/README.md
@@ -0,0 +1,295 @@
+# base/containers library
+
+## What goes here
+
+This directory contains some STL-like containers.
+
+Things should be moved here that are generally applicable across the code base.
+Don't add things here just because you need them in one place and think others
+may someday want something similar. You can put specialized containers in
+your component's directory and we can promote them here later if we feel there
+is broad applicability.
+
+### Design and naming
+
+Containers should adhere as closely to STL as possible. Functions and behaviors
+not present in STL should only be added when they are related to the specific
+data structure implemented by the container.
+
+For STL-like containers our policy is that they should use STL-like naming even
+when it may conflict with the style guide. So functions and class names should
+be lower case with underscores. Non-STL-like classes and functions should use
+Google naming. Be sure to use the base namespace.
+
+## Map and set selection
+
+### Usage advice
+
+ * Generally avoid **std::unordered\_set** and **std::unordered\_map**. In the
+ common case, query performance is unlikely to be sufficiently higher than
+ std::map to make a difference, insert performance is slightly worse, and
+ the memory overhead is high. This makes sense mostly for large tables where
+ you expect a lot of lookups.
+
+ * Most maps and sets in Chrome are small and contain objects that can be
+ moved efficiently. In this case, consider **base::flat\_map** and
+ **base::flat\_set**. You need to be aware of the maximum expected size of
+ the container since individual inserts and deletes are O(n), giving O(n^2)
+ construction time for the entire map. But because it avoids mallocs in most
+ cases, inserts are better or comparable to other containers even for
+ several dozen items, and efficiently-moved types are unlikely to have
+ performance problems for most cases until you have hundreds of items. If
+ your container can be constructed in one shot, the constructor from vector
+ gives O(n log n) construction times and it should be strictly better than
+ a std::map.
+
+ * **base::small\_map** has better runtime memory usage without the poor
+ mutation performance of large containers that base::flat\_map has. But this
+ advantage is partially offset by additional code size. Prefer in cases
+ where you make many objects so that the code/heap tradeoff is good.
+
+ * Use **std::map** and **std::set** if you can't decide. Even if they're not
+ great, they're unlikely to be bad or surprising.
+
+### Map and set details
+
+Sizes are on 64-bit platforms. Stable iterators aren't invalidated when the
+container is mutated.
+
+| Container | Empty size | Per-item overhead | Stable iterators? |
+|:---------------------------------------- |:--------------------- |:----------------- |:----------------- |
+| std::map, std::set | 16 bytes | 32 bytes | Yes |
+| std::unordered\_map, std::unordered\_set | 128 bytes | 16-24 bytes | No |
+| base::flat\_map and base::flat\_set | 24 bytes | 0 (see notes) | No |
+| base::small\_map | 24 bytes (see notes) | 32 bytes | No |
+
+**Takeaways:** std::unordered\_map and std::unordered\_map have high
+overhead for small container sizes, prefer these only for larger workloads.
+
+Code size comparisons for a block of code (see appendix) on Windows using
+strings as keys.
+
+| Container | Code size |
+|:------------------- |:---------- |
+| std::unordered\_map | 1646 bytes |
+| std::map | 1759 bytes |
+| base::flat\_map | 1872 bytes |
+| base::small\_map | 2410 bytes |
+
+**Takeaways:** base::small\_map generates more code because of the inlining of
+both brute-force and red-black tree searching. This makes it less attractive
+for random one-off uses. But if your code is called frequently, the runtime
+memory benefits will be more important. The code sizes of the other maps are
+close enough it's not worth worrying about.
+
+### std::map and std::set
+
+A red-black tree. Each inserted item requires the memory allocation of a node
+on the heap. Each node contains a left pointer, a right pointer, a parent
+pointer, and a "color" for the red-black tree (32-bytes per item on 64-bits).
+
+### std::unordered\_map and std::unordered\_set
+
+A hash table. Implemented on Windows as a std::vector + std::list and in libc++
+as the equivalent of a std::vector + a std::forward\_list. Both implementations
+allocate an 8-entry hash table (containing iterators into the list) on
+initialization, and grow to 64 entries once 8 items are inserted. Above 64
+items, the size doubles every time the load factor exceeds 1.
+
+The empty size is sizeof(std::unordered\_map) = 64 +
+the initial hash table size which is 8 pointers. The per-item overhead in the
+table above counts the list node (2 pointers on Windows, 1 pointer in libc++),
+plus amortizes the hash table assuming a 0.5 load factor on average.
+
+In a microbenchmark on Windows, inserts of 1M integers into a
+std::unordered\_set took 1.07x the time of std::set, and queries took 0.67x the
+time of std::set. For a typical 4-entry set (the statistical mode of map sizes
+in the browser), query performance is identical to std::set and base::flat\_set.
+On ARM, unordered\_set performance can be worse because integer division to
+compute the bucket is slow, and a few "less than" operations can be faster than
+computing a hash depending on the key type. The takeaway is that you should not
+default to using unordered maps because "they're faster."
+
+### base::flat\_map and base::flat\_set
+
+A sorted std::vector. Seached via binary search, inserts in the middle require
+moving elements to make room. Good cache locality. For large objects and large
+set sizes, std::vector's doubling-when-full strategy can waste memory.
+
+Supports efficient construction from a vector of items which avoids the O(n^2)
+insertion time of each element separately.
+
+The per-item overhead will depend on the underlying std::vector's reallocation
+strategy and the memory access pattern. Assuming items are being linearly added,
+one would expect it to be 3/4 full, so per-item overhead will be 0.25 *
+sizeof(T).
+
+
+flat\_set/flat\_map support a notion of transparent comparisons. Therefore you
+can, for example, lookup base::StringPiece in a set of std::strings without
+constructing a temporary std::string. This functionality is based on C++14
+extensions to std::set/std::map interface.
+
+You can find more information about transparent comparisons here:
+http://en.cppreference.com/w/cpp/utility/functional/less_void
+
+Example, smart pointer set:
+
+```cpp
+// Declare a type alias using base::UniquePtrComparator.
+template <typename T>
+using UniquePtrSet = base::flat_set<std::unique_ptr<T>,
+ base::UniquePtrComparator>;
+
+// ...
+// Collect data.
+std::vector<std::unique_ptr<int>> ptr_vec;
+ptr_vec.reserve(5);
+std::generate_n(std::back_inserter(ptr_vec), 5, []{
+ return std::make_unique<int>(0);
+});
+
+// Construct a set.
+UniquePtrSet<int> ptr_set(std::move(ptr_vec), base::KEEP_FIRST_OF_DUPES);
+
+// Use raw pointers to lookup keys.
+int* ptr = ptr_set.begin()->get();
+EXPECT_TRUE(ptr_set.find(ptr) == ptr_set.begin());
+```
+
+Example flat_map<std\::string, int>:
+
+```cpp
+base::flat_map<std::string, int> str_to_int({{"a", 1}, {"c", 2},{"b", 2}},
+ base::KEEP_FIRST_OF_DUPES);
+
+// Does not construct temporary strings.
+str_to_int.find("c")->second = 3;
+str_to_int.erase("c");
+EXPECT_EQ(str_to_int.end(), str_to_int.find("c")->second);
+
+// NOTE: This does construct a temporary string. This happens since if the
+// item is not in the container, then it needs to be constructed, which is
+// something that transparent comparators don't have to guarantee.
+str_to_int["c"] = 3;
+```
+
+### base::small\_map
+
+A small inline buffer that is brute-force searched that overflows into a full
+std::map or std::unordered\_map. This gives the memory benefit of
+base::flat\_map for small data sizes without the degenerate insertion
+performance for large container sizes.
+
+Since instantiations require both code for a std::map and a brute-force search
+of the inline container, plus a fancy iterator to cover both cases, code size
+is larger.
+
+The initial size in the above table is assuming a very small inline table. The
+actual size will be sizeof(int) + min(sizeof(std::map), sizeof(T) *
+inline\_size).
+
+# Deque
+
+### Usage advice
+
+Chromium code should always use `base::circular_deque` or `base::queue` in
+preference to `std::deque` or `std::queue` due to memory usage and platform
+variation.
+
+The `base::circular_deque` implementation (and the `base::queue` which uses it)
+provide performance consistent across platforms that better matches most
+programmer's expectations on performance (it doesn't waste as much space as
+libc++ and doesn't do as many heap allocations as MSVC). It also generates less
+code tham `std::queue`: using it across the code base saves several hundred
+kilobytes.
+
+Since `base::deque` does not have stable iterators and it will move the objects
+it contains, it may not be appropriate for all uses. If you need these,
+consider using a `std::list` which will provide constant time insert and erase.
+
+### std::deque and std::queue
+
+The implementation of `std::deque` varies considerably which makes it hard to
+reason about. All implementations use a sequence of data blocks referenced by
+an array of pointers. The standard guarantees random access, amortized
+constant operations at the ends, and linear mutations in the middle.
+
+In Microsoft's implementation, each block is the smaller of 16 bytes or the
+size of the contained element. This means in practice that every expansion of
+the deque of non-trivial classes requires a heap allocation. libc++ (on Android
+and Mac) uses 4K blocks which elimiates the problem of many heap allocations,
+but generally wastes a large amount of space (an Android analysis revealed more
+than 2.5MB wasted space from deque alone, resulting in some optimizations).
+libstdc++ uses an intermediate-size 512 byte buffer.
+
+Microsoft's implementation never shrinks the deque capacity, so the capacity
+will always be the maximum number of elements ever contained. libstdc++
+deallocates blocks as they are freed. libc++ keeps up to two empty blocks.
+
+### base::circular_deque and base::queue
+
+A deque implemented as a circular buffer in an array. The underlying array will
+grow like a `std::vector` while the beginning and end of the deque will move
+around. The items will wrap around the underlying buffer so the storage will
+not be contiguous, but fast random access iterators are still possible.
+
+When the underlying buffer is filled, it will be reallocated and the constents
+moved (like a `std::vector`). The underlying buffer will be shrunk if there is
+too much wasted space (_unlike_ a `std::vector`). As a result, iterators are
+not stable across mutations.
+
+# Stack
+
+`std::stack` is like `std::queue` in that it is a wrapper around an underlying
+container. The default container is `std::deque` so everything from the deque
+section applies.
+
+Chromium provides `base/containers/stack.h` which defines `base::stack` that
+should be used in preference to std::stack. This changes the underlying
+container to `base::circular_deque`. The result will be very similar to
+manually specifying a `std::vector` for the underlying implementation except
+that the storage will shrink when it gets too empty (vector will never
+reallocate to a smaller size).
+
+Watch out: with some stack usage patterns it's easy to depend on unstable
+behavior:
+
+```cpp
+base::stack<Foo> stack;
+for (...) {
+ Foo& current = stack.top();
+ DoStuff(); // May call stack.push(), say if writing a parser.
+ current.done = true; // Current may reference deleted item!
+}
+```
+
+## Appendix
+
+### Code for map code size comparison
+
+This just calls insert and query a number of times, with printfs that prevent
+things from being dead-code eliminated.
+
+```cpp
+TEST(Foo, Bar) {
+ base::small_map<std::map<std::string, Flubber>> foo;
+ foo.insert(std::make_pair("foo", Flubber(8, "bar")));
+ foo.insert(std::make_pair("bar", Flubber(8, "bar")));
+ foo.insert(std::make_pair("foo1", Flubber(8, "bar")));
+ foo.insert(std::make_pair("bar1", Flubber(8, "bar")));
+ foo.insert(std::make_pair("foo", Flubber(8, "bar")));
+ foo.insert(std::make_pair("bar", Flubber(8, "bar")));
+ auto found = foo.find("asdf");
+ printf("Found is %d\n", (int)(found == foo.end()));
+ found = foo.find("foo");
+ printf("Found is %d\n", (int)(found == foo.end()));
+ found = foo.find("bar");
+ printf("Found is %d\n", (int)(found == foo.end()));
+ found = foo.find("asdfhf");
+ printf("Found is %d\n", (int)(found == foo.end()));
+ found = foo.find("bar1");
+ printf("Found is %d\n", (int)(found == foo.end()));
+}
+```
+
diff --git a/base/containers/circular_deque.h b/base/containers/circular_deque.h
new file mode 100644
index 0000000000..bf42a95844
--- /dev/null
+++ b/base/containers/circular_deque.h
@@ -0,0 +1,1111 @@
+// 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_CONTAINERS_CIRCULAR_DEQUE_H_
+#define BASE_CONTAINERS_CIRCULAR_DEQUE_H_
+
+#include <algorithm>
+#include <cstddef>
+#include <iterator>
+#include <type_traits>
+#include <utility>
+
+#include "base/containers/vector_buffer.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/template_util.h"
+
+// base::circular_deque is similar to std::deque. Unlike std::deque, the
+// storage is provided in a flat circular buffer conceptually similar to a
+// vector. The beginning and end will wrap around as necessary so that
+// pushes and pops will be constant time as long as a capacity expansion is
+// not required.
+//
+// The API should be identical to std::deque with the following differences:
+//
+// - ITERATORS ARE NOT STABLE. Mutating the container will invalidate all
+// iterators.
+//
+// - Insertions may resize the vector and so are not constant time (std::deque
+// guarantees constant time for insertions at the ends).
+//
+// - Container-wide comparisons are not implemented. If you want to compare
+// two containers, use an algorithm so the expensive iteration is explicit.
+//
+// If you want a similar container with only a queue API, use base::queue in
+// base/containers/queue.h.
+//
+// Constructors:
+// circular_deque();
+// circular_deque(size_t count);
+// circular_deque(size_t count, const T& value);
+// circular_deque(InputIterator first, InputIterator last);
+// circular_deque(const circular_deque&);
+// circular_deque(circular_deque&&);
+// circular_deque(std::initializer_list<value_type>);
+//
+// Assignment functions:
+// circular_deque& operator=(const circular_deque&);
+// circular_deque& operator=(circular_deque&&);
+// circular_deque& operator=(std::initializer_list<T>);
+// void assign(size_t count, const T& value);
+// void assign(InputIterator first, InputIterator last);
+// void assign(std::initializer_list<T> value);
+//
+// Random accessors:
+// T& at(size_t);
+// const T& at(size_t) const;
+// T& operator[](size_t);
+// const T& operator[](size_t) const;
+//
+// End accessors:
+// T& front();
+// const T& front() const;
+// T& back();
+// const T& back() const;
+//
+// Iterator functions:
+// iterator begin();
+// const_iterator begin() const;
+// const_iterator cbegin() const;
+// iterator end();
+// const_iterator end() const;
+// const_iterator cend() const;
+// reverse_iterator rbegin();
+// const_reverse_iterator rbegin() const;
+// const_reverse_iterator crbegin() const;
+// reverse_iterator rend();
+// const_reverse_iterator rend() const;
+// const_reverse_iterator crend() const;
+//
+// Memory management:
+// void reserve(size_t); // SEE IMPLEMENTATION FOR SOME GOTCHAS.
+// size_t capacity() const;
+// void shrink_to_fit();
+//
+// Size management:
+// void clear();
+// bool empty() const;
+// size_t size() const;
+// void resize(size_t);
+// void resize(size_t count, const T& value);
+//
+// Positional insert and erase:
+// void insert(const_iterator pos, size_type count, const T& value);
+// void insert(const_iterator pos,
+// InputIterator first, InputIterator last);
+// iterator insert(const_iterator pos, const T& value);
+// iterator insert(const_iterator pos, T&& value);
+// iterator emplace(const_iterator pos, Args&&... args);
+// iterator erase(const_iterator pos);
+// iterator erase(const_iterator first, const_iterator last);
+//
+// End insert and erase:
+// void push_front(const T&);
+// void push_front(T&&);
+// void push_back(const T&);
+// void push_back(T&&);
+// T& emplace_front(Args&&...);
+// T& emplace_back(Args&&...);
+// void pop_front();
+// void pop_back();
+//
+// General:
+// void swap(circular_deque&);
+
+namespace base {
+
+template <class T>
+class circular_deque;
+
+namespace internal {
+
+// Start allocating nonempty buffers with this many entries. This is the
+// external capacity so the internal buffer will be one larger (= 4) which is
+// more even for the allocator. See the descriptions of internal vs. external
+// capacity on the comment above the buffer_ variable below.
+constexpr size_t kCircularBufferInitialCapacity = 3;
+
+template <typename T>
+class circular_deque_const_iterator {
+ public:
+ using difference_type = std::ptrdiff_t;
+ using value_type = T;
+ using pointer = const T*;
+ using reference = const T&;
+ using iterator_category = std::random_access_iterator_tag;
+
+ circular_deque_const_iterator() : parent_deque_(nullptr), index_(0) {
+#if DCHECK_IS_ON()
+ created_generation_ = 0;
+#endif // DCHECK_IS_ON()
+ }
+
+ // Dereferencing.
+ const T& operator*() const {
+ CheckUnstableUsage();
+ parent_deque_->CheckValidIndex(index_);
+ return parent_deque_->buffer_[index_];
+ }
+ const T* operator->() const {
+ CheckUnstableUsage();
+ parent_deque_->CheckValidIndex(index_);
+ return &parent_deque_->buffer_[index_];
+ }
+ const value_type& operator[](difference_type i) const { return *(*this + i); }
+
+ // Increment and decrement.
+ circular_deque_const_iterator& operator++() {
+ Increment();
+ return *this;
+ }
+ circular_deque_const_iterator operator++(int) {
+ circular_deque_const_iterator ret = *this;
+ Increment();
+ return ret;
+ }
+ circular_deque_const_iterator& operator--() {
+ Decrement();
+ return *this;
+ }
+ circular_deque_const_iterator operator--(int) {
+ circular_deque_const_iterator ret = *this;
+ Decrement();
+ return ret;
+ }
+
+ // Random access mutation.
+ friend circular_deque_const_iterator operator+(
+ const circular_deque_const_iterator& iter,
+ difference_type offset) {
+ circular_deque_const_iterator ret = iter;
+ ret.Add(offset);
+ return ret;
+ }
+ circular_deque_const_iterator& operator+=(difference_type offset) {
+ Add(offset);
+ return *this;
+ }
+ friend circular_deque_const_iterator operator-(
+ const circular_deque_const_iterator& iter,
+ difference_type offset) {
+ circular_deque_const_iterator ret = iter;
+ ret.Add(-offset);
+ return ret;
+ }
+ circular_deque_const_iterator& operator-=(difference_type offset) {
+ Add(-offset);
+ return *this;
+ }
+
+ friend std::ptrdiff_t operator-(const circular_deque_const_iterator& lhs,
+ const circular_deque_const_iterator& rhs) {
+ lhs.CheckComparable(rhs);
+ return lhs.OffsetFromBegin() - rhs.OffsetFromBegin();
+ }
+
+ // Comparisons.
+ friend bool operator==(const circular_deque_const_iterator& lhs,
+ const circular_deque_const_iterator& rhs) {
+ lhs.CheckComparable(rhs);
+ return lhs.index_ == rhs.index_;
+ }
+ friend bool operator!=(const circular_deque_const_iterator& lhs,
+ const circular_deque_const_iterator& rhs) {
+ return !(lhs == rhs);
+ }
+ friend bool operator<(const circular_deque_const_iterator& lhs,
+ const circular_deque_const_iterator& rhs) {
+ lhs.CheckComparable(rhs);
+ return lhs.OffsetFromBegin() < rhs.OffsetFromBegin();
+ }
+ friend bool operator<=(const circular_deque_const_iterator& lhs,
+ const circular_deque_const_iterator& rhs) {
+ return !(lhs > rhs);
+ }
+ friend bool operator>(const circular_deque_const_iterator& lhs,
+ const circular_deque_const_iterator& rhs) {
+ lhs.CheckComparable(rhs);
+ return lhs.OffsetFromBegin() > rhs.OffsetFromBegin();
+ }
+ friend bool operator>=(const circular_deque_const_iterator& lhs,
+ const circular_deque_const_iterator& rhs) {
+ return !(lhs < rhs);
+ }
+
+ protected:
+ friend class circular_deque<T>;
+
+ circular_deque_const_iterator(const circular_deque<T>* parent, size_t index)
+ : parent_deque_(parent), index_(index) {
+#if DCHECK_IS_ON()
+ created_generation_ = parent->generation_;
+#endif // DCHECK_IS_ON()
+ }
+
+ // Returns the offset from the beginning index of the buffer to the current
+ // item.
+ size_t OffsetFromBegin() const {
+ if (index_ >= parent_deque_->begin_)
+ return index_ - parent_deque_->begin_; // On the same side as begin.
+ return parent_deque_->buffer_.capacity() - parent_deque_->begin_ + index_;
+ }
+
+ // Most uses will be ++ and -- so use a simplified implementation.
+ void Increment() {
+ CheckUnstableUsage();
+ parent_deque_->CheckValidIndex(index_);
+ index_++;
+ if (index_ == parent_deque_->buffer_.capacity())
+ index_ = 0;
+ }
+ void Decrement() {
+ CheckUnstableUsage();
+ parent_deque_->CheckValidIndexOrEnd(index_);
+ if (index_ == 0)
+ index_ = parent_deque_->buffer_.capacity() - 1;
+ else
+ index_--;
+ }
+ void Add(difference_type delta) {
+ CheckUnstableUsage();
+#if DCHECK_IS_ON()
+ if (delta <= 0)
+ parent_deque_->CheckValidIndexOrEnd(index_);
+ else
+ parent_deque_->CheckValidIndex(index_);
+#endif
+ // It should be valid to add 0 to any iterator, even if the container is
+ // empty and the iterator points to end(). The modulo below will divide
+ // by 0 if the buffer capacity is empty, so it's important to check for
+ // this case explicitly.
+ if (delta == 0)
+ return;
+
+ difference_type new_offset = OffsetFromBegin() + delta;
+ DCHECK(new_offset >= 0 &&
+ new_offset <= static_cast<difference_type>(parent_deque_->size()));
+ index_ = (new_offset + parent_deque_->begin_) %
+ parent_deque_->buffer_.capacity();
+ }
+
+#if DCHECK_IS_ON()
+ void CheckUnstableUsage() const {
+ DCHECK(parent_deque_);
+ // Since circular_deque doesn't guarantee stability, any attempt to
+ // dereference this iterator after a mutation (i.e. the generation doesn't
+ // match the original) in the container is illegal.
+ DCHECK_EQ(created_generation_, parent_deque_->generation_)
+ << "circular_deque iterator dereferenced after mutation.";
+ }
+ void CheckComparable(const circular_deque_const_iterator& other) const {
+ DCHECK_EQ(parent_deque_, other.parent_deque_);
+ // Since circular_deque doesn't guarantee stability, two iterators that
+ // are compared must have been generated without mutating the container.
+ // If this fires, the container was mutated between generating the two
+ // iterators being compared.
+ DCHECK_EQ(created_generation_, other.created_generation_);
+ }
+#else
+ inline void CheckUnstableUsage() const {}
+ inline void CheckComparable(const circular_deque_const_iterator&) const {}
+#endif // DCHECK_IS_ON()
+
+ const circular_deque<T>* parent_deque_;
+ size_t index_;
+
+#if DCHECK_IS_ON()
+ // The generation of the parent deque when this iterator was created. The
+ // container will update the generation for every modification so we can
+ // test if the container was modified by comparing them.
+ uint64_t created_generation_;
+#endif // DCHECK_IS_ON()
+};
+
+template <typename T>
+class circular_deque_iterator : public circular_deque_const_iterator<T> {
+ using base = circular_deque_const_iterator<T>;
+
+ public:
+ friend class circular_deque<T>;
+
+ using difference_type = std::ptrdiff_t;
+ using value_type = T;
+ using pointer = T*;
+ using reference = T&;
+ using iterator_category = std::random_access_iterator_tag;
+
+ // Expose the base class' constructor.
+ circular_deque_iterator() : circular_deque_const_iterator<T>() {}
+
+ // Dereferencing.
+ T& operator*() const { return const_cast<T&>(base::operator*()); }
+ T* operator->() const { return const_cast<T*>(base::operator->()); }
+ T& operator[](difference_type i) {
+ return const_cast<T&>(base::operator[](i));
+ }
+
+ // Random access mutation.
+ friend circular_deque_iterator operator+(const circular_deque_iterator& iter,
+ difference_type offset) {
+ circular_deque_iterator ret = iter;
+ ret.Add(offset);
+ return ret;
+ }
+ circular_deque_iterator& operator+=(difference_type offset) {
+ base::Add(offset);
+ return *this;
+ }
+ friend circular_deque_iterator operator-(const circular_deque_iterator& iter,
+ difference_type offset) {
+ circular_deque_iterator ret = iter;
+ ret.Add(-offset);
+ return ret;
+ }
+ circular_deque_iterator& operator-=(difference_type offset) {
+ base::Add(-offset);
+ return *this;
+ }
+
+ // Increment and decrement.
+ circular_deque_iterator& operator++() {
+ base::Increment();
+ return *this;
+ }
+ circular_deque_iterator operator++(int) {
+ circular_deque_iterator ret = *this;
+ base::Increment();
+ return ret;
+ }
+ circular_deque_iterator& operator--() {
+ base::Decrement();
+ return *this;
+ }
+ circular_deque_iterator operator--(int) {
+ circular_deque_iterator ret = *this;
+ base::Decrement();
+ return ret;
+ }
+
+ private:
+ circular_deque_iterator(const circular_deque<T>* parent, size_t index)
+ : circular_deque_const_iterator<T>(parent, index) {}
+};
+
+} // namespace internal
+
+template <typename T>
+class circular_deque {
+ private:
+ using VectorBuffer = internal::VectorBuffer<T>;
+
+ public:
+ using value_type = T;
+ using size_type = std::size_t;
+ using difference_type = std::ptrdiff_t;
+ using reference = value_type&;
+ using const_reference = const value_type&;
+ using pointer = value_type*;
+ using const_pointer = const value_type*;
+
+ using iterator = internal::circular_deque_iterator<T>;
+ using const_iterator = internal::circular_deque_const_iterator<T>;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ // ---------------------------------------------------------------------------
+ // Constructor
+
+ constexpr circular_deque() = default;
+
+ // Constructs with |count| copies of |value| or default constructed version.
+ circular_deque(size_type count) { resize(count); }
+ circular_deque(size_type count, const T& value) { resize(count, value); }
+
+ // Range constructor.
+ template <class InputIterator>
+ circular_deque(InputIterator first, InputIterator last) {
+ assign(first, last);
+ }
+
+ // Copy/move.
+ circular_deque(const circular_deque& other) : buffer_(other.size() + 1) {
+ assign(other.begin(), other.end());
+ }
+ circular_deque(circular_deque&& other) noexcept
+ : buffer_(std::move(other.buffer_)),
+ begin_(other.begin_),
+ end_(other.end_) {
+ other.begin_ = 0;
+ other.end_ = 0;
+ }
+
+ circular_deque(std::initializer_list<value_type> init) { assign(init); }
+
+ ~circular_deque() { DestructRange(begin_, end_); }
+
+ // ---------------------------------------------------------------------------
+ // Assignments.
+ //
+ // All of these may invalidate iterators and references.
+
+ circular_deque& operator=(const circular_deque& other) {
+ if (&other == this)
+ return *this;
+
+ reserve(other.size());
+ assign(other.begin(), other.end());
+ return *this;
+ }
+ circular_deque& operator=(circular_deque&& other) noexcept {
+ if (&other == this)
+ return *this;
+
+ // We're about to overwrite the buffer, so don't free it in clear to
+ // avoid doing it twice.
+ ClearRetainCapacity();
+ buffer_ = std::move(other.buffer_);
+ begin_ = other.begin_;
+ end_ = other.end_;
+
+ other.begin_ = 0;
+ other.end_ = 0;
+
+ IncrementGeneration();
+ return *this;
+ }
+ circular_deque& operator=(std::initializer_list<value_type> ilist) {
+ reserve(ilist.size());
+ assign(std::begin(ilist), std::end(ilist));
+ return *this;
+ }
+
+ void assign(size_type count, const value_type& value) {
+ ClearRetainCapacity();
+ reserve(count);
+ for (size_t i = 0; i < count; i++)
+ emplace_back(value);
+ IncrementGeneration();
+ }
+
+ // This variant should be enabled only when InputIterator is an iterator.
+ template <typename InputIterator>
+ typename std::enable_if<::base::internal::is_iterator<InputIterator>::value,
+ void>::type
+ assign(InputIterator first, InputIterator last) {
+ // Possible future enhancement, dispatch on iterator tag type. For forward
+ // iterators we can use std::difference to preallocate the space required
+ // and only do one copy.
+ ClearRetainCapacity();
+ for (; first != last; ++first)
+ emplace_back(*first);
+ IncrementGeneration();
+ }
+
+ void assign(std::initializer_list<value_type> value) {
+ reserve(std::distance(value.begin(), value.end()));
+ assign(value.begin(), value.end());
+ }
+
+ // ---------------------------------------------------------------------------
+ // Accessors.
+ //
+ // Since this class assumes no exceptions, at() and operator[] are equivalent.
+
+ const value_type& at(size_type i) const {
+ DCHECK(i < size());
+ size_t right_size = buffer_.capacity() - begin_;
+ if (begin_ <= end_ || i < right_size)
+ return buffer_[begin_ + i];
+ return buffer_[i - right_size];
+ }
+ value_type& at(size_type i) {
+ return const_cast<value_type&>(
+ const_cast<const circular_deque*>(this)->at(i));
+ }
+
+ value_type& operator[](size_type i) { return at(i); }
+ const value_type& operator[](size_type i) const {
+ return const_cast<circular_deque*>(this)->at(i);
+ }
+
+ value_type& front() {
+ DCHECK(!empty());
+ return buffer_[begin_];
+ }
+ const value_type& front() const {
+ DCHECK(!empty());
+ return buffer_[begin_];
+ }
+
+ value_type& back() {
+ DCHECK(!empty());
+ return *(--end());
+ }
+ const value_type& back() const {
+ DCHECK(!empty());
+ return *(--end());
+ }
+
+ // ---------------------------------------------------------------------------
+ // Iterators.
+
+ iterator begin() { return iterator(this, begin_); }
+ const_iterator begin() const { return const_iterator(this, begin_); }
+ const_iterator cbegin() const { return const_iterator(this, begin_); }
+
+ iterator end() { return iterator(this, end_); }
+ const_iterator end() const { return const_iterator(this, end_); }
+ const_iterator cend() const { return const_iterator(this, end_); }
+
+ reverse_iterator rbegin() { return reverse_iterator(end()); }
+ const_reverse_iterator rbegin() const {
+ return const_reverse_iterator(end());
+ }
+ const_reverse_iterator crbegin() const { return rbegin(); }
+
+ reverse_iterator rend() { return reverse_iterator(begin()); }
+ const_reverse_iterator rend() const {
+ return const_reverse_iterator(begin());
+ }
+ const_reverse_iterator crend() const { return rend(); }
+
+ // ---------------------------------------------------------------------------
+ // Memory management.
+
+ // IMPORTANT NOTE ON reserve(...): This class implements auto-shrinking of
+ // the buffer when elements are deleted and there is "too much" wasted space.
+ // So if you call reserve() with a large size in anticipation of pushing many
+ // elements, but pop an element before the queue is full, the capacity you
+ // reserved may be lost.
+ //
+ // As a result, it's only worthwhile to call reserve() when you're adding
+ // many things at once with no intermediate operations.
+ void reserve(size_type new_capacity) {
+ if (new_capacity > capacity())
+ SetCapacityTo(new_capacity);
+ }
+
+ size_type capacity() const {
+ // One item is wasted to indicate end().
+ return buffer_.capacity() == 0 ? 0 : buffer_.capacity() - 1;
+ }
+
+ void shrink_to_fit() {
+ if (empty()) {
+ // Optimize empty case to really delete everything if there was
+ // something.
+ if (buffer_.capacity())
+ buffer_ = VectorBuffer();
+ } else {
+ SetCapacityTo(size());
+ }
+ }
+
+ // ---------------------------------------------------------------------------
+ // Size management.
+
+ // This will additionally reset the capacity() to 0.
+ void clear() {
+ // This can't resize(0) because that requires a default constructor to
+ // compile, which not all contained classes may implement.
+ ClearRetainCapacity();
+ buffer_ = VectorBuffer();
+ }
+
+ bool empty() const { return begin_ == end_; }
+
+ size_type size() const {
+ if (begin_ <= end_)
+ return end_ - begin_;
+ return buffer_.capacity() - begin_ + end_;
+ }
+
+ // When reducing size, the elements are deleted from the end. When expanding
+ // size, elements are added to the end with |value| or the default
+ // constructed version. Even when using resize(count) to shrink, a default
+ // constructor is required for the code to compile, even though it will not
+ // be called.
+ //
+ // There are two versions rather than using a default value to avoid
+ // creating a temporary when shrinking (when it's not needed). Plus if
+ // the default constructor is desired when expanding usually just calling it
+ // for each element is faster than making a default-constructed temporary and
+ // copying it.
+ void resize(size_type count) {
+ // SEE BELOW VERSION if you change this. The code is mostly the same.
+ if (count > size()) {
+ // This could be slighly more efficient but expanding a queue with
+ // identical elements is unusual and the extra computations of emplacing
+ // one-by-one will typically be small relative to calling the constructor
+ // for every item.
+ ExpandCapacityIfNecessary(count - size());
+ while (size() < count)
+ emplace_back();
+ } else if (count < size()) {
+ size_t new_end = (begin_ + count) % buffer_.capacity();
+ DestructRange(new_end, end_);
+ end_ = new_end;
+
+ ShrinkCapacityIfNecessary();
+ }
+ IncrementGeneration();
+ }
+ void resize(size_type count, const value_type& value) {
+ // SEE ABOVE VERSION if you change this. The code is mostly the same.
+ if (count > size()) {
+ ExpandCapacityIfNecessary(count - size());
+ while (size() < count)
+ emplace_back(value);
+ } else if (count < size()) {
+ size_t new_end = (begin_ + count) % buffer_.capacity();
+ DestructRange(new_end, end_);
+ end_ = new_end;
+
+ ShrinkCapacityIfNecessary();
+ }
+ IncrementGeneration();
+ }
+
+ // ---------------------------------------------------------------------------
+ // Insert and erase.
+ //
+ // Insertion and deletion in the middle is O(n) and invalidates all existing
+ // iterators.
+ //
+ // The implementation of insert isn't optimized as much as it could be. If
+ // the insertion requires that the buffer be grown, it will first be grown
+ // and everything moved, and then the items will be inserted, potentially
+ // moving some items twice. This simplifies the implemntation substantially
+ // and means less generated templatized code. Since this is an uncommon
+ // operation for deques, and already relatively slow, it doesn't seem worth
+ // the benefit to optimize this.
+
+ void insert(const_iterator pos, size_type count, const T& value) {
+ ValidateIterator(pos);
+
+ // Optimize insert at the beginning.
+ if (pos == begin()) {
+ ExpandCapacityIfNecessary(count);
+ for (size_t i = 0; i < count; i++)
+ push_front(value);
+ return;
+ }
+
+ iterator insert_cur(this, pos.index_);
+ iterator insert_end;
+ MakeRoomFor(count, &insert_cur, &insert_end);
+ while (insert_cur < insert_end) {
+ new (&buffer_[insert_cur.index_]) T(value);
+ ++insert_cur;
+ }
+
+ IncrementGeneration();
+ }
+
+ // This enable_if keeps this call from getting confused with the (pos, count,
+ // value) version when value is an integer.
+ template <class InputIterator>
+ typename std::enable_if<::base::internal::is_iterator<InputIterator>::value,
+ void>::type
+ insert(const_iterator pos, InputIterator first, InputIterator last) {
+ ValidateIterator(pos);
+
+ size_t inserted_items = std::distance(first, last);
+ if (inserted_items == 0)
+ return; // Can divide by 0 when doing modulo below, so return early.
+
+ // Make a hole to copy the items into.
+ iterator insert_cur;
+ iterator insert_end;
+ if (pos == begin()) {
+ // Optimize insert at the beginning, nothing needs to be shifted and the
+ // hole is the |inserted_items| block immediately before |begin_|.
+ ExpandCapacityIfNecessary(inserted_items);
+ insert_end = begin();
+ begin_ =
+ (begin_ + buffer_.capacity() - inserted_items) % buffer_.capacity();
+ insert_cur = begin();
+ } else {
+ insert_cur = iterator(this, pos.index_);
+ MakeRoomFor(inserted_items, &insert_cur, &insert_end);
+ }
+
+ // Copy the items.
+ while (insert_cur < insert_end) {
+ new (&buffer_[insert_cur.index_]) T(*first);
+ ++insert_cur;
+ ++first;
+ }
+
+ IncrementGeneration();
+ }
+
+ // These all return an iterator to the inserted item. Existing iterators will
+ // be invalidated.
+ iterator insert(const_iterator pos, const T& value) {
+ return emplace(pos, value);
+ }
+ iterator insert(const_iterator pos, T&& value) {
+ return emplace(pos, std::move(value));
+ }
+ template <class... Args>
+ iterator emplace(const_iterator pos, Args&&... args) {
+ ValidateIterator(pos);
+
+ // Optimize insert at beginning which doesn't require shifting.
+ if (pos == cbegin()) {
+ emplace_front(std::forward<Args>(args)...);
+ return begin();
+ }
+
+ // Do this before we make the new iterators we return.
+ IncrementGeneration();
+
+ iterator insert_begin(this, pos.index_);
+ iterator insert_end;
+ MakeRoomFor(1, &insert_begin, &insert_end);
+ new (&buffer_[insert_begin.index_]) T(std::forward<Args>(args)...);
+
+ return insert_begin;
+ }
+
+ // Calling erase() won't automatically resize the buffer smaller like resize
+ // or the pop functions. Erase is slow and relatively uncommon, and for
+ // normal deque usage a pop will normally be done on a regular basis that
+ // will prevent excessive buffer usage over long periods of time. It's not
+ // worth having the extra code for every template instantiation of erase()
+ // to resize capacity downward to a new buffer.
+ iterator erase(const_iterator pos) { return erase(pos, pos + 1); }
+ iterator erase(const_iterator first, const_iterator last) {
+ ValidateIterator(first);
+ ValidateIterator(last);
+
+ IncrementGeneration();
+
+ // First, call the destructor on the deleted items.
+ if (first.index_ == last.index_) {
+ // Nothing deleted. Need to return early to avoid falling through to
+ // moving items on top of themselves.
+ return iterator(this, first.index_);
+ } else if (first.index_ < last.index_) {
+ // Contiguous range.
+ buffer_.DestructRange(&buffer_[first.index_], &buffer_[last.index_]);
+ } else {
+ // Deleted range wraps around.
+ buffer_.DestructRange(&buffer_[first.index_],
+ &buffer_[buffer_.capacity()]);
+ buffer_.DestructRange(&buffer_[0], &buffer_[last.index_]);
+ }
+
+ if (first.index_ == begin_) {
+ // This deletion is from the beginning. Nothing needs to be copied, only
+ // begin_ needs to be updated.
+ begin_ = last.index_;
+ return iterator(this, last.index_);
+ }
+
+ // In an erase operation, the shifted items all move logically to the left,
+ // so move them from left-to-right.
+ iterator move_src(this, last.index_);
+ iterator move_src_end = end();
+ iterator move_dest(this, first.index_);
+ for (; move_src < move_src_end; move_src++, move_dest++) {
+ buffer_.MoveRange(&buffer_[move_src.index_],
+ &buffer_[move_src.index_ + 1],
+ &buffer_[move_dest.index_]);
+ }
+
+ end_ = move_dest.index_;
+
+ // Since we did not reallocate and only changed things after the erase
+ // element(s), the input iterator's index points to the thing following the
+ // deletion.
+ return iterator(this, first.index_);
+ }
+
+ // ---------------------------------------------------------------------------
+ // Begin/end operations.
+
+ void push_front(const T& value) { emplace_front(value); }
+ void push_front(T&& value) { emplace_front(std::move(value)); }
+
+ void push_back(const T& value) { emplace_back(value); }
+ void push_back(T&& value) { emplace_back(std::move(value)); }
+
+ template <class... Args>
+ reference emplace_front(Args&&... args) {
+ ExpandCapacityIfNecessary(1);
+ if (begin_ == 0)
+ begin_ = buffer_.capacity() - 1;
+ else
+ begin_--;
+ IncrementGeneration();
+ new (&buffer_[begin_]) T(std::forward<Args>(args)...);
+ return front();
+ }
+
+ template <class... Args>
+ reference emplace_back(Args&&... args) {
+ ExpandCapacityIfNecessary(1);
+ new (&buffer_[end_]) T(std::forward<Args>(args)...);
+ if (end_ == buffer_.capacity() - 1)
+ end_ = 0;
+ else
+ end_++;
+ IncrementGeneration();
+ return back();
+ }
+
+ void pop_front() {
+ DCHECK(size());
+ buffer_.DestructRange(&buffer_[begin_], &buffer_[begin_ + 1]);
+ begin_++;
+ if (begin_ == buffer_.capacity())
+ begin_ = 0;
+
+ ShrinkCapacityIfNecessary();
+
+ // Technically popping will not invalidate any iterators since the
+ // underlying buffer will be stable. But in the future we may want to add a
+ // feature that resizes the buffer smaller if there is too much wasted
+ // space. This ensures we can make such a change safely.
+ IncrementGeneration();
+ }
+ void pop_back() {
+ DCHECK(size());
+ if (end_ == 0)
+ end_ = buffer_.capacity() - 1;
+ else
+ end_--;
+ buffer_.DestructRange(&buffer_[end_], &buffer_[end_ + 1]);
+
+ ShrinkCapacityIfNecessary();
+
+ // See pop_front comment about why this is here.
+ IncrementGeneration();
+ }
+
+ // ---------------------------------------------------------------------------
+ // General operations.
+
+ void swap(circular_deque& other) {
+ std::swap(buffer_, other.buffer_);
+ std::swap(begin_, other.begin_);
+ std::swap(end_, other.end_);
+ IncrementGeneration();
+ }
+
+ friend void swap(circular_deque& lhs, circular_deque& rhs) { lhs.swap(rhs); }
+
+ private:
+ friend internal::circular_deque_iterator<T>;
+ friend internal::circular_deque_const_iterator<T>;
+
+ // Moves the items in the given circular buffer to the current one. The
+ // source is moved from so will become invalid. The destination buffer must
+ // have already been allocated with enough size.
+ static void MoveBuffer(VectorBuffer& from_buf,
+ size_t from_begin,
+ size_t from_end,
+ VectorBuffer* to_buf,
+ size_t* to_begin,
+ size_t* to_end) {
+ size_t from_capacity = from_buf.capacity();
+
+ *to_begin = 0;
+ if (from_begin < from_end) {
+ // Contiguous.
+ from_buf.MoveRange(&from_buf[from_begin], &from_buf[from_end],
+ to_buf->begin());
+ *to_end = from_end - from_begin;
+ } else if (from_begin > from_end) {
+ // Discontiguous, copy the right side to the beginning of the new buffer.
+ from_buf.MoveRange(&from_buf[from_begin], &from_buf[from_capacity],
+ to_buf->begin());
+ size_t right_size = from_capacity - from_begin;
+ // Append the left side.
+ from_buf.MoveRange(&from_buf[0], &from_buf[from_end],
+ &(*to_buf)[right_size]);
+ *to_end = right_size + from_end;
+ } else {
+ // No items.
+ *to_end = 0;
+ }
+ }
+
+ // Expands the buffer size. This assumes the size is larger than the
+ // number of elements in the vector (it won't call delete on anything).
+ void SetCapacityTo(size_t new_capacity) {
+ // Use the capacity + 1 as the internal buffer size to differentiate
+ // empty and full (see definition of buffer_ below).
+ VectorBuffer new_buffer(new_capacity + 1);
+ MoveBuffer(buffer_, begin_, end_, &new_buffer, &begin_, &end_);
+ buffer_ = std::move(new_buffer);
+ }
+ void ExpandCapacityIfNecessary(size_t additional_elts) {
+ size_t min_new_capacity = size() + additional_elts;
+ if (capacity() >= min_new_capacity)
+ return; // Already enough room.
+
+ min_new_capacity =
+ std::max(min_new_capacity, internal::kCircularBufferInitialCapacity);
+
+ // std::vector always grows by at least 50%. WTF::Deque grows by at least
+ // 25%. We expect queue workloads to generally stay at a similar size and
+ // grow less than a vector might, so use 25%.
+ size_t new_capacity =
+ std::max(min_new_capacity, capacity() + capacity() / 4);
+ SetCapacityTo(new_capacity);
+ }
+
+ void ShrinkCapacityIfNecessary() {
+ // Don't auto-shrink below this size.
+ if (capacity() <= internal::kCircularBufferInitialCapacity)
+ return;
+
+ // Shrink when 100% of the size() is wasted.
+ size_t sz = size();
+ size_t empty_spaces = capacity() - sz;
+ if (empty_spaces < sz)
+ return;
+
+ // Leave 1/4 the size as free capacity, not going below the initial
+ // capacity.
+ size_t new_capacity =
+ std::max(internal::kCircularBufferInitialCapacity, sz + sz / 4);
+ if (new_capacity < capacity()) {
+ // Count extra item to convert to internal capacity.
+ SetCapacityTo(new_capacity);
+ }
+ }
+
+ // Backend for clear() but does not resize the internal buffer.
+ void ClearRetainCapacity() {
+ // This can't resize(0) because that requires a default constructor to
+ // compile, which not all contained classes may implement.
+ DestructRange(begin_, end_);
+ begin_ = 0;
+ end_ = 0;
+ IncrementGeneration();
+ }
+
+ // Calls destructors for the given begin->end indices. The indices may wrap
+ // around. The buffer is not resized, and the begin_ and end_ members are
+ // not changed.
+ void DestructRange(size_t begin, size_t end) {
+ if (end == begin) {
+ return;
+ } else if (end > begin) {
+ buffer_.DestructRange(&buffer_[begin], &buffer_[end]);
+ } else {
+ buffer_.DestructRange(&buffer_[begin], &buffer_[buffer_.capacity()]);
+ buffer_.DestructRange(&buffer_[0], &buffer_[end]);
+ }
+ }
+
+ // Makes room for |count| items starting at |*insert_begin|. Since iterators
+ // are not stable across buffer resizes, |*insert_begin| will be updated to
+ // point to the beginning of the newly opened position in the new array (it's
+ // in/out), and the end of the newly opened position (it's out-only).
+ void MakeRoomFor(size_t count, iterator* insert_begin, iterator* insert_end) {
+ if (count == 0) {
+ *insert_end = *insert_begin;
+ return;
+ }
+
+ // The offset from the beginning will be stable across reallocations.
+ size_t begin_offset = insert_begin->OffsetFromBegin();
+ ExpandCapacityIfNecessary(count);
+
+ insert_begin->index_ = (begin_ + begin_offset) % buffer_.capacity();
+ *insert_end =
+ iterator(this, (insert_begin->index_ + count) % buffer_.capacity());
+
+ // Update the new end and prepare the iterators for copying.
+ iterator src = end();
+ end_ = (end_ + count) % buffer_.capacity();
+ iterator dest = end();
+
+ // Move the elements. This will always involve shifting logically to the
+ // right, so move in a right-to-left order.
+ while (true) {
+ if (src == *insert_begin)
+ break;
+ --src;
+ --dest;
+ buffer_.MoveRange(&buffer_[src.index_], &buffer_[src.index_ + 1],
+ &buffer_[dest.index_]);
+ }
+ }
+
+#if DCHECK_IS_ON()
+ // Asserts the given index is dereferencable. The index is an index into the
+ // buffer, not an index used by operator[] or at() which will be offsets from
+ // begin.
+ void CheckValidIndex(size_t i) const {
+ if (begin_ <= end_)
+ DCHECK(i >= begin_ && i < end_);
+ else
+ DCHECK((i >= begin_ && i < buffer_.capacity()) || i < end_);
+ }
+
+ // Asserts the given index is either dereferencable or points to end().
+ void CheckValidIndexOrEnd(size_t i) const {
+ if (i != end_)
+ CheckValidIndex(i);
+ }
+
+ void ValidateIterator(const const_iterator& i) const {
+ DCHECK(i.parent_deque_ == this);
+ i.CheckUnstableUsage();
+ }
+
+ // See generation_ below.
+ void IncrementGeneration() { generation_++; }
+#else
+ // No-op versions of these functions for release builds.
+ void CheckValidIndex(size_t) const {}
+ void CheckValidIndexOrEnd(size_t) const {}
+ void ValidateIterator(const const_iterator& i) const {}
+ void IncrementGeneration() {}
+#endif
+
+ // Danger, the buffer_.capacity() is the "internal capacity" which is
+ // capacity() + 1 since there is an extra item to indicate the end. Otherwise
+ // being completely empty and completely full are indistinguishable (begin ==
+ // end). We could add a separate flag to avoid it, but that adds significant
+ // extra complexity since every computation will have to check for it. Always
+ // keeping one extra unused element in the buffer makes iterator computations
+ // much simpler.
+ //
+ // Container internal code will want to use buffer_.capacity() for offset
+ // computations rather than capacity().
+ VectorBuffer buffer_;
+ size_type begin_ = 0;
+ size_type end_ = 0;
+
+#if DCHECK_IS_ON()
+ // Incremented every time a modification is made that could affect iterator
+ // invalidations.
+ uint64_t generation_ = 0;
+#endif
+};
+
+// Implementations of base::Erase[If] (see base/stl_util.h).
+template <class T, class Value>
+void Erase(circular_deque<T>& container, const Value& value) {
+ container.erase(std::remove(container.begin(), container.end(), value),
+ container.end());
+}
+
+template <class T, class Predicate>
+void EraseIf(circular_deque<T>& container, Predicate pred) {
+ container.erase(std::remove_if(container.begin(), container.end(), pred),
+ container.end());
+}
+
+} // namespace base
+
+#endif // BASE_CONTAINERS_CIRCULAR_DEQUE_H_
diff --git a/base/containers/circular_deque_unittest.cc b/base/containers/circular_deque_unittest.cc
new file mode 100644
index 0000000000..0c168e0c87
--- /dev/null
+++ b/base/containers/circular_deque_unittest.cc
@@ -0,0 +1,881 @@
+// 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/containers/circular_deque.h"
+
+#include "base/test/copy_only_int.h"
+#include "base/test/move_only_int.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::internal::VectorBuffer;
+
+namespace base {
+
+namespace {
+
+circular_deque<int> MakeSequence(size_t max) {
+ circular_deque<int> ret;
+ for (size_t i = 0; i < max; i++)
+ ret.push_back(i);
+ return ret;
+}
+
+// Cycles through the queue, popping items from the back and pushing items
+// at the front to validate behavior across different configurations of the
+// queue in relation to the underlying buffer. The tester closure is run for
+// each cycle.
+template <class QueueT, class Tester>
+void CycleTest(circular_deque<QueueT>& queue, const Tester& tester) {
+ size_t steps = queue.size() * 2;
+ for (size_t i = 0; i < steps; i++) {
+ tester(queue, i);
+ queue.pop_back();
+ queue.push_front(QueueT());
+ }
+}
+
+class DestructorCounter {
+ public:
+ DestructorCounter(int* counter) : counter_(counter) {}
+ ~DestructorCounter() { ++(*counter_); }
+
+ private:
+ int* counter_;
+};
+
+} // namespace
+
+TEST(CircularDeque, FillConstructor) {
+ constexpr size_t num_elts = 9;
+
+ std::vector<int> foo(15);
+ EXPECT_EQ(15u, foo.size());
+
+ // Fill with default constructor.
+ {
+ circular_deque<int> buf(num_elts);
+
+ EXPECT_EQ(num_elts, buf.size());
+ EXPECT_EQ(num_elts, static_cast<size_t>(buf.end() - buf.begin()));
+
+ for (size_t i = 0; i < num_elts; i++)
+ EXPECT_EQ(0, buf[i]);
+ }
+
+ // Fill with explicit value.
+ {
+ int value = 199;
+ circular_deque<int> buf(num_elts, value);
+
+ EXPECT_EQ(num_elts, buf.size());
+ EXPECT_EQ(num_elts, static_cast<size_t>(buf.end() - buf.begin()));
+
+ for (size_t i = 0; i < num_elts; i++)
+ EXPECT_EQ(value, buf[i]);
+ }
+}
+
+TEST(CircularDeque, CopyAndRangeConstructor) {
+ int values[] = {1, 2, 3, 4, 5, 6};
+ circular_deque<CopyOnlyInt> first(std::begin(values), std::end(values));
+
+ circular_deque<CopyOnlyInt> second(first);
+ EXPECT_EQ(6u, second.size());
+ for (int i = 0; i < 6; i++)
+ EXPECT_EQ(i + 1, second[i].data());
+}
+
+TEST(CircularDeque, MoveConstructor) {
+ int values[] = {1, 2, 3, 4, 5, 6};
+ circular_deque<MoveOnlyInt> first(std::begin(values), std::end(values));
+
+ circular_deque<MoveOnlyInt> second(std::move(first));
+ EXPECT_TRUE(first.empty());
+ EXPECT_EQ(6u, second.size());
+ for (int i = 0; i < 6; i++)
+ EXPECT_EQ(i + 1, second[i].data());
+}
+
+TEST(CircularDeque, InitializerListConstructor) {
+ circular_deque<int> empty({});
+ ASSERT_TRUE(empty.empty());
+
+ circular_deque<int> first({1, 2, 3, 4, 5, 6});
+ EXPECT_EQ(6u, first.size());
+ for (int i = 0; i < 6; i++)
+ EXPECT_EQ(i + 1, first[i]);
+}
+
+TEST(CircularDeque, Destructor) {
+ int destruct_count = 0;
+
+ // Contiguous buffer.
+ {
+ circular_deque<DestructorCounter> q;
+ q.resize(5, DestructorCounter(&destruct_count));
+
+ EXPECT_EQ(1, destruct_count); // The temporary in the call to resize().
+ destruct_count = 0;
+ }
+ EXPECT_EQ(5, destruct_count); // One call for each.
+
+ // Force a wraparound buffer.
+ {
+ circular_deque<DestructorCounter> q;
+ q.reserve(7);
+ q.resize(5, DestructorCounter(&destruct_count));
+
+ // Cycle throught some elements in our buffer to force a wraparound.
+ destruct_count = 0;
+ for (int i = 0; i < 4; i++) {
+ q.emplace_back(&destruct_count);
+ q.pop_front();
+ }
+ EXPECT_EQ(4, destruct_count); // One for each cycle.
+ destruct_count = 0;
+ }
+ EXPECT_EQ(5, destruct_count); // One call for each.
+}
+
+TEST(CircularDeque, EqualsCopy) {
+ circular_deque<int> first = {1, 2, 3, 4, 5, 6};
+ circular_deque<int> copy;
+ EXPECT_TRUE(copy.empty());
+ copy = first;
+ EXPECT_EQ(6u, copy.size());
+ for (int i = 0; i < 6; i++) {
+ EXPECT_EQ(i + 1, first[i]);
+ EXPECT_EQ(i + 1, copy[i]);
+ EXPECT_NE(&first[i], &copy[i]);
+ }
+}
+
+TEST(CircularDeque, EqualsMove) {
+ circular_deque<int> first = {1, 2, 3, 4, 5, 6};
+ circular_deque<int> move;
+ EXPECT_TRUE(move.empty());
+ move = std::move(first);
+ EXPECT_TRUE(first.empty());
+ EXPECT_EQ(6u, move.size());
+ for (int i = 0; i < 6; i++)
+ EXPECT_EQ(i + 1, move[i]);
+}
+
+// Tests that self-assignment is a no-op.
+TEST(CircularDeque, EqualsSelf) {
+ circular_deque<int> q = {1, 2, 3, 4, 5, 6};
+ q = *&q; // The *& defeats Clang's -Wself-assign warning.
+ EXPECT_EQ(6u, q.size());
+ for (int i = 0; i < 6; i++)
+ EXPECT_EQ(i + 1, q[i]);
+}
+
+TEST(CircularDeque, EqualsInitializerList) {
+ circular_deque<int> q;
+ EXPECT_TRUE(q.empty());
+ q = {1, 2, 3, 4, 5, 6};
+ EXPECT_EQ(6u, q.size());
+ for (int i = 0; i < 6; i++)
+ EXPECT_EQ(i + 1, q[i]);
+}
+
+TEST(CircularDeque, AssignCountValue) {
+ circular_deque<int> empty;
+ empty.assign(0, 52);
+ EXPECT_EQ(0u, empty.size());
+
+ circular_deque<int> full;
+ size_t count = 13;
+ int value = 12345;
+ full.assign(count, value);
+ EXPECT_EQ(count, full.size());
+
+ for (size_t i = 0; i < count; i++)
+ EXPECT_EQ(value, full[i]);
+}
+
+TEST(CircularDeque, AssignIterator) {
+ int range[8] = {11, 12, 13, 14, 15, 16, 17, 18};
+
+ circular_deque<int> empty;
+ empty.assign(std::begin(range), std::begin(range));
+ EXPECT_TRUE(empty.empty());
+
+ circular_deque<int> full;
+ full.assign(std::begin(range), std::end(range));
+ EXPECT_EQ(8u, full.size());
+ for (size_t i = 0; i < 8; i++)
+ EXPECT_EQ(range[i], full[i]);
+}
+
+TEST(CircularDeque, AssignInitializerList) {
+ circular_deque<int> empty;
+ empty.assign({});
+ EXPECT_TRUE(empty.empty());
+
+ circular_deque<int> full;
+ full.assign({11, 12, 13, 14, 15, 16, 17, 18});
+ EXPECT_EQ(8u, full.size());
+ for (int i = 0; i < 8; i++)
+ EXPECT_EQ(11 + i, full[i]);
+}
+
+// Tests [] and .at().
+TEST(CircularDeque, At) {
+ circular_deque<int> q = MakeSequence(10);
+ CycleTest(q, [](const circular_deque<int>& q, size_t cycle) {
+ size_t expected_size = 10;
+ EXPECT_EQ(expected_size, q.size());
+
+ // A sequence of 0's.
+ size_t index = 0;
+ size_t num_zeros = std::min(expected_size, cycle);
+ for (size_t i = 0; i < num_zeros; i++, index++) {
+ EXPECT_EQ(0, q[index]);
+ EXPECT_EQ(0, q.at(index));
+ }
+
+ // Followed by a sequence of increasing ints.
+ size_t num_ints = expected_size - num_zeros;
+ for (int i = 0; i < static_cast<int>(num_ints); i++, index++) {
+ EXPECT_EQ(i, q[index]);
+ EXPECT_EQ(i, q.at(index));
+ }
+ });
+}
+
+// This also tests the copy constructor with lots of different types of
+// input configurations.
+TEST(CircularDeque, FrontBackPushPop) {
+ circular_deque<int> q = MakeSequence(10);
+
+ int expected_front = 0;
+ int expected_back = 9;
+
+ // Go in one direction.
+ for (int i = 0; i < 100; i++) {
+ const circular_deque<int> const_q(q);
+
+ EXPECT_EQ(expected_front, q.front());
+ EXPECT_EQ(expected_back, q.back());
+ EXPECT_EQ(expected_front, const_q.front());
+ EXPECT_EQ(expected_back, const_q.back());
+
+ expected_front++;
+ expected_back++;
+
+ q.pop_front();
+ q.push_back(expected_back);
+ }
+
+ // Go back in reverse.
+ for (int i = 0; i < 100; i++) {
+ const circular_deque<int> const_q(q);
+
+ EXPECT_EQ(expected_front, q.front());
+ EXPECT_EQ(expected_back, q.back());
+ EXPECT_EQ(expected_front, const_q.front());
+ EXPECT_EQ(expected_back, const_q.back());
+
+ expected_front--;
+ expected_back--;
+
+ q.pop_back();
+ q.push_front(expected_front);
+ }
+}
+
+TEST(CircularDeque, ReallocateWithSplitBuffer) {
+ // Tests reallocating a deque with an internal buffer that looks like this:
+ // 4 5 x x 0 1 2 3
+ // end-^ ^-begin
+ circular_deque<int> q;
+ q.reserve(7); // Internal buffer is always 1 larger than requested.
+ q.push_back(-1);
+ q.push_back(-1);
+ q.push_back(-1);
+ q.push_back(-1);
+ q.push_back(0);
+ q.pop_front();
+ q.pop_front();
+ q.pop_front();
+ q.pop_front();
+ q.push_back(1);
+ q.push_back(2);
+ q.push_back(3);
+ q.push_back(4);
+ q.push_back(5);
+
+ q.shrink_to_fit();
+ EXPECT_EQ(6u, q.size());
+
+ EXPECT_EQ(0, q[0]);
+ EXPECT_EQ(1, q[1]);
+ EXPECT_EQ(2, q[2]);
+ EXPECT_EQ(3, q[3]);
+ EXPECT_EQ(4, q[4]);
+ EXPECT_EQ(5, q[5]);
+}
+
+TEST(CircularDeque, Swap) {
+ circular_deque<int> a = MakeSequence(10);
+ circular_deque<int> b = MakeSequence(100);
+
+ a.swap(b);
+ EXPECT_EQ(100u, a.size());
+ for (int i = 0; i < 100; i++)
+ EXPECT_EQ(i, a[i]);
+
+ EXPECT_EQ(10u, b.size());
+ for (int i = 0; i < 10; i++)
+ EXPECT_EQ(i, b[i]);
+}
+
+TEST(CircularDeque, Iteration) {
+ circular_deque<int> q = MakeSequence(10);
+
+ int expected_front = 0;
+ int expected_back = 9;
+
+ // This loop causes various combinations of begin and end to be tested.
+ for (int i = 0; i < 30; i++) {
+ // Range-based for loop going forward.
+ int current_expected = expected_front;
+ for (int cur : q) {
+ EXPECT_EQ(current_expected, cur);
+ current_expected++;
+ }
+
+ // Manually test reverse iterators.
+ current_expected = expected_back;
+ for (auto cur = q.crbegin(); cur < q.crend(); cur++) {
+ EXPECT_EQ(current_expected, *cur);
+ current_expected--;
+ }
+
+ expected_front++;
+ expected_back++;
+
+ q.pop_front();
+ q.push_back(expected_back);
+ }
+
+ // Go back in reverse.
+ for (int i = 0; i < 100; i++) {
+ const circular_deque<int> const_q(q);
+
+ EXPECT_EQ(expected_front, q.front());
+ EXPECT_EQ(expected_back, q.back());
+ EXPECT_EQ(expected_front, const_q.front());
+ EXPECT_EQ(expected_back, const_q.back());
+
+ expected_front--;
+ expected_back--;
+
+ q.pop_back();
+ q.push_front(expected_front);
+ }
+}
+
+TEST(CircularDeque, IteratorComparisons) {
+ circular_deque<int> q = MakeSequence(10);
+
+ // This loop causes various combinations of begin and end to be tested.
+ for (int i = 0; i < 30; i++) {
+ EXPECT_LT(q.begin(), q.end());
+ EXPECT_LE(q.begin(), q.end());
+ EXPECT_LE(q.begin(), q.begin());
+
+ EXPECT_GT(q.end(), q.begin());
+ EXPECT_GE(q.end(), q.begin());
+ EXPECT_GE(q.end(), q.end());
+
+ EXPECT_EQ(q.begin(), q.begin());
+ EXPECT_NE(q.begin(), q.end());
+
+ q.push_front(10);
+ q.pop_back();
+ }
+}
+
+TEST(CircularDeque, IteratorIncDec) {
+ circular_deque<int> q;
+
+ // No-op offset computations with no capacity.
+ EXPECT_EQ(q.end(), q.end() + 0);
+ EXPECT_EQ(q.end(), q.end() - 0);
+
+ q = MakeSequence(10);
+
+ // Mutable preincrement, predecrement.
+ {
+ circular_deque<int>::iterator it = q.begin();
+ circular_deque<int>::iterator op_result = ++it;
+ EXPECT_EQ(1, *op_result);
+ EXPECT_EQ(1, *it);
+
+ op_result = --it;
+ EXPECT_EQ(0, *op_result);
+ EXPECT_EQ(0, *it);
+ }
+
+ // Const preincrement, predecrement.
+ {
+ circular_deque<int>::const_iterator it = q.begin();
+ circular_deque<int>::const_iterator op_result = ++it;
+ EXPECT_EQ(1, *op_result);
+ EXPECT_EQ(1, *it);
+
+ op_result = --it;
+ EXPECT_EQ(0, *op_result);
+ EXPECT_EQ(0, *it);
+ }
+
+ // Mutable postincrement, postdecrement.
+ {
+ circular_deque<int>::iterator it = q.begin();
+ circular_deque<int>::iterator op_result = it++;
+ EXPECT_EQ(0, *op_result);
+ EXPECT_EQ(1, *it);
+
+ op_result = it--;
+ EXPECT_EQ(1, *op_result);
+ EXPECT_EQ(0, *it);
+ }
+
+ // Const postincrement, postdecrement.
+ {
+ circular_deque<int>::const_iterator it = q.begin();
+ circular_deque<int>::const_iterator op_result = it++;
+ EXPECT_EQ(0, *op_result);
+ EXPECT_EQ(1, *it);
+
+ op_result = it--;
+ EXPECT_EQ(1, *op_result);
+ EXPECT_EQ(0, *it);
+ }
+}
+
+TEST(CircularDeque, IteratorIntegerOps) {
+ circular_deque<int> q = MakeSequence(10);
+
+ int expected_front = 0;
+ int expected_back = 9;
+
+ for (int i = 0; i < 30; i++) {
+ EXPECT_EQ(0, q.begin() - q.begin());
+ EXPECT_EQ(0, q.end() - q.end());
+ EXPECT_EQ(q.size(), static_cast<size_t>(q.end() - q.begin()));
+
+ // +=
+ circular_deque<int>::iterator eight = q.begin();
+ eight += 8;
+ EXPECT_EQ(8, eight - q.begin());
+ EXPECT_EQ(expected_front + 8, *eight);
+
+ // -=
+ eight -= 8;
+ EXPECT_EQ(q.begin(), eight);
+
+ // +
+ eight = eight + 8;
+ EXPECT_EQ(8, eight - q.begin());
+
+ // -
+ eight = eight - 8;
+ EXPECT_EQ(q.begin(), eight);
+
+ expected_front++;
+ expected_back++;
+
+ q.pop_front();
+ q.push_back(expected_back);
+ }
+}
+
+TEST(CircularDeque, IteratorArrayAccess) {
+ circular_deque<int> q = MakeSequence(10);
+
+ circular_deque<int>::iterator begin = q.begin();
+ EXPECT_EQ(0, begin[0]);
+ EXPECT_EQ(9, begin[9]);
+
+ circular_deque<int>::iterator end = q.end();
+ EXPECT_EQ(0, end[-10]);
+ EXPECT_EQ(9, end[-1]);
+
+ begin[0] = 100;
+ EXPECT_EQ(100, end[-10]);
+}
+
+TEST(CircularDeque, ReverseIterator) {
+ circular_deque<int> q;
+ q.push_back(4);
+ q.push_back(3);
+ q.push_back(2);
+ q.push_back(1);
+
+ circular_deque<int>::reverse_iterator iter = q.rbegin();
+ EXPECT_EQ(1, *iter);
+ iter++;
+ EXPECT_EQ(2, *iter);
+ ++iter;
+ EXPECT_EQ(3, *iter);
+ iter++;
+ EXPECT_EQ(4, *iter);
+ ++iter;
+ EXPECT_EQ(q.rend(), iter);
+}
+
+TEST(CircularDeque, CapacityReserveShrink) {
+ circular_deque<int> q;
+
+ // A default constructed queue should have no capacity since it should waste
+ // no space.
+ EXPECT_TRUE(q.empty());
+ EXPECT_EQ(0u, q.size());
+ EXPECT_EQ(0u, q.capacity());
+
+ size_t new_capacity = 100;
+ q.reserve(new_capacity);
+ EXPECT_EQ(new_capacity, q.capacity());
+
+ // Adding that many items should not cause a resize.
+ for (size_t i = 0; i < new_capacity; i++)
+ q.push_back(i);
+ EXPECT_EQ(new_capacity, q.size());
+ EXPECT_EQ(new_capacity, q.capacity());
+
+ // Shrink to fit to a smaller size.
+ size_t capacity_2 = new_capacity / 2;
+ q.resize(capacity_2);
+ q.shrink_to_fit();
+ EXPECT_EQ(capacity_2, q.size());
+ EXPECT_EQ(capacity_2, q.capacity());
+}
+
+TEST(CircularDeque, CapacityAutoShrink) {
+ size_t big_size = 1000u;
+ circular_deque<int> q;
+ q.resize(big_size);
+
+ size_t big_capacity = q.capacity();
+
+ // Delete 3/4 of the items.
+ for (size_t i = 0; i < big_size / 4 * 3; i++)
+ q.pop_back();
+
+ // The capacity should have shrunk by deleting that many items.
+ size_t medium_capacity = q.capacity();
+ EXPECT_GT(big_capacity, medium_capacity);
+
+ // Using resize to shrink should keep some extra capacity.
+ q.resize(1);
+ EXPECT_LT(1u, q.capacity());
+
+ q.resize(0);
+ EXPECT_LT(0u, q.capacity());
+
+ // Using clear() should delete everything.
+ q.clear();
+ EXPECT_EQ(0u, q.capacity());
+}
+
+TEST(CircularDeque, ClearAndEmpty) {
+ circular_deque<int> q;
+ EXPECT_TRUE(q.empty());
+
+ q.resize(10);
+ EXPECT_EQ(10u, q.size());
+ EXPECT_FALSE(q.empty());
+
+ q.clear();
+ EXPECT_EQ(0u, q.size());
+ EXPECT_TRUE(q.empty());
+
+ // clear() also should reset the capacity.
+ EXPECT_EQ(0u, q.capacity());
+}
+
+TEST(CircularDeque, Resize) {
+ circular_deque<int> q;
+
+ // Resize with default constructor.
+ size_t first_size = 10;
+ q.resize(first_size);
+ EXPECT_EQ(first_size, q.size());
+ for (size_t i = 0; i < first_size; i++)
+ EXPECT_EQ(0, q[i]);
+
+ // Resize with different value.
+ size_t second_expand = 10;
+ q.resize(first_size + second_expand, 3);
+ EXPECT_EQ(first_size + second_expand, q.size());
+ for (size_t i = 0; i < first_size; i++)
+ EXPECT_EQ(0, q[i]);
+ for (size_t i = 0; i < second_expand; i++)
+ EXPECT_EQ(3, q[i + first_size]);
+
+ // Erase from the end and add to the beginning so resize is forced to cross
+ // a circular buffer wrap boundary.
+ q.shrink_to_fit();
+ for (int i = 0; i < 5; i++) {
+ q.pop_back();
+ q.push_front(6);
+ }
+ q.resize(10);
+
+ EXPECT_EQ(6, q[0]);
+ EXPECT_EQ(6, q[1]);
+ EXPECT_EQ(6, q[2]);
+ EXPECT_EQ(6, q[3]);
+ EXPECT_EQ(6, q[4]);
+ EXPECT_EQ(0, q[5]);
+ EXPECT_EQ(0, q[6]);
+ EXPECT_EQ(0, q[7]);
+ EXPECT_EQ(0, q[8]);
+ EXPECT_EQ(0, q[9]);
+}
+
+// Tests destructor behavior of resize.
+TEST(CircularDeque, ResizeDelete) {
+ int counter = 0;
+ circular_deque<DestructorCounter> q;
+ q.resize(10, DestructorCounter(&counter));
+ // The one temporary when calling resize() should be deleted, that's it.
+ EXPECT_EQ(1, counter);
+
+ // The loops below assume the capacity will be set by resize().
+ EXPECT_EQ(10u, q.capacity());
+
+ // Delete some via resize(). This is done so that the wasted items are
+ // still greater than the size() so that auto-shrinking is not triggered
+ // (which will mess up our destructor counting).
+ counter = 0;
+ q.resize(8, DestructorCounter(&counter));
+ // 2 deleted ones + the one temporary in the resize() call.
+ EXPECT_EQ(3, counter);
+
+ // Cycle through some items so two items will cross the boundary in the
+ // 11-item buffer (one more than the capacity).
+ // Before: x x x x x x x x . . .
+ // After: x . . . x x x x x x x
+ counter = 0;
+ for (int i = 0; i < 4; i++) {
+ q.emplace_back(&counter);
+ q.pop_front();
+ }
+ EXPECT_EQ(4, counter); // Loop should have deleted 7 items.
+
+ // Delete two items with resize, these should be on either side of the
+ // buffer wrap point.
+ counter = 0;
+ q.resize(6, DestructorCounter(&counter));
+ // 2 deleted ones + the one temporary in the resize() call.
+ EXPECT_EQ(3, counter);
+}
+
+TEST(CircularDeque, InsertEraseSingle) {
+ circular_deque<int> q;
+ q.push_back(1);
+ q.push_back(2);
+
+ // Insert at the beginning.
+ auto result = q.insert(q.begin(), 0);
+ EXPECT_EQ(q.begin(), result);
+ EXPECT_EQ(3u, q.size());
+ EXPECT_EQ(0, q[0]);
+ EXPECT_EQ(1, q[1]);
+ EXPECT_EQ(2, q[2]);
+
+ // Erase at the beginning.
+ result = q.erase(q.begin());
+ EXPECT_EQ(q.begin(), result);
+ EXPECT_EQ(2u, q.size());
+ EXPECT_EQ(1, q[0]);
+ EXPECT_EQ(2, q[1]);
+
+ // Insert at the end.
+ result = q.insert(q.end(), 3);
+ EXPECT_EQ(q.end() - 1, result);
+ EXPECT_EQ(1, q[0]);
+ EXPECT_EQ(2, q[1]);
+ EXPECT_EQ(3, q[2]);
+
+ // Erase at the end.
+ result = q.erase(q.end() - 1);
+ EXPECT_EQ(q.end(), result);
+ EXPECT_EQ(1, q[0]);
+ EXPECT_EQ(2, q[1]);
+
+ // Insert in the middle.
+ result = q.insert(q.begin() + 1, 10);
+ EXPECT_EQ(q.begin() + 1, result);
+ EXPECT_EQ(1, q[0]);
+ EXPECT_EQ(10, q[1]);
+ EXPECT_EQ(2, q[2]);
+
+ // Erase in the middle.
+ result = q.erase(q.begin() + 1);
+ EXPECT_EQ(q.begin() + 1, result);
+ EXPECT_EQ(1, q[0]);
+ EXPECT_EQ(2, q[1]);
+}
+
+TEST(CircularDeque, InsertFill) {
+ circular_deque<int> q;
+
+ // Fill when empty.
+ q.insert(q.begin(), 2, 1);
+
+ // 0's at the beginning.
+ q.insert(q.begin(), 2, 0);
+
+ // 50's in the middle (now at offset 3).
+ q.insert(q.begin() + 3, 2, 50);
+
+ // 200's at the end.
+ q.insert(q.end(), 2, 200);
+
+ ASSERT_EQ(8u, q.size());
+ EXPECT_EQ(0, q[0]);
+ EXPECT_EQ(0, q[1]);
+ EXPECT_EQ(1, q[2]);
+ EXPECT_EQ(50, q[3]);
+ EXPECT_EQ(50, q[4]);
+ EXPECT_EQ(1, q[5]);
+ EXPECT_EQ(200, q[6]);
+ EXPECT_EQ(200, q[7]);
+}
+
+TEST(CircularDeque, InsertEraseRange) {
+ circular_deque<int> q;
+
+ // Erase nothing from an empty deque should work.
+ q.erase(q.begin(), q.end());
+
+ // Loop index used below to shift the used items in the buffer.
+ for (int i = 0; i < 10; i++) {
+ circular_deque<int> source;
+
+ // Fill empty range.
+ q.insert(q.begin(), source.begin(), source.end());
+
+ // Have some stuff to insert.
+ source.push_back(1);
+ source.push_back(2);
+
+ q.insert(q.begin(), source.begin(), source.end());
+
+ // Shift the used items in the buffer by i which will place the two used
+ // elements in different places in the buffer each time through this loop.
+ for (int shift_i = 0; shift_i < i; shift_i++) {
+ q.push_back(0);
+ q.pop_front();
+ }
+
+ // Set the two items to notable values so we can check for them later.
+ ASSERT_EQ(2u, q.size());
+ q[0] = 100;
+ q[1] = 101;
+
+ // Insert at the beginning, middle (now at offset 3), and end.
+ q.insert(q.begin(), source.begin(), source.end());
+ q.insert(q.begin() + 3, source.begin(), source.end());
+ q.insert(q.end(), source.begin(), source.end());
+
+ ASSERT_EQ(8u, q.size());
+ EXPECT_EQ(1, q[0]);
+ EXPECT_EQ(2, q[1]);
+ EXPECT_EQ(100, q[2]); // First inserted one.
+ EXPECT_EQ(1, q[3]);
+ EXPECT_EQ(2, q[4]);
+ EXPECT_EQ(101, q[5]); // First inserted second one.
+ EXPECT_EQ(1, q[6]);
+ EXPECT_EQ(2, q[7]);
+
+ // Now erase the inserted ranges. Try each subsection also with no items
+ // being erased, which should be a no-op.
+ auto result = q.erase(q.begin(), q.begin()); // No-op.
+ EXPECT_EQ(q.begin(), result);
+ result = q.erase(q.begin(), q.begin() + 2);
+ EXPECT_EQ(q.begin(), result);
+
+ result = q.erase(q.begin() + 1, q.begin() + 1); // No-op.
+ EXPECT_EQ(q.begin() + 1, result);
+ result = q.erase(q.begin() + 1, q.begin() + 3);
+ EXPECT_EQ(q.begin() + 1, result);
+
+ result = q.erase(q.end() - 2, q.end() - 2); // No-op.
+ EXPECT_EQ(q.end() - 2, result);
+ result = q.erase(q.end() - 2, q.end());
+ EXPECT_EQ(q.end(), result);
+
+ ASSERT_EQ(2u, q.size());
+ EXPECT_EQ(100, q[0]);
+ EXPECT_EQ(101, q[1]);
+
+ // Erase everything.
+ result = q.erase(q.begin(), q.end());
+ EXPECT_EQ(q.end(), result);
+ EXPECT_TRUE(q.empty());
+ }
+}
+
+TEST(CircularDeque, EmplaceMoveOnly) {
+ int values[] = {1, 3};
+ circular_deque<MoveOnlyInt> q(std::begin(values), std::end(values));
+
+ q.emplace(q.begin(), MoveOnlyInt(0));
+ q.emplace(q.begin() + 2, MoveOnlyInt(2));
+ q.emplace(q.end(), MoveOnlyInt(4));
+
+ ASSERT_EQ(5u, q.size());
+ EXPECT_EQ(0, q[0].data());
+ EXPECT_EQ(1, q[1].data());
+ EXPECT_EQ(2, q[2].data());
+ EXPECT_EQ(3, q[3].data());
+ EXPECT_EQ(4, q[4].data());
+}
+
+TEST(CircularDeque, EmplaceFrontBackReturnsReference) {
+ circular_deque<int> q;
+ q.reserve(2);
+
+ int& front = q.emplace_front(1);
+ int& back = q.emplace_back(2);
+ ASSERT_EQ(2u, q.size());
+ EXPECT_EQ(1, q[0]);
+ EXPECT_EQ(2, q[1]);
+
+ EXPECT_EQ(&front, &q.front());
+ EXPECT_EQ(&back, &q.back());
+
+ front = 3;
+ back = 4;
+
+ ASSERT_EQ(2u, q.size());
+ EXPECT_EQ(3, q[0]);
+ EXPECT_EQ(4, q[1]);
+
+ EXPECT_EQ(&front, &q.front());
+ EXPECT_EQ(&back, &q.back());
+}
+
+/*
+This test should assert in a debug build. It tries to dereference an iterator
+after mutating the container. Uncomment to double-check that this works.
+TEST(CircularDeque, UseIteratorAfterMutate) {
+ circular_deque<int> q;
+ q.push_back(0);
+
+ auto old_begin = q.begin();
+ EXPECT_EQ(0, *old_begin);
+
+ q.push_back(1);
+ EXPECT_EQ(0, *old_begin); // Should DCHECK.
+}
+*/
+
+} // namespace base
diff --git a/base/containers/flat_map.h b/base/containers/flat_map.h
new file mode 100644
index 0000000000..b4fe5196ec
--- /dev/null
+++ b/base/containers/flat_map.h
@@ -0,0 +1,362 @@
+// 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_CONTAINERS_FLAT_MAP_H_
+#define BASE_CONTAINERS_FLAT_MAP_H_
+
+#include <functional>
+#include <tuple>
+#include <utility>
+
+#include "base/containers/flat_tree.h"
+#include "base/logging.h"
+#include "base/template_util.h"
+
+namespace base {
+
+namespace internal {
+
+// An implementation of the flat_tree GetKeyFromValue template parameter that
+// extracts the key as the first element of a pair.
+template <class Key, class Mapped>
+struct GetKeyFromValuePairFirst {
+ const Key& operator()(const std::pair<Key, Mapped>& p) const {
+ return p.first;
+ }
+};
+
+} // namespace internal
+
+// flat_map is a container with a std::map-like interface that stores its
+// contents in a sorted vector.
+//
+// Please see //base/containers/README.md for an overview of which container
+// to select.
+//
+// PROS
+//
+// - Good memory locality.
+// - Low overhead, especially for smaller maps.
+// - Performance is good for more workloads than you might expect (see
+// overview link above).
+// - Supports C++14 map interface.
+//
+// CONS
+//
+// - Inserts and removals are O(n).
+//
+// IMPORTANT NOTES
+//
+// - Iterators are invalidated across mutations.
+// - If possible, construct a flat_map in one operation by inserting into
+// a std::vector and moving that vector into the flat_map constructor.
+//
+// QUICK REFERENCE
+//
+// Most of the core functionality is inherited from flat_tree. Please see
+// flat_tree.h for more details for most of these functions. As a quick
+// reference, the functions available are:
+//
+// Constructors (inputs need not be sorted):
+// flat_map(InputIterator first, InputIterator last,
+// FlatContainerDupes = KEEP_FIRST_OF_DUPES,
+// const Compare& compare = Compare());
+// flat_map(const flat_map&);
+// flat_map(flat_map&&);
+// flat_map(std::vector<value_type>,
+// FlatContainerDupes = KEEP_FIRST_OF_DUPES,
+// const Compare& compare = Compare()); // Re-use storage.
+// flat_map(std::initializer_list<value_type> ilist,
+// FlatContainerDupes = KEEP_FIRST_OF_DUPES,
+// const Compare& comp = Compare());
+//
+// Assignment functions:
+// flat_map& operator=(const flat_map&);
+// flat_map& operator=(flat_map&&);
+// flat_map& operator=(initializer_list<value_type>);
+//
+// Memory management functions:
+// void reserve(size_t);
+// size_t capacity() const;
+// void shrink_to_fit();
+//
+// Size management functions:
+// void clear();
+// size_t size() const;
+// size_t max_size() const;
+// bool empty() const;
+//
+// Iterator functions:
+// iterator begin();
+// const_iterator begin() const;
+// const_iterator cbegin() const;
+// iterator end();
+// const_iterator end() const;
+// const_iterator cend() const;
+// reverse_iterator rbegin();
+// const reverse_iterator rbegin() const;
+// const_reverse_iterator crbegin() const;
+// reverse_iterator rend();
+// const_reverse_iterator rend() const;
+// const_reverse_iterator crend() const;
+//
+// Insert and accessor functions:
+// mapped_type& operator[](const key_type&);
+// mapped_type& operator[](key_type&&);
+// pair<iterator, bool> insert(const value_type&);
+// pair<iterator, bool> insert(value_type&&);
+// iterator insert(const_iterator hint, const value_type&);
+// iterator insert(const_iterator hint, value_type&&);
+// void insert(InputIterator first, InputIterator last,
+// FlatContainerDupes = KEEP_FIRST_OF_DUPES);
+// pair<iterator, bool> insert_or_assign(K&&, M&&);
+// iterator insert_or_assign(const_iterator hint, K&&, M&&);
+// pair<iterator, bool> emplace(Args&&...);
+// iterator emplace_hint(const_iterator, Args&&...);
+// pair<iterator, bool> try_emplace(K&&, Args&&...);
+// iterator try_emplace(const_iterator hint, K&&, Args&&...);
+//
+// Erase functions:
+// iterator erase(iterator);
+// iterator erase(const_iterator);
+// iterator erase(const_iterator first, const_iterator& last);
+// template <class K> size_t erase(const K& key);
+//
+// Comparators (see std::map documentation).
+// key_compare key_comp() const;
+// value_compare value_comp() const;
+//
+// Search functions:
+// template <typename K> size_t count(const K&) const;
+// template <typename K> iterator find(const K&);
+// template <typename K> const_iterator find(const K&) const;
+// template <typename K> pair<iterator, iterator> equal_range(const K&);
+// template <typename K> iterator lower_bound(const K&);
+// template <typename K> const_iterator lower_bound(const K&) const;
+// template <typename K> iterator upper_bound(const K&);
+// template <typename K> const_iterator upper_bound(const K&) const;
+//
+// General functions:
+// void swap(flat_map&&);
+//
+// Non-member operators:
+// bool operator==(const flat_map&, const flat_map);
+// bool operator!=(const flat_map&, const flat_map);
+// bool operator<(const flat_map&, const flat_map);
+// bool operator>(const flat_map&, const flat_map);
+// bool operator>=(const flat_map&, const flat_map);
+// bool operator<=(const flat_map&, const flat_map);
+//
+template <class Key, class Mapped, class Compare = std::less<>>
+class flat_map : public ::base::internal::flat_tree<
+ Key,
+ std::pair<Key, Mapped>,
+ ::base::internal::GetKeyFromValuePairFirst<Key, Mapped>,
+ Compare> {
+ private:
+ using tree = typename ::base::internal::flat_tree<
+ Key,
+ std::pair<Key, Mapped>,
+ ::base::internal::GetKeyFromValuePairFirst<Key, Mapped>,
+ Compare>;
+
+ public:
+ using key_type = typename tree::key_type;
+ using mapped_type = Mapped;
+ using value_type = typename tree::value_type;
+ using iterator = typename tree::iterator;
+ using const_iterator = typename tree::const_iterator;
+
+ // --------------------------------------------------------------------------
+ // Lifetime and assignments.
+ //
+ // Note: we could do away with these constructors, destructor and assignment
+ // operator overloads by inheriting |tree|'s, but this breaks the GCC build
+ // due to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84782 (see
+ // https://crbug.com/837221).
+
+ flat_map() = default;
+ explicit flat_map(const Compare& comp);
+
+ template <class InputIterator>
+ flat_map(InputIterator first,
+ InputIterator last,
+ FlatContainerDupes dupe_handling = KEEP_FIRST_OF_DUPES,
+ const Compare& comp = Compare());
+
+ flat_map(const flat_map&) = default;
+ flat_map(flat_map&&) noexcept = default;
+
+ flat_map(std::vector<value_type> items,
+ FlatContainerDupes dupe_handling = KEEP_FIRST_OF_DUPES,
+ const Compare& comp = Compare());
+
+ flat_map(std::initializer_list<value_type> ilist,
+ FlatContainerDupes dupe_handling = KEEP_FIRST_OF_DUPES,
+ const Compare& comp = Compare());
+
+ ~flat_map() = default;
+
+ flat_map& operator=(const flat_map&) = default;
+ flat_map& operator=(flat_map&&) = default;
+ // Takes the first if there are duplicates in the initializer list.
+ flat_map& operator=(std::initializer_list<value_type> ilist);
+
+ // --------------------------------------------------------------------------
+ // Map-specific insert operations.
+ //
+ // Normal insert() functions are inherited from flat_tree.
+ //
+ // Assume that every operation invalidates iterators and references.
+ // Insertion of one element can take O(size).
+
+ mapped_type& operator[](const key_type& key);
+ mapped_type& operator[](key_type&& key);
+
+ template <class K, class M>
+ std::pair<iterator, bool> insert_or_assign(K&& key, M&& obj);
+ template <class K, class M>
+ iterator insert_or_assign(const_iterator hint, K&& key, M&& obj);
+
+ template <class K, class... Args>
+ std::enable_if_t<std::is_constructible<key_type, K&&>::value,
+ std::pair<iterator, bool>>
+ try_emplace(K&& key, Args&&... args);
+
+ template <class K, class... Args>
+ std::enable_if_t<std::is_constructible<key_type, K&&>::value, iterator>
+ try_emplace(const_iterator hint, K&& key, Args&&... args);
+
+ // --------------------------------------------------------------------------
+ // General operations.
+ //
+ // Assume that swap invalidates iterators and references.
+
+ void swap(flat_map& other) noexcept;
+
+ friend void swap(flat_map& lhs, flat_map& rhs) noexcept { lhs.swap(rhs); }
+};
+
+// ----------------------------------------------------------------------------
+// Lifetime.
+
+template <class Key, class Mapped, class Compare>
+flat_map<Key, Mapped, Compare>::flat_map(const Compare& comp) : tree(comp) {}
+
+template <class Key, class Mapped, class Compare>
+template <class InputIterator>
+flat_map<Key, Mapped, Compare>::flat_map(InputIterator first,
+ InputIterator last,
+ FlatContainerDupes dupe_handling,
+ const Compare& comp)
+ : tree(first, last, dupe_handling, comp) {}
+
+template <class Key, class Mapped, class Compare>
+flat_map<Key, Mapped, Compare>::flat_map(std::vector<value_type> items,
+ FlatContainerDupes dupe_handling,
+ const Compare& comp)
+ : tree(std::move(items), dupe_handling, comp) {}
+
+template <class Key, class Mapped, class Compare>
+flat_map<Key, Mapped, Compare>::flat_map(
+ std::initializer_list<value_type> ilist,
+ FlatContainerDupes dupe_handling,
+ const Compare& comp)
+ : flat_map(std::begin(ilist), std::end(ilist), dupe_handling, comp) {}
+
+// ----------------------------------------------------------------------------
+// Assignments.
+
+template <class Key, class Mapped, class Compare>
+auto flat_map<Key, Mapped, Compare>::operator=(
+ std::initializer_list<value_type> ilist) -> flat_map& {
+ // When https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84782 gets fixed, we
+ // need to remember to inherit tree::operator= to prevent
+ // flat_map<...> x;
+ // x = {...};
+ // from first creating a flat_map and then move assigning it. This most
+ // likely would be optimized away but still affects our debug builds.
+ tree::operator=(ilist);
+ return *this;
+}
+
+// ----------------------------------------------------------------------------
+// Insert operations.
+
+template <class Key, class Mapped, class Compare>
+auto flat_map<Key, Mapped, Compare>::operator[](const key_type& key)
+ -> mapped_type& {
+ iterator found = tree::lower_bound(key);
+ if (found == tree::end() || tree::key_comp()(key, found->first))
+ found = tree::unsafe_emplace(found, key, mapped_type());
+ return found->second;
+}
+
+template <class Key, class Mapped, class Compare>
+auto flat_map<Key, Mapped, Compare>::operator[](key_type&& key)
+ -> mapped_type& {
+ iterator found = tree::lower_bound(key);
+ if (found == tree::end() || tree::key_comp()(key, found->first))
+ found = tree::unsafe_emplace(found, std::move(key), mapped_type());
+ return found->second;
+}
+
+template <class Key, class Mapped, class Compare>
+template <class K, class M>
+auto flat_map<Key, Mapped, Compare>::insert_or_assign(K&& key, M&& obj)
+ -> std::pair<iterator, bool> {
+ auto result =
+ tree::emplace_key_args(key, std::forward<K>(key), std::forward<M>(obj));
+ if (!result.second)
+ result.first->second = std::forward<M>(obj);
+ return result;
+}
+
+template <class Key, class Mapped, class Compare>
+template <class K, class M>
+auto flat_map<Key, Mapped, Compare>::insert_or_assign(const_iterator hint,
+ K&& key,
+ M&& obj) -> iterator {
+ auto result = tree::emplace_hint_key_args(hint, key, std::forward<K>(key),
+ std::forward<M>(obj));
+ if (!result.second)
+ result.first->second = std::forward<M>(obj);
+ return result.first;
+}
+
+template <class Key, class Mapped, class Compare>
+template <class K, class... Args>
+auto flat_map<Key, Mapped, Compare>::try_emplace(K&& key, Args&&... args)
+ -> std::enable_if_t<std::is_constructible<key_type, K&&>::value,
+ std::pair<iterator, bool>> {
+ return tree::emplace_key_args(
+ key, std::piecewise_construct,
+ std::forward_as_tuple(std::forward<K>(key)),
+ std::forward_as_tuple(std::forward<Args>(args)...));
+}
+
+template <class Key, class Mapped, class Compare>
+template <class K, class... Args>
+auto flat_map<Key, Mapped, Compare>::try_emplace(const_iterator hint,
+ K&& key,
+ Args&&... args)
+ -> std::enable_if_t<std::is_constructible<key_type, K&&>::value, iterator> {
+ return tree::emplace_hint_key_args(
+ hint, key, std::piecewise_construct,
+ std::forward_as_tuple(std::forward<K>(key)),
+ std::forward_as_tuple(std::forward<Args>(args)...))
+ .first;
+}
+
+// ----------------------------------------------------------------------------
+// General operations.
+
+template <class Key, class Mapped, class Compare>
+void flat_map<Key, Mapped, Compare>::swap(flat_map& other) noexcept {
+ tree::swap(other);
+}
+
+} // namespace base
+
+#endif // BASE_CONTAINERS_FLAT_MAP_H_
diff --git a/base/containers/flat_set.h b/base/containers/flat_set.h
new file mode 100644
index 0000000000..bf14c36fd7
--- /dev/null
+++ b/base/containers/flat_set.h
@@ -0,0 +1,140 @@
+// 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_CONTAINERS_FLAT_SET_H_
+#define BASE_CONTAINERS_FLAT_SET_H_
+
+#include <functional>
+
+#include "base/containers/flat_tree.h"
+#include "base/template_util.h"
+
+namespace base {
+
+// flat_set is a container with a std::set-like interface that stores its
+// contents in a sorted vector.
+//
+// Please see //base/containers/README.md for an overview of which container
+// to select.
+//
+// PROS
+//
+// - Good memory locality.
+// - Low overhead, especially for smaller sets.
+// - Performance is good for more workloads than you might expect (see
+// overview link above).
+// - Supports C++14 set interface.
+//
+// CONS
+//
+// - Inserts and removals are O(n).
+//
+// IMPORTANT NOTES
+//
+// - Iterators are invalidated across mutations.
+// - If possible, construct a flat_set in one operation by inserting into
+// a std::vector and moving that vector into the flat_set constructor.
+// - For multiple removals use base::EraseIf() which is O(n) rather than
+// O(n * removed_items).
+//
+// QUICK REFERENCE
+//
+// Most of the core functionality is inherited from flat_tree. Please see
+// flat_tree.h for more details for most of these functions. As a quick
+// reference, the functions available are:
+//
+// Constructors (inputs need not be sorted):
+// flat_set(InputIterator first, InputIterator last,
+// FlatContainerDupes = KEEP_FIRST_OF_DUPES,
+// const Compare& compare = Compare());
+// flat_set(const flat_set&);
+// flat_set(flat_set&&);
+// flat_set(std::vector<Key>,
+// FlatContainerDupes = KEEP_FIRST_OF_DUPES,
+// const Compare& compare = Compare()); // Re-use storage.
+// flat_set(std::initializer_list<value_type> ilist,
+// FlatContainerDupes = KEEP_FIRST_OF_DUPES,
+// const Compare& comp = Compare());
+//
+// Assignment functions:
+// flat_set& operator=(const flat_set&);
+// flat_set& operator=(flat_set&&);
+// flat_set& operator=(initializer_list<Key>);
+//
+// Memory management functions:
+// void reserve(size_t);
+// size_t capacity() const;
+// void shrink_to_fit();
+//
+// Size management functions:
+// void clear();
+// size_t size() const;
+// size_t max_size() const;
+// bool empty() const;
+//
+// Iterator functions:
+// iterator begin();
+// const_iterator begin() const;
+// const_iterator cbegin() const;
+// iterator end();
+// const_iterator end() const;
+// const_iterator cend() const;
+// reverse_iterator rbegin();
+// const reverse_iterator rbegin() const;
+// const_reverse_iterator crbegin() const;
+// reverse_iterator rend();
+// const_reverse_iterator rend() const;
+// const_reverse_iterator crend() const;
+//
+// Insert and accessor functions:
+// pair<iterator, bool> insert(const key_type&);
+// pair<iterator, bool> insert(key_type&&);
+// void insert(InputIterator first, InputIterator last,
+// FlatContainerDupes = KEEP_FIRST_OF_DUPES);
+// iterator insert(const_iterator hint, const key_type&);
+// iterator insert(const_iterator hint, key_type&&);
+// pair<iterator, bool> emplace(Args&&...);
+// iterator emplace_hint(const_iterator, Args&&...);
+//
+// Erase functions:
+// iterator erase(iterator);
+// iterator erase(const_iterator);
+// iterator erase(const_iterator first, const_iterator& last);
+// template <typename K> size_t erase(const K& key);
+//
+// Comparators (see std::set documentation).
+// key_compare key_comp() const;
+// value_compare value_comp() const;
+//
+// Search functions:
+// template <typename K> size_t count(const K&) const;
+// template <typename K> iterator find(const K&);
+// template <typename K> const_iterator find(const K&) const;
+// template <typename K> pair<iterator, iterator> equal_range(K&);
+// template <typename K> iterator lower_bound(const K&);
+// template <typename K> const_iterator lower_bound(const K&) const;
+// template <typename K> iterator upper_bound(const K&);
+// template <typename K> const_iterator upper_bound(const K&) const;
+//
+// General functions:
+// void swap(flat_set&&);
+//
+// Non-member operators:
+// bool operator==(const flat_set&, const flat_set);
+// bool operator!=(const flat_set&, const flat_set);
+// bool operator<(const flat_set&, const flat_set);
+// bool operator>(const flat_set&, const flat_set);
+// bool operator>=(const flat_set&, const flat_set);
+// bool operator<=(const flat_set&, const flat_set);
+//
+template <class Key, class Compare = std::less<>>
+using flat_set = typename ::base::internal::flat_tree<
+ Key,
+ Key,
+ ::base::internal::GetKeyFromValueIdentity<Key>,
+ Compare>;
+
+} // namespace base
+
+#endif // BASE_CONTAINERS_FLAT_SET_H_
diff --git a/base/containers/flat_tree.h b/base/containers/flat_tree.h
new file mode 100644
index 0000000000..7856e24233
--- /dev/null
+++ b/base/containers/flat_tree.h
@@ -0,0 +1,1004 @@
+// 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_CONTAINERS_FLAT_TREE_H_
+#define BASE_CONTAINERS_FLAT_TREE_H_
+
+#include <algorithm>
+#include <iterator>
+#include <type_traits>
+#include <vector>
+
+#include "base/template_util.h"
+
+namespace base {
+
+enum FlatContainerDupes {
+ KEEP_FIRST_OF_DUPES,
+ KEEP_LAST_OF_DUPES,
+};
+
+namespace internal {
+
+// This is a convenience method returning true if Iterator is at least a
+// ForwardIterator and thus supports multiple passes over a range.
+template <class Iterator>
+constexpr bool is_multipass() {
+ return std::is_base_of<
+ std::forward_iterator_tag,
+ typename std::iterator_traits<Iterator>::iterator_category>::value;
+}
+
+// This algorithm is like unique() from the standard library except it
+// selects only the last of consecutive values instead of the first.
+template <class Iterator, class BinaryPredicate>
+Iterator LastUnique(Iterator first, Iterator last, BinaryPredicate compare) {
+ Iterator replacable = std::adjacent_find(first, last, compare);
+
+ // No duplicate elements found.
+ if (replacable == last)
+ return last;
+
+ first = std::next(replacable);
+
+ // Last element is a duplicate but all others are unique.
+ if (first == last)
+ return replacable;
+
+ // This loop is based on std::adjacent_find but std::adjacent_find doesn't
+ // quite cut it.
+ for (Iterator next = std::next(first); next != last; ++next, ++first) {
+ if (!compare(*first, *next))
+ *replacable++ = std::move(*first);
+ }
+
+ // Last element should be copied unconditionally.
+ *replacable++ = std::move(*first);
+ return replacable;
+}
+
+// Uses SFINAE to detect whether type has is_transparent member.
+template <typename T, typename = void>
+struct IsTransparentCompare : std::false_type {};
+template <typename T>
+struct IsTransparentCompare<T, void_t<typename T::is_transparent>>
+ : std::true_type {};
+
+// Implementation -------------------------------------------------------------
+
+// Implementation of a sorted vector for backing flat_set and flat_map. Do not
+// use directly.
+//
+// The use of "value" in this is like std::map uses, meaning it's the thing
+// contained (in the case of map it's a <Kay, Mapped> pair). The Key is how
+// things are looked up. In the case of a set, Key == Value. In the case of
+// a map, the Key is a component of a Value.
+//
+// The helper class GetKeyFromValue provides the means to extract a key from a
+// value for comparison purposes. It should implement:
+// const Key& operator()(const Value&).
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+class flat_tree {
+ private:
+ using underlying_type = std::vector<Value>;
+
+ public:
+ // --------------------------------------------------------------------------
+ // Types.
+ //
+ using key_type = Key;
+ using key_compare = KeyCompare;
+ using value_type = Value;
+
+ // Wraps the templated key comparison to compare values.
+ class value_compare : public key_compare {
+ public:
+ value_compare() = default;
+
+ template <class Cmp>
+ explicit value_compare(Cmp&& compare_arg)
+ : KeyCompare(std::forward<Cmp>(compare_arg)) {}
+
+ bool operator()(const value_type& left, const value_type& right) const {
+ GetKeyFromValue extractor;
+ return key_compare::operator()(extractor(left), extractor(right));
+ }
+ };
+
+ using pointer = typename underlying_type::pointer;
+ using const_pointer = typename underlying_type::const_pointer;
+ using reference = typename underlying_type::reference;
+ using const_reference = typename underlying_type::const_reference;
+ using size_type = typename underlying_type::size_type;
+ using difference_type = typename underlying_type::difference_type;
+ using iterator = typename underlying_type::iterator;
+ using const_iterator = typename underlying_type::const_iterator;
+ using reverse_iterator = typename underlying_type::reverse_iterator;
+ using const_reverse_iterator =
+ typename underlying_type::const_reverse_iterator;
+
+ // --------------------------------------------------------------------------
+ // Lifetime.
+ //
+ // Constructors that take range guarantee O(N * log^2(N)) + O(N) complexity
+ // and take O(N * log(N)) + O(N) if extra memory is available (N is a range
+ // length).
+ //
+ // Assume that move constructors invalidate iterators and references.
+ //
+ // The constructors that take ranges, lists, and vectors do not require that
+ // the input be sorted.
+
+ flat_tree();
+ explicit flat_tree(const key_compare& comp);
+
+ template <class InputIterator>
+ flat_tree(InputIterator first,
+ InputIterator last,
+ FlatContainerDupes dupe_handling = KEEP_FIRST_OF_DUPES,
+ const key_compare& comp = key_compare());
+
+ flat_tree(const flat_tree&);
+ flat_tree(flat_tree&&) noexcept = default;
+
+ flat_tree(std::vector<value_type> items,
+ FlatContainerDupes dupe_handling = KEEP_FIRST_OF_DUPES,
+ const key_compare& comp = key_compare());
+
+ flat_tree(std::initializer_list<value_type> ilist,
+ FlatContainerDupes dupe_handling = KEEP_FIRST_OF_DUPES,
+ const key_compare& comp = key_compare());
+
+ ~flat_tree();
+
+ // --------------------------------------------------------------------------
+ // Assignments.
+ //
+ // Assume that move assignment invalidates iterators and references.
+
+ flat_tree& operator=(const flat_tree&);
+ flat_tree& operator=(flat_tree&&);
+ // Takes the first if there are duplicates in the initializer list.
+ flat_tree& operator=(std::initializer_list<value_type> ilist);
+
+ // --------------------------------------------------------------------------
+ // Memory management.
+ //
+ // Beware that shrink_to_fit() simply forwards the request to the
+ // underlying_type and its implementation is free to optimize otherwise and
+ // leave capacity() to be greater that its size.
+ //
+ // reserve() and shrink_to_fit() invalidate iterators and references.
+
+ void reserve(size_type new_capacity);
+ size_type capacity() const;
+ void shrink_to_fit();
+
+ // --------------------------------------------------------------------------
+ // Size management.
+ //
+ // clear() leaves the capacity() of the flat_tree unchanged.
+
+ void clear();
+
+ size_type size() const;
+ size_type max_size() const;
+ bool empty() const;
+
+ // --------------------------------------------------------------------------
+ // Iterators.
+
+ iterator begin();
+ const_iterator begin() const;
+ const_iterator cbegin() const;
+
+ iterator end();
+ const_iterator end() const;
+ const_iterator cend() const;
+
+ reverse_iterator rbegin();
+ const_reverse_iterator rbegin() const;
+ const_reverse_iterator crbegin() const;
+
+ reverse_iterator rend();
+ const_reverse_iterator rend() const;
+ const_reverse_iterator crend() const;
+
+ // --------------------------------------------------------------------------
+ // Insert operations.
+ //
+ // Assume that every operation invalidates iterators and references.
+ // Insertion of one element can take O(size). Capacity of flat_tree grows in
+ // an implementation-defined manner.
+ //
+ // NOTE: Prefer to build a new flat_tree from a std::vector (or similar)
+ // instead of calling insert() repeatedly.
+
+ std::pair<iterator, bool> insert(const value_type& val);
+ std::pair<iterator, bool> insert(value_type&& val);
+
+ iterator insert(const_iterator position_hint, const value_type& x);
+ iterator insert(const_iterator position_hint, value_type&& x);
+
+ // This method inserts the values from the range [first, last) into the
+ // current tree. In case of KEEP_LAST_OF_DUPES newly added elements can
+ // overwrite existing values.
+ template <class InputIterator>
+ void insert(InputIterator first,
+ InputIterator last,
+ FlatContainerDupes dupes = KEEP_FIRST_OF_DUPES);
+
+ template <class... Args>
+ std::pair<iterator, bool> emplace(Args&&... args);
+
+ template <class... Args>
+ iterator emplace_hint(const_iterator position_hint, Args&&... args);
+
+ // --------------------------------------------------------------------------
+ // Erase operations.
+ //
+ // Assume that every operation invalidates iterators and references.
+ //
+ // erase(position), erase(first, last) can take O(size).
+ // erase(key) may take O(size) + O(log(size)).
+ //
+ // Prefer base::EraseIf() or some other variation on erase(remove(), end())
+ // idiom when deleting multiple non-consecutive elements.
+
+ iterator erase(iterator position);
+ iterator erase(const_iterator position);
+ iterator erase(const_iterator first, const_iterator last);
+ template <typename K>
+ size_type erase(const K& key);
+
+ // --------------------------------------------------------------------------
+ // Comparators.
+
+ key_compare key_comp() const;
+ value_compare value_comp() const;
+
+ // --------------------------------------------------------------------------
+ // Search operations.
+ //
+ // Search operations have O(log(size)) complexity.
+
+ template <typename K>
+ size_type count(const K& key) const;
+
+ template <typename K>
+ iterator find(const K& key);
+
+ template <typename K>
+ const_iterator find(const K& key) const;
+
+ template <typename K>
+ std::pair<iterator, iterator> equal_range(const K& key);
+
+ template <typename K>
+ std::pair<const_iterator, const_iterator> equal_range(const K& key) const;
+
+ template <typename K>
+ iterator lower_bound(const K& key);
+
+ template <typename K>
+ const_iterator lower_bound(const K& key) const;
+
+ template <typename K>
+ iterator upper_bound(const K& key);
+
+ template <typename K>
+ const_iterator upper_bound(const K& key) const;
+
+ // --------------------------------------------------------------------------
+ // General operations.
+ //
+ // Assume that swap invalidates iterators and references.
+ //
+ // Implementation note: currently we use operator==() and operator<() on
+ // std::vector, because they have the same contract we need, so we use them
+ // directly for brevity and in case it is more optimal than calling equal()
+ // and lexicograhpical_compare(). If the underlying container type is changed,
+ // this code may need to be modified.
+
+ void swap(flat_tree& other) noexcept;
+
+ friend bool operator==(const flat_tree& lhs, const flat_tree& rhs) {
+ return lhs.impl_.body_ == rhs.impl_.body_;
+ }
+
+ friend bool operator!=(const flat_tree& lhs, const flat_tree& rhs) {
+ return !(lhs == rhs);
+ }
+
+ friend bool operator<(const flat_tree& lhs, const flat_tree& rhs) {
+ return lhs.impl_.body_ < rhs.impl_.body_;
+ }
+
+ friend bool operator>(const flat_tree& lhs, const flat_tree& rhs) {
+ return rhs < lhs;
+ }
+
+ friend bool operator>=(const flat_tree& lhs, const flat_tree& rhs) {
+ return !(lhs < rhs);
+ }
+
+ friend bool operator<=(const flat_tree& lhs, const flat_tree& rhs) {
+ return !(lhs > rhs);
+ }
+
+ friend void swap(flat_tree& lhs, flat_tree& rhs) noexcept { lhs.swap(rhs); }
+
+ protected:
+ // Emplaces a new item into the tree that is known not to be in it. This
+ // is for implementing map operator[].
+ template <class... Args>
+ iterator unsafe_emplace(const_iterator position, Args&&... args);
+
+ // Attempts to emplace a new element with key |key|. Only if |key| is not yet
+ // present, construct value_type from |args| and insert it. Returns an
+ // iterator to the element with key |key| and a bool indicating whether an
+ // insertion happened.
+ template <class K, class... Args>
+ std::pair<iterator, bool> emplace_key_args(const K& key, Args&&... args);
+
+ // Similar to |emplace_key_args|, but checks |hint| first as a possible
+ // insertion position.
+ template <class K, class... Args>
+ std::pair<iterator, bool> emplace_hint_key_args(const_iterator hint,
+ const K& key,
+ Args&&... args);
+
+ private:
+ // Helper class for e.g. lower_bound that can compare a value on the left
+ // to a key on the right.
+ struct KeyValueCompare {
+ // The key comparison object must outlive this class.
+ explicit KeyValueCompare(const key_compare& key_comp)
+ : key_comp_(key_comp) {}
+
+ template <typename T, typename U>
+ bool operator()(const T& lhs, const U& rhs) const {
+ return key_comp_(extract_if_value_type(lhs), extract_if_value_type(rhs));
+ }
+
+ private:
+ const key_type& extract_if_value_type(const value_type& v) const {
+ GetKeyFromValue extractor;
+ return extractor(v);
+ }
+
+ template <typename K>
+ const K& extract_if_value_type(const K& k) const {
+ return k;
+ }
+
+ const key_compare& key_comp_;
+ };
+
+ const flat_tree& as_const() { return *this; }
+
+ iterator const_cast_it(const_iterator c_it) {
+ auto distance = std::distance(cbegin(), c_it);
+ return std::next(begin(), distance);
+ }
+
+ // This method is inspired by both std::map::insert(P&&) and
+ // std::map::insert_or_assign(const K&, V&&). It inserts val if an equivalent
+ // element is not present yet, otherwise it overwrites. It returns an iterator
+ // to the modified element and a flag indicating whether insertion or
+ // assignment happened.
+ template <class V>
+ std::pair<iterator, bool> insert_or_assign(V&& val) {
+ auto position = lower_bound(GetKeyFromValue()(val));
+
+ if (position == end() || value_comp()(val, *position))
+ return {impl_.body_.emplace(position, std::forward<V>(val)), true};
+
+ *position = std::forward<V>(val);
+ return {position, false};
+ }
+
+ // This method is similar to insert_or_assign, with the following differences:
+ // - Instead of searching [begin(), end()) it only searches [first, last).
+ // - In case no equivalent element is found, val is appended to the end of the
+ // underlying body and an iterator to the next bigger element in [first,
+ // last) is returned.
+ template <class V>
+ std::pair<iterator, bool> append_or_assign(iterator first,
+ iterator last,
+ V&& val) {
+ auto position = std::lower_bound(first, last, val, value_comp());
+
+ if (position == last || value_comp()(val, *position)) {
+ // emplace_back might invalidate position, which is why distance needs to
+ // be cached.
+ const difference_type distance = std::distance(begin(), position);
+ impl_.body_.emplace_back(std::forward<V>(val));
+ return {std::next(begin(), distance), true};
+ }
+
+ *position = std::forward<V>(val);
+ return {position, false};
+ }
+
+ // This method is similar to insert, with the following differences:
+ // - Instead of searching [begin(), end()) it only searches [first, last).
+ // - In case no equivalent element is found, val is appended to the end of the
+ // underlying body and an iterator to the next bigger element in [first,
+ // last) is returned.
+ template <class V>
+ std::pair<iterator, bool> append_unique(iterator first,
+ iterator last,
+ V&& val) {
+ auto position = std::lower_bound(first, last, val, value_comp());
+
+ if (position == last || value_comp()(val, *position)) {
+ // emplace_back might invalidate position, which is why distance needs to
+ // be cached.
+ const difference_type distance = std::distance(begin(), position);
+ impl_.body_.emplace_back(std::forward<V>(val));
+ return {std::next(begin(), distance), true};
+ }
+
+ return {position, false};
+ }
+
+ void sort_and_unique(iterator first,
+ iterator last,
+ FlatContainerDupes dupes) {
+ // Preserve stability for the unique code below.
+ std::stable_sort(first, last, impl_.get_value_comp());
+
+ auto comparator = [this](const value_type& lhs, const value_type& rhs) {
+ // lhs is already <= rhs due to sort, therefore
+ // !(lhs < rhs) <=> lhs == rhs.
+ return !impl_.get_value_comp()(lhs, rhs);
+ };
+
+ iterator erase_after;
+ switch (dupes) {
+ case KEEP_FIRST_OF_DUPES:
+ erase_after = std::unique(first, last, comparator);
+ break;
+ case KEEP_LAST_OF_DUPES:
+ erase_after = LastUnique(first, last, comparator);
+ break;
+ }
+ erase(erase_after, last);
+ }
+
+ // To support comparators that may not be possible to default-construct, we
+ // have to store an instance of Compare. Using this to store all internal
+ // state of flat_tree and using private inheritance to store compare lets us
+ // take advantage of an empty base class optimization to avoid extra space in
+ // the common case when Compare has no state.
+ struct Impl : private value_compare {
+ Impl() = default;
+
+ template <class Cmp, class... Body>
+ explicit Impl(Cmp&& compare_arg, Body&&... underlying_type_args)
+ : value_compare(std::forward<Cmp>(compare_arg)),
+ body_(std::forward<Body>(underlying_type_args)...) {}
+
+ const value_compare& get_value_comp() const { return *this; }
+ const key_compare& get_key_comp() const { return *this; }
+
+ underlying_type body_;
+ } impl_;
+
+ // If the compare is not transparent we want to construct key_type once.
+ template <typename K>
+ using KeyTypeOrK = typename std::
+ conditional<IsTransparentCompare<key_compare>::value, K, key_type>::type;
+};
+
+// ----------------------------------------------------------------------------
+// Lifetime.
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::flat_tree() = default;
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::flat_tree(
+ const KeyCompare& comp)
+ : impl_(comp) {}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <class InputIterator>
+flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::flat_tree(
+ InputIterator first,
+ InputIterator last,
+ FlatContainerDupes dupe_handling,
+ const KeyCompare& comp)
+ : impl_(comp, first, last) {
+ sort_and_unique(begin(), end(), dupe_handling);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::flat_tree(
+ const flat_tree&) = default;
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::flat_tree(
+ std::vector<value_type> items,
+ FlatContainerDupes dupe_handling,
+ const KeyCompare& comp)
+ : impl_(comp, std::move(items)) {
+ sort_and_unique(begin(), end(), dupe_handling);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::flat_tree(
+ std::initializer_list<value_type> ilist,
+ FlatContainerDupes dupe_handling,
+ const KeyCompare& comp)
+ : flat_tree(std::begin(ilist), std::end(ilist), dupe_handling, comp) {}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::~flat_tree() = default;
+
+// ----------------------------------------------------------------------------
+// Assignments.
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::operator=(
+ const flat_tree&) -> flat_tree& = default;
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::operator=(flat_tree &&)
+ -> flat_tree& = default;
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::operator=(
+ std::initializer_list<value_type> ilist) -> flat_tree& {
+ impl_.body_ = ilist;
+ sort_and_unique(begin(), end(), KEEP_FIRST_OF_DUPES);
+ return *this;
+}
+
+// ----------------------------------------------------------------------------
+// Memory management.
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+void flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::reserve(
+ size_type new_capacity) {
+ impl_.body_.reserve(new_capacity);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::capacity() const
+ -> size_type {
+ return impl_.body_.capacity();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+void flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::shrink_to_fit() {
+ impl_.body_.shrink_to_fit();
+}
+
+// ----------------------------------------------------------------------------
+// Size management.
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+void flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::clear() {
+ impl_.body_.clear();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::size() const
+ -> size_type {
+ return impl_.body_.size();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::max_size() const
+ -> size_type {
+ return impl_.body_.max_size();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+bool flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::empty() const {
+ return impl_.body_.empty();
+}
+
+// ----------------------------------------------------------------------------
+// Iterators.
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::begin() -> iterator {
+ return impl_.body_.begin();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::begin() const
+ -> const_iterator {
+ return impl_.body_.begin();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::cbegin() const
+ -> const_iterator {
+ return impl_.body_.cbegin();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::end() -> iterator {
+ return impl_.body_.end();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::end() const
+ -> const_iterator {
+ return impl_.body_.end();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::cend() const
+ -> const_iterator {
+ return impl_.body_.cend();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::rbegin()
+ -> reverse_iterator {
+ return impl_.body_.rbegin();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::rbegin() const
+ -> const_reverse_iterator {
+ return impl_.body_.rbegin();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::crbegin() const
+ -> const_reverse_iterator {
+ return impl_.body_.crbegin();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::rend()
+ -> reverse_iterator {
+ return impl_.body_.rend();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::rend() const
+ -> const_reverse_iterator {
+ return impl_.body_.rend();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::crend() const
+ -> const_reverse_iterator {
+ return impl_.body_.crend();
+}
+
+// ----------------------------------------------------------------------------
+// Insert operations.
+//
+// Currently we use position_hint the same way as eastl or boost:
+// https://github.com/electronicarts/EASTL/blob/master/include/EASTL/vector_set.h#L493
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::insert(
+ const value_type& val) -> std::pair<iterator, bool> {
+ return emplace_key_args(GetKeyFromValue()(val), val);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::insert(
+ value_type&& val) -> std::pair<iterator, bool> {
+ return emplace_key_args(GetKeyFromValue()(val), std::move(val));
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::insert(
+ const_iterator position_hint,
+ const value_type& val) -> iterator {
+ return emplace_hint_key_args(position_hint, GetKeyFromValue()(val), val)
+ .first;
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::insert(
+ const_iterator position_hint,
+ value_type&& val) -> iterator {
+ return emplace_hint_key_args(position_hint, GetKeyFromValue()(val),
+ std::move(val))
+ .first;
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <class InputIterator>
+void flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::insert(
+ InputIterator first,
+ InputIterator last,
+ FlatContainerDupes dupes) {
+ if (first == last)
+ return;
+
+ // Cache results whether existing elements should be overwritten and whether
+ // inserting new elements happens immediately or will be done in a batch.
+ const bool overwrite_existing = dupes == KEEP_LAST_OF_DUPES;
+ const bool insert_inplace =
+ is_multipass<InputIterator>() && std::next(first) == last;
+
+ if (insert_inplace) {
+ if (overwrite_existing) {
+ for (; first != last; ++first)
+ insert_or_assign(*first);
+ } else
+ std::copy(first, last, std::inserter(*this, end()));
+ return;
+ }
+
+ // Provide a convenience lambda to obtain an iterator pointing past the last
+ // old element. This needs to be dymanic due to possible re-allocations.
+ const size_type original_size = size();
+ auto middle = [this, original_size]() {
+ return std::next(begin(), original_size);
+ };
+
+ // For batch updates initialize the first insertion point.
+ difference_type pos_first_new = original_size;
+
+ // Loop over the input range while appending new values and overwriting
+ // existing ones, if applicable. Keep track of the first insertion point.
+ if (overwrite_existing) {
+ for (; first != last; ++first) {
+ std::pair<iterator, bool> result =
+ append_or_assign(begin(), middle(), *first);
+ if (result.second) {
+ pos_first_new =
+ std::min(pos_first_new, std::distance(begin(), result.first));
+ }
+ }
+ } else {
+ for (; first != last; ++first) {
+ std::pair<iterator, bool> result =
+ append_unique(begin(), middle(), *first);
+ if (result.second) {
+ pos_first_new =
+ std::min(pos_first_new, std::distance(begin(), result.first));
+ }
+ }
+ }
+
+ // The new elements might be unordered and contain duplicates, so post-process
+ // the just inserted elements and merge them with the rest, inserting them at
+ // the previously found spot.
+ sort_and_unique(middle(), end(), dupes);
+ std::inplace_merge(std::next(begin(), pos_first_new), middle(), end(),
+ value_comp());
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <class... Args>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::emplace(Args&&... args)
+ -> std::pair<iterator, bool> {
+ return insert(value_type(std::forward<Args>(args)...));
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <class... Args>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::emplace_hint(
+ const_iterator position_hint,
+ Args&&... args) -> iterator {
+ return insert(position_hint, value_type(std::forward<Args>(args)...));
+}
+
+// ----------------------------------------------------------------------------
+// Erase operations.
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::erase(
+ iterator position) -> iterator {
+ return impl_.body_.erase(position);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::erase(
+ const_iterator position) -> iterator {
+ return impl_.body_.erase(position);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::erase(const K& val)
+ -> size_type {
+ auto eq_range = equal_range(val);
+ auto res = std::distance(eq_range.first, eq_range.second);
+ erase(eq_range.first, eq_range.second);
+ return res;
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::erase(
+ const_iterator first,
+ const_iterator last) -> iterator {
+ return impl_.body_.erase(first, last);
+}
+
+// ----------------------------------------------------------------------------
+// Comparators.
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::key_comp() const
+ -> key_compare {
+ return impl_.get_key_comp();
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::value_comp() const
+ -> value_compare {
+ return impl_.get_value_comp();
+}
+
+// ----------------------------------------------------------------------------
+// Search operations.
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::count(
+ const K& key) const -> size_type {
+ auto eq_range = equal_range(key);
+ return std::distance(eq_range.first, eq_range.second);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::find(const K& key)
+ -> iterator {
+ return const_cast_it(as_const().find(key));
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::find(
+ const K& key) const -> const_iterator {
+ auto eq_range = equal_range(key);
+ return (eq_range.first == eq_range.second) ? end() : eq_range.first;
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::equal_range(
+ const K& key) -> std::pair<iterator, iterator> {
+ auto res = as_const().equal_range(key);
+ return {const_cast_it(res.first), const_cast_it(res.second)};
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::equal_range(
+ const K& key) const -> std::pair<const_iterator, const_iterator> {
+ auto lower = lower_bound(key);
+
+ GetKeyFromValue extractor;
+ if (lower == end() || impl_.get_key_comp()(key, extractor(*lower)))
+ return {lower, lower};
+
+ return {lower, std::next(lower)};
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::lower_bound(
+ const K& key) -> iterator {
+ return const_cast_it(as_const().lower_bound(key));
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::lower_bound(
+ const K& key) const -> const_iterator {
+ static_assert(std::is_convertible<const KeyTypeOrK<K>&, const K&>::value,
+ "Requested type cannot be bound to the container's key_type "
+ "which is required for a non-transparent compare.");
+
+ const KeyTypeOrK<K>& key_ref = key;
+
+ KeyValueCompare key_value(impl_.get_key_comp());
+ return std::lower_bound(begin(), end(), key_ref, key_value);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::upper_bound(
+ const K& key) -> iterator {
+ return const_cast_it(as_const().upper_bound(key));
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <typename K>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::upper_bound(
+ const K& key) const -> const_iterator {
+ static_assert(std::is_convertible<const KeyTypeOrK<K>&, const K&>::value,
+ "Requested type cannot be bound to the container's key_type "
+ "which is required for a non-transparent compare.");
+
+ const KeyTypeOrK<K>& key_ref = key;
+
+ KeyValueCompare key_value(impl_.get_key_comp());
+ return std::upper_bound(begin(), end(), key_ref, key_value);
+}
+
+// ----------------------------------------------------------------------------
+// General operations.
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+void flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::swap(
+ flat_tree& other) noexcept {
+ std::swap(impl_, other.impl_);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <class... Args>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::unsafe_emplace(
+ const_iterator position,
+ Args&&... args) -> iterator {
+ return impl_.body_.emplace(position, std::forward<Args>(args)...);
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <class K, class... Args>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::emplace_key_args(
+ const K& key,
+ Args&&... args) -> std::pair<iterator, bool> {
+ auto lower = lower_bound(key);
+ if (lower == end() || key_comp()(key, GetKeyFromValue()(*lower)))
+ return {unsafe_emplace(lower, std::forward<Args>(args)...), true};
+ return {lower, false};
+}
+
+template <class Key, class Value, class GetKeyFromValue, class KeyCompare>
+template <class K, class... Args>
+auto flat_tree<Key, Value, GetKeyFromValue, KeyCompare>::emplace_hint_key_args(
+ const_iterator hint,
+ const K& key,
+ Args&&... args) -> std::pair<iterator, bool> {
+ GetKeyFromValue extractor;
+ if ((hint == begin() || key_comp()(extractor(*std::prev(hint)), key))) {
+ if (hint == end() || key_comp()(key, extractor(*hint))) {
+ // *(hint - 1) < key < *hint => key did not exist and hint is correct.
+ return {unsafe_emplace(hint, std::forward<Args>(args)...), true};
+ }
+ if (!key_comp()(extractor(*hint), key)) {
+ // key == *hint => no-op, return correct hint.
+ return {const_cast_it(hint), false};
+ }
+ }
+ // hint was not helpful, dispatch to hintless version.
+ return emplace_key_args(key, std::forward<Args>(args)...);
+}
+
+// For containers like sets, the key is the same as the value. This implements
+// the GetKeyFromValue template parameter to flat_tree for this case.
+template <class Key>
+struct GetKeyFromValueIdentity {
+ const Key& operator()(const Key& k) const { return k; }
+};
+
+} // namespace internal
+
+// ----------------------------------------------------------------------------
+// Free functions.
+
+// Erases all elements that match predicate. It has O(size) complexity.
+template <class Key,
+ class Value,
+ class GetKeyFromValue,
+ class KeyCompare,
+ typename Predicate>
+void EraseIf(base::internal::flat_tree<Key, Value, GetKeyFromValue, KeyCompare>&
+ container,
+ Predicate pred) {
+ container.erase(std::remove_if(container.begin(), container.end(), pred),
+ container.end());
+}
+
+} // namespace base
+
+#endif // BASE_CONTAINERS_FLAT_TREE_H_
diff --git a/base/containers/linked_list.h b/base/containers/linked_list.h
index 41461ff365..a913badb88 100644
--- a/base/containers/linked_list.h
+++ b/base/containers/linked_list.h
@@ -84,10 +84,24 @@ namespace base {
template <typename T>
class LinkNode {
public:
- LinkNode() : previous_(NULL), next_(NULL) {}
+ LinkNode() : previous_(nullptr), next_(nullptr) {}
LinkNode(LinkNode<T>* previous, LinkNode<T>* next)
: previous_(previous), next_(next) {}
+ LinkNode(LinkNode<T>&& rhs) {
+ next_ = rhs.next_;
+ rhs.next_ = nullptr;
+ previous_ = rhs.previous_;
+ rhs.previous_ = nullptr;
+
+ // If the node belongs to a list, next_ and previous_ are both non-null.
+ // Otherwise, they are both null.
+ if (next_) {
+ next_->previous_ = this;
+ previous_->next_ = this;
+ }
+ }
+
// Insert |this| into the linked list, before |e|.
void InsertBefore(LinkNode<T>* e) {
this->next_ = e;
@@ -108,10 +122,10 @@ class LinkNode {
void RemoveFromList() {
this->previous_->next_ = this->next_;
this->next_->previous_ = this->previous_;
- // next() and previous() return non-NULL if and only this node is not in any
+ // next() and previous() return non-null if and only this node is not in any
// list.
- this->next_ = NULL;
- this->previous_ = NULL;
+ this->next_ = nullptr;
+ this->previous_ = nullptr;
}
LinkNode<T>* previous() const {
diff --git a/base/containers/mru_cache.h b/base/containers/mru_cache.h
index 7c684a9690..4a9f44e868 100644
--- a/base/containers/mru_cache.h
+++ b/base/containers/mru_cache.h
@@ -29,6 +29,14 @@
#include "base/macros.h"
namespace base {
+namespace trace_event {
+namespace internal {
+
+template <class MruCacheType>
+size_t DoEstimateMemoryUsageForMruCache(const MruCacheType&);
+
+} // namespace internal
+} // namespace trace_event
// MRUCacheBase ----------------------------------------------------------------
@@ -74,7 +82,7 @@ class MRUCacheBase {
// can pass NO_AUTO_EVICT to not restrict the cache size.
explicit MRUCacheBase(size_type max_size) : max_size_(max_size) {}
- virtual ~MRUCacheBase() {}
+ virtual ~MRUCacheBase() = default;
size_type max_size() const { return max_size_; }
@@ -97,8 +105,8 @@ class MRUCacheBase {
ShrinkToSize(max_size_ - 1);
}
- ordering_.push_front(value_type(key, std::forward<Payload>(payload)));
- index_.insert(std::make_pair(key, ordering_.begin()));
+ ordering_.emplace_front(key, std::forward<Payload>(payload));
+ index_.emplace(key, ordering_.begin());
return ordering_.begin();
}
@@ -195,6 +203,10 @@ class MRUCacheBase {
bool empty() const { return ordering_.empty(); }
private:
+ template <class MruCacheType>
+ friend size_t trace_event::internal::DoEstimateMemoryUsageForMruCache(
+ const MruCacheType&);
+
PayloadList ordering_;
KeyIndex index_;
@@ -218,7 +230,7 @@ class MRUCache : public MRUCacheBase<KeyType, PayloadType, CompareType> {
// See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT.
explicit MRUCache(typename ParentType::size_type max_size)
: ParentType(max_size) {}
- virtual ~MRUCache() {}
+ virtual ~MRUCache() = default;
private:
DISALLOW_COPY_AND_ASSIGN(MRUCache);
@@ -245,7 +257,7 @@ class HashingMRUCache
// See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT.
explicit HashingMRUCache(typename ParentType::size_type max_size)
: ParentType(max_size) {}
- virtual ~HashingMRUCache() {}
+ virtual ~HashingMRUCache() = default;
private:
DISALLOW_COPY_AND_ASSIGN(HashingMRUCache);
diff --git a/base/containers/queue.h b/base/containers/queue.h
new file mode 100644
index 0000000000..b5bc5c36e5
--- /dev/null
+++ b/base/containers/queue.h
@@ -0,0 +1,23 @@
+// 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_CONTAINERS_QUEUE_H_
+#define BASE_CONTAINERS_QUEUE_H_
+
+#include <queue>
+
+#include "base/containers/circular_deque.h"
+
+namespace base {
+
+// Provides a definition of base::queue that's like std::queue but uses a
+// base::circular_deque instead of std::deque. Since std::queue is just a
+// wrapper for an underlying type, we can just provide a typedef for it that
+// defaults to the base circular_deque.
+template <class T, class Container = circular_deque<T>>
+using queue = std::queue<T, Container>;
+
+} // namespace base
+
+#endif // BASE_CONTAINERS_QUEUE_H_
diff --git a/base/containers/ring_buffer.h b/base/containers/ring_buffer.h
new file mode 100644
index 0000000000..ca4a48ddc9
--- /dev/null
+++ b/base/containers/ring_buffer.h
@@ -0,0 +1,133 @@
+// 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_CONTAINERS_RING_BUFFER_H_
+#define BASE_CONTAINERS_RING_BUFFER_H_
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+
+namespace base {
+
+// base::RingBuffer uses a fixed-size array, unlike base::circular_deque and
+// std::deque, and so, one can access only the last |kSize| elements. Also, you
+// can add elements to the front and read/modify random elements, but cannot
+// remove elements from the back. Therefore, it does not have a |Size| method,
+// only |BufferSize|, which is a constant, and |CurrentIndex|, which is the
+// number of elements added so far.
+//
+// If the above is sufficient for your use case, base::RingBuffer should be more
+// efficient than base::circular_deque.
+template <typename T, size_t kSize>
+class RingBuffer {
+ public:
+ RingBuffer() : current_index_(0) {}
+
+ size_t BufferSize() const { return kSize; }
+
+ size_t CurrentIndex() const { return current_index_; }
+
+ // Returns true if a value was saved to index |n|.
+ bool IsFilledIndex(size_t n) const {
+ return IsFilledIndexByBufferIndex(BufferIndex(n));
+ }
+
+ // Returns the element at index |n| (% |kSize|).
+ //
+ // n = 0 returns the oldest value and
+ // n = bufferSize() - 1 returns the most recent value.
+ const T& ReadBuffer(size_t n) const {
+ const size_t buffer_index = BufferIndex(n);
+ CHECK(IsFilledIndexByBufferIndex(buffer_index));
+ return buffer_[buffer_index];
+ }
+
+ T* MutableReadBuffer(size_t n) {
+ const size_t buffer_index = BufferIndex(n);
+ CHECK(IsFilledIndexByBufferIndex(buffer_index));
+ return &buffer_[buffer_index];
+ }
+
+ void SaveToBuffer(const T& value) {
+ buffer_[BufferIndex(0)] = value;
+ current_index_++;
+ }
+
+ void Clear() { current_index_ = 0; }
+
+ // Iterator has const access to the RingBuffer it got retrieved from.
+ class Iterator {
+ public:
+ size_t index() const { return index_; }
+
+ const T* operator->() const { return &buffer_.ReadBuffer(index_); }
+ const T* operator*() const { return &buffer_.ReadBuffer(index_); }
+
+ Iterator& operator++() {
+ index_++;
+ if (index_ == kSize)
+ out_of_range_ = true;
+ return *this;
+ }
+
+ Iterator& operator--() {
+ if (index_ == 0)
+ out_of_range_ = true;
+ index_--;
+ return *this;
+ }
+
+ operator bool() const {
+ return !out_of_range_ && buffer_.IsFilledIndex(index_);
+ }
+
+ private:
+ Iterator(const RingBuffer<T, kSize>& buffer, size_t index)
+ : buffer_(buffer), index_(index), out_of_range_(false) {}
+
+ const RingBuffer<T, kSize>& buffer_;
+ size_t index_;
+ bool out_of_range_;
+
+ friend class RingBuffer<T, kSize>;
+ };
+
+ // Returns an Iterator pointing to the oldest value in the buffer.
+ // Example usage (iterate from oldest to newest value):
+ // for (RingBuffer<T, kSize>::Iterator it = ring_buffer.Begin(); it; ++it) {}
+ Iterator Begin() const {
+ if (current_index_ < kSize)
+ return Iterator(*this, kSize - current_index_);
+ return Iterator(*this, 0);
+ }
+
+ // Returns an Iterator pointing to the newest value in the buffer.
+ // Example usage (iterate backwards from newest to oldest value):
+ // for (RingBuffer<T, kSize>::Iterator it = ring_buffer.End(); it; --it) {}
+ Iterator End() const { return Iterator(*this, kSize - 1); }
+
+ private:
+ inline size_t BufferIndex(size_t n) const {
+ return (current_index_ + n) % kSize;
+ }
+
+ // This specialization of |IsFilledIndex| is a micro-optimization that enables
+ // us to do e.g. `CHECK(IsFilledIndex(n))` without calling |BufferIndex|
+ // twice. Since |BufferIndex| involves a % operation, it's not quite free at a
+ // micro-scale.
+ inline bool IsFilledIndexByBufferIndex(size_t buffer_index) const {
+ return buffer_index < current_index_;
+ }
+
+ T buffer_[kSize];
+ size_t current_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(RingBuffer);
+};
+
+} // namespace base
+
+#endif // BASE_CONTAINERS_RING_BUFFER_H_
diff --git a/base/containers/small_map.h b/base/containers/small_map.h
index 2945d58769..495332fc35 100644
--- a/base/containers/small_map.h
+++ b/base/containers/small_map.h
@@ -8,68 +8,57 @@
#include <stddef.h>
#include <map>
+#include <new>
#include <string>
#include <utility>
#include "base/containers/hash_tables.h"
#include "base/logging.h"
-#include "base/memory/manual_constructor.h"
namespace base {
-// An STL-like associative container which starts out backed by a simple
-// array but switches to some other container type if it grows beyond a
-// fixed size.
+// small_map is a container with a std::map-like interface. It starts out
+// backed by a unsorted array but switches to some other container type if it
+// grows beyond this fixed size.
//
-// WHAT TYPE OF MAP SHOULD YOU USE?
-// --------------------------------
+// Please see //base/containers/README.md for an overview of which container
+// to select.
//
-// - std::map should be the default if you're not sure, since it's the most
-// difficult to mess up. Generally this is backed by a red-black tree. It
-// will generate a lot of code (if you use a common key type like int or
-// string the linker will probably emiminate the duplicates). It will
-// do heap allocations for each element.
+// PROS
//
-// - If you only ever keep a couple of items and have very simple usage,
-// consider whether a using a vector and brute-force searching it will be
-// the most efficient. It's not a lot of generated code (less than a
-// red-black tree if your key is "weird" and not eliminated as duplicate of
-// something else) and will probably be faster and do fewer heap allocations
-// than std::map if you have just a couple of items.
+// - Good memory locality and low overhead for smaller maps.
+// - Handles large maps without the degenerate performance of flat_map.
//
-// - base::hash_map should be used if you need O(1) lookups. It may waste
-// space in the hash table, and it can be easy to write correct-looking
-// code with the default hash function being wrong or poorly-behaving.
+// CONS
//
-// - SmallMap combines the performance benefits of the brute-force-searched
-// vector for small cases (no extra heap allocations), but can efficiently
-// fall back if you end up adding many items. It will generate more code
-// than std::map (at least 160 bytes for operator[]) which is bad if you
-// have a "weird" key where map functions can't be
-// duplicate-code-eliminated. If you have a one-off key and aren't in
-// performance-critical code, this bloat may negate some of the benefits and
-// you should consider on of the other options.
+// - Larger code size than the alternatives.
//
-// SmallMap will pick up the comparator from the underlying map type. In
-// std::map (and in MSVC additionally hash_map) only a "less" operator is
-// defined, which requires us to do two comparisons per element when doing the
-// brute-force search in the simple array.
+// IMPORTANT NOTES
+//
+// - Iterators are invalidated across mutations.
+//
+// DETAILS
+//
+// base::small_map will pick up the comparator from the underlying map type. In
+// std::map only a "less" operator is defined, which requires us to do two
+// comparisons per element when doing the brute-force search in the simple
+// array. std::unordered_map has a key_equal function which will be used.
//
// We define default overrides for the common map types to avoid this
// double-compare, but you should be aware of this if you use your own
-// operator< for your map and supply yor own version of == to the SmallMap.
+// operator< for your map and supply yor own version of == to the small_map.
// You can use regular operator== by just doing:
//
-// base::SmallMap<std::map<MyKey, MyValue>, 4, std::equal_to<KyKey> >
+// base::small_map<std::map<MyKey, MyValue>, 4, std::equal_to<KyKey>>
//
//
// USAGE
// -----
//
// NormalMap: The map type to fall back to. This also defines the key
-// and value types for the SmallMap.
+// and value types for the small_map.
// kArraySize: The size of the initial array of results. This will be
-// allocated with the SmallMap object rather than separately on
+// allocated with the small_map object rather than separately on
// the heap. Once the map grows beyond this size, the map type
// will be used instead.
// EqualKey: A functor which tests two keys for equality. If the wrapped
@@ -77,16 +66,15 @@ namespace base {
// be used by default. If the wrapped map type has a strict weak
// ordering "key_compare" (std::map does), that will be used to
// implement equality by default.
-// MapInit: A functor that takes a ManualConstructor<NormalMap>* and uses it to
-// initialize the map. This functor will be called at most once per
-// SmallMap, when the map exceeds the threshold of kArraySize and we
-// are about to copy values from the array to the map. The functor
-// *must* call one of the Init() methods provided by
-// ManualConstructor, since after it runs we assume that the NormalMap
-// has been initialized.
+// MapInit: A functor that takes a NormalMap* and uses it to initialize the map.
+// This functor will be called at most once per small_map, when the map
+// exceeds the threshold of kArraySize and we are about to copy values
+// from the array to the map. The functor *must* initialize the
+// NormalMap* argument with placement new, since after it runs we
+// assume that the NormalMap has been initialized.
//
// example:
-// base::SmallMap< std::map<string, int> > days;
+// base::small_map<std::map<string, int>> days;
// days["sunday" ] = 0;
// days["monday" ] = 1;
// days["tuesday" ] = 2;
@@ -94,18 +82,13 @@ namespace base {
// days["thursday" ] = 4;
// days["friday" ] = 5;
// days["saturday" ] = 6;
-//
-// You should assume that SmallMap might invalidate all the iterators
-// on any call to erase(), insert() and operator[].
namespace internal {
template <typename NormalMap>
-class SmallMapDefaultInit {
+class small_map_default_init {
public:
- void operator()(ManualConstructor<NormalMap>* map) const {
- map->Init();
- }
+ void operator()(NormalMap* map) const { new (map) NormalMap(); }
};
// has_key_equal<M>::value is true iff there exists a type M::key_equal. This is
@@ -151,7 +134,7 @@ struct select_equal_key {
// If we switch to using std::unordered_map for base::hash_map, then the
// hash_map specialization can be removed.
template <typename KeyType, typename ValueType>
-struct select_equal_key< std::map<KeyType, ValueType>, false> {
+struct select_equal_key<std::map<KeyType, ValueType>, false> {
struct equal_key {
bool operator()(const KeyType& left, const KeyType& right) {
return left == right;
@@ -159,7 +142,7 @@ struct select_equal_key< std::map<KeyType, ValueType>, false> {
};
};
template <typename KeyType, typename ValueType>
-struct select_equal_key< base::hash_map<KeyType, ValueType>, false> {
+struct select_equal_key<base::hash_map<KeyType, ValueType>, false> {
struct equal_key {
bool operator()(const KeyType& left, const KeyType& right) {
return left == right;
@@ -178,12 +161,11 @@ struct select_equal_key<M, true> {
template <typename NormalMap,
int kArraySize = 4,
- typename EqualKey =
- typename internal::select_equal_key<
- NormalMap,
- internal::has_key_equal<NormalMap>::value>::equal_key,
- typename MapInit = internal::SmallMapDefaultInit<NormalMap> >
-class SmallMap {
+ typename EqualKey = typename internal::select_equal_key<
+ NormalMap,
+ internal::has_key_equal<NormalMap>::value>::equal_key,
+ typename MapInit = internal::small_map_default_init<NormalMap>>
+class small_map {
// We cannot rely on the compiler to reject array of size 0. In
// particular, gcc 2.95.3 does it but later versions allow 0-length
// arrays. Therefore, we explicitly reject non-positive kArraySize
@@ -197,16 +179,16 @@ class SmallMap {
typedef typename NormalMap::value_type value_type;
typedef EqualKey key_equal;
- SmallMap() : size_(0), functor_(MapInit()) {}
+ small_map() : size_(0), functor_(MapInit()) {}
- explicit SmallMap(const MapInit& functor) : size_(0), functor_(functor) {}
+ explicit small_map(const MapInit& functor) : size_(0), functor_(functor) {}
// Allow copy-constructor and assignment, since STL allows them too.
- SmallMap(const SmallMap& src) {
+ small_map(const small_map& src) {
// size_ and functor_ are initted in InitFrom()
InitFrom(src);
}
- void operator=(const SmallMap& src) {
+ void operator=(const small_map& src) {
if (&src == this) return;
// This is not optimal. If src and dest are both using the small
@@ -216,9 +198,7 @@ class SmallMap {
Destroy();
InitFrom(src);
}
- ~SmallMap() {
- Destroy();
- }
+ ~small_map() { Destroy(); }
class const_iterator;
@@ -260,7 +240,7 @@ class SmallMap {
}
inline value_type* operator->() const {
if (array_iter_ != NULL) {
- return array_iter_->get();
+ return array_iter_;
} else {
return hash_iter_.operator->();
}
@@ -268,7 +248,7 @@ class SmallMap {
inline value_type& operator*() const {
if (array_iter_ != NULL) {
- return *array_iter_->get();
+ return *array_iter_;
} else {
return *hash_iter_;
}
@@ -290,14 +270,13 @@ class SmallMap {
bool operator!=(const const_iterator& other) const;
private:
- friend class SmallMap;
+ friend class small_map;
friend class const_iterator;
- inline explicit iterator(ManualConstructor<value_type>* init)
- : array_iter_(init) {}
+ inline explicit iterator(value_type* init) : array_iter_(init) {}
inline explicit iterator(const typename NormalMap::iterator& init)
: array_iter_(NULL), hash_iter_(init) {}
- ManualConstructor<value_type>* array_iter_;
+ value_type* array_iter_;
typename NormalMap::iterator hash_iter_;
};
@@ -345,7 +324,7 @@ class SmallMap {
inline const value_type* operator->() const {
if (array_iter_ != NULL) {
- return array_iter_->get();
+ return array_iter_;
} else {
return hash_iter_.operator->();
}
@@ -353,7 +332,7 @@ class SmallMap {
inline const value_type& operator*() const {
if (array_iter_ != NULL) {
- return *array_iter_->get();
+ return *array_iter_;
} else {
return *hash_iter_;
}
@@ -372,15 +351,14 @@ class SmallMap {
}
private:
- friend class SmallMap;
- inline explicit const_iterator(
- const ManualConstructor<value_type>* init)
- : array_iter_(init) {}
+ friend class small_map;
+ inline explicit const_iterator(const value_type* init)
+ : array_iter_(init) {}
inline explicit const_iterator(
const typename NormalMap::const_iterator& init)
: array_iter_(NULL), hash_iter_(init) {}
- const ManualConstructor<value_type>* array_iter_;
+ const value_type* array_iter_;
typename NormalMap::const_iterator hash_iter_;
};
@@ -388,7 +366,7 @@ class SmallMap {
key_equal compare;
if (size_ >= 0) {
for (int i = 0; i < size_; i++) {
- if (compare(array_[i]->first, key)) {
+ if (compare(array_[i].first, key)) {
return iterator(array_ + i);
}
}
@@ -402,7 +380,7 @@ class SmallMap {
key_equal compare;
if (size_ >= 0) {
for (int i = 0; i < size_; i++) {
- if (compare(array_[i]->first, key)) {
+ if (compare(array_[i].first, key)) {
return const_iterator(array_ + i);
}
}
@@ -420,19 +398,19 @@ class SmallMap {
// operator[] searches backwards, favoring recently-added
// elements.
for (int i = size_-1; i >= 0; --i) {
- if (compare(array_[i]->first, key)) {
- return array_[i]->second;
+ if (compare(array_[i].first, key)) {
+ return array_[i].second;
}
}
if (size_ == kArraySize) {
ConvertToRealMap();
- return (*map_)[key];
+ return map_[key];
} else {
- array_[size_].Init(key, data_type());
- return array_[size_++]->second;
+ new (&array_[size_]) value_type(key, data_type());
+ return array_[size_++].second;
}
} else {
- return (*map_)[key];
+ return map_[key];
}
}
@@ -442,20 +420,20 @@ class SmallMap {
if (size_ >= 0) {
for (int i = 0; i < size_; i++) {
- if (compare(array_[i]->first, x.first)) {
+ if (compare(array_[i].first, x.first)) {
return std::make_pair(iterator(array_ + i), false);
}
}
if (size_ == kArraySize) {
ConvertToRealMap(); // Invalidates all iterators!
- std::pair<typename NormalMap::iterator, bool> ret = map_->insert(x);
+ std::pair<typename NormalMap::iterator, bool> ret = map_.insert(x);
return std::make_pair(iterator(ret.first), ret.second);
} else {
- array_[size_].Init(x);
+ new (&array_[size_]) value_type(x);
return std::make_pair(iterator(array_ + size_++), true);
}
} else {
- std::pair<typename NormalMap::iterator, bool> ret = map_->insert(x);
+ std::pair<typename NormalMap::iterator, bool> ret = map_.insert(x);
return std::make_pair(iterator(ret.first), ret.second);
}
}
@@ -469,18 +447,46 @@ class SmallMap {
}
}
+ // Invalidates iterators.
+ template <typename... Args>
+ std::pair<iterator, bool> emplace(Args&&... args) {
+ key_equal compare;
+
+ if (size_ >= 0) {
+ value_type x(std::forward<Args>(args)...);
+ for (int i = 0; i < size_; i++) {
+ if (compare(array_[i].first, x.first)) {
+ return std::make_pair(iterator(array_ + i), false);
+ }
+ }
+ if (size_ == kArraySize) {
+ ConvertToRealMap(); // Invalidates all iterators!
+ std::pair<typename NormalMap::iterator, bool> ret =
+ map_.emplace(std::move(x));
+ return std::make_pair(iterator(ret.first), ret.second);
+ } else {
+ new (&array_[size_]) value_type(std::move(x));
+ return std::make_pair(iterator(array_ + size_++), true);
+ }
+ } else {
+ std::pair<typename NormalMap::iterator, bool> ret =
+ map_.emplace(std::forward<Args>(args)...);
+ return std::make_pair(iterator(ret.first), ret.second);
+ }
+ }
+
iterator begin() {
if (size_ >= 0) {
return iterator(array_);
} else {
- return iterator(map_->begin());
+ return iterator(map_.begin());
}
}
const_iterator begin() const {
if (size_ >= 0) {
return const_iterator(array_);
} else {
- return const_iterator(map_->begin());
+ return const_iterator(map_.begin());
}
}
@@ -488,24 +494,24 @@ class SmallMap {
if (size_ >= 0) {
return iterator(array_ + size_);
} else {
- return iterator(map_->end());
+ return iterator(map_.end());
}
}
const_iterator end() const {
if (size_ >= 0) {
return const_iterator(array_ + size_);
} else {
- return const_iterator(map_->end());
+ return const_iterator(map_.end());
}
}
void clear() {
if (size_ >= 0) {
for (int i = 0; i < size_; i++) {
- array_[i].Destroy();
+ array_[i].~value_type();
}
} else {
- map_.Destroy();
+ map_.~NormalMap();
}
size_ = 0;
}
@@ -514,16 +520,16 @@ class SmallMap {
iterator erase(const iterator& position) {
if (size_ >= 0) {
int i = position.array_iter_ - array_;
- array_[i].Destroy();
+ array_[i].~value_type();
--size_;
if (i != size_) {
- array_[i].InitFromMove(std::move(array_[size_]));
- array_[size_].Destroy();
+ new (&array_[i]) value_type(std::move(array_[size_]));
+ array_[size_].~value_type();
return iterator(array_ + i);
}
return end();
}
- return iterator(map_->erase(position.hash_iter_));
+ return iterator(map_.erase(position.hash_iter_));
}
size_t erase(const key_type& key) {
@@ -541,7 +547,7 @@ class SmallMap {
if (size_ >= 0) {
return static_cast<size_t>(size_);
} else {
- return map_->size();
+ return map_.size();
}
}
@@ -549,7 +555,7 @@ class SmallMap {
if (size_ >= 0) {
return (size_ == 0);
} else {
- return map_->empty();
+ return map_.empty();
}
}
@@ -561,11 +567,11 @@ class SmallMap {
inline NormalMap* map() {
CHECK(UsingFullMap());
- return map_.get();
+ return &map_;
}
inline const NormalMap* map() const {
CHECK(UsingFullMap());
- return map_.get();
+ return &map_;
}
private:
@@ -573,26 +579,28 @@ class SmallMap {
MapInit functor_;
- // We want to call constructors and destructors manually, but we don't
- // want to allocate and deallocate the memory used for them separately.
- // So, we use this crazy ManualConstructor class. Since C++11 it's possible
- // to use objects in unions like this, but the ManualDestructor syntax is
- // a bit better and doesn't have limitations on object type.
- //
- // Since array_ and map_ are mutually exclusive, we'll put them in a
- // union.
+ // We want to call constructors and destructors manually, but we don't want to
+ // allocate and deallocate the memory used for them separately. Since array_
+ // and map_ are mutually exclusive, we'll put them in a union.
union {
- ManualConstructor<value_type> array_[kArraySize];
- ManualConstructor<NormalMap> map_;
+ value_type array_[kArraySize];
+ NormalMap map_;
};
void ConvertToRealMap() {
- // Move the current elements into a temporary array.
- ManualConstructor<value_type> temp_array[kArraySize];
+ // Storage for the elements in the temporary array. This is intentionally
+ // declared as a union to avoid having to default-construct |kArraySize|
+ // elements, only to move construct over them in the initial loop.
+ union Storage {
+ Storage() {}
+ ~Storage() {}
+ value_type array[kArraySize];
+ } temp;
+ // Move the current elements into a temporary array.
for (int i = 0; i < kArraySize; i++) {
- temp_array[i].InitFromMove(std::move(array_[i]));
- array_[i].Destroy();
+ new (&temp.array[i]) value_type(std::move(array_[i]));
+ array_[i].~value_type();
}
// Initialize the map.
@@ -601,47 +609,49 @@ class SmallMap {
// Insert elements into it.
for (int i = 0; i < kArraySize; i++) {
- map_->insert(std::move(*temp_array[i]));
- temp_array[i].Destroy();
+ map_.insert(std::move(temp.array[i]));
+ temp.array[i].~value_type();
}
}
// Helpers for constructors and destructors.
- void InitFrom(const SmallMap& src) {
+ void InitFrom(const small_map& src) {
functor_ = src.functor_;
size_ = src.size_;
if (src.size_ >= 0) {
for (int i = 0; i < size_; i++) {
- array_[i].Init(*src.array_[i]);
+ new (&array_[i]) value_type(src.array_[i]);
}
} else {
functor_(&map_);
- (*map_.get()) = (*src.map_.get());
+ map_ = src.map_;
}
}
void Destroy() {
if (size_ >= 0) {
for (int i = 0; i < size_; i++) {
- array_[i].Destroy();
+ array_[i].~value_type();
}
} else {
- map_.Destroy();
+ map_.~NormalMap();
}
}
};
-template <typename NormalMap, int kArraySize, typename EqualKey,
+template <typename NormalMap,
+ int kArraySize,
+ typename EqualKey,
typename Functor>
-inline bool SmallMap<NormalMap, kArraySize, EqualKey,
- Functor>::iterator::operator==(
- const const_iterator& other) const {
+inline bool small_map<NormalMap, kArraySize, EqualKey, Functor>::iterator::
+operator==(const const_iterator& other) const {
return other == *this;
}
-template <typename NormalMap, int kArraySize, typename EqualKey,
+template <typename NormalMap,
+ int kArraySize,
+ typename EqualKey,
typename Functor>
-inline bool SmallMap<NormalMap, kArraySize, EqualKey,
- Functor>::iterator::operator!=(
- const const_iterator& other) const {
+inline bool small_map<NormalMap, kArraySize, EqualKey, Functor>::iterator::
+operator!=(const const_iterator& other) const {
return other != *this;
}
diff --git a/base/containers/span.h b/base/containers/span.h
new file mode 100644
index 0000000000..f1e000056e
--- /dev/null
+++ b/base/containers/span.h
@@ -0,0 +1,453 @@
+// 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_CONTAINERS_SPAN_H_
+#define BASE_CONTAINERS_SPAN_H_
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <array>
+#include <iterator>
+#include <type_traits>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+
+namespace base {
+
+// [views.constants]
+constexpr size_t dynamic_extent = static_cast<size_t>(-1);
+
+template <typename T, size_t Extent = dynamic_extent>
+class span;
+
+namespace internal {
+
+template <typename T>
+struct IsSpanImpl : std::false_type {};
+
+template <typename T, size_t Extent>
+struct IsSpanImpl<span<T, Extent>> : std::true_type {};
+
+template <typename T>
+using IsSpan = IsSpanImpl<std::decay_t<T>>;
+
+template <typename T>
+struct IsStdArrayImpl : std::false_type {};
+
+template <typename T, size_t N>
+struct IsStdArrayImpl<std::array<T, N>> : std::true_type {};
+
+template <typename T>
+using IsStdArray = IsStdArrayImpl<std::decay_t<T>>;
+
+template <typename T>
+using IsCArray = std::is_array<std::remove_reference_t<T>>;
+
+template <typename From, typename To>
+using IsLegalDataConversion = std::is_convertible<From (*)[], To (*)[]>;
+
+template <typename Container, typename T>
+using ContainerHasConvertibleData = IsLegalDataConversion<
+ std::remove_pointer_t<decltype(base::data(std::declval<Container>()))>,
+ T>;
+
+template <typename Container>
+using ContainerHasIntegralSize =
+ std::is_integral<decltype(base::size(std::declval<Container>()))>;
+
+template <typename From, size_t FromExtent, typename To, size_t ToExtent>
+using EnableIfLegalSpanConversion =
+ std::enable_if_t<(ToExtent == dynamic_extent || ToExtent == FromExtent) &&
+ IsLegalDataConversion<From, To>::value>;
+
+// SFINAE check if Array can be converted to a span<T>.
+template <typename Array, size_t N, typename T, size_t Extent>
+using EnableIfSpanCompatibleArray =
+ std::enable_if_t<(Extent == dynamic_extent || Extent == N) &&
+ ContainerHasConvertibleData<Array, T>::value>;
+
+// SFINAE check if Container can be converted to a span<T>.
+template <typename Container, typename T>
+using EnableIfSpanCompatibleContainer =
+ std::enable_if_t<!internal::IsSpan<Container>::value &&
+ !internal::IsStdArray<Container>::value &&
+ !internal::IsCArray<Container>::value &&
+ ContainerHasConvertibleData<Container, T>::value &&
+ ContainerHasIntegralSize<Container>::value>;
+
+} // namespace internal
+
+// A span is a value type that represents an array of elements of type T. Since
+// it only consists of a pointer to memory with an associated size, it is very
+// light-weight. It is cheap to construct, copy, move and use spans, so that
+// users are encouraged to use it as a pass-by-value parameter. A span does not
+// own the underlying memory, so care must be taken to ensure that a span does
+// not outlive the backing store.
+//
+// span is somewhat analogous to StringPiece, but with arbitrary element types,
+// allowing mutation if T is non-const.
+//
+// span is implicitly convertible from C++ arrays, as well as most [1]
+// container-like types that provide a data() and size() method (such as
+// std::vector<T>). A mutable span<T> can also be implicitly converted to an
+// immutable span<const T>.
+//
+// Consider using a span for functions that take a data pointer and size
+// parameter: it allows the function to still act on an array-like type, while
+// allowing the caller code to be a bit more concise.
+//
+// For read-only data access pass a span<const T>: the caller can supply either
+// a span<const T> or a span<T>, while the callee will have a read-only view.
+// For read-write access a mutable span<T> is required.
+//
+// Without span:
+// Read-Only:
+// // std::string HexEncode(const uint8_t* data, size_t size);
+// std::vector<uint8_t> data_buffer = GenerateData();
+// std::string r = HexEncode(data_buffer.data(), data_buffer.size());
+//
+// Mutable:
+// // ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt, Args...);
+// char str_buffer[100];
+// SafeSNPrintf(str_buffer, sizeof(str_buffer), "Pi ~= %lf", 3.14);
+//
+// With span:
+// Read-Only:
+// // std::string HexEncode(base::span<const uint8_t> data);
+// std::vector<uint8_t> data_buffer = GenerateData();
+// std::string r = HexEncode(data_buffer);
+//
+// Mutable:
+// // ssize_t SafeSNPrintf(base::span<char>, const char* fmt, Args...);
+// char str_buffer[100];
+// SafeSNPrintf(str_buffer, "Pi ~= %lf", 3.14);
+//
+// Spans with "const" and pointers
+// -------------------------------
+//
+// Const and pointers can get confusing. Here are vectors of pointers and their
+// corresponding spans:
+//
+// const std::vector<int*> => base::span<int* const>
+// std::vector<const int*> => base::span<const int*>
+// const std::vector<const int*> => base::span<const int* const>
+//
+// Differences from the working group proposal
+// -------------------------------------------
+//
+// https://wg21.link/P0122 is the latest working group proposal, Chromium
+// currently implements R7. Differences between the proposal and the
+// implementation are documented in subsections below.
+//
+// Differences from [span.objectrep]:
+// - as_bytes() and as_writable_bytes() return spans of uint8_t instead of
+// std::byte
+//
+// Differences in constants and types:
+// - index_type is aliased to size_t
+//
+// Differences from [span.sub]:
+// - using size_t instead of ptrdiff_t for indexing
+//
+// Differences from [span.obs]:
+// - using size_t instead of ptrdiff_t to represent size()
+//
+// Differences from [span.elem]:
+// - using size_t instead of ptrdiff_t for indexing
+//
+// Furthermore, all constructors and methods are marked noexcept due to the lack
+// of exceptions in Chromium.
+//
+// Due to the lack of class template argument deduction guides in C++14
+// appropriate make_span() utility functions are provided.
+
+// [span], class template span
+template <typename T, size_t Extent>
+class span {
+ public:
+ using element_type = T;
+ using value_type = std::remove_cv_t<T>;
+ using index_type = size_t;
+ using difference_type = ptrdiff_t;
+ using pointer = T*;
+ using reference = T&;
+ using iterator = T*;
+ using const_iterator = const T*;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+ static constexpr index_type extent = Extent;
+
+ // [span.cons], span constructors, copy, assignment, and destructor
+ constexpr span() noexcept : data_(nullptr), size_(0) {
+ static_assert(Extent == dynamic_extent || Extent == 0, "Invalid Extent");
+ }
+
+ constexpr span(T* data, size_t size) noexcept : data_(data), size_(size) {
+ CHECK(Extent == dynamic_extent || Extent == size);
+ }
+
+ // Artificially templatized to break ambiguity for span(ptr, 0).
+ template <typename = void>
+ constexpr span(T* begin, T* end) noexcept : span(begin, end - begin) {
+ // Note: CHECK_LE is not constexpr, hence regular CHECK must be used.
+ CHECK(begin <= end);
+ }
+
+ template <
+ size_t N,
+ typename = internal::EnableIfSpanCompatibleArray<T (&)[N], N, T, Extent>>
+ constexpr span(T (&array)[N]) noexcept : span(base::data(array), N) {}
+
+ template <
+ size_t N,
+ typename = internal::
+ EnableIfSpanCompatibleArray<std::array<value_type, N>&, N, T, Extent>>
+ constexpr span(std::array<value_type, N>& array) noexcept
+ : span(base::data(array), N) {}
+
+ template <size_t N,
+ typename = internal::EnableIfSpanCompatibleArray<
+ const std::array<value_type, N>&,
+ N,
+ T,
+ Extent>>
+ constexpr span(const std::array<value_type, N>& array) noexcept
+ : span(base::data(array), N) {}
+
+ // Conversion from a container that has compatible base::data() and integral
+ // base::size().
+ template <typename Container,
+ typename = internal::EnableIfSpanCompatibleContainer<Container&, T>>
+ constexpr span(Container& container) noexcept
+ : span(base::data(container), base::size(container)) {}
+
+ template <
+ typename Container,
+ typename = internal::EnableIfSpanCompatibleContainer<const Container&, T>>
+ span(const Container& container) noexcept
+ : span(base::data(container), base::size(container)) {}
+
+ constexpr span(const span& other) noexcept = default;
+
+ // Conversions from spans of compatible types and extents: this allows a
+ // span<T> to be seamlessly used as a span<const T>, but not the other way
+ // around. If extent is not dynamic, OtherExtent has to be equal to Extent.
+ template <
+ typename U,
+ size_t OtherExtent,
+ typename =
+ internal::EnableIfLegalSpanConversion<U, OtherExtent, T, Extent>>
+ constexpr span(const span<U, OtherExtent>& other)
+ : span(other.data(), other.size()) {}
+
+ constexpr span& operator=(const span& other) noexcept = default;
+ ~span() noexcept = default;
+
+ // [span.sub], span subviews
+ template <size_t Count>
+ constexpr span<T, Count> first() const noexcept {
+ static_assert(Extent == dynamic_extent || Count <= Extent,
+ "Count must not exceed Extent");
+ CHECK(Extent != dynamic_extent || Count <= size());
+ return {data(), Count};
+ }
+
+ template <size_t Count>
+ constexpr span<T, Count> last() const noexcept {
+ static_assert(Extent == dynamic_extent || Count <= Extent,
+ "Count must not exceed Extent");
+ CHECK(Extent != dynamic_extent || Count <= size());
+ return {data() + (size() - Count), Count};
+ }
+
+ template <size_t Offset, size_t Count = dynamic_extent>
+ constexpr span<T,
+ (Count != dynamic_extent
+ ? Count
+ : (Extent != dynamic_extent ? Extent - Offset
+ : dynamic_extent))>
+ subspan() const noexcept {
+ static_assert(Extent == dynamic_extent || Offset <= Extent,
+ "Offset must not exceed Extent");
+ static_assert(Extent == dynamic_extent || Count == dynamic_extent ||
+ Count <= Extent - Offset,
+ "Count must not exceed Extent - Offset");
+ CHECK(Extent != dynamic_extent || Offset <= size());
+ CHECK(Extent != dynamic_extent || Count == dynamic_extent ||
+ Count <= size() - Offset);
+ return {data() + Offset, Count != dynamic_extent ? Count : size() - Offset};
+ }
+
+ constexpr span<T, dynamic_extent> first(size_t count) const noexcept {
+ // Note: CHECK_LE is not constexpr, hence regular CHECK must be used.
+ CHECK(count <= size());
+ return {data(), count};
+ }
+
+ constexpr span<T, dynamic_extent> last(size_t count) const noexcept {
+ // Note: CHECK_LE is not constexpr, hence regular CHECK must be used.
+ CHECK(count <= size());
+ return {data() + (size() - count), count};
+ }
+
+ constexpr span<T, dynamic_extent> subspan(size_t offset,
+ size_t count = dynamic_extent) const
+ noexcept {
+ // Note: CHECK_LE is not constexpr, hence regular CHECK must be used.
+ CHECK(offset <= size());
+ CHECK(count == dynamic_extent || count <= size() - offset);
+ return {data() + offset, count != dynamic_extent ? count : size() - offset};
+ }
+
+ // [span.obs], span observers
+ constexpr size_t size() const noexcept { return size_; }
+ constexpr size_t size_bytes() const noexcept { return size() * sizeof(T); }
+ constexpr bool empty() const noexcept { return size() == 0; }
+
+ // [span.elem], span element access
+ constexpr T& operator[](size_t idx) const noexcept {
+ // Note: CHECK_LT is not constexpr, hence regular CHECK must be used.
+ CHECK(idx < size());
+ return *(data() + idx);
+ }
+
+ constexpr T& operator()(size_t idx) const noexcept {
+ // Note: CHECK_LT is not constexpr, hence regular CHECK must be used.
+ CHECK(idx < size());
+ return *(data() + idx);
+ }
+
+ constexpr T* data() const noexcept { return data_; }
+
+ // [span.iter], span iterator support
+ constexpr iterator begin() const noexcept { return data(); }
+ constexpr iterator end() const noexcept { return data() + size(); }
+
+ constexpr const_iterator cbegin() const noexcept { return begin(); }
+ constexpr const_iterator cend() const noexcept { return end(); }
+
+ constexpr reverse_iterator rbegin() const noexcept {
+ return reverse_iterator(end());
+ }
+ constexpr reverse_iterator rend() const noexcept {
+ return reverse_iterator(begin());
+ }
+
+ constexpr const_reverse_iterator crbegin() const noexcept {
+ return const_reverse_iterator(cend());
+ }
+ constexpr const_reverse_iterator crend() const noexcept {
+ return const_reverse_iterator(cbegin());
+ }
+
+ private:
+ T* data_;
+ size_t size_;
+};
+
+// span<T, Extent>::extent can not be declared inline prior to C++17, hence this
+// definition is required.
+template <class T, size_t Extent>
+constexpr size_t span<T, Extent>::extent;
+
+// [span.comparison], span comparison operators
+// Relational operators. Equality is a element-wise comparison.
+template <typename T, size_t X, typename U, size_t Y>
+constexpr bool operator==(span<T, X> lhs, span<U, Y> rhs) noexcept {
+ return std::equal(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend());
+}
+
+template <typename T, size_t X, typename U, size_t Y>
+constexpr bool operator!=(span<T, X> lhs, span<U, Y> rhs) noexcept {
+ return !(lhs == rhs);
+}
+
+template <typename T, size_t X, typename U, size_t Y>
+constexpr bool operator<(span<T, X> lhs, span<U, Y> rhs) noexcept {
+ return std::lexicographical_compare(lhs.cbegin(), lhs.cend(), rhs.cbegin(),
+ rhs.cend());
+}
+
+template <typename T, size_t X, typename U, size_t Y>
+constexpr bool operator<=(span<T, X> lhs, span<U, Y> rhs) noexcept {
+ return !(rhs < lhs);
+}
+
+template <typename T, size_t X, typename U, size_t Y>
+constexpr bool operator>(span<T, X> lhs, span<U, Y> rhs) noexcept {
+ return rhs < lhs;
+}
+
+template <typename T, size_t X, typename U, size_t Y>
+constexpr bool operator>=(span<T, X> lhs, span<U, Y> rhs) noexcept {
+ return !(lhs < rhs);
+}
+
+// [span.objectrep], views of object representation
+template <typename T, size_t X>
+span<const uint8_t, (X == dynamic_extent ? dynamic_extent : sizeof(T) * X)>
+as_bytes(span<T, X> s) noexcept {
+ return {reinterpret_cast<const uint8_t*>(s.data()), s.size_bytes()};
+}
+
+template <typename T,
+ size_t X,
+ typename = std::enable_if_t<!std::is_const<T>::value>>
+span<uint8_t, (X == dynamic_extent ? dynamic_extent : sizeof(T) * X)>
+as_writable_bytes(span<T, X> s) noexcept {
+ return {reinterpret_cast<uint8_t*>(s.data()), s.size_bytes()};
+}
+
+// Type-deducing helpers for constructing a span.
+template <typename T>
+constexpr span<T> make_span(T* data, size_t size) noexcept {
+ return {data, size};
+}
+
+template <typename T>
+constexpr span<T> make_span(T* begin, T* end) noexcept {
+ return {begin, end};
+}
+
+template <typename T, size_t N>
+constexpr span<T, N> make_span(T (&array)[N]) noexcept {
+ return array;
+}
+
+template <typename T, size_t N>
+constexpr span<T, N> make_span(std::array<T, N>& array) noexcept {
+ return array;
+}
+
+template <typename T, size_t N>
+constexpr span<const T, N> make_span(const std::array<T, N>& array) noexcept {
+ return array;
+}
+
+template <typename Container,
+ typename T = typename Container::value_type,
+ typename = internal::EnableIfSpanCompatibleContainer<Container&, T>>
+constexpr span<T> make_span(Container& container) noexcept {
+ return container;
+}
+
+template <
+ typename Container,
+ typename T = const typename Container::value_type,
+ typename = internal::EnableIfSpanCompatibleContainer<const Container&, T>>
+constexpr span<T> make_span(const Container& container) noexcept {
+ return container;
+}
+
+template <typename T, size_t X>
+constexpr span<T, X> make_span(const span<T, X>& span) noexcept {
+ return span;
+}
+
+} // namespace base
+
+#endif // BASE_CONTAINERS_SPAN_H_
diff --git a/base/containers/span_unittest.cc b/base/containers/span_unittest.cc
new file mode 100644
index 0000000000..de5e401863
--- /dev/null
+++ b/base/containers/span_unittest.cc
@@ -0,0 +1,1170 @@
+// 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/containers/span.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::Pointwise;
+
+namespace base {
+
+TEST(SpanTest, DefaultConstructor) {
+ span<int> dynamic_span;
+ EXPECT_EQ(nullptr, dynamic_span.data());
+ EXPECT_EQ(0u, dynamic_span.size());
+
+ constexpr span<int, 0> static_span;
+ static_assert(nullptr == static_span.data(), "");
+ static_assert(0u == static_span.size(), "");
+}
+
+TEST(SpanTest, ConstructFromDataAndSize) {
+ constexpr span<int> empty_span(nullptr, 0);
+ EXPECT_TRUE(empty_span.empty());
+ EXPECT_EQ(nullptr, empty_span.data());
+
+ std::vector<int> vector = {1, 1, 2, 3, 5, 8};
+
+ span<int> dynamic_span(vector.data(), vector.size());
+ EXPECT_EQ(vector.data(), dynamic_span.data());
+ EXPECT_EQ(vector.size(), dynamic_span.size());
+
+ for (size_t i = 0; i < dynamic_span.size(); ++i)
+ EXPECT_EQ(vector[i], dynamic_span[i]);
+
+ span<int, 6> static_span(vector.data(), vector.size());
+ EXPECT_EQ(vector.data(), static_span.data());
+ EXPECT_EQ(vector.size(), static_span.size());
+
+ for (size_t i = 0; i < static_span.size(); ++i)
+ EXPECT_EQ(vector[i], static_span[i]);
+}
+
+TEST(SpanTest, ConstructFromPointerPair) {
+ constexpr span<int> empty_span(nullptr, nullptr);
+ EXPECT_TRUE(empty_span.empty());
+ EXPECT_EQ(nullptr, empty_span.data());
+
+ std::vector<int> vector = {1, 1, 2, 3, 5, 8};
+
+ span<int> dynamic_span(vector.data(), vector.data() + vector.size() / 2);
+ EXPECT_EQ(vector.data(), dynamic_span.data());
+ EXPECT_EQ(vector.size() / 2, dynamic_span.size());
+
+ for (size_t i = 0; i < dynamic_span.size(); ++i)
+ EXPECT_EQ(vector[i], dynamic_span[i]);
+
+ span<int, 3> static_span(vector.data(), vector.data() + vector.size() / 2);
+ EXPECT_EQ(vector.data(), static_span.data());
+ EXPECT_EQ(vector.size() / 2, static_span.size());
+
+ for (size_t i = 0; i < static_span.size(); ++i)
+ EXPECT_EQ(vector[i], static_span[i]);
+}
+
+TEST(SpanTest, ConstructFromConstexprArray) {
+ static constexpr int kArray[] = {5, 4, 3, 2, 1};
+
+ constexpr span<const int> dynamic_span(kArray);
+ static_assert(kArray == dynamic_span.data(), "");
+ static_assert(base::size(kArray) == dynamic_span.size(), "");
+
+ static_assert(kArray[0] == dynamic_span[0], "");
+ static_assert(kArray[1] == dynamic_span[1], "");
+ static_assert(kArray[2] == dynamic_span[2], "");
+ static_assert(kArray[3] == dynamic_span[3], "");
+ static_assert(kArray[4] == dynamic_span[4], "");
+
+ constexpr span<const int, base::size(kArray)> static_span(kArray);
+ static_assert(kArray == static_span.data(), "");
+ static_assert(base::size(kArray) == static_span.size(), "");
+
+ static_assert(kArray[0] == static_span[0], "");
+ static_assert(kArray[1] == static_span[1], "");
+ static_assert(kArray[2] == static_span[2], "");
+ static_assert(kArray[3] == static_span[3], "");
+ static_assert(kArray[4] == static_span[4], "");
+}
+
+TEST(SpanTest, ConstructFromArray) {
+ int array[] = {5, 4, 3, 2, 1};
+
+ span<const int> const_span(array);
+ EXPECT_EQ(array, const_span.data());
+ EXPECT_EQ(arraysize(array), const_span.size());
+ for (size_t i = 0; i < const_span.size(); ++i)
+ EXPECT_EQ(array[i], const_span[i]);
+
+ span<int> dynamic_span(array);
+ EXPECT_EQ(array, dynamic_span.data());
+ EXPECT_EQ(base::size(array), dynamic_span.size());
+ for (size_t i = 0; i < dynamic_span.size(); ++i)
+ EXPECT_EQ(array[i], dynamic_span[i]);
+
+ span<int, base::size(array)> static_span(array);
+ EXPECT_EQ(array, static_span.data());
+ EXPECT_EQ(base::size(array), static_span.size());
+ for (size_t i = 0; i < static_span.size(); ++i)
+ EXPECT_EQ(array[i], static_span[i]);
+}
+
+TEST(SpanTest, ConstructFromStdArray) {
+ // Note: Constructing a constexpr span from a constexpr std::array does not
+ // work prior to C++17 due to non-constexpr std::array::data.
+ std::array<int, 5> array = {{5, 4, 3, 2, 1}};
+
+ span<const int> const_span(array);
+ EXPECT_EQ(array.data(), const_span.data());
+ EXPECT_EQ(array.size(), const_span.size());
+ for (size_t i = 0; i < const_span.size(); ++i)
+ EXPECT_EQ(array[i], const_span[i]);
+
+ span<int> dynamic_span(array);
+ EXPECT_EQ(array.data(), dynamic_span.data());
+ EXPECT_EQ(array.size(), dynamic_span.size());
+ for (size_t i = 0; i < dynamic_span.size(); ++i)
+ EXPECT_EQ(array[i], dynamic_span[i]);
+
+ span<int, base::size(array)> static_span(array);
+ EXPECT_EQ(array.data(), static_span.data());
+ EXPECT_EQ(array.size(), static_span.size());
+ for (size_t i = 0; i < static_span.size(); ++i)
+ EXPECT_EQ(array[i], static_span[i]);
+}
+
+TEST(SpanTest, ConstructFromInitializerList) {
+ std::initializer_list<int> il = {1, 1, 2, 3, 5, 8};
+
+ span<const int> const_span(il);
+ EXPECT_EQ(il.begin(), const_span.data());
+ EXPECT_EQ(il.size(), const_span.size());
+
+ for (size_t i = 0; i < const_span.size(); ++i)
+ EXPECT_EQ(il.begin()[i], const_span[i]);
+
+ span<const int, 6> static_span(il);
+ EXPECT_EQ(il.begin(), static_span.data());
+ EXPECT_EQ(il.size(), static_span.size());
+
+ for (size_t i = 0; i < static_span.size(); ++i)
+ EXPECT_EQ(il.begin()[i], static_span[i]);
+}
+
+TEST(SpanTest, ConstructFromStdString) {
+ std::string str = "foobar";
+
+ span<const char> const_span(str);
+ EXPECT_EQ(str.data(), const_span.data());
+ EXPECT_EQ(str.size(), const_span.size());
+
+ for (size_t i = 0; i < const_span.size(); ++i)
+ EXPECT_EQ(str[i], const_span[i]);
+
+ span<char> dynamic_span(str);
+ EXPECT_EQ(str.data(), dynamic_span.data());
+ EXPECT_EQ(str.size(), dynamic_span.size());
+
+ for (size_t i = 0; i < dynamic_span.size(); ++i)
+ EXPECT_EQ(str[i], dynamic_span[i]);
+
+ span<char, 6> static_span(str);
+ EXPECT_EQ(str.data(), static_span.data());
+ EXPECT_EQ(str.size(), static_span.size());
+
+ for (size_t i = 0; i < static_span.size(); ++i)
+ EXPECT_EQ(str[i], static_span[i]);
+}
+
+TEST(SpanTest, ConstructFromConstContainer) {
+ const std::vector<int> vector = {1, 1, 2, 3, 5, 8};
+
+ span<const int> const_span(vector);
+ EXPECT_EQ(vector.data(), const_span.data());
+ EXPECT_EQ(vector.size(), const_span.size());
+
+ for (size_t i = 0; i < const_span.size(); ++i)
+ EXPECT_EQ(vector[i], const_span[i]);
+
+ span<const int, 6> static_span(vector);
+ EXPECT_EQ(vector.data(), static_span.data());
+ EXPECT_EQ(vector.size(), static_span.size());
+
+ for (size_t i = 0; i < static_span.size(); ++i)
+ EXPECT_EQ(vector[i], static_span[i]);
+}
+
+TEST(SpanTest, ConstructFromContainer) {
+ std::vector<int> vector = {1, 1, 2, 3, 5, 8};
+
+ span<const int> const_span(vector);
+ EXPECT_EQ(vector.data(), const_span.data());
+ EXPECT_EQ(vector.size(), const_span.size());
+
+ for (size_t i = 0; i < const_span.size(); ++i)
+ EXPECT_EQ(vector[i], const_span[i]);
+
+ span<int> dynamic_span(vector);
+ EXPECT_EQ(vector.data(), dynamic_span.data());
+ EXPECT_EQ(vector.size(), dynamic_span.size());
+
+ for (size_t i = 0; i < dynamic_span.size(); ++i)
+ EXPECT_EQ(vector[i], dynamic_span[i]);
+
+ span<int, 6> static_span(vector);
+ EXPECT_EQ(vector.data(), static_span.data());
+ EXPECT_EQ(vector.size(), static_span.size());
+
+ for (size_t i = 0; i < static_span.size(); ++i)
+ EXPECT_EQ(vector[i], static_span[i]);
+}
+
+TEST(SpanTest, ConvertNonConstIntegralToConst) {
+ std::vector<int> vector = {1, 1, 2, 3, 5, 8};
+
+ span<int> int_span(vector.data(), vector.size());
+ span<const int> const_span(int_span);
+ EXPECT_THAT(const_span, Pointwise(Eq(), int_span));
+
+ span<int, 6> static_int_span(vector.data(), vector.size());
+ span<const int, 6> static_const_span(static_int_span);
+ EXPECT_THAT(static_const_span, Pointwise(Eq(), static_int_span));
+}
+
+TEST(SpanTest, ConvertNonConstPointerToConst) {
+ auto a = std::make_unique<int>(11);
+ auto b = std::make_unique<int>(22);
+ auto c = std::make_unique<int>(33);
+ std::vector<int*> vector = {a.get(), b.get(), c.get()};
+
+ span<int*> non_const_pointer_span(vector);
+ EXPECT_THAT(non_const_pointer_span, Pointwise(Eq(), vector));
+ span<int* const> const_pointer_span(non_const_pointer_span);
+ EXPECT_THAT(const_pointer_span, Pointwise(Eq(), non_const_pointer_span));
+ // Note: no test for conversion from span<int> to span<const int*>, since that
+ // would imply a conversion from int** to const int**, which is unsafe.
+ //
+ // Note: no test for conversion from span<int*> to span<const int* const>,
+ // due to CWG Defect 330:
+ // http://open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#330
+
+ span<int*, 3> static_non_const_pointer_span(vector);
+ EXPECT_THAT(static_non_const_pointer_span, Pointwise(Eq(), vector));
+ span<int* const, 3> static_const_pointer_span(static_non_const_pointer_span);
+ EXPECT_THAT(static_const_pointer_span,
+ Pointwise(Eq(), static_non_const_pointer_span));
+}
+
+TEST(SpanTest, ConvertBetweenEquivalentTypes) {
+ std::vector<int32_t> vector = {2, 4, 8, 16, 32};
+
+ span<int32_t> int32_t_span(vector);
+ span<int> converted_span(int32_t_span);
+ EXPECT_EQ(int32_t_span, converted_span);
+
+ span<int32_t, 5> static_int32_t_span(vector);
+ span<int, 5> static_converted_span(static_int32_t_span);
+ EXPECT_EQ(static_int32_t_span, static_converted_span);
+}
+
+TEST(SpanTest, TemplatedFirst) {
+ static constexpr int array[] = {1, 2, 3};
+ constexpr span<const int, 3> span(array);
+
+ {
+ constexpr auto subspan = span.first<0>();
+ static_assert(span.data() == subspan.data(), "");
+ static_assert(0u == subspan.size(), "");
+ static_assert(0u == decltype(subspan)::extent, "");
+ }
+
+ {
+ constexpr auto subspan = span.first<1>();
+ static_assert(span.data() == subspan.data(), "");
+ static_assert(1u == subspan.size(), "");
+ static_assert(1u == decltype(subspan)::extent, "");
+ static_assert(1 == subspan[0], "");
+ }
+
+ {
+ constexpr auto subspan = span.first<2>();
+ static_assert(span.data() == subspan.data(), "");
+ static_assert(2u == subspan.size(), "");
+ static_assert(2u == decltype(subspan)::extent, "");
+ static_assert(1 == subspan[0], "");
+ static_assert(2 == subspan[1], "");
+ }
+
+ {
+ constexpr auto subspan = span.first<3>();
+ static_assert(span.data() == subspan.data(), "");
+ static_assert(3u == subspan.size(), "");
+ static_assert(3u == decltype(subspan)::extent, "");
+ static_assert(1 == subspan[0], "");
+ static_assert(2 == subspan[1], "");
+ static_assert(3 == subspan[2], "");
+ }
+}
+
+TEST(SpanTest, TemplatedLast) {
+ static constexpr int array[] = {1, 2, 3};
+ constexpr span<const int, 3> span(array);
+
+ {
+ constexpr auto subspan = span.last<0>();
+ static_assert(span.data() + 3 == subspan.data(), "");
+ static_assert(0u == subspan.size(), "");
+ static_assert(0u == decltype(subspan)::extent, "");
+ }
+
+ {
+ constexpr auto subspan = span.last<1>();
+ static_assert(span.data() + 2 == subspan.data(), "");
+ static_assert(1u == subspan.size(), "");
+ static_assert(1u == decltype(subspan)::extent, "");
+ static_assert(3 == subspan[0], "");
+ }
+
+ {
+ constexpr auto subspan = span.last<2>();
+ static_assert(span.data() + 1 == subspan.data(), "");
+ static_assert(2u == subspan.size(), "");
+ static_assert(2u == decltype(subspan)::extent, "");
+ static_assert(2 == subspan[0], "");
+ static_assert(3 == subspan[1], "");
+ }
+
+ {
+ constexpr auto subspan = span.last<3>();
+ static_assert(span.data() == subspan.data(), "");
+ static_assert(3u == subspan.size(), "");
+ static_assert(3u == decltype(subspan)::extent, "");
+ static_assert(1 == subspan[0], "");
+ static_assert(2 == subspan[1], "");
+ static_assert(3 == subspan[2], "");
+ }
+}
+
+TEST(SpanTest, TemplatedSubspan) {
+ static constexpr int array[] = {1, 2, 3};
+ constexpr span<const int, 3> span(array);
+
+ {
+ constexpr auto subspan = span.subspan<0>();
+ static_assert(span.data() == subspan.data(), "");
+ static_assert(3u == subspan.size(), "");
+ static_assert(3u == decltype(subspan)::extent, "");
+ static_assert(1 == subspan[0], "");
+ static_assert(2 == subspan[1], "");
+ static_assert(3 == subspan[2], "");
+ }
+
+ {
+ constexpr auto subspan = span.subspan<1>();
+ static_assert(span.data() + 1 == subspan.data(), "");
+ static_assert(2u == subspan.size(), "");
+ static_assert(2u == decltype(subspan)::extent, "");
+ static_assert(2 == subspan[0], "");
+ static_assert(3 == subspan[1], "");
+ }
+
+ {
+ constexpr auto subspan = span.subspan<2>();
+ static_assert(span.data() + 2 == subspan.data(), "");
+ static_assert(1u == subspan.size(), "");
+ static_assert(1u == decltype(subspan)::extent, "");
+ static_assert(3 == subspan[0], "");
+ }
+
+ {
+ constexpr auto subspan = span.subspan<3>();
+ static_assert(span.data() + 3 == subspan.data(), "");
+ static_assert(0u == subspan.size(), "");
+ static_assert(0u == decltype(subspan)::extent, "");
+ }
+
+ {
+ constexpr auto subspan = span.subspan<0, 0>();
+ static_assert(span.data() == subspan.data(), "");
+ static_assert(0u == subspan.size(), "");
+ static_assert(0u == decltype(subspan)::extent, "");
+ }
+
+ {
+ constexpr auto subspan = span.subspan<1, 0>();
+ static_assert(span.data() + 1 == subspan.data(), "");
+ static_assert(0u == subspan.size(), "");
+ static_assert(0u == decltype(subspan)::extent, "");
+ }
+
+ {
+ constexpr auto subspan = span.subspan<2, 0>();
+ static_assert(span.data() + 2 == subspan.data(), "");
+ static_assert(0u == subspan.size(), "");
+ static_assert(0u == decltype(subspan)::extent, "");
+ }
+
+ {
+ constexpr auto subspan = span.subspan<0, 1>();
+ static_assert(span.data() == subspan.data(), "");
+ static_assert(1u == subspan.size(), "");
+ static_assert(1u == decltype(subspan)::extent, "");
+ static_assert(1 == subspan[0], "");
+ }
+
+ {
+ constexpr auto subspan = span.subspan<1, 1>();
+ static_assert(span.data() + 1 == subspan.data(), "");
+ static_assert(1u == subspan.size(), "");
+ static_assert(1u == decltype(subspan)::extent, "");
+ static_assert(2 == subspan[0], "");
+ }
+
+ {
+ constexpr auto subspan = span.subspan<2, 1>();
+ static_assert(span.data() + 2 == subspan.data(), "");
+ static_assert(1u == subspan.size(), "");
+ static_assert(1u == decltype(subspan)::extent, "");
+ static_assert(3 == subspan[0], "");
+ }
+
+ {
+ constexpr auto subspan = span.subspan<0, 2>();
+ static_assert(span.data() == subspan.data(), "");
+ static_assert(2u == subspan.size(), "");
+ static_assert(2u == decltype(subspan)::extent, "");
+ static_assert(1 == subspan[0], "");
+ static_assert(2 == subspan[1], "");
+ }
+
+ {
+ constexpr auto subspan = span.subspan<1, 2>();
+ static_assert(span.data() + 1 == subspan.data(), "");
+ static_assert(2u == subspan.size(), "");
+ static_assert(2u == decltype(subspan)::extent, "");
+ static_assert(2 == subspan[0], "");
+ static_assert(3 == subspan[1], "");
+ }
+
+ {
+ constexpr auto subspan = span.subspan<0, 3>();
+ static_assert(span.data() == subspan.data(), "");
+ static_assert(3u == subspan.size(), "");
+ static_assert(3u == decltype(subspan)::extent, "");
+ static_assert(1 == subspan[0], "");
+ static_assert(2 == subspan[1], "");
+ static_assert(3 == subspan[2], "");
+ }
+}
+
+TEST(SpanTest, TemplatedFirstOnDynamicSpan) {
+ int array[] = {1, 2, 3};
+ span<const int> span(array);
+
+ {
+ auto subspan = span.first<0>();
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(0u, subspan.size());
+ static_assert(0u == decltype(subspan)::extent, "");
+ }
+
+ {
+ auto subspan = span.first<1>();
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(1u, subspan.size());
+ static_assert(1u == decltype(subspan)::extent, "");
+ EXPECT_EQ(1, subspan[0]);
+ }
+
+ {
+ auto subspan = span.first<2>();
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(2u, subspan.size());
+ static_assert(2u == decltype(subspan)::extent, "");
+ EXPECT_EQ(1, subspan[0]);
+ EXPECT_EQ(2, subspan[1]);
+ }
+
+ {
+ auto subspan = span.first<3>();
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(3u, subspan.size());
+ static_assert(3u == decltype(subspan)::extent, "");
+ EXPECT_EQ(1, subspan[0]);
+ EXPECT_EQ(2, subspan[1]);
+ EXPECT_EQ(3, subspan[2]);
+ }
+}
+
+TEST(SpanTest, TemplatedLastOnDynamicSpan) {
+ int array[] = {1, 2, 3};
+ span<int> span(array);
+
+ {
+ auto subspan = span.last<0>();
+ EXPECT_EQ(span.data() + 3, subspan.data());
+ EXPECT_EQ(0u, subspan.size());
+ static_assert(0u == decltype(subspan)::extent, "");
+ }
+
+ {
+ auto subspan = span.last<1>();
+ EXPECT_EQ(span.data() + 2, subspan.data());
+ EXPECT_EQ(1u, subspan.size());
+ static_assert(1u == decltype(subspan)::extent, "");
+ EXPECT_EQ(3, subspan[0]);
+ }
+
+ {
+ auto subspan = span.last<2>();
+ EXPECT_EQ(span.data() + 1, subspan.data());
+ EXPECT_EQ(2u, subspan.size());
+ static_assert(2u == decltype(subspan)::extent, "");
+ EXPECT_EQ(2, subspan[0]);
+ EXPECT_EQ(3, subspan[1]);
+ }
+
+ {
+ auto subspan = span.last<3>();
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(3u, subspan.size());
+ static_assert(3u == decltype(subspan)::extent, "");
+ EXPECT_EQ(1, subspan[0]);
+ EXPECT_EQ(2, subspan[1]);
+ EXPECT_EQ(3, subspan[2]);
+ }
+}
+
+TEST(SpanTest, TemplatedSubspanFromDynamicSpan) {
+ int array[] = {1, 2, 3};
+ span<int, 3> span(array);
+
+ {
+ auto subspan = span.subspan<0>();
+ EXPECT_EQ(span.data(), subspan.data());
+ static_assert(3u == decltype(subspan)::extent, "");
+ EXPECT_EQ(3u, subspan.size());
+ EXPECT_EQ(1, subspan[0]);
+ EXPECT_EQ(2, subspan[1]);
+ EXPECT_EQ(3, subspan[2]);
+ }
+
+ {
+ auto subspan = span.subspan<1>();
+ EXPECT_EQ(span.data() + 1, subspan.data());
+ EXPECT_EQ(2u, subspan.size());
+ static_assert(2u == decltype(subspan)::extent, "");
+ EXPECT_EQ(2, subspan[0]);
+ EXPECT_EQ(3, subspan[1]);
+ }
+
+ {
+ auto subspan = span.subspan<2>();
+ EXPECT_EQ(span.data() + 2, subspan.data());
+ EXPECT_EQ(1u, subspan.size());
+ static_assert(1u == decltype(subspan)::extent, "");
+ EXPECT_EQ(3, subspan[0]);
+ }
+
+ {
+ auto subspan = span.subspan<3>();
+ EXPECT_EQ(span.data() + 3, subspan.data());
+ EXPECT_EQ(0u, subspan.size());
+ static_assert(0u == decltype(subspan)::extent, "");
+ }
+
+ {
+ auto subspan = span.subspan<0, 0>();
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(0u, subspan.size());
+ static_assert(0u == decltype(subspan)::extent, "");
+ }
+
+ {
+ auto subspan = span.subspan<1, 0>();
+ EXPECT_EQ(span.data() + 1, subspan.data());
+ EXPECT_EQ(0u, subspan.size());
+ static_assert(0u == decltype(subspan)::extent, "");
+ }
+
+ {
+ auto subspan = span.subspan<2, 0>();
+ EXPECT_EQ(span.data() + 2, subspan.data());
+ EXPECT_EQ(0u, subspan.size());
+ static_assert(0u == decltype(subspan)::extent, "");
+ }
+
+ {
+ auto subspan = span.subspan<0, 1>();
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(1u, subspan.size());
+ static_assert(1u == decltype(subspan)::extent, "");
+ EXPECT_EQ(1, subspan[0]);
+ }
+
+ {
+ auto subspan = span.subspan<1, 1>();
+ EXPECT_EQ(span.data() + 1, subspan.data());
+ EXPECT_EQ(1u, subspan.size());
+ static_assert(1u == decltype(subspan)::extent, "");
+ EXPECT_EQ(2, subspan[0]);
+ }
+
+ {
+ auto subspan = span.subspan<2, 1>();
+ EXPECT_EQ(span.data() + 2, subspan.data());
+ EXPECT_EQ(1u, subspan.size());
+ static_assert(1u == decltype(subspan)::extent, "");
+ EXPECT_EQ(3, subspan[0]);
+ }
+
+ {
+ auto subspan = span.subspan<0, 2>();
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(2u, subspan.size());
+ static_assert(2u == decltype(subspan)::extent, "");
+ EXPECT_EQ(1, subspan[0]);
+ EXPECT_EQ(2, subspan[1]);
+ }
+
+ {
+ auto subspan = span.subspan<1, 2>();
+ EXPECT_EQ(span.data() + 1, subspan.data());
+ EXPECT_EQ(2u, subspan.size());
+ static_assert(2u == decltype(subspan)::extent, "");
+ EXPECT_EQ(2, subspan[0]);
+ EXPECT_EQ(3, subspan[1]);
+ }
+
+ {
+ auto subspan = span.subspan<0, 3>();
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(3u, subspan.size());
+ static_assert(3u == decltype(subspan)::extent, "");
+ EXPECT_EQ(1, subspan[0]);
+ EXPECT_EQ(2, subspan[1]);
+ EXPECT_EQ(3, subspan[2]);
+ }
+}
+
+TEST(SpanTest, First) {
+ int array[] = {1, 2, 3};
+ span<int> span(array);
+
+ {
+ auto subspan = span.first(0);
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(0u, subspan.size());
+ }
+
+ {
+ auto subspan = span.first(1);
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(1u, subspan.size());
+ EXPECT_EQ(1, subspan[0]);
+ }
+
+ {
+ auto subspan = span.first(2);
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(2u, subspan.size());
+ EXPECT_EQ(1, subspan[0]);
+ EXPECT_EQ(2, subspan[1]);
+ }
+
+ {
+ auto subspan = span.first(3);
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(3u, subspan.size());
+ EXPECT_EQ(1, subspan[0]);
+ EXPECT_EQ(2, subspan[1]);
+ EXPECT_EQ(3, subspan[2]);
+ }
+}
+
+TEST(SpanTest, Last) {
+ int array[] = {1, 2, 3};
+ span<int> span(array);
+
+ {
+ auto subspan = span.last(0);
+ EXPECT_EQ(span.data() + 3, subspan.data());
+ EXPECT_EQ(0u, subspan.size());
+ }
+
+ {
+ auto subspan = span.last(1);
+ EXPECT_EQ(span.data() + 2, subspan.data());
+ EXPECT_EQ(1u, subspan.size());
+ EXPECT_EQ(3, subspan[0]);
+ }
+
+ {
+ auto subspan = span.last(2);
+ EXPECT_EQ(span.data() + 1, subspan.data());
+ EXPECT_EQ(2u, subspan.size());
+ EXPECT_EQ(2, subspan[0]);
+ EXPECT_EQ(3, subspan[1]);
+ }
+
+ {
+ auto subspan = span.last(3);
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(3u, subspan.size());
+ EXPECT_EQ(1, subspan[0]);
+ EXPECT_EQ(2, subspan[1]);
+ EXPECT_EQ(3, subspan[2]);
+ }
+}
+
+TEST(SpanTest, Subspan) {
+ int array[] = {1, 2, 3};
+ span<int> span(array);
+
+ {
+ auto subspan = span.subspan(0);
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(3u, subspan.size());
+ EXPECT_EQ(1, subspan[0]);
+ EXPECT_EQ(2, subspan[1]);
+ EXPECT_EQ(3, subspan[2]);
+ }
+
+ {
+ auto subspan = span.subspan(1);
+ EXPECT_EQ(span.data() + 1, subspan.data());
+ EXPECT_EQ(2u, subspan.size());
+ EXPECT_EQ(2, subspan[0]);
+ EXPECT_EQ(3, subspan[1]);
+ }
+
+ {
+ auto subspan = span.subspan(2);
+ EXPECT_EQ(span.data() + 2, subspan.data());
+ EXPECT_EQ(1u, subspan.size());
+ EXPECT_EQ(3, subspan[0]);
+ }
+
+ {
+ auto subspan = span.subspan(3);
+ EXPECT_EQ(span.data() + 3, subspan.data());
+ EXPECT_EQ(0u, subspan.size());
+ }
+
+ {
+ auto subspan = span.subspan(0, 0);
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(0u, subspan.size());
+ }
+
+ {
+ auto subspan = span.subspan(1, 0);
+ EXPECT_EQ(span.data() + 1, subspan.data());
+ EXPECT_EQ(0u, subspan.size());
+ }
+
+ {
+ auto subspan = span.subspan(2, 0);
+ EXPECT_EQ(span.data() + 2, subspan.data());
+ EXPECT_EQ(0u, subspan.size());
+ }
+
+ {
+ auto subspan = span.subspan(0, 1);
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(1u, subspan.size());
+ EXPECT_EQ(1, subspan[0]);
+ }
+
+ {
+ auto subspan = span.subspan(1, 1);
+ EXPECT_EQ(span.data() + 1, subspan.data());
+ EXPECT_EQ(1u, subspan.size());
+ EXPECT_EQ(2, subspan[0]);
+ }
+
+ {
+ auto subspan = span.subspan(2, 1);
+ EXPECT_EQ(span.data() + 2, subspan.data());
+ EXPECT_EQ(1u, subspan.size());
+ EXPECT_EQ(3, subspan[0]);
+ }
+
+ {
+ auto subspan = span.subspan(0, 2);
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(2u, subspan.size());
+ EXPECT_EQ(1, subspan[0]);
+ EXPECT_EQ(2, subspan[1]);
+ }
+
+ {
+ auto subspan = span.subspan(1, 2);
+ EXPECT_EQ(span.data() + 1, subspan.data());
+ EXPECT_EQ(2u, subspan.size());
+ EXPECT_EQ(2, subspan[0]);
+ EXPECT_EQ(3, subspan[1]);
+ }
+
+ {
+ auto subspan = span.subspan(0, 3);
+ EXPECT_EQ(span.data(), subspan.data());
+ EXPECT_EQ(span.size(), subspan.size());
+ EXPECT_EQ(1, subspan[0]);
+ EXPECT_EQ(2, subspan[1]);
+ EXPECT_EQ(3, subspan[2]);
+ }
+}
+
+TEST(SpanTest, Size) {
+ {
+ span<int> span;
+ EXPECT_EQ(0u, span.size());
+ }
+
+ {
+ int array[] = {1, 2, 3};
+ span<int> span(array);
+ EXPECT_EQ(3u, span.size());
+ }
+}
+
+TEST(SpanTest, SizeBytes) {
+ {
+ span<int> span;
+ EXPECT_EQ(0u, span.size_bytes());
+ }
+
+ {
+ int array[] = {1, 2, 3};
+ span<int> span(array);
+ EXPECT_EQ(3u * sizeof(int), span.size_bytes());
+ }
+}
+
+TEST(SpanTest, Empty) {
+ {
+ span<int> span;
+ EXPECT_TRUE(span.empty());
+ }
+
+ {
+ int array[] = {1, 2, 3};
+ span<int> span(array);
+ EXPECT_FALSE(span.empty());
+ }
+}
+
+TEST(SpanTest, OperatorAt) {
+ static constexpr int kArray[] = {1, 6, 1, 8, 0};
+ constexpr span<const int> span(kArray);
+
+ static_assert(kArray[0] == span[0], "span[0] does not equal kArray[0]");
+ static_assert(kArray[1] == span[1], "span[1] does not equal kArray[1]");
+ static_assert(kArray[2] == span[2], "span[2] does not equal kArray[2]");
+ static_assert(kArray[3] == span[3], "span[3] does not equal kArray[3]");
+ static_assert(kArray[4] == span[4], "span[4] does not equal kArray[4]");
+
+ static_assert(kArray[0] == span(0), "span(0) does not equal kArray[0]");
+ static_assert(kArray[1] == span(1), "span(1) does not equal kArray[1]");
+ static_assert(kArray[2] == span(2), "span(2) does not equal kArray[2]");
+ static_assert(kArray[3] == span(3), "span(3) does not equal kArray[3]");
+ static_assert(kArray[4] == span(4), "span(4) does not equal kArray[4]");
+}
+
+TEST(SpanTest, Iterator) {
+ static constexpr int kArray[] = {1, 6, 1, 8, 0};
+ constexpr span<const int> span(kArray);
+
+ std::vector<int> results;
+ for (int i : span)
+ results.emplace_back(i);
+ EXPECT_THAT(results, ElementsAre(1, 6, 1, 8, 0));
+}
+
+TEST(SpanTest, ReverseIterator) {
+ static constexpr int kArray[] = {1, 6, 1, 8, 0};
+ constexpr span<const int> span(kArray);
+
+ EXPECT_TRUE(std::equal(std::rbegin(kArray), std::rend(kArray), span.rbegin(),
+ span.rend()));
+ EXPECT_TRUE(std::equal(std::crbegin(kArray), std::crend(kArray),
+ span.crbegin(), span.crend()));
+}
+
+TEST(SpanTest, Equality) {
+ static constexpr int kArray1[] = {3, 1, 4, 1, 5};
+ static constexpr int kArray2[] = {3, 1, 4, 1, 5};
+ constexpr span<const int> span1(kArray1);
+ constexpr span<const int, 5> span2(kArray2);
+
+ EXPECT_EQ(span1, span2);
+
+ static constexpr int kArray3[] = {2, 7, 1, 8, 3};
+ constexpr span<const int> span3(kArray3);
+
+ EXPECT_FALSE(span1 == span3);
+
+ static double kArray4[] = {2.0, 7.0, 1.0, 8.0, 3.0};
+ span<double, 5> span4(kArray4);
+
+ EXPECT_EQ(span3, span4);
+}
+
+TEST(SpanTest, Inequality) {
+ static constexpr int kArray1[] = {2, 3, 5, 7, 11};
+ static constexpr int kArray2[] = {1, 4, 6, 8, 9};
+ constexpr span<const int> span1(kArray1);
+ constexpr span<const int, 5> span2(kArray2);
+
+ EXPECT_NE(span1, span2);
+
+ static constexpr int kArray3[] = {2, 3, 5, 7, 11};
+ constexpr span<const int> span3(kArray3);
+
+ EXPECT_FALSE(span1 != span3);
+
+ static double kArray4[] = {1.0, 4.0, 6.0, 8.0, 9.0};
+ span<double, 5> span4(kArray4);
+
+ EXPECT_NE(span3, span4);
+}
+
+TEST(SpanTest, LessThan) {
+ static constexpr int kArray1[] = {2, 3, 5, 7, 11};
+ static constexpr int kArray2[] = {2, 3, 5, 7, 11, 13};
+ constexpr span<const int> span1(kArray1);
+ constexpr span<const int, 6> span2(kArray2);
+
+ EXPECT_LT(span1, span2);
+
+ static constexpr int kArray3[] = {2, 3, 5, 7, 11};
+ constexpr span<const int> span3(kArray3);
+
+ EXPECT_FALSE(span1 < span3);
+
+ static double kArray4[] = {2.0, 3.0, 5.0, 7.0, 11.0, 13.0};
+ span<double, 6> span4(kArray4);
+
+ EXPECT_LT(span3, span4);
+}
+
+TEST(SpanTest, LessEqual) {
+ static constexpr int kArray1[] = {2, 3, 5, 7, 11};
+ static constexpr int kArray2[] = {2, 3, 5, 7, 11, 13};
+ constexpr span<const int> span1(kArray1);
+ constexpr span<const int, 6> span2(kArray2);
+
+ EXPECT_LE(span1, span1);
+ EXPECT_LE(span1, span2);
+
+ static constexpr int kArray3[] = {2, 3, 5, 7, 10};
+ constexpr span<const int> span3(kArray3);
+
+ EXPECT_FALSE(span1 <= span3);
+
+ static double kArray4[] = {2.0, 3.0, 5.0, 7.0, 11.0, 13.0};
+ span<double, 6> span4(kArray4);
+
+ EXPECT_LE(span3, span4);
+}
+
+TEST(SpanTest, GreaterThan) {
+ static constexpr int kArray1[] = {2, 3, 5, 7, 11, 13};
+ static constexpr int kArray2[] = {2, 3, 5, 7, 11};
+ constexpr span<const int> span1(kArray1);
+ constexpr span<const int, 5> span2(kArray2);
+
+ EXPECT_GT(span1, span2);
+
+ static constexpr int kArray3[] = {2, 3, 5, 7, 11, 13};
+ constexpr span<const int> span3(kArray3);
+
+ EXPECT_FALSE(span1 > span3);
+
+ static double kArray4[] = {2.0, 3.0, 5.0, 7.0, 11.0};
+ span<double, 5> span4(kArray4);
+
+ EXPECT_GT(span3, span4);
+}
+
+TEST(SpanTest, GreaterEqual) {
+ static constexpr int kArray1[] = {2, 3, 5, 7, 11, 13};
+ static constexpr int kArray2[] = {2, 3, 5, 7, 11};
+ constexpr span<const int> span1(kArray1);
+ constexpr span<const int, 5> span2(kArray2);
+
+ EXPECT_GE(span1, span1);
+ EXPECT_GE(span1, span2);
+
+ static constexpr int kArray3[] = {2, 3, 5, 7, 12};
+ constexpr span<const int> span3(kArray3);
+
+ EXPECT_FALSE(span1 >= span3);
+
+ static double kArray4[] = {2.0, 3.0, 5.0, 7.0, 11.0};
+ span<double, 5> span4(kArray4);
+
+ EXPECT_GE(span3, span4);
+}
+
+TEST(SpanTest, AsBytes) {
+ {
+ constexpr int kArray[] = {2, 3, 5, 7, 11, 13};
+ span<const uint8_t, sizeof(kArray)> bytes_span =
+ as_bytes(make_span(kArray));
+ EXPECT_EQ(reinterpret_cast<const uint8_t*>(kArray), bytes_span.data());
+ EXPECT_EQ(sizeof(kArray), bytes_span.size());
+ EXPECT_EQ(bytes_span.size(), bytes_span.size_bytes());
+ }
+
+ {
+ std::vector<int> vec = {1, 1, 2, 3, 5, 8};
+ span<int> mutable_span(vec);
+ span<const uint8_t> bytes_span = as_bytes(mutable_span);
+ EXPECT_EQ(reinterpret_cast<const uint8_t*>(vec.data()), bytes_span.data());
+ EXPECT_EQ(sizeof(int) * vec.size(), bytes_span.size());
+ EXPECT_EQ(bytes_span.size(), bytes_span.size_bytes());
+ }
+}
+
+TEST(SpanTest, AsWritableBytes) {
+ std::vector<int> vec = {1, 1, 2, 3, 5, 8};
+ span<int> mutable_span(vec);
+ span<uint8_t> writable_bytes_span = as_writable_bytes(mutable_span);
+ EXPECT_EQ(reinterpret_cast<uint8_t*>(vec.data()), writable_bytes_span.data());
+ EXPECT_EQ(sizeof(int) * vec.size(), writable_bytes_span.size());
+ EXPECT_EQ(writable_bytes_span.size(), writable_bytes_span.size_bytes());
+
+ // Set the first entry of vec to zero while writing through the span.
+ std::fill(writable_bytes_span.data(),
+ writable_bytes_span.data() + sizeof(int), 0);
+ EXPECT_EQ(0, vec[0]);
+}
+
+TEST(SpanTest, MakeSpanFromDataAndSize) {
+ int* nullint = nullptr;
+ auto empty_span = make_span(nullint, 0);
+ EXPECT_TRUE(empty_span.empty());
+ EXPECT_EQ(nullptr, empty_span.data());
+
+ std::vector<int> vector = {1, 1, 2, 3, 5, 8};
+ span<int> span(vector.data(), vector.size());
+ auto made_span = make_span(vector.data(), vector.size());
+ EXPECT_EQ(span, made_span);
+ static_assert(decltype(made_span)::extent == dynamic_extent, "");
+}
+
+TEST(SpanTest, MakeSpanFromPointerPair) {
+ int* nullint = nullptr;
+ auto empty_span = make_span(nullint, nullint);
+ EXPECT_TRUE(empty_span.empty());
+ EXPECT_EQ(nullptr, empty_span.data());
+
+ std::vector<int> vector = {1, 1, 2, 3, 5, 8};
+ span<int> span(vector.data(), vector.size());
+ auto made_span = make_span(vector.data(), vector.data() + vector.size());
+ EXPECT_EQ(span, made_span);
+ static_assert(decltype(made_span)::extent == dynamic_extent, "");
+}
+
+TEST(SpanTest, MakeSpanFromConstexprArray) {
+ static constexpr int kArray[] = {1, 2, 3, 4, 5};
+ constexpr span<const int> span(kArray);
+ EXPECT_EQ(span, make_span(kArray));
+ static_assert(decltype(make_span(kArray))::extent == 5, "");
+}
+
+TEST(SpanTest, MakeSpanFromStdArray) {
+ const std::array<int, 5> kArray = {{1, 2, 3, 4, 5}};
+ span<const int> span(kArray);
+ EXPECT_EQ(span, make_span(kArray));
+ static_assert(decltype(make_span(kArray))::extent == 5, "");
+}
+
+TEST(SpanTest, MakeSpanFromConstContainer) {
+ const std::vector<int> vector = {-1, -2, -3, -4, -5};
+ span<const int> span(vector);
+ EXPECT_EQ(span, make_span(vector));
+ static_assert(decltype(make_span(vector))::extent == dynamic_extent, "");
+}
+
+TEST(SpanTest, MakeSpanFromContainer) {
+ std::vector<int> vector = {-1, -2, -3, -4, -5};
+ span<int> span(vector);
+ EXPECT_EQ(span, make_span(vector));
+ static_assert(decltype(make_span(vector))::extent == dynamic_extent, "");
+}
+
+TEST(SpanTest, MakeSpanFromDynamicSpan) {
+ static constexpr int kArray[] = {1, 2, 3, 4, 5};
+ constexpr span<const int> span(kArray);
+ static_assert(std::is_same<decltype(span)::element_type,
+ decltype(make_span(span))::element_type>::value,
+ "make_span(span) should have the same element_type as span");
+
+ static_assert(span.data() == make_span(span).data(),
+ "make_span(span) should have the same data() as span");
+
+ static_assert(span.size() == make_span(span).size(),
+ "make_span(span) should have the same size() as span");
+
+ static_assert(decltype(make_span(span))::extent == decltype(span)::extent,
+ "make_span(span) should have the same extent as span");
+}
+
+TEST(SpanTest, MakeSpanFromStaticSpan) {
+ static constexpr int kArray[] = {1, 2, 3, 4, 5};
+ constexpr span<const int, 5> span(kArray);
+ static_assert(std::is_same<decltype(span)::element_type,
+ decltype(make_span(span))::element_type>::value,
+ "make_span(span) should have the same element_type as span");
+
+ static_assert(span.data() == make_span(span).data(),
+ "make_span(span) should have the same data() as span");
+
+ static_assert(span.size() == make_span(span).size(),
+ "make_span(span) should have the same size() as span");
+
+ static_assert(decltype(make_span(span))::extent == decltype(span)::extent,
+ "make_span(span) should have the same extent as span");
+}
+
+TEST(SpanTest, EnsureConstexprGoodness) {
+ static constexpr int kArray[] = {5, 4, 3, 2, 1};
+ constexpr span<const int> constexpr_span(kArray);
+ const size_t size = 2;
+
+ const size_t start = 1;
+ constexpr span<const int> subspan =
+ constexpr_span.subspan(start, start + size);
+ for (size_t i = 0; i < subspan.size(); ++i)
+ EXPECT_EQ(kArray[start + i], subspan[i]);
+
+ constexpr span<const int> firsts = constexpr_span.first(size);
+ for (size_t i = 0; i < firsts.size(); ++i)
+ EXPECT_EQ(kArray[i], firsts[i]);
+
+ constexpr span<const int> lasts = constexpr_span.last(size);
+ for (size_t i = 0; i < lasts.size(); ++i) {
+ const size_t j = (arraysize(kArray) - size) + i;
+ EXPECT_EQ(kArray[j], lasts[i]);
+ }
+
+ constexpr int item = constexpr_span[size];
+ EXPECT_EQ(kArray[size], item);
+}
+
+} // namespace base
diff --git a/base/containers/span_unittest.nc b/base/containers/span_unittest.nc
new file mode 100644
index 0000000000..0d2af89aec
--- /dev/null
+++ b/base/containers/span_unittest.nc
@@ -0,0 +1,167 @@
+// 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.
+
+// This is a "No Compile Test" suite.
+// http://dev.chromium.org/developers/testing/no-compile-tests
+
+#include "base/containers/span.h"
+
+#include <array>
+#include <set>
+#include <vector>
+
+namespace base {
+
+class Base {
+};
+
+class Derived : Base {
+};
+
+#if defined(NCTEST_DEFAULT_SPAN_WITH_NON_ZERO_STATIC_EXTENT_DISALLOWED) // [r"fatal error: static_assert failed \"Invalid Extent\""]
+
+// A default constructed span must have an extent of 0 or dynamic_extent.
+void WontCompile() {
+ span<int, 1> span;
+}
+
+#elif defined(NCTEST_SPAN_FROM_ARRAY_WITH_NON_MATCHING_STATIC_EXTENT_DISALLOWED) // [r"fatal error: no matching constructor for initialization of 'span<int, 1>'"]
+
+// A span with static extent constructed from an array must match the size of
+// the array.
+void WontCompile() {
+ int array[] = {1, 2, 3};
+ span<int, 1> span(array);
+}
+
+#elif defined(NCTEST_SPAN_FROM_STD_ARRAY_WITH_NON_MATCHING_STATIC_EXTENT_DISALLOWED) // [r"fatal error: no matching constructor for initialization of 'span<int, 2>'"]
+
+// A span with static extent constructed from std::array must match the size of
+// the array.
+void WontCompile() {
+ std::array<int, 3> array = {1, 2, 3};
+ span<int, 2> span(array);
+}
+
+#elif defined(NCTEST_SPAN_FROM_CONST_STD_ARRAY_WITH_NON_MATCHING_STATIC_EXTENT_DISALLOWED) // [r"fatal error: no matching constructor for initialization of 'span<const int, 2>'"]
+
+// A span with static extent constructed from std::array must match the size of
+// the array.
+void WontCompile() {
+ const std::array<int, 3> array = {1, 2, 3};
+ span<const int, 2> span(array);
+}
+
+#elif defined(NCTEST_SPAN_FROM_OTHER_SPAN_WITH_MISMATCHING_EXTENT_DISALLOWED) // [r"fatal error: no matching constructor for initialization of 'span<int, 4>'"]
+
+// A span with static extent constructed from another span must match the
+// extent.
+void WontCompile() {
+ std::array<int, 3> array = {1, 2, 3};
+ span<int, 3> span3(array);
+ span<int, 4> span4(span3);
+}
+
+#elif defined(NCTEST_DYNAMIC_SPAN_TO_STATIC_SPAN_DISALLOWED) // [r"fatal error: no matching constructor for initialization of 'span<int, 3>'"]
+
+// Converting a dynamic span to a static span should not be allowed.
+void WontCompile() {
+ span<int> dynamic_span;
+ span<int, 3> static_span(dynamic_span);
+}
+
+#elif defined(NCTEST_DERIVED_TO_BASE_CONVERSION_DISALLOWED) // [r"fatal error: no matching constructor for initialization of 'span<base::Base \*>'"]
+
+// Internally, this is represented as a pointer to pointers to Derived. An
+// implicit conversion to a pointer to pointers to Base must not be allowed.
+// If it were allowed, then something like this would be possible.
+// Cat** cats = GetCats();
+// Animals** animals = cats;
+// animals[0] = new Dog(); // Uhoh!
+void WontCompile() {
+ span<Derived*> derived_span;
+ span<Base*> base_span(derived_span);
+}
+
+#elif defined(NCTEST_PTR_TO_CONSTPTR_CONVERSION_DISALLOWED) // [r"fatal error: no matching constructor for initialization of 'span<const int \*>'"]
+
+// Similarly, converting a span<int*> to span<const int*> requires internally
+// converting T** to const T**. This is also disallowed, as it would allow code
+// to violate the contract of const.
+void WontCompile() {
+ span<int*> non_const_span;
+ span<const int*> const_span(non_const_span);
+}
+
+#elif defined(NCTEST_CONST_CONTAINER_TO_MUTABLE_CONVERSION_DISALLOWED) // [r"fatal error: no matching constructor for initialization of 'span<int>'"]
+
+// A const container should not be convertible to a mutable span.
+void WontCompile() {
+ const std::vector<int> v = {1, 2, 3};
+ span<int> span(v);
+}
+
+#elif defined(NCTEST_STD_SET_CONVERSION_DISALLOWED) // [r"fatal error: no matching constructor for initialization of 'span<int>'"]
+
+// A std::set() should not satisfy the requirements for conversion to a span.
+void WontCompile() {
+ std::set<int> set;
+ span<int> span(set);
+}
+
+#elif defined(NCTEST_STATIC_FRONT_WITH_EXCEEDING_COUNT_DISALLOWED) // [r"fatal error: static_assert failed \"Count must not exceed Extent\""]
+
+// Static first called on a span with static extent must not exceed the size.
+void WontCompile() {
+ std::array<int, 3> array = {1, 2, 3};
+ span<int, 3> span(array);
+ auto first = span.first<4>();
+}
+
+#elif defined(NCTEST_STATIC_LAST_WITH_EXCEEDING_COUNT_DISALLOWED) // [r"fatal error: static_assert failed \"Count must not exceed Extent\""]
+
+// Static last called on a span with static extent must not exceed the size.
+void WontCompile() {
+ std::array<int, 3> array = {1, 2, 3};
+ span<int, 3> span(array);
+ auto last = span.last<4>();
+}
+
+#elif defined(NCTEST_STATIC_SUBSPAN_WITH_EXCEEDING_OFFSET_DISALLOWED) // [r"fatal error: static_assert failed \"Offset must not exceed Extent\""]
+
+// Static subspan called on a span with static extent must not exceed the size.
+void WontCompile() {
+ std::array<int, 3> array = {1, 2, 3};
+ span<int, 3> span(array);
+ auto subspan = span.subspan<4>();
+}
+
+#elif defined(NCTEST_STATIC_SUBSPAN_WITH_EXCEEDING_COUNT_DISALLOWED) // [r"fatal error: static_assert failed \"Count must not exceed Extent - Offset\""]
+
+// Static subspan called on a span with static extent must not exceed the size.
+void WontCompile() {
+ std::array<int, 3> array = {1, 2, 3};
+ span<int, 3> span(array);
+ auto subspan = span.subspan<0, 4>();
+}
+
+#elif defined(NCTEST_AS_WRITABLE_BYTES_WITH_CONST_CONTAINER_DISALLOWED) // [r"fatal error: no matching function for call to 'as_writable_bytes'"]
+
+// as_writable_bytes should not be possible for a const container.
+void WontCompile() {
+ const std::vector<int> v = {1, 2, 3};
+ span<uint8_t> bytes = as_writable_bytes(make_span(v));
+}
+
+#elif defined(NCTEST_MAKE_SPAN_FROM_SET_CONVERSION_DISALLOWED) // [r"fatal error: no matching function for call to 'make_span'"]
+
+// A std::set() should not satisfy the requirements for conversion to a span.
+void WontCompile() {
+ std::set<int> set;
+ auto span = make_span(set);
+}
+
+#endif
+
+} // namespace base
diff --git a/base/containers/stack.h b/base/containers/stack.h
new file mode 100644
index 0000000000..5cf06f8251
--- /dev/null
+++ b/base/containers/stack.h
@@ -0,0 +1,23 @@
+// 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_CONTAINERS_STACK_H_
+#define BASE_CONTAINERS_STACK_H_
+
+#include <stack>
+
+#include "base/containers/circular_deque.h"
+
+namespace base {
+
+// Provides a definition of base::stack that's like std::stack but uses a
+// base::circular_deque instead of std::deque. Since std::stack is just a
+// wrapper for an underlying type, we can just provide a typedef for it that
+// defaults to the base circular_deque.
+template <class T, class Container = circular_deque<T>>
+using stack = std::stack<T, Container>;
+
+} // namespace base
+
+#endif // BASE_CONTAINERS_STACK_H_
diff --git a/base/containers/stack_container.h b/base/containers/stack_container.h
index 9e0efc13b4..c775744305 100644
--- a/base/containers/stack_container.h
+++ b/base/containers/stack_container.h
@@ -7,12 +7,9 @@
#include <stddef.h>
-#include <string>
#include <vector>
#include "base/macros.h"
-#include "base/memory/aligned_memory.h"
-#include "base/strings/string16.h"
#include "build/build_config.h"
namespace base {
@@ -49,17 +46,17 @@ class StackAllocator : public std::allocator<T> {
}
// Casts the buffer in its right type.
- T* stack_buffer() { return stack_buffer_.template data_as<T>(); }
+ T* stack_buffer() { return reinterpret_cast<T*>(stack_buffer_); }
const T* stack_buffer() const {
- return stack_buffer_.template data_as<T>();
+ return reinterpret_cast<const T*>(&stack_buffer_);
}
// The buffer itself. It is not of type T because we don't want the
// constructors and destructors to be automatically called. Define a POD
// buffer of the right size instead.
- base::AlignedMemory<sizeof(T[stack_capacity]), ALIGNOF(T)> stack_buffer_;
+ alignas(T) char stack_buffer_[sizeof(T[stack_capacity])];
#if defined(__GNUC__) && !defined(ARCH_CPU_X86_FAMILY)
- static_assert(ALIGNOF(T) <= 16, "http://crbug.com/115612");
+ static_assert(alignof(T) <= 16, "http://crbug.com/115612");
#endif
// Set when the stack buffer is used for an allocation. We do not track
@@ -133,6 +130,10 @@ class StackAllocator : public std::allocator<T> {
// stack capacity will transparently overflow onto the heap. The container must
// support reserve().
//
+// This will not work with std::string since some implementations allocate
+// more bytes than requested in calls to reserve(), forcing the allocation onto
+// the heap. http://crbug.com/709273
+//
// WATCH OUT: the ContainerType MUST use the proper StackAllocator for this
// type. This object is really intended to be used only internally. You'll want
// to use the wrappers below for different types.
@@ -182,46 +183,6 @@ class StackContainer {
DISALLOW_COPY_AND_ASSIGN(StackContainer);
};
-// StackString -----------------------------------------------------------------
-
-template<size_t stack_capacity>
-class StackString : public StackContainer<
- std::basic_string<char,
- std::char_traits<char>,
- StackAllocator<char, stack_capacity> >,
- stack_capacity> {
- public:
- StackString() : StackContainer<
- std::basic_string<char,
- std::char_traits<char>,
- StackAllocator<char, stack_capacity> >,
- stack_capacity>() {
- }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(StackString);
-};
-
-// StackStrin16 ----------------------------------------------------------------
-
-template<size_t stack_capacity>
-class StackString16 : public StackContainer<
- std::basic_string<char16,
- base::string16_char_traits,
- StackAllocator<char16, stack_capacity> >,
- stack_capacity> {
- public:
- StackString16() : StackContainer<
- std::basic_string<char16,
- base::string16_char_traits,
- StackAllocator<char16, stack_capacity> >,
- stack_capacity>() {
- }
-
- private:
- DISALLOW_COPY_AND_ASSIGN(StackString16);
-};
-
// StackVector -----------------------------------------------------------------
// Example:
diff --git a/base/containers/vector_buffer.h b/base/containers/vector_buffer.h
new file mode 100644
index 0000000000..a72c1ed95e
--- /dev/null
+++ b/base/containers/vector_buffer.h
@@ -0,0 +1,163 @@
+// 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_CONTAINERS_VECTOR_BUFFERS_H_
+#define BASE_CONTAINERS_VECTOR_BUFFERS_H_
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <type_traits>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/macros.h"
+
+namespace base {
+namespace internal {
+
+// Internal implementation detail of base/containers.
+//
+// Implements a vector-like buffer that holds a certain capacity of T. Unlike
+// std::vector, VectorBuffer never constructs or destructs its arguments, and
+// can't change sizes. But it does implement templates to assist in efficient
+// moving and destruction of those items manually.
+//
+// In particular, the destructor function does not iterate over the items if
+// there is no destructor. Moves should be implemented as a memcpy/memmove for
+// trivially copyable objects (POD) otherwise, it should be a std::move if
+// possible, and as a last resort it falls back to a copy. This behavior is
+// similar to std::vector.
+//
+// No special consideration is done for noexcept move constructors since
+// we compile without exceptions.
+//
+// The current API does not support moving overlapping ranges.
+template <typename T>
+class VectorBuffer {
+ public:
+ constexpr VectorBuffer() = default;
+
+#if defined(__clang__) && !defined(__native_client__)
+ // This constructor converts an uninitialized void* to a T* which triggers
+ // clang Control Flow Integrity. Since this is as-designed, disable.
+ __attribute__((no_sanitize("cfi-unrelated-cast", "vptr")))
+#endif
+ VectorBuffer(size_t count)
+ : buffer_(reinterpret_cast<T*>(malloc(sizeof(T) * count))),
+ capacity_(count) {
+ }
+ VectorBuffer(VectorBuffer&& other) noexcept
+ : buffer_(other.buffer_), capacity_(other.capacity_) {
+ other.buffer_ = nullptr;
+ other.capacity_ = 0;
+ }
+
+ ~VectorBuffer() { free(buffer_); }
+
+ VectorBuffer& operator=(VectorBuffer&& other) {
+ free(buffer_);
+ buffer_ = other.buffer_;
+ capacity_ = other.capacity_;
+
+ other.buffer_ = nullptr;
+ other.capacity_ = 0;
+ return *this;
+ }
+
+ size_t capacity() const { return capacity_; }
+
+ T& operator[](size_t i) { return buffer_[i]; }
+ const T& operator[](size_t i) const { return buffer_[i]; }
+ T* begin() { return buffer_; }
+ T* end() { return &buffer_[capacity_]; }
+
+ // DestructRange ------------------------------------------------------------
+
+ // Trivially destructible objects need not have their destructors called.
+ template <typename T2 = T,
+ typename std::enable_if<std::is_trivially_destructible<T2>::value,
+ int>::type = 0>
+ void DestructRange(T* begin, T* end) {}
+
+ // Non-trivially destructible objects must have their destructors called
+ // individually.
+ template <typename T2 = T,
+ typename std::enable_if<!std::is_trivially_destructible<T2>::value,
+ int>::type = 0>
+ void DestructRange(T* begin, T* end) {
+ while (begin != end) {
+ begin->~T();
+ begin++;
+ }
+ }
+
+ // MoveRange ----------------------------------------------------------------
+ //
+ // The destructor will be called (as necessary) for all moved types. The
+ // ranges must not overlap.
+ //
+ // The parameters and begin and end (one past the last) of the input buffer,
+ // and the address of the first element to copy to. There must be sufficient
+ // room in the destination for all items in the range [begin, end).
+
+ // Trivially copyable types can use memcpy. trivially copyable implies
+ // that there is a trivial destructor as we don't have to call it.
+ template <typename T2 = T,
+ typename std::enable_if<base::is_trivially_copyable<T2>::value,
+ int>::type = 0>
+ static void MoveRange(T* from_begin, T* from_end, T* to) {
+ DCHECK(!RangesOverlap(from_begin, from_end, to));
+ memcpy(to, from_begin, (from_end - from_begin) * sizeof(T));
+ }
+
+ // Not trivially copyable, but movable: call the move constructor and
+ // destruct the original.
+ template <typename T2 = T,
+ typename std::enable_if<std::is_move_constructible<T2>::value &&
+ !base::is_trivially_copyable<T2>::value,
+ int>::type = 0>
+ static void MoveRange(T* from_begin, T* from_end, T* to) {
+ DCHECK(!RangesOverlap(from_begin, from_end, to));
+ while (from_begin != from_end) {
+ new (to) T(std::move(*from_begin));
+ from_begin->~T();
+ from_begin++;
+ to++;
+ }
+ }
+
+ // Not movable, not trivially copyable: call the copy constructor and
+ // destruct the original.
+ template <typename T2 = T,
+ typename std::enable_if<!std::is_move_constructible<T2>::value &&
+ !base::is_trivially_copyable<T2>::value,
+ int>::type = 0>
+ static void MoveRange(T* from_begin, T* from_end, T* to) {
+ DCHECK(!RangesOverlap(from_begin, from_end, to));
+ while (from_begin != from_end) {
+ new (to) T(*from_begin);
+ from_begin->~T();
+ from_begin++;
+ to++;
+ }
+ }
+
+ private:
+ static bool RangesOverlap(const T* from_begin,
+ const T* from_end,
+ const T* to) {
+ return !(to >= from_end || to + (from_end - from_begin) <= from_begin);
+ }
+
+ T* buffer_ = nullptr;
+ size_t capacity_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(VectorBuffer);
+};
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_CONTAINERS_VECTOR_BUFFERS_H_
diff --git a/base/containers/vector_buffer_unittest.cc b/base/containers/vector_buffer_unittest.cc
new file mode 100644
index 0000000000..6d49505d40
--- /dev/null
+++ b/base/containers/vector_buffer_unittest.cc
@@ -0,0 +1,89 @@
+// 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/containers/vector_buffer.h"
+
+#include "base/test/copy_only_int.h"
+#include "base/test/move_only_int.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace internal {
+
+TEST(VectorBuffer, DeletePOD) {
+ constexpr int size = 10;
+ VectorBuffer<int> buffer(size);
+ for (int i = 0; i < size; i++)
+ buffer.begin()[i] = i + 1;
+
+ buffer.DestructRange(buffer.begin(), buffer.end());
+
+ // Delete should do nothing.
+ for (int i = 0; i < size; i++)
+ EXPECT_EQ(i + 1, buffer.begin()[i]);
+}
+
+TEST(VectorBuffer, DeleteMoveOnly) {
+ constexpr int size = 10;
+ VectorBuffer<MoveOnlyInt> buffer(size);
+ for (int i = 0; i < size; i++)
+ new (buffer.begin() + i) MoveOnlyInt(i + 1);
+
+ buffer.DestructRange(buffer.begin(), buffer.end());
+
+ // Delete should have reset all of the values to 0.
+ for (int i = 0; i < size; i++)
+ EXPECT_EQ(0, buffer.begin()[i].data());
+}
+
+TEST(VectorBuffer, PODMove) {
+ constexpr int size = 10;
+ VectorBuffer<int> dest(size);
+
+ VectorBuffer<int> original(size);
+ for (int i = 0; i < size; i++)
+ original.begin()[i] = i + 1;
+
+ original.MoveRange(original.begin(), original.end(), dest.begin());
+ for (int i = 0; i < size; i++)
+ EXPECT_EQ(i + 1, dest.begin()[i]);
+}
+
+TEST(VectorBuffer, MovableMove) {
+ constexpr int size = 10;
+ VectorBuffer<MoveOnlyInt> dest(size);
+
+ VectorBuffer<MoveOnlyInt> original(size);
+ for (int i = 0; i < size; i++)
+ new (original.begin() + i) MoveOnlyInt(i + 1);
+
+ original.MoveRange(original.begin(), original.end(), dest.begin());
+
+ // Moving from a MoveOnlyInt resets to 0.
+ for (int i = 0; i < size; i++) {
+ EXPECT_EQ(0, original.begin()[i].data());
+ EXPECT_EQ(i + 1, dest.begin()[i].data());
+ }
+}
+
+TEST(VectorBuffer, CopyToMove) {
+ constexpr int size = 10;
+ VectorBuffer<CopyOnlyInt> dest(size);
+
+ VectorBuffer<CopyOnlyInt> original(size);
+ for (int i = 0; i < size; i++)
+ new (original.begin() + i) CopyOnlyInt(i + 1);
+
+ original.MoveRange(original.begin(), original.end(), dest.begin());
+
+ // The original should have been destructed, which should reset the value to
+ // 0. Technically this dereferences the destructed object.
+ for (int i = 0; i < size; i++) {
+ EXPECT_EQ(0, original.begin()[i].data());
+ EXPECT_EQ(i + 1, dest.begin()[i].data());
+ }
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/cpu.cc b/base/cpu.cc
index 848208f7c1..cd9066f53a 100644
--- a/base/cpu.cc
+++ b/base/cpu.cc
@@ -10,6 +10,7 @@
#include <string.h>
#include <algorithm>
+#include <utility>
#include "base/macros.h"
#include "build/build_config.h"
@@ -19,7 +20,7 @@
#endif
#if defined(ARCH_CPU_X86_FAMILY)
-#if defined(_MSC_VER)
+#if defined(COMPILER_MSVC)
#include <intrin.h>
#include <immintrin.h> // For _xgetbv()
#endif
@@ -54,7 +55,7 @@ CPU::CPU()
namespace {
#if defined(ARCH_CPU_X86_FAMILY)
-#ifndef _MSC_VER
+#if !defined(COMPILER_MSVC)
#if defined(__pic__) && defined(__i386__)
@@ -89,7 +90,7 @@ uint64_t _xgetbv(uint32_t xcr) {
return (static_cast<uint64_t>(edx) << 32) | eax;
}
-#endif // !_MSC_VER
+#endif // !defined(COMPILER_MSVC)
#endif // ARCH_CPU_X86_FAMILY
#if defined(ARCH_CPU_ARM_FAMILY) && (defined(OS_ANDROID) || defined(OS_LINUX))
@@ -106,17 +107,14 @@ std::string* CpuInfoBrand() {
std::string contents;
ReadFileToString(FilePath("/proc/cpuinfo"), &contents);
DCHECK(!contents.empty());
- if (contents.empty()) {
- return new std::string();
- }
std::istringstream iss(contents);
std::string line;
while (std::getline(iss, line)) {
- if ((line.compare(0, strlen(kModelNamePrefix), kModelNamePrefix) == 0 ||
- line.compare(0, strlen(kProcessorPrefix), kProcessorPrefix) == 0)) {
+ if (line.compare(0, strlen(kModelNamePrefix), kModelNamePrefix) == 0)
return new std::string(line.substr(strlen(kModelNamePrefix)));
- }
+ if (line.compare(0, strlen(kProcessorPrefix), kProcessorPrefix) == 0)
+ return new std::string(line.substr(strlen(kProcessorPrefix)));
}
return new std::string();
@@ -127,12 +125,16 @@ std::string* CpuInfoBrand() {
#endif // defined(ARCH_CPU_ARM_FAMILY) && (defined(OS_ANDROID) ||
// defined(OS_LINUX))
-} // anonymous namespace
+} // namespace
void CPU::Initialize() {
#if defined(ARCH_CPU_X86_FAMILY)
int cpu_info[4] = {-1};
- char cpu_string[48];
+ // This array is used to temporarily hold the vendor name and then the brand
+ // name. Thus it has to be big enough for both use cases. There are
+ // static_asserts below for each of the use cases to make sure this array is
+ // big enough.
+ char cpu_string[sizeof(cpu_info) * 3 + 1];
// __cpuid with an InfoType argument of 0 returns the number of
// valid Ids in CPUInfo[0] and the CPU identification string in
@@ -140,12 +142,16 @@ void CPU::Initialize() {
// not in linear order. The code below arranges the information
// in a human readable form. The human readable order is CPUInfo[1] |
// CPUInfo[3] | CPUInfo[2]. CPUInfo[2] and CPUInfo[3] are swapped
- // before using memcpy to copy these three array elements to cpu_string.
+ // before using memcpy() to copy these three array elements to |cpu_string|.
__cpuid(cpu_info, 0);
int num_ids = cpu_info[0];
std::swap(cpu_info[2], cpu_info[3]);
- memcpy(cpu_string, &cpu_info[1], 3 * sizeof(cpu_info[1]));
- cpu_vendor_.assign(cpu_string, 3 * sizeof(cpu_info[1]));
+ static constexpr size_t kVendorNameSize = 3 * sizeof(cpu_info[1]);
+ static_assert(kVendorNameSize < arraysize(cpu_string),
+ "cpu_string too small");
+ memcpy(cpu_string, &cpu_info[1], kVendorNameSize);
+ cpu_string[kVendorNameSize] = '\0';
+ cpu_vendor_ = cpu_string;
// Interpret CPU feature information.
if (num_ids > 0) {
@@ -191,28 +197,33 @@ void CPU::Initialize() {
// Get the brand string of the cpu.
__cpuid(cpu_info, 0x80000000);
- const int parameter_end = 0x80000004;
- int max_parameter = cpu_info[0];
-
- if (cpu_info[0] >= parameter_end) {
- char* cpu_string_ptr = cpu_string;
-
- for (int parameter = 0x80000002; parameter <= parameter_end &&
- cpu_string_ptr < &cpu_string[sizeof(cpu_string)]; parameter++) {
+ const int max_parameter = cpu_info[0];
+
+ static constexpr int kParameterStart = 0x80000002;
+ static constexpr int kParameterEnd = 0x80000004;
+ static constexpr int kParameterSize = kParameterEnd - kParameterStart + 1;
+ static_assert(kParameterSize * sizeof(cpu_info) + 1 == arraysize(cpu_string),
+ "cpu_string has wrong size");
+
+ if (max_parameter >= kParameterEnd) {
+ size_t i = 0;
+ for (int parameter = kParameterStart; parameter <= kParameterEnd;
+ ++parameter) {
__cpuid(cpu_info, parameter);
- memcpy(cpu_string_ptr, cpu_info, sizeof(cpu_info));
- cpu_string_ptr += sizeof(cpu_info);
+ memcpy(&cpu_string[i], cpu_info, sizeof(cpu_info));
+ i += sizeof(cpu_info);
}
- cpu_brand_.assign(cpu_string, cpu_string_ptr - cpu_string);
+ cpu_string[i] = '\0';
+ cpu_brand_ = cpu_string;
}
- const int parameter_containing_non_stop_time_stamp_counter = 0x80000007;
- if (max_parameter >= parameter_containing_non_stop_time_stamp_counter) {
- __cpuid(cpu_info, parameter_containing_non_stop_time_stamp_counter);
+ static constexpr int kParameterContainingNonStopTimeStampCounter = 0x80000007;
+ if (max_parameter >= kParameterContainingNonStopTimeStampCounter) {
+ __cpuid(cpu_info, kParameterContainingNonStopTimeStampCounter);
has_non_stop_time_stamp_counter_ = (cpu_info[3] & (1 << 8)) != 0;
}
#elif defined(ARCH_CPU_ARM_FAMILY) && (defined(OS_ANDROID) || defined(OS_LINUX))
- cpu_brand_.assign(*CpuInfoBrand());
+ cpu_brand_ = *CpuInfoBrand();
#endif
}
diff --git a/base/cpu.h b/base/cpu.h
index 0e24df61dd..2c6caeafdd 100644
--- a/base/cpu.h
+++ b/base/cpu.h
@@ -12,9 +12,8 @@
namespace base {
// Query information about the processor.
-class BASE_EXPORT CPU {
+class BASE_EXPORT CPU final {
public:
- // Constructor
CPU();
enum IntelMicroArchitecture {
diff --git a/base/cpu_unittest.cc b/base/cpu_unittest.cc
index 9cabfd6998..8a68ea0781 100644
--- a/base/cpu_unittest.cc
+++ b/base/cpu_unittest.cc
@@ -3,8 +3,8 @@
// found in the LICENSE file.
#include "base/cpu.h"
+#include "base/stl_util.h"
#include "build/build_config.h"
-
#include "testing/gtest/include/gtest/gtest.h"
#if _MSC_VER >= 1700
@@ -125,3 +125,10 @@ TEST(CPU, RunExtendedInstructions) {
#endif // defined(COMPILER_GCC)
#endif // defined(ARCH_CPU_X86_FAMILY)
}
+
+// For https://crbug.com/249713
+TEST(CPU, BrandAndVendorContainsNoNUL) {
+ base::CPU cpu;
+ EXPECT_FALSE(base::ContainsValue(cpu.cpu_brand(), '\0'));
+ EXPECT_FALSE(base::ContainsValue(cpu.vendor_name(), '\0'));
+}
diff --git a/base/debug/activity_tracker.cc b/base/debug/activity_tracker.cc
index 5081c1c9d2..24cfa5a47e 100644
--- a/base/debug/activity_tracker.cc
+++ b/base/debug/activity_tracker.cc
@@ -25,6 +25,7 @@
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/platform_thread.h"
+#include "build/build_config.h"
namespace base {
namespace debug {
@@ -38,7 +39,6 @@ const int kMinStackDepth = 2;
// pairs) globally or associated with ActivityData entries.
const size_t kUserDataSize = 1 << 10; // 1 KiB
const size_t kProcessDataSize = 4 << 10; // 4 KiB
-const size_t kGlobalDataSize = 16 << 10; // 16 KiB
const size_t kMaxUserDataNameLength =
static_cast<size_t>(std::numeric_limits<uint8_t>::max());
@@ -50,22 +50,7 @@ const char kProcessPhaseDataKey[] = "process-phase";
// An atomically incrementing number, used to check for recreations of objects
// in the same memory space.
-StaticAtomicSequenceNumber g_next_id;
-
-union ThreadRef {
- int64_t as_id;
-#if defined(OS_WIN)
- // On Windows, the handle itself is often a pseudo-handle with a common
- // value meaning "this thread" and so the thread-id is used. The former
- // can be converted to a thread-id with a system call.
- PlatformThreadId as_tid;
-#elif defined(OS_POSIX)
- // On Posix, the handle is always a unique identifier so no conversion
- // needs to be done. However, it's value is officially opaque so there
- // is no one correct way to convert it to a numerical identifier.
- PlatformThreadHandle::Handle as_handle;
-#endif
-};
+AtomicSequenceNumber g_next_id;
// Gets the next non-zero identifier. It is only unique within a process.
uint32_t GetNextDataId() {
@@ -121,8 +106,23 @@ Time WallTimeFromTickTime(int64_t ticks_start, int64_t ticks, Time time_start) {
} // namespace
-OwningProcess::OwningProcess() {}
-OwningProcess::~OwningProcess() {}
+union ThreadRef {
+ int64_t as_id;
+#if defined(OS_WIN)
+ // On Windows, the handle itself is often a pseudo-handle with a common
+ // value meaning "this thread" and so the thread-id is used. The former
+ // can be converted to a thread-id with a system call.
+ PlatformThreadId as_tid;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ // On Posix and Fuchsia, the handle is always a unique identifier so no
+ // conversion needs to be done. However, its value is officially opaque so
+ // there is no one correct way to convert it to a numerical identifier.
+ PlatformThreadHandle::Handle as_handle;
+#endif
+};
+
+OwningProcess::OwningProcess() = default;
+OwningProcess::~OwningProcess() = default;
void OwningProcess::Release_Initialize(int64_t pid) {
uint32_t old_id = data_id.load(std::memory_order_acquire);
@@ -186,7 +186,7 @@ ActivityTrackerMemoryAllocator::ActivityTrackerMemoryAllocator(
DCHECK(allocator);
}
-ActivityTrackerMemoryAllocator::~ActivityTrackerMemoryAllocator() {}
+ActivityTrackerMemoryAllocator::~ActivityTrackerMemoryAllocator() = default;
ActivityTrackerMemoryAllocator::Reference
ActivityTrackerMemoryAllocator::GetObjectReference() {
@@ -261,8 +261,9 @@ void Activity::FillFrom(Activity* activity,
activity->activity_type = type;
activity->data = data;
-#if defined(SYZYASAN)
- // Create a stacktrace from the current location and get the addresses.
+#if (!defined(OS_NACL) && DCHECK_IS_ON()) || defined(ADDRESS_SANITIZER)
+ // Create a stacktrace from the current location and get the addresses for
+ // improved debuggability.
StackTrace stack_trace;
size_t stack_depth;
const void* const* stack_addrs = stack_trace.Addresses(&stack_depth);
@@ -277,9 +278,9 @@ void Activity::FillFrom(Activity* activity,
#endif
}
-ActivityUserData::TypedValue::TypedValue() {}
+ActivityUserData::TypedValue::TypedValue() = default;
ActivityUserData::TypedValue::TypedValue(const TypedValue& other) = default;
-ActivityUserData::TypedValue::~TypedValue() {}
+ActivityUserData::TypedValue::~TypedValue() = default;
StringPiece ActivityUserData::TypedValue::Get() const {
DCHECK_EQ(RAW_VALUE, type_);
@@ -324,13 +325,13 @@ StringPiece ActivityUserData::TypedValue::GetStringReference() const {
// These are required because std::atomic is (currently) not a POD type and
// thus clang requires explicit out-of-line constructors and destructors even
// when they do nothing.
-ActivityUserData::ValueInfo::ValueInfo() {}
+ActivityUserData::ValueInfo::ValueInfo() = default;
ActivityUserData::ValueInfo::ValueInfo(ValueInfo&&) = default;
-ActivityUserData::ValueInfo::~ValueInfo() {}
-ActivityUserData::MemoryHeader::MemoryHeader() {}
-ActivityUserData::MemoryHeader::~MemoryHeader() {}
-ActivityUserData::FieldHeader::FieldHeader() {}
-ActivityUserData::FieldHeader::~FieldHeader() {}
+ActivityUserData::ValueInfo::~ValueInfo() = default;
+ActivityUserData::MemoryHeader::MemoryHeader() = default;
+ActivityUserData::MemoryHeader::~MemoryHeader() = default;
+ActivityUserData::FieldHeader::FieldHeader() = default;
+ActivityUserData::FieldHeader::~FieldHeader() = default;
ActivityUserData::ActivityUserData() : ActivityUserData(nullptr, 0, -1) {}
@@ -363,7 +364,7 @@ ActivityUserData::ActivityUserData(void* memory, size_t size, int64_t pid)
ImportExistingData();
}
-ActivityUserData::~ActivityUserData() {}
+ActivityUserData::~ActivityUserData() = default;
bool ActivityUserData::CreateSnapshot(Snapshot* output_snapshot) const {
DCHECK(output_snapshot);
@@ -639,13 +640,11 @@ struct ThreadActivityTracker::Header {
// A memory location used to indicate if changes have been made to the data
// that would invalidate an in-progress read of its contents. The active
- // tracker will zero the value whenever something gets popped from the
- // stack. A monitoring tracker can write a non-zero value here, copy the
- // stack contents, and read the value to know, if it is still non-zero, that
- // the contents didn't change while being copied. This can handle concurrent
- // snapshot operations only if each snapshot writes a different bit (which
- // is not the current implementation so no parallel snapshots allowed).
- std::atomic<uint32_t> data_unchanged;
+ // tracker will increment the value whenever something gets popped from the
+ // stack. A monitoring tracker can check the value before and after access
+ // to know, if it's still the same, that the contents didn't change while
+ // being copied.
+ std::atomic<uint32_t> data_version;
// The last "exception" activity. This can't be stored on the stack because
// that could get popped as things unwind.
@@ -658,8 +657,8 @@ struct ThreadActivityTracker::Header {
char thread_name[32];
};
-ThreadActivityTracker::Snapshot::Snapshot() {}
-ThreadActivityTracker::Snapshot::~Snapshot() {}
+ThreadActivityTracker::Snapshot::Snapshot() = default;
+ThreadActivityTracker::Snapshot::~Snapshot() = default;
ThreadActivityTracker::ScopedActivity::ScopedActivity(
ThreadActivityTracker* tracker,
@@ -688,9 +687,11 @@ ThreadActivityTracker::ThreadActivityTracker(void* base, size_t size)
: header_(static_cast<Header*>(base)),
stack_(reinterpret_cast<Activity*>(reinterpret_cast<char*>(base) +
sizeof(Header))),
+#if DCHECK_IS_ON()
+ thread_id_(PlatformThreadRef()),
+#endif
stack_slots_(
static_cast<uint32_t>((size - sizeof(Header)) / sizeof(Activity))) {
- DCHECK(thread_checker_.CalledOnValidThread());
// Verify the parameters but fail gracefully if they're not valid so that
// production code based on external inputs will not crash. IsValid() will
@@ -727,7 +728,7 @@ ThreadActivityTracker::ThreadActivityTracker(void* base, size_t size)
DCHECK_EQ(0, header_->start_ticks);
DCHECK_EQ(0U, header_->stack_slots);
DCHECK_EQ(0U, header_->current_depth.load(std::memory_order_relaxed));
- DCHECK_EQ(0U, header_->data_unchanged.load(std::memory_order_relaxed));
+ DCHECK_EQ(0U, header_->data_version.load(std::memory_order_relaxed));
DCHECK_EQ(0, stack_[0].time_internal);
DCHECK_EQ(0U, stack_[0].origin_address);
DCHECK_EQ(0U, stack_[0].call_stack[0]);
@@ -735,7 +736,7 @@ ThreadActivityTracker::ThreadActivityTracker(void* base, size_t size)
#if defined(OS_WIN)
header_->thread_ref.as_tid = PlatformThread::CurrentId();
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
header_->thread_ref.as_handle =
PlatformThread::CurrentHandle().platform_handle();
#endif
@@ -759,7 +760,7 @@ ThreadActivityTracker::ThreadActivityTracker(void* base, size_t size)
}
}
-ThreadActivityTracker::~ThreadActivityTracker() {}
+ThreadActivityTracker::~ThreadActivityTracker() = default;
ThreadActivityTracker::ActivityId ThreadActivityTracker::PushActivity(
const void* program_counter,
@@ -768,8 +769,7 @@ ThreadActivityTracker::ActivityId ThreadActivityTracker::PushActivity(
const ActivityData& data) {
// A thread-checker creates a lock to check the thread-id which means
// re-entry into this code if lock acquisitions are being tracked.
- DCHECK(type == Activity::ACT_LOCK_ACQUIRE ||
- thread_checker_.CalledOnValidThread());
+ DCHECK(type == Activity::ACT_LOCK_ACQUIRE || CalledOnValidThread());
// Get the current depth of the stack. No access to other memory guarded
// by this variable is done here so a "relaxed" load is acceptable.
@@ -802,7 +802,7 @@ ThreadActivityTracker::ActivityId ThreadActivityTracker::PushActivity(
void ThreadActivityTracker::ChangeActivity(ActivityId id,
Activity::Type type,
const ActivityData& data) {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(CalledOnValidThread());
DCHECK(type != Activity::ACT_NULL || &data != &kNullActivityData);
DCHECK_LT(id, header_->current_depth.load(std::memory_order_acquire));
@@ -837,15 +837,14 @@ void ThreadActivityTracker::PopActivity(ActivityId id) {
// A thread-checker creates a lock to check the thread-id which means
// re-entry into this code if lock acquisitions are being tracked.
DCHECK(stack_[depth].activity_type == Activity::ACT_LOCK_ACQUIRE ||
- thread_checker_.CalledOnValidThread());
+ CalledOnValidThread());
// The stack has shrunk meaning that some other thread trying to copy the
- // contents for reporting purposes could get bad data. That thread would
- // have written a non-zero value into |data_unchanged|; clearing it here
- // will let that thread detect that something did change. This needs to
+ // contents for reporting purposes could get bad data. Increment the data
+ // version so that it con tell that things have changed. This needs to
// happen after the atomic |depth| operation above so a "release" store
// is required.
- header_->data_unchanged.store(0, std::memory_order_release);
+ header_->data_version.fetch_add(1, std::memory_order_release);
}
std::unique_ptr<ActivityUserData> ThreadActivityTracker::GetUserData(
@@ -854,12 +853,12 @@ std::unique_ptr<ActivityUserData> ThreadActivityTracker::GetUserData(
// Don't allow user data for lock acquisition as recursion may occur.
if (stack_[id].activity_type == Activity::ACT_LOCK_ACQUIRE) {
NOTREACHED();
- return MakeUnique<ActivityUserData>();
+ return std::make_unique<ActivityUserData>();
}
// User-data is only stored for activities actually held in the stack.
if (id >= stack_slots_)
- return MakeUnique<ActivityUserData>();
+ return std::make_unique<ActivityUserData>();
// Create and return a real UserData object.
return CreateUserDataForActivity(&stack_[id], allocator);
@@ -886,7 +885,7 @@ void ThreadActivityTracker::RecordExceptionActivity(const void* program_counter,
const ActivityData& data) {
// A thread-checker creates a lock to check the thread-id which means
// re-entry into this code if lock acquisitions are being tracked.
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(CalledOnValidThread());
// Fill the reusable exception activity.
Activity::FillFrom(&header_->last_exception, program_counter, origin, type,
@@ -894,7 +893,7 @@ void ThreadActivityTracker::RecordExceptionActivity(const void* program_counter,
// The data has changed meaning that some other thread trying to copy the
// contents for reporting purposes could get bad data.
- header_->data_unchanged.store(0, std::memory_order_relaxed);
+ header_->data_version.fetch_add(1, std::memory_order_relaxed);
}
bool ThreadActivityTracker::IsValid() const {
@@ -940,12 +939,13 @@ bool ThreadActivityTracker::CreateSnapshot(Snapshot* output_snapshot) const {
const int64_t starting_process_id = header_->owner.process_id;
const int64_t starting_thread_id = header_->thread_ref.as_id;
- // Write a non-zero value to |data_unchanged| so it's possible to detect
- // at the end that nothing has changed since copying the data began. A
- // "cst" operation is required to ensure it occurs before everything else.
- // Using "cst" memory ordering is relatively expensive but this is only
- // done during analysis so doesn't directly affect the worker threads.
- header_->data_unchanged.store(1, std::memory_order_seq_cst);
+ // Note the current |data_version| so it's possible to detect at the end
+ // that nothing has changed since copying the data began. A "cst" operation
+ // is required to ensure it occurs before everything else. Using "cst"
+ // memory ordering is relatively expensive but this is only done during
+ // analysis so doesn't directly affect the worker threads.
+ const uint32_t pre_version =
+ header_->data_version.load(std::memory_order_seq_cst);
// Fetching the current depth also "acquires" the contents of the stack.
depth = header_->current_depth.load(std::memory_order_acquire);
@@ -965,7 +965,7 @@ bool ThreadActivityTracker::CreateSnapshot(Snapshot* output_snapshot) const {
// Retry if something changed during the copy. A "cst" operation ensures
// it must happen after all the above operations.
- if (!header_->data_unchanged.load(std::memory_order_seq_cst))
+ if (header_->data_version.load(std::memory_order_seq_cst) != pre_version)
continue;
// Stack copied. Record it's full depth.
@@ -1025,6 +1025,10 @@ const void* ThreadActivityTracker::GetBaseAddress() {
return header_;
}
+uint32_t ThreadActivityTracker::GetDataVersionForTesting() {
+ return header_->data_version.load(std::memory_order_relaxed);
+}
+
void ThreadActivityTracker::SetOwningProcessIdForTesting(int64_t pid,
int64_t stamp) {
header_->owner.SetOwningProcessIdForTesting(pid, stamp);
@@ -1043,6 +1047,14 @@ size_t ThreadActivityTracker::SizeForStackDepth(int stack_depth) {
return static_cast<size_t>(stack_depth) * sizeof(Activity) + sizeof(Header);
}
+bool ThreadActivityTracker::CalledOnValidThread() {
+#if DCHECK_IS_ON()
+ return thread_id_ == PlatformThreadRef();
+#else
+ return true;
+#endif
+}
+
std::unique_ptr<ActivityUserData>
ThreadActivityTracker::CreateUserDataForActivity(
Activity* activity,
@@ -1053,14 +1065,14 @@ ThreadActivityTracker::CreateUserDataForActivity(
void* memory = allocator->GetAsArray<char>(ref, kUserDataSize);
if (memory) {
std::unique_ptr<ActivityUserData> user_data =
- MakeUnique<ActivityUserData>(memory, kUserDataSize);
+ std::make_unique<ActivityUserData>(memory, kUserDataSize);
activity->user_data_ref = ref;
activity->user_data_id = user_data->id();
return user_data;
}
// Return a dummy object that will still accept (but ignore) Set() calls.
- return MakeUnique<ActivityUserData>();
+ return std::make_unique<ActivityUserData>();
}
// The instantiation of the GlobalActivityTracker object.
@@ -1072,18 +1084,18 @@ ThreadActivityTracker::CreateUserDataForActivity(
// of std::atomic because the latter can create global ctors and dtors.
subtle::AtomicWord GlobalActivityTracker::g_tracker_ = 0;
-GlobalActivityTracker::ModuleInfo::ModuleInfo() {}
+GlobalActivityTracker::ModuleInfo::ModuleInfo() = default;
GlobalActivityTracker::ModuleInfo::ModuleInfo(ModuleInfo&& rhs) = default;
GlobalActivityTracker::ModuleInfo::ModuleInfo(const ModuleInfo& rhs) = default;
-GlobalActivityTracker::ModuleInfo::~ModuleInfo() {}
+GlobalActivityTracker::ModuleInfo::~ModuleInfo() = default;
GlobalActivityTracker::ModuleInfo& GlobalActivityTracker::ModuleInfo::operator=(
ModuleInfo&& rhs) = default;
GlobalActivityTracker::ModuleInfo& GlobalActivityTracker::ModuleInfo::operator=(
const ModuleInfo& rhs) = default;
-GlobalActivityTracker::ModuleInfoRecord::ModuleInfoRecord() {}
-GlobalActivityTracker::ModuleInfoRecord::~ModuleInfoRecord() {}
+GlobalActivityTracker::ModuleInfoRecord::ModuleInfoRecord() = default;
+GlobalActivityTracker::ModuleInfoRecord::~ModuleInfoRecord() = default;
bool GlobalActivityTracker::ModuleInfoRecord::DecodeTo(
GlobalActivityTracker::ModuleInfo* info,
@@ -1120,36 +1132,35 @@ bool GlobalActivityTracker::ModuleInfoRecord::DecodeTo(
return iter.ReadString(&info->file) && iter.ReadString(&info->debug_file);
}
-bool GlobalActivityTracker::ModuleInfoRecord::EncodeFrom(
+GlobalActivityTracker::ModuleInfoRecord*
+GlobalActivityTracker::ModuleInfoRecord::CreateFrom(
const GlobalActivityTracker::ModuleInfo& info,
- size_t record_size) {
+ PersistentMemoryAllocator* allocator) {
Pickle pickler;
- bool okay =
- pickler.WriteString(info.file) && pickler.WriteString(info.debug_file);
- if (!okay) {
- NOTREACHED();
- return false;
- }
- if (offsetof(ModuleInfoRecord, pickle) + pickler.size() > record_size) {
- NOTREACHED();
- return false;
- }
+ pickler.WriteString(info.file);
+ pickler.WriteString(info.debug_file);
+ size_t required_size = offsetof(ModuleInfoRecord, pickle) + pickler.size();
+ ModuleInfoRecord* record = allocator->New<ModuleInfoRecord>(required_size);
+ if (!record)
+ return nullptr;
// These fields never changes and are done before the record is made
// iterable so no thread protection is necessary.
- size = info.size;
- timestamp = info.timestamp;
- age = info.age;
- memcpy(identifier, info.identifier, sizeof(identifier));
- memcpy(pickle, pickler.data(), pickler.size());
- pickle_size = pickler.size();
- changes.store(0, std::memory_order_relaxed);
+ record->size = info.size;
+ record->timestamp = info.timestamp;
+ record->age = info.age;
+ memcpy(record->identifier, info.identifier, sizeof(identifier));
+ memcpy(record->pickle, pickler.data(), pickler.size());
+ record->pickle_size = pickler.size();
+ record->changes.store(0, std::memory_order_relaxed);
// Initialize the owner info.
- owner.Release_Initialize();
+ record->owner.Release_Initialize();
// Now set those fields that can change.
- return UpdateFrom(info);
+ bool success = record->UpdateFrom(info);
+ DCHECK(success);
+ return record;
}
bool GlobalActivityTracker::ModuleInfoRecord::UpdateFrom(
@@ -1177,17 +1188,6 @@ bool GlobalActivityTracker::ModuleInfoRecord::UpdateFrom(
return true;
}
-// static
-size_t GlobalActivityTracker::ModuleInfoRecord::EncodedSize(
- const GlobalActivityTracker::ModuleInfo& info) {
- PickleSizer sizer;
- sizer.AddString(info.file);
- sizer.AddString(info.debug_file);
-
- return offsetof(ModuleInfoRecord, pickle) + sizeof(Pickle::Header) +
- sizer.payload_size();
-}
-
GlobalActivityTracker::ScopedThreadActivity::ScopedThreadActivity(
const void* program_counter,
const void* origin,
@@ -1216,7 +1216,7 @@ ActivityUserData& GlobalActivityTracker::ScopedThreadActivity::user_data() {
user_data_ =
tracker_->GetUserData(activity_id_, &global->user_data_allocator_);
} else {
- user_data_ = MakeUnique<ActivityUserData>();
+ user_data_ = std::make_unique<ActivityUserData>();
}
}
return *user_data_;
@@ -1227,7 +1227,7 @@ GlobalActivityTracker::ThreadSafeUserData::ThreadSafeUserData(void* memory,
int64_t pid)
: ActivityUserData(memory, size, pid) {}
-GlobalActivityTracker::ThreadSafeUserData::~ThreadSafeUserData() {}
+GlobalActivityTracker::ThreadSafeUserData::~ThreadSafeUserData() = default;
void GlobalActivityTracker::ThreadSafeUserData::Set(StringPiece name,
ValueType type,
@@ -1266,7 +1266,7 @@ void GlobalActivityTracker::CreateWithAllocator(
#if !defined(OS_NACL)
// static
-void GlobalActivityTracker::CreateWithFile(const FilePath& file_path,
+bool GlobalActivityTracker::CreateWithFile(const FilePath& file_path,
size_t size,
uint64_t id,
StringPiece name,
@@ -1276,28 +1276,61 @@ void GlobalActivityTracker::CreateWithFile(const FilePath& file_path,
// Create and map the file into memory and make it globally available.
std::unique_ptr<MemoryMappedFile> mapped_file(new MemoryMappedFile());
- bool success =
- mapped_file->Initialize(File(file_path,
- File::FLAG_CREATE_ALWAYS | File::FLAG_READ |
- File::FLAG_WRITE | File::FLAG_SHARE_DELETE),
- {0, static_cast<int64_t>(size)},
- MemoryMappedFile::READ_WRITE_EXTEND);
- DCHECK(success);
- CreateWithAllocator(MakeUnique<FilePersistentMemoryAllocator>(
+ bool success = mapped_file->Initialize(
+ File(file_path, File::FLAG_CREATE_ALWAYS | File::FLAG_READ |
+ File::FLAG_WRITE | File::FLAG_SHARE_DELETE),
+ {0, size}, MemoryMappedFile::READ_WRITE_EXTEND);
+ if (!success)
+ return false;
+ if (!FilePersistentMemoryAllocator::IsFileAcceptable(*mapped_file, false))
+ return false;
+ CreateWithAllocator(std::make_unique<FilePersistentMemoryAllocator>(
std::move(mapped_file), size, id, name, false),
stack_depth, 0);
+ return true;
}
#endif // !defined(OS_NACL)
// static
-void GlobalActivityTracker::CreateWithLocalMemory(size_t size,
+bool GlobalActivityTracker::CreateWithLocalMemory(size_t size,
uint64_t id,
StringPiece name,
int stack_depth,
int64_t process_id) {
CreateWithAllocator(
- MakeUnique<LocalPersistentMemoryAllocator>(size, id, name), stack_depth,
- process_id);
+ std::make_unique<LocalPersistentMemoryAllocator>(size, id, name),
+ stack_depth, process_id);
+ return true;
+}
+
+// static
+bool GlobalActivityTracker::CreateWithSharedMemory(
+ std::unique_ptr<SharedMemory> shm,
+ uint64_t id,
+ StringPiece name,
+ int stack_depth) {
+ if (shm->mapped_size() == 0 ||
+ !SharedPersistentMemoryAllocator::IsSharedMemoryAcceptable(*shm)) {
+ return false;
+ }
+ CreateWithAllocator(std::make_unique<SharedPersistentMemoryAllocator>(
+ std::move(shm), id, name, false),
+ stack_depth, 0);
+ return true;
+}
+
+// static
+bool GlobalActivityTracker::CreateWithSharedMemoryHandle(
+ const SharedMemoryHandle& handle,
+ size_t size,
+ uint64_t id,
+ StringPiece name,
+ int stack_depth) {
+ std::unique_ptr<SharedMemory> shm(
+ new SharedMemory(handle, /*readonly=*/false));
+ if (!shm->Map(size))
+ return false;
+ return CreateWithSharedMemory(std::move(shm), id, name, stack_depth);
}
// static
@@ -1322,7 +1355,7 @@ GlobalActivityTracker::ReleaseForTesting() {
subtle::Release_Store(&g_tracker_, 0);
return WrapUnique(tracker);
-};
+}
ThreadActivityTracker* GlobalActivityTracker::CreateTrackerForCurrentThread() {
DCHECK(!this_thread_tracker_.Get());
@@ -1372,8 +1405,8 @@ ThreadActivityTracker* GlobalActivityTracker::CreateTrackerForCurrentThread() {
this_thread_tracker_.Set(tracker);
int old_count = thread_tracker_count_.fetch_add(1, std::memory_order_relaxed);
- UMA_HISTOGRAM_ENUMERATION("ActivityTracker.ThreadTrackers.Count",
- old_count + 1, kMaxThreadCount);
+ UMA_HISTOGRAM_EXACT_LINEAR("ActivityTracker.ThreadTrackers.Count",
+ old_count + 1, static_cast<int>(kMaxThreadCount));
return tracker;
}
@@ -1410,7 +1443,8 @@ void GlobalActivityTracker::RecordProcessLaunch(
// TODO(bcwhite): Measure this in UMA.
NOTREACHED() << "Process #" << process_id
<< " was previously recorded as \"launched\""
- << " with no corresponding exit.";
+ << " with no corresponding exit.\n"
+ << known_processes_[pid];
known_processes_.erase(pid);
}
@@ -1425,12 +1459,12 @@ void GlobalActivityTracker::RecordProcessLaunch(
ProcessId process_id,
const FilePath::StringType& exe,
const FilePath::StringType& args) {
- const int64_t pid = process_id;
if (exe.find(FILE_PATH_LITERAL(" "))) {
- RecordProcessLaunch(pid, FilePath::StringType(FILE_PATH_LITERAL("\"")) +
- exe + FILE_PATH_LITERAL("\" ") + args);
+ RecordProcessLaunch(process_id,
+ FilePath::StringType(FILE_PATH_LITERAL("\"")) + exe +
+ FILE_PATH_LITERAL("\" ") + args);
} else {
- RecordProcessLaunch(pid, exe + FILE_PATH_LITERAL(' ') + args);
+ RecordProcessLaunch(process_id, exe + FILE_PATH_LITERAL(' ') + args);
}
}
@@ -1460,11 +1494,11 @@ void GlobalActivityTracker::RecordProcessExit(ProcessId process_id,
// The persistent allocator is thread-safe so run the iteration and
// adjustments on a worker thread if one was provided.
- if (task_runner && !task_runner->RunsTasksOnCurrentThread()) {
+ if (task_runner && !task_runner->RunsTasksInCurrentSequence()) {
task_runner->PostTask(
FROM_HERE,
- Bind(&GlobalActivityTracker::CleanupAfterProcess, Unretained(this), pid,
- now_stamp, exit_code, Passed(&command_line)));
+ BindOnce(&GlobalActivityTracker::CleanupAfterProcess, Unretained(this),
+ pid, now_stamp, exit_code, std::move(command_line)));
return;
}
@@ -1499,6 +1533,8 @@ void GlobalActivityTracker::CleanupAfterProcess(int64_t process_id,
while ((ref = iter.GetNextOfType(kTypeIdProcessDataRecord)) != 0) {
const void* memory = allocator_->GetAsArray<char>(
ref, kTypeIdProcessDataRecord, PersistentMemoryAllocator::kSizeAny);
+ if (!memory)
+ continue;
int64_t found_id;
int64_t create_stamp;
if (ActivityUserData::GetOwningProcessId(memory, &found_id,
@@ -1536,6 +1572,8 @@ void GlobalActivityTracker::CleanupAfterProcess(int64_t process_id,
case ModuleInfoRecord::kPersistentTypeId: {
const void* memory = allocator_->GetAsArray<char>(
ref, type, PersistentMemoryAllocator::kSizeAny);
+ if (!memory)
+ continue;
int64_t found_id;
int64_t create_stamp;
@@ -1585,21 +1623,28 @@ void GlobalActivityTracker::RecordModuleInfo(const ModuleInfo& info) {
return;
}
- size_t required_size = ModuleInfoRecord::EncodedSize(info);
- ModuleInfoRecord* record = allocator_->New<ModuleInfoRecord>(required_size);
+ ModuleInfoRecord* record =
+ ModuleInfoRecord::CreateFrom(info, allocator_.get());
if (!record)
return;
-
- bool success = record->EncodeFrom(info, required_size);
- DCHECK(success);
allocator_->MakeIterable(record);
- modules_.insert(std::make_pair(info.file, record));
+ modules_.emplace(info.file, record);
}
void GlobalActivityTracker::RecordFieldTrial(const std::string& trial_name,
StringPiece group_name) {
const std::string key = std::string("FieldTrial.") + trial_name;
- global_data_.SetString(key, group_name);
+ process_data_.SetString(key, group_name);
+}
+
+void GlobalActivityTracker::RecordException(const void* pc,
+ const void* origin,
+ uint32_t code) {
+ RecordExceptionImpl(pc, origin, code);
+}
+
+void GlobalActivityTracker::MarkDeleted() {
+ allocator_->SetMemoryState(PersistentMemoryAllocator::MEMORY_DELETED);
}
GlobalActivityTracker::GlobalActivityTracker(
@@ -1631,14 +1676,7 @@ GlobalActivityTracker::GlobalActivityTracker(
kTypeIdProcessDataRecord,
kProcessDataSize),
kProcessDataSize,
- process_id_),
- global_data_(
- allocator_->GetAsArray<char>(
- allocator_->Allocate(kGlobalDataSize, kTypeIdGlobalDataRecord),
- kTypeIdGlobalDataRecord,
- kGlobalDataSize),
- kGlobalDataSize,
- process_id_) {
+ process_id_) {
DCHECK_NE(0, process_id_);
// Ensure that there is no other global object and then make this one such.
@@ -1648,8 +1686,6 @@ GlobalActivityTracker::GlobalActivityTracker(
// The data records must be iterable in order to be found by an analyzer.
allocator_->MakeIterable(allocator_->GetAsReference(
process_data_.GetBaseAddress(), kTypeIdProcessDataRecord));
- allocator_->MakeIterable(allocator_->GetAsReference(
- global_data_.GetBaseAddress(), kTypeIdGlobalDataRecord));
// Note that this process has launched.
SetProcessPhase(PROCESS_LAUNCHED);
diff --git a/base/debug/activity_tracker.h b/base/debug/activity_tracker.h
index c8cf1e972e..5647d864fc 100644
--- a/base/debug/activity_tracker.h
+++ b/base/debug/activity_tracker.h
@@ -27,13 +27,13 @@
#include "base/compiler_specific.h"
#include "base/gtest_prod_util.h"
#include "base/location.h"
+#include "base/memory/shared_memory.h"
#include "base/metrics/persistent_memory_allocator.h"
#include "base/process/process_handle.h"
#include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task_runner.h"
#include "base/threading/platform_thread.h"
-#include "base/threading/thread_checker.h"
#include "base/threading/thread_local_storage.h"
namespace base {
@@ -665,8 +665,7 @@ class BASE_EXPORT ThreadActivityTracker {
ActivityId PushActivity(const void* origin,
Activity::Type type,
const ActivityData& data) {
- return PushActivity(::tracked_objects::GetProgramCounter(), origin, type,
- data);
+ return PushActivity(GetProgramCounter(), origin, type, data);
}
// Changes the activity |type| and |data| of the top-most entry on the stack.
@@ -715,6 +714,10 @@ class BASE_EXPORT ThreadActivityTracker {
// Gets the base memory address used for storing data.
const void* GetBaseAddress();
+ // Access the "data version" value so tests can determine if an activity
+ // was pushed and popped in a single call.
+ uint32_t GetDataVersionForTesting();
+
// Explicitly sets the process ID.
void SetOwningProcessIdForTesting(int64_t pid, int64_t stamp);
@@ -732,18 +735,25 @@ class BASE_EXPORT ThreadActivityTracker {
private:
friend class ActivityTrackerTest;
+ bool CalledOnValidThread();
+
std::unique_ptr<ActivityUserData> CreateUserDataForActivity(
Activity* activity,
ActivityTrackerMemoryAllocator* allocator);
Header* const header_; // Pointer to the Header structure.
Activity* const stack_; // The stack of activities.
+
+#if DCHECK_IS_ON()
+ // The ActivityTracker is thread bound, and will be invoked across all the
+ // sequences that run on the thread. A ThreadChecker does not work here, as it
+ // asserts on running in the same sequence each time.
+ const PlatformThreadRef thread_id_; // The thread this instance is bound to.
+#endif
const uint32_t stack_slots_; // The total number of stack slots.
bool valid_ = false; // Tracks whether the data is valid or not.
- base::ThreadChecker thread_checker_;
-
DISALLOW_COPY_AND_ASSIGN(ThreadActivityTracker);
};
@@ -764,7 +774,6 @@ class BASE_EXPORT GlobalActivityTracker {
kTypeIdUserDataRecord = 0x615EDDD7 + 3, // SHA1(UserDataRecord) v3
kTypeIdGlobalLogMessage = 0x4CF434F9 + 1, // SHA1(GlobalLogMessage) v1
kTypeIdProcessDataRecord = kTypeIdUserDataRecord + 0x100,
- kTypeIdGlobalDataRecord = kTypeIdUserDataRecord + 0x200,
kTypeIdActivityTrackerFree = ~kTypeIdActivityTracker,
kTypeIdUserDataRecordFree = ~kTypeIdUserDataRecord,
@@ -826,9 +835,8 @@ class BASE_EXPORT GlobalActivityTracker {
};
// This is a thin wrapper around the thread-tracker's ScopedActivity that
- // accesses the global tracker to provide some of the information, notably
- // which thread-tracker to use. It is safe to create even if activity
- // tracking is not enabled.
+ // allows thread-safe access to data values. It is safe to use even if
+ // activity tracking is not enabled.
class BASE_EXPORT ScopedThreadActivity
: public ThreadActivityTracker::ScopedActivity {
public:
@@ -852,6 +860,13 @@ class BASE_EXPORT GlobalActivityTracker {
GlobalActivityTracker* global_tracker = Get();
if (!global_tracker)
return nullptr;
+
+ // It is not safe to use TLS once TLS has been destroyed. This can happen
+ // if code that runs late during thread destruction tries to use a
+ // base::Lock. See https://crbug.com/864589.
+ if (base::ThreadLocalStorage::HasBeenDestroyed())
+ return nullptr;
+
if (lock_allowed)
return global_tracker->GetOrCreateTrackerForCurrentThread();
else
@@ -880,8 +895,8 @@ class BASE_EXPORT GlobalActivityTracker {
// Like above but internally creates an allocator around a disk file with
// the specified |size| at the given |file_path|. Any existing file will be
// overwritten. The |id| and |name| are arbitrary and stored in the allocator
- // for reference by whatever process reads it.
- static void CreateWithFile(const FilePath& file_path,
+ // for reference by whatever process reads it. Returns true if successful.
+ static bool CreateWithFile(const FilePath& file_path,
size_t size,
uint64_t id,
StringPiece name,
@@ -891,12 +906,27 @@ class BASE_EXPORT GlobalActivityTracker {
// Like above but internally creates an allocator using local heap memory of
// the specified size. This is used primarily for unit tests. The |process_id|
// can be zero to get it from the OS but is taken for testing purposes.
- static void CreateWithLocalMemory(size_t size,
+ static bool CreateWithLocalMemory(size_t size,
uint64_t id,
StringPiece name,
int stack_depth,
int64_t process_id);
+ // Like above but internally creates an allocator using a shared-memory
+ // segment. The segment must already be mapped into the local memory space.
+ static bool CreateWithSharedMemory(std::unique_ptr<SharedMemory> shm,
+ uint64_t id,
+ StringPiece name,
+ int stack_depth);
+
+ // Like above but takes a handle to an existing shared memory segment and
+ // maps it before creating the tracker.
+ static bool CreateWithSharedMemoryHandle(const SharedMemoryHandle& handle,
+ size_t size,
+ uint64_t id,
+ StringPiece name,
+ int stack_depth);
+
// Gets the global activity-tracker or null if none exists.
static GlobalActivityTracker* Get() {
return reinterpret_cast<GlobalActivityTracker*>(
@@ -1020,22 +1050,21 @@ class BASE_EXPORT GlobalActivityTracker {
// Record exception information for the current thread.
ALWAYS_INLINE
void RecordException(const void* origin, uint32_t code) {
- return RecordExceptionImpl(::tracked_objects::GetProgramCounter(), origin,
- code);
+ return RecordExceptionImpl(GetProgramCounter(), origin, code);
}
+ void RecordException(const void* pc, const void* origin, uint32_t code);
+
+ // Marks the tracked data as deleted.
+ void MarkDeleted();
// Gets the process ID used for tracking. This is typically the same as what
// the OS thinks is the current process but can be overridden for testing.
- int64_t process_id() { return process_id_; };
+ int64_t process_id() { return process_id_; }
// Accesses the process data record for storing arbitrary key/value pairs.
// Updates to this are thread-safe.
ActivityUserData& process_data() { return process_data_; }
- // Accesses the global data record for storing arbitrary key/value pairs.
- // Updates to this are thread-safe.
- ActivityUserData& global_data() { return global_data_; }
-
private:
friend class GlobalActivityAnalyzer;
friend class ScopedThreadActivity;
@@ -1101,16 +1130,14 @@ class BASE_EXPORT GlobalActivityTracker {
// Decodes/encodes storage structure from more generic info structure.
bool DecodeTo(GlobalActivityTracker::ModuleInfo* info,
size_t record_size) const;
- bool EncodeFrom(const GlobalActivityTracker::ModuleInfo& info,
- size_t record_size);
+ static ModuleInfoRecord* CreateFrom(
+ const GlobalActivityTracker::ModuleInfo& info,
+ PersistentMemoryAllocator* allocator);
// Updates the core information without changing the encoded strings. This
// is useful when a known module changes state (i.e. new load or unload).
bool UpdateFrom(const GlobalActivityTracker::ModuleInfo& info);
- // Determines the required memory size for the encoded storage.
- static size_t EncodedSize(const GlobalActivityTracker::ModuleInfo& info);
-
private:
DISALLOW_COPY_AND_ASSIGN(ModuleInfoRecord);
};
@@ -1175,32 +1202,31 @@ class BASE_EXPORT GlobalActivityTracker {
const int64_t process_id_;
// The activity tracker for the currently executing thread.
- base::ThreadLocalStorage::Slot this_thread_tracker_;
+ ThreadLocalStorage::Slot this_thread_tracker_;
// The number of thread trackers currently active.
std::atomic<int> thread_tracker_count_;
// A caching memory allocator for thread-tracker objects.
ActivityTrackerMemoryAllocator thread_tracker_allocator_;
- base::Lock thread_tracker_allocator_lock_;
+ Lock thread_tracker_allocator_lock_;
// A caching memory allocator for user data attached to activity data.
ActivityTrackerMemoryAllocator user_data_allocator_;
- base::Lock user_data_allocator_lock_;
+ Lock user_data_allocator_lock_;
// An object for holding arbitrary key value pairs with thread-safe access.
ThreadSafeUserData process_data_;
- ThreadSafeUserData global_data_;
// A map of global module information, keyed by module path.
std::map<const std::string, ModuleInfoRecord*> modules_;
- base::Lock modules_lock_;
+ Lock modules_lock_;
// The active global activity tracker.
static subtle::AtomicWord g_tracker_;
// A lock that is used to protect access to the following fields.
- base::Lock global_tracker_lock_;
+ Lock global_tracker_lock_;
// The collection of processes being tracked and their command-lines.
std::map<int64_t, std::string> known_processes_;
@@ -1239,10 +1265,7 @@ class BASE_EXPORT ScopedActivity
// }
ALWAYS_INLINE
ScopedActivity(uint8_t action, uint32_t id, int32_t info)
- : ScopedActivity(::tracked_objects::GetProgramCounter(),
- action,
- id,
- info) {}
+ : ScopedActivity(GetProgramCounter(), action, id, info) {}
ScopedActivity() : ScopedActivity(0, 0, 0) {}
// Changes the |action| and/or |info| of this activity on the stack. This
@@ -1275,13 +1298,11 @@ class BASE_EXPORT ScopedTaskRunActivity
: public GlobalActivityTracker::ScopedThreadActivity {
public:
ALWAYS_INLINE
- explicit ScopedTaskRunActivity(const base::PendingTask& task)
- : ScopedTaskRunActivity(::tracked_objects::GetProgramCounter(),
- task) {}
+ explicit ScopedTaskRunActivity(const PendingTask& task)
+ : ScopedTaskRunActivity(GetProgramCounter(), task) {}
private:
- ScopedTaskRunActivity(const void* program_counter,
- const base::PendingTask& task);
+ ScopedTaskRunActivity(const void* program_counter, const PendingTask& task);
DISALLOW_COPY_AND_ASSIGN(ScopedTaskRunActivity);
};
@@ -1290,8 +1311,7 @@ class BASE_EXPORT ScopedLockAcquireActivity
public:
ALWAYS_INLINE
explicit ScopedLockAcquireActivity(const base::internal::LockImpl* lock)
- : ScopedLockAcquireActivity(::tracked_objects::GetProgramCounter(),
- lock) {}
+ : ScopedLockAcquireActivity(GetProgramCounter(), lock) {}
private:
ScopedLockAcquireActivity(const void* program_counter,
@@ -1303,13 +1323,12 @@ class BASE_EXPORT ScopedEventWaitActivity
: public GlobalActivityTracker::ScopedThreadActivity {
public:
ALWAYS_INLINE
- explicit ScopedEventWaitActivity(const base::WaitableEvent* event)
- : ScopedEventWaitActivity(::tracked_objects::GetProgramCounter(),
- event) {}
+ explicit ScopedEventWaitActivity(const WaitableEvent* event)
+ : ScopedEventWaitActivity(GetProgramCounter(), event) {}
private:
ScopedEventWaitActivity(const void* program_counter,
- const base::WaitableEvent* event);
+ const WaitableEvent* event);
DISALLOW_COPY_AND_ASSIGN(ScopedEventWaitActivity);
};
@@ -1317,13 +1336,12 @@ class BASE_EXPORT ScopedThreadJoinActivity
: public GlobalActivityTracker::ScopedThreadActivity {
public:
ALWAYS_INLINE
- explicit ScopedThreadJoinActivity(const base::PlatformThreadHandle* thread)
- : ScopedThreadJoinActivity(::tracked_objects::GetProgramCounter(),
- thread) {}
+ explicit ScopedThreadJoinActivity(const PlatformThreadHandle* thread)
+ : ScopedThreadJoinActivity(GetProgramCounter(), thread) {}
private:
ScopedThreadJoinActivity(const void* program_counter,
- const base::PlatformThreadHandle* thread);
+ const PlatformThreadHandle* thread);
DISALLOW_COPY_AND_ASSIGN(ScopedThreadJoinActivity);
};
@@ -1333,13 +1351,12 @@ class BASE_EXPORT ScopedProcessWaitActivity
: public GlobalActivityTracker::ScopedThreadActivity {
public:
ALWAYS_INLINE
- explicit ScopedProcessWaitActivity(const base::Process* process)
- : ScopedProcessWaitActivity(::tracked_objects::GetProgramCounter(),
- process) {}
+ explicit ScopedProcessWaitActivity(const Process* process)
+ : ScopedProcessWaitActivity(GetProgramCounter(), process) {}
private:
ScopedProcessWaitActivity(const void* program_counter,
- const base::Process* process);
+ const Process* process);
DISALLOW_COPY_AND_ASSIGN(ScopedProcessWaitActivity);
};
#endif
diff --git a/base/debug/activity_tracker_unittest.cc b/base/debug/activity_tracker_unittest.cc
index c7efa580e8..e2b61a920b 100644
--- a/base/debug/activity_tracker_unittest.cc
+++ b/base/debug/activity_tracker_unittest.cc
@@ -7,6 +7,7 @@
#include <memory>
#include "base/bind.h"
+#include "base/bind_helpers.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/files/memory_mapped_file.h"
@@ -33,7 +34,7 @@ class TestActivityTracker : public ThreadActivityTracker {
: ThreadActivityTracker(memset(memory.get(), 0, mem_size), mem_size),
mem_segment_(std::move(memory)) {}
- ~TestActivityTracker() override {}
+ ~TestActivityTracker() override = default;
private:
std::unique_ptr<char[]> mem_segment_;
@@ -49,7 +50,7 @@ class ActivityTrackerTest : public testing::Test {
using ActivityId = ThreadActivityTracker::ActivityId;
- ActivityTrackerTest() {}
+ ActivityTrackerTest() = default;
~ActivityTrackerTest() override {
GlobalActivityTracker* global_tracker = GlobalActivityTracker::Get();
@@ -61,7 +62,7 @@ class ActivityTrackerTest : public testing::Test {
std::unique_ptr<ThreadActivityTracker> CreateActivityTracker() {
std::unique_ptr<char[]> memory(new char[kStackSize]);
- return MakeUnique<TestActivityTracker>(std::move(memory), kStackSize);
+ return std::make_unique<TestActivityTracker>(std::move(memory), kStackSize);
}
size_t GetGlobalActiveTrackerCount() {
@@ -76,7 +77,7 @@ class ActivityTrackerTest : public testing::Test {
GlobalActivityTracker* global_tracker = GlobalActivityTracker::Get();
if (!global_tracker)
return 0;
- base::AutoLock autolock(global_tracker->thread_tracker_allocator_lock_);
+ AutoLock autolock(global_tracker->thread_tracker_allocator_lock_);
return global_tracker->thread_tracker_allocator_.cache_used();
}
@@ -90,22 +91,20 @@ class ActivityTrackerTest : public testing::Test {
GlobalActivityTracker::ProcessPhase phase,
std::string&& command,
ActivityUserData::Snapshot&& data) {
- exit_id = id;
- exit_stamp = stamp;
- exit_code = code;
- exit_phase = phase;
- exit_command = std::move(command);
- exit_data = std::move(data);
+ exit_id_ = id;
+ exit_stamp_ = stamp;
+ exit_code_ = code;
+ exit_phase_ = phase;
+ exit_command_ = std::move(command);
+ exit_data_ = std::move(data);
}
- static void DoNothing() {}
-
- int64_t exit_id = 0;
- int64_t exit_stamp;
- int exit_code;
- GlobalActivityTracker::ProcessPhase exit_phase;
- std::string exit_command;
- ActivityUserData::Snapshot exit_data;
+ int64_t exit_id_ = 0;
+ int64_t exit_stamp_;
+ int exit_code_;
+ GlobalActivityTracker::ProcessPhase exit_phase_;
+ std::string exit_command_;
+ ActivityUserData::Snapshot exit_data_;
};
TEST_F(ActivityTrackerTest, UserDataTest) {
@@ -216,7 +215,7 @@ TEST_F(ActivityTrackerTest, ScopedTaskTest) {
ASSERT_EQ(0U, snapshot.activity_stack.size());
{
- PendingTask task1(FROM_HERE, base::Bind(&DoNothing));
+ PendingTask task1(FROM_HERE, DoNothing());
ScopedTaskRunActivity activity1(task1);
ActivityUserData& user_data1 = activity1.user_data();
(void)user_data1; // Tell compiler it's been used.
@@ -227,7 +226,7 @@ TEST_F(ActivityTrackerTest, ScopedTaskTest) {
EXPECT_EQ(Activity::ACT_TASK, snapshot.activity_stack[0].activity_type);
{
- PendingTask task2(FROM_HERE, base::Bind(&DoNothing));
+ PendingTask task2(FROM_HERE, DoNothing());
ScopedTaskRunActivity activity2(task2);
ActivityUserData& user_data2 = activity2.user_data();
(void)user_data2; // Tell compiler it's been used.
@@ -250,6 +249,83 @@ TEST_F(ActivityTrackerTest, ScopedTaskTest) {
ASSERT_EQ(2U, GetGlobalUserDataMemoryCacheUsed());
}
+namespace {
+
+class SimpleLockThread : public SimpleThread {
+ public:
+ SimpleLockThread(const std::string& name, Lock* lock)
+ : SimpleThread(name, Options()),
+ lock_(lock),
+ data_changed_(false),
+ is_running_(false) {}
+
+ ~SimpleLockThread() override = default;
+
+ void Run() override {
+ ThreadActivityTracker* tracker =
+ GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread();
+ uint32_t pre_version = tracker->GetDataVersionForTesting();
+
+ is_running_.store(true, std::memory_order_relaxed);
+ lock_->Acquire();
+ data_changed_ = tracker->GetDataVersionForTesting() != pre_version;
+ lock_->Release();
+ is_running_.store(false, std::memory_order_relaxed);
+ }
+
+ bool IsRunning() { return is_running_.load(std::memory_order_relaxed); }
+
+ bool WasDataChanged() { return data_changed_; };
+
+ private:
+ Lock* lock_;
+ bool data_changed_;
+ std::atomic<bool> is_running_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleLockThread);
+};
+
+} // namespace
+
+TEST_F(ActivityTrackerTest, LockTest) {
+ GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
+
+ ThreadActivityTracker* tracker =
+ GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread();
+ ThreadActivityTracker::Snapshot snapshot;
+ ASSERT_EQ(0U, GetGlobalUserDataMemoryCacheUsed());
+
+ Lock lock;
+ uint32_t pre_version = tracker->GetDataVersionForTesting();
+
+ // Check no activity when only "trying" a lock.
+ EXPECT_TRUE(lock.Try());
+ EXPECT_EQ(pre_version, tracker->GetDataVersionForTesting());
+ lock.Release();
+ EXPECT_EQ(pre_version, tracker->GetDataVersionForTesting());
+
+ // Check no activity when acquiring a free lock.
+ SimpleLockThread t1("locker1", &lock);
+ t1.Start();
+ t1.Join();
+ EXPECT_FALSE(t1.WasDataChanged());
+
+ // Check that activity is recorded when acquring a busy lock.
+ SimpleLockThread t2("locker2", &lock);
+ lock.Acquire();
+ t2.Start();
+ while (!t2.IsRunning())
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(10));
+ // t2 can't join until the lock is released but have to give time for t2 to
+ // actually block on the lock before releasing it or the results will not
+ // be correct.
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(200));
+ lock.Release();
+ // Now the results will be valid.
+ t2.Join();
+ EXPECT_TRUE(t2.WasDataChanged());
+}
+
TEST_F(ActivityTrackerTest, ExceptionTest) {
GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
GlobalActivityTracker* global = GlobalActivityTracker::Get();
@@ -306,10 +382,10 @@ TEST_F(ActivityTrackerTest, BasicTest) {
// Ensure the data repositories have backing store, indicated by non-zero ID.
EXPECT_NE(0U, global->process_data().id());
- EXPECT_NE(0U, global->global_data().id());
- EXPECT_NE(global->process_data().id(), global->global_data().id());
}
+namespace {
+
class SimpleActivityThread : public SimpleThread {
public:
SimpleActivityThread(const std::string& name,
@@ -320,9 +396,11 @@ class SimpleActivityThread : public SimpleThread {
origin_(origin),
activity_(activity),
data_(data),
+ ready_(false),
+ exit_(false),
exit_condition_(&lock_) {}
- ~SimpleActivityThread() override {}
+ ~SimpleActivityThread() override = default;
void Run() override {
ThreadActivityTracker::ActivityId id =
@@ -332,8 +410,8 @@ class SimpleActivityThread : public SimpleThread {
{
AutoLock auto_lock(lock_);
- ready_ = true;
- while (!exit_)
+ ready_.store(true, std::memory_order_release);
+ while (!exit_.load(std::memory_order_relaxed))
exit_condition_.Wait();
}
@@ -342,12 +420,12 @@ class SimpleActivityThread : public SimpleThread {
void Exit() {
AutoLock auto_lock(lock_);
- exit_ = true;
+ exit_.store(true, std::memory_order_relaxed);
exit_condition_.Signal();
}
void WaitReady() {
- SPIN_FOR_1_SECOND_OR_UNTIL_TRUE(ready_);
+ SPIN_FOR_1_SECOND_OR_UNTIL_TRUE(ready_.load(std::memory_order_acquire));
}
private:
@@ -355,14 +433,16 @@ class SimpleActivityThread : public SimpleThread {
Activity::Type activity_;
ActivityData data_;
- bool ready_ = false;
- bool exit_ = false;
+ std::atomic<bool> ready_;
+ std::atomic<bool> exit_;
Lock lock_;
ConditionVariable exit_condition_;
DISALLOW_COPY_AND_ASSIGN(SimpleActivityThread);
};
+} // namespace
+
TEST_F(ActivityTrackerTest, ThreadDeathTest) {
GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread();
@@ -399,7 +479,7 @@ TEST_F(ActivityTrackerTest, ThreadDeathTest) {
TEST_F(ActivityTrackerTest, ProcessDeathTest) {
// This doesn't actually create and destroy a process. Instead, it uses for-
// testing interfaces to simulate data created by other processes.
- const ProcessId other_process_id = GetCurrentProcId() + 1;
+ const int64_t other_process_id = GetCurrentProcId() + 1;
GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0);
GlobalActivityTracker* global = GlobalActivityTracker::Get();
@@ -413,7 +493,7 @@ TEST_F(ActivityTrackerTest, ProcessDeathTest) {
global->RecordProcessLaunch(other_process_id, FILE_PATH_LITERAL("foo --bar"));
// Do some activities.
- PendingTask task(FROM_HERE, base::Bind(&DoNothing));
+ PendingTask task(FROM_HERE, DoNothing());
ScopedTaskRunActivity activity(task);
ActivityUserData& user_data = activity.user_data();
ASSERT_NE(0U, user_data.id());
@@ -440,7 +520,9 @@ TEST_F(ActivityTrackerTest, ProcessDeathTest) {
std::unique_ptr<char[]> tracker_copy(new char[tracker_size]);
memcpy(tracker_copy.get(), thread->GetBaseAddress(), tracker_size);
- // Change the objects to appear to be owned by another process.
+ // Change the objects to appear to be owned by another process. Use a "past"
+ // time so that exit-time is always later than create-time.
+ const int64_t past_stamp = Time::Now().ToInternalValue() - 1;
int64_t owning_id;
int64_t stamp;
ASSERT_TRUE(ActivityUserData::GetOwningProcessId(
@@ -452,9 +534,10 @@ TEST_F(ActivityTrackerTest, ProcessDeathTest) {
ASSERT_TRUE(ActivityUserData::GetOwningProcessId(user_data.GetBaseAddress(),
&owning_id, &stamp));
EXPECT_NE(other_process_id, owning_id);
- global->process_data().SetOwningProcessIdForTesting(other_process_id, stamp);
- thread->SetOwningProcessIdForTesting(other_process_id, stamp);
- user_data.SetOwningProcessIdForTesting(other_process_id, stamp);
+ global->process_data().SetOwningProcessIdForTesting(other_process_id,
+ past_stamp);
+ thread->SetOwningProcessIdForTesting(other_process_id, past_stamp);
+ user_data.SetOwningProcessIdForTesting(other_process_id, past_stamp);
ASSERT_TRUE(ActivityUserData::GetOwningProcessId(
global->process_data().GetBaseAddress(), &owning_id, &stamp));
EXPECT_EQ(other_process_id, owning_id);
@@ -466,7 +549,7 @@ TEST_F(ActivityTrackerTest, ProcessDeathTest) {
EXPECT_EQ(other_process_id, owning_id);
// Check that process exit will perform callback and free the allocations.
- ASSERT_EQ(0, exit_id);
+ ASSERT_EQ(0, exit_id_);
ASSERT_EQ(GlobalActivityTracker::kTypeIdProcessDataRecord,
global->allocator()->GetType(proc_data_ref));
ASSERT_EQ(GlobalActivityTracker::kTypeIdActivityTracker,
@@ -474,8 +557,8 @@ TEST_F(ActivityTrackerTest, ProcessDeathTest) {
ASSERT_EQ(GlobalActivityTracker::kTypeIdUserDataRecord,
global->allocator()->GetType(user_data_ref));
global->RecordProcessExit(other_process_id, 0);
- EXPECT_EQ(other_process_id, exit_id);
- EXPECT_EQ("foo --bar", exit_command);
+ EXPECT_EQ(other_process_id, exit_id_);
+ EXPECT_EQ("foo --bar", exit_command_);
EXPECT_EQ(GlobalActivityTracker::kTypeIdProcessDataRecordFree,
global->allocator()->GetType(proc_data_ref));
EXPECT_EQ(GlobalActivityTracker::kTypeIdActivityTrackerFree,
diff --git a/base/debug/alias.cc b/base/debug/alias.cc
index 6b0caaa6d1..ebc8b5a4ce 100644
--- a/base/debug/alias.cc
+++ b/base/debug/alias.cc
@@ -10,6 +10,8 @@ namespace debug {
#if defined(COMPILER_MSVC)
#pragma optimize("", off)
+#elif defined(__clang__)
+#pragma clang optimize off
#endif
void Alias(const void* var) {
@@ -17,6 +19,8 @@ void Alias(const void* var) {
#if defined(COMPILER_MSVC)
#pragma optimize("", on)
+#elif defined(__clang__)
+#pragma clang optimize on
#endif
} // namespace debug
diff --git a/base/debug/alias.h b/base/debug/alias.h
index 3b2ab64f33..128fdaa05d 100644
--- a/base/debug/alias.h
+++ b/base/debug/alias.h
@@ -6,16 +6,38 @@
#define BASE_DEBUG_ALIAS_H_
#include "base/base_export.h"
+#include "base/strings/string_util.h"
namespace base {
namespace debug {
// Make the optimizer think that var is aliased. This is to prevent it from
-// optimizing out variables that that would not otherwise be live at the point
+// optimizing out local variables that would not otherwise be live at the point
// of a potential crash.
+// base::debug::Alias should only be used for local variables, not globals,
+// object members, or function return values - these must be copied to locals if
+// you want to ensure they are recorded in crash dumps.
+// Note that if the local variable is a pointer then its value will be retained
+// but the memory that it points to will probably not be saved in the crash
+// dump - by default only stack memory is saved. Therefore the aliasing
+// technique is usually only worthwhile with non-pointer variables. If you have
+// a pointer to an object and you want to retain the object's state you need to
+// copy the object or its fields to local variables. Example usage:
+// int last_error = err_;
+// base::debug::Alias(&last_error);
+// DEBUG_ALIAS_FOR_CSTR(name_copy, p->name, 16);
+// CHECK(false);
void BASE_EXPORT Alias(const void* var);
} // namespace debug
} // namespace base
+// Convenience macro that copies the null-terminated string from |c_str| into a
+// stack-allocated char array named |var_name| that holds up to |char_count|
+// characters and should be preserved in memory dumps.
+#define DEBUG_ALIAS_FOR_CSTR(var_name, c_str, char_count) \
+ char var_name[char_count]; \
+ ::base::strlcpy(var_name, (c_str), arraysize(var_name)); \
+ ::base::debug::Alias(var_name);
+
#endif // BASE_DEBUG_ALIAS_H_
diff --git a/base/debug/alias_unittest.cc b/base/debug/alias_unittest.cc
new file mode 100644
index 0000000000..66682f1f02
--- /dev/null
+++ b/base/debug/alias_unittest.cc
@@ -0,0 +1,28 @@
+// 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.
+
+#include <memory>
+#include <string>
+
+#include "base/debug/alias.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(DebugAlias, Test) {
+ std::unique_ptr<std::string> input =
+ std::make_unique<std::string>("string contents");
+
+ // Verify the contents get copied + the new local variable has the right type.
+ DEBUG_ALIAS_FOR_CSTR(copy1, input->c_str(), 100 /* > input->size() */);
+ static_assert(sizeof(copy1) == 100,
+ "Verification that copy1 has expected size");
+ EXPECT_STREQ("string contents", copy1);
+
+ // Verify that the copy is properly null-terminated even when it is smaller
+ // than the input string.
+ DEBUG_ALIAS_FOR_CSTR(copy2, input->c_str(), 3 /* < input->size() */);
+ static_assert(sizeof(copy2) == 3,
+ "Verification that copy2 has expected size");
+ EXPECT_STREQ("st", copy2);
+ EXPECT_EQ('\0', copy2[2]);
+}
diff --git a/base/debug/crash_logging.cc b/base/debug/crash_logging.cc
new file mode 100644
index 0000000000..1dabb6b963
--- /dev/null
+++ b/base/debug/crash_logging.cc
@@ -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.
+
+#include "base/debug/crash_logging.h"
+
+namespace base {
+namespace debug {
+
+namespace {
+
+CrashKeyImplementation* g_crash_key_impl = nullptr;
+
+} // namespace
+
+CrashKeyString* AllocateCrashKeyString(const char name[],
+ CrashKeySize value_length) {
+ if (!g_crash_key_impl)
+ return nullptr;
+
+ return g_crash_key_impl->Allocate(name, value_length);
+}
+
+void SetCrashKeyString(CrashKeyString* crash_key, base::StringPiece value) {
+ if (!g_crash_key_impl || !crash_key)
+ return;
+
+ g_crash_key_impl->Set(crash_key, value);
+}
+
+void ClearCrashKeyString(CrashKeyString* crash_key) {
+ if (!g_crash_key_impl || !crash_key)
+ return;
+
+ g_crash_key_impl->Clear(crash_key);
+}
+
+ScopedCrashKeyString::ScopedCrashKeyString(CrashKeyString* crash_key,
+ base::StringPiece value)
+ : crash_key_(crash_key) {
+ SetCrashKeyString(crash_key_, value);
+}
+
+ScopedCrashKeyString::~ScopedCrashKeyString() {
+ ClearCrashKeyString(crash_key_);
+}
+
+void SetCrashKeyImplementation(std::unique_ptr<CrashKeyImplementation> impl) {
+ delete g_crash_key_impl;
+ g_crash_key_impl = impl.release();
+}
+
+} // namespace debug
+} // namespace base
diff --git a/base/debug/crash_logging.h b/base/debug/crash_logging.h
new file mode 100644
index 0000000000..9c6cd758da
--- /dev/null
+++ b/base/debug/crash_logging.h
@@ -0,0 +1,104 @@
+// 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_DEBUG_CRASH_LOGGING_H_
+#define BASE_DEBUG_CRASH_LOGGING_H_
+
+#include <stddef.h>
+
+#include <memory>
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+
+namespace base {
+namespace debug {
+
+// A crash key is an annotation that is carried along with a crash report, to
+// provide additional debugging information beyond a stack trace. Crash keys
+// have a name and a string value.
+//
+// The preferred API is //components/crash/core/common:crash_key, however not
+// all clients can hold a direct dependency on that target. The API provided
+// in this file indirects the dependency.
+//
+// Example usage:
+// static CrashKeyString* crash_key =
+// AllocateCrashKeyString("name", CrashKeySize::Size32);
+// SetCrashKeyString(crash_key, "value");
+// ClearCrashKeyString(crash_key);
+
+// The maximum length for a crash key's value must be one of the following
+// pre-determined values.
+enum class CrashKeySize {
+ Size32 = 32,
+ Size64 = 64,
+ Size256 = 256,
+};
+
+struct CrashKeyString;
+
+// Allocates a new crash key with the specified |name| with storage for a
+// value up to length |size|. This will return null if the crash key system is
+// not initialized.
+BASE_EXPORT CrashKeyString* AllocateCrashKeyString(const char name[],
+ CrashKeySize size);
+
+// Stores |value| into the specified |crash_key|. The |crash_key| may be null
+// if AllocateCrashKeyString() returned null. If |value| is longer than the
+// size with which the key was allocated, it will be truncated.
+BASE_EXPORT void SetCrashKeyString(CrashKeyString* crash_key,
+ base::StringPiece value);
+
+// Clears any value that was stored in |crash_key|. The |crash_key| may be
+// null.
+BASE_EXPORT void ClearCrashKeyString(CrashKeyString* crash_key);
+
+// A scoper that sets the specified key to value for the lifetime of the
+// object, and clears it on destruction.
+class BASE_EXPORT ScopedCrashKeyString {
+ public:
+ ScopedCrashKeyString(CrashKeyString* crash_key, base::StringPiece value);
+ ~ScopedCrashKeyString();
+
+ private:
+ CrashKeyString* const crash_key_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedCrashKeyString);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// The following declarations are used to initialize the crash key system
+// in //base by providing implementations for the above functions.
+
+// The virtual interface that provides the implementation for the crash key
+// API. This is implemented by a higher-layer component, and the instance is
+// set using the function below.
+class CrashKeyImplementation {
+ public:
+ virtual ~CrashKeyImplementation() = default;
+
+ virtual CrashKeyString* Allocate(const char name[], CrashKeySize size) = 0;
+ virtual void Set(CrashKeyString* crash_key, base::StringPiece value) = 0;
+ virtual void Clear(CrashKeyString* crash_key) = 0;
+};
+
+// Initializes the crash key system in base by replacing the existing
+// implementation, if it exists, with |impl|. The |impl| is copied into base.
+BASE_EXPORT void SetCrashKeyImplementation(
+ std::unique_ptr<CrashKeyImplementation> impl);
+
+// The base structure for a crash key, storing the allocation metadata.
+struct CrashKeyString {
+ constexpr CrashKeyString(const char name[], CrashKeySize size)
+ : name(name), size(size) {}
+ const char* const name;
+ const CrashKeySize size;
+};
+
+} // namespace debug
+} // namespace base
+
+#endif // BASE_DEBUG_CRASH_LOGGING_H_
diff --git a/base/debug/debugger_posix.cc b/base/debug/debugger_posix.cc
index 3255552333..b62bf013bd 100644
--- a/base/debug/debugger_posix.cc
+++ b/base/debug/debugger_posix.cc
@@ -122,7 +122,7 @@ bool BeingDebugged() {
return being_debugged;
}
-#elif defined(OS_LINUX) || defined(OS_ANDROID)
+#elif defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_AIX)
// We can look in /proc/self/status for TracerPid. We are likely used in crash
// handling, so we are careful not to use the heap or have side effects.
@@ -161,6 +161,13 @@ bool BeingDebugged() {
return pid_index < status.size() && status[pid_index] != '0';
}
+#elif defined(OS_FUCHSIA)
+
+bool BeingDebugged() {
+ // TODO(fuchsia): No gdb/gdbserver in the SDK yet.
+ return false;
+}
+
#else
bool BeingDebugged() {
diff --git a/base/debug/debugging_buildflags.h b/base/debug/debugging_buildflags.h
new file mode 100644
index 0000000000..33e3dd0483
--- /dev/null
+++ b/base/debug/debugging_buildflags.h
@@ -0,0 +1,12 @@
+// 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)
+#define BUILDFLAG_INTERNAL_CAN_UNWIND_WITH_FRAME_POINTERS() (0)
+#define BUILDFLAG_INTERNAL_ENABLE_LOCATION_SOURCE() (0)
+#define BUILDFLAG_INTERNAL_CFI_ENFORCEMENT_TRAP() (0)
+#define BUILDFLAG_INTERNAL_ENABLE_MUTEX_PRIORITY_INHERITANCE() (0)
+#endif // BASE_DEBUG_DEBUGGING_FLAGS_H_
diff --git a/base/debug/debugging_flags.h b/base/debug/debugging_flags.h
deleted file mode 100644
index e6ae1ee192..0000000000
--- a/base/debug/debugging_flags.h
+++ /dev/null
@@ -1,8 +0,0 @@
-// 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/base/debug/dump_without_crashing.cc b/base/debug/dump_without_crashing.cc
index 4b338ca293..1ab8c9cc41 100644
--- a/base/debug/dump_without_crashing.cc
+++ b/base/debug/dump_without_crashing.cc
@@ -10,7 +10,7 @@ namespace {
// Pointer to the function that's called by DumpWithoutCrashing() to dump the
// process's memory.
-void (CDECL *dump_without_crashing_function_)() = NULL;
+void(CDECL* dump_without_crashing_function_)() = nullptr;
} // namespace
@@ -27,6 +27,12 @@ bool DumpWithoutCrashing() {
}
void SetDumpWithoutCrashingFunction(void (CDECL *function)()) {
+#if !defined(COMPONENT_BUILD)
+ // In component builds, the same base is shared between modules
+ // so might be initialized several times. However in non-
+ // component builds this should never happen.
+ DCHECK(!dump_without_crashing_function_);
+#endif
dump_without_crashing_function_ = function;
}
diff --git a/base/debug/dump_without_crashing.h b/base/debug/dump_without_crashing.h
index a5c85d5ebe..913f6c428d 100644
--- a/base/debug/dump_without_crashing.h
+++ b/base/debug/dump_without_crashing.h
@@ -15,8 +15,14 @@ namespace debug {
// Handler to silently dump the current process without crashing.
// Before calling this function, call SetDumpWithoutCrashingFunction to pass a
-// function pointer, typically chrome!DumpProcessWithoutCrash. See example code
-// in chrome_main.cc that does this for chrome.dll.
+// function pointer.
+// Windows:
+// This must be done for each instance of base (i.e. module) and is normally
+// chrome_elf!DumpProcessWithoutCrash. See example code in chrome_main.cc that
+// does this for chrome.dll and chrome_child.dll. Note: Crashpad sets this up
+// for main chrome.exe as part of calling crash_reporter::InitializeCrashpad.
+// Mac/Linux:
+// Crashpad does this as part of crash_reporter::InitializeCrashpad.
// Returns false if called before SetDumpWithoutCrashingFunction.
BASE_EXPORT bool DumpWithoutCrashing();
diff --git a/base/debug/elf_reader_linux.cc b/base/debug/elf_reader_linux.cc
new file mode 100644
index 0000000000..cdf8193ff6
--- /dev/null
+++ b/base/debug/elf_reader_linux.cc
@@ -0,0 +1,132 @@
+// 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.
+
+#include "base/debug/elf_reader_linux.h"
+
+#include <arpa/inet.h>
+#include <elf.h>
+
+#include <vector>
+
+#include "base/bits.h"
+#include "base/containers/span.h"
+#include "base/sha1.h"
+#include "base/strings/stringprintf.h"
+
+namespace base {
+namespace debug {
+
+namespace {
+
+#if __SIZEOF_POINTER__ == 4
+using Ehdr = Elf32_Ehdr;
+using Dyn = Elf32_Dyn;
+using Half = Elf32_Half;
+using Nhdr = Elf32_Nhdr;
+using Phdr = Elf32_Phdr;
+using Word = Elf32_Word;
+#else
+using Ehdr = Elf64_Ehdr;
+using Dyn = Elf64_Dyn;
+using Half = Elf64_Half;
+using Nhdr = Elf64_Nhdr;
+using Phdr = Elf64_Phdr;
+using Word = Elf64_Word;
+#endif
+
+using ElfSegment = span<const char>;
+
+Optional<std::string> ElfSegmentBuildIDNoteAsString(const ElfSegment& segment) {
+ const void* section_end = segment.data() + segment.size_bytes();
+ const Nhdr* note_header = reinterpret_cast<const Nhdr*>(segment.data());
+ while (note_header < section_end) {
+ if (note_header->n_type == NT_GNU_BUILD_ID)
+ break;
+ note_header = reinterpret_cast<const Nhdr*>(
+ reinterpret_cast<const char*>(note_header) + sizeof(Nhdr) +
+ bits::Align(note_header->n_namesz, 4) +
+ bits::Align(note_header->n_descsz, 4));
+ }
+
+ if (note_header >= section_end || note_header->n_descsz != kSHA1Length)
+ return nullopt;
+
+ const uint8_t* guid = reinterpret_cast<const uint8_t*>(note_header) +
+ sizeof(Nhdr) + bits::Align(note_header->n_namesz, 4);
+
+ uint32_t dword = htonl(*reinterpret_cast<const int32_t*>(guid));
+ uint16_t word1 = htons(*reinterpret_cast<const int16_t*>(guid + 4));
+ uint16_t word2 = htons(*reinterpret_cast<const int16_t*>(guid + 6));
+ std::string identifier;
+ identifier.reserve(kSHA1Length * 2); // as hex string
+ SStringPrintf(&identifier, "%08X%04X%04X", dword, word1, word2);
+ for (size_t i = 8; i < note_header->n_descsz; ++i)
+ StringAppendF(&identifier, "%02X", guid[i]);
+
+ return identifier;
+}
+
+std::vector<ElfSegment> FindElfSegments(const void* elf_mapped_base,
+ uint32_t segment_type) {
+ const char* elf_base = reinterpret_cast<const char*>(elf_mapped_base);
+ if (strncmp(elf_base, ELFMAG, SELFMAG) != 0)
+ return std::vector<ElfSegment>();
+
+ const Ehdr* elf_header = reinterpret_cast<const Ehdr*>(elf_base);
+ const Phdr* phdrs =
+ reinterpret_cast<const Phdr*>(elf_base + elf_header->e_phoff);
+ std::vector<ElfSegment> segments;
+ for (Half i = 0; i < elf_header->e_phnum; ++i) {
+ if (phdrs[i].p_type == segment_type)
+ segments.push_back({elf_base + phdrs[i].p_offset, phdrs[i].p_filesz});
+ }
+ return segments;
+}
+
+} // namespace
+
+Optional<std::string> ReadElfBuildId(const void* elf_base) {
+ // Elf program headers can have multiple PT_NOTE arrays.
+ std::vector<ElfSegment> segs = FindElfSegments(elf_base, PT_NOTE);
+ if (segs.empty())
+ return nullopt;
+ Optional<std::string> id;
+ for (const ElfSegment& seg : segs) {
+ id = ElfSegmentBuildIDNoteAsString(seg);
+ if (id)
+ return id;
+ }
+
+ return nullopt;
+}
+
+Optional<std::string> ReadElfLibraryName(const void* elf_base) {
+ std::vector<ElfSegment> segs = FindElfSegments(elf_base, PT_DYNAMIC);
+ if (segs.empty())
+ return nullopt;
+ DCHECK_EQ(1u, segs.size());
+
+ const ElfSegment& dynamic_seg = segs.front();
+ const Dyn* dynamic_start = reinterpret_cast<const Dyn*>(dynamic_seg.data());
+ const Dyn* dynamic_end = reinterpret_cast<const Dyn*>(
+ dynamic_seg.data() + dynamic_seg.size_bytes());
+ Optional<std::string> soname;
+ Word soname_strtab_offset = 0;
+ const char* strtab_addr = 0;
+ for (const Dyn* dynamic_iter = dynamic_start; dynamic_iter < dynamic_end;
+ ++dynamic_iter) {
+ if (dynamic_iter->d_tag == DT_STRTAB) {
+ strtab_addr =
+ dynamic_iter->d_un.d_ptr + reinterpret_cast<const char*>(elf_base);
+ } else if (dynamic_iter->d_tag == DT_SONAME) {
+ soname_strtab_offset = dynamic_iter->d_un.d_val;
+ }
+ }
+ if (soname_strtab_offset && strtab_addr)
+ return std::string(strtab_addr + soname_strtab_offset);
+ return nullopt;
+}
+
+} // namespace debug
+} // namespace base
diff --git a/base/debug/elf_reader_linux.h b/base/debug/elf_reader_linux.h
new file mode 100644
index 0000000000..4086dfbe50
--- /dev/null
+++ b/base/debug/elf_reader_linux.h
@@ -0,0 +1,28 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_DEBUG_ELF_READER_LINUX_H_
+#define BASE_DEBUG_ELF_READER_LINUX_H_
+
+#include <string>
+
+#include "base/base_export.h"
+#include "base/optional.h"
+
+namespace base {
+namespace debug {
+
+// Returns the ELF section .note.gnu.build-id from the ELF file mapped at
+// |elf_base|, if present. The caller must ensure that the file is fully mapped
+// in memory.
+Optional<std::string> BASE_EXPORT ReadElfBuildId(const void* elf_base);
+
+// Returns the library name from the ELF file mapped at |elf_base|, if present.
+// The caller must ensure that the file is fully mapped in memory.
+Optional<std::string> BASE_EXPORT ReadElfLibraryName(const void* elf_base);
+
+} // namespace debug
+} // namespace base
+
+#endif // BASE_DEBUG_ELF_READER_LINUX_H_
diff --git a/base/debug/elf_reader_linux_unittest.cc b/base/debug/elf_reader_linux_unittest.cc
new file mode 100644
index 0000000000..5510418d00
--- /dev/null
+++ b/base/debug/elf_reader_linux_unittest.cc
@@ -0,0 +1,70 @@
+// 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.
+
+#include "base/debug/elf_reader_linux.h"
+
+#include <dlfcn.h>
+
+#include "base/files/memory_mapped_file.h"
+#include "base/strings/string_util.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+extern char __executable_start;
+
+namespace base {
+namespace debug {
+
+// The linker flag --build-id is passed only on official builds. Clang does not
+// enable it by default and we do not have build id section in non-official
+// builds.
+#if defined(OFFICIAL_BUILD)
+TEST(ElfReaderTest, ReadElfBuildId) {
+ Optional<std::string> build_id = ReadElfBuildId(&__executable_start);
+ ASSERT_TRUE(build_id);
+ const size_t kGuidBytes = 20;
+ EXPECT_EQ(2 * kGuidBytes, build_id.value().size());
+ for (char c : *build_id) {
+ EXPECT_TRUE(IsHexDigit(c));
+ EXPECT_FALSE(IsAsciiLower(c));
+ }
+}
+#endif
+
+TEST(ElfReaderTest, ReadElfLibraryName) {
+#if defined(OS_ANDROID)
+ // On Android the library loader memory maps the full so file.
+ const char kLibraryName[] = "lib_base_unittests__library";
+ const void* addr = &__executable_start;
+#else
+ // On Linux the executable does not contain soname and is not mapped till
+ // dynamic segment. So, use malloc wrapper so file on which the test already
+ // depends on.
+ const char kLibraryName[] = MALLOC_WRAPPER_LIB;
+ // Find any symbol in the loaded file.
+ void* handle = dlopen(kLibraryName, RTLD_NOW | RTLD_LOCAL);
+ const void* init_addr = dlsym(handle, "_init");
+ // Use this symbol to get full path to the loaded library.
+ Dl_info info;
+ int res = dladdr(init_addr, &info);
+ ASSERT_NE(0, res);
+ std::string filename(info.dli_fname);
+ EXPECT_FALSE(filename.empty());
+ EXPECT_NE(std::string::npos, filename.find(kLibraryName));
+
+ // Memory map the so file and use it to test reading so name.
+ MemoryMappedFile file;
+ file.Initialize(FilePath(filename));
+ const void* addr = file.data();
+#endif
+
+ auto name = ReadElfLibraryName(addr);
+ ASSERT_TRUE(name);
+ EXPECT_NE(std::string::npos, name->find(kLibraryName))
+ << "Library name " << *name << " doesn't contain expected "
+ << kLibraryName;
+}
+
+} // namespace debug
+} // namespace base
diff --git a/base/debug/leak_tracker.h b/base/debug/leak_tracker.h
index 9dd1622939..7ddd5b62d1 100644
--- a/base/debug/leak_tracker.h
+++ b/base/debug/leak_tracker.h
@@ -56,6 +56,8 @@ namespace debug {
template<typename T>
class LeakTracker {
public:
+ // This destructor suppresses warnings about instances of this class not being
+ // used.
~LeakTracker() {}
static void CheckForLeaks() {}
static int NumLiveInstances() { return -1; }
diff --git a/base/debug/proc_maps_linux.h b/base/debug/proc_maps_linux.h
index 38e92314c8..f5f8a59b8c 100644
--- a/base/debug/proc_maps_linux.h
+++ b/base/debug/proc_maps_linux.h
@@ -31,6 +31,9 @@ struct MappedMemoryRegion {
// Byte offset into |path| of the range mapped into memory.
unsigned long long offset;
+ // Image base, if this mapping corresponds to an ELF image.
+ uintptr_t base;
+
// Bitmask of read/write/execute/private/shared permissions.
uint8_t permissions;
diff --git a/base/debug/profiler.cc b/base/debug/profiler.cc
index b19e7ecd00..ef9afb6daa 100644
--- a/base/debug/profiler.cc
+++ b/base/debug/profiler.cc
@@ -6,7 +6,7 @@
#include <string>
-#include "base/debug/debugging_flags.h"
+#include "base/debug/debugging_buildflags.h"
#include "base/process/process_handle.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
@@ -19,7 +19,7 @@
// TODO(peria): Enable profiling on Windows.
#if BUILDFLAG(ENABLE_PROFILING) && !defined(NO_TCMALLOC) && !defined(OS_WIN)
-#include "third_party/tcmalloc/chromium/src/gperftools/profiler.h"
+#include "third_party/tcmalloc/gperftools-2.0/chromium/src/gperftools/profiler.h"
#endif
namespace base {
@@ -87,58 +87,24 @@ bool IsProfilingSupported() {
#if !defined(OS_WIN)
-bool IsBinaryInstrumented() {
- return false;
-}
-
ReturnAddressLocationResolver GetProfilerReturnAddrResolutionFunc() {
- return NULL;
+ return nullptr;
}
DynamicFunctionEntryHook GetProfilerDynamicFunctionEntryHookFunc() {
- return NULL;
+ return nullptr;
}
AddDynamicSymbol GetProfilerAddDynamicSymbolFunc() {
- return NULL;
+ return nullptr;
}
MoveDynamicSymbol GetProfilerMoveDynamicSymbolFunc() {
- return NULL;
+ return nullptr;
}
#else // defined(OS_WIN)
-bool IsBinaryInstrumented() {
- enum InstrumentationCheckState {
- UNINITIALIZED,
- INSTRUMENTED_IMAGE,
- NON_INSTRUMENTED_IMAGE,
- };
-
- static InstrumentationCheckState state = UNINITIALIZED;
-
- if (state == UNINITIALIZED) {
- base::win::PEImage image(CURRENT_MODULE());
-
- // Check to be sure our image is structured as we'd expect.
- DCHECK(image.VerifyMagic());
-
- // Syzygy-instrumented binaries contain a PE image section named ".thunks",
- // and all Syzygy-modified binaries contain the ".syzygy" image section.
- // This is a very fast check, as it only looks at the image header.
- if ((image.GetImageSectionHeaderByName(".thunks") != NULL) &&
- (image.GetImageSectionHeaderByName(".syzygy") != NULL)) {
- state = INSTRUMENTED_IMAGE;
- } else {
- state = NON_INSTRUMENTED_IMAGE;
- }
- }
- DCHECK(state != UNINITIALIZED);
-
- return state == INSTRUMENTED_IMAGE;
-}
-
namespace {
struct FunctionSearchContext {
@@ -186,9 +152,6 @@ bool FindResolutionFunctionInImports(
template <typename FunctionType>
FunctionType FindFunctionInImports(const char* function_name) {
- if (!IsBinaryInstrumented())
- return NULL;
-
base::win::PEImage image(CURRENT_MODULE());
FunctionSearchContext ctx = { function_name, NULL };
diff --git a/base/debug/profiler.h b/base/debug/profiler.h
index ea81b13c6a..f706a1a3b4 100644
--- a/base/debug/profiler.h
+++ b/base/debug/profiler.h
@@ -35,9 +35,6 @@ BASE_EXPORT bool BeingProfiled();
// Reset profiling after a fork, which disables timers.
BASE_EXPORT void RestartProfilingAfterFork();
-// Returns true iff this executable is instrumented with the Syzygy profiler.
-BASE_EXPORT bool IsBinaryInstrumented();
-
// Returns true iff this executable supports profiling.
BASE_EXPORT bool IsProfilingSupported();
diff --git a/base/debug/stack_trace.cc b/base/debug/stack_trace.cc
index 43a23d95ac..771512176b 100644
--- a/base/debug/stack_trace.cc
+++ b/base/debug/stack_trace.cc
@@ -12,7 +12,7 @@
#include "base/logging.h"
#include "base/macros.h"
-#if HAVE_TRACE_STACK_FRAME_POINTERS
+#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
#if defined(OS_LINUX) || defined(OS_ANDROID)
#include <pthread.h>
@@ -28,14 +28,14 @@
extern "C" void* __libc_stack_end;
#endif
-#endif // HAVE_TRACE_STACK_FRAME_POINTERS
+#endif // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
namespace base {
namespace debug {
namespace {
-#if HAVE_TRACE_STACK_FRAME_POINTERS && !defined(OS_WIN)
+#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
#if defined(__arm__) && defined(__GNUC__) && !defined(__clang__)
// GCC and LLVM generate slightly different frames on ARM, see
@@ -142,11 +142,11 @@ void* LinkStackFrames(void* fpp, void* parent_fp) {
return prev_parent_fp;
}
-#endif // HAVE_TRACE_STACK_FRAME_POINTERS && !defined(OS_WIN)
+#endif // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
} // namespace
-#if HAVE_TRACE_STACK_FRAME_POINTERS
+#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
uintptr_t GetStackEnd() {
#if defined(OS_ANDROID)
// Bionic reads proc/maps on every call to pthread_getattr_np() when called
@@ -194,7 +194,7 @@ uintptr_t GetStackEnd() {
// Don't know how to get end of the stack.
return 0;
}
-#endif // HAVE_TRACE_STACK_FRAME_POINTERS
+#endif // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
StackTrace::StackTrace() : StackTrace(arraysize(trace_)) {}
@@ -209,34 +209,22 @@ const void *const *StackTrace::Addresses(size_t* count) const {
*count = count_;
if (count_)
return trace_;
- return NULL;
+ return nullptr;
}
std::string StackTrace::ToString() const {
std::stringstream stream;
-#if !defined(__UCLIBC__)
+#if !defined(__UCLIBC__) && !defined(_AIX)
OutputToStream(&stream);
#endif
return stream.str();
}
-#if HAVE_TRACE_STACK_FRAME_POINTERS
+#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
size_t TraceStackFramePointers(const void** out_trace,
size_t max_depth,
size_t skip_initial) {
-// TODO(699863): Merge the frame-pointer based stack unwinder into the
-// base::debug::StackTrace platform-specific implementation files.
-#if defined(OS_WIN)
- StackTrace stack(max_depth);
- size_t count = 0;
- const void* const* frames = stack.Addresses(&count);
- if (count < skip_initial)
- return 0u;
- count -= skip_initial;
- memcpy(out_trace, frames + skip_initial, count * sizeof(void*));
- return count;
-#elif defined(OS_POSIX)
// Usage of __builtin_frame_address() enables frame pointers in this
// function even if they are not enabled globally. So 'fp' will always
// be valid.
@@ -270,10 +258,8 @@ size_t TraceStackFramePointers(const void** out_trace,
}
return depth;
-#endif
}
-#if !defined(OS_WIN)
ScopedStackFrameLinker::ScopedStackFrameLinker(void* fp, void* parent_fp)
: fp_(fp),
parent_fp_(parent_fp),
@@ -284,9 +270,8 @@ ScopedStackFrameLinker::~ScopedStackFrameLinker() {
CHECK_EQ(parent_fp_, previous_parent_fp)
<< "Stack frame's parent pointer has changed!";
}
-#endif // !defined(OS_WIN)
-#endif // HAVE_TRACE_STACK_FRAME_POINTERS
+#endif // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
} // namespace debug
} // namespace base
diff --git a/base/debug/stack_trace.h b/base/debug/stack_trace.h
index ab1d2ebe6a..81e6672e8a 100644
--- a/base/debug/stack_trace.h
+++ b/base/debug/stack_trace.h
@@ -11,6 +11,7 @@
#include <string>
#include "base/base_export.h"
+#include "base/debug/debugging_buildflags.h"
#include "base/macros.h"
#include "build/build_config.h"
@@ -23,24 +24,6 @@ struct _EXCEPTION_POINTERS;
struct _CONTEXT;
#endif
-// TODO(699863): Clean up HAVE_TRACE_STACK_FRAME_POINTERS.
-#if defined(OS_POSIX)
-
-#if defined(__i386__) || defined(__x86_64__)
-#define HAVE_TRACE_STACK_FRAME_POINTERS 1
-#elif defined(__arm__) && !defined(__thumb__)
-#define HAVE_TRACE_STACK_FRAME_POINTERS 1
-#else // defined(__arm__) && !defined(__thumb__)
-#define HAVE_TRACE_STACK_FRAME_POINTERS 0
-#endif // defined(__arm__) && !defined(__thumb__)
-
-#elif defined(OS_WIN)
-#define HAVE_TRACE_STACK_FRAME_POINTERS 1
-
-#else // defined(OS_WIN)
-#define HAVE_TRACE_STACK_FRAME_POINTERS 0
-#endif // defined(OS_WIN)
-
namespace base {
namespace debug {
@@ -55,8 +38,14 @@ namespace debug {
// done in official builds because it has security implications).
BASE_EXPORT bool EnableInProcessStackDumping();
+#if defined(OS_POSIX)
+BASE_EXPORT void SetStackDumpFirstChanceCallback(bool (*handler)(int,
+ void*,
+ void*));
+#endif
+
// Returns end of the stack, or 0 if we couldn't get it.
-#if HAVE_TRACE_STACK_FRAME_POINTERS
+#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
BASE_EXPORT uintptr_t GetStackEnd();
#endif
@@ -94,7 +83,7 @@ class BASE_EXPORT StackTrace {
// Prints the stack trace to stderr.
void Print() const;
-#if !defined(__UCLIBC__)
+#if !defined(__UCLIBC__) & !defined(_AIX)
// Resolves backtrace to symbols and write to stream.
void OutputToStream(std::ostream* os) const;
#endif
@@ -119,7 +108,7 @@ class BASE_EXPORT StackTrace {
size_t count_;
};
-#if HAVE_TRACE_STACK_FRAME_POINTERS
+#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
// Traces the stack by using frame pointers. This function is faster but less
// reliable than StackTrace. It should work for debug and profiling builds,
// but not for release builds (although there are some exceptions).
@@ -132,7 +121,6 @@ BASE_EXPORT size_t TraceStackFramePointers(const void** out_trace,
size_t max_depth,
size_t skip_initial);
-#if !defined(OS_WIN)
// Links stack frame |fp| to |parent_fp|, so that during stack unwinding
// TraceStackFramePointers() visits |parent_fp| after visiting |fp|.
// Both frame pointers must come from __builtin_frame_address().
@@ -182,9 +170,8 @@ class BASE_EXPORT ScopedStackFrameLinker {
DISALLOW_COPY_AND_ASSIGN(ScopedStackFrameLinker);
};
-#endif // !defined(OS_WIN)
-#endif // HAVE_TRACE_STACK_FRAME_POINTERS
+#endif // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
namespace internal {
diff --git a/base/debug/stack_trace_posix.cc b/base/debug/stack_trace_posix.cc
index 4a55f64cc6..f3f05dad26 100644
--- a/base/debug/stack_trace_posix.cc
+++ b/base/debug/stack_trace_posix.cc
@@ -11,6 +11,7 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -26,7 +27,7 @@
#if !defined(USE_SYMBOLIZE)
#include <cxxabi.h>
#endif
-#if !defined(__UCLIBC__)
+#if !defined(__UCLIBC__) && !defined(_AIX)
#include <execinfo.h>
#endif
@@ -38,7 +39,9 @@
#include "base/debug/proc_maps_linux.h"
#endif
+#include "base/cfi_buildflags.h"
#include "base/debug/debugger.h"
+#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/free_deleter.h"
@@ -59,6 +62,8 @@ namespace {
volatile sig_atomic_t in_signal_handler = 0;
+bool (*try_handle_signal)(int, void*, void*) = nullptr;
+
#if !defined(USE_SYMBOLIZE)
// The prefix used for mangled symbols, per the Itanium C++ ABI:
// http://www.codesourcery.com/cxx-abi/abi.html#mangling
@@ -80,8 +85,7 @@ void DemangleSymbols(std::string* text) {
// Note: code in this function is NOT async-signal safe (std::string uses
// malloc internally).
-#if !defined(__UCLIBC__)
-
+#if !defined(__UCLIBC__) && !defined(_AIX)
std::string::size_type search_from = 0;
while (search_from < text->size()) {
// Look for the start of a mangled symbol, from search_from.
@@ -103,7 +107,7 @@ void DemangleSymbols(std::string* text) {
// Try to demangle the mangled symbol candidate.
int status = 0;
std::unique_ptr<char, base::FreeDeleter> demangled_symbol(
- abi::__cxa_demangle(mangled_symbol.c_str(), NULL, 0, &status));
+ abi::__cxa_demangle(mangled_symbol.c_str(), nullptr, 0, &status));
if (status == 0) { // Demangling is successful.
// Remove the mangled symbol.
text->erase(mangled_start, mangled_end - mangled_start);
@@ -116,7 +120,7 @@ void DemangleSymbols(std::string* text) {
search_from = mangled_start + 2;
}
}
-#endif // !defined(__UCLIBC__)
+#endif // !defined(__UCLIBC__) && !defined(_AIX)
}
#endif // !defined(USE_SYMBOLIZE)
@@ -125,10 +129,10 @@ class BacktraceOutputHandler {
virtual void HandleOutput(const char* output) = 0;
protected:
- virtual ~BacktraceOutputHandler() {}
+ virtual ~BacktraceOutputHandler() = default;
};
-#if !defined(__UCLIBC__)
+#if !defined(__UCLIBC__) && !defined(_AIX)
void OutputPointer(void* pointer, BacktraceOutputHandler* handler) {
// This should be more than enough to store a 64-bit number in hex:
// 16 hex digits + 1 for null-terminator.
@@ -205,7 +209,7 @@ void ProcessBacktrace(void *const *trace,
}
#endif // defined(USE_SYMBOLIZE)
}
-#endif // !defined(__UCLIBC__)
+#endif // !defined(__UCLIBC__) && !defined(_AIX)
void PrintToStderr(const char* output) {
// NOTE: This code MUST be async-signal safe (it's used by in-process
@@ -217,9 +221,37 @@ void StackDumpSignalHandler(int signal, siginfo_t* info, void* void_context) {
// NOTE: This code MUST be async-signal safe.
// NO malloc or stdio is allowed here.
+ // Give a registered callback a chance to recover from this signal
+ //
+ // V8 uses guard regions to guarantee memory safety in WebAssembly. This means
+ // some signals might be expected if they originate from Wasm code while
+ // accessing the guard region. We give V8 the chance to handle and recover
+ // from these signals first.
+ if (try_handle_signal != nullptr &&
+ try_handle_signal(signal, info, void_context)) {
+ // The first chance handler took care of this. The SA_RESETHAND flag
+ // replaced this signal handler upon entry, but we want to stay
+ // installed. Thus, we reinstall ourselves before returning.
+ struct sigaction action;
+ memset(&action, 0, sizeof(action));
+ action.sa_flags = SA_RESETHAND | SA_SIGINFO;
+ action.sa_sigaction = &StackDumpSignalHandler;
+ sigemptyset(&action.sa_mask);
+
+ sigaction(signal, &action, nullptr);
+ return;
+ }
+
+// Do not take the "in signal handler" code path on Mac in a DCHECK-enabled
+// build, as this prevents seeing a useful (symbolized) stack trace on a crash
+// or DCHECK() failure. While it may not be fully safe to run the stack symbol
+// printing code, in practice it's better to provide meaningful stack traces -
+// and the risk is low given we're likely crashing already.
+#if !defined(OS_MACOSX) || !DCHECK_IS_ON()
// Record the fact that we are in the signal handler now, so that the rest
// of StackTrace can behave in an async-signal-safe manner.
in_signal_handler = 1;
+#endif
if (BeingDebugged())
BreakDebugger();
@@ -289,7 +321,7 @@ void StackDumpSignalHandler(int signal, siginfo_t* info, void* void_context) {
}
PrintToStderr("\n");
-#if defined(CFI_ENFORCEMENT)
+#if BUILDFLAG(CFI_ENFORCEMENT_TRAP)
if (signal == SIGILL && info->si_code == ILL_ILLOPN) {
PrintToStderr(
"CFI: Most likely a control flow integrity violation; for more "
@@ -297,7 +329,7 @@ void StackDumpSignalHandler(int signal, siginfo_t* info, void* void_context) {
PrintToStderr(
"https://www.chromium.org/developers/testing/control-flow-integrity\n");
}
-#endif
+#endif // BUILDFLAG(CFI_ENFORCEMENT_TRAP)
debug::StackTrace().Print();
@@ -390,7 +422,7 @@ void StackDumpSignalHandler(int signal, siginfo_t* info, void* void_context) {
class PrintBacktraceOutputHandler : public BacktraceOutputHandler {
public:
- PrintBacktraceOutputHandler() {}
+ PrintBacktraceOutputHandler() = default;
void HandleOutput(const char* output) override {
// NOTE: This code MUST be async-signal safe (it's used by in-process
@@ -498,7 +530,7 @@ class SandboxSymbolizeHelper {
if (strcmp((it->first).c_str(), file_path) == 0) {
// POSIX.1-2004 requires an implementation to guarantee that dup()
// is async-signal-safe.
- fd = dup(it->second);
+ fd = HANDLE_EINTR(dup(it->second));
break;
}
}
@@ -541,25 +573,10 @@ class SandboxSymbolizeHelper {
// The assumption here is that iterating over
// std::vector<MappedMemoryRegion> using a const_iterator does not allocate
// dynamic memory, hence it is async-signal-safe.
- std::vector<MappedMemoryRegion>::const_iterator it;
- bool is_first = true;
- for (it = instance->regions_.begin(); it != instance->regions_.end();
- ++it, is_first = false) {
- const MappedMemoryRegion& region = *it;
+ for (const MappedMemoryRegion& region : instance->regions_) {
if (region.start <= pc && pc < region.end) {
start_address = region.start;
- // Don't subtract 'start_address' from the first entry:
- // * If a binary is compiled w/o -pie, then the first entry in
- // process maps is likely the binary itself (all dynamic libs
- // are mapped higher in address space). For such a binary,
- // instruction offset in binary coincides with the actual
- // instruction address in virtual memory (as code section
- // is mapped to a fixed memory range).
- // * If a binary is compiled with -pie, all the modules are
- // mapped high at address space (in particular, higher than
- // shadow memory of the tool), so the module can't be the
- // first entry.
- base_address = (is_first ? 0U : start_address) - region.offset;
+ base_address = region.base;
if (file_path && file_path_size > 0) {
strncpy(file_path, region.path.c_str(), file_path_size);
// Ensure null termination.
@@ -571,6 +588,60 @@ class SandboxSymbolizeHelper {
return -1;
}
+ // Set the base address for each memory region by reading ELF headers in
+ // process memory.
+ void SetBaseAddressesForMemoryRegions() {
+ base::ScopedFD mem_fd(
+ HANDLE_EINTR(open("/proc/self/mem", O_RDONLY | O_CLOEXEC)));
+ if (!mem_fd.is_valid())
+ return;
+
+ auto safe_memcpy = [&mem_fd](void* dst, uintptr_t src, size_t size) {
+ return HANDLE_EINTR(pread(mem_fd.get(), dst, size, src)) == ssize_t(size);
+ };
+
+ uintptr_t cur_base = 0;
+ for (auto& r : regions_) {
+ ElfW(Ehdr) ehdr;
+ static_assert(SELFMAG <= sizeof(ElfW(Ehdr)), "SELFMAG too large");
+ if ((r.permissions & MappedMemoryRegion::READ) &&
+ safe_memcpy(&ehdr, r.start, sizeof(ElfW(Ehdr))) &&
+ memcmp(ehdr.e_ident, ELFMAG, SELFMAG) == 0) {
+ switch (ehdr.e_type) {
+ case ET_EXEC:
+ cur_base = 0;
+ break;
+ case ET_DYN:
+ // Find the segment containing file offset 0. This will correspond
+ // to the ELF header that we just read. Normally this will have
+ // virtual address 0, but this is not guaranteed. We must subtract
+ // the virtual address from the address where the ELF header was
+ // mapped to get the base address.
+ //
+ // If we fail to find a segment for file offset 0, use the address
+ // of the ELF header as the base address.
+ cur_base = r.start;
+ for (unsigned i = 0; i != ehdr.e_phnum; ++i) {
+ ElfW(Phdr) phdr;
+ if (safe_memcpy(&phdr, r.start + ehdr.e_phoff + i * sizeof(phdr),
+ sizeof(phdr)) &&
+ phdr.p_type == PT_LOAD && phdr.p_offset == 0) {
+ cur_base = r.start - phdr.p_vaddr;
+ break;
+ }
+ }
+ break;
+ default:
+ // ET_REL or ET_CORE. These aren't directly executable, so they
+ // don't affect the base address.
+ break;
+ }
+ }
+
+ r.base = cur_base;
+ }
+ }
+
// Parses /proc/self/maps in order to compile a list of all object file names
// for the modules that are loaded in the current process.
// Returns true on success.
@@ -588,6 +659,8 @@ class SandboxSymbolizeHelper {
return false;
}
+ SetBaseAddressesForMemoryRegions();
+
is_initialized_ = true;
return true;
}
@@ -644,7 +717,7 @@ class SandboxSymbolizeHelper {
// Unregister symbolization callback.
void UnregisterCallback() {
if (is_initialized_) {
- google::InstallSymbolizeOpenObjectFileCallback(NULL);
+ google::InstallSymbolizeOpenObjectFileCallback(nullptr);
is_initialized_ = false;
}
}
@@ -694,7 +767,7 @@ bool EnableInProcessStackDumping() {
memset(&sigpipe_action, 0, sizeof(sigpipe_action));
sigpipe_action.sa_handler = SIG_IGN;
sigemptyset(&sigpipe_action.sa_mask);
- bool success = (sigaction(SIGPIPE, &sigpipe_action, NULL) == 0);
+ bool success = (sigaction(SIGPIPE, &sigpipe_action, nullptr) == 0);
// Avoid hangs during backtrace initialization, see above.
WarmUpBacktrace();
@@ -705,24 +778,29 @@ bool EnableInProcessStackDumping() {
action.sa_sigaction = &StackDumpSignalHandler;
sigemptyset(&action.sa_mask);
- success &= (sigaction(SIGILL, &action, NULL) == 0);
- success &= (sigaction(SIGABRT, &action, NULL) == 0);
- success &= (sigaction(SIGFPE, &action, NULL) == 0);
- success &= (sigaction(SIGBUS, &action, NULL) == 0);
- success &= (sigaction(SIGSEGV, &action, NULL) == 0);
+ success &= (sigaction(SIGILL, &action, nullptr) == 0);
+ success &= (sigaction(SIGABRT, &action, nullptr) == 0);
+ success &= (sigaction(SIGFPE, &action, nullptr) == 0);
+ success &= (sigaction(SIGBUS, &action, nullptr) == 0);
+ success &= (sigaction(SIGSEGV, &action, nullptr) == 0);
// On Linux, SIGSYS is reserved by the kernel for seccomp-bpf sandboxing.
#if !defined(OS_LINUX)
- success &= (sigaction(SIGSYS, &action, NULL) == 0);
+ success &= (sigaction(SIGSYS, &action, nullptr) == 0);
#endif // !defined(OS_LINUX)
return success;
}
+void SetStackDumpFirstChanceCallback(bool (*handler)(int, void*, void*)) {
+ DCHECK(try_handle_signal == nullptr || handler == nullptr);
+ try_handle_signal = handler;
+}
+
StackTrace::StackTrace(size_t count) {
// NOTE: This code MUST be async-signal safe (it's used by in-process
// stack dumping signal handler). NO malloc or stdio is allowed here.
-#if !defined(__UCLIBC__)
+#if !defined(__UCLIBC__) && !defined(_AIX)
count = std::min(arraysize(trace_), count);
// Though the backtrace API man page does not list any possible negative
@@ -737,13 +815,13 @@ void StackTrace::Print() const {
// NOTE: This code MUST be async-signal safe (it's used by in-process
// stack dumping signal handler). NO malloc or stdio is allowed here.
-#if !defined(__UCLIBC__)
+#if !defined(__UCLIBC__) && !defined(_AIX)
PrintBacktraceOutputHandler handler;
ProcessBacktrace(trace_, count_, &handler);
#endif
}
-#if !defined(__UCLIBC__)
+#if !defined(__UCLIBC__) && !defined(_AIX)
void StackTrace::OutputToStream(std::ostream* os) const {
StreamBacktraceOutputHandler handler(os);
ProcessBacktrace(trace_, count_, &handler);
@@ -757,11 +835,11 @@ char* itoa_r(intptr_t i, char* buf, size_t sz, int base, size_t padding) {
// Make sure we can write at least one NUL byte.
size_t n = 1;
if (n > sz)
- return NULL;
+ return nullptr;
if (base < 2 || base > 16) {
buf[0] = '\000';
- return NULL;
+ return nullptr;
}
char* start = buf;
@@ -776,7 +854,7 @@ char* itoa_r(intptr_t i, char* buf, size_t sz, int base, size_t padding) {
// Make sure we can write the '-' character.
if (++n > sz) {
buf[0] = '\000';
- return NULL;
+ return nullptr;
}
*start++ = '-';
}
@@ -788,7 +866,7 @@ char* itoa_r(intptr_t i, char* buf, size_t sz, int base, size_t padding) {
// Make sure there is still enough space left in our output buffer.
if (++n > sz) {
buf[0] = '\000';
- return NULL;
+ return nullptr;
}
// Output the next digit.
diff --git a/base/debug/task_annotator.cc b/base/debug/task_annotator.cc
index 46969f28ca..18083c120d 100644
--- a/base/debug/task_annotator.cc
+++ b/base/debug/task_annotator.cc
@@ -8,40 +8,68 @@
#include "base/debug/activity_tracker.h"
#include "base/debug/alias.h"
+#include "base/no_destructor.h"
#include "base/pending_task.h"
+#include "base/threading/thread_local.h"
#include "base/trace_event/trace_event.h"
-#include "base/tracked_objects.h"
namespace base {
namespace debug {
-TaskAnnotator::TaskAnnotator() {
-}
+namespace {
+
+TaskAnnotator::ObserverForTesting* g_task_annotator_observer = nullptr;
-TaskAnnotator::~TaskAnnotator() {
+// Returns the TLS slot that stores the PendingTask currently in progress on
+// each thread. Used to allow creating a breadcrumb of program counters on the
+// stack to help identify a task's origin in crashes.
+ThreadLocalPointer<const PendingTask>* GetTLSForCurrentPendingTask() {
+ static NoDestructor<ThreadLocalPointer<const PendingTask>>
+ tls_for_current_pending_task;
+ return tls_for_current_pending_task.get();
}
-void TaskAnnotator::DidQueueTask(const char* queue_function,
- const PendingTask& pending_task) {
- TRACE_EVENT_WITH_FLOW0(TRACE_DISABLED_BY_DEFAULT("toplevel.flow"),
- queue_function,
- TRACE_ID_MANGLE(GetTaskTraceID(pending_task)),
- TRACE_EVENT_FLAG_FLOW_OUT);
+} // namespace
+
+TaskAnnotator::TaskAnnotator() = default;
+
+TaskAnnotator::~TaskAnnotator() = default;
+
+void TaskAnnotator::WillQueueTask(const char* queue_function,
+ PendingTask* pending_task) {
+ if (queue_function) {
+ TRACE_EVENT_WITH_FLOW0(TRACE_DISABLED_BY_DEFAULT("toplevel.flow"),
+ queue_function,
+ TRACE_ID_MANGLE(GetTaskTraceID(*pending_task)),
+ TRACE_EVENT_FLAG_FLOW_OUT);
+ }
+
+ // TODO(https://crbug.com/826902): Fix callers that invoke WillQueueTask()
+ // twice for the same PendingTask.
+ // DCHECK(!pending_task.task_backtrace[0])
+ // << "Task backtrace was already set, task posted twice??";
+ if (!pending_task->task_backtrace[0]) {
+ const PendingTask* parent_task = GetTLSForCurrentPendingTask()->Get();
+ if (parent_task) {
+ pending_task->task_backtrace[0] =
+ parent_task->posted_from.program_counter();
+ std::copy(parent_task->task_backtrace.begin(),
+ parent_task->task_backtrace.end() - 1,
+ pending_task->task_backtrace.begin() + 1);
+ }
+ }
}
void TaskAnnotator::RunTask(const char* queue_function,
PendingTask* pending_task) {
ScopedTaskRunActivity task_activity(*pending_task);
- tracked_objects::TaskStopwatch stopwatch;
- stopwatch.Start();
- tracked_objects::Duration queue_duration =
- stopwatch.StartTime() - pending_task->EffectiveTimePosted();
-
- TRACE_EVENT_WITH_FLOW1(
- TRACE_DISABLED_BY_DEFAULT("toplevel.flow"), queue_function,
- TRACE_ID_MANGLE(GetTaskTraceID(*pending_task)), TRACE_EVENT_FLAG_FLOW_IN,
- "queue_duration", queue_duration.InMilliseconds());
+ if (queue_function) {
+ TRACE_EVENT_WITH_FLOW0(TRACE_DISABLED_BY_DEFAULT("toplevel.flow"),
+ queue_function,
+ TRACE_ID_MANGLE(GetTaskTraceID(*pending_task)),
+ TRACE_EVENT_FLAG_FLOW_IN);
+ }
// Before running the task, store the task backtrace with the chain of
// PostTasks that resulted in this call and deliberately alias it to ensure
@@ -49,18 +77,30 @@ void TaskAnnotator::RunTask(const char* queue_function,
// variable itself will have the expected value when displayed by the
// optimizer in an optimized build. Look at a memory dump of the stack.
static constexpr int kStackTaskTraceSnapshotSize =
- std::tuple_size<decltype(pending_task->task_backtrace)>::value + 1;
+ std::tuple_size<decltype(pending_task->task_backtrace)>::value + 3;
std::array<const void*, kStackTaskTraceSnapshotSize> task_backtrace;
- task_backtrace[0] = pending_task->posted_from.program_counter();
+
+ // Store a marker to locate |task_backtrace| content easily on a memory
+ // dump.
+ task_backtrace.front() = reinterpret_cast<void*>(0xefefefefefefefef);
+ task_backtrace.back() = reinterpret_cast<void*>(0xfefefefefefefefe);
+
+ task_backtrace[1] = pending_task->posted_from.program_counter();
std::copy(pending_task->task_backtrace.begin(),
- pending_task->task_backtrace.end(), task_backtrace.begin() + 1);
+ pending_task->task_backtrace.end(), task_backtrace.begin() + 2);
debug::Alias(&task_backtrace);
+ ThreadLocalPointer<const PendingTask>* tls_for_current_pending_task =
+ GetTLSForCurrentPendingTask();
+ const PendingTask* previous_pending_task =
+ tls_for_current_pending_task->Get();
+ tls_for_current_pending_task->Set(pending_task);
+
+ if (g_task_annotator_observer)
+ g_task_annotator_observer->BeforeRunTask(pending_task);
std::move(pending_task->task).Run();
- stopwatch.Stop();
- tracked_objects::ThreadData::TallyRunOnNamedThreadIfTracking(*pending_task,
- stopwatch);
+ tls_for_current_pending_task->Set(previous_pending_task);
}
uint64_t TaskAnnotator::GetTaskTraceID(const PendingTask& task) const {
@@ -69,5 +109,16 @@ uint64_t TaskAnnotator::GetTaskTraceID(const PendingTask& task) const {
32);
}
+// static
+void TaskAnnotator::RegisterObserverForTesting(ObserverForTesting* observer) {
+ DCHECK(!g_task_annotator_observer);
+ g_task_annotator_observer = observer;
+}
+
+// static
+void TaskAnnotator::ClearObserverForTesting() {
+ g_task_annotator_observer = nullptr;
+}
+
} // namespace debug
} // namespace base
diff --git a/base/debug/task_annotator.h b/base/debug/task_annotator.h
index 34115d8f3d..9ff5c7bbb1 100644
--- a/base/debug/task_annotator.h
+++ b/base/debug/task_annotator.h
@@ -18,24 +18,44 @@ namespace debug {
// such as task origins, queueing durations and memory usage.
class BASE_EXPORT TaskAnnotator {
public:
+ class ObserverForTesting {
+ public:
+ virtual ~ObserverForTesting() = default;
+ // Invoked just before RunTask() in the scope in which the task is about to
+ // be executed.
+ virtual void BeforeRunTask(const PendingTask* pending_task) = 0;
+ };
+
TaskAnnotator();
~TaskAnnotator();
- // Called to indicate that a task has been queued to run in the future.
- // |queue_function| is used as the trace flow event name.
- void DidQueueTask(const char* queue_function,
- const PendingTask& pending_task);
+ // Called to indicate that a task is about to be queued to run in the future,
+ // giving one last chance for this TaskAnnotator to add metadata to
+ // |pending_task| before it is moved into the queue. |queue_function| is used
+ // as the trace flow event name. |queue_function| can be null if the caller
+ // doesn't want trace flow events logged to toplevel.flow.
+ void WillQueueTask(const char* queue_function, PendingTask* pending_task);
// Run a previously queued task. |queue_function| should match what was
// passed into |DidQueueTask| for this task.
void RunTask(const char* queue_function, PendingTask* pending_task);
- private:
// Creates a process-wide unique ID to represent this task in trace events.
// This will be mangled with a Process ID hash to reduce the likelyhood of
- // colliding with TaskAnnotator pointers on other processes.
+ // colliding with TaskAnnotator pointers on other processes. Callers may use
+ // this when generating their own flow events (i.e. when passing
+ // |queue_function == nullptr| in above methods).
uint64_t GetTaskTraceID(const PendingTask& task) const;
+ private:
+ friend class TaskAnnotatorBacktraceIntegrationTest;
+
+ // Registers an ObserverForTesting that will be invoked by all TaskAnnotators'
+ // RunTask(). This registration and the implementation of BeforeRunTask() are
+ // responsible to ensure thread-safety.
+ static void RegisterObserverForTesting(ObserverForTesting* observer);
+ static void ClearObserverForTesting();
+
DISALLOW_COPY_AND_ASSIGN(TaskAnnotator);
};
diff --git a/base/debug/task_annotator_unittest.cc b/base/debug/task_annotator_unittest.cc
index 8a1c8bdc87..2f07bbd15f 100644
--- a/base/debug/task_annotator_unittest.cc
+++ b/base/debug/task_annotator_unittest.cc
@@ -3,8 +3,24 @@
// found in the LICENSE file.
#include "base/debug/task_annotator.h"
+
+#include <algorithm>
+#include <vector>
+
#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
#include "base/pending_task.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
@@ -19,14 +35,337 @@ void TestTask(int* result) {
TEST(TaskAnnotatorTest, QueueAndRunTask) {
int result = 0;
- PendingTask pending_task(FROM_HERE, Bind(&TestTask, &result));
+ PendingTask pending_task(FROM_HERE, BindOnce(&TestTask, &result));
TaskAnnotator annotator;
- annotator.DidQueueTask("TaskAnnotatorTest::Queue", pending_task);
+ annotator.WillQueueTask("TaskAnnotatorTest::Queue", &pending_task);
EXPECT_EQ(0, result);
annotator.RunTask("TaskAnnotatorTest::Queue", &pending_task);
EXPECT_EQ(123, result);
}
+// Test task annotator integration in base APIs and ensuing support for
+// backtraces. Tasks posted across multiple threads in this test fixture should
+// be synchronized as BeforeRunTask() and VerifyTraceAndPost() assume tasks are
+// observed in lock steps, one at a time.
+class TaskAnnotatorBacktraceIntegrationTest
+ : public ::testing::Test,
+ public TaskAnnotator::ObserverForTesting {
+ public:
+ using ExpectedTrace = std::vector<const void*>;
+
+ TaskAnnotatorBacktraceIntegrationTest() = default;
+
+ ~TaskAnnotatorBacktraceIntegrationTest() override = default;
+
+ // TaskAnnotator::ObserverForTesting:
+ void BeforeRunTask(const PendingTask* pending_task) override {
+ AutoLock auto_lock(on_before_run_task_lock_);
+ last_posted_from_ = pending_task->posted_from;
+ last_task_backtrace_ = pending_task->task_backtrace;
+ }
+
+ void SetUp() override { TaskAnnotator::RegisterObserverForTesting(this); }
+
+ void TearDown() override { TaskAnnotator::ClearObserverForTesting(); }
+
+ void VerifyTraceAndPost(const scoped_refptr<SequencedTaskRunner>& task_runner,
+ const Location& posted_from,
+ const Location& next_from_here,
+ const ExpectedTrace& expected_trace,
+ OnceClosure task) {
+ SCOPED_TRACE(StringPrintf("Callback Depth: %zu", expected_trace.size()));
+
+ EXPECT_EQ(posted_from, last_posted_from_);
+ for (size_t i = 0; i < last_task_backtrace_.size(); i++) {
+ SCOPED_TRACE(StringPrintf("Trace frame: %zu", i));
+ if (i < expected_trace.size())
+ EXPECT_EQ(expected_trace[i], last_task_backtrace_[i]);
+ else
+ EXPECT_EQ(nullptr, last_task_backtrace_[i]);
+ }
+
+ task_runner->PostTask(next_from_here, std::move(task));
+ }
+
+ // Same as VerifyTraceAndPost() with the exception that it also posts a task
+ // that will prevent |task| from running until |wait_before_next_task| is
+ // signaled.
+ void VerifyTraceAndPostWithBlocker(
+ const scoped_refptr<SequencedTaskRunner>& task_runner,
+ const Location& posted_from,
+ const Location& next_from_here,
+ const ExpectedTrace& expected_trace,
+ OnceClosure task,
+ WaitableEvent* wait_before_next_task) {
+ DCHECK(wait_before_next_task);
+
+ // Need to lock to ensure the upcoming VerifyTraceAndPost() runs before the
+ // BeforeRunTask() hook for the posted WaitableEvent::Wait(). Otherwise the
+ // upcoming VerifyTraceAndPost() will race to read the state saved in the
+ // BeforeRunTask() hook preceding the current task.
+ AutoLock auto_lock(on_before_run_task_lock_);
+ task_runner->PostTask(
+ FROM_HERE,
+ BindOnce(&WaitableEvent::Wait, Unretained(wait_before_next_task)));
+ VerifyTraceAndPost(task_runner, posted_from, next_from_here, expected_trace,
+ std::move(task));
+ }
+
+ protected:
+ static void RunTwo(OnceClosure c1, OnceClosure c2) {
+ std::move(c1).Run();
+ std::move(c2).Run();
+ }
+
+ private:
+ // While calls to VerifyTraceAndPost() are strictly ordered in tests below
+ // (and hence non-racy), some helper methods (e.g. Wait/Signal) do racily call
+ // into BeforeRunTask(). This Lock ensures these unobserved writes are not
+ // racing. Locking isn't required on read per the VerifyTraceAndPost()
+ // themselves being ordered.
+ Lock on_before_run_task_lock_;
+
+ Location last_posted_from_ = {};
+ std::array<const void*, 4> last_task_backtrace_ = {};
+
+ DISALLOW_COPY_AND_ASSIGN(TaskAnnotatorBacktraceIntegrationTest);
+};
+
+// Ensure the task backtrace populates correctly.
+TEST_F(TaskAnnotatorBacktraceIntegrationTest, SingleThreadedSimple) {
+ MessageLoop loop;
+ const Location location0 = FROM_HERE;
+ const Location location1 = FROM_HERE;
+ const Location location2 = FROM_HERE;
+ const Location location3 = FROM_HERE;
+ const Location location4 = FROM_HERE;
+ const Location location5 = FROM_HERE;
+
+ RunLoop run_loop;
+
+ // Task 5 has tasks 4/3/2/1 as parents (task 0 isn't visible as only the
+ // last 4 parents are kept).
+ OnceClosure task5 = BindOnce(
+ &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
+ Unretained(this), loop.task_runner(), location5, FROM_HERE,
+ ExpectedTrace({location4.program_counter(), location3.program_counter(),
+ location2.program_counter(), location1.program_counter()}),
+ run_loop.QuitClosure());
+
+ // Task i=4/3/2/1/0 have tasks [0,i) as parents.
+ OnceClosure task4 = BindOnce(
+ &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
+ Unretained(this), loop.task_runner(), location4, location5,
+ ExpectedTrace({location3.program_counter(), location2.program_counter(),
+ location1.program_counter(), location0.program_counter()}),
+ std::move(task5));
+ OnceClosure task3 = BindOnce(
+ &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
+ Unretained(this), loop.task_runner(), location3, location4,
+ ExpectedTrace({location2.program_counter(), location1.program_counter(),
+ location0.program_counter()}),
+ std::move(task4));
+ OnceClosure task2 = BindOnce(
+ &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
+ Unretained(this), loop.task_runner(), location2, location3,
+ ExpectedTrace({location1.program_counter(), location0.program_counter()}),
+ std::move(task3));
+ OnceClosure task1 =
+ BindOnce(&TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
+ Unretained(this), loop.task_runner(), location1, location2,
+ ExpectedTrace({location0.program_counter()}), std::move(task2));
+ OnceClosure task0 =
+ BindOnce(&TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
+ Unretained(this), loop.task_runner(), location0, location1,
+ ExpectedTrace({}), std::move(task1));
+
+ loop.task_runner()->PostTask(location0, std::move(task0));
+
+ run_loop.Run();
+}
+
+// Ensure it works when posting tasks across multiple threads managed by //base.
+TEST_F(TaskAnnotatorBacktraceIntegrationTest, MultipleThreads) {
+ test::ScopedTaskEnvironment scoped_task_environment;
+
+ // Use diverse task runners (a MessageLoop on the main thread, a TaskScheduler
+ // based SequencedTaskRunner, and a TaskScheduler based
+ // SingleThreadTaskRunner) to verify that TaskAnnotator can capture backtraces
+ // for PostTasks back-and-forth between these.
+ auto main_thread_a = ThreadTaskRunnerHandle::Get();
+ auto task_runner_b = CreateSingleThreadTaskRunnerWithTraits({});
+ auto task_runner_c = CreateSequencedTaskRunnerWithTraits(
+ {base::MayBlock(), base::WithBaseSyncPrimitives()});
+
+ const Location& location_a0 = FROM_HERE;
+ const Location& location_a1 = FROM_HERE;
+ const Location& location_a2 = FROM_HERE;
+ const Location& location_a3 = FROM_HERE;
+
+ const Location& location_b0 = FROM_HERE;
+ const Location& location_b1 = FROM_HERE;
+
+ const Location& location_c0 = FROM_HERE;
+
+ RunLoop run_loop;
+
+ // All tasks below happen in lock step by nature of being posted by the
+ // previous one (plus the synchronous nature of RunTwo()) with the exception
+ // of the follow-up local task to |task_b0_local|. This WaitableEvent ensures
+ // it completes before |task_c0| runs to avoid racy invocations of
+ // BeforeRunTask()+VerifyTraceAndPost().
+ WaitableEvent lock_step(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+
+ // Here is the execution order generated below:
+ // A: TA0 -> TA1 \ TA2
+ // B: TB0L \ + TB0F \ Signal \ /
+ // ---------\--/ \ /
+ // \ \ /
+ // C: Wait........ TC0 /
+
+ // On task runner c, post a task back to main thread that verifies its trace
+ // and terminates after one more self-post.
+ OnceClosure task_a2 = BindOnce(
+ &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
+ Unretained(this), main_thread_a, location_a2, location_a3,
+ ExpectedTrace(
+ {location_c0.program_counter(), location_b0.program_counter(),
+ location_a1.program_counter(), location_a0.program_counter()}),
+ run_loop.QuitClosure());
+ OnceClosure task_c0 =
+ BindOnce(&TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
+ Unretained(this), main_thread_a, location_c0, location_a2,
+ ExpectedTrace({location_b0.program_counter(),
+ location_a1.program_counter(),
+ location_a0.program_counter()}),
+ std::move(task_a2));
+
+ // On task runner b run two tasks that conceptually come from the same
+ // location (managed via RunTwo().) One will post back to task runner b and
+ // another will post to task runner c to test spawning multiple tasks on
+ // different message loops. The task posted to task runner c will not get
+ // location b1 whereas the one posted back to task runner b will.
+ OnceClosure task_b0_fork = BindOnce(
+ &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPostWithBlocker,
+ Unretained(this), task_runner_c, location_b0, location_c0,
+ ExpectedTrace(
+ {location_a1.program_counter(), location_a0.program_counter()}),
+ std::move(task_c0), &lock_step);
+ OnceClosure task_b0_local =
+ BindOnce(&TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
+ Unretained(this), task_runner_b, location_b0, location_b1,
+ ExpectedTrace({location_a1.program_counter(),
+ location_a0.program_counter()}),
+ BindOnce(&WaitableEvent::Signal, Unretained(&lock_step)));
+
+ OnceClosure task_a1 =
+ BindOnce(&TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
+ Unretained(this), task_runner_b, location_a1, location_b0,
+ ExpectedTrace({location_a0.program_counter()}),
+ BindOnce(&TaskAnnotatorBacktraceIntegrationTest::RunTwo,
+ std::move(task_b0_local), std::move(task_b0_fork)));
+ OnceClosure task_a0 =
+ BindOnce(&TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
+ Unretained(this), main_thread_a, location_a0, location_a1,
+ ExpectedTrace({}), std::move(task_a1));
+
+ main_thread_a->PostTask(location_a0, std::move(task_a0));
+
+ run_loop.Run();
+}
+
+// Ensure nesting doesn't break the chain.
+TEST_F(TaskAnnotatorBacktraceIntegrationTest, SingleThreadedNested) {
+ MessageLoop loop;
+ const Location location0 = FROM_HERE;
+ const Location location1 = FROM_HERE;
+ const Location location2 = FROM_HERE;
+ const Location location3 = FROM_HERE;
+ const Location location4 = FROM_HERE;
+ const Location location5 = FROM_HERE;
+
+ RunLoop run_loop;
+
+ // Task execution below looks like this, w.r.t. to RunLoop depths:
+ // 1 : T0 \ + NRL1 \ ---------> T4 -> T5
+ // 2 : ---------> T1 \ -> NRL2 \ ----> T2 -> T3 / + Quit /
+ // 3 : ---------> DN /
+
+ // NRL1 tests that tasks that occur at a different nesting depth than their
+ // parent have a sane backtrace nonetheless (both ways).
+
+ // NRL2 tests that posting T2 right after exiting the RunLoop (from the same
+ // task) results in NRL2 being its parent (and not the DoNothing() task that
+ // just ran -- which would have been the case if the "current task" wasn't
+ // restored properly when returning from a task within a task).
+
+ // In other words, this is regression test for a bug in the previous
+ // implementation. In the current implementation, replacing
+ // tls_for_current_pending_task->Set(previous_pending_task);
+ // by
+ // tls_for_current_pending_task->Set(nullptr);
+ // at the end of TaskAnnotator::RunTask() makes this test fail.
+
+ RunLoop nested_run_loop1(RunLoop::Type::kNestableTasksAllowed);
+
+ // Expectations are the same as in SingleThreadedSimple test despite the
+ // nested loop starting between tasks 0 and 1 and stopping between tasks 3 and
+ // 4.
+ OnceClosure task5 = BindOnce(
+ &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
+ Unretained(this), loop.task_runner(), location5, FROM_HERE,
+ ExpectedTrace({location4.program_counter(), location3.program_counter(),
+ location2.program_counter(), location1.program_counter()}),
+ run_loop.QuitClosure());
+ OnceClosure task4 = BindOnce(
+ &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
+ Unretained(this), loop.task_runner(), location4, location5,
+ ExpectedTrace({location3.program_counter(), location2.program_counter(),
+ location1.program_counter(), location0.program_counter()}),
+ std::move(task5));
+ OnceClosure task3 = BindOnce(
+ &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
+ Unretained(this), loop.task_runner(), location3, location4,
+ ExpectedTrace({location2.program_counter(), location1.program_counter(),
+ location0.program_counter()}),
+ std::move(task4));
+
+ OnceClosure run_task_3_then_quit_nested_loop1 =
+ BindOnce(&TaskAnnotatorBacktraceIntegrationTest::RunTwo, std::move(task3),
+ nested_run_loop1.QuitClosure());
+
+ OnceClosure task2 = BindOnce(
+ &TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
+ Unretained(this), loop.task_runner(), location2, location3,
+ ExpectedTrace({location1.program_counter(), location0.program_counter()}),
+ std::move(run_task_3_then_quit_nested_loop1));
+
+ // Task 1 is custom. It enters another nested RunLoop, has it do work and exit
+ // before posting the next task. This confirms that |task1| is restored as the
+ // current task before posting |task2| after returning from the nested loop.
+ RunLoop nested_run_loop2(RunLoop::Type::kNestableTasksAllowed);
+ OnceClosure task1 = BindOnce(
+ [](RunLoop* nested_run_loop, const Location& location2,
+ OnceClosure task2) {
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, DoNothing());
+ nested_run_loop->RunUntilIdle();
+ ThreadTaskRunnerHandle::Get()->PostTask(location2, std::move(task2));
+ },
+ Unretained(&nested_run_loop2), location2, std::move(task2));
+
+ OnceClosure task0 =
+ BindOnce(&TaskAnnotatorBacktraceIntegrationTest::VerifyTraceAndPost,
+ Unretained(this), loop.task_runner(), location0, location1,
+ ExpectedTrace({}), std::move(task1));
+
+ loop.task_runner()->PostTask(location0, std::move(task0));
+ loop.task_runner()->PostTask(
+ FROM_HERE, BindOnce(&RunLoop::Run, Unretained(&nested_run_loop1)));
+
+ run_loop.Run();
+}
+
} // namespace debug
} // namespace base
diff --git a/base/debug/thread_heap_usage_tracker.h b/base/debug/thread_heap_usage_tracker.h
index 508a0a3973..eb03b3f8b8 100644
--- a/base/debug/thread_heap_usage_tracker.h
+++ b/base/debug/thread_heap_usage_tracker.h
@@ -7,7 +7,7 @@
#include <stdint.h>
-#include "base/allocator/features.h"
+#include "base/allocator/buildflags.h"
#include "base/base_export.h"
#include "base/threading/thread_checker.h"
@@ -82,7 +82,7 @@ class BASE_EXPORT ThreadHeapUsageTracker {
static ThreadHeapUsage GetUsageSnapshot();
// Enables the heap intercept. May only be called once, and only if the heap
- // shim is available, e.g. if BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM) is
+ // shim is available, e.g. if BUILDFLAG(USE_ALLOCATOR_SHIM) is
// true.
static void EnableHeapTracking();
diff --git a/base/environment.cc b/base/environment.cc
index 8b1d8fc312..cdea53c8c1 100644
--- a/base/environment.cc
+++ b/base/environment.cc
@@ -14,10 +14,10 @@
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
-#if defined(OS_POSIX)
-#include <stdlib.h>
-#elif defined(OS_WIN)
+#if defined(OS_WIN)
#include <windows.h>
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <stdlib.h>
#endif
namespace base {
@@ -56,15 +56,7 @@ class EnvironmentImpl : public Environment {
private:
bool GetVarImpl(StringPiece variable_name, std::string* result) {
-#if defined(OS_POSIX)
- const char* env_value = getenv(variable_name.data());
- if (!env_value)
- return false;
- // Note that the variable may be defined but empty.
- if (result)
- *result = env_value;
- return true;
-#elif defined(OS_WIN)
+#if defined(OS_WIN)
DWORD value_length =
::GetEnvironmentVariable(UTF8ToWide(variable_name).c_str(), nullptr, 0);
if (value_length == 0)
@@ -76,29 +68,35 @@ class EnvironmentImpl : public Environment {
*result = WideToUTF8(value.get());
}
return true;
-#else
-#error need to port
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ const char* env_value = getenv(variable_name.data());
+ if (!env_value)
+ return false;
+ // Note that the variable may be defined but empty.
+ if (result)
+ *result = env_value;
+ return true;
#endif
}
bool SetVarImpl(StringPiece variable_name, const std::string& new_value) {
-#if defined(OS_POSIX)
- // On success, zero is returned.
- return !setenv(variable_name.data(), new_value.c_str(), 1);
-#elif defined(OS_WIN)
+#if defined(OS_WIN)
// On success, a nonzero value is returned.
return !!SetEnvironmentVariable(UTF8ToWide(variable_name).c_str(),
UTF8ToWide(new_value).c_str());
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ // On success, zero is returned.
+ return !setenv(variable_name.data(), new_value.c_str(), 1);
#endif
}
bool UnSetVarImpl(StringPiece variable_name) {
-#if defined(OS_POSIX)
- // On success, zero is returned.
- return !unsetenv(variable_name.data());
-#elif defined(OS_WIN)
+#if defined(OS_WIN)
// On success, a nonzero value is returned.
return !!SetEnvironmentVariable(UTF8ToWide(variable_name).c_str(), nullptr);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ // On success, zero is returned.
+ return !unsetenv(variable_name.data());
#endif
}
};
@@ -124,7 +122,7 @@ size_t ParseEnvLine(const NativeEnvironmentString::value_type* input,
namespace env_vars {
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// On Posix systems, this variable contains the location of the user's home
// directory. (e.g, /home/username/).
const char kHome[] = "HOME";
@@ -132,11 +130,11 @@ const char kHome[] = "HOME";
} // namespace env_vars
-Environment::~Environment() {}
+Environment::~Environment() = default;
// static
std::unique_ptr<Environment> Environment::Create() {
- return MakeUnique<EnvironmentImpl>();
+ return std::make_unique<EnvironmentImpl>();
}
bool Environment::HasVar(StringPiece variable_name) {
@@ -183,7 +181,7 @@ string16 AlterEnvironment(const wchar_t* env,
return result;
}
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
std::unique_ptr<char* []> AlterEnvironment(const char* const* const env,
const EnvironmentMap& changes) {
@@ -235,6 +233,6 @@ std::unique_ptr<char* []> AlterEnvironment(const char* const* const env,
return result;
}
-#endif // OS_POSIX
+#endif // OS_WIN
} // namespace base
diff --git a/base/environment.h b/base/environment.h
index 3a4ed04e4b..e842ab087a 100644
--- a/base/environment.h
+++ b/base/environment.h
@@ -18,7 +18,7 @@ namespace base {
namespace env_vars {
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
BASE_EXPORT extern const char kHome[];
#endif
@@ -66,7 +66,7 @@ typedef std::map<NativeEnvironmentString, NativeEnvironmentString>
BASE_EXPORT string16 AlterEnvironment(const wchar_t* env,
const EnvironmentMap& changes);
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
typedef std::string NativeEnvironmentString;
typedef std::map<NativeEnvironmentString, NativeEnvironmentString>
diff --git a/base/environment_unittest.cc b/base/environment_unittest.cc
index ef264cf619..7cb8c9c4fb 100644
--- a/base/environment_unittest.cc
+++ b/base/environment_unittest.cc
@@ -14,11 +14,22 @@ typedef PlatformTest EnvironmentTest;
namespace base {
+namespace {
+
+// PATH env variable is not set on Fuchsia by default, while PWD is not set on
+// Windows.
+#if defined(OS_FUCHSIA)
+constexpr char kValidEnvironmentVariable[] = "PWD";
+#else
+constexpr char kValidEnvironmentVariable[] = "PATH";
+#endif
+
+} // namespace
+
TEST_F(EnvironmentTest, GetVar) {
- // Every setup should have non-empty PATH...
std::unique_ptr<Environment> env(Environment::Create());
std::string env_value;
- EXPECT_TRUE(env->GetVar("PATH", &env_value));
+ EXPECT_TRUE(env->GetVar(kValidEnvironmentVariable, &env_value));
EXPECT_NE(env_value, "");
}
@@ -51,9 +62,8 @@ TEST_F(EnvironmentTest, GetVarReverse) {
}
TEST_F(EnvironmentTest, HasVar) {
- // Every setup should have PATH...
std::unique_ptr<Environment> env(Environment::Create());
- EXPECT_TRUE(env->HasVar("PATH"));
+ EXPECT_TRUE(env->HasVar(kValidEnvironmentVariable));
}
TEST_F(EnvironmentTest, SetVar) {
@@ -127,39 +137,39 @@ TEST_F(EnvironmentTest, AlterEnvironment) {
#else
TEST_F(EnvironmentTest, AlterEnvironment) {
- const char* const empty[] = { NULL };
- const char* const a2[] = { "A=2", NULL };
+ const char* const empty[] = {nullptr};
+ const char* const a2[] = {"A=2", nullptr};
EnvironmentMap changes;
std::unique_ptr<char* []> e;
e = AlterEnvironment(empty, changes);
- EXPECT_TRUE(e[0] == NULL);
+ EXPECT_TRUE(e[0] == nullptr);
changes["A"] = "1";
e = AlterEnvironment(empty, changes);
EXPECT_EQ(std::string("A=1"), e[0]);
- EXPECT_TRUE(e[1] == NULL);
+ EXPECT_TRUE(e[1] == nullptr);
changes.clear();
changes["A"] = std::string();
e = AlterEnvironment(empty, changes);
- EXPECT_TRUE(e[0] == NULL);
+ EXPECT_TRUE(e[0] == nullptr);
changes.clear();
e = AlterEnvironment(a2, changes);
EXPECT_EQ(std::string("A=2"), e[0]);
- EXPECT_TRUE(e[1] == NULL);
+ EXPECT_TRUE(e[1] == nullptr);
changes.clear();
changes["A"] = "1";
e = AlterEnvironment(a2, changes);
EXPECT_EQ(std::string("A=1"), e[0]);
- EXPECT_TRUE(e[1] == NULL);
+ EXPECT_TRUE(e[1] == nullptr);
changes.clear();
changes["A"] = std::string();
e = AlterEnvironment(a2, changes);
- EXPECT_TRUE(e[0] == NULL);
+ EXPECT_TRUE(e[0] == nullptr);
}
#endif
diff --git a/base/export_template.h b/base/export_template.h
new file mode 100644
index 0000000000..aac8b7c7f1
--- /dev/null
+++ b/base/export_template.h
@@ -0,0 +1,163 @@
+// 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_EXPORT_TEMPLATE_H_
+#define BASE_EXPORT_TEMPLATE_H_
+
+// Synopsis
+//
+// This header provides macros for using FOO_EXPORT macros with explicit
+// template instantiation declarations and definitions.
+// Generally, the FOO_EXPORT macros are used at declarations,
+// and GCC requires them to be used at explicit instantiation declarations,
+// but MSVC requires __declspec(dllexport) to be used at the explicit
+// instantiation definitions instead.
+
+// Usage
+//
+// In a header file, write:
+//
+// extern template class EXPORT_TEMPLATE_DECLARE(FOO_EXPORT) foo<bar>;
+//
+// In a source file, write:
+//
+// template class EXPORT_TEMPLATE_DEFINE(FOO_EXPORT) foo<bar>;
+
+// Implementation notes
+//
+// The implementation of this header uses some subtle macro semantics to
+// detect what the provided FOO_EXPORT value was defined as and then
+// to dispatch to appropriate macro definitions. Unfortunately,
+// MSVC's C preprocessor is rather non-compliant and requires special
+// care to make it work.
+//
+// Issue 1.
+//
+// #define F(x)
+// F()
+//
+// MSVC emits warning C4003 ("not enough actual parameters for macro
+// 'F'), even though it's a valid macro invocation. This affects the
+// macros below that take just an "export" parameter, because export
+// may be empty.
+//
+// As a workaround, we can add a dummy parameter and arguments:
+//
+// #define F(x,_)
+// F(,)
+//
+// Issue 2.
+//
+// #define F(x) G##x
+// #define Gj() ok
+// F(j())
+//
+// The correct replacement for "F(j())" is "ok", but MSVC replaces it
+// with "Gj()". As a workaround, we can pass the result to an
+// identity macro to force MSVC to look for replacements again. (This
+// is why EXPORT_TEMPLATE_STYLE_3 exists.)
+
+#define EXPORT_TEMPLATE_DECLARE(export) \
+ EXPORT_TEMPLATE_INVOKE(DECLARE, EXPORT_TEMPLATE_STYLE(export, ), export)
+#define EXPORT_TEMPLATE_DEFINE(export) \
+ EXPORT_TEMPLATE_INVOKE(DEFINE, EXPORT_TEMPLATE_STYLE(export, ), export)
+
+// INVOKE is an internal helper macro to perform parameter replacements
+// and token pasting to chain invoke another macro. E.g.,
+// EXPORT_TEMPLATE_INVOKE(DECLARE, DEFAULT, FOO_EXPORT)
+// will export to call
+// EXPORT_TEMPLATE_DECLARE_DEFAULT(FOO_EXPORT, )
+// (but with FOO_EXPORT expanded too).
+#define EXPORT_TEMPLATE_INVOKE(which, style, export) \
+ EXPORT_TEMPLATE_INVOKE_2(which, style, export)
+#define EXPORT_TEMPLATE_INVOKE_2(which, style, export) \
+ EXPORT_TEMPLATE_##which##_##style(export, )
+
+// Default style is to apply the FOO_EXPORT macro at declaration sites.
+#define EXPORT_TEMPLATE_DECLARE_DEFAULT(export, _) export
+#define EXPORT_TEMPLATE_DEFINE_DEFAULT(export, _)
+
+// The "MSVC hack" style is used when FOO_EXPORT is defined
+// as __declspec(dllexport), which MSVC requires to be used at
+// definition sites instead.
+#define EXPORT_TEMPLATE_DECLARE_MSVC_HACK(export, _)
+#define EXPORT_TEMPLATE_DEFINE_MSVC_HACK(export, _) export
+
+// EXPORT_TEMPLATE_STYLE is an internal helper macro that identifies which
+// export style needs to be used for the provided FOO_EXPORT macro definition.
+// "", "__attribute__(...)", and "__declspec(dllimport)" are mapped
+// to "DEFAULT"; while "__declspec(dllexport)" is mapped to "MSVC_HACK".
+//
+// It's implemented with token pasting to transform the __attribute__ and
+// __declspec annotations into macro invocations. E.g., if FOO_EXPORT is
+// defined as "__declspec(dllimport)", it undergoes the following sequence of
+// macro substitutions:
+// EXPORT_TEMPLATE_STYLE(FOO_EXPORT, )
+// EXPORT_TEMPLATE_STYLE_2(__declspec(dllimport), )
+// EXPORT_TEMPLATE_STYLE_3(EXPORT_TEMPLATE_STYLE_MATCH__declspec(dllimport))
+// EXPORT_TEMPLATE_STYLE_MATCH__declspec(dllimport)
+// EXPORT_TEMPLATE_STYLE_MATCH_DECLSPEC_dllimport
+// DEFAULT
+#define EXPORT_TEMPLATE_STYLE(export, _) EXPORT_TEMPLATE_STYLE_2(export, )
+#define EXPORT_TEMPLATE_STYLE_2(export, _) \
+ EXPORT_TEMPLATE_STYLE_3( \
+ EXPORT_TEMPLATE_STYLE_MATCH_foj3FJo5StF0OvIzl7oMxA##export)
+#define EXPORT_TEMPLATE_STYLE_3(style) style
+
+// Internal helper macros for EXPORT_TEMPLATE_STYLE.
+//
+// XXX: C++ reserves all identifiers containing "__" for the implementation,
+// but "__attribute__" and "__declspec" already contain "__" and the token-paste
+// operator can only add characters; not remove them. To minimize the risk of
+// conflict with implementations, we include "foj3FJo5StF0OvIzl7oMxA" (a random
+// 128-bit string, encoded in Base64) in the macro name.
+#define EXPORT_TEMPLATE_STYLE_MATCH_foj3FJo5StF0OvIzl7oMxA DEFAULT
+#define EXPORT_TEMPLATE_STYLE_MATCH_foj3FJo5StF0OvIzl7oMxA__attribute__(...) \
+ DEFAULT
+#define EXPORT_TEMPLATE_STYLE_MATCH_foj3FJo5StF0OvIzl7oMxA__declspec(arg) \
+ EXPORT_TEMPLATE_STYLE_MATCH_DECLSPEC_##arg
+
+// Internal helper macros for EXPORT_TEMPLATE_STYLE.
+#define EXPORT_TEMPLATE_STYLE_MATCH_DECLSPEC_dllexport MSVC_HACK
+#define EXPORT_TEMPLATE_STYLE_MATCH_DECLSPEC_dllimport DEFAULT
+
+// Sanity checks.
+//
+// EXPORT_TEMPLATE_TEST uses the same macro invocation pattern as
+// EXPORT_TEMPLATE_DECLARE and EXPORT_TEMPLATE_DEFINE do to check that they're
+// working correctly. When they're working correctly, the sequence of macro
+// replacements should go something like:
+//
+// EXPORT_TEMPLATE_TEST(DEFAULT, __declspec(dllimport));
+//
+// static_assert(EXPORT_TEMPLATE_INVOKE(TEST_DEFAULT,
+// EXPORT_TEMPLATE_STYLE(__declspec(dllimport), ),
+// __declspec(dllimport)), "__declspec(dllimport)");
+//
+// static_assert(EXPORT_TEMPLATE_INVOKE(TEST_DEFAULT,
+// DEFAULT, __declspec(dllimport)), "__declspec(dllimport)");
+//
+// static_assert(EXPORT_TEMPLATE_TEST_DEFAULT_DEFAULT(
+// __declspec(dllimport)), "__declspec(dllimport)");
+//
+// static_assert(true, "__declspec(dllimport)");
+//
+// When they're not working correctly, a syntax error should occur instead.
+#define EXPORT_TEMPLATE_TEST(want, export) \
+ static_assert(EXPORT_TEMPLATE_INVOKE( \
+ TEST_##want, EXPORT_TEMPLATE_STYLE(export, ), export), \
+ #export)
+#define EXPORT_TEMPLATE_TEST_DEFAULT_DEFAULT(...) true
+#define EXPORT_TEMPLATE_TEST_MSVC_HACK_MSVC_HACK(...) true
+
+EXPORT_TEMPLATE_TEST(DEFAULT, );
+EXPORT_TEMPLATE_TEST(DEFAULT, __attribute__((visibility("default"))));
+EXPORT_TEMPLATE_TEST(MSVC_HACK, __declspec(dllexport));
+EXPORT_TEMPLATE_TEST(DEFAULT, __declspec(dllimport));
+
+#undef EXPORT_TEMPLATE_TEST
+#undef EXPORT_TEMPLATE_TEST_DEFAULT_DEFAULT
+#undef EXPORT_TEMPLATE_TEST_MSVC_HACK_MSVC_HACK
+
+#endif // BASE_EXPORT_TEMPLATE_H_
diff --git a/base/feature_list.cc b/base/feature_list.cc
index 61043ceb73..1610eecbcf 100644
--- a/base/feature_list.cc
+++ b/base/feature_list.cc
@@ -23,7 +23,7 @@ namespace {
// Pointer to the FeatureList instance singleton that was set via
// FeatureList::SetInstance(). Does not use base/memory/singleton.h in order to
// have more control over initialization timing. Leaky.
-FeatureList* g_instance = nullptr;
+FeatureList* g_feature_list_instance = nullptr;
// Tracks whether the FeatureList instance was initialized via an accessor.
bool g_initialized_from_accessor = false;
@@ -76,9 +76,14 @@ bool IsValidFeatureOrFieldTrialName(const std::string& name) {
} // namespace
-FeatureList::FeatureList() {}
+#if DCHECK_IS_CONFIGURABLE
+const Feature kDCheckIsFatalFeature{"DcheckIsFatal",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+#endif // DCHECK_IS_CONFIGURABLE
-FeatureList::~FeatureList() {}
+FeatureList::FeatureList() = default;
+
+FeatureList::~FeatureList() = default;
void FeatureList::InitializeFromCommandLine(
const std::string& enable_features,
@@ -181,50 +186,31 @@ void FeatureList::AddFeaturesToAllocator(PersistentMemoryAllocator* allocator) {
void FeatureList::GetFeatureOverrides(std::string* enable_overrides,
std::string* disable_overrides) {
- DCHECK(initialized_);
-
- enable_overrides->clear();
- disable_overrides->clear();
-
- // Note: Since |overrides_| is a std::map, iteration will be in alphabetical
- // order. This not guaranteed to users of this function, but is useful for
- // tests to assume the order.
- for (const auto& entry : overrides_) {
- std::string* target_list = nullptr;
- switch (entry.second.overridden_state) {
- case OVERRIDE_USE_DEFAULT:
- case OVERRIDE_ENABLE_FEATURE:
- target_list = enable_overrides;
- break;
- case OVERRIDE_DISABLE_FEATURE:
- target_list = disable_overrides;
- break;
- }
+ GetFeatureOverridesImpl(enable_overrides, disable_overrides, false);
+}
- if (!target_list->empty())
- target_list->push_back(',');
- if (entry.second.overridden_state == OVERRIDE_USE_DEFAULT)
- target_list->push_back('*');
- target_list->append(entry.first);
- if (entry.second.field_trial) {
- target_list->push_back('<');
- target_list->append(entry.second.field_trial->trial_name());
- }
- }
+void FeatureList::GetCommandLineFeatureOverrides(
+ std::string* enable_overrides,
+ std::string* disable_overrides) {
+ GetFeatureOverridesImpl(enable_overrides, disable_overrides, true);
}
// static
bool FeatureList::IsEnabled(const Feature& feature) {
- if (!g_instance) {
+ if (!g_feature_list_instance) {
g_initialized_from_accessor = true;
return feature.default_state == FEATURE_ENABLED_BY_DEFAULT;
}
- return g_instance->IsFeatureEnabled(feature);
+ return g_feature_list_instance->IsFeatureEnabled(feature);
}
// static
FieldTrial* FeatureList::GetFieldTrial(const Feature& feature) {
- return GetInstance()->GetAssociatedFieldTrial(feature);
+ if (!g_feature_list_instance) {
+ g_initialized_from_accessor = true;
+ return nullptr;
+ }
+ return g_feature_list_instance->GetAssociatedFieldTrial(feature);
}
// static
@@ -249,12 +235,12 @@ bool FeatureList::InitializeInstance(const std::string& enable_features,
// accessor call(s) which likely returned incorrect information.
CHECK(!g_initialized_from_accessor);
bool instance_existed_before = false;
- if (g_instance) {
- if (g_instance->initialized_from_command_line_)
+ if (g_feature_list_instance) {
+ if (g_feature_list_instance->initialized_from_command_line_)
return false;
- delete g_instance;
- g_instance = nullptr;
+ delete g_feature_list_instance;
+ g_feature_list_instance = nullptr;
instance_existed_before = true;
}
@@ -266,22 +252,36 @@ bool FeatureList::InitializeInstance(const std::string& enable_features,
// static
FeatureList* FeatureList::GetInstance() {
- return g_instance;
+ return g_feature_list_instance;
}
// static
void FeatureList::SetInstance(std::unique_ptr<FeatureList> instance) {
- DCHECK(!g_instance);
+ DCHECK(!g_feature_list_instance);
instance->FinalizeInitialization();
// Note: Intentional leak of global singleton.
- g_instance = instance.release();
+ g_feature_list_instance = instance.release();
+
+#if DCHECK_IS_CONFIGURABLE
+ // Update the behaviour of LOG_DCHECK to match the Feature configuration.
+ // DCHECK is also forced to be FATAL if we are running a death-test.
+ // TODO(asvitkine): If we find other use-cases that need integrating here
+ // then define a proper API/hook for the purpose.
+ if (base::FeatureList::IsEnabled(kDCheckIsFatalFeature) ||
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ "gtest_internal_run_death_test")) {
+ logging::LOG_DCHECK = logging::LOG_FATAL;
+ } else {
+ logging::LOG_DCHECK = logging::LOG_INFO;
+ }
+#endif // DCHECK_IS_CONFIGURABLE
}
// static
std::unique_ptr<FeatureList> FeatureList::ClearInstanceForTesting() {
- FeatureList* old_instance = g_instance;
- g_instance = nullptr;
+ FeatureList* old_instance = g_feature_list_instance;
+ g_feature_list_instance = nullptr;
g_initialized_from_accessor = false;
return base::WrapUnique(old_instance);
}
@@ -289,9 +289,9 @@ std::unique_ptr<FeatureList> FeatureList::ClearInstanceForTesting() {
// static
void FeatureList::RestoreInstanceForTesting(
std::unique_ptr<FeatureList> instance) {
- DCHECK(!g_instance);
+ DCHECK(!g_feature_list_instance);
// Note: Intentional leak of global singleton.
- g_instance = instance.release();
+ g_feature_list_instance = instance.release();
}
void FeatureList::FinalizeInitialization() {
@@ -375,6 +375,47 @@ void FeatureList::RegisterOverride(StringPiece feature_name,
feature_name.as_string(), OverrideEntry(overridden_state, field_trial)));
}
+void FeatureList::GetFeatureOverridesImpl(std::string* enable_overrides,
+ std::string* disable_overrides,
+ bool command_line_only) {
+ DCHECK(initialized_);
+
+ enable_overrides->clear();
+ disable_overrides->clear();
+
+ // Note: Since |overrides_| is a std::map, iteration will be in alphabetical
+ // order. This is not guaranteed to users of this function, but is useful for
+ // tests to assume the order.
+ for (const auto& entry : overrides_) {
+ if (command_line_only &&
+ (entry.second.field_trial != nullptr ||
+ entry.second.overridden_state == OVERRIDE_USE_DEFAULT)) {
+ continue;
+ }
+
+ std::string* target_list = nullptr;
+ switch (entry.second.overridden_state) {
+ case OVERRIDE_USE_DEFAULT:
+ case OVERRIDE_ENABLE_FEATURE:
+ target_list = enable_overrides;
+ break;
+ case OVERRIDE_DISABLE_FEATURE:
+ target_list = disable_overrides;
+ break;
+ }
+
+ if (!target_list->empty())
+ target_list->push_back(',');
+ if (entry.second.overridden_state == OVERRIDE_USE_DEFAULT)
+ target_list->push_back('*');
+ target_list->append(entry.first);
+ if (entry.second.field_trial) {
+ target_list->push_back('<');
+ target_list->append(entry.second.field_trial->trial_name());
+ }
+ }
+}
+
bool FeatureList::CheckFeatureIdentity(const Feature& feature) {
AutoLock auto_lock(feature_identity_tracker_lock_);
diff --git a/base/feature_list.h b/base/feature_list.h
index c9f4a7b0c4..2237507a4e 100644
--- a/base/feature_list.h
+++ b/base/feature_list.h
@@ -30,18 +30,26 @@ enum FeatureState {
// The Feature struct is used to define the default state for a feature. See
// comment below for more details. There must only ever be one struct instance
// for a given feature name - generally defined as a constant global variable or
-// file static.
+// file static. It should never be used as a constexpr as it breaks
+// pointer-based identity lookup.
struct BASE_EXPORT Feature {
- constexpr Feature(const char* name, FeatureState default_state)
- : name(name), default_state(default_state) {}
// The name of the feature. This should be unique to each feature and is used
// for enabling/disabling features via command line flags and experiments.
+ // It is strongly recommended to use CamelCase style for feature names, e.g.
+ // "MyGreatFeature".
const char* const name;
// The default state (i.e. enabled or disabled) for this feature.
const FeatureState default_state;
};
+#if DCHECK_IS_CONFIGURABLE
+// DCHECKs have been built-in, and are configurable at run-time to be fatal, or
+// not, via a DcheckIsFatal feature. We define the Feature here since it is
+// checked in FeatureList::SetInstance(). See https://crbug.com/596231.
+extern BASE_EXPORT const Feature kDCheckIsFatalFeature;
+#endif // DCHECK_IS_CONFIGURABLE
+
// The FeatureList class is used to determine whether a given feature is on or
// off. It provides an authoritative answer, taking into account command-line
// overrides and experimental control.
@@ -70,6 +78,10 @@ struct BASE_EXPORT Feature {
// --enable-features=Feature5,Feature7
// --disable-features=Feature1,Feature2,Feature3
//
+// To enable/disable features in a test, do NOT append --enable-features or
+// --disable-features to the command-line directly. Instead, use
+// ScopedFeatureList. See base/test/scoped_feature_list.h for details.
+//
// After initialization (which should be done single-threaded), the FeatureList
// API is thread safe.
//
@@ -146,6 +158,11 @@ class BASE_EXPORT FeatureList {
void GetFeatureOverrides(std::string* enable_overrides,
std::string* disable_overrides);
+ // Like GetFeatureOverrides(), but only returns overrides that were specified
+ // explicitly on the command-line, omitting the ones from field trials.
+ void GetCommandLineFeatureOverrides(std::string* enable_overrides,
+ std::string* disable_overrides);
+
// Returns whether the given |feature| is enabled. Must only be called after
// the singleton instance has been registered via SetInstance(). Additionally,
// a feature with a given name must only have a single corresponding Feature
@@ -254,6 +271,13 @@ class BASE_EXPORT FeatureList {
OverrideState overridden_state,
FieldTrial* field_trial);
+ // Implementation of GetFeatureOverrides() with a parameter that specifies
+ // whether only command-line enabled overrides should be emitted. See that
+ // function's comments for more details.
+ void GetFeatureOverridesImpl(std::string* enable_overrides,
+ std::string* disable_overrides,
+ bool command_line_only);
+
// Verifies that there's only a single definition of a Feature struct for a
// given feature name. Keeps track of the first seen Feature struct for each
// feature. Returns false when called on a Feature struct with a different
diff --git a/base/feature_list_unittest.cc b/base/feature_list_unittest.cc
index 5fbd294dcf..164997a721 100644
--- a/base/feature_list_unittest.cc
+++ b/base/feature_list_unittest.cc
@@ -373,6 +373,11 @@ TEST_F(FeatureListTest, GetFeatureOverrides) {
&disable_features);
EXPECT_EQ("A,OffByDefault<Trial,X", SortFeatureListString(enable_features));
EXPECT_EQ("D", SortFeatureListString(disable_features));
+
+ FeatureList::GetInstance()->GetCommandLineFeatureOverrides(&enable_features,
+ &disable_features);
+ EXPECT_EQ("A,X", SortFeatureListString(enable_features));
+ EXPECT_EQ("D", SortFeatureListString(disable_features));
}
TEST_F(FeatureListTest, GetFeatureOverrides_UseDefault) {
diff --git a/base/files/dir_reader_linux.h b/base/files/dir_reader_linux.h
index 4ce0c34245..259bcfede3 100644
--- a/base/files/dir_reader_linux.h
+++ b/base/files/dir_reader_linux.h
@@ -89,7 +89,7 @@ class DirReaderLinux {
private:
const int fd_;
- unsigned char buf_[512];
+ alignas(linux_dirent) unsigned char buf_[512];
size_t offset_;
size_t size_;
diff --git a/base/files/dir_reader_posix.h b/base/files/dir_reader_posix.h
index 6a32d9fd48..15fc744f6f 100644
--- a/base/files/dir_reader_posix.h
+++ b/base/files/dir_reader_posix.h
@@ -17,7 +17,7 @@
// seems worse than falling back to enumerating all file descriptors so we will
// probably never implement this on the Mac.
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_ANDROID)
#include "base/files/dir_reader_linux.h"
#else
#include "base/files/dir_reader_fallback.h"
@@ -25,7 +25,7 @@
namespace base {
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_ANDROID)
typedef DirReaderLinux DirReaderPosix;
#else
typedef DirReaderFallback DirReaderPosix;
diff --git a/base/files/dir_reader_posix_unittest.cc b/base/files/dir_reader_posix_unittest.cc
index 5d7fd8b139..1954cb2f08 100644
--- a/base/files/dir_reader_posix_unittest.cc
+++ b/base/files/dir_reader_posix_unittest.cc
@@ -5,6 +5,7 @@
#include "base/files/dir_reader_posix.h"
#include <fcntl.h>
+#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -32,8 +33,8 @@ TEST(DirReaderPosixUnittest, Read) {
const char* dir = temp_dir.GetPath().value().c_str();
ASSERT_TRUE(dir);
- const int prev_wd = open(".", O_RDONLY | O_DIRECTORY);
- DCHECK_GE(prev_wd, 0);
+ char wdbuf[PATH_MAX];
+ PCHECK(getcwd(wdbuf, PATH_MAX));
PCHECK(chdir(dir) == 0);
@@ -84,8 +85,7 @@ TEST(DirReaderPosixUnittest, Read) {
PCHECK(rmdir(dir) == 0);
- PCHECK(fchdir(prev_wd) == 0);
- PCHECK(close(prev_wd) == 0);
+ PCHECK(chdir(wdbuf) == 0);
EXPECT_TRUE(seen_dot);
EXPECT_TRUE(seen_dotdot);
diff --git a/base/files/file.cc b/base/files/file.cc
index 1b2224e323..e8934b1cdc 100644
--- a/base/files/file.cc
+++ b/base/files/file.cc
@@ -9,6 +9,10 @@
#include "base/timer/elapsed_timer.h"
#include "build/build_config.h"
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <errno.h>
+#endif
+
namespace base {
File::Info::Info()
@@ -17,8 +21,7 @@ File::Info::Info()
is_symbolic_link(false) {
}
-File::Info::~Info() {
-}
+File::Info::~Info() = default;
File::File()
: error_details_(FILE_ERROR_FAILED),
@@ -33,12 +36,14 @@ File::File(const FilePath& path, uint32_t flags)
}
#endif
-File::File(PlatformFile platform_file)
+File::File(PlatformFile platform_file) : File(platform_file, false) {}
+
+File::File(PlatformFile platform_file, bool async)
: file_(platform_file),
error_details_(FILE_OK),
created_(false),
- async_(false) {
-#if defined(OS_POSIX)
+ async_(async) {
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
DCHECK_GE(platform_file, -1);
#endif
}
@@ -61,17 +66,7 @@ File::~File() {
Close();
}
-// static
-File File::CreateForAsyncHandle(PlatformFile platform_file) {
- File file(platform_file);
- // It would be nice if we could validate that |platform_file| was opened with
- // FILE_FLAG_OVERLAPPED on Windows but this doesn't appear to be possible.
- file.async_ = true;
- return file;
-}
-
File& File::operator=(File&& other) {
- DCHECK_NE(this, &other);
Close();
SetPlatformFile(other.TakePlatformFile());
tracing_path_ = other.tracing_path_;
@@ -84,6 +79,13 @@ File& File::operator=(File&& other) {
#if !defined(OS_NACL)
void File::Initialize(const FilePath& path, uint32_t flags) {
if (path.ReferencesParent()) {
+#if defined(OS_WIN)
+ ::SetLastError(ERROR_ACCESS_DENIED);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ errno = EACCES;
+#else
+#error Unsupported platform
+#endif
error_details_ = FILE_ERROR_ACCESS_DENIED;
return;
}
diff --git a/base/files/file.h b/base/files/file.h
index 0155c7c259..30f4053214 100644
--- a/base/files/file.h
+++ b/base/files/file.h
@@ -12,36 +12,24 @@
#include "base/base_export.h"
#include "base/files/file_path.h"
#include "base/files/file_tracing.h"
+#include "base/files/platform_file.h"
#include "base/files/scoped_file.h"
#include "base/macros.h"
#include "base/time/time.h"
#include "build/build_config.h"
-#if defined(OS_WIN)
-#include <windows.h>
-#include "base/win/scoped_handle.h"
-#endif
-
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
#include <sys/stat.h>
#endif
namespace base {
-#if defined(OS_WIN)
-using PlatformFile = HANDLE;
-
-const PlatformFile kInvalidPlatformFile = INVALID_HANDLE_VALUE;
-#elif defined(OS_POSIX)
-using PlatformFile = int;
-
-const PlatformFile kInvalidPlatformFile = -1;
-#if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL)
+#if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL) || \
+ defined(OS_FUCHSIA) || (defined(OS_ANDROID) && __ANDROID_API__ < 21)
typedef struct stat stat_wrapper_t;
-#else
+#elif defined(OS_POSIX)
typedef struct stat64 stat_wrapper_t;
#endif
-#endif // defined(OS_POSIX)
// Thin wrapper around an OS-level file.
// Note that this class does not provide any support for asynchronous IO, other
@@ -90,9 +78,9 @@ class BASE_EXPORT File {
// See DeleteOnClose() for details.
};
- // This enum has been recorded in multiple histograms. If the order of the
- // fields needs to change, please ensure that those histograms are obsolete or
- // have been moved to a different enum.
+ // This enum has been recorded in multiple histograms using PlatformFileError
+ // enum. If the order of the fields needs to change, please ensure that those
+ // histograms are obsolete or have been moved to a different enum.
//
// FILE_ERROR_ACCESS_DENIED is returned when a call fails because of a
// filesystem restriction. FILE_ERROR_SECURITY is returned when a browser
@@ -134,7 +122,7 @@ class BASE_EXPORT File {
struct BASE_EXPORT Info {
Info();
~Info();
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Fills this struct with values from |stat_info|.
void FromStat(const stat_wrapper_t& stat_info);
#endif
@@ -165,9 +153,14 @@ class BASE_EXPORT File {
// |path| contains path traversal ('..') components.
File(const FilePath& path, uint32_t flags);
- // Takes ownership of |platform_file|.
+ // Takes ownership of |platform_file| and sets async to false.
explicit File(PlatformFile platform_file);
+ // Takes ownership of |platform_file| and sets async to the given value.
+ // This constructor exists because on Windows you can't check if platform_file
+ // is async or not.
+ File(PlatformFile platform_file, bool async);
+
// Creates an object with a specific error_details code.
explicit File(Error error_details);
@@ -175,9 +168,6 @@ class BASE_EXPORT File {
~File();
- // Takes ownership of |platform_file|.
- static File CreateForAsyncHandle(PlatformFile platform_file);
-
File& operator=(File&& other);
// Creates or opens the given file.
@@ -233,7 +223,7 @@ class BASE_EXPORT File {
// Writes the given buffer into the file at the given offset, overwritting any
// data that was previously there. Returns the number of bytes written, or -1
// on error. Note that this function makes a best effort to write all data on
- // all platforms.
+ // all platforms. |data| can be nullptr when |size| is 0.
// Ignores the offset and writes to the end of the file if the file was opened
// with FLAG_APPEND.
int Write(int64_t offset, const char* data, int size);
@@ -273,6 +263,8 @@ class BASE_EXPORT File {
// Returns some basic information for the given file.
bool GetInfo(Info* info);
+#if !defined(OS_FUCHSIA) // Fuchsia's POSIX API does not support file locking.
+
// Attempts to take an exclusive write lock on the file. Returns immediately
// (i.e. does not wait for another process to unlock the file). If the lock
// was obtained, the result will be FILE_OK. A lock only guarantees
@@ -298,6 +290,8 @@ class BASE_EXPORT File {
// Unlock a file previously locked.
Error Unlock();
+#endif // !defined(OS_FUCHSIA)
+
// Returns a new object referencing this file for use within the current
// process. Handling of FLAG_DELETE_ON_CLOSE varies by OS. On POSIX, the File
// object that was created or initialized with this flag will have unlinked
@@ -308,19 +302,21 @@ class BASE_EXPORT File {
bool async() const { return async_; }
#if defined(OS_WIN)
- // Sets or clears the DeleteFile disposition on the handle. Returns true if
+ // Sets or clears the DeleteFile disposition on the file. Returns true if
// the disposition was set or cleared, as indicated by |delete_on_close|.
//
- // Microsoft Windows deletes a file only when the last handle to the
- // underlying kernel object is closed when the DeleteFile disposition has been
- // set by any handle holder. This disposition is be set by:
+ // Microsoft Windows deletes a file only when the DeleteFile disposition is
+ // set on a file when the last handle to the last underlying kernel File
+ // object is closed. This disposition is be set by:
// - Calling the Win32 DeleteFile function with the path to a file.
- // - Opening/creating a file with FLAG_DELETE_ON_CLOSE.
+ // - Opening/creating a file with FLAG_DELETE_ON_CLOSE and then closing all
+ // handles to that File object.
// - Opening/creating a file with FLAG_CAN_DELETE_ON_CLOSE and subsequently
// calling DeleteOnClose(true).
//
// In all cases, all pre-existing handles to the file must have been opened
- // with FLAG_SHARE_DELETE.
+ // with FLAG_SHARE_DELETE. Once the disposition has been set by any of the
+ // above means, no new File objects can be created for the file.
//
// So:
// - Use FLAG_SHARE_DELETE when creating/opening a file to allow another
@@ -329,6 +325,9 @@ class BASE_EXPORT File {
// using this permission doesn't provide any protections.)
// - Use FLAG_DELETE_ON_CLOSE for any file that is to be deleted after use.
// The OS will ensure it is deleted even in the face of process termination.
+ // Note that it's possible for deletion to be cancelled via another File
+ // object referencing the same file using DeleteOnClose(false) to clear the
+ // DeleteFile disposition after the original File is closed.
// - Use FLAG_CAN_DELETE_ON_CLOSE in conjunction with DeleteOnClose() to alter
// the DeleteFile disposition on an open handle. This fine-grained control
// allows for marking a file for deletion during processing so that it is
@@ -339,10 +338,16 @@ class BASE_EXPORT File {
#if defined(OS_WIN)
static Error OSErrorToFileError(DWORD last_error);
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
static Error OSErrorToFileError(int saved_errno);
#endif
+ // Gets the last global error (errno or GetLastError()) and converts it to the
+ // closest base::File::Error equivalent via OSErrorToFileError(). The returned
+ // value is only trustworthy immediately after another base::File method
+ // fails. base::File never resets the global error to zero.
+ static Error GetLastFileError();
+
// Converts an error value to a human-readable form. Used for logging.
static std::string ErrorToString(Error error);
@@ -355,11 +360,7 @@ class BASE_EXPORT File {
void SetPlatformFile(PlatformFile file);
-#if defined(OS_WIN)
- win::ScopedHandle file_;
-#elif defined(OS_POSIX)
- ScopedFD file_;
-#endif
+ ScopedPlatformFile file_;
// A path to use for tracing purposes. Set if file tracing is enabled during
// |Initialize()|.
@@ -378,4 +379,3 @@ class BASE_EXPORT File {
} // namespace base
#endif // BASE_FILES_FILE_H_
-
diff --git a/base/files/file_descriptor_watcher_posix.cc b/base/files/file_descriptor_watcher_posix.cc
index 9746e35ea7..b26bf6c5d8 100644
--- a/base/files/file_descriptor_watcher_posix.cc
+++ b/base/files/file_descriptor_watcher_posix.cc
@@ -8,6 +8,8 @@
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/message_loop/message_pump_for_io.h"
#include "base/sequenced_task_runner.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/sequenced_task_runner_handle.h"
@@ -41,10 +43,10 @@ FileDescriptorWatcher::Controller::~Controller() {
}
class FileDescriptorWatcher::Controller::Watcher
- : public MessageLoopForIO::Watcher,
- public MessageLoop::DestructionObserver {
+ : public MessagePumpForIO::FdWatcher,
+ public MessageLoopCurrent::DestructionObserver {
public:
- Watcher(WeakPtr<Controller> controller, MessageLoopForIO::Mode mode, int fd);
+ Watcher(WeakPtr<Controller> controller, MessagePumpForIO::Mode mode, int fd);
~Watcher() override;
void StartWatching();
@@ -52,15 +54,15 @@ class FileDescriptorWatcher::Controller::Watcher
private:
friend class FileDescriptorWatcher;
- // MessageLoopForIO::Watcher:
+ // MessagePumpForIO::FdWatcher:
void OnFileCanReadWithoutBlocking(int fd) override;
void OnFileCanWriteWithoutBlocking(int fd) override;
- // MessageLoop::DestructionObserver:
+ // MessageLoopCurrent::DestructionObserver:
void WillDestroyCurrentMessageLoop() override;
- // Used to instruct the MessageLoopForIO to stop watching the file descriptor.
- MessageLoopForIO::FileDescriptorWatcher file_descriptor_watcher_;
+ // The MessageLoopForIO's watch handle (stops the watch when destroyed).
+ MessagePumpForIO::FdWatchController fd_watch_controller_;
// Runs tasks on the sequence on which this was instantiated (i.e. the
// sequence on which the callback must run).
@@ -72,7 +74,7 @@ class FileDescriptorWatcher::Controller::Watcher
// Whether this Watcher is notified when |fd_| becomes readable or writable
// without blocking.
- const MessageLoopForIO::Mode mode_;
+ const MessagePumpForIO::Mode mode_;
// The watched file descriptor.
const int fd_;
@@ -90,9 +92,9 @@ class FileDescriptorWatcher::Controller::Watcher
FileDescriptorWatcher::Controller::Watcher::Watcher(
WeakPtr<Controller> controller,
- MessageLoopForIO::Mode mode,
+ MessagePumpForIO::Mode mode,
int fd)
- : file_descriptor_watcher_(FROM_HERE),
+ : fd_watch_controller_(FROM_HERE),
controller_(controller),
mode_(mode),
fd_(fd) {
@@ -102,17 +104,22 @@ FileDescriptorWatcher::Controller::Watcher::Watcher(
FileDescriptorWatcher::Controller::Watcher::~Watcher() {
DCHECK(thread_checker_.CalledOnValidThread());
- MessageLoopForIO::current()->RemoveDestructionObserver(this);
+ MessageLoopCurrentForIO::Get()->RemoveDestructionObserver(this);
}
void FileDescriptorWatcher::Controller::Watcher::StartWatching() {
DCHECK(thread_checker_.CalledOnValidThread());
- MessageLoopForIO::current()->WatchFileDescriptor(
- fd_, false, mode_, &file_descriptor_watcher_, this);
+ if (!MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
+ fd_, false, mode_, &fd_watch_controller_, this)) {
+ // TODO(wez): Ideally we would [D]CHECK here, or propagate the failure back
+ // to the caller, but there is no guarantee that they haven't already
+ // closed |fd_| on another thread, so the best we can do is Debug-log.
+ DLOG(ERROR) << "Failed to watch fd=" << fd_;
+ }
if (!registered_as_destruction_observer_) {
- MessageLoopForIO::current()->AddDestructionObserver(this);
+ MessageLoopCurrentForIO::Get()->AddDestructionObserver(this);
registered_as_destruction_observer_ = true;
}
}
@@ -120,23 +127,23 @@ void FileDescriptorWatcher::Controller::Watcher::StartWatching() {
void FileDescriptorWatcher::Controller::Watcher::OnFileCanReadWithoutBlocking(
int fd) {
DCHECK_EQ(fd_, fd);
- DCHECK_EQ(MessageLoopForIO::WATCH_READ, mode_);
+ DCHECK_EQ(MessagePumpForIO::WATCH_READ, mode_);
DCHECK(thread_checker_.CalledOnValidThread());
// Run the callback on the sequence on which the watch was initiated.
- callback_task_runner_->PostTask(FROM_HERE,
- Bind(&Controller::RunCallback, controller_));
+ callback_task_runner_->PostTask(
+ FROM_HERE, BindOnce(&Controller::RunCallback, controller_));
}
void FileDescriptorWatcher::Controller::Watcher::OnFileCanWriteWithoutBlocking(
int fd) {
DCHECK_EQ(fd_, fd);
- DCHECK_EQ(MessageLoopForIO::WATCH_WRITE, mode_);
+ DCHECK_EQ(MessagePumpForIO::WATCH_WRITE, mode_);
DCHECK(thread_checker_.CalledOnValidThread());
// Run the callback on the sequence on which the watch was initiated.
- callback_task_runner_->PostTask(FROM_HERE,
- Bind(&Controller::RunCallback, controller_));
+ callback_task_runner_->PostTask(
+ FROM_HERE, BindOnce(&Controller::RunCallback, controller_));
}
void FileDescriptorWatcher::Controller::Watcher::
@@ -150,7 +157,7 @@ void FileDescriptorWatcher::Controller::Watcher::
delete this;
}
-FileDescriptorWatcher::Controller::Controller(MessageLoopForIO::Mode mode,
+FileDescriptorWatcher::Controller::Controller(MessagePumpForIO::Mode mode,
int fd,
const Closure& callback)
: callback_(callback),
@@ -159,7 +166,7 @@ FileDescriptorWatcher::Controller::Controller(MessageLoopForIO::Mode mode,
weak_factory_(this) {
DCHECK(!callback_.is_null());
DCHECK(message_loop_for_io_task_runner_);
- watcher_ = MakeUnique<Watcher>(weak_factory_.GetWeakPtr(), mode, fd);
+ watcher_ = std::make_unique<Watcher>(weak_factory_.GetWeakPtr(), mode, fd);
StartWatching();
}
@@ -170,7 +177,7 @@ void FileDescriptorWatcher::Controller::StartWatching() {
// Controller's destructor. Since this delete task hasn't been posted yet, it
// can't run before the task posted below.
message_loop_for_io_task_runner_->PostTask(
- FROM_HERE, Bind(&Watcher::StartWatching, Unretained(watcher_.get())));
+ FROM_HERE, BindOnce(&Watcher::StartWatching, Unretained(watcher_.get())));
}
void FileDescriptorWatcher::Controller::RunCallback() {
@@ -198,13 +205,13 @@ FileDescriptorWatcher::~FileDescriptorWatcher() {
std::unique_ptr<FileDescriptorWatcher::Controller>
FileDescriptorWatcher::WatchReadable(int fd, const Closure& callback) {
- return WrapUnique(new Controller(MessageLoopForIO::WATCH_READ, fd, callback));
+ return WrapUnique(new Controller(MessagePumpForIO::WATCH_READ, fd, callback));
}
std::unique_ptr<FileDescriptorWatcher::Controller>
FileDescriptorWatcher::WatchWritable(int fd, const Closure& callback) {
return WrapUnique(
- new Controller(MessageLoopForIO::WATCH_WRITE, fd, callback));
+ new Controller(MessagePumpForIO::WATCH_WRITE, fd, callback));
}
} // namespace base
diff --git a/base/files/file_descriptor_watcher_posix.h b/base/files/file_descriptor_watcher_posix.h
index 6cc011bb3e..aa4457904b 100644
--- a/base/files/file_descriptor_watcher_posix.h
+++ b/base/files/file_descriptor_watcher_posix.h
@@ -13,6 +13,7 @@
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_pump_for_io.h"
#include "base/sequence_checker.h"
namespace base {
@@ -21,6 +22,15 @@ class SingleThreadTaskRunner;
// The FileDescriptorWatcher API allows callbacks to be invoked when file
// descriptors are readable or writable without blocking.
+//
+// To enable this API in unit tests, use a ScopedTaskEnvironment with
+// MainThreadType::IO.
+//
+// Note: Prefer FileDescriptorWatcher to MessageLoopForIO::WatchFileDescriptor()
+// for non-critical IO. FileDescriptorWatcher works on threads/sequences without
+// MessagePumps but involves going through the task queue after being notified
+// by the OS (a desirablable property for non-critical IO that shouldn't preempt
+// the main queue).
class BASE_EXPORT FileDescriptorWatcher {
public:
// Instantiated and returned by WatchReadable() or WatchWritable(). The
@@ -37,7 +47,7 @@ class BASE_EXPORT FileDescriptorWatcher {
// Registers |callback| to be invoked when |fd| is readable or writable
// without blocking (depending on |mode|).
- Controller(MessageLoopForIO::Mode mode, int fd, const Closure& callback);
+ Controller(MessagePumpForIO::Mode mode, int fd, const Closure& callback);
// Starts watching the file descriptor.
void StartWatching();
@@ -79,12 +89,13 @@ class BASE_EXPORT FileDescriptorWatcher {
FileDescriptorWatcher(MessageLoopForIO* message_loop_for_io);
~FileDescriptorWatcher();
- // Registers |callback| to be invoked on the current sequence when |fd| is
+ // Registers |callback| to be posted on the current sequence when |fd| is
// readable or writable without blocking. |callback| is unregistered when the
// returned Controller is deleted (deletion must happen on the current
// sequence). To call these methods, a FileDescriptorWatcher must have been
// instantiated on the current thread and SequencedTaskRunnerHandle::IsSet()
- // must return true.
+ // must return true (these conditions are met at least on all TaskScheduler
+ // threads as well as on threads backed by a MessageLoopForIO).
static std::unique_ptr<Controller> WatchReadable(int fd,
const Closure& callback);
static std::unique_ptr<Controller> WatchWritable(int fd,
diff --git a/base/files/file_descriptor_watcher_posix_unittest.cc b/base/files/file_descriptor_watcher_posix_unittest.cc
index 7ff40c5fee..4ed044b975 100644
--- a/base/files/file_descriptor_watcher_posix_unittest.cc
+++ b/base/files/file_descriptor_watcher_posix_unittest.cc
@@ -69,7 +69,7 @@ class FileDescriptorWatcherTest
}
ASSERT_TRUE(message_loop_for_io->IsType(MessageLoop::TYPE_IO));
- file_descriptor_watcher_ = MakeUnique<FileDescriptorWatcher>(
+ file_descriptor_watcher_ = std::make_unique<FileDescriptorWatcher>(
static_cast<MessageLoopForIO*>(message_loop_for_io));
}
@@ -81,6 +81,9 @@ class FileDescriptorWatcherTest
base::RunLoop().RunUntilIdle();
}
+ // Ensure that OtherThread is done processing before closing fds.
+ other_thread_.Stop();
+
EXPECT_EQ(0, IGNORE_EINTR(close(pipe_fds_[0])));
EXPECT_EQ(0, IGNORE_EINTR(close(pipe_fds_[1])));
}
@@ -116,7 +119,7 @@ class FileDescriptorWatcherTest
std::unique_ptr<FileDescriptorWatcher::Controller> WatchWritable() {
std::unique_ptr<FileDescriptorWatcher::Controller> controller =
FileDescriptorWatcher::WatchWritable(
- read_file_descriptor(),
+ write_file_descriptor(),
Bind(&Mock::WritableCallback, Unretained(&mock_)));
EXPECT_TRUE(controller);
return controller;
@@ -167,14 +170,11 @@ class FileDescriptorWatcherTest
TEST_P(FileDescriptorWatcherTest, WatchWritable) {
auto controller = WatchWritable();
-// On Mac and iOS, the write end of a newly created pipe is writable without
-// blocking.
-#if defined(OS_MACOSX)
+ // The write end of a newly created pipe is immediately writable.
RunLoop run_loop;
EXPECT_CALL(mock_, WritableCallback())
.WillOnce(testing::Invoke(&run_loop, &RunLoop::Quit));
run_loop.Run();
-#endif // defined(OS_MACOSX)
}
TEST_P(FileDescriptorWatcherTest, WatchReadableOneByte) {
diff --git a/base/files/file_enumerator.cc b/base/files/file_enumerator.cc
index 974998036d..9dfb2ba04b 100644
--- a/base/files/file_enumerator.cc
+++ b/base/files/file_enumerator.cc
@@ -8,8 +8,7 @@
namespace base {
-FileEnumerator::FileInfo::~FileInfo() {
-}
+FileEnumerator::FileInfo::~FileInfo() = default;
bool FileEnumerator::ShouldSkip(const FilePath& path) {
FilePath::StringType basename = path.BaseName().value();
@@ -18,4 +17,9 @@ bool FileEnumerator::ShouldSkip(const FilePath& path) {
!(INCLUDE_DOT_DOT & file_type_));
}
+bool FileEnumerator::IsTypeMatched(bool is_dir) const {
+ return (file_type_ &
+ (is_dir ? FileEnumerator::DIRECTORIES : FileEnumerator::FILES)) != 0;
+}
+
} // namespace base
diff --git a/base/files/file_enumerator.h b/base/files/file_enumerator.h
index 7cac8dd9d4..0fa99a6861 100644
--- a/base/files/file_enumerator.h
+++ b/base/files/file_enumerator.h
@@ -8,10 +8,10 @@
#include <stddef.h>
#include <stdint.h>
-#include <stack>
#include <vector>
#include "base/base_export.h"
+#include "base/containers/stack.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/time/time.h"
@@ -19,7 +19,7 @@
#if defined(OS_WIN)
#include <windows.h>
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
#include <sys/stat.h>
#include <unistd.h>
#endif
@@ -60,7 +60,7 @@ class BASE_EXPORT FileEnumerator {
// of the WIN32_FIND_DATA will be empty. Since we don't use short file
// names, we tell Windows to omit it which speeds up the query slightly.
const WIN32_FIND_DATA& find_data() const { return find_data_; }
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
const struct stat& stat() const { return stat_; }
#endif
@@ -69,21 +69,32 @@ class BASE_EXPORT FileEnumerator {
#if defined(OS_WIN)
WIN32_FIND_DATA find_data_;
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
struct stat stat_;
FilePath filename_;
#endif
};
enum FileType {
- FILES = 1 << 0,
- DIRECTORIES = 1 << 1,
- INCLUDE_DOT_DOT = 1 << 2,
-#if defined(OS_POSIX)
- SHOW_SYM_LINKS = 1 << 4,
+ FILES = 1 << 0,
+ DIRECTORIES = 1 << 1,
+ INCLUDE_DOT_DOT = 1 << 2,
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ SHOW_SYM_LINKS = 1 << 4,
#endif
};
+ // Search policy for intermediate folders.
+ enum class FolderSearchPolicy {
+ // Recursive search will pass through folders whose names match the
+ // pattern. Inside each one, all files will be returned. Folders with names
+ // that do not match the pattern will be ignored within their interior.
+ MATCH_ONLY,
+ // Recursive search will pass through every folder and perform pattern
+ // matching inside each one.
+ ALL,
+ };
+
// |root_path| is the starting directory to search for. It may or may not end
// in a slash.
//
@@ -101,9 +112,6 @@ class BASE_EXPORT FileEnumerator {
// since the underlying code uses OS-specific matching routines. In general,
// Windows matching is less featureful than others, so test there first.
// If unspecified, this will match all files.
- // NOTE: the pattern only matches the contents of root_path, not files in
- // recursive subdirectories.
- // TODO(erikkay): Fix the pattern matching to work at all levels.
FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type);
@@ -111,6 +119,11 @@ class BASE_EXPORT FileEnumerator {
bool recursive,
int file_type,
const FilePath::StringType& pattern);
+ FileEnumerator(const FilePath& root_path,
+ bool recursive,
+ int file_type,
+ const FilePath::StringType& pattern,
+ FolderSearchPolicy folder_search_policy);
~FileEnumerator();
// Returns the next file or an empty string if there are no more results.
@@ -127,32 +140,31 @@ class BASE_EXPORT FileEnumerator {
// Returns true if the given path should be skipped in enumeration.
bool ShouldSkip(const FilePath& path);
+ bool IsTypeMatched(bool is_dir) const;
+
+ bool IsPatternMatched(const FilePath& src) const;
+
#if defined(OS_WIN)
// True when find_data_ is valid.
- bool has_find_data_;
+ bool has_find_data_ = false;
WIN32_FIND_DATA find_data_;
- HANDLE find_handle_;
-#elif defined(OS_POSIX)
-
- // Read the filenames in source into the vector of DirectoryEntryInfo's
- static bool ReadDirectory(std::vector<FileInfo>* entries,
- const FilePath& source, bool show_links);
-
+ HANDLE find_handle_ = INVALID_HANDLE_VALUE;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
// The files in the current directory
std::vector<FileInfo> directory_entries_;
// The next entry to use from the directory_entries_ vector
size_t current_directory_entry_;
#endif
-
FilePath root_path_;
- bool recursive_;
- int file_type_;
- FilePath::StringType pattern_; // Empty when we want to find everything.
+ const bool recursive_;
+ const int file_type_;
+ FilePath::StringType pattern_;
+ const FolderSearchPolicy folder_search_policy_;
// A stack that keeps track of which subdirectories we still need to
// enumerate in the breadth-first search.
- std::stack<FilePath> pending_paths_;
+ base::stack<FilePath> pending_paths_;
DISALLOW_COPY_AND_ASSIGN(FileEnumerator);
};
diff --git a/base/files/file_enumerator_posix.cc b/base/files/file_enumerator_posix.cc
index fb4010aadc..4b429c6448 100644
--- a/base/files/file_enumerator_posix.cc
+++ b/base/files/file_enumerator_posix.cc
@@ -8,12 +8,29 @@
#include <errno.h>
#include <fnmatch.h>
#include <stdint.h>
+#include <string.h>
#include "base/logging.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
namespace base {
+namespace {
+
+void GetStat(const FilePath& path, bool show_links, struct stat* st) {
+ DCHECK(st);
+ const int res = show_links ? lstat(path.value().c_str(), st)
+ : stat(path.value().c_str(), st);
+ if (res < 0) {
+ // Print the stat() error message unless it was ENOENT and we're following
+ // symlinks.
+ if (!(errno == ENOENT && !show_links))
+ DPLOG(ERROR) << "Couldn't stat" << path.value();
+ memset(st, 0, sizeof(*st));
+ }
+}
+
+} // namespace
// FileEnumerator::FileInfo ----------------------------------------------------
@@ -42,38 +59,44 @@ base::Time FileEnumerator::FileInfo::GetLastModifiedTime() const {
FileEnumerator::FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type)
- : current_directory_entry_(0),
- root_path_(root_path),
- recursive_(recursive),
- file_type_(file_type) {
- // INCLUDE_DOT_DOT must not be specified if recursive.
- DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_)));
- pending_paths_.push(root_path);
-}
+ : FileEnumerator(root_path,
+ recursive,
+ file_type,
+ FilePath::StringType(),
+ FolderSearchPolicy::MATCH_ONLY) {}
FileEnumerator::FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type,
const FilePath::StringType& pattern)
+ : FileEnumerator(root_path,
+ recursive,
+ file_type,
+ pattern,
+ FolderSearchPolicy::MATCH_ONLY) {}
+
+FileEnumerator::FileEnumerator(const FilePath& root_path,
+ bool recursive,
+ int file_type,
+ const FilePath::StringType& pattern,
+ FolderSearchPolicy folder_search_policy)
: current_directory_entry_(0),
root_path_(root_path),
recursive_(recursive),
file_type_(file_type),
- pattern_(root_path.Append(pattern).value()) {
+ pattern_(pattern),
+ folder_search_policy_(folder_search_policy) {
// INCLUDE_DOT_DOT must not be specified if recursive.
DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_)));
- // The Windows version of this code appends the pattern to the root_path,
- // potentially only matching against items in the top-most directory.
- // Do the same here.
- if (pattern.empty())
- pattern_ = FilePath::StringType();
+
pending_paths_.push(root_path);
}
-FileEnumerator::~FileEnumerator() {
-}
+FileEnumerator::~FileEnumerator() = default;
FilePath FileEnumerator::Next() {
+ AssertBlockingAllowed();
+
++current_directory_entry_;
// While we've exhausted the entries in the current directory, do the next
@@ -85,29 +108,68 @@ FilePath FileEnumerator::Next() {
root_path_ = root_path_.StripTrailingSeparators();
pending_paths_.pop();
- std::vector<FileInfo> entries;
- if (!ReadDirectory(&entries, root_path_, file_type_ & SHOW_SYM_LINKS))
+ DIR* dir = opendir(root_path_.value().c_str());
+ if (!dir)
continue;
directory_entries_.clear();
+
+#if defined(OS_FUCHSIA)
+ // Fuchsia does not support .. on the file system server side, see
+ // https://fuchsia.googlesource.com/docs/+/master/dotdot.md and
+ // https://crbug.com/735540. However, for UI purposes, having the parent
+ // directory show up in directory listings makes sense, so we add it here to
+ // match the expectation on other operating systems. In cases where this
+ // is useful it should be resolvable locally.
+ FileInfo dotdot;
+ dotdot.stat_.st_mode = S_IFDIR;
+ dotdot.filename_ = FilePath("..");
+ if (!ShouldSkip(dotdot.filename_)) {
+ directory_entries_.push_back(std::move(dotdot));
+ }
+#endif // OS_FUCHSIA
+
current_directory_entry_ = 0;
- for (std::vector<FileInfo>::const_iterator i = entries.begin();
- i != entries.end(); ++i) {
- FilePath full_path = root_path_.Append(i->filename_);
- if (ShouldSkip(full_path))
+ struct dirent* dent;
+ while ((dent = readdir(dir))) {
+ FileInfo info;
+ info.filename_ = FilePath(dent->d_name);
+
+ if (ShouldSkip(info.filename_))
continue;
- if (pattern_.size() &&
- fnmatch(pattern_.c_str(), full_path.value().c_str(), FNM_NOESCAPE))
+ const bool is_pattern_matched = IsPatternMatched(info.filename_);
+
+ // MATCH_ONLY policy enumerates files and directories which matching
+ // pattern only. So we can early skip further checks.
+ if (folder_search_policy_ == FolderSearchPolicy::MATCH_ONLY &&
+ !is_pattern_matched)
continue;
- if (recursive_ && S_ISDIR(i->stat_.st_mode))
+ // Do not call OS stat/lstat if there is no sense to do it. If pattern is
+ // not matched (file will not appear in results) and search is not
+ // recursive (possible directory will not be added to pending paths) -
+ // there is no sense to obtain item below.
+ if (!recursive_ && !is_pattern_matched)
+ continue;
+
+ const FilePath full_path = root_path_.Append(info.filename_);
+ GetStat(full_path, file_type_ & SHOW_SYM_LINKS, &info.stat_);
+
+ const bool is_dir = info.IsDirectory();
+
+ if (recursive_ && is_dir)
pending_paths_.push(full_path);
- if ((S_ISDIR(i->stat_.st_mode) && (file_type_ & DIRECTORIES)) ||
- (!S_ISDIR(i->stat_.st_mode) && (file_type_ & FILES)))
- directory_entries_.push_back(*i);
+ if (is_pattern_matched && IsTypeMatched(is_dir))
+ directory_entries_.push_back(std::move(info));
}
+ closedir(dir);
+
+ // MATCH_ONLY policy enumerates files in matched subfolders by "*" pattern.
+ // ALL policy enumerates files in all subfolders by origin pattern.
+ if (folder_search_policy_ == FolderSearchPolicy::MATCH_ONLY)
+ pattern_.clear();
}
return root_path_.Append(
@@ -118,45 +180,9 @@ FileEnumerator::FileInfo FileEnumerator::GetInfo() const {
return directory_entries_[current_directory_entry_];
}
-bool FileEnumerator::ReadDirectory(std::vector<FileInfo>* entries,
- const FilePath& source, bool show_links) {
- base::ThreadRestrictions::AssertIOAllowed();
- DIR* dir = opendir(source.value().c_str());
- if (!dir)
- return false;
-
-#if !defined(OS_LINUX) && !defined(OS_MACOSX) && !defined(OS_BSD) && \
- !defined(OS_SOLARIS) && !defined(OS_ANDROID)
- #error Port warning: depending on the definition of struct dirent, \
- additional space for pathname may be needed
-#endif
-
- struct dirent dent_buf;
- struct dirent* dent;
- while (readdir_r(dir, &dent_buf, &dent) == 0 && dent) {
- FileInfo info;
- info.filename_ = FilePath(dent->d_name);
-
- FilePath full_name = source.Append(dent->d_name);
- int ret;
- if (show_links)
- ret = lstat(full_name.value().c_str(), &info.stat_);
- else
- ret = stat(full_name.value().c_str(), &info.stat_);
- if (ret < 0) {
- // Print the stat() error message unless it was ENOENT and we're
- // following symlinks.
- if (!(errno == ENOENT && !show_links)) {
- DPLOG(ERROR) << "Couldn't stat "
- << source.Append(dent->d_name).value();
- }
- memset(&info.stat_, 0, sizeof(info.stat_));
- }
- entries->push_back(info);
- }
-
- closedir(dir);
- return true;
+bool FileEnumerator::IsPatternMatched(const FilePath& path) const {
+ return pattern_.empty() ||
+ !fnmatch(pattern_.c_str(), path.value().c_str(), FNM_NOESCAPE);
}
} // namespace base
diff --git a/base/files/file_enumerator_unittest.cc b/base/files/file_enumerator_unittest.cc
new file mode 100644
index 0000000000..11df075ecd
--- /dev/null
+++ b/base/files/file_enumerator_unittest.cc
@@ -0,0 +1,312 @@
+// 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/files/file_enumerator.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/containers/circular_deque.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::ElementsAre;
+using testing::IsEmpty;
+using testing::UnorderedElementsAre;
+
+namespace base {
+namespace {
+
+const FilePath::StringType kEmptyPattern;
+
+const std::vector<FileEnumerator::FolderSearchPolicy> kFolderSearchPolicies{
+ FileEnumerator::FolderSearchPolicy::MATCH_ONLY,
+ FileEnumerator::FolderSearchPolicy::ALL};
+
+circular_deque<FilePath> RunEnumerator(
+ const FilePath& root_path,
+ bool recursive,
+ int file_type,
+ const FilePath::StringType& pattern,
+ FileEnumerator::FolderSearchPolicy folder_search_policy) {
+ circular_deque<FilePath> rv;
+ FileEnumerator enumerator(root_path, recursive, file_type, pattern,
+ folder_search_policy);
+ for (auto file = enumerator.Next(); !file.empty(); file = enumerator.Next())
+ rv.emplace_back(std::move(file));
+ return rv;
+}
+
+bool CreateDummyFile(const FilePath& path) {
+ return WriteFile(path, "42", sizeof("42")) == sizeof("42");
+}
+
+} // namespace
+
+TEST(FileEnumerator, NotExistingPath) {
+ const FilePath path = FilePath::FromUTF8Unsafe("some_not_existing_path");
+ ASSERT_FALSE(PathExists(path));
+
+ for (auto policy : kFolderSearchPolicies) {
+ const auto files = RunEnumerator(
+ path, true, FileEnumerator::FILES & FileEnumerator::DIRECTORIES,
+ FILE_PATH_LITERAL(""), policy);
+ EXPECT_THAT(files, IsEmpty());
+ }
+}
+
+TEST(FileEnumerator, EmptyFolder) {
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ for (auto policy : kFolderSearchPolicies) {
+ const auto files =
+ RunEnumerator(temp_dir.GetPath(), true,
+ FileEnumerator::FILES & FileEnumerator::DIRECTORIES,
+ kEmptyPattern, policy);
+ EXPECT_THAT(files, IsEmpty());
+ }
+}
+
+TEST(FileEnumerator, SingleFileInFolderForFileSearch) {
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ const FilePath& path = temp_dir.GetPath();
+ const FilePath file = path.AppendASCII("test.txt");
+ ASSERT_TRUE(CreateDummyFile(file));
+
+ for (auto policy : kFolderSearchPolicies) {
+ const auto files = RunEnumerator(
+ temp_dir.GetPath(), true, FileEnumerator::FILES, kEmptyPattern, policy);
+ EXPECT_THAT(files, ElementsAre(file));
+ }
+}
+
+TEST(FileEnumerator, SingleFileInFolderForDirSearch) {
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ const FilePath& path = temp_dir.GetPath();
+ ASSERT_TRUE(CreateDummyFile(path.AppendASCII("test.txt")));
+
+ for (auto policy : kFolderSearchPolicies) {
+ const auto files = RunEnumerator(path, true, FileEnumerator::DIRECTORIES,
+ kEmptyPattern, policy);
+ EXPECT_THAT(files, IsEmpty());
+ }
+}
+
+TEST(FileEnumerator, SingleFileInFolderWithFiltering) {
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ const FilePath& path = temp_dir.GetPath();
+ const FilePath file = path.AppendASCII("test.txt");
+ ASSERT_TRUE(CreateDummyFile(file));
+
+ for (auto policy : kFolderSearchPolicies) {
+ auto files = RunEnumerator(path, true, FileEnumerator::FILES,
+ FILE_PATH_LITERAL("*.txt"), policy);
+ EXPECT_THAT(files, ElementsAre(file));
+
+ files = RunEnumerator(path, true, FileEnumerator::FILES,
+ FILE_PATH_LITERAL("*.pdf"), policy);
+ EXPECT_THAT(files, IsEmpty());
+ }
+}
+
+TEST(FileEnumerator, TwoFilesInFolder) {
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ const FilePath& path = temp_dir.GetPath();
+ const FilePath foo_txt = path.AppendASCII("foo.txt");
+ const FilePath bar_txt = path.AppendASCII("bar.txt");
+ ASSERT_TRUE(CreateDummyFile(foo_txt));
+ ASSERT_TRUE(CreateDummyFile(bar_txt));
+
+ for (auto policy : kFolderSearchPolicies) {
+ auto files = RunEnumerator(path, true, FileEnumerator::FILES,
+ FILE_PATH_LITERAL("*.txt"), policy);
+ EXPECT_THAT(files, UnorderedElementsAre(foo_txt, bar_txt));
+
+ files = RunEnumerator(path, true, FileEnumerator::FILES,
+ FILE_PATH_LITERAL("foo*"), policy);
+ EXPECT_THAT(files, ElementsAre(foo_txt));
+
+ files = RunEnumerator(path, true, FileEnumerator::FILES,
+ FILE_PATH_LITERAL("*.pdf"), policy);
+ EXPECT_THAT(files, IsEmpty());
+
+ files =
+ RunEnumerator(path, true, FileEnumerator::FILES, kEmptyPattern, policy);
+ EXPECT_THAT(files, UnorderedElementsAre(foo_txt, bar_txt));
+ }
+}
+
+TEST(FileEnumerator, SingleFolderInFolderForFileSearch) {
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ const FilePath& path = temp_dir.GetPath();
+
+ ScopedTempDir temp_subdir;
+ ASSERT_TRUE(temp_subdir.CreateUniqueTempDirUnderPath(path));
+
+ for (auto policy : kFolderSearchPolicies) {
+ const auto files =
+ RunEnumerator(path, true, FileEnumerator::FILES, kEmptyPattern, policy);
+ EXPECT_THAT(files, IsEmpty());
+ }
+}
+
+TEST(FileEnumerator, SingleFolderInFolderForDirSearch) {
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ const FilePath& path = temp_dir.GetPath();
+
+ ScopedTempDir temp_subdir;
+ ASSERT_TRUE(temp_subdir.CreateUniqueTempDirUnderPath(path));
+
+ for (auto policy : kFolderSearchPolicies) {
+ const auto files = RunEnumerator(path, true, FileEnumerator::DIRECTORIES,
+ kEmptyPattern, policy);
+ EXPECT_THAT(files, ElementsAre(temp_subdir.GetPath()));
+ }
+}
+
+TEST(FileEnumerator, TwoFoldersInFolder) {
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ const FilePath& path = temp_dir.GetPath();
+
+ const FilePath subdir_foo = path.AppendASCII("foo");
+ const FilePath subdir_bar = path.AppendASCII("bar");
+ ASSERT_TRUE(CreateDirectory(subdir_foo));
+ ASSERT_TRUE(CreateDirectory(subdir_bar));
+
+ for (auto policy : kFolderSearchPolicies) {
+ auto files = RunEnumerator(path, true, FileEnumerator::DIRECTORIES,
+ kEmptyPattern, policy);
+ EXPECT_THAT(files, UnorderedElementsAre(subdir_foo, subdir_bar));
+
+ files = RunEnumerator(path, true, FileEnumerator::DIRECTORIES,
+ FILE_PATH_LITERAL("foo"), policy);
+ EXPECT_THAT(files, ElementsAre(subdir_foo));
+ }
+}
+
+TEST(FileEnumerator, FolderAndFileInFolder) {
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ const FilePath& path = temp_dir.GetPath();
+
+ ScopedTempDir temp_subdir;
+ ASSERT_TRUE(temp_subdir.CreateUniqueTempDirUnderPath(path));
+ const FilePath file = path.AppendASCII("test.txt");
+ ASSERT_TRUE(CreateDummyFile(file));
+
+ for (auto policy : kFolderSearchPolicies) {
+ auto files =
+ RunEnumerator(path, true, FileEnumerator::FILES, kEmptyPattern, policy);
+ EXPECT_THAT(files, ElementsAre(file));
+
+ files = RunEnumerator(path, true, FileEnumerator::DIRECTORIES,
+ kEmptyPattern, policy);
+ EXPECT_THAT(files, ElementsAre(temp_subdir.GetPath()));
+
+ files = RunEnumerator(path, true,
+ FileEnumerator::FILES | FileEnumerator::DIRECTORIES,
+ kEmptyPattern, policy);
+ EXPECT_THAT(files, UnorderedElementsAre(file, temp_subdir.GetPath()));
+ }
+}
+
+TEST(FileEnumerator, FilesInParentFolderAlwaysFirst) {
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ const FilePath& path = temp_dir.GetPath();
+
+ ScopedTempDir temp_subdir;
+ ASSERT_TRUE(temp_subdir.CreateUniqueTempDirUnderPath(path));
+ const FilePath foo_txt = path.AppendASCII("foo.txt");
+ const FilePath bar_txt = temp_subdir.GetPath().AppendASCII("bar.txt");
+ ASSERT_TRUE(CreateDummyFile(foo_txt));
+ ASSERT_TRUE(CreateDummyFile(bar_txt));
+
+ for (auto policy : kFolderSearchPolicies) {
+ const auto files =
+ RunEnumerator(path, true, FileEnumerator::FILES, kEmptyPattern, policy);
+ EXPECT_THAT(files, ElementsAre(foo_txt, bar_txt));
+ }
+}
+
+TEST(FileEnumerator, FileInSubfolder) {
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ const FilePath subdir = temp_dir.GetPath().AppendASCII("subdir");
+ ASSERT_TRUE(CreateDirectory(subdir));
+
+ const FilePath file = subdir.AppendASCII("test.txt");
+ ASSERT_TRUE(CreateDummyFile(file));
+
+ for (auto policy : kFolderSearchPolicies) {
+ auto files = RunEnumerator(temp_dir.GetPath(), true, FileEnumerator::FILES,
+ kEmptyPattern, policy);
+ EXPECT_THAT(files, ElementsAre(file));
+
+ files = RunEnumerator(temp_dir.GetPath(), false, FileEnumerator::FILES,
+ kEmptyPattern, policy);
+ EXPECT_THAT(files, IsEmpty());
+ }
+}
+
+TEST(FileEnumerator, FilesInSubfoldersWithFiltering) {
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ const FilePath test_txt = temp_dir.GetPath().AppendASCII("test.txt");
+ const FilePath subdir_foo = temp_dir.GetPath().AppendASCII("foo_subdir");
+ const FilePath subdir_bar = temp_dir.GetPath().AppendASCII("bar_subdir");
+ const FilePath foo_test = subdir_foo.AppendASCII("test.txt");
+ const FilePath foo_foo = subdir_foo.AppendASCII("foo.txt");
+ const FilePath foo_bar = subdir_foo.AppendASCII("bar.txt");
+ const FilePath bar_test = subdir_bar.AppendASCII("test.txt");
+ const FilePath bar_foo = subdir_bar.AppendASCII("foo.txt");
+ const FilePath bar_bar = subdir_bar.AppendASCII("bar.txt");
+ ASSERT_TRUE(CreateDummyFile(test_txt));
+ ASSERT_TRUE(CreateDirectory(subdir_foo));
+ ASSERT_TRUE(CreateDirectory(subdir_bar));
+ ASSERT_TRUE(CreateDummyFile(foo_test));
+ ASSERT_TRUE(CreateDummyFile(foo_foo));
+ ASSERT_TRUE(CreateDummyFile(foo_bar));
+ ASSERT_TRUE(CreateDummyFile(bar_test));
+ ASSERT_TRUE(CreateDummyFile(bar_foo));
+ ASSERT_TRUE(CreateDummyFile(bar_bar));
+
+ auto files =
+ RunEnumerator(temp_dir.GetPath(), true,
+ FileEnumerator::FILES | FileEnumerator::DIRECTORIES,
+ FILE_PATH_LITERAL("foo*"),
+ FileEnumerator::FolderSearchPolicy::MATCH_ONLY);
+ EXPECT_THAT(files,
+ UnorderedElementsAre(subdir_foo, foo_test, foo_foo, foo_bar));
+
+ files = RunEnumerator(temp_dir.GetPath(), true,
+ FileEnumerator::FILES | FileEnumerator::DIRECTORIES,
+ FILE_PATH_LITERAL("foo*"),
+ FileEnumerator::FolderSearchPolicy::ALL);
+ EXPECT_THAT(files, UnorderedElementsAre(subdir_foo, foo_foo, bar_foo));
+}
+
+} // namespace base
diff --git a/base/files/file_path.cc b/base/files/file_path.cc
index 5b1eb29dd6..14f925166f 100644
--- a/base/files/file_path.cc
+++ b/base/files/file_path.cc
@@ -169,11 +169,9 @@ bool IsEmptyOrSpecialCase(const StringType& path) {
} // namespace
-FilePath::FilePath() {
-}
+FilePath::FilePath() = default;
-FilePath::FilePath(const FilePath& that) : path_(that.path_) {
-}
+FilePath::FilePath(const FilePath& that) = default;
FilePath::FilePath(FilePath&& that) noexcept = default;
FilePath::FilePath(StringPieceType path) {
@@ -183,13 +181,9 @@ FilePath::FilePath(StringPieceType path) {
path_.erase(nul_pos, StringType::npos);
}
-FilePath::~FilePath() {
-}
+FilePath::~FilePath() = default;
-FilePath& FilePath::operator=(const FilePath& that) {
- path_ = that.path_;
- return *this;
-}
+FilePath& FilePath::operator=(const FilePath& that) = default;
FilePath& FilePath::operator=(FilePath&& that) = default;
@@ -209,6 +203,10 @@ bool FilePath::operator!=(const FilePath& that) const {
#endif // defined(FILE_PATH_USES_DRIVE_LETTERS)
}
+std::ostream& operator<<(std::ostream& out, const FilePath& file_path) {
+ return out << file_path.value();
+}
+
// static
bool FilePath::IsSeparator(CharType character) {
for (size_t i = 0; i < kSeparatorsLength - 1; ++i) {
@@ -256,7 +254,7 @@ void FilePath::GetComponents(std::vector<StringType>* components) const {
}
bool FilePath::IsParent(const FilePath& child) const {
- return AppendRelativePath(child, NULL);
+ return AppendRelativePath(child, nullptr);
}
bool FilePath::AppendRelativePath(const FilePath& child,
@@ -295,7 +293,7 @@ bool FilePath::AppendRelativePath(const FilePath& child,
++child_comp;
}
- if (path != NULL) {
+ if (path != nullptr) {
for (; child_comp != child_components.end(); ++child_comp) {
*path = path->Append(*child_comp);
}
@@ -425,7 +423,7 @@ FilePath FilePath::InsertBeforeExtensionASCII(StringPiece suffix)
DCHECK(IsStringASCII(suffix));
#if defined(OS_WIN)
return InsertBeforeExtension(ASCIIToUTF16(suffix));
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
return InsertBeforeExtension(suffix);
#endif
}
@@ -488,7 +486,7 @@ FilePath FilePath::Append(StringPieceType component) const {
DCHECK(!IsPathAbsolute(appended));
- if (path_.compare(kCurrentDirectory) == 0) {
+ if (path_.compare(kCurrentDirectory) == 0 && !appended.empty()) {
// Append normally doesn't do any normalization, but as a special case,
// when appending to kCurrentDirectory, just return a new path for the
// component argument. Appending component to kCurrentDirectory would
@@ -528,7 +526,7 @@ FilePath FilePath::AppendASCII(StringPiece component) const {
DCHECK(base::IsStringASCII(component));
#if defined(OS_WIN)
return Append(ASCIIToUTF16(component));
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
return Append(component);
#endif
}
@@ -588,7 +586,38 @@ bool FilePath::ReferencesParent() const {
return false;
}
-#if defined(OS_POSIX)
+#if defined(OS_WIN)
+
+string16 FilePath::LossyDisplayName() const {
+ return path_;
+}
+
+std::string FilePath::MaybeAsASCII() const {
+ if (base::IsStringASCII(path_))
+ return UTF16ToASCII(path_);
+ return std::string();
+}
+
+std::string FilePath::AsUTF8Unsafe() const {
+ return WideToUTF8(value());
+}
+
+string16 FilePath::AsUTF16Unsafe() const {
+ return value();
+}
+
+// static
+FilePath FilePath::FromUTF8Unsafe(StringPiece utf8) {
+ return FilePath(UTF8ToWide(utf8));
+}
+
+// static
+FilePath FilePath::FromUTF16Unsafe(StringPiece16 utf16) {
+ return FilePath(utf16);
+}
+
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+
// See file_path.h for a discussion of the encoding of paths on POSIX
// platforms. These encoding conversion functions are not quite correct.
@@ -636,49 +665,15 @@ FilePath FilePath::FromUTF16Unsafe(StringPiece16 utf16) {
#endif
}
-#elif defined(OS_WIN)
-string16 FilePath::LossyDisplayName() const {
- return path_;
-}
-
-std::string FilePath::MaybeAsASCII() const {
- if (base::IsStringASCII(path_))
- return UTF16ToASCII(path_);
- return std::string();
-}
-
-std::string FilePath::AsUTF8Unsafe() const {
- return WideToUTF8(value());
-}
-
-string16 FilePath::AsUTF16Unsafe() const {
- return value();
-}
-
-// static
-FilePath FilePath::FromUTF8Unsafe(StringPiece utf8) {
- return FilePath(UTF8ToWide(utf8));
-}
-
-// static
-FilePath FilePath::FromUTF16Unsafe(StringPiece16 utf16) {
- return FilePath(utf16);
-}
-#endif
-
-void FilePath::GetSizeForPickle(PickleSizer* sizer) const {
-#if defined(OS_WIN)
- sizer->AddString16(path_);
-#else
- sizer->AddString(path_);
-#endif
-}
+#endif // defined(OS_WIN)
void FilePath::WriteToPickle(Pickle* pickle) const {
#if defined(OS_WIN)
pickle->WriteString16(path_);
-#else
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
pickle->WriteString(path_);
+#else
+#error Unsupported platform
#endif
}
@@ -686,9 +681,11 @@ bool FilePath::ReadFromPickle(PickleIterator* iter) {
#if defined(OS_WIN)
if (!iter->ReadString16(&path_))
return false;
-#else
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
if (!iter->ReadString(&path_))
return false;
+#else
+#error Unsupported platform
#endif
if (path_.find(kStringTerminator) != StringType::npos)
@@ -1278,7 +1275,7 @@ int FilePath::CompareIgnoreCase(StringPieceType string1,
return HFSFastUnicodeCompare(hfs1, hfs2);
}
-#else // << WIN. MACOSX | other (POSIX) >>
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
// Generic Posix system comparisons.
int FilePath::CompareIgnoreCase(StringPieceType string1,
diff --git a/base/files/file_path.h b/base/files/file_path.h
index 0be0ad0b10..2dc15f9d04 100644
--- a/base/files/file_path.h
+++ b/base/files/file_path.h
@@ -110,7 +110,6 @@
#include "base/base_export.h"
#include "base/compiler_specific.h"
-#include "base/containers/hash_tables.h"
#include "base/macros.h"
#include "base/strings/string16.h"
#include "base/strings/string_piece.h"
@@ -128,31 +127,30 @@
// To print path names portably use PRIsFP (based on PRIuS and friends from
// C99 and format_macros.h) like this:
// base::StringPrintf("Path is %" PRIsFP ".\n", path.value().c_str());
-#if defined(OS_POSIX)
-#define PRIsFP "s"
-#elif defined(OS_WIN)
+#if defined(OS_WIN)
#define PRIsFP "ls"
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#define PRIsFP "s"
#endif // OS_WIN
namespace base {
class Pickle;
class PickleIterator;
-class PickleSizer;
// An abstraction to isolate users from the differences between native
// pathnames on different platforms.
class BASE_EXPORT FilePath {
public:
-#if defined(OS_POSIX)
+#if defined(OS_WIN)
+ // On Windows, for Unicode-aware applications, native pathnames are wchar_t
+ // arrays encoded in UTF-16.
+ typedef std::wstring StringType;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
// On most platforms, native pathnames are char arrays, and the encoding
// may or may not be specified. On Mac OS X, native pathnames are encoded
// in UTF-8.
typedef std::string StringType;
-#elif defined(OS_WIN)
- // On Windows, for Unicode-aware applications, native pathnames are wchar_t
- // arrays encoded in UTF-16.
- typedef std::wstring StringType;
#endif // OS_WIN
typedef BasicStringPiece<StringType> StringPieceType;
@@ -240,7 +238,8 @@ class BASE_EXPORT FilePath {
// named by this object, stripping away the file component. If this object
// only contains one component, returns a FilePath identifying
// kCurrentDirectory. If this object already refers to the root directory,
- // returns a FilePath identifying the root directory.
+ // returns a FilePath identifying the root directory. Please note that this
+ // doesn't resolve directory navigation, e.g. the result for "../a" is "..".
FilePath DirName() const WARN_UNUSED_RESULT;
// Returns a FilePath corresponding to the last path component of this
@@ -385,7 +384,6 @@ class BASE_EXPORT FilePath {
// Similar to FromUTF8Unsafe, but accepts UTF-16 instead.
static FilePath FromUTF16Unsafe(StringPiece16 utf16);
- void GetSizeForPickle(PickleSizer* sizer) const;
void WriteToPickle(Pickle* pickle) const;
bool ReadFromPickle(PickleIterator* iter);
@@ -452,35 +450,32 @@ class BASE_EXPORT FilePath {
StringType path_;
};
-// This is required by googletest to print a readable output on test failures.
-// This is declared here for use in gtest-based unit tests but is defined in
-// the test_support_base target. Depend on that to use this in your unit test.
-// This should not be used in production code - call ToString() instead.
-void PrintTo(const FilePath& path, std::ostream* out);
+BASE_EXPORT std::ostream& operator<<(std::ostream& out,
+ const FilePath& file_path);
} // namespace base
// Macros for string literal initialization of FilePath::CharType[], and for
// using a FilePath::CharType[] in a printf-style format string.
-#if defined(OS_POSIX)
-#define FILE_PATH_LITERAL(x) x
-#define PRFilePath "s"
-#elif defined(OS_WIN)
+#if defined(OS_WIN)
#define FILE_PATH_LITERAL(x) L ## x
#define PRFilePath "ls"
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#define FILE_PATH_LITERAL(x) x
+#define PRFilePath "s"
#endif // OS_WIN
-// Provide a hash function so that hash_sets and maps can contain FilePath
-// objects.
-namespace BASE_HASH_NAMESPACE {
+namespace std {
-template<>
+template <>
struct hash<base::FilePath> {
- size_t operator()(const base::FilePath& f) const {
+ typedef base::FilePath argument_type;
+ typedef std::size_t result_type;
+ result_type operator()(argument_type const& f) const {
return hash<base::FilePath::StringType>()(f.value());
}
};
-} // namespace BASE_HASH_NAMESPACE
+} // namespace std
#endif // BASE_FILES_FILE_PATH_H_
diff --git a/base/files/file_path_unittest.cc b/base/files/file_path_unittest.cc
index 9f339527f5..e722c68285 100644
--- a/base/files/file_path_unittest.cc
+++ b/base/files/file_path_unittest.cc
@@ -13,7 +13,7 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
#include "base/test/scoped_locale.h"
#endif
@@ -89,6 +89,7 @@ TEST_F(FilePathTest, DirName) {
{ FPL("{:"), FPL(".") },
{ FPL("\xB3:"), FPL(".") },
{ FPL("\xC5:"), FPL(".") },
+ { FPL("/aa/../bb/cc"), FPL("/aa/../bb")},
#if defined(OS_WIN)
{ FPL("\x0143:"), FPL(".") },
#endif // OS_WIN
@@ -128,6 +129,7 @@ TEST_F(FilePathTest, DirName) {
{ FPL("\\\\aa\\bb"), FPL("\\\\aa") },
{ FPL("\\\\aa\\"), FPL("\\\\") },
{ FPL("\\\\aa"), FPL("\\\\") },
+ { FPL("aa\\..\\bb\\c"), FPL("aa\\..\\bb")},
#if defined(FILE_PATH_USES_DRIVE_LETTERS)
{ FPL("c:\\"), FPL("c:\\") },
{ FPL("c:\\\\"), FPL("c:\\\\") },
@@ -239,6 +241,7 @@ TEST_F(FilePathTest, Append) {
const struct BinaryTestData cases[] = {
{ { FPL(""), FPL("cc") }, FPL("cc") },
{ { FPL("."), FPL("ff") }, FPL("ff") },
+ { { FPL("."), FPL("") }, FPL(".") },
{ { FPL("/"), FPL("cc") }, FPL("/cc") },
{ { FPL("/aa"), FPL("") }, FPL("/aa") },
{ { FPL("/aa/"), FPL("") }, FPL("/aa") },
@@ -318,7 +321,7 @@ TEST_F(FilePathTest, Append) {
// handle the case when AppendASCII is passed UTF8
#if defined(OS_WIN)
std::string ascii = WideToUTF8(leaf);
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
std::string ascii = leaf;
#endif
observed_str = root.AppendASCII(ascii);
@@ -1286,12 +1289,11 @@ TEST_F(FilePathTest, ContentUriTest) {
}
#endif
-// Test the PrintTo overload for FilePath (used when a test fails to compare two
-// FilePaths).
-TEST_F(FilePathTest, PrintTo) {
+// Test the operator<<(ostream, FilePath).
+TEST_F(FilePathTest, PrintToOstream) {
std::stringstream ss;
FilePath fp(FPL("foo"));
- base::PrintTo(fp, &ss);
+ ss << fp;
EXPECT_EQ("foo", ss.str());
}
diff --git a/base/files/file_path_watcher.cc b/base/files/file_path_watcher.cc
index 245bd8efe2..af40346858 100644
--- a/base/files/file_path_watcher.cc
+++ b/base/files/file_path_watcher.cc
@@ -20,7 +20,7 @@ FilePathWatcher::~FilePathWatcher() {
// static
bool FilePathWatcher::RecursiveWatchAvailable() {
#if (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN) || \
- defined(OS_LINUX) || defined(OS_ANDROID)
+ defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_AIX)
return true;
#else
// FSEvents isn't available on iOS.
diff --git a/base/files/file_path_watcher_linux.cc b/base/files/file_path_watcher_linux.cc
index 1dc833dc88..c58d6865c2 100644
--- a/base/files/file_path_watcher_linux.cc
+++ b/base/files/file_path_watcher_linux.cc
@@ -16,11 +16,11 @@
#include <map>
#include <memory>
#include <set>
+#include <unordered_map>
#include <utility>
#include <vector>
#include "base/bind.h"
-#include "base/containers/hash_tables.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
@@ -34,8 +34,8 @@
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/synchronization/lock.h"
+#include "base/threading/platform_thread.h"
#include "base/threading/sequenced_task_runner_handle.h"
-#include "base/threading/thread.h"
#include "base/trace_event/trace_event.h"
namespace base {
@@ -43,6 +43,21 @@ namespace base {
namespace {
class FilePathWatcherImpl;
+class InotifyReader;
+
+class InotifyReaderThreadDelegate final : public PlatformThread::Delegate {
+ public:
+ InotifyReaderThreadDelegate(int inotify_fd) : inotify_fd_(inotify_fd){};
+
+ ~InotifyReaderThreadDelegate() override = default;
+
+ private:
+ void ThreadMain() override;
+
+ int inotify_fd_;
+
+ DISALLOW_COPY_AND_ASSIGN(InotifyReaderThreadDelegate);
+};
// Singleton to manage all inotify watches.
// TODO(tony): It would be nice if this wasn't a singleton.
@@ -72,18 +87,21 @@ class InotifyReader {
// base::LazyInstace::Leaky object. Having a destructor causes build
// issues with GCC 6 (http://crbug.com/636346).
+ // Returns true on successful thread creation.
+ bool StartThread();
+
// We keep track of which delegates want to be notified on which watches.
- hash_map<Watch, WatcherSet> watchers_;
+ std::unordered_map<Watch, WatcherSet> watchers_;
// Lock to protect watchers_.
Lock lock_;
- // Separate thread on which we run blocking read for inotify events.
- Thread thread_;
-
// File descriptor returned by inotify_init.
const int inotify_fd_;
+ // Thread delegate for the Inotify thread.
+ InotifyReaderThreadDelegate thread_delegate_;
+
// Flag set to true when startup was successful.
bool valid_;
@@ -184,29 +202,37 @@ class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate {
// |target_| and always stores an empty next component name in |subdir|.
WatchVector watches_;
- hash_map<InotifyReader::Watch, FilePath> recursive_paths_by_watch_;
+ std::unordered_map<InotifyReader::Watch, FilePath> recursive_paths_by_watch_;
std::map<FilePath, InotifyReader::Watch> recursive_watches_by_path_;
+ // Read only while INotifyReader::lock_ is held, and used to post asynchronous
+ // notifications to the Watcher on its home task_runner(). Ideally this should
+ // be const, but since it is initialized from |weak_factory_|, which must
+ // appear after it, that is not possible.
+ WeakPtr<FilePathWatcherImpl> weak_ptr_;
+
WeakPtrFactory<FilePathWatcherImpl> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl);
};
-void InotifyReaderCallback(InotifyReader* reader, int inotify_fd) {
- // Make sure the file descriptors are good for use with select().
- CHECK_LE(0, inotify_fd);
- CHECK_GT(FD_SETSIZE, inotify_fd);
+LazyInstance<InotifyReader>::Leaky g_inotify_reader = LAZY_INSTANCE_INITIALIZER;
- trace_event::TraceLog::GetInstance()->SetCurrentThreadBlocksMessageLoop();
+void InotifyReaderThreadDelegate::ThreadMain() {
+ PlatformThread::SetName("inotify_reader");
+
+ // Make sure the file descriptors are good for use with select().
+ CHECK_LE(0, inotify_fd_);
+ CHECK_GT(FD_SETSIZE, inotify_fd_);
while (true) {
fd_set rfds;
FD_ZERO(&rfds);
- FD_SET(inotify_fd, &rfds);
+ FD_SET(inotify_fd_, &rfds);
// Wait until some inotify events are available.
int select_result =
- HANDLE_EINTR(select(inotify_fd + 1, &rfds, NULL, NULL, NULL));
+ HANDLE_EINTR(select(inotify_fd_ + 1, &rfds, nullptr, nullptr, nullptr));
if (select_result < 0) {
DPLOG(WARNING) << "select failed";
return;
@@ -214,8 +240,7 @@ void InotifyReaderCallback(InotifyReader* reader, int inotify_fd) {
// Adjust buffer size to current event queue size.
int buffer_size;
- int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd, FIONREAD,
- &buffer_size));
+ int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd_, FIONREAD, &buffer_size));
if (ioctl_result != 0) {
DPLOG(WARNING) << "ioctl failed";
@@ -224,8 +249,8 @@ void InotifyReaderCallback(InotifyReader* reader, int inotify_fd) {
std::vector<char> buffer(buffer_size);
- ssize_t bytes_read = HANDLE_EINTR(read(inotify_fd, &buffer[0],
- buffer_size));
+ ssize_t bytes_read =
+ HANDLE_EINTR(read(inotify_fd_, &buffer[0], buffer_size));
if (bytes_read < 0) {
DPLOG(WARNING) << "read from inotify fd failed";
@@ -237,28 +262,31 @@ void InotifyReaderCallback(InotifyReader* reader, int inotify_fd) {
inotify_event* event = reinterpret_cast<inotify_event*>(&buffer[i]);
size_t event_size = sizeof(inotify_event) + event->len;
DCHECK(i + event_size <= static_cast<size_t>(bytes_read));
- reader->OnInotifyEvent(event);
+ g_inotify_reader.Get().OnInotifyEvent(event);
i += event_size;
}
}
}
-static LazyInstance<InotifyReader>::Leaky g_inotify_reader =
- LAZY_INSTANCE_INITIALIZER;
-
InotifyReader::InotifyReader()
- : thread_("inotify_reader"),
- inotify_fd_(inotify_init()),
+ : inotify_fd_(inotify_init()),
+ thread_delegate_(inotify_fd_),
valid_(false) {
- if (inotify_fd_ < 0)
+ if (inotify_fd_ < 0) {
PLOG(ERROR) << "inotify_init() failed";
-
- if (inotify_fd_ >= 0 && thread_.Start()) {
- thread_.task_runner()->PostTask(
- FROM_HERE,
- Bind(&InotifyReaderCallback, this, inotify_fd_));
- valid_ = true;
+ return;
}
+
+ if (!StartThread())
+ return;
+
+ valid_ = true;
+}
+
+bool InotifyReader::StartThread() {
+ // This object is LazyInstance::Leaky, so thread_delegate_ will outlive the
+ // thread.
+ return PlatformThread::CreateNonJoinable(0, &thread_delegate_);
}
InotifyReader::Watch InotifyReader::AddWatch(
@@ -314,10 +342,12 @@ void InotifyReader::OnInotifyEvent(const inotify_event* event) {
}
FilePathWatcherImpl::FilePathWatcherImpl()
- : recursive_(false), weak_factory_(this) {}
+ : recursive_(false), weak_factory_(this) {
+ weak_ptr_ = weak_factory_.GetWeakPtr();
+}
FilePathWatcherImpl::~FilePathWatcherImpl() {
- DCHECK(!task_runner() || task_runner()->RunsTasksOnCurrentThread());
+ DCHECK(!task_runner() || task_runner()->RunsTasksInCurrentSequence());
}
void FilePathWatcherImpl::OnFilePathChanged(InotifyReader::Watch fired_watch,
@@ -325,15 +355,15 @@ void FilePathWatcherImpl::OnFilePathChanged(InotifyReader::Watch fired_watch,
bool created,
bool deleted,
bool is_dir) {
- DCHECK(!task_runner()->RunsTasksOnCurrentThread());
+ DCHECK(!task_runner()->RunsTasksInCurrentSequence());
// This method is invoked on the Inotify thread. Switch to task_runner() to
// access |watches_| safely. Use a WeakPtr to prevent the callback from
// running after |this| is destroyed (i.e. after the watch is cancelled).
task_runner()->PostTask(
- FROM_HERE, Bind(&FilePathWatcherImpl::OnFilePathChangedOnOriginSequence,
- weak_factory_.GetWeakPtr(), fired_watch, child, created,
- deleted, is_dir));
+ FROM_HERE,
+ BindOnce(&FilePathWatcherImpl::OnFilePathChangedOnOriginSequence,
+ weak_ptr_, fired_watch, child, created, deleted, is_dir));
}
void FilePathWatcherImpl::OnFilePathChangedOnOriginSequence(
@@ -342,7 +372,7 @@ void FilePathWatcherImpl::OnFilePathChangedOnOriginSequence(
bool created,
bool deleted,
bool is_dir) {
- DCHECK(task_runner()->RunsTasksOnCurrentThread());
+ DCHECK(task_runner()->RunsTasksInCurrentSequence());
DCHECK(!watches_.empty());
DCHECK(HasValidWatchVector());
@@ -451,7 +481,7 @@ void FilePathWatcherImpl::Cancel() {
return;
}
- DCHECK(task_runner()->RunsTasksOnCurrentThread());
+ DCHECK(task_runner()->RunsTasksInCurrentSequence());
DCHECK(!is_cancelled());
set_cancelled();
@@ -467,7 +497,7 @@ void FilePathWatcherImpl::Cancel() {
void FilePathWatcherImpl::UpdateWatches() {
// Ensure this runs on the task_runner() exclusively in order to avoid
// concurrency issues.
- DCHECK(task_runner()->RunsTasksOnCurrentThread());
+ DCHECK(task_runner()->RunsTasksInCurrentSequence());
DCHECK(HasValidWatchVector());
// Walk the list of watches and update them as we go.
@@ -596,12 +626,9 @@ void FilePathWatcherImpl::RemoveRecursiveWatches() {
if (!recursive_)
return;
- for (hash_map<InotifyReader::Watch, FilePath>::const_iterator it =
- recursive_paths_by_watch_.begin();
- it != recursive_paths_by_watch_.end();
- ++it) {
- g_inotify_reader.Get().RemoveWatch(it->first, this);
- }
+ for (const auto& it : recursive_paths_by_watch_)
+ g_inotify_reader.Get().RemoveWatch(it.first, this);
+
recursive_paths_by_watch_.clear();
recursive_watches_by_path_.clear();
}
@@ -647,7 +674,7 @@ bool FilePathWatcherImpl::HasValidWatchVector() const {
FilePathWatcher::FilePathWatcher() {
sequence_checker_.DetachFromSequence();
- impl_ = MakeUnique<FilePathWatcherImpl>();
+ impl_ = std::make_unique<FilePathWatcherImpl>();
}
} // namespace base
diff --git a/base/files/file_path_watcher_unittest.cc b/base/files/file_path_watcher_unittest.cc
index d2ec37bbec..2530b271af 100644
--- a/base/files/file_path_watcher_unittest.cc
+++ b/base/files/file_path_watcher_unittest.cc
@@ -21,6 +21,7 @@
#include "base/files/scoped_temp_dir.h"
#include "base/location.h"
#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
@@ -56,15 +57,16 @@ class NotificationCollector
// Called from the file thread by the delegates.
void OnChange(TestDelegate* delegate) {
task_runner_->PostTask(
- FROM_HERE, base::Bind(&NotificationCollector::RecordChange, this,
- base::Unretained(delegate)));
+ FROM_HERE, base::BindOnce(&NotificationCollector::RecordChange, this,
+ base::Unretained(delegate)));
}
void Register(TestDelegate* delegate) {
delegates_.insert(delegate);
}
- void Reset() {
+ void Reset(base::OnceClosure signal_closure) {
+ signal_closure_ = std::move(signal_closure);
signaled_.clear();
}
@@ -74,7 +76,7 @@ class NotificationCollector
private:
friend class base::RefCountedThreadSafe<NotificationCollector>;
- ~NotificationCollector() {}
+ ~NotificationCollector() = default;
void RecordChange(TestDelegate* delegate) {
// Warning: |delegate| is Unretained. Do not dereference.
@@ -83,8 +85,8 @@ class NotificationCollector
signaled_.insert(delegate);
// Check whether all delegates have been signaled.
- if (signaled_ == delegates_)
- task_runner_->PostTask(FROM_HERE, MessageLoop::QuitWhenIdleClosure());
+ if (signal_closure_ && signaled_ == delegates_)
+ std::move(signal_closure_).Run();
}
// Set of registered delegates.
@@ -95,12 +97,15 @@ class NotificationCollector
// The loop we should break after all delegates signaled.
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ // Closure to run when all delegates have signaled.
+ base::OnceClosure signal_closure_;
};
class TestDelegateBase : public SupportsWeakPtr<TestDelegateBase> {
public:
- TestDelegateBase() {}
- virtual ~TestDelegateBase() {}
+ TestDelegateBase() = default;
+ virtual ~TestDelegateBase() = default;
virtual void OnFileChanged(const FilePath& path, bool error) = 0;
@@ -119,7 +124,7 @@ class TestDelegate : public TestDelegateBase {
: collector_(collector) {
collector_->Register(this);
}
- ~TestDelegate() override {}
+ ~TestDelegate() override = default;
void OnFileChanged(const FilePath& path, bool error) override {
if (error)
@@ -143,7 +148,7 @@ class FilePathWatcherTest : public testing::Test {
{
}
- ~FilePathWatcherTest() override {}
+ ~FilePathWatcherTest() override = default;
protected:
void SetUp() override {
@@ -183,13 +188,16 @@ class FilePathWatcherTest : public testing::Test {
bool recursive_watch) WARN_UNUSED_RESULT;
bool WaitForEvents() WARN_UNUSED_RESULT {
- collector_->Reset();
+ return WaitForEventsWithTimeout(TestTimeouts::action_timeout());
+ }
+ bool WaitForEventsWithTimeout(TimeDelta timeout) WARN_UNUSED_RESULT {
RunLoop run_loop;
+ collector_->Reset(run_loop.QuitClosure());
+
// Make sure we timeout if we don't get notified.
ThreadTaskRunnerHandle::Get()->PostDelayedTask(
- FROM_HERE, run_loop.QuitWhenIdleClosure(),
- TestTimeouts::action_timeout());
+ FROM_HERE, run_loop.QuitClosure(), timeout);
run_loop.Run();
return collector_->Success();
}
@@ -270,40 +278,37 @@ TEST_F(FilePathWatcherTest, DeletedFile) {
// Deletes the FilePathWatcher when it's notified.
class Deleter : public TestDelegateBase {
public:
- Deleter(FilePathWatcher* watcher, MessageLoop* loop)
- : watcher_(watcher),
- loop_(loop) {
- }
- ~Deleter() override {}
+ explicit Deleter(base::OnceClosure done_closure)
+ : watcher_(std::make_unique<FilePathWatcher>()),
+ done_closure_(std::move(done_closure)) {}
+ ~Deleter() override = default;
void OnFileChanged(const FilePath&, bool) override {
watcher_.reset();
- loop_->task_runner()->PostTask(FROM_HERE,
- MessageLoop::QuitWhenIdleClosure());
+ std::move(done_closure_).Run();
}
FilePathWatcher* watcher() const { return watcher_.get(); }
private:
std::unique_ptr<FilePathWatcher> watcher_;
- MessageLoop* loop_;
+ base::OnceClosure done_closure_;
DISALLOW_COPY_AND_ASSIGN(Deleter);
};
// Verify that deleting a watcher during the callback doesn't crash.
TEST_F(FilePathWatcherTest, DeleteDuringNotify) {
- FilePathWatcher* watcher = new FilePathWatcher;
- // Takes ownership of watcher.
- std::unique_ptr<Deleter> deleter(new Deleter(watcher, &loop_));
- ASSERT_TRUE(SetupWatch(test_file(), watcher, deleter.get(), false));
+ base::RunLoop run_loop;
+ Deleter deleter(run_loop.QuitClosure());
+ ASSERT_TRUE(SetupWatch(test_file(), deleter.watcher(), &deleter, false));
ASSERT_TRUE(WriteFile(test_file(), "content"));
- ASSERT_TRUE(WaitForEvents());
+ run_loop.Run();
// We win if we haven't crashed yet.
// Might as well double-check it got deleted, too.
- ASSERT_TRUE(deleter->watcher() == NULL);
+ ASSERT_TRUE(deleter.watcher() == nullptr);
}
// Verify that deleting the watcher works even if there is a pending
@@ -538,15 +543,14 @@ TEST_F(FilePathWatcherTest, RecursiveWatch) {
ASSERT_TRUE(WaitForEvents());
}
-#if defined(OS_POSIX)
-#if defined(OS_ANDROID)
+#if defined(OS_POSIX) && !defined(OS_ANDROID)
// Apps cannot create symlinks on Android in /sdcard as /sdcard uses the
// "fuse" file system, while /data uses "ext4". Running these tests in /data
// would be preferable and allow testing file attributes and symlinks.
// TODO(pauljensen): Re-enable when crbug.com/475568 is fixed and SetUp() places
// the |temp_dir_| in /data.
-#define RecursiveWithSymLink DISABLED_RecursiveWithSymLink
-#endif // defined(OS_ANDROID)
+//
+// This test is disabled on Fuchsia since it doesn't support symlinking.
TEST_F(FilePathWatcherTest, RecursiveWithSymLink) {
if (!FilePathWatcher::RecursiveWatchAvailable())
return;
@@ -584,7 +588,7 @@ TEST_F(FilePathWatcherTest, RecursiveWithSymLink) {
ASSERT_TRUE(WriteFile(target2_file, "content"));
ASSERT_TRUE(WaitForEvents());
}
-#endif // OS_POSIX
+#endif // defined(OS_POSIX) && !defined(OS_ANDROID)
TEST_F(FilePathWatcherTest, MoveChild) {
FilePathWatcher file_watcher;
@@ -853,10 +857,7 @@ TEST_F(FilePathWatcherTest, DirAttributesChanged) {
// We should not get notified in this case as it hasn't affected our ability
// to access the file.
ASSERT_TRUE(ChangeFilePermissions(test_dir1, Read, false));
- loop_.task_runner()->PostDelayedTask(FROM_HERE,
- MessageLoop::QuitWhenIdleClosure(),
- TestTimeouts::tiny_timeout());
- ASSERT_FALSE(WaitForEvents());
+ ASSERT_FALSE(WaitForEventsWithTimeout(TestTimeouts::tiny_timeout()));
ASSERT_TRUE(ChangeFilePermissions(test_dir1, Read, true));
// We should get notified in this case because filepathwatcher can no
diff --git a/base/files/file_posix.cc b/base/files/file_posix.cc
index 2738d6c45c..f9192c3471 100644
--- a/base/files/file_posix.cc
+++ b/base/files/file_posix.cc
@@ -11,7 +11,7 @@
#include <unistd.h>
#include "base/logging.h"
-#include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_functions.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_restrictions.h"
@@ -30,21 +30,22 @@ static_assert(File::FROM_BEGIN == SEEK_SET && File::FROM_CURRENT == SEEK_CUR &&
namespace {
-#if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL)
+#if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL) || \
+ defined(OS_FUCHSIA) || (defined(OS_ANDROID) && __ANDROID_API__ < 21)
int CallFstat(int fd, stat_wrapper_t *sb) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
return fstat(fd, sb);
}
#else
int CallFstat(int fd, stat_wrapper_t *sb) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
return fstat64(fd, sb);
}
#endif
// NaCl doesn't provide the following system calls, so either simulate them or
// wrap them in order to minimize the number of #ifdef's in this file.
-#if !defined(OS_NACL)
+#if !defined(OS_NACL) && !defined(OS_AIX)
bool IsOpenAppend(PlatformFile file) {
return (fcntl(file, F_GETFL) & O_APPEND) != 0;
}
@@ -70,6 +71,7 @@ int CallFutimes(PlatformFile file, const struct timeval times[2]) {
#endif
}
+#if !defined(OS_FUCHSIA)
File::Error CallFcntlFlock(PlatformFile file, bool do_lock) {
struct flock lock;
lock.l_type = do_lock ? F_WRLCK : F_UNLCK;
@@ -77,10 +79,12 @@ File::Error CallFcntlFlock(PlatformFile file, bool do_lock) {
lock.l_start = 0;
lock.l_len = 0; // Lock entire file.
if (HANDLE_EINTR(fcntl(file, F_SETLK, &lock)) == -1)
- return File::OSErrorToFileError(errno);
+ return File::GetLastFileError();
return File::FILE_OK;
}
-#else // defined(OS_NACL)
+#endif
+
+#else // defined(OS_NACL) && !defined(OS_AIX)
bool IsOpenAppend(PlatformFile file) {
// NaCl doesn't implement fcntl. Since NaCl's write conforms to the POSIX
@@ -175,12 +179,12 @@ void File::Close() {
return;
SCOPED_FILE_TRACE("Close");
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
file_.reset();
}
int64_t File::Seek(Whence whence, int64_t offset) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
DCHECK(IsValid());
SCOPED_FILE_TRACE_WITH_SIZE("Seek", offset);
@@ -199,7 +203,7 @@ int64_t File::Seek(Whence whence, int64_t offset) {
}
int File::Read(int64_t offset, char* data, int size) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
DCHECK(IsValid());
if (size < 0)
return -1;
@@ -221,7 +225,7 @@ int File::Read(int64_t offset, char* data, int size) {
}
int File::ReadAtCurrentPos(char* data, int size) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
DCHECK(IsValid());
if (size < 0)
return -1;
@@ -242,14 +246,14 @@ int File::ReadAtCurrentPos(char* data, int size) {
}
int File::ReadNoBestEffort(int64_t offset, char* data, int size) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
DCHECK(IsValid());
SCOPED_FILE_TRACE_WITH_SIZE("ReadNoBestEffort", size);
return HANDLE_EINTR(pread(file_.get(), data, size, offset));
}
int File::ReadAtCurrentPosNoBestEffort(char* data, int size) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
DCHECK(IsValid());
if (size < 0)
return -1;
@@ -259,7 +263,7 @@ int File::ReadAtCurrentPosNoBestEffort(char* data, int size) {
}
int File::Write(int64_t offset, const char* data, int size) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
if (IsOpenAppend(file_.get()))
return WriteAtCurrentPos(data, size);
@@ -273,8 +277,17 @@ int File::Write(int64_t offset, const char* data, int size) {
int bytes_written = 0;
int rv;
do {
+#if _FILE_OFFSET_BITS != 64 || defined(__BIONIC__)
+ // In case __USE_FILE_OFFSET64 is not used, we need to call pwrite64()
+ // instead of pwrite().
+ static_assert(sizeof(int64_t) == sizeof(off64_t),
+ "off64_t must be 64 bits");
+ rv = HANDLE_EINTR(pwrite64(file_.get(), data + bytes_written,
+ size - bytes_written, offset + bytes_written));
+#else
rv = HANDLE_EINTR(pwrite(file_.get(), data + bytes_written,
size - bytes_written, offset + bytes_written));
+#endif
if (rv <= 0)
break;
@@ -285,7 +298,7 @@ int File::Write(int64_t offset, const char* data, int size) {
}
int File::WriteAtCurrentPos(const char* data, int size) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
DCHECK(IsValid());
if (size < 0)
return -1;
@@ -307,7 +320,7 @@ int File::WriteAtCurrentPos(const char* data, int size) {
}
int File::WriteAtCurrentPosNoBestEffort(const char* data, int size) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
DCHECK(IsValid());
if (size < 0)
return -1;
@@ -329,7 +342,7 @@ int64_t File::GetLength() {
}
bool File::SetLength(int64_t length) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
DCHECK(IsValid());
SCOPED_FILE_TRACE_WITH_SIZE("SetLength", length);
@@ -337,7 +350,7 @@ bool File::SetLength(int64_t length) {
}
bool File::SetTimes(Time last_access_time, Time last_modified_time) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
DCHECK(IsValid());
SCOPED_FILE_TRACE("SetTimes");
@@ -362,6 +375,7 @@ bool File::GetInfo(Info* info) {
return true;
}
+#if !defined(OS_FUCHSIA)
File::Error File::Lock() {
SCOPED_FILE_TRACE("Lock");
return CallFcntlFlock(file_.get(), true);
@@ -371,6 +385,7 @@ File::Error File::Unlock() {
SCOPED_FILE_TRACE("Unlock");
return CallFcntlFlock(file_.get(), false);
}
+#endif
File File::Duplicate() const {
if (!IsValid())
@@ -378,14 +393,11 @@ File File::Duplicate() const {
SCOPED_FILE_TRACE("Duplicate");
- PlatformFile other_fd = dup(GetPlatformFile());
+ PlatformFile other_fd = HANDLE_EINTR(dup(GetPlatformFile()));
if (other_fd == -1)
- return File(OSErrorToFileError(errno));
+ return File(File::GetLastFileError());
- File other(other_fd);
- if (async())
- other.async_ = true;
- return other;
+ return File(other_fd, async());
}
// Static.
@@ -407,6 +419,7 @@ File::Error File::OSErrorToFileError(int saved_errno) {
return FILE_ERROR_IO;
case ENOENT:
return FILE_ERROR_NOT_FOUND;
+ case ENFILE: // fallthrough
case EMFILE:
return FILE_ERROR_TOO_MANY_OPENED;
case ENOMEM:
@@ -417,9 +430,10 @@ File::Error File::OSErrorToFileError(int saved_errno) {
return FILE_ERROR_NOT_A_DIRECTORY;
default:
#if !defined(OS_NACL) // NaCl build has no metrics code.
- UMA_HISTOGRAM_SPARSE_SLOWLY("PlatformFile.UnknownErrors.Posix",
- saved_errno);
+ UmaHistogramSparse("PlatformFile.UnknownErrors.Posix", saved_errno);
#endif
+ // This function should only be called for errors.
+ DCHECK_NE(0, saved_errno);
return FILE_ERROR_FAILED;
}
}
@@ -428,7 +442,7 @@ File::Error File::OSErrorToFileError(int saved_errno) {
#if !defined(OS_NACL)
// TODO(erikkay): does it make sense to support FLAG_EXCLUSIVE_* here?
void File::DoInitialize(const FilePath& path, uint32_t flags) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
DCHECK(!IsValid());
int open_flags = 0;
@@ -497,7 +511,7 @@ void File::DoInitialize(const FilePath& path, uint32_t flags) {
}
if (descriptor < 0) {
- error_details_ = File::OSErrorToFileError(errno);
+ error_details_ = File::GetLastFileError();
return;
}
@@ -514,7 +528,7 @@ void File::DoInitialize(const FilePath& path, uint32_t flags) {
#endif // !defined(OS_NACL)
bool File::Flush() {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
DCHECK(IsValid());
SCOPED_FILE_TRACE("Flush");
@@ -533,4 +547,9 @@ void File::SetPlatformFile(PlatformFile file) {
file_.reset(file);
}
+// static
+File::Error File::GetLastFileError() {
+ return base::File::OSErrorToFileError(errno);
+}
+
} // namespace base
diff --git a/base/files/file_unittest.cc b/base/files/file_unittest.cc
index 66c312b60d..8a5732280b 100644
--- a/base/files/file_unittest.cc
+++ b/base/files/file_unittest.cc
@@ -39,6 +39,7 @@ TEST(FileTest, Create) {
File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
EXPECT_FALSE(file.IsValid());
EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, file.error_details());
+ EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, base::File::GetLastFileError());
}
{
@@ -80,6 +81,7 @@ TEST(FileTest, Create) {
EXPECT_FALSE(file.IsValid());
EXPECT_FALSE(file.created());
EXPECT_EQ(base::File::FILE_ERROR_EXISTS, file.error_details());
+ EXPECT_EQ(base::File::FILE_ERROR_EXISTS, base::File::GetLastFileError());
}
{
@@ -105,6 +107,16 @@ TEST(FileTest, Create) {
EXPECT_FALSE(base::PathExists(file_path));
}
+TEST(FileTest, SelfSwap) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ FilePath file_path = temp_dir.GetPath().AppendASCII("create_file_1");
+ File file(file_path,
+ base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_DELETE_ON_CLOSE);
+ std::swap(file, file);
+ EXPECT_TRUE(file.IsValid());
+}
+
TEST(FileTest, Async) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
@@ -166,6 +178,10 @@ TEST(FileTest, ReadWrite) {
int bytes_written = file.Write(0, data_to_write, 0);
EXPECT_EQ(0, bytes_written);
+ // Write 0 bytes, with buf=nullptr.
+ bytes_written = file.Write(0, nullptr, 0);
+ EXPECT_EQ(0, bytes_written);
+
// Write "test" to the file.
bytes_written = file.Write(0, data_to_write, kTestDataSize);
EXPECT_EQ(kTestDataSize, bytes_written);
@@ -222,6 +238,25 @@ TEST(FileTest, ReadWrite) {
EXPECT_EQ(data_to_write[i - kOffsetBeyondEndOfFile], data_read_2[i]);
}
+TEST(FileTest, GetLastFileError) {
+#if defined(OS_WIN)
+ ::SetLastError(ERROR_ACCESS_DENIED);
+#else
+ errno = EACCES;
+#endif
+ EXPECT_EQ(File::FILE_ERROR_ACCESS_DENIED, File::GetLastFileError());
+
+ base::ScopedTempDir temp_dir;
+ EXPECT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ FilePath nonexistent_path(temp_dir.GetPath().AppendASCII("nonexistent"));
+ File file(nonexistent_path, File::FLAG_OPEN | File::FLAG_READ);
+ File::Error last_error = File::GetLastFileError();
+ EXPECT_FALSE(file.IsValid());
+ EXPECT_EQ(File::FILE_ERROR_NOT_FOUND, file.error_details());
+ EXPECT_EQ(File::FILE_ERROR_NOT_FOUND, last_error);
+}
+
TEST(FileTest, Append) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
@@ -236,6 +271,10 @@ TEST(FileTest, Append) {
int bytes_written = file.Write(0, data_to_write, 0);
EXPECT_EQ(0, bytes_written);
+ // Write 0 bytes, with buf=nullptr.
+ bytes_written = file.Write(0, nullptr, 0);
+ EXPECT_EQ(0, bytes_written);
+
// Write "test" to the file.
bytes_written = file.Write(0, data_to_write, kTestDataSize);
EXPECT_EQ(kTestDataSize, bytes_written);
@@ -315,6 +354,13 @@ TEST(FileTest, Length) {
EXPECT_EQ(file_size, bytes_read);
for (int i = 0; i < file_size; i++)
EXPECT_EQ(data_to_write[i], data_read[i]);
+
+ // Close the file and reopen with base::File::FLAG_CREATE_ALWAYS, and make
+ // sure the file is empty (old file was overridden).
+ file.Close();
+ file.Initialize(file_path,
+ base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+ EXPECT_EQ(0, file.GetLength());
}
// Flakily fails: http://crbug.com/86494
@@ -495,6 +541,33 @@ TEST(FileTest, DuplicateDeleteOnClose) {
}
#if defined(OS_WIN)
+// Flakily times out on Windows, see http://crbug.com/846276.
+#define MAYBE_WriteDataToLargeOffset DISABLED_WriteDataToLargeOffset
+#else
+#define MAYBE_WriteDataToLargeOffset WriteDataToLargeOffset
+#endif
+TEST(FileTest, MAYBE_WriteDataToLargeOffset) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ FilePath file_path = temp_dir.GetPath().AppendASCII("file");
+ File file(file_path,
+ (base::File::FLAG_CREATE | base::File::FLAG_READ |
+ base::File::FLAG_WRITE | base::File::FLAG_DELETE_ON_CLOSE));
+ ASSERT_TRUE(file.IsValid());
+
+ const char kData[] = "this file is sparse.";
+ const int kDataLen = sizeof(kData) - 1;
+ const int64_t kLargeFileOffset = (1LL << 31);
+
+ // If the file fails to write, it is probably we are running out of disk space
+ // and the file system doesn't support sparse file.
+ if (file.Write(kLargeFileOffset - kDataLen - 1, kData, kDataLen) < 0)
+ return;
+
+ ASSERT_EQ(kDataLen, file.Write(kLargeFileOffset + 1, kData, kDataLen));
+}
+
+#if defined(OS_WIN)
TEST(FileTest, GetInfoForDirectory) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
@@ -672,4 +745,18 @@ TEST(FileTest, NoDeleteOnCloseWithMappedFile) {
file.Close();
ASSERT_TRUE(base::PathExists(file_path));
}
+
+// Check that we handle the async bit being set incorrectly in a sane way.
+TEST(FileTest, UseSyncApiWithAsyncFile) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ FilePath file_path = temp_dir.GetPath().AppendASCII("file");
+
+ File file(file_path, base::File::FLAG_CREATE | base::File::FLAG_WRITE |
+ base::File::FLAG_ASYNC);
+ File lying_file(file.TakePlatformFile(), false /* async */);
+ ASSERT_TRUE(lying_file.IsValid());
+
+ ASSERT_EQ(lying_file.WriteAtCurrentPos("12345", 5), -1);
+}
#endif // defined(OS_WIN)
diff --git a/base/files/file_util.cc b/base/files/file_util.cc
index 80fa44f9ea..109cb229c5 100644
--- a/base/files/file_util.cc
+++ b/base/files/file_util.cc
@@ -136,27 +136,52 @@ bool ReadFileToStringWithMaxSize(const FilePath& path,
return false;
}
- const size_t kBufferSize = 1 << 16;
- std::unique_ptr<char[]> buf(new char[kBufferSize]);
- size_t len;
- size_t size = 0;
- bool read_status = true;
-
// Many files supplied in |path| have incorrect size (proc files etc).
- // Hence, the file is read sequentially as opposed to a one-shot read.
- while ((len = fread(buf.get(), 1, kBufferSize, file)) > 0) {
- if (contents)
- contents->append(buf.get(), std::min(len, max_size - size));
-
- if ((max_size - size) < len) {
+ // Hence, the file is read sequentially as opposed to a one-shot read, using
+ // file size as a hint for chunk size if available.
+ constexpr int64_t kDefaultChunkSize = 1 << 16;
+ int64_t chunk_size;
+#if !defined(OS_NACL_NONSFI)
+ if (!GetFileSize(path, &chunk_size) || chunk_size <= 0)
+ chunk_size = kDefaultChunkSize - 1;
+ // We need to attempt to read at EOF for feof flag to be set so here we
+ // use |chunk_size| + 1.
+ chunk_size = std::min<uint64_t>(chunk_size, max_size) + 1;
+#else
+ chunk_size = kDefaultChunkSize;
+#endif // !defined(OS_NACL_NONSFI)
+ size_t bytes_read_this_pass;
+ size_t bytes_read_so_far = 0;
+ bool read_status = true;
+ std::string local_contents;
+ local_contents.resize(chunk_size);
+
+ while ((bytes_read_this_pass = fread(&local_contents[bytes_read_so_far], 1,
+ chunk_size, file)) > 0) {
+ if ((max_size - bytes_read_so_far) < bytes_read_this_pass) {
+ // Read more than max_size bytes, bail out.
+ bytes_read_so_far = max_size;
read_status = false;
break;
}
-
- size += len;
+ // In case EOF was not reached, iterate again but revert to the default
+ // chunk size.
+ if (bytes_read_so_far == 0)
+ chunk_size = kDefaultChunkSize;
+
+ bytes_read_so_far += bytes_read_this_pass;
+ // Last fread syscall (after EOF) can be avoided via feof, which is just a
+ // flag check.
+ if (feof(file))
+ break;
+ local_contents.resize(bytes_read_so_far + chunk_size);
}
read_status = read_status && !ferror(file);
CloseFile(file);
+ if (contents) {
+ contents->swap(local_contents);
+ contents->resize(bytes_read_so_far);
+ }
return read_status;
}
@@ -178,13 +203,13 @@ bool IsDirectoryEmpty(const FilePath& dir_path) {
FILE* CreateAndOpenTemporaryFile(FilePath* path) {
FilePath directory;
if (!GetTempDir(&directory))
- return NULL;
+ return nullptr;
return CreateAndOpenTemporaryFileInDir(directory, path);
}
bool CreateDirectory(const FilePath& full_path) {
- return CreateDirectoryAndGetError(full_path, NULL);
+ return CreateDirectoryAndGetError(full_path, nullptr);
}
bool GetFileSize(const FilePath& file_path, int64_t* file_size) {
@@ -215,14 +240,14 @@ bool TouchFile(const FilePath& path,
#endif // !defined(OS_NACL_NONSFI)
bool CloseFile(FILE* file) {
- if (file == NULL)
+ if (file == nullptr)
return true;
return fclose(file) == 0;
}
#if !defined(OS_NACL_NONSFI)
bool TruncateFile(FILE* file) {
- if (file == NULL)
+ if (file == nullptr)
return false;
long current_offset = ftell(file);
if (current_offset == -1)
diff --git a/base/files/file_util.h b/base/files/file_util.h
index 5ada35f9a4..456962a42f 100644
--- a/base/files/file_util.h
+++ b/base/files/file_util.h
@@ -16,6 +16,11 @@
#include <string>
#include <vector>
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
#include "base/base_export.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
@@ -23,13 +28,8 @@
#include "build/build_config.h"
#if defined(OS_WIN)
-#include <windows.h>
-#elif defined(OS_POSIX)
-#include <sys/stat.h>
-#include <unistd.h>
-#endif
-
-#if defined(OS_POSIX)
+#include "base/win/windows_types.h"
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
#include "base/file_descriptor_posix.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
@@ -95,11 +95,26 @@ BASE_EXPORT bool ReplaceFile(const FilePath& from_path,
const FilePath& to_path,
File::Error* error);
-// Copies a single file. Use CopyDirectory to copy directories.
+// Copies a single file. Use CopyDirectory() to copy directories.
// This function fails if either path contains traversal components ('..').
+// This function also fails if |to_path| is a directory.
+//
+// On POSIX, if |to_path| is a symlink, CopyFile() will follow the symlink. This
+// may have security implications. Use with care.
+//
+// If |to_path| already exists and is a regular file, it will be overwritten,
+// though its permissions will stay the same.
//
-// This function keeps the metadata on Windows. The read only bit on Windows is
-// not kept.
+// If |to_path| does not exist, it will be created. The new file's permissions
+// varies per platform:
+//
+// - This function keeps the metadata on Windows. The read only bit is not kept.
+// - On Mac and iOS, |to_path| retains |from_path|'s permissions, except user
+// read/write permissions are always set.
+// - On Linux and Android, |to_path| has user read/write permissions only. i.e.
+// Always 0600.
+// - On ChromeOS, |to_path| has user read/write permissions and group/others
+// read permissions. i.e. Always 0644.
BASE_EXPORT bool CopyFile(const FilePath& from_path, const FilePath& to_path);
// Copies the given path, and optionally all subdirectories and their contents
@@ -108,14 +123,19 @@ BASE_EXPORT bool CopyFile(const FilePath& from_path, const FilePath& to_path);
// If there are files existing under to_path, always overwrite. Returns true
// if successful, false otherwise. Wildcards on the names are not supported.
//
-// This function calls into CopyFile() so the same behavior w.r.t. metadata
-// applies.
+// This function has the same metadata behavior as CopyFile().
//
// If you only need to copy a file use CopyFile, it's faster.
BASE_EXPORT bool CopyDirectory(const FilePath& from_path,
const FilePath& to_path,
bool recursive);
+// Like CopyDirectory() except trying to overwrite an existing file will not
+// work and will return false.
+BASE_EXPORT bool CopyDirectoryExcl(const FilePath& from_path,
+ const FilePath& to_path,
+ bool recursive);
+
// Returns true if the given path exists on the local filesystem,
// false otherwise.
BASE_EXPORT bool PathExists(const FilePath& path);
@@ -158,13 +178,23 @@ BASE_EXPORT bool ReadFileToStringWithMaxSize(const FilePath& path,
std::string* contents,
size_t max_size);
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Read exactly |bytes| bytes from file descriptor |fd|, storing the result
// in |buffer|. This function is protected against EINTR and partial reads.
// Returns true iff |bytes| bytes have been successfully read from |fd|.
BASE_EXPORT bool ReadFromFD(int fd, char* buffer, size_t bytes);
+// Performs the same function as CreateAndOpenTemporaryFileInDir(), but returns
+// the file-descriptor directly, rather than wrapping it into a FILE. Returns
+// -1 on failure.
+BASE_EXPORT int CreateAndOpenFdForTemporaryFileInDir(const FilePath& dir,
+ FilePath* path);
+
+#endif // OS_POSIX || OS_FUCHSIA
+
+#if defined(OS_POSIX)
+
// Creates a symbolic link at |symlink| pointing to |target|. Returns
// false on failure.
BASE_EXPORT bool CreateSymbolicLink(const FilePath& target,
@@ -205,6 +235,15 @@ BASE_EXPORT bool SetPosixFilePermissions(const FilePath& path, int mode);
BASE_EXPORT bool ExecutableExistsInPath(Environment* env,
const FilePath::StringType& executable);
+#if defined(OS_LINUX) || defined(OS_AIX)
+// Determine if files under a given |path| can be mapped and then mprotect'd
+// PROT_EXEC. This depends on the mount options used for |path|, which vary
+// among different Linux distributions and possibly local configuration. It also
+// depends on details of kernel--ChromeOS uses the noexec option for /dev/shm
+// but its kernel allows mprotect with PROT_EXEC anyway.
+BASE_EXPORT bool IsPathExecutable(const FilePath& path);
+#endif // OS_LINUX || OS_AIX
+
#endif // OS_POSIX
// Returns true if the given directory is empty
@@ -332,7 +371,7 @@ BASE_EXPORT int ReadFile(const FilePath& filename, char* data, int max_size);
BASE_EXPORT int WriteFile(const FilePath& filename, const char* data,
int size);
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Appends |data| to |fd|. Does not close |fd| when done. Returns true iff
// |size| bytes of |data| were written to |fd|.
BASE_EXPORT bool WriteFileDescriptor(const int fd, const char* data, int size);
@@ -362,7 +401,7 @@ BASE_EXPORT int GetUniquePathNumber(const FilePath& path,
// false.
BASE_EXPORT bool SetNonBlocking(int fd);
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Creates a non-blocking, close-on-exec pipe.
// This creates a non-blocking pipe that is not intended to be shared with any
// child process. This will be done atomically if the operating system supports
@@ -389,7 +428,7 @@ BASE_EXPORT bool VerifyPathControlledByUser(const base::FilePath& base,
const base::FilePath& path,
uid_t owner_uid,
const std::set<gid_t>& group_gids);
-#endif // defined(OS_POSIX)
+#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
#if defined(OS_MACOSX) && !defined(OS_IOS)
// Is |path| writable only by a user with administrator privileges?
@@ -406,7 +445,7 @@ BASE_EXPORT bool VerifyPathControlledByAdmin(const base::FilePath& path);
// the directory |path|, in the number of FilePath::CharType, or -1 on failure.
BASE_EXPORT int GetMaximumPathComponentLength(const base::FilePath& path);
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_AIX)
// Broad categories of file systems as returned by statfs() on Linux.
enum FileSystemType {
FILE_SYSTEM_UNKNOWN, // statfs failed.
@@ -426,7 +465,7 @@ enum FileSystemType {
BASE_EXPORT bool GetFileSystemType(const FilePath& path, FileSystemType* type);
#endif
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Get a temporary directory for shared memory files. The directory may depend
// on whether the destination is intended for executable files, which in turn
// depends on how /dev/shmem was mounted. As a result, you must supply whether
diff --git a/base/files/file_util_posix.cc b/base/files/file_util_posix.cc
index 91f12030a0..066a1d1d5a 100644
--- a/base/files/file_util_posix.cc
+++ b/base/files/file_util_posix.cc
@@ -13,7 +13,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <sys/errno.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/stat.h>
@@ -22,6 +21,9 @@
#include <time.h>
#include <unistd.h>
+#include "base/base_switches.h"
+#include "base/command_line.h"
+#include "base/containers/stack.h"
#include "base/environment.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
@@ -56,42 +58,38 @@
#include <grp.h>
#endif
+// We need to do this on AIX due to some inconsistencies in how AIX
+// handles XOPEN_SOURCE and ALL_SOURCE.
+#if defined(OS_AIX)
+extern "C" char* mkdtemp(char* path);
+#endif
+
namespace base {
namespace {
-#if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL)
-static int CallStat(const char *path, stat_wrapper_t *sb) {
- ThreadRestrictions::AssertIOAllowed();
+#if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL) || \
+ defined(OS_FUCHSIA) || (defined(OS_ANDROID) && __ANDROID_API__ < 21)
+int CallStat(const char* path, stat_wrapper_t* sb) {
+ AssertBlockingAllowed();
return stat(path, sb);
}
-static int CallLstat(const char *path, stat_wrapper_t *sb) {
- ThreadRestrictions::AssertIOAllowed();
+int CallLstat(const char* path, stat_wrapper_t* sb) {
+ AssertBlockingAllowed();
return lstat(path, sb);
}
-#else // defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL)
-static int CallStat(const char *path, stat_wrapper_t *sb) {
- ThreadRestrictions::AssertIOAllowed();
+#else
+int CallStat(const char* path, stat_wrapper_t* sb) {
+ AssertBlockingAllowed();
return stat64(path, sb);
}
-static int CallLstat(const char *path, stat_wrapper_t *sb) {
- ThreadRestrictions::AssertIOAllowed();
+int CallLstat(const char* path, stat_wrapper_t* sb) {
+ AssertBlockingAllowed();
return lstat64(path, sb);
}
-#endif // !(defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL))
+#endif
#if !defined(OS_NACL_NONSFI)
-// Helper for NormalizeFilePath(), defined below.
-bool RealPath(const FilePath& path, FilePath* real_path) {
- ThreadRestrictions::AssertIOAllowed(); // For realpath().
- FilePath::CharType buf[PATH_MAX];
- if (!realpath(path.value().c_str(), buf))
- return false;
-
- *real_path = FilePath(buf);
- return true;
-}
-
// Helper for VerifyPathControlledByUser.
bool VerifySpecificPathControlledByUser(const FilePath& path,
uid_t owner_uid,
@@ -104,14 +102,12 @@ bool VerifySpecificPathControlledByUser(const FilePath& path,
}
if (S_ISLNK(stat_info.st_mode)) {
- DLOG(ERROR) << "Path " << path.value()
- << " is a symbolic link.";
+ DLOG(ERROR) << "Path " << path.value() << " is a symbolic link.";
return false;
}
if (stat_info.st_uid != owner_uid) {
- DLOG(ERROR) << "Path " << path.value()
- << " is owned by the wrong user.";
+ DLOG(ERROR) << "Path " << path.value() << " is owned by the wrong user.";
return false;
}
@@ -123,8 +119,7 @@ bool VerifySpecificPathControlledByUser(const FilePath& path,
}
if (stat_info.st_mode & S_IWOTH) {
- DLOG(ERROR) << "Path " << path.value()
- << " is writable by any user.";
+ DLOG(ERROR) << "Path " << path.value() << " is writable by any user.";
return false;
}
@@ -143,46 +138,184 @@ std::string TempFileName() {
#endif
}
-// Creates and opens a temporary file in |directory|, returning the
-// file descriptor. |path| is set to the temporary file path.
-// This function does NOT unlink() the file.
-int CreateAndOpenFdForTemporaryFile(FilePath directory, FilePath* path) {
- ThreadRestrictions::AssertIOAllowed(); // For call to mkstemp().
- *path = directory.Append(base::TempFileName());
- const std::string& tmpdir_string = path->value();
- // this should be OK since mkstemp just replaces characters in place
- char* buffer = const_cast<char*>(tmpdir_string.c_str());
+bool AdvanceEnumeratorWithStat(FileEnumerator* traversal,
+ FilePath* out_next_path,
+ struct stat* out_next_stat) {
+ DCHECK(out_next_path);
+ DCHECK(out_next_stat);
+ *out_next_path = traversal->Next();
+ if (out_next_path->empty())
+ return false;
- return HANDLE_EINTR(mkstemp(buffer));
+ *out_next_stat = traversal->GetInfo().stat();
+ return true;
}
-#if defined(OS_LINUX)
-// Determine if /dev/shm files can be mapped and then mprotect'd PROT_EXEC.
-// This depends on the mount options used for /dev/shm, which vary among
-// different Linux distributions and possibly local configuration. It also
-// depends on details of kernel--ChromeOS uses the noexec option for /dev/shm
-// but its kernel allows mprotect with PROT_EXEC anyway.
-bool DetermineDevShmExecutable() {
- bool result = false;
- FilePath path;
+bool CopyFileContents(File* infile, File* outfile) {
+ static constexpr size_t kBufferSize = 32768;
+ std::vector<char> buffer(kBufferSize);
- ScopedFD fd(CreateAndOpenFdForTemporaryFile(FilePath("/dev/shm"), &path));
- if (fd.is_valid()) {
- DeleteFile(path, false);
- long sysconf_result = sysconf(_SC_PAGESIZE);
- CHECK_GE(sysconf_result, 0);
- size_t pagesize = static_cast<size_t>(sysconf_result);
- CHECK_GE(sizeof(pagesize), sizeof(sysconf_result));
- void* mapping = mmap(NULL, pagesize, PROT_READ, MAP_SHARED, fd.get(), 0);
- if (mapping != MAP_FAILED) {
- if (mprotect(mapping, pagesize, PROT_READ | PROT_EXEC) == 0)
- result = true;
- munmap(mapping, pagesize);
- }
+ for (;;) {
+ ssize_t bytes_read = infile->ReadAtCurrentPos(buffer.data(), buffer.size());
+ if (bytes_read < 0)
+ return false;
+ if (bytes_read == 0)
+ return true;
+ // Allow for partial writes
+ ssize_t bytes_written_per_read = 0;
+ do {
+ ssize_t bytes_written_partial = outfile->WriteAtCurrentPos(
+ &buffer[bytes_written_per_read], bytes_read - bytes_written_per_read);
+ if (bytes_written_partial < 0)
+ return false;
+
+ bytes_written_per_read += bytes_written_partial;
+ } while (bytes_written_per_read < bytes_read);
}
- return result;
+
+ NOTREACHED();
+ return false;
+}
+
+bool DoCopyDirectory(const FilePath& from_path,
+ const FilePath& to_path,
+ bool recursive,
+ bool open_exclusive) {
+ AssertBlockingAllowed();
+ // Some old callers of CopyDirectory want it to support wildcards.
+ // After some discussion, we decided to fix those callers.
+ // Break loudly here if anyone tries to do this.
+ DCHECK(to_path.value().find('*') == std::string::npos);
+ DCHECK(from_path.value().find('*') == std::string::npos);
+
+ if (from_path.value().size() >= PATH_MAX) {
+ return false;
+ }
+
+ // This function does not properly handle destinations within the source
+ FilePath real_to_path = to_path;
+ if (PathExists(real_to_path))
+ real_to_path = MakeAbsoluteFilePath(real_to_path);
+ else
+ real_to_path = MakeAbsoluteFilePath(real_to_path.DirName());
+ if (real_to_path.empty())
+ return false;
+
+ FilePath real_from_path = MakeAbsoluteFilePath(from_path);
+ if (real_from_path.empty())
+ return false;
+ if (real_to_path == real_from_path || real_from_path.IsParent(real_to_path))
+ return false;
+
+ int traverse_type = FileEnumerator::FILES | FileEnumerator::SHOW_SYM_LINKS;
+ if (recursive)
+ traverse_type |= FileEnumerator::DIRECTORIES;
+ FileEnumerator traversal(from_path, recursive, traverse_type);
+
+ // We have to mimic windows behavior here. |to_path| may not exist yet,
+ // start the loop with |to_path|.
+ struct stat from_stat;
+ FilePath current = from_path;
+ if (stat(from_path.value().c_str(), &from_stat) < 0) {
+ DPLOG(ERROR) << "CopyDirectory() couldn't stat source directory: "
+ << from_path.value();
+ return false;
+ }
+ FilePath from_path_base = from_path;
+ if (recursive && DirectoryExists(to_path)) {
+ // If the destination already exists and is a directory, then the
+ // top level of source needs to be copied.
+ from_path_base = from_path.DirName();
+ }
+
+ // The Windows version of this function assumes that non-recursive calls
+ // will always have a directory for from_path.
+ // TODO(maruel): This is not necessary anymore.
+ DCHECK(recursive || S_ISDIR(from_stat.st_mode));
+
+ do {
+ // current is the source path, including from_path, so append
+ // the suffix after from_path to to_path to create the target_path.
+ FilePath target_path(to_path);
+ if (from_path_base != current &&
+ !from_path_base.AppendRelativePath(current, &target_path)) {
+ return false;
+ }
+
+ if (S_ISDIR(from_stat.st_mode)) {
+ mode_t mode = (from_stat.st_mode & 01777) | S_IRUSR | S_IXUSR | S_IWUSR;
+ if (mkdir(target_path.value().c_str(), mode) == 0)
+ continue;
+ if (errno == EEXIST && !open_exclusive)
+ continue;
+
+ DPLOG(ERROR) << "CopyDirectory() couldn't create directory: "
+ << target_path.value();
+ return false;
+ }
+
+ if (!S_ISREG(from_stat.st_mode)) {
+ DLOG(WARNING) << "CopyDirectory() skipping non-regular file: "
+ << current.value();
+ continue;
+ }
+
+ // Add O_NONBLOCK so we can't block opening a pipe.
+ File infile(open(current.value().c_str(), O_RDONLY | O_NONBLOCK));
+ if (!infile.IsValid()) {
+ DPLOG(ERROR) << "CopyDirectory() couldn't open file: " << current.value();
+ return false;
+ }
+
+ struct stat stat_at_use;
+ if (fstat(infile.GetPlatformFile(), &stat_at_use) < 0) {
+ DPLOG(ERROR) << "CopyDirectory() couldn't stat file: " << current.value();
+ return false;
+ }
+
+ if (!S_ISREG(stat_at_use.st_mode)) {
+ DLOG(WARNING) << "CopyDirectory() skipping non-regular file: "
+ << current.value();
+ continue;
+ }
+
+ int open_flags = O_WRONLY | O_CREAT;
+ // If |open_exclusive| is set then we should always create the destination
+ // file, so O_NONBLOCK is not necessary to ensure we don't block on the
+ // open call for the target file below, and since the destination will
+ // always be a regular file it wouldn't affect the behavior of the
+ // subsequent write calls anyway.
+ if (open_exclusive)
+ open_flags |= O_EXCL;
+ else
+ open_flags |= O_TRUNC | O_NONBLOCK;
+ // Each platform has different default file opening modes for CopyFile which
+ // we want to replicate here. On OS X, we use copyfile(3) which takes the
+ // source file's permissions into account. On the other platforms, we just
+ // use the base::File constructor. On Chrome OS, base::File uses a different
+ // set of permissions than it does on other POSIX platforms.
+#if defined(OS_MACOSX)
+ int mode = 0600 | (stat_at_use.st_mode & 0177);
+#elif defined(OS_CHROMEOS)
+ int mode = 0644;
+#else
+ int mode = 0600;
+#endif
+ File outfile(open(target_path.value().c_str(), open_flags, mode));
+ if (!outfile.IsValid()) {
+ DPLOG(ERROR) << "CopyDirectory() couldn't create file: "
+ << target_path.value();
+ return false;
+ }
+
+ if (!CopyFileContents(&infile, &outfile)) {
+ DLOG(ERROR) << "CopyDirectory() couldn't copy file: " << current.value();
+ return false;
+ }
+ } while (AdvanceEnumeratorWithStat(&traversal, &current, &from_stat));
+
+ return true;
}
-#endif // defined(OS_LINUX)
#endif // !defined(OS_NACL_NONSFI)
#if !defined(OS_MACOSX)
@@ -202,9 +335,9 @@ std::string AppendModeCharacter(StringPiece mode, char mode_char) {
#if !defined(OS_NACL_NONSFI)
FilePath MakeAbsoluteFilePath(const FilePath& input) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
char full_path[PATH_MAX];
- if (realpath(input.value().c_str(), full_path) == NULL)
+ if (realpath(input.value().c_str(), full_path) == nullptr)
return FilePath();
return FilePath(full_path);
}
@@ -214,14 +347,12 @@ FilePath MakeAbsoluteFilePath(const FilePath& input) {
// that functionality. If not, remove from file_util_win.cc, otherwise add it
// here.
bool DeleteFile(const FilePath& path, bool recursive) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
const char* path_str = path.value().c_str();
stat_wrapper_t file_info;
- int test = CallLstat(path_str, &file_info);
- if (test != 0) {
+ if (CallLstat(path_str, &file_info) != 0) {
// The Windows version defines this condition as success.
- bool ret = (errno == ENOENT || errno == ENOTDIR);
- return ret;
+ return (errno == ENOENT || errno == ENOTDIR);
}
if (!S_ISDIR(file_info.st_mode))
return (unlink(path_str) == 0);
@@ -229,23 +360,23 @@ bool DeleteFile(const FilePath& path, bool recursive) {
return (rmdir(path_str) == 0);
bool success = true;
- std::stack<std::string> directories;
+ stack<std::string> directories;
directories.push(path.value());
FileEnumerator traversal(path, true,
FileEnumerator::FILES | FileEnumerator::DIRECTORIES |
FileEnumerator::SHOW_SYM_LINKS);
- for (FilePath current = traversal.Next(); success && !current.empty();
+ for (FilePath current = traversal.Next(); !current.empty();
current = traversal.Next()) {
if (traversal.GetInfo().IsDirectory())
directories.push(current.value());
else
- success = (unlink(current.value().c_str()) == 0);
+ success &= (unlink(current.value().c_str()) == 0);
}
- while (success && !directories.empty()) {
+ while (!directories.empty()) {
FilePath dir = FilePath(directories.top());
directories.pop();
- success = (rmdir(dir.value().c_str()) == 0);
+ success &= (rmdir(dir.value().c_str()) == 0);
}
return success;
}
@@ -253,111 +384,24 @@ bool DeleteFile(const FilePath& path, bool recursive) {
bool ReplaceFile(const FilePath& from_path,
const FilePath& to_path,
File::Error* error) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
if (rename(from_path.value().c_str(), to_path.value().c_str()) == 0)
return true;
if (error)
- *error = File::OSErrorToFileError(errno);
+ *error = File::GetLastFileError();
return false;
}
bool CopyDirectory(const FilePath& from_path,
const FilePath& to_path,
bool recursive) {
- ThreadRestrictions::AssertIOAllowed();
- // Some old callers of CopyDirectory want it to support wildcards.
- // After some discussion, we decided to fix those callers.
- // Break loudly here if anyone tries to do this.
- DCHECK(to_path.value().find('*') == std::string::npos);
- DCHECK(from_path.value().find('*') == std::string::npos);
-
- if (from_path.value().size() >= PATH_MAX) {
- return false;
- }
-
- // This function does not properly handle destinations within the source
- FilePath real_to_path = to_path;
- if (PathExists(real_to_path)) {
- real_to_path = MakeAbsoluteFilePath(real_to_path);
- if (real_to_path.empty())
- return false;
- } else {
- real_to_path = MakeAbsoluteFilePath(real_to_path.DirName());
- if (real_to_path.empty())
- return false;
- }
- FilePath real_from_path = MakeAbsoluteFilePath(from_path);
- if (real_from_path.empty())
- return false;
- if (real_to_path == real_from_path || real_from_path.IsParent(real_to_path))
- return false;
-
- int traverse_type = FileEnumerator::FILES | FileEnumerator::SHOW_SYM_LINKS;
- if (recursive)
- traverse_type |= FileEnumerator::DIRECTORIES;
- FileEnumerator traversal(from_path, recursive, traverse_type);
-
- // We have to mimic windows behavior here. |to_path| may not exist yet,
- // start the loop with |to_path|.
- struct stat from_stat;
- FilePath current = from_path;
- if (stat(from_path.value().c_str(), &from_stat) < 0) {
- DLOG(ERROR) << "CopyDirectory() couldn't stat source directory: "
- << from_path.value() << " errno = " << errno;
- return false;
- }
- struct stat to_path_stat;
- FilePath from_path_base = from_path;
- if (recursive && stat(to_path.value().c_str(), &to_path_stat) == 0 &&
- S_ISDIR(to_path_stat.st_mode)) {
- // If the destination already exists and is a directory, then the
- // top level of source needs to be copied.
- from_path_base = from_path.DirName();
- }
-
- // The Windows version of this function assumes that non-recursive calls
- // will always have a directory for from_path.
- // TODO(maruel): This is not necessary anymore.
- DCHECK(recursive || S_ISDIR(from_stat.st_mode));
-
- bool success = true;
- while (success && !current.empty()) {
- // current is the source path, including from_path, so append
- // the suffix after from_path to to_path to create the target_path.
- FilePath target_path(to_path);
- if (from_path_base != current) {
- if (!from_path_base.AppendRelativePath(current, &target_path)) {
- success = false;
- break;
- }
- }
-
- if (S_ISDIR(from_stat.st_mode)) {
- if (mkdir(target_path.value().c_str(),
- (from_stat.st_mode & 01777) | S_IRUSR | S_IXUSR | S_IWUSR) !=
- 0 &&
- errno != EEXIST) {
- DLOG(ERROR) << "CopyDirectory() couldn't create directory: "
- << target_path.value() << " errno = " << errno;
- success = false;
- }
- } else if (S_ISREG(from_stat.st_mode)) {
- if (!CopyFile(current, target_path)) {
- DLOG(ERROR) << "CopyDirectory() couldn't create file: "
- << target_path.value();
- success = false;
- }
- } else {
- DLOG(WARNING) << "CopyDirectory() skipping non-regular file: "
- << current.value();
- }
-
- current = traversal.Next();
- if (!current.empty())
- from_stat = traversal.GetInfo().stat();
- }
+ return DoCopyDirectory(from_path, to_path, recursive, false);
+}
- return success;
+bool CopyDirectoryExcl(const FilePath& from_path,
+ const FilePath& to_path,
+ bool recursive) {
+ return DoCopyDirectory(from_path, to_path, recursive, true);
}
#endif // !defined(OS_NACL_NONSFI)
@@ -411,7 +455,7 @@ bool SetCloseOnExec(int fd) {
}
bool PathExists(const FilePath& path) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
#if defined(OS_ANDROID)
if (path.IsContentUri()) {
return ContentUriExists(path);
@@ -422,17 +466,17 @@ bool PathExists(const FilePath& path) {
#if !defined(OS_NACL_NONSFI)
bool PathIsWritable(const FilePath& path) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
return access(path.value().c_str(), W_OK) == 0;
}
#endif // !defined(OS_NACL_NONSFI)
bool DirectoryExists(const FilePath& path) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
stat_wrapper_t file_info;
- if (CallStat(path.value().c_str(), &file_info) == 0)
- return S_ISDIR(file_info.st_mode);
- return false;
+ if (CallStat(path.value().c_str(), &file_info) != 0)
+ return false;
+ return S_ISDIR(file_info.st_mode);
}
bool ReadFromFD(int fd, char* buffer, size_t bytes) {
@@ -448,6 +492,19 @@ bool ReadFromFD(int fd, char* buffer, size_t bytes) {
}
#if !defined(OS_NACL_NONSFI)
+
+int CreateAndOpenFdForTemporaryFileInDir(const FilePath& directory,
+ FilePath* path) {
+ AssertBlockingAllowed(); // For call to mkstemp().
+ *path = directory.Append(TempFileName());
+ const std::string& tmpdir_string = path->value();
+ // this should be OK since mkstemp just replaces characters in place
+ char* buffer = const_cast<char*>(tmpdir_string.c_str());
+
+ return HANDLE_EINTR(mkstemp(buffer));
+}
+
+#if !defined(OS_FUCHSIA)
bool CreateSymbolicLink(const FilePath& target_path,
const FilePath& symlink_path) {
DCHECK(!symlink_path.empty());
@@ -472,7 +529,7 @@ bool ReadSymbolicLink(const FilePath& symlink_path, FilePath* target_path) {
}
bool GetPosixFilePermissions(const FilePath& path, int* mode) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
DCHECK(mode);
stat_wrapper_t file_info;
@@ -487,7 +544,7 @@ bool GetPosixFilePermissions(const FilePath& path, int* mode) {
bool SetPosixFilePermissions(const FilePath& path,
int mode) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
DCHECK_EQ(mode & ~FILE_PERMISSION_MASK, 0);
// Calls stat() so that we can preserve the higher bits like S_ISGID.
@@ -524,22 +581,26 @@ bool ExecutableExistsInPath(Environment* env,
return false;
}
+#endif // !OS_FUCHSIA
+
#if !defined(OS_MACOSX)
// This is implemented in file_util_mac.mm for Mac.
bool GetTempDir(FilePath* path) {
const char* tmp = getenv("TMPDIR");
if (tmp) {
*path = FilePath(tmp);
- } else {
+ return true;
+ }
+
#if defined(OS_ANDROID)
- return PathService::Get(base::DIR_CACHE, path);
+ return PathService::Get(DIR_CACHE, path);
#elif defined(__ANDROID__)
- *path = FilePath("/data/local/tmp");
+ *path = FilePath("/data/local/tmp");
+ return true;
#else
- *path = FilePath("/tmp");
-#endif
- }
+ *path = FilePath("/tmp");
return true;
+#endif
}
#endif // !defined(OS_MACOSX)
@@ -571,11 +632,11 @@ FilePath GetHomeDir() {
#endif // !defined(OS_MACOSX)
bool CreateTemporaryFile(FilePath* path) {
- ThreadRestrictions::AssertIOAllowed(); // For call to close().
+ AssertBlockingAllowed(); // For call to close().
FilePath directory;
if (!GetTempDir(&directory))
return false;
- int fd = CreateAndOpenFdForTemporaryFile(directory, path);
+ int fd = CreateAndOpenFdForTemporaryFileInDir(directory, path);
if (fd < 0)
return false;
close(fd);
@@ -583,9 +644,9 @@ bool CreateTemporaryFile(FilePath* path) {
}
FILE* CreateAndOpenTemporaryFileInDir(const FilePath& dir, FilePath* path) {
- int fd = CreateAndOpenFdForTemporaryFile(dir, path);
+ int fd = CreateAndOpenFdForTemporaryFileInDir(dir, path);
if (fd < 0)
- return NULL;
+ return nullptr;
FILE* file = fdopen(fd, "a+");
if (!file)
@@ -594,15 +655,15 @@ FILE* CreateAndOpenTemporaryFileInDir(const FilePath& dir, FilePath* path) {
}
bool CreateTemporaryFileInDir(const FilePath& dir, FilePath* temp_file) {
- ThreadRestrictions::AssertIOAllowed(); // For call to close().
- int fd = CreateAndOpenFdForTemporaryFile(dir, temp_file);
+ AssertBlockingAllowed(); // For call to close().
+ int fd = CreateAndOpenFdForTemporaryFileInDir(dir, temp_file);
return ((fd >= 0) && !IGNORE_EINTR(close(fd)));
}
static bool CreateTemporaryDirInDirImpl(const FilePath& base_dir,
const FilePath::StringType& name_tmpl,
FilePath* new_dir) {
- ThreadRestrictions::AssertIOAllowed(); // For call to mkdtemp().
+ AssertBlockingAllowed(); // For call to mkdtemp().
DCHECK(name_tmpl.find("XXXXXX") != FilePath::StringType::npos)
<< "Directory name template must contain \"XXXXXX\".";
@@ -639,7 +700,7 @@ bool CreateNewTempDirectory(const FilePath::StringType& prefix,
bool CreateDirectoryAndGetError(const FilePath& full_path,
File::Error* error) {
- ThreadRestrictions::AssertIOAllowed(); // For call to mkdir().
+ AssertBlockingAllowed(); // For call to mkdir().
std::vector<FilePath> subpaths;
// Collect a list of all parent directories.
@@ -673,15 +734,13 @@ bool CreateDirectoryAndGetError(const FilePath& full_path,
}
bool NormalizeFilePath(const FilePath& path, FilePath* normalized_path) {
- FilePath real_path_result;
- if (!RealPath(path, &real_path_result))
+ FilePath real_path_result = MakeAbsoluteFilePath(path);
+ if (real_path_result.empty())
return false;
// To be consistant with windows, fail if |real_path_result| is a
// directory.
- stat_wrapper_t file_info;
- if (CallStat(real_path_result.value().c_str(), &file_info) != 0 ||
- S_ISDIR(file_info.st_mode))
+ if (DirectoryExists(real_path_result))
return false;
*normalized_path = real_path_result;
@@ -696,11 +755,7 @@ bool IsLink(const FilePath& file_path) {
// least be a 'followable' link.
if (CallLstat(file_path.value().c_str(), &st) != 0)
return false;
-
- if (S_ISLNK(st.st_mode))
- return true;
- else
- return false;
+ return S_ISLNK(st.st_mode);
}
bool GetFileInfo(const FilePath& file_path, File::Info* results) {
@@ -730,8 +785,8 @@ FILE* OpenFile(const FilePath& filename, const char* mode) {
DCHECK(
strchr(mode, 'e') == nullptr ||
(strchr(mode, ',') != nullptr && strchr(mode, 'e') > strchr(mode, ',')));
- ThreadRestrictions::AssertIOAllowed();
- FILE* result = NULL;
+ AssertBlockingAllowed();
+ FILE* result = nullptr;
#if defined(OS_MACOSX)
// macOS does not provide a mode character to set O_CLOEXEC; see
// https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man3/fopen.3.html.
@@ -762,7 +817,7 @@ FILE* FileToFILE(File file, const char* mode) {
#endif // !defined(OS_NACL)
int ReadFile(const FilePath& filename, char* data, int max_size) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
int fd = HANDLE_EINTR(open(filename.value().c_str(), O_RDONLY));
if (fd < 0)
return -1;
@@ -774,7 +829,7 @@ int ReadFile(const FilePath& filename, char* data, int max_size) {
}
int WriteFile(const FilePath& filename, const char* data, int size) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
int fd = HANDLE_EINTR(creat(filename.value().c_str(), 0666));
if (fd < 0)
return -1;
@@ -803,7 +858,7 @@ bool WriteFileDescriptor(const int fd, const char* data, int size) {
#if !defined(OS_NACL_NONSFI)
bool AppendToFile(const FilePath& filename, const char* data, int size) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
bool ret = true;
int fd = HANDLE_EINTR(open(filename.value().c_str(), O_WRONLY | O_APPEND));
if (fd < 0) {
@@ -825,10 +880,9 @@ bool AppendToFile(const FilePath& filename, const char* data, int size) {
return ret;
}
-// Gets the current working directory for the process.
bool GetCurrentDirectory(FilePath* dir) {
// getcwd can return ENOENT, which implies it checks against the disk.
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
char system_buffer[PATH_MAX] = "";
if (!getcwd(system_buffer, sizeof(system_buffer))) {
@@ -839,11 +893,9 @@ bool GetCurrentDirectory(FilePath* dir) {
return true;
}
-// Sets the current working directory for the process.
bool SetCurrentDirectory(const FilePath& path) {
- ThreadRestrictions::AssertIOAllowed();
- int ret = chdir(path.value().c_str());
- return !ret;
+ AssertBlockingAllowed();
+ return chdir(path.value().c_str()) == 0;
}
bool VerifyPathControlledByUser(const FilePath& base,
@@ -897,7 +949,7 @@ bool VerifyPathControlledByAdmin(const FilePath& path) {
};
// Reading the groups database may touch the file system.
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
std::set<gid_t> allowed_group_ids;
for (int i = 0, ie = arraysize(kAdminGroupNames); i < ie; ++i) {
@@ -917,24 +969,36 @@ bool VerifyPathControlledByAdmin(const FilePath& path) {
#endif // defined(OS_MACOSX) && !defined(OS_IOS)
int GetMaximumPathComponentLength(const FilePath& path) {
- ThreadRestrictions::AssertIOAllowed();
+#if defined(OS_FUCHSIA)
+ // Return a value we do not expect anyone ever to reach, but which is small
+ // enough to guard against e.g. bugs causing multi-megabyte paths.
+ return 1024;
+#else
+ AssertBlockingAllowed();
return pathconf(path.value().c_str(), _PC_NAME_MAX);
+#endif
}
#if !defined(OS_ANDROID)
// This is implemented in file_util_android.cc for that platform.
bool GetShmemTempDir(bool executable, FilePath* path) {
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_AIX)
+ bool disable_dev_shm = false;
+#if !defined(OS_CHROMEOS)
+ disable_dev_shm = CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableDevShmUsage);
+#endif
bool use_dev_shm = true;
if (executable) {
- static const bool s_dev_shm_executable = DetermineDevShmExecutable();
+ static const bool s_dev_shm_executable =
+ IsPathExecutable(FilePath("/dev/shm"));
use_dev_shm = s_dev_shm_executable;
}
- if (use_dev_shm) {
+ if (use_dev_shm && !disable_dev_shm) {
*path = FilePath("/dev/shm");
return true;
}
-#endif
+#endif // defined(OS_LINUX) || defined(OS_AIX)
return GetTempDir(path);
}
#endif // !defined(OS_ANDROID)
@@ -942,7 +1006,7 @@ bool GetShmemTempDir(bool executable, FilePath* path) {
#if !defined(OS_MACOSX)
// Mac has its own implementation, this is for all other Posix systems.
bool CopyFile(const FilePath& from_path, const FilePath& to_path) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
File infile;
#if defined(OS_ANDROID)
if (from_path.IsContentUri()) {
@@ -960,32 +1024,7 @@ bool CopyFile(const FilePath& from_path, const FilePath& to_path) {
if (!outfile.IsValid())
return false;
- const size_t kBufferSize = 32768;
- std::vector<char> buffer(kBufferSize);
- bool result = true;
-
- while (result) {
- ssize_t bytes_read = infile.ReadAtCurrentPos(&buffer[0], buffer.size());
- if (bytes_read < 0) {
- result = false;
- break;
- }
- if (bytes_read == 0)
- break;
- // Allow for partial writes
- ssize_t bytes_written_per_read = 0;
- do {
- ssize_t bytes_written_partial = outfile.WriteAtCurrentPos(
- &buffer[bytes_written_per_read], bytes_read - bytes_written_per_read);
- if (bytes_written_partial < 0) {
- result = false;
- break;
- }
- bytes_written_per_read += bytes_written_partial;
- } while (bytes_written_per_read < bytes_read);
- }
-
- return result;
+ return CopyFileContents(&infile, &outfile);
}
#endif // !defined(OS_MACOSX)
@@ -994,18 +1033,16 @@ bool CopyFile(const FilePath& from_path, const FilePath& to_path) {
namespace internal {
bool MoveUnsafe(const FilePath& from_path, const FilePath& to_path) {
- ThreadRestrictions::AssertIOAllowed();
- // Windows compatibility: if to_path exists, from_path and to_path
+ AssertBlockingAllowed();
+ // Windows compatibility: if |to_path| exists, |from_path| and |to_path|
// must be the same type, either both files, or both directories.
stat_wrapper_t to_file_info;
if (CallStat(to_path.value().c_str(), &to_file_info) == 0) {
stat_wrapper_t from_file_info;
- if (CallStat(from_path.value().c_str(), &from_file_info) == 0) {
- if (S_ISDIR(to_file_info.st_mode) != S_ISDIR(from_file_info.st_mode))
- return false;
- } else {
+ if (CallStat(from_path.value().c_str(), &from_file_info) != 0)
+ return false;
+ if (S_ISDIR(to_file_info.st_mode) != S_ISDIR(from_file_info.st_mode))
return false;
- }
}
if (rename(from_path.value().c_str(), to_path.value().c_str()) == 0)
@@ -1021,4 +1058,28 @@ bool MoveUnsafe(const FilePath& from_path, const FilePath& to_path) {
} // namespace internal
#endif // !defined(OS_NACL_NONSFI)
+
+#if defined(OS_LINUX) || defined(OS_AIX)
+BASE_EXPORT bool IsPathExecutable(const FilePath& path) {
+ bool result = false;
+ FilePath tmp_file_path;
+
+ ScopedFD fd(CreateAndOpenFdForTemporaryFileInDir(path, &tmp_file_path));
+ if (fd.is_valid()) {
+ DeleteFile(tmp_file_path, false);
+ long sysconf_result = sysconf(_SC_PAGESIZE);
+ CHECK_GE(sysconf_result, 0);
+ size_t pagesize = static_cast<size_t>(sysconf_result);
+ CHECK_GE(sizeof(pagesize), sizeof(sysconf_result));
+ void* mapping = mmap(nullptr, pagesize, PROT_READ, MAP_SHARED, fd.get(), 0);
+ if (mapping != MAP_FAILED) {
+ if (mprotect(mapping, pagesize, PROT_READ | PROT_EXEC) == 0)
+ result = true;
+ munmap(mapping, pagesize);
+ }
+ }
+ return result;
+}
+#endif // defined(OS_LINUX) || defined(OS_AIX)
+
} // namespace base
diff --git a/base/files/important_file_writer.cc b/base/files/important_file_writer.cc
index b46846277b..235bb8d36b 100644
--- a/base/files/important_file_writer.cc
+++ b/base/files/important_file_writer.cc
@@ -11,6 +11,7 @@
#include <utility>
#include "base/bind.h"
+#include "base/callback_helpers.h"
#include "base/critical_closure.h"
#include "base/debug/alias.h"
#include "base/files/file.h"
@@ -18,6 +19,7 @@
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/macros.h"
+#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
@@ -32,7 +34,7 @@ namespace base {
namespace {
-const int kDefaultCommitIntervalMs = 10000;
+constexpr auto kDefaultCommitInterval = TimeDelta::FromSeconds(10);
// This enum is used to define the buckets for an enumerated UMA histogram.
// Hence,
@@ -48,10 +50,50 @@ enum TempFileFailure {
TEMP_FILE_FAILURE_MAX
};
-void LogFailure(const FilePath& path, TempFileFailure failure_code,
+// Helper function to write samples to a histogram with a dynamically assigned
+// histogram name. Works with different error code types convertible to int
+// which is the actual argument type of UmaHistogramExactLinear.
+template <typename SampleType>
+void UmaHistogramExactLinearWithSuffix(const char* histogram_name,
+ StringPiece histogram_suffix,
+ SampleType add_sample,
+ SampleType max_sample) {
+ static_assert(std::is_convertible<SampleType, int>::value,
+ "SampleType should be convertible to int");
+ DCHECK(histogram_name);
+ std::string histogram_full_name(histogram_name);
+ if (!histogram_suffix.empty()) {
+ histogram_full_name.append(".");
+ histogram_full_name.append(histogram_suffix.data(),
+ histogram_suffix.length());
+ }
+ UmaHistogramExactLinear(histogram_full_name, static_cast<int>(add_sample),
+ static_cast<int>(max_sample));
+}
+
+// Helper function to write samples to a histogram with a dynamically assigned
+// histogram name. Works with short timings from 1 ms up to 10 seconds (50
+// buckets) which is the actual argument type of UmaHistogramTimes.
+void UmaHistogramTimesWithSuffix(const char* histogram_name,
+ StringPiece histogram_suffix,
+ TimeDelta sample) {
+ DCHECK(histogram_name);
+ std::string histogram_full_name(histogram_name);
+ if (!histogram_suffix.empty()) {
+ histogram_full_name.append(".");
+ histogram_full_name.append(histogram_suffix.data(),
+ histogram_suffix.length());
+ }
+ UmaHistogramTimes(histogram_full_name, sample);
+}
+
+void LogFailure(const FilePath& path,
+ StringPiece histogram_suffix,
+ TempFileFailure failure_code,
StringPiece message) {
- UMA_HISTOGRAM_ENUMERATION("ImportantFile.TempFileFailures", failure_code,
- TEMP_FILE_FAILURE_MAX);
+ UmaHistogramExactLinearWithSuffix("ImportantFile.TempFileFailures",
+ histogram_suffix, failure_code,
+ TEMP_FILE_FAILURE_MAX);
DPLOG(WARNING) << "temp file failure: " << path.value() << " : " << message;
}
@@ -61,21 +103,38 @@ void WriteScopedStringToFileAtomically(
const FilePath& path,
std::unique_ptr<std::string> data,
Closure before_write_callback,
- Callback<void(bool success)> after_write_callback) {
+ Callback<void(bool success)> after_write_callback,
+ const std::string& histogram_suffix) {
if (!before_write_callback.is_null())
before_write_callback.Run();
- bool result = ImportantFileWriter::WriteFileAtomically(path, *data);
+ TimeTicks start_time = TimeTicks::Now();
+ bool result =
+ ImportantFileWriter::WriteFileAtomically(path, *data, histogram_suffix);
+ if (result) {
+ UmaHistogramTimesWithSuffix("ImportantFile.TimeToWrite", histogram_suffix,
+ TimeTicks::Now() - start_time);
+ }
if (!after_write_callback.is_null())
after_write_callback.Run(result);
}
+void DeleteTmpFile(const FilePath& tmp_file_path,
+ StringPiece histogram_suffix) {
+ if (!DeleteFile(tmp_file_path, false)) {
+ UmaHistogramExactLinearWithSuffix(
+ "ImportantFile.FileDeleteError", histogram_suffix,
+ -base::File::GetLastFileError(), -base::File::FILE_ERROR_MAX);
+ }
+}
+
} // namespace
// static
bool ImportantFileWriter::WriteFileAtomically(const FilePath& path,
- StringPiece data) {
+ StringPiece data,
+ StringPiece histogram_suffix) {
#if defined(OS_CHROMEOS)
// On Chrome OS, chrome gets killed when it cannot finish shutdown quickly,
// and this function seems to be one of the slowest shutdown steps.
@@ -96,13 +155,21 @@ bool ImportantFileWriter::WriteFileAtomically(const FilePath& path,
// is securely created.
FilePath tmp_file_path;
if (!CreateTemporaryFileInDir(path.DirName(), &tmp_file_path)) {
- LogFailure(path, FAILED_CREATING, "could not create temporary file");
+ UmaHistogramExactLinearWithSuffix(
+ "ImportantFile.FileCreateError", histogram_suffix,
+ -base::File::GetLastFileError(), -base::File::FILE_ERROR_MAX);
+ LogFailure(path, histogram_suffix, FAILED_CREATING,
+ "could not create temporary file");
return false;
}
File tmp_file(tmp_file_path, File::FLAG_OPEN | File::FLAG_WRITE);
if (!tmp_file.IsValid()) {
- LogFailure(path, FAILED_OPENING, "could not open temporary file");
+ UmaHistogramExactLinearWithSuffix(
+ "ImportantFile.FileOpenError", histogram_suffix,
+ -tmp_file.error_details(), -base::File::FILE_ERROR_MAX);
+ LogFailure(path, histogram_suffix, FAILED_OPENING,
+ "could not open temporary file");
DeleteFile(tmp_file_path, false);
return false;
}
@@ -110,25 +177,35 @@ bool ImportantFileWriter::WriteFileAtomically(const FilePath& path,
// If this fails in the wild, something really bad is going on.
const int data_length = checked_cast<int32_t>(data.length());
int bytes_written = tmp_file.Write(0, data.data(), data_length);
+ if (bytes_written < data_length) {
+ UmaHistogramExactLinearWithSuffix(
+ "ImportantFile.FileWriteError", histogram_suffix,
+ -base::File::GetLastFileError(), -base::File::FILE_ERROR_MAX);
+ }
bool flush_success = tmp_file.Flush();
tmp_file.Close();
if (bytes_written < data_length) {
- LogFailure(path, FAILED_WRITING, "error writing, bytes_written=" +
- IntToString(bytes_written));
- DeleteFile(tmp_file_path, false);
+ LogFailure(path, histogram_suffix, FAILED_WRITING,
+ "error writing, bytes_written=" + IntToString(bytes_written));
+ DeleteTmpFile(tmp_file_path, histogram_suffix);
return false;
}
if (!flush_success) {
- LogFailure(path, FAILED_FLUSHING, "error flushing");
- DeleteFile(tmp_file_path, false);
+ LogFailure(path, histogram_suffix, FAILED_FLUSHING, "error flushing");
+ DeleteTmpFile(tmp_file_path, histogram_suffix);
return false;
}
- if (!ReplaceFile(tmp_file_path, path, nullptr)) {
- LogFailure(path, FAILED_RENAMING, "could not rename temporary file");
- DeleteFile(tmp_file_path, false);
+ base::File::Error replace_file_error = base::File::FILE_OK;
+ if (!ReplaceFile(tmp_file_path, path, &replace_file_error)) {
+ UmaHistogramExactLinearWithSuffix("ImportantFile.FileRenameError",
+ histogram_suffix, -replace_file_error,
+ -base::File::FILE_ERROR_MAX);
+ LogFailure(path, histogram_suffix, FAILED_RENAMING,
+ "could not rename temporary file");
+ DeleteTmpFile(tmp_file_path, histogram_suffix);
return false;
}
@@ -137,26 +214,29 @@ bool ImportantFileWriter::WriteFileAtomically(const FilePath& path,
ImportantFileWriter::ImportantFileWriter(
const FilePath& path,
- scoped_refptr<SequencedTaskRunner> task_runner)
- : ImportantFileWriter(
- path,
- std::move(task_runner),
- TimeDelta::FromMilliseconds(kDefaultCommitIntervalMs)) {}
+ scoped_refptr<SequencedTaskRunner> task_runner,
+ const char* histogram_suffix)
+ : ImportantFileWriter(path,
+ std::move(task_runner),
+ kDefaultCommitInterval,
+ histogram_suffix) {}
ImportantFileWriter::ImportantFileWriter(
const FilePath& path,
scoped_refptr<SequencedTaskRunner> task_runner,
- TimeDelta interval)
+ TimeDelta interval,
+ const char* histogram_suffix)
: path_(path),
task_runner_(std::move(task_runner)),
serializer_(nullptr),
commit_interval_(interval),
+ histogram_suffix_(histogram_suffix ? histogram_suffix : ""),
weak_factory_(this) {
- DCHECK(CalledOnValidThread());
DCHECK(task_runner_);
}
ImportantFileWriter::~ImportantFileWriter() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// We're usually a member variable of some other object, which also tends
// to be our serializer. It may not be safe to call back to the parent object
// being destructed.
@@ -164,23 +244,21 @@ ImportantFileWriter::~ImportantFileWriter() {
}
bool ImportantFileWriter::HasPendingWrite() const {
- DCHECK(CalledOnValidThread());
- return timer_.IsRunning();
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return timer().IsRunning();
}
void ImportantFileWriter::WriteNow(std::unique_ptr<std::string> data) {
- DCHECK(CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!IsValueInRangeForNumericType<int32_t>(data->length())) {
NOTREACHED();
return;
}
- if (HasPendingWrite())
- timer_.Stop();
-
- Closure task = Bind(&WriteScopedStringToFileAtomically, path_, Passed(&data),
- Passed(&before_next_write_callback_),
- Passed(&after_next_write_callback_));
+ Closure task = AdaptCallbackForRepeating(
+ BindOnce(&WriteScopedStringToFileAtomically, path_, std::move(data),
+ std::move(before_next_write_callback_),
+ std::move(after_next_write_callback_), histogram_suffix_));
if (!task_runner_->PostTask(FROM_HERE, MakeCriticalClosure(task))) {
// Posting the task to background message loop is not expected
@@ -190,17 +268,19 @@ void ImportantFileWriter::WriteNow(std::unique_ptr<std::string> data) {
task.Run();
}
+ ClearPendingWrite();
}
void ImportantFileWriter::ScheduleWrite(DataSerializer* serializer) {
- DCHECK(CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(serializer);
serializer_ = serializer;
- if (!timer_.IsRunning()) {
- timer_.Start(FROM_HERE, commit_interval_, this,
- &ImportantFileWriter::DoScheduledWrite);
+ if (!timer().IsRunning()) {
+ timer().Start(
+ FROM_HERE, commit_interval_,
+ Bind(&ImportantFileWriter::DoScheduledWrite, Unretained(this)));
}
}
@@ -213,7 +293,7 @@ void ImportantFileWriter::DoScheduledWrite() {
DLOG(WARNING) << "failed to serialize data to be saved in "
<< path_.value();
}
- serializer_ = nullptr;
+ ClearPendingWrite();
}
void ImportantFileWriter::RegisterOnNextWriteCallbacks(
@@ -223,4 +303,13 @@ void ImportantFileWriter::RegisterOnNextWriteCallbacks(
after_next_write_callback_ = after_next_write_callback;
}
+void ImportantFileWriter::ClearPendingWrite() {
+ timer().Stop();
+ serializer_ = nullptr;
+}
+
+void ImportantFileWriter::SetTimerForTesting(Timer* timer_override) {
+ timer_override_ = timer_override;
+}
+
} // namespace base
diff --git a/base/files/important_file_writer.h b/base/files/important_file_writer.h
index f154b043b2..08a7ee34be 100644
--- a/base/files/important_file_writer.h
+++ b/base/files/important_file_writer.h
@@ -12,8 +12,8 @@
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
+#include "base/sequence_checker.h"
#include "base/strings/string_piece.h"
-#include "base/threading/non_thread_safe.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
@@ -35,7 +35,7 @@ class SequencedTaskRunner;
//
// Also note that ImportantFileWriter can be *really* slow (cf. File::Flush()
// for details) and thus please don't block shutdown on ImportantFileWriter.
-class BASE_EXPORT ImportantFileWriter : public NonThreadSafe {
+class BASE_EXPORT ImportantFileWriter {
public:
// Used by ScheduleSave to lazily provide the data to be saved. Allows us
// to also batch data serializations.
@@ -47,13 +47,15 @@ class BASE_EXPORT ImportantFileWriter : public NonThreadSafe {
virtual bool SerializeData(std::string* data) = 0;
protected:
- virtual ~DataSerializer() {}
+ virtual ~DataSerializer() = default;
};
// Save |data| to |path| in an atomic manner. Blocks and writes data on the
// current thread. Does not guarantee file integrity across system crash (see
// the class comment above).
- static bool WriteFileAtomically(const FilePath& path, StringPiece data);
+ static bool WriteFileAtomically(const FilePath& path,
+ StringPiece data,
+ StringPiece histogram_suffix = StringPiece());
// Initialize the writer.
// |path| is the name of file to write.
@@ -61,12 +63,14 @@ class BASE_EXPORT ImportantFileWriter : public NonThreadSafe {
// execute file I/O operations.
// All non-const methods, ctor and dtor must be called on the same thread.
ImportantFileWriter(const FilePath& path,
- scoped_refptr<SequencedTaskRunner> task_runner);
+ scoped_refptr<SequencedTaskRunner> task_runner,
+ const char* histogram_suffix = nullptr);
// Same as above, but with a custom commit interval.
ImportantFileWriter(const FilePath& path,
scoped_refptr<SequencedTaskRunner> task_runner,
- TimeDelta interval);
+ TimeDelta interval,
+ const char* histogram_suffix = nullptr);
// You have to ensure that there are no pending writes at the moment
// of destruction.
@@ -109,7 +113,18 @@ class BASE_EXPORT ImportantFileWriter : public NonThreadSafe {
return commit_interval_;
}
+ // Overrides the timer to use for scheduling writes with |timer_override|.
+ void SetTimerForTesting(Timer* timer_override);
+
private:
+ const Timer& timer() const {
+ return timer_override_ ? const_cast<const Timer&>(*timer_override_)
+ : timer_;
+ }
+ Timer& timer() { return timer_override_ ? *timer_override_ : timer_; }
+
+ void ClearPendingWrite();
+
// Invoked synchronously on the next write event.
Closure before_next_write_callback_;
Callback<void(bool success)> after_next_write_callback_;
@@ -123,12 +138,20 @@ class BASE_EXPORT ImportantFileWriter : public NonThreadSafe {
// Timer used to schedule commit after ScheduleWrite.
OneShotTimer timer_;
+ // An override for |timer_| used for testing.
+ Timer* timer_override_ = nullptr;
+
// Serializer which will provide the data to be saved.
DataSerializer* serializer_;
// Time delta after which scheduled data will be written to disk.
const TimeDelta commit_interval_;
+ // Custom histogram suffix.
+ const std::string histogram_suffix_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
WeakPtrFactory<ImportantFileWriter> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(ImportantFileWriter);
diff --git a/base/files/important_file_writer_unittest.cc b/base/files/important_file_writer_unittest.cc
index 9b8dcfd4e3..5dddc71456 100644
--- a/base/files/important_file_writer_unittest.cc
+++ b/base/files/important_file_writer_unittest.cc
@@ -13,11 +13,14 @@
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
+#include "base/test/metrics/histogram_tester.h"
#include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
+#include "base/timer/mock_timer.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
@@ -46,6 +49,11 @@ class DataSerializer : public ImportantFileWriter::DataSerializer {
const std::string data_;
};
+class FailingDataSerializer : public ImportantFileWriter::DataSerializer {
+ public:
+ bool SerializeData(std::string* output) override { return false; }
+};
+
enum WriteCallbackObservationState {
NOT_CALLED,
CALLED_WITH_ERROR,
@@ -107,7 +115,7 @@ WriteCallbacksObserver::GetAndResetObservationState() {
class ImportantFileWriterTest : public testing::Test {
public:
- ImportantFileWriterTest() { }
+ ImportantFileWriterTest() = default;
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
file_ = temp_dir_.GetPath().AppendASCII("test-file");
@@ -126,7 +134,7 @@ TEST_F(ImportantFileWriterTest, Basic) {
ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
EXPECT_FALSE(PathExists(writer.path()));
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
- writer.WriteNow(MakeUnique<std::string>("foo"));
+ writer.WriteNow(std::make_unique<std::string>("foo"));
RunLoop().RunUntilIdle();
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
@@ -141,7 +149,7 @@ TEST_F(ImportantFileWriterTest, WriteWithObserver) {
// Confirm that the observer is invoked.
write_callback_observer_.ObserveNextWriteCallbacks(&writer);
- writer.WriteNow(MakeUnique<std::string>("foo"));
+ writer.WriteNow(std::make_unique<std::string>("foo"));
RunLoop().RunUntilIdle();
EXPECT_EQ(CALLED_WITH_SUCCESS,
@@ -152,7 +160,7 @@ TEST_F(ImportantFileWriterTest, WriteWithObserver) {
// Confirm that re-installing the observer works for another write.
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
write_callback_observer_.ObserveNextWriteCallbacks(&writer);
- writer.WriteNow(MakeUnique<std::string>("bar"));
+ writer.WriteNow(std::make_unique<std::string>("bar"));
RunLoop().RunUntilIdle();
EXPECT_EQ(CALLED_WITH_SUCCESS,
@@ -163,7 +171,7 @@ TEST_F(ImportantFileWriterTest, WriteWithObserver) {
// Confirm that writing again without re-installing the observer doesn't
// result in a notification.
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
- writer.WriteNow(MakeUnique<std::string>("baz"));
+ writer.WriteNow(std::make_unique<std::string>("baz"));
RunLoop().RunUntilIdle();
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
@@ -179,7 +187,7 @@ TEST_F(ImportantFileWriterTest, FailedWriteWithObserver) {
EXPECT_FALSE(PathExists(writer.path()));
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
write_callback_observer_.ObserveNextWriteCallbacks(&writer);
- writer.WriteNow(MakeUnique<std::string>("foo"));
+ writer.WriteNow(std::make_unique<std::string>("foo"));
RunLoop().RunUntilIdle();
// Confirm that the write observer was invoked with its boolean parameter set
@@ -201,11 +209,11 @@ TEST_F(ImportantFileWriterTest, CallbackRunsOnWriterThread) {
base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
file_writer_thread.task_runner()->PostTask(
- FROM_HERE,
- base::Bind(&base::WaitableEvent::Wait, base::Unretained(&wait_helper)));
+ FROM_HERE, base::BindOnce(&base::WaitableEvent::Wait,
+ base::Unretained(&wait_helper)));
write_callback_observer_.ObserveNextWriteCallbacks(&writer);
- writer.WriteNow(MakeUnique<std::string>("foo"));
+ writer.WriteNow(std::make_unique<std::string>("foo"));
RunLoop().RunUntilIdle();
// Expect the callback to not have been executed before the
@@ -222,52 +230,122 @@ TEST_F(ImportantFileWriterTest, CallbackRunsOnWriterThread) {
}
TEST_F(ImportantFileWriterTest, ScheduleWrite) {
- ImportantFileWriter writer(file_,
- ThreadTaskRunnerHandle::Get(),
- TimeDelta::FromMilliseconds(25));
+ constexpr TimeDelta kCommitInterval = TimeDelta::FromSeconds(12345);
+ MockOneShotTimer timer;
+ ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get(),
+ kCommitInterval);
+ writer.SetTimerForTesting(&timer);
EXPECT_FALSE(writer.HasPendingWrite());
DataSerializer serializer("foo");
writer.ScheduleWrite(&serializer);
EXPECT_TRUE(writer.HasPendingWrite());
- ThreadTaskRunnerHandle::Get()->PostDelayedTask(
- FROM_HERE, MessageLoop::QuitWhenIdleClosure(),
- TimeDelta::FromMilliseconds(100));
- RunLoop().Run();
+ ASSERT_TRUE(timer.IsRunning());
+ EXPECT_EQ(kCommitInterval, timer.GetCurrentDelay());
+ timer.Fire();
EXPECT_FALSE(writer.HasPendingWrite());
+ EXPECT_FALSE(timer.IsRunning());
+ RunLoop().RunUntilIdle();
ASSERT_TRUE(PathExists(writer.path()));
EXPECT_EQ("foo", GetFileContent(writer.path()));
}
TEST_F(ImportantFileWriterTest, DoScheduledWrite) {
+ MockOneShotTimer timer;
ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
+ writer.SetTimerForTesting(&timer);
EXPECT_FALSE(writer.HasPendingWrite());
DataSerializer serializer("foo");
writer.ScheduleWrite(&serializer);
EXPECT_TRUE(writer.HasPendingWrite());
writer.DoScheduledWrite();
- ThreadTaskRunnerHandle::Get()->PostDelayedTask(
- FROM_HERE, MessageLoop::QuitWhenIdleClosure(),
- TimeDelta::FromMilliseconds(100));
- RunLoop().Run();
EXPECT_FALSE(writer.HasPendingWrite());
+ RunLoop().RunUntilIdle();
ASSERT_TRUE(PathExists(writer.path()));
EXPECT_EQ("foo", GetFileContent(writer.path()));
}
TEST_F(ImportantFileWriterTest, BatchingWrites) {
- ImportantFileWriter writer(file_,
- ThreadTaskRunnerHandle::Get(),
- TimeDelta::FromMilliseconds(25));
+ MockOneShotTimer timer;
+ ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
+ writer.SetTimerForTesting(&timer);
DataSerializer foo("foo"), bar("bar"), baz("baz");
writer.ScheduleWrite(&foo);
writer.ScheduleWrite(&bar);
writer.ScheduleWrite(&baz);
- ThreadTaskRunnerHandle::Get()->PostDelayedTask(
- FROM_HERE, MessageLoop::QuitWhenIdleClosure(),
- TimeDelta::FromMilliseconds(100));
- RunLoop().Run();
+ ASSERT_TRUE(timer.IsRunning());
+ timer.Fire();
+ RunLoop().RunUntilIdle();
ASSERT_TRUE(PathExists(writer.path()));
EXPECT_EQ("baz", GetFileContent(writer.path()));
}
+TEST_F(ImportantFileWriterTest, ScheduleWrite_FailToSerialize) {
+ MockOneShotTimer timer;
+ ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
+ writer.SetTimerForTesting(&timer);
+ EXPECT_FALSE(writer.HasPendingWrite());
+ FailingDataSerializer serializer;
+ writer.ScheduleWrite(&serializer);
+ EXPECT_TRUE(writer.HasPendingWrite());
+ ASSERT_TRUE(timer.IsRunning());
+ timer.Fire();
+ EXPECT_FALSE(writer.HasPendingWrite());
+ RunLoop().RunUntilIdle();
+ EXPECT_FALSE(PathExists(writer.path()));
+}
+
+TEST_F(ImportantFileWriterTest, ScheduleWrite_WriteNow) {
+ MockOneShotTimer timer;
+ ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
+ writer.SetTimerForTesting(&timer);
+ EXPECT_FALSE(writer.HasPendingWrite());
+ DataSerializer serializer("foo");
+ writer.ScheduleWrite(&serializer);
+ EXPECT_TRUE(writer.HasPendingWrite());
+ writer.WriteNow(std::make_unique<std::string>("bar"));
+ EXPECT_FALSE(writer.HasPendingWrite());
+ EXPECT_FALSE(timer.IsRunning());
+
+ RunLoop().RunUntilIdle();
+ ASSERT_TRUE(PathExists(writer.path()));
+ EXPECT_EQ("bar", GetFileContent(writer.path()));
+}
+
+TEST_F(ImportantFileWriterTest, DoScheduledWrite_FailToSerialize) {
+ MockOneShotTimer timer;
+ ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
+ writer.SetTimerForTesting(&timer);
+ EXPECT_FALSE(writer.HasPendingWrite());
+ FailingDataSerializer serializer;
+ writer.ScheduleWrite(&serializer);
+ EXPECT_TRUE(writer.HasPendingWrite());
+
+ writer.DoScheduledWrite();
+ EXPECT_FALSE(timer.IsRunning());
+ EXPECT_FALSE(writer.HasPendingWrite());
+ RunLoop().RunUntilIdle();
+ EXPECT_FALSE(PathExists(writer.path()));
+}
+
+TEST_F(ImportantFileWriterTest, WriteFileAtomicallyHistogramSuffixTest) {
+ base::HistogramTester histogram_tester;
+ EXPECT_FALSE(PathExists(file_));
+ EXPECT_TRUE(ImportantFileWriter::WriteFileAtomically(file_, "baz", "test"));
+ EXPECT_TRUE(PathExists(file_));
+ EXPECT_EQ("baz", GetFileContent(file_));
+ histogram_tester.ExpectTotalCount("ImportantFile.FileCreateError", 0);
+ histogram_tester.ExpectTotalCount("ImportantFile.FileCreateError.test", 0);
+
+ FilePath invalid_file_ = FilePath().AppendASCII("bad/../non_existent/path");
+ EXPECT_FALSE(PathExists(invalid_file_));
+ EXPECT_FALSE(
+ ImportantFileWriter::WriteFileAtomically(invalid_file_, nullptr));
+ histogram_tester.ExpectTotalCount("ImportantFile.FileCreateError", 1);
+ histogram_tester.ExpectTotalCount("ImportantFile.FileCreateError.test", 0);
+ EXPECT_FALSE(
+ ImportantFileWriter::WriteFileAtomically(invalid_file_, nullptr, "test"));
+ histogram_tester.ExpectTotalCount("ImportantFile.FileCreateError", 1);
+ histogram_tester.ExpectTotalCount("ImportantFile.FileCreateError.test", 1);
+}
+
} // namespace base
diff --git a/base/files/memory_mapped_file.cc b/base/files/memory_mapped_file.cc
index 67890d6885..ccd9e23665 100644
--- a/base/files/memory_mapped_file.cc
+++ b/base/files/memory_mapped_file.cc
@@ -8,6 +8,7 @@
#include "base/files/file_path.h"
#include "base/logging.h"
+#include "base/numerics/safe_math.h"
#include "base/sys_info.h"
#include "build/build_config.h"
@@ -71,16 +72,20 @@ bool MemoryMappedFile::Initialize(File file,
Access access) {
switch (access) {
case READ_WRITE_EXTEND:
- // Ensure that the extended size is within limits of File.
- if (region.size > std::numeric_limits<int64_t>::max() - region.offset) {
- DLOG(ERROR) << "Region bounds exceed maximum for base::File.";
- return false;
+ DCHECK(Region::kWholeFile != region);
+ {
+ CheckedNumeric<int64_t> region_end(region.offset);
+ region_end += region.size;
+ if (!region_end.IsValid()) {
+ DLOG(ERROR) << "Region bounds exceed maximum for base::File.";
+ return false;
+ }
}
- // Fall through.
+ FALLTHROUGH;
case READ_ONLY:
case READ_WRITE:
// Ensure that the region values are valid.
- if (region.offset < 0 || region.size < 0) {
+ if (region.offset < 0) {
DLOG(ERROR) << "Region bounds are not valid.";
return false;
}
@@ -90,10 +95,8 @@ bool MemoryMappedFile::Initialize(File file,
if (IsValid())
return false;
- if (region != Region::kWholeFile) {
+ if (region != Region::kWholeFile)
DCHECK_GE(region.offset, 0);
- DCHECK_GT(region.size, 0);
- }
file_ = std::move(file);
@@ -106,23 +109,22 @@ bool MemoryMappedFile::Initialize(File file,
}
bool MemoryMappedFile::IsValid() const {
- return data_ != NULL;
+ return data_ != nullptr;
}
// static
void MemoryMappedFile::CalculateVMAlignedBoundaries(int64_t start,
- int64_t size,
+ size_t size,
int64_t* aligned_start,
- int64_t* aligned_size,
+ size_t* aligned_size,
int32_t* offset) {
// Sadly, on Windows, the mmap alignment is not just equal to the page size.
- const int64_t mask =
- static_cast<int64_t>(SysInfo::VMAllocationGranularity()) - 1;
- DCHECK_LT(mask, std::numeric_limits<int32_t>::max());
+ auto mask = SysInfo::VMAllocationGranularity() - 1;
+ DCHECK(IsValueInRangeForNumericType<int32_t>(mask));
*offset = start & mask;
*aligned_start = start & ~mask;
*aligned_size = (size + *offset + mask) & ~mask;
}
-#endif
+#endif // !defined(OS_NACL)
} // namespace base
diff --git a/base/files/memory_mapped_file.h b/base/files/memory_mapped_file.h
index cad99f679d..04f43367d1 100644
--- a/base/files/memory_mapped_file.h
+++ b/base/files/memory_mapped_file.h
@@ -61,7 +61,7 @@ class BASE_EXPORT MemoryMappedFile {
int64_t offset;
// Length of the region in bytes.
- int64_t size;
+ size_t size;
};
// Opens an existing file and maps it into memory. |access| can be read-only
@@ -108,9 +108,9 @@ class BASE_EXPORT MemoryMappedFile {
// - |aligned_size| is a multiple of the VM granularity and >= |size|.
// - |offset| is the displacement of |start| w.r.t |aligned_start|.
static void CalculateVMAlignedBoundaries(int64_t start,
- int64_t size,
+ size_t size,
int64_t* aligned_start,
- int64_t* aligned_size,
+ size_t* aligned_size,
int32_t* offset);
// Map the file to memory, set data_ to that memory address. Return true on
diff --git a/base/files/memory_mapped_file_posix.cc b/base/files/memory_mapped_file_posix.cc
index 90ba6f49c1..45a0aea6dd 100644
--- a/base/files/memory_mapped_file_posix.cc
+++ b/base/files/memory_mapped_file_posix.cc
@@ -4,6 +4,7 @@
#include "base/files/memory_mapped_file.h"
+#include <fcntl.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/mman.h>
@@ -11,19 +12,23 @@
#include <unistd.h>
#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
+#if defined(OS_ANDROID)
+#include <android/api-level.h>
+#endif
+
namespace base {
-MemoryMappedFile::MemoryMappedFile() : data_(NULL), length_(0) {
-}
+MemoryMappedFile::MemoryMappedFile() : data_(nullptr), length_(0) {}
#if !defined(OS_NACL)
bool MemoryMappedFile::MapFileRegionToMemory(
const MemoryMappedFile::Region& region,
Access access) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
off_t map_start = 0;
size_t map_size = 0;
@@ -35,6 +40,8 @@ bool MemoryMappedFile::MapFileRegionToMemory(
DPLOG(ERROR) << "fstat " << file_.GetPlatformFile();
return false;
}
+ if (!IsValueInRangeForNumericType<size_t>(file_len))
+ return false;
map_size = static_cast<size_t>(file_len);
length_ = map_size;
} else {
@@ -43,7 +50,7 @@ bool MemoryMappedFile::MapFileRegionToMemory(
// outer region [|aligned_start|, |aligned_start| + |size|] which contains
// |region| and then add up the |data_offset| displacement.
int64_t aligned_start = 0;
- int64_t aligned_size = 0;
+ size_t aligned_size = 0;
CalculateVMAlignedBoundaries(region.offset,
region.size,
&aligned_start,
@@ -51,19 +58,15 @@ bool MemoryMappedFile::MapFileRegionToMemory(
&data_offset);
// Ensure that the casts in the mmap call below are sane.
- if (aligned_start < 0 || aligned_size < 0 ||
- aligned_start > std::numeric_limits<off_t>::max() ||
- static_cast<uint64_t>(aligned_size) >
- std::numeric_limits<size_t>::max() ||
- static_cast<uint64_t>(region.size) >
- std::numeric_limits<size_t>::max()) {
+ if (aligned_start < 0 ||
+ !IsValueInRangeForNumericType<off_t>(aligned_start)) {
DLOG(ERROR) << "Region bounds are not valid for mmap";
return false;
}
map_start = static_cast<off_t>(aligned_start);
- map_size = static_cast<size_t>(aligned_size);
- length_ = static_cast<size_t>(region.size);
+ map_size = aligned_size;
+ length_ = region.size;
}
int flags = 0;
@@ -71,23 +74,92 @@ bool MemoryMappedFile::MapFileRegionToMemory(
case READ_ONLY:
flags |= PROT_READ;
break;
+
case READ_WRITE:
flags |= PROT_READ | PROT_WRITE;
break;
+
case READ_WRITE_EXTEND:
+ flags |= PROT_READ | PROT_WRITE;
+
+ const int64_t new_file_len = region.offset + region.size;
+
// POSIX won't auto-extend the file when it is written so it must first
// be explicitly extended to the maximum size. Zeros will fill the new
- // space.
- auto file_len = file_.GetLength();
- if (file_len < 0) {
+ // space. It is assumed that the existing file is fully realized as
+ // otherwise the entire file would have to be read and possibly written.
+ const int64_t original_file_len = file_.GetLength();
+ if (original_file_len < 0) {
DPLOG(ERROR) << "fstat " << file_.GetPlatformFile();
return false;
}
- file_.SetLength(std::max(file_len, region.offset + region.size));
- flags |= PROT_READ | PROT_WRITE;
+
+ // Increase the actual length of the file, if necessary. This can fail if
+ // the disk is full and the OS doesn't support sparse files.
+ if (!file_.SetLength(std::max(original_file_len, new_file_len))) {
+ DPLOG(ERROR) << "ftruncate " << file_.GetPlatformFile();
+ return false;
+ }
+
+ // Realize the extent of the file so that it can't fail (and crash) later
+ // when trying to write to a memory page that can't be created. This can
+ // fail if the disk is full and the file is sparse.
+ bool do_manual_extension = false;
+
+#if defined(OS_ANDROID) && __ANDROID_API__ < 21
+ // Only Android API>=21 supports the fallocate call. Older versions need
+ // to manually extend the file by writing zeros at block intervals.
+ do_manual_extension = true;
+#elif defined(OS_MACOSX)
+ // MacOS doesn't support fallocate even though their new APFS filesystem
+ // does support sparse files. It does, however, have the functionality
+ // available via fcntl.
+ // See also: https://openradar.appspot.com/32720223
+ fstore_t params = {F_ALLOCATEALL, F_PEOFPOSMODE, region.offset,
+ region.size, 0};
+ if (fcntl(file_.GetPlatformFile(), F_PREALLOCATE, &params) != 0) {
+ DPLOG(ERROR) << "F_PREALLOCATE";
+ // This can fail because the filesystem doesn't support it so don't
+ // give up just yet. Try the manual method below.
+ do_manual_extension = true;
+ }
+#else
+ if (posix_fallocate(file_.GetPlatformFile(), region.offset,
+ region.size) != 0) {
+ DPLOG(ERROR) << "posix_fallocate";
+ // This can fail because the filesystem doesn't support it so don't
+ // give up just yet. Try the manual method below.
+ do_manual_extension = true;
+ }
+#endif
+
+ // Manually realize the extended file by writing bytes to it at intervals.
+ if (do_manual_extension) {
+ int64_t block_size = 512; // Start with something safe.
+ struct stat statbuf;
+ if (fstat(file_.GetPlatformFile(), &statbuf) == 0 &&
+ statbuf.st_blksize > 0) {
+ block_size = statbuf.st_blksize;
+ }
+
+ // Write starting at the next block boundary after the old file length.
+ const int64_t extension_start =
+ (original_file_len + block_size - 1) & ~(block_size - 1);
+ for (int64_t i = extension_start; i < new_file_len; i += block_size) {
+ char existing_byte;
+ if (pread(file_.GetPlatformFile(), &existing_byte, 1, i) != 1)
+ return false; // Can't read? Not viable.
+ if (existing_byte != 0)
+ continue; // Block has data so must already exist.
+ if (pwrite(file_.GetPlatformFile(), &existing_byte, 1, i) != 1)
+ return false; // Can't write? Not viable.
+ }
+ }
+
break;
}
- data_ = static_cast<uint8_t*>(mmap(NULL, map_size, flags, MAP_SHARED,
+
+ data_ = static_cast<uint8_t*>(mmap(nullptr, map_size, flags, MAP_SHARED,
file_.GetPlatformFile(), map_start));
if (data_ == MAP_FAILED) {
DPLOG(ERROR) << "mmap " << file_.GetPlatformFile();
@@ -100,13 +172,13 @@ bool MemoryMappedFile::MapFileRegionToMemory(
#endif
void MemoryMappedFile::CloseHandles() {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
- if (data_ != NULL)
+ if (data_ != nullptr)
munmap(data_, length_);
file_.Close();
- data_ = NULL;
+ data_ = nullptr;
length_ = 0;
}
diff --git a/base/files/platform_file.h b/base/files/platform_file.h
new file mode 100644
index 0000000000..3929a0d08f
--- /dev/null
+++ b/base/files/platform_file.h
@@ -0,0 +1,43 @@
+// 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_FILES_PLATFORM_FILE_H_
+#define BASE_FILES_PLATFORM_FILE_H_
+
+#include "base/files/scoped_file.h"
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include "base/win/scoped_handle.h"
+#include "base/win/windows_types.h"
+#endif
+
+// This file defines platform-independent types for dealing with
+// platform-dependent files. If possible, use the higher-level base::File class
+// rather than these primitives.
+
+namespace base {
+
+#if defined(OS_WIN)
+
+using PlatformFile = HANDLE;
+using ScopedPlatformFile = ::base::win::ScopedHandle;
+
+// It would be nice to make this constexpr but INVALID_HANDLE_VALUE is a
+// ((void*)(-1)) which Clang rejects since reinterpret_cast is technically
+// disallowed in constexpr. Visual Studio accepts this, however.
+const PlatformFile kInvalidPlatformFile = INVALID_HANDLE_VALUE;
+
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+using PlatformFile = int;
+using ScopedPlatformFile = ::base::ScopedFD;
+
+constexpr PlatformFile kInvalidPlatformFile = -1;
+
+#endif
+
+} // namespace
+
+#endif // BASE_FILES_PLATFORM_FILE_H_
diff --git a/base/files/scoped_file.cc b/base/files/scoped_file.cc
index 78d4ca5263..1b9227d977 100644
--- a/base/files/scoped_file.cc
+++ b/base/files/scoped_file.cc
@@ -7,18 +7,17 @@
#include "base/logging.h"
#include "build/build_config.h"
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
#include <errno.h>
#include <unistd.h>
-#include "base/debug/alias.h"
#include "base/posix/eintr_wrapper.h"
#endif
namespace base {
namespace internal {
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// static
void ScopedFDCloseTraits::Free(int fd) {
@@ -31,16 +30,12 @@ void ScopedFDCloseTraits::Free(int fd) {
// a single open directory would bypass the entire security model.
int ret = IGNORE_EINTR(close(fd));
- // TODO(davidben): Remove this once it's been determined whether
- // https://crbug.com/603354 is caused by EBADF or a network filesystem
- // returning some other error.
- int close_errno = errno;
- base::debug::Alias(&close_errno);
-
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_FUCHSIA) || \
+ defined(OS_ANDROID)
// NB: Some file descriptors can return errors from close() e.g. network
- // filesystems such as NFS and Linux input devices. On Linux, errors from
- // close other than EBADF do not indicate failure to actually close the fd.
+ // filesystems such as NFS and Linux input devices. On Linux, macOS, and
+ // Fuchsia's POSIX layer, errors from close other than EBADF do not indicate
+ // failure to actually close the fd.
if (ret != 0 && errno != EBADF)
ret = 0;
#endif
@@ -48,7 +43,7 @@ void ScopedFDCloseTraits::Free(int fd) {
PCHECK(0 == ret);
}
-#endif // OS_POSIX
+#endif // OS_POSIX || OS_FUCHSIA
} // namespace internal
} // namespace base
diff --git a/base/files/scoped_file.h b/base/files/scoped_file.h
index 68c0415824..e32a603909 100644
--- a/base/files/scoped_file.h
+++ b/base/files/scoped_file.h
@@ -18,7 +18,7 @@ namespace base {
namespace internal {
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
struct BASE_EXPORT ScopedFDCloseTraits {
static int InvalidValue() {
return -1;
@@ -39,7 +39,7 @@ struct ScopedFILECloser {
// -----------------------------------------------------------------------------
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// A low-level Posix file descriptor closer class. Use this when writing
// platform-specific code, especially that does non-file-like things with the
// FD (like sockets).
diff --git a/base/files/scoped_temp_dir.cc b/base/files/scoped_temp_dir.cc
index 26815217c6..01ec0f0caa 100644
--- a/base/files/scoped_temp_dir.cc
+++ b/base/files/scoped_temp_dir.cc
@@ -9,8 +9,14 @@
namespace base {
-ScopedTempDir::ScopedTempDir() {
-}
+namespace {
+
+constexpr FilePath::CharType kScopedDirPrefix[] =
+ FILE_PATH_LITERAL("scoped_dir");
+
+} // namespace
+
+ScopedTempDir::ScopedTempDir() = default;
ScopedTempDir::~ScopedTempDir() {
if (!path_.empty() && !Delete())
@@ -23,7 +29,7 @@ bool ScopedTempDir::CreateUniqueTempDir() {
// This "scoped_dir" prefix is only used on Windows and serves as a template
// for the unique name.
- if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL("scoped_dir"), &path_))
+ if (!base::CreateNewTempDirectory(kScopedDirPrefix, &path_))
return false;
return true;
@@ -38,9 +44,7 @@ bool ScopedTempDir::CreateUniqueTempDirUnderPath(const FilePath& base_path) {
return false;
// Create a new, uniquely named directory under |base_path|.
- if (!base::CreateTemporaryDirInDir(base_path,
- FILE_PATH_LITERAL("scoped_dir_"),
- &path_))
+ if (!base::CreateTemporaryDirInDir(base_path, kScopedDirPrefix, &path_))
return false;
return true;
@@ -85,4 +89,9 @@ bool ScopedTempDir::IsValid() const {
return !path_.empty() && DirectoryExists(path_);
}
+// static
+const FilePath::CharType* ScopedTempDir::GetTempDirPrefix() {
+ return kScopedDirPrefix;
+}
+
} // namespace base
diff --git a/base/files/scoped_temp_dir.h b/base/files/scoped_temp_dir.h
index a5aaf84362..872f6f81f4 100644
--- a/base/files/scoped_temp_dir.h
+++ b/base/files/scoped_temp_dir.h
@@ -5,11 +5,13 @@
#ifndef BASE_FILES_SCOPED_TEMP_DIR_H_
#define BASE_FILES_SCOPED_TEMP_DIR_H_
-// An object representing a temporary / scratch directory that should be cleaned
-// up (recursively) when this object goes out of scope. Note that since
-// deletion occurs during the destructor, no further error handling is possible
-// if the directory fails to be deleted. As a result, deletion is not
-// guaranteed by this class.
+// An object representing a temporary / scratch directory that should be
+// cleaned up (recursively) when this object goes out of scope. Since deletion
+// occurs during the destructor, no further error handling is possible if the
+// directory fails to be deleted. As a result, deletion is not guaranteed by
+// this class. (However note that, whenever possible, by default
+// CreateUniqueTempDir creates the directory in a location that is
+// automatically cleaned up on reboot, or at other appropriate times.)
//
// Multiple calls to the methods which establish a temporary directory
// (CreateUniqueTempDir, CreateUniqueTempDirUnderPath, and Set) must have
@@ -54,6 +56,10 @@ class BASE_EXPORT ScopedTempDir {
// Returns true if path_ is non-empty and exists.
bool IsValid() const;
+ // Returns the prefix used for temp directory names generated by
+ // ScopedTempDirs.
+ static const FilePath::CharType* GetTempDirPrefix();
+
private:
FilePath path_;
diff --git a/base/format_macros.h b/base/format_macros.h
index 0697c6ddf2..1279ff7816 100644
--- a/base/format_macros.h
+++ b/base/format_macros.h
@@ -26,19 +26,33 @@
#include "build/build_config.h"
-#if defined(OS_POSIX) && (defined(_INTTYPES_H) || defined(_INTTYPES_H_)) && \
- !defined(PRId64)
+#if (defined(OS_POSIX) || defined(OS_FUCHSIA)) && \
+ (defined(_INTTYPES_H) || defined(_INTTYPES_H_)) && !defined(PRId64)
#error "inttypes.h has already been included before this header file, but "
#error "without __STDC_FORMAT_MACROS defined."
#endif
-#if defined(OS_POSIX) && !defined(__STDC_FORMAT_MACROS)
+#if (defined(OS_POSIX) || defined(OS_FUCHSIA)) && !defined(__STDC_FORMAT_MACROS)
#define __STDC_FORMAT_MACROS
#endif
#include <inttypes.h>
-#if defined(OS_POSIX)
+#if defined(OS_WIN)
+
+#if !defined(PRId64) || !defined(PRIu64) || !defined(PRIx64)
+#error "inttypes.h provided by win toolchain should define these."
+#endif
+
+#define WidePRId64 L"I64d"
+#define WidePRIu64 L"I64u"
+#define WidePRIx64 L"I64x"
+
+#if !defined(PRIuS)
+#define PRIuS "Iu"
+#endif
+
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
// GCC will concatenate wide and narrow strings correctly, so nothing needs to
// be done here.
@@ -50,6 +64,8 @@
#define PRIuS "zu"
#endif
+#endif // defined(OS_WIN)
+
// The size of NSInteger and NSUInteger varies between 32-bit and 64-bit
// architectures and Apple does not provides standard format macros and
// recommends casting. This has many drawbacks, so instead define macros
@@ -78,20 +94,4 @@
#endif
#endif // defined(OS_MACOSX)
-#else // OS_WIN
-
-#if !defined(PRId64) || !defined(PRIu64) || !defined(PRIx64)
-#error "inttypes.h provided by win toolchain should define these."
-#endif
-
-#define WidePRId64 L"I64d"
-#define WidePRIu64 L"I64u"
-#define WidePRIx64 L"I64x"
-
-#if !defined(PRIuS)
-#define PRIuS "Iu"
-#endif
-
-#endif
-
#endif // BASE_FORMAT_MACROS_H_
diff --git a/base/gmock_unittest.cc b/base/gmock_unittest.cc
index 855380a975..5c16728e35 100644
--- a/base/gmock_unittest.cc
+++ b/base/gmock_unittest.cc
@@ -13,7 +13,7 @@
using testing::AnyOf;
using testing::Eq;
using testing::Return;
-using testing::SetArgumentPointee;
+using testing::SetArgPointee;
using testing::WithArg;
using testing::_;
@@ -23,8 +23,8 @@ namespace {
// for easy mocking.
class SampleClass {
public:
- SampleClass() {}
- virtual ~SampleClass() {}
+ SampleClass() = default;
+ virtual ~SampleClass() = default;
virtual int ReturnSomething() {
return -1;
@@ -84,8 +84,7 @@ TEST(GmockTest, AssignArgument) {
// Capture an argument for examination.
MockSampleClass mock;
- EXPECT_CALL(mock, OutputParam(_))
- .WillRepeatedly(SetArgumentPointee<0>(5));
+ EXPECT_CALL(mock, OutputParam(_)).WillRepeatedly(SetArgPointee<0>(5));
int arg = 0;
mock.OutputParam(&arg);
@@ -96,8 +95,7 @@ TEST(GmockTest, SideEffects) {
// Capture an argument for examination.
MockSampleClass mock;
- EXPECT_CALL(mock, OutputParam(_))
- .WillRepeatedly(SetArgumentPointee<0>(5));
+ EXPECT_CALL(mock, OutputParam(_)).WillRepeatedly(SetArgPointee<0>(5));
int arg = 0;
mock.OutputParam(&arg);
diff --git a/base/gtest_prod_util.h b/base/gtest_prod_util.h
index 3289e6301d..2ca267e27f 100644
--- a/base/gtest_prod_util.h
+++ b/base/gtest_prod_util.h
@@ -5,7 +5,7 @@
#ifndef BASE_GTEST_PROD_UTIL_H_
#define BASE_GTEST_PROD_UTIL_H_
-#include "testing/gtest/include/gtest/gtest_prod.h"
+#include "testing/gtest/include/gtest/gtest_prod.h" // nogncheck
// This is a wrapper for gtest's FRIEND_TEST macro that friends
// test with all possible prefixes. This is very helpful when changing the test
diff --git a/base/guid.cc b/base/guid.cc
index 571407330c..2a23658378 100644
--- a/base/guid.cc
+++ b/base/guid.cc
@@ -41,20 +41,23 @@ bool IsValidGUIDInternal(const base::StringPiece& guid, bool strict) {
} // namespace
std::string GenerateGUID() {
- uint64_t sixteen_bytes[2] = {base::RandUint64(), base::RandUint64()};
+ uint64_t sixteen_bytes[2];
+ // 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(&sixteen_bytes, sizeof(sixteen_bytes));
// Set the GUID to version 4 as described in RFC 4122, section 4.4.
// The format of GUID version 4 must be xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx,
// where y is one of [8, 9, A, B].
// Clear the version bits and set the version to 4:
- sixteen_bytes[0] &= 0xffffffffffff0fffULL;
- sixteen_bytes[0] |= 0x0000000000004000ULL;
+ sixteen_bytes[0] &= 0xffffffff'ffff0fffULL;
+ sixteen_bytes[0] |= 0x00000000'00004000ULL;
// Set the two most significant bits (bits 6 and 7) of the
// clock_seq_hi_and_reserved to zero and one, respectively:
- sixteen_bytes[1] &= 0x3fffffffffffffffULL;
- sixteen_bytes[1] |= 0x8000000000000000ULL;
+ sixteen_bytes[1] &= 0x3fffffff'ffffffffULL;
+ sixteen_bytes[1] |= 0x80000000'00000000ULL;
return RandomDataToGUIDString(sixteen_bytes);
}
@@ -73,7 +76,7 @@ std::string RandomDataToGUIDString(const uint64_t bytes[2]) {
static_cast<unsigned int>((bytes[0] >> 16) & 0x0000ffff),
static_cast<unsigned int>(bytes[0] & 0x0000ffff),
static_cast<unsigned int>(bytes[1] >> 48),
- bytes[1] & 0x0000ffffffffffffULL);
+ bytes[1] & 0x0000ffff'ffffffffULL);
}
} // namespace base
diff --git a/base/guid.h b/base/guid.h
index 29c24eacef..c6937a1e55 100644
--- a/base/guid.h
+++ b/base/guid.h
@@ -15,12 +15,14 @@
namespace base {
-// Generate a 128-bit (pseudo) random GUID in the form of version 4 as described
-// in RFC 4122, section 4.4.
+// Generate a 128-bit random GUID in the form of version 4 as described in
+// RFC 4122, section 4.4.
// The format of GUID version 4 must be xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx,
// where y is one of [8, 9, A, B].
// The hexadecimal values "a" through "f" are output as lower case characters.
-// If GUID generation fails an empty string is returned.
+//
+// A cryptographically secure random source will be used, but consider using
+// UnguessableToken for greater type-safety if GUID format is unnecessary.
BASE_EXPORT std::string GenerateGUID();
// Returns true if the input string conforms to the version 4 GUID format.
diff --git a/base/hash.cc b/base/hash.cc
index 4dfd0d0522..d9fe07e954 100644
--- a/base/hash.cc
+++ b/base/hash.cc
@@ -6,11 +6,101 @@
#include <functional>
-namespace base {
-
uint32_t SuperFastHash(const char* data, size_t len) {
std::hash<std::string> hash_fn;
return hash_fn(std::string(data, len));
}
+namespace base {
+
+uint32_t Hash(const void* data, size_t length) {
+ // Currently our in-memory hash is the same as the persistent hash. The
+ // split between in-memory and persistent hash functions is maintained to
+ // allow the in-memory hash function to be updated in the future.
+ return PersistentHash(data, length);
+}
+
+uint32_t Hash(const std::string& str) {
+ return PersistentHash(str.data(), str.size());
+}
+
+uint32_t Hash(const string16& str) {
+ return PersistentHash(str.data(), str.size() * sizeof(char16));
+}
+
+uint32_t PersistentHash(const void* data, size_t length) {
+ // This hash function must not change, since it is designed to be persistable
+ // to disk.
+ if (length > static_cast<size_t>(std::numeric_limits<int>::max())) {
+ NOTREACHED();
+ return 0;
+ }
+ return ::SuperFastHash(reinterpret_cast<const char*>(data),
+ static_cast<int>(length));
+}
+
+uint32_t PersistentHash(const std::string& str) {
+ return PersistentHash(str.data(), str.size());
+}
+
+// Implement hashing for pairs of at-most 32 bit integer values.
+// When size_t is 32 bits, we turn the 64-bit hash code into 32 bits by using
+// multiply-add hashing. This algorithm, as described in
+// Theorem 4.3.3 of the thesis "Über die Komplexität der Multiplikation in
+// eingeschränkten Branchingprogrammmodellen" by Woelfel, is:
+//
+// h32(x32, y32) = (h64(x32, y32) * rand_odd64 + rand16 * 2^16) % 2^64 / 2^32
+//
+// Contact danakj@chromium.org for any questions.
+size_t HashInts32(uint32_t value1, uint32_t value2) {
+ uint64_t value1_64 = value1;
+ uint64_t hash64 = (value1_64 << 32) | value2;
+
+ if (sizeof(size_t) >= sizeof(uint64_t))
+ return static_cast<size_t>(hash64);
+
+ uint64_t odd_random = 481046412LL << 32 | 1025306955LL;
+ uint32_t shift_random = 10121U << 16;
+
+ hash64 = hash64 * odd_random + shift_random;
+ size_t high_bits =
+ static_cast<size_t>(hash64 >> (8 * (sizeof(uint64_t) - sizeof(size_t))));
+ return high_bits;
+}
+
+// Implement hashing for pairs of up-to 64-bit integer values.
+// We use the compound integer hash method to produce a 64-bit hash code, by
+// breaking the two 64-bit inputs into 4 32-bit values:
+// http://opendatastructures.org/versions/edition-0.1d/ods-java/node33.html#SECTION00832000000000000000
+// Then we reduce our result to 32 bits if required, similar to above.
+size_t HashInts64(uint64_t value1, uint64_t value2) {
+ uint32_t short_random1 = 842304669U;
+ uint32_t short_random2 = 619063811U;
+ uint32_t short_random3 = 937041849U;
+ uint32_t short_random4 = 3309708029U;
+
+ uint32_t value1a = static_cast<uint32_t>(value1 & 0xffffffff);
+ uint32_t value1b = static_cast<uint32_t>((value1 >> 32) & 0xffffffff);
+ uint32_t value2a = static_cast<uint32_t>(value2 & 0xffffffff);
+ uint32_t value2b = static_cast<uint32_t>((value2 >> 32) & 0xffffffff);
+
+ uint64_t product1 = static_cast<uint64_t>(value1a) * short_random1;
+ uint64_t product2 = static_cast<uint64_t>(value1b) * short_random2;
+ uint64_t product3 = static_cast<uint64_t>(value2a) * short_random3;
+ uint64_t product4 = static_cast<uint64_t>(value2b) * short_random4;
+
+ uint64_t hash64 = product1 + product2 + product3 + product4;
+
+ if (sizeof(size_t) >= sizeof(uint64_t))
+ return static_cast<size_t>(hash64);
+
+ uint64_t odd_random = 1578233944LL << 32 | 194370989LL;
+ uint32_t shift_random = 20591U << 16;
+
+ hash64 = hash64 * odd_random + shift_random;
+ size_t high_bits =
+ static_cast<size_t>(hash64 >> (8 * (sizeof(uint64_t) - sizeof(size_t))));
+ return high_bits;
+}
+
} // namespace base
diff --git a/base/hash.h b/base/hash.h
index 7c0fba6d39..165899ee56 100644
--- a/base/hash.h
+++ b/base/hash.h
@@ -14,83 +14,31 @@
#include "base/base_export.h"
#include "base/logging.h"
+#include "base/strings/string16.h"
namespace base {
-// WARNING: This hash function should not be used for any cryptographic purpose.
-BASE_EXPORT uint32_t SuperFastHash(const char* data, size_t length);
-
-// Computes a hash of a memory buffer |data| of a given |length|.
-// WARNING: This hash function should not be used for any cryptographic purpose.
-inline uint32_t Hash(const char* data, size_t length) {
- return SuperFastHash(data, length);
-}
-
-// Computes a hash of a string |str|.
-// WARNING: This hash function should not be used for any cryptographic purpose.
-inline uint32_t Hash(const std::string& str) {
- return Hash(str.data(), str.size());
-}
-
-// Implement hashing for pairs of at-most 32 bit integer values.
-// When size_t is 32 bits, we turn the 64-bit hash code into 32 bits by using
-// multiply-add hashing. This algorithm, as described in
-// Theorem 4.3.3 of the thesis "Über die Komplexität der Multiplikation in
-// eingeschränkten Branchingprogrammmodellen" by Woelfel, is:
+// Computes a hash of a memory buffer. This hash function is subject to change
+// in the future, so use only for temporary in-memory structures. If you need
+// to persist a change on disk or between computers, use PersistentHash().
//
-// h32(x32, y32) = (h64(x32, y32) * rand_odd64 + rand16 * 2^16) % 2^64 / 2^32
+// WARNING: This hash function should not be used for any cryptographic purpose.
+BASE_EXPORT uint32_t Hash(const void* data, size_t length);
+BASE_EXPORT uint32_t Hash(const std::string& str);
+BASE_EXPORT uint32_t Hash(const string16& str);
+
+// Computes a hash of a memory buffer. This hash function must not change so
+// that code can use the hashed values for persistent storage purposes or
+// sending across the network. If a new persistent hash function is desired, a
+// new version will have to be added in addition.
//
-// Contact danakj@chromium.org for any questions.
-inline size_t HashInts32(uint32_t value1, uint32_t value2) {
- uint64_t value1_64 = value1;
- uint64_t hash64 = (value1_64 << 32) | value2;
-
- if (sizeof(size_t) >= sizeof(uint64_t))
- return static_cast<size_t>(hash64);
-
- uint64_t odd_random = 481046412LL << 32 | 1025306955LL;
- uint32_t shift_random = 10121U << 16;
-
- hash64 = hash64 * odd_random + shift_random;
- size_t high_bits =
- static_cast<size_t>(hash64 >> (8 * (sizeof(uint64_t) - sizeof(size_t))));
- return high_bits;
-}
-
-// Implement hashing for pairs of up-to 64-bit integer values.
-// We use the compound integer hash method to produce a 64-bit hash code, by
-// breaking the two 64-bit inputs into 4 32-bit values:
-// http://opendatastructures.org/versions/edition-0.1d/ods-java/node33.html#SECTION00832000000000000000
-// Then we reduce our result to 32 bits if required, similar to above.
-inline size_t HashInts64(uint64_t value1, uint64_t value2) {
- uint32_t short_random1 = 842304669U;
- uint32_t short_random2 = 619063811U;
- uint32_t short_random3 = 937041849U;
- uint32_t short_random4 = 3309708029U;
-
- uint32_t value1a = static_cast<uint32_t>(value1 & 0xffffffff);
- uint32_t value1b = static_cast<uint32_t>((value1 >> 32) & 0xffffffff);
- uint32_t value2a = static_cast<uint32_t>(value2 & 0xffffffff);
- uint32_t value2b = static_cast<uint32_t>((value2 >> 32) & 0xffffffff);
-
- uint64_t product1 = static_cast<uint64_t>(value1a) * short_random1;
- uint64_t product2 = static_cast<uint64_t>(value1b) * short_random2;
- uint64_t product3 = static_cast<uint64_t>(value2a) * short_random3;
- uint64_t product4 = static_cast<uint64_t>(value2b) * short_random4;
-
- uint64_t hash64 = product1 + product2 + product3 + product4;
-
- if (sizeof(size_t) >= sizeof(uint64_t))
- return static_cast<size_t>(hash64);
+// WARNING: This hash function should not be used for any cryptographic purpose.
+BASE_EXPORT uint32_t PersistentHash(const void* data, size_t length);
+BASE_EXPORT uint32_t PersistentHash(const std::string& str);
- uint64_t odd_random = 1578233944LL << 32 | 194370989LL;
- uint32_t shift_random = 20591U << 16;
-
- hash64 = hash64 * odd_random + shift_random;
- size_t high_bits =
- static_cast<size_t>(hash64 >> (8 * (sizeof(uint64_t) - sizeof(size_t))));
- return high_bits;
-}
+// Hash pairs of 32-bit or 64-bit numbers.
+BASE_EXPORT size_t HashInts32(uint32_t value1, uint32_t value2);
+BASE_EXPORT size_t HashInts64(uint64_t value1, uint64_t value2);
template <typename T1, typename T2>
inline size_t HashInts(T1 value1, T2 value2) {
@@ -102,7 +50,10 @@ inline size_t HashInts(T1 value1, T2 value2) {
return HashInts32(value1, value2);
}
-// A templated hasher for pairs of integer types.
+// A templated hasher for pairs of integer types. Example:
+//
+// using MyPair = std::pair<int32_t, int32_t>;
+// std::unordered_set<MyPair, base::IntPairHash<MyPair>> set;
template <typename T>
struct IntPairHash;
diff --git a/base/i18n/rtl.h b/base/i18n/rtl.h
index df15cd0208..e54f8ea355 100644
--- a/base/i18n/rtl.h
+++ b/base/i18n/rtl.h
@@ -54,12 +54,19 @@ 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();
+// A test utility function to set the application default text direction.
+BASE_I18N_EXPORT void SetRTLForTesting(bool rtl);
+
// 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();
+// Gets the explicitly forced text direction for debugging. If no forcing is
+// applied, returns UNKNOWN_DIRECTION.
+BASE_I18N_EXPORT TextDirection GetForcedTextDirection();
+
// Returns the text direction for |locale_name|.
// As a startup optimization, this method checks the locale against a list of
// Chrome-supported RTL locales.
@@ -116,6 +123,15 @@ BASE_I18N_EXPORT bool AdjustStringForLocaleDirection(string16* text);
// Undoes the actions of the above function (AdjustStringForLocaleDirection).
BASE_I18N_EXPORT bool UnadjustStringForLocaleDirection(string16* text);
+// Ensures |text| contains no unterminated directional formatting characters, by
+// appending the appropriate pop-directional-formatting characters to the end of
+// |text|.
+BASE_I18N_EXPORT void EnsureTerminatedDirectionalFormatting(string16* text);
+
+// Sanitizes the |text| by terminating any directional override/embedding
+// characters and then adjusting the string for locale direction.
+BASE_I18N_EXPORT void SanitizeUserSuppliedString(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.
diff --git a/base/json/json_correctness_fuzzer.cc b/base/json/json_correctness_fuzzer.cc
new file mode 100644
index 0000000000..1f32d8c230
--- /dev/null
+++ b/base/json/json_correctness_fuzzer.cc
@@ -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.
+
+// A fuzzer that checks correctness of json parser/writer.
+// The fuzzer input is passed through parsing twice,
+// so that presumably valid json is parsed/written again.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/json/string_escape.h"
+#include "base/logging.h"
+#include "base/values.h"
+
+// Entry point for libFuzzer.
+// We will use the last byte of data as parsing options.
+// The rest will be used as text input to the parser.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if (size < 2)
+ return 0;
+
+ int error_code, error_line, error_column;
+ std::string error_message;
+
+ // Create a copy of input buffer, as otherwise we don't catch
+ // overflow that touches the last byte (which is used in options).
+ std::unique_ptr<char[]> input(new char[size - 1]);
+ memcpy(input.get(), data, size - 1);
+
+ base::StringPiece input_string(input.get(), size - 1);
+
+ const int options = data[size - 1];
+ auto parsed_value = base::JSONReader::ReadAndReturnError(
+ input_string, options, &error_code, &error_message, &error_line,
+ &error_column);
+ if (!parsed_value)
+ return 0;
+
+ std::string parsed_output;
+ bool b = base::JSONWriter::Write(*parsed_value, &parsed_output);
+ LOG_ASSERT(b);
+
+ auto double_parsed_value = base::JSONReader::ReadAndReturnError(
+ parsed_output, options, &error_code, &error_message, &error_line,
+ &error_column);
+ LOG_ASSERT(double_parsed_value);
+ std::string double_parsed_output;
+ bool b2 =
+ base::JSONWriter::Write(*double_parsed_value, &double_parsed_output);
+ LOG_ASSERT(b2);
+
+ LOG_ASSERT(parsed_output == double_parsed_output)
+ << "Parser/Writer mismatch."
+ << "\nInput=" << base::GetQuotedJSONString(parsed_output)
+ << "\nOutput=" << base::GetQuotedJSONString(double_parsed_output);
+
+ return 0;
+}
diff --git a/base/json/json_file_value_serializer.cc b/base/json/json_file_value_serializer.cc
index 661d25d798..a7c68c59d5 100644
--- a/base/json/json_file_value_serializer.cc
+++ b/base/json/json_file_value_serializer.cc
@@ -21,8 +21,7 @@ JSONFileValueSerializer::JSONFileValueSerializer(
: json_file_path_(json_file_path) {
}
-JSONFileValueSerializer::~JSONFileValueSerializer() {
-}
+JSONFileValueSerializer::~JSONFileValueSerializer() = default;
bool JSONFileValueSerializer::Serialize(const base::Value& root) {
return SerializeInternal(root, false);
@@ -57,8 +56,7 @@ JSONFileValueDeserializer::JSONFileValueDeserializer(
int options)
: json_file_path_(json_file_path), options_(options), last_read_size_(0U) {}
-JSONFileValueDeserializer::~JSONFileValueDeserializer() {
-}
+JSONFileValueDeserializer::~JSONFileValueDeserializer() = default;
int JSONFileValueDeserializer::ReadFileToString(std::string* json_string) {
DCHECK(json_string);
@@ -109,7 +107,7 @@ std::unique_ptr<base::Value> JSONFileValueDeserializer::Deserialize(
*error_code = error;
if (error_str)
*error_str = GetErrorMessageForCode(error);
- return NULL;
+ return nullptr;
}
JSONStringValueDeserializer deserializer(json_string, options_);
diff --git a/base/json/json_parser.cc b/base/json/json_parser.cc
index c6f6409df6..dfe246c4fe 100644
--- a/base/json/json_parser.cc
+++ b/base/json/json_parser.cc
@@ -6,10 +6,11 @@
#include <cmath>
#include <utility>
+#include <vector>
#include "base/logging.h"
#include "base/macros.h"
-#include "base/memory/ptr_util.h"
+#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
@@ -24,43 +25,39 @@ namespace internal {
namespace {
-// Chosen to support 99.9% of documents found in the wild late 2016.
-// http://crbug.com/673263
-const int kStackMaxDepth = 200;
-
const int32_t kExtendedASCIIStart = 0x80;
// Simple class that checks for maximum recursion/"stack overflow."
class StackMarker {
public:
- explicit StackMarker(int* depth) : depth_(depth) {
+ StackMarker(int max_depth, int* depth)
+ : max_depth_(max_depth), depth_(depth) {
++(*depth_);
- DCHECK_LE(*depth_, kStackMaxDepth);
+ DCHECK_LE(*depth_, max_depth_);
}
~StackMarker() {
--(*depth_);
}
- bool IsTooDeep() const {
- return *depth_ >= kStackMaxDepth;
- }
+ bool IsTooDeep() const { return *depth_ >= max_depth_; }
private:
+ const int max_depth_;
int* const depth_;
DISALLOW_COPY_AND_ASSIGN(StackMarker);
};
+constexpr uint32_t kUnicodeReplacementPoint = 0xFFFD;
+
} // namespace
// This is U+FFFD.
const char kUnicodeReplacementString[] = "\xEF\xBF\xBD";
-JSONParser::JSONParser(int options)
+JSONParser::JSONParser(int options, int max_depth)
: options_(options),
- start_pos_(nullptr),
- pos_(nullptr),
- end_pos_(nullptr),
+ max_depth_(max_depth),
index_(0),
stack_depth_(0),
line_number_(0),
@@ -68,15 +65,13 @@ JSONParser::JSONParser(int options)
error_code_(JSONReader::JSON_NO_ERROR),
error_line_(0),
error_column_(0) {
+ CHECK_LE(max_depth, JSONReader::kStackMaxDepth);
}
-JSONParser::~JSONParser() {
-}
+JSONParser::~JSONParser() = default;
-std::unique_ptr<Value> JSONParser::Parse(StringPiece input) {
- start_pos_ = input.data();
- pos_ = start_pos_;
- end_pos_ = start_pos_ + input.length();
+Optional<Value> JSONParser::Parse(StringPiece input) {
+ input_ = input;
index_ = 0;
line_number_ = 1;
index_last_line_ = 0;
@@ -85,27 +80,27 @@ std::unique_ptr<Value> JSONParser::Parse(StringPiece input) {
error_line_ = 0;
error_column_ = 0;
- // When the input JSON string starts with a UTF-8 Byte-Order-Mark
- // <0xEF 0xBB 0xBF>, advance the start position to avoid the
- // ParseNextToken function mis-treating a Unicode BOM as an invalid
- // character and returning NULL.
- if (CanConsume(3) && static_cast<uint8_t>(*pos_) == 0xEF &&
- static_cast<uint8_t>(*(pos_ + 1)) == 0xBB &&
- static_cast<uint8_t>(*(pos_ + 2)) == 0xBF) {
- NextNChars(3);
+ // ICU and ReadUnicodeCharacter() use int32_t for lengths, so ensure
+ // that the index_ will not overflow when parsing.
+ if (!base::IsValueInRangeForNumericType<int32_t>(input.length())) {
+ ReportError(JSONReader::JSON_TOO_LARGE, 0);
+ return nullopt;
}
+ // When the input JSON string starts with a UTF-8 Byte-Order-Mark,
+ // advance the start position to avoid the ParseNextToken function mis-
+ // treating a Unicode BOM as an invalid character and returning NULL.
+ ConsumeIfMatch("\xEF\xBB\xBF");
+
// Parse the first and any nested tokens.
- std::unique_ptr<Value> root(ParseNextToken());
+ Optional<Value> root(ParseNextToken());
if (!root)
- return nullptr;
+ return nullopt;
// Make sure the input stream is at an end.
if (GetNextToken() != T_END_OF_INPUT) {
- if (!CanConsume(1) || (NextChar() && GetNextToken() != T_END_OF_INPUT)) {
- ReportError(JSONReader::JSON_UNEXPECTED_DATA_AFTER_ROOT, 1);
- return nullptr;
- }
+ ReportError(JSONReader::JSON_UNEXPECTED_DATA_AFTER_ROOT, 1);
+ return nullopt;
}
return root;
@@ -133,87 +128,85 @@ int JSONParser::error_column() const {
JSONParser::StringBuilder::StringBuilder() : StringBuilder(nullptr) {}
JSONParser::StringBuilder::StringBuilder(const char* pos)
- : pos_(pos), length_(0), has_string_(false) {}
+ : pos_(pos), length_(0) {}
-JSONParser::StringBuilder::~StringBuilder() {
- if (has_string_)
- string_.Destroy();
-}
+JSONParser::StringBuilder::~StringBuilder() = default;
-void JSONParser::StringBuilder::operator=(StringBuilder&& other) {
- pos_ = other.pos_;
- length_ = other.length_;
- has_string_ = other.has_string_;
- if (has_string_)
- string_.InitFromMove(std::move(other.string_));
-}
+JSONParser::StringBuilder& JSONParser::StringBuilder::operator=(
+ StringBuilder&& other) = default;
-void JSONParser::StringBuilder::Append(const char& c) {
- DCHECK_GE(c, 0);
- DCHECK_LT(static_cast<unsigned char>(c), 128);
+void JSONParser::StringBuilder::Append(uint32_t point) {
+ DCHECK(IsValidCharacter(point));
- if (has_string_)
- string_->push_back(c);
- else
+ if (point < kExtendedASCIIStart && !string_) {
+ DCHECK_EQ(static_cast<char>(point), pos_[length_]);
++length_;
-}
-
-void JSONParser::StringBuilder::AppendString(const char* str, size_t len) {
- DCHECK(has_string_);
- string_->append(str, len);
+ } else {
+ Convert();
+ if (UNLIKELY(point == kUnicodeReplacementPoint)) {
+ string_->append(kUnicodeReplacementString);
+ } else {
+ WriteUnicodeCharacter(point, &*string_);
+ }
+ }
}
void JSONParser::StringBuilder::Convert() {
- if (has_string_)
+ if (string_)
return;
-
- has_string_ = true;
- string_.Init(pos_, length_);
-}
-
-StringPiece JSONParser::StringBuilder::AsStringPiece() {
- if (has_string_)
- return StringPiece(*string_);
- return StringPiece(pos_, length_);
-}
-
-const std::string& JSONParser::StringBuilder::AsString() {
- if (!has_string_)
- Convert();
- return *string_;
+ string_.emplace(pos_, length_);
}
std::string JSONParser::StringBuilder::DestructiveAsString() {
- if (has_string_)
+ if (string_)
return std::move(*string_);
return std::string(pos_, length_);
}
// JSONParser private //////////////////////////////////////////////////////////
-inline bool JSONParser::CanConsume(int length) {
- return pos_ + length <= end_pos_;
+Optional<StringPiece> JSONParser::PeekChars(int count) {
+ if (static_cast<size_t>(index_) + count > input_.length())
+ return nullopt;
+ // Using StringPiece::substr() is significantly slower (according to
+ // base_perftests) than constructing a substring manually.
+ return StringPiece(input_.data() + index_, count);
}
-const char* JSONParser::NextChar() {
- DCHECK(CanConsume(1));
- ++index_;
- ++pos_;
- return pos_;
+Optional<char> JSONParser::PeekChar() {
+ Optional<StringPiece> chars = PeekChars(1);
+ if (chars)
+ return (*chars)[0];
+ return nullopt;
}
-void JSONParser::NextNChars(int n) {
- DCHECK(CanConsume(n));
- index_ += n;
- pos_ += n;
+Optional<StringPiece> JSONParser::ConsumeChars(int count) {
+ Optional<StringPiece> chars = PeekChars(count);
+ if (chars)
+ index_ += count;
+ return chars;
+}
+
+Optional<char> JSONParser::ConsumeChar() {
+ Optional<StringPiece> chars = ConsumeChars(1);
+ if (chars)
+ return (*chars)[0];
+ return nullopt;
+}
+
+const char* JSONParser::pos() {
+ CHECK_LE(static_cast<size_t>(index_), input_.length());
+ return input_.data() + index_;
}
JSONParser::Token JSONParser::GetNextToken() {
EatWhitespaceAndComments();
- if (!CanConsume(1))
+
+ Optional<char> c = PeekChar();
+ if (!c)
return T_END_OF_INPUT;
- switch (*pos_) {
+ switch (*c) {
case '{':
return T_OBJECT_BEGIN;
case '}':
@@ -252,18 +245,19 @@ JSONParser::Token JSONParser::GetNextToken() {
}
void JSONParser::EatWhitespaceAndComments() {
- while (pos_ < end_pos_) {
- switch (*pos_) {
+ while (Optional<char> c = PeekChar()) {
+ switch (*c) {
case '\r':
case '\n':
index_last_line_ = index_;
// Don't increment line_number_ twice for "\r\n".
- if (!(*pos_ == '\n' && pos_ > start_pos_ && *(pos_ - 1) == '\r'))
+ if (!(c == '\n' && index_ > 0 && input_[index_ - 1] == '\r')) {
++line_number_;
- // Fall through.
+ }
+ FALLTHROUGH;
case ' ':
case '\t':
- NextChar();
+ ConsumeChar();
break;
case '/':
if (!EatComment())
@@ -276,30 +270,29 @@ void JSONParser::EatWhitespaceAndComments() {
}
bool JSONParser::EatComment() {
- if (*pos_ != '/' || !CanConsume(1))
+ Optional<StringPiece> comment_start = ConsumeChars(2);
+ if (!comment_start)
return false;
- char next_char = *NextChar();
- if (next_char == '/') {
+ if (comment_start == "//") {
// Single line comment, read to newline.
- while (CanConsume(1)) {
- next_char = *NextChar();
- if (next_char == '\n' || next_char == '\r')
+ while (Optional<char> c = PeekChar()) {
+ if (c == '\n' || c == '\r')
return true;
+ ConsumeChar();
}
- } else if (next_char == '*') {
+ } else if (comment_start == "/*") {
char previous_char = '\0';
// Block comment, read until end marker.
- while (CanConsume(1)) {
- next_char = *NextChar();
- if (previous_char == '*' && next_char == '/') {
- // EatWhitespaceAndComments will inspect pos_, which will still be on
+ while (Optional<char> c = PeekChar()) {
+ if (previous_char == '*' && c == '/') {
+ // EatWhitespaceAndComments will inspect pos(), which will still be on
// the last / of the comment, so advance once more (which may also be
// end of input).
- NextChar();
+ ConsumeChar();
return true;
}
- previous_char = next_char;
+ previous_char = *ConsumeChar();
}
// If the comment is unterminated, GetNextToken will report T_END_OF_INPUT.
@@ -308,11 +301,11 @@ bool JSONParser::EatComment() {
return false;
}
-std::unique_ptr<Value> JSONParser::ParseNextToken() {
+Optional<Value> JSONParser::ParseNextToken() {
return ParseToken(GetNextToken());
}
-std::unique_ptr<Value> JSONParser::ParseToken(Token token) {
+Optional<Value> JSONParser::ParseToken(Token token) {
switch (token) {
case T_OBJECT_BEGIN:
return ConsumeDictionary();
@@ -328,127 +321,127 @@ std::unique_ptr<Value> JSONParser::ParseToken(Token token) {
return ConsumeLiteral();
default:
ReportError(JSONReader::JSON_UNEXPECTED_TOKEN, 1);
- return nullptr;
+ return nullopt;
}
}
-std::unique_ptr<Value> JSONParser::ConsumeDictionary() {
- if (*pos_ != '{') {
+Optional<Value> JSONParser::ConsumeDictionary() {
+ if (ConsumeChar() != '{') {
ReportError(JSONReader::JSON_UNEXPECTED_TOKEN, 1);
- return nullptr;
+ return nullopt;
}
- StackMarker depth_check(&stack_depth_);
+ StackMarker depth_check(max_depth_, &stack_depth_);
if (depth_check.IsTooDeep()) {
- ReportError(JSONReader::JSON_TOO_MUCH_NESTING, 1);
- return nullptr;
+ ReportError(JSONReader::JSON_TOO_MUCH_NESTING, 0);
+ return nullopt;
}
- std::unique_ptr<DictionaryValue> dict(new DictionaryValue);
+ std::vector<Value::DictStorage::value_type> dict_storage;
- NextChar();
Token token = GetNextToken();
while (token != T_OBJECT_END) {
if (token != T_STRING) {
ReportError(JSONReader::JSON_UNQUOTED_DICTIONARY_KEY, 1);
- return nullptr;
+ return nullopt;
}
// First consume the key.
StringBuilder key;
if (!ConsumeStringRaw(&key)) {
- return nullptr;
+ return nullopt;
}
// Read the separator.
- NextChar();
token = GetNextToken();
if (token != T_OBJECT_PAIR_SEPARATOR) {
ReportError(JSONReader::JSON_SYNTAX_ERROR, 1);
- return nullptr;
+ return nullopt;
}
// The next token is the value. Ownership transfers to |dict|.
- NextChar();
- std::unique_ptr<Value> value = ParseNextToken();
+ ConsumeChar();
+ Optional<Value> value = ParseNextToken();
if (!value) {
// ReportError from deeper level.
- return nullptr;
+ return nullopt;
}
- dict->SetWithoutPathExpansion(key.AsStringPiece(), std::move(value));
+ dict_storage.emplace_back(key.DestructiveAsString(),
+ std::make_unique<Value>(std::move(*value)));
- NextChar();
token = GetNextToken();
if (token == T_LIST_SEPARATOR) {
- NextChar();
+ ConsumeChar();
token = GetNextToken();
if (token == T_OBJECT_END && !(options_ & JSON_ALLOW_TRAILING_COMMAS)) {
ReportError(JSONReader::JSON_TRAILING_COMMA, 1);
- return nullptr;
+ return nullopt;
}
} else if (token != T_OBJECT_END) {
ReportError(JSONReader::JSON_SYNTAX_ERROR, 0);
- return nullptr;
+ return nullopt;
}
}
- return std::move(dict);
+ ConsumeChar(); // Closing '}'.
+
+ return Value(Value::DictStorage(std::move(dict_storage), KEEP_LAST_OF_DUPES));
}
-std::unique_ptr<Value> JSONParser::ConsumeList() {
- if (*pos_ != '[') {
+Optional<Value> JSONParser::ConsumeList() {
+ if (ConsumeChar() != '[') {
ReportError(JSONReader::JSON_UNEXPECTED_TOKEN, 1);
- return nullptr;
+ return nullopt;
}
- StackMarker depth_check(&stack_depth_);
+ StackMarker depth_check(max_depth_, &stack_depth_);
if (depth_check.IsTooDeep()) {
- ReportError(JSONReader::JSON_TOO_MUCH_NESTING, 1);
- return nullptr;
+ ReportError(JSONReader::JSON_TOO_MUCH_NESTING, 0);
+ return nullopt;
}
- std::unique_ptr<ListValue> list(new ListValue);
+ Value::ListStorage list_storage;
- NextChar();
Token token = GetNextToken();
while (token != T_ARRAY_END) {
- std::unique_ptr<Value> item = ParseToken(token);
+ Optional<Value> item = ParseToken(token);
if (!item) {
// ReportError from deeper level.
- return nullptr;
+ return nullopt;
}
- list->Append(std::move(item));
+ list_storage.push_back(std::move(*item));
- NextChar();
token = GetNextToken();
if (token == T_LIST_SEPARATOR) {
- NextChar();
+ ConsumeChar();
token = GetNextToken();
if (token == T_ARRAY_END && !(options_ & JSON_ALLOW_TRAILING_COMMAS)) {
ReportError(JSONReader::JSON_TRAILING_COMMA, 1);
- return nullptr;
+ return nullopt;
}
} else if (token != T_ARRAY_END) {
ReportError(JSONReader::JSON_SYNTAX_ERROR, 1);
- return nullptr;
+ return nullopt;
}
}
- return std::move(list);
+ ConsumeChar(); // Closing ']'.
+
+ return Value(std::move(list_storage));
}
-std::unique_ptr<Value> JSONParser::ConsumeString() {
+Optional<Value> JSONParser::ConsumeString() {
StringBuilder string;
if (!ConsumeStringRaw(&string))
- return nullptr;
+ return nullopt;
- return base::MakeUnique<Value>(string.DestructiveAsString());
+ return Value(string.DestructiveAsString());
}
bool JSONParser::ConsumeStringRaw(StringBuilder* out) {
- if (*pos_ != '"') {
+ if (ConsumeChar() != '"') {
ReportError(JSONReader::JSON_UNEXPECTED_TOKEN, 1);
return false;
}
@@ -456,39 +449,32 @@ bool JSONParser::ConsumeStringRaw(StringBuilder* out) {
// StringBuilder will internally build a StringPiece unless a UTF-16
// conversion occurs, at which point it will perform a copy into a
// std::string.
- StringBuilder string(NextChar());
-
- int length = end_pos_ - start_pos_;
- int32_t next_char = 0;
-
- while (CanConsume(1)) {
- int start_index = index_;
- pos_ = start_pos_ + index_; // CBU8_NEXT is postcrement.
- CBU8_NEXT(start_pos_, index_, length, next_char);
- if (next_char < 0 || !IsValidCharacter(next_char)) {
+ StringBuilder string(pos());
+
+ while (PeekChar()) {
+ uint32_t next_char = 0;
+ if (!ReadUnicodeCharacter(input_.data(),
+ static_cast<int32_t>(input_.length()),
+ &index_,
+ &next_char) ||
+ !IsValidCharacter(next_char)) {
if ((options_ & JSON_REPLACE_INVALID_CHARACTERS) == 0) {
ReportError(JSONReader::JSON_UNSUPPORTED_ENCODING, 1);
return false;
}
- CBU8_NEXT(start_pos_, start_index, length, next_char);
- string.Convert();
- string.AppendString(kUnicodeReplacementString,
- arraysize(kUnicodeReplacementString) - 1);
+ ConsumeChar();
+ string.Append(kUnicodeReplacementPoint);
continue;
}
if (next_char == '"') {
- --index_; // Rewind by one because of CBU8_NEXT.
+ ConsumeChar();
*out = std::move(string);
return true;
- }
-
- // If this character is not an escape sequence...
- if (next_char != '\\') {
- if (next_char < kExtendedASCIIStart)
- string.Append(static_cast<char>(next_char));
- else
- DecodeUTF8(next_char, &string);
+ } else if (next_char != '\\') {
+ // If this character is not an escape sequence...
+ ConsumeChar();
+ string.Append(next_char);
} else {
// And if it is an escape sequence, the input string will be adjusted
// (either by combining the two characters of an encoded escape sequence,
@@ -496,52 +482,42 @@ bool JSONParser::ConsumeStringRaw(StringBuilder* out) {
// a conversion.
string.Convert();
- if (!CanConsume(1)) {
+ // Read past the escape '\' and ensure there's a character following.
+ Optional<StringPiece> escape_sequence = ConsumeChars(2);
+ if (!escape_sequence) {
ReportError(JSONReader::JSON_INVALID_ESCAPE, 0);
return false;
}
- switch (*NextChar()) {
+ switch ((*escape_sequence)[1]) {
// Allowed esape sequences:
case 'x': { // UTF-8 sequence.
// UTF-8 \x escape sequences are not allowed in the spec, but they
// are supported here for backwards-compatiblity with the old parser.
- if (!CanConsume(2)) {
- ReportError(JSONReader::JSON_INVALID_ESCAPE, 1);
+ escape_sequence = ConsumeChars(2);
+ if (!escape_sequence) {
+ ReportError(JSONReader::JSON_INVALID_ESCAPE, -2);
return false;
}
int hex_digit = 0;
- if (!HexStringToInt(StringPiece(NextChar(), 2), &hex_digit) ||
+ if (!HexStringToInt(*escape_sequence, &hex_digit) ||
!IsValidCharacter(hex_digit)) {
- ReportError(JSONReader::JSON_INVALID_ESCAPE, -1);
+ ReportError(JSONReader::JSON_INVALID_ESCAPE, -2);
return false;
}
- NextChar();
- if (hex_digit < kExtendedASCIIStart)
- string.Append(static_cast<char>(hex_digit));
- else
- DecodeUTF8(hex_digit, &string);
+ string.Append(hex_digit);
break;
}
case 'u': { // UTF-16 sequence.
// UTF units are of the form \uXXXX.
- if (!CanConsume(5)) { // 5 being 'u' and four HEX digits.
+ uint32_t code_point;
+ if (!DecodeUTF16(&code_point)) {
ReportError(JSONReader::JSON_INVALID_ESCAPE, 0);
return false;
}
-
- // Skip the 'u'.
- NextChar();
-
- std::string utf8_units;
- if (!DecodeUTF16(&utf8_units)) {
- ReportError(JSONReader::JSON_INVALID_ESCAPE, -1);
- return false;
- }
-
- string.AppendString(utf8_units.data(), utf8_units.length());
+ string.Append(code_point);
break;
}
case '"':
@@ -584,28 +560,16 @@ bool JSONParser::ConsumeStringRaw(StringBuilder* out) {
}
// Entry is at the first X in \uXXXX.
-bool JSONParser::DecodeUTF16(std::string* dest_string) {
- if (!CanConsume(4))
+bool JSONParser::DecodeUTF16(uint32_t* out_code_point) {
+ Optional<StringPiece> escape_sequence = ConsumeChars(4);
+ if (!escape_sequence)
return false;
- // This is a 32-bit field because the shift operations in the
- // conversion process below cause MSVC to error about "data loss."
- // This only stores UTF-16 code units, though.
// Consume the UTF-16 code unit, which may be a high surrogate.
int code_unit16_high = 0;
- if (!HexStringToInt(StringPiece(pos_, 4), &code_unit16_high))
+ if (!HexStringToInt(*escape_sequence, &code_unit16_high))
return false;
- // Only add 3, not 4, because at the end of this iteration, the parser has
- // finished working with the last digit of the UTF sequence, meaning that
- // the next iteration will advance to the next byte.
- NextNChars(3);
-
- // Used to convert the UTF-16 code units to a code point and then to a UTF-8
- // code unit sequence.
- char code_unit8[8] = { 0 };
- size_t offset = 0;
-
// If this is a high surrogate, consume the next code unit to get the
// low surrogate.
if (CBU16_IS_SURROGATE(code_unit16_high)) {
@@ -616,97 +580,77 @@ bool JSONParser::DecodeUTF16(std::string* dest_string) {
// Make sure that the token has more characters to consume the
// lower surrogate.
- if (!CanConsume(6)) // 6 being '\' 'u' and four HEX digits.
+ if (!ConsumeIfMatch("\\u"))
return false;
- if (*NextChar() != '\\' || *NextChar() != 'u')
+
+ escape_sequence = ConsumeChars(4);
+ if (!escape_sequence)
return false;
- NextChar(); // Read past 'u'.
int code_unit16_low = 0;
- if (!HexStringToInt(StringPiece(pos_, 4), &code_unit16_low))
+ if (!HexStringToInt(*escape_sequence, &code_unit16_low))
return false;
- NextNChars(3);
-
- if (!CBU16_IS_TRAIL(code_unit16_low)) {
+ if (!CBU16_IS_TRAIL(code_unit16_low))
return false;
- }
uint32_t code_point =
CBU16_GET_SUPPLEMENTARY(code_unit16_high, code_unit16_low);
if (!IsValidCharacter(code_point))
return false;
- offset = 0;
- CBU8_APPEND_UNSAFE(code_unit8, offset, code_point);
+ *out_code_point = code_point;
} else {
// Not a surrogate.
DCHECK(CBU16_IS_SINGLE(code_unit16_high));
- if (!IsValidCharacter(code_unit16_high))
- return false;
+ if (!IsValidCharacter(code_unit16_high)) {
+ if ((options_ & JSON_REPLACE_INVALID_CHARACTERS) == 0) {
+ return false;
+ }
+ *out_code_point = kUnicodeReplacementPoint;
+ return true;
+ }
- CBU8_APPEND_UNSAFE(code_unit8, offset, code_unit16_high);
+ *out_code_point = code_unit16_high;
}
- dest_string->append(code_unit8);
return true;
}
-void JSONParser::DecodeUTF8(const int32_t& point, StringBuilder* dest) {
- DCHECK(IsValidCharacter(point));
-
- // Anything outside of the basic ASCII plane will need to be decoded from
- // int32_t to a multi-byte sequence.
- if (point < kExtendedASCIIStart) {
- dest->Append(static_cast<char>(point));
- } else {
- char utf8_units[4] = { 0 };
- int offset = 0;
- CBU8_APPEND_UNSAFE(utf8_units, offset, point);
- dest->Convert();
- // CBU8_APPEND_UNSAFE can overwrite up to 4 bytes, so utf8_units may not be
- // zero terminated at this point. |offset| contains the correct length.
- dest->AppendString(utf8_units, offset);
- }
-}
-
-std::unique_ptr<Value> JSONParser::ConsumeNumber() {
- const char* num_start = pos_;
+Optional<Value> JSONParser::ConsumeNumber() {
+ const char* num_start = pos();
const int start_index = index_;
int end_index = start_index;
- if (*pos_ == '-')
- NextChar();
+ if (PeekChar() == '-')
+ ConsumeChar();
if (!ReadInt(false)) {
ReportError(JSONReader::JSON_SYNTAX_ERROR, 1);
- return nullptr;
+ return nullopt;
}
end_index = index_;
// The optional fraction part.
- if (CanConsume(1) && *pos_ == '.') {
- NextChar();
+ if (PeekChar() == '.') {
+ ConsumeChar();
if (!ReadInt(true)) {
ReportError(JSONReader::JSON_SYNTAX_ERROR, 1);
- return nullptr;
+ return nullopt;
}
end_index = index_;
}
// Optional exponent part.
- if (CanConsume(1) && (*pos_ == 'e' || *pos_ == 'E')) {
- NextChar();
- if (!CanConsume(1)) {
- ReportError(JSONReader::JSON_SYNTAX_ERROR, 1);
- return nullptr;
- }
- if (*pos_ == '-' || *pos_ == '+') {
- NextChar();
+ Optional<char> c = PeekChar();
+ if (c == 'e' || c == 'E') {
+ ConsumeChar();
+ if (PeekChar() == '-' || PeekChar() == '+') {
+ ConsumeChar();
}
if (!ReadInt(true)) {
ReportError(JSONReader::JSON_SYNTAX_ERROR, 1);
- return nullptr;
+ return nullopt;
}
end_index = index_;
}
@@ -715,8 +659,7 @@ std::unique_ptr<Value> JSONParser::ConsumeNumber() {
// so save off where the parser should be on exit (see Consume invariant at
// the top of the header), then make sure the next token is one which is
// valid.
- const char* exit_pos = pos_ - 1;
- int exit_index = index_ - 1;
+ int exit_index = index_;
switch (GetNextToken()) {
case T_OBJECT_END:
@@ -726,40 +669,39 @@ std::unique_ptr<Value> JSONParser::ConsumeNumber() {
break;
default:
ReportError(JSONReader::JSON_SYNTAX_ERROR, 1);
- return nullptr;
+ return nullopt;
}
- pos_ = exit_pos;
index_ = exit_index;
StringPiece num_string(num_start, end_index - start_index);
int num_int;
if (StringToInt(num_string, &num_int))
- return base::MakeUnique<Value>(num_int);
+ return Value(num_int);
double num_double;
if (StringToDouble(num_string.as_string(), &num_double) &&
std::isfinite(num_double)) {
- return base::MakeUnique<Value>(num_double);
+ return Value(num_double);
}
- return nullptr;
+ return nullopt;
}
bool JSONParser::ReadInt(bool allow_leading_zeros) {
size_t len = 0;
char first = 0;
- while (CanConsume(1)) {
- if (!IsAsciiDigit(*pos_))
+ while (Optional<char> c = PeekChar()) {
+ if (!IsAsciiDigit(c))
break;
if (len == 0)
- first = *pos_;
+ first = *c;
++len;
- NextChar();
+ ConsumeChar();
}
if (len == 0)
@@ -771,50 +713,25 @@ bool JSONParser::ReadInt(bool allow_leading_zeros) {
return true;
}
-std::unique_ptr<Value> JSONParser::ConsumeLiteral() {
- switch (*pos_) {
- case 't': {
- const char kTrueLiteral[] = "true";
- const int kTrueLen = static_cast<int>(strlen(kTrueLiteral));
- if (!CanConsume(kTrueLen - 1) ||
- !StringsAreEqual(pos_, kTrueLiteral, kTrueLen)) {
- ReportError(JSONReader::JSON_SYNTAX_ERROR, 1);
- return nullptr;
- }
- NextNChars(kTrueLen - 1);
- return base::MakeUnique<Value>(true);
- }
- case 'f': {
- const char kFalseLiteral[] = "false";
- const int kFalseLen = static_cast<int>(strlen(kFalseLiteral));
- if (!CanConsume(kFalseLen - 1) ||
- !StringsAreEqual(pos_, kFalseLiteral, kFalseLen)) {
- ReportError(JSONReader::JSON_SYNTAX_ERROR, 1);
- return nullptr;
- }
- NextNChars(kFalseLen - 1);
- return base::MakeUnique<Value>(false);
- }
- case 'n': {
- const char kNullLiteral[] = "null";
- const int kNullLen = static_cast<int>(strlen(kNullLiteral));
- if (!CanConsume(kNullLen - 1) ||
- !StringsAreEqual(pos_, kNullLiteral, kNullLen)) {
- ReportError(JSONReader::JSON_SYNTAX_ERROR, 1);
- return nullptr;
- }
- NextNChars(kNullLen - 1);
- return Value::CreateNullValue();
- }
- default:
- ReportError(JSONReader::JSON_UNEXPECTED_TOKEN, 1);
- return nullptr;
+Optional<Value> JSONParser::ConsumeLiteral() {
+ if (ConsumeIfMatch("true")) {
+ return Value(true);
+ } else if (ConsumeIfMatch("false")) {
+ return Value(false);
+ } else if (ConsumeIfMatch("null")) {
+ return Value(Value::Type::NONE);
+ } else {
+ ReportError(JSONReader::JSON_SYNTAX_ERROR, 1);
+ return nullopt;
}
}
-// static
-bool JSONParser::StringsAreEqual(const char* one, const char* two, size_t len) {
- return strncmp(one, two, len) == 0;
+bool JSONParser::ConsumeIfMatch(StringPiece match) {
+ if (match == PeekChars(match.size())) {
+ ConsumeChars(match.size());
+ return true;
+ }
+ return false;
}
void JSONParser::ReportError(JSONReader::JsonParseError code,
diff --git a/base/json/json_parser.h b/base/json/json_parser.h
index 4f26458363..a4dd2ba365 100644
--- a/base/json/json_parser.h
+++ b/base/json/json_parser.h
@@ -16,7 +16,7 @@
#include "base/gtest_prod_util.h"
#include "base/json/json_reader.h"
#include "base/macros.h"
-#include "base/memory/manual_constructor.h"
+#include "base/optional.h"
#include "base/strings/string_piece.h"
namespace base {
@@ -30,31 +30,27 @@ class JSONParserTest;
// The implementation behind the JSONReader interface. This class is not meant
// to be used directly; it encapsulates logic that need not be exposed publicly.
//
-// This parser guarantees O(n) time through the input string. It also optimizes
-// base::Value by using StringPiece where possible when returning Value
-// objects by using "hidden roots," discussed in the implementation.
-//
-// Iteration happens on the byte level, with the functions CanConsume and
-// NextChar. The conversion from byte to JSON token happens without advancing
-// the parser in GetNextToken/ParseToken, that is tokenization operates on
-// the current parser position without advancing.
+// This parser guarantees O(n) time through the input string. Iteration happens
+// on the byte level, with the functions ConsumeChars() and ConsumeChar(). The
+// conversion from byte to JSON token happens without advancing the parser in
+// GetNextToken/ParseToken, that is tokenization operates on the current parser
+// position without advancing.
//
// Built on top of these are a family of Consume functions that iterate
// internally. Invariant: on entry of a Consume function, the parser is wound
-// to the first byte of a valid JSON token. On exit, it is on the last byte
-// of a token, such that the next iteration of the parser will be at the byte
-// immediately following the token, which would likely be the first byte of the
-// next token.
+// to the first byte of a valid JSON token. On exit, it is on the first byte
+// after the token that was just consumed, which would likely be the first byte
+// of the next token.
class BASE_EXPORT JSONParser {
public:
- explicit JSONParser(int options);
+ JSONParser(int options, int max_depth = JSONReader::kStackMaxDepth);
~JSONParser();
// Parses the input string according to the set options and returns the
// result as a Value.
// Wrap this in base::FooValue::From() to check the Value is of type Foo and
// convert to a FooValue at the same time.
- std::unique_ptr<Value> Parse(StringPiece input);
+ Optional<Value> Parse(StringPiece input);
// Returns the error code.
JSONReader::JsonParseError error_code() const;
@@ -102,28 +98,18 @@ class BASE_EXPORT JSONParser {
~StringBuilder();
- void operator=(StringBuilder&& other);
-
- // Either increases the |length_| of the string or copies the character if
- // the StringBuilder has been converted. |c| must be in the basic ASCII
- // plane; all other characters need to be in UTF-8 units, appended with
- // AppendString below.
- void Append(const char& c);
+ StringBuilder& operator=(StringBuilder&& other);
- // Appends a string to the std::string. Must be Convert()ed to use.
- void AppendString(const char* str, size_t len);
+ // Appends the Unicode code point |point| to the string, either by
+ // increasing the |length_| of the string if the string has not been
+ // converted, or by appending the UTF8 bytes for the code point.
+ void Append(uint32_t point);
// Converts the builder from its default StringPiece to a full std::string,
// performing a copy. Once a builder is converted, it cannot be made a
// StringPiece again.
void Convert();
- // Returns the builder as a StringPiece.
- StringPiece AsStringPiece();
-
- // Returns the builder as a std::string.
- const std::string& AsString();
-
// Returns the builder as a string, invalidating all state. This allows
// the internal string buffer representation to be destructively moved
// in cases where the builder will not be needed any more.
@@ -136,21 +122,27 @@ class BASE_EXPORT JSONParser {
// Number of bytes in |pos_| that make up the string being built.
size_t length_;
- // The copied string representation. Will be uninitialized until Convert()
- // is called, which will set has_string_ to true.
- bool has_string_;
- base::ManualConstructor<std::string> string_;
+ // The copied string representation. Will be unset until Convert() is
+ // called.
+ base::Optional<std::string> string_;
};
- // Quick check that the stream has capacity to consume |length| more bytes.
- bool CanConsume(int length);
+ // Returns the next |count| bytes of the input stream, or nullopt if fewer
+ // than |count| bytes remain.
+ Optional<StringPiece> PeekChars(int count);
- // The basic way to consume a single character in the stream. Consumes one
- // byte of the input stream and returns a pointer to the rest of it.
- const char* NextChar();
+ // Calls PeekChars() with a |count| of 1.
+ Optional<char> PeekChar();
- // Performs the equivalent of NextChar N times.
- void NextNChars(int n);
+ // Returns the next |count| bytes of the input stream, or nullopt if fewer
+ // than |count| bytes remain, and advances the parser position by |count|.
+ Optional<StringPiece> ConsumeChars(int count);
+
+ // Calls ConsumeChars() with a |count| of 1.
+ Optional<char> ConsumeChar();
+
+ // Returns a pointer to the current character position.
+ const char* pos();
// Skips over whitespace and comments to find the next token in the stream.
// This does not advance the parser for non-whitespace or comment chars.
@@ -164,22 +156,22 @@ class BASE_EXPORT JSONParser {
bool EatComment();
// Calls GetNextToken() and then ParseToken().
- std::unique_ptr<Value> ParseNextToken();
+ Optional<Value> ParseNextToken();
// Takes a token that represents the start of a Value ("a structural token"
// in RFC terms) and consumes it, returning the result as a Value.
- std::unique_ptr<Value> ParseToken(Token token);
+ Optional<Value> ParseToken(Token token);
// Assuming that the parser is currently wound to '{', this parses a JSON
- // object into a DictionaryValue.
- std::unique_ptr<Value> ConsumeDictionary();
+ // object into a Value.
+ Optional<Value> ConsumeDictionary();
// Assuming that the parser is wound to '[', this parses a JSON list into a
- // std::unique_ptr<ListValue>.
- std::unique_ptr<Value> ConsumeList();
+ // Value.
+ Optional<Value> ConsumeList();
// Calls through ConsumeStringRaw and wraps it in a value.
- std::unique_ptr<Value> ConsumeString();
+ Optional<Value> ConsumeString();
// Assuming that the parser is wound to a double quote, this parses a string,
// decoding any escape sequences and converts UTF-16 to UTF-8. Returns true on
@@ -189,27 +181,25 @@ class BASE_EXPORT JSONParser {
// Helper function for ConsumeStringRaw() that consumes the next four or 10
// bytes (parser is wound to the first character of a HEX sequence, with the
// potential for consuming another \uXXXX for a surrogate). Returns true on
- // success and places the UTF8 code units in |dest_string|, and false on
- // failure.
- bool DecodeUTF16(std::string* dest_string);
- // Helper function for ConsumeStringRaw() that takes a single code point,
- // decodes it into UTF-8 units, and appends it to the given builder. The
- // point must be valid.
- void DecodeUTF8(const int32_t& point, StringBuilder* dest);
+ // success and places the code point |out_code_point|, and false on failure.
+ bool DecodeUTF16(uint32_t* out_code_point);
// Assuming that the parser is wound to the start of a valid JSON number,
// this parses and converts it to either an int or double value.
- std::unique_ptr<Value> ConsumeNumber();
+ Optional<Value> ConsumeNumber();
// Helper that reads characters that are ints. Returns true if a number was
// read and false on error.
bool ReadInt(bool allow_leading_zeros);
// Consumes the literal values of |true|, |false|, and |null|, assuming the
// parser is wound to the first character of any of those.
- std::unique_ptr<Value> ConsumeLiteral();
+ Optional<Value> ConsumeLiteral();
- // Compares two string buffers of a given length.
- static bool StringsAreEqual(const char* left, const char* right, size_t len);
+ // Helper function that returns true if the byte squence |match| can be
+ // consumed at the current parser position. Returns false if there are fewer
+ // than |match|-length bytes or if the sequence does not match, and the
+ // parser state is unchanged.
+ bool ConsumeIfMatch(StringPiece match);
// Sets the error information to |code| at the current column, based on
// |index_| and |index_last_line_|, with an optional positive/negative
@@ -224,15 +214,11 @@ class BASE_EXPORT JSONParser {
// base::JSONParserOptions that control parsing.
const int options_;
- // Pointer to the start of the input data.
- const char* start_pos_;
-
- // Pointer to the current position in the input data. Equivalent to
- // |start_pos_ + index_|.
- const char* pos_;
+ // Maximum depth to parse.
+ const int max_depth_;
- // Pointer to the last character of the input data.
- const char* end_pos_;
+ // The input stream being parsed. Note: Not guaranteed to NUL-terminated.
+ StringPiece input_;
// The index in the input stream to which the parser is wound.
int index_;
@@ -260,6 +246,7 @@ class BASE_EXPORT JSONParser {
FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ConsumeNumbers);
FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ErrorMessages);
FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ReplaceInvalidCharacters);
+ FRIEND_TEST_ALL_PREFIXES(JSONParserTest, ReplaceInvalidUTF16EscapeSequence);
DISALLOW_COPY_AND_ASSIGN(JSONParser);
};
diff --git a/base/json/json_parser_unittest.cc b/base/json/json_parser_unittest.cc
index e3f635b76f..0da3db849e 100644
--- a/base/json/json_parser_unittest.cc
+++ b/base/json/json_parser_unittest.cc
@@ -10,6 +10,7 @@
#include "base/json/json_reader.h"
#include "base/memory/ptr_util.h"
+#include "base/optional.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -22,17 +23,29 @@ class JSONParserTest : public testing::Test {
JSONParser* NewTestParser(const std::string& input,
int options = JSON_PARSE_RFC) {
JSONParser* parser = new JSONParser(options);
- parser->start_pos_ = input.data();
- parser->pos_ = parser->start_pos_;
- parser->end_pos_ = parser->start_pos_ + input.length();
+ parser->input_ = input;
+ parser->index_ = 0;
return parser;
}
+ // MSan will do a better job detecting over-read errors if the input is
+ // not nul-terminated on the heap. This will copy |input| to a new buffer
+ // owned by |owner|, returning a StringPiece to |owner|.
+ StringPiece MakeNotNullTerminatedInput(const char* input,
+ std::unique_ptr<char[]>* owner) {
+ size_t str_len = strlen(input);
+ owner->reset(new char[str_len]);
+ memcpy(owner->get(), input, str_len);
+ return StringPiece(owner->get(), str_len);
+ }
+
void TestLastThree(JSONParser* parser) {
- EXPECT_EQ(',', *parser->NextChar());
- EXPECT_EQ('|', *parser->NextChar());
- EXPECT_EQ('\0', *parser->NextChar());
- EXPECT_EQ(parser->end_pos_, parser->pos_);
+ EXPECT_EQ(',', *parser->PeekChar());
+ parser->ConsumeChar();
+ EXPECT_EQ('|', *parser->PeekChar());
+ parser->ConsumeChar();
+ EXPECT_EQ('\0', *parser->pos());
+ EXPECT_EQ(static_cast<size_t>(parser->index_), parser->input_.length());
}
};
@@ -40,22 +53,25 @@ TEST_F(JSONParserTest, NextChar) {
std::string input("Hello world");
std::unique_ptr<JSONParser> parser(NewTestParser(input));
- EXPECT_EQ('H', *parser->pos_);
+ EXPECT_EQ('H', *parser->pos());
for (size_t i = 1; i < input.length(); ++i) {
- EXPECT_EQ(input[i], *parser->NextChar());
+ parser->ConsumeChar();
+ EXPECT_EQ(input[i], *parser->PeekChar());
}
- EXPECT_EQ(parser->end_pos_, parser->NextChar());
+ parser->ConsumeChar();
+ EXPECT_EQ('\0', *parser->pos());
+ EXPECT_EQ(static_cast<size_t>(parser->index_), parser->input_.length());
}
TEST_F(JSONParserTest, ConsumeString) {
std::string input("\"test\",|");
std::unique_ptr<JSONParser> parser(NewTestParser(input));
- std::unique_ptr<Value> value(parser->ConsumeString());
- EXPECT_EQ('"', *parser->pos_);
+ Optional<Value> value(parser->ConsumeString());
+ EXPECT_EQ(',', *parser->pos());
TestLastThree(parser.get());
- ASSERT_TRUE(value.get());
+ ASSERT_TRUE(value);
std::string str;
EXPECT_TRUE(value->GetAsString(&str));
EXPECT_EQ("test", str);
@@ -64,12 +80,12 @@ TEST_F(JSONParserTest, ConsumeString) {
TEST_F(JSONParserTest, ConsumeList) {
std::string input("[true, false],|");
std::unique_ptr<JSONParser> parser(NewTestParser(input));
- std::unique_ptr<Value> value(parser->ConsumeList());
- EXPECT_EQ(']', *parser->pos_);
+ Optional<Value> value(parser->ConsumeList());
+ EXPECT_EQ(',', *parser->pos());
TestLastThree(parser.get());
- ASSERT_TRUE(value.get());
+ ASSERT_TRUE(value);
base::ListValue* list;
EXPECT_TRUE(value->GetAsList(&list));
EXPECT_EQ(2u, list->GetSize());
@@ -78,12 +94,12 @@ TEST_F(JSONParserTest, ConsumeList) {
TEST_F(JSONParserTest, ConsumeDictionary) {
std::string input("{\"abc\":\"def\"},|");
std::unique_ptr<JSONParser> parser(NewTestParser(input));
- std::unique_ptr<Value> value(parser->ConsumeDictionary());
- EXPECT_EQ('}', *parser->pos_);
+ Optional<Value> value(parser->ConsumeDictionary());
+ EXPECT_EQ(',', *parser->pos());
TestLastThree(parser.get());
- ASSERT_TRUE(value.get());
+ ASSERT_TRUE(value);
base::DictionaryValue* dict;
EXPECT_TRUE(value->GetAsDictionary(&dict));
std::string str;
@@ -95,12 +111,12 @@ TEST_F(JSONParserTest, ConsumeLiterals) {
// Literal |true|.
std::string input("true,|");
std::unique_ptr<JSONParser> parser(NewTestParser(input));
- std::unique_ptr<Value> value(parser->ConsumeLiteral());
- EXPECT_EQ('e', *parser->pos_);
+ Optional<Value> value(parser->ConsumeLiteral());
+ EXPECT_EQ(',', *parser->pos());
TestLastThree(parser.get());
- ASSERT_TRUE(value.get());
+ ASSERT_TRUE(value);
bool bool_value = false;
EXPECT_TRUE(value->GetAsBoolean(&bool_value));
EXPECT_TRUE(bool_value);
@@ -109,11 +125,11 @@ TEST_F(JSONParserTest, ConsumeLiterals) {
input = "false,|";
parser.reset(NewTestParser(input));
value = parser->ConsumeLiteral();
- EXPECT_EQ('e', *parser->pos_);
+ EXPECT_EQ(',', *parser->pos());
TestLastThree(parser.get());
- ASSERT_TRUE(value.get());
+ ASSERT_TRUE(value);
EXPECT_TRUE(value->GetAsBoolean(&bool_value));
EXPECT_FALSE(bool_value);
@@ -121,24 +137,24 @@ TEST_F(JSONParserTest, ConsumeLiterals) {
input = "null,|";
parser.reset(NewTestParser(input));
value = parser->ConsumeLiteral();
- EXPECT_EQ('l', *parser->pos_);
+ EXPECT_EQ(',', *parser->pos());
TestLastThree(parser.get());
- ASSERT_TRUE(value.get());
- EXPECT_TRUE(value->IsType(Value::Type::NONE));
+ ASSERT_TRUE(value);
+ EXPECT_TRUE(value->is_none());
}
TEST_F(JSONParserTest, ConsumeNumbers) {
// Integer.
std::string input("1234,|");
std::unique_ptr<JSONParser> parser(NewTestParser(input));
- std::unique_ptr<Value> value(parser->ConsumeNumber());
- EXPECT_EQ('4', *parser->pos_);
+ Optional<Value> value(parser->ConsumeNumber());
+ EXPECT_EQ(',', *parser->pos());
TestLastThree(parser.get());
- ASSERT_TRUE(value.get());
+ ASSERT_TRUE(value);
int number_i;
EXPECT_TRUE(value->GetAsInteger(&number_i));
EXPECT_EQ(1234, number_i);
@@ -147,11 +163,11 @@ TEST_F(JSONParserTest, ConsumeNumbers) {
input = "-1234,|";
parser.reset(NewTestParser(input));
value = parser->ConsumeNumber();
- EXPECT_EQ('4', *parser->pos_);
+ EXPECT_EQ(',', *parser->pos());
TestLastThree(parser.get());
- ASSERT_TRUE(value.get());
+ ASSERT_TRUE(value);
EXPECT_TRUE(value->GetAsInteger(&number_i));
EXPECT_EQ(-1234, number_i);
@@ -159,11 +175,11 @@ TEST_F(JSONParserTest, ConsumeNumbers) {
input = "12.34,|";
parser.reset(NewTestParser(input));
value = parser->ConsumeNumber();
- EXPECT_EQ('4', *parser->pos_);
+ EXPECT_EQ(',', *parser->pos());
TestLastThree(parser.get());
- ASSERT_TRUE(value.get());
+ ASSERT_TRUE(value);
double number_d;
EXPECT_TRUE(value->GetAsDouble(&number_d));
EXPECT_EQ(12.34, number_d);
@@ -172,11 +188,11 @@ TEST_F(JSONParserTest, ConsumeNumbers) {
input = "42e3,|";
parser.reset(NewTestParser(input));
value = parser->ConsumeNumber();
- EXPECT_EQ('3', *parser->pos_);
+ EXPECT_EQ(',', *parser->pos());
TestLastThree(parser.get());
- ASSERT_TRUE(value.get());
+ ASSERT_TRUE(value);
EXPECT_TRUE(value->GetAsDouble(&number_d));
EXPECT_EQ(42000, number_d);
@@ -184,11 +200,11 @@ TEST_F(JSONParserTest, ConsumeNumbers) {
input = "314159e-5,|";
parser.reset(NewTestParser(input));
value = parser->ConsumeNumber();
- EXPECT_EQ('5', *parser->pos_);
+ EXPECT_EQ(',', *parser->pos());
TestLastThree(parser.get());
- ASSERT_TRUE(value.get());
+ ASSERT_TRUE(value);
EXPECT_TRUE(value->GetAsDouble(&number_d));
EXPECT_EQ(3.14159, number_d);
@@ -196,11 +212,11 @@ TEST_F(JSONParserTest, ConsumeNumbers) {
input = "0.42e+3,|";
parser.reset(NewTestParser(input));
value = parser->ConsumeNumber();
- EXPECT_EQ('3', *parser->pos_);
+ EXPECT_EQ(',', *parser->pos());
TestLastThree(parser.get());
- ASSERT_TRUE(value.get());
+ ASSERT_TRUE(value);
EXPECT_TRUE(value->GetAsDouble(&number_d));
EXPECT_EQ(420, number_d);
}
@@ -304,6 +320,12 @@ TEST_F(JSONParserTest, ErrorMessages) {
EXPECT_EQ(JSONParser::FormatErrorMessage(1, 7, JSONReader::kInvalidEscape),
error_message);
EXPECT_EQ(JSONReader::JSON_INVALID_ESCAPE, error_code);
+
+ root = JSONReader::ReadAndReturnError(("[\"\\ufffe\"]"), JSON_PARSE_RFC,
+ &error_code, &error_message);
+ EXPECT_EQ(JSONParser::FormatErrorMessage(1, 8, JSONReader::kInvalidEscape),
+ error_message);
+ EXPECT_EQ(JSONReader::JSON_INVALID_ESCAPE, error_code);
}
TEST_F(JSONParserTest, Decode4ByteUtf8Char) {
@@ -324,6 +346,11 @@ TEST_F(JSONParserTest, DecodeUnicodeNonCharacter) {
EXPECT_FALSE(JSONReader::Read("[\"\\ufdd0\"]"));
EXPECT_FALSE(JSONReader::Read("[\"\\ufffe\"]"));
EXPECT_FALSE(JSONReader::Read("[\"\\ud83f\\udffe\"]"));
+
+ EXPECT_TRUE(
+ JSONReader::Read("[\"\\ufdd0\"]", JSON_REPLACE_INVALID_CHARACTERS));
+ EXPECT_TRUE(
+ JSONReader::Read("[\"\\ufffe\"]", JSON_REPLACE_INVALID_CHARACTERS));
}
TEST_F(JSONParserTest, DecodeNegativeEscapeSequence) {
@@ -337,8 +364,19 @@ TEST_F(JSONParserTest, ReplaceInvalidCharacters) {
const std::string quoted_bogus_char = "\"" + bogus_char + "\"";
std::unique_ptr<JSONParser> parser(
NewTestParser(quoted_bogus_char, JSON_REPLACE_INVALID_CHARACTERS));
- std::unique_ptr<Value> value(parser->ConsumeString());
- ASSERT_TRUE(value.get());
+ Optional<Value> value(parser->ConsumeString());
+ ASSERT_TRUE(value);
+ std::string str;
+ EXPECT_TRUE(value->GetAsString(&str));
+ EXPECT_EQ(kUnicodeReplacementString, str);
+}
+
+TEST_F(JSONParserTest, ReplaceInvalidUTF16EscapeSequence) {
+ const std::string invalid = "\"\\ufffe\"";
+ std::unique_ptr<JSONParser> parser(
+ NewTestParser(invalid, JSON_REPLACE_INVALID_CHARACTERS));
+ Optional<Value> value(parser->ConsumeString());
+ ASSERT_TRUE(value);
std::string str;
EXPECT_TRUE(value->GetAsString(&str));
EXPECT_EQ(kUnicodeReplacementString, str);
@@ -367,14 +405,11 @@ TEST_F(JSONParserTest, ParseNumberErrors) {
auto test_case = kCases[i];
SCOPED_TRACE(StringPrintf("case %u: \"%s\"", i, test_case.input));
- // MSan will do a better job detecting over-read errors if the input is
- // not nul-terminated on the heap.
- size_t str_len = strlen(test_case.input);
- auto non_nul_termianted = MakeUnique<char[]>(str_len);
- memcpy(non_nul_termianted.get(), test_case.input, str_len);
+ std::unique_ptr<char[]> input_owner;
+ StringPiece input =
+ MakeNotNullTerminatedInput(test_case.input, &input_owner);
- StringPiece string_piece(non_nul_termianted.get(), str_len);
- std::unique_ptr<Value> result = JSONReader::Read(string_piece);
+ std::unique_ptr<Value> result = JSONReader::Read(input);
if (test_case.parse_success) {
EXPECT_TRUE(result);
} else {
@@ -390,5 +425,38 @@ TEST_F(JSONParserTest, ParseNumberErrors) {
}
}
+TEST_F(JSONParserTest, UnterminatedInputs) {
+ const char* kCases[] = {
+ // clang-format off
+ "/",
+ "//",
+ "/*",
+ "\"xxxxxx",
+ "\"",
+ "{ ",
+ "[\t",
+ "tru",
+ "fals",
+ "nul",
+ "\"\\x",
+ "\"\\x2",
+ "\"\\u123",
+ "\"\\uD803\\u",
+ "\"\\",
+ "\"\\/",
+ // clang-format on
+ };
+
+ for (unsigned int i = 0; i < arraysize(kCases); ++i) {
+ auto* test_case = kCases[i];
+ SCOPED_TRACE(StringPrintf("case %u: \"%s\"", i, test_case));
+
+ std::unique_ptr<char[]> input_owner;
+ StringPiece input = MakeNotNullTerminatedInput(test_case, &input_owner);
+
+ EXPECT_FALSE(JSONReader::Read(input));
+ }
+}
+
} // namespace internal
} // namespace base
diff --git a/base/json/json_reader.cc b/base/json/json_reader.cc
index 4ff7496bbb..bf2a18a5e5 100644
--- a/base/json/json_reader.cc
+++ b/base/json/json_reader.cc
@@ -4,12 +4,20 @@
#include "base/json/json_reader.h"
+#include <utility>
+#include <vector>
+
#include "base/json/json_parser.h"
#include "base/logging.h"
+#include "base/optional.h"
#include "base/values.h"
namespace base {
+// Chosen to support 99.9% of documents found in the wild late 2016.
+// http://crbug.com/673263
+const int JSONReader::kStackMaxDepth = 200;
+
// Values 1000 and above are used by JSONFileValueSerializer::JsonFileError.
static_assert(JSONReader::JSON_PARSE_ERROR_COUNT < 1000,
"JSONReader error out of bounds");
@@ -30,41 +38,34 @@ const char JSONReader::kUnsupportedEncoding[] =
"Unsupported encoding. JSON must be UTF-8.";
const char JSONReader::kUnquotedDictionaryKey[] =
"Dictionary keys must be quoted.";
+const char JSONReader::kInputTooLarge[] =
+ "Input string is too large (>2GB).";
-JSONReader::JSONReader()
- : JSONReader(JSON_PARSE_RFC) {
-}
-
-JSONReader::JSONReader(int options)
- : parser_(new internal::JSONParser(options)) {
-}
+JSONReader::JSONReader(int options, int max_depth)
+ : parser_(new internal::JSONParser(options, max_depth)) {}
-JSONReader::~JSONReader() {
-}
+JSONReader::~JSONReader() = default;
// static
-std::unique_ptr<Value> JSONReader::Read(StringPiece json) {
- internal::JSONParser parser(JSON_PARSE_RFC);
- return parser.Parse(json);
-}
-
-// static
-std::unique_ptr<Value> JSONReader::Read(StringPiece json, int options) {
- internal::JSONParser parser(options);
- return parser.Parse(json);
+std::unique_ptr<Value> JSONReader::Read(StringPiece json,
+ int options,
+ int max_depth) {
+ internal::JSONParser parser(options, max_depth);
+ Optional<Value> root = parser.Parse(json);
+ return root ? std::make_unique<Value>(std::move(*root)) : nullptr;
}
// static
std::unique_ptr<Value> JSONReader::ReadAndReturnError(
- const StringPiece& json,
+ StringPiece json,
int options,
int* error_code_out,
std::string* error_msg_out,
int* error_line_out,
int* error_column_out) {
internal::JSONParser parser(options);
- std::unique_ptr<Value> root(parser.Parse(json));
+ Optional<Value> root = parser.Parse(json);
if (!root) {
if (error_code_out)
*error_code_out = parser.error_code();
@@ -76,7 +77,7 @@ std::unique_ptr<Value> JSONReader::ReadAndReturnError(
*error_column_out = parser.error_column();
}
- return root;
+ return root ? std::make_unique<Value>(std::move(*root)) : nullptr;
}
// static
@@ -100,14 +101,18 @@ std::string JSONReader::ErrorCodeToString(JsonParseError error_code) {
return kUnsupportedEncoding;
case JSON_UNQUOTED_DICTIONARY_KEY:
return kUnquotedDictionaryKey;
- default:
- NOTREACHED();
- return std::string();
+ case JSON_TOO_LARGE:
+ return kInputTooLarge;
+ case JSON_PARSE_ERROR_COUNT:
+ break;
}
+ NOTREACHED();
+ return std::string();
}
std::unique_ptr<Value> JSONReader::ReadToValue(StringPiece json) {
- return parser_->Parse(json);
+ Optional<Value> value = parser_->Parse(json);
+ return value ? std::make_unique<Value>(std::move(*value)) : nullptr;
}
JSONReader::JsonParseError JSONReader::error_code() const {
diff --git a/base/json/json_reader.h b/base/json/json_reader.h
index a39b37adeb..2c6bd3e479 100644
--- a/base/json/json_reader.h
+++ b/base/json/json_reader.h
@@ -50,20 +50,16 @@ enum JSONParserOptions {
// Allows commas to exist after the last element in structures.
JSON_ALLOW_TRAILING_COMMAS = 1 << 0,
- // The parser can perform optimizations by placing hidden data in the root of
- // the JSON object, which speeds up certain operations on children. However,
- // if the child is Remove()d from root, it would result in use-after-free
- // unless it is DeepCopy()ed or this option is used.
- JSON_DETACHABLE_CHILDREN = 1 << 1,
-
// If set the parser replaces invalid characters with the Unicode replacement
// character (U+FFFD). If not set, invalid characters trigger a hard error and
// parsing fails.
- JSON_REPLACE_INVALID_CHARACTERS = 1 << 2,
+ JSON_REPLACE_INVALID_CHARACTERS = 1 << 1,
};
class BASE_EXPORT JSONReader {
public:
+ static const int kStackMaxDepth;
+
// Error codes during parsing.
enum JsonParseError {
JSON_NO_ERROR = 0,
@@ -75,6 +71,7 @@ class BASE_EXPORT JSONReader {
JSON_UNEXPECTED_DATA_AFTER_ROOT,
JSON_UNSUPPORTED_ENCODING,
JSON_UNQUOTED_DICTIONARY_KEY,
+ JSON_TOO_LARGE,
JSON_PARSE_ERROR_COUNT
};
@@ -87,12 +84,10 @@ class BASE_EXPORT JSONReader {
static const char kUnexpectedDataAfterRoot[];
static const char kUnsupportedEncoding[];
static const char kUnquotedDictionaryKey[];
+ static const char kInputTooLarge[];
- // Constructs a reader with the default options, JSON_PARSE_RFC.
- JSONReader();
-
- // Constructs a reader with custom options.
- explicit JSONReader(int options);
+ // Constructs a reader.
+ JSONReader(int options = JSON_PARSE_RFC, int max_depth = kStackMaxDepth);
~JSONReader();
@@ -100,17 +95,16 @@ class BASE_EXPORT JSONReader {
// If |json| is not a properly formed JSON string, returns nullptr.
// Wrap this in base::FooValue::From() to check the Value is of type Foo and
// convert to a FooValue at the same time.
- static std::unique_ptr<Value> Read(StringPiece json);
-
- // Same as Read() above, but the parser respects the given |options|.
- static std::unique_ptr<Value> Read(StringPiece json, int options);
+ static std::unique_ptr<Value> Read(StringPiece json,
+ int options = JSON_PARSE_RFC,
+ int max_depth = kStackMaxDepth);
// Reads and parses |json| like Read(). |error_code_out| and |error_msg_out|
// are optional. If specified and nullptr is returned, they will be populated
// an error code and a formatted error message (including error location if
// appropriate). Otherwise, they will be unmodified.
static std::unique_ptr<Value> ReadAndReturnError(
- const StringPiece& json,
+ StringPiece json,
int options, // JSONParserOptions
int* error_code_out,
std::string* error_msg_out,
diff --git a/base/json/json_reader_fuzzer.cc b/base/json/json_reader_fuzzer.cc
new file mode 100644
index 0000000000..5e69940e67
--- /dev/null
+++ b/base/json/json_reader_fuzzer.cc
@@ -0,0 +1,29 @@
+// 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/json/json_reader.h"
+#include "base/values.h"
+
+// Entry point for LibFuzzer.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if (size < 2)
+ return 0;
+
+ // Create a copy of input buffer, as otherwise we don't catch
+ // overflow that touches the last byte (which is used in options).
+ std::unique_ptr<char[]> input(new char[size - 1]);
+ memcpy(input.get(), data, size - 1);
+
+ base::StringPiece input_string(input.get(), size - 1);
+
+ const int options = data[size - 1];
+
+ int error_code, error_line, error_column;
+ std::string error_message;
+ base::JSONReader::ReadAndReturnError(input_string, options, &error_code,
+ &error_message, &error_line,
+ &error_column);
+
+ return 0;
+}
diff --git a/base/json/json_reader_unittest.cc b/base/json/json_reader_unittest.cc
index f645b427fc..2b8417c1e8 100644
--- a/base/json/json_reader_unittest.cc
+++ b/base/json/json_reader_unittest.cc
@@ -21,550 +21,528 @@
namespace base {
-TEST(JSONReaderTest, Reading) {
- {
- // some whitespace checking
- std::unique_ptr<Value> root = JSONReader().ReadToValue(" null ");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::NONE));
- }
-
- {
- // Invalid JSON string
- EXPECT_FALSE(JSONReader().ReadToValue("nu"));
- }
-
- {
- // Simple bool
- std::unique_ptr<Value> root = JSONReader().ReadToValue("true ");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::BOOLEAN));
- }
-
- {
- // Embedded comment
- std::unique_ptr<Value> root = JSONReader().ReadToValue("/* comment */null");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::NONE));
- root = JSONReader().ReadToValue("40 /* comment */");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::INTEGER));
- root = JSONReader().ReadToValue("true // comment");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::BOOLEAN));
- root = JSONReader().ReadToValue("/* comment */\"sample string\"");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::STRING));
- std::string value;
- EXPECT_TRUE(root->GetAsString(&value));
- EXPECT_EQ("sample string", value);
- std::unique_ptr<ListValue> list = ListValue::From(
- JSONReader().ReadToValue("[1, /* comment, 2 ] */ \n 3]"));
- ASSERT_TRUE(list);
- EXPECT_EQ(2u, list->GetSize());
- int int_val = 0;
- EXPECT_TRUE(list->GetInteger(0, &int_val));
- EXPECT_EQ(1, int_val);
- EXPECT_TRUE(list->GetInteger(1, &int_val));
- EXPECT_EQ(3, int_val);
- list = ListValue::From(JSONReader().ReadToValue("[1, /*a*/2, 3]"));
- ASSERT_TRUE(list);
- EXPECT_EQ(3u, list->GetSize());
- root = JSONReader().ReadToValue("/* comment **/42");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::INTEGER));
- EXPECT_TRUE(root->GetAsInteger(&int_val));
- EXPECT_EQ(42, int_val);
- root = JSONReader().ReadToValue(
- "/* comment **/\n"
- "// */ 43\n"
- "44");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::INTEGER));
- EXPECT_TRUE(root->GetAsInteger(&int_val));
- EXPECT_EQ(44, int_val);
- }
-
- {
- // Test number formats
- std::unique_ptr<Value> root = JSONReader().ReadToValue("43");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::INTEGER));
- int int_val = 0;
- EXPECT_TRUE(root->GetAsInteger(&int_val));
- EXPECT_EQ(43, int_val);
- }
+TEST(JSONReaderTest, Whitespace) {
+ std::unique_ptr<Value> root = JSONReader().ReadToValue(" null ");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_none());
+}
- {
- // According to RFC4627, oct, hex, and leading zeros are invalid JSON.
- EXPECT_FALSE(JSONReader().ReadToValue("043"));
- EXPECT_FALSE(JSONReader().ReadToValue("0x43"));
- EXPECT_FALSE(JSONReader().ReadToValue("00"));
- }
+TEST(JSONReaderTest, InvalidString) {
+ EXPECT_FALSE(JSONReader().ReadToValue("nu"));
+}
- {
- // Test 0 (which needs to be special cased because of the leading zero
- // clause).
- std::unique_ptr<Value> root = JSONReader().ReadToValue("0");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::INTEGER));
- int int_val = 1;
- EXPECT_TRUE(root->GetAsInteger(&int_val));
- EXPECT_EQ(0, int_val);
- }
+TEST(JSONReaderTest, SimpleBool) {
+ std::unique_ptr<Value> root = JSONReader().ReadToValue("true ");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_bool());
+}
- {
- // Numbers that overflow ints should succeed, being internally promoted to
- // storage as doubles
- std::unique_ptr<Value> root = JSONReader().ReadToValue("2147483648");
- ASSERT_TRUE(root);
- double double_val;
- EXPECT_TRUE(root->IsType(Value::Type::DOUBLE));
- double_val = 0.0;
- EXPECT_TRUE(root->GetAsDouble(&double_val));
- EXPECT_DOUBLE_EQ(2147483648.0, double_val);
- root = JSONReader().ReadToValue("-2147483649");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::DOUBLE));
- double_val = 0.0;
- EXPECT_TRUE(root->GetAsDouble(&double_val));
- EXPECT_DOUBLE_EQ(-2147483649.0, double_val);
- }
+TEST(JSONReaderTest, EmbeddedComments) {
+ std::unique_ptr<Value> root = JSONReader().ReadToValue("/* comment */null");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_none());
+ root = JSONReader().ReadToValue("40 /* comment */");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_int());
+ root = JSONReader().ReadToValue("true // comment");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_bool());
+ root = JSONReader().ReadToValue("/* comment */\"sample string\"");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_string());
+ std::string value;
+ EXPECT_TRUE(root->GetAsString(&value));
+ EXPECT_EQ("sample string", value);
+ std::unique_ptr<ListValue> list =
+ ListValue::From(JSONReader().ReadToValue("[1, /* comment, 2 ] */ \n 3]"));
+ ASSERT_TRUE(list);
+ EXPECT_EQ(2u, list->GetSize());
+ int int_val = 0;
+ EXPECT_TRUE(list->GetInteger(0, &int_val));
+ EXPECT_EQ(1, int_val);
+ EXPECT_TRUE(list->GetInteger(1, &int_val));
+ EXPECT_EQ(3, int_val);
+ list = ListValue::From(JSONReader().ReadToValue("[1, /*a*/2, 3]"));
+ ASSERT_TRUE(list);
+ EXPECT_EQ(3u, list->GetSize());
+ root = JSONReader().ReadToValue("/* comment **/42");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_int());
+ EXPECT_TRUE(root->GetAsInteger(&int_val));
+ EXPECT_EQ(42, int_val);
+ root = JSONReader().ReadToValue(
+ "/* comment **/\n"
+ "// */ 43\n"
+ "44");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_int());
+ EXPECT_TRUE(root->GetAsInteger(&int_val));
+ EXPECT_EQ(44, int_val);
+}
- {
- // Parse a double
- std::unique_ptr<Value> root = JSONReader().ReadToValue("43.1");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::DOUBLE));
- double double_val = 0.0;
- EXPECT_TRUE(root->GetAsDouble(&double_val));
- EXPECT_DOUBLE_EQ(43.1, double_val);
+TEST(JSONReaderTest, Ints) {
+ std::unique_ptr<Value> root = JSONReader().ReadToValue("43");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_int());
+ int int_val = 0;
+ EXPECT_TRUE(root->GetAsInteger(&int_val));
+ EXPECT_EQ(43, int_val);
+}
- root = JSONReader().ReadToValue("4.3e-1");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::DOUBLE));
- double_val = 0.0;
- EXPECT_TRUE(root->GetAsDouble(&double_val));
- EXPECT_DOUBLE_EQ(.43, double_val);
+TEST(JSONReaderTest, NonDecimalNumbers) {
+ // According to RFC4627, oct, hex, and leading zeros are invalid JSON.
+ EXPECT_FALSE(JSONReader().ReadToValue("043"));
+ EXPECT_FALSE(JSONReader().ReadToValue("0x43"));
+ EXPECT_FALSE(JSONReader().ReadToValue("00"));
+}
- root = JSONReader().ReadToValue("2.1e0");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::DOUBLE));
- double_val = 0.0;
- EXPECT_TRUE(root->GetAsDouble(&double_val));
- EXPECT_DOUBLE_EQ(2.1, double_val);
+TEST(JSONReaderTest, NumberZero) {
+ // Test 0 (which needs to be special cased because of the leading zero
+ // clause).
+ std::unique_ptr<Value> root = JSONReader().ReadToValue("0");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_int());
+ int int_val = 1;
+ EXPECT_TRUE(root->GetAsInteger(&int_val));
+ EXPECT_EQ(0, int_val);
+}
- root = JSONReader().ReadToValue("2.1e+0001");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::DOUBLE));
- double_val = 0.0;
- EXPECT_TRUE(root->GetAsDouble(&double_val));
- EXPECT_DOUBLE_EQ(21.0, double_val);
+TEST(JSONReaderTest, LargeIntPromotion) {
+ // Numbers that overflow ints should succeed, being internally promoted to
+ // storage as doubles
+ std::unique_ptr<Value> root = JSONReader().ReadToValue("2147483648");
+ ASSERT_TRUE(root);
+ double double_val;
+ EXPECT_TRUE(root->is_double());
+ double_val = 0.0;
+ EXPECT_TRUE(root->GetAsDouble(&double_val));
+ EXPECT_DOUBLE_EQ(2147483648.0, double_val);
+ root = JSONReader().ReadToValue("-2147483649");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_double());
+ double_val = 0.0;
+ EXPECT_TRUE(root->GetAsDouble(&double_val));
+ EXPECT_DOUBLE_EQ(-2147483649.0, double_val);
+}
- root = JSONReader().ReadToValue("0.01");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::DOUBLE));
- double_val = 0.0;
- EXPECT_TRUE(root->GetAsDouble(&double_val));
- EXPECT_DOUBLE_EQ(0.01, double_val);
+TEST(JSONReaderTest, Doubles) {
+ std::unique_ptr<Value> root = JSONReader().ReadToValue("43.1");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_double());
+ double double_val = 0.0;
+ EXPECT_TRUE(root->GetAsDouble(&double_val));
+ EXPECT_DOUBLE_EQ(43.1, double_val);
+
+ root = JSONReader().ReadToValue("4.3e-1");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_double());
+ double_val = 0.0;
+ EXPECT_TRUE(root->GetAsDouble(&double_val));
+ EXPECT_DOUBLE_EQ(.43, double_val);
+
+ root = JSONReader().ReadToValue("2.1e0");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_double());
+ double_val = 0.0;
+ EXPECT_TRUE(root->GetAsDouble(&double_val));
+ EXPECT_DOUBLE_EQ(2.1, double_val);
+
+ root = JSONReader().ReadToValue("2.1e+0001");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_double());
+ double_val = 0.0;
+ EXPECT_TRUE(root->GetAsDouble(&double_val));
+ EXPECT_DOUBLE_EQ(21.0, double_val);
+
+ root = JSONReader().ReadToValue("0.01");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_double());
+ double_val = 0.0;
+ EXPECT_TRUE(root->GetAsDouble(&double_val));
+ EXPECT_DOUBLE_EQ(0.01, double_val);
+
+ root = JSONReader().ReadToValue("1.00");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_double());
+ double_val = 0.0;
+ EXPECT_TRUE(root->GetAsDouble(&double_val));
+ EXPECT_DOUBLE_EQ(1.0, double_val);
+}
- root = JSONReader().ReadToValue("1.00");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::DOUBLE));
- double_val = 0.0;
- EXPECT_TRUE(root->GetAsDouble(&double_val));
- EXPECT_DOUBLE_EQ(1.0, double_val);
- }
+TEST(JSONReaderTest, FractionalNumbers) {
+ // Fractional parts must have a digit before and after the decimal point.
+ EXPECT_FALSE(JSONReader().ReadToValue("1."));
+ EXPECT_FALSE(JSONReader().ReadToValue(".1"));
+ EXPECT_FALSE(JSONReader().ReadToValue("1.e10"));
+}
- {
- // Fractional parts must have a digit before and after the decimal point.
- EXPECT_FALSE(JSONReader().ReadToValue("1."));
- EXPECT_FALSE(JSONReader().ReadToValue(".1"));
- EXPECT_FALSE(JSONReader().ReadToValue("1.e10"));
- }
+TEST(JSONReaderTest, ExponentialNumbers) {
+ // Exponent must have a digit following the 'e'.
+ EXPECT_FALSE(JSONReader().ReadToValue("1e"));
+ EXPECT_FALSE(JSONReader().ReadToValue("1E"));
+ EXPECT_FALSE(JSONReader().ReadToValue("1e1."));
+ EXPECT_FALSE(JSONReader().ReadToValue("1e1.0"));
+}
- {
- // Exponent must have a digit following the 'e'.
- EXPECT_FALSE(JSONReader().ReadToValue("1e"));
- EXPECT_FALSE(JSONReader().ReadToValue("1E"));
- EXPECT_FALSE(JSONReader().ReadToValue("1e1."));
- EXPECT_FALSE(JSONReader().ReadToValue("1e1.0"));
- }
+TEST(JSONReaderTest, InvalidNAN) {
+ EXPECT_FALSE(JSONReader().ReadToValue("1e1000"));
+ EXPECT_FALSE(JSONReader().ReadToValue("-1e1000"));
+ EXPECT_FALSE(JSONReader().ReadToValue("NaN"));
+ EXPECT_FALSE(JSONReader().ReadToValue("nan"));
+ EXPECT_FALSE(JSONReader().ReadToValue("inf"));
+}
- {
- // INF/-INF/NaN are not valid
- EXPECT_FALSE(JSONReader().ReadToValue("1e1000"));
- EXPECT_FALSE(JSONReader().ReadToValue("-1e1000"));
- EXPECT_FALSE(JSONReader().ReadToValue("NaN"));
- EXPECT_FALSE(JSONReader().ReadToValue("nan"));
- EXPECT_FALSE(JSONReader().ReadToValue("inf"));
- }
+TEST(JSONReaderTest, InvalidNumbers) {
+ EXPECT_FALSE(JSONReader().ReadToValue("4.3.1"));
+ EXPECT_FALSE(JSONReader().ReadToValue("4e3.1"));
+ EXPECT_FALSE(JSONReader().ReadToValue("4.a"));
+}
- {
- // Invalid number formats
- EXPECT_FALSE(JSONReader().ReadToValue("4.3.1"));
- EXPECT_FALSE(JSONReader().ReadToValue("4e3.1"));
- }
+TEST(JSONReader, SimpleString) {
+ std::unique_ptr<Value> root = JSONReader().ReadToValue("\"hello world\"");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_string());
+ std::string str_val;
+ EXPECT_TRUE(root->GetAsString(&str_val));
+ EXPECT_EQ("hello world", str_val);
+}
- {
- // Test string parser
- std::unique_ptr<Value> root = JSONReader().ReadToValue("\"hello world\"");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::STRING));
- std::string str_val;
- EXPECT_TRUE(root->GetAsString(&str_val));
- EXPECT_EQ("hello world", str_val);
- }
+TEST(JSONReaderTest, EmptyString) {
+ std::unique_ptr<Value> root = JSONReader().ReadToValue("\"\"");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_string());
+ std::string str_val;
+ EXPECT_TRUE(root->GetAsString(&str_val));
+ EXPECT_EQ("", str_val);
+}
- {
- // Empty string
- std::unique_ptr<Value> root = JSONReader().ReadToValue("\"\"");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::STRING));
- std::string str_val;
- EXPECT_TRUE(root->GetAsString(&str_val));
- EXPECT_EQ("", str_val);
- }
+TEST(JSONReaderTest, BasicStringEscapes) {
+ std::unique_ptr<Value> root =
+ JSONReader().ReadToValue("\" \\\"\\\\\\/\\b\\f\\n\\r\\t\\v\"");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_string());
+ std::string str_val;
+ EXPECT_TRUE(root->GetAsString(&str_val));
+ EXPECT_EQ(" \"\\/\b\f\n\r\t\v", str_val);
+}
- {
- // Test basic string escapes
- std::unique_ptr<Value> root =
- JSONReader().ReadToValue("\" \\\"\\\\\\/\\b\\f\\n\\r\\t\\v\"");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::STRING));
- std::string str_val;
- EXPECT_TRUE(root->GetAsString(&str_val));
- EXPECT_EQ(" \"\\/\b\f\n\r\t\v", str_val);
- }
+TEST(JSONReaderTest, UnicodeEscapes) {
+ // Test hex and unicode escapes including the null character.
+ std::unique_ptr<Value> root =
+ JSONReader().ReadToValue("\"\\x41\\x00\\u1234\\u0000\"");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_string());
+ std::string str_val;
+ EXPECT_TRUE(root->GetAsString(&str_val));
+ EXPECT_EQ(std::wstring(L"A\0\x1234\0", 4), UTF8ToWide(str_val));
+}
- {
- // Test hex and unicode escapes including the null character.
- std::unique_ptr<Value> root =
- JSONReader().ReadToValue("\"\\x41\\x00\\u1234\"");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::STRING));
- std::string str_val;
- EXPECT_TRUE(root->GetAsString(&str_val));
- EXPECT_EQ(std::wstring(L"A\0\x1234", 3), UTF8ToWide(str_val));
- }
+TEST(JSONReaderTest, InvalidStrings) {
+ EXPECT_FALSE(JSONReader().ReadToValue("\"no closing quote"));
+ EXPECT_FALSE(JSONReader().ReadToValue("\"\\z invalid escape char\""));
+ EXPECT_FALSE(JSONReader().ReadToValue("\"\\xAQ invalid hex code\""));
+ EXPECT_FALSE(JSONReader().ReadToValue("not enough hex chars\\x1\""));
+ EXPECT_FALSE(JSONReader().ReadToValue("\"not enough escape chars\\u123\""));
+ EXPECT_FALSE(
+ JSONReader().ReadToValue("\"extra backslash at end of input\\\""));
+}
- {
- // Test invalid strings
- EXPECT_FALSE(JSONReader().ReadToValue("\"no closing quote"));
- EXPECT_FALSE(JSONReader().ReadToValue("\"\\z invalid escape char\""));
- EXPECT_FALSE(JSONReader().ReadToValue("\"\\xAQ invalid hex code\""));
- EXPECT_FALSE(JSONReader().ReadToValue("not enough hex chars\\x1\""));
- EXPECT_FALSE(JSONReader().ReadToValue("\"not enough escape chars\\u123\""));
- EXPECT_FALSE(
- JSONReader().ReadToValue("\"extra backslash at end of input\\\""));
- }
+TEST(JSONReaderTest, BasicArray) {
+ std::unique_ptr<ListValue> list =
+ ListValue::From(JSONReader::Read("[true, false, null]"));
+ ASSERT_TRUE(list);
+ EXPECT_EQ(3U, list->GetSize());
- {
- // Basic array
- std::unique_ptr<ListValue> list =
- ListValue::From(JSONReader::Read("[true, false, null]"));
- ASSERT_TRUE(list);
- EXPECT_EQ(3U, list->GetSize());
-
- // Test with trailing comma. Should be parsed the same as above.
- std::unique_ptr<Value> root2 =
- JSONReader::Read("[true, false, null, ]", JSON_ALLOW_TRAILING_COMMAS);
- EXPECT_TRUE(list->Equals(root2.get()));
- }
+ // Test with trailing comma. Should be parsed the same as above.
+ std::unique_ptr<Value> root2 =
+ JSONReader::Read("[true, false, null, ]", JSON_ALLOW_TRAILING_COMMAS);
+ EXPECT_TRUE(list->Equals(root2.get()));
+}
- {
- // Empty array
- std::unique_ptr<ListValue> list = ListValue::From(JSONReader::Read("[]"));
- ASSERT_TRUE(list);
- EXPECT_EQ(0U, list->GetSize());
- }
+TEST(JSONReaderTest, EmptyArray) {
+ std::unique_ptr<ListValue> list = ListValue::From(JSONReader::Read("[]"));
+ ASSERT_TRUE(list);
+ EXPECT_EQ(0U, list->GetSize());
+}
- {
- // Nested arrays
- std::unique_ptr<ListValue> list = ListValue::From(
- JSONReader::Read("[[true], [], [false, [], [null]], null]"));
- ASSERT_TRUE(list);
- EXPECT_EQ(4U, list->GetSize());
-
- // Lots of trailing commas.
- std::unique_ptr<Value> root2 =
- JSONReader::Read("[[true], [], [false, [], [null, ] , ], null,]",
- JSON_ALLOW_TRAILING_COMMAS);
- EXPECT_TRUE(list->Equals(root2.get()));
- }
+TEST(JSONReaderTest, NestedArrays) {
+ std::unique_ptr<ListValue> list = ListValue::From(
+ JSONReader::Read("[[true], [], [false, [], [null]], null]"));
+ ASSERT_TRUE(list);
+ EXPECT_EQ(4U, list->GetSize());
+
+ // Lots of trailing commas.
+ std::unique_ptr<Value> root2 =
+ JSONReader::Read("[[true], [], [false, [], [null, ] , ], null,]",
+ JSON_ALLOW_TRAILING_COMMAS);
+ EXPECT_TRUE(list->Equals(root2.get()));
+}
- {
- // Invalid, missing close brace.
- EXPECT_FALSE(JSONReader::Read("[[true], [], [false, [], [null]], null"));
+TEST(JSONReaderTest, InvalidArrays) {
+ // Missing close brace.
+ EXPECT_FALSE(JSONReader::Read("[[true], [], [false, [], [null]], null"));
- // Invalid, too many commas
- EXPECT_FALSE(JSONReader::Read("[true,, null]"));
- EXPECT_FALSE(JSONReader::Read("[true,, null]", JSON_ALLOW_TRAILING_COMMAS));
+ // Too many commas.
+ EXPECT_FALSE(JSONReader::Read("[true,, null]"));
+ EXPECT_FALSE(JSONReader::Read("[true,, null]", JSON_ALLOW_TRAILING_COMMAS));
- // Invalid, no commas
- EXPECT_FALSE(JSONReader::Read("[true null]"));
+ // No commas.
+ EXPECT_FALSE(JSONReader::Read("[true null]"));
- // Invalid, trailing comma
- EXPECT_FALSE(JSONReader::Read("[true,]"));
- }
+ // Trailing comma.
+ EXPECT_FALSE(JSONReader::Read("[true,]"));
+}
- {
- // Valid if we set |allow_trailing_comma| to true.
- std::unique_ptr<ListValue> list = ListValue::From(
- JSONReader::Read("[true,]", JSON_ALLOW_TRAILING_COMMAS));
- ASSERT_TRUE(list);
- EXPECT_EQ(1U, list->GetSize());
- Value* tmp_value = nullptr;
- ASSERT_TRUE(list->Get(0, &tmp_value));
- EXPECT_TRUE(tmp_value->IsType(Value::Type::BOOLEAN));
- bool bool_value = false;
- EXPECT_TRUE(tmp_value->GetAsBoolean(&bool_value));
- EXPECT_TRUE(bool_value);
- }
+TEST(JSONReaderTest, ArrayTrailingComma) {
+ // Valid if we set |allow_trailing_comma| to true.
+ std::unique_ptr<ListValue> list =
+ ListValue::From(JSONReader::Read("[true,]", JSON_ALLOW_TRAILING_COMMAS));
+ ASSERT_TRUE(list);
+ EXPECT_EQ(1U, list->GetSize());
+ Value* tmp_value = nullptr;
+ ASSERT_TRUE(list->Get(0, &tmp_value));
+ EXPECT_TRUE(tmp_value->is_bool());
+ bool bool_value = false;
+ EXPECT_TRUE(tmp_value->GetAsBoolean(&bool_value));
+ EXPECT_TRUE(bool_value);
+}
- {
- // Don't allow empty elements, even if |allow_trailing_comma| is
- // true.
- EXPECT_FALSE(JSONReader::Read("[,]", JSON_ALLOW_TRAILING_COMMAS));
- EXPECT_FALSE(JSONReader::Read("[true,,]", JSON_ALLOW_TRAILING_COMMAS));
- EXPECT_FALSE(JSONReader::Read("[,true,]", JSON_ALLOW_TRAILING_COMMAS));
- EXPECT_FALSE(JSONReader::Read("[true,,false]", JSON_ALLOW_TRAILING_COMMAS));
- }
+TEST(JSONReaderTest, ArrayTrailingCommaNoEmptyElements) {
+ // Don't allow empty elements, even if |allow_trailing_comma| is
+ // true.
+ EXPECT_FALSE(JSONReader::Read("[,]", JSON_ALLOW_TRAILING_COMMAS));
+ EXPECT_FALSE(JSONReader::Read("[true,,]", JSON_ALLOW_TRAILING_COMMAS));
+ EXPECT_FALSE(JSONReader::Read("[,true,]", JSON_ALLOW_TRAILING_COMMAS));
+ EXPECT_FALSE(JSONReader::Read("[true,,false]", JSON_ALLOW_TRAILING_COMMAS));
+}
- {
- // Test objects
- std::unique_ptr<DictionaryValue> dict_val =
- DictionaryValue::From(JSONReader::Read("{}"));
- ASSERT_TRUE(dict_val);
-
- dict_val = DictionaryValue::From(JSONReader::Read(
- "{\"number\":9.87654321, \"null\":null , \"\\x53\" : \"str\" }"));
- ASSERT_TRUE(dict_val);
- double double_val = 0.0;
- EXPECT_TRUE(dict_val->GetDouble("number", &double_val));
- EXPECT_DOUBLE_EQ(9.87654321, double_val);
- Value* null_val = nullptr;
- ASSERT_TRUE(dict_val->Get("null", &null_val));
- EXPECT_TRUE(null_val->IsType(Value::Type::NONE));
- std::string str_val;
- EXPECT_TRUE(dict_val->GetString("S", &str_val));
- EXPECT_EQ("str", str_val);
-
- std::unique_ptr<Value> root2 = JSONReader::Read(
- "{\"number\":9.87654321, \"null\":null , \"\\x53\" : \"str\", }",
- JSON_ALLOW_TRAILING_COMMAS);
- ASSERT_TRUE(root2);
- EXPECT_TRUE(dict_val->Equals(root2.get()));
-
- // Test newline equivalence.
- root2 = JSONReader::Read(
- "{\n"
- " \"number\":9.87654321,\n"
- " \"null\":null,\n"
- " \"\\x53\":\"str\",\n"
- "}\n",
- JSON_ALLOW_TRAILING_COMMAS);
- ASSERT_TRUE(root2);
- EXPECT_TRUE(dict_val->Equals(root2.get()));
-
- root2 = JSONReader::Read(
- "{\r\n"
- " \"number\":9.87654321,\r\n"
- " \"null\":null,\r\n"
- " \"\\x53\":\"str\",\r\n"
- "}\r\n",
- JSON_ALLOW_TRAILING_COMMAS);
- ASSERT_TRUE(root2);
- EXPECT_TRUE(dict_val->Equals(root2.get()));
- }
+TEST(JSONReaderTest, EmptyDictionary) {
+ std::unique_ptr<DictionaryValue> dict_val =
+ DictionaryValue::From(JSONReader::Read("{}"));
+ ASSERT_TRUE(dict_val);
+}
- {
- // Test nesting
- std::unique_ptr<DictionaryValue> dict_val =
- DictionaryValue::From(JSONReader::Read(
- "{\"inner\":{\"array\":[true]},\"false\":false,\"d\":{}}"));
- ASSERT_TRUE(dict_val);
- DictionaryValue* inner_dict = nullptr;
- ASSERT_TRUE(dict_val->GetDictionary("inner", &inner_dict));
- ListValue* inner_array = nullptr;
- ASSERT_TRUE(inner_dict->GetList("array", &inner_array));
- EXPECT_EQ(1U, inner_array->GetSize());
- bool bool_value = true;
- EXPECT_TRUE(dict_val->GetBoolean("false", &bool_value));
- EXPECT_FALSE(bool_value);
- inner_dict = nullptr;
- EXPECT_TRUE(dict_val->GetDictionary("d", &inner_dict));
-
- std::unique_ptr<Value> root2 = JSONReader::Read(
- "{\"inner\": {\"array\":[true] , },\"false\":false,\"d\":{},}",
- JSON_ALLOW_TRAILING_COMMAS);
- EXPECT_TRUE(dict_val->Equals(root2.get()));
- }
+TEST(JSONReaderTest, CompleteDictionary) {
+ auto dict_val = DictionaryValue::From(JSONReader::Read(
+ "{\"number\":9.87654321, \"null\":null , \"\\x53\" : \"str\" }"));
+ ASSERT_TRUE(dict_val);
+ double double_val = 0.0;
+ EXPECT_TRUE(dict_val->GetDouble("number", &double_val));
+ EXPECT_DOUBLE_EQ(9.87654321, double_val);
+ Value* null_val = nullptr;
+ ASSERT_TRUE(dict_val->Get("null", &null_val));
+ EXPECT_TRUE(null_val->is_none());
+ std::string str_val;
+ EXPECT_TRUE(dict_val->GetString("S", &str_val));
+ EXPECT_EQ("str", str_val);
+
+ std::unique_ptr<Value> root2 = JSONReader::Read(
+ "{\"number\":9.87654321, \"null\":null , \"\\x53\" : \"str\", }",
+ JSON_ALLOW_TRAILING_COMMAS);
+ ASSERT_TRUE(root2);
+ EXPECT_TRUE(dict_val->Equals(root2.get()));
+
+ // Test newline equivalence.
+ root2 = JSONReader::Read(
+ "{\n"
+ " \"number\":9.87654321,\n"
+ " \"null\":null,\n"
+ " \"\\x53\":\"str\",\n"
+ "}\n",
+ JSON_ALLOW_TRAILING_COMMAS);
+ ASSERT_TRUE(root2);
+ EXPECT_TRUE(dict_val->Equals(root2.get()));
+
+ root2 = JSONReader::Read(
+ "{\r\n"
+ " \"number\":9.87654321,\r\n"
+ " \"null\":null,\r\n"
+ " \"\\x53\":\"str\",\r\n"
+ "}\r\n",
+ JSON_ALLOW_TRAILING_COMMAS);
+ ASSERT_TRUE(root2);
+ EXPECT_TRUE(dict_val->Equals(root2.get()));
+}
- {
- // Test keys with periods
- std::unique_ptr<DictionaryValue> dict_val = DictionaryValue::From(
- JSONReader::Read("{\"a.b\":3,\"c\":2,\"d.e.f\":{\"g.h.i.j\":1}}"));
- ASSERT_TRUE(dict_val);
- int integer_value = 0;
- EXPECT_TRUE(
- dict_val->GetIntegerWithoutPathExpansion("a.b", &integer_value));
- EXPECT_EQ(3, integer_value);
- EXPECT_TRUE(dict_val->GetIntegerWithoutPathExpansion("c", &integer_value));
- EXPECT_EQ(2, integer_value);
- DictionaryValue* inner_dict = nullptr;
- ASSERT_TRUE(
- dict_val->GetDictionaryWithoutPathExpansion("d.e.f", &inner_dict));
- EXPECT_EQ(1U, inner_dict->size());
- EXPECT_TRUE(
- inner_dict->GetIntegerWithoutPathExpansion("g.h.i.j", &integer_value));
- EXPECT_EQ(1, integer_value);
-
- dict_val =
- DictionaryValue::From(JSONReader::Read("{\"a\":{\"b\":2},\"a.b\":1}"));
- ASSERT_TRUE(dict_val);
- EXPECT_TRUE(dict_val->GetInteger("a.b", &integer_value));
- EXPECT_EQ(2, integer_value);
- EXPECT_TRUE(
- dict_val->GetIntegerWithoutPathExpansion("a.b", &integer_value));
- EXPECT_EQ(1, integer_value);
- }
+TEST(JSONReaderTest, NestedDictionaries) {
+ std::unique_ptr<DictionaryValue> dict_val =
+ DictionaryValue::From(JSONReader::Read(
+ "{\"inner\":{\"array\":[true]},\"false\":false,\"d\":{}}"));
+ ASSERT_TRUE(dict_val);
+ DictionaryValue* inner_dict = nullptr;
+ ASSERT_TRUE(dict_val->GetDictionary("inner", &inner_dict));
+ ListValue* inner_array = nullptr;
+ ASSERT_TRUE(inner_dict->GetList("array", &inner_array));
+ EXPECT_EQ(1U, inner_array->GetSize());
+ bool bool_value = true;
+ EXPECT_TRUE(dict_val->GetBoolean("false", &bool_value));
+ EXPECT_FALSE(bool_value);
+ inner_dict = nullptr;
+ EXPECT_TRUE(dict_val->GetDictionary("d", &inner_dict));
+
+ std::unique_ptr<Value> root2 = JSONReader::Read(
+ "{\"inner\": {\"array\":[true] , },\"false\":false,\"d\":{},}",
+ JSON_ALLOW_TRAILING_COMMAS);
+ EXPECT_TRUE(dict_val->Equals(root2.get()));
+}
- {
- // Invalid, no closing brace
- EXPECT_FALSE(JSONReader::Read("{\"a\": true"));
-
- // Invalid, keys must be quoted
- EXPECT_FALSE(JSONReader::Read("{foo:true}"));
-
- // Invalid, trailing comma
- EXPECT_FALSE(JSONReader::Read("{\"a\":true,}"));
-
- // Invalid, too many commas
- EXPECT_FALSE(JSONReader::Read("{\"a\":true,,\"b\":false}"));
- EXPECT_FALSE(JSONReader::Read("{\"a\":true,,\"b\":false}",
- JSON_ALLOW_TRAILING_COMMAS));
-
- // Invalid, no separator
- EXPECT_FALSE(JSONReader::Read("{\"a\" \"b\"}"));
-
- // Invalid, lone comma.
- EXPECT_FALSE(JSONReader::Read("{,}"));
- EXPECT_FALSE(JSONReader::Read("{,}", JSON_ALLOW_TRAILING_COMMAS));
- EXPECT_FALSE(
- JSONReader::Read("{\"a\":true,,}", JSON_ALLOW_TRAILING_COMMAS));
- EXPECT_FALSE(JSONReader::Read("{,\"a\":true}", JSON_ALLOW_TRAILING_COMMAS));
- EXPECT_FALSE(JSONReader::Read("{\"a\":true,,\"b\":false}",
- JSON_ALLOW_TRAILING_COMMAS));
- }
+TEST(JSONReaderTest, DictionaryKeysWithPeriods) {
+ std::unique_ptr<DictionaryValue> dict_val = DictionaryValue::From(
+ JSONReader::Read("{\"a.b\":3,\"c\":2,\"d.e.f\":{\"g.h.i.j\":1}}"));
+ ASSERT_TRUE(dict_val);
+ int integer_value = 0;
+ EXPECT_TRUE(dict_val->GetIntegerWithoutPathExpansion("a.b", &integer_value));
+ EXPECT_EQ(3, integer_value);
+ EXPECT_TRUE(dict_val->GetIntegerWithoutPathExpansion("c", &integer_value));
+ EXPECT_EQ(2, integer_value);
+ DictionaryValue* inner_dict = nullptr;
+ ASSERT_TRUE(
+ dict_val->GetDictionaryWithoutPathExpansion("d.e.f", &inner_dict));
+ EXPECT_EQ(1U, inner_dict->size());
+ EXPECT_TRUE(
+ inner_dict->GetIntegerWithoutPathExpansion("g.h.i.j", &integer_value));
+ EXPECT_EQ(1, integer_value);
+
+ dict_val =
+ DictionaryValue::From(JSONReader::Read("{\"a\":{\"b\":2},\"a.b\":1}"));
+ ASSERT_TRUE(dict_val);
+ EXPECT_TRUE(dict_val->GetInteger("a.b", &integer_value));
+ EXPECT_EQ(2, integer_value);
+ EXPECT_TRUE(dict_val->GetIntegerWithoutPathExpansion("a.b", &integer_value));
+ EXPECT_EQ(1, integer_value);
+}
- {
- // Test stack overflow
- std::string evil(1000000, '[');
- evil.append(std::string(1000000, ']'));
- EXPECT_FALSE(JSONReader::Read(evil));
- }
+TEST(JSONReaderTest, InvalidDictionaries) {
+ // No closing brace.
+ EXPECT_FALSE(JSONReader::Read("{\"a\": true"));
+
+ // Keys must be quoted strings.
+ EXPECT_FALSE(JSONReader::Read("{foo:true}"));
+ EXPECT_FALSE(JSONReader::Read("{1234: false}"));
+ EXPECT_FALSE(JSONReader::Read("{:false}"));
+
+ // Trailing comma.
+ EXPECT_FALSE(JSONReader::Read("{\"a\":true,}"));
+
+ // Too many commas.
+ EXPECT_FALSE(JSONReader::Read("{\"a\":true,,\"b\":false}"));
+ EXPECT_FALSE(JSONReader::Read("{\"a\":true,,\"b\":false}",
+ JSON_ALLOW_TRAILING_COMMAS));
+
+ // No separator.
+ EXPECT_FALSE(JSONReader::Read("{\"a\" \"b\"}"));
+
+ // Lone comma.
+ EXPECT_FALSE(JSONReader::Read("{,}"));
+ EXPECT_FALSE(JSONReader::Read("{,}", JSON_ALLOW_TRAILING_COMMAS));
+ EXPECT_FALSE(JSONReader::Read("{\"a\":true,,}", JSON_ALLOW_TRAILING_COMMAS));
+ EXPECT_FALSE(JSONReader::Read("{,\"a\":true}", JSON_ALLOW_TRAILING_COMMAS));
+ EXPECT_FALSE(JSONReader::Read("{\"a\":true,,\"b\":false}",
+ JSON_ALLOW_TRAILING_COMMAS));
+}
- {
- // A few thousand adjacent lists is fine.
- std::string not_evil("[");
- not_evil.reserve(15010);
- for (int i = 0; i < 5000; ++i)
- not_evil.append("[],");
- not_evil.append("[]]");
- std::unique_ptr<ListValue> list =
- ListValue::From(JSONReader::Read(not_evil));
- ASSERT_TRUE(list);
- EXPECT_EQ(5001U, list->GetSize());
- }
+TEST(JSONReaderTest, StackOverflow) {
+ std::string evil(1000000, '[');
+ evil.append(std::string(1000000, ']'));
+ EXPECT_FALSE(JSONReader::Read(evil));
+
+ // A few thousand adjacent lists is fine.
+ std::string not_evil("[");
+ not_evil.reserve(15010);
+ for (int i = 0; i < 5000; ++i)
+ not_evil.append("[],");
+ not_evil.append("[]]");
+ std::unique_ptr<ListValue> list = ListValue::From(JSONReader::Read(not_evil));
+ ASSERT_TRUE(list);
+ EXPECT_EQ(5001U, list->GetSize());
+}
- {
- // Test utf8 encoded input
- std::unique_ptr<Value> root =
- JSONReader().ReadToValue("\"\xe7\xbd\x91\xe9\xa1\xb5\"");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::STRING));
- std::string str_val;
- EXPECT_TRUE(root->GetAsString(&str_val));
- EXPECT_EQ(L"\x7f51\x9875", UTF8ToWide(str_val));
-
- std::unique_ptr<DictionaryValue> dict_val =
- DictionaryValue::From(JSONReader().ReadToValue(
- "{\"path\": \"/tmp/\xc3\xa0\xc3\xa8\xc3\xb2.png\"}"));
- ASSERT_TRUE(dict_val);
- EXPECT_TRUE(dict_val->GetString("path", &str_val));
- EXPECT_EQ("/tmp/\xC3\xA0\xC3\xA8\xC3\xB2.png", str_val);
- }
+TEST(JSONReaderTest, UTF8Input) {
+ std::unique_ptr<Value> root =
+ JSONReader().ReadToValue("\"\xe7\xbd\x91\xe9\xa1\xb5\"");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_string());
+ std::string str_val;
+ EXPECT_TRUE(root->GetAsString(&str_val));
+ EXPECT_EQ(L"\x7f51\x9875", UTF8ToWide(str_val));
+
+ std::unique_ptr<DictionaryValue> dict_val =
+ DictionaryValue::From(JSONReader().ReadToValue(
+ "{\"path\": \"/tmp/\xc3\xa0\xc3\xa8\xc3\xb2.png\"}"));
+ ASSERT_TRUE(dict_val);
+ EXPECT_TRUE(dict_val->GetString("path", &str_val));
+ EXPECT_EQ("/tmp/\xC3\xA0\xC3\xA8\xC3\xB2.png", str_val);
+}
- {
- // Test invalid utf8 encoded input
- EXPECT_FALSE(JSONReader().ReadToValue("\"345\xb0\xa1\xb0\xa2\""));
- EXPECT_FALSE(JSONReader().ReadToValue("\"123\xc0\x81\""));
- EXPECT_FALSE(JSONReader().ReadToValue("\"abc\xc0\xae\""));
- }
+TEST(JSONReaderTest, InvalidUTF8Input) {
+ EXPECT_FALSE(JSONReader().ReadToValue("\"345\xb0\xa1\xb0\xa2\""));
+ EXPECT_FALSE(JSONReader().ReadToValue("\"123\xc0\x81\""));
+ EXPECT_FALSE(JSONReader().ReadToValue("\"abc\xc0\xae\""));
+}
- {
- // Test utf16 encoded strings.
- std::unique_ptr<Value> root = JSONReader().ReadToValue("\"\\u20ac3,14\"");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::STRING));
- std::string str_val;
- EXPECT_TRUE(root->GetAsString(&str_val));
- EXPECT_EQ(
- "\xe2\x82\xac"
- "3,14",
- str_val);
-
- root = JSONReader().ReadToValue("\"\\ud83d\\udca9\\ud83d\\udc6c\"");
- ASSERT_TRUE(root);
- EXPECT_TRUE(root->IsType(Value::Type::STRING));
- str_val.clear();
- EXPECT_TRUE(root->GetAsString(&str_val));
- EXPECT_EQ("\xf0\x9f\x92\xa9\xf0\x9f\x91\xac", str_val);
- }
+TEST(JSONReaderTest, UTF16Escapes) {
+ std::unique_ptr<Value> root = JSONReader().ReadToValue("\"\\u20ac3,14\"");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_string());
+ std::string str_val;
+ EXPECT_TRUE(root->GetAsString(&str_val));
+ EXPECT_EQ(
+ "\xe2\x82\xac"
+ "3,14",
+ str_val);
+
+ root = JSONReader().ReadToValue("\"\\ud83d\\udca9\\ud83d\\udc6c\"");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_string());
+ str_val.clear();
+ EXPECT_TRUE(root->GetAsString(&str_val));
+ EXPECT_EQ("\xf0\x9f\x92\xa9\xf0\x9f\x91\xac", str_val);
+}
- {
- // Test invalid utf16 strings.
- const char* const cases[] = {
- "\"\\u123\"", // Invalid scalar.
- "\"\\ud83d\"", // Invalid scalar.
- "\"\\u$%@!\"", // Invalid scalar.
- "\"\\uzz89\"", // Invalid scalar.
- "\"\\ud83d\\udca\"", // Invalid lower surrogate.
- "\"\\ud83d\\ud83d\"", // Invalid lower surrogate.
- "\"\\ud83foo\"", // No lower surrogate.
- "\"\\ud83\\foo\"" // No lower surrogate.
- };
- std::unique_ptr<Value> root;
- for (size_t i = 0; i < arraysize(cases); ++i) {
- root = JSONReader().ReadToValue(cases[i]);
- EXPECT_FALSE(root) << cases[i];
- }
+TEST(JSONReaderTest, InvalidUTF16Escapes) {
+ const char* const cases[] = {
+ "\"\\u123\"", // Invalid scalar.
+ "\"\\ud83d\"", // Invalid scalar.
+ "\"\\u$%@!\"", // Invalid scalar.
+ "\"\\uzz89\"", // Invalid scalar.
+ "\"\\ud83d\\udca\"", // Invalid lower surrogate.
+ "\"\\ud83d\\ud83d\"", // Invalid lower surrogate.
+ "\"\\ud83d\\uaaaZ\"" // Invalid lower surrogate.
+ "\"\\ud83foo\"", // No lower surrogate.
+ "\"\\ud83d\\foo\"" // No lower surrogate.
+ "\"\\ud83\\foo\"" // Invalid upper surrogate.
+ "\"\\ud83d\\u1\"" // No lower surrogate.
+ "\"\\ud83\\u1\"" // Invalid upper surrogate.
+ };
+ std::unique_ptr<Value> root;
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ root = JSONReader().ReadToValue(cases[i]);
+ EXPECT_FALSE(root) << cases[i];
}
+}
- {
- // Test literal root objects.
- std::unique_ptr<Value> root = JSONReader::Read("null");
- EXPECT_TRUE(root->IsType(Value::Type::NONE));
-
- root = JSONReader::Read("true");
- ASSERT_TRUE(root);
- bool bool_value;
- EXPECT_TRUE(root->GetAsBoolean(&bool_value));
- EXPECT_TRUE(bool_value);
-
- root = JSONReader::Read("10");
- ASSERT_TRUE(root);
- int integer_value;
- EXPECT_TRUE(root->GetAsInteger(&integer_value));
- EXPECT_EQ(10, integer_value);
-
- root = JSONReader::Read("\"root\"");
- ASSERT_TRUE(root);
- std::string str_val;
- EXPECT_TRUE(root->GetAsString(&str_val));
- EXPECT_EQ("root", str_val);
- }
+TEST(JSONReaderTest, LiteralRoots) {
+ std::unique_ptr<Value> root = JSONReader::Read("null");
+ ASSERT_TRUE(root);
+ EXPECT_TRUE(root->is_none());
+
+ root = JSONReader::Read("true");
+ ASSERT_TRUE(root);
+ bool bool_value;
+ EXPECT_TRUE(root->GetAsBoolean(&bool_value));
+ EXPECT_TRUE(bool_value);
+
+ root = JSONReader::Read("10");
+ ASSERT_TRUE(root);
+ int integer_value;
+ EXPECT_TRUE(root->GetAsInteger(&integer_value));
+ EXPECT_EQ(10, integer_value);
+
+ root = JSONReader::Read("\"root\"");
+ ASSERT_TRUE(root);
+ std::string str_val;
+ EXPECT_TRUE(root->GetAsString(&str_val));
+ EXPECT_EQ("root", str_val);
}
TEST(JSONReaderTest, DISABLED_ReadFromFile) {
@@ -579,7 +557,7 @@ TEST(JSONReaderTest, DISABLED_ReadFromFile) {
JSONReader reader;
std::unique_ptr<Value> root(reader.ReadToValue(input));
ASSERT_TRUE(root) << reader.GetErrorMessage();
- EXPECT_TRUE(root->IsType(Value::Type::DICTIONARY));
+ EXPECT_TRUE(root->is_dict());
}
// Tests that the root of a JSON object can be deleted safely while its
@@ -606,7 +584,7 @@ TEST(JSONReaderTest, StringOptimizations) {
" \"b\""
" ]"
"}",
- JSON_DETACHABLE_CHILDREN);
+ JSON_PARSE_RFC);
ASSERT_TRUE(root);
DictionaryValue* root_dict = nullptr;
@@ -675,4 +653,13 @@ TEST(JSONReaderTest, IllegalTrailingNull) {
EXPECT_EQ(JSONReader::JSON_UNEXPECTED_DATA_AFTER_ROOT, reader.error_code());
}
+TEST(JSONReaderTest, MaxNesting) {
+ std::string json(R"({"outer": { "inner": {"foo": true}}})");
+ std::unique_ptr<Value> root;
+ root = JSONReader::Read(json, JSON_PARSE_RFC, 3);
+ ASSERT_FALSE(root);
+ root = JSONReader::Read(json, JSON_PARSE_RFC, 4);
+ ASSERT_TRUE(root);
+}
+
} // namespace base
diff --git a/base/json/json_string_value_serializer.cc b/base/json/json_string_value_serializer.cc
index 2e46ab387a..f9c45a40d3 100644
--- a/base/json/json_string_value_serializer.cc
+++ b/base/json/json_string_value_serializer.cc
@@ -15,7 +15,7 @@ JSONStringValueSerializer::JSONStringValueSerializer(std::string* json_string)
pretty_print_(false) {
}
-JSONStringValueSerializer::~JSONStringValueSerializer() {}
+JSONStringValueSerializer::~JSONStringValueSerializer() = default;
bool JSONStringValueSerializer::Serialize(const Value& root) {
return SerializeInternal(root, false);
@@ -45,7 +45,7 @@ JSONStringValueDeserializer::JSONStringValueDeserializer(
int options)
: json_string_(json_string), options_(options) {}
-JSONStringValueDeserializer::~JSONStringValueDeserializer() {}
+JSONStringValueDeserializer::~JSONStringValueDeserializer() = default;
std::unique_ptr<Value> JSONStringValueDeserializer::Deserialize(
int* error_code,
diff --git a/base/json/json_value_converter.h b/base/json/json_value_converter.h
index 68ebfa23de..ef0811501b 100644
--- a/base/json/json_value_converter.h
+++ b/base/json/json_value_converter.h
@@ -72,7 +72,7 @@
// Sometimes JSON format uses string representations for other types such
// like enum, timestamp, or URL. You can use RegisterCustomField method
// and specify a function to convert a StringPiece to your type.
-// bool ConvertFunc(const StringPiece& s, YourEnum* result) {
+// bool ConvertFunc(StringPiece s, YourEnum* result) {
// // do something and return true if succeed...
// }
// struct Message {
@@ -96,7 +96,7 @@ template<typename StructType>
class FieldConverterBase {
public:
explicit FieldConverterBase(const std::string& path) : field_path_(path) {}
- virtual ~FieldConverterBase() {}
+ virtual ~FieldConverterBase() = default;
virtual bool ConvertField(const base::Value& value, StructType* obj)
const = 0;
const std::string& field_path() const { return field_path_; }
@@ -109,7 +109,7 @@ class FieldConverterBase {
template <typename FieldType>
class ValueConverter {
public:
- virtual ~ValueConverter() {}
+ virtual ~ValueConverter() = default;
virtual bool Convert(const base::Value& value, FieldType* field) const = 0;
};
@@ -140,7 +140,7 @@ class BasicValueConverter;
template <>
class BASE_EXPORT BasicValueConverter<int> : public ValueConverter<int> {
public:
- BasicValueConverter() {}
+ BasicValueConverter() = default;
bool Convert(const base::Value& value, int* field) const override;
@@ -152,7 +152,7 @@ template <>
class BASE_EXPORT BasicValueConverter<std::string>
: public ValueConverter<std::string> {
public:
- BasicValueConverter() {}
+ BasicValueConverter() = default;
bool Convert(const base::Value& value, std::string* field) const override;
@@ -164,7 +164,7 @@ template <>
class BASE_EXPORT BasicValueConverter<string16>
: public ValueConverter<string16> {
public:
- BasicValueConverter() {}
+ BasicValueConverter() = default;
bool Convert(const base::Value& value, string16* field) const override;
@@ -175,7 +175,7 @@ class BASE_EXPORT BasicValueConverter<string16>
template <>
class BASE_EXPORT BasicValueConverter<double> : public ValueConverter<double> {
public:
- BasicValueConverter() {}
+ BasicValueConverter() = default;
bool Convert(const base::Value& value, double* field) const override;
@@ -186,7 +186,7 @@ class BASE_EXPORT BasicValueConverter<double> : public ValueConverter<double> {
template <>
class BASE_EXPORT BasicValueConverter<bool> : public ValueConverter<bool> {
public:
- BasicValueConverter() {}
+ BasicValueConverter() = default;
bool Convert(const base::Value& value, bool* field) const override;
@@ -215,7 +215,7 @@ class ValueFieldConverter : public ValueConverter<FieldType> {
template <typename FieldType>
class CustomFieldConverter : public ValueConverter<FieldType> {
public:
- typedef bool(*ConvertFunc)(const StringPiece& value, FieldType* field);
+ typedef bool (*ConvertFunc)(StringPiece value, FieldType* field);
explicit CustomFieldConverter(ConvertFunc convert_func)
: convert_func_(convert_func) {}
@@ -235,7 +235,7 @@ class CustomFieldConverter : public ValueConverter<FieldType> {
template <typename NestedType>
class NestedValueConverter : public ValueConverter<NestedType> {
public:
- NestedValueConverter() {}
+ NestedValueConverter() = default;
bool Convert(const base::Value& value, NestedType* field) const override {
return converter_.Convert(value, field);
@@ -250,7 +250,7 @@ template <typename Element>
class RepeatedValueConverter
: public ValueConverter<std::vector<std::unique_ptr<Element>>> {
public:
- RepeatedValueConverter() {}
+ RepeatedValueConverter() = default;
bool Convert(const base::Value& value,
std::vector<std::unique_ptr<Element>>* field) const override {
@@ -286,7 +286,7 @@ template <typename NestedType>
class RepeatedMessageConverter
: public ValueConverter<std::vector<std::unique_ptr<NestedType>>> {
public:
- RepeatedMessageConverter() {}
+ RepeatedMessageConverter() = default;
bool Convert(const base::Value& value,
std::vector<std::unique_ptr<NestedType>>* field) const override {
@@ -365,51 +365,53 @@ class JSONValueConverter {
void RegisterIntField(const std::string& field_name,
int StructType::* field) {
- fields_.push_back(MakeUnique<internal::FieldConverter<StructType, int>>(
- field_name, field, new internal::BasicValueConverter<int>));
+ fields_.push_back(
+ std::make_unique<internal::FieldConverter<StructType, int>>(
+ field_name, field, new internal::BasicValueConverter<int>));
}
void RegisterStringField(const std::string& field_name,
std::string StructType::* field) {
fields_.push_back(
- MakeUnique<internal::FieldConverter<StructType, std::string>>(
+ std::make_unique<internal::FieldConverter<StructType, std::string>>(
field_name, field, new internal::BasicValueConverter<std::string>));
}
void RegisterStringField(const std::string& field_name,
string16 StructType::* field) {
fields_.push_back(
- MakeUnique<internal::FieldConverter<StructType, string16>>(
+ std::make_unique<internal::FieldConverter<StructType, string16>>(
field_name, field, new internal::BasicValueConverter<string16>));
}
void RegisterBoolField(const std::string& field_name,
bool StructType::* field) {
- fields_.push_back(MakeUnique<internal::FieldConverter<StructType, bool>>(
- field_name, field, new internal::BasicValueConverter<bool>));
+ fields_.push_back(
+ std::make_unique<internal::FieldConverter<StructType, bool>>(
+ field_name, field, new internal::BasicValueConverter<bool>));
}
void RegisterDoubleField(const std::string& field_name,
double StructType::* field) {
- fields_.push_back(MakeUnique<internal::FieldConverter<StructType, double>>(
- field_name, field, new internal::BasicValueConverter<double>));
+ fields_.push_back(
+ std::make_unique<internal::FieldConverter<StructType, double>>(
+ field_name, field, new internal::BasicValueConverter<double>));
}
template <class NestedType>
void RegisterNestedField(
const std::string& field_name, NestedType StructType::* field) {
fields_.push_back(
- MakeUnique<internal::FieldConverter<StructType, NestedType>>(
+ std::make_unique<internal::FieldConverter<StructType, NestedType>>(
field_name, field, new internal::NestedValueConverter<NestedType>));
}
template <typename FieldType>
- void RegisterCustomField(
- const std::string& field_name,
- FieldType StructType::* field,
- bool (*convert_func)(const StringPiece&, FieldType*)) {
+ void RegisterCustomField(const std::string& field_name,
+ FieldType StructType::*field,
+ bool (*convert_func)(StringPiece, FieldType*)) {
fields_.push_back(
- MakeUnique<internal::FieldConverter<StructType, FieldType>>(
+ std::make_unique<internal::FieldConverter<StructType, FieldType>>(
field_name, field,
new internal::CustomFieldConverter<FieldType>(convert_func)));
}
@@ -420,7 +422,7 @@ class JSONValueConverter {
FieldType StructType::* field,
bool (*convert_func)(const base::Value*, FieldType*)) {
fields_.push_back(
- MakeUnique<internal::FieldConverter<StructType, FieldType>>(
+ std::make_unique<internal::FieldConverter<StructType, FieldType>>(
field_name, field,
new internal::ValueFieldConverter<FieldType>(convert_func)));
}
@@ -428,17 +430,16 @@ class JSONValueConverter {
void RegisterRepeatedInt(
const std::string& field_name,
std::vector<std::unique_ptr<int>> StructType::*field) {
- fields_.push_back(
- MakeUnique<internal::FieldConverter<StructType,
- std::vector<std::unique_ptr<int>>>>(
- field_name, field, new internal::RepeatedValueConverter<int>));
+ fields_.push_back(std::make_unique<internal::FieldConverter<
+ StructType, std::vector<std::unique_ptr<int>>>>(
+ field_name, field, new internal::RepeatedValueConverter<int>));
}
void RegisterRepeatedString(
const std::string& field_name,
std::vector<std::unique_ptr<std::string>> StructType::*field) {
fields_.push_back(
- MakeUnique<internal::FieldConverter<
+ std::make_unique<internal::FieldConverter<
StructType, std::vector<std::unique_ptr<std::string>>>>(
field_name, field,
new internal::RepeatedValueConverter<std::string>));
@@ -447,7 +448,7 @@ class JSONValueConverter {
void RegisterRepeatedString(
const std::string& field_name,
std::vector<std::unique_ptr<string16>> StructType::*field) {
- fields_.push_back(MakeUnique<internal::FieldConverter<
+ fields_.push_back(std::make_unique<internal::FieldConverter<
StructType, std::vector<std::unique_ptr<string16>>>>(
field_name, field, new internal::RepeatedValueConverter<string16>));
}
@@ -455,7 +456,7 @@ class JSONValueConverter {
void RegisterRepeatedDouble(
const std::string& field_name,
std::vector<std::unique_ptr<double>> StructType::*field) {
- fields_.push_back(MakeUnique<internal::FieldConverter<
+ fields_.push_back(std::make_unique<internal::FieldConverter<
StructType, std::vector<std::unique_ptr<double>>>>(
field_name, field, new internal::RepeatedValueConverter<double>));
}
@@ -463,7 +464,7 @@ class JSONValueConverter {
void RegisterRepeatedBool(
const std::string& field_name,
std::vector<std::unique_ptr<bool>> StructType::*field) {
- fields_.push_back(MakeUnique<internal::FieldConverter<
+ fields_.push_back(std::make_unique<internal::FieldConverter<
StructType, std::vector<std::unique_ptr<bool>>>>(
field_name, field, new internal::RepeatedValueConverter<bool>));
}
@@ -474,7 +475,7 @@ class JSONValueConverter {
std::vector<std::unique_ptr<NestedType>> StructType::*field,
bool (*convert_func)(const base::Value*, NestedType*)) {
fields_.push_back(
- MakeUnique<internal::FieldConverter<
+ std::make_unique<internal::FieldConverter<
StructType, std::vector<std::unique_ptr<NestedType>>>>(
field_name, field,
new internal::RepeatedCustomValueConverter<NestedType>(
@@ -486,7 +487,7 @@ class JSONValueConverter {
const std::string& field_name,
std::vector<std::unique_ptr<NestedType>> StructType::*field) {
fields_.push_back(
- MakeUnique<internal::FieldConverter<
+ std::make_unique<internal::FieldConverter<
StructType, std::vector<std::unique_ptr<NestedType>>>>(
field_name, field,
new internal::RepeatedMessageConverter<NestedType>));
diff --git a/base/json/json_value_converter_unittest.cc b/base/json/json_value_converter_unittest.cc
index 6a603d3a92..322f5f0a35 100644
--- a/base/json/json_value_converter_unittest.cc
+++ b/base/json/json_value_converter_unittest.cc
@@ -30,7 +30,7 @@ struct SimpleMessage {
std::vector<std::unique_ptr<std::string>> string_values;
SimpleMessage() : foo(0), baz(false), bstruct(false), simple_enum(FOO) {}
- static bool ParseSimpleEnum(const StringPiece& value, SimpleEnum* field) {
+ static bool ParseSimpleEnum(StringPiece value, SimpleEnum* field) {
if (value == "foo") {
*field = FOO;
return true;
@@ -42,12 +42,12 @@ struct SimpleMessage {
}
static bool HasFieldPresent(const base::Value* value, bool* result) {
- *result = value != NULL;
+ *result = value != nullptr;
return true;
}
static bool GetValueString(const base::Value* value, std::string* result) {
- const base::DictionaryValue* dict = NULL;
+ const base::DictionaryValue* dict = nullptr;
if (!value->GetAsDictionary(&dict))
return false;
diff --git a/base/json/json_value_serializer_unittest.cc b/base/json/json_value_serializer_unittest.cc
index e5cb126861..69a56610c3 100644
--- a/base/json/json_value_serializer_unittest.cc
+++ b/base/json/json_value_serializer_unittest.cc
@@ -224,7 +224,7 @@ TEST(JSONValueSerializerTest, Roundtrip) {
Value* null_value = nullptr;
ASSERT_TRUE(root_dict->Get("null", &null_value));
ASSERT_TRUE(null_value);
- ASSERT_TRUE(null_value->IsType(Value::Type::NONE));
+ ASSERT_TRUE(null_value->is_none());
bool bool_value = false;
ASSERT_TRUE(root_dict->GetBoolean("bool", &bool_value));
@@ -417,7 +417,7 @@ TEST_F(JSONFileValueSerializerTest, DISABLED_Roundtrip) {
Value* null_value = nullptr;
ASSERT_TRUE(root_dict->Get("null", &null_value));
ASSERT_TRUE(null_value);
- ASSERT_TRUE(null_value->IsType(Value::Type::NONE));
+ ASSERT_TRUE(null_value->is_none());
bool bool_value = false;
ASSERT_TRUE(root_dict->GetBoolean("bool", &bool_value));
diff --git a/base/json/json_writer.cc b/base/json/json_writer.cc
index 07b9d5091c..e4f1e3cf9d 100644
--- a/base/json/json_writer.cc
+++ b/base/json/json_writer.cc
@@ -56,7 +56,7 @@ JSONWriter::JSONWriter(int options, std::string* json)
}
bool JSONWriter::BuildJSONString(const Value& node, size_t depth) {
- switch (node.GetType()) {
+ switch (node.type()) {
case Value::Type::NONE: {
json_string_->append("null");
return true;
@@ -89,7 +89,7 @@ bool JSONWriter::BuildJSONString(const Value& node, size_t depth) {
json_string_->append(Int64ToString(static_cast<int64_t>(value)));
return result;
}
- std::string real = DoubleToString(value);
+ std::string real = NumberToString(value);
// 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.
@@ -123,12 +123,12 @@ bool JSONWriter::BuildJSONString(const Value& node, size_t depth) {
if (pretty_print_)
json_string_->push_back(' ');
- const ListValue* list = NULL;
+ const ListValue* list = nullptr;
bool first_value_has_been_output = false;
bool result = node.GetAsList(&list);
DCHECK(result);
for (const auto& value : *list) {
- if (omit_binary_values_ && value->GetType() == Value::Type::BINARY)
+ if (omit_binary_values_ && value.type() == Value::Type::BINARY)
continue;
if (first_value_has_been_output) {
@@ -137,7 +137,7 @@ bool JSONWriter::BuildJSONString(const Value& node, size_t depth) {
json_string_->push_back(' ');
}
- if (!BuildJSONString(*value, depth))
+ if (!BuildJSONString(value, depth))
result = false;
first_value_has_been_output = true;
@@ -154,14 +154,13 @@ bool JSONWriter::BuildJSONString(const Value& node, size_t depth) {
if (pretty_print_)
json_string_->append(kPrettyPrintLineEnding);
- const DictionaryValue* dict = NULL;
+ const DictionaryValue* dict = nullptr;
bool first_value_has_been_output = false;
bool result = node.GetAsDictionary(&dict);
DCHECK(result);
for (DictionaryValue::Iterator itr(*dict); !itr.IsAtEnd();
itr.Advance()) {
- if (omit_binary_values_ &&
- itr.value().GetType() == Value::Type::BINARY) {
+ if (omit_binary_values_ && itr.value().type() == Value::Type::BINARY) {
continue;
}
diff --git a/base/json/json_writer_unittest.cc b/base/json/json_writer_unittest.cc
index 6cb236fdc1..2d81af39ca 100644
--- a/base/json/json_writer_unittest.cc
+++ b/base/json/json_writer_unittest.cc
@@ -15,7 +15,7 @@ TEST(JSONWriterTest, BasicTypes) {
std::string output_js;
// Test null.
- EXPECT_TRUE(JSONWriter::Write(*Value::CreateNullValue(), &output_js));
+ EXPECT_TRUE(JSONWriter::Write(Value(), &output_js));
EXPECT_EQ("null", output_js);
// Test empty dict.
@@ -61,7 +61,7 @@ TEST(JSONWriterTest, NestedTypes) {
std::unique_ptr<DictionaryValue> inner_dict(new DictionaryValue());
inner_dict->SetInteger("inner int", 10);
list->Append(std::move(inner_dict));
- list->Append(MakeUnique<ListValue>());
+ list->Append(std::make_unique<ListValue>());
list->AppendBoolean(true);
root_dict.Set("list", std::move(list));
@@ -91,17 +91,17 @@ TEST(JSONWriterTest, KeysWithPeriods) {
std::string output_js;
DictionaryValue period_dict;
- period_dict.SetIntegerWithoutPathExpansion("a.b", 3);
- period_dict.SetIntegerWithoutPathExpansion("c", 2);
+ period_dict.SetKey("a.b", base::Value(3));
+ period_dict.SetKey("c", base::Value(2));
std::unique_ptr<DictionaryValue> period_dict2(new DictionaryValue());
- period_dict2->SetIntegerWithoutPathExpansion("g.h.i.j", 1);
+ period_dict2->SetKey("g.h.i.j", base::Value(1));
period_dict.SetWithoutPathExpansion("d.e.f", std::move(period_dict2));
EXPECT_TRUE(JSONWriter::Write(period_dict, &output_js));
EXPECT_EQ("{\"a.b\":3,\"c\":2,\"d.e.f\":{\"g.h.i.j\":1}}", output_js);
DictionaryValue period_dict3;
period_dict3.SetInteger("a.b", 2);
- period_dict3.SetIntegerWithoutPathExpansion("a.b", 1);
+ period_dict3.SetKey("a.b", base::Value(1));
EXPECT_TRUE(JSONWriter::Write(period_dict3, &output_js));
EXPECT_EQ("{\"a\":{\"b\":2},\"a.b\":1}", output_js);
}
@@ -111,29 +111,29 @@ TEST(JSONWriterTest, BinaryValues) {
// Binary values should return errors unless suppressed via the
// OPTIONS_OMIT_BINARY_VALUES flag.
- std::unique_ptr<Value> root(BinaryValue::CreateWithCopiedBuffer("asdf", 4));
+ std::unique_ptr<Value> root(Value::CreateWithCopiedBuffer("asdf", 4));
EXPECT_FALSE(JSONWriter::Write(*root, &output_js));
EXPECT_TRUE(JSONWriter::WriteWithOptions(
*root, JSONWriter::OPTIONS_OMIT_BINARY_VALUES, &output_js));
EXPECT_TRUE(output_js.empty());
ListValue binary_list;
- binary_list.Append(BinaryValue::CreateWithCopiedBuffer("asdf", 4));
- binary_list.Append(MakeUnique<Value>(5));
- binary_list.Append(BinaryValue::CreateWithCopiedBuffer("asdf", 4));
- binary_list.Append(MakeUnique<Value>(2));
- binary_list.Append(BinaryValue::CreateWithCopiedBuffer("asdf", 4));
+ binary_list.Append(Value::CreateWithCopiedBuffer("asdf", 4));
+ binary_list.Append(std::make_unique<Value>(5));
+ binary_list.Append(Value::CreateWithCopiedBuffer("asdf", 4));
+ binary_list.Append(std::make_unique<Value>(2));
+ binary_list.Append(Value::CreateWithCopiedBuffer("asdf", 4));
EXPECT_FALSE(JSONWriter::Write(binary_list, &output_js));
EXPECT_TRUE(JSONWriter::WriteWithOptions(
binary_list, JSONWriter::OPTIONS_OMIT_BINARY_VALUES, &output_js));
EXPECT_EQ("[5,2]", output_js);
DictionaryValue binary_dict;
- binary_dict.Set("a", BinaryValue::CreateWithCopiedBuffer("asdf", 4));
+ binary_dict.Set("a", Value::CreateWithCopiedBuffer("asdf", 4));
binary_dict.SetInteger("b", 5);
- binary_dict.Set("c", BinaryValue::CreateWithCopiedBuffer("asdf", 4));
+ binary_dict.Set("c", Value::CreateWithCopiedBuffer("asdf", 4));
binary_dict.SetInteger("d", 2);
- binary_dict.Set("e", BinaryValue::CreateWithCopiedBuffer("asdf", 4));
+ binary_dict.Set("e", Value::CreateWithCopiedBuffer("asdf", 4));
EXPECT_FALSE(JSONWriter::Write(binary_dict, &output_js));
EXPECT_TRUE(JSONWriter::WriteWithOptions(
binary_dict, JSONWriter::OPTIONS_OMIT_BINARY_VALUES, &output_js));
diff --git a/base/json/string_escape.cc b/base/json/string_escape.cc
index f67fa93bf2..471a9d30cf 100644
--- a/base/json/string_escape.cc
+++ b/base/json/string_escape.cc
@@ -91,7 +91,9 @@ bool EscapeJSONStringImpl(const S& str, bool put_in_quotes, std::string* dest) {
for (int32_t i = 0; i < length; ++i) {
uint32_t code_point;
- if (!ReadUnicodeCharacter(str.data(), length, &i, &code_point)) {
+ if (!ReadUnicodeCharacter(str.data(), length, &i, &code_point) ||
+ code_point == static_cast<decltype(code_point)>(CBU_SENTINEL) ||
+ !IsValidCharacter(code_point)) {
code_point = kReplacementCodePoint;
did_replacement = true;
}
@@ -114,33 +116,31 @@ bool EscapeJSONStringImpl(const S& str, bool put_in_quotes, std::string* dest) {
} // namespace
-bool EscapeJSONString(const StringPiece& str,
- bool put_in_quotes,
- std::string* dest) {
+bool EscapeJSONString(StringPiece str, bool put_in_quotes, std::string* dest) {
return EscapeJSONStringImpl(str, put_in_quotes, dest);
}
-bool EscapeJSONString(const StringPiece16& str,
+bool EscapeJSONString(StringPiece16 str,
bool put_in_quotes,
std::string* dest) {
return EscapeJSONStringImpl(str, put_in_quotes, dest);
}
-std::string GetQuotedJSONString(const StringPiece& str) {
+std::string GetQuotedJSONString(StringPiece str) {
std::string dest;
bool ok = EscapeJSONStringImpl(str, true, &dest);
DCHECK(ok);
return dest;
}
-std::string GetQuotedJSONString(const StringPiece16& str) {
+std::string GetQuotedJSONString(StringPiece16 str) {
std::string dest;
bool ok = EscapeJSONStringImpl(str, true, &dest);
DCHECK(ok);
return dest;
}
-std::string EscapeBytesAsInvalidJSONString(const StringPiece& str,
+std::string EscapeBytesAsInvalidJSONString(StringPiece str,
bool put_in_quotes) {
std::string dest;
diff --git a/base/json/string_escape.h b/base/json/string_escape.h
index b66b7e5453..f75f475afc 100644
--- a/base/json/string_escape.h
+++ b/base/json/string_escape.h
@@ -14,32 +14,33 @@
namespace base {
-// Appends to |dest| an escaped version of |str|. Valid UTF-8 code units will
-// pass through from the input to the output. Invalid code units will be
-// replaced with the U+FFFD replacement character. This function returns true
-// if no replacement was necessary and false if there was a lossy replacement.
-// On return, |dest| will contain a valid UTF-8 JSON string.
+// Appends to |dest| an escaped version of |str|. Valid UTF-8 code units and
+// characters will pass through from the input to the output. Invalid code
+// units and characters will be replaced with the U+FFFD replacement character.
+// This function returns true if no replacement was necessary and false if
+// there was a lossy replacement. On return, |dest| will contain a valid UTF-8
+// JSON string.
//
// Non-printing control characters will be escaped as \uXXXX sequences for
// readability.
//
// If |put_in_quotes| is true, then a leading and trailing double-quote mark
// will be appended to |dest| as well.
-BASE_EXPORT bool EscapeJSONString(const StringPiece& str,
+BASE_EXPORT bool EscapeJSONString(StringPiece str,
bool put_in_quotes,
std::string* dest);
// Performs a similar function to the UTF-8 StringPiece version above,
// converting UTF-16 code units to UTF-8 code units and escaping non-printing
// control characters. On return, |dest| will contain a valid UTF-8 JSON string.
-BASE_EXPORT bool EscapeJSONString(const StringPiece16& str,
+BASE_EXPORT bool EscapeJSONString(StringPiece16 str,
bool put_in_quotes,
std::string* dest);
// Helper functions that wrap the above two functions but return the value
// instead of appending. |put_in_quotes| is always true.
-BASE_EXPORT std::string GetQuotedJSONString(const StringPiece& str);
-BASE_EXPORT std::string GetQuotedJSONString(const StringPiece16& str);
+BASE_EXPORT std::string GetQuotedJSONString(StringPiece str);
+BASE_EXPORT std::string GetQuotedJSONString(StringPiece16 str);
// Given an arbitrary byte string |str|, this will escape all non-ASCII bytes
// as \uXXXX escape sequences. This function is *NOT* meant to be used with
@@ -52,7 +53,7 @@ BASE_EXPORT std::string GetQuotedJSONString(const StringPiece16& str);
//
// The output of this function takes the *appearance* of JSON but is not in
// fact valid according to RFC 4627.
-BASE_EXPORT std::string EscapeBytesAsInvalidJSONString(const StringPiece& str,
+BASE_EXPORT std::string EscapeBytesAsInvalidJSONString(StringPiece str,
bool put_in_quotes);
} // namespace base
diff --git a/base/json/string_escape_fuzzer.cc b/base/json/string_escape_fuzzer.cc
new file mode 100644
index 0000000000..f4304118c6
--- /dev/null
+++ b/base/json/string_escape_fuzzer.cc
@@ -0,0 +1,37 @@
+// 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.
+
+#include "base/json/string_escape.h"
+
+#include <memory>
+
+// Entry point for LibFuzzer.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if (size < 2)
+ return 0;
+
+ const bool put_in_quotes = data[size - 1];
+
+ // Create a copy of input buffer, as otherwise we don't catch
+ // overflow that touches the last byte (which is used in put_in_quotes).
+ size_t actual_size_char8 = size - 1;
+ std::unique_ptr<char[]> input(new char[actual_size_char8]);
+ memcpy(input.get(), data, actual_size_char8);
+
+ base::StringPiece input_string(input.get(), actual_size_char8);
+ std::string escaped_string;
+ base::EscapeJSONString(input_string, put_in_quotes, &escaped_string);
+
+ // Test for wide-strings if available size is even.
+ if (actual_size_char8 & 1)
+ return 0;
+
+ size_t actual_size_char16 = actual_size_char8 / 2;
+ base::StringPiece16 input_string16(
+ reinterpret_cast<base::char16*>(input.get()), actual_size_char16);
+ escaped_string.clear();
+ base::EscapeJSONString(input_string16, put_in_quotes, &escaped_string);
+
+ return 0;
+}
diff --git a/base/json/string_escape_unittest.cc b/base/json/string_escape_unittest.cc
index ae3d82ad5e..1e962c6041 100644
--- a/base/json/string_escape_unittest.cc
+++ b/base/json/string_escape_unittest.cc
@@ -18,14 +18,14 @@ TEST(JSONStringEscapeTest, EscapeUTF8) {
const char* to_escape;
const char* escaped;
} cases[] = {
- {"\b\001aZ\"\\wee", "\\b\\u0001aZ\\\"\\\\wee"},
- {"a\b\f\n\r\t\v\1\\.\"z",
- "a\\b\\f\\n\\r\\t\\u000B\\u0001\\\\.\\\"z"},
- {"b\x0f\x7f\xf0\xff!", // \xf0\xff is not a valid UTF-8 unit.
- "b\\u000F\x7F\xEF\xBF\xBD\xEF\xBF\xBD!"},
- {"c<>d", "c\\u003C>d"},
- {"Hello\xe2\x80\xa8world", "Hello\\u2028world"},
- {"\xe2\x80\xa9purple", "\\u2029purple"},
+ {"\b\001aZ\"\\wee", "\\b\\u0001aZ\\\"\\\\wee"},
+ {"a\b\f\n\r\t\v\1\\.\"z", "a\\b\\f\\n\\r\\t\\u000B\\u0001\\\\.\\\"z"},
+ {"b\x0f\x7f\xf0\xff!", // \xf0\xff is not a valid UTF-8 unit.
+ "b\\u000F\x7F\xEF\xBF\xBD\xEF\xBF\xBD!"},
+ {"c<>d", "c\\u003C>d"},
+ {"Hello\xe2\x80\xa8world", "Hello\\u2028world"},
+ {"\xe2\x80\xa9purple", "\\u2029purple"},
+ {"\xF3\xBF\xBF\xBF", "\xEF\xBF\xBD"},
};
for (size_t i = 0; i < arraysize(cases); ++i) {
diff --git a/base/lazy_instance.cc b/base/lazy_instance.cc
deleted file mode 100644
index 54680655a2..0000000000
--- a/base/lazy_instance.cc
+++ /dev/null
@@ -1,54 +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/lazy_instance.h"
-
-#include "base/at_exit.h"
-#include "base/atomicops.h"
-#include "base/threading/platform_thread.h"
-
-namespace base {
-namespace internal {
-
-// TODO(joth): This function could be shared with Singleton, in place of its
-// WaitForInstance() call.
-bool NeedsLazyInstance(subtle::AtomicWord* state) {
- // Try to create the instance, if we're the first, will go from 0 to
- // kLazyInstanceStateCreating, otherwise we've already been beaten here.
- // The memory access has no memory ordering as state 0 and
- // kLazyInstanceStateCreating have no associated data (memory barriers are
- // all about ordering of memory accesses to *associated* data).
- if (subtle::NoBarrier_CompareAndSwap(state, 0,
- kLazyInstanceStateCreating) == 0)
- // Caller must create instance
- return true;
-
- // It's either in the process of being created, or already created. Spin.
- // The load has acquire memory ordering as a thread which sees
- // state_ == STATE_CREATED needs to acquire visibility over
- // the associated data (buf_). Pairing Release_Store is in
- // CompleteLazyInstance().
- while (subtle::Acquire_Load(state) == kLazyInstanceStateCreating) {
- PlatformThread::YieldCurrentThread();
- }
- // Someone else created the instance.
- return false;
-}
-
-void CompleteLazyInstance(subtle::AtomicWord* state,
- subtle::AtomicWord new_instance,
- void* lazy_instance,
- void (*dtor)(void*)) {
- // Instance is created, go from CREATING to CREATED.
- // Releases visibility over private_buf_ to readers. Pairing Acquire_Load's
- // are in NeedsInstance() and Pointer().
- subtle::Release_Store(state, new_instance);
-
- // Make sure that the lazily instantiated object will get destroyed at exit.
- if (dtor)
- AtExitManager::RegisterCallback(dtor, lazy_instance);
-}
-
-} // namespace internal
-} // namespace base
diff --git a/base/lazy_instance.h b/base/lazy_instance.h
index b0d72bcbb5..4449373ead 100644
--- a/base/lazy_instance.h
+++ b/base/lazy_instance.h
@@ -1,7 +1,17 @@
// 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.
-
+//
+// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DEPRECATED !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+// Please don't introduce new instances of LazyInstance<T>. Use a function-local
+// static of type base::NoDestructor<T> instead:
+//
+// Factory& Factory::GetInstance() {
+// static base::NoDestructor<Factory> instance;
+// return *instance;
+// }
+// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+//
// The LazyInstance<Type, Traits> class manages a single instance of Type,
// which will be lazily created on the first time it's accessed. This class is
// useful for places you would normally use a function-level static, but you
@@ -38,28 +48,21 @@
#include <new> // For placement new.
#include "base/atomicops.h"
-#include "base/base_export.h"
#include "base/debug/leak_annotations.h"
+#include "base/lazy_instance_helpers.h"
#include "base/logging.h"
-#include "base/memory/aligned_memory.h"
#include "base/threading/thread_restrictions.h"
// LazyInstance uses its own struct initializer-list style static
-// initialization, as base's LINKER_INITIALIZED requires a constructor and on
-// some compilers (notably gcc 4.4) this still ends up needing runtime
-// initialization.
-#ifdef __clang__
- #define LAZY_INSTANCE_INITIALIZER {}
-#else
- #define LAZY_INSTANCE_INITIALIZER {0, 0}
-#endif
+// initialization, which does not require a constructor.
+#define LAZY_INSTANCE_INITIALIZER {}
namespace base {
template <typename Type>
struct LazyInstanceTraitsBase {
static Type* New(void* instance) {
- DCHECK_EQ(reinterpret_cast<uintptr_t>(instance) & (ALIGNOF(Type) - 1), 0u);
+ DCHECK_EQ(reinterpret_cast<uintptr_t>(instance) & (alignof(Type) - 1), 0u);
// Use placement new to initialize our instance in our preallocated space.
// The parenthesis is very important here to force POD type initialization.
return new (instance) Type();
@@ -120,22 +123,6 @@ struct LeakyLazyInstanceTraits {
template <typename Type>
struct ErrorMustSelectLazyOrDestructorAtExitForLazyInstance {};
-// Our AtomicWord doubles as a spinlock, where a value of
-// kLazyInstanceStateCreating means the spinlock is being held for creation.
-static const subtle::AtomicWord kLazyInstanceStateCreating = 1;
-
-// Check if instance needs to be created. If so return true otherwise
-// if another thread has beat us, wait for instance to be created and
-// return false.
-BASE_EXPORT bool NeedsLazyInstance(subtle::AtomicWord* state);
-
-// After creating an instance, call this to register the dtor to be called
-// at program exit and to update the atomic state to hold the |new_instance|
-BASE_EXPORT void CompleteLazyInstance(subtle::AtomicWord* state,
- subtle::AtomicWord new_instance,
- void* lazy_instance,
- void (*dtor)(void*));
-
} // namespace internal
template <
@@ -163,52 +150,44 @@ class LazyInstance {
Type* Pointer() {
#if DCHECK_IS_ON()
- // Avoid making TLS lookup on release builds.
if (!Traits::kAllowedToAccessOnNonjoinableThread)
ThreadRestrictions::AssertSingletonAllowed();
#endif
- // If any bit in the created mask is true, the instance has already been
- // fully constructed.
- static const subtle::AtomicWord kLazyInstanceCreatedMask =
- ~internal::kLazyInstanceStateCreating;
-
- // We will hopefully have fast access when the instance is already created.
- // Since a thread sees private_instance_ == 0 or kLazyInstanceStateCreating
- // at most once, the load is taken out of NeedsInstance() as a fast-path.
- // The load has acquire memory ordering as a thread which sees
- // private_instance_ > creating needs to acquire visibility over
- // the associated data (private_buf_). Pairing Release_Store is in
- // CompleteLazyInstance().
- subtle::AtomicWord value = subtle::Acquire_Load(&private_instance_);
- if (!(value & kLazyInstanceCreatedMask) &&
- internal::NeedsLazyInstance(&private_instance_)) {
- // Create the instance in the space provided by |private_buf_|.
- value = reinterpret_cast<subtle::AtomicWord>(
- Traits::New(private_buf_.void_data()));
- internal::CompleteLazyInstance(&private_instance_, value, this,
- Traits::kRegisterOnExit ? OnExit : NULL);
- }
- return instance();
+
+ return subtle::GetOrCreateLazyPointer(
+ &private_instance_, &Traits::New, private_buf_,
+ Traits::kRegisterOnExit ? OnExit : nullptr, this);
}
- bool operator==(Type* p) {
- switch (subtle::NoBarrier_Load(&private_instance_)) {
- case 0:
- return p == NULL;
- case internal::kLazyInstanceStateCreating:
- return static_cast<void*>(p) == private_buf_.void_data();
- default:
- return p == instance();
- }
+ // Returns true if the lazy instance has been created. Unlike Get() and
+ // Pointer(), calling IsCreated() will not instantiate the object of Type.
+ bool IsCreated() {
+ // Return true (i.e. "created") if |private_instance_| is either being
+ // created right now (i.e. |private_instance_| has value of
+ // internal::kLazyInstanceStateCreating) or was already created (i.e.
+ // |private_instance_| has any other non-zero value).
+ return 0 != subtle::NoBarrier_Load(&private_instance_);
}
+ // MSVC gives a warning that the alignment expands the size of the
+ // LazyInstance struct to make the size a multiple of the alignment. This
+ // is expected in this case.
+#if defined(OS_WIN)
+#pragma warning(push)
+#pragma warning(disable: 4324)
+#endif
+
// Effectively private: member data is only public to allow the linker to
// statically initialize it and to maintain a POD class. DO NOT USE FROM
// OUTSIDE THIS CLASS.
-
subtle::AtomicWord private_instance_;
+
// Preallocated space for the Type instance.
- base::AlignedMemory<sizeof(Type), ALIGNOF(Type)> private_buf_;
+ alignas(Type) char private_buf_[sizeof(Type)];
+
+#if defined(OS_WIN)
+#pragma warning(pop)
+#endif
private:
Type* instance() {
diff --git a/base/lazy_instance_helpers.cc b/base/lazy_instance_helpers.cc
new file mode 100644
index 0000000000..7b9e0de7c6
--- /dev/null
+++ b/base/lazy_instance_helpers.cc
@@ -0,0 +1,64 @@
+// 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.
+
+#include "base/lazy_instance_helpers.h"
+
+#include "base/at_exit.h"
+#include "base/atomicops.h"
+#include "base/threading/platform_thread.h"
+
+namespace base {
+namespace internal {
+
+bool NeedsLazyInstance(subtle::AtomicWord* state) {
+ // Try to create the instance, if we're the first, will go from 0 to
+ // kLazyInstanceStateCreating, otherwise we've already been beaten here.
+ // The memory access has no memory ordering as state 0 and
+ // kLazyInstanceStateCreating have no associated data (memory barriers are
+ // all about ordering of memory accesses to *associated* data).
+ if (subtle::NoBarrier_CompareAndSwap(state, 0, kLazyInstanceStateCreating) ==
+ 0) {
+ // Caller must create instance
+ return true;
+ }
+
+ // It's either in the process of being created, or already created. Spin.
+ // The load has acquire memory ordering as a thread which sees
+ // state_ == STATE_CREATED needs to acquire visibility over
+ // the associated data (buf_). Pairing Release_Store is in
+ // CompleteLazyInstance().
+ if (subtle::Acquire_Load(state) == kLazyInstanceStateCreating) {
+ const base::TimeTicks start = base::TimeTicks::Now();
+ do {
+ const base::TimeDelta elapsed = base::TimeTicks::Now() - start;
+ // Spin with YieldCurrentThread for at most one ms - this ensures maximum
+ // responsiveness. After that spin with Sleep(1ms) so that we don't burn
+ // excessive CPU time - this also avoids infinite loops due to priority
+ // inversions (https://crbug.com/797129).
+ if (elapsed < TimeDelta::FromMilliseconds(1))
+ PlatformThread::YieldCurrentThread();
+ else
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(1));
+ } while (subtle::Acquire_Load(state) == kLazyInstanceStateCreating);
+ }
+ // Someone else created the instance.
+ return false;
+}
+
+void CompleteLazyInstance(subtle::AtomicWord* state,
+ subtle::AtomicWord new_instance,
+ void (*destructor)(void*),
+ void* destructor_arg) {
+ // Instance is created, go from CREATING to CREATED (or reset it if
+ // |new_instance| is null). Releases visibility over |private_buf_| to
+ // readers. Pairing Acquire_Load is in NeedsLazyInstance().
+ subtle::Release_Store(state, new_instance);
+
+ // Make sure that the lazily instantiated object will get destroyed at exit.
+ if (new_instance && destructor)
+ AtExitManager::RegisterCallback(destructor, destructor_arg);
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/lazy_instance_helpers.h b/base/lazy_instance_helpers.h
new file mode 100644
index 0000000000..5a43d8b1f2
--- /dev/null
+++ b/base/lazy_instance_helpers.h
@@ -0,0 +1,101 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_LAZY_INSTANCE_INTERNAL_H_
+#define BASE_LAZY_INSTANCE_INTERNAL_H_
+
+#include "base/atomicops.h"
+#include "base/base_export.h"
+#include "base/logging.h"
+
+// Helper methods used by LazyInstance and a few other base APIs for thread-safe
+// lazy construction.
+
+namespace base {
+namespace internal {
+
+// Our AtomicWord doubles as a spinlock, where a value of
+// kLazyInstanceStateCreating means the spinlock is being held for creation.
+constexpr subtle::AtomicWord kLazyInstanceStateCreating = 1;
+
+// Helper for GetOrCreateLazyPointer(). Checks if instance needs to be created.
+// If so returns true otherwise if another thread has beat us, waits for
+// instance to be created and returns false.
+BASE_EXPORT bool NeedsLazyInstance(subtle::AtomicWord* state);
+
+// Helper for GetOrCreateLazyPointer(). After creating an instance, this is
+// called to register the dtor to be called at program exit and to update the
+// atomic state to hold the |new_instance|
+BASE_EXPORT void CompleteLazyInstance(subtle::AtomicWord* state,
+ subtle::AtomicWord new_instance,
+ void (*destructor)(void*),
+ void* destructor_arg);
+
+} // namespace internal
+
+namespace subtle {
+
+// If |state| is uninitialized (zero), constructs a value using
+// |creator_func(creator_arg)|, stores it into |state| and registers
+// |destructor(destructor_arg)| to be called when the current AtExitManager goes
+// out of scope. Then, returns the value stored in |state|. It is safe to have
+// concurrent calls to this function with the same |state|. |creator_func| may
+// return nullptr if it doesn't want to create an instance anymore (e.g. on
+// shutdown), it is from then on required to return nullptr to all callers (ref.
+// StaticMemorySingletonTraits). In that case, callers need to synchronize
+// before |creator_func| may return a non-null instance again (ref.
+// StaticMemorySingletonTraits::ResurectForTesting()).
+// Implementation note on |creator_func/creator_arg|. It makes for ugly adapters
+// but it avoids redundant template instantiations (e.g. saves 27KB in
+// chrome.dll) because linker is able to fold these for multiple Types but
+// couldn't with the more advanced CreatorFunc template type which in turn
+// improves code locality (and application startup) -- ref.
+// https://chromium-review.googlesource.com/c/chromium/src/+/530984/5/base/lazy_instance.h#140,
+// worsened by https://chromium-review.googlesource.com/c/chromium/src/+/868013
+// and caught then as https://crbug.com/804034.
+template <typename Type>
+Type* GetOrCreateLazyPointer(subtle::AtomicWord* state,
+ Type* (*creator_func)(void*),
+ void* creator_arg,
+ void (*destructor)(void*),
+ void* destructor_arg) {
+ DCHECK(state);
+ DCHECK(creator_func);
+
+ // If any bit in the created mask is true, the instance has already been
+ // fully constructed.
+ constexpr subtle::AtomicWord kLazyInstanceCreatedMask =
+ ~internal::kLazyInstanceStateCreating;
+
+ // We will hopefully have fast access when the instance is already created.
+ // Since a thread sees |state| == 0 or kLazyInstanceStateCreating at most
+ // once, the load is taken out of NeedsLazyInstance() as a fast-path. The load
+ // has acquire memory ordering as a thread which sees |state| > creating needs
+ // to acquire visibility over the associated data. Pairing Release_Store is in
+ // CompleteLazyInstance().
+ subtle::AtomicWord instance = subtle::Acquire_Load(state);
+ if (!(instance & kLazyInstanceCreatedMask)) {
+ if (internal::NeedsLazyInstance(state)) {
+ // This thread won the race and is now responsible for creating the
+ // instance and storing it back into |state|.
+ instance =
+ reinterpret_cast<subtle::AtomicWord>((*creator_func)(creator_arg));
+ internal::CompleteLazyInstance(state, instance, destructor,
+ destructor_arg);
+ } else {
+ // This thread lost the race but now has visibility over the constructed
+ // instance (NeedsLazyInstance() doesn't return until the constructing
+ // thread releases the instance via CompleteLazyInstance()).
+ instance = subtle::Acquire_Load(state);
+ DCHECK(instance & kLazyInstanceCreatedMask);
+ }
+ }
+ return reinterpret_cast<Type*>(instance);
+}
+
+} // namespace subtle
+
+} // namespace base
+
+#endif // BASE_LAZY_INSTANCE_INTERNAL_H_
diff --git a/base/lazy_instance_unittest.cc b/base/lazy_instance_unittest.cc
index 0aa4659465..a5f024cf5c 100644
--- a/base/lazy_instance_unittest.cc
+++ b/base/lazy_instance_unittest.cc
@@ -4,17 +4,26 @@
#include <stddef.h>
+#include <memory>
+#include <vector>
+
#include "base/at_exit.h"
#include "base/atomic_sequence_num.h"
+#include "base/atomicops.h"
+#include "base/barrier_closure.h"
+#include "base/bind.h"
#include "base/lazy_instance.h"
-#include "base/memory/aligned_memory.h"
+#include "base/sys_info.h"
+#include "base/threading/platform_thread.h"
#include "base/threading/simple_thread.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
-base::StaticAtomicSequenceNumber constructed_seq_;
-base::StaticAtomicSequenceNumber destructed_seq_;
+base::AtomicSequenceNumber constructed_seq_;
+base::AtomicSequenceNumber destructed_seq_;
class ConstructAndDestructLogger {
public:
@@ -24,6 +33,9 @@ class ConstructAndDestructLogger {
~ConstructAndDestructLogger() {
destructed_seq_.GetNext();
}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConstructAndDestructLogger);
};
class SlowConstructor {
@@ -39,8 +51,11 @@ class SlowConstructor {
static int constructed;
private:
int some_int_;
+
+ DISALLOW_COPY_AND_ASSIGN(SlowConstructor);
};
+// static
int SlowConstructor::constructed = 0;
class SlowDelegate : public base::DelegateSimpleThread::Delegate {
@@ -56,33 +71,39 @@ class SlowDelegate : public base::DelegateSimpleThread::Delegate {
private:
base::LazyInstance<SlowConstructor>::DestructorAtExit* lazy_;
+
+ DISALLOW_COPY_AND_ASSIGN(SlowDelegate);
};
} // namespace
-static base::LazyInstance<ConstructAndDestructLogger>::DestructorAtExit
- lazy_logger = LAZY_INSTANCE_INITIALIZER;
+base::LazyInstance<ConstructAndDestructLogger>::DestructorAtExit lazy_logger =
+ LAZY_INSTANCE_INITIALIZER;
TEST(LazyInstanceTest, Basic) {
{
base::ShadowingAtExitManager shadow;
+ EXPECT_FALSE(lazy_logger.IsCreated());
EXPECT_EQ(0, constructed_seq_.GetNext());
EXPECT_EQ(0, destructed_seq_.GetNext());
lazy_logger.Get();
+ EXPECT_TRUE(lazy_logger.IsCreated());
EXPECT_EQ(2, constructed_seq_.GetNext());
EXPECT_EQ(1, destructed_seq_.GetNext());
lazy_logger.Pointer();
+ EXPECT_TRUE(lazy_logger.IsCreated());
EXPECT_EQ(3, constructed_seq_.GetNext());
EXPECT_EQ(2, destructed_seq_.GetNext());
}
+ EXPECT_FALSE(lazy_logger.IsCreated());
EXPECT_EQ(4, constructed_seq_.GetNext());
EXPECT_EQ(4, destructed_seq_.GetNext());
}
-static base::LazyInstance<SlowConstructor>::DestructorAtExit lazy_slow =
+base::LazyInstance<SlowConstructor>::DestructorAtExit lazy_slow =
LAZY_INSTANCE_INITIALIZER;
TEST(LazyInstanceTest, ConstructorThreadSafety) {
@@ -108,7 +129,7 @@ namespace {
// It accepts a bool* and sets the bool to true when the dtor runs.
class DeleteLogger {
public:
- DeleteLogger() : deleted_(NULL) {}
+ DeleteLogger() : deleted_(nullptr) {}
~DeleteLogger() { *deleted_ = true; }
void SetDeletedPtr(bool* deleted) {
@@ -150,12 +171,12 @@ namespace {
template <size_t alignment>
class AlignedData {
public:
- AlignedData() {}
- ~AlignedData() {}
- base::AlignedMemory<alignment, alignment> data_;
+ AlignedData() = default;
+ ~AlignedData() = default;
+ alignas(alignment) char data_[alignment];
};
-} // anonymous namespace
+} // namespace
#define EXPECT_ALIGNED(ptr, align) \
EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(ptr) & (align - 1))
@@ -177,3 +198,125 @@ TEST(LazyInstanceTest, Alignment) {
EXPECT_ALIGNED(align32.Pointer(), 32);
EXPECT_ALIGNED(align4096.Pointer(), 4096);
}
+
+namespace {
+
+// A class whose constructor busy-loops until it is told to complete
+// construction.
+class BlockingConstructor {
+ public:
+ BlockingConstructor() {
+ EXPECT_FALSE(WasConstructorCalled());
+ base::subtle::NoBarrier_Store(&constructor_called_, 1);
+ EXPECT_TRUE(WasConstructorCalled());
+ while (!base::subtle::NoBarrier_Load(&complete_construction_))
+ base::PlatformThread::YieldCurrentThread();
+ done_construction_ = true;
+ }
+
+ ~BlockingConstructor() {
+ // Restore static state for the next test.
+ base::subtle::NoBarrier_Store(&constructor_called_, 0);
+ base::subtle::NoBarrier_Store(&complete_construction_, 0);
+ }
+
+ // Returns true if BlockingConstructor() was entered.
+ static bool WasConstructorCalled() {
+ return base::subtle::NoBarrier_Load(&constructor_called_);
+ }
+
+ // Instructs BlockingConstructor() that it may now unblock its construction.
+ static void CompleteConstructionNow() {
+ base::subtle::NoBarrier_Store(&complete_construction_, 1);
+ }
+
+ bool done_construction() { return done_construction_; }
+
+ private:
+ // Use Atomic32 instead of AtomicFlag for them to be trivially initialized.
+ static base::subtle::Atomic32 constructor_called_;
+ static base::subtle::Atomic32 complete_construction_;
+
+ bool done_construction_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(BlockingConstructor);
+};
+
+// A SimpleThread running at |thread_priority| which invokes |before_get|
+// (optional) and then invokes Get() on the LazyInstance it's assigned.
+class BlockingConstructorThread : public base::SimpleThread {
+ public:
+ BlockingConstructorThread(
+ base::ThreadPriority thread_priority,
+ base::LazyInstance<BlockingConstructor>::DestructorAtExit* lazy,
+ base::OnceClosure before_get)
+ : SimpleThread("BlockingConstructorThread", Options(thread_priority)),
+ lazy_(lazy),
+ before_get_(std::move(before_get)) {}
+
+ void Run() override {
+ if (before_get_)
+ std::move(before_get_).Run();
+ EXPECT_TRUE(lazy_->Get().done_construction());
+ }
+
+ private:
+ base::LazyInstance<BlockingConstructor>::DestructorAtExit* lazy_;
+ base::OnceClosure before_get_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlockingConstructorThread);
+};
+
+// static
+base::subtle::Atomic32 BlockingConstructor::constructor_called_ = 0;
+// static
+base::subtle::Atomic32 BlockingConstructor::complete_construction_ = 0;
+
+base::LazyInstance<BlockingConstructor>::DestructorAtExit lazy_blocking =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// Tests that if the thread assigned to construct the LazyInstance runs at
+// background priority : the foreground threads will yield to it enough for it
+// to eventually complete construction.
+// This is a regression test for https://crbug.com/797129.
+TEST(LazyInstanceTest, PriorityInversionAtInitializationResolves) {
+ base::TimeTicks test_begin = base::TimeTicks::Now();
+
+ // Construct BlockingConstructor from a background thread.
+ BlockingConstructorThread background_getter(
+ base::ThreadPriority::BACKGROUND, &lazy_blocking, base::OnceClosure());
+ background_getter.Start();
+
+ while (!BlockingConstructor::WasConstructorCalled())
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1));
+
+ // Spin 4 foreground thread per core contending to get the already under
+ // construction LazyInstance. When they are all running and poking at it :
+ // allow the background thread to complete its work.
+ const int kNumForegroundThreads = 4 * base::SysInfo::NumberOfProcessors();
+ std::vector<std::unique_ptr<base::SimpleThread>> foreground_threads;
+ base::RepeatingClosure foreground_thread_ready_callback =
+ base::BarrierClosure(
+ kNumForegroundThreads,
+ base::BindOnce(&BlockingConstructor::CompleteConstructionNow));
+ for (int i = 0; i < kNumForegroundThreads; ++i) {
+ foreground_threads.push_back(std::make_unique<BlockingConstructorThread>(
+ base::ThreadPriority::NORMAL, &lazy_blocking,
+ foreground_thread_ready_callback));
+ foreground_threads.back()->Start();
+ }
+
+ // This test will hang if the foreground threads become stuck in
+ // LazyInstance::Get() per the background thread never being scheduled to
+ // complete construction.
+ for (auto& foreground_thread : foreground_threads)
+ foreground_thread->Join();
+ background_getter.Join();
+
+ // Fail if this test takes more than 5 seconds (it takes 5-10 seconds on a
+ // Z840 without r527445 but is expected to be fast (~30ms) with the fix).
+ EXPECT_LT(base::TimeTicks::Now() - test_begin,
+ base::TimeDelta::FromSeconds(5));
+}
diff --git a/base/location.cc b/base/location.cc
index 1333e6ec45..8bbf6edaa4 100644
--- a/base/location.cc
+++ b/base/location.cc
@@ -2,17 +2,24 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "build/build_config.h"
+#include "base/location.h"
#if defined(COMPILER_MSVC)
#include <intrin.h>
#endif
-#include "base/location.h"
+#include "base/compiler_specific.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
-namespace tracked_objects {
+namespace base {
+
+Location::Location() = default;
+Location::Location(const Location& other) = default;
+
+Location::Location(const char* file_name, const void* program_counter)
+ : file_name_(file_name), program_counter_(program_counter) {}
Location::Location(const char* function_name,
const char* file_name,
@@ -22,85 +29,49 @@ Location::Location(const char* function_name,
file_name_(file_name),
line_number_(line_number),
program_counter_(program_counter) {
-}
-
-Location::Location()
- : function_name_("Unknown"),
- file_name_("Unknown"),
- line_number_(-1),
- program_counter_(NULL) {
-}
-
-Location::Location(const Location& other)
- : function_name_(other.function_name_),
- file_name_(other.file_name_),
- line_number_(other.line_number_),
- program_counter_(other.program_counter_) {
+#if !defined(OS_NACL)
+ // The program counter should not be null except in a default constructed
+ // (empty) Location object. This value is used for identity, so if it doesn't
+ // uniquely identify a location, things will break.
+ //
+ // The program counter isn't supported in NaCl so location objects won't work
+ // properly in that context.
+ DCHECK(program_counter);
+#endif
}
std::string Location::ToString() const {
- return std::string(function_name_) + "@" + file_name_ + ":" +
- base::IntToString(line_number_);
-}
-
-void Location::Write(bool display_filename, bool display_function_name,
- std::string* output) const {
- base::StringAppendF(output, "%s[%d] ",
- display_filename ? file_name_ : "line",
- line_number_);
-
- if (display_function_name) {
- WriteFunctionName(output);
- output->push_back(' ');
+ if (has_source_info()) {
+ return std::string(function_name_) + "@" + file_name_ + ":" +
+ IntToString(line_number_);
}
+ return StringPrintf("pc:%p", program_counter_);
}
-void Location::WriteFunctionName(std::string* output) const {
- // Translate "<" to "&lt;" for HTML safety.
- // TODO(jar): Support ASCII or html for logging in ASCII.
- for (const char *p = function_name_; *p; p++) {
- switch (*p) {
- case '<':
- output->append("&lt;");
- break;
-
- case '>':
- output->append("&gt;");
- break;
-
- default:
- output->push_back(*p);
- break;
- }
- }
-}
-
-//------------------------------------------------------------------------------
-LocationSnapshot::LocationSnapshot() : line_number(-1) {
-}
+#if defined(COMPILER_MSVC)
+#define RETURN_ADDRESS() _ReturnAddress()
+#elif defined(COMPILER_GCC) && !defined(OS_NACL)
+#define RETURN_ADDRESS() \
+ __builtin_extract_return_addr(__builtin_return_address(0))
+#else
+#define RETURN_ADDRESS() nullptr
+#endif
-LocationSnapshot::LocationSnapshot(
- const tracked_objects::Location& location)
- : file_name(location.file_name()),
- function_name(location.function_name()),
- line_number(location.line_number()) {
+// static
+NOINLINE Location Location::CreateFromHere(const char* file_name) {
+ return Location(file_name, RETURN_ADDRESS());
}
-LocationSnapshot::~LocationSnapshot() {
+// static
+NOINLINE Location Location::CreateFromHere(const char* function_name,
+ const char* file_name,
+ int line_number) {
+ return Location(function_name, file_name, line_number, RETURN_ADDRESS());
}
//------------------------------------------------------------------------------
-#if defined(COMPILER_MSVC)
-__declspec(noinline)
-#endif
-BASE_EXPORT const void* GetProgramCounter() {
-#if defined(COMPILER_MSVC)
- return _ReturnAddress();
-#elif defined(COMPILER_GCC) && !defined(OS_NACL)
- return __builtin_extract_return_addr(__builtin_return_address(0));
-#else
- return NULL;
-#endif
+NOINLINE const void* GetProgramCounter() {
+ return RETURN_ADDRESS();
}
-} // namespace tracked_objects
+} // namespace base
diff --git a/base/location.h b/base/location.h
index dd78515ce2..14fe2fa6be 100644
--- a/base/location.h
+++ b/base/location.h
@@ -11,14 +11,23 @@
#include <string>
#include "base/base_export.h"
+#include "base/debug/debugging_buildflags.h"
#include "base/hash.h"
-namespace tracked_objects {
+namespace base {
// Location provides basic info where of an object was constructed, or was
// significantly brought to life.
class BASE_EXPORT Location {
public:
+ Location();
+ Location(const Location& other);
+
+ // Only initializes the file name and program counter, the source information
+ // will be null for the strings, and -1 for the line number.
+ // TODO(http://crbug.com/760702) remove file name from this constructor.
+ Location(const char* file_name, const void* program_counter);
+
// Constructor should be called with a long-lived char*, such as __FILE__.
// It assumes the provided value will persist as a global constant, and it
// will not make a copy of it.
@@ -27,84 +36,82 @@ class BASE_EXPORT Location {
int line_number,
const void* program_counter);
- // Provide a default constructor for easy of debugging.
- Location();
-
- // Copy constructor.
- Location(const Location& other);
-
- // Comparator for hash map insertion.
- // No need to use |function_name_| since the other two fields uniquely
- // identify this location.
+ // Comparator for hash map insertion. The program counter should uniquely
+ // identify a location.
bool operator==(const Location& other) const {
- return line_number_ == other.line_number_ &&
- file_name_ == other.file_name_;
+ return program_counter_ == other.program_counter_;
}
- const char* function_name() const { return function_name_; }
- const char* file_name() const { return file_name_; }
- int line_number() const { return line_number_; }
+ // Returns true if there is source code location info. If this is false,
+ // the Location object only contains a program counter or is
+ // default-initialized (the program counter is also null).
+ bool has_source_info() const { return function_name_ && file_name_; }
+
+ // Will be nullptr for default initialized Location objects and when source
+ // names are disabled.
+ const char* function_name() const { return function_name_; }
+
+ // Will be nullptr for default initialized Location objects and when source
+ // names are disabled.
+ const char* file_name() const { return file_name_; }
+
+ // Will be -1 for default initialized Location objects and when source names
+ // are disabled.
+ int line_number() const { return line_number_; }
+
+ // The address of the code generating this Location object. Should always be
+ // valid except for default initialized Location objects, which will be
+ // nullptr.
const void* program_counter() const { return program_counter_; }
+ // Converts to the most user-readable form possible. If function and filename
+ // are not available, this will return "pc:<hex address>".
std::string ToString() const;
- // Hash operator for hash maps.
- struct Hash {
- size_t operator()(const Location& location) const {
- // Compute the hash value using file name pointer and line number.
- // No need to use |function_name_| since the other two fields uniquely
- // identify this location.
-
- // The file name will always be uniquely identified by its pointer since
- // it comes from __FILE__, so no need to check the contents of the string.
- // See the definition of FROM_HERE in location.h, and how it is used
- // elsewhere.
- return base::HashInts(reinterpret_cast<uintptr_t>(location.file_name()),
- location.line_number());
- }
- };
-
- // Translate the some of the state in this instance into a human readable
- // string with HTML characters in the function names escaped, and append that
- // string to |output|. Inclusion of the file_name_ and function_name_ are
- // optional, and controlled by the boolean arguments.
- void Write(bool display_filename, bool display_function_name,
- std::string* output) const;
-
- // Write function_name_ in HTML with '<' and '>' properly encoded.
- void WriteFunctionName(std::string* output) const;
+ static Location CreateFromHere(const char* file_name);
+ static Location CreateFromHere(const char* function_name,
+ const char* file_name,
+ int line_number);
private:
- const char* function_name_;
- const char* file_name_;
- int line_number_;
- const void* program_counter_;
-};
-
-// A "snapshotted" representation of the Location class that can safely be
-// passed across process boundaries.
-struct BASE_EXPORT LocationSnapshot {
- // The default constructor is exposed to support the IPC serialization macros.
- LocationSnapshot();
- explicit LocationSnapshot(const tracked_objects::Location& location);
- ~LocationSnapshot();
-
- std::string file_name;
- std::string function_name;
- int line_number;
+ const char* function_name_ = nullptr;
+ const char* file_name_ = nullptr;
+ int line_number_ = -1;
+ const void* program_counter_ = nullptr;
};
BASE_EXPORT const void* GetProgramCounter();
-// Define a macro to record the current source location.
+// The macros defined here will expand to the current function.
+#if BUILDFLAG(ENABLE_LOCATION_SOURCE)
+
+// Full source information should be included.
#define FROM_HERE FROM_HERE_WITH_EXPLICIT_FUNCTION(__func__)
+#define FROM_HERE_WITH_EXPLICIT_FUNCTION(function_name) \
+ ::base::Location::CreateFromHere(function_name, __FILE__, __LINE__)
+
+#else
+
+// TODO(http://crbug.com/760702) remove the __FILE__ argument from these calls.
+#define FROM_HERE ::base::Location::CreateFromHere(__FILE__)
+#define FROM_HERE_WITH_EXPLICIT_FUNCTION(function_name) \
+ ::base::Location::CreateFromHere(function_name, __FILE__, -1)
-#define FROM_HERE_WITH_EXPLICIT_FUNCTION(function_name) \
- ::tracked_objects::Location(function_name, \
- __FILE__, \
- __LINE__, \
- ::tracked_objects::GetProgramCounter())
+#endif
+
+} // namespace base
+
+namespace std {
+
+// Specialization for using Location in hash tables.
+template <>
+struct hash<::base::Location> {
+ std::size_t operator()(const ::base::Location& loc) const {
+ const void* program_counter = loc.program_counter();
+ return base::Hash(&program_counter, sizeof(void*));
+ }
+};
-} // namespace tracked_objects
+} // namespace std
#endif // BASE_LOCATION_H_
diff --git a/base/logging.cc b/base/logging.cc
index 01e311b1b6..112afb8e08 100644
--- a/base/logging.cc
+++ b/base/logging.cc
@@ -7,32 +7,62 @@
#include <limits.h>
#include <stdint.h>
-#include "base/debug/activity_tracker.h"
#include "base/macros.h"
#include "build/build_config.h"
#if defined(OS_WIN)
#include <io.h>
+#include <windows.h>
typedef HANDLE FileHandle;
typedef HANDLE MutexHandle;
// Windows warns on using write(). It prefers _write().
#define write(fd, buf, count) _write(fd, buf, static_cast<unsigned int>(count))
// Windows doesn't define STDERR_FILENO. Define it here.
#define STDERR_FILENO 2
+
#elif defined(OS_MACOSX)
+// In MacOS 10.12 and iOS 10.0 and later ASL (Apple System Log) was deprecated
+// in favor of OS_LOG (Unified Logging).
+#include <AvailabilityMacros.h>
+#if defined(OS_IOS)
+#if !defined(__IPHONE_10_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_10_0
+#define USE_ASL
+#endif
+#else // !defined(OS_IOS)
+#if !defined(MAC_OS_X_VERSION_10_12) || \
+ MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12
+#define USE_ASL
+#endif
+#endif // defined(OS_IOS)
+
+#if defined(USE_ASL)
#include <asl.h>
+#else
+#include <os/log.h>
+#endif
+
#include <CoreFoundation/CoreFoundation.h>
#include <mach/mach.h>
#include <mach/mach_time.h>
#include <mach-o/dyld.h>
-#elif defined(OS_POSIX)
+
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
#if defined(OS_NACL)
#include <sys/time.h> // timespec doesn't seem to be in <time.h>
#endif
#include <time.h>
#endif
-#if defined(OS_POSIX)
+#if defined(OS_FUCHSIA)
+#include <zircon/process.h>
+#include <zircon/syscalls.h>
+#endif
+
+#if defined(OS_ANDROID) || defined(__ANDROID__)
+#include <android/log.h>
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
#include <errno.h>
#include <paths.h>
#include <pthread.h>
@@ -52,12 +82,17 @@ typedef pthread_mutex_t* MutexHandle;
#include <iomanip>
#include <ostream>
#include <string>
+#include <utility>
#include "base/base_switches.h"
+#include "base/callback.h"
#include "base/command_line.h"
+#include "base/containers/stack.h"
+#include "base/debug/activity_tracker.h"
#include "base/debug/alias.h"
#include "base/debug/debugger.h"
#include "base/debug/stack_trace.h"
+#include "base/lazy_instance.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
@@ -67,16 +102,9 @@ typedef pthread_mutex_t* MutexHandle;
#include "base/synchronization/lock_impl.h"
#include "base/threading/platform_thread.h"
#include "base/vlog.h"
-#if defined(OS_POSIX)
-#include "base/posix/safe_strerror.h"
-#endif
-#if !defined(OS_ANDROID)
-#include "base/files/file_path.h"
-#endif
-
-#if defined(OS_ANDROID) || defined(__ANDROID__)
-#include <android/log.h>
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include "base/posix/safe_strerror.h"
#endif
namespace logging {
@@ -86,8 +114,9 @@ namespace {
VlogInfo* g_vlog_info = nullptr;
VlogInfo* g_vlog_info_prev = nullptr;
-const char* const log_severity_names[LOG_NUM_SEVERITIES] = {
- "INFO", "WARNING", "ERROR", "FATAL" };
+const char* const log_severity_names[] = {"INFO", "WARNING", "ERROR", "FATAL"};
+static_assert(LOG_NUM_SEVERITIES == arraysize(log_severity_names),
+ "Incorrect number of log_severity_names");
const char* log_severity_name(int severity) {
if (severity >= 0 && severity < LOG_NUM_SEVERITIES)
@@ -107,7 +136,7 @@ const int kAlwaysPrintErrorLevel = LOG_ERROR;
// first needed.
#if defined(OS_WIN)
typedef std::wstring PathString;
-#else
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
typedef std::string PathString;
#endif
PathString* g_log_file_name = nullptr;
@@ -125,8 +154,11 @@ bool g_log_tickcount = false;
bool show_error_dialogs = false;
// An assert handler override specified by the client to be called instead of
-// the debug message dialog and process termination.
-LogAssertHandlerFunction log_assert_handler = nullptr;
+// the debug message dialog and process termination. Assert handlers are stored
+// in stack to allow overriding and restoring.
+base::LazyInstance<base::stack<LogAssertHandlerFunction>>::Leaky
+ log_assert_handler_stack = LAZY_INSTANCE_INITIALIZER;
+
// A log message handler that gets notified of every log message we process.
LogMessageHandlerFunction log_message_handler = nullptr;
@@ -135,6 +167,11 @@ LogMessageHandlerFunction log_message_handler = nullptr;
int32_t CurrentProcessId() {
#if defined(OS_WIN)
return GetCurrentProcessId();
+#elif defined(OS_FUCHSIA)
+ zx_info_handle_basic_t basic = {};
+ zx_object_get_info(zx_process_self(), ZX_INFO_HANDLE_BASIC, &basic,
+ sizeof(basic), nullptr, nullptr);
+ return basic.koid;
#elif defined(OS_POSIX)
return getpid();
#endif
@@ -143,6 +180,9 @@ int32_t CurrentProcessId() {
uint64_t TickCount() {
#if defined(OS_WIN)
return GetTickCount();
+#elif defined(OS_FUCHSIA)
+ return zx_clock_get(ZX_CLOCK_MONOTONIC) /
+ static_cast<zx_time_t>(base::Time::kNanosecondsPerMicrosecond);
#elif defined(OS_MACOSX)
return mach_absolute_time();
#elif defined(OS_NACL)
@@ -165,8 +205,10 @@ void DeleteFilePath(const PathString& log_name) {
DeleteFile(log_name.c_str());
#elif defined(OS_NACL)
// Do nothing; unlink() isn't supported on NaCl.
-#else
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
unlink(log_name.c_str());
+#else
+#error Unsupported platform
#endif
}
@@ -182,7 +224,7 @@ PathString GetDefaultLogFile() {
log_name.erase(last_backslash + 1);
log_name += L"debug.log";
return log_name;
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
// On other platforms we just use the current directory.
return PathString("debug.log");
#endif
@@ -190,7 +232,7 @@ PathString GetDefaultLogFile() {
// We don't need locks on Windows for atomically appending to files. The OS
// provides this functionality.
-#if !defined(OS_WIN)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// This class acts as a wrapper for locking the logging files.
// LoggingLock::Init() should be called from the main thread before any logging
// is done. Then whenever logging, be sure to have a local LoggingLock
@@ -221,9 +263,7 @@ class LoggingLock {
private:
static void LockLogging() {
if (lock_log_file == LOCK_LOG_FILE) {
-#if defined(OS_POSIX)
pthread_mutex_lock(&log_mutex);
-#endif
} else {
// use the lock
log_lock->Lock();
@@ -232,9 +272,7 @@ class LoggingLock {
static void UnlockLogging() {
if (lock_log_file == LOCK_LOG_FILE) {
-#if defined(OS_POSIX)
pthread_mutex_unlock(&log_mutex);
-#endif
} else {
log_lock->Unlock();
}
@@ -247,9 +285,7 @@ class LoggingLock {
// When we don't use a lock, we are using a global mutex. We need to do this
// because LockFileEx is not thread safe.
-#if defined(OS_POSIX)
static pthread_mutex_t log_mutex;
-#endif
static bool initialized;
static LogLockingState lock_log_file;
@@ -262,11 +298,9 @@ base::internal::LockImpl* LoggingLock::log_lock = nullptr;
// static
LogLockingState LoggingLock::lock_log_file = LOCK_LOG_FILE;
-#if defined(OS_POSIX)
pthread_mutex_t LoggingLock::log_mutex = PTHREAD_MUTEX_INITIALIZER;
-#endif
-#endif // OS_WIN
+#endif // OS_POSIX || OS_FUCHSIA
// Called by logging functions to ensure that |g_log_file| is initialized
// and can be used for writing. Returns false if the file could not be
@@ -318,10 +352,12 @@ bool InitializeLogFileHandle() {
return false;
}
}
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
g_log_file = fopen(g_log_file_name->c_str(), "a");
if (g_log_file == nullptr)
return false;
+#else
+#error Unsupported platform
#endif
}
@@ -331,8 +367,10 @@ bool InitializeLogFileHandle() {
void CloseFile(FileHandle log) {
#if defined(OS_WIN)
CloseHandle(log);
-#else
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
fclose(log);
+#else
+#error Unsupported platform
#endif
}
@@ -346,6 +384,13 @@ void CloseLogFileUnlocked() {
} // namespace
+#if DCHECK_IS_CONFIGURABLE
+// In DCHECK-enabled Chrome builds, allow the meaning of LOG_DCHECK to be
+// determined at run-time. We default it to INFO, to avoid it triggering
+// crashes before the run-time has explicitly chosen the behaviour.
+BASE_EXPORT logging::LogSeverity LOG_DCHECK = LOG_INFO;
+#endif // DCHECK_IS_CONFIGURABLE
+
// This is never instantiated, it's just used for EAT_STREAM_PARAMETERS to have
// an object of the correct type on the LHS of the unused part of the ternary
// operator.
@@ -387,7 +432,7 @@ bool BaseInitLoggingImpl(const LoggingSettings& settings) {
if ((g_logging_destination & LOG_TO_FILE) == 0)
return true;
-#if !defined(OS_WIN)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
LoggingLock::Init(settings.lock_log, settings.log_file);
LoggingLock logging_lock;
#endif
@@ -450,8 +495,13 @@ void SetShowErrorDialogs(bool enable_dialogs) {
show_error_dialogs = enable_dialogs;
}
-void SetLogAssertHandler(LogAssertHandlerFunction handler) {
- log_assert_handler = handler;
+ScopedLogAssertHandler::ScopedLogAssertHandler(
+ LogAssertHandlerFunction handler) {
+ log_assert_handler_stack.Get().push(std::move(handler));
+}
+
+ScopedLogAssertHandler::~ScopedLogAssertHandler() {
+ log_assert_handler_stack.Get().pop();
}
void SetLogMessageHandler(LogMessageHandlerFunction handler) {
@@ -492,11 +542,10 @@ void DisplayDebugMessageInDialog(const std::string& str) {
return;
#if defined(OS_WIN)
- MessageBoxW(nullptr, base::UTF8ToUTF16(str).c_str(), L"Fatal error",
- MB_OK | MB_ICONHAND | MB_TOPMOST);
-#else
// We intentionally don't implement a dialog on other platforms.
// You can just look at stderr.
+ MessageBoxW(nullptr, base::UTF8ToUTF16(str).c_str(), L"Fatal error",
+ MB_OK | MB_ICONHAND | MB_TOPMOST);
#endif // defined(OS_WIN)
}
#endif // !defined(NDEBUG)
@@ -537,7 +586,9 @@ LogMessage::LogMessage(const char* file, int line, LogSeverity severity,
}
LogMessage::~LogMessage() {
-#if !defined(OFFICIAL_BUILD) && !defined(OS_NACL) && !defined(__UCLIBC__)
+ size_t stack_start = stream_.tellp();
+#if !defined(OFFICIAL_BUILD) && !defined(OS_NACL) && !defined(__UCLIBC__) && \
+ !defined(OS_AIX)
if (severity_ == LOG_FATAL && !base::debug::BeingDebugged()) {
// Include a stack trace on a fatal, unless a debugger is attached.
base::debug::StackTrace trace;
@@ -561,10 +612,10 @@ LogMessage::~LogMessage() {
OutputDebugStringA(str_newline.c_str());
#elif defined(OS_MACOSX)
// In LOG_TO_SYSTEM_DEBUG_LOG mode, log messages are always written to
- // stderr. If stderr is /dev/null, also log via ASL (Apple System Log). If
- // there's something weird about stderr, assume that log messages are going
- // nowhere and log via ASL too. Messages logged via ASL show up in
- // Console.app.
+ // stderr. If stderr is /dev/null, also log via ASL (Apple System Log) or
+ // its successor OS_LOG. If there's something weird about stderr, assume
+ // that log messages are going nowhere and log via ASL/OS_LOG too.
+ // Messages logged via ASL/OS_LOG show up in Console.app.
//
// Programs started by launchd, as UI applications normally are, have had
// stderr connected to /dev/null since OS X 10.8. Prior to that, stderr was
@@ -574,14 +625,14 @@ LogMessage::~LogMessage() {
// Another alternative would be to determine whether stderr is a pipe to
// launchd and avoid logging via ASL only in that case. See 10.7.5
// CF-635.21/CFUtilities.c also_do_stderr(). This would result in logging to
- // both stderr and ASL even in tests, where it's undesirable to log to the
- // system log at all.
+ // both stderr and ASL/OS_LOG even in tests, where it's undesirable to log
+ // to the system log at all.
//
// Note that the ASL client by default discards messages whose levels are
// below ASL_LEVEL_NOTICE. It's possible to change that with
// asl_set_filter(), but this is pointless because syslogd normally applies
// the same filter.
- const bool log_via_asl = []() {
+ const bool log_to_system = []() {
struct stat stderr_stat;
if (fstat(fileno(stderr), &stderr_stat) == -1) {
return true;
@@ -599,25 +650,22 @@ LogMessage::~LogMessage() {
stderr_stat.st_rdev == dev_null_stat.st_rdev;
}();
- if (log_via_asl) {
+ if (log_to_system) {
// Log roughly the same way that CFLog() and NSLog() would. See 10.10.5
// CF-1153.18/CFUtilities.c __CFLogCString().
- //
- // The ASL facility is set to the main bundle ID if available. Otherwise,
- // "com.apple.console" is used.
CFBundleRef main_bundle = CFBundleGetMainBundle();
CFStringRef main_bundle_id_cf =
main_bundle ? CFBundleGetIdentifier(main_bundle) : nullptr;
- std::string asl_facility =
+ std::string main_bundle_id =
main_bundle_id_cf ? base::SysCFStringRefToUTF8(main_bundle_id_cf)
- : std::string("com.apple.console");
-
- class ASLClient {
+ : std::string("");
+#if defined(USE_ASL)
+ // The facility is set to the main bundle ID if available. Otherwise,
+ // "com.apple.console" is used.
+ const class ASLClient {
public:
- explicit ASLClient(const std::string& asl_facility)
- : client_(asl_open(nullptr,
- asl_facility.c_str(),
- ASL_OPT_NO_DELAY)) {}
+ explicit ASLClient(const std::string& facility)
+ : client_(asl_open(nullptr, facility.c_str(), ASL_OPT_NO_DELAY)) {}
~ASLClient() { asl_close(client_); }
aslclient get() const { return client_; }
@@ -625,9 +673,10 @@ LogMessage::~LogMessage() {
private:
aslclient client_;
DISALLOW_COPY_AND_ASSIGN(ASLClient);
- } asl_client(asl_facility);
+ } asl_client(main_bundle_id.empty() ? main_bundle_id
+ : "com.apple.console");
- class ASLMessage {
+ const class ASLMessage {
public:
ASLMessage() : message_(asl_new(ASL_TYPE_MSG)) {}
~ASLMessage() { asl_free(message_); }
@@ -673,6 +722,40 @@ LogMessage::~LogMessage() {
asl_set(asl_message.get(), ASL_KEY_MSG, str_newline.c_str());
asl_send(asl_client.get(), asl_message.get());
+#else // !defined(USE_ASL)
+ const class OSLog {
+ public:
+ explicit OSLog(const char* subsystem)
+ : os_log_(subsystem ? os_log_create(subsystem, "chromium_logging")
+ : OS_LOG_DEFAULT) {}
+ ~OSLog() {
+ if (os_log_ != OS_LOG_DEFAULT) {
+ os_release(os_log_);
+ }
+ }
+ os_log_t get() const { return os_log_; }
+
+ private:
+ os_log_t os_log_;
+ DISALLOW_COPY_AND_ASSIGN(OSLog);
+ } log(main_bundle_id.empty() ? nullptr : main_bundle_id.c_str());
+ const os_log_type_t os_log_type = [](LogSeverity severity) {
+ switch (severity) {
+ case LOG_INFO:
+ return OS_LOG_TYPE_INFO;
+ case LOG_WARNING:
+ return OS_LOG_TYPE_DEFAULT;
+ case LOG_ERROR:
+ return OS_LOG_TYPE_ERROR;
+ case LOG_FATAL:
+ return OS_LOG_TYPE_FAULT;
+ default:
+ return severity < 0 ? OS_LOG_TYPE_DEBUG : OS_LOG_TYPE_DEFAULT;
+ }
+ }(severity_);
+ os_log_with_type(log.get(), os_log_type, "%{public}s",
+ str_newline.c_str());
+#endif // defined(USE_ASL)
}
#elif defined(OS_ANDROID) || defined(__ANDROID__)
android_LogPriority priority =
@@ -721,7 +804,7 @@ LogMessage::~LogMessage() {
// to do this at the same time, there will be a race condition to create
// the lock. This is why InitLogging should be called from the main
// thread at the beginning of execution.
-#if !defined(OS_WIN)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
LoggingLock::Init(LOCK_LOG_FILE, nullptr);
LoggingLock logging_lock;
#endif
@@ -733,10 +816,12 @@ LogMessage::~LogMessage() {
static_cast<DWORD>(str_newline.length()),
&num_written,
nullptr);
-#else
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
ignore_result(fwrite(
str_newline.data(), str_newline.size(), 1, g_log_file));
fflush(g_log_file);
+#else
+#error Unsupported platform
#endif
}
}
@@ -750,13 +835,20 @@ LogMessage::~LogMessage() {
// Ensure the first characters of the string are on the stack so they
// are contained in minidumps for diagnostic purposes.
- char str_stack[1024];
- str_newline.copy(str_stack, arraysize(str_stack));
- base::debug::Alias(str_stack);
-
- if (log_assert_handler) {
- // Make a copy of the string for the handler out of paranoia.
- log_assert_handler(std::string(stream_.str()));
+ DEBUG_ALIAS_FOR_CSTR(str_stack, str_newline.c_str(), 1024);
+
+ if (log_assert_handler_stack.IsCreated() &&
+ !log_assert_handler_stack.Get().empty()) {
+ LogAssertHandlerFunction log_assert_handler =
+ log_assert_handler_stack.Get().top();
+
+ if (log_assert_handler) {
+ log_assert_handler.Run(
+ file_, line_,
+ base::StringPiece(str_newline.c_str() + message_start_,
+ stack_start - message_start_),
+ base::StringPiece(str_newline.c_str() + stack_start));
+ }
} else {
// Don't use the string with the newline, get a fresh version to send to
// the debug message process. We also don't display assertions to the
@@ -791,7 +883,21 @@ void LogMessage::Init(const char* file, int line) {
if (g_log_thread_id)
stream_ << base::PlatformThread::CurrentId() << ':';
if (g_log_timestamp) {
-#if defined(OS_POSIX)
+#if defined(OS_WIN)
+ SYSTEMTIME local_time;
+ GetLocalTime(&local_time);
+ stream_ << std::setfill('0')
+ << std::setw(2) << local_time.wMonth
+ << std::setw(2) << local_time.wDay
+ << '/'
+ << std::setw(2) << local_time.wHour
+ << std::setw(2) << local_time.wMinute
+ << std::setw(2) << local_time.wSecond
+ << '.'
+ << std::setw(3)
+ << local_time.wMilliseconds
+ << ':';
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
timeval tv;
gettimeofday(&tv, nullptr);
time_t t = tv.tv_sec;
@@ -808,19 +914,8 @@ void LogMessage::Init(const char* file, int line) {
<< '.'
<< std::setw(6) << tv.tv_usec
<< ':';
-#elif defined(OS_WIN)
- SYSTEMTIME local_time;
- GetLocalTime(&local_time);
- stream_ << std::setfill('0')
- << std::setw(2) << local_time.wMonth
- << std::setw(2) << local_time.wDay
- << '/'
- << std::setw(2) << local_time.wHour
- << std::setw(2) << local_time.wMinute
- << std::setw(2) << local_time.wSecond
- << '.'
- << std::setw(3) << local_time.wMilliseconds
- << ':';
+#else
+#error Unsupported platform
#endif
}
if (g_log_tickcount)
@@ -845,15 +940,13 @@ typedef DWORD SystemErrorCode;
SystemErrorCode GetLastSystemErrorCode() {
#if defined(OS_WIN)
return ::GetLastError();
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
return errno;
-#else
-#error Not implemented
#endif
}
-#if defined(OS_WIN)
BASE_EXPORT std::string SystemErrorCodeToString(SystemErrorCode error_code) {
+#if defined(OS_WIN)
const int kErrorMessageBufferSize = 256;
char msgbuf[kErrorMessageBufferSize];
DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
@@ -862,18 +955,15 @@ BASE_EXPORT std::string SystemErrorCodeToString(SystemErrorCode error_code) {
if (len) {
// Messages returned by system end with line breaks.
return base::CollapseWhitespaceASCII(msgbuf, true) +
- base::StringPrintf(" (0x%X)", error_code);
+ base::StringPrintf(" (0x%lX)", error_code);
}
- return base::StringPrintf("Error (0x%X) while retrieving error. (0x%X)",
+ return base::StringPrintf("Error (0x%lX) while retrieving error. (0x%lX)",
GetLastError(), error_code);
-}
-#elif defined(OS_POSIX)
-BASE_EXPORT std::string SystemErrorCodeToString(SystemErrorCode error_code) {
- return base::safe_strerror(error_code);
-}
-#else
-#error Not implemented
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ return base::safe_strerror(error_code) +
+ base::StringPrintf(" (%d)", error_code);
#endif // defined(OS_WIN)
+}
#if defined(OS_WIN)
@@ -892,7 +982,7 @@ Win32ErrorLogMessage::~Win32ErrorLogMessage() {
DWORD last_error = err_;
base::debug::Alias(&last_error);
}
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
ErrnoLogMessage::ErrnoLogMessage(const char* file,
int line,
LogSeverity severity,
@@ -903,11 +993,15 @@ ErrnoLogMessage::ErrnoLogMessage(const char* file,
ErrnoLogMessage::~ErrnoLogMessage() {
stream() << ": " << SystemErrorCodeToString(err_);
+ // We're about to crash (CHECK). Put |err_| on the stack (by placing it in a
+ // field) and use Alias in hopes that it makes it into crash dumps.
+ int last_error = err_;
+ base::debug::Alias(&last_error);
}
#endif // defined(OS_WIN)
void CloseLogFile() {
-#if !defined(OS_WIN)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
LoggingLock logging_lock;
#endif
CloseLogFileUnlocked();
diff --git a/base/logging.h b/base/logging.h
index 7ca018e227..08c1f0fc59 100644
--- a/base/logging.h
+++ b/base/logging.h
@@ -15,9 +15,11 @@
#include <utility>
#include "base/base_export.h"
+#include "base/callback_forward.h"
#include "base/compiler_specific.h"
#include "base/debug/debugger.h"
#include "base/macros.h"
+#include "base/strings/string_piece_forward.h"
#include "base/template_util.h"
#include "build/build_config.h"
@@ -146,7 +148,7 @@ namespace logging {
// TODO(avi): do we want to do a unification of character types here?
#if defined(OS_WIN)
typedef wchar_t PathChar;
-#else
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
typedef char PathChar;
#endif
@@ -164,7 +166,7 @@ enum LoggingDestination {
// stderr.
#if defined(OS_WIN)
LOG_DEFAULT = LOG_TO_FILE,
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
LOG_DEFAULT = LOG_TO_SYSTEM_DEBUG_LOG,
#endif
};
@@ -204,7 +206,7 @@ struct BASE_EXPORT LoggingSettings {
// whether NDEBUG is defined or not so that we'll fail to link if someone tries
// to compile logging.cc with NDEBUG but includes logging.h without defining it,
// or vice versa.
-#if NDEBUG
+#if defined(NDEBUG)
#define BaseInitLoggingImpl BaseInitLoggingImpl_built_with_NDEBUG
#else
#define BaseInitLoggingImpl BaseInitLoggingImpl_built_without_NDEBUG
@@ -250,12 +252,10 @@ BASE_EXPORT bool ShouldCreateLogMessage(int severity);
// Gets the VLOG default verbosity level.
BASE_EXPORT int GetVlogVerbosity();
-// Gets the current vlog level for the given file (usually taken from
-// __FILE__).
-
// Note that |N| is the size *with* the null terminator.
BASE_EXPORT int GetVlogLevelHelper(const char* file_start, size_t N);
+// Gets the current vlog level for the given file (usually taken from __FILE__).
template <size_t N>
int GetVlogLevel(const char (&file)[N]) {
return GetVlogLevelHelper(file, N);
@@ -274,11 +274,24 @@ BASE_EXPORT void SetLogItems(bool enable_process_id, bool enable_thread_id,
BASE_EXPORT void SetShowErrorDialogs(bool enable_dialogs);
// Sets the Log Assert Handler that will be used to notify of check failures.
+// Resets Log Assert Handler on object destruction.
// The default handler shows a dialog box and then terminate the process,
// however clients can use this function to override with their own handling
// (e.g. a silent one for Unit Tests)
-typedef void (*LogAssertHandlerFunction)(const std::string& str);
-BASE_EXPORT void SetLogAssertHandler(LogAssertHandlerFunction handler);
+using LogAssertHandlerFunction =
+ base::Callback<void(const char* file,
+ int line,
+ const base::StringPiece message,
+ const base::StringPiece stack_trace)>;
+
+class BASE_EXPORT ScopedLogAssertHandler {
+ public:
+ explicit ScopedLogAssertHandler(LogAssertHandlerFunction handler);
+ ~ScopedLogAssertHandler();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedLogAssertHandler);
+};
// Sets the Log Message Handler that gets passed every log message before
// it's sent to other log destinations (if any).
@@ -289,6 +302,38 @@ typedef bool (*LogMessageHandlerFunction)(int severity,
BASE_EXPORT void SetLogMessageHandler(LogMessageHandlerFunction handler);
BASE_EXPORT LogMessageHandlerFunction GetLogMessageHandler();
+// The ANALYZER_ASSUME_TRUE(bool arg) macro adds compiler-specific hints
+// to Clang which control what code paths are statically analyzed,
+// and is meant to be used in conjunction with assert & assert-like functions.
+// The expression is passed straight through if analysis isn't enabled.
+//
+// ANALYZER_SKIP_THIS_PATH() suppresses static analysis for the current
+// codepath and any other branching codepaths that might follow.
+#if defined(__clang_analyzer__)
+
+inline constexpr bool AnalyzerNoReturn() __attribute__((analyzer_noreturn)) {
+ return false;
+}
+
+inline constexpr bool AnalyzerAssumeTrue(bool arg) {
+ // AnalyzerNoReturn() is invoked and analysis is terminated if |arg| is
+ // false.
+ return arg || AnalyzerNoReturn();
+}
+
+#define ANALYZER_ASSUME_TRUE(arg) logging::AnalyzerAssumeTrue(!!(arg))
+#define ANALYZER_SKIP_THIS_PATH() \
+ static_cast<void>(::logging::AnalyzerNoReturn())
+#define ANALYZER_ALLOW_UNUSED(var) static_cast<void>(var);
+
+#else // !defined(__clang_analyzer__)
+
+#define ANALYZER_ASSUME_TRUE(arg) (arg)
+#define ANALYZER_SKIP_THIS_PATH()
+#define ANALYZER_ALLOW_UNUSED(var) static_cast<void>(var);
+
+#endif // defined(__clang_analyzer__)
+
typedef int LogSeverity;
const LogSeverity LOG_VERBOSE = -1; // This is level 1 verbosity
// Note: the log severities are used to index into the array of names,
@@ -300,7 +345,7 @@ const LogSeverity LOG_FATAL = 3;
const LogSeverity LOG_NUM_SEVERITIES = 4;
// LOG_DFATAL is LOG_FATAL in debug mode, ERROR in normal mode
-#ifdef NDEBUG
+#if defined(NDEBUG)
const LogSeverity LOG_DFATAL = LOG_ERROR;
#else
const LogSeverity LOG_DFATAL = LOG_FATAL;
@@ -320,17 +365,15 @@ const LogSeverity LOG_DFATAL = LOG_FATAL;
::logging::ClassName(__FILE__, __LINE__, ::logging::LOG_FATAL, ##__VA_ARGS__)
#define COMPACT_GOOGLE_LOG_EX_DFATAL(ClassName, ...) \
::logging::ClassName(__FILE__, __LINE__, ::logging::LOG_DFATAL, ##__VA_ARGS__)
+#define COMPACT_GOOGLE_LOG_EX_DCHECK(ClassName, ...) \
+ ::logging::ClassName(__FILE__, __LINE__, ::logging::LOG_DCHECK, ##__VA_ARGS__)
-#define COMPACT_GOOGLE_LOG_INFO \
- COMPACT_GOOGLE_LOG_EX_INFO(LogMessage)
-#define COMPACT_GOOGLE_LOG_WARNING \
- COMPACT_GOOGLE_LOG_EX_WARNING(LogMessage)
-#define COMPACT_GOOGLE_LOG_ERROR \
- COMPACT_GOOGLE_LOG_EX_ERROR(LogMessage)
-#define COMPACT_GOOGLE_LOG_FATAL \
- COMPACT_GOOGLE_LOG_EX_FATAL(LogMessage)
-#define COMPACT_GOOGLE_LOG_DFATAL \
- COMPACT_GOOGLE_LOG_EX_DFATAL(LogMessage)
+#define COMPACT_GOOGLE_LOG_INFO COMPACT_GOOGLE_LOG_EX_INFO(LogMessage)
+#define COMPACT_GOOGLE_LOG_WARNING COMPACT_GOOGLE_LOG_EX_WARNING(LogMessage)
+#define COMPACT_GOOGLE_LOG_ERROR COMPACT_GOOGLE_LOG_EX_ERROR(LogMessage)
+#define COMPACT_GOOGLE_LOG_FATAL COMPACT_GOOGLE_LOG_EX_FATAL(LogMessage)
+#define COMPACT_GOOGLE_LOG_DFATAL COMPACT_GOOGLE_LOG_EX_DFATAL(LogMessage)
+#define COMPACT_GOOGLE_LOG_DCHECK COMPACT_GOOGLE_LOG_EX_DCHECK(LogMessage)
#if defined(OS_WIN)
// wingdi.h defines ERROR to be 0. When we call LOG(ERROR), it gets
@@ -393,7 +436,7 @@ const LogSeverity LOG_0 = LOG_ERROR;
#define VPLOG_STREAM(verbose_level) \
::logging::Win32ErrorLogMessage(__FILE__, __LINE__, -verbose_level, \
::logging::GetLastSystemErrorCode()).stream()
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
#define VPLOG_STREAM(verbose_level) \
::logging::ErrnoLogMessage(__FILE__, __LINE__, -verbose_level, \
::logging::GetLastSystemErrorCode()).stream()
@@ -408,14 +451,15 @@ const LogSeverity LOG_0 = LOG_ERROR;
// TODO(akalin): Add more VLOG variants, e.g. VPLOG.
-#define LOG_ASSERT(condition) \
- LOG_IF(FATAL, !(condition)) << "Assert failed: " #condition ". "
+#define LOG_ASSERT(condition) \
+ LOG_IF(FATAL, !(ANALYZER_ASSUME_TRUE(condition))) \
+ << "Assert failed: " #condition ". "
#if defined(OS_WIN)
#define PLOG_STREAM(severity) \
COMPACT_GOOGLE_LOG_EX_ ## severity(Win32ErrorLogMessage, \
::logging::GetLastSystemErrorCode()).stream()
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
#define PLOG_STREAM(severity) \
COMPACT_GOOGLE_LOG_EX_ ## severity(ErrnoLogMessage, \
::logging::GetLastSystemErrorCode()).stream()
@@ -508,9 +552,26 @@ class CheckOpResult {
#define TRAP_SEQUENCE() __builtin_trap()
#endif // ARCH_CPU_*
+// CHECK() and the trap sequence can be invoked from a constexpr function.
+// This could make compilation fail on GCC, as it forbids directly using inline
+// asm inside a constexpr function. However, it allows calling a lambda
+// expression including the same asm.
+// The side effect is that the top of the stacktrace will not point to the
+// calling function, but to this anonymous lambda. This is still useful as the
+// full name of the lambda will typically include the name of the function that
+// calls CHECK() and the debugger will still break at the right line of code.
+#if !defined(__clang__)
+#define WRAPPED_TRAP_SEQUENCE() \
+ do { \
+ [] { TRAP_SEQUENCE(); }(); \
+ } while (false)
+#else
+#define WRAPPED_TRAP_SEQUENCE() TRAP_SEQUENCE()
+#endif
+
#define IMMEDIATE_CRASH() \
({ \
- TRAP_SEQUENCE(); \
+ WRAPPED_TRAP_SEQUENCE(); \
__builtin_unreachable(); \
})
@@ -529,7 +590,11 @@ class CheckOpResult {
// TODO(scottmg): Reinvestigate a short sequence that will work on both
// compilers once clang supports more intrinsics. See https://crbug.com/693713.
#if defined(__clang__)
-#define IMMEDIATE_CRASH() ({__asm int 3 __asm ud2 __asm push __COUNTER__})
+#define IMMEDIATE_CRASH() \
+ ({ \
+ {__asm int 3 __asm ud2 __asm push __COUNTER__}; \
+ __builtin_unreachable(); \
+ })
#else
#define IMMEDIATE_CRASH() __debugbreak()
#endif // __clang__
@@ -556,7 +621,13 @@ class CheckOpResult {
#define CHECK(condition) \
UNLIKELY(!(condition)) ? IMMEDIATE_CRASH() : EAT_STREAM_PARAMETERS
-#define PCHECK(condition) CHECK(condition)
+// PCHECK includes the system error code, which is useful for determining
+// why the condition failed. In official builds, preserve only the error code
+// message so that it is available in crash reports. The stringified
+// condition and any additional stream parameters are dropped.
+#define PCHECK(condition) \
+ LAZY_STREAM(PLOG_STREAM(FATAL), UNLIKELY(!(condition))); \
+ EAT_STREAM_PARAMETERS
#define CHECK_OP(name, op, val1, val2) CHECK((val1) op (val2))
@@ -585,10 +656,10 @@ class CheckOpResult {
// Do as much work as possible out of line to reduce inline code size.
#define CHECK(condition) \
LAZY_STREAM(::logging::LogMessage(__FILE__, __LINE__, #condition).stream(), \
- !(condition))
+ !ANALYZER_ASSUME_TRUE(condition))
-#define PCHECK(condition) \
- LAZY_STREAM(PLOG_STREAM(FATAL), !(condition)) \
+#define PCHECK(condition) \
+ LAZY_STREAM(PLOG_STREAM(FATAL), !ANALYZER_ASSUME_TRUE(condition)) \
<< "Check failed: " #condition ". "
#endif // _PREFAST_
@@ -642,7 +713,7 @@ inline typename std::enable_if<
std::is_enum<T>::value,
void>::type
MakeCheckOpValueString(std::ostream* os, const T& v) {
- (*os) << static_cast<typename base::underlying_type<T>::type>(v);
+ (*os) << static_cast<typename std::underlying_type<T>::type>(v);
}
// We need an explicit overload for std::nullptr_t.
@@ -685,17 +756,21 @@ std::string* MakeCheckOpString<std::string, std::string>(
// The (int, int) specialization works around the issue that the compiler
// will not instantiate the template version of the function on values of
// unnamed enum type - see comment below.
+//
+// The checked condition is wrapped with ANALYZER_ASSUME_TRUE, which under
+// static analysis builds, blocks analysis of the current path if the
+// condition is false.
#define DEFINE_CHECK_OP_IMPL(name, op) \
template <class t1, class t2> \
inline std::string* Check##name##Impl(const t1& v1, const t2& v2, \
const char* names) { \
- if (v1 op v2) \
+ if (ANALYZER_ASSUME_TRUE(v1 op v2)) \
return NULL; \
else \
return ::logging::MakeCheckOpString(v1, v2, names); \
} \
inline std::string* Check##name##Impl(int v1, int v2, const char* names) { \
- if (v1 op v2) \
+ if (ANALYZER_ASSUME_TRUE(v1 op v2)) \
return NULL; \
else \
return ::logging::MakeCheckOpString(v1, v2, names); \
@@ -761,18 +836,17 @@ DEFINE_CHECK_OP_IMPL(GT, > )
#if DCHECK_IS_ON()
-#define COMPACT_GOOGLE_LOG_EX_DCHECK(ClassName, ...) \
- COMPACT_GOOGLE_LOG_EX_FATAL(ClassName , ##__VA_ARGS__)
-#define COMPACT_GOOGLE_LOG_DCHECK COMPACT_GOOGLE_LOG_FATAL
+#if DCHECK_IS_CONFIGURABLE
+BASE_EXPORT extern LogSeverity LOG_DCHECK;
+#else
const LogSeverity LOG_DCHECK = LOG_FATAL;
+#endif
#else // DCHECK_IS_ON()
-// These are just dummy values.
-#define COMPACT_GOOGLE_LOG_EX_DCHECK(ClassName, ...) \
- COMPACT_GOOGLE_LOG_EX_INFO(ClassName , ##__VA_ARGS__)
-#define COMPACT_GOOGLE_LOG_DCHECK COMPACT_GOOGLE_LOG_INFO
-const LogSeverity LOG_DCHECK = LOG_INFO;
+// There may be users of LOG_DCHECK that are enabled independently
+// of DCHECK_IS_ON(), so default to FATAL logging for those.
+const LogSeverity LOG_DCHECK = LOG_FATAL;
#endif // DCHECK_IS_ON()
@@ -798,35 +872,15 @@ const LogSeverity LOG_DCHECK = LOG_INFO;
LAZY_STREAM(PLOG_STREAM(DCHECK), false) \
<< "Check failed: " #condition ". "
-#elif defined(__clang_analyzer__)
-
-// Keeps the static analyzer from proceeding along the current codepath,
-// otherwise false positive errors may be generated by null pointer checks.
-inline constexpr bool AnalyzerNoReturn() __attribute__((analyzer_noreturn)) {
- return false;
-}
-
-#define DCHECK(condition) \
- LAZY_STREAM( \
- LOG_STREAM(DCHECK), \
- DCHECK_IS_ON() ? (logging::AnalyzerNoReturn() || !(condition)) : false) \
- << "Check failed: " #condition ". "
-
-#define DPCHECK(condition) \
- LAZY_STREAM( \
- PLOG_STREAM(DCHECK), \
- DCHECK_IS_ON() ? (logging::AnalyzerNoReturn() || !(condition)) : false) \
- << "Check failed: " #condition ". "
-
-#else
+#else // !(defined(_PREFAST_) && defined(OS_WIN))
#if DCHECK_IS_ON()
-#define DCHECK(condition) \
- LAZY_STREAM(LOG_STREAM(DCHECK), !(condition)) \
+#define DCHECK(condition) \
+ LAZY_STREAM(LOG_STREAM(DCHECK), !ANALYZER_ASSUME_TRUE(condition)) \
<< "Check failed: " #condition ". "
-#define DPCHECK(condition) \
- LAZY_STREAM(PLOG_STREAM(DCHECK), !(condition)) \
+#define DPCHECK(condition) \
+ LAZY_STREAM(PLOG_STREAM(DCHECK), !ANALYZER_ASSUME_TRUE(condition)) \
<< "Check failed: " #condition ". "
#else // DCHECK_IS_ON()
@@ -836,7 +890,7 @@ inline constexpr bool AnalyzerNoReturn() __attribute__((analyzer_noreturn)) {
#endif // DCHECK_IS_ON()
-#endif
+#endif // defined(_PREFAST_) && defined(OS_WIN)
// Helper macro for binary operators.
// Don't use this macro directly in your code, use DCHECK_EQ et al below.
@@ -849,9 +903,8 @@ inline constexpr bool AnalyzerNoReturn() __attribute__((analyzer_noreturn)) {
#define DCHECK_OP(name, op, val1, val2) \
switch (0) case 0: default: \
if (::logging::CheckOpResult true_if_passed = \
- DCHECK_IS_ON() ? \
::logging::Check##name##Impl((val1), (val2), \
- #val1 " " #op " " #val2) : nullptr) \
+ #val1 " " #op " " #val2)) \
; \
else \
::logging::LogMessage(__FILE__, __LINE__, ::logging::LOG_DCHECK, \
@@ -988,7 +1041,7 @@ class BASE_EXPORT LogMessage {
// is not used" and "statement has no effect".
class LogMessageVoidify {
public:
- LogMessageVoidify() { }
+ LogMessageVoidify() = default;
// This has to be an operator with a precedence lower than << but
// higher than ?:
void operator&(std::ostream&) { }
@@ -996,7 +1049,7 @@ class LogMessageVoidify {
#if defined(OS_WIN)
typedef unsigned long SystemErrorCode;
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
typedef int SystemErrorCode;
#endif
@@ -1025,7 +1078,7 @@ class BASE_EXPORT Win32ErrorLogMessage {
DISALLOW_COPY_AND_ASSIGN(Win32ErrorLogMessage);
};
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
// Appends a formatted system message of the errno type
class BASE_EXPORT ErrnoLogMessage {
public:
@@ -1097,25 +1150,9 @@ inline std::ostream& operator<<(std::ostream& out, const std::wstring& wstr) {
}
} // namespace std
-// The NOTIMPLEMENTED() macro annotates codepaths which have
-// not been implemented yet.
-//
-// The implementation of this macro is controlled by NOTIMPLEMENTED_POLICY:
-// 0 -- Do nothing (stripped by compiler)
-// 1 -- Warn at compile time
-// 2 -- Fail at compile time
-// 3 -- Fail at runtime (DCHECK)
-// 4 -- [default] LOG(ERROR) at runtime
-// 5 -- LOG(ERROR) at runtime, only once per call-site
-
-#ifndef NOTIMPLEMENTED_POLICY
-#if defined(OS_ANDROID) && defined(OFFICIAL_BUILD)
-#define NOTIMPLEMENTED_POLICY 0
-#else
-// Select default policy: LOG(ERROR)
-#define NOTIMPLEMENTED_POLICY 4
-#endif
-#endif
+// The NOTIMPLEMENTED() macro annotates codepaths which have not been
+// implemented yet. If output spam is a serious concern,
+// NOTIMPLEMENTED_LOG_ONCE can be used.
#if defined(COMPILER_GCC)
// On Linux, with GCC, we can use __PRETTY_FUNCTION__ to get the demangled name
@@ -1125,24 +1162,18 @@ inline std::ostream& operator<<(std::ostream& out, const std::wstring& wstr) {
#define NOTIMPLEMENTED_MSG "NOT IMPLEMENTED"
#endif
-#if NOTIMPLEMENTED_POLICY == 0
+#if defined(OS_ANDROID) && defined(OFFICIAL_BUILD)
#define NOTIMPLEMENTED() EAT_STREAM_PARAMETERS
-#elif NOTIMPLEMENTED_POLICY == 1
-// TODO, figure out how to generate a warning
-#define NOTIMPLEMENTED() static_assert(false, "NOT_IMPLEMENTED")
-#elif NOTIMPLEMENTED_POLICY == 2
-#define NOTIMPLEMENTED() static_assert(false, "NOT_IMPLEMENTED")
-#elif NOTIMPLEMENTED_POLICY == 3
-#define NOTIMPLEMENTED() NOTREACHED()
-#elif NOTIMPLEMENTED_POLICY == 4
+#define NOTIMPLEMENTED_LOG_ONCE() EAT_STREAM_PARAMETERS
+#else
#define NOTIMPLEMENTED() LOG(ERROR) << NOTIMPLEMENTED_MSG
-#elif NOTIMPLEMENTED_POLICY == 5
-#define NOTIMPLEMENTED() do {\
- static bool logged_once = false;\
- LOG_IF(ERROR, !logged_once) << NOTIMPLEMENTED_MSG;\
- logged_once = true;\
-} while(0);\
-EAT_STREAM_PARAMETERS
+#define NOTIMPLEMENTED_LOG_ONCE() \
+ do { \
+ static bool logged_once = false; \
+ LOG_IF(ERROR, !logged_once) << NOTIMPLEMENTED_MSG; \
+ logged_once = true; \
+ } while (0); \
+ EAT_STREAM_PARAMETERS
#endif
#endif // BASE_LOGGING_H_
diff --git a/base/logging_unittest.cc b/base/logging_unittest.cc
index 04f349cab6..9b79f9875d 100644
--- a/base/logging_unittest.cc
+++ b/base/logging_unittest.cc
@@ -2,9 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "base/compiler_specific.h"
#include "base/logging.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -24,18 +29,35 @@
#include <windows.h>
#endif // OS_WIN
+#if defined(OS_FUCHSIA)
+#include <lib/zx/event.h>
+#include <lib/zx/port.h>
+#include <lib/zx/process.h>
+#include <lib/zx/thread.h>
+#include <lib/zx/time.h>
+#include <zircon/process.h>
+#include <zircon/syscalls/debug.h>
+#include <zircon/syscalls/port.h>
+#include <zircon/types.h>
+#include "base/fuchsia/fuchsia_logging.h"
+#endif
+
namespace logging {
namespace {
using ::testing::Return;
+using ::testing::_;
// Needs to be global since log assert handlers can't maintain state.
-int log_sink_call_count = 0;
+int g_log_sink_call_count = 0;
#if !defined(OFFICIAL_BUILD) || defined(DCHECK_ALWAYS_ON) || !defined(NDEBUG)
-void LogSink(const std::string& str) {
- ++log_sink_call_count;
+void LogSink(const char* file,
+ int line,
+ const base::StringPiece message,
+ const base::StringPiece stack_trace) {
+ ++g_log_sink_call_count;
}
#endif
@@ -47,8 +69,7 @@ class LogStateSaver {
~LogStateSaver() {
SetMinLogLevel(old_min_log_level_);
- SetLogAssertHandler(NULL);
- log_sink_call_count = 0;
+ g_log_sink_call_count = 0;
}
private:
@@ -67,6 +88,13 @@ class MockLogSource {
MOCK_METHOD0(Log, const char*());
};
+class MockLogAssertHandler {
+ public:
+ MOCK_METHOD4(
+ HandleLogAssert,
+ void(const char*, int, const base::StringPiece, const base::StringPiece));
+};
+
TEST_F(LoggingTest, BasicLogging) {
MockLogSource mock_log_source;
EXPECT_CALL(mock_log_source, Log())
@@ -132,7 +160,7 @@ TEST_F(LoggingTest, LogIsOn) {
EXPECT_FALSE(LOG_IS_ON(WARNING));
EXPECT_FALSE(LOG_IS_ON(ERROR));
EXPECT_TRUE(LOG_IS_ON(FATAL));
- EXPECT_TRUE(kDfatalIsFatal == LOG_IS_ON(DFATAL));
+ EXPECT_EQ(kDfatalIsFatal, LOG_IS_ON(DFATAL));
}
TEST_F(LoggingTest, LoggingIsLazyBySeverity) {
@@ -185,13 +213,19 @@ TEST_F(LoggingTest, LoggingIsLazyByDestination) {
// Official builds have CHECKs directly call BreakDebugger.
#if !defined(OFFICIAL_BUILD)
-TEST_F(LoggingTest, CheckStreamsAreLazy) {
+// https://crbug.com/709067 tracks test flakiness on iOS.
+#if defined(OS_IOS)
+#define MAYBE_CheckStreamsAreLazy DISABLED_CheckStreamsAreLazy
+#else
+#define MAYBE_CheckStreamsAreLazy CheckStreamsAreLazy
+#endif
+TEST_F(LoggingTest, MAYBE_CheckStreamsAreLazy) {
MockLogSource mock_log_source, uncalled_mock_log_source;
EXPECT_CALL(mock_log_source, Log()).Times(8).
WillRepeatedly(Return("check message"));
EXPECT_CALL(uncalled_mock_log_source, Log()).Times(0);
- SetLogAssertHandler(&LogSink);
+ ScopedLogAssertHandler scoped_assert_handler(base::Bind(LogSink));
CHECK(mock_log_source.Log()) << uncalled_mock_log_source.Log();
PCHECK(!mock_log_source.Log()) << mock_log_source.Log();
@@ -254,7 +288,127 @@ TEST_F(LoggingTest, CheckCausesDistinctBreakpoints) {
EXPECT_NE(addr1, addr3);
EXPECT_NE(addr2, addr3);
}
+#elif defined(OS_FUCHSIA)
+
+// CHECK causes a direct crash (without jumping to another function) only in
+// official builds. Unfortunately, continuous test coverage on official builds
+// is lower. Furthermore, since the Fuchsia implementation uses threads, it is
+// not possible to rely on an implementation of CHECK that calls abort(), which
+// takes down the whole process, preventing the thread exception handler from
+// handling the exception. DO_CHECK here falls back on IMMEDIATE_CRASH() in
+// non-official builds, to catch regressions earlier in the CQ.
+#if defined(OFFICIAL_BUILD)
+#define DO_CHECK CHECK
+#else
+#define DO_CHECK(cond) \
+ if (!(cond)) { \
+ IMMEDIATE_CRASH(); \
+ }
+#endif
+
+static const unsigned int kExceptionPortKey = 1u;
+static const unsigned int kThreadEndedPortKey = 2u;
+
+struct thread_data_t {
+ // For signaling the thread ended properly.
+ zx::unowned_event event;
+ // For registering thread termination.
+ zx::unowned_port port;
+ // Location where the thread is expected to crash.
+ int death_location;
+};
+
+void* CrashThread(void* arg) {
+ zx_status_t status;
+
+ thread_data_t* data = (thread_data_t*)arg;
+ int death_location = data->death_location;
+
+ // Register the exception handler on the port.
+ status = zx::thread::self()->bind_exception_port(*data->port,
+ kExceptionPortKey, 0);
+ if (status != ZX_OK) {
+ data->event->signal(0, ZX_USER_SIGNAL_0);
+ return nullptr;
+ }
+
+ DO_CHECK(death_location != 1);
+ DO_CHECK(death_location != 2);
+ DO_CHECK(death_location != 3);
+
+ // We should never reach this point, signal the thread incorrectly ended
+ // properly.
+ data->event->signal(0, ZX_USER_SIGNAL_0);
+ return nullptr;
+}
+// Runs the CrashThread function in a separate thread.
+void SpawnCrashThread(int death_location, uintptr_t* child_crash_addr) {
+ zx::port port;
+ zx::event event;
+ zx_status_t status;
+
+ status = zx::port::create(0, &port);
+ ASSERT_EQ(status, ZX_OK);
+ status = zx::event::create(0, &event);
+ ASSERT_EQ(status, ZX_OK);
+
+ // Register the thread ended event on the port.
+ status = event.wait_async(port, kThreadEndedPortKey, ZX_USER_SIGNAL_0,
+ ZX_WAIT_ASYNC_ONCE);
+ ASSERT_EQ(status, ZX_OK);
+
+ // Run the thread.
+ thread_data_t thread_data = {zx::unowned_event(event), zx::unowned_port(port),
+ death_location};
+ pthread_t thread;
+ int ret = pthread_create(&thread, nullptr, CrashThread, &thread_data);
+ ASSERT_EQ(ret, 0);
+
+ // Wait on the port.
+ zx_port_packet_t packet;
+ status = port.wait(zx::time::infinite(), &packet);
+ ASSERT_EQ(status, ZX_OK);
+ // Check the thread did crash and not terminate.
+ ASSERT_EQ(packet.key, kExceptionPortKey);
+
+ // Get the crash address.
+ zx::thread zircon_thread;
+ status = zx::process::self()->get_child(packet.exception.tid,
+ ZX_RIGHT_SAME_RIGHTS, &zircon_thread);
+ ASSERT_EQ(status, ZX_OK);
+ zx_thread_state_general_regs_t buffer;
+ status = zircon_thread.read_state(ZX_THREAD_STATE_GENERAL_REGS, &buffer,
+ sizeof(buffer));
+ ASSERT_EQ(status, ZX_OK);
+#if defined(ARCH_CPU_X86_64)
+ *child_crash_addr = static_cast<uintptr_t>(buffer.rip);
+#elif defined(ARCH_CPU_ARM64)
+ *child_crash_addr = static_cast<uintptr_t>(buffer.pc);
+#else
+#error Unsupported architecture
+#endif
+
+ status = zircon_thread.kill();
+ ASSERT_EQ(status, ZX_OK);
+}
+
+TEST_F(LoggingTest, CheckCausesDistinctBreakpoints) {
+ uintptr_t child_crash_addr_1 = 0;
+ uintptr_t child_crash_addr_2 = 0;
+ uintptr_t child_crash_addr_3 = 0;
+
+ SpawnCrashThread(1, &child_crash_addr_1);
+ SpawnCrashThread(2, &child_crash_addr_2);
+ SpawnCrashThread(3, &child_crash_addr_3);
+
+ ASSERT_NE(0u, child_crash_addr_1);
+ ASSERT_NE(0u, child_crash_addr_2);
+ ASSERT_NE(0u, child_crash_addr_3);
+ ASSERT_NE(child_crash_addr_1, child_crash_addr_2);
+ ASSERT_NE(child_crash_addr_1, child_crash_addr_3);
+ ASSERT_NE(child_crash_addr_2, child_crash_addr_3);
+}
#elif defined(OS_POSIX) && !defined(OS_NACL) && !defined(OS_IOS) && \
(defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM_FAMILY))
@@ -269,7 +423,7 @@ void CheckCrashTestSighandler(int, siginfo_t* info, void* context_ptr) {
#if defined(OS_MACOSX)
crash_addr = reinterpret_cast<uintptr_t>(info->si_addr);
#else // OS_POSIX && !OS_MACOSX
- struct ucontext* context = reinterpret_cast<struct ucontext*>(context_ptr);
+ ucontext_t* context = reinterpret_cast<ucontext_t*>(context_ptr);
#if defined(ARCH_CPU_X86)
crash_addr = static_cast<uintptr_t>(context->uc_mcontext.gregs[REG_EIP]);
#elif defined(ARCH_CPU_X86_64)
@@ -300,9 +454,9 @@ void CrashChildMain(int death_location) {
struct sigaction act = {};
act.sa_sigaction = CheckCrashTestSighandler;
act.sa_flags = SA_SIGINFO;
- ASSERT_EQ(0, sigaction(SIGTRAP, &act, NULL));
- ASSERT_EQ(0, sigaction(SIGBUS, &act, NULL));
- ASSERT_EQ(0, sigaction(SIGILL, &act, NULL));
+ ASSERT_EQ(0, sigaction(SIGTRAP, &act, nullptr));
+ ASSERT_EQ(0, sigaction(SIGBUS, &act, nullptr));
+ ASSERT_EQ(0, sigaction(SIGILL, &act, nullptr));
DO_CHECK(death_location != 1);
DO_CHECK(death_location != 2);
printf("\n");
@@ -352,7 +506,7 @@ TEST_F(LoggingTest, CheckCausesDistinctBreakpoints) {
#endif // OS_POSIX
TEST_F(LoggingTest, DebugLoggingReleaseBehavior) {
-#if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)
+#if DCHECK_IS_ON()
int debug_only_variable = 1;
#endif
// These should avoid emitting references to |debug_only_variable|
@@ -373,7 +527,7 @@ TEST_F(LoggingTest, DcheckStreamsAreLazy) {
DCHECK(mock_log_source.Log()) << mock_log_source.Log();
DPCHECK(mock_log_source.Log()) << mock_log_source.Log();
DCHECK_EQ(0, 0) << mock_log_source.Log();
- DCHECK_EQ(mock_log_source.Log(), static_cast<const char*>(NULL))
+ DCHECK_EQ(mock_log_source.Log(), static_cast<const char*>(nullptr))
<< mock_log_source.Log();
#endif
}
@@ -386,50 +540,80 @@ void DcheckEmptyFunction1() {
}
void DcheckEmptyFunction2() {}
-TEST_F(LoggingTest, Dcheck) {
+#if DCHECK_IS_CONFIGURABLE
+class ScopedDcheckSeverity {
+ public:
+ ScopedDcheckSeverity(LogSeverity new_severity) : old_severity_(LOG_DCHECK) {
+ LOG_DCHECK = new_severity;
+ }
+
+ ~ScopedDcheckSeverity() { LOG_DCHECK = old_severity_; }
+
+ private:
+ LogSeverity old_severity_;
+};
+#endif // DCHECK_IS_CONFIGURABLE
+
+// https://crbug.com/709067 tracks test flakiness on iOS.
+#if defined(OS_IOS)
+#define MAYBE_Dcheck DISABLED_Dcheck
+#else
+#define MAYBE_Dcheck Dcheck
+#endif
+TEST_F(LoggingTest, MAYBE_Dcheck) {
+#if DCHECK_IS_CONFIGURABLE
+ // DCHECKs are enabled, and LOG_DCHECK is mutable, but defaults to non-fatal.
+ // Set it to LOG_FATAL to get the expected behavior from the rest of this
+ // test.
+ ScopedDcheckSeverity dcheck_severity(LOG_FATAL);
+#endif // DCHECK_IS_CONFIGURABLE
+
#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON)
// Release build.
EXPECT_FALSE(DCHECK_IS_ON());
EXPECT_FALSE(DLOG_IS_ON(DCHECK));
#elif defined(NDEBUG) && defined(DCHECK_ALWAYS_ON)
// Release build with real DCHECKS.
- SetLogAssertHandler(&LogSink);
+ ScopedLogAssertHandler scoped_assert_handler(base::Bind(LogSink));
EXPECT_TRUE(DCHECK_IS_ON());
EXPECT_TRUE(DLOG_IS_ON(DCHECK));
#else
// Debug build.
- SetLogAssertHandler(&LogSink);
+ ScopedLogAssertHandler scoped_assert_handler(base::Bind(LogSink));
EXPECT_TRUE(DCHECK_IS_ON());
EXPECT_TRUE(DLOG_IS_ON(DCHECK));
#endif
- EXPECT_EQ(0, log_sink_call_count);
+ // DCHECKs are fatal iff they're compiled in DCHECK_IS_ON() and the DCHECK
+ // log level is set to fatal.
+ const bool dchecks_are_fatal = DCHECK_IS_ON() && LOG_DCHECK == LOG_FATAL;
+ EXPECT_EQ(0, g_log_sink_call_count);
DCHECK(false);
- EXPECT_EQ(DCHECK_IS_ON() ? 1 : 0, log_sink_call_count);
+ EXPECT_EQ(dchecks_are_fatal ? 1 : 0, g_log_sink_call_count);
DPCHECK(false);
- EXPECT_EQ(DCHECK_IS_ON() ? 2 : 0, log_sink_call_count);
+ EXPECT_EQ(dchecks_are_fatal ? 2 : 0, g_log_sink_call_count);
DCHECK_EQ(0, 1);
- EXPECT_EQ(DCHECK_IS_ON() ? 3 : 0, log_sink_call_count);
+ EXPECT_EQ(dchecks_are_fatal ? 3 : 0, g_log_sink_call_count);
// Test DCHECK on std::nullptr_t
- log_sink_call_count = 0;
+ g_log_sink_call_count = 0;
const void* p_null = nullptr;
const void* p_not_null = &p_null;
DCHECK_EQ(p_null, nullptr);
DCHECK_EQ(nullptr, p_null);
DCHECK_NE(p_not_null, nullptr);
DCHECK_NE(nullptr, p_not_null);
- EXPECT_EQ(0, log_sink_call_count);
+ EXPECT_EQ(0, g_log_sink_call_count);
// Test DCHECK on a scoped enum.
enum class Animal { DOG, CAT };
DCHECK_EQ(Animal::DOG, Animal::DOG);
- EXPECT_EQ(0, log_sink_call_count);
+ EXPECT_EQ(0, g_log_sink_call_count);
DCHECK_EQ(Animal::DOG, Animal::CAT);
- EXPECT_EQ(DCHECK_IS_ON() ? 1 : 0, log_sink_call_count);
+ EXPECT_EQ(dchecks_are_fatal ? 1 : 0, g_log_sink_call_count);
// Test DCHECK on functions and function pointers.
- log_sink_call_count = 0;
+ g_log_sink_call_count = 0;
struct MemberFunctions {
void MemberFunction1() {
// See the comment in DcheckEmptyFunction1().
@@ -443,15 +627,15 @@ TEST_F(LoggingTest, Dcheck) {
void (*fp2)() = DcheckEmptyFunction2;
void (*fp3)() = DcheckEmptyFunction1;
DCHECK_EQ(fp1, fp3);
- EXPECT_EQ(0, log_sink_call_count);
+ EXPECT_EQ(0, g_log_sink_call_count);
DCHECK_EQ(mp1, &MemberFunctions::MemberFunction1);
- EXPECT_EQ(0, log_sink_call_count);
+ EXPECT_EQ(0, g_log_sink_call_count);
DCHECK_EQ(mp2, &MemberFunctions::MemberFunction2);
- EXPECT_EQ(0, log_sink_call_count);
+ EXPECT_EQ(0, g_log_sink_call_count);
DCHECK_EQ(fp1, fp2);
- EXPECT_EQ(DCHECK_IS_ON() ? 1 : 0, log_sink_call_count);
+ EXPECT_EQ(dchecks_are_fatal ? 1 : 0, g_log_sink_call_count);
DCHECK_EQ(mp2, &MemberFunctions::MemberFunction1);
- EXPECT_EQ(DCHECK_IS_ON() ? 2 : 0, log_sink_call_count);
+ EXPECT_EQ(dchecks_are_fatal ? 2 : 0, g_log_sink_call_count);
}
TEST_F(LoggingTest, DcheckReleaseBehavior) {
@@ -487,6 +671,43 @@ TEST_F(LoggingTest, CheckEqStatements) {
CHECK_EQ(false, true); // Unreached.
}
+TEST_F(LoggingTest, NestedLogAssertHandlers) {
+ ::testing::InSequence dummy;
+ ::testing::StrictMock<MockLogAssertHandler> handler_a, handler_b;
+
+ EXPECT_CALL(
+ handler_a,
+ HandleLogAssert(
+ _, _, base::StringPiece("First assert must be caught by handler_a"),
+ _));
+ EXPECT_CALL(
+ handler_b,
+ HandleLogAssert(
+ _, _, base::StringPiece("Second assert must be caught by handler_b"),
+ _));
+ EXPECT_CALL(
+ handler_a,
+ HandleLogAssert(
+ _, _,
+ base::StringPiece("Last assert must be caught by handler_a again"),
+ _));
+
+ logging::ScopedLogAssertHandler scoped_handler_a(base::Bind(
+ &MockLogAssertHandler::HandleLogAssert, base::Unretained(&handler_a)));
+
+ // Using LOG(FATAL) rather than CHECK(false) here since log messages aren't
+ // preserved for CHECKs in official builds.
+ LOG(FATAL) << "First assert must be caught by handler_a";
+
+ {
+ logging::ScopedLogAssertHandler scoped_handler_b(base::Bind(
+ &MockLogAssertHandler::HandleLogAssert, base::Unretained(&handler_b)));
+ LOG(FATAL) << "Second assert must be caught by handler_b";
+ }
+
+ LOG(FATAL) << "Last assert must be caught by handler_a again";
+}
+
// Test that defining an operator<< for a type in a namespace doesn't prevent
// other code in that namespace from calling the operator<<(ostream, wstring)
// defined by logging.h. This can fail if operator<<(ostream, wstring) can't be
@@ -506,6 +727,78 @@ namespace nested_test {
}
} // namespace nested_test
+#if DCHECK_IS_CONFIGURABLE
+TEST_F(LoggingTest, ConfigurableDCheck) {
+ // Verify that DCHECKs default to non-fatal in configurable-DCHECK builds.
+ // Note that we require only that DCHECK is non-fatal by default, rather
+ // than requiring that it be exactly INFO, ERROR, etc level.
+ EXPECT_LT(LOG_DCHECK, LOG_FATAL);
+ DCHECK(false);
+
+ // Verify that DCHECK* aren't hard-wired to crash on failure.
+ LOG_DCHECK = LOG_INFO;
+ DCHECK(false);
+ DCHECK_EQ(1, 2);
+
+ // Verify that DCHECK does crash if LOG_DCHECK is set to LOG_FATAL.
+ LOG_DCHECK = LOG_FATAL;
+
+ ::testing::StrictMock<MockLogAssertHandler> handler;
+ EXPECT_CALL(handler, HandleLogAssert(_, _, _, _)).Times(2);
+ {
+ logging::ScopedLogAssertHandler scoped_handler_b(base::Bind(
+ &MockLogAssertHandler::HandleLogAssert, base::Unretained(&handler)));
+ DCHECK(false);
+ DCHECK_EQ(1, 2);
+ }
+}
+
+TEST_F(LoggingTest, ConfigurableDCheckFeature) {
+ // Initialize FeatureList with and without DcheckIsFatal, and verify the
+ // value of LOG_DCHECK. Note that we don't require that DCHECK take a
+ // specific value when the feature is off, only that it is non-fatal.
+
+ {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitFromCommandLine("DcheckIsFatal", "");
+ EXPECT_EQ(LOG_DCHECK, LOG_FATAL);
+ }
+
+ {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitFromCommandLine("", "DcheckIsFatal");
+ EXPECT_LT(LOG_DCHECK, LOG_FATAL);
+ }
+
+ // The default case is last, so we leave LOG_DCHECK in the default state.
+ {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitFromCommandLine("", "");
+ EXPECT_LT(LOG_DCHECK, LOG_FATAL);
+ }
+}
+#endif // DCHECK_IS_CONFIGURABLE
+
+#if defined(OS_FUCHSIA)
+TEST_F(LoggingTest, FuchsiaLogging) {
+ MockLogSource mock_log_source;
+ EXPECT_CALL(mock_log_source, Log())
+ .Times(DCHECK_IS_ON() ? 2 : 1)
+ .WillRepeatedly(Return("log message"));
+
+ SetMinLogLevel(LOG_INFO);
+
+ EXPECT_TRUE(LOG_IS_ON(INFO));
+ EXPECT_TRUE((DCHECK_IS_ON() != 0) == DLOG_IS_ON(INFO));
+
+ ZX_LOG(INFO, ZX_ERR_INTERNAL) << mock_log_source.Log();
+ ZX_DLOG(INFO, ZX_ERR_INTERNAL) << mock_log_source.Log();
+
+ ZX_CHECK(true, ZX_ERR_INTERNAL);
+ ZX_DCHECK(true, ZX_ERR_INTERNAL);
+}
+#endif // defined(OS_FUCHSIA)
+
} // namespace
} // namespace logging
diff --git a/base/macros.h b/base/macros.h
index b5b03bb226..868511746b 100644
--- a/base/macros.h
+++ b/base/macros.h
@@ -19,6 +19,16 @@
// We define following macros conditionally as they may be defined by another libraries.
+// Distinguish mips32.
+#if defined(__mips__) && (_MIPS_SIM == _ABIO32) && !defined(__mips32__)
+#define __mips32__
+#endif
+
+// Distinguish mips64.
+#if defined(__mips__) && (_MIPS_SIM == _ABI64) && !defined(__mips64__)
+#define __mips64__
+#endif
+
// Put this in the declarations for a class to be uncopyable.
#if !defined(DISALLOW_COPY)
#define DISALLOW_COPY(TypeName) \
@@ -27,24 +37,19 @@
// Put this in the declarations for a class to be unassignable.
#if !defined(DISALLOW_ASSIGN)
-#define DISALLOW_ASSIGN(TypeName) \
- void operator=(const TypeName&) = delete
+#define DISALLOW_ASSIGN(TypeName) TypeName& 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.
+// Put this in the declarations for a class to be uncopyable and unassignable.
#if !defined(DISALLOW_COPY_AND_ASSIGN)
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
- TypeName(const TypeName&) = delete; \
- void operator=(const TypeName&) = delete
+ DISALLOW_COPY(TypeName); \
+ DISALLOW_ASSIGN(TypeName)
#endif
// A macro to disallow all the implicit constructors, namely the
// default constructor, copy constructor and operator= functions.
-//
-// 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.
+// This is especially useful for classes containing only static methods.
#if !defined(DISALLOW_IMPLICIT_CONSTRUCTORS)
#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
TypeName() = delete; \
@@ -60,6 +65,9 @@
// This template function declaration is used in defining arraysize.
// Note that the function doesn't need an implementation, as we only
// use its type.
+//
+// DEPRECATED, please use base::size(array) instead.
+// TODO(https://crbug.com/837308): Replace existing arraysize usages.
#if !defined(arraysize)
template <typename T, size_t N> char (&ArraySizeHelper(T (&array)[N]))[N];
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
@@ -77,30 +85,30 @@ template<typename T>
inline void ignore_result(const T&) {
}
-// The following enum should be used only as a constructor argument to indicate
-// that the variable has static storage class, and that the constructor should
-// do nothing to its state. It indicates to the reader that it is legal to
-// declare a static instance of the class, provided the constructor is given
-// the base::LINKER_INITIALIZED argument. Normally, it is unsafe to declare a
-// static variable that has a constructor or a destructor because invocation
-// order is undefined. However, IF the type can be initialized by filling with
-// zeroes (which the loader does for static variables), AND the destructor also
-// does nothing to the storage, AND there are no virtual methods, then a
-// constructor declared as
-// explicit MyClass(base::LinkerInitialized x) {}
-// and invoked as
-// static MyClass my_variable_name(base::LINKER_INITIALIZED);
namespace base {
-enum LinkerInitialized { LINKER_INITIALIZED };
// 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.
+// it is leaked so that its destructors are not called at exit. This is
+// thread-safe.
+//
+// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DEPRECATED !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+// Please don't use this macro. Use a function-local static of type
+// base::NoDestructor<T> instead:
+//
+// Factory& Factory::GetInstance() {
+// static base::NoDestructor<Factory> instance;
+// return *instance;
+// }
+// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#if !defined(CR_DEFINE_STATIC_LOCAL)
#define CR_DEFINE_STATIC_LOCAL(type, name, arguments) \
static type& name = *new type arguments
#endif
+// Workaround for MSVC, which expands __VA_ARGS__ as one macro argument. To
+// work around this bug, wrap the entire expression in this macro...
+#define CR_EXPAND_ARG(arg) arg
+
} // base
#endif // BASE_MACROS_H_
diff --git a/base/memory/aligned_memory.cc b/base/memory/aligned_memory.cc
index 526a49587a..93cbeb57f7 100644
--- a/base/memory/aligned_memory.cc
+++ b/base/memory/aligned_memory.cc
@@ -17,7 +17,7 @@ void* AlignedAlloc(size_t size, size_t alignment) {
DCHECK_GT(size, 0U);
DCHECK_EQ(alignment & (alignment - 1), 0U);
DCHECK_EQ(alignment % sizeof(void*), 0U);
- void* ptr = NULL;
+ void* ptr = nullptr;
#if defined(COMPILER_MSVC)
ptr = _aligned_malloc(size, alignment);
// Android technically supports posix_memalign(), but does not expose it in
@@ -29,7 +29,7 @@ void* AlignedAlloc(size_t size, size_t alignment) {
ptr = memalign(alignment, size);
#else
if (posix_memalign(&ptr, alignment, size))
- ptr = NULL;
+ ptr = nullptr;
#endif
// Since aligned allocations may fail for non-memory related reasons, force a
// crash if we encounter a failed allocation; maintaining consistent behavior
diff --git a/base/memory/aligned_memory.h b/base/memory/aligned_memory.h
index d8290115db..89f95054ce 100644
--- a/base/memory/aligned_memory.h
+++ b/base/memory/aligned_memory.h
@@ -2,43 +2,17 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// AlignedMemory is a POD type that gives you a portable way to specify static
-// or local stack data of a given alignment and size. For example, if you need
-// static storage for a class, but you want manual control over when the object
-// is constructed and destructed (you don't want static initialization and
-// destruction), use AlignedMemory:
-//
-// static AlignedMemory<sizeof(MyClass), ALIGNOF(MyClass)> my_class;
-//
-// // ... at runtime:
-// new(my_class.void_data()) MyClass();
-//
-// // ... use it:
-// MyClass* mc = my_class.data_as<MyClass>();
-//
-// // ... later, to destruct my_class:
-// my_class.data_as<MyClass>()->MyClass::~MyClass();
-//
-// Alternatively, a runtime sized aligned allocation can be created:
-//
-// float* my_array = static_cast<float*>(AlignedAlloc(size, alignment));
-//
-// // ... later, to release the memory:
-// AlignedFree(my_array);
-//
-// Or using unique_ptr:
-//
-// std::unique_ptr<float, AlignedFreeDeleter> my_array(
-// static_cast<float*>(AlignedAlloc(size, alignment)));
-
#ifndef BASE_MEMORY_ALIGNED_MEMORY_H_
#define BASE_MEMORY_ALIGNED_MEMORY_H_
#include <stddef.h>
#include <stdint.h>
+#include <type_traits>
+
#include "base/base_export.h"
#include "base/compiler_specific.h"
+#include "build/build_config.h"
#if defined(COMPILER_MSVC)
#include <malloc.h>
@@ -46,54 +20,21 @@
#include <stdlib.h>
#endif
-namespace base {
-
-// AlignedMemory is specialized for all supported alignments.
-// Make sure we get a compiler error if someone uses an unsupported alignment.
-template <size_t Size, size_t ByteAlignment>
-struct AlignedMemory {};
-
-#define BASE_DECL_ALIGNED_MEMORY(byte_alignment) \
- template <size_t Size> \
- class AlignedMemory<Size, byte_alignment> { \
- public: \
- ALIGNAS(byte_alignment) uint8_t data_[Size]; \
- void* void_data() { return static_cast<void*>(data_); } \
- const void* void_data() const { return static_cast<const void*>(data_); } \
- template <typename Type> \
- Type* data_as() { \
- return static_cast<Type*>(void_data()); \
- } \
- template <typename Type> \
- const Type* data_as() const { \
- return static_cast<const Type*>(void_data()); \
- } \
- \
- private: \
- void* operator new(size_t); \
- void operator delete(void*); \
- }
-
-// Specialization for all alignments is required because MSVC (as of VS 2008)
-// does not understand ALIGNAS(ALIGNOF(Type)) or ALIGNAS(template_param).
-// Greater than 4096 alignment is not supported by some compilers, so 4096 is
-// the maximum specified here.
-BASE_DECL_ALIGNED_MEMORY(1);
-BASE_DECL_ALIGNED_MEMORY(2);
-BASE_DECL_ALIGNED_MEMORY(4);
-BASE_DECL_ALIGNED_MEMORY(8);
-BASE_DECL_ALIGNED_MEMORY(16);
-BASE_DECL_ALIGNED_MEMORY(32);
-BASE_DECL_ALIGNED_MEMORY(64);
-BASE_DECL_ALIGNED_MEMORY(128);
-BASE_DECL_ALIGNED_MEMORY(256);
-BASE_DECL_ALIGNED_MEMORY(512);
-BASE_DECL_ALIGNED_MEMORY(1024);
-BASE_DECL_ALIGNED_MEMORY(2048);
-BASE_DECL_ALIGNED_MEMORY(4096);
+// A runtime sized aligned allocation can be created:
+//
+// float* my_array = static_cast<float*>(AlignedAlloc(size, alignment));
+//
+// // ... later, to release the memory:
+// AlignedFree(my_array);
+//
+// Or using unique_ptr:
+//
+// std::unique_ptr<float, AlignedFreeDeleter> my_array(
+// static_cast<float*>(AlignedAlloc(size, alignment)));
-#undef BASE_DECL_ALIGNED_MEMORY
+namespace base {
+// This can be replaced with std::aligned_malloc when we have C++17.
BASE_EXPORT void* AlignedAlloc(size_t size, size_t alignment);
inline void AlignedFree(void* ptr) {
diff --git a/base/memory/aligned_memory_unittest.cc b/base/memory/aligned_memory_unittest.cc
index 892c50ef70..e354f38b75 100644
--- a/base/memory/aligned_memory_unittest.cc
+++ b/base/memory/aligned_memory_unittest.cc
@@ -12,84 +12,35 @@
#define EXPECT_ALIGNED(ptr, align) \
EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(ptr) & (align - 1))
-namespace {
-
-using base::AlignedMemory;
-
-TEST(AlignedMemoryTest, StaticAlignment) {
- static AlignedMemory<8, 8> raw8;
- static AlignedMemory<8, 16> raw16;
- static AlignedMemory<8, 256> raw256;
- static AlignedMemory<8, 4096> raw4096;
-
- EXPECT_EQ(8u, ALIGNOF(raw8));
- EXPECT_EQ(16u, ALIGNOF(raw16));
- EXPECT_EQ(256u, ALIGNOF(raw256));
- EXPECT_EQ(4096u, ALIGNOF(raw4096));
-
- EXPECT_ALIGNED(raw8.void_data(), 8);
- EXPECT_ALIGNED(raw16.void_data(), 16);
- EXPECT_ALIGNED(raw256.void_data(), 256);
- EXPECT_ALIGNED(raw4096.void_data(), 4096);
-}
-
-TEST(AlignedMemoryTest, StackAlignment) {
- AlignedMemory<8, 8> raw8;
- AlignedMemory<8, 16> raw16;
- AlignedMemory<8, 128> raw128;
-
- EXPECT_EQ(8u, ALIGNOF(raw8));
- EXPECT_EQ(16u, ALIGNOF(raw16));
- EXPECT_EQ(128u, ALIGNOF(raw128));
-
- EXPECT_ALIGNED(raw8.void_data(), 8);
- EXPECT_ALIGNED(raw16.void_data(), 16);
- EXPECT_ALIGNED(raw128.void_data(), 128);
-
- // NaCl x86-64 compiler emits non-validating instructions for >128
- // bytes alignment.
- // http://www.chromium.org/nativeclient/design-documents/nacl-sfi-model-on-x86-64-systems
- // TODO(hamaji): Ideally, NaCl compiler for x86-64 should workaround
- // this limitation and this #if should be removed.
- // https://code.google.com/p/nativeclient/issues/detail?id=3463
-#if !(defined(OS_NACL) && defined(ARCH_CPU_X86_64))
- AlignedMemory<8, 256> raw256;
- EXPECT_EQ(256u, ALIGNOF(raw256));
- EXPECT_ALIGNED(raw256.void_data(), 256);
-
- AlignedMemory<8, 4096> raw4096;
- EXPECT_EQ(4096u, ALIGNOF(raw4096));
- EXPECT_ALIGNED(raw4096.void_data(), 4096);
-#endif // !(defined(OS_NACL) && defined(ARCH_CPU_X86_64))
-}
+namespace base {
TEST(AlignedMemoryTest, DynamicAllocation) {
- void* p = base::AlignedAlloc(8, 8);
+ void* p = AlignedAlloc(8, 8);
EXPECT_TRUE(p);
EXPECT_ALIGNED(p, 8);
- base::AlignedFree(p);
+ AlignedFree(p);
- p = base::AlignedAlloc(8, 16);
+ p = AlignedAlloc(8, 16);
EXPECT_TRUE(p);
EXPECT_ALIGNED(p, 16);
- base::AlignedFree(p);
+ AlignedFree(p);
- p = base::AlignedAlloc(8, 256);
+ p = AlignedAlloc(8, 256);
EXPECT_TRUE(p);
EXPECT_ALIGNED(p, 256);
- base::AlignedFree(p);
+ AlignedFree(p);
- p = base::AlignedAlloc(8, 4096);
+ p = AlignedAlloc(8, 4096);
EXPECT_TRUE(p);
EXPECT_ALIGNED(p, 4096);
- base::AlignedFree(p);
+ AlignedFree(p);
}
TEST(AlignedMemoryTest, ScopedDynamicAllocation) {
- std::unique_ptr<float, base::AlignedFreeDeleter> p(
- static_cast<float*>(base::AlignedAlloc(8, 8)));
+ std::unique_ptr<float, AlignedFreeDeleter> p(
+ static_cast<float*>(AlignedAlloc(8, 8)));
EXPECT_TRUE(p.get());
EXPECT_ALIGNED(p.get(), 8);
}
-} // namespace
+} // namespace base
diff --git a/base/memory/linked_ptr_unittest.cc b/base/memory/linked_ptr_unittest.cc
index f6bc410942..344ffa48d6 100644
--- a/base/memory/linked_ptr_unittest.cc
+++ b/base/memory/linked_ptr_unittest.cc
@@ -34,20 +34,20 @@ struct B: public A {
TEST(LinkedPtrTest, Test) {
{
linked_ptr<A> a0, a1, a2;
- a0 = a0;
+ a0 = *&a0; // The *& defeats Clang's -Wself-assign warning.
a1 = a2;
- ASSERT_EQ(a0.get(), static_cast<A*>(NULL));
- ASSERT_EQ(a1.get(), static_cast<A*>(NULL));
- ASSERT_EQ(a2.get(), static_cast<A*>(NULL));
- ASSERT_TRUE(a0 == NULL);
- ASSERT_TRUE(a1 == NULL);
- ASSERT_TRUE(a2 == NULL);
+ ASSERT_EQ(a0.get(), static_cast<A*>(nullptr));
+ ASSERT_EQ(a1.get(), static_cast<A*>(nullptr));
+ ASSERT_EQ(a2.get(), static_cast<A*>(nullptr));
+ ASSERT_TRUE(a0 == nullptr);
+ ASSERT_TRUE(a1 == nullptr);
+ ASSERT_TRUE(a2 == nullptr);
{
linked_ptr<A> a3(new A);
a0 = a3;
ASSERT_TRUE(a0 == a3);
- ASSERT_TRUE(a0 != NULL);
+ ASSERT_TRUE(a0 != nullptr);
ASSERT_TRUE(a0.get() == a3);
ASSERT_TRUE(a0 == a3.get());
linked_ptr<A> a4(a0);
@@ -60,7 +60,7 @@ TEST(LinkedPtrTest, Test) {
linked_ptr<A> a6(b0);
ASSERT_TRUE(b0 == a6);
ASSERT_TRUE(a6 == b0);
- ASSERT_TRUE(b0 != NULL);
+ ASSERT_TRUE(b0 != nullptr);
a5 = b0;
a5 = b0;
a3->Use();
diff --git a/base/memory/manual_constructor.h b/base/memory/manual_constructor.h
deleted file mode 100644
index f401f62d51..0000000000
--- a/base/memory/manual_constructor.h
+++ /dev/null
@@ -1,75 +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.
-
-// ManualConstructor statically-allocates space in which to store some
-// object, but does not initialize it. You can then call the constructor
-// and destructor for the object yourself as you see fit. This is useful
-// for memory management optimizations, where you want to initialize and
-// destroy an object multiple times but only allocate it once.
-//
-// (When I say ManualConstructor statically allocates space, I mean that
-// the ManualConstructor object itself is forced to be the right size.)
-//
-// For example usage, check out base/containers/small_map.h.
-
-#ifndef BASE_MEMORY_MANUAL_CONSTRUCTOR_H_
-#define BASE_MEMORY_MANUAL_CONSTRUCTOR_H_
-
-#include <stddef.h>
-
-#include "base/compiler_specific.h"
-#include "base/memory/aligned_memory.h"
-
-namespace base {
-
-template <typename Type>
-class ManualConstructor {
- public:
- // No constructor or destructor because one of the most useful uses of
- // this class is as part of a union, and members of a union cannot have
- // constructors or destructors. And, anyway, the whole point of this
- // class is to bypass these.
-
- // Support users creating arrays of ManualConstructor<>s. This ensures that
- // the array itself has the correct alignment.
- static void* operator new[](size_t size) {
- return AlignedAlloc(size, ALIGNOF(Type));
- }
- static void operator delete[](void* mem) {
- AlignedFree(mem);
- }
-
- inline Type* get() {
- return space_.template data_as<Type>();
- }
- inline const Type* get() const {
- return space_.template data_as<Type>();
- }
-
- inline Type* operator->() { return get(); }
- inline const Type* operator->() const { return get(); }
-
- inline Type& operator*() { return *get(); }
- inline const Type& operator*() const { return *get(); }
-
- template <typename... Ts>
- inline void Init(Ts&&... params) {
- new(space_.void_data()) Type(std::forward<Ts>(params)...);
- }
-
- inline void InitFromMove(ManualConstructor<Type>&& o) {
- Init(std::move(*o));
- }
-
- inline void Destroy() {
- get()->~Type();
- }
-
- private:
- AlignedMemory<sizeof(Type), ALIGNOF(Type)> space_;
-};
-
-} // namespace base
-
-#endif // BASE_MEMORY_MANUAL_CONSTRUCTOR_H_
diff --git a/base/memory/platform_shared_memory_region.cc b/base/memory/platform_shared_memory_region.cc
new file mode 100644
index 0000000000..c145336ebd
--- /dev/null
+++ b/base/memory/platform_shared_memory_region.cc
@@ -0,0 +1,37 @@
+// 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.
+
+#include "base/memory/platform_shared_memory_region.h"
+
+#include "base/memory/shared_memory_mapping.h"
+
+namespace base {
+namespace subtle {
+
+// static
+PlatformSharedMemoryRegion PlatformSharedMemoryRegion::CreateWritable(
+ size_t size) {
+ return Create(Mode::kWritable, size);
+}
+
+// static
+PlatformSharedMemoryRegion PlatformSharedMemoryRegion::CreateUnsafe(
+ size_t size) {
+ return Create(Mode::kUnsafe, size);
+}
+
+PlatformSharedMemoryRegion::PlatformSharedMemoryRegion() = default;
+PlatformSharedMemoryRegion::PlatformSharedMemoryRegion(
+ PlatformSharedMemoryRegion&& other) = default;
+PlatformSharedMemoryRegion& PlatformSharedMemoryRegion::operator=(
+ PlatformSharedMemoryRegion&& other) = default;
+PlatformSharedMemoryRegion::~PlatformSharedMemoryRegion() = default;
+
+PlatformSharedMemoryRegion::ScopedPlatformHandle
+PlatformSharedMemoryRegion::PassPlatformHandle() {
+ return std::move(handle_);
+}
+
+} // namespace subtle
+} // namespace base
diff --git a/base/memory/platform_shared_memory_region.h b/base/memory/platform_shared_memory_region.h
new file mode 100644
index 0000000000..3d830b6f93
--- /dev/null
+++ b/base/memory/platform_shared_memory_region.h
@@ -0,0 +1,229 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_PLATFORM_SHARED_MEMORY_REGION_H_
+#define BASE_MEMORY_PLATFORM_SHARED_MEMORY_REGION_H_
+
+#include <utility>
+
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/unguessable_token.h"
+#include "build/build_config.h"
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include <mach/mach.h>
+#include "base/mac/scoped_mach_port.h"
+#elif defined(OS_FUCHSIA)
+#include <lib/zx/vmo.h>
+#elif defined(OS_WIN)
+#include "base/win/scoped_handle.h"
+#include "base/win/windows_types.h"
+#elif defined(OS_POSIX)
+#include <sys/types.h>
+#include "base/file_descriptor_posix.h"
+#include "base/files/scoped_file.h"
+#endif
+
+namespace base {
+namespace subtle {
+
+#if defined(OS_POSIX) && (!defined(OS_MACOSX) || defined(OS_IOS)) && \
+ !defined(OS_ANDROID)
+// Helper structs to keep two descriptors on POSIX. It's needed to support
+// ConvertToReadOnly().
+struct BASE_EXPORT FDPair {
+ int fd;
+ int readonly_fd;
+};
+
+struct BASE_EXPORT ScopedFDPair {
+ ScopedFDPair();
+ ScopedFDPair(ScopedFD in_fd, ScopedFD in_readonly_fd);
+ ScopedFDPair(ScopedFDPair&&);
+ ScopedFDPair& operator=(ScopedFDPair&&);
+ ~ScopedFDPair();
+
+ FDPair get() const;
+
+ ScopedFD fd;
+ ScopedFD readonly_fd;
+};
+#endif
+
+// Implementation class for shared memory regions.
+//
+// This class does the following:
+//
+// - Wraps and owns a shared memory region platform handle.
+// - Provides a way to allocate a new region of platform shared memory of given
+// size.
+// - Provides a way to create mapping of the region in the current process'
+// address space, under special access-control constraints (see Mode).
+// - Provides methods to help transferring the handle across process boundaries.
+// - Holds a 128-bit unique identifier used to uniquely identify the same
+// kernel region resource across processes (used for memory tracking).
+// - Has a method to retrieve the region's size in bytes.
+//
+// IMPORTANT NOTE: Users should never use this directly, but
+// ReadOnlySharedMemoryRegion, WritableSharedMemoryRegion or
+// UnsafeSharedMemoryRegion since this is an implementation class.
+class BASE_EXPORT PlatformSharedMemoryRegion {
+ public:
+ // Permission mode of the platform handle. Each mode corresponds to one of the
+ // typed shared memory classes:
+ //
+ // * ReadOnlySharedMemoryRegion: A region that can only create read-only
+ // mappings.
+ //
+ // * WritableSharedMemoryRegion: A region that can only create writable
+ // mappings. The region can be demoted to ReadOnlySharedMemoryRegion without
+ // the possibility of promoting back to writable.
+ //
+ // * UnsafeSharedMemoryRegion: A region that can only create writable
+ // mappings. The region cannot be demoted to ReadOnlySharedMemoryRegion.
+ enum class Mode {
+ kReadOnly, // ReadOnlySharedMemoryRegion
+ kWritable, // WritableSharedMemoryRegion
+ kUnsafe, // UnsafeSharedMemoryRegion
+ kMaxValue = kUnsafe
+ };
+
+// Platform-specific shared memory type used by this class.
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ using PlatformHandle = mach_port_t;
+ using ScopedPlatformHandle = mac::ScopedMachSendRight;
+#elif defined(OS_FUCHSIA)
+ using PlatformHandle = zx_handle_t;
+ using ScopedPlatformHandle = zx::vmo;
+#elif defined(OS_WIN)
+ using PlatformHandle = HANDLE;
+ using ScopedPlatformHandle = win::ScopedHandle;
+#elif defined(OS_ANDROID)
+ using PlatformHandle = int;
+ using ScopedPlatformHandle = ScopedFD;
+#else
+ using PlatformHandle = FDPair;
+ using ScopedPlatformHandle = ScopedFDPair;
+#endif
+
+ // The minimum alignment in bytes that any mapped address produced by Map()
+ // and MapAt() is guaranteed to have.
+ enum { kMapMinimumAlignment = 32 };
+
+ // Creates a new PlatformSharedMemoryRegion with corresponding mode and size.
+ // Creating in kReadOnly mode isn't supported because then there will be no
+ // way to modify memory content.
+ static PlatformSharedMemoryRegion CreateWritable(size_t size);
+ static PlatformSharedMemoryRegion CreateUnsafe(size_t size);
+
+ // Returns a new PlatformSharedMemoryRegion that takes ownership of the
+ // |handle|. All parameters must be taken from another valid
+ // PlatformSharedMemoryRegion instance, e.g. |size| must be equal to the
+ // actual region size as allocated by the kernel.
+ // Closes the |handle| and returns an invalid instance if passed parameters
+ // are invalid.
+ static PlatformSharedMemoryRegion Take(ScopedPlatformHandle handle,
+ Mode mode,
+ size_t size,
+ const UnguessableToken& guid);
+
+ // Default constructor initializes an invalid instance, i.e. an instance that
+ // doesn't wrap any valid platform handle.
+ PlatformSharedMemoryRegion();
+
+ // Move operations are allowed.
+ PlatformSharedMemoryRegion(PlatformSharedMemoryRegion&&);
+ PlatformSharedMemoryRegion& operator=(PlatformSharedMemoryRegion&&);
+
+ // Destructor closes the platform handle. Does nothing if the handle is
+ // invalid.
+ ~PlatformSharedMemoryRegion();
+
+ // Passes ownership of the platform handle to the caller. The current instance
+ // becomes invalid. It's the responsibility of the caller to close the handle.
+ ScopedPlatformHandle PassPlatformHandle() WARN_UNUSED_RESULT;
+
+ // Returns the platform handle. The current instance keeps ownership of this
+ // handle.
+ PlatformHandle GetPlatformHandle() const;
+
+ // Whether the platform handle is valid.
+ bool IsValid() const;
+
+ // Duplicates the platform handle and creates a new PlatformSharedMemoryRegion
+ // with the same |mode_|, |size_| and |guid_| that owns this handle. Returns
+ // invalid region on failure, the current instance remains valid.
+ // Can be called only in kReadOnly and kUnsafe modes, CHECK-fails if is
+ // called in kWritable mode.
+ PlatformSharedMemoryRegion Duplicate() const;
+
+ // Converts the region to read-only. Returns whether the operation succeeded.
+ // Makes the current instance invalid on failure. Can be called only in
+ // kWritable mode, all other modes will CHECK-fail. The object will have
+ // kReadOnly mode after this call on success.
+ bool ConvertToReadOnly();
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // Same as above, but |mapped_addr| is used as a hint to avoid additional
+ // mapping of the memory object.
+ // |mapped_addr| must be mapped location of |memory_object_|. If the location
+ // is unknown, |mapped_addr| should be |nullptr|.
+ bool ConvertToReadOnly(void* mapped_addr);
+#endif // defined(OS_MACOSX) && !defined(OS_IOS)
+
+ // Converts the region to unsafe. Returns whether the operation succeeded.
+ // Makes the current instance invalid on failure. Can be called only in
+ // kWritable mode, all other modes will CHECK-fail. The object will have
+ // kUnsafe mode after this call on success.
+ bool ConvertToUnsafe();
+
+ // Maps |size| bytes of the shared memory region starting with the given
+ // |offset| into the caller's address space. |offset| must be aligned to value
+ // of |SysInfo::VMAllocationGranularity()|. Fails if requested bytes are out
+ // of the region limits.
+ // Returns true and sets |memory| and |mapped_size| on success, returns false
+ // and leaves output parameters in unspecified state otherwise. The mapped
+ // address is guaranteed to have an alignment of at least
+ // |kMapMinimumAlignment|.
+ bool MapAt(off_t offset,
+ size_t size,
+ void** memory,
+ size_t* mapped_size) const;
+
+ const UnguessableToken& GetGUID() const { return guid_; }
+
+ size_t GetSize() const { return size_; }
+
+ Mode GetMode() const { return mode_; }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(PlatformSharedMemoryRegionTest,
+ CreateReadOnlyRegionDeathTest);
+ FRIEND_TEST_ALL_PREFIXES(PlatformSharedMemoryRegionTest,
+ CheckPlatformHandlePermissionsCorrespondToMode);
+ static PlatformSharedMemoryRegion Create(Mode mode, size_t size);
+
+ static bool CheckPlatformHandlePermissionsCorrespondToMode(
+ PlatformHandle handle,
+ Mode mode,
+ size_t size);
+
+ PlatformSharedMemoryRegion(ScopedPlatformHandle handle,
+ Mode mode,
+ size_t size,
+ const UnguessableToken& guid);
+
+ ScopedPlatformHandle handle_;
+ Mode mode_ = Mode::kReadOnly;
+ size_t size_ = 0;
+ UnguessableToken guid_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformSharedMemoryRegion);
+};
+
+} // namespace subtle
+} // namespace base
+
+#endif // BASE_MEMORY_PLATFORM_SHARED_MEMORY_REGION_H_
diff --git a/base/memory/platform_shared_memory_region_mac.cc b/base/memory/platform_shared_memory_region_mac.cc
new file mode 100644
index 0000000000..4a8b440c26
--- /dev/null
+++ b/base/memory/platform_shared_memory_region_mac.cc
@@ -0,0 +1,233 @@
+// 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.
+
+#include "base/memory/platform_shared_memory_region.h"
+
+#include <mach/mach_vm.h>
+
+#include "base/mac/mach_logging.h"
+#include "base/mac/scoped_mach_vm.h"
+#include "base/numerics/checked_math.h"
+#include "build/build_config.h"
+
+#if defined(OS_IOS)
+#error "MacOS only - iOS uses platform_shared_memory_region_posix.cc"
+#endif
+
+namespace base {
+namespace subtle {
+
+// static
+PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Take(
+ mac::ScopedMachSendRight handle,
+ Mode mode,
+ size_t size,
+ const UnguessableToken& guid) {
+ if (!handle.is_valid())
+ return {};
+
+ if (size == 0)
+ return {};
+
+ if (size > static_cast<size_t>(std::numeric_limits<int>::max()))
+ return {};
+
+ CHECK(
+ CheckPlatformHandlePermissionsCorrespondToMode(handle.get(), mode, size));
+
+ return PlatformSharedMemoryRegion(std::move(handle), mode, size, guid);
+}
+
+mach_port_t PlatformSharedMemoryRegion::GetPlatformHandle() const {
+ return handle_.get();
+}
+
+bool PlatformSharedMemoryRegion::IsValid() const {
+ return handle_.is_valid();
+}
+
+PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Duplicate() const {
+ if (!IsValid())
+ return {};
+
+ CHECK_NE(mode_, Mode::kWritable)
+ << "Duplicating a writable shared memory region is prohibited";
+
+ // Increment the ref count.
+ kern_return_t kr = mach_port_mod_refs(mach_task_self(), handle_.get(),
+ MACH_PORT_RIGHT_SEND, 1);
+ if (kr != KERN_SUCCESS) {
+ MACH_DLOG(ERROR, kr) << "mach_port_mod_refs";
+ return {};
+ }
+
+ return PlatformSharedMemoryRegion(mac::ScopedMachSendRight(handle_.get()),
+ mode_, size_, guid_);
+}
+
+bool PlatformSharedMemoryRegion::ConvertToReadOnly() {
+ return ConvertToReadOnly(nullptr);
+}
+
+bool PlatformSharedMemoryRegion::ConvertToReadOnly(void* mapped_addr) {
+ if (!IsValid())
+ return false;
+
+ CHECK_EQ(mode_, Mode::kWritable)
+ << "Only writable shared memory region can be converted to read-only";
+
+ mac::ScopedMachSendRight handle_copy(handle_.release());
+
+ void* temp_addr = mapped_addr;
+ mac::ScopedMachVM scoped_memory;
+ if (!temp_addr) {
+ // Intentionally lower current prot and max prot to |VM_PROT_READ|.
+ kern_return_t kr = mach_vm_map(
+ mach_task_self(), reinterpret_cast<mach_vm_address_t*>(&temp_addr),
+ size_, 0, VM_FLAGS_ANYWHERE, handle_copy.get(), 0, FALSE, VM_PROT_READ,
+ VM_PROT_READ, VM_INHERIT_NONE);
+ if (kr != KERN_SUCCESS) {
+ MACH_DLOG(ERROR, kr) << "mach_vm_map";
+ return false;
+ }
+ scoped_memory.reset(reinterpret_cast<vm_address_t>(temp_addr),
+ mach_vm_round_page(size_));
+ }
+
+ // Make new memory object.
+ memory_object_size_t allocation_size = size_;
+ mac::ScopedMachSendRight named_right;
+ kern_return_t kr = mach_make_memory_entry_64(
+ mach_task_self(), &allocation_size,
+ reinterpret_cast<memory_object_offset_t>(temp_addr), VM_PROT_READ,
+ named_right.receive(), MACH_PORT_NULL);
+ if (kr != KERN_SUCCESS) {
+ MACH_DLOG(ERROR, kr) << "mach_make_memory_entry_64";
+ return false;
+ }
+ DCHECK_GE(allocation_size, size_);
+
+ handle_ = std::move(named_right);
+ mode_ = Mode::kReadOnly;
+ return true;
+}
+
+bool PlatformSharedMemoryRegion::ConvertToUnsafe() {
+ if (!IsValid())
+ return false;
+
+ CHECK_EQ(mode_, Mode::kWritable)
+ << "Only writable shared memory region can be converted to unsafe";
+
+ mode_ = Mode::kUnsafe;
+ return true;
+}
+
+bool PlatformSharedMemoryRegion::MapAt(off_t offset,
+ size_t size,
+ void** memory,
+ size_t* mapped_size) const {
+ if (!IsValid())
+ return false;
+
+ size_t end_byte;
+ if (!CheckAdd(offset, size).AssignIfValid(&end_byte) || end_byte > size_) {
+ return false;
+ }
+
+ bool write_allowed = mode_ != Mode::kReadOnly;
+ vm_prot_t vm_prot_write = write_allowed ? VM_PROT_WRITE : 0;
+ kern_return_t kr = mach_vm_map(
+ mach_task_self(),
+ reinterpret_cast<mach_vm_address_t*>(memory), // Output parameter
+ size,
+ 0, // Alignment mask
+ VM_FLAGS_ANYWHERE, handle_.get(), offset,
+ FALSE, // Copy
+ VM_PROT_READ | vm_prot_write, // Current protection
+ VM_PROT_READ | vm_prot_write, // Maximum protection
+ VM_INHERIT_NONE);
+ if (kr != KERN_SUCCESS) {
+ MACH_DLOG(ERROR, kr) << "mach_vm_map";
+ return false;
+ }
+
+ *mapped_size = size;
+ DCHECK_EQ(0U,
+ reinterpret_cast<uintptr_t>(*memory) & (kMapMinimumAlignment - 1));
+ return true;
+}
+
+// static
+PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Create(Mode mode,
+ size_t size) {
+ if (size == 0)
+ return {};
+
+ if (size > static_cast<size_t>(std::numeric_limits<int>::max()))
+ return {};
+
+ CHECK_NE(mode, Mode::kReadOnly) << "Creating a region in read-only mode will "
+ "lead to this region being non-modifiable";
+
+ mach_vm_size_t vm_size = size;
+ mac::ScopedMachSendRight named_right;
+ kern_return_t kr = mach_make_memory_entry_64(
+ mach_task_self(), &vm_size,
+ 0, // Address.
+ MAP_MEM_NAMED_CREATE | VM_PROT_READ | VM_PROT_WRITE,
+ named_right.receive(),
+ MACH_PORT_NULL); // Parent handle.
+ if (kr != KERN_SUCCESS) {
+ MACH_DLOG(ERROR, kr) << "mach_make_memory_entry_64";
+ return {};
+ }
+ DCHECK_GE(vm_size, size);
+
+ return PlatformSharedMemoryRegion(std::move(named_right), mode, size,
+ UnguessableToken::Create());
+}
+
+// static
+bool PlatformSharedMemoryRegion::CheckPlatformHandlePermissionsCorrespondToMode(
+ PlatformHandle handle,
+ Mode mode,
+ size_t size) {
+ mach_vm_address_t temp_addr = 0;
+ kern_return_t kr =
+ mach_vm_map(mach_task_self(), &temp_addr, size, 0, VM_FLAGS_ANYWHERE,
+ handle, 0, FALSE, VM_PROT_READ | VM_PROT_WRITE,
+ VM_PROT_READ | VM_PROT_WRITE, VM_INHERIT_NONE);
+ if (kr == KERN_SUCCESS) {
+ kern_return_t kr_deallocate =
+ mach_vm_deallocate(mach_task_self(), temp_addr, size);
+ MACH_DLOG_IF(ERROR, kr_deallocate != KERN_SUCCESS, kr_deallocate)
+ << "mach_vm_deallocate";
+ } else if (kr != KERN_INVALID_RIGHT) {
+ MACH_DLOG(ERROR, kr) << "mach_vm_map";
+ return false;
+ }
+
+ bool is_read_only = kr == KERN_INVALID_RIGHT;
+ bool expected_read_only = mode == Mode::kReadOnly;
+
+ if (is_read_only != expected_read_only) {
+ DLOG(ERROR) << "VM region has a wrong protection mask: it is"
+ << (is_read_only ? " " : " not ") << "read-only but it should"
+ << (expected_read_only ? " " : " not ") << "be";
+ return false;
+ }
+
+ return true;
+}
+
+PlatformSharedMemoryRegion::PlatformSharedMemoryRegion(
+ mac::ScopedMachSendRight handle,
+ Mode mode,
+ size_t size,
+ const UnguessableToken& guid)
+ : handle_(std::move(handle)), mode_(mode), size_(size), guid_(guid) {}
+
+} // namespace subtle
+} // namespace base
diff --git a/base/memory/platform_shared_memory_region_posix.cc b/base/memory/platform_shared_memory_region_posix.cc
new file mode 100644
index 0000000000..d4b6d5c00c
--- /dev/null
+++ b/base/memory/platform_shared_memory_region_posix.cc
@@ -0,0 +1,328 @@
+// 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.
+
+#include "base/memory/platform_shared_memory_region.h"
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include "base/files/file_util.h"
+#include "base/numerics/checked_math.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+
+namespace base {
+namespace subtle {
+
+namespace {
+
+struct ScopedPathUnlinkerTraits {
+ static const FilePath* InvalidValue() { return nullptr; }
+
+ static void Free(const FilePath* path) {
+ if (unlink(path->value().c_str()))
+ PLOG(WARNING) << "unlink";
+ }
+};
+
+// Unlinks the FilePath when the object is destroyed.
+using ScopedPathUnlinker =
+ ScopedGeneric<const FilePath*, ScopedPathUnlinkerTraits>;
+
+#if !defined(OS_NACL)
+bool CheckFDAccessMode(int fd, int expected_mode) {
+ int fd_status = fcntl(fd, F_GETFL);
+ if (fd_status == -1) {
+ DPLOG(ERROR) << "fcntl(" << fd << ", F_GETFL) failed";
+ return false;
+ }
+
+ int mode = fd_status & O_ACCMODE;
+ if (mode != expected_mode) {
+ DLOG(ERROR) << "Descriptor access mode (" << mode
+ << ") differs from expected (" << expected_mode << ")";
+ return false;
+ }
+
+ return true;
+}
+#endif // !defined(OS_NACL)
+
+} // namespace
+
+ScopedFDPair::ScopedFDPair() = default;
+
+ScopedFDPair::ScopedFDPair(ScopedFDPair&&) = default;
+
+ScopedFDPair& ScopedFDPair::operator=(ScopedFDPair&&) = default;
+
+ScopedFDPair::~ScopedFDPair() = default;
+
+ScopedFDPair::ScopedFDPair(ScopedFD in_fd, ScopedFD in_readonly_fd)
+ : fd(std::move(in_fd)), readonly_fd(std::move(in_readonly_fd)) {}
+
+FDPair ScopedFDPair::get() const {
+ return {fd.get(), readonly_fd.get()};
+}
+
+// static
+PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Take(
+ ScopedFDPair handle,
+ Mode mode,
+ size_t size,
+ const UnguessableToken& guid) {
+ if (!handle.fd.is_valid())
+ return {};
+
+ if (size == 0)
+ return {};
+
+ if (size > static_cast<size_t>(std::numeric_limits<int>::max()))
+ return {};
+
+ CHECK(
+ CheckPlatformHandlePermissionsCorrespondToMode(handle.get(), mode, size));
+
+ switch (mode) {
+ case Mode::kReadOnly:
+ case Mode::kUnsafe:
+ if (handle.readonly_fd.is_valid()) {
+ handle.readonly_fd.reset();
+ DLOG(WARNING) << "Readonly handle shouldn't be valid for a "
+ "non-writable memory region; closing";
+ }
+ break;
+ case Mode::kWritable:
+ if (!handle.readonly_fd.is_valid()) {
+ DLOG(ERROR)
+ << "Readonly handle must be valid for writable memory region";
+ return {};
+ }
+ break;
+ default:
+ DLOG(ERROR) << "Invalid permission mode: " << static_cast<int>(mode);
+ return {};
+ }
+
+ return PlatformSharedMemoryRegion(std::move(handle), mode, size, guid);
+}
+
+FDPair PlatformSharedMemoryRegion::GetPlatformHandle() const {
+ return handle_.get();
+}
+
+bool PlatformSharedMemoryRegion::IsValid() const {
+ return handle_.fd.is_valid() &&
+ (mode_ == Mode::kWritable ? handle_.readonly_fd.is_valid() : true);
+}
+
+PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Duplicate() const {
+ if (!IsValid())
+ return {};
+
+ CHECK_NE(mode_, Mode::kWritable)
+ << "Duplicating a writable shared memory region is prohibited";
+
+ ScopedFD duped_fd(HANDLE_EINTR(dup(handle_.fd.get())));
+ if (!duped_fd.is_valid()) {
+ DPLOG(ERROR) << "dup(" << handle_.fd.get() << ") failed";
+ return {};
+ }
+
+ return PlatformSharedMemoryRegion({std::move(duped_fd), ScopedFD()}, mode_,
+ size_, guid_);
+}
+
+bool PlatformSharedMemoryRegion::ConvertToReadOnly() {
+ if (!IsValid())
+ return false;
+
+ CHECK_EQ(mode_, Mode::kWritable)
+ << "Only writable shared memory region can be converted to read-only";
+
+ handle_.fd.reset(handle_.readonly_fd.release());
+ mode_ = Mode::kReadOnly;
+ return true;
+}
+
+bool PlatformSharedMemoryRegion::ConvertToUnsafe() {
+ if (!IsValid())
+ return false;
+
+ CHECK_EQ(mode_, Mode::kWritable)
+ << "Only writable shared memory region can be converted to unsafe";
+
+ handle_.readonly_fd.reset();
+ mode_ = Mode::kUnsafe;
+ return true;
+}
+
+bool PlatformSharedMemoryRegion::MapAt(off_t offset,
+ size_t size,
+ void** memory,
+ size_t* mapped_size) const {
+ if (!IsValid())
+ return false;
+
+ size_t end_byte;
+ if (!CheckAdd(offset, size).AssignIfValid(&end_byte) || end_byte > size_) {
+ return false;
+ }
+
+ bool write_allowed = mode_ != Mode::kReadOnly;
+ *memory = mmap(nullptr, size, PROT_READ | (write_allowed ? PROT_WRITE : 0),
+ MAP_SHARED, handle_.fd.get(), offset);
+
+ bool mmap_succeeded = *memory && *memory != MAP_FAILED;
+ if (!mmap_succeeded) {
+ DPLOG(ERROR) << "mmap " << handle_.fd.get() << " failed";
+ return false;
+ }
+
+ *mapped_size = size;
+ DCHECK_EQ(0U,
+ reinterpret_cast<uintptr_t>(*memory) & (kMapMinimumAlignment - 1));
+ return true;
+}
+
+// static
+PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Create(Mode mode,
+ size_t size) {
+#if defined(OS_NACL)
+ // Untrusted code can't create descriptors or handles.
+ return {};
+#else
+ if (size == 0)
+ return {};
+
+ if (size > static_cast<size_t>(std::numeric_limits<int>::max()))
+ return {};
+
+ CHECK_NE(mode, Mode::kReadOnly) << "Creating a region in read-only mode will "
+ "lead to this region being non-modifiable";
+
+ // This function theoretically can block on the disk, but realistically
+ // the temporary files we create will just go into the buffer cache
+ // and be deleted before they ever make it out to disk.
+ ThreadRestrictions::ScopedAllowIO allow_io;
+
+ // We don't use shm_open() API in order to support the --disable-dev-shm-usage
+ // flag.
+ FilePath directory;
+ if (!GetShmemTempDir(false /* executable */, &directory))
+ return {};
+
+ ScopedFD fd;
+ FilePath path;
+ fd.reset(CreateAndOpenFdForTemporaryFileInDir(directory, &path));
+
+ if (!fd.is_valid()) {
+ PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed";
+ FilePath dir = path.DirName();
+ if (access(dir.value().c_str(), W_OK | X_OK) < 0) {
+ PLOG(ERROR) << "Unable to access(W_OK|X_OK) " << dir.value();
+ if (dir.value() == "/dev/shm") {
+ LOG(FATAL) << "This is frequently caused by incorrect permissions on "
+ << "/dev/shm. Try 'sudo chmod 1777 /dev/shm' to fix.";
+ }
+ }
+ return {};
+ }
+
+ // Deleting the file prevents anyone else from mapping it in (making it
+ // private), and prevents the need for cleanup (once the last fd is
+ // closed, it is truly freed).
+ ScopedPathUnlinker path_unlinker(&path);
+
+ ScopedFD readonly_fd;
+ if (mode == Mode::kWritable) {
+ // Also open as readonly so that we can ConvertToReadOnly().
+ readonly_fd.reset(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)));
+ if (!readonly_fd.is_valid()) {
+ DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed";
+ return {};
+ }
+ }
+
+ // Get current size.
+ struct stat stat = {};
+ if (fstat(fd.get(), &stat) != 0)
+ return {};
+ const size_t current_size = stat.st_size;
+ if (current_size != size) {
+ if (HANDLE_EINTR(ftruncate(fd.get(), size)) != 0)
+ return {};
+ }
+
+ if (readonly_fd.is_valid()) {
+ struct stat readonly_stat = {};
+ if (fstat(readonly_fd.get(), &readonly_stat))
+ NOTREACHED();
+
+ if (stat.st_dev != readonly_stat.st_dev ||
+ stat.st_ino != readonly_stat.st_ino) {
+ LOG(ERROR) << "Writable and read-only inodes don't match; bailing";
+ return {};
+ }
+ }
+
+ return PlatformSharedMemoryRegion({std::move(fd), std::move(readonly_fd)},
+ mode, size, UnguessableToken::Create());
+#endif // !defined(OS_NACL)
+}
+
+bool PlatformSharedMemoryRegion::CheckPlatformHandlePermissionsCorrespondToMode(
+ PlatformHandle handle,
+ Mode mode,
+ size_t size) {
+#if !defined(OS_NACL)
+ if (!CheckFDAccessMode(handle.fd,
+ mode == Mode::kReadOnly ? O_RDONLY : O_RDWR)) {
+ return false;
+ }
+
+ if (mode == Mode::kWritable)
+ return CheckFDAccessMode(handle.readonly_fd, O_RDONLY);
+
+ // The second descriptor must be invalid in kReadOnly and kUnsafe modes.
+ if (handle.readonly_fd != -1) {
+ DLOG(ERROR) << "The second descriptor must be invalid";
+ return false;
+ }
+
+ return true;
+#else
+ // fcntl(_, F_GETFL) is not implemented on NaCl.
+ void* temp_memory = nullptr;
+ temp_memory =
+ mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, handle.fd, 0);
+
+ bool mmap_succeeded = temp_memory && temp_memory != MAP_FAILED;
+ if (mmap_succeeded)
+ munmap(temp_memory, size);
+
+ bool is_read_only = !mmap_succeeded;
+ bool expected_read_only = mode == Mode::kReadOnly;
+
+ if (is_read_only != expected_read_only) {
+ DLOG(ERROR) << "Descriptor has a wrong access mode: it is"
+ << (is_read_only ? " " : " not ") << "read-only but it should"
+ << (expected_read_only ? " " : " not ") << "be";
+ return false;
+ }
+
+ return true;
+#endif // !defined(OS_NACL)
+}
+
+PlatformSharedMemoryRegion::PlatformSharedMemoryRegion(
+ ScopedFDPair handle,
+ Mode mode,
+ size_t size,
+ const UnguessableToken& guid)
+ : handle_(std::move(handle)), mode_(mode), size_(size), guid_(guid) {}
+
+} // namespace subtle
+} // namespace base
diff --git a/base/memory/platform_shared_memory_region_unittest.cc b/base/memory/platform_shared_memory_region_unittest.cc
new file mode 100644
index 0000000000..5a83ee9b46
--- /dev/null
+++ b/base/memory/platform_shared_memory_region_unittest.cc
@@ -0,0 +1,347 @@
+// 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.
+
+#include "base/memory/platform_shared_memory_region.h"
+
+#include "base/memory/shared_memory_mapping.h"
+#include "base/process/process_metrics.h"
+#include "base/sys_info.h"
+#include "base/test/gtest_util.h"
+#include "base/test/test_shared_memory_util.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include <mach/mach_vm.h>
+#endif
+
+namespace base {
+namespace subtle {
+
+const size_t kRegionSize = 1024;
+
+class PlatformSharedMemoryRegionTest : public ::testing::Test {};
+
+// Tests that a default constructed region is invalid and produces invalid
+// mappings.
+TEST_F(PlatformSharedMemoryRegionTest, DefaultConstructedRegionIsInvalid) {
+ PlatformSharedMemoryRegion region;
+ EXPECT_FALSE(region.IsValid());
+ WritableSharedMemoryMapping mapping = MapForTesting(&region);
+ EXPECT_FALSE(mapping.IsValid());
+ PlatformSharedMemoryRegion duplicate = region.Duplicate();
+ EXPECT_FALSE(duplicate.IsValid());
+ EXPECT_FALSE(region.ConvertToReadOnly());
+}
+
+// Tests that creating a region of 0 size returns an invalid region.
+TEST_F(PlatformSharedMemoryRegionTest, CreateRegionOfZeroSizeIsInvalid) {
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(0);
+ EXPECT_FALSE(region.IsValid());
+
+ PlatformSharedMemoryRegion region2 =
+ PlatformSharedMemoryRegion::CreateUnsafe(0);
+ EXPECT_FALSE(region2.IsValid());
+}
+
+// Tests that creating a region of size bigger than the integer max value
+// returns an invalid region.
+TEST_F(PlatformSharedMemoryRegionTest, CreateTooLargeRegionIsInvalid) {
+ size_t too_large_region_size =
+ static_cast<size_t>(std::numeric_limits<int>::max()) + 1;
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(too_large_region_size);
+ EXPECT_FALSE(region.IsValid());
+
+ PlatformSharedMemoryRegion region2 =
+ PlatformSharedMemoryRegion::CreateUnsafe(too_large_region_size);
+ EXPECT_FALSE(region2.IsValid());
+}
+
+// Tests that regions consistently report their size as the size requested at
+// creation time even if their allocation size is larger due to platform
+// constraints.
+TEST_F(PlatformSharedMemoryRegionTest, ReportedSizeIsRequestedSize) {
+ constexpr size_t kTestSizes[] = {1, 2, 3, 64, 4096, 1024 * 1024};
+ for (size_t size : kTestSizes) {
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(size);
+ EXPECT_EQ(region.GetSize(), size);
+
+ region.ConvertToReadOnly();
+ EXPECT_EQ(region.GetSize(), size);
+ }
+}
+
+// Tests that a writable region can be converted to read-only.
+TEST_F(PlatformSharedMemoryRegionTest, ConvertWritableToReadOnly) {
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ EXPECT_EQ(region.GetMode(), PlatformSharedMemoryRegion::Mode::kWritable);
+ ASSERT_TRUE(region.ConvertToReadOnly());
+ EXPECT_EQ(region.GetMode(), PlatformSharedMemoryRegion::Mode::kReadOnly);
+}
+
+// Tests that a writable region can be converted to unsafe.
+TEST_F(PlatformSharedMemoryRegionTest, ConvertWritableToUnsafe) {
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ EXPECT_EQ(region.GetMode(), PlatformSharedMemoryRegion::Mode::kWritable);
+ ASSERT_TRUE(region.ConvertToUnsafe());
+ EXPECT_EQ(region.GetMode(), PlatformSharedMemoryRegion::Mode::kUnsafe);
+}
+
+// Tests that the platform-specific handle converted to read-only cannot be used
+// to perform a writable mapping with low-level system APIs like mmap().
+TEST_F(PlatformSharedMemoryRegionTest, ReadOnlyHandleIsNotWritable) {
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ EXPECT_TRUE(region.ConvertToReadOnly());
+ EXPECT_EQ(region.GetMode(), PlatformSharedMemoryRegion::Mode::kReadOnly);
+ EXPECT_TRUE(
+ CheckReadOnlyPlatformSharedMemoryRegionForTesting(std::move(region)));
+}
+
+// Tests that the PassPlatformHandle() call invalidates the region.
+TEST_F(PlatformSharedMemoryRegionTest, InvalidAfterPass) {
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ ignore_result(region.PassPlatformHandle());
+ EXPECT_FALSE(region.IsValid());
+}
+
+// Tests that the region is invalid after move.
+TEST_F(PlatformSharedMemoryRegionTest, InvalidAfterMove) {
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ PlatformSharedMemoryRegion moved_region = std::move(region);
+ EXPECT_FALSE(region.IsValid());
+ EXPECT_TRUE(moved_region.IsValid());
+}
+
+// Tests that calling Take() with the size parameter equal to zero returns an
+// invalid region.
+TEST_F(PlatformSharedMemoryRegionTest, TakeRegionOfZeroSizeIsInvalid) {
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ PlatformSharedMemoryRegion region2 = PlatformSharedMemoryRegion::Take(
+ region.PassPlatformHandle(), region.GetMode(), 0, region.GetGUID());
+ EXPECT_FALSE(region2.IsValid());
+}
+
+// Tests that calling Take() with the size parameter bigger than the integer max
+// value returns an invalid region.
+TEST_F(PlatformSharedMemoryRegionTest, TakeTooLargeRegionIsInvalid) {
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ PlatformSharedMemoryRegion region2 = PlatformSharedMemoryRegion::Take(
+ region.PassPlatformHandle(), region.GetMode(),
+ static_cast<size_t>(std::numeric_limits<int>::max()) + 1,
+ region.GetGUID());
+ EXPECT_FALSE(region2.IsValid());
+}
+
+// Tests that mapping bytes out of the region limits fails.
+TEST_F(PlatformSharedMemoryRegionTest, MapAtOutOfTheRegionLimitsTest) {
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ WritableSharedMemoryMapping mapping =
+ MapAtForTesting(&region, 0, region.GetSize() + 1);
+ EXPECT_FALSE(mapping.IsValid());
+}
+
+// Tests that mapping with a size and offset causing overflow fails.
+TEST_F(PlatformSharedMemoryRegionTest, MapAtWithOverflowTest) {
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(
+ SysInfo::VMAllocationGranularity() * 2);
+ ASSERT_TRUE(region.IsValid());
+ size_t size = std::numeric_limits<size_t>::max();
+ size_t offset = SysInfo::VMAllocationGranularity();
+ // |size| + |offset| should be below the region size due to overflow but
+ // mapping a region with these parameters should be invalid.
+ EXPECT_LT(size + offset, region.GetSize());
+ WritableSharedMemoryMapping mapping = MapAtForTesting(&region, offset, size);
+ EXPECT_FALSE(mapping.IsValid());
+}
+
+#if defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_MACOSX)
+// Tests that the second handle is closed after a conversion to read-only on
+// POSIX.
+TEST_F(PlatformSharedMemoryRegionTest,
+ ConvertToReadOnlyInvalidatesSecondHandle) {
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ ASSERT_TRUE(region.ConvertToReadOnly());
+ FDPair fds = region.GetPlatformHandle();
+ EXPECT_LT(fds.readonly_fd, 0);
+}
+
+// Tests that the second handle is closed after a conversion to unsafe on
+// POSIX.
+TEST_F(PlatformSharedMemoryRegionTest, ConvertToUnsafeInvalidatesSecondHandle) {
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ ASSERT_TRUE(region.ConvertToUnsafe());
+ FDPair fds = region.GetPlatformHandle();
+ EXPECT_LT(fds.readonly_fd, 0);
+}
+#endif
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+// Tests that protection bits are set correctly for read-only region on MacOS.
+TEST_F(PlatformSharedMemoryRegionTest, MapCurrentAndMaxProtectionSetCorrectly) {
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ ASSERT_TRUE(region.ConvertToReadOnly());
+ WritableSharedMemoryMapping ro_mapping = MapForTesting(&region);
+ ASSERT_TRUE(ro_mapping.IsValid());
+
+ vm_region_basic_info_64 basic_info;
+ mach_vm_size_t dummy_size = 0;
+ void* temp_addr = ro_mapping.memory();
+ MachVMRegionResult result = GetBasicInfo(
+ mach_task_self(), &dummy_size,
+ reinterpret_cast<mach_vm_address_t*>(&temp_addr), &basic_info);
+ EXPECT_EQ(result, MachVMRegionResult::Success);
+ EXPECT_EQ(basic_info.protection & VM_PROT_ALL, VM_PROT_READ);
+ EXPECT_EQ(basic_info.max_protection & VM_PROT_ALL, VM_PROT_READ);
+}
+#endif
+
+// Tests that platform handle permissions are checked correctly.
+TEST_F(PlatformSharedMemoryRegionTest,
+ CheckPlatformHandlePermissionsCorrespondToMode) {
+ using Mode = PlatformSharedMemoryRegion::Mode;
+ auto check = [](const PlatformSharedMemoryRegion& region,
+ PlatformSharedMemoryRegion::Mode mode) {
+ return PlatformSharedMemoryRegion::
+ CheckPlatformHandlePermissionsCorrespondToMode(
+ region.GetPlatformHandle(), mode, region.GetSize());
+ };
+
+ // Check kWritable region.
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ EXPECT_TRUE(check(region, Mode::kWritable));
+ EXPECT_FALSE(check(region, Mode::kReadOnly));
+
+ // Check kReadOnly region.
+ ASSERT_TRUE(region.ConvertToReadOnly());
+ EXPECT_TRUE(check(region, Mode::kReadOnly));
+ EXPECT_FALSE(check(region, Mode::kWritable));
+ EXPECT_FALSE(check(region, Mode::kUnsafe));
+
+ // Check kUnsafe region.
+ PlatformSharedMemoryRegion region2 =
+ PlatformSharedMemoryRegion::CreateUnsafe(kRegionSize);
+ ASSERT_TRUE(region2.IsValid());
+ EXPECT_TRUE(check(region2, Mode::kUnsafe));
+ EXPECT_FALSE(check(region2, Mode::kReadOnly));
+}
+
+// Tests that it's impossible to create read-only platform shared memory region.
+TEST_F(PlatformSharedMemoryRegionTest, CreateReadOnlyRegionDeathTest) {
+#ifdef OFFICIAL_BUILD
+ // The official build does not print the reason a CHECK failed.
+ const char kErrorRegex[] = "";
+#else
+ const char kErrorRegex[] =
+ "Creating a region in read-only mode will lead to this region being "
+ "non-modifiable";
+#endif
+ EXPECT_DEATH_IF_SUPPORTED(
+ PlatformSharedMemoryRegion::Create(
+ PlatformSharedMemoryRegion::Mode::kReadOnly, kRegionSize),
+ kErrorRegex);
+}
+
+// Tests that it's prohibited to duplicate a writable region.
+TEST_F(PlatformSharedMemoryRegionTest, DuplicateWritableRegionDeathTest) {
+#ifdef OFFICIAL_BUILD
+ const char kErrorRegex[] = "";
+#else
+ const char kErrorRegex[] =
+ "Duplicating a writable shared memory region is prohibited";
+#endif
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ EXPECT_DEATH_IF_SUPPORTED(region.Duplicate(), kErrorRegex);
+}
+
+// Tests that it's prohibited to convert an unsafe region to read-only.
+TEST_F(PlatformSharedMemoryRegionTest, UnsafeRegionConvertToReadOnlyDeathTest) {
+#ifdef OFFICIAL_BUILD
+ const char kErrorRegex[] = "";
+#else
+ const char kErrorRegex[] =
+ "Only writable shared memory region can be converted to read-only";
+#endif
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateUnsafe(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ EXPECT_DEATH_IF_SUPPORTED(region.ConvertToReadOnly(), kErrorRegex);
+}
+
+// Tests that it's prohibited to convert a read-only region to read-only.
+TEST_F(PlatformSharedMemoryRegionTest,
+ ReadOnlyRegionConvertToReadOnlyDeathTest) {
+#ifdef OFFICIAL_BUILD
+ const char kErrorRegex[] = "";
+#else
+ const char kErrorRegex[] =
+ "Only writable shared memory region can be converted to read-only";
+#endif
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ EXPECT_TRUE(region.ConvertToReadOnly());
+ EXPECT_DEATH_IF_SUPPORTED(region.ConvertToReadOnly(), kErrorRegex);
+}
+
+// Tests that it's prohibited to convert a read-only region to unsafe.
+TEST_F(PlatformSharedMemoryRegionTest, ReadOnlyRegionConvertToUnsafeDeathTest) {
+#ifdef OFFICIAL_BUILD
+ const char kErrorRegex[] = "";
+#else
+ const char kErrorRegex[] =
+ "Only writable shared memory region can be converted to unsafe";
+#endif
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateWritable(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ ASSERT_TRUE(region.ConvertToReadOnly());
+ EXPECT_DEATH_IF_SUPPORTED(region.ConvertToUnsafe(), kErrorRegex);
+}
+
+// Tests that it's prohibited to convert an unsafe region to unsafe.
+TEST_F(PlatformSharedMemoryRegionTest, UnsafeRegionConvertToUnsafeDeathTest) {
+#ifdef OFFICIAL_BUILD
+ const char kErrorRegex[] = "";
+#else
+ const char kErrorRegex[] =
+ "Only writable shared memory region can be converted to unsafe";
+#endif
+ PlatformSharedMemoryRegion region =
+ PlatformSharedMemoryRegion::CreateUnsafe(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ EXPECT_DEATH_IF_SUPPORTED(region.ConvertToUnsafe(), kErrorRegex);
+}
+
+} // namespace subtle
+} // namespace base
diff --git a/base/memory/protected_memory.cc b/base/memory/protected_memory.cc
new file mode 100644
index 0000000000..157a677e04
--- /dev/null
+++ b/base/memory/protected_memory.cc
@@ -0,0 +1,17 @@
+// 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/protected_memory.h"
+#include "base/synchronization/lock.h"
+
+namespace base {
+
+#if !defined(COMPONENT_BUILD)
+PROTECTED_MEMORY_SECTION int AutoWritableMemory::writers = 0;
+#endif // !defined(COMPONENT_BUILD)
+
+base::LazyInstance<Lock>::Leaky AutoWritableMemory::writers_lock =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace base
diff --git a/base/memory/protected_memory.h b/base/memory/protected_memory.h
new file mode 100644
index 0000000000..3cb2ec3c83
--- /dev/null
+++ b/base/memory/protected_memory.h
@@ -0,0 +1,276 @@
+// 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.
+
+// Protected memory is memory holding security-sensitive data intended to be
+// left read-only for the majority of its lifetime to avoid being overwritten
+// by attackers. ProtectedMemory is a simple wrapper around platform-specific
+// APIs to set memory read-write and read-only when required. Protected memory
+// should be set read-write for the minimum amount of time required.
+
+// Normally mutable variables are held in read-write memory and constant data
+// is held in read-only memory to ensure it is not accidentally overwritten.
+// In some cases we want to hold mutable variables in read-only memory, except
+// when they are being written to, to ensure that they are not tampered with.
+//
+// ProtectedMemory is a container class intended to hold a single variable in
+// read-only memory, except when explicitly set read-write. The variable can be
+// set read-write by creating a scoped AutoWritableMemory object by calling
+// AutoWritableMemory::Create(), the memory stays writable until the returned
+// object goes out of scope and is destructed. The wrapped variable can be
+// accessed using operator* and operator->.
+//
+// Instances of ProtectedMemory must be declared in the PROTECTED_MEMORY_SECTION
+// and as global variables. Because protected memory variables are globals, the
+// the same rules apply disallowing non-trivial constructors and destructors.
+// Global definitions are required to avoid the linker placing statics in
+// inlinable functions into a comdat section and setting the protected memory
+// section read-write when they are merged.
+//
+// EXAMPLE:
+//
+// struct Items { void* item1; };
+// static PROTECTED_MEMORY_SECTION base::ProtectedMemory<Items> items;
+// void InitializeItems() {
+// // Explicitly set items read-write before writing to it.
+// auto writer = base::AutoWritableMemory::Create(items);
+// items->item1 = /* ... */;
+// assert(items->item1 != nullptr);
+// // items is set back to read-only on the destruction of writer
+// }
+//
+// using FnPtr = void (*)(void);
+// PROTECTED_MEMORY_SECTION base::ProtectedMemory<FnPtr> fnPtr;
+// FnPtr ResolveFnPtr(void) {
+// // The Initializer nested class is a helper class for creating a static
+// // initializer for a ProtectedMemory variable. It implicitly sets the
+// // variable read-write during initialization.
+// static base::ProtectedMemory<FnPtr>::Initializer I(&fnPtr,
+// reinterpret_cast<FnPtr>(dlsym(/* ... */)));
+// return *fnPtr;
+// }
+
+#ifndef BASE_MEMORY_PROTECTED_MEMORY_H_
+#define BASE_MEMORY_PROTECTED_MEMORY_H_
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/protected_memory_buildflags.h"
+#include "base/synchronization/lock.h"
+#include "build/build_config.h"
+
+#define PROTECTED_MEMORY_ENABLED 1
+
+// Linking with lld is required to workaround crbug.com/792777.
+// TODO(vtsyrklevich): Remove once support for gold on Android/CrOs is dropped
+#if defined(OS_LINUX) && BUILDFLAG(USE_LLD)
+// Define the section read-only
+__asm__(".section protected_memory, \"a\"\n\t");
+#define PROTECTED_MEMORY_SECTION __attribute__((section("protected_memory")))
+
+// Explicitly mark these variables hidden so the symbols are local to the
+// currently built component. Otherwise they are created with global (external)
+// linkage and component builds would break because a single pair of these
+// symbols would override the rest.
+__attribute__((visibility("hidden"))) extern char __start_protected_memory;
+__attribute__((visibility("hidden"))) extern char __stop_protected_memory;
+
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+// The segment the section is in is defined read-only with a linker flag in
+// build/config/mac/BUILD.gn
+#define PROTECTED_MEMORY_SECTION \
+ __attribute__((section("PROTECTED_MEMORY, protected_memory")))
+extern char __start_protected_memory __asm(
+ "section$start$PROTECTED_MEMORY$protected_memory");
+extern char __stop_protected_memory __asm(
+ "section$end$PROTECTED_MEMORY$protected_memory");
+
+#elif defined(OS_WIN)
+// Define a read-write prot section. The $a, $mem, and $z 'sub-sections' are
+// merged alphabetically so $a and $z are used to define the start and end of
+// the protected memory section, and $mem holds protected variables.
+// (Note: Sections in Portable Executables are equivalent to segments in other
+// executable formats, so this section is mapped into its own pages.)
+#pragma section("prot$a", read, write)
+#pragma section("prot$mem", read, write)
+#pragma section("prot$z", read, write)
+
+// We want the protected memory section to be read-only, not read-write so we
+// instruct the linker to set the section read-only at link time. We do this
+// at link time instead of compile time, because defining the prot section
+// read-only would cause mis-compiles due to optimizations assuming that the
+// section contents are constant.
+#pragma comment(linker, "/SECTION:prot,R")
+
+__declspec(allocate("prot$a")) __declspec(selectany)
+char __start_protected_memory;
+__declspec(allocate("prot$z")) __declspec(selectany)
+char __stop_protected_memory;
+
+#define PROTECTED_MEMORY_SECTION __declspec(allocate("prot$mem"))
+
+#else
+#undef PROTECTED_MEMORY_ENABLED
+#define PROTECTED_MEMORY_ENABLED 0
+#define PROTECTED_MEMORY_SECTION
+#endif
+
+namespace base {
+
+template <typename T>
+class ProtectedMemory {
+ public:
+ ProtectedMemory() = default;
+
+ // Expose direct access to the encapsulated variable
+ T& operator*() { return data; }
+ const T& operator*() const { return data; }
+ T* operator->() { return &data; }
+ const T* operator->() const { return &data; }
+
+ // Helper class for creating simple ProtectedMemory static initializers.
+ class Initializer {
+ public:
+ // Defined out-of-line below to break circular definition dependency between
+ // ProtectedMemory and AutoWritableMemory.
+ Initializer(ProtectedMemory<T>* PM, const T& Init);
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(Initializer);
+ };
+
+ private:
+ T data;
+
+ DISALLOW_COPY_AND_ASSIGN(ProtectedMemory);
+};
+
+// DCHECK that the byte at |ptr| is read-only.
+BASE_EXPORT void AssertMemoryIsReadOnly(const void* ptr);
+
+// Abstract out platform-specific methods to get the beginning and end of the
+// PROTECTED_MEMORY_SECTION. ProtectedMemoryEnd returns a pointer to the byte
+// past the end of the PROTECTED_MEMORY_SECTION.
+#if PROTECTED_MEMORY_ENABLED
+constexpr void* ProtectedMemoryStart = &__start_protected_memory;
+constexpr void* ProtectedMemoryEnd = &__stop_protected_memory;
+#endif
+
+#if defined(COMPONENT_BUILD)
+namespace internal {
+
+// For component builds we want to define a separate global writers variable
+// (explained below) in every DSO that includes this header. To do that we use
+// this template to define a global without duplicate symbol errors.
+template <typename T>
+struct DsoSpecific {
+ static T value;
+};
+template <typename T>
+T DsoSpecific<T>::value = 0;
+
+} // namespace internal
+#endif // defined(COMPONENT_BUILD)
+
+// A class that sets a given ProtectedMemory variable writable while the
+// AutoWritableMemory is in scope. This class implements the logic for setting
+// the protected memory region read-only/read-write in a thread-safe manner.
+class AutoWritableMemory {
+ private:
+ // 'writers' is a global holding the number of ProtectedMemory instances set
+ // writable, used to avoid races setting protected memory readable/writable.
+ // When this reaches zero the protected memory region is set read only.
+ // Access is controlled by writers_lock.
+#if defined(COMPONENT_BUILD)
+ // For component builds writers is a reference to an int defined separately in
+ // every DSO.
+ static constexpr int& writers = internal::DsoSpecific<int>::value;
+#else
+ // Otherwise, we declare writers in the protected memory section to avoid the
+ // scenario where an attacker could overwrite it with a large value and invoke
+ // code that constructs and destructs an AutoWritableMemory. After such a call
+ // protected memory would still be set writable because writers > 0.
+ static int writers;
+#endif // defined(COMPONENT_BUILD)
+
+ // Synchronizes access to the writers variable and the simultaneous actions
+ // that need to happen alongside writers changes, e.g. setting the protected
+ // memory region readable when writers is decremented to 0.
+ static BASE_EXPORT base::LazyInstance<Lock>::Leaky writers_lock;
+
+ // Abstract out platform-specific memory APIs. |end| points to the byte past
+ // the end of the region of memory having its memory protections changed.
+ BASE_EXPORT bool SetMemoryReadWrite(void* start, void* end);
+ BASE_EXPORT bool SetMemoryReadOnly(void* start, void* end);
+
+ // If this is the first writer (e.g. writers == 0) set the writers variable
+ // read-write. Next, increment writers and set the requested memory writable.
+ AutoWritableMemory(void* ptr, void* ptr_end) {
+#if PROTECTED_MEMORY_ENABLED
+ DCHECK(ptr >= ProtectedMemoryStart && ptr_end <= ProtectedMemoryEnd);
+
+ {
+ base::AutoLock auto_lock(writers_lock.Get());
+ if (writers == 0) {
+ AssertMemoryIsReadOnly(ptr);
+#if !defined(COMPONENT_BUILD)
+ AssertMemoryIsReadOnly(&writers);
+ CHECK(SetMemoryReadWrite(&writers, &writers + 1));
+#endif // !defined(COMPONENT_BUILD)
+ }
+
+ writers++;
+ }
+
+ CHECK(SetMemoryReadWrite(ptr, ptr_end));
+#endif // PROTECTED_MEMORY_ENABLED
+ }
+
+ public:
+ // Wrap the private constructor to create an easy-to-use interface to
+ // construct AutoWritableMemory objects.
+ template <typename T>
+ static AutoWritableMemory Create(ProtectedMemory<T>& PM) {
+ T* ptr = &*PM;
+ return AutoWritableMemory(ptr, ptr + 1);
+ }
+
+ // Move constructor just increments writers
+ AutoWritableMemory(AutoWritableMemory&& original) {
+#if PROTECTED_MEMORY_ENABLED
+ base::AutoLock auto_lock(writers_lock.Get());
+ CHECK_GT(writers, 0);
+ writers++;
+#endif // PROTECTED_MEMORY_ENABLED
+ }
+
+ // On destruction decrement writers, and if no other writers exist, set the
+ // entire protected memory region read-only.
+ ~AutoWritableMemory() {
+#if PROTECTED_MEMORY_ENABLED
+ base::AutoLock auto_lock(writers_lock.Get());
+ CHECK_GT(writers, 0);
+ writers--;
+
+ if (writers == 0) {
+ CHECK(SetMemoryReadOnly(ProtectedMemoryStart, ProtectedMemoryEnd));
+#if !defined(COMPONENT_BUILD)
+ AssertMemoryIsReadOnly(&writers);
+#endif // !defined(COMPONENT_BUILD)
+ }
+#endif // PROTECTED_MEMORY_ENABLED
+ }
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(AutoWritableMemory);
+};
+
+template <typename T>
+ProtectedMemory<T>::Initializer::Initializer(ProtectedMemory<T>* PM,
+ const T& Init) {
+ AutoWritableMemory writer = AutoWritableMemory::Create(*PM);
+ **PM = Init;
+}
+
+} // namespace base
+
+#endif // BASE_MEMORY_PROTECTED_MEMORY_H_
diff --git a/base/memory/protected_memory_buildflags.h b/base/memory/protected_memory_buildflags.h
new file mode 100644
index 0000000000..852ef4eb99
--- /dev/null
+++ b/base/memory/protected_memory_buildflags.h
@@ -0,0 +1,7 @@
+// Generated by build/write_buildflag_header.py
+// From "base_debugging_flags"
+#ifndef BASE_PROTECTED_MEMORY_BUILDFLAGS_H_
+#define BASE_PROTECTED_MEMORY_BUILDFLAGS_H_
+#include "build/buildflag.h"
+#define BUILDFLAG_INTERNAL_USE_LLD() (0)
+#endif // BASE_PROTECTED_MEMORY_BUILDFLAGS_H_
diff --git a/base/memory/protected_memory_cfi.h b/base/memory/protected_memory_cfi.h
new file mode 100644
index 0000000000..a90023bc81
--- /dev/null
+++ b/base/memory/protected_memory_cfi.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.
+
+// Helper routines to call function pointers stored in protected memory with
+// Control Flow Integrity indirect call checking disabled. Some indirect calls,
+// e.g. dynamically resolved symbols in another DSO, can not be accounted for by
+// CFI-icall. These routines allow those symbols to be called without CFI-icall
+// checking safely by ensuring that they are placed in protected memory.
+
+#ifndef BASE_MEMORY_PROTECTED_MEMORY_CFI_H_
+#define BASE_MEMORY_PROTECTED_MEMORY_CFI_H_
+
+#include <utility>
+
+#include "base/cfi_buildflags.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/protected_memory.h"
+#include "build/build_config.h"
+
+#if BUILDFLAG(CFI_ICALL_CHECK) && !PROTECTED_MEMORY_ENABLED
+#error "CFI-icall enabled for platform without protected memory support"
+#endif // BUILDFLAG(CFI_ICALL_CHECK) && !PROTECTED_MEMORY_ENABLED
+
+namespace base {
+namespace internal {
+
+// This class is used to exempt calls to function pointers stored in
+// ProtectedMemory from cfi-icall checking. It's not secure to use directly, it
+// should only be used by the UnsanitizedCfiCall() functions below. Given an
+// UnsanitizedCfiCall object, you can use operator() to call the encapsulated
+// function pointer without cfi-icall checking.
+template <typename FunctionType>
+class UnsanitizedCfiCall {
+ public:
+ explicit UnsanitizedCfiCall(FunctionType function) : function_(function) {}
+ UnsanitizedCfiCall(UnsanitizedCfiCall&&) = default;
+
+ template <typename... Args>
+ NO_SANITIZE("cfi-icall")
+ auto operator()(Args&&... args) {
+ return function_(std::forward<Args>(args)...);
+ }
+
+ private:
+ FunctionType function_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(UnsanitizedCfiCall);
+};
+
+} // namespace internal
+
+// These functions can be used to call function pointers in ProtectedMemory
+// without cfi-icall checking. They are intended to be used to create an
+// UnsanitizedCfiCall object and immediately call it. UnsanitizedCfiCall objects
+// should not initialized directly or stored because they hold a function
+// pointer that will be called without CFI-icall checking in mutable memory. The
+// functions can be used as shown below:
+
+// ProtectedMemory<void (*)(int)> p;
+// UnsanitizedCfiCall(p)(5); /* In place of (*p)(5); */
+
+template <typename T>
+auto UnsanitizedCfiCall(const ProtectedMemory<T>& PM) {
+#if PROTECTED_MEMORY_ENABLED
+ DCHECK(&PM >= ProtectedMemoryStart && &PM < ProtectedMemoryEnd);
+#endif // PROTECTED_MEMORY_ENABLED
+ return internal::UnsanitizedCfiCall<T>(*PM);
+}
+
+// struct S { void (*fp)(int); } s;
+// ProtectedMemory<S> p;
+// UnsanitizedCfiCall(p, &S::fp)(5); /* In place of p->fp(5); */
+
+template <typename T, typename Member>
+auto UnsanitizedCfiCall(const ProtectedMemory<T>& PM, Member member) {
+#if PROTECTED_MEMORY_ENABLED
+ DCHECK(&PM >= ProtectedMemoryStart && &PM < ProtectedMemoryEnd);
+#endif // PROTECTED_MEMORY_ENABLED
+ return internal::UnsanitizedCfiCall<decltype(*PM.*member)>(*PM.*member);
+}
+
+} // namespace base
+
+#endif // BASE_MEMORY_PROTECTED_MEMORY_CFI_H_
diff --git a/base/memory/protected_memory_posix.cc b/base/memory/protected_memory_posix.cc
new file mode 100644
index 0000000000..d003d79bdb
--- /dev/null
+++ b/base/memory/protected_memory_posix.cc
@@ -0,0 +1,79 @@
+// 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/protected_memory.h"
+
+#include <stdint.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#if defined(OS_LINUX)
+#include <sys/resource.h>
+#endif // defined(OS_LINUX)
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include <mach/mach.h>
+#include <mach/mach_vm.h>
+#endif // defined(OS_MACOSX) && !defined(OS_IOS)
+
+#include "base/posix/eintr_wrapper.h"
+#include "base/process/process_metrics.h"
+#include "base/synchronization/lock.h"
+#include "build/build_config.h"
+
+namespace base {
+
+namespace {
+
+bool SetMemory(void* start, void* end, int prot) {
+ DCHECK(end > start);
+ const uintptr_t page_mask = ~(base::GetPageSize() - 1);
+ const uintptr_t page_start = reinterpret_cast<uintptr_t>(start) & page_mask;
+ return mprotect(reinterpret_cast<void*>(page_start),
+ reinterpret_cast<uintptr_t>(end) - page_start, prot) == 0;
+}
+
+} // namespace
+
+bool AutoWritableMemory::SetMemoryReadWrite(void* start, void* end) {
+ return SetMemory(start, end, PROT_READ | PROT_WRITE);
+}
+
+bool AutoWritableMemory::SetMemoryReadOnly(void* start, void* end) {
+ return SetMemory(start, end, PROT_READ);
+}
+
+#if defined(OS_LINUX)
+void AssertMemoryIsReadOnly(const void* ptr) {
+#if DCHECK_IS_ON()
+ const uintptr_t page_mask = ~(base::GetPageSize() - 1);
+ const uintptr_t page_start = reinterpret_cast<uintptr_t>(ptr) & page_mask;
+
+ // Note: We've casted away const here, which should not be meaningful since
+ // if the memory is written to we will abort immediately.
+ int result =
+ getrlimit(RLIMIT_NPROC, reinterpret_cast<struct rlimit*>(page_start));
+ DCHECK_EQ(result, -1);
+ DCHECK_EQ(errno, EFAULT);
+#endif // DCHECK_IS_ON()
+}
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+void AssertMemoryIsReadOnly(const void* ptr) {
+#if DCHECK_IS_ON()
+ mach_port_t object_name;
+ vm_region_basic_info_64 region_info;
+ mach_vm_size_t size = 1;
+ mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
+
+ kern_return_t kr = mach_vm_region(
+ mach_task_self(), reinterpret_cast<mach_vm_address_t*>(&ptr), &size,
+ VM_REGION_BASIC_INFO_64, reinterpret_cast<vm_region_info_t>(&region_info),
+ &count, &object_name);
+ DCHECK_EQ(kr, KERN_SUCCESS);
+ DCHECK_EQ(region_info.protection, VM_PROT_READ);
+#endif // DCHECK_IS_ON()
+}
+#endif // defined(OS_LINUX) || (defined(OS_MACOSX) && !defined(OS_IOS))
+
+} // namespace base
diff --git a/base/memory/protected_memory_unittest.cc b/base/memory/protected_memory_unittest.cc
new file mode 100644
index 0000000000..b7daed34fe
--- /dev/null
+++ b/base/memory/protected_memory_unittest.cc
@@ -0,0 +1,126 @@
+// 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/protected_memory.h"
+#include "base/cfi_buildflags.h"
+#include "base/memory/protected_memory_cfi.h"
+#include "base/synchronization/lock.h"
+#include "base/test/gtest_util.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+struct Data {
+ Data() = default;
+ Data(int foo_) : foo(foo_) {}
+ int foo;
+};
+
+} // namespace
+
+class ProtectedMemoryTest : public ::testing::Test {
+ protected:
+ // Run tests one at a time. Some of the negative tests can not be made thread
+ // safe.
+ void SetUp() final { lock.Acquire(); }
+ void TearDown() final { lock.Release(); }
+
+ Lock lock;
+};
+
+PROTECTED_MEMORY_SECTION ProtectedMemory<int> init;
+
+TEST_F(ProtectedMemoryTest, Initializer) {
+ static ProtectedMemory<int>::Initializer I(&init, 4);
+ EXPECT_EQ(*init, 4);
+}
+
+PROTECTED_MEMORY_SECTION ProtectedMemory<Data> data;
+
+TEST_F(ProtectedMemoryTest, Basic) {
+ AutoWritableMemory writer = AutoWritableMemory::Create(data);
+ data->foo = 5;
+ EXPECT_EQ(data->foo, 5);
+}
+
+#if defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID)
+
+#if PROTECTED_MEMORY_ENABLED
+TEST_F(ProtectedMemoryTest, ReadOnlyOnStart) {
+ EXPECT_DEATH({ data->foo = 6; AutoWritableMemory::Create(data); }, "");
+}
+
+TEST_F(ProtectedMemoryTest, ReadOnlyAfterSetWritable) {
+ { AutoWritableMemory writer = AutoWritableMemory::Create(data); }
+ EXPECT_DEATH({ data->foo = 7; }, "");
+}
+
+TEST_F(ProtectedMemoryTest, AssertMemoryIsReadOnly) {
+ AssertMemoryIsReadOnly(&data->foo);
+ { AutoWritableMemory::Create(data); }
+ AssertMemoryIsReadOnly(&data->foo);
+
+ ProtectedMemory<Data> writable_data;
+ EXPECT_DCHECK_DEATH({ AssertMemoryIsReadOnly(&writable_data->foo); });
+}
+
+TEST_F(ProtectedMemoryTest, FailsIfDefinedOutsideOfProtectMemoryRegion) {
+ ProtectedMemory<Data> data;
+ EXPECT_DCHECK_DEATH({ AutoWritableMemory::Create(data); });
+}
+
+TEST_F(ProtectedMemoryTest, UnsanitizedCfiCallOutsideOfProtectedMemoryRegion) {
+ ProtectedMemory<void (*)(void)> data;
+ EXPECT_DCHECK_DEATH({ UnsanitizedCfiCall(data)(); });
+}
+#endif // PROTECTED_MEMORY_ENABLED
+
+namespace {
+
+struct BadIcall {
+ BadIcall() = default;
+ BadIcall(int (*fp_)(int)) : fp(fp_) {}
+ int (*fp)(int);
+};
+
+unsigned int bad_icall(int i) {
+ return 4 + i;
+}
+
+} // namespace
+
+PROTECTED_MEMORY_SECTION ProtectedMemory<BadIcall> icall_pm1;
+
+TEST_F(ProtectedMemoryTest, BadMemberCall) {
+ static ProtectedMemory<BadIcall>::Initializer I(
+ &icall_pm1, BadIcall(reinterpret_cast<int (*)(int)>(&bad_icall)));
+
+ EXPECT_EQ(UnsanitizedCfiCall(icall_pm1, &BadIcall::fp)(1), 5);
+#if !BUILDFLAG(CFI_ICALL_CHECK)
+ EXPECT_EQ(icall_pm1->fp(1), 5);
+#elif BUILDFLAG(CFI_ENFORCEMENT_TRAP) || BUILDFLAG(CFI_ENFORCEMENT_DIAGNOSTIC)
+ EXPECT_DEATH({ icall_pm1->fp(1); }, "");
+#endif
+}
+
+PROTECTED_MEMORY_SECTION ProtectedMemory<int (*)(int)> icall_pm2;
+
+TEST_F(ProtectedMemoryTest, BadFnPtrCall) {
+ static ProtectedMemory<int (*)(int)>::Initializer I(
+ &icall_pm2, reinterpret_cast<int (*)(int)>(&bad_icall));
+
+ EXPECT_EQ(UnsanitizedCfiCall(icall_pm2)(1), 5);
+#if !BUILDFLAG(CFI_ICALL_CHECK)
+ EXPECT_EQ((*icall_pm2)(1), 5);
+#elif BUILDFLAG(CFI_ENFORCEMENT_TRAP) || BUILDFLAG(CFI_ENFORCEMENT_DIAGNOSTIC)
+ EXPECT_DEATH({ (*icall_pm2)(1); }, "");
+#endif
+}
+
+#endif // defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID)
+
+} // namespace base
diff --git a/base/memory/ptr_util.h b/base/memory/ptr_util.h
index 8747ac9463..42f4f49eeb 100644
--- a/base/memory/ptr_util.h
+++ b/base/memory/ptr_util.h
@@ -18,57 +18,6 @@ std::unique_ptr<T> WrapUnique(T* ptr) {
return std::unique_ptr<T>(ptr);
}
-namespace internal {
-
-template <typename T>
-struct MakeUniqueResult {
- using Scalar = std::unique_ptr<T>;
-};
-
-template <typename T>
-struct MakeUniqueResult<T[]> {
- using Array = std::unique_ptr<T[]>;
-};
-
-template <typename T, size_t N>
-struct MakeUniqueResult<T[N]> {
- using Invalid = void;
-};
-
-} // namespace internal
-
-// Helper to construct an object wrapped in a std::unique_ptr. This is an
-// implementation of C++14's std::make_unique that can be used in Chrome.
-//
-// MakeUnique<T>(args) should be preferred over WrapUnique(new T(args)): bare
-// calls to `new` should be treated with scrutiny.
-//
-// Usage:
-// // ptr is a std::unique_ptr<std::string>
-// auto ptr = MakeUnique<std::string>("hello world!");
-//
-// // arr is a std::unique_ptr<int[]>
-// auto arr = MakeUnique<int[]>(5);
-
-// Overload for non-array types. Arguments are forwarded to T's constructor.
-template <typename T, typename... Args>
-typename internal::MakeUniqueResult<T>::Scalar MakeUnique(Args&&... args) {
- return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
-}
-
-// Overload for array types of unknown bound, e.g. T[]. The array is allocated
-// with `new T[n]()` and value-initialized: note that this is distinct from
-// `new T[n]`, which default-initializes.
-template <typename T>
-typename internal::MakeUniqueResult<T>::Array MakeUnique(size_t size) {
- return std::unique_ptr<T>(new typename std::remove_extent<T>::type[size]());
-}
-
-// Overload to reject array types of known bound, e.g. T[n].
-template <typename T, typename... Args>
-typename internal::MakeUniqueResult<T>::Invalid MakeUnique(Args&&... args) =
- delete;
-
} // namespace base
#endif // BASE_MEMORY_PTR_UTIL_H_
diff --git a/base/memory/raw_scoped_refptr_mismatch_checker.h b/base/memory/raw_scoped_refptr_mismatch_checker.h
index 5dbc183acd..ab8b2abcbb 100644
--- a/base/memory/raw_scoped_refptr_mismatch_checker.h
+++ b/base/memory/raw_scoped_refptr_mismatch_checker.h
@@ -5,10 +5,9 @@
#ifndef BASE_MEMORY_RAW_SCOPED_REFPTR_MISMATCH_CHECKER_H_
#define BASE_MEMORY_RAW_SCOPED_REFPTR_MISMATCH_CHECKER_H_
-#include <tuple>
#include <type_traits>
-#include "base/memory/ref_counted.h"
+#include "base/template_util.h"
// It is dangerous to post a task with a T* argument where T is a subtype of
// RefCounted(Base|ThreadSafeBase), since by the time the parameter is used, the
@@ -23,34 +22,29 @@ namespace base {
// Not for public consumption, so we wrap it in namespace internal.
namespace internal {
+template <typename T, typename = void>
+struct IsRefCountedType : std::false_type {};
+
+template <typename T>
+struct IsRefCountedType<T,
+ void_t<decltype(std::declval<T*>()->AddRef()),
+ decltype(std::declval<T*>()->Release())>>
+ : std::true_type {};
+
template <typename T>
struct NeedsScopedRefptrButGetsRawPtr {
+ static_assert(!std::is_reference<T>::value,
+ "NeedsScopedRefptrButGetsRawPtr requires non-reference type.");
+
enum {
// Human readable translation: you needed to be a scoped_refptr if you are a
// raw pointer type and are convertible to a RefCounted(Base|ThreadSafeBase)
// type.
- value = (std::is_pointer<T>::value &&
- (std::is_convertible<T, subtle::RefCountedBase*>::value ||
- std::is_convertible<T, subtle::RefCountedThreadSafeBase*>::value))
+ value = std::is_pointer<T>::value &&
+ IsRefCountedType<std::remove_pointer_t<T>>::value
};
};
-template <typename Params>
-struct ParamsUseScopedRefptrCorrectly {
- enum { value = 0 };
-};
-
-template <>
-struct ParamsUseScopedRefptrCorrectly<std::tuple<>> {
- enum { value = 1 };
-};
-
-template <typename Head, typename... Tail>
-struct ParamsUseScopedRefptrCorrectly<std::tuple<Head, Tail...>> {
- enum { value = !NeedsScopedRefptrButGetsRawPtr<Head>::value &&
- ParamsUseScopedRefptrCorrectly<std::tuple<Tail...>>::value };
-};
-
} // namespace internal
} // namespace base
diff --git a/base/memory/read_only_shared_memory_region.cc b/base/memory/read_only_shared_memory_region.cc
new file mode 100644
index 0000000000..6b654c9894
--- /dev/null
+++ b/base/memory/read_only_shared_memory_region.cc
@@ -0,0 +1,97 @@
+// 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.
+
+#include "base/memory/read_only_shared_memory_region.h"
+
+#include <utility>
+
+#include "base/memory/shared_memory.h"
+#include "build/build_config.h"
+
+namespace base {
+
+// static
+MappedReadOnlyRegion ReadOnlySharedMemoryRegion::Create(size_t size) {
+ subtle::PlatformSharedMemoryRegion handle =
+ subtle::PlatformSharedMemoryRegion::CreateWritable(size);
+ if (!handle.IsValid())
+ return {};
+
+ void* memory_ptr = nullptr;
+ size_t mapped_size = 0;
+ if (!handle.MapAt(0, handle.GetSize(), &memory_ptr, &mapped_size))
+ return {};
+
+ WritableSharedMemoryMapping mapping(memory_ptr, size, mapped_size,
+ handle.GetGUID());
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ handle.ConvertToReadOnly(memory_ptr);
+#else
+ handle.ConvertToReadOnly();
+#endif // defined(OS_MACOSX) && !defined(OS_IOS)
+ ReadOnlySharedMemoryRegion region(std::move(handle));
+
+ if (!region.IsValid() || !mapping.IsValid())
+ return {};
+
+ return {std::move(region), std::move(mapping)};
+}
+
+// static
+ReadOnlySharedMemoryRegion ReadOnlySharedMemoryRegion::Deserialize(
+ subtle::PlatformSharedMemoryRegion handle) {
+ return ReadOnlySharedMemoryRegion(std::move(handle));
+}
+
+// static
+subtle::PlatformSharedMemoryRegion
+ReadOnlySharedMemoryRegion::TakeHandleForSerialization(
+ ReadOnlySharedMemoryRegion region) {
+ return std::move(region.handle_);
+}
+
+ReadOnlySharedMemoryRegion::ReadOnlySharedMemoryRegion() = default;
+ReadOnlySharedMemoryRegion::ReadOnlySharedMemoryRegion(
+ ReadOnlySharedMemoryRegion&& region) = default;
+ReadOnlySharedMemoryRegion& ReadOnlySharedMemoryRegion::operator=(
+ ReadOnlySharedMemoryRegion&& region) = default;
+ReadOnlySharedMemoryRegion::~ReadOnlySharedMemoryRegion() = default;
+
+ReadOnlySharedMemoryRegion ReadOnlySharedMemoryRegion::Duplicate() const {
+ return ReadOnlySharedMemoryRegion(handle_.Duplicate());
+}
+
+ReadOnlySharedMemoryMapping ReadOnlySharedMemoryRegion::Map() const {
+ return MapAt(0, handle_.GetSize());
+}
+
+ReadOnlySharedMemoryMapping ReadOnlySharedMemoryRegion::MapAt(
+ off_t offset,
+ size_t size) const {
+ if (!IsValid())
+ return {};
+
+ void* memory = nullptr;
+ size_t mapped_size = 0;
+ if (!handle_.MapAt(offset, size, &memory, &mapped_size))
+ return {};
+
+ return ReadOnlySharedMemoryMapping(memory, size, mapped_size,
+ handle_.GetGUID());
+}
+
+bool ReadOnlySharedMemoryRegion::IsValid() const {
+ return handle_.IsValid();
+}
+
+ReadOnlySharedMemoryRegion::ReadOnlySharedMemoryRegion(
+ subtle::PlatformSharedMemoryRegion handle)
+ : handle_(std::move(handle)) {
+ if (handle_.IsValid()) {
+ CHECK_EQ(handle_.GetMode(),
+ subtle::PlatformSharedMemoryRegion::Mode::kReadOnly);
+ }
+}
+
+} // namespace base
diff --git a/base/memory/read_only_shared_memory_region.h b/base/memory/read_only_shared_memory_region.h
new file mode 100644
index 0000000000..4f92762d5e
--- /dev/null
+++ b/base/memory/read_only_shared_memory_region.h
@@ -0,0 +1,122 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_READ_ONLY_SHARED_MEMORY_REGION_H_
+#define BASE_MEMORY_READ_ONLY_SHARED_MEMORY_REGION_H_
+
+#include <utility>
+
+#include "base/macros.h"
+#include "base/memory/platform_shared_memory_region.h"
+#include "base/memory/shared_memory_mapping.h"
+
+namespace base {
+
+struct MappedReadOnlyRegion;
+
+// Scoped move-only handle to a region of platform shared memory. The instance
+// owns the platform handle it wraps. Mappings created by this region are
+// read-only. These mappings remain valid even after the region handle is moved
+// or destroyed.
+class BASE_EXPORT ReadOnlySharedMemoryRegion {
+ public:
+ using MappingType = ReadOnlySharedMemoryMapping;
+ // Creates a new ReadOnlySharedMemoryRegion instance of a given size along
+ // with the WritableSharedMemoryMapping which provides the only way to modify
+ // the content of the newly created region. The returned region and mapping
+ // are guaranteed to either be both valid or both invalid. Use
+ // |MappedReadOnlyRegion::IsValid()| as a shortcut for checking creation
+ // success.
+ //
+ // This means that the caller's process is the only process that can modify
+ // the region content. If you need to pass write access to another process,
+ // consider using WritableSharedMemoryRegion or UnsafeSharedMemoryRegion.
+ static MappedReadOnlyRegion Create(size_t size);
+
+ // Returns a ReadOnlySharedMemoryRegion built from a platform-specific handle
+ // that was taken from another ReadOnlySharedMemoryRegion instance. Returns an
+ // invalid region iff the |handle| is invalid. CHECK-fails if the |handle|
+ // isn't read-only.
+ // This should be used only by the code passing handles across process
+ // boundaries.
+ static ReadOnlySharedMemoryRegion Deserialize(
+ subtle::PlatformSharedMemoryRegion handle);
+
+ // Extracts a platform handle from the region. Ownership is transferred to the
+ // returned region object.
+ // This should be used only for sending the handle from the current process to
+ // another.
+ static subtle::PlatformSharedMemoryRegion TakeHandleForSerialization(
+ ReadOnlySharedMemoryRegion region);
+
+ // Default constructor initializes an invalid instance.
+ ReadOnlySharedMemoryRegion();
+
+ // Move operations are allowed.
+ ReadOnlySharedMemoryRegion(ReadOnlySharedMemoryRegion&&);
+ ReadOnlySharedMemoryRegion& operator=(ReadOnlySharedMemoryRegion&&);
+
+ // Destructor closes shared memory region if valid.
+ // All created mappings will remain valid.
+ ~ReadOnlySharedMemoryRegion();
+
+ // Duplicates the underlying platform handle and creates a new
+ // ReadOnlySharedMemoryRegion instance that owns this handle. Returns a valid
+ // ReadOnlySharedMemoryRegion on success, invalid otherwise. The current
+ // region instance remains valid in any case.
+ ReadOnlySharedMemoryRegion Duplicate() const;
+
+ // Maps the shared memory region into the caller's address space with
+ // read-only access. The mapped address is guaranteed to have an alignment of
+ // at least |subtle::PlatformSharedMemoryRegion::kMapMinimumAlignment|.
+ // Returns a valid ReadOnlySharedMemoryMapping instance on success, invalid
+ // otherwise.
+ ReadOnlySharedMemoryMapping Map() const;
+
+ // Same as above, but maps only |size| bytes of the shared memory region
+ // starting with the given |offset|. |offset| must be aligned to value of
+ // |SysInfo::VMAllocationGranularity()|. Returns an invalid mapping if
+ // requested bytes are out of the region limits.
+ ReadOnlySharedMemoryMapping MapAt(off_t offset, size_t size) const;
+
+ // Whether the underlying platform handle is valid.
+ bool IsValid() const;
+
+ // Returns the maximum mapping size that can be created from this region.
+ size_t GetSize() const {
+ DCHECK(IsValid());
+ return handle_.GetSize();
+ }
+
+ // Returns 128-bit GUID of the region.
+ const UnguessableToken& GetGUID() const {
+ DCHECK(IsValid());
+ return handle_.GetGUID();
+ }
+
+ private:
+ explicit ReadOnlySharedMemoryRegion(
+ subtle::PlatformSharedMemoryRegion handle);
+
+ subtle::PlatformSharedMemoryRegion handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReadOnlySharedMemoryRegion);
+};
+
+// Helper struct for return value of ReadOnlySharedMemoryRegion::Create().
+struct MappedReadOnlyRegion {
+ ReadOnlySharedMemoryRegion region;
+ WritableSharedMemoryMapping mapping;
+ // Helper function to check return value of
+ // ReadOnlySharedMemoryRegion::Create(). |region| and |mapping| either both
+ // valid or invalid.
+ bool IsValid() {
+ DCHECK_EQ(region.IsValid(), mapping.IsValid());
+ return region.IsValid() && mapping.IsValid();
+ }
+};
+
+} // namespace base
+
+#endif // BASE_MEMORY_READ_ONLY_SHARED_MEMORY_REGION_H_
diff --git a/base/memory/ref_counted.cc b/base/memory/ref_counted.cc
index 039f255b15..b9fa15fd5e 100644
--- a/base/memory/ref_counted.cc
+++ b/base/memory/ref_counted.cc
@@ -10,7 +10,7 @@ namespace base {
namespace {
#if DCHECK_IS_ON()
-AtomicRefCount g_cross_thread_ref_count_access_allow_count = 0;
+std::atomic_int g_cross_thread_ref_count_access_allow_count(0);
#endif
} // namespace
@@ -18,45 +18,38 @@ AtomicRefCount g_cross_thread_ref_count_access_allow_count = 0;
namespace subtle {
bool RefCountedThreadSafeBase::HasOneRef() const {
- return AtomicRefCountIsOne(&ref_count_);
+ return ref_count_.IsOne();
}
-RefCountedThreadSafeBase::~RefCountedThreadSafeBase() {
#if DCHECK_IS_ON()
+RefCountedThreadSafeBase::~RefCountedThreadSafeBase() {
DCHECK(in_dtor_) << "RefCountedThreadSafe object deleted without "
"calling Release()";
-#endif
}
-
-void RefCountedThreadSafeBase::AddRef() const {
-#if DCHECK_IS_ON()
- DCHECK(!in_dtor_);
- DCHECK(!needs_adopt_ref_)
- << "This RefCounted object is created with non-zero reference count."
- << " The first reference to such a object has to be made by AdoptRef or"
- << " MakeShared.";
#endif
- AtomicRefCountInc(&ref_count_);
+
+#if defined(ARCH_CPU_64_BIT)
+void RefCountedBase::AddRefImpl() const {
+ // Check if |ref_count_| overflow only on 64 bit archs since the number of
+ // objects may exceed 2^32.
+ // To avoid the binary size bloat, use non-inline function here.
+ CHECK(++ref_count_ > 0);
}
+#endif
+#if !defined(ARCH_CPU_X86_FAMILY)
bool RefCountedThreadSafeBase::Release() const {
-#if DCHECK_IS_ON()
- DCHECK(!in_dtor_);
- DCHECK(!AtomicRefCountIsZero(&ref_count_));
-#endif
- if (!AtomicRefCountDec(&ref_count_)) {
-#if DCHECK_IS_ON()
- in_dtor_ = true;
-#endif
- return true;
- }
- return false;
+ return ReleaseImpl();
}
+void RefCountedThreadSafeBase::AddRef() const {
+ AddRefImpl();
+}
+#endif
#if DCHECK_IS_ON()
bool RefCountedBase::CalledOnValidSequence() const {
return sequence_checker_.CalledOnValidSequence() ||
- !AtomicRefCountIsZero(&g_cross_thread_ref_count_access_allow_count);
+ g_cross_thread_ref_count_access_allow_count.load() != 0;
}
#endif
@@ -64,11 +57,11 @@ bool RefCountedBase::CalledOnValidSequence() const {
#if DCHECK_IS_ON()
ScopedAllowCrossThreadRefCountAccess::ScopedAllowCrossThreadRefCountAccess() {
- AtomicRefCountInc(&g_cross_thread_ref_count_access_allow_count);
+ ++g_cross_thread_ref_count_access_allow_count;
}
ScopedAllowCrossThreadRefCountAccess::~ScopedAllowCrossThreadRefCountAccess() {
- AtomicRefCountDec(&g_cross_thread_ref_count_access_allow_count);
+ --g_cross_thread_ref_count_access_allow_count;
}
#endif
diff --git a/base/memory/ref_counted.h b/base/memory/ref_counted.h
index be493f632f..249f70e054 100644
--- a/base/memory/ref_counted.h
+++ b/base/memory/ref_counted.h
@@ -7,33 +7,21 @@
#include <stddef.h>
-#include <cassert>
-#include <iosfwd>
-#include <type_traits>
+#include <utility>
#include "base/atomic_ref_count.h"
#include "base/base_export.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
#include "base/sequence_checker.h"
#include "base/threading/thread_collision_warner.h"
#include "build/build_config.h"
-template <class T>
-class scoped_refptr;
-
namespace base {
-
-template <typename T>
-scoped_refptr<T> AdoptRef(T* t);
-
namespace subtle {
-enum AdoptRefTag { kAdoptRefTag };
-enum StartRefCountFromZeroTag { kStartRefCountFromZeroTag };
-enum StartRefCountFromOneTag { kStartRefCountFromOneTag };
-
class BASE_EXPORT RefCountedBase {
public:
bool HasOneRef() const { return ref_count_ == 1; }
@@ -68,13 +56,13 @@ class BASE_EXPORT RefCountedBase {
DCHECK(!needs_adopt_ref_)
<< "This RefCounted object is created with non-zero reference count."
<< " The first reference to such a object has to be made by AdoptRef or"
- << " MakeShared.";
+ << " MakeRefCounted.";
if (ref_count_ >= 1) {
DCHECK(CalledOnValidSequence());
}
#endif
- ++ref_count_;
+ AddRefImpl();
}
// Returns true if the object should self-delete.
@@ -100,6 +88,27 @@ class BASE_EXPORT RefCountedBase {
return ref_count_ == 0;
}
+ // Returns true if it is safe to read or write the object, from a thread
+ // safety standpoint. Should be DCHECK'd from the methods of RefCounted
+ // classes if there is a danger of objects being shared across threads.
+ //
+ // This produces fewer false positives than adding a separate SequenceChecker
+ // into the subclass, because it automatically detaches from the sequence when
+ // the reference count is 1 (and never fails if there is only one reference).
+ //
+ // This means unlike a separate SequenceChecker, it will permit a singly
+ // referenced object to be passed between threads (not holding a reference on
+ // the sending thread), but will trap if the sending thread holds onto a
+ // reference, or if the object is accessed from multiple threads
+ // simultaneously.
+ bool IsOnValidSequence() const {
+#if DCHECK_IS_ON()
+ return ref_count_ <= 1 || CalledOnValidSequence();
+#else
+ return true;
+#endif
+ }
+
private:
template <typename U>
friend scoped_refptr<U> base::AdoptRef(U*);
@@ -111,11 +120,17 @@ class BASE_EXPORT RefCountedBase {
#endif
}
+#if defined(ARCH_CPU_64_BIT)
+ void AddRefImpl() const;
+#else
+ void AddRefImpl() const { ++ref_count_; }
+#endif
+
#if DCHECK_IS_ON()
bool CalledOnValidSequence() const;
#endif
- mutable size_t ref_count_ = 0;
+ mutable uint32_t ref_count_ = 0;
#if DCHECK_IS_ON()
mutable bool needs_adopt_ref_ = false;
@@ -133,19 +148,32 @@ class BASE_EXPORT RefCountedThreadSafeBase {
bool HasOneRef() const;
protected:
- explicit RefCountedThreadSafeBase(StartRefCountFromZeroTag) {}
- explicit RefCountedThreadSafeBase(StartRefCountFromOneTag) : ref_count_(1) {
+ explicit constexpr RefCountedThreadSafeBase(StartRefCountFromZeroTag) {}
+ explicit constexpr RefCountedThreadSafeBase(StartRefCountFromOneTag)
+ : ref_count_(1) {
#if DCHECK_IS_ON()
needs_adopt_ref_ = true;
#endif
}
+#if DCHECK_IS_ON()
~RefCountedThreadSafeBase();
+#else
+ ~RefCountedThreadSafeBase() = default;
+#endif
- void AddRef() const;
-
+// Release and AddRef are suitable for inlining on X86 because they generate
+// very small code sequences. On other platforms (ARM), it causes a size
+// regression and is probably not worth it.
+#if defined(ARCH_CPU_X86_FAMILY)
+ // Returns true if the object should self-delete.
+ bool Release() const { return ReleaseImpl(); }
+ void AddRef() const { AddRefImpl(); }
+#else
// Returns true if the object should self-delete.
bool Release() const;
+ void AddRef() const;
+#endif
private:
template <typename U>
@@ -158,7 +186,32 @@ class BASE_EXPORT RefCountedThreadSafeBase {
#endif
}
- mutable AtomicRefCount ref_count_ = 0;
+ ALWAYS_INLINE void AddRefImpl() const {
+#if DCHECK_IS_ON()
+ DCHECK(!in_dtor_);
+ DCHECK(!needs_adopt_ref_)
+ << "This RefCounted object is created with non-zero reference count."
+ << " The first reference to such a object has to be made by AdoptRef or"
+ << " MakeRefCounted.";
+#endif
+ ref_count_.Increment();
+ }
+
+ ALWAYS_INLINE bool ReleaseImpl() const {
+#if DCHECK_IS_ON()
+ DCHECK(!in_dtor_);
+ DCHECK(!ref_count_.IsZero());
+#endif
+ if (!ref_count_.Decrement()) {
+#if DCHECK_IS_ON()
+ in_dtor_ = true;
+#endif
+ return true;
+ }
+ return false;
+ }
+
+ mutable AtomicRefCount ref_count_{0};
#if DCHECK_IS_ON()
mutable bool needs_adopt_ref_ = false;
mutable bool in_dtor_ = false;
@@ -210,7 +263,9 @@ class BASE_EXPORT ScopedAllowCrossThreadRefCountAccess final {
// to trap unsafe cross thread usage. A subclass instance of RefCounted can be
// passed to another execution sequence only when its ref count is 1. If the ref
// count is more than 1, the RefCounted class verifies the ref updates are made
-// on the same execution sequence as the previous ones.
+// on the same execution sequence as the previous ones. The subclass can also
+// manually call IsOnValidSequence to trap other non-thread-safe accesses; see
+// the documentation for that method.
//
//
// The reference count starts from zero by default, and we intended to migrate
@@ -218,8 +273,8 @@ class BASE_EXPORT ScopedAllowCrossThreadRefCountAccess final {
// the ref counted class to opt-in.
//
// If an object has start-from-one ref count, the first scoped_refptr need to be
-// created by base::AdoptRef() or base::MakeShared(). We can use
-// base::MakeShared() to create create both type of ref counted object.
+// created by base::AdoptRef() or base::MakeRefCounted(). We can use
+// base::MakeRefCounted() to create create both type of ref counted object.
//
// The motivations to use start-from-one ref count are:
// - Start-from-one ref count doesn't need the ref count increment for the
@@ -236,7 +291,17 @@ class BASE_EXPORT ScopedAllowCrossThreadRefCountAccess final {
static constexpr ::base::subtle::StartRefCountFromOneTag \
kRefCountPreference = ::base::subtle::kStartRefCountFromOneTag
-template <class T>
+template <class T, typename Traits>
+class RefCounted;
+
+template <typename T>
+struct DefaultRefCountedTraits {
+ static void Destruct(const T* x) {
+ RefCounted<T, DefaultRefCountedTraits>::DeleteInternal(x);
+ }
+};
+
+template <class T, typename Traits = DefaultRefCountedTraits<T>>
class RefCounted : public subtle::RefCountedBase {
public:
static constexpr subtle::StartRefCountFromZeroTag kRefCountPreference =
@@ -250,7 +315,12 @@ class RefCounted : public subtle::RefCountedBase {
void Release() const {
if (subtle::RefCountedBase::Release()) {
- delete static_cast<const T*>(this);
+ // Prune the code paths which the static analyzer may take to simulate
+ // object destruction. Use-after-free errors aren't possible given the
+ // lifetime guarantees of the refcounting system.
+ ANALYZER_SKIP_THIS_PATH();
+
+ Traits::Destruct(static_cast<const T*>(this));
}
}
@@ -258,6 +328,12 @@ class RefCounted : public subtle::RefCountedBase {
~RefCounted() = default;
private:
+ friend struct DefaultRefCountedTraits<T>;
+ template <typename U>
+ static void DeleteInternal(const U* x) {
+ delete x;
+ }
+
DISALLOW_COPY_AND_ASSIGN(RefCounted);
};
@@ -307,6 +383,7 @@ class RefCountedThreadSafe : public subtle::RefCountedThreadSafeBase {
void Release() const {
if (subtle::RefCountedThreadSafeBase::Release()) {
+ ANALYZER_SKIP_THIS_PATH();
Traits::Destruct(static_cast<const T*>(this));
}
}
@@ -316,7 +393,10 @@ class RefCountedThreadSafe : public subtle::RefCountedThreadSafeBase {
private:
friend struct DefaultRefCountedThreadSafeTraits<T>;
- static void DeleteInternal(const T* x) { delete x; }
+ template <typename U>
+ static void DeleteInternal(const U* x) {
+ delete x;
+ }
DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafe);
};
@@ -331,6 +411,7 @@ class RefCountedData
public:
RefCountedData() : data() {}
RefCountedData(const T& in_value) : data(in_value) {}
+ RefCountedData(T&& in_value) : data(std::move(in_value)) {}
T data;
@@ -339,289 +420,6 @@ class RefCountedData
~RefCountedData() = default;
};
-// Creates a scoped_refptr from a raw pointer without incrementing the reference
-// count. Use this only for a newly created object whose reference count starts
-// from 1 instead of 0.
-template <typename T>
-scoped_refptr<T> AdoptRef(T* obj) {
- using Tag = typename std::decay<decltype(T::kRefCountPreference)>::type;
- static_assert(std::is_same<subtle::StartRefCountFromOneTag, Tag>::value,
- "Use AdoptRef only for the reference count starts from one.");
-
- DCHECK(obj);
- DCHECK(obj->HasOneRef());
- obj->Adopted();
- return scoped_refptr<T>(obj, subtle::kAdoptRefTag);
-}
-
-namespace subtle {
-
-template <typename T>
-scoped_refptr<T> AdoptRefIfNeeded(T* obj, StartRefCountFromZeroTag) {
- return scoped_refptr<T>(obj);
-}
-
-template <typename T>
-scoped_refptr<T> AdoptRefIfNeeded(T* obj, StartRefCountFromOneTag) {
- return AdoptRef(obj);
-}
-
-} // namespace subtle
-
-// Constructs an instance of T, which is a ref counted type, and wraps the
-// object into a scoped_refptr.
-template <typename T, typename... Args>
-scoped_refptr<T> MakeShared(Args&&... args) {
- T* obj = new T(std::forward<Args>(args)...);
- return subtle::AdoptRefIfNeeded(obj, T::kRefCountPreference);
-}
-
} // namespace base
-//
-// A smart pointer class for reference counted objects. Use this class instead
-// of calling AddRef and Release manually on a reference counted object to
-// avoid common memory leaks caused by forgetting to Release an object
-// reference. Sample usage:
-//
-// class MyFoo : public RefCounted<MyFoo> {
-// ...
-// private:
-// friend class RefCounted<MyFoo>; // Allow destruction by RefCounted<>.
-// ~MyFoo(); // Destructor must be private/protected.
-// };
-//
-// void some_function() {
-// scoped_refptr<MyFoo> foo = new MyFoo();
-// foo->Method(param);
-// // |foo| is released when this function returns
-// }
-//
-// void some_other_function() {
-// scoped_refptr<MyFoo> foo = new MyFoo();
-// ...
-// foo = nullptr; // explicitly releases |foo|
-// ...
-// if (foo)
-// foo->Method(param);
-// }
-//
-// The above examples show how scoped_refptr<T> acts like a pointer to T.
-// Given two scoped_refptr<T> classes, it is also possible to exchange
-// references between the two objects, like so:
-//
-// {
-// scoped_refptr<MyFoo> a = new MyFoo();
-// scoped_refptr<MyFoo> b;
-//
-// b.swap(a);
-// // now, |b| references the MyFoo object, and |a| references nullptr.
-// }
-//
-// To make both |a| and |b| in the above example reference the same MyFoo
-// object, simply use the assignment operator:
-//
-// {
-// scoped_refptr<MyFoo> a = new MyFoo();
-// scoped_refptr<MyFoo> b;
-//
-// b = a;
-// // now, |a| and |b| each own a reference to the same MyFoo object.
-// }
-//
-template <class T>
-class scoped_refptr {
- public:
- typedef T element_type;
-
- scoped_refptr() {}
-
- scoped_refptr(T* p) : ptr_(p) {
- if (ptr_)
- AddRef(ptr_);
- }
-
- // Copy constructor.
- scoped_refptr(const scoped_refptr<T>& r) : ptr_(r.ptr_) {
- if (ptr_)
- AddRef(ptr_);
- }
-
- // Copy conversion constructor.
- template <typename U,
- typename = typename std::enable_if<
- std::is_convertible<U*, T*>::value>::type>
- scoped_refptr(const scoped_refptr<U>& r) : ptr_(r.get()) {
- if (ptr_)
- AddRef(ptr_);
- }
-
- // Move constructor. This is required in addition to the conversion
- // constructor below in order for clang to warn about pessimizing moves.
- scoped_refptr(scoped_refptr&& r) : ptr_(r.get()) { r.ptr_ = nullptr; }
-
- // Move conversion constructor.
- template <typename U,
- typename = typename std::enable_if<
- std::is_convertible<U*, T*>::value>::type>
- scoped_refptr(scoped_refptr<U>&& r) : ptr_(r.get()) {
- r.ptr_ = nullptr;
- }
-
- ~scoped_refptr() {
- if (ptr_)
- Release(ptr_);
- }
-
- T* get() const { return ptr_; }
-
- T& operator*() const {
- assert(ptr_ != nullptr);
- return *ptr_;
- }
-
- T* operator->() const {
- assert(ptr_ != nullptr);
- return ptr_;
- }
-
- scoped_refptr<T>& operator=(T* p) {
- // AddRef first so that self assignment should work
- if (p)
- AddRef(p);
- T* old_ptr = ptr_;
- ptr_ = p;
- if (old_ptr)
- Release(old_ptr);
- return *this;
- }
-
- scoped_refptr<T>& operator=(const scoped_refptr<T>& r) {
- return *this = r.ptr_;
- }
-
- template <typename U>
- scoped_refptr<T>& operator=(const scoped_refptr<U>& r) {
- return *this = r.get();
- }
-
- scoped_refptr<T>& operator=(scoped_refptr<T>&& r) {
- scoped_refptr<T>(std::move(r)).swap(*this);
- return *this;
- }
-
- template <typename U>
- scoped_refptr<T>& operator=(scoped_refptr<U>&& r) {
- scoped_refptr<T>(std::move(r)).swap(*this);
- return *this;
- }
-
- void swap(scoped_refptr<T>& r) {
- T* tmp = ptr_;
- ptr_ = r.ptr_;
- r.ptr_ = tmp;
- }
-
- explicit operator bool() const { return ptr_ != nullptr; }
-
- template <typename U>
- bool operator==(const scoped_refptr<U>& rhs) const {
- return ptr_ == rhs.get();
- }
-
- template <typename U>
- bool operator!=(const scoped_refptr<U>& rhs) const {
- return !operator==(rhs);
- }
-
- template <typename U>
- bool operator<(const scoped_refptr<U>& rhs) const {
- return ptr_ < rhs.get();
- }
-
- protected:
- T* ptr_ = nullptr;
-
- private:
- template <typename U>
- friend scoped_refptr<U> base::AdoptRef(U*);
-
- scoped_refptr(T* p, base::subtle::AdoptRefTag) : ptr_(p) {}
-
- // Friend required for move constructors that set r.ptr_ to null.
- template <typename U>
- friend class scoped_refptr;
-
- // Non-inline helpers to allow:
- // class Opaque;
- // extern template class scoped_refptr<Opaque>;
- // Otherwise the compiler will complain that Opaque is an incomplete type.
- static void AddRef(T* ptr);
- static void Release(T* ptr);
-};
-
-// static
-template <typename T>
-void scoped_refptr<T>::AddRef(T* ptr) {
- ptr->AddRef();
-}
-
-// static
-template <typename T>
-void scoped_refptr<T>::Release(T* ptr) {
- ptr->Release();
-}
-
-// Handy utility for creating a scoped_refptr<T> out of a T* explicitly without
-// having to retype all the template arguments
-template <typename T>
-scoped_refptr<T> make_scoped_refptr(T* t) {
- return scoped_refptr<T>(t);
-}
-
-template <typename T, typename U>
-bool operator==(const scoped_refptr<T>& lhs, const U* rhs) {
- return lhs.get() == rhs;
-}
-
-template <typename T, typename U>
-bool operator==(const T* lhs, const scoped_refptr<U>& rhs) {
- return lhs == rhs.get();
-}
-
-template <typename T>
-bool operator==(const scoped_refptr<T>& lhs, std::nullptr_t null) {
- return !static_cast<bool>(lhs);
-}
-
-template <typename T>
-bool operator==(std::nullptr_t null, const scoped_refptr<T>& rhs) {
- return !static_cast<bool>(rhs);
-}
-
-template <typename T, typename U>
-bool operator!=(const scoped_refptr<T>& lhs, const U* rhs) {
- return !operator==(lhs, rhs);
-}
-
-template <typename T, typename U>
-bool operator!=(const T* lhs, const scoped_refptr<U>& rhs) {
- return !operator==(lhs, rhs);
-}
-
-template <typename T>
-bool operator!=(const scoped_refptr<T>& lhs, std::nullptr_t null) {
- return !operator==(lhs, null);
-}
-
-template <typename T>
-bool operator!=(std::nullptr_t null, const scoped_refptr<T>& rhs) {
- return !operator==(null, rhs);
-}
-
-template <typename T>
-std::ostream& operator<<(std::ostream& out, const scoped_refptr<T>& p) {
- return out << p.get();
-}
-
#endif // BASE_MEMORY_REF_COUNTED_H_
diff --git a/base/memory/ref_counted_delete_on_sequence.h b/base/memory/ref_counted_delete_on_sequence.h
new file mode 100644
index 0000000000..dd301063d4
--- /dev/null
+++ b/base/memory/ref_counted_delete_on_sequence.h
@@ -0,0 +1,82 @@
+// 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_MEMORY_REF_COUNTED_DELETE_ON_SEQUENCE_H_
+#define BASE_MEMORY_REF_COUNTED_DELETE_ON_SEQUENCE_H_
+
+#include <utility>
+
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner.h"
+
+namespace base {
+
+// RefCountedDeleteOnSequence is similar to RefCountedThreadSafe, and ensures
+// that the object will be deleted on a specified sequence.
+//
+// Sample usage:
+// class Foo : public RefCountedDeleteOnSequence<Foo> {
+//
+// Foo(scoped_refptr<SequencedTaskRunner> task_runner)
+// : RefCountedDeleteOnSequence<Foo>(std::move(task_runner)) {}
+// ...
+// private:
+// friend class RefCountedDeleteOnSequence<Foo>;
+// friend class DeleteHelper<Foo>;
+//
+// ~Foo();
+// };
+template <class T>
+class RefCountedDeleteOnSequence : public subtle::RefCountedThreadSafeBase {
+ public:
+ static constexpr subtle::StartRefCountFromZeroTag kRefCountPreference =
+ subtle::kStartRefCountFromZeroTag;
+
+ // A SequencedTaskRunner for the current sequence can be acquired by calling
+ // SequencedTaskRunnerHandle::Get().
+ RefCountedDeleteOnSequence(
+ scoped_refptr<SequencedTaskRunner> owning_task_runner)
+ : subtle::RefCountedThreadSafeBase(T::kRefCountPreference),
+ owning_task_runner_(std::move(owning_task_runner)) {
+ DCHECK(owning_task_runner_);
+ }
+
+ void AddRef() const { subtle::RefCountedThreadSafeBase::AddRef(); }
+
+ void Release() const {
+ if (subtle::RefCountedThreadSafeBase::Release())
+ DestructOnSequence();
+ }
+
+ protected:
+ friend class DeleteHelper<RefCountedDeleteOnSequence>;
+ ~RefCountedDeleteOnSequence() = default;
+
+ SequencedTaskRunner* owning_task_runner() {
+ return owning_task_runner_.get();
+ }
+ const SequencedTaskRunner* owning_task_runner() const {
+ return owning_task_runner_.get();
+ }
+
+ private:
+ void DestructOnSequence() const {
+ const T* t = static_cast<const T*>(this);
+ if (owning_task_runner_->RunsTasksInCurrentSequence())
+ delete t;
+ else
+ owning_task_runner_->DeleteSoon(FROM_HERE, t);
+ }
+
+ const scoped_refptr<SequencedTaskRunner> owning_task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(RefCountedDeleteOnSequence);
+};
+
+} // namespace base
+
+#endif // BASE_MEMORY_REF_COUNTED_DELETE_ON_SEQUENCE_H_
diff --git a/base/memory/ref_counted_memory.cc b/base/memory/ref_counted_memory.cc
index 26b78f3663..23a5ffc256 100644
--- a/base/memory/ref_counted_memory.cc
+++ b/base/memory/ref_counted_memory.cc
@@ -4,7 +4,10 @@
#include "base/memory/ref_counted_memory.h"
+#include <utility>
+
#include "base/logging.h"
+#include "base/memory/read_only_shared_memory_region.h"
namespace base {
@@ -15,9 +18,9 @@ bool RefCountedMemory::Equals(
(memcmp(front(), other->front(), size()) == 0);
}
-RefCountedMemory::RefCountedMemory() {}
+RefCountedMemory::RefCountedMemory() = default;
-RefCountedMemory::~RefCountedMemory() {}
+RefCountedMemory::~RefCountedMemory() = default;
const unsigned char* RefCountedStaticMemory::front() const {
return data_;
@@ -27,9 +30,9 @@ size_t RefCountedStaticMemory::size() const {
return length_;
}
-RefCountedStaticMemory::~RefCountedStaticMemory() {}
+RefCountedStaticMemory::~RefCountedStaticMemory() = default;
-RefCountedBytes::RefCountedBytes() {}
+RefCountedBytes::RefCountedBytes() = default;
RefCountedBytes::RefCountedBytes(const std::vector<unsigned char>& initializer)
: data_(initializer) {
@@ -38,9 +41,11 @@ RefCountedBytes::RefCountedBytes(const std::vector<unsigned char>& initializer)
RefCountedBytes::RefCountedBytes(const unsigned char* p, size_t size)
: data_(p, p + size) {}
+RefCountedBytes::RefCountedBytes(size_t size) : data_(size, 0) {}
+
scoped_refptr<RefCountedBytes> RefCountedBytes::TakeVector(
std::vector<unsigned char>* to_destroy) {
- scoped_refptr<RefCountedBytes> bytes(new RefCountedBytes);
+ auto bytes = MakeRefCounted<RefCountedBytes>();
bytes->data_.swap(*to_destroy);
return bytes;
}
@@ -48,34 +53,80 @@ scoped_refptr<RefCountedBytes> RefCountedBytes::TakeVector(
const unsigned char* RefCountedBytes::front() const {
// STL will assert if we do front() on an empty vector, but calling code
// expects a NULL.
- return size() ? &data_.front() : NULL;
+ return size() ? &data_.front() : nullptr;
}
size_t RefCountedBytes::size() const {
return data_.size();
}
-RefCountedBytes::~RefCountedBytes() {}
+RefCountedBytes::~RefCountedBytes() = default;
-RefCountedString::RefCountedString() {}
+RefCountedString::RefCountedString() = default;
-RefCountedString::~RefCountedString() {}
+RefCountedString::~RefCountedString() = default;
// static
scoped_refptr<RefCountedString> RefCountedString::TakeString(
std::string* to_destroy) {
- scoped_refptr<RefCountedString> self(new RefCountedString);
+ auto self = MakeRefCounted<RefCountedString>();
to_destroy->swap(self->data_);
return self;
}
const unsigned char* RefCountedString::front() const {
- return data_.empty() ? NULL :
- reinterpret_cast<const unsigned char*>(data_.data());
+ return data_.empty() ? nullptr
+ : reinterpret_cast<const unsigned char*>(data_.data());
}
size_t RefCountedString::size() const {
return data_.size();
}
+RefCountedSharedMemory::RefCountedSharedMemory(
+ std::unique_ptr<SharedMemory> shm,
+ size_t size)
+ : shm_(std::move(shm)), size_(size) {
+ DCHECK(shm_);
+ DCHECK(shm_->memory());
+ DCHECK_GT(size_, 0U);
+ DCHECK_LE(size_, shm_->mapped_size());
+}
+
+RefCountedSharedMemory::~RefCountedSharedMemory() = default;
+
+const unsigned char* RefCountedSharedMemory::front() const {
+ return static_cast<const unsigned char*>(shm_->memory());
+}
+
+size_t RefCountedSharedMemory::size() const {
+ return size_;
+}
+
+RefCountedSharedMemoryMapping::RefCountedSharedMemoryMapping(
+ ReadOnlySharedMemoryMapping mapping)
+ : mapping_(std::move(mapping)), size_(mapping_.size()) {
+ DCHECK_GT(size_, 0U);
+}
+
+RefCountedSharedMemoryMapping::~RefCountedSharedMemoryMapping() = default;
+
+const unsigned char* RefCountedSharedMemoryMapping::front() const {
+ return static_cast<const unsigned char*>(mapping_.memory());
+}
+
+size_t RefCountedSharedMemoryMapping::size() const {
+ return size_;
+}
+
+// static
+scoped_refptr<RefCountedSharedMemoryMapping>
+RefCountedSharedMemoryMapping::CreateFromWholeRegion(
+ const ReadOnlySharedMemoryRegion& region) {
+ ReadOnlySharedMemoryMapping mapping = region.Map();
+ if (!mapping.IsValid())
+ return nullptr;
+ return MakeRefCounted<RefCountedSharedMemoryMapping>(std::move(mapping));
+}
+
} // namespace base
diff --git a/base/memory/ref_counted_memory.h b/base/memory/ref_counted_memory.h
index aa22c9e525..92a7d7b176 100644
--- a/base/memory/ref_counted_memory.h
+++ b/base/memory/ref_counted_memory.h
@@ -7,21 +7,25 @@
#include <stddef.h>
+#include <memory>
#include <string>
#include <vector>
#include "base/base_export.h"
-#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
+#include "base/memory/shared_memory.h"
+#include "base/memory/shared_memory_mapping.h"
namespace base {
-// A generic interface to memory. This object is reference counted because one
-// of its two subclasses own the data they carry, and we need to have
-// heterogeneous containers of these two types of memory.
+class ReadOnlySharedMemoryRegion;
+
+// A generic interface to memory. This object is reference counted because most
+// of its subclasses own the data they carry, and this interface needs to
+// support heterogeneous containers of these different types of memory.
class BASE_EXPORT RefCountedMemory
- : public base::RefCountedThreadSafe<RefCountedMemory> {
+ : public RefCountedThreadSafe<RefCountedMemory> {
public:
// Retrieves a pointer to the beginning of the data we point to. If the data
// is empty, this will return NULL.
@@ -39,7 +43,7 @@ class BASE_EXPORT RefCountedMemory
}
protected:
- friend class base::RefCountedThreadSafe<RefCountedMemory>;
+ friend class RefCountedThreadSafe<RefCountedMemory>;
RefCountedMemory();
virtual ~RefCountedMemory();
};
@@ -48,13 +52,12 @@ class BASE_EXPORT RefCountedMemory
// matter.
class BASE_EXPORT RefCountedStaticMemory : public RefCountedMemory {
public:
- RefCountedStaticMemory()
- : data_(NULL), length_(0) {}
+ RefCountedStaticMemory() : data_(nullptr), length_(0) {}
RefCountedStaticMemory(const void* data, size_t length)
- : data_(static_cast<const unsigned char*>(length ? data : NULL)),
+ : data_(static_cast<const unsigned char*>(length ? data : nullptr)),
length_(length) {}
- // Overridden from RefCountedMemory:
+ // RefCountedMemory:
const unsigned char* front() const override;
size_t size() const override;
@@ -67,30 +70,43 @@ class BASE_EXPORT RefCountedStaticMemory : public RefCountedMemory {
DISALLOW_COPY_AND_ASSIGN(RefCountedStaticMemory);
};
-// An implementation of RefCountedMemory, where we own the data in a vector.
+// An implementation of RefCountedMemory, where the data is stored in a STL
+// vector.
class BASE_EXPORT RefCountedBytes : public RefCountedMemory {
public:
RefCountedBytes();
- // Constructs a RefCountedBytes object by _copying_ from |initializer|.
+ // Constructs a RefCountedBytes object by copying from |initializer|.
explicit RefCountedBytes(const std::vector<unsigned char>& initializer);
// Constructs a RefCountedBytes object by copying |size| bytes from |p|.
RefCountedBytes(const unsigned char* p, size_t size);
+ // Constructs a RefCountedBytes object by zero-initializing a new vector of
+ // |size| bytes.
+ explicit RefCountedBytes(size_t size);
+
// Constructs a RefCountedBytes object by performing a swap. (To non
// destructively build a RefCountedBytes, use the constructor that takes a
// vector.)
static scoped_refptr<RefCountedBytes> TakeVector(
std::vector<unsigned char>* to_destroy);
- // Overridden from RefCountedMemory:
+ // RefCountedMemory:
const unsigned char* front() const override;
size_t size() const override;
const std::vector<unsigned char>& data() const { return data_; }
std::vector<unsigned char>& data() { return data_; }
+ // Non-const versions of front() and front_as() that are simply shorthand for
+ // data().data().
+ unsigned char* front() { return data_.data(); }
+ template <typename T>
+ T* front_as() {
+ return reinterpret_cast<T*>(front());
+ }
+
private:
~RefCountedBytes() override;
@@ -99,7 +115,7 @@ class BASE_EXPORT RefCountedBytes : public RefCountedMemory {
DISALLOW_COPY_AND_ASSIGN(RefCountedBytes);
};
-// An implementation of RefCountedMemory, where the bytes are stored in an STL
+// An implementation of RefCountedMemory, where the bytes are stored in a STL
// string. Use this if your data naturally arrives in that format.
class BASE_EXPORT RefCountedString : public RefCountedMemory {
public:
@@ -110,7 +126,7 @@ class BASE_EXPORT RefCountedString : public RefCountedMemory {
// copy into object->data()).
static scoped_refptr<RefCountedString> TakeString(std::string* to_destroy);
- // Overridden from RefCountedMemory:
+ // RefCountedMemory:
const unsigned char* front() const override;
size_t size() const override;
@@ -125,6 +141,53 @@ class BASE_EXPORT RefCountedString : public RefCountedMemory {
DISALLOW_COPY_AND_ASSIGN(RefCountedString);
};
+// An implementation of RefCountedMemory, where the bytes are stored in
+// SharedMemory.
+class BASE_EXPORT RefCountedSharedMemory : public RefCountedMemory {
+ public:
+ // Constructs a RefCountedMemory object by taking ownership of an already
+ // mapped SharedMemory object.
+ RefCountedSharedMemory(std::unique_ptr<SharedMemory> shm, size_t size);
+
+ // RefCountedMemory:
+ const unsigned char* front() const override;
+ size_t size() const override;
+
+ private:
+ ~RefCountedSharedMemory() override;
+
+ const std::unique_ptr<SharedMemory> shm_;
+ const size_t size_;
+
+ DISALLOW_COPY_AND_ASSIGN(RefCountedSharedMemory);
+};
+
+// An implementation of RefCountedMemory, where the bytes are stored in
+// ReadOnlySharedMemoryMapping.
+class BASE_EXPORT RefCountedSharedMemoryMapping : public RefCountedMemory {
+ public:
+ // Constructs a RefCountedMemory object by taking ownership of an already
+ // mapped ReadOnlySharedMemoryMapping object.
+ explicit RefCountedSharedMemoryMapping(ReadOnlySharedMemoryMapping mapping);
+
+ // Convenience method to map all of |region| and take ownership of the
+ // mapping. Returns an empty scoped_refptr if the map operation fails.
+ static scoped_refptr<RefCountedSharedMemoryMapping> CreateFromWholeRegion(
+ const ReadOnlySharedMemoryRegion& region);
+
+ // RefCountedMemory:
+ const unsigned char* front() const override;
+ size_t size() const override;
+
+ private:
+ ~RefCountedSharedMemoryMapping() override;
+
+ const ReadOnlySharedMemoryMapping mapping_;
+ const size_t size_;
+
+ DISALLOW_COPY_AND_ASSIGN(RefCountedSharedMemoryMapping);
+};
+
} // namespace base
#endif // BASE_MEMORY_REF_COUNTED_MEMORY_H_
diff --git a/base/memory/ref_counted_memory_unittest.cc b/base/memory/ref_counted_memory_unittest.cc
index bd2ed01f54..b7498f9ede 100644
--- a/base/memory/ref_counted_memory_unittest.cc
+++ b/base/memory/ref_counted_memory_unittest.cc
@@ -6,13 +6,19 @@
#include <stdint.h>
+#include <utility>
+
+#include "base/memory/read_only_shared_memory_region.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
+using testing::Each;
+using testing::ElementsAre;
+
namespace base {
TEST(RefCountedMemoryUnitTest, RefCountedStaticMemory) {
- scoped_refptr<RefCountedMemory> mem = new RefCountedStaticMemory(
- "static mem00", 10);
+ auto mem = MakeRefCounted<RefCountedStaticMemory>("static mem00", 10);
EXPECT_EQ(10U, mem->size());
EXPECT_EQ("static mem", std::string(mem->front_as<char>(), mem->size()));
@@ -26,41 +32,99 @@ TEST(RefCountedMemoryUnitTest, RefCountedBytes) {
EXPECT_EQ(0U, data.size());
- EXPECT_EQ(2U, mem->size());
+ ASSERT_EQ(2U, mem->size());
EXPECT_EQ(45U, mem->front()[0]);
EXPECT_EQ(99U, mem->front()[1]);
scoped_refptr<RefCountedMemory> mem2;
{
- unsigned char data2[] = { 12, 11, 99 };
- mem2 = new RefCountedBytes(data2, 3);
+ const unsigned char kData[] = {12, 11, 99};
+ mem2 = MakeRefCounted<RefCountedBytes>(kData, arraysize(kData));
}
- EXPECT_EQ(3U, mem2->size());
+ ASSERT_EQ(3U, mem2->size());
EXPECT_EQ(12U, mem2->front()[0]);
EXPECT_EQ(11U, mem2->front()[1]);
EXPECT_EQ(99U, mem2->front()[2]);
}
+TEST(RefCountedMemoryUnitTest, RefCountedBytesMutable) {
+ auto mem = base::MakeRefCounted<RefCountedBytes>(10);
+
+ ASSERT_EQ(10U, mem->size());
+ EXPECT_THAT(mem->data(), Each(0U));
+
+ // Test non-const versions of data(), front() and front_as<>().
+ mem->data()[0] = 1;
+ mem->front()[1] = 2;
+ mem->front_as<char>()[2] = 3;
+
+ EXPECT_THAT(mem->data(), ElementsAre(1, 2, 3, 0, 0, 0, 0, 0, 0, 0));
+}
+
TEST(RefCountedMemoryUnitTest, RefCountedString) {
std::string s("destroy me");
scoped_refptr<RefCountedMemory> mem = RefCountedString::TakeString(&s);
EXPECT_EQ(0U, s.size());
- EXPECT_EQ(10U, mem->size());
+ ASSERT_EQ(10U, mem->size());
EXPECT_EQ('d', mem->front()[0]);
EXPECT_EQ('e', mem->front()[1]);
+ EXPECT_EQ('e', mem->front()[9]);
+}
+
+TEST(RefCountedMemoryUnitTest, RefCountedSharedMemory) {
+ static const char kData[] = "shm_dummy_data";
+ auto shm = std::make_unique<SharedMemory>();
+ ASSERT_TRUE(shm->CreateAndMapAnonymous(sizeof(kData)));
+ memcpy(shm->memory(), kData, sizeof(kData));
+
+ auto mem =
+ MakeRefCounted<RefCountedSharedMemory>(std::move(shm), sizeof(kData));
+ ASSERT_EQ(sizeof(kData), mem->size());
+ EXPECT_EQ('s', mem->front()[0]);
+ EXPECT_EQ('h', mem->front()[1]);
+ EXPECT_EQ('_', mem->front()[9]);
+}
+
+TEST(RefCountedMemoryUnitTest, RefCountedSharedMemoryMapping) {
+ static const char kData[] = "mem_region_dummy_data";
+ scoped_refptr<RefCountedSharedMemoryMapping> mem;
+ {
+ MappedReadOnlyRegion region =
+ ReadOnlySharedMemoryRegion::Create(sizeof(kData));
+ ReadOnlySharedMemoryMapping ro_mapping = region.region.Map();
+ WritableSharedMemoryMapping rw_mapping = std::move(region.mapping);
+ ASSERT_TRUE(rw_mapping.IsValid());
+ memcpy(rw_mapping.memory(), kData, sizeof(kData));
+ mem = MakeRefCounted<RefCountedSharedMemoryMapping>(std::move(ro_mapping));
+ }
+
+ ASSERT_LE(sizeof(kData), mem->size());
+ EXPECT_EQ('e', mem->front()[1]);
+ EXPECT_EQ('m', mem->front()[2]);
+ EXPECT_EQ('o', mem->front()[8]);
+
+ {
+ MappedReadOnlyRegion region =
+ ReadOnlySharedMemoryRegion::Create(sizeof(kData));
+ WritableSharedMemoryMapping rw_mapping = std::move(region.mapping);
+ ASSERT_TRUE(rw_mapping.IsValid());
+ memcpy(rw_mapping.memory(), kData, sizeof(kData));
+ mem = RefCountedSharedMemoryMapping::CreateFromWholeRegion(region.region);
+ }
+
+ ASSERT_LE(sizeof(kData), mem->size());
+ EXPECT_EQ('_', mem->front()[3]);
+ EXPECT_EQ('r', mem->front()[4]);
+ EXPECT_EQ('i', mem->front()[7]);
}
TEST(RefCountedMemoryUnitTest, Equals) {
std::string s1("same");
scoped_refptr<RefCountedMemory> mem1 = RefCountedString::TakeString(&s1);
- std::vector<unsigned char> d2;
- d2.push_back('s');
- d2.push_back('a');
- d2.push_back('m');
- d2.push_back('e');
+ std::vector<unsigned char> d2 = {'s', 'a', 'm', 'e'};
scoped_refptr<RefCountedMemory> mem2 = RefCountedBytes::TakeVector(&d2);
EXPECT_TRUE(mem1->Equals(mem2));
@@ -75,7 +139,7 @@ TEST(RefCountedMemoryUnitTest, Equals) {
TEST(RefCountedMemoryUnitTest, EqualsNull) {
std::string s("str");
scoped_refptr<RefCountedMemory> mem = RefCountedString::TakeString(&s);
- EXPECT_FALSE(mem->Equals(NULL));
+ EXPECT_FALSE(mem->Equals(nullptr));
}
} // namespace base
diff --git a/base/memory/ref_counted_unittest.cc b/base/memory/ref_counted_unittest.cc
index 515f4227ea..df1c30f843 100644
--- a/base/memory/ref_counted_unittest.cc
+++ b/base/memory/ref_counted_unittest.cc
@@ -4,17 +4,17 @@
#include "base/memory/ref_counted.h"
+#include <type_traits>
#include <utility>
#include "base/test/gtest_util.h"
-#include "base/test/opaque_ref_counted.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
class SelfAssign : public base::RefCounted<SelfAssign> {
protected:
- virtual ~SelfAssign() {}
+ virtual ~SelfAssign() = default;
private:
friend class base::RefCounted<SelfAssign>;
@@ -22,7 +22,7 @@ class SelfAssign : public base::RefCounted<SelfAssign> {
class Derived : public SelfAssign {
protected:
- ~Derived() override {}
+ ~Derived() override = default;
private:
friend class base::RefCounted<Derived>;
@@ -112,9 +112,29 @@ class Other : public base::RefCounted<Other> {
private:
friend class base::RefCounted<Other>;
- ~Other() {}
+ ~Other() = default;
};
+class HasPrivateDestructorWithDeleter;
+
+struct Deleter {
+ static void Destruct(const HasPrivateDestructorWithDeleter* x);
+};
+
+class HasPrivateDestructorWithDeleter
+ : public base::RefCounted<HasPrivateDestructorWithDeleter, Deleter> {
+ public:
+ HasPrivateDestructorWithDeleter() = default;
+
+ private:
+ friend struct Deleter;
+ ~HasPrivateDestructorWithDeleter() = default;
+};
+
+void Deleter::Destruct(const HasPrivateDestructorWithDeleter* x) {
+ delete x;
+}
+
scoped_refptr<Other> Overloaded(scoped_refptr<Other> other) {
return other;
}
@@ -127,11 +147,30 @@ class InitialRefCountIsOne : public base::RefCounted<InitialRefCountIsOne> {
public:
REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE();
- InitialRefCountIsOne() {}
+ InitialRefCountIsOne() = default;
private:
friend class base::RefCounted<InitialRefCountIsOne>;
- ~InitialRefCountIsOne() {}
+ ~InitialRefCountIsOne() = default;
+};
+
+// Checks that the scoped_refptr is null before the reference counted object is
+// destroyed.
+class CheckRefptrNull : public base::RefCounted<CheckRefptrNull> {
+ public:
+ // Set the last scoped_refptr that will have a reference to this object.
+ void set_scoped_refptr(scoped_refptr<CheckRefptrNull>* ptr) { ptr_ = ptr; }
+
+ protected:
+ virtual ~CheckRefptrNull() {
+ EXPECT_NE(ptr_, nullptr);
+ EXPECT_EQ(ptr_->get(), nullptr);
+ }
+
+ private:
+ friend class base::RefCounted<CheckRefptrNull>;
+
+ scoped_refptr<CheckRefptrNull>* ptr_ = nullptr;
};
} // end namespace
@@ -139,7 +178,13 @@ class InitialRefCountIsOne : public base::RefCounted<InitialRefCountIsOne> {
TEST(RefCountedUnitTest, TestSelfAssignment) {
SelfAssign* p = new SelfAssign;
scoped_refptr<SelfAssign> var(p);
- var = var;
+ var = *&var; // The *& defeats Clang's -Wself-assign warning.
+ EXPECT_EQ(var.get(), p);
+ var = std::move(var);
+ EXPECT_EQ(var.get(), p);
+ var.swap(var);
+ EXPECT_EQ(var.get(), p);
+ swap(var, var);
EXPECT_EQ(var.get(), p);
}
@@ -168,37 +213,6 @@ TEST(RefCountedUnitTest, ScopedRefPtrToSelfMoveAssignment) {
EXPECT_TRUE(ScopedRefPtrToSelf::was_destroyed());
}
-TEST(RefCountedUnitTest, ScopedRefPtrToOpaque) {
- scoped_refptr<base::OpaqueRefCounted> initial = base::MakeOpaqueRefCounted();
- base::TestOpaqueRefCounted(initial);
-
- scoped_refptr<base::OpaqueRefCounted> assigned;
- assigned = initial;
-
- scoped_refptr<base::OpaqueRefCounted> copied(initial);
-
- scoped_refptr<base::OpaqueRefCounted> moved(std::move(initial));
-
- scoped_refptr<base::OpaqueRefCounted> move_assigned;
- move_assigned = std::move(moved);
-}
-
-TEST(RefCountedUnitTest, ScopedRefPtrToOpaqueThreadSafe) {
- scoped_refptr<base::OpaqueRefCountedThreadSafe> initial =
- base::MakeOpaqueRefCountedThreadSafe();
- base::TestOpaqueRefCountedThreadSafe(initial);
-
- scoped_refptr<base::OpaqueRefCountedThreadSafe> assigned;
- assigned = initial;
-
- scoped_refptr<base::OpaqueRefCountedThreadSafe> copied(initial);
-
- scoped_refptr<base::OpaqueRefCountedThreadSafe> moved(std::move(initial));
-
- scoped_refptr<base::OpaqueRefCountedThreadSafe> move_assigned;
- move_assigned = std::move(moved);
-}
-
TEST(RefCountedUnitTest, BooleanTesting) {
scoped_refptr<SelfAssign> ptr_to_an_instance = new SelfAssign;
EXPECT_TRUE(ptr_to_an_instance);
@@ -415,6 +429,27 @@ TEST(RefCountedUnitTest, MoveAssignmentDifferentInstances) {
EXPECT_EQ(2, ScopedRefPtrCountBase::destructor_count());
}
+TEST(RefCountedUnitTest, MoveAssignmentSelfMove) {
+ ScopedRefPtrCountBase::reset_count();
+
+ {
+ ScopedRefPtrCountBase* raw = new ScopedRefPtrCountBase;
+ scoped_refptr<ScopedRefPtrCountBase> p1(raw);
+ scoped_refptr<ScopedRefPtrCountBase>& p1_ref = p1;
+
+ EXPECT_EQ(1, ScopedRefPtrCountBase::constructor_count());
+ EXPECT_EQ(0, ScopedRefPtrCountBase::destructor_count());
+
+ p1 = std::move(p1_ref);
+
+ // |p1| is "valid but unspecified", so don't bother inspecting its
+ // contents, just ensure that we don't crash.
+ }
+
+ EXPECT_EQ(1, ScopedRefPtrCountBase::constructor_count());
+ EXPECT_EQ(1, ScopedRefPtrCountBase::destructor_count());
+}
+
TEST(RefCountedUnitTest, MoveAssignmentDerived) {
ScopedRefPtrCountBase::reset_count();
ScopedRefPtrCountDerived::reset_count();
@@ -522,47 +557,115 @@ TEST(RefCountedUnitTest, MoveConstructorDerived) {
}
TEST(RefCountedUnitTest, TestOverloadResolutionCopy) {
- scoped_refptr<Derived> derived(new Derived);
- scoped_refptr<SelfAssign> expected(derived);
+ const scoped_refptr<Derived> derived(new Derived);
+ const scoped_refptr<SelfAssign> expected(derived);
EXPECT_EQ(expected, Overloaded(derived));
- scoped_refptr<Other> other(new Other);
+ const scoped_refptr<Other> other(new Other);
EXPECT_EQ(other, Overloaded(other));
}
TEST(RefCountedUnitTest, TestOverloadResolutionMove) {
scoped_refptr<Derived> derived(new Derived);
- scoped_refptr<SelfAssign> expected(derived);
+ const scoped_refptr<SelfAssign> expected(derived);
EXPECT_EQ(expected, Overloaded(std::move(derived)));
scoped_refptr<Other> other(new Other);
- scoped_refptr<Other> other2(other);
+ const scoped_refptr<Other> other2(other);
EXPECT_EQ(other2, Overloaded(std::move(other)));
}
+TEST(RefCountedUnitTest, TestMakeRefCounted) {
+ scoped_refptr<Derived> derived = new Derived;
+ EXPECT_TRUE(derived->HasOneRef());
+ derived.reset();
+
+ scoped_refptr<Derived> derived2 = base::MakeRefCounted<Derived>();
+ EXPECT_TRUE(derived2->HasOneRef());
+ derived2.reset();
+}
+
TEST(RefCountedUnitTest, TestInitialRefCountIsOne) {
scoped_refptr<InitialRefCountIsOne> obj =
- base::MakeShared<InitialRefCountIsOne>();
+ base::MakeRefCounted<InitialRefCountIsOne>();
EXPECT_TRUE(obj->HasOneRef());
- obj = nullptr;
+ obj.reset();
scoped_refptr<InitialRefCountIsOne> obj2 =
base::AdoptRef(new InitialRefCountIsOne);
EXPECT_TRUE(obj2->HasOneRef());
- obj2 = nullptr;
+ obj2.reset();
- scoped_refptr<Other> obj3 = base::MakeShared<Other>();
+ scoped_refptr<Other> obj3 = base::MakeRefCounted<Other>();
EXPECT_TRUE(obj3->HasOneRef());
- obj3 = nullptr;
+ obj3.reset();
+}
+
+TEST(RefCountedUnitTest, TestPrivateDestructorWithDeleter) {
+ // Ensure that RefCounted doesn't need the access to the pointee dtor when
+ // a custom deleter is given.
+ scoped_refptr<HasPrivateDestructorWithDeleter> obj =
+ base::MakeRefCounted<HasPrivateDestructorWithDeleter>();
+}
+
+TEST(RefCountedUnitTest, TestReset) {
+ ScopedRefPtrCountBase::reset_count();
+
+ // Create ScopedRefPtrCountBase that is referenced by |obj1| and |obj2|.
+ scoped_refptr<ScopedRefPtrCountBase> obj1 =
+ base::MakeRefCounted<ScopedRefPtrCountBase>();
+ scoped_refptr<ScopedRefPtrCountBase> obj2 = obj1;
+ EXPECT_NE(obj1.get(), nullptr);
+ EXPECT_NE(obj2.get(), nullptr);
+ EXPECT_EQ(ScopedRefPtrCountBase::constructor_count(), 1);
+ EXPECT_EQ(ScopedRefPtrCountBase::destructor_count(), 0);
+
+ // Check that calling reset() on |obj1| resets it. |obj2| still has a
+ // reference to the ScopedRefPtrCountBase so it shouldn't be reset.
+ obj1.reset();
+ EXPECT_EQ(obj1.get(), nullptr);
+ EXPECT_EQ(ScopedRefPtrCountBase::constructor_count(), 1);
+ EXPECT_EQ(ScopedRefPtrCountBase::destructor_count(), 0);
+
+ // Check that calling reset() on |obj2| resets it and causes the deletion of
+ // the ScopedRefPtrCountBase.
+ obj2.reset();
+ EXPECT_EQ(obj2.get(), nullptr);
+ EXPECT_EQ(ScopedRefPtrCountBase::constructor_count(), 1);
+ EXPECT_EQ(ScopedRefPtrCountBase::destructor_count(), 1);
+}
+
+TEST(RefCountedUnitTest, TestResetAlreadyNull) {
+ // Check that calling reset() on a null scoped_refptr does nothing.
+ scoped_refptr<ScopedRefPtrCountBase> obj;
+ obj.reset();
+ // |obj| should still be null after calling reset().
+ EXPECT_EQ(obj.get(), nullptr);
+}
+
+TEST(RefCountedUnitTest, CheckScopedRefptrNullBeforeObjectDestruction) {
+ scoped_refptr<CheckRefptrNull> obj = base::MakeRefCounted<CheckRefptrNull>();
+ obj->set_scoped_refptr(&obj);
+
+ // Check that when reset() is called the scoped_refptr internal pointer is set
+ // to null before the reference counted object is destroyed. This check is
+ // done by the CheckRefptrNull destructor.
+ obj.reset();
+ EXPECT_EQ(obj.get(), nullptr);
}
TEST(RefCountedDeathTest, TestAdoptRef) {
- EXPECT_DCHECK_DEATH(make_scoped_refptr(new InitialRefCountIsOne));
+ // Check that WrapRefCounted() DCHECKs if passed a type that defines
+ // REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE.
+ EXPECT_DCHECK_DEATH(base::WrapRefCounted(new InitialRefCountIsOne));
+ // Check that AdoptRef() DCHECKs if passed a nullptr.
InitialRefCountIsOne* ptr = nullptr;
EXPECT_DCHECK_DEATH(base::AdoptRef(ptr));
+ // Check that AdoptRef() DCHECKs if passed an object that doesn't need to be
+ // adopted.
scoped_refptr<InitialRefCountIsOne> obj =
- base::MakeShared<InitialRefCountIsOne>();
+ base::MakeRefCounted<InitialRefCountIsOne>();
EXPECT_DCHECK_DEATH(base::AdoptRef(obj.get()));
}
diff --git a/base/memory/scoped_refptr.h b/base/memory/scoped_refptr.h
new file mode 100644
index 0000000000..389d0cbf62
--- /dev/null
+++ b/base/memory/scoped_refptr.h
@@ -0,0 +1,337 @@
+// 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_SCOPED_REFPTR_H_
+#define BASE_MEMORY_SCOPED_REFPTR_H_
+
+#include <stddef.h>
+
+#include <iosfwd>
+#include <type_traits>
+#include <utility>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/macros.h"
+
+template <class T>
+class scoped_refptr;
+
+namespace base {
+
+template <class, typename>
+class RefCounted;
+template <class, typename>
+class RefCountedThreadSafe;
+
+template <typename T>
+scoped_refptr<T> AdoptRef(T* t);
+
+namespace subtle {
+
+enum AdoptRefTag { kAdoptRefTag };
+enum StartRefCountFromZeroTag { kStartRefCountFromZeroTag };
+enum StartRefCountFromOneTag { kStartRefCountFromOneTag };
+
+template <typename T, typename U, typename V>
+constexpr bool IsRefCountPreferenceOverridden(const T*,
+ const RefCounted<U, V>*) {
+ return !std::is_same<std::decay_t<decltype(T::kRefCountPreference)>,
+ std::decay_t<decltype(U::kRefCountPreference)>>::value;
+}
+
+template <typename T, typename U, typename V>
+constexpr bool IsRefCountPreferenceOverridden(
+ const T*,
+ const RefCountedThreadSafe<U, V>*) {
+ return !std::is_same<std::decay_t<decltype(T::kRefCountPreference)>,
+ std::decay_t<decltype(U::kRefCountPreference)>>::value;
+}
+
+constexpr bool IsRefCountPreferenceOverridden(...) {
+ return false;
+}
+
+} // namespace subtle
+
+// Creates a scoped_refptr from a raw pointer without incrementing the reference
+// count. Use this only for a newly created object whose reference count starts
+// from 1 instead of 0.
+template <typename T>
+scoped_refptr<T> AdoptRef(T* obj) {
+ using Tag = std::decay_t<decltype(T::kRefCountPreference)>;
+ static_assert(std::is_same<subtle::StartRefCountFromOneTag, Tag>::value,
+ "Use AdoptRef only for the reference count starts from one.");
+
+ DCHECK(obj);
+ DCHECK(obj->HasOneRef());
+ obj->Adopted();
+ return scoped_refptr<T>(obj, subtle::kAdoptRefTag);
+}
+
+namespace subtle {
+
+template <typename T>
+scoped_refptr<T> AdoptRefIfNeeded(T* obj, StartRefCountFromZeroTag) {
+ return scoped_refptr<T>(obj);
+}
+
+template <typename T>
+scoped_refptr<T> AdoptRefIfNeeded(T* obj, StartRefCountFromOneTag) {
+ return AdoptRef(obj);
+}
+
+} // namespace subtle
+
+// Constructs an instance of T, which is a ref counted type, and wraps the
+// object into a scoped_refptr<T>.
+template <typename T, typename... Args>
+scoped_refptr<T> MakeRefCounted(Args&&... args) {
+ T* obj = new T(std::forward<Args>(args)...);
+ return subtle::AdoptRefIfNeeded(obj, T::kRefCountPreference);
+}
+
+// Takes an instance of T, which is a ref counted type, and wraps the object
+// into a scoped_refptr<T>.
+template <typename T>
+scoped_refptr<T> WrapRefCounted(T* t) {
+ return scoped_refptr<T>(t);
+}
+
+} // namespace base
+
+//
+// A smart pointer class for reference counted objects. Use this class instead
+// of calling AddRef and Release manually on a reference counted object to
+// avoid common memory leaks caused by forgetting to Release an object
+// reference. Sample usage:
+//
+// class MyFoo : public RefCounted<MyFoo> {
+// ...
+// private:
+// friend class RefCounted<MyFoo>; // Allow destruction by RefCounted<>.
+// ~MyFoo(); // Destructor must be private/protected.
+// };
+//
+// void some_function() {
+// scoped_refptr<MyFoo> foo = MakeRefCounted<MyFoo>();
+// foo->Method(param);
+// // |foo| is released when this function returns
+// }
+//
+// void some_other_function() {
+// scoped_refptr<MyFoo> foo = MakeRefCounted<MyFoo>();
+// ...
+// foo.reset(); // explicitly releases |foo|
+// ...
+// if (foo)
+// foo->Method(param);
+// }
+//
+// The above examples show how scoped_refptr<T> acts like a pointer to T.
+// Given two scoped_refptr<T> classes, it is also possible to exchange
+// references between the two objects, like so:
+//
+// {
+// scoped_refptr<MyFoo> a = MakeRefCounted<MyFoo>();
+// scoped_refptr<MyFoo> b;
+//
+// b.swap(a);
+// // now, |b| references the MyFoo object, and |a| references nullptr.
+// }
+//
+// To make both |a| and |b| in the above example reference the same MyFoo
+// object, simply use the assignment operator:
+//
+// {
+// scoped_refptr<MyFoo> a = MakeRefCounted<MyFoo>();
+// scoped_refptr<MyFoo> b;
+//
+// b = a;
+// // now, |a| and |b| each own a reference to the same MyFoo object.
+// }
+//
+// Also see Chromium's ownership and calling conventions:
+// https://chromium.googlesource.com/chromium/src/+/lkgr/styleguide/c++/c++.md#object-ownership-and-calling-conventions
+// Specifically:
+// If the function (at least sometimes) takes a ref on a refcounted object,
+// declare the param as scoped_refptr<T>. The caller can decide whether it
+// wishes to transfer ownership (by calling std::move(t) when passing t) or
+// retain its ref (by simply passing t directly).
+// In other words, use scoped_refptr like you would a std::unique_ptr except
+// in the odd case where it's required to hold on to a ref while handing one
+// to another component (if a component merely needs to use t on the stack
+// without keeping a ref: pass t as a raw T*).
+template <class T>
+class scoped_refptr {
+ public:
+ typedef T element_type;
+
+ constexpr scoped_refptr() = default;
+
+ // Constructs from raw pointer. constexpr if |p| is null.
+ constexpr scoped_refptr(T* p) : ptr_(p) {
+ if (ptr_)
+ AddRef(ptr_);
+ }
+
+ // Copy constructor. This is required in addition to the copy conversion
+ // constructor below.
+ scoped_refptr(const scoped_refptr& r) : scoped_refptr(r.ptr_) {}
+
+ // Copy conversion constructor.
+ template <typename U,
+ typename = typename std::enable_if<
+ std::is_convertible<U*, T*>::value>::type>
+ scoped_refptr(const scoped_refptr<U>& r) : scoped_refptr(r.ptr_) {}
+
+ // Move constructor. This is required in addition to the move conversion
+ // constructor below.
+ scoped_refptr(scoped_refptr&& r) noexcept : ptr_(r.ptr_) { r.ptr_ = nullptr; }
+
+ // Move conversion constructor.
+ template <typename U,
+ typename = typename std::enable_if<
+ std::is_convertible<U*, T*>::value>::type>
+ scoped_refptr(scoped_refptr<U>&& r) noexcept : ptr_(r.ptr_) {
+ r.ptr_ = nullptr;
+ }
+
+ ~scoped_refptr() {
+ static_assert(!base::subtle::IsRefCountPreferenceOverridden(
+ static_cast<T*>(nullptr), static_cast<T*>(nullptr)),
+ "It's unsafe to override the ref count preference."
+ " Please remove REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE"
+ " from subclasses.");
+ if (ptr_)
+ Release(ptr_);
+ }
+
+ T* get() const { return ptr_; }
+
+ T& operator*() const {
+ DCHECK(ptr_);
+ return *ptr_;
+ }
+
+ T* operator->() const {
+ DCHECK(ptr_);
+ return ptr_;
+ }
+
+ scoped_refptr& operator=(T* p) { return *this = scoped_refptr(p); }
+
+ // Unified assignment operator.
+ scoped_refptr& operator=(scoped_refptr r) noexcept {
+ swap(r);
+ return *this;
+ }
+
+ // Sets managed object to null and releases reference to the previous managed
+ // object, if it existed.
+ void reset() { scoped_refptr().swap(*this); }
+
+ void swap(scoped_refptr& r) noexcept { std::swap(ptr_, r.ptr_); }
+
+ explicit operator bool() const { return ptr_ != nullptr; }
+
+ template <typename U>
+ bool operator==(const scoped_refptr<U>& rhs) const {
+ return ptr_ == rhs.get();
+ }
+
+ template <typename U>
+ bool operator!=(const scoped_refptr<U>& rhs) const {
+ return !operator==(rhs);
+ }
+
+ template <typename U>
+ bool operator<(const scoped_refptr<U>& rhs) const {
+ return ptr_ < rhs.get();
+ }
+
+ protected:
+ T* ptr_ = nullptr;
+
+ private:
+ template <typename U>
+ friend scoped_refptr<U> base::AdoptRef(U*);
+
+ scoped_refptr(T* p, base::subtle::AdoptRefTag) : ptr_(p) {}
+
+ // Friend required for move constructors that set r.ptr_ to null.
+ template <typename U>
+ friend class scoped_refptr;
+
+ // Non-inline helpers to allow:
+ // class Opaque;
+ // extern template class scoped_refptr<Opaque>;
+ // Otherwise the compiler will complain that Opaque is an incomplete type.
+ static void AddRef(T* ptr);
+ static void Release(T* ptr);
+};
+
+// static
+template <typename T>
+void scoped_refptr<T>::AddRef(T* ptr) {
+ ptr->AddRef();
+}
+
+// static
+template <typename T>
+void scoped_refptr<T>::Release(T* ptr) {
+ ptr->Release();
+}
+
+template <typename T, typename U>
+bool operator==(const scoped_refptr<T>& lhs, const U* rhs) {
+ return lhs.get() == rhs;
+}
+
+template <typename T, typename U>
+bool operator==(const T* lhs, const scoped_refptr<U>& rhs) {
+ return lhs == rhs.get();
+}
+
+template <typename T>
+bool operator==(const scoped_refptr<T>& lhs, std::nullptr_t null) {
+ return !static_cast<bool>(lhs);
+}
+
+template <typename T>
+bool operator==(std::nullptr_t null, const scoped_refptr<T>& rhs) {
+ return !static_cast<bool>(rhs);
+}
+
+template <typename T, typename U>
+bool operator!=(const scoped_refptr<T>& lhs, const U* rhs) {
+ return !operator==(lhs, rhs);
+}
+
+template <typename T, typename U>
+bool operator!=(const T* lhs, const scoped_refptr<U>& rhs) {
+ return !operator==(lhs, rhs);
+}
+
+template <typename T>
+bool operator!=(const scoped_refptr<T>& lhs, std::nullptr_t null) {
+ return !operator==(lhs, null);
+}
+
+template <typename T>
+bool operator!=(std::nullptr_t null, const scoped_refptr<T>& rhs) {
+ return !operator==(null, rhs);
+}
+
+template <typename T>
+std::ostream& operator<<(std::ostream& out, const scoped_refptr<T>& p) {
+ return out << p.get();
+}
+
+template <typename T>
+void swap(scoped_refptr<T>& lhs, scoped_refptr<T>& rhs) noexcept {
+ lhs.swap(rhs);
+}
+
+#endif // BASE_MEMORY_SCOPED_REFPTR_H_
diff --git a/base/memory/scoped_vector.h b/base/memory/scoped_vector.h
deleted file mode 100644
index a320b1e5d1..0000000000
--- a/base/memory/scoped_vector.h
+++ /dev/null
@@ -1,153 +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_MEMORY_SCOPED_VECTOR_H_
-#define BASE_MEMORY_SCOPED_VECTOR_H_
-
-#include <stddef.h>
-
-#include <memory>
-#include <vector>
-
-#include "base/logging.h"
-#include "base/macros.h"
-
-// ScopedVector wraps a vector deleting the elements from its
-// destructor.
-//
-// TODO(http://crbug.com/554289): DEPRECATED: Use std::vector instead (now that
-// we have support for moveable types inside containers).
-template <class T>
-class ScopedVector {
- public:
- typedef typename std::vector<T*>::allocator_type allocator_type;
- typedef typename std::vector<T*>::size_type size_type;
- typedef typename std::vector<T*>::difference_type difference_type;
- typedef typename std::vector<T*>::pointer pointer;
- typedef typename std::vector<T*>::const_pointer const_pointer;
- typedef typename std::vector<T*>::reference reference;
- typedef typename std::vector<T*>::const_reference const_reference;
- typedef typename std::vector<T*>::value_type value_type;
- typedef typename std::vector<T*>::iterator iterator;
- typedef typename std::vector<T*>::const_iterator const_iterator;
- typedef typename std::vector<T*>::reverse_iterator reverse_iterator;
- typedef typename std::vector<T*>::const_reverse_iterator
- const_reverse_iterator;
-
- ScopedVector() {}
- ~ScopedVector() { clear(); }
- ScopedVector(ScopedVector&& other) { swap(other); }
-
- ScopedVector& operator=(ScopedVector&& rhs) {
- swap(rhs);
- return *this;
- }
-
- reference operator[](size_t index) { return v_[index]; }
- const_reference operator[](size_t index) const { return v_[index]; }
-
- bool empty() const { return v_.empty(); }
- size_t size() const { return v_.size(); }
-
- reverse_iterator rbegin() { return v_.rbegin(); }
- const_reverse_iterator rbegin() const { return v_.rbegin(); }
- reverse_iterator rend() { return v_.rend(); }
- const_reverse_iterator rend() const { return v_.rend(); }
-
- iterator begin() { return v_.begin(); }
- const_iterator begin() const { return v_.begin(); }
- iterator end() { return v_.end(); }
- const_iterator end() const { return v_.end(); }
-
- const_reference front() const { return v_.front(); }
- reference front() { return v_.front(); }
- const_reference back() const { return v_.back(); }
- reference back() { return v_.back(); }
-
- void push_back(T* elem) { v_.push_back(elem); }
- void push_back(std::unique_ptr<T> elem) { v_.push_back(elem.release()); }
-
- void pop_back() {
- DCHECK(!empty());
- delete v_.back();
- v_.pop_back();
- }
-
- std::vector<T*>& get() { return v_; }
- const std::vector<T*>& get() const { return v_; }
- void swap(std::vector<T*>& other) { v_.swap(other); }
- void swap(ScopedVector<T>& other) { v_.swap(other.v_); }
- void release(std::vector<T*>* out) {
- out->swap(v_);
- v_.clear();
- }
-
- void reserve(size_t capacity) { v_.reserve(capacity); }
-
- // Resize, deleting elements in the disappearing range if we are shrinking.
- void resize(size_t new_size) {
- if (v_.size() > new_size) {
- for (auto it = v_.begin() + new_size; it != v_.end(); ++it)
- delete *it;
- }
- v_.resize(new_size);
- }
-
- template<typename InputIterator>
- void assign(InputIterator begin, InputIterator end) {
- v_.assign(begin, end);
- }
-
- void clear() {
- for (auto* item : *this)
- delete item;
- v_.clear();
- }
-
- // Like |clear()|, but doesn't delete any elements.
- void weak_clear() { v_.clear(); }
-
- // Lets the ScopedVector take ownership of |x|.
- iterator insert(iterator position, T* x) {
- return v_.insert(position, x);
- }
-
- iterator insert(iterator position, std::unique_ptr<T> x) {
- return v_.insert(position, x.release());
- }
-
- // Lets the ScopedVector take ownership of elements in [first,last).
- template<typename InputIterator>
- void insert(iterator position, InputIterator first, InputIterator last) {
- v_.insert(position, first, last);
- }
-
- iterator erase(iterator position) {
- delete *position;
- return v_.erase(position);
- }
-
- iterator erase(iterator first, iterator last) {
- for (auto it = first; it != last; ++it)
- delete *it;
- return v_.erase(first, last);
- }
-
- // Like |erase()|, but doesn't delete the element at |position|.
- iterator weak_erase(iterator position) {
- return v_.erase(position);
- }
-
- // Like |erase()|, but doesn't delete the elements in [first, last).
- iterator weak_erase(iterator first, iterator last) {
- return v_.erase(first, last);
- }
-
- private:
- std::vector<T*> v_;
-
- DISALLOW_COPY_AND_ASSIGN(ScopedVector);
-};
-
-#endif // BASE_MEMORY_SCOPED_VECTOR_H_
diff --git a/base/memory/scoped_vector_unittest.cc b/base/memory/scoped_vector_unittest.cc
deleted file mode 100644
index 916dab9a15..0000000000
--- a/base/memory/scoped_vector_unittest.cc
+++ /dev/null
@@ -1,338 +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/memory/scoped_vector.h"
-
-#include <memory>
-#include <utility>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/macros.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace {
-
-// The LifeCycleObject notifies its Observer upon construction & destruction.
-class LifeCycleObject {
- public:
- class Observer {
- public:
- virtual void OnLifeCycleConstruct(LifeCycleObject* o) = 0;
- virtual void OnLifeCycleDestroy(LifeCycleObject* o) = 0;
-
- protected:
- virtual ~Observer() {}
- };
-
- ~LifeCycleObject() {
- if (observer_)
- observer_->OnLifeCycleDestroy(this);
- }
-
- private:
- friend class LifeCycleWatcher;
-
- explicit LifeCycleObject(Observer* observer)
- : observer_(observer) {
- observer_->OnLifeCycleConstruct(this);
- }
-
- void DisconnectObserver() {
- observer_ = nullptr;
- }
-
- Observer* observer_;
-
- DISALLOW_COPY_AND_ASSIGN(LifeCycleObject);
-};
-
-// The life cycle states we care about for the purposes of testing ScopedVector
-// against objects.
-enum LifeCycleState {
- LC_INITIAL,
- LC_CONSTRUCTED,
- LC_DESTROYED,
-};
-
-// Because we wish to watch the life cycle of an object being constructed and
-// destroyed, and further wish to test expectations against the state of that
-// object, we cannot save state in that object itself. Instead, we use this
-// pairing of the watcher, which observes the object and notifies of
-// construction & destruction. Since we also may be testing assumptions about
-// things not getting freed, this class also acts like a scoping object and
-// deletes the |constructed_life_cycle_object_|, if any when the
-// LifeCycleWatcher is destroyed. To keep this simple, the only expected state
-// changes are:
-// INITIAL -> CONSTRUCTED -> DESTROYED.
-// Anything more complicated than that should start another test.
-class LifeCycleWatcher : public LifeCycleObject::Observer {
- public:
- LifeCycleWatcher() : life_cycle_state_(LC_INITIAL) {}
- ~LifeCycleWatcher() override {
- // Stop watching the watched object. Without this, the object's destructor
- // will call into OnLifeCycleDestroy when destructed, which happens after
- // this destructor has finished running.
- if (constructed_life_cycle_object_)
- constructed_life_cycle_object_->DisconnectObserver();
- }
-
- // Assert INITIAL -> CONSTRUCTED and no LifeCycleObject associated with this
- // LifeCycleWatcher.
- void OnLifeCycleConstruct(LifeCycleObject* object) override {
- ASSERT_EQ(LC_INITIAL, life_cycle_state_);
- ASSERT_EQ(NULL, constructed_life_cycle_object_.get());
- life_cycle_state_ = LC_CONSTRUCTED;
- constructed_life_cycle_object_.reset(object);
- }
-
- // Assert CONSTRUCTED -> DESTROYED and the |object| being destroyed is the
- // same one we saw constructed.
- void OnLifeCycleDestroy(LifeCycleObject* object) override {
- ASSERT_EQ(LC_CONSTRUCTED, life_cycle_state_);
- LifeCycleObject* constructed_life_cycle_object =
- constructed_life_cycle_object_.release();
- ASSERT_EQ(constructed_life_cycle_object, object);
- life_cycle_state_ = LC_DESTROYED;
- }
-
- LifeCycleState life_cycle_state() const { return life_cycle_state_; }
-
- // Factory method for creating a new LifeCycleObject tied to this
- // LifeCycleWatcher.
- LifeCycleObject* NewLifeCycleObject() {
- return new LifeCycleObject(this);
- }
-
- // Returns true iff |object| is the same object that this watcher is tracking.
- bool IsWatching(LifeCycleObject* object) const {
- return object == constructed_life_cycle_object_.get();
- }
-
- private:
- LifeCycleState life_cycle_state_;
- std::unique_ptr<LifeCycleObject> constructed_life_cycle_object_;
-
- DISALLOW_COPY_AND_ASSIGN(LifeCycleWatcher);
-};
-
-TEST(ScopedVectorTest, LifeCycleWatcher) {
- LifeCycleWatcher watcher;
- EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state());
- LifeCycleObject* object = watcher.NewLifeCycleObject();
- EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state());
- delete object;
- EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state());
-}
-
-TEST(ScopedVectorTest, PopBack) {
- LifeCycleWatcher watcher;
- EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state());
- ScopedVector<LifeCycleObject> scoped_vector;
- scoped_vector.push_back(watcher.NewLifeCycleObject());
- EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state());
- EXPECT_TRUE(watcher.IsWatching(scoped_vector.back()));
- scoped_vector.pop_back();
- EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state());
- EXPECT_TRUE(scoped_vector.empty());
-}
-
-TEST(ScopedVectorTest, Clear) {
- LifeCycleWatcher watcher;
- EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state());
- ScopedVector<LifeCycleObject> scoped_vector;
- scoped_vector.push_back(watcher.NewLifeCycleObject());
- EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state());
- EXPECT_TRUE(watcher.IsWatching(scoped_vector.back()));
- scoped_vector.clear();
- EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state());
- EXPECT_TRUE(scoped_vector.empty());
-}
-
-TEST(ScopedVectorTest, WeakClear) {
- LifeCycleWatcher watcher;
- EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state());
- ScopedVector<LifeCycleObject> scoped_vector;
- scoped_vector.push_back(watcher.NewLifeCycleObject());
- EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state());
- EXPECT_TRUE(watcher.IsWatching(scoped_vector.back()));
- scoped_vector.weak_clear();
- EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state());
- EXPECT_TRUE(scoped_vector.empty());
-}
-
-TEST(ScopedVectorTest, ResizeShrink) {
- LifeCycleWatcher first_watcher;
- EXPECT_EQ(LC_INITIAL, first_watcher.life_cycle_state());
- LifeCycleWatcher second_watcher;
- EXPECT_EQ(LC_INITIAL, second_watcher.life_cycle_state());
- ScopedVector<LifeCycleObject> scoped_vector;
-
- scoped_vector.push_back(first_watcher.NewLifeCycleObject());
- EXPECT_EQ(LC_CONSTRUCTED, first_watcher.life_cycle_state());
- EXPECT_EQ(LC_INITIAL, second_watcher.life_cycle_state());
- EXPECT_TRUE(first_watcher.IsWatching(scoped_vector[0]));
- EXPECT_FALSE(second_watcher.IsWatching(scoped_vector[0]));
-
- scoped_vector.push_back(second_watcher.NewLifeCycleObject());
- EXPECT_EQ(LC_CONSTRUCTED, first_watcher.life_cycle_state());
- EXPECT_EQ(LC_CONSTRUCTED, second_watcher.life_cycle_state());
- EXPECT_FALSE(first_watcher.IsWatching(scoped_vector[1]));
- EXPECT_TRUE(second_watcher.IsWatching(scoped_vector[1]));
-
- // Test that shrinking a vector deletes elements in the disappearing range.
- scoped_vector.resize(1);
- EXPECT_EQ(LC_CONSTRUCTED, first_watcher.life_cycle_state());
- EXPECT_EQ(LC_DESTROYED, second_watcher.life_cycle_state());
- EXPECT_EQ(1u, scoped_vector.size());
- EXPECT_TRUE(first_watcher.IsWatching(scoped_vector[0]));
-}
-
-TEST(ScopedVectorTest, ResizeGrow) {
- LifeCycleWatcher watcher;
- EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state());
- ScopedVector<LifeCycleObject> scoped_vector;
- scoped_vector.push_back(watcher.NewLifeCycleObject());
- EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state());
- EXPECT_TRUE(watcher.IsWatching(scoped_vector.back()));
-
- scoped_vector.resize(5);
- EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state());
- ASSERT_EQ(5u, scoped_vector.size());
- EXPECT_TRUE(watcher.IsWatching(scoped_vector[0]));
- EXPECT_FALSE(watcher.IsWatching(scoped_vector[1]));
- EXPECT_FALSE(watcher.IsWatching(scoped_vector[2]));
- EXPECT_FALSE(watcher.IsWatching(scoped_vector[3]));
- EXPECT_FALSE(watcher.IsWatching(scoped_vector[4]));
-}
-
-TEST(ScopedVectorTest, Scope) {
- LifeCycleWatcher watcher;
- EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state());
- {
- ScopedVector<LifeCycleObject> scoped_vector;
- scoped_vector.push_back(watcher.NewLifeCycleObject());
- EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state());
- EXPECT_TRUE(watcher.IsWatching(scoped_vector.back()));
- }
- EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state());
-}
-
-TEST(ScopedVectorTest, MoveConstruct) {
- LifeCycleWatcher watcher;
- EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state());
- {
- ScopedVector<LifeCycleObject> scoped_vector;
- scoped_vector.push_back(watcher.NewLifeCycleObject());
- EXPECT_FALSE(scoped_vector.empty());
- EXPECT_TRUE(watcher.IsWatching(scoped_vector.back()));
-
- ScopedVector<LifeCycleObject> scoped_vector_copy(std::move(scoped_vector));
- EXPECT_TRUE(scoped_vector.empty());
- EXPECT_FALSE(scoped_vector_copy.empty());
- EXPECT_TRUE(watcher.IsWatching(scoped_vector_copy.back()));
-
- EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state());
- }
- EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state());
-}
-
-TEST(ScopedVectorTest, MoveAssign) {
- LifeCycleWatcher watcher;
- EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state());
- {
- ScopedVector<LifeCycleObject> scoped_vector;
- scoped_vector.push_back(watcher.NewLifeCycleObject());
- ScopedVector<LifeCycleObject> scoped_vector_assign;
- EXPECT_FALSE(scoped_vector.empty());
- EXPECT_TRUE(watcher.IsWatching(scoped_vector.back()));
-
- scoped_vector_assign = std::move(scoped_vector);
- EXPECT_TRUE(scoped_vector.empty());
- EXPECT_FALSE(scoped_vector_assign.empty());
- EXPECT_TRUE(watcher.IsWatching(scoped_vector_assign.back()));
-
- EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state());
- }
- EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state());
-}
-
-class DeleteCounter {
- public:
- explicit DeleteCounter(int* deletes)
- : deletes_(deletes) {
- }
-
- ~DeleteCounter() {
- (*deletes_)++;
- }
-
- void VoidMethod0() {}
-
- private:
- int* const deletes_;
-
- DISALLOW_COPY_AND_ASSIGN(DeleteCounter);
-};
-
-template <typename T>
-ScopedVector<T> PassThru(ScopedVector<T> scoper) {
- return scoper;
-}
-
-TEST(ScopedVectorTest, Passed) {
- int deletes = 0;
- ScopedVector<DeleteCounter> deleter_vector;
- deleter_vector.push_back(new DeleteCounter(&deletes));
- EXPECT_EQ(0, deletes);
- base::Callback<ScopedVector<DeleteCounter>(void)> callback =
- base::Bind(&PassThru<DeleteCounter>, base::Passed(&deleter_vector));
- EXPECT_EQ(0, deletes);
- ScopedVector<DeleteCounter> result = callback.Run();
- EXPECT_EQ(0, deletes);
- result.clear();
- EXPECT_EQ(1, deletes);
-};
-
-TEST(ScopedVectorTest, InsertRange) {
- LifeCycleWatcher watchers[5];
-
- std::vector<LifeCycleObject*> vec;
- for(LifeCycleWatcher* it = watchers; it != watchers + arraysize(watchers);
- ++it) {
- EXPECT_EQ(LC_INITIAL, it->life_cycle_state());
- vec.push_back(it->NewLifeCycleObject());
- EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state());
- }
- // Start scope for ScopedVector.
- {
- ScopedVector<LifeCycleObject> scoped_vector;
- scoped_vector.insert(scoped_vector.end(), vec.begin() + 1, vec.begin() + 3);
- for(LifeCycleWatcher* it = watchers; it != watchers + arraysize(watchers);
- ++it)
- EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state());
- }
- for(LifeCycleWatcher* it = watchers; it != watchers + 1; ++it)
- EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state());
- for(LifeCycleWatcher* it = watchers + 1; it != watchers + 3; ++it)
- EXPECT_EQ(LC_DESTROYED, it->life_cycle_state());
- for(LifeCycleWatcher* it = watchers + 3; it != watchers + arraysize(watchers);
- ++it)
- EXPECT_EQ(LC_CONSTRUCTED, it->life_cycle_state());
-}
-
-// Assertions for push_back(unique_ptr).
-TEST(ScopedVectorTest, PushBackScopedPtr) {
- int delete_counter = 0;
- std::unique_ptr<DeleteCounter> elem(new DeleteCounter(&delete_counter));
- EXPECT_EQ(0, delete_counter);
- {
- ScopedVector<DeleteCounter> v;
- v.push_back(std::move(elem));
- EXPECT_EQ(0, delete_counter);
- }
- EXPECT_EQ(1, delete_counter);
-}
-
-} // namespace
diff --git a/base/memory/shared_memory.h b/base/memory/shared_memory.h
index 4b66cc6edd..c573ef7250 100644
--- a/base/memory/shared_memory.h
+++ b/base/memory/shared_memory.h
@@ -10,12 +10,14 @@
#include <string>
#include "base/base_export.h"
+#include "base/hash.h"
#include "base/macros.h"
#include "base/memory/shared_memory_handle.h"
#include "base/process/process_handle.h"
+#include "base/strings/string16.h"
#include "build/build_config.h"
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
#include <stdio.h>
#include <sys/types.h>
#include <semaphore.h>
@@ -37,7 +39,7 @@ struct BASE_EXPORT SharedMemoryCreateOptions {
#if defined(OS_MACOSX) && !defined(OS_IOS)
// The type of OS primitive that should back the SharedMemory object.
SharedMemoryHandle::Type type = SharedMemoryHandle::MACH;
-#else
+#elif !defined(OS_FUCHSIA)
// DEPRECATED (crbug.com/345734):
// If NULL, the object is anonymous. This pointer is owned by the caller
// and must live through the call to Create().
@@ -62,8 +64,10 @@ struct BASE_EXPORT SharedMemoryCreateOptions {
bool share_read_only = false;
};
-// Platform abstraction for shared memory. Provides a C++ wrapper
-// around the OS primitive for a memory mapped file.
+// Platform abstraction for shared memory.
+// SharedMemory consumes a SharedMemoryHandle [potentially one that it created]
+// to map a shared memory OS resource into the virtual address space of the
+// current process.
class BASE_EXPORT SharedMemory {
public:
SharedMemory();
@@ -72,15 +76,15 @@ class BASE_EXPORT SharedMemory {
// Similar to the default constructor, except that this allows for
// calling LockDeprecated() to acquire the named mutex before either Create or
// Open are called on Windows.
- explicit SharedMemory(const std::wstring& name);
+ explicit SharedMemory(const string16& name);
#endif
// Create a new SharedMemory object from an existing, open
// shared memory file.
//
// WARNING: This does not reduce the OS-level permissions on the handle; it
- // only affects how the SharedMemory will be mmapped. Use
- // ShareReadOnlyToProcess to drop permissions. TODO(jln,jyasskin): DCHECK
+ // only affects how the SharedMemory will be mmapped. Use
+ // GetReadOnlyHandle to drop permissions. TODO(jln,jyasskin): DCHECK
// that |read_only| matches the permissions of the handle.
SharedMemory(const SharedMemoryHandle& handle, bool read_only);
@@ -91,17 +95,15 @@ class BASE_EXPORT SharedMemory {
// invalid value; NULL for a HANDLE and -1 for a file descriptor)
static bool IsHandleValid(const SharedMemoryHandle& handle);
- // Returns invalid handle (see comment above for exact definition).
- static SharedMemoryHandle NULLHandle();
-
// Closes a shared memory handle.
static void CloseHandle(const SharedMemoryHandle& handle);
// Returns the maximum number of handles that can be open at once per process.
static size_t GetHandleLimit();
- // Duplicates The underlying OS primitive. Returns NULLHandle() on failure.
- // The caller is responsible for destroying the duplicated OS primitive.
+ // Duplicates The underlying OS primitive. Returns an invalid handle on
+ // failure. The caller is responsible for destroying the duplicated OS
+ // primitive.
static SharedMemoryHandle DuplicateHandle(const SharedMemoryHandle& handle);
#if defined(OS_POSIX)
@@ -109,14 +111,6 @@ class BASE_EXPORT SharedMemory {
static int GetFdFromSharedMemoryHandle(const SharedMemoryHandle& handle);
#endif
-#if defined(OS_POSIX) && !defined(OS_ANDROID)
- // Gets the size of the shared memory region referred to by |handle|.
- // Returns false on a failure to determine the size. On success, populates the
- // output variable |size|.
- static bool GetSizeFromSharedMemoryHandle(const SharedMemoryHandle& handle,
- size_t* size);
-#endif // defined(OS_POSIX) && !defined(OS_ANDROID)
-
// Creates a shared memory object as described by the options struct.
// Returns true on success and false on failure.
bool Create(const SharedMemoryCreateOptions& options);
@@ -133,7 +127,7 @@ class BASE_EXPORT SharedMemory {
return Create(options);
}
-#if !defined(OS_MACOSX) || defined(OS_IOS)
+#if (!defined(OS_MACOSX) || defined(OS_IOS)) && !defined(OS_FUCHSIA)
// DEPRECATED (crbug.com/345734):
// Creates or opens a shared memory segment based on a name.
// If open_existing is true, and the shared memory already exists,
@@ -195,11 +189,10 @@ class BASE_EXPORT SharedMemory {
// identifier is not portable.
SharedMemoryHandle handle() const;
- // Returns the underlying OS handle for this segment. The caller also gets
- // ownership of the handle. This is logically equivalent to:
- // SharedMemoryHandle dup = DuplicateHandle(handle());
- // Close();
- // return dup;
+ // Returns the underlying OS handle for this segment. The caller takes
+ // ownership of the handle and memory is unmapped. This is equivalent to
+ // duplicating the handle and then calling Unmap() and Close() on this object,
+ // without the overhead of duplicating the handle.
SharedMemoryHandle TakeHandle();
// Closes the open shared memory segment. The memory will remain mapped if
@@ -207,68 +200,19 @@ class BASE_EXPORT SharedMemory {
// It is safe to call Close repeatedly.
void Close();
- // Shares the shared memory to another process. Attempts to create a
- // platform-specific new_handle which can be used in a remote process to read
- // the shared memory file. new_handle is an output parameter to receive the
- // handle for use in the remote process.
- //
- // |*this| must have been initialized using one of the Create*() or Open()
- // methods with share_read_only=true. If it was constructed from a
- // SharedMemoryHandle, this call will CHECK-fail.
- //
- // Returns true on success, false otherwise.
- bool ShareReadOnlyToProcess(ProcessHandle process,
- SharedMemoryHandle* new_handle) {
- return ShareToProcessCommon(process, new_handle, false, SHARE_READONLY);
- }
-
- // Logically equivalent to:
- // bool ok = ShareReadOnlyToProcess(process, new_handle);
- // Close();
- // return ok;
- // Note that the memory is unmapped by calling this method, regardless of the
- // return value.
- bool GiveReadOnlyToProcess(ProcessHandle process,
- SharedMemoryHandle* new_handle) {
- return ShareToProcessCommon(process, new_handle, true, SHARE_READONLY);
- }
-
- // Shares the shared memory to another process. Attempts
- // to create a platform-specific new_handle which can be
- // used in a remote process to access the shared memory
- // file. new_handle is an output parameter to receive
- // the handle for use in the remote process.
- // Returns true on success, false otherwise.
- bool ShareToProcess(ProcessHandle process,
- SharedMemoryHandle* new_handle) {
- return ShareToProcessCommon(process, new_handle, false, SHARE_CURRENT_MODE);
- }
-
- // Logically equivalent to:
- // bool ok = ShareToProcess(process, new_handle);
- // Close();
- // return ok;
- // Note that the memory is unmapped by calling this method, regardless of the
- // return value.
- bool GiveToProcess(ProcessHandle process,
- SharedMemoryHandle* new_handle) {
- return ShareToProcessCommon(process, new_handle, true, SHARE_CURRENT_MODE);
- }
-
-#if defined(OS_POSIX) && (!defined(OS_MACOSX) || defined(OS_IOS)) && \
- !defined(OS_NACL)
- using UniqueId = std::pair<dev_t, ino_t>;
-
- struct UniqueIdHash {
- size_t operator()(const UniqueId& id) const {
- return HashInts(id.first, id.second);
- }
- };
+ // Returns a read-only handle to this shared memory region. The caller takes
+ // ownership of the handle. For POSIX handles, CHECK-fails if the region
+ // wasn't Created or Opened with share_read_only=true, which is required to
+ // make the handle read-only. When the handle is passed to the IPC subsystem,
+ // that takes ownership of the handle. As such, it's not valid to pass the
+ // sample handle to the IPC subsystem twice. Returns an invalid handle on
+ // failure.
+ SharedMemoryHandle GetReadOnlyHandle() const;
- // Returns a unique ID for this shared memory's handle. Note this function may
- // access file system and be slow.
- bool GetUniqueId(UniqueId* id) const;
-#endif
+ // Returns an ID for the mapped region. This is ID of the SharedMemoryHandle
+ // that was mapped. The ID is valid even after the SharedMemoryHandle is
+ // Closed, as long as the region is not unmapped.
+ const UnguessableToken& mapped_id() const { return mapped_id_; }
private:
#if defined(OS_POSIX) && !defined(OS_NACL) && !defined(OS_ANDROID) && \
@@ -276,43 +220,31 @@ class BASE_EXPORT SharedMemory {
bool FilePathForMemoryName(const std::string& mem_name, FilePath* path);
#endif
- enum ShareMode {
- SHARE_READONLY,
- SHARE_CURRENT_MODE,
- };
-
-#if defined(OS_MACOSX)
- bool Share(SharedMemoryHandle* new_handle, ShareMode share_mode);
-#endif
-
- bool ShareToProcessCommon(ProcessHandle process,
- SharedMemoryHandle* new_handle,
- bool close_self,
- ShareMode);
-
#if defined(OS_WIN)
// If true indicates this came from an external source so needs extra checks
// before being mapped.
- bool external_section_;
- std::wstring name_;
- win::ScopedHandle mapped_file_;
-#elif defined(OS_MACOSX) && !defined(OS_IOS)
- // The OS primitive that backs the shared memory region.
- SharedMemoryHandle shm_;
+ bool external_section_ = false;
+ string16 name_;
+#elif !defined(OS_ANDROID) && !defined(OS_FUCHSIA)
+ // If valid, points to the same memory region as shm_, but with readonly
+ // permissions.
+ SharedMemoryHandle readonly_shm_;
+#endif
+#if defined(OS_MACOSX) && !defined(OS_IOS)
// The mechanism by which the memory is mapped. Only valid if |memory_| is not
// |nullptr|.
- SharedMemoryHandle::Type mapped_memory_mechanism_;
-
- int readonly_mapped_file_;
-#elif defined(OS_POSIX)
- int mapped_file_;
- int readonly_mapped_file_;
+ SharedMemoryHandle::Type mapped_memory_mechanism_ = SharedMemoryHandle::MACH;
#endif
- size_t mapped_size_;
- void* memory_;
- bool read_only_;
- size_t requested_size_;
+
+ // The OS primitive that backs the shared memory region.
+ SharedMemoryHandle shm_;
+
+ size_t mapped_size_ = 0;
+ void* memory_ = nullptr;
+ bool read_only_ = false;
+ size_t requested_size_ = 0;
+ base::UnguessableToken mapped_id_;
DISALLOW_COPY_AND_ASSIGN(SharedMemory);
};
diff --git a/base/memory/shared_memory_android.cc b/base/memory/shared_memory_android.cc
index 6f1d9cb874..c126767c6b 100644
--- a/base/memory/shared_memory_android.cc
+++ b/base/memory/shared_memory_android.cc
@@ -18,35 +18,28 @@ namespace base {
// are closed, the memory buffer will go away.
bool SharedMemory::Create(const SharedMemoryCreateOptions& options) {
- DCHECK_EQ(-1, mapped_file_ );
+ DCHECK(!shm_.IsValid());
if (options.size > static_cast<size_t>(std::numeric_limits<int>::max()))
return false;
// "name" is just a label in ashmem. It is visible in /proc/pid/maps.
- mapped_file_ = ashmem_create_region(
- options.name_deprecated == NULL ? "" : options.name_deprecated->c_str(),
+ int fd = ashmem_create_region(
+ options.name_deprecated ? options.name_deprecated->c_str() : "",
options.size);
- if (-1 == mapped_file_) {
+ shm_ = SharedMemoryHandle::ImportHandle(fd, options.size);
+ if (!shm_.IsValid()) {
DLOG(ERROR) << "Shared memory creation failed";
return false;
}
- int err = ashmem_set_prot_region(mapped_file_,
- PROT_READ | PROT_WRITE | PROT_EXEC);
+ int flags = PROT_READ | PROT_WRITE | (options.executable ? PROT_EXEC : 0);
+ int err = ashmem_set_prot_region(shm_.GetHandle(), flags);
if (err < 0) {
DLOG(ERROR) << "Error " << err << " when setting protection of ashmem";
return false;
}
- // Android doesn't appear to have a way to drop write access on an ashmem
- // segment for a single descriptor. http://crbug.com/320865
- readonly_mapped_file_ = dup(mapped_file_);
- if (-1 == readonly_mapped_file_) {
- DPLOG(ERROR) << "dup() failed";
- return false;
- }
-
requested_size_ = options.size;
return true;
@@ -64,4 +57,19 @@ bool SharedMemory::Open(const std::string& name, bool read_only) {
return false;
}
+void SharedMemory::Close() {
+ if (shm_.IsValid()) {
+ shm_.Close();
+ shm_ = SharedMemoryHandle();
+ }
+}
+
+SharedMemoryHandle SharedMemory::GetReadOnlyHandle() const {
+ // There are no read-only Ashmem descriptors on Android.
+ // Instead, the protection mask is a property of the region itself.
+ SharedMemoryHandle handle = shm_.Duplicate();
+ handle.SetReadOnly();
+ return handle;
+}
+
} // namespace base
diff --git a/base/memory/shared_memory_handle.cc b/base/memory/shared_memory_handle.cc
new file mode 100644
index 0000000000..085bde46c2
--- /dev/null
+++ b/base/memory/shared_memory_handle.cc
@@ -0,0 +1,23 @@
+// 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_handle.h"
+
+namespace base {
+
+SharedMemoryHandle::SharedMemoryHandle(const SharedMemoryHandle& handle) =
+ default;
+
+SharedMemoryHandle& SharedMemoryHandle::operator=(
+ const SharedMemoryHandle& handle) = default;
+
+base::UnguessableToken SharedMemoryHandle::GetGUID() const {
+ return guid_;
+}
+
+size_t SharedMemoryHandle::GetSize() const {
+ return size_;
+}
+
+} // namespace base
diff --git a/base/memory/shared_memory_handle.h b/base/memory/shared_memory_handle.h
index dc33eeafa1..7367188c8b 100644
--- a/base/memory/shared_memory_handle.h
+++ b/base/memory/shared_memory_handle.h
@@ -7,11 +7,12 @@
#include <stddef.h>
+#include "base/unguessable_token.h"
#include "build/build_config.h"
#if defined(OS_WIN)
-#include <windows.h>
#include "base/process/process_handle.h"
+#include "base/win/windows_types.h"
#elif defined(OS_MACOSX) && !defined(OS_IOS)
#include <mach/mach.h>
#include "base/base_export.h"
@@ -21,20 +22,22 @@
#elif defined(OS_POSIX)
#include <sys/types.h>
#include "base/file_descriptor_posix.h"
+#elif defined(OS_FUCHSIA)
+#include <zircon/types.h>
#endif
namespace base {
-// SharedMemoryHandle is a platform specific type which represents
-// the underlying OS handle to a shared memory segment.
-#if defined(OS_POSIX) && !(defined(OS_MACOSX) && !defined(OS_IOS))
-typedef FileDescriptor SharedMemoryHandle;
-#elif defined(OS_WIN)
+// SharedMemoryHandle is the smallest possible IPC-transportable "reference" to
+// a shared memory OS resource. A "reference" can be consumed exactly once [by
+// base::SharedMemory] to map the shared memory OS resource into the virtual
+// address space of the current process.
+// TODO(erikchen): This class should have strong ownership semantics to prevent
+// leaks of the underlying OS resource. https://crbug.com/640840.
class BASE_EXPORT SharedMemoryHandle {
public:
// The default constructor returns an invalid SharedMemoryHandle.
SharedMemoryHandle();
- SharedMemoryHandle(HANDLE h, base::ProcessId pid);
// Standard copy constructor. The new instance shares the underlying OS
// primitives.
@@ -44,46 +47,60 @@ class BASE_EXPORT SharedMemoryHandle {
// OS primitives.
SharedMemoryHandle& operator=(const SharedMemoryHandle& handle);
- // Comparison operators.
- bool operator==(const SharedMemoryHandle& handle) const;
- bool operator!=(const SharedMemoryHandle& handle) const;
-
- // Closes the underlying OS resources.
+ // Closes the underlying OS resource.
+ // The fact that this method needs to be "const" is an artifact of the
+ // original interface for base::SharedMemory::CloseHandle.
+ // TODO(erikchen): This doesn't clear the underlying reference, which seems
+ // like a bug, but is how this class has always worked. Fix this:
+ // https://crbug.com/716072.
void Close() const;
- // Whether the underlying OS primitive is valid.
- bool IsValid() const;
-
- // Whether |pid_| is the same as the current process's id.
- bool BelongsToCurrentProcess() const;
-
- // Whether handle_ needs to be duplicated into the destination process when
- // an instance of this class is passed over a Chrome IPC channel.
- bool NeedsBrokering() const;
-
+ // Whether ownership of the underlying OS resource is implicitly passed to
+ // the IPC subsystem during serialization.
void SetOwnershipPassesToIPC(bool ownership_passes);
bool OwnershipPassesToIPC() const;
- HANDLE GetHandle() const;
- base::ProcessId GetPID() const;
+ // Whether the underlying OS resource is valid.
+ bool IsValid() const;
- private:
- HANDLE handle_;
+ // Duplicates the underlying OS resource. Using the return value as a
+ // parameter to an IPC message will cause the IPC subsystem to consume the OS
+ // resource.
+ SharedMemoryHandle Duplicate() const;
- // The process in which |handle_| is valid and can be used. If |handle_| is
- // invalid, this will be kNullProcessId.
- base::ProcessId pid_;
+ // Uniques identifies the shared memory region that the underlying OS resource
+ // points to. Multiple SharedMemoryHandles that point to the same shared
+ // memory region will have the same GUID. Preserved across IPC.
+ base::UnguessableToken GetGUID() const;
- // Whether passing this object as a parameter to an IPC message passes
- // ownership of |handle_| to the IPC stack. This is meant to mimic the
- // behavior of the |auto_close| parameter of FileDescriptor. This member only
- // affects attachment-brokered SharedMemoryHandles.
- // Defaults to |false|.
- bool ownership_passes_to_ipc_;
-};
-#else
-class BASE_EXPORT SharedMemoryHandle {
- public:
+ // Returns the size of the memory region that SharedMemoryHandle points to.
+ size_t GetSize() const;
+
+#if defined(OS_WIN)
+ // Takes implicit ownership of |h|.
+ // |guid| uniquely identifies the shared memory region pointed to by the
+ // underlying OS resource. If the HANDLE is associated with another
+ // SharedMemoryHandle, the caller must pass the |guid| of that
+ // SharedMemoryHandle. Otherwise, the caller should generate a new
+ // UnguessableToken.
+ // Passing the wrong |size| has no immediate consequence, but may cause errors
+ // when trying to map the SharedMemoryHandle at a later point in time.
+ SharedMemoryHandle(HANDLE h, size_t size, const base::UnguessableToken& guid);
+ HANDLE GetHandle() const;
+#elif defined(OS_FUCHSIA)
+ // Takes implicit ownership of |h|.
+ // |guid| uniquely identifies the shared memory region pointed to by the
+ // underlying OS resource. If the zx_handle_t is associated with another
+ // SharedMemoryHandle, the caller must pass the |guid| of that
+ // SharedMemoryHandle. Otherwise, the caller should generate a new
+ // UnguessableToken.
+ // Passing the wrong |size| has no immediate consequence, but may cause errors
+ // when trying to map the SharedMemoryHandle at a later point in time.
+ SharedMemoryHandle(zx_handle_t h,
+ size_t size,
+ const base::UnguessableToken& guid);
+ zx_handle_t GetHandle() const;
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
enum Type {
// The SharedMemoryHandle is backed by a POSIX fd.
POSIX,
@@ -91,74 +108,100 @@ class BASE_EXPORT SharedMemoryHandle {
MACH,
};
- // The default constructor returns an invalid SharedMemoryHandle.
- SharedMemoryHandle();
-
- // Constructs a SharedMemoryHandle backed by the components of a
- // FileDescriptor. The newly created instance has the same ownership semantics
- // as base::FileDescriptor. This typically means that the SharedMemoryHandle
- // takes ownership of the |fd| if |auto_close| is true. Unfortunately, it's
- // common for existing code to make shallow copies of SharedMemoryHandle, and
- // the one that is finally passed into a base::SharedMemory is the one that
- // "consumes" the fd.
- explicit SharedMemoryHandle(const base::FileDescriptor& file_descriptor);
-
// Makes a Mach-based SharedMemoryHandle of the given size. On error,
// subsequent calls to IsValid() return false.
- explicit SharedMemoryHandle(mach_vm_size_t size);
+ // Passing the wrong |size| has no immediate consequence, but may cause errors
+ // when trying to map the SharedMemoryHandle at a later point in time.
+ SharedMemoryHandle(mach_vm_size_t size, const base::UnguessableToken& guid);
// Makes a Mach-based SharedMemoryHandle from |memory_object|, a named entry
- // in the task with process id |pid|. The memory region has size |size|.
+ // in the current task. The memory region has size |size|.
+ // Passing the wrong |size| has no immediate consequence, but may cause errors
+ // when trying to map the SharedMemoryHandle at a later point in time.
SharedMemoryHandle(mach_port_t memory_object,
mach_vm_size_t size,
- base::ProcessId pid);
-
- // Standard copy constructor. The new instance shares the underlying OS
- // primitives.
- SharedMemoryHandle(const SharedMemoryHandle& handle);
-
- // Standard assignment operator. The updated instance shares the underlying
- // OS primitives.
- SharedMemoryHandle& operator=(const SharedMemoryHandle& handle);
-
- // Duplicates the underlying OS resources.
- SharedMemoryHandle Duplicate() const;
-
- // Comparison operators.
- bool operator==(const SharedMemoryHandle& handle) const;
- bool operator!=(const SharedMemoryHandle& handle) const;
-
- // Whether the underlying OS primitive is valid. Once the SharedMemoryHandle
- // is backed by a valid OS primitive, it becomes immutable.
- bool IsValid() const;
+ const base::UnguessableToken& guid);
// Exposed so that the SharedMemoryHandle can be transported between
// processes.
mach_port_t GetMemoryObject() const;
- // Returns false on a failure to determine the size. On success, populates the
- // output variable |size|.
- bool GetSize(size_t* size) const;
-
// The SharedMemoryHandle must be valid.
// Returns whether the SharedMemoryHandle was successfully mapped into memory.
// On success, |memory| is an output variable that contains the start of the
// mapped memory.
bool MapAt(off_t offset, size_t bytes, void** memory, bool read_only);
+#elif defined(OS_POSIX)
+ // Creates a SharedMemoryHandle from an |fd| supplied from an external
+ // service.
+ // Passing the wrong |size| has no immediate consequence, but may cause errors
+ // when trying to map the SharedMemoryHandle at a later point in time.
+ static SharedMemoryHandle ImportHandle(int fd, size_t size);
+
+ // Returns the underlying OS resource.
+ int GetHandle() const;
+
+ // Invalidates [but doesn't close] the underlying OS resource. This will leak
+ // unless the caller is careful.
+ int Release();
+#endif
- // Closes the underlying OS primitive.
- void Close() const;
+#if defined(OS_ANDROID) || defined(__ANDROID__)
+ // Marks the current file descriptor as read-only, for the purpose of
+ // mapping. This is independent of the region's read-only status.
+ void SetReadOnly() { read_only_ = true; }
- void SetOwnershipPassesToIPC(bool ownership_passes);
- bool OwnershipPassesToIPC() const;
+ // Returns true iff the descriptor is to be used for read-only
+ // mappings.
+ bool IsReadOnly() const { return read_only_; }
+
+ // Returns true iff the corresponding region is read-only.
+ bool IsRegionReadOnly() const;
+
+ // Try to set the region read-only. This will fail any future attempt
+ // at read-write mapping.
+ bool SetRegionReadOnly() const;
+#endif
+
+#if defined(OS_POSIX)
+ // Constructs a SharedMemoryHandle backed by a FileDescriptor. The newly
+ // created instance has the same ownership semantics as base::FileDescriptor.
+ // This typically means that the SharedMemoryHandle takes ownership of the
+ // |fd| if |auto_close| is true. Unfortunately, it's common for existing code
+ // to make shallow copies of SharedMemoryHandle, and the one that is finally
+ // passed into a base::SharedMemory is the one that "consumes" the fd.
+ //
+ // |guid| uniquely identifies the shared memory region pointed to by the
+ // underlying OS resource. If |file_descriptor| is associated with another
+ // SharedMemoryHandle, the caller must pass the |guid| of that
+ // SharedMemoryHandle. Otherwise, the caller should generate a new
+ // UnguessableToken.
+ // Passing the wrong |size| has no immediate consequence, but may cause errors
+ // when trying to map the SharedMemoryHandle at a later point in time.
+ SharedMemoryHandle(const base::FileDescriptor& file_descriptor,
+ size_t size,
+ const base::UnguessableToken& guid);
+#endif
private:
- friend class SharedMemory;
+#if defined(OS_WIN)
+ HANDLE handle_ = nullptr;
- // Shared code between copy constructor and operator=.
- void CopyRelevantData(const SharedMemoryHandle& handle);
+ // Whether passing this object as a parameter to an IPC message passes
+ // ownership of |handle_| to the IPC stack. This is meant to mimic the
+ // behavior of the |auto_close| parameter of FileDescriptor. This member only
+ // affects attachment-brokered SharedMemoryHandles.
+ // Defaults to |false|.
+ bool ownership_passes_to_ipc_ = false;
+#elif defined(OS_FUCHSIA)
+ zx_handle_t handle_ = ZX_HANDLE_INVALID;
+ bool ownership_passes_to_ipc_ = false;
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ friend class SharedMemory;
+ friend bool CheckReadOnlySharedMemoryHandleForTesting(
+ SharedMemoryHandle handle);
- Type type_;
+ Type type_ = MACH;
// Each instance of a SharedMemoryHandle is backed either by a POSIX fd or a
// mach port. |type_| determines the backing member.
@@ -166,26 +209,30 @@ class BASE_EXPORT SharedMemoryHandle {
FileDescriptor file_descriptor_;
struct {
- mach_port_t memory_object_;
-
- // The size of the shared memory region when |type_| is MACH. Only
- // relevant if |memory_object_| is not |MACH_PORT_NULL|.
- mach_vm_size_t size_;
-
- // The pid of the process in which |memory_object_| is usable. Only
- // relevant if |memory_object_| is not |MACH_PORT_NULL|.
- base::ProcessId pid_;
+ mach_port_t memory_object_ = MACH_PORT_NULL;
// Whether passing this object as a parameter to an IPC message passes
// ownership of |memory_object_| to the IPC stack. This is meant to mimic
// the behavior of the |auto_close| parameter of FileDescriptor.
// Defaults to |false|.
- bool ownership_passes_to_ipc_;
+ bool ownership_passes_to_ipc_ = false;
};
};
-};
+#elif defined(OS_ANDROID) || defined(__ANDROID__)
+ friend class SharedMemory;
+
+ FileDescriptor file_descriptor_;
+ bool read_only_ = false;
+#elif defined(OS_POSIX)
+ FileDescriptor file_descriptor_;
#endif
+ base::UnguessableToken guid_;
+
+ // The size of the region referenced by the SharedMemoryHandle.
+ size_t size_ = 0;
+};
+
} // namespace base
#endif // BASE_MEMORY_SHARED_MEMORY_HANDLE_H_
diff --git a/base/memory/shared_memory_handle_android.cc b/base/memory/shared_memory_handle_android.cc
new file mode 100644
index 0000000000..1b615353bf
--- /dev/null
+++ b/base/memory/shared_memory_handle_android.cc
@@ -0,0 +1,115 @@
+// 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_handle.h"
+
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/posix/unix_domain_socket.h"
+#include "base/unguessable_token.h"
+#include "third_party/ashmem/ashmem.h"
+
+namespace base {
+
+static int GetAshmemRegionProtectionMask(int fd) {
+ int prot = ashmem_get_prot_region(fd);
+ if (prot < 0) {
+ DPLOG(ERROR) << "ashmem_get_prot_region";
+ return -1;
+ }
+ return prot;
+}
+
+SharedMemoryHandle::SharedMemoryHandle() = default;
+
+SharedMemoryHandle::SharedMemoryHandle(
+ const base::FileDescriptor& file_descriptor,
+ size_t size,
+ const base::UnguessableToken& guid)
+ : guid_(guid), size_(size) {
+ DCHECK_GE(file_descriptor.fd, 0);
+ file_descriptor_ = file_descriptor;
+}
+
+// static
+SharedMemoryHandle SharedMemoryHandle::ImportHandle(int fd, size_t size) {
+ SharedMemoryHandle handle;
+ handle.file_descriptor_.fd = fd;
+ handle.file_descriptor_.auto_close = false;
+ handle.guid_ = UnguessableToken::Create();
+ handle.size_ = size;
+ return handle;
+}
+
+int SharedMemoryHandle::GetHandle() const {
+ DCHECK(IsValid());
+ return file_descriptor_.fd;
+}
+
+bool SharedMemoryHandle::IsValid() const {
+ return file_descriptor_.fd >= 0;
+}
+
+void SharedMemoryHandle::Close() const {
+ DCHECK(IsValid());
+ if (IGNORE_EINTR(close(file_descriptor_.fd)) < 0)
+ PLOG(ERROR) << "close";
+}
+
+int SharedMemoryHandle::Release() {
+ int old_fd = file_descriptor_.fd;
+ file_descriptor_.fd = -1;
+ return old_fd;
+}
+
+SharedMemoryHandle SharedMemoryHandle::Duplicate() const {
+ DCHECK(IsValid());
+ SharedMemoryHandle result;
+ int duped_handle = HANDLE_EINTR(dup(file_descriptor_.fd));
+ if (duped_handle >= 0) {
+ result = SharedMemoryHandle(FileDescriptor(duped_handle, true), GetSize(),
+ GetGUID());
+ if (IsReadOnly())
+ result.SetReadOnly();
+ }
+ return result;
+}
+
+void SharedMemoryHandle::SetOwnershipPassesToIPC(bool ownership_passes) {
+ file_descriptor_.auto_close = ownership_passes;
+}
+
+bool SharedMemoryHandle::OwnershipPassesToIPC() const {
+ return file_descriptor_.auto_close;
+}
+
+bool SharedMemoryHandle::IsRegionReadOnly() const {
+ int prot = GetAshmemRegionProtectionMask(file_descriptor_.fd);
+ return (prot >= 0 && (prot & PROT_WRITE) == 0);
+}
+
+bool SharedMemoryHandle::SetRegionReadOnly() const {
+ int fd = file_descriptor_.fd;
+ int prot = GetAshmemRegionProtectionMask(fd);
+ if (prot < 0)
+ return false;
+
+ if ((prot & PROT_WRITE) == 0) {
+ // Region is already read-only.
+ return true;
+ }
+
+ prot &= ~PROT_WRITE;
+ int ret = ashmem_set_prot_region(fd, prot);
+ if (ret != 0) {
+ DPLOG(ERROR) << "ashmem_set_prot_region";
+ return false;
+ }
+ return true;
+}
+
+} // namespace base
diff --git a/base/memory/shared_memory_handle_posix.cc b/base/memory/shared_memory_handle_posix.cc
new file mode 100644
index 0000000000..09dfb9ca8b
--- /dev/null
+++ b/base/memory/shared_memory_handle_posix.cc
@@ -0,0 +1,71 @@
+// 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_handle.h"
+
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/unguessable_token.h"
+
+namespace base {
+
+SharedMemoryHandle::SharedMemoryHandle() = default;
+
+SharedMemoryHandle::SharedMemoryHandle(
+ const base::FileDescriptor& file_descriptor,
+ size_t size,
+ const base::UnguessableToken& guid)
+ : file_descriptor_(file_descriptor), guid_(guid), size_(size) {}
+
+// static
+SharedMemoryHandle SharedMemoryHandle::ImportHandle(int fd, size_t size) {
+ SharedMemoryHandle handle;
+ handle.file_descriptor_.fd = fd;
+ handle.file_descriptor_.auto_close = false;
+ handle.guid_ = UnguessableToken::Create();
+ handle.size_ = size;
+ return handle;
+}
+
+int SharedMemoryHandle::GetHandle() const {
+ return file_descriptor_.fd;
+}
+
+bool SharedMemoryHandle::IsValid() const {
+ return file_descriptor_.fd >= 0;
+}
+
+void SharedMemoryHandle::Close() const {
+ if (IGNORE_EINTR(close(file_descriptor_.fd)) < 0)
+ PLOG(ERROR) << "close";
+}
+
+int SharedMemoryHandle::Release() {
+ int old_fd = file_descriptor_.fd;
+ file_descriptor_.fd = -1;
+ return old_fd;
+}
+
+SharedMemoryHandle SharedMemoryHandle::Duplicate() const {
+ if (!IsValid())
+ return SharedMemoryHandle();
+
+ int duped_handle = HANDLE_EINTR(dup(file_descriptor_.fd));
+ if (duped_handle < 0)
+ return SharedMemoryHandle();
+ return SharedMemoryHandle(FileDescriptor(duped_handle, true), GetSize(),
+ GetGUID());
+}
+
+void SharedMemoryHandle::SetOwnershipPassesToIPC(bool ownership_passes) {
+ file_descriptor_.auto_close = ownership_passes;
+}
+
+bool SharedMemoryHandle::OwnershipPassesToIPC() const {
+ return file_descriptor_.auto_close;
+}
+
+} // namespace base
diff --git a/base/memory/shared_memory_helper.cc b/base/memory/shared_memory_helper.cc
index 7fbfb7afad..f98b734fb7 100644
--- a/base/memory/shared_memory_helper.cc
+++ b/base/memory/shared_memory_helper.cc
@@ -4,6 +4,13 @@
#include "base/memory/shared_memory_helper.h"
+#if defined(OS_CHROMEOS)
+#include <sys/resource.h>
+#include <sys/time.h>
+
+#include "base/debug/alias.h"
+#endif // defined(OS_CHROMEOS)
+
#include "base/threading/thread_restrictions.h"
namespace base {
@@ -23,13 +30,13 @@ using ScopedPathUnlinker =
#if !defined(OS_ANDROID)
bool CreateAnonymousSharedMemory(const SharedMemoryCreateOptions& options,
- ScopedFILE* fp,
+ ScopedFD* fd,
ScopedFD* readonly_fd,
FilePath* path) {
-#if !(defined(OS_MACOSX) && !defined(OS_IOS))
+#if defined(OS_LINUX)
// It doesn't make sense to have a open-existing private piece of shmem
DCHECK(!options.open_existing_deprecated);
-#endif // !(defined(OS_MACOSX) && !defined(OS_IOS)
+#endif // defined(OS_LINUX)
// Q: Why not use the shm_open() etc. APIs?
// A: Because they're limited to 4mb on OS X. FFFFFFFUUUUUUUUUUU
FilePath directory;
@@ -37,9 +44,9 @@ bool CreateAnonymousSharedMemory(const SharedMemoryCreateOptions& options,
if (!GetShmemTempDir(options.executable, &directory))
return false;
- fp->reset(base::CreateAndOpenTemporaryFileInDir(directory, path));
+ fd->reset(base::CreateAndOpenFdForTemporaryFileInDir(directory, path));
- if (!*fp)
+ if (!fd->is_valid())
return false;
// Deleting the file prevents anyone else from mapping it in (making it
@@ -48,22 +55,24 @@ bool CreateAnonymousSharedMemory(const SharedMemoryCreateOptions& options,
path_unlinker.reset(path);
if (options.share_read_only) {
- // Also open as readonly so that we can ShareReadOnlyToProcess.
+ // Also open as readonly so that we can GetReadOnlyHandle.
readonly_fd->reset(HANDLE_EINTR(open(path->value().c_str(), O_RDONLY)));
if (!readonly_fd->is_valid()) {
DPLOG(ERROR) << "open(\"" << path->value() << "\", O_RDONLY) failed";
- fp->reset();
+ fd->reset();
return false;
}
}
return true;
}
-bool PrepareMapFile(ScopedFILE fp, ScopedFD readonly_fd, int* mapped_file,
+bool PrepareMapFile(ScopedFD fd,
+ ScopedFD readonly_fd,
+ int* mapped_file,
int* readonly_mapped_file) {
DCHECK_EQ(-1, *mapped_file);
DCHECK_EQ(-1, *readonly_mapped_file);
- if (fp == NULL)
+ if (!fd.is_valid())
return false;
// This function theoretically can block on the disk, but realistically
@@ -73,7 +82,7 @@ bool PrepareMapFile(ScopedFILE fp, ScopedFD readonly_fd, int* mapped_file,
if (readonly_fd.is_valid()) {
struct stat st = {};
- if (fstat(fileno(fp.get()), &st))
+ if (fstat(fd.get(), &st))
NOTREACHED();
struct stat readonly_st = {};
@@ -85,9 +94,59 @@ bool PrepareMapFile(ScopedFILE fp, ScopedFD readonly_fd, int* mapped_file,
}
}
- *mapped_file = HANDLE_EINTR(dup(fileno(fp.get())));
+ *mapped_file = HANDLE_EINTR(dup(fd.get()));
if (*mapped_file == -1) {
NOTREACHED() << "Call to dup failed, errno=" << errno;
+
+#if defined(OS_CHROMEOS)
+ if (errno == EMFILE) {
+ // We're out of file descriptors and are probably about to crash somewhere
+ // else in Chrome anyway. Let's collect what FD information we can and
+ // crash.
+ // Added for debugging crbug.com/733718
+ int original_fd_limit = 16384;
+ struct rlimit rlim;
+ if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) {
+ original_fd_limit = rlim.rlim_cur;
+ if (rlim.rlim_max > rlim.rlim_cur) {
+ // Increase fd limit so breakpad has a chance to write a minidump.
+ rlim.rlim_cur = rlim.rlim_max;
+ if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) {
+ PLOG(ERROR) << "setrlimit() failed";
+ }
+ }
+ } else {
+ PLOG(ERROR) << "getrlimit() failed";
+ }
+
+ const char kFileDataMarker[] = "FDATA";
+ char buf[PATH_MAX];
+ char fd_path[PATH_MAX];
+ char crash_buffer[32 * 1024] = {0};
+ char* crash_ptr = crash_buffer;
+ base::debug::Alias(crash_buffer);
+
+ // Put a marker at the start of our data so we can confirm where it
+ // begins.
+ crash_ptr = strncpy(crash_ptr, kFileDataMarker, strlen(kFileDataMarker));
+ for (int i = original_fd_limit; i >= 0; --i) {
+ memset(buf, 0, arraysize(buf));
+ memset(fd_path, 0, arraysize(fd_path));
+ snprintf(fd_path, arraysize(fd_path) - 1, "/proc/self/fd/%d", i);
+ ssize_t count = readlink(fd_path, buf, arraysize(buf) - 1);
+ if (count < 0) {
+ PLOG(ERROR) << "readlink failed for: " << fd_path;
+ continue;
+ }
+
+ if (crash_ptr + count + 1 < crash_buffer + arraysize(crash_buffer)) {
+ crash_ptr = strncpy(crash_ptr, buf, count + 1);
+ }
+ LOG(ERROR) << i << ": " << buf;
+ }
+ LOG(FATAL) << "Logged for file descriptor exhaustion, crashing now";
+ }
+#endif // defined(OS_CHROMEOS)
}
*readonly_mapped_file = readonly_fd.release();
diff --git a/base/memory/shared_memory_helper.h b/base/memory/shared_memory_helper.h
index b515828c08..2c24f869f5 100644
--- a/base/memory/shared_memory_helper.h
+++ b/base/memory/shared_memory_helper.h
@@ -6,27 +6,30 @@
#define BASE_MEMORY_SHARED_MEMORY_HELPER_H_
#include "base/memory/shared_memory.h"
+#include "build/build_config.h"
#include <fcntl.h>
namespace base {
#if !defined(OS_ANDROID)
-// Makes a temporary file, fdopens it, and then unlinks it. |fp| is populated
-// with the fdopened FILE. |readonly_fd| is populated with the opened fd if
+// Makes a temporary file, fdopens it, and then unlinks it. |fd| is populated
+// with the opened fd. |readonly_fd| is populated with the opened fd if
// options.share_read_only is true. |path| is populated with the location of
// the file before it was unlinked.
// Returns false if there's an unhandled failure.
bool CreateAnonymousSharedMemory(const SharedMemoryCreateOptions& options,
- ScopedFILE* fp,
+ ScopedFD* fd,
ScopedFD* readonly_fd,
FilePath* path);
// Takes the outputs of CreateAnonymousSharedMemory and maps them properly to
// |mapped_file| or |readonly_mapped_file|, depending on which one is populated.
-bool PrepareMapFile(ScopedFILE fp, ScopedFD readonly_fd, int* mapped_file,
+bool PrepareMapFile(ScopedFD fd,
+ ScopedFD readonly_fd,
+ int* mapped_file,
int* readonly_mapped_file);
-#endif
+#endif // !defined(OS_ANDROID)
} // namespace base
diff --git a/base/memory/shared_memory_mapping.cc b/base/memory/shared_memory_mapping.cc
new file mode 100644
index 0000000000..2b42928076
--- /dev/null
+++ b/base/memory/shared_memory_mapping.cc
@@ -0,0 +1,117 @@
+// 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.
+
+#include "base/memory/shared_memory_mapping.h"
+
+#include <utility>
+
+#include "base/logging.h"
+// Unsupported in libchrome
+// #include "base/memory/shared_memory_tracker.h"
+#include "base/unguessable_token.h"
+#include "build/build_config.h"
+
+#if defined(OS_POSIX)
+#include <sys/mman.h>
+#endif
+
+#if defined(OS_WIN)
+#include <aclapi.h>
+#endif
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include <mach/mach_vm.h>
+#include "base/mac/mach_logging.h"
+#endif
+
+#if defined(OS_FUCHSIA)
+#include <zircon/process.h>
+#include <zircon/status.h>
+#include <zircon/syscalls.h>
+#endif
+
+namespace base {
+
+SharedMemoryMapping::SharedMemoryMapping() = default;
+
+SharedMemoryMapping::SharedMemoryMapping(SharedMemoryMapping&& mapping)
+ : memory_(mapping.memory_),
+ size_(mapping.size_),
+ mapped_size_(mapping.mapped_size_),
+ guid_(mapping.guid_) {
+ mapping.memory_ = nullptr;
+}
+
+SharedMemoryMapping& SharedMemoryMapping::operator=(
+ SharedMemoryMapping&& mapping) {
+ Unmap();
+ memory_ = mapping.memory_;
+ size_ = mapping.size_;
+ mapped_size_ = mapping.mapped_size_;
+ guid_ = mapping.guid_;
+ mapping.memory_ = nullptr;
+ return *this;
+}
+
+SharedMemoryMapping::~SharedMemoryMapping() {
+ Unmap();
+}
+
+SharedMemoryMapping::SharedMemoryMapping(void* memory,
+ size_t size,
+ size_t mapped_size,
+ const UnguessableToken& guid)
+ : memory_(memory), size_(size), mapped_size_(mapped_size), guid_(guid) {
+ // Unsupported in libchrome.
+ // SharedMemoryTracker::GetInstance()->IncrementMemoryUsage(*this);
+}
+
+void SharedMemoryMapping::Unmap() {
+ if (!IsValid())
+ return;
+ // Unsupported in libchrome.
+ // SharedMemoryTracker::GetInstance()->DecrementMemoryUsage(*this);
+#if defined(OS_WIN)
+ if (!UnmapViewOfFile(memory_))
+ DPLOG(ERROR) << "UnmapViewOfFile";
+#elif defined(OS_FUCHSIA)
+ uintptr_t addr = reinterpret_cast<uintptr_t>(memory_);
+ zx_status_t status = zx_vmar_unmap(zx_vmar_root_self(), addr, size_);
+ DLOG_IF(ERROR, status != ZX_OK)
+ << "zx_vmar_unmap failed: " << zx_status_get_string(status);
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ kern_return_t kr = mach_vm_deallocate(
+ mach_task_self(), reinterpret_cast<mach_vm_address_t>(memory_), size_);
+ MACH_DLOG_IF(ERROR, kr != KERN_SUCCESS, kr) << "mach_vm_deallocate";
+#else
+ if (munmap(memory_, size_) < 0)
+ DPLOG(ERROR) << "munmap";
+#endif
+}
+
+ReadOnlySharedMemoryMapping::ReadOnlySharedMemoryMapping() = default;
+ReadOnlySharedMemoryMapping::ReadOnlySharedMemoryMapping(
+ ReadOnlySharedMemoryMapping&&) = default;
+ReadOnlySharedMemoryMapping& ReadOnlySharedMemoryMapping::operator=(
+ ReadOnlySharedMemoryMapping&&) = default;
+ReadOnlySharedMemoryMapping::ReadOnlySharedMemoryMapping(
+ void* address,
+ size_t size,
+ size_t mapped_size,
+ const UnguessableToken& guid)
+ : SharedMemoryMapping(address, size, mapped_size, guid) {}
+
+WritableSharedMemoryMapping::WritableSharedMemoryMapping() = default;
+WritableSharedMemoryMapping::WritableSharedMemoryMapping(
+ WritableSharedMemoryMapping&&) = default;
+WritableSharedMemoryMapping& WritableSharedMemoryMapping::operator=(
+ WritableSharedMemoryMapping&&) = default;
+WritableSharedMemoryMapping::WritableSharedMemoryMapping(
+ void* address,
+ size_t size,
+ size_t mapped_size,
+ const UnguessableToken& guid)
+ : SharedMemoryMapping(address, size, mapped_size, guid) {}
+
+} // namespace base
diff --git a/base/memory/shared_memory_mapping.h b/base/memory/shared_memory_mapping.h
new file mode 100644
index 0000000000..ace4c15320
--- /dev/null
+++ b/base/memory/shared_memory_mapping.h
@@ -0,0 +1,144 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_SHARED_MEMORY_MAPPING_H_
+#define BASE_MEMORY_SHARED_MEMORY_MAPPING_H_
+
+#include <cstddef>
+
+#include "base/macros.h"
+#include "base/unguessable_token.h"
+
+namespace base {
+
+namespace subtle {
+class PlatformSharedMemoryRegion;
+} // namespace subtle
+
+// Base class for scoped handles to a shared memory mapping created from a
+// shared memory region. Created shared memory mappings remain valid even if the
+// creator region is transferred or destroyed.
+//
+// Each mapping has an UnguessableToken that identifies the shared memory region
+// it was created from. This is used for memory metrics, to avoid overcounting
+// shared memory.
+class BASE_EXPORT SharedMemoryMapping {
+ public:
+ // Default constructor initializes an invalid instance.
+ SharedMemoryMapping();
+
+ // Move operations are allowed.
+ SharedMemoryMapping(SharedMemoryMapping&& mapping);
+ SharedMemoryMapping& operator=(SharedMemoryMapping&& mapping);
+
+ // Unmaps the region if the mapping is valid.
+ virtual ~SharedMemoryMapping();
+
+ // Returns true iff the mapping is valid. False means there is no
+ // corresponding area of memory.
+ bool IsValid() const { return memory_ != nullptr; }
+
+ // Returns the logical size of the mapping in bytes. This is precisely the
+ // size requested by whoever created the mapping, and it is always less than
+ // or equal to |mapped_size()|. This is undefined for invalid instances.
+ size_t size() const {
+ DCHECK(IsValid());
+ return size_;
+ }
+
+ // Returns the actual size of the mapping in bytes. This is always at least
+ // as large as |size()| but may be larger due to platform mapping alignment
+ // constraints. This is undefined for invalid instances.
+ size_t mapped_size() const {
+ DCHECK(IsValid());
+ return mapped_size_;
+ }
+
+ // Returns 128-bit GUID of the region this mapping belongs to.
+ const UnguessableToken& guid() const {
+ DCHECK(IsValid());
+ return guid_;
+ }
+
+ protected:
+ SharedMemoryMapping(void* address,
+ size_t size,
+ size_t mapped_size,
+ const UnguessableToken& guid);
+ void* raw_memory_ptr() const { return memory_; }
+
+ private:
+ friend class SharedMemoryTracker;
+
+ void Unmap();
+
+ void* memory_ = nullptr;
+ size_t size_ = 0;
+ size_t mapped_size_ = 0;
+ UnguessableToken guid_;
+
+ DISALLOW_COPY_AND_ASSIGN(SharedMemoryMapping);
+};
+
+// Class modeling a read-only mapping of a shared memory region into the
+// current process' address space. This is created by ReadOnlySharedMemoryRegion
+// instances.
+class BASE_EXPORT ReadOnlySharedMemoryMapping : public SharedMemoryMapping {
+ public:
+ // Default constructor initializes an invalid instance.
+ ReadOnlySharedMemoryMapping();
+
+ // Move operations are allowed.
+ ReadOnlySharedMemoryMapping(ReadOnlySharedMemoryMapping&&);
+ ReadOnlySharedMemoryMapping& operator=(ReadOnlySharedMemoryMapping&&);
+
+ // Returns the base address of the mapping. This is read-only memory. This is
+ // page-aligned. This is nullptr for invalid instances.
+ const void* memory() const { return raw_memory_ptr(); }
+
+ private:
+ friend class ReadOnlySharedMemoryRegion;
+ ReadOnlySharedMemoryMapping(void* address,
+ size_t size,
+ size_t mapped_size,
+ const UnguessableToken& guid);
+
+ DISALLOW_COPY_AND_ASSIGN(ReadOnlySharedMemoryMapping);
+};
+
+// Class modeling a writable mapping of a shared memory region into the
+// current process' address space. This is created by *SharedMemoryRegion
+// instances.
+class BASE_EXPORT WritableSharedMemoryMapping : public SharedMemoryMapping {
+ public:
+ // Default constructor initializes an invalid instance.
+ WritableSharedMemoryMapping();
+
+ // Move operations are allowed.
+ WritableSharedMemoryMapping(WritableSharedMemoryMapping&&);
+ WritableSharedMemoryMapping& operator=(WritableSharedMemoryMapping&&);
+
+ // Returns the base address of the mapping. This is writable memory. This is
+ // page-aligned. This is nullptr for invalid instances.
+ void* memory() const { return raw_memory_ptr(); }
+
+ private:
+ friend WritableSharedMemoryMapping MapAtForTesting(
+ subtle::PlatformSharedMemoryRegion* region,
+ off_t offset,
+ size_t size);
+ friend class ReadOnlySharedMemoryRegion;
+ friend class WritableSharedMemoryRegion;
+ friend class UnsafeSharedMemoryRegion;
+ WritableSharedMemoryMapping(void* address,
+ size_t size,
+ size_t mapped_size,
+ const UnguessableToken& guid);
+
+ DISALLOW_COPY_AND_ASSIGN(WritableSharedMemoryMapping);
+};
+
+} // namespace base
+
+#endif // BASE_MEMORY_SHARED_MEMORY_MAPPING_H_
diff --git a/base/memory/shared_memory_posix.cc b/base/memory/shared_memory_posix.cc
index 3443cd9b4a..aa718957cf 100644
--- a/base/memory/shared_memory_posix.cc
+++ b/base/memory/shared_memory_posix.cc
@@ -14,6 +14,7 @@
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
+#include "base/macros.h"
#include "base/memory/shared_memory_helper.h"
// Unsupported in libchrome.
// #include "base/memory/shared_memory_tracker.h"
@@ -24,6 +25,7 @@
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_restrictions.h"
#include "base/trace_event/trace_event.h"
+#include "base/unguessable_token.h"
#include "build/build_config.h"
#if defined(OS_ANDROID)
@@ -33,25 +35,16 @@
#include "third_party/ashmem/ashmem.h"
#endif
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#error "MacOS uses shared_memory_mac.cc"
+#endif
+
namespace base {
-SharedMemory::SharedMemory()
- : mapped_file_(-1),
- readonly_mapped_file_(-1),
- mapped_size_(0),
- memory_(NULL),
- read_only_(false),
- requested_size_(0) {
-}
+SharedMemory::SharedMemory() = default;
SharedMemory::SharedMemory(const SharedMemoryHandle& handle, bool read_only)
- : mapped_file_(handle.fd),
- readonly_mapped_file_(-1),
- mapped_size_(0),
- memory_(NULL),
- read_only_(read_only),
- requested_size_(0) {
-}
+ : shm_(handle), read_only_(read_only) {}
SharedMemory::~SharedMemory() {
Unmap();
@@ -60,39 +53,30 @@ SharedMemory::~SharedMemory() {
// static
bool SharedMemory::IsHandleValid(const SharedMemoryHandle& handle) {
- return handle.fd >= 0;
-}
-
-// static
-SharedMemoryHandle SharedMemory::NULLHandle() {
- return SharedMemoryHandle();
+ return handle.IsValid();
}
// static
void SharedMemory::CloseHandle(const SharedMemoryHandle& handle) {
- DCHECK_GE(handle.fd, 0);
- if (IGNORE_EINTR(close(handle.fd)) < 0)
- DPLOG(ERROR) << "close";
+ DCHECK(handle.IsValid());
+ handle.Close();
}
// static
size_t SharedMemory::GetHandleLimit() {
- return base::GetMaxFds();
+ return GetMaxFds();
}
// static
SharedMemoryHandle SharedMemory::DuplicateHandle(
const SharedMemoryHandle& handle) {
- int duped_handle = HANDLE_EINTR(dup(handle.fd));
- if (duped_handle < 0)
- return base::SharedMemory::NULLHandle();
- return base::FileDescriptor(duped_handle, true);
+ return handle.Duplicate();
}
// static
int SharedMemory::GetFdFromSharedMemoryHandle(
const SharedMemoryHandle& handle) {
- return handle.fd;
+ return handle.GetHandle();
}
bool SharedMemory::CreateAndMapAnonymous(size_t size) {
@@ -100,18 +84,6 @@ bool SharedMemory::CreateAndMapAnonymous(size_t size) {
}
#if !defined(OS_ANDROID) && !defined(__ANDROID__)
-// static
-bool SharedMemory::GetSizeFromSharedMemoryHandle(
- const SharedMemoryHandle& handle,
- size_t* size) {
- struct stat st;
- if (fstat(handle.fd, &st) != 0)
- return false;
- if (st.st_size < 0)
- return false;
- *size = st.st_size;
- return true;
-}
// Chromium mostly only uses the unique/private shmem as specified by
// "name == L"". The exception is in the StatsTable.
@@ -120,7 +92,7 @@ bool SharedMemory::GetSizeFromSharedMemoryHandle(
// In case we want to delete it later, it may be useful to save the value
// of mem_filename after FilePathForMemoryName().
bool SharedMemory::Create(const SharedMemoryCreateOptions& options) {
- DCHECK_EQ(-1, mapped_file_);
+ DCHECK(!shm_.IsValid());
if (options.size == 0) return false;
if (options.size > static_cast<size_t>(std::numeric_limits<int>::max()))
@@ -129,16 +101,15 @@ bool SharedMemory::Create(const SharedMemoryCreateOptions& options) {
// This function theoretically can block on the disk, but realistically
// the temporary files we create will just go into the buffer cache
// and be deleted before they ever make it out to disk.
- base::ThreadRestrictions::ScopedAllowIO allow_io;
+ ThreadRestrictions::ScopedAllowIO allow_io;
- ScopedFILE fp;
bool fix_size = true;
+ ScopedFD fd;
ScopedFD readonly_fd;
-
FilePath path;
- if (options.name_deprecated == NULL || options.name_deprecated->empty()) {
+ if (!options.name_deprecated || options.name_deprecated->empty()) {
bool result =
- CreateAnonymousSharedMemory(options, &fp, &readonly_fd, &path);
+ CreateAnonymousSharedMemory(options, &fd, &readonly_fd, &path);
if (!result)
return false;
} else {
@@ -150,9 +121,9 @@ bool SharedMemory::Create(const SharedMemoryCreateOptions& options) {
const mode_t kOwnerOnly = S_IRUSR | S_IWUSR;
// First, try to create the file.
- int fd = HANDLE_EINTR(
- open(path.value().c_str(), O_RDWR | O_CREAT | O_EXCL, kOwnerOnly));
- if (fd == -1 && options.open_existing_deprecated) {
+ fd.reset(HANDLE_EINTR(
+ open(path.value().c_str(), O_RDWR | O_CREAT | O_EXCL, kOwnerOnly)));
+ if (!fd.is_valid() && options.open_existing_deprecated) {
// If this doesn't work, try and open an existing file in append mode.
// Opening an existing file in a world writable directory has two main
// security implications:
@@ -160,21 +131,26 @@ bool SharedMemory::Create(const SharedMemoryCreateOptions& options) {
// the file is checked below.
// - Attackers could plant a symbolic link so that an unexpected file
// is opened, so O_NOFOLLOW is passed to open().
- fd = HANDLE_EINTR(
- open(path.value().c_str(), O_RDWR | O_APPEND | O_NOFOLLOW));
-
+#if !defined(OS_AIX)
+ fd.reset(HANDLE_EINTR(
+ open(path.value().c_str(), O_RDWR | O_APPEND | O_NOFOLLOW)));
+#else
+ // AIX has no 64-bit support for open flags such as -
+ // O_CLOEXEC, O_NOFOLLOW and O_TTY_INIT.
+ fd.reset(HANDLE_EINTR(open(path.value().c_str(), O_RDWR | O_APPEND)));
+#endif
// Check that the current user owns the file.
// If uid != euid, then a more complex permission model is used and this
// API is not appropriate.
const uid_t real_uid = getuid();
const uid_t effective_uid = geteuid();
struct stat sb;
- if (fd >= 0 &&
- (fstat(fd, &sb) != 0 || sb.st_uid != real_uid ||
+ if (fd.is_valid() &&
+ (fstat(fd.get(), &sb) != 0 || sb.st_uid != real_uid ||
sb.st_uid != effective_uid)) {
LOG(ERROR) <<
"Invalid owner when opening existing shared memory file.";
- close(fd);
+ close(fd.get());
return false;
}
@@ -183,33 +159,28 @@ bool SharedMemory::Create(const SharedMemoryCreateOptions& options) {
}
if (options.share_read_only) {
- // Also open as readonly so that we can ShareReadOnlyToProcess.
+ // Also open as readonly so that we can GetReadOnlyHandle.
readonly_fd.reset(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)));
if (!readonly_fd.is_valid()) {
DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed";
- close(fd);
- fd = -1;
+ close(fd.get());
return false;
}
}
- if (fd >= 0) {
- // "a+" is always appropriate: if it's a new file, a+ is similar to w+.
- fp.reset(fdopen(fd, "a+"));
- }
}
- if (fp && fix_size) {
+ if (fd.is_valid() && fix_size) {
// Get current size.
struct stat stat;
- if (fstat(fileno(fp.get()), &stat) != 0)
+ if (fstat(fd.get(), &stat) != 0)
return false;
const size_t current_size = stat.st_size;
if (current_size != options.size) {
- if (HANDLE_EINTR(ftruncate(fileno(fp.get()), options.size)) != 0)
+ if (HANDLE_EINTR(ftruncate(fd.get(), options.size)) != 0)
return false;
}
requested_size_ = options.size;
}
- if (fp == NULL) {
+ if (!fd.is_valid()) {
PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed";
FilePath dir = path.DirName();
if (access(dir.value().c_str(), W_OK | X_OK) < 0) {
@@ -222,8 +193,17 @@ bool SharedMemory::Create(const SharedMemoryCreateOptions& options) {
return false;
}
- return PrepareMapFile(std::move(fp), std::move(readonly_fd), &mapped_file_,
- &readonly_mapped_file_);
+ int mapped_file = -1;
+ int readonly_mapped_file = -1;
+
+ bool result = PrepareMapFile(std::move(fd), std::move(readonly_fd),
+ &mapped_file, &readonly_mapped_file);
+ shm_ = SharedMemoryHandle(FileDescriptor(mapped_file, false), options.size,
+ UnguessableToken::Create());
+ readonly_shm_ =
+ SharedMemoryHandle(FileDescriptor(readonly_mapped_file, false),
+ options.size, shm_.GetGUID());
+ return result;
}
// Our current implementation of shmem is with mmap()ing of files.
@@ -235,7 +215,7 @@ bool SharedMemory::Delete(const std::string& name) {
return false;
if (PathExists(path))
- return base::DeleteFile(path, false);
+ return DeleteFile(path, false);
// Doesn't exist, so success.
return true;
@@ -248,20 +228,37 @@ bool SharedMemory::Open(const std::string& name, bool read_only) {
read_only_ = read_only;
- const char *mode = read_only ? "r" : "r+";
- ScopedFILE fp(base::OpenFile(path, mode));
+ int mode = read_only ? O_RDONLY : O_RDWR;
+ ScopedFD fd(HANDLE_EINTR(open(path.value().c_str(), mode)));
ScopedFD readonly_fd(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)));
if (!readonly_fd.is_valid()) {
DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed";
return false;
}
- return PrepareMapFile(std::move(fp), std::move(readonly_fd), &mapped_file_,
- &readonly_mapped_file_);
+ int mapped_file = -1;
+ int readonly_mapped_file = -1;
+ bool result = PrepareMapFile(std::move(fd), std::move(readonly_fd),
+ &mapped_file, &readonly_mapped_file);
+ // This form of sharing shared memory is deprecated. https://crbug.com/345734.
+ // However, we can't get rid of it without a significant refactor because its
+ // used to communicate between two versions of the same service process, very
+ // early in the life cycle.
+ // Technically, we should also pass the GUID from the original shared memory
+ // region. We don't do that - this means that we will overcount this memory,
+ // which thankfully isn't relevant since Chrome only communicates with a
+ // single version of the service process.
+ // We pass the size |0|, which is a dummy size and wrong, but otherwise
+ // harmless.
+ shm_ = SharedMemoryHandle(FileDescriptor(mapped_file, false), 0u,
+ UnguessableToken::Create());
+ readonly_shm_ = SharedMemoryHandle(
+ FileDescriptor(readonly_mapped_file, false), 0, shm_.GetGUID());
+ return result;
}
#endif // !defined(OS_ANDROID) && !defined(__ANDROID__)
bool SharedMemory::MapAt(off_t offset, size_t bytes) {
- if (mapped_file_ == -1)
+ if (!shm_.IsValid())
return false;
if (bytes > static_cast<size_t>(std::numeric_limits<int>::max()))
@@ -275,69 +272,85 @@ bool SharedMemory::MapAt(off_t offset, size_t bytes) {
// ashmem-determined size.
if (bytes == 0) {
DCHECK_EQ(0, offset);
- int ashmem_bytes = ashmem_get_size_region(mapped_file_);
+ int ashmem_bytes = ashmem_get_size_region(shm_.GetHandle());
if (ashmem_bytes < 0)
return false;
bytes = ashmem_bytes;
}
+
+ // Sanity check. This shall catch invalid uses of the SharedMemory APIs
+ // but will not protect against direct mmap() attempts.
+ // if (shm_.IsReadOnly()) {
+ // // Use a DCHECK() to call writable mappings with read-only descriptors
+ // // in debug builds immediately. Return an error for release builds
+ // // or during unit-testing (assuming a ScopedLogAssertHandler was installed).
+ // DCHECK(read_only_)
+ // << "Trying to map a region writable with a read-only descriptor.";
+ // if (!read_only_) {
+ // return false;
+ // }
+ // if (!shm_.SetRegionReadOnly()) { // Ensure the region is read-only.
+ // return false;
+ // }
+ // }
#endif
- memory_ = mmap(NULL, bytes, PROT_READ | (read_only_ ? 0 : PROT_WRITE),
- MAP_SHARED, mapped_file_, offset);
+ memory_ = mmap(nullptr, bytes, PROT_READ | (read_only_ ? 0 : PROT_WRITE),
+ MAP_SHARED, shm_.GetHandle(), offset);
- bool mmap_succeeded = memory_ != (void*)-1 && memory_ != NULL;
+ bool mmap_succeeded = memory_ && memory_ != reinterpret_cast<void*>(-1);
if (mmap_succeeded) {
mapped_size_ = bytes;
+ mapped_id_ = shm_.GetGUID();
DCHECK_EQ(0U,
reinterpret_cast<uintptr_t>(memory_) &
(SharedMemory::MAP_MINIMUM_ALIGNMENT - 1));
// Unsupported in libchrome.
// SharedMemoryTracker::GetInstance()->IncrementMemoryUsage(*this);
} else {
- memory_ = NULL;
+ memory_ = nullptr;
}
return mmap_succeeded;
}
bool SharedMemory::Unmap() {
- if (memory_ == NULL)
+ if (!memory_)
return false;
- munmap(memory_, mapped_size_);
// Unsupported in libchrome.
// SharedMemoryTracker::GetInstance()->DecrementMemoryUsage(*this);
- memory_ = NULL;
+ munmap(memory_, mapped_size_);
+ memory_ = nullptr;
mapped_size_ = 0;
+ mapped_id_ = UnguessableToken();
return true;
}
SharedMemoryHandle SharedMemory::handle() const {
- return FileDescriptor(mapped_file_, false);
+ return shm_;
}
SharedMemoryHandle SharedMemory::TakeHandle() {
- FileDescriptor handle(mapped_file_, true);
- mapped_file_ = -1;
- memory_ = nullptr;
- mapped_size_ = 0;
- return handle;
+ SharedMemoryHandle handle_copy = shm_;
+ handle_copy.SetOwnershipPassesToIPC(true);
+ Unmap();
+ shm_ = SharedMemoryHandle();
+ return handle_copy;
}
+#if !defined(OS_ANDROID) && !defined(__ANDROID__)
void SharedMemory::Close() {
- if (mapped_file_ > 0) {
- if (IGNORE_EINTR(close(mapped_file_)) < 0)
- PLOG(ERROR) << "close";
- mapped_file_ = -1;
+ if (shm_.IsValid()) {
+ shm_.Close();
+ shm_ = SharedMemoryHandle();
}
- if (readonly_mapped_file_ > 0) {
- if (IGNORE_EINTR(close(readonly_mapped_file_)) < 0)
- PLOG(ERROR) << "close";
- readonly_mapped_file_ = -1;
+ if (readonly_shm_.IsValid()) {
+ readonly_shm_.Close();
+ readonly_shm_ = SharedMemoryHandle();
}
}
-#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.
@@ -353,69 +366,19 @@ bool SharedMemory::FilePathForMemoryName(const std::string& mem_name,
return false;
#if defined(GOOGLE_CHROME_BUILD)
- std::string name_base = std::string("com.google.Chrome");
+ static const char kShmem[] = "com.google.Chrome.shmem.";
#else
- std::string name_base = std::string("org.chromium.Chromium");
+ static const char kShmem[] = "org.chromium.Chromium.shmem.";
#endif
- *path = temp_dir.AppendASCII(name_base + ".shmem." + mem_name);
+ CR_DEFINE_STATIC_LOCAL(const std::string, name_base, (kShmem));
+ *path = temp_dir.AppendASCII(name_base + mem_name);
return true;
}
-#endif // !defined(OS_ANDROID) && !defined(__ANDROID__)
-bool SharedMemory::ShareToProcessCommon(ProcessHandle process,
- SharedMemoryHandle* new_handle,
- bool close_self,
- ShareMode share_mode) {
- int handle_to_dup = -1;
- switch(share_mode) {
- case SHARE_CURRENT_MODE:
- handle_to_dup = mapped_file_;
- break;
- case SHARE_READONLY:
- // We could imagine re-opening the file from /dev/fd, but that can't make
- // it readonly on Mac: https://codereview.chromium.org/27265002/#msg10
- CHECK_GE(readonly_mapped_file_, 0);
- handle_to_dup = readonly_mapped_file_;
- break;
- }
-
- const int new_fd = HANDLE_EINTR(dup(handle_to_dup));
- if (new_fd < 0) {
- if (close_self) {
- Unmap();
- Close();
- }
- DPLOG(ERROR) << "dup() failed.";
- return false;
- }
-
- new_handle->fd = new_fd;
- new_handle->auto_close = true;
-
- if (close_self) {
- Unmap();
- Close();
- }
-
- return true;
-}
-
-bool SharedMemory::GetUniqueId(SharedMemory::UniqueId* id) const {
- // This function is called just after mmap. fstat is a system call that might
- // cause I/O. It's safe to call fstat here because mmap for shared memory is
- // called in two cases:
- // 1) To handle file-mapped memory
- // 2) To handle annonymous shared memory
- // In 1), I/O is already permitted. In 2), the backend is on page cache and
- // fstat doesn't cause I/O access to the disk. See the discussion at
- // crbug.com/604726#c41.
- base::ThreadRestrictions::ScopedAllowIO allow_io;
- struct stat file_stat;
- if (HANDLE_EINTR(::fstat(static_cast<int>(handle().fd), &file_stat)) != 0)
- return false;
- id->first = file_stat.st_dev;
- id->second = file_stat.st_ino;
- return true;
+SharedMemoryHandle SharedMemory::GetReadOnlyHandle() const {
+ CHECK(readonly_shm_.IsValid());
+ return readonly_shm_.Duplicate();
}
+#endif // !defined(OS_ANDROID) && !defined(__ANDROID__)
} // namespace base
diff --git a/base/memory/shared_memory_region_unittest.cc b/base/memory/shared_memory_region_unittest.cc
new file mode 100644
index 0000000000..78ac630898
--- /dev/null
+++ b/base/memory/shared_memory_region_unittest.cc
@@ -0,0 +1,280 @@
+// 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.
+
+#include <utility>
+
+#include "base/memory/platform_shared_memory_region.h"
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "base/memory/writable_shared_memory_region.h"
+#include "base/sys_info.h"
+#include "base/test/test_shared_memory_util.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+const size_t kRegionSize = 1024;
+
+bool IsMemoryFilledWithByte(const void* memory, size_t size, char byte) {
+ const char* start_ptr = static_cast<const char*>(memory);
+ const char* end_ptr = start_ptr + size;
+ for (const char* ptr = start_ptr; ptr < end_ptr; ++ptr) {
+ if (*ptr != byte)
+ return false;
+ }
+
+ return true;
+}
+
+template <typename SharedMemoryRegionType>
+class SharedMemoryRegionTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ std::tie(region_, rw_mapping_) =
+ CreateMappedRegion<SharedMemoryRegionType>(kRegionSize);
+ ASSERT_TRUE(region_.IsValid());
+ ASSERT_TRUE(rw_mapping_.IsValid());
+ memset(rw_mapping_.memory(), 'G', kRegionSize);
+ EXPECT_TRUE(IsMemoryFilledWithByte(rw_mapping_.memory(), kRegionSize, 'G'));
+ }
+
+ protected:
+ SharedMemoryRegionType region_;
+ WritableSharedMemoryMapping rw_mapping_;
+};
+
+typedef ::testing::Types<WritableSharedMemoryRegion,
+ UnsafeSharedMemoryRegion,
+ ReadOnlySharedMemoryRegion>
+ AllRegionTypes;
+TYPED_TEST_CASE(SharedMemoryRegionTest, AllRegionTypes);
+
+TYPED_TEST(SharedMemoryRegionTest, NonValidRegion) {
+ TypeParam region;
+ EXPECT_FALSE(region.IsValid());
+ // We shouldn't crash on Map but should return an invalid mapping.
+ typename TypeParam::MappingType mapping = region.Map();
+ EXPECT_FALSE(mapping.IsValid());
+}
+
+TYPED_TEST(SharedMemoryRegionTest, MoveRegion) {
+ TypeParam moved_region = std::move(this->region_);
+ EXPECT_FALSE(this->region_.IsValid());
+ ASSERT_TRUE(moved_region.IsValid());
+
+ // Check that moved region maps correctly.
+ typename TypeParam::MappingType mapping = moved_region.Map();
+ ASSERT_TRUE(mapping.IsValid());
+ EXPECT_NE(this->rw_mapping_.memory(), mapping.memory());
+ EXPECT_EQ(memcmp(this->rw_mapping_.memory(), mapping.memory(), kRegionSize),
+ 0);
+
+ // Verify that the second mapping reflects changes in the first.
+ memset(this->rw_mapping_.memory(), '#', kRegionSize);
+ EXPECT_EQ(memcmp(this->rw_mapping_.memory(), mapping.memory(), kRegionSize),
+ 0);
+}
+
+TYPED_TEST(SharedMemoryRegionTest, MappingValidAfterClose) {
+ // Check the mapping is still valid after the region is closed.
+ this->region_ = TypeParam();
+ EXPECT_FALSE(this->region_.IsValid());
+ ASSERT_TRUE(this->rw_mapping_.IsValid());
+ EXPECT_TRUE(
+ IsMemoryFilledWithByte(this->rw_mapping_.memory(), kRegionSize, 'G'));
+}
+
+TYPED_TEST(SharedMemoryRegionTest, MapTwice) {
+ // The second mapping is either writable or read-only.
+ typename TypeParam::MappingType mapping = this->region_.Map();
+ ASSERT_TRUE(mapping.IsValid());
+ EXPECT_NE(this->rw_mapping_.memory(), mapping.memory());
+ EXPECT_EQ(memcmp(this->rw_mapping_.memory(), mapping.memory(), kRegionSize),
+ 0);
+
+ // Verify that the second mapping reflects changes in the first.
+ memset(this->rw_mapping_.memory(), '#', kRegionSize);
+ EXPECT_EQ(memcmp(this->rw_mapping_.memory(), mapping.memory(), kRegionSize),
+ 0);
+
+ // Close the region and unmap the first memory segment, verify the second
+ // still has the right data.
+ this->region_ = TypeParam();
+ this->rw_mapping_ = WritableSharedMemoryMapping();
+ EXPECT_TRUE(IsMemoryFilledWithByte(mapping.memory(), kRegionSize, '#'));
+}
+
+TYPED_TEST(SharedMemoryRegionTest, MapUnmapMap) {
+ this->rw_mapping_ = WritableSharedMemoryMapping();
+
+ typename TypeParam::MappingType mapping = this->region_.Map();
+ ASSERT_TRUE(mapping.IsValid());
+ EXPECT_TRUE(IsMemoryFilledWithByte(mapping.memory(), kRegionSize, 'G'));
+}
+
+TYPED_TEST(SharedMemoryRegionTest, SerializeAndDeserialize) {
+ subtle::PlatformSharedMemoryRegion platform_region =
+ TypeParam::TakeHandleForSerialization(std::move(this->region_));
+ EXPECT_EQ(platform_region.GetGUID(), this->rw_mapping_.guid());
+ TypeParam region = TypeParam::Deserialize(std::move(platform_region));
+ EXPECT_TRUE(region.IsValid());
+ EXPECT_FALSE(this->region_.IsValid());
+ typename TypeParam::MappingType mapping = region.Map();
+ ASSERT_TRUE(mapping.IsValid());
+ EXPECT_TRUE(IsMemoryFilledWithByte(mapping.memory(), kRegionSize, 'G'));
+
+ // Verify that the second mapping reflects changes in the first.
+ memset(this->rw_mapping_.memory(), '#', kRegionSize);
+ EXPECT_EQ(memcmp(this->rw_mapping_.memory(), mapping.memory(), kRegionSize),
+ 0);
+}
+
+// Map() will return addresses which are aligned to the platform page size, this
+// varies from platform to platform though. Since we'd like to advertise a
+// minimum alignment that callers can count on, test for it here.
+TYPED_TEST(SharedMemoryRegionTest, MapMinimumAlignment) {
+ EXPECT_EQ(0U,
+ reinterpret_cast<uintptr_t>(this->rw_mapping_.memory()) &
+ (subtle::PlatformSharedMemoryRegion::kMapMinimumAlignment - 1));
+}
+
+TYPED_TEST(SharedMemoryRegionTest, MapSize) {
+ EXPECT_EQ(this->rw_mapping_.size(), kRegionSize);
+ EXPECT_GE(this->rw_mapping_.mapped_size(), kRegionSize);
+}
+
+TYPED_TEST(SharedMemoryRegionTest, MapGranularity) {
+ EXPECT_LT(this->rw_mapping_.mapped_size(),
+ kRegionSize + SysInfo::VMAllocationGranularity());
+}
+
+TYPED_TEST(SharedMemoryRegionTest, MapAt) {
+ const size_t kPageSize = SysInfo::VMAllocationGranularity();
+ ASSERT_TRUE(kPageSize >= sizeof(uint32_t));
+ ASSERT_EQ(kPageSize % sizeof(uint32_t), 0U);
+ const size_t kDataSize = kPageSize * 2;
+ const size_t kCount = kDataSize / sizeof(uint32_t);
+
+ TypeParam region;
+ WritableSharedMemoryMapping rw_mapping;
+ std::tie(region, rw_mapping) = CreateMappedRegion<TypeParam>(kDataSize);
+ ASSERT_TRUE(region.IsValid());
+ ASSERT_TRUE(rw_mapping.IsValid());
+ uint32_t* ptr = static_cast<uint32_t*>(rw_mapping.memory());
+
+ for (size_t i = 0; i < kCount; ++i)
+ ptr[i] = i;
+
+ rw_mapping = WritableSharedMemoryMapping();
+ off_t bytes_offset = kPageSize;
+ typename TypeParam::MappingType mapping =
+ region.MapAt(bytes_offset, kDataSize - bytes_offset);
+ ASSERT_TRUE(mapping.IsValid());
+
+ off_t int_offset = bytes_offset / sizeof(uint32_t);
+ const uint32_t* ptr2 = static_cast<const uint32_t*>(mapping.memory());
+ for (size_t i = int_offset; i < kCount; ++i) {
+ EXPECT_EQ(ptr2[i - int_offset], i);
+ }
+}
+
+TYPED_TEST(SharedMemoryRegionTest, MapAtNotAlignedOffsetFails) {
+ const size_t kDataSize = SysInfo::VMAllocationGranularity();
+
+ TypeParam region;
+ WritableSharedMemoryMapping rw_mapping;
+ std::tie(region, rw_mapping) = CreateMappedRegion<TypeParam>(kDataSize);
+ ASSERT_TRUE(region.IsValid());
+ ASSERT_TRUE(rw_mapping.IsValid());
+ off_t offset = kDataSize / 2;
+ typename TypeParam::MappingType mapping =
+ region.MapAt(offset, kDataSize - offset);
+ EXPECT_FALSE(mapping.IsValid());
+}
+
+TYPED_TEST(SharedMemoryRegionTest, MapMoreBytesThanRegionSizeFails) {
+ size_t region_real_size = this->region_.GetSize();
+ typename TypeParam::MappingType mapping =
+ this->region_.MapAt(0, region_real_size + 1);
+ EXPECT_FALSE(mapping.IsValid());
+}
+
+template <typename DuplicatableSharedMemoryRegion>
+class DuplicatableSharedMemoryRegionTest
+ : public SharedMemoryRegionTest<DuplicatableSharedMemoryRegion> {};
+
+typedef ::testing::Types<UnsafeSharedMemoryRegion, ReadOnlySharedMemoryRegion>
+ DuplicatableRegionTypes;
+TYPED_TEST_CASE(DuplicatableSharedMemoryRegionTest, DuplicatableRegionTypes);
+
+TYPED_TEST(DuplicatableSharedMemoryRegionTest, Duplicate) {
+ TypeParam dup_region = this->region_.Duplicate();
+ EXPECT_EQ(this->region_.GetGUID(), dup_region.GetGUID());
+ typename TypeParam::MappingType mapping = dup_region.Map();
+ ASSERT_TRUE(mapping.IsValid());
+ EXPECT_NE(this->rw_mapping_.memory(), mapping.memory());
+ EXPECT_EQ(this->rw_mapping_.guid(), mapping.guid());
+ EXPECT_TRUE(IsMemoryFilledWithByte(mapping.memory(), kRegionSize, 'G'));
+}
+
+class ReadOnlySharedMemoryRegionTest : public ::testing::Test {
+ public:
+ ReadOnlySharedMemoryRegion GetInitiallyReadOnlyRegion(size_t size) {
+ MappedReadOnlyRegion mapped_region =
+ ReadOnlySharedMemoryRegion::Create(size);
+ ReadOnlySharedMemoryRegion region = std::move(mapped_region.region);
+ return region;
+ }
+
+ ReadOnlySharedMemoryRegion GetConvertedToReadOnlyRegion(size_t size) {
+ WritableSharedMemoryRegion region =
+ WritableSharedMemoryRegion::Create(kRegionSize);
+ ReadOnlySharedMemoryRegion ro_region =
+ WritableSharedMemoryRegion::ConvertToReadOnly(std::move(region));
+ return ro_region;
+ }
+};
+
+TEST_F(ReadOnlySharedMemoryRegionTest,
+ InitiallyReadOnlyRegionCannotBeMappedAsWritable) {
+ ReadOnlySharedMemoryRegion region = GetInitiallyReadOnlyRegion(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+
+ EXPECT_TRUE(CheckReadOnlyPlatformSharedMemoryRegionForTesting(
+ ReadOnlySharedMemoryRegion::TakeHandleForSerialization(
+ std::move(region))));
+}
+
+TEST_F(ReadOnlySharedMemoryRegionTest,
+ ConvertedToReadOnlyRegionCannotBeMappedAsWritable) {
+ ReadOnlySharedMemoryRegion region = GetConvertedToReadOnlyRegion(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+
+ EXPECT_TRUE(CheckReadOnlyPlatformSharedMemoryRegionForTesting(
+ ReadOnlySharedMemoryRegion::TakeHandleForSerialization(
+ std::move(region))));
+}
+
+TEST_F(ReadOnlySharedMemoryRegionTest,
+ InitiallyReadOnlyRegionProducedMappingWriteDeathTest) {
+ ReadOnlySharedMemoryRegion region = GetInitiallyReadOnlyRegion(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ ReadOnlySharedMemoryMapping mapping = region.Map();
+ ASSERT_TRUE(mapping.IsValid());
+ void* memory_ptr = const_cast<void*>(mapping.memory());
+ EXPECT_DEATH_IF_SUPPORTED(memset(memory_ptr, 'G', kRegionSize), "");
+}
+
+TEST_F(ReadOnlySharedMemoryRegionTest,
+ ConvertedToReadOnlyRegionProducedMappingWriteDeathTest) {
+ ReadOnlySharedMemoryRegion region = GetConvertedToReadOnlyRegion(kRegionSize);
+ ASSERT_TRUE(region.IsValid());
+ ReadOnlySharedMemoryMapping mapping = region.Map();
+ ASSERT_TRUE(mapping.IsValid());
+ void* memory_ptr = const_cast<void*>(mapping.memory());
+ EXPECT_DEATH_IF_SUPPORTED(memset(memory_ptr, 'G', kRegionSize), "");
+}
+
+} // namespace base
diff --git a/base/memory/shared_memory_unittest.cc b/base/memory/shared_memory_unittest.cc
index d87fad01d3..dd76a86a1c 100644
--- a/base/memory/shared_memory_unittest.cc
+++ b/base/memory/shared_memory_unittest.cc
@@ -10,19 +10,30 @@
#include <memory>
#include "base/atomicops.h"
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/shared_memory_handle.h"
#include "base/process/kill.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
#include "base/sys_info.h"
#include "base/test/multiprocess_test.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
+#include "base/unguessable_token.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
+#if defined(OS_ANDROID)
+#include "base/callback.h"
+#endif
+
#if defined(OS_POSIX)
#include <errno.h>
#include <fcntl.h>
@@ -32,22 +43,31 @@
#include <unistd.h>
#endif
+#if defined(OS_LINUX)
+#include <sys/syscall.h>
+#endif
+
#if defined(OS_WIN)
#include "base/win/scoped_handle.h"
#endif
+#if defined(OS_FUCHSIA)
+#include <lib/zx/vmar.h>
+#include <lib/zx/vmo.h>
+#endif
+
namespace base {
namespace {
-#if !defined(OS_MACOSX)
+#if !defined(OS_MACOSX) && !defined(OS_FUCHSIA)
// Each thread will open the shared memory. Each thread will take a different 4
// byte int pointer, and keep changing it, with some small pauses in between.
// Verify that each thread's value in the shared memory is always correct.
class MultipleThreadMain : public PlatformThread::Delegate {
public:
explicit MultipleThreadMain(int16_t id) : id_(id) {}
- ~MultipleThreadMain() override {}
+ ~MultipleThreadMain() override = default;
static void CleanUp() {
SharedMemory memory;
@@ -86,14 +106,38 @@ class MultipleThreadMain : public PlatformThread::Delegate {
const char MultipleThreadMain::s_test_name_[] =
"SharedMemoryOpenThreadTest";
-#endif // !defined(OS_MACOSX)
+#endif // !defined(OS_MACOSX) && !defined(OS_FUCHSIA)
+
+enum class Mode {
+ Default,
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+ DisableDevShm = 1,
+#endif
+};
+
+class SharedMemoryTest : public ::testing::TestWithParam<Mode> {
+ public:
+ void SetUp() override {
+ switch (GetParam()) {
+ case Mode::Default:
+ break;
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+ case Mode::DisableDevShm:
+ CommandLine* cmdline = CommandLine::ForCurrentProcess();
+ cmdline->AppendSwitch(switches::kDisableDevShmUsage);
+ break;
+#endif // defined(OS_LINUX) && !defined(OS_CHROMEOS)
+ }
+ }
+};
} // namespace
-// Android/Mac doesn't support SharedMemory::Open/Delete/
+// Android/Mac/Fuchsia doesn't support SharedMemory::Open/Delete/
// CreateNamedDeprecated(openExisting=true)
-#if !defined(OS_ANDROID) && !defined(OS_MACOSX)
-TEST(SharedMemoryTest, OpenClose) {
+#if !defined(OS_ANDROID) && !defined(OS_MACOSX) && !defined(OS_FUCHSIA)
+
+TEST_P(SharedMemoryTest, OpenClose) {
const uint32_t kDataSize = 1024;
std::string test_name = "SharedMemoryOpenCloseTest";
@@ -118,8 +162,8 @@ TEST(SharedMemoryTest, OpenClose) {
EXPECT_NE(memory1.memory(), memory2.memory()); // Compare the pointers.
// Make sure we don't segfault. (it actually happened!)
- ASSERT_NE(memory1.memory(), static_cast<void*>(NULL));
- ASSERT_NE(memory2.memory(), static_cast<void*>(NULL));
+ ASSERT_NE(memory1.memory(), static_cast<void*>(nullptr));
+ ASSERT_NE(memory2.memory(), static_cast<void*>(nullptr));
// Write data to the first memory segment, verify contents of second.
memset(memory1.memory(), '1', kDataSize);
@@ -141,7 +185,7 @@ TEST(SharedMemoryTest, OpenClose) {
EXPECT_TRUE(rv);
}
-TEST(SharedMemoryTest, OpenExclusive) {
+TEST_P(SharedMemoryTest, OpenExclusive) {
const uint32_t kDataSize = 1024;
const uint32_t kDataSize2 = 2048;
std::ostringstream test_name_stream;
@@ -206,22 +250,22 @@ TEST(SharedMemoryTest, OpenExclusive) {
rv = memory1.Delete(test_name);
EXPECT_TRUE(rv);
}
-#endif // !defined(OS_ANDROID) && !defined(OS_MACOSX)
+#endif // !defined(OS_ANDROID) && !defined(OS_MACOSX) && !defined(OS_FUCHSIA)
// Check that memory is still mapped after its closed.
-TEST(SharedMemoryTest, CloseNoUnmap) {
+TEST_P(SharedMemoryTest, CloseNoUnmap) {
const size_t kDataSize = 4096;
SharedMemory memory;
ASSERT_TRUE(memory.CreateAndMapAnonymous(kDataSize));
char* ptr = static_cast<char*>(memory.memory());
- ASSERT_NE(ptr, static_cast<void*>(NULL));
+ ASSERT_NE(ptr, static_cast<void*>(nullptr));
memset(ptr, 'G', kDataSize);
memory.Close();
EXPECT_EQ(ptr, memory.memory());
- EXPECT_EQ(SharedMemory::NULLHandle(), memory.handle());
+ EXPECT_TRUE(!memory.handle().IsValid());
for (size_t i = 0; i < kDataSize; i++) {
EXPECT_EQ('G', ptr[i]);
@@ -231,10 +275,10 @@ TEST(SharedMemoryTest, CloseNoUnmap) {
EXPECT_EQ(nullptr, memory.memory());
}
-#if !defined(OS_MACOSX)
+#if !defined(OS_MACOSX) && !defined(OS_FUCHSIA)
// Create a set of N threads to each open a shared memory segment and write to
// it. Verify that they are always reading/writing consistent data.
-TEST(SharedMemoryTest, MultipleThreads) {
+TEST_P(SharedMemoryTest, MultipleThreads) {
const int kNumThreads = 5;
MultipleThreadMain::CleanUp();
@@ -275,7 +319,7 @@ TEST(SharedMemoryTest, MultipleThreads) {
// Allocate private (unique) shared memory with an empty string for a
// name. Make sure several of them don't point to the same thing as
// we might expect if the names are equal.
-TEST(SharedMemoryTest, AnonymousPrivate) {
+TEST_P(SharedMemoryTest, AnonymousPrivate) {
int i, j;
int count = 4;
bool rv;
@@ -316,7 +360,7 @@ TEST(SharedMemoryTest, AnonymousPrivate) {
}
}
-TEST(SharedMemoryTest, ShareReadOnly) {
+TEST_P(SharedMemoryTest, GetReadOnlyHandle) {
StringPiece contents = "Hello World";
SharedMemory writable_shmem;
@@ -332,9 +376,10 @@ TEST(SharedMemoryTest, ShareReadOnly) {
memcpy(writable_shmem.memory(), contents.data(), contents.size());
EXPECT_TRUE(writable_shmem.Unmap());
- SharedMemoryHandle readonly_handle;
- ASSERT_TRUE(writable_shmem.ShareReadOnlyToProcess(GetCurrentProcessHandle(),
- &readonly_handle));
+ SharedMemoryHandle readonly_handle = writable_shmem.GetReadOnlyHandle();
+ EXPECT_EQ(writable_shmem.handle().GetGUID(), readonly_handle.GetGUID());
+ EXPECT_EQ(writable_shmem.handle().GetSize(), readonly_handle.GetSize());
+ ASSERT_TRUE(readonly_handle.IsValid());
SharedMemory readonly_shmem(readonly_handle, /*readonly=*/true);
ASSERT_TRUE(readonly_shmem.Map(contents.size()));
@@ -343,6 +388,11 @@ TEST(SharedMemoryTest, ShareReadOnly) {
contents.size()));
EXPECT_TRUE(readonly_shmem.Unmap());
+#if defined(OS_ANDROID)
+ // On Android, mapping a region through a read-only descriptor makes the
+ // region read-only. Any writable mapping attempt should fail.
+ ASSERT_FALSE(writable_shmem.Map(contents.size()));
+#else
// Make sure the writable instance is still writable.
ASSERT_TRUE(writable_shmem.Map(contents.size()));
StringPiece new_contents = "Goodbye";
@@ -350,6 +400,7 @@ TEST(SharedMemoryTest, ShareReadOnly) {
EXPECT_EQ(new_contents,
StringPiece(static_cast<const char*>(writable_shmem.memory()),
new_contents.size()));
+#endif
// We'd like to check that if we send the read-only segment to another
// process, then that other process can't reopen it read/write. (Since that
@@ -364,13 +415,28 @@ TEST(SharedMemoryTest, ShareReadOnly) {
// The "read-only" handle is still writable on Android:
// http://crbug.com/320865
(void)handle;
+#elif defined(OS_FUCHSIA)
+ uintptr_t addr;
+ EXPECT_NE(ZX_OK, zx::vmar::root_self()->map(
+ 0, *zx::unowned_vmo(handle.GetHandle()), 0,
+ contents.size(), ZX_VM_FLAG_PERM_WRITE, &addr))
+ << "Shouldn't be able to map as writable.";
+
+ zx::vmo duped_handle;
+ EXPECT_NE(ZX_OK, zx::unowned_vmo(handle.GetHandle())
+ ->duplicate(ZX_RIGHT_WRITE, &duped_handle))
+ << "Shouldn't be able to duplicate the handle into a writable one.";
+
+ EXPECT_EQ(ZX_OK, zx::unowned_vmo(handle.GetHandle())
+ ->duplicate(ZX_RIGHT_READ, &duped_handle))
+ << "Should be able to duplicate the handle into a readable one.";
#elif defined(OS_POSIX)
int handle_fd = SharedMemory::GetFdFromSharedMemoryHandle(handle);
EXPECT_EQ(O_RDONLY, fcntl(handle_fd, F_GETFL) & O_ACCMODE)
<< "The descriptor itself should be read-only.";
errno = 0;
- void* writable = mmap(NULL, contents.size(), PROT_READ | PROT_WRITE,
+ void* writable = mmap(nullptr, contents.size(), PROT_READ | PROT_WRITE,
MAP_SHARED, handle_fd, 0);
int mmap_errno = errno;
EXPECT_EQ(MAP_FAILED, writable)
@@ -403,7 +469,7 @@ TEST(SharedMemoryTest, ShareReadOnly) {
#endif // defined(OS_POSIX) || defined(OS_WIN)
}
-TEST(SharedMemoryTest, ShareToSelf) {
+TEST_P(SharedMemoryTest, ShareToSelf) {
StringPiece contents = "Hello World";
SharedMemory shmem;
@@ -411,11 +477,11 @@ TEST(SharedMemoryTest, ShareToSelf) {
memcpy(shmem.memory(), contents.data(), contents.size());
EXPECT_TRUE(shmem.Unmap());
- SharedMemoryHandle shared_handle;
- ASSERT_TRUE(shmem.ShareToProcess(GetCurrentProcessHandle(), &shared_handle));
-#if defined(OS_WIN)
- ASSERT_TRUE(shared_handle.OwnershipPassesToIPC());
-#endif
+ SharedMemoryHandle shared_handle = shmem.handle().Duplicate();
+ ASSERT_TRUE(shared_handle.IsValid());
+ EXPECT_TRUE(shared_handle.OwnershipPassesToIPC());
+ EXPECT_EQ(shared_handle.GetGUID(), shmem.handle().GetGUID());
+ EXPECT_EQ(shared_handle.GetSize(), shmem.handle().GetSize());
SharedMemory shared(shared_handle, /*readonly=*/false);
ASSERT_TRUE(shared.Map(contents.size()));
@@ -423,11 +489,9 @@ TEST(SharedMemoryTest, ShareToSelf) {
contents,
StringPiece(static_cast<const char*>(shared.memory()), contents.size()));
- shared_handle = SharedMemoryHandle();
- ASSERT_TRUE(shmem.ShareToProcess(GetCurrentProcessHandle(), &shared_handle));
-#if defined(OS_WIN)
+ shared_handle = shmem.handle().Duplicate();
+ ASSERT_TRUE(shared_handle.IsValid());
ASSERT_TRUE(shared_handle.OwnershipPassesToIPC());
-#endif
SharedMemory readonly(shared_handle, /*readonly=*/true);
ASSERT_TRUE(readonly.Map(contents.size()));
@@ -436,7 +500,51 @@ TEST(SharedMemoryTest, ShareToSelf) {
contents.size()));
}
-TEST(SharedMemoryTest, MapAt) {
+TEST_P(SharedMemoryTest, ShareWithMultipleInstances) {
+ static const StringPiece kContents = "Hello World";
+
+ SharedMemory shmem;
+ ASSERT_TRUE(shmem.CreateAndMapAnonymous(kContents.size()));
+ // We do not need to unmap |shmem| to let |shared| map.
+ const StringPiece shmem_contents(static_cast<const char*>(shmem.memory()),
+ shmem.requested_size());
+
+ SharedMemoryHandle shared_handle = shmem.handle().Duplicate();
+ ASSERT_TRUE(shared_handle.IsValid());
+ SharedMemory shared(shared_handle, /*readonly=*/false);
+ ASSERT_TRUE(shared.Map(kContents.size()));
+ // The underlying shared memory is created by |shmem|, so both
+ // |shared|.requested_size() and |readonly|.requested_size() are zero.
+ ASSERT_EQ(0U, shared.requested_size());
+ const StringPiece shared_contents(static_cast<const char*>(shared.memory()),
+ shmem.requested_size());
+
+ shared_handle = shmem.handle().Duplicate();
+ ASSERT_TRUE(shared_handle.IsValid());
+ ASSERT_TRUE(shared_handle.OwnershipPassesToIPC());
+ SharedMemory readonly(shared_handle, /*readonly=*/true);
+ ASSERT_TRUE(readonly.Map(kContents.size()));
+ ASSERT_EQ(0U, readonly.requested_size());
+ const StringPiece readonly_contents(
+ static_cast<const char*>(readonly.memory()),
+ shmem.requested_size());
+
+ // |shmem| should be able to update the content.
+ memcpy(shmem.memory(), kContents.data(), kContents.size());
+
+ ASSERT_EQ(kContents, shmem_contents);
+ ASSERT_EQ(kContents, shared_contents);
+ ASSERT_EQ(kContents, readonly_contents);
+
+ // |shared| should also be able to update the content.
+ memcpy(shared.memory(), ToLowerASCII(kContents).c_str(), kContents.size());
+
+ ASSERT_EQ(StringPiece(ToLowerASCII(kContents)), shmem_contents);
+ ASSERT_EQ(StringPiece(ToLowerASCII(kContents)), shared_contents);
+ ASSERT_EQ(StringPiece(ToLowerASCII(kContents)), readonly_contents);
+}
+
+TEST_P(SharedMemoryTest, MapAt) {
ASSERT_TRUE(SysInfo::VMAllocationGranularity() >= sizeof(uint32_t));
const size_t kCount = SysInfo::VMAllocationGranularity();
const size_t kDataSize = kCount * sizeof(uint32_t);
@@ -444,7 +552,7 @@ TEST(SharedMemoryTest, MapAt) {
SharedMemory memory;
ASSERT_TRUE(memory.CreateAndMapAnonymous(kDataSize));
uint32_t* ptr = static_cast<uint32_t*>(memory.memory());
- ASSERT_NE(ptr, static_cast<void*>(NULL));
+ ASSERT_NE(ptr, static_cast<void*>(nullptr));
for (size_t i = 0; i < kCount; ++i) {
ptr[i] = i;
@@ -456,13 +564,13 @@ TEST(SharedMemoryTest, MapAt) {
ASSERT_TRUE(memory.MapAt(offset, kDataSize - offset));
offset /= sizeof(uint32_t);
ptr = static_cast<uint32_t*>(memory.memory());
- ASSERT_NE(ptr, static_cast<void*>(NULL));
+ ASSERT_NE(ptr, static_cast<void*>(nullptr));
for (size_t i = offset; i < kCount; ++i) {
EXPECT_EQ(ptr[i - offset], i);
}
}
-TEST(SharedMemoryTest, MapTwice) {
+TEST_P(SharedMemoryTest, MapTwice) {
const uint32_t kDataSize = 1024;
SharedMemory memory;
bool rv = memory.CreateAndMapAnonymous(kDataSize);
@@ -479,7 +587,16 @@ TEST(SharedMemoryTest, MapTwice) {
// This test is not applicable for iOS (crbug.com/399384).
#if !defined(OS_IOS)
// Create a shared memory object, mmap it, and mprotect it to PROT_EXEC.
-TEST(SharedMemoryTest, AnonymousExecutable) {
+TEST_P(SharedMemoryTest, AnonymousExecutable) {
+#if defined(OS_LINUX)
+ // On Chromecast both /dev/shm and /tmp are mounted with 'noexec' option,
+ // which makes this test fail. But Chromecast doesn't use NaCL so we don't
+ // need this.
+ if (!IsPathExecutable(FilePath("/dev/shm")) &&
+ !IsPathExecutable(FilePath("/tmp"))) {
+ return;
+ }
+#endif // OS_LINUX
const uint32_t kTestSize = 1 << 16;
SharedMemory shared_memory;
@@ -499,10 +616,35 @@ TEST(SharedMemoryTest, AnonymousExecutable) {
}
#endif // !defined(OS_IOS)
+#if defined(OS_ANDROID)
+// This test is restricted to Android since there is no way on other platforms
+// to guarantee that a region can never be mapped with PROT_EXEC. E.g. on
+// Linux, anonymous shared regions come from /dev/shm which can be mounted
+// without 'noexec'. In this case, anything can perform an mprotect() to
+// change the protection mask of a given page.
+TEST(SharedMemoryTest, AnonymousIsNotExecutableByDefault) {
+ const uint32_t kTestSize = 1 << 16;
+
+ SharedMemory shared_memory;
+ SharedMemoryCreateOptions options;
+ options.size = kTestSize;
+
+ EXPECT_TRUE(shared_memory.Create(options));
+ EXPECT_TRUE(shared_memory.Map(shared_memory.requested_size()));
+
+ errno = 0;
+ EXPECT_EQ(-1, mprotect(shared_memory.memory(), shared_memory.requested_size(),
+ PROT_READ | PROT_EXEC));
+ EXPECT_EQ(EACCES, errno);
+}
+#endif // OS_ANDROID
+
// Android supports a different permission model than POSIX for its "ashmem"
// shared memory implementation. So the tests about file permissions are not
-// included on Android.
-#if !defined(OS_ANDROID)
+// included on Android. Fuchsia does not use a file-backed shared memory
+// implementation.
+
+#if !defined(OS_ANDROID) && !defined(OS_FUCHSIA)
// Set a umask and restore the old mask on destruction.
class ScopedUmaskSetter {
@@ -517,7 +659,7 @@ class ScopedUmaskSetter {
};
// Create a shared memory object, check its permissions.
-TEST(SharedMemoryTest, FilePermissionsAnonymous) {
+TEST_P(SharedMemoryTest, FilePermissionsAnonymous) {
const uint32_t kTestSize = 1 << 8;
SharedMemory shared_memory;
@@ -543,7 +685,7 @@ TEST(SharedMemoryTest, FilePermissionsAnonymous) {
}
// Create a shared memory object, check its permissions.
-TEST(SharedMemoryTest, FilePermissionsNamed) {
+TEST_P(SharedMemoryTest, FilePermissionsNamed) {
const uint32_t kTestSize = 1 << 8;
SharedMemory shared_memory;
@@ -567,14 +709,14 @@ TEST(SharedMemoryTest, FilePermissionsNamed) {
EXPECT_FALSE(shm_stat.st_mode & S_IRWXO);
EXPECT_FALSE(shm_stat.st_mode & S_IRWXG);
}
-#endif // !defined(OS_ANDROID)
+#endif // !defined(OS_ANDROID) && !defined(OS_FUCHSIA)
#endif // defined(OS_POSIX)
// Map() will return addresses which are aligned to the platform page size, this
// varies from platform to platform though. Since we'd like to advertise a
// minimum alignment that callers can count on, test for it here.
-TEST(SharedMemoryTest, MapMinimumAlignment) {
+TEST_P(SharedMemoryTest, MapMinimumAlignment) {
static const int kDataSize = 8192;
SharedMemory shared_memory;
@@ -585,7 +727,7 @@ TEST(SharedMemoryTest, MapMinimumAlignment) {
}
#if defined(OS_WIN)
-TEST(SharedMemoryTest, UnsafeImageSection) {
+TEST_P(SharedMemoryTest, UnsafeImageSection) {
const char kTestSectionName[] = "UnsafeImageSection";
wchar_t path[MAX_PATH];
EXPECT_GT(::GetModuleFileName(nullptr, path, arraysize(path)), 0U);
@@ -606,7 +748,8 @@ TEST(SharedMemoryTest, UnsafeImageSection) {
EXPECT_EQ(nullptr, shared_memory_open.memory());
SharedMemory shared_memory_handle_local(
- SharedMemoryHandle(section_handle.Take(), ::GetCurrentProcessId()), true);
+ SharedMemoryHandle(section_handle.Take(), 1, UnguessableToken::Create()),
+ true);
EXPECT_FALSE(shared_memory_handle_local.Map(1));
EXPECT_EQ(nullptr, shared_memory_handle_local.memory());
@@ -621,7 +764,9 @@ TEST(SharedMemoryTest, UnsafeImageSection) {
::GetCurrentProcess(), shared_memory_handle_dummy.handle().GetHandle(),
::GetCurrentProcess(), &handle_no_query, FILE_MAP_READ, FALSE, 0));
SharedMemory shared_memory_handle_no_query(
- SharedMemoryHandle(handle_no_query, ::GetCurrentProcessId()), true);
+ SharedMemoryHandle(handle_no_query, options.size,
+ UnguessableToken::Create()),
+ true);
EXPECT_FALSE(shared_memory_handle_no_query.Map(1));
EXPECT_EQ(nullptr, shared_memory_handle_no_query.memory());
}
@@ -629,8 +774,10 @@ TEST(SharedMemoryTest, UnsafeImageSection) {
// iOS does not allow multiple processes.
// Android ashmem does not support named shared memory.
+// Fuchsia SharedMemory does not support named shared memory.
// Mac SharedMemory does not support named shared memory. crbug.com/345734
-#if !defined(OS_IOS) && !defined(OS_ANDROID) && !defined(OS_MACOSX)
+#if !defined(OS_IOS) && !defined(OS_ANDROID) && !defined(OS_MACOSX) && \
+ !defined(OS_FUCHSIA)
// On POSIX it is especially important we test shmem across processes,
// not just across threads. But the test is enabled on all platforms.
class SharedMemoryProcessTest : public MultiProcessTest {
@@ -682,16 +829,16 @@ TEST_F(SharedMemoryProcessTest, SharedMemoryAcrossProcesses) {
// Start |kNumTasks| processes, each of which atomically increments the first
// word by 1.
- SpawnChildResult children[kNumTasks];
+ Process processes[kNumTasks];
for (int index = 0; index < kNumTasks; ++index) {
- children[index] = SpawnChild("SharedMemoryTestMain");
- ASSERT_TRUE(children[index].process.IsValid());
+ processes[index] = SpawnChild("SharedMemoryTestMain");
+ ASSERT_TRUE(processes[index].IsValid());
}
// Check that each process exited correctly.
int exit_code = 0;
for (int index = 0; index < kNumTasks; ++index) {
- EXPECT_TRUE(children[index].process.WaitForExit(&exit_code));
+ EXPECT_TRUE(processes[index].WaitForExit(&exit_code));
EXPECT_EQ(0, exit_code);
}
@@ -705,6 +852,123 @@ TEST_F(SharedMemoryProcessTest, SharedMemoryAcrossProcesses) {
MULTIPROCESS_TEST_MAIN(SharedMemoryTestMain) {
return SharedMemoryProcessTest::TaskTestMain();
}
-#endif // !defined(OS_IOS) && !defined(OS_ANDROID) && !defined(OS_MACOSX)
+#endif // !defined(OS_IOS) && !defined(OS_ANDROID) && !defined(OS_MACOSX) &&
+ // !defined(OS_FUCHSIA)
+
+TEST_P(SharedMemoryTest, MappedId) {
+ const uint32_t kDataSize = 1024;
+ SharedMemory memory;
+ SharedMemoryCreateOptions options;
+ options.size = kDataSize;
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // The Mach functionality is tested in shared_memory_mac_unittest.cc.
+ options.type = SharedMemoryHandle::POSIX;
+#endif
+
+ EXPECT_TRUE(memory.Create(options));
+ base::UnguessableToken id = memory.handle().GetGUID();
+ EXPECT_FALSE(id.is_empty());
+ EXPECT_TRUE(memory.mapped_id().is_empty());
+
+ EXPECT_TRUE(memory.Map(kDataSize));
+ EXPECT_EQ(id, memory.mapped_id());
+
+ memory.Close();
+ EXPECT_EQ(id, memory.mapped_id());
+
+ memory.Unmap();
+ EXPECT_TRUE(memory.mapped_id().is_empty());
+}
+
+INSTANTIATE_TEST_CASE_P(Default,
+ SharedMemoryTest,
+ ::testing::Values(Mode::Default));
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+INSTANTIATE_TEST_CASE_P(SkipDevShm,
+ SharedMemoryTest,
+ ::testing::Values(Mode::DisableDevShm));
+#endif // defined(OS_LINUX) && !defined(OS_CHROMEOS)
+
+#if defined(OS_ANDROID)
+TEST(SharedMemoryTest, ReadOnlyRegions) {
+ const uint32_t kDataSize = 1024;
+ SharedMemory memory;
+ SharedMemoryCreateOptions options;
+ options.size = kDataSize;
+ EXPECT_TRUE(memory.Create(options));
+
+ EXPECT_FALSE(memory.handle().IsRegionReadOnly());
+
+ // Check that it is possible to map the region directly from the fd.
+ int region_fd = memory.handle().GetHandle();
+ EXPECT_GE(region_fd, 0);
+ void* address = mmap(nullptr, kDataSize, PROT_READ | PROT_WRITE, MAP_SHARED,
+ region_fd, 0);
+ bool success = address && address != MAP_FAILED;
+ ASSERT_TRUE(address);
+ ASSERT_NE(address, MAP_FAILED);
+ if (success) {
+ EXPECT_EQ(0, munmap(address, kDataSize));
+ }
+
+ ASSERT_TRUE(memory.handle().SetRegionReadOnly());
+ EXPECT_TRUE(memory.handle().IsRegionReadOnly());
+
+ // Check that it is no longer possible to map the region read/write.
+ errno = 0;
+ address = mmap(nullptr, kDataSize, PROT_READ | PROT_WRITE, MAP_SHARED,
+ region_fd, 0);
+ success = address && address != MAP_FAILED;
+ ASSERT_FALSE(success);
+ ASSERT_EQ(EPERM, errno);
+ if (success) {
+ EXPECT_EQ(0, munmap(address, kDataSize));
+ }
+}
+
+TEST(SharedMemoryTest, ReadOnlyDescriptors) {
+ const uint32_t kDataSize = 1024;
+ SharedMemory memory;
+ SharedMemoryCreateOptions options;
+ options.size = kDataSize;
+ EXPECT_TRUE(memory.Create(options));
+
+ EXPECT_FALSE(memory.handle().IsRegionReadOnly());
+
+ // Getting a read-only descriptor should not make the region read-only itself.
+ SharedMemoryHandle ro_handle = memory.GetReadOnlyHandle();
+ EXPECT_FALSE(memory.handle().IsRegionReadOnly());
+
+ // Mapping a writable region from a read-only descriptor should not
+ // be possible, it will DCHECK() in debug builds (see test below),
+ // while returning false on release ones.
+ {
+ bool dcheck_fired = false;
+ logging::ScopedLogAssertHandler log_assert(
+ base::BindRepeating([](bool* flag, const char*, int, base::StringPiece,
+ base::StringPiece) { *flag = true; },
+ base::Unretained(&dcheck_fired)));
+
+ SharedMemory rw_region(ro_handle.Duplicate(), /* read_only */ false);
+ EXPECT_FALSE(rw_region.Map(kDataSize));
+ EXPECT_EQ(DCHECK_IS_ON() ? true : false, dcheck_fired);
+ }
+
+ // Nor shall it turn the region read-only itself.
+ EXPECT_FALSE(ro_handle.IsRegionReadOnly());
+
+ // Mapping a read-only region from a read-only descriptor should work.
+ SharedMemory ro_region(ro_handle.Duplicate(), /* read_only */ true);
+ EXPECT_TRUE(ro_region.Map(kDataSize));
+
+ // And it should turn the region read-only too.
+ EXPECT_TRUE(ro_handle.IsRegionReadOnly());
+ EXPECT_TRUE(memory.handle().IsRegionReadOnly());
+ EXPECT_FALSE(memory.Map(kDataSize));
+
+ ro_handle.Close();
+}
+
+#endif // OS_ANDROID
} // namespace base
diff --git a/base/memory/singleton.cc b/base/memory/singleton.cc
deleted file mode 100644
index f68ecaa8da..0000000000
--- a/base/memory/singleton.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/memory/singleton.h"
-#include "base/threading/platform_thread.h"
-
-namespace base {
-namespace internal {
-
-subtle::AtomicWord WaitForInstance(subtle::AtomicWord* instance) {
- // Handle the race. Another thread beat us and either:
- // - Has the object in BeingCreated state
- // - Already has the object created...
- // We know value != NULL. It could be kBeingCreatedMarker, or a valid ptr.
- // Unless your constructor can be very time consuming, it is very unlikely
- // to hit this race. When it does, we just spin and yield the thread until
- // the object has been created.
- subtle::AtomicWord value;
- while (true) {
- // The load has acquire memory ordering as the thread which reads the
- // instance pointer must acquire visibility over the associated data.
- // The pairing Release_Store operation is in Singleton::get().
- value = subtle::Acquire_Load(instance);
- if (value != kBeingCreatedMarker)
- break;
- PlatformThread::YieldCurrentThread();
- }
- return value;
-}
-
-} // namespace internal
-} // namespace base
-
diff --git a/base/memory/singleton.h b/base/memory/singleton.h
index 5c58d5fe29..880ef0a58b 100644
--- a/base/memory/singleton.h
+++ b/base/memory/singleton.h
@@ -1,8 +1,16 @@
// 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.
-
-// PLEASE READ: Do you really need a singleton?
+//
+// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+// PLEASE READ: Do you really need a singleton? If possible, use a
+// function-local static of type base::NoDestructor<T> instead:
+//
+// Factory& Factory::GetInstance() {
+// static base::NoDestructor<Factory> instance;
+// return *instance;
+// }
+// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// Singletons make it hard to determine the lifetime of an object, which can
// lead to buggy code and spurious crashes.
@@ -22,26 +30,12 @@
#include "base/at_exit.h"
#include "base/atomicops.h"
#include "base/base_export.h"
+#include "base/lazy_instance_helpers.h"
#include "base/logging.h"
#include "base/macros.h"
-#include "base/memory/aligned_memory.h"
#include "base/threading/thread_restrictions.h"
namespace base {
-namespace internal {
-
-// Our AtomicWord doubles as a spinlock, where a value of
-// kBeingCreatedMarker means the spinlock is being held for creation.
-static const subtle::AtomicWord kBeingCreatedMarker = 1;
-
-// We pull out some of the functionality into a non-templated function, so that
-// we can implement the more complicated pieces out of line in the .cc file.
-BASE_EXPORT subtle::AtomicWord WaitForInstance(subtle::AtomicWord* instance);
-
-class DeleteTraceLogForTesting;
-
-} // namespace internal
-
// Default traits for Singleton<Type>. Calls operator new and operator delete on
// the object. Registers automatic deletion at process exit.
@@ -84,11 +78,10 @@ struct LeakySingletonTraits : public DefaultSingletonTraits<Type> {
#endif
};
-
// Alternate traits for use with the Singleton<Type>. Allocates memory
// for the singleton instance from a static buffer. The singleton will
// be cleaned up at exit, but can't be revived after destruction unless
-// the Resurrect() method is called.
+// the ResurrectForTesting() method is called.
//
// This is useful for a certain category of things, notably logging and
// tracing, where the singleton instance is of a type carefully constructed to
@@ -108,36 +101,36 @@ struct LeakySingletonTraits : public DefaultSingletonTraits<Type> {
// process once you've unloaded.
template <typename Type>
struct StaticMemorySingletonTraits {
- // WARNING: User has to deal with get() in the singleton class
- // this is traits for returning NULL.
+ // WARNING: User has to support a New() which returns null.
static Type* New() {
- // Only constructs once and returns pointer; otherwise returns NULL.
+ // Only constructs once and returns pointer; otherwise returns null.
if (subtle::NoBarrier_AtomicExchange(&dead_, 1))
- return NULL;
+ return nullptr;
- return new(buffer_.void_data()) Type();
+ return new (buffer_) Type();
}
static void Delete(Type* p) {
- if (p != NULL)
+ if (p)
p->Type::~Type();
}
static const bool kRegisterAtExit = true;
+
+#if DCHECK_IS_ON()
static const bool kAllowedToAccessOnNonjoinableThread = true;
+#endif
- // Exposed for unittesting.
- static void Resurrect() { subtle::NoBarrier_Store(&dead_, 0); }
+ static void ResurrectForTesting() { subtle::NoBarrier_Store(&dead_, 0); }
private:
- static AlignedMemory<sizeof(Type), ALIGNOF(Type)> buffer_;
+ alignas(Type) static char buffer_[sizeof(Type)];
// Signal the object was already deleted, so it is not revived.
static subtle::Atomic32 dead_;
};
template <typename Type>
-AlignedMemory<sizeof(Type), ALIGNOF(Type)>
- StaticMemorySingletonTraits<Type>::buffer_;
+alignas(Type) char StaticMemorySingletonTraits<Type>::buffer_[sizeof(Type)];
template <typename Type>
subtle::Atomic32 StaticMemorySingletonTraits<Type>::dead_ = 0;
@@ -230,51 +223,25 @@ class Singleton {
// method and call Singleton::get() from within that.
friend Type* Type::GetInstance();
- // Allow TraceLog tests to test tracing after OnExit.
- friend class internal::DeleteTraceLogForTesting;
-
// This class is safe to be constructed and copy-constructed since it has no
// member.
// Return a pointer to the one true instance of the class.
static Type* get() {
#if DCHECK_IS_ON()
- // Avoid making TLS lookup on release builds.
if (!Traits::kAllowedToAccessOnNonjoinableThread)
ThreadRestrictions::AssertSingletonAllowed();
#endif
- // The load has acquire memory ordering as the thread which reads the
- // instance_ pointer must acquire visibility over the singleton data.
- subtle::AtomicWord value = subtle::Acquire_Load(&instance_);
- if (value != 0 && value != internal::kBeingCreatedMarker) {
- return reinterpret_cast<Type*>(value);
- }
-
- // Object isn't created yet, maybe we will get to create it, let's try...
- if (subtle::Acquire_CompareAndSwap(&instance_, 0,
- internal::kBeingCreatedMarker) == 0) {
- // instance_ was NULL and is now kBeingCreatedMarker. Only one thread
- // will ever get here. Threads might be spinning on us, and they will
- // stop right after we do this store.
- Type* newval = Traits::New();
-
- // Releases the visibility over instance_ to the readers.
- subtle::Release_Store(&instance_,
- reinterpret_cast<subtle::AtomicWord>(newval));
-
- if (newval != NULL && Traits::kRegisterAtExit)
- AtExitManager::RegisterCallback(OnExit, NULL);
-
- return newval;
- }
-
- // We hit a race. Wait for the other thread to complete it.
- value = internal::WaitForInstance(&instance_);
-
- return reinterpret_cast<Type*>(value);
+ return subtle::GetOrCreateLazyPointer(
+ &instance_, &CreatorFunc, nullptr,
+ Traits::kRegisterAtExit ? OnExit : nullptr, nullptr);
}
+ // Internal method used as an adaptor for GetOrCreateLazyPointer(). Do not use
+ // outside of that use case.
+ static Type* CreatorFunc(void* /* creator_arg*/) { return Traits::New(); }
+
// Adapter function for use with AtExit(). This should be called single
// threaded, so don't use atomic operations.
// Calling OnExit while singleton is in use by other threads is a mistake.
diff --git a/base/memory/singleton_unittest.cc b/base/memory/singleton_unittest.cc
index a15145c874..06e53b24cd 100644
--- a/base/memory/singleton_unittest.cc
+++ b/base/memory/singleton_unittest.cc
@@ -16,6 +16,14 @@ static_assert(DefaultSingletonTraits<int>::kRegisterAtExit == true,
typedef void (*CallbackFunc)();
+template <size_t alignment>
+class AlignedData {
+ public:
+ AlignedData() = default;
+ ~AlignedData() = default;
+ alignas(alignment) char data_[alignment];
+};
+
class IntSingleton {
public:
static IntSingleton* GetInstance() {
@@ -63,7 +71,7 @@ struct CallbackTrait : public DefaultSingletonTraits<Type> {
class CallbackSingleton {
public:
- CallbackSingleton() : callback_(NULL) { }
+ CallbackSingleton() : callback_(nullptr) {}
CallbackFunc callback_;
};
@@ -115,8 +123,8 @@ struct CallbackSingletonWithStaticTrait::Trait
template <class Type>
class AlignedTestSingleton {
public:
- AlignedTestSingleton() {}
- ~AlignedTestSingleton() {}
+ AlignedTestSingleton() = default;
+ ~AlignedTestSingleton() = default;
static AlignedTestSingleton* GetInstance() {
return Singleton<AlignedTestSingleton,
StaticMemorySingletonTraits<AlignedTestSingleton>>::get();
@@ -154,7 +162,7 @@ CallbackFunc* GetStaticSingleton() {
class SingletonTest : public testing::Test {
public:
- SingletonTest() {}
+ SingletonTest() = default;
void SetUp() override {
non_leak_called_ = false;
@@ -241,7 +249,7 @@ TEST_F(SingletonTest, Basic) {
DeleteLeakySingleton();
// The static singleton can't be acquired post-atexit.
- EXPECT_EQ(NULL, GetStaticSingleton());
+ EXPECT_EQ(nullptr, GetStaticSingleton());
{
ShadowingAtExitManager sem;
@@ -257,7 +265,7 @@ TEST_F(SingletonTest, Basic) {
{
// Resurrect the static singleton, and assert that it
// still points to the same (static) memory.
- CallbackSingletonWithStaticTrait::Trait::Resurrect();
+ CallbackSingletonWithStaticTrait::Trait::ResurrectForTesting();
EXPECT_EQ(GetStaticSingleton(), static_singleton);
}
}
@@ -269,19 +277,17 @@ TEST_F(SingletonTest, Basic) {
EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(ptr) & (align - 1))
TEST_F(SingletonTest, Alignment) {
- using base::AlignedMemory;
-
// Create some static singletons with increasing sizes and alignment
// requirements. By ordering this way, the linker will need to do some work to
// ensure proper alignment of the static data.
AlignedTestSingleton<int32_t>* align4 =
AlignedTestSingleton<int32_t>::GetInstance();
- AlignedTestSingleton<AlignedMemory<32, 32> >* align32 =
- AlignedTestSingleton<AlignedMemory<32, 32> >::GetInstance();
- AlignedTestSingleton<AlignedMemory<128, 128> >* align128 =
- AlignedTestSingleton<AlignedMemory<128, 128> >::GetInstance();
- AlignedTestSingleton<AlignedMemory<4096, 4096> >* align4096 =
- AlignedTestSingleton<AlignedMemory<4096, 4096> >::GetInstance();
+ AlignedTestSingleton<AlignedData<32>>* align32 =
+ AlignedTestSingleton<AlignedData<32>>::GetInstance();
+ AlignedTestSingleton<AlignedData<128>>* align128 =
+ AlignedTestSingleton<AlignedData<128>>::GetInstance();
+ AlignedTestSingleton<AlignedData<4096>>* align4096 =
+ AlignedTestSingleton<AlignedData<4096>>::GetInstance();
EXPECT_ALIGNED(align4, 4);
EXPECT_ALIGNED(align32, 32);
diff --git a/base/memory/unsafe_shared_memory_region.cc b/base/memory/unsafe_shared_memory_region.cc
new file mode 100644
index 0000000000..422b5a9fe3
--- /dev/null
+++ b/base/memory/unsafe_shared_memory_region.cc
@@ -0,0 +1,76 @@
+// 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.
+
+#include "base/memory/unsafe_shared_memory_region.h"
+
+#include <utility>
+
+#include "base/memory/shared_memory.h"
+
+namespace base {
+
+// static
+UnsafeSharedMemoryRegion UnsafeSharedMemoryRegion::Create(size_t size) {
+ subtle::PlatformSharedMemoryRegion handle =
+ subtle::PlatformSharedMemoryRegion::CreateUnsafe(size);
+
+ return UnsafeSharedMemoryRegion(std::move(handle));
+}
+
+// static
+UnsafeSharedMemoryRegion UnsafeSharedMemoryRegion::Deserialize(
+ subtle::PlatformSharedMemoryRegion handle) {
+ return UnsafeSharedMemoryRegion(std::move(handle));
+}
+
+// static
+subtle::PlatformSharedMemoryRegion
+UnsafeSharedMemoryRegion::TakeHandleForSerialization(
+ UnsafeSharedMemoryRegion region) {
+ return std::move(region.handle_);
+}
+
+UnsafeSharedMemoryRegion::UnsafeSharedMemoryRegion() = default;
+UnsafeSharedMemoryRegion::UnsafeSharedMemoryRegion(
+ UnsafeSharedMemoryRegion&& region) = default;
+UnsafeSharedMemoryRegion& UnsafeSharedMemoryRegion::operator=(
+ UnsafeSharedMemoryRegion&& region) = default;
+UnsafeSharedMemoryRegion::~UnsafeSharedMemoryRegion() = default;
+
+UnsafeSharedMemoryRegion UnsafeSharedMemoryRegion::Duplicate() const {
+ return UnsafeSharedMemoryRegion(handle_.Duplicate());
+}
+
+WritableSharedMemoryMapping UnsafeSharedMemoryRegion::Map() const {
+ return MapAt(0, handle_.GetSize());
+}
+
+WritableSharedMemoryMapping UnsafeSharedMemoryRegion::MapAt(off_t offset,
+ size_t size) const {
+ if (!IsValid())
+ return {};
+
+ void* memory = nullptr;
+ size_t mapped_size = 0;
+ if (!handle_.MapAt(offset, size, &memory, &mapped_size))
+ return {};
+
+ return WritableSharedMemoryMapping(memory, size, mapped_size,
+ handle_.GetGUID());
+}
+
+bool UnsafeSharedMemoryRegion::IsValid() const {
+ return handle_.IsValid();
+}
+
+UnsafeSharedMemoryRegion::UnsafeSharedMemoryRegion(
+ subtle::PlatformSharedMemoryRegion handle)
+ : handle_(std::move(handle)) {
+ if (handle_.IsValid()) {
+ CHECK_EQ(handle_.GetMode(),
+ subtle::PlatformSharedMemoryRegion::Mode::kUnsafe);
+ }
+}
+
+} // namespace base
diff --git a/base/memory/unsafe_shared_memory_region.h b/base/memory/unsafe_shared_memory_region.h
new file mode 100644
index 0000000000..ea637cd51b
--- /dev/null
+++ b/base/memory/unsafe_shared_memory_region.h
@@ -0,0 +1,118 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_UNSAFE_SHARED_MEMORY_REGION_H_
+#define BASE_MEMORY_UNSAFE_SHARED_MEMORY_REGION_H_
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/platform_shared_memory_region.h"
+#include "base/memory/shared_memory_mapping.h"
+
+namespace base {
+
+// Scoped move-only handle to a region of platform shared memory. The instance
+// owns the platform handle it wraps. Mappings created by this region are
+// writable. These mappings remain valid even after the region handle is moved
+// or destroyed.
+//
+// NOTE: UnsafeSharedMemoryRegion cannot be converted to a read-only region. Use
+// with caution as the region will be writable to any process with a handle to
+// the region.
+//
+// Use this if and only if the following is true:
+// - You do not need to share the region as read-only, and,
+// - You need to have several instances of the region simultaneously, possibly
+// in different processes, that can produce writable mappings.
+
+class BASE_EXPORT UnsafeSharedMemoryRegion {
+ public:
+ using MappingType = WritableSharedMemoryMapping;
+ // Creates a new UnsafeSharedMemoryRegion instance of a given size that can be
+ // used for mapping writable shared memory into the virtual address space.
+ static UnsafeSharedMemoryRegion Create(size_t size);
+
+ // Returns an UnsafeSharedMemoryRegion built from a platform-specific handle
+ // that was taken from another UnsafeSharedMemoryRegion instance. Returns an
+ // invalid region iff the |handle| is invalid. CHECK-fails if the |handle|
+ // isn't unsafe.
+ // This should be used only by the code passing a handle across
+ // process boundaries.
+ static UnsafeSharedMemoryRegion Deserialize(
+ subtle::PlatformSharedMemoryRegion handle);
+
+ // Extracts a platform handle from the region. Ownership is transferred to the
+ // returned region object.
+ // This should be used only for sending the handle from the current
+ // process to another.
+ static subtle::PlatformSharedMemoryRegion TakeHandleForSerialization(
+ UnsafeSharedMemoryRegion region);
+
+ // Default constructor initializes an invalid instance.
+ UnsafeSharedMemoryRegion();
+
+ // Move operations are allowed.
+ UnsafeSharedMemoryRegion(UnsafeSharedMemoryRegion&&);
+ UnsafeSharedMemoryRegion& operator=(UnsafeSharedMemoryRegion&&);
+
+ // Destructor closes shared memory region if valid.
+ // All created mappings will remain valid.
+ ~UnsafeSharedMemoryRegion();
+
+ // Duplicates the underlying platform handle and creates a new
+ // UnsafeSharedMemoryRegion instance that owns the newly created handle.
+ // Returns a valid UnsafeSharedMemoryRegion on success, invalid otherwise.
+ // The current region instance remains valid in any case.
+ UnsafeSharedMemoryRegion Duplicate() const;
+
+ // Maps the shared memory region into the caller's address space with write
+ // access. The mapped address is guaranteed to have an alignment of
+ // at least |subtle::PlatformSharedMemoryRegion::kMapMinimumAlignment|.
+ // Returns a valid WritableSharedMemoryMapping instance on success, invalid
+ // otherwise.
+ WritableSharedMemoryMapping Map() const;
+
+ // Same as above, but maps only |size| bytes of the shared memory region
+ // starting with the given |offset|. |offset| must be aligned to value of
+ // |SysInfo::VMAllocationGranularity()|. Returns an invalid mapping if
+ // requested bytes are out of the region limits.
+ WritableSharedMemoryMapping MapAt(off_t offset, size_t size) const;
+
+ // Whether the underlying platform handle is valid.
+ bool IsValid() const;
+
+ // Returns the maximum mapping size that can be created from this region.
+ size_t GetSize() const {
+ DCHECK(IsValid());
+ return handle_.GetSize();
+ }
+
+ // Returns 128-bit GUID of the region.
+ const UnguessableToken& GetGUID() const {
+ DCHECK(IsValid());
+ return handle_.GetGUID();
+ }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(DiscardableSharedMemoryTest,
+ LockShouldFailIfPlatformLockPagesFails);
+ friend class DiscardableSharedMemory;
+
+ explicit UnsafeSharedMemoryRegion(subtle::PlatformSharedMemoryRegion handle);
+
+ // Returns a platform shared memory handle. |this| remains the owner of the
+ // handle.
+ subtle::PlatformSharedMemoryRegion::PlatformHandle GetPlatformHandle() const {
+ DCHECK(IsValid());
+ return handle_.GetPlatformHandle();
+ }
+
+ subtle::PlatformSharedMemoryRegion handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(UnsafeSharedMemoryRegion);
+};
+
+} // namespace base
+
+#endif // BASE_MEMORY_UNSAFE_SHARED_MEMORY_REGION_H_
diff --git a/base/memory/weak_ptr.cc b/base/memory/weak_ptr.cc
index c179b80097..d2a7d89e56 100644
--- a/base/memory/weak_ptr.cc
+++ b/base/memory/weak_ptr.cc
@@ -28,27 +28,24 @@ bool WeakReference::Flag::IsValid() const {
return is_valid_;
}
-WeakReference::Flag::~Flag() {
-}
+WeakReference::Flag::~Flag() = default;
-WeakReference::WeakReference() {
-}
+WeakReference::WeakReference() = default;
-WeakReference::WeakReference(const Flag* flag) : flag_(flag) {
-}
+WeakReference::WeakReference(const scoped_refptr<Flag>& flag) : flag_(flag) {}
-WeakReference::~WeakReference() {
-}
+WeakReference::~WeakReference() = default;
WeakReference::WeakReference(WeakReference&& other) = default;
WeakReference::WeakReference(const WeakReference& other) = default;
-bool WeakReference::is_valid() const { return flag_.get() && flag_->IsValid(); }
-
-WeakReferenceOwner::WeakReferenceOwner() {
+bool WeakReference::is_valid() const {
+ return flag_ && flag_->IsValid();
}
+WeakReferenceOwner::WeakReferenceOwner() = default;
+
WeakReferenceOwner::~WeakReferenceOwner() {
Invalidate();
}
@@ -58,23 +55,31 @@ WeakReference WeakReferenceOwner::GetRef() const {
if (!HasRefs())
flag_ = new WeakReference::Flag();
- return WeakReference(flag_.get());
+ return WeakReference(flag_);
}
void WeakReferenceOwner::Invalidate() {
- if (flag_.get()) {
+ if (flag_) {
flag_->Invalidate();
- flag_ = NULL;
+ flag_ = nullptr;
}
}
-WeakPtrBase::WeakPtrBase() {
+WeakPtrBase::WeakPtrBase() : ptr_(0) {}
+
+WeakPtrBase::~WeakPtrBase() = default;
+
+WeakPtrBase::WeakPtrBase(const WeakReference& ref, uintptr_t ptr)
+ : ref_(ref), ptr_(ptr) {
+ DCHECK(ptr_);
}
-WeakPtrBase::~WeakPtrBase() {
+WeakPtrFactoryBase::WeakPtrFactoryBase(uintptr_t ptr) : ptr_(ptr) {
+ DCHECK(ptr_);
}
-WeakPtrBase::WeakPtrBase(const WeakReference& ref) : ref_(ref) {
+WeakPtrFactoryBase::~WeakPtrFactoryBase() {
+ ptr_ = 0;
}
} // namespace internal
diff --git a/base/memory/weak_ptr.h b/base/memory/weak_ptr.h
index 3544439dd3..34e7d2e358 100644
--- a/base/memory/weak_ptr.h
+++ b/base/memory/weak_ptr.h
@@ -109,7 +109,7 @@ class BASE_EXPORT WeakReference {
};
WeakReference();
- explicit WeakReference(const Flag* flag);
+ explicit WeakReference(const scoped_refptr<Flag>& flag);
~WeakReference();
WeakReference(WeakReference&& other);
@@ -130,9 +130,7 @@ class BASE_EXPORT WeakReferenceOwner {
WeakReference GetRef() const;
- bool HasRefs() const {
- return flag_.get() && !flag_->HasOneRef();
- }
+ bool HasRefs() const { return flag_ && !flag_->HasOneRef(); }
void Invalidate();
@@ -154,10 +152,19 @@ class BASE_EXPORT WeakPtrBase {
WeakPtrBase& operator=(const WeakPtrBase& other) = default;
WeakPtrBase& operator=(WeakPtrBase&& other) = default;
+ void reset() {
+ ref_ = internal::WeakReference();
+ ptr_ = 0;
+ }
+
protected:
- explicit WeakPtrBase(const WeakReference& ref);
+ WeakPtrBase(const WeakReference& ref, uintptr_t ptr);
WeakReference ref_;
+
+ // This pointer is only valid when ref_.is_valid() is true. Otherwise, its
+ // value is undefined (as opposed to nullptr).
+ uintptr_t ptr_;
};
// This class provides a common implementation of common functions that would
@@ -169,12 +176,14 @@ class SupportsWeakPtrBase {
// conversion will only compile if there is exists a Base which inherits
// from SupportsWeakPtr<Base>. See base::AsWeakPtr() below for a helper
// function that makes calling this easier.
+ //
+ // Precondition: t != nullptr
template<typename Derived>
static WeakPtr<Derived> StaticAsWeakPtr(Derived* t) {
static_assert(
std::is_base_of<internal::SupportsWeakPtrBase, Derived>::value,
"AsWeakPtr argument must inherit from SupportsWeakPtr");
- return AsWeakPtrImpl<Derived>(t, *t);
+ return AsWeakPtrImpl<Derived>(t);
}
private:
@@ -182,10 +191,10 @@ class SupportsWeakPtrBase {
// which is an instance of SupportsWeakPtr<Base>. We can then safely
// static_cast the Base* to a Derived*.
template <typename Derived, typename Base>
- static WeakPtr<Derived> AsWeakPtrImpl(
- Derived* t, const SupportsWeakPtr<Base>&) {
- WeakPtr<Base> ptr = t->Base::AsWeakPtr();
- return WeakPtr<Derived>(ptr.ref_, static_cast<Derived*>(ptr.ptr_));
+ static WeakPtr<Derived> AsWeakPtrImpl(SupportsWeakPtr<Base>* t) {
+ WeakPtr<Base> ptr = t->AsWeakPtr();
+ return WeakPtr<Derived>(
+ ptr.ref_, static_cast<Derived*>(reinterpret_cast<Base*>(ptr.ptr_)));
}
};
@@ -209,20 +218,30 @@ template <typename T> class WeakPtrFactory;
template <typename T>
class WeakPtr : public internal::WeakPtrBase {
public:
- WeakPtr() : ptr_(nullptr) {}
+ WeakPtr() = default;
- WeakPtr(std::nullptr_t) : ptr_(nullptr) {}
+ WeakPtr(std::nullptr_t) {}
// Allow conversion from U to T provided U "is a" T. Note that this
// is separate from the (implicit) copy and move constructors.
template <typename U>
- WeakPtr(const WeakPtr<U>& other) : WeakPtrBase(other), ptr_(other.ptr_) {
+ WeakPtr(const WeakPtr<U>& other) : WeakPtrBase(other) {
+ // Need to cast from U* to T* to do pointer adjustment in case of multiple
+ // inheritance. This also enforces the "U is a T" rule.
+ T* t = reinterpret_cast<U*>(other.ptr_);
+ ptr_ = reinterpret_cast<uintptr_t>(t);
}
template <typename U>
- WeakPtr(WeakPtr<U>&& other)
- : WeakPtrBase(std::move(other)), ptr_(other.ptr_) {}
+ WeakPtr(WeakPtr<U>&& other) : WeakPtrBase(std::move(other)) {
+ // Need to cast from U* to T* to do pointer adjustment in case of multiple
+ // inheritance. This also enforces the "U is a T" rule.
+ T* t = reinterpret_cast<U*>(other.ptr_);
+ ptr_ = reinterpret_cast<uintptr_t>(t);
+ }
- T* get() const { return ref_.is_valid() ? ptr_ : nullptr; }
+ T* get() const {
+ return ref_.is_valid() ? reinterpret_cast<T*>(ptr_) : nullptr;
+ }
T& operator*() const {
DCHECK(get() != nullptr);
@@ -233,11 +252,6 @@ class WeakPtr : public internal::WeakPtrBase {
return get();
}
- void reset() {
- ref_ = internal::WeakReference();
- ptr_ = nullptr;
- }
-
// Allow conditionals to test validity, e.g. if (weak_ptr) {...};
explicit operator bool() const { return get() != nullptr; }
@@ -248,13 +262,7 @@ class WeakPtr : public internal::WeakPtrBase {
friend class WeakPtrFactory<T>;
WeakPtr(const internal::WeakReference& ref, T* ptr)
- : WeakPtrBase(ref),
- ptr_(ptr) {
- }
-
- // This pointer is only valid when ref_.is_valid() is true. Otherwise, its
- // value is undefined (as opposed to nullptr).
- T* ptr_;
+ : WeakPtrBase(ref, reinterpret_cast<uintptr_t>(ptr)) {}
};
// Allow callers to compare WeakPtrs against nullptr to test validity.
@@ -275,22 +283,32 @@ bool operator==(std::nullptr_t, const WeakPtr<T>& weak_ptr) {
return weak_ptr == nullptr;
}
+namespace internal {
+class BASE_EXPORT WeakPtrFactoryBase {
+ protected:
+ WeakPtrFactoryBase(uintptr_t ptr);
+ ~WeakPtrFactoryBase();
+ internal::WeakReferenceOwner weak_reference_owner_;
+ uintptr_t ptr_;
+};
+} // namespace internal
+
// A class may be composed of a WeakPtrFactory and thereby
// control how it exposes weak pointers to itself. This is helpful if you only
// need weak pointers within the implementation of a class. This class is also
// useful when working with primitive types. For example, you could have a
// WeakPtrFactory<bool> that is used to pass around a weak reference to a bool.
template <class T>
-class WeakPtrFactory {
+class WeakPtrFactory : public internal::WeakPtrFactoryBase {
public:
- explicit WeakPtrFactory(T* ptr) : ptr_(ptr) {
- }
+ explicit WeakPtrFactory(T* ptr)
+ : WeakPtrFactoryBase(reinterpret_cast<uintptr_t>(ptr)) {}
- ~WeakPtrFactory() { ptr_ = nullptr; }
+ ~WeakPtrFactory() = default;
WeakPtr<T> GetWeakPtr() {
- DCHECK(ptr_);
- return WeakPtr<T>(weak_reference_owner_.GetRef(), ptr_);
+ return WeakPtr<T>(weak_reference_owner_.GetRef(),
+ reinterpret_cast<T*>(ptr_));
}
// Call this method to invalidate all existing weak pointers.
@@ -306,8 +324,6 @@ class WeakPtrFactory {
}
private:
- internal::WeakReferenceOwner weak_reference_owner_;
- T* ptr_;
DISALLOW_IMPLICIT_CONSTRUCTORS(WeakPtrFactory);
};
@@ -319,14 +335,14 @@ class WeakPtrFactory {
template <class T>
class SupportsWeakPtr : public internal::SupportsWeakPtrBase {
public:
- SupportsWeakPtr() {}
+ SupportsWeakPtr() = default;
WeakPtr<T> AsWeakPtr() {
return WeakPtr<T>(weak_reference_owner_.GetRef(), static_cast<T*>(this));
}
protected:
- ~SupportsWeakPtr() {}
+ ~SupportsWeakPtr() = default;
private:
internal::WeakReferenceOwner weak_reference_owner_;
diff --git a/base/memory/weak_ptr_unittest.cc b/base/memory/weak_ptr_unittest.cc
index 1a4870eab1..f8dfb7c0f0 100644
--- a/base/memory/weak_ptr_unittest.cc
+++ b/base/memory/weak_ptr_unittest.cc
@@ -32,7 +32,8 @@ class OffThreadObjectCreator {
Thread creator_thread("creator_thread");
creator_thread.Start();
creator_thread.task_runner()->PostTask(
- FROM_HERE, base::Bind(OffThreadObjectCreator::CreateObject, &result));
+ FROM_HERE,
+ base::BindOnce(OffThreadObjectCreator::CreateObject, &result));
}
DCHECK(result); // We synchronized on thread destruction above.
return result;
@@ -50,9 +51,29 @@ struct Derived : public Base {};
struct TargetBase {};
struct Target : public TargetBase, public SupportsWeakPtr<Target> {
- virtual ~Target() {}
+ virtual ~Target() = default;
};
+
struct DerivedTarget : public Target {};
+
+// A class inheriting from Target and defining a nested type called 'Base'.
+// To guard against strange compilation errors.
+struct DerivedTargetWithNestedBase : public Target {
+ using Base = void;
+};
+
+// A struct with a virtual destructor.
+struct VirtualDestructor {
+ virtual ~VirtualDestructor() = default;
+};
+
+// A class inheriting from Target where Target is not the first base, and where
+// the first base has a virtual method table. This creates a structure where the
+// Target base is not positioned at the beginning of
+// DerivedTargetMultipleInheritance.
+struct DerivedTargetMultipleInheritance : public VirtualDestructor,
+ public Target {};
+
struct Arrow {
WeakPtr<Target> target;
};
@@ -73,8 +94,8 @@ class BackgroundThread : public Thread {
WaitableEvent completion(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED);
task_runner()->PostTask(
- FROM_HERE, base::Bind(&BackgroundThread::DoCreateArrowFromTarget, arrow,
- target, &completion));
+ FROM_HERE, base::BindOnce(&BackgroundThread::DoCreateArrowFromTarget,
+ arrow, target, &completion));
completion.Wait();
}
@@ -82,8 +103,8 @@ class BackgroundThread : public Thread {
WaitableEvent completion(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED);
task_runner()->PostTask(
- FROM_HERE, base::Bind(&BackgroundThread::DoCreateArrowFromArrow, arrow,
- other, &completion));
+ FROM_HERE, base::BindOnce(&BackgroundThread::DoCreateArrowFromArrow,
+ arrow, other, &completion));
completion.Wait();
}
@@ -92,7 +113,7 @@ class BackgroundThread : public Thread {
WaitableEvent::InitialState::NOT_SIGNALED);
task_runner()->PostTask(
FROM_HERE,
- base::Bind(&BackgroundThread::DoDeleteTarget, object, &completion));
+ base::BindOnce(&BackgroundThread::DoDeleteTarget, object, &completion));
completion.Wait();
}
@@ -100,8 +121,8 @@ class BackgroundThread : public Thread {
WaitableEvent completion(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED);
task_runner()->PostTask(
- FROM_HERE, base::Bind(&BackgroundThread::DoCopyAndAssignArrow, object,
- &completion));
+ FROM_HERE, base::BindOnce(&BackgroundThread::DoCopyAndAssignArrow,
+ object, &completion));
completion.Wait();
}
@@ -109,8 +130,8 @@ class BackgroundThread : public Thread {
WaitableEvent completion(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED);
task_runner()->PostTask(
- FROM_HERE, base::Bind(&BackgroundThread::DoCopyAndAssignArrowBase,
- object, &completion));
+ FROM_HERE, base::BindOnce(&BackgroundThread::DoCopyAndAssignArrowBase,
+ object, &completion));
completion.Wait();
}
@@ -119,7 +140,7 @@ class BackgroundThread : public Thread {
WaitableEvent::InitialState::NOT_SIGNALED);
task_runner()->PostTask(
FROM_HERE,
- base::Bind(&BackgroundThread::DoDeleteArrow, object, &completion));
+ base::BindOnce(&BackgroundThread::DoDeleteArrow, object, &completion));
completion.Wait();
}
@@ -127,8 +148,9 @@ class BackgroundThread : public Thread {
WaitableEvent completion(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED);
Target* result = nullptr;
- task_runner()->PostTask(FROM_HERE, base::Bind(&BackgroundThread::DoDeRef,
- arrow, &result, &completion));
+ task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&BackgroundThread::DoDeRef, arrow, &result,
+ &completion));
completion.Wait();
return result;
}
@@ -288,6 +310,22 @@ TEST(WeakPtrTest, DerivedTarget) {
EXPECT_EQ(&target, ptr.get());
}
+TEST(WeakPtrTest, DerivedTargetWithNestedBase) {
+ DerivedTargetWithNestedBase target;
+ WeakPtr<DerivedTargetWithNestedBase> ptr = AsWeakPtr(&target);
+ EXPECT_EQ(&target, ptr.get());
+}
+
+TEST(WeakPtrTest, DerivedTargetMultipleInheritance) {
+ DerivedTargetMultipleInheritance d;
+ Target& b = d;
+ EXPECT_NE(static_cast<void*>(&d), static_cast<void*>(&b));
+ const WeakPtr<Target> pb = AsWeakPtr(&b);
+ EXPECT_EQ(pb.get(), &b);
+ const WeakPtr<DerivedTargetMultipleInheritance> pd = AsWeakPtr(&d);
+ EXPECT_EQ(pd.get(), &d);
+}
+
TEST(WeakPtrFactoryTest, BooleanTesting) {
int data;
WeakPtrFactory<int> factory(&data);
diff --git a/base/memory/weak_ptr_unittest.nc b/base/memory/weak_ptr_unittest.nc
index 9b1226b794..b96b033d3a 100644
--- a/base/memory/weak_ptr_unittest.nc
+++ b/base/memory/weak_ptr_unittest.nc
@@ -17,7 +17,7 @@ struct MultiplyDerivedProducer : Producer,
struct Unrelated {};
struct DerivedUnrelated : Unrelated {};
-#if defined(NCTEST_AUTO_DOWNCAST) // [r"fatal error: cannot initialize a member subobject of type 'base::DerivedProducer \*' with an lvalue of type 'base::Producer \*const'"]
+#if defined(NCTEST_AUTO_DOWNCAST) // [r"cannot initialize a variable of type 'base::DerivedProducer \*' with an rvalue of type 'base::Producer \*'"]
void WontCompile() {
Producer f;
@@ -25,7 +25,7 @@ void WontCompile() {
WeakPtr<DerivedProducer> derived_ptr = ptr;
}
-#elif defined(NCTEST_STATIC_DOWNCAST) // [r"fatal error: cannot initialize a member subobject of type 'base::DerivedProducer \*' with an lvalue of type 'base::Producer \*const'"]
+#elif defined(NCTEST_STATIC_DOWNCAST) // [r"cannot initialize a variable of type 'base::DerivedProducer \*' with an rvalue of type 'base::Producer \*'"]
void WontCompile() {
Producer f;
@@ -59,7 +59,7 @@ void WontCompile() {
SupportsWeakPtr<Producer>::StaticAsWeakPtr<DerivedProducer>(&f);
}
-#elif defined(NCTEST_UNSAFE_HELPER_DOWNCAST) // [r"fatal error: cannot initialize a member subobject of type 'base::DerivedProducer \*' with an lvalue of type 'base::Producer \*'"]
+#elif defined(NCTEST_UNSAFE_HELPER_DOWNCAST) // [r"cannot initialize a variable of type 'base::DerivedProducer \*' with an rvalue of type 'base::Producer \*'"]
void WontCompile() {
Producer f;
@@ -73,14 +73,14 @@ void WontCompile() {
WeakPtr<DerivedProducer> ptr = AsWeakPtr<DerivedProducer>(&f);
}
-#elif defined(NCTEST_UNSAFE_WRONG_INSANTIATED_HELPER_DOWNCAST) // [r"fatal error: cannot initialize a member subobject of type 'base::DerivedProducer \*' with an lvalue of type 'base::Producer \*'"]
+#elif defined(NCTEST_UNSAFE_WRONG_INSANTIATED_HELPER_DOWNCAST) // [r"cannot initialize a variable of type 'base::DerivedProducer \*' with an rvalue of type 'base::Producer \*'"]
void WontCompile() {
- Producer f;
+ Producer f;
WeakPtr<DerivedProducer> ptr = AsWeakPtr<Producer>(&f);
}
-#elif defined(NCTEST_UNSAFE_HELPER_CAST) // [r"fatal error: cannot initialize a member subobject of type 'base::OtherDerivedProducer \*' with an lvalue of type 'base::DerivedProducer \*'"]
+#elif defined(NCTEST_UNSAFE_HELPER_CAST) // [r"cannot initialize a variable of type 'base::OtherDerivedProducer \*' with an rvalue of type 'base::DerivedProducer \*'"]
void WontCompile() {
DerivedProducer f;
@@ -94,14 +94,14 @@ void WontCompile() {
WeakPtr<OtherDerivedProducer> ptr = AsWeakPtr<OtherDerivedProducer>(&f);
}
-#elif defined(NCTEST_UNSAFE_WRONG_INSTANTIATED_HELPER_SIDECAST) // [r"fatal error: cannot initialize a member subobject of type 'base::OtherDerivedProducer \*' with an lvalue of type 'base::DerivedProducer \*'"]
+#elif defined(NCTEST_UNSAFE_WRONG_INSTANTIATED_HELPER_SIDECAST) // [r"cannot initialize a variable of type 'base::OtherDerivedProducer \*' with an rvalue of type 'base::DerivedProducer \*'"]
void WontCompile() {
DerivedProducer f;
WeakPtr<OtherDerivedProducer> ptr = AsWeakPtr<DerivedProducer>(&f);
}
-#elif defined(NCTEST_UNRELATED_HELPER) // [r"fatal error: cannot initialize a member subobject of type 'base::Unrelated \*' with an lvalue of type 'base::DerivedProducer \*'"]
+#elif defined(NCTEST_UNRELATED_HELPER) // [r"cannot initialize a variable of type 'base::Unrelated \*' with an rvalue of type 'base::DerivedProducer \*'"]
void WontCompile() {
DerivedProducer f;
@@ -115,14 +115,17 @@ void WontCompile() {
WeakPtr<Unrelated> ptr = AsWeakPtr<Unrelated>(&f);
}
-#elif defined(NCTEST_COMPLETELY_UNRELATED_HELPER) // [r"fatal error: static_assert failed \"AsWeakPtr argument must inherit from SupportsWeakPtr\""]
+// TODO(hans): Remove .* and update the static_assert expectations once we roll
+// past Clang r313315. https://crbug.com/765692.
+
+#elif defined(NCTEST_COMPLETELY_UNRELATED_HELPER) // [r"fatal error: static_assert failed .*\"AsWeakPtr argument must inherit from SupportsWeakPtr\""]
void WontCompile() {
Unrelated f;
WeakPtr<Unrelated> ptr = AsWeakPtr(&f);
}
-#elif defined(NCTEST_DERIVED_COMPLETELY_UNRELATED_HELPER) // [r"fatal error: static_assert failed \"AsWeakPtr argument must inherit from SupportsWeakPtr\""]
+#elif defined(NCTEST_DERIVED_COMPLETELY_UNRELATED_HELPER) // [r"fatal error: static_assert failed .*\"AsWeakPtr argument must inherit from SupportsWeakPtr\""]
void WontCompile() {
DerivedUnrelated f;
diff --git a/base/memory/writable_shared_memory_region.cc b/base/memory/writable_shared_memory_region.cc
new file mode 100644
index 0000000000..063e6720c4
--- /dev/null
+++ b/base/memory/writable_shared_memory_region.cc
@@ -0,0 +1,93 @@
+// 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.
+
+#include "base/memory/writable_shared_memory_region.h"
+
+#include <utility>
+
+#include "base/memory/shared_memory.h"
+#include "build/build_config.h"
+
+namespace base {
+
+// static
+WritableSharedMemoryRegion WritableSharedMemoryRegion::Create(size_t size) {
+ subtle::PlatformSharedMemoryRegion handle =
+ subtle::PlatformSharedMemoryRegion::CreateWritable(size);
+
+ return WritableSharedMemoryRegion(std::move(handle));
+}
+
+// static
+WritableSharedMemoryRegion WritableSharedMemoryRegion::Deserialize(
+ subtle::PlatformSharedMemoryRegion handle) {
+ return WritableSharedMemoryRegion(std::move(handle));
+}
+
+// static
+subtle::PlatformSharedMemoryRegion
+WritableSharedMemoryRegion::TakeHandleForSerialization(
+ WritableSharedMemoryRegion region) {
+ return std::move(region.handle_);
+}
+
+// static
+ReadOnlySharedMemoryRegion WritableSharedMemoryRegion::ConvertToReadOnly(
+ WritableSharedMemoryRegion region) {
+ subtle::PlatformSharedMemoryRegion handle = std::move(region.handle_);
+ if (!handle.ConvertToReadOnly())
+ return {};
+
+ return ReadOnlySharedMemoryRegion::Deserialize(std::move(handle));
+}
+
+UnsafeSharedMemoryRegion WritableSharedMemoryRegion::ConvertToUnsafe(
+ WritableSharedMemoryRegion region) {
+ subtle::PlatformSharedMemoryRegion handle = std::move(region.handle_);
+ if (!handle.ConvertToUnsafe())
+ return {};
+
+ return UnsafeSharedMemoryRegion::Deserialize(std::move(handle));
+}
+
+WritableSharedMemoryRegion::WritableSharedMemoryRegion() = default;
+WritableSharedMemoryRegion::WritableSharedMemoryRegion(
+ WritableSharedMemoryRegion&& region) = default;
+WritableSharedMemoryRegion& WritableSharedMemoryRegion::operator=(
+ WritableSharedMemoryRegion&& region) = default;
+WritableSharedMemoryRegion::~WritableSharedMemoryRegion() = default;
+
+WritableSharedMemoryMapping WritableSharedMemoryRegion::Map() const {
+ return MapAt(0, handle_.GetSize());
+}
+
+WritableSharedMemoryMapping WritableSharedMemoryRegion::MapAt(
+ off_t offset,
+ size_t size) const {
+ if (!IsValid())
+ return {};
+
+ void* memory = nullptr;
+ size_t mapped_size = 0;
+ if (!handle_.MapAt(offset, size, &memory, &mapped_size))
+ return {};
+
+ return WritableSharedMemoryMapping(memory, size, mapped_size,
+ handle_.GetGUID());
+}
+
+bool WritableSharedMemoryRegion::IsValid() const {
+ return handle_.IsValid();
+}
+
+WritableSharedMemoryRegion::WritableSharedMemoryRegion(
+ subtle::PlatformSharedMemoryRegion handle)
+ : handle_(std::move(handle)) {
+ if (handle_.IsValid()) {
+ CHECK_EQ(handle_.GetMode(),
+ subtle::PlatformSharedMemoryRegion::Mode::kWritable);
+ }
+}
+
+} // namespace base
diff --git a/base/memory/writable_shared_memory_region.h b/base/memory/writable_shared_memory_region.h
new file mode 100644
index 0000000000..f656db1ef4
--- /dev/null
+++ b/base/memory/writable_shared_memory_region.h
@@ -0,0 +1,109 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MEMORY_WRITABLE_SHARED_MEMORY_REGION_H_
+#define BASE_MEMORY_WRITABLE_SHARED_MEMORY_REGION_H_
+
+#include "base/macros.h"
+#include "base/memory/platform_shared_memory_region.h"
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/unsafe_shared_memory_region.h"
+
+namespace base {
+
+// Scoped move-only handle to a region of platform shared memory. The instance
+// owns the platform handle it wraps. Mappings created by this region are
+// writable. These mappings remain valid even after the region handle is moved
+// or destroyed.
+//
+// This region can be locked to read-only access by converting it to a
+// ReadOnlySharedMemoryRegion. However, unlike ReadOnlySharedMemoryRegion and
+// UnsafeSharedMemoryRegion, ownership of this region (while writable) is unique
+// and may only be transferred, not duplicated.
+class BASE_EXPORT WritableSharedMemoryRegion {
+ public:
+ using MappingType = WritableSharedMemoryMapping;
+ // Creates a new WritableSharedMemoryRegion instance of a given
+ // size that can be used for mapping writable shared memory into the virtual
+ // address space.
+ static WritableSharedMemoryRegion Create(size_t size);
+
+ // Returns a WritableSharedMemoryRegion built from a platform handle that was
+ // taken from another WritableSharedMemoryRegion instance. Returns an invalid
+ // region iff the |handle| is invalid. CHECK-fails if the |handle| isn't
+ // writable.
+ // This should be used only by the code passing handles across process
+ // boundaries.
+ static WritableSharedMemoryRegion Deserialize(
+ subtle::PlatformSharedMemoryRegion handle);
+
+ // Extracts a platform handle from the region. Ownership is transferred to the
+ // returned region object.
+ // This should be used only for sending the handle from the current
+ // process to another.
+ static subtle::PlatformSharedMemoryRegion TakeHandleForSerialization(
+ WritableSharedMemoryRegion region);
+
+ // Makes the region read-only. No new writable mappings of the region can be
+ // created after this call. Returns an invalid region on failure.
+ static ReadOnlySharedMemoryRegion ConvertToReadOnly(
+ WritableSharedMemoryRegion region);
+
+ // Makes the region unsafe. The region cannot be converted to read-only after
+ // this call. Returns an invalid region on failure.
+ static UnsafeSharedMemoryRegion ConvertToUnsafe(
+ WritableSharedMemoryRegion region);
+
+ // Default constructor initializes an invalid instance.
+ WritableSharedMemoryRegion();
+
+ // Move operations are allowed.
+ WritableSharedMemoryRegion(WritableSharedMemoryRegion&&);
+ WritableSharedMemoryRegion& operator=(WritableSharedMemoryRegion&&);
+
+ // Destructor closes shared memory region if valid.
+ // All created mappings will remain valid.
+ ~WritableSharedMemoryRegion();
+
+ // Maps the shared memory region into the caller's address space with write
+ // access. The mapped address is guaranteed to have an alignment of
+ // at least |subtle::PlatformSharedMemoryRegion::kMapMinimumAlignment|.
+ // Returns a valid WritableSharedMemoryMapping instance on success, invalid
+ // otherwise.
+ WritableSharedMemoryMapping Map() const;
+
+ // Same as above, but maps only |size| bytes of the shared memory block
+ // starting with the given |offset|. |offset| must be aligned to value of
+ // |SysInfo::VMAllocationGranularity()|. Returns an invalid mapping if
+ // requested bytes are out of the region limits.
+ WritableSharedMemoryMapping MapAt(off_t offset, size_t size) const;
+
+ // Whether underlying platform handles are valid.
+ bool IsValid() const;
+
+ // Returns the maximum mapping size that can be created from this region.
+ size_t GetSize() const {
+ DCHECK(IsValid());
+ return handle_.GetSize();
+ }
+
+ // Returns 128-bit GUID of the region.
+ const UnguessableToken& GetGUID() const {
+ DCHECK(IsValid());
+ return handle_.GetGUID();
+ }
+
+ private:
+ explicit WritableSharedMemoryRegion(
+ subtle::PlatformSharedMemoryRegion handle);
+
+ subtle::PlatformSharedMemoryRegion handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(WritableSharedMemoryRegion);
+};
+
+} // namespace base
+
+#endif // BASE_MEMORY_WRITABLE_SHARED_MEMORY_REGION_H_
diff --git a/base/message_loop/incoming_task_queue.cc b/base/message_loop/incoming_task_queue.cc
index 316b5ec645..6821730f19 100644
--- a/base/message_loop/incoming_task_queue.cc
+++ b/base/message_loop/incoming_task_queue.cc
@@ -7,8 +7,10 @@
#include <limits>
#include <utility>
+#include "base/bind.h"
+#include "base/callback_helpers.h"
#include "base/location.h"
-#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram_macros.h"
#include "base/synchronization/waitable_event.h"
#include "base/time/time.h"
#include "build/build_config.h"
@@ -21,23 +23,9 @@ namespace {
#if DCHECK_IS_ON()
// Delays larger than this are often bogus, and a warning should be emitted in
// debug builds to warn developers. http://crbug.com/450045
-const int kTaskDelayWarningThresholdInSeconds =
- 14 * 24 * 60 * 60; // 14 days.
+constexpr TimeDelta kTaskDelayWarningThreshold = TimeDelta::FromDays(14);
#endif
-// Returns true if MessagePump::ScheduleWork() must be called one
-// time for every task that is added to the MessageLoop incoming queue.
-bool AlwaysNotifyPump(MessageLoop::Type type) {
-#if defined(OS_ANDROID)
- // The Android UI message loop needs to get notified each time a task is
- // added
- // to the incoming queue.
- return type == MessageLoop::TYPE_UI || type == MessageLoop::TYPE_JAVA;
-#else
- return false;
-#endif
-}
-
TimeTicks CalculateDelayedRuntime(TimeDelta delay) {
TimeTicks delayed_run_time;
if (delay > TimeDelta())
@@ -49,23 +37,25 @@ TimeTicks CalculateDelayedRuntime(TimeDelta delay) {
} // namespace
-IncomingTaskQueue::IncomingTaskQueue(MessageLoop* message_loop)
- : high_res_task_count_(0),
- message_loop_(message_loop),
- next_sequence_num_(0),
- message_loop_scheduled_(false),
- always_schedule_work_(AlwaysNotifyPump(message_loop_->type())),
- is_ready_for_scheduling_(false) {
-}
-
-bool IncomingTaskQueue::AddToIncomingQueue(
- const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay,
- bool nestable) {
- DCHECK(task);
- DLOG_IF(WARNING,
- delay.InSeconds() > kTaskDelayWarningThresholdInSeconds)
+IncomingTaskQueue::IncomingTaskQueue(
+ std::unique_ptr<Observer> task_queue_observer)
+ : task_queue_observer_(std::move(task_queue_observer)),
+ triage_tasks_(this) {
+ // The constructing sequence is not necessarily the running sequence, e.g. in
+ // the case of a MessageLoop created unbound.
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+}
+
+IncomingTaskQueue::~IncomingTaskQueue() = default;
+
+bool IncomingTaskQueue::AddToIncomingQueue(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay,
+ Nestable nestable) {
+ // Use CHECK instead of DCHECK to crash earlier. See http://crbug.com/711167
+ // for details.
+ CHECK(task);
+ DLOG_IF(WARNING, delay > kTaskDelayWarningThreshold)
<< "Requesting super-long task delay period of " << delay.InSeconds()
<< " seconds from here: " << from_here.ToString();
@@ -81,120 +71,238 @@ bool IncomingTaskQueue::AddToIncomingQueue(
pending_task.is_high_res = true;
}
#endif
+
+ if (!delay.is_zero())
+ UMA_HISTOGRAM_LONG_TIMES("MessageLoop.DelayedTaskQueue.PostedDelay", delay);
+
return PostPendingTask(&pending_task);
}
-bool IncomingTaskQueue::HasHighResolutionTasks() {
- AutoLock lock(incoming_queue_lock_);
- return high_res_task_count_ > 0;
+void IncomingTaskQueue::Shutdown() {
+ AutoLock auto_lock(incoming_queue_lock_);
+ accept_new_tasks_ = false;
}
-bool IncomingTaskQueue::IsIdleForTesting() {
- AutoLock lock(incoming_queue_lock_);
- return incoming_queue_.empty();
+void IncomingTaskQueue::ReportMetricsOnIdle() const {
+ UMA_HISTOGRAM_COUNTS_1M(
+ "MessageLoop.DelayedTaskQueue.PendingTasksCountOnIdle",
+ delayed_tasks_.Size());
}
-int IncomingTaskQueue::ReloadWorkQueue(TaskQueue* work_queue) {
- // Make sure no tasks are lost.
- DCHECK(work_queue->empty());
+IncomingTaskQueue::TriageQueue::TriageQueue(IncomingTaskQueue* outer)
+ : outer_(outer) {}
- // Acquire all we can from the inter-thread queue with one lock acquisition.
- AutoLock lock(incoming_queue_lock_);
- if (incoming_queue_.empty()) {
- // If the loop attempts to reload but there are no tasks in the incoming
- // queue, that means it will go to sleep waiting for more work. If the
- // incoming queue becomes nonempty we need to schedule it again.
- message_loop_scheduled_ = false;
- } else {
- incoming_queue_.swap(*work_queue);
- }
- // Reset the count of high resolution tasks since our queue is now empty.
- int high_res_tasks = high_res_task_count_;
- high_res_task_count_ = 0;
- return high_res_tasks;
+IncomingTaskQueue::TriageQueue::~TriageQueue() = default;
+
+const PendingTask& IncomingTaskQueue::TriageQueue::Peek() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_);
+ ReloadFromIncomingQueueIfEmpty();
+ DCHECK(!queue_.empty());
+ return queue_.front();
}
-void IncomingTaskQueue::WillDestroyCurrentMessageLoop() {
- base::subtle::AutoWriteLock lock(message_loop_lock_);
- message_loop_ = NULL;
+PendingTask IncomingTaskQueue::TriageQueue::Pop() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_);
+ ReloadFromIncomingQueueIfEmpty();
+ DCHECK(!queue_.empty());
+ PendingTask pending_task = std::move(queue_.front());
+ queue_.pop();
+ return pending_task;
}
-void IncomingTaskQueue::StartScheduling() {
- bool schedule_work;
- {
- AutoLock lock(incoming_queue_lock_);
- DCHECK(!is_ready_for_scheduling_);
- DCHECK(!message_loop_scheduled_);
- is_ready_for_scheduling_ = true;
- schedule_work = !incoming_queue_.empty();
+bool IncomingTaskQueue::TriageQueue::HasTasks() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_);
+ ReloadFromIncomingQueueIfEmpty();
+ return !queue_.empty();
+}
+
+void IncomingTaskQueue::TriageQueue::Clear() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_);
+
+ // Clear() should be invoked before WillDestroyCurrentMessageLoop().
+ DCHECK(outer_->accept_new_tasks_);
+
+ // Delete all currently pending tasks but not tasks potentially posted from
+ // their destructors. See ~MessageLoop() for the full logic mitigating against
+ // infite loops when clearing pending tasks. The ScopedClosureRunner below
+ // will be bound to a task posted at the end of the queue. After it is posted,
+ // tasks will be deleted one by one, when the bound ScopedClosureRunner is
+ // deleted and sets |deleted_all_originally_pending|, we know we've deleted
+ // all originally pending tasks.
+ bool deleted_all_originally_pending = false;
+ ScopedClosureRunner capture_deleted_all_originally_pending(BindOnce(
+ [](bool* deleted_all_originally_pending) {
+ *deleted_all_originally_pending = true;
+ },
+ Unretained(&deleted_all_originally_pending)));
+ outer_->AddToIncomingQueue(
+ FROM_HERE,
+ BindOnce([](ScopedClosureRunner) {},
+ std::move(capture_deleted_all_originally_pending)),
+ TimeDelta(), Nestable::kNestable);
+
+ while (!deleted_all_originally_pending) {
+ PendingTask pending_task = Pop();
+
+ if (!pending_task.delayed_run_time.is_null()) {
+ outer_->delayed_tasks().Push(std::move(pending_task));
+ }
}
- if (schedule_work) {
- DCHECK(message_loop_);
- // Don't need to lock |message_loop_lock_| here because this function is
- // called by MessageLoop on its thread.
- message_loop_->ScheduleWork();
+}
+
+void IncomingTaskQueue::TriageQueue::ReloadFromIncomingQueueIfEmpty() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(outer_->sequence_checker_);
+ if (queue_.empty()) {
+ outer_->ReloadWorkQueue(&queue_);
}
}
-IncomingTaskQueue::~IncomingTaskQueue() {
- // Verify that WillDestroyCurrentMessageLoop() has been called.
- DCHECK(!message_loop_);
+IncomingTaskQueue::DelayedQueue::DelayedQueue() {
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+}
+
+IncomingTaskQueue::DelayedQueue::~DelayedQueue() = default;
+
+void IncomingTaskQueue::DelayedQueue::Push(PendingTask pending_task) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (pending_task.is_high_res)
+ ++pending_high_res_tasks_;
+
+ queue_.push(std::move(pending_task));
+}
+
+const PendingTask& IncomingTaskQueue::DelayedQueue::Peek() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!queue_.empty());
+ return queue_.top();
+}
+
+PendingTask IncomingTaskQueue::DelayedQueue::Pop() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!queue_.empty());
+ PendingTask delayed_task = std::move(const_cast<PendingTask&>(queue_.top()));
+ queue_.pop();
+
+ if (delayed_task.is_high_res)
+ --pending_high_res_tasks_;
+ DCHECK_GE(pending_high_res_tasks_, 0);
+
+ return delayed_task;
+}
+
+bool IncomingTaskQueue::DelayedQueue::HasTasks() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // TODO(robliao): The other queues don't check for IsCancelled(). Should they?
+ while (!queue_.empty() && Peek().task.IsCancelled())
+ Pop();
+
+ return !queue_.empty();
+}
+
+void IncomingTaskQueue::DelayedQueue::Clear() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ while (!queue_.empty())
+ Pop();
+}
+
+size_t IncomingTaskQueue::DelayedQueue::Size() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return queue_.size();
+}
+
+IncomingTaskQueue::DeferredQueue::DeferredQueue() {
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+}
+
+IncomingTaskQueue::DeferredQueue::~DeferredQueue() = default;
+
+void IncomingTaskQueue::DeferredQueue::Push(PendingTask pending_task) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ queue_.push(std::move(pending_task));
+}
+
+const PendingTask& IncomingTaskQueue::DeferredQueue::Peek() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!queue_.empty());
+ return queue_.front();
+}
+
+PendingTask IncomingTaskQueue::DeferredQueue::Pop() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!queue_.empty());
+ PendingTask deferred_task = std::move(queue_.front());
+ queue_.pop();
+ return deferred_task;
+}
+
+bool IncomingTaskQueue::DeferredQueue::HasTasks() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return !queue_.empty();
+}
+
+void IncomingTaskQueue::DeferredQueue::Clear() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ while (!queue_.empty())
+ Pop();
}
bool IncomingTaskQueue::PostPendingTask(PendingTask* pending_task) {
// Warning: Don't try to short-circuit, and handle this thread's tasks more
// directly, as it could starve handling of foreign threads. Put every task
// into this queue.
+ bool accept_new_tasks;
+ bool was_empty = false;
+ {
+ AutoLock auto_lock(incoming_queue_lock_);
+ accept_new_tasks = accept_new_tasks_;
+ if (accept_new_tasks) {
+ was_empty =
+ PostPendingTaskLockRequired(pending_task) && triage_queue_empty_;
+ }
+ }
- // Ensures |message_loop_| isn't destroyed while running.
- base::subtle::AutoReadLock hold_message_loop(message_loop_lock_);
-
- if (!message_loop_) {
+ if (!accept_new_tasks) {
+ // Clear the pending task outside of |incoming_queue_lock_| to prevent any
+ // chance of self-deadlock if destroying a task also posts a task to this
+ // queue.
pending_task->task.Reset();
return false;
}
- bool schedule_work = false;
- {
- AutoLock hold(incoming_queue_lock_);
+ // Let |task_queue_observer_| know of the queued task. This is done outside
+ // |incoming_queue_lock_| to avoid conflating locks (DidQueueTask() can also
+ // use a lock).
+ task_queue_observer_->DidQueueTask(was_empty);
-#if defined(OS_WIN)
- if (pending_task->is_high_res)
- ++high_res_task_count_;
-#endif
+ return true;
+}
- // Initialize the sequence number. The sequence number is used for delayed
- // tasks (to facilitate FIFO sorting when two tasks have the same
- // delayed_run_time value) and for identifying the task in about:tracing.
- pending_task->sequence_num = next_sequence_num_++;
-
- message_loop_->task_annotator()->DidQueueTask("MessageLoop::PostTask",
- *pending_task);
-
- bool was_empty = incoming_queue_.empty();
- incoming_queue_.push(std::move(*pending_task));
-
- if (is_ready_for_scheduling_ &&
- (always_schedule_work_ || (!message_loop_scheduled_ && was_empty))) {
- schedule_work = true;
- // After we've scheduled the message loop, we do not need to do so again
- // until we know it has processed all of the work in our queue and is
- // waiting for more work again. The message loop will always attempt to
- // reload from the incoming queue before waiting again so we clear this
- // flag in ReloadWorkQueue().
- message_loop_scheduled_ = true;
- }
- }
+bool IncomingTaskQueue::PostPendingTaskLockRequired(PendingTask* pending_task) {
+ incoming_queue_lock_.AssertAcquired();
- // Wake up the message loop and schedule work. This is done outside
- // |incoming_queue_lock_| because signaling the message loop may cause this
- // thread to be switched. If |incoming_queue_lock_| is held, any other thread
- // that wants to post a task will be blocked until this thread switches back
- // in and releases |incoming_queue_lock_|.
- if (schedule_work)
- message_loop_->ScheduleWork();
+ // Initialize the sequence number. The sequence number is used for delayed
+ // tasks (to facilitate FIFO sorting when two tasks have the same
+ // delayed_run_time value) and for identifying the task in about:tracing.
+ pending_task->sequence_num = next_sequence_num_++;
- return true;
+ task_queue_observer_->WillQueueTask(pending_task);
+
+ bool was_empty = incoming_queue_.empty();
+ incoming_queue_.push(std::move(*pending_task));
+ return was_empty;
+}
+
+void IncomingTaskQueue::ReloadWorkQueue(TaskQueue* work_queue) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Make sure no tasks are lost.
+ DCHECK(work_queue->empty());
+
+ // Acquire all we can from the inter-thread queue with one lock acquisition.
+ AutoLock lock(incoming_queue_lock_);
+ incoming_queue_.swap(*work_queue);
+ triage_queue_empty_ = work_queue->empty();
}
} // namespace internal
diff --git a/base/message_loop/incoming_task_queue.h b/base/message_loop/incoming_task_queue.h
index 17bea07674..bdcd6d7a75 100644
--- a/base/message_loop/incoming_task_queue.h
+++ b/base/message_loop/incoming_task_queue.h
@@ -10,13 +10,13 @@
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/pending_task.h"
+#include "base/sequence_checker.h"
#include "base/synchronization/lock.h"
-#include "base/synchronization/read_write_lock.h"
#include "base/time/time.h"
namespace base {
-class MessageLoop;
+class BasicPostTaskPerfTest;
namespace internal {
@@ -26,7 +26,67 @@ namespace internal {
class BASE_EXPORT IncomingTaskQueue
: public RefCountedThreadSafe<IncomingTaskQueue> {
public:
- explicit IncomingTaskQueue(MessageLoop* message_loop);
+ // TODO(gab): Move this to SequencedTaskSource::Observer in
+ // https://chromium-review.googlesource.com/c/chromium/src/+/1088762.
+ class Observer {
+ public:
+ virtual ~Observer() = default;
+
+ // Notifies this Observer that it is about to enqueue |task|. The Observer
+ // may alter |task| as a result (e.g. add metadata to the PendingTask
+ // struct). This may be called while holding a lock and shouldn't perform
+ // logic requiring synchronization (override DidQueueTask() for that).
+ virtual void WillQueueTask(PendingTask* task) = 0;
+
+ // Notifies this Observer that a task was queued in the IncomingTaskQueue it
+ // observes. |was_empty| is true if the task source was empty (i.e.
+ // |!HasTasks()|) before this task was posted. DidQueueTask() can be invoked
+ // from any thread.
+ virtual void DidQueueTask(bool was_empty) = 0;
+ };
+
+ // Provides a read and remove only view into a task queue.
+ class ReadAndRemoveOnlyQueue {
+ public:
+ ReadAndRemoveOnlyQueue() = default;
+ virtual ~ReadAndRemoveOnlyQueue() = default;
+
+ // Returns the next task. HasTasks() is assumed to be true.
+ virtual const PendingTask& Peek() = 0;
+
+ // Removes and returns the next task. HasTasks() is assumed to be true.
+ virtual PendingTask Pop() = 0;
+
+ // Whether this queue has tasks.
+ virtual bool HasTasks() = 0;
+
+ // Removes all tasks.
+ virtual void Clear() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReadAndRemoveOnlyQueue);
+ };
+
+ // Provides a read-write task queue.
+ class Queue : public ReadAndRemoveOnlyQueue {
+ public:
+ Queue() = default;
+ ~Queue() override = default;
+
+ // Adds the task to the end of the queue.
+ virtual void Push(PendingTask pending_task) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Queue);
+ };
+
+ // Constructs an IncomingTaskQueue which will invoke |task_queue_observer|
+ // when tasks are queued. |task_queue_observer| will be bound to this
+ // IncomingTaskQueue's lifetime. Ownership is required as opposed to a raw
+ // pointer since IncomingTaskQueue is ref-counted. For the same reasons,
+ // |task_queue_observer| needs to support being invoked racily during
+ // shutdown).
+ explicit IncomingTaskQueue(std::unique_ptr<Observer> task_queue_observer);
// Appends a task to the incoming queue. Posting of all tasks is routed though
// AddToIncomingQueue() or TryAddToIncomingQueue() to make sure that posting
@@ -35,32 +95,130 @@ class BASE_EXPORT IncomingTaskQueue
// Returns true if the task was successfully added to the queue, otherwise
// returns false. In all cases, the ownership of |task| is transferred to the
// called method.
- bool AddToIncomingQueue(const tracked_objects::Location& from_here,
+ bool AddToIncomingQueue(const Location& from_here,
OnceClosure task,
TimeDelta delay,
- bool nestable);
+ Nestable nestable);
- // Returns true if the queue contains tasks that require higher than default
- // timer resolution. Currently only needed for Windows.
- bool HasHighResolutionTasks();
+ // Instructs this IncomingTaskQueue to stop accepting tasks, this cannot be
+ // undone. Note that the registered IncomingTaskQueue::Observer may still
+ // racily receive a few DidQueueTask() calls while the Shutdown() signal
+ // propagates to other threads and it needs to support that.
+ void Shutdown();
- // Returns true if the message loop is "idle". Provided for testing.
- bool IsIdleForTesting();
+ ReadAndRemoveOnlyQueue& triage_tasks() { return triage_tasks_; }
- // Loads tasks from the |incoming_queue_| into |*work_queue|. Must be called
- // from the thread that is running the loop. Returns the number of tasks that
- // require high resolution timers.
- int ReloadWorkQueue(TaskQueue* work_queue);
+ Queue& delayed_tasks() { return delayed_tasks_; }
+
+ Queue& deferred_tasks() { return deferred_tasks_; }
- // Disconnects |this| from the parent message loop.
- void WillDestroyCurrentMessageLoop();
+ bool HasPendingHighResolutionTasks() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return delayed_tasks_.HasPendingHighResolutionTasks();
+ }
- // This should be called when the message loop becomes ready for
- // scheduling work.
- void StartScheduling();
+ // Reports UMA metrics about its queues before the MessageLoop goes to sleep
+ // per being idle.
+ void ReportMetricsOnIdle() const;
private:
+ friend class base::BasicPostTaskPerfTest;
friend class RefCountedThreadSafe<IncomingTaskQueue>;
+
+ // These queues below support the previous MessageLoop behavior of
+ // maintaining three queue queues to process tasks:
+ //
+ // TriageQueue
+ // The first queue to receive all tasks for the processing sequence (when
+ // reloading from the thread-safe |incoming_queue_|). Tasks are generally
+ // either dispatched immediately or sent to the queues below.
+ //
+ // DelayedQueue
+ // The queue for holding tasks that should be run later and sorted by expected
+ // run time.
+ //
+ // DeferredQueue
+ // The queue for holding tasks that couldn't be run while the MessageLoop was
+ // nested. These are generally processed during the idle stage.
+ //
+ // Many of these do not share implementations even though they look like they
+ // could because of small quirks (reloading semantics) or differing underlying
+ // data strucutre (TaskQueue vs DelayedTaskQueue).
+
+ // The starting point for all tasks on the sequence processing the tasks.
+ class TriageQueue : public ReadAndRemoveOnlyQueue {
+ public:
+ TriageQueue(IncomingTaskQueue* outer);
+ ~TriageQueue() override;
+
+ // ReadAndRemoveOnlyQueue:
+ // The methods below will attempt to reload from the incoming queue if the
+ // queue itself is empty (Clear() has special logic to reload only once
+ // should destructors post more tasks).
+ const PendingTask& Peek() override;
+ PendingTask Pop() override;
+ // Whether this queue has tasks after reloading from the incoming queue.
+ bool HasTasks() override;
+ void Clear() override;
+
+ private:
+ void ReloadFromIncomingQueueIfEmpty();
+
+ IncomingTaskQueue* const outer_;
+ TaskQueue queue_;
+
+ DISALLOW_COPY_AND_ASSIGN(TriageQueue);
+ };
+
+ class DelayedQueue : public Queue {
+ public:
+ DelayedQueue();
+ ~DelayedQueue() override;
+
+ // Queue:
+ const PendingTask& Peek() override;
+ PendingTask Pop() override;
+ // Whether this queue has tasks after sweeping the cancelled ones in front.
+ bool HasTasks() override;
+ void Clear() override;
+ void Push(PendingTask pending_task) override;
+
+ size_t Size() const;
+ bool HasPendingHighResolutionTasks() const {
+ return pending_high_res_tasks_ > 0;
+ }
+
+ private:
+ DelayedTaskQueue queue_;
+
+ // Number of high resolution tasks in |queue_|.
+ int pending_high_res_tasks_ = 0;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ DISALLOW_COPY_AND_ASSIGN(DelayedQueue);
+ };
+
+ class DeferredQueue : public Queue {
+ public:
+ DeferredQueue();
+ ~DeferredQueue() override;
+
+ // Queue:
+ const PendingTask& Peek() override;
+ PendingTask Pop() override;
+ bool HasTasks() override;
+ void Clear() override;
+ void Push(PendingTask pending_task) override;
+
+ private:
+ TaskQueue queue_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ DISALLOW_COPY_AND_ASSIGN(DeferredQueue);
+ };
+
virtual ~IncomingTaskQueue();
// Adds a task to |incoming_queue_|. The caller retains ownership of
@@ -69,42 +227,48 @@ class BASE_EXPORT IncomingTaskQueue
// does not retain |pending_task->task| beyond this function call.
bool PostPendingTask(PendingTask* pending_task);
- // Wakes up the message loop and schedules work.
- void ScheduleWork();
+ // Does the real work of posting a pending task. Returns true if
+ // |incoming_queue_| was empty before |pending_task| was posted.
+ bool PostPendingTaskLockRequired(PendingTask* pending_task);
+
+ // Loads tasks from the |incoming_queue_| into |*work_queue|. Must be called
+ // from the sequence processing the tasks.
+ void ReloadWorkQueue(TaskQueue* work_queue);
- // Number of tasks that require high resolution timing. This value is kept
- // so that ReloadWorkQueue() completes in constant time.
- int high_res_task_count_;
+ // Checks calls made only on the MessageLoop thread.
+ SEQUENCE_CHECKER(sequence_checker_);
- // The lock that protects access to the members of this class, except
- // |message_loop_|.
- base::Lock incoming_queue_lock_;
+ const std::unique_ptr<Observer> task_queue_observer_;
+
+ // Queue for initial triaging of tasks on the |sequence_checker_| sequence.
+ TriageQueue triage_tasks_;
- // Lock that protects |message_loop_| to prevent it from being deleted while a
- // task is being posted.
- base::subtle::ReadWriteLock message_loop_lock_;
+ // Queue for delayed tasks on the |sequence_checker_| sequence.
+ DelayedQueue delayed_tasks_;
+
+ // Queue for non-nestable deferred tasks on the |sequence_checker_| sequence.
+ DeferredQueue deferred_tasks_;
+
+ // Synchronizes access to all members below this line.
+ base::Lock incoming_queue_lock_;
// An incoming queue of tasks that are acquired under a mutex for processing
// on this instance's thread. These tasks have not yet been been pushed to
- // |message_loop_|.
+ // |triage_tasks_|.
TaskQueue incoming_queue_;
- // Points to the message loop that owns |this|.
- MessageLoop* message_loop_;
+ // True if new tasks should be accepted.
+ bool accept_new_tasks_ = true;
// The next sequence number to use for delayed tasks.
- int next_sequence_num_;
-
- // True if our message loop has already been scheduled and does not need to be
- // scheduled again until an empty reload occurs.
- bool message_loop_scheduled_;
-
- // True if we always need to call ScheduleWork when receiving a new task, even
- // if the incoming queue was not empty.
- const bool always_schedule_work_;
+ int next_sequence_num_ = 0;
- // False until StartScheduling() is called.
- bool is_ready_for_scheduling_;
+ // True if the outgoing queue (|triage_tasks_|) is empty. Toggled under
+ // |incoming_queue_lock_| in ReloadWorkQueue() so that
+ // PostPendingTaskLockRequired() can tell, without accessing the thread unsafe
+ // |triage_tasks_|, if the IncomingTaskQueue has been made non-empty by a
+ // PostTask() (and needs to inform its Observer).
+ bool triage_queue_empty_ = true;
DISALLOW_COPY_AND_ASSIGN(IncomingTaskQueue);
};
diff --git a/base/message_loop/message_loop.cc b/base/message_loop/message_loop.cc
index 3d55920afd..b3e32de0ef 100644
--- a/base/message_loop/message_loop.cc
+++ b/base/message_loop/message_loop.cc
@@ -9,73 +9,154 @@
#include "base/bind.h"
#include "base/compiler_specific.h"
+#include "base/debug/task_annotator.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_pump_default.h"
+#include "base/message_loop/message_pump_for_io.h"
+#include "base/message_loop/message_pump_for_ui.h"
+#include "base/metrics/histogram_macros.h"
#include "base/run_loop.h"
#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
#include "base/threading/thread_id_name_manager.h"
-#include "base/threading/thread_local.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/trace_event.h"
#if defined(OS_MACOSX)
#include "base/message_loop/message_pump_mac.h"
#endif
-#if defined(OS_POSIX) && !defined(OS_IOS)
-#include "base/message_loop/message_pump_libevent.h"
-#endif
-#if defined(OS_ANDROID)
-#include "base/message_loop/message_pump_android.h"
-#endif
-#if defined(USE_GLIB)
-#include "base/message_loop/message_pump_glib.h"
-#endif
namespace base {
namespace {
-// A lazily created thread local storage for quick access to a thread's message
-// loop, if one exists.
-base::ThreadLocalPointer<MessageLoop>* GetTLSMessageLoop() {
- static auto* lazy_tls_ptr = new base::ThreadLocalPointer<MessageLoop>();
- return lazy_tls_ptr;
-}
-MessageLoop::MessagePumpFactory* message_pump_for_ui_factory_ = NULL;
-
-#if defined(OS_IOS)
-typedef MessagePumpIOSForIO MessagePumpForIO;
-#elif defined(OS_NACL_SFI)
-typedef MessagePumpDefault MessagePumpForIO;
-#elif defined(OS_POSIX)
-typedef MessagePumpLibevent MessagePumpForIO;
-#endif
-
-#if !defined(OS_NACL_SFI)
-MessagePumpForIO* ToPumpIO(MessagePump* pump) {
- return static_cast<MessagePumpForIO*>(pump);
-}
-#endif // !defined(OS_NACL_SFI)
+MessageLoop::MessagePumpFactory* message_pump_for_ui_factory_ = nullptr;
std::unique_ptr<MessagePump> ReturnPump(std::unique_ptr<MessagePump> pump) {
return pump;
}
+enum class ScheduledWakeupResult {
+ // The MessageLoop went to sleep with a timeout and woke up because of that
+ // timeout.
+ kCompleted,
+ // The MessageLoop went to sleep with a timeout but was woken up before it
+ // fired.
+ kInterrupted,
+};
+
+// Reports a ScheduledWakeup's result when waking up from a non-infinite sleep.
+// Reports are using a 14 day spread (maximum examined delay for
+// https://crbug.com/850450#c3), with 50 buckets that still yields 7 buckets
+// under 16ms and hence plenty of resolution.
+void ReportScheduledWakeupResult(ScheduledWakeupResult result,
+ TimeDelta intended_sleep) {
+ switch (result) {
+ case ScheduledWakeupResult::kCompleted:
+ UMA_HISTOGRAM_CUSTOM_TIMES("MessageLoop.ScheduledSleep.Completed",
+ intended_sleep,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromDays(14), 50);
+ break;
+ case ScheduledWakeupResult::kInterrupted:
+ UMA_HISTOGRAM_CUSTOM_TIMES("MessageLoop.ScheduledSleep.Interrupted",
+ intended_sleep,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromDays(14), 50);
+ break;
+ }
+}
+
} // namespace
-//------------------------------------------------------------------------------
+class MessageLoop::Controller : public internal::IncomingTaskQueue::Observer {
+ public:
+ // Constructs a MessageLoopController which controls |message_loop|, notifying
+ // |task_annotator_| when tasks are queued scheduling work on |message_loop|
+ // as fits. |message_loop| and |task_annotator_| will not be used after
+ // DisconnectFromParent() returns.
+ Controller(MessageLoop* message_loop);
+
+ ~Controller() override;
+
+ // IncomingTaskQueue::Observer:
+ void WillQueueTask(PendingTask* task) final;
+ void DidQueueTask(bool was_empty) final;
+
+ void StartScheduling();
+
+ // Disconnects |message_loop_| from this Controller instance (DidQueueTask()
+ // will no-op from this point forward).
+ void DisconnectFromParent();
+
+ // Shares this Controller's TaskAnnotator with MessageLoop as TaskAnnotator
+ // requires DidQueueTask(x)/RunTask(x) to be invoked on the same TaskAnnotator
+ // instance.
+ debug::TaskAnnotator& task_annotator() { return task_annotator_; }
+
+ private:
+ // A TaskAnnotator which is owned by this Controller to be able to use it
+ // without locking |message_loop_lock_|. It cannot be owned by MessageLoop
+ // because this Controller cannot access |message_loop_| safely without the
+ // lock. Note: the TaskAnnotator API itself is thread-safe.
+ debug::TaskAnnotator task_annotator_;
+
+ // Lock that serializes |message_loop_->ScheduleWork()| and access to all
+ // members below.
+ base::Lock message_loop_lock_;
+
+ // Points to this Controller's outer MessageLoop instance. Null after
+ // DisconnectFromParent().
+ MessageLoop* message_loop_;
+
+ // False until StartScheduling() is called.
+ bool is_ready_for_scheduling_ = false;
+
+ // True if DidQueueTask() has been called before StartScheduling(); letting it
+ // know whether it needs to ScheduleWork() right away or not.
+ bool pending_schedule_work_ = false;
-MessageLoop::TaskObserver::TaskObserver() {
+ DISALLOW_COPY_AND_ASSIGN(Controller);
+};
+
+MessageLoop::Controller::Controller(MessageLoop* message_loop)
+ : message_loop_(message_loop) {}
+
+MessageLoop::Controller::~Controller() {
+ DCHECK(!message_loop_)
+ << "DisconnectFromParent() needs to be invoked before destruction.";
}
-MessageLoop::TaskObserver::~TaskObserver() {
+void MessageLoop::Controller::WillQueueTask(PendingTask* task) {
+ task_annotator_.WillQueueTask("MessageLoop::PostTask", task);
}
-MessageLoop::DestructionObserver::~DestructionObserver() {
+void MessageLoop::Controller::DidQueueTask(bool was_empty) {
+ // Avoid locking if we don't need to schedule.
+ if (!was_empty)
+ return;
+
+ AutoLock auto_lock(message_loop_lock_);
+
+ if (message_loop_ && is_ready_for_scheduling_)
+ message_loop_->ScheduleWork();
+ else
+ pending_schedule_work_ = true;
}
-MessageLoop::NestingObserver::~NestingObserver() {}
+void MessageLoop::Controller::StartScheduling() {
+ AutoLock lock(message_loop_lock_);
+ DCHECK(message_loop_);
+ DCHECK(!is_ready_for_scheduling_);
+ is_ready_for_scheduling_ = true;
+ if (pending_schedule_work_)
+ message_loop_->ScheduleWork();
+}
+
+void MessageLoop::Controller::DisconnectFromParent() {
+ AutoLock lock(message_loop_lock_);
+ message_loop_ = nullptr;
+}
//------------------------------------------------------------------------------
@@ -85,7 +166,7 @@ MessageLoop::MessageLoop(Type type)
}
MessageLoop::MessageLoop(std::unique_ptr<MessagePump> pump)
- : MessageLoop(TYPE_CUSTOM, Bind(&ReturnPump, Passed(&pump))) {
+ : MessageLoop(TYPE_CUSTOM, BindOnce(&ReturnPump, std::move(pump))) {
BindToCurrentThread();
}
@@ -94,13 +175,19 @@ MessageLoop::~MessageLoop() {
// current one on this thread. Otherwise, this loop is being destructed before
// it was bound to a thread, so a different message loop (or no loop at all)
// may be current.
- DCHECK((pump_ && current() == this) || (!pump_ && current() != this));
+ DCHECK((pump_ && MessageLoopCurrent::IsBoundToCurrentThreadInternal(this)) ||
+ (!pump_ && !MessageLoopCurrent::IsBoundToCurrentThreadInternal(this)));
// iOS just attaches to the loop, it doesn't Run it.
// TODO(stuartmorgan): Consider wiring up a Detach().
#if !defined(OS_IOS)
- DCHECK(!run_loop_);
-#endif
+ // There should be no active RunLoops on this thread, unless this MessageLoop
+ // isn't bound to the current thread (see other condition at the top of this
+ // method).
+ DCHECK(
+ (!pump_ && !MessageLoopCurrent::IsBoundToCurrentThreadInternal(this)) ||
+ !RunLoop::IsRunningOnCurrentThread());
+#endif // !defined(OS_IOS)
#if defined(OS_WIN)
if (in_high_res_mode_)
@@ -112,16 +199,15 @@ MessageLoop::~MessageLoop() {
// tasks. Normally, we should only pass through this loop once or twice. If
// we end up hitting the loop limit, then it is probably due to one task that
// is being stubborn. Inspect the queues to see who is left.
- bool did_work;
+ bool tasks_remain;
for (int i = 0; i < 100; ++i) {
DeletePendingTasks();
- ReloadWorkQueue();
// If we end up with empty queues, then break out of the loop.
- did_work = DeletePendingTasks();
- if (!did_work)
+ tasks_remain = incoming_task_queue_->triage_tasks().HasTasks();
+ if (!tasks_remain)
break;
}
- DCHECK(!did_work);
+ DCHECK(!tasks_remain);
// Let interested parties have one last shot at accessing this.
for (auto& observer : destruction_observers_)
@@ -130,22 +216,20 @@ MessageLoop::~MessageLoop() {
thread_task_runner_handle_.reset();
// Tell the incoming queue that we are dying.
- incoming_task_queue_->WillDestroyCurrentMessageLoop();
- incoming_task_queue_ = NULL;
- unbound_task_runner_ = NULL;
- task_runner_ = NULL;
+ message_loop_controller_->DisconnectFromParent();
+ incoming_task_queue_->Shutdown();
+ incoming_task_queue_ = nullptr;
+ unbound_task_runner_ = nullptr;
+ task_runner_ = nullptr;
// OK, now make it so that no one can find us.
- if (current() == this)
- GetTLSMessageLoop()->Set(nullptr);
+ if (MessageLoopCurrent::IsBoundToCurrentThreadInternal(this))
+ MessageLoopCurrent::UnbindFromCurrentThreadInternal(this);
}
// static
-MessageLoop* MessageLoop::current() {
- // TODO(darin): sadly, we cannot enable this yet since people call us even
- // when they have no intention of using us.
- // DCHECK(loop) << "Ouch, did you forget to initialize me?";
- return GetTLSMessageLoop()->Get();
+MessageLoopCurrent MessageLoop::current() {
+ return MessageLoopCurrent::Get();
}
// static
@@ -159,37 +243,21 @@ bool MessageLoop::InitMessagePumpForUIFactory(MessagePumpFactory* factory) {
// static
std::unique_ptr<MessagePump> MessageLoop::CreateMessagePumpForType(Type type) {
-// TODO(rvargas): Get rid of the OS guards.
-#if defined(USE_GLIB) && !defined(OS_NACL)
- typedef MessagePumpGlib MessagePumpForUI;
-#elif (defined(OS_LINUX) && !defined(OS_NACL)) || defined(OS_BSD)
- typedef MessagePumpLibevent MessagePumpForUI;
-#endif
-
-#if defined(OS_IOS) || defined(OS_MACOSX)
-#define MESSAGE_PUMP_UI std::unique_ptr<MessagePump>(MessagePumpMac::Create())
-#elif defined(OS_NACL)
-// Currently NaCl doesn't have a UI MessageLoop.
-// TODO(abarth): Figure out if we need this.
-#define MESSAGE_PUMP_UI std::unique_ptr<MessagePump>()
-#else
-#define MESSAGE_PUMP_UI std::unique_ptr<MessagePump>(new MessagePumpForUI())
-#endif
-
-#if defined(OS_MACOSX)
- // Use an OS native runloop on Mac to support timer coalescing.
-#define MESSAGE_PUMP_DEFAULT \
- std::unique_ptr<MessagePump>(new MessagePumpCFRunLoop())
-#else
-#define MESSAGE_PUMP_DEFAULT \
- std::unique_ptr<MessagePump>(new MessagePumpDefault())
-#endif
-
if (type == MessageLoop::TYPE_UI) {
if (message_pump_for_ui_factory_)
return message_pump_for_ui_factory_();
- return MESSAGE_PUMP_UI;
+#if defined(OS_IOS) || defined(OS_MACOSX)
+ return MessagePumpMac::Create();
+#elif defined(OS_NACL) || defined(OS_AIX)
+ // Currently NaCl and AIX don't have a UI MessageLoop.
+ // TODO(abarth): Figure out if we need this.
+ NOTREACHED();
+ return nullptr;
+#else
+ return std::make_unique<MessagePumpForUI>();
+#endif
}
+
if (type == MessageLoop::TYPE_IO)
return std::unique_ptr<MessagePump>(new MessagePumpForIO());
@@ -199,108 +267,43 @@ std::unique_ptr<MessagePump> MessageLoop::CreateMessagePumpForType(Type type) {
#endif
DCHECK_EQ(MessageLoop::TYPE_DEFAULT, type);
- return MESSAGE_PUMP_DEFAULT;
-}
-
-void MessageLoop::AddDestructionObserver(
- DestructionObserver* destruction_observer) {
- DCHECK_EQ(this, current());
- destruction_observers_.AddObserver(destruction_observer);
-}
-
-void MessageLoop::RemoveDestructionObserver(
- DestructionObserver* destruction_observer) {
- DCHECK_EQ(this, current());
- destruction_observers_.RemoveObserver(destruction_observer);
-}
-
-void MessageLoop::AddNestingObserver(NestingObserver* observer) {
- DCHECK_EQ(this, current());
- CHECK(allow_nesting_);
- nesting_observers_.AddObserver(observer);
-}
-
-void MessageLoop::RemoveNestingObserver(NestingObserver* observer) {
- DCHECK_EQ(this, current());
- CHECK(allow_nesting_);
- nesting_observers_.RemoveObserver(observer);
-}
-
-void MessageLoop::QuitWhenIdle() {
- DCHECK_EQ(this, current());
- if (run_loop_) {
- run_loop_->QuitWhenIdle();
- } else {
- NOTREACHED() << "Must be inside Run to call QuitWhenIdle";
- }
-}
-
-void MessageLoop::QuitNow() {
- DCHECK_EQ(this, current());
- if (run_loop_) {
- pump_->Quit();
- } else {
- NOTREACHED() << "Must be inside Run to call Quit";
- }
+#if defined(OS_IOS)
+ // On iOS, a native runloop is always required to pump system work.
+ return std::make_unique<MessagePumpCFRunLoop>();
+#else
+ return std::make_unique<MessagePumpDefault>();
+#endif
}
bool MessageLoop::IsType(Type type) const {
return type_ == type;
}
-static void QuitCurrentWhenIdle() {
- MessageLoop::current()->QuitWhenIdle();
-}
-
-// static
-Closure MessageLoop::QuitWhenIdleClosure() {
- return Bind(&QuitCurrentWhenIdle);
-}
-
-void MessageLoop::SetNestableTasksAllowed(bool allowed) {
- if (allowed) {
- CHECK(allow_nesting_);
-
- // Kick the native pump just in case we enter a OS-driven nested message
- // loop.
- pump_->ScheduleWork();
- }
- nestable_tasks_allowed_ = allowed;
-}
-
-bool MessageLoop::NestableTasksAllowed() const {
- return nestable_tasks_allowed_;
-}
-
-bool MessageLoop::IsNested() {
- return run_loop_->run_depth_ > 1;
-}
-
+// TODO(gab): Migrate TaskObservers to RunLoop as part of separating concerns
+// between MessageLoop and RunLoop and making MessageLoop a swappable
+// implementation detail. http://crbug.com/703346
void MessageLoop::AddTaskObserver(TaskObserver* task_observer) {
- DCHECK_EQ(this, current());
- CHECK(allow_task_observers_);
+ DCHECK_CALLED_ON_VALID_THREAD(bound_thread_checker_);
task_observers_.AddObserver(task_observer);
}
void MessageLoop::RemoveTaskObserver(TaskObserver* task_observer) {
- DCHECK_EQ(this, current());
- CHECK(allow_task_observers_);
+ DCHECK_CALLED_ON_VALID_THREAD(bound_thread_checker_);
task_observers_.RemoveObserver(task_observer);
}
-bool MessageLoop::is_running() const {
- DCHECK_EQ(this, current());
- return run_loop_ != NULL;
-}
+bool MessageLoop::IsIdleForTesting() {
+ // Have unprocessed tasks? (this reloads the work queue if necessary)
+ if (incoming_task_queue_->triage_tasks().HasTasks())
+ return false;
-bool MessageLoop::HasHighResolutionTasks() {
- return incoming_task_queue_->HasHighResolutionTasks();
-}
+ // Have unprocessed deferred tasks which can be processed at this run-level?
+ if (incoming_task_queue_->deferred_tasks().HasTasks() &&
+ !RunLoop::IsNestedOnCurrentThread()) {
+ return false;
+ }
-bool MessageLoop::IsIdleForTesting() {
- // We only check the incoming queue, since we don't want to lock the work
- // queue.
- return incoming_task_queue_->IsIdleForTesting();
+ return true;
}
//------------------------------------------------------------------------------
@@ -309,43 +312,59 @@ bool MessageLoop::IsIdleForTesting() {
std::unique_ptr<MessageLoop> MessageLoop::CreateUnbound(
Type type,
MessagePumpFactoryCallback pump_factory) {
- return WrapUnique(new MessageLoop(type, pump_factory));
+ return WrapUnique(new MessageLoop(type, std::move(pump_factory)));
}
+// TODO(gab): Avoid bare new + WrapUnique below when introducing
+// SequencedTaskSource in follow-up @
+// https://chromium-review.googlesource.com/c/chromium/src/+/1088762.
MessageLoop::MessageLoop(Type type, MessagePumpFactoryCallback pump_factory)
- : type_(type),
-#if defined(OS_WIN)
- pending_high_res_tasks_(0),
- in_high_res_mode_(false),
-#endif
- nestable_tasks_allowed_(true),
- pump_factory_(pump_factory),
- run_loop_(nullptr),
- current_pending_task_(nullptr),
- incoming_task_queue_(new internal::IncomingTaskQueue(this)),
- unbound_task_runner_(
- new internal::MessageLoopTaskRunner(incoming_task_queue_)),
- task_runner_(unbound_task_runner_),
- thread_id_(kInvalidThreadId) {
+ : MessageLoopCurrent(this),
+ type_(type),
+ pump_factory_(std::move(pump_factory)),
+ message_loop_controller_(new Controller(this)),
+ incoming_task_queue_(MakeRefCounted<internal::IncomingTaskQueue>(
+ WrapUnique(message_loop_controller_))),
+ unbound_task_runner_(MakeRefCounted<internal::MessageLoopTaskRunner>(
+ incoming_task_queue_)),
+ task_runner_(unbound_task_runner_) {
// If type is TYPE_CUSTOM non-null pump_factory must be given.
DCHECK(type_ != TYPE_CUSTOM || !pump_factory_.is_null());
+
+ // Bound in BindToCurrentThread();
+ DETACH_FROM_THREAD(bound_thread_checker_);
}
void MessageLoop::BindToCurrentThread() {
+ DCHECK_CALLED_ON_VALID_THREAD(bound_thread_checker_);
+
DCHECK(!pump_);
if (!pump_factory_.is_null())
- pump_ = pump_factory_.Run();
+ pump_ = std::move(pump_factory_).Run();
else
pump_ = CreateMessagePumpForType(type_);
- DCHECK(!current()) << "should only have one message loop per thread";
- GetTLSMessageLoop()->Set(this);
+ DCHECK(!MessageLoopCurrent::IsSet())
+ << "should only have one message loop per thread";
+ MessageLoopCurrent::BindToCurrentThreadInternal(this);
- incoming_task_queue_->StartScheduling();
+ message_loop_controller_->StartScheduling();
unbound_task_runner_->BindToCurrentThread();
unbound_task_runner_ = nullptr;
SetThreadTaskRunnerHandle();
thread_id_ = PlatformThread::CurrentId();
+
+ scoped_set_sequence_local_storage_map_for_current_thread_ = std::make_unique<
+ internal::ScopedSetSequenceLocalStorageMapForCurrentThread>(
+ &sequence_local_storage_map_);
+
+ RunLoop::RegisterDelegateForCurrentThread(this);
+
+#if defined(OS_ANDROID)
+ // On Android, attach to the native loop when there is one.
+ if (type_ == TYPE_UI || type_ == TYPE_JAVA)
+ static_cast<MessagePumpForUI*>(pump_.get())->Attach(this);
+#endif
}
std::string MessageLoop::GetThreadName() const {
@@ -357,7 +376,8 @@ std::string MessageLoop::GetThreadName() const {
void MessageLoop::SetTaskRunner(
scoped_refptr<SingleThreadTaskRunner> task_runner) {
- DCHECK_EQ(this, current());
+ DCHECK_CALLED_ON_VALID_THREAD(bound_thread_checker_);
+
DCHECK(task_runner);
DCHECK(task_runner->BelongsToCurrentThread());
DCHECK(!unbound_task_runner_);
@@ -366,168 +386,142 @@ void MessageLoop::SetTaskRunner(
}
void MessageLoop::ClearTaskRunnerForTesting() {
- DCHECK_EQ(this, current());
+ DCHECK_CALLED_ON_VALID_THREAD(bound_thread_checker_);
+
DCHECK(!unbound_task_runner_);
task_runner_ = nullptr;
thread_task_runner_handle_.reset();
}
+void MessageLoop::Run(bool application_tasks_allowed) {
+ DCHECK_CALLED_ON_VALID_THREAD(bound_thread_checker_);
+ if (application_tasks_allowed && !task_execution_allowed_) {
+ // Allow nested task execution as explicitly requested.
+ DCHECK(RunLoop::IsNestedOnCurrentThread());
+ task_execution_allowed_ = true;
+ pump_->Run(this);
+ task_execution_allowed_ = false;
+ } else {
+ pump_->Run(this);
+ }
+}
+
+void MessageLoop::Quit() {
+ DCHECK_CALLED_ON_VALID_THREAD(bound_thread_checker_);
+ pump_->Quit();
+}
+
+void MessageLoop::EnsureWorkScheduled() {
+ DCHECK_CALLED_ON_VALID_THREAD(bound_thread_checker_);
+ if (incoming_task_queue_->triage_tasks().HasTasks())
+ pump_->ScheduleWork();
+}
+
void MessageLoop::SetThreadTaskRunnerHandle() {
- DCHECK_EQ(this, current());
+ DCHECK_CALLED_ON_VALID_THREAD(bound_thread_checker_);
// Clear the previous thread task runner first, because only one can exist at
// a time.
thread_task_runner_handle_.reset();
thread_task_runner_handle_.reset(new ThreadTaskRunnerHandle(task_runner_));
}
-void MessageLoop::RunHandler() {
- DCHECK_EQ(this, current());
- DCHECK(run_loop_);
- CHECK(allow_nesting_ || run_loop_->run_depth_ == 1);
- pump_->Run(this);
-}
-
bool MessageLoop::ProcessNextDelayedNonNestableTask() {
- if (run_loop_->run_depth_ != 1)
+ if (RunLoop::IsNestedOnCurrentThread())
return false;
- if (deferred_non_nestable_work_queue_.empty())
- return false;
-
- PendingTask pending_task =
- std::move(deferred_non_nestable_work_queue_.front());
- deferred_non_nestable_work_queue_.pop();
+ while (incoming_task_queue_->deferred_tasks().HasTasks()) {
+ PendingTask pending_task = incoming_task_queue_->deferred_tasks().Pop();
+ if (!pending_task.task.IsCancelled()) {
+ RunTask(&pending_task);
+ return true;
+ }
+ }
- RunTask(&pending_task);
- return true;
+ return false;
}
void MessageLoop::RunTask(PendingTask* pending_task) {
- DCHECK(nestable_tasks_allowed_);
- current_pending_task_ = pending_task;
-
-#if defined(OS_WIN)
- if (pending_task->is_high_res) {
- pending_high_res_tasks_--;
- CHECK_GE(pending_high_res_tasks_, 0);
- }
-#endif
+ DCHECK(task_execution_allowed_);
// Execute the task and assume the worst: It is probably not reentrant.
- nestable_tasks_allowed_ = false;
+ task_execution_allowed_ = false;
TRACE_TASK_EXECUTION("MessageLoop::RunTask", *pending_task);
for (auto& observer : task_observers_)
observer.WillProcessTask(*pending_task);
- task_annotator_.RunTask("MessageLoop::PostTask", pending_task);
+ message_loop_controller_->task_annotator().RunTask("MessageLoop::PostTask",
+ pending_task);
for (auto& observer : task_observers_)
observer.DidProcessTask(*pending_task);
- nestable_tasks_allowed_ = true;
-
- current_pending_task_ = nullptr;
+ task_execution_allowed_ = true;
}
bool MessageLoop::DeferOrRunPendingTask(PendingTask pending_task) {
- if (pending_task.nestable || run_loop_->run_depth_ == 1) {
+ if (pending_task.nestable == Nestable::kNestable ||
+ !RunLoop::IsNestedOnCurrentThread()) {
RunTask(&pending_task);
// Show that we ran a task (Note: a new one might arrive as a
// consequence!).
return true;
}
- // We couldn't run the task now because we're in a nested message loop
+ // We couldn't run the task now because we're in a nested run loop
// and the task isn't nestable.
- deferred_non_nestable_work_queue_.push(std::move(pending_task));
+ incoming_task_queue_->deferred_tasks().Push(std::move(pending_task));
return false;
}
-void MessageLoop::AddToDelayedWorkQueue(PendingTask pending_task) {
- // Move to the delayed work queue.
- delayed_work_queue_.push(std::move(pending_task));
-}
-
-bool MessageLoop::DeletePendingTasks() {
- bool did_work = !work_queue_.empty();
- while (!work_queue_.empty()) {
- PendingTask pending_task = std::move(work_queue_.front());
- work_queue_.pop();
- if (!pending_task.delayed_run_time.is_null()) {
- // We want to delete delayed tasks in the same order in which they would
- // normally be deleted in case of any funny dependencies between delayed
- // tasks.
- AddToDelayedWorkQueue(std::move(pending_task));
- }
- }
- did_work |= !deferred_non_nestable_work_queue_.empty();
- while (!deferred_non_nestable_work_queue_.empty()) {
- deferred_non_nestable_work_queue_.pop();
- }
- did_work |= !delayed_work_queue_.empty();
-
- // Historically, we always delete the task regardless of valgrind status. It's
- // not completely clear why we want to leak them in the loops above. This
- // code is replicating legacy behavior, and should not be considered
- // absolutely "correct" behavior. See TODO above about deleting all tasks
- // when it's safe.
- while (!delayed_work_queue_.empty()) {
- delayed_work_queue_.pop();
- }
- return did_work;
-}
-
-void MessageLoop::ReloadWorkQueue() {
- // We can improve performance of our loading tasks from the incoming queue to
- // |*work_queue| by waiting until the last minute (|*work_queue| is empty) to
- // load. That reduces the number of locks-per-task significantly when our
- // queues get large.
- if (work_queue_.empty()) {
-#if defined(OS_WIN)
- pending_high_res_tasks_ +=
- incoming_task_queue_->ReloadWorkQueue(&work_queue_);
-#else
- incoming_task_queue_->ReloadWorkQueue(&work_queue_);
-#endif
- }
+void MessageLoop::DeletePendingTasks() {
+ incoming_task_queue_->triage_tasks().Clear();
+ incoming_task_queue_->deferred_tasks().Clear();
+ // TODO(robliao): Determine if we can move delayed task destruction before
+ // deferred tasks to maintain the MessagePump DoWork, DoDelayedWork, and
+ // DoIdleWork processing order.
+ incoming_task_queue_->delayed_tasks().Clear();
}
void MessageLoop::ScheduleWork() {
pump_->ScheduleWork();
}
-void MessageLoop::NotifyBeginNestedLoop() {
- for (auto& observer : nesting_observers_)
- observer.OnBeginNestedMessageLoop();
-}
-
bool MessageLoop::DoWork() {
- if (!nestable_tasks_allowed_) {
- // Task can't be executed right now.
+ if (!task_execution_allowed_)
return false;
- }
- for (;;) {
- ReloadWorkQueue();
- if (work_queue_.empty())
- break;
+ // Execute oldest task.
+ while (incoming_task_queue_->triage_tasks().HasTasks()) {
+ if (!scheduled_wakeup_.next_run_time.is_null()) {
+ // While the frontmost task may racily be ripe. The MessageLoop was awaken
+ // without needing the timeout anyways. Since this metric is about
+ // determining whether sleeping for long periods ever succeeds: it's
+ // easier to just consider any untriaged task as an interrupt (this also
+ // makes the logic simpler for untriaged delayed tasks which may alter the
+ // top of the task queue prior to DoDelayedWork() but did cause a wakeup
+ // regardless -- per currently requiring this immediate triage step even
+ // for long delays).
+ ReportScheduledWakeupResult(ScheduledWakeupResult::kInterrupted,
+ scheduled_wakeup_.intended_sleep);
+ scheduled_wakeup_ = ScheduledWakeup();
+ }
- // Execute oldest task.
- do {
- PendingTask pending_task = std::move(work_queue_.front());
- work_queue_.pop();
- if (!pending_task.delayed_run_time.is_null()) {
- int sequence_num = pending_task.sequence_num;
- TimeTicks delayed_run_time = pending_task.delayed_run_time;
- AddToDelayedWorkQueue(std::move(pending_task));
- // If we changed the topmost task, then it is time to reschedule.
- if (delayed_work_queue_.top().sequence_num == sequence_num)
- pump_->ScheduleDelayedWork(delayed_run_time);
- } else {
- if (DeferOrRunPendingTask(std::move(pending_task)))
- return true;
+ PendingTask pending_task = incoming_task_queue_->triage_tasks().Pop();
+ if (pending_task.task.IsCancelled())
+ continue;
+
+ if (!pending_task.delayed_run_time.is_null()) {
+ int sequence_num = pending_task.sequence_num;
+ TimeTicks delayed_run_time = pending_task.delayed_run_time;
+ incoming_task_queue_->delayed_tasks().Push(std::move(pending_task));
+ // If we changed the topmost task, then it is time to reschedule.
+ if (incoming_task_queue_->delayed_tasks().Peek().sequence_num ==
+ sequence_num) {
+ pump_->ScheduleDelayedWork(delayed_run_time);
}
- } while (!work_queue_.empty());
+ } else if (DeferOrRunPendingTask(std::move(pending_task))) {
+ return true;
+ }
}
// Nothing happened.
@@ -535,8 +529,27 @@ bool MessageLoop::DoWork() {
}
bool MessageLoop::DoDelayedWork(TimeTicks* next_delayed_work_time) {
- if (!nestable_tasks_allowed_ || delayed_work_queue_.empty()) {
- recent_time_ = *next_delayed_work_time = TimeTicks();
+ if (!task_execution_allowed_) {
+ *next_delayed_work_time = TimeTicks();
+ // |scheduled_wakeup_| isn't used in nested loops that don't process
+ // application tasks.
+ DCHECK(scheduled_wakeup_.next_run_time.is_null());
+ return false;
+ }
+
+ if (!incoming_task_queue_->delayed_tasks().HasTasks()) {
+ *next_delayed_work_time = TimeTicks();
+
+ // It's possible to be woken up by a system event and have it cancel the
+ // upcoming delayed task from under us before DoDelayedWork() -- see comment
+ // under |next_run_time > recent_time_|. This condition covers the special
+ // case where such a system event cancelled *all* pending delayed tasks.
+ if (!scheduled_wakeup_.next_run_time.is_null()) {
+ ReportScheduledWakeupResult(ScheduledWakeupResult::kInterrupted,
+ scheduled_wakeup_.intended_sleep);
+ scheduled_wakeup_ = ScheduledWakeup();
+ }
+
return false;
}
@@ -547,21 +560,49 @@ bool MessageLoop::DoDelayedWork(TimeTicks* next_delayed_work_time) {
// fall behind (and have a lot of ready-to-run delayed tasks), the more
// efficient we'll be at handling the tasks.
- TimeTicks next_run_time = delayed_work_queue_.top().delayed_run_time;
+ TimeTicks next_run_time =
+ incoming_task_queue_->delayed_tasks().Peek().delayed_run_time;
+
if (next_run_time > recent_time_) {
recent_time_ = TimeTicks::Now(); // Get a better view of Now();
if (next_run_time > recent_time_) {
*next_delayed_work_time = next_run_time;
+
+ // If the loop was woken up early by an untriaged task:
+ // |scheduled_wakeup_| will have been handled already in DoWork(). If it
+ // wasn't, it means the early wake up was caused by a system event (e.g.
+ // MessageLoopForUI or IO).
+ if (!scheduled_wakeup_.next_run_time.is_null()) {
+ // Handling the system event may have resulted in cancelling the
+ // upcoming delayed task (and then it being pruned by
+ // DelayedTaskQueue::HasTasks()); hence, we cannot check for strict
+ // equality here. We can however check that the pending task is either
+ // still there or that a later delay replaced it in front of the queue.
+ // There shouldn't have been new tasks added in |delayed_tasks()| per
+ // DoWork() not having triaged new tasks since the last DoIdleWork().
+ DCHECK_GE(next_run_time, scheduled_wakeup_.next_run_time);
+
+ ReportScheduledWakeupResult(ScheduledWakeupResult::kInterrupted,
+ scheduled_wakeup_.intended_sleep);
+ scheduled_wakeup_ = ScheduledWakeup();
+ }
+
return false;
}
}
- PendingTask pending_task =
- std::move(const_cast<PendingTask&>(delayed_work_queue_.top()));
- delayed_work_queue_.pop();
+ if (next_run_time == scheduled_wakeup_.next_run_time) {
+ ReportScheduledWakeupResult(ScheduledWakeupResult::kCompleted,
+ scheduled_wakeup_.intended_sleep);
+ scheduled_wakeup_ = ScheduledWakeup();
+ }
- if (!delayed_work_queue_.empty())
- *next_delayed_work_time = delayed_work_queue_.top().delayed_run_time;
+ PendingTask pending_task = incoming_task_queue_->delayed_tasks().Pop();
+
+ if (incoming_task_queue_->delayed_tasks().HasTasks()) {
+ *next_delayed_work_time =
+ incoming_task_queue_->delayed_tasks().Peek().delayed_run_time;
+ }
return DeferOrRunPendingTask(std::move(pending_task));
}
@@ -570,106 +611,132 @@ bool MessageLoop::DoIdleWork() {
if (ProcessNextDelayedNonNestableTask())
return true;
- if (run_loop_->quit_when_idle_received_)
+#if defined(OS_WIN)
+ bool need_high_res_timers = false;
+#endif
+
+ // Do not report idle metrics nor do any logic related to delayed tasks if
+ // about to quit the loop and/or in a nested loop where
+ // |!task_execution_allowed_|. In the former case, the loop isn't going to
+ // sleep and in the latter case DoDelayedWork() will not actually do the work
+ // this is prepping for.
+ if (ShouldQuitWhenIdle()) {
pump_->Quit();
+ } else if (task_execution_allowed_) {
+ incoming_task_queue_->ReportMetricsOnIdle();
+
+ if (incoming_task_queue_->delayed_tasks().HasTasks()) {
+ TimeTicks scheduled_wakeup_time =
+ incoming_task_queue_->delayed_tasks().Peek().delayed_run_time;
+
+ if (!scheduled_wakeup_.next_run_time.is_null()) {
+ // It's possible for DoIdleWork() to be invoked twice in a row (e.g. if
+ // the MessagePump processed system work and became idle twice in a row
+ // without application tasks in between -- some pumps with a native
+ // message loop do not invoke DoWork() / DoDelayedWork() when awaken for
+ // system work only). As in DoDelayedWork(), we cannot check for strict
+ // equality below as the system work may have cancelled the frontmost
+ // task.
+ DCHECK_GE(scheduled_wakeup_time, scheduled_wakeup_.next_run_time);
+
+ ReportScheduledWakeupResult(ScheduledWakeupResult::kInterrupted,
+ scheduled_wakeup_.intended_sleep);
+ scheduled_wakeup_ = ScheduledWakeup();
+ }
+
+ // Store the remaining delay as well as the programmed wakeup time in
+ // order to know next time this MessageLoop wakes up whether it woke up
+ // because of this pending task (is it still the frontmost task in the
+ // queue?) and be able to report the slept delta (which is lost if not
+ // saved here).
+ scheduled_wakeup_ = ScheduledWakeup{
+ scheduled_wakeup_time, scheduled_wakeup_time - TimeTicks::Now()};
+ }
- // When we return we will do a kernel wait for more tasks.
#if defined(OS_WIN)
- // On Windows we activate the high resolution timer so that the wait
- // _if_ triggered by the timer happens with good resolution. If we don't
- // do this the default resolution is 15ms which might not be acceptable
- // for some tasks.
- bool high_res = pending_high_res_tasks_ > 0;
- if (high_res != in_high_res_mode_) {
- in_high_res_mode_ = high_res;
+ // On Windows we activate the high resolution timer so that the wait
+ // _if_ triggered by the timer happens with good resolution. If we don't
+ // do this the default resolution is 15ms which might not be acceptable
+ // for some tasks.
+ need_high_res_timers =
+ incoming_task_queue_->HasPendingHighResolutionTasks();
+#endif
+ }
+
+#if defined(OS_WIN)
+ if (in_high_res_mode_ != need_high_res_timers) {
+ in_high_res_mode_ = need_high_res_timers;
Time::ActivateHighResolutionTimer(in_high_res_mode_);
}
#endif
+
+ // When we return we will do a kernel wait for more tasks.
return false;
}
#if !defined(OS_NACL)
+
//------------------------------------------------------------------------------
// MessageLoopForUI
-MessageLoopForUI::MessageLoopForUI(std::unique_ptr<MessagePump> pump)
- : MessageLoop(TYPE_UI, Bind(&ReturnPump, Passed(&pump))) {}
-
+MessageLoopForUI::MessageLoopForUI(Type type) : MessageLoop(type) {
#if defined(OS_ANDROID)
-void MessageLoopForUI::Start() {
- // No Histogram support for UI message loop as it is managed by Java side
- static_cast<MessagePumpForUI*>(pump_.get())->Start(this);
+ DCHECK(type == TYPE_UI || type == TYPE_JAVA);
+#else
+ DCHECK_EQ(type, TYPE_UI);
+#endif
}
-void MessageLoopForUI::StartForTesting(
- base::android::JavaMessageHandlerFactory* factory,
- WaitableEvent* test_done_event) {
- // No Histogram support for UI message loop as it is managed by Java side
- static_cast<MessagePumpForUI*>(pump_.get())
- ->StartForUnitTest(this, factory, test_done_event);
+// static
+MessageLoopCurrentForUI MessageLoopForUI::current() {
+ return MessageLoopCurrentForUI::Get();
}
-void MessageLoopForUI::Abort() {
- static_cast<MessagePumpForUI*>(pump_.get())->Abort();
+// static
+bool MessageLoopForUI::IsCurrent() {
+ return MessageLoopCurrentForUI::IsSet();
}
-#endif
#if defined(OS_IOS)
void MessageLoopForUI::Attach() {
static_cast<MessagePumpUIApplication*>(pump_.get())->Attach(this);
}
-#endif
+#endif // defined(OS_IOS)
-#if defined(USE_OZONE) || (defined(USE_X11) && !defined(USE_GLIB))
-bool MessageLoopForUI::WatchFileDescriptor(
- int fd,
- bool persistent,
- MessagePumpLibevent::Mode mode,
- MessagePumpLibevent::FileDescriptorWatcher *controller,
- MessagePumpLibevent::Watcher *delegate) {
- return static_cast<MessagePumpLibevent*>(pump_.get())->WatchFileDescriptor(
- fd,
- persistent,
- mode,
- controller,
- delegate);
+#if defined(OS_ANDROID)
+void MessageLoopForUI::Abort() {
+ static_cast<MessagePumpForUI*>(pump_.get())->Abort();
}
-#endif
-
-#endif // !defined(OS_NACL)
-//------------------------------------------------------------------------------
-// MessageLoopForIO
+bool MessageLoopForUI::IsAborted() {
+ return static_cast<MessagePumpForUI*>(pump_.get())->IsAborted();
+}
-#if !defined(OS_NACL_SFI)
+void MessageLoopForUI::QuitWhenIdle(base::OnceClosure callback) {
+ static_cast<MessagePumpForUI*>(pump_.get())
+ ->QuitWhenIdle(std::move(callback));
+}
+#endif // defined(OS_ANDROID)
#if defined(OS_WIN)
-void MessageLoopForIO::RegisterIOHandler(HANDLE file, IOHandler* handler) {
- ToPumpIO(pump_.get())->RegisterIOHandler(file, handler);
+void MessageLoopForUI::EnableWmQuit() {
+ static_cast<MessagePumpForUI*>(pump_.get())->EnableWmQuit();
}
+#endif // defined(OS_WIN)
-bool MessageLoopForIO::RegisterJobObject(HANDLE job, IOHandler* handler) {
- return ToPumpIO(pump_.get())->RegisterJobObject(job, handler);
-}
+#endif // !defined(OS_NACL)
-bool MessageLoopForIO::WaitForIOCompletion(DWORD timeout, IOHandler* filter) {
- return ToPumpIO(pump_.get())->WaitForIOCompletion(timeout, filter);
-}
-#elif defined(OS_POSIX)
-bool MessageLoopForIO::WatchFileDescriptor(int fd,
- bool persistent,
- Mode mode,
- FileDescriptorWatcher* controller,
- Watcher* delegate) {
- return ToPumpIO(pump_.get())->WatchFileDescriptor(
- fd,
- persistent,
- mode,
- controller,
- delegate);
+//------------------------------------------------------------------------------
+// MessageLoopForIO
+
+// static
+MessageLoopCurrentForIO MessageLoopForIO::current() {
+ return MessageLoopCurrentForIO::Get();
}
-#endif
-#endif // !defined(OS_NACL_SFI)
+// static
+bool MessageLoopForIO::IsCurrent() {
+ return MessageLoopCurrentForIO::IsSet();
+}
} // namespace base
diff --git a/base/message_loop/message_loop.h b/base/message_loop/message_loop.h
index fa054f421a..e093502061 100644
--- a/base/message_loop/message_loop.h
+++ b/base/message_loop/message_loop.h
@@ -11,44 +11,31 @@
#include "base/base_export.h"
#include "base/callback_forward.h"
-#include "base/debug/task_annotator.h"
#include "base/gtest_prod_util.h"
#include "base/macros.h"
-#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
#include "base/message_loop/incoming_task_queue.h"
+#include "base/message_loop/message_loop_current.h"
#include "base/message_loop/message_loop_task_runner.h"
#include "base/message_loop/message_pump.h"
#include "base/message_loop/timer_slack.h"
#include "base/observer_list.h"
#include "base/pending_task.h"
+#include "base/run_loop.h"
#include "base/synchronization/lock.h"
+#include "base/threading/sequence_local_storage_map.h"
+#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "build/build_config.h"
-// TODO(sky): these includes should not be necessary. Nuke them.
-#if defined(OS_WIN)
-#include "base/message_loop/message_pump_win.h"
-#elif defined(OS_IOS)
-#include "base/message_loop/message_pump_io_ios.h"
-#elif defined(OS_POSIX)
-#include "base/message_loop/message_pump_libevent.h"
-#endif
-
-#if defined(OS_ANDROID)
-namespace base {
-namespace android {
-
-class JavaMessageHandlerFactory;
-
-} // namespace android
-} // namespace base
-#endif // defined(OS_ANDROID)
+// Just in libchrome
+namespace brillo {
+class BaseMessageLoop;
+}
namespace base {
-class RunLoop;
class ThreadTaskRunnerHandle;
-class WaitableEvent;
// A MessageLoop is used to process events for a particular thread. There is
// at most one MessageLoop instance per thread.
@@ -59,6 +46,18 @@ class WaitableEvent;
// time permits) and signals sent to a registered set of HANDLEs may also be
// processed.
//
+// The MessageLoop's API should only be used directly by its owner (and users
+// which the owner opts to share a MessageLoop* with). Other ways to access
+// subsets of the MessageLoop API:
+// - base::RunLoop : Drive the MessageLoop from the thread it's bound to.
+// - base::Thread/SequencedTaskRunnerHandle : Post back to the MessageLoop
+// from a task running on it.
+// - SequenceLocalStorageSlot : Bind external state to this MessageLoop.
+// - base::MessageLoopCurrent : Access statically exposed APIs of this
+// MessageLoop.
+// - Embedders may provide their own static accessors to post tasks on
+// specific loops (e.g. content::BrowserThreads).
+//
// NOTE: Unless otherwise specified, a MessageLoop's methods may only be called
// on the thread where the MessageLoop's Run method executes.
//
@@ -73,7 +72,7 @@ class WaitableEvent;
// Sample workaround when inner task processing is needed:
// HRESULT hr;
// {
-// MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current());
+// MessageLoopCurrent::ScopedNestableTaskAllower allow;
// hr = DoDragDrop(...); // Implicitly runs a modal message loop.
// }
// // Process |hr| (the result returned by DoDragDrop()).
@@ -81,8 +80,17 @@ class WaitableEvent;
// Please be SURE your task is reentrant (nestable) and all global variables
// are stable and accessible before calling SetNestableTasksAllowed(true).
//
-class BASE_EXPORT MessageLoop : public MessagePump::Delegate {
+// TODO(gab): MessageLoop doesn't need to be a MessageLoopCurrent once callers
+// that store MessageLoop::current() in a MessageLoop* variable have been
+// updated to use a MessageLoopCurrent variable.
+class BASE_EXPORT MessageLoop : public MessagePump::Delegate,
+ public RunLoop::Delegate,
+ public MessageLoopCurrent {
public:
+ // TODO(gab): Migrate usage of this class to MessageLoopCurrent and remove
+ // this forwarded declaration.
+ using DestructionObserver = MessageLoopCurrent::DestructionObserver;
+
// A MessageLoop has a particular type, which indicates the set of
// asynchronous events it may process in addition to tasks and timers.
//
@@ -125,10 +133,10 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate {
~MessageLoop() override;
- // Returns the MessageLoop object for the current thread, or null if none.
- static MessageLoop* current();
+ // TODO(gab): Mass migrate callers to MessageLoopCurrent::Get().
+ static MessageLoopCurrent current();
- typedef std::unique_ptr<MessagePump>(MessagePumpFactory)();
+ using MessagePumpFactory = std::unique_ptr<MessagePump>();
// Uses the given base::MessagePumpForUIFactory to override the default
// MessagePump implementation for 'TYPE_UI'. Returns true if the factory
// was successfully registered.
@@ -138,71 +146,6 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate {
// value.
static std::unique_ptr<MessagePump> CreateMessagePumpForType(Type type);
- // A DestructionObserver is notified when the current MessageLoop is being
- // destroyed. These observers are notified prior to MessageLoop::current()
- // being changed to return NULL. This gives interested parties the chance to
- // do final cleanup that depends on the MessageLoop.
- //
- // NOTE: Any tasks posted to the MessageLoop during this notification will
- // not be run. Instead, they will be deleted.
- //
- class BASE_EXPORT DestructionObserver {
- public:
- virtual void WillDestroyCurrentMessageLoop() = 0;
-
- protected:
- virtual ~DestructionObserver();
- };
-
- // Add a DestructionObserver, which will start receiving notifications
- // immediately.
- void AddDestructionObserver(DestructionObserver* destruction_observer);
-
- // Remove a DestructionObserver. It is safe to call this method while a
- // DestructionObserver is receiving a notification callback.
- void RemoveDestructionObserver(DestructionObserver* destruction_observer);
-
- // A NestingObserver is notified when a nested message loop begins. The
- // observers are notified before the first task is processed.
- class BASE_EXPORT NestingObserver {
- public:
- virtual void OnBeginNestedMessageLoop() = 0;
-
- protected:
- virtual ~NestingObserver();
- };
-
- void AddNestingObserver(NestingObserver* observer);
- void RemoveNestingObserver(NestingObserver* observer);
-
- // Deprecated: use RunLoop instead.
- //
- // Signals the Run method to return when it becomes idle. It will continue to
- // process pending messages and future messages as long as they are enqueued.
- // Warning: if the MessageLoop remains busy, it may never quit. Only use this
- // Quit method when looping procedures (such as web pages) have been shut
- // down.
- //
- // This method may only be called on the same thread that called Run, and Run
- // must still be on the call stack.
- //
- // Use QuitClosure variants if you need to Quit another thread's MessageLoop,
- // but note that doing so is fairly dangerous if the target thread makes
- // nested calls to MessageLoop::Run. The problem being that you won't know
- // which nested run loop you are quitting, so be careful!
- void QuitWhenIdle();
-
- // Deprecated: use RunLoop instead.
- //
- // This method is a variant of Quit, that does not wait for pending messages
- // to be processed before returning from Run.
- void QuitNow();
-
- // Deprecated: use RunLoop instead.
- // Construct a Closure that will call QuitWhenIdle(). Useful to schedule an
- // arbitrary MessageLoop to QuitWhenIdle.
- static Closure QuitWhenIdleClosure();
-
// Set the timer slack for this message loop.
void SetTimerSlack(TimerSlack timer_slack) {
pump_->SetTimerSlack(timer_slack);
@@ -223,7 +166,7 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate {
std::string GetThreadName() const;
// Gets the TaskRunner associated with this message loop.
- const scoped_refptr<SingleThreadTaskRunner>& task_runner() {
+ const scoped_refptr<SingleThreadTaskRunner>& task_runner() const {
return task_runner_;
}
@@ -238,103 +181,31 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate {
// Must be called on the thread to which the message loop is bound.
void ClearTaskRunnerForTesting();
- // Enables or disables the recursive task processing. This happens in the case
- // of recursive message loops. Some unwanted message loops may occur when
- // using common controls or printer functions. By default, recursive task
- // processing is disabled.
- //
- // Please use |ScopedNestableTaskAllower| instead of calling these methods
- // directly. In general, nestable message loops are to be avoided. They are
- // dangerous and difficult to get right, so please use with extreme caution.
- //
- // The specific case where tasks get queued is:
- // - The thread is running a message loop.
- // - It receives a task #1 and executes it.
- // - The task #1 implicitly starts a message loop, like a MessageBox in the
- // unit test. This can also be StartDoc or GetSaveFileName.
- // - The thread receives a task #2 before or while in this second message
- // loop.
- // - With NestableTasksAllowed set to true, the task #2 will run right away.
- // Otherwise, it will get executed right after task #1 completes at "thread
- // message loop level".
- void SetNestableTasksAllowed(bool allowed);
- bool NestableTasksAllowed() const;
-
- // Enables nestable tasks on |loop| while in scope.
- class ScopedNestableTaskAllower {
- public:
- explicit ScopedNestableTaskAllower(MessageLoop* loop)
- : loop_(loop),
- old_state_(loop_->NestableTasksAllowed()) {
- loop_->SetNestableTasksAllowed(true);
- }
- ~ScopedNestableTaskAllower() {
- loop_->SetNestableTasksAllowed(old_state_);
- }
-
- private:
- MessageLoop* loop_;
- bool old_state_;
- };
-
- // Returns true if we are currently running a nested message loop.
- bool IsNested();
-
- // A TaskObserver is an object that receives task notifications from the
- // MessageLoop.
- //
- // NOTE: A TaskObserver implementation should be extremely fast!
- class BASE_EXPORT TaskObserver {
- public:
- TaskObserver();
-
- // This method is called before processing a task.
- virtual void WillProcessTask(const PendingTask& pending_task) = 0;
-
- // This method is called after processing a task.
- virtual void DidProcessTask(const PendingTask& pending_task) = 0;
-
- protected:
- virtual ~TaskObserver();
- };
+ // TODO(https://crbug.com/825327): Remove users of TaskObservers through
+ // MessageLoop::current() and migrate the type back here.
+ using TaskObserver = MessageLoopCurrent::TaskObserver;
// These functions can only be called on the same thread that |this| is
// running on.
void AddTaskObserver(TaskObserver* task_observer);
void RemoveTaskObserver(TaskObserver* task_observer);
- // Can only be called from the thread that owns the MessageLoop.
- bool is_running() const;
-
- // Returns true if the message loop has high resolution timers enabled.
- // Provided for testing.
- bool HasHighResolutionTasks();
-
- // Returns true if the message loop is "idle". Provided for testing.
+ // Returns true if the message loop is idle (ignoring delayed tasks). This is
+ // the same condition which triggers DoWork() to return false: i.e.
+ // out of tasks which can be processed at the current run-level -- there might
+ // be deferred non-nestable tasks remaining if currently in a nested run
+ // level.
bool IsIdleForTesting();
- // Returns the TaskAnnotator which is used to add debug information to posted
- // tasks.
- debug::TaskAnnotator* task_annotator() { return &task_annotator_; }
-
// Runs the specified PendingTask.
void RunTask(PendingTask* pending_task);
- bool nesting_allowed() const { return allow_nesting_; }
-
- // Disallow nesting. After this is called, running a nested RunLoop or calling
- // Add/RemoveNestingObserver() on this MessageLoop will crash.
- void DisallowNesting() { allow_nesting_ = false; }
-
- // Disallow task observers. After this is called, calling
- // Add/RemoveTaskObserver() on this MessageLoop will crash.
- void DisallowTaskObservers() { allow_task_observers_ = false; }
-
//----------------------------------------------------------------------------
protected:
std::unique_ptr<MessagePump> pump_;
- using MessagePumpFactoryCallback = Callback<std::unique_ptr<MessagePump>()>;
+ using MessagePumpFactoryCallback =
+ OnceCallback<std::unique_ptr<MessagePump>()>;
// Common protected constructor. Other constructors delegate the
// initialization to this constructor.
@@ -348,13 +219,17 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate {
void BindToCurrentThread();
private:
+ //only in libchrome
+ friend class brillo::BaseMessageLoop;
friend class internal::IncomingTaskQueue;
- friend class RunLoop;
+ friend class MessageLoopCurrent;
+ friend class MessageLoopCurrentForIO;
+ friend class MessageLoopCurrentForUI;
friend class ScheduleWorkTest;
friend class Thread;
- friend struct PendingTask;
FRIEND_TEST_ALL_PREFIXES(MessageLoopTest, DeleteUnboundLoop);
- friend class PendingTaskTest;
+
+ class Controller;
// Creates a MessageLoop without binding to a thread.
// If |type| is TYPE_CUSTOM non-null |pump_factory| must be also given
@@ -375,8 +250,10 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate {
// task runner for this message loop.
void SetThreadTaskRunnerHandle();
- // Invokes the actual run loop using the message pump.
- void RunHandler();
+ // RunLoop::Delegate:
+ void Run(bool application_tasks_allowed) override;
+ void Quit() override;
+ void EnsureWorkScheduled() override;
// Called to process any delayed non-nestable tasks.
bool ProcessNextDelayedNonNestableTask();
@@ -385,25 +262,14 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate {
// cannot be run right now. Returns true if the task was run.
bool DeferOrRunPendingTask(PendingTask pending_task);
- // Adds the pending task to delayed_work_queue_.
- void AddToDelayedWorkQueue(PendingTask pending_task);
-
// Delete tasks that haven't run yet without running them. Used in the
- // destructor to make sure all the task's destructors get called. Returns
- // true if some work was done.
- bool DeletePendingTasks();
-
- // Loads tasks from the incoming queue to |work_queue_| if the latter is
- // empty.
- void ReloadWorkQueue();
+ // destructor to make sure all the task's destructors get called.
+ void DeletePendingTasks();
// Wakes up the message pump. Can be called on any thread. The caller is
// responsible for synchronizing ScheduleWork() calls.
void ScheduleWork();
- // Notify observers that a nested message loop is starting.
- void NotifyBeginNestedLoop();
-
// MessagePump::Delegate methods:
bool DoWork() override;
bool DoDelayedWork(TimeTicks* next_delayed_work_time) override;
@@ -411,56 +277,45 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate {
const Type type_;
- // A list of tasks that need to be processed by this instance. Note that
- // this queue is only accessed (push/pop) by our current thread.
- TaskQueue work_queue_;
-
#if defined(OS_WIN)
- // How many high resolution tasks are in the pending task queue. This value
- // increases by N every time we call ReloadWorkQueue() and decreases by 1
- // every time we call RunTask() if the task needs a high resolution timer.
- int pending_high_res_tasks_;
// Tracks if we have requested high resolution timers. Its only use is to
// turn off the high resolution timer upon loop destruction.
- bool in_high_res_mode_;
+ bool in_high_res_mode_ = false;
#endif
- // Contains delayed tasks, sorted by their 'delayed_run_time' property.
- DelayedTaskQueue delayed_work_queue_;
-
// A recent snapshot of Time::Now(), used to check delayed_work_queue_.
TimeTicks recent_time_;
- // A queue of non-nestable tasks that we had to defer because when it came
- // time to execute them we were in a nested message loop. They will execute
- // once we're out of nested message loops.
- TaskQueue deferred_non_nestable_work_queue_;
+ // Non-null when the last thing this MessageLoop did is become idle with
+ // pending delayed tasks. Used to report metrics on the following wake up.
+ struct ScheduledWakeup {
+ // The scheduled time of the next delayed task when this loop became idle.
+ TimeTicks next_run_time;
+ // The delta until |next_run_time| when this loop became idle.
+ TimeDelta intended_sleep;
+ } scheduled_wakeup_;
ObserverList<DestructionObserver> destruction_observers_;
- ObserverList<NestingObserver> nesting_observers_;
-
- // A recursion block that prevents accidentally running additional tasks when
- // insider a (accidentally induced?) nested message pump.
- bool nestable_tasks_allowed_;
+ // A boolean which prevents unintentional reentrant task execution (e.g. from
+ // induced nested message loops). As such, nested message loops will only
+ // process system messages (not application tasks) by default. A nested loop
+ // layer must have been explicitly granted permission to be able to execute
+ // application tasks. This is granted either by
+ // RunLoop::Type::kNestableTasksAllowed when the loop is driven by the
+ // application or by a ScopedNestableTaskAllower preceding a system call that
+ // is known to generate a system-driven nested loop.
+ bool task_execution_allowed_ = true;
// pump_factory_.Run() is called to create a message pump for this loop
// if type_ is TYPE_CUSTOM and pump_ is null.
MessagePumpFactoryCallback pump_factory_;
- RunLoop* run_loop_;
-
ObserverList<TaskObserver> task_observers_;
- debug::TaskAnnotator task_annotator_;
-
- // Used to allow creating a breadcrumb of program counters in PostTask.
- // This variable is only initialized while a task is being executed and is
- // meant only to store context for creating a backtrace breadcrumb. Do not
- // attach other semantics to it without thinking through the use caes
- // thoroughly.
- const PendingTask* current_pending_task_;
-
+ // Pointer to this MessageLoop's Controller, valid until the reference to
+ // |incoming_task_queue_| is dropped below.
+ Controller* const message_loop_controller_;
scoped_refptr<internal::IncomingTaskQueue> incoming_task_queue_;
// A task runner which we haven't bound to a thread yet.
@@ -472,13 +327,19 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate {
// Id of the thread this message loop is bound to. Initialized once when the
// MessageLoop is bound to its thread and constant forever after.
- PlatformThreadId thread_id_;
+ PlatformThreadId thread_id_ = kInvalidThreadId;
+
+ // Holds data stored through the SequenceLocalStorageSlot API.
+ internal::SequenceLocalStorageMap sequence_local_storage_map_;
- // Whether nesting is allowed.
- bool allow_nesting_ = true;
+ // Enables the SequenceLocalStorageSlot API within its scope.
+ // Instantiated in BindToCurrentThread().
+ std::unique_ptr<internal::ScopedSetSequenceLocalStorageMapForCurrentThread>
+ scoped_set_sequence_local_storage_map_for_current_thread_;
- // Whether task observers are allowed.
- bool allow_task_observers_ = true;
+ // Verifies that calls are made on the thread on which BindToCurrentThread()
+ // was invoked.
+ THREAD_CHECKER(bound_thread_checker_);
DISALLOW_COPY_AND_ASSIGN(MessageLoop);
};
@@ -489,28 +350,19 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate {
// MessageLoopForUI extends MessageLoop with methods that are particular to a
// MessageLoop instantiated with TYPE_UI.
//
-// This class is typically used like so:
-// MessageLoopForUI::current()->...call some method...
+// By instantiating a MessageLoopForUI on the current thread, the owner enables
+// native UI message pumping.
+//
+// MessageLoopCurrentForUI is exposed statically on its thread via
+// MessageLoopCurrentForUI::Get() to provide additional functionality.
//
class BASE_EXPORT MessageLoopForUI : public MessageLoop {
public:
- MessageLoopForUI() : MessageLoop(TYPE_UI) {
- }
+ explicit MessageLoopForUI(Type type = TYPE_UI);
- explicit MessageLoopForUI(std::unique_ptr<MessagePump> pump);
-
- // Returns the MessageLoopForUI of the current thread.
- static MessageLoopForUI* current() {
- MessageLoop* loop = MessageLoop::current();
- DCHECK(loop);
- DCHECK(loop->IsType(MessageLoop::TYPE_UI));
- return static_cast<MessageLoopForUI*>(loop);
- }
-
- static bool IsCurrent() {
- MessageLoop* loop = MessageLoop::current();
- return loop && loop->IsType(MessageLoop::TYPE_UI);
- }
+ // TODO(gab): Mass migrate callers to MessageLoopCurrentForUI::Get()/IsSet().
+ static MessageLoopCurrentForUI current();
+ static bool IsCurrent();
#if defined(OS_IOS)
// On iOS, the main message loop cannot be Run(). Instead call Attach(),
@@ -520,25 +372,23 @@ class BASE_EXPORT MessageLoopForUI : public MessageLoop {
#endif
#if defined(OS_ANDROID)
- // On Android, the UI message loop is handled by Java side. So Run() should
- // never be called. Instead use Start(), which will forward all the native UI
- // events to the Java message loop.
- void Start();
- void StartForTesting(base::android::JavaMessageHandlerFactory* factory,
- WaitableEvent* test_done_event);
- // In Android there are cases where we want to abort immediately without
+ // On Android there are cases where we want to abort immediately without
// calling Quit(), in these cases we call Abort().
void Abort();
+
+ // True if this message pump has been aborted.
+ bool IsAborted();
+
+ // Since Run() is never called on Android, and the message loop is run by the
+ // java Looper, quitting the RunLoop won't join the thread, so we need a
+ // callback to run when the RunLoop goes idle to let the Java thread know when
+ // it can safely quit.
+ void QuitWhenIdle(base::OnceClosure callback);
#endif
-#if defined(USE_OZONE) || (defined(USE_X11) && !defined(USE_GLIB))
- // Please see MessagePumpLibevent for definition.
- bool WatchFileDescriptor(
- int fd,
- bool persistent,
- MessagePumpLibevent::Mode mode,
- MessagePumpLibevent::FileDescriptorWatcher* controller,
- MessagePumpLibevent::Watcher* delegate);
+#if defined(OS_WIN)
+ // See method of the same name in the Windows MessagePumpForUI implementation.
+ void EnableWmQuit();
#endif
};
@@ -554,70 +404,19 @@ static_assert(sizeof(MessageLoop) == sizeof(MessageLoopForUI),
// MessageLoopForIO extends MessageLoop with methods that are particular to a
// MessageLoop instantiated with TYPE_IO.
//
-// This class is typically used like so:
-// MessageLoopForIO::current()->...call some method...
+// By instantiating a MessageLoopForIO on the current thread, the owner enables
+// native async IO message pumping.
+//
+// MessageLoopCurrentForIO is exposed statically on its thread via
+// MessageLoopCurrentForIO::Get() to provide additional functionality.
//
class BASE_EXPORT MessageLoopForIO : public MessageLoop {
public:
- MessageLoopForIO() : MessageLoop(TYPE_IO) {
- }
-
- // Returns the MessageLoopForIO of the current thread.
- static MessageLoopForIO* current() {
- MessageLoop* loop = MessageLoop::current();
- 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<MessageLoopForIO*>(loop);
- }
+ MessageLoopForIO() : MessageLoop(TYPE_IO) {}
- static bool IsCurrent() {
- MessageLoop* loop = MessageLoop::current();
- return loop && loop->type() == MessageLoop::TYPE_IO;
- }
-
-#if !defined(OS_NACL_SFI)
-
-#if defined(OS_WIN)
- typedef MessagePumpForIO::IOHandler IOHandler;
- typedef MessagePumpForIO::IOContext IOContext;
-#elif defined(OS_IOS)
- typedef MessagePumpIOSForIO::Watcher Watcher;
- typedef MessagePumpIOSForIO::FileDescriptorWatcher
- FileDescriptorWatcher;
-
- enum Mode {
- WATCH_READ = MessagePumpIOSForIO::WATCH_READ,
- WATCH_WRITE = MessagePumpIOSForIO::WATCH_WRITE,
- WATCH_READ_WRITE = MessagePumpIOSForIO::WATCH_READ_WRITE
- };
-#elif defined(OS_POSIX)
- typedef MessagePumpLibevent::Watcher Watcher;
- typedef MessagePumpLibevent::FileDescriptorWatcher
- FileDescriptorWatcher;
-
- enum Mode {
- WATCH_READ = MessagePumpLibevent::WATCH_READ,
- WATCH_WRITE = MessagePumpLibevent::WATCH_WRITE,
- WATCH_READ_WRITE = MessagePumpLibevent::WATCH_READ_WRITE
- };
-#endif
-
-#if defined(OS_WIN)
- // Please see MessagePumpWin for definitions of these methods.
- void RegisterIOHandler(HANDLE file, IOHandler* handler);
- bool RegisterJobObject(HANDLE job, IOHandler* handler);
- bool WaitForIOCompletion(DWORD timeout, IOHandler* filter);
-#elif defined(OS_POSIX)
- // Please see MessagePumpIOSForIO/MessagePumpLibevent for definition.
- bool WatchFileDescriptor(int fd,
- bool persistent,
- Mode mode,
- FileDescriptorWatcher* controller,
- Watcher* delegate);
-#endif // defined(OS_IOS) || defined(OS_POSIX)
-#endif // !defined(OS_NACL_SFI)
+ // TODO(gab): Mass migrate callers to MessageLoopCurrentForIO::Get()/IsSet().
+ static MessageLoopCurrentForIO current();
+ static bool IsCurrent();
};
// Do not add any member variables to MessageLoopForIO! This is important b/c
diff --git a/base/message_loop/message_loop_current.cc b/base/message_loop/message_loop_current.cc
new file mode 100644
index 0000000000..4959b70e06
--- /dev/null
+++ b/base/message_loop/message_loop_current.cc
@@ -0,0 +1,248 @@
+// 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.
+
+#include "base/message_loop/message_loop_current.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_pump_for_io.h"
+#include "base/message_loop/message_pump_for_ui.h"
+#include "base/no_destructor.h"
+#include "base/threading/thread_local.h"
+
+namespace base {
+
+namespace {
+
+base::ThreadLocalPointer<MessageLoop>* GetTLSMessageLoop() {
+ static NoDestructor<ThreadLocalPointer<MessageLoop>> lazy_tls_ptr;
+ return lazy_tls_ptr.get();
+}
+
+} // namespace
+
+//------------------------------------------------------------------------------
+// MessageLoopCurrent
+
+// static
+MessageLoopCurrent MessageLoopCurrent::Get() {
+ return MessageLoopCurrent(GetTLSMessageLoop()->Get());
+}
+
+// static
+bool MessageLoopCurrent::IsSet() {
+ return !!GetTLSMessageLoop()->Get();
+}
+
+void MessageLoopCurrent::AddDestructionObserver(
+ DestructionObserver* destruction_observer) {
+ DCHECK_CALLED_ON_VALID_THREAD(current_->bound_thread_checker_);
+ current_->destruction_observers_.AddObserver(destruction_observer);
+}
+
+void MessageLoopCurrent::RemoveDestructionObserver(
+ DestructionObserver* destruction_observer) {
+ DCHECK_CALLED_ON_VALID_THREAD(current_->bound_thread_checker_);
+ current_->destruction_observers_.RemoveObserver(destruction_observer);
+}
+
+const scoped_refptr<SingleThreadTaskRunner>& MessageLoopCurrent::task_runner()
+ const {
+ DCHECK_CALLED_ON_VALID_THREAD(current_->bound_thread_checker_);
+ return current_->task_runner();
+}
+
+void MessageLoopCurrent::SetTaskRunner(
+ scoped_refptr<SingleThreadTaskRunner> task_runner) {
+ DCHECK_CALLED_ON_VALID_THREAD(current_->bound_thread_checker_);
+ current_->SetTaskRunner(std::move(task_runner));
+}
+
+bool MessageLoopCurrent::IsIdleForTesting() {
+ DCHECK_CALLED_ON_VALID_THREAD(current_->bound_thread_checker_);
+ return current_->IsIdleForTesting();
+}
+
+void MessageLoopCurrent::AddTaskObserver(TaskObserver* task_observer) {
+ DCHECK_CALLED_ON_VALID_THREAD(current_->bound_thread_checker_);
+ current_->AddTaskObserver(task_observer);
+}
+
+void MessageLoopCurrent::RemoveTaskObserver(TaskObserver* task_observer) {
+ DCHECK_CALLED_ON_VALID_THREAD(current_->bound_thread_checker_);
+ current_->RemoveTaskObserver(task_observer);
+}
+
+void MessageLoopCurrent::SetNestableTasksAllowed(bool allowed) {
+ DCHECK_CALLED_ON_VALID_THREAD(current_->bound_thread_checker_);
+ if (allowed) {
+ // Kick the native pump just in case we enter a OS-driven nested message
+ // loop that does not go through RunLoop::Run().
+ current_->pump_->ScheduleWork();
+ }
+ current_->task_execution_allowed_ = allowed;
+}
+
+bool MessageLoopCurrent::NestableTasksAllowed() const {
+ DCHECK_CALLED_ON_VALID_THREAD(current_->bound_thread_checker_);
+ return current_->task_execution_allowed_;
+}
+
+MessageLoopCurrent::ScopedNestableTaskAllower::ScopedNestableTaskAllower()
+ : loop_(GetTLSMessageLoop()->Get()),
+ old_state_(loop_->NestableTasksAllowed()) {
+ loop_->SetNestableTasksAllowed(true);
+}
+
+MessageLoopCurrent::ScopedNestableTaskAllower::~ScopedNestableTaskAllower() {
+ loop_->SetNestableTasksAllowed(old_state_);
+}
+
+// static
+void MessageLoopCurrent::BindToCurrentThreadInternal(MessageLoop* current) {
+ DCHECK(!GetTLSMessageLoop()->Get())
+ << "Can't register a second MessageLoop on the same thread.";
+ GetTLSMessageLoop()->Set(current);
+}
+
+// static
+void MessageLoopCurrent::UnbindFromCurrentThreadInternal(MessageLoop* current) {
+ DCHECK_EQ(current, GetTLSMessageLoop()->Get());
+ GetTLSMessageLoop()->Set(nullptr);
+}
+
+bool MessageLoopCurrent::IsBoundToCurrentThreadInternal(
+ MessageLoop* message_loop) {
+ return GetTLSMessageLoop()->Get() == message_loop;
+}
+
+#if !defined(OS_NACL)
+
+//------------------------------------------------------------------------------
+// MessageLoopCurrentForUI
+
+// static
+MessageLoopCurrentForUI MessageLoopCurrentForUI::Get() {
+ MessageLoop* loop = GetTLSMessageLoop()->Get();
+ DCHECK(loop);
+#if defined(OS_ANDROID)
+ DCHECK(loop->IsType(MessageLoop::TYPE_UI) ||
+ loop->IsType(MessageLoop::TYPE_JAVA));
+#else // defined(OS_ANDROID)
+ DCHECK(loop->IsType(MessageLoop::TYPE_UI));
+#endif // defined(OS_ANDROID)
+ auto* loop_for_ui = static_cast<MessageLoopForUI*>(loop);
+ return MessageLoopCurrentForUI(
+ loop_for_ui, static_cast<MessagePumpForUI*>(loop_for_ui->pump_.get()));
+}
+
+// static
+bool MessageLoopCurrentForUI::IsSet() {
+ MessageLoop* loop = GetTLSMessageLoop()->Get();
+ return loop &&
+#if defined(OS_ANDROID)
+ (loop->IsType(MessageLoop::TYPE_UI) ||
+ loop->IsType(MessageLoop::TYPE_JAVA));
+#else // defined(OS_ANDROID)
+ loop->IsType(MessageLoop::TYPE_UI);
+#endif // defined(OS_ANDROID)
+}
+
+#if defined(USE_OZONE) && !defined(OS_FUCHSIA) && !defined(OS_WIN)
+bool MessageLoopCurrentForUI::WatchFileDescriptor(
+ int fd,
+ bool persistent,
+ MessagePumpForUI::Mode mode,
+ MessagePumpForUI::FdWatchController* controller,
+ MessagePumpForUI::FdWatcher* delegate) {
+ DCHECK_CALLED_ON_VALID_THREAD(current_->bound_thread_checker_);
+ return pump_->WatchFileDescriptor(fd, persistent, mode, controller, delegate);
+}
+#endif
+
+#if defined(OS_IOS)
+void MessageLoopCurrentForUI::Attach() {
+ static_cast<MessageLoopForUI*>(current_)->Attach();
+}
+#endif // defined(OS_IOS)
+
+#if defined(OS_ANDROID)
+void MessageLoopCurrentForUI::Abort() {
+ static_cast<MessageLoopForUI*>(current_)->Abort();
+}
+#endif // defined(OS_ANDROID)
+
+#endif // !defined(OS_NACL)
+
+//------------------------------------------------------------------------------
+// MessageLoopCurrentForIO
+
+// static
+MessageLoopCurrentForIO MessageLoopCurrentForIO::Get() {
+ MessageLoop* loop = GetTLSMessageLoop()->Get();
+ DCHECK(loop);
+ DCHECK_EQ(MessageLoop::TYPE_IO, loop->type());
+ auto* loop_for_io = static_cast<MessageLoopForIO*>(loop);
+ return MessageLoopCurrentForIO(
+ loop_for_io, static_cast<MessagePumpForIO*>(loop_for_io->pump_.get()));
+}
+
+// static
+bool MessageLoopCurrentForIO::IsSet() {
+ MessageLoop* loop = GetTLSMessageLoop()->Get();
+ return loop && loop->IsType(MessageLoop::TYPE_IO);
+}
+
+#if !defined(OS_NACL_SFI)
+
+#if defined(OS_WIN)
+HRESULT MessageLoopCurrentForIO::RegisterIOHandler(
+ HANDLE file,
+ MessagePumpForIO::IOHandler* handler) {
+ DCHECK_CALLED_ON_VALID_THREAD(current_->bound_thread_checker_);
+ return pump_->RegisterIOHandler(file, handler);
+}
+
+bool MessageLoopCurrentForIO::RegisterJobObject(
+ HANDLE job,
+ MessagePumpForIO::IOHandler* handler) {
+ DCHECK_CALLED_ON_VALID_THREAD(current_->bound_thread_checker_);
+ return pump_->RegisterJobObject(job, handler);
+}
+
+bool MessageLoopCurrentForIO::WaitForIOCompletion(
+ DWORD timeout,
+ MessagePumpForIO::IOHandler* filter) {
+ DCHECK_CALLED_ON_VALID_THREAD(current_->bound_thread_checker_);
+ return pump_->WaitForIOCompletion(timeout, filter);
+}
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+bool MessageLoopCurrentForIO::WatchFileDescriptor(
+ int fd,
+ bool persistent,
+ MessagePumpForIO::Mode mode,
+ MessagePumpForIO::FdWatchController* controller,
+ MessagePumpForIO::FdWatcher* delegate) {
+ DCHECK_CALLED_ON_VALID_THREAD(current_->bound_thread_checker_);
+ return pump_->WatchFileDescriptor(fd, persistent, mode, controller, delegate);
+}
+#endif // defined(OS_WIN)
+
+#endif // !defined(OS_NACL_SFI)
+
+#if defined(OS_FUCHSIA)
+// Additional watch API for native platform resources.
+bool MessageLoopCurrentForIO::WatchZxHandle(
+ zx_handle_t handle,
+ bool persistent,
+ zx_signals_t signals,
+ MessagePumpForIO::ZxHandleWatchController* controller,
+ MessagePumpForIO::ZxHandleWatcher* delegate) {
+ DCHECK_CALLED_ON_VALID_THREAD(current_->bound_thread_checker_);
+ return pump_->WatchZxHandle(handle, persistent, signals, controller,
+ delegate);
+}
+#endif
+
+} // namespace base
diff --git a/base/message_loop/message_loop_current.h b/base/message_loop/message_loop_current.h
new file mode 100644
index 0000000000..61d1607e31
--- /dev/null
+++ b/base/message_loop/message_loop_current.h
@@ -0,0 +1,297 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MESSAGE_LOOP_MESSAGE_LOOP_CURRENT_H_
+#define BASE_MESSAGE_LOOP_MESSAGE_LOOP_CURRENT_H_
+
+#include "base/base_export.h"
+#include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/message_loop/message_pump_for_io.h"
+#include "base/message_loop/message_pump_for_ui.h"
+#include "base/pending_task.h"
+#include "base/single_thread_task_runner.h"
+#include "build/build_config.h"
+
+namespace base {
+
+class MessageLoop;
+
+// MessageLoopCurrent is a proxy to the public interface of the MessageLoop
+// bound to the thread it's obtained on.
+//
+// MessageLoopCurrent(ForUI|ForIO) is available statically through
+// MessageLoopCurrent(ForUI|ForIO)::Get() on threads that have a matching
+// MessageLoop instance. APIs intended for all consumers on the thread should be
+// on MessageLoopCurrent(ForUI|ForIO), while APIs intended for the owner of the
+// instance should be on MessageLoop(ForUI|ForIO).
+//
+// Why: Historically MessageLoop::current() gave access to the full MessageLoop
+// API, preventing both addition of powerful owner-only APIs as well as making
+// it harder to remove callers of deprecated APIs (that need to stick around for
+// a few owner-only use cases and re-accrue callers after cleanup per remaining
+// publicly available).
+//
+// As such, many methods below are flagged as deprecated and should be removed
+// (or moved back to MessageLoop) once all static callers have been migrated.
+class BASE_EXPORT MessageLoopCurrent {
+ public:
+ // MessageLoopCurrent is effectively just a disguised pointer and is fine to
+ // copy around.
+ MessageLoopCurrent(const MessageLoopCurrent& other) = default;
+ MessageLoopCurrent& operator=(const MessageLoopCurrent& other) = default;
+
+ // Returns a proxy object to interact with the MessageLoop running the
+ // current thread. It must only be used on the thread it was obtained.
+ static MessageLoopCurrent Get();
+
+ // Returns true if the current thread is running a MessageLoop. Prefer this to
+ // verifying the boolean value of Get() (so that Get() can ultimately DCHECK
+ // it's only invoked when IsSet()).
+ static bool IsSet();
+
+ // Allow MessageLoopCurrent to be used like a pointer to support the many
+ // callsites that used MessageLoop::current() that way when it was a
+ // MessageLoop*.
+ MessageLoopCurrent* operator->() { return this; }
+ explicit operator bool() const { return !!current_; }
+
+ // TODO(gab): Migrate the types of variables that store MessageLoop::current()
+ // and remove this implicit cast back to MessageLoop*.
+ operator MessageLoop*() const { return current_; }
+
+ // A DestructionObserver is notified when the current MessageLoop is being
+ // destroyed. These observers are notified prior to MessageLoop::current()
+ // being changed to return NULL. This gives interested parties the chance to
+ // do final cleanup that depends on the MessageLoop.
+ //
+ // NOTE: Any tasks posted to the MessageLoop during this notification will
+ // not be run. Instead, they will be deleted.
+ //
+ // Deprecation note: Prefer SequenceLocalStorageSlot<std::unique_ptr<Foo>> to
+ // DestructionObserver to bind an object's lifetime to the current
+ // thread/sequence.
+ class BASE_EXPORT DestructionObserver {
+ public:
+ virtual void WillDestroyCurrentMessageLoop() = 0;
+
+ protected:
+ virtual ~DestructionObserver() = default;
+ };
+
+ // Add a DestructionObserver, which will start receiving notifications
+ // immediately.
+ void AddDestructionObserver(DestructionObserver* destruction_observer);
+
+ // Remove a DestructionObserver. It is safe to call this method while a
+ // DestructionObserver is receiving a notification callback.
+ void RemoveDestructionObserver(DestructionObserver* destruction_observer);
+
+ // Forwards to MessageLoop::task_runner().
+ // DEPRECATED(https://crbug.com/616447): Use ThreadTaskRunnerHandle::Get()
+ // instead of MessageLoopCurrent::Get()->task_runner().
+ const scoped_refptr<SingleThreadTaskRunner>& task_runner() const;
+
+ // Forwards to MessageLoop::SetTaskRunner().
+ // DEPRECATED(https://crbug.com/825327): only owners of the MessageLoop
+ // instance should replace its TaskRunner.
+ void SetTaskRunner(scoped_refptr<SingleThreadTaskRunner> task_runner);
+
+ // A TaskObserver is an object that receives task notifications from the
+ // MessageLoop.
+ //
+ // NOTE: A TaskObserver implementation should be extremely fast!
+ class BASE_EXPORT TaskObserver {
+ public:
+ // This method is called before processing a task.
+ virtual void WillProcessTask(const PendingTask& pending_task) = 0;
+
+ // This method is called after processing a task.
+ virtual void DidProcessTask(const PendingTask& pending_task) = 0;
+
+ protected:
+ virtual ~TaskObserver() = default;
+ };
+
+ // Forwards to MessageLoop::(Add|Remove)TaskObserver.
+ // DEPRECATED(https://crbug.com/825327): only owners of the MessageLoop
+ // instance should add task observers on it.
+ void AddTaskObserver(TaskObserver* task_observer);
+ void RemoveTaskObserver(TaskObserver* task_observer);
+
+ // Enables or disables the recursive task processing. This happens in the case
+ // of recursive message loops. Some unwanted message loops may occur when
+ // using common controls or printer functions. By default, recursive task
+ // processing is disabled.
+ //
+ // Please use |ScopedNestableTaskAllower| instead of calling these methods
+ // directly. In general, nestable message loops are to be avoided. They are
+ // dangerous and difficult to get right, so please use with extreme caution.
+ //
+ // The specific case where tasks get queued is:
+ // - The thread is running a message loop.
+ // - It receives a task #1 and executes it.
+ // - The task #1 implicitly starts a message loop, like a MessageBox in the
+ // unit test. This can also be StartDoc or GetSaveFileName.
+ // - The thread receives a task #2 before or while in this second message
+ // loop.
+ // - With NestableTasksAllowed set to true, the task #2 will run right away.
+ // Otherwise, it will get executed right after task #1 completes at "thread
+ // message loop level".
+ //
+ // DEPRECATED(https://crbug.com/750779): Use RunLoop::Type on the relevant
+ // RunLoop instead of these methods.
+ // TODO(gab): Migrate usage and delete these methods.
+ void SetNestableTasksAllowed(bool allowed);
+ bool NestableTasksAllowed() const;
+
+ // Enables nestable tasks on the current MessageLoop while in scope.
+ // DEPRECATED(https://crbug.com/750779): This should not be used when the
+ // nested loop is driven by RunLoop (use RunLoop::Type::kNestableTasksAllowed
+ // instead). It can however still be useful in a few scenarios where re-
+ // entrancy is caused by a native message loop.
+ // TODO(gab): Remove usage of this class alongside RunLoop and rename it to
+ // ScopedApplicationTasksAllowedInNativeNestedLoop(?) for remaining use cases.
+ class BASE_EXPORT ScopedNestableTaskAllower {
+ public:
+ ScopedNestableTaskAllower();
+ ~ScopedNestableTaskAllower();
+
+ private:
+ MessageLoop* const loop_;
+ const bool old_state_;
+ };
+
+ // Returns true if the message loop is idle (ignoring delayed tasks). This is
+ // the same condition which triggers DoWork() to return false: i.e.
+ // out of tasks which can be processed at the current run-level -- there might
+ // be deferred non-nestable tasks remaining if currently in a nested run
+ // level.
+ bool IsIdleForTesting();
+
+ // Binds |current| to the current thread. It will from then on be the
+ // MessageLoop driven by MessageLoopCurrent on this thread. This is only meant
+ // to be invoked by the MessageLoop itself.
+ static void BindToCurrentThreadInternal(MessageLoop* current);
+
+ // Unbinds |current| from the current thread. Must be invoked on the same
+ // thread that invoked |BindToCurrentThreadInternal(current)|. This is only
+ // meant to be invoked by the MessageLoop itself.
+ static void UnbindFromCurrentThreadInternal(MessageLoop* current);
+
+ // Returns true if |message_loop| is bound to MessageLoopCurrent on the
+ // current thread. This is only meant to be invoked by the MessageLoop itself.
+ static bool IsBoundToCurrentThreadInternal(MessageLoop* message_loop);
+
+ protected:
+ explicit MessageLoopCurrent(MessageLoop* current) : current_(current) {}
+
+ MessageLoop* const current_;
+};
+
+#if !defined(OS_NACL)
+
+// ForUI extension of MessageLoopCurrent.
+class BASE_EXPORT MessageLoopCurrentForUI : public MessageLoopCurrent {
+ public:
+ // Returns an interface for the MessageLoopForUI of the current thread.
+ // Asserts that IsSet().
+ static MessageLoopCurrentForUI Get();
+
+ // Returns true if the current thread is running a MessageLoopForUI.
+ static bool IsSet();
+
+ MessageLoopCurrentForUI* operator->() { return this; }
+
+#if defined(USE_OZONE) && !defined(OS_FUCHSIA) && !defined(OS_WIN)
+ // Please see MessagePumpLibevent for definition.
+ static_assert(std::is_same<MessagePumpForUI, MessagePumpLibevent>::value,
+ "MessageLoopCurrentForUI::WatchFileDescriptor is not supported "
+ "when MessagePumpForUI is not a MessagePumpLibevent.");
+ bool WatchFileDescriptor(int fd,
+ bool persistent,
+ MessagePumpForUI::Mode mode,
+ MessagePumpForUI::FdWatchController* controller,
+ MessagePumpForUI::FdWatcher* delegate);
+#endif
+
+#if defined(OS_IOS)
+ // Forwards to MessageLoopForUI::Attach().
+ // TODO(https://crbug.com/825327): Plumb the actual MessageLoopForUI* to
+ // callers and remove ability to access this method from
+ // MessageLoopCurrentForUI.
+ void Attach();
+#endif
+
+#if defined(OS_ANDROID)
+ // Forwards to MessageLoopForUI::Abort().
+ // TODO(https://crbug.com/825327): Plumb the actual MessageLoopForUI* to
+ // callers and remove ability to access this method from
+ // MessageLoopCurrentForUI.
+ void Abort();
+#endif
+
+ private:
+ MessageLoopCurrentForUI(MessageLoop* current, MessagePumpForUI* pump)
+ : MessageLoopCurrent(current), pump_(pump) {
+ DCHECK(pump_);
+ }
+
+ MessagePumpForUI* const pump_;
+};
+
+#endif // !defined(OS_NACL)
+
+// ForIO extension of MessageLoopCurrent.
+class BASE_EXPORT MessageLoopCurrentForIO : public MessageLoopCurrent {
+ public:
+ // Returns an interface for the MessageLoopForIO of the current thread.
+ // Asserts that IsSet().
+ static MessageLoopCurrentForIO Get();
+
+ // Returns true if the current thread is running a MessageLoopForIO.
+ static bool IsSet();
+
+ MessageLoopCurrentForIO* operator->() { return this; }
+
+#if !defined(OS_NACL_SFI)
+
+#if defined(OS_WIN)
+ // Please see MessagePumpWin for definitions of these methods.
+ HRESULT RegisterIOHandler(HANDLE file, MessagePumpForIO::IOHandler* handler);
+ bool RegisterJobObject(HANDLE job, MessagePumpForIO::IOHandler* handler);
+ bool WaitForIOCompletion(DWORD timeout, MessagePumpForIO::IOHandler* filter);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ // Please see WatchableIOMessagePumpPosix for definition.
+ // Prefer base::FileDescriptorWatcher for non-critical IO.
+ bool WatchFileDescriptor(int fd,
+ bool persistent,
+ MessagePumpForIO::Mode mode,
+ MessagePumpForIO::FdWatchController* controller,
+ MessagePumpForIO::FdWatcher* delegate);
+#endif // defined(OS_WIN)
+
+#if defined(OS_FUCHSIA)
+ // Additional watch API for native platform resources.
+ bool WatchZxHandle(zx_handle_t handle,
+ bool persistent,
+ zx_signals_t signals,
+ MessagePumpForIO::ZxHandleWatchController* controller,
+ MessagePumpForIO::ZxHandleWatcher* delegate);
+#endif // defined(OS_FUCHSIA)
+
+#endif // !defined(OS_NACL_SFI)
+
+ private:
+ MessageLoopCurrentForIO(MessageLoop* current, MessagePumpForIO* pump)
+ : MessageLoopCurrent(current), pump_(pump) {
+ DCHECK(pump_);
+ }
+
+ MessagePumpForIO* const pump_;
+};
+
+} // namespace base
+
+#endif // BASE_MESSAGE_LOOP_MESSAGE_LOOP_CURRENT_H_
diff --git a/base/message_loop/message_loop_io_posix_unittest.cc b/base/message_loop/message_loop_io_posix_unittest.cc
new file mode 100644
index 0000000000..4dd5f28028
--- /dev/null
+++ b/base/message_loop/message_loop_io_posix_unittest.cc
@@ -0,0 +1,418 @@
+// 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/message_loop/message_loop.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/message_loop/message_pump_for_io.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/run_loop.h"
+#include "base/test/gtest_util.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+#if !defined(OS_NACL)
+
+namespace {
+
+class MessageLoopForIoPosixTest : public testing::Test {
+ public:
+ MessageLoopForIoPosixTest() = default;
+
+ // testing::Test interface.
+ void SetUp() override {
+ // Create a file descriptor. Doesn't need to be readable or writable,
+ // as we don't need to actually get any notifications.
+ // pipe() is just the easiest way to do it.
+ int pipefds[2];
+ int err = pipe(pipefds);
+ ASSERT_EQ(0, err);
+ read_fd_ = ScopedFD(pipefds[0]);
+ write_fd_ = ScopedFD(pipefds[1]);
+ }
+
+ void TriggerReadEvent() {
+ // Write from the other end of the pipe to trigger the event.
+ char c = '\0';
+ EXPECT_EQ(1, HANDLE_EINTR(write(write_fd_.get(), &c, 1)));
+ }
+
+ protected:
+ ScopedFD read_fd_;
+ ScopedFD write_fd_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageLoopForIoPosixTest);
+};
+
+class TestHandler : public MessagePumpForIO::FdWatcher {
+ public:
+ void OnFileCanReadWithoutBlocking(int fd) override {
+ watcher_to_delete_ = nullptr;
+ is_readable_ = true;
+ RunLoop::QuitCurrentWhenIdleDeprecated();
+ }
+ void OnFileCanWriteWithoutBlocking(int fd) override {
+ watcher_to_delete_ = nullptr;
+ is_writable_ = true;
+ RunLoop::QuitCurrentWhenIdleDeprecated();
+ }
+
+ bool is_readable_ = false;
+ bool is_writable_ = false;
+
+ // If set then the contained watcher will be deleted on notification.
+ std::unique_ptr<MessagePumpForIO::FdWatchController> watcher_to_delete_;
+};
+
+// Watcher that calls specified closures when read/write events occur. Verifies
+// that each non-null closure passed to this class is called once and only once.
+// Also resets the read event by reading from the FD.
+class CallClosureHandler : public MessagePumpForIO::FdWatcher {
+ public:
+ CallClosureHandler(OnceClosure read_closure, OnceClosure write_closure)
+ : read_closure_(std::move(read_closure)),
+ write_closure_(std::move(write_closure)) {}
+
+ ~CallClosureHandler() override {
+ EXPECT_TRUE(read_closure_.is_null());
+ EXPECT_TRUE(write_closure_.is_null());
+ }
+
+ void SetReadClosure(OnceClosure read_closure) {
+ EXPECT_TRUE(read_closure_.is_null());
+ read_closure_ = std::move(read_closure);
+ }
+
+ void SetWriteClosure(OnceClosure write_closure) {
+ EXPECT_TRUE(write_closure_.is_null());
+ write_closure_ = std::move(write_closure);
+ }
+
+ // base:MessagePumpFuchsia::Watcher interface.
+ void OnFileCanReadWithoutBlocking(int fd) override {
+ // Empty the pipe buffer to reset the event. Otherwise libevent
+ // implementation of MessageLoop may call the event handler again even if
+ // |read_closure_| below quits the RunLoop.
+ char c;
+ int result = HANDLE_EINTR(read(fd, &c, 1));
+ if (result == -1) {
+ PLOG(ERROR) << "read";
+ FAIL();
+ }
+ EXPECT_EQ(result, 1);
+
+ ASSERT_FALSE(read_closure_.is_null());
+ std::move(read_closure_).Run();
+ }
+
+ void OnFileCanWriteWithoutBlocking(int fd) override {
+ ASSERT_FALSE(write_closure_.is_null());
+ std::move(write_closure_).Run();
+ }
+
+ private:
+ OnceClosure read_closure_;
+ OnceClosure write_closure_;
+};
+
+TEST_F(MessageLoopForIoPosixTest, FileDescriptorWatcherOutlivesMessageLoop) {
+ // Simulate a MessageLoop that dies before an FileDescriptorWatcher.
+ // This could happen when people use the Singleton pattern or atexit.
+
+ // Arrange for watcher to live longer than message loop.
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
+ TestHandler handler;
+ {
+ MessageLoopForIO message_loop;
+
+ MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
+ write_fd_.get(), true, MessagePumpForIO::WATCH_WRITE, &watcher,
+ &handler);
+ // Don't run the message loop, just destroy it.
+ }
+
+ ASSERT_FALSE(handler.is_readable_);
+ ASSERT_FALSE(handler.is_writable_);
+}
+
+TEST_F(MessageLoopForIoPosixTest, FileDescriptorWatcherDoubleStop) {
+ // Verify that it's ok to call StopWatchingFileDescriptor().
+
+ // Arrange for message loop to live longer than watcher.
+ MessageLoopForIO message_loop;
+ {
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
+
+ TestHandler handler;
+ MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
+ write_fd_.get(), true, MessagePumpForIO::WATCH_WRITE, &watcher,
+ &handler);
+ ASSERT_TRUE(watcher.StopWatchingFileDescriptor());
+ ASSERT_TRUE(watcher.StopWatchingFileDescriptor());
+ }
+}
+
+TEST_F(MessageLoopForIoPosixTest, FileDescriptorWatcherDeleteInCallback) {
+ // Verify that it is OK to delete the FileDescriptorWatcher from within a
+ // callback.
+ MessageLoopForIO message_loop;
+
+ TestHandler handler;
+ handler.watcher_to_delete_ =
+ std::make_unique<MessagePumpForIO::FdWatchController>(FROM_HERE);
+
+ MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
+ write_fd_.get(), true, MessagePumpForIO::WATCH_WRITE,
+ handler.watcher_to_delete_.get(), &handler);
+ RunLoop().Run();
+}
+
+// Verify that basic readable notification works.
+TEST_F(MessageLoopForIoPosixTest, WatchReadable) {
+ MessageLoopForIO message_loop;
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
+ TestHandler handler;
+
+ // Watch the pipe for readability.
+ ASSERT_TRUE(MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
+ read_fd_.get(), /*persistent=*/false, MessagePumpForIO::WATCH_READ,
+ &watcher, &handler));
+
+ // The pipe should not be readable when first created.
+ RunLoop().RunUntilIdle();
+ ASSERT_FALSE(handler.is_readable_);
+ ASSERT_FALSE(handler.is_writable_);
+
+ TriggerReadEvent();
+
+ // We don't want to assume that the read fd becomes readable the
+ // instant a bytes is written, so Run until quit by an event.
+ RunLoop().Run();
+
+ ASSERT_TRUE(handler.is_readable_);
+ ASSERT_FALSE(handler.is_writable_);
+}
+
+// Verify that watching a file descriptor for writability succeeds.
+TEST_F(MessageLoopForIoPosixTest, WatchWritable) {
+ MessageLoopForIO message_loop;
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
+ TestHandler handler;
+
+ // Watch the pipe for writability.
+ ASSERT_TRUE(MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
+ write_fd_.get(), /*persistent=*/false, MessagePumpForIO::WATCH_WRITE,
+ &watcher, &handler));
+
+ // We should not receive a writable notification until we process events.
+ ASSERT_FALSE(handler.is_readable_);
+ ASSERT_FALSE(handler.is_writable_);
+
+ // The pipe should be writable immediately, but wait for the quit closure
+ // anyway, to be sure.
+ RunLoop().Run();
+
+ ASSERT_FALSE(handler.is_readable_);
+ ASSERT_TRUE(handler.is_writable_);
+}
+
+// Verify that RunUntilIdle() receives IO notifications.
+TEST_F(MessageLoopForIoPosixTest, RunUntilIdle) {
+ MessageLoopForIO message_loop;
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
+ TestHandler handler;
+
+ // Watch the pipe for readability.
+ ASSERT_TRUE(MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
+ read_fd_.get(), /*persistent=*/false, MessagePumpForIO::WATCH_READ,
+ &watcher, &handler));
+
+ // The pipe should not be readable when first created.
+ RunLoop().RunUntilIdle();
+ ASSERT_FALSE(handler.is_readable_);
+
+ TriggerReadEvent();
+
+ while (!handler.is_readable_)
+ RunLoop().RunUntilIdle();
+}
+
+void StopWatching(MessagePumpForIO::FdWatchController* controller,
+ RunLoop* run_loop) {
+ controller->StopWatchingFileDescriptor();
+ run_loop->Quit();
+}
+
+// Verify that StopWatchingFileDescriptor() works from an event handler.
+TEST_F(MessageLoopForIoPosixTest, StopFromHandler) {
+ MessageLoopForIO message_loop;
+ RunLoop run_loop;
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
+ CallClosureHandler handler(BindOnce(&StopWatching, &watcher, &run_loop),
+ OnceClosure());
+
+ // Create persistent watcher.
+ ASSERT_TRUE(MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
+ read_fd_.get(), /*persistent=*/true, MessagePumpForIO::WATCH_READ,
+ &watcher, &handler));
+
+ TriggerReadEvent();
+ run_loop.Run();
+
+ // Trigger the event again. The event handler should not be called again.
+ TriggerReadEvent();
+ RunLoop().RunUntilIdle();
+}
+
+// Verify that non-persistent watcher is called only once.
+TEST_F(MessageLoopForIoPosixTest, NonPersistentWatcher) {
+ MessageLoopForIO message_loop;
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
+
+ RunLoop run_loop;
+ CallClosureHandler handler(run_loop.QuitClosure(), OnceClosure());
+
+ // Create a non-persistent watcher.
+ ASSERT_TRUE(MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
+ read_fd_.get(), /*persistent=*/false, MessagePumpForIO::WATCH_READ,
+ &watcher, &handler));
+
+ TriggerReadEvent();
+ run_loop.Run();
+
+ // Trigger the event again. handler should not be called again.
+ TriggerReadEvent();
+ RunLoop().RunUntilIdle();
+}
+
+// Verify that persistent watcher is called every time the event is triggered.
+TEST_F(MessageLoopForIoPosixTest, PersistentWatcher) {
+ MessageLoopForIO message_loop;
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
+
+ RunLoop run_loop1;
+ CallClosureHandler handler(run_loop1.QuitClosure(), OnceClosure());
+
+ // Create persistent watcher.
+ ASSERT_TRUE(MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
+ read_fd_.get(), /*persistent=*/true, MessagePumpForIO::WATCH_READ,
+ &watcher, &handler));
+
+ TriggerReadEvent();
+ run_loop1.Run();
+
+ RunLoop run_loop2;
+ handler.SetReadClosure(run_loop2.QuitClosure());
+
+ // Trigger the event again. handler should be called now, which will quit
+ // run_loop2.
+ TriggerReadEvent();
+ run_loop2.Run();
+}
+
+void StopWatchingAndWatchAgain(MessagePumpForIO::FdWatchController* controller,
+ int fd,
+ MessagePumpForIO::FdWatcher* new_handler,
+ RunLoop* run_loop) {
+ controller->StopWatchingFileDescriptor();
+
+ ASSERT_TRUE(MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
+ fd, /*persistent=*/true, MessagePumpForIO::WATCH_READ, controller,
+ new_handler));
+
+ run_loop->Quit();
+}
+
+// Verify that a watcher can be stopped and reused from an event handler.
+TEST_F(MessageLoopForIoPosixTest, StopAndRestartFromHandler) {
+ MessageLoopForIO message_loop;
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
+
+ RunLoop run_loop1;
+ RunLoop run_loop2;
+ CallClosureHandler handler2(run_loop2.QuitClosure(), OnceClosure());
+ CallClosureHandler handler1(BindOnce(&StopWatchingAndWatchAgain, &watcher,
+ read_fd_.get(), &handler2, &run_loop1),
+ OnceClosure());
+
+ // Create persistent watcher.
+ ASSERT_TRUE(MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
+ read_fd_.get(), /*persistent=*/true, MessagePumpForIO::WATCH_READ,
+ &watcher, &handler1));
+
+ TriggerReadEvent();
+ run_loop1.Run();
+
+ // Trigger the event again. handler2 should be called now, which will quit
+ // run_loop2
+ TriggerReadEvent();
+ run_loop2.Run();
+}
+
+// Verify that the pump properly handles a delayed task after an IO event.
+TEST_F(MessageLoopForIoPosixTest, IoEventThenTimer) {
+ MessageLoopForIO message_loop;
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
+
+ RunLoop timer_run_loop;
+ message_loop.task_runner()->PostDelayedTask(
+ FROM_HERE, timer_run_loop.QuitClosure(),
+ base::TimeDelta::FromMilliseconds(10));
+
+ RunLoop watcher_run_loop;
+ CallClosureHandler handler(watcher_run_loop.QuitClosure(), OnceClosure());
+
+ // Create a non-persistent watcher.
+ ASSERT_TRUE(MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
+ read_fd_.get(), /*persistent=*/false, MessagePumpForIO::WATCH_READ,
+ &watcher, &handler));
+
+ TriggerReadEvent();
+
+ // Normally the IO event will be received before the delayed task is
+ // executed, so this run loop will first handle the IO event and then quit on
+ // the timer.
+ timer_run_loop.Run();
+
+ // Run watcher_run_loop in case the IO event wasn't received before the
+ // delayed task.
+ watcher_run_loop.Run();
+}
+
+// Verify that the pipe can handle an IO event after a delayed task.
+TEST_F(MessageLoopForIoPosixTest, TimerThenIoEvent) {
+ MessageLoopForIO message_loop;
+ MessagePumpForIO::FdWatchController watcher(FROM_HERE);
+
+ // Trigger read event from a delayed task.
+ message_loop.task_runner()->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&MessageLoopForIoPosixTest::TriggerReadEvent, Unretained(this)),
+ TimeDelta::FromMilliseconds(1));
+
+ RunLoop run_loop;
+ CallClosureHandler handler(run_loop.QuitClosure(), OnceClosure());
+
+ // Create a non-persistent watcher.
+ ASSERT_TRUE(MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
+ read_fd_.get(), /*persistent=*/false, MessagePumpForIO::WATCH_READ,
+ &watcher, &handler));
+
+ run_loop.Run();
+}
+
+} // namespace
+
+#endif // !defined(OS_NACL)
+
+} // namespace base
diff --git a/base/message_loop/message_loop_perftest.cc b/base/message_loop/message_loop_perftest.cc
new file mode 100644
index 0000000000..867e8fe850
--- /dev/null
+++ b/base/message_loop/message_loop_perftest.cc
@@ -0,0 +1,254 @@
+// 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 <memory>
+#include <vector>
+
+#include "base/atomicops.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/atomic_flag.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_test.h"
+
+namespace base {
+
+namespace {
+
+// A thread that waits for the caller to signal an event before proceeding to
+// call Action::Run().
+class PostingThread {
+ public:
+ class Action {
+ public:
+ virtual ~Action() = default;
+
+ // Called after the thread is started and |start_event_| is signalled.
+ virtual void Run() = 0;
+
+ protected:
+ Action() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Action);
+ };
+
+ // Creates a PostingThread where the thread waits on |start_event| before
+ // calling action->Run(). If a thread is returned, the thread is guaranteed to
+ // be allocated and running and the caller must call Join() before destroying
+ // the PostingThread.
+ static std::unique_ptr<PostingThread> Create(WaitableEvent* start_event,
+ std::unique_ptr<Action> action) {
+ auto posting_thread =
+ WrapUnique(new PostingThread(start_event, std::move(action)));
+
+ if (!posting_thread->Start())
+ return nullptr;
+
+ return posting_thread;
+ }
+
+ ~PostingThread() { DCHECK_EQ(!thread_handle_.is_null(), join_called_); }
+
+ void Join() {
+ PlatformThread::Join(thread_handle_);
+ join_called_ = true;
+ }
+
+ private:
+ class Delegate final : public PlatformThread::Delegate {
+ public:
+ Delegate(PostingThread* outer, std::unique_ptr<Action> action)
+ : outer_(outer), action_(std::move(action)) {
+ DCHECK(outer_);
+ DCHECK(action_);
+ }
+
+ ~Delegate() override = default;
+
+ private:
+ void ThreadMain() override {
+ outer_->thread_started_.Signal();
+ outer_->start_event_->Wait();
+ action_->Run();
+ }
+
+ PostingThread* const outer_;
+ const std::unique_ptr<Action> action_;
+
+ DISALLOW_COPY_AND_ASSIGN(Delegate);
+ };
+
+ PostingThread(WaitableEvent* start_event, std::unique_ptr<Action> delegate)
+ : start_event_(start_event),
+ thread_started_(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED),
+ delegate_(this, std::move(delegate)) {
+ DCHECK(start_event_);
+ }
+
+ bool Start() {
+ bool thread_created =
+ PlatformThread::Create(0, &delegate_, &thread_handle_);
+ if (thread_created)
+ thread_started_.Wait();
+
+ return thread_created;
+ }
+
+ bool join_called_ = false;
+ WaitableEvent* const start_event_;
+ WaitableEvent thread_started_;
+ Delegate delegate_;
+
+ PlatformThreadHandle thread_handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(PostingThread);
+};
+
+class MessageLoopPerfTest : public ::testing::TestWithParam<int> {
+ public:
+ MessageLoopPerfTest()
+ : message_loop_task_runner_(SequencedTaskRunnerHandle::Get()),
+ run_posting_threads_(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED) {}
+
+ static std::string ParamInfoToString(
+ ::testing::TestParamInfo<int> param_info) {
+ return PostingThreadCountToString(param_info.param);
+ }
+
+ static std::string PostingThreadCountToString(int posting_threads) {
+ // Special case 1 thread for thread vs threads.
+ if (posting_threads == 1)
+ return "1_Posting_Thread";
+
+ return StringPrintf("%d_Posting_Threads", posting_threads);
+ }
+
+ protected:
+ class ContinuouslyPostTasks final : public PostingThread::Action {
+ public:
+ ContinuouslyPostTasks(MessageLoopPerfTest* outer) : outer_(outer) {
+ DCHECK(outer_);
+ }
+ ~ContinuouslyPostTasks() override = default;
+
+ private:
+ void Run() override {
+ RepeatingClosure task_to_run =
+ BindRepeating([](size_t* num_tasks_run) { ++*num_tasks_run; },
+ &outer_->num_tasks_run_);
+ while (!outer_->stop_posting_threads_.IsSet()) {
+ outer_->message_loop_task_runner_->PostTask(FROM_HERE, task_to_run);
+ subtle::NoBarrier_AtomicIncrement(&outer_->num_tasks_posted_, 1);
+ }
+ }
+
+ MessageLoopPerfTest* const outer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContinuouslyPostTasks);
+ };
+
+ void SetUp() override {
+ // This check is here because we can't ASSERT_TRUE in the constructor.
+ ASSERT_TRUE(message_loop_task_runner_);
+ }
+
+ // Runs ActionType::Run() on |num_posting_threads| and requests test
+ // termination around |duration|.
+ template <typename ActionType>
+ void RunTest(const int num_posting_threads, TimeDelta duration) {
+ std::vector<std::unique_ptr<PostingThread>> threads;
+ for (int i = 0; i < num_posting_threads; ++i) {
+ threads.emplace_back(PostingThread::Create(
+ &run_posting_threads_, std::make_unique<ActionType>(this)));
+ // Don't assert here to simplify the code that requires a Join() call for
+ // every created PostingThread.
+ EXPECT_TRUE(threads[i]);
+ }
+
+ RunLoop run_loop;
+ message_loop_task_runner_->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(
+ [](RunLoop* run_loop, AtomicFlag* stop_posting_threads) {
+ stop_posting_threads->Set();
+ run_loop->Quit();
+ },
+ &run_loop, &stop_posting_threads_),
+ duration);
+
+ TimeTicks post_task_start = TimeTicks::Now();
+ run_posting_threads_.Signal();
+
+ TimeTicks run_loop_start = TimeTicks::Now();
+ run_loop.Run();
+ tasks_run_duration_ = TimeTicks::Now() - run_loop_start;
+
+ for (auto& thread : threads)
+ thread->Join();
+
+ tasks_posted_duration_ = TimeTicks::Now() - post_task_start;
+ }
+
+ size_t num_tasks_posted() const {
+ return subtle::NoBarrier_Load(&num_tasks_posted_);
+ }
+
+ TimeDelta tasks_posted_duration() const { return tasks_posted_duration_; }
+
+ size_t num_tasks_run() const { return num_tasks_run_; }
+
+ TimeDelta tasks_run_duration() const { return tasks_run_duration_; }
+
+ private:
+ MessageLoop message_loop_;
+
+ // Accessed on multiple threads, thread-safe or constant:
+ const scoped_refptr<SequencedTaskRunner> message_loop_task_runner_;
+ WaitableEvent run_posting_threads_;
+ AtomicFlag stop_posting_threads_;
+ subtle::AtomicWord num_tasks_posted_ = 0;
+
+ // Accessed only on the test case thread:
+ TimeDelta tasks_posted_duration_;
+ TimeDelta tasks_run_duration_;
+ size_t num_tasks_run_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageLoopPerfTest);
+};
+
+} // namespace
+
+TEST_P(MessageLoopPerfTest, PostTaskRate) {
+ // Measures the average rate of posting tasks from different threads and the
+ // average rate that the message loop is running those tasks.
+ RunTest<ContinuouslyPostTasks>(GetParam(), TimeDelta::FromSeconds(3));
+ perf_test::PrintResult("task_posting", "",
+ PostingThreadCountToString(GetParam()),
+ tasks_posted_duration().InMicroseconds() /
+ static_cast<double>(num_tasks_posted()),
+ "us/task", true);
+ perf_test::PrintResult("task_running", "",
+ PostingThreadCountToString(GetParam()),
+ tasks_run_duration().InMicroseconds() /
+ static_cast<double>(num_tasks_run()),
+ "us/task", true);
+}
+
+INSTANTIATE_TEST_CASE_P(,
+ MessageLoopPerfTest,
+ ::testing::Values(1, 5, 10),
+ MessageLoopPerfTest::ParamInfoToString);
+} // namespace base
diff --git a/base/message_loop/message_loop_task_runner.cc b/base/message_loop/message_loop_task_runner.cc
index aece087b76..f251e3b8b2 100644
--- a/base/message_loop/message_loop_task_runner.cc
+++ b/base/message_loop/message_loop_task_runner.cc
@@ -24,31 +24,29 @@ void MessageLoopTaskRunner::BindToCurrentThread() {
valid_thread_id_ = PlatformThread::CurrentId();
}
-bool MessageLoopTaskRunner::PostDelayedTask(
- const tracked_objects::Location& from_here,
- OnceClosure task,
- base::TimeDelta delay) {
+bool MessageLoopTaskRunner::PostDelayedTask(const Location& from_here,
+ OnceClosure task,
+ base::TimeDelta delay) {
DCHECK(!task.is_null()) << from_here.ToString();
return incoming_queue_->AddToIncomingQueue(from_here, std::move(task), delay,
- true);
+ Nestable::kNestable);
}
bool MessageLoopTaskRunner::PostNonNestableDelayedTask(
- const tracked_objects::Location& from_here,
+ const Location& from_here,
OnceClosure task,
base::TimeDelta delay) {
DCHECK(!task.is_null()) << from_here.ToString();
return incoming_queue_->AddToIncomingQueue(from_here, std::move(task), delay,
- false);
+ Nestable::kNonNestable);
}
-bool MessageLoopTaskRunner::RunsTasksOnCurrentThread() const {
+bool MessageLoopTaskRunner::RunsTasksInCurrentSequence() const {
AutoLock lock(valid_thread_id_lock_);
return valid_thread_id_ == PlatformThread::CurrentId();
}
-MessageLoopTaskRunner::~MessageLoopTaskRunner() {
-}
+MessageLoopTaskRunner::~MessageLoopTaskRunner() = default;
} // namespace internal
diff --git a/base/message_loop/message_loop_task_runner.h b/base/message_loop/message_loop_task_runner.h
index 99a96a711e..c7d48c28c1 100644
--- a/base/message_loop/message_loop_task_runner.h
+++ b/base/message_loop/message_loop_task_runner.h
@@ -31,13 +31,13 @@ class BASE_EXPORT MessageLoopTaskRunner : public SingleThreadTaskRunner {
void BindToCurrentThread();
// SingleThreadTaskRunner implementation
- bool PostDelayedTask(const tracked_objects::Location& from_here,
+ bool PostDelayedTask(const Location& from_here,
OnceClosure task,
- base::TimeDelta delay) override;
- bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here,
+ TimeDelta delay) override;
+ bool PostNonNestableDelayedTask(const Location& from_here,
OnceClosure task,
- base::TimeDelta delay) override;
- bool RunsTasksOnCurrentThread() const override;
+ TimeDelta delay) override;
+ bool RunsTasksInCurrentSequence() const override;
private:
friend class RefCountedThreadSafe<MessageLoopTaskRunner>;
diff --git a/base/message_loop/message_loop_task_runner_perftest.cc b/base/message_loop/message_loop_task_runner_perftest.cc
new file mode 100644
index 0000000000..3ab9ba2dcf
--- /dev/null
+++ b/base/message_loop/message_loop_task_runner_perftest.cc
@@ -0,0 +1,191 @@
+// 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.
+
+#include "base/message_loop/message_loop_task_runner.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/debug/task_annotator.h"
+#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/message_loop/incoming_task_queue.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_task_runner.h"
+#include "base/message_loop/message_pump.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_test.h"
+
+namespace base {
+
+namespace {
+
+// Tests below will post tasks in a loop until |kPostTaskPerfTestDuration| has
+// elapsed.
+constexpr TimeDelta kPostTaskPerfTestDuration =
+ base::TimeDelta::FromSeconds(30);
+
+} // namespace
+
+class FakeObserver : public internal::IncomingTaskQueue::Observer {
+ public:
+ // IncomingTaskQueue::Observer
+ void WillQueueTask(PendingTask* task) override {}
+ void DidQueueTask(bool was_empty) override {}
+
+ virtual void RunTask(PendingTask* task) { std::move(task->task).Run(); }
+};
+
+// Exercises MessageLoopTaskRunner's multi-threaded queue in isolation.
+class BasicPostTaskPerfTest : public testing::Test {
+ public:
+ void Run(int batch_size,
+ int tasks_per_reload,
+ std::unique_ptr<FakeObserver> task_source_observer) {
+ base::TimeTicks start = base::TimeTicks::Now();
+ base::TimeTicks now;
+ FakeObserver* task_source_observer_raw = task_source_observer.get();
+ scoped_refptr<internal::IncomingTaskQueue> queue(
+ base::MakeRefCounted<internal::IncomingTaskQueue>(
+ std::move(task_source_observer)));
+ scoped_refptr<SingleThreadTaskRunner> task_runner(
+ base::MakeRefCounted<internal::MessageLoopTaskRunner>(queue));
+ uint32_t num_posted = 0;
+ do {
+ for (int i = 0; i < batch_size; ++i) {
+ for (int j = 0; j < tasks_per_reload; ++j) {
+ task_runner->PostTask(FROM_HERE, DoNothing());
+ num_posted++;
+ }
+ TaskQueue loop_local_queue;
+ queue->ReloadWorkQueue(&loop_local_queue);
+ while (!loop_local_queue.empty()) {
+ PendingTask t = std::move(loop_local_queue.front());
+ loop_local_queue.pop();
+ task_source_observer_raw->RunTask(&t);
+ }
+ }
+
+ now = base::TimeTicks::Now();
+ } while (now - start < kPostTaskPerfTestDuration);
+ std::string trace = StringPrintf("%d_tasks_per_reload", tasks_per_reload);
+ perf_test::PrintResult(
+ "task", "", trace,
+ (now - start).InMicroseconds() / static_cast<double>(num_posted),
+ "us/task", true);
+ }
+};
+
+TEST_F(BasicPostTaskPerfTest, OneTaskPerReload) {
+ Run(10000, 1, std::make_unique<FakeObserver>());
+}
+
+TEST_F(BasicPostTaskPerfTest, TenTasksPerReload) {
+ Run(10000, 10, std::make_unique<FakeObserver>());
+}
+
+TEST_F(BasicPostTaskPerfTest, OneHundredTasksPerReload) {
+ Run(1000, 100, std::make_unique<FakeObserver>());
+}
+
+class StubMessagePump : public MessagePump {
+ public:
+ StubMessagePump() = default;
+ ~StubMessagePump() override = default;
+
+ // MessagePump:
+ void Run(Delegate* delegate) override {}
+ void Quit() override {}
+ void ScheduleWork() override {}
+ void ScheduleDelayedWork(const TimeTicks& delayed_work_time) override {}
+};
+
+// Simulates the overhead of hooking TaskAnnotator and ScheduleWork() to the
+// post task machinery.
+class FakeObserverSimulatingOverhead : public FakeObserver {
+ public:
+ FakeObserverSimulatingOverhead() = default;
+
+ // FakeObserver:
+ void WillQueueTask(PendingTask* task) final {
+ task_annotator_.WillQueueTask("MessageLoop::PostTask", task);
+ }
+
+ void DidQueueTask(bool was_empty) final {
+ AutoLock scoped_lock(message_loop_lock_);
+ pump_->ScheduleWork();
+ }
+
+ void RunTask(PendingTask* task) final {
+ task_annotator_.RunTask("MessageLoop::PostTask", task);
+ }
+
+ private:
+ // Simulates overhead from ScheduleWork() and TaskAnnotator calls involved in
+ // a real PostTask (stores the StubMessagePump in a pointer to force a virtual
+ // dispatch for ScheduleWork() and be closer to reality).
+ Lock message_loop_lock_;
+ std::unique_ptr<MessagePump> pump_{std::make_unique<StubMessagePump>()};
+ debug::TaskAnnotator task_annotator_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeObserverSimulatingOverhead);
+};
+
+TEST_F(BasicPostTaskPerfTest, OneTaskPerReloadWithOverhead) {
+ Run(10000, 1, std::make_unique<FakeObserverSimulatingOverhead>());
+}
+
+TEST_F(BasicPostTaskPerfTest, TenTasksPerReloadWithOverhead) {
+ Run(10000, 10, std::make_unique<FakeObserverSimulatingOverhead>());
+}
+
+TEST_F(BasicPostTaskPerfTest, OneHundredTasksPerReloadWithOverhead) {
+ Run(1000, 100, std::make_unique<FakeObserverSimulatingOverhead>());
+}
+
+// Exercises the full MessageLoop/RunLoop machinery.
+class IntegratedPostTaskPerfTest : public testing::Test {
+ public:
+ void Run(int batch_size, int tasks_per_reload) {
+ base::TimeTicks start = base::TimeTicks::Now();
+ base::TimeTicks now;
+ MessageLoop loop;
+ uint32_t num_posted = 0;
+ do {
+ for (int i = 0; i < batch_size; ++i) {
+ for (int j = 0; j < tasks_per_reload; ++j) {
+ loop->task_runner()->PostTask(FROM_HERE, DoNothing());
+ num_posted++;
+ }
+ RunLoop().RunUntilIdle();
+ }
+
+ now = base::TimeTicks::Now();
+ } while (now - start < kPostTaskPerfTestDuration);
+ std::string trace = StringPrintf("%d_tasks_per_reload", tasks_per_reload);
+ perf_test::PrintResult(
+ "task", "", trace,
+ (now - start).InMicroseconds() / static_cast<double>(num_posted),
+ "us/task", true);
+ }
+};
+
+TEST_F(IntegratedPostTaskPerfTest, OneTaskPerReload) {
+ Run(10000, 1);
+}
+
+TEST_F(IntegratedPostTaskPerfTest, TenTasksPerReload) {
+ Run(10000, 10);
+}
+
+TEST_F(IntegratedPostTaskPerfTest, OneHundredTasksPerReload) {
+ Run(1000, 100);
+}
+
+} // namespace base
diff --git a/base/message_loop/message_loop_task_runner_unittest.cc b/base/message_loop/message_loop_task_runner_unittest.cc
index d403c70700..c7e9aa0596 100644
--- a/base/message_loop/message_loop_task_runner_unittest.cc
+++ b/base/message_loop/message_loop_task_runner_unittest.cc
@@ -38,8 +38,8 @@ class MessageLoopTaskRunnerTest : public testing::Test {
// Allow us to pause the |task_thread_|'s MessageLoop.
task_thread_.task_runner()->PostTask(
- FROM_HERE, Bind(&MessageLoopTaskRunnerTest::BlockTaskThreadHelper,
- Unretained(this)));
+ FROM_HERE, BindOnce(&MessageLoopTaskRunnerTest::BlockTaskThreadHelper,
+ Unretained(this)));
}
void TearDown() override {
@@ -82,14 +82,14 @@ class MessageLoopTaskRunnerTest : public testing::Test {
static void RecordLoopAndQuit(scoped_refptr<LoopRecorder> recorder) {
recorder->RecordRun();
- MessageLoop::current()->QuitWhenIdle();
+ RunLoop::QuitCurrentWhenIdleDeprecated();
}
void UnblockTaskThread() { thread_sync_.Signal(); }
void BlockTaskThreadHelper() { thread_sync_.Wait(); }
- static StaticAtomicSequenceNumber g_order;
+ static AtomicSequenceNumber g_order;
std::unique_ptr<MessageLoop> current_loop_;
Thread task_thread_;
@@ -98,14 +98,14 @@ class MessageLoopTaskRunnerTest : public testing::Test {
base::WaitableEvent thread_sync_;
};
-StaticAtomicSequenceNumber MessageLoopTaskRunnerTest::g_order;
+AtomicSequenceNumber MessageLoopTaskRunnerTest::g_order;
TEST_F(MessageLoopTaskRunnerTest, PostTaskAndReply_Basic) {
- MessageLoop* task_run_on = NULL;
- MessageLoop* task_deleted_on = NULL;
+ MessageLoop* task_run_on = nullptr;
+ MessageLoop* task_deleted_on = nullptr;
int task_delete_order = -1;
- MessageLoop* reply_run_on = NULL;
- MessageLoop* reply_deleted_on = NULL;
+ MessageLoop* reply_run_on = nullptr;
+ MessageLoop* reply_deleted_on = nullptr;
int reply_delete_order = -1;
scoped_refptr<LoopRecorder> task_recorder =
@@ -114,12 +114,12 @@ TEST_F(MessageLoopTaskRunnerTest, PostTaskAndReply_Basic) {
new LoopRecorder(&reply_run_on, &reply_deleted_on, &reply_delete_order);
ASSERT_TRUE(task_thread_.task_runner()->PostTaskAndReply(
- FROM_HERE, Bind(&RecordLoop, task_recorder),
- Bind(&RecordLoopAndQuit, reply_recorder)));
+ FROM_HERE, BindOnce(&RecordLoop, task_recorder),
+ BindOnce(&RecordLoopAndQuit, reply_recorder)));
// Die if base::Bind doesn't retain a reference to the recorders.
- task_recorder = NULL;
- reply_recorder = NULL;
+ task_recorder = nullptr;
+ reply_recorder = nullptr;
ASSERT_FALSE(task_deleted_on);
ASSERT_FALSE(reply_deleted_on);
@@ -134,11 +134,11 @@ TEST_F(MessageLoopTaskRunnerTest, PostTaskAndReply_Basic) {
}
TEST_F(MessageLoopTaskRunnerTest, PostTaskAndReplyOnDeletedThreadDoesNotLeak) {
- MessageLoop* task_run_on = NULL;
- MessageLoop* task_deleted_on = NULL;
+ MessageLoop* task_run_on = nullptr;
+ MessageLoop* task_deleted_on = nullptr;
int task_delete_order = -1;
- MessageLoop* reply_run_on = NULL;
- MessageLoop* reply_deleted_on = NULL;
+ MessageLoop* reply_run_on = nullptr;
+ MessageLoop* reply_deleted_on = nullptr;
int reply_delete_order = -1;
scoped_refptr<LoopRecorder> task_recorder =
@@ -152,9 +152,9 @@ TEST_F(MessageLoopTaskRunnerTest, PostTaskAndReplyOnDeletedThreadDoesNotLeak) {
UnblockTaskThread();
task_thread_.Stop();
- ASSERT_FALSE(
- task_runner->PostTaskAndReply(FROM_HERE, Bind(&RecordLoop, task_recorder),
- Bind(&RecordLoopAndQuit, reply_recorder)));
+ ASSERT_FALSE(task_runner->PostTaskAndReply(
+ FROM_HERE, BindOnce(&RecordLoop, task_recorder),
+ BindOnce(&RecordLoopAndQuit, reply_recorder)));
// The relay should have properly deleted its resources leaving us as the only
// reference.
@@ -168,11 +168,11 @@ TEST_F(MessageLoopTaskRunnerTest, PostTaskAndReplyOnDeletedThreadDoesNotLeak) {
}
TEST_F(MessageLoopTaskRunnerTest, PostTaskAndReply_SameLoop) {
- MessageLoop* task_run_on = NULL;
- MessageLoop* task_deleted_on = NULL;
+ MessageLoop* task_run_on = nullptr;
+ MessageLoop* task_deleted_on = nullptr;
int task_delete_order = -1;
- MessageLoop* reply_run_on = NULL;
- MessageLoop* reply_deleted_on = NULL;
+ MessageLoop* reply_run_on = nullptr;
+ MessageLoop* reply_deleted_on = nullptr;
int reply_delete_order = -1;
scoped_refptr<LoopRecorder> task_recorder =
@@ -182,12 +182,12 @@ TEST_F(MessageLoopTaskRunnerTest, PostTaskAndReply_SameLoop) {
// Enqueue the relay.
ASSERT_TRUE(current_loop_->task_runner()->PostTaskAndReply(
- FROM_HERE, Bind(&RecordLoop, task_recorder),
- Bind(&RecordLoopAndQuit, reply_recorder)));
+ FROM_HERE, BindOnce(&RecordLoop, task_recorder),
+ BindOnce(&RecordLoopAndQuit, reply_recorder)));
// Die if base::Bind doesn't retain a reference to the recorders.
- task_recorder = NULL;
- reply_recorder = NULL;
+ task_recorder = nullptr;
+ reply_recorder = nullptr;
ASSERT_FALSE(task_deleted_on);
ASSERT_FALSE(reply_deleted_on);
@@ -204,11 +204,11 @@ TEST_F(MessageLoopTaskRunnerTest,
PostTaskAndReply_DeadReplyTaskRunnerBehavior) {
// Annotate the scope as having memory leaks to suppress heapchecker reports.
ANNOTATE_SCOPED_MEMORY_LEAK;
- MessageLoop* task_run_on = NULL;
- MessageLoop* task_deleted_on = NULL;
+ MessageLoop* task_run_on = nullptr;
+ MessageLoop* task_deleted_on = nullptr;
int task_delete_order = -1;
- MessageLoop* reply_run_on = NULL;
- MessageLoop* reply_deleted_on = NULL;
+ MessageLoop* reply_run_on = nullptr;
+ MessageLoop* reply_deleted_on = nullptr;
int reply_delete_order = -1;
scoped_refptr<LoopRecorder> task_recorder =
@@ -218,12 +218,12 @@ TEST_F(MessageLoopTaskRunnerTest,
// Enqueue the relay.
task_thread_.task_runner()->PostTaskAndReply(
- FROM_HERE, Bind(&RecordLoop, task_recorder),
- Bind(&RecordLoopAndQuit, reply_recorder));
+ FROM_HERE, BindOnce(&RecordLoop, task_recorder),
+ BindOnce(&RecordLoopAndQuit, reply_recorder));
// Die if base::Bind doesn't retain a reference to the recorders.
- task_recorder = NULL;
- reply_recorder = NULL;
+ task_recorder = nullptr;
+ reply_recorder = nullptr;
ASSERT_FALSE(task_deleted_on);
ASSERT_FALSE(reply_deleted_on);
@@ -262,8 +262,8 @@ class MessageLoopTaskRunnerThreadingTest : public testing::Test {
}
void Quit() const {
- loop_.task_runner()->PostTask(FROM_HERE,
- MessageLoop::QuitWhenIdleClosure());
+ loop_.task_runner()->PostTask(
+ FROM_HERE, RunLoop::QuitCurrentWhenIdleClosureDeprecated());
}
void AssertOnIOThread() const {
@@ -331,8 +331,8 @@ TEST_F(MessageLoopTaskRunnerThreadingTest, Delete) {
TEST_F(MessageLoopTaskRunnerThreadingTest, PostTask) {
EXPECT_TRUE(file_thread_->task_runner()->PostTask(
- FROM_HERE, Bind(&MessageLoopTaskRunnerThreadingTest::BasicFunction,
- Unretained(this))));
+ FROM_HERE, BindOnce(&MessageLoopTaskRunnerThreadingTest::BasicFunction,
+ Unretained(this))));
RunLoop().Run();
}
@@ -345,7 +345,7 @@ TEST_F(MessageLoopTaskRunnerThreadingTest, PostTaskAfterThreadExits) {
test_thread->Stop();
bool ret = task_runner->PostTask(
- FROM_HERE, Bind(&MessageLoopTaskRunnerThreadingTest::AssertNotRun));
+ FROM_HERE, BindOnce(&MessageLoopTaskRunnerThreadingTest::AssertNotRun));
EXPECT_FALSE(ret);
}
@@ -358,7 +358,7 @@ TEST_F(MessageLoopTaskRunnerThreadingTest, PostTaskAfterThreadIsDeleted) {
task_runner = test_thread->task_runner();
}
bool ret = task_runner->PostTask(
- FROM_HERE, Bind(&MessageLoopTaskRunnerThreadingTest::AssertNotRun));
+ FROM_HERE, BindOnce(&MessageLoopTaskRunnerThreadingTest::AssertNotRun));
EXPECT_FALSE(ret);
}
diff --git a/base/message_loop/message_loop_test.cc b/base/message_loop/message_loop_test.cc
deleted file mode 100644
index 6ffb16d05a..0000000000
--- a/base/message_loop/message_loop_test.cc
+++ /dev/null
@@ -1,1041 +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.
-
-#include "base/message_loop/message_loop_test.h"
-
-#include <stddef.h>
-
-#include <utility>
-
-#include "base/bind.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/run_loop.h"
-#include "base/single_thread_task_runner.h"
-#include "base/synchronization/waitable_event.h"
-#include "base/threading/thread.h"
-#include "base/threading/thread_task_runner_handle.h"
-
-namespace base {
-namespace test {
-
-namespace {
-
-class Foo : public RefCounted<Foo> {
- public:
- Foo() : test_count_(0) {
- }
-
- void Test0() {
- ++test_count_;
- }
-
- void Test1ConstRef(const std::string& a) {
- ++test_count_;
- result_.append(a);
- }
-
- void Test1Ptr(std::string* a) {
- ++test_count_;
- result_.append(*a);
- }
-
- void Test1Int(int a) {
- test_count_ += a;
- }
-
- void Test2Ptr(std::string* a, std::string* b) {
- ++test_count_;
- result_.append(*a);
- result_.append(*b);
- }
-
- void Test2Mixed(const std::string& a, std::string* b) {
- ++test_count_;
- result_.append(a);
- result_.append(*b);
- }
-
- int test_count() const { return test_count_; }
- const std::string& result() const { return result_; }
-
- private:
- friend class RefCounted<Foo>;
-
- ~Foo() {}
-
- int test_count_;
- std::string result_;
-
- DISALLOW_COPY_AND_ASSIGN(Foo);
-};
-
-// This function runs slowly to simulate a large amount of work being done.
-void SlowFunc(TimeDelta pause, int* quit_counter) {
- PlatformThread::Sleep(pause);
- if (--(*quit_counter) == 0)
- MessageLoop::current()->QuitWhenIdle();
-}
-
-// This function records the time when Run was called in a Time object, which is
-// useful for building a variety of MessageLoop tests.
-// TODO(sky): remove?
-void RecordRunTimeFunc(Time* run_time, int* quit_counter) {
- *run_time = Time::Now();
-
- // Cause our Run function to take some time to execute. As a result we can
- // count on subsequent RecordRunTimeFunc()s running at a future time,
- // without worry about the resolution of our system clock being an issue.
- SlowFunc(TimeDelta::FromMilliseconds(10), quit_counter);
-}
-
-} // namespace
-
-void RunTest_PostTask(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
- // Add tests to message loop
- scoped_refptr<Foo> foo(new Foo());
- std::string a("a"), b("b"), c("c"), d("d");
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, Bind(&Foo::Test0, foo));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&Foo::Test1ConstRef, foo, a));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&Foo::Test1Ptr, foo, &b));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&Foo::Test1Int, foo, 100));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&Foo::Test2Ptr, foo, &a, &c));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&Foo::Test2Mixed, foo, a, &d));
- // After all tests, post a message that will shut down the message loop
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- Bind(&MessageLoop::QuitWhenIdle, Unretained(MessageLoop::current())));
-
- // Now kick things off
- RunLoop().Run();
-
- EXPECT_EQ(foo->test_count(), 105);
- EXPECT_EQ(foo->result(), "abacad");
-}
-
-void RunTest_PostDelayedTask_Basic(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- // Test that PostDelayedTask results in a delayed task.
-
- const TimeDelta kDelay = TimeDelta::FromMilliseconds(100);
-
- int num_tasks = 1;
- Time run_time;
-
- loop.task_runner()->PostDelayedTask(
- FROM_HERE, Bind(&RecordRunTimeFunc, &run_time, &num_tasks), kDelay);
-
- Time time_before_run = Time::Now();
- RunLoop().Run();
- Time time_after_run = Time::Now();
-
- EXPECT_EQ(0, num_tasks);
- EXPECT_LT(kDelay, time_after_run - time_before_run);
-}
-
-void RunTest_PostDelayedTask_InDelayOrder(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- // Test that two tasks with different delays run in the right order.
- int num_tasks = 2;
- Time run_time1, run_time2;
-
- loop.task_runner()->PostDelayedTask(
- FROM_HERE, Bind(&RecordRunTimeFunc, &run_time1, &num_tasks),
- TimeDelta::FromMilliseconds(200));
- // If we get a large pause in execution (due to a context switch) here, this
- // test could fail.
- loop.task_runner()->PostDelayedTask(
- FROM_HERE, Bind(&RecordRunTimeFunc, &run_time2, &num_tasks),
- TimeDelta::FromMilliseconds(10));
-
- RunLoop().Run();
- EXPECT_EQ(0, num_tasks);
-
- EXPECT_TRUE(run_time2 < run_time1);
-}
-
-void RunTest_PostDelayedTask_InPostOrder(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- // Test that two tasks with the same delay run in the order in which they
- // were posted.
- //
- // NOTE: This is actually an approximate test since the API only takes a
- // "delay" parameter, so we are not exactly simulating two tasks that get
- // posted at the exact same time. It would be nice if the API allowed us to
- // specify the desired run time.
-
- const TimeDelta kDelay = TimeDelta::FromMilliseconds(100);
-
- int num_tasks = 2;
- Time run_time1, run_time2;
-
- loop.task_runner()->PostDelayedTask(
- FROM_HERE, Bind(&RecordRunTimeFunc, &run_time1, &num_tasks), kDelay);
- loop.task_runner()->PostDelayedTask(
- FROM_HERE, Bind(&RecordRunTimeFunc, &run_time2, &num_tasks), kDelay);
-
- RunLoop().Run();
- EXPECT_EQ(0, num_tasks);
-
- EXPECT_TRUE(run_time1 < run_time2);
-}
-
-void RunTest_PostDelayedTask_InPostOrder_2(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- // Test that a delayed task still runs after a normal tasks even if the
- // normal tasks take a long time to run.
-
- const TimeDelta kPause = TimeDelta::FromMilliseconds(50);
-
- int num_tasks = 2;
- Time run_time;
-
- loop.task_runner()->PostTask(FROM_HERE, Bind(&SlowFunc, kPause, &num_tasks));
- loop.task_runner()->PostDelayedTask(
- FROM_HERE, Bind(&RecordRunTimeFunc, &run_time, &num_tasks),
- TimeDelta::FromMilliseconds(10));
-
- Time time_before_run = Time::Now();
- RunLoop().Run();
- Time time_after_run = Time::Now();
-
- EXPECT_EQ(0, num_tasks);
-
- EXPECT_LT(kPause, time_after_run - time_before_run);
-}
-
-void RunTest_PostDelayedTask_InPostOrder_3(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- // Test that a delayed task still runs after a pile of normal tasks. The key
- // difference between this test and the previous one is that here we return
- // the MessageLoop a lot so we give the MessageLoop plenty of opportunities
- // to maybe run the delayed task. It should know not to do so until the
- // delayed task's delay has passed.
-
- int num_tasks = 11;
- Time run_time1, run_time2;
-
- // Clutter the ML with tasks.
- for (int i = 1; i < num_tasks; ++i)
- loop.task_runner()->PostTask(
- FROM_HERE, Bind(&RecordRunTimeFunc, &run_time1, &num_tasks));
-
- loop.task_runner()->PostDelayedTask(
- FROM_HERE, Bind(&RecordRunTimeFunc, &run_time2, &num_tasks),
- TimeDelta::FromMilliseconds(1));
-
- RunLoop().Run();
- EXPECT_EQ(0, num_tasks);
-
- EXPECT_TRUE(run_time2 > run_time1);
-}
-
-void RunTest_PostDelayedTask_SharedTimer(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- // Test that the interval of the timer, used to run the next delayed task, is
- // set to a value corresponding to when the next delayed task should run.
-
- // By setting num_tasks to 1, we ensure that the first task to run causes the
- // run loop to exit.
- int num_tasks = 1;
- Time run_time1, run_time2;
-
- loop.task_runner()->PostDelayedTask(
- FROM_HERE, Bind(&RecordRunTimeFunc, &run_time1, &num_tasks),
- TimeDelta::FromSeconds(1000));
- loop.task_runner()->PostDelayedTask(
- FROM_HERE, Bind(&RecordRunTimeFunc, &run_time2, &num_tasks),
- TimeDelta::FromMilliseconds(10));
-
- Time start_time = Time::Now();
-
- RunLoop().Run();
- EXPECT_EQ(0, num_tasks);
-
- // Ensure that we ran in far less time than the slower timer.
- TimeDelta total_time = Time::Now() - start_time;
- EXPECT_GT(5000, total_time.InMilliseconds());
-
- // In case both timers somehow run at nearly the same time, sleep a little
- // and then run all pending to force them both to have run. This is just
- // encouraging flakiness if there is any.
- PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
- RunLoop().RunUntilIdle();
-
- EXPECT_TRUE(run_time1.is_null());
- EXPECT_FALSE(run_time2.is_null());
-}
-
-// This is used to inject a test point for recording the destructor calls for
-// Closure objects send to MessageLoop::PostTask(). It is awkward usage since we
-// are trying to hook the actual destruction, which is not a common operation.
-class RecordDeletionProbe : public RefCounted<RecordDeletionProbe> {
- public:
- RecordDeletionProbe(RecordDeletionProbe* post_on_delete, bool* was_deleted)
- : post_on_delete_(post_on_delete), was_deleted_(was_deleted) {
- }
- void Run() {}
-
- private:
- friend class RefCounted<RecordDeletionProbe>;
-
- ~RecordDeletionProbe() {
- *was_deleted_ = true;
- if (post_on_delete_.get())
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&RecordDeletionProbe::Run, post_on_delete_));
- }
-
- scoped_refptr<RecordDeletionProbe> post_on_delete_;
- bool* was_deleted_;
-};
-
-void RunTest_EnsureDeletion(MessagePumpFactory factory) {
- bool a_was_deleted = false;
- bool b_was_deleted = false;
- {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
- loop.task_runner()->PostTask(
- FROM_HERE, Bind(&RecordDeletionProbe::Run,
- new RecordDeletionProbe(NULL, &a_was_deleted)));
- // TODO(ajwong): Do we really need 1000ms here?
- loop.task_runner()->PostDelayedTask(
- FROM_HERE, Bind(&RecordDeletionProbe::Run,
- new RecordDeletionProbe(NULL, &b_was_deleted)),
- TimeDelta::FromMilliseconds(1000));
- }
- EXPECT_TRUE(a_was_deleted);
- EXPECT_TRUE(b_was_deleted);
-}
-
-void RunTest_EnsureDeletion_Chain(MessagePumpFactory factory) {
- bool a_was_deleted = false;
- bool b_was_deleted = false;
- bool c_was_deleted = false;
- {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
- // The scoped_refptr for each of the below is held either by the chained
- // RecordDeletionProbe, or the bound RecordDeletionProbe::Run() callback.
- RecordDeletionProbe* a = new RecordDeletionProbe(NULL, &a_was_deleted);
- RecordDeletionProbe* b = new RecordDeletionProbe(a, &b_was_deleted);
- RecordDeletionProbe* c = new RecordDeletionProbe(b, &c_was_deleted);
- loop.task_runner()->PostTask(FROM_HERE, Bind(&RecordDeletionProbe::Run, c));
- }
- EXPECT_TRUE(a_was_deleted);
- EXPECT_TRUE(b_was_deleted);
- EXPECT_TRUE(c_was_deleted);
-}
-
-void NestingFunc(int* depth) {
- if (*depth > 0) {
- *depth -= 1;
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&NestingFunc, depth));
-
- MessageLoop::current()->SetNestableTasksAllowed(true);
- RunLoop().Run();
- }
- MessageLoop::current()->QuitWhenIdle();
-}
-
-void RunTest_Nesting(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- int depth = 100;
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&NestingFunc, &depth));
- RunLoop().Run();
- EXPECT_EQ(depth, 0);
-}
-
-// A NestingObserver that tracks the number of nested message loop starts it
-// has seen.
-class TestNestingObserver : public MessageLoop::NestingObserver {
- public:
- TestNestingObserver() {}
- ~TestNestingObserver() override {}
-
- int begin_nested_loop_count() const { return begin_nested_loop_count_; }
-
- // MessageLoop::NestingObserver:
- void OnBeginNestedMessageLoop() override { begin_nested_loop_count_++; }
-
- private:
- int begin_nested_loop_count_ = 0;
-
- DISALLOW_COPY_AND_ASSIGN(TestNestingObserver);
-};
-
-void ExpectOneBeginNestedLoop(TestNestingObserver* observer) {
- EXPECT_EQ(1, observer->begin_nested_loop_count());
-}
-
-// Starts a nested message loop.
-void RunNestedLoop(TestNestingObserver* observer,
- const Closure& quit_outer_loop) {
- // The nested loop hasn't started yet.
- EXPECT_EQ(0, observer->begin_nested_loop_count());
-
- MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current());
- RunLoop nested_loop;
- // Verify that by the time the first task is run the observer has seen the
- // message loop begin.
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&ExpectOneBeginNestedLoop, observer));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, nested_loop.QuitClosure());
- nested_loop.Run();
-
- // Quitting message loops doesn't change the begin count.
- EXPECT_EQ(1, observer->begin_nested_loop_count());
-
- quit_outer_loop.Run();
-}
-
-// Tests that a NestingObserver is notified when a nested message loop begins.
-void RunTest_NestingObserver(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop outer_loop(std::move(pump));
-
- // Observe the outer loop for nested message loops beginning.
- TestNestingObserver nesting_observer;
- outer_loop.AddNestingObserver(&nesting_observer);
-
- // Post a task that runs a nested message loop.
- outer_loop.task_runner()->PostTask(FROM_HERE,
- Bind(&RunNestedLoop, &nesting_observer,
- outer_loop.QuitWhenIdleClosure()));
- RunLoop().Run();
-
- outer_loop.RemoveNestingObserver(&nesting_observer);
-}
-
-enum TaskType {
- MESSAGEBOX,
- ENDDIALOG,
- RECURSIVE,
- TIMEDMESSAGELOOP,
- QUITMESSAGELOOP,
- ORDERED,
- PUMPS,
- SLEEP,
- RUNS,
-};
-
-struct TaskItem {
- TaskItem(TaskType t, int c, bool s)
- : type(t),
- cookie(c),
- start(s) {
- }
-
- TaskType type;
- int cookie;
- bool start;
-
- bool operator == (const TaskItem& other) const {
- return type == other.type && cookie == other.cookie && start == other.start;
- }
-};
-
-std::ostream& operator <<(std::ostream& os, TaskType type) {
- switch (type) {
- case MESSAGEBOX: os << "MESSAGEBOX"; break;
- case ENDDIALOG: os << "ENDDIALOG"; break;
- case RECURSIVE: os << "RECURSIVE"; break;
- case TIMEDMESSAGELOOP: os << "TIMEDMESSAGELOOP"; break;
- case QUITMESSAGELOOP: os << "QUITMESSAGELOOP"; break;
- case ORDERED: os << "ORDERED"; break;
- case PUMPS: os << "PUMPS"; break;
- case SLEEP: os << "SLEEP"; break;
- default:
- NOTREACHED();
- os << "Unknown TaskType";
- break;
- }
- return os;
-}
-
-std::ostream& operator <<(std::ostream& os, const TaskItem& item) {
- if (item.start)
- return os << item.type << " " << item.cookie << " starts";
- else
- return os << item.type << " " << item.cookie << " ends";
-}
-
-class TaskList {
- public:
- void RecordStart(TaskType type, int cookie) {
- TaskItem item(type, cookie, true);
- DVLOG(1) << item;
- task_list_.push_back(item);
- }
-
- void RecordEnd(TaskType type, int cookie) {
- TaskItem item(type, cookie, false);
- DVLOG(1) << item;
- task_list_.push_back(item);
- }
-
- size_t Size() {
- return task_list_.size();
- }
-
- TaskItem Get(int n) {
- return task_list_[n];
- }
-
- private:
- std::vector<TaskItem> task_list_;
-};
-
-void RecursiveFunc(TaskList* order, int cookie, int depth,
- bool is_reentrant) {
- order->RecordStart(RECURSIVE, cookie);
- if (depth > 0) {
- if (is_reentrant)
- MessageLoop::current()->SetNestableTasksAllowed(true);
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- Bind(&RecursiveFunc, order, cookie, depth - 1, is_reentrant));
- }
- order->RecordEnd(RECURSIVE, cookie);
-}
-
-void QuitFunc(TaskList* order, int cookie) {
- order->RecordStart(QUITMESSAGELOOP, cookie);
- MessageLoop::current()->QuitWhenIdle();
- order->RecordEnd(QUITMESSAGELOOP, cookie);
-}
-void RunTest_RecursiveDenial1(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- EXPECT_TRUE(MessageLoop::current()->NestableTasksAllowed());
- TaskList order;
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&RecursiveFunc, &order, 1, 2, false));
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&RecursiveFunc, &order, 2, 2, false));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&QuitFunc, &order, 3));
-
- RunLoop().Run();
-
- // FIFO order.
- ASSERT_EQ(14U, order.Size());
- EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true));
- EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false));
- EXPECT_EQ(order.Get(2), TaskItem(RECURSIVE, 2, true));
- EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 2, false));
- EXPECT_EQ(order.Get(4), TaskItem(QUITMESSAGELOOP, 3, true));
- EXPECT_EQ(order.Get(5), TaskItem(QUITMESSAGELOOP, 3, false));
- EXPECT_EQ(order.Get(6), TaskItem(RECURSIVE, 1, true));
- EXPECT_EQ(order.Get(7), TaskItem(RECURSIVE, 1, false));
- EXPECT_EQ(order.Get(8), TaskItem(RECURSIVE, 2, true));
- EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 2, false));
- EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, true));
- EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 1, false));
- EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 2, true));
- EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 2, false));
-}
-
-void RecursiveSlowFunc(TaskList* order, int cookie, int depth,
- bool is_reentrant) {
- RecursiveFunc(order, cookie, depth, is_reentrant);
- PlatformThread::Sleep(TimeDelta::FromMilliseconds(10));
-}
-
-void OrderedFunc(TaskList* order, int cookie) {
- order->RecordStart(ORDERED, cookie);
- order->RecordEnd(ORDERED, cookie);
-}
-
-void RunTest_RecursiveDenial3(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- EXPECT_TRUE(MessageLoop::current()->NestableTasksAllowed());
- TaskList order;
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&RecursiveSlowFunc, &order, 1, 2, false));
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&RecursiveSlowFunc, &order, 2, 2, false));
- ThreadTaskRunnerHandle::Get()->PostDelayedTask(
- FROM_HERE, Bind(&OrderedFunc, &order, 3), TimeDelta::FromMilliseconds(5));
- ThreadTaskRunnerHandle::Get()->PostDelayedTask(
- FROM_HERE, Bind(&QuitFunc, &order, 4), TimeDelta::FromMilliseconds(5));
-
- RunLoop().Run();
-
- // FIFO order.
- ASSERT_EQ(16U, order.Size());
- EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true));
- EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false));
- EXPECT_EQ(order.Get(2), TaskItem(RECURSIVE, 2, true));
- EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 2, false));
- EXPECT_EQ(order.Get(4), TaskItem(RECURSIVE, 1, true));
- EXPECT_EQ(order.Get(5), TaskItem(RECURSIVE, 1, false));
- EXPECT_EQ(order.Get(6), TaskItem(ORDERED, 3, true));
- EXPECT_EQ(order.Get(7), TaskItem(ORDERED, 3, false));
- EXPECT_EQ(order.Get(8), TaskItem(RECURSIVE, 2, true));
- EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 2, false));
- EXPECT_EQ(order.Get(10), TaskItem(QUITMESSAGELOOP, 4, true));
- EXPECT_EQ(order.Get(11), TaskItem(QUITMESSAGELOOP, 4, false));
- EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 1, true));
- EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 1, false));
- EXPECT_EQ(order.Get(14), TaskItem(RECURSIVE, 2, true));
- EXPECT_EQ(order.Get(15), TaskItem(RECURSIVE, 2, false));
-}
-
-void RunTest_RecursiveSupport1(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- TaskList order;
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&RecursiveFunc, &order, 1, 2, true));
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&RecursiveFunc, &order, 2, 2, true));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&QuitFunc, &order, 3));
-
- RunLoop().Run();
-
- // FIFO order.
- ASSERT_EQ(14U, order.Size());
- EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true));
- EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false));
- EXPECT_EQ(order.Get(2), TaskItem(RECURSIVE, 2, true));
- EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 2, false));
- EXPECT_EQ(order.Get(4), TaskItem(QUITMESSAGELOOP, 3, true));
- EXPECT_EQ(order.Get(5), TaskItem(QUITMESSAGELOOP, 3, false));
- EXPECT_EQ(order.Get(6), TaskItem(RECURSIVE, 1, true));
- EXPECT_EQ(order.Get(7), TaskItem(RECURSIVE, 1, false));
- EXPECT_EQ(order.Get(8), TaskItem(RECURSIVE, 2, true));
- EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 2, false));
- EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, true));
- EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 1, false));
- EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 2, true));
- EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 2, false));
-}
-
-// Tests that non nestable tasks run in FIFO if there are no nested loops.
-void RunTest_NonNestableWithNoNesting(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- TaskList order;
-
- ThreadTaskRunnerHandle::Get()->PostNonNestableTask(
- FROM_HERE, Bind(&OrderedFunc, &order, 1));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&OrderedFunc, &order, 2));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&QuitFunc, &order, 3));
- RunLoop().Run();
-
- // FIFO order.
- ASSERT_EQ(6U, order.Size());
- EXPECT_EQ(order.Get(0), TaskItem(ORDERED, 1, true));
- EXPECT_EQ(order.Get(1), TaskItem(ORDERED, 1, false));
- EXPECT_EQ(order.Get(2), TaskItem(ORDERED, 2, true));
- EXPECT_EQ(order.Get(3), TaskItem(ORDERED, 2, false));
- EXPECT_EQ(order.Get(4), TaskItem(QUITMESSAGELOOP, 3, true));
- EXPECT_EQ(order.Get(5), TaskItem(QUITMESSAGELOOP, 3, false));
-}
-
-void FuncThatPumps(TaskList* order, int cookie) {
- order->RecordStart(PUMPS, cookie);
- {
- MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current());
- RunLoop().RunUntilIdle();
- }
- order->RecordEnd(PUMPS, cookie);
-}
-
-void SleepFunc(TaskList* order, int cookie, TimeDelta delay) {
- order->RecordStart(SLEEP, cookie);
- PlatformThread::Sleep(delay);
- order->RecordEnd(SLEEP, cookie);
-}
-
-// Tests that non nestable tasks don't run when there's code in the call stack.
-void RunTest_NonNestableInNestedLoop(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- TaskList order;
-
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&FuncThatPumps, &order, 1));
- ThreadTaskRunnerHandle::Get()->PostNonNestableTask(
- FROM_HERE, Bind(&OrderedFunc, &order, 2));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&OrderedFunc, &order, 3));
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&SleepFunc, &order, 4, TimeDelta::FromMilliseconds(50)));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&OrderedFunc, &order, 5));
- ThreadTaskRunnerHandle::Get()->PostNonNestableTask(
- FROM_HERE, Bind(&QuitFunc, &order, 6));
-
- RunLoop().Run();
-
- // FIFO order.
- ASSERT_EQ(12U, order.Size());
- EXPECT_EQ(order.Get(0), TaskItem(PUMPS, 1, true));
- EXPECT_EQ(order.Get(1), TaskItem(ORDERED, 3, true));
- EXPECT_EQ(order.Get(2), TaskItem(ORDERED, 3, false));
- EXPECT_EQ(order.Get(3), TaskItem(SLEEP, 4, true));
- EXPECT_EQ(order.Get(4), TaskItem(SLEEP, 4, false));
- EXPECT_EQ(order.Get(5), TaskItem(ORDERED, 5, true));
- EXPECT_EQ(order.Get(6), TaskItem(ORDERED, 5, false));
- EXPECT_EQ(order.Get(7), TaskItem(PUMPS, 1, false));
- EXPECT_EQ(order.Get(8), TaskItem(ORDERED, 2, true));
- EXPECT_EQ(order.Get(9), TaskItem(ORDERED, 2, false));
- EXPECT_EQ(order.Get(10), TaskItem(QUITMESSAGELOOP, 6, true));
- EXPECT_EQ(order.Get(11), TaskItem(QUITMESSAGELOOP, 6, false));
-}
-
-void FuncThatRuns(TaskList* order, int cookie, RunLoop* run_loop) {
- order->RecordStart(RUNS, cookie);
- {
- MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current());
- run_loop->Run();
- }
- order->RecordEnd(RUNS, cookie);
-}
-
-void FuncThatQuitsNow() {
- MessageLoop::current()->QuitNow();
-}
-// Tests RunLoopQuit only quits the corresponding MessageLoop::Run.
-void RunTest_QuitNow(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- TaskList order;
-
- RunLoop run_loop;
-
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&FuncThatRuns, &order, 1, Unretained(&run_loop)));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&OrderedFunc, &order, 2));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, Bind(&FuncThatQuitsNow));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&OrderedFunc, &order, 3));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, Bind(&FuncThatQuitsNow));
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&OrderedFunc, &order, 4)); // never runs
-
- RunLoop().Run();
-
- ASSERT_EQ(6U, order.Size());
- int task_index = 0;
- EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false));
- EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, false));
- EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
-}
-
-// Tests RunLoopQuit only quits the corresponding MessageLoop::Run.
-void RunTest_RunLoopQuitTop(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- TaskList order;
-
- RunLoop outer_run_loop;
- RunLoop nested_run_loop;
-
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop)));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- outer_run_loop.QuitClosure());
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&OrderedFunc, &order, 2));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- nested_run_loop.QuitClosure());
-
- outer_run_loop.Run();
-
- ASSERT_EQ(4U, order.Size());
- int task_index = 0;
- EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false));
- EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
- EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
-}
-
-// Tests RunLoopQuit only quits the corresponding MessageLoop::Run.
-void RunTest_RunLoopQuitNested(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- TaskList order;
-
- RunLoop outer_run_loop;
- RunLoop nested_run_loop;
-
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop)));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- nested_run_loop.QuitClosure());
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&OrderedFunc, &order, 2));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- outer_run_loop.QuitClosure());
-
- outer_run_loop.Run();
-
- ASSERT_EQ(4U, order.Size());
- int task_index = 0;
- EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false));
- EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
-}
-
-// Tests RunLoopQuit only quits the corresponding MessageLoop::Run.
-void RunTest_RunLoopQuitBogus(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- TaskList order;
-
- RunLoop outer_run_loop;
- RunLoop nested_run_loop;
- RunLoop bogus_run_loop;
-
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop)));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- bogus_run_loop.QuitClosure());
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&OrderedFunc, &order, 2));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- outer_run_loop.QuitClosure());
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- nested_run_loop.QuitClosure());
-
- outer_run_loop.Run();
-
- ASSERT_EQ(4U, order.Size());
- int task_index = 0;
- EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false));
- EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
- EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
-}
-
-// Tests RunLoopQuit only quits the corresponding MessageLoop::Run.
-void RunTest_RunLoopQuitDeep(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- TaskList order;
-
- RunLoop outer_run_loop;
- RunLoop nested_loop1;
- RunLoop nested_loop2;
- RunLoop nested_loop3;
- RunLoop nested_loop4;
-
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&FuncThatRuns, &order, 1, Unretained(&nested_loop1)));
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&FuncThatRuns, &order, 2, Unretained(&nested_loop2)));
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&FuncThatRuns, &order, 3, Unretained(&nested_loop3)));
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&FuncThatRuns, &order, 4, Unretained(&nested_loop4)));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&OrderedFunc, &order, 5));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- outer_run_loop.QuitClosure());
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&OrderedFunc, &order, 6));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- nested_loop1.QuitClosure());
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&OrderedFunc, &order, 7));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- nested_loop2.QuitClosure());
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&OrderedFunc, &order, 8));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- nested_loop3.QuitClosure());
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&OrderedFunc, &order, 9));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- nested_loop4.QuitClosure());
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&OrderedFunc, &order, 10));
-
- outer_run_loop.Run();
-
- ASSERT_EQ(18U, order.Size());
- int task_index = 0;
- EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 2, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 3, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 4, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 5, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 5, false));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 6, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 6, false));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 7, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 7, false));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 8, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 8, false));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 9, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 9, false));
- EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 4, false));
- EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 3, false));
- EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 2, false));
- EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
- EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
-}
-
-// Tests RunLoopQuit works before RunWithID.
-void RunTest_RunLoopQuitOrderBefore(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- TaskList order;
-
- RunLoop run_loop;
-
- run_loop.Quit();
-
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&OrderedFunc, &order, 1)); // never runs
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&FuncThatQuitsNow)); // never runs
-
- run_loop.Run();
-
- ASSERT_EQ(0U, order.Size());
-}
-
-// Tests RunLoopQuit works during RunWithID.
-void RunTest_RunLoopQuitOrderDuring(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- TaskList order;
-
- RunLoop run_loop;
-
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&OrderedFunc, &order, 1));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, run_loop.QuitClosure());
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&OrderedFunc, &order, 2)); // never runs
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&FuncThatQuitsNow)); // never runs
-
- run_loop.Run();
-
- ASSERT_EQ(2U, order.Size());
- int task_index = 0;
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 1, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 1, false));
- EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
-}
-
-// Tests RunLoopQuit works after RunWithID.
-void RunTest_RunLoopQuitOrderAfter(MessagePumpFactory factory) {
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
-
- TaskList order;
-
- RunLoop run_loop;
-
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&FuncThatRuns, &order, 1, Unretained(&run_loop)));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&OrderedFunc, &order, 2));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, Bind(&FuncThatQuitsNow));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&OrderedFunc, &order, 3));
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, run_loop.QuitClosure()); // has no affect
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&OrderedFunc, &order, 4));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, Bind(&FuncThatQuitsNow));
-
- RunLoop outer_run_loop;
- outer_run_loop.Run();
-
- ASSERT_EQ(8U, order.Size());
- int task_index = 0;
- EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false));
- EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, false));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 4, true));
- EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 4, false));
- EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
-}
-
-void PostNTasksThenQuit(int posts_remaining) {
- if (posts_remaining > 1) {
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&PostNTasksThenQuit, posts_remaining - 1));
- } else {
- MessageLoop::current()->QuitWhenIdle();
- }
-}
-
-// There was a bug in the MessagePumpGLib where posting tasks recursively
-// caused the message loop to hang, due to the buffer of the internal pipe
-// becoming full. Test all MessageLoop types to ensure this issue does not
-// exist in other MessagePumps.
-//
-// On Linux, the pipe buffer size is 64KiB by default. The bug caused one
-// byte accumulated in the pipe per two posts, so we should repeat 128K
-// times to reproduce the bug.
-void RunTest_RecursivePosts(MessagePumpFactory factory) {
- const int kNumTimes = 1 << 17;
- std::unique_ptr<MessagePump> pump(factory());
- MessageLoop loop(std::move(pump));
- loop.task_runner()->PostTask(FROM_HERE, Bind(&PostNTasksThenQuit, kNumTimes));
- RunLoop().Run();
-}
-
-} // namespace test
-} // namespace base
diff --git a/base/message_loop/message_loop_test.h b/base/message_loop/message_loop_test.h
deleted file mode 100644
index b7ae28ea18..0000000000
--- a/base/message_loop/message_loop_test.h
+++ /dev/null
@@ -1,130 +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_MESSAGE_LOOP_MESSAGE_LOOP_TEST_H_
-#define BASE_MESSAGE_LOOP_MESSAGE_LOOP_TEST_H_
-
-#include "base/message_loop/message_loop.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-// This file consists of tests meant to exercise the combination of MessageLoop
-// and MessagePump. To use these define the macro RUN_MESSAGE_LOOP_TESTS using
-// an ID appropriate for your MessagePump, eg
-// RUN_MESSAGE_LOOP_TESTS(UI, factory). Factory is a function called to create
-// the MessagePump.
-namespace base {
-namespace test {
-
-typedef MessageLoop::MessagePumpFactory MessagePumpFactory;
-
-void RunTest_PostTask(MessagePumpFactory factory);
-void RunTest_PostDelayedTask_Basic(MessagePumpFactory factory);
-void RunTest_PostDelayedTask_InDelayOrder(MessagePumpFactory factory);
-void RunTest_PostDelayedTask_InPostOrder(MessagePumpFactory factory);
-void RunTest_PostDelayedTask_InPostOrder_2(MessagePumpFactory factory);
-void RunTest_PostDelayedTask_InPostOrder_3(MessagePumpFactory factory);
-void RunTest_PostDelayedTask_SharedTimer(MessagePumpFactory factory);
-void RunTest_EnsureDeletion(MessagePumpFactory factory);
-void RunTest_EnsureDeletion_Chain(MessagePumpFactory factory);
-void RunTest_Nesting(MessagePumpFactory factory);
-void RunTest_NestingObserver(MessagePumpFactory factory);
-void RunTest_RecursiveDenial1(MessagePumpFactory factory);
-void RunTest_RecursiveDenial3(MessagePumpFactory factory);
-void RunTest_RecursiveSupport1(MessagePumpFactory factory);
-void RunTest_NonNestableWithNoNesting(MessagePumpFactory factory);
-void RunTest_NonNestableInNestedLoop(MessagePumpFactory factory);
-void RunTest_QuitNow(MessagePumpFactory factory);
-void RunTest_RunLoopQuitTop(MessagePumpFactory factory);
-void RunTest_RunLoopQuitNested(MessagePumpFactory factory);
-void RunTest_RunLoopQuitBogus(MessagePumpFactory factory);
-void RunTest_RunLoopQuitDeep(MessagePumpFactory factory);
-void RunTest_RunLoopQuitOrderBefore(MessagePumpFactory factory);
-void RunTest_RunLoopQuitOrderDuring(MessagePumpFactory factory);
-void RunTest_RunLoopQuitOrderAfter(MessagePumpFactory factory);
-void RunTest_RecursivePosts(MessagePumpFactory factory);
-
-} // namespace test
-} // namespace base
-
-#define RUN_MESSAGE_LOOP_TESTS(id, factory) \
- TEST(MessageLoopTestType##id, PostTask) { \
- base::test::RunTest_PostTask(factory); \
- } \
- TEST(MessageLoopTestType##id, PostDelayedTask_Basic) { \
- base::test::RunTest_PostDelayedTask_Basic(factory); \
- } \
- TEST(MessageLoopTestType##id, PostDelayedTask_InDelayOrder) { \
- base::test::RunTest_PostDelayedTask_InDelayOrder(factory); \
- } \
- TEST(MessageLoopTestType##id, PostDelayedTask_InPostOrder) { \
- base::test::RunTest_PostDelayedTask_InPostOrder(factory); \
- } \
- TEST(MessageLoopTestType##id, PostDelayedTask_InPostOrder_2) { \
- base::test::RunTest_PostDelayedTask_InPostOrder_2(factory); \
- } \
- TEST(MessageLoopTestType##id, PostDelayedTask_InPostOrder_3) { \
- base::test::RunTest_PostDelayedTask_InPostOrder_3(factory); \
- } \
- TEST(MessageLoopTestType##id, PostDelayedTask_SharedTimer) { \
- base::test::RunTest_PostDelayedTask_SharedTimer(factory); \
- } \
- /* TODO(darin): MessageLoop does not support deleting all tasks in the */ \
- /* destructor. */ \
- /* Fails, http://crbug.com/50272. */ \
- TEST(MessageLoopTestType##id, DISABLED_EnsureDeletion) { \
- base::test::RunTest_EnsureDeletion(factory); \
- } \
- /* TODO(darin): MessageLoop does not support deleting all tasks in the */ \
- /* destructor. */ \
- /* Fails, http://crbug.com/50272. */ \
- TEST(MessageLoopTestType##id, DISABLED_EnsureDeletion_Chain) { \
- base::test::RunTest_EnsureDeletion_Chain(factory); \
- } \
- TEST(MessageLoopTestType##id, Nesting) { \
- base::test::RunTest_Nesting(factory); \
- } \
- TEST(MessageLoopTestType##id, RecursiveDenial1) { \
- base::test::RunTest_RecursiveDenial1(factory); \
- } \
- TEST(MessageLoopTestType##id, RecursiveDenial3) { \
- base::test::RunTest_RecursiveDenial3(factory); \
- } \
- TEST(MessageLoopTestType##id, RecursiveSupport1) { \
- base::test::RunTest_RecursiveSupport1(factory); \
- } \
- TEST(MessageLoopTestType##id, NonNestableWithNoNesting) { \
- base::test::RunTest_NonNestableWithNoNesting(factory); \
- } \
- TEST(MessageLoopTestType##id, NonNestableDelayedInNestedLoop) { \
- base::test::RunTest_NonNestableInNestedLoop(factory); \
- } \
- TEST(MessageLoopTestType##id, QuitNow) { \
- base::test::RunTest_QuitNow(factory); \
- } \
- TEST(MessageLoopTestType##id, RunLoopQuitTop) { \
- base::test::RunTest_RunLoopQuitTop(factory); \
- } \
- TEST(MessageLoopTestType##id, RunLoopQuitNested) { \
- base::test::RunTest_RunLoopQuitNested(factory); \
- } \
- TEST(MessageLoopTestType##id, RunLoopQuitBogus) { \
- base::test::RunTest_RunLoopQuitBogus(factory); \
- } \
- TEST(MessageLoopTestType##id, RunLoopQuitDeep) { \
- base::test::RunTest_RunLoopQuitDeep(factory); \
- } \
- TEST(MessageLoopTestType##id, RunLoopQuitOrderBefore) { \
- base::test::RunTest_RunLoopQuitOrderBefore(factory); \
- } \
- TEST(MessageLoopTestType##id, RunLoopQuitOrderDuring) { \
- base::test::RunTest_RunLoopQuitOrderDuring(factory); \
- } \
- TEST(MessageLoopTestType##id, RunLoopQuitOrderAfter) { \
- base::test::RunTest_RunLoopQuitOrderAfter(factory); \
- } \
- TEST(MessageLoopTestType##id, RecursivePosts) { \
- base::test::RunTest_RecursivePosts(factory); \
- } \
-
-#endif // BASE_MESSAGE_LOOP_MESSAGE_LOOP_TEST_H_
diff --git a/base/message_loop/message_loop_unittest.cc b/base/message_loop/message_loop_unittest.cc
index 9d771d5ecb..774347939a 100644
--- a/base/message_loop/message_loop_unittest.cc
+++ b/base/message_loop/message_loop_unittest.cc
@@ -15,22 +15,29 @@
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
-#include "base/message_loop/message_loop_test.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/message_loop/message_pump_for_io.h"
#include "base/pending_task.h"
#include "base/posix/eintr_wrapper.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/waitable_event.h"
+// Unsupported in libchrome
+// #include "base/task_scheduler/task_scheduler.h"
+#include "base/test/gtest_util.h"
#include "base/test/test_simple_task_runner.h"
+#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
+#include "base/threading/sequence_local_storage_slot.h"
#include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_ANDROID)
+#include "base/android/java_handler_thread.h"
#include "base/android/jni_android.h"
-#include "base/test/android/java_handler_thread_for_testing.h"
+#include "base/test/android/java_handler_thread_helpers.h"
#endif
#if defined(OS_WIN)
@@ -48,171 +55,69 @@ namespace base {
namespace {
-std::unique_ptr<MessagePump> TypeDefaultMessagePumpFactory() {
- return MessageLoop::CreateMessagePumpForType(MessageLoop::TYPE_DEFAULT);
-}
-
-std::unique_ptr<MessagePump> TypeIOMessagePumpFactory() {
- return MessageLoop::CreateMessagePumpForType(MessageLoop::TYPE_IO);
-}
-
-std::unique_ptr<MessagePump> TypeUIMessagePumpFactory() {
- return MessageLoop::CreateMessagePumpForType(MessageLoop::TYPE_UI);
-}
-
class Foo : public RefCounted<Foo> {
public:
Foo() : test_count_(0) {
}
+ void Test0() { ++test_count_; }
+
void Test1ConstRef(const std::string& a) {
++test_count_;
result_.append(a);
}
+ void Test1Ptr(std::string* a) {
+ ++test_count_;
+ result_.append(*a);
+ }
+
+ void Test1Int(int a) { test_count_ += a; }
+
+ void Test2Ptr(std::string* a, std::string* b) {
+ ++test_count_;
+ result_.append(*a);
+ result_.append(*b);
+ }
+
+ void Test2Mixed(const std::string& a, std::string* b) {
+ ++test_count_;
+ result_.append(a);
+ result_.append(*b);
+ }
+
int test_count() const { return test_count_; }
const std::string& result() const { return result_; }
private:
friend class RefCounted<Foo>;
- ~Foo() {}
+ ~Foo() = default;
int test_count_;
std::string result_;
-};
-#if defined(OS_ANDROID)
-void AbortMessagePump() {
- JNIEnv* env = base::android::AttachCurrentThread();
- jclass exception = env->FindClass(
- "org/chromium/base/TestSystemMessageHandler$TestException");
-
- env->ThrowNew(exception,
- "This is a test exception that should be caught in "
- "TestSystemMessageHandler.handleMessage");
- static_cast<base::MessageLoopForUI*>(base::MessageLoop::current())->Abort();
-}
-
-void RunTest_AbortDontRunMoreTasks(bool delayed, bool init_java_first) {
- WaitableEvent test_done_event(WaitableEvent::ResetPolicy::MANUAL,
- WaitableEvent::InitialState::NOT_SIGNALED);
-
- std::unique_ptr<android::JavaHandlerThread> java_thread;
- if (init_java_first) {
- java_thread =
- android::JavaHandlerThreadForTesting::CreateJavaFirst(&test_done_event);
- } else {
- java_thread = android::JavaHandlerThreadForTesting::Create(
- "JavaHandlerThreadForTesting from AbortDontRunMoreTasks",
- &test_done_event);
- }
- java_thread->Start();
-
- if (delayed) {
- java_thread->message_loop()->task_runner()->PostDelayedTask(
- FROM_HERE, Bind(&AbortMessagePump), TimeDelta::FromMilliseconds(10));
- } else {
- java_thread->message_loop()->task_runner()->PostTask(
- FROM_HERE, Bind(&AbortMessagePump));
- }
-
- // Wait to ensure we catch the correct exception (and don't crash)
- test_done_event.Wait();
-
- java_thread->Stop();
- java_thread.reset();
-}
-
-TEST(MessageLoopTest, JavaExceptionAbort) {
- constexpr bool delayed = false;
- constexpr bool init_java_first = false;
- RunTest_AbortDontRunMoreTasks(delayed, init_java_first);
-}
-TEST(MessageLoopTest, DelayedJavaExceptionAbort) {
- constexpr bool delayed = true;
- constexpr bool init_java_first = false;
- RunTest_AbortDontRunMoreTasks(delayed, init_java_first);
-}
-TEST(MessageLoopTest, JavaExceptionAbortInitJavaFirst) {
- constexpr bool delayed = false;
- constexpr bool init_java_first = true;
- RunTest_AbortDontRunMoreTasks(delayed, init_java_first);
-}
-#endif // defined(OS_ANDROID)
-
-#if defined(OS_WIN)
+ DISALLOW_COPY_AND_ASSIGN(Foo);
+};
// This function runs slowly to simulate a large amount of work being done.
static void SlowFunc(TimeDelta pause, int* quit_counter) {
- PlatformThread::Sleep(pause);
- if (--(*quit_counter) == 0)
- MessageLoop::current()->QuitWhenIdle();
+ PlatformThread::Sleep(pause);
+ if (--(*quit_counter) == 0)
+ RunLoop::QuitCurrentWhenIdleDeprecated();
}
// This function records the time when Run was called in a Time object, which is
// useful for building a variety of MessageLoop tests.
-static void RecordRunTimeFunc(Time* run_time, int* quit_counter) {
- *run_time = Time::Now();
+static void RecordRunTimeFunc(TimeTicks* run_time, int* quit_counter) {
+ *run_time = TimeTicks::Now();
- // Cause our Run function to take some time to execute. As a result we can
- // count on subsequent RecordRunTimeFunc()s running at a future time,
- // without worry about the resolution of our system clock being an issue.
+ // Cause our Run function to take some time to execute. As a result we can
+ // count on subsequent RecordRunTimeFunc()s running at a future time,
+ // without worry about the resolution of our system clock being an issue.
SlowFunc(TimeDelta::FromMilliseconds(10), quit_counter);
}
-void SubPumpFunc() {
- MessageLoop::current()->SetNestableTasksAllowed(true);
- MSG msg;
- while (GetMessage(&msg, NULL, 0, 0)) {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- MessageLoop::current()->QuitWhenIdle();
-}
-
-void RunTest_PostDelayedTask_SharedTimer_SubPump() {
- MessageLoop message_loop(MessageLoop::TYPE_UI);
-
- // Test that the interval of the timer, used to run the next delayed task, is
- // set to a value corresponding to when the next delayed task should run.
-
- // By setting num_tasks to 1, we ensure that the first task to run causes the
- // run loop to exit.
- int num_tasks = 1;
- Time run_time;
-
- message_loop.task_runner()->PostTask(FROM_HERE, Bind(&SubPumpFunc));
-
- // This very delayed task should never run.
- message_loop.task_runner()->PostDelayedTask(
- FROM_HERE, Bind(&RecordRunTimeFunc, &run_time, &num_tasks),
- TimeDelta::FromSeconds(1000));
-
- // This slightly delayed task should run from within SubPumpFunc.
- message_loop.task_runner()->PostDelayedTask(
- FROM_HERE, Bind(&PostQuitMessage, 0), TimeDelta::FromMilliseconds(10));
-
- Time start_time = Time::Now();
-
- RunLoop().Run();
- EXPECT_EQ(1, num_tasks);
-
- // Ensure that we ran in far less time than the slower timer.
- TimeDelta total_time = Time::Now() - start_time;
- EXPECT_GT(5000, total_time.InMilliseconds());
-
- // In case both timers somehow run at nearly the same time, sleep a little
- // and then run all pending to force them both to have run. This is just
- // encouraging flakiness if there is any.
- PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
- RunLoop().RunUntilIdle();
-
- EXPECT_TRUE(run_time.is_null());
-}
-
-const wchar_t kMessageBoxTitle[] = L"MessageLoop Unit Test";
-
enum TaskType {
MESSAGEBOX,
ENDDIALOG,
@@ -293,13 +198,268 @@ class TaskList {
std::vector<TaskItem> task_list_;
};
+class DummyTaskObserver : public MessageLoop::TaskObserver {
+ public:
+ explicit DummyTaskObserver(int num_tasks)
+ : num_tasks_started_(0), num_tasks_processed_(0), num_tasks_(num_tasks) {}
+
+ DummyTaskObserver(int num_tasks, int num_tasks_started)
+ : num_tasks_started_(num_tasks_started),
+ num_tasks_processed_(0),
+ num_tasks_(num_tasks) {}
+
+ ~DummyTaskObserver() override = default;
+
+ void WillProcessTask(const PendingTask& pending_task) override {
+ num_tasks_started_++;
+ EXPECT_LE(num_tasks_started_, num_tasks_);
+ EXPECT_EQ(num_tasks_started_, num_tasks_processed_ + 1);
+ }
+
+ void DidProcessTask(const PendingTask& pending_task) override {
+ num_tasks_processed_++;
+ EXPECT_LE(num_tasks_started_, num_tasks_);
+ EXPECT_EQ(num_tasks_started_, num_tasks_processed_);
+ }
+
+ int num_tasks_started() const { return num_tasks_started_; }
+ int num_tasks_processed() const { return num_tasks_processed_; }
+
+ private:
+ int num_tasks_started_;
+ int num_tasks_processed_;
+ const int num_tasks_;
+
+ DISALLOW_COPY_AND_ASSIGN(DummyTaskObserver);
+};
+
+void RecursiveFunc(TaskList* order, int cookie, int depth,
+ bool is_reentrant) {
+ order->RecordStart(RECURSIVE, cookie);
+ if (depth > 0) {
+ if (is_reentrant)
+ MessageLoopCurrent::Get()->SetNestableTasksAllowed(true);
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ BindOnce(&RecursiveFunc, order, cookie, depth - 1, is_reentrant));
+ }
+ order->RecordEnd(RECURSIVE, cookie);
+}
+
+void QuitFunc(TaskList* order, int cookie) {
+ order->RecordStart(QUITMESSAGELOOP, cookie);
+ RunLoop::QuitCurrentWhenIdleDeprecated();
+ order->RecordEnd(QUITMESSAGELOOP, cookie);
+}
+
+void PostNTasks(int posts_remaining) {
+ if (posts_remaining > 1) {
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&PostNTasks, posts_remaining - 1));
+ }
+}
+
+enum class TaskSchedulerAvailability {
+ NO_TASK_SCHEDULER,
+ // Unsupported in libchrome.
+ // WITH_TASK_SCHEDULER,
+};
+
+std::string TaskSchedulerAvailabilityToString(
+ TaskSchedulerAvailability availability) {
+ switch (availability) {
+ case TaskSchedulerAvailability::NO_TASK_SCHEDULER:
+ return "NoTaskScheduler";
+ // Unsupported in libchrome.
+ // case TaskSchedulerAvailability::WITH_TASK_SCHEDULER:
+ // return "WithTaskScheduler";
+ }
+ NOTREACHED();
+ return "Unknown";
+}
+
+class MessageLoopTest
+ : public ::testing::TestWithParam<TaskSchedulerAvailability> {
+ public:
+ MessageLoopTest() = default;
+ ~MessageLoopTest() override = default;
+
+ void SetUp() override {
+ // Unsupported in libchrome.
+#if 0
+ if (GetParam() == TaskSchedulerAvailability::WITH_TASK_SCHEDULER)
+ TaskScheduler::CreateAndStartWithDefaultParams("MessageLoopTest");
+#endif
+ }
+
+ void TearDown() override {
+ // Unsupported in libchrome.
+#if 0
+ if (GetParam() == TaskSchedulerAvailability::WITH_TASK_SCHEDULER) {
+ // Failure to call FlushForTesting() could result in task leaks as tasks
+ // are skipped on shutdown.
+ base::TaskScheduler::GetInstance()->FlushForTesting();
+ base::TaskScheduler::GetInstance()->Shutdown();
+ base::TaskScheduler::GetInstance()->JoinForTesting();
+ base::TaskScheduler::SetInstance(nullptr);
+ }
+#endif
+ }
+
+ static std::string ParamInfoToString(
+ ::testing::TestParamInfo<TaskSchedulerAvailability> param_info) {
+ return TaskSchedulerAvailabilityToString(param_info.param);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MessageLoopTest);
+};
+
+#if defined(OS_ANDROID)
+void DoNotRun() {
+ ASSERT_TRUE(false);
+}
+
+void RunTest_AbortDontRunMoreTasks(bool delayed, bool init_java_first) {
+ WaitableEvent test_done_event(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ std::unique_ptr<android::JavaHandlerThread> java_thread;
+ if (init_java_first) {
+ java_thread = android::JavaHandlerThreadHelpers::CreateJavaFirst();
+ } else {
+ java_thread = std::make_unique<android::JavaHandlerThread>(
+ "JavaHandlerThreadForTesting from AbortDontRunMoreTasks");
+ }
+ java_thread->Start();
+ java_thread->ListenForUncaughtExceptionsForTesting();
+
+ auto target =
+ BindOnce(&android::JavaHandlerThreadHelpers::ThrowExceptionAndAbort,
+ &test_done_event);
+ if (delayed) {
+ java_thread->message_loop()->task_runner()->PostDelayedTask(
+ FROM_HERE, std::move(target), TimeDelta::FromMilliseconds(10));
+ } else {
+ java_thread->message_loop()->task_runner()->PostTask(FROM_HERE,
+ std::move(target));
+ java_thread->message_loop()->task_runner()->PostTask(FROM_HERE,
+ BindOnce(&DoNotRun));
+ }
+ test_done_event.Wait();
+ java_thread->Stop();
+ android::ScopedJavaLocalRef<jthrowable> exception =
+ java_thread->GetUncaughtExceptionIfAny();
+ ASSERT_TRUE(
+ android::JavaHandlerThreadHelpers::IsExceptionTestException(exception));
+}
+
+TEST_P(MessageLoopTest, JavaExceptionAbort) {
+ constexpr bool delayed = false;
+ constexpr bool init_java_first = false;
+ RunTest_AbortDontRunMoreTasks(delayed, init_java_first);
+}
+TEST_P(MessageLoopTest, DelayedJavaExceptionAbort) {
+ constexpr bool delayed = true;
+ constexpr bool init_java_first = false;
+ RunTest_AbortDontRunMoreTasks(delayed, init_java_first);
+}
+TEST_P(MessageLoopTest, JavaExceptionAbortInitJavaFirst) {
+ constexpr bool delayed = false;
+ constexpr bool init_java_first = true;
+ RunTest_AbortDontRunMoreTasks(delayed, init_java_first);
+}
+
+TEST_P(MessageLoopTest, RunTasksWhileShuttingDownJavaThread) {
+ const int kNumPosts = 6;
+ DummyTaskObserver observer(kNumPosts, 1);
+
+ auto java_thread = std::make_unique<android::JavaHandlerThread>("test");
+ java_thread->Start();
+
+ java_thread->message_loop()->task_runner()->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](android::JavaHandlerThread* java_thread,
+ DummyTaskObserver* observer, int num_posts) {
+ java_thread->message_loop()->AddTaskObserver(observer);
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, BindOnce([]() { ADD_FAILURE(); }),
+ TimeDelta::FromDays(1));
+ java_thread->StopMessageLoopForTesting();
+ PostNTasks(num_posts);
+ },
+ Unretained(java_thread.get()), Unretained(&observer), kNumPosts));
+
+ java_thread->JoinForTesting();
+ java_thread.reset();
+
+ EXPECT_EQ(kNumPosts, observer.num_tasks_started());
+ EXPECT_EQ(kNumPosts, observer.num_tasks_processed());
+}
+#endif // defined(OS_ANDROID)
+
+#if defined(OS_WIN)
+
+void SubPumpFunc() {
+ MessageLoopCurrent::Get()->SetNestableTasksAllowed(true);
+ MSG msg;
+ while (GetMessage(&msg, NULL, 0, 0)) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ RunLoop::QuitCurrentWhenIdleDeprecated();
+}
+
+void RunTest_PostDelayedTask_SharedTimer_SubPump() {
+ MessageLoop message_loop(MessageLoop::TYPE_UI);
+
+ // Test that the interval of the timer, used to run the next delayed task, is
+ // set to a value corresponding to when the next delayed task should run.
+
+ // By setting num_tasks to 1, we ensure that the first task to run causes the
+ // run loop to exit.
+ int num_tasks = 1;
+ TimeTicks run_time;
+
+ message_loop.task_runner()->PostTask(FROM_HERE, BindOnce(&SubPumpFunc));
+
+ // This very delayed task should never run.
+ message_loop.task_runner()->PostDelayedTask(
+ FROM_HERE, BindOnce(&RecordRunTimeFunc, &run_time, &num_tasks),
+ TimeDelta::FromSeconds(1000));
+
+ // This slightly delayed task should run from within SubPumpFunc.
+ message_loop.task_runner()->PostDelayedTask(FROM_HERE,
+ BindOnce(&PostQuitMessage, 0),
+ TimeDelta::FromMilliseconds(10));
+
+ Time start_time = Time::Now();
+
+ RunLoop().Run();
+ EXPECT_EQ(1, num_tasks);
+
+ // Ensure that we ran in far less time than the slower timer.
+ TimeDelta total_time = Time::Now() - start_time;
+ EXPECT_GT(5000, total_time.InMilliseconds());
+
+ // In case both timers somehow run at nearly the same time, sleep a little
+ // and then run all pending to force them both to have run. This is just
+ // encouraging flakiness if there is any.
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
+ RunLoop().RunUntilIdle();
+
+ EXPECT_TRUE(run_time.is_null());
+}
+
+const wchar_t kMessageBoxTitle[] = L"MessageLoop Unit Test";
+
// MessageLoop implicitly start a "modal message loop". Modal dialog boxes,
// common controls (like OpenFile) and StartDoc printing function can cause
// implicit message loops.
void MessageBoxFunc(TaskList* order, int cookie, bool is_reentrant) {
order->RecordStart(MESSAGEBOX, cookie);
if (is_reentrant)
- MessageLoop::current()->SetNestableTasksAllowed(true);
+ MessageLoopCurrent::Get()->SetNestableTasksAllowed(true);
MessageBox(NULL, L"Please wait...", kMessageBoxTitle, MB_OK);
order->RecordEnd(MESSAGEBOX, cookie);
}
@@ -316,44 +476,25 @@ void EndDialogFunc(TaskList* order, int cookie) {
}
}
-void RecursiveFunc(TaskList* order, int cookie, int depth,
- bool is_reentrant) {
- order->RecordStart(RECURSIVE, cookie);
- if (depth > 0) {
- if (is_reentrant)
- MessageLoop::current()->SetNestableTasksAllowed(true);
- ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- Bind(&RecursiveFunc, order, cookie, depth - 1, is_reentrant));
- }
- order->RecordEnd(RECURSIVE, cookie);
-}
-
-void QuitFunc(TaskList* order, int cookie) {
- order->RecordStart(QUITMESSAGELOOP, cookie);
- MessageLoop::current()->QuitWhenIdle();
- order->RecordEnd(QUITMESSAGELOOP, cookie);
-}
-
void RecursiveFuncWin(scoped_refptr<SingleThreadTaskRunner> task_runner,
HANDLE event,
bool expect_window,
TaskList* order,
bool is_reentrant) {
task_runner->PostTask(FROM_HERE,
- Bind(&RecursiveFunc, order, 1, 2, is_reentrant));
+ BindOnce(&RecursiveFunc, order, 1, 2, is_reentrant));
task_runner->PostTask(FROM_HERE,
- Bind(&MessageBoxFunc, order, 2, is_reentrant));
+ BindOnce(&MessageBoxFunc, order, 2, is_reentrant));
task_runner->PostTask(FROM_HERE,
- Bind(&RecursiveFunc, order, 3, 2, is_reentrant));
+ BindOnce(&RecursiveFunc, order, 3, 2, is_reentrant));
// The trick here is that for recursive task processing, this task will be
// ran _inside_ the MessageBox message loop, dismissing the MessageBox
// without a chance.
// For non-recursive task processing, this will be executed _after_ the
// MessageBox will have been dismissed by the code below, where
// expect_window_ is true.
- task_runner->PostTask(FROM_HERE, Bind(&EndDialogFunc, order, 4));
- task_runner->PostTask(FROM_HERE, Bind(&QuitFunc, order, 5));
+ task_runner->PostTask(FROM_HERE, BindOnce(&EndDialogFunc, order, 4));
+ task_runner->PostTask(FROM_HERE, BindOnce(&QuitFunc, order, 5));
// Enforce that every tasks are sent before starting to run the main thread
// message loop.
@@ -392,8 +533,8 @@ void RunTest_RecursiveDenial2(MessageLoop::Type message_loop_type) {
TaskList order;
win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL));
worker.task_runner()->PostTask(
- FROM_HERE, Bind(&RecursiveFuncWin, ThreadTaskRunnerHandle::Get(),
- event.Get(), true, &order, false));
+ FROM_HERE, BindOnce(&RecursiveFuncWin, ThreadTaskRunnerHandle::Get(),
+ event.Get(), true, &order, false));
// Let the other thread execute.
WaitForSingleObject(event.Get(), INFINITE);
RunLoop().Run();
@@ -432,8 +573,8 @@ void RunTest_RecursiveSupport2(MessageLoop::Type message_loop_type) {
TaskList order;
win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, NULL));
worker.task_runner()->PostTask(
- FROM_HERE, Bind(&RecursiveFuncWin, ThreadTaskRunnerHandle::Get(),
- event.Get(), false, &order, true));
+ FROM_HERE, BindOnce(&RecursiveFuncWin, ThreadTaskRunnerHandle::Get(),
+ event.Get(), false, &order, true));
// Let the other thread execute.
WaitForSingleObject(event.Get(), INFINITE);
RunLoop().Run();
@@ -450,7 +591,7 @@ void RunTest_RecursiveSupport2(MessageLoop::Type message_loop_type) {
EXPECT_EQ(order.Get(7), TaskItem(MESSAGEBOX, 2, false));
/* The order can subtly change here. The reason is that when RecursiveFunc(1)
is called in the main thread, if it is faster than getting to the
- PostTask(FROM_HERE, Bind(&QuitFunc) execution, the order of task
+ PostTask(FROM_HERE, BindOnce(&QuitFunc) execution, the order of task
execution can change. We don't care anyway that the order isn't correct.
EXPECT_EQ(order.Get(8), TaskItem(QUITMESSAGELOOP, 5, true));
EXPECT_EQ(order.Get(9), TaskItem(QUITMESSAGELOOP, 5, false));
@@ -470,19 +611,19 @@ void RunTest_RecursiveSupport2(MessageLoop::Type message_loop_type) {
void PostNTasksThenQuit(int posts_remaining) {
if (posts_remaining > 1) {
ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&PostNTasksThenQuit, posts_remaining - 1));
+ FROM_HERE, BindOnce(&PostNTasksThenQuit, posts_remaining - 1));
} else {
- MessageLoop::current()->QuitWhenIdle();
+ RunLoop::QuitCurrentWhenIdleDeprecated();
}
}
#if defined(OS_WIN)
-class TestIOHandler : public MessageLoopForIO::IOHandler {
+class TestIOHandler : public MessagePumpForIO::IOHandler {
public:
TestIOHandler(const wchar_t* name, HANDLE signal, bool wait);
- void OnIOCompleted(MessageLoopForIO::IOContext* context,
+ void OnIOCompleted(MessagePumpForIO::IOContext* context,
DWORD bytes_transfered,
DWORD error) override;
@@ -493,7 +634,7 @@ class TestIOHandler : public MessageLoopForIO::IOHandler {
private:
char buffer_[48];
- MessageLoopForIO::IOContext context_;
+ MessagePumpForIO::IOContext context_;
HANDLE signal_;
win::ScopedHandle file_;
bool wait_;
@@ -509,7 +650,7 @@ TestIOHandler::TestIOHandler(const wchar_t* name, HANDLE signal, bool wait)
}
void TestIOHandler::Init() {
- MessageLoopForIO::current()->RegisterIOHandler(file_.Get(), this);
+ MessageLoopCurrentForIO::Get()->RegisterIOHandler(file_.Get(), this);
DWORD read;
EXPECT_FALSE(ReadFile(file_.Get(), buffer_, size(), &read, context()));
@@ -518,15 +659,16 @@ void TestIOHandler::Init() {
WaitForIO();
}
-void TestIOHandler::OnIOCompleted(MessageLoopForIO::IOContext* context,
- DWORD bytes_transfered, DWORD error) {
+void TestIOHandler::OnIOCompleted(MessagePumpForIO::IOContext* context,
+ DWORD bytes_transfered,
+ DWORD error) {
ASSERT_TRUE(context == &context_);
ASSERT_TRUE(SetEvent(signal_));
}
void TestIOHandler::WaitForIO() {
- EXPECT_TRUE(MessageLoopForIO::current()->WaitForIOCompletion(300, this));
- EXPECT_TRUE(MessageLoopForIO::current()->WaitForIOCompletion(400, this));
+ EXPECT_TRUE(MessageLoopCurrentForIO::Get()->WaitForIOCompletion(300, this));
+ EXPECT_TRUE(MessageLoopCurrentForIO::Get()->WaitForIOCompletion(400, this));
}
void RunTest_IOHandler() {
@@ -545,7 +687,7 @@ void RunTest_IOHandler() {
TestIOHandler handler(kPipeName, callback_called.Get(), false);
thread.task_runner()->PostTask(
- FROM_HERE, Bind(&TestIOHandler::Init, Unretained(&handler)));
+ FROM_HERE, BindOnce(&TestIOHandler::Init, Unretained(&handler)));
// Make sure the thread runs and sleeps for lack of work.
PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
@@ -584,13 +726,13 @@ void RunTest_WaitForIO() {
TestIOHandler handler1(kPipeName1, callback1_called.Get(), false);
TestIOHandler handler2(kPipeName2, callback2_called.Get(), true);
thread.task_runner()->PostTask(
- FROM_HERE, Bind(&TestIOHandler::Init, Unretained(&handler1)));
+ FROM_HERE, BindOnce(&TestIOHandler::Init, Unretained(&handler1)));
// TODO(ajwong): Do we really need such long Sleeps in this function?
// Make sure the thread runs and sleeps for lack of work.
TimeDelta delay = TimeDelta::FromMilliseconds(100);
PlatformThread::Sleep(delay);
thread.task_runner()->PostTask(
- FROM_HERE, Bind(&TestIOHandler::Init, Unretained(&handler2)));
+ FROM_HERE, BindOnce(&TestIOHandler::Init, Unretained(&handler2)));
PlatformThread::Sleep(delay);
// At this time handler1 is waiting to be called, and the thread is waiting
@@ -622,67 +764,1119 @@ void RunTest_WaitForIO() {
// that message loops work properly in all configurations. Of course, in some
// cases, a unit test may only be for a particular type of loop.
-RUN_MESSAGE_LOOP_TESTS(Default, &TypeDefaultMessagePumpFactory);
-RUN_MESSAGE_LOOP_TESTS(UI, &TypeUIMessagePumpFactory);
-RUN_MESSAGE_LOOP_TESTS(IO, &TypeIOMessagePumpFactory);
+namespace {
+
+struct MessageLoopTypedTestParams {
+ MessageLoopTypedTestParams(
+ MessageLoop::Type type_in,
+ TaskSchedulerAvailability task_scheduler_availability_in) {
+ type = type_in;
+ task_scheduler_availability = task_scheduler_availability_in;
+ }
-#if defined(OS_WIN)
-TEST(MessageLoopTest, PostDelayedTask_SharedTimer_SubPump) {
- RunTest_PostDelayedTask_SharedTimer_SubPump();
+ MessageLoop::Type type;
+ TaskSchedulerAvailability task_scheduler_availability;
+};
+
+class MessageLoopTypedTest
+ : public ::testing::TestWithParam<MessageLoopTypedTestParams> {
+ public:
+ MessageLoopTypedTest() = default;
+ ~MessageLoopTypedTest() = default;
+
+ void SetUp() override {
+// Unsupported in libchrome.
+#if 0
+ if (GetTaskSchedulerAvailability() ==
+ TaskSchedulerAvailability::WITH_TASK_SCHEDULER) {
+ TaskScheduler::CreateAndStartWithDefaultParams("MessageLoopTypedTest");
+ }
+#endif
+ }
+
+ void TearDown() override {
+// Unsupported in libchrome.
+#if 0
+ if (GetTaskSchedulerAvailability() ==
+ TaskSchedulerAvailability::WITH_TASK_SCHEDULER) {
+ // Failure to call FlushForTesting() could result in task leaks as tasks
+ // are skipped on shutdown.
+ base::TaskScheduler::GetInstance()->FlushForTesting();
+ base::TaskScheduler::GetInstance()->Shutdown();
+ base::TaskScheduler::GetInstance()->JoinForTesting();
+ base::TaskScheduler::SetInstance(nullptr);
+ }
+#endif
+ }
+
+ static std::string ParamInfoToString(
+ ::testing::TestParamInfo<MessageLoopTypedTestParams> param_info) {
+ return MessageLoopTypeToString(param_info.param.type) + "_" +
+ TaskSchedulerAvailabilityToString(
+ param_info.param.task_scheduler_availability);
+ }
+
+ protected:
+ MessageLoop::Type GetMessageLoopType() { return GetParam().type; }
+
+ private:
+ static std::string MessageLoopTypeToString(MessageLoop::Type type) {
+ switch (type) {
+ case MessageLoop::TYPE_DEFAULT:
+ return "Default";
+ case MessageLoop::TYPE_IO:
+ return "IO";
+ case MessageLoop::TYPE_UI:
+ return "UI";
+ case MessageLoop::TYPE_CUSTOM:
+#if defined(OS_ANDROID)
+ case MessageLoop::TYPE_JAVA:
+#endif // defined(OS_ANDROID)
+ break;
+ }
+ NOTREACHED();
+ return "NotSupported";
+ }
+
+ TaskSchedulerAvailability GetTaskSchedulerAvailability() {
+ return GetParam().task_scheduler_availability;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(MessageLoopTypedTest);
+};
+
+} // namespace
+
+TEST_P(MessageLoopTypedTest, PostTask) {
+ MessageLoop loop(GetMessageLoopType());
+ // Add tests to message loop
+ scoped_refptr<Foo> foo(new Foo());
+ std::string a("a"), b("b"), c("c"), d("d");
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&Foo::Test0, foo));
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&Foo::Test1ConstRef, foo, a));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&Foo::Test1Ptr, foo, &b));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&Foo::Test1Int, foo, 100));
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&Foo::Test2Ptr, foo, &a, &c));
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&Foo::Test2Mixed, foo, a, &d));
+ // After all tests, post a message that will shut down the message loop
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&RunLoop::QuitCurrentWhenIdleDeprecated));
+
+ // Now kick things off
+ RunLoop().Run();
+
+ EXPECT_EQ(foo->test_count(), 105);
+ EXPECT_EQ(foo->result(), "abacad");
}
-// This test occasionally hangs. See http://crbug.com/44567.
-TEST(MessageLoopTest, DISABLED_RecursiveDenial2) {
- RunTest_RecursiveDenial2(MessageLoop::TYPE_DEFAULT);
- RunTest_RecursiveDenial2(MessageLoop::TYPE_UI);
- RunTest_RecursiveDenial2(MessageLoop::TYPE_IO);
+TEST_P(MessageLoopTypedTest, PostDelayedTask_Basic) {
+ MessageLoop loop(GetMessageLoopType());
+
+ // Test that PostDelayedTask results in a delayed task.
+
+ const TimeDelta kDelay = TimeDelta::FromMilliseconds(100);
+
+ int num_tasks = 1;
+ TimeTicks run_time;
+
+ TimeTicks time_before_run = TimeTicks::Now();
+ loop.task_runner()->PostDelayedTask(
+ FROM_HERE, BindOnce(&RecordRunTimeFunc, &run_time, &num_tasks), kDelay);
+ RunLoop().Run();
+ TimeTicks time_after_run = TimeTicks::Now();
+
+ EXPECT_EQ(0, num_tasks);
+ EXPECT_LT(kDelay, time_after_run - time_before_run);
}
-TEST(MessageLoopTest, RecursiveSupport2) {
- // This test requires a UI loop.
- RunTest_RecursiveSupport2(MessageLoop::TYPE_UI);
+TEST_P(MessageLoopTypedTest, PostDelayedTask_InDelayOrder) {
+ MessageLoop loop(GetMessageLoopType());
+
+ // Test that two tasks with different delays run in the right order.
+ int num_tasks = 2;
+ TimeTicks run_time1, run_time2;
+
+ loop.task_runner()->PostDelayedTask(
+ FROM_HERE, BindOnce(&RecordRunTimeFunc, &run_time1, &num_tasks),
+ TimeDelta::FromMilliseconds(200));
+ // If we get a large pause in execution (due to a context switch) here, this
+ // test could fail.
+ loop.task_runner()->PostDelayedTask(
+ FROM_HERE, BindOnce(&RecordRunTimeFunc, &run_time2, &num_tasks),
+ TimeDelta::FromMilliseconds(10));
+
+ RunLoop().Run();
+ EXPECT_EQ(0, num_tasks);
+
+ EXPECT_TRUE(run_time2 < run_time1);
}
-#endif // defined(OS_WIN)
-class DummyTaskObserver : public MessageLoop::TaskObserver {
+TEST_P(MessageLoopTypedTest, PostDelayedTask_InPostOrder) {
+ MessageLoop loop(GetMessageLoopType());
+
+ // Test that two tasks with the same delay run in the order in which they
+ // were posted.
+ //
+ // NOTE: This is actually an approximate test since the API only takes a
+ // "delay" parameter, so we are not exactly simulating two tasks that get
+ // posted at the exact same time. It would be nice if the API allowed us to
+ // specify the desired run time.
+
+ const TimeDelta kDelay = TimeDelta::FromMilliseconds(100);
+
+ int num_tasks = 2;
+ TimeTicks run_time1, run_time2;
+
+ loop.task_runner()->PostDelayedTask(
+ FROM_HERE, BindOnce(&RecordRunTimeFunc, &run_time1, &num_tasks), kDelay);
+ loop.task_runner()->PostDelayedTask(
+ FROM_HERE, BindOnce(&RecordRunTimeFunc, &run_time2, &num_tasks), kDelay);
+
+ RunLoop().Run();
+ EXPECT_EQ(0, num_tasks);
+
+ EXPECT_TRUE(run_time1 < run_time2);
+}
+
+TEST_P(MessageLoopTypedTest, PostDelayedTask_InPostOrder_2) {
+ MessageLoop loop(GetMessageLoopType());
+
+ // Test that a delayed task still runs after a normal tasks even if the
+ // normal tasks take a long time to run.
+
+ const TimeDelta kPause = TimeDelta::FromMilliseconds(50);
+
+ int num_tasks = 2;
+ TimeTicks run_time;
+
+ loop.task_runner()->PostTask(FROM_HERE,
+ BindOnce(&SlowFunc, kPause, &num_tasks));
+ loop.task_runner()->PostDelayedTask(
+ FROM_HERE, BindOnce(&RecordRunTimeFunc, &run_time, &num_tasks),
+ TimeDelta::FromMilliseconds(10));
+
+ TimeTicks time_before_run = TimeTicks::Now();
+ RunLoop().Run();
+ TimeTicks time_after_run = TimeTicks::Now();
+
+ EXPECT_EQ(0, num_tasks);
+
+ EXPECT_LT(kPause, time_after_run - time_before_run);
+}
+
+TEST_P(MessageLoopTypedTest, PostDelayedTask_InPostOrder_3) {
+ MessageLoop loop(GetMessageLoopType());
+
+ // Test that a delayed task still runs after a pile of normal tasks. The key
+ // difference between this test and the previous one is that here we return
+ // the MessageLoop a lot so we give the MessageLoop plenty of opportunities
+ // to maybe run the delayed task. It should know not to do so until the
+ // delayed task's delay has passed.
+
+ int num_tasks = 11;
+ TimeTicks run_time1, run_time2;
+
+ // Clutter the ML with tasks.
+ for (int i = 1; i < num_tasks; ++i)
+ loop.task_runner()->PostTask(
+ FROM_HERE, BindOnce(&RecordRunTimeFunc, &run_time1, &num_tasks));
+
+ loop.task_runner()->PostDelayedTask(
+ FROM_HERE, BindOnce(&RecordRunTimeFunc, &run_time2, &num_tasks),
+ TimeDelta::FromMilliseconds(1));
+
+ RunLoop().Run();
+ EXPECT_EQ(0, num_tasks);
+
+ EXPECT_TRUE(run_time2 > run_time1);
+}
+
+TEST_P(MessageLoopTypedTest, PostDelayedTask_SharedTimer) {
+ MessageLoop loop(GetMessageLoopType());
+
+ // Test that the interval of the timer, used to run the next delayed task, is
+ // set to a value corresponding to when the next delayed task should run.
+
+ // By setting num_tasks to 1, we ensure that the first task to run causes the
+ // run loop to exit.
+ int num_tasks = 1;
+ TimeTicks run_time1, run_time2;
+
+ loop.task_runner()->PostDelayedTask(
+ FROM_HERE, BindOnce(&RecordRunTimeFunc, &run_time1, &num_tasks),
+ TimeDelta::FromSeconds(1000));
+ loop.task_runner()->PostDelayedTask(
+ FROM_HERE, BindOnce(&RecordRunTimeFunc, &run_time2, &num_tasks),
+ TimeDelta::FromMilliseconds(10));
+
+ TimeTicks start_time = TimeTicks::Now();
+
+ RunLoop().Run();
+ EXPECT_EQ(0, num_tasks);
+
+ // Ensure that we ran in far less time than the slower timer.
+ TimeDelta total_time = TimeTicks::Now() - start_time;
+ EXPECT_GT(5000, total_time.InMilliseconds());
+
+ // In case both timers somehow run at nearly the same time, sleep a little
+ // and then run all pending to force them both to have run. This is just
+ // encouraging flakiness if there is any.
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
+ RunLoop().RunUntilIdle();
+
+ EXPECT_TRUE(run_time1.is_null());
+ EXPECT_FALSE(run_time2.is_null());
+}
+
+namespace {
+
+// This is used to inject a test point for recording the destructor calls for
+// Closure objects send to MessageLoop::PostTask(). It is awkward usage since we
+// are trying to hook the actual destruction, which is not a common operation.
+class RecordDeletionProbe : public RefCounted<RecordDeletionProbe> {
public:
- explicit DummyTaskObserver(int num_tasks)
- : num_tasks_started_(0),
- num_tasks_processed_(0),
- num_tasks_(num_tasks) {}
+ RecordDeletionProbe(RecordDeletionProbe* post_on_delete, bool* was_deleted)
+ : post_on_delete_(post_on_delete), was_deleted_(was_deleted) {}
+ void Run() {}
- ~DummyTaskObserver() override {}
+ private:
+ friend class RefCounted<RecordDeletionProbe>;
- void WillProcessTask(const PendingTask& pending_task) override {
- num_tasks_started_++;
- EXPECT_LE(num_tasks_started_, num_tasks_);
- EXPECT_EQ(num_tasks_started_, num_tasks_processed_ + 1);
+ ~RecordDeletionProbe() {
+ *was_deleted_ = true;
+ if (post_on_delete_.get())
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&RecordDeletionProbe::Run, post_on_delete_));
}
- void DidProcessTask(const PendingTask& pending_task) override {
- num_tasks_processed_++;
- EXPECT_LE(num_tasks_started_, num_tasks_);
- EXPECT_EQ(num_tasks_started_, num_tasks_processed_);
+ scoped_refptr<RecordDeletionProbe> post_on_delete_;
+ bool* was_deleted_;
+};
+
+} // namespace
+
+/* TODO(darin): MessageLoop does not support deleting all tasks in the */
+/* destructor. */
+/* Fails, http://crbug.com/50272. */
+TEST_P(MessageLoopTypedTest, DISABLED_EnsureDeletion) {
+ bool a_was_deleted = false;
+ bool b_was_deleted = false;
+ {
+ MessageLoop loop(GetMessageLoopType());
+ loop.task_runner()->PostTask(
+ FROM_HERE, BindOnce(&RecordDeletionProbe::Run,
+ new RecordDeletionProbe(nullptr, &a_was_deleted)));
+ // TODO(ajwong): Do we really need 1000ms here?
+ loop.task_runner()->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&RecordDeletionProbe::Run,
+ new RecordDeletionProbe(nullptr, &b_was_deleted)),
+ TimeDelta::FromMilliseconds(1000));
}
+ EXPECT_TRUE(a_was_deleted);
+ EXPECT_TRUE(b_was_deleted);
+}
- int num_tasks_started() const { return num_tasks_started_; }
- int num_tasks_processed() const { return num_tasks_processed_; }
+/* TODO(darin): MessageLoop does not support deleting all tasks in the */
+/* destructor. */
+/* Fails, http://crbug.com/50272. */
+TEST_P(MessageLoopTypedTest, DISABLED_EnsureDeletion_Chain) {
+ bool a_was_deleted = false;
+ bool b_was_deleted = false;
+ bool c_was_deleted = false;
+ {
+ MessageLoop loop(GetMessageLoopType());
+ // The scoped_refptr for each of the below is held either by the chained
+ // RecordDeletionProbe, or the bound RecordDeletionProbe::Run() callback.
+ RecordDeletionProbe* a = new RecordDeletionProbe(nullptr, &a_was_deleted);
+ RecordDeletionProbe* b = new RecordDeletionProbe(a, &b_was_deleted);
+ RecordDeletionProbe* c = new RecordDeletionProbe(b, &c_was_deleted);
+ loop.task_runner()->PostTask(FROM_HERE,
+ BindOnce(&RecordDeletionProbe::Run, c));
+ }
+ EXPECT_TRUE(a_was_deleted);
+ EXPECT_TRUE(b_was_deleted);
+ EXPECT_TRUE(c_was_deleted);
+}
- private:
- int num_tasks_started_;
- int num_tasks_processed_;
- const int num_tasks_;
+namespace {
- DISALLOW_COPY_AND_ASSIGN(DummyTaskObserver);
-};
+void NestingFunc(int* depth) {
+ if (*depth > 0) {
+ *depth -= 1;
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&NestingFunc, depth));
+
+ MessageLoopCurrent::Get()->SetNestableTasksAllowed(true);
+ RunLoop().Run();
+ }
+ base::RunLoop::QuitCurrentWhenIdleDeprecated();
+}
+
+} // namespace
+
+TEST_P(MessageLoopTypedTest, Nesting) {
+ MessageLoop loop(GetMessageLoopType());
+
+ int depth = 50;
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&NestingFunc, &depth));
+ RunLoop().Run();
+ EXPECT_EQ(depth, 0);
+}
+
+TEST_P(MessageLoopTypedTest, RecursiveDenial1) {
+ MessageLoop loop(GetMessageLoopType());
+
+ EXPECT_TRUE(MessageLoopCurrent::Get()->NestableTasksAllowed());
+ TaskList order;
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&RecursiveFunc, &order, 1, 2, false));
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&RecursiveFunc, &order, 2, 2, false));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&QuitFunc, &order, 3));
+
+ RunLoop().Run();
+
+ // FIFO order.
+ ASSERT_EQ(14U, order.Size());
+ EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(2), TaskItem(RECURSIVE, 2, true));
+ EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 2, false));
+ EXPECT_EQ(order.Get(4), TaskItem(QUITMESSAGELOOP, 3, true));
+ EXPECT_EQ(order.Get(5), TaskItem(QUITMESSAGELOOP, 3, false));
+ EXPECT_EQ(order.Get(6), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(7), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(8), TaskItem(RECURSIVE, 2, true));
+ EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 2, false));
+ EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 2, true));
+ EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 2, false));
+}
+
+namespace {
+
+void RecursiveSlowFunc(TaskList* order,
+ int cookie,
+ int depth,
+ bool is_reentrant) {
+ RecursiveFunc(order, cookie, depth, is_reentrant);
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(10));
+}
+
+void OrderedFunc(TaskList* order, int cookie) {
+ order->RecordStart(ORDERED, cookie);
+ order->RecordEnd(ORDERED, cookie);
+}
+
+} // namespace
+
+TEST_P(MessageLoopTypedTest, RecursiveDenial3) {
+ MessageLoop loop(GetMessageLoopType());
+
+ EXPECT_TRUE(MessageLoopCurrent::Get()->NestableTasksAllowed());
+ TaskList order;
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&RecursiveSlowFunc, &order, 1, 2, false));
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&RecursiveSlowFunc, &order, 2, 2, false));
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, BindOnce(&OrderedFunc, &order, 3),
+ TimeDelta::FromMilliseconds(5));
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, BindOnce(&QuitFunc, &order, 4),
+ TimeDelta::FromMilliseconds(5));
+
+ RunLoop().Run();
+
+ // FIFO order.
+ ASSERT_EQ(16U, order.Size());
+ EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(2), TaskItem(RECURSIVE, 2, true));
+ EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 2, false));
+ EXPECT_EQ(order.Get(4), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(5), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(6), TaskItem(ORDERED, 3, true));
+ EXPECT_EQ(order.Get(7), TaskItem(ORDERED, 3, false));
+ EXPECT_EQ(order.Get(8), TaskItem(RECURSIVE, 2, true));
+ EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 2, false));
+ EXPECT_EQ(order.Get(10), TaskItem(QUITMESSAGELOOP, 4, true));
+ EXPECT_EQ(order.Get(11), TaskItem(QUITMESSAGELOOP, 4, false));
+ EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(14), TaskItem(RECURSIVE, 2, true));
+ EXPECT_EQ(order.Get(15), TaskItem(RECURSIVE, 2, false));
+}
+
+TEST_P(MessageLoopTypedTest, RecursiveSupport1) {
+ MessageLoop loop(GetMessageLoopType());
+
+ TaskList order;
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&RecursiveFunc, &order, 1, 2, true));
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&RecursiveFunc, &order, 2, 2, true));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&QuitFunc, &order, 3));
+
+ RunLoop().Run();
+
+ // FIFO order.
+ ASSERT_EQ(14U, order.Size());
+ EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(2), TaskItem(RECURSIVE, 2, true));
+ EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 2, false));
+ EXPECT_EQ(order.Get(4), TaskItem(QUITMESSAGELOOP, 3, true));
+ EXPECT_EQ(order.Get(5), TaskItem(QUITMESSAGELOOP, 3, false));
+ EXPECT_EQ(order.Get(6), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(7), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(8), TaskItem(RECURSIVE, 2, true));
+ EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 2, false));
+ EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, true));
+ EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 1, false));
+ EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 2, true));
+ EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 2, false));
+}
+
+// Tests that non nestable tasks run in FIFO if there are no nested loops.
+TEST_P(MessageLoopTypedTest, NonNestableWithNoNesting) {
+ MessageLoop loop(GetMessageLoopType());
+
+ TaskList order;
+
+ ThreadTaskRunnerHandle::Get()->PostNonNestableTask(
+ FROM_HERE, BindOnce(&OrderedFunc, &order, 1));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&OrderedFunc, &order, 2));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&QuitFunc, &order, 3));
+ RunLoop().Run();
+
+ // FIFO order.
+ ASSERT_EQ(6U, order.Size());
+ EXPECT_EQ(order.Get(0), TaskItem(ORDERED, 1, true));
+ EXPECT_EQ(order.Get(1), TaskItem(ORDERED, 1, false));
+ EXPECT_EQ(order.Get(2), TaskItem(ORDERED, 2, true));
+ EXPECT_EQ(order.Get(3), TaskItem(ORDERED, 2, false));
+ EXPECT_EQ(order.Get(4), TaskItem(QUITMESSAGELOOP, 3, true));
+ EXPECT_EQ(order.Get(5), TaskItem(QUITMESSAGELOOP, 3, false));
+}
+
+namespace {
+
+void FuncThatPumps(TaskList* order, int cookie) {
+ order->RecordStart(PUMPS, cookie);
+ RunLoop(RunLoop::Type::kNestableTasksAllowed).RunUntilIdle();
+ order->RecordEnd(PUMPS, cookie);
+}
+
+void SleepFunc(TaskList* order, int cookie, TimeDelta delay) {
+ order->RecordStart(SLEEP, cookie);
+ PlatformThread::Sleep(delay);
+ order->RecordEnd(SLEEP, cookie);
+}
+
+} // namespace
+
+// Tests that non nestable tasks don't run when there's code in the call stack.
+TEST_P(MessageLoopTypedTest, NonNestableDelayedInNestedLoop) {
+ MessageLoop loop(GetMessageLoopType());
+
+ TaskList order;
+
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&FuncThatPumps, &order, 1));
+ ThreadTaskRunnerHandle::Get()->PostNonNestableTask(
+ FROM_HERE, BindOnce(&OrderedFunc, &order, 2));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&OrderedFunc, &order, 3));
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ BindOnce(&SleepFunc, &order, 4, TimeDelta::FromMilliseconds(50)));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&OrderedFunc, &order, 5));
+ ThreadTaskRunnerHandle::Get()->PostNonNestableTask(
+ FROM_HERE, BindOnce(&QuitFunc, &order, 6));
+
+ RunLoop().Run();
+
+ // FIFO order.
+ ASSERT_EQ(12U, order.Size());
+ EXPECT_EQ(order.Get(0), TaskItem(PUMPS, 1, true));
+ EXPECT_EQ(order.Get(1), TaskItem(ORDERED, 3, true));
+ EXPECT_EQ(order.Get(2), TaskItem(ORDERED, 3, false));
+ EXPECT_EQ(order.Get(3), TaskItem(SLEEP, 4, true));
+ EXPECT_EQ(order.Get(4), TaskItem(SLEEP, 4, false));
+ EXPECT_EQ(order.Get(5), TaskItem(ORDERED, 5, true));
+ EXPECT_EQ(order.Get(6), TaskItem(ORDERED, 5, false));
+ EXPECT_EQ(order.Get(7), TaskItem(PUMPS, 1, false));
+ EXPECT_EQ(order.Get(8), TaskItem(ORDERED, 2, true));
+ EXPECT_EQ(order.Get(9), TaskItem(ORDERED, 2, false));
+ EXPECT_EQ(order.Get(10), TaskItem(QUITMESSAGELOOP, 6, true));
+ EXPECT_EQ(order.Get(11), TaskItem(QUITMESSAGELOOP, 6, false));
+}
+
+namespace {
+
+void FuncThatRuns(TaskList* order, int cookie, RunLoop* run_loop) {
+ order->RecordStart(RUNS, cookie);
+ {
+ MessageLoopCurrent::ScopedNestableTaskAllower allow;
+ run_loop->Run();
+ }
+ order->RecordEnd(RUNS, cookie);
+}
+
+void FuncThatQuitsNow() {
+ base::RunLoop::QuitCurrentDeprecated();
+}
+
+} // namespace
+
+// Tests RunLoopQuit only quits the corresponding MessageLoop::Run.
+TEST_P(MessageLoopTypedTest, QuitNow) {
+ MessageLoop loop(GetMessageLoopType());
+
+ TaskList order;
+
+ RunLoop run_loop;
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&FuncThatRuns, &order, 1, Unretained(&run_loop)));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&OrderedFunc, &order, 2));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&FuncThatQuitsNow));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&OrderedFunc, &order, 3));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&FuncThatQuitsNow));
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&OrderedFunc, &order, 4)); // never runs
+
+ RunLoop().Run();
+
+ ASSERT_EQ(6U, order.Size());
+ int task_index = 0;
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, false));
+ EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
+}
+
+// Tests RunLoopQuit only quits the corresponding MessageLoop::Run.
+TEST_P(MessageLoopTypedTest, RunLoopQuitTop) {
+ MessageLoop loop(GetMessageLoopType());
+
+ TaskList order;
+
+ RunLoop outer_run_loop;
+ RunLoop nested_run_loop;
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ BindOnce(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop)));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ outer_run_loop.QuitClosure());
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&OrderedFunc, &order, 2));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ nested_run_loop.QuitClosure());
+
+ outer_run_loop.Run();
+
+ ASSERT_EQ(4U, order.Size());
+ int task_index = 0;
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
+ EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
+}
+
+// Tests RunLoopQuit only quits the corresponding MessageLoop::Run.
+TEST_P(MessageLoopTypedTest, RunLoopQuitNested) {
+ MessageLoop loop(GetMessageLoopType());
+
+ TaskList order;
+
+ RunLoop outer_run_loop;
+ RunLoop nested_run_loop;
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ BindOnce(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop)));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ nested_run_loop.QuitClosure());
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&OrderedFunc, &order, 2));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ outer_run_loop.QuitClosure());
+
+ outer_run_loop.Run();
+
+ ASSERT_EQ(4U, order.Size());
+ int task_index = 0;
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false));
+ EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
+}
+
+// Quits current loop and immediately runs a nested loop.
+void QuitAndRunNestedLoop(TaskList* order,
+ int cookie,
+ RunLoop* outer_run_loop,
+ RunLoop* nested_run_loop) {
+ order->RecordStart(RUNS, cookie);
+ outer_run_loop->Quit();
+ nested_run_loop->Run();
+ order->RecordEnd(RUNS, cookie);
+}
+
+// Test that we can run nested loop after quitting the current one.
+TEST_P(MessageLoopTypedTest, RunLoopNestedAfterQuit) {
+ MessageLoop loop(GetMessageLoopType());
+
+ TaskList order;
+
+ RunLoop outer_run_loop;
+ RunLoop nested_run_loop;
-TEST(MessageLoopTest, TaskObserver) {
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ nested_run_loop.QuitClosure());
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&QuitAndRunNestedLoop, &order, 1, &outer_run_loop,
+ &nested_run_loop));
+
+ outer_run_loop.Run();
+
+ ASSERT_EQ(2U, order.Size());
+ int task_index = 0;
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
+ EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
+}
+
+// Tests RunLoopQuit only quits the corresponding MessageLoop::Run.
+TEST_P(MessageLoopTypedTest, RunLoopQuitBogus) {
+ MessageLoop loop(GetMessageLoopType());
+
+ TaskList order;
+
+ RunLoop outer_run_loop;
+ RunLoop nested_run_loop;
+ RunLoop bogus_run_loop;
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ BindOnce(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop)));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ bogus_run_loop.QuitClosure());
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&OrderedFunc, &order, 2));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ outer_run_loop.QuitClosure());
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ nested_run_loop.QuitClosure());
+
+ outer_run_loop.Run();
+
+ ASSERT_EQ(4U, order.Size());
+ int task_index = 0;
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
+ EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
+}
+
+// Tests RunLoopQuit only quits the corresponding MessageLoop::Run.
+TEST_P(MessageLoopTypedTest, RunLoopQuitDeep) {
+ MessageLoop loop(GetMessageLoopType());
+
+ TaskList order;
+
+ RunLoop outer_run_loop;
+ RunLoop nested_loop1;
+ RunLoop nested_loop2;
+ RunLoop nested_loop3;
+ RunLoop nested_loop4;
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&FuncThatRuns, &order, 1, Unretained(&nested_loop1)));
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&FuncThatRuns, &order, 2, Unretained(&nested_loop2)));
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&FuncThatRuns, &order, 3, Unretained(&nested_loop3)));
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&FuncThatRuns, &order, 4, Unretained(&nested_loop4)));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&OrderedFunc, &order, 5));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ outer_run_loop.QuitClosure());
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&OrderedFunc, &order, 6));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ nested_loop1.QuitClosure());
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&OrderedFunc, &order, 7));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ nested_loop2.QuitClosure());
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&OrderedFunc, &order, 8));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ nested_loop3.QuitClosure());
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&OrderedFunc, &order, 9));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ nested_loop4.QuitClosure());
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&OrderedFunc, &order, 10));
+
+ outer_run_loop.Run();
+
+ ASSERT_EQ(18U, order.Size());
+ int task_index = 0;
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 2, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 3, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 4, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 5, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 5, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 6, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 6, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 7, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 7, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 8, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 8, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 9, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 9, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 4, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 3, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 2, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
+ EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
+}
+
+// Tests RunLoopQuit works before RunWithID.
+TEST_P(MessageLoopTypedTest, RunLoopQuitOrderBefore) {
+ MessageLoop loop(GetMessageLoopType());
+
+ TaskList order;
+
+ RunLoop run_loop;
+
+ run_loop.Quit();
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&OrderedFunc, &order, 1)); // never runs
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&FuncThatQuitsNow)); // never runs
+
+ run_loop.Run();
+
+ ASSERT_EQ(0U, order.Size());
+}
+
+// Tests RunLoopQuit works during RunWithID.
+TEST_P(MessageLoopTypedTest, RunLoopQuitOrderDuring) {
+ MessageLoop loop(GetMessageLoopType());
+
+ TaskList order;
+
+ RunLoop run_loop;
+
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&OrderedFunc, &order, 1));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, run_loop.QuitClosure());
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&OrderedFunc, &order, 2)); // never runs
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&FuncThatQuitsNow)); // never runs
+
+ run_loop.Run();
+
+ ASSERT_EQ(2U, order.Size());
+ int task_index = 0;
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 1, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 1, false));
+ EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
+}
+
+// Tests RunLoopQuit works after RunWithID.
+TEST_P(MessageLoopTypedTest, RunLoopQuitOrderAfter) {
+ MessageLoop loop(GetMessageLoopType());
+
+ TaskList order;
+
+ RunLoop run_loop;
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&FuncThatRuns, &order, 1, Unretained(&run_loop)));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&OrderedFunc, &order, 2));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&FuncThatQuitsNow));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&OrderedFunc, &order, 3));
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, run_loop.QuitClosure()); // has no affect
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&OrderedFunc, &order, 4));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ BindOnce(&FuncThatQuitsNow));
+
+ run_loop.allow_quit_current_deprecated_ = true;
+
+ RunLoop outer_run_loop;
+ outer_run_loop.Run();
+
+ ASSERT_EQ(8U, order.Size());
+ int task_index = 0;
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, false));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 4, true));
+ EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 4, false));
+ EXPECT_EQ(static_cast<size_t>(task_index), order.Size());
+}
+
+// There was a bug in the MessagePumpGLib where posting tasks recursively
+// caused the message loop to hang, due to the buffer of the internal pipe
+// becoming full. Test all MessageLoop types to ensure this issue does not
+// exist in other MessagePumps.
+//
+// On Linux, the pipe buffer size is 64KiB by default. The bug caused one
+// byte accumulated in the pipe per two posts, so we should repeat 128K
+// times to reproduce the bug.
+#if defined(OS_FUCHSIA)
+// TODO(crbug.com/810077): This is flaky on Fuchsia.
+#define MAYBE_RecursivePosts DISABLED_RecursivePosts
+#else
+#define MAYBE_RecursivePosts RecursivePosts
+#endif
+TEST_P(MessageLoopTypedTest, MAYBE_RecursivePosts) {
+ const int kNumTimes = 1 << 17;
+ MessageLoop loop(GetMessageLoopType());
+ loop.task_runner()->PostTask(FROM_HERE,
+ BindOnce(&PostNTasksThenQuit, kNumTimes));
+ RunLoop().Run();
+}
+
+TEST_P(MessageLoopTypedTest, NestableTasksAllowedAtTopLevel) {
+ MessageLoop loop(GetMessageLoopType());
+ EXPECT_TRUE(MessageLoopCurrent::Get()->NestableTasksAllowed());
+}
+
+// Nestable tasks shouldn't be allowed to run reentrantly by default (regression
+// test for https://crbug.com/754112).
+TEST_P(MessageLoopTypedTest, NestableTasksDisallowedByDefault) {
+ MessageLoop loop(GetMessageLoopType());
+ RunLoop run_loop;
+ loop.task_runner()->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](RunLoop* run_loop) {
+ EXPECT_FALSE(MessageLoopCurrent::Get()->NestableTasksAllowed());
+ run_loop->Quit();
+ },
+ Unretained(&run_loop)));
+ run_loop.Run();
+}
+
+TEST_P(MessageLoopTypedTest, NestableTasksProcessedWhenRunLoopAllows) {
+ MessageLoop loop(GetMessageLoopType());
+ RunLoop run_loop;
+ loop.task_runner()->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](RunLoop* run_loop) {
+ // This test would hang if this RunLoop wasn't of type
+ // kNestableTasksAllowed (i.e. this is testing that this is
+ // processed and doesn't hang).
+ RunLoop nested_run_loop(RunLoop::Type::kNestableTasksAllowed);
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](RunLoop* nested_run_loop) {
+ // Each additional layer of application task nesting
+ // requires its own allowance. The kNestableTasksAllowed
+ // RunLoop allowed this task to be processed but further
+ // nestable tasks are by default disallowed from this
+ // layer.
+ EXPECT_FALSE(
+ MessageLoopCurrent::Get()->NestableTasksAllowed());
+ nested_run_loop->Quit();
+ },
+ Unretained(&nested_run_loop)));
+ nested_run_loop.Run();
+
+ run_loop->Quit();
+ },
+ Unretained(&run_loop)));
+ run_loop.Run();
+}
+
+TEST_P(MessageLoopTypedTest, NestableTasksAllowedExplicitlyInScope) {
+ MessageLoop loop(GetMessageLoopType());
+ RunLoop run_loop;
+ loop.task_runner()->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](RunLoop* run_loop) {
+ {
+ MessageLoopCurrent::ScopedNestableTaskAllower
+ allow_nestable_tasks;
+ EXPECT_TRUE(MessageLoopCurrent::Get()->NestableTasksAllowed());
+ }
+ EXPECT_FALSE(MessageLoopCurrent::Get()->NestableTasksAllowed());
+ run_loop->Quit();
+ },
+ Unretained(&run_loop)));
+ run_loop.Run();
+}
+
+TEST_P(MessageLoopTypedTest, NestableTasksAllowedManually) {
+ MessageLoop loop(GetMessageLoopType());
+ RunLoop run_loop;
+ loop.task_runner()->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](RunLoop* run_loop) {
+ EXPECT_FALSE(MessageLoopCurrent::Get()->NestableTasksAllowed());
+ MessageLoopCurrent::Get()->SetNestableTasksAllowed(true);
+ EXPECT_TRUE(MessageLoopCurrent::Get()->NestableTasksAllowed());
+ MessageLoopCurrent::Get()->SetNestableTasksAllowed(false);
+ EXPECT_FALSE(MessageLoopCurrent::Get()->NestableTasksAllowed());
+ run_loop->Quit();
+ },
+ Unretained(&run_loop)));
+ run_loop.Run();
+}
+
+INSTANTIATE_TEST_CASE_P(
+ ,
+ MessageLoopTypedTest,
+ ::testing::Values(MessageLoopTypedTestParams(
+ MessageLoop::TYPE_DEFAULT,
+ TaskSchedulerAvailability::NO_TASK_SCHEDULER),
+ MessageLoopTypedTestParams(
+ MessageLoop::TYPE_IO,
+ TaskSchedulerAvailability::NO_TASK_SCHEDULER),
+ MessageLoopTypedTestParams(
+ MessageLoop::TYPE_UI,
+ TaskSchedulerAvailability::NO_TASK_SCHEDULER)
+// Unsupported in libchrome.
+#if 0
+ ,MessageLoopTypedTestParams(
+ MessageLoop::TYPE_DEFAULT,
+ TaskSchedulerAvailability::WITH_TASK_SCHEDULER),
+ MessageLoopTypedTestParams(
+ MessageLoop::TYPE_IO,
+ TaskSchedulerAvailability::WITH_TASK_SCHEDULER),
+ MessageLoopTypedTestParams(
+ MessageLoop::TYPE_UI,
+ TaskSchedulerAvailability::WITH_TASK_SCHEDULER)
+#endif
+ ),
+ MessageLoopTypedTest::ParamInfoToString);
+
+#if defined(OS_WIN)
+// Verifies that the MessageLoop ignores WM_QUIT, rather than quitting.
+// Users of MessageLoop typically expect to control when their RunLoops stop
+// Run()ning explicitly, via QuitClosure() etc (see https://crbug.com/720078)
+TEST_P(MessageLoopTest, WmQuitIsIgnored) {
+ MessageLoop loop(MessageLoop::TYPE_UI);
+
+ // Post a WM_QUIT message to the current thread.
+ ::PostQuitMessage(0);
+
+ // Post a task to the current thread, with a small delay to make it less
+ // likely that we process the posted task before looking for WM_* messages.
+ bool task_was_run = false;
+ RunLoop run_loop;
+ loop.task_runner()->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(
+ [](bool* flag, OnceClosure closure) {
+ *flag = true;
+ std::move(closure).Run();
+ },
+ &task_was_run, run_loop.QuitClosure()),
+ TestTimeouts::tiny_timeout());
+
+ // Run the loop, and ensure that the posted task is processed before we quit.
+ run_loop.Run();
+ EXPECT_TRUE(task_was_run);
+}
+
+TEST_P(MessageLoopTest, WmQuitIsNotIgnoredWithEnableWmQuit) {
+ MessageLoop loop(MessageLoop::TYPE_UI);
+ static_cast<MessageLoopForUI*>(&loop)->EnableWmQuit();
+
+ // Post a WM_QUIT message to the current thread.
+ ::PostQuitMessage(0);
+
+ // Post a task to the current thread, with a small delay to make it less
+ // likely that we process the posted task before looking for WM_* messages.
+ RunLoop run_loop;
+ loop.task_runner()->PostDelayedTask(FROM_HERE,
+ BindOnce(
+ [](OnceClosure closure) {
+ ADD_FAILURE();
+ std::move(closure).Run();
+ },
+ run_loop.QuitClosure()),
+ TestTimeouts::tiny_timeout());
+
+ // Run the loop. It should not result in ADD_FAILURE() getting called.
+ run_loop.Run();
+}
+
+TEST_P(MessageLoopTest, PostDelayedTask_SharedTimer_SubPump) {
+ RunTest_PostDelayedTask_SharedTimer_SubPump();
+}
+
+// This test occasionally hangs. See http://crbug.com/44567.
+TEST_P(MessageLoopTest, DISABLED_RecursiveDenial2) {
+ RunTest_RecursiveDenial2(MessageLoop::TYPE_DEFAULT);
+ RunTest_RecursiveDenial2(MessageLoop::TYPE_UI);
+ RunTest_RecursiveDenial2(MessageLoop::TYPE_IO);
+}
+
+TEST_P(MessageLoopTest, RecursiveSupport2) {
+ // This test requires a UI loop.
+ RunTest_RecursiveSupport2(MessageLoop::TYPE_UI);
+}
+#endif // defined(OS_WIN)
+
+TEST_P(MessageLoopTest, TaskObserver) {
const int kNumPosts = 6;
DummyTaskObserver observer(kNumPosts);
MessageLoop loop;
loop.AddTaskObserver(&observer);
- loop.task_runner()->PostTask(FROM_HERE, Bind(&PostNTasksThenQuit, kNumPosts));
+ loop.task_runner()->PostTask(FROM_HERE,
+ BindOnce(&PostNTasksThenQuit, kNumPosts));
RunLoop().Run();
loop.RemoveTaskObserver(&observer);
@@ -691,111 +1885,55 @@ TEST(MessageLoopTest, TaskObserver) {
}
#if defined(OS_WIN)
-TEST(MessageLoopTest, IOHandler) {
+TEST_P(MessageLoopTest, IOHandler) {
RunTest_IOHandler();
}
-TEST(MessageLoopTest, WaitForIO) {
+TEST_P(MessageLoopTest, WaitForIO) {
RunTest_WaitForIO();
}
-TEST(MessageLoopTest, HighResolutionTimer) {
+TEST_P(MessageLoopTest, HighResolutionTimer) {
MessageLoop message_loop;
Time::EnableHighResolutionTimer(true);
- const TimeDelta kFastTimer = TimeDelta::FromMilliseconds(5);
- const TimeDelta kSlowTimer = TimeDelta::FromMilliseconds(100);
-
- EXPECT_FALSE(message_loop.HasHighResolutionTasks());
- // Post a fast task to enable the high resolution timers.
- message_loop.task_runner()->PostDelayedTask(
- FROM_HERE, Bind(&PostNTasksThenQuit, 1), kFastTimer);
- EXPECT_TRUE(message_loop.HasHighResolutionTasks());
- RunLoop().Run();
- EXPECT_FALSE(message_loop.HasHighResolutionTasks());
- EXPECT_FALSE(Time::IsHighResolutionTimerInUse());
- // Check that a slow task does not trigger the high resolution logic.
- message_loop.task_runner()->PostDelayedTask(
- FROM_HERE, Bind(&PostNTasksThenQuit, 1), kSlowTimer);
- EXPECT_FALSE(message_loop.HasHighResolutionTasks());
- RunLoop().Run();
- EXPECT_FALSE(message_loop.HasHighResolutionTasks());
- Time::EnableHighResolutionTimer(false);
-}
-
-#endif // defined(OS_WIN)
-
-#if defined(OS_POSIX) && !defined(OS_NACL)
-
-namespace {
+ constexpr TimeDelta kFastTimer = TimeDelta::FromMilliseconds(5);
+ constexpr TimeDelta kSlowTimer = TimeDelta::FromMilliseconds(100);
-class QuitDelegate : public MessageLoopForIO::Watcher {
- public:
- void OnFileCanWriteWithoutBlocking(int fd) override {
- MessageLoop::current()->QuitWhenIdle();
- }
- void OnFileCanReadWithoutBlocking(int fd) override {
- MessageLoop::current()->QuitWhenIdle();
- }
-};
-
-TEST(MessageLoopTest, FileDescriptorWatcherOutlivesMessageLoop) {
- // Simulate a MessageLoop that dies before an FileDescriptorWatcher.
- // This could happen when people use the Singleton pattern or atexit.
-
- // Create a file descriptor. Doesn't need to be readable or writable,
- // as we don't need to actually get any notifications.
- // pipe() is just the easiest way to do it.
- int pipefds[2];
- int err = pipe(pipefds);
- ASSERT_EQ(0, err);
- int fd = pipefds[1];
{
- // Arrange for controller to live longer than message loop.
- MessageLoopForIO::FileDescriptorWatcher controller(FROM_HERE);
- {
- MessageLoopForIO message_loop;
-
- QuitDelegate delegate;
- message_loop.WatchFileDescriptor(fd,
- true, MessageLoopForIO::WATCH_WRITE, &controller, &delegate);
- // and don't run the message loop, just destroy it.
- }
+ // Post a fast task to enable the high resolution timers.
+ RunLoop run_loop;
+ message_loop.task_runner()->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(
+ [](RunLoop* run_loop) {
+ EXPECT_TRUE(Time::IsHighResolutionTimerInUse());
+ run_loop->QuitWhenIdle();
+ },
+ &run_loop),
+ kFastTimer);
+ run_loop.Run();
}
- if (IGNORE_EINTR(close(pipefds[0])) < 0)
- PLOG(ERROR) << "close";
- if (IGNORE_EINTR(close(pipefds[1])) < 0)
- PLOG(ERROR) << "close";
-}
-
-TEST(MessageLoopTest, FileDescriptorWatcherDoubleStop) {
- // Verify that it's ok to call StopWatchingFileDescriptor().
- // (Errors only showed up in valgrind.)
- int pipefds[2];
- int err = pipe(pipefds);
- ASSERT_EQ(0, err);
- int fd = pipefds[1];
+ EXPECT_FALSE(Time::IsHighResolutionTimerInUse());
{
- // Arrange for message loop to live longer than controller.
- MessageLoopForIO message_loop;
- {
- MessageLoopForIO::FileDescriptorWatcher controller(FROM_HERE);
-
- QuitDelegate delegate;
- message_loop.WatchFileDescriptor(fd,
- true, MessageLoopForIO::WATCH_WRITE, &controller, &delegate);
- controller.StopWatchingFileDescriptor();
- }
+ // Check that a slow task does not trigger the high resolution logic.
+ RunLoop run_loop;
+ message_loop.task_runner()->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(
+ [](RunLoop* run_loop) {
+ EXPECT_FALSE(Time::IsHighResolutionTimerInUse());
+ run_loop->QuitWhenIdle();
+ },
+ &run_loop),
+ kSlowTimer);
+ run_loop.Run();
}
- if (IGNORE_EINTR(close(pipefds[0])) < 0)
- PLOG(ERROR) << "close";
- if (IGNORE_EINTR(close(pipefds[1])) < 0)
- PLOG(ERROR) << "close";
+ Time::EnableHighResolutionTimer(false);
+ Time::ResetHighResolutionTimerUsage();
}
-} // namespace
-
-#endif // defined(OS_POSIX) && !defined(OS_NACL)
+#endif // defined(OS_WIN)
namespace {
// Inject a test point for recording the destructor calls for Closure objects
@@ -825,7 +1963,7 @@ class DestructionObserverProbe :
bool* destruction_observer_called_;
};
-class MLDestructionObserver : public MessageLoop::DestructionObserver {
+class MLDestructionObserver : public MessageLoopCurrent::DestructionObserver {
public:
MLDestructionObserver(bool* task_destroyed, bool* destruction_observer_called)
: task_destroyed_(task_destroyed),
@@ -847,7 +1985,7 @@ class MLDestructionObserver : public MessageLoop::DestructionObserver {
} // namespace
-TEST(MessageLoopTest, DestructionObserverTest) {
+TEST_P(MessageLoopTest, DestructionObserverTest) {
// Verify that the destruction observer gets called at the very end (after
// all the pending tasks have been destroyed).
MessageLoop* loop = new MessageLoop;
@@ -859,9 +1997,10 @@ TEST(MessageLoopTest, DestructionObserverTest) {
MLDestructionObserver observer(&task_destroyed, &destruction_observer_called);
loop->AddDestructionObserver(&observer);
loop->task_runner()->PostDelayedTask(
- FROM_HERE, Bind(&DestructionObserverProbe::Run,
- new DestructionObserverProbe(
- &task_destroyed, &destruction_observer_called)),
+ FROM_HERE,
+ BindOnce(&DestructionObserverProbe::Run,
+ new DestructionObserverProbe(&task_destroyed,
+ &destruction_observer_called)),
kDelay);
delete loop;
EXPECT_TRUE(observer.task_destroyed_before_message_loop());
@@ -873,18 +2012,17 @@ TEST(MessageLoopTest, DestructionObserverTest) {
// Verify that MessageLoop sets ThreadMainTaskRunner::current() and it
// posts tasks on that message loop.
-TEST(MessageLoopTest, ThreadMainTaskRunner) {
+TEST_P(MessageLoopTest, ThreadMainTaskRunner) {
MessageLoop loop;
scoped_refptr<Foo> foo(new Foo());
std::string a("a");
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, Bind(
- &Foo::Test1ConstRef, foo, a));
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&Foo::Test1ConstRef, foo, a));
// Post quit task;
ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- Bind(&MessageLoop::QuitWhenIdle, Unretained(MessageLoop::current())));
+ FROM_HERE, BindOnce(&RunLoop::QuitCurrentWhenIdleDeprecated));
// Now kick things off
RunLoop().Run();
@@ -893,7 +2031,7 @@ TEST(MessageLoopTest, ThreadMainTaskRunner) {
EXPECT_EQ(foo->result(), "a");
}
-TEST(MessageLoopTest, IsType) {
+TEST_P(MessageLoopTest, IsType) {
MessageLoop loop(MessageLoop::TYPE_UI);
EXPECT_TRUE(loop.IsType(MessageLoop::TYPE_UI));
EXPECT_FALSE(loop.IsType(MessageLoop::TYPE_IO));
@@ -905,9 +2043,9 @@ void EmptyFunction() {}
void PostMultipleTasks() {
ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- base::Bind(&EmptyFunction));
+ base::BindOnce(&EmptyFunction));
ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- base::Bind(&EmptyFunction));
+ base::BindOnce(&EmptyFunction));
}
static const int kSignalMsg = WM_USER + 2;
@@ -936,19 +2074,19 @@ LRESULT CALLBACK TestWndProcThunk(HWND hwnd, UINT message,
// that the pump's incoming task queue does not become empty during the
// test.
ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- base::Bind(&PostMultipleTasks));
+ base::BindOnce(&PostMultipleTasks));
// Next, we post a task that posts a windows message to trigger the second
// stage of the test.
ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(&PostWindowsMessage, hwnd));
+ FROM_HERE, base::BindOnce(&PostWindowsMessage, hwnd));
break;
case 2:
// Since we're about to enter a modal loop, tell the message loop that we
// intend to nest tasks.
- MessageLoop::current()->SetNestableTasksAllowed(true);
+ MessageLoopCurrent::Get()->SetNestableTasksAllowed(true);
bool did_run = false;
ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(&EndTest, &did_run, hwnd));
+ FROM_HERE, base::BindOnce(&EndTest, &did_run, hwnd));
// Run a nested windows-style message loop and verify that our task runs. If
// it doesn't, then we'll loop here until the test times out.
MSG msg;
@@ -962,13 +2100,13 @@ LRESULT CALLBACK TestWndProcThunk(HWND hwnd, UINT message,
break;
}
EXPECT_TRUE(did_run);
- MessageLoop::current()->QuitWhenIdle();
+ RunLoop::QuitCurrentWhenIdleDeprecated();
break;
}
return 0;
}
-TEST(MessageLoopTest, AlwaysHaveUserMessageWhenNesting) {
+TEST_P(MessageLoopTest, AlwaysHaveUserMessageWhenNesting) {
MessageLoop loop(MessageLoop::TYPE_UI);
HINSTANCE instance = CURRENT_MODULE();
WNDCLASSEX wc = {0};
@@ -991,7 +2129,7 @@ TEST(MessageLoopTest, AlwaysHaveUserMessageWhenNesting) {
}
#endif // defined(OS_WIN)
-TEST(MessageLoopTest, SetTaskRunner) {
+TEST_P(MessageLoopTest, SetTaskRunner) {
MessageLoop loop;
scoped_refptr<SingleThreadTaskRunner> new_runner(new TestSimpleTaskRunner());
@@ -1000,20 +2138,19 @@ TEST(MessageLoopTest, SetTaskRunner) {
EXPECT_EQ(new_runner, ThreadTaskRunnerHandle::Get());
}
-TEST(MessageLoopTest, OriginalRunnerWorks) {
+TEST_P(MessageLoopTest, OriginalRunnerWorks) {
MessageLoop loop;
scoped_refptr<SingleThreadTaskRunner> new_runner(new TestSimpleTaskRunner());
scoped_refptr<SingleThreadTaskRunner> original_runner(loop.task_runner());
loop.SetTaskRunner(new_runner);
scoped_refptr<Foo> foo(new Foo());
- original_runner->PostTask(FROM_HERE,
- Bind(&Foo::Test1ConstRef, foo, "a"));
+ original_runner->PostTask(FROM_HERE, BindOnce(&Foo::Test1ConstRef, foo, "a"));
RunLoop().RunUntilIdle();
EXPECT_EQ(1, foo->test_count());
}
-TEST(MessageLoopTest, DeleteUnboundLoop) {
+TEST_P(MessageLoopTest, DeleteUnboundLoop) {
// It should be possible to delete an unbound message loop on a thread which
// already has another active loop. This happens when thread creation fails.
MessageLoop loop;
@@ -1024,7 +2161,7 @@ TEST(MessageLoopTest, DeleteUnboundLoop) {
EXPECT_EQ(loop.task_runner(), ThreadTaskRunnerHandle::Get());
}
-TEST(MessageLoopTest, ThreadName) {
+TEST_P(MessageLoopTest, ThreadName) {
{
std::string kThreadName("foo");
MessageLoop loop;
@@ -1040,4 +2177,107 @@ TEST(MessageLoopTest, ThreadName) {
}
}
+// Verify that tasks posted to and code running in the scope of the same
+// MessageLoop access the same SequenceLocalStorage values.
+TEST_P(MessageLoopTest, SequenceLocalStorageSetGet) {
+ MessageLoop loop;
+
+ SequenceLocalStorageSlot<int> slot;
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ BindOnce(&SequenceLocalStorageSlot<int>::Set, Unretained(&slot), 11));
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(
+ [](SequenceLocalStorageSlot<int>* slot) {
+ EXPECT_EQ(slot->Get(), 11);
+ },
+ &slot));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_EQ(slot.Get(), 11);
+}
+
+// Verify that tasks posted to and code running in different MessageLoops access
+// different SequenceLocalStorage values.
+TEST_P(MessageLoopTest, SequenceLocalStorageDifferentMessageLoops) {
+ SequenceLocalStorageSlot<int> slot;
+
+ {
+ MessageLoop loop;
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ BindOnce(&SequenceLocalStorageSlot<int>::Set, Unretained(&slot), 11));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_EQ(slot.Get(), 11);
+ }
+
+ MessageLoop loop;
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(
+ [](SequenceLocalStorageSlot<int>* slot) {
+ EXPECT_NE(slot->Get(), 11);
+ },
+ &slot));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_NE(slot.Get(), 11);
+}
+
+INSTANTIATE_TEST_CASE_P(
+ ,
+ MessageLoopTest,
+ ::testing::Values(TaskSchedulerAvailability::NO_TASK_SCHEDULER
+ // Unsupported in libchrome
+ //, TaskSchedulerAvailability::WITH_TASK_SCHEDULER
+ ),
+ MessageLoopTest::ParamInfoToString);
+
+namespace {
+
+class PostTaskOnDestroy {
+ public:
+ PostTaskOnDestroy(int times) : times_remaining_(times) {}
+ ~PostTaskOnDestroy() { PostTaskWithPostingDestructor(times_remaining_); }
+
+ // Post a task that will repost itself on destruction |times| times.
+ static void PostTaskWithPostingDestructor(int times) {
+ if (times > 0) {
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce([](std::unique_ptr<PostTaskOnDestroy>) {},
+ std::make_unique<PostTaskOnDestroy>(times - 1)));
+ }
+ }
+
+ private:
+ const int times_remaining_;
+
+ DISALLOW_COPY_AND_ASSIGN(PostTaskOnDestroy);
+};
+
+} // namespace
+
+// Test that MessageLoop destruction handles a task's destructor posting another
+// task by:
+// 1) Not getting stuck clearing its task queue.
+// 2) DCHECKing when clearing pending tasks many times still doesn't yield an
+// empty queue.
+TEST(MessageLoopDestructionTest, ExpectDeathWithStubbornPostTaskOnDestroy) {
+ std::unique_ptr<MessageLoop> loop = std::make_unique<MessageLoop>();
+
+ EXPECT_DCHECK_DEATH({
+ PostTaskOnDestroy::PostTaskWithPostingDestructor(1000);
+ loop.reset();
+ });
+}
+
+TEST(MessageLoopDestructionTest, DestroysFineWithReasonablePostTaskOnDestroy) {
+ std::unique_ptr<MessageLoop> loop = std::make_unique<MessageLoop>();
+
+ PostTaskOnDestroy::PostTaskWithPostingDestructor(10);
+ loop.reset();
+}
+
} // namespace base
diff --git a/base/message_loop/message_pump.cc b/base/message_loop/message_pump.cc
index 3d85b9b564..907617624a 100644
--- a/base/message_loop/message_pump.cc
+++ b/base/message_loop/message_pump.cc
@@ -6,11 +6,9 @@
namespace base {
-MessagePump::MessagePump() {
-}
+MessagePump::MessagePump() = default;
-MessagePump::~MessagePump() {
-}
+MessagePump::~MessagePump() = default;
void MessagePump::SetTimerSlack(TimerSlack) {
}
diff --git a/base/message_loop/message_pump.h b/base/message_loop/message_pump.h
index c53be80410..dec0c94f62 100644
--- a/base/message_loop/message_pump.h
+++ b/base/message_loop/message_pump.h
@@ -7,19 +7,19 @@
#include "base/base_export.h"
#include "base/message_loop/timer_slack.h"
-#include "base/threading/non_thread_safe.h"
+#include "base/sequence_checker.h"
namespace base {
class TimeTicks;
-class BASE_EXPORT MessagePump : public NonThreadSafe {
+class BASE_EXPORT MessagePump {
public:
// Please see the comments above the Run method for an illustration of how
// these delegate methods are used.
class BASE_EXPORT Delegate {
public:
- virtual ~Delegate() {}
+ virtual ~Delegate() = default;
// Called from within Run in response to ScheduleWork or when the message
// pump would otherwise call DoDelayedWork. Returns true to indicate that
diff --git a/base/message_loop/message_pump_default.cc b/base/message_loop/message_pump_default.cc
index cf68270c56..4104e73465 100644
--- a/base/message_loop/message_pump_default.cc
+++ b/base/message_loop/message_pump_default.cc
@@ -4,11 +4,16 @@
#include "base/message_loop/message_pump_default.h"
+#include "base/auto_reset.h"
#include "base/logging.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#if defined(OS_MACOSX)
+#include <mach/thread_policy.h>
+
+#include "base/mac/mach_logging.h"
+#include "base/mac/scoped_mach_port.h"
#include "base/mac/scoped_nsautorelease_pool.h"
#endif
@@ -19,11 +24,10 @@ MessagePumpDefault::MessagePumpDefault()
event_(WaitableEvent::ResetPolicy::AUTOMATIC,
WaitableEvent::InitialState::NOT_SIGNALED) {}
-MessagePumpDefault::~MessagePumpDefault() {
-}
+MessagePumpDefault::~MessagePumpDefault() = default;
void MessagePumpDefault::Run(Delegate* delegate) {
- DCHECK(keep_running_) << "Quit must have been called outside of Run!";
+ AutoReset<bool> auto_reset_keep_running(&keep_running_, true);
for (;;) {
#if defined(OS_MACOSX)
@@ -61,8 +65,6 @@ void MessagePumpDefault::Run(Delegate* delegate) {
// Since event_ is auto-reset, we don't need to do anything special here
// other than service each delegate method.
}
-
- keep_running_ = true;
}
void MessagePumpDefault::Quit() {
@@ -83,4 +85,19 @@ void MessagePumpDefault::ScheduleDelayedWork(
delayed_work_time_ = delayed_work_time;
}
+#if defined(OS_MACOSX)
+void MessagePumpDefault::SetTimerSlack(TimerSlack timer_slack) {
+ thread_latency_qos_policy_data_t policy{};
+ policy.thread_latency_qos_tier = timer_slack == TIMER_SLACK_MAXIMUM
+ ? LATENCY_QOS_TIER_3
+ : LATENCY_QOS_TIER_UNSPECIFIED;
+ mac::ScopedMachSendRight thread_port(mach_thread_self());
+ kern_return_t kr =
+ thread_policy_set(thread_port.get(), THREAD_LATENCY_QOS_POLICY,
+ reinterpret_cast<thread_policy_t>(&policy),
+ THREAD_LATENCY_QOS_POLICY_COUNT);
+ MACH_DVLOG_IF(1, kr != KERN_SUCCESS, kr) << "thread_policy_set";
+}
+#endif
+
} // namespace base
diff --git a/base/message_loop/message_pump_default.h b/base/message_loop/message_pump_default.h
index 4cd7cd17d5..dd11adcb6c 100644
--- a/base/message_loop/message_pump_default.h
+++ b/base/message_loop/message_pump_default.h
@@ -10,6 +10,7 @@
#include "base/message_loop/message_pump.h"
#include "base/synchronization/waitable_event.h"
#include "base/time/time.h"
+#include "build/build_config.h"
namespace base {
@@ -23,6 +24,9 @@ class BASE_EXPORT MessagePumpDefault : public MessagePump {
void Quit() override;
void ScheduleWork() override;
void ScheduleDelayedWork(const TimeTicks& delayed_work_time) override;
+#if defined(OS_MACOSX)
+ void SetTimerSlack(TimerSlack timer_slack) override;
+#endif
private:
// This flag is set to false when Run should return.
diff --git a/base/message_loop/message_pump_for_io.h b/base/message_loop/message_pump_for_io.h
new file mode 100644
index 0000000000..6aac1e609f
--- /dev/null
+++ b/base/message_loop/message_pump_for_io.h
@@ -0,0 +1,44 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MESSAGE_LOOP_MESSAGE_PUMP_FOR_IO_H_
+#define BASE_MESSAGE_LOOP_MESSAGE_PUMP_FOR_IO_H_
+
+// This header is a forwarding header to coalesce the various platform specific
+// types representing MessagePumpForIO.
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include "base/message_loop/message_pump_win.h"
+#elif defined(OS_IOS)
+#include "base/message_loop/message_pump_io_ios.h"
+#elif defined(OS_NACL_SFI)
+#include "base/message_loop/message_pump_default.h"
+#elif defined(OS_FUCHSIA)
+#include "base/message_loop/message_pump_fuchsia.h"
+#elif defined(OS_POSIX)
+#include "base/message_loop/message_pump_libevent.h"
+#endif
+
+namespace base {
+
+#if defined(OS_WIN)
+// Windows defines it as-is.
+using MessagePumpForIO = MessagePumpForIO;
+#elif defined(OS_IOS)
+using MessagePumpForIO = MessagePumpIOSForIO;
+#elif defined(OS_NACL_SFI)
+using MessagePumpForIO = MessagePumpDefault;
+#elif defined(OS_FUCHSIA)
+using MessagePumpForIO = MessagePumpFuchsia;
+#elif defined(OS_POSIX)
+using MessagePumpForIO = MessagePumpLibevent;
+#else
+#error Platform does not define MessagePumpForIO
+#endif
+
+} // namespace base
+
+#endif // BASE_MESSAGE_LOOP_MESSAGE_PUMP_FOR_IO_H_
diff --git a/base/message_loop/message_pump_for_ui.h b/base/message_loop/message_pump_for_ui.h
new file mode 100644
index 0000000000..6614f3dfbf
--- /dev/null
+++ b/base/message_loop/message_pump_for_ui.h
@@ -0,0 +1,57 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MESSAGE_LOOP_MESSAGE_PUMP_FOR_UI_H_
+#define BASE_MESSAGE_LOOP_MESSAGE_PUMP_FOR_UI_H_
+
+// This header is a forwarding header to coalesce the various platform specific
+// implementations of MessagePumpForUI.
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include "base/message_loop/message_pump_win.h"
+#elif defined(OS_ANDROID)
+#include "base/message_loop/message_pump_android.h"
+#elif defined(OS_MACOSX)
+#include "base/message_loop/message_pump.h"
+#elif defined(OS_NACL) || defined(OS_AIX)
+// No MessagePumpForUI, see below.
+#elif defined(USE_GLIB) && !defined(ANDROID)
+#include "base/message_loop/message_pump_glib.h"
+#elif defined(OS_LINUX) || defined(OS_BSD)|| defined(ANDROID)
+#include "base/message_loop/message_pump_libevent.h"
+#elif defined(OS_FUCHSIA)
+#include "base/message_loop/message_pump_fuchsia.h"
+#endif
+
+namespace base {
+
+#if defined(OS_WIN)
+// Windows defines it as-is.
+using MessagePumpForUI = MessagePumpForUI;
+#elif defined(OS_ANDROID)
+// Android defines it as-is.
+using MessagePumpForUI = MessagePumpForUI;
+#elif defined(OS_MACOSX)
+// MessagePumpForUI isn't bound to a specific impl on Mac. While each impl can
+// be represented by a plain MessagePump: MessagePumpMac::Create() must be used
+// to instantiate the right impl.
+using MessagePumpForUI = MessagePump;
+#elif defined(OS_NACL) || defined(OS_AIX)
+// Currently NaCl and AIX don't have a MessagePumpForUI.
+// TODO(abarth): Figure out if we need this.
+#elif defined(USE_GLIB) && !defined(ANDROID)
+using MessagePumpForUI = MessagePumpGlib;
+#elif defined(OS_LINUX) || defined(OS_BSD) || defined(ANDROID)
+using MessagePumpForUI = MessagePumpLibevent;
+#elif defined(OS_FUCHSIA)
+using MessagePumpForUI = MessagePumpFuchsia;
+#else
+#error Platform does not define MessagePumpForUI
+#endif
+
+} // namespace base
+
+#endif // BASE_MESSAGE_LOOP_MESSAGE_PUMP_FOR_UI_H_
diff --git a/base/message_loop/message_pump_glib.cc b/base/message_loop/message_pump_glib.cc
index fd23745f4e..2f1909b84b 100644
--- a/base/message_loop/message_pump_glib.cc
+++ b/base/message_loop/message_pump_glib.cc
@@ -112,12 +112,8 @@ gboolean WorkSourceDispatch(GSource* source,
}
// I wish these could be const, but g_source_new wants non-const.
-GSourceFuncs WorkSourceFuncs = {
- WorkSourcePrepare,
- WorkSourceCheck,
- WorkSourceDispatch,
- NULL
-};
+GSourceFuncs WorkSourceFuncs = {WorkSourcePrepare, WorkSourceCheck,
+ WorkSourceDispatch, nullptr};
// The following is used to make sure we only run the MessagePumpGlib on one
// thread. X only has one message pump so we can only have one UI loop per
@@ -180,7 +176,7 @@ struct MessagePumpGlib::RunState {
};
MessagePumpGlib::MessagePumpGlib()
- : state_(NULL),
+ : state_(nullptr),
context_(g_main_context_default()),
wakeup_gpollfd_(new GPollFD) {
// Create our wakeup pipe, which is used to flag when work was scheduled.
diff --git a/base/message_loop/message_pump_glib_unittest.cc b/base/message_loop/message_pump_glib_unittest.cc
index 607d3c93d6..512cea6312 100644
--- a/base/message_loop/message_pump_glib_unittest.cc
+++ b/base/message_loop/message_pump_glib_unittest.cc
@@ -16,6 +16,7 @@
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_current.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread.h"
@@ -33,7 +34,7 @@ class EventInjector {
EventInjector() : processed_events_(0) {
source_ = static_cast<Source*>(g_source_new(&SourceFuncs, sizeof(Source)));
source_->injector = this;
- g_source_attach(source_, NULL);
+ g_source_attach(source_, nullptr);
g_source_set_can_recurse(source_, TRUE);
}
@@ -59,27 +60,27 @@ class EventInjector {
void HandleDispatch() {
if (events_.empty())
return;
- Event event = events_[0];
+ Event event = std::move(events_[0]);
events_.erase(events_.begin());
++processed_events_;
if (!event.callback.is_null())
- event.callback.Run();
+ std::move(event.callback).Run();
else if (!event.task.is_null())
- event.task.Run();
+ std::move(event.task).Run();
}
// Adds an event to the queue. When "handled", executes |callback|.
// delay_ms is relative to the last event if any, or to Now() otherwise.
- void AddEvent(int delay_ms, const Closure& callback) {
- AddEventHelper(delay_ms, callback, Closure());
+ void AddEvent(int delay_ms, OnceClosure callback) {
+ AddEventHelper(delay_ms, std::move(callback), OnceClosure());
}
void AddDummyEvent(int delay_ms) {
- AddEventHelper(delay_ms, Closure(), Closure());
+ AddEventHelper(delay_ms, OnceClosure(), OnceClosure());
}
- void AddEventAsTask(int delay_ms, const Closure& task) {
- AddEventHelper(delay_ms, Closure(), task);
+ void AddEventAsTask(int delay_ms, OnceClosure task) {
+ AddEventHelper(delay_ms, OnceClosure(), std::move(task));
}
void Reset() {
@@ -92,16 +93,15 @@ class EventInjector {
private:
struct Event {
Time time;
- Closure callback;
- Closure task;
+ OnceClosure callback;
+ OnceClosure task;
};
struct Source : public GSource {
EventInjector* injector;
};
- void AddEventHelper(
- int delay_ms, const Closure& callback, const Closure& task) {
+ void AddEventHelper(int delay_ms, OnceClosure callback, OnceClosure task) {
Time last_time;
if (!events_.empty())
last_time = (events_.end()-1)->time;
@@ -109,8 +109,8 @@ class EventInjector {
last_time = Time::NowFromSystemTime();
Time future = last_time + TimeDelta::FromMilliseconds(delay_ms);
- EventInjector::Event event = {future, callback, task};
- events_.push_back(event);
+ EventInjector::Event event = {future, std::move(callback), std::move(task)};
+ events_.push_back(std::move(event));
}
static gboolean Prepare(GSource* source, gint* timeout_ms) {
@@ -136,12 +136,9 @@ class EventInjector {
DISALLOW_COPY_AND_ASSIGN(EventInjector);
};
-GSourceFuncs EventInjector::SourceFuncs = {
- EventInjector::Prepare,
- EventInjector::Check,
- EventInjector::Dispatch,
- NULL
-};
+GSourceFuncs EventInjector::SourceFuncs = {EventInjector::Prepare,
+ EventInjector::Check,
+ EventInjector::Dispatch, nullptr};
void IncrementInt(int *value) {
++*value;
@@ -153,15 +150,14 @@ void ExpectProcessedEvents(EventInjector* injector, int count) {
}
// Posts a task on the current message loop.
-void PostMessageLoopTask(const tracked_objects::Location& from_here,
- const Closure& task) {
- ThreadTaskRunnerHandle::Get()->PostTask(from_here, task);
+void PostMessageLoopTask(const Location& from_here, OnceClosure task) {
+ ThreadTaskRunnerHandle::Get()->PostTask(from_here, std::move(task));
}
// Test fixture.
class MessagePumpGLibTest : public testing::Test {
public:
- MessagePumpGLibTest() : loop_(NULL), injector_(NULL) { }
+ MessagePumpGLibTest() : loop_(nullptr), injector_(nullptr) {}
// Overridden from testing::Test:
void SetUp() override {
@@ -170,9 +166,9 @@ class MessagePumpGLibTest : public testing::Test {
}
void TearDown() override {
delete injector_;
- injector_ = NULL;
+ injector_ = nullptr;
delete loop_;
- loop_ = NULL;
+ loop_ = nullptr;
}
MessageLoop* loop() const { return loop_; }
@@ -195,8 +191,9 @@ TEST_F(MessagePumpGLibTest, TestQuit) {
injector()->Reset();
// Quit from an event
- injector()->AddEvent(0, MessageLoop::QuitWhenIdleClosure());
- RunLoop().Run();
+ RunLoop run_loop;
+ injector()->AddEvent(0, run_loop.QuitClosure());
+ run_loop.Run();
EXPECT_EQ(1, injector()->processed_events());
}
@@ -208,26 +205,32 @@ TEST_F(MessagePumpGLibTest, TestEventTaskInterleave) {
// If changes cause this test to fail, it is reasonable to change it, but
// TestWorkWhileWaitingForEvents and TestEventsWhileWaitingForWork have to be
// changed accordingly, otherwise they can become flaky.
- injector()->AddEventAsTask(0, Bind(&DoNothing));
- Closure check_task =
- Bind(&ExpectProcessedEvents, Unretained(injector()), 2);
- Closure posted_task =
- Bind(&PostMessageLoopTask, FROM_HERE, check_task);
- injector()->AddEventAsTask(0, posted_task);
- injector()->AddEventAsTask(0, Bind(&DoNothing));
- injector()->AddEvent(0, MessageLoop::QuitWhenIdleClosure());
- RunLoop().Run();
+ injector()->AddEventAsTask(0, DoNothing());
+ OnceClosure check_task =
+ BindOnce(&ExpectProcessedEvents, Unretained(injector()), 2);
+ OnceClosure posted_task =
+ BindOnce(&PostMessageLoopTask, FROM_HERE, std::move(check_task));
+ injector()->AddEventAsTask(0, std::move(posted_task));
+ injector()->AddEventAsTask(0, DoNothing());
+ {
+ RunLoop run_loop;
+ injector()->AddEvent(0, run_loop.QuitClosure());
+ run_loop.Run();
+ }
EXPECT_EQ(4, injector()->processed_events());
injector()->Reset();
- injector()->AddEventAsTask(0, Bind(&DoNothing));
- check_task =
- Bind(&ExpectProcessedEvents, Unretained(injector()), 2);
- posted_task = Bind(&PostMessageLoopTask, FROM_HERE, check_task);
- injector()->AddEventAsTask(0, posted_task);
- injector()->AddEventAsTask(10, Bind(&DoNothing));
- injector()->AddEvent(0, MessageLoop::QuitWhenIdleClosure());
- RunLoop().Run();
+ injector()->AddEventAsTask(0, DoNothing());
+ check_task = BindOnce(&ExpectProcessedEvents, Unretained(injector()), 2);
+ posted_task =
+ BindOnce(&PostMessageLoopTask, FROM_HERE, std::move(check_task));
+ injector()->AddEventAsTask(0, std::move(posted_task));
+ injector()->AddEventAsTask(10, DoNothing());
+ {
+ RunLoop run_loop;
+ injector()->AddEvent(0, run_loop.QuitClosure());
+ run_loop.Run();
+ }
EXPECT_EQ(4, injector()->processed_events());
}
@@ -237,14 +240,17 @@ TEST_F(MessagePumpGLibTest, TestWorkWhileWaitingForEvents) {
// The event queue is empty at first.
for (int i = 0; i < 10; ++i) {
loop()->task_runner()->PostTask(FROM_HERE,
- Bind(&IncrementInt, &task_count));
+ BindOnce(&IncrementInt, &task_count));
}
// After all the previous tasks have executed, enqueue an event that will
// quit.
- loop()->task_runner()->PostTask(
- FROM_HERE, Bind(&EventInjector::AddEvent, Unretained(injector()), 0,
- MessageLoop::QuitWhenIdleClosure()));
- RunLoop().Run();
+ {
+ RunLoop run_loop;
+ loop()->task_runner()->PostTask(
+ FROM_HERE, BindOnce(&EventInjector::AddEvent, Unretained(injector()), 0,
+ run_loop.QuitClosure()));
+ run_loop.Run();
+ }
ASSERT_EQ(10, task_count);
EXPECT_EQ(1, injector()->processed_events());
@@ -253,18 +259,22 @@ TEST_F(MessagePumpGLibTest, TestWorkWhileWaitingForEvents) {
task_count = 0;
for (int i = 0; i < 10; ++i) {
loop()->task_runner()->PostDelayedTask(FROM_HERE,
- Bind(&IncrementInt, &task_count),
+ BindOnce(&IncrementInt, &task_count),
TimeDelta::FromMilliseconds(10 * i));
}
// After all the previous tasks have executed, enqueue an event that will
// quit.
// This relies on the fact that delayed tasks are executed in delay order.
// That is verified in message_loop_unittest.cc.
- loop()->task_runner()->PostDelayedTask(
- FROM_HERE, Bind(&EventInjector::AddEvent, Unretained(injector()), 10,
- MessageLoop::QuitWhenIdleClosure()),
- TimeDelta::FromMilliseconds(150));
- RunLoop().Run();
+ {
+ RunLoop run_loop;
+ loop()->task_runner()->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&EventInjector::AddEvent, Unretained(injector()), 0,
+ run_loop.QuitClosure()),
+ TimeDelta::FromMilliseconds(150));
+ run_loop.Run();
+ }
ASSERT_EQ(10, task_count);
EXPECT_EQ(1, injector()->processed_events());
}
@@ -278,15 +288,16 @@ TEST_F(MessagePumpGLibTest, TestEventsWhileWaitingForWork) {
// After all the events have been processed, post a task that will check that
// the events have been processed (note: the task executes after the event
// that posted it has been handled, so we expect 11 at that point).
- Closure check_task =
- Bind(&ExpectProcessedEvents, Unretained(injector()), 11);
- Closure posted_task =
- Bind(&PostMessageLoopTask, FROM_HERE, check_task);
- injector()->AddEventAsTask(10, posted_task);
+ OnceClosure check_task =
+ BindOnce(&ExpectProcessedEvents, Unretained(injector()), 11);
+ OnceClosure posted_task =
+ BindOnce(&PostMessageLoopTask, FROM_HERE, std::move(check_task));
+ injector()->AddEventAsTask(10, std::move(posted_task));
// And then quit (relies on the condition tested by TestEventTaskInterleave).
- injector()->AddEvent(10, MessageLoop::QuitWhenIdleClosure());
- RunLoop().Run();
+ RunLoop run_loop;
+ injector()->AddEvent(10, run_loop.QuitClosure());
+ run_loop.Run();
EXPECT_EQ(12, injector()->processed_events());
}
@@ -298,21 +309,21 @@ namespace {
// while making sure there is always work to do and events in the queue.
class ConcurrentHelper : public RefCounted<ConcurrentHelper> {
public:
- explicit ConcurrentHelper(EventInjector* injector)
+ ConcurrentHelper(EventInjector* injector, OnceClosure done_closure)
: injector_(injector),
+ done_closure_(std::move(done_closure)),
event_count_(kStartingEventCount),
- task_count_(kStartingTaskCount) {
- }
+ task_count_(kStartingTaskCount) {}
void FromTask() {
if (task_count_ > 0) {
--task_count_;
}
if (task_count_ == 0 && event_count_ == 0) {
- MessageLoop::current()->QuitWhenIdle();
+ std::move(done_closure_).Run();
} else {
ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, Bind(&ConcurrentHelper::FromTask, this));
+ FROM_HERE, BindOnce(&ConcurrentHelper::FromTask, this));
}
}
@@ -321,10 +332,10 @@ class ConcurrentHelper : public RefCounted<ConcurrentHelper> {
--event_count_;
}
if (task_count_ == 0 && event_count_ == 0) {
- MessageLoop::current()->QuitWhenIdle();
+ std::move(done_closure_).Run();
} else {
- injector_->AddEventAsTask(
- 0, Bind(&ConcurrentHelper::FromEvent, this));
+ injector_->AddEventAsTask(0,
+ BindOnce(&ConcurrentHelper::FromEvent, this));
}
}
@@ -340,6 +351,7 @@ class ConcurrentHelper : public RefCounted<ConcurrentHelper> {
static const int kStartingTaskCount = 20;
EventInjector* injector_;
+ OnceClosure done_closure_;
int event_count_;
int task_count_;
};
@@ -352,42 +364,42 @@ TEST_F(MessagePumpGLibTest, TestConcurrentEventPostedTask) {
// full, the helper verifies that both tasks and events get processed.
// If that is not the case, either event_count_ or task_count_ will not get
// to 0, and MessageLoop::QuitWhenIdle() will never be called.
- scoped_refptr<ConcurrentHelper> helper = new ConcurrentHelper(injector());
+ RunLoop run_loop;
+ scoped_refptr<ConcurrentHelper> helper =
+ new ConcurrentHelper(injector(), run_loop.QuitClosure());
// Add 2 events to the queue to make sure it is always full (when we remove
// the event before processing it).
- injector()->AddEventAsTask(
- 0, Bind(&ConcurrentHelper::FromEvent, helper));
- injector()->AddEventAsTask(
- 0, Bind(&ConcurrentHelper::FromEvent, helper));
+ injector()->AddEventAsTask(0, BindOnce(&ConcurrentHelper::FromEvent, helper));
+ injector()->AddEventAsTask(0, BindOnce(&ConcurrentHelper::FromEvent, helper));
// Similarly post 2 tasks.
loop()->task_runner()->PostTask(
- FROM_HERE, Bind(&ConcurrentHelper::FromTask, helper));
+ FROM_HERE, BindOnce(&ConcurrentHelper::FromTask, helper));
loop()->task_runner()->PostTask(
- FROM_HERE, Bind(&ConcurrentHelper::FromTask, helper));
+ FROM_HERE, BindOnce(&ConcurrentHelper::FromTask, helper));
- RunLoop().Run();
+ run_loop.Run();
EXPECT_EQ(0, helper->event_count());
EXPECT_EQ(0, helper->task_count());
}
namespace {
-void AddEventsAndDrainGLib(EventInjector* injector) {
+void AddEventsAndDrainGLib(EventInjector* injector, OnceClosure on_drained) {
// Add a couple of dummy events
injector->AddDummyEvent(0);
injector->AddDummyEvent(0);
// Then add an event that will quit the main loop.
- injector->AddEvent(0, MessageLoop::QuitWhenIdleClosure());
+ injector->AddEvent(0, std::move(on_drained));
// Post a couple of dummy tasks
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, Bind(&DoNothing));
- ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, Bind(&DoNothing));
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, DoNothing());
+ ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, DoNothing());
// Drain the events
- while (g_main_context_pending(NULL)) {
- g_main_context_iteration(NULL, FALSE);
+ while (g_main_context_pending(nullptr)) {
+ g_main_context_iteration(nullptr, FALSE);
}
}
@@ -395,9 +407,11 @@ void AddEventsAndDrainGLib(EventInjector* injector) {
TEST_F(MessagePumpGLibTest, TestDrainingGLib) {
// Tests that draining events using GLib works.
+ RunLoop run_loop;
loop()->task_runner()->PostTask(
- FROM_HERE, Bind(&AddEventsAndDrainGLib, Unretained(injector())));
- RunLoop().Run();
+ FROM_HERE, BindOnce(&AddEventsAndDrainGLib, Unretained(injector()),
+ run_loop.QuitClosure()));
+ run_loop.Run();
EXPECT_EQ(3, injector()->processed_events());
}
@@ -411,13 +425,13 @@ class GLibLoopRunner : public RefCounted<GLibLoopRunner> {
void RunGLib() {
while (!quit_) {
- g_main_context_iteration(NULL, TRUE);
+ g_main_context_iteration(nullptr, TRUE);
}
}
void RunLoop() {
while (!quit_) {
- g_main_context_iteration(NULL, TRUE);
+ g_main_context_iteration(nullptr, TRUE);
}
}
@@ -437,9 +451,9 @@ class GLibLoopRunner : public RefCounted<GLibLoopRunner> {
bool quit_;
};
-void TestGLibLoopInternal(EventInjector* injector) {
+void TestGLibLoopInternal(EventInjector* injector, OnceClosure done) {
// Allow tasks to be processed from 'native' event loops.
- MessageLoop::current()->SetNestableTasksAllowed(true);
+ MessageLoopCurrent::Get()->SetNestableTasksAllowed(true);
scoped_refptr<GLibLoopRunner> runner = new GLibLoopRunner();
int task_count = 0;
@@ -448,18 +462,18 @@ void TestGLibLoopInternal(EventInjector* injector) {
injector->AddDummyEvent(0);
// Post a couple of dummy tasks
ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&IncrementInt, &task_count));
+ BindOnce(&IncrementInt, &task_count));
ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&IncrementInt, &task_count));
+ BindOnce(&IncrementInt, &task_count));
// Delayed events
injector->AddDummyEvent(10);
injector->AddDummyEvent(10);
// Delayed work
ThreadTaskRunnerHandle::Get()->PostDelayedTask(
- FROM_HERE, Bind(&IncrementInt, &task_count),
+ FROM_HERE, BindOnce(&IncrementInt, &task_count),
TimeDelta::FromMilliseconds(30));
ThreadTaskRunnerHandle::Get()->PostDelayedTask(
- FROM_HERE, Bind(&GLibLoopRunner::Quit, runner),
+ FROM_HERE, BindOnce(&GLibLoopRunner::Quit, runner),
TimeDelta::FromMilliseconds(40));
// Run a nested, straight GLib message loop.
@@ -467,12 +481,12 @@ void TestGLibLoopInternal(EventInjector* injector) {
ASSERT_EQ(3, task_count);
EXPECT_EQ(4, injector->processed_events());
- MessageLoop::current()->QuitWhenIdle();
+ std::move(done).Run();
}
-void TestGtkLoopInternal(EventInjector* injector) {
+void TestGtkLoopInternal(EventInjector* injector, OnceClosure done) {
// Allow tasks to be processed from 'native' event loops.
- MessageLoop::current()->SetNestableTasksAllowed(true);
+ MessageLoopCurrent::Get()->SetNestableTasksAllowed(true);
scoped_refptr<GLibLoopRunner> runner = new GLibLoopRunner();
int task_count = 0;
@@ -481,18 +495,18 @@ void TestGtkLoopInternal(EventInjector* injector) {
injector->AddDummyEvent(0);
// Post a couple of dummy tasks
ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&IncrementInt, &task_count));
+ BindOnce(&IncrementInt, &task_count));
ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- Bind(&IncrementInt, &task_count));
+ BindOnce(&IncrementInt, &task_count));
// Delayed events
injector->AddDummyEvent(10);
injector->AddDummyEvent(10);
// Delayed work
ThreadTaskRunnerHandle::Get()->PostDelayedTask(
- FROM_HERE, Bind(&IncrementInt, &task_count),
+ FROM_HERE, BindOnce(&IncrementInt, &task_count),
TimeDelta::FromMilliseconds(30));
ThreadTaskRunnerHandle::Get()->PostDelayedTask(
- FROM_HERE, Bind(&GLibLoopRunner::Quit, runner),
+ FROM_HERE, BindOnce(&GLibLoopRunner::Quit, runner),
TimeDelta::FromMilliseconds(40));
// Run a nested, straight Gtk message loop.
@@ -500,7 +514,7 @@ void TestGtkLoopInternal(EventInjector* injector) {
ASSERT_EQ(3, task_count);
EXPECT_EQ(4, injector->processed_events());
- MessageLoop::current()->QuitWhenIdle();
+ std::move(done).Run();
}
} // namespace
@@ -510,9 +524,11 @@ TEST_F(MessagePumpGLibTest, TestGLibLoop) {
// loop is not run by MessageLoop::Run() but by a straight GLib loop.
// Note that in this case we don't make strong guarantees about niceness
// between events and posted tasks.
+ RunLoop run_loop;
loop()->task_runner()->PostTask(
- FROM_HERE, Bind(&TestGLibLoopInternal, Unretained(injector())));
- RunLoop().Run();
+ FROM_HERE, BindOnce(&TestGLibLoopInternal, Unretained(injector()),
+ run_loop.QuitClosure()));
+ run_loop.Run();
}
TEST_F(MessagePumpGLibTest, TestGtkLoop) {
@@ -520,9 +536,11 @@ TEST_F(MessagePumpGLibTest, TestGtkLoop) {
// loop is not run by MessageLoop::Run() but by a straight Gtk loop.
// Note that in this case we don't make strong guarantees about niceness
// between events and posted tasks.
+ RunLoop run_loop;
loop()->task_runner()->PostTask(
- FROM_HERE, Bind(&TestGtkLoopInternal, Unretained(injector())));
- RunLoop().Run();
+ FROM_HERE, BindOnce(&TestGtkLoopInternal, Unretained(injector()),
+ run_loop.QuitClosure()));
+ run_loop.Run();
}
} // namespace base
diff --git a/base/message_loop/message_pump_libevent.cc b/base/message_loop/message_pump_libevent.cc
index 48cb98a330..2a595e5fe5 100644
--- a/base/message_loop/message_pump_libevent.cc
+++ b/base/message_loop/message_pump_libevent.cc
@@ -7,7 +7,7 @@
#include <errno.h>
#include <unistd.h>
-#include <memory>
+#include <utility>
#include "base/auto_reset.h"
#include "base/compiler_specific.h"
@@ -29,29 +29,25 @@
// struct event (of which there is roughly one per socket).
// The socket's struct event is created in
// MessagePumpLibevent::WatchFileDescriptor(),
-// is owned by the FileDescriptorWatcher, and is destroyed in
+// is owned by the FdWatchController, and is destroyed in
// StopWatchingFileDescriptor().
// It is moved into and out of lists in struct event_base by
// the libevent functions event_add() and event_del().
//
// TODO(dkegel):
-// At the moment bad things happen if a FileDescriptorWatcher
+// At the moment bad things happen if a FdWatchController
// is active after its MessagePumpLibevent has been destroyed.
-// See MessageLoopTest.FileDescriptorWatcherOutlivesMessageLoop
+// See MessageLoopTest.FdWatchControllerOutlivesMessageLoop
// Not clear yet whether that situation occurs in practice,
// but if it does, we need to fix it.
namespace base {
-MessagePumpLibevent::FileDescriptorWatcher::FileDescriptorWatcher(
- const tracked_objects::Location& from_here)
- : event_(NULL),
- pump_(NULL),
- watcher_(NULL),
- was_destroyed_(NULL),
- created_from_location_(from_here) {}
+MessagePumpLibevent::FdWatchController::FdWatchController(
+ const Location& from_here)
+ : FdWatchControllerInterface(from_here) {}
-MessagePumpLibevent::FileDescriptorWatcher::~FileDescriptorWatcher() {
+MessagePumpLibevent::FdWatchController::~FdWatchController() {
if (event_) {
StopWatchingFileDescriptor();
}
@@ -61,33 +57,30 @@ MessagePumpLibevent::FileDescriptorWatcher::~FileDescriptorWatcher() {
}
}
-bool MessagePumpLibevent::FileDescriptorWatcher::StopWatchingFileDescriptor() {
- event* e = ReleaseEvent();
- if (e == NULL)
+bool MessagePumpLibevent::FdWatchController::StopWatchingFileDescriptor() {
+ std::unique_ptr<event> e = ReleaseEvent();
+ if (!e)
return true;
// event_del() is a no-op if the event isn't active.
- int rv = event_del(e);
- delete e;
- pump_ = NULL;
- watcher_ = NULL;
+ int rv = event_del(e.get());
+ pump_ = nullptr;
+ watcher_ = nullptr;
return (rv == 0);
}
-void MessagePumpLibevent::FileDescriptorWatcher::Init(event* e) {
+void MessagePumpLibevent::FdWatchController::Init(std::unique_ptr<event> e) {
DCHECK(e);
DCHECK(!event_);
- event_ = e;
+ event_ = std::move(e);
}
-event* MessagePumpLibevent::FileDescriptorWatcher::ReleaseEvent() {
- struct event* e = event_;
- event_ = NULL;
- return e;
+std::unique_ptr<event> MessagePumpLibevent::FdWatchController::ReleaseEvent() {
+ return std::move(event_);
}
-void MessagePumpLibevent::FileDescriptorWatcher::OnFileCanReadWithoutBlocking(
+void MessagePumpLibevent::FdWatchController::OnFileCanReadWithoutBlocking(
int fd,
MessagePumpLibevent* pump) {
// Since OnFileCanWriteWithoutBlocking() gets called first, it can stop
@@ -97,7 +90,7 @@ void MessagePumpLibevent::FileDescriptorWatcher::OnFileCanReadWithoutBlocking(
watcher_->OnFileCanReadWithoutBlocking(fd);
}
-void MessagePumpLibevent::FileDescriptorWatcher::OnFileCanWriteWithoutBlocking(
+void MessagePumpLibevent::FdWatchController::OnFileCanWriteWithoutBlocking(
int fd,
MessagePumpLibevent* pump) {
DCHECK(watcher_);
@@ -134,8 +127,8 @@ MessagePumpLibevent::~MessagePumpLibevent() {
bool MessagePumpLibevent::WatchFileDescriptor(int fd,
bool persistent,
int mode,
- FileDescriptorWatcher* controller,
- Watcher* delegate) {
+ FdWatchController* controller,
+ FdWatcher* delegate) {
DCHECK_GE(fd, 0);
DCHECK(controller);
DCHECK(delegate);
@@ -153,13 +146,12 @@ bool MessagePumpLibevent::WatchFileDescriptor(int fd,
}
std::unique_ptr<event> evt(controller->ReleaseEvent());
- if (evt.get() == NULL) {
+ if (!evt) {
// Ownership is transferred to the controller.
evt.reset(new event);
} else {
// Make sure we don't pick up any funky internal libevent masks.
- int old_interest_mask = evt.get()->ev_events &
- (EV_READ | EV_WRITE | EV_PERSIST);
+ int old_interest_mask = evt->ev_events & (EV_READ | EV_WRITE | EV_PERSIST);
// Combine old/new event masks.
event_mask |= old_interest_mask;
@@ -180,20 +172,19 @@ bool MessagePumpLibevent::WatchFileDescriptor(int fd,
// Tell libevent which message pump this socket will belong to when we add it.
if (event_base_set(event_base_, evt.get())) {
+ DPLOG(ERROR) << "event_base_set(fd=" << EVENT_FD(evt.get()) << ")";
return false;
}
// Add this socket to the list of monitored sockets.
- if (event_add(evt.get(), NULL)) {
+ if (event_add(evt.get(), nullptr)) {
+ DPLOG(ERROR) << "event_add failed(fd=" << EVENT_FD(evt.get()) << ")";
return false;
}
- // Transfer ownership of evt to controller.
- controller->Init(evt.release());
-
+ controller->Init(std::move(evt));
controller->set_watcher(delegate);
controller->set_pump(this);
-
return true;
}
@@ -304,7 +295,7 @@ bool MessagePumpLibevent::Init() {
OnWakeup, this);
event_base_set(event_base_, wakeup_event_);
- if (event_add(wakeup_event_, 0))
+ if (event_add(wakeup_event_, nullptr))
return false;
return true;
}
@@ -313,8 +304,7 @@ bool MessagePumpLibevent::Init() {
void MessagePumpLibevent::OnLibeventNotification(int fd,
short flags,
void* context) {
- FileDescriptorWatcher* controller =
- static_cast<FileDescriptorWatcher*>(context);
+ FdWatchController* controller = static_cast<FdWatchController*>(context);
DCHECK(controller);
TRACE_EVENT2("toplevel", "MessagePumpLibevent::OnLibeventNotification",
"src_file", controller->created_from_location().file_name(),
diff --git a/base/message_loop/message_pump_libevent.h b/base/message_loop/message_pump_libevent.h
index 1124560d66..002c36cb77 100644
--- a/base/message_loop/message_pump_libevent.h
+++ b/base/message_loop/message_pump_libevent.h
@@ -5,10 +5,12 @@
#ifndef BASE_MESSAGE_LOOP_MESSAGE_PUMP_LIBEVENT_H_
#define BASE_MESSAGE_LOOP_MESSAGE_PUMP_LIBEVENT_H_
+#include <memory>
+
#include "base/compiler_specific.h"
-#include "base/location.h"
#include "base/macros.h"
#include "base/message_loop/message_pump.h"
+#include "base/message_loop/watchable_io_message_pump_posix.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
@@ -20,95 +22,55 @@ namespace base {
// Class to monitor sockets and issue callbacks when sockets are ready for I/O
// TODO(dkegel): add support for background file IO somehow
-class BASE_EXPORT MessagePumpLibevent : public MessagePump {
+class BASE_EXPORT MessagePumpLibevent : public MessagePump,
+ public WatchableIOMessagePumpPosix {
public:
- // Used with WatchFileDescriptor to asynchronously monitor the I/O readiness
- // of a file descriptor.
- class Watcher {
- public:
- // Called from MessageLoop::Run when an FD can be read from/written to
- // without blocking
- virtual void OnFileCanReadWithoutBlocking(int fd) = 0;
- virtual void OnFileCanWriteWithoutBlocking(int fd) = 0;
-
- protected:
- virtual ~Watcher() {}
- };
-
- // Object returned by WatchFileDescriptor to manage further watching.
- class FileDescriptorWatcher {
+ class FdWatchController : public FdWatchControllerInterface {
public:
- explicit FileDescriptorWatcher(const tracked_objects::Location& from_here);
- ~FileDescriptorWatcher(); // Implicitly calls StopWatchingFileDescriptor.
+ explicit FdWatchController(const Location& from_here);
- // NOTE: These methods aren't called StartWatching()/StopWatching() to
- // avoid confusion with the win32 ObjectWatcher class.
+ // Implicitly calls StopWatchingFileDescriptor.
+ ~FdWatchController() override;
- // Stop watching the FD, always safe to call. No-op if there's nothing
- // to do.
- bool StopWatchingFileDescriptor();
-
- const tracked_objects::Location& created_from_location() {
- return created_from_location_;
- }
+ // FdWatchControllerInterface:
+ bool StopWatchingFileDescriptor() override;
private:
friend class MessagePumpLibevent;
friend class MessagePumpLibeventTest;
- // Called by MessagePumpLibevent, ownership of |e| is transferred to this
- // object.
- void Init(event* e);
+ // Called by MessagePumpLibevent.
+ void Init(std::unique_ptr<event> e);
- // Used by MessagePumpLibevent to take ownership of event_.
- event* ReleaseEvent();
+ // Used by MessagePumpLibevent to take ownership of |event_|.
+ std::unique_ptr<event> ReleaseEvent();
void set_pump(MessagePumpLibevent* pump) { pump_ = pump; }
MessagePumpLibevent* pump() const { return pump_; }
- void set_watcher(Watcher* watcher) { watcher_ = watcher; }
+ void set_watcher(FdWatcher* watcher) { watcher_ = watcher; }
void OnFileCanReadWithoutBlocking(int fd, MessagePumpLibevent* pump);
void OnFileCanWriteWithoutBlocking(int fd, MessagePumpLibevent* pump);
- event* event_;
- MessagePumpLibevent* pump_;
- Watcher* watcher_;
+ std::unique_ptr<event> event_;
+ MessagePumpLibevent* pump_ = nullptr;
+ FdWatcher* watcher_ = nullptr;
// If this pointer is non-NULL, the pointee is set to true in the
// destructor.
- bool* was_destroyed_;
-
- const tracked_objects::Location created_from_location_;
-
- DISALLOW_COPY_AND_ASSIGN(FileDescriptorWatcher);
- };
+ bool* was_destroyed_ = nullptr;
- enum Mode {
- WATCH_READ = 1 << 0,
- WATCH_WRITE = 1 << 1,
- WATCH_READ_WRITE = WATCH_READ | WATCH_WRITE
+ DISALLOW_COPY_AND_ASSIGN(FdWatchController);
};
MessagePumpLibevent();
~MessagePumpLibevent() override;
- // Have the current thread's message loop watch for a a situation in which
- // reading/writing to the FD can be performed without blocking.
- // Callers must provide a preallocated FileDescriptorWatcher object which
- // can later be used to manage the lifetime of this event.
- // If a FileDescriptorWatcher is passed in which is already attached to
- // an event, then the effect is cumulative i.e. after the call |controller|
- // will watch both the previous event and the new one.
- // If an error occurs while calling this method in a cumulative fashion, the
- // event previously attached to |controller| is aborted.
- // Returns true on success.
- // Must be called on the same thread the message_pump is running on.
- // TODO(dkegel): switch to edge-triggered readiness notification
bool WatchFileDescriptor(int fd,
bool persistent,
int mode,
- FileDescriptorWatcher* controller,
- Watcher* delegate);
+ FdWatchController* controller,
+ FdWatcher* delegate);
// MessagePump methods:
void Run(Delegate* delegate) override;
diff --git a/base/message_loop/watchable_io_message_pump_posix.cc b/base/message_loop/watchable_io_message_pump_posix.cc
new file mode 100644
index 0000000000..1850137604
--- /dev/null
+++ b/base/message_loop/watchable_io_message_pump_posix.cc
@@ -0,0 +1,16 @@
+// 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.
+
+#include "base/message_loop/watchable_io_message_pump_posix.h"
+
+namespace base {
+
+WatchableIOMessagePumpPosix::FdWatchControllerInterface::
+ FdWatchControllerInterface(const Location& from_here)
+ : created_from_location_(from_here) {}
+
+WatchableIOMessagePumpPosix::FdWatchControllerInterface::
+ ~FdWatchControllerInterface() = default;
+
+} // namespace base
diff --git a/base/message_loop/watchable_io_message_pump_posix.h b/base/message_loop/watchable_io_message_pump_posix.h
new file mode 100644
index 0000000000..74583d9a21
--- /dev/null
+++ b/base/message_loop/watchable_io_message_pump_posix.h
@@ -0,0 +1,88 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_MESSAGE_LOOP_WATCHABLE_IO_MESSAGE_PUMP_POSIX_H_
+#define BASE_MESSAGE_LOOP_WATCHABLE_IO_MESSAGE_PUMP_POSIX_H_
+
+#include "base/location.h"
+#include "base/macros.h"
+
+namespace base {
+
+class WatchableIOMessagePumpPosix {
+ public:
+ // Used with WatchFileDescriptor to asynchronously monitor the I/O readiness
+ // of a file descriptor.
+ class FdWatcher {
+ public:
+ virtual void OnFileCanReadWithoutBlocking(int fd) = 0;
+ virtual void OnFileCanWriteWithoutBlocking(int fd) = 0;
+
+ protected:
+ virtual ~FdWatcher() = default;
+ };
+
+ class FdWatchControllerInterface {
+ public:
+ explicit FdWatchControllerInterface(const Location& from_here);
+ // Subclasses must call StopWatchingFileDescriptor() in their destructor
+ // (this parent class cannot generically do it for them as it must usually
+ // be invoked before they destroy their state which happens before the
+ // parent destructor is invoked).
+ virtual ~FdWatchControllerInterface();
+
+ // NOTE: This method isn't called StopWatching() to avoid confusion with the
+ // win32 ObjectWatcher class. While this doesn't really need to be virtual
+ // as there's only one impl per platform and users don't use pointers to the
+ // base class. Having this interface forces implementers to share similar
+ // implementations (a problem in the past).
+
+ // Stop watching the FD, always safe to call. No-op if there's nothing to
+ // do.
+ virtual bool StopWatchingFileDescriptor() = 0;
+
+ const Location& created_from_location() const {
+ return created_from_location_;
+ }
+
+ private:
+ const Location created_from_location_;
+
+ DISALLOW_COPY_AND_ASSIGN(FdWatchControllerInterface);
+ };
+
+ enum Mode {
+ WATCH_READ = 1 << 0,
+ WATCH_WRITE = 1 << 1,
+ WATCH_READ_WRITE = WATCH_READ | WATCH_WRITE
+ };
+
+ // Every subclass of WatchableIOMessagePumpPosix must provide a
+ // WatchFileDescriptor() which has the following signature where
+ // |FdWatchController| must be the complete type based on
+ // FdWatchControllerInterface.
+
+ // Registers |delegate| with the current thread's message loop so that its
+ // methods are invoked when file descriptor |fd| becomes ready for reading or
+ // writing (or both) without blocking. |mode| selects ready for reading, for
+ // writing, or both. See "enum Mode" above. |controller| manages the
+ // lifetime of registrations. ("Registrations" are also ambiguously called
+ // "events" in many places, for instance in libevent.) It is an error to use
+ // the same |controller| for different file descriptors; however, the same
+ // controller can be reused to add registrations with a different |mode|. If
+ // |controller| is already attached to one or more registrations, the new
+ // registration is added onto those. If an error occurs while calling this
+ // method, any registration previously attached to |controller| is removed.
+ // Returns true on success. Must be called on the same thread the MessagePump
+ // is running on.
+ // bool WatchFileDescriptor(int fd,
+ // bool persistent,
+ // int mode,
+ // FdWatchController* controller,
+ // FdWatcher* delegate) = 0;
+};
+
+} // namespace base
+
+#endif // BASE_MESSAGE_LOOP_WATCHABLE_IO_MESSAGE_PUMP_POSIX_H_
diff --git a/base/metrics/bucket_ranges.cc b/base/metrics/bucket_ranges.cc
index 084cdd3293..39b379320e 100644
--- a/base/metrics/bucket_ranges.cc
+++ b/base/metrics/bucket_ranges.cc
@@ -75,30 +75,13 @@ const uint32_t kCrcTable[256] = {
// a nice hash, that tends to depend on all the bits of the sample, with very
// little chance of changes in one place impacting changes in another place.
static uint32_t Crc32(uint32_t sum, HistogramBase::Sample value) {
- // TODO(jar): Switch to false and watch stats.
- const bool kUseRealCrc = true;
-
- if (kUseRealCrc) {
- union {
- HistogramBase::Sample range;
- unsigned char bytes[sizeof(HistogramBase::Sample)];
- } converter;
- converter.range = value;
- for (size_t i = 0; i < sizeof(converter); ++i)
- sum = kCrcTable[(sum & 0xff) ^ converter.bytes[i]] ^ (sum >> 8);
- } else {
- // Use hash techniques provided in ReallyFastHash, except we don't care
- // about "avalanching" (which would worsten the hash, and add collisions),
- // and we don't care about edge cases since we have an even number of bytes.
- union {
- HistogramBase::Sample range;
- uint16_t ints[sizeof(HistogramBase::Sample) / 2];
- } converter;
- DCHECK_EQ(sizeof(HistogramBase::Sample), sizeof(converter));
- converter.range = value;
- sum += converter.ints[0];
- sum = (sum << 16) ^ sum ^ (static_cast<uint32_t>(converter.ints[1]) << 11);
- sum += sum >> 11;
+ union {
+ HistogramBase::Sample range;
+ unsigned char bytes[sizeof(HistogramBase::Sample)];
+ } converter;
+ converter.range = value;
+ for (size_t i = 0; i < sizeof(converter); ++i) {
+ sum = kCrcTable[(sum & 0xff) ^ converter.bytes[i]] ^ (sum >> 8);
}
return sum;
}
@@ -107,13 +90,7 @@ BucketRanges::BucketRanges(size_t num_ranges)
: ranges_(num_ranges, 0),
checksum_(0) {}
-BucketRanges::~BucketRanges() {}
-
-void BucketRanges::set_range(size_t i, HistogramBase::Sample value) {
- DCHECK_LT(i, ranges_.size());
- CHECK_GE(value, 0);
- ranges_[i] = value;
-}
+BucketRanges::~BucketRanges() = default;
uint32_t BucketRanges::CalculateChecksum() const {
// Seed checksum.
diff --git a/base/metrics/bucket_ranges.h b/base/metrics/bucket_ranges.h
index c356195ba7..1b6d069bdf 100644
--- a/base/metrics/bucket_ranges.h
+++ b/base/metrics/bucket_ranges.h
@@ -24,6 +24,7 @@
#include <limits.h>
+#include "base/atomicops.h"
#include "base/base_export.h"
#include "base/macros.h"
#include "base/metrics/histogram_base.h"
@@ -39,7 +40,11 @@ class BASE_EXPORT BucketRanges {
size_t size() const { return ranges_.size(); }
HistogramBase::Sample range(size_t i) const { return ranges_[i]; }
- void set_range(size_t i, HistogramBase::Sample value);
+ void set_range(size_t i, HistogramBase::Sample value) {
+ DCHECK_LT(i, ranges_.size());
+ DCHECK_GE(value, 0);
+ ranges_[i] = value;
+ }
uint32_t checksum() const { return checksum_; }
void set_checksum(uint32_t checksum) { checksum_ = checksum; }
@@ -58,6 +63,17 @@ class BASE_EXPORT BucketRanges {
// Return true iff |other| object has same ranges_ as |this| object's ranges_.
bool Equals(const BucketRanges* other) const;
+ // Set and get a reference into persistent memory where this bucket data
+ // can be found (and re-used). These calls are internally atomic with no
+ // safety against overwriting an existing value since though it is wasteful
+ // to have multiple identical persistent records, it is still safe.
+ void set_persistent_reference(uint32_t ref) const {
+ subtle::Release_Store(&persistent_reference_, ref);
+ }
+ uint32_t persistent_reference() const {
+ return subtle::Acquire_Load(&persistent_reference_);
+ }
+
private:
// A monotonically increasing list of values which determine which bucket to
// put a sample into. For each index, show the smallest sample that can be
@@ -71,6 +87,12 @@ class BASE_EXPORT BucketRanges {
// noise on UMA dashboard.
uint32_t checksum_;
+ // A reference into a global PersistentMemoryAllocator where the ranges
+ // information is stored. This allows for the record to be created once and
+ // re-used simply by having all histograms with the same ranges use the
+ // same reference.
+ mutable subtle::Atomic32 persistent_reference_ = 0;
+
DISALLOW_COPY_AND_ASSIGN(BucketRanges);
};
diff --git a/base/metrics/dummy_histogram.cc b/base/metrics/dummy_histogram.cc
new file mode 100644
index 0000000000..2707733b2d
--- /dev/null
+++ b/base/metrics/dummy_histogram.cc
@@ -0,0 +1,102 @@
+// 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/metrics/dummy_histogram.h"
+
+#include <memory>
+
+#include "base/logging.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/metrics_hashes.h"
+
+namespace base {
+
+namespace {
+
+// Helper classes for DummyHistogram.
+class DummySampleCountIterator : public SampleCountIterator {
+ public:
+ DummySampleCountIterator() {}
+ ~DummySampleCountIterator() override {}
+
+ // SampleCountIterator:
+ bool Done() const override { return true; }
+ void Next() override { NOTREACHED(); }
+ void Get(HistogramBase::Sample* min,
+ int64_t* max,
+ HistogramBase::Count* count) const override {
+ NOTREACHED();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DummySampleCountIterator);
+};
+
+class DummyHistogramSamples : public HistogramSamples {
+ public:
+ explicit DummyHistogramSamples() : HistogramSamples(0, new LocalMetadata()) {}
+ ~DummyHistogramSamples() override {
+ delete static_cast<LocalMetadata*>(meta());
+ }
+
+ // HistogramSamples:
+ void Accumulate(HistogramBase::Sample value,
+ HistogramBase::Count count) override {}
+ HistogramBase::Count GetCount(HistogramBase::Sample value) const override {
+ return HistogramBase::Count();
+ }
+ HistogramBase::Count TotalCount() const override {
+ return HistogramBase::Count();
+ }
+ std::unique_ptr<SampleCountIterator> Iterator() const override {
+ return std::make_unique<DummySampleCountIterator>();
+ }
+ bool AddSubtractImpl(SampleCountIterator* iter, Operator op) override {
+ return true;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DummyHistogramSamples);
+};
+
+} // namespace
+
+// static
+DummyHistogram* DummyHistogram::GetInstance() {
+ static base::NoDestructor<DummyHistogram> dummy_histogram;
+ return dummy_histogram.get();
+}
+
+uint64_t DummyHistogram::name_hash() const {
+ return HashMetricName(histogram_name());
+}
+
+HistogramType DummyHistogram::GetHistogramType() const {
+ return DUMMY_HISTOGRAM;
+}
+
+bool DummyHistogram::HasConstructionArguments(
+ Sample expected_minimum,
+ Sample expected_maximum,
+ uint32_t expected_bucket_count) const {
+ return true;
+}
+
+bool DummyHistogram::AddSamplesFromPickle(PickleIterator* iter) {
+ return true;
+}
+
+std::unique_ptr<HistogramSamples> DummyHistogram::SnapshotSamples() const {
+ return std::make_unique<DummyHistogramSamples>();
+}
+
+std::unique_ptr<HistogramSamples> DummyHistogram::SnapshotDelta() {
+ return std::make_unique<DummyHistogramSamples>();
+}
+
+std::unique_ptr<HistogramSamples> DummyHistogram::SnapshotFinalDelta() const {
+ return std::make_unique<DummyHistogramSamples>();
+}
+
+} // namespace base
diff --git a/base/metrics/dummy_histogram.h b/base/metrics/dummy_histogram.h
new file mode 100644
index 0000000000..e2cb64ecbb
--- /dev/null
+++ b/base/metrics/dummy_histogram.h
@@ -0,0 +1,61 @@
+// 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_METRICS_DUMMY_HISTOGRAM_H_
+#define BASE_METRICS_DUMMY_HISTOGRAM_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/base_export.h"
+#include "base/metrics/histogram_base.h"
+#include "base/no_destructor.h"
+
+namespace base {
+
+// DummyHistogram is used for mocking histogram objects for histograms that
+// shouldn't be recorded. It doesn't do any actual processing.
+class BASE_EXPORT DummyHistogram : public HistogramBase {
+ public:
+ static DummyHistogram* GetInstance();
+
+ // HistogramBase:
+ void CheckName(const StringPiece& name) const override {}
+ uint64_t name_hash() const override;
+ HistogramType GetHistogramType() const override;
+ bool HasConstructionArguments(Sample expected_minimum,
+ Sample expected_maximum,
+ uint32_t expected_bucket_count) const override;
+ void Add(Sample value) override {}
+ void AddCount(Sample value, int count) override {}
+ void AddSamples(const HistogramSamples& samples) override {}
+ bool AddSamplesFromPickle(PickleIterator* iter) override;
+ std::unique_ptr<HistogramSamples> SnapshotSamples() const override;
+ std::unique_ptr<HistogramSamples> SnapshotDelta() override;
+ std::unique_ptr<HistogramSamples> SnapshotFinalDelta() const override;
+ void WriteHTMLGraph(std::string* output) const override {}
+ void WriteAscii(std::string* output) const override {}
+
+ protected:
+ // HistogramBase:
+ void SerializeInfoImpl(Pickle* pickle) const override {}
+ void GetParameters(DictionaryValue* params) const override {}
+ void GetCountAndBucketData(Count* count,
+ int64_t* sum,
+ ListValue* buckets) const override {}
+
+ private:
+ friend class NoDestructor<DummyHistogram>;
+
+ DummyHistogram() : HistogramBase("dummy_histogram") {}
+ ~DummyHistogram() override {}
+
+ DISALLOW_COPY_AND_ASSIGN(DummyHistogram);
+};
+
+} // namespace base
+
+#endif // BASE_METRICS_DUMMY_HISTOGRAM_H_
diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc
index 6b38d55bbd..ff37880c5f 100644
--- a/base/metrics/field_trial.cc
+++ b/base/metrics/field_trial.cc
@@ -14,11 +14,15 @@
#include "base/logging.h"
#include "base/metrics/field_trial_param_associator.h"
#include "base/process/memory.h"
+#include "base/process/process_handle.h"
+#include "base/process/process_info.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
+#include "base/unguessable_token.h"
// On POSIX, the fd is shared using the mapping in GlobalDescriptors.
#if defined(OS_POSIX) && !defined(OS_NACL)
@@ -46,7 +50,11 @@ const char kActivationMarker = '*';
// This is safe from race conditions because MakeIterable is a release operation
// and GetNextOfType is an acquire operation, so memory writes before
// MakeIterable happen before memory reads after GetNextOfType.
+#if defined(OS_FUCHSIA) // TODO(752368): Not yet supported on Fuchsia.
+const bool kUseSharedMemoryForFieldTrials = false;
+#else
const bool kUseSharedMemoryForFieldTrials = true;
+#endif
// Constants for the field trial allocator.
const char kAllocatorName[] = "FieldTrialAllocator";
@@ -61,25 +69,19 @@ const char kAllocatorName[] = "FieldTrialAllocator";
const size_t kFieldTrialAllocationSize = 128 << 10; // 128 KiB
// Writes out string1 and then string2 to pickle.
-bool WriteStringPair(Pickle* pickle,
+void WriteStringPair(Pickle* pickle,
const StringPiece& string1,
const StringPiece& string2) {
- if (!pickle->WriteString(string1))
- return false;
- if (!pickle->WriteString(string2))
- return false;
- return true;
+ pickle->WriteString(string1);
+ pickle->WriteString(string2);
}
// Writes out the field trial's contents (via trial_state) to the pickle. The
// format of the pickle looks like:
// TrialName, GroupName, ParamKey1, ParamValue1, ParamKey2, ParamValue2, ...
// If there are no parameters, then it just ends at GroupName.
-bool PickleFieldTrial(const FieldTrial::State& trial_state, Pickle* pickle) {
- if (!WriteStringPair(pickle, *trial_state.trial_name,
- *trial_state.group_name)) {
- return false;
- }
+void PickleFieldTrial(const FieldTrial::State& trial_state, Pickle* pickle) {
+ WriteStringPair(pickle, *trial_state.trial_name, *trial_state.group_name);
// Get field trial params.
std::map<std::string, std::string> params;
@@ -87,11 +89,8 @@ bool PickleFieldTrial(const FieldTrial::State& trial_state, Pickle* pickle) {
*trial_state.trial_name, *trial_state.group_name, &params);
// Write params to pickle.
- for (const auto& param : params) {
- if (!WriteStringPair(pickle, param.first, param.second))
- return false;
- }
- return true;
+ for (const auto& param : params)
+ WriteStringPair(pickle, param.first, param.second);
}
// Created a time value based on |year|, |month| and |day_of_month| parameters.
@@ -200,34 +199,13 @@ void AddFeatureAndFieldTrialFlags(const char* enable_features_switch,
cmd_line->AppendSwitchASCII(disable_features_switch, disabled_features);
std::string field_trial_states;
- FieldTrialList::AllStatesToString(&field_trial_states);
+ FieldTrialList::AllStatesToString(&field_trial_states, false);
if (!field_trial_states.empty()) {
cmd_line->AppendSwitchASCII(switches::kForceFieldTrials,
field_trial_states);
}
}
-#if defined(OS_WIN)
-HANDLE CreateReadOnlyHandle(FieldTrialList::FieldTrialAllocator* allocator) {
- HANDLE src = allocator->shared_memory()->handle().GetHandle();
- ProcessHandle process = GetCurrentProcess();
- DWORD access = SECTION_MAP_READ | SECTION_QUERY;
- HANDLE dst;
- if (!::DuplicateHandle(process, src, process, &dst, access, true, 0))
- return kInvalidPlatformFile;
- return dst;
-}
-#endif
-
-#if defined(OS_POSIX) && !defined(OS_NACL)
-int CreateReadOnlyHandle(FieldTrialList::FieldTrialAllocator* allocator) {
- SharedMemoryHandle new_handle;
- allocator->shared_memory()->ShareReadOnlyToProcess(GetCurrentProcessHandle(),
- &new_handle);
- return SharedMemory::GetFdFromSharedMemoryHandle(new_handle);
-}
-#endif
-
void OnOutOfMemory(size_t size) {
#if defined(OS_NACL)
NOTREACHED();
@@ -236,6 +214,36 @@ void OnOutOfMemory(size_t size) {
#endif
}
+#if !defined(OS_NACL)
+// Returns whether the operation succeeded.
+bool DeserializeGUIDFromStringPieces(base::StringPiece first,
+ base::StringPiece second,
+ base::UnguessableToken* guid) {
+ uint64_t high = 0;
+ uint64_t low = 0;
+ if (!base::StringToUint64(first, &high) ||
+ !base::StringToUint64(second, &low)) {
+ return false;
+ }
+
+ *guid = base::UnguessableToken::Deserialize(high, low);
+ return true;
+}
+
+// Extract a read-only SharedMemoryHandle from an existing |shared_memory|
+// handle. Note that on Android, this also makes the whole region read-only.
+SharedMemoryHandle GetSharedMemoryReadOnlyHandle(SharedMemory* shared_memory) {
+ SharedMemoryHandle result = shared_memory->GetReadOnlyHandle();
+#if defined(OS_ANDROID)
+ // On Android, turn the region read-only. This prevents any future
+ // writable mapping attempts, but the original one in |shm| survives
+ // and is still usable in the current process.
+ result.SetRegionReadOnly();
+#endif // OS_ANDROID
+ return result;
+}
+#endif // !OS_NACL
+
} // namespace
// statics
@@ -248,14 +256,13 @@ int FieldTrialList::kNoExpirationYear = 0;
//------------------------------------------------------------------------------
// FieldTrial methods and members.
-FieldTrial::EntropyProvider::~EntropyProvider() {
-}
+FieldTrial::EntropyProvider::~EntropyProvider() = default;
-FieldTrial::State::State() {}
+FieldTrial::State::State() = default;
FieldTrial::State::State(const State& other) = default;
-FieldTrial::State::~State() {}
+FieldTrial::State::~State() = default;
bool FieldTrial::FieldTrialEntry::GetTrialAndGroupName(
StringPiece* trial_name,
@@ -414,10 +421,11 @@ FieldTrial::FieldTrial(const std::string& trial_name,
ref_(FieldTrialList::FieldTrialAllocator::kReferenceNull) {
DCHECK_GT(total_probability, 0);
DCHECK(!trial_name_.empty());
- DCHECK(!default_group_name_.empty());
+ DCHECK(!default_group_name_.empty())
+ << "Trial " << trial_name << " is missing a default group name.";
}
-FieldTrial::~FieldTrial() {}
+FieldTrial::~FieldTrial() = default;
void FieldTrial::SetTrialRegistered() {
DCHECK_EQ(kNotFinalized, group_);
@@ -461,18 +469,9 @@ bool FieldTrial::GetActiveGroup(ActiveGroup* active_group) const {
return true;
}
-bool FieldTrial::GetState(State* field_trial_state) {
- if (!enable_field_trial_)
- return false;
- FinalizeGroupChoice();
- field_trial_state->trial_name = &trial_name_;
- field_trial_state->group_name = &group_name_;
- field_trial_state->activated = group_reported_;
- return true;
-}
-
-bool FieldTrial::GetStateWhileLocked(State* field_trial_state) {
- if (!enable_field_trial_)
+bool FieldTrial::GetStateWhileLocked(State* field_trial_state,
+ bool include_expired) {
+ if (!include_expired && !enable_field_trial_)
return false;
FinalizeGroupChoiceImpl(true);
field_trial_state->trial_name = &trial_name_;
@@ -485,19 +484,18 @@ bool FieldTrial::GetStateWhileLocked(State* field_trial_state) {
// FieldTrialList methods and members.
// static
-FieldTrialList* FieldTrialList::global_ = NULL;
+FieldTrialList* FieldTrialList::global_ = nullptr;
// static
bool FieldTrialList::used_without_global_ = false;
-FieldTrialList::Observer::~Observer() {
-}
+FieldTrialList::Observer::~Observer() = default;
FieldTrialList::FieldTrialList(
std::unique_ptr<const FieldTrial::EntropyProvider> entropy_provider)
: entropy_provider_(std::move(entropy_provider)),
observer_list_(new ObserverListThreadSafe<FieldTrialList::Observer>(
- ObserverListBase<FieldTrialList::Observer>::NOTIFY_EXISTING_ONLY)) {
+ ObserverListPolicy::EXISTING_ONLY)) {
DCHECK(!global_);
DCHECK(!used_without_global_);
global_ = this;
@@ -516,7 +514,7 @@ FieldTrialList::~FieldTrialList() {
registered_.erase(it->first);
}
DCHECK_EQ(this, global_);
- global_ = NULL;
+ global_ = nullptr;
}
// static
@@ -531,7 +529,7 @@ FieldTrial* FieldTrialList::FactoryGetFieldTrial(
int* default_group_number) {
return FactoryGetFieldTrialWithRandomizationSeed(
trial_name, total_probability, default_group_name, year, month,
- day_of_month, randomization_type, 0, default_group_number, NULL);
+ day_of_month, randomization_type, 0, default_group_number, nullptr);
}
// static
@@ -603,7 +601,7 @@ FieldTrial* FieldTrialList::FactoryGetFieldTrialWithRandomizationSeed(
// static
FieldTrial* FieldTrialList::Find(const std::string& trial_name) {
if (!global_)
- return NULL;
+ return nullptr;
AutoLock auto_lock(global_->lock_);
return global_->PreLockedFind(trial_name);
}
@@ -626,7 +624,7 @@ std::string FieldTrialList::FindFullName(const std::string& trial_name) {
// static
bool FieldTrialList::TrialExists(const std::string& trial_name) {
- return Find(trial_name) != NULL;
+ return Find(trial_name) != nullptr;
}
// static
@@ -654,14 +652,15 @@ void FieldTrialList::StatesToString(std::string* output) {
}
// static
-void FieldTrialList::AllStatesToString(std::string* output) {
+void FieldTrialList::AllStatesToString(std::string* output,
+ bool include_expired) {
if (!global_)
return;
AutoLock auto_lock(global_->lock_);
for (const auto& registered : global_->registered_) {
FieldTrial::State trial;
- if (!registered.second->GetStateWhileLocked(&trial))
+ if (!registered.second->GetStateWhileLocked(&trial, include_expired))
continue;
DCHECK_EQ(std::string::npos,
trial.trial_name->find(kPersistentStringSeparator));
@@ -677,6 +676,50 @@ void FieldTrialList::AllStatesToString(std::string* output) {
}
// static
+std::string FieldTrialList::AllParamsToString(bool include_expired,
+ EscapeDataFunc encode_data_func) {
+ FieldTrialParamAssociator* params_associator =
+ FieldTrialParamAssociator::GetInstance();
+ std::string output;
+ for (const auto& registered : GetRegisteredTrials()) {
+ FieldTrial::State trial;
+ if (!registered.second->GetStateWhileLocked(&trial, include_expired))
+ continue;
+ DCHECK_EQ(std::string::npos,
+ trial.trial_name->find(kPersistentStringSeparator));
+ DCHECK_EQ(std::string::npos,
+ trial.group_name->find(kPersistentStringSeparator));
+ std::map<std::string, std::string> params;
+ if (params_associator->GetFieldTrialParamsWithoutFallback(
+ *trial.trial_name, *trial.group_name, &params)) {
+ if (params.size() > 0) {
+ // Add comma to seprate from previous entry if it exists.
+ if (!output.empty())
+ output.append(1, ',');
+
+ output.append(encode_data_func(*trial.trial_name));
+ output.append(1, '.');
+ output.append(encode_data_func(*trial.group_name));
+ output.append(1, ':');
+
+ std::string param_str;
+ for (const auto& param : params) {
+ // Add separator from previous param information if it exists.
+ if (!param_str.empty())
+ param_str.append(1, kPersistentStringSeparator);
+ param_str.append(encode_data_func(param.first));
+ param_str.append(1, kPersistentStringSeparator);
+ param_str.append(encode_data_func(param.second));
+ }
+
+ output.append(param_str);
+ }
+ }
+ }
+ return output;
+}
+
+// static
void FieldTrialList::GetActiveFieldTrialGroups(
FieldTrial::ActiveGroups* active_groups) {
DCHECK(active_groups->empty());
@@ -714,6 +757,7 @@ void FieldTrialList::GetActiveFieldTrialGroupsFromString(
void FieldTrialList::GetInitiallyActiveFieldTrials(
const base::CommandLine& command_line,
FieldTrial::ActiveGroups* active_groups) {
+ DCHECK(global_);
DCHECK(global_->create_trials_from_command_line_called_);
if (!global_->field_trial_allocator_) {
@@ -756,8 +800,13 @@ bool FieldTrialList::CreateTrialsFromString(
const std::string trial_name = entry.trial_name.as_string();
const std::string group_name = entry.group_name.as_string();
- if (ContainsKey(ignored_trial_names, trial_name))
+ if (ContainsKey(ignored_trial_names, trial_name)) {
+ // This is to warn that the field trial forced through command-line
+ // input is unforcable.
+ // Use --enable-logging or --enable-logging=stderr to see this warning.
+ LOG(WARNING) << "Field trial: " << trial_name << " cannot be forced.";
continue;
+ }
FieldTrial* trial = CreateFieldTrial(trial_name, group_name);
if (!trial)
@@ -779,21 +828,21 @@ void FieldTrialList::CreateTrialsFromCommandLine(
int fd_key) {
global_->create_trials_from_command_line_called_ = true;
-#if defined(OS_WIN)
+#if defined(OS_WIN) || defined(OS_FUCHSIA)
if (cmd_line.HasSwitch(field_trial_handle_switch)) {
- std::string handle_switch =
+ std::string switch_value =
cmd_line.GetSwitchValueASCII(field_trial_handle_switch);
- bool result = CreateTrialsFromHandleSwitch(handle_switch);
+ bool result = CreateTrialsFromSwitchValue(switch_value);
DCHECK(result);
}
-#endif
-
-#if defined(OS_POSIX) && !defined(OS_NACL)
+#elif defined(OS_POSIX) && !defined(OS_NACL)
// On POSIX, we check if the handle is valid by seeing if the browser process
// sent over the switch (we don't care about the value). Invalid handles
// occur in some browser tests which don't initialize the allocator.
if (cmd_line.HasSwitch(field_trial_handle_switch)) {
- bool result = CreateTrialsFromDescriptor(fd_key);
+ std::string switch_value =
+ cmd_line.GetSwitchValueASCII(field_trial_handle_switch);
+ bool result = CreateTrialsFromDescriptor(fd_key, switch_value);
DCHECK(result);
}
#endif
@@ -832,21 +881,21 @@ void FieldTrialList::AppendFieldTrialHandleIfNeeded(
return;
if (kUseSharedMemoryForFieldTrials) {
InstantiateFieldTrialAllocatorIfNeeded();
- if (global_->readonly_allocator_handle_)
- handles->push_back(global_->readonly_allocator_handle_);
+ if (global_->readonly_allocator_handle_.IsValid())
+ handles->push_back(global_->readonly_allocator_handle_.GetHandle());
}
}
-#endif
-
-#if defined(OS_POSIX) && !defined(OS_NACL)
+#elif defined(OS_FUCHSIA)
+// TODO(fuchsia): Implement shared-memory configuration (crbug.com/752368).
+#elif defined(OS_POSIX) && !defined(OS_NACL)
// static
-int FieldTrialList::GetFieldTrialHandle() {
+SharedMemoryHandle FieldTrialList::GetFieldTrialHandle() {
if (global_ && kUseSharedMemoryForFieldTrials) {
InstantiateFieldTrialAllocatorIfNeeded();
// We check for an invalid handle where this gets called.
return global_->readonly_allocator_handle_;
}
- return kInvalidPlatformFile;
+ return SharedMemoryHandle();
}
#endif
@@ -873,37 +922,31 @@ void FieldTrialList::CopyFieldTrialStateToFlags(
InstantiateFieldTrialAllocatorIfNeeded();
// If the readonly handle didn't get duplicated properly, then fallback to
// original behavior.
- if (global_->readonly_allocator_handle_ == kInvalidPlatformFile) {
+ if (!global_->readonly_allocator_handle_.IsValid()) {
AddFeatureAndFieldTrialFlags(enable_features_switch,
disable_features_switch, cmd_line);
return;
}
global_->field_trial_allocator_->UpdateTrackingHistograms();
+ std::string switch_value = SerializeSharedMemoryHandleMetadata(
+ global_->readonly_allocator_handle_);
+ cmd_line->AppendSwitchASCII(field_trial_handle_switch, switch_value);
+
+ // Append --enable-features and --disable-features switches corresponding
+ // to the features enabled on the command-line, so that child and browser
+ // process command lines match and clearly show what has been specified
+ // explicitly by the user.
+ std::string enabled_features;
+ std::string disabled_features;
+ FeatureList::GetInstance()->GetCommandLineFeatureOverrides(
+ &enabled_features, &disabled_features);
+
+ if (!enabled_features.empty())
+ cmd_line->AppendSwitchASCII(enable_features_switch, enabled_features);
+ if (!disabled_features.empty())
+ cmd_line->AppendSwitchASCII(disable_features_switch, disabled_features);
-#if defined(OS_WIN)
- // We need to pass a named anonymous handle to shared memory over the
- // command line on Windows, since the child doesn't know which of the
- // handles it inherited it should open.
- // PlatformFile is typedef'd to HANDLE which is typedef'd to void *. We
- // basically cast the handle into an int (uintptr_t, to be exact), stringify
- // the int, and pass it as a command-line flag. The child process will do
- // the reverse conversions to retrieve the handle. See
- // http://stackoverflow.com/a/153077
- auto uintptr_handle =
- reinterpret_cast<uintptr_t>(global_->readonly_allocator_handle_);
- std::string field_trial_handle = std::to_string(uintptr_handle);
- cmd_line->AppendSwitchASCII(field_trial_handle_switch, field_trial_handle);
-#elif defined(OS_POSIX)
- // On POSIX, we dup the fd into a fixed fd kFieldTrialDescriptor, so we
- // don't have to pass over the handle (it's not even the right handle
- // anyways). But some browser tests don't create the allocator, so we need
- // to be able to distinguish valid and invalid handles. We do that by just
- // checking that the flag is set with a dummy value.
- cmd_line->AppendSwitchASCII(field_trial_handle_switch, "1");
-#else
-#error Unsupported OS
-#endif
return;
}
@@ -919,14 +962,14 @@ FieldTrial* FieldTrialList::CreateFieldTrial(
DCHECK_GE(name.size(), 0u);
DCHECK_GE(group_name.size(), 0u);
if (name.empty() || group_name.empty() || !global_)
- return NULL;
+ return nullptr;
FieldTrial* field_trial = FieldTrialList::Find(name);
if (field_trial) {
// In single process mode, or when we force them from the command line,
// we may have already created the field trial.
if (field_trial->group_name_internal() != group_name)
- return NULL;
+ return nullptr;
return field_trial;
}
const int kTotalProbability = 100;
@@ -938,10 +981,11 @@ FieldTrial* FieldTrialList::CreateFieldTrial(
}
// static
-void FieldTrialList::AddObserver(Observer* observer) {
+bool FieldTrialList::AddObserver(Observer* observer) {
if (!global_)
- return;
+ return false;
global_->observer_list_->AddObserver(observer);
+ return true;
}
// static
@@ -952,6 +996,18 @@ void FieldTrialList::RemoveObserver(Observer* observer) {
}
// static
+void FieldTrialList::SetSynchronousObserver(Observer* observer) {
+ DCHECK(!global_->synchronous_observer_);
+ global_->synchronous_observer_ = observer;
+}
+
+// static
+void FieldTrialList::RemoveSynchronousObserver(Observer* observer) {
+ DCHECK_EQ(global_->synchronous_observer_, observer);
+ global_->synchronous_observer_ = nullptr;
+}
+
+// static
void FieldTrialList::OnGroupFinalized(bool is_locked, FieldTrial* field_trial) {
if (!global_)
return;
@@ -992,6 +1048,11 @@ void FieldTrialList::NotifyFieldTrialGroupSelection(FieldTrial* field_trial) {
field_trial->group_name_internal());
}
+ if (global_->synchronous_observer_) {
+ global_->synchronous_observer_->OnFieldTrialGroupFinalized(
+ field_trial->trial_name(), field_trial->group_name_internal());
+ }
+
global_->observer_list_->Notify(
FROM_HERE, &FieldTrialList::Observer::OnFieldTrialGroupFinalized,
field_trial->trial_name(), field_trial->group_name_internal());
@@ -1132,20 +1193,111 @@ FieldTrialList::GetAllFieldTrialsFromPersistentAllocator(
return entries;
}
+// static
+bool FieldTrialList::IsGlobalSetForTesting() {
+ return global_ != nullptr;
+}
+
+// static
+std::string FieldTrialList::SerializeSharedMemoryHandleMetadata(
+ const SharedMemoryHandle& shm) {
+ std::stringstream ss;
#if defined(OS_WIN)
+ // Tell the child process the name of the inherited HANDLE.
+ uintptr_t uintptr_handle = reinterpret_cast<uintptr_t>(shm.GetHandle());
+ ss << uintptr_handle << ",";
+#elif defined(OS_FUCHSIA)
+ ss << shm.GetHandle() << ",";
+#elif !defined(OS_POSIX)
+#error Unsupported OS
+#endif
+
+ base::UnguessableToken guid = shm.GetGUID();
+ ss << guid.GetHighForSerialization() << "," << guid.GetLowForSerialization();
+ ss << "," << shm.GetSize();
+ return ss.str();
+}
+
+#if defined(OS_WIN) || defined(OS_FUCHSIA)
+
// static
-bool FieldTrialList::CreateTrialsFromHandleSwitch(
- const std::string& handle_switch) {
- int field_trial_handle = std::stoi(handle_switch);
+SharedMemoryHandle FieldTrialList::DeserializeSharedMemoryHandleMetadata(
+ const std::string& switch_value) {
+ std::vector<base::StringPiece> tokens = base::SplitStringPiece(
+ switch_value, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+
+ if (tokens.size() != 4)
+ return SharedMemoryHandle();
+
+ int field_trial_handle = 0;
+ if (!base::StringToInt(tokens[0], &field_trial_handle))
+ return SharedMemoryHandle();
+#if defined(OS_FUCHSIA)
+ zx_handle_t handle = static_cast<zx_handle_t>(field_trial_handle);
+#elif defined(OS_WIN)
HANDLE handle = reinterpret_cast<HANDLE>(field_trial_handle);
- SharedMemoryHandle shm_handle(handle, GetCurrentProcId());
- return FieldTrialList::CreateTrialsFromSharedMemoryHandle(shm_handle);
+ if (base::IsCurrentProcessElevated()) {
+ // base::LaunchElevatedProcess doesn't have a way to duplicate the handle,
+ // but this process can since by definition it's not sandboxed.
+ base::ProcessId parent_pid = base::GetParentProcessId(GetCurrentProcess());
+ HANDLE parent_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, parent_pid);
+ DuplicateHandle(parent_handle, handle, GetCurrentProcess(), &handle, 0,
+ FALSE, DUPLICATE_SAME_ACCESS);
+ CloseHandle(parent_handle);
+ }
+#endif // defined(OS_WIN)
+
+ base::UnguessableToken guid;
+ if (!DeserializeGUIDFromStringPieces(tokens[1], tokens[2], &guid))
+ return SharedMemoryHandle();
+
+ int size;
+ if (!base::StringToInt(tokens[3], &size))
+ return SharedMemoryHandle();
+
+ return SharedMemoryHandle(handle, static_cast<size_t>(size), guid);
}
+
+#elif defined(OS_POSIX) && !defined(OS_NACL)
+
+// static
+SharedMemoryHandle FieldTrialList::DeserializeSharedMemoryHandleMetadata(
+ int fd,
+ const std::string& switch_value) {
+ std::vector<base::StringPiece> tokens = base::SplitStringPiece(
+ switch_value, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+
+ if (tokens.size() != 3)
+ return SharedMemoryHandle();
+
+ base::UnguessableToken guid;
+ if (!DeserializeGUIDFromStringPieces(tokens[0], tokens[1], &guid))
+ return SharedMemoryHandle();
+
+ int size;
+ if (!base::StringToInt(tokens[2], &size))
+ return SharedMemoryHandle();
+
+ return SharedMemoryHandle(FileDescriptor(fd, true), static_cast<size_t>(size),
+ guid);
+}
+
#endif
-#if defined(OS_POSIX) && !defined(OS_NACL)
+#if defined(OS_WIN) || defined(OS_FUCHSIA)
// static
-bool FieldTrialList::CreateTrialsFromDescriptor(int fd_key) {
+bool FieldTrialList::CreateTrialsFromSwitchValue(
+ const std::string& switch_value) {
+ SharedMemoryHandle shm = DeserializeSharedMemoryHandleMetadata(switch_value);
+ if (!shm.IsValid())
+ return false;
+ return FieldTrialList::CreateTrialsFromSharedMemoryHandle(shm);
+}
+#elif defined(OS_POSIX) && !defined(OS_NACL)
+// static
+bool FieldTrialList::CreateTrialsFromDescriptor(
+ int fd_key,
+ const std::string& switch_value) {
if (!kUseSharedMemoryForFieldTrials)
return false;
@@ -1156,17 +1308,16 @@ bool FieldTrialList::CreateTrialsFromDescriptor(int fd_key) {
if (fd == -1)
return false;
-#if defined(OS_MACOSX) && !defined(OS_IOS)
- SharedMemoryHandle shm_handle(FileDescriptor(fd, true));
-#else
- SharedMemoryHandle shm_handle(fd, true);
-#endif
+ SharedMemoryHandle shm =
+ DeserializeSharedMemoryHandleMetadata(fd, switch_value);
+ if (!shm.IsValid())
+ return false;
- bool result = FieldTrialList::CreateTrialsFromSharedMemoryHandle(shm_handle);
+ bool result = FieldTrialList::CreateTrialsFromSharedMemoryHandle(shm);
DCHECK(result);
return true;
}
-#endif
+#endif // defined(OS_POSIX) && !defined(OS_NACL)
// static
bool FieldTrialList::CreateTrialsFromSharedMemoryHandle(
@@ -1250,10 +1401,8 @@ void FieldTrialList::InstantiateFieldTrialAllocatorIfNeeded() {
global_->field_trial_allocator_.get());
#if !defined(OS_NACL)
- // Set |readonly_allocator_handle_| so we can pass it to be inherited and
- // via the command line.
- global_->readonly_allocator_handle_ =
- CreateReadOnlyHandle(global_->field_trial_allocator_.get());
+ global_->readonly_allocator_handle_ = GetSharedMemoryReadOnlyHandle(
+ global_->field_trial_allocator_->shared_memory());
#endif
}
@@ -1271,7 +1420,7 @@ void FieldTrialList::AddToAllocatorWhileLocked(
return;
FieldTrial::State trial_state;
- if (!field_trial->GetStateWhileLocked(&trial_state))
+ if (!field_trial->GetStateWhileLocked(&trial_state, false))
return;
// Or if we've already added it. We must check after GetState since it can
@@ -1280,10 +1429,7 @@ void FieldTrialList::AddToAllocatorWhileLocked(
return;
Pickle pickle;
- if (!PickleFieldTrial(trial_state, &pickle)) {
- NOTREACHED();
- return;
- }
+ PickleFieldTrial(trial_state, &pickle);
size_t total_size = sizeof(FieldTrial::FieldTrialEntry) + pickle.size();
FieldTrial::FieldTrialRef ref = allocator->Allocate(
@@ -1314,15 +1460,14 @@ void FieldTrialList::ActivateFieldTrialEntryWhileLocked(
FieldTrialAllocator* allocator = global_->field_trial_allocator_.get();
// Check if we're in the child process and return early if so.
- if (allocator && allocator->IsReadonly())
+ if (!allocator || allocator->IsReadonly())
return;
FieldTrial::FieldTrialRef ref = field_trial->ref_;
if (ref == FieldTrialAllocator::kReferenceNull) {
// It's fine to do this even if the allocator hasn't been instantiated
// yet -- it'll just return early.
- AddToAllocatorWhileLocked(global_->field_trial_allocator_.get(),
- field_trial);
+ AddToAllocatorWhileLocked(allocator, field_trial);
} else {
// It's also okay to do this even though the callee doesn't have a lock --
// the only thing that happens on a stale read here is a slight performance
@@ -1338,7 +1483,7 @@ const FieldTrial::EntropyProvider*
FieldTrialList::GetEntropyProviderForOneTimeRandomization() {
if (!global_) {
used_without_global_ = true;
- return NULL;
+ return nullptr;
}
return global_->entropy_provider_.get();
@@ -1347,7 +1492,7 @@ const FieldTrial::EntropyProvider*
FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) {
RegistrationMap::iterator it = registered_.find(name);
if (registered_.end() == it)
- return NULL;
+ return nullptr;
return it->second;
}
@@ -1364,4 +1509,14 @@ void FieldTrialList::Register(FieldTrial* trial) {
global_->registered_[trial->trial_name()] = trial;
}
+// static
+FieldTrialList::RegistrationMap FieldTrialList::GetRegisteredTrials() {
+ RegistrationMap output;
+ if (global_) {
+ AutoLock auto_lock(global_->lock_);
+ output = global_->registered_;
+ }
+ return output;
+}
+
} // namespace base
diff --git a/base/metrics/field_trial.h b/base/metrics/field_trial.h
index 60a6592ce6..ac4ea1c044 100644
--- a/base/metrics/field_trial.h
+++ b/base/metrics/field_trial.h
@@ -72,6 +72,7 @@
#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/metrics/persistent_memory_allocator.h"
#include "base/observer_list_threadsafe.h"
#include "base/pickle.h"
@@ -79,6 +80,7 @@
#include "base/strings/string_piece.h"
#include "base/synchronization/lock.h"
#include "base/time/time.h"
+#include "build/build_config.h"
namespace base {
@@ -318,15 +320,13 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> {
bool GetActiveGroup(ActiveGroup* active_group) const;
// Returns the trial name and selected group name for this field trial via
- // the output parameter |field_trial_state|, but only if the trial has not
- // been disabled. In that case, true is returned and |field_trial_state| is
- // filled in; otherwise, the result is false and |field_trial_state| is left
- // untouched.
- bool GetState(State* field_trial_state);
-
- // Does the same thing as above, but is deadlock-free if the caller is holding
- // a lock.
- bool GetStateWhileLocked(State* field_trial_state);
+ // the output parameter |field_trial_state| for all the studies when
+ // |bool include_expired| is true. In case when |bool include_expired| is
+ // false, if the trial has not been disabled true is returned and
+ // |field_trial_state| is filled in; otherwise, the result is false and
+ // |field_trial_state| is left untouched.
+ // This function is deadlock-free if the caller is holding a lock.
+ bool GetStateWhileLocked(State* field_trial_state, bool include_expired);
// Returns the group_name. A winner need not have been chosen.
std::string group_name_internal() const { return group_name_; }
@@ -388,11 +388,16 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> {
//------------------------------------------------------------------------------
// Class with a list of all active field trials. A trial is active if it has
// been registered, which includes evaluating its state based on its probaility.
-// Only one instance of this class exists.
+// Only one instance of this class exists and outside of testing, will live for
+// the entire life time of the process.
class BASE_EXPORT FieldTrialList {
public:
typedef SharedPersistentMemoryAllocator FieldTrialAllocator;
+ // Type for function pointer passed to |AllParamsToString| used to escape
+ // special characters from |input|.
+ typedef std::string (*EscapeDataFunc)(const std::string& input);
+
// Year that is guaranteed to not be expired when instantiating a field trial
// via |FactoryGetFieldTrial()|. Set to two years from the build date.
static int kNoExpirationYear;
@@ -504,11 +509,21 @@ class BASE_EXPORT FieldTrialList {
// resurrection in another process. This allows randomization to be done in
// one process, and secondary processes can be synchronized on the result.
// The resulting string contains the name and group name pairs of all
- // registered FieldTrials which have not been disabled, with "/" used
- // to separate all names and to terminate the string. All activated trials
- // have their name prefixed with "*". This string is parsed by
- // |CreateTrialsFromString()|.
- static void AllStatesToString(std::string* output);
+ // registered FieldTrials including disabled based on |include_expired|,
+ // with "/" used to separate all names and to terminate the string. All
+ // activated trials have their name prefixed with "*". This string is parsed
+ // by |CreateTrialsFromString()|.
+ static void AllStatesToString(std::string* output, bool include_expired);
+
+ // Creates a persistent representation of all FieldTrial params for
+ // resurrection in another process. The returned string contains the trial
+ // name and group name pairs of all registered FieldTrials including disabled
+ // based on |include_expired| separated by '.'. The pair is followed by ':'
+ // separator and list of param name and values separated by '/'. It also takes
+ // |encode_data_func| function pointer for encodeing special charactors.
+ // This string is parsed by |AssociateParamsFromString()|.
+ static std::string AllParamsToString(bool include_expired,
+ EscapeDataFunc encode_data_func);
// Fills in the supplied vector |active_groups| (which must be empty when
// called) with a snapshot of all registered FieldTrials for which the group
@@ -569,13 +584,13 @@ class BASE_EXPORT FieldTrialList {
// list of handles to be inherited.
static void AppendFieldTrialHandleIfNeeded(
base::HandlesToInheritVector* handles);
-#endif
-
-#if defined(OS_POSIX) && !defined(OS_NACL)
+#elif defined(OS_FUCHSIA)
+ // TODO(fuchsia): Implement shared-memory configuration (crbug.com/752368).
+#elif defined(OS_POSIX) && !defined(OS_NACL)
// On POSIX, we also need to explicitly pass down this file descriptor that
- // should be shared with the child process. Returns kInvalidPlatformFile if no
- // handle exists or was not initialized properly.
- static PlatformFile GetFieldTrialHandle();
+ // should be shared with the child process. Returns an invalid handle if it
+ // was not initialized properly.
+ static base::SharedMemoryHandle GetFieldTrialHandle();
#endif
// Adds a switch to the command line containing the field trial state as a
@@ -599,12 +614,27 @@ class BASE_EXPORT FieldTrialList {
// Add an observer to be notified when a field trial is irrevocably committed
// to being part of some specific field_group (and hence the group_name is
- // also finalized for that field_trial).
- static void AddObserver(Observer* observer);
+ // also finalized for that field_trial). Returns false and does nothing if
+ // there is no FieldTrialList singleton.
+ static bool AddObserver(Observer* observer);
// Remove an observer.
static void RemoveObserver(Observer* observer);
+ // Similar to AddObserver(), but the passed observer will be notified
+ // synchronously when a field trial is activated and its group selected. It
+ // will be notified synchronously on the same thread where the activation and
+ // group selection happened. It is the responsibility of the observer to make
+ // sure that this is a safe operation and the operation must be fast, as this
+ // work is done synchronously as part of group() or related APIs. Only a
+ // single such observer is supported, exposed specifically for crash
+ // reporting. Must be called on the main thread before any other threads
+ // have been started.
+ static void SetSynchronousObserver(Observer* observer);
+
+ // Removes the single synchronous observer.
+ static void RemoveSynchronousObserver(Observer* observer);
+
// Grabs the lock if necessary and adds the field trial to the allocator. This
// should only be called from FinalizeGroupChoice().
static void OnGroupFinalized(bool is_locked, FieldTrial* field_trial);
@@ -637,6 +667,9 @@ class BASE_EXPORT FieldTrialList {
GetAllFieldTrialsFromPersistentAllocator(
PersistentMemoryAllocator const& allocator);
+ // Returns true if a global field trial list is set. Only used for testing.
+ static bool IsGlobalSetForTesting();
+
private:
// Allow tests to access our innards for testing purposes.
FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, InstantiateAllocator);
@@ -645,19 +678,38 @@ class BASE_EXPORT FieldTrialList {
DoNotAddSimulatedFieldTrialsToAllocator);
FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, AssociateFieldTrialParams);
FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, ClearParamsFromSharedMemory);
+ FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest,
+ SerializeSharedMemoryHandleMetadata);
+ FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, CheckReadOnlySharedMemoryHandle);
+
+ // Serialization is used to pass information about the handle to child
+ // processes. It passes a reference to the relevant OS resource, and it passes
+ // a GUID. Serialization and deserialization doesn't actually transport the
+ // underlying OS resource - that must be done by the Process launcher.
+ static std::string SerializeSharedMemoryHandleMetadata(
+ const SharedMemoryHandle& shm);
+#if defined(OS_WIN) || defined(OS_FUCHSIA)
+ static SharedMemoryHandle DeserializeSharedMemoryHandleMetadata(
+ const std::string& switch_value);
+#elif defined(OS_POSIX) && !defined(OS_NACL)
+ static SharedMemoryHandle DeserializeSharedMemoryHandleMetadata(
+ int fd,
+ const std::string& switch_value);
+#endif
-#if defined(OS_WIN)
+#if defined(OS_WIN) || defined(OS_FUCHSIA)
// Takes in |handle_switch| from the command line which represents the shared
// memory handle for field trials, parses it, and creates the field trials.
// Returns true on success, false on failure.
- static bool CreateTrialsFromHandleSwitch(const std::string& handle_switch);
-#endif
-
-#if defined(OS_POSIX) && !defined(OS_NACL)
+ // |switch_value| also contains the serialized GUID.
+ static bool CreateTrialsFromSwitchValue(const std::string& switch_value);
+#elif defined(OS_POSIX) && !defined(OS_NACL)
// On POSIX systems that use the zygote, we look up the correct fd that backs
// the shared memory segment containing the field trials by looking it up via
// an fd key in GlobalDescriptors. Returns true on success, false on failure.
- static bool CreateTrialsFromDescriptor(int fd_key);
+ // |switch_value| also contains the serialized GUID.
+ static bool CreateTrialsFromDescriptor(int fd_key,
+ const std::string& switch_value);
#endif
// Takes an unmapped SharedMemoryHandle, creates a SharedMemory object from it
@@ -701,6 +753,9 @@ class BASE_EXPORT FieldTrialList {
// This should always be called after creating a new FieldTrial instance.
static void Register(FieldTrial* trial);
+ // Returns all the registered trials.
+ static RegistrationMap GetRegisteredTrials();
+
static FieldTrialList* global_; // The singleton of this class.
// This will tell us if there is an attempt to register a field
@@ -722,6 +777,9 @@ class BASE_EXPORT FieldTrialList {
// List of observers to be notified when a group is selected for a FieldTrial.
scoped_refptr<ObserverListThreadSafe<Observer> > observer_list_;
+ // Single synchronous observer to be notified when a trial group is chosen.
+ Observer* synchronous_observer_ = nullptr;
+
// Allocator in shared memory containing field trial data. Used in both
// browser and child processes, but readonly in the child.
// In the future, we may want to move this to a more generic place if we want
@@ -731,7 +789,7 @@ class BASE_EXPORT FieldTrialList {
// Readonly copy of the handle to the allocator. Needs to be a member variable
// because it's needed from both CopyFieldTrialStateToFlags() and
// AppendFieldTrialHandleIfNeeded().
- PlatformFile readonly_allocator_handle_ = kInvalidPlatformFile;
+ base::SharedMemoryHandle readonly_allocator_handle_;
// Tracks whether CreateTrialsFromCommandLine() has been called.
bool create_trials_from_command_line_called_ = false;
diff --git a/base/metrics/field_trial_param_associator.cc b/base/metrics/field_trial_param_associator.cc
index 3bac18d6a9..af76eafaca 100644
--- a/base/metrics/field_trial_param_associator.cc
+++ b/base/metrics/field_trial_param_associator.cc
@@ -8,8 +8,8 @@
namespace base {
-FieldTrialParamAssociator::FieldTrialParamAssociator() {}
-FieldTrialParamAssociator::~FieldTrialParamAssociator() {}
+FieldTrialParamAssociator::FieldTrialParamAssociator() = default;
+FieldTrialParamAssociator::~FieldTrialParamAssociator() = default;
// static
FieldTrialParamAssociator* FieldTrialParamAssociator::GetInstance() {
@@ -72,6 +72,14 @@ void FieldTrialParamAssociator::ClearAllParamsForTesting() {
FieldTrialList::ClearParamsFromSharedMemoryForTesting();
}
+void FieldTrialParamAssociator::ClearParamsForTesting(
+ const std::string& trial_name,
+ const std::string& group_name) {
+ AutoLock scoped_lock(lock_);
+ const FieldTrialKey key(trial_name, group_name);
+ field_trial_params_.erase(key);
+}
+
void FieldTrialParamAssociator::ClearAllCachedParamsForTesting() {
AutoLock scoped_lock(lock_);
field_trial_params_.clear();
diff --git a/base/metrics/field_trial_param_associator.h b/base/metrics/field_trial_param_associator.h
index b19c66661c..b35e2cc5b9 100644
--- a/base/metrics/field_trial_param_associator.h
+++ b/base/metrics/field_trial_param_associator.h
@@ -51,6 +51,11 @@ class BASE_EXPORT FieldTrialParamAssociator {
// shared memory.
void ClearAllParamsForTesting();
+ // Clears a single field trial param.
+ // Note: this does NOT remove the param in shared memory.
+ void ClearParamsForTesting(const std::string& trial_name,
+ const std::string& group_name);
+
// Clears the internal field_trial_params_ mapping.
void ClearAllCachedParamsForTesting();
diff --git a/base/metrics/field_trial_params_unittest.nc b/base/metrics/field_trial_params_unittest.nc
new file mode 100644
index 0000000000..4c6005e34d
--- /dev/null
+++ b/base/metrics/field_trial_params_unittest.nc
@@ -0,0 +1,47 @@
+// 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.
+
+// This is a "No Compile Test" suite.
+// http://dev.chromium.org/developers/testing/no-compile-tests
+
+#include "base/feature_list.h"
+#include "base/metrics/field_trial_params.h"
+
+constexpr base::Feature kFeature{"NoCompileFeature"};
+
+enum Param { FOO, BAR };
+
+#if defined(NCTEST_NO_PARAM_TYPE) // [r"too few template arguments"]
+
+constexpr base::FeatureParam<> kParam{
+ &kFeature, "Param"};
+
+#elif defined(NCTEST_VOID_PARAM_TYPE) // [r"unsupported FeatureParam<> type"]
+
+constexpr base::FeatureParam<void> kParam{
+ &kFeature, "Param"};
+
+#elif defined(NCTEST_INVALID_PARAM_TYPE) // [r"unsupported FeatureParam<> type"]
+
+constexpr base::FeatureParam<size_t> kParam{
+ &kFeature, "Param", 1u};
+
+#elif defined(NCTEST_ENUM_NULL_OPTIONS) // [r"candidate template ignored: could not match"]
+
+constexpr base::FeatureParam<Param> kParam{
+ &kFeature, "Param", FOO, nullptr};
+
+#elif defined(NCTEST_ENUM_EMPTY_OPTIONS) // [r"zero-length arrays are not permitted"]
+
+constexpr base::FeatureParam<Param>::Option kParamOptions[] = {};
+constexpr base::FeatureParam<Param> kParam{
+ &kFeature, "Param", FOO, &kParamOptions};
+
+#else
+
+void suppress_unused_variable_warning() {
+ (void)kFeature;
+}
+
+#endif
diff --git a/base/metrics/field_trial_unittest.cc b/base/metrics/field_trial_unittest.cc
index 54672e63d5..7dbe737d8a 100644
--- a/base/metrics/field_trial_unittest.cc
+++ b/base/metrics/field_trial_unittest.cc
@@ -20,6 +20,8 @@
#include "base/test/gtest_util.h"
#include "base/test/mock_entropy_provider.h"
#include "base/test/scoped_feature_list.h"
+#include "base/test/test_shared_memory_util.h"
+#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
@@ -30,15 +32,15 @@ namespace {
const char kDefaultGroupName[] = "DefaultGroup";
// Call FieldTrialList::FactoryGetFieldTrial() with a future expiry date.
-scoped_refptr<base::FieldTrial> CreateFieldTrial(
+scoped_refptr<FieldTrial> CreateFieldTrial(
const std::string& trial_name,
int total_probability,
const std::string& default_group_name,
int* default_group_number) {
return FieldTrialList::FactoryGetFieldTrial(
trial_name, total_probability, default_group_name,
- base::FieldTrialList::kNoExpirationYear, 1, 1,
- base::FieldTrial::SESSION_RANDOMIZED, default_group_number);
+ FieldTrialList::kNoExpirationYear, 1, 1, FieldTrial::SESSION_RANDOMIZED,
+ default_group_number);
}
int OneYearBeforeBuildTime() {
@@ -51,11 +53,24 @@ int OneYearBeforeBuildTime() {
// FieldTrialList::Observer implementation for testing.
class TestFieldTrialObserver : public FieldTrialList::Observer {
public:
- TestFieldTrialObserver() {
- FieldTrialList::AddObserver(this);
+ enum Type {
+ ASYNCHRONOUS,
+ SYNCHRONOUS,
+ };
+
+ TestFieldTrialObserver(Type type) : type_(type) {
+ if (type == SYNCHRONOUS)
+ FieldTrialList::SetSynchronousObserver(this);
+ else
+ FieldTrialList::AddObserver(this);
}
- ~TestFieldTrialObserver() override { FieldTrialList::RemoveObserver(this); }
+ ~TestFieldTrialObserver() override {
+ if (type_ == SYNCHRONOUS)
+ FieldTrialList::RemoveSynchronousObserver(this);
+ else
+ FieldTrialList::RemoveObserver(this);
+ }
void OnFieldTrialGroupFinalized(const std::string& trial,
const std::string& group) override {
@@ -67,17 +82,22 @@ class TestFieldTrialObserver : public FieldTrialList::Observer {
const std::string& group_name() const { return group_name_; }
private:
+ const Type type_;
std::string trial_name_;
std::string group_name_;
DISALLOW_COPY_AND_ASSIGN(TestFieldTrialObserver);
};
+std::string MockEscapeQueryParamValue(const std::string& input) {
+ return input;
+}
+
} // namespace
-class FieldTrialTest : public testing::Test {
+class FieldTrialTest : public ::testing::Test {
public:
- FieldTrialTest() : trial_list_(NULL) {}
+ FieldTrialTest() : trial_list_(nullptr) {}
private:
MessageLoop message_loop_;
@@ -86,8 +106,7 @@ class FieldTrialTest : public testing::Test {
DISALLOW_COPY_AND_ASSIGN(FieldTrialTest);
};
-// Test registration, and also check that destructors are called for trials
-// (and that Valgrind doesn't catch us leaking).
+// Test registration, and also check that destructors are called for trials.
TEST_F(FieldTrialTest, Registration) {
const char name1[] = "name 1 test";
const char name2[] = "name 2 test";
@@ -95,7 +114,7 @@ TEST_F(FieldTrialTest, Registration) {
EXPECT_FALSE(FieldTrialList::Find(name2));
scoped_refptr<FieldTrial> trial1 =
- CreateFieldTrial(name1, 10, "default name 1 test", NULL);
+ CreateFieldTrial(name1, 10, "default name 1 test", nullptr);
EXPECT_EQ(FieldTrial::kNotFinalized, trial1->group_);
EXPECT_EQ(name1, trial1->trial_name());
EXPECT_EQ("", trial1->group_name_internal());
@@ -106,7 +125,7 @@ TEST_F(FieldTrialTest, Registration) {
EXPECT_FALSE(FieldTrialList::Find(name2));
scoped_refptr<FieldTrial> trial2 =
- CreateFieldTrial(name2, 10, "default name 2 test", NULL);
+ CreateFieldTrial(name2, 10, "default name 2 test", nullptr);
EXPECT_EQ(FieldTrial::kNotFinalized, trial2->group_);
EXPECT_EQ(name2, trial2->trial_name());
EXPECT_EQ("", trial2->group_name_internal());
@@ -132,7 +151,7 @@ TEST_F(FieldTrialTest, AbsoluteProbabilities) {
default_always_false[0] = c;
scoped_refptr<FieldTrial> trial_true =
- CreateFieldTrial(always_true, 10, default_always_true, NULL);
+ CreateFieldTrial(always_true, 10, default_always_true, nullptr);
const std::string winner = "TheWinner";
int winner_group = trial_true->AppendGroup(winner, 10);
@@ -140,7 +159,7 @@ TEST_F(FieldTrialTest, AbsoluteProbabilities) {
EXPECT_EQ(winner, trial_true->group_name());
scoped_refptr<FieldTrial> trial_false =
- CreateFieldTrial(always_false, 10, default_always_false, NULL);
+ CreateFieldTrial(always_false, 10, default_always_false, nullptr);
int loser_group = trial_false->AppendGroup("ALoser", 0);
EXPECT_NE(loser_group, trial_false->group());
@@ -177,11 +196,11 @@ TEST_F(FieldTrialTest, FiftyFiftyProbability) {
bool second_winner = false;
int counter = 0;
do {
- std::string name = base::StringPrintf("FiftyFifty%d", ++counter);
- std::string default_group_name = base::StringPrintf("Default FiftyFifty%d",
- ++counter);
+ std::string name = StringPrintf("FiftyFifty%d", ++counter);
+ std::string default_group_name =
+ StringPrintf("Default FiftyFifty%d", ++counter);
scoped_refptr<FieldTrial> trial =
- CreateFieldTrial(name, 2, default_group_name, NULL);
+ CreateFieldTrial(name, 2, default_group_name, nullptr);
trial->AppendGroup("first", 1); // 50% chance of being chosen.
// If group_ is kNotFinalized, then a group assignement hasn't been done.
if (trial->group_ != FieldTrial::kNotFinalized) {
@@ -206,7 +225,7 @@ TEST_F(FieldTrialTest, MiddleProbabilities) {
name[0] = c;
default_group_name[0] = c;
scoped_refptr<FieldTrial> trial =
- CreateFieldTrial(name, 10, default_group_name, NULL);
+ CreateFieldTrial(name, 10, default_group_name, nullptr);
int might_win = trial->AppendGroup("MightWin", 5);
if (trial->group() == might_win) {
@@ -230,7 +249,7 @@ TEST_F(FieldTrialTest, OneWinner) {
int default_group_number = -1;
scoped_refptr<FieldTrial> trial =
- CreateFieldTrial(name, group_count, default_group_name, NULL);
+ CreateFieldTrial(name, group_count, default_group_name, nullptr);
int winner_index(-2);
std::string winner_name;
@@ -277,7 +296,7 @@ TEST_F(FieldTrialTest, DisableProbability) {
TEST_F(FieldTrialTest, ActiveGroups) {
std::string no_group("No Group");
scoped_refptr<FieldTrial> trial =
- CreateFieldTrial(no_group, 10, "Default", NULL);
+ CreateFieldTrial(no_group, 10, "Default", nullptr);
// There is no winner yet, so no NameGroupId should be returned.
FieldTrial::ActiveGroup active_group;
@@ -285,7 +304,7 @@ TEST_F(FieldTrialTest, ActiveGroups) {
// Create a single winning group.
std::string one_winner("One Winner");
- trial = CreateFieldTrial(one_winner, 10, "Default", NULL);
+ trial = CreateFieldTrial(one_winner, 10, "Default", nullptr);
std::string winner("Winner");
trial->AppendGroup(winner, 10);
EXPECT_FALSE(trial->GetActiveGroup(&active_group));
@@ -297,7 +316,7 @@ TEST_F(FieldTrialTest, ActiveGroups) {
std::string multi_group("MultiGroup");
scoped_refptr<FieldTrial> multi_group_trial =
- CreateFieldTrial(multi_group, 9, "Default", NULL);
+ CreateFieldTrial(multi_group, 9, "Default", nullptr);
multi_group_trial->AppendGroup("Me", 3);
multi_group_trial->AppendGroup("You", 3);
@@ -334,36 +353,6 @@ TEST_F(FieldTrialTest, GetActiveFieldTrialGroupsFromString) {
EXPECT_EQ("Z", active_groups[1].group_name);
}
-TEST_F(FieldTrialTest, AllGroups) {
- FieldTrial::State field_trial_state;
- std::string one_winner("One Winner");
- scoped_refptr<FieldTrial> trial =
- CreateFieldTrial(one_winner, 10, "Default", NULL);
- std::string winner("Winner");
- trial->AppendGroup(winner, 10);
- EXPECT_TRUE(trial->GetState(&field_trial_state));
- EXPECT_EQ(one_winner, *field_trial_state.trial_name);
- EXPECT_EQ(winner, *field_trial_state.group_name);
- trial->group();
- EXPECT_TRUE(trial->GetState(&field_trial_state));
- EXPECT_EQ(one_winner, *field_trial_state.trial_name);
- EXPECT_EQ(winner, *field_trial_state.group_name);
-
- std::string multi_group("MultiGroup");
- scoped_refptr<FieldTrial> multi_group_trial =
- CreateFieldTrial(multi_group, 9, "Default", NULL);
-
- multi_group_trial->AppendGroup("Me", 3);
- multi_group_trial->AppendGroup("You", 3);
- multi_group_trial->AppendGroup("Them", 3);
- EXPECT_TRUE(multi_group_trial->GetState(&field_trial_state));
- // Finalize the group selection by accessing the selected group.
- multi_group_trial->group();
- EXPECT_TRUE(multi_group_trial->GetState(&field_trial_state));
- EXPECT_EQ(multi_group, *field_trial_state.trial_name);
- EXPECT_EQ(multi_group_trial->group_name(), *field_trial_state.group_name);
-}
-
TEST_F(FieldTrialTest, ActiveGroupsNotFinalized) {
const char kTrialName[] = "TestTrial";
const char kSecondaryGroupName[] = "SecondaryGroup";
@@ -425,7 +414,7 @@ TEST_F(FieldTrialTest, Save) {
std::string save_string;
scoped_refptr<FieldTrial> trial =
- CreateFieldTrial("Some name", 10, "Default some name", NULL);
+ CreateFieldTrial("Some name", 10, "Default some name", nullptr);
// There is no winner yet, so no textual group name is associated with trial.
// In this case, the trial should not be included.
EXPECT_EQ("", trial->group_name_internal());
@@ -443,7 +432,7 @@ TEST_F(FieldTrialTest, Save) {
// Create a second trial and winning group.
scoped_refptr<FieldTrial> trial2 =
- CreateFieldTrial("xxx", 10, "Default xxx", NULL);
+ CreateFieldTrial("xxx", 10, "Default xxx", nullptr);
trial2->AppendGroup("yyyy", 10);
// Finalize the group selection by accessing the selected group.
trial2->group();
@@ -455,7 +444,7 @@ TEST_F(FieldTrialTest, Save) {
// Create a third trial with only the default group.
scoped_refptr<FieldTrial> trial3 =
- CreateFieldTrial("zzz", 10, "default", NULL);
+ CreateFieldTrial("zzz", 10, "default", nullptr);
// Finalize the group selection by accessing the selected group.
trial3->group();
@@ -469,7 +458,7 @@ TEST_F(FieldTrialTest, SaveAll) {
scoped_refptr<FieldTrial> trial =
CreateFieldTrial("Some name", 10, "Default some name", nullptr);
EXPECT_EQ("", trial->group_name_internal());
- FieldTrialList::AllStatesToString(&save_string);
+ FieldTrialList::AllStatesToString(&save_string, false);
EXPECT_EQ("Some name/Default some name/", save_string);
// Getting all states should have finalized the trial.
EXPECT_EQ("Default some name", trial->group_name_internal());
@@ -480,7 +469,7 @@ TEST_F(FieldTrialTest, SaveAll) {
trial->AppendGroup("Winner", 10);
// Finalize the group selection by accessing the selected group.
trial->group();
- FieldTrialList::AllStatesToString(&save_string);
+ FieldTrialList::AllStatesToString(&save_string, false);
EXPECT_EQ("Some name/Default some name/*trial2/Winner/", save_string);
save_string.clear();
@@ -491,7 +480,7 @@ TEST_F(FieldTrialTest, SaveAll) {
// Finalize the group selection by accessing the selected group.
trial2->group();
- FieldTrialList::AllStatesToString(&save_string);
+ FieldTrialList::AllStatesToString(&save_string, false);
// We assume names are alphabetized... though this is not critical.
EXPECT_EQ("Some name/Default some name/*trial2/Winner/*xxx/yyyy/",
save_string);
@@ -499,11 +488,31 @@ TEST_F(FieldTrialTest, SaveAll) {
// Create a third trial with only the default group.
scoped_refptr<FieldTrial> trial3 =
- CreateFieldTrial("zzz", 10, "default", NULL);
+ CreateFieldTrial("zzz", 10, "default", nullptr);
- FieldTrialList::AllStatesToString(&save_string);
+ FieldTrialList::AllStatesToString(&save_string, false);
EXPECT_EQ("Some name/Default some name/*trial2/Winner/*xxx/yyyy/zzz/default/",
save_string);
+
+ // Create expired study.
+ int default_group_number = -1;
+ scoped_refptr<FieldTrial> expired_trial =
+ FieldTrialList::FactoryGetFieldTrial(
+ "Expired trial name", 1000000000, "Default group",
+ OneYearBeforeBuildTime(), 1, 1, FieldTrial::SESSION_RANDOMIZED,
+ &default_group_number);
+ expired_trial->AppendGroup("Expired trial group name", 999999999);
+
+ save_string.clear();
+ FieldTrialList::AllStatesToString(&save_string, false);
+ EXPECT_EQ("Some name/Default some name/*trial2/Winner/*xxx/yyyy/zzz/default/",
+ save_string);
+ save_string.clear();
+ FieldTrialList::AllStatesToString(&save_string, true);
+ EXPECT_EQ(
+ "Expired trial name/Default group/"
+ "Some name/Default some name/*trial2/Winner/*xxx/yyyy/zzz/default/",
+ save_string);
}
TEST_F(FieldTrialTest, Restore) {
@@ -514,12 +523,12 @@ TEST_F(FieldTrialTest, Restore) {
std::set<std::string>());
FieldTrial* trial = FieldTrialList::Find("Some_name");
- ASSERT_NE(static_cast<FieldTrial*>(NULL), trial);
+ ASSERT_NE(static_cast<FieldTrial*>(nullptr), trial);
EXPECT_EQ("Winner", trial->group_name());
EXPECT_EQ("Some_name", trial->trial_name());
trial = FieldTrialList::Find("xxx");
- ASSERT_NE(static_cast<FieldTrial*>(NULL), trial);
+ ASSERT_NE(static_cast<FieldTrial*>(nullptr), trial);
EXPECT_EQ("yyyy", trial->group_name());
EXPECT_EQ("xxx", trial->trial_name());
}
@@ -529,7 +538,7 @@ TEST_F(FieldTrialTest, RestoreNotEndingWithSlash) {
std::set<std::string>()));
FieldTrial* trial = FieldTrialList::Find("tname");
- ASSERT_NE(static_cast<FieldTrial*>(NULL), trial);
+ ASSERT_NE(static_cast<FieldTrial*>(nullptr), trial);
EXPECT_EQ("gname", trial->group_name());
EXPECT_EQ("tname", trial->trial_name());
}
@@ -549,7 +558,7 @@ TEST_F(FieldTrialTest, BogusRestore) {
TEST_F(FieldTrialTest, DuplicateRestore) {
scoped_refptr<FieldTrial> trial =
- CreateFieldTrial("Some name", 10, "Default", NULL);
+ CreateFieldTrial("Some name", 10, "Default", nullptr);
trial->AppendGroup("Winner", 10);
// Finalize the group selection by accessing the selected group.
trial->group();
@@ -607,7 +616,7 @@ TEST_F(FieldTrialTest, CreateTrialsFromStringForceActivation) {
TEST_F(FieldTrialTest, CreateTrialsFromStringNotActiveObserver) {
ASSERT_FALSE(FieldTrialList::TrialExists("Abc"));
- TestFieldTrialObserver observer;
+ TestFieldTrialObserver observer(TestFieldTrialObserver::ASYNCHRONOUS);
ASSERT_TRUE(FieldTrialList::CreateTrialsFromString("Abc/def/",
std::set<std::string>()));
RunLoop().RunUntilIdle();
@@ -653,12 +662,12 @@ TEST_F(FieldTrialTest, CreateTrialsFromStringWithIgnoredFieldTrials) {
EXPECT_TRUE(active_groups.empty());
FieldTrial* trial = FieldTrialList::Find("Foo");
- ASSERT_NE(static_cast<FieldTrial*>(NULL), trial);
+ ASSERT_NE(static_cast<FieldTrial*>(nullptr), trial);
EXPECT_EQ("Foo", trial->trial_name());
EXPECT_EQ("Foo_name", trial->group_name());
trial = FieldTrialList::Find("Bar");
- ASSERT_NE(static_cast<FieldTrial*>(NULL), trial);
+ ASSERT_NE(static_cast<FieldTrial*>(nullptr), trial);
EXPECT_EQ("Bar", trial->trial_name());
EXPECT_EQ("Bar_name", trial->group_name());
}
@@ -669,7 +678,7 @@ TEST_F(FieldTrialTest, CreateFieldTrial) {
FieldTrialList::CreateFieldTrial("Some_name", "Winner");
FieldTrial* trial = FieldTrialList::Find("Some_name");
- ASSERT_NE(static_cast<FieldTrial*>(NULL), trial);
+ ASSERT_NE(static_cast<FieldTrial*>(nullptr), trial);
EXPECT_EQ("Winner", trial->group_name());
EXPECT_EQ("Some_name", trial->trial_name());
}
@@ -687,16 +696,16 @@ TEST_F(FieldTrialTest, CreateFieldTrialIsNotActive) {
TEST_F(FieldTrialTest, DuplicateFieldTrial) {
scoped_refptr<FieldTrial> trial =
- CreateFieldTrial("Some_name", 10, "Default", NULL);
+ CreateFieldTrial("Some_name", 10, "Default", nullptr);
trial->AppendGroup("Winner", 10);
// It is OK if we redundantly specify a winner.
FieldTrial* trial1 = FieldTrialList::CreateFieldTrial("Some_name", "Winner");
- EXPECT_TRUE(trial1 != NULL);
+ EXPECT_TRUE(trial1 != nullptr);
// But it is an error to try to change to a different winner.
FieldTrial* trial2 = FieldTrialList::CreateFieldTrial("Some_name", "Loser");
- EXPECT_TRUE(trial2 == NULL);
+ EXPECT_TRUE(trial2 == nullptr);
}
TEST_F(FieldTrialTest, DisableImmediately) {
@@ -710,7 +719,7 @@ TEST_F(FieldTrialTest, DisableImmediately) {
TEST_F(FieldTrialTest, DisableAfterInitialization) {
scoped_refptr<FieldTrial> trial =
- CreateFieldTrial("trial", 100, "default", NULL);
+ CreateFieldTrial("trial", 100, "default", nullptr);
trial->AppendGroup("non_default", 100);
trial->Disable();
ASSERT_EQ("default", trial->group_name());
@@ -800,7 +809,7 @@ TEST_F(FieldTrialTest, SetForcedDefaultOnly) {
CreateFieldTrial(kTrialName, 100, kDefaultGroupName, &default_group);
trial->SetForced();
- trial = CreateFieldTrial(kTrialName, 100, kDefaultGroupName, NULL);
+ trial = CreateFieldTrial(kTrialName, 100, kDefaultGroupName, nullptr);
EXPECT_EQ(default_group, trial->group());
EXPECT_EQ(kDefaultGroupName, trial->group_name());
}
@@ -814,7 +823,7 @@ TEST_F(FieldTrialTest, SetForcedDefaultWithExtraGroup) {
CreateFieldTrial(kTrialName, 100, kDefaultGroupName, &default_group);
trial->SetForced();
- trial = CreateFieldTrial(kTrialName, 100, kDefaultGroupName, NULL);
+ trial = CreateFieldTrial(kTrialName, 100, kDefaultGroupName, nullptr);
const int extra_group = trial->AppendGroup("Extra", 100);
EXPECT_EQ(default_group, trial->group());
EXPECT_NE(extra_group, trial->group());
@@ -829,7 +838,7 @@ TEST_F(FieldTrialTest, SetForcedTurnFeatureOn) {
// Simulate a server-side (forced) config that turns the feature on when the
// original hard-coded config had it disabled.
scoped_refptr<FieldTrial> forced_trial =
- CreateFieldTrial(kTrialName, 100, kDefaultGroupName, NULL);
+ CreateFieldTrial(kTrialName, 100, kDefaultGroupName, nullptr);
forced_trial->AppendGroup(kExtraGroupName, 100);
forced_trial->SetForced();
@@ -853,7 +862,7 @@ TEST_F(FieldTrialTest, SetForcedTurnFeatureOff) {
// Simulate a server-side (forced) config that turns the feature off when the
// original hard-coded config had it enabled.
scoped_refptr<FieldTrial> forced_trial =
- CreateFieldTrial(kTrialName, 100, kDefaultGroupName, NULL);
+ CreateFieldTrial(kTrialName, 100, kDefaultGroupName, nullptr);
forced_trial->AppendGroup(kExtraGroupName, 0);
forced_trial->SetForced();
@@ -878,7 +887,7 @@ TEST_F(FieldTrialTest, SetForcedChangeDefault_Default) {
// Simulate a server-side (forced) config that switches which group is default
// and ensures that the non-forced code receives the correct group numbers.
scoped_refptr<FieldTrial> forced_trial =
- CreateFieldTrial(kTrialName, 100, kGroupAName, NULL);
+ CreateFieldTrial(kTrialName, 100, kGroupAName, nullptr);
forced_trial->AppendGroup(kGroupBName, 100);
forced_trial->SetForced();
@@ -903,7 +912,7 @@ TEST_F(FieldTrialTest, SetForcedChangeDefault_NonDefault) {
// Simulate a server-side (forced) config that switches which group is default
// and ensures that the non-forced code receives the correct group numbers.
scoped_refptr<FieldTrial> forced_trial =
- CreateFieldTrial(kTrialName, 100, kGroupAName, NULL);
+ CreateFieldTrial(kTrialName, 100, kGroupAName, nullptr);
forced_trial->AppendGroup(kGroupBName, 0);
forced_trial->SetForced();
@@ -923,7 +932,7 @@ TEST_F(FieldTrialTest, Observe) {
const char kTrialName[] = "TrialToObserve1";
const char kSecondaryGroupName[] = "SecondaryGroup";
- TestFieldTrialObserver observer;
+ TestFieldTrialObserver observer(TestFieldTrialObserver::ASYNCHRONOUS);
int default_group = -1;
scoped_refptr<FieldTrial> trial =
CreateFieldTrial(kTrialName, 100, kDefaultGroupName, &default_group);
@@ -931,7 +940,31 @@ TEST_F(FieldTrialTest, Observe) {
const int chosen_group = trial->group();
EXPECT_TRUE(chosen_group == default_group || chosen_group == secondary_group);
+ // Observers are called asynchronously.
+ EXPECT_TRUE(observer.trial_name().empty());
+ EXPECT_TRUE(observer.group_name().empty());
RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(kTrialName, observer.trial_name());
+ if (chosen_group == default_group)
+ EXPECT_EQ(kDefaultGroupName, observer.group_name());
+ else
+ EXPECT_EQ(kSecondaryGroupName, observer.group_name());
+}
+
+TEST_F(FieldTrialTest, SynchronousObserver) {
+ const char kTrialName[] = "TrialToObserve1";
+ const char kSecondaryGroupName[] = "SecondaryGroup";
+
+ TestFieldTrialObserver observer(TestFieldTrialObserver::SYNCHRONOUS);
+ int default_group = -1;
+ scoped_refptr<FieldTrial> trial =
+ CreateFieldTrial(kTrialName, 100, kDefaultGroupName, &default_group);
+ const int secondary_group = trial->AppendGroup(kSecondaryGroupName, 50);
+ const int chosen_group = trial->group();
+ EXPECT_TRUE(chosen_group == default_group || chosen_group == secondary_group);
+
+ // The observer should be notified synchronously by the group() call.
EXPECT_EQ(kTrialName, observer.trial_name());
if (chosen_group == default_group)
EXPECT_EQ(kDefaultGroupName, observer.group_name());
@@ -942,7 +975,7 @@ TEST_F(FieldTrialTest, Observe) {
TEST_F(FieldTrialTest, ObserveDisabled) {
const char kTrialName[] = "TrialToObserve2";
- TestFieldTrialObserver observer;
+ TestFieldTrialObserver observer(TestFieldTrialObserver::ASYNCHRONOUS);
int default_group = -1;
scoped_refptr<FieldTrial> trial =
CreateFieldTrial(kTrialName, 100, kDefaultGroupName, &default_group);
@@ -966,7 +999,7 @@ TEST_F(FieldTrialTest, ObserveDisabled) {
TEST_F(FieldTrialTest, ObserveForcedDisabled) {
const char kTrialName[] = "TrialToObserve3";
- TestFieldTrialObserver observer;
+ TestFieldTrialObserver observer(TestFieldTrialObserver::ASYNCHRONOUS);
int default_group = -1;
scoped_refptr<FieldTrial> trial =
CreateFieldTrial(kTrialName, 100, kDefaultGroupName, &default_group);
@@ -993,7 +1026,7 @@ TEST_F(FieldTrialTest, DisabledTrialNotActive) {
ASSERT_FALSE(FieldTrialList::TrialExists(kTrialName));
scoped_refptr<FieldTrial> trial =
- CreateFieldTrial(kTrialName, 100, kDefaultGroupName, NULL);
+ CreateFieldTrial(kTrialName, 100, kDefaultGroupName, nullptr);
trial->AppendGroup("X", 50);
trial->Disable();
@@ -1015,7 +1048,7 @@ TEST_F(FieldTrialTest, ExpirationYearNotExpired) {
ASSERT_FALSE(FieldTrialList::TrialExists(kTrialName));
scoped_refptr<FieldTrial> trial =
- CreateFieldTrial(kTrialName, kProbability, kDefaultGroupName, NULL);
+ CreateFieldTrial(kTrialName, kProbability, kDefaultGroupName, nullptr);
trial->AppendGroup(kGroupName, kProbability);
EXPECT_EQ(kGroupName, trial->group_name());
}
@@ -1027,12 +1060,12 @@ TEST_F(FieldTrialTest, FloatBoundariesGiveEqualGroupSizes) {
for (int i = 0; i < kBucketCount; ++i) {
const double entropy = i / static_cast<double>(kBucketCount);
- scoped_refptr<base::FieldTrial> trial(
- new base::FieldTrial("test", kBucketCount, "default", entropy));
+ scoped_refptr<FieldTrial> trial(
+ new FieldTrial("test", kBucketCount, "default", entropy));
for (int j = 0; j < kBucketCount; ++j)
- trial->AppendGroup(base::IntToString(j), 1);
+ trial->AppendGroup(IntToString(j), 1);
- EXPECT_EQ(base::IntToString(i), trial->group_name());
+ EXPECT_EQ(IntToString(i), trial->group_name());
}
}
@@ -1040,8 +1073,8 @@ TEST_F(FieldTrialTest, DoesNotSurpassTotalProbability) {
const double kEntropyValue = 1.0 - 1e-9;
ASSERT_LT(kEntropyValue, 1.0);
- scoped_refptr<base::FieldTrial> trial(
- new base::FieldTrial("test", 2, "default", kEntropyValue));
+ scoped_refptr<FieldTrial> trial(
+ new FieldTrial("test", 2, "default", kEntropyValue));
trial->AppendGroup("1", 1);
trial->AppendGroup("2", 1);
@@ -1063,7 +1096,7 @@ TEST_F(FieldTrialTest, CreateSimulatedFieldTrial) {
};
for (size_t i = 0; i < arraysize(test_cases); ++i) {
- TestFieldTrialObserver observer;
+ TestFieldTrialObserver observer(TestFieldTrialObserver::ASYNCHRONOUS);
scoped_refptr<FieldTrial> trial(
FieldTrial::CreateSimulatedFieldTrial(kTrialName, 100, kDefaultGroupName,
test_cases[i].entropy_value));
@@ -1097,23 +1130,23 @@ TEST(FieldTrialTestWithoutList, StatesStringFormat) {
// Scoping the first FieldTrialList, as we need another one to test the
// importing function.
{
- FieldTrialList field_trial_list(NULL);
+ FieldTrialList field_trial_list(nullptr);
scoped_refptr<FieldTrial> trial =
- CreateFieldTrial("Abc", 10, "Default some name", NULL);
+ CreateFieldTrial("Abc", 10, "Default some name", nullptr);
trial->AppendGroup("cba", 10);
trial->group();
scoped_refptr<FieldTrial> trial2 =
- CreateFieldTrial("Xyz", 10, "Default xxx", NULL);
+ CreateFieldTrial("Xyz", 10, "Default xxx", nullptr);
trial2->AppendGroup("zyx", 10);
trial2->group();
scoped_refptr<FieldTrial> trial3 =
- CreateFieldTrial("zzz", 10, "default", NULL);
+ CreateFieldTrial("zzz", 10, "default", nullptr);
- FieldTrialList::AllStatesToString(&save_string);
+ FieldTrialList::AllStatesToString(&save_string, false);
}
// Starting with a new blank FieldTrialList.
- FieldTrialList field_trial_list(NULL);
+ FieldTrialList field_trial_list(nullptr);
ASSERT_TRUE(field_trial_list.CreateTrialsFromString(save_string,
std::set<std::string>()));
@@ -1133,29 +1166,47 @@ TEST(FieldTrialDeathTest, OneTimeRandomizedTrialWithoutFieldTrialList) {
EXPECT_DEATH_IF_SUPPORTED(
FieldTrialList::FactoryGetFieldTrial(
"OneTimeRandomizedTrialWithoutFieldTrialList", 100, kDefaultGroupName,
- base::FieldTrialList::kNoExpirationYear, 1, 1,
- base::FieldTrial::ONE_TIME_RANDOMIZED, NULL),
+ FieldTrialList::kNoExpirationYear, 1, 1,
+ FieldTrial::ONE_TIME_RANDOMIZED, nullptr),
"");
}
-#if defined(OS_WIN)
-TEST(FieldTrialListTest, TestCopyFieldTrialStateToFlags) {
- base::FieldTrialList field_trial_list(
- base::MakeUnique<base::MockEntropyProvider>());
- base::FieldTrialList::CreateFieldTrial("Trial1", "Group1");
- base::FilePath test_file_path = base::FilePath(FILE_PATH_LITERAL("Program"));
- base::CommandLine cmd_line = base::CommandLine(test_file_path);
- const char field_trial_handle[] = "test-field-trial-handle";
- const char enable_features_switch[] = "test-enable-features";
- const char disable_features_switch[] = "test-disable-features";
-
- base::FieldTrialList::CopyFieldTrialStateToFlags(
- field_trial_handle, enable_features_switch, disable_features_switch,
- &cmd_line);
- EXPECT_TRUE(cmd_line.HasSwitch(field_trial_handle) ||
- cmd_line.HasSwitch(switches::kForceFieldTrials));
-}
+#if defined(OS_FUCHSIA)
+// TODO(crbug.com/752368): This is flaky on Fuchsia.
+#define MAYBE_TestCopyFieldTrialStateToFlags \
+ DISABLED_TestCopyFieldTrialStateToFlags
+#else
+#define MAYBE_TestCopyFieldTrialStateToFlags TestCopyFieldTrialStateToFlags
#endif
+TEST(FieldTrialListTest, MAYBE_TestCopyFieldTrialStateToFlags) {
+ constexpr char kFieldTrialHandleSwitch[] = "test-field-trial-handle";
+ constexpr char kEnableFeaturesSwitch[] = "test-enable-features";
+ constexpr char kDisableFeaturesSwitch[] = "test-disable-features";
+
+ FieldTrialList field_trial_list(std::make_unique<MockEntropyProvider>());
+
+ std::unique_ptr<FeatureList> feature_list(new FeatureList);
+ feature_list->InitializeFromCommandLine("A,B", "C");
+
+ FieldTrial* trial = FieldTrialList::CreateFieldTrial("Trial1", "Group1");
+ feature_list->RegisterFieldTrialOverride(
+ "MyFeature", FeatureList::OVERRIDE_ENABLE_FEATURE, trial);
+
+ test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitWithFeatureList(std::move(feature_list));
+
+ FilePath test_file_path = FilePath(FILE_PATH_LITERAL("Program"));
+ CommandLine command_line = CommandLine(test_file_path);
+
+ FieldTrialList::CopyFieldTrialStateToFlags(
+ kFieldTrialHandleSwitch, kEnableFeaturesSwitch, kDisableFeaturesSwitch,
+ &command_line);
+ EXPECT_TRUE(command_line.HasSwitch(kFieldTrialHandleSwitch));
+
+ // Explictly specified enabled/disabled features should be specified.
+ EXPECT_EQ("A,B", command_line.GetSwitchValueASCII(kEnableFeaturesSwitch));
+ EXPECT_EQ("C", command_line.GetSwitchValueASCII(kDisableFeaturesSwitch));
+}
TEST(FieldTrialListTest, InstantiateAllocator) {
test::ScopedFeatureList scoped_feature_list;
@@ -1178,7 +1229,7 @@ TEST(FieldTrialListTest, InstantiateAllocator) {
TEST(FieldTrialListTest, AddTrialsToAllocator) {
std::string save_string;
- base::SharedMemoryHandle handle;
+ SharedMemoryHandle handle;
// Scoping the first FieldTrialList, as we need another one to test that it
// matches.
@@ -1189,24 +1240,24 @@ TEST(FieldTrialListTest, AddTrialsToAllocator) {
FieldTrialList field_trial_list(nullptr);
FieldTrialList::CreateFieldTrial("Trial1", "Group1");
FieldTrialList::InstantiateFieldTrialAllocatorIfNeeded();
- FieldTrialList::AllStatesToString(&save_string);
- handle = base::SharedMemory::DuplicateHandle(
+ FieldTrialList::AllStatesToString(&save_string, false);
+ handle = SharedMemory::DuplicateHandle(
field_trial_list.field_trial_allocator_->shared_memory()->handle());
}
FieldTrialList field_trial_list2(nullptr);
- std::unique_ptr<base::SharedMemory> shm(new SharedMemory(handle, true));
+ std::unique_ptr<SharedMemory> shm(new SharedMemory(handle, true));
// 4 KiB is enough to hold the trials only created for this test.
shm.get()->Map(4 << 10);
FieldTrialList::CreateTrialsFromSharedMemory(std::move(shm));
std::string check_string;
- FieldTrialList::AllStatesToString(&check_string);
+ FieldTrialList::AllStatesToString(&check_string, false);
EXPECT_EQ(save_string, check_string);
}
TEST(FieldTrialListTest, DoNotAddSimulatedFieldTrialsToAllocator) {
constexpr char kTrialName[] = "trial";
- base::SharedMemoryHandle handle;
+ SharedMemoryHandle handle;
{
test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.Init();
@@ -1227,18 +1278,18 @@ TEST(FieldTrialListTest, DoNotAddSimulatedFieldTrialsToAllocator) {
FieldTrialList::CreateFieldTrial(kTrialName, "Real");
real_trial->group();
- handle = base::SharedMemory::DuplicateHandle(
+ handle = SharedMemory::DuplicateHandle(
field_trial_list.field_trial_allocator_->shared_memory()->handle());
}
// Check that there's only one entry in the allocator.
FieldTrialList field_trial_list2(nullptr);
- std::unique_ptr<base::SharedMemory> shm(new SharedMemory(handle, true));
+ std::unique_ptr<SharedMemory> shm(new SharedMemory(handle, true));
// 4 KiB is enough to hold the trials only created for this test.
shm.get()->Map(4 << 10);
FieldTrialList::CreateTrialsFromSharedMemory(std::move(shm));
std::string check_string;
- FieldTrialList::AllStatesToString(&check_string);
+ FieldTrialList::AllStatesToString(&check_string, false);
ASSERT_EQ(check_string.find("Simulated"), std::string::npos);
}
@@ -1276,11 +1327,17 @@ TEST(FieldTrialListTest, AssociateFieldTrialParams) {
EXPECT_EQ(2U, new_params.size());
}
-TEST(FieldTrialListTest, ClearParamsFromSharedMemory) {
+#if defined(OS_FUCHSIA)
+// TODO(crbug.com/752368): This is flaky on Fuchsia.
+#define MAYBE_ClearParamsFromSharedMemory DISABLED_ClearParamsFromSharedMemory
+#else
+#define MAYBE_ClearParamsFromSharedMemory ClearParamsFromSharedMemory
+#endif
+TEST(FieldTrialListTest, MAYBE_ClearParamsFromSharedMemory) {
std::string trial_name("Trial1");
std::string group_name("Group1");
- base::SharedMemoryHandle handle;
+ SharedMemoryHandle handle;
{
test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.Init();
@@ -1311,18 +1368,18 @@ TEST(FieldTrialListTest, ClearParamsFromSharedMemory) {
// Now duplicate the handle so we can easily check that the trial is still
// in shared memory via AllStatesToString.
- handle = base::SharedMemory::DuplicateHandle(
+ handle = SharedMemory::DuplicateHandle(
field_trial_list.field_trial_allocator_->shared_memory()->handle());
}
// Check that we have the trial.
FieldTrialList field_trial_list2(nullptr);
- std::unique_ptr<base::SharedMemory> shm(new SharedMemory(handle, true));
+ std::unique_ptr<SharedMemory> shm(new SharedMemory(handle, true));
// 4 KiB is enough to hold the trials only created for this test.
shm.get()->Map(4 << 10);
FieldTrialList::CreateTrialsFromSharedMemory(std::move(shm));
std::string check_string;
- FieldTrialList::AllStatesToString(&check_string);
+ FieldTrialList::AllStatesToString(&check_string, false);
EXPECT_EQ("*Trial1/Group1/", check_string);
}
@@ -1339,7 +1396,7 @@ TEST(FieldTrialListTest, DumpAndFetchFromSharedMemory) {
FieldTrialParamAssociator::GetInstance()->AssociateFieldTrialParams(
trial_name, group_name, params);
- std::unique_ptr<base::SharedMemory> shm(new SharedMemory());
+ std::unique_ptr<SharedMemory> shm(new SharedMemory());
// 4 KiB is enough to hold the trials only created for this test.
shm.get()->CreateAndMapAnonymous(4 << 10);
// We _could_ use PersistentMemoryAllocator, this just has less params.
@@ -1369,4 +1426,79 @@ TEST(FieldTrialListTest, DumpAndFetchFromSharedMemory) {
EXPECT_EQ("value2", shm_params["key2"]);
}
+#if !defined(OS_NACL)
+TEST(FieldTrialListTest, SerializeSharedMemoryHandleMetadata) {
+ std::unique_ptr<SharedMemory> shm(new SharedMemory());
+ shm->CreateAndMapAnonymous(4 << 10);
+
+ std::string serialized =
+ FieldTrialList::SerializeSharedMemoryHandleMetadata(shm->handle());
+#if defined(OS_WIN) || defined(OS_FUCHSIA)
+ SharedMemoryHandle deserialized =
+ FieldTrialList::DeserializeSharedMemoryHandleMetadata(serialized);
+#else
+ // Use a valid-looking arbitrary number for the file descriptor. It's not
+ // being used in this unittest, but needs to pass sanity checks in the
+ // handle's constructor.
+ SharedMemoryHandle deserialized =
+ FieldTrialList::DeserializeSharedMemoryHandleMetadata(42, serialized);
+#endif
+ EXPECT_EQ(deserialized.GetGUID(), shm->handle().GetGUID());
+ EXPECT_FALSE(deserialized.GetGUID().is_empty());
+}
+#endif // !defined(OS_NACL)
+
+// Verify that the field trial shared memory handle is really read-only, and
+// does not allow writable mappings. Test disabled on NaCl, Windows and Fuchsia
+// which don't support/implement GetFieldTrialHandle(). For Fuchsia, see
+// crbug.com/752368
+#if !defined(OS_NACL) && !defined(OS_WIN) && !defined(OS_FUCHSIA)
+TEST(FieldTrialListTest, CheckReadOnlySharedMemoryHandle) {
+ FieldTrialList field_trial_list(nullptr);
+ FieldTrialList::CreateFieldTrial("Trial1", "Group1");
+
+ test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.Init();
+
+ FieldTrialList::InstantiateFieldTrialAllocatorIfNeeded();
+
+ SharedMemoryHandle handle = FieldTrialList::GetFieldTrialHandle();
+ ASSERT_TRUE(handle.IsValid());
+
+ ASSERT_TRUE(CheckReadOnlySharedMemoryHandleForTesting(handle));
+}
+#endif // !OS_NACL && !OS_WIN && !OS_FUCHSIA
+
+TEST_F(FieldTrialTest, TestAllParamsToString) {
+ std::string exptected_output = "t1.g1:p1/v1/p2/v2";
+
+ // Create study with one group and two params.
+ std::map<std::string, std::string> params;
+ params["p1"] = "v1";
+ params["p2"] = "v2";
+ FieldTrialParamAssociator::GetInstance()->AssociateFieldTrialParams(
+ "t1", "g1", params);
+ EXPECT_EQ(
+ "", FieldTrialList::AllParamsToString(false, &MockEscapeQueryParamValue));
+
+ scoped_refptr<FieldTrial> trial1 =
+ CreateFieldTrial("t1", 100, "Default", nullptr);
+ trial1->AppendGroup("g1", 100);
+ trial1->group();
+ EXPECT_EQ(exptected_output, FieldTrialList::AllParamsToString(
+ false, &MockEscapeQueryParamValue));
+
+ // Create study with two groups and params that don't belog to the assigned
+ // group. This should be in the output.
+ FieldTrialParamAssociator::GetInstance()->AssociateFieldTrialParams(
+ "t2", "g2", params);
+ scoped_refptr<FieldTrial> trial2 =
+ CreateFieldTrial("t2", 100, "Default", nullptr);
+ trial2->AppendGroup("g1", 100);
+ trial2->AppendGroup("g2", 0);
+ trial2->group();
+ EXPECT_EQ(exptected_output, FieldTrialList::AllParamsToString(
+ false, &MockEscapeQueryParamValue));
+}
+
} // namespace base
diff --git a/base/metrics/histogram.cc b/base/metrics/histogram.cc
index 16e36ae4b0..5b29ee1746 100644
--- a/base/metrics/histogram.cc
+++ b/base/metrics/histogram.cc
@@ -9,17 +9,20 @@
#include "base/metrics/histogram.h"
+#include <inttypes.h>
#include <limits.h>
#include <math.h>
#include <algorithm>
#include <string>
+#include <utility>
#include "base/compiler_specific.h"
#include "base/debug/alias.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
-#include "base/metrics/histogram_macros.h"
+#include "base/metrics/dummy_histogram.h"
+#include "base/metrics/histogram_functions.h"
#include "base/metrics/metrics_hashes.h"
#include "base/metrics/persistent_histogram_allocator.h"
#include "base/metrics/persistent_memory_allocator.h"
@@ -30,6 +33,7 @@
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "base/values.h"
+#include "build/build_config.h"
namespace base {
@@ -72,6 +76,11 @@ bool ReadHistogramArguments(PickleIterator* iter,
bool ValidateRangeChecksum(const HistogramBase& histogram,
uint32_t range_checksum) {
+ // Normally, |histogram| should have type HISTOGRAM or be inherited from it.
+ // However, if it's expired, it will actually be a DUMMY_HISTOGRAM.
+ // Skip the checks in that case.
+ if (histogram.GetHistogramType() == DUMMY_HISTOGRAM)
+ return true;
const Histogram& casted_histogram =
static_cast<const Histogram&>(histogram);
@@ -124,7 +133,8 @@ class Histogram::Factory {
// Allocate the correct Histogram object off the heap (in case persistent
// memory is not available).
virtual std::unique_ptr<HistogramBase> HeapAlloc(const BucketRanges* ranges) {
- return WrapUnique(new Histogram(name_, minimum_, maximum_, ranges));
+ return WrapUnique(
+ new Histogram(GetPermanentName(name_), minimum_, maximum_, ranges));
}
// Perform any required datafill on the just-created histogram. If
@@ -149,6 +159,12 @@ class Histogram::Factory {
HistogramBase* Histogram::Factory::Build() {
HistogramBase* histogram = StatisticsRecorder::FindHistogram(name_);
if (!histogram) {
+ // TODO(gayane): |HashMetricName()| is called again in Histogram
+ // constructor. Refactor code to avoid the additional call.
+ bool should_record =
+ StatisticsRecorder::ShouldRecordHistogram(HashMetricName(name_));
+ if (!should_record)
+ return DummyHistogram::GetInstance();
// To avoid racy destruction at shutdown, the following will be leaked.
const BucketRanges* created_ranges = CreateRanges();
const BucketRanges* registered_ranges =
@@ -164,6 +180,8 @@ HistogramBase* Histogram::Factory::Build() {
minimum_ = registered_ranges->range(1);
maximum_ = registered_ranges->range(bucket_count_ - 1);
}
+ DCHECK_EQ(minimum_, registered_ranges->range(1));
+ DCHECK_EQ(maximum_, registered_ranges->range(bucket_count_ - 1));
// Try to create the histogram using a "persistent" allocator. As of
// 2016-02-25, the availability of such is controlled by a base::Feature
@@ -209,25 +227,21 @@ HistogramBase* Histogram::Factory::Build() {
allocator->FinalizeHistogram(histogram_ref,
histogram == tentative_histogram_ptr);
}
-
- // Update report on created histograms.
- ReportHistogramActivity(*histogram, HISTOGRAM_CREATED);
- } else {
- // Update report on lookup histograms.
- ReportHistogramActivity(*histogram, HISTOGRAM_LOOKUP);
}
- CHECK_EQ(histogram_type_, histogram->GetHistogramType()) << name_;
- if (bucket_count_ != 0 &&
- !histogram->HasConstructionArguments(minimum_, maximum_, bucket_count_)) {
+ if (histogram_type_ != histogram->GetHistogramType() ||
+ (bucket_count_ != 0 && !histogram->HasConstructionArguments(
+ minimum_, maximum_, bucket_count_))) {
// The construction arguments do not match the existing histogram. This can
// come about if an extension updates in the middle of a chrome run and has
- // changed one of them, or simply by bad code within Chrome itself. We
- // return NULL here with the expectation that bad code in Chrome will crash
- // on dereference, but extension/Pepper APIs will guard against NULL and not
- // crash.
- DLOG(ERROR) << "Histogram " << name_ << " has bad construction arguments";
- return nullptr;
+ // changed one of them, or simply by bad code within Chrome itself. A NULL
+ // return would cause Chrome to crash; better to just record it for later
+ // analysis.
+ UmaHistogramSparse("Histogram.MismatchedConstructionArguments",
+ static_cast<Sample>(HashMetricName(name_)));
+ DLOG(ERROR) << "Histogram " << name_
+ << " has mismatched construction arguments";
+ return DummyHistogram::GetInstance();
}
return histogram;
}
@@ -254,6 +268,16 @@ HistogramBase* Histogram::FactoryTimeGet(const std::string& name,
flags);
}
+HistogramBase* Histogram::FactoryMicrosecondsTimeGet(const std::string& name,
+ TimeDelta minimum,
+ TimeDelta maximum,
+ uint32_t bucket_count,
+ int32_t flags) {
+ return FactoryGet(name, static_cast<Sample>(minimum.InMicroseconds()),
+ static_cast<Sample>(maximum.InMicroseconds()), bucket_count,
+ flags);
+}
+
HistogramBase* Histogram::FactoryGet(const char* name,
Sample minimum,
Sample maximum,
@@ -271,19 +295,26 @@ HistogramBase* Histogram::FactoryTimeGet(const char* name,
flags);
}
+HistogramBase* Histogram::FactoryMicrosecondsTimeGet(const char* name,
+ TimeDelta minimum,
+ TimeDelta maximum,
+ uint32_t bucket_count,
+ int32_t flags) {
+ return FactoryMicrosecondsTimeGet(std::string(name), minimum, maximum,
+ bucket_count, flags);
+}
+
std::unique_ptr<HistogramBase> Histogram::PersistentCreate(
- const std::string& name,
+ const char* name,
Sample minimum,
Sample maximum,
const BucketRanges* ranges,
- HistogramBase::AtomicCount* counts,
- HistogramBase::AtomicCount* logged_counts,
- uint32_t counts_size,
+ const DelayedPersistentAllocation& counts,
+ const DelayedPersistentAllocation& logged_counts,
HistogramSamples::Metadata* meta,
HistogramSamples::Metadata* logged_meta) {
return WrapUnique(new Histogram(name, minimum, maximum, ranges, counts,
- logged_counts, counts_size, meta,
- logged_meta));
+ logged_counts, meta, logged_meta));
}
// Calculate what range of values are held in each bucket.
@@ -314,7 +345,7 @@ void Histogram::InitializeBucketRanges(Sample minimum,
// See where the next bucket would start.
log_next = log_current + log_ratio;
Sample next;
- next = static_cast<int>(floor(exp(log_next) + 0.5));
+ next = static_cast<int>(std::round(exp(log_next)));
if (next > current)
current = next;
else
@@ -347,12 +378,10 @@ uint32_t Histogram::FindCorruption(const HistogramSamples& samples) const {
if (delta != delta64)
delta = INT_MAX; // Flag all giant errors as INT_MAX.
if (delta > 0) {
- UMA_HISTOGRAM_COUNTS("Histogram.InconsistentCountHigh", delta);
if (delta > kCommonRaceBasedCountMismatch)
inconsistencies |= COUNT_HIGH_ERROR;
} else {
DCHECK_GT(0, delta);
- UMA_HISTOGRAM_COUNTS("Histogram.InconsistentCountLow", -delta);
if (-delta > kCommonRaceBasedCountMismatch)
inconsistencies |= COUNT_LOW_ERROR;
}
@@ -360,16 +389,34 @@ uint32_t Histogram::FindCorruption(const HistogramSamples& samples) const {
return inconsistencies;
}
+const BucketRanges* Histogram::bucket_ranges() const {
+ return unlogged_samples_->bucket_ranges();
+}
+
+Sample Histogram::declared_min() const {
+ const BucketRanges* ranges = bucket_ranges();
+ if (ranges->bucket_count() < 2)
+ return -1;
+ return ranges->range(1);
+}
+
+Sample Histogram::declared_max() const {
+ const BucketRanges* ranges = bucket_ranges();
+ if (ranges->bucket_count() < 2)
+ return -1;
+ return ranges->range(ranges->bucket_count() - 1);
+}
+
Sample Histogram::ranges(uint32_t i) const {
- return bucket_ranges_->range(i);
+ return bucket_ranges()->range(i);
}
uint32_t Histogram::bucket_count() const {
- return static_cast<uint32_t>(bucket_ranges_->bucket_count());
+ return static_cast<uint32_t>(bucket_ranges()->bucket_count());
}
// static
-bool Histogram::InspectConstructionArguments(const std::string& name,
+bool Histogram::InspectConstructionArguments(StringPiece name,
Sample* minimum,
Sample* maximum,
uint32_t* bucket_count) {
@@ -388,17 +435,42 @@ bool Histogram::InspectConstructionArguments(const std::string& name,
*bucket_count = kBucketCount_MAX - 1;
}
- if (*minimum >= *maximum)
- return false;
- if (*bucket_count < 3)
- return false;
- if (*bucket_count > static_cast<uint32_t>(*maximum - *minimum + 2))
- return false;
- return true;
+ bool check_okay = true;
+
+ if (*minimum > *maximum) {
+ check_okay = false;
+ std::swap(*minimum, *maximum);
+ }
+ if (*maximum == *minimum) {
+ check_okay = false;
+ *maximum = *minimum + 1;
+ }
+ if (*bucket_count < 3) {
+ check_okay = false;
+ *bucket_count = 3;
+ }
+ // Very high bucket counts are wasteful. Use a sparse histogram instead.
+ // Value of 10002 equals a user-supplied value of 10k + 2 overflow buckets.
+ constexpr uint32_t kMaxBucketCount = 10002;
+ if (*bucket_count > kMaxBucketCount) {
+ check_okay = false;
+ *bucket_count = kMaxBucketCount;
+ }
+ if (*bucket_count > static_cast<uint32_t>(*maximum - *minimum + 2)) {
+ check_okay = false;
+ *bucket_count = static_cast<uint32_t>(*maximum - *minimum + 2);
+ }
+
+ if (!check_okay) {
+ UmaHistogramSparse("Histogram.BadConstructionArguments",
+ static_cast<Sample>(HashMetricName(name)));
+ }
+
+ return check_okay;
}
uint64_t Histogram::name_hash() const {
- return samples_->id();
+ return unlogged_samples_->id();
}
HistogramType Histogram::GetHistogramType() const {
@@ -408,9 +480,9 @@ HistogramType Histogram::GetHistogramType() const {
bool Histogram::HasConstructionArguments(Sample expected_minimum,
Sample expected_maximum,
uint32_t expected_bucket_count) const {
- return ((expected_minimum == declared_min_) &&
- (expected_maximum == declared_max_) &&
- (expected_bucket_count == bucket_count()));
+ return (expected_bucket_count == bucket_count() &&
+ expected_minimum == declared_min() &&
+ expected_maximum == declared_max());
}
void Histogram::Add(int value) {
@@ -429,50 +501,54 @@ void Histogram::AddCount(int value, int count) {
NOTREACHED();
return;
}
- samples_->Accumulate(value, count);
+ unlogged_samples_->Accumulate(value, count);
FindAndRunCallback(value);
}
std::unique_ptr<HistogramSamples> Histogram::SnapshotSamples() const {
- return SnapshotSampleVector();
+ return SnapshotAllSamples();
}
std::unique_ptr<HistogramSamples> Histogram::SnapshotDelta() {
+#if DCHECK_IS_ON()
DCHECK(!final_delta_created_);
-
- std::unique_ptr<HistogramSamples> snapshot = SnapshotSampleVector();
- if (!logged_samples_) {
- // If nothing has been previously logged, save this one as
- // |logged_samples_| and gather another snapshot to return.
- logged_samples_.swap(snapshot);
- return SnapshotSampleVector();
- }
-
- // Subtract what was previously logged and update that information.
- snapshot->Subtract(*logged_samples_);
+#endif
+
+ // The code below has subtle thread-safety guarantees! All changes to
+ // the underlying SampleVectors use atomic integer operations, which guarantee
+ // eventual consistency, but do not guarantee full synchronization between
+ // different entries in the SampleVector. In particular, this means that
+ // concurrent updates to the histogram might result in the reported sum not
+ // matching the individual bucket counts; or there being some buckets that are
+ // logically updated "together", but end up being only partially updated when
+ // a snapshot is captured. Note that this is why it's important to subtract
+ // exactly the snapshotted unlogged samples, rather than simply resetting the
+ // vector: this way, the next snapshot will include any concurrent updates
+ // missed by the current snapshot.
+
+ std::unique_ptr<HistogramSamples> snapshot = SnapshotUnloggedSamples();
+ unlogged_samples_->Subtract(*snapshot);
logged_samples_->Add(*snapshot);
+
return snapshot;
}
std::unique_ptr<HistogramSamples> Histogram::SnapshotFinalDelta() const {
+#if DCHECK_IS_ON()
DCHECK(!final_delta_created_);
final_delta_created_ = true;
+#endif
- std::unique_ptr<HistogramSamples> snapshot = SnapshotSampleVector();
-
- // Subtract what was previously logged and then return.
- if (logged_samples_)
- snapshot->Subtract(*logged_samples_);
- return snapshot;
+ return SnapshotUnloggedSamples();
}
void Histogram::AddSamples(const HistogramSamples& samples) {
- samples_->Add(samples);
+ unlogged_samples_->Add(samples);
}
bool Histogram::AddSamplesFromPickle(PickleIterator* iter) {
- return samples_->AddFromPickle(iter);
+ return unlogged_samples_->AddFromPickle(iter);
}
// The following methods provide a graphical histogram display.
@@ -487,51 +563,52 @@ void Histogram::WriteAscii(std::string* output) const {
WriteAsciiImpl(true, "\n", output);
}
-bool Histogram::SerializeInfoImpl(Pickle* pickle) const {
+void Histogram::ValidateHistogramContents() const {
+ CHECK(unlogged_samples_);
+ CHECK(unlogged_samples_->bucket_ranges());
+ CHECK(logged_samples_);
+ CHECK(logged_samples_->bucket_ranges());
+ CHECK_NE(0U, logged_samples_->id());
+}
+
+void Histogram::SerializeInfoImpl(Pickle* pickle) const {
DCHECK(bucket_ranges()->HasValidChecksum());
- return pickle->WriteString(histogram_name()) &&
- pickle->WriteInt(flags()) &&
- pickle->WriteInt(declared_min()) &&
- pickle->WriteInt(declared_max()) &&
- pickle->WriteUInt32(bucket_count()) &&
- pickle->WriteUInt32(bucket_ranges()->checksum());
+ pickle->WriteString(histogram_name());
+ pickle->WriteInt(flags());
+ pickle->WriteInt(declared_min());
+ pickle->WriteInt(declared_max());
+ pickle->WriteUInt32(bucket_count());
+ pickle->WriteUInt32(bucket_ranges()->checksum());
}
-Histogram::Histogram(const std::string& name,
+// TODO(bcwhite): Remove minimum/maximum parameters from here and call chain.
+Histogram::Histogram(const char* name,
Sample minimum,
Sample maximum,
const BucketRanges* ranges)
- : HistogramBase(name),
- bucket_ranges_(ranges),
- declared_min_(minimum),
- declared_max_(maximum) {
- if (ranges)
- samples_.reset(new SampleVector(HashMetricName(name), ranges));
+ : HistogramBase(name) {
+ DCHECK(ranges) << name << ": " << minimum << "-" << maximum;
+ unlogged_samples_.reset(new SampleVector(HashMetricName(name), ranges));
+ logged_samples_.reset(new SampleVector(unlogged_samples_->id(), ranges));
}
-Histogram::Histogram(const std::string& name,
+Histogram::Histogram(const char* name,
Sample minimum,
Sample maximum,
const BucketRanges* ranges,
- HistogramBase::AtomicCount* counts,
- HistogramBase::AtomicCount* logged_counts,
- uint32_t counts_size,
+ const DelayedPersistentAllocation& counts,
+ const DelayedPersistentAllocation& logged_counts,
HistogramSamples::Metadata* meta,
HistogramSamples::Metadata* logged_meta)
- : HistogramBase(name),
- bucket_ranges_(ranges),
- declared_min_(minimum),
- declared_max_(maximum) {
- if (ranges) {
- samples_.reset(new SampleVector(HashMetricName(name),
- counts, counts_size, meta, ranges));
- logged_samples_.reset(new SampleVector(samples_->id(), logged_counts,
- counts_size, logged_meta, ranges));
- }
+ : HistogramBase(name) {
+ DCHECK(ranges) << name << ": " << minimum << "-" << maximum;
+ unlogged_samples_.reset(
+ new PersistentSampleVector(HashMetricName(name), ranges, meta, counts));
+ logged_samples_.reset(new PersistentSampleVector(
+ unlogged_samples_->id(), ranges, logged_meta, logged_counts));
}
-Histogram::~Histogram() {
-}
+Histogram::~Histogram() = default;
bool Histogram::PrintEmptyBucket(uint32_t index) const {
return true;
@@ -569,24 +646,32 @@ HistogramBase* Histogram::DeserializeInfoImpl(PickleIterator* iter) {
if (!ReadHistogramArguments(iter, &histogram_name, &flags, &declared_min,
&declared_max, &bucket_count, &range_checksum)) {
- return NULL;
+ return nullptr;
}
// Find or create the local version of the histogram in this process.
HistogramBase* histogram = Histogram::FactoryGet(
histogram_name, declared_min, declared_max, bucket_count, flags);
+ if (!histogram)
+ return nullptr;
+
+ // The serialized histogram might be corrupted.
+ if (!ValidateRangeChecksum(*histogram, range_checksum))
+ return nullptr;
- if (!ValidateRangeChecksum(*histogram, range_checksum)) {
- // The serialized histogram might be corrupted.
- return NULL;
- }
return histogram;
}
-std::unique_ptr<SampleVector> Histogram::SnapshotSampleVector() const {
+std::unique_ptr<SampleVector> Histogram::SnapshotAllSamples() const {
+ std::unique_ptr<SampleVector> samples = SnapshotUnloggedSamples();
+ samples->Add(*logged_samples_);
+ return samples;
+}
+
+std::unique_ptr<SampleVector> Histogram::SnapshotUnloggedSamples() const {
std::unique_ptr<SampleVector> samples(
- new SampleVector(samples_->id(), bucket_ranges()));
- samples->Add(*samples_);
+ new SampleVector(unlogged_samples_->id(), bucket_ranges()));
+ samples->Add(*unlogged_samples_);
return samples;
}
@@ -595,7 +680,7 @@ void Histogram::WriteAsciiImpl(bool graph_it,
std::string* output) const {
// Get local (stack) copies of all effectively volatile class data so that we
// are consistent across our output activities.
- std::unique_ptr<SampleVector> snapshot = SnapshotSampleVector();
+ std::unique_ptr<SampleVector> snapshot = SnapshotAllSamples();
Count sample_count = snapshot->TotalCount();
WriteAsciiHeader(*snapshot, sample_count, output);
@@ -657,7 +742,7 @@ void Histogram::WriteAsciiImpl(bool graph_it,
DCHECK_EQ(sample_count, past);
}
-double Histogram::GetPeakBucketSize(const SampleVector& samples) const {
+double Histogram::GetPeakBucketSize(const SampleVectorBase& samples) const {
double max = 0;
for (uint32_t i = 0; i < bucket_count() ; ++i) {
double current_size = GetBucketSize(samples.GetCountAtIndex(i), i);
@@ -667,12 +752,10 @@ double Histogram::GetPeakBucketSize(const SampleVector& samples) const {
return max;
}
-void Histogram::WriteAsciiHeader(const SampleVector& samples,
+void Histogram::WriteAsciiHeader(const SampleVectorBase& samples,
Count sample_count,
std::string* output) const {
- StringAppendF(output,
- "Histogram: %s recorded %d samples",
- histogram_name().c_str(),
+ StringAppendF(output, "Histogram: %s recorded %d samples", histogram_name(),
sample_count);
if (sample_count == 0) {
DCHECK_EQ(samples.sum(), 0);
@@ -707,7 +790,7 @@ void Histogram::GetParameters(DictionaryValue* params) const {
void Histogram::GetCountAndBucketData(Count* count,
int64_t* sum,
ListValue* buckets) const {
- std::unique_ptr<SampleVector> snapshot = SnapshotSampleVector();
+ std::unique_ptr<SampleVector> snapshot = SnapshotAllSamples();
*count = snapshot->TotalCount();
*sum = snapshot->sum();
uint32_t index = 0;
@@ -719,7 +802,7 @@ void Histogram::GetCountAndBucketData(Count* count,
if (i != bucket_count() - 1)
bucket_value->SetInteger("high", ranges(i + 1));
bucket_value->SetInteger("count", count_at_index);
- buckets->Set(index, bucket_value.release());
+ buckets->Set(index, std::move(bucket_value));
++index;
}
}
@@ -753,11 +836,17 @@ class LinearHistogram::Factory : public Histogram::Factory {
std::unique_ptr<HistogramBase> HeapAlloc(
const BucketRanges* ranges) override {
- return WrapUnique(new LinearHistogram(name_, minimum_, maximum_, ranges));
+ return WrapUnique(new LinearHistogram(GetPermanentName(name_), minimum_,
+ maximum_, ranges));
}
void FillHistogram(HistogramBase* base_histogram) override {
Histogram::Factory::FillHistogram(base_histogram);
+ // Normally, |base_histogram| should have type LINEAR_HISTOGRAM or be
+ // inherited from it. However, if it's expired, it will actually be a
+ // DUMMY_HISTOGRAM. Skip filling in that case.
+ if (base_histogram->GetHistogramType() == DUMMY_HISTOGRAM)
+ return;
LinearHistogram* histogram = static_cast<LinearHistogram*>(base_histogram);
// Set range descriptions.
if (descriptions_) {
@@ -774,15 +863,15 @@ class LinearHistogram::Factory : public Histogram::Factory {
DISALLOW_COPY_AND_ASSIGN(Factory);
};
-LinearHistogram::~LinearHistogram() {}
+LinearHistogram::~LinearHistogram() = default;
HistogramBase* LinearHistogram::FactoryGet(const std::string& name,
Sample minimum,
Sample maximum,
uint32_t bucket_count,
int32_t flags) {
- return FactoryGetWithRangeDescription(
- name, minimum, maximum, bucket_count, flags, NULL);
+ return FactoryGetWithRangeDescription(name, minimum, maximum, bucket_count,
+ flags, NULL);
}
HistogramBase* LinearHistogram::FactoryTimeGet(const std::string& name,
@@ -813,18 +902,16 @@ HistogramBase* LinearHistogram::FactoryTimeGet(const char* name,
}
std::unique_ptr<HistogramBase> LinearHistogram::PersistentCreate(
- const std::string& name,
+ const char* name,
Sample minimum,
Sample maximum,
const BucketRanges* ranges,
- HistogramBase::AtomicCount* counts,
- HistogramBase::AtomicCount* logged_counts,
- uint32_t counts_size,
+ const DelayedPersistentAllocation& counts,
+ const DelayedPersistentAllocation& logged_counts,
HistogramSamples::Metadata* meta,
HistogramSamples::Metadata* logged_meta) {
- return WrapUnique(new LinearHistogram(name, minimum, maximum, ranges,
- counts, logged_counts,
- counts_size, meta, logged_meta));
+ return WrapUnique(new LinearHistogram(name, minimum, maximum, ranges, counts,
+ logged_counts, meta, logged_meta));
}
HistogramBase* LinearHistogram::FactoryGetWithRangeDescription(
@@ -846,24 +933,29 @@ HistogramType LinearHistogram::GetHistogramType() const {
return LINEAR_HISTOGRAM;
}
-LinearHistogram::LinearHistogram(const std::string& name,
+LinearHistogram::LinearHistogram(const char* name,
Sample minimum,
Sample maximum,
const BucketRanges* ranges)
- : Histogram(name, minimum, maximum, ranges) {
-}
+ : Histogram(name, minimum, maximum, ranges) {}
-LinearHistogram::LinearHistogram(const std::string& name,
- Sample minimum,
- Sample maximum,
- const BucketRanges* ranges,
- HistogramBase::AtomicCount* counts,
- HistogramBase::AtomicCount* logged_counts,
- uint32_t counts_size,
- HistogramSamples::Metadata* meta,
- HistogramSamples::Metadata* logged_meta)
- : Histogram(name, minimum, maximum, ranges, counts, logged_counts,
- counts_size, meta, logged_meta) {}
+LinearHistogram::LinearHistogram(
+ const char* name,
+ Sample minimum,
+ Sample maximum,
+ const BucketRanges* ranges,
+ const DelayedPersistentAllocation& counts,
+ const DelayedPersistentAllocation& logged_counts,
+ HistogramSamples::Metadata* meta,
+ HistogramSamples::Metadata* logged_meta)
+ : Histogram(name,
+ minimum,
+ maximum,
+ ranges,
+ counts,
+ logged_counts,
+ meta,
+ logged_meta) {}
double LinearHistogram::GetBucketSize(Count current, uint32_t i) const {
DCHECK_GT(ranges(i + 1), ranges(i));
@@ -896,8 +988,6 @@ void LinearHistogram::InitializeBucketRanges(Sample minimum,
double linear_range =
(min * (bucket_count - 1 - i) + max * (i - 1)) / (bucket_count - 2);
ranges->set_range(i, static_cast<Sample>(linear_range + 0.5));
- // TODO(bcwhite): Remove once crbug/586622 is fixed.
- base::debug::Alias(&linear_range);
}
ranges->set_range(ranges->bucket_count(), HistogramBase::kSampleType_MAX);
ranges->ResetChecksum();
@@ -914,19 +1004,88 @@ HistogramBase* LinearHistogram::DeserializeInfoImpl(PickleIterator* iter) {
if (!ReadHistogramArguments(iter, &histogram_name, &flags, &declared_min,
&declared_max, &bucket_count, &range_checksum)) {
- return NULL;
+ return nullptr;
}
HistogramBase* histogram = LinearHistogram::FactoryGet(
histogram_name, declared_min, declared_max, bucket_count, flags);
+ if (!histogram)
+ return nullptr;
+
if (!ValidateRangeChecksum(*histogram, range_checksum)) {
// The serialized histogram might be corrupted.
- return NULL;
+ return nullptr;
}
return histogram;
}
//------------------------------------------------------------------------------
+// ScaledLinearHistogram: This is a wrapper around a LinearHistogram that
+// scales input counts.
+//------------------------------------------------------------------------------
+
+ScaledLinearHistogram::ScaledLinearHistogram(const char* name,
+ Sample minimum,
+ Sample maximum,
+ uint32_t bucket_count,
+ int32_t scale,
+ int32_t flags)
+ : histogram_(static_cast<LinearHistogram*>(
+ LinearHistogram::FactoryGet(name,
+ minimum,
+ maximum,
+ bucket_count,
+ flags))),
+ scale_(scale) {
+ DCHECK(histogram_);
+ DCHECK_LT(1, scale);
+ DCHECK_EQ(1, minimum);
+ CHECK_EQ(static_cast<Sample>(bucket_count), maximum - minimum + 2)
+ << " ScaledLinearHistogram requires buckets of size 1";
+
+ remainders_.resize(histogram_->bucket_count(), 0);
+}
+
+ScaledLinearHistogram::~ScaledLinearHistogram() = default;
+
+void ScaledLinearHistogram::AddScaledCount(Sample value, int count) {
+ if (count == 0)
+ return;
+ if (count < 0) {
+ NOTREACHED();
+ return;
+ }
+ const int32_t max_value =
+ static_cast<int32_t>(histogram_->bucket_count() - 1);
+ if (value > max_value)
+ value = max_value;
+ if (value < 0)
+ value = 0;
+
+ int scaled_count = count / scale_;
+ subtle::Atomic32 remainder = count - scaled_count * scale_;
+
+ // ScaledLinearHistogram currently requires 1-to-1 mappings between value
+ // and bucket which alleviates the need to do a bucket lookup here (something
+ // that is internal to the HistogramSamples object).
+ if (remainder > 0) {
+ remainder =
+ subtle::NoBarrier_AtomicIncrement(&remainders_[value], remainder);
+ // If remainder passes 1/2 scale, increment main count (thus rounding up).
+ // The remainder is decremented by the full scale, though, which will
+ // cause it to go negative and thus requrire another increase by the full
+ // scale amount before another bump of the scaled count.
+ if (remainder >= scale_ / 2) {
+ scaled_count += 1;
+ subtle::NoBarrier_AtomicIncrement(&remainders_[value], -scale_);
+ }
+ }
+
+ if (scaled_count > 0)
+ histogram_->AddCount(value, scaled_count);
+}
+
+//------------------------------------------------------------------------------
// This section provides implementation for BooleanHistogram.
//------------------------------------------------------------------------------
@@ -945,7 +1104,7 @@ class BooleanHistogram::Factory : public Histogram::Factory {
std::unique_ptr<HistogramBase> HeapAlloc(
const BucketRanges* ranges) override {
- return WrapUnique(new BooleanHistogram(name_, ranges));
+ return WrapUnique(new BooleanHistogram(GetPermanentName(name_), ranges));
}
private:
@@ -962,31 +1121,37 @@ HistogramBase* BooleanHistogram::FactoryGet(const char* name, int32_t flags) {
}
std::unique_ptr<HistogramBase> BooleanHistogram::PersistentCreate(
- const std::string& name,
+ const char* name,
const BucketRanges* ranges,
- HistogramBase::AtomicCount* counts,
- HistogramBase::AtomicCount* logged_counts,
+ const DelayedPersistentAllocation& counts,
+ const DelayedPersistentAllocation& logged_counts,
HistogramSamples::Metadata* meta,
HistogramSamples::Metadata* logged_meta) {
- return WrapUnique(new BooleanHistogram(
- name, ranges, counts, logged_counts, meta, logged_meta));
+ return WrapUnique(new BooleanHistogram(name, ranges, counts, logged_counts,
+ meta, logged_meta));
}
HistogramType BooleanHistogram::GetHistogramType() const {
return BOOLEAN_HISTOGRAM;
}
-BooleanHistogram::BooleanHistogram(const std::string& name,
- const BucketRanges* ranges)
+BooleanHistogram::BooleanHistogram(const char* name, const BucketRanges* ranges)
: LinearHistogram(name, 1, 2, ranges) {}
-BooleanHistogram::BooleanHistogram(const std::string& name,
- const BucketRanges* ranges,
- HistogramBase::AtomicCount* counts,
- HistogramBase::AtomicCount* logged_counts,
- HistogramSamples::Metadata* meta,
- HistogramSamples::Metadata* logged_meta)
- : LinearHistogram(name, 1, 2, ranges, counts, logged_counts, 2, meta,
+BooleanHistogram::BooleanHistogram(
+ const char* name,
+ const BucketRanges* ranges,
+ const DelayedPersistentAllocation& counts,
+ const DelayedPersistentAllocation& logged_counts,
+ HistogramSamples::Metadata* meta,
+ HistogramSamples::Metadata* logged_meta)
+ : LinearHistogram(name,
+ 1,
+ 2,
+ ranges,
+ counts,
+ logged_counts,
+ meta,
logged_meta) {}
HistogramBase* BooleanHistogram::DeserializeInfoImpl(PickleIterator* iter) {
@@ -999,14 +1164,17 @@ HistogramBase* BooleanHistogram::DeserializeInfoImpl(PickleIterator* iter) {
if (!ReadHistogramArguments(iter, &histogram_name, &flags, &declared_min,
&declared_max, &bucket_count, &range_checksum)) {
- return NULL;
+ return nullptr;
}
HistogramBase* histogram = BooleanHistogram::FactoryGet(
histogram_name, flags);
+ if (!histogram)
+ return nullptr;
+
if (!ValidateRangeChecksum(*histogram, range_checksum)) {
// The serialized histogram might be corrupted.
- return NULL;
+ return nullptr;
}
return histogram;
}
@@ -1044,7 +1212,7 @@ class CustomHistogram::Factory : public Histogram::Factory {
std::unique_ptr<HistogramBase> HeapAlloc(
const BucketRanges* ranges) override {
- return WrapUnique(new CustomHistogram(name_, ranges));
+ return WrapUnique(new CustomHistogram(GetPermanentName(name_), ranges));
}
private:
@@ -1070,15 +1238,14 @@ HistogramBase* CustomHistogram::FactoryGet(
}
std::unique_ptr<HistogramBase> CustomHistogram::PersistentCreate(
- const std::string& name,
+ const char* name,
const BucketRanges* ranges,
- HistogramBase::AtomicCount* counts,
- HistogramBase::AtomicCount* logged_counts,
- uint32_t counts_size,
+ const DelayedPersistentAllocation& counts,
+ const DelayedPersistentAllocation& logged_counts,
HistogramSamples::Metadata* meta,
HistogramSamples::Metadata* logged_meta) {
- return WrapUnique(new CustomHistogram(
- name, ranges, counts, logged_counts, counts_size, meta, logged_meta));
+ return WrapUnique(new CustomHistogram(name, ranges, counts, logged_counts,
+ meta, logged_meta));
}
HistogramType CustomHistogram::GetHistogramType() const {
@@ -1086,11 +1253,10 @@ HistogramType CustomHistogram::GetHistogramType() const {
}
// static
-std::vector<Sample> CustomHistogram::ArrayToCustomRanges(
- const Sample* values, uint32_t num_values) {
+std::vector<Sample> CustomHistogram::ArrayToCustomEnumRanges(
+ base::span<const Sample> values) {
std::vector<Sample> all_values;
- for (uint32_t i = 0; i < num_values; ++i) {
- Sample value = values[i];
+ for (Sample value : values) {
all_values.push_back(value);
// Ensure that a guard bucket is added. If we end up with duplicate
@@ -1100,41 +1266,35 @@ std::vector<Sample> CustomHistogram::ArrayToCustomRanges(
return all_values;
}
-CustomHistogram::CustomHistogram(const std::string& name,
- const BucketRanges* ranges)
+CustomHistogram::CustomHistogram(const char* name, const BucketRanges* ranges)
: Histogram(name,
ranges->range(1),
ranges->range(ranges->bucket_count() - 1),
ranges) {}
-CustomHistogram::CustomHistogram(const std::string& name,
- const BucketRanges* ranges,
- HistogramBase::AtomicCount* counts,
- HistogramBase::AtomicCount* logged_counts,
- uint32_t counts_size,
- HistogramSamples::Metadata* meta,
- HistogramSamples::Metadata* logged_meta)
+CustomHistogram::CustomHistogram(
+ const char* name,
+ const BucketRanges* ranges,
+ const DelayedPersistentAllocation& counts,
+ const DelayedPersistentAllocation& logged_counts,
+ HistogramSamples::Metadata* meta,
+ HistogramSamples::Metadata* logged_meta)
: Histogram(name,
ranges->range(1),
ranges->range(ranges->bucket_count() - 1),
ranges,
counts,
logged_counts,
- counts_size,
meta,
logged_meta) {}
-bool CustomHistogram::SerializeInfoImpl(Pickle* pickle) const {
- if (!Histogram::SerializeInfoImpl(pickle))
- return false;
+void CustomHistogram::SerializeInfoImpl(Pickle* pickle) const {
+ Histogram::SerializeInfoImpl(pickle);
// Serialize ranges. First and last ranges are alwasy 0 and INT_MAX, so don't
// write them.
- for (uint32_t i = 1; i < bucket_ranges()->bucket_count(); ++i) {
- if (!pickle->WriteInt(bucket_ranges()->range(i)))
- return false;
- }
- return true;
+ for (uint32_t i = 1; i < bucket_ranges()->bucket_count(); ++i)
+ pickle->WriteInt(bucket_ranges()->range(i));
}
double CustomHistogram::GetBucketSize(Count current, uint32_t i) const {
@@ -1154,7 +1314,7 @@ HistogramBase* CustomHistogram::DeserializeInfoImpl(PickleIterator* iter) {
if (!ReadHistogramArguments(iter, &histogram_name, &flags, &declared_min,
&declared_max, &bucket_count, &range_checksum)) {
- return NULL;
+ return nullptr;
}
// First and last ranges are not serialized.
@@ -1162,14 +1322,17 @@ HistogramBase* CustomHistogram::DeserializeInfoImpl(PickleIterator* iter) {
for (uint32_t i = 0; i < sample_ranges.size(); ++i) {
if (!iter->ReadInt(&sample_ranges[i]))
- return NULL;
+ return nullptr;
}
HistogramBase* histogram = CustomHistogram::FactoryGet(
histogram_name, sample_ranges, flags);
+ if (!histogram)
+ return nullptr;
+
if (!ValidateRangeChecksum(*histogram, range_checksum)) {
// The serialized histogram might be corrupted.
- return NULL;
+ return nullptr;
}
return histogram;
}
diff --git a/base/metrics/histogram.h b/base/metrics/histogram.h
index a76dd63226..bd5a9ebe38 100644
--- a/base/metrics/histogram.h
+++ b/base/metrics/histogram.h
@@ -74,23 +74,28 @@
#include "base/base_export.h"
#include "base/compiler_specific.h"
+#include "base/containers/span.h"
#include "base/gtest_prod_util.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/metrics/bucket_ranges.h"
#include "base/metrics/histogram_base.h"
#include "base/metrics/histogram_samples.h"
+#include "base/strings/string_piece.h"
#include "base/time/time.h"
namespace base {
class BooleanHistogram;
class CustomHistogram;
+class DelayedPersistentAllocation;
class Histogram;
+class HistogramTest;
class LinearHistogram;
class Pickle;
class PickleIterator;
class SampleVector;
+class SampleVectorBase;
class BASE_EXPORT Histogram : public HistogramBase {
public:
@@ -121,10 +126,15 @@ class BASE_EXPORT Histogram : public HistogramBase {
base::TimeDelta maximum,
uint32_t bucket_count,
int32_t flags);
-
- // Overloads of the above two functions that take a const char* |name| param,
- // to avoid code bloat from the std::string constructor being inlined into
- // call sites.
+ static HistogramBase* FactoryMicrosecondsTimeGet(const std::string& name,
+ base::TimeDelta minimum,
+ base::TimeDelta maximum,
+ uint32_t bucket_count,
+ int32_t flags);
+
+ // Overloads of the above functions that take a const char* |name| param, to
+ // avoid code bloat from the std::string constructor being inlined into call
+ // sites.
static HistogramBase* FactoryGet(const char* name,
Sample minimum,
Sample maximum,
@@ -135,16 +145,20 @@ class BASE_EXPORT Histogram : public HistogramBase {
base::TimeDelta maximum,
uint32_t bucket_count,
int32_t flags);
+ static HistogramBase* FactoryMicrosecondsTimeGet(const char* name,
+ base::TimeDelta minimum,
+ base::TimeDelta maximum,
+ uint32_t bucket_count,
+ int32_t flags);
// Create a histogram using data in persistent storage.
static std::unique_ptr<HistogramBase> PersistentCreate(
- const std::string& name,
+ const char* name,
Sample minimum,
Sample maximum,
const BucketRanges* ranges,
- HistogramBase::AtomicCount* counts,
- HistogramBase::AtomicCount* logged_counts,
- uint32_t counts_size,
+ const DelayedPersistentAllocation& counts,
+ const DelayedPersistentAllocation& logged_counts,
HistogramSamples::Metadata* meta,
HistogramSamples::Metadata* logged_meta);
@@ -172,19 +186,20 @@ class BASE_EXPORT Histogram : public HistogramBase {
//----------------------------------------------------------------------------
// Accessors for factory construction, serialization and testing.
//----------------------------------------------------------------------------
- Sample declared_min() const { return declared_min_; }
- Sample declared_max() const { return declared_max_; }
+ const BucketRanges* bucket_ranges() const;
+ Sample declared_min() const;
+ Sample declared_max() const;
virtual Sample ranges(uint32_t i) const;
virtual uint32_t bucket_count() const;
- const BucketRanges* bucket_ranges() const { return bucket_ranges_; }
// This function validates histogram construction arguments. It returns false
- // if some of the arguments are totally bad.
+ // if some of the arguments are bad but also corrects them so they should
+ // function on non-dcheck builds without crashing.
// Note. Currently it allow some bad input, e.g. 0 as minimum, but silently
// converts it to good input: 1.
- // TODO(kaiwang): Be more restrict and return false for any bad input, and
- // make this a readonly validating function.
- static bool InspectConstructionArguments(const std::string& name,
+ // TODO(bcwhite): Use false returns to create "sink" histograms so that bad
+ // data doesn't create confusion on the servers.
+ static bool InspectConstructionArguments(StringPiece name,
Sample* minimum,
Sample* maximum,
uint32_t* bucket_count);
@@ -205,6 +220,10 @@ class BASE_EXPORT Histogram : public HistogramBase {
void WriteHTMLGraph(std::string* output) const override;
void WriteAscii(std::string* output) const override;
+ // Validates the histogram contents and CHECKs on errors.
+ // TODO(bcwhite): Remove this after https://crbug/836875.
+ void ValidateHistogramContents() const override;
+
protected:
// This class, defined entirely within the .cc file, contains all the
// common logic for building a Histogram and can be overridden by more
@@ -215,7 +234,7 @@ class BASE_EXPORT Histogram : public HistogramBase {
// |ranges| should contain the underflow and overflow buckets. See top
// comments for example.
- Histogram(const std::string& name,
+ Histogram(const char* name,
Sample minimum,
Sample maximum,
const BucketRanges* ranges);
@@ -226,18 +245,17 @@ class BASE_EXPORT Histogram : public HistogramBase {
// the life of this memory is managed externally and exceeds the lifetime
// of this object. Practically, this memory is never released until the
// process exits and the OS cleans it up.
- Histogram(const std::string& name,
+ Histogram(const char* name,
Sample minimum,
Sample maximum,
const BucketRanges* ranges,
- HistogramBase::AtomicCount* counts,
- HistogramBase::AtomicCount* logged_counts,
- uint32_t counts_size,
+ const DelayedPersistentAllocation& counts,
+ const DelayedPersistentAllocation& logged_counts,
HistogramSamples::Metadata* meta,
HistogramSamples::Metadata* logged_meta);
// HistogramBase implementation:
- bool SerializeInfoImpl(base::Pickle* pickle) const override;
+ void SerializeInfoImpl(base::Pickle* pickle) const override;
// Method to override to skip the display of the i'th bucket if it's empty.
virtual bool PrintEmptyBucket(uint32_t index) const;
@@ -252,6 +270,7 @@ class BASE_EXPORT Histogram : public HistogramBase {
private:
// Allow tests to corrupt our innards for testing purposes.
+ friend class HistogramTest;
FRIEND_TEST_ALL_PREFIXES(HistogramTest, BoundsTest);
FRIEND_TEST_ALL_PREFIXES(HistogramTest, BucketPlacementTest);
FRIEND_TEST_ALL_PREFIXES(HistogramTest, CorruptSampleCounts);
@@ -263,8 +282,13 @@ class BASE_EXPORT Histogram : public HistogramBase {
base::PickleIterator* iter);
static HistogramBase* DeserializeInfoImpl(base::PickleIterator* iter);
- // Implementation of SnapshotSamples function.
- std::unique_ptr<SampleVector> SnapshotSampleVector() const;
+ // Create a snapshot containing all samples (both logged and unlogged).
+ // Implementation of SnapshotSamples method with a more specific type for
+ // internal use.
+ std::unique_ptr<SampleVector> SnapshotAllSamples() const;
+
+ // Create a copy of unlogged samples.
+ std::unique_ptr<SampleVector> SnapshotUnloggedSamples() const;
//----------------------------------------------------------------------------
// Helpers for emitting Ascii graphic. Each method appends data to output.
@@ -274,10 +298,10 @@ class BASE_EXPORT Histogram : public HistogramBase {
std::string* output) const;
// Find out how large (graphically) the largest bucket will appear to be.
- double GetPeakBucketSize(const SampleVector& samples) const;
+ double GetPeakBucketSize(const SampleVectorBase& samples) const;
// Write a common header message describing this histogram.
- void WriteAsciiHeader(const SampleVector& samples,
+ void WriteAsciiHeader(const SampleVectorBase& samples,
Count sample_count,
std::string* output) const;
@@ -296,22 +320,17 @@ class BASE_EXPORT Histogram : public HistogramBase {
int64_t* sum,
ListValue* buckets) const override;
- // Does not own this object. Should get from StatisticsRecorder.
- const BucketRanges* bucket_ranges_;
-
- Sample declared_min_; // Less than this goes into the first bucket.
- Sample declared_max_; // Over this goes into the last bucket.
+ // Samples that have not yet been logged with SnapshotDelta().
+ std::unique_ptr<SampleVectorBase> unlogged_samples_;
- // Finally, provide the state that changes with the addition of each new
- // sample.
- std::unique_ptr<SampleVector> samples_;
-
- // Also keep a previous uploaded state for calculating deltas.
- std::unique_ptr<HistogramSamples> logged_samples_;
+ // Accumulation of all samples that have been logged with SnapshotDelta().
+ std::unique_ptr<SampleVectorBase> logged_samples_;
+#if DCHECK_IS_ON() // Don't waste memory if it won't be used.
// Flag to indicate if PrepareFinalDelta has been previously called. It is
// used to DCHECK that a final delta is not created multiple times.
mutable bool final_delta_created_ = false;
+#endif
DISALLOW_COPY_AND_ASSIGN(Histogram);
};
@@ -353,13 +372,12 @@ class BASE_EXPORT LinearHistogram : public Histogram {
// Create a histogram using data in persistent storage.
static std::unique_ptr<HistogramBase> PersistentCreate(
- const std::string& name,
+ const char* name,
Sample minimum,
Sample maximum,
const BucketRanges* ranges,
- HistogramBase::AtomicCount* counts,
- HistogramBase::AtomicCount* logged_counts,
- uint32_t counts_size,
+ const DelayedPersistentAllocation& counts,
+ const DelayedPersistentAllocation& logged_counts,
HistogramSamples::Metadata* meta,
HistogramSamples::Metadata* logged_meta);
@@ -391,18 +409,17 @@ class BASE_EXPORT LinearHistogram : public Histogram {
protected:
class Factory;
- LinearHistogram(const std::string& name,
+ LinearHistogram(const char* name,
Sample minimum,
Sample maximum,
const BucketRanges* ranges);
- LinearHistogram(const std::string& name,
+ LinearHistogram(const char* name,
Sample minimum,
Sample maximum,
const BucketRanges* ranges,
- HistogramBase::AtomicCount* counts,
- HistogramBase::AtomicCount* logged_counts,
- uint32_t counts_size,
+ const DelayedPersistentAllocation& counts,
+ const DelayedPersistentAllocation& logged_counts,
HistogramSamples::Metadata* meta,
HistogramSamples::Metadata* logged_meta);
@@ -432,6 +449,55 @@ class BASE_EXPORT LinearHistogram : public Histogram {
//------------------------------------------------------------------------------
+// ScaledLinearHistogram is a wrapper around a linear histogram that scales the
+// counts down by some factor. Remainder values are kept locally but lost when
+// uploaded or serialized. The integral counts are rounded up/down so should
+// average to the correct value when many reports are added.
+//
+// This is most useful when adding many counts at once via AddCount() that can
+// cause overflows of the 31-bit counters, usually with an enum as the value.
+class BASE_EXPORT ScaledLinearHistogram {
+ using AtomicCount = Histogram::AtomicCount;
+ using Sample = Histogram::Sample;
+
+ public:
+ // Currently only works with "exact" linear histograms: minimum=1, maximum=N,
+ // and bucket_count=N+1.
+ ScaledLinearHistogram(const char* name,
+ Sample minimum,
+ Sample maximum,
+ uint32_t bucket_count,
+ int32_t scale,
+ int32_t flags);
+
+ ~ScaledLinearHistogram();
+
+ // Like AddCount() but actually accumulates |count|/|scale| and increments
+ // the accumulated remainder by |count|%|scale|. An additional increment
+ // is done when the remainder has grown sufficiently large.
+ void AddScaledCount(Sample value, int count);
+
+ int32_t scale() const { return scale_; }
+ LinearHistogram* histogram() { return histogram_; }
+
+ private:
+ // Pointer to the underlying histogram. Ownership of it remains with
+ // the statistics-recorder.
+ LinearHistogram* const histogram_;
+
+ // The scale factor of the sample counts.
+ const int32_t scale_;
+
+ // A vector of "remainder" counts indexed by bucket number. These values
+ // may be negative as the scaled count is actually bumped once the
+ // remainder is 1/2 way to the scale value (thus "rounding").
+ std::vector<AtomicCount> remainders_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScaledLinearHistogram);
+};
+
+//------------------------------------------------------------------------------
+
// BooleanHistogram is a histogram for booleans.
class BASE_EXPORT BooleanHistogram : public LinearHistogram {
public:
@@ -444,10 +510,10 @@ class BASE_EXPORT BooleanHistogram : public LinearHistogram {
// Create a histogram using data in persistent storage.
static std::unique_ptr<HistogramBase> PersistentCreate(
- const std::string& name,
+ const char* name,
const BucketRanges* ranges,
- HistogramBase::AtomicCount* counts,
- HistogramBase::AtomicCount* logged_counts,
+ const DelayedPersistentAllocation& counts,
+ const DelayedPersistentAllocation& logged_counts,
HistogramSamples::Metadata* meta,
HistogramSamples::Metadata* logged_meta);
@@ -457,11 +523,11 @@ class BASE_EXPORT BooleanHistogram : public LinearHistogram {
class Factory;
private:
- BooleanHistogram(const std::string& name, const BucketRanges* ranges);
- BooleanHistogram(const std::string& name,
+ BooleanHistogram(const char* name, const BucketRanges* ranges);
+ BooleanHistogram(const char* name,
const BucketRanges* ranges,
- HistogramBase::AtomicCount* counts,
- HistogramBase::AtomicCount* logged_counts,
+ const DelayedPersistentAllocation& counts,
+ const DelayedPersistentAllocation& logged_counts,
HistogramSamples::Metadata* meta,
HistogramSamples::Metadata* logged_meta);
@@ -494,11 +560,10 @@ class BASE_EXPORT CustomHistogram : public Histogram {
// Create a histogram using data in persistent storage.
static std::unique_ptr<HistogramBase> PersistentCreate(
- const std::string& name,
+ const char* name,
const BucketRanges* ranges,
- HistogramBase::AtomicCount* counts,
- HistogramBase::AtomicCount* logged_counts,
- uint32_t counts_size,
+ const DelayedPersistentAllocation& counts,
+ const DelayedPersistentAllocation& logged_counts,
HistogramSamples::Metadata* meta,
HistogramSamples::Metadata* logged_meta);
@@ -510,25 +575,23 @@ class BASE_EXPORT CustomHistogram : public Histogram {
// This function ensures that a guard bucket exists right after any
// valid sample value (unless the next higher sample is also a valid value),
// so that invalid samples never fall into the same bucket as valid samples.
- // TODO(kaiwang): Change name to ArrayToCustomEnumRanges.
- static std::vector<Sample> ArrayToCustomRanges(const Sample* values,
- uint32_t num_values);
+ static std::vector<Sample> ArrayToCustomEnumRanges(
+ base::span<const Sample> values);
+
protected:
class Factory;
- CustomHistogram(const std::string& name,
- const BucketRanges* ranges);
+ CustomHistogram(const char* name, const BucketRanges* ranges);
- CustomHistogram(const std::string& name,
+ CustomHistogram(const char* name,
const BucketRanges* ranges,
- HistogramBase::AtomicCount* counts,
- HistogramBase::AtomicCount* logged_counts,
- uint32_t counts_size,
+ const DelayedPersistentAllocation& counts,
+ const DelayedPersistentAllocation& logged_counts,
HistogramSamples::Metadata* meta,
HistogramSamples::Metadata* logged_meta);
// HistogramBase implementation:
- bool SerializeInfoImpl(base::Pickle* pickle) const override;
+ void SerializeInfoImpl(base::Pickle* pickle) const override;
double GetBucketSize(Count current, uint32_t i) const override;
diff --git a/base/metrics/histogram_base.cc b/base/metrics/histogram_base.cc
index 671cad2429..990d9f5e04 100644
--- a/base/metrics/histogram_base.cc
+++ b/base/metrics/histogram_base.cc
@@ -7,17 +7,23 @@
#include <limits.h>
#include <memory>
+#include <set>
#include <utility>
#include "base/json/json_string_value_serializer.h"
+#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_macros.h"
#include "base/metrics/histogram_samples.h"
#include "base/metrics/sparse_histogram.h"
#include "base/metrics/statistics_recorder.h"
+#include "base/numerics/safe_conversions.h"
#include "base/pickle.h"
#include "base/process/process_handle.h"
+#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
+#include "base/synchronization/lock.h"
#include "base/values.h"
namespace base {
@@ -34,6 +40,8 @@ std::string HistogramTypeToString(HistogramType type) {
return "CUSTOM_HISTOGRAM";
case SPARSE_HISTOGRAM:
return "SPARSE_HISTOGRAM";
+ case DUMMY_HISTOGRAM:
+ return "DUMMY_HISTOGRAM";
}
NOTREACHED();
return "UNKNOWN";
@@ -42,7 +50,7 @@ std::string HistogramTypeToString(HistogramType type) {
HistogramBase* DeserializeHistogramInfo(PickleIterator* iter) {
int type;
if (!iter->ReadInt(&type))
- return NULL;
+ return nullptr;
switch (type) {
case HISTOGRAM:
@@ -56,21 +64,19 @@ HistogramBase* DeserializeHistogramInfo(PickleIterator* iter) {
case SPARSE_HISTOGRAM:
return SparseHistogram::DeserializeInfoImpl(iter);
default:
- return NULL;
+ return nullptr;
}
}
const HistogramBase::Sample HistogramBase::kSampleType_MAX = INT_MAX;
-HistogramBase* HistogramBase::report_histogram_ = nullptr;
-HistogramBase::HistogramBase(const std::string& name)
- : histogram_name_(name),
- flags_(kNoFlags) {}
+HistogramBase::HistogramBase(const char* name)
+ : histogram_name_(name), flags_(kNoFlags) {}
-HistogramBase::~HistogramBase() {}
+HistogramBase::~HistogramBase() = default;
void HistogramBase::CheckName(const StringPiece& name) const {
- DCHECK_EQ(histogram_name(), name);
+ DCHECK_EQ(StringPiece(histogram_name()), name);
}
void HistogramBase::SetFlags(int32_t flags) {
@@ -83,18 +89,49 @@ void HistogramBase::ClearFlags(int32_t flags) {
subtle::NoBarrier_Store(&flags_, old_flags & ~flags);
}
-void HistogramBase::AddTime(const TimeDelta& time) {
- Add(static_cast<Sample>(time.InMilliseconds()));
+void HistogramBase::AddScaled(Sample value, int count, int scale) {
+ DCHECK_LT(0, scale);
+
+ // Convert raw count and probabilistically round up/down if the remainder
+ // is more than a random number [0, scale). This gives a more accurate
+ // count when there are a large number of records. RandInt is "inclusive",
+ // hence the -1 for the max value.
+ int64_t count_scaled = count / scale;
+ if (count - (count_scaled * scale) > base::RandInt(0, scale - 1))
+ count_scaled += 1;
+ if (count_scaled == 0)
+ return;
+
+ AddCount(value, count_scaled);
+}
+
+void HistogramBase::AddKilo(Sample value, int count) {
+ AddScaled(value, count, 1000);
+}
+
+void HistogramBase::AddKiB(Sample value, int count) {
+ AddScaled(value, count, 1024);
+}
+
+void HistogramBase::AddTimeMillisecondsGranularity(const TimeDelta& time) {
+ Add(saturated_cast<Sample>(time.InMilliseconds()));
+}
+
+void HistogramBase::AddTimeMicrosecondsGranularity(const TimeDelta& time) {
+ // Intentionally drop high-resolution reports on clients with low-resolution
+ // clocks. High-resolution metrics cannot make use of low-resolution data and
+ // reporting it merely adds noise to the metric. https://crbug.com/807615#c16
+ if (TimeTicks::IsHighResolution())
+ Add(saturated_cast<Sample>(time.InMicroseconds()));
}
void HistogramBase::AddBoolean(bool value) {
Add(value ? 1 : 0);
}
-bool HistogramBase::SerializeInfo(Pickle* pickle) const {
- if (!pickle->WriteInt(GetHistogramType()))
- return false;
- return SerializeInfoImpl(pickle);
+void HistogramBase::SerializeInfo(Pickle* pickle) const {
+ pickle->WriteInt(GetHistogramType());
+ SerializeInfoImpl(pickle);
}
uint32_t HistogramBase::FindCorruption(const HistogramSamples& samples) const {
@@ -102,7 +139,10 @@ uint32_t HistogramBase::FindCorruption(const HistogramSamples& samples) const {
return NO_INCONSISTENCIES;
}
-void HistogramBase::WriteJSON(std::string* output) const {
+void HistogramBase::ValidateHistogramContents() const {}
+
+void HistogramBase::WriteJSON(std::string* output,
+ JSONVerbosityLevel verbosity_level) const {
Count count;
int64_t sum;
std::unique_ptr<ListValue> buckets(new ListValue());
@@ -117,37 +157,12 @@ void HistogramBase::WriteJSON(std::string* output) const {
root.SetDouble("sum", static_cast<double>(sum));
root.SetInteger("flags", flags());
root.Set("params", std::move(parameters));
- root.Set("buckets", std::move(buckets));
+ if (verbosity_level != JSON_VERBOSITY_LEVEL_OMIT_BUCKETS)
+ root.Set("buckets", std::move(buckets));
root.SetInteger("pid", GetUniqueIdForProcess());
serializer.Serialize(root);
}
-// static
-void HistogramBase::EnableActivityReportHistogram(
- const std::string& process_type) {
- if (report_histogram_)
- return;
-
- size_t existing = StatisticsRecorder::GetHistogramCount();
- if (existing != 0) {
- DVLOG(1) << existing
- << " histograms were created before reporting was enabled.";
- }
-
- std::string name =
- "UMA.Histograms.Activity" +
- (process_type.empty() ? process_type : "." + process_type);
-
- // Calling FactoryGet() here rather than using a histogram-macro works
- // around some problems with tests that could end up seeing the results
- // histogram when not expected due to a bad interaction between
- // HistogramTester and StatisticsRecorder.
- report_histogram_ = LinearHistogram::FactoryGet(
- name, 1, HISTOGRAM_REPORT_MAX, HISTOGRAM_REPORT_MAX + 1,
- kUmaTargetedHistogramFlag);
- report_histogram_->Add(HISTOGRAM_REPORT_CREATED);
-}
-
void HistogramBase::FindAndRunCallback(HistogramBase::Sample sample) const {
if ((flags() & kCallbackExists) == 0)
return;
@@ -185,46 +200,16 @@ void HistogramBase::WriteAsciiBucketValue(Count current,
}
// static
-void HistogramBase::ReportHistogramActivity(const HistogramBase& histogram,
- ReportActivity activity) {
- if (!report_histogram_)
- return;
-
- const int32_t flags = histogram.flags_;
- HistogramReport report_type = HISTOGRAM_REPORT_MAX;
- switch (activity) {
- case HISTOGRAM_CREATED:
- report_histogram_->Add(HISTOGRAM_REPORT_HISTOGRAM_CREATED);
- switch (histogram.GetHistogramType()) {
- case HISTOGRAM:
- report_type = HISTOGRAM_REPORT_TYPE_LOGARITHMIC;
- break;
- case LINEAR_HISTOGRAM:
- report_type = HISTOGRAM_REPORT_TYPE_LINEAR;
- break;
- case BOOLEAN_HISTOGRAM:
- report_type = HISTOGRAM_REPORT_TYPE_BOOLEAN;
- break;
- case CUSTOM_HISTOGRAM:
- report_type = HISTOGRAM_REPORT_TYPE_CUSTOM;
- break;
- case SPARSE_HISTOGRAM:
- report_type = HISTOGRAM_REPORT_TYPE_SPARSE;
- break;
- }
- report_histogram_->Add(report_type);
- if (flags & kIsPersistent)
- report_histogram_->Add(HISTOGRAM_REPORT_FLAG_PERSISTENT);
- if ((flags & kUmaStabilityHistogramFlag) == kUmaStabilityHistogramFlag)
- report_histogram_->Add(HISTOGRAM_REPORT_FLAG_UMA_STABILITY);
- else if (flags & kUmaTargetedHistogramFlag)
- report_histogram_->Add(HISTOGRAM_REPORT_FLAG_UMA_TARGETED);
- break;
-
- case HISTOGRAM_LOOKUP:
- report_histogram_->Add(HISTOGRAM_REPORT_HISTOGRAM_LOOKUP);
- break;
- }
+char const* HistogramBase::GetPermanentName(const std::string& name) {
+ // A set of histogram names that provides the "permanent" lifetime required
+ // by histogram objects for those strings that are not already code constants
+ // or held in persistent memory.
+ static LazyInstance<std::set<std::string>>::Leaky permanent_names;
+ static LazyInstance<Lock>::Leaky permanent_names_lock;
+
+ AutoLock lock(permanent_names_lock.Get());
+ auto result = permanent_names.Get().insert(name);
+ return result.first->c_str();
}
} // namespace base
diff --git a/base/metrics/histogram_base.h b/base/metrics/histogram_base.h
index 4f5ba049bc..f128ff2d9d 100644
--- a/base/metrics/histogram_base.h
+++ b/base/metrics/histogram_base.h
@@ -39,6 +39,17 @@ enum HistogramType {
BOOLEAN_HISTOGRAM,
CUSTOM_HISTOGRAM,
SPARSE_HISTOGRAM,
+ DUMMY_HISTOGRAM,
+};
+
+// Controls the verbosity of the information when the histogram is serialized to
+// a JSON.
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.base.metrics
+enum JSONVerbosityLevel {
+ // The histogram is completely serialized.
+ JSON_VERBOSITY_LEVEL_FULL,
+ // The bucket information is not serialized.
+ JSON_VERBOSITY_LEVEL_OMIT_BUCKETS,
};
std::string HistogramTypeToString(HistogramType type);
@@ -133,15 +144,17 @@ class BASE_EXPORT HistogramBase {
NEVER_EXCEEDED_VALUE = 0x10,
};
- explicit HistogramBase(const std::string& name);
+ // Construct the base histogram. The name is not copied; it's up to the
+ // caller to ensure that it lives at least as long as this object.
+ explicit HistogramBase(const char* name);
virtual ~HistogramBase();
- const std::string& histogram_name() const { return histogram_name_; }
+ const char* histogram_name() const { return histogram_name_; }
- // Comapres |name| to the histogram name and triggers a DCHECK if they do not
+ // Compares |name| to the histogram name and triggers a DCHECK if they do not
// match. This is a helper function used by histogram macros, which results in
// in more compact machine code being generated by the macros.
- void CheckName(const StringPiece& name) const;
+ virtual void CheckName(const StringPiece& name) const;
// Get a unique ID for this histogram's samples.
virtual uint64_t name_hash() const = 0;
@@ -169,8 +182,21 @@ class BASE_EXPORT HistogramBase {
// than or equal to 1.
virtual void AddCount(Sample value, int count) = 0;
- // 2 convenient functions that call Add(Sample).
- void AddTime(const TimeDelta& time);
+ // Similar to above but divides |count| by the |scale| amount. Probabilistic
+ // rounding is used to yield a reasonably accurate total when many samples
+ // are added. Methods for common cases of scales 1000 and 1024 are included.
+ // The ScaledLinearHistogram (which can also used be for enumerations) may be
+ // a better (and faster) solution.
+ void AddScaled(Sample value, int count, int scale);
+ void AddKilo(Sample value, int count); // scale=1000
+ void AddKiB(Sample value, int count); // scale=1024
+
+ // Convenient functions that call Add(Sample).
+ void AddTime(const TimeDelta& time) { AddTimeMillisecondsGranularity(time); }
+ void AddTimeMillisecondsGranularity(const TimeDelta& time);
+ // Note: AddTimeMicrosecondsGranularity() drops the report if this client
+ // doesn't have a high-resolution clock.
+ void AddTimeMicrosecondsGranularity(const TimeDelta& time);
void AddBoolean(bool value);
virtual void AddSamples(const HistogramSamples& samples) = 0;
@@ -179,7 +205,7 @@ class BASE_EXPORT HistogramBase {
// Serialize the histogram info into |pickle|.
// Note: This only serializes the construction arguments of the histogram, but
// does not serialize the samples.
- bool SerializeInfo(base::Pickle* pickle) const;
+ void SerializeInfo(base::Pickle* pickle) const;
// Try to find out data corruption from histogram and the samples.
// The returned value is a combination of Inconsistency enum.
@@ -187,6 +213,9 @@ class BASE_EXPORT HistogramBase {
// Snapshot the current complete set of sample data.
// Override with atomic/locked snapshot if needed.
+ // NOTE: this data can overflow for long-running sessions. It should be
+ // handled with care and this method is recommended to be used only
+ // in about:histograms and test code.
virtual std::unique_ptr<HistogramSamples> SnapshotSamples() const = 0;
// Calculate the change (delta) in histogram counts since the previous call
@@ -207,24 +236,20 @@ class BASE_EXPORT HistogramBase {
virtual void WriteHTMLGraph(std::string* output) const = 0;
virtual void WriteAscii(std::string* output) const = 0;
- // Produce a JSON representation of the histogram. This is implemented with
- // the help of GetParameters and GetCountAndBucketData; overwrite them to
- // customize the output.
- void WriteJSON(std::string* output) const;
+ // TODO(bcwhite): Remove this after https://crbug/836875.
+ virtual void ValidateHistogramContents() const;
- // This enables a histogram that reports the what types of histograms are
- // created and their flags. It must be called while still single-threaded.
- //
- // IMPORTANT: Callers must update tools/metrics/histograms/histograms.xml
- // with the following histogram:
- // UMA.Histograms.process_type.Creations
- static void EnableActivityReportHistogram(const std::string& process_type);
+ // Produce a JSON representation of the histogram with |verbosity_level| as
+ // the serialization verbosity. This is implemented with the help of
+ // GetParameters and GetCountAndBucketData; overwrite them to customize the
+ // output.
+ void WriteJSON(std::string* output, JSONVerbosityLevel verbosity_level) const;
protected:
enum ReportActivity { HISTOGRAM_CREATED, HISTOGRAM_LOOKUP };
// Subclasses should implement this function to make SerializeInfo work.
- virtual bool SerializeInfoImpl(base::Pickle* pickle) const = 0;
+ virtual void SerializeInfoImpl(base::Pickle* pickle) const = 0;
// Writes information about the construction parameters in |params|.
virtual void GetParameters(DictionaryValue* params) const = 0;
@@ -254,17 +279,24 @@ class BASE_EXPORT HistogramBase {
// passing |sample| as the parameter.
void FindAndRunCallback(Sample sample) const;
- // Update report with an |activity| that occurred for |histogram|.
- static void ReportHistogramActivity(const HistogramBase& histogram,
- ReportActivity activicty);
-
- // Retrieves the global histogram reporting what histograms are created.
- static HistogramBase* report_histogram_;
+ // Gets a permanent string that can be used for histogram objects when the
+ // original is not a code constant or held in persistent memory.
+ static const char* GetPermanentName(const std::string& name);
private:
friend class HistogramBaseTest;
- const std::string histogram_name_;
+ // A pointer to permanent storage where the histogram name is held. This can
+ // be code space or the output of GetPermanentName() or any other storage
+ // that is known to never change. This is not StringPiece because (a) char*
+ // is 1/2 the size and (b) StringPiece transparently casts from std::string
+ // which can easily lead to a pointer to non-permanent space.
+ // For persistent histograms, this will simply point into the persistent
+ // memory segment, thus avoiding duplication. For heap histograms, the
+ // GetPermanentName method will create the necessary copy.
+ const char* const histogram_name_;
+
+ // Additional information about the histogram.
AtomicCount flags_;
DISALLOW_COPY_AND_ASSIGN(HistogramBase);
diff --git a/base/metrics/histogram_base_unittest.cc b/base/metrics/histogram_base_unittest.cc
index 1eb8fd4608..0314ef4d61 100644
--- a/base/metrics/histogram_base_unittest.cc
+++ b/base/metrics/histogram_base_unittest.cc
@@ -6,6 +6,7 @@
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_base.h"
+#include "base/metrics/sample_vector.h"
#include "base/metrics/sparse_histogram.h"
#include "base/metrics/statistics_recorder.h"
#include "base/pickle.h"
@@ -21,9 +22,7 @@ class HistogramBaseTest : public testing::Test {
ResetStatisticsRecorder();
}
- ~HistogramBaseTest() override {
- HistogramBase::report_histogram_ = nullptr;
- }
+ ~HistogramBaseTest() override = default;
void ResetStatisticsRecorder() {
// It is necessary to fully destruct any existing StatisticsRecorder
@@ -32,11 +31,6 @@ class HistogramBaseTest : public testing::Test {
statistics_recorder_ = StatisticsRecorder::CreateTemporaryForTesting();
}
- HistogramBase* GetCreationReportHistogram(const std::string& name) {
- HistogramBase::EnableActivityReportHistogram(name);
- return HistogramBase::report_histogram_;
- }
-
private:
std::unique_ptr<StatisticsRecorder> statistics_recorder_;
@@ -50,7 +44,7 @@ TEST_F(HistogramBaseTest, DeserializeHistogram) {
HistogramBase::kIPCSerializationSourceFlag));
Pickle pickle;
- ASSERT_TRUE(histogram->SerializeInfo(&pickle));
+ histogram->SerializeInfo(&pickle);
PickleIterator iter(pickle);
HistogramBase* deserialized = DeserializeHistogramInfo(&iter);
@@ -62,7 +56,7 @@ TEST_F(HistogramBaseTest, DeserializeHistogram) {
deserialized = DeserializeHistogramInfo(&iter2);
EXPECT_TRUE(deserialized);
EXPECT_NE(histogram, deserialized);
- EXPECT_EQ("TestHistogram", deserialized->histogram_name());
+ EXPECT_EQ("TestHistogram", StringPiece(deserialized->histogram_name()));
EXPECT_TRUE(deserialized->HasConstructionArguments(1, 1000, 10));
// kIPCSerializationSourceFlag will be cleared.
@@ -75,7 +69,7 @@ TEST_F(HistogramBaseTest, DeserializeLinearHistogram) {
HistogramBase::kIPCSerializationSourceFlag);
Pickle pickle;
- ASSERT_TRUE(histogram->SerializeInfo(&pickle));
+ histogram->SerializeInfo(&pickle);
PickleIterator iter(pickle);
HistogramBase* deserialized = DeserializeHistogramInfo(&iter);
@@ -87,7 +81,7 @@ TEST_F(HistogramBaseTest, DeserializeLinearHistogram) {
deserialized = DeserializeHistogramInfo(&iter2);
EXPECT_TRUE(deserialized);
EXPECT_NE(histogram, deserialized);
- EXPECT_EQ("TestHistogram", deserialized->histogram_name());
+ EXPECT_EQ("TestHistogram", StringPiece(deserialized->histogram_name()));
EXPECT_TRUE(deserialized->HasConstructionArguments(1, 1000, 10));
EXPECT_EQ(0, deserialized->flags());
}
@@ -97,7 +91,7 @@ TEST_F(HistogramBaseTest, DeserializeBooleanHistogram) {
"TestHistogram", HistogramBase::kIPCSerializationSourceFlag);
Pickle pickle;
- ASSERT_TRUE(histogram->SerializeInfo(&pickle));
+ histogram->SerializeInfo(&pickle);
PickleIterator iter(pickle);
HistogramBase* deserialized = DeserializeHistogramInfo(&iter);
@@ -109,7 +103,7 @@ TEST_F(HistogramBaseTest, DeserializeBooleanHistogram) {
deserialized = DeserializeHistogramInfo(&iter2);
EXPECT_TRUE(deserialized);
EXPECT_NE(histogram, deserialized);
- EXPECT_EQ("TestHistogram", deserialized->histogram_name());
+ EXPECT_EQ("TestHistogram", StringPiece(deserialized->histogram_name()));
EXPECT_TRUE(deserialized->HasConstructionArguments(1, 2, 3));
EXPECT_EQ(0, deserialized->flags());
}
@@ -124,7 +118,7 @@ TEST_F(HistogramBaseTest, DeserializeCustomHistogram) {
"TestHistogram", ranges, HistogramBase::kIPCSerializationSourceFlag);
Pickle pickle;
- ASSERT_TRUE(histogram->SerializeInfo(&pickle));
+ histogram->SerializeInfo(&pickle);
PickleIterator iter(pickle);
HistogramBase* deserialized = DeserializeHistogramInfo(&iter);
@@ -136,7 +130,7 @@ TEST_F(HistogramBaseTest, DeserializeCustomHistogram) {
deserialized = DeserializeHistogramInfo(&iter2);
EXPECT_TRUE(deserialized);
EXPECT_NE(histogram, deserialized);
- EXPECT_EQ("TestHistogram", deserialized->histogram_name());
+ EXPECT_EQ("TestHistogram", StringPiece(deserialized->histogram_name()));
EXPECT_TRUE(deserialized->HasConstructionArguments(5, 13, 4));
EXPECT_EQ(0, deserialized->flags());
}
@@ -146,7 +140,7 @@ TEST_F(HistogramBaseTest, DeserializeSparseHistogram) {
"TestHistogram", HistogramBase::kIPCSerializationSourceFlag);
Pickle pickle;
- ASSERT_TRUE(histogram->SerializeInfo(&pickle));
+ histogram->SerializeInfo(&pickle);
PickleIterator iter(pickle);
HistogramBase* deserialized = DeserializeHistogramInfo(&iter);
@@ -158,65 +152,124 @@ TEST_F(HistogramBaseTest, DeserializeSparseHistogram) {
deserialized = DeserializeHistogramInfo(&iter2);
EXPECT_TRUE(deserialized);
EXPECT_NE(histogram, deserialized);
- EXPECT_EQ("TestHistogram", deserialized->histogram_name());
+ EXPECT_EQ("TestHistogram", StringPiece(deserialized->histogram_name()));
EXPECT_EQ(0, deserialized->flags());
}
-TEST_F(HistogramBaseTest, CreationReportHistogram) {
- // Enabled creation report. Itself is not included in the report.
- HistogramBase* report = GetCreationReportHistogram("CreationReportTest");
- ASSERT_TRUE(report);
+TEST_F(HistogramBaseTest, AddKilo) {
+ HistogramBase* histogram =
+ LinearHistogram::FactoryGet("TestAddKiloHistogram", 1, 1000, 100, 0);
- std::vector<HistogramBase::Sample> ranges;
- ranges.push_back(1);
- ranges.push_back(2);
- ranges.push_back(4);
- ranges.push_back(8);
- ranges.push_back(10);
-
- // Create all histogram types and verify counts.
- Histogram::FactoryGet("CRH-Histogram", 1, 10, 5, 0);
- LinearHistogram::FactoryGet("CRH-Linear", 1, 10, 5, 0);
- BooleanHistogram::FactoryGet("CRH-Boolean", 0);
- CustomHistogram::FactoryGet("CRH-Custom", ranges, 0);
- SparseHistogram::FactoryGet("CRH-Sparse", 0);
-
- std::unique_ptr<HistogramSamples> samples = report->SnapshotSamples();
- EXPECT_EQ(1, samples->GetCount(HISTOGRAM_REPORT_CREATED));
- EXPECT_EQ(5, samples->GetCount(HISTOGRAM_REPORT_HISTOGRAM_CREATED));
- EXPECT_EQ(0, samples->GetCount(HISTOGRAM_REPORT_HISTOGRAM_LOOKUP));
- EXPECT_EQ(1, samples->GetCount(HISTOGRAM_REPORT_TYPE_LOGARITHMIC));
- EXPECT_EQ(1, samples->GetCount(HISTOGRAM_REPORT_TYPE_LINEAR));
- EXPECT_EQ(1, samples->GetCount(HISTOGRAM_REPORT_TYPE_BOOLEAN));
- EXPECT_EQ(1, samples->GetCount(HISTOGRAM_REPORT_TYPE_CUSTOM));
- EXPECT_EQ(1, samples->GetCount(HISTOGRAM_REPORT_TYPE_SPARSE));
-
- // Create all flag types and verify counts.
- Histogram::FactoryGet("CRH-Histogram-UMA-Targeted", 1, 10, 5,
- HistogramBase::kUmaTargetedHistogramFlag);
- Histogram::FactoryGet("CRH-Histogram-UMA-Stability", 1, 10, 5,
- HistogramBase::kUmaStabilityHistogramFlag);
- SparseHistogram::FactoryGet("CRH-Sparse-UMA-Targeted",
- HistogramBase::kUmaTargetedHistogramFlag);
- SparseHistogram::FactoryGet("CRH-Sparse-UMA-Stability",
- HistogramBase::kUmaStabilityHistogramFlag);
- samples = report->SnapshotSamples();
- EXPECT_EQ(1, samples->GetCount(HISTOGRAM_REPORT_CREATED));
- EXPECT_EQ(9, samples->GetCount(HISTOGRAM_REPORT_HISTOGRAM_CREATED));
- EXPECT_EQ(0, samples->GetCount(HISTOGRAM_REPORT_HISTOGRAM_LOOKUP));
- EXPECT_EQ(2, samples->GetCount(HISTOGRAM_REPORT_FLAG_UMA_TARGETED));
- EXPECT_EQ(2, samples->GetCount(HISTOGRAM_REPORT_FLAG_UMA_STABILITY));
-
- // Do lookup of existing histograms and verify counts.
- Histogram::FactoryGet("CRH-Histogram", 1, 10, 5, 0);
- LinearHistogram::FactoryGet("CRH-Linear", 1, 10, 5, 0);
- BooleanHistogram::FactoryGet("CRH-Boolean", 0);
- CustomHistogram::FactoryGet("CRH-Custom", ranges, 0);
- SparseHistogram::FactoryGet("CRH-Sparse", 0);
- samples = report->SnapshotSamples();
- EXPECT_EQ(1, samples->GetCount(HISTOGRAM_REPORT_CREATED));
- EXPECT_EQ(9, samples->GetCount(HISTOGRAM_REPORT_HISTOGRAM_CREATED));
- EXPECT_EQ(5, samples->GetCount(HISTOGRAM_REPORT_HISTOGRAM_LOOKUP));
+ histogram->AddKilo(100, 1000);
+ histogram->AddKilo(200, 2000);
+ histogram->AddKilo(300, 1500);
+
+ std::unique_ptr<HistogramSamples> samples = histogram->SnapshotSamples();
+ EXPECT_EQ(1, samples->GetCount(100));
+ EXPECT_EQ(2, samples->GetCount(200));
+ EXPECT_LE(1, samples->GetCount(300));
+ EXPECT_GE(2, samples->GetCount(300));
+}
+
+TEST_F(HistogramBaseTest, AddKiB) {
+ HistogramBase* histogram =
+ LinearHistogram::FactoryGet("TestAddKiBHistogram", 1, 1000, 100, 0);
+
+ histogram->AddKiB(100, 1024);
+ histogram->AddKiB(200, 2048);
+ histogram->AddKiB(300, 1536);
+
+ std::unique_ptr<HistogramSamples> samples = histogram->SnapshotSamples();
+ EXPECT_EQ(1, samples->GetCount(100));
+ EXPECT_EQ(2, samples->GetCount(200));
+ EXPECT_LE(1, samples->GetCount(300));
+ EXPECT_GE(2, samples->GetCount(300));
+}
+
+TEST_F(HistogramBaseTest, AddTimeMillisecondsGranularityOverflow) {
+ const HistogramBase::Sample sample_max =
+ std::numeric_limits<HistogramBase::Sample>::max() / 2;
+ HistogramBase* histogram = LinearHistogram::FactoryGet(
+ "TestAddTimeMillisecondsGranularity1", 1, sample_max, 100, 0);
+ int64_t large_positive = std::numeric_limits<int64_t>::max();
+ // |add_count| is the number of large values that have been added to the
+ // histogram. We consider a number to be 'large' if it cannot be represented
+ // in a HistogramBase::Sample.
+ int add_count = 0;
+ while (large_positive > std::numeric_limits<HistogramBase::Sample>::max()) {
+ // Add the TimeDelta corresponding to |large_positive| milliseconds to the
+ // histogram.
+ histogram->AddTimeMillisecondsGranularity(
+ TimeDelta::FromMilliseconds(large_positive));
+ ++add_count;
+ // Reduce the value of |large_positive|. The choice of 7 here is
+ // arbitrary.
+ large_positive /= 7;
+ }
+ std::unique_ptr<HistogramSamples> samples = histogram->SnapshotSamples();
+ // All of the reported values must have gone into the max overflow bucket.
+ EXPECT_EQ(add_count, samples->GetCount(sample_max));
+
+ // We now perform the analoguous operations, now with negative values with a
+ // large absolute value.
+ histogram = LinearHistogram::FactoryGet("TestAddTimeMillisecondsGranularity2",
+ 1, sample_max, 100, 0);
+ int64_t large_negative = std::numeric_limits<int64_t>::min();
+ add_count = 0;
+ while (large_negative < std::numeric_limits<HistogramBase::Sample>::min()) {
+ histogram->AddTimeMillisecondsGranularity(
+ TimeDelta::FromMilliseconds(large_negative));
+ ++add_count;
+ large_negative /= 7;
+ }
+ samples = histogram->SnapshotSamples();
+ // All of the reported values must have gone into the min overflow bucket.
+ EXPECT_EQ(add_count, samples->GetCount(0));
+}
+
+TEST_F(HistogramBaseTest, AddTimeMicrosecondsGranularityOverflow) {
+ // Nothing to test if we don't have a high resolution clock.
+ if (!TimeTicks::IsHighResolution())
+ return;
+
+ const HistogramBase::Sample sample_max =
+ std::numeric_limits<HistogramBase::Sample>::max() / 2;
+ HistogramBase* histogram = LinearHistogram::FactoryGet(
+ "TestAddTimeMicrosecondsGranularity1", 1, sample_max, 100, 0);
+ int64_t large_positive = std::numeric_limits<int64_t>::max();
+ // |add_count| is the number of large values that have been added to the
+ // histogram. We consider a number to be 'large' if it cannot be represented
+ // in a HistogramBase::Sample.
+ int add_count = 0;
+ while (large_positive > std::numeric_limits<HistogramBase::Sample>::max()) {
+ // Add the TimeDelta corresponding to |large_positive| microseconds to the
+ // histogram.
+ histogram->AddTimeMicrosecondsGranularity(
+ TimeDelta::FromMicroseconds(large_positive));
+ ++add_count;
+ // Reduce the value of |large_positive|. The choice of 7 here is
+ // arbitrary.
+ large_positive /= 7;
+ }
+ std::unique_ptr<HistogramSamples> samples = histogram->SnapshotSamples();
+ // All of the reported values must have gone into the max overflow bucket.
+ EXPECT_EQ(add_count, samples->GetCount(sample_max));
+
+ // We now perform the analoguous operations, now with negative values with a
+ // large absolute value.
+ histogram = LinearHistogram::FactoryGet("TestAddTimeMicrosecondsGranularity2",
+ 1, sample_max, 100, 0);
+ int64_t large_negative = std::numeric_limits<int64_t>::min();
+ add_count = 0;
+ while (large_negative < std::numeric_limits<HistogramBase::Sample>::min()) {
+ histogram->AddTimeMicrosecondsGranularity(
+ TimeDelta::FromMicroseconds(large_negative));
+ ++add_count;
+ large_negative /= 7;
+ }
+ samples = histogram->SnapshotSamples();
+ // All of the reported values must have gone into the min overflow bucket.
+ EXPECT_EQ(add_count, samples->GetCount(0));
}
} // namespace base
diff --git a/base/metrics/histogram_delta_serialization.cc b/base/metrics/histogram_delta_serialization.cc
index 3e5d154a62..a74b87f0d0 100644
--- a/base/metrics/histogram_delta_serialization.cc
+++ b/base/metrics/histogram_delta_serialization.cc
@@ -35,30 +35,9 @@ void DeserializeHistogramAndAddSamples(PickleIterator* iter) {
HistogramDeltaSerialization::HistogramDeltaSerialization(
const std::string& caller_name)
- : histogram_snapshot_manager_(this),
- serialized_deltas_(NULL) {
- inconsistencies_histogram_ =
- LinearHistogram::FactoryGet(
- "Histogram.Inconsistencies" + caller_name, 1,
- HistogramBase::NEVER_EXCEEDED_VALUE,
- HistogramBase::NEVER_EXCEEDED_VALUE + 1,
- HistogramBase::kUmaTargetedHistogramFlag);
+ : histogram_snapshot_manager_(this), serialized_deltas_(nullptr) {}
- inconsistencies_unique_histogram_ =
- LinearHistogram::FactoryGet(
- "Histogram.Inconsistencies" + caller_name + "Unique", 1,
- HistogramBase::NEVER_EXCEEDED_VALUE,
- HistogramBase::NEVER_EXCEEDED_VALUE + 1,
- HistogramBase::kUmaTargetedHistogramFlag);
-
- inconsistent_snapshot_histogram_ =
- Histogram::FactoryGet(
- "Histogram.InconsistentSnapshot" + caller_name, 1, 1000000, 50,
- HistogramBase::kUmaTargetedHistogramFlag);
-}
-
-HistogramDeltaSerialization::~HistogramDeltaSerialization() {
-}
+HistogramDeltaSerialization::~HistogramDeltaSerialization() = default;
void HistogramDeltaSerialization::PrepareAndSerializeDeltas(
std::vector<std::string>* serialized_deltas,
@@ -69,10 +48,10 @@ void HistogramDeltaSerialization::PrepareAndSerializeDeltas(
// Note: Before serializing, we set the kIPCSerializationSourceFlag for all
// the histograms, so that the receiving process can distinguish them from the
// local histograms.
- histogram_snapshot_manager_.PrepareDeltas(
- StatisticsRecorder::begin(include_persistent), StatisticsRecorder::end(),
- Histogram::kIPCSerializationSourceFlag, Histogram::kNoFlags);
- serialized_deltas_ = NULL;
+ StatisticsRecorder::PrepareDeltas(
+ include_persistent, Histogram::kIPCSerializationSourceFlag,
+ Histogram::kNoFlags, &histogram_snapshot_manager_);
+ serialized_deltas_ = nullptr;
}
// static
@@ -99,25 +78,4 @@ void HistogramDeltaSerialization::RecordDelta(
std::string(static_cast<const char*>(pickle.data()), pickle.size()));
}
-void HistogramDeltaSerialization::InconsistencyDetected(
- HistogramBase::Inconsistency problem) {
- DCHECK(thread_checker_.CalledOnValidThread());
-
- inconsistencies_histogram_->Add(problem);
-}
-
-void HistogramDeltaSerialization::UniqueInconsistencyDetected(
- HistogramBase::Inconsistency problem) {
- DCHECK(thread_checker_.CalledOnValidThread());
-
- inconsistencies_unique_histogram_->Add(problem);
-}
-
-void HistogramDeltaSerialization::InconsistencyDetectedInLoggedCount(
- int amount) {
- DCHECK(thread_checker_.CalledOnValidThread());
-
- inconsistent_snapshot_histogram_->Add(std::abs(amount));
-}
-
} // namespace base
diff --git a/base/metrics/histogram_delta_serialization.h b/base/metrics/histogram_delta_serialization.h
index 3bb04cb4d2..57ebd2c424 100644
--- a/base/metrics/histogram_delta_serialization.h
+++ b/base/metrics/histogram_delta_serialization.h
@@ -44,10 +44,6 @@ class BASE_EXPORT HistogramDeltaSerialization : public HistogramFlattener {
// HistogramFlattener implementation.
void RecordDelta(const HistogramBase& histogram,
const HistogramSamples& snapshot) override;
- void InconsistencyDetected(HistogramBase::Inconsistency problem) override;
- void UniqueInconsistencyDetected(
- HistogramBase::Inconsistency problem) override;
- void InconsistencyDetectedInLoggedCount(int amount) override;
ThreadChecker thread_checker_;
@@ -57,11 +53,6 @@ class BASE_EXPORT HistogramDeltaSerialization : public HistogramFlattener {
// Output buffer for serialized deltas.
std::vector<std::string>* serialized_deltas_;
- // Histograms to count inconsistencies in snapshots.
- HistogramBase* inconsistencies_histogram_;
- HistogramBase* inconsistencies_unique_histogram_;
- HistogramBase* inconsistent_snapshot_histogram_;
-
DISALLOW_COPY_AND_ASSIGN(HistogramDeltaSerialization);
};
diff --git a/base/metrics/histogram_flattener.h b/base/metrics/histogram_flattener.h
index b5fe976656..6a5e3f4298 100644
--- a/base/metrics/histogram_flattener.h
+++ b/base/metrics/histogram_flattener.h
@@ -17,30 +17,14 @@ class HistogramSamples;
// HistogramFlattener is an interface used by HistogramSnapshotManager, which
// handles the logistics of gathering up available histograms for recording.
-// The implementors handle the exact lower level recording mechanism, or
-// error report mechanism.
class BASE_EXPORT HistogramFlattener {
public:
virtual void RecordDelta(const HistogramBase& histogram,
const HistogramSamples& snapshot) = 0;
- // Will be called each time a type of Inconsistency is seen on a histogram,
- // during inspections done internally in HistogramSnapshotManager class.
- virtual void InconsistencyDetected(HistogramBase::Inconsistency problem) = 0;
-
- // Will be called when a type of Inconsistency is seen for the first time on
- // a histogram.
- virtual void UniqueInconsistencyDetected(
- HistogramBase::Inconsistency problem) = 0;
-
- // Will be called when the total logged sample count of a histogram
- // differs from the sum of logged sample count in all the buckets. The
- // argument |amount| is the non-zero discrepancy.
- virtual void InconsistencyDetectedInLoggedCount(int amount) = 0;
-
protected:
- HistogramFlattener() {}
- virtual ~HistogramFlattener() {}
+ HistogramFlattener() = default;
+ virtual ~HistogramFlattener() = default;
private:
DISALLOW_COPY_AND_ASSIGN(HistogramFlattener);
diff --git a/base/metrics/histogram_functions.cc b/base/metrics/histogram_functions.cc
new file mode 100644
index 0000000000..31bf219e5a
--- /dev/null
+++ b/base/metrics/histogram_functions.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 "base/metrics/histogram_functions.h"
+
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/time/time.h"
+
+namespace base {
+
+void UmaHistogramBoolean(const std::string& name, bool sample) {
+ HistogramBase* histogram = BooleanHistogram::FactoryGet(
+ name, HistogramBase::kUmaTargetedHistogramFlag);
+ histogram->Add(sample);
+}
+
+void UmaHistogramExactLinear(const std::string& name,
+ int sample,
+ int value_max) {
+ HistogramBase* histogram =
+ LinearHistogram::FactoryGet(name, 1, value_max, value_max + 1,
+ HistogramBase::kUmaTargetedHistogramFlag);
+ histogram->Add(sample);
+}
+
+void UmaHistogramPercentage(const std::string& name, int percent) {
+ UmaHistogramExactLinear(name, percent, 100);
+}
+
+void UmaHistogramCustomCounts(const std::string& name,
+ int sample,
+ int min,
+ int max,
+ int buckets) {
+ HistogramBase* histogram = Histogram::FactoryGet(
+ name, min, max, buckets, HistogramBase::kUmaTargetedHistogramFlag);
+ histogram->Add(sample);
+}
+
+void UmaHistogramCounts100(const std::string& name, int sample) {
+ UmaHistogramCustomCounts(name, sample, 1, 100, 50);
+}
+
+void UmaHistogramCounts1000(const std::string& name, int sample) {
+ UmaHistogramCustomCounts(name, sample, 1, 1000, 50);
+}
+
+void UmaHistogramCounts10000(const std::string& name, int sample) {
+ UmaHistogramCustomCounts(name, sample, 1, 10000, 50);
+}
+
+void UmaHistogramCounts100000(const std::string& name, int sample) {
+ UmaHistogramCustomCounts(name, sample, 1, 100000, 50);
+}
+
+void UmaHistogramCounts1M(const std::string& name, int sample) {
+ UmaHistogramCustomCounts(name, sample, 1, 1000000, 50);
+}
+
+void UmaHistogramCounts10M(const std::string& name, int sample) {
+ UmaHistogramCustomCounts(name, sample, 1, 10000000, 50);
+}
+
+void UmaHistogramCustomTimes(const std::string& name,
+ TimeDelta sample,
+ TimeDelta min,
+ TimeDelta max,
+ int buckets) {
+ HistogramBase* histogram = Histogram::FactoryTimeGet(
+ name, min, max, buckets, HistogramBase::kUmaTargetedHistogramFlag);
+ histogram->AddTimeMillisecondsGranularity(sample);
+}
+
+void UmaHistogramTimes(const std::string& name, TimeDelta sample) {
+ UmaHistogramCustomTimes(name, sample, TimeDelta::FromMilliseconds(1),
+ TimeDelta::FromSeconds(10), 50);
+}
+
+void UmaHistogramMediumTimes(const std::string& name, TimeDelta sample) {
+ UmaHistogramCustomTimes(name, sample, TimeDelta::FromMilliseconds(1),
+ TimeDelta::FromMinutes(3), 50);
+}
+
+void UmaHistogramLongTimes(const std::string& name, TimeDelta sample) {
+ UmaHistogramCustomTimes(name, sample, TimeDelta::FromMilliseconds(1),
+ TimeDelta::FromHours(1), 50);
+}
+
+void UmaHistogramMemoryKB(const std::string& name, int sample) {
+ UmaHistogramCustomCounts(name, sample, 1000, 500000, 50);
+}
+
+void UmaHistogramMemoryMB(const std::string& name, int sample) {
+ UmaHistogramCustomCounts(name, sample, 1, 1000, 50);
+}
+
+void UmaHistogramMemoryLargeMB(const std::string& name, int sample) {
+ UmaHistogramCustomCounts(name, sample, 1, 64000, 100);
+}
+
+void UmaHistogramSparse(const std::string& name, int sample) {
+ HistogramBase* histogram = SparseHistogram::FactoryGet(
+ name, HistogramBase::kUmaTargetedHistogramFlag);
+ histogram->Add(sample);
+}
+
+} // namespace base
diff --git a/base/metrics/histogram_functions.h b/base/metrics/histogram_functions.h
new file mode 100644
index 0000000000..60c005726f
--- /dev/null
+++ b/base/metrics/histogram_functions.h
@@ -0,0 +1,158 @@
+// 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_METRICS_HISTOGRAM_FUNCTIONS_H_
+#define BASE_METRICS_HISTOGRAM_FUNCTIONS_H_
+
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_base.h"
+#include "base/time/time.h"
+
+// Functions for recording metrics.
+//
+// For best practices on deciding when to emit to a histogram and what form
+// the histogram should take, see
+// https://chromium.googlesource.com/chromium/src.git/+/HEAD/tools/metrics/histograms/README.md
+
+// Functions for recording UMA histograms. These can be used for cases
+// when the histogram name is generated at runtime. The functionality is
+// equivalent to macros defined in histogram_macros.h but allowing non-constant
+// histogram names. These functions are slower compared to their macro
+// equivalent because the histogram objects are not cached between calls.
+// So, these shouldn't be used in performance critical code.
+namespace base {
+
+// For histograms with linear buckets.
+// Used for capturing integer data with a linear bucketing scheme. This can be
+// used when you want the exact value of some small numeric count, with a max of
+// 100 or less. If you need to capture a range of greater than 100, we recommend
+// the use of the COUNT histograms below.
+// Sample usage:
+// base::UmaHistogramExactLinear("Histogram.Linear", some_value, 10);
+BASE_EXPORT void UmaHistogramExactLinear(const std::string& name,
+ int sample,
+ int value_max);
+
+// For adding a sample to an enumerated histogram.
+// Sample usage:
+// // These values are persisted to logs. Entries should not be renumbered and
+// // numeric values should never be reused.
+// enum class MyEnum {
+// FIRST_VALUE = 0,
+// SECOND_VALUE = 1,
+// ...
+// FINAL_VALUE = N,
+// COUNT
+// };
+// base::UmaHistogramEnumeration("My.Enumeration",
+// MyEnum::SOME_VALUE, MyEnum::COUNT);
+//
+// Note: The value in |sample| must be strictly less than |enum_size|.
+template <typename T>
+void UmaHistogramEnumeration(const std::string& name, T sample, T enum_size) {
+ static_assert(std::is_enum<T>::value,
+ "Non enum passed to UmaHistogramEnumeration");
+ DCHECK_LE(static_cast<uintmax_t>(enum_size), static_cast<uintmax_t>(INT_MAX));
+ DCHECK_LT(static_cast<uintmax_t>(sample), static_cast<uintmax_t>(enum_size));
+ return UmaHistogramExactLinear(name, static_cast<int>(sample),
+ static_cast<int>(enum_size));
+}
+
+// Same as above, but uses T::kMaxValue as the inclusive maximum value of the
+// enum.
+template <typename T>
+void UmaHistogramEnumeration(const std::string& name, T sample) {
+ static_assert(std::is_enum<T>::value,
+ "Non enum passed to UmaHistogramEnumeration");
+ DCHECK_LE(static_cast<uintmax_t>(T::kMaxValue),
+ static_cast<uintmax_t>(INT_MAX) - 1);
+ DCHECK_LE(static_cast<uintmax_t>(sample),
+ static_cast<uintmax_t>(T::kMaxValue));
+ return UmaHistogramExactLinear(name, static_cast<int>(sample),
+ static_cast<int>(T::kMaxValue) + 1);
+}
+
+// For adding boolean sample to histogram.
+// Sample usage:
+// base::UmaHistogramBoolean("My.Boolean", true)
+BASE_EXPORT void UmaHistogramBoolean(const std::string& name, bool sample);
+
+// For adding histogram with percent.
+// Percents are integer between 1 and 100.
+// Sample usage:
+// base::UmaHistogramPercentage("My.Percent", 69)
+BASE_EXPORT void UmaHistogramPercentage(const std::string& name, int percent);
+
+// For adding counts histogram.
+// Sample usage:
+// base::UmaHistogramCustomCounts("My.Counts", some_value, 1, 600, 30)
+BASE_EXPORT void UmaHistogramCustomCounts(const std::string& name,
+ int sample,
+ int min,
+ int max,
+ int buckets);
+
+// Counts specialization for maximum counts 100, 1000, 10k, 100k, 1M and 10M.
+BASE_EXPORT void UmaHistogramCounts100(const std::string& name, int sample);
+BASE_EXPORT void UmaHistogramCounts1000(const std::string& name, int sample);
+BASE_EXPORT void UmaHistogramCounts10000(const std::string& name, int sample);
+BASE_EXPORT void UmaHistogramCounts100000(const std::string& name, int sample);
+BASE_EXPORT void UmaHistogramCounts1M(const std::string& name, int sample);
+BASE_EXPORT void UmaHistogramCounts10M(const std::string& name, int sample);
+
+// For histograms storing times.
+BASE_EXPORT void UmaHistogramCustomTimes(const std::string& name,
+ TimeDelta sample,
+ TimeDelta min,
+ TimeDelta max,
+ int buckets);
+// For short timings from 1 ms up to 10 seconds (50 buckets).
+BASE_EXPORT void UmaHistogramTimes(const std::string& name, TimeDelta sample);
+// For medium timings up to 3 minutes (50 buckets).
+BASE_EXPORT void UmaHistogramMediumTimes(const std::string& name,
+ TimeDelta sample);
+// For time intervals up to 1 hr (50 buckets).
+BASE_EXPORT void UmaHistogramLongTimes(const std::string& name,
+ TimeDelta sample);
+
+// For recording memory related histograms.
+// Used to measure common KB-granularity memory stats. Range is up to 500M.
+BASE_EXPORT void UmaHistogramMemoryKB(const std::string& name, int sample);
+// Used to measure common MB-granularity memory stats. Range is up to ~1G.
+BASE_EXPORT void UmaHistogramMemoryMB(const std::string& name, int sample);
+// Used to measure common MB-granularity memory stats. Range is up to ~64G.
+BASE_EXPORT void UmaHistogramMemoryLargeMB(const std::string& name, int sample);
+
+// For recording sparse histograms.
+// The |sample| can be a negative or non-negative number.
+//
+// Sparse histograms are well suited for recording counts of exact sample values
+// that are sparsely distributed over a relatively large range, in cases where
+// ultra-fast performance is not critical. For instance, Sqlite.Version.* are
+// sparse because for any given database, there's going to be exactly one
+// version logged.
+//
+// Performance:
+// ------------
+// Sparse histograms are typically more memory-efficient but less time-efficient
+// than other histograms. Essentially, they sparse histograms use a map rather
+// than a vector for their backing storage; they also require lock acquisition
+// to increment a sample, whereas other histogram do not. Hence, each increment
+// operation is a bit slower than for other histograms. But, if the data is
+// sparse, then they use less memory client-side, because they allocate buckets
+// on demand rather than preallocating.
+//
+// Data size:
+// ----------
+// Note that server-side, we still need to load all buckets, across all users,
+// at once. Thus, please avoid exploding such histograms, i.e. uploading many
+// many distinct values to the server (across all users). Concretely, keep the
+// number of distinct values <= 100 ideally, definitely <= 1000. If you have no
+// guarantees on the range of your data, use clamping, e.g.:
+// UmaHistogramSparse("MyHistogram", ClampToRange(value, 0, 200));
+BASE_EXPORT void UmaHistogramSparse(const std::string& name, int sample);
+
+} // namespace base
+
+#endif // BASE_METRICS_HISTOGRAM_FUNCTIONS_H_
diff --git a/base/metrics/histogram_macros.h b/base/metrics/histogram_macros.h
index d39972a8a1..93bd4bdff7 100644
--- a/base/metrics/histogram_macros.h
+++ b/base/metrics/histogram_macros.h
@@ -5,6 +5,7 @@
#ifndef BASE_METRICS_HISTOGRAM_MACROS_H_
#define BASE_METRICS_HISTOGRAM_MACROS_H_
+#include "base/macros.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_macros_internal.h"
#include "base/metrics/histogram_macros_local.h"
@@ -35,15 +36,60 @@
// an element of the Enum.
// All of these macros must be called with |name| as a runtime constant.
+// The first variant of UMA_HISTOGRAM_ENUMERATION accepts two arguments: the
+// histogram name and the enum sample. It deduces the correct boundary value to
+// use by looking for an enumerator with the name kMaxValue. kMaxValue should
+// share the value of the highest enumerator: this avoids switch statements
+// having to handle a sentinel no-op value.
+//
// Sample usage:
-// UMA_HISTOGRAM_ENUMERATION("My.Enumeration", VALUE, EVENT_MAX_VALUE);
-// New Enum values can be added, but existing enums must never be renumbered or
-// delete and reused. The value in |sample| must be strictly less than
-// |enum_max|.
-
-#define UMA_HISTOGRAM_ENUMERATION(name, sample, enum_max) \
- INTERNAL_HISTOGRAM_ENUMERATION_WITH_FLAG( \
- name, sample, enum_max, base::HistogramBase::kUmaTargetedHistogramFlag)
+// // These values are persisted to logs. Entries should not be renumbered and
+// // numeric values should never be reused.
+// enum class MyEnum {
+// kFirstValue = 0,
+// kSecondValue = 1,
+// ...
+// kFinalValue = N,
+// kMaxValue = kFinalValue,
+// };
+// UMA_HISTOGRAM_ENUMERATION("My.Enumeration", MyEnum::kSomeValue);
+//
+// The second variant requires three arguments: the first two are the same as
+// before, and the third argument is the enum boundary: this must be strictly
+// greater than any other enumerator that will be sampled.
+//
+// Sample usage:
+// // These values are persisted to logs. Entries should not be renumbered and
+// // numeric values should never be reused.
+// enum class MyEnum {
+// FIRST_VALUE = 0,
+// SECOND_VALUE = 1,
+// ...
+// FINAL_VALUE = N,
+// COUNT
+// };
+// UMA_HISTOGRAM_ENUMERATION("My.Enumeration",
+// MyEnum::SOME_VALUE, MyEnum::COUNT);
+//
+// Note: If the enum is used in a switch, it is often desirable to avoid writing
+// a case statement to handle an unused sentinel value (i.e. COUNT in the above
+// example). For scoped enums, this is awkward since it requires casting the
+// enum to an arithmetic type and adding one. Instead, prefer the two argument
+// version of the macro which automatically deduces the boundary from kMaxValue.
+#define UMA_HISTOGRAM_ENUMERATION(name, ...) \
+ CR_EXPAND_ARG(INTERNAL_UMA_HISTOGRAM_ENUMERATION_GET_MACRO( \
+ __VA_ARGS__, INTERNAL_UMA_HISTOGRAM_ENUMERATION_SPECIFY_BOUNDARY, \
+ INTERNAL_UMA_HISTOGRAM_ENUMERATION_DEDUCE_BOUNDARY)( \
+ name, __VA_ARGS__, base::HistogramBase::kUmaTargetedHistogramFlag))
+
+// As above but "scaled" count to avoid overflows caused by increments of
+// large amounts. See UMA_HISTOGRAM_SCALED_EXACT_LINEAR for more information.
+// Only the new format utilizing an internal kMaxValue is supported.
+// It'll be necessary to #include "base/lazy_instance.h" to use this macro.
+#define UMA_HISTOGRAM_SCALED_ENUMERATION(name, sample, count, scale) \
+ INTERNAL_HISTOGRAM_SCALED_ENUMERATION_WITH_FLAG( \
+ name, sample, count, scale, \
+ base::HistogramBase::kUmaTargetedHistogramFlag)
// Histogram for boolean values.
@@ -78,6 +124,20 @@
UMA_HISTOGRAM_EXACT_LINEAR(name, percent_as_int, 101)
//------------------------------------------------------------------------------
+// Scaled Linear histograms.
+
+// These take |count| and |scale| parameters to allow cumulative reporting of
+// large numbers. Only the scaled count is reported but the reminder is kept so
+// multiple calls will accumulate correctly. Only "exact linear" is supported.
+// It'll be necessary to #include "base/lazy_instance.h" to use this macro.
+
+#define UMA_HISTOGRAM_SCALED_EXACT_LINEAR(name, sample, count, value_max, \
+ scale) \
+ INTERNAL_HISTOGRAM_SCALED_EXACT_LINEAR_WITH_FLAG( \
+ name, sample, count, value_max, scale, \
+ base::HistogramBase::kUmaTargetedHistogramFlag)
+
+//------------------------------------------------------------------------------
// Count histograms. These are used for collecting numeric data. Note that we
// have macros for more specialized use cases below (memory, time, percentages).
@@ -141,7 +201,8 @@
// Sample usage:
// UMA_HISTOGRAM_TIMES("My.Timing.Histogram", time_delta);
-// Short timings - up to 10 seconds.
+// Short timings - up to 10 seconds. For high-resolution (microseconds) timings,
+// see UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES.
#define UMA_HISTOGRAM_TIMES(name, sample) UMA_HISTOGRAM_CUSTOM_TIMES( \
name, sample, base::TimeDelta::FromMilliseconds(1), \
base::TimeDelta::FromSeconds(10), 50)
@@ -167,12 +228,34 @@
// as the number of buckets recorded.
// Sample usage:
-// UMA_HISTOGRAM_CUSTOM_TIMES("Very.Long.Timing.Histogram", duration_in_ms,
+// UMA_HISTOGRAM_CUSTOM_TIMES("Very.Long.Timing.Histogram", time_delta,
// base::TimeDelta::FromSeconds(1), base::TimeDelta::FromDays(1), 100);
-#define UMA_HISTOGRAM_CUSTOM_TIMES(name, sample, min, max, bucket_count) \
- STATIC_HISTOGRAM_POINTER_BLOCK(name, AddTime(sample), \
- base::Histogram::FactoryTimeGet(name, min, max, bucket_count, \
- base::HistogramBase::kUmaTargetedHistogramFlag))
+#define UMA_HISTOGRAM_CUSTOM_TIMES(name, sample, min, max, bucket_count) \
+ STATIC_HISTOGRAM_POINTER_BLOCK( \
+ name, AddTimeMillisecondsGranularity(sample), \
+ base::Histogram::FactoryTimeGet( \
+ name, min, max, bucket_count, \
+ base::HistogramBase::kUmaTargetedHistogramFlag))
+
+// Same as UMA_HISTOGRAM_CUSTOM_TIMES but reports |sample| in microseconds,
+// dropping the report if this client doesn't have a high-resolution clock.
+//
+// Note: dropping reports on clients with low-resolution clocks means these
+// reports will be biased to a portion of the population on Windows. See
+// Windows.HasHighResolutionTimeTicks for the affected sample.
+//
+// Sample usage:
+// UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
+// "High.Resolution.TimingMicroseconds.Histogram", time_delta,
+// base::TimeDelta::FromMicroseconds(1),
+// base::TimeDelta::FromMilliseconds(10), 100);
+#define UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(name, sample, min, max, \
+ bucket_count) \
+ STATIC_HISTOGRAM_POINTER_BLOCK( \
+ name, AddTimeMicrosecondsGranularity(sample), \
+ base::Histogram::FactoryMicrosecondsTimeGet( \
+ name, min, max, bucket_count, \
+ base::HistogramBase::kUmaTargetedHistogramFlag))
// Scoped class which logs its time on this earth as a UMA statistic. This is
// recommended for when you want a histogram which measures the time it takes
@@ -240,22 +323,6 @@
base::HistogramBase::kUmaStabilityHistogramFlag)
//------------------------------------------------------------------------------
-// Sparse histograms.
-
-// Sparse histograms are well suited for recording counts of exact sample values
-// that are sparsely distributed over a large range.
-//
-// UMA_HISTOGRAM_SPARSE_SLOWLY is good for sparsely distributed and/or
-// infrequently recorded values since the implementation is slower
-// and takes more memory.
-//
-// For instance, Sqlite.Version.* are sparse because for any given database,
-// there's going to be exactly one version logged.
-// The |sample| can be a negative or non-negative number.
-#define UMA_HISTOGRAM_SPARSE_SLOWLY(name, sample) \
- INTERNAL_HISTOGRAM_SPARSE_SLOWLY(name, sample)
-
-//------------------------------------------------------------------------------
// Histogram instantiation helpers.
// Support a collection of histograms, perhaps one for each entry in an
@@ -305,8 +372,8 @@
// Samples should be one of the std::vector<int> list provided via
// |custom_ranges|. See comments above CustomRanges::FactoryGet about the
// requirement of |custom_ranges|. You can use the helper function
-// CustomHistogram::ArrayToCustomRanges to transform a C-style array of valid
-// sample values to a std::vector<int>.
+// CustomHistogram::ArrayToCustomEnumRanges to transform a C-style array of
+// valid sample values to a std::vector<int>.
#define UMA_HISTOGRAM_CUSTOM_ENUMERATION(name, sample, custom_ranges) \
STATIC_HISTOGRAM_POINTER_BLOCK(name, Add(sample), \
base::CustomHistogram::FactoryGet(name, custom_ranges, \
diff --git a/base/metrics/histogram_macros_internal.h b/base/metrics/histogram_macros_internal.h
index c107a4729d..cc7c76aef1 100644
--- a/base/metrics/histogram_macros_internal.h
+++ b/base/metrics/histogram_macros_internal.h
@@ -16,11 +16,40 @@
#include "base/metrics/sparse_histogram.h"
#include "base/time/time.h"
-// This is for macros internal to base/metrics. They should not be used outside
-// of this directory. For writing to UMA histograms, see histogram_macros.h.
+// This is for macros and helpers internal to base/metrics. They should not be
+// used outside of this directory. For writing to UMA histograms, see
+// histogram_macros.h.
-// TODO(rkaplow): Improve commenting of these methods.
+namespace base {
+namespace internal {
+
+// Helper traits for deducing the boundary value for enums.
+template <typename Enum, typename SFINAE = void>
+struct EnumSizeTraits {
+ static constexpr Enum Count() {
+ static_assert(
+ sizeof(Enum) == 0,
+ "enumerator must define kMaxValue enumerator to use this macro!");
+ return Enum();
+ }
+};
+
+// Since the UMA histogram macros expect a value one larger than the max defined
+// enumerator value, add one.
+template <typename Enum>
+struct EnumSizeTraits<
+ Enum,
+ std::enable_if_t<std::is_enum<decltype(Enum::kMaxValue)>::value>> {
+ static constexpr Enum Count() {
+ return static_cast<Enum>(
+ static_cast<std::underlying_type_t<Enum>>(Enum::kMaxValue) + 1);
+ }
+};
+
+} // namespace internal
+} // namespace base
+// TODO(rkaplow): Improve commenting of these methods.
//------------------------------------------------------------------------------
// Histograms are often put in areas where they are called many many times, and
// performance is critical. As a result, they are designed to have a very low
@@ -31,7 +60,6 @@
// have to validate using the pointers at any time during the running of the
// process.
-
// In some cases (integration into 3rd party code), it's useful to separate the
// definition of |atomic_histogram_pointer| from its use. To achieve this we
// define HISTOGRAM_POINTER_USE, which uses an |atomic_histogram_pointer|, and
@@ -127,24 +155,66 @@
flag)); \
} while (0)
+// While this behaves the same as the above macro, the wrapping of a linear
+// histogram with another object to do the scaling means the POINTER_BLOCK
+// macro can't be used as it is tied to HistogramBase
+#define INTERNAL_HISTOGRAM_SCALED_EXACT_LINEAR_WITH_FLAG( \
+ name, sample, count, boundary, scale, flag) \
+ do { \
+ static_assert(!std::is_enum<decltype(sample)>::value, \
+ "|sample| should not be an enum type!"); \
+ static_assert(!std::is_enum<decltype(boundary)>::value, \
+ "|boundary| should not be an enum type!"); \
+ class ScaledLinearHistogramInstance : public base::ScaledLinearHistogram { \
+ public: \
+ ScaledLinearHistogramInstance() \
+ : ScaledLinearHistogram(name, \
+ 1, \
+ boundary, \
+ boundary + 1, \
+ scale, \
+ flag) {} \
+ }; \
+ static base::LazyInstance<ScaledLinearHistogramInstance>::Leaky scaled; \
+ scaled.Get().AddScaledCount(sample, count); \
+ } while (0)
+
+// Helper for 'overloading' UMA_HISTOGRAM_ENUMERATION with a variable number of
+// arguments.
+#define INTERNAL_UMA_HISTOGRAM_ENUMERATION_GET_MACRO(_1, _2, NAME, ...) NAME
+
+#define INTERNAL_UMA_HISTOGRAM_ENUMERATION_DEDUCE_BOUNDARY(name, sample, \
+ flags) \
+ INTERNAL_HISTOGRAM_ENUMERATION_WITH_FLAG( \
+ name, sample, base::internal::EnumSizeTraits<decltype(sample)>::Count(), \
+ flags)
+
+// Note: The value in |sample| must be strictly less than |enum_size|.
+#define INTERNAL_UMA_HISTOGRAM_ENUMERATION_SPECIFY_BOUNDARY(name, sample, \
+ enum_size, flags) \
+ INTERNAL_HISTOGRAM_ENUMERATION_WITH_FLAG(name, sample, enum_size, flags)
+
// Similar to the previous macro but intended for enumerations. This delegates
// the work to the previous macro, but supports scoped enumerations as well by
// forcing an explicit cast to the HistogramBase::Sample integral type.
//
// Note the range checks verify two separate issues:
-// - that the declared enum max isn't out of range of HistogramBase::Sample
-// - that the declared enum max is > 0
+// - that the declared enum size isn't out of range of HistogramBase::Sample
+// - that the declared enum size is > 0
//
// TODO(dcheng): This should assert that the passed in types are actually enum
// types.
#define INTERNAL_HISTOGRAM_ENUMERATION_WITH_FLAG(name, sample, boundary, flag) \
do { \
- static_assert( \
- !std::is_enum<decltype(sample)>::value || \
- !std::is_enum<decltype(boundary)>::value || \
- std::is_same<std::remove_const<decltype(sample)>::type, \
- std::remove_const<decltype(boundary)>::type>::value, \
- "|sample| and |boundary| shouldn't be of different enums"); \
+ using decayed_sample = std::decay<decltype(sample)>::type; \
+ using decayed_boundary = std::decay<decltype(boundary)>::type; \
+ static_assert(!std::is_enum<decayed_boundary>::value || \
+ std::is_enum<decayed_sample>::value, \
+ "Unexpected: |boundary| is enum, but |sample| is not."); \
+ static_assert(!std::is_enum<decayed_sample>::value || \
+ !std::is_enum<decayed_boundary>::value || \
+ std::is_same<decayed_sample, decayed_boundary>::value, \
+ "|sample| and |boundary| shouldn't be of different enums"); \
static_assert( \
static_cast<uintmax_t>(boundary) < \
static_cast<uintmax_t>( \
@@ -155,6 +225,24 @@
static_cast<base::HistogramBase::Sample>(boundary), flag); \
} while (0)
+#define INTERNAL_HISTOGRAM_SCALED_ENUMERATION_WITH_FLAG(name, sample, count, \
+ scale, flag) \
+ do { \
+ using decayed_sample = std::decay<decltype(sample)>::type; \
+ static_assert(std::is_enum<decayed_sample>::value, \
+ "Unexpected: |sample| is not at enum."); \
+ constexpr auto boundary = \
+ base::internal::EnumSizeTraits<decltype(sample)>::Count(); \
+ static_assert( \
+ static_cast<uintmax_t>(boundary) < \
+ static_cast<uintmax_t>( \
+ std::numeric_limits<base::HistogramBase::Sample>::max()), \
+ "|boundary| is out of range of HistogramBase::Sample"); \
+ INTERNAL_HISTOGRAM_SCALED_EXACT_LINEAR_WITH_FLAG( \
+ name, static_cast<base::HistogramBase::Sample>(sample), count, \
+ static_cast<base::HistogramBase::Sample>(boundary), scale, flag); \
+ } while (0)
+
// This is a helper macro used by other macros and shouldn't be used directly.
// This is necessary to expand __COUNTER__ to an actual value.
#define INTERNAL_SCOPED_UMA_HISTOGRAM_TIMER_EXPANDER(name, is_long, key) \
@@ -177,16 +265,4 @@
base::TimeTicks constructed_; \
} scoped_histogram_timer_##key
-// Macro for sparse histogram.
-// The implementation is more costly to add values to, and each value
-// stored has more overhead, compared to the other histogram types. However it
-// may be more efficient in memory if the total number of sample values is small
-// compared to the range of their values.
-#define INTERNAL_HISTOGRAM_SPARSE_SLOWLY(name, sample) \
- do { \
- base::HistogramBase* histogram = base::SparseHistogram::FactoryGet( \
- name, base::HistogramBase::kUmaTargetedHistogramFlag); \
- histogram->Add(sample); \
- } while (0)
-
#endif // BASE_METRICS_HISTOGRAM_MACROS_INTERNAL_H_
diff --git a/base/metrics/histogram_macros_local.h b/base/metrics/histogram_macros_local.h
index 7571a9c4ad..c4d333b501 100644
--- a/base/metrics/histogram_macros_local.h
+++ b/base/metrics/histogram_macros_local.h
@@ -18,10 +18,11 @@
//
// For usage details, see the equivalents in histogram_macros.h.
-#define LOCAL_HISTOGRAM_ENUMERATION(name, sample, enum_max) \
- INTERNAL_HISTOGRAM_ENUMERATION_WITH_FLAG( \
- name, sample, enum_max, \
- base::HistogramBase::kNoFlags)
+#define LOCAL_HISTOGRAM_ENUMERATION(name, ...) \
+ CR_EXPAND_ARG(INTERNAL_UMA_HISTOGRAM_ENUMERATION_GET_MACRO( \
+ __VA_ARGS__, INTERNAL_UMA_HISTOGRAM_ENUMERATION_SPECIFY_BOUNDARY, \
+ INTERNAL_UMA_HISTOGRAM_ENUMERATION_DEDUCE_BOUNDARY)( \
+ name, __VA_ARGS__, base::HistogramBase::kNoFlags))
#define LOCAL_HISTOGRAM_BOOLEAN(name, sample) \
STATIC_HISTOGRAM_POINTER_BLOCK(name, AddBoolean(sample), \
@@ -63,10 +64,11 @@
name, sample, base::TimeDelta::FromMilliseconds(1), \
base::TimeDelta::FromSeconds(10), 50)
-#define LOCAL_HISTOGRAM_CUSTOM_TIMES(name, sample, min, max, bucket_count) \
- STATIC_HISTOGRAM_POINTER_BLOCK(name, AddTime(sample), \
- base::Histogram::FactoryTimeGet(name, min, max, bucket_count, \
- base::HistogramBase::kNoFlags))
+#define LOCAL_HISTOGRAM_CUSTOM_TIMES(name, sample, min, max, bucket_count) \
+ STATIC_HISTOGRAM_POINTER_BLOCK( \
+ name, AddTimeMillisecondsGranularity(sample), \
+ base::Histogram::FactoryTimeGet(name, min, max, bucket_count, \
+ base::HistogramBase::kNoFlags))
//------------------------------------------------------------------------------
// Memory histograms.
diff --git a/base/metrics/histogram_macros_unittest.cc b/base/metrics/histogram_macros_unittest.cc
index 33a9c6e5b2..3c592b00e5 100644
--- a/base/metrics/histogram_macros_unittest.cc
+++ b/base/metrics/histogram_macros_unittest.cc
@@ -40,10 +40,18 @@ TEST(HistogramMacro, ScopedEnumeration) {
FIRST_VALUE,
SECOND_VALUE,
THIRD_VALUE,
+ kMaxValue = THIRD_VALUE,
+ };
+ UMA_HISTOGRAM_ENUMERATION("Test.ScopedEnumeration", TestEnum::FIRST_VALUE);
+
+ enum class TestEnum2 {
+ FIRST_VALUE,
+ SECOND_VALUE,
+ THIRD_VALUE,
MAX_ENTRIES,
};
- UMA_HISTOGRAM_ENUMERATION("Test.ScopedEnumeration", TestEnum::SECOND_VALUE,
- TestEnum::MAX_ENTRIES);
+ UMA_HISTOGRAM_ENUMERATION("Test.ScopedEnumeration2", TestEnum2::SECOND_VALUE,
+ TestEnum2::MAX_ENTRIES);
}
} // namespace base
diff --git a/base/metrics/histogram_samples.cc b/base/metrics/histogram_samples.cc
index 3475cd59f7..6830637c06 100644
--- a/base/metrics/histogram_samples.cc
+++ b/base/metrics/histogram_samples.cc
@@ -4,13 +4,29 @@
#include "base/metrics/histogram_samples.h"
+#include <limits>
+
#include "base/compiler_specific.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/numerics/safe_math.h"
#include "base/pickle.h"
namespace base {
namespace {
+// A shorthand constant for the max value of size_t.
+constexpr size_t kSizeMax = std::numeric_limits<size_t>::max();
+
+// A constant stored in an AtomicSingleSample (as_atomic) to indicate that the
+// sample is "disabled" and no further accumulation should be done with it. The
+// value is chosen such that it will be MAX_UINT16 for both |bucket| & |count|,
+// and thus less likely to conflict with real use. Conflicts are explicitly
+// handled in the code but it's worth making them as unlikely as possible.
+constexpr int32_t kDisabledSingleSample = -1;
+
class SampleCountPickleIterator : public SampleCountIterator {
public:
explicit SampleCountPickleIterator(PickleIterator* iter);
@@ -18,14 +34,14 @@ class SampleCountPickleIterator : public SampleCountIterator {
bool Done() const override;
void Next() override;
void Get(HistogramBase::Sample* min,
- HistogramBase::Sample* max,
+ int64_t* max,
HistogramBase::Count* count) const override;
private:
PickleIterator* const iter_;
HistogramBase::Sample min_;
- HistogramBase::Sample max_;
+ int64_t max_;
HistogramBase::Count count_;
bool is_done_;
};
@@ -42,14 +58,14 @@ bool SampleCountPickleIterator::Done() const {
void SampleCountPickleIterator::Next() {
DCHECK(!Done());
- if (!iter_->ReadInt(&min_) ||
- !iter_->ReadInt(&max_) ||
- !iter_->ReadInt(&count_))
+ if (!iter_->ReadInt(&min_) || !iter_->ReadInt64(&max_) ||
+ !iter_->ReadInt(&count_)) {
is_done_ = true;
+ }
}
void SampleCountPickleIterator::Get(HistogramBase::Sample* min,
- HistogramBase::Sample* max,
+ int64_t* max,
HistogramBase::Count* count) const {
DCHECK(!Done());
*min = min_;
@@ -59,15 +75,104 @@ void SampleCountPickleIterator::Get(HistogramBase::Sample* min,
} // namespace
-// Don't try to delegate behavior to the constructor below that accepts a
-// Matadata pointer by passing &local_meta_. Such cannot be reliably passed
-// because it has not yet been constructed -- no member variables have; the
-// class itself is in the middle of being constructed. Using it to
-// initialize meta_ is okay because the object now exists and local_meta_
-// is before meta_ in the construction order.
-HistogramSamples::HistogramSamples(uint64_t id)
- : meta_(&local_meta_) {
- meta_->id = id;
+static_assert(sizeof(HistogramSamples::AtomicSingleSample) ==
+ sizeof(subtle::Atomic32),
+ "AtomicSingleSample isn't 32 bits");
+
+HistogramSamples::SingleSample HistogramSamples::AtomicSingleSample::Load()
+ const {
+ AtomicSingleSample single_sample = subtle::Acquire_Load(&as_atomic);
+
+ // If the sample was extracted/disabled, it's still zero to the outside.
+ if (single_sample.as_atomic == kDisabledSingleSample)
+ single_sample.as_atomic = 0;
+
+ return single_sample.as_parts;
+}
+
+HistogramSamples::SingleSample HistogramSamples::AtomicSingleSample::Extract(
+ bool disable) {
+ AtomicSingleSample single_sample = subtle::NoBarrier_AtomicExchange(
+ &as_atomic, disable ? kDisabledSingleSample : 0);
+ if (single_sample.as_atomic == kDisabledSingleSample)
+ single_sample.as_atomic = 0;
+ return single_sample.as_parts;
+}
+
+bool HistogramSamples::AtomicSingleSample::Accumulate(
+ size_t bucket,
+ HistogramBase::Count count) {
+ if (count == 0)
+ return true;
+
+ // Convert the parameters to 16-bit variables because it's all 16-bit below.
+ // To support decrements/subtractions, divide the |count| into sign/value and
+ // do the proper operation below. The alternative is to change the single-
+ // sample's count to be a signed integer (int16_t) and just add an int16_t
+ // |count16| but that is somewhat wasteful given that the single-sample is
+ // never expected to have a count less than zero.
+ if (count < -std::numeric_limits<uint16_t>::max() ||
+ count > std::numeric_limits<uint16_t>::max() ||
+ bucket > std::numeric_limits<uint16_t>::max()) {
+ return false;
+ }
+ bool count_is_negative = count < 0;
+ uint16_t count16 = static_cast<uint16_t>(count_is_negative ? -count : count);
+ uint16_t bucket16 = static_cast<uint16_t>(bucket);
+
+ // A local, unshared copy of the single-sample is necessary so the parts
+ // can be manipulated without worrying about atomicity.
+ AtomicSingleSample single_sample;
+
+ bool sample_updated;
+ do {
+ subtle::Atomic32 original = subtle::Acquire_Load(&as_atomic);
+ if (original == kDisabledSingleSample)
+ return false;
+ single_sample.as_atomic = original;
+ if (single_sample.as_atomic != 0) {
+ // Only the same bucket (parameter and stored) can be counted multiple
+ // times.
+ if (single_sample.as_parts.bucket != bucket16)
+ return false;
+ } else {
+ // The |single_ sample| was zero so becomes the |bucket| parameter, the
+ // contents of which were checked above to fit in 16 bits.
+ single_sample.as_parts.bucket = bucket16;
+ }
+
+ // Update count, making sure that it doesn't overflow.
+ CheckedNumeric<uint16_t> new_count(single_sample.as_parts.count);
+ if (count_is_negative)
+ new_count -= count16;
+ else
+ new_count += count16;
+ if (!new_count.AssignIfValid(&single_sample.as_parts.count))
+ return false;
+
+ // Don't let this become equivalent to the "disabled" value.
+ if (single_sample.as_atomic == kDisabledSingleSample)
+ return false;
+
+ // Store the updated single-sample back into memory. |existing| is what
+ // was in that memory location at the time of the call; if it doesn't
+ // match |original| then the swap didn't happen so loop again.
+ subtle::Atomic32 existing = subtle::Release_CompareAndSwap(
+ &as_atomic, original, single_sample.as_atomic);
+ sample_updated = (existing == original);
+ } while (!sample_updated);
+
+ return true;
+}
+
+bool HistogramSamples::AtomicSingleSample::IsDisabled() const {
+ return subtle::Acquire_Load(&as_atomic) == kDisabledSingleSample;
+}
+
+HistogramSamples::LocalMetadata::LocalMetadata() {
+ // This is the same way it's done for persistent metadata since no ctor
+ // is called for the data members in that case.
+ memset(this, 0, sizeof(*this));
}
HistogramSamples::HistogramSamples(uint64_t id, Metadata* meta)
@@ -80,13 +185,14 @@ HistogramSamples::HistogramSamples(uint64_t id, Metadata* meta)
meta_->id = id;
}
-HistogramSamples::~HistogramSamples() {}
+// This mustn't do anything with |meta_|. It was passed to the ctor and may
+// be invalid by the time this dtor gets called.
+HistogramSamples::~HistogramSamples() = default;
void HistogramSamples::Add(const HistogramSamples& other) {
- IncreaseSum(other.sum());
- subtle::NoBarrier_AtomicIncrement(&meta_->redundant_count,
- other.redundant_count());
- bool success = AddSubtractImpl(other.Iterator().get(), ADD);
+ IncreaseSumAndCount(other.sum(), other.redundant_count());
+ std::unique_ptr<SampleCountIterator> it = other.Iterator();
+ bool success = AddSubtractImpl(it.get(), ADD);
DCHECK(success);
}
@@ -97,59 +203,113 @@ bool HistogramSamples::AddFromPickle(PickleIterator* iter) {
if (!iter->ReadInt64(&sum) || !iter->ReadInt(&redundant_count))
return false;
- IncreaseSum(sum);
- subtle::NoBarrier_AtomicIncrement(&meta_->redundant_count,
- redundant_count);
+ IncreaseSumAndCount(sum, redundant_count);
SampleCountPickleIterator pickle_iter(iter);
return AddSubtractImpl(&pickle_iter, ADD);
}
void HistogramSamples::Subtract(const HistogramSamples& other) {
- IncreaseSum(-other.sum());
- subtle::NoBarrier_AtomicIncrement(&meta_->redundant_count,
- -other.redundant_count());
- bool success = AddSubtractImpl(other.Iterator().get(), SUBTRACT);
+ IncreaseSumAndCount(-other.sum(), -other.redundant_count());
+ std::unique_ptr<SampleCountIterator> it = other.Iterator();
+ bool success = AddSubtractImpl(it.get(), SUBTRACT);
DCHECK(success);
}
-bool HistogramSamples::Serialize(Pickle* pickle) const {
- if (!pickle->WriteInt64(sum()))
- return false;
- if (!pickle->WriteInt(redundant_count()))
- return false;
+void HistogramSamples::Serialize(Pickle* pickle) const {
+ pickle->WriteInt64(sum());
+ pickle->WriteInt(redundant_count());
HistogramBase::Sample min;
- HistogramBase::Sample max;
+ int64_t max;
HistogramBase::Count count;
for (std::unique_ptr<SampleCountIterator> it = Iterator(); !it->Done();
it->Next()) {
it->Get(&min, &max, &count);
- if (!pickle->WriteInt(min) ||
- !pickle->WriteInt(max) ||
- !pickle->WriteInt(count))
- return false;
+ pickle->WriteInt(min);
+ pickle->WriteInt64(max);
+ pickle->WriteInt(count);
}
- return true;
}
-void HistogramSamples::IncreaseSum(int64_t diff) {
+bool HistogramSamples::AccumulateSingleSample(HistogramBase::Sample value,
+ HistogramBase::Count count,
+ size_t bucket) {
+ if (single_sample().Accumulate(bucket, count)) {
+ // Success. Update the (separate) sum and redundant-count.
+ IncreaseSumAndCount(strict_cast<int64_t>(value) * count, count);
+ return true;
+ }
+ return false;
+}
+
+void HistogramSamples::IncreaseSumAndCount(int64_t sum,
+ HistogramBase::Count count) {
#ifdef ARCH_CPU_64_BITS
- subtle::NoBarrier_AtomicIncrement(&meta_->sum, diff);
+ subtle::NoBarrier_AtomicIncrement(&meta_->sum, sum);
#else
- meta_->sum += diff;
+ meta_->sum += sum;
#endif
+ subtle::NoBarrier_AtomicIncrement(&meta_->redundant_count, count);
}
-void HistogramSamples::IncreaseRedundantCount(HistogramBase::Count diff) {
- subtle::NoBarrier_AtomicIncrement(&meta_->redundant_count, diff);
+void HistogramSamples::RecordNegativeSample(NegativeSampleReason reason,
+ HistogramBase::Count increment) {
+ UMA_HISTOGRAM_ENUMERATION("UMA.NegativeSamples.Reason", reason,
+ MAX_NEGATIVE_SAMPLE_REASONS);
+ UMA_HISTOGRAM_CUSTOM_COUNTS("UMA.NegativeSamples.Increment", increment, 1,
+ 1 << 30, 100);
+ UmaHistogramSparse("UMA.NegativeSamples.Histogram",
+ static_cast<int32_t>(id()));
}
-SampleCountIterator::~SampleCountIterator() {}
+SampleCountIterator::~SampleCountIterator() = default;
bool SampleCountIterator::GetBucketIndex(size_t* index) const {
DCHECK(!Done());
return false;
}
+SingleSampleIterator::SingleSampleIterator(HistogramBase::Sample min,
+ int64_t max,
+ HistogramBase::Count count)
+ : SingleSampleIterator(min, max, count, kSizeMax) {}
+
+SingleSampleIterator::SingleSampleIterator(HistogramBase::Sample min,
+ int64_t max,
+ HistogramBase::Count count,
+ size_t bucket_index)
+ : min_(min), max_(max), bucket_index_(bucket_index), count_(count) {}
+
+SingleSampleIterator::~SingleSampleIterator() = default;
+
+bool SingleSampleIterator::Done() const {
+ return count_ == 0;
+}
+
+void SingleSampleIterator::Next() {
+ DCHECK(!Done());
+ count_ = 0;
+}
+
+void SingleSampleIterator::Get(HistogramBase::Sample* min,
+ int64_t* max,
+ HistogramBase::Count* count) const {
+ DCHECK(!Done());
+ if (min != nullptr)
+ *min = min_;
+ if (max != nullptr)
+ *max = max_;
+ if (count != nullptr)
+ *count = count_;
+}
+
+bool SingleSampleIterator::GetBucketIndex(size_t* index) const {
+ DCHECK(!Done());
+ if (bucket_index_ == kSizeMax)
+ return false;
+ *index = bucket_index_;
+ return true;
+}
+
} // namespace base
diff --git a/base/metrics/histogram_samples.h b/base/metrics/histogram_samples.h
index 93f6d21c8a..6908873cee 100644
--- a/base/metrics/histogram_samples.h
+++ b/base/metrics/histogram_samples.h
@@ -8,6 +8,7 @@
#include <stddef.h>
#include <stdint.h>
+#include <limits>
#include <memory>
#include "base/atomicops.h"
@@ -24,8 +25,64 @@ class SampleCountIterator;
// elements must be of a fixed width to ensure 32/64-bit interoperability.
// If this structure changes, bump the version number for kTypeIdHistogram
// in persistent_histogram_allocator.cc.
+//
+// Note that though these samples are individually consistent (through the use
+// of atomic operations on the counts), there is only "eventual consistency"
+// overall when multiple threads are accessing this data. That means that the
+// sum, redundant-count, etc. could be momentarily out-of-sync with the stored
+// counts but will settle to a consistent "steady state" once all threads have
+// exited this code.
class BASE_EXPORT HistogramSamples {
public:
+ // A single bucket and count. To fit within a single atomic on 32-bit build
+ // architectures, both |bucket| and |count| are limited in size to 16 bits.
+ // This limits the functionality somewhat but if an entry can't fit then
+ // the full array of samples can be allocated and used.
+ struct SingleSample {
+ uint16_t bucket;
+ uint16_t count;
+ };
+
+ // A structure for managing an atomic single sample. Because this is generally
+ // used in association with other atomic values, the defined methods use
+ // acquire/release operations to guarantee ordering with outside values.
+ union BASE_EXPORT AtomicSingleSample {
+ AtomicSingleSample() : as_atomic(0) {}
+ AtomicSingleSample(subtle::Atomic32 rhs) : as_atomic(rhs) {}
+
+ // Returns the single sample in an atomic manner. This in an "acquire"
+ // load. The returned sample isn't shared and thus its fields can be safely
+ // accessed.
+ SingleSample Load() const;
+
+ // Extracts the single sample in an atomic manner. If |disable| is true
+ // then this object will be set so it will never accumulate another value.
+ // This is "no barrier" so doesn't enforce ordering with other atomic ops.
+ SingleSample Extract(bool disable);
+
+ // Adds a given count to the held bucket. If not possible, it returns false
+ // and leaves the parts unchanged. Once extracted/disabled, this always
+ // returns false. This in an "acquire/release" operation.
+ bool Accumulate(size_t bucket, HistogramBase::Count count);
+
+ // Returns if the sample has been "disabled" (via Extract) and thus not
+ // allowed to accept further accumulation.
+ bool IsDisabled() const;
+
+ private:
+ // union field: The actual sample bucket and count.
+ SingleSample as_parts;
+
+ // union field: The sample as an atomic value. Atomic64 would provide
+ // more flexibility but isn't available on all builds. This can hold a
+ // special, internal "disabled" value indicating that it must not accept
+ // further accumulation.
+ subtle::Atomic32 as_atomic;
+ };
+
+ // A structure of information about the data, common to all sample containers.
+ // Because of how this is used in persistent memory, it must be a POD object
+ // that makes sense when initialized to all zeros.
struct Metadata {
// Expected size for 32/64-bit check.
static constexpr size_t kExpectedInstanceSize = 24;
@@ -58,24 +115,19 @@ class BASE_EXPORT HistogramSamples {
// might mismatch even when no memory corruption has happened.
HistogramBase::AtomicCount redundant_count;
- // 4 bytes of padding to explicitly extend this structure to a multiple of
- // 64-bits. This is required to ensure the structure is the same size on
- // both 32-bit and 64-bit builds.
- char padding[4];
+ // A single histogram value and associated count. This allows histograms
+ // that typically report only a single value to not require full storage
+ // to be allocated.
+ AtomicSingleSample single_sample; // 32 bits
};
- // Because sturctures held in persistent memory must be POD, there can be no
+ // Because structures held in persistent memory must be POD, there can be no
// default constructor to clear the fields. This derived class exists just
// to clear them when being allocated on the heap.
- struct LocalMetadata : Metadata {
- LocalMetadata() {
- id = 0;
- sum = 0;
- redundant_count = 0;
- }
+ struct BASE_EXPORT LocalMetadata : Metadata {
+ LocalMetadata();
};
- explicit HistogramSamples(uint64_t id);
HistogramSamples(uint64_t id, Metadata* meta);
virtual ~HistogramSamples();
@@ -92,7 +144,7 @@ class BASE_EXPORT HistogramSamples {
virtual void Subtract(const HistogramSamples& other);
virtual std::unique_ptr<SampleCountIterator> Iterator() const = 0;
- virtual bool Serialize(Pickle* pickle) const;
+ virtual void Serialize(Pickle* pickle) const;
// Accessor fuctions.
uint64_t id() const { return meta_->id; }
@@ -108,18 +160,48 @@ class BASE_EXPORT HistogramSamples {
}
protected:
+ enum NegativeSampleReason {
+ SAMPLES_HAVE_LOGGED_BUT_NOT_SAMPLE,
+ SAMPLES_SAMPLE_LESS_THAN_LOGGED,
+ SAMPLES_ADDED_NEGATIVE_COUNT,
+ SAMPLES_ADD_WENT_NEGATIVE,
+ SAMPLES_ADD_OVERFLOW,
+ SAMPLES_ACCUMULATE_NEGATIVE_COUNT,
+ SAMPLES_ACCUMULATE_WENT_NEGATIVE,
+ DEPRECATED_SAMPLES_ACCUMULATE_OVERFLOW,
+ SAMPLES_ACCUMULATE_OVERFLOW,
+ MAX_NEGATIVE_SAMPLE_REASONS
+ };
+
// Based on |op| type, add or subtract sample counts data from the iterator.
enum Operator { ADD, SUBTRACT };
virtual bool AddSubtractImpl(SampleCountIterator* iter, Operator op) = 0;
- void IncreaseSum(int64_t diff);
- void IncreaseRedundantCount(HistogramBase::Count diff);
+ // Accumulates to the embedded single-sample field if possible. Returns true
+ // on success, false otherwise. Sum and redundant-count are also updated in
+ // the success case.
+ bool AccumulateSingleSample(HistogramBase::Sample value,
+ HistogramBase::Count count,
+ size_t bucket);
+
+ // Atomically adjust the sum and redundant-count.
+ void IncreaseSumAndCount(int64_t sum, HistogramBase::Count count);
+
+ // Record a negative-sample observation and the reason why.
+ void RecordNegativeSample(NegativeSampleReason reason,
+ HistogramBase::Count increment);
+
+ AtomicSingleSample& single_sample() { return meta_->single_sample; }
+ const AtomicSingleSample& single_sample() const {
+ return meta_->single_sample;
+ }
+
+ Metadata* meta() { return meta_; }
private:
- // In order to support histograms shared through an external memory segment,
- // meta values may be the local storage or external storage depending on the
- // wishes of the derived class.
- LocalMetadata local_meta_;
+ // Depending on derived class meta values can come from local stoarge or
+ // external storage in which case HistogramSamples class cannot take ownership
+ // of Metadata*.
Metadata* meta_;
DISALLOW_COPY_AND_ASSIGN(HistogramSamples);
@@ -134,10 +216,16 @@ class BASE_EXPORT SampleCountIterator {
// Get the sample and count at current position.
// |min| |max| and |count| can be NULL if the value is not of interest.
+ // Note: |max| is int64_t because histograms support logged values in the
+ // full int32_t range and bucket max is exclusive, so it needs to support
+ // values up to MAXINT32+1.
// Requires: !Done();
virtual void Get(HistogramBase::Sample* min,
- HistogramBase::Sample* max,
+ int64_t* max,
HistogramBase::Count* count) const = 0;
+ static_assert(std::numeric_limits<HistogramBase::Sample>::max() <
+ std::numeric_limits<int64_t>::max(),
+ "Get() |max| must be able to hold Histogram::Sample max + 1");
// Get the index of current histogram bucket.
// For histograms that don't use predefined buckets, it returns false.
@@ -145,6 +233,35 @@ class BASE_EXPORT SampleCountIterator {
virtual bool GetBucketIndex(size_t* index) const;
};
+class BASE_EXPORT SingleSampleIterator : public SampleCountIterator {
+ public:
+ SingleSampleIterator(HistogramBase::Sample min,
+ int64_t max,
+ HistogramBase::Count count);
+ SingleSampleIterator(HistogramBase::Sample min,
+ int64_t max,
+ HistogramBase::Count count,
+ size_t bucket_index);
+ ~SingleSampleIterator() override;
+
+ // SampleCountIterator:
+ bool Done() const override;
+ void Next() override;
+ void Get(HistogramBase::Sample* min,
+ int64_t* max,
+ HistogramBase::Count* count) const override;
+
+ // SampleVector uses predefined buckets so iterator can return bucket index.
+ bool GetBucketIndex(size_t* index) const override;
+
+ private:
+ // Information about the single value to return.
+ const HistogramBase::Sample min_;
+ const int64_t max_;
+ const size_t bucket_index_;
+ HistogramBase::Count count_;
+};
+
} // namespace base
#endif // BASE_METRICS_HISTOGRAM_SAMPLES_H_
diff --git a/base/metrics/histogram_samples_unittest.cc b/base/metrics/histogram_samples_unittest.cc
new file mode 100644
index 0000000000..74c743b604
--- /dev/null
+++ b/base/metrics/histogram_samples_unittest.cc
@@ -0,0 +1,84 @@
+// Copyright (c) 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/metrics/histogram_samples.h"
+
+#include <limits>
+
+#include "base/test/gtest_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+using SingleSample = HistogramSamples::SingleSample;
+using AtomicSingleSample = HistogramSamples::AtomicSingleSample;
+
+TEST(SingleSampleTest, Load) {
+ AtomicSingleSample sample;
+ ASSERT_TRUE(sample.Accumulate(9, 1));
+
+ SingleSample s = sample.Load();
+ EXPECT_EQ(9U, s.bucket);
+ EXPECT_EQ(1U, s.count);
+
+ s = sample.Load();
+ EXPECT_EQ(9U, s.bucket);
+ EXPECT_EQ(1U, s.count);
+}
+
+TEST(SingleSampleTest, Extract) {
+ AtomicSingleSample sample;
+ ASSERT_TRUE(sample.Accumulate(9, 1));
+
+ SingleSample s = sample.Extract(/*disable=*/false);
+ EXPECT_EQ(9U, s.bucket);
+ EXPECT_EQ(1U, s.count);
+
+ s = sample.Extract(/*disable=*/false);
+ EXPECT_EQ(0U, s.bucket);
+ EXPECT_EQ(0U, s.count);
+}
+
+TEST(SingleSampleTest, Disable) {
+ AtomicSingleSample sample;
+ EXPECT_EQ(0U, sample.Extract(/*disable=*/false).count);
+ EXPECT_FALSE(sample.IsDisabled());
+
+ ASSERT_TRUE(sample.Accumulate(9, 1));
+ EXPECT_EQ(1U, sample.Extract(/*disable=*/true).count);
+ EXPECT_TRUE(sample.IsDisabled());
+
+ ASSERT_FALSE(sample.Accumulate(9, 1));
+ EXPECT_EQ(0U, sample.Extract(/*disable=*/false).count);
+ EXPECT_FALSE(sample.IsDisabled());
+}
+
+TEST(SingleSampleTest, Accumulate) {
+ AtomicSingleSample sample;
+
+ ASSERT_TRUE(sample.Accumulate(9, 1));
+ ASSERT_TRUE(sample.Accumulate(9, 2));
+ ASSERT_TRUE(sample.Accumulate(9, 4));
+ EXPECT_EQ(7U, sample.Extract(/*disable=*/false).count);
+
+ ASSERT_TRUE(sample.Accumulate(9, 4));
+ ASSERT_TRUE(sample.Accumulate(9, -2));
+ ASSERT_TRUE(sample.Accumulate(9, 1));
+ EXPECT_EQ(3U, sample.Extract(/*disable=*/false).count);
+}
+
+TEST(SingleSampleTest, Overflow) {
+ AtomicSingleSample sample;
+
+ ASSERT_TRUE(sample.Accumulate(9, 1));
+ ASSERT_FALSE(sample.Accumulate(9, -2));
+ EXPECT_EQ(1U, sample.Extract(/*disable=*/false).count);
+
+ ASSERT_TRUE(sample.Accumulate(9, std::numeric_limits<uint16_t>::max()));
+ ASSERT_FALSE(sample.Accumulate(9, 1));
+ EXPECT_EQ(std::numeric_limits<uint16_t>::max(),
+ sample.Extract(/*disable=*/false).count);
+}
+
+} // namespace base
diff --git a/base/metrics/histogram_snapshot_manager.cc b/base/metrics/histogram_snapshot_manager.cc
index a774ea6177..c1b804ebde 100644
--- a/base/metrics/histogram_snapshot_manager.cc
+++ b/base/metrics/histogram_snapshot_manager.cc
@@ -14,21 +14,54 @@
namespace base {
+namespace {
+
+// A simple object to set an "active" flag and clear it upon destruction. It is
+// an error if the flag is already set.
+class MakeActive {
+ public:
+ MakeActive(std::atomic<bool>* is_active) : is_active_(is_active) {
+ bool was_active = is_active_->exchange(true, std::memory_order_relaxed);
+ CHECK(!was_active);
+ }
+ ~MakeActive() { is_active_->store(false, std::memory_order_relaxed); }
+
+ private:
+ std::atomic<bool>* is_active_;
+
+ DISALLOW_COPY_AND_ASSIGN(MakeActive);
+};
+
+} // namespace
+
HistogramSnapshotManager::HistogramSnapshotManager(
HistogramFlattener* histogram_flattener)
: histogram_flattener_(histogram_flattener) {
DCHECK(histogram_flattener_);
+ is_active_.store(false, std::memory_order_relaxed);
}
-HistogramSnapshotManager::~HistogramSnapshotManager() {
+HistogramSnapshotManager::~HistogramSnapshotManager() = default;
+
+void HistogramSnapshotManager::PrepareDeltas(
+ const std::vector<HistogramBase*>& histograms,
+ HistogramBase::Flags flags_to_set,
+ HistogramBase::Flags required_flags) {
+ for (HistogramBase* const histogram : histograms) {
+ histogram->SetFlags(flags_to_set);
+ if ((histogram->flags() & required_flags) == required_flags)
+ PrepareDelta(histogram);
+ }
}
void HistogramSnapshotManager::PrepareDelta(HistogramBase* histogram) {
+ histogram->ValidateHistogramContents();
PrepareSamples(histogram, histogram->SnapshotDelta());
}
void HistogramSnapshotManager::PrepareFinalDelta(
const HistogramBase* histogram) {
+ histogram->ValidateHistogramContents();
PrepareSamples(histogram, histogram->SnapshotFinalDelta());
}
@@ -37,6 +70,11 @@ void HistogramSnapshotManager::PrepareSamples(
std::unique_ptr<HistogramSamples> samples) {
DCHECK(histogram_flattener_);
+ // Ensure that there is no concurrent access going on while accessing the
+ // set of known histograms. The flag will be reset when this object goes
+ // out of scope.
+ MakeActive make_active(&is_active_);
+
// Get information known about this histogram. If it did not previously
// exist, one will be created and initialized.
SampleInfo* sample_info = &known_histograms_[histogram->name_hash()];
@@ -49,23 +87,16 @@ void HistogramSnapshotManager::PrepareSamples(
// Extract fields useful during debug.
const BucketRanges* ranges =
static_cast<const Histogram*>(histogram)->bucket_ranges();
- std::vector<HistogramBase::Sample> ranges_copy;
- for (size_t i = 0; i < ranges->size(); ++i)
- ranges_copy.push_back(ranges->range(i));
- HistogramBase::Sample* ranges_ptr = &ranges_copy[0];
uint32_t ranges_checksum = ranges->checksum();
uint32_t ranges_calc_checksum = ranges->CalculateChecksum();
- const char* histogram_name = histogram->histogram_name().c_str();
int32_t flags = histogram->flags();
// The checksum should have caught this, so crash separately if it didn't.
CHECK_NE(0U, HistogramBase::RANGE_CHECKSUM_ERROR & corruption);
CHECK(false); // Crash for the bucket order corruption.
// Ensure that compiler keeps around pointers to |histogram| and its
// internal |bucket_ranges_| for any minidumps.
- base::debug::Alias(&ranges_ptr);
base::debug::Alias(&ranges_checksum);
base::debug::Alias(&ranges_calc_checksum);
- base::debug::Alias(&histogram_name);
base::debug::Alias(&flags);
}
// Checksum corruption might not have caused order corruption.
@@ -77,15 +108,11 @@ void HistogramSnapshotManager::PrepareSamples(
if (corruption) {
DLOG(ERROR) << "Histogram: \"" << histogram->histogram_name()
<< "\" has data corruption: " << corruption;
- histogram_flattener_->InconsistencyDetected(
- static_cast<HistogramBase::Inconsistency>(corruption));
// Don't record corrupt data to metrics services.
const uint32_t old_corruption = sample_info->inconsistencies;
if (old_corruption == (corruption | old_corruption))
return; // We've already seen this corruption for this histogram.
sample_info->inconsistencies |= corruption;
- histogram_flattener_->UniqueInconsistencyDetected(
- static_cast<HistogramBase::Inconsistency>(corruption));
return;
}
@@ -93,20 +120,4 @@ void HistogramSnapshotManager::PrepareSamples(
histogram_flattener_->RecordDelta(*histogram, *samples);
}
-void HistogramSnapshotManager::InspectLoggedSamplesInconsistency(
- const HistogramSamples& new_snapshot,
- HistogramSamples* logged_samples) {
- HistogramBase::Count discrepancy =
- logged_samples->TotalCount() - logged_samples->redundant_count();
- if (!discrepancy)
- return;
-
- histogram_flattener_->InconsistencyDetectedInLoggedCount(discrepancy);
- if (discrepancy > Histogram::kCommonRaceBasedCountMismatch) {
- // Fix logged_samples.
- logged_samples->Subtract(*logged_samples);
- logged_samples->Add(new_snapshot);
- }
-}
-
} // namespace base
diff --git a/base/metrics/histogram_snapshot_manager.h b/base/metrics/histogram_snapshot_manager.h
index de4a2e195a..cf7c149cc1 100644
--- a/base/metrics/histogram_snapshot_manager.h
+++ b/base/metrics/histogram_snapshot_manager.h
@@ -7,6 +7,7 @@
#include <stdint.h>
+#include <atomic>
#include <map>
#include <string>
#include <vector>
@@ -27,10 +28,10 @@ class HistogramFlattener;
// corruption, this class also validates as much redundancy as it can before
// calling for the marginal change (a.k.a., delta) in a histogram to be
// recorded.
-class BASE_EXPORT HistogramSnapshotManager {
+class BASE_EXPORT HistogramSnapshotManager final {
public:
explicit HistogramSnapshotManager(HistogramFlattener* histogram_flattener);
- virtual ~HistogramSnapshotManager();
+ ~HistogramSnapshotManager();
// Snapshot all histograms, and ask |histogram_flattener_| to record the
// delta. |flags_to_set| is used to set flags for each histogram.
@@ -38,17 +39,9 @@ class BASE_EXPORT HistogramSnapshotManager {
// Only histograms that have all the flags specified by the argument will be
// chosen. If all histograms should be recorded, set it to
// |Histogram::kNoFlags|.
- template <class ForwardHistogramIterator>
- void PrepareDeltas(ForwardHistogramIterator begin,
- ForwardHistogramIterator end,
+ void PrepareDeltas(const std::vector<HistogramBase*>& histograms,
HistogramBase::Flags flags_to_set,
- HistogramBase::Flags required_flags) {
- for (ForwardHistogramIterator it = begin; it != end; ++it) {
- (*it)->SetFlags(flags_to_set);
- if (((*it)->flags() & required_flags) == required_flags)
- PrepareDelta(*it);
- }
- }
+ HistogramBase::Flags required_flags);
// When the collection is not so simple as can be done using a single
// iterator, the steps can be performed separately. Call PerpareDelta()
@@ -75,18 +68,19 @@ class BASE_EXPORT HistogramSnapshotManager {
void PrepareSamples(const HistogramBase* histogram,
std::unique_ptr<HistogramSamples> samples);
- // Try to detect and fix count inconsistency of logged samples.
- void InspectLoggedSamplesInconsistency(
- const HistogramSamples& new_snapshot,
- HistogramSamples* logged_samples);
+ // |histogram_flattener_| handles the logistics of recording the histogram
+ // deltas.
+ HistogramFlattener* const histogram_flattener_; // Weak.
// For histograms, track what has been previously seen, indexed
// by the hash of the histogram name.
std::map<uint64_t, SampleInfo> known_histograms_;
- // |histogram_flattener_| handles the logistics of recording the histogram
- // deltas.
- HistogramFlattener* histogram_flattener_; // Weak.
+ // A flag indicating if a thread is currently doing an operation. This is
+ // used to check against concurrent access which is not supported. A Thread-
+ // Checker is not sufficient because it may be guarded by at outside lock
+ // (as is the case with cronet).
+ std::atomic<bool> is_active_;
DISALLOW_COPY_AND_ASSIGN(HistogramSnapshotManager);
};
diff --git a/base/metrics/histogram_snapshot_manager_unittest.cc b/base/metrics/histogram_snapshot_manager_unittest.cc
index 3c13e1a5a9..1e2c599ec6 100644
--- a/base/metrics/histogram_snapshot_manager_unittest.cc
+++ b/base/metrics/histogram_snapshot_manager_unittest.cc
@@ -19,7 +19,7 @@ namespace base {
class HistogramFlattenerDeltaRecorder : public HistogramFlattener {
public:
- HistogramFlattenerDeltaRecorder() {}
+ HistogramFlattenerDeltaRecorder() = default;
void RecordDelta(const HistogramBase& histogram,
const HistogramSamples& snapshot) override {
@@ -32,19 +32,6 @@ class HistogramFlattenerDeltaRecorder : public HistogramFlattener {
recorded_delta_histogram_sum_[histogram.histogram_name()] = snapshot.sum();
}
- void InconsistencyDetected(HistogramBase::Inconsistency problem) override {
- ASSERT_TRUE(false);
- }
-
- void UniqueInconsistencyDetected(
- HistogramBase::Inconsistency problem) override {
- ASSERT_TRUE(false);
- }
-
- void InconsistencyDetectedInLoggedCount(int amount) override {
- ASSERT_TRUE(false);
- }
-
void Reset() {
recorded_delta_histogram_names_.clear();
recorded_delta_histogram_sum_.clear();
@@ -72,7 +59,7 @@ class HistogramSnapshotManagerTest : public testing::Test {
: statistics_recorder_(StatisticsRecorder::CreateTemporaryForTesting()),
histogram_snapshot_manager_(&histogram_flattener_delta_recorder_) {}
- ~HistogramSnapshotManagerTest() override {}
+ ~HistogramSnapshotManagerTest() override = default;
std::unique_ptr<StatisticsRecorder> statistics_recorder_;
HistogramFlattenerDeltaRecorder histogram_flattener_delta_recorder_;
@@ -84,9 +71,9 @@ TEST_F(HistogramSnapshotManagerTest, PrepareDeltasNoFlagsFilter) {
UMA_HISTOGRAM_ENUMERATION("UmaHistogram", 1, 4);
UMA_STABILITY_HISTOGRAM_ENUMERATION("UmaStabilityHistogram", 1, 2);
- histogram_snapshot_manager_.PrepareDeltas(
- StatisticsRecorder::begin(false), StatisticsRecorder::end(),
- HistogramBase::kNoFlags, HistogramBase::kNoFlags);
+ StatisticsRecorder::PrepareDeltas(false, HistogramBase::kNoFlags,
+ HistogramBase::kNoFlags,
+ &histogram_snapshot_manager_);
const std::vector<std::string>& histograms =
histogram_flattener_delta_recorder_.GetRecordedDeltaHistogramNames();
@@ -100,9 +87,9 @@ TEST_F(HistogramSnapshotManagerTest, PrepareDeltasUmaHistogramFlagFilter) {
UMA_HISTOGRAM_ENUMERATION("UmaHistogram", 1, 4);
UMA_STABILITY_HISTOGRAM_ENUMERATION("UmaStabilityHistogram", 1, 2);
- histogram_snapshot_manager_.PrepareDeltas(
- StatisticsRecorder::begin(false), StatisticsRecorder::end(),
- HistogramBase::kNoFlags, HistogramBase::kUmaTargetedHistogramFlag);
+ StatisticsRecorder::PrepareDeltas(false, HistogramBase::kNoFlags,
+ HistogramBase::kUmaTargetedHistogramFlag,
+ &histogram_snapshot_manager_);
const std::vector<std::string>& histograms =
histogram_flattener_delta_recorder_.GetRecordedDeltaHistogramNames();
@@ -116,9 +103,9 @@ TEST_F(HistogramSnapshotManagerTest,
UMA_HISTOGRAM_ENUMERATION("UmaHistogram", 1, 4);
UMA_STABILITY_HISTOGRAM_ENUMERATION("UmaStabilityHistogram", 1, 2);
- histogram_snapshot_manager_.PrepareDeltas(
- StatisticsRecorder::begin(false), StatisticsRecorder::end(),
- HistogramBase::kNoFlags, HistogramBase::kUmaStabilityHistogramFlag);
+ StatisticsRecorder::PrepareDeltas(false, HistogramBase::kNoFlags,
+ HistogramBase::kUmaStabilityHistogramFlag,
+ &histogram_snapshot_manager_);
const std::vector<std::string>& histograms =
histogram_flattener_delta_recorder_.GetRecordedDeltaHistogramNames();
diff --git a/base/metrics/histogram_unittest.cc b/base/metrics/histogram_unittest.cc
index 02ed93ba0e..e516acbe2d 100644
--- a/base/metrics/histogram_unittest.cc
+++ b/base/metrics/histogram_unittest.cc
@@ -13,11 +13,15 @@
#include <string>
#include <vector>
+#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/metrics/bucket_ranges.h"
+#include "base/metrics/dummy_histogram.h"
#include "base/metrics/histogram_macros.h"
+#include "base/metrics/metrics_hashes.h"
#include "base/metrics/persistent_histogram_allocator.h"
#include "base/metrics/persistent_memory_allocator.h"
+#include "base/metrics/record_histogram_checker.h"
#include "base/metrics/sample_vector.h"
#include "base/metrics/statistics_recorder.h"
#include "base/pickle.h"
@@ -27,6 +31,22 @@
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
+namespace {
+
+const char kExpiredHistogramName[] = "ExpiredHistogram";
+
+// Test implementation of RecordHistogramChecker interface.
+class TestRecordHistogramChecker : public RecordHistogramChecker {
+ public:
+ ~TestRecordHistogramChecker() override = default;
+
+ // RecordHistogramChecker:
+ bool ShouldRecord(uint64_t histogram_hash) const override {
+ return histogram_hash != HashMetricName(kExpiredHistogramName);
+ }
+};
+
+} // namespace
// Test parameter indicates if a persistent memory allocator should be used
// for histogram allocation. False will allocate histograms from the process
@@ -65,11 +85,6 @@ class HistogramTest : public testing::TestWithParam<bool> {
}
void CreatePersistentHistogramAllocator() {
- // By getting the results-histogram before any persistent allocator
- // is attached, that histogram is guaranteed not to be stored in
- // any persistent memory segment (which simplifies some tests).
- GlobalHistogramAllocator::GetCreateHistogramResultHistogram();
-
GlobalHistogramAllocator::CreateWithLocalMemory(
kAllocatorMemorySize, 0, "HistogramAllocatorTest");
allocator_ = GlobalHistogramAllocator::Get()->memory_allocator();
@@ -80,6 +95,10 @@ class HistogramTest : public testing::TestWithParam<bool> {
GlobalHistogramAllocator::ReleaseForTesting();
}
+ std::unique_ptr<SampleVector> SnapshotAllSamples(Histogram* h) {
+ return h->SnapshotAllSamples();
+ }
+
const bool use_persistent_histogram_allocator_;
std::unique_ptr<StatisticsRecorder> statistics_recorder_;
@@ -112,7 +131,7 @@ TEST_P(HistogramTest, BasicTest) {
"TestCustomHistogram", custom_ranges, HistogramBase::kNoFlags);
EXPECT_TRUE(custom_histogram);
- // Macros that create hitograms have an internal static variable which will
+ // Macros that create histograms have an internal static variable which will
// continue to point to those from the very first run of this method even
// during subsequent runs.
static bool already_run = false;
@@ -130,7 +149,7 @@ TEST_P(HistogramTest, BasicTest) {
// Check that the macro correctly matches histograms by name and records their
// data together.
TEST_P(HistogramTest, NameMatchTest) {
- // Macros that create hitograms have an internal static variable which will
+ // Macros that create histograms have an internal static variable which will
// continue to point to those from the very first run of this method even
// during subsequent runs.
static bool already_run = false;
@@ -276,10 +295,10 @@ TEST_P(HistogramTest, LinearRangesTest) {
EXPECT_TRUE(ranges2.Equals(histogram2->bucket_ranges()));
}
-TEST_P(HistogramTest, ArrayToCustomRangesTest) {
+TEST_P(HistogramTest, ArrayToCustomEnumRangesTest) {
const HistogramBase::Sample ranges[3] = {5, 10, 20};
std::vector<HistogramBase::Sample> ranges_vec =
- CustomHistogram::ArrayToCustomRanges(ranges, 3);
+ CustomHistogram::ArrayToCustomEnumRanges(ranges);
ASSERT_EQ(6u, ranges_vec.size());
EXPECT_EQ(5, ranges_vec[0]);
EXPECT_EQ(6, ranges_vec[1]);
@@ -401,6 +420,26 @@ TEST_P(HistogramTest, AddCount_LargeValuesDontOverflow) {
EXPECT_EQ(19400000000LL, samples2->sum());
}
+// Some metrics are designed so that they are guaranteed not to overflow between
+// snapshots, but could overflow over a long-running session.
+// Make sure that counts returned by Histogram::SnapshotDelta do not overflow
+// even when a total count (returned by Histogram::SnapshotSample) does.
+TEST_P(HistogramTest, AddCount_LargeCountsDontOverflow) {
+ const size_t kBucketCount = 10;
+ Histogram* histogram = static_cast<Histogram*>(Histogram::FactoryGet(
+ "AddCountHistogram", 10, 50, kBucketCount, HistogramBase::kNoFlags));
+
+ const int count = (1 << 30) - 1;
+
+ // Repeat N times to make sure that there is no internal value overflow.
+ for (int i = 0; i < 10; ++i) {
+ histogram->AddCount(42, count);
+ std::unique_ptr<HistogramSamples> samples = histogram->SnapshotDelta();
+ EXPECT_EQ(count, samples->TotalCount());
+ EXPECT_EQ(count, samples->GetCount(42));
+ }
+}
+
// Make sure histogram handles out-of-bounds data gracefully.
TEST_P(HistogramTest, BoundsTest) {
const size_t kBucketCount = 50;
@@ -416,7 +455,7 @@ TEST_P(HistogramTest, BoundsTest) {
histogram->Add(10000);
// Verify they landed in the underflow, and overflow buckets.
- std::unique_ptr<SampleVector> samples = histogram->SnapshotSampleVector();
+ std::unique_ptr<SampleVector> samples = histogram->SnapshotAllSamples();
EXPECT_EQ(2, samples->GetCountAtIndex(0));
EXPECT_EQ(0, samples->GetCountAtIndex(1));
size_t array_size = histogram->bucket_count();
@@ -441,7 +480,7 @@ TEST_P(HistogramTest, BoundsTest) {
// Verify they landed in the underflow, and overflow buckets.
std::unique_ptr<SampleVector> custom_samples =
- test_custom_histogram->SnapshotSampleVector();
+ test_custom_histogram->SnapshotAllSamples();
EXPECT_EQ(2, custom_samples->GetCountAtIndex(0));
EXPECT_EQ(0, custom_samples->GetCountAtIndex(1));
size_t bucket_count = test_custom_histogram->bucket_count();
@@ -464,7 +503,7 @@ TEST_P(HistogramTest, BucketPlacementTest) {
}
// Check to see that the bucket counts reflect our additions.
- std::unique_ptr<SampleVector> samples = histogram->SnapshotSampleVector();
+ std::unique_ptr<SampleVector> samples = histogram->SnapshotAllSamples();
for (int i = 0; i < 8; i++)
EXPECT_EQ(i + 1, samples->GetCountAtIndex(i));
}
@@ -484,21 +523,21 @@ TEST_P(HistogramTest, CorruptSampleCounts) {
histogram->Add(20);
histogram->Add(40);
- std::unique_ptr<SampleVector> snapshot = histogram->SnapshotSampleVector();
+ std::unique_ptr<SampleVector> snapshot = histogram->SnapshotAllSamples();
EXPECT_EQ(HistogramBase::NO_INCONSISTENCIES,
histogram->FindCorruption(*snapshot));
EXPECT_EQ(2, snapshot->redundant_count());
EXPECT_EQ(2, snapshot->TotalCount());
- snapshot->counts_[3] += 100; // Sample count won't match redundant count.
+ snapshot->counts()[3] += 100; // Sample count won't match redundant count.
EXPECT_EQ(HistogramBase::COUNT_LOW_ERROR,
histogram->FindCorruption(*snapshot));
- snapshot->counts_[2] -= 200;
+ snapshot->counts()[2] -= 200;
EXPECT_EQ(HistogramBase::COUNT_HIGH_ERROR,
histogram->FindCorruption(*snapshot));
// But we can't spot a corruption if it is compensated for.
- snapshot->counts_[1] += 100;
+ snapshot->counts()[1] += 100;
EXPECT_EQ(HistogramBase::NO_INCONSISTENCIES,
histogram->FindCorruption(*snapshot));
}
@@ -622,10 +661,10 @@ TEST_P(HistogramTest, BadConstruction) {
// Try to get the same histogram name with different arguments.
HistogramBase* bad_histogram = Histogram::FactoryGet(
"BadConstruction", 0, 100, 7, HistogramBase::kNoFlags);
- EXPECT_EQ(NULL, bad_histogram);
+ EXPECT_EQ(DummyHistogram::GetInstance(), bad_histogram);
bad_histogram = Histogram::FactoryGet(
"BadConstruction", 0, 99, 8, HistogramBase::kNoFlags);
- EXPECT_EQ(NULL, bad_histogram);
+ EXPECT_EQ(DummyHistogram::GetInstance(), bad_histogram);
HistogramBase* linear_histogram = LinearHistogram::FactoryGet(
"BadConstructionLinear", 0, 100, 8, HistogramBase::kNoFlags);
@@ -634,10 +673,10 @@ TEST_P(HistogramTest, BadConstruction) {
// Try to get the same histogram name with different arguments.
bad_histogram = LinearHistogram::FactoryGet(
"BadConstructionLinear", 0, 100, 7, HistogramBase::kNoFlags);
- EXPECT_EQ(NULL, bad_histogram);
+ EXPECT_EQ(DummyHistogram::GetInstance(), bad_histogram);
bad_histogram = LinearHistogram::FactoryGet(
"BadConstructionLinear", 10, 100, 8, HistogramBase::kNoFlags);
- EXPECT_EQ(NULL, bad_histogram);
+ EXPECT_EQ(DummyHistogram::GetInstance(), bad_histogram);
}
TEST_P(HistogramTest, FactoryTime) {
@@ -702,6 +741,41 @@ TEST_P(HistogramTest, FactoryTime) {
<< "ns each.";
}
+TEST_P(HistogramTest, ScaledLinearHistogram) {
+ ScaledLinearHistogram scaled("SLH", 1, 5, 6, 100, HistogramBase::kNoFlags);
+
+ scaled.AddScaledCount(0, 1);
+ scaled.AddScaledCount(1, 49);
+ scaled.AddScaledCount(2, 50);
+ scaled.AddScaledCount(3, 101);
+ scaled.AddScaledCount(4, 160);
+ scaled.AddScaledCount(5, 130);
+ scaled.AddScaledCount(6, 140);
+
+ std::unique_ptr<SampleVector> samples =
+ SnapshotAllSamples(scaled.histogram());
+ EXPECT_EQ(0, samples->GetCountAtIndex(0));
+ EXPECT_EQ(0, samples->GetCountAtIndex(1));
+ EXPECT_EQ(1, samples->GetCountAtIndex(2));
+ EXPECT_EQ(1, samples->GetCountAtIndex(3));
+ EXPECT_EQ(2, samples->GetCountAtIndex(4));
+ EXPECT_EQ(3, samples->GetCountAtIndex(5));
+
+ // Make sure the macros compile properly. This can only be run when
+ // there is no persistent allocator which can be discarded and leave
+ // dangling pointers.
+ if (!use_persistent_histogram_allocator_) {
+ enum EnumWithMax {
+ kA = 0,
+ kB = 1,
+ kC = 2,
+ kMaxValue = kC,
+ };
+ UMA_HISTOGRAM_SCALED_EXACT_LINEAR("h1", 1, 5000, 5, 100);
+ UMA_HISTOGRAM_SCALED_ENUMERATION("h2", kB, 5000, 100);
+ }
+}
+
// For Histogram, LinearHistogram and CustomHistogram, the minimum for a
// declared range is 1, while the maximum is (HistogramBase::kSampleType_MAX -
// 1). But we accept ranges exceeding those limits, and silently clamped to
@@ -749,4 +823,60 @@ TEST(HistogramDeathTest, BadRangesTest) {
"");
}
+TEST_P(HistogramTest, ExpiredHistogramTest) {
+ auto record_checker = std::make_unique<TestRecordHistogramChecker>();
+ StatisticsRecorder::SetRecordChecker(std::move(record_checker));
+
+ HistogramBase* expired = Histogram::FactoryGet(kExpiredHistogramName, 1, 1000,
+ 10, HistogramBase::kNoFlags);
+ ASSERT_TRUE(expired);
+ expired->Add(5);
+ expired->Add(500);
+ auto samples = expired->SnapshotDelta();
+ EXPECT_EQ(0, samples->TotalCount());
+
+ HistogramBase* linear_expired = LinearHistogram::FactoryGet(
+ kExpiredHistogramName, 1, 1000, 10, HistogramBase::kNoFlags);
+ ASSERT_TRUE(linear_expired);
+ linear_expired->Add(5);
+ linear_expired->Add(500);
+ samples = linear_expired->SnapshotDelta();
+ EXPECT_EQ(0, samples->TotalCount());
+
+ std::vector<int> custom_ranges;
+ custom_ranges.push_back(1);
+ custom_ranges.push_back(5);
+ HistogramBase* custom_expired = CustomHistogram::FactoryGet(
+ kExpiredHistogramName, custom_ranges, HistogramBase::kNoFlags);
+ ASSERT_TRUE(custom_expired);
+ custom_expired->Add(2);
+ custom_expired->Add(4);
+ samples = custom_expired->SnapshotDelta();
+ EXPECT_EQ(0, samples->TotalCount());
+
+ HistogramBase* valid = Histogram::FactoryGet("ValidHistogram", 1, 1000, 10,
+ HistogramBase::kNoFlags);
+ ASSERT_TRUE(valid);
+ valid->Add(5);
+ valid->Add(500);
+ samples = valid->SnapshotDelta();
+ EXPECT_EQ(2, samples->TotalCount());
+
+ HistogramBase* linear_valid = LinearHistogram::FactoryGet(
+ "LinearHistogram", 1, 1000, 10, HistogramBase::kNoFlags);
+ ASSERT_TRUE(linear_valid);
+ linear_valid->Add(5);
+ linear_valid->Add(500);
+ samples = linear_valid->SnapshotDelta();
+ EXPECT_EQ(2, samples->TotalCount());
+
+ HistogramBase* custom_valid = CustomHistogram::FactoryGet(
+ "CustomHistogram", custom_ranges, HistogramBase::kNoFlags);
+ ASSERT_TRUE(custom_valid);
+ custom_valid->Add(2);
+ custom_valid->Add(4);
+ samples = custom_valid->SnapshotDelta();
+ EXPECT_EQ(2, samples->TotalCount());
+}
+
} // namespace base
diff --git a/base/metrics/persistent_histogram_allocator.cc b/base/metrics/persistent_histogram_allocator.cc
index 5f44b67311..cb01c410f5 100644
--- a/base/metrics/persistent_histogram_allocator.cc
+++ b/base/metrics/persistent_histogram_allocator.cc
@@ -17,19 +17,22 @@
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_base.h"
#include "base/metrics/histogram_samples.h"
+#include "base/metrics/metrics_hashes.h"
#include "base/metrics/persistent_sample_map.h"
#include "base/metrics/sparse_histogram.h"
#include "base/metrics/statistics_recorder.h"
+#include "base/numerics/safe_conversions.h"
#include "base/pickle.h"
+#include "base/process/process_handle.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
namespace base {
namespace {
-// Name of histogram for storing results of local operations.
-const char kResultHistogram[] = "UMA.CreatePersistentHistogram.Result";
-
// Type identifiers used when storing in persistent memory so they can be
// identified during extraction; the first 4 bytes of the SHA1 of the name
// is used as a unique integer. A "version number" is added to the base
@@ -48,7 +51,7 @@ enum : uint32_t {
// managed elsewhere and which could be destructed first. An AtomicWord is
// used instead of std::atomic because the latter can create global ctors
// and dtors.
-subtle::AtomicWord g_allocator = 0;
+subtle::AtomicWord g_histogram_allocator = 0;
// Take an array of range boundaries and create a proper BucketRanges object
// which is returned to the caller. A return of nullptr indicates that the
@@ -100,7 +103,8 @@ PersistentSparseHistogramDataManager::PersistentSparseHistogramDataManager(
PersistentMemoryAllocator* allocator)
: allocator_(allocator), record_iterator_(allocator) {}
-PersistentSparseHistogramDataManager::~PersistentSparseHistogramDataManager() {}
+PersistentSparseHistogramDataManager::~PersistentSparseHistogramDataManager() =
+ default;
PersistentSampleMapRecords*
PersistentSparseHistogramDataManager::UseSampleMapRecords(uint64_t id,
@@ -119,7 +123,7 @@ PersistentSparseHistogramDataManager::GetSampleMapRecordsWhileLocked(
return found->second.get();
std::unique_ptr<PersistentSampleMapRecords>& samples = sample_records_[id];
- samples = MakeUnique<PersistentSampleMapRecords>(this, id);
+ samples = std::make_unique<PersistentSampleMapRecords>(this, id);
return samples.get();
}
@@ -183,7 +187,7 @@ PersistentSampleMapRecords::PersistentSampleMapRecords(
uint64_t sample_map_id)
: data_manager_(data_manager), sample_map_id_(sample_map_id) {}
-PersistentSampleMapRecords::~PersistentSampleMapRecords() {}
+PersistentSampleMapRecords::~PersistentSampleMapRecords() = default;
PersistentSampleMapRecords* PersistentSampleMapRecords::Acquire(
const void* user) {
@@ -240,7 +244,7 @@ struct PersistentHistogramAllocator::PersistentHistogramData {
uint32_t bucket_count;
PersistentMemoryAllocator::Reference ranges_ref;
uint32_t ranges_checksum;
- PersistentMemoryAllocator::Reference counts_ref;
+ subtle::Atomic32 counts_ref; // PersistentMemoryAllocator::Reference
HistogramSamples::Metadata samples_metadata;
HistogramSamples::Metadata logged_metadata;
@@ -270,7 +274,7 @@ PersistentHistogramAllocator::PersistentHistogramAllocator(
: memory_allocator_(std::move(memory)),
sparse_histogram_data_manager_(memory_allocator_.get()) {}
-PersistentHistogramAllocator::~PersistentHistogramAllocator() {}
+PersistentHistogramAllocator::~PersistentHistogramAllocator() = default;
std::unique_ptr<HistogramBase> PersistentHistogramAllocator::GetHistogram(
Reference ref) {
@@ -279,23 +283,27 @@ std::unique_ptr<HistogramBase> PersistentHistogramAllocator::GetHistogram(
// count data (while these must reference the persistent counts) and always
// add it to the local list of known histograms (while these may be simple
// references to histograms in other processes).
- PersistentHistogramData* histogram_data =
+ PersistentHistogramData* data =
memory_allocator_->GetAsObject<PersistentHistogramData>(ref);
- size_t length = memory_allocator_->GetAllocSize(ref);
+ const size_t length = memory_allocator_->GetAllocSize(ref);
- // Check that metadata is reasonable: name is NUL terminated and non-empty,
+ // Check that metadata is reasonable: name is null-terminated and non-empty,
// ID fields have been loaded with a hash of the name (0 is considered
// unset/invalid).
- if (!histogram_data ||
- reinterpret_cast<char*>(histogram_data)[length - 1] != '\0' ||
- histogram_data->name[0] == '\0' ||
- histogram_data->samples_metadata.id == 0 ||
- histogram_data->logged_metadata.id == 0) {
- RecordCreateHistogramResult(CREATE_HISTOGRAM_INVALID_METADATA);
- NOTREACHED();
+ if (!data || data->name[0] == '\0' ||
+ reinterpret_cast<char*>(data)[length - 1] != '\0' ||
+ data->samples_metadata.id == 0 || data->logged_metadata.id == 0 ||
+ // Note: Sparse histograms use |id + 1| in |logged_metadata|.
+ (data->logged_metadata.id != data->samples_metadata.id &&
+ data->logged_metadata.id != data->samples_metadata.id + 1) ||
+ // Most non-matching values happen due to truncated names. Ideally, we
+ // could just verify the name length based on the overall alloc length,
+ // but that doesn't work because the allocated block may have been
+ // aligned to the next boundary value.
+ HashMetricName(data->name) != data->samples_metadata.id) {
return nullptr;
}
- return CreateHistogram(histogram_data);
+ return CreateHistogram(data);
}
std::unique_ptr<HistogramBase> PersistentHistogramAllocator::AllocateHistogram(
@@ -310,10 +318,8 @@ std::unique_ptr<HistogramBase> PersistentHistogramAllocator::AllocateHistogram(
// This also allows differentiating on the dashboard between allocations
// failed due to a corrupt allocator and the number of process instances
// with one, the latter being idicated by "newly corrupt", below.
- if (memory_allocator_->IsCorrupt()) {
- RecordCreateHistogramResult(CREATE_HISTOGRAM_ALLOCATOR_CORRUPT);
+ if (memory_allocator_->IsCorrupt())
return nullptr;
- }
// Create the metadata necessary for a persistent sparse histogram. This
// is done first because it is a small subset of what is required for
@@ -336,28 +342,49 @@ std::unique_ptr<HistogramBase> PersistentHistogramAllocator::AllocateHistogram(
size_t counts_bytes = CalculateRequiredCountsBytes(bucket_count);
if (counts_bytes == 0) {
// |bucket_count| was out-of-range.
- NOTREACHED();
return nullptr;
}
- size_t ranges_count = bucket_count + 1;
- size_t ranges_bytes = ranges_count * sizeof(HistogramBase::Sample);
- PersistentMemoryAllocator::Reference counts_ref =
- memory_allocator_->Allocate(counts_bytes, kTypeIdCountsArray);
+ // Since the StasticsRecorder keeps a global collection of BucketRanges
+ // objects for re-use, it would be dangerous for one to hold a reference
+ // from a persistent allocator that is not the global one (which is
+ // permanent once set). If this stops being the case, this check can
+ // become an "if" condition beside "!ranges_ref" below and before
+ // set_persistent_reference() farther down.
+ DCHECK_EQ(this, GlobalHistogramAllocator::Get());
+
+ // Re-use an existing BucketRanges persistent allocation if one is known;
+ // otherwise, create one.
PersistentMemoryAllocator::Reference ranges_ref =
- memory_allocator_->Allocate(ranges_bytes, kTypeIdRangesArray);
- HistogramBase::Sample* ranges_data =
- memory_allocator_->GetAsArray<HistogramBase::Sample>(
- ranges_ref, kTypeIdRangesArray, ranges_count);
+ bucket_ranges->persistent_reference();
+ if (!ranges_ref) {
+ size_t ranges_count = bucket_count + 1;
+ size_t ranges_bytes = ranges_count * sizeof(HistogramBase::Sample);
+ ranges_ref =
+ memory_allocator_->Allocate(ranges_bytes, kTypeIdRangesArray);
+ if (ranges_ref) {
+ HistogramBase::Sample* ranges_data =
+ memory_allocator_->GetAsArray<HistogramBase::Sample>(
+ ranges_ref, kTypeIdRangesArray, ranges_count);
+ if (ranges_data) {
+ for (size_t i = 0; i < bucket_ranges->size(); ++i)
+ ranges_data[i] = bucket_ranges->range(i);
+ bucket_ranges->set_persistent_reference(ranges_ref);
+ } else {
+ // This should never happen but be tolerant if it does.
+ ranges_ref = PersistentMemoryAllocator::kReferenceNull;
+ }
+ }
+ } else {
+ DCHECK_EQ(kTypeIdRangesArray, memory_allocator_->GetType(ranges_ref));
+ }
+
// Only continue here if all allocations were successful. If they weren't,
// there is no way to free the space but that's not really a problem since
// the allocations only fail because the space is full or corrupt and so
// any future attempts will also fail.
- if (counts_ref && ranges_data && histogram_data) {
- for (size_t i = 0; i < bucket_ranges->size(); ++i)
- ranges_data[i] = bucket_ranges->range(i);
-
+ if (ranges_ref && histogram_data) {
histogram_data->minimum = minimum;
histogram_data->maximum = maximum;
// |bucket_count| must fit within 32-bits or the allocation of the counts
@@ -366,7 +393,6 @@ std::unique_ptr<HistogramBase> PersistentHistogramAllocator::AllocateHistogram(
histogram_data->bucket_count = static_cast<uint32_t>(bucket_count);
histogram_data->ranges_ref = ranges_ref;
histogram_data->ranges_checksum = bucket_ranges->checksum();
- histogram_data->counts_ref = counts_ref;
} else {
histogram_data = nullptr; // Clear this for proper handling below.
}
@@ -396,22 +422,6 @@ std::unique_ptr<HistogramBase> PersistentHistogramAllocator::AllocateHistogram(
return histogram;
}
- CreateHistogramResultType result;
- if (memory_allocator_->IsCorrupt()) {
- RecordCreateHistogramResult(CREATE_HISTOGRAM_ALLOCATOR_NEWLY_CORRUPT);
- result = CREATE_HISTOGRAM_ALLOCATOR_CORRUPT;
- } else if (memory_allocator_->IsFull()) {
- result = CREATE_HISTOGRAM_ALLOCATOR_FULL;
- } else {
- result = CREATE_HISTOGRAM_ALLOCATOR_ERROR;
- }
- RecordCreateHistogramResult(result);
-
- // Crash for failures caused by internal bugs but not "full" which is
- // dependent on outside code.
- if (result != CREATE_HISTOGRAM_ALLOCATOR_FULL)
- NOTREACHED() << memory_allocator_->Name() << ", error=" << result;
-
return nullptr;
}
@@ -444,7 +454,6 @@ void PersistentHistogramAllocator::MergeHistogramDeltaToStatisticsRecorder(
// so a future try, if successful, will get what was missed. If it
// continues to fail, some metric data will be lost but that is better
// than crashing.
- NOTREACHED();
return;
}
@@ -460,7 +469,6 @@ void PersistentHistogramAllocator::MergeHistogramFinalDeltaToStatisticsRecorder(
if (!existing) {
// The above should never fail but if it does, no real harm is done.
// Some metric data will be lost but that is better than crashing.
- NOTREACHED();
return;
}
@@ -486,50 +494,10 @@ void PersistentHistogramAllocator::ClearLastCreatedReferenceForTesting() {
subtle::NoBarrier_Store(&last_created_, 0);
}
-// static
-HistogramBase*
-PersistentHistogramAllocator::GetCreateHistogramResultHistogram() {
- // Get the histogram in which create-results are stored. This is copied
- // almost exactly from the STATIC_HISTOGRAM_POINTER_BLOCK macro but with
- // added code to prevent recursion (a likely occurance because the creation
- // of a new a histogram can end up calling this.)
- static base::subtle::AtomicWord atomic_histogram_pointer = 0;
- HistogramBase* histogram_pointer =
- reinterpret_cast<HistogramBase*>(
- base::subtle::Acquire_Load(&atomic_histogram_pointer));
- if (!histogram_pointer) {
- // It's possible for multiple threads to make it here in parallel but
- // they'll always return the same result as there is a mutex in the Get.
- // The purpose of the "initialized" variable is just to ensure that
- // the same thread doesn't recurse which is also why it doesn't have
- // to be atomic.
- static bool initialized = false;
- if (!initialized) {
- initialized = true;
- if (GlobalHistogramAllocator::Get()) {
- DVLOG(1) << "Creating the results-histogram inside persistent"
- << " memory can cause future allocations to crash if"
- << " that memory is ever released (for testing).";
- }
-
- histogram_pointer = LinearHistogram::FactoryGet(
- kResultHistogram, 1, CREATE_HISTOGRAM_MAX, CREATE_HISTOGRAM_MAX + 1,
- HistogramBase::kUmaTargetedHistogramFlag);
- base::subtle::Release_Store(
- &atomic_histogram_pointer,
- reinterpret_cast<base::subtle::AtomicWord>(histogram_pointer));
- }
- }
- return histogram_pointer;
-}
-
std::unique_ptr<HistogramBase> PersistentHistogramAllocator::CreateHistogram(
PersistentHistogramData* histogram_data_ptr) {
- if (!histogram_data_ptr) {
- RecordCreateHistogramResult(CREATE_HISTOGRAM_INVALID_METADATA_POINTER);
- NOTREACHED();
+ if (!histogram_data_ptr)
return nullptr;
- }
// Sparse histograms are quite different so handle them as a special case.
if (histogram_data_ptr->histogram_type == SPARSE_HISTOGRAM) {
@@ -539,84 +507,90 @@ std::unique_ptr<HistogramBase> PersistentHistogramAllocator::CreateHistogram(
&histogram_data_ptr->logged_metadata);
DCHECK(histogram);
histogram->SetFlags(histogram_data_ptr->flags);
- RecordCreateHistogramResult(CREATE_HISTOGRAM_SUCCESS);
return histogram;
}
- // Copy the histogram_data to local storage because anything in persistent
- // memory cannot be trusted as it could be changed at any moment by a
- // malicious actor that shares access. The contents of histogram_data are
- // validated below; the local copy is to ensure that the contents cannot
- // be externally changed between validation and use.
- PersistentHistogramData histogram_data = *histogram_data_ptr;
+ // Copy the configuration fields from histogram_data_ptr to local storage
+ // because anything in persistent memory cannot be trusted as it could be
+ // changed at any moment by a malicious actor that shares access. The local
+ // values are validated below and then used to create the histogram, knowing
+ // they haven't changed between validation and use.
+ int32_t histogram_type = histogram_data_ptr->histogram_type;
+ int32_t histogram_flags = histogram_data_ptr->flags;
+ int32_t histogram_minimum = histogram_data_ptr->minimum;
+ int32_t histogram_maximum = histogram_data_ptr->maximum;
+ uint32_t histogram_bucket_count = histogram_data_ptr->bucket_count;
+ uint32_t histogram_ranges_ref = histogram_data_ptr->ranges_ref;
+ uint32_t histogram_ranges_checksum = histogram_data_ptr->ranges_checksum;
HistogramBase::Sample* ranges_data =
memory_allocator_->GetAsArray<HistogramBase::Sample>(
- histogram_data.ranges_ref, kTypeIdRangesArray,
+ histogram_ranges_ref, kTypeIdRangesArray,
PersistentMemoryAllocator::kSizeAny);
const uint32_t max_buckets =
std::numeric_limits<uint32_t>::max() / sizeof(HistogramBase::Sample);
size_t required_bytes =
- (histogram_data.bucket_count + 1) * sizeof(HistogramBase::Sample);
+ (histogram_bucket_count + 1) * sizeof(HistogramBase::Sample);
size_t allocated_bytes =
- memory_allocator_->GetAllocSize(histogram_data.ranges_ref);
- if (!ranges_data || histogram_data.bucket_count < 2 ||
- histogram_data.bucket_count >= max_buckets ||
+ memory_allocator_->GetAllocSize(histogram_ranges_ref);
+ if (!ranges_data || histogram_bucket_count < 2 ||
+ histogram_bucket_count >= max_buckets ||
allocated_bytes < required_bytes) {
- RecordCreateHistogramResult(CREATE_HISTOGRAM_INVALID_RANGES_ARRAY);
- NOTREACHED();
return nullptr;
}
- std::unique_ptr<const BucketRanges> created_ranges =
- CreateRangesFromData(ranges_data, histogram_data.ranges_checksum,
- histogram_data.bucket_count + 1);
- if (!created_ranges) {
- RecordCreateHistogramResult(CREATE_HISTOGRAM_INVALID_RANGES_ARRAY);
- NOTREACHED();
+ std::unique_ptr<const BucketRanges> created_ranges = CreateRangesFromData(
+ ranges_data, histogram_ranges_checksum, histogram_bucket_count + 1);
+ if (!created_ranges)
return nullptr;
- }
const BucketRanges* ranges =
StatisticsRecorder::RegisterOrDeleteDuplicateRanges(
created_ranges.release());
- HistogramBase::AtomicCount* counts_data =
- memory_allocator_->GetAsArray<HistogramBase::AtomicCount>(
- histogram_data.counts_ref, kTypeIdCountsArray,
- PersistentMemoryAllocator::kSizeAny);
- size_t counts_bytes =
- CalculateRequiredCountsBytes(histogram_data.bucket_count);
- if (!counts_data || counts_bytes == 0 ||
- memory_allocator_->GetAllocSize(histogram_data.counts_ref) <
- counts_bytes) {
- RecordCreateHistogramResult(CREATE_HISTOGRAM_INVALID_COUNTS_ARRAY);
- NOTREACHED();
+ size_t counts_bytes = CalculateRequiredCountsBytes(histogram_bucket_count);
+ PersistentMemoryAllocator::Reference counts_ref =
+ subtle::Acquire_Load(&histogram_data_ptr->counts_ref);
+ if (counts_bytes == 0 ||
+ (counts_ref != 0 &&
+ memory_allocator_->GetAllocSize(counts_ref) < counts_bytes)) {
return nullptr;
}
- // After the main "counts" array is a second array using for storing what
- // was previously logged. This is used to calculate the "delta" during
- // snapshot operations.
- HistogramBase::AtomicCount* logged_data =
- counts_data + histogram_data.bucket_count;
-
- std::string name(histogram_data_ptr->name);
+ // The "counts" data (including both samples and logged samples) is a delayed
+ // persistent allocation meaning that though its size and storage for a
+ // reference is defined, no space is reserved until actually needed. When
+ // it is needed, memory will be allocated from the persistent segment and
+ // a reference to it stored at the passed address. Other threads can then
+ // notice the valid reference and access the same data.
+ DelayedPersistentAllocation counts_data(memory_allocator_.get(),
+ &histogram_data_ptr->counts_ref,
+ kTypeIdCountsArray, counts_bytes, 0);
+
+ // A second delayed allocations is defined using the same reference storage
+ // location as the first so the allocation of one will automatically be found
+ // by the other. Within the block, the first half of the space is for "counts"
+ // and the second half is for "logged counts".
+ DelayedPersistentAllocation logged_data(
+ memory_allocator_.get(), &histogram_data_ptr->counts_ref,
+ kTypeIdCountsArray, counts_bytes, counts_bytes / 2,
+ /*make_iterable=*/false);
+
+ // Create the right type of histogram.
+ const char* name = histogram_data_ptr->name;
std::unique_ptr<HistogramBase> histogram;
- switch (histogram_data.histogram_type) {
+ switch (histogram_type) {
case HISTOGRAM:
histogram = Histogram::PersistentCreate(
- name, histogram_data.minimum, histogram_data.maximum, ranges,
- counts_data, logged_data, histogram_data.bucket_count,
- &histogram_data_ptr->samples_metadata,
+ name, histogram_minimum, histogram_maximum, ranges, counts_data,
+ logged_data, &histogram_data_ptr->samples_metadata,
&histogram_data_ptr->logged_metadata);
DCHECK(histogram);
break;
case LINEAR_HISTOGRAM:
histogram = LinearHistogram::PersistentCreate(
- name, histogram_data.minimum, histogram_data.maximum, ranges,
- counts_data, logged_data, histogram_data.bucket_count,
- &histogram_data_ptr->samples_metadata,
+ name, histogram_minimum, histogram_maximum, ranges, counts_data,
+ logged_data, &histogram_data_ptr->samples_metadata,
&histogram_data_ptr->logged_metadata);
DCHECK(histogram);
break;
@@ -629,21 +603,18 @@ std::unique_ptr<HistogramBase> PersistentHistogramAllocator::CreateHistogram(
break;
case CUSTOM_HISTOGRAM:
histogram = CustomHistogram::PersistentCreate(
- name, ranges, counts_data, logged_data, histogram_data.bucket_count,
+ name, ranges, counts_data, logged_data,
&histogram_data_ptr->samples_metadata,
&histogram_data_ptr->logged_metadata);
DCHECK(histogram);
break;
default:
- NOTREACHED();
+ return nullptr;
}
if (histogram) {
- DCHECK_EQ(histogram_data.histogram_type, histogram->GetHistogramType());
- histogram->SetFlags(histogram_data.flags);
- RecordCreateHistogramResult(CREATE_HISTOGRAM_SUCCESS);
- } else {
- RecordCreateHistogramResult(CREATE_HISTOGRAM_UNKNOWN_TYPE);
+ DCHECK_EQ(histogram_type, histogram->GetHistogramType());
+ histogram->SetFlags(histogram_flags);
}
return histogram;
@@ -668,8 +639,7 @@ PersistentHistogramAllocator::GetOrCreateStatisticsRecorderHistogram(
// FactoryGet() which will create the histogram in the global persistent-
// histogram allocator if such is set.
base::Pickle pickle;
- if (!histogram->SerializeInfo(&pickle))
- return nullptr;
+ histogram->SerializeInfo(&pickle);
PickleIterator iter(pickle);
existing = DeserializeHistogramInfo(&iter);
if (!existing)
@@ -681,15 +651,7 @@ PersistentHistogramAllocator::GetOrCreateStatisticsRecorderHistogram(
return StatisticsRecorder::RegisterOrDeleteDuplicate(existing);
}
-// static
-void PersistentHistogramAllocator::RecordCreateHistogramResult(
- CreateHistogramResultType result) {
- HistogramBase* result_histogram = GetCreateHistogramResultHistogram();
- if (result_histogram)
- result_histogram->Add(result);
-}
-
-GlobalHistogramAllocator::~GlobalHistogramAllocator() {}
+GlobalHistogramAllocator::~GlobalHistogramAllocator() = default;
// static
void GlobalHistogramAllocator::CreateWithPersistentMemory(
@@ -699,7 +661,7 @@ void GlobalHistogramAllocator::CreateWithPersistentMemory(
uint64_t id,
StringPiece name) {
Set(WrapUnique(
- new GlobalHistogramAllocator(MakeUnique<PersistentMemoryAllocator>(
+ new GlobalHistogramAllocator(std::make_unique<PersistentMemoryAllocator>(
base, size, page_size, id, name, false))));
}
@@ -709,7 +671,7 @@ void GlobalHistogramAllocator::CreateWithLocalMemory(
uint64_t id,
StringPiece name) {
Set(WrapUnique(new GlobalHistogramAllocator(
- MakeUnique<LocalPersistentMemoryAllocator>(size, id, name))));
+ std::make_unique<LocalPersistentMemoryAllocator>(size, id, name))));
}
#if !defined(OS_NACL)
@@ -726,20 +688,20 @@ bool GlobalHistogramAllocator::CreateWithFile(
std::unique_ptr<MemoryMappedFile> mmfile(new MemoryMappedFile());
if (exists) {
+ size = saturated_cast<size_t>(file.GetLength());
mmfile->Initialize(std::move(file), MemoryMappedFile::READ_WRITE);
} else {
- mmfile->Initialize(std::move(file), {0, static_cast<int64_t>(size)},
+ mmfile->Initialize(std::move(file), {0, size},
MemoryMappedFile::READ_WRITE_EXTEND);
}
if (!mmfile->IsValid() ||
!FilePersistentMemoryAllocator::IsFileAcceptable(*mmfile, true)) {
- NOTREACHED();
return false;
}
- Set(WrapUnique(
- new GlobalHistogramAllocator(MakeUnique<FilePersistentMemoryAllocator>(
- std::move(mmfile), size, id, name, false))));
+ Set(WrapUnique(new GlobalHistogramAllocator(
+ std::make_unique<FilePersistentMemoryAllocator>(std::move(mmfile), size,
+ id, name, false))));
Get()->SetPersistentLocation(file_path);
return true;
}
@@ -747,11 +709,20 @@ bool GlobalHistogramAllocator::CreateWithFile(
// static
bool GlobalHistogramAllocator::CreateWithActiveFile(const FilePath& base_path,
const FilePath& active_path,
+ const FilePath& spare_path,
size_t size,
uint64_t id,
StringPiece name) {
+ // Old "active" becomes "base".
if (!base::ReplaceFile(active_path, base_path, nullptr))
base::DeleteFile(base_path, /*recursive=*/false);
+ DCHECK(!base::PathExists(active_path));
+
+ // Move any "spare" into "active". Okay to continue if file doesn't exist.
+ if (!spare_path.empty()) {
+ base::ReplaceFile(spare_path, active_path, nullptr);
+ DCHECK(!base::PathExists(spare_path));
+ }
return base::GlobalHistogramAllocator::CreateWithFile(active_path, size, id,
name);
@@ -762,26 +733,139 @@ bool GlobalHistogramAllocator::CreateWithActiveFileInDir(const FilePath& dir,
size_t size,
uint64_t id,
StringPiece name) {
- FilePath base_path, active_path;
- ConstructFilePaths(dir, name, &base_path, &active_path);
- return CreateWithActiveFile(base_path, active_path, size, id, name);
+ FilePath base_path, active_path, spare_path;
+ ConstructFilePaths(dir, name, &base_path, &active_path, &spare_path);
+ return CreateWithActiveFile(base_path, active_path, spare_path, size, id,
+ name);
+}
+
+// static
+FilePath GlobalHistogramAllocator::ConstructFilePath(const FilePath& dir,
+ StringPiece name) {
+ return dir.AppendASCII(name).AddExtension(
+ PersistentMemoryAllocator::kFileExtension);
+}
+
+// static
+FilePath GlobalHistogramAllocator::ConstructFilePathForUploadDir(
+ const FilePath& dir,
+ StringPiece name,
+ base::Time stamp,
+ ProcessId pid) {
+ return ConstructFilePath(
+ dir,
+ StringPrintf("%.*s-%lX-%lX", static_cast<int>(name.length()), name.data(),
+ static_cast<long>(stamp.ToTimeT()), static_cast<long>(pid)));
+}
+
+// static
+bool GlobalHistogramAllocator::ParseFilePath(const FilePath& path,
+ std::string* out_name,
+ Time* out_stamp,
+ ProcessId* out_pid) {
+ std::string filename = path.BaseName().AsUTF8Unsafe();
+ std::vector<base::StringPiece> parts = base::SplitStringPiece(
+ filename, "-.", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+ if (parts.size() != 4)
+ return false;
+
+ if (out_name)
+ *out_name = parts[0].as_string();
+
+ if (out_stamp) {
+ int64_t stamp;
+ if (!HexStringToInt64(parts[1], &stamp))
+ return false;
+ *out_stamp = Time::FromTimeT(static_cast<time_t>(stamp));
+ }
+
+ if (out_pid) {
+ int64_t pid;
+ if (!HexStringToInt64(parts[2], &pid))
+ return false;
+ *out_pid = static_cast<ProcessId>(pid);
+ }
+
+ return true;
}
// static
void GlobalHistogramAllocator::ConstructFilePaths(const FilePath& dir,
StringPiece name,
FilePath* out_base_path,
- FilePath* out_active_path) {
- if (out_base_path) {
- *out_base_path = dir.AppendASCII(name).AddExtension(
- PersistentMemoryAllocator::kFileExtension);
+ FilePath* out_active_path,
+ FilePath* out_spare_path) {
+ if (out_base_path)
+ *out_base_path = ConstructFilePath(dir, name);
+
+ if (out_active_path) {
+ *out_active_path =
+ ConstructFilePath(dir, name.as_string().append("-active"));
}
+
+ if (out_spare_path) {
+ *out_spare_path = ConstructFilePath(dir, name.as_string().append("-spare"));
+ }
+}
+
+// static
+void GlobalHistogramAllocator::ConstructFilePathsForUploadDir(
+ const FilePath& active_dir,
+ const FilePath& upload_dir,
+ const std::string& name,
+ FilePath* out_upload_path,
+ FilePath* out_active_path,
+ FilePath* out_spare_path) {
+ if (out_upload_path) {
+ *out_upload_path = ConstructFilePathForUploadDir(
+ upload_dir, name, Time::Now(), GetCurrentProcId());
+ }
+
if (out_active_path) {
*out_active_path =
- dir.AppendASCII(name.as_string() + std::string("-active"))
- .AddExtension(PersistentMemoryAllocator::kFileExtension);
+ ConstructFilePath(active_dir, name + std::string("-active"));
+ }
+
+ if (out_spare_path) {
+ *out_spare_path =
+ ConstructFilePath(active_dir, name + std::string("-spare"));
}
}
+
+// static
+bool GlobalHistogramAllocator::CreateSpareFile(const FilePath& spare_path,
+ size_t size) {
+ FilePath temp_spare_path = spare_path.AddExtension(FILE_PATH_LITERAL(".tmp"));
+ bool success = true;
+ {
+ File spare_file(temp_spare_path, File::FLAG_CREATE_ALWAYS |
+ File::FLAG_READ | File::FLAG_WRITE);
+ if (!spare_file.IsValid())
+ return false;
+
+ MemoryMappedFile mmfile;
+ mmfile.Initialize(std::move(spare_file), {0, size},
+ MemoryMappedFile::READ_WRITE_EXTEND);
+ success = mmfile.IsValid();
+ }
+
+ if (success)
+ success = ReplaceFile(temp_spare_path, spare_path, nullptr);
+
+ if (!success)
+ DeleteFile(temp_spare_path, /*recursive=*/false);
+
+ return success;
+}
+
+// static
+bool GlobalHistogramAllocator::CreateSpareFileInDir(const FilePath& dir,
+ size_t size,
+ StringPiece name) {
+ FilePath spare_path;
+ ConstructFilePaths(dir, name, nullptr, nullptr, &spare_path);
+ return CreateSpareFile(spare_path, size);
+}
#endif // !defined(OS_NACL)
// static
@@ -792,12 +876,11 @@ void GlobalHistogramAllocator::CreateWithSharedMemoryHandle(
new SharedMemory(handle, /*readonly=*/false));
if (!shm->Map(size) ||
!SharedPersistentMemoryAllocator::IsSharedMemoryAcceptable(*shm)) {
- NOTREACHED();
return;
}
- Set(WrapUnique(
- new GlobalHistogramAllocator(MakeUnique<SharedPersistentMemoryAllocator>(
+ Set(WrapUnique(new GlobalHistogramAllocator(
+ std::make_unique<SharedPersistentMemoryAllocator>(
std::move(shm), 0, StringPiece(), /*readonly=*/false))));
}
@@ -807,8 +890,8 @@ void GlobalHistogramAllocator::Set(
// Releasing or changing an allocator is extremely dangerous because it
// likely has histograms stored within it. If the backing memory is also
// also released, future accesses to those histograms will seg-fault.
- CHECK(!subtle::NoBarrier_Load(&g_allocator));
- subtle::Release_Store(&g_allocator,
+ CHECK(!subtle::NoBarrier_Load(&g_histogram_allocator));
+ subtle::Release_Store(&g_histogram_allocator,
reinterpret_cast<uintptr_t>(allocator.release()));
size_t existing = StatisticsRecorder::GetHistogramCount();
@@ -819,7 +902,7 @@ void GlobalHistogramAllocator::Set(
// static
GlobalHistogramAllocator* GlobalHistogramAllocator::Get() {
return reinterpret_cast<GlobalHistogramAllocator*>(
- subtle::Acquire_Load(&g_allocator));
+ subtle::Acquire_Load(&g_histogram_allocator));
}
// static
@@ -838,18 +921,9 @@ GlobalHistogramAllocator::ReleaseForTesting() {
const PersistentHistogramData* data;
while ((data = iter.GetNextOfObject<PersistentHistogramData>()) != nullptr) {
StatisticsRecorder::ForgetHistogramForTesting(data->name);
-
- // If a test breaks here then a memory region containing a histogram
- // actively used by this code is being released back to the test.
- // If that memory segment were to be deleted, future calls to create
- // persistent histograms would crash. To avoid this, have the test call
- // the method GetCreateHistogramResultHistogram() *before* setting
- // the (temporary) memory allocator via SetGlobalAllocator() so that
- // histogram is instead allocated from the process heap.
- DCHECK_NE(kResultHistogram, data->name);
}
- subtle::Release_Store(&g_allocator, 0);
+ subtle::Release_Store(&g_histogram_allocator, 0);
return WrapUnique(histogram_allocator);
};
@@ -908,9 +982,6 @@ GlobalHistogramAllocator::GlobalHistogramAllocator(
std::unique_ptr<PersistentMemoryAllocator> memory)
: PersistentHistogramAllocator(std::move(memory)),
import_iterator_(this) {
- // Make sure the StatisticsRecorder is initialized to prevent duplicate
- // histograms from being created. It's safe to call this multiple times.
- StatisticsRecorder::Initialize();
}
void GlobalHistogramAllocator::ImportHistogramsToStatisticsRecorder() {
diff --git a/base/metrics/persistent_histogram_allocator.h b/base/metrics/persistent_histogram_allocator.h
index 851d7ef5a4..395511fb74 100644
--- a/base/metrics/persistent_histogram_allocator.h
+++ b/base/metrics/persistent_histogram_allocator.h
@@ -287,9 +287,6 @@ class BASE_EXPORT PersistentHistogramAllocator {
// operation without that optimization.
void ClearLastCreatedReferenceForTesting();
- // Histogram containing creation results. Visible for testing.
- static HistogramBase* GetCreateHistogramResultHistogram();
-
protected:
// The structure used to hold histogram data in persistent memory. It is
// defined and used entirely within the .cc file.
@@ -307,42 +304,6 @@ class BASE_EXPORT PersistentHistogramAllocator {
Reference ignore);
private:
- // Enumerate possible creation results for reporting.
- enum CreateHistogramResultType {
- // Everything was fine.
- CREATE_HISTOGRAM_SUCCESS = 0,
-
- // Pointer to metadata was not valid.
- CREATE_HISTOGRAM_INVALID_METADATA_POINTER,
-
- // Histogram metadata was not valid.
- CREATE_HISTOGRAM_INVALID_METADATA,
-
- // Ranges information was not valid.
- CREATE_HISTOGRAM_INVALID_RANGES_ARRAY,
-
- // Counts information was not valid.
- CREATE_HISTOGRAM_INVALID_COUNTS_ARRAY,
-
- // Could not allocate histogram memory due to corruption.
- CREATE_HISTOGRAM_ALLOCATOR_CORRUPT,
-
- // Could not allocate histogram memory due to lack of space.
- CREATE_HISTOGRAM_ALLOCATOR_FULL,
-
- // Could not allocate histogram memory due to unknown error.
- CREATE_HISTOGRAM_ALLOCATOR_ERROR,
-
- // Histogram was of unknown type.
- CREATE_HISTOGRAM_UNKNOWN_TYPE,
-
- // Instance has detected a corrupt allocator (recorded only once).
- CREATE_HISTOGRAM_ALLOCATOR_NEWLY_CORRUPT,
-
- // Always keep this at the end.
- CREATE_HISTOGRAM_MAX
- };
-
// Create a histogram based on saved (persistent) information about it.
std::unique_ptr<HistogramBase> CreateHistogram(
PersistentHistogramData* histogram_data_ptr);
@@ -353,9 +314,6 @@ class BASE_EXPORT PersistentHistogramAllocator {
HistogramBase* GetOrCreateStatisticsRecorderHistogram(
const HistogramBase* histogram);
- // Record the result of a histogram creation.
- static void RecordCreateHistogramResult(CreateHistogramResultType result);
-
// The memory allocator that provides the actual histogram storage.
std::unique_ptr<PersistentMemoryAllocator> memory_allocator_;
@@ -404,10 +362,12 @@ class BASE_EXPORT GlobalHistogramAllocator
// Creates a new file at |active_path|. If it already exists, it will first be
// moved to |base_path|. In all cases, any old file at |base_path| will be
- // removed. The file will be created using the given size, id, and name.
- // Returns whether the global allocator was set.
+ // removed. If |spare_path| is non-empty and exists, that will be renamed and
+ // used as the active file. Otherwise, the file will be created using the
+ // given size, id, and name. Returns whether the global allocator was set.
static bool CreateWithActiveFile(const FilePath& base_path,
const FilePath& active_path,
+ const FilePath& spare_path,
size_t size,
uint64_t id,
StringPiece name);
@@ -421,14 +381,52 @@ class BASE_EXPORT GlobalHistogramAllocator
uint64_t id,
StringPiece name);
- // Constructs a pair of names in |dir| based on name that can be used for a
+ // Constructs a filename using a name.
+ static FilePath ConstructFilePath(const FilePath& dir, StringPiece name);
+
+ // Like above but with timestamp and pid for use in upload directories.
+ static FilePath ConstructFilePathForUploadDir(const FilePath& dir,
+ StringPiece name,
+ base::Time stamp,
+ ProcessId pid);
+
+ // Parses a filename to extract name, timestamp, and pid.
+ static bool ParseFilePath(const FilePath& path,
+ std::string* out_name,
+ Time* out_stamp,
+ ProcessId* out_pid);
+
+ // Constructs a set of names in |dir| based on name that can be used for a
// base + active persistent memory mapped location for CreateWithActiveFile().
- // |name| will be used as the basename of the file inside |dir|.
- // |out_base_path| or |out_active_path| may be null if not needed.
+ // The spare path is a file that can be pre-created and moved to be active
+ // without any startup penalty that comes from constructing the file. |name|
+ // will be used as the basename of the file inside |dir|. |out_base_path|,
+ // |out_active_path|, or |out_spare_path| may be null if not needed.
static void ConstructFilePaths(const FilePath& dir,
StringPiece name,
FilePath* out_base_path,
- FilePath* out_active_path);
+ FilePath* out_active_path,
+ FilePath* out_spare_path);
+
+ // As above but puts the base files in a different "upload" directory. This
+ // is useful when moving all completed files into a single directory for easy
+ // upload management.
+ static void ConstructFilePathsForUploadDir(const FilePath& active_dir,
+ const FilePath& upload_dir,
+ const std::string& name,
+ FilePath* out_upload_path,
+ FilePath* out_active_path,
+ FilePath* out_spare_path);
+
+ // Create a "spare" file that can later be made the "active" file. This
+ // should be done on a background thread if possible.
+ static bool CreateSpareFile(const FilePath& spare_path, size_t size);
+
+ // Same as above but uses standard names. |name| is the name of the allocator
+ // and is also used to create the correct filename.
+ static bool CreateSpareFileInDir(const FilePath& dir_path,
+ size_t size,
+ StringPiece name);
#endif
// Create a global allocator using a block of shared memory accessed
@@ -489,6 +487,9 @@ class BASE_EXPORT GlobalHistogramAllocator
// nothing new has been added.
void ImportHistogramsToStatisticsRecorder();
+ // Builds a FilePath for a metrics file.
+ static FilePath MakeMetricsFilePath(const FilePath& dir, StringPiece name);
+
// Import always continues from where it left off, making use of a single
// iterator to continue the work.
Iterator import_iterator_;
diff --git a/base/metrics/persistent_histogram_allocator_unittest.cc b/base/metrics/persistent_histogram_allocator_unittest.cc
index df250a37b0..7e07386d1b 100644
--- a/base/metrics/persistent_histogram_allocator_unittest.cc
+++ b/base/metrics/persistent_histogram_allocator_unittest.cc
@@ -4,6 +4,8 @@
#include "base/metrics/persistent_histogram_allocator.h"
+#include "base/files/file.h"
+#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
@@ -32,7 +34,6 @@ class PersistentHistogramAllocatorTest : public testing::Test {
GlobalHistogramAllocator::ReleaseForTesting();
memset(allocator_memory_.get(), 0, kAllocatorMemorySize);
- GlobalHistogramAllocator::GetCreateHistogramResultHistogram();
GlobalHistogramAllocator::CreateWithPersistentMemory(
allocator_memory_.get(), kAllocatorMemorySize, 0, 0,
"PersistentHistogramAllocatorTest");
@@ -52,7 +53,7 @@ class PersistentHistogramAllocatorTest : public testing::Test {
DISALLOW_COPY_AND_ASSIGN(PersistentHistogramAllocatorTest);
};
-TEST_F(PersistentHistogramAllocatorTest, CreateAndIterateTest) {
+TEST_F(PersistentHistogramAllocatorTest, CreateAndIterate) {
PersistentMemoryAllocator::MemoryInfo meminfo0;
allocator_->GetMemoryInfo(&meminfo0);
@@ -102,8 +103,9 @@ TEST_F(PersistentHistogramAllocatorTest, CreateAndIterateTest) {
// Create a second allocator and have it access the memory of the first.
std::unique_ptr<HistogramBase> recovered;
- PersistentHistogramAllocator recovery(MakeUnique<PersistentMemoryAllocator>(
- allocator_memory_.get(), kAllocatorMemorySize, 0, 0, "", false));
+ PersistentHistogramAllocator recovery(
+ std::make_unique<PersistentMemoryAllocator>(
+ allocator_memory_.get(), kAllocatorMemorySize, 0, 0, "", false));
PersistentHistogramAllocator::Iterator histogram_iter(&recovery);
recovered = histogram_iter.GetNext();
@@ -126,7 +128,35 @@ TEST_F(PersistentHistogramAllocatorTest, CreateAndIterateTest) {
EXPECT_FALSE(recovered);
}
-TEST_F(PersistentHistogramAllocatorTest, CreateWithFileTest) {
+TEST_F(PersistentHistogramAllocatorTest, ConstructPaths) {
+ const FilePath dir_path(FILE_PATH_LITERAL("foo/"));
+ const std::string dir_string =
+ dir_path.NormalizePathSeparators().AsUTF8Unsafe();
+
+ FilePath path = GlobalHistogramAllocator::ConstructFilePath(dir_path, "bar");
+ EXPECT_EQ(dir_string + "bar.pma", path.AsUTF8Unsafe());
+
+ std::string name;
+ Time stamp;
+ ProcessId pid;
+ EXPECT_FALSE(
+ GlobalHistogramAllocator::ParseFilePath(path, &name, nullptr, nullptr));
+ EXPECT_FALSE(
+ GlobalHistogramAllocator::ParseFilePath(path, nullptr, &stamp, nullptr));
+ EXPECT_FALSE(
+ GlobalHistogramAllocator::ParseFilePath(path, nullptr, nullptr, &pid));
+
+ path = GlobalHistogramAllocator::ConstructFilePathForUploadDir(
+ dir_path, "bar", Time::FromTimeT(12345), 6789);
+ EXPECT_EQ(dir_string + "bar-3039-1A85.pma", path.AsUTF8Unsafe());
+ ASSERT_TRUE(
+ GlobalHistogramAllocator::ParseFilePath(path, &name, &stamp, &pid));
+ EXPECT_EQ(name, "bar");
+ EXPECT_EQ(Time::FromTimeT(12345), stamp);
+ EXPECT_EQ(static_cast<ProcessId>(6789), pid);
+}
+
+TEST_F(PersistentHistogramAllocatorTest, CreateWithFile) {
const char temp_name[] = "CreateWithFileTest";
ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
@@ -155,7 +185,29 @@ TEST_F(PersistentHistogramAllocatorTest, CreateWithFileTest) {
GlobalHistogramAllocator::ReleaseForTesting();
}
-TEST_F(PersistentHistogramAllocatorTest, StatisticsRecorderMergeTest) {
+TEST_F(PersistentHistogramAllocatorTest, CreateSpareFile) {
+ const char temp_name[] = "CreateSpareFileTest.pma";
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ FilePath temp_file = temp_dir.GetPath().AppendASCII(temp_name);
+ const size_t temp_size = 64 << 10; // 64 KiB
+
+ ASSERT_TRUE(GlobalHistogramAllocator::CreateSpareFile(temp_file, temp_size));
+
+ File file(temp_file, File::FLAG_OPEN | File::FLAG_READ);
+ ASSERT_TRUE(file.IsValid());
+ EXPECT_EQ(static_cast<int64_t>(temp_size), file.GetLength());
+
+ char buffer[256];
+ for (size_t pos = 0; pos < temp_size; pos += sizeof(buffer)) {
+ ASSERT_EQ(static_cast<int>(sizeof(buffer)),
+ file.ReadAtCurrentPos(buffer, sizeof(buffer)));
+ for (size_t i = 0; i < sizeof(buffer); ++i)
+ EXPECT_EQ(0, buffer[i]);
+ }
+}
+
+TEST_F(PersistentHistogramAllocatorTest, StatisticsRecorderMerge) {
const char LinearHistogramName[] = "SRTLinearHistogram";
const char SparseHistogramName[] = "SRTSparseHistogram";
const size_t starting_sr_count = StatisticsRecorder::GetHistogramCount();
@@ -204,9 +256,10 @@ TEST_F(PersistentHistogramAllocatorTest, StatisticsRecorderMergeTest) {
GlobalHistogramAllocator::Set(std::move(old_allocator));
// Create a "recovery" allocator using the same memory as the local one.
- PersistentHistogramAllocator recovery1(MakeUnique<PersistentMemoryAllocator>(
- const_cast<void*>(new_allocator->memory_allocator()->data()),
- new_allocator->memory_allocator()->size(), 0, 0, "", false));
+ PersistentHistogramAllocator recovery1(
+ std::make_unique<PersistentMemoryAllocator>(
+ const_cast<void*>(new_allocator->memory_allocator()->data()),
+ new_allocator->memory_allocator()->size(), 0, 0, "", false));
PersistentHistogramAllocator::Iterator histogram_iter1(&recovery1);
// Get the histograms that were created locally (and forgotten) and merge
@@ -250,9 +303,10 @@ TEST_F(PersistentHistogramAllocatorTest, StatisticsRecorderMergeTest) {
histogram2->Add(7);
// Do another merge.
- PersistentHistogramAllocator recovery2(MakeUnique<PersistentMemoryAllocator>(
- const_cast<void*>(new_allocator->memory_allocator()->data()),
- new_allocator->memory_allocator()->size(), 0, 0, "", false));
+ PersistentHistogramAllocator recovery2(
+ std::make_unique<PersistentMemoryAllocator>(
+ const_cast<void*>(new_allocator->memory_allocator()->data()),
+ new_allocator->memory_allocator()->size(), 0, 0, "", false));
PersistentHistogramAllocator::Iterator histogram_iter2(&recovery2);
while (true) {
recovered = histogram_iter2.GetNext();
@@ -281,4 +335,41 @@ TEST_F(PersistentHistogramAllocatorTest, StatisticsRecorderMergeTest) {
EXPECT_EQ(1, snapshot->GetCount(7));
}
+TEST_F(PersistentHistogramAllocatorTest, RangesDeDuplication) {
+ // This corresponds to the "ranges_ref" field of the PersistentHistogramData
+ // structure defined (privately) inside persistent_histogram_allocator.cc.
+ const int kRangesRefIndex = 5;
+
+ // Create two histograms with the same ranges.
+ HistogramBase* histogram1 =
+ Histogram::FactoryGet("TestHistogram1", 1, 1000, 10, 0);
+ HistogramBase* histogram2 =
+ Histogram::FactoryGet("TestHistogram2", 1, 1000, 10, 0);
+ const uint32_t ranges_ref = static_cast<Histogram*>(histogram1)
+ ->bucket_ranges()
+ ->persistent_reference();
+ ASSERT_NE(0U, ranges_ref);
+ EXPECT_EQ(ranges_ref, static_cast<Histogram*>(histogram2)
+ ->bucket_ranges()
+ ->persistent_reference());
+
+ // Make sure that the persistent data record is also correct. Two histograms
+ // will be fetched; other allocations are not "iterable".
+ PersistentMemoryAllocator::Iterator iter(allocator_);
+ uint32_t type;
+ uint32_t ref1 = iter.GetNext(&type);
+ uint32_t ref2 = iter.GetNext(&type);
+ EXPECT_EQ(0U, iter.GetNext(&type));
+ EXPECT_NE(0U, ref1);
+ EXPECT_NE(0U, ref2);
+ EXPECT_NE(ref1, ref2);
+
+ uint32_t* data1 =
+ allocator_->GetAsArray<uint32_t>(ref1, 0, kRangesRefIndex + 1);
+ uint32_t* data2 =
+ allocator_->GetAsArray<uint32_t>(ref2, 0, kRangesRefIndex + 1);
+ EXPECT_EQ(ranges_ref, data1[kRangesRefIndex]);
+ EXPECT_EQ(ranges_ref, data2[kRangesRefIndex]);
+}
+
} // namespace base
diff --git a/base/metrics/persistent_histogram_storage.cc b/base/metrics/persistent_histogram_storage.cc
new file mode 100644
index 0000000000..e2a56d7df6
--- /dev/null
+++ b/base/metrics/persistent_histogram_storage.cc
@@ -0,0 +1,103 @@
+// 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.
+
+#include "base/metrics/persistent_histogram_storage.h"
+
+#include "base/files/file_util.h"
+#include "base/files/important_file_writer.h"
+#include "base/logging.h"
+#include "base/metrics/persistent_histogram_allocator.h"
+#include "base/metrics/persistent_memory_allocator.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+
+namespace {
+
+constexpr size_t kAllocSize = 1 << 20; // 1 MiB
+
+} // namespace
+
+namespace base {
+
+PersistentHistogramStorage::PersistentHistogramStorage(
+ StringPiece allocator_name,
+ StorageDirManagement storage_dir_management)
+ : storage_dir_management_(storage_dir_management) {
+ DCHECK(!allocator_name.empty());
+ DCHECK(IsStringASCII(allocator_name));
+
+ GlobalHistogramAllocator::CreateWithLocalMemory(kAllocSize,
+ 0, // No identifier.
+ allocator_name);
+ GlobalHistogramAllocator::Get()->CreateTrackingHistograms(allocator_name);
+}
+
+PersistentHistogramStorage::~PersistentHistogramStorage() {
+ PersistentHistogramAllocator* allocator = GlobalHistogramAllocator::Get();
+ allocator->UpdateTrackingHistograms();
+
+ // TODO(chengx): Investigate making early return depend on whethere there are
+ // metrics to report at this point or not.
+ if (disabled_)
+ return;
+
+ // Stop if the storage base directory has not been properly set.
+ if (storage_base_dir_.empty()) {
+ LOG(ERROR)
+ << "Could not write \"" << allocator->Name()
+ << "\" persistent histograms to file as the storage base directory "
+ "is not properly set.";
+ return;
+ }
+
+ FilePath storage_dir = storage_base_dir_.AppendASCII(allocator->Name());
+
+ switch (storage_dir_management_) {
+ case StorageDirManagement::kCreate:
+ if (!CreateDirectory(storage_dir)) {
+ LOG(ERROR)
+ << "Could not write \"" << allocator->Name()
+ << "\" persistent histograms to file as the storage directory "
+ "cannot be created.";
+ return;
+ }
+ break;
+ case StorageDirManagement::kUseExisting:
+ if (!DirectoryExists(storage_dir)) {
+ // When the consumer of this class decides to use an existing storage
+ // directory, it should ensure the directory's existence if it's
+ // essential.
+ LOG(ERROR)
+ << "Could not write \"" << allocator->Name()
+ << "\" persistent histograms to file as the storage directory "
+ "does not exist.";
+ return;
+ }
+ break;
+ }
+
+ // Save data using the current time as the filename. The actual filename
+ // doesn't matter (so long as it ends with the correct extension) but this
+ // works as well as anything.
+ Time::Exploded exploded;
+ Time::Now().LocalExplode(&exploded);
+ const FilePath file_path =
+ storage_dir
+ .AppendASCII(StringPrintf("%04d%02d%02d%02d%02d%02d", exploded.year,
+ exploded.month, exploded.day_of_month,
+ exploded.hour, exploded.minute,
+ exploded.second))
+ .AddExtension(PersistentMemoryAllocator::kFileExtension);
+
+ StringPiece contents(static_cast<const char*>(allocator->data()),
+ allocator->used());
+ if (!ImportantFileWriter::WriteFileAtomically(file_path, contents)) {
+ LOG(ERROR) << "Persistent histograms fail to write to file: "
+ << file_path.value();
+ }
+}
+
+} // namespace base
diff --git a/base/metrics/persistent_histogram_storage.h b/base/metrics/persistent_histogram_storage.h
new file mode 100644
index 0000000000..397236dd75
--- /dev/null
+++ b/base/metrics/persistent_histogram_storage.h
@@ -0,0 +1,68 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_METRICS_PERSISTENT_HISTOGRAM_STORAGE_H_
+#define BASE_METRICS_PERSISTENT_HISTOGRAM_STORAGE_H_
+
+#include "base/base_export.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+
+namespace base {
+
+// This class creates a fixed sized persistent memory to allow histograms to be
+// stored in it. When a PersistentHistogramStorage is destructed, histograms
+// recorded during its lifetime are persisted in the directory
+// |storage_base_dir_|/|allocator_name| (see the ctor for allocator_name).
+// Histograms are not persisted if the storage directory does not exist on
+// destruction. PersistentHistogramStorage should be instantiated as early as
+// possible in the process lifetime and should never be instantiated again.
+// Persisted histograms will eventually be reported by Chrome.
+class BASE_EXPORT PersistentHistogramStorage {
+ public:
+ enum class StorageDirManagement { kCreate, kUseExisting };
+
+ // Creates a process-wide storage location for histograms that will be written
+ // to a file within a directory provided by |set_storage_base_dir()| on
+ // destruction.
+ // The |allocator_name| is used both as an internal name for the allocator,
+ // well as the leaf directory name for the file to which the histograms are
+ // persisted. The string must be ASCII.
+ // |storage_dir_management| specifies if this instance reuses an existing
+ // storage directory, or is responsible for creating one.
+ PersistentHistogramStorage(StringPiece allocator_name,
+ StorageDirManagement storage_dir_management);
+
+ ~PersistentHistogramStorage();
+
+ // The storage directory isn't always known during initial construction so
+ // it's set separately. The last one wins if there are multiple calls to this
+ // method.
+ void set_storage_base_dir(const FilePath& storage_base_dir) {
+ storage_base_dir_ = storage_base_dir;
+ }
+
+ // Disables histogram storage.
+ void Disable() { disabled_ = true; }
+
+ private:
+ // Metrics files are written into directory
+ // |storage_base_dir_|/|allocator_name| (see the ctor for allocator_name).
+ FilePath storage_base_dir_;
+
+ // The setting of the storage directory management.
+ const StorageDirManagement storage_dir_management_;
+
+ // A flag indicating if histogram storage is disabled. It starts with false,
+ // but can be set to true by the caller who decides to throw away its
+ // histogram data.
+ bool disabled_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(PersistentHistogramStorage);
+};
+
+} // namespace base
+
+#endif // BASE_METRICS_PERSISTENT_HISTOGRAM_STORAGE_H_
diff --git a/base/metrics/persistent_histogram_storage_unittest.cc b/base/metrics/persistent_histogram_storage_unittest.cc
new file mode 100644
index 0000000000..0b9b1ce6b0
--- /dev/null
+++ b/base/metrics/persistent_histogram_storage_unittest.cc
@@ -0,0 +1,78 @@
+// 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.
+
+#include "base/metrics/persistent_histogram_storage.h"
+
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+// Name of the allocator for storing histograms.
+constexpr char kTestHistogramAllocatorName[] = "TestMetrics";
+
+} // namespace
+
+class PersistentHistogramStorageTest : public testing::Test {
+ protected:
+ PersistentHistogramStorageTest() = default;
+ ~PersistentHistogramStorageTest() override = default;
+
+ // Creates a unique temporary directory, and sets the test storage directory.
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ test_storage_dir_ =
+ temp_dir_path().AppendASCII(kTestHistogramAllocatorName);
+ }
+
+ // Gets the path to the temporary directory.
+ const FilePath& temp_dir_path() { return temp_dir_.GetPath(); }
+
+ const FilePath& test_storage_dir() { return test_storage_dir_; }
+
+ private:
+ // A temporary directory where all file IO operations take place.
+ ScopedTempDir temp_dir_;
+
+ // The directory into which metrics files are written.
+ FilePath test_storage_dir_;
+
+ DISALLOW_COPY_AND_ASSIGN(PersistentHistogramStorageTest);
+};
+
+// TODO(chengx): Re-enable the test on OS_IOS after issue 836789 is fixed.
+// PersistentHistogramStorage is only used on OS_WIN now, so disabling this
+// test on OS_IOS is fine.
+#if !defined(OS_NACL) && !defined(OS_IOS)
+TEST_F(PersistentHistogramStorageTest, HistogramWriteTest) {
+ auto persistent_histogram_storage =
+ std::make_unique<PersistentHistogramStorage>(
+ kTestHistogramAllocatorName,
+ PersistentHistogramStorage::StorageDirManagement::kCreate);
+
+ persistent_histogram_storage->set_storage_base_dir(temp_dir_path());
+
+ // Log some random data.
+ UMA_HISTOGRAM_BOOLEAN("Some.Test.Metric", true);
+
+ // Deleting the object causes the data to be written to the disk.
+ persistent_histogram_storage.reset();
+
+ // The storage directory and the histogram file are created during the
+ // destruction of the PersistentHistogramStorage instance.
+ EXPECT_TRUE(DirectoryExists(test_storage_dir()));
+ EXPECT_FALSE(IsDirectoryEmpty(test_storage_dir()));
+}
+#endif // !defined(OS_NACL) && !defined(OS_IOS)
+
+} // namespace base
diff --git a/base/metrics/persistent_memory_allocator.cc b/base/metrics/persistent_memory_allocator.cc
index d381d8784d..9b18a000db 100644
--- a/base/metrics/persistent_memory_allocator.cc
+++ b/base/metrics/persistent_memory_allocator.cc
@@ -8,17 +8,21 @@
#include <algorithm>
#if defined(OS_WIN)
+#include <windows.h>
#include "winbase.h"
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
#include <sys/mman.h>
#endif
#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "base/memory/shared_memory.h"
-#include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_functions.h"
#include "base/metrics/sparse_histogram.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/sys_info.h"
#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
namespace {
@@ -162,6 +166,11 @@ void PersistentMemoryAllocator::Iterator::Reset() {
}
void PersistentMemoryAllocator::Iterator::Reset(Reference starting_after) {
+ if (starting_after == 0) {
+ Reset();
+ return;
+ }
+
last_record_.store(starting_after, std::memory_order_relaxed);
record_count_.store(0, std::memory_order_relaxed);
@@ -311,6 +320,11 @@ PersistentMemoryAllocator::PersistentMemoryAllocator(Memory memory,
mem_type_(memory.type),
mem_size_(static_cast<uint32_t>(size)),
mem_page_(static_cast<uint32_t>((page_size ? page_size : size))),
+#if defined(OS_NACL)
+ vm_page_size_(4096U), // SysInfo is not built for NACL.
+#else
+ vm_page_size_(SysInfo::VMAllocationGranularity()),
+#endif
readonly_(readonly),
corrupt_(0),
allocs_histogram_(nullptr),
@@ -336,9 +350,9 @@ PersistentMemoryAllocator::PersistentMemoryAllocator(Memory memory,
// These atomics operate inter-process and so must be lock-free. The local
// casts are to make sure it can be evaluated at compile time to a constant.
- CHECK(((SharedMetadata*)0)->freeptr.is_lock_free());
- CHECK(((SharedMetadata*)0)->flags.is_lock_free());
- CHECK(((BlockHeader*)0)->next.is_lock_free());
+ CHECK(((SharedMetadata*)nullptr)->freeptr.is_lock_free());
+ CHECK(((SharedMetadata*)nullptr)->flags.is_lock_free());
+ CHECK(((BlockHeader*)nullptr)->next.is_lock_free());
CHECK(corrupt_.is_lock_free());
if (shared_meta()->cookie != kGlobalCookie) {
@@ -718,6 +732,28 @@ PersistentMemoryAllocator::Reference PersistentMemoryAllocator::AllocateImpl(
return kReferenceNull;
}
+ // Make sure the memory exists by writing to the first byte of every memory
+ // page it touches beyond the one containing the block header itself.
+ // As the underlying storage is often memory mapped from disk or shared
+ // space, sometimes things go wrong and those address don't actually exist
+ // leading to a SIGBUS (or Windows equivalent) at some arbitrary location
+ // in the code. This should concentrate all those failures into this
+ // location for easy tracking and, eventually, proper handling.
+ volatile char* mem_end = reinterpret_cast<volatile char*>(block) + size;
+ volatile char* mem_begin = reinterpret_cast<volatile char*>(
+ (reinterpret_cast<uintptr_t>(block) + sizeof(BlockHeader) +
+ (vm_page_size_ - 1)) &
+ ~static_cast<uintptr_t>(vm_page_size_ - 1));
+ for (volatile char* memory = mem_begin; memory < mem_end;
+ memory += vm_page_size_) {
+ // It's required that a memory segment start as all zeros and thus the
+ // newly allocated block is all zeros at this point. Thus, writing a
+ // zero to it allows testing that the memory exists without actually
+ // changing its contents. The compiler doesn't know about the requirement
+ // and so cannot optimize-away these writes.
+ *memory = 0;
+ }
+
// Load information into the block header. There is no "release" of the
// data here because this memory can, currently, be seen only by the thread
// performing the allocation. When it comes time to share this, the thread
@@ -931,17 +967,17 @@ LocalPersistentMemoryAllocator::AllocateLocalMemory(size_t size) {
::VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (address)
return Memory(address, MEM_VIRTUAL);
- UMA_HISTOGRAM_SPARSE_SLOWLY("UMA.LocalPersistentMemoryAllocator.Failures.Win",
- ::GetLastError());
-#elif defined(OS_POSIX)
+ UmaHistogramSparse("UMA.LocalPersistentMemoryAllocator.Failures.Win",
+ ::GetLastError());
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
// MAP_ANON is deprecated on Linux but MAP_ANONYMOUS is not universal on Mac.
// MAP_SHARED is not available on Linux <2.4 but required on Mac.
address = ::mmap(nullptr, size, PROT_READ | PROT_WRITE,
MAP_ANON | MAP_SHARED, -1, 0);
if (address != MAP_FAILED)
return Memory(address, MEM_VIRTUAL);
- UMA_HISTOGRAM_SPARSE_SLOWLY(
- "UMA.LocalPersistentMemoryAllocator.Failures.Posix", errno);
+ UmaHistogramSparse("UMA.LocalPersistentMemoryAllocator.Failures.Posix",
+ errno);
#else
#error This architecture is not (yet) supported.
#endif
@@ -969,7 +1005,7 @@ void LocalPersistentMemoryAllocator::DeallocateLocalMemory(void* memory,
#if defined(OS_WIN)
BOOL success = ::VirtualFree(memory, 0, MEM_DECOMMIT);
DCHECK(success);
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
int result = ::munmap(memory, size);
DCHECK_EQ(0, result);
#else
@@ -994,7 +1030,7 @@ SharedPersistentMemoryAllocator::SharedPersistentMemoryAllocator(
read_only),
shared_memory_(std::move(memory)) {}
-SharedPersistentMemoryAllocator::~SharedPersistentMemoryAllocator() {}
+SharedPersistentMemoryAllocator::~SharedPersistentMemoryAllocator() = default;
// static
bool SharedPersistentMemoryAllocator::IsSharedMemoryAcceptable(
@@ -1019,14 +1055,9 @@ FilePersistentMemoryAllocator::FilePersistentMemoryAllocator(
id,
name,
read_only),
- mapped_file_(std::move(file)) {
- // Ensure the disk-copy of the data reflects the fully-initialized memory as
- // there is no guarantee as to what order the pages might be auto-flushed by
- // the OS in the future.
- Flush(true);
-}
+ mapped_file_(std::move(file)) {}
-FilePersistentMemoryAllocator::~FilePersistentMemoryAllocator() {}
+FilePersistentMemoryAllocator::~FilePersistentMemoryAllocator() = default;
// static
bool FilePersistentMemoryAllocator::IsFileAcceptable(
@@ -1037,12 +1068,13 @@ bool FilePersistentMemoryAllocator::IsFileAcceptable(
void FilePersistentMemoryAllocator::FlushPartial(size_t length, bool sync) {
if (sync)
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
if (IsReadonly())
return;
#if defined(OS_WIN)
- // Windows doesn't support a synchronous flush.
+ // Windows doesn't support asynchronous flush.
+ AssertBlockingAllowed();
BOOL success = ::FlushViewOfFile(data(), length);
DPCHECK(success);
#elif defined(OS_MACOSX)
@@ -1051,7 +1083,7 @@ void FilePersistentMemoryAllocator::FlushPartial(size_t length, bool sync) {
int result =
::msync(const_cast<void*>(data()), length, sync ? MS_SYNC : MS_ASYNC);
DCHECK_NE(EINVAL, result);
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
// On POSIX, "invalidate" forces _other_ processes to recognize what has
// been written to disk and so is applicable to "flush".
int result = ::msync(const_cast<void*>(data()), length,
@@ -1063,4 +1095,110 @@ void FilePersistentMemoryAllocator::FlushPartial(size_t length, bool sync) {
}
#endif // !defined(OS_NACL)
+//----- DelayedPersistentAllocation --------------------------------------------
+
+// Forwarding constructors.
+DelayedPersistentAllocation::DelayedPersistentAllocation(
+ PersistentMemoryAllocator* allocator,
+ subtle::Atomic32* ref,
+ uint32_t type,
+ size_t size,
+ bool make_iterable)
+ : DelayedPersistentAllocation(
+ allocator,
+ reinterpret_cast<std::atomic<Reference>*>(ref),
+ type,
+ size,
+ 0,
+ make_iterable) {}
+
+DelayedPersistentAllocation::DelayedPersistentAllocation(
+ PersistentMemoryAllocator* allocator,
+ subtle::Atomic32* ref,
+ uint32_t type,
+ size_t size,
+ size_t offset,
+ bool make_iterable)
+ : DelayedPersistentAllocation(
+ allocator,
+ reinterpret_cast<std::atomic<Reference>*>(ref),
+ type,
+ size,
+ offset,
+ make_iterable) {}
+
+DelayedPersistentAllocation::DelayedPersistentAllocation(
+ PersistentMemoryAllocator* allocator,
+ std::atomic<Reference>* ref,
+ uint32_t type,
+ size_t size,
+ bool make_iterable)
+ : DelayedPersistentAllocation(allocator,
+ ref,
+ type,
+ size,
+ 0,
+ make_iterable) {}
+
+// Real constructor.
+DelayedPersistentAllocation::DelayedPersistentAllocation(
+ PersistentMemoryAllocator* allocator,
+ std::atomic<Reference>* ref,
+ uint32_t type,
+ size_t size,
+ size_t offset,
+ bool make_iterable)
+ : allocator_(allocator),
+ type_(type),
+ size_(checked_cast<uint32_t>(size)),
+ offset_(checked_cast<uint32_t>(offset)),
+ make_iterable_(make_iterable),
+ reference_(ref) {
+ DCHECK(allocator_);
+ DCHECK_NE(0U, type_);
+ DCHECK_LT(0U, size_);
+ DCHECK(reference_);
+}
+
+DelayedPersistentAllocation::~DelayedPersistentAllocation() = default;
+
+void* DelayedPersistentAllocation::Get() const {
+ // Relaxed operations are acceptable here because it's not protecting the
+ // contents of the allocation in any way.
+ Reference ref = reference_->load(std::memory_order_acquire);
+ if (!ref) {
+ ref = allocator_->Allocate(size_, type_);
+ if (!ref)
+ return nullptr;
+
+ // Store the new reference in its proper location using compare-and-swap.
+ // Use a "strong" exchange to ensure no false-negatives since the operation
+ // cannot be retried.
+ Reference existing = 0; // Must be mutable; receives actual value.
+ if (reference_->compare_exchange_strong(existing, ref,
+ std::memory_order_release,
+ std::memory_order_relaxed)) {
+ if (make_iterable_)
+ allocator_->MakeIterable(ref);
+ } else {
+ // Failure indicates that something else has raced ahead, performed the
+ // allocation, and stored its reference. Purge the allocation that was
+ // just done and use the other one instead.
+ DCHECK_EQ(type_, allocator_->GetType(existing));
+ DCHECK_LE(size_, allocator_->GetAllocSize(existing));
+ allocator_->ChangeType(ref, 0, type_, /*clear=*/false);
+ ref = existing;
+ }
+ }
+
+ char* mem = allocator_->GetAsArray<char>(ref, type_, size_);
+ if (!mem) {
+ // This should never happen but be tolerant if it does as corruption from
+ // the outside is something to guard against.
+ NOTREACHED();
+ return nullptr;
+ }
+ return mem + offset_;
+}
+
} // namespace base
diff --git a/base/metrics/persistent_memory_allocator.h b/base/metrics/persistent_memory_allocator.h
index 94a7744bfb..978a362cd9 100644
--- a/base/metrics/persistent_memory_allocator.h
+++ b/base/metrics/persistent_memory_allocator.h
@@ -328,7 +328,8 @@ class BASE_EXPORT PersistentMemoryAllocator {
// The |sync| parameter indicates if this call should block until the flush
// is complete but is only advisory and may or may not have an effect
// depending on the capabilities of the OS. Synchronous flushes are allowed
- // only from theads that are allowed to do I/O.
+ // only from theads that are allowed to do I/O but since |sync| is only
+ // advisory, all flushes should be done on IO-capable threads.
void Flush(bool sync);
// Direct access to underlying memory segment. If the segment is shared
@@ -494,7 +495,8 @@ class BASE_EXPORT PersistentMemoryAllocator {
// Reserve space in the memory segment of the desired |size| and |type_id|.
// A return value of zero indicates the allocation failed, otherwise the
// returned reference can be used by any process to get a real pointer via
- // the GetAsObject() or GetAsArray calls.
+ // the GetAsObject() or GetAsArray calls. The actual allocated size may be
+ // larger and will always be a multiple of 8 bytes (64 bits).
Reference Allocate(size_t size, uint32_t type_id);
// Allocate and construct an object in persistent memory. The type must have
@@ -512,7 +514,7 @@ class BASE_EXPORT PersistentMemoryAllocator {
const_cast<void*>(GetBlockData(ref, T::kPersistentTypeId, size));
if (!mem)
return nullptr;
- DCHECK_EQ(0U, reinterpret_cast<uintptr_t>(mem) & (ALIGNOF(T) - 1));
+ DCHECK_EQ(0U, reinterpret_cast<uintptr_t>(mem) & (alignof(T) - 1));
return new (mem) T();
}
template <typename T>
@@ -540,7 +542,7 @@ class BASE_EXPORT PersistentMemoryAllocator {
return nullptr;
// Ensure the allocator's internal alignment is sufficient for this object.
// This protects against coding errors in the allocator.
- DCHECK_EQ(0U, reinterpret_cast<uintptr_t>(mem) & (ALIGNOF(T) - 1));
+ DCHECK_EQ(0U, reinterpret_cast<uintptr_t>(mem) & (alignof(T) - 1));
// Change the type, clearing the memory if so desired. The new type is
// "transitioning" so that there is no race condition with the construction
// of the object should another thread be simultaneously iterating over
@@ -670,6 +672,7 @@ class BASE_EXPORT PersistentMemoryAllocator {
// Record an error in the internal histogram.
void RecordError(int error) const;
+ const size_t vm_page_size_; // The page size used by the OS.
const bool readonly_; // Indicates access to read-only memory.
mutable std::atomic<bool> corrupt_; // Local version of "corrupted" flag.
@@ -767,6 +770,103 @@ class BASE_EXPORT FilePersistentMemoryAllocator
};
#endif // !defined(OS_NACL)
+// An allocation that is defined but not executed until required at a later
+// time. This allows for potential users of an allocation to be decoupled
+// from the logic that defines it. In addition, there can be multiple users
+// of the same allocation or any region thereof that are guaranteed to always
+// use the same space. It's okay to copy/move these objects.
+//
+// This is a top-level class instead of an inner class of the PMA so that it
+// can be forward-declared in other header files without the need to include
+// the full contents of this file.
+class BASE_EXPORT DelayedPersistentAllocation {
+ public:
+ using Reference = PersistentMemoryAllocator::Reference;
+
+ // Creates a delayed allocation using the specified |allocator|. When
+ // needed, the memory will be allocated using the specified |type| and
+ // |size|. If |offset| is given, the returned pointer will be at that
+ // offset into the segment; this allows combining allocations into a
+ // single persistent segment to reduce overhead and means an "all or
+ // nothing" request. Note that |size| is always the total memory size
+ // and |offset| is just indicating the start of a block within it. If
+ // |make_iterable| was true, the allocation will made iterable when it
+ // is created; already existing allocations are not changed.
+ //
+ // Once allocated, a reference to the segment will be stored at |ref|.
+ // This shared location must be initialized to zero (0); it is checked
+ // with every Get() request to see if the allocation has already been
+ // done. If reading |ref| outside of this object, be sure to do an
+ // "acquire" load. Don't write to it -- leave that to this object.
+ //
+ // For convenience, methods taking both Atomic32 and std::atomic<Reference>
+ // are defined.
+ DelayedPersistentAllocation(PersistentMemoryAllocator* allocator,
+ subtle::Atomic32* ref,
+ uint32_t type,
+ size_t size,
+ bool make_iterable);
+ DelayedPersistentAllocation(PersistentMemoryAllocator* allocator,
+ subtle::Atomic32* ref,
+ uint32_t type,
+ size_t size,
+ size_t offset,
+ bool make_iterable);
+ DelayedPersistentAllocation(PersistentMemoryAllocator* allocator,
+ std::atomic<Reference>* ref,
+ uint32_t type,
+ size_t size,
+ bool make_iterable);
+ DelayedPersistentAllocation(PersistentMemoryAllocator* allocator,
+ std::atomic<Reference>* ref,
+ uint32_t type,
+ size_t size,
+ size_t offset,
+ bool make_iterable);
+ ~DelayedPersistentAllocation();
+
+ // Gets a pointer to the defined allocation. This will realize the request
+ // and update the reference provided during construction. The memory will
+ // be zeroed the first time it is returned, after that it is shared with
+ // all other Get() requests and so shows any changes made to it elsewhere.
+ //
+ // If the allocation fails for any reason, null will be returned. This works
+ // even on "const" objects because the allocation is already defined, just
+ // delayed.
+ void* Get() const;
+
+ // Gets the internal reference value. If this returns a non-zero value then
+ // a subsequent call to Get() will do nothing but convert that reference into
+ // a memory location -- useful for accessing an existing allocation without
+ // creating one unnecessarily.
+ Reference reference() const {
+ return reference_->load(std::memory_order_relaxed);
+ }
+
+ private:
+ // The underlying object that does the actual allocation of memory. Its
+ // lifetime must exceed that of all DelayedPersistentAllocation objects
+ // that use it.
+ PersistentMemoryAllocator* const allocator_;
+
+ // The desired type and size of the allocated segment plus the offset
+ // within it for the defined request.
+ const uint32_t type_;
+ const uint32_t size_;
+ const uint32_t offset_;
+
+ // Flag indicating if allocation should be made iterable when done.
+ const bool make_iterable_;
+
+ // The location at which a reference to the allocated segment is to be
+ // stored once the allocation is complete. If multiple delayed allocations
+ // share the same pointer then an allocation on one will amount to an
+ // allocation for all.
+ volatile std::atomic<Reference>* const reference_;
+
+ // No DISALLOW_COPY_AND_ASSIGN as it's okay to copy/move these objects.
+};
+
} // namespace base
#endif // BASE_METRICS_PERSISTENT_MEMORY_ALLOCATOR_H_
diff --git a/base/metrics/persistent_memory_allocator_unittest.cc b/base/metrics/persistent_memory_allocator_unittest.cc
index c3027ecc12..75e4faa769 100644
--- a/base/metrics/persistent_memory_allocator_unittest.cc
+++ b/base/metrics/persistent_memory_allocator_unittest.cc
@@ -14,11 +14,14 @@
#include "base/metrics/histogram.h"
#include "base/rand_util.h"
#include "base/strings/safe_sprintf.h"
+#include "base/strings/stringprintf.h"
#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "base/threading/simple_thread.h"
#include "testing/gmock/include/gmock/gmock.h"
+namespace base {
+
namespace {
const uint32_t TEST_MEMORY_SIZE = 1 << 20; // 1 MiB
@@ -26,9 +29,19 @@ const uint32_t TEST_MEMORY_PAGE = 64 << 10; // 64 KiB
const uint32_t TEST_ID = 12345;
const char TEST_NAME[] = "TestAllocator";
-} // namespace
+void SetFileLength(const base::FilePath& path, size_t length) {
+ {
+ File file(path, File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE);
+ DCHECK(file.IsValid());
+ ASSERT_TRUE(file.SetLength(static_cast<int64_t>(length)));
+ }
-namespace base {
+ int64_t actual_length;
+ DCHECK(GetFileSize(path, &actual_length));
+ DCHECK_EQ(length, static_cast<size_t>(actual_length));
+}
+
+} // namespace
typedef PersistentMemoryAllocator::Reference Reference;
@@ -472,6 +485,47 @@ TEST_F(PersistentMemoryAllocatorTest, IteratorParallelismTest) {
#endif
}
+TEST_F(PersistentMemoryAllocatorTest, DelayedAllocationTest) {
+ std::atomic<Reference> ref1, ref2;
+ ref1.store(0, std::memory_order_relaxed);
+ ref2.store(0, std::memory_order_relaxed);
+ DelayedPersistentAllocation da1(allocator_.get(), &ref1, 1001, 100, true);
+ DelayedPersistentAllocation da2a(allocator_.get(), &ref2, 2002, 200, 0, true);
+ DelayedPersistentAllocation da2b(allocator_.get(), &ref2, 2002, 200, 5, true);
+
+ // Nothing should yet have been allocated.
+ uint32_t type;
+ PersistentMemoryAllocator::Iterator iter(allocator_.get());
+ EXPECT_EQ(0U, iter.GetNext(&type));
+
+ // Do first delayed allocation and check that a new persistent object exists.
+ EXPECT_EQ(0U, da1.reference());
+ void* mem1 = da1.Get();
+ ASSERT_TRUE(mem1);
+ EXPECT_NE(0U, da1.reference());
+ EXPECT_EQ(allocator_->GetAsReference(mem1, 1001),
+ ref1.load(std::memory_order_relaxed));
+ EXPECT_NE(0U, iter.GetNext(&type));
+ EXPECT_EQ(1001U, type);
+ EXPECT_EQ(0U, iter.GetNext(&type));
+
+ // Do second delayed allocation and check.
+ void* mem2a = da2a.Get();
+ ASSERT_TRUE(mem2a);
+ EXPECT_EQ(allocator_->GetAsReference(mem2a, 2002),
+ ref2.load(std::memory_order_relaxed));
+ EXPECT_NE(0U, iter.GetNext(&type));
+ EXPECT_EQ(2002U, type);
+ EXPECT_EQ(0U, iter.GetNext(&type));
+
+ // Third allocation should just return offset into second allocation.
+ void* mem2b = da2b.Get();
+ ASSERT_TRUE(mem2b);
+ EXPECT_EQ(0U, iter.GetNext(&type));
+ EXPECT_EQ(reinterpret_cast<uintptr_t>(mem2a) + 5,
+ reinterpret_cast<uintptr_t>(mem2b));
+}
+
// This test doesn't verify anything other than it doesn't crash. Its goal
// is to find coding errors that aren't otherwise tested for, much like a
// "fuzzer" would.
@@ -579,10 +633,10 @@ TEST(SharedPersistentMemoryAllocatorTest, CreationTest) {
EXPECT_FALSE(local.IsFull());
EXPECT_FALSE(local.IsCorrupt());
- ASSERT_TRUE(local.shared_memory()->ShareToProcess(GetCurrentProcessHandle(),
- &shared_handle_1));
- ASSERT_TRUE(local.shared_memory()->ShareToProcess(GetCurrentProcessHandle(),
- &shared_handle_2));
+ shared_handle_1 = local.shared_memory()->handle().Duplicate();
+ ASSERT_TRUE(shared_handle_1.IsValid());
+ shared_handle_2 = local.shared_memory()->handle().Duplicate();
+ ASSERT_TRUE(shared_handle_2.IsValid());
}
// Read-only test.
@@ -873,6 +927,75 @@ TEST(FilePersistentMemoryAllocatorTest, AcceptableTest) {
}
}
}
+
+TEST_F(PersistentMemoryAllocatorTest, TruncateTest) {
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ FilePath file_path = temp_dir.GetPath().AppendASCII("truncate_test");
+
+ // Start with a small but valid file of persistent data. Keep the "used"
+ // amount for both allocations.
+ Reference a1_ref;
+ Reference a2_ref;
+ size_t a1_used;
+ size_t a2_used;
+ ASSERT_FALSE(PathExists(file_path));
+ {
+ LocalPersistentMemoryAllocator allocator(TEST_MEMORY_SIZE, TEST_ID, "");
+ a1_ref = allocator.Allocate(100 << 10, 1);
+ allocator.MakeIterable(a1_ref);
+ a1_used = allocator.used();
+ a2_ref = allocator.Allocate(200 << 10, 11);
+ allocator.MakeIterable(a2_ref);
+ a2_used = allocator.used();
+
+ File writer(file_path, File::FLAG_CREATE | File::FLAG_WRITE);
+ ASSERT_TRUE(writer.IsValid());
+ writer.Write(0, static_cast<const char*>(allocator.data()),
+ allocator.size());
+ }
+ ASSERT_TRUE(PathExists(file_path));
+ EXPECT_LE(a1_used, a2_ref);
+
+ // Truncate the file to include everything and make sure it can be read, both
+ // with read-write and read-only access.
+ for (size_t file_length : {a2_used, a1_used, a1_used / 2}) {
+ SCOPED_TRACE(StringPrintf("file_length=%zu", file_length));
+ SetFileLength(file_path, file_length);
+
+ for (bool read_only : {false, true}) {
+ SCOPED_TRACE(StringPrintf("read_only=%s", read_only ? "true" : "false"));
+
+ std::unique_ptr<MemoryMappedFile> mmfile(new MemoryMappedFile());
+ mmfile->Initialize(
+ File(file_path, File::FLAG_OPEN |
+ (read_only ? File::FLAG_READ
+ : File::FLAG_READ | File::FLAG_WRITE)),
+ read_only ? MemoryMappedFile::READ_ONLY
+ : MemoryMappedFile::READ_WRITE);
+ ASSERT_TRUE(
+ FilePersistentMemoryAllocator::IsFileAcceptable(*mmfile, read_only));
+
+ FilePersistentMemoryAllocator allocator(std::move(mmfile), 0, 0, "",
+ read_only);
+
+ PersistentMemoryAllocator::Iterator iter(&allocator);
+ uint32_t type_id;
+ EXPECT_EQ(file_length >= a1_used ? a1_ref : 0U, iter.GetNext(&type_id));
+ EXPECT_EQ(file_length >= a2_used ? a2_ref : 0U, iter.GetNext(&type_id));
+ EXPECT_EQ(0U, iter.GetNext(&type_id));
+
+ // Ensure that short files are detected as corrupt and full files are not.
+ EXPECT_EQ(file_length < a2_used, allocator.IsCorrupt());
+ }
+
+ // Ensure that file length was not adjusted.
+ int64_t actual_length;
+ ASSERT_TRUE(GetFileSize(file_path, &actual_length));
+ EXPECT_EQ(file_length, static_cast<size_t>(actual_length));
+ }
+}
+
#endif // !defined(OS_NACL)
} // namespace base
diff --git a/base/metrics/persistent_sample_map.cc b/base/metrics/persistent_sample_map.cc
index 51cc0c709d..f38b9d1f60 100644
--- a/base/metrics/persistent_sample_map.cc
+++ b/base/metrics/persistent_sample_map.cc
@@ -8,6 +8,7 @@
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/persistent_histogram_allocator.h"
+#include "base/numerics/safe_conversions.h"
#include "base/stl_util.h"
namespace base {
@@ -17,12 +18,6 @@ typedef HistogramBase::Sample Sample;
namespace {
-enum NegativeSampleReason {
- PERSISTENT_SPARSE_HAVE_LOGGED_BUT_NOT_SAMPLE,
- PERSISTENT_SPARSE_SAMPLE_LESS_THAN_LOGGED,
- MAX_NEGATIVE_SAMPLE_REASONS
-};
-
// An iterator for going through a PersistentSampleMap. The logic here is
// identical to that of SampleMapIterator but with different data structures.
// Changes here likely need to be duplicated there.
@@ -38,7 +33,7 @@ class PersistentSampleMapIterator : public SampleCountIterator {
bool Done() const override;
void Next() override;
void Get(HistogramBase::Sample* min,
- HistogramBase::Sample* max,
+ int64_t* max,
HistogramBase::Count* count) const override;
private:
@@ -55,7 +50,7 @@ PersistentSampleMapIterator::PersistentSampleMapIterator(
SkipEmptyBuckets();
}
-PersistentSampleMapIterator::~PersistentSampleMapIterator() {}
+PersistentSampleMapIterator::~PersistentSampleMapIterator() = default;
bool PersistentSampleMapIterator::Done() const {
return iter_ == end_;
@@ -68,13 +63,13 @@ void PersistentSampleMapIterator::Next() {
}
void PersistentSampleMapIterator::Get(Sample* min,
- Sample* max,
+ int64_t* max,
Count* count) const {
DCHECK(!Done());
if (min)
*min = iter_->first;
if (max)
- *max = iter_->first + 1;
+ *max = strict_cast<int64_t>(iter_->first) + 1;
if (count)
*count = *iter_->second;
}
@@ -114,9 +109,25 @@ PersistentSampleMap::~PersistentSampleMap() {
}
void PersistentSampleMap::Accumulate(Sample value, Count count) {
+#if 0 // TODO(bcwhite) Re-enable efficient version after crbug.com/682680.
*GetOrCreateSampleCountStorage(value) += count;
- IncreaseSum(static_cast<int64_t>(count) * value);
- IncreaseRedundantCount(count);
+#else
+ Count* local_count_ptr = GetOrCreateSampleCountStorage(value);
+ if (count < 0) {
+ if (*local_count_ptr < -count)
+ RecordNegativeSample(SAMPLES_ACCUMULATE_WENT_NEGATIVE, -count);
+ else
+ RecordNegativeSample(SAMPLES_ACCUMULATE_NEGATIVE_COUNT, -count);
+ *local_count_ptr += count;
+ } else {
+ Sample old_value = *local_count_ptr;
+ Sample new_value = old_value + count;
+ *local_count_ptr = new_value;
+ if ((new_value >= 0) != (old_value >= 0))
+ RecordNegativeSample(SAMPLES_ACCUMULATE_OVERFLOW, count);
+ }
+#endif
+ IncreaseSumAndCount(strict_cast<int64_t>(count) * value, count);
}
Count PersistentSampleMap::GetCount(Sample value) const {
@@ -184,43 +195,16 @@ PersistentSampleMap::CreatePersistentRecord(
bool PersistentSampleMap::AddSubtractImpl(SampleCountIterator* iter,
Operator op) {
Sample min;
- Sample max;
+ int64_t max;
Count count;
for (; !iter->Done(); iter->Next()) {
iter->Get(&min, &max, &count);
if (count == 0)
continue;
- if (min + 1 != max)
+ if (strict_cast<int64_t>(min) + 1 != max)
return false; // SparseHistogram only supports bucket with size 1.
-
-#if 0 // TODO(bcwhite) Re-enable efficient version after crbug.com/682680.
*GetOrCreateSampleCountStorage(min) +=
(op == HistogramSamples::ADD) ? count : -count;
-#else
- if (op == HistogramSamples::ADD) {
- *GetOrCreateSampleCountStorage(min) += count;
- } else {
- // Subtract is used only for determining deltas when reporting which
- // means that it's in the "logged" iterator. It should have an active
- // sample record and thus there is no need to try to create one.
- NegativeSampleReason reason = MAX_NEGATIVE_SAMPLE_REASONS;
- Count* bucket = GetSampleCountStorage(min);
- if (bucket == nullptr) {
- reason = PERSISTENT_SPARSE_HAVE_LOGGED_BUT_NOT_SAMPLE;
- } else {
- if (*bucket < count) {
- reason = PERSISTENT_SPARSE_SAMPLE_LESS_THAN_LOGGED;
- *bucket = 0;
- } else {
- *bucket -= count;
- }
- }
- if (reason != MAX_NEGATIVE_SAMPLE_REASONS) {
- UMA_HISTOGRAM_ENUMERATION("UMA.NegativeSamples.Reason", reason,
- MAX_NEGATIVE_SAMPLE_REASONS);
- }
- }
-#endif
}
return true;
}
diff --git a/base/metrics/persistent_sample_map_unittest.cc b/base/metrics/persistent_sample_map_unittest.cc
index d50ab997b2..b25f58203d 100644
--- a/base/metrics/persistent_sample_map_unittest.cc
+++ b/base/metrics/persistent_sample_map_unittest.cc
@@ -16,14 +16,14 @@ namespace {
std::unique_ptr<PersistentHistogramAllocator> CreateHistogramAllocator(
size_t bytes) {
- return MakeUnique<PersistentHistogramAllocator>(
- MakeUnique<LocalPersistentMemoryAllocator>(bytes, 0, ""));
+ return std::make_unique<PersistentHistogramAllocator>(
+ std::make_unique<LocalPersistentMemoryAllocator>(bytes, 0, ""));
}
std::unique_ptr<PersistentHistogramAllocator> DuplicateHistogramAllocator(
PersistentHistogramAllocator* original) {
- return MakeUnique<PersistentHistogramAllocator>(
- MakeUnique<PersistentMemoryAllocator>(
+ return std::make_unique<PersistentHistogramAllocator>(
+ std::make_unique<PersistentMemoryAllocator>(
const_cast<void*>(original->data()), original->length(), 0,
original->Id(), original->Name(), false));
}
@@ -164,14 +164,14 @@ TEST(PersistentSampleMapIteratorTest, IterateTest) {
std::unique_ptr<SampleCountIterator> it = samples.Iterator();
HistogramBase::Sample min;
- HistogramBase::Sample max;
+ int64_t max;
HistogramBase::Count count;
it->Get(&min, &max, &count);
EXPECT_EQ(1, min);
EXPECT_EQ(2, max);
EXPECT_EQ(100, count);
- EXPECT_FALSE(it->GetBucketIndex(NULL));
+ EXPECT_FALSE(it->GetBucketIndex(nullptr));
it->Next();
it->Get(&min, &max, &count);
@@ -214,7 +214,7 @@ TEST(PersistentSampleMapIteratorTest, SkipEmptyRanges) {
EXPECT_FALSE(it->Done());
HistogramBase::Sample min;
- HistogramBase::Sample max;
+ int64_t max;
HistogramBase::Count count;
it->Get(&min, &max, &count);
@@ -245,7 +245,7 @@ TEST(PersistentSampleMapIteratorDeathTest, IterateDoneTest) {
EXPECT_TRUE(it->Done());
HistogramBase::Sample min;
- HistogramBase::Sample max;
+ int64_t max;
HistogramBase::Count count;
EXPECT_DCHECK_DEATH(it->Get(&min, &max, &count));
diff --git a/base/metrics/record_histogram_checker.h b/base/metrics/record_histogram_checker.h
new file mode 100644
index 0000000000..75bc336d18
--- /dev/null
+++ b/base/metrics/record_histogram_checker.h
@@ -0,0 +1,27 @@
+// 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_METRICS_RECORD_HISTOGRAM_CHECKER_H_
+#define BASE_METRICS_RECORD_HISTOGRAM_CHECKER_H_
+
+#include <stdint.h>
+
+#include "base/base_export.h"
+
+namespace base {
+
+// RecordHistogramChecker provides an interface for checking whether
+// the given histogram should be recorded.
+class BASE_EXPORT RecordHistogramChecker {
+ public:
+ virtual ~RecordHistogramChecker() = default;
+
+ // Returns true iff the given histogram should be recorded.
+ // This method may be called on any thread, so it should not mutate any state.
+ virtual bool ShouldRecord(uint64_t histogram_hash) const = 0;
+};
+
+} // namespace base
+
+#endif // BASE_METRICS_RECORD_HISTOGRAM_CHECKER_H_
diff --git a/base/metrics/sample_map.cc b/base/metrics/sample_map.cc
index 8abd01edfa..c6dce29321 100644
--- a/base/metrics/sample_map.cc
+++ b/base/metrics/sample_map.cc
@@ -6,6 +6,7 @@
#include "base/logging.h"
#include "base/memory/ptr_util.h"
+#include "base/numerics/safe_conversions.h"
#include "base/stl_util.h"
namespace base {
@@ -30,7 +31,7 @@ class SampleMapIterator : public SampleCountIterator {
bool Done() const override;
void Next() override;
void Get(HistogramBase::Sample* min,
- HistogramBase::Sample* max,
+ int64_t* max,
HistogramBase::Count* count) const override;
private:
@@ -46,7 +47,7 @@ SampleMapIterator::SampleMapIterator(const SampleToCountMap& sample_counts)
SkipEmptyBuckets();
}
-SampleMapIterator::~SampleMapIterator() {}
+SampleMapIterator::~SampleMapIterator() = default;
bool SampleMapIterator::Done() const {
return iter_ == end_;
@@ -58,12 +59,12 @@ void SampleMapIterator::Next() {
SkipEmptyBuckets();
}
-void SampleMapIterator::Get(Sample* min, Sample* max, Count* count) const {
+void SampleMapIterator::Get(Sample* min, int64_t* max, Count* count) const {
DCHECK(!Done());
if (min)
*min = iter_->first;
if (max)
- *max = iter_->first + 1;
+ *max = strict_cast<int64_t>(iter_->first) + 1;
if (count)
*count = iter_->second;
}
@@ -78,14 +79,15 @@ void SampleMapIterator::SkipEmptyBuckets() {
SampleMap::SampleMap() : SampleMap(0) {}
-SampleMap::SampleMap(uint64_t id) : HistogramSamples(id) {}
+SampleMap::SampleMap(uint64_t id) : HistogramSamples(id, new LocalMetadata()) {}
-SampleMap::~SampleMap() {}
+SampleMap::~SampleMap() {
+ delete static_cast<LocalMetadata*>(meta());
+}
void SampleMap::Accumulate(Sample value, Count count) {
sample_counts_[value] += count;
- IncreaseSum(static_cast<int64_t>(count) * value);
- IncreaseRedundantCount(count);
+ IncreaseSumAndCount(strict_cast<int64_t>(count) * value, count);
}
Count SampleMap::GetCount(Sample value) const {
@@ -109,11 +111,11 @@ std::unique_ptr<SampleCountIterator> SampleMap::Iterator() const {
bool SampleMap::AddSubtractImpl(SampleCountIterator* iter, Operator op) {
Sample min;
- Sample max;
+ int64_t max;
Count count;
for (; !iter->Done(); iter->Next()) {
iter->Get(&min, &max, &count);
- if (min + 1 != max)
+ if (strict_cast<int64_t>(min) + 1 != max)
return false; // SparseHistogram only supports bucket with size 1.
sample_counts_[min] += (op == HistogramSamples::ADD) ? count : -count;
diff --git a/base/metrics/sample_map_unittest.cc b/base/metrics/sample_map_unittest.cc
index 91a9dcf40f..83db56f503 100644
--- a/base/metrics/sample_map_unittest.cc
+++ b/base/metrics/sample_map_unittest.cc
@@ -81,14 +81,14 @@ TEST(SampleMapIteratorTest, IterateTest) {
std::unique_ptr<SampleCountIterator> it = samples.Iterator();
HistogramBase::Sample min;
- HistogramBase::Sample max;
+ int64_t max;
HistogramBase::Count count;
it->Get(&min, &max, &count);
EXPECT_EQ(1, min);
EXPECT_EQ(2, max);
EXPECT_EQ(100, count);
- EXPECT_FALSE(it->GetBucketIndex(NULL));
+ EXPECT_FALSE(it->GetBucketIndex(nullptr));
it->Next();
it->Get(&min, &max, &count);
@@ -125,7 +125,7 @@ TEST(SampleMapIteratorTest, SkipEmptyRanges) {
EXPECT_FALSE(it->Done());
HistogramBase::Sample min;
- HistogramBase::Sample max;
+ int64_t max;
HistogramBase::Count count;
it->Get(&min, &max, &count);
@@ -153,7 +153,7 @@ TEST(SampleMapIteratorDeathTest, IterateDoneTest) {
EXPECT_TRUE(it->Done());
HistogramBase::Sample min;
- HistogramBase::Sample max;
+ int64_t max;
HistogramBase::Count count;
EXPECT_DCHECK_DEATH(it->Get(&min, &max, &count));
diff --git a/base/metrics/sample_vector.cc b/base/metrics/sample_vector.cc
index 477b8aff8c..cf8634e836 100644
--- a/base/metrics/sample_vector.cc
+++ b/base/metrics/sample_vector.cc
@@ -4,103 +4,217 @@
#include "base/metrics/sample_vector.h"
+#include "base/lazy_instance.h"
#include "base/logging.h"
-#include "base/metrics/bucket_ranges.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/persistent_memory_allocator.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/platform_thread.h"
+
+// This SampleVector makes use of the single-sample embedded in the base
+// HistogramSamples class. If the count is non-zero then there is guaranteed
+// (within the bounds of "eventual consistency") to be no allocated external
+// storage. Once the full counts storage is allocated, the single-sample must
+// be extracted and disabled.
namespace base {
typedef HistogramBase::Count Count;
typedef HistogramBase::Sample Sample;
-SampleVector::SampleVector(const BucketRanges* bucket_ranges)
- : SampleVector(0, bucket_ranges) {}
-
-SampleVector::SampleVector(uint64_t id, const BucketRanges* bucket_ranges)
- : HistogramSamples(id),
- local_counts_(bucket_ranges->bucket_count()),
- counts_(&local_counts_[0]),
- counts_size_(local_counts_.size()),
- bucket_ranges_(bucket_ranges) {
+SampleVectorBase::SampleVectorBase(uint64_t id,
+ Metadata* meta,
+ const BucketRanges* bucket_ranges)
+ : HistogramSamples(id, meta), bucket_ranges_(bucket_ranges) {
CHECK_GE(bucket_ranges_->bucket_count(), 1u);
}
-SampleVector::SampleVector(uint64_t id,
- HistogramBase::AtomicCount* counts,
- size_t counts_size,
- Metadata* meta,
- const BucketRanges* bucket_ranges)
- : HistogramSamples(id, meta),
- counts_(counts),
- counts_size_(bucket_ranges->bucket_count()),
- bucket_ranges_(bucket_ranges) {
- CHECK_LE(bucket_ranges_->bucket_count(), counts_size_);
- CHECK_GE(bucket_ranges_->bucket_count(), 1u);
-}
+SampleVectorBase::~SampleVectorBase() = default;
+
+void SampleVectorBase::Accumulate(Sample value, Count count) {
+ const size_t bucket_index = GetBucketIndex(value);
-SampleVector::~SampleVector() {}
+ // Handle the single-sample case.
+ if (!counts()) {
+ // Try to accumulate the parameters into the single-count entry.
+ if (AccumulateSingleSample(value, count, bucket_index)) {
+ // A race condition could lead to a new single-sample being accumulated
+ // above just after another thread executed the MountCountsStorage below.
+ // Since it is mounted, it could be mounted elsewhere and have values
+ // written to it. It's not allowed to have both a single-sample and
+ // entries in the counts array so move the single-sample.
+ if (counts())
+ MoveSingleSampleToCounts();
+ return;
+ }
-void SampleVector::Accumulate(Sample value, Count count) {
- size_t bucket_index = GetBucketIndex(value);
- subtle::NoBarrier_AtomicIncrement(&counts_[bucket_index], count);
- IncreaseSum(static_cast<int64_t>(count) * value);
- IncreaseRedundantCount(count);
+ // Need real storage to store both what was in the single-sample plus the
+ // parameter information.
+ MountCountsStorageAndMoveSingleSample();
+ }
+
+ // Handle the multi-sample case.
+ Count new_value =
+ subtle::NoBarrier_AtomicIncrement(&counts()[bucket_index], count);
+ IncreaseSumAndCount(strict_cast<int64_t>(count) * value, count);
+
+ // TODO(bcwhite) Remove after crbug.com/682680.
+ Count old_value = new_value - count;
+ if ((new_value >= 0) != (old_value >= 0) && count > 0)
+ RecordNegativeSample(SAMPLES_ACCUMULATE_OVERFLOW, count);
}
-Count SampleVector::GetCount(Sample value) const {
- size_t bucket_index = GetBucketIndex(value);
- return subtle::NoBarrier_Load(&counts_[bucket_index]);
+Count SampleVectorBase::GetCount(Sample value) const {
+ return GetCountAtIndex(GetBucketIndex(value));
}
-Count SampleVector::TotalCount() const {
- Count count = 0;
- for (size_t i = 0; i < counts_size_; i++) {
- count += subtle::NoBarrier_Load(&counts_[i]);
+Count SampleVectorBase::TotalCount() const {
+ // Handle the single-sample case.
+ SingleSample sample = single_sample().Load();
+ if (sample.count != 0)
+ return sample.count;
+
+ // Handle the multi-sample case.
+ if (counts() || MountExistingCountsStorage()) {
+ Count count = 0;
+ size_t size = counts_size();
+ const HistogramBase::AtomicCount* counts_array = counts();
+ for (size_t i = 0; i < size; ++i) {
+ count += subtle::NoBarrier_Load(&counts_array[i]);
+ }
+ return count;
}
- return count;
+
+ // And the no-value case.
+ return 0;
}
-Count SampleVector::GetCountAtIndex(size_t bucket_index) const {
- DCHECK(bucket_index < counts_size_);
- return subtle::NoBarrier_Load(&counts_[bucket_index]);
+Count SampleVectorBase::GetCountAtIndex(size_t bucket_index) const {
+ DCHECK(bucket_index < counts_size());
+
+ // Handle the single-sample case.
+ SingleSample sample = single_sample().Load();
+ if (sample.count != 0)
+ return sample.bucket == bucket_index ? sample.count : 0;
+
+ // Handle the multi-sample case.
+ if (counts() || MountExistingCountsStorage())
+ return subtle::NoBarrier_Load(&counts()[bucket_index]);
+
+ // And the no-value case.
+ return 0;
}
-std::unique_ptr<SampleCountIterator> SampleVector::Iterator() const {
- return std::unique_ptr<SampleCountIterator>(
- new SampleVectorIterator(counts_, counts_size_, bucket_ranges_));
+std::unique_ptr<SampleCountIterator> SampleVectorBase::Iterator() const {
+ // Handle the single-sample case.
+ SingleSample sample = single_sample().Load();
+ if (sample.count != 0) {
+ return std::make_unique<SingleSampleIterator>(
+ bucket_ranges_->range(sample.bucket),
+ bucket_ranges_->range(sample.bucket + 1), sample.count, sample.bucket);
+ }
+
+ // Handle the multi-sample case.
+ if (counts() || MountExistingCountsStorage()) {
+ return std::make_unique<SampleVectorIterator>(counts(), counts_size(),
+ bucket_ranges_);
+ }
+
+ // And the no-value case.
+ return std::make_unique<SampleVectorIterator>(nullptr, 0, bucket_ranges_);
}
-bool SampleVector::AddSubtractImpl(SampleCountIterator* iter,
- HistogramSamples::Operator op) {
+bool SampleVectorBase::AddSubtractImpl(SampleCountIterator* iter,
+ HistogramSamples::Operator op) {
+ // Stop now if there's nothing to do.
+ if (iter->Done())
+ return true;
+
+ // Get the first value and its index.
HistogramBase::Sample min;
- HistogramBase::Sample max;
+ int64_t max;
HistogramBase::Count count;
+ iter->Get(&min, &max, &count);
+ size_t dest_index = GetBucketIndex(min);
+
+ // The destination must be a superset of the source meaning that though the
+ // incoming ranges will find an exact match, the incoming bucket-index, if
+ // it exists, may be offset from the destination bucket-index. Calculate
+ // that offset of the passed iterator; there are are no overflow checks
+ // because 2's compliment math will work it out in the end.
+ //
+ // Because GetBucketIndex() always returns the same true or false result for
+ // a given iterator object, |index_offset| is either set here and used below,
+ // or never set and never used. The compiler doesn't know this, though, which
+ // is why it's necessary to initialize it to something.
+ size_t index_offset = 0;
+ size_t iter_index;
+ if (iter->GetBucketIndex(&iter_index))
+ index_offset = dest_index - iter_index;
+ if (dest_index >= counts_size())
+ return false;
+
+ // Post-increment. Information about the current sample is not available
+ // after this point.
+ iter->Next();
+
+ // Single-value storage is possible if there is no counts storage and the
+ // retrieved entry is the only one in the iterator.
+ if (!counts()) {
+ if (iter->Done()) {
+ // Don't call AccumulateSingleSample because that updates sum and count
+ // which was already done by the caller of this method.
+ if (single_sample().Accumulate(
+ dest_index, op == HistogramSamples::ADD ? count : -count)) {
+ // Handle race-condition that mounted counts storage between above and
+ // here.
+ if (counts())
+ MoveSingleSampleToCounts();
+ return true;
+ }
+ }
+
+ // The counts storage will be needed to hold the multiple incoming values.
+ MountCountsStorageAndMoveSingleSample();
+ }
// Go through the iterator and add the counts into correct bucket.
- size_t index = 0;
- while (index < counts_size_ && !iter->Done()) {
+ while (true) {
+ // Ensure that the sample's min/max match the ranges min/max.
+ if (min != bucket_ranges_->range(dest_index) ||
+ max != bucket_ranges_->range(dest_index + 1)) {
+ NOTREACHED() << "sample=" << min << "," << max
+ << "; range=" << bucket_ranges_->range(dest_index) << ","
+ << bucket_ranges_->range(dest_index + 1);
+ return false;
+ }
+
+ // Sample's bucket matches exactly. Adjust count.
+ subtle::NoBarrier_AtomicIncrement(
+ &counts()[dest_index], op == HistogramSamples::ADD ? count : -count);
+
+ // Advance to the next iterable sample. See comments above for how
+ // everything works.
+ if (iter->Done())
+ return true;
iter->Get(&min, &max, &count);
- if (min == bucket_ranges_->range(index) &&
- max == bucket_ranges_->range(index + 1)) {
- // Sample matches this bucket!
- subtle::NoBarrier_AtomicIncrement(
- &counts_[index], op == HistogramSamples::ADD ? count : -count);
- iter->Next();
- } else if (min > bucket_ranges_->range(index)) {
- // Sample is larger than current bucket range. Try next.
- index++;
+ if (iter->GetBucketIndex(&iter_index)) {
+ // Destination bucket is a known offset from the source bucket.
+ dest_index = iter_index + index_offset;
} else {
- // Sample is smaller than current bucket range. We scan buckets from
- // smallest to largest, so the sample value must be invalid.
- return false;
+ // Destination bucket has to be determined anew each time.
+ dest_index = GetBucketIndex(min);
}
+ if (dest_index >= counts_size())
+ return false;
+ iter->Next();
}
-
- return iter->Done();
}
// Use simple binary search. This is very general, but there are better
// approaches if we knew that the buckets were linearly distributed.
-size_t SampleVector::GetBucketIndex(Sample value) const {
+size_t SampleVectorBase::GetBucketIndex(Sample value) const {
size_t bucket_count = bucket_ranges_->bucket_count();
CHECK_GE(bucket_count, 1u);
CHECK_GE(value, bucket_ranges_->range(0));
@@ -125,6 +239,128 @@ size_t SampleVector::GetBucketIndex(Sample value) const {
return mid;
}
+void SampleVectorBase::MoveSingleSampleToCounts() {
+ DCHECK(counts());
+
+ // Disable the single-sample since there is now counts storage for the data.
+ SingleSample sample = single_sample().Extract(/*disable=*/true);
+
+ // Stop here if there is no "count" as trying to find the bucket index of
+ // an invalid (including zero) "value" will crash.
+ if (sample.count == 0)
+ return;
+
+ // Move the value into storage. Sum and redundant-count already account
+ // for this entry so no need to call IncreaseSumAndCount().
+ subtle::NoBarrier_AtomicIncrement(&counts()[sample.bucket], sample.count);
+}
+
+void SampleVectorBase::MountCountsStorageAndMoveSingleSample() {
+ // There are many SampleVector objects and the lock is needed very
+ // infrequently (just when advancing from single-sample to multi-sample) so
+ // define a single, global lock that all can use. This lock only prevents
+ // concurrent entry into the code below; access and updates to |counts_|
+ // still requires atomic operations.
+ static LazyInstance<Lock>::Leaky counts_lock = LAZY_INSTANCE_INITIALIZER;
+ if (subtle::NoBarrier_Load(&counts_) == 0) {
+ AutoLock lock(counts_lock.Get());
+ if (subtle::NoBarrier_Load(&counts_) == 0) {
+ // Create the actual counts storage while the above lock is acquired.
+ HistogramBase::Count* counts = CreateCountsStorageWhileLocked();
+ DCHECK(counts);
+
+ // Point |counts_| to the newly created storage. This is done while
+ // locked to prevent possible concurrent calls to CreateCountsStorage
+ // but, between that call and here, other threads could notice the
+ // existence of the storage and race with this to set_counts(). That's
+ // okay because (a) it's atomic and (b) it always writes the same value.
+ set_counts(counts);
+ }
+ }
+
+ // Move any single-sample into the newly mounted storage.
+ MoveSingleSampleToCounts();
+}
+
+SampleVector::SampleVector(const BucketRanges* bucket_ranges)
+ : SampleVector(0, bucket_ranges) {}
+
+SampleVector::SampleVector(uint64_t id, const BucketRanges* bucket_ranges)
+ : SampleVectorBase(id, new LocalMetadata(), bucket_ranges) {}
+
+SampleVector::~SampleVector() {
+ delete static_cast<LocalMetadata*>(meta());
+}
+
+bool SampleVector::MountExistingCountsStorage() const {
+ // There is never any existing storage other than what is already in use.
+ return counts() != nullptr;
+}
+
+HistogramBase::AtomicCount* SampleVector::CreateCountsStorageWhileLocked() {
+ local_counts_.resize(counts_size());
+ return &local_counts_[0];
+}
+
+PersistentSampleVector::PersistentSampleVector(
+ uint64_t id,
+ const BucketRanges* bucket_ranges,
+ Metadata* meta,
+ const DelayedPersistentAllocation& counts)
+ : SampleVectorBase(id, meta, bucket_ranges), persistent_counts_(counts) {
+ // Only mount the full storage if the single-sample has been disabled.
+ // Otherwise, it is possible for this object instance to start using (empty)
+ // storage that was created incidentally while another instance continues to
+ // update to the single sample. This "incidental creation" can happen because
+ // the memory is a DelayedPersistentAllocation which allows multiple memory
+ // blocks within it and applies an all-or-nothing approach to the allocation.
+ // Thus, a request elsewhere for one of the _other_ blocks would make _this_
+ // block available even though nothing has explicitly requested it.
+ //
+ // Note that it's not possible for the ctor to mount existing storage and
+ // move any single-sample to it because sometimes the persistent memory is
+ // read-only. Only non-const methods (which assume that memory is read/write)
+ // can do that.
+ if (single_sample().IsDisabled()) {
+ bool success = MountExistingCountsStorage();
+ DCHECK(success);
+ }
+}
+
+PersistentSampleVector::~PersistentSampleVector() = default;
+
+bool PersistentSampleVector::MountExistingCountsStorage() const {
+ // There is no early exit if counts is not yet mounted because, given that
+ // this is a virtual function, it's more efficient to do that at the call-
+ // site. There is no danger, however, should this get called anyway (perhaps
+ // because of a race condition) because at worst the |counts_| value would
+ // be over-written (in an atomic manner) with the exact same address.
+
+ if (!persistent_counts_.reference())
+ return false; // Nothing to mount.
+
+ // Mount the counts array in position.
+ set_counts(
+ static_cast<HistogramBase::AtomicCount*>(persistent_counts_.Get()));
+
+ // The above shouldn't fail but can if the data is corrupt or incomplete.
+ return counts() != nullptr;
+}
+
+HistogramBase::AtomicCount*
+PersistentSampleVector::CreateCountsStorageWhileLocked() {
+ void* mem = persistent_counts_.Get();
+ if (!mem) {
+ // The above shouldn't fail but can if Bad Things(tm) are occurring in the
+ // persistent allocator. Crashing isn't a good option so instead just
+ // allocate something from the heap and return that. There will be no
+ // sharing or persistence but worse things are already happening.
+ return new HistogramBase::AtomicCount[counts_size()];
+ }
+
+ return static_cast<HistogramBase::AtomicCount*>(mem);
+}
+
SampleVectorIterator::SampleVectorIterator(
const std::vector<HistogramBase::AtomicCount>* counts,
const BucketRanges* bucket_ranges)
@@ -132,7 +368,7 @@ SampleVectorIterator::SampleVectorIterator(
counts_size_(counts->size()),
bucket_ranges_(bucket_ranges),
index_(0) {
- CHECK_GE(bucket_ranges_->bucket_count(), counts_size_);
+ DCHECK_GE(bucket_ranges_->bucket_count(), counts_size_);
SkipEmptyBuckets();
}
@@ -144,11 +380,11 @@ SampleVectorIterator::SampleVectorIterator(
counts_size_(counts_size),
bucket_ranges_(bucket_ranges),
index_(0) {
- CHECK_GE(bucket_ranges_->bucket_count(), counts_size_);
+ DCHECK_GE(bucket_ranges_->bucket_count(), counts_size_);
SkipEmptyBuckets();
}
-SampleVectorIterator::~SampleVectorIterator() {}
+SampleVectorIterator::~SampleVectorIterator() = default;
bool SampleVectorIterator::Done() const {
return index_ >= counts_size_;
@@ -161,20 +397,20 @@ void SampleVectorIterator::Next() {
}
void SampleVectorIterator::Get(HistogramBase::Sample* min,
- HistogramBase::Sample* max,
+ int64_t* max,
HistogramBase::Count* count) const {
DCHECK(!Done());
- if (min != NULL)
+ if (min != nullptr)
*min = bucket_ranges_->range(index_);
- if (max != NULL)
- *max = bucket_ranges_->range(index_ + 1);
- if (count != NULL)
+ if (max != nullptr)
+ *max = strict_cast<int64_t>(bucket_ranges_->range(index_ + 1));
+ if (count != nullptr)
*count = subtle::NoBarrier_Load(&counts_[index_]);
}
bool SampleVectorIterator::GetBucketIndex(size_t* index) const {
DCHECK(!Done());
- if (index != NULL)
+ if (index != nullptr)
*index = index_;
return true;
}
diff --git a/base/metrics/sample_vector.h b/base/metrics/sample_vector.h
index ee26c52101..278272da1b 100644
--- a/base/metrics/sample_vector.h
+++ b/base/metrics/sample_vector.h
@@ -14,28 +14,27 @@
#include <memory>
#include <vector>
+#include "base/atomicops.h"
#include "base/compiler_specific.h"
#include "base/gtest_prod_util.h"
#include "base/macros.h"
+#include "base/metrics/bucket_ranges.h"
#include "base/metrics/histogram_base.h"
#include "base/metrics/histogram_samples.h"
+#include "base/metrics/persistent_memory_allocator.h"
namespace base {
class BucketRanges;
-class BASE_EXPORT SampleVector : public HistogramSamples {
+class BASE_EXPORT SampleVectorBase : public HistogramSamples {
public:
- explicit SampleVector(const BucketRanges* bucket_ranges);
- SampleVector(uint64_t id, const BucketRanges* bucket_ranges);
- SampleVector(uint64_t id,
- HistogramBase::AtomicCount* counts,
- size_t counts_size,
- Metadata* meta,
- const BucketRanges* bucket_ranges);
- ~SampleVector() override;
+ SampleVectorBase(uint64_t id,
+ Metadata* meta,
+ const BucketRanges* bucket_ranges);
+ ~SampleVectorBase() override;
- // HistogramSamples implementation:
+ // HistogramSamples:
void Accumulate(HistogramBase::Sample value,
HistogramBase::Count count) override;
HistogramBase::Count GetCount(HistogramBase::Sample value) const override;
@@ -45,6 +44,9 @@ class BASE_EXPORT SampleVector : public HistogramSamples {
// Get count of a specific bucket.
HistogramBase::Count GetCountAtIndex(size_t bucket_index) const;
+ // Access the bucket ranges held externally.
+ const BucketRanges* bucket_ranges() const { return bucket_ranges_; }
+
protected:
bool AddSubtractImpl(
SampleCountIterator* iter,
@@ -52,25 +54,103 @@ class BASE_EXPORT SampleVector : public HistogramSamples {
virtual size_t GetBucketIndex(HistogramBase::Sample value) const;
+ // Moves the single-sample value to a mounted "counts" array.
+ void MoveSingleSampleToCounts();
+
+ // Mounts (creating if necessary) an array of "counts" for multi-value
+ // storage.
+ void MountCountsStorageAndMoveSingleSample();
+
+ // Mounts "counts" storage that already exists. This does not attempt to move
+ // any single-sample information to that storage as that would violate the
+ // "const" restriction that is often used to indicate read-only memory.
+ virtual bool MountExistingCountsStorage() const = 0;
+
+ // Creates "counts" storage and returns a pointer to it. Ownership of the
+ // array remains with the called method but will never change. This must be
+ // called while some sort of lock is held to prevent reentry.
+ virtual HistogramBase::Count* CreateCountsStorageWhileLocked() = 0;
+
+ HistogramBase::AtomicCount* counts() {
+ return reinterpret_cast<HistogramBase::AtomicCount*>(
+ subtle::Acquire_Load(&counts_));
+ }
+
+ const HistogramBase::AtomicCount* counts() const {
+ return reinterpret_cast<HistogramBase::AtomicCount*>(
+ subtle::Acquire_Load(&counts_));
+ }
+
+ void set_counts(const HistogramBase::AtomicCount* counts) const {
+ subtle::Release_Store(&counts_, reinterpret_cast<uintptr_t>(counts));
+ }
+
+ size_t counts_size() const { return bucket_ranges_->bucket_count(); }
+
private:
+ friend class SampleVectorTest;
FRIEND_TEST_ALL_PREFIXES(HistogramTest, CorruptSampleCounts);
FRIEND_TEST_ALL_PREFIXES(SharedHistogramTest, CorruptSampleCounts);
- // In the case where this class manages the memory, here it is.
- std::vector<HistogramBase::AtomicCount> local_counts_;
-
- // These are raw pointers rather than objects for flexibility. The actual
- // memory is either managed by local_counts_ above or by an external object
- // and passed in directly.
- HistogramBase::AtomicCount* counts_;
- size_t counts_size_;
+ // |counts_| is actually a pointer to a HistogramBase::AtomicCount array but
+ // is held as an AtomicWord for concurrency reasons. When combined with the
+ // single_sample held in the metadata, there are four possible states:
+ // 1) single_sample == zero, counts_ == null
+ // 2) single_sample != zero, counts_ == null
+ // 3) single_sample != zero, counts_ != null BUT IS EMPTY
+ // 4) single_sample == zero, counts_ != null and may have data
+ // Once |counts_| is set, it can never revert and any existing single-sample
+ // must be moved to this storage. It is mutable because changing it doesn't
+ // change the (const) data but must adapt if a non-const object causes the
+ // storage to be allocated and updated.
+ mutable subtle::AtomicWord counts_ = 0;
// Shares the same BucketRanges with Histogram object.
const BucketRanges* const bucket_ranges_;
+ DISALLOW_COPY_AND_ASSIGN(SampleVectorBase);
+};
+
+// A sample vector that uses local memory for the counts array.
+class BASE_EXPORT SampleVector : public SampleVectorBase {
+ public:
+ explicit SampleVector(const BucketRanges* bucket_ranges);
+ SampleVector(uint64_t id, const BucketRanges* bucket_ranges);
+ ~SampleVector() override;
+
+ private:
+ // SampleVectorBase:
+ bool MountExistingCountsStorage() const override;
+ HistogramBase::Count* CreateCountsStorageWhileLocked() override;
+
+ // Simple local storage for counts.
+ mutable std::vector<HistogramBase::AtomicCount> local_counts_;
+
DISALLOW_COPY_AND_ASSIGN(SampleVector);
};
+// A sample vector that uses persistent memory for the counts array.
+class BASE_EXPORT PersistentSampleVector : public SampleVectorBase {
+ public:
+ PersistentSampleVector(uint64_t id,
+ const BucketRanges* bucket_ranges,
+ Metadata* meta,
+ const DelayedPersistentAllocation& counts);
+ ~PersistentSampleVector() override;
+
+ private:
+ // SampleVectorBase:
+ bool MountExistingCountsStorage() const override;
+ HistogramBase::Count* CreateCountsStorageWhileLocked() override;
+
+ // Persistent storage for counts.
+ DelayedPersistentAllocation persistent_counts_;
+
+ DISALLOW_COPY_AND_ASSIGN(PersistentSampleVector);
+};
+
+// An iterator for sample vectors. This could be defined privately in the .cc
+// file but is here for easy testing.
class BASE_EXPORT SampleVectorIterator : public SampleCountIterator {
public:
SampleVectorIterator(const std::vector<HistogramBase::AtomicCount>* counts,
@@ -84,7 +164,7 @@ class BASE_EXPORT SampleVectorIterator : public SampleCountIterator {
bool Done() const override;
void Next() override;
void Get(HistogramBase::Sample* min,
- HistogramBase::Sample* max,
+ int64_t* max,
HistogramBase::Count* count) const override;
// SampleVector uses predefined buckets, so iterator can return bucket index.
diff --git a/base/metrics/sample_vector_unittest.cc b/base/metrics/sample_vector_unittest.cc
index 2d77d2376b..49218023d0 100644
--- a/base/metrics/sample_vector_unittest.cc
+++ b/base/metrics/sample_vector_unittest.cc
@@ -7,18 +7,29 @@
#include <limits.h>
#include <stddef.h>
+#include <atomic>
#include <memory>
#include <vector>
#include "base/metrics/bucket_ranges.h"
#include "base/metrics/histogram.h"
+#include "base/metrics/persistent_memory_allocator.h"
#include "base/test/gtest_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
-namespace {
-TEST(SampleVectorTest, AccumulateTest) {
+// This framework class has "friend" access to the SampleVector for accessing
+// non-public methods and fields.
+class SampleVectorTest : public testing::Test {
+ public:
+ const HistogramBase::AtomicCount* GetSamplesCounts(
+ const SampleVectorBase& samples) {
+ return samples.counts();
+ }
+};
+
+TEST_F(SampleVectorTest, Accumulate) {
// Custom buckets: [1, 5) [5, 10)
BucketRanges ranges(3);
ranges.set_range(0, 1);
@@ -45,7 +56,7 @@ TEST(SampleVectorTest, AccumulateTest) {
EXPECT_EQ(samples.TotalCount(), samples.redundant_count());
}
-TEST(SampleVectorTest, Accumulate_LargeValuesDontOverflow) {
+TEST_F(SampleVectorTest, Accumulate_LargeValuesDontOverflow) {
// Custom buckets: [1, 250000000) [250000000, 500000000)
BucketRanges ranges(3);
ranges.set_range(0, 1);
@@ -72,7 +83,7 @@ TEST(SampleVectorTest, Accumulate_LargeValuesDontOverflow) {
EXPECT_EQ(samples.TotalCount(), samples.redundant_count());
}
-TEST(SampleVectorTest, AddSubtractTest) {
+TEST_F(SampleVectorTest, AddSubtract) {
// Custom buckets: [0, 1) [1, 2) [2, 3) [3, INT_MAX)
BucketRanges ranges(5);
ranges.set_range(0, 0);
@@ -116,7 +127,7 @@ TEST(SampleVectorTest, AddSubtractTest) {
EXPECT_EQ(samples1.redundant_count(), samples1.TotalCount());
}
-TEST(SampleVectorDeathTest, BucketIndexTest) {
+TEST_F(SampleVectorTest, BucketIndexDeath) {
// 8 buckets with exponential layout:
// [0, 1) [1, 2) [2, 4) [4, 8) [8, 16) [16, 32) [32, 64) [64, INT_MAX)
BucketRanges ranges(9);
@@ -132,9 +143,9 @@ TEST(SampleVectorDeathTest, BucketIndexTest) {
EXPECT_EQ(3, samples.GetCount(65));
// Extreme case.
- EXPECT_DCHECK_DEATH(samples.Accumulate(INT_MIN, 100));
- EXPECT_DCHECK_DEATH(samples.Accumulate(-1, 100));
- EXPECT_DCHECK_DEATH(samples.Accumulate(INT_MAX, 100));
+ EXPECT_DEATH_IF_SUPPORTED(samples.Accumulate(INT_MIN, 100), "");
+ EXPECT_DEATH_IF_SUPPORTED(samples.Accumulate(-1, 100), "");
+ EXPECT_DEATH_IF_SUPPORTED(samples.Accumulate(INT_MAX, 100), "");
// Custom buckets: [1, 5) [5, 10)
// Note, this is not a valid BucketRanges for Histogram because it does not
@@ -154,11 +165,11 @@ TEST(SampleVectorDeathTest, BucketIndexTest) {
EXPECT_EQ(4, samples2.GetCount(5));
// Extreme case.
- EXPECT_DCHECK_DEATH(samples2.Accumulate(0, 100));
- EXPECT_DCHECK_DEATH(samples2.Accumulate(10, 100));
+ EXPECT_DEATH_IF_SUPPORTED(samples2.Accumulate(0, 100), "");
+ EXPECT_DEATH_IF_SUPPORTED(samples2.Accumulate(10, 100), "");
}
-TEST(SampleVectorDeathTest, AddSubtractBucketNotMatchTest) {
+TEST_F(SampleVectorTest, AddSubtractBucketNotMatchDeath) {
// Custom buckets 1: [1, 3) [3, 5)
BucketRanges ranges1(3);
ranges1.set_range(0, 1);
@@ -179,25 +190,27 @@ TEST(SampleVectorDeathTest, AddSubtractBucketNotMatchTest) {
samples1.Add(samples2);
EXPECT_EQ(100, samples1.GetCountAtIndex(0));
- // Extra bucket in the beginning.
+ // Extra bucket in the beginning. These should CHECK in GetBucketIndex.
samples2.Accumulate(0, 100);
- EXPECT_DCHECK_DEATH(samples1.Add(samples2));
- EXPECT_DCHECK_DEATH(samples1.Subtract(samples2));
+ EXPECT_DEATH_IF_SUPPORTED(samples1.Add(samples2), "");
+ EXPECT_DEATH_IF_SUPPORTED(samples1.Subtract(samples2), "");
- // Extra bucket in the end.
+ // Extra bucket in the end. These should cause AddSubtractImpl to fail, and
+ // Add to DCHECK as a result.
samples2.Accumulate(0, -100);
samples2.Accumulate(6, 100);
EXPECT_DCHECK_DEATH(samples1.Add(samples2));
EXPECT_DCHECK_DEATH(samples1.Subtract(samples2));
- // Bucket not match: [3, 5) VS [3, 6)
+ // Bucket not match: [3, 5) VS [3, 6). These should cause AddSubtractImpl to
+ // DCHECK.
samples2.Accumulate(6, -100);
samples2.Accumulate(3, 100);
EXPECT_DCHECK_DEATH(samples1.Add(samples2));
EXPECT_DCHECK_DEATH(samples1.Subtract(samples2));
}
-TEST(SampleVectorIteratorTest, IterateTest) {
+TEST_F(SampleVectorTest, Iterate) {
BucketRanges ranges(5);
ranges.set_range(0, 0);
ranges.set_range(1, 1);
@@ -215,7 +228,7 @@ TEST(SampleVectorIteratorTest, IterateTest) {
size_t index;
HistogramBase::Sample min;
- HistogramBase::Sample max;
+ int64_t max;
HistogramBase::Count count;
it.Get(&min, &max, &count);
EXPECT_EQ(0, min);
@@ -257,7 +270,7 @@ TEST(SampleVectorIteratorTest, IterateTest) {
EXPECT_EQ(4, i);
}
-TEST(SampleVectorIteratorDeathTest, IterateDoneTest) {
+TEST_F(SampleVectorTest, IterateDoneDeath) {
BucketRanges ranges(5);
ranges.set_range(0, 0);
ranges.set_range(1, 1);
@@ -271,7 +284,7 @@ TEST(SampleVectorIteratorDeathTest, IterateDoneTest) {
EXPECT_TRUE(it->Done());
HistogramBase::Sample min;
- HistogramBase::Sample max;
+ int64_t max;
HistogramBase::Count count;
EXPECT_DCHECK_DEATH(it->Get(&min, &max, &count));
@@ -282,5 +295,251 @@ TEST(SampleVectorIteratorDeathTest, IterateDoneTest) {
EXPECT_FALSE(it->Done());
}
-} // namespace
+TEST_F(SampleVectorTest, SingleSample) {
+ // Custom buckets: [1, 5) [5, 10)
+ BucketRanges ranges(3);
+ ranges.set_range(0, 1);
+ ranges.set_range(1, 5);
+ ranges.set_range(2, 10);
+ SampleVector samples(&ranges);
+
+ // Ensure that a single value accumulates correctly.
+ EXPECT_FALSE(GetSamplesCounts(samples));
+ samples.Accumulate(3, 200);
+ EXPECT_EQ(200, samples.GetCount(3));
+ EXPECT_FALSE(GetSamplesCounts(samples));
+ samples.Accumulate(3, 400);
+ EXPECT_EQ(600, samples.GetCount(3));
+ EXPECT_FALSE(GetSamplesCounts(samples));
+ EXPECT_EQ(3 * 600, samples.sum());
+ EXPECT_EQ(600, samples.TotalCount());
+ EXPECT_EQ(600, samples.redundant_count());
+
+ // Ensure that the iterator returns only one value.
+ HistogramBase::Sample min;
+ int64_t max;
+ HistogramBase::Count count;
+ std::unique_ptr<SampleCountIterator> it = samples.Iterator();
+ ASSERT_FALSE(it->Done());
+ it->Get(&min, &max, &count);
+ EXPECT_EQ(1, min);
+ EXPECT_EQ(5, max);
+ EXPECT_EQ(600, count);
+ it->Next();
+ EXPECT_TRUE(it->Done());
+
+ // Ensure that it can be merged to another single-sample vector.
+ SampleVector samples_copy(&ranges);
+ samples_copy.Add(samples);
+ EXPECT_FALSE(GetSamplesCounts(samples_copy));
+ EXPECT_EQ(3 * 600, samples_copy.sum());
+ EXPECT_EQ(600, samples_copy.TotalCount());
+ EXPECT_EQ(600, samples_copy.redundant_count());
+
+ // A different value should cause creation of the counts array.
+ samples.Accumulate(8, 100);
+ EXPECT_TRUE(GetSamplesCounts(samples));
+ EXPECT_EQ(600, samples.GetCount(3));
+ EXPECT_EQ(100, samples.GetCount(8));
+ EXPECT_EQ(3 * 600 + 8 * 100, samples.sum());
+ EXPECT_EQ(600 + 100, samples.TotalCount());
+ EXPECT_EQ(600 + 100, samples.redundant_count());
+
+ // The iterator should now return both values.
+ it = samples.Iterator();
+ ASSERT_FALSE(it->Done());
+ it->Get(&min, &max, &count);
+ EXPECT_EQ(1, min);
+ EXPECT_EQ(5, max);
+ EXPECT_EQ(600, count);
+ it->Next();
+ ASSERT_FALSE(it->Done());
+ it->Get(&min, &max, &count);
+ EXPECT_EQ(5, min);
+ EXPECT_EQ(10, max);
+ EXPECT_EQ(100, count);
+ it->Next();
+ EXPECT_TRUE(it->Done());
+
+ // Ensure that it can merged to a single-sample vector.
+ samples_copy.Add(samples);
+ EXPECT_TRUE(GetSamplesCounts(samples_copy));
+ EXPECT_EQ(3 * 1200 + 8 * 100, samples_copy.sum());
+ EXPECT_EQ(1200 + 100, samples_copy.TotalCount());
+ EXPECT_EQ(1200 + 100, samples_copy.redundant_count());
+}
+
+TEST_F(SampleVectorTest, PersistentSampleVector) {
+ LocalPersistentMemoryAllocator allocator(64 << 10, 0, "");
+ std::atomic<PersistentMemoryAllocator::Reference> samples_ref;
+ samples_ref.store(0, std::memory_order_relaxed);
+ HistogramSamples::Metadata samples_meta;
+ memset(&samples_meta, 0, sizeof(samples_meta));
+
+ // Custom buckets: [1, 5) [5, 10)
+ BucketRanges ranges(3);
+ ranges.set_range(0, 1);
+ ranges.set_range(1, 5);
+ ranges.set_range(2, 10);
+
+ // Persistent allocation.
+ const size_t counts_bytes =
+ sizeof(HistogramBase::AtomicCount) * ranges.bucket_count();
+ const DelayedPersistentAllocation allocation(&allocator, &samples_ref, 1,
+ counts_bytes, false);
+
+ PersistentSampleVector samples1(0, &ranges, &samples_meta, allocation);
+ EXPECT_FALSE(GetSamplesCounts(samples1));
+ samples1.Accumulate(3, 200);
+ EXPECT_EQ(200, samples1.GetCount(3));
+ EXPECT_FALSE(GetSamplesCounts(samples1));
+ EXPECT_EQ(0, samples1.GetCount(8));
+ EXPECT_FALSE(GetSamplesCounts(samples1));
+
+ PersistentSampleVector samples2(0, &ranges, &samples_meta, allocation);
+ EXPECT_EQ(200, samples2.GetCount(3));
+ EXPECT_FALSE(GetSamplesCounts(samples2));
+
+ HistogramBase::Sample min;
+ int64_t max;
+ HistogramBase::Count count;
+ std::unique_ptr<SampleCountIterator> it = samples2.Iterator();
+ ASSERT_FALSE(it->Done());
+ it->Get(&min, &max, &count);
+ EXPECT_EQ(1, min);
+ EXPECT_EQ(5, max);
+ EXPECT_EQ(200, count);
+ it->Next();
+ EXPECT_TRUE(it->Done());
+
+ samples1.Accumulate(8, 100);
+ EXPECT_TRUE(GetSamplesCounts(samples1));
+
+ EXPECT_FALSE(GetSamplesCounts(samples2));
+ EXPECT_EQ(200, samples2.GetCount(3));
+ EXPECT_EQ(100, samples2.GetCount(8));
+ EXPECT_TRUE(GetSamplesCounts(samples2));
+ EXPECT_EQ(3 * 200 + 8 * 100, samples2.sum());
+ EXPECT_EQ(300, samples2.TotalCount());
+ EXPECT_EQ(300, samples2.redundant_count());
+
+ it = samples2.Iterator();
+ ASSERT_FALSE(it->Done());
+ it->Get(&min, &max, &count);
+ EXPECT_EQ(1, min);
+ EXPECT_EQ(5, max);
+ EXPECT_EQ(200, count);
+ it->Next();
+ ASSERT_FALSE(it->Done());
+ it->Get(&min, &max, &count);
+ EXPECT_EQ(5, min);
+ EXPECT_EQ(10, max);
+ EXPECT_EQ(100, count);
+ it->Next();
+ EXPECT_TRUE(it->Done());
+
+ PersistentSampleVector samples3(0, &ranges, &samples_meta, allocation);
+ EXPECT_TRUE(GetSamplesCounts(samples2));
+ EXPECT_EQ(200, samples3.GetCount(3));
+ EXPECT_EQ(100, samples3.GetCount(8));
+ EXPECT_EQ(3 * 200 + 8 * 100, samples3.sum());
+ EXPECT_EQ(300, samples3.TotalCount());
+ EXPECT_EQ(300, samples3.redundant_count());
+
+ it = samples3.Iterator();
+ ASSERT_FALSE(it->Done());
+ it->Get(&min, &max, &count);
+ EXPECT_EQ(1, min);
+ EXPECT_EQ(5, max);
+ EXPECT_EQ(200, count);
+ it->Next();
+ ASSERT_FALSE(it->Done());
+ it->Get(&min, &max, &count);
+ EXPECT_EQ(5, min);
+ EXPECT_EQ(10, max);
+ EXPECT_EQ(100, count);
+ it->Next();
+ EXPECT_TRUE(it->Done());
+}
+
+TEST_F(SampleVectorTest, PersistentSampleVectorTestWithOutsideAlloc) {
+ LocalPersistentMemoryAllocator allocator(64 << 10, 0, "");
+ std::atomic<PersistentMemoryAllocator::Reference> samples_ref;
+ samples_ref.store(0, std::memory_order_relaxed);
+ HistogramSamples::Metadata samples_meta;
+ memset(&samples_meta, 0, sizeof(samples_meta));
+
+ // Custom buckets: [1, 5) [5, 10)
+ BucketRanges ranges(3);
+ ranges.set_range(0, 1);
+ ranges.set_range(1, 5);
+ ranges.set_range(2, 10);
+
+ // Persistent allocation.
+ const size_t counts_bytes =
+ sizeof(HistogramBase::AtomicCount) * ranges.bucket_count();
+ const DelayedPersistentAllocation allocation(&allocator, &samples_ref, 1,
+ counts_bytes, false);
+
+ PersistentSampleVector samples1(0, &ranges, &samples_meta, allocation);
+ EXPECT_FALSE(GetSamplesCounts(samples1));
+ samples1.Accumulate(3, 200);
+ EXPECT_EQ(200, samples1.GetCount(3));
+ EXPECT_FALSE(GetSamplesCounts(samples1));
+
+ // Because the delayed allocation can be shared with other objects (the
+ // |offset| parameter allows concatinating multiple data blocks into the
+ // same allocation), it's possible that the allocation gets realized from
+ // the outside even though the data block being accessed is all zero.
+ allocation.Get();
+ EXPECT_EQ(200, samples1.GetCount(3));
+ EXPECT_FALSE(GetSamplesCounts(samples1));
+
+ HistogramBase::Sample min;
+ int64_t max;
+ HistogramBase::Count count;
+ std::unique_ptr<SampleCountIterator> it = samples1.Iterator();
+ ASSERT_FALSE(it->Done());
+ it->Get(&min, &max, &count);
+ EXPECT_EQ(1, min);
+ EXPECT_EQ(5, max);
+ EXPECT_EQ(200, count);
+ it->Next();
+ EXPECT_TRUE(it->Done());
+
+ // A duplicate samples object should still see the single-sample entry even
+ // when storage is available.
+ PersistentSampleVector samples2(0, &ranges, &samples_meta, allocation);
+ EXPECT_EQ(200, samples2.GetCount(3));
+
+ // New accumulations, in both directions, of the existing value should work.
+ samples1.Accumulate(3, 50);
+ EXPECT_EQ(250, samples1.GetCount(3));
+ EXPECT_EQ(250, samples2.GetCount(3));
+ samples2.Accumulate(3, 50);
+ EXPECT_EQ(300, samples1.GetCount(3));
+ EXPECT_EQ(300, samples2.GetCount(3));
+
+ it = samples1.Iterator();
+ ASSERT_FALSE(it->Done());
+ it->Get(&min, &max, &count);
+ EXPECT_EQ(1, min);
+ EXPECT_EQ(5, max);
+ EXPECT_EQ(300, count);
+ it->Next();
+ EXPECT_TRUE(it->Done());
+
+ samples1.Accumulate(8, 100);
+ EXPECT_TRUE(GetSamplesCounts(samples1));
+ EXPECT_EQ(300, samples1.GetCount(3));
+ EXPECT_EQ(300, samples2.GetCount(3));
+ EXPECT_EQ(100, samples1.GetCount(8));
+ EXPECT_EQ(100, samples2.GetCount(8));
+ samples2.Accumulate(8, 100);
+ EXPECT_EQ(300, samples1.GetCount(3));
+ EXPECT_EQ(300, samples2.GetCount(3));
+ EXPECT_EQ(200, samples1.GetCount(8));
+ EXPECT_EQ(200, samples2.GetCount(8));
+}
+
} // namespace base
diff --git a/base/metrics/single_sample_metrics.cc b/base/metrics/single_sample_metrics.cc
new file mode 100644
index 0000000000..57c1c8f163
--- /dev/null
+++ b/base/metrics/single_sample_metrics.cc
@@ -0,0 +1,77 @@
+// 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/metrics/single_sample_metrics.h"
+
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram.h"
+
+namespace base {
+
+static SingleSampleMetricsFactory* g_factory = nullptr;
+
+// static
+SingleSampleMetricsFactory* SingleSampleMetricsFactory::Get() {
+ if (!g_factory)
+ g_factory = new DefaultSingleSampleMetricsFactory();
+
+ return g_factory;
+}
+
+// static
+void SingleSampleMetricsFactory::SetFactory(
+ std::unique_ptr<SingleSampleMetricsFactory> factory) {
+ DCHECK(!g_factory);
+ g_factory = factory.release();
+}
+
+// static
+void SingleSampleMetricsFactory::DeleteFactoryForTesting() {
+ DCHECK(g_factory);
+ delete g_factory;
+ g_factory = nullptr;
+}
+
+std::unique_ptr<SingleSampleMetric>
+DefaultSingleSampleMetricsFactory::CreateCustomCountsMetric(
+ const std::string& histogram_name,
+ HistogramBase::Sample min,
+ HistogramBase::Sample max,
+ uint32_t bucket_count) {
+ return std::make_unique<DefaultSingleSampleMetric>(
+ histogram_name, min, max, bucket_count,
+ HistogramBase::kUmaTargetedHistogramFlag);
+}
+
+DefaultSingleSampleMetric::DefaultSingleSampleMetric(
+ const std::string& histogram_name,
+ HistogramBase::Sample min,
+ HistogramBase::Sample max,
+ uint32_t bucket_count,
+ int32_t flags)
+ : histogram_(Histogram::FactoryGet(histogram_name,
+ min,
+ max,
+ bucket_count,
+ flags)) {
+ // Bad construction parameters may lead to |histogram_| being null; DCHECK to
+ // find accidental errors in production. We must still handle the nullptr in
+ // destruction though since this construction may come from another untrusted
+ // process.
+ DCHECK(histogram_);
+}
+
+DefaultSingleSampleMetric::~DefaultSingleSampleMetric() {
+ // |histogram_| may be nullptr if bad construction parameters are given.
+ if (sample_ < 0 || !histogram_)
+ return;
+ histogram_->Add(sample_);
+}
+
+void DefaultSingleSampleMetric::SetSample(HistogramBase::Sample sample) {
+ DCHECK_GE(sample, 0);
+ sample_ = sample;
+}
+
+} // namespace base
diff --git a/base/metrics/single_sample_metrics.h b/base/metrics/single_sample_metrics.h
new file mode 100644
index 0000000000..b966cb1ac5
--- /dev/null
+++ b/base/metrics/single_sample_metrics.h
@@ -0,0 +1,104 @@
+// 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_METRICS_SINGLE_SAMPLE_METRICS_H_
+#define BASE_METRICS_SINGLE_SAMPLE_METRICS_H_
+
+#include <string>
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/metrics/histogram_base.h"
+
+namespace base {
+
+// See base/metrics/histograms.h for parameter definitions. Must only be used
+// and destroyed from the same thread as construction.
+class BASE_EXPORT SingleSampleMetric {
+ public:
+ virtual ~SingleSampleMetric() = default;
+
+ virtual void SetSample(HistogramBase::Sample sample) = 0;
+};
+
+// Factory for creating single sample metrics. A single sample metric only
+// reports its sample once at destruction time. The sample may be changed prior
+// to destruction using the SetSample() method as many times as desired.
+//
+// The metric creation methods are safe to call from any thread, however the
+// returned class must only be used and destroyed from the same thread as
+// construction.
+//
+// See base/metrics/histogram_macros.h for usage recommendations and
+// base/metrics/histogram.h for full parameter definitions.
+class BASE_EXPORT SingleSampleMetricsFactory {
+ public:
+ virtual ~SingleSampleMetricsFactory() = default;
+
+ // Returns the factory provided by SetFactory(), or if no factory has been set
+ // a default factory will be provided (future calls to SetFactory() will fail
+ // if the default factory is ever vended).
+ static SingleSampleMetricsFactory* Get();
+ static void SetFactory(std::unique_ptr<SingleSampleMetricsFactory> factory);
+
+ // The factory normally persists until process shutdown, but in testing we
+ // should avoid leaking it since it sets a global.
+ static void DeleteFactoryForTesting();
+
+ // The methods below return a single sample metric for counts histograms; see
+ // method comments for the corresponding histogram macro.
+
+ // UMA_HISTOGRAM_CUSTOM_COUNTS()
+ virtual std::unique_ptr<SingleSampleMetric> CreateCustomCountsMetric(
+ const std::string& histogram_name,
+ HistogramBase::Sample min,
+ HistogramBase::Sample max,
+ uint32_t bucket_count) = 0;
+};
+
+// Default implementation for when no factory has been provided to the process.
+// Samples are only recorded within the current process in this case, so samples
+// will be lost in the event of sudden process termination.
+class BASE_EXPORT DefaultSingleSampleMetricsFactory
+ : public SingleSampleMetricsFactory {
+ public:
+ DefaultSingleSampleMetricsFactory() = default;
+ ~DefaultSingleSampleMetricsFactory() override = default;
+
+ // SingleSampleMetricsFactory:
+ std::unique_ptr<SingleSampleMetric> CreateCustomCountsMetric(
+ const std::string& histogram_name,
+ HistogramBase::Sample min,
+ HistogramBase::Sample max,
+ uint32_t bucket_count) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DefaultSingleSampleMetricsFactory);
+};
+
+class BASE_EXPORT DefaultSingleSampleMetric : public SingleSampleMetric {
+ public:
+ DefaultSingleSampleMetric(const std::string& histogram_name,
+ HistogramBase::Sample min,
+ HistogramBase::Sample max,
+ uint32_t bucket_count,
+ int32_t flags);
+ ~DefaultSingleSampleMetric() override;
+
+ // SingleSampleMetric:
+ void SetSample(HistogramBase::Sample sample) override;
+
+ private:
+ HistogramBase* const histogram_;
+
+ // The last sample provided to SetSample(). We use -1 as a sentinel value to
+ // indicate no sample has been set.
+ HistogramBase::Sample sample_ = -1;
+
+ DISALLOW_COPY_AND_ASSIGN(DefaultSingleSampleMetric);
+};
+
+} // namespace base
+
+#endif // BASE_METRICS_SINGLE_SAMPLE_METRICS_H_
diff --git a/base/metrics/single_sample_metrics_unittest.cc b/base/metrics/single_sample_metrics_unittest.cc
new file mode 100644
index 0000000000..d4d5913d63
--- /dev/null
+++ b/base/metrics/single_sample_metrics_unittest.cc
@@ -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.
+
+#include "base/metrics/single_sample_metrics.h"
+
+#include "base/memory/ptr_util.h"
+#include "base/metrics/dummy_histogram.h"
+#include "base/test/gtest_util.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+const HistogramBase::Sample kMin = 1;
+const HistogramBase::Sample kMax = 10;
+const uint32_t kBucketCount = 10;
+const char kMetricName[] = "Single.Sample.Metric";
+
+class SingleSampleMetricsTest : public testing::Test {
+ public:
+ SingleSampleMetricsTest() = default;
+
+ ~SingleSampleMetricsTest() override {
+ // Ensure we cleanup after ourselves.
+ SingleSampleMetricsFactory::DeleteFactoryForTesting();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SingleSampleMetricsTest);
+};
+
+} // namespace
+
+TEST_F(SingleSampleMetricsTest, DefaultFactoryGetSet) {
+ SingleSampleMetricsFactory* factory = SingleSampleMetricsFactory::Get();
+ ASSERT_TRUE(factory);
+
+ // Same factory should be returned evermore.
+ EXPECT_EQ(factory, SingleSampleMetricsFactory::Get());
+
+ // Setting a factory after the default has been instantiated should fail.
+ EXPECT_DCHECK_DEATH(SingleSampleMetricsFactory::SetFactory(
+ WrapUnique<SingleSampleMetricsFactory>(nullptr)));
+}
+
+TEST_F(SingleSampleMetricsTest, CustomFactoryGetSet) {
+ SingleSampleMetricsFactory* factory = new DefaultSingleSampleMetricsFactory();
+ SingleSampleMetricsFactory::SetFactory(WrapUnique(factory));
+ EXPECT_EQ(factory, SingleSampleMetricsFactory::Get());
+}
+
+TEST_F(SingleSampleMetricsTest, DefaultSingleSampleMetricNoValue) {
+ SingleSampleMetricsFactory* factory = SingleSampleMetricsFactory::Get();
+
+ HistogramTester tester;
+ std::unique_ptr<SingleSampleMetric> metric =
+ factory->CreateCustomCountsMetric(kMetricName, kMin, kMax, kBucketCount);
+ metric.reset();
+
+ // Verify that no sample is recorded if SetSample() is never called.
+ tester.ExpectTotalCount(kMetricName, 0);
+}
+
+TEST_F(SingleSampleMetricsTest, DefaultSingleSampleMetricWithValue) {
+ SingleSampleMetricsFactory* factory = SingleSampleMetricsFactory::Get();
+
+ HistogramTester tester;
+ std::unique_ptr<SingleSampleMetric> metric =
+ factory->CreateCustomCountsMetric(kMetricName, kMin, kMax, kBucketCount);
+
+ const HistogramBase::Sample kLastSample = 9;
+ metric->SetSample(1);
+ metric->SetSample(3);
+ metric->SetSample(5);
+ metric->SetSample(kLastSample);
+ metric.reset();
+
+ // Verify only the last sample sent to SetSample() is recorded.
+ tester.ExpectUniqueSample(kMetricName, kLastSample, 1);
+
+ // Verify construction implicitly by requesting a histogram with the same
+ // parameters; this test relies on the fact that histogram objects are unique
+ // per name. Different parameters will result in a Dummy histogram returned.
+ EXPECT_EQ(
+ DummyHistogram::GetInstance(),
+ Histogram::FactoryGet(kMetricName, 1, 3, 3, HistogramBase::kNoFlags));
+ EXPECT_NE(DummyHistogram::GetInstance(),
+ Histogram::FactoryGet(kMetricName, kMin, kMax, kBucketCount,
+ HistogramBase::kUmaTargetedHistogramFlag));
+}
+
+TEST_F(SingleSampleMetricsTest, MultipleMetricsAreDistinct) {
+ SingleSampleMetricsFactory* factory = SingleSampleMetricsFactory::Get();
+
+ HistogramTester tester;
+ std::unique_ptr<SingleSampleMetric> metric =
+ factory->CreateCustomCountsMetric(kMetricName, kMin, kMax, kBucketCount);
+ std::unique_ptr<SingleSampleMetric> metric2 =
+ factory->CreateCustomCountsMetric(kMetricName, kMin, kMax, kBucketCount);
+ const char kMetricName2[] = "Single.Sample.Metric.2";
+ std::unique_ptr<SingleSampleMetric> metric3 =
+ factory->CreateCustomCountsMetric(kMetricName2, kMin, kMax, kBucketCount);
+
+ const HistogramBase::Sample kSample1 = 5;
+ metric->SetSample(kSample1);
+ metric2->SetSample(kSample1);
+
+ const HistogramBase::Sample kSample2 = 7;
+ metric3->SetSample(kSample2);
+
+ metric.reset();
+ tester.ExpectUniqueSample(kMetricName, kSample1, 1);
+
+ metric2.reset();
+ tester.ExpectUniqueSample(kMetricName, kSample1, 2);
+
+ metric3.reset();
+ tester.ExpectUniqueSample(kMetricName2, kSample2, 1);
+}
+
+} // namespace base
diff --git a/base/metrics/sparse_histogram.cc b/base/metrics/sparse_histogram.cc
index 415d7f9430..30175a0780 100644
--- a/base/metrics/sparse_histogram.cc
+++ b/base/metrics/sparse_histogram.cc
@@ -7,6 +7,7 @@
#include <utility>
#include "base/memory/ptr_util.h"
+#include "base/metrics/dummy_histogram.h"
#include "base/metrics/metrics_hashes.h"
#include "base/metrics/persistent_histogram_allocator.h"
#include "base/metrics/persistent_sample_map.h"
@@ -26,6 +27,12 @@ HistogramBase* SparseHistogram::FactoryGet(const std::string& name,
int32_t flags) {
HistogramBase* histogram = StatisticsRecorder::FindHistogram(name);
if (!histogram) {
+ // TODO(gayane): |HashMetricName| is called again in Histogram constructor.
+ // Refactor code to avoid the additional call.
+ bool should_record =
+ StatisticsRecorder::ShouldRecordHistogram(HashMetricName(name));
+ if (!should_record)
+ return DummyHistogram::GetInstance();
// Try to create the histogram using a "persistent" allocator. As of
// 2016-02-25, the availability of such is controlled by a base::Feature
// that is off by default. If the allocator doesn't exist or if
@@ -45,7 +52,7 @@ HistogramBase* SparseHistogram::FactoryGet(const std::string& name,
DCHECK(!histogram_ref); // Should never have been set.
DCHECK(!allocator); // Shouldn't have failed.
flags &= ~HistogramBase::kIsPersistent;
- tentative_histogram.reset(new SparseHistogram(name));
+ tentative_histogram.reset(new SparseHistogram(GetPermanentName(name)));
tentative_histogram->SetFlags(flags);
}
@@ -62,10 +69,6 @@ HistogramBase* SparseHistogram::FactoryGet(const std::string& name,
allocator->FinalizeHistogram(histogram_ref,
histogram == tentative_histogram_ptr);
}
-
- ReportHistogramActivity(*histogram, HISTOGRAM_CREATED);
- } else {
- ReportHistogramActivity(*histogram, HISTOGRAM_LOOKUP);
}
CHECK_EQ(SPARSE_HISTOGRAM, histogram->GetHistogramType());
@@ -75,17 +78,17 @@ HistogramBase* SparseHistogram::FactoryGet(const std::string& name,
// static
std::unique_ptr<HistogramBase> SparseHistogram::PersistentCreate(
PersistentHistogramAllocator* allocator,
- const std::string& name,
+ const char* name,
HistogramSamples::Metadata* meta,
HistogramSamples::Metadata* logged_meta) {
return WrapUnique(
new SparseHistogram(allocator, name, meta, logged_meta));
}
-SparseHistogram::~SparseHistogram() {}
+SparseHistogram::~SparseHistogram() = default;
uint64_t SparseHistogram::name_hash() const {
- return samples_->id();
+ return unlogged_samples_->id();
}
HistogramType SparseHistogram::GetHistogramType() const {
@@ -111,7 +114,7 @@ void SparseHistogram::AddCount(Sample value, int count) {
}
{
base::AutoLock auto_lock(lock_);
- samples_->Accumulate(value, count);
+ unlogged_samples_->Accumulate(value, count);
}
FindAndRunCallback(value);
@@ -121,7 +124,8 @@ std::unique_ptr<HistogramSamples> SparseHistogram::SnapshotSamples() const {
std::unique_ptr<SampleMap> snapshot(new SampleMap(name_hash()));
base::AutoLock auto_lock(lock_);
- snapshot->Add(*samples_);
+ snapshot->Add(*unlogged_samples_);
+ snapshot->Add(*logged_samples_);
return std::move(snapshot);
}
@@ -130,10 +134,9 @@ std::unique_ptr<HistogramSamples> SparseHistogram::SnapshotDelta() {
std::unique_ptr<SampleMap> snapshot(new SampleMap(name_hash()));
base::AutoLock auto_lock(lock_);
- snapshot->Add(*samples_);
+ snapshot->Add(*unlogged_samples_);
- // Subtract what was previously logged and update that information.
- snapshot->Subtract(*logged_samples_);
+ unlogged_samples_->Subtract(*snapshot);
logged_samples_->Add(*snapshot);
return std::move(snapshot);
}
@@ -144,21 +147,19 @@ std::unique_ptr<HistogramSamples> SparseHistogram::SnapshotFinalDelta() const {
std::unique_ptr<SampleMap> snapshot(new SampleMap(name_hash()));
base::AutoLock auto_lock(lock_);
- snapshot->Add(*samples_);
+ snapshot->Add(*unlogged_samples_);
- // Subtract what was previously logged and then return.
- snapshot->Subtract(*logged_samples_);
return std::move(snapshot);
}
void SparseHistogram::AddSamples(const HistogramSamples& samples) {
base::AutoLock auto_lock(lock_);
- samples_->Add(samples);
+ unlogged_samples_->Add(samples);
}
bool SparseHistogram::AddSamplesFromPickle(PickleIterator* iter) {
base::AutoLock auto_lock(lock_);
- return samples_->AddFromPickle(iter);
+ return unlogged_samples_->AddFromPickle(iter);
}
void SparseHistogram::WriteHTMLGraph(std::string* output) const {
@@ -171,17 +172,18 @@ void SparseHistogram::WriteAscii(std::string* output) const {
WriteAsciiImpl(true, "\n", output);
}
-bool SparseHistogram::SerializeInfoImpl(Pickle* pickle) const {
- return pickle->WriteString(histogram_name()) && pickle->WriteInt(flags());
+void SparseHistogram::SerializeInfoImpl(Pickle* pickle) const {
+ pickle->WriteString(histogram_name());
+ pickle->WriteInt(flags());
}
-SparseHistogram::SparseHistogram(const std::string& name)
+SparseHistogram::SparseHistogram(const char* name)
: HistogramBase(name),
- samples_(new SampleMap(HashMetricName(name))),
- logged_samples_(new SampleMap(samples_->id())) {}
+ unlogged_samples_(new SampleMap(HashMetricName(name))),
+ logged_samples_(new SampleMap(unlogged_samples_->id())) {}
SparseHistogram::SparseHistogram(PersistentHistogramAllocator* allocator,
- const std::string& name,
+ const char* name,
HistogramSamples::Metadata* meta,
HistogramSamples::Metadata* logged_meta)
: HistogramBase(name),
@@ -195,17 +197,18 @@ SparseHistogram::SparseHistogram(PersistentHistogramAllocator* allocator,
// "active" samples use, for convenience purposes, an ID matching
// that of the histogram while the "logged" samples use that number
// plus 1.
- samples_(new PersistentSampleMap(HashMetricName(name), allocator, meta)),
- logged_samples_(
- new PersistentSampleMap(samples_->id() + 1, allocator, logged_meta)) {
-}
+ unlogged_samples_(
+ new PersistentSampleMap(HashMetricName(name), allocator, meta)),
+ logged_samples_(new PersistentSampleMap(unlogged_samples_->id() + 1,
+ allocator,
+ logged_meta)) {}
HistogramBase* SparseHistogram::DeserializeInfoImpl(PickleIterator* iter) {
std::string histogram_name;
int flags;
if (!iter->ReadString(&histogram_name) || !iter->ReadInt(&flags)) {
DLOG(ERROR) << "Pickle error decoding Histogram: " << histogram_name;
- return NULL;
+ return nullptr;
}
flags &= ~HistogramBase::kIPCSerializationSourceFlag;
@@ -243,7 +246,7 @@ void SparseHistogram::WriteAsciiImpl(bool graph_it,
std::unique_ptr<SampleCountIterator> it = snapshot->Iterator();
while (!it->Done()) {
Sample min;
- Sample max;
+ int64_t max;
Count count;
it->Get(&min, &max, &count);
if (min > largest_sample)
@@ -258,7 +261,7 @@ void SparseHistogram::WriteAsciiImpl(bool graph_it,
it = snapshot->Iterator();
while (!it->Done()) {
Sample min;
- Sample max;
+ int64_t max;
Count count;
it->Get(&min, &max, &count);
@@ -278,9 +281,7 @@ void SparseHistogram::WriteAsciiImpl(bool graph_it,
void SparseHistogram::WriteAsciiHeader(const Count total_count,
std::string* output) const {
- StringAppendF(output,
- "Histogram: %s recorded %d samples",
- histogram_name().c_str(),
+ StringAppendF(output, "Histogram: %s recorded %d samples", histogram_name(),
total_count);
if (flags())
StringAppendF(output, " (flags = 0x%x)", flags());
diff --git a/base/metrics/sparse_histogram.h b/base/metrics/sparse_histogram.h
index 97709ba18f..913762c95d 100644
--- a/base/metrics/sparse_histogram.h
+++ b/base/metrics/sparse_histogram.h
@@ -35,7 +35,7 @@ class BASE_EXPORT SparseHistogram : public HistogramBase {
// live longer than the created sparse histogram.
static std::unique_ptr<HistogramBase> PersistentCreate(
PersistentHistogramAllocator* allocator,
- const std::string& name,
+ const char* name,
HistogramSamples::Metadata* meta,
HistogramSamples::Metadata* logged_meta);
@@ -59,14 +59,14 @@ class BASE_EXPORT SparseHistogram : public HistogramBase {
protected:
// HistogramBase implementation:
- bool SerializeInfoImpl(base::Pickle* pickle) const override;
+ void SerializeInfoImpl(base::Pickle* pickle) const override;
private:
// Clients should always use FactoryGet to create SparseHistogram.
- explicit SparseHistogram(const std::string& name);
+ explicit SparseHistogram(const char* name);
SparseHistogram(PersistentHistogramAllocator* allocator,
- const std::string& name,
+ const char* name,
HistogramSamples::Metadata* meta,
HistogramSamples::Metadata* logged_meta);
@@ -97,7 +97,7 @@ class BASE_EXPORT SparseHistogram : public HistogramBase {
// Flag to indicate if PrepareFinalDelta has been previously called.
mutable bool final_delta_created_ = false;
- std::unique_ptr<HistogramSamples> samples_;
+ std::unique_ptr<HistogramSamples> unlogged_samples_;
std::unique_ptr<HistogramSamples> logged_samples_;
DISALLOW_COPY_AND_ASSIGN(SparseHistogram);
diff --git a/base/metrics/sparse_histogram_unittest.cc b/base/metrics/sparse_histogram_unittest.cc
index f4a7c9495e..72dd905441 100644
--- a/base/metrics/sparse_histogram_unittest.cc
+++ b/base/metrics/sparse_histogram_unittest.cc
@@ -8,14 +8,16 @@
#include <string>
#include "base/metrics/histogram_base.h"
-#include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_samples.h"
+#include "base/metrics/metrics_hashes.h"
#include "base/metrics/persistent_histogram_allocator.h"
#include "base/metrics/persistent_memory_allocator.h"
#include "base/metrics/sample_map.h"
#include "base/metrics/statistics_recorder.h"
#include "base/pickle.h"
#include "base/strings/stringprintf.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
@@ -57,11 +59,6 @@ class SparseHistogramTest : public testing::TestWithParam<bool> {
}
void CreatePersistentMemoryAllocator() {
- // By getting the results-histogram before any persistent allocator
- // is attached, that histogram is guaranteed not to be stored in
- // any persistent memory segment (which simplifies some tests).
- GlobalHistogramAllocator::GetCreateHistogramResultHistogram();
-
GlobalHistogramAllocator::CreateWithLocalMemory(
kAllocatorMemorySize, 0, "SparseHistogramAllocatorTest");
allocator_ = GlobalHistogramAllocator::Get()->memory_allocator();
@@ -72,7 +69,9 @@ class SparseHistogramTest : public testing::TestWithParam<bool> {
GlobalHistogramAllocator::ReleaseForTesting();
}
- std::unique_ptr<SparseHistogram> NewSparseHistogram(const std::string& name) {
+ std::unique_ptr<SparseHistogram> NewSparseHistogram(const char* name) {
+ // std::make_unique can't access protected ctor so do it manually. This
+ // test class is a friend so can access it.
return std::unique_ptr<SparseHistogram>(new SparseHistogram(name));
}
@@ -149,19 +148,38 @@ TEST_P(SparseHistogramTest, AddCount_LargeValuesDontOverflow) {
EXPECT_EQ(55250000000LL, snapshot2->sum());
}
+// Make sure that counts returned by Histogram::SnapshotDelta do not overflow
+// even when a total count (returned by Histogram::SnapshotSample) does.
+TEST_P(SparseHistogramTest, AddCount_LargeCountsDontOverflow) {
+ std::unique_ptr<SparseHistogram> histogram(NewSparseHistogram("Sparse"));
+ std::unique_ptr<HistogramSamples> snapshot(histogram->SnapshotSamples());
+ EXPECT_EQ(0, snapshot->TotalCount());
+ EXPECT_EQ(0, snapshot->sum());
+
+ const int count = (1 << 30) - 1;
+
+ // Repeat N times to make sure that there is no internal value overflow.
+ for (int i = 0; i < 10; ++i) {
+ histogram->AddCount(42, count);
+ std::unique_ptr<HistogramSamples> samples = histogram->SnapshotDelta();
+ EXPECT_EQ(count, samples->TotalCount());
+ EXPECT_EQ(count, samples->GetCount(42));
+ }
+}
+
TEST_P(SparseHistogramTest, MacroBasicTest) {
- UMA_HISTOGRAM_SPARSE_SLOWLY("Sparse", 100);
- UMA_HISTOGRAM_SPARSE_SLOWLY("Sparse", 200);
- UMA_HISTOGRAM_SPARSE_SLOWLY("Sparse", 100);
+ UmaHistogramSparse("Sparse", 100);
+ UmaHistogramSparse("Sparse", 200);
+ UmaHistogramSparse("Sparse", 100);
- StatisticsRecorder::Histograms histograms;
- StatisticsRecorder::GetHistograms(&histograms);
+ const StatisticsRecorder::Histograms histograms =
+ StatisticsRecorder::GetHistograms();
- ASSERT_EQ(1U, histograms.size());
- HistogramBase* sparse_histogram = histograms[0];
+ ASSERT_THAT(histograms, testing::SizeIs(1));
+ const HistogramBase* const sparse_histogram = histograms[0];
EXPECT_EQ(SPARSE_HISTOGRAM, sparse_histogram->GetHistogramType());
- EXPECT_EQ("Sparse", sparse_histogram->histogram_name());
+ EXPECT_EQ("Sparse", StringPiece(sparse_histogram->histogram_name()));
EXPECT_EQ(
HistogramBase::kUmaTargetedHistogramFlag |
(use_persistent_histogram_allocator_ ? HistogramBase::kIsPersistent
@@ -179,18 +197,14 @@ TEST_P(SparseHistogramTest, MacroInLoopTest) {
// Unlike the macros in histogram.h, SparseHistogram macros can have a
// variable as histogram name.
for (int i = 0; i < 2; i++) {
- std::string name = StringPrintf("Sparse%d", i + 1);
- UMA_HISTOGRAM_SPARSE_SLOWLY(name, 100);
+ UmaHistogramSparse(StringPrintf("Sparse%d", i), 100);
}
- StatisticsRecorder::Histograms histograms;
- StatisticsRecorder::GetHistograms(&histograms);
- ASSERT_EQ(2U, histograms.size());
-
- std::string name1 = histograms[0]->histogram_name();
- std::string name2 = histograms[1]->histogram_name();
- EXPECT_TRUE(("Sparse1" == name1 && "Sparse2" == name2) ||
- ("Sparse2" == name1 && "Sparse1" == name2));
+ const StatisticsRecorder::Histograms histograms =
+ StatisticsRecorder::Sort(StatisticsRecorder::GetHistograms());
+ ASSERT_THAT(histograms, testing::SizeIs(2));
+ EXPECT_STREQ(histograms[0]->histogram_name(), "Sparse0");
+ EXPECT_STREQ(histograms[1]->histogram_name(), "Sparse1");
}
TEST_P(SparseHistogramTest, Serialize) {
@@ -327,4 +341,48 @@ TEST_P(SparseHistogramTest, FactoryTime) {
<< "ns each.";
}
+TEST_P(SparseHistogramTest, ExtremeValues) {
+ static const struct {
+ Histogram::Sample sample;
+ int64_t expected_max;
+ } cases[] = {
+ // Note: We use -2147483647 - 1 rather than -2147483648 because the later
+ // is interpreted as - operator applied to 2147483648 and the latter can't
+ // be represented as an int32 and causes a warning.
+ {-2147483647 - 1, -2147483647LL},
+ {0, 1},
+ {2147483647, 2147483648LL},
+ };
+
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ HistogramBase* histogram =
+ SparseHistogram::FactoryGet(StringPrintf("ExtremeValues_%zu", i),
+ HistogramBase::kUmaTargetedHistogramFlag);
+ histogram->Add(cases[i].sample);
+
+ std::unique_ptr<HistogramSamples> snapshot = histogram->SnapshotSamples();
+ std::unique_ptr<SampleCountIterator> it = snapshot->Iterator();
+ ASSERT_FALSE(it->Done());
+
+ base::Histogram::Sample min;
+ int64_t max;
+ base::Histogram::Count count;
+ it->Get(&min, &max, &count);
+
+ EXPECT_EQ(1, count);
+ EXPECT_EQ(cases[i].sample, min);
+ EXPECT_EQ(cases[i].expected_max, max);
+
+ it->Next();
+ EXPECT_TRUE(it->Done());
+ }
+}
+
+TEST_P(SparseHistogramTest, HistogramNameHash) {
+ const char kName[] = "TestName";
+ HistogramBase* histogram = SparseHistogram::FactoryGet(
+ kName, HistogramBase::kUmaTargetedHistogramFlag);
+ EXPECT_EQ(histogram->name_hash(), HashMetricName(kName));
+}
+
} // namespace base
diff --git a/base/metrics/statistics_recorder.cc b/base/metrics/statistics_recorder.cc
index ba2101bccf..28773a13de 100644
--- a/base/metrics/statistics_recorder.cc
+++ b/base/metrics/statistics_recorder.cc
@@ -12,259 +12,162 @@
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_snapshot_manager.h"
#include "base/metrics/metrics_hashes.h"
#include "base/metrics/persistent_histogram_allocator.h"
+#include "base/metrics/record_histogram_checker.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
+namespace base {
namespace {
-// Initialize histogram statistics gathering system.
-base::LazyInstance<base::StatisticsRecorder>::Leaky g_statistics_recorder_ =
- LAZY_INSTANCE_INITIALIZER;
-
bool HistogramNameLesser(const base::HistogramBase* a,
const base::HistogramBase* b) {
- return a->histogram_name() < b->histogram_name();
+ return strcmp(a->histogram_name(), b->histogram_name()) < 0;
}
} // namespace
-namespace base {
-
-StatisticsRecorder::HistogramIterator::HistogramIterator(
- const HistogramMap::iterator& iter, bool include_persistent)
- : iter_(iter),
- include_persistent_(include_persistent) {
- // The starting location could point to a persistent histogram when such
- // is not wanted. If so, skip it.
- if (!include_persistent_ && iter_ != histograms_->end() &&
- (iter_->second->flags() & HistogramBase::kIsPersistent)) {
- // This operator will continue to skip until a non-persistent histogram
- // is found.
- operator++();
- }
-}
-
-StatisticsRecorder::HistogramIterator::HistogramIterator(
- const HistogramIterator& rhs)
- : iter_(rhs.iter_),
- include_persistent_(rhs.include_persistent_) {
-}
-
-StatisticsRecorder::HistogramIterator::~HistogramIterator() {}
+// static
+LazyInstance<Lock>::Leaky StatisticsRecorder::lock_;
-StatisticsRecorder::HistogramIterator&
-StatisticsRecorder::HistogramIterator::operator++() {
- const HistogramMap::iterator histograms_end = histograms_->end();
- if (iter_ == histograms_end)
- return *this;
+// static
+StatisticsRecorder* StatisticsRecorder::top_ = nullptr;
- base::AutoLock auto_lock(lock_.Get());
+// static
+bool StatisticsRecorder::is_vlog_initialized_ = false;
- for (;;) {
- ++iter_;
- if (iter_ == histograms_end)
- break;
- if (!include_persistent_ && (iter_->second->flags() &
- HistogramBase::kIsPersistent)) {
- continue;
- }
- break;
- }
+size_t StatisticsRecorder::BucketRangesHash::operator()(
+ const BucketRanges* const a) const {
+ return a->checksum();
+}
- return *this;
+bool StatisticsRecorder::BucketRangesEqual::operator()(
+ const BucketRanges* const a,
+ const BucketRanges* const b) const {
+ return a->Equals(b);
}
StatisticsRecorder::~StatisticsRecorder() {
- DCHECK(histograms_);
- DCHECK(ranges_);
-
- // Clean out what this object created and then restore what existed before.
- Reset();
- base::AutoLock auto_lock(lock_.Get());
- histograms_ = existing_histograms_.release();
- callbacks_ = existing_callbacks_.release();
- ranges_ = existing_ranges_.release();
- providers_ = existing_providers_.release();
+ const AutoLock auto_lock(lock_.Get());
+ DCHECK_EQ(this, top_);
+ top_ = previous_;
}
// static
-void StatisticsRecorder::Initialize() {
- // Tests sometimes create local StatisticsRecorders in order to provide a
- // contained environment of histograms that can be later discarded. If a
- // true global instance gets created in this environment then it will
- // eventually get disconnected when the local instance destructs and
- // restores the previous state, resulting in no StatisticsRecorder at all.
- // The global lazy instance, however, will remain valid thus ensuring that
- // another never gets installed via this method. If a |histograms_| map
- // exists then assume the StatisticsRecorder is already "initialized".
- if (histograms_)
+void StatisticsRecorder::EnsureGlobalRecorderWhileLocked() {
+ lock_.Get().AssertAcquired();
+ if (top_)
return;
- // Ensure that an instance of the StatisticsRecorder object is created.
- g_statistics_recorder_.Get();
-}
-
-// static
-bool StatisticsRecorder::IsActive() {
- base::AutoLock auto_lock(lock_.Get());
- return histograms_ != nullptr;
+ const StatisticsRecorder* const p = new StatisticsRecorder;
+ // The global recorder is never deleted.
+ ANNOTATE_LEAKING_OBJECT_PTR(p);
+ DCHECK_EQ(p, top_);
}
// static
void StatisticsRecorder::RegisterHistogramProvider(
const WeakPtr<HistogramProvider>& provider) {
- providers_->push_back(provider);
+ const AutoLock auto_lock(lock_.Get());
+ EnsureGlobalRecorderWhileLocked();
+ top_->providers_.push_back(provider);
}
// static
HistogramBase* StatisticsRecorder::RegisterOrDeleteDuplicate(
HistogramBase* histogram) {
- HistogramBase* histogram_to_delete = nullptr;
- HistogramBase* histogram_to_return = nullptr;
- {
- base::AutoLock auto_lock(lock_.Get());
- if (!histograms_) {
- histogram_to_return = histogram;
-
- // As per crbug.com/79322 the histograms are intentionally leaked, so we
- // need to annotate them. Because ANNOTATE_LEAKING_OBJECT_PTR may be used
- // only once for an object, the duplicates should not be annotated.
- // Callers are responsible for not calling RegisterOrDeleteDuplicate(ptr)
- // twice |if (!histograms_)|.
- ANNOTATE_LEAKING_OBJECT_PTR(histogram); // see crbug.com/79322
- } else {
- const std::string& name = histogram->histogram_name();
- HistogramMap::iterator it = histograms_->find(name);
- if (histograms_->end() == it) {
- // The StringKey references the name within |histogram| rather than
- // making a copy.
- (*histograms_)[name] = histogram;
- ANNOTATE_LEAKING_OBJECT_PTR(histogram); // see crbug.com/79322
- // If there are callbacks for this histogram, we set the kCallbackExists
- // flag.
- auto callback_iterator = callbacks_->find(name);
- if (callback_iterator != callbacks_->end()) {
- if (!callback_iterator->second.is_null())
- histogram->SetFlags(HistogramBase::kCallbackExists);
- else
- histogram->ClearFlags(HistogramBase::kCallbackExists);
- }
- histogram_to_return = histogram;
- } else if (histogram == it->second) {
- // The histogram was registered before.
- histogram_to_return = histogram;
- } else {
- // We already have one histogram with this name.
- DCHECK_EQ(histogram->histogram_name(),
- it->second->histogram_name()) << "hash collision";
- histogram_to_return = it->second;
- histogram_to_delete = histogram;
- }
+ // Declared before |auto_lock| to ensure correct destruction order.
+ std::unique_ptr<HistogramBase> histogram_deleter;
+ const AutoLock auto_lock(lock_.Get());
+ EnsureGlobalRecorderWhileLocked();
+
+ const char* const name = histogram->histogram_name();
+ HistogramBase*& registered = top_->histograms_[name];
+
+ if (!registered) {
+ // |name| is guaranteed to never change or be deallocated so long
+ // as the histogram is alive (which is forever).
+ registered = histogram;
+ ANNOTATE_LEAKING_OBJECT_PTR(histogram); // see crbug.com/79322
+ // If there are callbacks for this histogram, we set the kCallbackExists
+ // flag.
+ const auto callback_iterator = top_->callbacks_.find(name);
+ if (callback_iterator != top_->callbacks_.end()) {
+ if (!callback_iterator->second.is_null())
+ histogram->SetFlags(HistogramBase::kCallbackExists);
+ else
+ histogram->ClearFlags(HistogramBase::kCallbackExists);
}
+ return histogram;
}
- delete histogram_to_delete;
- return histogram_to_return;
+
+ if (histogram == registered) {
+ // The histogram was registered before.
+ return histogram;
+ }
+
+ // We already have one histogram with this name.
+ histogram_deleter.reset(histogram);
+ return registered;
}
// static
const BucketRanges* StatisticsRecorder::RegisterOrDeleteDuplicateRanges(
const BucketRanges* ranges) {
DCHECK(ranges->HasValidChecksum());
+
+ // Declared before |auto_lock| to ensure correct destruction order.
std::unique_ptr<const BucketRanges> ranges_deleter;
+ const AutoLock auto_lock(lock_.Get());
+ EnsureGlobalRecorderWhileLocked();
- base::AutoLock auto_lock(lock_.Get());
- if (!ranges_) {
+ const BucketRanges* const registered = *top_->ranges_.insert(ranges).first;
+ if (registered == ranges) {
ANNOTATE_LEAKING_OBJECT_PTR(ranges);
- return ranges;
- }
-
- std::list<const BucketRanges*>* checksum_matching_list;
- RangesMap::iterator ranges_it = ranges_->find(ranges->checksum());
- if (ranges_->end() == ranges_it) {
- // Add a new matching list to map.
- checksum_matching_list = new std::list<const BucketRanges*>();
- ANNOTATE_LEAKING_OBJECT_PTR(checksum_matching_list);
- (*ranges_)[ranges->checksum()] = checksum_matching_list;
} else {
- checksum_matching_list = ranges_it->second;
+ ranges_deleter.reset(ranges);
}
- for (const BucketRanges* existing_ranges : *checksum_matching_list) {
- if (existing_ranges->Equals(ranges)) {
- if (existing_ranges == ranges) {
- return ranges;
- } else {
- ranges_deleter.reset(ranges);
- return existing_ranges;
- }
- }
- }
- // We haven't found a BucketRanges which has the same ranges. Register the
- // new BucketRanges.
- checksum_matching_list->push_front(ranges);
- return ranges;
+ return registered;
}
// static
void StatisticsRecorder::WriteHTMLGraph(const std::string& query,
std::string* output) {
- if (!IsActive())
- return;
-
- Histograms snapshot;
- GetSnapshot(query, &snapshot);
- std::sort(snapshot.begin(), snapshot.end(), &HistogramNameLesser);
- for (const HistogramBase* histogram : snapshot) {
+ for (const HistogramBase* const histogram :
+ Sort(WithName(GetHistograms(), query))) {
histogram->WriteHTMLGraph(output);
- output->append("<br><hr><br>");
+ *output += "<br><hr><br>";
}
}
// static
void StatisticsRecorder::WriteGraph(const std::string& query,
std::string* output) {
- if (!IsActive())
- return;
if (query.length())
StringAppendF(output, "Collections of histograms for %s\n", query.c_str());
else
output->append("Collections of all histograms\n");
- Histograms snapshot;
- GetSnapshot(query, &snapshot);
- std::sort(snapshot.begin(), snapshot.end(), &HistogramNameLesser);
- for (const HistogramBase* histogram : snapshot) {
+ for (const HistogramBase* const histogram :
+ Sort(WithName(GetHistograms(), query))) {
histogram->WriteAscii(output);
output->append("\n");
}
}
// static
-std::string StatisticsRecorder::ToJSON(const std::string& query) {
- if (!IsActive())
- return std::string();
-
- std::string output("{");
- if (!query.empty()) {
- output += "\"query\":";
- EscapeJSONString(query, true, &output);
- output += ",";
- }
-
- Histograms snapshot;
- GetSnapshot(query, &snapshot);
- output += "\"histograms\":[";
- bool first_histogram = true;
- for (const HistogramBase* histogram : snapshot) {
- if (first_histogram)
- first_histogram = false;
- else
- output += ",";
+std::string StatisticsRecorder::ToJSON(JSONVerbosityLevel verbosity_level) {
+ std::string output = "{\"histograms\":[";
+ const char* sep = "";
+ for (const HistogramBase* const histogram : Sort(GetHistograms())) {
+ output += sep;
+ sep = ",";
std::string json;
- histogram->WriteJSON(&json);
+ histogram->WriteJSON(&json, verbosity_level);
output += json;
}
output += "]}";
@@ -272,28 +175,13 @@ std::string StatisticsRecorder::ToJSON(const std::string& query) {
}
// static
-void StatisticsRecorder::GetHistograms(Histograms* output) {
- base::AutoLock auto_lock(lock_.Get());
- if (!histograms_)
- return;
-
- for (const auto& entry : *histograms_) {
- output->push_back(entry.second);
- }
-}
-
-// static
-void StatisticsRecorder::GetBucketRanges(
- std::vector<const BucketRanges*>* output) {
- base::AutoLock auto_lock(lock_.Get());
- if (!ranges_)
- return;
-
- for (const auto& entry : *ranges_) {
- for (auto* range_entry : *entry.second) {
- output->push_back(range_entry);
- }
- }
+std::vector<const BucketRanges*> StatisticsRecorder::GetBucketRanges() {
+ std::vector<const BucketRanges*> out;
+ const AutoLock auto_lock(lock_.Get());
+ EnsureGlobalRecorderWhileLocked();
+ out.reserve(top_->ranges_.size());
+ out.assign(top_->ranges_.begin(), top_->ranges_.end());
+ return out;
}
// static
@@ -303,23 +191,25 @@ HistogramBase* StatisticsRecorder::FindHistogram(base::StringPiece name) {
// will acquire the lock at that time.
ImportGlobalPersistentHistograms();
- base::AutoLock auto_lock(lock_.Get());
- if (!histograms_)
- return nullptr;
+ const AutoLock auto_lock(lock_.Get());
+ EnsureGlobalRecorderWhileLocked();
- HistogramMap::iterator it = histograms_->find(name);
- if (histograms_->end() == it)
- return nullptr;
- return it->second;
+ const HistogramMap::const_iterator it = top_->histograms_.find(name);
+ return it != top_->histograms_.end() ? it->second : nullptr;
}
// static
-void StatisticsRecorder::ImportProvidedHistograms() {
- if (!providers_)
- return;
+StatisticsRecorder::HistogramProviders
+StatisticsRecorder::GetHistogramProviders() {
+ const AutoLock auto_lock(lock_.Get());
+ EnsureGlobalRecorderWhileLocked();
+ return top_->providers_;
+}
+// static
+void StatisticsRecorder::ImportProvidedHistograms() {
// Merge histogram data from each provider in turn.
- for (const WeakPtr<HistogramProvider>& provider : *providers_) {
+ for (const WeakPtr<HistogramProvider>& provider : GetHistogramProviders()) {
// Weak-pointer may be invalid if the provider was destructed, though they
// generally never are.
if (provider)
@@ -328,51 +218,22 @@ void StatisticsRecorder::ImportProvidedHistograms() {
}
// static
-StatisticsRecorder::HistogramIterator StatisticsRecorder::begin(
- bool include_persistent) {
- DCHECK(histograms_);
- ImportGlobalPersistentHistograms();
-
- HistogramMap::iterator iter_begin;
- {
- base::AutoLock auto_lock(lock_.Get());
- iter_begin = histograms_->begin();
- }
- return HistogramIterator(iter_begin, include_persistent);
-}
-
-// static
-StatisticsRecorder::HistogramIterator StatisticsRecorder::end() {
- HistogramMap::iterator iter_end;
- {
- base::AutoLock auto_lock(lock_.Get());
- iter_end = histograms_->end();
- }
- return HistogramIterator(iter_end, true);
+void StatisticsRecorder::PrepareDeltas(
+ bool include_persistent,
+ HistogramBase::Flags flags_to_set,
+ HistogramBase::Flags required_flags,
+ HistogramSnapshotManager* snapshot_manager) {
+ Histograms histograms = GetHistograms();
+ if (!include_persistent)
+ histograms = NonPersistent(std::move(histograms));
+ snapshot_manager->PrepareDeltas(Sort(std::move(histograms)), flags_to_set,
+ required_flags);
}
// static
void StatisticsRecorder::InitLogOnShutdown() {
- if (!histograms_)
- return;
-
- base::AutoLock auto_lock(lock_.Get());
- g_statistics_recorder_.Get().InitLogOnShutdownWithoutLock();
-}
-
-// static
-void StatisticsRecorder::GetSnapshot(const std::string& query,
- Histograms* snapshot) {
- base::AutoLock auto_lock(lock_.Get());
- if (!histograms_)
- return;
-
- ImportGlobalPersistentHistograms();
-
- for (const auto& entry : *histograms_) {
- if (entry.second->histogram_name().find(query) != std::string::npos)
- snapshot->push_back(entry.second);
- }
+ const AutoLock auto_lock(lock_.Get());
+ InitLogOnShutdownWhileLocked();
}
// static
@@ -380,16 +241,14 @@ bool StatisticsRecorder::SetCallback(
const std::string& name,
const StatisticsRecorder::OnSampleCallback& cb) {
DCHECK(!cb.is_null());
- base::AutoLock auto_lock(lock_.Get());
- if (!histograms_)
- return false;
+ const AutoLock auto_lock(lock_.Get());
+ EnsureGlobalRecorderWhileLocked();
- if (ContainsKey(*callbacks_, name))
+ if (!top_->callbacks_.insert({name, cb}).second)
return false;
- callbacks_->insert(std::make_pair(name, cb));
- auto it = histograms_->find(name);
- if (it != histograms_->end())
+ const HistogramMap::const_iterator it = top_->histograms_.find(name);
+ if (it != top_->histograms_.end())
it->second->SetFlags(HistogramBase::kCallbackExists);
return true;
@@ -397,146 +256,161 @@ bool StatisticsRecorder::SetCallback(
// static
void StatisticsRecorder::ClearCallback(const std::string& name) {
- base::AutoLock auto_lock(lock_.Get());
- if (!histograms_)
- return;
+ const AutoLock auto_lock(lock_.Get());
+ EnsureGlobalRecorderWhileLocked();
- callbacks_->erase(name);
+ top_->callbacks_.erase(name);
// We also clear the flag from the histogram (if it exists).
- auto it = histograms_->find(name);
- if (it != histograms_->end())
+ const HistogramMap::const_iterator it = top_->histograms_.find(name);
+ if (it != top_->histograms_.end())
it->second->ClearFlags(HistogramBase::kCallbackExists);
}
// static
StatisticsRecorder::OnSampleCallback StatisticsRecorder::FindCallback(
const std::string& name) {
- base::AutoLock auto_lock(lock_.Get());
- if (!histograms_)
- return OnSampleCallback();
-
- auto callback_iterator = callbacks_->find(name);
- return callback_iterator != callbacks_->end() ? callback_iterator->second
- : OnSampleCallback();
+ const AutoLock auto_lock(lock_.Get());
+ EnsureGlobalRecorderWhileLocked();
+ const auto it = top_->callbacks_.find(name);
+ return it != top_->callbacks_.end() ? it->second : OnSampleCallback();
}
// static
size_t StatisticsRecorder::GetHistogramCount() {
- base::AutoLock auto_lock(lock_.Get());
- if (!histograms_)
- return 0;
- return histograms_->size();
+ const AutoLock auto_lock(lock_.Get());
+ EnsureGlobalRecorderWhileLocked();
+ return top_->histograms_.size();
}
// static
void StatisticsRecorder::ForgetHistogramForTesting(base::StringPiece name) {
- if (histograms_)
- histograms_->erase(name);
+ const AutoLock auto_lock(lock_.Get());
+ EnsureGlobalRecorderWhileLocked();
+
+ const HistogramMap::iterator found = top_->histograms_.find(name);
+ if (found == top_->histograms_.end())
+ return;
+
+ HistogramBase* const base = found->second;
+ if (base->GetHistogramType() != SPARSE_HISTOGRAM) {
+ // When forgetting a histogram, it's likely that other information is
+ // also becoming invalid. Clear the persistent reference that may no
+ // longer be valid. There's no danger in this as, at worst, duplicates
+ // will be created in persistent memory.
+ static_cast<Histogram*>(base)->bucket_ranges()->set_persistent_reference(0);
+ }
+
+ top_->histograms_.erase(found);
}
// static
std::unique_ptr<StatisticsRecorder>
StatisticsRecorder::CreateTemporaryForTesting() {
+ const AutoLock auto_lock(lock_.Get());
return WrapUnique(new StatisticsRecorder());
}
// static
-void StatisticsRecorder::UninitializeForTesting() {
- // Stop now if it's never been initialized.
- if (!histograms_)
- return;
-
- // Get the global instance and destruct it. It's held in static memory so
- // can't "delete" it; call the destructor explicitly.
- DCHECK(g_statistics_recorder_.private_instance_);
- g_statistics_recorder_.Get().~StatisticsRecorder();
+void StatisticsRecorder::SetRecordChecker(
+ std::unique_ptr<RecordHistogramChecker> record_checker) {
+ const AutoLock auto_lock(lock_.Get());
+ EnsureGlobalRecorderWhileLocked();
+ top_->record_checker_ = std::move(record_checker);
+}
- // Now the ugly part. There's no official way to release a LazyInstance once
- // created so it's necessary to clear out an internal variable which
- // shouldn't be publicly visible but is for initialization reasons.
- g_statistics_recorder_.private_instance_ = 0;
+// static
+bool StatisticsRecorder::ShouldRecordHistogram(uint64_t histogram_hash) {
+ const AutoLock auto_lock(lock_.Get());
+ EnsureGlobalRecorderWhileLocked();
+ return !top_->record_checker_ ||
+ top_->record_checker_->ShouldRecord(histogram_hash);
}
// static
-void StatisticsRecorder::ImportGlobalPersistentHistograms() {
- if (!histograms_)
- return;
+StatisticsRecorder::Histograms StatisticsRecorder::GetHistograms() {
+ // This must be called *before* the lock is acquired below because it will
+ // call back into this object to register histograms. Those called methods
+ // will acquire the lock at that time.
+ ImportGlobalPersistentHistograms();
- // Import histograms from known persistent storage. Histograms could have
- // been added by other processes and they must be fetched and recognized
- // locally. If the persistent memory segment is not shared between processes,
- // this call does nothing.
- GlobalHistogramAllocator* allocator = GlobalHistogramAllocator::Get();
- if (allocator)
- allocator->ImportHistogramsToStatisticsRecorder();
-}
+ Histograms out;
-// This singleton instance should be started during the single threaded portion
-// of main(), and hence it is not thread safe. It initializes globals to
-// provide support for all future calls.
-StatisticsRecorder::StatisticsRecorder() {
- base::AutoLock auto_lock(lock_.Get());
+ const AutoLock auto_lock(lock_.Get());
+ EnsureGlobalRecorderWhileLocked();
- existing_histograms_.reset(histograms_);
- existing_callbacks_.reset(callbacks_);
- existing_ranges_.reset(ranges_);
- existing_providers_.reset(providers_);
+ out.reserve(top_->histograms_.size());
+ for (const auto& entry : top_->histograms_)
+ out.push_back(entry.second);
- histograms_ = new HistogramMap;
- callbacks_ = new CallbackMap;
- ranges_ = new RangesMap;
- providers_ = new HistogramProviders;
+ return out;
+}
- InitLogOnShutdownWithoutLock();
+// static
+StatisticsRecorder::Histograms StatisticsRecorder::Sort(Histograms histograms) {
+ std::sort(histograms.begin(), histograms.end(), &HistogramNameLesser);
+ return histograms;
}
-void StatisticsRecorder::InitLogOnShutdownWithoutLock() {
- if (!vlog_initialized_ && VLOG_IS_ON(1)) {
- vlog_initialized_ = true;
- AtExitManager::RegisterCallback(&DumpHistogramsToVlog, this);
- }
+// static
+StatisticsRecorder::Histograms StatisticsRecorder::WithName(
+ Histograms histograms,
+ const std::string& query) {
+ // Need a C-string query for comparisons against C-string histogram name.
+ const char* const query_string = query.c_str();
+ histograms.erase(std::remove_if(histograms.begin(), histograms.end(),
+ [query_string](const HistogramBase* const h) {
+ return !strstr(h->histogram_name(),
+ query_string);
+ }),
+ histograms.end());
+ return histograms;
}
// static
-void StatisticsRecorder::Reset() {
-
- std::unique_ptr<HistogramMap> histograms_deleter;
- std::unique_ptr<CallbackMap> callbacks_deleter;
- std::unique_ptr<RangesMap> ranges_deleter;
- std::unique_ptr<HistogramProviders> providers_deleter;
- {
- base::AutoLock auto_lock(lock_.Get());
- histograms_deleter.reset(histograms_);
- callbacks_deleter.reset(callbacks_);
- ranges_deleter.reset(ranges_);
- providers_deleter.reset(providers_);
- histograms_ = nullptr;
- callbacks_ = nullptr;
- ranges_ = nullptr;
- providers_ = nullptr;
- }
- // We are going to leak the histograms and the ranges.
+StatisticsRecorder::Histograms StatisticsRecorder::NonPersistent(
+ Histograms histograms) {
+ histograms.erase(
+ std::remove_if(histograms.begin(), histograms.end(),
+ [](const HistogramBase* const h) {
+ return (h->flags() & HistogramBase::kIsPersistent) != 0;
+ }),
+ histograms.end());
+ return histograms;
}
// static
-void StatisticsRecorder::DumpHistogramsToVlog(void* instance) {
- std::string output;
- StatisticsRecorder::WriteGraph(std::string(), &output);
- VLOG(1) << output;
+void StatisticsRecorder::ImportGlobalPersistentHistograms() {
+ // Import histograms from known persistent storage. Histograms could have been
+ // added by other processes and they must be fetched and recognized locally.
+ // If the persistent memory segment is not shared between processes, this call
+ // does nothing.
+ if (GlobalHistogramAllocator* allocator = GlobalHistogramAllocator::Get())
+ allocator->ImportHistogramsToStatisticsRecorder();
}
+// This singleton instance should be started during the single threaded portion
+// of main(), and hence it is not thread safe. It initializes globals to provide
+// support for all future calls.
+StatisticsRecorder::StatisticsRecorder() {
+ lock_.Get().AssertAcquired();
+ previous_ = top_;
+ top_ = this;
+ InitLogOnShutdownWhileLocked();
+}
// static
-StatisticsRecorder::HistogramMap* StatisticsRecorder::histograms_ = nullptr;
-// static
-StatisticsRecorder::CallbackMap* StatisticsRecorder::callbacks_ = nullptr;
-// static
-StatisticsRecorder::RangesMap* StatisticsRecorder::ranges_ = nullptr;
-// static
-StatisticsRecorder::HistogramProviders* StatisticsRecorder::providers_;
-// static
-base::LazyInstance<base::Lock>::Leaky StatisticsRecorder::lock_ =
- LAZY_INSTANCE_INITIALIZER;
+void StatisticsRecorder::InitLogOnShutdownWhileLocked() {
+ lock_.Get().AssertAcquired();
+ if (!is_vlog_initialized_ && VLOG_IS_ON(1)) {
+ is_vlog_initialized_ = true;
+ const auto dump_to_vlog = [](void*) {
+ std::string output;
+ WriteGraph("", &output);
+ VLOG(1) << output;
+ };
+ AtExitManager::RegisterCallback(dump_to_vlog, nullptr);
+ }
+}
} // namespace base
diff --git a/base/metrics/statistics_recorder.h b/base/metrics/statistics_recorder.h
index 55be86a85b..08137a81e6 100644
--- a/base/metrics/statistics_recorder.h
+++ b/base/metrics/statistics_recorder.h
@@ -12,10 +12,10 @@
#include <stdint.h>
-#include <list>
-#include <map>
#include <memory>
#include <string>
+#include <unordered_map>
+#include <unordered_set>
#include <vector>
#include "base/base_export.h"
@@ -25,44 +25,28 @@
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_base.h"
+#include "base/metrics/record_histogram_checker.h"
#include "base/strings/string_piece.h"
#include "base/synchronization/lock.h"
namespace base {
class BucketRanges;
-
+class HistogramSnapshotManager;
+
+// In-memory recorder of usage statistics (aka metrics, aka histograms).
+//
+// All the public methods are static and act on a global recorder. This global
+// recorder is internally synchronized and all the static methods are thread
+// safe.
+//
+// StatisticsRecorder doesn't have any public constructor. For testing purpose,
+// you can create a temporary recorder using the factory method
+// CreateTemporaryForTesting(). This temporary recorder becomes the global one
+// until deleted. When this temporary recorder is deleted, it restores the
+// previous global one.
class BASE_EXPORT StatisticsRecorder {
public:
- // A class used as a key for the histogram map below. It always references
- // a string owned outside of this class, likely in the value of the map.
- class StringKey : public StringPiece {
- public:
- // Constructs the StringKey using various sources. The source must live
- // at least as long as the created object.
- StringKey(const std::string& str) : StringPiece(str) {}
- StringKey(StringPiece str) : StringPiece(str) {}
-
- // Though StringPiece is better passed by value than by reference, in
- // this case it's being passed many times and likely already been stored
- // in memory (not just registers) so the benefit of pass-by-value is
- // negated.
- bool operator<(const StringKey& rhs) const {
- // Since order is unimportant in the map and string comparisons can be
- // slow, use the length as the primary sort value.
- if (length() < rhs.length())
- return true;
- if (length() > rhs.length())
- return false;
-
- // Fall back to an actual string comparison. The lengths are the same
- // so a simple memory-compare is sufficient. This is slightly more
- // efficient than calling operator<() for StringPiece which would
- // again have to check lengths before calling wordmemcmp().
- return wordmemcmp(data(), rhs.data(), length()) < 0;
- }
- };
-
// An interface class that allows the StatisticsRecorder to forcibly merge
// histograms from providers when necessary.
class HistogramProvider {
@@ -72,191 +56,244 @@ class BASE_EXPORT StatisticsRecorder {
virtual void MergeHistogramDeltas() = 0;
};
- typedef std::map<StringKey, HistogramBase*> HistogramMap;
typedef std::vector<HistogramBase*> Histograms;
- typedef std::vector<WeakPtr<HistogramProvider>> HistogramProviders;
-
- // A class for iterating over the histograms held within this global resource.
- class BASE_EXPORT HistogramIterator {
- public:
- HistogramIterator(const HistogramMap::iterator& iter,
- bool include_persistent);
- HistogramIterator(const HistogramIterator& rhs); // Must be copyable.
- ~HistogramIterator();
-
- HistogramIterator& operator++();
- HistogramIterator operator++(int) {
- HistogramIterator tmp(*this);
- operator++();
- return tmp;
- }
-
- bool operator==(const HistogramIterator& rhs) const {
- return iter_ == rhs.iter_;
- }
- bool operator!=(const HistogramIterator& rhs) const {
- return iter_ != rhs.iter_;
- }
- HistogramBase* operator*() { return iter_->second; }
-
- private:
- HistogramMap::iterator iter_;
- const bool include_persistent_;
- };
+ // Restores the previous global recorder.
+ //
+ // When several temporary recorders are created using
+ // CreateTemporaryForTesting(), these recorders must be deleted in reverse
+ // order of creation.
+ //
+ // This method is thread safe.
+ //
+ // Precondition: The recorder being deleted is the current global recorder.
~StatisticsRecorder();
- // Initializes the StatisticsRecorder system. Safe to call multiple times.
- static void Initialize();
-
- // Find out if histograms can now be registered into our list.
- static bool IsActive();
-
- // Register a provider of histograms that can be called to merge those into
- // the global StatisticsRecorder. Calls to ImportProvidedHistograms() will
- // fetch from registered providers.
+ // Registers a provider of histograms that can be called to merge those into
+ // the global recorder. Calls to ImportProvidedHistograms() will fetch from
+ // registered providers.
+ //
+ // This method is thread safe.
static void RegisterHistogramProvider(
const WeakPtr<HistogramProvider>& provider);
- // Register, or add a new histogram to the collection of statistics. If an
+ // Registers or adds a new histogram to the collection of statistics. If an
// identically named histogram is already registered, then the argument
- // |histogram| will deleted. The returned value is always the registered
+ // |histogram| will be deleted. The returned value is always the registered
// histogram (either the argument, or the pre-existing registered histogram).
+ //
+ // This method is thread safe.
static HistogramBase* RegisterOrDeleteDuplicate(HistogramBase* histogram);
- // Register, or add a new BucketRanges. If an identically BucketRanges is
- // already registered, then the argument |ranges| will deleted. The returned
- // value is always the registered BucketRanges (either the argument, or the
- // pre-existing one).
+ // Registers or adds a new BucketRanges. If an equivalent BucketRanges is
+ // already registered, then the argument |ranges| will be deleted. The
+ // returned value is always the registered BucketRanges (either the argument,
+ // or the pre-existing one).
+ //
+ // This method is thread safe.
static const BucketRanges* RegisterOrDeleteDuplicateRanges(
const BucketRanges* ranges);
// Methods for appending histogram data to a string. Only histograms which
// have |query| as a substring are written to |output| (an empty string will
// process all registered histograms).
+ //
+ // These methods are thread safe.
static void WriteHTMLGraph(const std::string& query, std::string* output);
static void WriteGraph(const std::string& query, std::string* output);
- // Returns the histograms with |query| as a substring as JSON text (an empty
- // |query| will process all registered histograms).
- static std::string ToJSON(const std::string& query);
-
- // Method for extracting histograms which were marked for use by UMA.
- static void GetHistograms(Histograms* output);
-
- // Method for extracting BucketRanges used by all histograms registered.
- static void GetBucketRanges(std::vector<const BucketRanges*>* output);
-
- // Find a histogram by name. It matches the exact name. This method is thread
- // safe. It returns NULL if a matching histogram is not found.
+ // Returns the histograms with |verbosity_level| as the serialization
+ // verbosity.
+ //
+ // This method is thread safe.
+ static std::string ToJSON(JSONVerbosityLevel verbosity_level);
+
+ // Gets existing histograms.
+ //
+ // The order of returned histograms is not guaranteed.
+ //
+ // Ownership of the individual histograms remains with the StatisticsRecorder.
+ //
+ // This method is thread safe.
+ static Histograms GetHistograms();
+
+ // Gets BucketRanges used by all histograms registered. The order of returned
+ // BucketRanges is not guaranteed.
+ //
+ // This method is thread safe.
+ static std::vector<const BucketRanges*> GetBucketRanges();
+
+ // Finds a histogram by name. Matches the exact name. Returns a null pointer
+ // if a matching histogram is not found.
+ //
+ // This method is thread safe.
static HistogramBase* FindHistogram(base::StringPiece name);
- // Imports histograms from providers. This must be called on the UI thread.
+ // Imports histograms from providers.
+ //
+ // This method must be called on the UI thread.
static void ImportProvidedHistograms();
- // Support for iterating over known histograms.
- static HistogramIterator begin(bool include_persistent);
- static HistogramIterator end();
-
- // GetSnapshot copies some of the pointers to registered histograms into the
- // caller supplied vector (Histograms). Only histograms which have |query| as
- // a substring are copied (an empty string will process all registered
- // histograms).
- static void GetSnapshot(const std::string& query, Histograms* snapshot);
+ // Snapshots all histograms via |snapshot_manager|. |flags_to_set| is used to
+ // set flags for each histogram. |required_flags| is used to select
+ // histograms to be recorded. Only histograms that have all the flags
+ // specified by the argument will be chosen. If all histograms should be
+ // recorded, set it to |Histogram::kNoFlags|.
+ static void PrepareDeltas(bool include_persistent,
+ HistogramBase::Flags flags_to_set,
+ HistogramBase::Flags required_flags,
+ HistogramSnapshotManager* snapshot_manager);
typedef base::Callback<void(HistogramBase::Sample)> OnSampleCallback;
- // SetCallback sets the callback to notify when a new sample is recorded on
- // the histogram referred to by |histogram_name|. The call to this method can
- // be be done before or after the histogram is created. This method is thread
- // safe. The return value is whether or not the callback was successfully set.
+ // Sets the callback to notify when a new sample is recorded on the histogram
+ // referred to by |histogram_name|. Can be called before or after the
+ // histogram is created. Returns whether the callback was successfully set.
+ //
+ // This method is thread safe.
static bool SetCallback(const std::string& histogram_name,
const OnSampleCallback& callback);
- // ClearCallback clears any callback set on the histogram referred to by
- // |histogram_name|. This method is thread safe.
+ // Clears any callback set on the histogram referred to by |histogram_name|.
+ //
+ // This method is thread safe.
static void ClearCallback(const std::string& histogram_name);
- // FindCallback retrieves the callback for the histogram referred to by
- // |histogram_name|, or a null callback if no callback exists for this
- // histogram. This method is thread safe.
+ // Retrieves the callback for the histogram referred to by |histogram_name|,
+ // or a null callback if no callback exists for this histogram.
+ //
+ // This method is thread safe.
static OnSampleCallback FindCallback(const std::string& histogram_name);
// Returns the number of known histograms.
+ //
+ // This method is thread safe.
static size_t GetHistogramCount();
// Initializes logging histograms with --v=1. Safe to call multiple times.
// Is called from ctor but for browser it seems that it is more useful to
// start logging after statistics recorder, so we need to init log-on-shutdown
// later.
+ //
+ // This method is thread safe.
static void InitLogOnShutdown();
// Removes a histogram from the internal set of known ones. This can be
// necessary during testing persistent histograms where the underlying
// memory is being released.
+ //
+ // This method is thread safe.
static void ForgetHistogramForTesting(base::StringPiece name);
- // Creates a local StatisticsRecorder object for testing purposes. All new
- // histograms will be registered in it until it is destructed or pushed
- // aside for the lifetime of yet another SR object. The destruction of the
- // returned object will re-activate the previous one. Always release SR
- // objects in the opposite order to which they're created.
+ // Creates a temporary StatisticsRecorder object for testing purposes. All new
+ // histograms will be registered in it until it is destructed or pushed aside
+ // for the lifetime of yet another StatisticsRecorder object. The destruction
+ // of the returned object will re-activate the previous one.
+ // StatisticsRecorder objects must be deleted in the opposite order to which
+ // they're created.
+ //
+ // This method is thread safe.
static std::unique_ptr<StatisticsRecorder> CreateTemporaryForTesting()
WARN_UNUSED_RESULT;
- // Resets any global instance of the statistics-recorder that was created
- // by a call to Initialize().
- static void UninitializeForTesting();
+ // Sets the record checker for determining if a histogram should be recorded.
+ // Record checker doesn't affect any already recorded histograms, so this
+ // method must be called very early, before any threads have started.
+ // Record checker methods can be called on any thread, so they shouldn't
+ // mutate any state.
+ static void SetRecordChecker(
+ std::unique_ptr<RecordHistogramChecker> record_checker);
+
+ // Checks if the given histogram should be recorded based on the
+ // ShouldRecord() method of the record checker. If the record checker is not
+ // set, returns true.
+ //
+ // This method is thread safe.
+ static bool ShouldRecordHistogram(uint64_t histogram_hash);
+
+ // Sorts histograms by name.
+ static Histograms Sort(Histograms histograms);
+
+ // Filters histograms by name. Only histograms which have |query| as a
+ // substring in their name are kept. An empty query keeps all histograms.
+ static Histograms WithName(Histograms histograms, const std::string& query);
+
+ // Filters histograms by persistency. Only non-persistent histograms are kept.
+ static Histograms NonPersistent(Histograms histograms);
private:
+ typedef std::vector<WeakPtr<HistogramProvider>> HistogramProviders;
+
+ typedef std::unordered_map<StringPiece, HistogramBase*, StringPieceHash>
+ HistogramMap;
+
// We keep a map of callbacks to histograms, so that as histograms are
// created, we can set the callback properly.
- typedef std::map<std::string, OnSampleCallback> CallbackMap;
+ typedef std::unordered_map<std::string, OnSampleCallback> CallbackMap;
- // We keep all |bucket_ranges_| in a map, from checksum to a list of
- // |bucket_ranges_|. Checksum is calculated from the |ranges_| in
- // |bucket_ranges_|.
- typedef std::map<uint32_t, std::list<const BucketRanges*>*> RangesMap;
+ struct BucketRangesHash {
+ size_t operator()(const BucketRanges* a) const;
+ };
- friend struct LazyInstanceTraitsBase<StatisticsRecorder>;
- friend class StatisticsRecorderTest;
+ struct BucketRangesEqual {
+ bool operator()(const BucketRanges* a, const BucketRanges* b) const;
+ };
+
+ typedef std::
+ unordered_set<const BucketRanges*, BucketRangesHash, BucketRangesEqual>
+ RangesMap;
- // Imports histograms from global persistent memory. The global lock must
- // not be held during this call.
+ friend class StatisticsRecorderTest;
+ FRIEND_TEST_ALL_PREFIXES(StatisticsRecorderTest, IterationTest);
+
+ // Initializes the global recorder if it doesn't already exist. Safe to call
+ // multiple times.
+ //
+ // Precondition: The global lock is already acquired.
+ static void EnsureGlobalRecorderWhileLocked();
+
+ // Gets histogram providers.
+ //
+ // This method is thread safe.
+ static HistogramProviders GetHistogramProviders();
+
+ // Imports histograms from global persistent memory.
+ //
+ // Precondition: The global lock must not be held during this call.
static void ImportGlobalPersistentHistograms();
- // The constructor just initializes static members. Usually client code should
- // use Initialize to do this. But in test code, you can friend this class and
- // call the constructor to get a clean StatisticsRecorder.
+ // Constructs a new StatisticsRecorder and sets it as the current global
+ // recorder.
+ //
+ // Precondition: The global lock is already acquired.
StatisticsRecorder();
// Initialize implementation but without lock. Caller should guard
// StatisticsRecorder by itself if needed (it isn't in unit tests).
- void InitLogOnShutdownWithoutLock();
-
- // These are copies of everything that existed when the (test) Statistics-
- // Recorder was created. The global ones have to be moved aside to create a
- // clean environment.
- std::unique_ptr<HistogramMap> existing_histograms_;
- std::unique_ptr<CallbackMap> existing_callbacks_;
- std::unique_ptr<RangesMap> existing_ranges_;
- std::unique_ptr<HistogramProviders> existing_providers_;
-
- bool vlog_initialized_ = false;
-
- static void Reset();
- static void DumpHistogramsToVlog(void* instance);
-
- static HistogramMap* histograms_;
- static CallbackMap* callbacks_;
- static RangesMap* ranges_;
- static HistogramProviders* providers_;
-
- // Lock protects access to above maps. This is a LazyInstance to avoid races
- // when the above methods are used before Initialize(). Previously each method
- // would do |if (!lock_) return;| which would race with
- // |lock_ = new Lock;| in StatisticsRecorder(). http://crbug.com/672852.
- static base::LazyInstance<base::Lock>::Leaky lock_;
+ //
+ // Precondition: The global lock is already acquired.
+ static void InitLogOnShutdownWhileLocked();
+
+ HistogramMap histograms_;
+ CallbackMap callbacks_;
+ RangesMap ranges_;
+ HistogramProviders providers_;
+ std::unique_ptr<RecordHistogramChecker> record_checker_;
+
+ // Previous global recorder that existed when this one was created.
+ StatisticsRecorder* previous_ = nullptr;
+
+ // Global lock for internal synchronization.
+ static LazyInstance<Lock>::Leaky lock_;
+
+ // Current global recorder. This recorder is used by static methods. When a
+ // new global recorder is created by CreateTemporaryForTesting(), then the
+ // previous global recorder is referenced by top_->previous_.
+ static StatisticsRecorder* top_;
+
+ // Tracks whether InitLogOnShutdownWhileLocked() has registered a logging
+ // function that will be called when the program finishes.
+ static bool is_vlog_initialized_;
DISALLOW_COPY_AND_ASSIGN(StatisticsRecorder);
};
diff --git a/base/metrics/statistics_recorder_unittest.cc b/base/metrics/statistics_recorder_unittest.cc
index 48b6df3068..a65283cfa0 100644
--- a/base/metrics/statistics_recorder_unittest.cc
+++ b/base/metrics/statistics_recorder_unittest.cc
@@ -7,16 +7,20 @@
#include <stddef.h>
#include <memory>
+#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
+#include "base/metrics/histogram_base.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/persistent_histogram_allocator.h"
+#include "base/metrics/record_histogram_checker.h"
#include "base/metrics/sparse_histogram.h"
#include "base/values.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
@@ -27,10 +31,7 @@ class LogStateSaver {
public:
LogStateSaver() : old_min_log_level_(logging::GetMinLogLevel()) {}
- ~LogStateSaver() {
- logging::SetMinLogLevel(old_min_log_level_);
- logging::SetLogAssertHandler(nullptr);
- }
+ ~LogStateSaver() { logging::SetMinLogLevel(old_min_log_level_); }
private:
int old_min_log_level_;
@@ -38,27 +39,38 @@ class LogStateSaver {
DISALLOW_COPY_AND_ASSIGN(LogStateSaver);
};
+// Test implementation of RecordHistogramChecker interface.
+class OddRecordHistogramChecker : public base::RecordHistogramChecker {
+ public:
+ ~OddRecordHistogramChecker() override = default;
+
+ // base::RecordHistogramChecker:
+ bool ShouldRecord(uint64_t histogram_hash) const override {
+ return histogram_hash % 2;
+ }
+};
+
} // namespace
namespace base {
+using testing::IsEmpty;
+using testing::SizeIs;
+using testing::UnorderedElementsAre;
+
class StatisticsRecorderTest : public testing::TestWithParam<bool> {
protected:
const int32_t kAllocatorMemorySize = 64 << 10; // 64 KiB
StatisticsRecorderTest() : use_persistent_histogram_allocator_(GetParam()) {
- // Get this first so it never gets created in persistent storage and will
- // not appear in the StatisticsRecorder after it is re-initialized.
- PersistentHistogramAllocator::GetCreateHistogramResultHistogram();
-
// Each test will have a clean state (no Histogram / BucketRanges
// registered).
InitializeStatisticsRecorder();
// Use persistent memory for histograms if so indicated by test parameter.
if (use_persistent_histogram_allocator_) {
- GlobalHistogramAllocator::CreateWithLocalMemory(
- kAllocatorMemorySize, 0, "StatisticsRecorderTest");
+ GlobalHistogramAllocator::CreateWithLocalMemory(kAllocatorMemorySize, 0,
+ "StatisticsRecorderTest");
}
}
@@ -69,16 +81,20 @@ class StatisticsRecorderTest : public testing::TestWithParam<bool> {
void InitializeStatisticsRecorder() {
DCHECK(!statistics_recorder_);
- StatisticsRecorder::UninitializeForTesting();
statistics_recorder_ = StatisticsRecorder::CreateTemporaryForTesting();
}
+ // Deletes the global recorder if there is any. This is used by test
+ // NotInitialized to ensure a clean global state.
void UninitializeStatisticsRecorder() {
statistics_recorder_.reset();
- StatisticsRecorder::UninitializeForTesting();
+ delete StatisticsRecorder::top_;
+ DCHECK(!StatisticsRecorder::top_);
}
- Histogram* CreateHistogram(const std::string& name,
+ bool HasGlobalRecorder() { return StatisticsRecorder::top_ != nullptr; }
+
+ Histogram* CreateHistogram(const char* name,
HistogramBase::Sample min,
HistogramBase::Sample max,
size_t bucket_count) {
@@ -89,26 +105,13 @@ class StatisticsRecorderTest : public testing::TestWithParam<bool> {
return new Histogram(name, min, max, registered_ranges);
}
- void DeleteHistogram(HistogramBase* histogram) {
- delete histogram;
- }
+ void InitLogOnShutdown() { StatisticsRecorder::InitLogOnShutdown(); }
- int CountIterableHistograms(StatisticsRecorder::HistogramIterator* iter) {
- int count = 0;
- for (; *iter != StatisticsRecorder::end(); ++*iter) {
- ++count;
- }
- return count;
- }
+ bool IsVLogInitialized() { return StatisticsRecorder::is_vlog_initialized_; }
- void InitLogOnShutdown() {
- DCHECK(statistics_recorder_);
- statistics_recorder_->InitLogOnShutdownWithoutLock();
- }
-
- bool VLogInitialized() {
- DCHECK(statistics_recorder_);
- return statistics_recorder_->vlog_initialized_;
+ void ResetVLogInitialized() {
+ UninitializeStatisticsRecorder();
+ StatisticsRecorder::is_vlog_initialized_ = false;
}
const bool use_persistent_histogram_allocator_;
@@ -127,30 +130,26 @@ INSTANTIATE_TEST_CASE_P(Allocator, StatisticsRecorderTest, testing::Bool());
TEST_P(StatisticsRecorderTest, NotInitialized) {
UninitializeStatisticsRecorder();
+ EXPECT_FALSE(HasGlobalRecorder());
- ASSERT_FALSE(StatisticsRecorder::IsActive());
-
- StatisticsRecorder::Histograms registered_histograms;
- std::vector<const BucketRanges*> registered_ranges;
-
- StatisticsRecorder::GetHistograms(&registered_histograms);
- EXPECT_EQ(0u, registered_histograms.size());
+ HistogramBase* const histogram =
+ CreateHistogram("TestHistogram", 1, 1000, 10);
+ EXPECT_EQ(StatisticsRecorder::RegisterOrDeleteDuplicate(histogram),
+ histogram);
+ EXPECT_TRUE(HasGlobalRecorder());
+ EXPECT_THAT(StatisticsRecorder::GetHistograms(),
+ UnorderedElementsAre(histogram));
- Histogram* histogram = CreateHistogram("TestHistogram", 1, 1000, 10);
-
- // When StatisticsRecorder is not initialized, register is a noop.
- EXPECT_EQ(histogram,
- StatisticsRecorder::RegisterOrDeleteDuplicate(histogram));
- // Manually delete histogram that was not registered.
- DeleteHistogram(histogram);
+ UninitializeStatisticsRecorder();
+ EXPECT_FALSE(HasGlobalRecorder());
- // RegisterOrDeleteDuplicateRanges is a no-op.
- BucketRanges* ranges = new BucketRanges(3);
+ BucketRanges* const ranges = new BucketRanges(3);
ranges->ResetChecksum();
- EXPECT_EQ(ranges,
- StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges));
- StatisticsRecorder::GetBucketRanges(&registered_ranges);
- EXPECT_EQ(0u, registered_ranges.size());
+ EXPECT_EQ(StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges),
+ ranges);
+ EXPECT_TRUE(HasGlobalRecorder());
+ EXPECT_THAT(StatisticsRecorder::GetBucketRanges(),
+ UnorderedElementsAre(ranges));
}
TEST_P(StatisticsRecorderTest, RegisterBucketRanges) {
@@ -166,15 +165,15 @@ TEST_P(StatisticsRecorderTest, RegisterBucketRanges) {
StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges1));
EXPECT_EQ(ranges2,
StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges2));
- StatisticsRecorder::GetBucketRanges(&registered_ranges);
- ASSERT_EQ(2u, registered_ranges.size());
+ EXPECT_THAT(StatisticsRecorder::GetBucketRanges(),
+ UnorderedElementsAre(ranges1, ranges2));
// Register some ranges again.
EXPECT_EQ(ranges1,
StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges1));
- registered_ranges.clear();
- StatisticsRecorder::GetBucketRanges(&registered_ranges);
- ASSERT_EQ(2u, registered_ranges.size());
+ EXPECT_THAT(StatisticsRecorder::GetBucketRanges(),
+ UnorderedElementsAre(ranges1, ranges2));
+
// Make sure the ranges is still the one we know.
ASSERT_EQ(3u, ranges1->size());
EXPECT_EQ(0, ranges1->range(0));
@@ -186,31 +185,43 @@ TEST_P(StatisticsRecorderTest, RegisterBucketRanges) {
ranges3->ResetChecksum();
EXPECT_EQ(ranges1, // returning ranges1
StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges3));
- registered_ranges.clear();
- StatisticsRecorder::GetBucketRanges(&registered_ranges);
- ASSERT_EQ(2u, registered_ranges.size());
+ EXPECT_THAT(StatisticsRecorder::GetBucketRanges(),
+ UnorderedElementsAre(ranges1, ranges2));
}
TEST_P(StatisticsRecorderTest, RegisterHistogram) {
// Create a Histogram that was not registered.
- Histogram* histogram = CreateHistogram("TestHistogram", 1, 1000, 10);
+ Histogram* const histogram1 = CreateHistogram("TestHistogram1", 1, 1000, 10);
- StatisticsRecorder::Histograms registered_histograms;
- StatisticsRecorder::GetHistograms(&registered_histograms);
- EXPECT_EQ(0u, registered_histograms.size());
+ EXPECT_THAT(StatisticsRecorder::GetHistograms(), IsEmpty());
// Register the Histogram.
- EXPECT_EQ(histogram,
- StatisticsRecorder::RegisterOrDeleteDuplicate(histogram));
- StatisticsRecorder::GetHistograms(&registered_histograms);
- EXPECT_EQ(1u, registered_histograms.size());
+ EXPECT_EQ(histogram1,
+ StatisticsRecorder::RegisterOrDeleteDuplicate(histogram1));
+ EXPECT_THAT(StatisticsRecorder::GetHistograms(),
+ UnorderedElementsAre(histogram1));
// Register the same Histogram again.
- EXPECT_EQ(histogram,
- StatisticsRecorder::RegisterOrDeleteDuplicate(histogram));
- registered_histograms.clear();
- StatisticsRecorder::GetHistograms(&registered_histograms);
- EXPECT_EQ(1u, registered_histograms.size());
+ EXPECT_EQ(histogram1,
+ StatisticsRecorder::RegisterOrDeleteDuplicate(histogram1));
+ EXPECT_THAT(StatisticsRecorder::GetHistograms(),
+ UnorderedElementsAre(histogram1));
+
+ // Register another Histogram with the same name.
+ Histogram* const histogram2 = CreateHistogram("TestHistogram1", 1, 1000, 10);
+ EXPECT_NE(histogram1, histogram2);
+ EXPECT_EQ(histogram1,
+ StatisticsRecorder::RegisterOrDeleteDuplicate(histogram2));
+ EXPECT_THAT(StatisticsRecorder::GetHistograms(),
+ UnorderedElementsAre(histogram1));
+
+ // Register another Histogram with a different name.
+ Histogram* const histogram3 = CreateHistogram("TestHistogram0", 1, 1000, 10);
+ EXPECT_NE(histogram1, histogram3);
+ EXPECT_EQ(histogram3,
+ StatisticsRecorder::RegisterOrDeleteDuplicate(histogram3));
+ EXPECT_THAT(StatisticsRecorder::GetHistograms(),
+ UnorderedElementsAre(histogram1, histogram3));
}
TEST_P(StatisticsRecorderTest, FindHistogram) {
@@ -247,68 +258,56 @@ TEST_P(StatisticsRecorderTest, FindHistogram) {
EXPECT_FALSE(StatisticsRecorder::FindHistogram("TestHistogram"));
}
-TEST_P(StatisticsRecorderTest, GetSnapshot) {
+TEST_P(StatisticsRecorderTest, WithName) {
Histogram::FactoryGet("TestHistogram1", 1, 1000, 10, Histogram::kNoFlags);
Histogram::FactoryGet("TestHistogram2", 1, 1000, 10, Histogram::kNoFlags);
Histogram::FactoryGet("TestHistogram3", 1, 1000, 10, Histogram::kNoFlags);
- StatisticsRecorder::Histograms snapshot;
- StatisticsRecorder::GetSnapshot("Test", &snapshot);
- EXPECT_EQ(3u, snapshot.size());
-
- snapshot.clear();
- StatisticsRecorder::GetSnapshot("1", &snapshot);
- EXPECT_EQ(1u, snapshot.size());
-
- snapshot.clear();
- StatisticsRecorder::GetSnapshot("hello", &snapshot);
- EXPECT_EQ(0u, snapshot.size());
+ const auto histograms = StatisticsRecorder::GetHistograms();
+ EXPECT_THAT(histograms, SizeIs(3));
+ EXPECT_THAT(StatisticsRecorder::WithName(histograms, ""), SizeIs(3));
+ EXPECT_THAT(StatisticsRecorder::WithName(histograms, "Test"), SizeIs(3));
+ EXPECT_THAT(StatisticsRecorder::WithName(histograms, "1"), SizeIs(1));
+ EXPECT_THAT(StatisticsRecorder::WithName(histograms, "hello"), IsEmpty());
}
TEST_P(StatisticsRecorderTest, RegisterHistogramWithFactoryGet) {
- StatisticsRecorder::Histograms registered_histograms;
-
- StatisticsRecorder::GetHistograms(&registered_histograms);
- ASSERT_EQ(0u, registered_histograms.size());
+ EXPECT_THAT(StatisticsRecorder::GetHistograms(), IsEmpty());
// Create a histogram.
- HistogramBase* histogram = Histogram::FactoryGet(
+ HistogramBase* const histogram1 = Histogram::FactoryGet(
"TestHistogram", 1, 1000, 10, HistogramBase::kNoFlags);
- registered_histograms.clear();
- StatisticsRecorder::GetHistograms(&registered_histograms);
- EXPECT_EQ(1u, registered_histograms.size());
+ EXPECT_THAT(StatisticsRecorder::GetHistograms(),
+ UnorderedElementsAre(histogram1));
// Get an existing histogram.
- HistogramBase* histogram2 = Histogram::FactoryGet(
+ HistogramBase* const histogram2 = Histogram::FactoryGet(
"TestHistogram", 1, 1000, 10, HistogramBase::kNoFlags);
- registered_histograms.clear();
- StatisticsRecorder::GetHistograms(&registered_histograms);
- EXPECT_EQ(1u, registered_histograms.size());
- EXPECT_EQ(histogram, histogram2);
+ EXPECT_EQ(histogram1, histogram2);
+ EXPECT_THAT(StatisticsRecorder::GetHistograms(),
+ UnorderedElementsAre(histogram1));
// Create a LinearHistogram.
- histogram = LinearHistogram::FactoryGet(
+ HistogramBase* const histogram3 = LinearHistogram::FactoryGet(
"TestLinearHistogram", 1, 1000, 10, HistogramBase::kNoFlags);
- registered_histograms.clear();
- StatisticsRecorder::GetHistograms(&registered_histograms);
- EXPECT_EQ(2u, registered_histograms.size());
+ EXPECT_THAT(StatisticsRecorder::GetHistograms(),
+ UnorderedElementsAre(histogram1, histogram3));
// Create a BooleanHistogram.
- histogram = BooleanHistogram::FactoryGet(
+ HistogramBase* const histogram4 = BooleanHistogram::FactoryGet(
"TestBooleanHistogram", HistogramBase::kNoFlags);
- registered_histograms.clear();
- StatisticsRecorder::GetHistograms(&registered_histograms);
- EXPECT_EQ(3u, registered_histograms.size());
+ EXPECT_THAT(StatisticsRecorder::GetHistograms(),
+ UnorderedElementsAre(histogram1, histogram3, histogram4));
// Create a CustomHistogram.
std::vector<int> custom_ranges;
custom_ranges.push_back(1);
custom_ranges.push_back(5);
- histogram = CustomHistogram::FactoryGet(
+ HistogramBase* const histogram5 = CustomHistogram::FactoryGet(
"TestCustomHistogram", custom_ranges, HistogramBase::kNoFlags);
- registered_histograms.clear();
- StatisticsRecorder::GetHistograms(&registered_histograms);
- EXPECT_EQ(4u, registered_histograms.size());
+ EXPECT_THAT(
+ StatisticsRecorder::GetHistograms(),
+ UnorderedElementsAre(histogram1, histogram3, histogram4, histogram5));
}
TEST_P(StatisticsRecorderTest, RegisterHistogramWithMacros) {
@@ -326,35 +325,25 @@ TEST_P(StatisticsRecorderTest, RegisterHistogramWithMacros) {
// The histogram we got from macro is the same as from FactoryGet.
LOCAL_HISTOGRAM_COUNTS("TestHistogramCounts", 30);
- registered_histograms.clear();
- StatisticsRecorder::GetHistograms(&registered_histograms);
+ registered_histograms = StatisticsRecorder::GetHistograms();
ASSERT_EQ(1u, registered_histograms.size());
EXPECT_EQ(histogram, registered_histograms[0]);
LOCAL_HISTOGRAM_TIMES("TestHistogramTimes", TimeDelta::FromDays(1));
LOCAL_HISTOGRAM_ENUMERATION("TestHistogramEnumeration", 20, 200);
- registered_histograms.clear();
- StatisticsRecorder::GetHistograms(&registered_histograms);
- EXPECT_EQ(3u, registered_histograms.size());
+ EXPECT_THAT(StatisticsRecorder::GetHistograms(), SizeIs(3));
}
TEST_P(StatisticsRecorderTest, BucketRangesSharing) {
- std::vector<const BucketRanges*> ranges;
- StatisticsRecorder::GetBucketRanges(&ranges);
- EXPECT_EQ(0u, ranges.size());
+ EXPECT_THAT(StatisticsRecorder::GetBucketRanges(), IsEmpty());
Histogram::FactoryGet("Histogram", 1, 64, 8, HistogramBase::kNoFlags);
Histogram::FactoryGet("Histogram2", 1, 64, 8, HistogramBase::kNoFlags);
-
- StatisticsRecorder::GetBucketRanges(&ranges);
- EXPECT_EQ(1u, ranges.size());
+ EXPECT_THAT(StatisticsRecorder::GetBucketRanges(), SizeIs(1));
Histogram::FactoryGet("Histogram3", 1, 64, 16, HistogramBase::kNoFlags);
-
- ranges.clear();
- StatisticsRecorder::GetBucketRanges(&ranges);
- EXPECT_EQ(2u, ranges.size());
+ EXPECT_THAT(StatisticsRecorder::GetBucketRanges(), SizeIs(2));
}
TEST_P(StatisticsRecorderTest, ToJSON) {
@@ -367,69 +356,61 @@ TEST_P(StatisticsRecorderTest, ToJSON) {
Histogram::FactoryGet("TestHistogram2", 1, 1000, 50, HistogramBase::kNoFlags)
->Add(40);
- std::string json(StatisticsRecorder::ToJSON(std::string()));
+ std::string json(StatisticsRecorder::ToJSON(JSON_VERBOSITY_LEVEL_FULL));
// Check for valid JSON.
std::unique_ptr<Value> root = JSONReader::Read(json);
ASSERT_TRUE(root.get());
- DictionaryValue* root_dict = NULL;
+ DictionaryValue* root_dict = nullptr;
ASSERT_TRUE(root->GetAsDictionary(&root_dict));
// No query should be set.
ASSERT_FALSE(root_dict->HasKey("query"));
- ListValue* histogram_list = NULL;
+ ListValue* histogram_list = nullptr;
ASSERT_TRUE(root_dict->GetList("histograms", &histogram_list));
ASSERT_EQ(2u, histogram_list->GetSize());
// Examine the first histogram.
- DictionaryValue* histogram_dict = NULL;
+ DictionaryValue* histogram_dict = nullptr;
ASSERT_TRUE(histogram_list->GetDictionary(0, &histogram_dict));
int sample_count;
ASSERT_TRUE(histogram_dict->GetInteger("count", &sample_count));
EXPECT_EQ(2, sample_count);
- // Test the query filter.
- std::string query("TestHistogram2");
- json = StatisticsRecorder::ToJSON(query);
+ ListValue* buckets_list = nullptr;
+ ASSERT_TRUE(histogram_dict->GetList("buckets", &buckets_list));
+ EXPECT_EQ(2u, buckets_list->GetList().size());
+ // Check the serialized JSON with a different verbosity level.
+ json = StatisticsRecorder::ToJSON(JSON_VERBOSITY_LEVEL_OMIT_BUCKETS);
root = JSONReader::Read(json);
ASSERT_TRUE(root.get());
+ root_dict = nullptr;
ASSERT_TRUE(root->GetAsDictionary(&root_dict));
-
- std::string query_value;
- ASSERT_TRUE(root_dict->GetString("query", &query_value));
- EXPECT_EQ(query, query_value);
-
+ histogram_list = nullptr;
ASSERT_TRUE(root_dict->GetList("histograms", &histogram_list));
- ASSERT_EQ(1u, histogram_list->GetSize());
-
+ ASSERT_EQ(2u, histogram_list->GetSize());
+ histogram_dict = nullptr;
ASSERT_TRUE(histogram_list->GetDictionary(0, &histogram_dict));
-
- std::string histogram_name;
- ASSERT_TRUE(histogram_dict->GetString("name", &histogram_name));
- EXPECT_EQ("TestHistogram2", histogram_name);
-
- json.clear();
- UninitializeStatisticsRecorder();
-
- // No data should be returned.
- json = StatisticsRecorder::ToJSON(query);
- EXPECT_TRUE(json.empty());
+ sample_count = 0;
+ ASSERT_TRUE(histogram_dict->GetInteger("count", &sample_count));
+ EXPECT_EQ(2, sample_count);
+ buckets_list = nullptr;
+ // Bucket information should be omitted.
+ ASSERT_FALSE(histogram_dict->GetList("buckets", &buckets_list));
}
TEST_P(StatisticsRecorderTest, IterationTest) {
Histogram::FactoryGet("IterationTest1", 1, 64, 16, HistogramBase::kNoFlags);
Histogram::FactoryGet("IterationTest2", 1, 64, 16, HistogramBase::kNoFlags);
- StatisticsRecorder::HistogramIterator i1 = StatisticsRecorder::begin(true);
- EXPECT_EQ(2, CountIterableHistograms(&i1));
-
- StatisticsRecorder::HistogramIterator i2 = StatisticsRecorder::begin(false);
- EXPECT_EQ(use_persistent_histogram_allocator_ ? 0 : 2,
- CountIterableHistograms(&i2));
+ auto histograms = StatisticsRecorder::GetHistograms();
+ EXPECT_THAT(histograms, SizeIs(2));
+ histograms = StatisticsRecorder::NonPersistent(std::move(histograms));
+ EXPECT_THAT(histograms, SizeIs(use_persistent_histogram_allocator_ ? 0 : 2));
// Create a new global allocator using the same memory as the old one. Any
// old one is kept around so the memory doesn't get released.
@@ -445,12 +426,10 @@ TEST_P(StatisticsRecorderTest, IterationTest) {
UninitializeStatisticsRecorder();
InitializeStatisticsRecorder();
- StatisticsRecorder::HistogramIterator i3 = StatisticsRecorder::begin(true);
- EXPECT_EQ(use_persistent_histogram_allocator_ ? 2 : 0,
- CountIterableHistograms(&i3));
-
- StatisticsRecorder::HistogramIterator i4 = StatisticsRecorder::begin(false);
- EXPECT_EQ(0, CountIterableHistograms(&i4));
+ histograms = StatisticsRecorder::GetHistograms();
+ EXPECT_THAT(histograms, SizeIs(use_persistent_histogram_allocator_ ? 2 : 0));
+ histograms = StatisticsRecorder::NonPersistent(std::move(histograms));
+ EXPECT_THAT(histograms, IsEmpty());
}
namespace {
@@ -628,33 +607,33 @@ TEST_P(StatisticsRecorderTest, CallbackUsedBeforeHistogramCreatedTest) {
}
TEST_P(StatisticsRecorderTest, LogOnShutdownNotInitialized) {
- UninitializeStatisticsRecorder();
+ ResetVLogInitialized();
logging::SetMinLogLevel(logging::LOG_WARNING);
InitializeStatisticsRecorder();
EXPECT_FALSE(VLOG_IS_ON(1));
- EXPECT_FALSE(VLogInitialized());
+ EXPECT_FALSE(IsVLogInitialized());
InitLogOnShutdown();
- EXPECT_FALSE(VLogInitialized());
+ EXPECT_FALSE(IsVLogInitialized());
}
TEST_P(StatisticsRecorderTest, LogOnShutdownInitializedExplicitly) {
- UninitializeStatisticsRecorder();
+ ResetVLogInitialized();
logging::SetMinLogLevel(logging::LOG_WARNING);
InitializeStatisticsRecorder();
EXPECT_FALSE(VLOG_IS_ON(1));
- EXPECT_FALSE(VLogInitialized());
+ EXPECT_FALSE(IsVLogInitialized());
logging::SetMinLogLevel(logging::LOG_VERBOSE);
EXPECT_TRUE(VLOG_IS_ON(1));
InitLogOnShutdown();
- EXPECT_TRUE(VLogInitialized());
+ EXPECT_TRUE(IsVLogInitialized());
}
TEST_P(StatisticsRecorderTest, LogOnShutdownInitialized) {
- UninitializeStatisticsRecorder();
+ ResetVLogInitialized();
logging::SetMinLogLevel(logging::LOG_VERBOSE);
InitializeStatisticsRecorder();
EXPECT_TRUE(VLOG_IS_ON(1));
- EXPECT_TRUE(VLogInitialized());
+ EXPECT_TRUE(IsVLogInitialized());
}
class TestHistogramProvider : public StatisticsRecorder::HistogramProvider {
@@ -727,4 +706,15 @@ TEST_P(StatisticsRecorderTest, ImportHistogramsTest) {
EXPECT_EQ(1, snapshot->GetCount(5));
}
+TEST_P(StatisticsRecorderTest, RecordHistogramChecker) {
+ // Before record checker is set all histograms should be recorded.
+ EXPECT_TRUE(StatisticsRecorder::ShouldRecordHistogram(1));
+ EXPECT_TRUE(StatisticsRecorder::ShouldRecordHistogram(2));
+
+ auto record_checker = std::make_unique<OddRecordHistogramChecker>();
+ StatisticsRecorder::SetRecordChecker(std::move(record_checker));
+ EXPECT_TRUE(StatisticsRecorder::ShouldRecordHistogram(1));
+ EXPECT_FALSE(StatisticsRecorder::ShouldRecordHistogram(2));
+}
+
} // namespace base
diff --git a/base/metrics/user_metrics.cc b/base/metrics/user_metrics.cc
index 65ac918817..9fcc9e8a18 100644
--- a/base/metrics/user_metrics.cc
+++ b/base/metrics/user_metrics.cc
@@ -36,7 +36,7 @@ void RecordComputedAction(const std::string& action) {
if (!g_task_runner.Get()->BelongsToCurrentThread()) {
g_task_runner.Get()->PostTask(FROM_HERE,
- Bind(&RecordComputedAction, action));
+ BindOnce(&RecordComputedAction, action));
return;
}
diff --git a/base/metrics/user_metrics_action.h b/base/metrics/user_metrics_action.h
index 3eca3ddb8b..454ed83840 100644
--- a/base/metrics/user_metrics_action.h
+++ b/base/metrics/user_metrics_action.h
@@ -19,7 +19,7 @@ namespace base {
// Please see tools/metrics/actions/extract_actions.py for details.
struct UserMetricsAction {
const char* str_;
- explicit UserMetricsAction(const char* str) : str_(str) {}
+ explicit constexpr UserMetricsAction(const char* str) noexcept : str_(str) {}
};
} // namespace base
diff --git a/base/native_library.cc b/base/native_library.cc
new file mode 100644
index 0000000000..72012a3334
--- /dev/null
+++ b/base/native_library.cc
@@ -0,0 +1,15 @@
+// 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/native_library.h"
+
+namespace base {
+
+NativeLibrary LoadNativeLibrary(const FilePath& library_path,
+ NativeLibraryLoadError* error) {
+ return LoadNativeLibraryWithOptions(
+ library_path, NativeLibraryOptions(), error);
+}
+
+} // namespace base
diff --git a/base/native_library.h b/base/native_library.h
index e2b9ca7e6d..04356d9686 100644
--- a/base/native_library.h
+++ b/base/native_library.h
@@ -46,7 +46,7 @@ struct NativeLibraryStruct {
};
};
using NativeLibrary = NativeLibraryStruct*;
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
using NativeLibrary = void*;
#endif // OS_*
@@ -60,7 +60,7 @@ struct BASE_EXPORT NativeLibraryLoadError {
#if defined(OS_WIN)
DWORD code;
-#else
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
std::string message;
#endif // OS_WIN
};
@@ -98,13 +98,21 @@ BASE_EXPORT void UnloadNativeLibrary(NativeLibrary library);
BASE_EXPORT void* GetFunctionPointerFromNativeLibrary(NativeLibrary library,
StringPiece name);
-// Returns the full platform specific name for a native library.
-// |name| must be ASCII.
-// For example:
-// "mylib" returns "mylib.dll" on Windows, "libmylib.so" on Linux,
-// "libmylib.dylib" on Mac.
+// Returns the full platform-specific name for a native library. |name| must be
+// ASCII. This is also the default name for the output of a gn |shared_library|
+// target. See tools/gn/docs/reference.md#shared_library.
+// For example for "mylib", it returns:
+// - "mylib.dll" on Windows
+// - "libmylib.so" on Linux
+// - "libmylib.dylib" on Mac
BASE_EXPORT std::string GetNativeLibraryName(StringPiece name);
+// Returns the full platform-specific name for a gn |loadable_module| target.
+// See tools/gn/docs/reference.md#loadable_module
+// The returned name is the same as GetNativeLibraryName() on all platforms
+// except for Mac where for "mylib" it returns "mylib.so".
+BASE_EXPORT std::string GetLoadableModuleName(StringPiece name);
+
} // namespace base
#endif // BASE_NATIVE_LIBRARY_H_
diff --git a/base/native_library_posix.cc b/base/native_library_posix.cc
index 3459716af1..19ff7a4b0b 100644
--- a/base/native_library_posix.cc
+++ b/base/native_library_posix.cc
@@ -18,12 +18,11 @@ std::string NativeLibraryLoadError::ToString() const {
return message;
}
-// static
NativeLibrary LoadNativeLibraryWithOptions(const FilePath& library_path,
const NativeLibraryOptions& options,
NativeLibraryLoadError* error) {
// dlopen() opens the file off disk.
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
// We deliberately do not use RTLD_DEEPBIND by default. For the history why,
// please refer to the bug tracker. Some useful bug reports to read include:
@@ -46,7 +45,6 @@ NativeLibrary LoadNativeLibraryWithOptions(const FilePath& library_path,
return dl;
}
-// static
void UnloadNativeLibrary(NativeLibrary library) {
int ret = dlclose(library);
if (ret < 0) {
@@ -55,16 +53,18 @@ void UnloadNativeLibrary(NativeLibrary library) {
}
}
-// static
void* GetFunctionPointerFromNativeLibrary(NativeLibrary library,
StringPiece name) {
return dlsym(library, name.data());
}
-// static
std::string GetNativeLibraryName(StringPiece name) {
DCHECK(IsStringASCII(name));
return "lib" + name.as_string() + ".so";
}
+std::string GetLoadableModuleName(StringPiece name) {
+ return GetNativeLibraryName(name);
+}
+
} // namespace base
diff --git a/base/no_destructor.h b/base/no_destructor.h
new file mode 100644
index 0000000000..aabc6e6a7d
--- /dev/null
+++ b/base/no_destructor.h
@@ -0,0 +1,99 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_NO_DESTRUCTOR_H_
+#define BASE_NO_DESTRUCTOR_H_
+
+#include <new>
+#include <utility>
+
+namespace base {
+
+// A wrapper that makes it easy to create an object of type T with static
+// storage duration that:
+// - is only constructed on first access
+// - never invokes the destructor
+// in order to satisfy the styleguide ban on global constructors and
+// destructors.
+//
+// Runtime constant example:
+// const std::string& GetLineSeparator() {
+// // Forwards to std::string(size_t, char, const Allocator&) constructor.
+// static const base::NoDestructor<std::string> s(5, '-');
+// return *s;
+// }
+//
+// More complex initialization with a lambda:
+// const std::string& GetSessionNonce() {
+// static const base::NoDestructor<std::string> nonce([] {
+// std::string s(16);
+// crypto::RandString(s.data(), s.size());
+// return s;
+// }());
+// return *nonce;
+// }
+//
+// NoDestructor<T> stores the object inline, so it also avoids a pointer
+// indirection and a malloc. Also note that since C++11 static local variable
+// initialization is thread-safe and so is this pattern. Code should prefer to
+// use NoDestructor<T> over:
+// - The CR_DEFINE_STATIC_LOCAL() helper macro.
+// - A function scoped static T* or T& that is dynamically initialized.
+// - A global base::LazyInstance<T>.
+//
+// Note that since the destructor is never run, this *will* leak memory if used
+// as a stack or member variable. Furthermore, a NoDestructor<T> should never
+// have global scope as that may require a static initializer.
+template <typename T>
+class NoDestructor {
+ public:
+ // Not constexpr; just write static constexpr T x = ...; if the value should
+ // be a constexpr.
+ template <typename... Args>
+ explicit NoDestructor(Args&&... args) {
+ new (storage_) T(std::forward<Args>(args)...);
+ }
+
+ // Allows copy and move construction of the contained type, to allow
+ // construction from an initializer list, e.g. for std::vector.
+ explicit NoDestructor(const T& x) { new (storage_) T(x); }
+ explicit NoDestructor(T&& x) { new (storage_) T(std::move(x)); }
+
+ NoDestructor(const NoDestructor&) = delete;
+ NoDestructor& operator=(const NoDestructor&) = delete;
+
+ ~NoDestructor() = default;
+
+ const T& operator*() const { return *get(); }
+ T& operator*() { return *get(); }
+
+ const T* operator->() const { return get(); }
+ T* operator->() { return get(); }
+
+ const T* get() const { return reinterpret_cast<const T*>(storage_); }
+ T* get() { return reinterpret_cast<T*>(storage_); }
+
+ private:
+ alignas(T) char storage_[sizeof(T)];
+
+#if defined(LEAK_SANITIZER)
+ // TODO(https://crbug.com/812277): This is a hack to work around the fact
+ // that LSan doesn't seem to treat NoDestructor as a root for reachability
+ // analysis. This means that code like this:
+ // static base::NoDestructor<std::vector<int>> v({1, 2, 3});
+ // is considered a leak. Using the standard leak sanitizer annotations to
+ // suppress leaks doesn't work: std::vector is implicitly constructed before
+ // calling the base::NoDestructor constructor.
+ //
+ // Unfortunately, I haven't been able to demonstrate this issue in simpler
+ // reproductions: until that's resolved, hold an explicit pointer to the
+ // placement-new'd object in leak sanitizer mode to help LSan realize that
+ // objects allocated by the contained type are still reachable.
+ T* storage_ptr_ = reinterpret_cast<T*>(storage_);
+#endif // defined(LEAK_SANITIZER)
+};
+
+} // namespace base
+
+#endif // BASE_NO_DESTRUCTOR_H_
diff --git a/base/no_destructor_unittest.cc b/base/no_destructor_unittest.cc
new file mode 100644
index 0000000000..8f9d4a48c7
--- /dev/null
+++ b/base/no_destructor_unittest.cc
@@ -0,0 +1,76 @@
+// 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.
+
+#include "base/no_destructor.h"
+
+#include <string>
+#include <utility>
+
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+struct CheckOnDestroy {
+ ~CheckOnDestroy() { CHECK(false); }
+};
+
+TEST(NoDestructorTest, SkipsDestructors) {
+ NoDestructor<CheckOnDestroy> destructor_should_not_run;
+}
+
+struct CopyOnly {
+ CopyOnly() = default;
+
+ CopyOnly(const CopyOnly&) = default;
+ CopyOnly& operator=(const CopyOnly&) = default;
+
+ CopyOnly(CopyOnly&&) = delete;
+ CopyOnly& operator=(CopyOnly&&) = delete;
+};
+
+struct MoveOnly {
+ MoveOnly() = default;
+
+ MoveOnly(const MoveOnly&) = delete;
+ MoveOnly& operator=(const MoveOnly&) = delete;
+
+ MoveOnly(MoveOnly&&) = default;
+ MoveOnly& operator=(MoveOnly&&) = default;
+};
+
+struct ForwardingTestStruct {
+ ForwardingTestStruct(const CopyOnly&, MoveOnly&&) {}
+};
+
+TEST(NoDestructorTest, ForwardsArguments) {
+ CopyOnly copy_only;
+ MoveOnly move_only;
+
+ static NoDestructor<ForwardingTestStruct> test_forwarding(
+ copy_only, std::move(move_only));
+}
+
+TEST(NoDestructorTest, Accessors) {
+ static NoDestructor<std::string> awesome("awesome");
+
+ EXPECT_EQ("awesome", *awesome);
+ EXPECT_EQ(0, awesome->compare("awesome"));
+ EXPECT_EQ(0, awesome.get()->compare("awesome"));
+}
+
+// Passing initializer list to a NoDestructor like in this test
+// is ambiguous in GCC.
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84849
+#if !defined(COMPILER_GCC) && !defined(__clang__)
+TEST(NoDestructorTest, InitializerList) {
+ static NoDestructor<std::vector<std::string>> vector({"a", "b", "c"});
+}
+#endif
+} // namespace
+
+} // namespace base
diff --git a/base/numerics/BUILD.gn b/base/numerics/BUILD.gn
new file mode 100644
index 0000000000..0bb8dd1036
--- /dev/null
+++ b/base/numerics/BUILD.gn
@@ -0,0 +1,28 @@
+# Copyright (c) 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.
+
+# This is a dependency-free, header-only, library, and it needs to stay that
+# way to facilitate pulling it into various third-party projects. So, this
+# file is here to protect against accidentally introducing external
+# dependencies or depending on internal implementation details.
+source_set("base_numerics") {
+ visibility = [ "//base/*" ]
+ sources = [
+ "checked_math_impl.h",
+ "clamped_math_impl.h",
+ "safe_conversions_arm_impl.h",
+ "safe_conversions_impl.h",
+ "safe_math_arm_impl.h",
+ "safe_math_clang_gcc_impl.h",
+ "safe_math_shared_impl.h",
+ ]
+ public = [
+ "checked_math.h",
+ "clamped_math.h",
+ "math_constants.h",
+ "ranges.h",
+ "safe_conversions.h",
+ "safe_math.h",
+ ]
+}
diff --git a/base/numerics/README.md b/base/numerics/README.md
new file mode 100644
index 0000000000..896b124213
--- /dev/null
+++ b/base/numerics/README.md
@@ -0,0 +1,409 @@
+# `base/numerics`
+
+This directory contains a dependency-free, header-only library of templates
+providing well-defined semantics for safely and performantly handling a variety
+of numeric operations, including most common arithmetic operations and
+conversions.
+
+The public API is broken out into the following header files:
+
+* `checked_math.h` contains the `CheckedNumeric` template class and helper
+ functions for performing arithmetic and conversion operations that detect
+ errors and boundary conditions (e.g. overflow, truncation, etc.).
+* `clamped_math.h` contains the `ClampedNumeric` template class and
+ helper functions for performing fast, clamped (i.e. non-sticky saturating)
+ arithmetic operations and conversions.
+* `safe_conversions.h` contains the `StrictNumeric` template class and
+ a collection of custom casting templates and helper functions for safely
+ converting between a range of numeric types.
+* `safe_math.h` includes all of the previously mentioned headers.
+
+*** aside
+**Note:** The `Numeric` template types implicitly convert from C numeric types
+and `Numeric` templates that are convertable to an underlying C numeric type.
+The conversion priority for `Numeric` type coercions is:
+
+* `StrictNumeric` coerces to `ClampedNumeric` and `CheckedNumeric`
+* `ClampedNumeric` coerces to `CheckedNumeric`
+***
+
+[TOC]
+
+## Common patterns and use-cases
+
+The following covers the preferred style for the most common uses of this
+library. Please don't cargo-cult from anywhere else. 😉
+
+### Performing checked arithmetic conversions
+
+The `checked_cast` template converts between arbitrary arithmetic types, and is
+used for cases where a conversion failure should result in program termination:
+
+```cpp
+// Crash if signed_value is out of range for buff_size.
+size_t buff_size = checked_cast<size_t>(signed_value);
+```
+
+### Performing saturated (clamped) arithmetic conversions
+
+The `saturated_cast` template converts between arbitrary arithmetic types, and
+is used in cases where an out-of-bounds source value should be saturated to the
+corresponding maximum or minimum of the destination type:
+
+```cpp
+// Convert from float with saturation to INT_MAX, INT_MIN, or 0 for NaN.
+int int_value = saturated_cast<int>(floating_point_value);
+```
+
+### Enforcing arithmetic conversions at compile-time
+
+The `strict_cast` enforces type restrictions at compile-time and results in
+emitted code that is identical to a normal `static_cast`. However, a
+`strict_cast` assignment will fail to compile if the destination type cannot
+represent the full range of the source type:
+
+```cpp
+// Throw a compiler error if byte_value is changed to an out-of-range-type.
+int int_value = strict_cast<int>(byte_value);
+```
+
+You can also enforce these compile-time restrictions on function parameters by
+using the `StrictNumeric` template:
+
+```cpp
+// Throw a compiler error if the size argument cannot be represented by a
+// size_t (e.g. passing an int will fail to compile).
+bool AllocateBuffer(void** buffer, StrictCast<size_t> size);
+```
+
+### Comparing values between arbitrary arithmetic types
+
+Both the `StrictNumeric` and `ClampedNumeric` types provide well defined
+comparisons between arbitrary arithmetic types. This allows you to perform
+comparisons that are not legal or would trigger compiler warnings or errors
+under the normal arithmetic promotion rules:
+
+```cpp
+bool foo(unsigned value, int upper_bound) {
+ // Converting to StrictNumeric allows this comparison to work correctly.
+ if (MakeStrictNum(value) >= upper_bound)
+ return false;
+```
+
+*** note
+**Warning:** Do not perform manual conversions using the comparison operators.
+Instead, use the cast templates described in the previous sections, or the
+constexpr template functions `IsValueInRangeForNumericType` and
+`IsTypeInRangeForNumericType`, as these templates properly handle the full range
+of corner cases and employ various optimizations.
+***
+
+### Calculating a buffer size (checked arithmetic)
+
+When making exact calculations—such as for buffer lengths—it's often necessary
+to know when those calculations trigger an overflow, undefined behavior, or
+other boundary conditions. The `CheckedNumeric` template does this by storing
+a bit determining whether or not some arithmetic operation has occured that
+would put the variable in an "invalid" state. Attempting to extract the value
+from a variable in an invalid state will trigger a check/trap condition, that
+by default will result in process termination.
+
+Here's an example of a buffer calculation using a `CheckedNumeric` type (note:
+the AssignIfValid method will trigger a compile error if the result is ignored).
+
+```cpp
+// Calculate the buffer size and detect if an overflow occurs.
+size_t size;
+if (!CheckAdd(kHeaderSize, CheckMul(count, kItemSize)).AssignIfValid(&size)) {
+ // Handle an overflow error...
+}
+```
+
+### Calculating clamped coordinates (non-sticky saturating arithmetic)
+
+Certain classes of calculations—such as coordinate calculations—require
+well-defined semantics that always produce a valid result on boundary
+conditions. The `ClampedNumeric` template addresses this by providing
+performant, non-sticky saturating arithmetic operations.
+
+Here's an example of using a `ClampedNumeric` to calculate an operation
+insetting a rectangle.
+
+```cpp
+// Use clamped arithmetic since inset calculations might overflow.
+void Rect::Inset(int left, int top, int right, int bottom) {
+ origin_ += Vector2d(left, top);
+ set_width(ClampSub(width(), ClampAdd(left, right)));
+ set_height(ClampSub(height(), ClampAdd(top, bottom)));
+}
+```
+
+*** note
+The `ClampedNumeric` type is not "sticky", which means the saturation is not
+retained across individual operations. As such, one arithmetic operation may
+result in a saturated value, while the next operation may then "desaturate"
+the value. Here's an example:
+
+```cpp
+ClampedNumeric<int> value = INT_MAX;
+++value; // value is still INT_MAX, due to saturation.
+--value; // value is now (INT_MAX - 1), because saturation is not sticky.
+```
+
+***
+
+## Conversion functions and StrictNumeric<> in safe_conversions.h
+
+This header includes a collection of helper `constexpr` templates for safely
+performing a range of conversions, assignments, and tests.
+
+### Safe casting templates
+
+* `as_signed()` - Returns the supplied integral value as a signed type of
+ the same width.
+* `as_unsigned()` - Returns the supplied integral value as an unsigned type
+ of the same width.
+* `checked_cast<>()` - Analogous to `static_cast<>` for numeric types, except
+ that by default it will trigger a crash on an out-of-bounds conversion (e.g.
+ overflow, underflow, NaN to integral) or a compile error if the conversion
+ error can be detected at compile time. The crash handler can be overridden
+ to perform a behavior other than crashing.
+* `saturated_cast<>()` - Analogous to `static_cast` for numeric types, except
+ that it returns a saturated result when the specified numeric conversion
+ would otherwise overflow or underflow. An NaN source returns 0 by
+ default, but can be overridden to return a different result.
+* `strict_cast<>()` - Analogous to `static_cast` for numeric types, except
+ this causes a compile failure if the destination type is not large
+ enough to contain any value in the source type. It performs no runtime
+ checking and thus introduces no runtime overhead.
+
+### Other helper and conversion functions
+
+* `IsValueInRangeForNumericType<>()` - A convenience function that returns
+ true if the type supplied as the template parameter can represent the value
+ passed as an argument to the function.
+* `IsTypeInRangeForNumericType<>()` - A convenience function that evaluates
+ entirely at compile-time and returns true if the destination type (first
+ template parameter) can represent the full range of the source type
+ (second template parameter).
+* `IsValueNegative()` - A convenience function that will accept any
+ arithmetic type as an argument and will return whether the value is less
+ than zero. Unsigned types always return false.
+* `SafeUnsignedAbs()` - Returns the absolute value of the supplied integer
+ parameter as an unsigned result (thus avoiding an overflow if the value
+ is the signed, two's complement minimum).
+
+### StrictNumeric<>
+
+`StrictNumeric<>` is a wrapper type that performs assignments and copies via
+the `strict_cast` template, and can perform valid arithmetic comparisons
+across any range of arithmetic types. `StrictNumeric` is the return type for
+values extracted from a `CheckedNumeric` class instance. The raw numeric value
+is extracted via `static_cast` to the underlying type or any type with
+sufficient range to represent the underlying type.
+
+* `MakeStrictNum()` - Creates a new `StrictNumeric` from the underlying type
+ of the supplied arithmetic or StrictNumeric type.
+* `SizeT` - Alias for `StrictNumeric<size_t>`.
+
+## CheckedNumeric<> in checked_math.h
+
+`CheckedNumeric<>` implements all the logic and operators for detecting integer
+boundary conditions such as overflow, underflow, and invalid conversions.
+The `CheckedNumeric` type implicitly converts from floating point and integer
+data types, and contains overloads for basic arithmetic operations (i.e.: `+`,
+`-`, `*`, `/` for all types and `%`, `<<`, `>>`, `&`, `|`, `^` for integers).
+However, *the [variadic template functions
+](#CheckedNumeric_in-checked_math_h-Non_member-helper-functions)
+are the prefered API,* as they remove type ambiguities and help prevent a number
+of common errors. The variadic functions can also be more performant, as they
+eliminate redundant expressions that are unavoidable with the with the operator
+overloads. (Ideally the compiler should optimize those away, but better to avoid
+them in the first place.)
+
+Type promotions are a slightly modified version of the [standard C/C++ numeric
+promotions
+](http://en.cppreference.com/w/cpp/language/implicit_conversion#Numeric_promotions)
+with the two differences being that *there is no default promotion to int*
+and *bitwise logical operations always return an unsigned of the wider type.*
+
+### Members
+
+The unary negation, increment, and decrement operators are supported, along
+with the following unary arithmetic methods, which return a new
+`CheckedNumeric` as a result of the operation:
+
+* `Abs()` - Absolute value.
+* `UnsignedAbs()` - Absolute value as an equal-width unsigned underlying type
+ (valid for only integral types).
+* `Max()` - Returns whichever is greater of the current instance or argument.
+ The underlying return type is whichever has the greatest magnitude.
+* `Min()` - Returns whichever is lowest of the current instance or argument.
+ The underlying return type is whichever has can represent the lowest
+ number in the smallest width (e.g. int8_t over unsigned, int over
+ int8_t, and float over int).
+
+The following are for converting `CheckedNumeric` instances:
+
+* `type` - The underlying numeric type.
+* `AssignIfValid()` - Assigns the underlying value to the supplied
+ destination pointer if the value is currently valid and within the
+ range supported by the destination type. Returns true on success.
+* `Cast<>()` - Instance method returning a `CheckedNumeric` derived from
+ casting the current instance to a `CheckedNumeric` of the supplied
+ destination type.
+
+*** aside
+The following member functions return a `StrictNumeric`, which is valid for
+comparison and assignment operations, but will trigger a compile failure on
+attempts to assign to a type of insufficient range. The underlying value can
+be extracted by an explicit `static_cast` to the underlying type or any type
+with sufficient range to represent the underlying type.
+***
+
+* `IsValid()` - Returns true if the underlying numeric value is valid (i.e.
+ has not wrapped or saturated and is not the result of an invalid
+ conversion).
+* `ValueOrDie()` - Returns the underlying value. If the state is not valid
+ this call will trigger a crash by default (but may be overridden by
+ supplying an alternate handler to the template).
+* `ValueOrDefault()` - Returns the current value, or the supplied default if
+ the state is not valid (but will not crash).
+
+**Comparison operators are explicitly not provided** for `CheckedNumeric`
+types because they could result in a crash if the type is not in a valid state.
+Patterns like the following should be used instead:
+
+```cpp
+// Either input or padding (or both) may be arbitrary sizes.
+size_t buff_size;
+if (!CheckAdd(input, padding, kHeaderLength).AssignIfValid(&buff_size) ||
+ buff_size >= kMaxBuffer) {
+ // Handle an error...
+} else {
+ // Do stuff on success...
+}
+```
+
+### Non-member helper functions
+
+The following variadic convenience functions, which accept standard arithmetic
+or `CheckedNumeric` types, perform arithmetic operations, and return a
+`CheckedNumeric` result. The supported functions are:
+
+* `CheckAdd()` - Addition.
+* `CheckSub()` - Subtraction.
+* `CheckMul()` - Multiplication.
+* `CheckDiv()` - Division.
+* `CheckMod()` - Modulus (integer only).
+* `CheckLsh()` - Left integer shift (integer only).
+* `CheckRsh()` - Right integer shift (integer only).
+* `CheckAnd()` - Bitwise AND (integer only with unsigned result).
+* `CheckOr()` - Bitwise OR (integer only with unsigned result).
+* `CheckXor()` - Bitwise XOR (integer only with unsigned result).
+* `CheckMax()` - Maximum of supplied arguments.
+* `CheckMin()` - Minimum of supplied arguments.
+
+The following wrapper functions can be used to avoid the template
+disambiguator syntax when converting a destination type.
+
+* `IsValidForType<>()` in place of: `a.template IsValid<>()`
+* `ValueOrDieForType<>()` in place of: `a.template ValueOrDie<>()`
+* `ValueOrDefaultForType<>()` in place of: `a.template ValueOrDefault<>()`
+
+The following general utility methods is are useful for converting from
+arithmetic types to `CheckedNumeric` types:
+
+* `MakeCheckedNum()` - Creates a new `CheckedNumeric` from the underlying type
+ of the supplied arithmetic or directly convertible type.
+
+## ClampedNumeric<> in clamped_math.h
+
+`ClampedNumeric<>` implements all the logic and operators for clamped
+(non-sticky saturating) arithmetic operations and conversions. The
+`ClampedNumeric` type implicitly converts back and forth between floating point
+and integer data types, saturating on assignment as appropriate. It contains
+overloads for basic arithmetic operations (i.e.: `+`, `-`, `*`, `/` for
+all types and `%`, `<<`, `>>`, `&`, `|`, `^` for integers) along with comparison
+operators for arithmetic types of any size. However, *the [variadic template
+functions
+](#ClampedNumeric_in-clamped_math_h-Non_member-helper-functions)
+are the prefered API,* as they remove type ambiguities and help prevent
+a number of common errors. The variadic functions can also be more performant,
+as they eliminate redundant expressions that are unavoidable with the operator
+overloads. (Ideally the compiler should optimize those away, but better to avoid
+them in the first place.)
+
+Type promotions are a slightly modified version of the [standard C/C++ numeric
+promotions
+](http://en.cppreference.com/w/cpp/language/implicit_conversion#Numeric_promotions)
+with the two differences being that *there is no default promotion to int*
+and *bitwise logical operations always return an unsigned of the wider type.*
+
+*** aside
+Most arithmetic operations saturate normally, to the numeric limit in the
+direction of the sign. The potentially unusual cases are:
+
+* **Division:** Division by zero returns the saturated limit in the direction
+ of sign of the dividend (first argument). The one exception is 0/0, which
+ returns zero (although logically is NaN).
+* **Modulus:** Division by zero returns the dividend (first argument).
+* **Left shift:** Non-zero values saturate in the direction of the signed
+ limit (max/min), even for shifts larger than the bit width. 0 shifted any
+ amount results in 0.
+* **Right shift:** Negative values saturate to -1. Positive or 0 saturates
+ to 0. (Effectively just an unbounded arithmetic-right-shift.)
+* **Bitwise operations:** No saturation; bit pattern is identical to
+ non-saturated bitwise operations.
+***
+
+### Members
+
+The unary negation, increment, and decrement operators are supported, along
+with the following unary arithmetic methods, which return a new
+`ClampedNumeric` as a result of the operation:
+
+* `Abs()` - Absolute value.
+* `UnsignedAbs()` - Absolute value as an equal-width unsigned underlying type
+ (valid for only integral types).
+* `Max()` - Returns whichever is greater of the current instance or argument.
+ The underlying return type is whichever has the greatest magnitude.
+* `Min()` - Returns whichever is lowest of the current instance or argument.
+ The underlying return type is whichever has can represent the lowest
+ number in the smallest width (e.g. int8_t over unsigned, int over
+ int8_t, and float over int).
+
+The following are for converting `ClampedNumeric` instances:
+
+* `type` - The underlying numeric type.
+* `RawValue()` - Returns the raw value as the underlying arithmetic type. This
+ is useful when e.g. assigning to an auto type or passing as a deduced
+ template parameter.
+* `Cast<>()` - Instance method returning a `ClampedNumeric` derived from
+ casting the current instance to a `ClampedNumeric` of the supplied
+ destination type.
+
+### Non-member helper functions
+
+The following variadic convenience functions, which accept standard arithmetic
+or `ClampedNumeric` types, perform arithmetic operations, and return a
+`ClampedNumeric` result. The supported functions are:
+
+* `ClampAdd()` - Addition.
+* `ClampSub()` - Subtraction.
+* `ClampMul()` - Multiplication.
+* `ClampDiv()` - Division.
+* `ClampMod()` - Modulus (integer only).
+* `ClampLsh()` - Left integer shift (integer only).
+* `ClampRsh()` - Right integer shift (integer only).
+* `ClampAnd()` - Bitwise AND (integer only with unsigned result).
+* `ClampOr()` - Bitwise OR (integer only with unsigned result).
+* `ClampXor()` - Bitwise XOR (integer only with unsigned result).
+* `ClampMax()` - Maximum of supplied arguments.
+* `ClampMin()` - Minimum of supplied arguments.
+
+The following is a general utility method that is useful for converting
+to a `ClampedNumeric` type:
+
+* `MakeClampedNum()` - Creates a new `ClampedNumeric` from the underlying type
+ of the supplied arithmetic or directly convertible type.
diff --git a/base/numerics/checked_math.h b/base/numerics/checked_math.h
new file mode 100644
index 0000000000..ede3344f82
--- /dev/null
+++ b/base/numerics/checked_math.h
@@ -0,0 +1,393 @@
+// 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_NUMERICS_CHECKED_MATH_H_
+#define BASE_NUMERICS_CHECKED_MATH_H_
+
+#include <stddef.h>
+
+#include <limits>
+#include <type_traits>
+
+#include "base/numerics/checked_math_impl.h"
+
+namespace base {
+namespace internal {
+
+template <typename T>
+class CheckedNumeric {
+ static_assert(std::is_arithmetic<T>::value,
+ "CheckedNumeric<T>: T must be a numeric type.");
+
+ public:
+ using type = T;
+
+ constexpr CheckedNumeric() = default;
+
+ // Copy constructor.
+ template <typename Src>
+ constexpr CheckedNumeric(const CheckedNumeric<Src>& rhs)
+ : state_(rhs.state_.value(), rhs.IsValid()) {}
+
+ template <typename Src>
+ friend class CheckedNumeric;
+
+ // This is not an explicit constructor because we implicitly upgrade regular
+ // numerics to CheckedNumerics to make them easier to use.
+ template <typename Src>
+ constexpr CheckedNumeric(Src value) // NOLINT(runtime/explicit)
+ : state_(value) {
+ static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
+ }
+
+ // This is not an explicit constructor because we want a seamless conversion
+ // from StrictNumeric types.
+ template <typename Src>
+ constexpr CheckedNumeric(
+ StrictNumeric<Src> value) // NOLINT(runtime/explicit)
+ : state_(static_cast<Src>(value)) {}
+
+ // IsValid() - The public API to test if a CheckedNumeric is currently valid.
+ // A range checked destination type can be supplied using the Dst template
+ // parameter.
+ template <typename Dst = T>
+ constexpr bool IsValid() const {
+ return state_.is_valid() &&
+ IsValueInRangeForNumericType<Dst>(state_.value());
+ }
+
+ // AssignIfValid(Dst) - Assigns the underlying value if it is currently valid
+ // and is within the range supported by the destination type. Returns true if
+ // successful and false otherwise.
+ template <typename Dst>
+#if defined(__clang__) || defined(__GNUC__)
+ __attribute__((warn_unused_result))
+#elif defined(_MSC_VER)
+ _Check_return_
+#endif
+ constexpr bool
+ AssignIfValid(Dst* result) const {
+ return BASE_NUMERICS_LIKELY(IsValid<Dst>())
+ ? ((*result = static_cast<Dst>(state_.value())), true)
+ : false;
+ }
+
+ // ValueOrDie() - The primary accessor for the underlying value. If the
+ // current state is not valid it will CHECK and crash.
+ // A range checked destination type can be supplied using the Dst template
+ // parameter, which will trigger a CHECK if the value is not in bounds for
+ // the destination.
+ // The CHECK behavior can be overridden by supplying a handler as a
+ // template parameter, for test code, etc. However, the handler cannot access
+ // the underlying value, and it is not available through other means.
+ template <typename Dst = T, class CheckHandler = CheckOnFailure>
+ constexpr StrictNumeric<Dst> ValueOrDie() const {
+ return BASE_NUMERICS_LIKELY(IsValid<Dst>())
+ ? static_cast<Dst>(state_.value())
+ : CheckHandler::template HandleFailure<Dst>();
+ }
+
+ // ValueOrDefault(T default_value) - A convenience method that returns the
+ // current value if the state is valid, and the supplied default_value for
+ // any other state.
+ // A range checked destination type can be supplied using the Dst template
+ // parameter. WARNING: This function may fail to compile or CHECK at runtime
+ // if the supplied default_value is not within range of the destination type.
+ template <typename Dst = T, typename Src>
+ constexpr StrictNumeric<Dst> ValueOrDefault(const Src default_value) const {
+ return BASE_NUMERICS_LIKELY(IsValid<Dst>())
+ ? static_cast<Dst>(state_.value())
+ : checked_cast<Dst>(default_value);
+ }
+
+ // Returns a checked numeric of the specified type, cast from the current
+ // CheckedNumeric. If the current state is invalid or the destination cannot
+ // represent the result then the returned CheckedNumeric will be invalid.
+ template <typename Dst>
+ constexpr CheckedNumeric<typename UnderlyingType<Dst>::type> Cast() const {
+ return *this;
+ }
+
+ // This friend method is available solely for providing more detailed logging
+ // in the the tests. Do not implement it in production code, because the
+ // underlying values may change at any time.
+ template <typename U>
+ friend U GetNumericValueForTest(const CheckedNumeric<U>& src);
+
+ // Prototypes for the supported arithmetic operator overloads.
+ template <typename Src>
+ constexpr CheckedNumeric& operator+=(const Src rhs);
+ template <typename Src>
+ constexpr CheckedNumeric& operator-=(const Src rhs);
+ template <typename Src>
+ constexpr CheckedNumeric& operator*=(const Src rhs);
+ template <typename Src>
+ constexpr CheckedNumeric& operator/=(const Src rhs);
+ template <typename Src>
+ constexpr CheckedNumeric& operator%=(const Src rhs);
+ template <typename Src>
+ constexpr CheckedNumeric& operator<<=(const Src rhs);
+ template <typename Src>
+ constexpr CheckedNumeric& operator>>=(const Src rhs);
+ template <typename Src>
+ constexpr CheckedNumeric& operator&=(const Src rhs);
+ template <typename Src>
+ constexpr CheckedNumeric& operator|=(const Src rhs);
+ template <typename Src>
+ constexpr CheckedNumeric& operator^=(const Src rhs);
+
+ constexpr CheckedNumeric operator-() const {
+ // The negation of two's complement int min is int min, so we simply
+ // check for that in the constexpr case.
+ // We use an optimized code path for a known run-time variable.
+ return MustTreatAsConstexpr(state_.value()) || !std::is_signed<T>::value ||
+ std::is_floating_point<T>::value
+ ? CheckedNumeric<T>(
+ NegateWrapper(state_.value()),
+ IsValid() && (!std::is_signed<T>::value ||
+ std::is_floating_point<T>::value ||
+ NegateWrapper(state_.value()) !=
+ std::numeric_limits<T>::lowest()))
+ : FastRuntimeNegate();
+ }
+
+ constexpr CheckedNumeric operator~() const {
+ return CheckedNumeric<decltype(InvertWrapper(T()))>(
+ InvertWrapper(state_.value()), IsValid());
+ }
+
+ constexpr CheckedNumeric Abs() const {
+ return !IsValueNegative(state_.value()) ? *this : -*this;
+ }
+
+ template <typename U>
+ constexpr CheckedNumeric<typename MathWrapper<CheckedMaxOp, T, U>::type> Max(
+ const U rhs) const {
+ using R = typename UnderlyingType<U>::type;
+ using result_type = typename MathWrapper<CheckedMaxOp, T, U>::type;
+ // TODO(jschuh): This can be converted to the MathOp version and remain
+ // constexpr once we have C++14 support.
+ return CheckedNumeric<result_type>(
+ static_cast<result_type>(
+ IsGreater<T, R>::Test(state_.value(), Wrapper<U>::value(rhs))
+ ? state_.value()
+ : Wrapper<U>::value(rhs)),
+ state_.is_valid() && Wrapper<U>::is_valid(rhs));
+ }
+
+ template <typename U>
+ constexpr CheckedNumeric<typename MathWrapper<CheckedMinOp, T, U>::type> Min(
+ const U rhs) const {
+ using R = typename UnderlyingType<U>::type;
+ using result_type = typename MathWrapper<CheckedMinOp, T, U>::type;
+ // TODO(jschuh): This can be converted to the MathOp version and remain
+ // constexpr once we have C++14 support.
+ return CheckedNumeric<result_type>(
+ static_cast<result_type>(
+ IsLess<T, R>::Test(state_.value(), Wrapper<U>::value(rhs))
+ ? state_.value()
+ : Wrapper<U>::value(rhs)),
+ state_.is_valid() && Wrapper<U>::is_valid(rhs));
+ }
+
+ // This function is available only for integral types. It returns an unsigned
+ // integer of the same width as the source type, containing the absolute value
+ // of the source, and properly handling signed min.
+ constexpr CheckedNumeric<typename UnsignedOrFloatForSize<T>::type>
+ UnsignedAbs() const {
+ return CheckedNumeric<typename UnsignedOrFloatForSize<T>::type>(
+ SafeUnsignedAbs(state_.value()), state_.is_valid());
+ }
+
+ constexpr CheckedNumeric& operator++() {
+ *this += 1;
+ return *this;
+ }
+
+ constexpr CheckedNumeric operator++(int) {
+ CheckedNumeric value = *this;
+ *this += 1;
+ return value;
+ }
+
+ constexpr CheckedNumeric& operator--() {
+ *this -= 1;
+ return *this;
+ }
+
+ constexpr CheckedNumeric operator--(int) {
+ CheckedNumeric value = *this;
+ *this -= 1;
+ return value;
+ }
+
+ // These perform the actual math operations on the CheckedNumerics.
+ // Binary arithmetic operations.
+ template <template <typename, typename, typename> class M,
+ typename L,
+ typename R>
+ static constexpr CheckedNumeric MathOp(const L lhs, const R rhs) {
+ using Math = typename MathWrapper<M, L, R>::math;
+ T result = 0;
+ bool is_valid =
+ Wrapper<L>::is_valid(lhs) && Wrapper<R>::is_valid(rhs) &&
+ Math::Do(Wrapper<L>::value(lhs), Wrapper<R>::value(rhs), &result);
+ return CheckedNumeric<T>(result, is_valid);
+ }
+
+ // Assignment arithmetic operations.
+ template <template <typename, typename, typename> class M, typename R>
+ constexpr CheckedNumeric& MathOp(const R rhs) {
+ using Math = typename MathWrapper<M, T, R>::math;
+ T result = 0; // Using T as the destination saves a range check.
+ bool is_valid = state_.is_valid() && Wrapper<R>::is_valid(rhs) &&
+ Math::Do(state_.value(), Wrapper<R>::value(rhs), &result);
+ *this = CheckedNumeric<T>(result, is_valid);
+ return *this;
+ }
+
+ private:
+ CheckedNumericState<T> state_;
+
+ CheckedNumeric FastRuntimeNegate() const {
+ T result;
+ bool success = CheckedSubOp<T, T>::Do(T(0), state_.value(), &result);
+ return CheckedNumeric<T>(result, IsValid() && success);
+ }
+
+ template <typename Src>
+ constexpr CheckedNumeric(Src value, bool is_valid)
+ : state_(value, is_valid) {}
+
+ // These wrappers allow us to handle state the same way for both
+ // CheckedNumeric and POD arithmetic types.
+ template <typename Src>
+ struct Wrapper {
+ static constexpr bool is_valid(Src) { return true; }
+ static constexpr Src value(Src value) { return value; }
+ };
+
+ template <typename Src>
+ struct Wrapper<CheckedNumeric<Src>> {
+ static constexpr bool is_valid(const CheckedNumeric<Src> v) {
+ return v.IsValid();
+ }
+ static constexpr Src value(const CheckedNumeric<Src> v) {
+ return v.state_.value();
+ }
+ };
+
+ template <typename Src>
+ struct Wrapper<StrictNumeric<Src>> {
+ static constexpr bool is_valid(const StrictNumeric<Src>) { return true; }
+ static constexpr Src value(const StrictNumeric<Src> v) {
+ return static_cast<Src>(v);
+ }
+ };
+};
+
+// Convenience functions to avoid the ugly template disambiguator syntax.
+template <typename Dst, typename Src>
+constexpr bool IsValidForType(const CheckedNumeric<Src> value) {
+ return value.template IsValid<Dst>();
+}
+
+template <typename Dst, typename Src>
+constexpr StrictNumeric<Dst> ValueOrDieForType(
+ const CheckedNumeric<Src> value) {
+ return value.template ValueOrDie<Dst>();
+}
+
+template <typename Dst, typename Src, typename Default>
+constexpr StrictNumeric<Dst> ValueOrDefaultForType(
+ const CheckedNumeric<Src> value,
+ const Default default_value) {
+ return value.template ValueOrDefault<Dst>(default_value);
+}
+
+// Convience wrapper to return a new CheckedNumeric from the provided arithmetic
+// or CheckedNumericType.
+template <typename T>
+constexpr CheckedNumeric<typename UnderlyingType<T>::type> MakeCheckedNum(
+ const T value) {
+ return value;
+}
+
+// These implement the variadic wrapper for the math operations.
+template <template <typename, typename, typename> class M,
+ typename L,
+ typename R>
+constexpr CheckedNumeric<typename MathWrapper<M, L, R>::type> CheckMathOp(
+ const L lhs,
+ const R rhs) {
+ using Math = typename MathWrapper<M, L, R>::math;
+ return CheckedNumeric<typename Math::result_type>::template MathOp<M>(lhs,
+ rhs);
+}
+
+// General purpose wrapper template for arithmetic operations.
+template <template <typename, typename, typename> class M,
+ typename L,
+ typename R,
+ typename... Args>
+constexpr CheckedNumeric<typename ResultType<M, L, R, Args...>::type>
+CheckMathOp(const L lhs, const R rhs, const Args... args) {
+ return CheckMathOp<M>(CheckMathOp<M>(lhs, rhs), args...);
+}
+
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Add, +, +=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Sub, -, -=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Mul, *, *=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Div, /, /=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Mod, %, %=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Lsh, <<, <<=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Rsh, >>, >>=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, And, &, &=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Or, |, |=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Checked, Check, Xor, ^, ^=)
+BASE_NUMERIC_ARITHMETIC_VARIADIC(Checked, Check, Max)
+BASE_NUMERIC_ARITHMETIC_VARIADIC(Checked, Check, Min)
+
+// These are some extra StrictNumeric operators to support simple pointer
+// arithmetic with our result types. Since wrapping on a pointer is always
+// bad, we trigger the CHECK condition here.
+template <typename L, typename R>
+L* operator+(L* lhs, const StrictNumeric<R> rhs) {
+ uintptr_t result = CheckAdd(reinterpret_cast<uintptr_t>(lhs),
+ CheckMul(sizeof(L), static_cast<R>(rhs)))
+ .template ValueOrDie<uintptr_t>();
+ return reinterpret_cast<L*>(result);
+}
+
+template <typename L, typename R>
+L* operator-(L* lhs, const StrictNumeric<R> rhs) {
+ uintptr_t result = CheckSub(reinterpret_cast<uintptr_t>(lhs),
+ CheckMul(sizeof(L), static_cast<R>(rhs)))
+ .template ValueOrDie<uintptr_t>();
+ return reinterpret_cast<L*>(result);
+}
+
+} // namespace internal
+
+using internal::CheckedNumeric;
+using internal::IsValidForType;
+using internal::ValueOrDieForType;
+using internal::ValueOrDefaultForType;
+using internal::MakeCheckedNum;
+using internal::CheckMax;
+using internal::CheckMin;
+using internal::CheckAdd;
+using internal::CheckSub;
+using internal::CheckMul;
+using internal::CheckDiv;
+using internal::CheckMod;
+using internal::CheckLsh;
+using internal::CheckRsh;
+using internal::CheckAnd;
+using internal::CheckOr;
+using internal::CheckXor;
+
+} // namespace base
+
+#endif // BASE_NUMERICS_CHECKED_MATH_H_
diff --git a/base/numerics/checked_math_impl.h b/base/numerics/checked_math_impl.h
new file mode 100644
index 0000000000..e083389ebf
--- /dev/null
+++ b/base/numerics/checked_math_impl.h
@@ -0,0 +1,567 @@
+// 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_NUMERICS_CHECKED_MATH_IMPL_H_
+#define BASE_NUMERICS_CHECKED_MATH_IMPL_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <climits>
+#include <cmath>
+#include <cstdlib>
+#include <limits>
+#include <type_traits>
+
+#include "base/numerics/safe_conversions.h"
+#include "base/numerics/safe_math_shared_impl.h"
+
+namespace base {
+namespace internal {
+
+template <typename T>
+constexpr bool CheckedAddImpl(T x, T y, T* result) {
+ static_assert(std::is_integral<T>::value, "Type must be integral");
+ // Since the value of x+y is undefined if we have a signed type, we compute
+ // it using the unsigned type of the same size.
+ using UnsignedDst = typename std::make_unsigned<T>::type;
+ using SignedDst = typename std::make_signed<T>::type;
+ UnsignedDst ux = static_cast<UnsignedDst>(x);
+ UnsignedDst uy = static_cast<UnsignedDst>(y);
+ UnsignedDst uresult = static_cast<UnsignedDst>(ux + uy);
+ *result = static_cast<T>(uresult);
+ // Addition is valid if the sign of (x + y) is equal to either that of x or
+ // that of y.
+ return (std::is_signed<T>::value)
+ ? static_cast<SignedDst>((uresult ^ ux) & (uresult ^ uy)) >= 0
+ : uresult >= uy; // Unsigned is either valid or underflow.
+}
+
+template <typename T, typename U, class Enable = void>
+struct CheckedAddOp {};
+
+template <typename T, typename U>
+struct CheckedAddOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = typename MaxExponentPromotion<T, U>::type;
+ template <typename V>
+ static constexpr bool Do(T x, U y, V* result) {
+ // TODO(jschuh) Make this "constexpr if" once we're C++17.
+ if (CheckedAddFastOp<T, U>::is_supported)
+ return CheckedAddFastOp<T, U>::Do(x, y, result);
+
+ // Double the underlying type up to a full machine word.
+ using FastPromotion = typename FastIntegerArithmeticPromotion<T, U>::type;
+ using Promotion =
+ typename std::conditional<(IntegerBitsPlusSign<FastPromotion>::value >
+ IntegerBitsPlusSign<intptr_t>::value),
+ typename BigEnoughPromotion<T, U>::type,
+ FastPromotion>::type;
+ // Fail if either operand is out of range for the promoted type.
+ // TODO(jschuh): This could be made to work for a broader range of values.
+ if (BASE_NUMERICS_UNLIKELY(!IsValueInRangeForNumericType<Promotion>(x) ||
+ !IsValueInRangeForNumericType<Promotion>(y))) {
+ return false;
+ }
+
+ Promotion presult = {};
+ bool is_valid = true;
+ if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
+ presult = static_cast<Promotion>(x) + static_cast<Promotion>(y);
+ } else {
+ is_valid = CheckedAddImpl(static_cast<Promotion>(x),
+ static_cast<Promotion>(y), &presult);
+ }
+ *result = static_cast<V>(presult);
+ return is_valid && IsValueInRangeForNumericType<V>(presult);
+ }
+};
+
+template <typename T>
+constexpr bool CheckedSubImpl(T x, T y, T* result) {
+ static_assert(std::is_integral<T>::value, "Type must be integral");
+ // Since the value of x+y is undefined if we have a signed type, we compute
+ // it using the unsigned type of the same size.
+ using UnsignedDst = typename std::make_unsigned<T>::type;
+ using SignedDst = typename std::make_signed<T>::type;
+ UnsignedDst ux = static_cast<UnsignedDst>(x);
+ UnsignedDst uy = static_cast<UnsignedDst>(y);
+ UnsignedDst uresult = static_cast<UnsignedDst>(ux - uy);
+ *result = static_cast<T>(uresult);
+ // Subtraction is valid if either x and y have same sign, or (x-y) and x have
+ // the same sign.
+ return (std::is_signed<T>::value)
+ ? static_cast<SignedDst>((uresult ^ ux) & (ux ^ uy)) >= 0
+ : x >= y;
+}
+
+template <typename T, typename U, class Enable = void>
+struct CheckedSubOp {};
+
+template <typename T, typename U>
+struct CheckedSubOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = typename MaxExponentPromotion<T, U>::type;
+ template <typename V>
+ static constexpr bool Do(T x, U y, V* result) {
+ // TODO(jschuh) Make this "constexpr if" once we're C++17.
+ if (CheckedSubFastOp<T, U>::is_supported)
+ return CheckedSubFastOp<T, U>::Do(x, y, result);
+
+ // Double the underlying type up to a full machine word.
+ using FastPromotion = typename FastIntegerArithmeticPromotion<T, U>::type;
+ using Promotion =
+ typename std::conditional<(IntegerBitsPlusSign<FastPromotion>::value >
+ IntegerBitsPlusSign<intptr_t>::value),
+ typename BigEnoughPromotion<T, U>::type,
+ FastPromotion>::type;
+ // Fail if either operand is out of range for the promoted type.
+ // TODO(jschuh): This could be made to work for a broader range of values.
+ if (BASE_NUMERICS_UNLIKELY(!IsValueInRangeForNumericType<Promotion>(x) ||
+ !IsValueInRangeForNumericType<Promotion>(y))) {
+ return false;
+ }
+
+ Promotion presult = {};
+ bool is_valid = true;
+ if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
+ presult = static_cast<Promotion>(x) - static_cast<Promotion>(y);
+ } else {
+ is_valid = CheckedSubImpl(static_cast<Promotion>(x),
+ static_cast<Promotion>(y), &presult);
+ }
+ *result = static_cast<V>(presult);
+ return is_valid && IsValueInRangeForNumericType<V>(presult);
+ }
+};
+
+template <typename T>
+constexpr bool CheckedMulImpl(T x, T y, T* result) {
+ static_assert(std::is_integral<T>::value, "Type must be integral");
+ // Since the value of x*y is potentially undefined if we have a signed type,
+ // we compute it using the unsigned type of the same size.
+ using UnsignedDst = typename std::make_unsigned<T>::type;
+ using SignedDst = typename std::make_signed<T>::type;
+ const UnsignedDst ux = SafeUnsignedAbs(x);
+ const UnsignedDst uy = SafeUnsignedAbs(y);
+ UnsignedDst uresult = static_cast<UnsignedDst>(ux * uy);
+ const bool is_negative =
+ std::is_signed<T>::value && static_cast<SignedDst>(x ^ y) < 0;
+ *result = is_negative ? 0 - uresult : uresult;
+ // We have a fast out for unsigned identity or zero on the second operand.
+ // After that it's an unsigned overflow check on the absolute value, with
+ // a +1 bound for a negative result.
+ return uy <= UnsignedDst(!std::is_signed<T>::value || is_negative) ||
+ ux <= (std::numeric_limits<T>::max() + UnsignedDst(is_negative)) / uy;
+}
+
+template <typename T, typename U, class Enable = void>
+struct CheckedMulOp {};
+
+template <typename T, typename U>
+struct CheckedMulOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = typename MaxExponentPromotion<T, U>::type;
+ template <typename V>
+ static constexpr bool Do(T x, U y, V* result) {
+ // TODO(jschuh) Make this "constexpr if" once we're C++17.
+ if (CheckedMulFastOp<T, U>::is_supported)
+ return CheckedMulFastOp<T, U>::Do(x, y, result);
+
+ using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
+ // Verify the destination type can hold the result (always true for 0).
+ if (BASE_NUMERICS_UNLIKELY((!IsValueInRangeForNumericType<Promotion>(x) ||
+ !IsValueInRangeForNumericType<Promotion>(y)) &&
+ x && y)) {
+ return false;
+ }
+
+ Promotion presult = {};
+ bool is_valid = true;
+ if (CheckedMulFastOp<Promotion, Promotion>::is_supported) {
+ // The fast op may be available with the promoted type.
+ is_valid = CheckedMulFastOp<Promotion, Promotion>::Do(x, y, &presult);
+ } else if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
+ presult = static_cast<Promotion>(x) * static_cast<Promotion>(y);
+ } else {
+ is_valid = CheckedMulImpl(static_cast<Promotion>(x),
+ static_cast<Promotion>(y), &presult);
+ }
+ *result = static_cast<V>(presult);
+ return is_valid && IsValueInRangeForNumericType<V>(presult);
+ }
+};
+
+// Division just requires a check for a zero denominator or an invalid negation
+// on signed min/-1.
+template <typename T, typename U, class Enable = void>
+struct CheckedDivOp {};
+
+template <typename T, typename U>
+struct CheckedDivOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = typename MaxExponentPromotion<T, U>::type;
+ template <typename V>
+ static constexpr bool Do(T x, U y, V* result) {
+ if (BASE_NUMERICS_UNLIKELY(!y))
+ return false;
+
+ // The overflow check can be compiled away if we don't have the exact
+ // combination of types needed to trigger this case.
+ using Promotion = typename BigEnoughPromotion<T, U>::type;
+ if (BASE_NUMERICS_UNLIKELY(
+ (std::is_signed<T>::value && std::is_signed<U>::value &&
+ IsTypeInRangeForNumericType<T, Promotion>::value &&
+ static_cast<Promotion>(x) ==
+ std::numeric_limits<Promotion>::lowest() &&
+ y == static_cast<U>(-1)))) {
+ return false;
+ }
+
+ // This branch always compiles away if the above branch wasn't removed.
+ if (BASE_NUMERICS_UNLIKELY((!IsValueInRangeForNumericType<Promotion>(x) ||
+ !IsValueInRangeForNumericType<Promotion>(y)) &&
+ x)) {
+ return false;
+ }
+
+ Promotion presult = Promotion(x) / Promotion(y);
+ *result = static_cast<V>(presult);
+ return IsValueInRangeForNumericType<V>(presult);
+ }
+};
+
+template <typename T, typename U, class Enable = void>
+struct CheckedModOp {};
+
+template <typename T, typename U>
+struct CheckedModOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = typename MaxExponentPromotion<T, U>::type;
+ template <typename V>
+ static constexpr bool Do(T x, U y, V* result) {
+ using Promotion = typename BigEnoughPromotion<T, U>::type;
+ if (BASE_NUMERICS_LIKELY(y)) {
+ Promotion presult = static_cast<Promotion>(x) % static_cast<Promotion>(y);
+ *result = static_cast<Promotion>(presult);
+ return IsValueInRangeForNumericType<V>(presult);
+ }
+ return false;
+ }
+};
+
+template <typename T, typename U, class Enable = void>
+struct CheckedLshOp {};
+
+// Left shift. Shifts less than 0 or greater than or equal to the number
+// of bits in the promoted type are undefined. Shifts of negative values
+// are undefined. Otherwise it is defined when the result fits.
+template <typename T, typename U>
+struct CheckedLshOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = T;
+ template <typename V>
+ static constexpr bool Do(T x, U shift, V* result) {
+ // Disallow negative numbers and verify the shift is in bounds.
+ if (BASE_NUMERICS_LIKELY(!IsValueNegative(x) &&
+ as_unsigned(shift) <
+ as_unsigned(std::numeric_limits<T>::digits))) {
+ // Shift as unsigned to avoid undefined behavior.
+ *result = static_cast<V>(as_unsigned(x) << shift);
+ // If the shift can be reversed, we know it was valid.
+ return *result >> shift == x;
+ }
+
+ // Handle the legal corner-case of a full-width signed shift of zero.
+ return std::is_signed<T>::value && !x &&
+ as_unsigned(shift) == as_unsigned(std::numeric_limits<T>::digits);
+ }
+};
+
+template <typename T, typename U, class Enable = void>
+struct CheckedRshOp {};
+
+// Right shift. Shifts less than 0 or greater than or equal to the number
+// of bits in the promoted type are undefined. Otherwise, it is always defined,
+// but a right shift of a negative value is implementation-dependent.
+template <typename T, typename U>
+struct CheckedRshOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = T;
+ template <typename V>
+ static bool Do(T x, U shift, V* result) {
+ // Use the type conversion push negative values out of range.
+ if (BASE_NUMERICS_LIKELY(as_unsigned(shift) <
+ IntegerBitsPlusSign<T>::value)) {
+ T tmp = x >> shift;
+ *result = static_cast<V>(tmp);
+ return IsValueInRangeForNumericType<V>(tmp);
+ }
+ return false;
+ }
+};
+
+template <typename T, typename U, class Enable = void>
+struct CheckedAndOp {};
+
+// For simplicity we support only unsigned integer results.
+template <typename T, typename U>
+struct CheckedAndOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = typename std::make_unsigned<
+ typename MaxExponentPromotion<T, U>::type>::type;
+ template <typename V>
+ static constexpr bool Do(T x, U y, V* result) {
+ result_type tmp = static_cast<result_type>(x) & static_cast<result_type>(y);
+ *result = static_cast<V>(tmp);
+ return IsValueInRangeForNumericType<V>(tmp);
+ }
+};
+
+template <typename T, typename U, class Enable = void>
+struct CheckedOrOp {};
+
+// For simplicity we support only unsigned integers.
+template <typename T, typename U>
+struct CheckedOrOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = typename std::make_unsigned<
+ typename MaxExponentPromotion<T, U>::type>::type;
+ template <typename V>
+ static constexpr bool Do(T x, U y, V* result) {
+ result_type tmp = static_cast<result_type>(x) | static_cast<result_type>(y);
+ *result = static_cast<V>(tmp);
+ return IsValueInRangeForNumericType<V>(tmp);
+ }
+};
+
+template <typename T, typename U, class Enable = void>
+struct CheckedXorOp {};
+
+// For simplicity we support only unsigned integers.
+template <typename T, typename U>
+struct CheckedXorOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = typename std::make_unsigned<
+ typename MaxExponentPromotion<T, U>::type>::type;
+ template <typename V>
+ static constexpr bool Do(T x, U y, V* result) {
+ result_type tmp = static_cast<result_type>(x) ^ static_cast<result_type>(y);
+ *result = static_cast<V>(tmp);
+ return IsValueInRangeForNumericType<V>(tmp);
+ }
+};
+
+// Max doesn't really need to be implemented this way because it can't fail,
+// but it makes the code much cleaner to use the MathOp wrappers.
+template <typename T, typename U, class Enable = void>
+struct CheckedMaxOp {};
+
+template <typename T, typename U>
+struct CheckedMaxOp<
+ T,
+ U,
+ typename std::enable_if<std::is_arithmetic<T>::value &&
+ std::is_arithmetic<U>::value>::type> {
+ using result_type = typename MaxExponentPromotion<T, U>::type;
+ template <typename V>
+ static constexpr bool Do(T x, U y, V* result) {
+ result_type tmp = IsGreater<T, U>::Test(x, y) ? static_cast<result_type>(x)
+ : static_cast<result_type>(y);
+ *result = static_cast<V>(tmp);
+ return IsValueInRangeForNumericType<V>(tmp);
+ }
+};
+
+// Min doesn't really need to be implemented this way because it can't fail,
+// but it makes the code much cleaner to use the MathOp wrappers.
+template <typename T, typename U, class Enable = void>
+struct CheckedMinOp {};
+
+template <typename T, typename U>
+struct CheckedMinOp<
+ T,
+ U,
+ typename std::enable_if<std::is_arithmetic<T>::value &&
+ std::is_arithmetic<U>::value>::type> {
+ using result_type = typename LowestValuePromotion<T, U>::type;
+ template <typename V>
+ static constexpr bool Do(T x, U y, V* result) {
+ result_type tmp = IsLess<T, U>::Test(x, y) ? static_cast<result_type>(x)
+ : static_cast<result_type>(y);
+ *result = static_cast<V>(tmp);
+ return IsValueInRangeForNumericType<V>(tmp);
+ }
+};
+
+// This is just boilerplate that wraps the standard floating point arithmetic.
+// A macro isn't the nicest solution, but it beats rewriting these repeatedly.
+#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \
+ template <typename T, typename U> \
+ struct Checked##NAME##Op< \
+ T, U, \
+ typename std::enable_if<std::is_floating_point<T>::value || \
+ std::is_floating_point<U>::value>::type> { \
+ using result_type = typename MaxExponentPromotion<T, U>::type; \
+ template <typename V> \
+ static constexpr bool Do(T x, U y, V* result) { \
+ using Promotion = typename MaxExponentPromotion<T, U>::type; \
+ Promotion presult = x OP y; \
+ *result = static_cast<V>(presult); \
+ return IsValueInRangeForNumericType<V>(presult); \
+ } \
+ };
+
+BASE_FLOAT_ARITHMETIC_OPS(Add, +)
+BASE_FLOAT_ARITHMETIC_OPS(Sub, -)
+BASE_FLOAT_ARITHMETIC_OPS(Mul, *)
+BASE_FLOAT_ARITHMETIC_OPS(Div, /)
+
+#undef BASE_FLOAT_ARITHMETIC_OPS
+
+// Floats carry around their validity state with them, but integers do not. So,
+// we wrap the underlying value in a specialization in order to hide that detail
+// and expose an interface via accessors.
+enum NumericRepresentation {
+ NUMERIC_INTEGER,
+ NUMERIC_FLOATING,
+ NUMERIC_UNKNOWN
+};
+
+template <typename NumericType>
+struct GetNumericRepresentation {
+ static const NumericRepresentation value =
+ std::is_integral<NumericType>::value
+ ? NUMERIC_INTEGER
+ : (std::is_floating_point<NumericType>::value ? NUMERIC_FLOATING
+ : NUMERIC_UNKNOWN);
+};
+
+template <typename T,
+ NumericRepresentation type = GetNumericRepresentation<T>::value>
+class CheckedNumericState {};
+
+// Integrals require quite a bit of additional housekeeping to manage state.
+template <typename T>
+class CheckedNumericState<T, NUMERIC_INTEGER> {
+ private:
+ // is_valid_ precedes value_ because member intializers in the constructors
+ // are evaluated in field order, and is_valid_ must be read when initializing
+ // value_.
+ bool is_valid_;
+ T value_;
+
+ // Ensures that a type conversion does not trigger undefined behavior.
+ template <typename Src>
+ static constexpr T WellDefinedConversionOrZero(const Src value,
+ const bool is_valid) {
+ using SrcType = typename internal::UnderlyingType<Src>::type;
+ return (std::is_integral<SrcType>::value || is_valid)
+ ? static_cast<T>(value)
+ : static_cast<T>(0);
+ }
+
+ public:
+ template <typename Src, NumericRepresentation type>
+ friend class CheckedNumericState;
+
+ constexpr CheckedNumericState() : is_valid_(true), value_(0) {}
+
+ template <typename Src>
+ constexpr CheckedNumericState(Src value, bool is_valid)
+ : is_valid_(is_valid && IsValueInRangeForNumericType<T>(value)),
+ value_(WellDefinedConversionOrZero(value, is_valid_)) {
+ static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
+ }
+
+ // Copy constructor.
+ template <typename Src>
+ constexpr CheckedNumericState(const CheckedNumericState<Src>& rhs)
+ : is_valid_(rhs.IsValid()),
+ value_(WellDefinedConversionOrZero(rhs.value(), is_valid_)) {}
+
+ template <typename Src>
+ constexpr explicit CheckedNumericState(Src value)
+ : is_valid_(IsValueInRangeForNumericType<T>(value)),
+ value_(WellDefinedConversionOrZero(value, is_valid_)) {}
+
+ constexpr bool is_valid() const { return is_valid_; }
+ constexpr T value() const { return value_; }
+};
+
+// Floating points maintain their own validity, but need translation wrappers.
+template <typename T>
+class CheckedNumericState<T, NUMERIC_FLOATING> {
+ private:
+ T value_;
+
+ // Ensures that a type conversion does not trigger undefined behavior.
+ template <typename Src>
+ static constexpr T WellDefinedConversionOrNaN(const Src value,
+ const bool is_valid) {
+ using SrcType = typename internal::UnderlyingType<Src>::type;
+ return (StaticDstRangeRelationToSrcRange<T, SrcType>::value ==
+ NUMERIC_RANGE_CONTAINED ||
+ is_valid)
+ ? static_cast<T>(value)
+ : std::numeric_limits<T>::quiet_NaN();
+ }
+
+ public:
+ template <typename Src, NumericRepresentation type>
+ friend class CheckedNumericState;
+
+ constexpr CheckedNumericState() : value_(0.0) {}
+
+ template <typename Src>
+ constexpr CheckedNumericState(Src value, bool is_valid)
+ : value_(WellDefinedConversionOrNaN(value, is_valid)) {}
+
+ template <typename Src>
+ constexpr explicit CheckedNumericState(Src value)
+ : value_(WellDefinedConversionOrNaN(
+ value,
+ IsValueInRangeForNumericType<T>(value))) {}
+
+ // Copy constructor.
+ template <typename Src>
+ constexpr CheckedNumericState(const CheckedNumericState<Src>& rhs)
+ : value_(WellDefinedConversionOrNaN(
+ rhs.value(),
+ rhs.is_valid() && IsValueInRangeForNumericType<T>(rhs.value()))) {}
+
+ constexpr bool is_valid() const {
+ // Written this way because std::isfinite is not reliably constexpr.
+ return MustTreatAsConstexpr(value_)
+ ? value_ <= std::numeric_limits<T>::max() &&
+ value_ >= std::numeric_limits<T>::lowest()
+ : std::isfinite(value_);
+ }
+ constexpr T value() const { return value_; }
+};
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_NUMERICS_CHECKED_MATH_IMPL_H_
diff --git a/base/numerics/clamped_math.h b/base/numerics/clamped_math.h
new file mode 100644
index 0000000000..b184363886
--- /dev/null
+++ b/base/numerics/clamped_math.h
@@ -0,0 +1,262 @@
+// 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_NUMERICS_CLAMPED_MATH_H_
+#define BASE_NUMERICS_CLAMPED_MATH_H_
+
+#include <stddef.h>
+
+#include <limits>
+#include <type_traits>
+
+#include "base/numerics/clamped_math_impl.h"
+
+namespace base {
+namespace internal {
+
+template <typename T>
+class ClampedNumeric {
+ static_assert(std::is_arithmetic<T>::value,
+ "ClampedNumeric<T>: T must be a numeric type.");
+
+ public:
+ using type = T;
+
+ constexpr ClampedNumeric() : value_(0) {}
+
+ // Copy constructor.
+ template <typename Src>
+ constexpr ClampedNumeric(const ClampedNumeric<Src>& rhs)
+ : value_(saturated_cast<T>(rhs.value_)) {}
+
+ template <typename Src>
+ friend class ClampedNumeric;
+
+ // This is not an explicit constructor because we implicitly upgrade regular
+ // numerics to ClampedNumerics to make them easier to use.
+ template <typename Src>
+ constexpr ClampedNumeric(Src value) // NOLINT(runtime/explicit)
+ : value_(saturated_cast<T>(value)) {
+ static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
+ }
+
+ // This is not an explicit constructor because we want a seamless conversion
+ // from StrictNumeric types.
+ template <typename Src>
+ constexpr ClampedNumeric(
+ StrictNumeric<Src> value) // NOLINT(runtime/explicit)
+ : value_(saturated_cast<T>(static_cast<Src>(value))) {}
+
+ // Returns a ClampedNumeric of the specified type, cast from the current
+ // ClampedNumeric, and saturated to the destination type.
+ template <typename Dst>
+ constexpr ClampedNumeric<typename UnderlyingType<Dst>::type> Cast() const {
+ return *this;
+ }
+
+ // Prototypes for the supported arithmetic operator overloads.
+ template <typename Src>
+ constexpr ClampedNumeric& operator+=(const Src rhs);
+ template <typename Src>
+ constexpr ClampedNumeric& operator-=(const Src rhs);
+ template <typename Src>
+ constexpr ClampedNumeric& operator*=(const Src rhs);
+ template <typename Src>
+ constexpr ClampedNumeric& operator/=(const Src rhs);
+ template <typename Src>
+ constexpr ClampedNumeric& operator%=(const Src rhs);
+ template <typename Src>
+ constexpr ClampedNumeric& operator<<=(const Src rhs);
+ template <typename Src>
+ constexpr ClampedNumeric& operator>>=(const Src rhs);
+ template <typename Src>
+ constexpr ClampedNumeric& operator&=(const Src rhs);
+ template <typename Src>
+ constexpr ClampedNumeric& operator|=(const Src rhs);
+ template <typename Src>
+ constexpr ClampedNumeric& operator^=(const Src rhs);
+
+ constexpr ClampedNumeric operator-() const {
+ // The negation of two's complement int min is int min, so that's the
+ // only overflow case where we will saturate.
+ return ClampedNumeric<T>(SaturatedNegWrapper(value_));
+ }
+
+ constexpr ClampedNumeric operator~() const {
+ return ClampedNumeric<decltype(InvertWrapper(T()))>(InvertWrapper(value_));
+ }
+
+ constexpr ClampedNumeric Abs() const {
+ // The negation of two's complement int min is int min, so that's the
+ // only overflow case where we will saturate.
+ return ClampedNumeric<T>(SaturatedAbsWrapper(value_));
+ }
+
+ template <typename U>
+ constexpr ClampedNumeric<typename MathWrapper<ClampedMaxOp, T, U>::type> Max(
+ const U rhs) const {
+ using result_type = typename MathWrapper<ClampedMaxOp, T, U>::type;
+ return ClampedNumeric<result_type>(
+ ClampedMaxOp<T, U>::Do(value_, Wrapper<U>::value(rhs)));
+ }
+
+ template <typename U>
+ constexpr ClampedNumeric<typename MathWrapper<ClampedMinOp, T, U>::type> Min(
+ const U rhs) const {
+ using result_type = typename MathWrapper<ClampedMinOp, T, U>::type;
+ return ClampedNumeric<result_type>(
+ ClampedMinOp<T, U>::Do(value_, Wrapper<U>::value(rhs)));
+ }
+
+ // This function is available only for integral types. It returns an unsigned
+ // integer of the same width as the source type, containing the absolute value
+ // of the source, and properly handling signed min.
+ constexpr ClampedNumeric<typename UnsignedOrFloatForSize<T>::type>
+ UnsignedAbs() const {
+ return ClampedNumeric<typename UnsignedOrFloatForSize<T>::type>(
+ SafeUnsignedAbs(value_));
+ }
+
+ constexpr ClampedNumeric& operator++() {
+ *this += 1;
+ return *this;
+ }
+
+ constexpr ClampedNumeric operator++(int) {
+ ClampedNumeric value = *this;
+ *this += 1;
+ return value;
+ }
+
+ constexpr ClampedNumeric& operator--() {
+ *this -= 1;
+ return *this;
+ }
+
+ constexpr ClampedNumeric operator--(int) {
+ ClampedNumeric value = *this;
+ *this -= 1;
+ return value;
+ }
+
+ // These perform the actual math operations on the ClampedNumerics.
+ // Binary arithmetic operations.
+ template <template <typename, typename, typename> class M,
+ typename L,
+ typename R>
+ static constexpr ClampedNumeric MathOp(const L lhs, const R rhs) {
+ using Math = typename MathWrapper<M, L, R>::math;
+ return ClampedNumeric<T>(
+ Math::template Do<T>(Wrapper<L>::value(lhs), Wrapper<R>::value(rhs)));
+ }
+
+ // Assignment arithmetic operations.
+ template <template <typename, typename, typename> class M, typename R>
+ constexpr ClampedNumeric& MathOp(const R rhs) {
+ using Math = typename MathWrapper<M, T, R>::math;
+ *this =
+ ClampedNumeric<T>(Math::template Do<T>(value_, Wrapper<R>::value(rhs)));
+ return *this;
+ }
+
+ template <typename Dst>
+ constexpr operator Dst() const {
+ return saturated_cast<typename ArithmeticOrUnderlyingEnum<Dst>::type>(
+ value_);
+ }
+
+ // This method extracts the raw integer value without saturating it to the
+ // destination type as the conversion operator does. This is useful when
+ // e.g. assigning to an auto type or passing as a deduced template parameter.
+ constexpr T RawValue() const { return value_; }
+
+ private:
+ T value_;
+
+ // These wrappers allow us to handle state the same way for both
+ // ClampedNumeric and POD arithmetic types.
+ template <typename Src>
+ struct Wrapper {
+ static constexpr Src value(Src value) {
+ return static_cast<typename UnderlyingType<Src>::type>(value);
+ }
+ };
+};
+
+// Convience wrapper to return a new ClampedNumeric from the provided arithmetic
+// or ClampedNumericType.
+template <typename T>
+constexpr ClampedNumeric<typename UnderlyingType<T>::type> MakeClampedNum(
+ const T value) {
+ return value;
+}
+
+// Overload the ostream output operator to make logging work nicely.
+template <typename T>
+std::ostream& operator<<(std::ostream& os, const ClampedNumeric<T>& value) {
+ os << static_cast<T>(value);
+ return os;
+}
+
+// These implement the variadic wrapper for the math operations.
+template <template <typename, typename, typename> class M,
+ typename L,
+ typename R>
+constexpr ClampedNumeric<typename MathWrapper<M, L, R>::type> ClampMathOp(
+ const L lhs,
+ const R rhs) {
+ using Math = typename MathWrapper<M, L, R>::math;
+ return ClampedNumeric<typename Math::result_type>::template MathOp<M>(lhs,
+ rhs);
+}
+
+// General purpose wrapper template for arithmetic operations.
+template <template <typename, typename, typename> class M,
+ typename L,
+ typename R,
+ typename... Args>
+constexpr ClampedNumeric<typename ResultType<M, L, R, Args...>::type>
+ClampMathOp(const L lhs, const R rhs, const Args... args) {
+ return ClampMathOp<M>(ClampMathOp<M>(lhs, rhs), args...);
+}
+
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Add, +, +=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Sub, -, -=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Mul, *, *=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Div, /, /=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Mod, %, %=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Lsh, <<, <<=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Rsh, >>, >>=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, And, &, &=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Or, |, |=)
+BASE_NUMERIC_ARITHMETIC_OPERATORS(Clamped, Clamp, Xor, ^, ^=)
+BASE_NUMERIC_ARITHMETIC_VARIADIC(Clamped, Clamp, Max)
+BASE_NUMERIC_ARITHMETIC_VARIADIC(Clamped, Clamp, Min)
+BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsLess, <);
+BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsLessOrEqual, <=);
+BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsGreater, >);
+BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsGreaterOrEqual, >=);
+BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsEqual, ==);
+BASE_NUMERIC_COMPARISON_OPERATORS(Clamped, IsNotEqual, !=);
+
+} // namespace internal
+
+using internal::ClampedNumeric;
+using internal::MakeClampedNum;
+using internal::ClampMax;
+using internal::ClampMin;
+using internal::ClampAdd;
+using internal::ClampSub;
+using internal::ClampMul;
+using internal::ClampDiv;
+using internal::ClampMod;
+using internal::ClampLsh;
+using internal::ClampRsh;
+using internal::ClampAnd;
+using internal::ClampOr;
+using internal::ClampXor;
+
+} // namespace base
+
+#endif // BASE_NUMERICS_CLAMPED_MATH_H_
diff --git a/base/numerics/clamped_math_impl.h b/base/numerics/clamped_math_impl.h
new file mode 100644
index 0000000000..303a7e945a
--- /dev/null
+++ b/base/numerics/clamped_math_impl.h
@@ -0,0 +1,341 @@
+// 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_NUMERICS_CLAMPED_MATH_IMPL_H_
+#define BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <climits>
+#include <cmath>
+#include <cstdlib>
+#include <limits>
+#include <type_traits>
+
+#include "base/numerics/checked_math.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/numerics/safe_math_shared_impl.h"
+
+namespace base {
+namespace internal {
+
+template <typename T,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_signed<T>::value>::type* = nullptr>
+constexpr T SaturatedNegWrapper(T value) {
+ return MustTreatAsConstexpr(value) || !ClampedNegFastOp<T>::is_supported
+ ? (NegateWrapper(value) != std::numeric_limits<T>::lowest()
+ ? NegateWrapper(value)
+ : std::numeric_limits<T>::max())
+ : ClampedNegFastOp<T>::Do(value);
+}
+
+template <typename T,
+ typename std::enable_if<std::is_integral<T>::value &&
+ !std::is_signed<T>::value>::type* = nullptr>
+constexpr T SaturatedNegWrapper(T value) {
+ return T(0);
+}
+
+template <
+ typename T,
+ typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
+constexpr T SaturatedNegWrapper(T value) {
+ return -value;
+}
+
+template <typename T,
+ typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+constexpr T SaturatedAbsWrapper(T value) {
+ // The calculation below is a static identity for unsigned types, but for
+ // signed integer types it provides a non-branching, saturated absolute value.
+ // This works because SafeUnsignedAbs() returns an unsigned type, which can
+ // represent the absolute value of all negative numbers of an equal-width
+ // integer type. The call to IsValueNegative() then detects overflow in the
+ // special case of numeric_limits<T>::min(), by evaluating the bit pattern as
+ // a signed integer value. If it is the overflow case, we end up subtracting
+ // one from the unsigned result, thus saturating to numeric_limits<T>::max().
+ return static_cast<T>(SafeUnsignedAbs(value) -
+ IsValueNegative<T>(SafeUnsignedAbs(value)));
+}
+
+template <
+ typename T,
+ typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
+constexpr T SaturatedAbsWrapper(T value) {
+ return value < 0 ? -value : value;
+}
+
+template <typename T, typename U, class Enable = void>
+struct ClampedAddOp {};
+
+template <typename T, typename U>
+struct ClampedAddOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = typename MaxExponentPromotion<T, U>::type;
+ template <typename V = result_type>
+ static constexpr V Do(T x, U y) {
+ if (ClampedAddFastOp<T, U>::is_supported)
+ return ClampedAddFastOp<T, U>::template Do<V>(x, y);
+
+ static_assert(std::is_same<V, result_type>::value ||
+ IsTypeInRangeForNumericType<U, V>::value,
+ "The saturation result cannot be determined from the "
+ "provided types.");
+ const V saturated = CommonMaxOrMin<V>(IsValueNegative(y));
+ V result = {};
+ return BASE_NUMERICS_LIKELY((CheckedAddOp<T, U>::Do(x, y, &result)))
+ ? result
+ : saturated;
+ }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedSubOp {};
+
+template <typename T, typename U>
+struct ClampedSubOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = typename MaxExponentPromotion<T, U>::type;
+ template <typename V = result_type>
+ static constexpr V Do(T x, U y) {
+ // TODO(jschuh) Make this "constexpr if" once we're C++17.
+ if (ClampedSubFastOp<T, U>::is_supported)
+ return ClampedSubFastOp<T, U>::template Do<V>(x, y);
+
+ static_assert(std::is_same<V, result_type>::value ||
+ IsTypeInRangeForNumericType<U, V>::value,
+ "The saturation result cannot be determined from the "
+ "provided types.");
+ const V saturated = CommonMaxOrMin<V>(!IsValueNegative(y));
+ V result = {};
+ return BASE_NUMERICS_LIKELY((CheckedSubOp<T, U>::Do(x, y, &result)))
+ ? result
+ : saturated;
+ }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedMulOp {};
+
+template <typename T, typename U>
+struct ClampedMulOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = typename MaxExponentPromotion<T, U>::type;
+ template <typename V = result_type>
+ static constexpr V Do(T x, U y) {
+ // TODO(jschuh) Make this "constexpr if" once we're C++17.
+ if (ClampedMulFastOp<T, U>::is_supported)
+ return ClampedMulFastOp<T, U>::template Do<V>(x, y);
+
+ V result = {};
+ const V saturated =
+ CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y));
+ return BASE_NUMERICS_LIKELY((CheckedMulOp<T, U>::Do(x, y, &result)))
+ ? result
+ : saturated;
+ }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedDivOp {};
+
+template <typename T, typename U>
+struct ClampedDivOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = typename MaxExponentPromotion<T, U>::type;
+ template <typename V = result_type>
+ static constexpr V Do(T x, U y) {
+ V result = {};
+ if (BASE_NUMERICS_LIKELY((CheckedDivOp<T, U>::Do(x, y, &result))))
+ return result;
+ // Saturation goes to max, min, or NaN (if x is zero).
+ return x ? CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y))
+ : SaturationDefaultLimits<V>::NaN();
+ }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedModOp {};
+
+template <typename T, typename U>
+struct ClampedModOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = typename MaxExponentPromotion<T, U>::type;
+ template <typename V = result_type>
+ static constexpr V Do(T x, U y) {
+ V result = {};
+ return BASE_NUMERICS_LIKELY((CheckedModOp<T, U>::Do(x, y, &result)))
+ ? result
+ : x;
+ }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedLshOp {};
+
+// Left shift. Non-zero values saturate in the direction of the sign. A zero
+// shifted by any value always results in zero.
+template <typename T, typename U>
+struct ClampedLshOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = T;
+ template <typename V = result_type>
+ static constexpr V Do(T x, U shift) {
+ static_assert(!std::is_signed<U>::value, "Shift value must be unsigned.");
+ if (BASE_NUMERICS_LIKELY(shift < std::numeric_limits<T>::digits)) {
+ // Shift as unsigned to avoid undefined behavior.
+ V result = static_cast<V>(as_unsigned(x) << shift);
+ // If the shift can be reversed, we know it was valid.
+ if (BASE_NUMERICS_LIKELY(result >> shift == x))
+ return result;
+ }
+ return x ? CommonMaxOrMin<V>(IsValueNegative(x)) : 0;
+ }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedRshOp {};
+
+// Right shift. Negative values saturate to -1. Positive or 0 saturates to 0.
+template <typename T, typename U>
+struct ClampedRshOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = T;
+ template <typename V = result_type>
+ static constexpr V Do(T x, U shift) {
+ static_assert(!std::is_signed<U>::value, "Shift value must be unsigned.");
+ // Signed right shift is odd, because it saturates to -1 or 0.
+ const V saturated = as_unsigned(V(0)) - IsValueNegative(x);
+ return BASE_NUMERICS_LIKELY(shift < IntegerBitsPlusSign<T>::value)
+ ? saturated_cast<V>(x >> shift)
+ : saturated;
+ }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedAndOp {};
+
+template <typename T, typename U>
+struct ClampedAndOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = typename std::make_unsigned<
+ typename MaxExponentPromotion<T, U>::type>::type;
+ template <typename V>
+ static constexpr V Do(T x, U y) {
+ return static_cast<result_type>(x) & static_cast<result_type>(y);
+ }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedOrOp {};
+
+// For simplicity we promote to unsigned integers.
+template <typename T, typename U>
+struct ClampedOrOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = typename std::make_unsigned<
+ typename MaxExponentPromotion<T, U>::type>::type;
+ template <typename V>
+ static constexpr V Do(T x, U y) {
+ return static_cast<result_type>(x) | static_cast<result_type>(y);
+ }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedXorOp {};
+
+// For simplicity we support only unsigned integers.
+template <typename T, typename U>
+struct ClampedXorOp<T,
+ U,
+ typename std::enable_if<std::is_integral<T>::value &&
+ std::is_integral<U>::value>::type> {
+ using result_type = typename std::make_unsigned<
+ typename MaxExponentPromotion<T, U>::type>::type;
+ template <typename V>
+ static constexpr V Do(T x, U y) {
+ return static_cast<result_type>(x) ^ static_cast<result_type>(y);
+ }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedMaxOp {};
+
+template <typename T, typename U>
+struct ClampedMaxOp<
+ T,
+ U,
+ typename std::enable_if<std::is_arithmetic<T>::value &&
+ std::is_arithmetic<U>::value>::type> {
+ using result_type = typename MaxExponentPromotion<T, U>::type;
+ template <typename V = result_type>
+ static constexpr V Do(T x, U y) {
+ return IsGreater<T, U>::Test(x, y) ? saturated_cast<V>(x)
+ : saturated_cast<V>(y);
+ }
+};
+
+template <typename T, typename U, class Enable = void>
+struct ClampedMinOp {};
+
+template <typename T, typename U>
+struct ClampedMinOp<
+ T,
+ U,
+ typename std::enable_if<std::is_arithmetic<T>::value &&
+ std::is_arithmetic<U>::value>::type> {
+ using result_type = typename LowestValuePromotion<T, U>::type;
+ template <typename V = result_type>
+ static constexpr V Do(T x, U y) {
+ return IsLess<T, U>::Test(x, y) ? saturated_cast<V>(x)
+ : saturated_cast<V>(y);
+ }
+};
+
+// This is just boilerplate that wraps the standard floating point arithmetic.
+// A macro isn't the nicest solution, but it beats rewriting these repeatedly.
+#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \
+ template <typename T, typename U> \
+ struct Clamped##NAME##Op< \
+ T, U, \
+ typename std::enable_if<std::is_floating_point<T>::value || \
+ std::is_floating_point<U>::value>::type> { \
+ using result_type = typename MaxExponentPromotion<T, U>::type; \
+ template <typename V = result_type> \
+ static constexpr V Do(T x, U y) { \
+ return saturated_cast<V>(x OP y); \
+ } \
+ };
+
+BASE_FLOAT_ARITHMETIC_OPS(Add, +)
+BASE_FLOAT_ARITHMETIC_OPS(Sub, -)
+BASE_FLOAT_ARITHMETIC_OPS(Mul, *)
+BASE_FLOAT_ARITHMETIC_OPS(Div, /)
+
+#undef BASE_FLOAT_ARITHMETIC_OPS
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_NUMERICS_CLAMPED_MATH_IMPL_H_
diff --git a/base/numerics/math_constants.h b/base/numerics/math_constants.h
new file mode 100644
index 0000000000..9a5b8efdbc
--- /dev/null
+++ b/base/numerics/math_constants.h
@@ -0,0 +1,15 @@
+// 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_NUMERICS_MATH_CONSTANTS_H_
+#define BASE_NUMERICS_MATH_CONSTANTS_H_
+
+namespace base {
+
+constexpr double kPiDouble = 3.14159265358979323846;
+constexpr float kPiFloat = 3.14159265358979323846f;
+
+} // namespace base
+
+#endif // BASE_NUMERICS_MATH_CONSTANTS_H_
diff --git a/base/numerics/ranges.h b/base/numerics/ranges.h
new file mode 100644
index 0000000000..f19320cedd
--- /dev/null
+++ b/base/numerics/ranges.h
@@ -0,0 +1,27 @@
+// 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_NUMERICS_RANGES_H_
+#define BASE_NUMERICS_RANGES_H_
+
+#include <algorithm>
+#include <cmath>
+
+namespace base {
+
+// To be replaced with std::clamp() from C++17, someday.
+template <class T>
+constexpr const T& ClampToRange(const T& value, const T& min, const T& max) {
+ return std::min(std::max(value, min), max);
+}
+
+template <typename T>
+constexpr bool IsApproximatelyEqual(T lhs, T rhs, T tolerance) {
+ static_assert(std::is_arithmetic<T>::value, "Argument must be arithmetic");
+ return std::abs(rhs - lhs) <= tolerance;
+}
+
+} // namespace base
+
+#endif // BASE_NUMERICS_RANGES_H_
diff --git a/base/numerics/safe_conversions.h b/base/numerics/safe_conversions.h
index b0ec279eb5..9284f8fa72 100644
--- a/base/numerics/safe_conversions.h
+++ b/base/numerics/safe_conversions.h
@@ -13,90 +13,120 @@
#include "base/numerics/safe_conversions_impl.h"
+#if !defined(__native_client__) && (defined(__ARMEL__) || defined(__arch64__))
+#include "base/numerics/safe_conversions_arm_impl.h"
+#define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (1)
+#else
+#define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (0)
+#endif
+
namespace base {
+namespace internal {
+
+#if !BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
+template <typename Dst, typename Src>
+struct SaturateFastAsmOp {
+ static const bool is_supported = false;
+ static constexpr Dst Do(Src) {
+ // Force a compile failure if instantiated.
+ return CheckOnFailure::template HandleFailure<Dst>();
+ }
+};
+#endif // BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
+#undef BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS
+
+// The following special case a few specific integer conversions where we can
+// eke out better performance than range checking.
+template <typename Dst, typename Src, typename Enable = void>
+struct IsValueInRangeFastOp {
+ static const bool is_supported = false;
+ static constexpr bool Do(Src value) {
+ // Force a compile failure if instantiated.
+ return CheckOnFailure::template HandleFailure<bool>();
+ }
+};
-// The following are helper constexpr template functions and classes for safely
-// performing a range of conversions, assignments, and tests:
-//
-// checked_cast<> - Analogous to static_cast<> for numeric types, except
-// that it CHECKs that the specified numeric conversion will not overflow
-// or underflow. NaN source will always trigger a CHECK.
-// The default CHECK triggers a crash, but the handler can be overriden.
-// saturated_cast<> - Analogous to static_cast<> for numeric types, except
-// that it returns a saturated result when the specified numeric conversion
-// would otherwise overflow or underflow. An NaN source returns 0 by
-// default, but can be overridden to return a different result.
-// strict_cast<> - Analogous to static_cast<> for numeric types, except that
-// it will cause a compile failure if the destination type is not large
-// enough to contain any value in the source type. It performs no runtime
-// checking and thus introduces no runtime overhead.
-// IsValueInRangeForNumericType<>() - A convenience function that returns true
-// if the type supplied to the template parameter can represent the value
-// passed as an argument to the function.
-// IsValueNegative<>() - A convenience function that will accept any arithmetic
-// type as an argument and will return whether the value is less than zero.
-// Unsigned types always return false.
-// SafeUnsignedAbs() - Returns the absolute value of the supplied integer
-// parameter as an unsigned result (thus avoiding an overflow if the value
-// is the signed, two's complement minimum).
-// StrictNumeric<> - A wrapper type that performs assignments and copies via
-// the strict_cast<> template, and can perform valid arithmetic comparisons
-// across any range of arithmetic types. StrictNumeric is the return type
-// for values extracted from a CheckedNumeric class instance. The raw
-// arithmetic value is extracted via static_cast to the underlying type.
-// MakeStrictNum() - Creates a new StrictNumeric from the underlying type of
-// the supplied arithmetic or StrictNumeric type.
+// Signed to signed range comparison.
+template <typename Dst, typename Src>
+struct IsValueInRangeFastOp<
+ Dst,
+ Src,
+ typename std::enable_if<
+ std::is_integral<Dst>::value && std::is_integral<Src>::value &&
+ std::is_signed<Dst>::value && std::is_signed<Src>::value &&
+ !IsTypeInRangeForNumericType<Dst, Src>::value>::type> {
+ static const bool is_supported = true;
+
+ static constexpr bool Do(Src value) {
+ // Just downcast to the smaller type, sign extend it back to the original
+ // type, and then see if it matches the original value.
+ return value == static_cast<Dst>(value);
+ }
+};
+
+// Signed to unsigned range comparison.
+template <typename Dst, typename Src>
+struct IsValueInRangeFastOp<
+ Dst,
+ Src,
+ typename std::enable_if<
+ std::is_integral<Dst>::value && std::is_integral<Src>::value &&
+ !std::is_signed<Dst>::value && std::is_signed<Src>::value &&
+ !IsTypeInRangeForNumericType<Dst, Src>::value>::type> {
+ static const bool is_supported = true;
+
+ static constexpr bool Do(Src value) {
+ // We cast a signed as unsigned to overflow negative values to the top,
+ // then compare against whichever maximum is smaller, as our upper bound.
+ return as_unsigned(value) <= as_unsigned(CommonMax<Src, Dst>());
+ }
+};
// Convenience function that returns true if the supplied value is in range
// for the destination type.
template <typename Dst, typename Src>
constexpr bool IsValueInRangeForNumericType(Src value) {
- return internal::DstRangeRelationToSrcRange<Dst>(value).IsValid();
+ using SrcType = typename internal::UnderlyingType<Src>::type;
+ return internal::IsValueInRangeFastOp<Dst, SrcType>::is_supported
+ ? internal::IsValueInRangeFastOp<Dst, SrcType>::Do(
+ static_cast<SrcType>(value))
+ : internal::DstRangeRelationToSrcRange<Dst>(
+ static_cast<SrcType>(value))
+ .IsValid();
}
-// Forces a crash, like a CHECK(false). Used for numeric boundary errors.
-struct CheckOnFailure {
- template <typename T>
- static T HandleFailure() {
-#if defined(__GNUC__) || defined(__clang__)
- __builtin_trap();
-#else
- ((void)(*(volatile char*)0 = 0));
-#endif
- return T();
- }
-};
-
// checked_cast<> is analogous to static_cast<> for numeric types,
// except that it CHECKs that the specified numeric conversion will not
// overflow or underflow. NaN source will always trigger a CHECK.
template <typename Dst,
- class CheckHandler = CheckOnFailure,
+ class CheckHandler = internal::CheckOnFailure,
typename Src>
constexpr Dst checked_cast(Src value) {
// This throws a compile-time error on evaluating the constexpr if it can be
// determined at compile-time as failing, otherwise it will CHECK at runtime.
using SrcType = typename internal::UnderlyingType<Src>::type;
- return IsValueInRangeForNumericType<Dst, SrcType>(value)
+ return BASE_NUMERICS_LIKELY((IsValueInRangeForNumericType<Dst>(value)))
? static_cast<Dst>(static_cast<SrcType>(value))
: CheckHandler::template HandleFailure<Dst>();
}
// Default boundaries for integral/float: max/infinity, lowest/-infinity, 0/NaN.
+// You may provide your own limits (e.g. to saturated_cast) so long as you
+// implement all of the static constexpr member functions in the class below.
template <typename T>
-struct SaturationDefaultHandler {
+struct SaturationDefaultLimits : public std::numeric_limits<T> {
static constexpr T NaN() {
return std::numeric_limits<T>::has_quiet_NaN
? std::numeric_limits<T>::quiet_NaN()
: T();
}
- static constexpr T max() { return std::numeric_limits<T>::max(); }
+ using std::numeric_limits<T>::max;
static constexpr T Overflow() {
return std::numeric_limits<T>::has_infinity
? std::numeric_limits<T>::infinity()
: std::numeric_limits<T>::max();
}
- static constexpr T lowest() { return std::numeric_limits<T>::lowest(); }
+ using std::numeric_limits<T>::lowest;
static constexpr T Underflow() {
return std::numeric_limits<T>::has_infinity
? std::numeric_limits<T>::infinity() * -1
@@ -104,8 +134,6 @@ struct SaturationDefaultHandler {
}
};
-namespace internal {
-
template <typename Dst, template <typename> class S, typename Src>
constexpr Dst saturated_cast_impl(Src value, RangeCheck constraint) {
// For some reason clang generates much better code when the branch is
@@ -119,19 +147,59 @@ constexpr Dst saturated_cast_impl(Src value, RangeCheck constraint) {
: S<Dst>::NaN());
}
+// We can reduce the number of conditions and get slightly better performance
+// for normal signed and unsigned integer ranges. And in the specific case of
+// Arm, we can use the optimized saturation instructions.
+template <typename Dst, typename Src, typename Enable = void>
+struct SaturateFastOp {
+ static const bool is_supported = false;
+ static constexpr Dst Do(Src value) {
+ // Force a compile failure if instantiated.
+ return CheckOnFailure::template HandleFailure<Dst>();
+ }
+};
+
+template <typename Dst, typename Src>
+struct SaturateFastOp<
+ Dst,
+ Src,
+ typename std::enable_if<std::is_integral<Src>::value &&
+ std::is_integral<Dst>::value>::type> {
+ static const bool is_supported = true;
+ static Dst Do(Src value) {
+ if (SaturateFastAsmOp<Dst, Src>::is_supported)
+ return SaturateFastAsmOp<Dst, Src>::Do(value);
+
+ // The exact order of the following is structured to hit the correct
+ // optimization heuristics across compilers. Do not change without
+ // checking the emitted code.
+ Dst saturated = CommonMaxOrMin<Dst, Src>(
+ IsMaxInRangeForNumericType<Dst, Src>() ||
+ (!IsMinInRangeForNumericType<Dst, Src>() && IsValueNegative(value)));
+ return BASE_NUMERICS_LIKELY(IsValueInRangeForNumericType<Dst>(value))
+ ? static_cast<Dst>(value)
+ : saturated;
+ }
+};
+
// saturated_cast<> is analogous to static_cast<> for numeric types, except
// that the specified numeric conversion will saturate by default rather than
// overflow or underflow, and NaN assignment to an integral will return 0.
// All boundary condition behaviors can be overriden with a custom handler.
template <typename Dst,
- template <typename>
- class SaturationHandler = SaturationDefaultHandler,
+ template <typename> class SaturationHandler = SaturationDefaultLimits,
typename Src>
constexpr Dst saturated_cast(Src value) {
using SrcType = typename UnderlyingType<Src>::type;
- return saturated_cast_impl<Dst, SaturationHandler, SrcType>(
- value,
- DstRangeRelationToSrcRange<Dst, SaturationHandler, SrcType>(value));
+ return !IsCompileTimeConstant(value) &&
+ SaturateFastOp<Dst, SrcType>::is_supported &&
+ std::is_same<SaturationHandler<Dst>,
+ SaturationDefaultLimits<Dst>>::value
+ ? SaturateFastOp<Dst, SrcType>::Do(static_cast<SrcType>(value))
+ : saturated_cast_impl<Dst, SaturationHandler, SrcType>(
+ static_cast<SrcType>(value),
+ DstRangeRelationToSrcRange<Dst, SaturationHandler, SrcType>(
+ static_cast<SrcType>(value)));
}
// strict_cast<> is analogous to static_cast<> for numeric types, except that
@@ -238,30 +306,34 @@ std::ostream& operator<<(std::ostream& os, const StrictNumeric<T>& value) {
return os;
}
-#define STRICT_COMPARISON_OP(NAME, OP) \
- template <typename L, typename R, \
- typename std::enable_if< \
- internal::IsStrictOp<L, R>::value>::type* = nullptr> \
- constexpr bool operator OP(const L lhs, const R rhs) { \
- return SafeCompare<NAME, typename UnderlyingType<L>::type, \
- typename UnderlyingType<R>::type>(lhs, rhs); \
+#define BASE_NUMERIC_COMPARISON_OPERATORS(CLASS, NAME, OP) \
+ template <typename L, typename R, \
+ typename std::enable_if< \
+ internal::Is##CLASS##Op<L, R>::value>::type* = nullptr> \
+ constexpr bool operator OP(const L lhs, const R rhs) { \
+ return SafeCompare<NAME, typename UnderlyingType<L>::type, \
+ typename UnderlyingType<R>::type>(lhs, rhs); \
}
-STRICT_COMPARISON_OP(IsLess, <);
-STRICT_COMPARISON_OP(IsLessOrEqual, <=);
-STRICT_COMPARISON_OP(IsGreater, >);
-STRICT_COMPARISON_OP(IsGreaterOrEqual, >=);
-STRICT_COMPARISON_OP(IsEqual, ==);
-STRICT_COMPARISON_OP(IsNotEqual, !=);
+BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsLess, <);
+BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsLessOrEqual, <=);
+BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsGreater, >);
+BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsGreaterOrEqual, >=);
+BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsEqual, ==);
+BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsNotEqual, !=);
-#undef STRICT_COMPARISON_OP
-};
+}; // namespace internal
+using internal::as_signed;
+using internal::as_unsigned;
+using internal::checked_cast;
using internal::strict_cast;
using internal::saturated_cast;
using internal::SafeUnsignedAbs;
using internal::StrictNumeric;
using internal::MakeStrictNum;
+using internal::IsValueInRangeForNumericType;
+using internal::IsTypeInRangeForNumericType;
using internal::IsValueNegative;
// Explicitly make a shorter size_t alias for convenience.
diff --git a/base/numerics/safe_conversions_arm_impl.h b/base/numerics/safe_conversions_arm_impl.h
new file mode 100644
index 0000000000..da5813f65e
--- /dev/null
+++ b/base/numerics/safe_conversions_arm_impl.h
@@ -0,0 +1,51 @@
+// 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_NUMERICS_SAFE_CONVERSIONS_ARM_IMPL_H_
+#define BASE_NUMERICS_SAFE_CONVERSIONS_ARM_IMPL_H_
+
+#include <cassert>
+#include <limits>
+#include <type_traits>
+
+#include "base/numerics/safe_conversions_impl.h"
+
+namespace base {
+namespace internal {
+
+// Fast saturation to a destination type.
+template <typename Dst, typename Src>
+struct SaturateFastAsmOp {
+ static const bool is_supported =
+ std::is_signed<Src>::value && std::is_integral<Dst>::value &&
+ std::is_integral<Src>::value &&
+ IntegerBitsPlusSign<Src>::value <= IntegerBitsPlusSign<int32_t>::value &&
+ IntegerBitsPlusSign<Dst>::value <= IntegerBitsPlusSign<int32_t>::value &&
+ !IsTypeInRangeForNumericType<Dst, Src>::value;
+
+ __attribute__((always_inline)) static Dst Do(Src value) {
+ int32_t src = value;
+ typename std::conditional<std::is_signed<Dst>::value, int32_t,
+ uint32_t>::type result;
+ if (std::is_signed<Dst>::value) {
+ asm("ssat %[dst], %[shift], %[src]"
+ : [dst] "=r"(result)
+ : [src] "r"(src), [shift] "n"(IntegerBitsPlusSign<Dst>::value <= 32
+ ? IntegerBitsPlusSign<Dst>::value
+ : 32));
+ } else {
+ asm("usat %[dst], %[shift], %[src]"
+ : [dst] "=r"(result)
+ : [src] "r"(src), [shift] "n"(IntegerBitsPlusSign<Dst>::value < 32
+ ? IntegerBitsPlusSign<Dst>::value
+ : 31));
+ }
+ return static_cast<Dst>(result);
+ }
+};
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_NUMERICS_SAFE_CONVERSIONS_ARM_IMPL_H_
diff --git a/base/numerics/safe_conversions_impl.h b/base/numerics/safe_conversions_impl.h
index 24357fd6a5..2516204499 100644
--- a/base/numerics/safe_conversions_impl.h
+++ b/base/numerics/safe_conversions_impl.h
@@ -10,6 +10,14 @@
#include <limits>
#include <type_traits>
+#if defined(__GNUC__) || defined(__clang__)
+#define BASE_NUMERICS_LIKELY(x) __builtin_expect(!!(x), 1)
+#define BASE_NUMERICS_UNLIKELY(x) __builtin_expect(!!(x), 0)
+#else
+#define BASE_NUMERICS_LIKELY(x) (x)
+#define BASE_NUMERICS_UNLIKELY(x) (x)
+#endif
+
namespace base {
namespace internal {
@@ -76,6 +84,48 @@ constexpr typename std::make_unsigned<T>::type SafeUnsignedAbs(T value) {
: static_cast<UnsignedT>(value);
}
+// This allows us to switch paths on known compile-time constants.
+#if defined(__clang__) || defined(__GNUC__)
+constexpr bool CanDetectCompileTimeConstant() {
+ return true;
+}
+template <typename T>
+constexpr bool IsCompileTimeConstant(const T v) {
+ return __builtin_constant_p(v);
+}
+#else
+constexpr bool CanDetectCompileTimeConstant() {
+ return false;
+}
+template <typename T>
+constexpr bool IsCompileTimeConstant(const T) {
+ return false;
+}
+#endif
+template <typename T>
+constexpr bool MustTreatAsConstexpr(const T v) {
+ // Either we can't detect a compile-time constant, and must always use the
+ // constexpr path, or we know we have a compile-time constant.
+ return !CanDetectCompileTimeConstant() || IsCompileTimeConstant(v);
+}
+
+// Forces a crash, like a CHECK(false). Used for numeric boundary errors.
+// Also used in a constexpr template to trigger a compilation failure on
+// an error condition.
+struct CheckOnFailure {
+ template <typename T>
+ static T HandleFailure() {
+#if defined(_MSC_VER)
+ __debugbreak();
+#elif defined(__GNUC__) || defined(__clang__)
+ __builtin_trap();
+#else
+ ((void)(*(volatile char*)0 = 0));
+#endif
+ return T();
+ }
+};
+
enum IntegerRepresentation {
INTEGER_REPRESENTATION_UNSIGNED,
INTEGER_REPRESENTATION_SIGNED
@@ -337,6 +387,13 @@ struct DstRangeRelationToSrcRangeImpl<Dst,
}
};
+// Simple wrapper for statically checking if a type's range is contained.
+template <typename Dst, typename Src>
+struct IsTypeInRangeForNumericType {
+ static const bool value = StaticDstRangeRelationToSrcRange<Dst, Src>::value ==
+ NUMERIC_RANGE_CONTAINED;
+};
+
template <typename Dst,
template <typename> class Bounds = std::numeric_limits,
typename Src>
@@ -517,38 +574,16 @@ struct FastIntegerArithmeticPromotion<Lhs, Rhs, false> {
static const bool is_contained = false;
};
-// This hacks around libstdc++ 4.6 missing stuff in type_traits.
-#if defined(__GLIBCXX__)
-#define PRIV_GLIBCXX_4_7_0 20120322
-#define PRIV_GLIBCXX_4_5_4 20120702
-#define PRIV_GLIBCXX_4_6_4 20121127
-#if (__GLIBCXX__ < PRIV_GLIBCXX_4_7_0 || __GLIBCXX__ == PRIV_GLIBCXX_4_5_4 || \
- __GLIBCXX__ == PRIV_GLIBCXX_4_6_4)
-#define PRIV_USE_FALLBACKS_FOR_OLD_GLIBCXX
-#undef PRIV_GLIBCXX_4_7_0
-#undef PRIV_GLIBCXX_4_5_4
-#undef PRIV_GLIBCXX_4_6_4
-#endif
-#endif
-
// Extracts the underlying type from an enum.
template <typename T, bool is_enum = std::is_enum<T>::value>
struct ArithmeticOrUnderlyingEnum;
template <typename T>
struct ArithmeticOrUnderlyingEnum<T, true> {
-#if defined(PRIV_USE_FALLBACKS_FOR_OLD_GLIBCXX)
- using type = __underlying_type(T);
-#else
using type = typename std::underlying_type<T>::type;
-#endif
static const bool value = std::is_arithmetic<type>::value;
};
-#if defined(PRIV_USE_FALLBACKS_FOR_OLD_GLIBCXX)
-#undef PRIV_USE_FALLBACKS_FOR_OLD_GLIBCXX
-#endif
-
template <typename T>
struct ArithmeticOrUnderlyingEnum<T, false> {
using type = T;
@@ -560,6 +595,9 @@ template <typename T>
class CheckedNumeric;
template <typename T>
+class ClampedNumeric;
+
+template <typename T>
class StrictNumeric;
// Used to treat CheckedNumeric and arithmetic underlying types the same.
@@ -568,6 +606,7 @@ struct UnderlyingType {
using type = typename ArithmeticOrUnderlyingEnum<T>::type;
static const bool is_numeric = std::is_arithmetic<type>::value;
static const bool is_checked = false;
+ static const bool is_clamped = false;
static const bool is_strict = false;
};
@@ -576,6 +615,16 @@ struct UnderlyingType<CheckedNumeric<T>> {
using type = T;
static const bool is_numeric = true;
static const bool is_checked = true;
+ static const bool is_clamped = false;
+ static const bool is_strict = false;
+};
+
+template <typename T>
+struct UnderlyingType<ClampedNumeric<T>> {
+ using type = T;
+ static const bool is_numeric = true;
+ static const bool is_checked = false;
+ static const bool is_clamped = true;
static const bool is_strict = false;
};
@@ -584,6 +633,7 @@ struct UnderlyingType<StrictNumeric<T>> {
using type = T;
static const bool is_numeric = true;
static const bool is_checked = false;
+ static const bool is_clamped = false;
static const bool is_strict = true;
};
@@ -595,13 +645,47 @@ struct IsCheckedOp {
};
template <typename L, typename R>
-struct IsStrictOp {
+struct IsClampedOp {
static const bool value =
UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
- (UnderlyingType<L>::is_strict || UnderlyingType<R>::is_strict);
+ (UnderlyingType<L>::is_clamped || UnderlyingType<R>::is_clamped) &&
+ !(UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked);
};
template <typename L, typename R>
+struct IsStrictOp {
+ static const bool value =
+ UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
+ (UnderlyingType<L>::is_strict || UnderlyingType<R>::is_strict) &&
+ !(UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked) &&
+ !(UnderlyingType<L>::is_clamped || UnderlyingType<R>::is_clamped);
+};
+
+// as_signed<> returns the supplied integral value (or integral castable
+// Numeric template) cast as a signed integral of equivalent precision.
+// I.e. it's mostly an alias for: static_cast<std::make_signed<T>::type>(t)
+template <typename Src>
+constexpr typename std::make_signed<
+ typename base::internal::UnderlyingType<Src>::type>::type
+as_signed(const Src value) {
+ static_assert(std::is_integral<decltype(as_signed(value))>::value,
+ "Argument must be a signed or unsigned integer type.");
+ return static_cast<decltype(as_signed(value))>(value);
+}
+
+// as_unsigned<> returns the supplied integral value (or integral castable
+// Numeric template) cast as an unsigned integral of equivalent precision.
+// I.e. it's mostly an alias for: static_cast<std::make_unsigned<T>::type>(t)
+template <typename Src>
+constexpr typename std::make_unsigned<
+ typename base::internal::UnderlyingType<Src>::type>::type
+as_unsigned(const Src value) {
+ static_assert(std::is_integral<decltype(as_unsigned(value))>::value,
+ "Argument must be a signed or unsigned integer type.");
+ return static_cast<decltype(as_unsigned(value))>(value);
+}
+
+template <typename L, typename R>
constexpr bool IsLessImpl(const L lhs,
const R rhs,
const RangeCheck l_range,
@@ -724,7 +808,41 @@ constexpr bool SafeCompare(const L lhs, const R rhs) {
static_cast<BigType>(static_cast<R>(rhs)))
// Let the template functions figure it out for mixed types.
: C<L, R>::Test(lhs, rhs);
-};
+}
+
+template <typename Dst, typename Src>
+constexpr bool IsMaxInRangeForNumericType() {
+ return IsGreaterOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::max(),
+ std::numeric_limits<Src>::max());
+}
+
+template <typename Dst, typename Src>
+constexpr bool IsMinInRangeForNumericType() {
+ return IsLessOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::lowest(),
+ std::numeric_limits<Src>::lowest());
+}
+
+template <typename Dst, typename Src>
+constexpr Dst CommonMax() {
+ return !IsMaxInRangeForNumericType<Dst, Src>()
+ ? Dst(std::numeric_limits<Dst>::max())
+ : Dst(std::numeric_limits<Src>::max());
+}
+
+template <typename Dst, typename Src>
+constexpr Dst CommonMin() {
+ return !IsMinInRangeForNumericType<Dst, Src>()
+ ? Dst(std::numeric_limits<Dst>::lowest())
+ : Dst(std::numeric_limits<Src>::lowest());
+}
+
+// This is a wrapper to generate return the max or min for a supplied type.
+// If the argument is false, the returned value is the maximum. If true the
+// returned value is the minimum.
+template <typename Dst, typename Src = Dst>
+constexpr Dst CommonMaxOrMin(bool is_min) {
+ return is_min ? CommonMin<Dst, Src>() : CommonMax<Dst, Src>();
+}
} // namespace internal
} // namespace base
diff --git a/base/numerics/safe_math.h b/base/numerics/safe_math.h
index f5007db39c..e30be901f9 100644
--- a/base/numerics/safe_math.h
+++ b/base/numerics/safe_math.h
@@ -1,508 +1,12 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
+// 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_NUMERICS_SAFE_MATH_H_
#define BASE_NUMERICS_SAFE_MATH_H_
-#include <stddef.h>
-
-#include <limits>
-#include <type_traits>
-
-#include "base/numerics/safe_math_impl.h"
-
-namespace base {
-namespace internal {
-
-// CheckedNumeric<> implements all the logic and operators for detecting integer
-// boundary conditions such as overflow, underflow, and invalid conversions.
-// The CheckedNumeric type implicitly converts from floating point and integer
-// data types, and contains overloads for basic arithmetic operations (i.e.: +,
-// -, *, / for all types and %, <<, >>, &, |, ^ for integers). Type promotions
-// are a slightly modified version of the standard C arithmetic rules with the
-// two differences being that there is no default promotion to int and bitwise
-// logical operations always return an unsigned of the wider type.
-//
-// You may also use one of the variadic convenience functions, which accept
-// standard arithmetic or CheckedNumeric types, perform arithmetic operations,
-// and return a CheckedNumeric result. The supported functions are:
-// CheckAdd() - Addition.
-// CheckSub() - Subtraction.
-// CheckMul() - Multiplication.
-// CheckDiv() - Division.
-// CheckMod() - Modulous (integer only).
-// CheckLsh() - Left integer shift (integer only).
-// CheckRsh() - Right integer shift (integer only).
-// CheckAnd() - Bitwise AND (integer only with unsigned result).
-// CheckOr() - Bitwise OR (integer only with unsigned result).
-// CheckXor() - Bitwise XOR (integer only with unsigned result).
-// CheckMax() - Maximum of supplied arguments.
-// CheckMin() - Minimum of supplied arguments.
-//
-// The unary negation, increment, and decrement operators are supported, along
-// with the following unary arithmetic methods, which return a new
-// CheckedNumeric as a result of the operation:
-// Abs() - Absolute value.
-// UnsignedAbs() - Absolute value as an equal-width unsigned underlying type
-// (valid for only integral types).
-// Max() - Returns whichever is greater of the current instance or argument.
-// The underlying return type is whichever has the greatest magnitude.
-// Min() - Returns whichever is lowest of the current instance or argument.
-// The underlying return type is whichever has can represent the lowest
-// number in the smallest width (e.g. int8_t over unsigned, int over
-// int8_t, and float over int).
-//
-// The following methods convert from CheckedNumeric to standard numeric values:
-// AssignIfValid() - Assigns the underlying value to the supplied destination
-// pointer if the value is currently valid and within the range
-// supported by the destination type. Returns true on success.
-// ****************************************************************************
-// * WARNING: All of the following functions return a StrictNumeric, which *
-// * is valid for comparison and assignment operations, but will trigger a *
-// * compile failure on attempts to assign to a type of insufficient range. *
-// ****************************************************************************
-// IsValid() - Returns true if the underlying numeric value is valid (i.e. has
-// has not wrapped and is not the result of an invalid conversion).
-// ValueOrDie() - Returns the underlying value. If the state is not valid this
-// call will crash on a CHECK.
-// ValueOrDefault() - Returns the current value, or the supplied default if the
-// state is not valid (will not trigger a CHECK).
-//
-// The following wrapper functions can be used to avoid the template
-// disambiguator syntax when converting a destination type.
-// IsValidForType<>() in place of: a.template IsValid<Dst>()
-// ValueOrDieForType<>() in place of: a.template ValueOrDie()
-// ValueOrDefaultForType<>() in place of: a.template ValueOrDefault(default)
-//
-// The following are general utility methods that are useful for converting
-// between arithmetic types and CheckedNumeric types:
-// CheckedNumeric::Cast<Dst>() - Instance method returning a CheckedNumeric
-// derived from casting the current instance to a CheckedNumeric of
-// the supplied destination type.
-// MakeCheckedNum() - Creates a new CheckedNumeric from the underlying type of
-// the supplied arithmetic, CheckedNumeric, or StrictNumeric type.
-//
-// Comparison operations are explicitly not supported because they could result
-// in a crash on an unexpected CHECK condition. You should use patterns like the
-// following for comparisons:
-// CheckedNumeric<size_t> checked_size = untrusted_input_value;
-// checked_size += HEADER LENGTH;
-// if (checked_size.IsValid() && checked_size.ValueOrDie() < buffer_size)
-// Do stuff...
-
-template <typename T>
-class CheckedNumeric {
- static_assert(std::is_arithmetic<T>::value,
- "CheckedNumeric<T>: T must be a numeric type.");
-
- public:
- using type = T;
-
- constexpr CheckedNumeric() {}
-
- // Copy constructor.
- template <typename Src>
- constexpr CheckedNumeric(const CheckedNumeric<Src>& rhs)
- : state_(rhs.state_.value(), rhs.IsValid()) {}
-
- template <typename Src>
- friend class CheckedNumeric;
-
- // This is not an explicit constructor because we implicitly upgrade regular
- // numerics to CheckedNumerics to make them easier to use.
- template <typename Src>
- constexpr CheckedNumeric(Src value) // NOLINT(runtime/explicit)
- : state_(value) {
- static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
- }
-
- // This is not an explicit constructor because we want a seamless conversion
- // from StrictNumeric types.
- template <typename Src>
- constexpr CheckedNumeric(
- StrictNumeric<Src> value) // NOLINT(runtime/explicit)
- : state_(static_cast<Src>(value)) {}
-
- // IsValid() - The public API to test if a CheckedNumeric is currently valid.
- // A range checked destination type can be supplied using the Dst template
- // parameter.
- template <typename Dst = T>
- constexpr bool IsValid() const {
- return state_.is_valid() &&
- IsValueInRangeForNumericType<Dst>(state_.value());
- }
-
- // AssignIfValid(Dst) - Assigns the underlying value if it is currently valid
- // and is within the range supported by the destination type. Returns true if
- // successful and false otherwise.
- template <typename Dst>
- constexpr bool AssignIfValid(Dst* result) const {
- return IsValid<Dst>() ? ((*result = static_cast<Dst>(state_.value())), true)
- : false;
- }
-
- // ValueOrDie() - The primary accessor for the underlying value. If the
- // current state is not valid it will CHECK and crash.
- // A range checked destination type can be supplied using the Dst template
- // parameter, which will trigger a CHECK if the value is not in bounds for
- // the destination.
- // The CHECK behavior can be overridden by supplying a handler as a
- // template parameter, for test code, etc. However, the handler cannot access
- // the underlying value, and it is not available through other means.
- template <typename Dst = T, class CheckHandler = CheckOnFailure>
- constexpr StrictNumeric<Dst> ValueOrDie() const {
- return IsValid<Dst>() ? static_cast<Dst>(state_.value())
- : CheckHandler::template HandleFailure<Dst>();
- }
-
- // ValueOrDefault(T default_value) - A convenience method that returns the
- // current value if the state is valid, and the supplied default_value for
- // any other state.
- // A range checked destination type can be supplied using the Dst template
- // parameter. WARNING: This function may fail to compile or CHECK at runtime
- // if the supplied default_value is not within range of the destination type.
- template <typename Dst = T, typename Src>
- constexpr StrictNumeric<Dst> ValueOrDefault(const Src default_value) const {
- return IsValid<Dst>() ? static_cast<Dst>(state_.value())
- : checked_cast<Dst>(default_value);
- }
-
- // Returns a checked numeric of the specified type, cast from the current
- // CheckedNumeric. If the current state is invalid or the destination cannot
- // represent the result then the returned CheckedNumeric will be invalid.
- template <typename Dst>
- constexpr CheckedNumeric<typename UnderlyingType<Dst>::type> Cast() const {
- return *this;
- }
-
- // This friend method is available solely for providing more detailed logging
- // in the the tests. Do not implement it in production code, because the
- // underlying values may change at any time.
- template <typename U>
- friend U GetNumericValueForTest(const CheckedNumeric<U>& src);
-
- // Prototypes for the supported arithmetic operator overloads.
- template <typename Src>
- CheckedNumeric& operator+=(const Src rhs);
- template <typename Src>
- CheckedNumeric& operator-=(const Src rhs);
- template <typename Src>
- CheckedNumeric& operator*=(const Src rhs);
- template <typename Src>
- CheckedNumeric& operator/=(const Src rhs);
- template <typename Src>
- CheckedNumeric& operator%=(const Src rhs);
- template <typename Src>
- CheckedNumeric& operator<<=(const Src rhs);
- template <typename Src>
- CheckedNumeric& operator>>=(const Src rhs);
- template <typename Src>
- CheckedNumeric& operator&=(const Src rhs);
- template <typename Src>
- CheckedNumeric& operator|=(const Src rhs);
- template <typename Src>
- CheckedNumeric& operator^=(const Src rhs);
-
- constexpr CheckedNumeric operator-() const {
- return CheckedNumeric<T>(
- NegateWrapper(state_.value()),
- IsValid() &&
- (!std::is_signed<T>::value || std::is_floating_point<T>::value ||
- NegateWrapper(state_.value()) !=
- std::numeric_limits<T>::lowest()));
- }
-
- constexpr CheckedNumeric operator~() const {
- return CheckedNumeric<decltype(InvertWrapper(T()))>(
- InvertWrapper(state_.value()), IsValid());
- }
-
- constexpr CheckedNumeric Abs() const {
- return CheckedNumeric<T>(
- AbsWrapper(state_.value()),
- IsValid() &&
- (!std::is_signed<T>::value || std::is_floating_point<T>::value ||
- AbsWrapper(state_.value()) != std::numeric_limits<T>::lowest()));
- }
-
- template <typename U>
- constexpr CheckedNumeric<typename MathWrapper<CheckedMaxOp, T, U>::type> Max(
- const U rhs) const {
- using R = typename UnderlyingType<U>::type;
- using result_type = typename MathWrapper<CheckedMaxOp, T, U>::type;
- // TODO(jschuh): This can be converted to the MathOp version and remain
- // constexpr once we have C++14 support.
- return CheckedNumeric<result_type>(
- static_cast<result_type>(
- IsGreater<T, R>::Test(state_.value(), Wrapper<U>::value(rhs))
- ? state_.value()
- : Wrapper<U>::value(rhs)),
- state_.is_valid() && Wrapper<U>::is_valid(rhs));
- }
-
- template <typename U>
- constexpr CheckedNumeric<typename MathWrapper<CheckedMinOp, T, U>::type> Min(
- const U rhs) const {
- using R = typename UnderlyingType<U>::type;
- using result_type = typename MathWrapper<CheckedMinOp, T, U>::type;
- // TODO(jschuh): This can be converted to the MathOp version and remain
- // constexpr once we have C++14 support.
- return CheckedNumeric<result_type>(
- static_cast<result_type>(
- IsLess<T, R>::Test(state_.value(), Wrapper<U>::value(rhs))
- ? state_.value()
- : Wrapper<U>::value(rhs)),
- state_.is_valid() && Wrapper<U>::is_valid(rhs));
- }
-
- // This function is available only for integral types. It returns an unsigned
- // integer of the same width as the source type, containing the absolute value
- // of the source, and properly handling signed min.
- constexpr CheckedNumeric<typename UnsignedOrFloatForSize<T>::type>
- UnsignedAbs() const {
- return CheckedNumeric<typename UnsignedOrFloatForSize<T>::type>(
- SafeUnsignedAbs(state_.value()), state_.is_valid());
- }
-
- CheckedNumeric& operator++() {
- *this += 1;
- return *this;
- }
-
- CheckedNumeric operator++(int) {
- CheckedNumeric value = *this;
- *this += 1;
- return value;
- }
-
- CheckedNumeric& operator--() {
- *this -= 1;
- return *this;
- }
-
- CheckedNumeric operator--(int) {
- CheckedNumeric value = *this;
- *this -= 1;
- return value;
- }
-
- // These perform the actual math operations on the CheckedNumerics.
- // Binary arithmetic operations.
- template <template <typename, typename, typename> class M,
- typename L,
- typename R>
- static CheckedNumeric MathOp(const L lhs, const R rhs) {
- using Math = typename MathWrapper<M, L, R>::math;
- T result = 0;
- bool is_valid =
- Wrapper<L>::is_valid(lhs) && Wrapper<R>::is_valid(rhs) &&
- Math::Do(Wrapper<L>::value(lhs), Wrapper<R>::value(rhs), &result);
- return CheckedNumeric<T>(result, is_valid);
- };
-
- // Assignment arithmetic operations.
- template <template <typename, typename, typename> class M, typename R>
- CheckedNumeric& MathOp(const R rhs) {
- using Math = typename MathWrapper<M, T, R>::math;
- T result = 0; // Using T as the destination saves a range check.
- bool is_valid = state_.is_valid() && Wrapper<R>::is_valid(rhs) &&
- Math::Do(state_.value(), Wrapper<R>::value(rhs), &result);
- *this = CheckedNumeric<T>(result, is_valid);
- return *this;
- };
-
- private:
- CheckedNumericState<T> state_;
-
- template <typename Src>
- constexpr CheckedNumeric(Src value, bool is_valid)
- : state_(value, is_valid) {}
-
- // These wrappers allow us to handle state the same way for both
- // CheckedNumeric and POD arithmetic types.
- template <typename Src>
- struct Wrapper {
- static constexpr bool is_valid(Src) { return true; }
- static constexpr Src value(Src value) { return value; }
- };
-
- template <typename Src>
- struct Wrapper<CheckedNumeric<Src>> {
- static constexpr bool is_valid(const CheckedNumeric<Src> v) {
- return v.IsValid();
- }
- static constexpr Src value(const CheckedNumeric<Src> v) {
- return v.state_.value();
- }
- };
-
- template <typename Src>
- struct Wrapper<StrictNumeric<Src>> {
- static constexpr bool is_valid(const StrictNumeric<Src>) { return true; }
- static constexpr Src value(const StrictNumeric<Src> v) {
- return static_cast<Src>(v);
- }
- };
-};
-
-// Convenience functions to avoid the ugly template disambiguator syntax.
-template <typename Dst, typename Src>
-constexpr bool IsValidForType(const CheckedNumeric<Src> value) {
- return value.template IsValid<Dst>();
-}
-
-template <typename Dst, typename Src>
-constexpr StrictNumeric<Dst> ValueOrDieForType(
- const CheckedNumeric<Src> value) {
- return value.template ValueOrDie<Dst>();
-}
-
-template <typename Dst, typename Src, typename Default>
-constexpr StrictNumeric<Dst> ValueOrDefaultForType(
- const CheckedNumeric<Src> value,
- const Default default_value) {
- return value.template ValueOrDefault<Dst>(default_value);
-}
-
-// These variadic templates work out the return types.
-// TODO(jschuh): Rip all this out once we have C++14 non-trailing auto support.
-template <template <typename, typename, typename> class M,
- typename L,
- typename R,
- typename... Args>
-struct ResultType;
-
-template <template <typename, typename, typename> class M,
- typename L,
- typename R>
-struct ResultType<M, L, R> {
- using type = typename MathWrapper<M, L, R>::type;
-};
-
-template <template <typename, typename, typename> class M,
- typename L,
- typename R,
- typename... Args>
-struct ResultType {
- using type =
- typename ResultType<M, typename ResultType<M, L, R>::type, Args...>::type;
-};
-
-// Convience wrapper to return a new CheckedNumeric from the provided arithmetic
-// or CheckedNumericType.
-template <typename T>
-constexpr CheckedNumeric<typename UnderlyingType<T>::type> MakeCheckedNum(
- const T value) {
- return value;
-}
-
-// These implement the variadic wrapper for the math operations.
-template <template <typename, typename, typename> class M,
- typename L,
- typename R>
-CheckedNumeric<typename MathWrapper<M, L, R>::type> ChkMathOp(const L lhs,
- const R rhs) {
- using Math = typename MathWrapper<M, L, R>::math;
- return CheckedNumeric<typename Math::result_type>::template MathOp<M>(lhs,
- rhs);
-}
-
-// General purpose wrapper template for arithmetic operations.
-template <template <typename, typename, typename> class M,
- typename L,
- typename R,
- typename... Args>
-CheckedNumeric<typename ResultType<M, L, R, Args...>::type>
-ChkMathOp(const L lhs, const R rhs, const Args... args) {
- auto tmp = ChkMathOp<M>(lhs, rhs);
- return tmp.IsValid() ? ChkMathOp<M>(tmp, args...)
- : decltype(ChkMathOp<M>(tmp, args...))(tmp);
-};
-
-// The following macros are just boilerplate for the standard arithmetic
-// operator overloads and variadic function templates. A macro isn't the nicest
-// solution, but it beats rewriting these over and over again.
-#define BASE_NUMERIC_ARITHMETIC_VARIADIC(NAME) \
- template <typename L, typename R, typename... Args> \
- CheckedNumeric<typename ResultType<Checked##NAME##Op, L, R, Args...>::type> \
- Check##NAME(const L lhs, const R rhs, const Args... args) { \
- return ChkMathOp<Checked##NAME##Op, L, R, Args...>(lhs, rhs, args...); \
- }
-
-#define BASE_NUMERIC_ARITHMETIC_OPERATORS(NAME, OP, COMPOUND_OP) \
- /* Binary arithmetic operator for all CheckedNumeric operations. */ \
- template <typename L, typename R, \
- typename std::enable_if<IsCheckedOp<L, R>::value>::type* = \
- nullptr> \
- CheckedNumeric<typename MathWrapper<Checked##NAME##Op, L, R>::type> \
- operator OP(const L lhs, const R rhs) { \
- return decltype(lhs OP rhs)::template MathOp<Checked##NAME##Op>(lhs, rhs); \
- } \
- /* Assignment arithmetic operator implementation from CheckedNumeric. */ \
- template <typename L> \
- template <typename R> \
- CheckedNumeric<L>& CheckedNumeric<L>::operator COMPOUND_OP(const R rhs) { \
- return MathOp<Checked##NAME##Op>(rhs); \
- } \
- /* Variadic arithmetic functions that return CheckedNumeric. */ \
- BASE_NUMERIC_ARITHMETIC_VARIADIC(NAME)
-
-BASE_NUMERIC_ARITHMETIC_OPERATORS(Add, +, +=)
-BASE_NUMERIC_ARITHMETIC_OPERATORS(Sub, -, -=)
-BASE_NUMERIC_ARITHMETIC_OPERATORS(Mul, *, *=)
-BASE_NUMERIC_ARITHMETIC_OPERATORS(Div, /, /=)
-BASE_NUMERIC_ARITHMETIC_OPERATORS(Mod, %, %=)
-BASE_NUMERIC_ARITHMETIC_OPERATORS(Lsh, <<, <<=)
-BASE_NUMERIC_ARITHMETIC_OPERATORS(Rsh, >>, >>=)
-BASE_NUMERIC_ARITHMETIC_OPERATORS(And, &, &=)
-BASE_NUMERIC_ARITHMETIC_OPERATORS(Or, |, |=)
-BASE_NUMERIC_ARITHMETIC_OPERATORS(Xor, ^, ^=)
-BASE_NUMERIC_ARITHMETIC_VARIADIC(Max)
-BASE_NUMERIC_ARITHMETIC_VARIADIC(Min)
-
-#undef BASE_NUMERIC_ARITHMETIC_VARIADIC
-#undef BASE_NUMERIC_ARITHMETIC_OPERATORS
-
-// These are some extra StrictNumeric operators to support simple pointer
-// arithmetic with our result types. Since wrapping on a pointer is always
-// bad, we trigger the CHECK condition here.
-template <typename L, typename R>
-L* operator+(L* lhs, const StrictNumeric<R> rhs) {
- uintptr_t result = CheckAdd(reinterpret_cast<uintptr_t>(lhs),
- CheckMul(sizeof(L), static_cast<R>(rhs)))
- .template ValueOrDie<uintptr_t>();
- return reinterpret_cast<L*>(result);
-}
-
-template <typename L, typename R>
-L* operator-(L* lhs, const StrictNumeric<R> rhs) {
- uintptr_t result = CheckSub(reinterpret_cast<uintptr_t>(lhs),
- CheckMul(sizeof(L), static_cast<R>(rhs)))
- .template ValueOrDie<uintptr_t>();
- return reinterpret_cast<L*>(result);
-}
-
-} // namespace internal
-
-using internal::CheckedNumeric;
-using internal::IsValidForType;
-using internal::ValueOrDieForType;
-using internal::ValueOrDefaultForType;
-using internal::MakeCheckedNum;
-using internal::CheckMax;
-using internal::CheckMin;
-using internal::CheckAdd;
-using internal::CheckSub;
-using internal::CheckMul;
-using internal::CheckDiv;
-using internal::CheckMod;
-using internal::CheckLsh;
-using internal::CheckRsh;
-using internal::CheckAnd;
-using internal::CheckOr;
-using internal::CheckXor;
-
-} // namespace base
+#include "base/numerics/checked_math.h"
+#include "base/numerics/clamped_math.h"
+#include "base/numerics/safe_conversions.h"
#endif // BASE_NUMERICS_SAFE_MATH_H_
diff --git a/base/numerics/safe_math_arm_impl.h b/base/numerics/safe_math_arm_impl.h
new file mode 100644
index 0000000000..a7cda1bb23
--- /dev/null
+++ b/base/numerics/safe_math_arm_impl.h
@@ -0,0 +1,122 @@
+// 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_NUMERICS_SAFE_MATH_ARM_IMPL_H_
+#define BASE_NUMERICS_SAFE_MATH_ARM_IMPL_H_
+
+#include <cassert>
+#include <limits>
+#include <type_traits>
+
+#include "base/numerics/safe_conversions.h"
+
+namespace base {
+namespace internal {
+
+template <typename T, typename U>
+struct CheckedMulFastAsmOp {
+ static const bool is_supported =
+ FastIntegerArithmeticPromotion<T, U>::is_contained;
+
+ // The following is much more efficient than the Clang and GCC builtins for
+ // performing overflow-checked multiplication when a twice wider type is
+ // available. The below compiles down to 2-3 instructions, depending on the
+ // width of the types in use.
+ // As an example, an int32_t multiply compiles to:
+ // smull r0, r1, r0, r1
+ // cmp r1, r1, asr #31
+ // And an int16_t multiply compiles to:
+ // smulbb r1, r1, r0
+ // asr r2, r1, #16
+ // cmp r2, r1, asr #15
+ template <typename V>
+ __attribute__((always_inline)) static bool Do(T x, U y, V* result) {
+ using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
+ Promotion presult;
+
+ presult = static_cast<Promotion>(x) * static_cast<Promotion>(y);
+ *result = static_cast<V>(presult);
+ return IsValueInRangeForNumericType<V>(presult);
+ }
+};
+
+template <typename T, typename U>
+struct ClampedAddFastAsmOp {
+ static const bool is_supported =
+ BigEnoughPromotion<T, U>::is_contained &&
+ IsTypeInRangeForNumericType<
+ int32_t,
+ typename BigEnoughPromotion<T, U>::type>::value;
+
+ template <typename V>
+ __attribute__((always_inline)) static V Do(T x, U y) {
+ // This will get promoted to an int, so let the compiler do whatever is
+ // clever and rely on the saturated cast to bounds check.
+ if (IsIntegerArithmeticSafe<int, T, U>::value)
+ return saturated_cast<V>(x + y);
+
+ int32_t result;
+ int32_t x_i32 = x;
+ int32_t y_i32 = y;
+
+ asm("qadd %[result], %[first], %[second]"
+ : [result] "=r"(result)
+ : [first] "r"(x_i32), [second] "r"(y_i32));
+ return saturated_cast<V>(result);
+ }
+};
+
+template <typename T, typename U>
+struct ClampedSubFastAsmOp {
+ static const bool is_supported =
+ BigEnoughPromotion<T, U>::is_contained &&
+ IsTypeInRangeForNumericType<
+ int32_t,
+ typename BigEnoughPromotion<T, U>::type>::value;
+
+ template <typename V>
+ __attribute__((always_inline)) static V Do(T x, U y) {
+ // This will get promoted to an int, so let the compiler do whatever is
+ // clever and rely on the saturated cast to bounds check.
+ if (IsIntegerArithmeticSafe<int, T, U>::value)
+ return saturated_cast<V>(x - y);
+
+ int32_t result;
+ int32_t x_i32 = x;
+ int32_t y_i32 = y;
+
+ asm("qsub %[result], %[first], %[second]"
+ : [result] "=r"(result)
+ : [first] "r"(x_i32), [second] "r"(y_i32));
+ return saturated_cast<V>(result);
+ }
+};
+
+template <typename T, typename U>
+struct ClampedMulFastAsmOp {
+ static const bool is_supported = CheckedMulFastAsmOp<T, U>::is_supported;
+
+ template <typename V>
+ __attribute__((always_inline)) static V Do(T x, U y) {
+ // Use the CheckedMulFastAsmOp for full-width 32-bit values, because
+ // it's fewer instructions than promoting and then saturating.
+ if (!IsIntegerArithmeticSafe<int32_t, T, U>::value &&
+ !IsIntegerArithmeticSafe<uint32_t, T, U>::value) {
+ V result;
+ if (CheckedMulFastAsmOp<T, U>::Do(x, y, &result))
+ return result;
+ return CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y));
+ }
+
+ assert((FastIntegerArithmeticPromotion<T, U>::is_contained));
+ using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
+ return saturated_cast<V>(static_cast<Promotion>(x) *
+ static_cast<Promotion>(y));
+ }
+};
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_NUMERICS_SAFE_MATH_ARM_IMPL_H_
diff --git a/base/numerics/safe_math_clang_gcc_impl.h b/base/numerics/safe_math_clang_gcc_impl.h
new file mode 100644
index 0000000000..1760338b08
--- /dev/null
+++ b/base/numerics/safe_math_clang_gcc_impl.h
@@ -0,0 +1,157 @@
+// 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_NUMERICS_SAFE_MATH_CLANG_GCC_IMPL_H_
+#define BASE_NUMERICS_SAFE_MATH_CLANG_GCC_IMPL_H_
+
+#include <cassert>
+#include <limits>
+#include <type_traits>
+
+#include "base/numerics/safe_conversions.h"
+
+#if !defined(__native_client__) && (defined(__ARMEL__) || defined(__arch64__))
+#include "base/numerics/safe_math_arm_impl.h"
+#define BASE_HAS_ASSEMBLER_SAFE_MATH (1)
+#else
+#define BASE_HAS_ASSEMBLER_SAFE_MATH (0)
+#endif
+
+namespace base {
+namespace internal {
+
+// These are the non-functioning boilerplate implementations of the optimized
+// safe math routines.
+#if !BASE_HAS_ASSEMBLER_SAFE_MATH
+template <typename T, typename U>
+struct CheckedMulFastAsmOp {
+ static const bool is_supported = false;
+ template <typename V>
+ static constexpr bool Do(T, U, V*) {
+ // Force a compile failure if instantiated.
+ return CheckOnFailure::template HandleFailure<bool>();
+ }
+};
+
+template <typename T, typename U>
+struct ClampedAddFastAsmOp {
+ static const bool is_supported = false;
+ template <typename V>
+ static constexpr V Do(T, U) {
+ // Force a compile failure if instantiated.
+ return CheckOnFailure::template HandleFailure<V>();
+ }
+};
+
+template <typename T, typename U>
+struct ClampedSubFastAsmOp {
+ static const bool is_supported = false;
+ template <typename V>
+ static constexpr V Do(T, U) {
+ // Force a compile failure if instantiated.
+ return CheckOnFailure::template HandleFailure<V>();
+ }
+};
+
+template <typename T, typename U>
+struct ClampedMulFastAsmOp {
+ static const bool is_supported = false;
+ template <typename V>
+ static constexpr V Do(T, U) {
+ // Force a compile failure if instantiated.
+ return CheckOnFailure::template HandleFailure<V>();
+ }
+};
+#endif // BASE_HAS_ASSEMBLER_SAFE_MATH
+#undef BASE_HAS_ASSEMBLER_SAFE_MATH
+
+template <typename T, typename U>
+struct CheckedAddFastOp {
+ static const bool is_supported = true;
+ template <typename V>
+ __attribute__((always_inline)) static constexpr bool Do(T x, U y, V* result) {
+ return !__builtin_add_overflow(x, y, result);
+ }
+};
+
+template <typename T, typename U>
+struct CheckedSubFastOp {
+ static const bool is_supported = true;
+ template <typename V>
+ __attribute__((always_inline)) static constexpr bool Do(T x, U y, V* result) {
+ return !__builtin_sub_overflow(x, y, result);
+ }
+};
+
+template <typename T, typename U>
+struct CheckedMulFastOp {
+#if defined(__clang__)
+ // TODO(jschuh): Get the Clang runtime library issues sorted out so we can
+ // support full-width, mixed-sign multiply builtins.
+ // https://crbug.com/613003
+ // We can support intptr_t, uintptr_t, or a smaller common type.
+ static const bool is_supported =
+ (IsTypeInRangeForNumericType<intptr_t, T>::value &&
+ IsTypeInRangeForNumericType<intptr_t, U>::value) ||
+ (IsTypeInRangeForNumericType<uintptr_t, T>::value &&
+ IsTypeInRangeForNumericType<uintptr_t, U>::value);
+#else
+ static const bool is_supported = true;
+#endif
+ template <typename V>
+ __attribute__((always_inline)) static constexpr bool Do(T x, U y, V* result) {
+ return CheckedMulFastAsmOp<T, U>::is_supported
+ ? CheckedMulFastAsmOp<T, U>::Do(x, y, result)
+ : !__builtin_mul_overflow(x, y, result);
+ }
+};
+
+template <typename T, typename U>
+struct ClampedAddFastOp {
+ static const bool is_supported = ClampedAddFastAsmOp<T, U>::is_supported;
+ template <typename V>
+ __attribute__((always_inline)) static V Do(T x, U y) {
+ return ClampedAddFastAsmOp<T, U>::template Do<V>(x, y);
+ }
+};
+
+template <typename T, typename U>
+struct ClampedSubFastOp {
+ static const bool is_supported = ClampedSubFastAsmOp<T, U>::is_supported;
+ template <typename V>
+ __attribute__((always_inline)) static V Do(T x, U y) {
+ return ClampedSubFastAsmOp<T, U>::template Do<V>(x, y);
+ }
+};
+
+template <typename T, typename U>
+struct ClampedMulFastOp {
+ static const bool is_supported = ClampedMulFastAsmOp<T, U>::is_supported;
+ template <typename V>
+ __attribute__((always_inline)) static V Do(T x, U y) {
+ return ClampedMulFastAsmOp<T, U>::template Do<V>(x, y);
+ }
+};
+
+template <typename T>
+struct ClampedNegFastOp {
+ static const bool is_supported = std::is_signed<T>::value;
+ __attribute__((always_inline)) static T Do(T value) {
+ // Use this when there is no assembler path available.
+ if (!ClampedSubFastAsmOp<T, T>::is_supported) {
+ T result;
+ return !__builtin_sub_overflow(T(0), value, &result)
+ ? result
+ : std::numeric_limits<T>::max();
+ }
+
+ // Fallback to the normal subtraction path.
+ return ClampedSubFastOp<T, T>::template Do<T>(T(0), value);
+ }
+};
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_NUMERICS_SAFE_MATH_CLANG_GCC_IMPL_H_
diff --git a/base/numerics/safe_math_impl.h b/base/numerics/safe_math_impl.h
deleted file mode 100644
index a224f692dd..0000000000
--- a/base/numerics/safe_math_impl.h
+++ /dev/null
@@ -1,641 +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_NUMERICS_SAFE_MATH_IMPL_H_
-#define BASE_NUMERICS_SAFE_MATH_IMPL_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <climits>
-#include <cmath>
-#include <cstdlib>
-#include <limits>
-#include <type_traits>
-
-#include "base/numerics/safe_conversions.h"
-
-namespace base {
-namespace internal {
-
-// Everything from here up to the floating point operations is portable C++,
-// but it may not be fast. This code could be split based on
-// platform/architecture and replaced with potentially faster implementations.
-
-// This is used for UnsignedAbs, where we need to support floating-point
-// template instantiations even though we don't actually support the operations.
-// However, there is no corresponding implementation of e.g. SafeUnsignedAbs,
-// so the float versions will not compile.
-template <typename Numeric,
- bool IsInteger = std::is_integral<Numeric>::value,
- bool IsFloat = std::is_floating_point<Numeric>::value>
-struct UnsignedOrFloatForSize;
-
-template <typename Numeric>
-struct UnsignedOrFloatForSize<Numeric, true, false> {
- using type = typename std::make_unsigned<Numeric>::type;
-};
-
-template <typename Numeric>
-struct UnsignedOrFloatForSize<Numeric, false, true> {
- using type = Numeric;
-};
-
-// Probe for builtin math overflow support on Clang and version check on GCC.
-#if defined(__has_builtin)
-#define USE_OVERFLOW_BUILTINS (__has_builtin(__builtin_add_overflow))
-#elif defined(__GNUC__)
-#define USE_OVERFLOW_BUILTINS (__GNUC__ >= 5)
-#else
-#define USE_OVERFLOW_BUILTINS (0)
-#endif
-
-template <typename T>
-bool CheckedAddImpl(T x, T y, T* result) {
- static_assert(std::is_integral<T>::value, "Type must be integral");
- // Since the value of x+y is undefined if we have a signed type, we compute
- // it using the unsigned type of the same size.
- using UnsignedDst = typename std::make_unsigned<T>::type;
- using SignedDst = typename std::make_signed<T>::type;
- UnsignedDst ux = static_cast<UnsignedDst>(x);
- UnsignedDst uy = static_cast<UnsignedDst>(y);
- UnsignedDst uresult = static_cast<UnsignedDst>(ux + uy);
- *result = static_cast<T>(uresult);
- // Addition is valid if the sign of (x + y) is equal to either that of x or
- // that of y.
- return (std::is_signed<T>::value)
- ? static_cast<SignedDst>((uresult ^ ux) & (uresult ^ uy)) >= 0
- : uresult >= uy; // Unsigned is either valid or underflow.
-}
-
-template <typename T, typename U, class Enable = void>
-struct CheckedAddOp {};
-
-template <typename T, typename U>
-struct CheckedAddOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename MaxExponentPromotion<T, U>::type;
- template <typename V>
- static bool Do(T x, U y, V* result) {
-#if USE_OVERFLOW_BUILTINS
- return !__builtin_add_overflow(x, y, result);
-#else
- using Promotion = typename BigEnoughPromotion<T, U>::type;
- Promotion presult;
- // Fail if either operand is out of range for the promoted type.
- // TODO(jschuh): This could be made to work for a broader range of values.
- bool is_valid = IsValueInRangeForNumericType<Promotion>(x) &&
- IsValueInRangeForNumericType<Promotion>(y);
-
- if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
- presult = static_cast<Promotion>(x) + static_cast<Promotion>(y);
- } else {
- is_valid &= CheckedAddImpl(static_cast<Promotion>(x),
- static_cast<Promotion>(y), &presult);
- }
- *result = static_cast<V>(presult);
- return is_valid && IsValueInRangeForNumericType<V>(presult);
-#endif
- }
-};
-
-template <typename T>
-bool CheckedSubImpl(T x, T y, T* result) {
- static_assert(std::is_integral<T>::value, "Type must be integral");
- // Since the value of x+y is undefined if we have a signed type, we compute
- // it using the unsigned type of the same size.
- using UnsignedDst = typename std::make_unsigned<T>::type;
- using SignedDst = typename std::make_signed<T>::type;
- UnsignedDst ux = static_cast<UnsignedDst>(x);
- UnsignedDst uy = static_cast<UnsignedDst>(y);
- UnsignedDst uresult = static_cast<UnsignedDst>(ux - uy);
- *result = static_cast<T>(uresult);
- // Subtraction is valid if either x and y have same sign, or (x-y) and x have
- // the same sign.
- return (std::is_signed<T>::value)
- ? static_cast<SignedDst>((uresult ^ ux) & (ux ^ uy)) >= 0
- : x >= y;
-}
-
-template <typename T, typename U, class Enable = void>
-struct CheckedSubOp {};
-
-template <typename T, typename U>
-struct CheckedSubOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename MaxExponentPromotion<T, U>::type;
- template <typename V>
- static bool Do(T x, U y, V* result) {
-#if USE_OVERFLOW_BUILTINS
- return !__builtin_sub_overflow(x, y, result);
-#else
- using Promotion = typename BigEnoughPromotion<T, U>::type;
- Promotion presult;
- // Fail if either operand is out of range for the promoted type.
- // TODO(jschuh): This could be made to work for a broader range of values.
- bool is_valid = IsValueInRangeForNumericType<Promotion>(x) &&
- IsValueInRangeForNumericType<Promotion>(y);
-
- if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
- presult = static_cast<Promotion>(x) - static_cast<Promotion>(y);
- } else {
- is_valid &= CheckedSubImpl(static_cast<Promotion>(x),
- static_cast<Promotion>(y), &presult);
- }
- *result = static_cast<V>(presult);
- return is_valid && IsValueInRangeForNumericType<V>(presult);
-#endif
- }
-};
-
-template <typename T>
-bool CheckedMulImpl(T x, T y, T* result) {
- static_assert(std::is_integral<T>::value, "Type must be integral");
- // Since the value of x*y is potentially undefined if we have a signed type,
- // we compute it using the unsigned type of the same size.
- using UnsignedDst = typename std::make_unsigned<T>::type;
- using SignedDst = typename std::make_signed<T>::type;
- const UnsignedDst ux = SafeUnsignedAbs(x);
- const UnsignedDst uy = SafeUnsignedAbs(y);
- UnsignedDst uresult = static_cast<UnsignedDst>(ux * uy);
- const bool is_negative =
- std::is_signed<T>::value && static_cast<SignedDst>(x ^ y) < 0;
- *result = is_negative ? 0 - uresult : uresult;
- // We have a fast out for unsigned identity or zero on the second operand.
- // After that it's an unsigned overflow check on the absolute value, with
- // a +1 bound for a negative result.
- return uy <= UnsignedDst(!std::is_signed<T>::value || is_negative) ||
- ux <= (std::numeric_limits<T>::max() + UnsignedDst(is_negative)) / uy;
-}
-
-template <typename T, typename U, class Enable = void>
-struct CheckedMulOp {};
-
-template <typename T, typename U>
-struct CheckedMulOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename MaxExponentPromotion<T, U>::type;
- template <typename V>
- static bool Do(T x, U y, V* result) {
-#if USE_OVERFLOW_BUILTINS
-#if defined(__clang__)
- // TODO(jschuh): Get the Clang runtime library issues sorted out so we can
- // support full-width, mixed-sign multiply builtins.
- // https://crbug.com/613003
- static const bool kUseMaxInt =
- // Narrower type than uintptr_t is always safe.
- std::numeric_limits<__typeof__(x * y)>::digits <
- std::numeric_limits<intptr_t>::digits ||
- // Safe for intptr_t and uintptr_t if the sign matches.
- (IntegerBitsPlusSign<__typeof__(x * y)>::value ==
- IntegerBitsPlusSign<intptr_t>::value &&
- std::is_signed<T>::value == std::is_signed<U>::value);
-#else
- static const bool kUseMaxInt = true;
-#endif
- if (kUseMaxInt)
- return !__builtin_mul_overflow(x, y, result);
-#endif
- using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
- Promotion presult;
- // Fail if either operand is out of range for the promoted type.
- // TODO(jschuh): This could be made to work for a broader range of values.
- bool is_valid = IsValueInRangeForNumericType<Promotion>(x) &&
- IsValueInRangeForNumericType<Promotion>(y);
-
- if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
- presult = static_cast<Promotion>(x) * static_cast<Promotion>(y);
- } else {
- is_valid &= CheckedMulImpl(static_cast<Promotion>(x),
- static_cast<Promotion>(y), &presult);
- }
- *result = static_cast<V>(presult);
- return is_valid && IsValueInRangeForNumericType<V>(presult);
- }
-};
-
-// Avoid poluting the namespace once we're done with the macro.
-#undef USE_OVERFLOW_BUILTINS
-
-// Division just requires a check for a zero denominator or an invalid negation
-// on signed min/-1.
-template <typename T>
-bool CheckedDivImpl(T x, T y, T* result) {
- static_assert(std::is_integral<T>::value, "Type must be integral");
- if (y && (!std::is_signed<T>::value ||
- x != std::numeric_limits<T>::lowest() || y != static_cast<T>(-1))) {
- *result = x / y;
- return true;
- }
- return false;
-}
-
-template <typename T, typename U, class Enable = void>
-struct CheckedDivOp {};
-
-template <typename T, typename U>
-struct CheckedDivOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename MaxExponentPromotion<T, U>::type;
- template <typename V>
- static bool Do(T x, U y, V* result) {
- using Promotion = typename BigEnoughPromotion<T, U>::type;
- Promotion presult;
- // Fail if either operand is out of range for the promoted type.
- // TODO(jschuh): This could be made to work for a broader range of values.
- bool is_valid = IsValueInRangeForNumericType<Promotion>(x) &&
- IsValueInRangeForNumericType<Promotion>(y);
- is_valid &= CheckedDivImpl(static_cast<Promotion>(x),
- static_cast<Promotion>(y), &presult);
- *result = static_cast<V>(presult);
- return is_valid && IsValueInRangeForNumericType<V>(presult);
- }
-};
-
-template <typename T>
-bool CheckedModImpl(T x, T y, T* result) {
- static_assert(std::is_integral<T>::value, "Type must be integral");
- if (y > 0) {
- *result = static_cast<T>(x % y);
- return true;
- }
- return false;
-}
-
-template <typename T, typename U, class Enable = void>
-struct CheckedModOp {};
-
-template <typename T, typename U>
-struct CheckedModOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename MaxExponentPromotion<T, U>::type;
- template <typename V>
- static bool Do(T x, U y, V* result) {
- using Promotion = typename BigEnoughPromotion<T, U>::type;
- Promotion presult;
- bool is_valid = CheckedModImpl(static_cast<Promotion>(x),
- static_cast<Promotion>(y), &presult);
- *result = static_cast<V>(presult);
- return is_valid && IsValueInRangeForNumericType<V>(presult);
- }
-};
-
-template <typename T, typename U, class Enable = void>
-struct CheckedLshOp {};
-
-// Left shift. Shifts less than 0 or greater than or equal to the number
-// of bits in the promoted type are undefined. Shifts of negative values
-// are undefined. Otherwise it is defined when the result fits.
-template <typename T, typename U>
-struct CheckedLshOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = T;
- template <typename V>
- static bool Do(T x, U shift, V* result) {
- using ShiftType = typename std::make_unsigned<T>::type;
- static const ShiftType kBitWidth = IntegerBitsPlusSign<T>::value;
- const ShiftType real_shift = static_cast<ShiftType>(shift);
- // Signed shift is not legal on negative values.
- if (!IsValueNegative(x) && real_shift < kBitWidth) {
- // Just use a multiplication because it's easy.
- // TODO(jschuh): This could probably be made more efficient.
- if (!std::is_signed<T>::value || real_shift != kBitWidth - 1)
- return CheckedMulOp<T, T>::Do(x, static_cast<T>(1) << shift, result);
- return !x; // Special case zero for a full width signed shift.
- }
- return false;
- }
-};
-
-template <typename T, typename U, class Enable = void>
-struct CheckedRshOp {};
-
-// Right shift. Shifts less than 0 or greater than or equal to the number
-// of bits in the promoted type are undefined. Otherwise, it is always defined,
-// but a right shift of a negative value is implementation-dependent.
-template <typename T, typename U>
-struct CheckedRshOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = T;
- template <typename V = result_type>
- static bool Do(T x, U shift, V* result) {
- // Use the type conversion push negative values out of range.
- using ShiftType = typename std::make_unsigned<T>::type;
- if (static_cast<ShiftType>(shift) < IntegerBitsPlusSign<T>::value) {
- T tmp = x >> shift;
- *result = static_cast<V>(tmp);
- return IsValueInRangeForNumericType<V>(tmp);
- }
- return false;
- }
-};
-
-template <typename T, typename U, class Enable = void>
-struct CheckedAndOp {};
-
-// For simplicity we support only unsigned integer results.
-template <typename T, typename U>
-struct CheckedAndOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename std::make_unsigned<
- typename MaxExponentPromotion<T, U>::type>::type;
- template <typename V = result_type>
- static bool Do(T x, U y, V* result) {
- result_type tmp = static_cast<result_type>(x) & static_cast<result_type>(y);
- *result = static_cast<V>(tmp);
- return IsValueInRangeForNumericType<V>(tmp);
- }
-};
-
-template <typename T, typename U, class Enable = void>
-struct CheckedOrOp {};
-
-// For simplicity we support only unsigned integers.
-template <typename T, typename U>
-struct CheckedOrOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename std::make_unsigned<
- typename MaxExponentPromotion<T, U>::type>::type;
- template <typename V = result_type>
- static bool Do(T x, U y, V* result) {
- result_type tmp = static_cast<result_type>(x) | static_cast<result_type>(y);
- *result = static_cast<V>(tmp);
- return IsValueInRangeForNumericType<V>(tmp);
- }
-};
-
-template <typename T, typename U, class Enable = void>
-struct CheckedXorOp {};
-
-// For simplicity we support only unsigned integers.
-template <typename T, typename U>
-struct CheckedXorOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename std::make_unsigned<
- typename MaxExponentPromotion<T, U>::type>::type;
- template <typename V = result_type>
- static bool Do(T x, U y, V* result) {
- result_type tmp = static_cast<result_type>(x) ^ static_cast<result_type>(y);
- *result = static_cast<V>(tmp);
- return IsValueInRangeForNumericType<V>(tmp);
- }
-};
-
-// Max doesn't really need to be implemented this way because it can't fail,
-// but it makes the code much cleaner to use the MathOp wrappers.
-template <typename T, typename U, class Enable = void>
-struct CheckedMaxOp {};
-
-template <typename T, typename U>
-struct CheckedMaxOp<
- T,
- U,
- typename std::enable_if<std::is_arithmetic<T>::value &&
- std::is_arithmetic<U>::value>::type> {
- using result_type = typename MaxExponentPromotion<T, U>::type;
- template <typename V = result_type>
- static bool Do(T x, U y, V* result) {
- *result = IsGreater<T, U>::Test(x, y) ? static_cast<result_type>(x)
- : static_cast<result_type>(y);
- return true;
- }
-};
-
-// Min doesn't really need to be implemented this way because it can't fail,
-// but it makes the code much cleaner to use the MathOp wrappers.
-template <typename T, typename U, class Enable = void>
-struct CheckedMinOp {};
-
-template <typename T, typename U>
-struct CheckedMinOp<
- T,
- U,
- typename std::enable_if<std::is_arithmetic<T>::value &&
- std::is_arithmetic<U>::value>::type> {
- using result_type = typename LowestValuePromotion<T, U>::type;
- template <typename V = result_type>
- static bool Do(T x, U y, V* result) {
- *result = IsLess<T, U>::Test(x, y) ? static_cast<result_type>(x)
- : static_cast<result_type>(y);
- return true;
- }
-};
-
-// This is just boilerplate that wraps the standard floating point arithmetic.
-// A macro isn't the nicest solution, but it beats rewriting these repeatedly.
-#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \
- template <typename T, typename U> \
- struct Checked##NAME##Op< \
- T, U, typename std::enable_if<std::is_floating_point<T>::value || \
- std::is_floating_point<U>::value>::type> { \
- using result_type = typename MaxExponentPromotion<T, U>::type; \
- template <typename V> \
- static bool Do(T x, U y, V* result) { \
- using Promotion = typename MaxExponentPromotion<T, U>::type; \
- Promotion presult = x OP y; \
- *result = static_cast<V>(presult); \
- return IsValueInRangeForNumericType<V>(presult); \
- } \
- };
-
-BASE_FLOAT_ARITHMETIC_OPS(Add, +)
-BASE_FLOAT_ARITHMETIC_OPS(Sub, -)
-BASE_FLOAT_ARITHMETIC_OPS(Mul, *)
-BASE_FLOAT_ARITHMETIC_OPS(Div, /)
-
-#undef BASE_FLOAT_ARITHMETIC_OPS
-
-// Wrap the unary operations to allow SFINAE when instantiating integrals versus
-// floating points. These don't perform any overflow checking. Rather, they
-// exhibit well-defined overflow semantics and rely on the caller to detect
-// if an overflow occured.
-
-template <typename T,
- typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
-constexpr T NegateWrapper(T value) {
- using UnsignedT = typename std::make_unsigned<T>::type;
- // This will compile to a NEG on Intel, and is normal negation on ARM.
- return static_cast<T>(UnsignedT(0) - static_cast<UnsignedT>(value));
-}
-
-template <
- typename T,
- typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
-constexpr T NegateWrapper(T value) {
- return -value;
-}
-
-template <typename T,
- typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
-constexpr typename std::make_unsigned<T>::type InvertWrapper(T value) {
- return ~value;
-}
-
-template <typename T,
- typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
-constexpr T AbsWrapper(T value) {
- return static_cast<T>(SafeUnsignedAbs(value));
-}
-
-template <
- typename T,
- typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
-constexpr T AbsWrapper(T value) {
- return value < 0 ? -value : value;
-}
-
-// Floats carry around their validity state with them, but integers do not. So,
-// we wrap the underlying value in a specialization in order to hide that detail
-// and expose an interface via accessors.
-enum NumericRepresentation {
- NUMERIC_INTEGER,
- NUMERIC_FLOATING,
- NUMERIC_UNKNOWN
-};
-
-template <typename NumericType>
-struct GetNumericRepresentation {
- static const NumericRepresentation value =
- std::is_integral<NumericType>::value
- ? NUMERIC_INTEGER
- : (std::is_floating_point<NumericType>::value ? NUMERIC_FLOATING
- : NUMERIC_UNKNOWN);
-};
-
-template <typename T, NumericRepresentation type =
- GetNumericRepresentation<T>::value>
-class CheckedNumericState {};
-
-// Integrals require quite a bit of additional housekeeping to manage state.
-template <typename T>
-class CheckedNumericState<T, NUMERIC_INTEGER> {
- private:
- // is_valid_ precedes value_ because member intializers in the constructors
- // are evaluated in field order, and is_valid_ must be read when initializing
- // value_.
- bool is_valid_;
- T value_;
-
- // Ensures that a type conversion does not trigger undefined behavior.
- template <typename Src>
- static constexpr T WellDefinedConversionOrZero(const Src value,
- const bool is_valid) {
- using SrcType = typename internal::UnderlyingType<Src>::type;
- return (std::is_integral<SrcType>::value || is_valid)
- ? static_cast<T>(value)
- : static_cast<T>(0);
- }
-
- public:
- template <typename Src, NumericRepresentation type>
- friend class CheckedNumericState;
-
- constexpr CheckedNumericState() : is_valid_(true), value_(0) {}
-
- template <typename Src>
- constexpr CheckedNumericState(Src value, bool is_valid)
- : is_valid_(is_valid && IsValueInRangeForNumericType<T>(value)),
- value_(WellDefinedConversionOrZero(value, is_valid_)) {
- static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
- }
-
- // Copy constructor.
- template <typename Src>
- constexpr CheckedNumericState(const CheckedNumericState<Src>& rhs)
- : is_valid_(rhs.IsValid()),
- value_(WellDefinedConversionOrZero(rhs.value(), is_valid_)) {}
-
- template <typename Src>
- constexpr explicit CheckedNumericState(Src value)
- : is_valid_(IsValueInRangeForNumericType<T>(value)),
- value_(WellDefinedConversionOrZero(value, is_valid_)) {}
-
- constexpr bool is_valid() const { return is_valid_; }
- constexpr T value() const { return value_; }
-};
-
-// Floating points maintain their own validity, but need translation wrappers.
-template <typename T>
-class CheckedNumericState<T, NUMERIC_FLOATING> {
- private:
- T value_;
-
- // Ensures that a type conversion does not trigger undefined behavior.
- template <typename Src>
- static constexpr T WellDefinedConversionOrNaN(const Src value,
- const bool is_valid) {
- using SrcType = typename internal::UnderlyingType<Src>::type;
- return (StaticDstRangeRelationToSrcRange<T, SrcType>::value ==
- NUMERIC_RANGE_CONTAINED ||
- is_valid)
- ? static_cast<T>(value)
- : std::numeric_limits<T>::quiet_NaN();
- }
-
- public:
- template <typename Src, NumericRepresentation type>
- friend class CheckedNumericState;
-
- constexpr CheckedNumericState() : value_(0.0) {}
-
- template <typename Src>
- constexpr CheckedNumericState(Src value, bool is_valid)
- : value_(WellDefinedConversionOrNaN(value, is_valid)) {}
-
- template <typename Src>
- constexpr explicit CheckedNumericState(Src value)
- : value_(WellDefinedConversionOrNaN(
- value,
- IsValueInRangeForNumericType<T>(value))) {}
-
- // Copy constructor.
- template <typename Src>
- constexpr CheckedNumericState(const CheckedNumericState<Src>& rhs)
- : value_(WellDefinedConversionOrNaN(
- rhs.value(),
- rhs.is_valid() && IsValueInRangeForNumericType<T>(rhs.value()))) {}
-
- constexpr bool is_valid() const {
- // Written this way because std::isfinite is not reliably constexpr.
- // TODO(jschuh): Fix this if the libraries ever get fixed.
- return value_ <= std::numeric_limits<T>::max() &&
- value_ >= std::numeric_limits<T>::lowest();
- }
- constexpr T value() const { return value_; }
-};
-
-template <template <typename, typename, typename> class M,
- typename L,
- typename R>
-struct MathWrapper {
- using math = M<typename UnderlyingType<L>::type,
- typename UnderlyingType<R>::type,
- void>;
- using type = typename math::result_type;
-};
-
-} // namespace internal
-} // namespace base
-
-#endif // BASE_NUMERICS_SAFE_MATH_IMPL_H_
diff --git a/base/numerics/safe_math_shared_impl.h b/base/numerics/safe_math_shared_impl.h
new file mode 100644
index 0000000000..583c487a42
--- /dev/null
+++ b/base/numerics/safe_math_shared_impl.h
@@ -0,0 +1,237 @@
+// 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_NUMERICS_SAFE_MATH_SHARED_IMPL_H_
+#define BASE_NUMERICS_SAFE_MATH_SHARED_IMPL_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <cassert>
+#include <climits>
+#include <cmath>
+#include <cstdlib>
+#include <limits>
+#include <type_traits>
+
+#include "base/numerics/safe_conversions.h"
+
+// Where available use builtin math overflow support on Clang and GCC.
+#if !defined(__native_client__) && \
+ ((defined(__clang__) && \
+ ((__clang_major__ > 3) || \
+ (__clang_major__ == 3 && __clang_minor__ >= 4))) || \
+ (defined(__GNUC__) && __GNUC__ >= 5))
+#include "base/numerics/safe_math_clang_gcc_impl.h"
+#define BASE_HAS_OPTIMIZED_SAFE_MATH (1)
+#else
+#define BASE_HAS_OPTIMIZED_SAFE_MATH (0)
+#endif
+
+namespace base {
+namespace internal {
+
+// These are the non-functioning boilerplate implementations of the optimized
+// safe math routines.
+#if !BASE_HAS_OPTIMIZED_SAFE_MATH
+template <typename T, typename U>
+struct CheckedAddFastOp {
+ static const bool is_supported = false;
+ template <typename V>
+ static constexpr bool Do(T, U, V*) {
+ // Force a compile failure if instantiated.
+ return CheckOnFailure::template HandleFailure<bool>();
+ }
+};
+
+template <typename T, typename U>
+struct CheckedSubFastOp {
+ static const bool is_supported = false;
+ template <typename V>
+ static constexpr bool Do(T, U, V*) {
+ // Force a compile failure if instantiated.
+ return CheckOnFailure::template HandleFailure<bool>();
+ }
+};
+
+template <typename T, typename U>
+struct CheckedMulFastOp {
+ static const bool is_supported = false;
+ template <typename V>
+ static constexpr bool Do(T, U, V*) {
+ // Force a compile failure if instantiated.
+ return CheckOnFailure::template HandleFailure<bool>();
+ }
+};
+
+template <typename T, typename U>
+struct ClampedAddFastOp {
+ static const bool is_supported = false;
+ template <typename V>
+ static constexpr V Do(T, U) {
+ // Force a compile failure if instantiated.
+ return CheckOnFailure::template HandleFailure<V>();
+ }
+};
+
+template <typename T, typename U>
+struct ClampedSubFastOp {
+ static const bool is_supported = false;
+ template <typename V>
+ static constexpr V Do(T, U) {
+ // Force a compile failure if instantiated.
+ return CheckOnFailure::template HandleFailure<V>();
+ }
+};
+
+template <typename T, typename U>
+struct ClampedMulFastOp {
+ static const bool is_supported = false;
+ template <typename V>
+ static constexpr V Do(T, U) {
+ // Force a compile failure if instantiated.
+ return CheckOnFailure::template HandleFailure<V>();
+ }
+};
+
+template <typename T>
+struct ClampedNegFastOp {
+ static const bool is_supported = false;
+ static constexpr T Do(T) {
+ // Force a compile failure if instantiated.
+ return CheckOnFailure::template HandleFailure<T>();
+ }
+};
+#endif // BASE_HAS_OPTIMIZED_SAFE_MATH
+#undef BASE_HAS_OPTIMIZED_SAFE_MATH
+
+// This is used for UnsignedAbs, where we need to support floating-point
+// template instantiations even though we don't actually support the operations.
+// However, there is no corresponding implementation of e.g. SafeUnsignedAbs,
+// so the float versions will not compile.
+template <typename Numeric,
+ bool IsInteger = std::is_integral<Numeric>::value,
+ bool IsFloat = std::is_floating_point<Numeric>::value>
+struct UnsignedOrFloatForSize;
+
+template <typename Numeric>
+struct UnsignedOrFloatForSize<Numeric, true, false> {
+ using type = typename std::make_unsigned<Numeric>::type;
+};
+
+template <typename Numeric>
+struct UnsignedOrFloatForSize<Numeric, false, true> {
+ using type = Numeric;
+};
+
+// Wrap the unary operations to allow SFINAE when instantiating integrals versus
+// floating points. These don't perform any overflow checking. Rather, they
+// exhibit well-defined overflow semantics and rely on the caller to detect
+// if an overflow occured.
+
+template <typename T,
+ typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+constexpr T NegateWrapper(T value) {
+ using UnsignedT = typename std::make_unsigned<T>::type;
+ // This will compile to a NEG on Intel, and is normal negation on ARM.
+ return static_cast<T>(UnsignedT(0) - static_cast<UnsignedT>(value));
+}
+
+template <
+ typename T,
+ typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
+constexpr T NegateWrapper(T value) {
+ return -value;
+}
+
+template <typename T,
+ typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+constexpr typename std::make_unsigned<T>::type InvertWrapper(T value) {
+ return ~value;
+}
+
+template <typename T,
+ typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+constexpr T AbsWrapper(T value) {
+ return static_cast<T>(SafeUnsignedAbs(value));
+}
+
+template <
+ typename T,
+ typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
+constexpr T AbsWrapper(T value) {
+ return value < 0 ? -value : value;
+}
+
+template <template <typename, typename, typename> class M,
+ typename L,
+ typename R>
+struct MathWrapper {
+ using math = M<typename UnderlyingType<L>::type,
+ typename UnderlyingType<R>::type,
+ void>;
+ using type = typename math::result_type;
+};
+
+// These variadic templates work out the return types.
+// TODO(jschuh): Rip all this out once we have C++14 non-trailing auto support.
+template <template <typename, typename, typename> class M,
+ typename L,
+ typename R,
+ typename... Args>
+struct ResultType;
+
+template <template <typename, typename, typename> class M,
+ typename L,
+ typename R>
+struct ResultType<M, L, R> {
+ using type = typename MathWrapper<M, L, R>::type;
+};
+
+template <template <typename, typename, typename> class M,
+ typename L,
+ typename R,
+ typename... Args>
+struct ResultType {
+ using type =
+ typename ResultType<M, typename ResultType<M, L, R>::type, Args...>::type;
+};
+
+// The following macros are just boilerplate for the standard arithmetic
+// operator overloads and variadic function templates. A macro isn't the nicest
+// solution, but it beats rewriting these over and over again.
+#define BASE_NUMERIC_ARITHMETIC_VARIADIC(CLASS, CL_ABBR, OP_NAME) \
+ template <typename L, typename R, typename... Args> \
+ constexpr CLASS##Numeric< \
+ typename ResultType<CLASS##OP_NAME##Op, L, R, Args...>::type> \
+ CL_ABBR##OP_NAME(const L lhs, const R rhs, const Args... args) { \
+ return CL_ABBR##MathOp<CLASS##OP_NAME##Op, L, R, Args...>(lhs, rhs, \
+ args...); \
+ }
+
+#define BASE_NUMERIC_ARITHMETIC_OPERATORS(CLASS, CL_ABBR, OP_NAME, OP, CMP_OP) \
+ /* Binary arithmetic operator for all CLASS##Numeric operations. */ \
+ template <typename L, typename R, \
+ typename std::enable_if<Is##CLASS##Op<L, R>::value>::type* = \
+ nullptr> \
+ constexpr CLASS##Numeric< \
+ typename MathWrapper<CLASS##OP_NAME##Op, L, R>::type> \
+ operator OP(const L lhs, const R rhs) { \
+ return decltype(lhs OP rhs)::template MathOp<CLASS##OP_NAME##Op>(lhs, \
+ rhs); \
+ } \
+ /* Assignment arithmetic operator implementation from CLASS##Numeric. */ \
+ template <typename L> \
+ template <typename R> \
+ constexpr CLASS##Numeric<L>& CLASS##Numeric<L>::operator CMP_OP( \
+ const R rhs) { \
+ return MathOp<CLASS##OP_NAME##Op>(rhs); \
+ } \
+ /* Variadic arithmetic functions that return CLASS##Numeric. */ \
+ BASE_NUMERIC_ARITHMETIC_VARIADIC(CLASS, CL_ABBR, OP_NAME)
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_NUMERICS_SAFE_MATH_SHARED_IMPL_H_
diff --git a/base/numerics/safe_numerics_unittest.cc b/base/numerics/safe_numerics_unittest.cc
deleted file mode 100644
index ec6d0037c9..0000000000
--- a/base/numerics/safe_numerics_unittest.cc
+++ /dev/null
@@ -1,1186 +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.
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <limits>
-#include <type_traits>
-
-#include "base/compiler_specific.h"
-#include "base/logging.h"
-#include "base/numerics/safe_conversions.h"
-#include "base/numerics/safe_math.h"
-#include "base/test/gtest_util.h"
-#include "build/build_config.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-#if defined(COMPILER_MSVC) && defined(ARCH_CPU_32_BITS)
-#include <mmintrin.h>
-#endif
-
-using std::numeric_limits;
-using base::CheckedNumeric;
-using base::IsValidForType;
-using base::ValueOrDieForType;
-using base::ValueOrDefaultForType;
-using base::MakeCheckedNum;
-using base::CheckMax;
-using base::CheckMin;
-using base::CheckAdd;
-using base::CheckSub;
-using base::CheckMul;
-using base::CheckDiv;
-using base::CheckMod;
-using base::CheckLsh;
-using base::CheckRsh;
-using base::checked_cast;
-using base::IsValueInRangeForNumericType;
-using base::IsValueNegative;
-using base::SizeT;
-using base::StrictNumeric;
-using base::MakeStrictNum;
-using base::saturated_cast;
-using base::strict_cast;
-using base::internal::MaxExponent;
-using base::internal::IntegerBitsPlusSign;
-using base::internal::RangeCheck;
-
-// These tests deliberately cause arithmetic boundary errors. If the compiler is
-// aggressive enough, it can const detect these errors, so we disable warnings.
-#if defined(OS_WIN)
-#pragma warning(disable : 4756) // Arithmetic overflow.
-#pragma warning(disable : 4293) // Invalid shift.
-#endif
-
-// This is a helper function for finding the maximum value in Src that can be
-// wholy represented as the destination floating-point type.
-template <typename Dst, typename Src>
-Dst GetMaxConvertibleToFloat() {
- using DstLimits = numeric_limits<Dst>;
- using SrcLimits = numeric_limits<Src>;
- static_assert(SrcLimits::is_specialized, "Source must be numeric.");
- static_assert(DstLimits::is_specialized, "Destination must be numeric.");
- CHECK(DstLimits::is_iec559);
-
- if (SrcLimits::digits <= DstLimits::digits &&
- MaxExponent<Src>::value <= MaxExponent<Dst>::value)
- return SrcLimits::max();
- Src max = SrcLimits::max() / 2 + (SrcLimits::is_integer ? 1 : 0);
- while (max != static_cast<Src>(static_cast<Dst>(max))) {
- max /= 2;
- }
- return static_cast<Dst>(max);
-}
-
-namespace base {
-namespace internal {
-
-// Test corner case promotions used
-static_assert(IsIntegerArithmeticSafe<int32_t, int8_t, int8_t>::value, "");
-static_assert(IsIntegerArithmeticSafe<int32_t, int16_t, int8_t>::value, "");
-static_assert(IsIntegerArithmeticSafe<int32_t, int8_t, int16_t>::value, "");
-static_assert(!IsIntegerArithmeticSafe<int32_t, int32_t, int8_t>::value, "");
-static_assert(BigEnoughPromotion<int16_t, int8_t>::is_contained, "");
-static_assert(BigEnoughPromotion<int32_t, uint32_t>::is_contained, "");
-static_assert(BigEnoughPromotion<intmax_t, int8_t>::is_contained, "");
-static_assert(!BigEnoughPromotion<uintmax_t, int8_t>::is_contained, "");
-static_assert(
- std::is_same<BigEnoughPromotion<int16_t, int8_t>::type, int16_t>::value,
- "");
-static_assert(
- std::is_same<BigEnoughPromotion<int32_t, uint32_t>::type, int64_t>::value,
- "");
-static_assert(
- std::is_same<BigEnoughPromotion<intmax_t, int8_t>::type, intmax_t>::value,
- "");
-static_assert(
- std::is_same<BigEnoughPromotion<uintmax_t, int8_t>::type, uintmax_t>::value,
- "");
-static_assert(BigEnoughPromotion<int16_t, int8_t>::is_contained, "");
-static_assert(BigEnoughPromotion<int32_t, uint32_t>::is_contained, "");
-static_assert(BigEnoughPromotion<intmax_t, int8_t>::is_contained, "");
-static_assert(!BigEnoughPromotion<uintmax_t, int8_t>::is_contained, "");
-static_assert(
- std::is_same<FastIntegerArithmeticPromotion<int16_t, int8_t>::type,
- int32_t>::value,
- "");
-static_assert(
- std::is_same<FastIntegerArithmeticPromotion<int32_t, uint32_t>::type,
- int64_t>::value,
- "");
-static_assert(
- std::is_same<FastIntegerArithmeticPromotion<intmax_t, int8_t>::type,
- intmax_t>::value,
- "");
-static_assert(
- std::is_same<FastIntegerArithmeticPromotion<uintmax_t, int8_t>::type,
- uintmax_t>::value,
- "");
-static_assert(FastIntegerArithmeticPromotion<int16_t, int8_t>::is_contained,
- "");
-static_assert(FastIntegerArithmeticPromotion<int32_t, uint32_t>::is_contained,
- "");
-static_assert(!FastIntegerArithmeticPromotion<intmax_t, int8_t>::is_contained,
- "");
-static_assert(!FastIntegerArithmeticPromotion<uintmax_t, int8_t>::is_contained,
- "");
-
-template <typename U>
-U GetNumericValueForTest(const CheckedNumeric<U>& src) {
- return src.state_.value();
-}
-} // namespace internal.
-} // namespace base.
-
-using base::internal::GetNumericValueForTest;
-
-// Logs the ValueOrDie() failure instead of crashing.
-struct LogOnFailure {
- template <typename T>
- static T HandleFailure() {
- LOG(WARNING) << "ValueOrDie() failed unexpectedly.";
- return T();
- }
-};
-
-// Helper macros to wrap displaying the conversion types and line numbers.
-#define TEST_EXPECTED_VALIDITY(expected, actual) \
- EXPECT_EQ(expected, (actual).template Cast<Dst>().IsValid()) \
- << "Result test: Value " << GetNumericValueForTest(actual) << " as " \
- << dst << " on line " << line
-
-#define TEST_EXPECTED_SUCCESS(actual) TEST_EXPECTED_VALIDITY(true, actual)
-#define TEST_EXPECTED_FAILURE(actual) TEST_EXPECTED_VALIDITY(false, actual)
-
-// We have to handle promotions, so infer the underlying type below from actual.
-#define TEST_EXPECTED_VALUE(expected, actual) \
- EXPECT_EQ(static_cast<typename std::decay<decltype(actual)>::type::type>( \
- expected), \
- ((actual) \
- .template ValueOrDie< \
- typename std::decay<decltype(actual)>::type::type, \
- LogOnFailure>())) \
- << "Result test: Value " << GetNumericValueForTest(actual) << " as " \
- << dst << " on line " << line
-
-// Test the simple pointer arithmetic overrides.
-template <typename Dst>
-void TestStrictPointerMath() {
- Dst dummy_value = 0;
- Dst* dummy_ptr = &dummy_value;
- static const Dst kDummyOffset = 2; // Don't want to go too far.
- EXPECT_EQ(dummy_ptr + kDummyOffset,
- dummy_ptr + StrictNumeric<Dst>(kDummyOffset));
- EXPECT_EQ(dummy_ptr - kDummyOffset,
- dummy_ptr - StrictNumeric<Dst>(kDummyOffset));
- EXPECT_NE(dummy_ptr, dummy_ptr + StrictNumeric<Dst>(kDummyOffset));
- EXPECT_NE(dummy_ptr, dummy_ptr - StrictNumeric<Dst>(kDummyOffset));
- EXPECT_DEATH_IF_SUPPORTED(
- dummy_ptr + StrictNumeric<size_t>(std::numeric_limits<size_t>::max()),
- "");
-}
-
-// Signed integer arithmetic.
-template <typename Dst>
-static void TestSpecializedArithmetic(
- const char* dst,
- int line,
- typename std::enable_if<numeric_limits<Dst>::is_integer &&
- numeric_limits<Dst>::is_signed,
- int>::type = 0) {
- using DstLimits = numeric_limits<Dst>;
- TEST_EXPECTED_FAILURE(-CheckedNumeric<Dst>(DstLimits::lowest()));
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()).Abs());
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(-1).Abs());
- TEST_EXPECTED_VALUE(DstLimits::max(),
- MakeCheckedNum(-DstLimits::max()).Abs());
-
- TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::max()) + -1);
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) + -1);
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) +
- DstLimits::lowest());
-
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) - 1);
- TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::lowest()) - -1);
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::max()) -
- DstLimits::lowest());
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) -
- DstLimits::max());
-
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) * 2);
-
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) / -1);
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(-1) / 2);
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) * -1);
- TEST_EXPECTED_VALUE(DstLimits::max(),
- CheckedNumeric<Dst>(DstLimits::lowest() + 1) * Dst(-1));
- TEST_EXPECTED_VALUE(DstLimits::max(),
- CheckedNumeric<Dst>(-1) * Dst(DstLimits::lowest() + 1));
- TEST_EXPECTED_VALUE(DstLimits::lowest(),
- CheckedNumeric<Dst>(DstLimits::lowest()) * Dst(1));
- TEST_EXPECTED_VALUE(DstLimits::lowest(),
- CheckedNumeric<Dst>(1) * Dst(DstLimits::lowest()));
- TEST_EXPECTED_VALUE(DstLimits::lowest(),
- MakeCheckedNum(DstLimits::lowest()).UnsignedAbs());
- TEST_EXPECTED_VALUE(DstLimits::max(),
- MakeCheckedNum(DstLimits::max()).UnsignedAbs());
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(0).UnsignedAbs());
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1).UnsignedAbs());
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(-1).UnsignedAbs());
-
- // Modulus is legal only for integers.
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>() % 1);
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) % 1);
- TEST_EXPECTED_VALUE(-1, CheckedNumeric<Dst>(-1) % 2);
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(-1) % -2);
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(DstLimits::lowest()) % 2);
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(DstLimits::max()) % 2);
- // Test all the different modulus combinations.
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) % CheckedNumeric<Dst>(1));
- TEST_EXPECTED_VALUE(0, 1 % CheckedNumeric<Dst>(1));
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) % 1);
- CheckedNumeric<Dst> checked_dst = 1;
- TEST_EXPECTED_VALUE(0, checked_dst %= 1);
- // Test that div by 0 is avoided but returns invalid result.
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1) % 0);
- // Test bit shifts.
- volatile Dst negative_one = -1;
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1) << negative_one);
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1)
- << (IntegerBitsPlusSign<Dst>::value - 1));
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(0)
- << IntegerBitsPlusSign<Dst>::value);
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::max()) << 1);
- TEST_EXPECTED_VALUE(
- static_cast<Dst>(1) << (IntegerBitsPlusSign<Dst>::value - 2),
- CheckedNumeric<Dst>(1) << (IntegerBitsPlusSign<Dst>::value - 2));
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(0)
- << (IntegerBitsPlusSign<Dst>::value - 1));
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) << 0);
- TEST_EXPECTED_VALUE(2, CheckedNumeric<Dst>(1) << 1);
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1) >>
- IntegerBitsPlusSign<Dst>::value);
- TEST_EXPECTED_VALUE(
- 0, CheckedNumeric<Dst>(1) >> (IntegerBitsPlusSign<Dst>::value - 1));
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1) >> negative_one);
-
- TestStrictPointerMath<Dst>();
-}
-
-// Unsigned integer arithmetic.
-template <typename Dst>
-static void TestSpecializedArithmetic(
- const char* dst,
- int line,
- typename std::enable_if<numeric_limits<Dst>::is_integer &&
- !numeric_limits<Dst>::is_signed,
- int>::type = 0) {
- using DstLimits = numeric_limits<Dst>;
- TEST_EXPECTED_SUCCESS(-CheckedNumeric<Dst>(DstLimits::lowest()));
- TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::lowest()).Abs());
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) + -1);
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) - 1);
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(DstLimits::lowest()) * 2);
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) / 2);
- TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::lowest()).UnsignedAbs());
- TEST_EXPECTED_SUCCESS(
- CheckedNumeric<typename std::make_signed<Dst>::type>(
- std::numeric_limits<typename std::make_signed<Dst>::type>::lowest())
- .UnsignedAbs());
- TEST_EXPECTED_VALUE(DstLimits::lowest(),
- MakeCheckedNum(DstLimits::lowest()).UnsignedAbs());
- TEST_EXPECTED_VALUE(DstLimits::max(),
- MakeCheckedNum(DstLimits::max()).UnsignedAbs());
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(0).UnsignedAbs());
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1).UnsignedAbs());
-
- // Modulus is legal only for integers.
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>() % 1);
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) % 1);
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) % 2);
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(DstLimits::lowest()) % 2);
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(DstLimits::max()) % 2);
- // Test all the different modulus combinations.
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) % CheckedNumeric<Dst>(1));
- TEST_EXPECTED_VALUE(0, 1 % CheckedNumeric<Dst>(1));
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) % 1);
- CheckedNumeric<Dst> checked_dst = 1;
- TEST_EXPECTED_VALUE(0, checked_dst %= 1);
- // Test that div by 0 is avoided but returns invalid result.
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1) % 0);
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1)
- << IntegerBitsPlusSign<Dst>::value);
- // Test bit shifts.
- volatile int negative_one = -1;
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1) << negative_one);
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1)
- << IntegerBitsPlusSign<Dst>::value);
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(0)
- << IntegerBitsPlusSign<Dst>::value);
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::max()) << 1);
- TEST_EXPECTED_VALUE(
- static_cast<Dst>(1) << (IntegerBitsPlusSign<Dst>::value - 1),
- CheckedNumeric<Dst>(1) << (IntegerBitsPlusSign<Dst>::value - 1));
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) << 0);
- TEST_EXPECTED_VALUE(2, CheckedNumeric<Dst>(1) << 1);
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1) >>
- IntegerBitsPlusSign<Dst>::value);
- TEST_EXPECTED_VALUE(
- 0, CheckedNumeric<Dst>(1) >> (IntegerBitsPlusSign<Dst>::value - 1));
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(1) >> negative_one);
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) & 1);
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) & 0);
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(0) & 1);
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) & 0);
- TEST_EXPECTED_VALUE(std::numeric_limits<Dst>::max(),
- MakeCheckedNum(DstLimits::max()) & -1);
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) | 1);
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) | 0);
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(0) | 1);
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(0) | 0);
- TEST_EXPECTED_VALUE(std::numeric_limits<Dst>::max(),
- CheckedNumeric<Dst>(0) | static_cast<Dst>(-1));
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) ^ 1);
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) ^ 0);
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(0) ^ 1);
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(0) ^ 0);
- TEST_EXPECTED_VALUE(std::numeric_limits<Dst>::max(),
- CheckedNumeric<Dst>(0) ^ static_cast<Dst>(-1));
- TEST_EXPECTED_VALUE(DstLimits::max(), ~CheckedNumeric<Dst>(0));
-
- TestStrictPointerMath<Dst>();
-}
-
-// Floating point arithmetic.
-template <typename Dst>
-void TestSpecializedArithmetic(
- const char* dst,
- int line,
- typename std::enable_if<numeric_limits<Dst>::is_iec559, int>::type = 0) {
- using DstLimits = numeric_limits<Dst>;
- TEST_EXPECTED_SUCCESS(-CheckedNumeric<Dst>(DstLimits::lowest()));
-
- TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::lowest()).Abs());
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(-1).Abs());
-
- TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::lowest()) + -1);
- TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::max()) + 1);
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) +
- DstLimits::lowest());
-
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::max()) -
- DstLimits::lowest());
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) -
- DstLimits::max());
-
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) * 2);
-
- TEST_EXPECTED_VALUE(-0.5, CheckedNumeric<Dst>(-1.0) / 2);
-}
-
-// Generic arithmetic tests.
-template <typename Dst>
-static void TestArithmetic(const char* dst, int line) {
- using DstLimits = numeric_limits<Dst>;
-
- EXPECT_EQ(true, CheckedNumeric<Dst>().IsValid());
- EXPECT_EQ(false,
- CheckedNumeric<Dst>(CheckedNumeric<Dst>(DstLimits::max()) *
- DstLimits::max()).IsValid());
- EXPECT_EQ(static_cast<Dst>(0), CheckedNumeric<Dst>().ValueOrDie());
- EXPECT_EQ(static_cast<Dst>(0), CheckedNumeric<Dst>().ValueOrDefault(1));
- EXPECT_EQ(static_cast<Dst>(1),
- CheckedNumeric<Dst>(CheckedNumeric<Dst>(DstLimits::max()) *
- DstLimits::max()).ValueOrDefault(1));
-
- // Test the operator combinations.
- TEST_EXPECTED_VALUE(2, CheckedNumeric<Dst>(1) + CheckedNumeric<Dst>(1));
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) - CheckedNumeric<Dst>(1));
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) * CheckedNumeric<Dst>(1));
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) / CheckedNumeric<Dst>(1));
- TEST_EXPECTED_VALUE(2, 1 + CheckedNumeric<Dst>(1));
- TEST_EXPECTED_VALUE(0, 1 - CheckedNumeric<Dst>(1));
- TEST_EXPECTED_VALUE(1, 1 * CheckedNumeric<Dst>(1));
- TEST_EXPECTED_VALUE(1, 1 / CheckedNumeric<Dst>(1));
- TEST_EXPECTED_VALUE(2, CheckedNumeric<Dst>(1) + 1);
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(1) - 1);
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) * 1);
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) / 1);
- CheckedNumeric<Dst> checked_dst = 1;
- TEST_EXPECTED_VALUE(2, checked_dst += 1);
- checked_dst = 1;
- TEST_EXPECTED_VALUE(0, checked_dst -= 1);
- checked_dst = 1;
- TEST_EXPECTED_VALUE(1, checked_dst *= 1);
- checked_dst = 1;
- TEST_EXPECTED_VALUE(1, checked_dst /= 1);
-
- // Generic negation.
- if (DstLimits::is_signed) {
- TEST_EXPECTED_VALUE(0, -CheckedNumeric<Dst>());
- TEST_EXPECTED_VALUE(-1, -CheckedNumeric<Dst>(1));
- TEST_EXPECTED_VALUE(1, -CheckedNumeric<Dst>(-1));
- TEST_EXPECTED_VALUE(static_cast<Dst>(DstLimits::max() * -1),
- -CheckedNumeric<Dst>(DstLimits::max()));
- }
-
- // Generic absolute value.
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>().Abs());
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1).Abs());
- TEST_EXPECTED_VALUE(DstLimits::max(),
- CheckedNumeric<Dst>(DstLimits::max()).Abs());
-
- // Generic addition.
- TEST_EXPECTED_VALUE(1, (CheckedNumeric<Dst>() + 1));
- TEST_EXPECTED_VALUE(2, (CheckedNumeric<Dst>(1) + 1));
- if (numeric_limits<Dst>::is_signed)
- TEST_EXPECTED_VALUE(0, (CheckedNumeric<Dst>(-1) + 1));
- TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::lowest()) + 1);
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::max()) +
- DstLimits::max());
-
- // Generic subtraction.
- TEST_EXPECTED_VALUE(0, (CheckedNumeric<Dst>(1) - 1));
- TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::max()) - 1);
- if (numeric_limits<Dst>::is_signed) {
- TEST_EXPECTED_VALUE(-1, (CheckedNumeric<Dst>() - 1));
- TEST_EXPECTED_VALUE(-2, (CheckedNumeric<Dst>(-1) - 1));
- } else {
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::max()) - -1);
- }
-
- // Generic multiplication.
- TEST_EXPECTED_VALUE(0, (CheckedNumeric<Dst>() * 1));
- TEST_EXPECTED_VALUE(1, (CheckedNumeric<Dst>(1) * 1));
- TEST_EXPECTED_VALUE(0, (CheckedNumeric<Dst>(0) * 0));
- if (numeric_limits<Dst>::is_signed) {
- TEST_EXPECTED_VALUE(0, (CheckedNumeric<Dst>(-1) * 0));
- TEST_EXPECTED_VALUE(0, (CheckedNumeric<Dst>(0) * -1));
- TEST_EXPECTED_VALUE(-2, (CheckedNumeric<Dst>(-1) * 2));
- } else {
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::max()) * -2);
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::max()) *
- CheckedNumeric<uintmax_t>(-2));
- }
- TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::max()) *
- DstLimits::max());
-
- // Generic division.
- TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>() / 1);
- TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1) / 1);
- TEST_EXPECTED_VALUE(DstLimits::lowest() / 2,
- CheckedNumeric<Dst>(DstLimits::lowest()) / 2);
- TEST_EXPECTED_VALUE(DstLimits::max() / 2,
- CheckedNumeric<Dst>(DstLimits::max()) / 2);
-
- TestSpecializedArithmetic<Dst>(dst, line);
-}
-
-// Helper macro to wrap displaying the conversion types and line numbers.
-#define TEST_ARITHMETIC(Dst) TestArithmetic<Dst>(#Dst, __LINE__)
-
-TEST(SafeNumerics, SignedIntegerMath) {
- TEST_ARITHMETIC(int8_t);
- TEST_ARITHMETIC(int);
- TEST_ARITHMETIC(intptr_t);
- TEST_ARITHMETIC(intmax_t);
-}
-
-TEST(SafeNumerics, UnsignedIntegerMath) {
- TEST_ARITHMETIC(uint8_t);
- TEST_ARITHMETIC(unsigned int);
- TEST_ARITHMETIC(uintptr_t);
- TEST_ARITHMETIC(uintmax_t);
-}
-
-TEST(SafeNumerics, FloatingPointMath) {
- TEST_ARITHMETIC(float);
- TEST_ARITHMETIC(double);
-}
-
-// Enumerates the five different conversions types we need to test.
-enum NumericConversionType {
- SIGN_PRESERVING_VALUE_PRESERVING,
- SIGN_PRESERVING_NARROW,
- SIGN_TO_UNSIGN_WIDEN_OR_EQUAL,
- SIGN_TO_UNSIGN_NARROW,
- UNSIGN_TO_SIGN_NARROW_OR_EQUAL,
-};
-
-// Template covering the different conversion tests.
-template <typename Dst, typename Src, NumericConversionType conversion>
-struct TestNumericConversion {};
-
-enum RangeConstraint {
- RANGE_VALID = 0x0, // Value can be represented by the destination type.
- RANGE_UNDERFLOW = 0x1, // Value would underflow.
- RANGE_OVERFLOW = 0x2, // Value would overflow.
- RANGE_INVALID = RANGE_UNDERFLOW | RANGE_OVERFLOW // Invalid (i.e. NaN).
-};
-
-// These are some wrappers to make the tests a bit cleaner.
-constexpr RangeConstraint RangeCheckToEnum(const RangeCheck constraint) {
- return static_cast<RangeConstraint>(
- static_cast<int>(constraint.IsOverflowFlagSet()) << 1 |
- static_cast<int>(constraint.IsUnderflowFlagSet()));
-}
-
-// EXPECT_EQ wrappers providing specific detail on test failures.
-#define TEST_EXPECTED_RANGE(expected, actual) \
- EXPECT_EQ(expected, \
- RangeCheckToEnum( \
- base::internal::DstRangeRelationToSrcRange<Dst>(actual))) \
- << "Conversion test: " << src << " value " << actual << " to " << dst \
- << " on line " << line
-
-template <typename Dst, typename Src>
-void TestStrictComparison() {
- using DstLimits = numeric_limits<Dst>;
- using SrcLimits = numeric_limits<Src>;
- static_assert(StrictNumeric<Src>(SrcLimits::lowest()) < DstLimits::max(), "");
- static_assert(StrictNumeric<Src>(SrcLimits::lowest()) < SrcLimits::max(), "");
- static_assert(!(StrictNumeric<Src>(SrcLimits::lowest()) >= DstLimits::max()),
- "");
- static_assert(!(StrictNumeric<Src>(SrcLimits::lowest()) >= SrcLimits::max()),
- "");
- static_assert(StrictNumeric<Src>(SrcLimits::lowest()) <= DstLimits::max(),
- "");
- static_assert(StrictNumeric<Src>(SrcLimits::lowest()) <= SrcLimits::max(),
- "");
- static_assert(!(StrictNumeric<Src>(SrcLimits::lowest()) > DstLimits::max()),
- "");
- static_assert(!(StrictNumeric<Src>(SrcLimits::lowest()) > SrcLimits::max()),
- "");
- static_assert(StrictNumeric<Src>(SrcLimits::max()) > DstLimits::lowest(), "");
- static_assert(StrictNumeric<Src>(SrcLimits::max()) > SrcLimits::lowest(), "");
- static_assert(!(StrictNumeric<Src>(SrcLimits::max()) <= DstLimits::lowest()),
- "");
- static_assert(!(StrictNumeric<Src>(SrcLimits::max()) <= SrcLimits::lowest()),
- "");
- static_assert(StrictNumeric<Src>(SrcLimits::max()) >= DstLimits::lowest(),
- "");
- static_assert(StrictNumeric<Src>(SrcLimits::max()) >= SrcLimits::lowest(),
- "");
- static_assert(!(StrictNumeric<Src>(SrcLimits::max()) < DstLimits::lowest()),
- "");
- static_assert(!(StrictNumeric<Src>(SrcLimits::max()) < SrcLimits::lowest()),
- "");
- static_assert(StrictNumeric<Src>(static_cast<Src>(1)) == static_cast<Dst>(1),
- "");
- static_assert(StrictNumeric<Src>(static_cast<Src>(1)) != static_cast<Dst>(0),
- "");
- static_assert(StrictNumeric<Src>(SrcLimits::max()) != static_cast<Dst>(0),
- "");
- static_assert(StrictNumeric<Src>(SrcLimits::max()) != DstLimits::lowest(),
- "");
- static_assert(
- !(StrictNumeric<Src>(static_cast<Src>(1)) != static_cast<Dst>(1)), "");
- static_assert(
- !(StrictNumeric<Src>(static_cast<Src>(1)) == static_cast<Dst>(0)), "");
-
- // Due to differences in float handling between compilers, these aren't
- // compile-time constants everywhere. So, we use run-time tests.
- EXPECT_EQ(
- SrcLimits::max(),
- MakeCheckedNum(SrcLimits::max()).Max(DstLimits::lowest()).ValueOrDie());
- EXPECT_EQ(
- DstLimits::max(),
- MakeCheckedNum(SrcLimits::lowest()).Max(DstLimits::max()).ValueOrDie());
- EXPECT_EQ(
- DstLimits::lowest(),
- MakeCheckedNum(SrcLimits::max()).Min(DstLimits::lowest()).ValueOrDie());
- EXPECT_EQ(
- SrcLimits::lowest(),
- MakeCheckedNum(SrcLimits::lowest()).Min(DstLimits::max()).ValueOrDie());
- EXPECT_EQ(SrcLimits::lowest(), CheckMin(MakeStrictNum(1), MakeCheckedNum(0),
- DstLimits::max(), SrcLimits::lowest())
- .ValueOrDie());
- EXPECT_EQ(DstLimits::max(), CheckMax(MakeStrictNum(1), MakeCheckedNum(0),
- DstLimits::max(), SrcLimits::lowest())
- .ValueOrDie());
-}
-
-template <typename Dst, typename Src>
-struct TestNumericConversion<Dst, Src, SIGN_PRESERVING_VALUE_PRESERVING> {
- static void Test(const char *dst, const char *src, int line) {
- using SrcLimits = numeric_limits<Src>;
- using DstLimits = numeric_limits<Dst>;
- // Integral to floating.
- static_assert((DstLimits::is_iec559 && SrcLimits::is_integer) ||
- // Not floating to integral and...
- (!(DstLimits::is_integer && SrcLimits::is_iec559) &&
- // Same sign, same numeric, source is narrower or same.
- ((SrcLimits::is_signed == DstLimits::is_signed &&
- MaxExponent<Dst>::value >= MaxExponent<Src>::value) ||
- // Or signed destination and source is smaller
- (DstLimits::is_signed &&
- MaxExponent<Dst>::value >= MaxExponent<Src>::value))),
- "Comparison must be sign preserving and value preserving");
-
- TestStrictComparison<Dst, Src>();
-
- const CheckedNumeric<Dst> checked_dst = SrcLimits::max();
- TEST_EXPECTED_SUCCESS(checked_dst);
- if (MaxExponent<Dst>::value > MaxExponent<Src>::value) {
- if (MaxExponent<Dst>::value >= MaxExponent<Src>::value * 2 - 1) {
- // At least twice larger type.
- TEST_EXPECTED_SUCCESS(SrcLimits::max() * checked_dst);
-
- } else { // Larger, but not at least twice as large.
- TEST_EXPECTED_FAILURE(SrcLimits::max() * checked_dst);
- TEST_EXPECTED_SUCCESS(checked_dst + 1);
- }
- } else { // Same width type.
- TEST_EXPECTED_FAILURE(checked_dst + 1);
- }
-
- TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::max());
- TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1));
- if (SrcLimits::is_iec559) {
- TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::max() * static_cast<Src>(-1));
- TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::infinity());
- TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::infinity() * -1);
- TEST_EXPECTED_RANGE(RANGE_INVALID, SrcLimits::quiet_NaN());
- } else if (numeric_limits<Src>::is_signed) {
- TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(-1));
- TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::lowest());
- }
- }
-};
-
-template <typename Dst, typename Src>
-struct TestNumericConversion<Dst, Src, SIGN_PRESERVING_NARROW> {
- static void Test(const char *dst, const char *src, int line) {
- using SrcLimits = numeric_limits<Src>;
- using DstLimits = numeric_limits<Dst>;
- static_assert(SrcLimits::is_signed == DstLimits::is_signed,
- "Destination and source sign must be the same");
- static_assert(MaxExponent<Dst>::value <= MaxExponent<Src>::value,
- "Destination must be narrower than source");
-
- TestStrictComparison<Dst, Src>();
-
- const CheckedNumeric<Dst> checked_dst;
- TEST_EXPECTED_FAILURE(checked_dst + SrcLimits::max());
- TEST_EXPECTED_VALUE(1, checked_dst + static_cast<Src>(1));
- TEST_EXPECTED_FAILURE(checked_dst - SrcLimits::max());
-
- TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::max());
- TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1));
- if (SrcLimits::is_iec559) {
- TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::max() * -1);
- TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(-1));
- TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::infinity());
- TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::infinity() * -1);
- TEST_EXPECTED_RANGE(RANGE_INVALID, SrcLimits::quiet_NaN());
- if (DstLimits::is_integer) {
- if (SrcLimits::digits < DstLimits::digits) {
- TEST_EXPECTED_RANGE(RANGE_OVERFLOW,
- static_cast<Src>(DstLimits::max()));
- } else {
- TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(DstLimits::max()));
- }
- TEST_EXPECTED_RANGE(
- RANGE_VALID,
- static_cast<Src>(GetMaxConvertibleToFloat<Src, Dst>()));
- TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(DstLimits::lowest()));
- }
- } else if (SrcLimits::is_signed) {
- TEST_EXPECTED_VALUE(-1, checked_dst - static_cast<Src>(1));
- TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::lowest());
- TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(-1));
- } else {
- TEST_EXPECTED_FAILURE(checked_dst - static_cast<Src>(1));
- TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::lowest());
- }
- }
-};
-
-template <typename Dst, typename Src>
-struct TestNumericConversion<Dst, Src, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL> {
- static void Test(const char *dst, const char *src, int line) {
- using SrcLimits = numeric_limits<Src>;
- using DstLimits = numeric_limits<Dst>;
- static_assert(MaxExponent<Dst>::value >= MaxExponent<Src>::value,
- "Destination must be equal or wider than source.");
- static_assert(SrcLimits::is_signed, "Source must be signed");
- static_assert(!DstLimits::is_signed, "Destination must be unsigned");
-
- TestStrictComparison<Dst, Src>();
-
- const CheckedNumeric<Dst> checked_dst;
- TEST_EXPECTED_VALUE(SrcLimits::max(), checked_dst + SrcLimits::max());
- TEST_EXPECTED_FAILURE(checked_dst + static_cast<Src>(-1));
- TEST_EXPECTED_FAILURE(checked_dst + SrcLimits::lowest());
-
- TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::lowest());
- TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::max());
- TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1));
- TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, static_cast<Src>(-1));
- }
-};
-
-template <typename Dst, typename Src>
-struct TestNumericConversion<Dst, Src, SIGN_TO_UNSIGN_NARROW> {
- static void Test(const char *dst, const char *src, int line) {
- using SrcLimits = numeric_limits<Src>;
- using DstLimits = numeric_limits<Dst>;
- static_assert(MaxExponent<Dst>::value < MaxExponent<Src>::value,
- "Destination must be narrower than source.");
- static_assert(SrcLimits::is_signed, "Source must be signed.");
- static_assert(!DstLimits::is_signed, "Destination must be unsigned.");
-
- TestStrictComparison<Dst, Src>();
-
- const CheckedNumeric<Dst> checked_dst;
- TEST_EXPECTED_VALUE(1, checked_dst + static_cast<Src>(1));
- TEST_EXPECTED_FAILURE(checked_dst + SrcLimits::max());
- TEST_EXPECTED_FAILURE(checked_dst + static_cast<Src>(-1));
- TEST_EXPECTED_FAILURE(checked_dst + SrcLimits::lowest());
-
- TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::max());
- TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1));
- TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, static_cast<Src>(-1));
-
- // Additional saturation tests.
- EXPECT_EQ(DstLimits::max(), saturated_cast<Dst>(SrcLimits::max())) << src;
- EXPECT_EQ(DstLimits::lowest(), saturated_cast<Dst>(SrcLimits::lowest()));
-
- if (SrcLimits::is_iec559) {
- EXPECT_EQ(Dst(0), saturated_cast<Dst>(SrcLimits::quiet_NaN()));
-
- TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::max() * -1);
- TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::infinity());
- TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::infinity() * -1);
- TEST_EXPECTED_RANGE(RANGE_INVALID, SrcLimits::quiet_NaN());
- if (DstLimits::is_integer) {
- if (SrcLimits::digits < DstLimits::digits) {
- TEST_EXPECTED_RANGE(RANGE_OVERFLOW,
- static_cast<Src>(DstLimits::max()));
- } else {
- TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(DstLimits::max()));
- }
- TEST_EXPECTED_RANGE(
- RANGE_VALID,
- static_cast<Src>(GetMaxConvertibleToFloat<Src, Dst>()));
- TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(DstLimits::lowest()));
- }
- } else {
- TEST_EXPECTED_RANGE(RANGE_UNDERFLOW, SrcLimits::lowest());
- }
- }
-};
-
-template <typename Dst, typename Src>
-struct TestNumericConversion<Dst, Src, UNSIGN_TO_SIGN_NARROW_OR_EQUAL> {
- static void Test(const char *dst, const char *src, int line) {
- using SrcLimits = numeric_limits<Src>;
- using DstLimits = numeric_limits<Dst>;
- static_assert(MaxExponent<Dst>::value <= MaxExponent<Src>::value,
- "Destination must be narrower or equal to source.");
- static_assert(!SrcLimits::is_signed, "Source must be unsigned.");
- static_assert(DstLimits::is_signed, "Destination must be signed.");
-
- TestStrictComparison<Dst, Src>();
-
- const CheckedNumeric<Dst> checked_dst;
- TEST_EXPECTED_VALUE(1, checked_dst + static_cast<Src>(1));
- TEST_EXPECTED_FAILURE(checked_dst + SrcLimits::max());
- TEST_EXPECTED_VALUE(SrcLimits::lowest(), checked_dst + SrcLimits::lowest());
-
- TEST_EXPECTED_RANGE(RANGE_VALID, SrcLimits::lowest());
- TEST_EXPECTED_RANGE(RANGE_OVERFLOW, SrcLimits::max());
- TEST_EXPECTED_RANGE(RANGE_VALID, static_cast<Src>(1));
-
- // Additional saturation tests.
- EXPECT_EQ(DstLimits::max(), saturated_cast<Dst>(SrcLimits::max()));
- EXPECT_EQ(Dst(0), saturated_cast<Dst>(SrcLimits::lowest()));
- }
-};
-
-// Helper macro to wrap displaying the conversion types and line numbers
-#define TEST_NUMERIC_CONVERSION(d, s, t) \
- TestNumericConversion<d, s, t>::Test(#d, #s, __LINE__)
-
-TEST(SafeNumerics, IntMinOperations) {
- TEST_NUMERIC_CONVERSION(int8_t, int8_t, SIGN_PRESERVING_VALUE_PRESERVING);
- TEST_NUMERIC_CONVERSION(uint8_t, uint8_t, SIGN_PRESERVING_VALUE_PRESERVING);
-
- TEST_NUMERIC_CONVERSION(int8_t, int, SIGN_PRESERVING_NARROW);
- TEST_NUMERIC_CONVERSION(uint8_t, unsigned int, SIGN_PRESERVING_NARROW);
- TEST_NUMERIC_CONVERSION(int8_t, float, SIGN_PRESERVING_NARROW);
-
- TEST_NUMERIC_CONVERSION(uint8_t, int8_t, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL);
-
- TEST_NUMERIC_CONVERSION(uint8_t, int, SIGN_TO_UNSIGN_NARROW);
- TEST_NUMERIC_CONVERSION(uint8_t, intmax_t, SIGN_TO_UNSIGN_NARROW);
- TEST_NUMERIC_CONVERSION(uint8_t, float, SIGN_TO_UNSIGN_NARROW);
-
- TEST_NUMERIC_CONVERSION(int8_t, unsigned int, UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
- TEST_NUMERIC_CONVERSION(int8_t, uintmax_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
-}
-
-TEST(SafeNumerics, IntOperations) {
- TEST_NUMERIC_CONVERSION(int, int, SIGN_PRESERVING_VALUE_PRESERVING);
- TEST_NUMERIC_CONVERSION(unsigned int, unsigned int,
- SIGN_PRESERVING_VALUE_PRESERVING);
- TEST_NUMERIC_CONVERSION(int, int8_t, SIGN_PRESERVING_VALUE_PRESERVING);
- TEST_NUMERIC_CONVERSION(unsigned int, uint8_t,
- SIGN_PRESERVING_VALUE_PRESERVING);
- TEST_NUMERIC_CONVERSION(int, uint8_t, SIGN_PRESERVING_VALUE_PRESERVING);
-
- TEST_NUMERIC_CONVERSION(int, intmax_t, SIGN_PRESERVING_NARROW);
- TEST_NUMERIC_CONVERSION(unsigned int, uintmax_t, SIGN_PRESERVING_NARROW);
- TEST_NUMERIC_CONVERSION(int, float, SIGN_PRESERVING_NARROW);
- TEST_NUMERIC_CONVERSION(int, double, SIGN_PRESERVING_NARROW);
-
- TEST_NUMERIC_CONVERSION(unsigned int, int, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL);
- TEST_NUMERIC_CONVERSION(unsigned int, int8_t, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL);
-
- TEST_NUMERIC_CONVERSION(unsigned int, intmax_t, SIGN_TO_UNSIGN_NARROW);
- TEST_NUMERIC_CONVERSION(unsigned int, float, SIGN_TO_UNSIGN_NARROW);
- TEST_NUMERIC_CONVERSION(unsigned int, double, SIGN_TO_UNSIGN_NARROW);
-
- TEST_NUMERIC_CONVERSION(int, unsigned int, UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
- TEST_NUMERIC_CONVERSION(int, uintmax_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
-}
-
-TEST(SafeNumerics, IntMaxOperations) {
- TEST_NUMERIC_CONVERSION(intmax_t, intmax_t, SIGN_PRESERVING_VALUE_PRESERVING);
- TEST_NUMERIC_CONVERSION(uintmax_t, uintmax_t,
- SIGN_PRESERVING_VALUE_PRESERVING);
- TEST_NUMERIC_CONVERSION(intmax_t, int, SIGN_PRESERVING_VALUE_PRESERVING);
- TEST_NUMERIC_CONVERSION(uintmax_t, unsigned int,
- SIGN_PRESERVING_VALUE_PRESERVING);
- TEST_NUMERIC_CONVERSION(intmax_t, unsigned int,
- SIGN_PRESERVING_VALUE_PRESERVING);
- TEST_NUMERIC_CONVERSION(intmax_t, uint8_t, SIGN_PRESERVING_VALUE_PRESERVING);
-
- TEST_NUMERIC_CONVERSION(intmax_t, float, SIGN_PRESERVING_NARROW);
- TEST_NUMERIC_CONVERSION(intmax_t, double, SIGN_PRESERVING_NARROW);
-
- TEST_NUMERIC_CONVERSION(uintmax_t, int, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL);
- TEST_NUMERIC_CONVERSION(uintmax_t, int8_t, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL);
-
- TEST_NUMERIC_CONVERSION(uintmax_t, float, SIGN_TO_UNSIGN_NARROW);
- TEST_NUMERIC_CONVERSION(uintmax_t, double, SIGN_TO_UNSIGN_NARROW);
-
- TEST_NUMERIC_CONVERSION(intmax_t, uintmax_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
-}
-
-TEST(SafeNumerics, FloatOperations) {
- TEST_NUMERIC_CONVERSION(float, intmax_t, SIGN_PRESERVING_VALUE_PRESERVING);
- TEST_NUMERIC_CONVERSION(float, uintmax_t,
- SIGN_PRESERVING_VALUE_PRESERVING);
- TEST_NUMERIC_CONVERSION(float, int, SIGN_PRESERVING_VALUE_PRESERVING);
- TEST_NUMERIC_CONVERSION(float, unsigned int,
- SIGN_PRESERVING_VALUE_PRESERVING);
-
- TEST_NUMERIC_CONVERSION(float, double, SIGN_PRESERVING_NARROW);
-}
-
-TEST(SafeNumerics, DoubleOperations) {
- TEST_NUMERIC_CONVERSION(double, intmax_t, SIGN_PRESERVING_VALUE_PRESERVING);
- TEST_NUMERIC_CONVERSION(double, uintmax_t,
- SIGN_PRESERVING_VALUE_PRESERVING);
- TEST_NUMERIC_CONVERSION(double, int, SIGN_PRESERVING_VALUE_PRESERVING);
- TEST_NUMERIC_CONVERSION(double, unsigned int,
- SIGN_PRESERVING_VALUE_PRESERVING);
-}
-
-TEST(SafeNumerics, SizeTOperations) {
- TEST_NUMERIC_CONVERSION(size_t, int, SIGN_TO_UNSIGN_WIDEN_OR_EQUAL);
- TEST_NUMERIC_CONVERSION(int, size_t, UNSIGN_TO_SIGN_NARROW_OR_EQUAL);
-}
-
-// A one-off test to ensure StrictNumeric won't resolve to an incorrect type.
-// If this fails we'll just get a compiler error on an ambiguous overload.
-int TestOverload(int) { // Overload fails.
- return 0;
-}
-uint8_t TestOverload(uint8_t) { // Overload fails.
- return 0;
-}
-size_t TestOverload(size_t) { // Overload succeeds.
- return 0;
-}
-
-static_assert(
- std::is_same<decltype(TestOverload(StrictNumeric<int>())), int>::value,
- "");
-static_assert(std::is_same<decltype(TestOverload(StrictNumeric<size_t>())),
- size_t>::value,
- "");
-
-template <typename T>
-struct CastTest1 {
- static constexpr T NaN() { return -1; }
- static constexpr T max() { return numeric_limits<T>::max() - 1; }
- static constexpr T Overflow() { return max(); }
- static constexpr T lowest() { return numeric_limits<T>::lowest() + 1; }
- static constexpr T Underflow() { return lowest(); }
-};
-
-template <typename T>
-struct CastTest2 {
- static constexpr T NaN() { return 11; }
- static constexpr T max() { return 10; }
- static constexpr T Overflow() { return max(); }
- static constexpr T lowest() { return 1; }
- static constexpr T Underflow() { return lowest(); }
-};
-
-TEST(SafeNumerics, CastTests) {
-// MSVC catches and warns that we're forcing saturation in these tests.
-// Since that's intentional, we need to shut this warning off.
-#if defined(COMPILER_MSVC)
-#pragma warning(disable : 4756)
-#endif
-
- int small_positive = 1;
- int small_negative = -1;
- double double_small = 1.0;
- double double_large = numeric_limits<double>::max();
- double double_infinity = numeric_limits<float>::infinity();
- double double_large_int = numeric_limits<int>::max();
- double double_small_int = numeric_limits<int>::lowest();
-
- // Just test that the casts compile, since the other tests cover logic.
- EXPECT_EQ(0, checked_cast<int>(static_cast<size_t>(0)));
- EXPECT_EQ(0, strict_cast<int>(static_cast<char>(0)));
- EXPECT_EQ(0, strict_cast<int>(static_cast<unsigned char>(0)));
- EXPECT_EQ(0U, strict_cast<unsigned>(static_cast<unsigned char>(0)));
- EXPECT_EQ(1ULL, static_cast<uint64_t>(StrictNumeric<size_t>(1U)));
- EXPECT_EQ(1ULL, static_cast<uint64_t>(SizeT(1U)));
- EXPECT_EQ(1U, static_cast<size_t>(StrictNumeric<unsigned>(1U)));
-
- EXPECT_TRUE(CheckedNumeric<uint64_t>(StrictNumeric<unsigned>(1U)).IsValid());
- EXPECT_TRUE(CheckedNumeric<int>(StrictNumeric<unsigned>(1U)).IsValid());
- EXPECT_FALSE(CheckedNumeric<unsigned>(StrictNumeric<int>(-1)).IsValid());
-
- EXPECT_TRUE(IsValueNegative(-1));
- EXPECT_TRUE(IsValueNegative(numeric_limits<int>::lowest()));
- EXPECT_FALSE(IsValueNegative(numeric_limits<unsigned>::lowest()));
- EXPECT_TRUE(IsValueNegative(numeric_limits<double>::lowest()));
- EXPECT_FALSE(IsValueNegative(0));
- EXPECT_FALSE(IsValueNegative(1));
- EXPECT_FALSE(IsValueNegative(0u));
- EXPECT_FALSE(IsValueNegative(1u));
- EXPECT_FALSE(IsValueNegative(numeric_limits<int>::max()));
- EXPECT_FALSE(IsValueNegative(numeric_limits<unsigned>::max()));
- EXPECT_FALSE(IsValueNegative(numeric_limits<double>::max()));
-
- // These casts and coercions will fail to compile:
- // EXPECT_EQ(0, strict_cast<int>(static_cast<size_t>(0)));
- // EXPECT_EQ(0, strict_cast<size_t>(static_cast<int>(0)));
- // EXPECT_EQ(1ULL, StrictNumeric<size_t>(1));
- // EXPECT_EQ(1, StrictNumeric<size_t>(1U));
-
- // Test various saturation corner cases.
- EXPECT_EQ(saturated_cast<int>(small_negative),
- static_cast<int>(small_negative));
- EXPECT_EQ(saturated_cast<int>(small_positive),
- static_cast<int>(small_positive));
- EXPECT_EQ(saturated_cast<unsigned>(small_negative),
- static_cast<unsigned>(0));
- EXPECT_EQ(saturated_cast<int>(double_small),
- static_cast<int>(double_small));
- EXPECT_EQ(saturated_cast<int>(double_large), numeric_limits<int>::max());
- EXPECT_EQ(saturated_cast<float>(double_large), double_infinity);
- EXPECT_EQ(saturated_cast<float>(-double_large), -double_infinity);
- EXPECT_EQ(numeric_limits<int>::lowest(),
- saturated_cast<int>(double_small_int));
- EXPECT_EQ(numeric_limits<int>::max(), saturated_cast<int>(double_large_int));
-
- // Test the saturated cast overrides.
- using FloatLimits = numeric_limits<float>;
- using IntLimits = numeric_limits<int>;
- EXPECT_EQ(-1, (saturated_cast<int, CastTest1>(FloatLimits::quiet_NaN())));
- EXPECT_EQ(CastTest1<int>::max(),
- (saturated_cast<int, CastTest1>(FloatLimits::infinity())));
- EXPECT_EQ(CastTest1<int>::max(),
- (saturated_cast<int, CastTest1>(FloatLimits::max())));
- EXPECT_EQ(CastTest1<int>::max(),
- (saturated_cast<int, CastTest1>(float(IntLimits::max()))));
- EXPECT_EQ(CastTest1<int>::lowest(),
- (saturated_cast<int, CastTest1>(-FloatLimits::infinity())));
- EXPECT_EQ(CastTest1<int>::lowest(),
- (saturated_cast<int, CastTest1>(FloatLimits::lowest())));
- EXPECT_EQ(0, (saturated_cast<int, CastTest1>(0.0)));
- EXPECT_EQ(1, (saturated_cast<int, CastTest1>(1.0)));
- EXPECT_EQ(-1, (saturated_cast<int, CastTest1>(-1.0)));
- EXPECT_EQ(0, (saturated_cast<int, CastTest1>(0)));
- EXPECT_EQ(1, (saturated_cast<int, CastTest1>(1)));
- EXPECT_EQ(-1, (saturated_cast<int, CastTest1>(-1)));
- EXPECT_EQ(CastTest1<int>::lowest(),
- (saturated_cast<int, CastTest1>(float(IntLimits::lowest()))));
- EXPECT_EQ(11, (saturated_cast<int, CastTest2>(FloatLimits::quiet_NaN())));
- EXPECT_EQ(10, (saturated_cast<int, CastTest2>(FloatLimits::infinity())));
- EXPECT_EQ(10, (saturated_cast<int, CastTest2>(FloatLimits::max())));
- EXPECT_EQ(1, (saturated_cast<int, CastTest2>(-FloatLimits::infinity())));
- EXPECT_EQ(1, (saturated_cast<int, CastTest2>(FloatLimits::lowest())));
- EXPECT_EQ(1, (saturated_cast<int, CastTest2>(0U)));
-
- float not_a_number = std::numeric_limits<float>::infinity() -
- std::numeric_limits<float>::infinity();
- EXPECT_TRUE(std::isnan(not_a_number));
- EXPECT_EQ(0, saturated_cast<int>(not_a_number));
-
- // Test the CheckedNumeric value extractions functions.
- auto int8_min = MakeCheckedNum(numeric_limits<int8_t>::lowest());
- auto int8_max = MakeCheckedNum(numeric_limits<int8_t>::max());
- auto double_max = MakeCheckedNum(numeric_limits<double>::max());
- static_assert(
- std::is_same<int16_t,
- decltype(int8_min.ValueOrDie<int16_t>())::type>::value,
- "ValueOrDie returning incorrect type.");
- static_assert(
- std::is_same<int16_t,
- decltype(int8_min.ValueOrDefault<int16_t>(0))::type>::value,
- "ValueOrDefault returning incorrect type.");
- EXPECT_FALSE(IsValidForType<uint8_t>(int8_min));
- EXPECT_TRUE(IsValidForType<uint8_t>(int8_max));
- EXPECT_EQ(static_cast<int>(numeric_limits<int8_t>::lowest()),
- ValueOrDieForType<int>(int8_min));
- EXPECT_TRUE(IsValidForType<uint32_t>(int8_max));
- EXPECT_EQ(static_cast<int>(numeric_limits<int8_t>::max()),
- ValueOrDieForType<int>(int8_max));
- EXPECT_EQ(0, ValueOrDefaultForType<int>(double_max, 0));
- uint8_t uint8_dest = 0;
- int16_t int16_dest = 0;
- double double_dest = 0;
- EXPECT_TRUE(int8_max.AssignIfValid(&uint8_dest));
- EXPECT_EQ(static_cast<uint8_t>(numeric_limits<int8_t>::max()), uint8_dest);
- EXPECT_FALSE(int8_min.AssignIfValid(&uint8_dest));
- EXPECT_TRUE(int8_max.AssignIfValid(&int16_dest));
- EXPECT_EQ(static_cast<int16_t>(numeric_limits<int8_t>::max()), int16_dest);
- EXPECT_TRUE(int8_min.AssignIfValid(&int16_dest));
- EXPECT_EQ(static_cast<int16_t>(numeric_limits<int8_t>::lowest()), int16_dest);
- EXPECT_FALSE(double_max.AssignIfValid(&uint8_dest));
- EXPECT_FALSE(double_max.AssignIfValid(&int16_dest));
- EXPECT_TRUE(double_max.AssignIfValid(&double_dest));
- EXPECT_EQ(numeric_limits<double>::max(), double_dest);
- EXPECT_EQ(1, checked_cast<int>(StrictNumeric<int>(1)));
- EXPECT_EQ(1, saturated_cast<int>(StrictNumeric<int>(1)));
- EXPECT_EQ(1, strict_cast<int>(StrictNumeric<int>(1)));
-}
-
-TEST(SafeNumerics, IsValueInRangeForNumericType) {
- EXPECT_TRUE(IsValueInRangeForNumericType<uint32_t>(0));
- EXPECT_TRUE(IsValueInRangeForNumericType<uint32_t>(1));
- EXPECT_TRUE(IsValueInRangeForNumericType<uint32_t>(2));
- EXPECT_FALSE(IsValueInRangeForNumericType<uint32_t>(-1));
- EXPECT_TRUE(IsValueInRangeForNumericType<uint32_t>(0xffffffffu));
- EXPECT_TRUE(IsValueInRangeForNumericType<uint32_t>(UINT64_C(0xffffffff)));
- EXPECT_FALSE(IsValueInRangeForNumericType<uint32_t>(UINT64_C(0x100000000)));
- EXPECT_FALSE(IsValueInRangeForNumericType<uint32_t>(UINT64_C(0x100000001)));
- EXPECT_FALSE(IsValueInRangeForNumericType<uint32_t>(
- std::numeric_limits<int32_t>::lowest()));
- EXPECT_FALSE(IsValueInRangeForNumericType<uint32_t>(
- std::numeric_limits<int64_t>::lowest()));
-
- EXPECT_TRUE(IsValueInRangeForNumericType<int32_t>(0));
- EXPECT_TRUE(IsValueInRangeForNumericType<int32_t>(1));
- EXPECT_TRUE(IsValueInRangeForNumericType<int32_t>(2));
- EXPECT_TRUE(IsValueInRangeForNumericType<int32_t>(-1));
- EXPECT_TRUE(IsValueInRangeForNumericType<int32_t>(0x7fffffff));
- EXPECT_TRUE(IsValueInRangeForNumericType<int32_t>(0x7fffffffu));
- EXPECT_FALSE(IsValueInRangeForNumericType<int32_t>(0x80000000u));
- EXPECT_FALSE(IsValueInRangeForNumericType<int32_t>(0xffffffffu));
- EXPECT_FALSE(IsValueInRangeForNumericType<int32_t>(INT64_C(0x80000000)));
- EXPECT_FALSE(IsValueInRangeForNumericType<int32_t>(INT64_C(0xffffffff)));
- EXPECT_FALSE(IsValueInRangeForNumericType<int32_t>(INT64_C(0x100000000)));
- EXPECT_TRUE(IsValueInRangeForNumericType<int32_t>(
- std::numeric_limits<int32_t>::lowest()));
- EXPECT_TRUE(IsValueInRangeForNumericType<int32_t>(
- static_cast<int64_t>(std::numeric_limits<int32_t>::lowest())));
- EXPECT_FALSE(IsValueInRangeForNumericType<int32_t>(
- static_cast<int64_t>(std::numeric_limits<int32_t>::lowest()) - 1));
- EXPECT_FALSE(IsValueInRangeForNumericType<int32_t>(
- std::numeric_limits<int64_t>::lowest()));
-
- EXPECT_TRUE(IsValueInRangeForNumericType<uint64_t>(0));
- EXPECT_TRUE(IsValueInRangeForNumericType<uint64_t>(1));
- EXPECT_TRUE(IsValueInRangeForNumericType<uint64_t>(2));
- EXPECT_FALSE(IsValueInRangeForNumericType<uint64_t>(-1));
- EXPECT_TRUE(IsValueInRangeForNumericType<uint64_t>(0xffffffffu));
- EXPECT_TRUE(IsValueInRangeForNumericType<uint64_t>(UINT64_C(0xffffffff)));
- EXPECT_TRUE(IsValueInRangeForNumericType<uint64_t>(UINT64_C(0x100000000)));
- EXPECT_TRUE(IsValueInRangeForNumericType<uint64_t>(UINT64_C(0x100000001)));
- EXPECT_FALSE(IsValueInRangeForNumericType<uint64_t>(
- std::numeric_limits<int32_t>::lowest()));
- EXPECT_FALSE(IsValueInRangeForNumericType<uint64_t>(INT64_C(-1)));
- EXPECT_FALSE(IsValueInRangeForNumericType<uint64_t>(
- std::numeric_limits<int64_t>::lowest()));
-
- EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(0));
- EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(1));
- EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(2));
- EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(-1));
- EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(0x7fffffff));
- EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(0x7fffffffu));
- EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(0x80000000u));
- EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(0xffffffffu));
- EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(INT64_C(0x80000000)));
- EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(INT64_C(0xffffffff)));
- EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(INT64_C(0x100000000)));
- EXPECT_TRUE(
- IsValueInRangeForNumericType<int64_t>(INT64_C(0x7fffffffffffffff)));
- EXPECT_TRUE(
- IsValueInRangeForNumericType<int64_t>(UINT64_C(0x7fffffffffffffff)));
- EXPECT_FALSE(
- IsValueInRangeForNumericType<int64_t>(UINT64_C(0x8000000000000000)));
- EXPECT_FALSE(
- IsValueInRangeForNumericType<int64_t>(UINT64_C(0xffffffffffffffff)));
- EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(
- std::numeric_limits<int32_t>::lowest()));
- EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(
- static_cast<int64_t>(std::numeric_limits<int32_t>::lowest())));
- EXPECT_TRUE(IsValueInRangeForNumericType<int64_t>(
- std::numeric_limits<int64_t>::lowest()));
-}
-
-TEST(SafeNumerics, CompoundNumericOperations) {
- CheckedNumeric<int> a = 1;
- CheckedNumeric<int> b = 2;
- CheckedNumeric<int> c = 3;
- CheckedNumeric<int> d = 4;
- a += b;
- EXPECT_EQ(3, a.ValueOrDie());
- a -= c;
- EXPECT_EQ(0, a.ValueOrDie());
- d /= b;
- EXPECT_EQ(2, d.ValueOrDie());
- d *= d;
- EXPECT_EQ(4, d.ValueOrDie());
-
- CheckedNumeric<int> too_large = std::numeric_limits<int>::max();
- EXPECT_TRUE(too_large.IsValid());
- too_large += d;
- EXPECT_FALSE(too_large.IsValid());
- too_large -= d;
- EXPECT_FALSE(too_large.IsValid());
- too_large /= d;
- EXPECT_FALSE(too_large.IsValid());
-}
-
-TEST(SafeNumerics, VariadicNumericOperations) {
- auto a = CheckAdd(1, 2UL, MakeCheckedNum(3LL), 4).ValueOrDie();
- EXPECT_EQ(static_cast<decltype(a)::type>(10), a);
- auto b = CheckSub(MakeCheckedNum(20.0), 2UL, 4).ValueOrDie();
- EXPECT_EQ(static_cast<decltype(b)::type>(14.0), b);
- auto c = CheckMul(20.0, MakeCheckedNum(1), 5, 3UL).ValueOrDie();
- EXPECT_EQ(static_cast<decltype(c)::type>(300.0), c);
- auto d = CheckDiv(20.0, 2.0, MakeCheckedNum(5LL), -4).ValueOrDie();
- EXPECT_EQ(static_cast<decltype(d)::type>(-.5), d);
- auto e = CheckMod(MakeCheckedNum(20), 3).ValueOrDie();
- EXPECT_EQ(static_cast<decltype(e)::type>(2), e);
- auto f = CheckLsh(1, MakeCheckedNum(2)).ValueOrDie();
- EXPECT_EQ(static_cast<decltype(f)::type>(4), f);
- auto g = CheckRsh(4, MakeCheckedNum(2)).ValueOrDie();
- EXPECT_EQ(static_cast<decltype(g)::type>(1), g);
- auto h = CheckRsh(CheckAdd(1, 1, 1, 1), CheckSub(4, 2)).ValueOrDie();
- EXPECT_EQ(static_cast<decltype(h)::type>(1), h);
-}
diff --git a/base/numerics/saturated_arithmetic.h b/base/numerics/saturated_arithmetic.h
deleted file mode 100644
index 74fbba808d..0000000000
--- a/base/numerics/saturated_arithmetic.h
+++ /dev/null
@@ -1,107 +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_NUMERICS_SATURATED_ARITHMETIC_H_
-#define BASE_NUMERICS_SATURATED_ARITHMETIC_H_
-
-#include <stdint.h>
-
-#include <limits>
-
-#include "base/compiler_specific.h"
-
-#if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS) && \
- defined(COMPILER_GCC) && !defined(OS_NACL) && __OPTIMIZE__
-
-// If we're building ARM 32-bit on GCC we replace the C++ versions with some
-// native ARM assembly for speed.
-#include "base/numerics/saturated_arithmetic_arm.h"
-
-#else
-
-namespace base {
-
-ALWAYS_INLINE int32_t SaturatedAddition(int32_t a, int32_t b) {
- uint32_t ua = a;
- uint32_t ub = b;
- uint32_t result = ua + ub;
-
- // Can only overflow if the signed bit of the two values match. If the
- // signed bit of the result and one of the values differ it overflowed.
- // The branch compiles to a CMOVNS instruction on x86.
- if (~(ua ^ ub) & (result ^ ua) & (1 << 31))
- return std::numeric_limits<int>::max() + (ua >> 31);
-
- return result;
-}
-
-ALWAYS_INLINE int32_t SaturatedSubtraction(int32_t a, int32_t b) {
- uint32_t ua = a;
- uint32_t ub = b;
- uint32_t result = ua - ub;
-
- // Can only overflow if the signed bit of the two input values differ. If
- // the signed bit of the result and the first value differ it overflowed.
- // The branch compiles to a CMOVNS instruction on x86.
- if ((ua ^ ub) & (result ^ ua) & (1 << 31))
- return std::numeric_limits<int>::max() + (ua >> 31);
-
- return result;
-}
-
-ALWAYS_INLINE int32_t SaturatedNegative(int32_t a) {
- if (UNLIKELY(a == std::numeric_limits<int>::min()))
- return std::numeric_limits<int>::max();
- return -a;
-}
-
-ALWAYS_INLINE int32_t SaturatedAbsolute(int32_t a) {
- if (a >= 0)
- return a;
- return SaturatedNegative(a);
-}
-
-ALWAYS_INLINE int GetMaxSaturatedSetResultForTesting(int fractional_shift) {
- // For C version the set function maxes out to max int, this differs from
- // the ARM asm version, see saturated_arithmetic_arm.h for the equivalent asm
- // version.
- return std::numeric_limits<int>::max();
-}
-
-ALWAYS_INLINE int GetMinSaturatedSetResultForTesting(int fractional_shift) {
- return std::numeric_limits<int>::min();
-}
-
-template <int fractional_shift>
-ALWAYS_INLINE int SaturatedSet(int value) {
- const int kIntMaxForLayoutUnit =
- std::numeric_limits<int>::max() >> fractional_shift;
-
- const int kIntMinForLayoutUnit =
- std::numeric_limits<int>::min() >> fractional_shift;
-
- if (value > kIntMaxForLayoutUnit)
- return std::numeric_limits<int>::max();
-
- if (value < kIntMinForLayoutUnit)
- return std::numeric_limits<int>::min();
-
- return value << fractional_shift;
-}
-
-template <int fractional_shift>
-ALWAYS_INLINE int SaturatedSet(unsigned value) {
- const unsigned kIntMaxForLayoutUnit =
- std::numeric_limits<int>::max() >> fractional_shift;
-
- if (value >= kIntMaxForLayoutUnit)
- return std::numeric_limits<int>::max();
-
- return value << fractional_shift;
-}
-
-} // namespace base
-
-#endif // CPU(ARM) && COMPILER(GCC)
-#endif // BASE_NUMERICS_SATURATED_ARITHMETIC_H_
diff --git a/base/numerics/saturated_arithmetic_arm.h b/base/numerics/saturated_arithmetic_arm.h
deleted file mode 100644
index 732f5f2c1f..0000000000
--- a/base/numerics/saturated_arithmetic_arm.h
+++ /dev/null
@@ -1,108 +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_NUMERICS_SATURATED_ARITHMETIC_ARM_H_
-#define BASE_NUMERICS_SATURATED_ARITHMETIC_ARM_H_
-
-#include <limits>
-
-namespace base {
-
-inline int32_t SaturatedAddition(int32_t a, int32_t b) {
- int32_t result;
-
- asm("qadd %[output],%[first],%[second]"
- : [output] "=r"(result)
- : [first] "r"(a), [second] "r"(b));
-
- return result;
-}
-
-inline int32_t SaturatedSubtraction(int32_t a, int32_t b) {
- int32_t result;
-
- asm("qsub %[output],%[first],%[second]"
- : [output] "=r"(result)
- : [first] "r"(a), [second] "r"(b));
-
- return result;
-}
-
-inline int32_t SaturatedNegative(int32_t a) {
- return SaturatedSubtraction(0, a);
-}
-
-inline int32_t SaturatedAbsolute(int32_t a) {
- if (a >= 0)
- return a;
- return SaturatedNegative(a);
-}
-
-inline int GetMaxSaturatedSetResultForTesting(int fractional_shift) {
- // For ARM Asm version the set function maxes out to the biggest
- // possible integer part with the fractional part zero'd out.
- // e.g. 0x7fffffc0.
- return std::numeric_limits<int>::max() & ~((1 << fractional_shift) - 1);
-}
-
-inline int GetMinSaturatedSetResultForTesting(int fractional_shift) {
- return std::numeric_limits<int>::min();
-}
-
-template <int fractional_shift>
-inline int SaturatedSet(int value) {
- // Figure out how many bits are left for storing the integer part of
- // the fixed point number, and saturate our input to that
- enum { Saturate = 32 - fractional_shift };
-
- int result;
-
- // The following ARM code will Saturate the passed value to the number of
- // bits used for the whole part of the fixed point representation, then
- // shift it up into place. This will result in the low <FractionShift> bits
- // all being 0's. When the value saturates this gives a different result
- // to from the C++ case; in the C++ code a saturated value has all the low
- // bits set to 1 (for a +ve number at least). This cannot be done rapidly
- // in ARM ... we live with the difference, for the sake of speed.
-
- asm("ssat %[output],%[saturate],%[value]\n\t"
- "lsl %[output],%[shift]"
- : [output] "=r"(result)
- : [value] "r"(value), [saturate] "n"(Saturate),
- [shift] "n"(fractional_shift));
-
- return result;
-}
-
-template <int fractional_shift>
-inline int SaturatedSet(unsigned value) {
- // Here we are being passed an unsigned value to saturate,
- // even though the result is returned as a signed integer. The ARM
- // instruction for unsigned saturation therefore needs to be given one
- // less bit (i.e. the sign bit) for the saturation to work correctly; hence
- // the '31' below.
- enum { Saturate = 31 - fractional_shift };
-
- // The following ARM code will Saturate the passed value to the number of
- // bits used for the whole part of the fixed point representation, then
- // shift it up into place. This will result in the low <FractionShift> bits
- // all being 0's. When the value saturates this gives a different result
- // to from the C++ case; in the C++ code a saturated value has all the low
- // bits set to 1. This cannot be done rapidly in ARM, so we live with the
- // difference, for the sake of speed.
-
- int result;
-
- asm("usat %[output],%[saturate],%[value]\n\t"
- "lsl %[output],%[shift]"
- : [output] "=r"(result)
- : [value] "r"(value), [saturate] "n"(Saturate),
- [shift] "n"(fractional_shift));
-
- return result;
-}
-
-} // namespace base
-
-#endif // BASE_NUMERICS_SATURATED_ARITHMETIC_ARM_H_
diff --git a/base/observer_list.h b/base/observer_list.h
index 0572ba6500..e900e435d1 100644
--- a/base/observer_list.h
+++ b/base/observer_list.h
@@ -8,7 +8,9 @@
#include <stddef.h>
#include <algorithm>
+#include <iterator>
#include <limits>
+#include <utility>
#include <vector>
#include "base/gtest_prod_util.h"
@@ -21,10 +23,20 @@
//
// OVERVIEW:
//
-// A container for a list of observers. Unlike a normal STL vector or list,
-// this container can be modified during iteration without invalidating the
-// iterator. So, it safely handles the case of an observer removing itself
-// or other observers from the list while observers are being notified.
+// A list of observers. Unlike a standard vector or list, this container can
+// be modified during iteration without invalidating the iterator. So, it
+// safely handles the case of an observer removing itself or other observers
+// from the list while observers are being notified.
+//
+//
+// WARNING:
+//
+// ObserverList is not thread-compatible. Iterating on the same ObserverList
+// simultaneously in different threads is not safe, even when the ObserverList
+// itself is not modified.
+//
+// For a thread-safe observer list, see ObserverListThreadSafe.
+//
//
// TYPICAL USAGE:
//
@@ -39,26 +51,25 @@
// };
//
// void AddObserver(Observer* obs) {
-// observer_list_.AddObserver(obs);
+// observers_.AddObserver(obs);
// }
//
-// void RemoveObserver(Observer* obs) {
-// observer_list_.RemoveObserver(obs);
+// void RemoveObserver(const Observer* obs) {
+// observers_.RemoveObserver(obs);
// }
//
// void NotifyFoo() {
-// for (auto& observer : observer_list_)
-// observer.OnFoo(this);
+// for (Observer& obs : observers_)
+// obs.OnFoo(this);
// }
//
// void NotifyBar(int x, int y) {
-// for (FooList::iterator i = observer_list.begin(),
-// e = observer_list.end(); i != e; ++i)
-// i->OnBar(this, x, y);
+// for (Observer& obs : observers_)
+// obs.OnBar(this, x, y);
// }
//
// private:
-// base::ObserverList<Observer> observer_list_;
+// base::ObserverList<Observer> observers_;
// };
//
//
@@ -66,49 +77,125 @@
namespace base {
-template <typename ObserverType>
-class ObserverListThreadSafe;
+// Enumeration of which observers are notified by ObserverList.
+enum class ObserverListPolicy {
+ // Specifies that any observers added during notification are notified.
+ // This is the default policy if no policy is provided to the constructor.
+ ALL,
-template <class ObserverType>
-class ObserverListBase
- : public SupportsWeakPtr<ObserverListBase<ObserverType>> {
- public:
- // Enumeration of which observers are notified.
- enum NotificationType {
- // Specifies that any observers added during notification are notified.
- // This is the default type if non type is provided to the constructor.
- NOTIFY_ALL,
-
- // Specifies that observers added while sending out notification are not
- // notified.
- NOTIFY_EXISTING_ONLY
- };
+ // Specifies that observers added while sending out notification are not
+ // notified.
+ EXISTING_ONLY,
+};
+// When check_empty is true, assert that the list is empty on destruction.
+// When allow_reentrancy is false, iterating throught the list while already in
+// the iteration loop will result in DCHECK failure.
+// TODO(oshima): Change the default to non reentrant. https://crbug.com/812109
+template <class ObserverType,
+ bool check_empty = false,
+ bool allow_reentrancy = true>
+class ObserverList
+ : public SupportsWeakPtr<
+ ObserverList<ObserverType, check_empty, allow_reentrancy>> {
+ public:
// An iterator class that can be used to access the list of observers.
- template <class ContainerType>
class Iter {
public:
- Iter();
- explicit Iter(ContainerType* list);
- ~Iter();
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = ObserverType;
+ using difference_type = ptrdiff_t;
+ using pointer = ObserverType*;
+ using reference = ObserverType&;
+
+ Iter() : index_(0), max_index_(0) {}
+
+ explicit Iter(const ObserverList* list)
+ : list_(const_cast<ObserverList*>(list)->AsWeakPtr()),
+ index_(0),
+ max_index_(list->policy_ == ObserverListPolicy::ALL
+ ? std::numeric_limits<size_t>::max()
+ : list->observers_.size()) {
+ DCHECK(list_);
+ DCHECK(allow_reentrancy || !list_->live_iterator_count_);
+ EnsureValidIndex();
+ ++list_->live_iterator_count_;
+ }
+
+ ~Iter() {
+ if (!list_)
+ return;
- // A workaround for C2244. MSVC requires fully qualified type name for
- // return type on a function definition to match a function declaration.
- using ThisType =
- typename ObserverListBase<ObserverType>::template Iter<ContainerType>;
+ DCHECK_GT(list_->live_iterator_count_, 0);
+ if (--list_->live_iterator_count_ == 0)
+ list_->Compact();
+ }
+
+ Iter(const Iter& other)
+ : list_(other.list_),
+ index_(other.index_),
+ max_index_(other.max_index_) {
+ if (list_)
+ ++list_->live_iterator_count_;
+ }
+
+ Iter& operator=(Iter other) {
+ using std::swap;
+ swap(list_, other.list_);
+ swap(index_, other.index_);
+ swap(max_index_, other.max_index_);
+ return *this;
+ }
+
+ bool operator==(const Iter& other) const {
+ return (is_end() && other.is_end()) ||
+ (list_.get() == other.list_.get() && index_ == other.index_);
+ }
- bool operator==(const Iter& other) const;
- bool operator!=(const Iter& other) const;
- ThisType& operator++();
- ObserverType* operator->() const;
- ObserverType& operator*() const;
+ bool operator!=(const Iter& other) const { return !(*this == other); }
+
+ Iter& operator++() {
+ if (list_) {
+ ++index_;
+ EnsureValidIndex();
+ }
+ return *this;
+ }
+
+ Iter operator++(int) {
+ Iter it(*this);
+ ++(*this);
+ return it;
+ }
+
+ ObserverType* operator->() const {
+ ObserverType* const current = GetCurrent();
+ DCHECK(current);
+ return current;
+ }
+
+ ObserverType& operator*() const {
+ ObserverType* const current = GetCurrent();
+ DCHECK(current);
+ return *current;
+ }
private:
FRIEND_TEST_ALL_PREFIXES(ObserverListTest, BasicStdIterator);
FRIEND_TEST_ALL_PREFIXES(ObserverListTest, StdIteratorRemoveFront);
- ObserverType* GetCurrent() const;
- void EnsureValidIndex();
+ ObserverType* GetCurrent() const {
+ DCHECK(list_);
+ DCHECK_LT(index_, clamped_max_index());
+ return list_->observers_[index_];
+ }
+
+ void EnsureValidIndex() {
+ DCHECK(list_);
+ const size_t max_index = clamped_max_index();
+ while (index_ < max_index && !list_->observers_[index_])
+ ++index_;
+ }
size_t clamped_max_index() const {
return std::min(max_index_, list_->observers_.size());
@@ -116,7 +203,8 @@ class ObserverListBase
bool is_end() const { return !list_ || index_ == clamped_max_index(); }
- WeakPtr<ObserverListBase<ObserverType>> list_;
+ WeakPtr<ObserverList> list_;
+
// When initially constructed and each time the iterator is incremented,
// |index_| is guaranteed to point to a non-null index if the iterator
// has not reached the end of the ObserverList.
@@ -124,226 +212,96 @@ class ObserverListBase
size_t max_index_;
};
- using Iterator = Iter<ObserverListBase<ObserverType>>;
+ using iterator = Iter;
+ using const_iterator = Iter;
- using iterator = Iter<ObserverListBase<ObserverType>>;
- iterator begin() {
+ const_iterator begin() const {
// An optimization: do not involve weak pointers for empty list.
- // Note: can't use ?: operator here due to some MSVC bug (unit tests fail)
- if (observers_.empty())
- return iterator();
- return iterator(this);
+ return observers_.empty() ? const_iterator() : const_iterator(this);
}
- iterator end() { return iterator(); }
- using const_iterator = Iter<const ObserverListBase<ObserverType>>;
- const_iterator begin() const {
- if (observers_.empty())
- return const_iterator();
- return const_iterator(this);
- }
const_iterator end() const { return const_iterator(); }
- ObserverListBase() : notify_depth_(0), type_(NOTIFY_ALL) {}
- explicit ObserverListBase(NotificationType type)
- : notify_depth_(0), type_(type) {}
-
- // Add an observer to the list. An observer should not be added to
- // the same list more than once.
- void AddObserver(ObserverType* obs);
-
- // Remove an observer from the list if it is in the list.
- void RemoveObserver(ObserverType* obs);
+ ObserverList() = default;
+ explicit ObserverList(ObserverListPolicy policy) : policy_(policy) {}
- // Determine whether a particular observer is in the list.
- bool HasObserver(const ObserverType* observer) const;
-
- void Clear();
-
- protected:
- size_t size() const { return observers_.size(); }
-
- void Compact();
-
- private:
- friend class ObserverListThreadSafe<ObserverType>;
-
- typedef std::vector<ObserverType*> ListType;
-
- ListType observers_;
- int notify_depth_;
- NotificationType type_;
+ ~ObserverList() {
+ if (check_empty) {
+ Compact();
+ DCHECK(observers_.empty());
+ }
+ }
- template <class ContainerType>
- friend class Iter;
+ // Add an observer to this list. An observer should not be added to the same
+ // list more than once.
+ //
+ // Precondition: obs != nullptr
+ // Precondition: !HasObserver(obs)
+ void AddObserver(ObserverType* obs) {
+ DCHECK(obs);
+ if (HasObserver(obs)) {
+ NOTREACHED() << "Observers can only be added once!";
+ return;
+ }
+ observers_.push_back(obs);
+ }
- DISALLOW_COPY_AND_ASSIGN(ObserverListBase);
-};
+ // Removes the given observer from this list. Does nothing if this observer is
+ // not in this list.
+ void RemoveObserver(const ObserverType* obs) {
+ DCHECK(obs);
+ const auto it = std::find(observers_.begin(), observers_.end(), obs);
+ if (it == observers_.end())
+ return;
-template <class ObserverType>
-template <class ContainerType>
-ObserverListBase<ObserverType>::Iter<ContainerType>::Iter()
- : index_(0), max_index_(0) {}
-
-template <class ObserverType>
-template <class ContainerType>
-ObserverListBase<ObserverType>::Iter<ContainerType>::Iter(ContainerType* list)
- : list_(const_cast<ObserverListBase<ObserverType>*>(list)->AsWeakPtr()),
- index_(0),
- max_index_(list->type_ == NOTIFY_ALL ? std::numeric_limits<size_t>::max()
- : list->observers_.size()) {
- EnsureValidIndex();
- DCHECK(list_);
- ++list_->notify_depth_;
-}
-
-template <class ObserverType>
-template <class ContainerType>
-ObserverListBase<ObserverType>::Iter<ContainerType>::~Iter() {
- if (list_ && --list_->notify_depth_ == 0)
- list_->Compact();
-}
-
-template <class ObserverType>
-template <class ContainerType>
-bool ObserverListBase<ObserverType>::Iter<ContainerType>::operator==(
- const Iter& other) const {
- if (is_end() && other.is_end())
- return true;
- return list_.get() == other.list_.get() && index_ == other.index_;
-}
-
-template <class ObserverType>
-template <class ContainerType>
-bool ObserverListBase<ObserverType>::Iter<ContainerType>::operator!=(
- const Iter& other) const {
- return !operator==(other);
-}
-
-template <class ObserverType>
-template <class ContainerType>
-typename ObserverListBase<ObserverType>::template Iter<ContainerType>&
- ObserverListBase<ObserverType>::Iter<ContainerType>::operator++() {
- if (list_) {
- ++index_;
- EnsureValidIndex();
- }
- return *this;
-}
-
-template <class ObserverType>
-template <class ContainerType>
-ObserverType* ObserverListBase<ObserverType>::Iter<ContainerType>::operator->()
- const {
- ObserverType* current = GetCurrent();
- DCHECK(current);
- return current;
-}
-
-template <class ObserverType>
-template <class ContainerType>
-ObserverType& ObserverListBase<ObserverType>::Iter<ContainerType>::operator*()
- const {
- ObserverType* current = GetCurrent();
- DCHECK(current);
- return *current;
-}
-
-template <class ObserverType>
-template <class ContainerType>
-ObserverType* ObserverListBase<ObserverType>::Iter<ContainerType>::GetCurrent()
- const {
- if (!list_)
- return nullptr;
- return index_ < clamped_max_index() ? list_->observers_[index_] : nullptr;
-}
-
-template <class ObserverType>
-template <class ContainerType>
-void ObserverListBase<ObserverType>::Iter<ContainerType>::EnsureValidIndex() {
- if (!list_)
- return;
-
- size_t max_index = clamped_max_index();
- while (index_ < max_index && !list_->observers_[index_])
- ++index_;
-}
-
-template <class ObserverType>
-void ObserverListBase<ObserverType>::AddObserver(ObserverType* obs) {
- DCHECK(obs);
- if (ContainsValue(observers_, obs)) {
- NOTREACHED() << "Observers can only be added once!";
- return;
- }
- observers_.push_back(obs);
-}
-
-template <class ObserverType>
-void ObserverListBase<ObserverType>::RemoveObserver(ObserverType* obs) {
- DCHECK(obs);
- typename ListType::iterator it =
- std::find(observers_.begin(), observers_.end(), obs);
- if (it != observers_.end()) {
- if (notify_depth_) {
+ DCHECK_GE(live_iterator_count_, 0);
+ if (live_iterator_count_) {
*it = nullptr;
} else {
observers_.erase(it);
}
}
-}
-
-template <class ObserverType>
-bool ObserverListBase<ObserverType>::HasObserver(
- const ObserverType* observer) const {
- for (size_t i = 0; i < observers_.size(); ++i) {
- if (observers_[i] == observer)
- return true;
+
+ // Determine whether a particular observer is in the list.
+ bool HasObserver(const ObserverType* obs) const {
+ return ContainsValue(observers_, obs);
}
- return false;
-}
-
-template <class ObserverType>
-void ObserverListBase<ObserverType>::Clear() {
- if (notify_depth_) {
- for (typename ListType::iterator it = observers_.begin();
- it != observers_.end(); ++it) {
- *it = nullptr;
+
+ // Removes all the observers from this list.
+ void Clear() {
+ DCHECK_GE(live_iterator_count_, 0);
+ if (live_iterator_count_) {
+ std::fill(observers_.begin(), observers_.end(), nullptr);
+ } else {
+ observers_.clear();
}
- } else {
- observers_.clear();
}
-}
-template <class ObserverType>
-void ObserverListBase<ObserverType>::Compact() {
- observers_.erase(std::remove(observers_.begin(), observers_.end(), nullptr),
- observers_.end());
-}
+ bool might_have_observers() const { return !observers_.empty(); }
-template <class ObserverType, bool check_empty = false>
-class ObserverList : public ObserverListBase<ObserverType> {
- public:
- typedef typename ObserverListBase<ObserverType>::NotificationType
- NotificationType;
+ private:
+ // Compacts list of observers by removing null pointers.
+ void Compact() {
+ observers_.erase(std::remove(observers_.begin(), observers_.end(), nullptr),
+ observers_.end());
+ }
- ObserverList() {}
- explicit ObserverList(NotificationType type)
- : ObserverListBase<ObserverType>(type) {}
+ std::vector<ObserverType*> observers_;
- ~ObserverList() {
- // When check_empty is true, assert that the list is empty on destruction.
- if (check_empty) {
- ObserverListBase<ObserverType>::Compact();
- DCHECK_EQ(ObserverListBase<ObserverType>::size(), 0U);
- }
- }
+ // Number of active iterators referencing this ObserverList.
+ //
+ // This counter is not synchronized although it is modified by const
+ // iterators.
+ int live_iterator_count_ = 0;
- bool might_have_observers() const {
- return ObserverListBase<ObserverType>::size() != 0;
- }
+ const ObserverListPolicy policy_ = ObserverListPolicy::ALL;
+
+ DISALLOW_COPY_AND_ASSIGN(ObserverList);
};
+template <class ObserverType, bool check_empty = false>
+using ReentrantObserverList = ObserverList<ObserverType, check_empty, true>;
+
} // namespace base
#endif // BASE_OBSERVER_LIST_H_
diff --git a/base/observer_list_threadsafe.cc b/base/observer_list_threadsafe.cc
new file mode 100644
index 0000000000..95c852f6be
--- /dev/null
+++ b/base/observer_list_threadsafe.cc
@@ -0,0 +1,16 @@
+// 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/observer_list_threadsafe.h"
+
+namespace base {
+namespace internal {
+
+LazyInstance<ThreadLocalPointer<
+ const ObserverListThreadSafeBase::NotificationDataBase>>::Leaky
+ ObserverListThreadSafeBase::tls_current_notification_ =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace internal
+} // namespace base
diff --git a/base/observer_list_threadsafe.h b/base/observer_list_threadsafe.h
index afb1010b67..bd349f3200 100644
--- a/base/observer_list_threadsafe.h
+++ b/base/observer_list_threadsafe.h
@@ -5,220 +5,229 @@
#ifndef BASE_OBSERVER_LIST_THREADSAFE_H_
#define BASE_OBSERVER_LIST_THREADSAFE_H_
-#include <algorithm>
-#include <map>
-#include <memory>
-#include <tuple>
+#include <unordered_map>
+#include "base/base_export.h"
#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/memory/ref_counted.h"
#include "base/observer_list.h"
+#include "base/sequenced_task_runner.h"
+#include "base/stl_util.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread_local.h"
+#include "build/build_config.h"
+
+// TODO(fdoray): Removing these includes causes IWYU failures in other headers,
+// remove them in a follow- up CL.
+#include "base/memory/ptr_util.h"
#include "base/single_thread_task_runner.h"
-#include "base/threading/platform_thread.h"
#include "base/threading/thread_task_runner_handle.h"
///////////////////////////////////////////////////////////////////////////////
//
// OVERVIEW:
//
-// A thread-safe container for a list of observers.
-// This is similar to the observer_list (see observer_list.h), but it
-// is more robust for multi-threaded situations.
+// A thread-safe container for a list of observers. This is similar to the
+// observer_list (see observer_list.h), but it is more robust for multi-
+// threaded situations.
//
// The following use cases are supported:
-// * Observers can register for notifications from any thread.
-// Callbacks to the observer will occur on the same thread where
-// the observer initially called AddObserver() from.
-// * Any thread may trigger a notification via Notify().
-// * Observers can remove themselves from the observer list inside
-// of a callback.
-// * If one thread is notifying observers concurrently with an observer
-// removing itself from the observer list, the notifications will
-// be silently dropped.
+// * Observers can register for notifications from any sequence. They are
+// always notified on the sequence from which they were registered.
+// * Any sequence may trigger a notification via Notify().
+// * Observers can remove themselves from the observer list inside of a
+// callback.
+// * If one sequence is notifying observers concurrently with an observer
+// removing itself from the observer list, the notifications will be
+// silently dropped.
//
-// The drawback of the threadsafe observer list is that notifications
-// are not as real-time as the non-threadsafe version of this class.
-// Notifications will always be done via PostTask() to another thread,
-// whereas with the non-thread-safe observer_list, notifications happen
-// synchronously and immediately.
-//
-// IMPLEMENTATION NOTES
-// The ObserverListThreadSafe maintains an ObserverList for each thread
-// which uses the ThreadSafeObserver. When Notifying the observers,
-// we simply call PostTask to each registered thread, and then each thread
-// will notify its regular ObserverList.
+// The drawback of the threadsafe observer list is that notifications are not
+// as real-time as the non-threadsafe version of this class. Notifications
+// will always be done via PostTask() to another sequence, whereas with the
+// non-thread-safe observer_list, notifications happen synchronously.
//
///////////////////////////////////////////////////////////////////////////////
namespace base {
namespace internal {
-template <typename ObserverType, typename Method>
-struct Dispatcher;
+class BASE_EXPORT ObserverListThreadSafeBase
+ : public RefCountedThreadSafe<ObserverListThreadSafeBase> {
+ public:
+ ObserverListThreadSafeBase() = default;
+
+ protected:
+ template <typename ObserverType, typename Method>
+ struct Dispatcher;
+
+ template <typename ObserverType, typename ReceiverType, typename... Params>
+ struct Dispatcher<ObserverType, void (ReceiverType::*)(Params...)> {
+ static void Run(void (ReceiverType::*m)(Params...),
+ Params... params,
+ ObserverType* obj) {
+ (obj->*m)(std::forward<Params>(params)...);
+ }
+ };
-template <typename ObserverType, typename ReceiverType, typename... Params>
-struct Dispatcher<ObserverType, void(ReceiverType::*)(Params...)> {
- static void Run(void(ReceiverType::* m)(Params...),
- Params... params, ObserverType* obj) {
- (obj->*m)(std::forward<Params>(params)...);
- }
+ struct NotificationDataBase {
+ NotificationDataBase(void* observer_list_in, const Location& from_here_in)
+ : observer_list(observer_list_in), from_here(from_here_in) {}
+
+ void* observer_list;
+ Location from_here;
+ };
+
+ virtual ~ObserverListThreadSafeBase() = default;
+
+ static LazyInstance<ThreadLocalPointer<const NotificationDataBase>>::Leaky
+ tls_current_notification_;
+
+ private:
+ friend class RefCountedThreadSafe<ObserverListThreadSafeBase>;
+
+ DISALLOW_COPY_AND_ASSIGN(ObserverListThreadSafeBase);
};
} // namespace internal
template <class ObserverType>
-class ObserverListThreadSafe
- : public RefCountedThreadSafe<ObserverListThreadSafe<ObserverType>> {
+class ObserverListThreadSafe : public internal::ObserverListThreadSafeBase {
public:
- using NotificationType =
- typename ObserverList<ObserverType>::NotificationType;
-
- ObserverListThreadSafe()
- : type_(ObserverListBase<ObserverType>::NOTIFY_ALL) {}
- explicit ObserverListThreadSafe(NotificationType type) : type_(type) {}
-
- // Add an observer to the list. An observer should not be added to
- // the same list more than once.
- void AddObserver(ObserverType* obs) {
- // If there is no ThreadTaskRunnerHandle, it is impossible to notify on it,
- // so do not add the observer.
- if (!ThreadTaskRunnerHandle::IsSet())
+ ObserverListThreadSafe() = default;
+ explicit ObserverListThreadSafe(ObserverListPolicy policy)
+ : policy_(policy) {}
+
+ // Adds |observer| to the list. |observer| must not already be in the list.
+ void AddObserver(ObserverType* observer) {
+ // TODO(fdoray): Change this to a DCHECK once all call sites have a
+ // SequencedTaskRunnerHandle.
+ if (!SequencedTaskRunnerHandle::IsSet())
return;
- ObserverList<ObserverType>* list = nullptr;
- PlatformThreadId thread_id = PlatformThread::CurrentId();
- {
- AutoLock lock(list_lock_);
- if (observer_lists_.find(thread_id) == observer_lists_.end()) {
- observer_lists_[thread_id] =
- base::MakeUnique<ObserverListContext>(type_);
+ AutoLock auto_lock(lock_);
+
+ // Add |observer| to the list of observers.
+ DCHECK(!ContainsKey(observers_, observer));
+ const scoped_refptr<SequencedTaskRunner> task_runner =
+ SequencedTaskRunnerHandle::Get();
+ observers_[observer] = task_runner;
+
+ // If this is called while a notification is being dispatched on this thread
+ // and |policy_| is ALL, |observer| must be notified (if a notification is
+ // being dispatched on another thread in parallel, the notification may or
+ // may not make it to |observer| depending on the outcome of the race to
+ // |lock_|).
+ if (policy_ == ObserverListPolicy::ALL) {
+ const NotificationDataBase* current_notification =
+ tls_current_notification_.Get().Get();
+ if (current_notification && current_notification->observer_list == this) {
+ task_runner->PostTask(
+ current_notification->from_here,
+ BindOnce(
+ &ObserverListThreadSafe<ObserverType>::NotifyWrapper, this,
+ observer,
+ *static_cast<const NotificationData*>(current_notification)));
}
- list = &(observer_lists_[thread_id]->list);
}
- list->AddObserver(obs);
}
// Remove an observer from the list if it is in the list.
- // If there are pending notifications in-transit to the observer, they will
- // be aborted.
- // If the observer to be removed is in the list, RemoveObserver MUST
- // be called from the same thread which called AddObserver.
- void RemoveObserver(ObserverType* obs) {
- PlatformThreadId thread_id = PlatformThread::CurrentId();
- {
- AutoLock lock(list_lock_);
- auto it = observer_lists_.find(thread_id);
- if (it == observer_lists_.end()) {
- // This will happen if we try to remove an observer on a thread
- // we never added an observer for.
- return;
- }
- ObserverList<ObserverType>& list = it->second->list;
-
- list.RemoveObserver(obs);
-
- // If that was the last observer in the list, remove the ObserverList
- // entirely.
- if (list.size() == 0)
- observer_lists_.erase(it);
- }
+ //
+ // If a notification was sent to the observer but hasn't started to run yet,
+ // it will be aborted. If a notification has started to run, removing the
+ // observer won't stop it.
+ void RemoveObserver(ObserverType* observer) {
+ AutoLock auto_lock(lock_);
+ observers_.erase(observer);
}
// Verifies that the list is currently empty (i.e. there are no observers).
void AssertEmpty() const {
- AutoLock lock(list_lock_);
- DCHECK(observer_lists_.empty());
+#if DCHECK_IS_ON()
+ AutoLock auto_lock(lock_);
+ DCHECK(observers_.empty());
+#endif
}
- // Notify methods.
- // Make a thread-safe callback to each Observer in the list.
- // Note, these calls are effectively asynchronous. You cannot assume
- // that at the completion of the Notify call that all Observers have
- // been Notified. The notification may still be pending delivery.
+ // Asynchronously invokes a callback on all observers, on their registration
+ // sequence. You cannot assume that at the completion of the Notify call that
+ // all Observers have been Notified. The notification may still be pending
+ // delivery.
template <typename Method, typename... Params>
- void Notify(const tracked_objects::Location& from_here,
- Method m, Params&&... params) {
+ void Notify(const Location& from_here, Method m, Params&&... params) {
Callback<void(ObserverType*)> method =
- Bind(&internal::Dispatcher<ObserverType, Method>::Run,
- m, std::forward<Params>(params)...);
+ Bind(&Dispatcher<ObserverType, Method>::Run, m,
+ std::forward<Params>(params)...);
- AutoLock lock(list_lock_);
- for (const auto& entry : observer_lists_) {
- ObserverListContext* context = entry.second.get();
- context->task_runner->PostTask(
+ AutoLock lock(lock_);
+ for (const auto& observer : observers_) {
+ observer.second->PostTask(
from_here,
- Bind(&ObserverListThreadSafe<ObserverType>::NotifyWrapper,
- this, context, method));
+ BindOnce(&ObserverListThreadSafe<ObserverType>::NotifyWrapper, this,
+ observer.first, NotificationData(this, from_here, method)));
}
}
private:
- friend class RefCountedThreadSafe<ObserverListThreadSafe<ObserverType>>;
+ friend class RefCountedThreadSafe<ObserverListThreadSafeBase>;
- struct ObserverListContext {
- explicit ObserverListContext(NotificationType type)
- : task_runner(ThreadTaskRunnerHandle::Get()), list(type) {}
+ struct NotificationData : public NotificationDataBase {
+ NotificationData(ObserverListThreadSafe* observer_list_in,
+ const Location& from_here_in,
+ const Callback<void(ObserverType*)>& method_in)
+ : NotificationDataBase(observer_list_in, from_here_in),
+ method(method_in) {}
- scoped_refptr<SingleThreadTaskRunner> task_runner;
- ObserverList<ObserverType> list;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(ObserverListContext);
+ Callback<void(ObserverType*)> method;
};
- ~ObserverListThreadSafe() {
- }
+ ~ObserverListThreadSafe() override = default;
- // Wrapper which is called to fire the notifications for each thread's
- // ObserverList. This function MUST be called on the thread which owns
- // the unsafe ObserverList.
- void NotifyWrapper(ObserverListContext* context,
- const Callback<void(ObserverType*)>& method) {
- // Check that this list still needs notifications.
+ void NotifyWrapper(ObserverType* observer,
+ const NotificationData& notification) {
{
- AutoLock lock(list_lock_);
- auto it = observer_lists_.find(PlatformThread::CurrentId());
-
- // The ObserverList could have been removed already. In fact, it could
- // have been removed and then re-added! If the master list's loop
- // does not match this one, then we do not need to finish this
- // notification.
- if (it == observer_lists_.end() || it->second.get() != context)
- return;
- }
+ AutoLock auto_lock(lock_);
- for (auto& observer : context->list) {
- method.Run(&observer);
+ // Check whether the observer still needs a notification.
+ auto it = observers_.find(observer);
+ if (it == observers_.end())
+ return;
+ DCHECK(it->second->RunsTasksInCurrentSequence());
}
- // If there are no more observers on the list, we can now delete it.
- if (context->list.size() == 0) {
- {
- AutoLock lock(list_lock_);
- // Remove |list| if it's not already removed.
- // This can happen if multiple observers got removed in a notification.
- // See http://crbug.com/55725.
- auto it = observer_lists_.find(PlatformThread::CurrentId());
- if (it != observer_lists_.end() && it->second.get() == context)
- observer_lists_.erase(it);
- }
- }
+ // Keep track of the notification being dispatched on the current thread.
+ // This will be used if the callback below calls AddObserver().
+ //
+ // Note: |tls_current_notification_| may not be nullptr if this runs in a
+ // nested loop started by a notification callback. In that case, it is
+ // important to save the previous value to restore it later.
+ auto& tls_current_notification = tls_current_notification_.Get();
+ const NotificationDataBase* const previous_notification =
+ tls_current_notification.Get();
+ tls_current_notification.Set(&notification);
+
+ // Invoke the callback.
+ notification.method.Run(observer);
+
+ // Reset the notification being dispatched on the current thread to its
+ // previous value.
+ tls_current_notification.Set(previous_notification);
}
- mutable Lock list_lock_; // Protects the observer_lists_.
+ const ObserverListPolicy policy_ = ObserverListPolicy::ALL;
- // Key by PlatformThreadId because in tests, clients can attempt to remove
- // observers without a SingleThreadTaskRunner. If this were keyed by
- // SingleThreadTaskRunner, that operation would be silently ignored, leaving
- // garbage in the ObserverList.
- std::map<PlatformThreadId, std::unique_ptr<ObserverListContext>>
- observer_lists_;
+ // Synchronizes access to |observers_|.
+ mutable Lock lock_;
- const NotificationType type_;
+ // Keys are observers. Values are the SequencedTaskRunners on which they must
+ // be notified.
+ std::unordered_map<ObserverType*, scoped_refptr<SequencedTaskRunner>>
+ observers_;
DISALLOW_COPY_AND_ASSIGN(ObserverListThreadSafe);
};
diff --git a/base/observer_list_unittest.cc b/base/observer_list_unittest.cc
index c5e556bd9d..50d7e7e9b3 100644
--- a/base/observer_list_unittest.cc
+++ b/base/observer_list_unittest.cc
@@ -5,14 +5,29 @@
#include "base/observer_list.h"
#include "base/observer_list_threadsafe.h"
+#include <memory>
+#include <utility>
#include <vector>
+#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/location.h"
#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
+#include "base/sequenced_task_runner.h"
#include "base/single_thread_task_runner.h"
+#include "base/strings/string_piece.h"
+#include "base/synchronization/waitable_event.h"
+// TaskScheduler not supported in libchrome
+// #include "base/task_scheduler/post_task.h"
+// #include "base/task_scheduler/task_scheduler.h"
+#include "base/test/gtest_util.h"
+#include "base/test/scoped_task_environment.h"
#include "base/threading/platform_thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
@@ -21,14 +36,14 @@ namespace {
class Foo {
public:
virtual void Observe(int x) = 0;
- virtual ~Foo() {}
+ virtual ~Foo() = default;
virtual int GetValue() const { return 0; }
};
class Adder : public Foo {
public:
explicit Adder(int scaler) : total(0), scaler_(scaler) {}
- ~Adder() override {}
+ ~Adder() override = default;
void Observe(int x) override { total += x * scaler_; }
int GetValue() const override { return total; }
@@ -48,7 +63,7 @@ class Disrupter : public Foo {
Disrupter(ObserverList<Foo>* list, bool remove_self)
: Disrupter(list, nullptr, remove_self) {}
- ~Disrupter() override {}
+ ~Disrupter() override = default;
void Observe(int x) override {
if (remove_self_)
@@ -65,20 +80,6 @@ class Disrupter : public Foo {
bool remove_self_;
};
-class ThreadSafeDisrupter : public Foo {
- public:
- ThreadSafeDisrupter(ObserverListThreadSafe<Foo>* list, Foo* doomed)
- : list_(list),
- doomed_(doomed) {
- }
- ~ThreadSafeDisrupter() override {}
- void Observe(int x) override { list_->RemoveObserver(doomed_); }
-
- private:
- ObserverListThreadSafe<Foo>* list_;
- Foo* doomed_;
-};
-
template <typename ObserverListType>
class AddInObserve : public Foo {
public:
@@ -107,7 +108,9 @@ static const int kThreadRunTime = 2000; // ms to run the multi-threaded test.
class AddRemoveThread : public PlatformThread::Delegate,
public Foo {
public:
- AddRemoveThread(ObserverListThreadSafe<Foo>* list, bool notify)
+ AddRemoveThread(ObserverListThreadSafe<Foo>* list,
+ bool notify,
+ WaitableEvent* ready)
: list_(list),
loop_(nullptr),
in_list_(false),
@@ -115,17 +118,23 @@ class AddRemoveThread : public PlatformThread::Delegate,
count_observes_(0),
count_addtask_(0),
do_notifies_(notify),
- weak_factory_(this) {
- }
+ ready_(ready),
+ weak_factory_(this) {}
- ~AddRemoveThread() override {}
+ ~AddRemoveThread() override = default;
void ThreadMain() override {
loop_ = new MessageLoop(); // Fire up a message loop.
loop_->task_runner()->PostTask(
FROM_HERE,
- base::Bind(&AddRemoveThread::AddTask, weak_factory_.GetWeakPtr()));
- RunLoop().Run();
+ base::BindOnce(&AddRemoveThread::AddTask, weak_factory_.GetWeakPtr()));
+ ready_->Signal();
+ // After ready_ is signaled, loop_ is only accessed by the main test thread
+ // (i.e. not this thread) in particular by Quit() which causes Run() to
+ // return, and we "control" loop_ again.
+ RunLoop run_loop;
+ quit_loop_ = run_loop.QuitClosure();
+ run_loop.Run();
delete loop_;
loop_ = reinterpret_cast<MessageLoop*>(0xdeadbeef);
delete this;
@@ -152,13 +161,11 @@ class AddRemoveThread : public PlatformThread::Delegate,
loop_->task_runner()->PostTask(
FROM_HERE,
- base::Bind(&AddRemoveThread::AddTask, weak_factory_.GetWeakPtr()));
+ base::BindOnce(&AddRemoveThread::AddTask, weak_factory_.GetWeakPtr()));
}
- void Quit() {
- loop_->task_runner()->PostTask(FROM_HERE,
- MessageLoop::QuitWhenIdleClosure());
- }
+ // This function is only callable from the main thread.
+ void Quit() { std::move(quit_loop_).Run(); }
void Observe(int x) override {
count_observes_++;
@@ -184,6 +191,9 @@ class AddRemoveThread : public PlatformThread::Delegate,
int count_observes_; // Number of times we observed.
int count_addtask_; // Number of times thread AddTask was called
bool do_notifies_; // Whether these threads should do notifications.
+ WaitableEvent* ready_;
+
+ base::OnceClosure quit_loop_;
base::WeakPtrFactory<AddRemoveThread> weak_factory_;
};
@@ -192,14 +202,96 @@ class AddRemoveThread : public PlatformThread::Delegate,
TEST(ObserverListTest, BasicTest) {
ObserverList<Foo> observer_list;
+ const ObserverList<Foo>& const_observer_list = observer_list;
+
+ {
+ const ObserverList<Foo>::const_iterator it1 = const_observer_list.begin();
+ EXPECT_EQ(it1, const_observer_list.end());
+ // Iterator copy.
+ const ObserverList<Foo>::const_iterator it2 = it1;
+ EXPECT_EQ(it2, it1);
+ // Iterator assignment.
+ ObserverList<Foo>::const_iterator it3;
+ it3 = it2;
+ EXPECT_EQ(it3, it1);
+ EXPECT_EQ(it3, it2);
+ // Self assignment.
+ it3 = *&it3; // The *& defeats Clang's -Wself-assign warning.
+ EXPECT_EQ(it3, it1);
+ EXPECT_EQ(it3, it2);
+ }
+
+ {
+ const ObserverList<Foo>::iterator it1 = observer_list.begin();
+ EXPECT_EQ(it1, observer_list.end());
+ // Iterator copy.
+ const ObserverList<Foo>::iterator it2 = it1;
+ EXPECT_EQ(it2, it1);
+ // Iterator assignment.
+ ObserverList<Foo>::iterator it3;
+ it3 = it2;
+ EXPECT_EQ(it3, it1);
+ EXPECT_EQ(it3, it2);
+ // Self assignment.
+ it3 = *&it3; // The *& defeats Clang's -Wself-assign warning.
+ EXPECT_EQ(it3, it1);
+ EXPECT_EQ(it3, it2);
+ }
+
Adder a(1), b(-1), c(1), d(-1), e(-1);
Disrupter evil(&observer_list, &c);
observer_list.AddObserver(&a);
observer_list.AddObserver(&b);
- EXPECT_TRUE(observer_list.HasObserver(&a));
- EXPECT_FALSE(observer_list.HasObserver(&c));
+ EXPECT_TRUE(const_observer_list.HasObserver(&a));
+ EXPECT_FALSE(const_observer_list.HasObserver(&c));
+
+ {
+ const ObserverList<Foo>::const_iterator it1 = const_observer_list.begin();
+ EXPECT_NE(it1, const_observer_list.end());
+ // Iterator copy.
+ const ObserverList<Foo>::const_iterator it2 = it1;
+ EXPECT_EQ(it2, it1);
+ EXPECT_NE(it2, const_observer_list.end());
+ // Iterator assignment.
+ ObserverList<Foo>::const_iterator it3;
+ it3 = it2;
+ EXPECT_EQ(it3, it1);
+ EXPECT_EQ(it3, it2);
+ // Self assignment.
+ it3 = *&it3; // The *& defeats Clang's -Wself-assign warning.
+ EXPECT_EQ(it3, it1);
+ EXPECT_EQ(it3, it2);
+ // Iterator post increment.
+ ObserverList<Foo>::const_iterator it4 = it3++;
+ EXPECT_EQ(it4, it1);
+ EXPECT_EQ(it4, it2);
+ EXPECT_NE(it4, it3);
+ }
+
+ {
+ const ObserverList<Foo>::iterator it1 = observer_list.begin();
+ EXPECT_NE(it1, observer_list.end());
+ // Iterator copy.
+ const ObserverList<Foo>::iterator it2 = it1;
+ EXPECT_EQ(it2, it1);
+ EXPECT_NE(it2, observer_list.end());
+ // Iterator assignment.
+ ObserverList<Foo>::iterator it3;
+ it3 = it2;
+ EXPECT_EQ(it3, it1);
+ EXPECT_EQ(it3, it2);
+ // Self assignment.
+ it3 = *&it3; // The *& defeats Clang's -Wself-assign warning.
+ EXPECT_EQ(it3, it1);
+ EXPECT_EQ(it3, it2);
+ // Iterator post increment.
+ ObserverList<Foo>::iterator it4 = it3++;
+ EXPECT_EQ(it4, it1);
+ EXPECT_EQ(it4, it2);
+ EXPECT_NE(it4, it3);
+ }
for (auto& observer : observer_list)
observer.Observe(10);
@@ -221,6 +313,76 @@ TEST(ObserverListTest, BasicTest) {
EXPECT_EQ(0, e.total);
}
+TEST(ObserverListTest, CompactsWhenNoActiveIterator) {
+ ObserverList<const Foo> ol;
+ const ObserverList<const Foo>& col = ol;
+
+ const Adder a(1);
+ const Adder b(2);
+ const Adder c(3);
+
+ ol.AddObserver(&a);
+ ol.AddObserver(&b);
+
+ EXPECT_TRUE(col.HasObserver(&a));
+ EXPECT_FALSE(col.HasObserver(&c));
+
+ EXPECT_TRUE(col.might_have_observers());
+
+ using It = ObserverList<const Foo>::const_iterator;
+
+ {
+ It it = col.begin();
+ EXPECT_NE(it, col.end());
+ It ita = it;
+ EXPECT_EQ(ita, it);
+ EXPECT_NE(++it, col.end());
+ EXPECT_NE(ita, it);
+ It itb = it;
+ EXPECT_EQ(itb, it);
+ EXPECT_EQ(++it, col.end());
+
+ EXPECT_TRUE(col.might_have_observers());
+ EXPECT_EQ(&*ita, &a);
+ EXPECT_EQ(&*itb, &b);
+
+ ol.RemoveObserver(&a);
+ EXPECT_TRUE(col.might_have_observers());
+ EXPECT_FALSE(col.HasObserver(&a));
+ EXPECT_EQ(&*itb, &b);
+
+ ol.RemoveObserver(&b);
+ EXPECT_TRUE(col.might_have_observers());
+ EXPECT_FALSE(col.HasObserver(&a));
+ EXPECT_FALSE(col.HasObserver(&b));
+
+ it = It();
+ ita = It();
+ EXPECT_TRUE(col.might_have_observers());
+ ita = itb;
+ itb = It();
+ EXPECT_TRUE(col.might_have_observers());
+ ita = It();
+ EXPECT_FALSE(col.might_have_observers());
+ }
+
+ ol.AddObserver(&a);
+ ol.AddObserver(&b);
+ EXPECT_TRUE(col.might_have_observers());
+ ol.Clear();
+ EXPECT_FALSE(col.might_have_observers());
+
+ ol.AddObserver(&a);
+ ol.AddObserver(&b);
+ EXPECT_TRUE(col.might_have_observers());
+ {
+ const It it = col.begin();
+ ol.Clear();
+ EXPECT_TRUE(col.might_have_observers());
+ }
+ EXPECT_FALSE(col.might_have_observers());
+}
+
TEST(ObserverListTest, DisruptSelf) {
ObserverList<Foo> observer_list;
Adder a(1), b(-1), c(1), d(-1);
@@ -276,7 +438,6 @@ TEST(ObserverListThreadSafeTest, BasicTest) {
Adder b(-1);
Adder c(1);
Adder d(-1);
- ThreadSafeDisrupter evil(observer_list.get(), &c);
observer_list->AddObserver(&a);
observer_list->AddObserver(&b);
@@ -284,11 +445,11 @@ TEST(ObserverListThreadSafeTest, BasicTest) {
observer_list->Notify(FROM_HERE, &Foo::Observe, 10);
RunLoop().RunUntilIdle();
- observer_list->AddObserver(&evil);
observer_list->AddObserver(&c);
observer_list->AddObserver(&d);
observer_list->Notify(FROM_HERE, &Foo::Observe, 10);
+ observer_list->RemoveObserver(&c);
RunLoop().RunUntilIdle();
EXPECT_EQ(20, a.total);
@@ -329,18 +490,18 @@ TEST(ObserverListThreadSafeTest, RemoveObserver) {
EXPECT_EQ(0, b.total);
}
-TEST(ObserverListThreadSafeTest, WithoutMessageLoop) {
+TEST(ObserverListThreadSafeTest, WithoutSequence) {
scoped_refptr<ObserverListThreadSafe<Foo> > observer_list(
new ObserverListThreadSafe<Foo>);
Adder a(1), b(1), c(1);
- // No MessageLoop, so these should not be added.
+ // No sequence, so these should not be added.
observer_list->AddObserver(&a);
observer_list->AddObserver(&b);
{
- // Add c when there's a loop.
+ // Add c when there's a sequence.
MessageLoop loop;
observer_list->AddObserver(&c);
@@ -351,10 +512,10 @@ TEST(ObserverListThreadSafeTest, WithoutMessageLoop) {
EXPECT_EQ(0, b.total);
EXPECT_EQ(10, c.total);
- // Now add a when there's a loop.
+ // Now add a when there's a sequence.
observer_list->AddObserver(&a);
- // Remove c when there's a loop.
+ // Remove c when there's a sequence.
observer_list->RemoveObserver(&c);
// Notify again.
@@ -366,7 +527,7 @@ TEST(ObserverListThreadSafeTest, WithoutMessageLoop) {
EXPECT_EQ(10, c.total);
}
- // Removing should always succeed with or without a loop.
+ // Removing should always succeed with or without a sequence.
observer_list->RemoveObserver(&a);
// Notifying should not fail but should also be a no-op.
@@ -383,7 +544,7 @@ TEST(ObserverListThreadSafeTest, WithoutMessageLoop) {
class FooRemover : public Foo {
public:
explicit FooRemover(ObserverListThreadSafe<Foo>* list) : list_(list) {}
- ~FooRemover() override {}
+ ~FooRemover() override = default;
void AddFooToRemove(Foo* foo) {
foos_.push_back(foo);
@@ -430,26 +591,35 @@ static void ThreadSafeObserverHarness(int num_threads,
bool cross_thread_notifies) {
MessageLoop loop;
- const int kMaxThreads = 15;
- num_threads = num_threads > kMaxThreads ? kMaxThreads : num_threads;
-
scoped_refptr<ObserverListThreadSafe<Foo> > observer_list(
new ObserverListThreadSafe<Foo>);
Adder a(1);
Adder b(-1);
- Adder c(1);
- Adder d(-1);
observer_list->AddObserver(&a);
observer_list->AddObserver(&b);
- AddRemoveThread* threaded_observer[kMaxThreads];
- base::PlatformThreadHandle threads[kMaxThreads];
+ std::vector<AddRemoveThread*> threaded_observer;
+ std::vector<base::PlatformThreadHandle> threads(num_threads);
+ std::vector<std::unique_ptr<base::WaitableEvent>> ready;
+ threaded_observer.reserve(num_threads);
+ ready.reserve(num_threads);
for (int index = 0; index < num_threads; index++) {
- threaded_observer[index] = new AddRemoveThread(observer_list.get(), false);
- EXPECT_TRUE(PlatformThread::Create(0,
- threaded_observer[index], &threads[index]));
+ ready.push_back(std::make_unique<WaitableEvent>(
+ WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED));
+ threaded_observer.push_back(new AddRemoveThread(
+ observer_list.get(), cross_thread_notifies, ready.back().get()));
+ EXPECT_TRUE(
+ PlatformThread::Create(0, threaded_observer.back(), &threads[index]));
}
+ ASSERT_EQ(static_cast<size_t>(num_threads), threaded_observer.size());
+ ASSERT_EQ(static_cast<size_t>(num_threads), ready.size());
+
+ // This makes sure that threaded_observer has gotten to set loop_, so that we
+ // can call Quit() below safe-ish-ly.
+ for (int i = 0; i < num_threads; ++i)
+ ready[i]->Wait();
Time start = Time::Now();
while (true) {
@@ -467,7 +637,13 @@ static void ThreadSafeObserverHarness(int num_threads,
}
}
-TEST(ObserverListThreadSafeTest, CrossThreadObserver) {
+#if defined(OS_FUCHSIA)
+// TODO(crbug.com/738275): This is flaky on Fuchsia.
+#define MAYBE_CrossThreadObserver DISABLED_CrossThreadObserver
+#else
+#define MAYBE_CrossThreadObserver CrossThreadObserver
+#endif
+TEST(ObserverListThreadSafeTest, MAYBE_CrossThreadObserver) {
// Use 7 observer threads. Notifications only come from
// the main thread.
ThreadSafeObserverHarness(7, false);
@@ -491,8 +667,146 @@ TEST(ObserverListThreadSafeTest, OutlivesMessageLoop) {
observer_list->Notify(FROM_HERE, &Foo::Observe, 1);
}
+namespace {
+
+class SequenceVerificationObserver : public Foo {
+ public:
+ explicit SequenceVerificationObserver(
+ scoped_refptr<SequencedTaskRunner> task_runner)
+ : task_runner_(std::move(task_runner)) {}
+ ~SequenceVerificationObserver() override = default;
+
+ void Observe(int x) override {
+ called_on_valid_sequence_ = task_runner_->RunsTasksInCurrentSequence();
+ }
+
+ bool called_on_valid_sequence() const { return called_on_valid_sequence_; }
+
+ private:
+ const scoped_refptr<SequencedTaskRunner> task_runner_;
+ bool called_on_valid_sequence_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(SequenceVerificationObserver);
+};
+
+} // namespace
+
+// Verify that observers are notified on the correct sequence.
+// TaskScheduler not supported in libchrome
+#if 0
+TEST(ObserverListThreadSafeTest, NotificationOnValidSequence) {
+ test::ScopedTaskEnvironment scoped_task_environment;
+
+ auto task_runner_1 = CreateSequencedTaskRunnerWithTraits(TaskTraits());
+ auto task_runner_2 = CreateSequencedTaskRunnerWithTraits(TaskTraits());
+
+ auto observer_list = MakeRefCounted<ObserverListThreadSafe<Foo>>();
+
+ SequenceVerificationObserver observer_1(task_runner_1);
+ SequenceVerificationObserver observer_2(task_runner_2);
+
+ task_runner_1->PostTask(FROM_HERE,
+ BindOnce(&ObserverListThreadSafe<Foo>::AddObserver,
+ observer_list, Unretained(&observer_1)));
+ task_runner_2->PostTask(FROM_HERE,
+ BindOnce(&ObserverListThreadSafe<Foo>::AddObserver,
+ observer_list, Unretained(&observer_2)));
+
+ TaskScheduler::GetInstance()->FlushForTesting();
+
+ observer_list->Notify(FROM_HERE, &Foo::Observe, 1);
+
+ TaskScheduler::GetInstance()->FlushForTesting();
+
+ EXPECT_TRUE(observer_1.called_on_valid_sequence());
+ EXPECT_TRUE(observer_2.called_on_valid_sequence());
+}
+#endif
+
+// Verify that when an observer is added to a NOTIFY_ALL ObserverListThreadSafe
+// from a notification, it is itself notified.
+// TaskScheduler not supported in libchrome
+#if 0
+TEST(ObserverListThreadSafeTest, AddObserverFromNotificationNotifyAll) {
+ test::ScopedTaskEnvironment scoped_task_environment;
+ auto observer_list = MakeRefCounted<ObserverListThreadSafe<Foo>>();
+
+ Adder observer_added_from_notification(1);
+
+ AddInObserve<ObserverListThreadSafe<Foo>> initial_observer(
+ observer_list.get());
+ initial_observer.SetToAdd(&observer_added_from_notification);
+ observer_list->AddObserver(&initial_observer);
+
+ observer_list->Notify(FROM_HERE, &Foo::Observe, 1);
+
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(1, observer_added_from_notification.GetValue());
+}
+#endif
+
+namespace {
+
+class RemoveWhileNotificationIsRunningObserver : public Foo {
+ public:
+ RemoveWhileNotificationIsRunningObserver()
+ : notification_running_(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED),
+ barrier_(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED) {}
+ ~RemoveWhileNotificationIsRunningObserver() override = default;
+
+ void Observe(int x) override {
+ notification_running_.Signal();
+ ScopedAllowBaseSyncPrimitivesForTesting allow_base_sync_primitives;
+ barrier_.Wait();
+ }
+
+ void WaitForNotificationRunning() { notification_running_.Wait(); }
+ void Unblock() { barrier_.Signal(); }
+
+ private:
+ WaitableEvent notification_running_;
+ WaitableEvent barrier_;
+
+ DISALLOW_COPY_AND_ASSIGN(RemoveWhileNotificationIsRunningObserver);
+};
+
+} // namespace
+
+// Verify that there is no crash when an observer is removed while it is being
+// notified.
+// TaskScheduler not supported in libchrome
+#if 0
+TEST(ObserverListThreadSafeTest, RemoveWhileNotificationIsRunning) {
+ auto observer_list = MakeRefCounted<ObserverListThreadSafe<Foo>>();
+ RemoveWhileNotificationIsRunningObserver observer;
+
+ WaitableEvent task_running(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ WaitableEvent barrier(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+
+ // This must be after the declaration of |barrier| so that tasks posted to
+ // TaskScheduler can safely use |barrier|.
+ test::ScopedTaskEnvironment scoped_task_environment;
+
+ CreateSequencedTaskRunnerWithTraits({})->PostTask(
+ FROM_HERE, base::BindOnce(&ObserverListThreadSafe<Foo>::AddObserver,
+ observer_list, Unretained(&observer)));
+ TaskScheduler::GetInstance()->FlushForTesting();
+
+ observer_list->Notify(FROM_HERE, &Foo::Observe, 1);
+ observer.WaitForNotificationRunning();
+ observer_list->RemoveObserver(&observer);
+
+ observer.Unblock();
+}
+#endif
+
TEST(ObserverListTest, Existing) {
- ObserverList<Foo> observer_list(ObserverList<Foo>::NOTIFY_EXISTING_ONLY);
+ ObserverList<Foo> observer_list(ObserverListPolicy::EXISTING_ONLY);
Adder a(1);
AddInObserve<ObserverList<Foo> > b(&observer_list);
Adder c(1);
@@ -518,8 +832,8 @@ TEST(ObserverListTest, Existing) {
// Same as above, but for ObserverListThreadSafe
TEST(ObserverListThreadSafeTest, Existing) {
MessageLoop loop;
- scoped_refptr<ObserverListThreadSafe<Foo> > observer_list(
- new ObserverListThreadSafe<Foo>(ObserverList<Foo>::NOTIFY_EXISTING_ONLY));
+ scoped_refptr<ObserverListThreadSafe<Foo>> observer_list(
+ new ObserverListThreadSafe<Foo>(ObserverListPolicy::EXISTING_ONLY));
Adder a(1);
AddInObserve<ObserverListThreadSafe<Foo> > b(observer_list.get());
Adder c(1);
@@ -577,7 +891,7 @@ TEST(ObserverListTest, ClearNotifyAll) {
}
TEST(ObserverListTest, ClearNotifyExistingOnly) {
- ObserverList<Foo> observer_list(ObserverList<Foo>::NOTIFY_EXISTING_ONLY);
+ ObserverList<Foo> observer_list(ObserverListPolicy::EXISTING_ONLY);
AddInClearObserve a(&observer_list);
observer_list.AddObserver(&a);
@@ -592,7 +906,7 @@ TEST(ObserverListTest, ClearNotifyExistingOnly) {
class ListDestructor : public Foo {
public:
explicit ListDestructor(ObserverList<Foo>* list) : list_(list) {}
- ~ListDestructor() override {}
+ ~ListDestructor() override = default;
void Observe(int x) override { delete list_; }
@@ -608,8 +922,10 @@ TEST(ObserverListTest, IteratorOutlivesList) {
for (auto& observer : *observer_list)
observer.Observe(0);
- // If this test fails, there'll be Valgrind errors when this function goes out
- // of scope.
+
+ // There are no EXPECT* statements for this test, if we catch
+ // use-after-free errors for observer_list (eg with ASan) then
+ // this test has failed. See http://crbug.com/85296.
}
TEST(ObserverListTest, BasicStdIterator) {
@@ -932,4 +1248,48 @@ TEST(ObserverListTest, AddObserverInTheLastObserve) {
EXPECT_EQ(-10, b.total);
}
+class MockLogAssertHandler {
+ public:
+ MOCK_METHOD4(
+ HandleLogAssert,
+ void(const char*, int, const base::StringPiece, const base::StringPiece));
+};
+
+#if DCHECK_IS_ON()
+TEST(ObserverListTest, NonReentrantObserverList) {
+ using ::testing::_;
+
+ ObserverList<Foo, /*check_empty=*/false, /*allow_reentrancy=*/false>
+ non_reentrant_observer_list;
+ Adder a(1);
+ non_reentrant_observer_list.AddObserver(&a);
+
+ EXPECT_DCHECK_DEATH({
+ for (const Foo& a : non_reentrant_observer_list) {
+ for (const Foo& b : non_reentrant_observer_list) {
+ std::ignore = a;
+ std::ignore = b;
+ }
+ }
+ });
+}
+
+TEST(ObserverListTest, ReentrantObserverList) {
+ using ::testing::_;
+
+ ReentrantObserverList<Foo> reentrant_observer_list;
+ Adder a(1);
+ reentrant_observer_list.AddObserver(&a);
+ bool passed = false;
+ for (const Foo& a : reentrant_observer_list) {
+ for (const Foo& b : reentrant_observer_list) {
+ std::ignore = a;
+ std::ignore = b;
+ passed = true;
+ }
+ }
+ EXPECT_TRUE(passed);
+}
+#endif
+
} // namespace base
diff --git a/base/optional.h b/base/optional.h
index cf65ad7dac..f6263b8b7c 100644
--- a/base/optional.h
+++ b/base/optional.h
@@ -6,6 +6,7 @@
#define BASE_OPTIONAL_H_
#include <type_traits>
+#include <utility>
#include "base/logging.h"
#include "base/template_util.h"
@@ -30,34 +31,46 @@ constexpr in_place_t in_place = {};
// http://en.cppreference.com/w/cpp/utility/optional/nullopt
constexpr nullopt_t nullopt(0);
+// Forward declaration, which is refered by following helpers.
+template <typename T>
+class Optional;
+
namespace internal {
-template <typename T, bool = base::is_trivially_destructible<T>::value>
-struct OptionalStorage {
+template <typename T, bool = std::is_trivially_destructible<T>::value>
+struct OptionalStorageBase {
// Initializing |empty_| here instead of using default member initializing
// to avoid errors in g++ 4.8.
- constexpr OptionalStorage() : empty_('\0') {}
-
- constexpr explicit OptionalStorage(const T& value)
- : is_null_(false), value_(value) {}
+ constexpr OptionalStorageBase() : empty_('\0') {}
- // TODO(alshabalin): Can't use 'constexpr' with std::move until C++14.
- explicit OptionalStorage(T&& value)
- : is_null_(false), value_(std::move(value)) {}
-
- // TODO(alshabalin): Can't use 'constexpr' with std::forward until C++14.
template <class... Args>
- explicit OptionalStorage(base::in_place_t, Args&&... args)
- : is_null_(false), value_(std::forward<Args>(args)...) {}
+ constexpr explicit OptionalStorageBase(in_place_t, Args&&... args)
+ : is_populated_(true), value_(std::forward<Args>(args)...) {}
// When T is not trivially destructible we must call its
// destructor before deallocating its memory.
- ~OptionalStorage() {
- if (!is_null_)
+ // Note that this hides the (implicitly declared) move constructor, which
+ // would be used for constexpr move constructor in OptionalStorage<T>.
+ // It is needed iff T is trivially move constructible. However, the current
+ // is_trivially_{copy,move}_constructible implementation requires
+ // is_trivially_destructible (which looks a bug, cf:
+ // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51452 and
+ // http://cplusplus.github.io/LWG/lwg-active.html#2116), so it is not
+ // necessary for this case at the moment. Please see also the destructor
+ // comment in "is_trivially_destructible = true" specialization below.
+ ~OptionalStorageBase() {
+ if (is_populated_)
value_.~T();
}
- bool is_null_ = true;
+ template <class... Args>
+ void Init(Args&&... args) {
+ DCHECK(!is_populated_);
+ ::new (&value_) T(std::forward<Args>(args)...);
+ is_populated_ = true;
+ }
+
+ bool is_populated_ = false;
union {
// |empty_| exists so that the union will always be initialized, even when
// it doesn't contain a value. Union members must be initialized for the
@@ -68,29 +81,37 @@ struct OptionalStorage {
};
template <typename T>
-struct OptionalStorage<T, true> {
+struct OptionalStorageBase<T, true /* trivially destructible */> {
// Initializing |empty_| here instead of using default member initializing
// to avoid errors in g++ 4.8.
- constexpr OptionalStorage() : empty_('\0') {}
-
- constexpr explicit OptionalStorage(const T& value)
- : is_null_(false), value_(value) {}
+ constexpr OptionalStorageBase() : empty_('\0') {}
- // TODO(alshabalin): Can't use 'constexpr' with std::move until C++14.
- explicit OptionalStorage(T&& value)
- : is_null_(false), value_(std::move(value)) {}
-
- // TODO(alshabalin): Can't use 'constexpr' with std::forward until C++14.
template <class... Args>
- explicit OptionalStorage(base::in_place_t, Args&&... args)
- : is_null_(false), value_(std::forward<Args>(args)...) {}
+ constexpr explicit OptionalStorageBase(in_place_t, Args&&... args)
+ : is_populated_(true), value_(std::forward<Args>(args)...) {}
// When T is trivially destructible (i.e. its destructor does nothing) there
- // is no need to call it. Explicitly defaulting the destructor means it's not
- // user-provided. Those two together make this destructor trivial.
- ~OptionalStorage() = default;
+ // is no need to call it. Implicitly defined destructor is trivial, because
+ // both members (bool and union containing only variants which are trivially
+ // destructible) are trivially destructible.
+ // Explicitly-defaulted destructor is also trivial, but do not use it here,
+ // because it hides the implicit move constructor. It is needed to implement
+ // constexpr move constructor in OptionalStorage iff T is trivially move
+ // constructible. Note that, if T is trivially move constructible, the move
+ // constructor of OptionalStorageBase<T> is also implicitly defined and it is
+ // trivially move constructor. If T is not trivially move constructible,
+ // "not declaring move constructor without destructor declaration" here means
+ // "delete move constructor", which works because any move constructor of
+ // OptionalStorage will not refer to it in that case.
+
+ template <class... Args>
+ void Init(Args&&... args) {
+ DCHECK(!is_populated_);
+ ::new (&value_) T(std::forward<Args>(args)...);
+ is_populated_ = true;
+ }
- bool is_null_ = true;
+ bool is_populated_ = false;
union {
// |empty_| exists so that the union will always be initialized, even when
// it doesn't contain a value. Union members must be initialized for the
@@ -100,8 +121,291 @@ struct OptionalStorage<T, true> {
};
};
+// Implement conditional constexpr copy and move constructors. These are
+// constexpr if is_trivially_{copy,move}_constructible<T>::value is true
+// respectively. If each is true, the corresponding constructor is defined as
+// "= default;", which generates a constexpr constructor (In this case,
+// the condition of constexpr-ness is satisfied because the base class also has
+// compiler generated constexpr {copy,move} constructors). Note that
+// placement-new is prohibited in constexpr.
+template <typename T,
+ bool = is_trivially_copy_constructible<T>::value,
+ bool = std::is_trivially_move_constructible<T>::value>
+struct OptionalStorage : OptionalStorageBase<T> {
+ // This is no trivially {copy,move} constructible case. Other cases are
+ // defined below as specializations.
+
+ // Accessing the members of template base class requires explicit
+ // declaration.
+ using OptionalStorageBase<T>::is_populated_;
+ using OptionalStorageBase<T>::value_;
+ using OptionalStorageBase<T>::Init;
+
+ // Inherit constructors (specifically, the in_place constructor).
+ using OptionalStorageBase<T>::OptionalStorageBase;
+
+ // User defined constructor deletes the default constructor.
+ // Define it explicitly.
+ OptionalStorage() = default;
+
+ OptionalStorage(const OptionalStorage& other) {
+ if (other.is_populated_)
+ Init(other.value_);
+ }
+
+ OptionalStorage(OptionalStorage&& other) noexcept(
+ std::is_nothrow_move_constructible<T>::value) {
+ if (other.is_populated_)
+ Init(std::move(other.value_));
+ }
+};
+
+template <typename T>
+struct OptionalStorage<T,
+ true /* trivially copy constructible */,
+ false /* trivially move constructible */>
+ : OptionalStorageBase<T> {
+ using OptionalStorageBase<T>::is_populated_;
+ using OptionalStorageBase<T>::value_;
+ using OptionalStorageBase<T>::Init;
+ using OptionalStorageBase<T>::OptionalStorageBase;
+
+ OptionalStorage() = default;
+ OptionalStorage(const OptionalStorage& other) = default;
+
+ OptionalStorage(OptionalStorage&& other) noexcept(
+ std::is_nothrow_move_constructible<T>::value) {
+ if (other.is_populated_)
+ Init(std::move(other.value_));
+ }
+};
+
+template <typename T>
+struct OptionalStorage<T,
+ false /* trivially copy constructible */,
+ true /* trivially move constructible */>
+ : OptionalStorageBase<T> {
+ using OptionalStorageBase<T>::is_populated_;
+ using OptionalStorageBase<T>::value_;
+ using OptionalStorageBase<T>::Init;
+ using OptionalStorageBase<T>::OptionalStorageBase;
+
+ OptionalStorage() = default;
+ OptionalStorage(OptionalStorage&& other) = default;
+
+ OptionalStorage(const OptionalStorage& other) {
+ if (other.is_populated_)
+ Init(other.value_);
+ }
+};
+
+template <typename T>
+struct OptionalStorage<T,
+ true /* trivially copy constructible */,
+ true /* trivially move constructible */>
+ : OptionalStorageBase<T> {
+ // If both trivially {copy,move} constructible are true, it is not necessary
+ // to use user-defined constructors. So, just inheriting constructors
+ // from the base class works.
+ using OptionalStorageBase<T>::OptionalStorageBase;
+};
+
+// Base class to support conditionally usable copy-/move- constructors
+// and assign operators.
+template <typename T>
+class OptionalBase {
+ // This class provides implementation rather than public API, so everything
+ // should be hidden. Often we use composition, but we cannot in this case
+ // because of C++ language restriction.
+ protected:
+ constexpr OptionalBase() = default;
+ constexpr OptionalBase(const OptionalBase& other) = default;
+ constexpr OptionalBase(OptionalBase&& other) = default;
+
+ template <class... Args>
+ constexpr explicit OptionalBase(in_place_t, Args&&... args)
+ : storage_(in_place, std::forward<Args>(args)...) {}
+
+ // Implementation of converting constructors.
+ template <typename U>
+ explicit OptionalBase(const OptionalBase<U>& other) {
+ if (other.storage_.is_populated_)
+ storage_.Init(other.storage_.value_);
+ }
+
+ template <typename U>
+ explicit OptionalBase(OptionalBase<U>&& other) {
+ if (other.storage_.is_populated_)
+ storage_.Init(std::move(other.storage_.value_));
+ }
+
+ ~OptionalBase() = default;
+
+ OptionalBase& operator=(const OptionalBase& other) {
+ CopyAssign(other);
+ return *this;
+ }
+
+ OptionalBase& operator=(OptionalBase&& other) noexcept(
+ std::is_nothrow_move_assignable<T>::value&&
+ std::is_nothrow_move_constructible<T>::value) {
+ MoveAssign(std::move(other));
+ return *this;
+ }
+
+ template <typename U>
+ void CopyAssign(const OptionalBase<U>& other) {
+ if (other.storage_.is_populated_)
+ InitOrAssign(other.storage_.value_);
+ else
+ FreeIfNeeded();
+ }
+
+ template <typename U>
+ void MoveAssign(OptionalBase<U>&& other) {
+ if (other.storage_.is_populated_)
+ InitOrAssign(std::move(other.storage_.value_));
+ else
+ FreeIfNeeded();
+ }
+
+ template <typename U>
+ void InitOrAssign(U&& value) {
+ if (storage_.is_populated_)
+ storage_.value_ = std::forward<U>(value);
+ else
+ storage_.Init(std::forward<U>(value));
+ }
+
+ void FreeIfNeeded() {
+ if (!storage_.is_populated_)
+ return;
+ storage_.value_.~T();
+ storage_.is_populated_ = false;
+ }
+
+ // For implementing conversion, allow access to other typed OptionalBase
+ // class.
+ template <typename U>
+ friend class OptionalBase;
+
+ OptionalStorage<T> storage_;
+};
+
+// The following {Copy,Move}{Constructible,Assignable} structs are helpers to
+// implement constructor/assign-operator overloading. Specifically, if T is
+// is not movable but copyable, Optional<T>'s move constructor should not
+// participate in overload resolution. This inheritance trick implements that.
+template <bool is_copy_constructible>
+struct CopyConstructible {};
+
+template <>
+struct CopyConstructible<false> {
+ constexpr CopyConstructible() = default;
+ constexpr CopyConstructible(const CopyConstructible&) = delete;
+ constexpr CopyConstructible(CopyConstructible&&) = default;
+ CopyConstructible& operator=(const CopyConstructible&) = default;
+ CopyConstructible& operator=(CopyConstructible&&) = default;
+};
+
+template <bool is_move_constructible>
+struct MoveConstructible {};
+
+template <>
+struct MoveConstructible<false> {
+ constexpr MoveConstructible() = default;
+ constexpr MoveConstructible(const MoveConstructible&) = default;
+ constexpr MoveConstructible(MoveConstructible&&) = delete;
+ MoveConstructible& operator=(const MoveConstructible&) = default;
+ MoveConstructible& operator=(MoveConstructible&&) = default;
+};
+
+template <bool is_copy_assignable>
+struct CopyAssignable {};
+
+template <>
+struct CopyAssignable<false> {
+ constexpr CopyAssignable() = default;
+ constexpr CopyAssignable(const CopyAssignable&) = default;
+ constexpr CopyAssignable(CopyAssignable&&) = default;
+ CopyAssignable& operator=(const CopyAssignable&) = delete;
+ CopyAssignable& operator=(CopyAssignable&&) = default;
+};
+
+template <bool is_move_assignable>
+struct MoveAssignable {};
+
+template <>
+struct MoveAssignable<false> {
+ constexpr MoveAssignable() = default;
+ constexpr MoveAssignable(const MoveAssignable&) = default;
+ constexpr MoveAssignable(MoveAssignable&&) = default;
+ MoveAssignable& operator=(const MoveAssignable&) = default;
+ MoveAssignable& operator=(MoveAssignable&&) = delete;
+};
+
+// Helper to conditionally enable converting constructors and assign operators.
+template <typename T, typename U>
+struct IsConvertibleFromOptional
+ : std::integral_constant<
+ bool,
+ std::is_constructible<T, Optional<U>&>::value ||
+ std::is_constructible<T, const Optional<U>&>::value ||
+ std::is_constructible<T, Optional<U>&&>::value ||
+ std::is_constructible<T, const Optional<U>&&>::value ||
+ std::is_convertible<Optional<U>&, T>::value ||
+ std::is_convertible<const Optional<U>&, T>::value ||
+ std::is_convertible<Optional<U>&&, T>::value ||
+ std::is_convertible<const Optional<U>&&, T>::value> {};
+
+template <typename T, typename U>
+struct IsAssignableFromOptional
+ : std::integral_constant<
+ bool,
+ IsConvertibleFromOptional<T, U>::value ||
+ std::is_assignable<T&, Optional<U>&>::value ||
+ std::is_assignable<T&, const Optional<U>&>::value ||
+ std::is_assignable<T&, Optional<U>&&>::value ||
+ std::is_assignable<T&, const Optional<U>&&>::value> {};
+
+// Forward compatibility for C++17.
+// Introduce one more deeper nested namespace to avoid leaking using std::swap.
+namespace swappable_impl {
+using std::swap;
+
+struct IsSwappableImpl {
+ // Tests if swap can be called. Check<T&>(0) returns true_type iff swap
+ // is available for T. Otherwise, Check's overload resolution falls back
+ // to Check(...) declared below thanks to SFINAE, so returns false_type.
+ template <typename T>
+ static auto Check(int)
+ -> decltype(swap(std::declval<T>(), std::declval<T>()), std::true_type());
+
+ template <typename T>
+ static std::false_type Check(...);
+};
+} // namespace swappable_impl
+
+template <typename T>
+struct IsSwappable : decltype(swappable_impl::IsSwappableImpl::Check<T&>(0)) {};
+
+// Forward compatibility for C++20.
+template <typename T>
+using RemoveCvRefT = std::remove_cv_t<std::remove_reference_t<T>>;
+
} // namespace internal
+// On Windows, by default, empty-base class optimization does not work,
+// which means even if the base class is empty struct, it still consumes one
+// byte for its body. __declspec(empty_bases) enables the optimization.
+// cf)
+// https://blogs.msdn.microsoft.com/vcblog/2016/03/30/optimizing-the-layout-of-empty-base-classes-in-vs2015-update-2-3/
+#ifdef OS_WIN
+#define OPTIONAL_DECLSPEC_EMPTY_BASES __declspec(empty_bases)
+#else
+#define OPTIONAL_DECLSPEC_EMPTY_BASES
+#endif
+
// base::Optional is a Chromium version of the C++17 optional class:
// std::optional documentation:
// http://en.cppreference.com/w/cpp/utility/optional
@@ -109,128 +413,218 @@ struct OptionalStorage<T, true> {
// https://chromium.googlesource.com/chromium/src/+/master/docs/optional.md
//
// These are the differences between the specification and the implementation:
-// - The constructor and emplace method using initializer_list are not
-// implemented because 'initializer_list' is banned from Chromium.
// - Constructors do not use 'constexpr' as it is a C++14 extension.
// - 'constexpr' might be missing in some places for reasons specified locally.
// - No exceptions are thrown, because they are banned from Chromium.
+// Marked noexcept for only move constructor and move assign operators.
// - All the non-members are in the 'base' namespace instead of 'std'.
+//
+// Note that T cannot have a constructor T(Optional<T>) etc. Optional<T> checks
+// T's constructor (specifically via IsConvertibleFromOptional), and in the
+// check whether T can be constructible from Optional<T>, which is recursive
+// so it does not work. As of Feb 2018, std::optional C++17 implementation in
+// both clang and gcc has same limitation. MSVC SFINAE looks to have different
+// behavior, but anyway it reports an error, too.
template <typename T>
-class Optional {
+class OPTIONAL_DECLSPEC_EMPTY_BASES Optional
+ : public internal::OptionalBase<T>,
+ public internal::CopyConstructible<std::is_copy_constructible<T>::value>,
+ public internal::MoveConstructible<std::is_move_constructible<T>::value>,
+ public internal::CopyAssignable<std::is_copy_constructible<T>::value &&
+ std::is_copy_assignable<T>::value>,
+ public internal::MoveAssignable<std::is_move_constructible<T>::value &&
+ std::is_move_assignable<T>::value> {
public:
+#undef OPTIONAL_DECLSPEC_EMPTY_BASES
using value_type = T;
- constexpr Optional() {}
-
- constexpr Optional(base::nullopt_t) {}
-
- Optional(const Optional& other) {
- if (!other.storage_.is_null_)
- Init(other.value());
- }
-
- Optional(Optional&& other) {
- if (!other.storage_.is_null_)
- Init(std::move(other.value()));
- }
-
- constexpr Optional(const T& value) : storage_(value) {}
-
- // TODO(alshabalin): Can't use 'constexpr' with std::move until C++14.
- Optional(T&& value) : storage_(std::move(value)) {}
+ // Defer default/copy/move constructor implementation to OptionalBase.
+ constexpr Optional() = default;
+ constexpr Optional(const Optional& other) = default;
+ constexpr Optional(Optional&& other) noexcept(
+ std::is_nothrow_move_constructible<T>::value) = default;
+
+ constexpr Optional(nullopt_t) {} // NOLINT(runtime/explicit)
+
+ // Converting copy constructor. "explicit" only if
+ // std::is_convertible<const U&, T>::value is false. It is implemented by
+ // declaring two almost same constructors, but that condition in enable_if_t
+ // is different, so that either one is chosen, thanks to SFINAE.
+ template <
+ typename U,
+ std::enable_if_t<std::is_constructible<T, const U&>::value &&
+ !internal::IsConvertibleFromOptional<T, U>::value &&
+ std::is_convertible<const U&, T>::value,
+ bool> = false>
+ Optional(const Optional<U>& other) : internal::OptionalBase<T>(other) {}
+
+ template <
+ typename U,
+ std::enable_if_t<std::is_constructible<T, const U&>::value &&
+ !internal::IsConvertibleFromOptional<T, U>::value &&
+ !std::is_convertible<const U&, T>::value,
+ bool> = false>
+ explicit Optional(const Optional<U>& other)
+ : internal::OptionalBase<T>(other) {}
+
+ // Converting move constructor. Similar to converting copy constructor,
+ // declaring two (explicit and non-explicit) constructors.
+ template <
+ typename U,
+ std::enable_if_t<std::is_constructible<T, U&&>::value &&
+ !internal::IsConvertibleFromOptional<T, U>::value &&
+ std::is_convertible<U&&, T>::value,
+ bool> = false>
+ Optional(Optional<U>&& other) : internal::OptionalBase<T>(std::move(other)) {}
+
+ template <
+ typename U,
+ std::enable_if_t<std::is_constructible<T, U&&>::value &&
+ !internal::IsConvertibleFromOptional<T, U>::value &&
+ !std::is_convertible<U&&, T>::value,
+ bool> = false>
+ explicit Optional(Optional<U>&& other)
+ : internal::OptionalBase<T>(std::move(other)) {}
- // TODO(alshabalin): Can't use 'constexpr' with std::forward until C++14.
template <class... Args>
- explicit Optional(base::in_place_t, Args&&... args)
- : storage_(base::in_place, std::forward<Args>(args)...) {}
+ constexpr explicit Optional(in_place_t, Args&&... args)
+ : internal::OptionalBase<T>(in_place, std::forward<Args>(args)...) {}
+
+ template <
+ class U,
+ class... Args,
+ class = std::enable_if_t<std::is_constructible<value_type,
+ std::initializer_list<U>&,
+ Args...>::value>>
+ constexpr explicit Optional(in_place_t,
+ std::initializer_list<U> il,
+ Args&&... args)
+ : internal::OptionalBase<T>(in_place, il, std::forward<Args>(args)...) {}
+
+ // Forward value constructor. Similar to converting constructors,
+ // conditionally explicit.
+ template <
+ typename U = value_type,
+ std::enable_if_t<
+ std::is_constructible<T, U&&>::value &&
+ !std::is_same<internal::RemoveCvRefT<U>, in_place_t>::value &&
+ !std::is_same<internal::RemoveCvRefT<U>, Optional<T>>::value &&
+ std::is_convertible<U&&, T>::value,
+ bool> = false>
+ constexpr Optional(U&& value)
+ : internal::OptionalBase<T>(in_place, std::forward<U>(value)) {}
+
+ template <
+ typename U = value_type,
+ std::enable_if_t<
+ std::is_constructible<T, U&&>::value &&
+ !std::is_same<internal::RemoveCvRefT<U>, in_place_t>::value &&
+ !std::is_same<internal::RemoveCvRefT<U>, Optional<T>>::value &&
+ !std::is_convertible<U&&, T>::value,
+ bool> = false>
+ constexpr explicit Optional(U&& value)
+ : internal::OptionalBase<T>(in_place, std::forward<U>(value)) {}
~Optional() = default;
- Optional& operator=(base::nullopt_t) {
+ // Defer copy-/move- assign operator implementation to OptionalBase.
+ Optional& operator=(const Optional& other) = default;
+ Optional& operator=(Optional&& other) noexcept(
+ std::is_nothrow_move_assignable<T>::value&&
+ std::is_nothrow_move_constructible<T>::value) = default;
+
+ Optional& operator=(nullopt_t) {
FreeIfNeeded();
return *this;
}
- Optional& operator=(const Optional& other) {
- if (other.storage_.is_null_) {
- FreeIfNeeded();
- return *this;
- }
-
- InitOrAssign(other.value());
+ // Perfect-forwarded assignment.
+ template <typename U>
+ std::enable_if_t<
+ !std::is_same<internal::RemoveCvRefT<U>, Optional<T>>::value &&
+ std::is_constructible<T, U>::value &&
+ std::is_assignable<T&, U>::value &&
+ (!std::is_scalar<T>::value ||
+ !std::is_same<std::decay_t<U>, T>::value),
+ Optional&>
+ operator=(U&& value) {
+ InitOrAssign(std::forward<U>(value));
return *this;
}
- Optional& operator=(Optional&& other) {
- if (other.storage_.is_null_) {
- FreeIfNeeded();
- return *this;
- }
-
- InitOrAssign(std::move(other.value()));
+ // Copy assign the state of other.
+ template <typename U>
+ std::enable_if_t<!internal::IsAssignableFromOptional<T, U>::value &&
+ std::is_constructible<T, const U&>::value &&
+ std::is_assignable<T&, const U&>::value,
+ Optional&>
+ operator=(const Optional<U>& other) {
+ CopyAssign(other);
return *this;
}
- template <class U>
- typename std::enable_if<std::is_same<std::decay<U>, T>::value,
- Optional&>::type
- operator=(U&& value) {
- InitOrAssign(std::forward<U>(value));
+ // Move assign the state of other.
+ template <typename U>
+ std::enable_if_t<!internal::IsAssignableFromOptional<T, U>::value &&
+ std::is_constructible<T, U>::value &&
+ std::is_assignable<T&, U>::value,
+ Optional&>
+ operator=(Optional<U>&& other) {
+ MoveAssign(std::move(other));
return *this;
}
- // TODO(mlamouri): can't use 'constexpr' with DCHECK.
- const T* operator->() const {
- DCHECK(!storage_.is_null_);
- return &value();
+ constexpr const T* operator->() const {
+ DCHECK(storage_.is_populated_);
+ return &storage_.value_;
}
- // TODO(mlamouri): using 'constexpr' here breaks compiler that assume it was
- // meant to be 'constexpr const'.
- T* operator->() {
- DCHECK(!storage_.is_null_);
- return &value();
+ constexpr T* operator->() {
+ DCHECK(storage_.is_populated_);
+ return &storage_.value_;
}
- constexpr const T& operator*() const& { return value(); }
+ constexpr const T& operator*() const & {
+ DCHECK(storage_.is_populated_);
+ return storage_.value_;
+ }
- // TODO(mlamouri): using 'constexpr' here breaks compiler that assume it was
- // meant to be 'constexpr const'.
- T& operator*() & { return value(); }
+ constexpr T& operator*() & {
+ DCHECK(storage_.is_populated_);
+ return storage_.value_;
+ }
- constexpr const T&& operator*() const&& { return std::move(value()); }
+ constexpr const T&& operator*() const && {
+ DCHECK(storage_.is_populated_);
+ return std::move(storage_.value_);
+ }
- // TODO(mlamouri): using 'constexpr' here breaks compiler that assume it was
- // meant to be 'constexpr const'.
- T&& operator*() && { return std::move(value()); }
+ constexpr T&& operator*() && {
+ DCHECK(storage_.is_populated_);
+ return std::move(storage_.value_);
+ }
- constexpr explicit operator bool() const { return !storage_.is_null_; }
+ constexpr explicit operator bool() const { return storage_.is_populated_; }
- constexpr bool has_value() const { return !storage_.is_null_; }
+ constexpr bool has_value() const { return storage_.is_populated_; }
- // TODO(mlamouri): using 'constexpr' here breaks compiler that assume it was
- // meant to be 'constexpr const'.
- T& value() & {
- DCHECK(!storage_.is_null_);
+ constexpr T& value() & {
+ CHECK(storage_.is_populated_);
return storage_.value_;
}
- // TODO(mlamouri): can't use 'constexpr' with DCHECK.
- const T& value() const& {
- DCHECK(!storage_.is_null_);
+ constexpr const T& value() const & {
+ CHECK(storage_.is_populated_);
return storage_.value_;
}
- // TODO(mlamouri): using 'constexpr' here breaks compiler that assume it was
- // meant to be 'constexpr const'.
- T&& value() && {
- DCHECK(!storage_.is_null_);
+ constexpr T&& value() && {
+ CHECK(storage_.is_populated_);
return std::move(storage_.value_);
}
- // TODO(mlamouri): can't use 'constexpr' with DCHECK.
- const T&& value() const&& {
- DCHECK(!storage_.is_null_);
+ constexpr const T&& value() const && {
+ CHECK(storage_.is_populated_);
return std::move(storage_.value_);
}
@@ -241,252 +635,274 @@ class Optional {
// "T must be copy constructible");
static_assert(std::is_convertible<U, T>::value,
"U must be convertible to T");
- return storage_.is_null_ ? static_cast<T>(std::forward<U>(default_value))
- : value();
+ return storage_.is_populated_
+ ? storage_.value_
+ : static_cast<T>(std::forward<U>(default_value));
}
template <class U>
- T value_or(U&& default_value) && {
+ constexpr T value_or(U&& default_value) && {
// TODO(mlamouri): add the following assert when possible:
// static_assert(std::is_move_constructible<T>::value,
// "T must be move constructible");
static_assert(std::is_convertible<U, T>::value,
"U must be convertible to T");
- return storage_.is_null_ ? static_cast<T>(std::forward<U>(default_value))
- : std::move(value());
+ return storage_.is_populated_
+ ? std::move(storage_.value_)
+ : static_cast<T>(std::forward<U>(default_value));
}
void swap(Optional& other) {
- if (storage_.is_null_ && other.storage_.is_null_)
+ if (!storage_.is_populated_ && !other.storage_.is_populated_)
return;
- if (storage_.is_null_ != other.storage_.is_null_) {
- if (storage_.is_null_) {
- Init(std::move(other.storage_.value_));
- other.FreeIfNeeded();
- } else {
- other.Init(std::move(storage_.value_));
+ if (storage_.is_populated_ != other.storage_.is_populated_) {
+ if (storage_.is_populated_) {
+ other.storage_.Init(std::move(storage_.value_));
FreeIfNeeded();
+ } else {
+ storage_.Init(std::move(other.storage_.value_));
+ other.FreeIfNeeded();
}
return;
}
- DCHECK(!storage_.is_null_ && !other.storage_.is_null_);
+ DCHECK(storage_.is_populated_ && other.storage_.is_populated_);
using std::swap;
swap(**this, *other);
}
- void reset() {
- FreeIfNeeded();
- }
+ void reset() { FreeIfNeeded(); }
template <class... Args>
- void emplace(Args&&... args) {
+ T& emplace(Args&&... args) {
FreeIfNeeded();
- Init(std::forward<Args>(args)...);
- }
-
- private:
- void Init(const T& value) {
- DCHECK(storage_.is_null_);
- new (&storage_.value_) T(value);
- storage_.is_null_ = false;
- }
-
- void Init(T&& value) {
- DCHECK(storage_.is_null_);
- new (&storage_.value_) T(std::move(value));
- storage_.is_null_ = false;
- }
-
- template <class... Args>
- void Init(Args&&... args) {
- DCHECK(storage_.is_null_);
- new (&storage_.value_) T(std::forward<Args>(args)...);
- storage_.is_null_ = false;
- }
-
- void InitOrAssign(const T& value) {
- if (storage_.is_null_)
- Init(value);
- else
- storage_.value_ = value;
- }
-
- void InitOrAssign(T&& value) {
- if (storage_.is_null_)
- Init(std::move(value));
- else
- storage_.value_ = std::move(value);
+ storage_.Init(std::forward<Args>(args)...);
+ return storage_.value_;
}
- void FreeIfNeeded() {
- if (storage_.is_null_)
- return;
- storage_.value_.~T();
- storage_.is_null_ = true;
+ template <class U, class... Args>
+ std::enable_if_t<
+ std::is_constructible<T, std::initializer_list<U>&, Args&&...>::value,
+ T&>
+ emplace(std::initializer_list<U> il, Args&&... args) {
+ FreeIfNeeded();
+ storage_.Init(il, std::forward<Args>(args)...);
+ return storage_.value_;
}
- internal::OptionalStorage<T> storage_;
+ private:
+ // Accessing template base class's protected member needs explicit
+ // declaration to do so.
+ using internal::OptionalBase<T>::CopyAssign;
+ using internal::OptionalBase<T>::FreeIfNeeded;
+ using internal::OptionalBase<T>::InitOrAssign;
+ using internal::OptionalBase<T>::MoveAssign;
+ using internal::OptionalBase<T>::storage_;
};
-template <class T>
-constexpr bool operator==(const Optional<T>& lhs, const Optional<T>& rhs) {
- return !!lhs != !!rhs ? false : lhs == nullopt || (*lhs == *rhs);
-}
-
-template <class T>
-constexpr bool operator!=(const Optional<T>& lhs, const Optional<T>& rhs) {
- return !(lhs == rhs);
-}
-
-template <class T>
-constexpr bool operator<(const Optional<T>& lhs, const Optional<T>& rhs) {
- return rhs == nullopt ? false : (lhs == nullopt ? true : *lhs < *rhs);
-}
-
-template <class T>
-constexpr bool operator<=(const Optional<T>& lhs, const Optional<T>& rhs) {
- return !(rhs < lhs);
-}
-
-template <class T>
-constexpr bool operator>(const Optional<T>& lhs, const Optional<T>& rhs) {
- return rhs < lhs;
-}
-
-template <class T>
-constexpr bool operator>=(const Optional<T>& lhs, const Optional<T>& rhs) {
- return !(lhs < rhs);
-}
-
-template <class T>
-constexpr bool operator==(const Optional<T>& opt, base::nullopt_t) {
+// Here after defines comparation operators. The definition follows
+// http://en.cppreference.com/w/cpp/utility/optional/operator_cmp
+// while bool() casting is replaced by has_value() to meet the chromium
+// style guide.
+template <class T, class U>
+constexpr bool operator==(const Optional<T>& lhs, const Optional<U>& rhs) {
+ if (lhs.has_value() != rhs.has_value())
+ return false;
+ if (!lhs.has_value())
+ return true;
+ return *lhs == *rhs;
+}
+
+template <class T, class U>
+constexpr bool operator!=(const Optional<T>& lhs, const Optional<U>& rhs) {
+ if (lhs.has_value() != rhs.has_value())
+ return true;
+ if (!lhs.has_value())
+ return false;
+ return *lhs != *rhs;
+}
+
+template <class T, class U>
+constexpr bool operator<(const Optional<T>& lhs, const Optional<U>& rhs) {
+ if (!rhs.has_value())
+ return false;
+ if (!lhs.has_value())
+ return true;
+ return *lhs < *rhs;
+}
+
+template <class T, class U>
+constexpr bool operator<=(const Optional<T>& lhs, const Optional<U>& rhs) {
+ if (!lhs.has_value())
+ return true;
+ if (!rhs.has_value())
+ return false;
+ return *lhs <= *rhs;
+}
+
+template <class T, class U>
+constexpr bool operator>(const Optional<T>& lhs, const Optional<U>& rhs) {
+ if (!lhs.has_value())
+ return false;
+ if (!rhs.has_value())
+ return true;
+ return *lhs > *rhs;
+}
+
+template <class T, class U>
+constexpr bool operator>=(const Optional<T>& lhs, const Optional<U>& rhs) {
+ if (!rhs.has_value())
+ return true;
+ if (!lhs.has_value())
+ return false;
+ return *lhs >= *rhs;
+}
+
+template <class T>
+constexpr bool operator==(const Optional<T>& opt, nullopt_t) {
return !opt;
}
template <class T>
-constexpr bool operator==(base::nullopt_t, const Optional<T>& opt) {
+constexpr bool operator==(nullopt_t, const Optional<T>& opt) {
return !opt;
}
template <class T>
-constexpr bool operator!=(const Optional<T>& opt, base::nullopt_t) {
- return !!opt;
+constexpr bool operator!=(const Optional<T>& opt, nullopt_t) {
+ return opt.has_value();
}
template <class T>
-constexpr bool operator!=(base::nullopt_t, const Optional<T>& opt) {
- return !!opt;
+constexpr bool operator!=(nullopt_t, const Optional<T>& opt) {
+ return opt.has_value();
}
template <class T>
-constexpr bool operator<(const Optional<T>& opt, base::nullopt_t) {
+constexpr bool operator<(const Optional<T>& opt, nullopt_t) {
return false;
}
template <class T>
-constexpr bool operator<(base::nullopt_t, const Optional<T>& opt) {
- return !!opt;
+constexpr bool operator<(nullopt_t, const Optional<T>& opt) {
+ return opt.has_value();
}
template <class T>
-constexpr bool operator<=(const Optional<T>& opt, base::nullopt_t) {
+constexpr bool operator<=(const Optional<T>& opt, nullopt_t) {
return !opt;
}
template <class T>
-constexpr bool operator<=(base::nullopt_t, const Optional<T>& opt) {
+constexpr bool operator<=(nullopt_t, const Optional<T>& opt) {
return true;
}
template <class T>
-constexpr bool operator>(const Optional<T>& opt, base::nullopt_t) {
- return !!opt;
+constexpr bool operator>(const Optional<T>& opt, nullopt_t) {
+ return opt.has_value();
}
template <class T>
-constexpr bool operator>(base::nullopt_t, const Optional<T>& opt) {
+constexpr bool operator>(nullopt_t, const Optional<T>& opt) {
return false;
}
template <class T>
-constexpr bool operator>=(const Optional<T>& opt, base::nullopt_t) {
+constexpr bool operator>=(const Optional<T>& opt, nullopt_t) {
return true;
}
template <class T>
-constexpr bool operator>=(base::nullopt_t, const Optional<T>& opt) {
+constexpr bool operator>=(nullopt_t, const Optional<T>& opt) {
return !opt;
}
-template <class T>
-constexpr bool operator==(const Optional<T>& opt, const T& value) {
- return opt != nullopt ? *opt == value : false;
+template <class T, class U>
+constexpr bool operator==(const Optional<T>& opt, const U& value) {
+ return opt.has_value() ? *opt == value : false;
}
-template <class T>
-constexpr bool operator==(const T& value, const Optional<T>& opt) {
- return opt == value;
+template <class T, class U>
+constexpr bool operator==(const U& value, const Optional<T>& opt) {
+ return opt.has_value() ? value == *opt : false;
}
-template <class T>
-constexpr bool operator!=(const Optional<T>& opt, const T& value) {
- return !(opt == value);
+template <class T, class U>
+constexpr bool operator!=(const Optional<T>& opt, const U& value) {
+ return opt.has_value() ? *opt != value : true;
}
-template <class T>
-constexpr bool operator!=(const T& value, const Optional<T>& opt) {
- return !(opt == value);
+template <class T, class U>
+constexpr bool operator!=(const U& value, const Optional<T>& opt) {
+ return opt.has_value() ? value != *opt : true;
}
-template <class T>
-constexpr bool operator<(const Optional<T>& opt, const T& value) {
- return opt != nullopt ? *opt < value : true;
+template <class T, class U>
+constexpr bool operator<(const Optional<T>& opt, const U& value) {
+ return opt.has_value() ? *opt < value : true;
}
-template <class T>
-constexpr bool operator<(const T& value, const Optional<T>& opt) {
- return opt != nullopt ? value < *opt : false;
+template <class T, class U>
+constexpr bool operator<(const U& value, const Optional<T>& opt) {
+ return opt.has_value() ? value < *opt : false;
}
-template <class T>
-constexpr bool operator<=(const Optional<T>& opt, const T& value) {
- return !(opt > value);
+template <class T, class U>
+constexpr bool operator<=(const Optional<T>& opt, const U& value) {
+ return opt.has_value() ? *opt <= value : true;
}
-template <class T>
-constexpr bool operator<=(const T& value, const Optional<T>& opt) {
- return !(value > opt);
+template <class T, class U>
+constexpr bool operator<=(const U& value, const Optional<T>& opt) {
+ return opt.has_value() ? value <= *opt : false;
}
-template <class T>
-constexpr bool operator>(const Optional<T>& opt, const T& value) {
- return value < opt;
+template <class T, class U>
+constexpr bool operator>(const Optional<T>& opt, const U& value) {
+ return opt.has_value() ? *opt > value : false;
}
-template <class T>
-constexpr bool operator>(const T& value, const Optional<T>& opt) {
- return opt < value;
+template <class T, class U>
+constexpr bool operator>(const U& value, const Optional<T>& opt) {
+ return opt.has_value() ? value > *opt : true;
}
-template <class T>
-constexpr bool operator>=(const Optional<T>& opt, const T& value) {
- return !(opt < value);
+template <class T, class U>
+constexpr bool operator>=(const Optional<T>& opt, const U& value) {
+ return opt.has_value() ? *opt >= value : false;
}
-template <class T>
-constexpr bool operator>=(const T& value, const Optional<T>& opt) {
- return !(value < opt);
+template <class T, class U>
+constexpr bool operator>=(const U& value, const Optional<T>& opt) {
+ return opt.has_value() ? value >= *opt : true;
}
template <class T>
-constexpr Optional<typename std::decay<T>::type> make_optional(T&& value) {
- return Optional<typename std::decay<T>::type>(std::forward<T>(value));
+constexpr Optional<std::decay_t<T>> make_optional(T&& value) {
+ return Optional<std::decay_t<T>>(std::forward<T>(value));
+}
+
+template <class T, class... Args>
+constexpr Optional<T> make_optional(Args&&... args) {
+ return Optional<T>(in_place, std::forward<Args>(args)...);
+}
+
+template <class T, class U, class... Args>
+constexpr Optional<T> make_optional(std::initializer_list<U> il,
+ Args&&... args) {
+ return Optional<T>(in_place, il, std::forward<Args>(args)...);
}
+// Partial specialization for a function template is not allowed. Also, it is
+// not allowed to add overload function to std namespace, while it is allowed
+// to specialize the template in std. Thus, swap() (kind of) overloading is
+// defined in base namespace, instead.
template <class T>
-void swap(Optional<T>& lhs, Optional<T>& rhs) {
+std::enable_if_t<std::is_move_constructible<T>::value &&
+ internal::IsSwappable<T>::value>
+swap(Optional<T>& lhs, Optional<T>& rhs) {
lhs.swap(rhs);
}
diff --git a/base/optional_unittest.cc b/base/optional_unittest.cc
index 83025e8bda..7bdb46b761 100644
--- a/base/optional_unittest.cc
+++ b/base/optional_unittest.cc
@@ -4,10 +4,16 @@
#include "base/optional.h"
+#include <memory>
#include <set>
+#include <string>
+#include <vector>
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
+using ::testing::ElementsAre;
+
namespace base {
namespace {
@@ -33,12 +39,16 @@ class TestObject {
: foo_(foo), bar_(bar), state_(State::VALUE_CONSTRUCTED) {}
TestObject(const TestObject& other)
- : foo_(other.foo_), bar_(other.bar_), state_(State::COPY_CONSTRUCTED) {}
+ : foo_(other.foo_),
+ bar_(other.bar_),
+ state_(State::COPY_CONSTRUCTED),
+ move_ctors_count_(other.move_ctors_count_) {}
TestObject(TestObject&& other)
: foo_(std::move(other.foo_)),
bar_(std::move(other.bar_)),
- state_(State::MOVE_CONSTRUCTED) {
+ state_(State::MOVE_CONSTRUCTED),
+ move_ctors_count_(other.move_ctors_count_ + 1) {
other.state_ = State::MOVED_FROM;
}
@@ -46,6 +56,7 @@ class TestObject {
foo_ = other.foo_;
bar_ = other.bar_;
state_ = State::COPY_ASSIGNED;
+ move_ctors_count_ = other.move_ctors_count_;
return *this;
}
@@ -53,6 +64,7 @@ class TestObject {
foo_ = other.foo_;
bar_ = other.bar_;
state_ = State::MOVE_ASSIGNED;
+ move_ctors_count_ = other.move_ctors_count_;
other.state_ = State::MOVED_FROM;
return *this;
}
@@ -61,21 +73,26 @@ class TestObject {
using std::swap;
swap(foo_, other->foo_);
swap(bar_, other->bar_);
+ swap(move_ctors_count_, other->move_ctors_count_);
state_ = State::SWAPPED;
other->state_ = State::SWAPPED;
}
bool operator==(const TestObject& other) const {
- return foo_ == other.foo_ && bar_ == other.bar_;
+ return std::tie(foo_, bar_) == std::tie(other.foo_, other.bar_);
}
+ bool operator!=(const TestObject& other) const { return !(*this == other); }
+
int foo() const { return foo_; }
State state() const { return state_; }
+ int move_ctors_count() const { return move_ctors_count_; }
private:
int foo_;
double bar_;
State state_;
+ int move_ctors_count_ = 0;
};
// Implementing Swappable concept.
@@ -87,15 +104,85 @@ class NonTriviallyDestructible {
~NonTriviallyDestructible() {}
};
+class DeletedDefaultConstructor {
+ public:
+ DeletedDefaultConstructor() = delete;
+ DeletedDefaultConstructor(int foo) : foo_(foo) {}
+
+ int foo() const { return foo_; }
+
+ private:
+ int foo_;
+};
+
+class DeletedCopy {
+ public:
+ explicit DeletedCopy(int foo) : foo_(foo) {}
+ DeletedCopy(const DeletedCopy&) = delete;
+ DeletedCopy(DeletedCopy&&) = default;
+
+ DeletedCopy& operator=(const DeletedCopy&) = delete;
+ DeletedCopy& operator=(DeletedCopy&&) = default;
+
+ int foo() const { return foo_; }
+
+ private:
+ int foo_;
+};
+
+class DeletedMove {
+ public:
+ explicit DeletedMove(int foo) : foo_(foo) {}
+ DeletedMove(const DeletedMove&) = default;
+ DeletedMove(DeletedMove&&) = delete;
+
+ DeletedMove& operator=(const DeletedMove&) = default;
+ DeletedMove& operator=(DeletedMove&&) = delete;
+
+ int foo() const { return foo_; }
+
+ private:
+ int foo_;
+};
+
+class NonTriviallyDestructibleDeletedCopyConstructor {
+ public:
+ explicit NonTriviallyDestructibleDeletedCopyConstructor(int foo)
+ : foo_(foo) {}
+ NonTriviallyDestructibleDeletedCopyConstructor(
+ const NonTriviallyDestructibleDeletedCopyConstructor&) = delete;
+ NonTriviallyDestructibleDeletedCopyConstructor(
+ NonTriviallyDestructibleDeletedCopyConstructor&&) = default;
+
+ ~NonTriviallyDestructibleDeletedCopyConstructor() {}
+
+ int foo() const { return foo_; }
+
+ private:
+ int foo_;
+};
+
+class DeleteNewOperators {
+ public:
+ void* operator new(size_t) = delete;
+ void* operator new(size_t, void*) = delete;
+ void* operator new[](size_t) = delete;
+ void* operator new[](size_t, void*) = delete;
+};
+
} // anonymous namespace
-static_assert(is_trivially_destructible<Optional<int>>::value,
+static_assert(std::is_trivially_destructible<Optional<int>>::value,
"OptionalIsTriviallyDestructible");
static_assert(
- !is_trivially_destructible<Optional<NonTriviallyDestructible>>::value,
+ !std::is_trivially_destructible<Optional<NonTriviallyDestructible>>::value,
"OptionalIsTriviallyDestructible");
+static_assert(sizeof(Optional<int>) == sizeof(internal::OptionalBase<int>),
+ "internal::{Copy,Move}{Constructible,Assignable} structs "
+ "should be 0-sized");
+
TEST(OptionalTest, DefaultConstructor) {
{
constexpr Optional<float> o;
@@ -115,8 +202,8 @@ TEST(OptionalTest, DefaultConstructor) {
TEST(OptionalTest, CopyConstructor) {
{
- Optional<float> first(0.1f);
- Optional<float> other(first);
+ constexpr Optional<float> first(0.1f);
+ constexpr Optional<float> other(first);
EXPECT_TRUE(other);
EXPECT_EQ(other.value(), 0.1f);
@@ -133,6 +220,15 @@ TEST(OptionalTest, CopyConstructor) {
}
{
+ const Optional<std::string> first("foo");
+ Optional<std::string> other(first);
+
+ EXPECT_TRUE(other);
+ EXPECT_EQ(other.value(), "foo");
+ EXPECT_EQ(first, other);
+ }
+
+ {
Optional<TestObject> first(TestObject(3, 0.1));
Optional<TestObject> other(first);
@@ -171,40 +267,82 @@ TEST(OptionalTest, ValueConstructor) {
TEST(OptionalTest, MoveConstructor) {
{
- Optional<float> first(0.1f);
- Optional<float> second(std::move(first));
+ constexpr Optional<float> first(0.1f);
+ constexpr Optional<float> second(std::move(first));
- EXPECT_TRUE(second);
+ EXPECT_TRUE(second.has_value());
EXPECT_EQ(second.value(), 0.1f);
- EXPECT_TRUE(first);
+ EXPECT_TRUE(first.has_value());
}
{
Optional<std::string> first("foo");
Optional<std::string> second(std::move(first));
- EXPECT_TRUE(second);
+ EXPECT_TRUE(second.has_value());
EXPECT_EQ("foo", second.value());
- EXPECT_TRUE(first);
+ EXPECT_TRUE(first.has_value());
}
{
Optional<TestObject> first(TestObject(3, 0.1));
Optional<TestObject> second(std::move(first));
- EXPECT_TRUE(!!second);
+ EXPECT_TRUE(second.has_value());
EXPECT_EQ(TestObject::State::MOVE_CONSTRUCTED, second->state());
EXPECT_TRUE(TestObject(3, 0.1) == second.value());
- EXPECT_TRUE(!!first);
+ EXPECT_TRUE(first.has_value());
EXPECT_EQ(TestObject::State::MOVED_FROM, first->state());
}
+
+ // Even if copy constructor is deleted, move constructor needs to work.
+ // Note that it couldn't be constexpr.
+ {
+ Optional<DeletedCopy> first(in_place, 42);
+ Optional<DeletedCopy> second(std::move(first));
+
+ EXPECT_TRUE(second.has_value());
+ EXPECT_EQ(42, second->foo());
+
+ EXPECT_TRUE(first.has_value());
+ }
+
+ {
+ Optional<DeletedMove> first(in_place, 42);
+ Optional<DeletedMove> second(std::move(first));
+
+ EXPECT_TRUE(second.has_value());
+ EXPECT_EQ(42, second->foo());
+
+ EXPECT_TRUE(first.has_value());
+ }
+
+ {
+ Optional<NonTriviallyDestructibleDeletedCopyConstructor> first(in_place,
+ 42);
+ Optional<NonTriviallyDestructibleDeletedCopyConstructor> second(
+ std::move(first));
+
+ EXPECT_TRUE(second.has_value());
+ EXPECT_EQ(42, second->foo());
+
+ EXPECT_TRUE(first.has_value());
+ }
}
TEST(OptionalTest, MoveValueConstructor) {
{
+ constexpr float value = 0.1f;
+ constexpr Optional<float> o(std::move(value));
+
+ EXPECT_TRUE(o);
+ EXPECT_EQ(0.1f, o.value());
+ }
+
+ {
float value = 0.1f;
Optional<float> o(std::move(value));
@@ -230,8 +368,73 @@ TEST(OptionalTest, MoveValueConstructor) {
}
}
+TEST(OptionalTest, ConvertingCopyConstructor) {
+ {
+ Optional<int> first(1);
+ Optional<double> second(first);
+ EXPECT_TRUE(second.has_value());
+ EXPECT_EQ(1.0, second.value());
+ }
+
+ // Make sure explicit is not marked for convertible case.
+ {
+ Optional<int> o(1);
+ ignore_result<Optional<double>>(o);
+ }
+}
+
+TEST(OptionalTest, ConvertingMoveConstructor) {
+ {
+ Optional<int> first(1);
+ Optional<double> second(std::move(first));
+ EXPECT_TRUE(second.has_value());
+ EXPECT_EQ(1.0, second.value());
+ }
+
+ // Make sure explicit is not marked for convertible case.
+ {
+ Optional<int> o(1);
+ ignore_result<Optional<double>>(std::move(o));
+ }
+
+ {
+ class Test1 {
+ public:
+ explicit Test1(int foo) : foo_(foo) {}
+
+ int foo() const { return foo_; }
+
+ private:
+ int foo_;
+ };
+
+ // Not copyable but convertible from Test1.
+ class Test2 {
+ public:
+ Test2(const Test2&) = delete;
+ explicit Test2(Test1&& other) : bar_(other.foo()) {}
+
+ double bar() const { return bar_; }
+
+ private:
+ double bar_;
+ };
+
+ Optional<Test1> first(in_place, 42);
+ Optional<Test2> second(std::move(first));
+ EXPECT_TRUE(second.has_value());
+ EXPECT_EQ(42.0, second->bar());
+ }
+}
+
TEST(OptionalTest, ConstructorForwardArguments) {
{
+ constexpr Optional<float> a(base::in_place, 0.1f);
+ EXPECT_TRUE(a);
+ EXPECT_EQ(0.1f, a.value());
+ }
+
+ {
Optional<float> a(base::in_place, 0.1f);
EXPECT_TRUE(a);
EXPECT_EQ(0.1f, a.value());
@@ -250,6 +453,99 @@ TEST(OptionalTest, ConstructorForwardArguments) {
}
}
+TEST(OptionalTest, ConstructorForwardInitListAndArguments) {
+ {
+ Optional<std::vector<int>> opt(in_place, {3, 1});
+ EXPECT_TRUE(opt);
+ EXPECT_THAT(*opt, ElementsAre(3, 1));
+ EXPECT_EQ(2u, opt->size());
+ }
+
+ {
+ Optional<std::vector<int>> opt(in_place, {3, 1}, std::allocator<int>());
+ EXPECT_TRUE(opt);
+ EXPECT_THAT(*opt, ElementsAre(3, 1));
+ EXPECT_EQ(2u, opt->size());
+ }
+}
+
+TEST(OptionalTest, ForwardConstructor) {
+ {
+ Optional<double> a(1);
+ EXPECT_TRUE(a.has_value());
+ EXPECT_EQ(1.0, a.value());
+ }
+
+ // Test that default type of 'U' is value_type.
+ {
+ struct TestData {
+ int a;
+ double b;
+ bool c;
+ };
+
+ Optional<TestData> a({1, 2.0, true});
+ EXPECT_TRUE(a.has_value());
+ EXPECT_EQ(1, a->a);
+ EXPECT_EQ(2.0, a->b);
+ EXPECT_TRUE(a->c);
+ }
+
+ // If T has a constructor with a param Optional<U>, and another ctor with a
+ // param U, then T(Optional<U>) should be used for Optional<T>(Optional<U>)
+ // constructor.
+ {
+ enum class ParamType {
+ DEFAULT_CONSTRUCTED,
+ COPY_CONSTRUCTED,
+ MOVE_CONSTRUCTED,
+ INT,
+ IN_PLACE,
+ OPTIONAL_INT,
+ };
+ struct Test {
+ Test() : param_type(ParamType::DEFAULT_CONSTRUCTED) {}
+ Test(const Test& param) : param_type(ParamType::COPY_CONSTRUCTED) {}
+ Test(Test&& param) : param_type(ParamType::MOVE_CONSTRUCTED) {}
+ explicit Test(int param) : param_type(ParamType::INT) {}
+ explicit Test(in_place_t param) : param_type(ParamType::IN_PLACE) {}
+ explicit Test(Optional<int> param)
+ : param_type(ParamType::OPTIONAL_INT) {}
+
+ ParamType param_type;
+ };
+
+ // Overload resolution with copy-conversion constructor.
+ {
+ const Optional<int> arg(in_place, 1);
+ Optional<Test> testee(arg);
+ EXPECT_EQ(ParamType::OPTIONAL_INT, testee->param_type);
+ }
+
+ // Overload resolution with move conversion constructor.
+ {
+ Optional<Test> testee(Optional<int>(in_place, 1));
+ EXPECT_EQ(ParamType::OPTIONAL_INT, testee->param_type);
+ }
+
+ // Default constructor should be used.
+ {
+ Optional<Test> testee(in_place);
+ EXPECT_EQ(ParamType::DEFAULT_CONSTRUCTED, testee->param_type);
+ }
+ }
+
+ {
+ struct Test {
+ Test(int a) {} // NOLINT(runtime/explicit)
+ };
+ // If T is convertible from U, it is not marked as explicit.
+ static_assert(std::is_convertible<int, Test>::value,
+ "Int should be convertible to Test.");
+ ([](Optional<Test> param) {})(1);
+ }
+}
+
TEST(OptionalTest, NulloptConstructor) {
constexpr Optional<int> a(base::nullopt);
EXPECT_FALSE(a);
@@ -337,6 +633,57 @@ TEST(OptionalTest, AssignObject) {
EXPECT_TRUE(a.value() == TestObject(3, 0.1));
EXPECT_TRUE(a == b);
}
+
+ {
+ Optional<DeletedMove> a(in_place, 42);
+ Optional<DeletedMove> b;
+ b = a;
+
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(a->foo(), b->foo());
+ }
+
+ {
+ Optional<DeletedMove> a(in_place, 42);
+ Optional<DeletedMove> b(in_place, 1);
+ b = a;
+
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(a->foo(), b->foo());
+ }
+
+ // Converting assignment.
+ {
+ Optional<int> a(in_place, 1);
+ Optional<double> b;
+ b = a;
+
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(1, a.value());
+ EXPECT_EQ(1.0, b.value());
+ }
+
+ {
+ Optional<int> a(in_place, 42);
+ Optional<double> b(in_place, 1);
+ b = a;
+
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(42, a.value());
+ EXPECT_EQ(42.0, b.value());
+ }
+
+ {
+ Optional<int> a;
+ Optional<double> b(in_place, 1);
+ b = a;
+ EXPECT_FALSE(!!a);
+ EXPECT_FALSE(!!b);
+ }
}
TEST(OptionalTest, AssignObject_rvalue) {
@@ -385,6 +732,56 @@ TEST(OptionalTest, AssignObject_rvalue) {
EXPECT_EQ(TestObject::State::MOVE_ASSIGNED, a->state());
EXPECT_EQ(TestObject::State::MOVED_FROM, b->state());
}
+
+ {
+ Optional<DeletedMove> a(in_place, 42);
+ Optional<DeletedMove> b;
+ b = std::move(a);
+
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(42, b->foo());
+ }
+
+ {
+ Optional<DeletedMove> a(in_place, 42);
+ Optional<DeletedMove> b(in_place, 1);
+ b = std::move(a);
+
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(42, b->foo());
+ }
+
+ // Converting assignment.
+ {
+ Optional<int> a(in_place, 1);
+ Optional<double> b;
+ b = std::move(a);
+
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(1.0, b.value());
+ }
+
+ {
+ Optional<int> a(in_place, 42);
+ Optional<double> b(in_place, 1);
+ b = std::move(a);
+
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(42.0, b.value());
+ }
+
+ {
+ Optional<int> a;
+ Optional<double> b(in_place, 1);
+ b = std::move(a);
+
+ EXPECT_FALSE(!!a);
+ EXPECT_FALSE(!!b);
+ }
}
TEST(OptionalTest, AssignNull) {
@@ -413,6 +810,190 @@ TEST(OptionalTest, AssignNull) {
}
}
+TEST(OptionalTest, AssignOverload) {
+ struct Test1 {
+ enum class State {
+ CONSTRUCTED,
+ MOVED,
+ };
+ State state = State::CONSTRUCTED;
+ };
+
+ // Here, Optional<Test2> can be assigned from Optioanl<Test1>.
+ // In case of move, marks MOVED to Test1 instance.
+ struct Test2 {
+ enum class State {
+ DEFAULT_CONSTRUCTED,
+ COPY_CONSTRUCTED_FROM_TEST1,
+ MOVE_CONSTRUCTED_FROM_TEST1,
+ COPY_ASSIGNED_FROM_TEST1,
+ MOVE_ASSIGNED_FROM_TEST1,
+ };
+
+ Test2() = default;
+ explicit Test2(const Test1& test1)
+ : state(State::COPY_CONSTRUCTED_FROM_TEST1) {}
+ explicit Test2(Test1&& test1) : state(State::MOVE_CONSTRUCTED_FROM_TEST1) {
+ test1.state = Test1::State::MOVED;
+ }
+ Test2& operator=(const Test1& test1) {
+ state = State::COPY_ASSIGNED_FROM_TEST1;
+ return *this;
+ }
+ Test2& operator=(Test1&& test1) {
+ state = State::MOVE_ASSIGNED_FROM_TEST1;
+ test1.state = Test1::State::MOVED;
+ return *this;
+ }
+
+ State state = State::DEFAULT_CONSTRUCTED;
+ };
+
+ {
+ Optional<Test1> a(in_place);
+ Optional<Test2> b;
+
+ b = a;
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(Test1::State::CONSTRUCTED, a->state);
+ EXPECT_EQ(Test2::State::COPY_CONSTRUCTED_FROM_TEST1, b->state);
+ }
+
+ {
+ Optional<Test1> a(in_place);
+ Optional<Test2> b(in_place);
+
+ b = a;
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(Test1::State::CONSTRUCTED, a->state);
+ EXPECT_EQ(Test2::State::COPY_ASSIGNED_FROM_TEST1, b->state);
+ }
+
+ {
+ Optional<Test1> a(in_place);
+ Optional<Test2> b;
+
+ b = std::move(a);
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(Test1::State::MOVED, a->state);
+ EXPECT_EQ(Test2::State::MOVE_CONSTRUCTED_FROM_TEST1, b->state);
+ }
+
+ {
+ Optional<Test1> a(in_place);
+ Optional<Test2> b(in_place);
+
+ b = std::move(a);
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(Test1::State::MOVED, a->state);
+ EXPECT_EQ(Test2::State::MOVE_ASSIGNED_FROM_TEST1, b->state);
+ }
+
+ // Similar to Test2, but Test3 also has copy/move ctor and assign operators
+ // from Optional<Test1>, too. In this case, for a = b where a is
+ // Optional<Test3> and b is Optional<Test1>,
+ // Optional<T>::operator=(U&&) where U is Optional<Test1> should be used
+ // rather than Optional<T>::operator=(Optional<U>&&) where U is Test1.
+ struct Test3 {
+ enum class State {
+ DEFAULT_CONSTRUCTED,
+ COPY_CONSTRUCTED_FROM_TEST1,
+ MOVE_CONSTRUCTED_FROM_TEST1,
+ COPY_CONSTRUCTED_FROM_OPTIONAL_TEST1,
+ MOVE_CONSTRUCTED_FROM_OPTIONAL_TEST1,
+ COPY_ASSIGNED_FROM_TEST1,
+ MOVE_ASSIGNED_FROM_TEST1,
+ COPY_ASSIGNED_FROM_OPTIONAL_TEST1,
+ MOVE_ASSIGNED_FROM_OPTIONAL_TEST1,
+ };
+
+ Test3() = default;
+ explicit Test3(const Test1& test1)
+ : state(State::COPY_CONSTRUCTED_FROM_TEST1) {}
+ explicit Test3(Test1&& test1) : state(State::MOVE_CONSTRUCTED_FROM_TEST1) {
+ test1.state = Test1::State::MOVED;
+ }
+ explicit Test3(const Optional<Test1>& test1)
+ : state(State::COPY_CONSTRUCTED_FROM_OPTIONAL_TEST1) {}
+ explicit Test3(Optional<Test1>&& test1)
+ : state(State::MOVE_CONSTRUCTED_FROM_OPTIONAL_TEST1) {
+ // In the following senarios, given |test1| should always have value.
+ DCHECK(test1.has_value());
+ test1->state = Test1::State::MOVED;
+ }
+ Test3& operator=(const Test1& test1) {
+ state = State::COPY_ASSIGNED_FROM_TEST1;
+ return *this;
+ }
+ Test3& operator=(Test1&& test1) {
+ state = State::MOVE_ASSIGNED_FROM_TEST1;
+ test1.state = Test1::State::MOVED;
+ return *this;
+ }
+ Test3& operator=(const Optional<Test1>& test1) {
+ state = State::COPY_ASSIGNED_FROM_OPTIONAL_TEST1;
+ return *this;
+ }
+ Test3& operator=(Optional<Test1>&& test1) {
+ state = State::MOVE_ASSIGNED_FROM_OPTIONAL_TEST1;
+ // In the following senarios, given |test1| should always have value.
+ DCHECK(test1.has_value());
+ test1->state = Test1::State::MOVED;
+ return *this;
+ }
+
+ State state = State::DEFAULT_CONSTRUCTED;
+ };
+
+ {
+ Optional<Test1> a(in_place);
+ Optional<Test3> b;
+
+ b = a;
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(Test1::State::CONSTRUCTED, a->state);
+ EXPECT_EQ(Test3::State::COPY_CONSTRUCTED_FROM_OPTIONAL_TEST1, b->state);
+ }
+
+ {
+ Optional<Test1> a(in_place);
+ Optional<Test3> b(in_place);
+
+ b = a;
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(Test1::State::CONSTRUCTED, a->state);
+ EXPECT_EQ(Test3::State::COPY_ASSIGNED_FROM_OPTIONAL_TEST1, b->state);
+ }
+
+ {
+ Optional<Test1> a(in_place);
+ Optional<Test3> b;
+
+ b = std::move(a);
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(Test1::State::MOVED, a->state);
+ EXPECT_EQ(Test3::State::MOVE_CONSTRUCTED_FROM_OPTIONAL_TEST1, b->state);
+ }
+
+ {
+ Optional<Test1> a(in_place);
+ Optional<Test3> b(in_place);
+
+ b = std::move(a);
+ EXPECT_TRUE(!!a);
+ EXPECT_TRUE(!!b);
+ EXPECT_EQ(Test1::State::MOVED, a->state);
+ EXPECT_EQ(Test3::State::MOVE_ASSIGNED_FROM_OPTIONAL_TEST1, b->state);
+ }
+}
+
TEST(OptionalTest, OperatorStar) {
{
Optional<float> a(0.1f);
@@ -460,6 +1041,18 @@ TEST(OptionalTest, ValueOr) {
EXPECT_EQ(0.0f, a.value_or(0.0f));
}
+ // value_or() can be constexpr.
+ {
+ constexpr Optional<int> a(in_place, 1);
+ constexpr int value = a.value_or(10);
+ EXPECT_EQ(1, value);
+ }
+ {
+ constexpr Optional<int> a;
+ constexpr int value = a.value_or(10);
+ EXPECT_EQ(10, value);
+ }
+
{
Optional<std::string> a;
EXPECT_EQ("bar", a.value_or("bar"));
@@ -532,7 +1125,7 @@ TEST(OptionalTest, Swap_bothValue) {
TEST(OptionalTest, Emplace) {
{
Optional<float> a(0.1f);
- a.emplace(0.3f);
+ EXPECT_EQ(0.3f, a.emplace(0.3f));
EXPECT_TRUE(a);
EXPECT_EQ(0.3f, a.value());
@@ -540,7 +1133,7 @@ TEST(OptionalTest, Emplace) {
{
Optional<std::string> a("foo");
- a.emplace("bar");
+ EXPECT_EQ("bar", a.emplace("bar"));
EXPECT_TRUE(a);
EXPECT_EQ("bar", a.value());
@@ -548,11 +1141,29 @@ TEST(OptionalTest, Emplace) {
{
Optional<TestObject> a(TestObject(0, 0.1));
- a.emplace(TestObject(1, 0.2));
+ EXPECT_EQ(TestObject(1, 0.2), a.emplace(TestObject(1, 0.2)));
EXPECT_TRUE(!!a);
EXPECT_TRUE(TestObject(1, 0.2) == a.value());
}
+
+ {
+ Optional<std::vector<int>> a;
+ auto& ref = a.emplace({2, 3});
+ static_assert(std::is_same<std::vector<int>&, decltype(ref)>::value, "");
+ EXPECT_TRUE(a);
+ EXPECT_THAT(*a, ElementsAre(2, 3));
+ EXPECT_EQ(&ref, &*a);
+ }
+
+ {
+ Optional<std::vector<int>> a;
+ auto& ref = a.emplace({4, 5}, std::allocator<int>());
+ static_assert(std::is_same<std::vector<int>&, decltype(ref)>::value, "");
+ EXPECT_TRUE(a);
+ EXPECT_THAT(*a, ElementsAre(4, 5));
+ EXPECT_EQ(&ref, &*a);
+ }
}
TEST(OptionalTest, Equals_TwoEmpty) {
@@ -583,6 +1194,13 @@ TEST(OptionalTest, Equals_TwoDifferent) {
EXPECT_FALSE(a == b);
}
+TEST(OptionalTest, Equals_DifferentType) {
+ Optional<int> a(0);
+ Optional<double> b(0);
+
+ EXPECT_TRUE(a == b);
+}
+
TEST(OptionalTest, NotEquals_TwoEmpty) {
Optional<int> a;
Optional<int> b;
@@ -611,6 +1229,13 @@ TEST(OptionalTest, NotEquals_TwoDifferent) {
EXPECT_TRUE(a != b);
}
+TEST(OptionalTest, NotEquals_DifferentType) {
+ Optional<int> a(0);
+ Optional<double> b(0.0);
+
+ EXPECT_FALSE(a != b);
+}
+
TEST(OptionalTest, Less_LeftEmpty) {
Optional<int> l;
Optional<int> r(1);
@@ -653,6 +1278,13 @@ TEST(OptionalTest, Less_BothValues) {
}
}
+TEST(OptionalTest, Less_DifferentType) {
+ Optional<int> l(1);
+ Optional<double> r(2.0);
+
+ EXPECT_TRUE(l < r);
+}
+
TEST(OptionalTest, LessEq_LeftEmpty) {
Optional<int> l;
Optional<int> r(1);
@@ -695,6 +1327,13 @@ TEST(OptionalTest, LessEq_BothValues) {
}
}
+TEST(OptionalTest, LessEq_DifferentType) {
+ Optional<int> l(1);
+ Optional<double> r(2.0);
+
+ EXPECT_TRUE(l <= r);
+}
+
TEST(OptionalTest, Greater_BothEmpty) {
Optional<int> l;
Optional<int> r;
@@ -737,6 +1376,13 @@ TEST(OptionalTest, Greater_BothValue) {
}
}
+TEST(OptionalTest, Greater_DifferentType) {
+ Optional<int> l(1);
+ Optional<double> r(2.0);
+
+ EXPECT_FALSE(l > r);
+}
+
TEST(OptionalTest, GreaterEq_BothEmpty) {
Optional<int> l;
Optional<int> r;
@@ -779,6 +1425,13 @@ TEST(OptionalTest, GreaterEq_BothValue) {
}
}
+TEST(OptionalTest, GreaterEq_DifferentType) {
+ Optional<int> l(1);
+ Optional<double> r(2.0);
+
+ EXPECT_FALSE(l >= r);
+}
+
TEST(OptionalTest, OptNullEq) {
{
Optional<int> opt;
@@ -927,6 +1580,11 @@ TEST(OptionalTest, ValueEq_NotEmpty) {
}
}
+TEST(OptionalTest, ValueEq_DifferentType) {
+ Optional<int> opt(0);
+ EXPECT_TRUE(opt == 0.0);
+}
+
TEST(OptionalTest, EqValue_Empty) {
Optional<int> opt;
EXPECT_FALSE(1 == opt);
@@ -943,6 +1601,11 @@ TEST(OptionalTest, EqValue_NotEmpty) {
}
}
+TEST(OptionalTest, EqValue_DifferentType) {
+ Optional<int> opt(0);
+ EXPECT_TRUE(0.0 == opt);
+}
+
TEST(OptionalTest, ValueNotEq_Empty) {
Optional<int> opt;
EXPECT_TRUE(opt != 1);
@@ -959,6 +1622,11 @@ TEST(OptionalTest, ValueNotEq_NotEmpty) {
}
}
+TEST(OPtionalTest, ValueNotEq_DifferentType) {
+ Optional<int> opt(0);
+ EXPECT_FALSE(opt != 0.0);
+}
+
TEST(OptionalTest, NotEqValue_Empty) {
Optional<int> opt;
EXPECT_TRUE(1 != opt);
@@ -975,6 +1643,11 @@ TEST(OptionalTest, NotEqValue_NotEmpty) {
}
}
+TEST(OptionalTest, NotEqValue_DifferentType) {
+ Optional<int> opt(0);
+ EXPECT_FALSE(0.0 != opt);
+}
+
TEST(OptionalTest, ValueLess_Empty) {
Optional<int> opt;
EXPECT_TRUE(opt < 1);
@@ -995,6 +1668,11 @@ TEST(OptionalTest, ValueLess_NotEmpty) {
}
}
+TEST(OPtionalTest, ValueLess_DifferentType) {
+ Optional<int> opt(0);
+ EXPECT_TRUE(opt < 1.0);
+}
+
TEST(OptionalTest, LessValue_Empty) {
Optional<int> opt;
EXPECT_FALSE(1 < opt);
@@ -1015,6 +1693,11 @@ TEST(OptionalTest, LessValue_NotEmpty) {
}
}
+TEST(OptionalTest, LessValue_DifferentType) {
+ Optional<int> opt(0);
+ EXPECT_FALSE(0.0 < opt);
+}
+
TEST(OptionalTest, ValueLessEq_Empty) {
Optional<int> opt;
EXPECT_TRUE(opt <= 1);
@@ -1035,6 +1718,11 @@ TEST(OptionalTest, ValueLessEq_NotEmpty) {
}
}
+TEST(OptionalTest, ValueLessEq_DifferentType) {
+ Optional<int> opt(0);
+ EXPECT_TRUE(opt <= 0.0);
+}
+
TEST(OptionalTest, LessEqValue_Empty) {
Optional<int> opt;
EXPECT_FALSE(1 <= opt);
@@ -1055,6 +1743,11 @@ TEST(OptionalTest, LessEqValue_NotEmpty) {
}
}
+TEST(OptionalTest, LessEqValue_DifferentType) {
+ Optional<int> opt(0);
+ EXPECT_TRUE(0.0 <= opt);
+}
+
TEST(OptionalTest, ValueGreater_Empty) {
Optional<int> opt;
EXPECT_FALSE(opt > 1);
@@ -1075,6 +1768,11 @@ TEST(OptionalTest, ValueGreater_NotEmpty) {
}
}
+TEST(OptionalTest, ValueGreater_DifferentType) {
+ Optional<int> opt(0);
+ EXPECT_FALSE(opt > 0.0);
+}
+
TEST(OptionalTest, GreaterValue_Empty) {
Optional<int> opt;
EXPECT_TRUE(1 > opt);
@@ -1095,6 +1793,11 @@ TEST(OptionalTest, GreaterValue_NotEmpty) {
}
}
+TEST(OptionalTest, GreaterValue_DifferentType) {
+ Optional<int> opt(0);
+ EXPECT_FALSE(0.0 > opt);
+}
+
TEST(OptionalTest, ValueGreaterEq_Empty) {
Optional<int> opt;
EXPECT_FALSE(opt >= 1);
@@ -1115,6 +1818,11 @@ TEST(OptionalTest, ValueGreaterEq_NotEmpty) {
}
}
+TEST(OptionalTest, ValueGreaterEq_DifferentType) {
+ Optional<int> opt(0);
+ EXPECT_TRUE(opt <= 0.0);
+}
+
TEST(OptionalTest, GreaterEqValue_Empty) {
Optional<int> opt;
EXPECT_TRUE(1 >= opt);
@@ -1135,6 +1843,11 @@ TEST(OptionalTest, GreaterEqValue_NotEmpty) {
}
}
+TEST(OptionalTest, GreaterEqValue_DifferentType) {
+ Optional<int> opt(0);
+ EXPECT_TRUE(0.0 >= opt);
+}
+
TEST(OptionalTest, NotEquals) {
{
Optional<float> a(0.1f);
@@ -1149,6 +1862,12 @@ TEST(OptionalTest, NotEquals) {
}
{
+ Optional<int> a(1);
+ Optional<double> b(2);
+ EXPECT_NE(a, b);
+ }
+
+ {
Optional<TestObject> a(TestObject(3, 0.1));
Optional<TestObject> b(TestObject(4, 1.0));
EXPECT_TRUE(a != b);
@@ -1180,34 +1899,34 @@ TEST(OptionalTest, NotEqualsNull) {
TEST(OptionalTest, MakeOptional) {
{
- Optional<float> o = base::make_optional(32.f);
+ Optional<float> o = make_optional(32.f);
EXPECT_TRUE(o);
EXPECT_EQ(32.f, *o);
float value = 3.f;
- o = base::make_optional(std::move(value));
+ o = make_optional(std::move(value));
EXPECT_TRUE(o);
EXPECT_EQ(3.f, *o);
}
{
- Optional<std::string> o = base::make_optional(std::string("foo"));
+ Optional<std::string> o = make_optional(std::string("foo"));
EXPECT_TRUE(o);
EXPECT_EQ("foo", *o);
std::string value = "bar";
- o = base::make_optional(std::move(value));
+ o = make_optional(std::move(value));
EXPECT_TRUE(o);
EXPECT_EQ(std::string("bar"), *o);
}
{
- Optional<TestObject> o = base::make_optional(TestObject(3, 0.1));
+ Optional<TestObject> o = make_optional(TestObject(3, 0.1));
EXPECT_TRUE(!!o);
EXPECT_TRUE(TestObject(3, 0.1) == *o);
TestObject value = TestObject(0, 0.42);
- o = base::make_optional(std::move(value));
+ o = make_optional(std::move(value));
EXPECT_TRUE(!!o);
EXPECT_TRUE(TestObject(0, 0.42) == *o);
EXPECT_EQ(TestObject::State::MOVED_FROM, value.state());
@@ -1216,6 +1935,31 @@ TEST(OptionalTest, MakeOptional) {
EXPECT_EQ(TestObject::State::MOVE_CONSTRUCTED,
base::make_optional(std::move(value))->state());
}
+
+ {
+ struct Test {
+ Test(int a, double b, bool c) : a(a), b(b), c(c) {}
+
+ int a;
+ double b;
+ bool c;
+ };
+
+ Optional<Test> o = make_optional<Test>(1, 2.0, true);
+ EXPECT_TRUE(!!o);
+ EXPECT_EQ(1, o->a);
+ EXPECT_EQ(2.0, o->b);
+ EXPECT_TRUE(o->c);
+ }
+
+ {
+ auto str1 = make_optional<std::string>({'1', '2', '3'});
+ EXPECT_EQ("123", *str1);
+
+ auto str2 =
+ make_optional<std::string>({'a', 'b', 'c'}, std::allocator<char>());
+ EXPECT_EQ("abc", *str2);
+ }
}
TEST(OptionalTest, NonMemberSwap_bothNoValue) {
@@ -1343,4 +2087,99 @@ TEST(OptionalTest, Reset_NoOp) {
EXPECT_FALSE(a.has_value());
}
+TEST(OptionalTest, AssignFromRValue) {
+ Optional<TestObject> a;
+ EXPECT_FALSE(a.has_value());
+
+ TestObject obj;
+ a = std::move(obj);
+ EXPECT_TRUE(a.has_value());
+ EXPECT_EQ(1, a->move_ctors_count());
+}
+
+TEST(OptionalTest, DontCallDefaultCtor) {
+ Optional<DeletedDefaultConstructor> a;
+ EXPECT_FALSE(a.has_value());
+
+ a = base::make_optional<DeletedDefaultConstructor>(42);
+ EXPECT_TRUE(a.has_value());
+ EXPECT_EQ(42, a->foo());
+}
+
+TEST(OptionalTest, DontCallNewMemberFunction) {
+ Optional<DeleteNewOperators> a;
+ EXPECT_FALSE(a.has_value());
+
+ a = DeleteNewOperators();
+ EXPECT_TRUE(a.has_value());
+}
+
+TEST(OptionalTest, Noexcept) {
+ // Trivial copy ctor, non-trivial move ctor, nothrow move assign.
+ struct Test1 {
+ Test1(const Test1&) = default;
+ Test1(Test1&&) {}
+ Test1& operator=(Test1&&) = default;
+ };
+ // Non-trivial copy ctor, trivial move ctor, throw move assign.
+ struct Test2 {
+ Test2(const Test2&) {}
+ Test2(Test2&&) = default;
+ Test2& operator=(Test2&&) { return *this; }
+ };
+ // Trivial copy ctor, non-trivial nothrow move ctor.
+ struct Test3 {
+ Test3(const Test3&) = default;
+ Test3(Test3&&) noexcept {}
+ };
+ // Non-trivial copy ctor, non-trivial nothrow move ctor.
+ struct Test4 {
+ Test4(const Test4&) {}
+ Test4(Test4&&) noexcept {}
+ };
+ // Non-trivial copy ctor, non-trivial move ctor.
+ struct Test5 {
+ Test5(const Test5&) {}
+ Test5(Test5&&) {}
+ };
+
+ static_assert(
+ noexcept(Optional<int>(std::declval<Optional<int>>())),
+ "move constructor for noexcept move-constructible T must be noexcept "
+ "(trivial copy, trivial move)");
+ static_assert(
+ !noexcept(Optional<Test1>(std::declval<Optional<Test1>>())),
+ "move constructor for non-noexcept move-constructible T must not be "
+ "noexcept (trivial copy)");
+ static_assert(
+ noexcept(Optional<Test2>(std::declval<Optional<Test2>>())),
+ "move constructor for noexcept move-constructible T must be noexcept "
+ "(non-trivial copy, trivial move)");
+ static_assert(
+ noexcept(Optional<Test3>(std::declval<Optional<Test3>>())),
+ "move constructor for noexcept move-constructible T must be noexcept "
+ "(trivial copy, non-trivial move)");
+ static_assert(
+ noexcept(Optional<Test4>(std::declval<Optional<Test4>>())),
+ "move constructor for noexcept move-constructible T must be noexcept "
+ "(non-trivial copy, non-trivial move)");
+ static_assert(
+ !noexcept(Optional<Test5>(std::declval<Optional<Test5>>())),
+ "move constructor for non-noexcept move-constructible T must not be "
+ "noexcept (non-trivial copy)");
+
+ static_assert(
+ noexcept(std::declval<Optional<int>>() = std::declval<Optional<int>>()),
+ "move assign for noexcept move-constructible/move-assignable T "
+ "must be noexcept");
+ static_assert(
+ !noexcept(std::declval<Optional<Test1>>() =
+ std::declval<Optional<Test1>>()),
+ "move assign for non-noexcept move-constructible T must not be noexcept");
+ static_assert(
+ !noexcept(std::declval<Optional<Test2>>() =
+ std::declval<Optional<Test2>>()),
+ "move assign for non-noexcept move-assignable T must not be noexcept");
+}
+
} // namespace base
diff --git a/base/optional_unittest.nc b/base/optional_unittest.nc
new file mode 100644
index 0000000000..62c0196765
--- /dev/null
+++ b/base/optional_unittest.nc
@@ -0,0 +1,65 @@
+// 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.
+
+// This is a "No Compile Test" suite.
+// http://dev.chromium.org/developers/testing/no-compile-tests
+
+#include <type_traits>
+
+#include "base/optional.h"
+
+namespace base {
+
+#if defined(NCTEST_EXPLICIT_CONVERTING_COPY_CONSTRUCTOR) // [r"fatal error: no matching function for call to object of type"]
+
+// Optional<T>(const Optional<U>& arg) constructor is marked explicit if
+// T is not convertible from "const U&".
+void WontCompile() {
+ struct Test {
+ // Declares as explicit so that Test is still constructible from int,
+ // but not convertible.
+ explicit Test(int a) {}
+ };
+
+ static_assert(!std::is_convertible<const int&, Test>::value,
+ "const int& to Test is convertible");
+ const Optional<int> arg(in_place, 1);
+ ([](Optional<Test> param) {})(arg);
+}
+
+#elif defined(NCTEST_EXPLICIT_CONVERTING_MOVE_CONSTRUCTOR) // [r"fatal error: no matching function for call to object of type"]
+
+// Optional<T>(Optional<U>&& arg) constructor is marked explicit if
+// T is not convertible from "U&&".
+void WontCompile() {
+ struct Test {
+ // Declares as explicit so that Test is still constructible from int,
+ // but not convertible.
+ explicit Test(int a) {}
+ };
+
+ static_assert(!std::is_convertible<int&&, Test>::value,
+ "int&& to Test is convertible");
+ ([](Optional<Test> param) {})(Optional<int>(in_place, 1));
+}
+
+#elif defined(NCTEST_EXPLICIT_VALUE_FORWARD_CONSTRUCTOR) // [r"fatal error: no matching function for call to object of type"]
+
+// Optional<T>(U&&) constructor is marked explicit if T is not convertible
+// from U&&.
+void WontCompile() {
+ struct Test {
+ // Declares as explicit so that Test is still constructible from int,
+ // but not convertible.
+ explicit Test(int a) {}
+ };
+
+ static_assert(!std::is_convertible<int&&, Test>::value,
+ "int&& to Test is convertible");
+ ([](Optional<Test> param) {})(1);
+}
+
+#endif
+
+} // namespace base
diff --git a/base/path_service.cc b/base/path_service.cc
index 1b9d394930..6ac501eafe 100644
--- a/base/path_service.cc
+++ b/base/path_service.cc
@@ -4,13 +4,14 @@
#include "base/path_service.h"
+#include <unordered_map>
+
#if defined(OS_WIN)
#include <windows.h>
#include <shellapi.h>
#include <shlobj.h>
#endif
-#include "base/containers/hash_tables.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
@@ -27,6 +28,8 @@ bool PathProviderWin(int key, FilePath* result);
bool PathProviderMac(int key, FilePath* result);
#elif defined(OS_ANDROID)
bool PathProviderAndroid(int key, FilePath* result);
+#elif defined(OS_FUCHSIA)
+bool PathProviderFuchsia(int key, FilePath* result);
#elif defined(OS_POSIX)
// PathProviderPosix is the default path provider on POSIX OSes other than
// Mac and Android.
@@ -35,7 +38,7 @@ bool PathProviderPosix(int key, FilePath* result);
namespace {
-typedef hash_map<int, FilePath> PathMap;
+typedef std::unordered_map<int, FilePath> PathMap;
// We keep a linked list of providers. In a debug build we ensure that no two
// providers claim overlapping keys.
@@ -49,15 +52,11 @@ struct Provider {
bool is_static;
};
-Provider base_provider = {
- PathProvider,
- NULL,
+Provider base_provider = {PathProvider, nullptr,
#ifndef NDEBUG
- PATH_START,
- PATH_END,
+ PATH_START, PATH_END,
#endif
- true
-};
+ true};
#if defined(OS_WIN)
Provider base_provider_win = {
@@ -95,7 +94,16 @@ Provider base_provider_android = {
};
#endif
-#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+#if defined(OS_FUCHSIA)
+Provider base_provider_fuchsia = {PathProviderFuchsia, &base_provider,
+#ifndef NDEBUG
+ 0, 0,
+#endif
+ true};
+#endif
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) && \
+ !defined(OS_FUCHSIA)
Provider base_provider_posix = {
PathProviderPosix,
&base_provider,
@@ -122,6 +130,8 @@ struct PathData {
providers = &base_provider_mac;
#elif defined(OS_ANDROID)
providers = &base_provider_android;
+#elif defined(OS_FUCHSIA)
+ providers = &base_provider_fuchsia;
#elif defined(OS_POSIX)
providers = &base_provider_posix;
#endif
@@ -176,7 +186,7 @@ bool PathService::Get(int key, FilePath* result) {
if (key == DIR_CURRENT)
return GetCurrentDirectory(result);
- Provider* provider = NULL;
+ Provider* provider = nullptr;
{
AutoLock scoped_lock(path_data->lock);
if (LockedGetFromCache(key, path_data, result))
diff --git a/base/path_service.h b/base/path_service.h
index c7f1abe714..9b4715f073 100644
--- a/base/path_service.h
+++ b/base/path_service.h
@@ -91,7 +91,4 @@ class BASE_EXPORT PathService {
} // 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/pending_task.cc b/base/pending_task.cc
index b2f95b4c45..50924fdda3 100644
--- a/base/pending_task.cc
+++ b/base/pending_task.cc
@@ -4,42 +4,21 @@
#include "base/pending_task.h"
-#include "base/message_loop/message_loop.h"
-#include "base/tracked_objects.h"
namespace base {
-PendingTask::PendingTask(const tracked_objects::Location& posted_from,
- OnceClosure task)
- : PendingTask(posted_from, std::move(task), TimeTicks(), true) {}
-
-PendingTask::PendingTask(const tracked_objects::Location& posted_from,
+PendingTask::PendingTask(const Location& posted_from,
OnceClosure task,
TimeTicks delayed_run_time,
- bool nestable)
- : base::TrackingInfo(posted_from, delayed_run_time),
- task(std::move(task)),
+ Nestable nestable)
+ : task(std::move(task)),
posted_from(posted_from),
- sequence_num(0),
- nestable(nestable),
- is_high_res(false) {
- const PendingTask* parent_task =
- MessageLoop::current() ? MessageLoop::current()->current_pending_task_
- : nullptr;
- if (parent_task) {
- task_backtrace[0] = parent_task->posted_from.program_counter();
- std::copy(parent_task->task_backtrace.begin(),
- parent_task->task_backtrace.end() - 1,
- task_backtrace.begin() + 1);
- } else {
- task_backtrace.fill(nullptr);
- }
-}
+ delayed_run_time(delayed_run_time),
+ nestable(nestable) {}
PendingTask::PendingTask(PendingTask&& other) = default;
-PendingTask::~PendingTask() {
-}
+PendingTask::~PendingTask() = default;
PendingTask& PendingTask::operator=(PendingTask&& other) = default;
diff --git a/base/pending_task.h b/base/pending_task.h
index 7f3fccd882..b71a371f22 100644
--- a/base/pending_task.h
+++ b/base/pending_task.h
@@ -6,24 +6,27 @@
#define BASE_PENDING_TASK_H_
#include <array>
-#include <queue>
#include "base/base_export.h"
#include "base/callback.h"
+#include "base/containers/queue.h"
#include "base/location.h"
#include "base/time/time.h"
-#include "base/tracking_info.h"
namespace base {
+enum class Nestable {
+ kNonNestable,
+ kNestable,
+};
+
// Contains data about a pending task. Stored in TaskQueue and DelayedTaskQueue
// for use by classes that queue and execute tasks.
-struct BASE_EXPORT PendingTask : public TrackingInfo {
- PendingTask(const tracked_objects::Location& posted_from, OnceClosure task);
- PendingTask(const tracked_objects::Location& posted_from,
+struct BASE_EXPORT PendingTask {
+ PendingTask(const Location& posted_from,
OnceClosure task,
- TimeTicks delayed_run_time,
- bool nestable);
+ TimeTicks delayed_run_time = TimeTicks(),
+ Nestable nestable = Nestable::kNestable);
PendingTask(PendingTask&& other);
~PendingTask();
@@ -36,22 +39,26 @@ struct BASE_EXPORT PendingTask : public TrackingInfo {
OnceClosure task;
// The site this PendingTask was posted from.
- tracked_objects::Location posted_from;
+ Location posted_from;
+
+ // The time when the task should be run.
+ base::TimeTicks delayed_run_time;
- // Task backtrace.
- std::array<const void*, 4> task_backtrace;
+ // Chain of up-to-four symbols of the parent tasks which led to this one being
+ // posted.
+ std::array<const void*, 4> task_backtrace = {};
// Secondary sort key for run time.
- int sequence_num;
+ int sequence_num = 0;
// OK to dispatch from a nested loop.
- bool nestable;
+ Nestable nestable;
// Needs high resolution timers.
- bool is_high_res;
+ bool is_high_res = false;
};
-using TaskQueue = std::queue<PendingTask>;
+using TaskQueue = base::queue<PendingTask>;
// PendingTasks are sorted by their |delayed_run_time| property.
using DelayedTaskQueue = std::priority_queue<base::PendingTask>;
diff --git a/base/pickle.cc b/base/pickle.cc
index 02f39b57b7..c2189c8fb5 100644
--- a/base/pickle.cc
+++ b/base/pickle.cc
@@ -12,6 +12,7 @@
#include "base/bits.h"
#include "base/macros.h"
#include "base/numerics/safe_conversions.h"
+#include "base/numerics/safe_math.h"
#include "build/build_config.h"
namespace base {
@@ -52,7 +53,7 @@ template<typename Type>
inline const char* PickleIterator::GetReadPointerAndAdvance() {
if (sizeof(Type) > end_index_ - read_index_) {
read_index_ = end_index_;
- return NULL;
+ return nullptr;
}
const char* current_read_ptr = payload_ + read_index_;
Advance(sizeof(Type));
@@ -63,7 +64,7 @@ const char* PickleIterator::GetReadPointerAndAdvance(int num_bytes) {
if (num_bytes < 0 ||
end_index_ - read_index_ < static_cast<size_t>(num_bytes)) {
read_index_ = end_index_;
- return NULL;
+ return nullptr;
}
const char* current_read_ptr = payload_ + read_index_;
Advance(num_bytes);
@@ -74,11 +75,10 @@ inline const char* PickleIterator::GetReadPointerAndAdvance(
int num_elements,
size_t size_element) {
// Check for int32_t overflow.
- int64_t num_bytes = static_cast<int64_t>(num_elements) * size_element;
- int num_bytes32 = static_cast<int>(num_bytes);
- if (num_bytes != static_cast<int64_t>(num_bytes32))
- return NULL;
- return GetReadPointerAndAdvance(num_bytes32);
+ int num_bytes;
+ if (!CheckMul(num_elements, size_element).AssignIfValid(&num_bytes))
+ return nullptr;
+ return GetReadPointerAndAdvance(num_bytes);
}
bool PickleIterator::ReadBool(bool* result) {
@@ -191,7 +191,7 @@ bool PickleIterator::ReadStringPiece16(StringPiece16* result) {
bool PickleIterator::ReadData(const char** data, int* length) {
*length = 0;
- *data = 0;
+ *data = nullptr;
if (!ReadInt(length))
return false;
@@ -207,52 +207,14 @@ bool PickleIterator::ReadBytes(const char** data, int length) {
return true;
}
-PickleSizer::PickleSizer() {}
+Pickle::Attachment::Attachment() = default;
-PickleSizer::~PickleSizer() {}
-
-void PickleSizer::AddString(const StringPiece& value) {
- AddInt();
- AddBytes(static_cast<int>(value.size()));
-}
-
-void PickleSizer::AddString16(const StringPiece16& value) {
- AddInt();
- AddBytes(static_cast<int>(value.size() * sizeof(char16)));
-}
-
-void PickleSizer::AddData(int length) {
- CHECK_GE(length, 0);
- AddInt();
- AddBytes(length);
-}
-
-void PickleSizer::AddBytes(int length) {
- payload_size_ += bits::Align(length, sizeof(uint32_t));
-}
-
-void PickleSizer::AddAttachment() {
- // From IPC::Message::WriteAttachment
- AddInt();
-}
-
-template <size_t length> void PickleSizer::AddBytesStatic() {
- DCHECK_LE(length, static_cast<size_t>(std::numeric_limits<int>::max()));
- AddBytes(length);
-}
-
-template void PickleSizer::AddBytesStatic<2>();
-template void PickleSizer::AddBytesStatic<4>();
-template void PickleSizer::AddBytesStatic<8>();
-
-Pickle::Attachment::Attachment() {}
-
-Pickle::Attachment::~Attachment() {}
+Pickle::Attachment::~Attachment() = default;
// Payload is uint32_t aligned.
Pickle::Pickle()
- : header_(NULL),
+ : header_(nullptr),
header_size_(sizeof(Header)),
capacity_after_header_(0),
write_offset_(0) {
@@ -263,7 +225,7 @@ Pickle::Pickle()
}
Pickle::Pickle(int header_size)
- : header_(NULL),
+ : header_(nullptr),
header_size_(bits::Align(header_size, sizeof(uint32_t))),
capacity_after_header_(0),
write_offset_(0) {
@@ -289,11 +251,11 @@ Pickle::Pickle(const char* data, int data_len)
// If there is anything wrong with the data, we're not going to use it.
if (!header_size_)
- header_ = NULL;
+ header_ = nullptr;
}
Pickle::Pickle(const Pickle& other)
- : header_(NULL),
+ : header_(nullptr),
header_size_(other.header_size_),
capacity_after_header_(0),
write_offset_(other.write_offset_) {
@@ -308,16 +270,15 @@ Pickle::~Pickle() {
Pickle& Pickle::operator=(const Pickle& other) {
if (this == &other) {
- NOTREACHED();
return *this;
}
if (capacity_after_header_ == kCapacityReadOnly) {
- header_ = NULL;
+ header_ = nullptr;
capacity_after_header_ = 0;
}
if (header_size_ != other.header_size_) {
free(header_);
- header_ = NULL;
+ header_ = nullptr;
header_size_ = other.header_size_;
}
Resize(other.header_->payload_size);
@@ -327,28 +288,24 @@ Pickle& Pickle::operator=(const Pickle& other) {
return *this;
}
-bool Pickle::WriteString(const StringPiece& value) {
- if (!WriteInt(static_cast<int>(value.size())))
- return false;
-
- return WriteBytes(value.data(), static_cast<int>(value.size()));
+void Pickle::WriteString(const StringPiece& value) {
+ WriteInt(static_cast<int>(value.size()));
+ WriteBytes(value.data(), static_cast<int>(value.size()));
}
-bool Pickle::WriteString16(const StringPiece16& value) {
- if (!WriteInt(static_cast<int>(value.size())))
- return false;
-
- return WriteBytes(value.data(),
- static_cast<int>(value.size()) * sizeof(char16));
+void Pickle::WriteString16(const StringPiece16& value) {
+ WriteInt(static_cast<int>(value.size()));
+ WriteBytes(value.data(), static_cast<int>(value.size()) * sizeof(char16));
}
-bool Pickle::WriteData(const char* data, int length) {
- return length >= 0 && WriteInt(length) && WriteBytes(data, length);
+void Pickle::WriteData(const char* data, int length) {
+ DCHECK_GE(length, 0);
+ WriteInt(length);
+ WriteBytes(data, length);
}
-bool Pickle::WriteBytes(const void* data, int length) {
+void Pickle::WriteBytes(const void* data, int length) {
WriteBytesCommon(data, length);
- return true;
}
void Pickle::Reserve(size_t length) {
@@ -403,10 +360,10 @@ const char* Pickle::FindNext(size_t header_size,
const char* end) {
size_t pickle_size = 0;
if (!PeekNext(header_size, start, end, &pickle_size))
- return NULL;
+ return nullptr;
if (pickle_size > static_cast<size_t>(end - start))
- return NULL;
+ return nullptr;
return start + pickle_size;
}
@@ -428,13 +385,9 @@ bool Pickle::PeekNext(size_t header_size,
if (length < header_size)
return false;
- if (hdr->payload_size > std::numeric_limits<size_t>::max() - header_size) {
- // If payload_size causes an overflow, we return maximum possible
- // pickle size to indicate that.
- *pickle_size = std::numeric_limits<size_t>::max();
- } else {
- *pickle_size = header_size + hdr->payload_size;
- }
+ // If payload_size causes an overflow, we return maximum possible
+ // pickle size to indicate that.
+ *pickle_size = ClampAdd(header_size, hdr->payload_size);
return true;
}
diff --git a/base/pickle.h b/base/pickle.h
index 40f5d262bc..eff20923a3 100644
--- a/base/pickle.h
+++ b/base/pickle.h
@@ -108,42 +108,6 @@ class BASE_EXPORT PickleIterator {
FRIEND_TEST_ALL_PREFIXES(PickleTest, GetReadPointerAndAdvance);
};
-// This class provides an interface analogous to base::Pickle's WriteFoo()
-// methods and can be used to accurately compute the size of a hypothetical
-// Pickle's payload without having to reference the Pickle implementation.
-class BASE_EXPORT PickleSizer {
- public:
- PickleSizer();
- ~PickleSizer();
-
- // Returns the computed size of the payload.
- size_t payload_size() const { return payload_size_; }
-
- void AddBool() { return AddInt(); }
- void AddInt() { AddPOD<int>(); }
- void AddLong() { AddPOD<uint64_t>(); }
- void AddUInt16() { return AddPOD<uint16_t>(); }
- void AddUInt32() { return AddPOD<uint32_t>(); }
- void AddInt64() { return AddPOD<int64_t>(); }
- void AddUInt64() { return AddPOD<uint64_t>(); }
- void AddFloat() { return AddPOD<float>(); }
- void AddDouble() { return AddPOD<double>(); }
- void AddString(const StringPiece& value);
- void AddString16(const StringPiece16& value);
- void AddData(int length);
- void AddBytes(int length);
- void AddAttachment();
-
- private:
- // Just like AddBytes() but with a compile-time size for performance.
- template<size_t length> void BASE_EXPORT AddBytesStatic();
-
- template <typename T>
- void AddPOD() { AddBytesStatic<sizeof(T)>(); }
-
- size_t payload_size_ = 0;
-};
-
// This class provides facilities for basic binary value packing and unpacking.
//
// The Pickle class supports appending primitive values (ints, strings, etc.)
@@ -222,36 +186,28 @@ class BASE_EXPORT Pickle {
// Pickle, it is important to read them in the order in which they were added
// to the Pickle.
- bool WriteBool(bool value) {
- return WriteInt(value ? 1 : 0);
- }
- bool WriteInt(int value) {
- return WritePOD(value);
- }
- bool WriteLong(long value) {
+ void WriteBool(bool value) { WriteInt(value ? 1 : 0); }
+ void WriteInt(int value) { WritePOD(value); }
+ void WriteLong(long value) {
// Always write long as a 64-bit value to ensure compatibility between
// 32-bit and 64-bit processes.
- return WritePOD(static_cast<int64_t>(value));
+ WritePOD(static_cast<int64_t>(value));
}
- bool WriteUInt16(uint16_t value) { return WritePOD(value); }
- bool WriteUInt32(uint32_t value) { return WritePOD(value); }
- bool WriteInt64(int64_t value) { return WritePOD(value); }
- bool WriteUInt64(uint64_t value) { return WritePOD(value); }
- bool WriteFloat(float value) {
- return WritePOD(value);
- }
- bool WriteDouble(double value) {
- return WritePOD(value);
- }
- bool WriteString(const StringPiece& value);
- bool WriteString16(const StringPiece16& value);
+ void WriteUInt16(uint16_t value) { WritePOD(value); }
+ void WriteUInt32(uint32_t value) { WritePOD(value); }
+ void WriteInt64(int64_t value) { WritePOD(value); }
+ void WriteUInt64(uint64_t value) { WritePOD(value); }
+ void WriteFloat(float value) { WritePOD(value); }
+ void WriteDouble(double value) { WritePOD(value); }
+ void WriteString(const StringPiece& value);
+ void WriteString16(const StringPiece16& value);
// "Data" is a blob with a length. When you read it out you will be given the
// length. See also WriteBytes.
- bool WriteData(const char* data, int length);
+ void WriteData(const char* data, int length);
// "Bytes" is a blob with no length. The caller must specify the length both
// when reading and writing. It is normally used to serialize PoD types of a
// known size. See also WriteData.
- bool WriteBytes(const void* data, int length);
+ void WriteBytes(const void* data, int length);
// WriteAttachment appends |attachment| to the pickle. It returns
// false iff the set is full or if the Pickle implementation does not support
@@ -307,6 +263,10 @@ class BASE_EXPORT Pickle {
}
protected:
+ // Returns size of the header, which can have default value, set by user or
+ // calculated by passed raw data.
+ size_t header_size() const { return header_size_; }
+
char* mutable_payload() {
return reinterpret_cast<char*>(header_) + header_size_;
}
diff --git a/base/pickle_unittest.cc b/base/pickle_unittest.cc
index e00edd9e03..4563047060 100644
--- a/base/pickle_unittest.cc
+++ b/base/pickle_unittest.cc
@@ -22,12 +22,12 @@ namespace {
const bool testbool1 = false;
const bool testbool2 = true;
-const int testint = 2093847192;
-const long testlong = 1093847192;
+const int testint = 2'093'847'192;
+const long testlong = 1'093'847'192;
const uint16_t testuint16 = 32123;
const uint32_t testuint32 = 1593847192;
-const int64_t testint64 = -0x7E8CA9253104BDFCLL;
-const uint64_t testuint64 = 0xCE8CA9253104BDF7ULL;
+const int64_t testint64 = -0x7E8CA925'3104BDFCLL;
+const uint64_t testuint64 = 0xCE8CA925'3104BDF7ULL;
const float testfloat = 3.1415926935f;
const double testdouble = 2.71828182845904523;
const std::string teststring("Hello world"); // note non-aligned string length
@@ -112,21 +112,21 @@ void VerifyResult(const Pickle& pickle) {
TEST(PickleTest, EncodeDecode) {
Pickle pickle;
- EXPECT_TRUE(pickle.WriteBool(testbool1));
- EXPECT_TRUE(pickle.WriteBool(testbool2));
- EXPECT_TRUE(pickle.WriteInt(testint));
- EXPECT_TRUE(pickle.WriteLong(testlong));
- EXPECT_TRUE(pickle.WriteUInt16(testuint16));
- EXPECT_TRUE(pickle.WriteUInt32(testuint32));
- EXPECT_TRUE(pickle.WriteInt64(testint64));
- EXPECT_TRUE(pickle.WriteUInt64(testuint64));
- EXPECT_TRUE(pickle.WriteFloat(testfloat));
- EXPECT_TRUE(pickle.WriteDouble(testdouble));
- EXPECT_TRUE(pickle.WriteString(teststring));
- EXPECT_TRUE(pickle.WriteString16(teststring16));
- EXPECT_TRUE(pickle.WriteString(testrawstring));
- EXPECT_TRUE(pickle.WriteString16(testrawstring16));
- EXPECT_TRUE(pickle.WriteData(testdata, testdatalen));
+ pickle.WriteBool(testbool1);
+ pickle.WriteBool(testbool2);
+ pickle.WriteInt(testint);
+ pickle.WriteLong(testlong);
+ pickle.WriteUInt16(testuint16);
+ pickle.WriteUInt32(testuint32);
+ pickle.WriteInt64(testint64);
+ pickle.WriteUInt64(testuint64);
+ pickle.WriteFloat(testfloat);
+ pickle.WriteDouble(testdouble);
+ pickle.WriteString(teststring);
+ pickle.WriteString16(teststring16);
+ pickle.WriteString(testrawstring);
+ pickle.WriteString16(testrawstring16);
+ pickle.WriteData(testdata, testdatalen);
VerifyResult(pickle);
// test copy constructor
@@ -146,7 +146,7 @@ TEST(PickleTest, LongFrom64Bit) {
Pickle pickle;
// Under the hood long is always written as a 64-bit value, so simulate a
// 64-bit long even on 32-bit architectures by explicitly writing an int64_t.
- EXPECT_TRUE(pickle.WriteInt64(testint64));
+ pickle.WriteInt64(testint64);
PickleIterator iter(pickle);
long outlong;
@@ -197,7 +197,7 @@ TEST(PickleTest, UnalignedSize) {
TEST(PickleTest, ZeroLenStr) {
Pickle pickle;
- EXPECT_TRUE(pickle.WriteString(std::string()));
+ pickle.WriteString(std::string());
PickleIterator iter(pickle);
std::string outstr;
@@ -207,7 +207,7 @@ TEST(PickleTest, ZeroLenStr) {
TEST(PickleTest, ZeroLenStr16) {
Pickle pickle;
- EXPECT_TRUE(pickle.WriteString16(string16()));
+ pickle.WriteString16(string16());
PickleIterator iter(pickle);
std::string outstr;
@@ -217,7 +217,7 @@ TEST(PickleTest, ZeroLenStr16) {
TEST(PickleTest, BadLenStr) {
Pickle pickle;
- EXPECT_TRUE(pickle.WriteInt(-2));
+ pickle.WriteInt(-2);
PickleIterator iter(pickle);
std::string outstr;
@@ -226,7 +226,7 @@ TEST(PickleTest, BadLenStr) {
TEST(PickleTest, BadLenStr16) {
Pickle pickle;
- EXPECT_TRUE(pickle.WriteInt(-1));
+ pickle.WriteInt(-1);
PickleIterator iter(pickle);
string16 outstr;
@@ -240,7 +240,7 @@ TEST(PickleTest, PeekNext) {
Pickle pickle(sizeof(CustomHeader));
- EXPECT_TRUE(pickle.WriteString("Goooooooooooogle"));
+ pickle.WriteString("Goooooooooooogle");
const char* pickle_data = static_cast<const char*>(pickle.data());
@@ -317,15 +317,15 @@ TEST(PickleTest, PeekNextOverflow) {
TEST(PickleTest, FindNext) {
Pickle pickle;
- EXPECT_TRUE(pickle.WriteInt(1));
- EXPECT_TRUE(pickle.WriteString("Domo"));
+ pickle.WriteInt(1);
+ pickle.WriteString("Domo");
const char* start = reinterpret_cast<const char*>(pickle.data());
const char* end = start + pickle.size();
- EXPECT_TRUE(end == Pickle::FindNext(pickle.header_size_, start, end));
- EXPECT_TRUE(NULL == Pickle::FindNext(pickle.header_size_, start, end - 1));
- EXPECT_TRUE(end == Pickle::FindNext(pickle.header_size_, start, end + 1));
+ EXPECT_EQ(end, Pickle::FindNext(pickle.header_size_, start, end));
+ EXPECT_EQ(nullptr, Pickle::FindNext(pickle.header_size_, start, end - 1));
+ EXPECT_EQ(end, Pickle::FindNext(pickle.header_size_, start, end + 1));
}
TEST(PickleTest, FindNextWithIncompleteHeader) {
@@ -336,7 +336,7 @@ TEST(PickleTest, FindNextWithIncompleteHeader) {
const char* start = buffer.get();
const char* end = start + header_size - 1;
- EXPECT_TRUE(NULL == Pickle::FindNext(header_size, start, end));
+ EXPECT_EQ(nullptr, Pickle::FindNext(header_size, start, end));
}
#if defined(COMPILER_MSVC)
@@ -357,14 +357,14 @@ TEST(PickleTest, FindNextOverflow) {
return;
header->payload_size = -(reinterpret_cast<uintptr_t>(start) + header_size2);
- EXPECT_TRUE(NULL == Pickle::FindNext(header_size2, start, end));
+ EXPECT_EQ(nullptr, Pickle::FindNext(header_size2, start, end));
header->payload_size = -header_size2;
- EXPECT_TRUE(NULL == Pickle::FindNext(header_size2, start, end));
+ EXPECT_EQ(nullptr, Pickle::FindNext(header_size2, start, end));
header->payload_size = 0;
end = start + header_size;
- EXPECT_TRUE(NULL == Pickle::FindNext(header_size2, start, end));
+ EXPECT_EQ(nullptr, Pickle::FindNext(header_size2, start, end));
}
#if defined(COMPILER_MSVC)
#pragma warning(pop)
@@ -376,8 +376,8 @@ TEST(PickleTest, GetReadPointerAndAdvance) {
PickleIterator iter(pickle);
EXPECT_FALSE(iter.GetReadPointerAndAdvance(1));
- EXPECT_TRUE(pickle.WriteInt(1));
- EXPECT_TRUE(pickle.WriteInt(2));
+ pickle.WriteInt(1);
+ pickle.WriteInt(2);
int bytes = sizeof(int) * 2;
EXPECT_TRUE(PickleIterator(pickle).GetReadPointerAndAdvance(0));
@@ -459,7 +459,7 @@ TEST(PickleTest, EqualsOperator) {
TEST(PickleTest, EvilLengths) {
Pickle source;
std::string str(100000, 'A');
- EXPECT_TRUE(source.WriteData(str.c_str(), 100000));
+ source.WriteData(str.c_str(), 100000);
// ReadString16 used to have its read buffer length calculation wrong leading
// to out-of-bounds reading.
PickleIterator iter(source);
@@ -469,7 +469,7 @@ TEST(PickleTest, EvilLengths) {
// And check we didn't break ReadString16.
str16 = (wchar_t) 'A';
Pickle str16_pickle;
- EXPECT_TRUE(str16_pickle.WriteString16(str16));
+ str16_pickle.WriteString16(str16);
iter = PickleIterator(str16_pickle);
EXPECT_TRUE(iter.ReadString16(&str16));
EXPECT_EQ(1U, str16.length());
@@ -477,7 +477,7 @@ TEST(PickleTest, EvilLengths) {
// Check we don't fail in a length check with invalid String16 size.
// (1<<31) * sizeof(char16) == 0, so this is particularly evil.
Pickle bad_len;
- EXPECT_TRUE(bad_len.WriteInt(1 << 31));
+ bad_len.WriteInt(1 << 31);
iter = PickleIterator(bad_len);
EXPECT_FALSE(iter.ReadString16(&str16));
}
@@ -485,7 +485,7 @@ TEST(PickleTest, EvilLengths) {
// Check we can write zero bytes of data and 'data' can be NULL.
TEST(PickleTest, ZeroLength) {
Pickle pickle;
- EXPECT_TRUE(pickle.WriteData(NULL, 0));
+ pickle.WriteData(nullptr, 0);
PickleIterator iter(pickle);
const char* outdata;
@@ -499,10 +499,10 @@ TEST(PickleTest, ZeroLength) {
TEST(PickleTest, ReadBytes) {
Pickle pickle;
int data = 0x7abcd;
- EXPECT_TRUE(pickle.WriteBytes(&data, sizeof(data)));
+ pickle.WriteBytes(&data, sizeof(data));
PickleIterator iter(pickle);
- const char* outdata_char = NULL;
+ const char* outdata_char = nullptr;
EXPECT_TRUE(iter.ReadBytes(&outdata_char, sizeof(data)));
int outdata;
@@ -529,7 +529,7 @@ namespace {
// Publicly exposes the ClaimBytes interface for testing.
class TestingPickle : public Pickle {
public:
- TestingPickle() {}
+ TestingPickle() = default;
void* ClaimBytes(size_t num_bytes) { return Pickle::ClaimBytes(num_bytes); }
};
@@ -570,99 +570,4 @@ TEST(PickleTest, ClaimBytes) {
EXPECT_EQ(42, out_value);
}
-// Checks that PickleSizer and Pickle agree on the size of things.
-TEST(PickleTest, PickleSizer) {
- {
- TestingPickle pickle;
- base::PickleSizer sizer;
- pickle.WriteBool(true);
- sizer.AddBool();
- EXPECT_EQ(sizer.payload_size(), pickle.payload_size());
- }
- {
- TestingPickle pickle;
- base::PickleSizer sizer;
- pickle.WriteInt(42);
- sizer.AddInt();
- EXPECT_EQ(sizer.payload_size(), pickle.payload_size());
- }
- {
- TestingPickle pickle;
- base::PickleSizer sizer;
- pickle.WriteLong(42);
- sizer.AddLong();
- EXPECT_EQ(sizer.payload_size(), pickle.payload_size());
- }
- {
- TestingPickle pickle;
- base::PickleSizer sizer;
- pickle.WriteUInt16(42);
- sizer.AddUInt16();
- EXPECT_EQ(sizer.payload_size(), pickle.payload_size());
- }
- {
- TestingPickle pickle;
- base::PickleSizer sizer;
- pickle.WriteUInt32(42);
- sizer.AddUInt32();
- EXPECT_EQ(sizer.payload_size(), pickle.payload_size());
- }
- {
- TestingPickle pickle;
- base::PickleSizer sizer;
- pickle.WriteInt64(42);
- sizer.AddInt64();
- EXPECT_EQ(sizer.payload_size(), pickle.payload_size());
- }
- {
- TestingPickle pickle;
- base::PickleSizer sizer;
- pickle.WriteUInt64(42);
- sizer.AddUInt64();
- EXPECT_EQ(sizer.payload_size(), pickle.payload_size());
- }
- {
- TestingPickle pickle;
- base::PickleSizer sizer;
- pickle.WriteFloat(42.0f);
- sizer.AddFloat();
- EXPECT_EQ(sizer.payload_size(), pickle.payload_size());
- }
- {
- TestingPickle pickle;
- base::PickleSizer sizer;
- pickle.WriteDouble(42.0);
- sizer.AddDouble();
- EXPECT_EQ(sizer.payload_size(), pickle.payload_size());
- }
- {
- TestingPickle pickle;
- base::PickleSizer sizer;
- pickle.WriteString(teststring);
- sizer.AddString(teststring);
- EXPECT_EQ(sizer.payload_size(), pickle.payload_size());
- }
- {
- TestingPickle pickle;
- base::PickleSizer sizer;
- pickle.WriteString16(teststring16);
- sizer.AddString16(teststring16);
- EXPECT_EQ(sizer.payload_size(), pickle.payload_size());
- }
- {
- TestingPickle pickle;
- base::PickleSizer sizer;
- pickle.WriteData(testdata, testdatalen);
- sizer.AddData(testdatalen);
- EXPECT_EQ(sizer.payload_size(), pickle.payload_size());
- }
- {
- TestingPickle pickle;
- base::PickleSizer sizer;
- pickle.WriteBytes(testdata, testdatalen);
- sizer.AddBytes(testdatalen);
- EXPECT_EQ(sizer.payload_size(), pickle.payload_size());
- }
-}
-
} // namespace base
diff --git a/base/posix/eintr_wrapper.h b/base/posix/eintr_wrapper.h
index 5a5dc758a9..0e6e437953 100644
--- a/base/posix/eintr_wrapper.h
+++ b/base/posix/eintr_wrapper.h
@@ -8,7 +8,8 @@
// that should be masked) to go unnoticed, there is a limit after which the
// caller will nonetheless see an EINTR in Debug builds.
//
-// On Windows, this wrapper macro does nothing.
+// On Windows and Fuchsia, this wrapper macro does nothing because there are no
+// signals.
//
// Don't wrap close calls in HANDLE_EINTR. Use IGNORE_EINTR if the return
// value of close is significant. See http://crbug.com/269623.
@@ -57,11 +58,11 @@
eintr_wrapper_result; \
})
-#else
+#else // !OS_POSIX
#define HANDLE_EINTR(x) (x)
#define IGNORE_EINTR(x) (x)
-#endif // OS_POSIX
+#endif // !OS_POSIX
#endif // BASE_POSIX_EINTR_WRAPPER_H_
diff --git a/base/posix/file_descriptor_shuffle.h b/base/posix/file_descriptor_shuffle.h
index 78e3a7d493..2afdc28832 100644
--- a/base/posix/file_descriptor_shuffle.h
+++ b/base/posix/file_descriptor_shuffle.h
@@ -42,7 +42,7 @@ class InjectionDelegate {
virtual void Close(int fd) = 0;
protected:
- virtual ~InjectionDelegate() {}
+ virtual ~InjectionDelegate() = default;
};
// An implementation of the InjectionDelegate interface using the file
diff --git a/base/posix/global_descriptors.cc b/base/posix/global_descriptors.cc
index 8da808e52d..738d14e3ad 100644
--- a/base/posix/global_descriptors.cc
+++ b/base/posix/global_descriptors.cc
@@ -33,7 +33,7 @@ int GlobalDescriptors::Get(Key key) const {
const int ret = MaybeGet(key);
if (ret == -1)
- DLOG(FATAL) << "Unknown global descriptor: " << key;
+ DLOG(DCHECK) << "Unknown global descriptor: " << key;
return ret;
}
@@ -86,7 +86,7 @@ base::MemoryMappedFile::Region GlobalDescriptors::GetRegion(Key key) const {
if (i.key == key)
return i.region;
}
- DLOG(FATAL) << "Unknown global descriptor: " << key;
+ DLOG(DCHECK) << "Unknown global descriptor: " << key;
return base::MemoryMappedFile::Region::kWholeFile;
}
@@ -94,8 +94,8 @@ void GlobalDescriptors::Reset(const Mapping& mapping) {
descriptors_ = mapping;
}
-GlobalDescriptors::GlobalDescriptors() {}
+GlobalDescriptors::GlobalDescriptors() = default;
-GlobalDescriptors::~GlobalDescriptors() {}
+GlobalDescriptors::~GlobalDescriptors() = default;
} // namespace base
diff --git a/base/posix/safe_strerror.cc b/base/posix/safe_strerror.cc
index 798658e962..aef5742d33 100644
--- a/base/posix/safe_strerror.cc
+++ b/base/posix/safe_strerror.cc
@@ -108,7 +108,7 @@ static void POSSIBLY_UNUSED wrap_posix_strerror_r(
}
void safe_strerror_r(int err, char *buf, size_t len) {
- if (buf == NULL || len <= 0) {
+ if (buf == nullptr || len <= 0) {
return;
}
// If using glibc (i.e., Linux), the compiler will automatically select the
diff --git a/base/posix/unix_domain_socket.cc b/base/posix/unix_domain_socket.cc
new file mode 100644
index 0000000000..7c087a53b5
--- /dev/null
+++ b/base/posix/unix_domain_socket.cc
@@ -0,0 +1,288 @@
+// 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/posix/unix_domain_socket.h"
+
+#include <errno.h>
+#include <sys/socket.h>
+#if !defined(OS_NACL_NONSFI)
+#include <sys/un.h>
+#endif
+#include <unistd.h>
+
+#include <vector>
+
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/pickle.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/stl_util.h"
+#include "build/build_config.h"
+
+#if !defined(OS_NACL_NONSFI)
+#include <sys/uio.h>
+#endif
+
+namespace base {
+
+const size_t UnixDomainSocket::kMaxFileDescriptors = 16;
+
+#if !defined(OS_NACL_NONSFI)
+bool CreateSocketPair(ScopedFD* one, ScopedFD* two) {
+ int raw_socks[2];
+#if defined(OS_MACOSX)
+ // macOS does not support SEQPACKET.
+ const int flags = SOCK_STREAM;
+#else
+ const int flags = SOCK_SEQPACKET;
+#endif
+ if (socketpair(AF_UNIX, flags, 0, raw_socks) == -1)
+ return false;
+#if defined(OS_MACOSX)
+ // On macOS, preventing SIGPIPE is done with socket option.
+ const int no_sigpipe = 1;
+ if (setsockopt(raw_socks[0], SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe,
+ sizeof(no_sigpipe)) != 0)
+ return false;
+ if (setsockopt(raw_socks[1], SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe,
+ sizeof(no_sigpipe)) != 0)
+ return false;
+#endif
+ one->reset(raw_socks[0]);
+ two->reset(raw_socks[1]);
+ return true;
+}
+
+// static
+bool UnixDomainSocket::EnableReceiveProcessId(int fd) {
+#if !defined(OS_MACOSX)
+ const int enable = 1;
+ return setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &enable, sizeof(enable)) == 0;
+#else
+ // SO_PASSCRED is not supported on macOS.
+ return true;
+#endif // OS_MACOSX
+}
+#endif // !defined(OS_NACL_NONSFI)
+
+// static
+bool UnixDomainSocket::SendMsg(int fd,
+ const void* buf,
+ size_t length,
+ const std::vector<int>& fds) {
+ struct msghdr msg = {};
+ struct iovec iov = {const_cast<void*>(buf), length};
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ char* control_buffer = nullptr;
+ if (fds.size()) {
+ const unsigned control_len = CMSG_SPACE(sizeof(int) * fds.size());
+ control_buffer = new char[control_len];
+
+ struct cmsghdr* cmsg;
+ msg.msg_control = control_buffer;
+ msg.msg_controllen = control_len;
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int) * fds.size());
+ memcpy(CMSG_DATA(cmsg), &fds[0], sizeof(int) * fds.size());
+ msg.msg_controllen = cmsg->cmsg_len;
+ }
+
+// Avoid a SIGPIPE if the other end breaks the connection.
+// Due to a bug in the Linux kernel (net/unix/af_unix.c) MSG_NOSIGNAL isn't
+// regarded for SOCK_SEQPACKET in the AF_UNIX domain, but it is mandated by
+// POSIX. On Mac MSG_NOSIGNAL is not supported, so we need to ensure that
+// SO_NOSIGPIPE is set during socket creation.
+#if defined(OS_MACOSX)
+ const int flags = 0;
+ int no_sigpipe = 0;
+ socklen_t no_sigpipe_len = sizeof(no_sigpipe);
+ DPCHECK(getsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe,
+ &no_sigpipe_len) == 0)
+ << "Failed ot get socket option.";
+ DCHECK(no_sigpipe) << "SO_NOSIGPIPE not set on the socket.";
+#else
+ const int flags = MSG_NOSIGNAL;
+#endif // OS_MACOSX
+ const ssize_t r = HANDLE_EINTR(sendmsg(fd, &msg, flags));
+ const bool ret = static_cast<ssize_t>(length) == r;
+ delete[] control_buffer;
+ return ret;
+}
+
+// static
+ssize_t UnixDomainSocket::RecvMsg(int fd,
+ void* buf,
+ size_t length,
+ std::vector<ScopedFD>* fds) {
+ return UnixDomainSocket::RecvMsgWithPid(fd, buf, length, fds, nullptr);
+}
+
+// static
+ssize_t UnixDomainSocket::RecvMsgWithPid(int fd,
+ void* buf,
+ size_t length,
+ std::vector<ScopedFD>* fds,
+ ProcessId* pid) {
+ return UnixDomainSocket::RecvMsgWithFlags(fd, buf, length, 0, fds, pid);
+}
+
+// static
+ssize_t UnixDomainSocket::RecvMsgWithFlags(int fd,
+ void* buf,
+ size_t length,
+ int flags,
+ std::vector<ScopedFD>* fds,
+ ProcessId* out_pid) {
+ fds->clear();
+
+ struct msghdr msg = {};
+ struct iovec iov = {buf, length};
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ const size_t kControlBufferSize =
+ CMSG_SPACE(sizeof(int) * kMaxFileDescriptors)
+#if !defined(OS_NACL_NONSFI) && !defined(OS_MACOSX)
+ // The PNaCl toolchain for Non-SFI binary build and macOS do not support
+ // ucred. macOS supports xucred, but this structure is insufficient.
+ + CMSG_SPACE(sizeof(struct ucred))
+#endif // OS_NACL_NONSFI or OS_MACOSX
+ ;
+ char control_buffer[kControlBufferSize];
+ msg.msg_control = control_buffer;
+ msg.msg_controllen = sizeof(control_buffer);
+
+ const ssize_t r = HANDLE_EINTR(recvmsg(fd, &msg, flags));
+ if (r == -1)
+ return -1;
+
+ int* wire_fds = nullptr;
+ unsigned wire_fds_len = 0;
+ ProcessId pid = -1;
+
+ if (msg.msg_controllen > 0) {
+ struct cmsghdr* cmsg;
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0);
+ if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
+ DCHECK_EQ(payload_len % sizeof(int), 0u);
+ DCHECK_EQ(wire_fds, static_cast<void*>(nullptr));
+ wire_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg));
+ wire_fds_len = payload_len / sizeof(int);
+ }
+#if !defined(OS_NACL_NONSFI) && !defined(OS_MACOSX)
+ // The PNaCl toolchain for Non-SFI binary build and macOS do not support
+ // SCM_CREDENTIALS.
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_CREDENTIALS) {
+ DCHECK_EQ(payload_len, sizeof(struct ucred));
+ DCHECK_EQ(pid, -1);
+ pid = reinterpret_cast<struct ucred*>(CMSG_DATA(cmsg))->pid;
+ }
+#endif // !defined(OS_NACL_NONSFI) && !defined(OS_MACOSX)
+ }
+ }
+
+ if (msg.msg_flags & MSG_TRUNC || msg.msg_flags & MSG_CTRUNC) {
+ if (msg.msg_flags & MSG_CTRUNC) {
+ // Extraordinary case, not caller fixable. Log something.
+ LOG(ERROR) << "recvmsg returned MSG_CTRUNC flag, buffer len is "
+ << msg.msg_controllen;
+ }
+ for (unsigned i = 0; i < wire_fds_len; ++i)
+ close(wire_fds[i]);
+ errno = EMSGSIZE;
+ return -1;
+ }
+
+ if (wire_fds) {
+ for (unsigned i = 0; i < wire_fds_len; ++i)
+ fds->push_back(ScopedFD(wire_fds[i])); // TODO(mdempsky): emplace_back
+ }
+
+ if (out_pid) {
+#if defined(OS_MACOSX)
+ socklen_t pid_size = sizeof(pid);
+ if (getsockopt(fd, SOL_LOCAL, LOCAL_PEERPID, &pid, &pid_size) != 0)
+ pid = -1;
+#else
+ // |pid| will legitimately be -1 if we read EOF, so only DCHECK if we
+ // actually received a message. Unfortunately, Linux allows sending zero
+ // length messages, which are indistinguishable from EOF, so this check
+ // has false negatives.
+ if (r > 0 || msg.msg_controllen > 0)
+ DCHECK_GE(pid, 0);
+#endif
+
+ *out_pid = pid;
+ }
+
+ return r;
+}
+
+#if !defined(OS_NACL_NONSFI)
+// static
+ssize_t UnixDomainSocket::SendRecvMsg(int fd,
+ uint8_t* reply,
+ unsigned max_reply_len,
+ int* result_fd,
+ const Pickle& request) {
+ return UnixDomainSocket::SendRecvMsgWithFlags(fd, reply, max_reply_len,
+ 0, /* recvmsg_flags */
+ result_fd, request);
+}
+
+// static
+ssize_t UnixDomainSocket::SendRecvMsgWithFlags(int fd,
+ uint8_t* reply,
+ unsigned max_reply_len,
+ int recvmsg_flags,
+ int* result_fd,
+ const Pickle& request) {
+ // This socketpair is only used for the IPC and is cleaned up before
+ // returning.
+ ScopedFD recv_sock, send_sock;
+ if (!CreateSocketPair(&recv_sock, &send_sock))
+ return -1;
+
+ {
+ std::vector<int> send_fds;
+ send_fds.push_back(send_sock.get());
+ if (!SendMsg(fd, request.data(), request.size(), send_fds))
+ return -1;
+ }
+
+ // Close the sending end of the socket right away so that if our peer closes
+ // it before sending a response (e.g., from exiting), RecvMsgWithFlags() will
+ // return EOF instead of hanging.
+ send_sock.reset();
+
+ std::vector<ScopedFD> recv_fds;
+ // When porting to OSX keep in mind it doesn't support MSG_NOSIGNAL, so the
+ // sender might get a SIGPIPE.
+ const ssize_t reply_len = RecvMsgWithFlags(
+ recv_sock.get(), reply, max_reply_len, recvmsg_flags, &recv_fds, nullptr);
+ recv_sock.reset();
+ if (reply_len == -1)
+ return -1;
+
+ // If we received more file descriptors than caller expected, then we treat
+ // that as an error.
+ if (recv_fds.size() > (result_fd != nullptr ? 1 : 0)) {
+ NOTREACHED();
+ return -1;
+ }
+
+ if (result_fd)
+ *result_fd = recv_fds.empty() ? -1 : recv_fds[0].release();
+
+ return reply_len;
+}
+#endif // !defined(OS_NACL_NONSFI)
+
+} // namespace base
diff --git a/base/posix/unix_domain_socket.h b/base/posix/unix_domain_socket.h
new file mode 100644
index 0000000000..5c74f07e7a
--- /dev/null
+++ b/base/posix/unix_domain_socket.h
@@ -0,0 +1,111 @@
+// 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_POSIX_UNIX_DOMAIN_SOCKET_H_
+#define BASE_POSIX_UNIX_DOMAIN_SOCKET_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/files/scoped_file.h"
+#include "base/process/process_handle.h"
+#include "build/build_config.h"
+
+namespace base {
+
+class Pickle;
+
+#if !defined(OS_NACL_NONSFI)
+// Creates a connected pair of UNIX-domain SOCK_SEQPACKET sockets, and passes
+// ownership of the newly allocated file descriptors to |one| and |two|.
+// Returns true on success.
+bool BASE_EXPORT CreateSocketPair(ScopedFD* one, ScopedFD* two);
+#endif
+
+class BASE_EXPORT UnixDomainSocket {
+ public:
+ // Maximum number of file descriptors that can be read by RecvMsg().
+ static const size_t kMaxFileDescriptors;
+
+#if !defined(OS_NACL_NONSFI)
+ // Use to enable receiving process IDs in RecvMsgWithPid. Should be called on
+ // the receiving socket (i.e., the socket passed to RecvMsgWithPid). Returns
+ // true if successful.
+ static bool EnableReceiveProcessId(int fd);
+#endif // !defined(OS_NACL_NONSFI)
+
+ // Use sendmsg to write the given msg and include a vector of file
+ // descriptors. Returns true if successful.
+ static bool SendMsg(int fd,
+ const void* msg,
+ size_t length,
+ const std::vector<int>& fds);
+
+ // Use recvmsg to read a message and an array of file descriptors. Returns
+ // -1 on failure. Note: will read, at most, |kMaxFileDescriptors| descriptors.
+ static ssize_t RecvMsg(int fd,
+ void* msg,
+ size_t length,
+ std::vector<ScopedFD>* fds);
+
+ // Same as RecvMsg above, but also returns the sender's process ID (as seen
+ // from the caller's namespace). However, before using this function to
+ // receive process IDs, EnableReceiveProcessId() should be called on the
+ // receiving socket.
+ static ssize_t RecvMsgWithPid(int fd,
+ void* msg,
+ size_t length,
+ std::vector<ScopedFD>* fds,
+ ProcessId* pid);
+
+#if !defined(OS_NACL_NONSFI)
+ // Perform a sendmsg/recvmsg pair.
+ // 1. This process creates a UNIX SEQPACKET socketpair. Using
+ // connection-oriented sockets (SEQPACKET or STREAM) is critical here,
+ // because if one of the ends closes the other one must be notified.
+ // 2. This process writes a request to |fd| with an SCM_RIGHTS control
+ // message containing on end of the fresh socket pair.
+ // 3. This process blocks reading from the other end of the fresh
+ // socketpair.
+ // 4. The target process receives the request, processes it and writes the
+ // reply to the end of the socketpair contained in the request.
+ // 5. This process wakes up and continues.
+ //
+ // fd: descriptor to send the request on
+ // reply: buffer for the reply
+ // reply_len: size of |reply|
+ // result_fd: (may be NULL) the file descriptor returned in the reply
+ // (if any)
+ // request: the bytes to send in the request
+ static ssize_t SendRecvMsg(int fd,
+ uint8_t* reply,
+ unsigned reply_len,
+ int* result_fd,
+ const Pickle& request);
+
+ // Similar to SendRecvMsg(), but |recvmsg_flags| allows to control the flags
+ // of the recvmsg(2) call.
+ static ssize_t SendRecvMsgWithFlags(int fd,
+ uint8_t* reply,
+ unsigned reply_len,
+ int recvmsg_flags,
+ int* result_fd,
+ const Pickle& request);
+#endif // !defined(OS_NACL_NONSFI)
+ private:
+ // Similar to RecvMsg, but allows to specify |flags| for recvmsg(2).
+ static ssize_t RecvMsgWithFlags(int fd,
+ void* msg,
+ size_t length,
+ int flags,
+ std::vector<ScopedFD>* fds,
+ ProcessId* pid);
+};
+
+} // namespace base
+
+#endif // BASE_POSIX_UNIX_DOMAIN_SOCKET_H_
diff --git a/base/posix/unix_domain_socket_linux.cc b/base/posix/unix_domain_socket_linux.cc
deleted file mode 100644
index 8b3094eedf..0000000000
--- a/base/posix/unix_domain_socket_linux.cc
+++ /dev/null
@@ -1,245 +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/posix/unix_domain_socket_linux.h"
-
-#include <errno.h>
-#include <sys/socket.h>
-#include <unistd.h>
-
-#include <vector>
-
-#include "base/files/scoped_file.h"
-#include "base/logging.h"
-#include "base/pickle.h"
-#include "base/posix/eintr_wrapper.h"
-#include "base/stl_util.h"
-#include "build/build_config.h"
-
-#if !defined(OS_NACL_NONSFI)
-#include <sys/uio.h>
-#endif
-
-namespace base {
-
-const size_t UnixDomainSocket::kMaxFileDescriptors = 16;
-
-#if !defined(OS_NACL_NONSFI)
-// Creates a connected pair of UNIX-domain SOCK_SEQPACKET sockets, and passes
-// ownership of the newly allocated file descriptors to |one| and |two|.
-// Returns true on success.
-static bool CreateSocketPair(ScopedFD* one, ScopedFD* two) {
- int raw_socks[2];
- if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, raw_socks) == -1)
- return false;
- one->reset(raw_socks[0]);
- two->reset(raw_socks[1]);
- return true;
-}
-
-// static
-bool UnixDomainSocket::EnableReceiveProcessId(int fd) {
- const int enable = 1;
- return setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &enable, sizeof(enable)) == 0;
-}
-#endif // !defined(OS_NACL_NONSFI)
-
-// static
-bool UnixDomainSocket::SendMsg(int fd,
- const void* buf,
- size_t length,
- const std::vector<int>& fds) {
- struct msghdr msg = {};
- struct iovec iov = { const_cast<void*>(buf), length };
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- char* control_buffer = NULL;
- if (fds.size()) {
- const unsigned control_len = CMSG_SPACE(sizeof(int) * fds.size());
- control_buffer = new char[control_len];
-
- struct cmsghdr* cmsg;
- msg.msg_control = control_buffer;
- msg.msg_controllen = control_len;
- cmsg = CMSG_FIRSTHDR(&msg);
- cmsg->cmsg_level = SOL_SOCKET;
- cmsg->cmsg_type = SCM_RIGHTS;
- cmsg->cmsg_len = CMSG_LEN(sizeof(int) * fds.size());
- memcpy(CMSG_DATA(cmsg), &fds[0], sizeof(int) * fds.size());
- msg.msg_controllen = cmsg->cmsg_len;
- }
-
- // Avoid a SIGPIPE if the other end breaks the connection.
- // Due to a bug in the Linux kernel (net/unix/af_unix.c) MSG_NOSIGNAL isn't
- // regarded for SOCK_SEQPACKET in the AF_UNIX domain, but it is mandated by
- // POSIX.
- const int flags = MSG_NOSIGNAL;
- const ssize_t r = HANDLE_EINTR(sendmsg(fd, &msg, flags));
- const bool ret = static_cast<ssize_t>(length) == r;
- delete[] control_buffer;
- return ret;
-}
-
-// static
-ssize_t UnixDomainSocket::RecvMsg(int fd,
- void* buf,
- size_t length,
- std::vector<ScopedFD>* fds) {
- return UnixDomainSocket::RecvMsgWithPid(fd, buf, length, fds, NULL);
-}
-
-// static
-ssize_t UnixDomainSocket::RecvMsgWithPid(int fd,
- void* buf,
- size_t length,
- std::vector<ScopedFD>* fds,
- ProcessId* pid) {
- return UnixDomainSocket::RecvMsgWithFlags(fd, buf, length, 0, fds, pid);
-}
-
-// static
-ssize_t UnixDomainSocket::RecvMsgWithFlags(int fd,
- void* buf,
- size_t length,
- int flags,
- std::vector<ScopedFD>* fds,
- ProcessId* out_pid) {
- fds->clear();
-
- struct msghdr msg = {};
- struct iovec iov = { buf, length };
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
-
- const size_t kControlBufferSize =
- CMSG_SPACE(sizeof(int) * kMaxFileDescriptors)
-#if !defined(OS_NACL_NONSFI)
- // The PNaCl toolchain for Non-SFI binary build does not support ucred.
- + CMSG_SPACE(sizeof(struct ucred))
-#endif
- ;
- char control_buffer[kControlBufferSize];
- msg.msg_control = control_buffer;
- msg.msg_controllen = sizeof(control_buffer);
-
- const ssize_t r = HANDLE_EINTR(recvmsg(fd, &msg, flags));
- if (r == -1)
- return -1;
-
- int* wire_fds = NULL;
- unsigned wire_fds_len = 0;
- ProcessId pid = -1;
-
- if (msg.msg_controllen > 0) {
- struct cmsghdr* cmsg;
- for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
- const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0);
- if (cmsg->cmsg_level == SOL_SOCKET &&
- cmsg->cmsg_type == SCM_RIGHTS) {
- DCHECK_EQ(payload_len % sizeof(int), 0u);
- DCHECK_EQ(wire_fds, static_cast<void*>(nullptr));
- wire_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg));
- wire_fds_len = payload_len / sizeof(int);
- }
-#if !defined(OS_NACL_NONSFI)
- // The PNaCl toolchain for Non-SFI binary build does not support
- // SCM_CREDENTIALS.
- if (cmsg->cmsg_level == SOL_SOCKET &&
- cmsg->cmsg_type == SCM_CREDENTIALS) {
- DCHECK_EQ(payload_len, sizeof(struct ucred));
- DCHECK_EQ(pid, -1);
- pid = reinterpret_cast<struct ucred*>(CMSG_DATA(cmsg))->pid;
- }
-#endif
- }
- }
-
- if (msg.msg_flags & MSG_TRUNC || msg.msg_flags & MSG_CTRUNC) {
- for (unsigned i = 0; i < wire_fds_len; ++i)
- close(wire_fds[i]);
- errno = EMSGSIZE;
- return -1;
- }
-
- if (wire_fds) {
- for (unsigned i = 0; i < wire_fds_len; ++i)
- fds->push_back(ScopedFD(wire_fds[i])); // TODO(mdempsky): emplace_back
- }
-
- if (out_pid) {
- // |pid| will legitimately be -1 if we read EOF, so only DCHECK if we
- // actually received a message. Unfortunately, Linux allows sending zero
- // length messages, which are indistinguishable from EOF, so this check
- // has false negatives.
- if (r > 0 || msg.msg_controllen > 0)
- DCHECK_GE(pid, 0);
-
- *out_pid = pid;
- }
-
- return r;
-}
-
-#if !defined(OS_NACL_NONSFI)
-// static
-ssize_t UnixDomainSocket::SendRecvMsg(int fd,
- uint8_t* reply,
- unsigned max_reply_len,
- int* result_fd,
- const Pickle& request) {
- return UnixDomainSocket::SendRecvMsgWithFlags(fd, reply, max_reply_len,
- 0, /* recvmsg_flags */
- result_fd, request);
-}
-
-// static
-ssize_t UnixDomainSocket::SendRecvMsgWithFlags(int fd,
- uint8_t* reply,
- unsigned max_reply_len,
- int recvmsg_flags,
- int* result_fd,
- const Pickle& request) {
- // This socketpair is only used for the IPC and is cleaned up before
- // returning.
- ScopedFD recv_sock, send_sock;
- if (!CreateSocketPair(&recv_sock, &send_sock))
- return -1;
-
- {
- std::vector<int> send_fds;
- send_fds.push_back(send_sock.get());
- if (!SendMsg(fd, request.data(), request.size(), send_fds))
- return -1;
- }
-
- // Close the sending end of the socket right away so that if our peer closes
- // it before sending a response (e.g., from exiting), RecvMsgWithFlags() will
- // return EOF instead of hanging.
- send_sock.reset();
-
- std::vector<ScopedFD> recv_fds;
- // When porting to OSX keep in mind it doesn't support MSG_NOSIGNAL, so the
- // sender might get a SIGPIPE.
- const ssize_t reply_len = RecvMsgWithFlags(
- recv_sock.get(), reply, max_reply_len, recvmsg_flags, &recv_fds, NULL);
- recv_sock.reset();
- if (reply_len == -1)
- return -1;
-
- // If we received more file descriptors than caller expected, then we treat
- // that as an error.
- if (recv_fds.size() > (result_fd != NULL ? 1 : 0)) {
- NOTREACHED();
- return -1;
- }
-
- if (result_fd)
- *result_fd = recv_fds.empty() ? -1 : recv_fds[0].release();
-
- return reply_len;
-}
-#endif // !defined(OS_NACL_NONSFI)
-
-} // namespace base
diff --git a/base/posix/unix_domain_socket_linux.h b/base/posix/unix_domain_socket_linux.h
deleted file mode 100644
index 2ba739e108..0000000000
--- a/base/posix/unix_domain_socket_linux.h
+++ /dev/null
@@ -1,104 +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_POSIX_UNIX_DOMAIN_SOCKET_LINUX_H_
-#define BASE_POSIX_UNIX_DOMAIN_SOCKET_LINUX_H_
-
-#include <stddef.h>
-#include <stdint.h>
-#include <sys/types.h>
-#include <vector>
-
-#include "base/base_export.h"
-#include "base/files/scoped_file.h"
-#include "base/process/process_handle.h"
-#include "build/build_config.h"
-
-namespace base {
-
-class Pickle;
-
-class BASE_EXPORT UnixDomainSocket {
- public:
- // Maximum number of file descriptors that can be read by RecvMsg().
- static const size_t kMaxFileDescriptors;
-
-#if !defined(OS_NACL_NONSFI)
- // Use to enable receiving process IDs in RecvMsgWithPid. Should be called on
- // the receiving socket (i.e., the socket passed to RecvMsgWithPid). Returns
- // true if successful.
- static bool EnableReceiveProcessId(int fd);
-#endif // !defined(OS_NACL_NONSFI)
-
- // Use sendmsg to write the given msg and include a vector of file
- // descriptors. Returns true if successful.
- static bool SendMsg(int fd,
- const void* msg,
- size_t length,
- const std::vector<int>& fds);
-
- // Use recvmsg to read a message and an array of file descriptors. Returns
- // -1 on failure. Note: will read, at most, |kMaxFileDescriptors| descriptors.
- static ssize_t RecvMsg(int fd,
- void* msg,
- size_t length,
- std::vector<ScopedFD>* fds);
-
- // Same as RecvMsg above, but also returns the sender's process ID (as seen
- // from the caller's namespace). However, before using this function to
- // receive process IDs, EnableReceiveProcessId() should be called on the
- // receiving socket.
- static ssize_t RecvMsgWithPid(int fd,
- void* msg,
- size_t length,
- std::vector<ScopedFD>* fds,
- ProcessId* pid);
-
-#if !defined(OS_NACL_NONSFI)
- // Perform a sendmsg/recvmsg pair.
- // 1. This process creates a UNIX SEQPACKET socketpair. Using
- // connection-oriented sockets (SEQPACKET or STREAM) is critical here,
- // because if one of the ends closes the other one must be notified.
- // 2. This process writes a request to |fd| with an SCM_RIGHTS control
- // message containing on end of the fresh socket pair.
- // 3. This process blocks reading from the other end of the fresh
- // socketpair.
- // 4. The target process receives the request, processes it and writes the
- // reply to the end of the socketpair contained in the request.
- // 5. This process wakes up and continues.
- //
- // fd: descriptor to send the request on
- // reply: buffer for the reply
- // reply_len: size of |reply|
- // result_fd: (may be NULL) the file descriptor returned in the reply
- // (if any)
- // request: the bytes to send in the request
- static ssize_t SendRecvMsg(int fd,
- uint8_t* reply,
- unsigned reply_len,
- int* result_fd,
- const Pickle& request);
-
- // Similar to SendRecvMsg(), but |recvmsg_flags| allows to control the flags
- // of the recvmsg(2) call.
- static ssize_t SendRecvMsgWithFlags(int fd,
- uint8_t* reply,
- unsigned reply_len,
- int recvmsg_flags,
- int* result_fd,
- const Pickle& request);
-#endif // !defined(OS_NACL_NONSFI)
- private:
- // Similar to RecvMsg, but allows to specify |flags| for recvmsg(2).
- static ssize_t RecvMsgWithFlags(int fd,
- void* msg,
- size_t length,
- int flags,
- std::vector<ScopedFD>* fds,
- ProcessId* pid);
-};
-
-} // namespace base
-
-#endif // BASE_POSIX_UNIX_DOMAIN_SOCKET_LINUX_H_
diff --git a/base/posix/unix_domain_socket_linux_unittest.cc b/base/posix/unix_domain_socket_linux_unittest.cc
deleted file mode 100644
index 3f5173cfc2..0000000000
--- a/base/posix/unix_domain_socket_linux_unittest.cc
+++ /dev/null
@@ -1,163 +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 <stddef.h>
-#include <stdint.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/files/file_util.h"
-#include "base/files/scoped_file.h"
-#include "base/location.h"
-#include "base/pickle.h"
-#include "base/posix/unix_domain_socket_linux.h"
-#include "base/single_thread_task_runner.h"
-#include "base/synchronization/waitable_event.h"
-#include "base/threading/thread.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace base {
-
-namespace {
-
-TEST(UnixDomainSocketTest, SendRecvMsgAbortOnReplyFDClose) {
- Thread message_thread("UnixDomainSocketTest");
- ASSERT_TRUE(message_thread.Start());
-
- int fds[2];
- ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds));
- ScopedFD scoped_fd0(fds[0]);
- ScopedFD scoped_fd1(fds[1]);
-
- // Have the thread send a synchronous message via the socket.
- Pickle request;
- message_thread.task_runner()->PostTask(
- FROM_HERE,
- Bind(IgnoreResult(&UnixDomainSocket::SendRecvMsg), fds[1],
- static_cast<uint8_t*>(NULL), 0U, static_cast<int*>(NULL), request));
-
- // Receive the message.
- std::vector<ScopedFD> message_fds;
- uint8_t buffer[16];
- ASSERT_EQ(static_cast<int>(request.size()),
- UnixDomainSocket::RecvMsg(fds[0], buffer, sizeof(buffer),
- &message_fds));
- ASSERT_EQ(1U, message_fds.size());
-
- // Close the reply FD.
- message_fds.clear();
-
- // Check that the thread didn't get blocked.
- WaitableEvent event(WaitableEvent::ResetPolicy::AUTOMATIC,
- WaitableEvent::InitialState::NOT_SIGNALED);
- message_thread.task_runner()->PostTask(
- FROM_HERE, Bind(&WaitableEvent::Signal, Unretained(&event)));
- ASSERT_TRUE(event.TimedWait(TimeDelta::FromMilliseconds(5000)));
-}
-
-TEST(UnixDomainSocketTest, SendRecvMsgAvoidsSIGPIPE) {
- // Make sure SIGPIPE isn't being ignored.
- struct sigaction act = {}, oldact;
- act.sa_handler = SIG_DFL;
- ASSERT_EQ(0, sigaction(SIGPIPE, &act, &oldact));
- int fds[2];
- ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds));
- ScopedFD scoped_fd1(fds[1]);
- ASSERT_EQ(0, IGNORE_EINTR(close(fds[0])));
-
- // Have the thread send a synchronous message via the socket. Unless the
- // message is sent with MSG_NOSIGNAL, this shall result in SIGPIPE.
- Pickle request;
- ASSERT_EQ(-1,
- UnixDomainSocket::SendRecvMsg(fds[1], static_cast<uint8_t*>(NULL),
- 0U, static_cast<int*>(NULL), request));
- ASSERT_EQ(EPIPE, errno);
- // Restore the SIGPIPE handler.
- ASSERT_EQ(0, sigaction(SIGPIPE, &oldact, NULL));
-}
-
-// Simple sanity check within a single process that receiving PIDs works.
-TEST(UnixDomainSocketTest, RecvPid) {
- int fds[2];
- ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds));
- ScopedFD recv_sock(fds[0]);
- ScopedFD send_sock(fds[1]);
-
- ASSERT_TRUE(UnixDomainSocket::EnableReceiveProcessId(recv_sock.get()));
-
- static const char kHello[] = "hello";
- ASSERT_TRUE(UnixDomainSocket::SendMsg(
- send_sock.get(), kHello, sizeof(kHello), std::vector<int>()));
-
- // Extra receiving buffer space to make sure we really received only
- // sizeof(kHello) bytes and it wasn't just truncated to fit the buffer.
- char buf[sizeof(kHello) + 1];
- ProcessId sender_pid;
- std::vector<ScopedFD> fd_vec;
- const ssize_t nread = UnixDomainSocket::RecvMsgWithPid(
- recv_sock.get(), buf, sizeof(buf), &fd_vec, &sender_pid);
- ASSERT_EQ(sizeof(kHello), static_cast<size_t>(nread));
- ASSERT_EQ(0, memcmp(buf, kHello, sizeof(kHello)));
- ASSERT_EQ(0U, fd_vec.size());
-
- ASSERT_EQ(getpid(), sender_pid);
-}
-
-// Same as above, but send the max number of file descriptors too.
-TEST(UnixDomainSocketTest, RecvPidWithMaxDescriptors) {
- int fds[2];
- ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds));
- ScopedFD recv_sock(fds[0]);
- ScopedFD send_sock(fds[1]);
-
- ASSERT_TRUE(UnixDomainSocket::EnableReceiveProcessId(recv_sock.get()));
-
- static const char kHello[] = "hello";
- std::vector<int> send_fds(UnixDomainSocket::kMaxFileDescriptors,
- send_sock.get());
- ASSERT_TRUE(UnixDomainSocket::SendMsg(
- send_sock.get(), kHello, sizeof(kHello), send_fds));
-
- // Extra receiving buffer space to make sure we really received only
- // sizeof(kHello) bytes and it wasn't just truncated to fit the buffer.
- char buf[sizeof(kHello) + 1];
- ProcessId sender_pid;
- std::vector<ScopedFD> recv_fds;
- const ssize_t nread = UnixDomainSocket::RecvMsgWithPid(
- recv_sock.get(), buf, sizeof(buf), &recv_fds, &sender_pid);
- ASSERT_EQ(sizeof(kHello), static_cast<size_t>(nread));
- ASSERT_EQ(0, memcmp(buf, kHello, sizeof(kHello)));
- ASSERT_EQ(UnixDomainSocket::kMaxFileDescriptors, recv_fds.size());
-
- ASSERT_EQ(getpid(), sender_pid);
-}
-
-// Check that RecvMsgWithPid doesn't DCHECK fail when reading EOF from a
-// disconnected socket.
-TEST(UnixDomianSocketTest, RecvPidDisconnectedSocket) {
- int fds[2];
- ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds));
- ScopedFD recv_sock(fds[0]);
- ScopedFD send_sock(fds[1]);
-
- ASSERT_TRUE(UnixDomainSocket::EnableReceiveProcessId(recv_sock.get()));
-
- send_sock.reset();
-
- char ch;
- ProcessId sender_pid;
- std::vector<ScopedFD> recv_fds;
- const ssize_t nread = UnixDomainSocket::RecvMsgWithPid(
- recv_sock.get(), &ch, sizeof(ch), &recv_fds, &sender_pid);
- ASSERT_EQ(0, nread);
- ASSERT_EQ(-1, sender_pid);
- ASSERT_EQ(0U, recv_fds.size());
-}
-
-} // namespace
-
-} // namespace base
diff --git a/base/posix/unix_domain_socket_unittest.cc b/base/posix/unix_domain_socket_unittest.cc
new file mode 100644
index 0000000000..453064f535
--- /dev/null
+++ b/base/posix/unix_domain_socket_unittest.cc
@@ -0,0 +1,183 @@
+// 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 "build/build_config.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/location.h"
+#include "base/pickle.h"
+#include "base/posix/unix_domain_socket.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+// Callers should use ASSERT_NO_FATAL_FAILURE with this function, to
+// ensure that execution is aborted if the function has assertion failure.
+void CreateSocketPair(int fds[2]) {
+#if defined(OS_MACOSX)
+ // Mac OS does not support SOCK_SEQPACKET.
+ int flags = SOCK_STREAM;
+#else
+ int flags = SOCK_SEQPACKET;
+#endif
+ ASSERT_EQ(0, socketpair(AF_UNIX, flags, 0, fds));
+#if defined(OS_MACOSX)
+ // On OSX an attempt to read or write to a closed socket may generate a
+ // SIGPIPE rather than returning -1, corrected with SO_NOSIGPIPE option.
+ int nosigpipe = 1;
+ ASSERT_EQ(0, setsockopt(fds[0], SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe,
+ sizeof(nosigpipe)));
+ ASSERT_EQ(0, setsockopt(fds[1], SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe,
+ sizeof(nosigpipe)));
+#endif
+}
+
+TEST(UnixDomainSocketTest, SendRecvMsgAbortOnReplyFDClose) {
+ Thread message_thread("UnixDomainSocketTest");
+ ASSERT_TRUE(message_thread.Start());
+ int fds[2];
+ ASSERT_NO_FATAL_FAILURE(CreateSocketPair(fds));
+ ScopedFD scoped_fd0(fds[0]);
+ ScopedFD scoped_fd1(fds[1]);
+
+ // Have the thread send a synchronous message via the socket.
+ Pickle request;
+ message_thread.task_runner()->PostTask(
+ FROM_HERE, BindOnce(IgnoreResult(&UnixDomainSocket::SendRecvMsg), fds[1],
+ nullptr, 0U, nullptr, request));
+
+ // Receive the message.
+ std::vector<ScopedFD> message_fds;
+ uint8_t buffer[16];
+ ASSERT_EQ(
+ static_cast<int>(request.size()),
+ UnixDomainSocket::RecvMsg(fds[0], buffer, sizeof(buffer), &message_fds));
+ ASSERT_EQ(1U, message_fds.size());
+
+ // Close the reply FD.
+ message_fds.clear();
+
+ // Check that the thread didn't get blocked.
+ WaitableEvent event(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ message_thread.task_runner()->PostTask(
+ FROM_HERE, BindOnce(&WaitableEvent::Signal, Unretained(&event)));
+ ASSERT_TRUE(event.TimedWait(TimeDelta::FromMilliseconds(5000)));
+}
+
+TEST(UnixDomainSocketTest, SendRecvMsgAvoidsSIGPIPE) {
+ // Make sure SIGPIPE isn't being ignored.
+ struct sigaction act = {}, oldact;
+ act.sa_handler = SIG_DFL;
+ ASSERT_EQ(0, sigaction(SIGPIPE, &act, &oldact));
+ int fds[2];
+ ASSERT_NO_FATAL_FAILURE(CreateSocketPair(fds));
+ ScopedFD scoped_fd1(fds[1]);
+ ASSERT_EQ(0, IGNORE_EINTR(close(fds[0])));
+
+ // Have the thread send a synchronous message via the socket. Unless the
+ // message is sent with MSG_NOSIGNAL, this shall result in SIGPIPE.
+ Pickle request;
+ ASSERT_EQ(
+ -1, UnixDomainSocket::SendRecvMsg(fds[1], nullptr, 0U, nullptr, request));
+ ASSERT_EQ(EPIPE, errno);
+ // Restore the SIGPIPE handler.
+ ASSERT_EQ(0, sigaction(SIGPIPE, &oldact, nullptr));
+}
+
+// Simple sanity check within a single process that receiving PIDs works.
+TEST(UnixDomainSocketTest, RecvPid) {
+ int fds[2];
+ ASSERT_NO_FATAL_FAILURE(CreateSocketPair(fds));
+ ScopedFD recv_sock(fds[0]);
+ ScopedFD send_sock(fds[1]);
+
+ ASSERT_TRUE(UnixDomainSocket::EnableReceiveProcessId(recv_sock.get()));
+
+ static const char kHello[] = "hello";
+ ASSERT_TRUE(UnixDomainSocket::SendMsg(send_sock.get(), kHello, sizeof(kHello),
+ std::vector<int>()));
+
+ // Extra receiving buffer space to make sure we really received only
+ // sizeof(kHello) bytes and it wasn't just truncated to fit the buffer.
+ char buf[sizeof(kHello) + 1];
+ ProcessId sender_pid;
+ std::vector<ScopedFD> fd_vec;
+ const ssize_t nread = UnixDomainSocket::RecvMsgWithPid(
+ recv_sock.get(), buf, sizeof(buf), &fd_vec, &sender_pid);
+ ASSERT_EQ(sizeof(kHello), static_cast<size_t>(nread));
+ ASSERT_EQ(0, memcmp(buf, kHello, sizeof(kHello)));
+ ASSERT_EQ(0U, fd_vec.size());
+
+ ASSERT_EQ(getpid(), sender_pid);
+}
+
+// Same as above, but send the max number of file descriptors too.
+TEST(UnixDomainSocketTest, RecvPidWithMaxDescriptors) {
+ int fds[2];
+ ASSERT_NO_FATAL_FAILURE(CreateSocketPair(fds));
+ ScopedFD recv_sock(fds[0]);
+ ScopedFD send_sock(fds[1]);
+
+ ASSERT_TRUE(UnixDomainSocket::EnableReceiveProcessId(recv_sock.get()));
+
+ static const char kHello[] = "hello";
+ std::vector<int> send_fds(UnixDomainSocket::kMaxFileDescriptors,
+ send_sock.get());
+ ASSERT_TRUE(UnixDomainSocket::SendMsg(send_sock.get(), kHello, sizeof(kHello),
+ send_fds));
+
+ // Extra receiving buffer space to make sure we really received only
+ // sizeof(kHello) bytes and it wasn't just truncated to fit the buffer.
+ char buf[sizeof(kHello) + 1];
+ ProcessId sender_pid;
+ std::vector<ScopedFD> recv_fds;
+ const ssize_t nread = UnixDomainSocket::RecvMsgWithPid(
+ recv_sock.get(), buf, sizeof(buf), &recv_fds, &sender_pid);
+ ASSERT_EQ(sizeof(kHello), static_cast<size_t>(nread));
+ ASSERT_EQ(0, memcmp(buf, kHello, sizeof(kHello)));
+ ASSERT_EQ(UnixDomainSocket::kMaxFileDescriptors, recv_fds.size());
+
+ ASSERT_EQ(getpid(), sender_pid);
+}
+
+// Check that RecvMsgWithPid doesn't DCHECK fail when reading EOF from a
+// disconnected socket.
+TEST(UnixDomianSocketTest, RecvPidDisconnectedSocket) {
+ int fds[2];
+ ASSERT_NO_FATAL_FAILURE(CreateSocketPair(fds));
+ ScopedFD recv_sock(fds[0]);
+ ScopedFD send_sock(fds[1]);
+
+ ASSERT_TRUE(UnixDomainSocket::EnableReceiveProcessId(recv_sock.get()));
+
+ send_sock.reset();
+
+ char ch;
+ ProcessId sender_pid;
+ std::vector<ScopedFD> recv_fds;
+ const ssize_t nread = UnixDomainSocket::RecvMsgWithPid(
+ recv_sock.get(), &ch, sizeof(ch), &recv_fds, &sender_pid);
+ ASSERT_EQ(0, nread);
+ ASSERT_EQ(-1, sender_pid);
+ ASSERT_EQ(0U, recv_fds.size());
+}
+
+} // namespace
+
+} // namespace base
diff --git a/base/power_monitor/power_monitor.h b/base/power_monitor/power_monitor.h
index e025b32401..b8e02e50ea 100644
--- a/base/power_monitor/power_monitor.h
+++ b/base/power_monitor/power_monitor.h
@@ -27,7 +27,8 @@ class BASE_EXPORT PowerMonitor {
static PowerMonitor* Get();
// Add and remove an observer.
- // Can be called from any thread.
+ // Can be called from any thread. |observer| is notified on the sequence
+ // from which it was registered.
// Must not be called from within a notification callback.
void AddObserver(PowerObserver* observer);
void RemoveObserver(PowerObserver* observer);
@@ -44,7 +45,7 @@ class BASE_EXPORT PowerMonitor {
void NotifySuspend();
void NotifyResume();
- scoped_refptr<ObserverListThreadSafe<PowerObserver> > observers_;
+ scoped_refptr<ObserverListThreadSafe<PowerObserver>> observers_;
std::unique_ptr<PowerMonitorSource> source_;
DISALLOW_COPY_AND_ASSIGN(PowerMonitor);
diff --git a/base/power_monitor/power_monitor_device_source.h b/base/power_monitor/power_monitor_device_source.h
index 1e2c885fa4..fc19b2435f 100644
--- a/base/power_monitor/power_monitor_device_source.h
+++ b/base/power_monitor/power_monitor_device_source.h
@@ -28,6 +28,8 @@ class BASE_EXPORT PowerMonitorDeviceSource : public PowerMonitorSource {
PowerMonitorDeviceSource();
~PowerMonitorDeviceSource() override;
+ void Shutdown() override;
+
#if defined(OS_MACOSX)
// Allocate system resources needed by the PowerMonitor class.
//
diff --git a/base/power_monitor/power_monitor_source.h b/base/power_monitor/power_monitor_source.h
index b69cbf8317..dd22bb619d 100644
--- a/base/power_monitor/power_monitor_source.h
+++ b/base/power_monitor/power_monitor_source.h
@@ -31,15 +31,20 @@ class BASE_EXPORT PowerMonitorSource {
// Is the computer currently on battery power. Can be called on any thread.
bool IsOnBatteryPower();
+ // Called by PowerMonitor just before PowerMonitor destroys both itself and
+ // this instance). After return from this call it is no longer safe for
+ // subclasses to call into PowerMonitor (e.g., via PowerMonitor::Get(). Hence,
+ // subclasses should take any necessary actions here to ensure that after
+ // return from this invocation they will no longer make any calls on
+ // PowerMonitor.
+ virtual void Shutdown() = 0;
+
protected:
friend class PowerMonitorTest;
// Friend function that is allowed to access the protected ProcessPowerEvent.
friend void ProcessPowerEventHelper(PowerEvent);
- // Get the process-wide PowerMonitorSource (if not present, returns NULL).
- static PowerMonitorSource* Get();
-
// ProcessPowerEvent should only be called from a single thread, most likely
// the UI thread or, in child processes, the IO thread.
static void ProcessPowerEvent(PowerEvent event_id);
diff --git a/base/power_monitor/power_observer.h b/base/power_monitor/power_observer.h
index 6be70bba9d..0142b2af32 100644
--- a/base/power_monitor/power_observer.h
+++ b/base/power_monitor/power_observer.h
@@ -23,7 +23,7 @@ class BASE_EXPORT PowerObserver {
virtual void OnResume() {}
protected:
- virtual ~PowerObserver() {}
+ virtual ~PowerObserver() = default;
};
} // namespace base
diff --git a/base/process/internal_aix.cc b/base/process/internal_aix.cc
new file mode 100644
index 0000000000..7f03aee379
--- /dev/null
+++ b/base/process/internal_aix.cc
@@ -0,0 +1,155 @@
+// 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/process/internal_aix.h"
+
+#include <sys/procfs.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+
+// Not defined on AIX by default.
+#define NAME_MAX 255
+
+namespace base {
+namespace internalAIX {
+
+const char kProcDir[] = "/proc";
+
+const char kStatFile[] = "psinfo"; // AIX specific
+
+FilePath GetProcPidDir(pid_t pid) {
+ return FilePath(kProcDir).Append(IntToString(pid));
+}
+
+pid_t ProcDirSlotToPid(const char* d_name) {
+ int i;
+ for (i = 0; i < NAME_MAX && d_name[i]; ++i) {
+ if (!IsAsciiDigit(d_name[i])) {
+ return 0;
+ }
+ }
+ if (i == NAME_MAX)
+ return 0;
+
+ // Read the process's command line.
+ pid_t pid;
+ std::string pid_string(d_name);
+ if (!StringToInt(pid_string, &pid)) {
+ NOTREACHED();
+ return 0;
+ }
+ return pid;
+}
+
+bool ReadProcFile(const FilePath& file, struct psinfo* info) {
+ // Synchronously reading files in /proc is safe.
+ ThreadRestrictions::ScopedAllowIO allow_io;
+ int fileId;
+ if ((fileId = open(file.value().c_str(), O_RDONLY)) < 0) {
+ DLOG(WARNING) << "Failed to open " << file.MaybeAsASCII()
+ << " errno = " << errno;
+ return false;
+ }
+
+ if (read(fileId, info, sizeof(*info)) < 0) {
+ DLOG(WARNING) << "Failed to read " << file.MaybeAsASCII()
+ << " errno = " << errno;
+ return false;
+ }
+
+ return true;
+}
+
+bool ReadProcStats(pid_t pid, struct psinfo* info) {
+ FilePath stat_file = internalAIX::GetProcPidDir(pid).Append(kStatFile);
+ return ReadProcFile(stat_file, info);
+}
+
+bool ParseProcStats(struct psinfo& stats_data,
+ std::vector<std::string>* proc_stats) {
+ // The stat file is formatted as:
+ // struct psinfo
+ // see -
+ // https://www.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.files/proc.htm
+ proc_stats->clear();
+ // PID.
+ proc_stats->push_back(IntToString(stats_data.pr_pid));
+ // Process name without parentheses. // 1
+ proc_stats->push_back(stats_data.pr_fname);
+ // Process State (Not available) // 2
+ proc_stats->push_back("0");
+ // Process id of parent // 3
+ proc_stats->push_back(IntToString(stats_data.pr_ppid));
+
+ // Process group id // 4
+ proc_stats->push_back(IntToString(stats_data.pr_pgid));
+
+ return true;
+}
+
+typedef std::map<std::string, std::string> ProcStatMap;
+void ParseProcStat(const std::string& contents, ProcStatMap* output) {
+ StringPairs key_value_pairs;
+ SplitStringIntoKeyValuePairs(contents, ' ', '\n', &key_value_pairs);
+ for (size_t i = 0; i < key_value_pairs.size(); ++i) {
+ output->insert(key_value_pairs[i]);
+ }
+}
+
+int64_t GetProcStatsFieldAsInt64(const std::vector<std::string>& proc_stats,
+ ProcStatsFields field_num) {
+ DCHECK_GE(field_num, VM_PPID);
+ CHECK_LT(static_cast<size_t>(field_num), proc_stats.size());
+
+ int64_t value;
+ return StringToInt64(proc_stats[field_num], &value) ? value : 0;
+}
+
+size_t GetProcStatsFieldAsSizeT(const std::vector<std::string>& proc_stats,
+ ProcStatsFields field_num) {
+ DCHECK_GE(field_num, VM_PPID);
+ CHECK_LT(static_cast<size_t>(field_num), proc_stats.size());
+
+ size_t value;
+ return StringToSizeT(proc_stats[field_num], &value) ? value : 0;
+}
+
+int64_t ReadProcStatsAndGetFieldAsInt64(pid_t pid, ProcStatsFields field_num) {
+ struct psinfo stats_data;
+ if (!ReadProcStats(pid, &stats_data))
+ return 0;
+ std::vector<std::string> proc_stats;
+ if (!ParseProcStats(stats_data, &proc_stats))
+ return 0;
+
+ return GetProcStatsFieldAsInt64(proc_stats, field_num);
+}
+
+size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid, ProcStatsFields field_num) {
+ struct psinfo stats_data;
+ if (!ReadProcStats(pid, &stats_data))
+ return 0;
+ std::vector<std::string> proc_stats;
+ if (!ParseProcStats(stats_data, &proc_stats))
+ return 0;
+ return GetProcStatsFieldAsSizeT(proc_stats, field_num);
+}
+
+} // namespace internalAIX
+} // namespace base
diff --git a/base/process/internal_aix.h b/base/process/internal_aix.h
new file mode 100644
index 0000000000..d9694ffc2e
--- /dev/null
+++ b/base/process/internal_aix.h
@@ -0,0 +1,84 @@
+// 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.
+
+// This file contains internal routines that are called by other files in
+// base/process/.
+
+#ifndef BASE_PROCESS_INTERNAL_AIX_H_
+#define BASE_PROCESS_INTERNAL_AIX_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include "base/files/file_path.h"
+
+namespace base {
+
+namespace internalAIX {
+
+// "/proc"
+extern const char kProcDir[];
+
+// "psinfo"
+extern const char kStatFile[];
+
+// Returns a FilePath to "/proc/pid".
+base::FilePath GetProcPidDir(pid_t pid);
+
+// Take a /proc directory entry named |d_name|, and if it is the directory for
+// a process, convert it to a pid_t.
+// Returns 0 on failure.
+// e.g. /proc/self/ will return 0, whereas /proc/1234 will return 1234.
+pid_t ProcDirSlotToPid(const char* d_name);
+
+// Reads /proc/<pid>/stat into |buffer|. Returns true if the file can be read
+// and is non-empty.
+bool ReadProcStats(pid_t pid, std::string* buffer);
+
+// Takes |stats_data| and populates |proc_stats| with the values split by
+// spaces. Taking into account the 2nd field may, in itself, contain spaces.
+// Returns true if successful.
+bool ParseProcStats(const std::string& stats_data,
+ std::vector<std::string>* proc_stats);
+
+// Fields from /proc/<pid>/psinfo.
+// If the ordering ever changes, carefully review functions that use these
+// values.
+// For AIX this is the bare minimum that we need. Most of the commented out
+// fields can still be extracted but currently none of these are required.
+enum ProcStatsFields {
+ VM_COMM = 1, // Filename of executable, without parentheses.
+ // VM_STATE = 2, // Letter indicating the state of the process.
+ VM_PPID = 3, // PID of the parent.
+ VM_PGRP = 4, // Process group id.
+ // VM_UTIME = 13, // Time scheduled in user mode in clock ticks.
+ // VM_STIME = 14, // Time scheduled in kernel mode in clock ticks.
+ // VM_NUMTHREADS = 19, // Number of threads.
+ // VM_STARTTIME = 21, // The time the process started in clock ticks.
+ // VM_VSIZE = 22, // Virtual memory size in bytes.
+ // VM_RSS = 23, // Resident Set Size in pages.
+};
+
+// Reads the |field_num|th field from |proc_stats|. Returns 0 on failure.
+// This version does not handle the first 3 values, since the first value is
+// simply |pid|, and the next two values are strings.
+int64_t GetProcStatsFieldAsInt64(const std::vector<std::string>& proc_stats,
+ ProcStatsFields field_num);
+
+// Same as GetProcStatsFieldAsInt64(), but for size_t values.
+size_t GetProcStatsFieldAsSizeT(const std::vector<std::string>& proc_stats,
+ ProcStatsFields field_num);
+
+// Convenience wrapper around GetProcStatsFieldAsInt64(), ParseProcStats() and
+// ReadProcStats(). See GetProcStatsFieldAsInt64() for details.
+int64_t ReadProcStatsAndGetFieldAsInt64(pid_t pid, ProcStatsFields field_num);
+
+// Same as ReadProcStatsAndGetFieldAsInt64() but for size_t values.
+size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid, ProcStatsFields field_num);
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_PROCESS_INTERNAL_AIX_H_
diff --git a/base/process/internal_linux.cc b/base/process/internal_linux.cc
index c7820040ce..7f38fff23a 100644
--- a/base/process/internal_linux.cc
+++ b/base/process/internal_linux.cc
@@ -19,6 +19,11 @@
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
+// Not defined on AIX by default.
+#if defined(OS_AIX)
+#define NAME_MAX 255
+#endif
+
namespace base {
namespace internal {
diff --git a/base/process/internal_linux.h b/base/process/internal_linux.h
index 99d0fd5af1..d8904fd110 100644
--- a/base/process/internal_linux.h
+++ b/base/process/internal_linux.h
@@ -50,16 +50,18 @@ bool ParseProcStats(const std::string& stats_data,
// If the ordering ever changes, carefully review functions that use these
// values.
enum ProcStatsFields {
- VM_COMM = 1, // Filename of executable, without parentheses.
- VM_STATE = 2, // Letter indicating the state of the process.
- VM_PPID = 3, // PID of the parent.
- VM_PGRP = 4, // Process group id.
- VM_UTIME = 13, // Time scheduled in user mode in clock ticks.
- VM_STIME = 14, // Time scheduled in kernel mode in clock ticks.
- VM_NUMTHREADS = 19, // Number of threads.
- VM_STARTTIME = 21, // The time the process started in clock ticks.
- VM_VSIZE = 22, // Virtual memory size in bytes.
- VM_RSS = 23, // Resident Set Size in pages.
+ VM_COMM = 1, // Filename of executable, without parentheses.
+ VM_STATE = 2, // Letter indicating the state of the process.
+ VM_PPID = 3, // PID of the parent.
+ VM_PGRP = 4, // Process group id.
+ VM_MINFLT = 9, // Minor page fault count excluding children.
+ VM_MAJFLT = 11, // Major page fault count excluding children.
+ VM_UTIME = 13, // Time scheduled in user mode in clock ticks.
+ VM_STIME = 14, // Time scheduled in kernel mode in clock ticks.
+ VM_NUMTHREADS = 19, // Number of threads.
+ VM_STARTTIME = 21, // The time the process started in clock ticks.
+ VM_VSIZE = 22, // Virtual memory size in bytes.
+ VM_RSS = 23, // Resident Set Size in pages.
};
// Reads the |field_num|th field from |proc_stats|. Returns 0 on failure.
diff --git a/base/process/kill.cc b/base/process/kill.cc
index 5d8ba6a2d7..0332ac0303 100644
--- a/base/process/kill.cc
+++ b/base/process/kill.cc
@@ -4,7 +4,10 @@
#include "base/process/kill.h"
+#include "base/bind.h"
#include "base/process/process_iterator.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/time/time.h"
namespace base {
@@ -15,9 +18,44 @@ bool KillProcesses(const FilePath::StringType& executable_name,
NamedProcessIterator iter(executable_name, filter);
while (const ProcessEntry* entry = iter.NextProcessEntry()) {
Process process = Process::Open(entry->pid());
+ // Sometimes process open fails. This would cause a DCHECK in
+ // process.Terminate(). Maybe the process has killed itself between the
+ // time the process list was enumerated and the time we try to open the
+ // process?
+ if (!process.IsValid()) {
+ result = false;
+ continue;
+ }
result &= process.Terminate(exit_code, true);
}
return result;
}
+#if defined(OS_WIN) || defined(OS_FUCHSIA)
+// Common implementation for platforms under which |process| is a handle to
+// the process, rather than an identifier that must be "reaped".
+void EnsureProcessTerminated(Process process) {
+ DCHECK(!process.is_current());
+
+ if (process.WaitForExitWithTimeout(TimeDelta(), nullptr))
+ return;
+
+ PostDelayedTaskWithTraits(
+ FROM_HERE,
+ {TaskPriority::BACKGROUND, TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+ BindOnce(
+ [](Process process) {
+ if (process.WaitForExitWithTimeout(TimeDelta(), nullptr))
+ return;
+#if defined(OS_WIN)
+ process.Terminate(win::kProcessKilledExitCode, false);
+#else
+ process.Terminate(-1, false);
+#endif
+ },
+ std::move(process)),
+ TimeDelta::FromSeconds(2));
+}
+#endif // defined(OS_WIN) || defined(OS_FUCHSIA)
+
} // namespace base
diff --git a/base/process/kill.h b/base/process/kill.h
index 6d410e02a0..9acfb8a738 100644
--- a/base/process/kill.h
+++ b/base/process/kill.h
@@ -24,6 +24,20 @@ namespace win {
// See definition in sandbox/win/src/sandbox_types.h
const DWORD kSandboxFatalMemoryExceeded = 7012;
+// Exit codes with special meanings on Windows.
+const DWORD kNormalTerminationExitCode = 0;
+const DWORD kDebuggerInactiveExitCode = 0xC0000354;
+const DWORD kKeyboardInterruptExitCode = 0xC000013A;
+const DWORD kDebuggerTerminatedExitCode = 0x40010004;
+
+// This exit code is used by the Windows task manager when it kills a
+// process. It's value is obviously not that unique, and it's
+// surprising to me that the task manager uses this value, but it
+// seems to be common practice on Windows to test for it as an
+// indication that the task manager has killed something if the
+// process goes away.
+const DWORD kProcessKilledExitCode = 1;
+
} // namespace win
#endif // OS_WIN
@@ -70,9 +84,8 @@ BASE_EXPORT bool KillProcessGroup(ProcessHandle process_group_id);
// Get the termination status of the process by interpreting the
// circumstances of the child process' death. |exit_code| is set to
-// the status returned by waitpid() on POSIX, and from
-// GetExitCodeProcess() on Windows. |exit_code| may be NULL if the
-// caller is not interested in it. Note that on Linux, this function
+// the status returned by waitpid() on POSIX, and from GetExitCodeProcess() on
+// Windows, and may not be null. Note that on Linux, this function
// will only return a useful result the first time it is called after
// the child exits (because it will reap the child and the information
// will no longer be available).
@@ -97,8 +110,25 @@ BASE_EXPORT TerminationStatus GetTerminationStatus(ProcessHandle handle,
//
BASE_EXPORT TerminationStatus GetKnownDeadTerminationStatus(
ProcessHandle handle, int* exit_code);
+
+#if defined(OS_LINUX)
+// Spawns a thread to wait asynchronously for the child |process| to exit
+// and then reaps it.
+BASE_EXPORT void EnsureProcessGetsReaped(Process process);
+#endif // defined(OS_LINUX)
#endif // defined(OS_POSIX)
+// Registers |process| to be asynchronously monitored for termination, forcibly
+// terminated if necessary, and reaped on exit. The caller should have signalled
+// |process| to exit before calling this API. The API will allow a couple of
+// seconds grace period before forcibly terminating |process|.
+// TODO(https://crbug.com/806451): The Mac implementation currently blocks the
+// calling thread for up to two seconds.
+BASE_EXPORT void EnsureProcessTerminated(Process process);
+
+// These are only sparingly used, and not needed on Fuchsia. They could be
+// implemented if necessary.
+#if !defined(OS_FUCHSIA)
// Wait for all the processes based on the named executable to exit. If filter
// is non-null, then only processes selected by the filter are waited on.
// Returns after all processes have exited or wait_milliseconds have expired.
@@ -118,28 +148,7 @@ BASE_EXPORT bool CleanupProcesses(const FilePath::StringType& executable_name,
base::TimeDelta wait,
int exit_code,
const ProcessFilter* filter);
-
-// This method ensures that the specified process eventually terminates, and
-// then it closes the given process handle.
-//
-// It assumes that the process has already been signalled to exit, and it
-// begins by waiting a small amount of time for it to exit. If the process
-// does not appear to have exited, then this function starts to become
-// aggressive about ensuring that the process terminates.
-//
-// On Linux this method does not block the calling thread.
-// On OS X this method may block for up to 2 seconds.
-//
-// NOTE: The process must have been opened with the PROCESS_TERMINATE and
-// SYNCHRONIZE permissions.
-//
-BASE_EXPORT void EnsureProcessTerminated(Process process);
-
-#if defined(OS_POSIX) && !defined(OS_MACOSX)
-// The nicer version of EnsureProcessTerminated() that is patient and will
-// wait for |pid| to finish and then reap it.
-BASE_EXPORT void EnsureProcessGetsReaped(ProcessId pid);
-#endif
+#endif // !defined(OS_FUCHSIA)
} // namespace base
diff --git a/base/process/kill_posix.cc b/base/process/kill_posix.cc
index 4dc60ef2a2..4b52d8b0ef 100644
--- a/base/process/kill_posix.cc
+++ b/base/process/kill_posix.cc
@@ -10,13 +10,13 @@
#include <sys/wait.h>
#include <unistd.h>
+#include "base/debug/activity_tracker.h"
#include "base/files/file_util.h"
-#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/process_iterator.h"
-#include "base/synchronization/waitable_event.h"
+#include "base/task_scheduler/post_task.h"
#include "base/threading/platform_thread.h"
#include "build/build_config.h"
@@ -27,23 +27,22 @@ namespace {
TerminationStatus GetTerminationStatusImpl(ProcessHandle handle,
bool can_block,
int* exit_code) {
+ DCHECK(exit_code);
+
int status = 0;
const pid_t result = HANDLE_EINTR(waitpid(handle, &status,
can_block ? 0 : WNOHANG));
if (result == -1) {
DPLOG(ERROR) << "waitpid(" << handle << ")";
- if (exit_code)
- *exit_code = 0;
+ *exit_code = 0;
return TERMINATION_STATUS_NORMAL_TERMINATION;
} else if (result == 0) {
// the child hasn't exited yet.
- if (exit_code)
- *exit_code = 0;
+ *exit_code = 0;
return TERMINATION_STATUS_STILL_RUNNING;
}
- if (exit_code)
- *exit_code = status;
+ *exit_code = status;
if (WIFSIGNALED(status)) {
switch (WTERMSIG(status)) {
@@ -136,97 +135,50 @@ bool CleanupProcesses(const FilePath::StringType& executable_name,
namespace {
-// Return true if the given child is dead. This will also reap the process.
-// Doesn't block.
-static bool IsChildDead(pid_t child) {
- const pid_t result = HANDLE_EINTR(waitpid(child, NULL, WNOHANG));
- if (result == -1) {
- DPLOG(ERROR) << "waitpid(" << child << ")";
- NOTREACHED();
- } else if (result > 0) {
- // The child has died.
- return true;
- }
-
- return false;
-}
-
-// A thread class which waits for the given child to exit and reaps it.
-// If the child doesn't exit within a couple of seconds, kill it.
class BackgroundReaper : public PlatformThread::Delegate {
public:
- BackgroundReaper(pid_t child, unsigned timeout)
- : child_(child),
- timeout_(timeout) {
- }
+ BackgroundReaper(base::Process child_process, const TimeDelta& wait_time)
+ : child_process_(std::move(child_process)), wait_time_(wait_time) {}
- // Overridden from PlatformThread::Delegate:
void ThreadMain() override {
- WaitForChildToDie();
- delete this;
- }
-
- void WaitForChildToDie() {
- // Wait forever case.
- if (timeout_ == 0) {
- pid_t r = HANDLE_EINTR(waitpid(child_, NULL, 0));
- if (r != child_) {
- DPLOG(ERROR) << "While waiting for " << child_
- << " to terminate, we got the following result: " << r;
- }
- return;
- }
-
- // There's no good way to wait for a specific child to exit in a timed
- // fashion. (No kqueue on Linux), so we just loop and sleep.
-
- // Wait for 2 * timeout_ 500 milliseconds intervals.
- for (unsigned i = 0; i < 2 * timeout_; ++i) {
- PlatformThread::Sleep(TimeDelta::FromMilliseconds(500));
- if (IsChildDead(child_))
- return;
- }
-
- if (kill(child_, SIGKILL) == 0) {
- // SIGKILL is uncatchable. Since the signal was delivered, we can
- // just wait for the process to die now in a blocking manner.
- if (HANDLE_EINTR(waitpid(child_, NULL, 0)) < 0)
- DPLOG(WARNING) << "waitpid";
- } else {
- DLOG(ERROR) << "While waiting for " << child_ << " to terminate we"
- << " failed to deliver a SIGKILL signal (" << errno << ").";
+ if (!wait_time_.is_zero()) {
+ child_process_.WaitForExitWithTimeout(wait_time_, nullptr);
+ kill(child_process_.Handle(), SIGKILL);
}
+ child_process_.WaitForExit(nullptr);
+ delete this;
}
private:
- const pid_t child_;
- // Number of seconds to wait, if 0 then wait forever and do not attempt to
- // kill |child_|.
- const unsigned timeout_;
-
+ Process child_process_;
+ const TimeDelta wait_time_;
DISALLOW_COPY_AND_ASSIGN(BackgroundReaper);
};
} // namespace
void EnsureProcessTerminated(Process process) {
- // If the child is already dead, then there's nothing to do.
- if (IsChildDead(process.Pid()))
+ DCHECK(!process.is_current());
+
+ if (process.WaitForExitWithTimeout(TimeDelta(), nullptr))
return;
- const unsigned timeout = 2; // seconds
- BackgroundReaper* reaper = new BackgroundReaper(process.Pid(), timeout);
- PlatformThread::CreateNonJoinable(0, reaper);
+ PlatformThread::CreateNonJoinable(
+ 0, new BackgroundReaper(std::move(process), TimeDelta::FromSeconds(2)));
}
-void EnsureProcessGetsReaped(ProcessId pid) {
+#if defined(OS_LINUX)
+void EnsureProcessGetsReaped(Process process) {
+ DCHECK(!process.is_current());
+
// If the child is already dead, then there's nothing to do.
- if (IsChildDead(pid))
+ if (process.WaitForExitWithTimeout(TimeDelta(), nullptr))
return;
- BackgroundReaper* reaper = new BackgroundReaper(pid, 0);
- PlatformThread::CreateNonJoinable(0, reaper);
+ PlatformThread::CreateNonJoinable(
+ 0, new BackgroundReaper(std::move(process), TimeDelta()));
}
+#endif // defined(OS_LINUX)
#endif // !defined(OS_MACOSX)
#endif // !defined(OS_NACL_NONSFI)
diff --git a/base/process/launch.h b/base/process/launch.h
index 99a7280cb3..7a2def2ef4 100644
--- a/base/process/launch.h
+++ b/base/process/launch.h
@@ -21,10 +21,15 @@
#include "base/strings/string_piece.h"
#include "build/build_config.h"
-#if defined(OS_POSIX)
-#include "base/posix/file_descriptor_shuffle.h"
-#elif defined(OS_WIN)
+#if defined(OS_WIN)
#include <windows.h>
+#elif defined(OS_FUCHSIA)
+#include <lib/fdio/spawn.h>
+#include <zircon/types.h>
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include "base/posix/file_descriptor_shuffle.h"
#endif
namespace base {
@@ -33,20 +38,31 @@ class CommandLine;
#if defined(OS_WIN)
typedef std::vector<HANDLE> HandlesToInheritVector;
-#endif
-// TODO(viettrungluu): Only define this on POSIX?
-typedef std::vector<std::pair<int, int> > FileHandleMappingVector;
+#elif defined(OS_FUCHSIA)
+struct PathToTransfer {
+ base::FilePath path;
+ zx_handle_t handle;
+};
+struct HandleToTransfer {
+ uint32_t id;
+ zx_handle_t handle;
+};
+typedef std::vector<HandleToTransfer> HandlesToTransferVector;
+typedef std::vector<std::pair<int, int>> FileHandleMappingVector;
+#elif defined(OS_POSIX)
+typedef std::vector<std::pair<int, int>> FileHandleMappingVector;
+#endif // defined(OS_WIN)
// Options for launching a subprocess that are passed to LaunchProcess().
// The default constructor constructs the object with default options.
struct BASE_EXPORT LaunchOptions {
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Delegate to be run in between fork and exec in the subprocess (see
// pre_exec_delegate below)
class BASE_EXPORT PreExecDelegate {
public:
- PreExecDelegate() {}
- virtual ~PreExecDelegate() {}
+ PreExecDelegate() = default;
+ virtual ~PreExecDelegate() = default;
// Since this is to be run between fork and exec, and fork may have happened
// while multiple threads were running, this function needs to be async
@@ -71,17 +87,35 @@ struct BASE_EXPORT LaunchOptions {
#if defined(OS_WIN)
bool start_hidden = false;
- // If non-null, inherit exactly the list of handles in this vector (these
- // handles must be inheritable).
- HandlesToInheritVector* handles_to_inherit = nullptr;
-
- // If true, the new process inherits handles from the parent. In production
- // code this flag should be used only when running short-lived, trusted
- // binaries, because open handles from other libraries and subsystems will
- // leak to the child process, causing errors such as open socket hangs.
- // Note: If |handles_to_inherit| is non-null, this flag is ignored and only
- // those handles will be inherited.
- bool inherit_handles = false;
+ // Windows can inherit handles when it launches child processes.
+ // See https://blogs.msdn.microsoft.com/oldnewthing/20111216-00/?p=8873
+ // for a good overview of Windows handle inheritance.
+ //
+ // Implementation note: it might be nice to implement in terms of
+ // base::Optional<>, but then the natural default state (vector not present)
+ // would be "all inheritable handles" while we want "no inheritance."
+ enum class Inherit {
+ // Only those handles in |handles_to_inherit| vector are inherited. If the
+ // vector is empty, no handles are inherited. The handles in the vector must
+ // all be inheritable.
+ kSpecific,
+
+ // All handles in the current process which are inheritable are inherited.
+ // In production code this flag should be used only when running
+ // short-lived, trusted binaries, because open handles from other libraries
+ // and subsystems will leak to the child process, causing errors such as
+ // open socket hangs. There are also race conditions that can cause handle
+ // over-sharing.
+ //
+ // |handles_to_inherit| must be null.
+ //
+ // DEPRECATED. THIS SHOULD NOT BE USED. Explicitly map all handles that
+ // need to be shared in new code.
+ // TODO(brettw) bug 748258: remove this.
+ kAll
+ };
+ Inherit inherit_mode = Inherit::kSpecific;
+ HandlesToInheritVector handles_to_inherit;
// If non-null, runs as if the user represented by the token had launched it.
// Whether the application is visible on the interactive desktop depends on
@@ -100,10 +134,16 @@ struct BASE_EXPORT LaunchOptions {
// the job object fails.
HANDLE job_handle = nullptr;
- // Handles for the redirection of stdin, stdout and stderr. The handles must
- // be inheritable. Caller should either set all three of them or none (i.e.
- // there is no way to redirect stderr without redirecting stdin). The
- // |inherit_handles| flag must be set to true when redirecting stdio stream.
+ // Handles for the redirection of stdin, stdout and stderr. The caller should
+ // either set all three of them or none (i.e. there is no way to redirect
+ // stderr without redirecting stdin).
+ //
+ // The handles must be inheritable. Pseudo handles are used when stdout and
+ // stderr redirect to the console. In that case, GetFileType() will return
+ // FILE_TYPE_CHAR and they're automatically inherited by child processes. See
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms682075.aspx
+ // Otherwise, the caller must ensure that the |inherit_mode| and/or
+ // |handles_to_inherit| set so that the handles are inherited.
HANDLE stdin_handle = nullptr;
HANDLE stdout_handle = nullptr;
HANDLE stderr_handle = nullptr;
@@ -112,7 +152,11 @@ struct BASE_EXPORT LaunchOptions {
// CREATE_BREAKAWAY_FROM_JOB flag which allows it to breakout of the parent
// job if any.
bool force_breakaway_from_job_ = false;
-#else // !defined(OS_WIN)
+
+ // If set to true, permission to bring windows to the foreground is passed to
+ // the launched process if the current process has such permission.
+ bool grant_foreground_privilege = false;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
// Set/unset environment variables. These are applied on top of the parent
// process environment. Empty (the default) means to inherit the same
// environment. See AlterEnvironment().
@@ -122,21 +166,10 @@ struct BASE_EXPORT LaunchOptions {
// |environ|.
bool clear_environ = false;
- // If non-null, remap file descriptors according to the mapping of
- // src fd->dest fd to propagate FDs into the child process.
- // This pointer is owned by the caller and must live through the
- // call to LaunchProcess().
- const FileHandleMappingVector* fds_to_remap = nullptr;
-
- // Each element is an RLIMIT_* constant that should be raised to its
- // rlim_max. This pointer is owned by the caller and must live through
- // the call to LaunchProcess().
- const std::vector<int>* maximize_rlimits = nullptr;
-
- // If true, start the process in a new process group, instead of
- // inheriting the parent's process group. The pgid of the child process
- // will be the same as its pid.
- bool new_process_group = false;
+ // Remap file descriptors according to the mapping of src_fd->dest_fd to
+ // propagate FDs into the child process.
+ FileHandleMappingVector fds_to_remap;
+#endif // defined(OS_WIN)
#if defined(OS_LINUX)
// If non-zero, start the process using clone(), using flags as provided.
@@ -153,6 +186,37 @@ struct BASE_EXPORT LaunchOptions {
bool kill_on_parent_death = false;
#endif // defined(OS_LINUX)
+#if defined(OS_FUCHSIA)
+ // If valid, launches the application in that job object.
+ zx_handle_t job_handle = ZX_HANDLE_INVALID;
+
+ // Specifies additional handles to transfer (not duplicate) to the child
+ // process. Each entry is an <id,handle> pair, with an |id| created using the
+ // PA_HND() macro. The child retrieves the handle
+ // |zx_take_startup_handle(id)|. The supplied handles are consumed by
+ // LaunchProcess() even on failure.
+ HandlesToTransferVector handles_to_transfer;
+
+ // Specifies which basic capabilities to grant to the child process.
+ // By default the child process will receive the caller's complete namespace,
+ // access to the current base::fuchsia::DefaultJob(), handles for stdio and
+ // access to the dynamic library loader.
+ // Note that the child is always provided access to the loader service.
+ uint32_t spawn_flags = FDIO_SPAWN_CLONE_NAMESPACE | FDIO_SPAWN_CLONE_STDIO |
+ FDIO_SPAWN_CLONE_JOB;
+
+ // Specifies paths to clone from the calling process' namespace into that of
+ // the child process. If |paths_to_clone| is empty then the process will
+ // receive either a full copy of the parent's namespace, or an empty one,
+ // depending on whether FDIO_SPAWN_CLONE_NAMESPACE is set.
+ std::vector<FilePath> paths_to_clone;
+
+ // Specifies handles which will be installed as files or directories in the
+ // child process' namespace. Paths installed by |paths_to_clone| will be
+ // overridden by these entries.
+ std::vector<PathToTransfer> paths_to_transfer;
+#endif // defined(OS_FUCHSIA)
+
#if defined(OS_POSIX)
// If not empty, launch the specified executable instead of
// cmdline.GetProgram(). This is useful when it is necessary to pass a custom
@@ -166,6 +230,16 @@ struct BASE_EXPORT LaunchOptions {
// code running in this delegate essentially needs to be async-signal safe
// (see man 7 signal for a list of allowed functions).
PreExecDelegate* pre_exec_delegate = nullptr;
+
+ // Each element is an RLIMIT_* constant that should be raised to its
+ // rlim_max. This pointer is owned by the caller and must live through
+ // the call to LaunchProcess().
+ const std::vector<int>* maximize_rlimits = nullptr;
+
+ // If true, start the process in a new process group, instead of
+ // inheriting the parent's process group. The pgid of the child process
+ // will be the same as its pid.
+ bool new_process_group = false;
#endif // defined(OS_POSIX)
#if defined(OS_CHROMEOS)
@@ -173,7 +247,6 @@ struct BASE_EXPORT LaunchOptions {
// process' controlling terminal.
int ctrl_terminal_fd = -1;
#endif // defined(OS_CHROMEOS)
-#endif // !defined(OS_WIN)
};
// Launch a process via the command line |cmdline|.
@@ -214,7 +287,7 @@ BASE_EXPORT Process LaunchProcess(const string16& cmdline,
BASE_EXPORT Process LaunchElevatedProcess(const CommandLine& cmdline,
const LaunchOptions& options);
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
// A POSIX-specific version of LaunchProcess that takes an argv array
// instead of a CommandLine. Useful for situations where you need to
// control the command line arguments directly, but prefer the
@@ -226,7 +299,7 @@ BASE_EXPORT Process LaunchProcess(const std::vector<std::string>& argv,
// given multimap. Only call this function in a child process where you know
// that there aren't any other threads.
BASE_EXPORT void CloseSuperfluousFds(const InjectiveMultimap& saved_map);
-#endif // defined(OS_POSIX)
+#endif // defined(OS_WIN)
#if defined(OS_WIN)
// Set |job_object|'s JOBOBJECT_EXTENDED_LIMIT_INFORMATION
@@ -248,14 +321,19 @@ BASE_EXPORT bool GetAppOutput(const CommandLine& cl, std::string* output);
BASE_EXPORT bool GetAppOutputAndError(const CommandLine& cl,
std::string* output);
+// A version of |GetAppOutput()| which also returns the exit code of the
+// executed command. Returns true if the application runs and exits cleanly. If
+// this is the case the exit code of the application is available in
+// |*exit_code|.
+BASE_EXPORT bool GetAppOutputWithExitCode(const CommandLine& cl,
+ std::string* output, int* exit_code);
+
#if defined(OS_WIN)
// A Windows-specific version of GetAppOutput that takes a command line string
// instead of a CommandLine object. Useful for situations where you need to
// control the command line arguments directly.
BASE_EXPORT bool GetAppOutput(const StringPiece16& cl, std::string* output);
-#endif
-
-#if defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
// A POSIX-specific version of GetAppOutput that takes an argv array
// instead of a CommandLine. Useful for situations where you need to
// control the command line arguments directly.
@@ -266,14 +344,7 @@ BASE_EXPORT bool GetAppOutput(const std::vector<std::string>& argv,
// stderr.
BASE_EXPORT bool GetAppOutputAndError(const std::vector<std::string>& argv,
std::string* output);
-
-// A version of |GetAppOutput()| which also returns the exit code of the
-// executed command. Returns true if the application runs and exits cleanly. If
-// this is the case the exit code of the application is available in
-// |*exit_code|.
-BASE_EXPORT bool GetAppOutputWithExitCode(const CommandLine& cl,
- std::string* output, int* exit_code);
-#endif // defined(OS_POSIX)
+#endif // defined(OS_WIN)
// If supported on the platform, and the user has sufficent rights, increase
// the current process's scheduling priority to a high priority.
@@ -307,15 +378,16 @@ BASE_EXPORT LaunchOptions LaunchOptionsForTest();
//
// This function uses the libc clone wrapper (which updates libc's pid cache)
// internally, so callers may expect things like getpid() to work correctly
-// after in both the child and parent. An exception is when this code is run
-// under Valgrind. Valgrind does not support the libc clone wrapper, so the libc
-// pid cache may be incorrect after this function is called under Valgrind.
+// after in both the child and parent.
//
// As with fork(), callers should be extremely careful when calling this while
// multiple threads are running, since at the time the fork happened, the
// threads could have been in any state (potentially holding locks, etc.).
// Callers should most likely call execve() in the child soon after calling
// this.
+//
+// It is unsafe to use any pthread APIs after ForkWithFlags().
+// However, performing an exec() will lift this restriction.
BASE_EXPORT pid_t ForkWithFlags(unsigned long flags, pid_t* ptid, pid_t* ctid);
#endif
diff --git a/base/process/launch_posix.cc b/base/process/launch_posix.cc
index 2184051552..ec584883b9 100644
--- a/base/process/launch_posix.cc
+++ b/base/process/launch_posix.cc
@@ -33,18 +33,19 @@
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/process.h"
#include "base/process/process_metrics.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/waitable_event.h"
-#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
-#include "base/third_party/valgrind/valgrind.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_AIX)
#include <sys/prctl.h>
#endif
@@ -68,6 +69,13 @@ extern char** environ;
namespace base {
+// Friend and derived class of ScopedAllowBaseSyncPrimitives which allows
+// GetAppOutputInternal() to join a process. GetAppOutputInternal() can't itself
+// be a friend of ScopedAllowBaseSyncPrimitives because it is in the anonymous
+// namespace.
+class GetAppOutputScopedAllowBaseSyncPrimitives
+ : public base::ScopedAllowBaseSyncPrimitives {};
+
#if !defined(OS_NACL_NONSFI)
namespace {
@@ -113,7 +121,7 @@ sigset_t SetSignalMask(const sigset_t& new_sigmask) {
return old_sigmask;
}
-#if !defined(OS_LINUX) || \
+#if (!defined(OS_LINUX) && !defined(OS_AIX)) || \
(!defined(__i386__) && !defined(__x86_64__) && !defined(__arm__))
void ResetChildSignalHandlersToDefaults() {
// The previous signal handlers are likely to be meaningless in the child's
@@ -163,7 +171,7 @@ int sys_rt_sigaction(int sig, const struct kernel_sigaction* act,
// See crbug.com/177956.
void ResetChildSignalHandlersToDefaults(void) {
for (int signum = 1; ; ++signum) {
- struct kernel_sigaction act = {0};
+ struct kernel_sigaction act = {nullptr};
int sigaction_get_ret = sys_rt_sigaction(signum, nullptr, &act);
if (sigaction_get_ret && errno == EINVAL) {
#if !defined(NDEBUG)
@@ -211,7 +219,7 @@ struct ScopedDIRClose {
// Automatically closes |DIR*|s.
typedef std::unique_ptr<DIR, ScopedDIRClose> ScopedDIR;
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_AIX)
static const char kFDDir[] = "/proc/self/fd";
#elif defined(OS_MACOSX)
static const char kFDDir[] = "/dev/fd";
@@ -280,14 +288,8 @@ void CloseSuperfluousFds(const base::InjectiveMultimap& saved_mapping) {
if (fd == dir_fd)
continue;
- // When running under Valgrind, Valgrind opens several FDs for its
- // own use and will complain if we try to close them. All of
- // these FDs are >= |max_fds|, so we can check against that here
- // before closing. See https://bugs.kde.org/show_bug.cgi?id=191758
- if (fd < static_cast<int>(max_fds)) {
- int ret = IGNORE_EINTR(close(fd));
- DPCHECK(ret == 0);
- }
+ int ret = IGNORE_EINTR(close(fd));
+ DPCHECK(ret == 0);
}
}
@@ -298,6 +300,7 @@ Process LaunchProcess(const CommandLine& cmdline,
Process LaunchProcess(const std::vector<std::string>& argv,
const LaunchOptions& options) {
+ TRACE_EVENT0("base", "LaunchProcess");
#if defined(OS_MACOSX)
if (FeatureList::IsEnabled(kMacLaunchProcessPosixSpawn)) {
// TODO(rsesek): Do this unconditionally. There is one user for each of
@@ -307,21 +310,16 @@ Process LaunchProcess(const std::vector<std::string>& argv,
}
#endif
- size_t fd_shuffle_size = 0;
- if (options.fds_to_remap) {
- fd_shuffle_size = options.fds_to_remap->size();
- }
-
InjectiveMultimap fd_shuffle1;
InjectiveMultimap fd_shuffle2;
- fd_shuffle1.reserve(fd_shuffle_size);
- fd_shuffle2.reserve(fd_shuffle_size);
+ fd_shuffle1.reserve(options.fds_to_remap.size());
+ fd_shuffle2.reserve(options.fds_to_remap.size());
- std::unique_ptr<char* []> argv_cstr(new char*[argv.size() + 1]);
- for (size_t i = 0; i < argv.size(); i++) {
- argv_cstr[i] = const_cast<char*>(argv[i].c_str());
- }
- argv_cstr[argv.size()] = nullptr;
+ std::vector<char*> argv_cstr;
+ argv_cstr.reserve(argv.size() + 1);
+ for (const auto& arg : argv)
+ argv_cstr.push_back(const_cast<char*>(arg.c_str()));
+ argv_cstr.push_back(nullptr);
std::unique_ptr<char* []> new_environ;
char* const empty_environ = nullptr;
@@ -341,7 +339,8 @@ Process LaunchProcess(const std::vector<std::string>& argv,
}
pid_t pid;
-#if defined(OS_LINUX)
+ base::TimeTicks before_fork = TimeTicks::Now();
+#if defined(OS_LINUX) || defined(OS_AIX)
if (options.clone_flags) {
// Signal handling in this function assumes the creation of a new
// process, so we check that a thread is not being created by mistake
@@ -367,7 +366,11 @@ Process LaunchProcess(const std::vector<std::string>& argv,
// Always restore the original signal mask in the parent.
if (pid != 0) {
+ base::TimeTicks after_fork = TimeTicks::Now();
SetSignalMask(orig_sigmask);
+
+ base::TimeDelta fork_time = after_fork - before_fork;
+ UMA_HISTOGRAM_TIMES("MPArch.ForkTime", fork_time);
}
if (pid < 0) {
@@ -455,14 +458,12 @@ Process LaunchProcess(const std::vector<std::string>& argv,
}
#endif // defined(OS_CHROMEOS)
- if (options.fds_to_remap) {
- // Cannot use STL iterators here, since debug iterators use locks.
- for (size_t i = 0; i < options.fds_to_remap->size(); ++i) {
- const FileHandleMappingVector::value_type& value =
- (*options.fds_to_remap)[i];
- fd_shuffle1.push_back(InjectionArc(value.first, value.second, false));
- fd_shuffle2.push_back(InjectionArc(value.first, value.second, false));
- }
+ // Cannot use STL iterators here, since debug iterators use locks.
+ for (size_t i = 0; i < options.fds_to_remap.size(); ++i) {
+ const FileHandleMappingVector::value_type& value =
+ options.fds_to_remap[i];
+ fd_shuffle1.push_back(InjectionArc(value.first, value.second, false));
+ fd_shuffle2.push_back(InjectionArc(value.first, value.second, false));
}
if (!options.environ.empty() || options.clear_environ)
@@ -476,7 +477,7 @@ Process LaunchProcess(const std::vector<std::string>& argv,
// Set NO_NEW_PRIVS by default. Since NO_NEW_PRIVS only exists in kernel
// 3.5+, do not check the return value of prctl here.
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_AIX)
#ifndef PR_SET_NO_NEW_PRIVS
#define PR_SET_NO_NEW_PRIVS 38
#endif
@@ -506,7 +507,7 @@ Process LaunchProcess(const std::vector<std::string>& argv,
const char* executable_path = !options.real_path.empty() ?
options.real_path.value().c_str() : argv_cstr[0];
- execvp(executable_path, argv_cstr.get());
+ execvp(executable_path, argv_cstr.data());
RAW_LOG(ERROR, "LaunchProcess: failed to execvp:");
RAW_LOG(ERROR, argv_cstr[0]);
@@ -516,8 +517,8 @@ Process LaunchProcess(const std::vector<std::string>& argv,
if (options.wait) {
// While this isn't strictly disk IO, waiting for another process to
// finish is the sort of thing ThreadRestrictions is trying to prevent.
- base::ThreadRestrictions::AssertIOAllowed();
- pid_t ret = HANDLE_EINTR(waitpid(pid, 0, 0));
+ base::AssertBlockingAllowed();
+ pid_t ret = HANDLE_EINTR(waitpid(pid, nullptr, 0));
DPCHECK(ret > 0);
}
}
@@ -547,17 +548,17 @@ static bool GetAppOutputInternal(
std::string* output,
bool do_search_path,
int* exit_code) {
- // Doing a blocking wait for another command to finish counts as IO.
- base::ThreadRestrictions::AssertIOAllowed();
+ base::AssertBlockingAllowed();
// exit_code must be supplied so calling function can determine success.
DCHECK(exit_code);
*exit_code = EXIT_FAILURE;
- int pipe_fd[2];
- pid_t pid;
- InjectiveMultimap fd_shuffle1, fd_shuffle2;
- std::unique_ptr<char* []> argv_cstr(new char*[argv.size() + 1]);
-
+ // Declare and call reserve() here before calling fork() because the child
+ // process cannot allocate memory.
+ std::vector<char*> argv_cstr;
+ argv_cstr.reserve(argv.size() + 1);
+ InjectiveMultimap fd_shuffle1;
+ InjectiveMultimap fd_shuffle2;
fd_shuffle1.reserve(3);
fd_shuffle2.reserve(3);
@@ -565,81 +566,90 @@ static bool GetAppOutputInternal(
// both.
DCHECK(!do_search_path ^ !envp);
+ int pipe_fd[2];
if (pipe(pipe_fd) < 0)
return false;
- switch (pid = fork()) {
- case -1: // error
+ pid_t pid = fork();
+ switch (pid) {
+ case -1: {
+ // error
close(pipe_fd[0]);
close(pipe_fd[1]);
return false;
- case 0: // child
- {
- // DANGER: no calls to malloc or locks are allowed from now on:
- // http://crbug.com/36678
+ }
+ case 0: {
+ // child
+ //
+ // DANGER: no calls to malloc or locks are allowed from now on:
+ // http://crbug.com/36678
#if defined(OS_MACOSX)
- RestoreDefaultExceptionHandler();
+ RestoreDefaultExceptionHandler();
#endif
- // Obscure fork() rule: in the child, if you don't end up doing exec*(),
- // you call _exit() instead of exit(). This is because _exit() does not
- // call any previously-registered (in the parent) exit handlers, which
- // might do things like block waiting for threads that don't even exist
- // in the child.
- int dev_null = open("/dev/null", O_WRONLY);
- if (dev_null < 0)
- _exit(127);
-
- fd_shuffle1.push_back(InjectionArc(pipe_fd[1], STDOUT_FILENO, true));
- fd_shuffle1.push_back(InjectionArc(
- include_stderr ? pipe_fd[1] : dev_null,
- STDERR_FILENO, true));
- fd_shuffle1.push_back(InjectionArc(dev_null, STDIN_FILENO, true));
- // Adding another element here? Remeber to increase the argument to
- // reserve(), above.
-
- for (size_t i = 0; i < fd_shuffle1.size(); ++i)
- fd_shuffle2.push_back(fd_shuffle1[i]);
-
- if (!ShuffleFileDescriptors(&fd_shuffle1))
- _exit(127);
-
- CloseSuperfluousFds(fd_shuffle2);
-
- for (size_t i = 0; i < argv.size(); i++)
- argv_cstr[i] = const_cast<char*>(argv[i].c_str());
- argv_cstr[argv.size()] = nullptr;
- if (do_search_path)
- execvp(argv_cstr[0], argv_cstr.get());
- else
- execve(argv_cstr[0], argv_cstr.get(), envp);
+ // Obscure fork() rule: in the child, if you don't end up doing exec*(),
+ // you call _exit() instead of exit(). This is because _exit() does not
+ // call any previously-registered (in the parent) exit handlers, which
+ // might do things like block waiting for threads that don't even exist
+ // in the child.
+ int dev_null = open("/dev/null", O_WRONLY);
+ if (dev_null < 0)
_exit(127);
- }
- default: // parent
- {
- // Close our writing end of pipe now. Otherwise later read would not
- // be able to detect end of child's output (in theory we could still
- // write to the pipe).
- close(pipe_fd[1]);
-
- output->clear();
-
- while (true) {
- char buffer[256];
- ssize_t bytes_read =
- HANDLE_EINTR(read(pipe_fd[0], buffer, sizeof(buffer)));
- if (bytes_read <= 0)
- break;
- output->append(buffer, bytes_read);
- }
- close(pipe_fd[0]);
- // Always wait for exit code (even if we know we'll declare
- // GOT_MAX_OUTPUT).
- Process process(pid);
- return process.WaitForExit(exit_code);
+ fd_shuffle1.push_back(InjectionArc(pipe_fd[1], STDOUT_FILENO, true));
+ fd_shuffle1.push_back(InjectionArc(include_stderr ? pipe_fd[1] : dev_null,
+ STDERR_FILENO, true));
+ fd_shuffle1.push_back(InjectionArc(dev_null, STDIN_FILENO, true));
+ // Adding another element here? Remeber to increase the argument to
+ // reserve(), above.
+
+ for (size_t i = 0; i < fd_shuffle1.size(); ++i)
+ fd_shuffle2.push_back(fd_shuffle1[i]);
+
+ if (!ShuffleFileDescriptors(&fd_shuffle1))
+ _exit(127);
+
+ CloseSuperfluousFds(fd_shuffle2);
+
+ for (const auto& arg : argv)
+ argv_cstr.push_back(const_cast<char*>(arg.c_str()));
+ argv_cstr.push_back(nullptr);
+
+ if (do_search_path)
+ execvp(argv_cstr[0], argv_cstr.data());
+ else
+ execve(argv_cstr[0], argv_cstr.data(), envp);
+ _exit(127);
+ }
+ default: {
+ // parent
+ //
+ // Close our writing end of pipe now. Otherwise later read would not
+ // be able to detect end of child's output (in theory we could still
+ // write to the pipe).
+ close(pipe_fd[1]);
+
+ output->clear();
+
+ while (true) {
+ char buffer[256];
+ ssize_t bytes_read =
+ HANDLE_EINTR(read(pipe_fd[0], buffer, sizeof(buffer)));
+ if (bytes_read <= 0)
+ break;
+ output->append(buffer, bytes_read);
}
+ close(pipe_fd[0]);
+
+ // Always wait for exit code (even if we know we'll declare
+ // GOT_MAX_OUTPUT).
+ Process process(pid);
+ // A process launched with GetAppOutput*() usually doesn't wait on the
+ // process that launched it and thus chances of deadlock are low.
+ GetAppOutputScopedAllowBaseSyncPrimitives allow_base_sync_primitives;
+ return process.WaitForExit(exit_code);
+ }
}
}
@@ -681,13 +691,9 @@ bool GetAppOutputWithExitCode(const CommandLine& cl,
#endif // !defined(OS_NACL_NONSFI)
-#if defined(OS_LINUX) || defined(OS_NACL_NONSFI)
+#if defined(OS_LINUX) || defined(OS_NACL_NONSFI) || defined(OS_AIX)
namespace {
-bool IsRunningOnValgrind() {
- return RUNNING_ON_VALGRIND;
-}
-
// This function runs on the stack specified on the clone call. It uses longjmp
// to switch back to the original stack so the child can return from sys_clone.
int CloneHelper(void* arg) {
@@ -719,9 +725,10 @@ NOINLINE pid_t CloneAndLongjmpInChild(unsigned long flags,
// internal pid cache. The libc interface unfortunately requires
// specifying a new stack, so we use setjmp/longjmp to emulate
// fork-like behavior.
- char stack_buf[PTHREAD_STACK_MIN] ALIGNAS(16);
-#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM_FAMILY) || \
- defined(ARCH_CPU_MIPS_FAMILY)
+ alignas(16) char stack_buf[PTHREAD_STACK_MIN];
+#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM_FAMILY) || \
+ defined(ARCH_CPU_MIPS_FAMILY) || defined(ARCH_CPU_S390_FAMILY) || \
+ defined(ARCH_CPU_PPC64_FAMILY)
// The stack grows downward.
void* stack = stack_buf + sizeof(stack_buf);
#else
@@ -745,24 +752,6 @@ pid_t ForkWithFlags(unsigned long flags, pid_t* ptid, pid_t* ctid) {
RAW_LOG(FATAL, "Invalid usage of ForkWithFlags");
}
- // Valgrind's clone implementation does not support specifiying a child_stack
- // without CLONE_VM, so we cannot use libc's clone wrapper when running under
- // Valgrind. As a result, the libc pid cache may be incorrect under Valgrind.
- // See crbug.com/442817 for more details.
- if (IsRunningOnValgrind()) {
- // See kernel/fork.c in Linux. There is different ordering of sys_clone
- // parameters depending on CONFIG_CLONE_BACKWARDS* configuration options.
-#if defined(ARCH_CPU_X86_64)
- return syscall(__NR_clone, flags, nullptr, ptid, ctid, nullptr);
-#elif defined(ARCH_CPU_X86) || defined(ARCH_CPU_ARM_FAMILY) || \
- defined(ARCH_CPU_MIPS_FAMILY)
- // CONFIG_CLONE_BACKWARDS defined.
- return syscall(__NR_clone, flags, nullptr, ptid, nullptr, ctid);
-#else
-#error "Unsupported architecture"
-#endif
- }
-
jmp_buf env;
if (setjmp(env) == 0) {
return CloneAndLongjmpInChild(flags, ptid, ctid, &env);
diff --git a/base/process/memory.cc b/base/process/memory.cc
index 6349c08ca0..5b987339ba 100644
--- a/base/process/memory.cc
+++ b/base/process/memory.cc
@@ -38,7 +38,7 @@ bool UncheckedCalloc(size_t num_items, size_t size, void** result) {
// Overflow check
if (size && ((alloc_size / size) != num_items)) {
- *result = NULL;
+ *result = nullptr;
return false;
}
diff --git a/base/process/memory.h b/base/process/memory.h
index 77911cfc35..7f16e12d8a 100644
--- a/base/process/memory.h
+++ b/base/process/memory.h
@@ -32,7 +32,7 @@ BASE_EXPORT void EnableTerminationOnOutOfMemory();
// Crash reporting classifies such crashes as OOM.
BASE_EXPORT void TerminateBecauseOutOfMemory(size_t size);
-#if defined(OS_LINUX) || defined(OS_ANDROID)
+#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_AIX)
BASE_EXPORT extern size_t g_oom_size;
// The maximum allowed value for the OOM score.
diff --git a/base/process/memory_linux.cc b/base/process/memory_linux.cc
index 985bc54eb6..11e482b2e8 100644
--- a/base/process/memory_linux.cc
+++ b/base/process/memory_linux.cc
@@ -9,7 +9,7 @@
#include <new>
#include "base/allocator/allocator_shim.h"
-#include "base/allocator/features.h"
+#include "base/allocator/buildflags.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
@@ -18,9 +18,14 @@
#include "build/build_config.h"
#if defined(USE_TCMALLOC)
-#include "third_party/tcmalloc/chromium/src/gperftools/tcmalloc.h"
+#include "third_party/tcmalloc/gperftools-2.0/chromium/src/config.h"
+#include "third_party/tcmalloc/gperftools-2.0/chromium/src/gperftools/tcmalloc.h"
#endif
+extern "C" {
+void* __libc_malloc(size_t size);
+}
+
namespace base {
size_t g_oom_size = 0U;
@@ -41,105 +46,6 @@ void OnNoMemory() {
} // namespace
-// TODO(primiano): Once the unified shim is on by default (crbug.com/550886)
-// get rid of the code in this entire #if section. The whole termination-on-OOM
-// logic is implemented in the shim.
-#if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) && \
- !defined(THREAD_SANITIZER) && !defined(LEAK_SANITIZER) && \
- !BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM)
-
-#if defined(LIBC_GLIBC) && !defined(USE_TCMALLOC)
-
-extern "C" {
-void* __libc_malloc(size_t size);
-void* __libc_realloc(void* ptr, size_t size);
-void* __libc_calloc(size_t nmemb, size_t size);
-void* __libc_valloc(size_t size);
-#if PVALLOC_AVAILABLE == 1
-void* __libc_pvalloc(size_t size);
-#endif
-void* __libc_memalign(size_t alignment, size_t size);
-
-// Overriding the system memory allocation functions:
-//
-// For security reasons, we want malloc failures to be fatal. Too much code
-// doesn't check for a NULL return value from malloc and unconditionally uses
-// the resulting pointer. If the first offset that they try to access is
-// attacker controlled, then the attacker can direct the code to access any
-// part of memory.
-//
-// Thus, we define all the standard malloc functions here and mark them as
-// visibility 'default'. This means that they replace the malloc functions for
-// all Chromium code and also for all code in shared libraries. There are tests
-// for this in process_util_unittest.cc.
-//
-// If we are using tcmalloc, then the problem is moot since tcmalloc handles
-// this for us. Thus this code is in a !defined(USE_TCMALLOC) block.
-//
-// If we are testing the binary with AddressSanitizer, we should not
-// redefine malloc and let AddressSanitizer do it instead.
-//
-// We call the real libc functions in this code by using __libc_malloc etc.
-// Previously we tried using dlsym(RTLD_NEXT, ...) but that failed depending on
-// the link order. Since ld.so needs calloc during symbol resolution, it
-// defines its own versions of several of these functions in dl-minimal.c.
-// Depending on the runtime library order, dlsym ended up giving us those
-// functions and bad things happened. See crbug.com/31809
-//
-// This means that any code which calls __libc_* gets the raw libc versions of
-// these functions.
-
-#define DIE_ON_OOM_1(function_name) \
- void* function_name(size_t) __attribute__ ((visibility("default"))); \
- \
- void* function_name(size_t size) { \
- void* ret = __libc_##function_name(size); \
- if (ret == NULL && size != 0) \
- OnNoMemorySize(size); \
- return ret; \
- }
-
-#define DIE_ON_OOM_2(function_name, arg1_type) \
- void* function_name(arg1_type, size_t) \
- __attribute__ ((visibility("default"))); \
- \
- void* function_name(arg1_type arg1, size_t size) { \
- void* ret = __libc_##function_name(arg1, size); \
- if (ret == NULL && size != 0) \
- OnNoMemorySize(size); \
- return ret; \
- }
-
-DIE_ON_OOM_1(malloc)
-DIE_ON_OOM_1(valloc)
-#if PVALLOC_AVAILABLE == 1
-DIE_ON_OOM_1(pvalloc)
-#endif
-
-DIE_ON_OOM_2(calloc, size_t)
-DIE_ON_OOM_2(realloc, void*)
-DIE_ON_OOM_2(memalign, size_t)
-
-// posix_memalign has a unique signature and doesn't have a __libc_ variant.
-int posix_memalign(void** ptr, size_t alignment, size_t size)
- __attribute__ ((visibility("default")));
-
-int posix_memalign(void** ptr, size_t alignment, size_t size) {
- // This will use the safe version of memalign, above.
- *ptr = memalign(alignment, size);
- return 0;
-}
-
-} // extern C
-
-#else
-
-// TODO(mostynb@opera.com): dlsym dance
-
-#endif // LIBC_GLIBC && !USE_TCMALLOC
-
-#endif // !*_SANITIZER
-
void EnableTerminationOnHeapCorruption() {
// On Linux, there nothing to do AFAIK.
}
@@ -150,7 +56,7 @@ void EnableTerminationOnOutOfMemory() {
// If we're using glibc's allocator, the above functions will override
// malloc and friends and make them die on out of memory.
-#if BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM)
+#if BUILDFLAG(USE_ALLOCATOR_SHIM)
allocator::SetCallNewHandlerOnMallocFailure(true);
#elif defined(USE_TCMALLOC)
// For tcmalloc, we need to tell it to behave like new.
@@ -196,7 +102,7 @@ bool AdjustOOMScore(ProcessId process, int score) {
}
bool UncheckedMalloc(size_t size, void** result) {
-#if BUILDFLAG(USE_EXPERIMENTAL_ALLOCATOR_SHIM)
+#if BUILDFLAG(USE_ALLOCATOR_SHIM)
*result = allocator::UncheckedAlloc(size);
#elif defined(MEMORY_TOOL_REPLACES_ALLOCATOR) || \
(!defined(LIBC_GLIBC) && !defined(USE_TCMALLOC))
@@ -206,7 +112,7 @@ bool UncheckedMalloc(size_t size, void** result) {
#elif defined(USE_TCMALLOC)
*result = tc_malloc_skip_new_handler(size);
#endif
- return *result != NULL;
+ return *result != nullptr;
}
} // namespace base
diff --git a/base/process/process.h b/base/process/process.h
index fc2add24c5..674cea3d02 100644
--- a/base/process/process.h
+++ b/base/process/process.h
@@ -15,6 +15,10 @@
#include "base/win/scoped_handle.h"
#endif
+#if defined(OS_FUCHSIA)
+#include <lib/zx/process.h>
+#endif
+
#if defined(OS_MACOSX)
#include "base/feature_list.h"
#include "base/process/port_provider_mac.h"
@@ -36,11 +40,13 @@ extern const Feature kMacAllowBackgroundingProcesses;
// and can be used to gather some information about that process, but most
// methods will obviously fail.
//
-// POSIX: The underlying PorcessHandle is not guaranteed to remain valid after
+// POSIX: The underlying ProcessHandle is not guaranteed to remain valid after
// the process dies, and it may be reused by the system, which means that it may
// end up pointing to the wrong process.
class BASE_EXPORT Process {
public:
+ // On Windows, this takes ownership of |handle|. On POSIX, this does not take
+ // ownership of |handle|.
explicit Process(ProcessHandle handle = kNullProcessHandle);
Process(Process&& other);
@@ -77,7 +83,7 @@ class BASE_EXPORT Process {
static bool CanBackgroundProcesses();
// Terminates the current process immediately with |exit_code|.
- static void TerminateCurrentProcessImmediately(int exit_code);
+ [[noreturn]] static void TerminateCurrentProcessImmediately(int exit_code);
// Returns true if this objects represents a valid process.
bool IsValid() const;
@@ -98,6 +104,16 @@ class BASE_EXPORT Process {
// Close the process handle. This will not terminate the process.
void Close();
+ // Returns true if this process is still running. This is only safe on Windows
+ // (and maybe Fuchsia?), because the ProcessHandle will keep the zombie
+ // process information available until itself has been released. But on Posix,
+ // the OS may reuse the ProcessId.
+#if defined(OS_WIN)
+ bool IsRunning() const {
+ return !WaitForExitWithTimeout(base::TimeDelta(), nullptr);
+ }
+#endif
+
// Terminates the process with extreme prejudice. The given |exit_code| will
// be the exit code of the process. If |wait| is true, this method will wait
// for up to one minute for the process to actually terminate.
@@ -118,6 +134,13 @@ class BASE_EXPORT Process {
// is not required.
bool WaitForExitWithTimeout(TimeDelta timeout, int* exit_code) const;
+ // Indicates that the process has exited with the specified |exit_code|.
+ // This should be called if process exit is observed outside of this class.
+ // (i.e. Not because Terminate or WaitForExit, above, was called.)
+ // Note that nothing prevents this being called multiple times for a dead
+ // process though that should be avoided.
+ void Exited(int exit_code) const;
+
#if defined(OS_MACOSX)
// The Mac needs a Mach port in order to manipulate a process's priority,
// and there's no good way to get that from base given the pid. These Mac
@@ -163,12 +186,17 @@ class BASE_EXPORT Process {
private:
#if defined(OS_WIN)
- bool is_current_process_;
win::ScopedHandle process_;
+#elif defined(OS_FUCHSIA)
+ zx::process process_;
#else
ProcessHandle process_;
#endif
+#if defined(OS_WIN) || defined(OS_FUCHSIA)
+ bool is_current_process_;
+#endif
+
DISALLOW_COPY_AND_ASSIGN(Process);
};
diff --git a/base/process/process_handle.cc b/base/process/process_handle.cc
index 1f22b93d31..58ceb08469 100644
--- a/base/process/process_handle.cc
+++ b/base/process/process_handle.cc
@@ -39,7 +39,7 @@ uint32_t GetUniqueIdForProcess() {
return g_unique_id;
}
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_AIX)
void InitUniqueIdForProcessInPidNamespace(ProcessId pid_outside_of_namespace) {
g_unique_id = MangleProcessId(pid_outside_of_namespace);
diff --git a/base/process/process_handle.h b/base/process/process_handle.h
index ef7a602552..f3f63439df 100644
--- a/base/process/process_handle.h
+++ b/base/process/process_handle.h
@@ -13,7 +13,11 @@
#include "build/build_config.h"
#if defined(OS_WIN)
-#include <windows.h>
+#include "base/win/windows_types.h"
+#endif
+
+#if defined(OS_FUCHSIA)
+#include <zircon/types.h>
#endif
namespace base {
@@ -27,6 +31,11 @@ typedef DWORD ProcessId;
typedef HANDLE UserTokenHandle;
const ProcessHandle kNullProcessHandle = NULL;
const ProcessId kNullProcessId = 0;
+#elif defined(OS_FUCHSIA)
+typedef zx_handle_t ProcessHandle;
+typedef zx_koid_t ProcessId;
+const ProcessHandle kNullProcessHandle = ZX_HANDLE_INVALID;
+const ProcessId kNullProcessId = ZX_KOID_INVALID;
#elif defined(OS_POSIX)
// On POSIX, our ProcessHandle will just be the PID.
typedef pid_t ProcessHandle;
@@ -35,6 +44,15 @@ const ProcessHandle kNullProcessHandle = 0;
const ProcessId kNullProcessId = 0;
#endif // defined(OS_WIN)
+// To print ProcessIds portably use CrPRIdPid (based on PRIuS and friends from
+// C99 and format_macros.h) like this:
+// base::StringPrintf("PID is %" CrPRIdPid ".\n", pid);
+#if defined(OS_WIN) || defined(OS_FUCHSIA)
+#define CrPRIdPid "ld"
+#else
+#define CrPRIdPid "d"
+#endif
+
// Returns the id of the current process.
// Note that on some platforms, this is not guaranteed to be unique across
// processes (use GetUniqueIdForProcess if uniqueness is required).
@@ -68,8 +86,12 @@ BASE_EXPORT ProcessHandle GetCurrentProcessHandle();
// processes.
BASE_EXPORT ProcessId GetProcId(ProcessHandle process);
-// Returns the ID for the parent of the given process.
+#if !defined(OS_FUCHSIA)
+// Returns the ID for the parent of the given process. Not available on Fuchsia.
+// Returning a negative value indicates an error, such as if the |process| does
+// not exist. Returns 0 when |process| has no parent process.
BASE_EXPORT ProcessId GetParentProcessId(ProcessHandle process);
+#endif // !defined(OS_FUCHSIA)
#if defined(OS_POSIX)
// Returns the path to the executable of the given process.
diff --git a/base/process/process_handle_linux.cc b/base/process/process_handle_linux.cc
index 950b888cfa..f921b426a3 100644
--- a/base/process/process_handle_linux.cc
+++ b/base/process/process_handle_linux.cc
@@ -6,12 +6,21 @@
#include "base/files/file_util.h"
#include "base/process/internal_linux.h"
+#if defined(OS_AIX)
+#include "base/process/internal_aix.h"
+#endif
namespace base {
ProcessId GetParentProcessId(ProcessHandle process) {
ProcessId pid =
+#if defined(OS_AIX)
+ internalAIX::ReadProcStatsAndGetFieldAsInt64(process,
+ internalAIX::VM_PPID);
+#else
internal::ReadProcStatsAndGetFieldAsInt64(process, internal::VM_PPID);
+#endif
+ // TODO(zijiehe): Returns 0 if |process| does not have a parent process.
if (pid)
return pid;
return -1;
diff --git a/base/process/process_info.h b/base/process/process_info.h
index 1d76f42eb2..f06370e5bf 100644
--- a/base/process/process_info.h
+++ b/base/process/process_info.h
@@ -21,22 +21,22 @@ class BASE_EXPORT CurrentProcessInfo {
};
#if defined(OS_WIN)
-
enum IntegrityLevel {
INTEGRITY_UNKNOWN,
+ UNTRUSTED_INTEGRITY,
LOW_INTEGRITY,
MEDIUM_INTEGRITY,
HIGH_INTEGRITY,
};
-// Returns the integrity level of the process. Returns INTEGRITY_UNKNOWN if the
-// system does not support integrity levels (pre-Vista) or in the case of an
-// underlying system failure.
+// Returns the integrity level of the process. Returns INTEGRITY_UNKNOWN in the
+// case of an underlying system failure.
BASE_EXPORT IntegrityLevel GetCurrentProcessIntegrityLevel();
-#endif // defined(OS_WIN)
-
+// Determines whether the current process is elevated.
+BASE_EXPORT bool IsCurrentProcessElevated();
+#endif // defined(OS_WIN)
} // namespace base
diff --git a/base/process/process_info_unittest.cc b/base/process/process_info_unittest.cc
index a757774fda..f54d957bb6 100644
--- a/base/process/process_info_unittest.cc
+++ b/base/process/process_info_unittest.cc
@@ -10,11 +10,13 @@
namespace base {
-#if !defined(OS_IOS)
+// See https://crbug.com/726484 for Fuchsia.
+// Cannot read boot time on Android O, crbug.com/788870.
+#if !defined(OS_IOS) && !defined(OS_FUCHSIA) && !defined(OS_ANDROID)
TEST(ProcessInfoTest, CreationTime) {
Time creation_time = CurrentProcessInfo::CreationTime();
ASSERT_FALSE(creation_time.is_null());
}
-#endif // !defined(OS_IOS)
+#endif // !defined(OS_IOS) && !defined(OS_FUCHSIA) && !defined(OS_ANDROID)
} // namespace base
diff --git a/base/process/process_iterator.cc b/base/process/process_iterator.cc
index d4024d9a5e..8b530a067e 100644
--- a/base/process/process_iterator.cc
+++ b/base/process/process_iterator.cc
@@ -7,10 +7,10 @@
namespace base {
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
ProcessEntry::ProcessEntry() : pid_(0), ppid_(0), gid_(0) {}
ProcessEntry::ProcessEntry(const ProcessEntry& other) = default;
-ProcessEntry::~ProcessEntry() {}
+ProcessEntry::~ProcessEntry() = default;
#endif
const ProcessEntry* ProcessIterator::NextProcessEntry() {
@@ -20,7 +20,7 @@ const ProcessEntry* ProcessIterator::NextProcessEntry() {
} while (result && !IncludeEntry());
if (result)
return &entry_;
- return NULL;
+ return nullptr;
}
ProcessIterator::ProcessEntries ProcessIterator::Snapshot() {
@@ -52,8 +52,7 @@ NamedProcessIterator::NamedProcessIterator(
#endif
}
-NamedProcessIterator::~NamedProcessIterator() {
-}
+NamedProcessIterator::~NamedProcessIterator() = default;
int GetProcessCount(const FilePath::StringType& executable_name,
const ProcessFilter* filter) {
diff --git a/base/process/process_iterator.h b/base/process/process_iterator.h
index 0d1f1a6244..b30ad41222 100644
--- a/base/process/process_iterator.h
+++ b/base/process/process_iterator.h
@@ -26,7 +26,7 @@
#include <sys/sysctl.h>
#elif defined(OS_FREEBSD)
#include <sys/user.h>
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
#include <dirent.h>
#endif
@@ -38,7 +38,7 @@ struct ProcessEntry : public PROCESSENTRY32 {
ProcessId parent_pid() const { return th32ParentProcessID; }
const wchar_t* exe_file() const { return szExeFile; }
};
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
struct BASE_EXPORT ProcessEntry {
ProcessEntry();
ProcessEntry(const ProcessEntry& other);
@@ -58,7 +58,7 @@ struct BASE_EXPORT ProcessEntry {
std::string exe_file_;
std::vector<std::string> cmd_line_args_;
};
-#endif // defined(OS_POSIX)
+#endif // defined(OS_WIN)
// Used to filter processes by process ID.
class ProcessFilter {
@@ -68,7 +68,7 @@ class ProcessFilter {
virtual bool Includes(const ProcessEntry& entry) const = 0;
protected:
- virtual ~ProcessFilter() {}
+ virtual ~ProcessFilter() = default;
};
// This class provides a way to iterate through a list of processes on the
@@ -112,7 +112,7 @@ class BASE_EXPORT ProcessIterator {
#elif defined(OS_MACOSX) || defined(OS_BSD)
std::vector<kinfo_proc> kinfo_procs_;
size_t index_of_kinfo_proc_;
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
DIR* procfs_dir_;
#endif
ProcessEntry entry_;
diff --git a/base/process/process_iterator_linux.cc b/base/process/process_iterator_linux.cc
index 421565f8e4..9fea70e4d3 100644
--- a/base/process/process_iterator_linux.cc
+++ b/base/process/process_iterator_linux.cc
@@ -33,7 +33,7 @@ std::string GetProcStatsFieldAsString(
return proc_stats[field_num];
NOTREACHED();
- return 0;
+ return nullptr;
}
// Reads /proc/<pid>/cmdline and populates |proc_cmd_line_args| with the command
diff --git a/base/process/process_metrics.cc b/base/process/process_metrics.cc
index ad555aedff..c3a70633a3 100644
--- a/base/process/process_metrics.cc
+++ b/base/process/process_metrics.cc
@@ -10,6 +10,40 @@
#include "base/values.h"
#include "build/build_config.h"
+#if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_AIX)
+namespace {
+int CalculateEventsPerSecond(uint64_t event_count,
+ uint64_t* last_event_count,
+ base::TimeTicks* last_calculated) {
+ base::TimeTicks time = base::TimeTicks::Now();
+
+ if (*last_event_count == 0) {
+ // First call, just set the last values.
+ *last_calculated = time;
+ *last_event_count = event_count;
+ return 0;
+ }
+
+ int64_t events_delta = event_count - *last_event_count;
+ int64_t time_delta = (time - *last_calculated).InMicroseconds();
+ if (time_delta == 0) {
+ NOTREACHED();
+ return 0;
+ }
+
+ *last_calculated = time;
+ *last_event_count = event_count;
+
+ int64_t events_delta_for_ms =
+ events_delta * base::Time::kMicrosecondsPerSecond;
+ // Round the result up by adding 1/2 (the second term resolves to 1/2 without
+ // dropping down into floating point).
+ return (events_delta_for_ms + time_delta / 2) / time_delta;
+}
+
+} // namespace
+#endif // defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_AIX)
+
namespace base {
SystemMemoryInfoKB::SystemMemoryInfoKB() = default;
@@ -27,6 +61,7 @@ SystemMetrics SystemMetrics::Sample() {
system_metrics.committed_memory_ = GetSystemCommitCharge();
#if defined(OS_LINUX) || defined(OS_ANDROID)
GetSystemMemoryInfo(&system_metrics.memory_info_);
+ GetVmStatInfo(&system_metrics.vmstat_info_);
GetSystemDiskInfo(&system_metrics.disk_info_);
#endif
#if defined(OS_CHROMEOS)
@@ -41,7 +76,10 @@ std::unique_ptr<Value> SystemMetrics::ToValue() const {
res->SetInteger("committed_memory", static_cast<int>(committed_memory_));
#if defined(OS_LINUX) || defined(OS_ANDROID)
- res->Set("meminfo", memory_info_.ToValue());
+ std::unique_ptr<DictionaryValue> meminfo = memory_info_.ToValue();
+ std::unique_ptr<DictionaryValue> vmstat = vmstat_info_.ToValue();
+ meminfo->MergeDictionary(vmstat.get());
+ res->Set("meminfo", std::move(meminfo));
res->Set("diskinfo", disk_info_.ToValue());
#endif
#if defined(OS_CHROMEOS)
@@ -59,46 +97,53 @@ std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateCurrentProcessMetrics() {
#endif // !defined(OS_MACOSX) || defined(OS_IOS)
}
+#if !defined(OS_FREEBSD) || !defined(OS_POSIX)
double ProcessMetrics::GetPlatformIndependentCPUUsage() {
-#if defined(OS_WIN)
- return GetCPUUsage() * processor_count_;
-#else
- return GetCPUUsage();
-#endif
-}
-
-#if defined(OS_MACOSX) || defined(OS_LINUX)
-int ProcessMetrics::CalculateIdleWakeupsPerSecond(
- uint64_t absolute_idle_wakeups) {
+ TimeDelta cumulative_cpu = GetCumulativeCPUUsage();
TimeTicks time = TimeTicks::Now();
- if (last_absolute_idle_wakeups_ == 0) {
+ if (last_cumulative_cpu_.is_zero()) {
// First call, just set the last values.
- last_idle_wakeups_time_ = time;
- last_absolute_idle_wakeups_ = absolute_idle_wakeups;
+ last_cumulative_cpu_ = cumulative_cpu;
+ last_cpu_time_ = time;
return 0;
}
- int64_t wakeups_delta = absolute_idle_wakeups - last_absolute_idle_wakeups_;
- int64_t time_delta = (time - last_idle_wakeups_time_).InMicroseconds();
- if (time_delta == 0) {
- NOTREACHED();
+ TimeDelta system_time_delta = cumulative_cpu - last_cumulative_cpu_;
+ TimeDelta time_delta = time - last_cpu_time_;
+ DCHECK(!time_delta.is_zero());
+ if (time_delta.is_zero())
return 0;
- }
- last_idle_wakeups_time_ = time;
- last_absolute_idle_wakeups_ = absolute_idle_wakeups;
+ last_cumulative_cpu_ = cumulative_cpu;
+ last_cpu_time_ = time;
- int64_t wakeups_delta_for_ms = wakeups_delta * Time::kMicrosecondsPerSecond;
- // Round the result up by adding 1/2 (the second term resolves to 1/2 without
- // dropping down into floating point).
- return (wakeups_delta_for_ms + time_delta / 2) / time_delta;
+ return 100.0 * system_time_delta.InMicrosecondsF() /
+ time_delta.InMicrosecondsF();
+}
+#endif
+
+#if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_AIX)
+int ProcessMetrics::CalculateIdleWakeupsPerSecond(
+ uint64_t absolute_idle_wakeups) {
+ return CalculateEventsPerSecond(absolute_idle_wakeups,
+ &last_absolute_idle_wakeups_,
+ &last_idle_wakeups_time_);
}
#else
int ProcessMetrics::GetIdleWakeupsPerSecond() {
NOTIMPLEMENTED(); // http://crbug.com/120488
return 0;
}
-#endif // defined(OS_MACOSX) || defined(OS_LINUX)
+#endif // defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_AIX)
+
+#if defined(OS_MACOSX)
+int ProcessMetrics::CalculatePackageIdleWakeupsPerSecond(
+ uint64_t absolute_package_idle_wakeups) {
+ return CalculateEventsPerSecond(absolute_package_idle_wakeups,
+ &last_absolute_package_idle_wakeups_,
+ &last_package_idle_wakeups_time_);
+}
+#endif // defined(OS_MACOSX)
} // namespace base
diff --git a/base/process/process_metrics.h b/base/process/process_metrics.h
index 1562e7b156..6bfd93ece7 100644
--- a/base/process/process_metrics.h
+++ b/base/process/process_metrics.h
@@ -25,88 +25,51 @@
#if defined(OS_MACOSX)
#include <mach/mach.h>
#include "base/process/port_provider_mac.h"
+
+#if !defined(OS_IOS)
+#include <mach/mach_vm.h>
+#endif
#endif
#if defined(OS_WIN)
#include "base/win/scoped_handle.h"
+#include "base/win/windows_types.h"
#endif
namespace base {
-#if defined(OS_WIN)
-struct IoCounters : public IO_COUNTERS {
-};
-#elif defined(OS_POSIX)
-struct IoCounters {
- uint64_t ReadOperationCount;
- uint64_t WriteOperationCount;
- uint64_t OtherOperationCount;
- uint64_t ReadTransferCount;
- uint64_t WriteTransferCount;
- uint64_t OtherTransferCount;
-};
-#endif
+// Full declaration is in process_metrics_iocounters.h.
+struct IoCounters;
-// Working Set (resident) memory usage broken down by
-//
-// On Windows:
-// priv (private): These pages (kbytes) cannot be shared with any other process.
-// shareable: These pages (kbytes) can be shared with other processes under
-// the right circumstances.
-// shared : These pages (kbytes) are currently shared with at least one
-// other process.
-//
-// On Linux:
-// priv: Pages mapped only by this process.
-// shared: PSS or 0 if the kernel doesn't support this.
-// shareable: 0
-
-// On ChromeOS:
-// priv: Pages mapped only by this process.
-// shared: PSS or 0 if the kernel doesn't support this.
-// shareable: 0
-// swapped Pages swapped out to zram.
-//
-// On macOS:
-// priv: Resident size (RSS) including shared memory. Warning: This
-// does not include compressed size and does not always
-// accurately account for shared memory due to things like
-// copy-on-write. TODO(erikchen): Revamp this with something
-// more accurate.
-// shared: 0
-// shareable: 0
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+// Minor and major page fault counts since the process creation.
+// Both counts are process-wide, and exclude child processes.
//
-struct WorkingSetKBytes {
- WorkingSetKBytes() : priv(0), shareable(0), shared(0) {}
- size_t priv;
- size_t shareable;
- size_t shared;
-#if defined(OS_CHROMEOS)
- size_t swapped;
-#endif
-};
-
-// Committed (resident + paged) memory usage broken down by
-// private: These pages cannot be shared with any other process.
-// mapped: These pages are mapped into the view of a section (backed by
-// pagefile.sys)
-// image: These pages are mapped into the view of an image section (backed by
-// file system)
-struct CommittedKBytes {
- CommittedKBytes() : priv(0), mapped(0), image(0) {}
- size_t priv;
- size_t mapped;
- size_t image;
+// minor: Number of page faults that didn't require disk IO.
+// major: Number of page faults that required disk IO.
+struct PageFaultCounts {
+ int64_t minor;
+ int64_t major;
};
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
// Convert a POSIX timeval to microseconds.
BASE_EXPORT int64_t TimeValToMicroseconds(const struct timeval& tv);
-// Provides performance metrics for a specified process (CPU usage, memory and
-// IO counters). Use CreateCurrentProcessMetrics() to get an instance for the
+// Provides performance metrics for a specified process (CPU usage and IO
+// counters). Use CreateCurrentProcessMetrics() to get an instance for the
// current process, or CreateProcessMetrics() to get an instance for an
// arbitrary process. Then, access the information with the different get
// methods.
+//
+// This class exposes a few platform-specific APIs for parsing memory usage, but
+// these are not intended to generalize to other platforms, since the memory
+// models differ substantially.
+//
+// To obtain consistent memory metrics, use the memory_instrumentation service.
+//
+// For further documentation on memory, see
+// https://chromium.googlesource.com/chromium/src/+/HEAD/docs/README.md
class BASE_EXPORT ProcessMetrics {
public:
~ProcessMetrics();
@@ -129,64 +92,83 @@ class BASE_EXPORT ProcessMetrics {
// convenience wrapper for CreateProcessMetrics().
static std::unique_ptr<ProcessMetrics> CreateCurrentProcessMetrics();
- // Returns the current space allocated for the pagefile, in bytes (these pages
- // may or may not be in memory). On Linux, this returns the total virtual
- // memory size.
- size_t GetPagefileUsage() const;
- // Returns the peak space allocated for the pagefile, in bytes.
- size_t GetPeakPagefileUsage() const;
- // Returns the current working set size, in bytes. On Linux, this returns
- // the resident set size.
- size_t GetWorkingSetSize() const;
- // Returns the peak working set size, in bytes.
- size_t GetPeakWorkingSetSize() const;
- // Returns private and sharedusage, in bytes. Private bytes is the amount of
- // memory currently allocated to a process that cannot be shared. Returns
- // false on platform specific error conditions. Note: |private_bytes|
- // returns 0 on unsupported OSes: prior to XP SP2.
- bool GetMemoryBytes(size_t* private_bytes, size_t* shared_bytes) const;
- // Fills a CommittedKBytes with both resident and paged
- // memory usage as per definition of CommittedBytes.
- void GetCommittedKBytes(CommittedKBytes* usage) const;
- // Fills a WorkingSetKBytes containing resident private and shared memory
- // usage in bytes, as per definition of WorkingSetBytes. Note that this
- // function is somewhat expensive on Windows (a few ms per process).
- bool GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const;
- // Computes pss (proportional set size) of a process. Note that this
- // function is somewhat expensive on Windows (a few ms per process).
- bool GetProportionalSetSizeBytes(uint64_t* pss_bytes) const;
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ // Resident Set Size is a Linux/Android specific memory concept. Do not
+ // attempt to extend this to other platforms.
+ BASE_EXPORT size_t GetResidentSetSize() const;
+#endif
+
+#if defined(OS_CHROMEOS)
+ // /proc/<pid>/totmaps is a syscall that returns memory summary statistics for
+ // the process.
+ // totmaps is a Linux specific concept, currently only being used on ChromeOS.
+ // Do not attempt to extend this to other platforms.
+ //
+ struct TotalsSummary {
+ size_t private_clean_kb;
+ size_t private_dirty_kb;
+ size_t swap_kb;
+ };
+ BASE_EXPORT TotalsSummary GetTotalsSummary() const;
+#endif
#if defined(OS_MACOSX)
- // Fills both CommitedKBytes and WorkingSetKBytes in a single operation. This
- // is more efficient on Mac OS X, as the two can be retrieved with a single
- // system call.
- bool GetCommittedAndWorkingSetKBytes(CommittedKBytes* usage,
- WorkingSetKBytes* ws_usage) const;
- // Returns private, shared, and total resident bytes. |locked_bytes| refers to
- // bytes that must stay resident. |locked_bytes| only counts bytes locked by
- // this task, not bytes locked by the kernel.
- bool GetMemoryBytes(size_t* private_bytes,
- size_t* shared_bytes,
- size_t* resident_bytes,
- size_t* locked_bytes) const;
+ struct TaskVMInfo {
+ // Only available on macOS 10.12+.
+ // Anonymous, non-discardable memory, including non-volatile IOKit.
+ // Measured in bytes.
+ uint64_t phys_footprint = 0;
+
+ // Anonymous, non-discardable, non-compressed memory, excluding IOKit.
+ // Measured in bytes.
+ uint64_t internal = 0;
+
+ // Compressed memory measured in bytes.
+ uint64_t compressed = 0;
+ };
+ TaskVMInfo GetTaskVMInfo() const;
#endif
- // Returns the CPU usage in percent since the last time this method or
- // GetPlatformIndependentCPUUsage() was called. The first time this method
- // is called it returns 0 and will return the actual CPU info on subsequent
- // calls. On Windows, the CPU usage value is for all CPUs. So if you have
- // 2 CPUs and your process is using all the cycles of 1 CPU and not the other
- // CPU, this method returns 50.
- double GetCPUUsage();
+ // Returns the percentage of time spent executing, across all threads of the
+ // process, in the interval since the last time the method was called. Since
+ // this considers the total execution time across all threads in a process,
+ // the result can easily exceed 100% in multi-thread processes running on
+ // multi-core systems. In general the result is therefore a value in the
+ // range 0% to SysInfo::NumberOfProcessors() * 100%.
+ //
+ // To obtain the percentage of total available CPU resources consumed by this
+ // process over the interval, the caller must divide by NumberOfProcessors().
+ //
+ // Since this API measures usage over an interval, it will return zero on the
+ // first call, and an actual value only on the second and subsequent calls.
+ double GetPlatformIndependentCPUUsage();
+
+ // Returns the cumulative CPU usage across all threads of the process since
+ // process start. In case of multi-core processors, a process can consume CPU
+ // at a rate higher than wall-clock time, e.g. two cores at full utilization
+ // will result in a time delta of 2 seconds/per 1 wall-clock second.
+ TimeDelta GetCumulativeCPUUsage();
// Returns the number of average idle cpu wakeups per second since the last
// call.
int GetIdleWakeupsPerSecond();
- // Same as GetCPUUsage(), but will return consistent values on all platforms
- // (cancelling the Windows exception mentioned above) by returning a value in
- // the range of 0 to (100 * numCPUCores) everywhere.
- double GetPlatformIndependentCPUUsage();
+#if defined(OS_MACOSX)
+ // Returns the number of average "package idle exits" per second, which have
+ // a higher energy impact than a regular wakeup, since the last call.
+ //
+ // From the powermetrics man page:
+ // "With the exception of some Mac Pro systems, Mac and
+ // iOS systems are typically single package systems, wherein all CPUs are
+ // part of a single processor complex (typically a single IC die) with shared
+ // logic that can include (depending on system specifics) shared last level
+ // caches, an integrated memory controller etc. When all CPUs in the package
+ // are idle, the hardware can power-gate significant portions of the shared
+ // logic in addition to each individual processor's logic, as well as take
+ // measures such as placing DRAM in to self-refresh (also referred to as
+ // auto-refresh), place interconnects into lower-power states etc"
+ int GetPackageIdleWakeupsPerSecond();
+#endif
// Retrieves accounting information for all I/O operations performed by the
// process.
@@ -195,7 +177,7 @@ class BASE_EXPORT ProcessMetrics {
// otherwise.
bool GetIOCounters(IoCounters* io_counters) const;
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_AIX) || defined(OS_ANDROID)
// Returns the number of file descriptors currently open by the process, or
// -1 on error.
int GetOpenFdCount() const;
@@ -203,7 +185,19 @@ class BASE_EXPORT ProcessMetrics {
// Returns the soft limit of file descriptors that can be opened by the
// process, or -1 on error.
int GetOpenFdSoftLimit() const;
-#endif // defined(OS_LINUX)
+#endif // defined(OS_LINUX) || defined(OS_AIX) || defined(OS_ANDROID)
+
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ // Bytes of swap as reported by /proc/[pid]/status.
+ uint64_t GetVmSwapBytes() const;
+
+ // Minor and major page fault count as reported by /proc/[pid]/stat.
+ // Returns true for success.
+ bool GetPageFaultCounts(PageFaultCounts* counts) const;
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
+ // Returns total memory usage of malloc.
+ size_t GetMallocUsage();
private:
#if !defined(OS_MACOSX) || defined(OS_IOS)
@@ -212,17 +206,15 @@ class BASE_EXPORT ProcessMetrics {
ProcessMetrics(ProcessHandle process, PortProvider* port_provider);
#endif // !defined(OS_MACOSX) || defined(OS_IOS)
-#if defined(OS_LINUX) || defined(OS_ANDROID)
- bool GetWorkingSetKBytesStatm(WorkingSetKBytes* ws_usage) const;
-#endif
-
-#if defined(OS_CHROMEOS)
- bool GetWorkingSetKBytesTotmaps(WorkingSetKBytes *ws_usage) const;
-#endif
-
-#if defined(OS_MACOSX) || defined(OS_LINUX)
+#if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_AIX)
int CalculateIdleWakeupsPerSecond(uint64_t absolute_idle_wakeups);
#endif
+#if defined(OS_MACOSX)
+ // The subset of wakeups that cause a "package exit" can be tracked on macOS.
+ // See |GetPackageIdleWakeupsForSecond| comment for more info.
+ int CalculatePackageIdleWakeupsPerSecond(
+ uint64_t absolute_package_idle_wakeups);
+#endif
#if defined(OS_WIN)
win::ScopedHandle process_;
@@ -230,29 +222,32 @@ class BASE_EXPORT ProcessMetrics {
ProcessHandle process_;
#endif
- int processor_count_;
-
// Used to store the previous times and CPU usage counts so we can
// compute the CPU usage between calls.
TimeTicks last_cpu_time_;
- int64_t last_system_time_;
+#if !defined(OS_FREEBSD) || !defined(OS_POSIX)
+ TimeDelta last_cumulative_cpu_;
+#endif
-#if defined(OS_MACOSX) || defined(OS_LINUX)
+#if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_AIX)
// Same thing for idle wakeups.
TimeTicks last_idle_wakeups_time_;
uint64_t last_absolute_idle_wakeups_;
#endif
+#if defined(OS_MACOSX)
+ // And same thing for package idle exit wakeups.
+ TimeTicks last_package_idle_wakeups_time_;
+ uint64_t last_absolute_package_idle_wakeups_;
+#endif
+
#if !defined(OS_IOS)
#if defined(OS_MACOSX)
// Queries the port provider if it's set.
mach_port_t TaskForPid(ProcessHandle process) const;
PortProvider* port_provider_;
-#elif defined(OS_POSIX)
- // Jiffie count at the last_cpu_time_ we updated.
- int last_cpu_;
-#endif // defined(OS_POSIX)
+#endif // defined(OS_MACOSX)
#endif // !defined(OS_IOS)
DISALLOW_COPY_AND_ASSIGN(ProcessMetrics);
@@ -268,18 +263,19 @@ BASE_EXPORT size_t GetSystemCommitCharge();
// returned by GetPageSize().
BASE_EXPORT size_t GetPageSize();
-#if defined(OS_POSIX)
// Returns the maximum number of file descriptors that can be open by a process
// at once. If the number is unavailable, a conservative best guess is returned.
BASE_EXPORT size_t GetMaxFds();
-// Sets the file descriptor soft limit to |max_descriptors| or the OS hard
-// limit, whichever is lower.
-BASE_EXPORT void SetFdLimit(unsigned int max_descriptors);
+#if defined(OS_POSIX)
+// Increases the file descriptor soft limit to |max_descriptors| or the OS hard
+// limit, whichever is lower. If the limit is already higher than
+// |max_descriptors|, then nothing happens.
+BASE_EXPORT void IncreaseFdLimitTo(unsigned int max_descriptors);
#endif // defined(OS_POSIX)
#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) || \
- defined(OS_ANDROID)
+ defined(OS_ANDROID) || defined(OS_AIX) || defined(OS_FUCHSIA)
// Data about system-wide memory consumption. Values are in KB. Available on
// Windows, Mac, Linux, Android and Chrome OS.
//
@@ -295,7 +291,7 @@ struct BASE_EXPORT SystemMemoryInfoKB {
SystemMemoryInfoKB(const SystemMemoryInfoKB& other);
// Serializes the platform specific fields to value.
- std::unique_ptr<Value> ToValue() const;
+ std::unique_ptr<DictionaryValue> ToValue() const;
int total = 0;
@@ -312,7 +308,7 @@ struct BASE_EXPORT SystemMemoryInfoKB {
int avail_phys = 0;
#endif
-#if defined(OS_LINUX) || defined(OS_ANDROID)
+#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_AIX)
// This provides an estimate of available memory as described here:
// https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
// NOTE: this is ONLY valid in kernels 3.14 and up. Its value will always
@@ -326,7 +322,8 @@ struct BASE_EXPORT SystemMemoryInfoKB {
int swap_free = 0;
#endif
-#if defined(OS_ANDROID) || defined(OS_LINUX)
+#if defined(OS_ANDROID) || defined(OS_LINUX) || defined(OS_AIX) || \
+ defined(OS_FUCHSIA)
int buffers = 0;
int cached = 0;
int active_anon = 0;
@@ -335,12 +332,8 @@ struct BASE_EXPORT SystemMemoryInfoKB {
int inactive_file = 0;
int dirty = 0;
int reclaimable = 0;
-
- // vmstats data.
- unsigned long pswpin = 0;
- unsigned long pswpout = 0;
- unsigned long pgmajfault = 0;
-#endif // defined(OS_ANDROID) || defined(OS_LINUX)
+#endif // defined(OS_ANDROID) || defined(OS_LINUX) || defined(OS_AIX) ||
+ // defined(OS_FUCHSIA)
#if defined(OS_CHROMEOS)
int shmem = 0;
@@ -366,13 +359,13 @@ struct BASE_EXPORT SystemMemoryInfoKB {
BASE_EXPORT bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo);
#endif // defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) ||
- // defined(OS_ANDROID)
+ // defined(OS_ANDROID) || defined(OS_AIX) || defined(OS_FUCHSIA)
-#if defined(OS_LINUX) || defined(OS_ANDROID)
+#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_AIX)
// Parse the data found in /proc/<pid>/stat and return the sum of the
// CPU-related ticks. Returns -1 on parse error.
// Exposed for testing.
-BASE_EXPORT int ParseProcStatCPU(const std::string& input);
+BASE_EXPORT int ParseProcStatCPU(StringPiece input);
// Get the number of threads of |process| as available in /proc/<pid>/stat.
// This should be used with care as no synchronization with running threads is
@@ -385,13 +378,28 @@ BASE_EXPORT extern const char kProcSelfExe[];
// Parses a string containing the contents of /proc/meminfo
// returns true on success or false for a parsing error
-BASE_EXPORT bool ParseProcMeminfo(const std::string& input,
+// Exposed for testing.
+BASE_EXPORT bool ParseProcMeminfo(StringPiece input,
SystemMemoryInfoKB* meminfo);
+// Data from /proc/vmstat.
+struct BASE_EXPORT VmStatInfo {
+ // Serializes the platform specific fields to value.
+ std::unique_ptr<DictionaryValue> ToValue() const;
+
+ unsigned long pswpin = 0;
+ unsigned long pswpout = 0;
+ unsigned long pgmajfault = 0;
+};
+
+// Retrieves data from /proc/vmstat about system-wide vm operations.
+// Fills in the provided |vmstat| structure. Returns true on success.
+BASE_EXPORT bool GetVmStatInfo(VmStatInfo* vmstat);
+
// Parses a string containing the contents of /proc/vmstat
// returns true on success or false for a parsing error
-BASE_EXPORT bool ParseProcVmstat(const std::string& input,
- SystemMemoryInfoKB* meminfo);
+// Exposed for testing.
+BASE_EXPORT bool ParseProcVmstat(StringPiece input, VmStatInfo* vmstat);
// Data from /proc/diskstats about system-wide disk I/O.
struct BASE_EXPORT SystemDiskInfo {
@@ -401,23 +409,23 @@ struct BASE_EXPORT SystemDiskInfo {
// Serializes the platform specific fields to value.
std::unique_ptr<Value> ToValue() const;
- uint64_t reads;
- uint64_t reads_merged;
- uint64_t sectors_read;
- uint64_t read_time;
- uint64_t writes;
- uint64_t writes_merged;
- uint64_t sectors_written;
- uint64_t write_time;
- uint64_t io;
- uint64_t io_time;
- uint64_t weighted_io_time;
+ uint64_t reads = 0;
+ uint64_t reads_merged = 0;
+ uint64_t sectors_read = 0;
+ uint64_t read_time = 0;
+ uint64_t writes = 0;
+ uint64_t writes_merged = 0;
+ uint64_t sectors_written = 0;
+ uint64_t write_time = 0;
+ uint64_t io = 0;
+ uint64_t io_time = 0;
+ uint64_t weighted_io_time = 0;
};
// Checks whether the candidate string is a valid disk name, [hsv]d[a-z]+
// for a generic disk or mmcblk[0-9]+ for the MMC case.
// Names of disk partitions (e.g. sda1) are not valid.
-BASE_EXPORT bool IsValidDiskName(const std::string& candidate);
+BASE_EXPORT bool IsValidDiskName(StringPiece candidate);
// Retrieves data from /proc/diskstats about system-wide disk I/O.
// Fills in the provided |diskinfo| structure. Returns true on success.
@@ -425,6 +433,7 @@ BASE_EXPORT bool GetSystemDiskInfo(SystemDiskInfo* diskinfo);
// Returns the amount of time spent in user space since boot across all CPUs.
BASE_EXPORT TimeDelta GetUserCpuTimeSinceBoot();
+
#endif // defined(OS_LINUX) || defined(OS_ANDROID)
#if defined(OS_CHROMEOS)
@@ -441,16 +450,29 @@ struct BASE_EXPORT SwapInfo {
// Serializes the platform specific fields to value.
std::unique_ptr<Value> ToValue() const;
- uint64_t num_reads;
- uint64_t num_writes;
- uint64_t compr_data_size;
- uint64_t orig_data_size;
- uint64_t mem_used_total;
+ uint64_t num_reads = 0;
+ uint64_t num_writes = 0;
+ uint64_t compr_data_size = 0;
+ uint64_t orig_data_size = 0;
+ uint64_t mem_used_total = 0;
};
+// Parses a string containing the contents of /sys/block/zram0/mm_stat.
+// This should be used for the new ZRAM sysfs interfaces.
+// Returns true on success or false for a parsing error.
+// Exposed for testing.
+BASE_EXPORT bool ParseZramMmStat(StringPiece mm_stat_data, SwapInfo* swap_info);
+
+// Parses a string containing the contents of /sys/block/zram0/stat
+// This should be used for the new ZRAM sysfs interfaces.
+// Returns true on success or false for a parsing error.
+// Exposed for testing.
+BASE_EXPORT bool ParseZramStat(StringPiece stat_data, SwapInfo* swap_info);
+
// In ChromeOS, reads files from /sys/block/zram0 that contain ZRAM usage data.
// Fills in the provided |swap_data| structure.
-BASE_EXPORT void GetSwapInfo(SwapInfo* swap_info);
+// Returns true on success or false for a parsing error.
+BASE_EXPORT bool GetSwapInfo(SwapInfo* swap_info);
#endif // defined(OS_CHROMEOS)
// Collects and holds performance metrics for system memory and disk.
@@ -471,6 +493,7 @@ class SystemMetrics {
size_t committed_memory_;
#if defined(OS_LINUX) || defined(OS_ANDROID)
SystemMemoryInfoKB memory_info_;
+ VmStatInfo vmstat_info_;
SystemDiskInfo disk_info_;
#endif
#if defined(OS_CHROMEOS)
@@ -478,6 +501,42 @@ class SystemMetrics {
#endif
};
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+enum class MachVMRegionResult {
+ // There were no more memory regions between |address| and the end of the
+ // virtual address space.
+ Finished,
+
+ // All output parameters are invalid.
+ Error,
+
+ // All output parameters are filled in.
+ Success
+};
+
+// Returns info on the first memory region at or after |address|, including
+// resident memory and share mode. On Success, |size| reflects the size of the
+// memory region.
+// |size| and |info| are output parameters, only valid on Success.
+// |address| is an in-out parameter, than represents both the address to start
+// looking, and the start address of the memory region.
+BASE_EXPORT MachVMRegionResult GetTopInfo(mach_port_t task,
+ mach_vm_size_t* size,
+ mach_vm_address_t* address,
+ vm_region_top_info_data_t* info);
+
+// Returns info on the first memory region at or after |address|, including
+// protection values. On Success, |size| reflects the size of the
+// memory region.
+// Returns info on the first memory region at or after |address|, including
+// resident memory and share mode.
+// |size| and |info| are output parameters, only valid on Success.
+BASE_EXPORT MachVMRegionResult GetBasicInfo(mach_port_t task,
+ mach_vm_size_t* size,
+ mach_vm_address_t* address,
+ vm_region_basic_info_64* info);
+#endif // defined(OS_MACOSX) && !defined(OS_IOS)
+
} // namespace base
#endif // BASE_PROCESS_PROCESS_METRICS_H_
diff --git a/base/process/process_metrics_iocounters.h b/base/process/process_metrics_iocounters.h
new file mode 100644
index 0000000000..e12d090604
--- /dev/null
+++ b/base/process/process_metrics_iocounters.h
@@ -0,0 +1,37 @@
+// 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.
+
+// This is a separate file so that users of process metrics don't need to
+// include windows.h unless they need IoCounters.
+
+#ifndef BASE_PROCESS_PROCESS_METRICS_IOCOUNTERS_H_
+#define BASE_PROCESS_PROCESS_METRICS_IOCOUNTERS_H_
+
+#include <stdint.h>
+
+#include "base/process/process_metrics.h"
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace base {
+
+#if defined(OS_WIN)
+struct IoCounters : public IO_COUNTERS {};
+#elif defined(OS_POSIX)
+struct IoCounters {
+ uint64_t ReadOperationCount;
+ uint64_t WriteOperationCount;
+ uint64_t OtherOperationCount;
+ uint64_t ReadTransferCount;
+ uint64_t WriteTransferCount;
+ uint64_t OtherTransferCount;
+};
+#endif
+
+} // namespace base
+
+#endif // BASE_PROCESS_PROCESS_METRICS_IOCOUNTERS_H_
diff --git a/base/process/process_metrics_linux.cc b/base/process/process_metrics_linux.cc
index ba0dfa76b9..16cde35368 100644
--- a/base/process/process_metrics_linux.cc
+++ b/base/process/process_metrics_linux.cc
@@ -18,12 +18,13 @@
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
+#include "base/optional.h"
#include "base/process/internal_linux.h"
+#include "base/process/process_metrics_iocounters.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/string_util.h"
-#include "base/sys_info.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
@@ -32,134 +33,157 @@ namespace base {
namespace {
void TrimKeyValuePairs(StringPairs* pairs) {
- DCHECK(pairs);
- StringPairs& p_ref = *pairs;
- for (size_t i = 0; i < p_ref.size(); ++i) {
- TrimWhitespaceASCII(p_ref[i].first, TRIM_ALL, &p_ref[i].first);
- TrimWhitespaceASCII(p_ref[i].second, TRIM_ALL, &p_ref[i].second);
+ for (auto& pair : *pairs) {
+ TrimWhitespaceASCII(pair.first, TRIM_ALL, &pair.first);
+ TrimWhitespaceASCII(pair.second, TRIM_ALL, &pair.second);
}
}
#if defined(OS_CHROMEOS)
// Read a file with a single number string and return the number as a uint64_t.
-static uint64_t ReadFileToUint64(const FilePath file) {
- std::string file_as_string;
- if (!ReadFileToString(file, &file_as_string))
+uint64_t ReadFileToUint64(const FilePath& file) {
+ std::string file_contents;
+ if (!ReadFileToString(file, &file_contents))
return 0;
- TrimWhitespaceASCII(file_as_string, TRIM_ALL, &file_as_string);
- uint64_t file_as_uint64 = 0;
- if (!StringToUint64(file_as_string, &file_as_uint64))
+ TrimWhitespaceASCII(file_contents, TRIM_ALL, &file_contents);
+ uint64_t file_contents_uint64 = 0;
+ if (!StringToUint64(file_contents, &file_contents_uint64))
return 0;
- return file_as_uint64;
+ return file_contents_uint64;
}
#endif
-// Read /proc/<pid>/status and return the value for |field|, or 0 on failure.
-// Only works for fields in the form of "Field: value kB".
-size_t ReadProcStatusAndGetFieldAsSizeT(pid_t pid, const std::string& field) {
- std::string status;
+// Read |filename| in /proc/<pid>/, split the entries into key/value pairs, and
+// trim the key and value. On success, return true and write the trimmed
+// key/value pairs into |key_value_pairs|.
+bool ReadProcFileToTrimmedStringPairs(pid_t pid,
+ StringPiece filename,
+ StringPairs* key_value_pairs) {
+ std::string status_data;
{
// Synchronously reading files in /proc does not hit the disk.
ThreadRestrictions::ScopedAllowIO allow_io;
- FilePath stat_file = internal::GetProcPidDir(pid).Append("status");
- if (!ReadFileToString(stat_file, &status))
- return 0;
+ FilePath status_file = internal::GetProcPidDir(pid).Append(filename);
+ if (!ReadFileToString(status_file, &status_data))
+ return false;
}
+ SplitStringIntoKeyValuePairs(status_data, ':', '\n', key_value_pairs);
+ TrimKeyValuePairs(key_value_pairs);
+ return true;
+}
+// Read /proc/<pid>/status and return the value for |field|, or 0 on failure.
+// Only works for fields in the form of "Field: value kB".
+size_t ReadProcStatusAndGetFieldAsSizeT(pid_t pid, StringPiece field) {
StringPairs pairs;
- SplitStringIntoKeyValuePairs(status, ':', '\n', &pairs);
- TrimKeyValuePairs(&pairs);
- for (size_t i = 0; i < pairs.size(); ++i) {
- const std::string& key = pairs[i].first;
- const std::string& value_str = pairs[i].second;
- if (key == field) {
- std::vector<StringPiece> split_value_str =
- SplitStringPiece(value_str, " ", TRIM_WHITESPACE, SPLIT_WANT_ALL);
- if (split_value_str.size() != 2 || split_value_str[1] != "kB") {
- NOTREACHED();
- return 0;
- }
- size_t value;
- if (!StringToSizeT(split_value_str[0], &value)) {
- NOTREACHED();
- return 0;
- }
- return value;
+ if (!ReadProcFileToTrimmedStringPairs(pid, "status", &pairs))
+ return 0;
+
+ for (const auto& pair : pairs) {
+ const std::string& key = pair.first;
+ const std::string& value_str = pair.second;
+ if (key != field)
+ continue;
+
+ std::vector<StringPiece> split_value_str =
+ SplitStringPiece(value_str, " ", TRIM_WHITESPACE, SPLIT_WANT_ALL);
+ if (split_value_str.size() != 2 || split_value_str[1] != "kB") {
+ NOTREACHED();
+ return 0;
+ }
+ size_t value;
+ if (!StringToSizeT(split_value_str[0], &value)) {
+ NOTREACHED();
+ return 0;
}
+ return value;
}
// This can be reached if the process dies when proc is read -- in that case,
// the kernel can return missing fields.
return 0;
}
-#if defined(OS_LINUX)
-// Read /proc/<pid>/sched and look for |field|. On succes, return true and
+#if defined(OS_LINUX) || defined(OS_AIX)
+// Read /proc/<pid>/status and look for |field|. On success, return true and
// write the value for |field| into |result|.
// Only works for fields in the form of "field : uint_value"
-bool ReadProcSchedAndGetFieldAsUint64(pid_t pid,
- const std::string& field,
- uint64_t* result) {
- std::string sched_data;
- {
- // Synchronously reading files in /proc does not hit the disk.
- ThreadRestrictions::ScopedAllowIO allow_io;
- FilePath sched_file = internal::GetProcPidDir(pid).Append("sched");
- if (!ReadFileToString(sched_file, &sched_data))
- return false;
- }
-
+bool ReadProcStatusAndGetFieldAsUint64(pid_t pid,
+ StringPiece field,
+ uint64_t* result) {
StringPairs pairs;
- SplitStringIntoKeyValuePairs(sched_data, ':', '\n', &pairs);
- TrimKeyValuePairs(&pairs);
- for (size_t i = 0; i < pairs.size(); ++i) {
- const std::string& key = pairs[i].first;
- const std::string& value_str = pairs[i].second;
- if (key == field) {
- uint64_t value;
- if (!StringToUint64(value_str, &value))
- return false;
- *result = value;
- return true;
- }
+ if (!ReadProcFileToTrimmedStringPairs(pid, "status", &pairs))
+ return false;
+
+ for (const auto& pair : pairs) {
+ const std::string& key = pair.first;
+ const std::string& value_str = pair.second;
+ if (key != field)
+ continue;
+
+ uint64_t value;
+ if (!StringToUint64(value_str, &value))
+ return false;
+ *result = value;
+ return true;
}
return false;
}
-#endif // defined(OS_LINUX)
+#endif // defined(OS_LINUX) || defined(OS_AIX)
// Get the total CPU of a single process. Return value is number of jiffies
// on success or -1 on error.
-int GetProcessCPU(pid_t pid) {
- // Use /proc/<pid>/task to find all threads and parse their /stat file.
- FilePath task_path = internal::GetProcPidDir(pid).Append("task");
-
- DIR* dir = opendir(task_path.value().c_str());
- if (!dir) {
- DPLOG(ERROR) << "opendir(" << task_path.value() << ")";
+int64_t GetProcessCPU(pid_t pid) {
+ std::string buffer;
+ std::vector<std::string> proc_stats;
+ if (!internal::ReadProcStats(pid, &buffer) ||
+ !internal::ParseProcStats(buffer, &proc_stats)) {
return -1;
}
- int total_cpu = 0;
- while (struct dirent* ent = readdir(dir)) {
- pid_t tid = internal::ProcDirSlotToPid(ent->d_name);
- if (!tid)
- continue;
+ int64_t total_cpu =
+ internal::GetProcStatsFieldAsInt64(proc_stats, internal::VM_UTIME) +
+ internal::GetProcStatsFieldAsInt64(proc_stats, internal::VM_STIME);
- // Synchronously reading files in /proc does not hit the disk.
- ThreadRestrictions::ScopedAllowIO allow_io;
+ return total_cpu;
+}
- std::string stat;
- FilePath stat_path =
- task_path.Append(ent->d_name).Append(internal::kStatFile);
- if (ReadFileToString(stat_path, &stat)) {
- int cpu = ParseProcStatCPU(stat);
- if (cpu > 0)
- total_cpu += cpu;
+#if defined(OS_CHROMEOS)
+// Report on Chrome OS GEM object graphics memory. /run/debugfs_gpu is a
+// bind mount into /sys/kernel/debug and synchronously reading the in-memory
+// files in /sys is fast.
+void ReadChromeOSGraphicsMemory(SystemMemoryInfoKB* meminfo) {
+#if defined(ARCH_CPU_ARM_FAMILY)
+ FilePath geminfo_file("/run/debugfs_gpu/exynos_gem_objects");
+#else
+ FilePath geminfo_file("/run/debugfs_gpu/i915_gem_objects");
+#endif
+ std::string geminfo_data;
+ meminfo->gem_objects = -1;
+ meminfo->gem_size = -1;
+ if (ReadFileToString(geminfo_file, &geminfo_data)) {
+ int gem_objects = -1;
+ long long gem_size = -1;
+ int num_res = sscanf(geminfo_data.c_str(), "%d objects, %lld bytes",
+ &gem_objects, &gem_size);
+ if (num_res == 2) {
+ meminfo->gem_objects = gem_objects;
+ meminfo->gem_size = gem_size;
}
}
- closedir(dir);
- return total_cpu;
+#if defined(ARCH_CPU_ARM_FAMILY)
+ // Incorporate Mali graphics memory if present.
+ FilePath mali_memory_file("/sys/class/misc/mali0/device/memory");
+ std::string mali_memory_data;
+ if (ReadFileToString(mali_memory_file, &mali_memory_data)) {
+ long long mali_size = -1;
+ int num_res = sscanf(mali_memory_data.c_str(), "%lld bytes", &mali_size);
+ if (num_res == 1)
+ meminfo->gem_size += mali_size;
+ }
+#endif // defined(ARCH_CPU_ARM_FAMILY)
}
+#endif // defined(OS_CHROMEOS)
} // namespace
@@ -169,114 +193,29 @@ std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateProcessMetrics(
return WrapUnique(new ProcessMetrics(process));
}
-// On linux, we return vsize.
-size_t ProcessMetrics::GetPagefileUsage() const {
- return internal::ReadProcStatsAndGetFieldAsSizeT(process_,
- internal::VM_VSIZE);
-}
-
-// On linux, we return the high water mark of vsize.
-size_t ProcessMetrics::GetPeakPagefileUsage() const {
- return ReadProcStatusAndGetFieldAsSizeT(process_, "VmPeak") * 1024;
-}
-
-// On linux, we return RSS.
-size_t ProcessMetrics::GetWorkingSetSize() const {
+size_t ProcessMetrics::GetResidentSetSize() const {
return internal::ReadProcStatsAndGetFieldAsSizeT(process_, internal::VM_RSS) *
getpagesize();
}
-// On linux, we return the high water mark of RSS.
-size_t ProcessMetrics::GetPeakWorkingSetSize() const {
- return ReadProcStatusAndGetFieldAsSizeT(process_, "VmHWM") * 1024;
-}
-
-bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes,
- size_t* shared_bytes) const {
- WorkingSetKBytes ws_usage;
- if (!GetWorkingSetKBytes(&ws_usage))
- return false;
-
- if (private_bytes)
- *private_bytes = ws_usage.priv * 1024;
-
- if (shared_bytes)
- *shared_bytes = ws_usage.shared * 1024;
-
- return true;
-}
-
-bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const {
-#if defined(OS_CHROMEOS)
- if (GetWorkingSetKBytesTotmaps(ws_usage))
- return true;
-#endif
- return GetWorkingSetKBytesStatm(ws_usage);
-}
-
-double ProcessMetrics::GetCPUUsage() {
- TimeTicks time = TimeTicks::Now();
-
- if (last_cpu_ == 0) {
- // First call, just set the last values.
- last_cpu_time_ = time;
- last_cpu_ = GetProcessCPU(process_);
- return 0.0;
- }
-
- TimeDelta time_delta = time - last_cpu_time_;
- if (time_delta.is_zero()) {
- NOTREACHED();
- return 0.0;
- }
-
- int cpu = GetProcessCPU(process_);
-
- // We have the number of jiffies in the time period. Convert to percentage.
- // Note this means we will go *over* 100 in the case where multiple threads
- // are together adding to more than one CPU's worth.
- TimeDelta cpu_time = internal::ClockTicksToTimeDelta(cpu);
- TimeDelta last_cpu_time = internal::ClockTicksToTimeDelta(last_cpu_);
-
- // If the number of threads running in the process has decreased since the
- // last time this function was called, |last_cpu_time| will be greater than
- // |cpu_time| which will result in a negative value in the below percentage
- // calculation. We prevent this by clamping to 0. crbug.com/546565.
- // This computation is known to be shaky when threads are destroyed between
- // "last" and "now", but for our current purposes, it's all right.
- double percentage = 0.0;
- if (last_cpu_time < cpu_time) {
- percentage = 100.0 * (cpu_time - last_cpu_time).InSecondsF() /
- time_delta.InSecondsF();
- }
-
- last_cpu_time_ = time;
- last_cpu_ = cpu;
-
- return percentage;
+TimeDelta ProcessMetrics::GetCumulativeCPUUsage() {
+ return internal::ClockTicksToTimeDelta(GetProcessCPU(process_));
}
-// To have /proc/self/io file you must enable CONFIG_TASK_IO_ACCOUNTING
-// in your kernel configuration.
+// For the /proc/self/io file to exist, the Linux kernel must have
+// CONFIG_TASK_IO_ACCOUNTING enabled.
bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const {
- // Synchronously reading files in /proc does not hit the disk.
- ThreadRestrictions::ScopedAllowIO allow_io;
-
- std::string proc_io_contents;
- FilePath io_file = internal::GetProcPidDir(process_).Append("io");
- if (!ReadFileToString(io_file, &proc_io_contents))
+ StringPairs pairs;
+ if (!ReadProcFileToTrimmedStringPairs(process_, "io", &pairs))
return false;
io_counters->OtherOperationCount = 0;
io_counters->OtherTransferCount = 0;
- StringPairs pairs;
- SplitStringIntoKeyValuePairs(proc_io_contents, ':', '\n', &pairs);
- TrimKeyValuePairs(&pairs);
- for (size_t i = 0; i < pairs.size(); ++i) {
- const std::string& key = pairs[i].first;
- const std::string& value_str = pairs[i].second;
- uint64_t* target_counter = NULL;
+ for (const auto& pair : pairs) {
+ const std::string& key = pair.first;
+ const std::string& value_str = pair.second;
+ uint64_t* target_counter = nullptr;
if (key == "syscr")
target_counter = &io_counters->ReadOperationCount;
else if (key == "syscw")
@@ -293,7 +232,31 @@ bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const {
return true;
}
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+uint64_t ProcessMetrics::GetVmSwapBytes() const {
+ return ReadProcStatusAndGetFieldAsSizeT(process_, "VmSwap") * 1024;
+}
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+bool ProcessMetrics::GetPageFaultCounts(PageFaultCounts* counts) const {
+ // We are not using internal::ReadStatsFileAndGetFieldAsInt64(), since it
+ // would read the file twice, and return inconsistent numbers.
+ std::string stats_data;
+ if (!internal::ReadProcStats(process_, &stats_data))
+ return false;
+ std::vector<std::string> proc_stats;
+ if (!internal::ParseProcStats(stats_data, &proc_stats))
+ return false;
+
+ counts->minor =
+ internal::GetProcStatsFieldAsInt64(proc_stats, internal::VM_MINFLT);
+ counts->major =
+ internal::GetProcStatsFieldAsInt64(proc_stats, internal::VM_MAJFLT);
+ return true;
+}
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
int ProcessMetrics::GetOpenFdCount() const {
// Use /proc/<pid>/fd to count the number of entries there.
FilePath fd_path = internal::GetProcPidDir(process_).Append("fd");
@@ -320,40 +283,34 @@ int ProcessMetrics::GetOpenFdSoftLimit() const {
if (!ReadFileToString(fd_path, &limits_contents))
return -1;
- for (const auto& line :
- base::SplitStringPiece(limits_contents, "\n", base::KEEP_WHITESPACE,
- base::SPLIT_WANT_NONEMPTY)) {
- if (line.starts_with("Max open files")) {
- auto tokens = base::SplitStringPiece(line, " ", base::TRIM_WHITESPACE,
- base::SPLIT_WANT_NONEMPTY);
- if (tokens.size() > 3) {
- int limit = -1;
- if (StringToInt(tokens[3], &limit))
- return limit;
+ for (const auto& line : SplitStringPiece(
+ limits_contents, "\n", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY)) {
+ if (!line.starts_with("Max open files"))
+ continue;
+
+ auto tokens =
+ SplitStringPiece(line, " ", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+ if (tokens.size() > 3) {
+ int limit = -1;
+ if (!StringToInt(tokens[3], &limit))
return -1;
- }
+ return limit;
}
}
return -1;
}
-#endif // defined(OS_LINUX)
-
+#if defined(OS_LINUX) || defined(OS_AIX)
ProcessMetrics::ProcessMetrics(ProcessHandle process)
- : process_(process),
- last_system_time_(0),
-#if defined(OS_LINUX)
- last_absolute_idle_wakeups_(0),
+ : process_(process), last_absolute_idle_wakeups_(0) {}
+#else
+ProcessMetrics::ProcessMetrics(ProcessHandle process) : process_(process) {}
#endif
- last_cpu_(0) {
- processor_count_ = SysInfo::NumberOfProcessors();
-}
#if defined(OS_CHROMEOS)
// Private, Shared and Proportional working set sizes are obtained from
// /proc/<pid>/totmaps
-bool ProcessMetrics::GetWorkingSetKBytesTotmaps(WorkingSetKBytes *ws_usage)
- const {
+ProcessMetrics::TotalsSummary ProcessMetrics::GetTotalsSummary() const {
// The format of /proc/<pid>/totmaps is:
//
// Rss: 6120 kB
@@ -367,7 +324,8 @@ bool ProcessMetrics::GetWorkingSetKBytesTotmaps(WorkingSetKBytes *ws_usage)
// AnonHugePages: XXX kB
// Swap: XXX kB
// Locked: XXX kB
- const size_t kPssIndex = (1 * 3) + 1;
+ ProcessMetrics::TotalsSummary summary = {};
+
const size_t kPrivate_CleanIndex = (4 * 3) + 1;
const size_t kPrivate_DirtyIndex = (5 * 3) + 1;
const size_t kSwapIndex = (9 * 3) + 1;
@@ -378,84 +336,36 @@ bool ProcessMetrics::GetWorkingSetKBytesTotmaps(WorkingSetKBytes *ws_usage)
ThreadRestrictions::ScopedAllowIO allow_io;
bool ret = ReadFileToString(totmaps_file, &totmaps_data);
if (!ret || totmaps_data.length() == 0)
- return false;
+ return summary;
}
std::vector<std::string> totmaps_fields = SplitString(
totmaps_data, kWhitespaceASCII, KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY);
- DCHECK_EQ("Pss:", totmaps_fields[kPssIndex-1]);
DCHECK_EQ("Private_Clean:", totmaps_fields[kPrivate_CleanIndex - 1]);
DCHECK_EQ("Private_Dirty:", totmaps_fields[kPrivate_DirtyIndex - 1]);
DCHECK_EQ("Swap:", totmaps_fields[kSwapIndex-1]);
- int pss = 0;
- int private_clean = 0;
- int private_dirty = 0;
- int swap = 0;
- bool ret = true;
- ret &= StringToInt(totmaps_fields[kPssIndex], &pss);
- ret &= StringToInt(totmaps_fields[kPrivate_CleanIndex], &private_clean);
- ret &= StringToInt(totmaps_fields[kPrivate_DirtyIndex], &private_dirty);
- ret &= StringToInt(totmaps_fields[kSwapIndex], &swap);
-
- // On ChromeOS swap is to zram. We count this as private / shared, as
- // increased swap decreases available RAM to user processes, which would
- // otherwise create surprising results.
- ws_usage->priv = private_clean + private_dirty + swap;
- ws_usage->shared = pss + swap;
- ws_usage->shareable = 0;
- ws_usage->swapped = swap;
- return ret;
-}
-#endif
-
-// Private and Shared working set sizes are obtained from /proc/<pid>/statm.
-bool ProcessMetrics::GetWorkingSetKBytesStatm(WorkingSetKBytes* ws_usage)
- const {
- // Use statm instead of smaps because smaps is:
- // a) Large and slow to parse.
- // b) Unavailable in the SUID sandbox.
-
- // First we need to get the page size, since everything is measured in pages.
- // For details, see: man 5 proc.
- const int page_size_kb = getpagesize() / 1024;
- if (page_size_kb <= 0)
- return false;
-
- std::string statm;
- {
- FilePath statm_file = internal::GetProcPidDir(process_).Append("statm");
- // Synchronously reading files in /proc does not hit the disk.
- ThreadRestrictions::ScopedAllowIO allow_io;
- bool ret = ReadFileToString(statm_file, &statm);
- if (!ret || statm.length() == 0)
- return false;
- }
-
- std::vector<StringPiece> statm_vec =
- SplitStringPiece(statm, " ", TRIM_WHITESPACE, SPLIT_WANT_ALL);
- if (statm_vec.size() != 7)
- return false; // Not the format we expect.
-
- int statm_rss, statm_shared;
- bool ret = true;
- ret &= StringToInt(statm_vec[1], &statm_rss);
- ret &= StringToInt(statm_vec[2], &statm_shared);
-
- ws_usage->priv = (statm_rss - statm_shared) * page_size_kb;
- ws_usage->shared = statm_shared * page_size_kb;
+ int private_clean_kb = 0;
+ int private_dirty_kb = 0;
+ int swap_kb = 0;
+ bool success = true;
+ success &=
+ StringToInt(totmaps_fields[kPrivate_CleanIndex], &private_clean_kb);
+ success &=
+ StringToInt(totmaps_fields[kPrivate_DirtyIndex], &private_dirty_kb);
+ success &= StringToInt(totmaps_fields[kSwapIndex], &swap_kb);
- // Sharable is not calculated, as it does not provide interesting data.
- ws_usage->shareable = 0;
+ if (!success)
+ return summary;
-#if defined(OS_CHROMEOS)
- // Can't get swapped memory from statm.
- ws_usage->swapped = 0;
-#endif
+ summary.private_clean_kb = private_clean_kb;
+ summary.private_dirty_kb = private_dirty_kb;
+ summary.swap_kb = swap_kb;
- return ret;
+ return summary;
}
+#endif
size_t GetSystemCommitCharge() {
SystemMemoryInfoKB meminfo;
@@ -464,7 +374,7 @@ size_t GetSystemCommitCharge() {
return meminfo.total - meminfo.free - meminfo.buffers - meminfo.cached;
}
-int ParseProcStatCPU(const std::string& input) {
+int ParseProcStatCPU(StringPiece input) {
// |input| may be empty if the process disappeared somehow.
// e.g. http://crbug.com/145811.
if (input.empty())
@@ -496,13 +406,13 @@ int ParseProcStatCPU(const std::string& input) {
return -1;
}
-const char kProcSelfExe[] = "/proc/self/exe";
-
int GetNumberOfThreads(ProcessHandle process) {
return internal::ReadProcStatsAndGetFieldAsInt64(process,
internal::VM_NUMTHREADS);
}
+const char kProcSelfExe[] = "/proc/self/exe";
+
namespace {
// The format of /proc/diskstats is:
@@ -558,9 +468,8 @@ const size_t kDiskWeightedIOTime = 13;
} // namespace
-std::unique_ptr<Value> SystemMemoryInfoKB::ToValue() const {
- std::unique_ptr<DictionaryValue> res(new DictionaryValue());
-
+std::unique_ptr<DictionaryValue> SystemMemoryInfoKB::ToValue() const {
+ auto res = std::make_unique<DictionaryValue>();
res->SetInteger("total", total);
res->SetInteger("free", free);
res->SetInteger("available", available);
@@ -575,9 +484,6 @@ std::unique_ptr<Value> SystemMemoryInfoKB::ToValue() const {
res->SetInteger("swap_used", swap_total - swap_free);
res->SetInteger("dirty", dirty);
res->SetInteger("reclaimable", reclaimable);
- res->SetInteger("pswpin", pswpin);
- res->SetInteger("pswpout", pswpout);
- res->SetInteger("pgmajfault", pgmajfault);
#ifdef OS_CHROMEOS
res->SetInteger("shmem", shmem);
res->SetInteger("slab", slab);
@@ -585,12 +491,10 @@ std::unique_ptr<Value> SystemMemoryInfoKB::ToValue() const {
res->SetInteger("gem_size", gem_size);
#endif
- return std::move(res);
+ return res;
}
-// exposed for testing
-bool ParseProcMeminfo(const std::string& meminfo_data,
- SystemMemoryInfoKB* meminfo) {
+bool ParseProcMeminfo(StringPiece meminfo_data, SystemMemoryInfoKB* meminfo) {
// The format of /proc/meminfo is:
//
// MemTotal: 8235324 kB
@@ -601,23 +505,23 @@ bool ParseProcMeminfo(const std::string& meminfo_data,
// There is no guarantee on the ordering or position
// though it doesn't appear to change very often
- // As a basic sanity check, let's make sure we at least get non-zero
- // MemTotal value
+ // As a basic sanity check at the end, make sure the MemTotal value will be at
+ // least non-zero. So start off with a zero total.
meminfo->total = 0;
for (const StringPiece& line : SplitStringPiece(
meminfo_data, "\n", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY)) {
std::vector<StringPiece> tokens = SplitStringPiece(
line, kWhitespaceASCII, TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
- // HugePages_* only has a number and no suffix so we can't rely on
- // there being exactly 3 tokens.
+ // HugePages_* only has a number and no suffix so there may not be exactly 3
+ // tokens.
if (tokens.size() <= 1) {
DLOG(WARNING) << "meminfo: tokens: " << tokens.size()
<< " malformed line: " << line.as_string();
continue;
}
- int* target = NULL;
+ int* target = nullptr;
if (tokens[0] == "MemTotal:")
target = &meminfo->total;
else if (tokens[0] == "MemFree:")
@@ -645,7 +549,7 @@ bool ParseProcMeminfo(const std::string& meminfo_data,
else if (tokens[0] == "SReclaimable:")
target = &meminfo->reclaimable;
#if defined(OS_CHROMEOS)
- // Chrome OS has a tweaked kernel that allows us to query Shmem, which is
+ // Chrome OS has a tweaked kernel that allows querying Shmem, which is
// usually video memory otherwise invisible to the OS.
else if (tokens[0] == "Shmem:")
target = &meminfo->shmem;
@@ -656,13 +560,11 @@ bool ParseProcMeminfo(const std::string& meminfo_data,
StringToInt(tokens[1], target);
}
- // Make sure we got a valid MemTotal.
+ // Make sure the MemTotal is valid.
return meminfo->total > 0;
}
-// exposed for testing
-bool ParseProcVmstat(const std::string& vmstat_data,
- SystemMemoryInfoKB* meminfo) {
+bool ParseProcVmstat(StringPiece vmstat_data, VmStatInfo* vmstat) {
// The format of /proc/vmstat is:
//
// nr_free_pages 299878
@@ -671,9 +573,11 @@ bool ParseProcVmstat(const std::string& vmstat_data,
// nr_inactive_file 2015629
// ...
//
- // We iterate through the whole file because the position of the
+ // Iterate through the whole file because the position of the
// fields are dependent on the kernel version and configuration.
-
+ bool has_pswpin = false;
+ bool has_pswpout = false;
+ bool has_pgmajfault = false;
for (const StringPiece& line : SplitStringPiece(
vmstat_data, "\n", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY)) {
std::vector<StringPiece> tokens = SplitStringPiece(
@@ -686,15 +590,23 @@ bool ParseProcVmstat(const std::string& vmstat_data,
continue;
if (tokens[0] == "pswpin") {
- meminfo->pswpin = val;
+ vmstat->pswpin = val;
+ DCHECK(!has_pswpin);
+ has_pswpin = true;
} else if (tokens[0] == "pswpout") {
- meminfo->pswpout = val;
+ vmstat->pswpout = val;
+ DCHECK(!has_pswpout);
+ has_pswpout = true;
} else if (tokens[0] == "pgmajfault") {
- meminfo->pgmajfault = val;
+ vmstat->pgmajfault = val;
+ DCHECK(!has_pgmajfault);
+ has_pgmajfault = true;
}
+ if (has_pswpin && has_pswpout && has_pgmajfault)
+ return true;
}
- return true;
+ return false;
}
bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) {
@@ -715,41 +627,23 @@ bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) {
}
#if defined(OS_CHROMEOS)
- // Report on Chrome OS GEM object graphics memory. /run/debugfs_gpu is a
- // bind mount into /sys/kernel/debug and synchronously reading the in-memory
- // files in /sys is fast.
-#if defined(ARCH_CPU_ARM_FAMILY)
- FilePath geminfo_file("/run/debugfs_gpu/exynos_gem_objects");
-#else
- FilePath geminfo_file("/run/debugfs_gpu/i915_gem_objects");
+ ReadChromeOSGraphicsMemory(meminfo);
#endif
- std::string geminfo_data;
- meminfo->gem_objects = -1;
- meminfo->gem_size = -1;
- if (ReadFileToString(geminfo_file, &geminfo_data)) {
- int gem_objects = -1;
- long long gem_size = -1;
- int num_res = sscanf(geminfo_data.c_str(),
- "%d objects, %lld bytes",
- &gem_objects, &gem_size);
- if (num_res == 2) {
- meminfo->gem_objects = gem_objects;
- meminfo->gem_size = gem_size;
- }
- }
-#if defined(ARCH_CPU_ARM_FAMILY)
- // Incorporate Mali graphics memory if present.
- FilePath mali_memory_file("/sys/class/misc/mali0/device/memory");
- std::string mali_memory_data;
- if (ReadFileToString(mali_memory_file, &mali_memory_data)) {
- long long mali_size = -1;
- int num_res = sscanf(mali_memory_data.c_str(), "%lld bytes", &mali_size);
- if (num_res == 1)
- meminfo->gem_size += mali_size;
- }
-#endif // defined(ARCH_CPU_ARM_FAMILY)
-#endif // defined(OS_CHROMEOS)
+ return true;
+}
+
+std::unique_ptr<DictionaryValue> VmStatInfo::ToValue() const {
+ auto res = std::make_unique<DictionaryValue>();
+ res->SetInteger("pswpin", pswpin);
+ res->SetInteger("pswpout", pswpout);
+ res->SetInteger("pgmajfault", pgmajfault);
+ return res;
+}
+
+bool GetVmStatInfo(VmStatInfo* vmstat) {
+ // Synchronously reading files in /proc and /sys are safe.
+ ThreadRestrictions::ScopedAllowIO allow_io;
FilePath vmstat_file("/proc/vmstat");
std::string vmstat_data;
@@ -757,11 +651,10 @@ bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) {
DLOG(WARNING) << "Failed to open " << vmstat_file.value();
return false;
}
- if (!ParseProcVmstat(vmstat_data, meminfo)) {
+ if (!ParseProcVmstat(vmstat_data, vmstat)) {
DLOG(WARNING) << "Failed to parse " << vmstat_file.value();
return false;
}
-
return true;
}
@@ -782,7 +675,7 @@ SystemDiskInfo::SystemDiskInfo() {
SystemDiskInfo::SystemDiskInfo(const SystemDiskInfo& other) = default;
std::unique_ptr<Value> SystemDiskInfo::ToValue() const {
- std::unique_ptr<DictionaryValue> res(new DictionaryValue());
+ auto res = std::make_unique<DictionaryValue>();
// Write out uint64_t variables as doubles.
// Note: this may discard some precision, but for JS there's no other option.
@@ -801,9 +694,10 @@ std::unique_ptr<Value> SystemDiskInfo::ToValue() const {
return std::move(res);
}
-bool IsValidDiskName(const std::string& candidate) {
+bool IsValidDiskName(StringPiece candidate) {
if (candidate.length() < 3)
return false;
+
if (candidate[1] == 'd' &&
(candidate[0] == 'h' || candidate[0] == 's' || candidate[0] == 'v')) {
// [hsv]d[a-z]+ case
@@ -815,14 +709,11 @@ bool IsValidDiskName(const std::string& candidate) {
}
const char kMMCName[] = "mmcblk";
- const size_t kMMCNameLen = strlen(kMMCName);
- if (candidate.length() < kMMCNameLen + 1)
- return false;
- if (candidate.compare(0, kMMCNameLen, kMMCName) != 0)
+ if (!candidate.starts_with(kMMCName))
return false;
// mmcblk[0-9]+ case
- for (size_t i = kMMCNameLen; i < candidate.length(); ++i) {
+ for (size_t i = strlen(kMMCName); i < candidate.length(); ++i) {
if (!isdigit(candidate[i]))
return false;
}
@@ -842,7 +733,7 @@ bool GetSystemDiskInfo(SystemDiskInfo* diskinfo) {
std::vector<StringPiece> diskinfo_lines = SplitStringPiece(
diskinfo_data, "\n", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY);
- if (diskinfo_lines.size() == 0) {
+ if (diskinfo_lines.empty()) {
DLOG(WARNING) << "No lines found";
return false;
}
@@ -876,31 +767,32 @@ bool GetSystemDiskInfo(SystemDiskInfo* diskinfo) {
line, kWhitespaceASCII, TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
// Fields may have overflowed and reset to zero.
- if (IsValidDiskName(disk_fields[kDiskDriveName].as_string())) {
- StringToUint64(disk_fields[kDiskReads], &reads);
- StringToUint64(disk_fields[kDiskReadsMerged], &reads_merged);
- StringToUint64(disk_fields[kDiskSectorsRead], &sectors_read);
- StringToUint64(disk_fields[kDiskReadTime], &read_time);
- StringToUint64(disk_fields[kDiskWrites], &writes);
- StringToUint64(disk_fields[kDiskWritesMerged], &writes_merged);
- StringToUint64(disk_fields[kDiskSectorsWritten], &sectors_written);
- StringToUint64(disk_fields[kDiskWriteTime], &write_time);
- StringToUint64(disk_fields[kDiskIO], &io);
- StringToUint64(disk_fields[kDiskIOTime], &io_time);
- StringToUint64(disk_fields[kDiskWeightedIOTime], &weighted_io_time);
-
- diskinfo->reads += reads;
- diskinfo->reads_merged += reads_merged;
- diskinfo->sectors_read += sectors_read;
- diskinfo->read_time += read_time;
- diskinfo->writes += writes;
- diskinfo->writes_merged += writes_merged;
- diskinfo->sectors_written += sectors_written;
- diskinfo->write_time += write_time;
- diskinfo->io += io;
- diskinfo->io_time += io_time;
- diskinfo->weighted_io_time += weighted_io_time;
- }
+ if (!IsValidDiskName(disk_fields[kDiskDriveName].as_string()))
+ continue;
+
+ StringToUint64(disk_fields[kDiskReads], &reads);
+ StringToUint64(disk_fields[kDiskReadsMerged], &reads_merged);
+ StringToUint64(disk_fields[kDiskSectorsRead], &sectors_read);
+ StringToUint64(disk_fields[kDiskReadTime], &read_time);
+ StringToUint64(disk_fields[kDiskWrites], &writes);
+ StringToUint64(disk_fields[kDiskWritesMerged], &writes_merged);
+ StringToUint64(disk_fields[kDiskSectorsWritten], &sectors_written);
+ StringToUint64(disk_fields[kDiskWriteTime], &write_time);
+ StringToUint64(disk_fields[kDiskIO], &io);
+ StringToUint64(disk_fields[kDiskIOTime], &io_time);
+ StringToUint64(disk_fields[kDiskWeightedIOTime], &weighted_io_time);
+
+ diskinfo->reads += reads;
+ diskinfo->reads_merged += reads_merged;
+ diskinfo->sectors_read += sectors_read;
+ diskinfo->read_time += read_time;
+ diskinfo->writes += writes;
+ diskinfo->writes_merged += writes_merged;
+ diskinfo->sectors_written += sectors_written;
+ diskinfo->write_time += write_time;
+ diskinfo->io += io;
+ diskinfo->io_time += io_time;
+ diskinfo->weighted_io_time += weighted_io_time;
}
return true;
@@ -912,7 +804,7 @@ TimeDelta GetUserCpuTimeSinceBoot() {
#if defined(OS_CHROMEOS)
std::unique_ptr<Value> SwapInfo::ToValue() const {
- std::unique_ptr<DictionaryValue> res(new DictionaryValue());
+ auto res = std::make_unique<DictionaryValue>();
// Write out uint64_t variables as doubles.
// Note: this may discard some precision, but for JS there's no other option.
@@ -921,32 +813,91 @@ std::unique_ptr<Value> SwapInfo::ToValue() const {
res->SetDouble("orig_data_size", static_cast<double>(orig_data_size));
res->SetDouble("compr_data_size", static_cast<double>(compr_data_size));
res->SetDouble("mem_used_total", static_cast<double>(mem_used_total));
- if (compr_data_size > 0)
- res->SetDouble("compression_ratio", static_cast<double>(orig_data_size) /
- static_cast<double>(compr_data_size));
- else
- res->SetDouble("compression_ratio", 0);
+ double ratio = compr_data_size ? static_cast<double>(orig_data_size) /
+ static_cast<double>(compr_data_size)
+ : 0;
+ res->SetDouble("compression_ratio", ratio);
return std::move(res);
}
-void GetSwapInfo(SwapInfo* swap_info) {
- // Synchronously reading files in /sys/block/zram0 does not hit the disk.
- ThreadRestrictions::ScopedAllowIO allow_io;
+bool ParseZramMmStat(StringPiece mm_stat_data, SwapInfo* swap_info) {
+ // There are 7 columns in /sys/block/zram0/mm_stat,
+ // split by several spaces. The first three columns
+ // are orig_data_size, compr_data_size and mem_used_total.
+ // Example:
+ // 17715200 5008166 566062 0 1225715712 127 183842
+ //
+ // For more details:
+ // https://www.kernel.org/doc/Documentation/blockdev/zram.txt
+
+ std::vector<StringPiece> tokens = SplitStringPiece(
+ mm_stat_data, kWhitespaceASCII, TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+ if (tokens.size() < 7) {
+ DLOG(WARNING) << "zram mm_stat: tokens: " << tokens.size()
+ << " malformed line: " << mm_stat_data.as_string();
+ return false;
+ }
- FilePath zram_path("/sys/block/zram0");
- uint64_t orig_data_size =
- ReadFileToUint64(zram_path.Append("orig_data_size"));
+ if (!StringToUint64(tokens[0], &swap_info->orig_data_size))
+ return false;
+ if (!StringToUint64(tokens[1], &swap_info->compr_data_size))
+ return false;
+ if (!StringToUint64(tokens[2], &swap_info->mem_used_total))
+ return false;
+
+ return true;
+}
+
+bool ParseZramStat(StringPiece stat_data, SwapInfo* swap_info) {
+ // There are 11 columns in /sys/block/zram0/stat,
+ // split by several spaces. The first column is read I/Os
+ // and fifth column is write I/Os.
+ // Example:
+ // 299 0 2392 0 1 0 8 0 0 0 0
+ //
+ // For more details:
+ // https://www.kernel.org/doc/Documentation/blockdev/zram.txt
+
+ std::vector<StringPiece> tokens = SplitStringPiece(
+ stat_data, kWhitespaceASCII, TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+ if (tokens.size() < 11) {
+ DLOG(WARNING) << "zram stat: tokens: " << tokens.size()
+ << " malformed line: " << stat_data.as_string();
+ return false;
+ }
+
+ if (!StringToUint64(tokens[0], &swap_info->num_reads))
+ return false;
+ if (!StringToUint64(tokens[4], &swap_info->num_writes))
+ return false;
+
+ return true;
+}
+
+namespace {
+
+bool IgnoreZramFirstPage(uint64_t orig_data_size, SwapInfo* swap_info) {
if (orig_data_size <= 4096) {
// A single page is compressed at startup, and has a high compression
- // ratio. We ignore this as it doesn't indicate any real swapping.
+ // ratio. Ignore this as it doesn't indicate any real swapping.
swap_info->orig_data_size = 0;
swap_info->num_reads = 0;
swap_info->num_writes = 0;
swap_info->compr_data_size = 0;
swap_info->mem_used_total = 0;
- return;
+ return true;
}
+ return false;
+}
+
+void ParseZramPath(SwapInfo* swap_info) {
+ FilePath zram_path("/sys/block/zram0");
+ uint64_t orig_data_size =
+ ReadFileToUint64(zram_path.Append("orig_data_size"));
+ if (IgnoreZramFirstPage(orig_data_size, swap_info))
+ return;
+
swap_info->orig_data_size = orig_data_size;
swap_info->num_reads = ReadFileToUint64(zram_path.Append("num_reads"));
swap_info->num_writes = ReadFileToUint64(zram_path.Append("num_writes"));
@@ -955,15 +906,70 @@ void GetSwapInfo(SwapInfo* swap_info) {
swap_info->mem_used_total =
ReadFileToUint64(zram_path.Append("mem_used_total"));
}
+
+bool GetSwapInfoImpl(SwapInfo* swap_info) {
+ // Synchronously reading files in /sys/block/zram0 does not hit the disk.
+ ThreadRestrictions::ScopedAllowIO allow_io;
+
+ // Since ZRAM update, it shows the usage data in different places.
+ // If file "/sys/block/zram0/mm_stat" exists, use the new way, otherwise,
+ // use the old way.
+ static Optional<bool> use_new_zram_interface;
+ FilePath zram_mm_stat_file("/sys/block/zram0/mm_stat");
+ if (!use_new_zram_interface.has_value()) {
+ use_new_zram_interface = PathExists(zram_mm_stat_file);
+ }
+
+ if (!use_new_zram_interface.value()) {
+ ParseZramPath(swap_info);
+ return true;
+ }
+
+ std::string mm_stat_data;
+ if (!ReadFileToString(zram_mm_stat_file, &mm_stat_data)) {
+ DLOG(WARNING) << "Failed to open " << zram_mm_stat_file.value();
+ return false;
+ }
+ if (!ParseZramMmStat(mm_stat_data, swap_info)) {
+ DLOG(WARNING) << "Failed to parse " << zram_mm_stat_file.value();
+ return false;
+ }
+ if (IgnoreZramFirstPage(swap_info->orig_data_size, swap_info))
+ return true;
+
+ FilePath zram_stat_file("/sys/block/zram0/stat");
+ std::string stat_data;
+ if (!ReadFileToString(zram_stat_file, &stat_data)) {
+ DLOG(WARNING) << "Failed to open " << zram_stat_file.value();
+ return false;
+ }
+ if (!ParseZramStat(stat_data, swap_info)) {
+ DLOG(WARNING) << "Failed to parse " << zram_stat_file.value();
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+bool GetSwapInfo(SwapInfo* swap_info) {
+ if (!GetSwapInfoImpl(swap_info)) {
+ *swap_info = SwapInfo();
+ return false;
+ }
+ return true;
+}
#endif // defined(OS_CHROMEOS)
-#if defined(OS_LINUX)
+#if defined(OS_LINUX) || defined(OS_AIX)
int ProcessMetrics::GetIdleWakeupsPerSecond() {
- uint64_t wake_ups;
- const char kWakeupStat[] = "se.statistics.nr_wakeups";
- return ReadProcSchedAndGetFieldAsUint64(process_, kWakeupStat, &wake_ups) ?
- CalculateIdleWakeupsPerSecond(wake_ups) : 0;
+ uint64_t num_switches;
+ static const char kSwitchStat[] = "voluntary_ctxt_switches";
+ return ReadProcStatusAndGetFieldAsUint64(process_, kSwitchStat, &num_switches)
+ ? CalculateIdleWakeupsPerSecond(num_switches)
+ : 0;
}
-#endif // defined(OS_LINUX)
+#endif // defined(OS_LINUX) || defined(OS_AIX)
} // namespace base
diff --git a/base/process/process_metrics_posix.cc b/base/process/process_metrics_posix.cc
index 13acf2ea34..a09bbf2c56 100644
--- a/base/process/process_metrics_posix.cc
+++ b/base/process/process_metrics_posix.cc
@@ -7,13 +7,22 @@
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
-#include <sys/resource.h>
#include <sys/time.h>
#include <unistd.h>
#include "base/logging.h"
#include "build/build_config.h"
+#if !defined(OS_FUCHSIA)
+#include <sys/resource.h>
+#endif
+
+#if defined(OS_MACOSX)
+#include <malloc/malloc.h>
+#else
+#include <malloc.h>
+#endif
+
namespace base {
int64_t TimeValToMicroseconds(const struct timeval& tv) {
@@ -23,7 +32,9 @@ int64_t TimeValToMicroseconds(const struct timeval& tv) {
return ret;
}
-ProcessMetrics::~ProcessMetrics() { }
+ProcessMetrics::~ProcessMetrics() = default;
+
+#if !defined(OS_FUCHSIA)
#if defined(OS_LINUX)
static const rlim_t kSystemDefaultMaxFds = 8192;
@@ -39,6 +50,8 @@ static const rlim_t kSystemDefaultMaxFds = 1024;
static const rlim_t kSystemDefaultMaxFds = 256;
#elif defined(OS_ANDROID)
static const rlim_t kSystemDefaultMaxFds = 1024;
+#elif defined(OS_AIX)
+static const rlim_t kSystemDefaultMaxFds = 8192;
#endif
size_t GetMaxFds() {
@@ -58,11 +71,12 @@ size_t GetMaxFds() {
return static_cast<size_t>(max_fds);
}
-
-void SetFdLimit(unsigned int max_descriptors) {
+void IncreaseFdLimitTo(unsigned int max_descriptors) {
struct rlimit limits;
if (getrlimit(RLIMIT_NOFILE, &limits) == 0) {
unsigned int new_limit = max_descriptors;
+ if (max_descriptors <= limits.rlim_cur)
+ return;
if (limits.rlim_max > 0 && limits.rlim_max < max_descriptors) {
new_limit = limits.rlim_max;
}
@@ -75,8 +89,28 @@ void SetFdLimit(unsigned int max_descriptors) {
}
}
+#endif // !defined(OS_FUCHSIA)
+
size_t GetPageSize() {
return getpagesize();
}
+size_t ProcessMetrics::GetMallocUsage() {
+#if defined(OS_MACOSX) || defined(OS_IOS)
+ malloc_statistics_t stats = {0};
+ malloc_zone_statistics(nullptr, &stats);
+ return stats.size_in_use;
+#elif defined(OS_LINUX) || defined(OS_ANDROID)
+ struct mallinfo minfo = mallinfo();
+#if defined(USE_TCMALLOC)
+ return minfo.uordblks;
+#else
+ return minfo.hblkhd + minfo.arena;
+#endif
+#elif defined(OS_FUCHSIA)
+ // TODO(fuchsia): Not currently exposed. https://crbug.com/735087.
+ return 0;
+#endif
+}
+
} // namespace base
diff --git a/base/process/process_metrics_unittest.cc b/base/process/process_metrics_unittest.cc
index d9c8aaa2fe..ad741f8f13 100644
--- a/base/process/process_metrics_unittest.cc
+++ b/base/process/process_metrics_unittest.cc
@@ -9,6 +9,7 @@
#include <sstream>
#include <string>
+#include <vector>
#include "base/bind.h"
#include "base/command_line.h"
@@ -16,6 +17,7 @@
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
+#include "base/memory/shared_memory.h"
#include "base/strings/string_number_conversions.h"
#include "base/sys_info.h"
#include "base/test/multiprocess_test.h"
@@ -31,7 +33,7 @@
namespace base {
namespace debug {
-#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_WIN)
namespace {
void BusyWork(std::vector<std::string>* vec) {
@@ -49,60 +51,22 @@ void BusyWork(std::vector<std::string>* vec) {
// Exists as a class so it can be a friend of SystemMetrics.
class SystemMetricsTest : public testing::Test {
public:
- SystemMetricsTest() {}
+ SystemMetricsTest() = default;
private:
DISALLOW_COPY_AND_ASSIGN(SystemMetricsTest);
};
-/////////////////////////////////////////////////////////////////////////////
-
-#if defined(OS_MACOSX) && !defined(OS_IOS) && !defined(ADDRESS_SANITIZER)
-TEST_F(SystemMetricsTest, LockedBytes) {
- ProcessHandle handle = GetCurrentProcessHandle();
- std::unique_ptr<ProcessMetrics> metrics(
- ProcessMetrics::CreateProcessMetrics(handle, nullptr));
-
- size_t initial_locked_bytes;
- bool result =
- metrics->GetMemoryBytes(nullptr, nullptr, nullptr, &initial_locked_bytes);
- ASSERT_TRUE(result);
-
- size_t size = 8 * 1024 * 1024;
- std::unique_ptr<char[]> memory(new char[size]);
- int r = mlock(memory.get(), size);
- ASSERT_EQ(0, r);
-
- size_t new_locked_bytes;
- result =
- metrics->GetMemoryBytes(nullptr, nullptr, nullptr, &new_locked_bytes);
- ASSERT_TRUE(result);
-
- // There should be around |size| more locked bytes, but multi-threading might
- // cause noise.
- EXPECT_LT(initial_locked_bytes + size / 2, new_locked_bytes);
- EXPECT_GT(initial_locked_bytes + size * 1.5, new_locked_bytes);
-
- r = munlock(memory.get(), size);
- ASSERT_EQ(0, r);
-
- result =
- metrics->GetMemoryBytes(nullptr, nullptr, nullptr, &new_locked_bytes);
- ASSERT_TRUE(result);
- EXPECT_EQ(initial_locked_bytes, new_locked_bytes);
-}
-#endif // defined(OS_MACOSX) && !defined(OS_IOS) && !defined(ADDRESS_SANITIZER)
-
#if defined(OS_LINUX) || defined(OS_ANDROID)
TEST_F(SystemMetricsTest, IsValidDiskName) {
- std::string invalid_input1 = "";
- std::string invalid_input2 = "s";
- std::string invalid_input3 = "sdz+";
- std::string invalid_input4 = "hda0";
- std::string invalid_input5 = "mmcbl";
- std::string invalid_input6 = "mmcblka";
- std::string invalid_input7 = "mmcblkb";
- std::string invalid_input8 = "mmmblk0";
+ const char invalid_input1[] = "";
+ const char invalid_input2[] = "s";
+ const char invalid_input3[] = "sdz+";
+ const char invalid_input4[] = "hda0";
+ const char invalid_input5[] = "mmcbl";
+ const char invalid_input6[] = "mmcblka";
+ const char invalid_input7[] = "mmcblkb";
+ const char invalid_input8[] = "mmmblk0";
EXPECT_FALSE(IsValidDiskName(invalid_input1));
EXPECT_FALSE(IsValidDiskName(invalid_input2));
@@ -113,11 +77,11 @@ TEST_F(SystemMetricsTest, IsValidDiskName) {
EXPECT_FALSE(IsValidDiskName(invalid_input7));
EXPECT_FALSE(IsValidDiskName(invalid_input8));
- std::string valid_input1 = "sda";
- std::string valid_input2 = "sdaaaa";
- std::string valid_input3 = "hdz";
- std::string valid_input4 = "mmcblk0";
- std::string valid_input5 = "mmcblk999";
+ const char valid_input1[] = "sda";
+ const char valid_input2[] = "sdaaaa";
+ const char valid_input3[] = "hdz";
+ const char valid_input4[] = "mmcblk0";
+ const char valid_input5[] = "mmcblk999";
EXPECT_TRUE(IsValidDiskName(valid_input1));
EXPECT_TRUE(IsValidDiskName(valid_input2));
@@ -127,88 +91,88 @@ TEST_F(SystemMetricsTest, IsValidDiskName) {
}
TEST_F(SystemMetricsTest, ParseMeminfo) {
- struct SystemMemoryInfoKB meminfo;
- std::string invalid_input1 = "abc";
- std::string invalid_input2 = "MemTotal:";
+ SystemMemoryInfoKB meminfo;
+ const char invalid_input1[] = "abc";
+ const char invalid_input2[] = "MemTotal:";
// Partial file with no MemTotal
- std::string invalid_input3 =
- "MemFree: 3913968 kB\n"
- "Buffers: 2348340 kB\n"
- "Cached: 49071596 kB\n"
- "SwapCached: 12 kB\n"
- "Active: 36393900 kB\n"
- "Inactive: 21221496 kB\n"
- "Active(anon): 5674352 kB\n"
- "Inactive(anon): 633992 kB\n";
+ const char invalid_input3[] =
+ "MemFree: 3913968 kB\n"
+ "Buffers: 2348340 kB\n"
+ "Cached: 49071596 kB\n"
+ "SwapCached: 12 kB\n"
+ "Active: 36393900 kB\n"
+ "Inactive: 21221496 kB\n"
+ "Active(anon): 5674352 kB\n"
+ "Inactive(anon): 633992 kB\n";
EXPECT_FALSE(ParseProcMeminfo(invalid_input1, &meminfo));
EXPECT_FALSE(ParseProcMeminfo(invalid_input2, &meminfo));
EXPECT_FALSE(ParseProcMeminfo(invalid_input3, &meminfo));
- std::string valid_input1 =
- "MemTotal: 3981504 kB\n"
- "MemFree: 140764 kB\n"
- "MemAvailable: 535413 kB\n"
- "Buffers: 116480 kB\n"
- "Cached: 406160 kB\n"
- "SwapCached: 21304 kB\n"
- "Active: 3152040 kB\n"
- "Inactive: 472856 kB\n"
- "Active(anon): 2972352 kB\n"
- "Inactive(anon): 270108 kB\n"
- "Active(file): 179688 kB\n"
- "Inactive(file): 202748 kB\n"
- "Unevictable: 0 kB\n"
- "Mlocked: 0 kB\n"
- "SwapTotal: 5832280 kB\n"
- "SwapFree: 3672368 kB\n"
- "Dirty: 184 kB\n"
- "Writeback: 0 kB\n"
- "AnonPages: 3101224 kB\n"
- "Mapped: 142296 kB\n"
- "Shmem: 140204 kB\n"
- "Slab: 54212 kB\n"
- "SReclaimable: 30936 kB\n"
- "SUnreclaim: 23276 kB\n"
- "KernelStack: 2464 kB\n"
- "PageTables: 24812 kB\n"
- "NFS_Unstable: 0 kB\n"
- "Bounce: 0 kB\n"
- "WritebackTmp: 0 kB\n"
- "CommitLimit: 7823032 kB\n"
- "Committed_AS: 7973536 kB\n"
- "VmallocTotal: 34359738367 kB\n"
- "VmallocUsed: 375940 kB\n"
- "VmallocChunk: 34359361127 kB\n"
- "DirectMap4k: 72448 kB\n"
- "DirectMap2M: 4061184 kB\n";
+ const char valid_input1[] =
+ "MemTotal: 3981504 kB\n"
+ "MemFree: 140764 kB\n"
+ "MemAvailable: 535413 kB\n"
+ "Buffers: 116480 kB\n"
+ "Cached: 406160 kB\n"
+ "SwapCached: 21304 kB\n"
+ "Active: 3152040 kB\n"
+ "Inactive: 472856 kB\n"
+ "Active(anon): 2972352 kB\n"
+ "Inactive(anon): 270108 kB\n"
+ "Active(file): 179688 kB\n"
+ "Inactive(file): 202748 kB\n"
+ "Unevictable: 0 kB\n"
+ "Mlocked: 0 kB\n"
+ "SwapTotal: 5832280 kB\n"
+ "SwapFree: 3672368 kB\n"
+ "Dirty: 184 kB\n"
+ "Writeback: 0 kB\n"
+ "AnonPages: 3101224 kB\n"
+ "Mapped: 142296 kB\n"
+ "Shmem: 140204 kB\n"
+ "Slab: 54212 kB\n"
+ "SReclaimable: 30936 kB\n"
+ "SUnreclaim: 23276 kB\n"
+ "KernelStack: 2464 kB\n"
+ "PageTables: 24812 kB\n"
+ "NFS_Unstable: 0 kB\n"
+ "Bounce: 0 kB\n"
+ "WritebackTmp: 0 kB\n"
+ "CommitLimit: 7823032 kB\n"
+ "Committed_AS: 7973536 kB\n"
+ "VmallocTotal: 34359738367 kB\n"
+ "VmallocUsed: 375940 kB\n"
+ "VmallocChunk: 34359361127 kB\n"
+ "DirectMap4k: 72448 kB\n"
+ "DirectMap2M: 4061184 kB\n";
// output from a much older kernel where the Active and Inactive aren't
// broken down into anon and file and Huge Pages are enabled
- std::string valid_input2 =
- "MemTotal: 255908 kB\n"
- "MemFree: 69936 kB\n"
- "Buffers: 15812 kB\n"
- "Cached: 115124 kB\n"
- "SwapCached: 0 kB\n"
- "Active: 92700 kB\n"
- "Inactive: 63792 kB\n"
- "HighTotal: 0 kB\n"
- "HighFree: 0 kB\n"
- "LowTotal: 255908 kB\n"
- "LowFree: 69936 kB\n"
- "SwapTotal: 524280 kB\n"
- "SwapFree: 524200 kB\n"
- "Dirty: 4 kB\n"
- "Writeback: 0 kB\n"
- "Mapped: 42236 kB\n"
- "Slab: 25912 kB\n"
- "Committed_AS: 118680 kB\n"
- "PageTables: 1236 kB\n"
- "VmallocTotal: 3874808 kB\n"
- "VmallocUsed: 1416 kB\n"
- "VmallocChunk: 3872908 kB\n"
- "HugePages_Total: 0\n"
- "HugePages_Free: 0\n"
- "Hugepagesize: 4096 kB\n";
+ const char valid_input2[] =
+ "MemTotal: 255908 kB\n"
+ "MemFree: 69936 kB\n"
+ "Buffers: 15812 kB\n"
+ "Cached: 115124 kB\n"
+ "SwapCached: 0 kB\n"
+ "Active: 92700 kB\n"
+ "Inactive: 63792 kB\n"
+ "HighTotal: 0 kB\n"
+ "HighFree: 0 kB\n"
+ "LowTotal: 255908 kB\n"
+ "LowFree: 69936 kB\n"
+ "SwapTotal: 524280 kB\n"
+ "SwapFree: 524200 kB\n"
+ "Dirty: 4 kB\n"
+ "Writeback: 0 kB\n"
+ "Mapped: 42236 kB\n"
+ "Slab: 25912 kB\n"
+ "Committed_AS: 118680 kB\n"
+ "PageTables: 1236 kB\n"
+ "VmallocTotal: 3874808 kB\n"
+ "VmallocUsed: 1416 kB\n"
+ "VmallocChunk: 3872908 kB\n"
+ "HugePages_Total: 0\n"
+ "HugePages_Free: 0\n"
+ "Hugepagesize: 4096 kB\n";
EXPECT_TRUE(ParseProcMeminfo(valid_input1, &meminfo));
EXPECT_EQ(meminfo.total, 3981504);
@@ -249,118 +213,125 @@ TEST_F(SystemMetricsTest, ParseMeminfo) {
}
TEST_F(SystemMetricsTest, ParseVmstat) {
- struct SystemMemoryInfoKB meminfo;
+ VmStatInfo vmstat;
// part of vmstat from a 3.2 kernel with numa enabled
- std::string valid_input1 =
- "nr_free_pages 905104\n"
- "nr_inactive_anon 142478"
- "nr_active_anon 1520046\n"
- "nr_inactive_file 4481001\n"
- "nr_active_file 8313439\n"
- "nr_unevictable 5044\n"
- "nr_mlock 5044\n"
- "nr_anon_pages 1633780\n"
- "nr_mapped 104742\n"
- "nr_file_pages 12828218\n"
- "nr_dirty 245\n"
- "nr_writeback 0\n"
- "nr_slab_reclaimable 831609\n"
- "nr_slab_unreclaimable 41164\n"
- "nr_page_table_pages 31470\n"
- "nr_kernel_stack 1735\n"
- "nr_unstable 0\n"
- "nr_bounce 0\n"
- "nr_vmscan_write 406\n"
- "nr_vmscan_immediate_reclaim 281\n"
- "nr_writeback_temp 0\n"
- "nr_isolated_anon 0\n"
- "nr_isolated_file 0\n"
- "nr_shmem 28820\n"
- "nr_dirtied 84674644\n"
- "nr_written 75307109\n"
- "nr_anon_transparent_hugepages 0\n"
- "nr_dirty_threshold 1536206\n"
- "nr_dirty_background_threshold 768103\n"
- "pgpgin 30777108\n"
- "pgpgout 319023278\n"
- "pswpin 179\n"
- "pswpout 406\n"
- "pgalloc_dma 0\n"
- "pgalloc_dma32 20833399\n"
- "pgalloc_normal 1622609290\n"
- "pgalloc_movable 0\n"
- "pgfree 1644355583\n"
- "pgactivate 75391882\n"
- "pgdeactivate 4121019\n"
- "pgfault 2542879679\n"
- "pgmajfault 487192\n";
- std::string valid_input2 =
- "nr_free_pages 180125\n"
- "nr_inactive_anon 51\n"
- "nr_active_anon 38832\n"
- "nr_inactive_file 50171\n"
- "nr_active_file 47510\n"
- "nr_unevictable 0\n"
- "nr_mlock 0\n"
- "nr_anon_pages 38825\n"
- "nr_mapped 24043\n"
- "nr_file_pages 97733\n"
- "nr_dirty 0\n"
- "nr_writeback 0\n"
- "nr_slab_reclaimable 4032\n"
- "nr_slab_unreclaimable 2848\n"
- "nr_page_table_pages 1505\n"
- "nr_kernel_stack 626\n"
- "nr_unstable 0\n"
- "nr_bounce 0\n"
- "nr_vmscan_write 0\n"
- "nr_vmscan_immediate_reclaim 0\n"
- "nr_writeback_temp 0\n"
- "nr_isolated_anon 0\n"
- "nr_isolated_file 0\n"
- "nr_shmem 58\n"
- "nr_dirtied 435358\n"
- "nr_written 401258\n"
- "nr_anon_transparent_hugepages 0\n"
- "nr_dirty_threshold 18566\n"
- "nr_dirty_background_threshold 4641\n"
- "pgpgin 299464\n"
- "pgpgout 2437788\n"
- "pswpin 12\n"
- "pswpout 901\n"
- "pgalloc_normal 144213030\n"
- "pgalloc_high 164501274\n"
- "pgalloc_movable 0\n"
- "pgfree 308894908\n"
- "pgactivate 239320\n"
- "pgdeactivate 1\n"
- "pgfault 716044601\n"
- "pgmajfault 2023\n"
- "pgrefill_normal 0\n"
- "pgrefill_high 0\n"
- "pgrefill_movable 0\n";
- EXPECT_TRUE(ParseProcVmstat(valid_input1, &meminfo));
- EXPECT_EQ(179LU, meminfo.pswpin);
- EXPECT_EQ(406LU, meminfo.pswpout);
- EXPECT_EQ(487192LU, meminfo.pgmajfault);
- EXPECT_TRUE(ParseProcVmstat(valid_input2, &meminfo));
- EXPECT_EQ(12LU, meminfo.pswpin);
- EXPECT_EQ(901LU, meminfo.pswpout);
- EXPECT_EQ(2023LU, meminfo.pgmajfault);
+ const char valid_input1[] =
+ "nr_free_pages 905104\n"
+ "nr_inactive_anon 142478"
+ "nr_active_anon 1520046\n"
+ "nr_inactive_file 4481001\n"
+ "nr_active_file 8313439\n"
+ "nr_unevictable 5044\n"
+ "nr_mlock 5044\n"
+ "nr_anon_pages 1633780\n"
+ "nr_mapped 104742\n"
+ "nr_file_pages 12828218\n"
+ "nr_dirty 245\n"
+ "nr_writeback 0\n"
+ "nr_slab_reclaimable 831609\n"
+ "nr_slab_unreclaimable 41164\n"
+ "nr_page_table_pages 31470\n"
+ "nr_kernel_stack 1735\n"
+ "nr_unstable 0\n"
+ "nr_bounce 0\n"
+ "nr_vmscan_write 406\n"
+ "nr_vmscan_immediate_reclaim 281\n"
+ "nr_writeback_temp 0\n"
+ "nr_isolated_anon 0\n"
+ "nr_isolated_file 0\n"
+ "nr_shmem 28820\n"
+ "nr_dirtied 84674644\n"
+ "nr_written 75307109\n"
+ "nr_anon_transparent_hugepages 0\n"
+ "nr_dirty_threshold 1536206\n"
+ "nr_dirty_background_threshold 768103\n"
+ "pgpgin 30777108\n"
+ "pgpgout 319023278\n"
+ "pswpin 179\n"
+ "pswpout 406\n"
+ "pgalloc_dma 0\n"
+ "pgalloc_dma32 20833399\n"
+ "pgalloc_normal 1622609290\n"
+ "pgalloc_movable 0\n"
+ "pgfree 1644355583\n"
+ "pgactivate 75391882\n"
+ "pgdeactivate 4121019\n"
+ "pgfault 2542879679\n"
+ "pgmajfault 487192\n";
+ const char valid_input2[] =
+ "nr_free_pages 180125\n"
+ "nr_inactive_anon 51\n"
+ "nr_active_anon 38832\n"
+ "nr_inactive_file 50171\n"
+ "nr_active_file 47510\n"
+ "nr_unevictable 0\n"
+ "nr_mlock 0\n"
+ "nr_anon_pages 38825\n"
+ "nr_mapped 24043\n"
+ "nr_file_pages 97733\n"
+ "nr_dirty 0\n"
+ "nr_writeback 0\n"
+ "nr_slab_reclaimable 4032\n"
+ "nr_slab_unreclaimable 2848\n"
+ "nr_page_table_pages 1505\n"
+ "nr_kernel_stack 626\n"
+ "nr_unstable 0\n"
+ "nr_bounce 0\n"
+ "nr_vmscan_write 0\n"
+ "nr_vmscan_immediate_reclaim 0\n"
+ "nr_writeback_temp 0\n"
+ "nr_isolated_anon 0\n"
+ "nr_isolated_file 0\n"
+ "nr_shmem 58\n"
+ "nr_dirtied 435358\n"
+ "nr_written 401258\n"
+ "nr_anon_transparent_hugepages 0\n"
+ "nr_dirty_threshold 18566\n"
+ "nr_dirty_background_threshold 4641\n"
+ "pgpgin 299464\n"
+ "pgpgout 2437788\n"
+ "pswpin 12\n"
+ "pswpout 901\n"
+ "pgalloc_normal 144213030\n"
+ "pgalloc_high 164501274\n"
+ "pgalloc_movable 0\n"
+ "pgfree 308894908\n"
+ "pgactivate 239320\n"
+ "pgdeactivate 1\n"
+ "pgfault 716044601\n"
+ "pgmajfault 2023\n"
+ "pgrefill_normal 0\n"
+ "pgrefill_high 0\n"
+ "pgrefill_movable 0\n";
+ EXPECT_TRUE(ParseProcVmstat(valid_input1, &vmstat));
+ EXPECT_EQ(179LU, vmstat.pswpin);
+ EXPECT_EQ(406LU, vmstat.pswpout);
+ EXPECT_EQ(487192LU, vmstat.pgmajfault);
+ EXPECT_TRUE(ParseProcVmstat(valid_input2, &vmstat));
+ EXPECT_EQ(12LU, vmstat.pswpin);
+ EXPECT_EQ(901LU, vmstat.pswpout);
+ EXPECT_EQ(2023LU, vmstat.pgmajfault);
+
+ const char missing_pgmajfault_input[] =
+ "pswpin 12\n"
+ "pswpout 901\n";
+ EXPECT_FALSE(ParseProcVmstat(missing_pgmajfault_input, &vmstat));
+ const char empty_input[] = "";
+ EXPECT_FALSE(ParseProcVmstat(empty_input, &vmstat));
}
#endif // defined(OS_LINUX) || defined(OS_ANDROID)
-#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_WIN)
-// Test that ProcessMetrics::GetCPUUsage() doesn't return negative values when
-// the number of threads running on the process decreases between two successive
-// calls to it.
+// Test that ProcessMetrics::GetPlatformIndependentCPUUsage() doesn't return
+// negative values when the number of threads running on the process decreases
+// between two successive calls to it.
TEST_F(SystemMetricsTest, TestNoNegativeCpuUsage) {
ProcessHandle handle = GetCurrentProcessHandle();
std::unique_ptr<ProcessMetrics> metrics(
ProcessMetrics::CreateProcessMetrics(handle));
- EXPECT_GE(metrics->GetCPUUsage(), 0.0);
+ EXPECT_GE(metrics->GetPlatformIndependentCPUUsage(), 0.0);
Thread thread1("thread1");
Thread thread2("thread2");
Thread thread3("thread3");
@@ -377,24 +348,71 @@ TEST_F(SystemMetricsTest, TestNoNegativeCpuUsage) {
std::vector<std::string> vec2;
std::vector<std::string> vec3;
- thread1.task_runner()->PostTask(FROM_HERE, Bind(&BusyWork, &vec1));
- thread2.task_runner()->PostTask(FROM_HERE, Bind(&BusyWork, &vec2));
- thread3.task_runner()->PostTask(FROM_HERE, Bind(&BusyWork, &vec3));
+ thread1.task_runner()->PostTask(FROM_HERE, BindOnce(&BusyWork, &vec1));
+ thread2.task_runner()->PostTask(FROM_HERE, BindOnce(&BusyWork, &vec2));
+ thread3.task_runner()->PostTask(FROM_HERE, BindOnce(&BusyWork, &vec3));
- EXPECT_GE(metrics->GetCPUUsage(), 0.0);
+ TimeDelta prev_cpu_usage = metrics->GetCumulativeCPUUsage();
+ EXPECT_GE(prev_cpu_usage, TimeDelta());
+ EXPECT_GE(metrics->GetPlatformIndependentCPUUsage(), 0.0);
thread1.Stop();
- EXPECT_GE(metrics->GetCPUUsage(), 0.0);
+ TimeDelta current_cpu_usage = metrics->GetCumulativeCPUUsage();
+ EXPECT_GE(current_cpu_usage, prev_cpu_usage);
+ prev_cpu_usage = current_cpu_usage;
+ EXPECT_GE(metrics->GetPlatformIndependentCPUUsage(), 0.0);
thread2.Stop();
- EXPECT_GE(metrics->GetCPUUsage(), 0.0);
+ current_cpu_usage = metrics->GetCumulativeCPUUsage();
+ EXPECT_GE(current_cpu_usage, prev_cpu_usage);
+ prev_cpu_usage = current_cpu_usage;
+ EXPECT_GE(metrics->GetPlatformIndependentCPUUsage(), 0.0);
thread3.Stop();
- EXPECT_GE(metrics->GetCPUUsage(), 0.0);
+ current_cpu_usage = metrics->GetCumulativeCPUUsage();
+ EXPECT_GE(current_cpu_usage, prev_cpu_usage);
+ EXPECT_GE(metrics->GetPlatformIndependentCPUUsage(), 0.0);
}
#endif // defined(OS_LINUX) || defined(OS_CHROMEOS)
+#if defined(OS_CHROMEOS)
+TEST_F(SystemMetricsTest, ParseZramMmStat) {
+ SwapInfo swapinfo;
+
+ const char invalid_input1[] = "aaa";
+ const char invalid_input2[] = "1 2 3 4 5 6";
+ const char invalid_input3[] = "a 2 3 4 5 6 7";
+ EXPECT_FALSE(ParseZramMmStat(invalid_input1, &swapinfo));
+ EXPECT_FALSE(ParseZramMmStat(invalid_input2, &swapinfo));
+ EXPECT_FALSE(ParseZramMmStat(invalid_input3, &swapinfo));
+
+ const char valid_input1[] =
+ "17715200 5008166 566062 0 1225715712 127 183842";
+ EXPECT_TRUE(ParseZramMmStat(valid_input1, &swapinfo));
+ EXPECT_EQ(17715200ULL, swapinfo.orig_data_size);
+ EXPECT_EQ(5008166ULL, swapinfo.compr_data_size);
+ EXPECT_EQ(566062ULL, swapinfo.mem_used_total);
+}
+
+TEST_F(SystemMetricsTest, ParseZramStat) {
+ SwapInfo swapinfo;
+
+ const char invalid_input1[] = "aaa";
+ const char invalid_input2[] = "1 2 3 4 5 6 7 8 9 10";
+ const char invalid_input3[] = "a 2 3 4 5 6 7 8 9 10 11";
+ EXPECT_FALSE(ParseZramStat(invalid_input1, &swapinfo));
+ EXPECT_FALSE(ParseZramStat(invalid_input2, &swapinfo));
+ EXPECT_FALSE(ParseZramStat(invalid_input3, &swapinfo));
+
+ const char valid_input1[] =
+ "299 0 2392 0 1 0 8 0 0 0 0";
+ EXPECT_TRUE(ParseZramStat(valid_input1, &swapinfo));
+ EXPECT_EQ(299ULL, swapinfo.num_reads);
+ EXPECT_EQ(1ULL, swapinfo.num_writes);
+}
+#endif // defined(OS_CHROMEOS)
+
#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) || \
defined(OS_ANDROID)
TEST(SystemMetrics2Test, GetSystemMemoryInfo) {
@@ -418,7 +436,8 @@ TEST(SystemMetrics2Test, GetSystemMemoryInfo) {
#endif // defined(OS_LINUX) || defined(OS_ANDROID)
// All the values should be less than the total amount of memory.
-#if !defined(OS_WIN)
+#if !defined(OS_WIN) && !defined(OS_IOS)
+ // TODO(crbug.com/711450): re-enable the following assertion on iOS.
EXPECT_LT(info.free, info.total);
#endif
#if defined(OS_LINUX) || defined(OS_ANDROID)
@@ -503,7 +522,8 @@ TEST(ProcessMetricsTest, DISABLED_GetNumberOfThreads) {
#if defined(OS_LINUX)
namespace {
-// Keep these in sync so the GetOpenFdCount test can refer to correct test main.
+// Keep these in sync so the GetChildOpenFdCount test can refer to correct test
+// main.
#define ChildMain ChildFdCount
#define ChildMainString "ChildFdCount"
@@ -552,19 +572,19 @@ MULTIPROCESS_TEST_MAIN(ChildMain) {
// 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) {
+TEST(ProcessMetricsTest, GetChildOpenFdCount) {
ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
const FilePath temp_path = temp_dir.GetPath();
CommandLine child_command_line(GetMultiProcessTestChildBaseCommandLine());
child_command_line.AppendSwitchPath(kTempDirFlag, temp_path);
- SpawnChildResult spawn_child = SpawnMultiProcessTestChild(
+ Process child = SpawnMultiProcessTestChild(
ChildMainString, child_command_line, LaunchOptions());
- ASSERT_TRUE(spawn_child.process.IsValid());
+ ASSERT_TRUE(child.IsValid());
WaitForEvent(temp_path, kSignalClosed);
std::unique_ptr<ProcessMetrics> metrics(
- ProcessMetrics::CreateProcessMetrics(spawn_child.process.Handle()));
+ ProcessMetrics::CreateProcessMetrics(child.Handle()));
// 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
@@ -578,11 +598,53 @@ TEST(ProcessMetricsTest, GetOpenFdCount) {
PlatformThread::Sleep(TimeDelta::FromMilliseconds(1));
}
EXPECT_EQ(0, open_fds);
- ASSERT_TRUE(spawn_child.process.Terminate(0, true));
+ ASSERT_TRUE(child.Terminate(0, true));
}
#endif // !defined(__ANDROID__)
#endif // defined(OS_LINUX)
+#if defined(OS_ANDROID) || defined(OS_LINUX)
+
+TEST(ProcessMetricsTest, GetOpenFdCount) {
+ std::unique_ptr<base::ProcessMetrics> metrics(
+ base::ProcessMetrics::CreateProcessMetrics(
+ base::GetCurrentProcessHandle()));
+ int fd_count = metrics->GetOpenFdCount();
+ EXPECT_GT(fd_count, 0);
+ ScopedFILE file(fopen("/proc/self/statm", "r"));
+ EXPECT_TRUE(file);
+ int new_fd_count = metrics->GetOpenFdCount();
+ EXPECT_GT(new_fd_count, 0);
+ EXPECT_EQ(new_fd_count, fd_count + 1);
+}
+
+TEST(ProcessMetricsTestLinux, GetPageFaultCounts) {
+ std::unique_ptr<base::ProcessMetrics> process_metrics(
+ base::ProcessMetrics::CreateProcessMetrics(
+ base::GetCurrentProcessHandle()));
+
+ PageFaultCounts counts;
+ ASSERT_TRUE(process_metrics->GetPageFaultCounts(&counts));
+ ASSERT_GT(counts.minor, 0);
+ ASSERT_GE(counts.major, 0);
+
+ {
+ // Allocate and touch memory. Touching it is required to make sure that the
+ // page fault count goes up, as memory is typically mapped lazily.
+ const size_t kMappedSize = 4 * (1 << 20);
+ SharedMemory memory;
+ ASSERT_TRUE(memory.CreateAndMapAnonymous(kMappedSize));
+ memset(memory.memory(), 42, kMappedSize);
+ memory.Unmap();
+ }
+
+ PageFaultCounts counts_after;
+ ASSERT_TRUE(process_metrics->GetPageFaultCounts(&counts_after));
+ ASSERT_GT(counts_after.minor, counts.minor);
+ ASSERT_GE(counts_after.major, counts.major);
+}
+#endif // defined(OS_ANDROID) || defined(OS_LINUX)
+
} // namespace debug
} // namespace base
diff --git a/base/process/process_posix.cc b/base/process/process_posix.cc
index 9b94891dd9..08a918c1a8 100644
--- a/base/process/process_posix.cc
+++ b/base/process/process_posix.cc
@@ -5,6 +5,7 @@
#include "base/process/process.h"
#include <errno.h>
+#include <signal.h>
#include <stdint.h>
#include <sys/resource.h>
#include <sys/wait.h>
@@ -14,7 +15,7 @@
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/kill.h"
-#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
+#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#if defined(OS_MACOSX)
@@ -91,10 +92,9 @@ bool WaitpidWithTimeout(base::ProcessHandle handle,
// Using kqueue on Mac so that we can wait on non-child processes.
// We can't use kqueues on child processes because we need to reap
// our own children using wait.
-static bool WaitForSingleNonChildProcess(base::ProcessHandle handle,
- base::TimeDelta wait) {
+bool WaitForSingleNonChildProcess(base::ProcessHandle handle,
+ base::TimeDelta wait) {
DCHECK_GT(handle, 0);
- DCHECK_GT(wait, base::TimeDelta());
base::ScopedFD kq(kqueue());
if (!kq.is_valid()) {
@@ -128,7 +128,7 @@ static bool WaitForSingleNonChildProcess(base::ProcessHandle handle,
result = -1;
struct kevent event = {0};
- while (wait_forever || remaining_delta > base::TimeDelta()) {
+ do {
struct timespec remaining_timespec;
struct timespec* remaining_timespec_ptr;
if (wait_forever) {
@@ -148,7 +148,7 @@ static bool WaitForSingleNonChildProcess(base::ProcessHandle handle,
} else {
break;
}
- }
+ } while (wait_forever || remaining_delta > base::TimeDelta());
if (result < 0) {
DPLOG(ERROR) << "kevent (wait " << handle << ")";
@@ -181,9 +181,16 @@ static bool WaitForSingleNonChildProcess(base::ProcessHandle handle,
bool WaitForExitWithTimeoutImpl(base::ProcessHandle handle,
int* exit_code,
base::TimeDelta timeout) {
- base::ProcessHandle parent_pid = base::GetParentProcessId(handle);
- base::ProcessHandle our_pid = base::GetCurrentProcessHandle();
- if (parent_pid != our_pid) {
+ const base::ProcessHandle our_pid = base::GetCurrentProcessHandle();
+ if (handle == our_pid) {
+ // We won't be able to wait for ourselves to exit.
+ return false;
+ }
+
+ const base::ProcessHandle parent_pid = base::GetParentProcessId(handle);
+ const bool exited = (parent_pid < 0);
+
+ if (!exited && parent_pid != our_pid) {
#if defined(OS_MACOSX)
// On Mac we can wait on non child processes.
return WaitForSingleNonChildProcess(handle, timeout);
@@ -195,7 +202,7 @@ bool WaitForExitWithTimeoutImpl(base::ProcessHandle handle,
int status;
if (!WaitpidWithTimeout(handle, &status, timeout))
- return false;
+ return exited;
if (WIFSIGNALED(status)) {
if (exit_code)
*exit_code = -1;
@@ -206,7 +213,7 @@ bool WaitForExitWithTimeoutImpl(base::ProcessHandle handle,
*exit_code = WEXITSTATUS(status);
return true;
}
- return false;
+ return exited;
}
#endif // !defined(OS_NACL_NONSFI)
@@ -217,15 +224,13 @@ namespace base {
Process::Process(ProcessHandle handle) : process_(handle) {
}
-Process::~Process() {
-}
+Process::~Process() = default;
Process::Process(Process&& other) : process_(other.process_) {
other.Close();
}
Process& Process::operator=(Process&& other) {
- DCHECK_NE(this, &other);
process_ = other.process_;
other.Close();
return *this;
@@ -257,12 +262,12 @@ Process Process::DeprecatedGetProcessFromHandle(ProcessHandle handle) {
return Process(handle);
}
-#if !defined(OS_LINUX) && !defined(OS_MACOSX)
+#if !defined(OS_LINUX) && !defined(OS_MACOSX) && !defined(OS_AIX)
// static
bool Process::CanBackgroundProcesses() {
return false;
}
-#endif // !defined(OS_LINUX) && !defined(OS_MACOSX)
+#endif // !defined(OS_LINUX) && !defined(OS_MACOSX) && !defined(OS_AIX)
// static
void Process::TerminateCurrentProcessImmediately(int exit_code) {
@@ -306,52 +311,20 @@ bool Process::Terminate(int exit_code, bool wait) const {
DCHECK(IsValid());
CHECK_GT(process_, 0);
- bool result = kill(process_, SIGTERM) == 0;
- if (result && wait) {
- int tries = 60;
-
- if (RunningOnValgrind()) {
- // Wait for some extra time when running under Valgrind since the child
- // processes may take some time doing leak checking.
- tries *= 2;
- }
-
- unsigned sleep_ms = 4;
+ bool did_terminate = kill(process_, SIGTERM) == 0;
- // The process may not end immediately due to pending I/O
- bool exited = false;
- while (tries-- > 0) {
- pid_t pid = HANDLE_EINTR(waitpid(process_, NULL, WNOHANG));
- if (pid == process_) {
- exited = true;
- break;
- }
- if (pid == -1) {
- if (errno == ECHILD) {
- // The wait may fail with ECHILD if another process also waited for
- // the same pid, causing the process state to get cleaned up.
- exited = true;
- break;
- }
- DPLOG(ERROR) << "Error waiting for process " << process_;
- }
-
- usleep(sleep_ms * 1000);
- const unsigned kMaxSleepMs = 1000;
- if (sleep_ms < kMaxSleepMs)
- sleep_ms *= 2;
- }
-
- // If we're waiting and the child hasn't died by now, force it
- // with a SIGKILL.
- if (!exited)
- result = kill(process_, SIGKILL) == 0;
+ if (wait && did_terminate) {
+ if (WaitForExitWithTimeout(TimeDelta::FromSeconds(60), nullptr))
+ return true;
+ did_terminate = kill(process_, SIGKILL) == 0;
+ if (did_terminate)
+ return WaitForExit(nullptr);
}
- if (!result)
+ if (!did_terminate)
DPLOG(ERROR) << "Unable to terminate process " << process_;
- return result;
+ return did_terminate;
}
#endif // !defined(OS_NACL_NONSFI)
@@ -360,13 +333,25 @@ bool Process::WaitForExit(int* exit_code) const {
}
bool Process::WaitForExitWithTimeout(TimeDelta timeout, int* exit_code) const {
+ if (!timeout.is_zero())
+ internal::AssertBaseSyncPrimitivesAllowed();
+
// Record the event that this thread is blocking upon (for hang diagnosis).
base::debug::ScopedProcessWaitActivity process_activity(this);
- return WaitForExitWithTimeoutImpl(Handle(), exit_code, timeout);
+ int local_exit_code = 0;
+ bool exited = WaitForExitWithTimeoutImpl(Handle(), &local_exit_code, timeout);
+ if (exited) {
+ Exited(local_exit_code);
+ if (exit_code)
+ *exit_code = local_exit_code;
+ }
+ return exited;
}
-#if !defined(OS_LINUX) && !defined(OS_MACOSX)
+void Process::Exited(int exit_code) const {}
+
+#if !defined(OS_LINUX) && !defined(OS_MACOSX) && !defined(OS_AIX)
bool Process::IsProcessBackgrounded() const {
// See SetProcessBackgrounded().
DCHECK(IsValid());
@@ -380,7 +365,7 @@ bool Process::SetProcessBackgrounded(bool value) {
NOTIMPLEMENTED();
return false;
}
-#endif // !defined(OS_LINUX) && !defined(OS_MACOSX)
+#endif // !defined(OS_LINUX) && !defined(OS_MACOSX) && !defined(OS_AIX)
int Process::GetPriority() const {
DCHECK(IsValid());
diff --git a/base/profiler/tracked_time.cc b/base/profiler/tracked_time.cc
deleted file mode 100644
index 7e0040c03c..0000000000
--- a/base/profiler/tracked_time.cc
+++ /dev/null
@@ -1,68 +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/profiler/tracked_time.h"
-
-#include "build/build_config.h"
-
-#if defined(OS_WIN)
-#include <mmsystem.h> // Declare timeGetTime()... after including build_config.
-#endif
-
-namespace tracked_objects {
-
-Duration::Duration() : ms_(0) {}
-Duration::Duration(int32_t duration) : ms_(duration) {}
-
-Duration& Duration::operator+=(const Duration& other) {
- ms_ += other.ms_;
- return *this;
-}
-
-Duration Duration::operator+(const Duration& other) const {
- return Duration(ms_ + other.ms_);
-}
-
-bool Duration::operator==(const Duration& other) const {
- return ms_ == other.ms_;
-}
-
-bool Duration::operator!=(const Duration& other) const {
- return ms_ != other.ms_;
-}
-
-bool Duration::operator>(const Duration& other) const {
- return ms_ > other.ms_;
-}
-
-// static
-Duration Duration::FromMilliseconds(int ms) { return Duration(ms); }
-
-int32_t Duration::InMilliseconds() const {
- return ms_;
-}
-
-//------------------------------------------------------------------------------
-
-TrackedTime::TrackedTime() : ms_(0) {}
-TrackedTime::TrackedTime(int32_t ms) : ms_(ms) {}
-TrackedTime::TrackedTime(const base::TimeTicks& time)
- : ms_(static_cast<int32_t>((time - base::TimeTicks()).InMilliseconds())) {}
-
-// static
-TrackedTime TrackedTime::Now() {
- return TrackedTime(base::TimeTicks::Now());
-}
-
-Duration TrackedTime::operator-(const TrackedTime& other) const {
- return Duration(ms_ - other.ms_);
-}
-
-TrackedTime TrackedTime::operator+(const Duration& other) const {
- return TrackedTime(ms_ + other.ms_);
-}
-
-bool TrackedTime::is_null() const { return ms_ == 0; }
-
-} // namespace tracked_objects
diff --git a/base/profiler/tracked_time.h b/base/profiler/tracked_time.h
deleted file mode 100644
index b32f41b39c..0000000000
--- a/base/profiler/tracked_time.h
+++ /dev/null
@@ -1,71 +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_PROFILER_TRACKED_TIME_H_
-#define BASE_PROFILER_TRACKED_TIME_H_
-
-#include <stdint.h>
-
-#include "base/base_export.h"
-#include "base/time/time.h"
-
-namespace tracked_objects {
-
-//------------------------------------------------------------------------------
-
-// TimeTicks maintains a wasteful 64 bits of data (we need less than 32), and on
-// windows, a 64 bit timer is expensive to even obtain. We use a simple
-// millisecond counter for most of our time values, as well as millisecond units
-// of duration between those values. This means we can only handle durations
-// up to 49 days (range), or 24 days (non-negative time durations).
-// We only define enough methods to service the needs of the tracking classes,
-// and our interfaces are modeled after what TimeTicks and TimeDelta use (so we
-// can swap them into place if we want to use the "real" classes).
-
-class BASE_EXPORT Duration { // Similar to base::TimeDelta.
- public:
- Duration();
-
- Duration& operator+=(const Duration& other);
- Duration operator+(const Duration& other) const;
-
- bool operator==(const Duration& other) const;
- bool operator!=(const Duration& other) const;
- bool operator>(const Duration& other) const;
-
- static Duration FromMilliseconds(int ms);
-
- int32_t InMilliseconds() const;
-
- private:
- friend class TrackedTime;
- explicit Duration(int32_t duration);
-
- // Internal time is stored directly in milliseconds.
- int32_t ms_;
-};
-
-class BASE_EXPORT TrackedTime { // Similar to base::TimeTicks.
- public:
- TrackedTime();
- explicit TrackedTime(const base::TimeTicks& time);
-
- static TrackedTime Now();
- Duration operator-(const TrackedTime& other) const;
- TrackedTime operator+(const Duration& other) const;
- bool is_null() const;
-
- static TrackedTime FromMilliseconds(int32_t ms) { return TrackedTime(ms); }
-
- private:
- friend class Duration;
- explicit TrackedTime(int32_t ms);
-
- // Internal duration is stored directly in milliseconds.
- uint32_t ms_;
-};
-
-} // namespace tracked_objects
-
-#endif // BASE_PROFILER_TRACKED_TIME_H_
diff --git a/base/profiler/tracked_time_unittest.cc b/base/profiler/tracked_time_unittest.cc
deleted file mode 100644
index f6d35baab3..0000000000
--- a/base/profiler/tracked_time_unittest.cc
+++ /dev/null
@@ -1,105 +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.
-
-// Test of classes in tracked_time.cc
-
-#include <stdint.h>
-
-#include "base/profiler/tracked_time.h"
-#include "base/time/time.h"
-#include "base/tracked_objects.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace tracked_objects {
-
-TEST(TrackedTimeTest, TrackedTimerMilliseconds) {
- // First make sure we basicallly transfer simple milliseconds values as
- // expected. Most critically, things should not become null.
- int32_t kSomeMilliseconds = 243; // Some example times.
- int64_t kReallyBigMilliseconds = (1LL << 35) + kSomeMilliseconds;
-
- TrackedTime some = TrackedTime() +
- Duration::FromMilliseconds(kSomeMilliseconds);
- EXPECT_EQ(kSomeMilliseconds, (some - TrackedTime()).InMilliseconds());
- EXPECT_FALSE(some.is_null());
-
- // Now create a big time, to check that it is wrapped modulo 2^32.
- base::TimeTicks big = base::TimeTicks() +
- base::TimeDelta::FromMilliseconds(kReallyBigMilliseconds);
- EXPECT_EQ(kReallyBigMilliseconds, (big - base::TimeTicks()).InMilliseconds());
-
- TrackedTime wrapped_big(big);
- // Expect wrapping at 32 bits.
- EXPECT_EQ(kSomeMilliseconds, (wrapped_big - TrackedTime()).InMilliseconds());
-}
-
-TEST(TrackedTimeTest, TrackedTimerDuration) {
- int kFirstMilliseconds = 793;
- int kSecondMilliseconds = 14889;
-
- Duration first = Duration::FromMilliseconds(kFirstMilliseconds);
- Duration second = Duration::FromMilliseconds(kSecondMilliseconds);
-
- EXPECT_EQ(kFirstMilliseconds, first.InMilliseconds());
- EXPECT_EQ(kSecondMilliseconds, second.InMilliseconds());
-
- Duration sum = first + second;
- EXPECT_EQ(kFirstMilliseconds + kSecondMilliseconds, sum.InMilliseconds());
-}
-
-TEST(TrackedTimeTest, TrackedTimerVsTimeTicks) {
- // Make sure that our 32 bit timer is aligned with the TimeTicks() timer.
-
- // First get a 64 bit timer (which should not be null).
- base::TimeTicks ticks_before = base::TimeTicks::Now();
- EXPECT_FALSE(ticks_before.is_null());
-
- // Then get a 32 bit timer that can be be null when it wraps.
- TrackedTime now = TrackedTime::Now();
-
- // Then get a bracketing time.
- base::TimeTicks ticks_after = base::TimeTicks::Now();
- EXPECT_FALSE(ticks_after.is_null());
-
- // Now make sure that we bracketed our tracked time nicely.
- Duration before = now - TrackedTime(ticks_before);
- EXPECT_LE(0, before.InMilliseconds());
- Duration after = now - TrackedTime(ticks_after);
- EXPECT_GE(0, after.InMilliseconds());
-}
-
-TEST(TrackedTimeTest, TrackedTimerDisabled) {
- // Check to be sure disabling the collection of data induces a null time
- // (which we know will return much faster).
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::DEACTIVATED);
- // Since we disabled tracking, we should get a null response.
- TrackedTime track_now = ThreadData::Now();
- EXPECT_TRUE(track_now.is_null());
-}
-
-TEST(TrackedTimeTest, TrackedTimerEnabled) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
- // Make sure that when we enable tracking, we get a real timer result.
-
- // First get a 64 bit timer (which should not be null).
- base::TimeTicks ticks_before = base::TimeTicks::Now();
- EXPECT_FALSE(ticks_before.is_null());
-
- // Then get a 32 bit timer that can be null when it wraps.
- // Crtical difference from the TrackedTimerVsTimeTicks test, is that we use
- // ThreadData::Now(). It can sometimes return the null time.
- TrackedTime now = ThreadData::Now();
-
- // Then get a bracketing time.
- base::TimeTicks ticks_after = base::TimeTicks::Now();
- EXPECT_FALSE(ticks_after.is_null());
-
- // Now make sure that we bracketed our tracked time nicely.
- Duration before = now - TrackedTime(ticks_before);
- EXPECT_LE(0, before.InMilliseconds());
- Duration after = now - TrackedTime(ticks_after);
- EXPECT_GE(0, after.InMilliseconds());
-}
-
-} // namespace tracked_objects
diff --git a/base/rand_util.cc b/base/rand_util.cc
index fab6c6613c..5881ef25e3 100644
--- a/base/rand_util.cc
+++ b/base/rand_util.cc
@@ -16,6 +16,12 @@
namespace base {
+uint64_t RandUint64() {
+ uint64_t number;
+ RandBytes(&number, sizeof(number));
+ return number;
+}
+
int RandInt(int min, int max) {
DCHECK_LE(min, max);
diff --git a/base/rand_util.h b/base/rand_util.h
index 881dbd50bb..45e4283223 100644
--- a/base/rand_util.h
+++ b/base/rand_util.h
@@ -8,6 +8,7 @@
#include <stddef.h>
#include <stdint.h>
+#include <algorithm>
#include <string>
#include "base/base_export.h"
@@ -22,10 +23,6 @@ BASE_EXPORT uint64_t RandUint64();
BASE_EXPORT int RandInt(int min, int max);
// Returns a random number in range [0, range). Thread-safe.
-//
-// Note that this can be used as an adapter for std::random_shuffle():
-// Given a pre-populated |std::vector<int> myvector|, shuffle it as
-// std::random_shuffle(myvector.begin(), myvector.end(), base::RandGenerator);
BASE_EXPORT uint64_t RandGenerator(uint64_t range);
// Returns a random double in range [0, 1). Thread-safe.
@@ -35,24 +32,43 @@ BASE_EXPORT double RandDouble();
// the range [0, 1). Thread-safe.
BASE_EXPORT double BitsToOpenEndedUnitInterval(uint64_t bits);
-// Fills |output_length| bytes of |output| with random data.
+// Fills |output_length| bytes of |output| with random data. Thread-safe.
//
-// WARNING:
-// Do not use for security-sensitive purposes.
-// See crypto/ for cryptographically secure random number generation APIs.
+// Although implementations are required to use a cryptographically secure
+// random number source, code outside of base/ that relies on this should use
+// crypto::RandBytes instead to ensure the requirement is easily discoverable.
BASE_EXPORT void RandBytes(void* output, size_t output_length);
// Fills a string of length |length| with random data and returns it.
-// |length| should be nonzero.
+// |length| should be nonzero. Thread-safe.
//
// Note that this is a variation of |RandBytes| with a different return type.
// The returned string is likely not ASCII/UTF-8. Use with care.
//
-// WARNING:
-// Do not use for security-sensitive purposes.
-// See crypto/ for cryptographically secure random number generation APIs.
+// Although implementations are required to use a cryptographically secure
+// random number source, code outside of base/ that relies on this should use
+// crypto::RandBytes instead to ensure the requirement is easily discoverable.
BASE_EXPORT std::string RandBytesAsString(size_t length);
+// An STL UniformRandomBitGenerator backed by RandUint64.
+// TODO(tzik): Consider replacing this with a faster implementation.
+class RandomBitGenerator {
+ public:
+ using result_type = uint64_t;
+ static constexpr result_type min() { return 0; }
+ static constexpr result_type max() { return UINT64_MAX; }
+ result_type operator()() const { return RandUint64(); }
+
+ RandomBitGenerator() = default;
+ ~RandomBitGenerator() = default;
+};
+
+// Shuffles [first, last) randomly. Thread-safe.
+template <typename Itr>
+void RandomShuffle(Itr first, Itr last) {
+ std::shuffle(first, last, RandomBitGenerator());
+}
+
#if defined(OS_POSIX)
BASE_EXPORT int GetUrandomFD();
#endif
diff --git a/base/rand_util_posix.cc b/base/rand_util_posix.cc
index 469f7af9bf..2c1653d322 100644
--- a/base/rand_util_posix.cc
+++ b/base/rand_util_posix.cc
@@ -23,9 +23,17 @@ namespace {
// we can use LazyInstance to handle opening it on the first access.
class URandomFd {
public:
+#if defined(OS_AIX)
+ // AIX has no 64-bit support for open falgs such as -
+ // O_CLOEXEC, O_NOFOLLOW and O_TTY_INIT
+ URandomFd() : fd_(HANDLE_EINTR(open("/dev/urandom", O_RDONLY))) {
+ DCHECK_GE(fd_, 0) << "Cannot open /dev/urandom: " << errno;
+ }
+#else
URandomFd() : fd_(HANDLE_EINTR(open("/dev/urandom", O_RDONLY | O_CLOEXEC))) {
DCHECK_GE(fd_, 0) << "Cannot open /dev/urandom: " << errno;
}
+#endif
~URandomFd() { close(fd_); }
@@ -41,13 +49,6 @@ base::LazyInstance<URandomFd>::Leaky g_urandom_fd = LAZY_INSTANCE_INITIALIZER;
namespace base {
-// NOTE: This function must be cryptographically secure. http://crbug.com/140076
-uint64_t RandUint64() {
- uint64_t number;
- RandBytes(&number, sizeof(number));
- return number;
-}
-
void RandBytes(void* output, size_t output_length) {
const int urandom_fd = g_urandom_fd.Pointer()->fd();
const bool success =
diff --git a/base/rand_util_unittest.cc b/base/rand_util_unittest.cc
index 4f46b80705..11a118a944 100644
--- a/base/rand_util_unittest.cc
+++ b/base/rand_util_unittest.cc
@@ -52,6 +52,11 @@ TEST(RandUtilTest, RandBytes) {
EXPECT_GT(std::unique(buffer, buffer + buffer_size) - buffer, 25);
}
+// Verify that calling base::RandBytes with an empty buffer doesn't fail.
+TEST(RandUtilTest, RandBytes0) {
+ base::RandBytes(nullptr, 0);
+}
+
TEST(RandUtilTest, RandBytesAsString) {
std::string random_string = base::RandBytesAsString(1);
EXPECT_EQ(1U, random_string.size());
@@ -134,6 +139,17 @@ TEST(RandUtilTest, RandUint64ProducesBothValuesOfAllBits) {
FAIL() << "Didn't achieve all bit values in maximum number of tries.";
}
+TEST(RandUtilTest, RandBytesLonger) {
+ // Fuchsia can only retrieve 256 bytes of entropy at a time, so make sure we
+ // handle longer requests than that.
+ std::string random_string0 = base::RandBytesAsString(255);
+ EXPECT_EQ(255u, random_string0.size());
+ std::string random_string1 = base::RandBytesAsString(1023);
+ EXPECT_EQ(1023u, random_string1.size());
+ std::string random_string2 = base::RandBytesAsString(4097);
+ EXPECT_EQ(4097u, random_string2.size());
+}
+
// Benchmark test for RandBytes(). Disabled since it's intentionally slow and
// does not test anything that isn't already tested by the existing RandBytes()
// tests.
diff --git a/base/run_loop.cc b/base/run_loop.cc
index 4c19d3589f..d4d87d72a0 100644
--- a/base/run_loop.cc
+++ b/base/run_loop.cc
@@ -5,97 +5,308 @@
#include "base/run_loop.h"
#include "base/bind.h"
-#include "base/tracked_objects.h"
+#include "base/callback.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop/message_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_local.h"
+#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
namespace base {
-RunLoop::RunLoop()
- : loop_(MessageLoop::current()),
- previous_run_loop_(NULL),
- run_depth_(0),
- run_called_(false),
- quit_called_(false),
- running_(false),
- quit_when_idle_received_(false),
+namespace {
+
+LazyInstance<ThreadLocalPointer<RunLoop::Delegate>>::Leaky tls_delegate =
+ LAZY_INSTANCE_INITIALIZER;
+
+// Runs |closure| immediately if this is called on |task_runner|, otherwise
+// forwards |closure| to it.
+void ProxyToTaskRunner(scoped_refptr<SequencedTaskRunner> task_runner,
+ OnceClosure closure) {
+ if (task_runner->RunsTasksInCurrentSequence()) {
+ std::move(closure).Run();
+ return;
+ }
+ task_runner->PostTask(FROM_HERE, std::move(closure));
+}
+
+} // namespace
+
+RunLoop::Delegate::Delegate() {
+ // The Delegate can be created on another thread. It is only bound in
+ // RegisterDelegateForCurrentThread().
+ DETACH_FROM_THREAD(bound_thread_checker_);
+}
+
+RunLoop::Delegate::~Delegate() {
+ DCHECK_CALLED_ON_VALID_THREAD(bound_thread_checker_);
+ // A RunLoop::Delegate may be destroyed before it is bound, if so it may still
+ // be on its creation thread (e.g. a Thread that fails to start) and
+ // shouldn't disrupt that thread's state.
+ if (bound_)
+ tls_delegate.Get().Set(nullptr);
+}
+
+bool RunLoop::Delegate::ShouldQuitWhenIdle() {
+ return active_run_loops_.top()->quit_when_idle_received_;
+}
+
+// static
+void RunLoop::RegisterDelegateForCurrentThread(Delegate* delegate) {
+ // Bind |delegate| to this thread.
+ DCHECK(!delegate->bound_);
+ DCHECK_CALLED_ON_VALID_THREAD(delegate->bound_thread_checker_);
+
+ // There can only be one RunLoop::Delegate per thread.
+ DCHECK(!tls_delegate.Get().Get())
+ << "Error: Multiple RunLoop::Delegates registered on the same thread.\n\n"
+ "Hint: You perhaps instantiated a second "
+ "MessageLoop/ScopedTaskEnvironment on a thread that already had one?";
+ tls_delegate.Get().Set(delegate);
+ delegate->bound_ = true;
+}
+
+RunLoop::RunLoop(Type type)
+ : delegate_(tls_delegate.Get().Get()),
+ type_(type),
+ origin_task_runner_(ThreadTaskRunnerHandle::Get()),
weak_factory_(this) {
- DCHECK(loop_);
+ DCHECK(delegate_) << "A RunLoop::Delegate must be bound to this thread prior "
+ "to using RunLoop.";
+ DCHECK(origin_task_runner_);
}
RunLoop::~RunLoop() {
+ // TODO(gab): Fix bad usage and enable this check, http://crbug.com/715235.
+ // DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void RunLoop::Run() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
if (!BeforeRun())
return;
- // Use task stopwatch to exclude the loop run time from the current task, if
- // any.
- tracked_objects::TaskStopwatch stopwatch;
- stopwatch.Start();
- loop_->RunHandler();
- stopwatch.Stop();
+ // It is okay to access this RunLoop from another sequence while Run() is
+ // active as this RunLoop won't touch its state until after that returns (if
+ // the RunLoop's state is accessed while processing Run(), it will be re-bound
+ // to the accessing sequence for the remainder of that Run() -- accessing from
+ // multiple sequences is still disallowed).
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+
+ DCHECK_EQ(this, delegate_->active_run_loops_.top());
+ const bool application_tasks_allowed =
+ delegate_->active_run_loops_.size() == 1U ||
+ type_ == Type::kNestableTasksAllowed;
+ delegate_->Run(application_tasks_allowed);
+
+ // Rebind this RunLoop to the current thread after Run().
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AfterRun();
}
void RunLoop::RunUntilIdle() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
quit_when_idle_received_ = true;
Run();
}
void RunLoop::Quit() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ // Thread-safe.
+
+ // This can only be hit if run_loop->Quit() is called directly (QuitClosure()
+ // proxies through ProxyToTaskRunner() as it can only deref its WeakPtr on
+ // |origin_task_runner_|).
+ if (!origin_task_runner_->RunsTasksInCurrentSequence()) {
+ origin_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&RunLoop::Quit, Unretained(this)));
+ return;
+ }
+
quit_called_ = true;
- if (running_ && loop_->run_loop_ == this) {
+ if (running_ && delegate_->active_run_loops_.top() == this) {
// This is the inner-most RunLoop, so quit now.
- loop_->QuitNow();
+ delegate_->Quit();
}
}
void RunLoop::QuitWhenIdle() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ // Thread-safe.
+
+ // This can only be hit if run_loop->QuitWhenIdle() is called directly
+ // (QuitWhenIdleClosure() proxies through ProxyToTaskRunner() as it can only
+ // deref its WeakPtr on |origin_task_runner_|).
+ if (!origin_task_runner_->RunsTasksInCurrentSequence()) {
+ origin_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&RunLoop::QuitWhenIdle, Unretained(this)));
+ return;
+ }
+
quit_when_idle_received_ = true;
}
base::Closure RunLoop::QuitClosure() {
- return base::Bind(&RunLoop::Quit, weak_factory_.GetWeakPtr());
+ // TODO(gab): Fix bad usage and enable this check, http://crbug.com/715235.
+ // DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ allow_quit_current_deprecated_ = false;
+
+ // Need to use ProxyToTaskRunner() as WeakPtrs vended from
+ // |weak_factory_| may only be accessed on |origin_task_runner_|.
+ // TODO(gab): It feels wrong that QuitClosure() is bound to a WeakPtr.
+ return base::Bind(&ProxyToTaskRunner, origin_task_runner_,
+ base::Bind(&RunLoop::Quit, weak_factory_.GetWeakPtr()));
}
base::Closure RunLoop::QuitWhenIdleClosure() {
- return base::Bind(&RunLoop::QuitWhenIdle, weak_factory_.GetWeakPtr());
+ // TODO(gab): Fix bad usage and enable this check, http://crbug.com/715235.
+ // DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ allow_quit_current_deprecated_ = false;
+
+ // Need to use ProxyToTaskRunner() as WeakPtrs vended from
+ // |weak_factory_| may only be accessed on |origin_task_runner_|.
+ // TODO(gab): It feels wrong that QuitWhenIdleClosure() is bound to a WeakPtr.
+ return base::Bind(
+ &ProxyToTaskRunner, origin_task_runner_,
+ base::Bind(&RunLoop::QuitWhenIdle, weak_factory_.GetWeakPtr()));
+}
+
+// static
+bool RunLoop::IsRunningOnCurrentThread() {
+ Delegate* delegate = tls_delegate.Get().Get();
+ return delegate && !delegate->active_run_loops_.empty();
+}
+
+// static
+bool RunLoop::IsNestedOnCurrentThread() {
+ Delegate* delegate = tls_delegate.Get().Get();
+ return delegate && delegate->active_run_loops_.size() > 1;
+}
+
+// static
+void RunLoop::AddNestingObserverOnCurrentThread(NestingObserver* observer) {
+ Delegate* delegate = tls_delegate.Get().Get();
+ DCHECK(delegate);
+ delegate->nesting_observers_.AddObserver(observer);
+}
+
+// static
+void RunLoop::RemoveNestingObserverOnCurrentThread(NestingObserver* observer) {
+ Delegate* delegate = tls_delegate.Get().Get();
+ DCHECK(delegate);
+ delegate->nesting_observers_.RemoveObserver(observer);
+}
+
+// static
+void RunLoop::QuitCurrentDeprecated() {
+ DCHECK(IsRunningOnCurrentThread());
+ Delegate* delegate = tls_delegate.Get().Get();
+ DCHECK(delegate->active_run_loops_.top()->allow_quit_current_deprecated_)
+ << "Please migrate off QuitCurrentDeprecated(), e.g. to QuitClosure().";
+ delegate->active_run_loops_.top()->Quit();
+}
+
+// static
+void RunLoop::QuitCurrentWhenIdleDeprecated() {
+ DCHECK(IsRunningOnCurrentThread());
+ Delegate* delegate = tls_delegate.Get().Get();
+ DCHECK(delegate->active_run_loops_.top()->allow_quit_current_deprecated_)
+ << "Please migrate off QuitCurrentWhenIdleDeprecated(), e.g. to "
+ "QuitWhenIdleClosure().";
+ delegate->active_run_loops_.top()->QuitWhenIdle();
}
+// static
+Closure RunLoop::QuitCurrentWhenIdleClosureDeprecated() {
+ // TODO(844016): Fix callsites and enable this check, or remove the API.
+ // Delegate* delegate = tls_delegate.Get().Get();
+ // DCHECK(delegate->active_run_loops_.top()->allow_quit_current_deprecated_)
+ // << "Please migrate off QuitCurrentWhenIdleClosureDeprecated(), e.g to "
+ // "QuitWhenIdleClosure().";
+ return Bind(&RunLoop::QuitCurrentWhenIdleDeprecated);
+}
+
+#if DCHECK_IS_ON()
+RunLoop::ScopedDisallowRunningForTesting::ScopedDisallowRunningForTesting()
+ : current_delegate_(tls_delegate.Get().Get()),
+ previous_run_allowance_(
+ current_delegate_ ? current_delegate_->allow_running_for_testing_
+ : false) {
+ if (current_delegate_)
+ current_delegate_->allow_running_for_testing_ = false;
+}
+
+RunLoop::ScopedDisallowRunningForTesting::~ScopedDisallowRunningForTesting() {
+ DCHECK_EQ(current_delegate_, tls_delegate.Get().Get());
+ if (current_delegate_)
+ current_delegate_->allow_running_for_testing_ = previous_run_allowance_;
+}
+#else // DCHECK_IS_ON()
+// Defined out of line so that the compiler doesn't inline these and realize
+// the scope has no effect and then throws an "unused variable" warning in
+// non-dcheck builds.
+RunLoop::ScopedDisallowRunningForTesting::ScopedDisallowRunningForTesting() =
+ default;
+RunLoop::ScopedDisallowRunningForTesting::~ScopedDisallowRunningForTesting() =
+ default;
+#endif // DCHECK_IS_ON()
+
bool RunLoop::BeforeRun() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+#if DCHECK_IS_ON()
+ DCHECK(delegate_->allow_running_for_testing_)
+ << "RunLoop::Run() isn't allowed in the scope of a "
+ "ScopedDisallowRunningForTesting. Hint: if mixing "
+ "TestMockTimeTaskRunners on same thread, use TestMockTimeTaskRunner's "
+ "API instead of RunLoop to drive individual task runners.";
DCHECK(!run_called_);
run_called_ = true;
+#endif // DCHECK_IS_ON()
// Allow Quit to be called before Run.
if (quit_called_)
return false;
- // Push RunLoop stack:
- previous_run_loop_ = loop_->run_loop_;
- run_depth_ = previous_run_loop_? previous_run_loop_->run_depth_ + 1 : 1;
- loop_->run_loop_ = this;
+ auto& active_run_loops_ = delegate_->active_run_loops_;
+ active_run_loops_.push(this);
- if (run_depth_ > 1)
- loop_->NotifyBeginNestedLoop();
+ const bool is_nested = active_run_loops_.size() > 1;
+
+ if (is_nested) {
+ for (auto& observer : delegate_->nesting_observers_)
+ observer.OnBeginNestedRunLoop();
+ if (type_ == Type::kNestableTasksAllowed)
+ delegate_->EnsureWorkScheduled();
+ }
running_ = true;
return true;
}
void RunLoop::AfterRun() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
running_ = false;
- // Pop RunLoop stack:
- loop_->run_loop_ = previous_run_loop_;
+ auto& active_run_loops_ = delegate_->active_run_loops_;
+ DCHECK_EQ(active_run_loops_.top(), this);
+ active_run_loops_.pop();
+
+ RunLoop* previous_run_loop =
+ active_run_loops_.empty() ? nullptr : active_run_loops_.top();
+
+ if (previous_run_loop) {
+ for (auto& observer : delegate_->nesting_observers_)
+ observer.OnExitNestedRunLoop();
+ }
- // Execute deferred QuitNow, if any:
- if (previous_run_loop_ && previous_run_loop_->quit_called_)
- loop_->QuitNow();
+ // Execute deferred Quit, if any:
+ if (previous_run_loop && previous_run_loop->quit_called_)
+ delegate_->Quit();
}
} // namespace base
diff --git a/base/run_loop.h b/base/run_loop.h
index 077d097ba9..2582a69f1d 100644
--- a/base/run_loop.h
+++ b/base/run_loop.h
@@ -5,11 +5,17 @@
#ifndef BASE_RUN_LOOP_H_
#define BASE_RUN_LOOP_H_
+#include <utility>
+#include <vector>
+
#include "base/base_export.h"
#include "base/callback.h"
+#include "base/containers/stack.h"
#include "base/macros.h"
+#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
-#include "base/message_loop/message_loop.h"
+#include "base/observer_list.h"
+#include "base/sequence_checker.h"
#include "base/threading/thread_checker.h"
#include "build/build_config.h"
@@ -22,41 +28,78 @@ class MessagePumpForUI;
class MessagePumpUIApplication;
#endif
-// Helper class to Run a nested MessageLoop. Please do not use nested
-// MessageLoops in production code! If you must, use this class instead of
-// calling MessageLoop::Run/Quit directly. RunLoop::Run can only be called once
+class SingleThreadTaskRunner;
+
+// Helper class to run the RunLoop::Delegate associated with the current thread.
+// A RunLoop::Delegate must have been bound to this thread (ref.
+// RunLoop::RegisterDelegateForCurrentThread()) prior to using any of RunLoop's
+// member and static methods unless explicitly indicated otherwise (e.g.
+// IsRunning/IsNestedOnCurrentThread()). RunLoop::Run can only be called once
// per RunLoop lifetime. Create a RunLoop on the stack and call Run/Quit to run
-// a nested MessageLoop.
+// a nested RunLoop but please do not use nested loops in production code!
class BASE_EXPORT RunLoop {
public:
- RunLoop();
+ // The type of RunLoop: a kDefault RunLoop at the top-level (non-nested) will
+ // process system and application tasks assigned to its Delegate. When nested
+ // however a kDefault RunLoop will only process system tasks while a
+ // kNestableTasksAllowed RunLoop will continue to process application tasks
+ // even if nested.
+ //
+ // This is relevant in the case of recursive RunLoops. Some unwanted run loops
+ // may occur when using common controls or printer functions. By default,
+ // recursive task processing is disabled.
+ //
+ // In general, nestable RunLoops are to be avoided. They are dangerous and
+ // difficult to get right, so please use with extreme caution.
+ //
+ // A specific example where this makes a difference is:
+ // - The thread is running a RunLoop.
+ // - It receives a task #1 and executes it.
+ // - The task #1 implicitly starts a RunLoop, like a MessageBox in the unit
+ // test. This can also be StartDoc or GetSaveFileName.
+ // - The thread receives a task #2 before or while in this second RunLoop.
+ // - With a kNestableTasksAllowed RunLoop, the task #2 will run right away.
+ // Otherwise, it will get executed right after task #1 completes in the main
+ // RunLoop.
+ enum class Type {
+ kDefault,
+ kNestableTasksAllowed,
+ };
+
+ RunLoop(Type type = Type::kDefault);
~RunLoop();
- // Run the current MessageLoop. This blocks until Quit is called. Before
+ // Run the current RunLoop::Delegate. This blocks until Quit is called. Before
// calling Run, be sure to grab the QuitClosure in order to stop the
- // MessageLoop asynchronously. MessageLoop::QuitWhenIdle and QuitNow will also
- // trigger a return from Run, but those are deprecated.
+ // RunLoop::Delegate asynchronously.
void Run();
- // Run the current MessageLoop until it doesn't find any tasks or messages in
- // the queue (it goes idle). WARNING: This may never return! Only use this
- // when repeating tasks such as animated web pages have been shut down.
+ // Run the current RunLoop::Delegate until it doesn't find any tasks or
+ // messages in its queue (it goes idle). WARNING: This may never return! Only
+ // use this when repeating tasks such as animated web pages have been shut
+ // down.
void RunUntilIdle();
- bool running() const { return running_; }
+ bool running() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return running_;
+ }
// Quit() quits an earlier call to Run() immediately. QuitWhenIdle() quits an
// earlier call to Run() when there aren't any tasks or messages in the queue.
//
- // There can be other nested RunLoops servicing the same task queue
- // (MessageLoop); Quitting one RunLoop has no bearing on the others. Quit()
- // and QuitWhenIdle() can be called before, during or after Run(). If called
- // before Run(), Run() will return immediately when called. Calling Quit() or
- // QuitWhenIdle() after the RunLoop has already finished running has no
- // effect.
+ // These methods are thread-safe but note that Quit() is best-effort when
+ // called from another thread (will quit soon but tasks that were already
+ // queued on this RunLoop will get to run first).
+ //
+ // There can be other nested RunLoops servicing the same task queue. Quitting
+ // one RunLoop has no bearing on the others. Quit() and QuitWhenIdle() can be
+ // called before, during or after Run(). If called before Run(), Run() will
+ // return immediately when called. Calling Quit() or QuitWhenIdle() after the
+ // RunLoop has already finished running has no effect.
//
// WARNING: You must NEVER assume that a call to Quit() or QuitWhenIdle() will
- // terminate the targetted message loop. If a nested message loop continues
+ // terminate the targetted message loop. If a nested RunLoop continues
// running, the target may NEVER terminate. It is very easy to livelock (run
// forever) in such a case.
void Quit();
@@ -65,6 +108,10 @@ class BASE_EXPORT RunLoop {
// Convenience methods to get a closure that safely calls Quit() or
// QuitWhenIdle() (has no effect if the RunLoop instance is gone).
//
+ // The resulting Closure is thread-safe (note however that invoking the
+ // QuitClosure() from another thread than this RunLoop's will result in an
+ // asynchronous rather than immediate Quit()).
+ //
// Example:
// RunLoop run_loop;
// PostTask(run_loop.QuitClosure());
@@ -72,16 +119,145 @@ class BASE_EXPORT RunLoop {
base::Closure QuitClosure();
base::Closure QuitWhenIdleClosure();
+ // Returns true if there is an active RunLoop on this thread.
+ // Safe to call before RegisterDelegateForCurrentThread().
+ static bool IsRunningOnCurrentThread();
+
+ // Returns true if there is an active RunLoop on this thread and it's nested
+ // within another active RunLoop.
+ // Safe to call before RegisterDelegateForCurrentThread().
+ static bool IsNestedOnCurrentThread();
+
+ // A NestingObserver is notified when a nested RunLoop begins and ends.
+ class BASE_EXPORT NestingObserver {
+ public:
+ // Notified before a nested loop starts running work on the current thread.
+ virtual void OnBeginNestedRunLoop() = 0;
+ // Notified after a nested loop is done running work on the current thread.
+ virtual void OnExitNestedRunLoop() {}
+
+ protected:
+ virtual ~NestingObserver() = default;
+ };
+
+ static void AddNestingObserverOnCurrentThread(NestingObserver* observer);
+ static void RemoveNestingObserverOnCurrentThread(NestingObserver* observer);
+
+ // A RunLoop::Delegate is a generic interface that allows RunLoop to be
+ // separate from the underlying implementation of the message loop for this
+ // thread. It holds private state used by RunLoops on its associated thread.
+ // One and only one RunLoop::Delegate must be registered on a given thread
+ // via RunLoop::RegisterDelegateForCurrentThread() before RunLoop instances
+ // and RunLoop static methods can be used on it.
+ class BASE_EXPORT Delegate {
+ public:
+ Delegate();
+ virtual ~Delegate();
+
+ // Used by RunLoop to inform its Delegate to Run/Quit. Implementations are
+ // expected to keep on running synchronously from the Run() call until the
+ // eventual matching Quit() call. Upon receiving a Quit() call it should
+ // return from the Run() call as soon as possible without executing
+ // remaining tasks/messages. Run() calls can nest in which case each Quit()
+ // call should result in the topmost active Run() call returning. The only
+ // other trigger for Run() to return is the
+ // |should_quit_when_idle_callback_| which the Delegate should probe before
+ // sleeping when it becomes idle. |application_tasks_allowed| is true if
+ // this is the first Run() call on the stack or it was made from a nested
+ // RunLoop of Type::kNestableTasksAllowed (otherwise this Run() level should
+ // only process system tasks).
+ virtual void Run(bool application_tasks_allowed) = 0;
+ virtual void Quit() = 0;
+
+ // Invoked right before a RunLoop enters a nested Run() call on this
+ // Delegate iff this RunLoop is of type kNestableTasksAllowed. The Delegate
+ // should ensure that the upcoming Run() call will result in processing
+ // application tasks queued ahead of it without further probing. e.g.
+ // message pumps on some platforms, like Mac, need an explicit request to
+ // process application tasks when nested, otherwise they'll only wait for
+ // system messages.
+ virtual void EnsureWorkScheduled() = 0;
+
+ protected:
+ // Returns the result of this Delegate's |should_quit_when_idle_callback_|.
+ // "protected" so it can be invoked only by the Delegate itself.
+ bool ShouldQuitWhenIdle();
+
+ private:
+ // While the state is owned by the Delegate subclass, only RunLoop can use
+ // it.
+ friend class RunLoop;
+
+ // A vector-based stack is more memory efficient than the default
+ // deque-based stack as the active RunLoop stack isn't expected to ever
+ // have more than a few entries.
+ using RunLoopStack = base::stack<RunLoop*, std::vector<RunLoop*>>;
+
+ RunLoopStack active_run_loops_;
+ ObserverList<RunLoop::NestingObserver> nesting_observers_;
+
+#if DCHECK_IS_ON()
+ bool allow_running_for_testing_ = true;
+#endif
+
+ // True once this Delegate is bound to a thread via
+ // RegisterDelegateForCurrentThread().
+ bool bound_ = false;
+
+ // Thread-affine per its use of TLS.
+ THREAD_CHECKER(bound_thread_checker_);
+
+ DISALLOW_COPY_AND_ASSIGN(Delegate);
+ };
+
+ // Registers |delegate| on the current thread. Must be called once and only
+ // once per thread before using RunLoop methods on it. |delegate| is from then
+ // on forever bound to that thread (including its destruction).
+ static void RegisterDelegateForCurrentThread(Delegate* delegate);
+
+ // Quits the active RunLoop (when idle) -- there must be one. These were
+ // introduced as prefered temporary replacements to the long deprecated
+ // MessageLoop::Quit(WhenIdle)(Closure) methods. Callers should properly plumb
+ // a reference to the appropriate RunLoop instance (or its QuitClosure)
+ // instead of using these in order to link Run()/Quit() to a single RunLoop
+ // instance and increase readability.
+ static void QuitCurrentDeprecated();
+ static void QuitCurrentWhenIdleDeprecated();
+ static Closure QuitCurrentWhenIdleClosureDeprecated();
+
+ // Run() will DCHECK if called while there's a ScopedDisallowRunningForTesting
+ // in scope on its thread. This is useful to add safety to some test
+ // constructs which allow multiple task runners to share the main thread in
+ // unit tests. While the main thread can be shared by multiple runners to
+ // deterministically fake multi threading, there can still only be a single
+ // RunLoop::Delegate per thread and RunLoop::Run() should only be invoked from
+ // it (or it would result in incorrectly driving TaskRunner A while in
+ // TaskRunner B's context).
+ class BASE_EXPORT ScopedDisallowRunningForTesting {
+ public:
+ ScopedDisallowRunningForTesting();
+ ~ScopedDisallowRunningForTesting();
+
+ private:
+#if DCHECK_IS_ON()
+ Delegate* current_delegate_;
+ const bool previous_run_allowance_;
+#endif // DCHECK_IS_ON()
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedDisallowRunningForTesting);
+ };
+
private:
- friend class MessageLoop;
+ FRIEND_TEST_ALL_PREFIXES(MessageLoopTypedTest, RunLoopQuitOrderAfter);
+
#if defined(OS_ANDROID)
- // Android doesn't support the blocking MessageLoop::Run, so it calls
+ // Android doesn't support the blocking RunLoop::Run, so it calls
// BeforeRun and AfterRun directly.
friend class base::MessagePumpForUI;
#endif
#if defined(OS_IOS)
- // iOS doesn't support the blocking MessageLoop::Run, so it calls
+ // iOS doesn't support the blocking RunLoop::Run, so it calls
// BeforeRun directly.
friend class base::MessagePumpUIApplication;
#endif
@@ -90,23 +266,39 @@ class BASE_EXPORT RunLoop {
bool BeforeRun();
void AfterRun();
- MessageLoop* loop_;
+ // A copy of RunLoop::Delegate for the thread driven by tis RunLoop for quick
+ // access without using TLS (also allows access to state from another sequence
+ // during Run(), ref. |sequence_checker_| below).
+ Delegate* delegate_;
- // Parent RunLoop or NULL if this is the top-most RunLoop.
- RunLoop* previous_run_loop_;
+ const Type type_;
+
+#if DCHECK_IS_ON()
+ bool run_called_ = false;
+#endif
- // Used to count how many nested Run() invocations are on the stack.
- int run_depth_;
+ bool quit_called_ = false;
+ bool running_ = false;
+ // Used to record that QuitWhenIdle() was called on this RunLoop, meaning that
+ // the Delegate should quit Run() once it becomes idle (it's responsible for
+ // probing this state via ShouldQuitWhenIdle()). This state is stored here
+ // rather than pushed to Delegate to support nested RunLoops.
+ bool quit_when_idle_received_ = false;
- bool run_called_;
- bool quit_called_;
- bool running_;
+ // True if use of QuitCurrent*Deprecated() is allowed. Taking a Quit*Closure()
+ // from a RunLoop implicitly sets this to false, so QuitCurrent*Deprecated()
+ // cannot be used while that RunLoop is being Run().
+ bool allow_quit_current_deprecated_ = true;
- // Used to record that QuitWhenIdle() was called on the MessageLoop, meaning
- // that we should quit Run once it becomes idle.
- bool quit_when_idle_received_;
+ // RunLoop is not thread-safe. Its state/methods, unless marked as such, may
+ // not be accessed from any other sequence than the thread it was constructed
+ // on. Exception: RunLoop can be safely accessed from one other sequence (or
+ // single parallel task) during Run() -- e.g. to Quit() without having to
+ // plumb ThreatTaskRunnerHandle::Get() throughout a test to repost QuitClosure
+ // to origin thread.
+ SEQUENCE_CHECKER(sequence_checker_);
- base::ThreadChecker thread_checker_;
+ const scoped_refptr<SingleThreadTaskRunner> origin_task_runner_;
// WeakPtrFactory for QuitClosure safety.
base::WeakPtrFactory<RunLoop> weak_factory_;
diff --git a/base/sampling_heap_profiler/benchmark-octane.js b/base/sampling_heap_profiler/benchmark-octane.js
new file mode 100644
index 0000000000..602e152756
--- /dev/null
+++ b/base/sampling_heap_profiler/benchmark-octane.js
@@ -0,0 +1,58 @@
+// 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.
+
+// To benchmark a specific version of Chrome set the CHROME_PATH environment
+// variable, e.g.:
+// $ CHROME_PATH=~/chromium/src/out/Release/chrome node benchmark-octane.js
+
+const puppeteer = require('puppeteer');
+
+async function runOctane(samplingRate) {
+ const args = ['--enable-devtools-experiments'];
+ if (samplingRate)
+ args.push(`--sampling-heap-profiler=${samplingRate}`);
+ while (true) {
+ let brower;
+ try {
+ browser = await puppeteer.launch({
+ executablePath: process.env.CHROME_PATH, args, headless: true});
+ const page = await browser.newPage();
+ await page.goto('https://chromium.github.io/octane/');
+ await page.waitForSelector('#run-octane'); // Just in case.
+ await page.click('#run-octane');
+
+ const scoreDiv = await page.waitForSelector('#main-banner:only-child',
+ {timeout: 120000});
+ const scoreText = await page.evaluate(e => e.innerText, scoreDiv);
+ const match = /Score:\s*(\d+)/.exec(scoreText);
+ if (match.length < 2)
+ continue;
+ return parseInt(match[1]);
+ } finally {
+ if (browser)
+ await browser.close();
+ }
+ }
+}
+
+async function makeRuns(rates) {
+ const scores = [];
+ for (const rate of rates)
+ scores.push(await runOctane(rate));
+ console.log(scores.join('\t'));
+}
+
+async function main() {
+ console.log(`Using ${process.env.CHROME_PATH || puppeteer.executablePath()}`);
+ const rates = [0];
+ for (let rate = 8; rate <= 2048; rate *= 2)
+ rates.push(rate);
+ console.log('Rates [KB]:');
+ console.log(rates.join('\t'));
+ console.log('='.repeat(rates.length * 8));
+ for (let i = 0; i < 100; ++i)
+ await makeRuns(rates);
+}
+
+main();
diff --git a/base/sampling_heap_profiler/lock_free_address_hash_set.cc b/base/sampling_heap_profiler/lock_free_address_hash_set.cc
new file mode 100644
index 0000000000..d06dc5c79e
--- /dev/null
+++ b/base/sampling_heap_profiler/lock_free_address_hash_set.cc
@@ -0,0 +1,72 @@
+// 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.
+
+#include "base/sampling_heap_profiler/lock_free_address_hash_set.h"
+
+#include <limits>
+
+#include "base/bits.h"
+
+namespace base {
+
+LockFreeAddressHashSet::LockFreeAddressHashSet(size_t buckets_count)
+ : buckets_(buckets_count), bucket_mask_(buckets_count - 1) {
+ DCHECK(bits::IsPowerOfTwo(buckets_count));
+ DCHECK(bucket_mask_ <= std::numeric_limits<uint32_t>::max());
+}
+
+LockFreeAddressHashSet::~LockFreeAddressHashSet() {
+ for (subtle::AtomicWord bucket : buckets_) {
+ Node* node = reinterpret_cast<Node*>(bucket);
+ while (node) {
+ Node* next = reinterpret_cast<Node*>(node->next);
+ delete node;
+ node = next;
+ }
+ }
+}
+
+void LockFreeAddressHashSet::Insert(void* key) {
+ // TODO(alph): Replace with DCHECK.
+ CHECK(key != nullptr);
+ CHECK(!Contains(key));
+ subtle::NoBarrier_AtomicIncrement(&size_, 1);
+ uint32_t h = Hash(key);
+ subtle::AtomicWord* bucket_ptr = &buckets_[h & bucket_mask_];
+ Node* node = reinterpret_cast<Node*>(subtle::NoBarrier_Load(bucket_ptr));
+ // First iterate over the bucket nodes and try to reuse an empty one if found.
+ for (; node != nullptr; node = next_node(node)) {
+ if (subtle::NoBarrier_CompareAndSwap(
+ &node->key, 0, reinterpret_cast<subtle::AtomicWord>(key)) == 0) {
+ return;
+ }
+ }
+ DCHECK(node == nullptr);
+ // There are no empty nodes to reuse in the bucket.
+ // Create a new node and prepend it to the list.
+ Node* new_node = new Node(key);
+ subtle::AtomicWord current_head = subtle::NoBarrier_Load(bucket_ptr);
+ subtle::AtomicWord expected_head;
+ do {
+ subtle::NoBarrier_Store(&new_node->next, current_head);
+ expected_head = current_head;
+ current_head = subtle::Release_CompareAndSwap(
+ bucket_ptr, current_head,
+ reinterpret_cast<subtle::AtomicWord>(new_node));
+ } while (current_head != expected_head);
+}
+
+void LockFreeAddressHashSet::Copy(const LockFreeAddressHashSet& other) {
+ DCHECK_EQ(0u, size());
+ for (subtle::AtomicWord bucket : other.buckets_) {
+ for (Node* node = reinterpret_cast<Node*>(bucket); node;
+ node = next_node(node)) {
+ subtle::AtomicWord k = subtle::NoBarrier_Load(&node->key);
+ if (k)
+ Insert(reinterpret_cast<void*>(k));
+ }
+ }
+}
+
+} // namespace base
diff --git a/base/sampling_heap_profiler/lock_free_address_hash_set.h b/base/sampling_heap_profiler/lock_free_address_hash_set.h
new file mode 100644
index 0000000000..7e96b89adc
--- /dev/null
+++ b/base/sampling_heap_profiler/lock_free_address_hash_set.h
@@ -0,0 +1,152 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_SAMPLING_HEAP_PROFILER_LOCK_FREE_ADDRESS_HASH_SET_H_
+#define BASE_SAMPLING_HEAP_PROFILER_LOCK_FREE_ADDRESS_HASH_SET_H_
+
+#include <cstdint>
+#include <vector>
+
+#include "base/atomicops.h"
+#include "base/logging.h"
+
+namespace base {
+
+// A hash set container that provides lock-free versions of
+// |Insert|, |Remove|, and |Contains| operations.
+// It does not support concurrent write operations |Insert| and |Remove|
+// over the same key. Concurrent writes of distinct keys are ok.
+// |Contains| method can be executed concurrently with other |Insert|, |Remove|,
+// or |Contains| even over the same key.
+// However, please note the result of concurrent execution of |Contains|
+// with |Insert| or |Remove| is racy.
+//
+// The hash set never rehashes, so the number of buckets stays the same
+// for the lifetime of the set.
+//
+// Internally the hashset is implemented as a vector of N buckets
+// (N has to be a power of 2). Each bucket holds a single-linked list of
+// nodes each corresponding to a key.
+// It is not possible to really delete nodes from the list as there might
+// be concurrent reads being executed over the node. The |Remove| operation
+// just marks the node as empty by placing nullptr into its key field.
+// Consequent |Insert| operations may reuse empty nodes when possible.
+//
+// The structure of the hashset for N buckets is the following:
+// 0: {*}--> {key1,*}--> {key2,*}--> NULL
+// 1: {*}--> NULL
+// 2: {*}--> {NULL,*}--> {key3,*}--> {key4,*}--> NULL
+// ...
+// N-1: {*}--> {keyM,*}--> NULL
+class BASE_EXPORT LockFreeAddressHashSet {
+ public:
+ explicit LockFreeAddressHashSet(size_t buckets_count);
+ ~LockFreeAddressHashSet();
+
+ // Checks if the |key| is in the set. Can be executed concurrently with
+ // |Insert|, |Remove|, and |Contains| operations.
+ bool Contains(void* key) const;
+
+ // Removes the |key| from the set. The key must be present in the set before
+ // the invocation.
+ // Can be concurrent with other |Insert| and |Remove| executions, provided
+ // they operate over distinct keys.
+ // Concurrent |Insert| or |Remove| executions over the same key are not
+ // supported.
+ void Remove(void* key);
+
+ // Inserts the |key| into the set. The key must not be present in the set
+ // before the invocation.
+ // Can be concurrent with other |Insert| and |Remove| executions, provided
+ // they operate over distinct keys.
+ // Concurrent |Insert| or |Remove| executions over the same key are not
+ // supported.
+ void Insert(void* key);
+
+ // Copies contents of |other| set into the current set. The current set
+ // must be empty before the call.
+ // The operation cannot be executed concurrently with any other methods.
+ void Copy(const LockFreeAddressHashSet& other);
+
+ size_t buckets_count() const { return buckets_.size(); }
+ size_t size() const {
+ return static_cast<size_t>(subtle::NoBarrier_Load(&size_));
+ }
+
+ // Returns the average bucket utilization.
+ float load_factor() const { return 1.f * size() / buckets_.size(); }
+
+ private:
+ friend class LockFreeAddressHashSetTest;
+
+ struct Node {
+ Node() : key(0), next(0) {}
+ explicit Node(void* key);
+
+ subtle::AtomicWord key;
+ subtle::AtomicWord next;
+ };
+
+ static uint32_t Hash(void* key);
+ Node* FindNode(void* key) const;
+ Node* Bucket(void* key) const;
+ static Node* next_node(Node* node) {
+ return reinterpret_cast<Node*>(subtle::NoBarrier_Load(&node->next));
+ }
+
+ std::vector<subtle::AtomicWord> buckets_;
+ size_t bucket_mask_;
+ subtle::AtomicWord size_ = 0;
+};
+
+inline LockFreeAddressHashSet::Node::Node(void* a_key) {
+ subtle::NoBarrier_Store(&key, reinterpret_cast<subtle::AtomicWord>(a_key));
+ subtle::NoBarrier_Store(&next, 0);
+}
+
+inline bool LockFreeAddressHashSet::Contains(void* key) const {
+ return FindNode(key) != nullptr;
+}
+
+inline void LockFreeAddressHashSet::Remove(void* key) {
+ Node* node = FindNode(key);
+ // TODO(alph): Replace with DCHECK.
+ CHECK(node != nullptr);
+ // We can never delete the node, nor detach it from the current bucket
+ // as there may always be another thread currently iterating over it.
+ // Instead we just mark it as empty, so |Insert| can reuse it later.
+ subtle::NoBarrier_Store(&node->key, 0);
+ subtle::NoBarrier_AtomicIncrement(&size_, -1);
+}
+
+inline LockFreeAddressHashSet::Node* LockFreeAddressHashSet::FindNode(
+ void* key) const {
+ for (Node* node = Bucket(key); node != nullptr; node = next_node(node)) {
+ void* k = reinterpret_cast<void*>(subtle::NoBarrier_Load(&node->key));
+ if (k == key)
+ return node;
+ }
+ return nullptr;
+}
+
+inline LockFreeAddressHashSet::Node* LockFreeAddressHashSet::Bucket(
+ void* key) const {
+ // TODO(alph): Replace with DCHECK.
+ CHECK(key != nullptr);
+ uint32_t h = Hash(key);
+ return reinterpret_cast<Node*>(
+ subtle::NoBarrier_Load(&buckets_[h & bucket_mask_]));
+}
+
+// static
+inline uint32_t LockFreeAddressHashSet::Hash(void* key) {
+ // A simple fast hash function for addresses.
+ uint64_t k = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(key));
+ uint64_t random_bits = 0x4bfdb9df5a6f243bull;
+ return static_cast<uint32_t>((k * random_bits) >> 32);
+}
+
+} // namespace base
+
+#endif // BASE_SAMPLING_HEAP_PROFILER_LOCK_FREE_ADDRESS_HASH_SET_H_
diff --git a/base/sampling_heap_profiler/lock_free_address_hash_set_unittest.cc b/base/sampling_heap_profiler/lock_free_address_hash_set_unittest.cc
new file mode 100644
index 0000000000..dac0066982
--- /dev/null
+++ b/base/sampling_heap_profiler/lock_free_address_hash_set_unittest.cc
@@ -0,0 +1,183 @@
+// 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.
+
+#include "base/sampling_heap_profiler/lock_free_address_hash_set.h"
+
+#include <stdlib.h>
+#include <cinttypes>
+#include <memory>
+
+#include "base/allocator/allocator_shim.h"
+#include "base/debug/alias.h"
+#include "base/threading/simple_thread.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+class LockFreeAddressHashSetTest : public ::testing::Test {
+ public:
+ static bool Subset(const LockFreeAddressHashSet& superset,
+ const LockFreeAddressHashSet& subset) {
+ for (subtle::AtomicWord bucket : subset.buckets_) {
+ for (LockFreeAddressHashSet::Node* node =
+ reinterpret_cast<LockFreeAddressHashSet::Node*>(bucket);
+ node; node = LockFreeAddressHashSet::next_node(node)) {
+ void* key = reinterpret_cast<void*>(node->key);
+ if (key && !superset.Contains(key))
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static bool Equals(const LockFreeAddressHashSet& set1,
+ const LockFreeAddressHashSet& set2) {
+ return Subset(set1, set2) && Subset(set2, set1);
+ }
+
+ static size_t BucketSize(const LockFreeAddressHashSet& set, size_t bucket) {
+ size_t count = 0;
+ LockFreeAddressHashSet::Node* node =
+ reinterpret_cast<LockFreeAddressHashSet::Node*>(set.buckets_[bucket]);
+ for (; node; node = set.next_node(node))
+ ++count;
+ return count;
+ }
+};
+
+namespace {
+
+TEST_F(LockFreeAddressHashSetTest, EmptySet) {
+ LockFreeAddressHashSet set(8);
+ EXPECT_EQ(size_t(0), set.size());
+ EXPECT_EQ(size_t(8), set.buckets_count());
+ EXPECT_EQ(0., set.load_factor());
+ EXPECT_FALSE(set.Contains(&set));
+}
+
+TEST_F(LockFreeAddressHashSetTest, BasicOperations) {
+ LockFreeAddressHashSet set(8);
+
+ for (size_t i = 1; i <= 100; ++i) {
+ void* ptr = reinterpret_cast<void*>(i);
+ set.Insert(ptr);
+ EXPECT_EQ(i, set.size());
+ EXPECT_TRUE(set.Contains(ptr));
+ }
+
+ size_t size = 100;
+ EXPECT_EQ(size, set.size());
+ EXPECT_EQ(size_t(8), set.buckets_count());
+ EXPECT_EQ(size / 8., set.load_factor());
+
+ for (size_t i = 99; i >= 3; i -= 3) {
+ void* ptr = reinterpret_cast<void*>(i);
+ set.Remove(ptr);
+ EXPECT_EQ(--size, set.size());
+ EXPECT_FALSE(set.Contains(ptr));
+ }
+ // Removed every 3rd value (33 total) from the set, 67 have left.
+ EXPECT_EQ(size_t(67), set.size());
+
+ for (size_t i = 1; i <= 100; ++i) {
+ void* ptr = reinterpret_cast<void*>(i);
+ EXPECT_EQ(i % 3 != 0, set.Contains(ptr));
+ }
+}
+
+TEST_F(LockFreeAddressHashSetTest, Copy) {
+ LockFreeAddressHashSet set(16);
+
+ for (size_t i = 1000; i <= 16000; i += 1000) {
+ void* ptr = reinterpret_cast<void*>(i);
+ set.Insert(ptr);
+ }
+
+ LockFreeAddressHashSet set2(4);
+ LockFreeAddressHashSet set3(64);
+ set2.Copy(set);
+ set3.Copy(set);
+
+ EXPECT_TRUE(Equals(set, set2));
+ EXPECT_TRUE(Equals(set, set3));
+ EXPECT_TRUE(Equals(set2, set3));
+
+ set.Insert(reinterpret_cast<void*>(42));
+
+ EXPECT_FALSE(Equals(set, set2));
+ EXPECT_FALSE(Equals(set, set3));
+ EXPECT_TRUE(Equals(set2, set3));
+
+ EXPECT_TRUE(Subset(set, set2));
+ EXPECT_FALSE(Subset(set2, set));
+}
+
+class WriterThread : public SimpleThread {
+ public:
+ WriterThread(LockFreeAddressHashSet* set, subtle::Atomic32* cancel)
+ : SimpleThread("ReaderThread"), set_(set), cancel_(cancel) {}
+
+ void Run() override {
+ for (size_t value = 42; !subtle::Acquire_Load(cancel_); ++value) {
+ void* ptr = reinterpret_cast<void*>(value);
+ set_->Insert(ptr);
+ EXPECT_TRUE(set_->Contains(ptr));
+ set_->Remove(ptr);
+ EXPECT_FALSE(set_->Contains(ptr));
+ }
+ // Leave a key for reader to test.
+ set_->Insert(reinterpret_cast<void*>(0x1337));
+ }
+
+ private:
+ LockFreeAddressHashSet* set_;
+ subtle::Atomic32* cancel_;
+};
+
+#if defined(THREAD_SANITIZER)
+#define DISABLE_ON_TSAN(test_name) DISABLED_##test_name
+#else
+#define DISABLE_ON_TSAN(test_name) test_name
+#endif // defined(THREAD_SANITIZER)
+TEST_F(LockFreeAddressHashSetTest, DISABLE_ON_TSAN(ConcurrentAccess)) {
+ // The purpose of this test is to make sure adding/removing keys concurrently
+ // does not disrupt the state of other keys.
+ LockFreeAddressHashSet set(16);
+ subtle::Atomic32 cancel = 0;
+ auto thread = std::make_unique<WriterThread>(&set, &cancel);
+ thread->Start();
+ for (size_t i = 1; i <= 20; ++i)
+ set.Insert(reinterpret_cast<void*>(i));
+ // Remove some items to test empty nodes.
+ for (size_t i = 16; i <= 20; ++i)
+ set.Remove(reinterpret_cast<void*>(i));
+ for (size_t k = 0; k < 100000; ++k) {
+ for (size_t i = 1; i <= 30; ++i) {
+ EXPECT_EQ(i < 16, set.Contains(reinterpret_cast<void*>(i)));
+ }
+ }
+ subtle::Release_Store(&cancel, 1);
+ thread->Join();
+
+ EXPECT_TRUE(set.Contains(reinterpret_cast<void*>(0x1337)));
+ EXPECT_FALSE(set.Contains(reinterpret_cast<void*>(0xbadf00d)));
+}
+
+TEST_F(LockFreeAddressHashSetTest, BucketsUsage) {
+ // Test the uniformity of buckets usage.
+ size_t count = 10000;
+ LockFreeAddressHashSet set(16);
+ for (size_t i = 0; i < count; ++i)
+ set.Insert(reinterpret_cast<void*>(0x10000 + 0x10 * i));
+ size_t average_per_bucket = count / set.buckets_count();
+ for (size_t i = 0; i < set.buckets_count(); ++i) {
+ size_t usage = BucketSize(set, i);
+ EXPECT_LT(average_per_bucket * 95 / 100, usage);
+ EXPECT_GT(average_per_bucket * 105 / 100, usage);
+ }
+}
+
+} // namespace
+} // namespace base
diff --git a/base/sampling_heap_profiler/sampling_heap_profiler.cc b/base/sampling_heap_profiler/sampling_heap_profiler.cc
new file mode 100644
index 0000000000..04c173725e
--- /dev/null
+++ b/base/sampling_heap_profiler/sampling_heap_profiler.cc
@@ -0,0 +1,481 @@
+// 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.
+
+#include "base/sampling_heap_profiler/sampling_heap_profiler.h"
+
+#include <algorithm>
+#include <cmath>
+#include <utility>
+
+#include "base/allocator/allocator_shim.h"
+#include "base/allocator/buildflags.h"
+#include "base/allocator/partition_allocator/partition_alloc.h"
+#include "base/atomicops.h"
+#include "base/debug/stack_trace.h"
+#include "base/macros.h"
+#include "base/no_destructor.h"
+#include "base/partition_alloc_buildflags.h"
+#include "base/rand_util.h"
+#include "base/sampling_heap_profiler/lock_free_address_hash_set.h"
+#include "base/threading/thread_local_storage.h"
+#include "build/build_config.h"
+
+#if defined(OS_ANDROID) && BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE) && \
+ defined(OFFICIAL_BUILD)
+#include "base/trace_event/cfi_backtrace_android.h"
+#endif
+
+namespace base {
+
+using base::allocator::AllocatorDispatch;
+using base::subtle::Atomic32;
+using base::subtle::AtomicWord;
+
+namespace {
+
+// Control how many top frames to skip when recording call stack.
+// These frames correspond to the profiler own frames.
+const uint32_t kSkipBaseAllocatorFrames = 2;
+
+const size_t kDefaultSamplingIntervalBytes = 128 * 1024;
+
+// Controls if sample intervals should not be randomized. Used for testing.
+bool g_deterministic;
+
+// A positive value if profiling is running, otherwise it's zero.
+Atomic32 g_running;
+
+// Pointer to the current |LockFreeAddressHashSet|.
+AtomicWord g_sampled_addresses_set;
+
+// Sampling interval parameter, the mean value for intervals between samples.
+AtomicWord g_sampling_interval = kDefaultSamplingIntervalBytes;
+
+void (*g_hooks_install_callback)();
+Atomic32 g_hooks_installed;
+
+void* AllocFn(const AllocatorDispatch* self, size_t size, void* context) {
+ void* address = self->next->alloc_function(self->next, size, context);
+ SamplingHeapProfiler::RecordAlloc(address, size, kSkipBaseAllocatorFrames);
+ return address;
+}
+
+void* AllocZeroInitializedFn(const AllocatorDispatch* self,
+ size_t n,
+ size_t size,
+ void* context) {
+ void* address =
+ self->next->alloc_zero_initialized_function(self->next, n, size, context);
+ SamplingHeapProfiler::RecordAlloc(address, n * size,
+ kSkipBaseAllocatorFrames);
+ return address;
+}
+
+void* AllocAlignedFn(const AllocatorDispatch* self,
+ size_t alignment,
+ size_t size,
+ void* context) {
+ void* address =
+ self->next->alloc_aligned_function(self->next, alignment, size, context);
+ SamplingHeapProfiler::RecordAlloc(address, size, kSkipBaseAllocatorFrames);
+ return address;
+}
+
+void* ReallocFn(const AllocatorDispatch* self,
+ void* address,
+ size_t size,
+ void* context) {
+ // Note: size == 0 actually performs free.
+ SamplingHeapProfiler::RecordFree(address);
+ address = self->next->realloc_function(self->next, address, size, context);
+ SamplingHeapProfiler::RecordAlloc(address, size, kSkipBaseAllocatorFrames);
+ return address;
+}
+
+void FreeFn(const AllocatorDispatch* self, void* address, void* context) {
+ SamplingHeapProfiler::RecordFree(address);
+ self->next->free_function(self->next, address, context);
+}
+
+size_t GetSizeEstimateFn(const AllocatorDispatch* self,
+ void* address,
+ void* context) {
+ return self->next->get_size_estimate_function(self->next, address, context);
+}
+
+unsigned BatchMallocFn(const AllocatorDispatch* self,
+ size_t size,
+ void** results,
+ unsigned num_requested,
+ void* context) {
+ unsigned num_allocated = self->next->batch_malloc_function(
+ self->next, size, results, num_requested, context);
+ for (unsigned i = 0; i < num_allocated; ++i) {
+ SamplingHeapProfiler::RecordAlloc(results[i], size,
+ kSkipBaseAllocatorFrames);
+ }
+ return num_allocated;
+}
+
+void BatchFreeFn(const AllocatorDispatch* self,
+ void** to_be_freed,
+ unsigned num_to_be_freed,
+ void* context) {
+ for (unsigned i = 0; i < num_to_be_freed; ++i)
+ SamplingHeapProfiler::RecordFree(to_be_freed[i]);
+ self->next->batch_free_function(self->next, to_be_freed, num_to_be_freed,
+ context);
+}
+
+void FreeDefiniteSizeFn(const AllocatorDispatch* self,
+ void* address,
+ size_t size,
+ void* context) {
+ SamplingHeapProfiler::RecordFree(address);
+ self->next->free_definite_size_function(self->next, address, size, context);
+}
+
+AllocatorDispatch g_allocator_dispatch = {&AllocFn,
+ &AllocZeroInitializedFn,
+ &AllocAlignedFn,
+ &ReallocFn,
+ &FreeFn,
+ &GetSizeEstimateFn,
+ &BatchMallocFn,
+ &BatchFreeFn,
+ &FreeDefiniteSizeFn,
+ nullptr};
+
+#if BUILDFLAG(USE_PARTITION_ALLOC) && !defined(OS_NACL)
+
+void PartitionAllocHook(void* address, size_t size, const char*) {
+ SamplingHeapProfiler::RecordAlloc(address, size);
+}
+
+void PartitionFreeHook(void* address) {
+ SamplingHeapProfiler::RecordFree(address);
+}
+
+#endif // BUILDFLAG(USE_PARTITION_ALLOC) && !defined(OS_NACL)
+
+ThreadLocalStorage::Slot& AccumulatedBytesTLS() {
+ static base::NoDestructor<base::ThreadLocalStorage::Slot>
+ accumulated_bytes_tls;
+ return *accumulated_bytes_tls;
+}
+
+} // namespace
+
+SamplingHeapProfiler::Sample::Sample(size_t size,
+ size_t total,
+ uint32_t ordinal)
+ : size(size), total(total), ordinal(ordinal) {}
+
+SamplingHeapProfiler::Sample::Sample(const Sample&) = default;
+
+SamplingHeapProfiler::Sample::~Sample() = default;
+
+SamplingHeapProfiler* SamplingHeapProfiler::instance_;
+
+SamplingHeapProfiler::SamplingHeapProfiler() {
+ instance_ = this;
+ auto sampled_addresses = std::make_unique<LockFreeAddressHashSet>(64);
+ base::subtle::NoBarrier_Store(
+ &g_sampled_addresses_set,
+ reinterpret_cast<AtomicWord>(sampled_addresses.get()));
+ sampled_addresses_stack_.push(std::move(sampled_addresses));
+}
+
+// static
+void SamplingHeapProfiler::InitTLSSlot() {
+ // Preallocate the TLS slot early, so it can't cause reentracy issues
+ // when sampling is started.
+ ignore_result(AccumulatedBytesTLS().Get());
+}
+
+// static
+void SamplingHeapProfiler::InstallAllocatorHooksOnce() {
+ static bool hook_installed = InstallAllocatorHooks();
+ ignore_result(hook_installed);
+}
+
+// static
+bool SamplingHeapProfiler::InstallAllocatorHooks() {
+#if BUILDFLAG(USE_ALLOCATOR_SHIM)
+ base::allocator::InsertAllocatorDispatch(&g_allocator_dispatch);
+#else
+ ignore_result(g_allocator_dispatch);
+ DLOG(WARNING)
+ << "base::allocator shims are not available for memory sampling.";
+#endif // BUILDFLAG(USE_ALLOCATOR_SHIM)
+
+#if BUILDFLAG(USE_PARTITION_ALLOC) && !defined(OS_NACL)
+ base::PartitionAllocHooks::SetAllocationHook(&PartitionAllocHook);
+ base::PartitionAllocHooks::SetFreeHook(&PartitionFreeHook);
+#endif // BUILDFLAG(USE_PARTITION_ALLOC) && !defined(OS_NACL)
+
+ int32_t hooks_install_callback_has_been_set =
+ base::subtle::Acquire_CompareAndSwap(&g_hooks_installed, 0, 1);
+ if (hooks_install_callback_has_been_set)
+ g_hooks_install_callback();
+
+ return true;
+}
+
+// static
+void SamplingHeapProfiler::SetHooksInstallCallback(
+ void (*hooks_install_callback)()) {
+ CHECK(!g_hooks_install_callback && hooks_install_callback);
+ g_hooks_install_callback = hooks_install_callback;
+
+ int32_t profiler_has_already_been_initialized =
+ base::subtle::Release_CompareAndSwap(&g_hooks_installed, 0, 1);
+ if (profiler_has_already_been_initialized)
+ g_hooks_install_callback();
+}
+
+uint32_t SamplingHeapProfiler::Start() {
+#if defined(OS_ANDROID) && BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE) && \
+ defined(OFFICIAL_BUILD)
+ if (!base::trace_event::CFIBacktraceAndroid::GetInitializedInstance()
+ ->can_unwind_stack_frames()) {
+ LOG(WARNING) << "Sampling heap profiler: Stack unwinding is not available.";
+ return 0;
+ }
+#endif
+ InstallAllocatorHooksOnce();
+ base::subtle::Barrier_AtomicIncrement(&g_running, 1);
+ return last_sample_ordinal_;
+}
+
+void SamplingHeapProfiler::Stop() {
+ AtomicWord count = base::subtle::Barrier_AtomicIncrement(&g_running, -1);
+ CHECK_GE(count, 0);
+}
+
+void SamplingHeapProfiler::SetSamplingInterval(size_t sampling_interval) {
+ // TODO(alph): Reset the sample being collected if running.
+ base::subtle::Release_Store(&g_sampling_interval,
+ static_cast<AtomicWord>(sampling_interval));
+}
+
+// static
+size_t SamplingHeapProfiler::GetNextSampleInterval(size_t interval) {
+ if (UNLIKELY(g_deterministic))
+ return interval;
+
+ // We sample with a Poisson process, with constant average sampling
+ // interval. This follows the exponential probability distribution with
+ // parameter λ = 1/interval where |interval| is the average number of bytes
+ // between samples.
+ // Let u be a uniformly distributed random number between 0 and 1, then
+ // next_sample = -ln(u) / λ
+ double uniform = base::RandDouble();
+ double value = -log(uniform) * interval;
+ size_t min_value = sizeof(intptr_t);
+ // We limit the upper bound of a sample interval to make sure we don't have
+ // huge gaps in the sampling stream. Probability of the upper bound gets hit
+ // is exp(-20) ~ 2e-9, so it should not skew the distibution.
+ size_t max_value = interval * 20;
+ if (UNLIKELY(value < min_value))
+ return min_value;
+ if (UNLIKELY(value > max_value))
+ return max_value;
+ return static_cast<size_t>(value);
+}
+
+// static
+void SamplingHeapProfiler::RecordAlloc(void* address,
+ size_t size,
+ uint32_t skip_frames) {
+ if (UNLIKELY(!base::subtle::NoBarrier_Load(&g_running)))
+ return;
+ if (UNLIKELY(base::ThreadLocalStorage::HasBeenDestroyed()))
+ return;
+
+ // TODO(alph): On MacOS it may call the hook several times for a single
+ // allocation. Handle the case.
+
+ intptr_t accumulated_bytes =
+ reinterpret_cast<intptr_t>(AccumulatedBytesTLS().Get());
+ accumulated_bytes += size;
+ if (LIKELY(accumulated_bytes < 0)) {
+ AccumulatedBytesTLS().Set(reinterpret_cast<void*>(accumulated_bytes));
+ return;
+ }
+
+ size_t mean_interval = base::subtle::NoBarrier_Load(&g_sampling_interval);
+ size_t samples = accumulated_bytes / mean_interval;
+ accumulated_bytes %= mean_interval;
+
+ do {
+ accumulated_bytes -= GetNextSampleInterval(mean_interval);
+ ++samples;
+ } while (accumulated_bytes >= 0);
+
+ AccumulatedBytesTLS().Set(reinterpret_cast<void*>(accumulated_bytes));
+
+ instance_->DoRecordAlloc(samples * mean_interval, size, address, skip_frames);
+}
+
+void SamplingHeapProfiler::RecordStackTrace(Sample* sample,
+ uint32_t skip_frames) {
+#if !defined(OS_NACL)
+ constexpr uint32_t kMaxStackEntries = 256;
+ constexpr uint32_t kSkipProfilerOwnFrames = 2;
+ skip_frames += kSkipProfilerOwnFrames;
+#if defined(OS_ANDROID) && BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE) && \
+ defined(OFFICIAL_BUILD)
+ const void* frames[kMaxStackEntries];
+ size_t frame_count =
+ base::trace_event::CFIBacktraceAndroid::GetInitializedInstance()->Unwind(
+ frames, kMaxStackEntries);
+#elif BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
+ const void* frames[kMaxStackEntries];
+ size_t frame_count = base::debug::TraceStackFramePointers(
+ frames, kMaxStackEntries, skip_frames);
+ skip_frames = 0;
+#else
+ // Fall-back to capturing the stack with base::debug::StackTrace,
+ // which is likely slower, but more reliable.
+ base::debug::StackTrace stack_trace(kMaxStackEntries);
+ size_t frame_count = 0;
+ const void* const* frames = stack_trace.Addresses(&frame_count);
+#endif
+
+ sample->stack.insert(
+ sample->stack.end(), const_cast<void**>(&frames[skip_frames]),
+ const_cast<void**>(&frames[std::max<size_t>(frame_count, skip_frames)]));
+#endif
+}
+
+void SamplingHeapProfiler::DoRecordAlloc(size_t total_allocated,
+ size_t size,
+ void* address,
+ uint32_t skip_frames) {
+ if (entered_.Get())
+ return;
+ entered_.Set(true);
+ {
+ base::AutoLock lock(mutex_);
+ Sample sample(size, total_allocated, ++last_sample_ordinal_);
+ RecordStackTrace(&sample, skip_frames);
+ for (auto* observer : observers_)
+ observer->SampleAdded(sample.ordinal, size, total_allocated);
+ samples_.emplace(address, std::move(sample));
+ // TODO(alph): Sometimes RecordAlloc is called twice in a row without
+ // a RecordFree in between. Investigate it.
+ if (!sampled_addresses_set().Contains(address))
+ sampled_addresses_set().Insert(address);
+ BalanceAddressesHashSet();
+ }
+ entered_.Set(false);
+}
+
+// static
+void SamplingHeapProfiler::RecordFree(void* address) {
+ if (UNLIKELY(address == nullptr))
+ return;
+ if (UNLIKELY(sampled_addresses_set().Contains(address)))
+ instance_->DoRecordFree(address);
+}
+
+void SamplingHeapProfiler::DoRecordFree(void* address) {
+ if (UNLIKELY(base::ThreadLocalStorage::HasBeenDestroyed()))
+ return;
+ if (entered_.Get())
+ return;
+ entered_.Set(true);
+ {
+ base::AutoLock lock(mutex_);
+ auto it = samples_.find(address);
+ CHECK(it != samples_.end());
+ for (auto* observer : observers_)
+ observer->SampleRemoved(it->second.ordinal);
+ samples_.erase(it);
+ sampled_addresses_set().Remove(address);
+ }
+ entered_.Set(false);
+}
+
+void SamplingHeapProfiler::BalanceAddressesHashSet() {
+ // Check if the load_factor of the current addresses hash set becomes higher
+ // than 1, allocate a new twice larger one, copy all the data,
+ // and switch to using it.
+ // During the copy process no other writes are made to both sets
+ // as it's behind the lock.
+ // All the readers continue to use the old one until the atomic switch
+ // process takes place.
+ LockFreeAddressHashSet& current_set = sampled_addresses_set();
+ if (current_set.load_factor() < 1)
+ return;
+ auto new_set =
+ std::make_unique<LockFreeAddressHashSet>(current_set.buckets_count() * 2);
+ new_set->Copy(current_set);
+ // Atomically switch all the new readers to the new set.
+ base::subtle::Release_Store(&g_sampled_addresses_set,
+ reinterpret_cast<AtomicWord>(new_set.get()));
+ // We still have to keep all the old maps alive to resolve the theoretical
+ // race with readers in |RecordFree| that have already obtained the map,
+ // but haven't yet managed to access it.
+ sampled_addresses_stack_.push(std::move(new_set));
+}
+
+// static
+LockFreeAddressHashSet& SamplingHeapProfiler::sampled_addresses_set() {
+ return *reinterpret_cast<LockFreeAddressHashSet*>(
+ base::subtle::NoBarrier_Load(&g_sampled_addresses_set));
+}
+
+// static
+SamplingHeapProfiler* SamplingHeapProfiler::GetInstance() {
+ static base::NoDestructor<SamplingHeapProfiler> instance;
+ return instance.get();
+}
+
+// static
+void SamplingHeapProfiler::SuppressRandomnessForTest(bool suppress) {
+ g_deterministic = suppress;
+}
+
+void SamplingHeapProfiler::AddSamplesObserver(SamplesObserver* observer) {
+ CHECK(!entered_.Get());
+ entered_.Set(true);
+ {
+ base::AutoLock lock(mutex_);
+ observers_.push_back(observer);
+ }
+ entered_.Set(false);
+}
+
+void SamplingHeapProfiler::RemoveSamplesObserver(SamplesObserver* observer) {
+ CHECK(!entered_.Get());
+ entered_.Set(true);
+ {
+ base::AutoLock lock(mutex_);
+ auto it = std::find(observers_.begin(), observers_.end(), observer);
+ CHECK(it != observers_.end());
+ observers_.erase(it);
+ }
+ entered_.Set(false);
+}
+
+std::vector<SamplingHeapProfiler::Sample> SamplingHeapProfiler::GetSamples(
+ uint32_t profile_id) {
+ CHECK(!entered_.Get());
+ entered_.Set(true);
+ std::vector<Sample> samples;
+ {
+ base::AutoLock lock(mutex_);
+ for (auto& it : samples_) {
+ Sample& sample = it.second;
+ if (sample.ordinal > profile_id)
+ samples.push_back(sample);
+ }
+ }
+ entered_.Set(false);
+ return samples;
+}
+
+} // namespace base
diff --git a/base/sampling_heap_profiler/sampling_heap_profiler.h b/base/sampling_heap_profiler/sampling_heap_profiler.h
new file mode 100644
index 0000000000..ea50e67a2d
--- /dev/null
+++ b/base/sampling_heap_profiler/sampling_heap_profiler.h
@@ -0,0 +1,119 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_SAMPLING_HEAP_PROFILER_SAMPLING_HEAP_PROFILER_H_
+#define BASE_SAMPLING_HEAP_PROFILER_SAMPLING_HEAP_PROFILER_H_
+
+#include <memory>
+#include <stack>
+#include <unordered_map>
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_local.h"
+
+namespace base {
+
+template <typename T>
+class NoDestructor;
+
+class LockFreeAddressHashSet;
+
+// The class implements sampling profiling of native memory heap.
+// It hooks on base::allocator and base::PartitionAlloc.
+// When started it selects and records allocation samples based on
+// the sampling_interval parameter.
+// The recorded samples can then be retrieved using GetSamples method.
+class BASE_EXPORT SamplingHeapProfiler {
+ public:
+ class BASE_EXPORT Sample {
+ public:
+ Sample(const Sample&);
+ ~Sample();
+
+ size_t size; // Allocation size.
+ size_t total; // Total size attributed to the sample.
+ std::vector<void*> stack;
+
+ private:
+ friend class SamplingHeapProfiler;
+
+ Sample(size_t, size_t total, uint32_t ordinal);
+
+ uint32_t ordinal;
+ };
+
+ class SamplesObserver {
+ public:
+ virtual ~SamplesObserver() = default;
+ virtual void SampleAdded(uint32_t id, size_t size, size_t total) = 0;
+ virtual void SampleRemoved(uint32_t id) = 0;
+ };
+
+ // Must be called early during the process initialization. It creates and
+ // reserves a TLS slot.
+ static void InitTLSSlot();
+
+ // This is an entry point for plugging in an external allocator.
+ // Profiler will invoke the provided callback upon initialization.
+ // The callback should install hooks onto the corresponding memory allocator
+ // and make them invoke SamplingHeapProfiler::RecordAlloc and
+ // SamplingHeapProfiler::RecordFree upon corresponding allocation events.
+ //
+ // If the method is called after profiler is initialized, the callback
+ // is invoked right away.
+ static void SetHooksInstallCallback(void (*hooks_install_callback)());
+
+ void AddSamplesObserver(SamplesObserver*);
+ void RemoveSamplesObserver(SamplesObserver*);
+
+ uint32_t Start();
+ void Stop();
+ void SetSamplingInterval(size_t sampling_interval);
+ void SuppressRandomnessForTest(bool suppress);
+
+ std::vector<Sample> GetSamples(uint32_t profile_id);
+
+ static void RecordAlloc(void* address, size_t, uint32_t skip_frames = 0);
+ static void RecordFree(void* address);
+
+ static SamplingHeapProfiler* GetInstance();
+
+ private:
+ SamplingHeapProfiler();
+ ~SamplingHeapProfiler() = delete;
+
+ static void InstallAllocatorHooksOnce();
+ static bool InstallAllocatorHooks();
+ static size_t GetNextSampleInterval(size_t base_interval);
+
+ void DoRecordAlloc(size_t total_allocated,
+ size_t allocation_size,
+ void* address,
+ uint32_t skip_frames);
+ void DoRecordFree(void* address);
+ void RecordStackTrace(Sample*, uint32_t skip_frames);
+ static LockFreeAddressHashSet& sampled_addresses_set();
+
+ void BalanceAddressesHashSet();
+
+ base::ThreadLocalBoolean entered_;
+ base::Lock mutex_;
+ std::stack<std::unique_ptr<LockFreeAddressHashSet>> sampled_addresses_stack_;
+ std::unordered_map<void*, Sample> samples_;
+ std::vector<SamplesObserver*> observers_;
+ uint32_t last_sample_ordinal_ = 1;
+
+ static SamplingHeapProfiler* instance_;
+
+ friend class base::NoDestructor<SamplingHeapProfiler>;
+
+ DISALLOW_COPY_AND_ASSIGN(SamplingHeapProfiler);
+};
+
+} // namespace base
+
+#endif // BASE_SAMPLING_HEAP_PROFILER_SAMPLING_HEAP_PROFILER_H_
diff --git a/base/sampling_heap_profiler/sampling_heap_profiler_unittest.cc b/base/sampling_heap_profiler/sampling_heap_profiler_unittest.cc
new file mode 100644
index 0000000000..6602e6c5a6
--- /dev/null
+++ b/base/sampling_heap_profiler/sampling_heap_profiler_unittest.cc
@@ -0,0 +1,165 @@
+// 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.
+
+#include "base/sampling_heap_profiler/sampling_heap_profiler.h"
+
+#include <stdlib.h>
+#include <cinttypes>
+
+#include "base/allocator/allocator_shim.h"
+#include "base/debug/alias.h"
+#include "base/threading/simple_thread.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+class SamplingHeapProfilerTest : public ::testing::Test {
+#if defined(OS_MACOSX)
+ void SetUp() override { allocator::InitializeAllocatorShim(); }
+#endif
+};
+
+class SamplesCollector : public SamplingHeapProfiler::SamplesObserver {
+ public:
+ explicit SamplesCollector(size_t watch_size) : watch_size_(watch_size) {}
+
+ void SampleAdded(uint32_t id, size_t size, size_t) override {
+ if (sample_added || size != watch_size_)
+ return;
+ sample_id_ = id;
+ sample_added = true;
+ }
+
+ void SampleRemoved(uint32_t id) override {
+ if (id == sample_id_)
+ sample_removed = true;
+ }
+
+ bool sample_added = false;
+ bool sample_removed = false;
+
+ private:
+ size_t watch_size_;
+ uint32_t sample_id_ = 0;
+};
+
+TEST_F(SamplingHeapProfilerTest, CollectSamples) {
+ SamplingHeapProfiler::InitTLSSlot();
+ SamplesCollector collector(10000);
+ SamplingHeapProfiler* profiler = SamplingHeapProfiler::GetInstance();
+ profiler->SuppressRandomnessForTest(true);
+ profiler->SetSamplingInterval(1024);
+ profiler->Start();
+ profiler->AddSamplesObserver(&collector);
+ void* volatile p = malloc(10000);
+ free(p);
+ profiler->Stop();
+ profiler->RemoveSamplesObserver(&collector);
+ CHECK(collector.sample_added);
+ CHECK(collector.sample_removed);
+}
+
+const int kNumberOfAllocations = 10000;
+
+NOINLINE void Allocate1() {
+ void* p = malloc(400);
+ base::debug::Alias(&p);
+}
+
+NOINLINE void Allocate2() {
+ void* p = malloc(700);
+ base::debug::Alias(&p);
+}
+
+NOINLINE void Allocate3() {
+ void* p = malloc(20480);
+ base::debug::Alias(&p);
+}
+
+class MyThread1 : public SimpleThread {
+ public:
+ MyThread1() : SimpleThread("MyThread1") {}
+ void Run() override {
+ for (int i = 0; i < kNumberOfAllocations; ++i)
+ Allocate1();
+ }
+};
+
+class MyThread2 : public SimpleThread {
+ public:
+ MyThread2() : SimpleThread("MyThread2") {}
+ void Run() override {
+ for (int i = 0; i < kNumberOfAllocations; ++i)
+ Allocate2();
+ }
+};
+
+void CheckAllocationPattern(void (*allocate_callback)()) {
+ SamplingHeapProfiler::InitTLSSlot();
+ SamplingHeapProfiler* profiler = SamplingHeapProfiler::GetInstance();
+ profiler->SuppressRandomnessForTest(false);
+ profiler->SetSamplingInterval(10240);
+ base::TimeTicks t0 = base::TimeTicks::Now();
+ std::map<size_t, size_t> sums;
+ const int iterations = 40;
+ for (int i = 0; i < iterations; ++i) {
+ uint32_t id = profiler->Start();
+ allocate_callback();
+ std::vector<SamplingHeapProfiler::Sample> samples =
+ profiler->GetSamples(id);
+ profiler->Stop();
+ std::map<size_t, size_t> buckets;
+ for (auto& sample : samples) {
+ buckets[sample.size] += sample.total;
+ }
+ for (auto& it : buckets) {
+ if (it.first != 400 && it.first != 700 && it.first != 20480)
+ continue;
+ sums[it.first] += it.second;
+ printf("%zu,", it.second);
+ }
+ printf("\n");
+ }
+
+ printf("Time taken %" PRIu64 "ms\n",
+ (base::TimeTicks::Now() - t0).InMilliseconds());
+
+ for (auto sum : sums) {
+ intptr_t expected = sum.first * kNumberOfAllocations;
+ intptr_t actual = sum.second / iterations;
+ printf("%zu:\tmean: %zu\trelative error: %.2f%%\n", sum.first, actual,
+ 100. * (actual - expected) / expected);
+ }
+}
+
+// Manual tests to check precision of the sampling profiler.
+// Yes, they do leak lots of memory.
+
+TEST_F(SamplingHeapProfilerTest, DISABLED_ParallelLargeSmallStats) {
+ CheckAllocationPattern([]() {
+ SimpleThread* t1 = new MyThread1();
+ SimpleThread* t2 = new MyThread2();
+ t1->Start();
+ t2->Start();
+ for (int i = 0; i < kNumberOfAllocations; ++i)
+ Allocate3();
+ t1->Join();
+ t2->Join();
+ });
+}
+
+TEST_F(SamplingHeapProfilerTest, DISABLED_SequentialLargeSmallStats) {
+ CheckAllocationPattern([]() {
+ for (int i = 0; i < kNumberOfAllocations; ++i) {
+ Allocate1();
+ Allocate2();
+ Allocate3();
+ }
+ });
+}
+
+} // namespace
+} // namespace base
diff --git a/base/scoped_generic.h b/base/scoped_generic.h
index c2d51cfdb4..25e620859f 100644
--- a/base/scoped_generic.h
+++ b/base/scoped_generic.h
@@ -124,6 +124,13 @@ class ScopedGeneric {
return old_generic;
}
+ // Returns a raw pointer to the object storage, to allow the scoper to be used
+ // to receive and manage out-parameter values. Implies reset().
+ element_type* receive() WARN_UNUSED_RESULT {
+ reset();
+ return &data_.generic;
+ }
+
const element_type& get() const { return data_.generic; }
// Returns true if this object doesn't hold the special null value for the
diff --git a/base/scoped_native_library.cc b/base/scoped_native_library.cc
new file mode 100644
index 0000000000..c94f262603
--- /dev/null
+++ b/base/scoped_native_library.cc
@@ -0,0 +1,43 @@
+// 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/scoped_native_library.h"
+
+namespace base {
+
+ScopedNativeLibrary::ScopedNativeLibrary() : library_(nullptr) {}
+
+ScopedNativeLibrary::ScopedNativeLibrary(NativeLibrary library)
+ : library_(library) {
+}
+
+ScopedNativeLibrary::ScopedNativeLibrary(const FilePath& library_path) {
+ library_ = base::LoadNativeLibrary(library_path, nullptr);
+}
+
+ScopedNativeLibrary::~ScopedNativeLibrary() {
+ if (library_)
+ base::UnloadNativeLibrary(library_);
+}
+
+void* ScopedNativeLibrary::GetFunctionPointer(
+ const char* function_name) const {
+ if (!library_)
+ return nullptr;
+ return base::GetFunctionPointerFromNativeLibrary(library_, function_name);
+}
+
+void ScopedNativeLibrary::Reset(NativeLibrary library) {
+ if (library_)
+ base::UnloadNativeLibrary(library_);
+ library_ = library;
+}
+
+NativeLibrary ScopedNativeLibrary::Release() {
+ NativeLibrary result = library_;
+ library_ = nullptr;
+ return result;
+}
+
+} // namespace base
diff --git a/base/scoped_native_library.h b/base/scoped_native_library.h
new file mode 100644
index 0000000000..e58297b27c
--- /dev/null
+++ b/base/scoped_native_library.h
@@ -0,0 +1,55 @@
+// 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_SCOPED_NATIVE_LIBRARY_H_
+#define BASE_SCOPED_NATIVE_LIBRARY_H_
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/native_library.h"
+
+namespace base {
+
+class FilePath;
+
+// A class which encapsulates a base::NativeLibrary object available only in a
+// scope.
+// This class automatically unloads the loaded library in its destructor.
+class BASE_EXPORT ScopedNativeLibrary {
+ public:
+ // Initializes with a NULL library.
+ ScopedNativeLibrary();
+
+ // Takes ownership of the given library handle.
+ explicit ScopedNativeLibrary(NativeLibrary library);
+
+ // Opens the given library and manages its lifetime.
+ explicit ScopedNativeLibrary(const FilePath& library_path);
+
+ ~ScopedNativeLibrary();
+
+ // Returns true if there's a valid library loaded.
+ bool is_valid() const { return !!library_; }
+
+ NativeLibrary get() const { return library_; }
+
+ void* GetFunctionPointer(const char* function_name) const;
+
+ // Takes ownership of the given library handle. Any existing handle will
+ // be freed.
+ void Reset(NativeLibrary library);
+
+ // Returns the native library handle and removes it from this object. The
+ // caller must manage the lifetime of the handle.
+ NativeLibrary Release();
+
+ private:
+ NativeLibrary library_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedNativeLibrary);
+};
+
+} // namespace base
+
+#endif // BASE_SCOPED_NATIVE_LIBRARY_H_
diff --git a/base/security_unittest.cc b/base/security_unittest.cc
index 519c997eb0..13e9594e2d 100644
--- a/base/security_unittest.cc
+++ b/base/security_unittest.cc
@@ -14,6 +14,7 @@
#include <limits>
#include <memory>
+#include "base/allocator/buildflags.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/free_deleter.h"
@@ -44,31 +45,15 @@ NOINLINE Type HideValueFromCompiler(volatile Type value) {
return value;
}
-// Tcmalloc and Windows allocator shim support setting malloc limits.
+// TCmalloc, currently supported only by Linux/CrOS, supports malloc limits.
// - NO_TCMALLOC (should be defined if compiled with use_allocator!="tcmalloc")
-// - ADDRESS_SANITIZER and SYZYASAN because they have their own memory allocator
-// - IOS does not use tcmalloc
-// - OS_MACOSX does not use tcmalloc
-// - Windows allocator shim defines ALLOCATOR_SHIM
-#if (!defined(NO_TCMALLOC) || defined(ALLOCATOR_SHIM)) && \
- !defined(ADDRESS_SANITIZER) && !defined(OS_IOS) && !defined(OS_MACOSX) && \
- !defined(SYZYASAN)
+// - ADDRESS_SANITIZER it has its own memory allocator
+#if defined(OS_LINUX) && !defined(NO_TCMALLOC) && !defined(ADDRESS_SANITIZER)
#define MALLOC_OVERFLOW_TEST(function) function
#else
#define MALLOC_OVERFLOW_TEST(function) DISABLED_##function
#endif
-#if defined(OS_LINUX) && defined(__x86_64__)
-// Detect runtime TCMalloc bypasses.
-bool IsTcMallocBypassed() {
- // This should detect a TCMalloc bypass from Valgrind.
- char* g_slice = getenv("G_SLICE");
- if (g_slice && !strcmp(g_slice, "always-malloc"))
- return true;
- return false;
-}
-#endif
-
// There are platforms where these tests are known to fail. We would like to
// be able to easily check the status on the bots, but marking tests as
// FAILS_ is too clunky.
@@ -87,7 +72,7 @@ void OverflowTestsSoftExpectTrue(bool overflow_detected) {
}
}
-#if defined(OS_IOS) || defined(ADDRESS_SANITIZER) || \
+#if defined(OS_IOS) || defined(OS_FUCHSIA) || defined(ADDRESS_SANITIZER) || \
defined(THREAD_SANITIZER) || defined(MEMORY_SANITIZER)
#define MAYBE_NewOverflow DISABLED_NewOverflow
#else
@@ -95,6 +80,8 @@ void OverflowTestsSoftExpectTrue(bool overflow_detected) {
#endif
// Test array[TooBig][X] and array[X][TooBig] allocations for int overflows.
// IOS doesn't honor nothrow, so disable the test there.
+// TODO(https://crbug.com/828229): Fuchsia SDK exports an incorrect new[] that
+// gets picked up in Debug/component builds, breaking this test.
// Disabled under XSan because asan aborts when new returns nullptr,
// https://bugs.chromium.org/p/chromium/issues/detail?id=690271#c15
TEST(SecurityTest, MAYBE_NewOverflow) {
@@ -137,8 +124,6 @@ bool ArePointersToSameArea(void* ptr1, void* ptr2, size_t size) {
// Check if TCMalloc uses an underlying random memory allocator.
TEST(SecurityTest, MALLOC_OVERFLOW_TEST(RandomMemoryAllocations)) {
- if (IsTcMallocBypassed())
- return;
size_t kPageSize = 4096; // We support x86_64 only.
// Check that malloc() returns an address that is neither the kernel's
// un-hinted mmap area, nor the current brk() area. The first malloc() may
@@ -146,20 +131,20 @@ TEST(SecurityTest, MALLOC_OVERFLOW_TEST(RandomMemoryAllocations)) {
// that it has allocated early on, before starting the sophisticated
// allocators.
void* default_mmap_heap_address =
- mmap(0, kPageSize, PROT_READ|PROT_WRITE,
- MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+ mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(default_mmap_heap_address,
static_cast<void*>(MAP_FAILED));
ASSERT_EQ(munmap(default_mmap_heap_address, kPageSize), 0);
void* brk_heap_address = sbrk(0);
ASSERT_NE(brk_heap_address, reinterpret_cast<void*>(-1));
- ASSERT_TRUE(brk_heap_address != NULL);
+ ASSERT_TRUE(brk_heap_address != nullptr);
// 1 MB should get us past what TCMalloc pre-allocated before initializing
// the sophisticated allocators.
size_t kAllocSize = 1<<20;
std::unique_ptr<char, base::FreeDeleter> ptr(
static_cast<char*>(malloc(kAllocSize)));
- ASSERT_TRUE(ptr != NULL);
+ ASSERT_TRUE(ptr != nullptr);
// If two pointers are separated by less than 512MB, they are considered
// to be in the same area.
// Our random pointer could be anywhere within 0x3fffffffffff (46bits),
diff --git a/base/sequence_checker.h b/base/sequence_checker.h
index 471631844b..48b593bb0b 100644
--- a/base/sequence_checker.h
+++ b/base/sequence_checker.h
@@ -5,40 +5,76 @@
#ifndef BASE_SEQUENCE_CHECKER_H_
#define BASE_SEQUENCE_CHECKER_H_
+#include "base/compiler_specific.h"
+#include "base/logging.h"
#include "base/sequence_checker_impl.h"
+// SequenceChecker is a helper class used to help verify that some methods of a
+// class are called sequentially (for thread-safety).
+//
+// Use the macros below instead of the SequenceChecker directly so that the
+// unused member doesn't result in an extra byte (four when padded) per
+// instance in production.
+//
+// This class is much prefered to ThreadChecker for thread-safety checks.
+// ThreadChecker should only be used for classes that are truly thread-affine
+// (use thread-local-storage or a third-party API that does).
+//
+// Usage:
+// class MyClass {
+// public:
+// MyClass() {
+// // It's sometimes useful to detach on construction for objects that are
+// // constructed in one place and forever after used from another
+// // sequence.
+// DETACH_FROM_SEQUENCE(my_sequence_checker_);
+// }
+//
+// ~MyClass() {
+// // SequenceChecker doesn't automatically check it's destroyed on origin
+// // sequence for the same reason it's sometimes detached in the
+// // constructor. It's okay to destroy off sequence if the owner
+// // otherwise knows usage on the associated sequence is done. If you're
+// // not detaching in the constructor, you probably want to explicitly
+// // check in the destructor.
+// DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+// }
+// void MyMethod() {
+// DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+// ... (do stuff) ...
+// }
+//
+// private:
+// SEQUENCE_CHECKER(my_sequence_checker_);
+// }
+
+#if DCHECK_IS_ON()
+#define SEQUENCE_CHECKER(name) base::SequenceChecker name
+#define DCHECK_CALLED_ON_VALID_SEQUENCE(name) \
+ DCHECK((name).CalledOnValidSequence())
+#define DETACH_FROM_SEQUENCE(name) (name).DetachFromSequence()
+#else // DCHECK_IS_ON()
+#define SEQUENCE_CHECKER(name)
+#define DCHECK_CALLED_ON_VALID_SEQUENCE(name) EAT_STREAM_PARAMETERS
+#define DETACH_FROM_SEQUENCE(name)
+#endif // DCHECK_IS_ON()
+
namespace base {
// Do nothing implementation, for use in release mode.
//
-// Note: You should almost always use the SequenceChecker class to get
-// the right version for your build configuration.
+// Note: You should almost always use the SequenceChecker class (through the
+// above macros) to get the right version for your build configuration.
class SequenceCheckerDoNothing {
public:
- bool CalledOnValidSequence() const { return true; }
-
+ SequenceCheckerDoNothing() = default;
+ bool CalledOnValidSequence() const WARN_UNUSED_RESULT { return true; }
void DetachFromSequence() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SequenceCheckerDoNothing);
};
-// SequenceChecker is a helper class to verify that calls to some methods of a
-// class are sequenced. Calls are sequenced when they are issued:
-// - From tasks posted to SequencedTaskRunners or SingleThreadTaskRunners bound
-// to the same sequence, or,
-// - From a single thread outside of any task.
-//
-// Example:
-// class MyClass {
-// public:
-// void Foo() {
-// DCHECK(sequence_checker_.CalledOnValidSequence());
-// ... (do stuff) ...
-// }
-//
-// private:
-// SequenceChecker sequence_checker_;
-// }
-//
-// In Release mode, CalledOnValidSequence() will always return true.
#if DCHECK_IS_ON()
class SequenceChecker : public SequenceCheckerImpl {
};
diff --git a/base/sequence_checker_impl.cc b/base/sequence_checker_impl.cc
index 6a9b5b2d0f..daa774b0f9 100644
--- a/base/sequence_checker_impl.cc
+++ b/base/sequence_checker_impl.cc
@@ -7,22 +7,13 @@
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/sequence_token.h"
-#include "base/threading/sequenced_worker_pool.h"
#include "base/threading/thread_checker_impl.h"
namespace base {
class SequenceCheckerImpl::Core {
public:
- Core()
- : sequence_token_(SequenceToken::GetForCurrentThread()),
- sequenced_worker_pool_token_(
- SequencedWorkerPool::GetSequenceTokenForCurrentThread()) {
- // SequencedWorkerPool doesn't use SequenceToken and code outside of
- // SequenceWorkerPool doesn't set a SequencedWorkerPool token.
- DCHECK(!sequence_token_.IsValid() ||
- !sequenced_worker_pool_token_.IsValid());
- }
+ Core() : sequence_token_(SequenceToken::GetForCurrentThread()) {}
~Core() = default;
@@ -30,11 +21,6 @@ class SequenceCheckerImpl::Core {
if (sequence_token_.IsValid())
return sequence_token_ == SequenceToken::GetForCurrentThread();
- if (sequenced_worker_pool_token_.IsValid()) {
- return sequenced_worker_pool_token_.Equals(
- SequencedWorkerPool::GetSequenceTokenForCurrentThread());
- }
-
// SequenceChecker behaves as a ThreadChecker when it is not bound to a
// valid sequence token.
return thread_checker_.CalledOnValidThread();
@@ -43,21 +29,17 @@ class SequenceCheckerImpl::Core {
private:
SequenceToken sequence_token_;
- // TODO(gab): Remove this when SequencedWorkerPool is deprecated in favor of
- // TaskScheduler. crbug.com/622400
- SequencedWorkerPool::SequenceToken sequenced_worker_pool_token_;
-
- // Used when |sequenced_worker_pool_token_| and |sequence_token_| are invalid.
+ // Used when |sequence_token_| is invalid.
ThreadCheckerImpl thread_checker_;
};
-SequenceCheckerImpl::SequenceCheckerImpl() : core_(MakeUnique<Core>()) {}
+SequenceCheckerImpl::SequenceCheckerImpl() : core_(std::make_unique<Core>()) {}
SequenceCheckerImpl::~SequenceCheckerImpl() = default;
bool SequenceCheckerImpl::CalledOnValidSequence() const {
AutoLock auto_lock(lock_);
if (!core_)
- core_ = MakeUnique<Core>();
+ core_ = std::make_unique<Core>();
return core_->CalledOnValidSequence();
}
diff --git a/base/sequence_checker_unittest.cc b/base/sequence_checker_unittest.cc
index 86e9298d97..8d44f3e41b 100644
--- a/base/sequence_checker_unittest.cc
+++ b/base/sequence_checker_unittest.cc
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "base/sequence_checker.h"
+
#include <stddef.h>
#include <memory>
@@ -11,11 +13,9 @@
#include "base/bind_helpers.h"
#include "base/callback_forward.h"
#include "base/macros.h"
-#include "base/message_loop/message_loop.h"
-#include "base/sequence_checker_impl.h"
#include "base/sequence_token.h"
#include "base/single_thread_task_runner.h"
-#include "base/test/sequenced_worker_pool_owner.h"
+#include "base/test/gtest_util.h"
#include "base/threading/simple_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -23,8 +23,6 @@ namespace base {
namespace {
-constexpr size_t kNumWorkerThreads = 3;
-
// Runs a callback on another thread.
class RunCallbackThread : public SimpleThread {
public:
@@ -43,27 +41,6 @@ class RunCallbackThread : public SimpleThread {
DISALLOW_COPY_AND_ASSIGN(RunCallbackThread);
};
-class SequenceCheckerTest : public testing::Test {
- protected:
- SequenceCheckerTest() : pool_owner_(kNumWorkerThreads, "test") {}
-
- void PostToSequencedWorkerPool(const Closure& callback,
- const std::string& token_name) {
- pool_owner_.pool()->PostNamedSequencedWorkerTask(token_name, FROM_HERE,
- callback);
- }
-
- void FlushSequencedWorkerPoolForTesting() {
- pool_owner_.pool()->FlushForTesting();
- }
-
- private:
- MessageLoop message_loop_; // Needed by SequencedWorkerPool to function.
- SequencedWorkerPoolOwner pool_owner_;
-
- DISALLOW_COPY_AND_ASSIGN(SequenceCheckerTest);
-};
-
void ExpectCalledOnValidSequence(SequenceCheckerImpl* sequence_checker) {
ASSERT_TRUE(sequence_checker);
@@ -91,25 +68,25 @@ void ExpectNotCalledOnValidSequence(SequenceCheckerImpl* sequence_checker) {
} // namespace
-TEST_F(SequenceCheckerTest, CallsAllowedOnSameThreadNoSequenceToken) {
+TEST(SequenceCheckerTest, CallsAllowedOnSameThreadNoSequenceToken) {
SequenceCheckerImpl sequence_checker;
EXPECT_TRUE(sequence_checker.CalledOnValidSequence());
}
-TEST_F(SequenceCheckerTest, CallsAllowedOnSameThreadSameSequenceToken) {
+TEST(SequenceCheckerTest, CallsAllowedOnSameThreadSameSequenceToken) {
ScopedSetSequenceTokenForCurrentThread
scoped_set_sequence_token_for_current_thread(SequenceToken::Create());
SequenceCheckerImpl sequence_checker;
EXPECT_TRUE(sequence_checker.CalledOnValidSequence());
}
-TEST_F(SequenceCheckerTest, CallsDisallowedOnDifferentThreadsNoSequenceToken) {
+TEST(SequenceCheckerTest, CallsDisallowedOnDifferentThreadsNoSequenceToken) {
SequenceCheckerImpl sequence_checker;
RunCallbackThread thread(
Bind(&ExpectNotCalledOnValidSequence, Unretained(&sequence_checker)));
}
-TEST_F(SequenceCheckerTest, CallsAllowedOnDifferentThreadsSameSequenceToken) {
+TEST(SequenceCheckerTest, CallsAllowedOnDifferentThreadsSameSequenceToken) {
const SequenceToken sequence_token(SequenceToken::Create());
ScopedSetSequenceTokenForCurrentThread
@@ -121,7 +98,7 @@ TEST_F(SequenceCheckerTest, CallsAllowedOnDifferentThreadsSameSequenceToken) {
Unretained(&sequence_checker), sequence_token));
}
-TEST_F(SequenceCheckerTest, CallsDisallowedOnSameThreadDifferentSequenceToken) {
+TEST(SequenceCheckerTest, CallsDisallowedOnSameThreadDifferentSequenceToken) {
std::unique_ptr<SequenceCheckerImpl> sequence_checker;
{
@@ -141,7 +118,7 @@ TEST_F(SequenceCheckerTest, CallsDisallowedOnSameThreadDifferentSequenceToken) {
EXPECT_FALSE(sequence_checker->CalledOnValidSequence());
}
-TEST_F(SequenceCheckerTest, DetachFromSequence) {
+TEST(SequenceCheckerTest, DetachFromSequence) {
std::unique_ptr<SequenceCheckerImpl> sequence_checker;
{
@@ -161,7 +138,7 @@ TEST_F(SequenceCheckerTest, DetachFromSequence) {
}
}
-TEST_F(SequenceCheckerTest, DetachFromSequenceNoSequenceToken) {
+TEST(SequenceCheckerTest, DetachFromSequenceNoSequenceToken) {
SequenceCheckerImpl sequence_checker;
sequence_checker.DetachFromSequence();
@@ -173,86 +150,32 @@ TEST_F(SequenceCheckerTest, DetachFromSequenceNoSequenceToken) {
EXPECT_FALSE(sequence_checker.CalledOnValidSequence());
}
-TEST_F(SequenceCheckerTest, SequencedWorkerPool_SameSequenceTokenValid) {
- SequenceCheckerImpl sequence_checker;
- sequence_checker.DetachFromSequence();
-
- PostToSequencedWorkerPool(
- Bind(&ExpectCalledOnValidSequence, Unretained(&sequence_checker)), "A");
- PostToSequencedWorkerPool(
- Bind(&ExpectCalledOnValidSequence, Unretained(&sequence_checker)), "A");
- FlushSequencedWorkerPoolForTesting();
-}
-
-TEST_F(SequenceCheckerTest, SequencedWorkerPool_DetachSequenceTokenValid) {
- SequenceCheckerImpl sequence_checker;
- sequence_checker.DetachFromSequence();
-
- PostToSequencedWorkerPool(
- Bind(&ExpectCalledOnValidSequence, Unretained(&sequence_checker)), "A");
- PostToSequencedWorkerPool(
- Bind(&ExpectCalledOnValidSequence, Unretained(&sequence_checker)), "A");
- FlushSequencedWorkerPoolForTesting();
-
- sequence_checker.DetachFromSequence();
-
- PostToSequencedWorkerPool(
- Bind(&ExpectCalledOnValidSequence, Unretained(&sequence_checker)), "B");
- PostToSequencedWorkerPool(
- Bind(&ExpectCalledOnValidSequence, Unretained(&sequence_checker)), "B");
- FlushSequencedWorkerPoolForTesting();
-}
-
-TEST_F(SequenceCheckerTest,
- SequencedWorkerPool_DifferentSequenceTokensInvalid) {
- SequenceCheckerImpl sequence_checker;
- sequence_checker.DetachFromSequence();
+TEST(SequenceCheckerMacroTest, Macros) {
+ auto scope = std::make_unique<ScopedSetSequenceTokenForCurrentThread>(
+ SequenceToken::Create());
+ SEQUENCE_CHECKER(my_sequence_checker);
- PostToSequencedWorkerPool(
- Bind(&ExpectCalledOnValidSequence, Unretained(&sequence_checker)), "A");
- PostToSequencedWorkerPool(
- Bind(&ExpectCalledOnValidSequence, Unretained(&sequence_checker)), "A");
- FlushSequencedWorkerPoolForTesting();
-
- PostToSequencedWorkerPool(
- Bind(&ExpectNotCalledOnValidSequence, Unretained(&sequence_checker)),
- "B");
- PostToSequencedWorkerPool(
- Bind(&ExpectNotCalledOnValidSequence, Unretained(&sequence_checker)),
- "B");
- FlushSequencedWorkerPoolForTesting();
-}
-
-TEST_F(SequenceCheckerTest,
- SequencedWorkerPool_WorkerPoolAndSimpleThreadInvalid) {
- SequenceCheckerImpl sequence_checker;
- sequence_checker.DetachFromSequence();
+ // Don't expect a DCHECK death when a SequenceChecker is used on the right
+ // sequence.
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker) << "Error message.";
- PostToSequencedWorkerPool(
- Bind(&ExpectCalledOnValidSequence, Unretained(&sequence_checker)), "A");
- PostToSequencedWorkerPool(
- Bind(&ExpectCalledOnValidSequence, Unretained(&sequence_checker)), "A");
- FlushSequencedWorkerPoolForTesting();
+ scope.reset();
- EXPECT_FALSE(sequence_checker.CalledOnValidSequence());
-}
+#if DCHECK_IS_ON()
+ // Expect DCHECK death when used on a different sequence.
+ EXPECT_DCHECK_DEATH({
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker) << "Error message.";
+ });
+#else
+ // Happily no-ops on non-dcheck builds.
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker) << "Error message.";
+#endif
-TEST_F(SequenceCheckerTest,
- SequencedWorkerPool_TwoDifferentWorkerPoolsInvalid) {
- SequenceCheckerImpl sequence_checker;
- sequence_checker.DetachFromSequence();
+ DETACH_FROM_SEQUENCE(my_sequence_checker);
- PostToSequencedWorkerPool(
- Bind(&ExpectCalledOnValidSequence, Unretained(&sequence_checker)), "A");
- PostToSequencedWorkerPool(
- Bind(&ExpectCalledOnValidSequence, Unretained(&sequence_checker)), "A");
- FlushSequencedWorkerPoolForTesting();
-
- SequencedWorkerPoolOwner second_pool_owner(kNumWorkerThreads, "test2");
- second_pool_owner.pool()->PostNamedSequencedWorkerTask(
- "A", FROM_HERE, base::Bind(&ExpectNotCalledOnValidSequence,
- base::Unretained(&sequence_checker)));
- second_pool_owner.pool()->FlushForTesting();
+ // Don't expect a DCHECK death when a SequenceChecker is used for the first
+ // time after having been detached.
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker) << "Error message.";
}
} // namespace base
diff --git a/base/sequence_token.cc b/base/sequence_token.cc
index 264e3b65e3..0bf2b44a44 100644
--- a/base/sequence_token.cc
+++ b/base/sequence_token.cc
@@ -13,9 +13,9 @@ namespace base {
namespace {
-base::StaticAtomicSequenceNumber g_sequence_token_generator;
+base::AtomicSequenceNumber g_sequence_token_generator;
-base::StaticAtomicSequenceNumber g_task_token_generator;
+base::AtomicSequenceNumber g_task_token_generator;
LazyInstance<ThreadLocalPointer<const SequenceToken>>::Leaky
tls_current_sequence_token = LAZY_INSTANCE_INITIALIZER;
diff --git a/base/sequence_token_unittest.cc b/base/sequence_token_unittest.cc
index b0e69de42b..2ed6878058 100644
--- a/base/sequence_token_unittest.cc
+++ b/base/sequence_token_unittest.cc
@@ -14,8 +14,8 @@ TEST(SequenceTokenTest, IsValid) {
}
TEST(SequenceTokenTest, OperatorEquals) {
- SequenceToken invalid_a;
- SequenceToken invalid_b;
+ const SequenceToken invalid_a;
+ const SequenceToken invalid_b;
const SequenceToken valid_a = SequenceToken::Create();
const SequenceToken valid_b = SequenceToken::Create();
@@ -31,8 +31,8 @@ TEST(SequenceTokenTest, OperatorEquals) {
}
TEST(SequenceTokenTest, OperatorNotEquals) {
- SequenceToken invalid_a;
- SequenceToken invalid_b;
+ const SequenceToken invalid_a;
+ const SequenceToken invalid_b;
const SequenceToken valid_a = SequenceToken::Create();
const SequenceToken valid_b = SequenceToken::Create();
diff --git a/base/sequenced_task_runner.cc b/base/sequenced_task_runner.cc
index 4c367cb927..86771c67b5 100644
--- a/base/sequenced_task_runner.cc
+++ b/base/sequenced_task_runner.cc
@@ -10,18 +10,17 @@
namespace base {
-bool SequencedTaskRunner::PostNonNestableTask(
- const tracked_objects::Location& from_here,
- OnceClosure task) {
+bool SequencedTaskRunner::PostNonNestableTask(const Location& from_here,
+ OnceClosure task) {
return PostNonNestableDelayedTask(from_here, std::move(task),
base::TimeDelta());
}
bool SequencedTaskRunner::DeleteOrReleaseSoonInternal(
- const tracked_objects::Location& from_here,
+ const Location& from_here,
void (*deleter)(const void*),
const void* object) {
- return PostNonNestableTask(from_here, Bind(deleter, object));
+ return PostNonNestableTask(from_here, BindOnce(deleter, object));
}
OnTaskRunnerDeleter::OnTaskRunnerDeleter(
@@ -29,8 +28,7 @@ OnTaskRunnerDeleter::OnTaskRunnerDeleter(
: task_runner_(std::move(task_runner)) {
}
-OnTaskRunnerDeleter::~OnTaskRunnerDeleter() {
-}
+OnTaskRunnerDeleter::~OnTaskRunnerDeleter() = default;
OnTaskRunnerDeleter::OnTaskRunnerDeleter(OnTaskRunnerDeleter&&) = default;
diff --git a/base/sequenced_task_runner.h b/base/sequenced_task_runner.h
index b29153927e..53d21ad166 100644
--- a/base/sequenced_task_runner.h
+++ b/base/sequenced_task_runner.h
@@ -5,6 +5,8 @@
#ifndef BASE_SEQUENCED_TASK_RUNNER_H_
#define BASE_SEQUENCED_TASK_RUNNER_H_
+#include <memory>
+
#include "base/base_export.h"
#include "base/callback.h"
#include "base/sequenced_task_runner_helpers.h"
@@ -109,43 +111,49 @@ class BASE_EXPORT SequencedTaskRunner : public TaskRunner {
// TODO(akalin): Get rid of the boolean return value for the methods
// below.
- bool PostNonNestableTask(const tracked_objects::Location& from_here,
- OnceClosure task);
+ bool PostNonNestableTask(const Location& from_here, OnceClosure task);
- virtual bool PostNonNestableDelayedTask(
- const tracked_objects::Location& from_here,
- OnceClosure task,
- base::TimeDelta delay) = 0;
+ virtual bool PostNonNestableDelayedTask(const Location& from_here,
+ OnceClosure task,
+ base::TimeDelta delay) = 0;
// Submits a non-nestable task to delete the given object. Returns
// true if the object may be deleted at some point in the future,
// and false if the object definitely will not be deleted.
template <class T>
- bool DeleteSoon(const tracked_objects::Location& from_here,
- const T* object) {
+ bool DeleteSoon(const Location& from_here, const T* object) {
return DeleteOrReleaseSoonInternal(from_here, &DeleteHelper<T>::DoDelete,
object);
}
+ template <class T>
+ bool DeleteSoon(const Location& from_here, std::unique_ptr<T> object) {
+ return DeleteSoon(from_here, object.release());
+ }
+
// Submits a non-nestable task to release the given object. Returns
// true if the object may be released at some point in the future,
// and false if the object definitely will not be released.
template <class T>
- bool ReleaseSoon(const tracked_objects::Location& from_here,
- const T* object) {
+ bool ReleaseSoon(const Location& from_here, const T* object) {
return DeleteOrReleaseSoonInternal(from_here, &ReleaseHelper<T>::DoRelease,
object);
}
protected:
- ~SequencedTaskRunner() override {}
+ ~SequencedTaskRunner() override = default;
private:
- bool DeleteOrReleaseSoonInternal(const tracked_objects::Location& from_here,
+ bool DeleteOrReleaseSoonInternal(const Location& from_here,
void (*deleter)(const void*),
const void* object);
};
+// Sample usage with std::unique_ptr :
+// std::unique_ptr<Foo, base::OnTaskRunnerDeleter> ptr(
+// new Foo, base::OnTaskRunnerDeleter(my_task_runner));
+//
+// For RefCounted see base::RefCountedDeleteOnSequence.
struct BASE_EXPORT OnTaskRunnerDeleter {
explicit OnTaskRunnerDeleter(scoped_refptr<SequencedTaskRunner> task_runner);
~OnTaskRunnerDeleter();
@@ -153,6 +161,7 @@ struct BASE_EXPORT OnTaskRunnerDeleter {
OnTaskRunnerDeleter(OnTaskRunnerDeleter&&);
OnTaskRunnerDeleter& operator=(OnTaskRunnerDeleter&&);
+ // For compatibility with std:: deleters.
template <typename T>
void operator()(const T* ptr) {
if (ptr)
diff --git a/base/single_thread_task_runner.h b/base/single_thread_task_runner.h
index 6e93193148..4d6938ed6c 100644
--- a/base/single_thread_task_runner.h
+++ b/base/single_thread_task_runner.h
@@ -24,13 +24,11 @@ namespace base {
// running other kinds of message loop, e.g. Jingle threads.
class BASE_EXPORT SingleThreadTaskRunner : public SequencedTaskRunner {
public:
- // A more explicit alias to RunsTasksOnCurrentThread().
- bool BelongsToCurrentThread() const {
- return RunsTasksOnCurrentThread();
- }
+ // A more explicit alias to RunsTasksInCurrentSequence().
+ bool BelongsToCurrentThread() const { return RunsTasksInCurrentSequence(); }
protected:
- ~SingleThreadTaskRunner() override {}
+ ~SingleThreadTaskRunner() override = default;
};
} // namespace base
diff --git a/base/stl_util.h b/base/stl_util.h
index b0670b295e..6d521cc209 100644
--- a/base/stl_util.h
+++ b/base/stl_util.h
@@ -11,6 +11,7 @@
#include <deque>
#include <forward_list>
#include <functional>
+#include <initializer_list>
#include <iterator>
#include <list>
#include <map>
@@ -21,6 +22,7 @@
#include <vector>
#include "base/logging.h"
+#include "base/optional.h"
namespace base {
@@ -39,6 +41,79 @@ void IterateAndEraseIf(Container& container, Predicate pred) {
} // namespace internal
+// C++14 implementation of C++17's std::size():
+// http://en.cppreference.com/w/cpp/iterator/size
+template <typename Container>
+constexpr auto size(const Container& c) -> decltype(c.size()) {
+ return c.size();
+}
+
+template <typename T, size_t N>
+constexpr size_t size(const T (&array)[N]) noexcept {
+ return N;
+}
+
+// C++14 implementation of C++17's std::empty():
+// http://en.cppreference.com/w/cpp/iterator/empty
+template <typename Container>
+constexpr auto empty(const Container& c) -> decltype(c.empty()) {
+ return c.empty();
+}
+
+template <typename T, size_t N>
+constexpr bool empty(const T (&array)[N]) noexcept {
+ return false;
+}
+
+template <typename T>
+constexpr bool empty(std::initializer_list<T> il) noexcept {
+ return il.size() == 0;
+}
+
+// C++14 implementation of C++17's std::data():
+// http://en.cppreference.com/w/cpp/iterator/data
+template <typename Container>
+constexpr auto data(Container& c) -> decltype(c.data()) {
+ return c.data();
+}
+
+// std::basic_string::data() had no mutable overload prior to C++17 [1].
+// Hence this overload is provided.
+// Note: str[0] is safe even for empty strings, as they are guaranteed to be
+// null-terminated [2].
+//
+// [1] http://en.cppreference.com/w/cpp/string/basic_string/data
+// [2] http://en.cppreference.com/w/cpp/string/basic_string/operator_at
+template <typename CharT, typename Traits, typename Allocator>
+CharT* data(std::basic_string<CharT, Traits, Allocator>& str) {
+ return std::addressof(str[0]);
+}
+
+template <typename Container>
+constexpr auto data(const Container& c) -> decltype(c.data()) {
+ return c.data();
+}
+
+template <typename T, size_t N>
+constexpr T* data(T (&array)[N]) noexcept {
+ return array;
+}
+
+template <typename T>
+constexpr const T* data(std::initializer_list<T> il) noexcept {
+ return il.begin();
+}
+
+// Returns a const reference to the underlying container of a container adapter.
+// Works for std::priority_queue, std::queue, and std::stack.
+template <class A>
+const typename A::container_type& GetUnderlyingContainer(const A& adapter) {
+ struct ExposedAdapter : A {
+ using A::c;
+ };
+ return adapter.*&ExposedAdapter::c;
+}
+
// Clears internal memory of an STL object.
// STL clear()/reserve(0) does not always free internal memory allocated
// This function uses swap/destructor to ensure the internal memory is freed.
@@ -59,36 +134,39 @@ STLCount(const Container& container, const T& val) {
return std::count(container.begin(), container.end(), val);
}
-// Return a mutable char* pointing to a string's internal buffer,
-// which may not be null-terminated. Writing through this pointer will
-// modify the string.
-//
-// string_as_array(&str)[i] is valid for 0 <= i < str.size() until the
-// next call to a string method that invalidates iterators.
-//
-// As of 2006-04, there is no standard-blessed way of getting a
-// mutable reference to a string's internal buffer. However, issue 530
-// (http://www.open-std.org/JTC1/SC22/WG21/docs/lwg-active.html#530)
-// proposes this as the method. According to Matt Austern, this should
-// already work on all current implementations.
-inline char* string_as_array(std::string* str) {
- // DO NOT USE const_cast<char*>(str->data())
- return str->empty() ? NULL : &*str->begin();
-}
-
-// Test to see if a set, map, hash_set or hash_map contains a particular key.
+// Test to see if a set or map contains a particular key.
// Returns true if the key is in the collection.
template <typename Collection, typename Key>
bool ContainsKey(const Collection& collection, const Key& key) {
return collection.find(key) != collection.end();
}
+namespace internal {
+
+template <typename Collection>
+class HasKeyType {
+ template <typename C>
+ static std::true_type test(typename C::key_type*);
+ template <typename C>
+ static std::false_type test(...);
+
+ public:
+ static constexpr bool value = decltype(test<Collection>(nullptr))::value;
+};
+
+} // namespace internal
+
// Test to see if a collection like a vector contains a particular value.
// Returns true if the value is in the collection.
-template <typename Collection, typename Value>
+// Don't use this on collections such as sets or maps. This is enforced by
+// disabling this method if the collection defines a key_type.
+template <typename Collection,
+ typename Value,
+ typename std::enable_if<!internal::HasKeyType<Collection>::value,
+ int>::type = 0>
bool ContainsValue(const Collection& collection, const Value& value) {
- return std::find(collection.begin(), collection.end(), value) !=
- collection.end();
+ return std::find(std::begin(collection), std::end(collection), value) !=
+ std::end(collection);
}
// Returns true if the container is sorted.
@@ -287,6 +365,46 @@ void EraseIf(std::unordered_multiset<Key, Hash, KeyEqual, Allocator>& container,
internal::IterateAndEraseIf(container, pred);
}
+// A helper class to be used as the predicate with |EraseIf| to implement
+// in-place set intersection. Helps implement the algorithm of going through
+// each container an element at a time, erasing elements from the first
+// container if they aren't in the second container. Requires each container be
+// sorted. Note that the logic below appears inverted since it is returning
+// whether an element should be erased.
+template <class Collection>
+class IsNotIn {
+ public:
+ explicit IsNotIn(const Collection& collection)
+ : i_(collection.begin()), end_(collection.end()) {}
+
+ bool operator()(const typename Collection::value_type& x) {
+ while (i_ != end_ && *i_ < x)
+ ++i_;
+ if (i_ == end_)
+ return true;
+ if (*i_ == x) {
+ ++i_;
+ return false;
+ }
+ return true;
+ }
+
+ private:
+ typename Collection::const_iterator i_;
+ const typename Collection::const_iterator end_;
+};
+
+// Helper for returning the optional value's address, or nullptr.
+template <class T>
+T* OptionalOrNullptr(base::Optional<T>& optional) {
+ return optional.has_value() ? &optional.value() : nullptr;
+}
+
+template <class T>
+const T* OptionalOrNullptr(const base::Optional<T>& optional) {
+ return optional.has_value() ? &optional.value() : nullptr;
+}
+
} // namespace base
#endif // BASE_STL_UTIL_H_
diff --git a/base/stl_util_unittest.cc b/base/stl_util_unittest.cc
index 48d0f660b5..f13f881564 100644
--- a/base/stl_util_unittest.cc
+++ b/base/stl_util_unittest.cc
@@ -4,20 +4,27 @@
#include "base/stl_util.h"
+#include <array>
#include <deque>
#include <forward_list>
#include <functional>
+#include <initializer_list>
#include <iterator>
#include <list>
#include <map>
+#include <queue>
#include <set>
+#include <stack>
#include <string>
+#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <vector>
+#include "base/containers/queue.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
@@ -94,6 +101,185 @@ struct HashByFirst {
namespace base {
namespace {
+TEST(STLUtilTest, Size) {
+ {
+ std::vector<int> vector = {1, 2, 3, 4, 5};
+ static_assert(
+ std::is_same<decltype(base::size(vector)),
+ decltype(vector.size())>::value,
+ "base::size(vector) should have the same type as vector.size()");
+ EXPECT_EQ(vector.size(), base::size(vector));
+ }
+
+ {
+ std::string empty_str;
+ static_assert(
+ std::is_same<decltype(base::size(empty_str)),
+ decltype(empty_str.size())>::value,
+ "base::size(empty_str) should have the same type as empty_str.size()");
+ EXPECT_EQ(0u, base::size(empty_str));
+ }
+
+ {
+ std::array<int, 4> array = {{1, 2, 3, 4}};
+ static_assert(
+ std::is_same<decltype(base::size(array)),
+ decltype(array.size())>::value,
+ "base::size(array) should have the same type as array.size()");
+ static_assert(base::size(array) == array.size(),
+ "base::size(array) should be equal to array.size()");
+ }
+
+ {
+ int array[] = {1, 2, 3};
+ static_assert(std::is_same<size_t, decltype(base::size(array))>::value,
+ "base::size(array) should be of type size_t");
+ static_assert(3u == base::size(array), "base::size(array) should be 3");
+ }
+}
+
+TEST(STLUtilTest, Empty) {
+ {
+ std::vector<int> vector;
+ static_assert(
+ std::is_same<decltype(base::empty(vector)),
+ decltype(vector.empty())>::value,
+ "base::empty(vector) should have the same type as vector.empty()");
+ EXPECT_EQ(vector.empty(), base::empty(vector));
+ }
+
+ {
+ std::array<int, 4> array = {{1, 2, 3, 4}};
+ static_assert(
+ std::is_same<decltype(base::empty(array)),
+ decltype(array.empty())>::value,
+ "base::empty(array) should have the same type as array.empty()");
+ static_assert(base::empty(array) == array.empty(),
+ "base::empty(array) should be equal to array.empty()");
+ }
+
+ {
+ int array[] = {1, 2, 3};
+ static_assert(std::is_same<bool, decltype(base::empty(array))>::value,
+ "base::empty(array) should be of type bool");
+ static_assert(!base::empty(array), "base::empty(array) should be false");
+ }
+
+ {
+ constexpr std::initializer_list<int> il;
+ static_assert(std::is_same<bool, decltype(base::empty(il))>::value,
+ "base::empty(il) should be of type bool");
+ static_assert(base::empty(il), "base::empty(il) should be true");
+ }
+}
+
+TEST(STLUtilTest, Data) {
+ {
+ std::vector<int> vector = {1, 2, 3, 4, 5};
+ static_assert(
+ std::is_same<decltype(base::data(vector)),
+ decltype(vector.data())>::value,
+ "base::data(vector) should have the same type as vector.data()");
+ EXPECT_EQ(vector.data(), base::data(vector));
+ }
+
+ {
+ const std::string cstr = "const string";
+ static_assert(
+ std::is_same<decltype(base::data(cstr)), decltype(cstr.data())>::value,
+ "base::data(cstr) should have the same type as cstr.data()");
+
+ EXPECT_EQ(cstr.data(), base::data(cstr));
+ }
+
+ {
+ std::string str = "mutable string";
+ static_assert(std::is_same<decltype(base::data(str)), char*>::value,
+ "base::data(str) should be of type char*");
+ EXPECT_EQ(str.data(), base::data(str));
+ }
+
+ {
+ std::string empty_str;
+ static_assert(std::is_same<decltype(base::data(empty_str)), char*>::value,
+ "base::data(empty_str) should be of type char*");
+ EXPECT_EQ(empty_str.data(), base::data(empty_str));
+ }
+
+ {
+ std::array<int, 4> array = {{1, 2, 3, 4}};
+ static_assert(
+ std::is_same<decltype(base::data(array)),
+ decltype(array.data())>::value,
+ "base::data(array) should have the same type as array.data()");
+ // std::array::data() is not constexpr prior to C++17, hence the runtime
+ // check.
+ EXPECT_EQ(array.data(), base::data(array));
+ }
+
+ {
+ constexpr int array[] = {1, 2, 3};
+ static_assert(std::is_same<const int*, decltype(base::data(array))>::value,
+ "base::data(array) should be of type const int*");
+ static_assert(array == base::data(array),
+ "base::data(array) should be array");
+ }
+
+ {
+ constexpr std::initializer_list<int> il;
+ static_assert(
+ std::is_same<decltype(il.begin()), decltype(base::data(il))>::value,
+ "base::data(il) should have the same type as il.begin()");
+ static_assert(il.begin() == base::data(il),
+ "base::data(il) should be equal to il.begin()");
+ }
+}
+
+TEST(STLUtilTest, GetUnderlyingContainer) {
+ {
+ std::queue<int> queue({1, 2, 3, 4, 5});
+ static_assert(std::is_same<decltype(GetUnderlyingContainer(queue)),
+ const std::deque<int>&>::value,
+ "GetUnderlyingContainer(queue) should be of type deque");
+ EXPECT_THAT(GetUnderlyingContainer(queue),
+ testing::ElementsAre(1, 2, 3, 4, 5));
+ }
+
+ {
+ std::queue<int> queue;
+ EXPECT_THAT(GetUnderlyingContainer(queue), testing::ElementsAre());
+ }
+
+ {
+ base::queue<int> queue({1, 2, 3, 4, 5});
+ static_assert(
+ std::is_same<decltype(GetUnderlyingContainer(queue)),
+ const base::circular_deque<int>&>::value,
+ "GetUnderlyingContainer(queue) should be of type circular_deque");
+ EXPECT_THAT(GetUnderlyingContainer(queue),
+ testing::ElementsAre(1, 2, 3, 4, 5));
+ }
+
+ {
+ std::vector<int> values = {1, 2, 3, 4, 5};
+ std::priority_queue<int> queue(values.begin(), values.end());
+ static_assert(std::is_same<decltype(GetUnderlyingContainer(queue)),
+ const std::vector<int>&>::value,
+ "GetUnderlyingContainer(queue) should be of type vector");
+ EXPECT_THAT(GetUnderlyingContainer(queue),
+ testing::UnorderedElementsAre(1, 2, 3, 4, 5));
+ }
+
+ {
+ std::stack<int> stack({1, 2, 3, 4, 5});
+ static_assert(std::is_same<decltype(GetUnderlyingContainer(stack)),
+ const std::deque<int>&>::value,
+ "GetUnderlyingContainer(stack) should be of type deque");
+ EXPECT_THAT(GetUnderlyingContainer(stack),
+ testing::ElementsAre(1, 2, 3, 4, 5));
+ }
+}
+
TEST(STLUtilTest, STLIsSorted) {
{
std::set<int> set;
@@ -299,31 +485,6 @@ TEST(STLUtilTest, STLIncludes) {
EXPECT_TRUE(STLIncludes<std::set<int> >(a3, a2));
}
-TEST(StringAsArrayTest, Empty) {
- std::string empty;
- EXPECT_EQ(nullptr, string_as_array(&empty));
-}
-
-TEST(StringAsArrayTest, NullTerminated) {
- // If any std::string implementation is not null-terminated, this should
- // fail. All compilers we use return a null-terminated buffer, but please do
- // not rely on this fact in your code.
- std::string str("abcde");
- str.resize(3);
- EXPECT_STREQ("abc", string_as_array(&str));
-}
-
-TEST(StringAsArrayTest, WriteCopy) {
- // With a COW implementation, this test will fail if
- // string_as_array(&str) is implemented as
- // const_cast<char*>(str->data()).
- std::string s1("abc");
- const std::string s2(s1);
- string_as_array(&s1)[1] = 'x';
- EXPECT_EQ("axc", s1);
- EXPECT_EQ("abc", s2);
-}
-
TEST(Erase, String) {
const std::pair<std::string, std::string> test_data[] = {
{"", ""}, {"abc", "bc"}, {"abca", "bc"},
@@ -419,5 +580,33 @@ TEST(Erase, UnorderedMultiset) {
RunEraseIfTest<std::unordered_multiset<std::pair<int, int>, HashByFirst>>();
}
+TEST(Erase, IsNotIn) {
+ // Should keep both '2' but only one '4', like std::set_intersection.
+ std::vector<int> lhs = {0, 2, 2, 4, 4, 4, 6, 8, 10};
+ std::vector<int> rhs = {1, 2, 2, 4, 5, 6, 7};
+ std::vector<int> expected = {2, 2, 4, 6};
+ EraseIf(lhs, IsNotIn<std::vector<int>>(rhs));
+ EXPECT_EQ(expected, lhs);
+}
+
+TEST(ContainsValue, OrdinaryArrays) {
+ const char allowed_chars[] = {'a', 'b', 'c', 'd'};
+ EXPECT_TRUE(ContainsValue(allowed_chars, 'a'));
+ EXPECT_FALSE(ContainsValue(allowed_chars, 'z'));
+ EXPECT_FALSE(ContainsValue(allowed_chars, 0));
+
+ const char allowed_chars_including_nul[] = "abcd";
+ EXPECT_TRUE(ContainsValue(allowed_chars_including_nul, 0));
+}
+
+TEST(STLUtilTest, OptionalOrNullptr) {
+ Optional<float> optional;
+ EXPECT_EQ(nullptr, base::OptionalOrNullptr(optional));
+
+ optional = 0.1f;
+ EXPECT_EQ(&optional.value(), base::OptionalOrNullptr(optional));
+ EXPECT_NE(nullptr, base::OptionalOrNullptr(optional));
+}
+
} // namespace
} // namespace base
diff --git a/base/strings/char_traits.h b/base/strings/char_traits.h
new file mode 100644
index 0000000000..b193e216cc
--- /dev/null
+++ b/base/strings/char_traits.h
@@ -0,0 +1,92 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_STRINGS_CHAR_TRAITS_H_
+#define BASE_STRINGS_CHAR_TRAITS_H_
+
+#include <stddef.h>
+
+#include "base/compiler_specific.h"
+
+namespace base {
+
+// constexpr version of http://en.cppreference.com/w/cpp/string/char_traits.
+// This currently just implements the bits needed to support a (mostly)
+// constexpr StringPiece.
+//
+// TODO(dcheng): Once we switch to C++17, most methods will become constexpr and
+// we can switch over to using the one in the standard library.
+template <typename T>
+struct CharTraits {
+ // Performs a lexographical comparison of the first N characters of |s1| and
+ // |s2|. Returns 0 if equal, -1 if |s1| is less than |s2|, and 1 if |s1| is
+ // greater than |s2|.
+ static constexpr int compare(const T* s1, const T* s2, size_t n) noexcept;
+
+ // Returns the length of |s|, assuming null termination (and not including the
+ // terminating null).
+ static constexpr size_t length(const T* s) noexcept;
+};
+
+template <typename T>
+constexpr int CharTraits<T>::compare(const T* s1,
+ const T* s2,
+ size_t n) noexcept {
+ for (; n; --n, ++s1, ++s2) {
+ if (*s1 < *s2)
+ return -1;
+ if (*s1 > *s2)
+ return 1;
+ }
+ return 0;
+}
+
+template <typename T>
+constexpr size_t CharTraits<T>::length(const T* s) noexcept {
+ size_t i = 0;
+ for (; *s; ++s)
+ ++i;
+ return i;
+}
+
+// char specialization of CharTraits that can use clang's constexpr instrinsics,
+// where available.
+template <>
+struct CharTraits<char> {
+ static constexpr int compare(const char* s1,
+ const char* s2,
+ size_t n) noexcept;
+ static constexpr size_t length(const char* s) noexcept;
+};
+
+constexpr int CharTraits<char>::compare(const char* s1,
+ const char* s2,
+ size_t n) noexcept {
+#if HAS_FEATURE(cxx_constexpr_string_builtins)
+ return __builtin_memcmp(s1, s2, n);
+#else
+ for (; n; --n, ++s1, ++s2) {
+ if (*s1 < *s2)
+ return -1;
+ if (*s1 > *s2)
+ return 1;
+ }
+ return 0;
+#endif
+}
+
+constexpr size_t CharTraits<char>::length(const char* s) noexcept {
+#if defined(__clang__)
+ return __builtin_strlen(s);
+#else
+ size_t i = 0;
+ for (; *s; ++s)
+ ++i;
+ return i;
+#endif
+}
+
+} // namespace base
+
+#endif // BASE_STRINGS_CHAR_TRAITS_H_
diff --git a/base/strings/char_traits_unittest.cc b/base/strings/char_traits_unittest.cc
new file mode 100644
index 0000000000..31c421b5b9
--- /dev/null
+++ b/base/strings/char_traits_unittest.cc
@@ -0,0 +1,32 @@
+// 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.
+
+#include "base/strings/char_traits.h"
+#include "base/strings/string16.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+TEST(CharTraitsTest, CharCompare) {
+ static_assert(CharTraits<char>::compare("abc", "def", 3) == -1, "");
+ static_assert(CharTraits<char>::compare("def", "def", 3) == 0, "");
+ static_assert(CharTraits<char>::compare("ghi", "def", 3) == 1, "");
+}
+
+TEST(CharTraitsTest, CharLength) {
+ static_assert(CharTraits<char>::length("") == 0, "");
+ static_assert(CharTraits<char>::length("abc") == 3, "");
+}
+
+TEST(CharTraitsTest, Char16TCompare) {
+ static_assert(CharTraits<char16_t>::compare(u"abc", u"def", 3) == -1, "");
+ static_assert(CharTraits<char16_t>::compare(u"def", u"def", 3) == 0, "");
+ static_assert(CharTraits<char16_t>::compare(u"ghi", u"def", 3) == 1, "");
+}
+
+TEST(CharTraitsTest, Char16TLength) {
+ static_assert(CharTraits<char16_t>::length(u"abc") == 3, "");
+}
+
+} // namespace base
diff --git a/base/strings/nullable_string16.cc b/base/strings/nullable_string16.cc
index 07f81d4339..076b282eb1 100644
--- a/base/strings/nullable_string16.cc
+++ b/base/strings/nullable_string16.cc
@@ -5,13 +5,29 @@
#include "base/strings/nullable_string16.h"
#include <ostream>
-
-#include "base/strings/utf_string_conversions.h"
+#include <utility>
namespace base {
+NullableString16::NullableString16() = default;
+NullableString16::NullableString16(const NullableString16& other) = default;
+NullableString16::NullableString16(NullableString16&& other) = default;
+
+NullableString16::NullableString16(const string16& string, bool is_null) {
+ if (!is_null)
+ string_.emplace(string);
+}
+
+NullableString16::NullableString16(Optional<string16> optional_string16)
+ : string_(std::move(optional_string16)) {}
+
+NullableString16::~NullableString16() = default;
+NullableString16& NullableString16::operator=(const NullableString16& other) =
+ default;
+NullableString16& NullableString16::operator=(NullableString16&& other) =
+ default;
std::ostream& operator<<(std::ostream& out, const NullableString16& value) {
- return value.is_null() ? out << "(null)" : out << UTF16ToUTF8(value.string());
+ return value.is_null() ? out << "(null)" : out << value.string();
}
} // namespace base
diff --git a/base/strings/nullable_string16.h b/base/strings/nullable_string16.h
index 016c25c250..abddee0f74 100644
--- a/base/strings/nullable_string16.h
+++ b/base/strings/nullable_string16.h
@@ -8,30 +8,39 @@
#include <iosfwd>
#include "base/base_export.h"
+#include "base/optional.h"
#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
namespace base {
// This class is a simple wrapper for string16 which also contains a null
// state. This should be used only where the difference between null and
// empty is meaningful.
-class NullableString16 {
+class BASE_EXPORT NullableString16 {
public:
- NullableString16() : is_null_(true) { }
- NullableString16(const string16& string, bool is_null)
- : string_(string), is_null_(is_null) {
+ NullableString16();
+ NullableString16(const NullableString16& other);
+ NullableString16(NullableString16&& other);
+ NullableString16(const string16& string, bool is_null);
+ explicit NullableString16(Optional<string16> optional_string16);
+ ~NullableString16();
+
+ NullableString16& operator=(const NullableString16& other);
+ NullableString16& operator=(NullableString16&& other);
+
+ const string16& string() const {
+ return string_ ? *string_ : EmptyString16();
}
-
- const string16& string() const { return string_; }
- bool is_null() const { return is_null_; }
+ bool is_null() const { return !string_; }
+ const Optional<string16>& as_optional_string16() const { return string_; }
private:
- string16 string_;
- bool is_null_;
+ Optional<string16> string_;
};
inline bool operator==(const NullableString16& a, const NullableString16& b) {
- return a.is_null() == b.is_null() && a.string() == b.string();
+ return a.as_optional_string16() == b.as_optional_string16();
}
inline bool operator!=(const NullableString16& a, const NullableString16& b) {
diff --git a/base/strings/old_utf_string_conversions.cc b/base/strings/old_utf_string_conversions.cc
new file mode 100644
index 0000000000..5cab038ab4
--- /dev/null
+++ b/base/strings/old_utf_string_conversions.cc
@@ -0,0 +1,262 @@
+// 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.
+
+#include "base/strings/old_utf_string_conversions.h"
+
+#include <stdint.h>
+
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversion_utils.h"
+#include "build/build_config.h"
+
+namespace base_old {
+
+using base::IsStringASCII;
+using base::ReadUnicodeCharacter;
+using base::WriteUnicodeCharacter;
+
+template<typename CHAR>
+void PrepareForUTF8Output(const CHAR* src,
+ size_t src_len,
+ std::string* output) {
+ output->clear();
+ if (src_len == 0)
+ return;
+ if (src[0] < 0x80) {
+ // Assume that the entire input will be ASCII.
+ output->reserve(src_len);
+ } else {
+ // Assume that the entire input is non-ASCII and will have 3 bytes per char.
+ output->reserve(src_len * 3);
+ }
+}
+
+template<typename STRING>
+void PrepareForUTF16Or32Output(const char* src,
+ size_t src_len,
+ STRING* output) {
+ output->clear();
+ if (src_len == 0)
+ return;
+ if (static_cast<unsigned char>(src[0]) < 0x80) {
+ // Assume the input is all ASCII, which means 1:1 correspondence.
+ output->reserve(src_len);
+ } else {
+ // Otherwise assume that the UTF-8 sequences will have 2 bytes for each
+ // character.
+ output->reserve(src_len / 2);
+ }
+}
+
+namespace {
+
+// Generalized Unicode converter -----------------------------------------------
+
+// Converts the given source Unicode character type to the given destination
+// Unicode character type as a STL string. The given input buffer and size
+// determine the source, and the given output STL string will be replaced by
+// the result.
+template <typename SRC_CHAR, typename DEST_STRING>
+bool ConvertUnicode(const SRC_CHAR* src, size_t src_len, DEST_STRING* output) {
+ // ICU requires 32-bit numbers.
+ bool success = true;
+ int32_t src_len32 = static_cast<int32_t>(src_len);
+ for (int32_t i = 0; i < src_len32; i++) {
+ uint32_t code_point;
+ if (ReadUnicodeCharacter(src, src_len32, &i, &code_point)) {
+ WriteUnicodeCharacter(code_point, output);
+ } else {
+ WriteUnicodeCharacter(0xFFFD, output);
+ success = false;
+ }
+ }
+
+ return success;
+}
+
+} // namespace
+
+// UTF-8 <-> Wide --------------------------------------------------------------
+
+bool WideToUTF8(const wchar_t* src, size_t src_len, std::string* output) {
+ if (IsStringASCII(std::wstring(src, src_len))) {
+ output->assign(src, src + src_len);
+ return true;
+ } else {
+ PrepareForUTF8Output(src, src_len, output);
+ return ConvertUnicode(src, src_len, output);
+ }
+}
+
+std::string WideToUTF8(const std::wstring& wide) {
+ if (IsStringASCII(wide)) {
+ return std::string(wide.data(), wide.data() + wide.length());
+ }
+
+ std::string ret;
+ PrepareForUTF8Output(wide.data(), wide.length(), &ret);
+ ConvertUnicode(wide.data(), wide.length(), &ret);
+ return ret;
+}
+
+bool UTF8ToWide(const char* src, size_t src_len, std::wstring* output) {
+ if (IsStringASCII(StringPiece(src, src_len))) {
+ output->assign(src, src + src_len);
+ return true;
+ } else {
+ PrepareForUTF16Or32Output(src, src_len, output);
+ return ConvertUnicode(src, src_len, output);
+ }
+}
+
+std::wstring UTF8ToWide(StringPiece utf8) {
+ if (IsStringASCII(utf8)) {
+ return std::wstring(utf8.begin(), utf8.end());
+ }
+
+ std::wstring ret;
+ PrepareForUTF16Or32Output(utf8.data(), utf8.length(), &ret);
+ ConvertUnicode(utf8.data(), utf8.length(), &ret);
+ return ret;
+}
+
+// UTF-16 <-> Wide -------------------------------------------------------------
+
+#if defined(WCHAR_T_IS_UTF16)
+
+// When wide == UTF-16, then conversions are a NOP.
+bool WideToUTF16(const wchar_t* src, size_t src_len, string16* output) {
+ output->assign(src, src_len);
+ return true;
+}
+
+string16 WideToUTF16(const std::wstring& wide) {
+ return wide;
+}
+
+bool UTF16ToWide(const char16* src, size_t src_len, std::wstring* output) {
+ output->assign(src, src_len);
+ return true;
+}
+
+std::wstring UTF16ToWide(const string16& utf16) {
+ return utf16;
+}
+
+#elif defined(WCHAR_T_IS_UTF32)
+
+bool WideToUTF16(const wchar_t* src, size_t src_len, string16* output) {
+ output->clear();
+ // Assume that normally we won't have any non-BMP characters so the counts
+ // will be the same.
+ output->reserve(src_len);
+ return ConvertUnicode(src, src_len, output);
+}
+
+string16 WideToUTF16(const std::wstring& wide) {
+ string16 ret;
+ WideToUTF16(wide.data(), wide.length(), &ret);
+ return ret;
+}
+
+bool UTF16ToWide(const char16* src, size_t src_len, std::wstring* output) {
+ output->clear();
+ // Assume that normally we won't have any non-BMP characters so the counts
+ // will be the same.
+ output->reserve(src_len);
+ return ConvertUnicode(src, src_len, output);
+}
+
+std::wstring UTF16ToWide(const string16& utf16) {
+ std::wstring ret;
+ UTF16ToWide(utf16.data(), utf16.length(), &ret);
+ return ret;
+}
+
+#endif // defined(WCHAR_T_IS_UTF32)
+
+// UTF16 <-> UTF8 --------------------------------------------------------------
+
+#if defined(WCHAR_T_IS_UTF32)
+
+bool UTF8ToUTF16(const char* src, size_t src_len, string16* output) {
+ if (IsStringASCII(StringPiece(src, src_len))) {
+ output->assign(src, src + src_len);
+ return true;
+ } else {
+ PrepareForUTF16Or32Output(src, src_len, output);
+ return ConvertUnicode(src, src_len, output);
+ }
+}
+
+string16 UTF8ToUTF16(StringPiece utf8) {
+ if (IsStringASCII(utf8)) {
+ return string16(utf8.begin(), utf8.end());
+ }
+
+ string16 ret;
+ PrepareForUTF16Or32Output(utf8.data(), utf8.length(), &ret);
+ // Ignore the success flag of this call, it will do the best it can for
+ // invalid input, which is what we want here.
+ ConvertUnicode(utf8.data(), utf8.length(), &ret);
+ return ret;
+}
+
+bool UTF16ToUTF8(const char16* src, size_t src_len, std::string* output) {
+ if (IsStringASCII(StringPiece16(src, src_len))) {
+ output->assign(src, src + src_len);
+ return true;
+ } else {
+ PrepareForUTF8Output(src, src_len, output);
+ return ConvertUnicode(src, src_len, output);
+ }
+}
+
+std::string UTF16ToUTF8(StringPiece16 utf16) {
+ std::string ret;
+ // Ignore the success flag of this call, it will do the best it can for
+ // invalid input, which is what we want here.
+ UTF16ToUTF8(utf16.data(), utf16.length(), &ret);
+ return ret;
+}
+
+#elif defined(WCHAR_T_IS_UTF16)
+// Easy case since we can use the "wide" versions we already wrote above.
+
+bool UTF8ToUTF16(const char* src, size_t src_len, string16* output) {
+ return UTF8ToWide(src, src_len, output);
+}
+
+string16 UTF8ToUTF16(StringPiece utf8) {
+ return UTF8ToWide(utf8);
+}
+
+bool UTF16ToUTF8(const char16* src, size_t src_len, std::string* output) {
+ return WideToUTF8(src, src_len, output);
+}
+
+std::string UTF16ToUTF8(StringPiece16 utf16) {
+ if (IsStringASCII(utf16))
+ return std::string(utf16.data(), utf16.data() + utf16.length());
+
+ std::string ret;
+ PrepareForUTF8Output(utf16.data(), utf16.length(), &ret);
+ ConvertUnicode(utf16.data(), utf16.length(), &ret);
+ return ret;
+}
+
+#endif
+
+string16 ASCIIToUTF16(StringPiece ascii) {
+ DCHECK(IsStringASCII(ascii)) << ascii;
+ return string16(ascii.begin(), ascii.end());
+}
+
+std::string UTF16ToASCII(StringPiece16 utf16) {
+ DCHECK(IsStringASCII(utf16)) << UTF16ToUTF8(utf16);
+ return std::string(utf16.begin(), utf16.end());
+}
+
+} // namespace base_old
diff --git a/base/strings/old_utf_string_conversions.h b/base/strings/old_utf_string_conversions.h
new file mode 100644
index 0000000000..2f0c6c51a6
--- /dev/null
+++ b/base/strings/old_utf_string_conversions.h
@@ -0,0 +1,64 @@
+// 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_STRINGS_OLD_UTF_STRING_CONVERSIONS_H_
+#define BASE_STRINGS_OLD_UTF_STRING_CONVERSIONS_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/base_export.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_piece.h"
+
+namespace base_old {
+
+using base::char16;
+using base::string16;
+using base::StringPiece16;
+using base::StringPiece;
+
+// These convert between UTF-8, -16, and -32 strings. They are potentially slow,
+// so avoid unnecessary conversions. The low-level versions return a boolean
+// indicating whether the conversion was 100% valid. In this case, it will still
+// do the best it can and put the result in the output buffer. The versions that
+// return strings ignore this error and just return the best conversion
+// possible.
+BASE_EXPORT bool WideToUTF8(const wchar_t* src,
+ size_t src_len,
+ std::string* output);
+BASE_EXPORT std::string WideToUTF8(const std::wstring& wide);
+BASE_EXPORT bool UTF8ToWide(const char* src,
+ size_t src_len,
+ std::wstring* output);
+BASE_EXPORT std::wstring UTF8ToWide(StringPiece utf8);
+
+BASE_EXPORT bool WideToUTF16(const wchar_t* src,
+ size_t src_len,
+ string16* output);
+BASE_EXPORT string16 WideToUTF16(const std::wstring& wide);
+BASE_EXPORT bool UTF16ToWide(const char16* src,
+ size_t src_len,
+ std::wstring* output);
+BASE_EXPORT std::wstring UTF16ToWide(const string16& utf16);
+
+BASE_EXPORT bool UTF8ToUTF16(const char* src, size_t src_len, string16* output);
+BASE_EXPORT string16 UTF8ToUTF16(StringPiece utf8);
+BASE_EXPORT bool UTF16ToUTF8(const char16* src,
+ size_t src_len,
+ std::string* output);
+BASE_EXPORT std::string UTF16ToUTF8(StringPiece16 utf16);
+
+// This converts an ASCII string, typically a hardcoded constant, to a UTF16
+// string.
+BASE_EXPORT string16 ASCIIToUTF16(StringPiece ascii);
+
+// Converts to 7-bit ASCII by truncating. The result must be known to be ASCII
+// beforehand.
+BASE_EXPORT std::string UTF16ToASCII(StringPiece16 utf16);
+
+} // namespace base_old
+
+#endif // BASE_STRINGS_OLD_UTF_STRING_CONVERSIONS_H_
diff --git a/base/strings/pattern.cc b/base/strings/pattern.cc
index af30aab86d..f3de0afd47 100644
--- a/base/strings/pattern.cc
+++ b/base/strings/pattern.cc
@@ -10,126 +10,114 @@ namespace base {
namespace {
-static bool IsWildcard(base_icu::UChar32 character) {
+constexpr bool IsWildcard(base_icu::UChar32 character) {
return character == '*' || character == '?';
}
-// Move the strings pointers to the point where they start to differ.
+// Searches for the next subpattern of |pattern| in |string|, up to the given
+// |maximum_distance|. The subpattern extends from the start of |pattern| up to
+// the first wildcard character (or the end of the string). If the value of
+// |maximum_distance| is negative, the maximum distance is considered infinite.
template <typename CHAR, typename NEXT>
-static void EatSameChars(const CHAR** pattern, const CHAR* pattern_end,
- const CHAR** string, const CHAR* string_end,
- NEXT next) {
- const CHAR* escape = NULL;
- while (*pattern != pattern_end && *string != string_end) {
- if (!escape && IsWildcard(**pattern)) {
- // We don't want to match wildcard here, except if it's escaped.
- return;
- }
+constexpr bool SearchForChars(const CHAR** pattern,
+ const CHAR* pattern_end,
+ const CHAR** string,
+ const CHAR* string_end,
+ int maximum_distance,
+ NEXT next) {
+ const CHAR* pattern_start = *pattern;
+ const CHAR* string_start = *string;
+ bool escape = false;
+ while (true) {
+ if (*pattern == pattern_end) {
+ // If this is the end of the pattern, only accept the end of the string;
+ // anything else falls through to the mismatch case.
+ if (*string == string_end)
+ return true;
+ } else {
+ // If we have found a wildcard, we're done.
+ if (!escape && IsWildcard(**pattern))
+ return true;
- // Check if the escapement char is found. If so, skip it and move to the
- // next character.
- if (!escape && **pattern == '\\') {
- escape = *pattern;
- next(pattern, pattern_end);
- continue;
- }
+ // Check if the escape character is found. If so, skip it and move to the
+ // next character.
+ if (!escape && **pattern == '\\') {
+ escape = true;
+ next(pattern, pattern_end);
+ continue;
+ }
- // Check if the chars match, if so, increment the ptrs.
- const CHAR* pattern_next = *pattern;
- const CHAR* string_next = *string;
- base_icu::UChar32 pattern_char = next(&pattern_next, pattern_end);
- if (pattern_char == next(&string_next, string_end) &&
- pattern_char != CBU_SENTINEL) {
- *pattern = pattern_next;
- *string = string_next;
- } else {
- // Uh oh, it did not match, we are done. If the last char was an
- // escapement, that means that it was an error to advance the ptr here,
- // let's put it back where it was. This also mean that the MatchPattern
- // function will return false because if we can't match an escape char
- // here, then no one will.
- if (escape) {
- *pattern = escape;
+ escape = false;
+
+ if (*string == string_end)
+ return false;
+
+ // Check if the chars match, if so, increment the ptrs.
+ const CHAR* pattern_next = *pattern;
+ const CHAR* string_next = *string;
+ base_icu::UChar32 pattern_char = next(&pattern_next, pattern_end);
+ if (pattern_char == next(&string_next, string_end) &&
+ pattern_char != CBU_SENTINEL) {
+ *pattern = pattern_next;
+ *string = string_next;
+ continue;
}
- return;
}
- escape = NULL;
+ // Mismatch. If we have reached the maximum distance, return false,
+ // otherwise restart at the beginning of the pattern with the next character
+ // in the string.
+ // TODO(bauerb): This is a naive implementation of substring search, which
+ // could be implemented with a more efficient algorithm, e.g.
+ // Knuth-Morris-Pratt (at the expense of requiring preprocessing).
+ if (maximum_distance == 0)
+ return false;
+
+ // Because unlimited distance is represented as -1, this will never reach 0
+ // and therefore fail the match above.
+ maximum_distance--;
+ *pattern = pattern_start;
+ next(&string_start, string_end);
+ *string = string_start;
}
}
+// Consumes consecutive wildcard characters (? or *). Returns the maximum number
+// of characters matched by the sequence of wildcards, or -1 if the wildcards
+// match an arbitrary number of characters (which is the case if it contains at
+// least one *).
template <typename CHAR, typename NEXT>
-static void EatWildcard(const CHAR** pattern, const CHAR* end, NEXT next) {
+constexpr int EatWildcards(const CHAR** pattern, const CHAR* end, NEXT next) {
+ int num_question_marks = 0;
+ bool has_asterisk = false;
while (*pattern != end) {
- if (!IsWildcard(**pattern))
- return;
+ if (**pattern == '?') {
+ num_question_marks++;
+ } else if (**pattern == '*') {
+ has_asterisk = true;
+ } else {
+ break;
+ }
+
next(pattern, end);
}
+ return has_asterisk ? -1 : num_question_marks;
}
template <typename CHAR, typename NEXT>
-static bool MatchPatternT(const CHAR* eval, const CHAR* eval_end,
- const CHAR* pattern, const CHAR* pattern_end,
- int depth,
- NEXT next) {
- const int kMaxDepth = 16;
- if (depth > kMaxDepth)
- return false;
-
- // Eat all the matching chars.
- EatSameChars(&pattern, pattern_end, &eval, eval_end, next);
-
- // If the string is empty, then the pattern must be empty too, or contains
- // only wildcards.
- if (eval == eval_end) {
- EatWildcard(&pattern, pattern_end, next);
- return pattern == pattern_end;
- }
-
- // Pattern is empty but not string, this is not a match.
- if (pattern == pattern_end)
- return false;
-
- // If this is a question mark, then we need to compare the rest with
- // the current string or the string with one character eaten.
- const CHAR* next_pattern = pattern;
- next(&next_pattern, pattern_end);
- if (pattern[0] == '?') {
- if (MatchPatternT(eval, eval_end, next_pattern, pattern_end,
- depth + 1, next))
- return true;
- const CHAR* next_eval = eval;
- next(&next_eval, eval_end);
- if (MatchPatternT(next_eval, eval_end, next_pattern, pattern_end,
- depth + 1, next))
- return true;
- }
-
- // This is a *, try to match all the possible substrings with the remainder
- // of the pattern.
- if (pattern[0] == '*') {
- // Collapse duplicate wild cards (********** into *) so that the
- // method does not recurse unnecessarily. http://crbug.com/52839
- EatWildcard(&next_pattern, pattern_end, next);
-
- while (eval != eval_end) {
- if (MatchPatternT(eval, eval_end, next_pattern, pattern_end,
- depth + 1, next))
- return true;
- eval++;
- }
-
- // We reached the end of the string, let see if the pattern contains only
- // wildcards.
- if (eval == eval_end) {
- EatWildcard(&pattern, pattern_end, next);
- if (pattern != pattern_end)
- return false;
- return true;
+constexpr bool MatchPatternT(const CHAR* eval,
+ const CHAR* eval_end,
+ const CHAR* pattern,
+ const CHAR* pattern_end,
+ NEXT next) {
+ do {
+ int maximum_wildcard_length = EatWildcards(&pattern, pattern_end, next);
+ if (!SearchForChars(&pattern, pattern_end, &eval, eval_end,
+ maximum_wildcard_length, next)) {
+ return false;
}
- }
-
- return false;
+ } while (pattern != pattern_end);
+ return true;
}
struct NextCharUTF8 {
@@ -154,16 +142,14 @@ struct NextCharUTF16 {
} // namespace
-bool MatchPattern(const StringPiece& eval, const StringPiece& pattern) {
- return MatchPatternT(eval.data(), eval.data() + eval.size(),
- pattern.data(), pattern.data() + pattern.size(),
- 0, NextCharUTF8());
+bool MatchPattern(StringPiece eval, StringPiece pattern) {
+ return MatchPatternT(eval.data(), eval.data() + eval.size(), pattern.data(),
+ pattern.data() + pattern.size(), NextCharUTF8());
}
-bool MatchPattern(const StringPiece16& eval, const StringPiece16& pattern) {
- return MatchPatternT(eval.data(), eval.data() + eval.size(),
- pattern.data(), pattern.data() + pattern.size(),
- 0, NextCharUTF16());
+bool MatchPattern(StringPiece16 eval, StringPiece16 pattern) {
+ return MatchPatternT(eval.data(), eval.data() + eval.size(), pattern.data(),
+ pattern.data() + pattern.size(), NextCharUTF16());
}
} // namespace base
diff --git a/base/strings/pattern.h b/base/strings/pattern.h
index b698207b9d..b5172abf5e 100644
--- a/base/strings/pattern.h
+++ b/base/strings/pattern.h
@@ -10,16 +10,13 @@
namespace base {
-// Returns true if the string passed in matches the pattern. The pattern
-// string can contain wildcards like * and ?
+// Returns true if the |string| passed in matches the |pattern|. The pattern
+// string can contain wildcards like * and ?.
//
-// The backslash character (\) is an escape character for * and ?
-// We limit the patterns to having a max of 16 * or ? characters.
+// The backslash character (\) is an escape character for * and ?.
// ? matches 0 or 1 character, while * matches 0 or more characters.
-BASE_EXPORT bool MatchPattern(const StringPiece& string,
- const StringPiece& pattern);
-BASE_EXPORT bool MatchPattern(const StringPiece16& string,
- const StringPiece16& pattern);
+BASE_EXPORT bool MatchPattern(StringPiece string, StringPiece pattern);
+BASE_EXPORT bool MatchPattern(StringPiece16 string, StringPiece16 pattern);
} // namespace base
diff --git a/base/strings/pattern_unittest.cc b/base/strings/pattern_unittest.cc
index 9e82b3cba1..8ec54954ca 100644
--- a/base/strings/pattern_unittest.cc
+++ b/base/strings/pattern_unittest.cc
@@ -22,8 +22,10 @@ TEST(StringUtilTest, MatchPatternTest) {
EXPECT_TRUE(MatchPattern("", ""));
EXPECT_FALSE(MatchPattern("Hello", ""));
EXPECT_TRUE(MatchPattern("Hello*", "Hello*"));
- // Stop after a certain recursion depth.
- EXPECT_FALSE(MatchPattern("123456789012345678", "?????????????????*"));
+ EXPECT_TRUE(MatchPattern("abcd", "*???"));
+ EXPECT_FALSE(MatchPattern("abcd", "???"));
+ EXPECT_TRUE(MatchPattern("abcb", "a*b"));
+ EXPECT_FALSE(MatchPattern("abcb", "a?b"));
// Test UTF8 matching.
EXPECT_TRUE(MatchPattern("heart: \xe2\x99\xa0", "*\xe2\x99\xa0"));
@@ -40,11 +42,11 @@ TEST(StringUtilTest, MatchPatternTest) {
EXPECT_TRUE(MatchPattern(UTF8ToUTF16("Hello*1234"),
UTF8ToUTF16("He??o\\*1*")));
- // This test verifies that consecutive wild cards are collapsed into 1
- // wildcard (when this doesn't occur, MatchPattern reaches it's maximum
- // recursion depth).
- EXPECT_TRUE(MatchPattern(UTF8ToUTF16("Hello"),
- UTF8ToUTF16("He********************************o")));
+ // Some test cases that might cause naive implementations to exhibit
+ // exponential run time or fail.
+ EXPECT_TRUE(MatchPattern("Hello", "He********************************o"));
+ EXPECT_TRUE(MatchPattern("123456789012345678", "?????????????????*"));
+ EXPECT_TRUE(MatchPattern("aaaaaaaaaaab", "a*a*a*a*a*a*a*a*a*a*a*b"));
}
} // namespace base
diff --git a/base/strings/safe_sprintf.cc b/base/strings/safe_sprintf.cc
index a51c778271..4d695cf984 100644
--- a/base/strings/safe_sprintf.cc
+++ b/base/strings/safe_sprintf.cc
@@ -318,7 +318,7 @@ bool Buffer::IToASCII(bool sign, bool upcase, int64_t i, int base,
// We cannot choose the easier approach of just reversing the number, as that
// fails in situations where we need to truncate numbers that have padding
// and/or prefixes.
- const char* reverse_prefix = NULL;
+ const char* reverse_prefix = nullptr;
if (prefix && *prefix) {
if (pad == '0') {
while (*prefix) {
@@ -327,13 +327,13 @@ bool Buffer::IToASCII(bool sign, bool upcase, int64_t i, int base,
}
Out(*prefix++);
}
- prefix = NULL;
+ prefix = nullptr;
} else {
for (reverse_prefix = prefix; *reverse_prefix; ++reverse_prefix) {
}
}
} else
- prefix = NULL;
+ prefix = nullptr;
const size_t prefix_length = reverse_prefix - prefix;
// Loop until we have converted the entire number. Output at least one
@@ -530,7 +530,7 @@ ssize_t SafeSNPrintf(char* buf, size_t sz, const char* fmt, const Arg* args,
const Arg& arg = args[cur_arg++];
int64_t i;
- const char* prefix = NULL;
+ const char* prefix = nullptr;
if (ch != 'p') {
// Check that the argument has the expected type.
if (arg.type != Arg::INT && arg.type != Arg::UINT) {
diff --git a/base/strings/safe_sprintf.h b/base/strings/safe_sprintf.h
index 65524a50c3..01d649d07a 100644
--- a/base/strings/safe_sprintf.h
+++ b/base/strings/safe_sprintf.h
@@ -11,7 +11,7 @@
#include <stdint.h>
#include <stdlib.h>
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// For ssize_t
#include <unistd.h>
#endif
@@ -21,7 +21,7 @@
namespace base {
namespace strings {
-#if defined(_MSC_VER)
+#if defined(COMPILER_MSVC)
// Define ssize_t inside of our namespace.
#if defined(_WIN64)
typedef __int64 ssize_t;
diff --git a/base/strings/safe_sprintf_unittest.cc b/base/strings/safe_sprintf_unittest.cc
index 1a21728a8e..bb9908f928 100644
--- a/base/strings/safe_sprintf_unittest.cc
+++ b/base/strings/safe_sprintf_unittest.cc
@@ -384,14 +384,16 @@ void PrintLongString(char* buf, size_t sz) {
size_t out_sz = sz;
size_t len;
for (std::unique_ptr<char[]> perfect_buf;;) {
- size_t needed = SafeSNPrintf(out, out_sz,
+ size_t needed =
+ SafeSNPrintf(out, out_sz,
#if defined(NDEBUG)
- "A%2cong %s: %d %010X %d %p%7s", 'l', "string", "",
+ "A%2cong %s: %d %010X %d %p%7s", 'l', "string", "",
#else
- "A%2cong %s: %%d %010X %d %p%7s", 'l', "string",
+ "A%2cong %s: %%d %010X %d %p%7s", 'l', "string",
#endif
- 0xDEADBEEF, std::numeric_limits<intptr_t>::min(),
- PrintLongString, static_cast<char*>(NULL)) + 1;
+ 0xDEADBEEF, std::numeric_limits<intptr_t>::min(),
+ PrintLongString, static_cast<char*>(nullptr)) +
+ 1;
// Various sanity checks:
// The numbered of characters needed to print the full string should always
diff --git a/base/strings/strcat.cc b/base/strings/strcat.cc
new file mode 100644
index 0000000000..3d5b2cade5
--- /dev/null
+++ b/base/strings/strcat.cc
@@ -0,0 +1,81 @@
+// 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/strings/strcat.h"
+
+namespace base {
+
+namespace {
+
+// Reserves an additional amount of size in the given string, growing by at
+// least 2x. Used by StrAppend().
+//
+// The "at least 2x" growing rule duplicates the exponential growth of
+// std::string. The problem is that most implementations of reserve() will grow
+// exactly to the requested amount instead of exponentially growing like would
+// happen when appending normally. If we didn't do this, an append after the
+// call to StrAppend() would definitely cause a reallocation, and loops with
+// StrAppend() calls would have O(n^2) complexity to execute. Instead, we want
+// StrAppend() to have the same semantics as std::string::append().
+//
+// If the string is empty, we assume that exponential growth is not necessary.
+template <typename String>
+void ReserveAdditional(String* str, typename String::size_type additional) {
+ str->reserve(std::max(str->size() + additional, str->size() * 2));
+}
+
+template <typename DestString, typename InputString>
+void StrAppendT(DestString* dest, span<const InputString> pieces) {
+ size_t additional_size = 0;
+ for (const auto& cur : pieces)
+ additional_size += cur.size();
+ ReserveAdditional(dest, additional_size);
+
+ for (const auto& cur : pieces)
+ dest->append(cur.data(), cur.size());
+}
+
+} // namespace
+
+std::string StrCat(span<const StringPiece> pieces) {
+ std::string result;
+ StrAppendT(&result, pieces);
+ return result;
+}
+
+string16 StrCat(span<const StringPiece16> pieces) {
+ string16 result;
+ StrAppendT(&result, pieces);
+ return result;
+}
+
+std::string StrCat(span<const std::string> pieces) {
+ std::string result;
+ StrAppendT(&result, pieces);
+ return result;
+}
+
+string16 StrCat(span<const string16> pieces) {
+ string16 result;
+ StrAppendT(&result, pieces);
+ return result;
+}
+
+void StrAppend(std::string* dest, span<const StringPiece> pieces) {
+ StrAppendT(dest, pieces);
+}
+
+void StrAppend(string16* dest, span<const StringPiece16> pieces) {
+ StrAppendT(dest, pieces);
+}
+
+void StrAppend(std::string* dest, span<const std::string> pieces) {
+ StrAppendT(dest, pieces);
+}
+
+void StrAppend(string16* dest, span<const string16> pieces) {
+ StrAppendT(dest, pieces);
+}
+
+} // namespace base
diff --git a/base/strings/strcat.h b/base/strings/strcat.h
new file mode 100644
index 0000000000..44c6211d6b
--- /dev/null
+++ b/base/strings/strcat.h
@@ -0,0 +1,99 @@
+// 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_STRINGS_STRCAT_H_
+#define BASE_STRINGS_STRCAT_H_
+
+#include <initializer_list>
+
+#include "base/base_export.h"
+#include "base/compiler_specific.h"
+#include "base/containers/span.h"
+#include "base/strings/string_piece.h"
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+// To resolve a conflict with Win32 API StrCat macro.
+#include "base/win/windows_types.h"
+#endif
+
+namespace base {
+
+// StrCat ----------------------------------------------------------------------
+//
+// StrCat is a function to perform concatenation on a sequence of strings.
+// It is preferrable to a sequence of "a + b + c" because it is both faster and
+// generates less code.
+//
+// std::string result = base::StrCat({"foo ", result, "\nfoo ", bar});
+//
+// To join an array of strings with a separator, see base::JoinString in
+// base/strings/string_util.h.
+//
+// MORE INFO
+//
+// StrCat can see all arguments at once, so it can allocate one return buffer
+// of exactly the right size and copy once, as opposed to a sequence of
+// operator+ which generates a series of temporary strings, copying as it goes.
+// And by using StringPiece arguments, StrCat can avoid creating temporary
+// string objects for char* constants.
+//
+// ALTERNATIVES
+//
+// Internal Google / Abseil has a similar StrCat function. That version takes
+// an overloaded number of arguments instead of initializer list (overflowing
+// to initializer list for many arguments). We don't have any legacy
+// requirements and using only initializer_list is simpler and generates
+// roughly the same amount of code at the call sites.
+//
+// Abseil's StrCat also allows numbers by using an intermediate class that can
+// be implicitly constructed from either a string or various number types. This
+// class formats the numbers into a static buffer for increased performance,
+// and the call sites look nice.
+//
+// As-written Abseil's helper class for numbers generates slightly more code
+// than the raw StringPiece version. We can de-inline the helper class'
+// constructors which will cause the StringPiece constructors to be de-inlined
+// for this call and generate slightly less code. This is something we can
+// explore more in the future.
+
+BASE_EXPORT std::string StrCat(span<const StringPiece> pieces);
+BASE_EXPORT string16 StrCat(span<const StringPiece16> pieces);
+BASE_EXPORT std::string StrCat(span<const std::string> pieces);
+BASE_EXPORT string16 StrCat(span<const string16> pieces);
+
+// Initializer list forwards to the array version.
+inline std::string StrCat(std::initializer_list<StringPiece> pieces) {
+ return StrCat(make_span(pieces.begin(), pieces.size()));
+}
+inline string16 StrCat(std::initializer_list<StringPiece16> pieces) {
+ return StrCat(make_span(pieces.begin(), pieces.size()));
+}
+
+// StrAppend -------------------------------------------------------------------
+//
+// Appends a sequence of strings to a destination. Prefer:
+// StrAppend(&foo, ...);
+// over:
+// foo += StrCat(...);
+// because it avoids a temporary string allocation and copy.
+
+BASE_EXPORT void StrAppend(std::string* dest, span<const StringPiece> pieces);
+BASE_EXPORT void StrAppend(string16* dest, span<const StringPiece16> pieces);
+BASE_EXPORT void StrAppend(std::string* dest, span<const std::string> pieces);
+BASE_EXPORT void StrAppend(string16* dest, span<const string16> pieces);
+
+// Initializer list forwards to the array version.
+inline void StrAppend(std::string* dest,
+ std::initializer_list<StringPiece> pieces) {
+ return StrAppend(dest, make_span(pieces.begin(), pieces.size()));
+}
+inline void StrAppend(string16* dest,
+ std::initializer_list<StringPiece16> pieces) {
+ return StrAppend(dest, make_span(pieces.begin(), pieces.size()));
+}
+
+} // namespace base
+
+#endif // BASE_STRINGS_STRCAT_H_
diff --git a/base/strings/strcat_unittest.cc b/base/strings/strcat_unittest.cc
new file mode 100644
index 0000000000..cf2db5180f
--- /dev/null
+++ b/base/strings/strcat_unittest.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 "base/strings/strcat.h"
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+TEST(StrCat, 8Bit) {
+ EXPECT_EQ("", StrCat({""}));
+ EXPECT_EQ("1", StrCat({"1"}));
+ EXPECT_EQ("122", StrCat({"1", "22"}));
+ EXPECT_EQ("122333", StrCat({"1", "22", "333"}));
+ EXPECT_EQ("1223334444", StrCat({"1", "22", "333", "4444"}));
+ EXPECT_EQ("122333444455555", StrCat({"1", "22", "333", "4444", "55555"}));
+}
+
+TEST(StrCat, 16Bit) {
+ string16 arg1 = ASCIIToUTF16("1");
+ string16 arg2 = ASCIIToUTF16("22");
+ string16 arg3 = ASCIIToUTF16("333");
+
+ EXPECT_EQ(ASCIIToUTF16(""), StrCat({string16()}));
+ EXPECT_EQ(ASCIIToUTF16("1"), StrCat({arg1}));
+ EXPECT_EQ(ASCIIToUTF16("122"), StrCat({arg1, arg2}));
+ EXPECT_EQ(ASCIIToUTF16("122333"), StrCat({arg1, arg2, arg3}));
+}
+
+TEST(StrAppend, 8Bit) {
+ std::string result;
+
+ result = "foo";
+ StrAppend(&result, {std::string()});
+ EXPECT_EQ("foo", result);
+
+ result = "foo";
+ StrAppend(&result, {"1"});
+ EXPECT_EQ("foo1", result);
+
+ result = "foo";
+ StrAppend(&result, {"1", "22", "333"});
+ EXPECT_EQ("foo122333", result);
+}
+
+TEST(StrAppend, 16Bit) {
+ string16 arg1 = ASCIIToUTF16("1");
+ string16 arg2 = ASCIIToUTF16("22");
+ string16 arg3 = ASCIIToUTF16("333");
+
+ string16 result;
+
+ result = ASCIIToUTF16("foo");
+ StrAppend(&result, {string16()});
+ EXPECT_EQ(ASCIIToUTF16("foo"), result);
+
+ result = ASCIIToUTF16("foo");
+ StrAppend(&result, {arg1});
+ EXPECT_EQ(ASCIIToUTF16("foo1"), result);
+
+ result = ASCIIToUTF16("foo");
+ StrAppend(&result, {arg1, arg2, arg3});
+ EXPECT_EQ(ASCIIToUTF16("foo122333"), result);
+}
+
+} // namespace base
diff --git a/base/strings/string16.cc b/base/strings/string16.cc
index f4c8cf7460..2abb0e5df5 100644
--- a/base/strings/string16.cc
+++ b/base/strings/string16.cc
@@ -4,7 +4,7 @@
#include "base/strings/string16.h"
-#if defined(WCHAR_T_IS_UTF16)
+#if defined(WCHAR_T_IS_UTF16) && !defined(_AIX)
#error This file should not be used on 2-byte wchar_t systems
// If this winds up being needed on 2-byte wchar_t systems, either the
@@ -47,7 +47,7 @@ const char16* c16memchr(const char16* s, char16 c, size_t n) {
}
++s;
}
- return 0;
+ return nullptr;
}
char16* c16memmove(char16* s1, const char16* s2, size_t n) {
@@ -67,6 +67,8 @@ char16* c16memset(char16* s, char16 c, size_t n) {
return s_orig;
}
+namespace string16_internals {
+
std::ostream& operator<<(std::ostream& out, const string16& str) {
return out << UTF16ToUTF8(str);
}
@@ -75,8 +77,11 @@ void PrintTo(const string16& str, std::ostream* out) {
*out << str;
}
+} // namespace string16_internals
+
} // namespace base
-template class std::basic_string<base::char16, base::string16_char_traits>;
+template class std::
+ basic_string<base::char16, base::string16_internals::string16_char_traits>;
#endif // WCHAR_T_IS_UTF32
diff --git a/base/strings/string16.h b/base/strings/string16.h
index 30f4e3eec0..a86baa25e6 100644
--- a/base/strings/string16.h
+++ b/base/strings/string16.h
@@ -42,7 +42,6 @@ namespace base {
typedef wchar_t char16;
typedef std::wstring string16;
-typedef std::char_traits<wchar_t> string16_char_traits;
} // namespace base
@@ -64,6 +63,11 @@ BASE_EXPORT char16* c16memmove(char16* s1, const char16* s2, size_t n);
BASE_EXPORT char16* c16memcpy(char16* s1, const char16* s2, size_t n);
BASE_EXPORT char16* c16memset(char16* s, char16 c, size_t n);
+// This namespace contains the implementation of base::string16 along with
+// things that need to be found via argument-dependent lookup from a
+// base::string16.
+namespace string16_internals {
+
struct string16_char_traits {
typedef char16 char_type;
typedef int int_type;
@@ -134,7 +138,13 @@ struct string16_char_traits {
}
};
-typedef std::basic_string<char16, base::string16_char_traits> string16;
+} // namespace string16_internals
+
+typedef std::basic_string<char16,
+ base::string16_internals::string16_char_traits>
+ string16;
+
+namespace string16_internals {
BASE_EXPORT extern std::ostream& operator<<(std::ostream& out,
const string16& str);
@@ -142,6 +152,8 @@ BASE_EXPORT extern std::ostream& operator<<(std::ostream& out,
// This is required by googletest to print a readable output on test failures.
BASE_EXPORT extern void PrintTo(const string16& str, std::ostream* out);
+} // namespace string16_internals
+
} // namespace base
// The string class will be explicitly instantiated only once, in string16.cc.
@@ -183,8 +195,9 @@ BASE_EXPORT extern void PrintTo(const string16& str, std::ostream* out);
//
// TODO(mark): File this bug with Apple and update this note with a bug number.
-extern template
-class BASE_EXPORT std::basic_string<base::char16, base::string16_char_traits>;
+extern template class BASE_EXPORT
+ std::basic_string<base::char16,
+ base::string16_internals::string16_char_traits>;
// Specialize std::hash for base::string16. Although the style guide forbids
// this in general, it is necessary for consistency with WCHAR_T_IS_UTF16
diff --git a/base/strings/string16_unittest.nc b/base/strings/string16_unittest.nc
new file mode 100644
index 0000000000..5186a450af
--- /dev/null
+++ b/base/strings/string16_unittest.nc
@@ -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.
+
+// This is a "No Compile Test".
+// http://dev.chromium.org/developers/testing/no-compile-tests
+
+#include "base/strings/string16.h"
+
+#if defined(NCTEST_NO_KOENIG_LOOKUP_FOR_STRING16) // [r"use of undeclared identifier 'ShouldNotBeFound'"]
+
+// base::string16 is declared as a typedef. It should not cause other functions
+// in base to be found via Argument-dependent lookup.
+
+namespace base {
+void ShouldNotBeFound(const base::string16& arg) {}
+}
+
+// Intentionally not in base:: namespace.
+void WontCompile() {
+ base::string16 s;
+ ShouldNotBeFound(s);
+}
+
+#endif
diff --git a/base/strings/string_number_conversions.cc b/base/strings/string_number_conversions.cc
index adb4bdb8d2..3c5bc3529f 100644
--- a/base/strings/string_number_conversions.cc
+++ b/base/strings/string_number_conversions.cc
@@ -15,6 +15,7 @@
#include "base/logging.h"
#include "base/numerics/safe_math.h"
#include "base/scoped_clear_errno.h"
+#include "base/strings/utf_string_conversions.h"
namespace base {
@@ -282,23 +283,6 @@ typedef BaseHexIteratorRangeToInt64Traits<StringPiece::const_iterator>
typedef BaseHexIteratorRangeToUInt64Traits<StringPiece::const_iterator>
HexIteratorRangeToUInt64Traits;
-template <typename STR>
-bool HexStringToBytesT(const STR& input, std::vector<uint8_t>* output) {
- DCHECK_EQ(output->size(), 0u);
- size_t count = input.size();
- if (count == 0 || (count % 2) != 0)
- return false;
- for (uintptr_t i = 0; i < count / 2; ++i) {
- uint8_t msb = 0; // most significant 4 bits
- uint8_t lsb = 0; // least significant 4 bits
- if (!CharToDigit<16>(input[i * 2], &msb) ||
- !CharToDigit<16>(input[i * 2 + 1], &lsb))
- return false;
- output->push_back((msb << 4) | lsb);
- }
- return true;
-}
-
template <typename VALUE, int BASE>
class StringPieceToNumberTraits
: public BaseIteratorRangeToNumberTraits<StringPiece::const_iterator,
@@ -307,7 +291,7 @@ class StringPieceToNumberTraits
};
template <typename VALUE>
-bool StringToIntImpl(const StringPiece& input, VALUE* output) {
+bool StringToIntImpl(StringPiece input, VALUE* output) {
return IteratorRangeToNumber<StringPieceToNumberTraits<VALUE, 10> >::Invoke(
input.begin(), input.end(), output);
}
@@ -320,54 +304,62 @@ class StringPiece16ToNumberTraits
};
template <typename VALUE>
-bool String16ToIntImpl(const StringPiece16& input, VALUE* output) {
+bool String16ToIntImpl(StringPiece16 input, VALUE* output) {
return IteratorRangeToNumber<StringPiece16ToNumberTraits<VALUE, 10> >::Invoke(
input.begin(), input.end(), output);
}
} // namespace
-std::string IntToString(int value) {
+std::string NumberToString(int value) {
return IntToStringT<std::string, int>::IntToString(value);
}
-string16 IntToString16(int value) {
+string16 NumberToString16(int value) {
return IntToStringT<string16, int>::IntToString(value);
}
-std::string UintToString(unsigned int value) {
- return IntToStringT<std::string, unsigned int>::IntToString(value);
+std::string NumberToString(unsigned value) {
+ return IntToStringT<std::string, unsigned>::IntToString(value);
+}
+
+string16 NumberToString16(unsigned value) {
+ return IntToStringT<string16, unsigned>::IntToString(value);
+}
+
+std::string NumberToString(long value) {
+ return IntToStringT<std::string, long>::IntToString(value);
}
-string16 UintToString16(unsigned int value) {
- return IntToStringT<string16, unsigned int>::IntToString(value);
+string16 NumberToString16(long value) {
+ return IntToStringT<string16, long>::IntToString(value);
}
-std::string Int64ToString(int64_t value) {
- return IntToStringT<std::string, int64_t>::IntToString(value);
+std::string NumberToString(unsigned long value) {
+ return IntToStringT<std::string, unsigned long>::IntToString(value);
}
-string16 Int64ToString16(int64_t value) {
- return IntToStringT<string16, int64_t>::IntToString(value);
+string16 NumberToString16(unsigned long value) {
+ return IntToStringT<string16, unsigned long>::IntToString(value);
}
-std::string Uint64ToString(uint64_t value) {
- return IntToStringT<std::string, uint64_t>::IntToString(value);
+std::string NumberToString(long long value) {
+ return IntToStringT<std::string, long long>::IntToString(value);
}
-string16 Uint64ToString16(uint64_t value) {
- return IntToStringT<string16, uint64_t>::IntToString(value);
+string16 NumberToString16(long long value) {
+ return IntToStringT<string16, long long>::IntToString(value);
}
-std::string SizeTToString(size_t value) {
- return IntToStringT<std::string, size_t>::IntToString(value);
+std::string NumberToString(unsigned long long value) {
+ return IntToStringT<std::string, unsigned long long>::IntToString(value);
}
-string16 SizeTToString16(size_t value) {
- return IntToStringT<string16, size_t>::IntToString(value);
+string16 NumberToString16(unsigned long long value) {
+ return IntToStringT<string16, unsigned long long>::IntToString(value);
}
-std::string DoubleToString(double value) {
+std::string NumberToString(double value) {
auto ret = std::to_string(value);
// If this returned an integer, don't do anything.
if (ret.find('.') == std::string::npos) {
@@ -382,43 +374,60 @@ std::string DoubleToString(double value) {
return ret;
}
-bool StringToInt(const StringPiece& input, int* output) {
+base::string16 NumberToString16(double value) {
+ auto tmp = std::to_string(value);
+ base::string16 ret(tmp.c_str(), tmp.c_str() + tmp.length());
+
+ // 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(StringPiece input, int* output) {
return StringToIntImpl(input, output);
}
-bool StringToInt(const StringPiece16& input, int* output) {
+bool StringToInt(StringPiece16 input, int* output) {
return String16ToIntImpl(input, output);
}
-bool StringToUint(const StringPiece& input, unsigned* output) {
+bool StringToUint(StringPiece input, unsigned* output) {
return StringToIntImpl(input, output);
}
-bool StringToUint(const StringPiece16& input, unsigned* output) {
+bool StringToUint(StringPiece16 input, unsigned* output) {
return String16ToIntImpl(input, output);
}
-bool StringToInt64(const StringPiece& input, int64_t* output) {
+bool StringToInt64(StringPiece input, int64_t* output) {
return StringToIntImpl(input, output);
}
-bool StringToInt64(const StringPiece16& input, int64_t* output) {
+bool StringToInt64(StringPiece16 input, int64_t* output) {
return String16ToIntImpl(input, output);
}
-bool StringToUint64(const StringPiece& input, uint64_t* output) {
+bool StringToUint64(StringPiece input, uint64_t* output) {
return StringToIntImpl(input, output);
}
-bool StringToUint64(const StringPiece16& input, uint64_t* output) {
+bool StringToUint64(StringPiece16 input, uint64_t* output) {
return String16ToIntImpl(input, output);
}
-bool StringToSizeT(const StringPiece& input, size_t* output) {
+bool StringToSizeT(StringPiece input, size_t* output) {
return StringToIntImpl(input, output);
}
-bool StringToSizeT(const StringPiece16& input, size_t* output) {
+bool StringToSizeT(StringPiece16 input, size_t* output) {
return String16ToIntImpl(input, output);
}
@@ -463,28 +472,41 @@ std::string HexEncode(const void* bytes, size_t size) {
return ret;
}
-bool HexStringToInt(const StringPiece& input, int* output) {
+bool HexStringToInt(StringPiece input, int* output) {
return IteratorRangeToNumber<HexIteratorRangeToIntTraits>::Invoke(
input.begin(), input.end(), output);
}
-bool HexStringToUInt(const StringPiece& input, uint32_t* output) {
+bool HexStringToUInt(StringPiece input, uint32_t* output) {
return IteratorRangeToNumber<HexIteratorRangeToUIntTraits>::Invoke(
input.begin(), input.end(), output);
}
-bool HexStringToInt64(const StringPiece& input, int64_t* output) {
+bool HexStringToInt64(StringPiece input, int64_t* output) {
return IteratorRangeToNumber<HexIteratorRangeToInt64Traits>::Invoke(
input.begin(), input.end(), output);
}
-bool HexStringToUInt64(const StringPiece& input, uint64_t* output) {
+bool HexStringToUInt64(StringPiece input, uint64_t* output) {
return IteratorRangeToNumber<HexIteratorRangeToUInt64Traits>::Invoke(
input.begin(), input.end(), output);
}
-bool HexStringToBytes(const std::string& input, std::vector<uint8_t>* output) {
- return HexStringToBytesT(input, output);
+bool HexStringToBytes(StringPiece input, std::vector<uint8_t>* output) {
+ DCHECK_EQ(output->size(), 0u);
+ size_t count = input.size();
+ if (count == 0 || (count % 2) != 0)
+ return false;
+ for (uintptr_t i = 0; i < count / 2; ++i) {
+ uint8_t msb = 0; // most significant 4 bits
+ uint8_t lsb = 0; // least significant 4 bits
+ if (!CharToDigit<16>(input[i * 2], &msb) ||
+ !CharToDigit<16>(input[i * 2 + 1], &lsb)) {
+ return false;
+ }
+ output->push_back((msb << 4) | lsb);
+ }
+ return true;
}
} // namespace base
diff --git a/base/strings/string_number_conversions.h b/base/strings/string_number_conversions.h
index a95544e88f..057b60abc6 100644
--- a/base/strings/string_number_conversions.h
+++ b/base/strings/string_number_conversions.h
@@ -14,6 +14,7 @@
#include "base/base_export.h"
#include "base/strings/string16.h"
#include "base/strings/string_piece.h"
+#include "build/build_config.h"
// ----------------------------------------------------------------------------
// IMPORTANT MESSAGE FROM YOUR SPONSOR
@@ -39,25 +40,44 @@ namespace base {
// Number -> string conversions ------------------------------------------------
-BASE_EXPORT std::string IntToString(int value);
-BASE_EXPORT string16 IntToString16(int value);
-
-BASE_EXPORT std::string UintToString(unsigned value);
-BASE_EXPORT string16 UintToString16(unsigned value);
-
-BASE_EXPORT std::string Int64ToString(int64_t value);
-BASE_EXPORT string16 Int64ToString16(int64_t value);
-
-BASE_EXPORT std::string Uint64ToString(uint64_t value);
-BASE_EXPORT string16 Uint64ToString16(uint64_t value);
-
-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);
+// Ignores locale! see warning above.
+BASE_EXPORT std::string NumberToString(int value);
+BASE_EXPORT string16 NumberToString16(int value);
+BASE_EXPORT std::string NumberToString(unsigned int value);
+BASE_EXPORT string16 NumberToString16(unsigned int value);
+BASE_EXPORT std::string NumberToString(long value);
+BASE_EXPORT string16 NumberToString16(long value);
+BASE_EXPORT std::string NumberToString(unsigned long value);
+BASE_EXPORT string16 NumberToString16(unsigned long value);
+BASE_EXPORT std::string NumberToString(long long value);
+BASE_EXPORT string16 NumberToString16(long long value);
+BASE_EXPORT std::string NumberToString(unsigned long long value);
+BASE_EXPORT string16 NumberToString16(unsigned long long value);
+BASE_EXPORT std::string NumberToString(double value);
+BASE_EXPORT string16 NumberToString16(double value);
+
+// Type-specific naming for backwards compatibility.
+//
+// TODO(brettw) these should be removed and callers converted to the overloaded
+// "NumberToString" variant.
+inline std::string IntToString(int value) {
+ return NumberToString(value);
+}
+inline string16 IntToString16(int value) {
+ return NumberToString16(value);
+}
+inline std::string UintToString(unsigned value) {
+ return NumberToString(value);
+}
+inline string16 UintToString16(unsigned value) {
+ return NumberToString16(value);
+}
+inline std::string Int64ToString(int64_t value) {
+ return NumberToString(value);
+}
+inline string16 Int64ToString16(int64_t value) {
+ return NumberToString16(value);
+}
// String -> number conversions ------------------------------------------------
@@ -77,22 +97,21 @@ BASE_EXPORT std::string DoubleToString(double value);
// - Empty string. |*output| will be set to 0.
// WARNING: Will write to |output| even when returning false.
// Read the comments above carefully.
-BASE_EXPORT bool StringToInt(const StringPiece& input, int* output);
-BASE_EXPORT bool StringToInt(const StringPiece16& input, int* output);
+BASE_EXPORT bool StringToInt(StringPiece input, int* output);
+BASE_EXPORT bool StringToInt(StringPiece16 input, int* output);
-BASE_EXPORT bool StringToUint(const StringPiece& input, unsigned* output);
-BASE_EXPORT bool StringToUint(const StringPiece16& input, unsigned* output);
+BASE_EXPORT bool StringToUint(StringPiece input, unsigned* output);
+BASE_EXPORT bool StringToUint(StringPiece16 input, unsigned* output);
-BASE_EXPORT bool StringToInt64(const StringPiece& input, int64_t* output);
-BASE_EXPORT bool StringToInt64(const StringPiece16& input, int64_t* output);
+BASE_EXPORT bool StringToInt64(StringPiece input, int64_t* output);
+BASE_EXPORT bool StringToInt64(StringPiece16 input, int64_t* output);
-BASE_EXPORT bool StringToUint64(const StringPiece& input, uint64_t* output);
-BASE_EXPORT bool StringToUint64(const StringPiece16& input, uint64_t* output);
+BASE_EXPORT bool StringToUint64(StringPiece input, uint64_t* output);
+BASE_EXPORT bool StringToUint64(StringPiece16 input, uint64_t* output);
-BASE_EXPORT bool StringToSizeT(const StringPiece& input, size_t* output);
-BASE_EXPORT bool StringToSizeT(const StringPiece16& input, size_t* output);
+BASE_EXPORT bool StringToSizeT(StringPiece input, size_t* output);
+BASE_EXPORT bool StringToSizeT(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
@@ -116,30 +135,30 @@ BASE_EXPORT std::string HexEncode(const void* bytes, size_t size);
// Best effort conversion, see StringToInt above for restrictions.
// Will only successful parse hex values that will fit into |output|, i.e.
// -0x80000000 < |input| < 0x7FFFFFFF.
-BASE_EXPORT bool HexStringToInt(const StringPiece& input, int* output);
+BASE_EXPORT bool HexStringToInt(StringPiece input, int* output);
// Best effort conversion, see StringToInt above for restrictions.
// Will only successful parse hex values that will fit into |output|, i.e.
// 0x00000000 < |input| < 0xFFFFFFFF.
// The string is not required to start with 0x.
-BASE_EXPORT bool HexStringToUInt(const StringPiece& input, uint32_t* output);
+BASE_EXPORT bool HexStringToUInt(StringPiece input, uint32_t* output);
// Best effort conversion, see StringToInt above for restrictions.
// Will only successful parse hex values that will fit into |output|, i.e.
// -0x8000000000000000 < |input| < 0x7FFFFFFFFFFFFFFF.
-BASE_EXPORT bool HexStringToInt64(const StringPiece& input, int64_t* output);
+BASE_EXPORT bool HexStringToInt64(StringPiece input, int64_t* output);
// Best effort conversion, see StringToInt above for restrictions.
// Will only successful parse hex values that will fit into |output|, i.e.
// 0x0000000000000000 < |input| < 0xFFFFFFFFFFFFFFFF.
// The string is not required to start with 0x.
-BASE_EXPORT bool HexStringToUInt64(const StringPiece& input, uint64_t* output);
+BASE_EXPORT bool HexStringToUInt64(StringPiece input, uint64_t* output);
// Similar to the previous functions, except that output is a vector of bytes.
// |*output| will contain as many bytes as were successfully parsed prior to the
// error. There is no overflow, but input.size() must be evenly divisible by 2.
// Leading 0x or +/- are not allowed.
-BASE_EXPORT bool HexStringToBytes(const std::string& input,
+BASE_EXPORT bool HexStringToBytes(StringPiece input,
std::vector<uint8_t>* output);
} // namespace base
diff --git a/base/strings/string_number_conversions_fuzzer.cc b/base/strings/string_number_conversions_fuzzer.cc
new file mode 100644
index 0000000000..2fed7de9c5
--- /dev/null
+++ b/base/strings/string_number_conversions_fuzzer.cc
@@ -0,0 +1,67 @@
+// 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 <stddef.h>
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+
+// Entry point for LibFuzzer.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ base::StringPiece string_piece_input(reinterpret_cast<const char*>(data),
+ size);
+ std::string string_input(reinterpret_cast<const char*>(data), size);
+
+ int out_int;
+ base::StringToInt(string_piece_input, &out_int);
+ unsigned out_uint;
+ base::StringToUint(string_piece_input, &out_uint);
+ int64_t out_int64;
+ base::StringToInt64(string_piece_input, &out_int64);
+ uint64_t out_uint64;
+ base::StringToUint64(string_piece_input, &out_uint64);
+ size_t out_size;
+ base::StringToSizeT(string_piece_input, &out_size);
+
+ // Test for StringPiece16 if size is even.
+ if (size % 2 == 0) {
+ base::StringPiece16 string_piece_input16(
+ reinterpret_cast<const base::char16*>(data), size / 2);
+
+ base::StringToInt(string_piece_input16, &out_int);
+ base::StringToUint(string_piece_input16, &out_uint);
+ base::StringToInt64(string_piece_input16, &out_int64);
+ base::StringToUint64(string_piece_input16, &out_uint64);
+ base::StringToSizeT(string_piece_input16, &out_size);
+ }
+
+ double out_double;
+ base::StringToDouble(string_input, &out_double);
+
+ base::HexStringToInt(string_piece_input, &out_int);
+ base::HexStringToUInt(string_piece_input, &out_uint);
+ base::HexStringToInt64(string_piece_input, &out_int64);
+ base::HexStringToUInt64(string_piece_input, &out_uint64);
+ std::vector<uint8_t> out_bytes;
+ base::HexStringToBytes(string_piece_input, &out_bytes);
+
+ base::HexEncode(data, size);
+
+ // Convert the numbers back to strings.
+ base::NumberToString(out_int);
+ base::NumberToString16(out_int);
+ base::NumberToString(out_uint);
+ base::NumberToString16(out_uint);
+ base::NumberToString(out_int64);
+ base::NumberToString16(out_int64);
+ base::NumberToString(out_uint64);
+ base::NumberToString16(out_uint64);
+ base::NumberToString(out_double);
+ base::NumberToString16(out_double);
+
+ return 0;
+}
diff --git a/base/strings/string_number_conversions_unittest.cc b/base/strings/string_number_conversions_unittest.cc
index b4c3068f36..999585253a 100644
--- a/base/strings/string_number_conversions_unittest.cc
+++ b/base/strings/string_number_conversions_unittest.cc
@@ -25,7 +25,7 @@ namespace base {
namespace {
template <typename INT>
-struct IntToStringTest {
+struct NumberToStringTest {
INT num;
const char* sexpected;
const char* uexpected;
@@ -33,14 +33,14 @@ struct IntToStringTest {
} // namespace
-TEST(StringNumberConversionsTest, IntToString) {
- static const IntToStringTest<int> int_tests[] = {
- { 0, "0", "0" },
- { -1, "-1", "4294967295" },
- { std::numeric_limits<int>::max(), "2147483647", "2147483647" },
- { std::numeric_limits<int>::min(), "-2147483648", "2147483648" },
+TEST(StringNumberConversionsTest, NumberToString) {
+ static const NumberToStringTest<int> int_tests[] = {
+ {0, "0", "0"},
+ {-1, "-1", "4294967295"},
+ {std::numeric_limits<int>::max(), "2147483647", "2147483647"},
+ {std::numeric_limits<int>::min(), "-2147483648", "2147483648"},
};
- static const IntToStringTest<int64_t> int64_tests[] = {
+ static const NumberToStringTest<int64_t> int64_tests[] = {
{0, "0", "0"},
{-1, "-1", "18446744073709551615"},
{
@@ -52,18 +52,20 @@ TEST(StringNumberConversionsTest, IntToString) {
};
for (size_t i = 0; i < arraysize(int_tests); ++i) {
- const IntToStringTest<int>* test = &int_tests[i];
- EXPECT_EQ(IntToString(test->num), test->sexpected);
- EXPECT_EQ(IntToString16(test->num), UTF8ToUTF16(test->sexpected));
- EXPECT_EQ(UintToString(test->num), test->uexpected);
- EXPECT_EQ(UintToString16(test->num), UTF8ToUTF16(test->uexpected));
+ const NumberToStringTest<int>& test = int_tests[i];
+ EXPECT_EQ(NumberToString(test.num), test.sexpected);
+ EXPECT_EQ(NumberToString16(test.num), UTF8ToUTF16(test.sexpected));
+ EXPECT_EQ(NumberToString(static_cast<unsigned>(test.num)), test.uexpected);
+ EXPECT_EQ(NumberToString16(static_cast<unsigned>(test.num)),
+ UTF8ToUTF16(test.uexpected));
}
for (size_t i = 0; i < arraysize(int64_tests); ++i) {
- const IntToStringTest<int64_t>* test = &int64_tests[i];
- EXPECT_EQ(Int64ToString(test->num), test->sexpected);
- EXPECT_EQ(Int64ToString16(test->num), UTF8ToUTF16(test->sexpected));
- EXPECT_EQ(Uint64ToString(test->num), test->uexpected);
- EXPECT_EQ(Uint64ToString16(test->num), UTF8ToUTF16(test->uexpected));
+ const NumberToStringTest<int64_t>& test = int64_tests[i];
+ EXPECT_EQ(NumberToString(test.num), test.sexpected);
+ EXPECT_EQ(NumberToString16(test.num), UTF8ToUTF16(test.sexpected));
+ EXPECT_EQ(NumberToString(static_cast<uint64_t>(test.num)), test.uexpected);
+ EXPECT_EQ(NumberToString16(static_cast<uint64_t>(test.num)),
+ UTF8ToUTF16(test.uexpected));
}
}
@@ -79,7 +81,7 @@ TEST(StringNumberConversionsTest, Uint64ToString) {
};
for (size_t i = 0; i < arraysize(cases); ++i)
- EXPECT_EQ(cases[i].output, Uint64ToString(cases[i].input));
+ EXPECT_EQ(cases[i].output, NumberToString(cases[i].input));
}
TEST(StringNumberConversionsTest, SizeTToString) {
@@ -102,7 +104,7 @@ TEST(StringNumberConversionsTest, SizeTToString) {
};
for (size_t i = 0; i < arraysize(cases); ++i)
- EXPECT_EQ(cases[i].output, Uint64ToString(cases[i].input));
+ EXPECT_EQ(cases[i].output, NumberToString(cases[i].input));
}
TEST(StringNumberConversionsTest, StringToInt) {
@@ -815,23 +817,24 @@ TEST(StringNumberConversionsTest, DoubleToString) {
};
for (size_t i = 0; i < arraysize(cases); ++i) {
- EXPECT_EQ(cases[i].expected, DoubleToString(cases[i].input));
+ EXPECT_EQ(cases[i].expected, NumberToString(cases[i].input));
+ EXPECT_EQ(cases[i].expected, UTF16ToUTF8(NumberToString16(cases[i].input)));
}
// The following two values were seen in crashes in the wild.
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.0", DoubleToString(input));
+ EXPECT_EQ("1335179083776.0", NumberToString(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.0", DoubleToString(input));
+ EXPECT_EQ("1334890332160.0", NumberToString(input));
}
TEST(StringNumberConversionsTest, HexEncode) {
- std::string hex(HexEncode(NULL, 0));
+ std::string hex(HexEncode(nullptr, 0));
EXPECT_EQ(hex.length(), 0U);
unsigned char bytes[] = {0x01, 0xff, 0x02, 0xfe, 0x03, 0x80, 0x81};
hex = HexEncode(bytes, sizeof(bytes));
diff --git a/base/strings/string_piece.cc b/base/strings/string_piece.cc
index c26bb3652f..c82a223d14 100644
--- a/base/strings/string_piece.cc
+++ b/base/strings/string_piece.cc
@@ -44,7 +44,8 @@ bool operator==(const StringPiece& x, const StringPiece& y) {
if (x.size() != y.size())
return false;
- return StringPiece::wordmemcmp(x.data(), y.data(), x.size()) == 0;
+ return CharTraits<StringPiece::value_type>::compare(x.data(), y.data(),
+ x.size()) == 0;
}
std::ostream& operator<<(std::ostream& o, const StringPiece& piece) {
diff --git a/base/strings/string_piece.h b/base/strings/string_piece.h
index 5333640fee..775ea7cf00 100644
--- a/base/strings/string_piece.h
+++ b/base/strings/string_piece.h
@@ -29,14 +29,12 @@
#include "base/base_export.h"
#include "base/logging.h"
+#include "base/strings/char_traits.h"
#include "base/strings/string16.h"
+#include "base/strings/string_piece_forward.h"
namespace base {
-template <typename STRING_TYPE> class BasicStringPiece;
-typedef BasicStringPiece<std::string> StringPiece;
-typedef BasicStringPiece<string16> StringPiece16;
-
// internal --------------------------------------------------------------------
// Many of the StringPiece functions use different implementations for the
@@ -178,13 +176,16 @@ template <typename STRING_TYPE> class BasicStringPiece {
// We provide non-explicit singleton constructors so users can pass
// in a "const char*" or a "string" wherever a "StringPiece" is
// expected (likewise for char16, string16, StringPiece16).
- BasicStringPiece() : ptr_(NULL), length_(0) {}
- BasicStringPiece(const value_type* str)
- : ptr_(str),
- length_((str == NULL) ? 0 : STRING_TYPE::traits_type::length(str)) {}
+ constexpr BasicStringPiece() : ptr_(NULL), length_(0) {}
+ // TODO(dcheng): Construction from nullptr is not allowed for
+ // std::basic_string_view, so remove the special handling for it.
+ // Note: This doesn't just use STRING_TYPE::traits_type::length(), since that
+ // isn't constexpr until C++17.
+ constexpr BasicStringPiece(const value_type* str)
+ : ptr_(str), length_(!str ? 0 : CharTraits<value_type>::length(str)) {}
BasicStringPiece(const STRING_TYPE& str)
: ptr_(str.data()), length_(str.size()) {}
- BasicStringPiece(const value_type* offset, size_type len)
+ constexpr BasicStringPiece(const value_type* offset, size_type len)
: ptr_(offset), length_(len) {}
BasicStringPiece(const typename STRING_TYPE::const_iterator& begin,
const typename STRING_TYPE::const_iterator& end) {
@@ -204,9 +205,9 @@ template <typename STRING_TYPE> class BasicStringPiece {
// returned buffer may or may not be null terminated. Therefore it is
// typically a mistake to pass data() to a routine that expects a NUL
// terminated string.
- const value_type* data() const { return ptr_; }
- size_type size() const { return length_; }
- size_type length() const { return length_; }
+ constexpr const value_type* data() const { return ptr_; }
+ constexpr size_type size() const { return length_; }
+ constexpr size_type length() const { return length_; }
bool empty() const { return length_ == 0; }
void clear() {
@@ -222,21 +223,34 @@ template <typename STRING_TYPE> class BasicStringPiece {
length_ = str ? STRING_TYPE::traits_type::length(str) : 0;
}
- value_type operator[](size_type i) const { return ptr_[i]; }
- value_type front() const { return ptr_[0]; }
- value_type back() const { return ptr_[length_ - 1]; }
+ constexpr value_type operator[](size_type i) const {
+ CHECK(i < length_);
+ return ptr_[i];
+ }
+
+ value_type front() const {
+ CHECK_NE(0UL, length_);
+ return ptr_[0];
+ }
+
+ value_type back() const {
+ CHECK_NE(0UL, length_);
+ return ptr_[length_ - 1];
+ }
- void remove_prefix(size_type n) {
+ constexpr void remove_prefix(size_type n) {
+ CHECK(n <= length_);
ptr_ += n;
length_ -= n;
}
- void remove_suffix(size_type n) {
+ constexpr void remove_suffix(size_type n) {
+ CHECK(n <= length_);
length_ -= n;
}
- int compare(const BasicStringPiece<STRING_TYPE>& x) const {
- int r = wordmemcmp(
+ constexpr int compare(BasicStringPiece x) const noexcept {
+ int r = CharTraits<value_type>::compare(
ptr_, x.ptr_, (length_ < x.length_ ? length_ : x.length_));
if (r == 0) {
if (length_ < x.length_) r = -1;
@@ -265,12 +279,6 @@ template <typename STRING_TYPE> class BasicStringPiece {
size_type max_size() const { return length_; }
size_type capacity() const { return length_; }
- static int wordmemcmp(const value_type* p,
- const value_type* p2,
- size_type N) {
- return STRING_TYPE::traits_type::compare(p, p2, N);
- }
-
// Sets the value of the given string target type to be the current string.
// This saves a temporary over doing |a = b.as_string()|
void CopyToString(STRING_TYPE* target) const {
@@ -286,16 +294,18 @@ template <typename STRING_TYPE> class BasicStringPiece {
}
// Does "this" start with "x"
- bool starts_with(const BasicStringPiece& x) const {
- return ((this->length_ >= x.length_) &&
- (wordmemcmp(this->ptr_, x.ptr_, x.length_) == 0));
+ constexpr bool starts_with(BasicStringPiece x) const noexcept {
+ return (
+ (this->length_ >= x.length_) &&
+ (CharTraits<value_type>::compare(this->ptr_, x.ptr_, x.length_) == 0));
}
// Does "this" end with "x"
- bool ends_with(const BasicStringPiece& x) const {
+ constexpr bool ends_with(BasicStringPiece x) const noexcept {
return ((this->length_ >= x.length_) &&
- (wordmemcmp(this->ptr_ + (this->length_-x.length_),
- x.ptr_, x.length_) == 0));
+ (CharTraits<value_type>::compare(
+ this->ptr_ + (this->length_ - x.length_), x.ptr_, x.length_) ==
+ 0));
}
// find: Search for a character or substring at a given offset.
@@ -362,7 +372,7 @@ template <typename STRING_TYPE> class BasicStringPiece {
protected:
const value_type* ptr_;
- size_type length_;
+ size_type length_;
};
template <typename STRING_TYPE>
@@ -385,7 +395,7 @@ inline bool operator!=(const StringPiece& x, const StringPiece& y) {
}
inline bool operator<(const StringPiece& x, const StringPiece& y) {
- const int r = StringPiece::wordmemcmp(
+ const int r = CharTraits<StringPiece::value_type>::compare(
x.data(), y.data(), (x.size() < y.size() ? x.size() : y.size()));
return ((r < 0) || ((r == 0) && (x.size() < y.size())));
}
@@ -408,7 +418,8 @@ inline bool operator==(const StringPiece16& x, const StringPiece16& y) {
if (x.size() != y.size())
return false;
- return StringPiece16::wordmemcmp(x.data(), y.data(), x.size()) == 0;
+ return CharTraits<StringPiece16::value_type>::compare(x.data(), y.data(),
+ x.size()) == 0;
}
inline bool operator!=(const StringPiece16& x, const StringPiece16& y) {
@@ -416,7 +427,7 @@ inline bool operator!=(const StringPiece16& x, const StringPiece16& y) {
}
inline bool operator<(const StringPiece16& x, const StringPiece16& y) {
- const int r = StringPiece16::wordmemcmp(
+ const int r = CharTraits<StringPiece16::value_type>::compare(
x.data(), y.data(), (x.size() < y.size() ? x.size() : y.size()));
return ((r < 0) || ((r == 0) && (x.size() < y.size())));
}
@@ -461,6 +472,11 @@ struct StringPiece16Hash {
HASH_STRING_PIECE(StringPiece16, sp16);
}
};
+struct WStringPieceHash {
+ std::size_t operator()(const WStringPiece& wsp) const {
+ HASH_STRING_PIECE(WStringPiece, wsp);
+ }
+};
} // namespace base
diff --git a/base/strings/string_piece_forward.h b/base/strings/string_piece_forward.h
new file mode 100644
index 0000000000..b50b9806c9
--- /dev/null
+++ b/base/strings/string_piece_forward.h
@@ -0,0 +1,24 @@
+// 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.
+
+// Forward declaration of StringPiece types from base/strings/string_piece.h
+
+#ifndef BASE_STRINGS_STRING_PIECE_FORWARD_H_
+#define BASE_STRINGS_STRING_PIECE_FORWARD_H_
+
+#include <string>
+
+#include "base/strings/string16.h"
+
+namespace base {
+
+template <typename STRING_TYPE>
+class BasicStringPiece;
+typedef BasicStringPiece<std::string> StringPiece;
+typedef BasicStringPiece<string16> StringPiece16;
+typedef BasicStringPiece<std::wstring> WStringPiece;
+
+} // namespace base
+
+#endif // BASE_STRINGS_STRING_PIECE_FORWARD_H_
diff --git a/base/strings/string_piece_unittest.cc b/base/strings/string_piece_unittest.cc
index 7dfd71116b..17d08973ec 100644
--- a/base/strings/string_piece_unittest.cc
+++ b/base/strings/string_piece_unittest.cc
@@ -158,7 +158,7 @@ TYPED_TEST(CommonStringPieceTest, CheckSTL) {
ASSERT_EQ(*d.data(), static_cast<typename TypeParam::value_type>('f'));
ASSERT_EQ(d.data()[5], static_cast<typename TypeParam::value_type>('r'));
- ASSERT_TRUE(e.data() == NULL);
+ ASSERT_EQ(e.data(), nullptr);
ASSERT_EQ(*a.begin(), static_cast<typename TypeParam::value_type>('a'));
ASSERT_EQ(*(b.begin() + 2), static_cast<typename TypeParam::value_type>('c'));
@@ -168,7 +168,7 @@ TYPED_TEST(CommonStringPieceTest, CheckSTL) {
ASSERT_EQ(*(b.rbegin() + 2),
static_cast<typename TypeParam::value_type>('a'));
ASSERT_EQ(*(c.rend() - 1), static_cast<typename TypeParam::value_type>('x'));
- ASSERT_TRUE(a.rbegin() + 26 == a.rend());
+ ASSERT_EQ(a.rbegin() + 26, a.rend());
ASSERT_EQ(a.size(), 26U);
ASSERT_EQ(b.size(), 3U);
@@ -179,16 +179,16 @@ TYPED_TEST(CommonStringPieceTest, CheckSTL) {
ASSERT_TRUE(!d.empty());
ASSERT_TRUE(d.begin() != d.end());
- ASSERT_TRUE(d.begin() + 6 == d.end());
+ ASSERT_EQ(d.begin() + 6, d.end());
ASSERT_TRUE(e.empty());
- ASSERT_TRUE(e.begin() == e.end());
+ ASSERT_EQ(e.begin(), e.end());
d.clear();
ASSERT_EQ(d.size(), 0U);
ASSERT_TRUE(d.empty());
- ASSERT_TRUE(d.data() == NULL);
- ASSERT_TRUE(d.begin() == d.end());
+ ASSERT_EQ(d.data(), nullptr);
+ ASSERT_EQ(d.begin(), d.end());
ASSERT_GE(a.max_size(), a.capacity());
ASSERT_GE(a.capacity(), a.size());
@@ -517,13 +517,13 @@ TYPED_TEST(CommonStringPieceTest, CheckCustom) {
// as_string
TypeParam s3(a.as_string().c_str(), 7); // Note, has an embedded NULL
- ASSERT_TRUE(c == s3);
+ ASSERT_EQ(c, s3);
TypeParam s4(e.as_string());
ASSERT_TRUE(s4.empty());
// operator STRING_TYPE()
TypeParam s5(TypeParam(a).c_str(), 7); // Note, has an embedded NULL
- ASSERT_TRUE(c == s5);
+ ASSERT_EQ(c, s5);
TypeParam s6(e);
ASSERT_TRUE(s6.empty());
}
@@ -591,12 +591,12 @@ TEST(StringPieceTest, CheckCustom) {
TYPED_TEST(CommonStringPieceTest, CheckNULL) {
// we used to crash here, but now we don't.
- BasicStringPiece<TypeParam> s(NULL);
- ASSERT_EQ(s.data(), (const typename TypeParam::value_type*)NULL);
+ BasicStringPiece<TypeParam> s(nullptr);
+ ASSERT_EQ(s.data(), nullptr);
ASSERT_EQ(s.size(), 0U);
- s.set(NULL);
- ASSERT_EQ(s.data(), (const typename TypeParam::value_type*)NULL);
+ s.set(nullptr);
+ ASSERT_EQ(s.data(), nullptr);
ASSERT_EQ(s.size(), 0U);
TypeParam str(s);
@@ -615,7 +615,7 @@ TYPED_TEST(CommonStringPieceTest, CheckComparisons2) {
BasicStringPiece<TypeParam> abc(alphabet);
// check comparison operations on strings longer than 4 bytes.
- ASSERT_TRUE(abc == BasicStringPiece<TypeParam>(alphabet));
+ ASSERT_EQ(abc, BasicStringPiece<TypeParam>(alphabet));
ASSERT_EQ(abc.compare(BasicStringPiece<TypeParam>(alphabet)), 0);
ASSERT_TRUE(abc < BasicStringPiece<TypeParam>(alphabet_z));
@@ -650,8 +650,8 @@ TYPED_TEST(CommonStringPieceTest, StringCompareNotAmbiguous) {
TYPED_TEST(CommonStringPieceTest, HeterogenousStringPieceEquals) {
TypeParam hello(TestFixture::as_string("hello"));
- ASSERT_TRUE(BasicStringPiece<TypeParam>(hello) == hello);
- ASSERT_TRUE(hello.c_str() == BasicStringPiece<TypeParam>(hello));
+ ASSERT_EQ(BasicStringPiece<TypeParam>(hello), hello);
+ ASSERT_EQ(hello.c_str(), BasicStringPiece<TypeParam>(hello));
}
// string16-specific stuff
@@ -684,20 +684,122 @@ TYPED_TEST(CommonStringPieceTest, CheckConstructors) {
TypeParam str(TestFixture::as_string("hello world"));
TypeParam empty;
- ASSERT_TRUE(str == BasicStringPiece<TypeParam>(str));
- ASSERT_TRUE(str == BasicStringPiece<TypeParam>(str.c_str()));
+ ASSERT_EQ(str, BasicStringPiece<TypeParam>(str));
+ ASSERT_EQ(str, BasicStringPiece<TypeParam>(str.c_str()));
ASSERT_TRUE(TestFixture::as_string("hello") ==
BasicStringPiece<TypeParam>(str.c_str(), 5));
- ASSERT_TRUE(empty == BasicStringPiece<TypeParam>(str.c_str(),
- static_cast<typename BasicStringPiece<TypeParam>::size_type>(0)));
- ASSERT_TRUE(empty == BasicStringPiece<TypeParam>(NULL));
- ASSERT_TRUE(empty == BasicStringPiece<TypeParam>(NULL,
- static_cast<typename BasicStringPiece<TypeParam>::size_type>(0)));
- ASSERT_TRUE(empty == BasicStringPiece<TypeParam>());
- ASSERT_TRUE(str == BasicStringPiece<TypeParam>(str.begin(), str.end()));
- ASSERT_TRUE(empty == BasicStringPiece<TypeParam>(str.begin(), str.begin()));
- ASSERT_TRUE(empty == BasicStringPiece<TypeParam>(empty));
- ASSERT_TRUE(empty == BasicStringPiece<TypeParam>(empty.begin(), empty.end()));
+ ASSERT_EQ(
+ empty,
+ BasicStringPiece<TypeParam>(
+ str.c_str(),
+ static_cast<typename BasicStringPiece<TypeParam>::size_type>(0)));
+ ASSERT_EQ(empty, BasicStringPiece<TypeParam>(nullptr));
+ ASSERT_TRUE(
+ empty ==
+ BasicStringPiece<TypeParam>(
+ nullptr,
+ static_cast<typename BasicStringPiece<TypeParam>::size_type>(0)));
+ ASSERT_EQ(empty, BasicStringPiece<TypeParam>());
+ ASSERT_EQ(str, BasicStringPiece<TypeParam>(str.begin(), str.end()));
+ ASSERT_EQ(empty, BasicStringPiece<TypeParam>(str.begin(), str.begin()));
+ ASSERT_EQ(empty, BasicStringPiece<TypeParam>(empty));
+ ASSERT_EQ(empty, BasicStringPiece<TypeParam>(empty.begin(), empty.end()));
+}
+
+TEST(StringPieceTest, ConstexprCtor) {
+ {
+ constexpr StringPiece piece;
+ std::ignore = piece;
+ }
+
+ {
+ constexpr StringPiece piece("abc");
+ std::ignore = piece;
+ }
+
+ {
+ constexpr StringPiece piece("abc", 2);
+ std::ignore = piece;
+ }
+}
+
+TEST(StringPieceTest, ConstexprData) {
+ {
+ constexpr StringPiece piece;
+ static_assert(piece.data() == nullptr, "");
+ }
+
+ {
+ constexpr StringPiece piece("abc");
+ static_assert(piece.data()[0] == 'a', "");
+ static_assert(piece.data()[1] == 'b', "");
+ static_assert(piece.data()[2] == 'c', "");
+ }
+
+ {
+ constexpr StringPiece piece("def", 2);
+ static_assert(piece.data()[0] == 'd', "");
+ static_assert(piece.data()[1] == 'e', "");
+ }
+}
+
+TEST(StringPieceTest, ConstexprSize) {
+ {
+ constexpr StringPiece piece;
+ static_assert(piece.size() == 0, "");
+ }
+
+ {
+ constexpr StringPiece piece("abc");
+ static_assert(piece.size() == 3, "");
+ }
+
+ {
+ constexpr StringPiece piece("def", 2);
+ static_assert(piece.size() == 2, "");
+ }
+}
+
+TEST(StringPieceTest, Compare) {
+ constexpr StringPiece piece = "def";
+
+ static_assert(piece.compare("ab") == 1, "");
+ static_assert(piece.compare("abc") == 1, "");
+ static_assert(piece.compare("abcd") == 1, "");
+ static_assert(piece.compare("de") == 1, "");
+ static_assert(piece.compare("def") == 0, "");
+ static_assert(piece.compare("defg") == -1, "");
+ static_assert(piece.compare("gh") == -1, "");
+ static_assert(piece.compare("ghi") == -1, "");
+ static_assert(piece.compare("ghij") == -1, "");
+}
+
+TEST(StringPieceTest, StartsWith) {
+ constexpr StringPiece piece("abc");
+
+ static_assert(piece.starts_with(""), "");
+ static_assert(piece.starts_with("a"), "");
+ static_assert(piece.starts_with("ab"), "");
+ static_assert(piece.starts_with("abc"), "");
+
+ static_assert(!piece.starts_with("b"), "");
+ static_assert(!piece.starts_with("bc"), "");
+
+ static_assert(!piece.starts_with("abcd"), "");
+}
+
+TEST(StringPieceTest, EndsWith) {
+ constexpr StringPiece piece("abc");
+
+ static_assert(piece.ends_with(""), "");
+ static_assert(piece.ends_with("c"), "");
+ static_assert(piece.ends_with("bc"), "");
+ static_assert(piece.ends_with("abc"), "");
+
+ static_assert(!piece.ends_with("a"), "");
+ static_assert(!piece.ends_with("ab"), "");
+
+ static_assert(!piece.ends_with("abcd"), "");
}
} // namespace base
diff --git a/base/strings/string_tokenizer.h b/base/strings/string_tokenizer.h
index 8defbac3b8..72fc01650f 100644
--- a/base/strings/string_tokenizer.h
+++ b/base/strings/string_tokenizer.h
@@ -17,14 +17,6 @@ namespace base {
// refer to the next token in the input string. The user may optionally
// configure the tokenizer to return delimiters.
//
-// Warning: be careful not to pass a C string into the 2-arg constructor:
-// StringTokenizer t("this is a test", " "); // WRONG
-// This will create a temporary std::string, save the begin() and end()
-// iterators, and then the string will be freed before we actually start
-// tokenizing it.
-// Instead, use a std::string or use the 3 arg constructor of CStringTokenizer.
-//
-//
// EXAMPLE 1:
//
// char input[] = "this is a test";
@@ -99,13 +91,19 @@ class StringTokenizerT {
RETURN_DELIMS = 1 << 0,
};
- // The string object must live longer than the tokenizer. (In particular this
- // should not be constructed with a temporary.)
+ // The string object must live longer than the tokenizer. In particular, this
+ // should not be constructed with a temporary. The deleted rvalue constructor
+ // blocks the most obvious instances of this (e.g. passing a string literal to
+ // the constructor), but caution must still be exercised.
StringTokenizerT(const str& string,
const str& delims) {
Init(string.begin(), string.end(), delims);
}
+ // Don't allow temporary strings to be used with string tokenizer, since
+ // Init() would otherwise save iterators to a temporary string.
+ StringTokenizerT(str&&, const str& delims) = delete;
+
StringTokenizerT(const_iterator string_begin,
const_iterator string_end,
const str& delims) {
@@ -147,9 +145,9 @@ class StringTokenizerT {
const_iterator token_begin() const { return token_begin_; }
const_iterator token_end() const { return token_end_; }
str token() const { return str(token_begin_, token_end_); }
- base::StringPiece token_piece() const {
- return base::StringPiece(&*token_begin_,
- std::distance(token_begin_, token_end_));
+ BasicStringPiece<str> token_piece() const {
+ return BasicStringPiece<str>(&*token_begin_,
+ std::distance(token_begin_, token_end_));
}
private:
diff --git a/base/strings/string_tokenizer_fuzzer.cc b/base/strings/string_tokenizer_fuzzer.cc
new file mode 100644
index 0000000000..917041bd7b
--- /dev/null
+++ b/base/strings/string_tokenizer_fuzzer.cc
@@ -0,0 +1,56 @@
+// 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 <stddef.h>
+#include <stdint.h>
+
+#include <string>
+
+#include "base/strings/string_tokenizer.h"
+
+void GetAllTokens(base::StringTokenizer& t) {
+ while (t.GetNext()) {
+ (void)t.token();
+ }
+}
+
+// Entry point for LibFuzzer.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ uint8_t size_t_bytes = sizeof(size_t);
+ if (size < size_t_bytes + 1) {
+ return 0;
+ }
+
+ // Calculate pattern size based on remaining bytes, otherwise fuzzing is
+ // inefficient with bailouts in most cases.
+ size_t pattern_size =
+ *reinterpret_cast<const size_t*>(data) % (size - size_t_bytes);
+
+ std::string pattern(reinterpret_cast<const char*>(data + size_t_bytes),
+ pattern_size);
+ std::string input(
+ reinterpret_cast<const char*>(data + size_t_bytes + pattern_size),
+ size - pattern_size - size_t_bytes);
+
+ // Allow quote_chars and options to be set. Otherwise full coverage
+ // won't be possible since IsQuote, FullGetNext and other functions
+ // won't be called.
+ base::StringTokenizer t(input, pattern);
+ GetAllTokens(t);
+
+ base::StringTokenizer t_quote(input, pattern);
+ t_quote.set_quote_chars("\"");
+ GetAllTokens(t_quote);
+
+ base::StringTokenizer t_options(input, pattern);
+ t_options.set_options(base::StringTokenizer::RETURN_DELIMS);
+ GetAllTokens(t_options);
+
+ base::StringTokenizer t_quote_and_options(input, pattern);
+ t_quote_and_options.set_quote_chars("\"");
+ t_quote_and_options.set_options(base::StringTokenizer::RETURN_DELIMS);
+ GetAllTokens(t_quote_and_options);
+
+ return 0;
+}
diff --git a/base/strings/string_util.cc b/base/strings/string_util.cc
index 71ae894dd6..32e5ff2b72 100644
--- a/base/strings/string_util.cc
+++ b/base/strings/string_util.cc
@@ -36,7 +36,7 @@ namespace {
// prevents other code that might accidentally use Singleton<string> from
// getting our internal one.
struct EmptyStrings {
- EmptyStrings() {}
+ EmptyStrings() = default;
const std::string s;
const string16 s16;
@@ -245,50 +245,37 @@ const string16& EmptyString16() {
return EmptyStrings::GetInstance()->s16;
}
-template<typename STR>
-bool ReplaceCharsT(const STR& input,
- const STR& replace_chars,
- const STR& replace_with,
- STR* output) {
- bool removed = false;
- size_t replace_length = replace_with.length();
-
- *output = input;
-
- size_t found = output->find_first_of(replace_chars);
- while (found != STR::npos) {
- removed = true;
- output->replace(found, 1, replace_with);
- found = output->find_first_of(replace_chars, found + replace_length);
- }
-
- return removed;
-}
+template <class StringType>
+bool ReplaceCharsT(const StringType& input,
+ BasicStringPiece<StringType> find_any_of_these,
+ BasicStringPiece<StringType> replace_with,
+ StringType* output);
bool ReplaceChars(const string16& input,
- const StringPiece16& replace_chars,
+ StringPiece16 replace_chars,
const string16& replace_with,
string16* output) {
- return ReplaceCharsT(input, replace_chars.as_string(), replace_with, output);
+ return ReplaceCharsT(input, replace_chars, StringPiece16(replace_with),
+ output);
}
bool ReplaceChars(const std::string& input,
- const StringPiece& replace_chars,
+ StringPiece replace_chars,
const std::string& replace_with,
std::string* output) {
- return ReplaceCharsT(input, replace_chars.as_string(), replace_with, output);
+ return ReplaceCharsT(input, replace_chars, StringPiece(replace_with), output);
}
bool RemoveChars(const string16& input,
- const StringPiece16& remove_chars,
+ StringPiece16 remove_chars,
string16* output) {
- return ReplaceChars(input, remove_chars.as_string(), string16(), output);
+ return ReplaceCharsT(input, remove_chars, StringPiece16(), output);
}
bool RemoveChars(const std::string& input,
- const StringPiece& remove_chars,
+ StringPiece remove_chars,
std::string* output) {
- return ReplaceChars(input, remove_chars.as_string(), std::string(), output);
+ return ReplaceCharsT(input, remove_chars, StringPiece(), output);
}
template<typename Str>
@@ -351,13 +338,13 @@ BasicStringPiece<Str> TrimStringPieceT(BasicStringPiece<Str> input,
}
StringPiece16 TrimString(StringPiece16 input,
- const StringPiece16& trim_chars,
+ StringPiece16 trim_chars,
TrimPositions positions) {
return TrimStringPieceT(input, trim_chars, positions);
}
StringPiece TrimString(StringPiece input,
- const StringPiece& trim_chars,
+ StringPiece trim_chars,
TrimPositions positions) {
return TrimStringPieceT(input, trim_chars, positions);
}
@@ -472,13 +459,11 @@ std::string CollapseWhitespaceASCII(const std::string& text,
return CollapseWhitespaceT(text, trim_sequences_with_line_breaks);
}
-bool ContainsOnlyChars(const StringPiece& input,
- const StringPiece& characters) {
+bool ContainsOnlyChars(StringPiece input, StringPiece characters) {
return input.find_first_not_of(characters) == StringPiece::npos;
}
-bool ContainsOnlyChars(const StringPiece16& input,
- const StringPiece16& characters) {
+bool ContainsOnlyChars(StringPiece16 input, StringPiece16 characters) {
return input.find_first_not_of(characters) == StringPiece16::npos;
}
@@ -512,25 +497,21 @@ inline bool DoIsStringASCII(const Char* characters, size_t length) {
return !(all_char_bits & non_ascii_bit_mask);
}
-bool IsStringASCII(const StringPiece& str) {
+bool IsStringASCII(StringPiece str) {
return DoIsStringASCII(str.data(), str.length());
}
-bool IsStringASCII(const StringPiece16& str) {
- return DoIsStringASCII(str.data(), str.length());
-}
-
-bool IsStringASCII(const string16& str) {
+bool IsStringASCII(StringPiece16 str) {
return DoIsStringASCII(str.data(), str.length());
}
#if defined(WCHAR_T_IS_UTF32)
-bool IsStringASCII(const std::wstring& str) {
+bool IsStringASCII(WStringPiece str) {
return DoIsStringASCII(str.data(), str.length());
}
#endif
-bool IsStringUTF8(const StringPiece& str) {
+bool IsStringUTF8(StringPiece str) {
const char *src = str.data();
int32_t src_len = static_cast<int32_t>(str.length());
int32_t char_index = 0;
@@ -711,144 +692,227 @@ string16 FormatBytesUnlocalized(int64_t bytes) {
return ASCIIToUTF16(buf);
}
-// Runs in O(n) time in the length of |str|.
-template<class StringType>
-void DoReplaceSubstringsAfterOffset(StringType* str,
- size_t offset,
- BasicStringPiece<StringType> find_this,
- BasicStringPiece<StringType> replace_with,
- bool replace_all) {
- DCHECK(!find_this.empty());
+// A Matcher for DoReplaceMatchesAfterOffset() that matches substrings.
+template <class StringType>
+struct SubstringMatcher {
+ BasicStringPiece<StringType> find_this;
+
+ size_t Find(const StringType& input, size_t pos) {
+ return input.find(find_this.data(), pos, find_this.length());
+ }
+ size_t MatchSize() { return find_this.length(); }
+};
+
+// A Matcher for DoReplaceMatchesAfterOffset() that matches single characters.
+template <class StringType>
+struct CharacterMatcher {
+ BasicStringPiece<StringType> find_any_of_these;
+
+ size_t Find(const StringType& input, size_t pos) {
+ return input.find_first_of(find_any_of_these.data(), pos,
+ find_any_of_these.length());
+ }
+ constexpr size_t MatchSize() { return 1; }
+};
+
+enum class ReplaceType { REPLACE_ALL, REPLACE_FIRST };
+
+// Runs in O(n) time in the length of |str|, and transforms the string without
+// reallocating when possible. Returns |true| if any matches were found.
+//
+// This is parameterized on a |Matcher| traits type, so that it can be the
+// implementation for both ReplaceChars() and ReplaceSubstringsAfterOffset().
+template <class StringType, class Matcher>
+bool DoReplaceMatchesAfterOffset(StringType* str,
+ size_t initial_offset,
+ Matcher matcher,
+ BasicStringPiece<StringType> replace_with,
+ ReplaceType replace_type) {
+ using CharTraits = typename StringType::traits_type;
+
+ const size_t find_length = matcher.MatchSize();
+ if (!find_length)
+ return false;
// If the find string doesn't appear, there's nothing to do.
- offset = str->find(find_this.data(), offset, find_this.size());
- if (offset == StringType::npos)
- return;
+ size_t first_match = matcher.Find(*str, initial_offset);
+ if (first_match == StringType::npos)
+ return false;
// If we're only replacing one instance, there's no need to do anything
// complicated.
- size_t find_length = find_this.length();
- if (!replace_all) {
- str->replace(offset, find_length, replace_with.data(), replace_with.size());
- return;
+ const size_t replace_length = replace_with.length();
+ if (replace_type == ReplaceType::REPLACE_FIRST) {
+ str->replace(first_match, find_length, replace_with.data(), replace_length);
+ return true;
}
// If the find and replace strings are the same length, we can simply use
// replace() on each instance, and finish the entire operation in O(n) time.
- size_t replace_length = replace_with.length();
if (find_length == replace_length) {
- do {
- str->replace(offset, find_length,
- replace_with.data(), replace_with.size());
- offset = str->find(find_this.data(), offset + replace_length,
- find_this.size());
- } while (offset != StringType::npos);
- return;
+ auto* buffer = &((*str)[0]);
+ for (size_t offset = first_match; offset != StringType::npos;
+ offset = matcher.Find(*str, offset + replace_length)) {
+ CharTraits::copy(buffer + offset, replace_with.data(), replace_length);
+ }
+ return true;
}
// Since the find and replace strings aren't the same length, a loop like the
// one above would be O(n^2) in the worst case, as replace() will shift the
- // entire remaining string each time. We need to be more clever to keep
- // things O(n).
+ // entire remaining string each time. We need to be more clever to keep things
+ // O(n).
//
- // If we're shortening the string, we can alternate replacements with shifting
- // forward the intervening characters using memmove().
+ // When the string is being shortened, it's possible to just shift the matches
+ // down in one pass while finding, and truncate the length at the end of the
+ // search.
+ //
+ // If the string is being lengthened, more work is required. The strategy used
+ // here is to make two find() passes through the string. The first pass counts
+ // the number of matches to determine the new size. The second pass will
+ // either construct the new string into a new buffer (if the existing buffer
+ // lacked capacity), or else -- if there is room -- create a region of scratch
+ // space after |first_match| by shifting the tail of the string to a higher
+ // index, and doing in-place moves from the tail to lower indices thereafter.
size_t str_length = str->length();
- if (find_length > replace_length) {
- size_t write_offset = offset;
- do {
- if (replace_length) {
- str->replace(write_offset, replace_length,
- replace_with.data(), replace_with.size());
- write_offset += replace_length;
- }
- size_t read_offset = offset + find_length;
- offset = std::min(
- str->find(find_this.data(), read_offset, find_this.size()),
- str_length);
- size_t length = offset - read_offset;
- if (length) {
- memmove(&(*str)[write_offset], &(*str)[read_offset],
- length * sizeof(typename StringType::value_type));
- write_offset += length;
+ size_t expansion = 0;
+ if (replace_length > find_length) {
+ // This operation lengthens the string; determine the new length by counting
+ // matches.
+ const size_t expansion_per_match = (replace_length - find_length);
+ size_t num_matches = 0;
+ for (size_t match = first_match; match != StringType::npos;
+ match = matcher.Find(*str, match + find_length)) {
+ expansion += expansion_per_match;
+ ++num_matches;
+ }
+ const size_t final_length = str_length + expansion;
+
+ if (str->capacity() < final_length) {
+ // If we'd have to allocate a new buffer to grow the string, build the
+ // result directly into the new allocation via append().
+ StringType src(str->get_allocator());
+ str->swap(src);
+ str->reserve(final_length);
+
+ size_t pos = 0;
+ for (size_t match = first_match;; match = matcher.Find(src, pos)) {
+ str->append(src, pos, match - pos);
+ str->append(replace_with.data(), replace_length);
+ pos = match + find_length;
+
+ // A mid-loop test/break enables skipping the final Find() call; the
+ // number of matches is known, so don't search past the last one.
+ if (!--num_matches)
+ break;
}
- } while (offset < str_length);
- str->resize(write_offset);
- return;
+
+ // Handle substring after the final match.
+ str->append(src, pos, str_length - pos);
+ return true;
+ }
+
+ // Prepare for the copy/move loop below -- expand the string to its final
+ // size by shifting the data after the first match to the end of the resized
+ // string.
+ size_t shift_src = first_match + find_length;
+ size_t shift_dst = shift_src + expansion;
+
+ // Big |expansion| factors (relative to |str_length|) require padding up to
+ // |shift_dst|.
+ if (shift_dst > str_length)
+ str->resize(shift_dst);
+
+ str->replace(shift_dst, str_length - shift_src, *str, shift_src,
+ str_length - shift_src);
+ str_length = final_length;
}
- // We're lengthening the string. We can use alternating replacements and
- // memmove() calls like above, but we need to precalculate the final string
- // length and then expand from back-to-front to avoid overwriting the string
- // as we're reading it, needing to shift, or having to copy to a second string
- // temporarily.
- size_t first_match = offset;
-
- // First, calculate the final length and resize the string.
- size_t final_length = str_length;
- size_t expansion = replace_length - find_length;
- size_t current_match;
+ // We can alternate replacement and move operations. This won't overwrite the
+ // unsearched region of the string so long as |write_offset| <= |read_offset|;
+ // that condition is always satisfied because:
+ //
+ // (a) If the string is being shortened, |expansion| is zero and
+ // |write_offset| grows slower than |read_offset|.
+ //
+ // (b) If the string is being lengthened, |write_offset| grows faster than
+ // |read_offset|, but |expansion| is big enough so that |write_offset|
+ // will only catch up to |read_offset| at the point of the last match.
+ auto* buffer = &((*str)[0]);
+ size_t write_offset = first_match;
+ size_t read_offset = first_match + expansion;
do {
- final_length += expansion;
- // Minor optimization: save this offset into |current_match|, so that on
- // exit from the loop, |current_match| will point at the last instance of
- // the find string, and we won't need to find() it again immediately.
- current_match = offset;
- offset = str->find(find_this.data(), offset + find_length,
- find_this.size());
- } while (offset != StringType::npos);
- str->resize(final_length);
-
- // Now do the replacement loop, working backwards through the string.
- for (size_t prev_match = str_length, write_offset = final_length; ;
- current_match = str->rfind(find_this.data(), current_match - 1,
- find_this.size())) {
- size_t read_offset = current_match + find_length;
- size_t length = prev_match - read_offset;
+ if (replace_length) {
+ CharTraits::copy(buffer + write_offset, replace_with.data(),
+ replace_length);
+ write_offset += replace_length;
+ }
+ read_offset += find_length;
+
+ // min() clamps StringType::npos (the largest unsigned value) to str_length.
+ size_t match = std::min(matcher.Find(*str, read_offset), str_length);
+
+ size_t length = match - read_offset;
if (length) {
- write_offset -= length;
- memmove(&(*str)[write_offset], &(*str)[read_offset],
- length * sizeof(typename StringType::value_type));
+ CharTraits::move(buffer + write_offset, buffer + read_offset, length);
+ write_offset += length;
+ read_offset += length;
}
- write_offset -= replace_length;
- str->replace(write_offset, replace_length,
- replace_with.data(), replace_with.size());
- if (current_match == first_match)
- return;
- prev_match = current_match;
- }
+ } while (read_offset < str_length);
+
+ // If we're shortening the string, truncate it now.
+ str->resize(write_offset);
+ return true;
+}
+
+template <class StringType>
+bool ReplaceCharsT(const StringType& input,
+ BasicStringPiece<StringType> find_any_of_these,
+ BasicStringPiece<StringType> replace_with,
+ StringType* output) {
+ // Commonly, this is called with output and input being the same string; in
+ // that case, this assignment is inexpensive.
+ *output = input;
+
+ return DoReplaceMatchesAfterOffset(
+ output, 0, CharacterMatcher<StringType>{find_any_of_these}, replace_with,
+ ReplaceType::REPLACE_ALL);
}
void ReplaceFirstSubstringAfterOffset(string16* str,
size_t start_offset,
StringPiece16 find_this,
StringPiece16 replace_with) {
- DoReplaceSubstringsAfterOffset<string16>(
- str, start_offset, find_this, replace_with, false); // Replace first.
+ DoReplaceMatchesAfterOffset(str, start_offset,
+ SubstringMatcher<string16>{find_this},
+ replace_with, ReplaceType::REPLACE_FIRST);
}
void ReplaceFirstSubstringAfterOffset(std::string* str,
size_t start_offset,
StringPiece find_this,
StringPiece replace_with) {
- DoReplaceSubstringsAfterOffset<std::string>(
- str, start_offset, find_this, replace_with, false); // Replace first.
+ DoReplaceMatchesAfterOffset(str, start_offset,
+ SubstringMatcher<std::string>{find_this},
+ replace_with, ReplaceType::REPLACE_FIRST);
}
void ReplaceSubstringsAfterOffset(string16* str,
size_t start_offset,
StringPiece16 find_this,
StringPiece16 replace_with) {
- DoReplaceSubstringsAfterOffset<string16>(
- str, start_offset, find_this, replace_with, true); // Replace all.
+ DoReplaceMatchesAfterOffset(str, start_offset,
+ SubstringMatcher<string16>{find_this},
+ replace_with, ReplaceType::REPLACE_ALL);
}
void ReplaceSubstringsAfterOffset(std::string* str,
size_t start_offset,
StringPiece find_this,
StringPiece replace_with) {
- DoReplaceSubstringsAfterOffset<std::string>(
- str, start_offset, find_this, replace_with, true); // Replace all.
+ DoReplaceMatchesAfterOffset(str, start_offset,
+ SubstringMatcher<std::string>{find_this},
+ replace_with, ReplaceType::REPLACE_ALL);
}
template <class string_type>
@@ -868,6 +932,11 @@ char16* WriteInto(string16* str, size_t length_with_null) {
return WriteIntoT(str, length_with_null);
}
+#if defined(_MSC_VER) && !defined(__clang__)
+// Work around VC++ code-gen bug. https://crbug.com/804884
+#pragma optimize("", off)
+#endif
+
// Generic version for all JoinString overloads. |list_type| must be a sequence
// (std::vector or std::initializer_list) of strings/StringPieces (std::string,
// string16, StringPiece or StringPiece16). |string_type| is either std::string
@@ -915,6 +984,11 @@ string16 JoinString(const std::vector<string16>& parts,
return JoinStringT(parts, separator);
}
+#if defined(_MSC_VER) && !defined(__clang__)
+// Work around VC++ code-gen bug. https://crbug.com/804884
+#pragma optimize("", on)
+#endif
+
std::string JoinString(const std::vector<StringPiece>& parts,
StringPiece separator) {
return JoinStringT(parts, separator);
@@ -969,12 +1043,11 @@ OutStringType DoReplaceStringPlaceholders(
uintptr_t index = *i - '1';
if (offsets) {
ReplacementOffset r_offset(index,
- static_cast<int>(formatted.size()));
- r_offsets.insert(std::lower_bound(r_offsets.begin(),
- r_offsets.end(),
- r_offset,
- &CompareParameter),
- r_offset);
+ static_cast<int>(formatted.size()));
+ r_offsets.insert(
+ std::upper_bound(r_offsets.begin(), r_offsets.end(), r_offset,
+ &CompareParameter),
+ r_offset);
}
if (index < substitutions)
formatted.append(subst.at(index));
@@ -997,7 +1070,7 @@ string16 ReplaceStringPlaceholders(const string16& format_string,
return DoReplaceStringPlaceholders(format_string, subst, offsets);
}
-std::string ReplaceStringPlaceholders(const StringPiece& format_string,
+std::string ReplaceStringPlaceholders(StringPiece format_string,
const std::vector<std::string>& subst,
std::vector<size_t>* offsets) {
return DoReplaceStringPlaceholders(format_string, subst, offsets);
diff --git a/base/strings/string_util.h b/base/strings/string_util.h
index 35b2603786..d6780ec7fc 100644
--- a/base/strings/string_util.h
+++ b/base/strings/string_util.h
@@ -174,10 +174,10 @@ BASE_EXPORT extern const char kUtf8ByteOrderMark[];
// if any characters were removed. |remove_chars| must be null-terminated.
// NOTE: Safe to use the same variable for both |input| and |output|.
BASE_EXPORT bool RemoveChars(const string16& input,
- const StringPiece16& remove_chars,
+ StringPiece16 remove_chars,
string16* output);
BASE_EXPORT bool RemoveChars(const std::string& input,
- const StringPiece& remove_chars,
+ StringPiece remove_chars,
std::string* output);
// Replaces characters in |replace_chars| from anywhere in |input| with
@@ -186,11 +186,11 @@ BASE_EXPORT bool RemoveChars(const std::string& input,
// |replace_chars| must be null-terminated.
// NOTE: Safe to use the same variable for both |input| and |output|.
BASE_EXPORT bool ReplaceChars(const string16& input,
- const StringPiece16& replace_chars,
+ StringPiece16 replace_chars,
const string16& replace_with,
string16* output);
BASE_EXPORT bool ReplaceChars(const std::string& input,
- const StringPiece& replace_chars,
+ StringPiece replace_chars,
const std::string& replace_with,
std::string* output);
@@ -202,7 +202,8 @@ enum TrimPositions {
};
// Removes characters in |trim_chars| from the beginning and end of |input|.
-// The 8-bit version only works on 8-bit characters, not UTF-8.
+// The 8-bit version only works on 8-bit characters, not UTF-8. Returns true if
+// any characters were removed.
//
// It is safe to use the same variable for both |input| and |output| (this is
// the normal usage to trim in-place).
@@ -216,10 +217,10 @@ BASE_EXPORT bool TrimString(const std::string& input,
// StringPiece versions of the above. The returned pieces refer to the original
// buffer.
BASE_EXPORT StringPiece16 TrimString(StringPiece16 input,
- const StringPiece16& trim_chars,
+ StringPiece16 trim_chars,
TrimPositions positions);
BASE_EXPORT StringPiece TrimString(StringPiece input,
- const StringPiece& trim_chars,
+ StringPiece trim_chars,
TrimPositions positions);
// Truncates a string to the nearest UTF-8 character that will leave
@@ -246,7 +247,7 @@ BASE_EXPORT TrimPositions TrimWhitespaceASCII(const std::string& input,
BASE_EXPORT StringPiece TrimWhitespaceASCII(StringPiece input,
TrimPositions positions);
-// Searches for CR or LF characters. Removes all contiguous whitespace
+// Searches for CR or LF characters. Removes all contiguous whitespace
// strings that contain them. This is useful when trying to deal with text
// copied from terminals.
// Returns |text|, with the following three transformations:
@@ -263,10 +264,9 @@ BASE_EXPORT std::string CollapseWhitespaceASCII(
// Returns true if |input| is empty or contains only characters found in
// |characters|.
-BASE_EXPORT bool ContainsOnlyChars(const StringPiece& input,
- const StringPiece& characters);
-BASE_EXPORT bool ContainsOnlyChars(const StringPiece16& input,
- const StringPiece16& characters);
+BASE_EXPORT bool ContainsOnlyChars(StringPiece input, StringPiece characters);
+BASE_EXPORT bool ContainsOnlyChars(StringPiece16 input,
+ StringPiece16 characters);
// Returns true if the specified string matches the criteria. How can a wide
// string be 8-bit or UTF8? It contains only characters that are < 256 (in the
@@ -282,12 +282,11 @@ BASE_EXPORT bool ContainsOnlyChars(const StringPiece16& input,
//
// IsStringASCII assumes the input is likely all ASCII, and does not leave early
// if it is not the case.
-BASE_EXPORT bool IsStringUTF8(const StringPiece& str);
-BASE_EXPORT bool IsStringASCII(const StringPiece& str);
-BASE_EXPORT bool IsStringASCII(const StringPiece16& str);
-BASE_EXPORT bool IsStringASCII(const string16& str);
+BASE_EXPORT bool IsStringUTF8(StringPiece str);
+BASE_EXPORT bool IsStringASCII(StringPiece str);
+BASE_EXPORT bool IsStringASCII(StringPiece16 str);
#if defined(WCHAR_T_IS_UTF32)
-BASE_EXPORT bool IsStringASCII(const std::wstring& str);
+BASE_EXPORT bool IsStringASCII(WStringPiece str);
#endif
// Compare the lower-case form of the given string against the given
@@ -426,9 +425,6 @@ BASE_EXPORT void ReplaceSubstringsAfterOffset(
// to this function (probably 0).
BASE_EXPORT char* WriteInto(std::string* str, size_t length_with_null);
BASE_EXPORT char16* WriteInto(string16* str, size_t length_with_null);
-#ifndef OS_WIN
-BASE_EXPORT wchar_t* WriteInto(std::wstring* str, size_t length_with_null);
-#endif
// Does the opposite of SplitString()/SplitStringPiece(). Joins a vector or list
// of strings into a single string, inserting |separator| (which may be empty)
@@ -439,6 +435,8 @@ BASE_EXPORT wchar_t* WriteInto(std::wstring* str, size_t length_with_null);
// strings. For example, instead of using SplitString, modifying the vector,
// then using JoinString, use SplitStringPiece followed by JoinString so that no
// copies of those strings are created until the final join operation.
+//
+// Use StrCat (in base/strings/strcat.h) if you don't need a separator.
BASE_EXPORT std::string JoinString(const std::vector<std::string>& parts,
StringPiece separator);
BASE_EXPORT string16 JoinString(const std::vector<string16>& parts,
@@ -465,7 +463,7 @@ BASE_EXPORT string16 ReplaceStringPlaceholders(
std::vector<size_t>* offsets);
BASE_EXPORT std::string ReplaceStringPlaceholders(
- const StringPiece& format_string,
+ StringPiece format_string,
const std::vector<std::string>& subst,
std::vector<size_t>* offsets);
@@ -478,7 +476,7 @@ BASE_EXPORT string16 ReplaceStringPlaceholders(const string16& format_string,
#if defined(OS_WIN)
#include "base/strings/string_util_win.h"
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
#include "base/strings/string_util_posix.h"
#else
#error Define string operations appropriately for your platform
diff --git a/base/strings/string_util_unittest.cc b/base/strings/string_util_unittest.cc
index 6ac307ec2b..509889eb19 100644
--- a/base/strings/string_util_unittest.cc
+++ b/base/strings/string_util_unittest.cc
@@ -585,32 +585,70 @@ TEST(StringUtilTest, FormatBytesUnlocalized) {
}
TEST(StringUtilTest, ReplaceSubstringsAfterOffset) {
static const struct {
- const char* str;
- string16::size_type start_offset;
- const char* find_this;
- const char* replace_with;
- const char* expected;
+ StringPiece str;
+ size_t start_offset;
+ StringPiece find_this;
+ StringPiece replace_with;
+ StringPiece expected;
} cases[] = {
- {"aaa", 0, "a", "b", "bbb"},
- {"abb", 0, "ab", "a", "ab"},
- {"Removing some substrings inging", 0, "ing", "", "Remov some substrs "},
- {"Not found", 0, "x", "0", "Not found"},
- {"Not found again", 5, "x", "0", "Not found again"},
- {" Making it much longer ", 0, " ", "Four score and seven years ago",
- "Four score and seven years agoMakingFour score and seven years agoit"
- "Four score and seven years agomuchFour score and seven years agolonger"
- "Four score and seven years ago"},
- {"Invalid offset", 9999, "t", "foobar", "Invalid offset"},
- {"Replace me only me once", 9, "me ", "", "Replace me only once"},
- {"abababab", 2, "ab", "c", "abccc"},
+ {"aaa", 0, "", "b", "aaa"},
+ {"aaa", 1, "", "b", "aaa"},
+ {"aaa", 0, "a", "b", "bbb"},
+ {"aaa", 0, "aa", "b", "ba"},
+ {"aaa", 0, "aa", "bbb", "bbba"},
+ {"aaaaa", 0, "aa", "b", "bba"},
+ {"ababaaababa", 0, "aba", "", "baaba"},
+ {"ababaaababa", 0, "aba", "_", "_baa_ba"},
+ {"ababaaababa", 0, "aba", "__", "__baa__ba"},
+ {"ababaaababa", 0, "aba", "___", "___baa___ba"},
+ {"ababaaababa", 0, "aba", "____", "____baa____ba"},
+ {"ababaaababa", 0, "aba", "_____", "_____baa_____ba"},
+ {"abb", 0, "ab", "a", "ab"},
+ {"Removing some substrings inging", 0, "ing", "", "Remov some substrs "},
+ {"Not found", 0, "x", "0", "Not found"},
+ {"Not found again", 5, "x", "0", "Not found again"},
+ {" Making it much longer ", 0, " ", "Four score and seven years ago",
+ "Four score and seven years agoMakingFour score and seven years agoit"
+ "Four score and seven years agomuchFour score and seven years agolonger"
+ "Four score and seven years ago"},
+ {" Making it much much much much shorter ", 0,
+ "Making it much much much much shorter", "", " "},
+ {"so much much much much much very much much much shorter", 0, "much ",
+ "", "so very shorter"},
+ {"Invalid offset", 9999, "t", "foobar", "Invalid offset"},
+ {"Replace me only me once", 9, "me ", "", "Replace me only once"},
+ {"abababab", 2, "ab", "c", "abccc"},
+ {"abababab", 1, "ab", "c", "abccc"},
+ {"abababab", 1, "aba", "c", "abcbab"},
};
- for (size_t i = 0; i < arraysize(cases); i++) {
- string16 str = ASCIIToUTF16(cases[i].str);
- ReplaceSubstringsAfterOffset(&str, cases[i].start_offset,
- ASCIIToUTF16(cases[i].find_this),
- ASCIIToUTF16(cases[i].replace_with));
- EXPECT_EQ(ASCIIToUTF16(cases[i].expected), str);
+ // base::string16 variant
+ for (const auto& scenario : cases) {
+ string16 str = ASCIIToUTF16(scenario.str);
+ ReplaceSubstringsAfterOffset(&str, scenario.start_offset,
+ ASCIIToUTF16(scenario.find_this),
+ ASCIIToUTF16(scenario.replace_with));
+ EXPECT_EQ(ASCIIToUTF16(scenario.expected), str);
+ }
+
+ // std::string with insufficient capacity: expansion must realloc the buffer.
+ for (const auto& scenario : cases) {
+ std::string str = scenario.str.as_string();
+ str.shrink_to_fit(); // This is nonbinding, but it's the best we've got.
+ ReplaceSubstringsAfterOffset(&str, scenario.start_offset,
+ scenario.find_this, scenario.replace_with);
+ EXPECT_EQ(scenario.expected, str);
+ }
+
+ // std::string with ample capacity: should be possible to grow in-place.
+ for (const auto& scenario : cases) {
+ std::string str = scenario.str.as_string();
+ str.reserve(std::max(scenario.str.length(), scenario.expected.length()) *
+ 2);
+
+ ReplaceSubstringsAfterOffset(&str, scenario.start_offset,
+ scenario.find_this, scenario.replace_with);
+ EXPECT_EQ(scenario.expected, str);
}
}
@@ -954,6 +992,54 @@ TEST(StringUtilTest, ReplaceStringPlaceholders) {
EXPECT_EQ(ASCIIToUTF16("9aa,8bb,7cc,6dd,5ee,4ff,3gg,2hh,1ii"), formatted);
}
+TEST(StringUtilTest, ReplaceStringPlaceholdersNetExpansionWithContraction) {
+ // In this test, some of the substitutions are shorter than the placeholders,
+ // but overall the string gets longer.
+ std::vector<string16> subst;
+ subst.push_back(ASCIIToUTF16("9a____"));
+ subst.push_back(ASCIIToUTF16("B"));
+ subst.push_back(ASCIIToUTF16("7c___"));
+ subst.push_back(ASCIIToUTF16("d"));
+ subst.push_back(ASCIIToUTF16("5e____"));
+ subst.push_back(ASCIIToUTF16("F"));
+ subst.push_back(ASCIIToUTF16("3g___"));
+ subst.push_back(ASCIIToUTF16("h"));
+ subst.push_back(ASCIIToUTF16("1i_____"));
+
+ string16 original = ASCIIToUTF16("$1a,$2b,$3c,$4d,$5e,$6f,$7g,$8h,$9i");
+ string16 expected =
+ ASCIIToUTF16("9a____a,Bb,7c___c,dd,5e____e,Ff,3g___g,hh,1i_____i");
+
+ EXPECT_EQ(expected, ReplaceStringPlaceholders(original, subst, nullptr));
+
+ std::vector<size_t> offsets;
+ EXPECT_EQ(expected, ReplaceStringPlaceholders(original, subst, &offsets));
+ std::vector<size_t> expected_offsets = {0, 8, 11, 18, 21, 29, 32, 39, 42};
+ EXPECT_EQ(offsets.size(), subst.size());
+ EXPECT_EQ(expected_offsets, offsets);
+ for (size_t i = 0; i < offsets.size(); i++) {
+ EXPECT_EQ(expected.substr(expected_offsets[i], subst[i].length()),
+ subst[i]);
+ }
+}
+
+TEST(StringUtilTest, ReplaceStringPlaceholdersNetContractionWithExpansion) {
+ // In this test, some of the substitutions are longer than the placeholders,
+ // but overall the string gets smaller. Additionally, the placeholders appear
+ // in a permuted order.
+ std::vector<string16> subst;
+ subst.push_back(ASCIIToUTF16("z"));
+ subst.push_back(ASCIIToUTF16("y"));
+ subst.push_back(ASCIIToUTF16("XYZW"));
+ subst.push_back(ASCIIToUTF16("x"));
+ subst.push_back(ASCIIToUTF16("w"));
+
+ string16 formatted =
+ ReplaceStringPlaceholders(ASCIIToUTF16("$3_$4$2$1$5"), subst, nullptr);
+
+ EXPECT_EQ(ASCIIToUTF16("XYZW_xyzw"), formatted);
+}
+
TEST(StringUtilTest, ReplaceStringPlaceholdersOneDigit) {
std::vector<string16> subst;
subst.push_back(ASCIIToUTF16("1a"));
@@ -989,6 +1075,22 @@ TEST(StringUtilTest, StdStringReplaceStringPlaceholders) {
EXPECT_EQ("9aa,8bb,7cc,6dd,5ee,4ff,3gg,2hh,1ii", formatted);
}
+TEST(StringUtilTest, StdStringReplaceStringPlaceholdersMultipleMatches) {
+ std::vector<std::string> subst;
+ subst.push_back("4"); // Referenced twice.
+ subst.push_back("?"); // Unreferenced.
+ subst.push_back("!"); // Unreferenced.
+ subst.push_back("16"); // Referenced once.
+
+ std::string original = "$1 * $1 == $4";
+ std::string expected = "4 * 4 == 16";
+ EXPECT_EQ(expected, ReplaceStringPlaceholders(original, subst, nullptr));
+ std::vector<size_t> offsets;
+ EXPECT_EQ(expected, ReplaceStringPlaceholders(original, subst, &offsets));
+ std::vector<size_t> expected_offsets = {0, 4, 9};
+ EXPECT_EQ(expected_offsets, offsets);
+}
+
TEST(StringUtilTest, ReplaceStringPlaceholdersConsecutiveDollarSigns) {
std::vector<std::string> subst;
subst.push_back("a");
@@ -1108,29 +1210,66 @@ TEST(StringUtilTest, ReplaceChars) {
const char* output;
bool result;
} cases[] = {
- { "", "", "", "", false },
- { "test", "", "", "test", false },
- { "test", "", "!", "test", false },
- { "test", "z", "!", "test", false },
- { "test", "e", "!", "t!st", true },
- { "test", "e", "!?", "t!?st", true },
- { "test", "ez", "!", "t!st", true },
- { "test", "zed", "!?", "t!?st", true },
- { "test", "t", "!?", "!?es!?", true },
- { "test", "et", "!>", "!>!>s!>", true },
- { "test", "zest", "!", "!!!!", true },
- { "test", "szt", "!", "!e!!", true },
- { "test", "t", "test", "testestest", true },
+ {"", "", "", "", false},
+ {"t", "t", "t", "t", true},
+ {"a", "b", "c", "a", false},
+ {"b", "b", "c", "c", true},
+ {"bob", "b", "p", "pop", true},
+ {"bob", "o", "i", "bib", true},
+ {"test", "", "", "test", false},
+ {"test", "", "!", "test", false},
+ {"test", "z", "!", "test", false},
+ {"test", "e", "!", "t!st", true},
+ {"test", "e", "!?", "t!?st", true},
+ {"test", "ez", "!", "t!st", true},
+ {"test", "zed", "!?", "t!?st", true},
+ {"test", "t", "!?", "!?es!?", true},
+ {"test", "et", "!>", "!>!>s!>", true},
+ {"test", "zest", "!", "!!!!", true},
+ {"test", "szt", "!", "!e!!", true},
+ {"test", "t", "test", "testestest", true},
+ {"tetst", "t", "test", "testeteststest", true},
+ {"ttttttt", "t", "-", "-------", true},
+ {"aAaAaAAaAAa", "A", "", "aaaaa", true},
+ {"xxxxxxxxxx", "x", "", "", true},
+ {"xxxxxxxxxx", "x", "x", "xxxxxxxxxx", true},
+ {"xxxxxxxxxx", "x", "y-", "y-y-y-y-y-y-y-y-y-y-", true},
+ {"xxxxxxxxxx", "x", "xy", "xyxyxyxyxyxyxyxyxyxy", true},
+ {"xxxxxxxxxx", "x", "zyx", "zyxzyxzyxzyxzyxzyxzyxzyxzyxzyx", true},
+ {"xaxxaxxxaxxxax", "x", "xy", "xyaxyxyaxyxyxyaxyxyxyaxy", true},
+ {"-xaxxaxxxaxxxax-", "x", "xy", "-xyaxyxyaxyxyxyaxyxyxyaxy-", true},
};
- for (size_t i = 0; i < arraysize(cases); ++i) {
+ for (const TestData& scenario : cases) {
+ // Test with separate output and input vars.
std::string output;
- bool result = ReplaceChars(cases[i].input,
- cases[i].replace_chars,
- cases[i].replace_with,
- &output);
- EXPECT_EQ(cases[i].result, result);
- EXPECT_EQ(cases[i].output, output);
+ bool result = ReplaceChars(scenario.input, scenario.replace_chars,
+ scenario.replace_with, &output);
+ EXPECT_EQ(scenario.result, result) << scenario.input;
+ EXPECT_EQ(scenario.output, output);
+ }
+
+ for (const TestData& scenario : cases) {
+ // Test with an input/output var of limited capacity.
+ std::string input_output = scenario.input;
+ input_output.shrink_to_fit();
+ bool result = ReplaceChars(input_output, scenario.replace_chars,
+ scenario.replace_with, &input_output);
+ EXPECT_EQ(scenario.result, result) << scenario.input;
+ EXPECT_EQ(scenario.output, input_output);
+ }
+
+ for (const TestData& scenario : cases) {
+ // Test with an input/output var of ample capacity; should
+ // not realloc.
+ std::string input_output = scenario.input;
+ input_output.reserve(strlen(scenario.output) * 2);
+ const void* original_buffer = input_output.data();
+ bool result = ReplaceChars(input_output, scenario.replace_chars,
+ scenario.replace_with, &input_output);
+ EXPECT_EQ(scenario.result, result) << scenario.input;
+ EXPECT_EQ(scenario.output, input_output);
+ EXPECT_EQ(original_buffer, input_output.data());
}
}
diff --git a/base/strings/string_util_win.h b/base/strings/string_util_win.h
deleted file mode 100644
index 7f260bfc8b..0000000000
--- a/base/strings/string_util_win.h
+++ /dev/null
@@ -1,44 +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_STRINGS_STRING_UTIL_WIN_H_
-#define BASE_STRINGS_STRING_UTIL_WIN_H_
-
-#include <stdarg.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <string.h>
-#include <wchar.h>
-
-#include "base/logging.h"
-
-namespace base {
-
-// Chromium code style is to not use malloc'd strings; this is only for use
-// for interaction with APIs that require it.
-inline char* strdup(const char* str) {
- return _strdup(str);
-}
-
-inline int vsnprintf(char* buffer, size_t size,
- const char* format, va_list arguments) {
- int length = vsnprintf_s(buffer, size, size - 1, format, arguments);
- if (length < 0)
- return _vscprintf(format, arguments);
- return length;
-}
-
-inline int vswprintf(wchar_t* buffer, size_t size,
- const wchar_t* format, va_list arguments) {
- DCHECK(IsWprintfFormatPortable(format));
-
- int length = _vsnwprintf_s(buffer, size, size - 1, format, arguments);
- if (length < 0)
- return _vscwprintf(format, arguments);
- return length;
-}
-
-} // namespace base
-
-#endif // BASE_STRINGS_STRING_UTIL_WIN_H_
diff --git a/base/strings/stringprintf_unittest.cc b/base/strings/stringprintf_unittest.cc
index e2d3a90ffd..3d43e8c6e4 100644
--- a/base/strings/stringprintf_unittest.cc
+++ b/base/strings/stringprintf_unittest.cc
@@ -117,7 +117,7 @@ TEST(StringPrintfTest, Grow) {
char* ref = new char[kRefSize];
#if defined(OS_WIN)
sprintf_s(ref, kRefSize, fmt, src, src, src, src, src, src, src);
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
snprintf(ref, kRefSize, fmt, src, src, src, src, src, src, src);
#endif
diff --git a/base/strings/sys_string_conversions.h b/base/strings/sys_string_conversions.h
index b41a2288ca..1ad0307816 100644
--- a/base/strings/sys_string_conversions.h
+++ b/base/strings/sys_string_conversions.h
@@ -32,13 +32,13 @@ namespace base {
// Converts between wide and UTF-8 representations of a string. On error, the
// result is system-dependent.
BASE_EXPORT std::string SysWideToUTF8(const std::wstring& wide);
-BASE_EXPORT std::wstring SysUTF8ToWide(const StringPiece& utf8);
+BASE_EXPORT std::wstring SysUTF8ToWide(StringPiece utf8);
// Converts between wide and the system multi-byte representations of a string.
// DANGER: This will lose information and can change (on Windows, this can
// change between reboots).
BASE_EXPORT std::string SysWideToNativeMB(const std::wstring& wide);
-BASE_EXPORT std::wstring SysNativeMBToWide(const StringPiece& native_mb);
+BASE_EXPORT std::wstring SysNativeMBToWide(StringPiece native_mb);
// Windows-specific ------------------------------------------------------------
@@ -47,8 +47,7 @@ BASE_EXPORT std::wstring SysNativeMBToWide(const StringPiece& native_mb);
// Converts between 8-bit and wide strings, using the given code page. The
// code page identifier is one accepted by the Windows function
// MultiByteToWideChar().
-BASE_EXPORT std::wstring SysMultiByteToWide(const StringPiece& mb,
- uint32_t code_page);
+BASE_EXPORT std::wstring SysMultiByteToWide(StringPiece mb, uint32_t code_page);
BASE_EXPORT std::string SysWideToMultiByte(const std::wstring& wide,
uint32_t code_page);
diff --git a/base/strings/sys_string_conversions_posix.cc b/base/strings/sys_string_conversions_posix.cc
index a8dcfd0a90..cfa7b7670b 100644
--- a/base/strings/sys_string_conversions_posix.cc
+++ b/base/strings/sys_string_conversions_posix.cc
@@ -18,7 +18,7 @@ std::string SysWideToUTF8(const std::wstring& wide) {
// than our ICU, but this will do for now.
return WideToUTF8(wide);
}
-std::wstring SysUTF8ToWide(const StringPiece& utf8) {
+std::wstring SysUTF8ToWide(StringPiece utf8) {
// In theory this should be using the system-provided conversion rather
// than our ICU, but this will do for now.
std::wstring out;
@@ -34,7 +34,7 @@ std::string SysWideToNativeMB(const std::wstring& wide) {
return WideToUTF8(wide);
}
-std::wstring SysNativeMBToWide(const StringPiece& native_mb) {
+std::wstring SysNativeMBToWide(StringPiece native_mb) {
return SysUTF8ToWide(native_mb);
}
@@ -100,7 +100,7 @@ std::string SysWideToNativeMB(const std::wstring& wide) {
return out;
}
-std::wstring SysNativeMBToWide(const StringPiece& native_mb) {
+std::wstring SysNativeMBToWide(StringPiece native_mb) {
mbstate_t ps;
// Calculate the number of wide characters. We walk through the string
@@ -109,7 +109,7 @@ std::wstring SysNativeMBToWide(const StringPiece& native_mb) {
memset(&ps, 0, sizeof(ps));
for (size_t i = 0; i < native_mb.size(); ) {
const char* src = native_mb.data() + i;
- size_t res = mbrtowc(NULL, src, native_mb.size() - i, &ps);
+ size_t res = mbrtowc(nullptr, src, native_mb.size() - i, &ps);
switch (res) {
// Handle any errors and return an empty string.
case static_cast<size_t>(-2):
@@ -118,7 +118,8 @@ std::wstring SysNativeMBToWide(const StringPiece& native_mb) {
break;
case 0:
// We hit an embedded null byte, keep going.
- i += 1; // Fall through.
+ i += 1;
+ FALLTHROUGH;
default:
i += res;
++num_out_chars;
@@ -157,6 +158,6 @@ std::wstring SysNativeMBToWide(const StringPiece& native_mb) {
return out;
}
-#endif // OS_CHROMEOS
+#endif // defined(SYSTEM_NATIVE_UTF8) || defined(OS_ANDROID)
} // namespace base
diff --git a/base/strings/utf_string_conversion_utils.cc b/base/strings/utf_string_conversion_utils.cc
index 3101a60288..f7682c1be9 100644
--- a/base/strings/utf_string_conversion_utils.cc
+++ b/base/strings/utf_string_conversion_utils.cc
@@ -5,6 +5,7 @@
#include "base/strings/utf_string_conversion_utils.h"
#include "base/third_party/icu/icu_utf.h"
+#include "build/build_config.h"
namespace base {
@@ -121,7 +122,10 @@ void PrepareForUTF8Output(const CHAR* src,
}
// Instantiate versions we know callers will need.
+#if !defined(OS_WIN)
+// wchar_t and char16 are the same thing on Windows.
template void PrepareForUTF8Output(const wchar_t*, size_t, std::string*);
+#endif
template void PrepareForUTF8Output(const char16*, size_t, std::string*);
template<typename STRING>
@@ -142,7 +146,10 @@ void PrepareForUTF16Or32Output(const char* src,
}
// Instantiate versions we know callers will need.
+#if !defined(OS_WIN)
+// std::wstring and string16 are the same thing on Windows.
template void PrepareForUTF16Or32Output(const char*, size_t, std::wstring*);
+#endif
template void PrepareForUTF16Or32Output(const char*, size_t, string16*);
} // namespace base
diff --git a/base/strings/utf_string_conversions.cc b/base/strings/utf_string_conversions.cc
index 85450c6566..89acc3806c 100644
--- a/base/strings/utf_string_conversions.cc
+++ b/base/strings/utf_string_conversions.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -9,96 +9,235 @@
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversion_utils.h"
+#include "base/third_party/icu/icu_utf.h"
#include "build/build_config.h"
namespace base {
namespace {
-// Generalized Unicode converter -----------------------------------------------
-
-// Converts the given source Unicode character type to the given destination
-// Unicode character type as a STL string. The given input buffer and size
-// determine the source, and the given output STL string will be replaced by
-// the result.
-template<typename SRC_CHAR, typename DEST_STRING>
-bool ConvertUnicode(const SRC_CHAR* src,
- size_t src_len,
- DEST_STRING* output) {
- // ICU requires 32-bit numbers.
+constexpr int32_t kErrorCodePoint = 0xFFFD;
+
+// Size coefficient ----------------------------------------------------------
+// The maximum number of codeunits in the destination encoding corresponding to
+// one codeunit in the source encoding.
+
+template <typename SrcChar, typename DestChar>
+struct SizeCoefficient {
+ static_assert(sizeof(SrcChar) < sizeof(DestChar),
+ "Default case: from a smaller encoding to the bigger one");
+
+ // ASCII symbols are encoded by one codeunit in all encodings.
+ static constexpr int value = 1;
+};
+
+template <>
+struct SizeCoefficient<char16, char> {
+ // One UTF-16 codeunit corresponds to at most 3 codeunits in UTF-8.
+ static constexpr int value = 3;
+};
+
+#if defined(WCHAR_T_IS_UTF32)
+template <>
+struct SizeCoefficient<wchar_t, char> {
+ // UTF-8 uses at most 4 codeunits per character.
+ static constexpr int value = 4;
+};
+
+template <>
+struct SizeCoefficient<wchar_t, char16> {
+ // UTF-16 uses at most 2 codeunits per character.
+ static constexpr int value = 2;
+};
+#endif // defined(WCHAR_T_IS_UTF32)
+
+template <typename SrcChar, typename DestChar>
+constexpr int size_coefficient_v =
+ SizeCoefficient<std::decay_t<SrcChar>, std::decay_t<DestChar>>::value;
+
+// UnicodeAppendUnsafe --------------------------------------------------------
+// Function overloads that write code_point to the output string. Output string
+// has to have enough space for the codepoint.
+
+void UnicodeAppendUnsafe(char* out, int32_t* size, uint32_t code_point) {
+ CBU8_APPEND_UNSAFE(out, *size, code_point);
+}
+
+void UnicodeAppendUnsafe(char16* out, int32_t* size, uint32_t code_point) {
+ CBU16_APPEND_UNSAFE(out, *size, code_point);
+}
+
+#if defined(WCHAR_T_IS_UTF32)
+
+void UnicodeAppendUnsafe(wchar_t* out, int32_t* size, uint32_t code_point) {
+ out[(*size)++] = code_point;
+}
+
+#endif // defined(WCHAR_T_IS_UTF32)
+
+// DoUTFConversion ------------------------------------------------------------
+// Main driver of UTFConversion specialized for different Src encodings.
+// dest has to have enough room for the converted text.
+
+template <typename DestChar>
+bool DoUTFConversion(const char* src,
+ int32_t src_len,
+ DestChar* dest,
+ int32_t* dest_len) {
bool success = true;
- int32_t src_len32 = static_cast<int32_t>(src_len);
- for (int32_t i = 0; i < src_len32; i++) {
- uint32_t code_point;
- if (ReadUnicodeCharacter(src, src_len32, &i, &code_point)) {
- WriteUnicodeCharacter(code_point, output);
- } else {
- WriteUnicodeCharacter(0xFFFD, output);
+
+ for (int32_t i = 0; i < src_len;) {
+ int32_t code_point;
+ CBU8_NEXT(src, i, src_len, code_point);
+
+ if (!IsValidCodepoint(code_point)) {
success = false;
+ code_point = kErrorCodePoint;
}
+
+ UnicodeAppendUnsafe(dest, dest_len, code_point);
}
return success;
}
-} // namespace
+template <typename DestChar>
+bool DoUTFConversion(const char16* src,
+ int32_t src_len,
+ DestChar* dest,
+ int32_t* dest_len) {
+ bool success = true;
-// UTF-8 <-> Wide --------------------------------------------------------------
+ auto ConvertSingleChar = [&success](char16 in) -> int32_t {
+ if (!CBU16_IS_SINGLE(in) || !IsValidCodepoint(in)) {
+ success = false;
+ return kErrorCodePoint;
+ }
+ return in;
+ };
+
+ int32_t i = 0;
+
+ // Always have another symbol in order to avoid checking boundaries in the
+ // middle of the surrogate pair.
+ while (i < src_len - 1) {
+ int32_t code_point;
+
+ if (CBU16_IS_LEAD(src[i]) && CBU16_IS_TRAIL(src[i + 1])) {
+ code_point = CBU16_GET_SUPPLEMENTARY(src[i], src[i + 1]);
+ if (!IsValidCodepoint(code_point)) {
+ code_point = kErrorCodePoint;
+ success = false;
+ }
+ i += 2;
+ } else {
+ code_point = ConvertSingleChar(src[i]);
+ ++i;
+ }
-bool WideToUTF8(const wchar_t* src, size_t src_len, std::string* output) {
- if (IsStringASCII(std::wstring(src, src_len))) {
- output->assign(src, src + src_len);
- return true;
- } else {
- PrepareForUTF8Output(src, src_len, output);
- return ConvertUnicode(src, src_len, output);
+ UnicodeAppendUnsafe(dest, dest_len, code_point);
}
+
+ if (i < src_len)
+ UnicodeAppendUnsafe(dest, dest_len, ConvertSingleChar(src[i]));
+
+ return success;
}
-std::string WideToUTF8(const std::wstring& wide) {
- if (IsStringASCII(wide)) {
- return std::string(wide.data(), wide.data() + wide.length());
+#if defined(WCHAR_T_IS_UTF32)
+
+template <typename DestChar>
+bool DoUTFConversion(const wchar_t* src,
+ int32_t src_len,
+ DestChar* dest,
+ int32_t* dest_len) {
+ bool success = true;
+
+ for (int32_t i = 0; i < src_len; ++i) {
+ int32_t code_point = src[i];
+
+ if (!IsValidCodepoint(code_point)) {
+ success = false;
+ code_point = kErrorCodePoint;
+ }
+
+ UnicodeAppendUnsafe(dest, dest_len, code_point);
}
- std::string ret;
- PrepareForUTF8Output(wide.data(), wide.length(), &ret);
- ConvertUnicode(wide.data(), wide.length(), &ret);
- return ret;
+ return success;
}
-bool UTF8ToWide(const char* src, size_t src_len, std::wstring* output) {
- if (IsStringASCII(StringPiece(src, src_len))) {
- output->assign(src, src + src_len);
+#endif // defined(WCHAR_T_IS_UTF32)
+
+// UTFConversion --------------------------------------------------------------
+// Function template for generating all UTF conversions.
+
+template <typename InputString, typename DestString>
+bool UTFConversion(const InputString& src_str, DestString* dest_str) {
+ if (IsStringASCII(src_str)) {
+ dest_str->assign(src_str.begin(), src_str.end());
return true;
- } else {
- PrepareForUTF16Or32Output(src, src_len, output);
- return ConvertUnicode(src, src_len, output);
}
+
+ dest_str->resize(src_str.length() *
+ size_coefficient_v<typename InputString::value_type,
+ typename DestString::value_type>);
+
+ // Empty string is ASCII => it OK to call operator[].
+ auto* dest = &(*dest_str)[0];
+
+ // ICU requires 32 bit numbers.
+ int32_t src_len32 = static_cast<int32_t>(src_str.length());
+ int32_t dest_len32 = 0;
+
+ bool res = DoUTFConversion(src_str.data(), src_len32, dest, &dest_len32);
+
+ dest_str->resize(dest_len32);
+ dest_str->shrink_to_fit();
+
+ return res;
}
-std::wstring UTF8ToWide(StringPiece utf8) {
- if (IsStringASCII(utf8)) {
- return std::wstring(utf8.begin(), utf8.end());
- }
+} // namespace
- std::wstring ret;
- PrepareForUTF16Or32Output(utf8.data(), utf8.length(), &ret);
- ConvertUnicode(utf8.data(), utf8.length(), &ret);
+// UTF16 <-> UTF8 --------------------------------------------------------------
+
+bool UTF8ToUTF16(const char* src, size_t src_len, string16* output) {
+ return UTFConversion(StringPiece(src, src_len), output);
+}
+
+string16 UTF8ToUTF16(StringPiece utf8) {
+ string16 ret;
+ // Ignore the success flag of this call, it will do the best it can for
+ // invalid input, which is what we want here.
+ UTF8ToUTF16(utf8.data(), utf8.size(), &ret);
+ return ret;
+}
+
+bool UTF16ToUTF8(const char16* src, size_t src_len, std::string* output) {
+ return UTFConversion(StringPiece16(src, src_len), output);
+}
+
+std::string UTF16ToUTF8(StringPiece16 utf16) {
+ std::string ret;
+ // Ignore the success flag of this call, it will do the best it can for
+ // invalid input, which is what we want here.
+ UTF16ToUTF8(utf16.data(), utf16.length(), &ret);
return ret;
}
// UTF-16 <-> Wide -------------------------------------------------------------
#if defined(WCHAR_T_IS_UTF16)
+// When wide == UTF-16 the conversions are a NOP.
-// When wide == UTF-16, then conversions are a NOP.
bool WideToUTF16(const wchar_t* src, size_t src_len, string16* output) {
output->assign(src, src_len);
return true;
}
-string16 WideToUTF16(const std::wstring& wide) {
- return wide;
+string16 WideToUTF16(WStringPiece wide) {
+ return wide.as_string();
}
bool UTF16ToWide(const char16* src, size_t src_len, std::wstring* output) {
@@ -106,113 +245,80 @@ bool UTF16ToWide(const char16* src, size_t src_len, std::wstring* output) {
return true;
}
-std::wstring UTF16ToWide(const string16& utf16) {
- return utf16;
+std::wstring UTF16ToWide(StringPiece16 utf16) {
+ return utf16.as_string();
}
#elif defined(WCHAR_T_IS_UTF32)
bool WideToUTF16(const wchar_t* src, size_t src_len, string16* output) {
- output->clear();
- // Assume that normally we won't have any non-BMP characters so the counts
- // will be the same.
- output->reserve(src_len);
- return ConvertUnicode(src, src_len, output);
+ return UTFConversion(base::WStringPiece(src, src_len), output);
}
-string16 WideToUTF16(const std::wstring& wide) {
+string16 WideToUTF16(WStringPiece wide) {
string16 ret;
+ // Ignore the success flag of this call, it will do the best it can for
+ // invalid input, which is what we want here.
WideToUTF16(wide.data(), wide.length(), &ret);
return ret;
}
bool UTF16ToWide(const char16* src, size_t src_len, std::wstring* output) {
- output->clear();
- // Assume that normally we won't have any non-BMP characters so the counts
- // will be the same.
- output->reserve(src_len);
- return ConvertUnicode(src, src_len, output);
+ return UTFConversion(StringPiece16(src, src_len), output);
}
-std::wstring UTF16ToWide(const string16& utf16) {
+std::wstring UTF16ToWide(StringPiece16 utf16) {
std::wstring ret;
+ // Ignore the success flag of this call, it will do the best it can for
+ // invalid input, which is what we want here.
UTF16ToWide(utf16.data(), utf16.length(), &ret);
return ret;
}
#endif // defined(WCHAR_T_IS_UTF32)
-// UTF16 <-> UTF8 --------------------------------------------------------------
+// UTF-8 <-> Wide --------------------------------------------------------------
-#if defined(WCHAR_T_IS_UTF32)
+// UTF8ToWide is the same code, regardless of whether wide is 16 or 32 bits
-bool UTF8ToUTF16(const char* src, size_t src_len, string16* output) {
- if (IsStringASCII(StringPiece(src, src_len))) {
- output->assign(src, src + src_len);
- return true;
- } else {
- PrepareForUTF16Or32Output(src, src_len, output);
- return ConvertUnicode(src, src_len, output);
- }
+bool UTF8ToWide(const char* src, size_t src_len, std::wstring* output) {
+ return UTFConversion(StringPiece(src, src_len), output);
}
-string16 UTF8ToUTF16(StringPiece utf8) {
- if (IsStringASCII(utf8)) {
- return string16(utf8.begin(), utf8.end());
- }
-
- string16 ret;
- PrepareForUTF16Or32Output(utf8.data(), utf8.length(), &ret);
+std::wstring UTF8ToWide(StringPiece utf8) {
+ std::wstring ret;
// Ignore the success flag of this call, it will do the best it can for
// invalid input, which is what we want here.
- ConvertUnicode(utf8.data(), utf8.length(), &ret);
+ UTF8ToWide(utf8.data(), utf8.length(), &ret);
return ret;
}
-bool UTF16ToUTF8(const char16* src, size_t src_len, std::string* output) {
- if (IsStringASCII(StringPiece16(src, src_len))) {
- output->assign(src, src + src_len);
- return true;
- } else {
- PrepareForUTF8Output(src, src_len, output);
- return ConvertUnicode(src, src_len, output);
- }
-}
+#if defined(WCHAR_T_IS_UTF16)
+// Easy case since we can use the "utf" versions we already wrote above.
-std::string UTF16ToUTF8(StringPiece16 utf16) {
- std::string ret;
- // Ignore the success flag of this call, it will do the best it can for
- // invalid input, which is what we want here.
- UTF16ToUTF8(utf16.data(), utf16.length(), &ret);
- return ret;
+bool WideToUTF8(const wchar_t* src, size_t src_len, std::string* output) {
+ return UTF16ToUTF8(src, src_len, output);
}
-#elif defined(WCHAR_T_IS_UTF16)
-// Easy case since we can use the "wide" versions we already wrote above.
-
-bool UTF8ToUTF16(const char* src, size_t src_len, string16* output) {
- return UTF8ToWide(src, src_len, output);
+std::string WideToUTF8(WStringPiece wide) {
+ return UTF16ToUTF8(wide);
}
-string16 UTF8ToUTF16(StringPiece utf8) {
- return UTF8ToWide(utf8);
-}
+#elif defined(WCHAR_T_IS_UTF32)
-bool UTF16ToUTF8(const char16* src, size_t src_len, std::string* output) {
- return WideToUTF8(src, src_len, output);
+bool WideToUTF8(const wchar_t* src, size_t src_len, std::string* output) {
+ return UTFConversion(WStringPiece(src, src_len), output);
}
-std::string UTF16ToUTF8(StringPiece16 utf16) {
- if (IsStringASCII(utf16))
- return std::string(utf16.data(), utf16.data() + utf16.length());
-
+std::string WideToUTF8(WStringPiece wide) {
std::string ret;
- PrepareForUTF8Output(utf16.data(), utf16.length(), &ret);
- ConvertUnicode(utf16.data(), utf16.length(), &ret);
+ // Ignore the success flag of this call, it will do the best it can for
+ // invalid input, which is what we want here.
+ WideToUTF8(wide.data(), wide.length(), &ret);
return ret;
}
-#endif
+#endif // defined(WCHAR_T_IS_UTF32)
string16 ASCIIToUTF16(StringPiece ascii) {
DCHECK(IsStringASCII(ascii)) << ascii;
diff --git a/base/strings/utf_string_conversions.h b/base/strings/utf_string_conversions.h
index 2995f4cbcf..14f94ac967 100644
--- a/base/strings/utf_string_conversions.h
+++ b/base/strings/utf_string_conversions.h
@@ -23,17 +23,17 @@ namespace base {
// possible.
BASE_EXPORT bool WideToUTF8(const wchar_t* src, size_t src_len,
std::string* output);
-BASE_EXPORT std::string WideToUTF8(const std::wstring& wide);
+BASE_EXPORT std::string WideToUTF8(WStringPiece wide);
BASE_EXPORT bool UTF8ToWide(const char* src, size_t src_len,
std::wstring* output);
BASE_EXPORT std::wstring UTF8ToWide(StringPiece utf8);
BASE_EXPORT bool WideToUTF16(const wchar_t* src, size_t src_len,
string16* output);
-BASE_EXPORT string16 WideToUTF16(const std::wstring& wide);
+BASE_EXPORT string16 WideToUTF16(WStringPiece wide);
BASE_EXPORT bool UTF16ToWide(const char16* src, size_t src_len,
std::wstring* output);
-BASE_EXPORT std::wstring UTF16ToWide(const string16& utf16);
+BASE_EXPORT std::wstring UTF16ToWide(StringPiece16 utf16);
BASE_EXPORT bool UTF8ToUTF16(const char* src, size_t src_len, string16* output);
BASE_EXPORT string16 UTF8ToUTF16(StringPiece utf8);
diff --git a/base/strings/utf_string_conversions_fuzzer.cc b/base/strings/utf_string_conversions_fuzzer.cc
new file mode 100644
index 0000000000..37d4be215d
--- /dev/null
+++ b/base/strings/utf_string_conversions_fuzzer.cc
@@ -0,0 +1,56 @@
+// 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.
+
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+
+std::string output_std_string;
+std::wstring output_std_wstring;
+base::string16 output_string16;
+
+// Entry point for LibFuzzer.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ base::StringPiece string_piece_input(reinterpret_cast<const char*>(data),
+ size);
+
+ base::UTF8ToWide(string_piece_input);
+ base::UTF8ToWide(reinterpret_cast<const char*>(data), size,
+ &output_std_wstring);
+ base::UTF8ToUTF16(string_piece_input);
+ base::UTF8ToUTF16(reinterpret_cast<const char*>(data), size,
+ &output_string16);
+
+ // Test for char16.
+ if (size % 2 == 0) {
+ base::StringPiece16 string_piece_input16(
+ reinterpret_cast<const base::char16*>(data), size / 2);
+ base::UTF16ToWide(output_string16);
+ base::UTF16ToWide(reinterpret_cast<const base::char16*>(data), size / 2,
+ &output_std_wstring);
+ base::UTF16ToUTF8(string_piece_input16);
+ base::UTF16ToUTF8(reinterpret_cast<const base::char16*>(data), size / 2,
+ &output_std_string);
+ }
+
+ // Test for wchar_t.
+ size_t wchar_t_size = sizeof(wchar_t);
+ if (size % wchar_t_size == 0) {
+ base::WideToUTF8(output_std_wstring);
+ base::WideToUTF8(reinterpret_cast<const wchar_t*>(data),
+ size / wchar_t_size, &output_std_string);
+ base::WideToUTF16(output_std_wstring);
+ base::WideToUTF16(reinterpret_cast<const wchar_t*>(data),
+ size / wchar_t_size, &output_string16);
+ }
+
+ // Test for ASCII. This condition is needed to avoid hitting instant CHECK
+ // failures.
+ if (base::IsStringASCII(string_piece_input)) {
+ output_string16 = base::ASCIIToUTF16(string_piece_input);
+ base::StringPiece16 string_piece_input16(output_string16);
+ base::UTF16ToASCII(string_piece_input16);
+ }
+
+ return 0;
+}
diff --git a/base/strings/utf_string_conversions_regression_fuzzer.cc b/base/strings/utf_string_conversions_regression_fuzzer.cc
new file mode 100644
index 0000000000..ca6b4a27a3
--- /dev/null
+++ b/base/strings/utf_string_conversions_regression_fuzzer.cc
@@ -0,0 +1,105 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/logging.h"
+#include "base/strings/old_utf_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace {
+
+void UTF8ToCheck(const uint8_t* data, size_t size) {
+ const auto* src = reinterpret_cast<const char*>(data);
+ const size_t src_len = size;
+
+ // UTF16
+ {
+ base::string16 new_out;
+ bool new_res = base::UTF8ToUTF16(src, src_len, &new_out);
+
+ base::string16 old_out;
+ bool old_res = base_old::UTF8ToUTF16(src, src_len, &old_out);
+
+ CHECK(new_res == old_res);
+ CHECK(new_out == old_out);
+ }
+
+ // Wide
+ {
+ std::wstring new_out;
+ bool new_res = base::UTF8ToWide(src, src_len, &new_out);
+
+ std::wstring old_out;
+ bool old_res = base_old::UTF8ToWide(src, src_len, &old_out);
+
+ CHECK(new_res == old_res);
+ CHECK(new_out == old_out);
+ }
+}
+
+void UTF16ToCheck(const uint8_t* data, size_t size) {
+ const auto* src = reinterpret_cast<const base::char16*>(data);
+ const size_t src_len = size / 2;
+
+ // UTF8
+ {
+ std::string new_out;
+ bool new_res = base::UTF16ToUTF8(src, src_len, &new_out);
+
+ std::string old_out;
+ bool old_res = base_old::UTF16ToUTF8(src, src_len, &old_out);
+
+ CHECK(new_res == old_res);
+ CHECK(new_out == old_out);
+ }
+
+ // Wide
+ {
+ std::wstring new_out;
+ bool new_res = base::UTF16ToWide(src, src_len, &new_out);
+
+ std::wstring old_out;
+ bool old_res = base_old::UTF16ToWide(src, src_len, &old_out);
+
+ CHECK(new_res == old_res);
+ CHECK(new_out == old_out);
+ }
+}
+
+void WideToCheck(const uint8_t* data, size_t size) {
+ const auto* src = reinterpret_cast<const wchar_t*>(data);
+ const size_t src_len = size / 4; // It's OK even if Wide is 16bit.
+
+ // UTF8
+ {
+ std::string new_out;
+ bool new_res = base::WideToUTF8(src, src_len, &new_out);
+
+ std::string old_out;
+ bool old_res = base_old::WideToUTF8(src, src_len, &old_out);
+
+ CHECK(new_res == old_res);
+ CHECK(new_out == old_out);
+ }
+
+ // UTF16
+ {
+ base::string16 new_out;
+ bool new_res = base::WideToUTF16(src, src_len, &new_out);
+
+ base::string16 old_out;
+ bool old_res = base_old::WideToUTF16(src, src_len, &old_out);
+
+ CHECK(new_res == old_res);
+ CHECK(new_out == old_out);
+ }
+}
+
+} // namespace
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ UTF8ToCheck(data, size);
+ UTF16ToCheck(data, size);
+ WideToCheck(data, size);
+ return 0;
+}
diff --git a/base/strings/utf_string_conversions_unittest.cc b/base/strings/utf_string_conversions_unittest.cc
index 810771357a..6f5e60cb95 100644
--- a/base/strings/utf_string_conversions_unittest.cc
+++ b/base/strings/utf_string_conversions_unittest.cc
@@ -83,9 +83,9 @@ TEST(UTFStringConversionsTest, ConvertUTF8ToWide) {
// Truncated off the end.
{"\xe5\xa5\xbd\xe4\xa0", L"\x597d\xfffd", false},
// Non-shortest-form UTF-8.
- {"\xf0\x84\xbd\xa0\xe5\xa5\xbd", L"\xfffd\x597d", false},
+ {"\xf0\x84\xbd\xa0\xe5\xa5\xbd", L"\xfffd\xfffd\xfffd\xfffd\x597d", false},
// This UTF-8 character decodes to a UTF-16 surrogate, which is illegal.
- {"\xed\xb0\x80", L"\xfffd", false},
+ {"\xed\xb0\x80", L"\xfffd\xfffd\xfffd", false},
// Non-BMP characters. The second is a non-character regarded as valid.
// The result will either be in UTF-16 or UTF-32.
#if defined(WCHAR_T_IS_UTF16)
diff --git a/base/sync_socket.h b/base/sync_socket.h
index fcf4155047..42db9a2b0d 100644
--- a/base/sync_socket.h
+++ b/base/sync_socket.h
@@ -24,7 +24,7 @@
#endif
#include <sys/types.h>
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
#include "base/file_descriptor_posix.h"
#endif
@@ -35,7 +35,7 @@ class BASE_EXPORT SyncSocket {
#if defined(OS_WIN)
typedef HANDLE Handle;
typedef Handle TransitDescriptor;
-#else
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
typedef int Handle;
typedef FileDescriptor TransitDescriptor;
#endif
@@ -93,6 +93,9 @@ class BASE_EXPORT SyncSocket {
// processes.
Handle handle() const { return handle_; }
+ // Extracts and takes ownership of the contained handle.
+ Handle Release();
+
protected:
Handle handle_;
@@ -107,7 +110,7 @@ class BASE_EXPORT CancelableSyncSocket : public SyncSocket {
public:
CancelableSyncSocket();
explicit CancelableSyncSocket(Handle handle);
- ~CancelableSyncSocket() override {}
+ ~CancelableSyncSocket() override = default;
// Initializes a pair of cancelable sockets. See documentation for
// SyncSocket::CreatePair for more details.
diff --git a/base/sync_socket_posix.cc b/base/sync_socket_posix.cc
index 5d9e25e5ea..ff1e0e6caa 100644
--- a/base/sync_socket_posix.cc
+++ b/base/sync_socket_posix.cc
@@ -7,6 +7,7 @@
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
+#include <poll.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/ioctl.h>
@@ -119,12 +120,12 @@ bool SyncSocket::Close() {
}
size_t SyncSocket::Send(const void* buffer, size_t length) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
return SendHelper(handle_, buffer, length);
}
size_t SyncSocket::Receive(void* buffer, size_t length) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
DCHECK_GT(length, 0u);
DCHECK_LE(length, kMaxMessageLength);
DCHECK_NE(handle_, kInvalidHandle);
@@ -137,49 +138,47 @@ size_t SyncSocket::Receive(void* buffer, size_t length) {
size_t SyncSocket::ReceiveWithTimeout(void* buffer,
size_t length,
TimeDelta timeout) {
- ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
DCHECK_GT(length, 0u);
DCHECK_LE(length, kMaxMessageLength);
DCHECK_NE(handle_, kInvalidHandle);
- // TODO(dalecurtis): There's an undiagnosed issue on OSX where we're seeing
- // large numbers of open files which prevents select() from being used. In
- // this case, the best we can do is Peek() to see if we can Receive() now or
- // return a timeout error (0) if not. See http://crbug.com/314364.
- if (handle_ >= FD_SETSIZE)
- return Peek() < length ? 0 : Receive(buffer, length);
-
// Only timeouts greater than zero and less than one second are allowed.
DCHECK_GT(timeout.InMicroseconds(), 0);
DCHECK_LT(timeout.InMicroseconds(),
- base::TimeDelta::FromSeconds(1).InMicroseconds());
+ TimeDelta::FromSeconds(1).InMicroseconds());
// Track the start time so we can reduce the timeout as data is read.
TimeTicks start_time = TimeTicks::Now();
const TimeTicks finish_time = start_time + timeout;
- fd_set read_fds;
- size_t bytes_read_total;
- for (bytes_read_total = 0;
- bytes_read_total < length && timeout.InMicroseconds() > 0;
- timeout = finish_time - base::TimeTicks::Now()) {
- FD_ZERO(&read_fds);
- FD_SET(handle_, &read_fds);
-
- // Wait for data to become available.
- struct timeval timeout_struct =
- { 0, static_cast<suseconds_t>(timeout.InMicroseconds()) };
- const int select_result =
- select(handle_ + 1, &read_fds, NULL, NULL, &timeout_struct);
+ struct pollfd pollfd;
+ pollfd.fd = handle_;
+ pollfd.events = POLLIN;
+ pollfd.revents = 0;
+
+ size_t bytes_read_total = 0;
+ while (bytes_read_total < length) {
+ const TimeDelta this_timeout = finish_time - TimeTicks::Now();
+ const int timeout_ms =
+ static_cast<int>(this_timeout.InMillisecondsRoundedUp());
+ if (timeout_ms <= 0)
+ break;
+ const int poll_result = poll(&pollfd, 1, timeout_ms);
// Handle EINTR manually since we need to update the timeout value.
- if (select_result == -1 && errno == EINTR)
+ if (poll_result == -1 && errno == EINTR)
continue;
- if (select_result <= 0)
+ // Return if other type of error or a timeout.
+ if (poll_result <= 0)
return bytes_read_total;
- // select() only tells us that data is ready for reading, not how much. We
+ // poll() only tells us that data is ready for reading, not how much. We
// must Peek() for the amount ready for reading to avoid blocking.
- DCHECK(FD_ISSET(handle_, &read_fds));
+ // At hang up (POLLHUP), the write end has been closed and there might still
+ // be data to be read.
+ // No special handling is needed for error (POLLERR); we can let any of the
+ // following operations fail and handle it there.
+ DCHECK(pollfd.revents & (POLLIN | POLLHUP | POLLERR)) << pollfd.revents;
const size_t bytes_to_read = std::min(Peek(), length - bytes_read_total);
// There may be zero bytes to read if the socket at the other end closed.
@@ -207,7 +206,13 @@ size_t SyncSocket::Peek() {
return number_chars;
}
-CancelableSyncSocket::CancelableSyncSocket() {}
+SyncSocket::Handle SyncSocket::Release() {
+ Handle r = handle_;
+ handle_ = kInvalidHandle;
+ return r;
+}
+
+CancelableSyncSocket::CancelableSyncSocket() = default;
CancelableSyncSocket::CancelableSyncSocket(Handle handle)
: SyncSocket(handle) {
}
diff --git a/base/sync_socket_unittest.cc b/base/sync_socket_unittest.cc
index 97a1aec47d..fdcd9a1cc6 100644
--- a/base/sync_socket_unittest.cc
+++ b/base/sync_socket_unittest.cc
@@ -2,51 +2,73 @@
// 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/sync_socket.h"
+
+#include "base/macros.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 "testing/gtest/include/gtest/gtest.h"
+namespace base {
+
namespace {
-const int kReceiveTimeoutInMilliseconds = 750;
+constexpr TimeDelta kReceiveTimeout = base::TimeDelta::FromMilliseconds(750);
-class HangingReceiveThread : public base::DelegateSimpleThread::Delegate {
+class HangingReceiveThread : public DelegateSimpleThread::Delegate {
public:
- explicit HangingReceiveThread(base::SyncSocket* socket)
+ explicit HangingReceiveThread(SyncSocket* socket, bool with_timeout)
: socket_(socket),
- thread_(this, "HangingReceiveThread") {
+ thread_(this, "HangingReceiveThread"),
+ with_timeout_(with_timeout),
+ started_event_(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED),
+ done_event_(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED) {
thread_.Start();
}
- ~HangingReceiveThread() override {}
+ ~HangingReceiveThread() override = default;
void Run() override {
int data = 0;
ASSERT_EQ(socket_->Peek(), 0u);
- // Use receive with timeout so we don't hang the test harness indefinitely.
- ASSERT_EQ(0u, socket_->ReceiveWithTimeout(
- &data, sizeof(data), base::TimeDelta::FromMilliseconds(
- kReceiveTimeoutInMilliseconds)));
+ started_event_.Signal();
+
+ if (with_timeout_) {
+ ASSERT_EQ(0u, socket_->ReceiveWithTimeout(&data, sizeof(data),
+ kReceiveTimeout));
+ } else {
+ ASSERT_EQ(0u, socket_->Receive(&data, sizeof(data)));
+ }
+
+ done_event_.Signal();
}
void Stop() {
thread_.Join();
}
+ WaitableEvent* started_event() { return &started_event_; }
+ WaitableEvent* done_event() { return &done_event_; }
+
private:
- base::SyncSocket* socket_;
- base::DelegateSimpleThread thread_;
+ SyncSocket* socket_;
+ DelegateSimpleThread thread_;
+ bool with_timeout_;
+ WaitableEvent started_event_;
+ WaitableEvent done_event_;
DISALLOW_COPY_AND_ASSIGN(HangingReceiveThread);
};
-// Tests sending data between two SyncSockets. Uses ASSERT() and thus will exit
+// Tests sending data between two SyncSockets. Uses ASSERT() and thus will exit
// early upon failure. Callers should use ASSERT_NO_FATAL_FAILURE() if testing
// continues after return.
-void SendReceivePeek(base::SyncSocket* socket_a, base::SyncSocket* socket_b) {
+void SendReceivePeek(SyncSocket* socket_a, SyncSocket* socket_b) {
int received = 0;
const int kSending = 123;
static_assert(sizeof(kSending) == sizeof(received), "invalid data size");
@@ -78,54 +100,91 @@ void SendReceivePeek(base::SyncSocket* socket_a, base::SyncSocket* socket_b) {
ASSERT_TRUE(socket_b->Close());
}
-template <class SocketType>
-void NormalSendReceivePeek() {
- SocketType socket_a, socket_b;
- ASSERT_TRUE(SocketType::CreatePair(&socket_a, &socket_b));
- SendReceivePeek(&socket_a, &socket_b);
-}
+} // namespace
-template <class SocketType>
-void ClonedSendReceivePeek() {
- SocketType socket_a, socket_b;
- ASSERT_TRUE(SocketType::CreatePair(&socket_a, &socket_b));
+class SyncSocketTest : public testing::Test {
+ public:
+ void SetUp() override {
+ ASSERT_TRUE(SyncSocket::CreatePair(&socket_a_, &socket_b_));
+ }
- // Create new SyncSockets from the paired handles.
- SocketType socket_c(socket_a.handle()), socket_d(socket_b.handle());
- SendReceivePeek(&socket_c, &socket_d);
+ protected:
+ SyncSocket socket_a_;
+ SyncSocket socket_b_;
+};
+
+TEST_F(SyncSocketTest, NormalSendReceivePeek) {
+ SendReceivePeek(&socket_a_, &socket_b_);
}
-} // namespace
+TEST_F(SyncSocketTest, ClonedSendReceivePeek) {
+ SyncSocket socket_c(socket_a_.Release());
+ SyncSocket socket_d(socket_b_.Release());
+ SendReceivePeek(&socket_c, &socket_d);
+};
-TEST(SyncSocket, NormalSendReceivePeek) {
- NormalSendReceivePeek<base::SyncSocket>();
-}
+class CancelableSyncSocketTest : public testing::Test {
+ public:
+ void SetUp() override {
+ ASSERT_TRUE(CancelableSyncSocket::CreatePair(&socket_a_, &socket_b_));
+ }
-TEST(SyncSocket, ClonedSendReceivePeek) {
- ClonedSendReceivePeek<base::SyncSocket>();
+ protected:
+ CancelableSyncSocket socket_a_;
+ CancelableSyncSocket socket_b_;
+};
+
+TEST_F(CancelableSyncSocketTest, NormalSendReceivePeek) {
+ SendReceivePeek(&socket_a_, &socket_b_);
}
-TEST(CancelableSyncSocket, NormalSendReceivePeek) {
- NormalSendReceivePeek<base::CancelableSyncSocket>();
+TEST_F(CancelableSyncSocketTest, ClonedSendReceivePeek) {
+ CancelableSyncSocket socket_c(socket_a_.Release());
+ CancelableSyncSocket socket_d(socket_b_.Release());
+ SendReceivePeek(&socket_c, &socket_d);
}
-TEST(CancelableSyncSocket, ClonedSendReceivePeek) {
- ClonedSendReceivePeek<base::CancelableSyncSocket>();
+TEST_F(CancelableSyncSocketTest, ShutdownCancelsReceive) {
+ HangingReceiveThread thread(&socket_b_, /* with_timeout = */ false);
+
+ // Wait for the thread to be started. Note that this doesn't guarantee that
+ // Receive() is called before Shutdown().
+ thread.started_event()->Wait();
+
+ EXPECT_TRUE(socket_b_.Shutdown());
+ EXPECT_TRUE(thread.done_event()->TimedWait(kReceiveTimeout));
+
+ thread.Stop();
}
-TEST(CancelableSyncSocket, CancelReceiveShutdown) {
- base::CancelableSyncSocket socket_a, socket_b;
- ASSERT_TRUE(base::CancelableSyncSocket::CreatePair(&socket_a, &socket_b));
+TEST_F(CancelableSyncSocketTest, ShutdownCancelsReceiveWithTimeout) {
+ HangingReceiveThread thread(&socket_b_, /* with_timeout = */ true);
+
+ // Wait for the thread to be started. Note that this doesn't guarantee that
+ // Receive() is called before Shutdown().
+ thread.started_event()->Wait();
+
+ EXPECT_TRUE(socket_b_.Shutdown());
+ EXPECT_TRUE(thread.done_event()->TimedWait(kReceiveTimeout));
- base::TimeTicks start = base::TimeTicks::Now();
- HangingReceiveThread thread(&socket_b);
- ASSERT_TRUE(socket_b.Shutdown());
thread.Stop();
+}
- // Ensure the receive didn't just timeout.
- ASSERT_LT((base::TimeTicks::Now() - start).InMilliseconds(),
- kReceiveTimeoutInMilliseconds);
+TEST_F(CancelableSyncSocketTest, ReceiveAfterShutdown) {
+ socket_a_.Shutdown();
+ int data = 0;
+ EXPECT_EQ(0u, socket_a_.Receive(&data, sizeof(data)));
+}
+
+TEST_F(CancelableSyncSocketTest, ReceiveWithTimeoutAfterShutdown) {
+ socket_a_.Shutdown();
+ TimeTicks start = TimeTicks::Now();
+ int data = 0;
+ EXPECT_EQ(0u,
+ socket_a_.ReceiveWithTimeout(&data, sizeof(data), kReceiveTimeout));
- ASSERT_TRUE(socket_a.Close());
- ASSERT_TRUE(socket_b.Close());
+ // Ensure the receive didn't just timeout.
+ EXPECT_LT(TimeTicks::Now() - start, kReceiveTimeout);
}
+
+} // namespace base
diff --git a/base/synchronization/atomic_flag_unittest.cc b/base/synchronization/atomic_flag_unittest.cc
index a3aa3341a0..f7daafa502 100644
--- a/base/synchronization/atomic_flag_unittest.cc
+++ b/base/synchronization/atomic_flag_unittest.cc
@@ -68,10 +68,9 @@ TEST(AtomicFlagTest, ReadFromDifferentThread) {
Thread thread("AtomicFlagTest.ReadFromDifferentThread");
ASSERT_TRUE(thread.Start());
- thread.task_runner()->PostTask(
- FROM_HERE,
- Bind(&BusyWaitUntilFlagIsSet, &tested_flag, &expected_after_flag,
- &reset_flag));
+ thread.task_runner()->PostTask(FROM_HERE,
+ BindOnce(&BusyWaitUntilFlagIsSet, &tested_flag,
+ &expected_after_flag, &reset_flag));
// To verify that IsSet() fetches the flag's value from memory every time it
// is called (not just the first time that it is called on a thread), sleep
@@ -90,7 +89,8 @@ TEST(AtomicFlagTest, ReadFromDifferentThread) {
// Use |reset_flag| to confirm that the above completed (which the rest of
// this test assumes).
- ASSERT_TRUE(reset_flag.IsSet());
+ while (!reset_flag.IsSet())
+ PlatformThread::YieldCurrentThread();
tested_flag.UnsafeResetForTesting();
EXPECT_FALSE(tested_flag.IsSet());
@@ -100,10 +100,9 @@ TEST(AtomicFlagTest, ReadFromDifferentThread) {
// |thread| is guaranteed to be synchronized past the
// |UnsafeResetForTesting()| call when the task runs per the implicit
// synchronization in the post task mechanism.
- thread.task_runner()->PostTask(
- FROM_HERE,
- Bind(&BusyWaitUntilFlagIsSet, &tested_flag, &expected_after_flag,
- nullptr));
+ thread.task_runner()->PostTask(FROM_HERE,
+ BindOnce(&BusyWaitUntilFlagIsSet, &tested_flag,
+ &expected_after_flag, nullptr));
PlatformThread::Sleep(TimeDelta::FromMilliseconds(20));
@@ -118,14 +117,19 @@ TEST(AtomicFlagTest, SetOnDifferentSequenceDeathTest) {
// Checks that Set() can't be called from another sequence after being called
// on this one. AtomicFlag should die on a DCHECK if Set() is called again
// from another sequence.
+
+ // Note: flag must be declared before the Thread so that its destructor runs
+ // later. Otherwise there's a race between destructing flag and running
+ // ExpectSetFlagDeath.
+ AtomicFlag flag;
+
::testing::FLAGS_gtest_death_test_style = "threadsafe";
Thread t("AtomicFlagTest.SetOnDifferentThreadDeathTest");
ASSERT_TRUE(t.Start());
EXPECT_TRUE(t.WaitUntilThreadStarted());
- AtomicFlag flag;
flag.Set();
- t.task_runner()->PostTask(FROM_HERE, Bind(&ExpectSetFlagDeath, &flag));
+ t.task_runner()->PostTask(FROM_HERE, BindOnce(&ExpectSetFlagDeath, &flag));
}
} // namespace base
diff --git a/base/synchronization/condition_variable.h b/base/synchronization/condition_variable.h
index b567751172..dfcf813d4b 100644
--- a/base/synchronization/condition_variable.h
+++ b/base/synchronization/condition_variable.h
@@ -65,18 +65,18 @@
#ifndef BASE_SYNCHRONIZATION_CONDITION_VARIABLE_H_
#define BASE_SYNCHRONIZATION_CONDITION_VARIABLE_H_
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <pthread.h>
+#endif
+
#include "base/base_export.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/synchronization/lock.h"
#include "build/build_config.h"
-#if defined(OS_POSIX)
-#include <pthread.h>
-#endif
-
#if defined(OS_WIN)
-#include <windows.h>
+#include "base/win/windows_types.h"
#endif
namespace base {
@@ -105,14 +105,14 @@ class BASE_EXPORT ConditionVariable {
private:
#if defined(OS_WIN)
- CONDITION_VARIABLE cv_;
- SRWLOCK* const srwlock_;
-#elif defined(OS_POSIX)
+ CHROME_CONDITION_VARIABLE cv_;
+ CHROME_SRWLOCK* const srwlock_;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
pthread_cond_t condition_;
pthread_mutex_t* user_mutex_;
#endif
-#if DCHECK_IS_ON() && (defined(OS_WIN) || defined(OS_POSIX))
+#if DCHECK_IS_ON()
base::Lock* const user_lock_; // Needed to adjust shadow lock state on wait.
#endif
diff --git a/base/synchronization/condition_variable_posix.cc b/base/synchronization/condition_variable_posix.cc
index d07c671810..f263252b87 100644
--- a/base/synchronization/condition_variable_posix.cc
+++ b/base/synchronization/condition_variable_posix.cc
@@ -9,6 +9,7 @@
#include <sys/time.h>
#include "base/synchronization/lock.h"
+#include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "build/build_config.h"
@@ -62,7 +63,8 @@ ConditionVariable::~ConditionVariable() {
}
void ConditionVariable::Wait() {
- base::ThreadRestrictions::AssertWaitAllowed();
+ internal::AssertBaseSyncPrimitivesAllowed();
+ ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
#if DCHECK_IS_ON()
user_lock_->CheckHeldAndUnmark();
#endif
@@ -74,7 +76,8 @@ void ConditionVariable::Wait() {
}
void ConditionVariable::TimedWait(const TimeDelta& max_time) {
- base::ThreadRestrictions::AssertWaitAllowed();
+ internal::AssertBaseSyncPrimitivesAllowed();
+ ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
int64_t usecs = max_time.InMicroseconds();
struct timespec relative_time;
relative_time.tv_sec = usecs / Time::kMicrosecondsPerSecond;
diff --git a/base/synchronization/condition_variable_unittest.cc b/base/synchronization/condition_variable_unittest.cc
index d60b2b8af5..d66aecc491 100644
--- a/base/synchronization/condition_variable_unittest.cc
+++ b/base/synchronization/condition_variable_unittest.cc
@@ -201,9 +201,9 @@ void BackInTime(Lock* lock) {
AutoLock auto_lock(*lock);
timeval tv;
- gettimeofday(&tv, NULL);
+ gettimeofday(&tv, nullptr);
tv.tv_sec -= kDiscontinuitySeconds;
- settimeofday(&tv, NULL);
+ settimeofday(&tv, nullptr);
}
// Tests that TimedWait ignores changes to the system clock.
@@ -212,9 +212,9 @@ void BackInTime(Lock* lock) {
// http://crbug.com/293736
TEST_F(ConditionVariableTest, DISABLED_TimeoutAcrossSetTimeOfDay) {
timeval tv;
- gettimeofday(&tv, NULL);
+ gettimeofday(&tv, nullptr);
tv.tv_sec += kDiscontinuitySeconds;
- if (settimeofday(&tv, NULL) < 0) {
+ if (settimeofday(&tv, nullptr) < 0) {
PLOG(ERROR) << "Could not set time of day. Run as root?";
return;
}
@@ -225,7 +225,7 @@ TEST_F(ConditionVariableTest, DISABLED_TimeoutAcrossSetTimeOfDay) {
Thread thread("Helper");
thread.Start();
- thread.task_runner()->PostTask(FROM_HERE, base::Bind(&BackInTime, &lock));
+ thread.task_runner()->PostTask(FROM_HERE, base::BindOnce(&BackInTime, &lock));
TimeTicks start = TimeTicks::Now();
const TimeDelta kWaitTime = TimeDelta::FromMilliseconds(300);
@@ -245,10 +245,10 @@ TEST_F(ConditionVariableTest, DISABLED_TimeoutAcrossSetTimeOfDay) {
}
#endif
-
// Suddenly got flaky on Win, see http://crbug.com/10607 (starting at
// comment #15).
-#if defined(OS_WIN)
+// This is also flaky on Fuchsia, see http://crbug.com/738275.
+#if defined(OS_WIN) || defined(OS_FUCHSIA)
#define MAYBE_MultiThreadConsumerTest DISABLED_MultiThreadConsumerTest
#else
#define MAYBE_MultiThreadConsumerTest MultiThreadConsumerTest
@@ -398,7 +398,13 @@ TEST_F(ConditionVariableTest, MAYBE_MultiThreadConsumerTest) {
queue.ThreadSafeCheckShutdown(kThreadCount));
}
-TEST_F(ConditionVariableTest, LargeFastTaskTest) {
+#if defined(OS_FUCHSIA)
+// TODO(crbug.com/751894): This flakily times out on Fuchsia.
+#define MAYBE_LargeFastTaskTest DISABLED_LargeFastTaskTest
+#else
+#define MAYBE_LargeFastTaskTest LargeFastTaskTest
+#endif
+TEST_F(ConditionVariableTest, MAYBE_LargeFastTaskTest) {
const int kThreadCount = 200;
WorkQueue queue(kThreadCount); // Start the threads.
diff --git a/base/synchronization/lock.h b/base/synchronization/lock.h
index 599984e8b6..d1c647c6bc 100644
--- a/base/synchronization/lock.h
+++ b/base/synchronization/lock.h
@@ -64,26 +64,24 @@ class BASE_EXPORT Lock {
// Whether Lock mitigates priority inversion when used from different thread
// priorities.
static bool HandlesMultipleThreadPriorities() {
-#if defined(OS_POSIX)
- // POSIX mitigates priority inversion by setting the priority of a thread
- // holding a Lock to the maximum priority of any other thread waiting on it.
- return internal::LockImpl::PriorityInheritanceAvailable();
-#elif defined(OS_WIN)
+#if defined(OS_WIN)
// Windows mitigates priority inversion by randomly boosting the priority of
// ready threads.
// https://msdn.microsoft.com/library/windows/desktop/ms684831.aspx
return true;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ // POSIX mitigates priority inversion by setting the priority of a thread
+ // holding a Lock to the maximum priority of any other thread waiting on it.
+ return internal::LockImpl::PriorityInheritanceAvailable();
#else
#error Unsupported platform
#endif
}
-#if defined(OS_POSIX) || defined(OS_WIN)
// Both Windows and POSIX implementations of ConditionVariable need to be
// able to see our lock and tweak our debugging counters, as they release and
// acquire locks inside of their condition variable APIs.
friend class ConditionVariable;
-#endif
private:
#if DCHECK_IS_ON()
diff --git a/base/synchronization/lock_impl.h b/base/synchronization/lock_impl.h
index 603585a050..221d763658 100644
--- a/base/synchronization/lock_impl.h
+++ b/base/synchronization/lock_impl.h
@@ -6,12 +6,14 @@
#define BASE_SYNCHRONIZATION_LOCK_IMPL_H_
#include "base/base_export.h"
+#include "base/logging.h"
#include "base/macros.h"
#include "build/build_config.h"
#if defined(OS_WIN)
-#include <windows.h>
-#elif defined(OS_POSIX)
+#include "base/win/windows_types.h"
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <errno.h>
#include <pthread.h>
#endif
@@ -24,9 +26,9 @@ namespace internal {
class BASE_EXPORT LockImpl {
public:
#if defined(OS_WIN)
- using NativeHandle = SRWLOCK;
-#elif defined(OS_POSIX)
- using NativeHandle = pthread_mutex_t;
+ using NativeHandle = CHROME_SRWLOCK;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ using NativeHandle = pthread_mutex_t;
#endif
LockImpl();
@@ -41,14 +43,14 @@ class BASE_EXPORT LockImpl {
// Release the lock. This must only be called by the lock's holder: after
// a successful call to Try, or a call to Lock.
- void Unlock();
+ inline void Unlock();
// Return the native underlying lock.
// TODO(awalker): refactor lock and condition variables so that this is
// unnecessary.
NativeHandle* native_handle() { return &native_handle_; }
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Whether this lock will attempt to use priority inheritance.
static bool PriorityInheritanceAvailable();
#endif
@@ -59,6 +61,17 @@ class BASE_EXPORT LockImpl {
DISALLOW_COPY_AND_ASSIGN(LockImpl);
};
+#if defined(OS_WIN)
+void LockImpl::Unlock() {
+ ::ReleaseSRWLockExclusive(reinterpret_cast<PSRWLOCK>(&native_handle_));
+}
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+void LockImpl::Unlock() {
+ int rv = pthread_mutex_unlock(&native_handle_);
+ DCHECK_EQ(rv, 0) << ". " << strerror(rv);
+}
+#endif
+
} // namespace internal
} // namespace base
diff --git a/base/synchronization/lock_impl_posix.cc b/base/synchronization/lock_impl_posix.cc
index e54595b87f..7571f68a9a 100644
--- a/base/synchronization/lock_impl_posix.cc
+++ b/base/synchronization/lock_impl_posix.cc
@@ -4,16 +4,43 @@
#include "base/synchronization/lock_impl.h"
-#include <errno.h>
-#include <string.h>
+#include <string>
#include "base/debug/activity_tracker.h"
#include "base/logging.h"
+#include "base/posix/safe_strerror.h"
+#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
+#include "base/synchronization/synchronization_buildflags.h"
+#include "build/build_config.h"
namespace base {
namespace internal {
+namespace {
+
+#if DCHECK_IS_ON()
+const char* AdditionalHintForSystemErrorCode(int error_code) {
+ switch (error_code) {
+ case EINVAL:
+ return "Hint: This is often related to a use-after-free.";
+ default:
+ return "";
+ }
+}
+#endif // DCHECK_IS_ON()
+
+std::string SystemErrorCodeToString(int error_code) {
+#if DCHECK_IS_ON()
+ return base::safe_strerror(error_code) + ". " +
+ AdditionalHintForSystemErrorCode(error_code);
+#else // DCHECK_IS_ON()
+ return std::string();
+#endif // DCHECK_IS_ON()
+}
+
+} // namespace
+
// Determines which platforms can consider using priority inheritance locks. Use
// this define for platform code that may not compile if priority inheritance
// locks aren't available. For this platform code,
@@ -21,7 +48,7 @@ namespace internal {
// Lock::PriorityInheritanceAvailable still must be checked as the code may
// compile but the underlying platform still may not correctly support priority
// inheritance locks.
-#if defined(OS_NACL) || defined(OS_ANDROID)
+#if defined(OS_NACL) || defined(OS_ANDROID) || defined(OS_FUCHSIA)
#define PRIORITY_INHERITANCE_LOCKS_POSSIBLE() 0
#else
#define PRIORITY_INHERITANCE_LOCKS_POSSIBLE() 1
@@ -30,53 +57,61 @@ namespace internal {
LockImpl::LockImpl() {
pthread_mutexattr_t mta;
int rv = pthread_mutexattr_init(&mta);
- DCHECK_EQ(rv, 0) << ". " << strerror(rv);
+ DCHECK_EQ(rv, 0) << ". " << SystemErrorCodeToString(rv);
#if PRIORITY_INHERITANCE_LOCKS_POSSIBLE()
if (PriorityInheritanceAvailable()) {
rv = pthread_mutexattr_setprotocol(&mta, PTHREAD_PRIO_INHERIT);
- DCHECK_EQ(rv, 0) << ". " << strerror(rv);
+ DCHECK_EQ(rv, 0) << ". " << SystemErrorCodeToString(rv);
}
#endif
#ifndef NDEBUG
// In debug, setup attributes for lock error checking.
rv = pthread_mutexattr_settype(&mta, PTHREAD_MUTEX_ERRORCHECK);
- DCHECK_EQ(rv, 0) << ". " << strerror(rv);
+ DCHECK_EQ(rv, 0) << ". " << SystemErrorCodeToString(rv);
#endif
rv = pthread_mutex_init(&native_handle_, &mta);
- DCHECK_EQ(rv, 0) << ". " << strerror(rv);
+ DCHECK_EQ(rv, 0) << ". " << SystemErrorCodeToString(rv);
rv = pthread_mutexattr_destroy(&mta);
- DCHECK_EQ(rv, 0) << ". " << strerror(rv);
+ DCHECK_EQ(rv, 0) << ". " << SystemErrorCodeToString(rv);
}
LockImpl::~LockImpl() {
int rv = pthread_mutex_destroy(&native_handle_);
- DCHECK_EQ(rv, 0) << ". " << strerror(rv);
+ DCHECK_EQ(rv, 0) << ". " << SystemErrorCodeToString(rv);
}
bool LockImpl::Try() {
int rv = pthread_mutex_trylock(&native_handle_);
- DCHECK(rv == 0 || rv == EBUSY) << ". " << strerror(rv);
+ DCHECK(rv == 0 || rv == EBUSY) << ". " << SystemErrorCodeToString(rv);
return rv == 0;
}
void LockImpl::Lock() {
+ // The ScopedLockAcquireActivity below is relatively expensive and so its
+ // actions can become significant due to the very large number of locks
+ // that tend to be used throughout the build. To avoid this cost in the
+ // vast majority of the calls, simply "try" the lock first and only do the
+ // (tracked) blocking call if that fails. Since "try" itself is a system
+ // call, and thus also somewhat expensive, don't bother with it unless
+ // tracking is actually enabled.
+ if (base::debug::GlobalActivityTracker::IsEnabled())
+ if (Try())
+ return;
+
base::debug::ScopedLockAcquireActivity lock_activity(this);
int rv = pthread_mutex_lock(&native_handle_);
- DCHECK_EQ(rv, 0) << ". " << strerror(rv);
-}
-
-void LockImpl::Unlock() {
- int rv = pthread_mutex_unlock(&native_handle_);
- DCHECK_EQ(rv, 0) << ". " << strerror(rv);
+ DCHECK_EQ(rv, 0) << ". " << SystemErrorCodeToString(rv);
}
// static
bool LockImpl::PriorityInheritanceAvailable() {
-#if PRIORITY_INHERITANCE_LOCKS_POSSIBLE() && defined(OS_MACOSX)
+#if BUILDFLAG(ENABLE_MUTEX_PRIORITY_INHERITANCE)
+ return true;
+#elif PRIORITY_INHERITANCE_LOCKS_POSSIBLE() && defined(OS_MACOSX)
return true;
#else
// Security concerns prevent the use of priority inheritance mutexes on Linux.
- // * CVE-2010-0622 - wake_futex_pi unlocks incorrect, possible DoS.
+ // * CVE-2010-0622 - Linux < 2.6.33-rc7, wake_futex_pi possible DoS.
// https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-0622
// * CVE-2012-6647 - Linux < 3.5.1, futex_wait_requeue_pi possible DoS.
// https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2012-6647
@@ -88,7 +123,7 @@ bool LockImpl::PriorityInheritanceAvailable() {
// * glibc Bug 14652: https://sourceware.org/bugzilla/show_bug.cgi?id=14652
// Fixed in glibc 2.17.
// Priority inheritance mutexes may deadlock with condition variables
- // during recacquisition of the mutex after the condition variable is
+ // during reacquisition of the mutex after the condition variable is
// signalled.
return false;
#endif
diff --git a/base/synchronization/lock_unittest.cc b/base/synchronization/lock_unittest.cc
index 27f335e2cc..1e2f9981da 100644
--- a/base/synchronization/lock_unittest.cc
+++ b/base/synchronization/lock_unittest.cc
@@ -7,6 +7,7 @@
#include <stdlib.h>
#include "base/compiler_specific.h"
+#include "base/debug/activity_tracker.h"
#include "base/macros.h"
#include "base/threading/platform_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -146,6 +147,47 @@ TEST(LockTest, TryLock) {
lock.Release();
}
+TEST(LockTest, TryTrackedLock) {
+ // Enable the activity tracker.
+ debug::GlobalActivityTracker::CreateWithLocalMemory(64 << 10, 0, "", 3, 0);
+
+ Lock lock;
+
+ ASSERT_TRUE(lock.Try());
+ // We now have the lock....
+
+ // This thread will not be able to get the lock.
+ {
+ TryLockTestThread thread(&lock);
+ PlatformThreadHandle handle;
+
+ ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle));
+
+ PlatformThread::Join(handle);
+
+ ASSERT_FALSE(thread.got_lock());
+ }
+
+ lock.Release();
+
+ // This thread will....
+ {
+ TryLockTestThread thread(&lock);
+ PlatformThreadHandle handle;
+
+ ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle));
+
+ PlatformThread::Join(handle);
+
+ ASSERT_TRUE(thread.got_lock());
+ // But it released it....
+ ASSERT_TRUE(lock.Try());
+ }
+
+ lock.Release();
+ debug::GlobalActivityTracker::ReleaseForTesting();
+}
+
// Tests that locks actually exclude -------------------------------------------
class MutexLockTestThread : public PlatformThread::Delegate {
diff --git a/base/synchronization/read_write_lock.h b/base/synchronization/read_write_lock.h
deleted file mode 100644
index 4c59b7b116..0000000000
--- a/base/synchronization/read_write_lock.h
+++ /dev/null
@@ -1,105 +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_SYNCHRONIZATION_READ_WRITE_LOCK_H_
-#define BASE_SYNCHRONIZATION_READ_WRITE_LOCK_H_
-
-#include "base/base_export.h"
-#include "base/macros.h"
-#include "build/build_config.h"
-
-#if defined(OS_NACL)
-#include "base/synchronization/lock.h"
-#endif
-
-#if defined(OS_WIN)
-#include <windows.h>
-#elif defined(OS_POSIX)
-#include <pthread.h>
-#else
-# error No reader-writer lock defined for this platform.
-#endif
-
-namespace base {
-namespace subtle {
-
-// An OS-independent wrapper around reader-writer locks. There's no magic here.
-//
-// You are strongly encouraged to use base::Lock instead of this, unless you
-// can demonstrate contention and show that this would lead to an improvement.
-// This lock does not make any guarantees of fairness, which can lead to writer
-// starvation under certain access patterns. You should carefully consider your
-// writer access patterns before using this lock.
-class BASE_EXPORT ReadWriteLock {
- public:
- ReadWriteLock();
- ~ReadWriteLock();
-
- // Reader lock functions.
- void ReadAcquire();
- void ReadRelease();
-
- // Writer lock functions.
- void WriteAcquire();
- void WriteRelease();
-
- private:
-#if defined(OS_WIN)
- using NativeHandle = SRWLOCK;
-#elif defined(OS_NACL)
- using NativeHandle = Lock;
-#elif defined(OS_POSIX)
- using NativeHandle = pthread_rwlock_t;
-#endif
-
- NativeHandle native_handle_;
-
-#if defined(OS_NACL)
- // Even though NaCl has a pthread_rwlock implementation, the build rules don't
- // make it universally available. So instead, implement a slower and trivial
- // reader-writer lock using a regular mutex.
- // TODO(amistry): Remove this and use the posix implementation when it's
- // available in all build configurations.
- uint32_t readers_ = 0;
- // base::Lock does checking to ensure the lock is acquired and released on the
- // same thread. This is not the case for this lock, so use pthread mutexes
- // directly here.
- pthread_mutex_t writer_lock_ = PTHREAD_MUTEX_INITIALIZER;
-#endif
-
- DISALLOW_COPY_AND_ASSIGN(ReadWriteLock);
-};
-
-class AutoReadLock {
- public:
- explicit AutoReadLock(ReadWriteLock& lock) : lock_(lock) {
- lock_.ReadAcquire();
- }
- ~AutoReadLock() {
- lock_.ReadRelease();
- }
-
- private:
- ReadWriteLock& lock_;
- DISALLOW_COPY_AND_ASSIGN(AutoReadLock);
-};
-
-class AutoWriteLock {
- public:
- explicit AutoWriteLock(ReadWriteLock& lock) : lock_(lock) {
- lock_.WriteAcquire();
- }
- ~AutoWriteLock() {
- lock_.WriteRelease();
- }
-
- private:
- ReadWriteLock& lock_;
- DISALLOW_COPY_AND_ASSIGN(AutoWriteLock);
-};
-
-} // namespace subtle
-} // namespace base
-
-#endif // BASE_SYNCHRONIZATION_READ_WRITE_LOCK_H_
diff --git a/base/synchronization/read_write_lock_posix.cc b/base/synchronization/read_write_lock_posix.cc
deleted file mode 100644
index e5de091f06..0000000000
--- a/base/synchronization/read_write_lock_posix.cc
+++ /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.
-
-#include "base/synchronization/read_write_lock.h"
-
-#include "base/logging.h"
-
-namespace base {
-namespace subtle {
-
-ReadWriteLock::ReadWriteLock() : native_handle_(PTHREAD_RWLOCK_INITIALIZER) {}
-
-ReadWriteLock::~ReadWriteLock() {
- int result = pthread_rwlock_destroy(&native_handle_);
- DCHECK_EQ(result, 0) << ". " << strerror(result);
-}
-
-void ReadWriteLock::ReadAcquire() {
- int result = pthread_rwlock_rdlock(&native_handle_);
- DCHECK_EQ(result, 0) << ". " << strerror(result);
-}
-
-void ReadWriteLock::ReadRelease() {
- int result = pthread_rwlock_unlock(&native_handle_);
- DCHECK_EQ(result, 0) << ". " << strerror(result);
-}
-
-void ReadWriteLock::WriteAcquire() {
- int result = pthread_rwlock_wrlock(&native_handle_);
- DCHECK_EQ(result, 0) << ". " << strerror(result);
-}
-
-void ReadWriteLock::WriteRelease() {
- int result = pthread_rwlock_unlock(&native_handle_);
- DCHECK_EQ(result, 0) << ". " << strerror(result);
-}
-
-} // namespace subtle
-} // namespace base
diff --git a/base/synchronization/synchronization_buildflags.h b/base/synchronization/synchronization_buildflags.h
new file mode 100644
index 0000000000..98941f4784
--- /dev/null
+++ b/base/synchronization/synchronization_buildflags.h
@@ -0,0 +1,4 @@
+#ifndef BASE_SYNCHRONIZATION_BUILDFLAGS_H_
+#define BASE_SYNCHRONIZATION_BUILDFLAGS_H_
+#include "build/buildflag.h"
+#endif // BASE_SYNCHRONIZATION_BUILDFLAGS_H_
diff --git a/base/synchronization/waitable_event.h b/base/synchronization/waitable_event.h
index e8caffeec3..836adc0978 100644
--- a/base/synchronization/waitable_event.h
+++ b/base/synchronization/waitable_event.h
@@ -13,11 +13,20 @@
#if defined(OS_WIN)
#include "base/win/scoped_handle.h"
-#endif
+#elif defined(OS_MACOSX)
+#include <mach/mach.h>
+
+#include <list>
+#include <memory>
-#if defined(OS_POSIX)
+#include "base/callback_forward.h"
+#include "base/mac/scoped_mach_port.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
#include <list>
#include <utility>
+
#include "base/memory/ref_counted.h"
#include "base/synchronization/lock.h"
#endif
@@ -55,7 +64,8 @@ class BASE_EXPORT WaitableEvent {
// Constructs a WaitableEvent with policy and initial state as detailed in
// the above enums.
- WaitableEvent(ResetPolicy reset_policy, InitialState initial_state);
+ WaitableEvent(ResetPolicy reset_policy = ResetPolicy::MANUAL,
+ InitialState initial_state = InitialState::NOT_SIGNALED);
#if defined(OS_WIN)
// Create a WaitableEvent from an Event HANDLE which has already been
@@ -146,7 +156,7 @@ class BASE_EXPORT WaitableEvent {
virtual bool Compare(void* tag) = 0;
protected:
- virtual ~Waiter() {}
+ virtual ~Waiter() = default;
};
private:
@@ -154,10 +164,79 @@ class BASE_EXPORT WaitableEvent {
#if defined(OS_WIN)
win::ScopedHandle handle_;
-#else
- // On Windows, one can close a HANDLE which is currently being waited on. The
- // MSDN documentation says that the resulting behaviour is 'undefined', but
- // it doesn't crash. However, if we were to include the following members
+#elif defined(OS_MACOSX)
+ // Prior to macOS 10.12, a TYPE_MACH_RECV dispatch source may not be invoked
+ // immediately. If a WaitableEventWatcher is used on a manual-reset event,
+ // and another thread that is Wait()ing on the event calls Reset()
+ // immediately after waking up, the watcher may not receive the callback.
+ // On macOS 10.12 and higher, dispatch delivery is reliable. But for OSes
+ // prior, a lock-protected list of callbacks is used for manual-reset event
+ // watchers. Automatic-reset events are not prone to this issue, since the
+ // first thread to wake will claim the event.
+ static bool UseSlowWatchList(ResetPolicy policy);
+
+ // Peeks the message queue named by |port| and returns true if a message
+ // is present and false if not. If |dequeue| is true, the messsage will be
+ // drained from the queue. If |dequeue| is false, the queue will only be
+ // peeked. |port| must be a receive right.
+ static bool PeekPort(mach_port_t port, bool dequeue);
+
+ // The Mach receive right is waited on by both WaitableEvent and
+ // WaitableEventWatcher. It is valid to signal and then delete an event, and
+ // a watcher should still be notified. If the right were to be destroyed
+ // immediately, the watcher would not receive the signal. Because Mach
+ // receive rights cannot have a user refcount greater than one, the right
+ // must be reference-counted manually.
+ class ReceiveRight : public RefCountedThreadSafe<ReceiveRight> {
+ public:
+ ReceiveRight(mach_port_t name, bool create_slow_watch_list);
+
+ mach_port_t Name() const { return right_.get(); };
+
+ // This structure is used iff UseSlowWatchList() is true. See the comment
+ // in Signal() for details.
+ struct WatchList {
+ WatchList();
+ ~WatchList();
+
+ // The lock protects a list of closures to be run when the event is
+ // Signal()ed. The closures are invoked on the signaling thread, so they
+ // must be safe to be called from any thread.
+ Lock lock;
+ std::list<OnceClosure> list;
+ };
+
+ WatchList* SlowWatchList() const { return slow_watch_list_.get(); }
+
+ private:
+ friend class RefCountedThreadSafe<ReceiveRight>;
+ ~ReceiveRight();
+
+ mac::ScopedMachReceiveRight right_;
+
+ // This is allocated iff UseSlowWatchList() is true. It is created on the
+ // heap to avoid performing initialization when not using the slow path.
+ std::unique_ptr<WatchList> slow_watch_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReceiveRight);
+ };
+
+ const ResetPolicy policy_;
+
+ // The receive right for the event.
+ scoped_refptr<ReceiveRight> receive_right_;
+
+ // The send right used to signal the event. This can be disposed of with
+ // the event, unlike the receive right, since a deleted event cannot be
+ // signaled.
+ mac::ScopedMachSendRight send_right_;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ // On Windows, you must not close a HANDLE which is currently being waited on.
+ // The MSDN documentation says that the resulting behaviour is 'undefined'.
+ // To solve that issue each WaitableEventWatcher duplicates the given event
+ // handle.
+
+ // However, if we were to include the following members
// directly then, on POSIX, one couldn't use WaitableEventWatcher to watch an
// event which gets deleted. This mismatch has bitten us several times now,
// so we have a kernel of the WaitableEvent, which is reference counted.
diff --git a/base/synchronization/waitable_event_perftest.cc b/base/synchronization/waitable_event_perftest.cc
new file mode 100644
index 0000000000..1888077a19
--- /dev/null
+++ b/base/synchronization/waitable_event_perftest.cc
@@ -0,0 +1,178 @@
+// 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/synchronization/waitable_event.h"
+
+#include <numeric>
+
+#include "base/threading/simple_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_test.h"
+
+namespace base {
+
+namespace {
+
+class TraceWaitableEvent {
+ public:
+ TraceWaitableEvent(size_t samples)
+ : event_(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED),
+ samples_(samples) {
+ signal_times_.reserve(samples);
+ wait_times_.reserve(samples);
+ }
+
+ ~TraceWaitableEvent() = default;
+
+ void Signal() {
+ TimeTicks start = TimeTicks::Now();
+ event_.Signal();
+ signal_times_.push_back(TimeTicks::Now() - start);
+ }
+
+ void Wait() {
+ TimeTicks start = TimeTicks::Now();
+ event_.Wait();
+ wait_times_.push_back(TimeTicks::Now() - start);
+ }
+
+ bool TimedWaitUntil(const TimeTicks& end_time) {
+ TimeTicks start = TimeTicks::Now();
+ bool signaled = event_.TimedWaitUntil(end_time);
+ wait_times_.push_back(TimeTicks::Now() - start);
+ return signaled;
+ }
+
+ bool IsSignaled() { return event_.IsSignaled(); }
+
+ const std::vector<TimeDelta>& signal_times() const { return signal_times_; }
+ const std::vector<TimeDelta>& wait_times() const { return wait_times_; }
+ size_t samples() const { return samples_; }
+
+ private:
+ WaitableEvent event_;
+
+ std::vector<TimeDelta> signal_times_;
+ std::vector<TimeDelta> wait_times_;
+
+ const size_t samples_;
+
+ DISALLOW_COPY_AND_ASSIGN(TraceWaitableEvent);
+};
+
+class SignalerThread : public SimpleThread {
+ public:
+ SignalerThread(TraceWaitableEvent* waiter, TraceWaitableEvent* signaler)
+ : SimpleThread("WaitableEventPerfTest signaler"),
+ waiter_(waiter),
+ signaler_(signaler) {}
+
+ ~SignalerThread() override = default;
+
+ void Run() override {
+ while (!stop_event_.IsSignaled()) {
+ if (waiter_)
+ waiter_->Wait();
+ if (signaler_)
+ signaler_->Signal();
+ }
+ }
+
+ // Signals the thread to stop on the next iteration of its loop (which
+ // will happen immediately if no |waiter_| is present or is signaled.
+ void RequestStop() { stop_event_.Signal(); }
+
+ private:
+ WaitableEvent stop_event_{WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED};
+ TraceWaitableEvent* waiter_;
+ TraceWaitableEvent* signaler_;
+ DISALLOW_COPY_AND_ASSIGN(SignalerThread);
+};
+
+void PrintPerfWaitableEvent(const TraceWaitableEvent* event,
+ const std::string& modifier,
+ const std::string& trace) {
+ TimeDelta signal_time = std::accumulate(
+ event->signal_times().begin(), event->signal_times().end(), TimeDelta());
+ TimeDelta wait_time = std::accumulate(event->wait_times().begin(),
+ event->wait_times().end(), TimeDelta());
+ perf_test::PrintResult(
+ "signal_time", modifier, trace,
+ static_cast<size_t>(signal_time.InNanoseconds()) / event->samples(),
+ "ns/sample", true);
+ perf_test::PrintResult(
+ "wait_time", modifier, trace,
+ static_cast<size_t>(wait_time.InNanoseconds()) / event->samples(),
+ "ns/sample", true);
+}
+
+} // namespace
+
+TEST(WaitableEventPerfTest, SingleThread) {
+ const size_t kSamples = 1000;
+
+ TraceWaitableEvent event(kSamples);
+
+ for (size_t i = 0; i < kSamples; ++i) {
+ event.Signal();
+ event.Wait();
+ }
+
+ PrintPerfWaitableEvent(&event, "", "singlethread-1000-samples");
+}
+
+TEST(WaitableEventPerfTest, MultipleThreads) {
+ const size_t kSamples = 1000;
+
+ TraceWaitableEvent waiter(kSamples);
+ TraceWaitableEvent signaler(kSamples);
+
+ // The other thread will wait and signal on the respective opposite events.
+ SignalerThread thread(&signaler, &waiter);
+ thread.Start();
+
+ for (size_t i = 0; i < kSamples; ++i) {
+ signaler.Signal();
+ waiter.Wait();
+ }
+
+ // Signal the stop event and then make sure the signaler event it is
+ // waiting on is also signaled.
+ thread.RequestStop();
+ signaler.Signal();
+
+ thread.Join();
+
+ PrintPerfWaitableEvent(&waiter, "_waiter", "multithread-1000-samples");
+ PrintPerfWaitableEvent(&signaler, "_signaler", "multithread-1000-samples");
+}
+
+TEST(WaitableEventPerfTest, Throughput) {
+ // Reserve a lot of sample space.
+ const size_t kCapacity = 500000;
+ TraceWaitableEvent event(kCapacity);
+
+ SignalerThread thread(nullptr, &event);
+ thread.Start();
+
+ TimeTicks end_time = TimeTicks::Now() + TimeDelta::FromSeconds(1);
+ size_t count = 0;
+ while (event.TimedWaitUntil(end_time)) {
+ ++count;
+ }
+
+ thread.RequestStop();
+ thread.Join();
+
+ perf_test::PrintResult("counts", "", "throughput", count, "signals", true);
+ PrintPerfWaitableEvent(&event, "", "throughput");
+
+ // Make sure that allocation didn't happen during the test.
+ EXPECT_LE(event.signal_times().capacity(), kCapacity);
+ EXPECT_LE(event.wait_times().capacity(), kCapacity);
+}
+
+} // namespace base
diff --git a/base/synchronization/waitable_event_posix.cc b/base/synchronization/waitable_event_posix.cc
index 846fa06700..9799e7d031 100644
--- a/base/synchronization/waitable_event_posix.cc
+++ b/base/synchronization/waitable_event_posix.cc
@@ -13,6 +13,7 @@
#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
+#include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_restrictions.h"
// -----------------------------------------------------------------------------
@@ -88,11 +89,7 @@ bool WaitableEvent::IsSignaled() {
class SyncWaiter : public WaitableEvent::Waiter {
public:
SyncWaiter()
- : fired_(false),
- signaling_event_(NULL),
- lock_(),
- cv_(&lock_) {
- }
+ : fired_(false), signaling_event_(nullptr), lock_(), cv_(&lock_) {}
bool Fire(WaitableEvent* signaling_event) override {
base::AutoLock locked(lock_);
@@ -165,7 +162,8 @@ bool WaitableEvent::TimedWait(const TimeDelta& wait_delta) {
}
bool WaitableEvent::TimedWaitUntil(const TimeTicks& end_time) {
- base::ThreadRestrictions::AssertWaitAllowed();
+ internal::AssertBaseSyncPrimitivesAllowed();
+ ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
// Record the event that this thread is blocking upon (for hang diagnosis).
base::debug::ScopedEventWaitActivity event_activity(this);
@@ -239,9 +237,9 @@ cmp_fst_addr(const std::pair<WaitableEvent*, unsigned> &a,
// static
size_t WaitableEvent::WaitMany(WaitableEvent** raw_waitables,
size_t count) {
- base::ThreadRestrictions::AssertWaitAllowed();
+ internal::AssertBaseSyncPrimitivesAllowed();
DCHECK(count) << "Cannot wait on no events";
-
+ ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
// Record an event (the first) that this thread is blocking upon.
base::debug::ScopedEventWaitActivity event_activity(raw_waitables[0]);
diff --git a/base/synchronization/waitable_event_unittest.cc b/base/synchronization/waitable_event_unittest.cc
index 3aa1af1619..e1d2683a99 100644
--- a/base/synchronization/waitable_event_unittest.cc
+++ b/base/synchronization/waitable_event_unittest.cc
@@ -35,6 +35,25 @@ TEST(WaitableEventTest, ManualBasics) {
EXPECT_TRUE(event.TimedWait(TimeDelta::FromMilliseconds(10)));
}
+TEST(WaitableEventTest, ManualInitiallySignaled) {
+ WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::SIGNALED);
+
+ EXPECT_TRUE(event.IsSignaled());
+ EXPECT_TRUE(event.IsSignaled());
+
+ event.Reset();
+
+ EXPECT_FALSE(event.IsSignaled());
+ EXPECT_FALSE(event.IsSignaled());
+
+ event.Signal();
+
+ event.Wait();
+ EXPECT_TRUE(event.IsSignaled());
+ EXPECT_TRUE(event.IsSignaled());
+}
+
TEST(WaitableEventTest, AutoBasics) {
WaitableEvent event(WaitableEvent::ResetPolicy::AUTOMATIC,
WaitableEvent::InitialState::NOT_SIGNALED);
@@ -57,6 +76,19 @@ TEST(WaitableEventTest, AutoBasics) {
EXPECT_TRUE(event.TimedWait(TimeDelta::FromMilliseconds(10)));
}
+TEST(WaitableEventTest, AutoInitiallySignaled) {
+ WaitableEvent event(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::SIGNALED);
+
+ EXPECT_TRUE(event.IsSignaled());
+ EXPECT_FALSE(event.IsSignaled());
+
+ event.Signal();
+
+ EXPECT_TRUE(event.IsSignaled());
+ EXPECT_FALSE(event.IsSignaled());
+}
+
TEST(WaitableEventTest, WaitManyShortcut) {
WaitableEvent* ev[5];
for (unsigned i = 0; i < 5; ++i) {
diff --git a/base/synchronization/waitable_event_watcher.h b/base/synchronization/waitable_event_watcher.h
index 44ef5047ed..51728e3bca 100644
--- a/base/synchronization/waitable_event_watcher.h
+++ b/base/synchronization/waitable_event_watcher.h
@@ -7,16 +7,27 @@
#include "base/base_export.h"
#include "base/macros.h"
-#include "base/sequence_checker.h"
+#include "base/sequenced_task_runner.h"
#include "build/build_config.h"
#if defined(OS_WIN)
#include "base/win/object_watcher.h"
+#include "base/win/scoped_handle.h"
+#elif defined(OS_MACOSX)
+#include <dispatch/dispatch.h>
+
+#include "base/mac/scoped_dispatch_object.h"
+#include "base/memory/weak_ptr.h"
+#include "base/synchronization/waitable_event.h"
#else
-#include "base/callback.h"
+#include "base/sequence_checker.h"
#include "base/synchronization/waitable_event.h"
#endif
+#if !defined(OS_WIN)
+#include "base/callback.h"
+#endif
+
namespace base {
class Flag;
@@ -35,7 +46,7 @@ class WaitableEvent;
// public:
// void DoStuffWhenSignaled(WaitableEvent *waitable_event) {
// watcher_.StartWatching(waitable_event,
-// base::Bind(&MyClass::OnWaitableEventSignaled, this);
+// base::BindOnce(&MyClass::OnWaitableEventSignaled, this);
// }
// private:
// void OnWaitableEventSignaled(WaitableEvent* waitable_event) {
@@ -56,7 +67,8 @@ class WaitableEvent;
// missing a signal.
//
// NOTE: you /are/ allowed to delete the WaitableEvent while still waiting on
-// it with a Watcher. It will act as if the event was never signaled.
+// it with a Watcher. But pay attention: if the event was signaled and deleted
+// right after, the callback may be called with deleted WaitableEvent pointer.
class BASE_EXPORT WaitableEventWatcher
#if defined(OS_WIN)
@@ -64,7 +76,8 @@ class BASE_EXPORT WaitableEventWatcher
#endif
{
public:
- typedef Callback<void(WaitableEvent*)> EventCallback;
+ using EventCallback = OnceCallback<void(WaitableEvent*)>;
+
WaitableEventWatcher();
#if defined(OS_WIN)
@@ -75,7 +88,10 @@ class BASE_EXPORT WaitableEventWatcher
// When |event| is signaled, |callback| is called on the sequence that called
// StartWatching().
- bool StartWatching(WaitableEvent* event, const EventCallback& callback);
+ // |task_runner| is used for asynchronous executions of calling |callback|.
+ bool StartWatching(WaitableEvent* event,
+ EventCallback callback,
+ scoped_refptr<SequencedTaskRunner> task_runner);
// Cancel the current watch. Must be called from the same sequence which
// started the watch.
@@ -90,9 +106,36 @@ class BASE_EXPORT WaitableEventWatcher
#if defined(OS_WIN)
void OnObjectSignaled(HANDLE h) override;
+ // Duplicated handle of the event passed to StartWatching().
+ win::ScopedHandle duplicated_event_handle_;
+
+ // A watcher for |duplicated_event_handle_|. The handle MUST outlive
+ // |watcher_|.
win::ObjectWatcher watcher_;
+
EventCallback callback_;
WaitableEvent* event_ = nullptr;
+#elif defined(OS_MACOSX)
+ // Invokes the callback and resets the source. Must be called on the task
+ // runner on which StartWatching() was called.
+ void InvokeCallback();
+
+ // Closure bound to the event being watched. This will be is_null() if
+ // nothing is being watched.
+ OnceClosure callback_;
+
+ // A reference to the receive right that is kept alive while a watcher
+ // is waiting. Null if no event is being watched.
+ scoped_refptr<WaitableEvent::ReceiveRight> receive_right_;
+
+ // A TYPE_MACH_RECV dispatch source on |receive_right_|. When a receive event
+ // is delivered, the message queue will be peeked and the bound |callback_|
+ // may be run. This will be null if nothing is currently being watched.
+ ScopedDispatchObject<dispatch_source_t> source_;
+
+ // Used to vend a weak pointer for calling InvokeCallback() from the
+ // |source_| event handler.
+ WeakPtrFactory<WaitableEventWatcher> weak_ptr_factory_;
#else
// Instantiated in StartWatching(). Set before the callback runs. Reset in
// StopWatching() or StartWatching().
diff --git a/base/synchronization/waitable_event_watcher_posix.cc b/base/synchronization/waitable_event_watcher_posix.cc
index 3adbc5f977..2b296dafd7 100644
--- a/base/synchronization/waitable_event_watcher_posix.cc
+++ b/base/synchronization/waitable_event_watcher_posix.cc
@@ -46,7 +46,7 @@ class Flag : public RefCountedThreadSafe<Flag> {
private:
friend class RefCountedThreadSafe<Flag>;
- ~Flag() {}
+ ~Flag() = default;
mutable Lock lock_;
bool flag_;
@@ -61,16 +61,16 @@ class Flag : public RefCountedThreadSafe<Flag> {
class AsyncWaiter : public WaitableEvent::Waiter {
public:
AsyncWaiter(scoped_refptr<SequencedTaskRunner> task_runner,
- const base::Closure& callback,
+ base::OnceClosure callback,
Flag* flag)
: task_runner_(std::move(task_runner)),
- callback_(callback),
+ callback_(std::move(callback)),
flag_(flag) {}
bool Fire(WaitableEvent* event) override {
// Post the callback if we haven't been cancelled.
if (!flag_->value())
- task_runner_->PostTask(FROM_HERE, callback_);
+ task_runner_->PostTask(FROM_HERE, std::move(callback_));
// We are removed from the wait-list by the WaitableEvent itself. It only
// remains to delete ourselves.
@@ -86,7 +86,7 @@ class AsyncWaiter : public WaitableEvent::Waiter {
private:
const scoped_refptr<SequencedTaskRunner> task_runner_;
- const base::Closure callback_;
+ base::OnceClosure callback_;
const scoped_refptr<Flag> flag_;
};
@@ -96,13 +96,13 @@ class AsyncWaiter : public WaitableEvent::Waiter {
// of when the event is canceled.
// -----------------------------------------------------------------------------
void AsyncCallbackHelper(Flag* flag,
- const WaitableEventWatcher::EventCallback& callback,
+ WaitableEventWatcher::EventCallback callback,
WaitableEvent* event) {
// Runs on the sequence that called StartWatching().
if (!flag->value()) {
// This is to let the WaitableEventWatcher know that the event has occured.
flag->Set();
- callback.Run(event);
+ std::move(callback).Run(event);
}
}
@@ -124,9 +124,9 @@ WaitableEventWatcher::~WaitableEventWatcher() {
// -----------------------------------------------------------------------------
bool WaitableEventWatcher::StartWatching(
WaitableEvent* event,
- const EventCallback& callback) {
+ EventCallback callback,
+ scoped_refptr<SequencedTaskRunner> task_runner) {
DCHECK(sequence_checker_.CalledOnValidSequence());
- DCHECK(SequencedTaskRunnerHandle::Get());
// A user may call StartWatching from within the callback function. In this
// case, we won't know that we have finished watching, expect that the Flag
@@ -137,8 +137,9 @@ bool WaitableEventWatcher::StartWatching(
DCHECK(!cancel_flag_) << "StartWatching called while still watching";
cancel_flag_ = new Flag;
- const Closure internal_callback = base::Bind(
- &AsyncCallbackHelper, base::RetainedRef(cancel_flag_), callback, event);
+ OnceClosure internal_callback =
+ base::BindOnce(&AsyncCallbackHelper, base::RetainedRef(cancel_flag_),
+ std::move(callback), event);
WaitableEvent::WaitableEventKernel* kernel = event->kernel_.get();
AutoLock locked(kernel->lock_);
@@ -148,14 +149,14 @@ bool WaitableEventWatcher::StartWatching(
kernel->signaled_ = false;
// No hairpinning - we can't call the delegate directly here. We have to
- // post a task to the SequencedTaskRunnerHandle as usual.
- SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE, internal_callback);
+ // post a task to |task_runner| as usual.
+ task_runner->PostTask(FROM_HERE, std::move(internal_callback));
return true;
}
kernel_ = kernel;
- waiter_ = new AsyncWaiter(SequencedTaskRunnerHandle::Get(), internal_callback,
- cancel_flag_.get());
+ waiter_ = new AsyncWaiter(std::move(task_runner),
+ std::move(internal_callback), cancel_flag_.get());
event->Enqueue(waiter_);
return true;
@@ -170,7 +171,7 @@ void WaitableEventWatcher::StopWatching() {
if (cancel_flag_->value()) {
// In this case, the event has fired, but we haven't figured that out yet.
// The WaitableEvent may have been deleted too.
- cancel_flag_ = NULL;
+ cancel_flag_ = nullptr;
return;
}
@@ -184,7 +185,7 @@ void WaitableEventWatcher::StopWatching() {
// delegate getting called. If the task has run then we have the last
// reference to the flag and it will be deleted immedately after.
cancel_flag_->Set();
- cancel_flag_ = NULL;
+ cancel_flag_ = nullptr;
return;
}
@@ -210,7 +211,7 @@ void WaitableEventWatcher::StopWatching() {
// have been enqueued with the MessageLoop because the waiter was never
// signaled)
delete waiter_;
- cancel_flag_ = NULL;
+ cancel_flag_ = nullptr;
return;
}
@@ -219,7 +220,7 @@ void WaitableEventWatcher::StopWatching() {
// task on the SequencedTaskRunner, but to delete it instead. The Waiter
// deletes itself once run.
cancel_flag_->Set();
- cancel_flag_ = NULL;
+ cancel_flag_ = nullptr;
// If the waiter has already run then the task has been enqueued. If the Task
// hasn't yet run, the flag will stop the delegate from getting called. (This
diff --git a/base/sys_info.cc b/base/sys_info.cc
index 5aac9b71cd..379d7f26f9 100644
--- a/base/sys_info.cc
+++ b/base/sys_info.cc
@@ -4,21 +4,56 @@
#include "base/sys_info.h"
+#include <algorithm>
+
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/lazy_instance.h"
-#include "base/metrics/field_trial.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/string_util.h"
#include "base/sys_info_internal.h"
#include "base/time/time.h"
#include "build/build_config.h"
namespace base {
+namespace {
+static const int kLowMemoryDeviceThresholdMB = 512;
+}
-#if !defined(OS_ANDROID)
+// static
+int64_t SysInfo::AmountOfPhysicalMemory() {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableLowEndDeviceMode)) {
+ return kLowMemoryDeviceThresholdMB * 1024 * 1024;
+ }
-static const int kLowMemoryDeviceThresholdMB = 512;
+ return AmountOfPhysicalMemoryImpl();
+}
+
+// static
+int64_t SysInfo::AmountOfAvailablePhysicalMemory() {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableLowEndDeviceMode)) {
+ // Estimate the available memory by subtracting our memory used estimate
+ // from the fake |kLowMemoryDeviceThresholdMB| limit.
+ size_t memory_used =
+ AmountOfPhysicalMemoryImpl() - AmountOfAvailablePhysicalMemoryImpl();
+ size_t memory_limit = kLowMemoryDeviceThresholdMB * 1024 * 1024;
+ // std::min ensures no underflow, as |memory_used| can be > |memory_limit|.
+ return memory_limit - std::min(memory_used, memory_limit);
+ }
+
+ return AmountOfAvailablePhysicalMemoryImpl();
+}
+
+bool SysInfo::IsLowEndDevice() {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableLowEndDeviceMode)) {
+ return true;
+ }
+
+ return IsLowEndDeviceImpl();
+}
+
+#if !defined(OS_ANDROID)
bool DetectLowEndDevice() {
CommandLine* command_line = CommandLine::ForCurrentProcess();
@@ -36,20 +71,12 @@ static LazyInstance<
g_lazy_low_end_device = LAZY_INSTANCE_INITIALIZER;
// static
-bool SysInfo::IsLowEndDevice() {
- const std::string group_name =
- base::FieldTrialList::FindFullName("MemoryReduction");
-
- // Low End Device Mode will be enabled if this client is assigned to
- // one of those EnabledXXX groups.
- if (StartsWith(group_name, "Enabled", CompareCase::SENSITIVE))
- return true;
-
+bool SysInfo::IsLowEndDeviceImpl() {
return g_lazy_low_end_device.Get().value();
}
#endif
-#if (!defined(OS_MACOSX) || defined(OS_IOS)) && !defined(OS_ANDROID)
+#if !defined(OS_MACOSX) && !defined(OS_ANDROID)
std::string SysInfo::HardwareModelName() {
return std::string();
}
diff --git a/base/sys_info.h b/base/sys_info.h
index 18bdaf0096..36bf565172 100644
--- a/base/sys_info.h
+++ b/base/sys_info.h
@@ -69,8 +69,9 @@ class BASE_EXPORT SysInfo {
// Returns a descriptive string for the current machine model or an empty
// string if the machine model is unknown or an error occured.
- // e.g. "MacPro1,1" on Mac, or "Nexus 5" on Android. Only implemented on OS X,
- // Android, and Chrome OS. This returns an empty string on other platforms.
+ // e.g. "MacPro1,1" on Mac, "iPhone9,3" on iOS or "Nexus 5" on Android. Only
+ // implemented on OS X, iOS, Android, and Chrome OS. This returns an empty
+ // string on other platforms.
static std::string HardwareModelName();
// Returns the name of the host operating system.
@@ -115,19 +116,21 @@ class BASE_EXPORT SysInfo {
static bool GetLsbReleaseValue(const std::string& key, std::string* value);
// Convenience function for GetLsbReleaseValue("CHROMEOS_RELEASE_BOARD",...).
- // Returns "unknown" if CHROMEOS_RELEASE_BOARD is not set. Otherwise returns
- // the full name of the board. WARNING: the returned value often differs in
- // developer built system compared to devices that use the official version.
- // E.g. for developer built version, the function could return 'glimmer' while
- // for officially used versions it would be like 'glimmer-signed-mp-v4keys'.
- // Use GetStrippedReleaseBoard() function if you need only the short name of
- // the board (would be 'glimmer' in the case described above).
+ // Returns "unknown" if CHROMEOS_RELEASE_BOARD is not set. Otherwise, returns
+ // the full name of the board. Note that the returned value often differs
+ // between developers' systems and devices that use official builds. E.g. for
+ // a developer-built image, the function could return 'glimmer', while in an
+ // official build, it may be something like 'glimmer-signed-mp-v4keys'.
+ //
+ // NOTE: Strings returned by this function should be treated as opaque values
+ // within Chrome (e.g. for reporting metrics elsewhere). If you need to make
+ // Chrome behave differently for different Chrome OS devices, either directly
+ // check for the hardware feature that you care about (preferred) or add a
+ // command-line flag to Chrome and pass it from session_manager (based on
+ // whether a USE flag is set or not). See https://goo.gl/BbBkzg for more
+ // details.
static std::string GetLsbReleaseBoard();
- // Convenience function for GetLsbReleaseBoard() removing trailing "-signed-*"
- // if present. Returns "unknown" if CHROMEOS_RELEASE_BOARD is not set.
- static std::string GetStrippedReleaseBoard();
-
// Returns the creation time of /etc/lsb-release. (Used to get the date and
// time of the Chrome OS build).
static Time GetLsbReleaseTime();
@@ -152,15 +155,20 @@ class BASE_EXPORT SysInfo {
#endif // defined(OS_ANDROID)
// Returns true if this is a low-end device.
- // Low-end device refers to devices having less than 512M memory in the
- // current implementation.
+ // Low-end device refers to devices having a very low amount of total
+ // system memory, typically <= 1GB.
+ // See also SysUtils.java, method isLowEndDevice.
static bool IsLowEndDevice();
private:
FRIEND_TEST_ALL_PREFIXES(SysInfoTest, AmountOfAvailablePhysicalMemory);
FRIEND_TEST_ALL_PREFIXES(debug::SystemMetricsTest, ParseMeminfo);
-#if defined(OS_LINUX) || defined(OS_ANDROID)
+ static int64_t AmountOfPhysicalMemoryImpl();
+ static int64_t AmountOfAvailablePhysicalMemoryImpl();
+ static bool IsLowEndDeviceImpl();
+
+#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_AIX)
static int64_t AmountOfAvailablePhysicalMemory(
const SystemMemoryInfoKB& meminfo);
#endif
diff --git a/base/sys_info_chromeos.cc b/base/sys_info_chromeos.cc
index 29f83845dc..53bfbd64d9 100644
--- a/base/sys_info_chromeos.cc
+++ b/base/sys_info_chromeos.cc
@@ -131,16 +131,13 @@ class ChromeOSVersionInfo {
}
StringTokenizer tokenizer(version, ".");
if (tokenizer.GetNext()) {
- StringToInt(StringPiece(tokenizer.token_begin(), tokenizer.token_end()),
- &major_version_);
+ StringToInt(tokenizer.token_piece(), &major_version_);
}
if (tokenizer.GetNext()) {
- StringToInt(StringPiece(tokenizer.token_begin(), tokenizer.token_end()),
- &minor_version_);
+ StringToInt(tokenizer.token_piece(), &minor_version_);
}
if (tokenizer.GetNext()) {
- StringToInt(StringPiece(tokenizer.token_begin(), tokenizer.token_end()),
- &bugfix_version_);
+ StringToInt(tokenizer.token_piece(), &bugfix_version_);
}
// Check release name for Chrome OS.
@@ -200,16 +197,6 @@ std::string SysInfo::GetLsbReleaseBoard() {
}
// static
-std::string SysInfo::GetStrippedReleaseBoard() {
- std::string board = GetLsbReleaseBoard();
- const size_t index = board.find("-signed-");
- if (index != std::string::npos)
- board.resize(index);
-
- return base::ToLowerASCII(board);
-}
-
-// static
Time SysInfo::GetLsbReleaseTime() {
return GetChromeOSVersionInfo().lsb_release_time();
}
@@ -224,8 +211,7 @@ void SysInfo::SetChromeOSVersionInfoForTest(const std::string& lsb_release,
const Time& lsb_release_time) {
std::unique_ptr<Environment> env(Environment::Create());
env->SetVar(kLsbReleaseKey, lsb_release);
- env->SetVar(kLsbReleaseTimeKey,
- DoubleToString(lsb_release_time.ToDoubleT()));
+ env->SetVar(kLsbReleaseTimeKey, NumberToString(lsb_release_time.ToDoubleT()));
g_chrome_os_version_info.Get().Parse();
}
diff --git a/base/sys_info_internal.h b/base/sys_info_internal.h
index a1792191f5..2168e9fc1c 100644
--- a/base/sys_info_internal.h
+++ b/base/sys_info_internal.h
@@ -17,7 +17,7 @@ class LazySysInfoValue {
LazySysInfoValue()
: value_(F()) { }
- ~LazySysInfoValue() { }
+ ~LazySysInfoValue() = default;
T value() { return value_; }
diff --git a/base/sys_info_linux.cc b/base/sys_info_linux.cc
index 0cd05b363a..b1fecff4bd 100644
--- a/base/sys_info_linux.cc
+++ b/base/sys_info_linux.cc
@@ -43,12 +43,12 @@ base::LazyInstance<
namespace base {
// static
-int64_t SysInfo::AmountOfPhysicalMemory() {
+int64_t SysInfo::AmountOfPhysicalMemoryImpl() {
return g_lazy_physical_memory.Get().value();
}
// static
-int64_t SysInfo::AmountOfAvailablePhysicalMemory() {
+int64_t SysInfo::AmountOfAvailablePhysicalMemoryImpl() {
SystemMemoryInfoKB info;
if (!GetSystemMemoryInfo(&info))
return 0;
diff --git a/base/sys_info_posix.cc b/base/sys_info_posix.cc
index 36119abf00..70d4fbab07 100644
--- a/base/sys_info_posix.cc
+++ b/base/sys_info_posix.cc
@@ -9,7 +9,6 @@
#include <stdint.h>
#include <string.h>
#include <sys/param.h>
-#include <sys/resource.h>
#include <sys/utsname.h>
#include <unistd.h>
@@ -21,6 +20,10 @@
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
+#if !defined(OS_FUCHSIA)
+#include <sys/resource.h>
+#endif
+
#if defined(OS_ANDROID)
#include <sys/vfs.h>
#define statvfs statfs // Android uses a statvfs-like statfs struct and call.
@@ -35,7 +38,7 @@
namespace {
-#if !defined(OS_OPENBSD)
+#if !defined(OS_OPENBSD) && !defined(OS_FUCHSIA)
int NumberOfProcessors() {
// sysconf returns the number of "logical" (not "physical") processors on both
// Mac and Linux. So we get the number of max available "logical" processors.
@@ -62,8 +65,9 @@ int NumberOfProcessors() {
base::LazyInstance<
base::internal::LazySysInfoValue<int, NumberOfProcessors> >::Leaky
g_lazy_number_of_processors = LAZY_INSTANCE_INITIALIZER;
-#endif
+#endif // !defined(OS_OPENBSD) && !defined(OS_FUCHSIA)
+#if !defined(OS_FUCHSIA)
int64_t AmountOfVirtualMemory() {
struct rlimit limit;
int result = getrlimit(RLIMIT_DATA, &limit);
@@ -77,6 +81,7 @@ int64_t AmountOfVirtualMemory() {
base::LazyInstance<
base::internal::LazySysInfoValue<int64_t, AmountOfVirtualMemory>>::Leaky
g_lazy_virtual_memory = LAZY_INSTANCE_INITIALIZER;
+#endif // !defined(OS_FUCHSIA)
#if defined(OS_LINUX)
bool IsStatsZeroIfUnlimited(const base::FilePath& path) {
@@ -131,20 +136,22 @@ bool GetDiskSpaceInfo(const base::FilePath& path,
namespace base {
-#if !defined(OS_OPENBSD)
+#if !defined(OS_OPENBSD) && !defined(OS_FUCHSIA)
int SysInfo::NumberOfProcessors() {
return g_lazy_number_of_processors.Get().value();
}
#endif
+#if !defined(OS_FUCHSIA)
// static
int64_t SysInfo::AmountOfVirtualMemory() {
return g_lazy_virtual_memory.Get().value();
}
+#endif
// static
int64_t SysInfo::AmountOfFreeDiskSpace(const FilePath& path) {
- base::ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
int64_t available;
if (!GetDiskSpaceInfo(path, &available, nullptr))
@@ -154,7 +161,7 @@ int64_t SysInfo::AmountOfFreeDiskSpace(const FilePath& path) {
// static
int64_t SysInfo::AmountOfTotalDiskSpace(const FilePath& path) {
- base::ThreadRestrictions::AssertIOAllowed();
+ AssertBlockingAllowed();
int64_t total;
if (!GetDiskSpaceInfo(path, nullptr, &total))
@@ -222,6 +229,8 @@ std::string SysInfo::OperatingSystemArchitecture() {
arch = "x86";
} else if (arch == "amd64") {
arch = "x86_64";
+ } else if (std::string(info.sysname) == "AIX") {
+ arch = "ppc64";
}
return arch;
}
diff --git a/base/sys_info_unittest.cc b/base/sys_info_unittest.cc
index 94b5a84971..4bd5558da0 100644
--- a/base/sys_info_unittest.cc
+++ b/base/sys_info_unittest.cc
@@ -7,6 +7,8 @@
#include "base/environment.h"
#include "base/files/file_util.h"
#include "base/process/process_metrics.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
#include "base/sys_info.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
@@ -32,7 +34,13 @@ TEST_F(SysInfoTest, AmountOfMem) {
}
#if defined(OS_LINUX) || defined(OS_ANDROID)
-TEST_F(SysInfoTest, AmountOfAvailablePhysicalMemory) {
+#if defined(OS_LINUX)
+#define MAYBE_AmountOfAvailablePhysicalMemory \
+ DISABLED_AmountOfAvailablePhysicalMemory
+#else
+#define MAYBE_AmountOfAvailablePhysicalMemory AmountOfAvailablePhysicalMemory
+#endif // defined(OS_LINUX)
+TEST_F(SysInfoTest, MAYBE_AmountOfAvailablePhysicalMemory) {
// Note: info is in _K_bytes.
SystemMemoryInfoKB info;
ASSERT_TRUE(GetSystemMemoryInfo(&info));
@@ -57,14 +65,28 @@ TEST_F(SysInfoTest, AmountOfAvailablePhysicalMemory) {
}
#endif // defined(OS_LINUX) || defined(OS_ANDROID)
-TEST_F(SysInfoTest, AmountOfFreeDiskSpace) {
+#if defined(OS_FUCHSIA)
+// TODO(crbug.com/851734): Implementation depends on statvfs, which is not
+// implemented on Fuchsia
+#define MAYBE_AmountOfFreeDiskSpace DISABLED_AmountOfFreeDiskSpace
+#else
+#define MAYBE_AmountOfFreeDiskSpace AmountOfFreeDiskSpace
+#endif
+TEST_F(SysInfoTest, MAYBE_AmountOfFreeDiskSpace) {
// We aren't actually testing that it's correct, just that it's sane.
FilePath tmp_path;
ASSERT_TRUE(GetTempDir(&tmp_path));
EXPECT_GE(SysInfo::AmountOfFreeDiskSpace(tmp_path), 0) << tmp_path.value();
}
-TEST_F(SysInfoTest, AmountOfTotalDiskSpace) {
+#if defined(OS_FUCHSIA)
+// TODO(crbug.com/851734): Implementation depends on statvfs, which is not
+// implemented on Fuchsia
+#define MAYBE_AmountOfTotalDiskSpace DISABLED_AmountOfTotalDiskSpace
+#else
+#define MAYBE_AmountOfTotalDiskSpace AmountOfTotalDiskSpace
+#endif
+TEST_F(SysInfoTest, MAYBE_AmountOfTotalDiskSpace) {
// We aren't actually testing that it's correct, just that it's sane.
FilePath tmp_path;
ASSERT_TRUE(GetTempDir(&tmp_path));
@@ -95,10 +117,17 @@ TEST_F(SysInfoTest, Uptime) {
EXPECT_GT(up_time_2.InMicroseconds(), up_time_1.InMicroseconds());
}
-#if defined(OS_MACOSX) && !defined(OS_IOS)
-TEST_F(SysInfoTest, HardwareModelName) {
+#if defined(OS_MACOSX)
+TEST_F(SysInfoTest, HardwareModelNameFormatMacAndiOS) {
std::string hardware_model = SysInfo::HardwareModelName();
- EXPECT_FALSE(hardware_model.empty());
+ ASSERT_FALSE(hardware_model.empty());
+ // Check that the model is of the expected format "Foo,Bar" where "Bar" is
+ // a number.
+ std::vector<StringPiece> pieces =
+ SplitStringPiece(hardware_model, ",", KEEP_WHITESPACE, SPLIT_WANT_ALL);
+ ASSERT_EQ(2u, pieces.size()) << hardware_model;
+ int value;
+ EXPECT_TRUE(StringToInt(pieces[1], &value)) << hardware_model;
}
#endif
@@ -182,16 +211,6 @@ TEST_F(SysInfoTest, IsRunningOnChromeOS) {
EXPECT_TRUE(SysInfo::IsRunningOnChromeOS());
}
-TEST_F(SysInfoTest, GetStrippedReleaseBoard) {
- const char* kLsbRelease1 = "CHROMEOS_RELEASE_BOARD=Glimmer\n";
- SysInfo::SetChromeOSVersionInfoForTest(kLsbRelease1, Time());
- EXPECT_EQ("glimmer", SysInfo::GetStrippedReleaseBoard());
-
- const char* kLsbRelease2 = "CHROMEOS_RELEASE_BOARD=glimmer-signed-mp-v4keys";
- SysInfo::SetChromeOSVersionInfoForTest(kLsbRelease2, Time());
- EXPECT_EQ("glimmer", SysInfo::GetStrippedReleaseBoard());
-}
-
#endif // OS_CHROMEOS
} // namespace base
diff --git a/base/task/README.md b/base/task/README.md
new file mode 100644
index 0000000000..0db116a141
--- /dev/null
+++ b/base/task/README.md
@@ -0,0 +1,12 @@
+This directory has the following layout (WIP):
+- base/task/: public APIs for posting tasks and managing task queues.
+- base/task/task_scheduler/: implementation of the TaskScheduler.
+- base/task/sequence_manager/: implementation of the SequenceManager.
+
+Apart from embedders explicitly managing a TaskScheduler and/or SequenceManager
+instance(s) for their process/threads, the vast majority of users should only
+need APIs in base/task/.
+
+Documentation:
+- [Threading and tasks](https://chromium.googlesource.com/chromium/src/+/lkcr/docs/threading_and_tasks.md)
+- [Callbacks](https://chromium.googlesource.com/chromium/src/+/lkcr/docs/callback.md)
diff --git a/base/task/cancelable_task_tracker.cc b/base/task/cancelable_task_tracker.cc
index 2a68a57bc6..f304da895b 100644
--- a/base/task/cancelable_task_tracker.cc
+++ b/base/task/cancelable_task_tracker.cc
@@ -8,6 +8,8 @@
#include <utility>
+#include "base/bind.h"
+#include "base/bind_helpers.h"
#include "base/callback_helpers.h"
#include "base/location.h"
#include "base/memory/ref_counted.h"
@@ -19,33 +21,33 @@ namespace base {
namespace {
-void RunIfNotCanceled(const CancellationFlag* flag, const Closure& task) {
+void RunIfNotCanceled(const CancellationFlag* flag, OnceClosure task) {
if (!flag->IsSet())
- task.Run();
+ std::move(task).Run();
}
void RunIfNotCanceledThenUntrack(const CancellationFlag* flag,
- const Closure& task,
- const Closure& untrack) {
- RunIfNotCanceled(flag, task);
- untrack.Run();
+ OnceClosure task,
+ OnceClosure untrack) {
+ RunIfNotCanceled(flag, std::move(task));
+ std::move(untrack).Run();
}
bool IsCanceled(const CancellationFlag* flag,
- base::ScopedClosureRunner* cleanup_runner) {
+ ScopedClosureRunner* cleanup_runner) {
return flag->IsSet();
}
-void RunAndDeleteFlag(const Closure& closure, const CancellationFlag* flag) {
- closure.Run();
+void RunAndDeleteFlag(OnceClosure closure, const CancellationFlag* flag) {
+ std::move(closure).Run();
delete flag;
}
-void RunOrPostToTaskRunner(TaskRunner* task_runner, const Closure& closure) {
- if (task_runner->RunsTasksOnCurrentThread())
- closure.Run();
+void RunOrPostToTaskRunner(TaskRunner* task_runner, OnceClosure closure) {
+ if (task_runner->RunsTasksInCurrentSequence())
+ std::move(closure).Run();
else
- task_runner->PostTask(FROM_HERE, closure);
+ task_runner->PostTask(FROM_HERE, std::move(closure));
}
} // namespace
@@ -64,22 +66,22 @@ CancelableTaskTracker::~CancelableTaskTracker() {
CancelableTaskTracker::TaskId CancelableTaskTracker::PostTask(
TaskRunner* task_runner,
- const tracked_objects::Location& from_here,
- const Closure& task) {
+ const Location& from_here,
+ OnceClosure task) {
DCHECK(sequence_checker_.CalledOnValidSequence());
- return PostTaskAndReply(task_runner, from_here, task, Bind(&base::DoNothing));
+ return PostTaskAndReply(task_runner, from_here, std::move(task), DoNothing());
}
CancelableTaskTracker::TaskId CancelableTaskTracker::PostTaskAndReply(
TaskRunner* task_runner,
- const tracked_objects::Location& from_here,
- Closure task,
- Closure reply) {
+ const Location& from_here,
+ OnceClosure task,
+ OnceClosure reply) {
DCHECK(sequence_checker_.CalledOnValidSequence());
// We need a SequencedTaskRunnerHandle to run |reply|.
- DCHECK(base::SequencedTaskRunnerHandle::IsSet());
+ DCHECK(SequencedTaskRunnerHandle::IsSet());
// Owned by reply callback below.
CancellationFlag* flag = new CancellationFlag();
@@ -87,12 +89,12 @@ CancelableTaskTracker::TaskId CancelableTaskTracker::PostTaskAndReply(
TaskId id = next_id_;
next_id_++; // int64_t is big enough that we ignore the potential overflow.
- Closure untrack_closure =
- Bind(&CancelableTaskTracker::Untrack, weak_factory_.GetWeakPtr(), id);
+ OnceClosure untrack_closure =
+ BindOnce(&CancelableTaskTracker::Untrack, weak_factory_.GetWeakPtr(), id);
bool success = task_runner->PostTaskAndReply(
- from_here, Bind(&RunIfNotCanceled, flag, std::move(task)),
- Bind(&RunIfNotCanceledThenUntrack, base::Owned(flag), std::move(reply),
- std::move(untrack_closure)));
+ from_here, BindOnce(&RunIfNotCanceled, flag, std::move(task)),
+ BindOnce(&RunIfNotCanceledThenUntrack, Owned(flag), std::move(reply),
+ std::move(untrack_closure)));
if (!success)
return kBadTaskId;
@@ -104,7 +106,7 @@ CancelableTaskTracker::TaskId CancelableTaskTracker::PostTaskAndReply(
CancelableTaskTracker::TaskId CancelableTaskTracker::NewTrackedTaskId(
IsCanceledCallback* is_canceled_cb) {
DCHECK(sequence_checker_.CalledOnValidSequence());
- DCHECK(base::SequencedTaskRunnerHandle::IsSet());
+ DCHECK(SequencedTaskRunnerHandle::IsSet());
TaskId id = next_id_;
next_id_++; // int64_t is big enough that we ignore the potential overflow.
@@ -112,20 +114,19 @@ CancelableTaskTracker::TaskId CancelableTaskTracker::NewTrackedTaskId(
// Will be deleted by |untrack_and_delete_flag| after Untrack().
CancellationFlag* flag = new CancellationFlag();
- Closure untrack_and_delete_flag = Bind(
+ OnceClosure untrack_and_delete_flag = BindOnce(
&RunAndDeleteFlag,
- Bind(&CancelableTaskTracker::Untrack, weak_factory_.GetWeakPtr(), id),
+ BindOnce(&CancelableTaskTracker::Untrack, weak_factory_.GetWeakPtr(), id),
flag);
// Will always run |untrack_and_delete_flag| on current sequence.
- base::ScopedClosureRunner* untrack_and_delete_flag_runner =
- new base::ScopedClosureRunner(
- Bind(&RunOrPostToTaskRunner,
- RetainedRef(base::SequencedTaskRunnerHandle::Get()),
- untrack_and_delete_flag));
+ ScopedClosureRunner* untrack_and_delete_flag_runner =
+ new ScopedClosureRunner(BindOnce(
+ &RunOrPostToTaskRunner, RetainedRef(SequencedTaskRunnerHandle::Get()),
+ std::move(untrack_and_delete_flag)));
*is_canceled_cb =
- Bind(&IsCanceled, flag, base::Owned(untrack_and_delete_flag_runner));
+ Bind(&IsCanceled, flag, Owned(untrack_and_delete_flag_runner));
Track(id, flag);
return id;
@@ -134,7 +135,7 @@ CancelableTaskTracker::TaskId CancelableTaskTracker::NewTrackedTaskId(
void CancelableTaskTracker::TryCancel(TaskId id) {
DCHECK(sequence_checker_.CalledOnValidSequence());
- hash_map<TaskId, CancellationFlag*>::const_iterator it = task_flags_.find(id);
+ const auto it = task_flags_.find(id);
if (it == task_flags_.end()) {
// Two possibilities:
//
@@ -149,13 +150,10 @@ void CancelableTaskTracker::TryCancel(TaskId id) {
void CancelableTaskTracker::TryCancelAll() {
DCHECK(sequence_checker_.CalledOnValidSequence());
-
- for (hash_map<TaskId, CancellationFlag*>::const_iterator it =
- task_flags_.begin();
- it != task_flags_.end();
- ++it) {
- it->second->Set();
- }
+ for (const auto& it : task_flags_)
+ it.second->Set();
+ weak_factory_.InvalidateWeakPtrs();
+ task_flags_.clear();
}
bool CancelableTaskTracker::HasTrackedTasks() const {
@@ -165,7 +163,6 @@ bool CancelableTaskTracker::HasTrackedTasks() const {
void CancelableTaskTracker::Track(TaskId id, CancellationFlag* flag) {
DCHECK(sequence_checker_.CalledOnValidSequence());
-
bool success = task_flags_.insert(std::make_pair(id, flag)).second;
DCHECK(success);
}
diff --git a/base/task/cancelable_task_tracker.h b/base/task/cancelable_task_tracker.h
index 4f64a24060..e5e6b5e90d 100644
--- a/base/task/cancelable_task_tracker.h
+++ b/base/task/cancelable_task_tracker.h
@@ -29,7 +29,8 @@
// 2. It's safe to destroy a CancelableTaskTracker while there are outstanding
// tasks. This is commonly used to cancel all outstanding tasks.
//
-// 3. Both task and reply are deleted on the originating sequence.
+// 3. The task is deleted on the target sequence, and the reply are deleted on
+// the originating sequence.
//
// 4. IsCanceledCallback can be run or deleted on any sequence.
#ifndef BASE_TASK_CANCELABLE_TASK_TRACKER_H_
@@ -42,19 +43,16 @@
#include "base/base_export.h"
#include "base/bind.h"
#include "base/callback.h"
-#include "base/containers/hash_tables.h"
+#include "base/containers/small_map.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/post_task_and_reply_with_result_internal.h"
#include "base/sequence_checker.h"
-namespace tracked_objects {
-class Location;
-} // namespace tracked_objects
-
namespace base {
class CancellationFlag;
+class Location;
class TaskRunner;
class BASE_EXPORT CancelableTaskTracker {
@@ -63,34 +61,50 @@ class BASE_EXPORT CancelableTaskTracker {
typedef int64_t TaskId;
static const TaskId kBadTaskId;
- typedef base::Callback<bool()> IsCanceledCallback;
+ typedef Callback<bool()> IsCanceledCallback;
CancelableTaskTracker();
// Cancels all tracked tasks.
~CancelableTaskTracker();
- TaskId PostTask(base::TaskRunner* task_runner,
- const tracked_objects::Location& from_here,
- const base::Closure& task);
+ TaskId PostTask(TaskRunner* task_runner,
+ const Location& from_here,
+ OnceClosure task);
- TaskId PostTaskAndReply(base::TaskRunner* task_runner,
- const tracked_objects::Location& from_here,
- base::Closure task,
- base::Closure reply);
+ TaskId PostTaskAndReply(TaskRunner* task_runner,
+ const Location& from_here,
+ OnceClosure task,
+ OnceClosure reply);
template <typename TaskReturnType, typename ReplyArgType>
- TaskId PostTaskAndReplyWithResult(base::TaskRunner* task_runner,
- const tracked_objects::Location& from_here,
- base::Callback<TaskReturnType()> task,
- base::Callback<void(ReplyArgType)> reply) {
+ TaskId PostTaskAndReplyWithResult(TaskRunner* task_runner,
+ const Location& from_here,
+ OnceCallback<TaskReturnType()> task,
+ OnceCallback<void(ReplyArgType)> reply) {
TaskReturnType* result = new TaskReturnType();
return PostTaskAndReply(
task_runner, from_here,
- base::Bind(&base::internal::ReturnAsParamAdapter<TaskReturnType>,
- std::move(task), base::Unretained(result)),
- base::Bind(&base::internal::ReplyAdapter<TaskReturnType, ReplyArgType>,
- std::move(reply), base::Owned(result)));
+ BindOnce(&internal::ReturnAsParamAdapter<TaskReturnType>,
+ std::move(task), Unretained(result)),
+ BindOnce(&internal::ReplyAdapter<TaskReturnType, ReplyArgType>,
+ std::move(reply), Owned(result)));
+ }
+
+ // Callback version of PostTaskWithTraitsAndReplyWithResult above.
+ // Though RepeatingCallback is convertible to OnceCallback, we need this since
+ // we can not use template deduction and object conversion at once on the
+ // overload resolution.
+ // TODO(tzik): Update all callers of the Callback version to use OnceCallback.
+ template <typename TaskReturnType, typename ReplyArgType>
+ TaskId PostTaskAndReplyWithResult(TaskRunner* task_runner,
+ const Location& from_here,
+ Callback<TaskReturnType()> task,
+ Callback<void(ReplyArgType)> reply) {
+ return PostTaskAndReplyWithResult(
+ task_runner, from_here,
+ static_cast<OnceCallback<TaskReturnType()>>(std::move(task)),
+ static_cast<OnceCallback<void(ReplyArgType)>>(std::move(reply)));
}
// Creates a tracked TaskId and an associated IsCanceledCallback. Client can
@@ -122,15 +136,19 @@ class BASE_EXPORT CancelableTaskTracker {
bool HasTrackedTasks() const;
private:
- void Track(TaskId id, base::CancellationFlag* flag);
+ void Track(TaskId id, CancellationFlag* flag);
void Untrack(TaskId id);
- base::hash_map<TaskId, base::CancellationFlag*> task_flags_;
+ // Typically the number of tasks are 0-2 and occationally 3-4. But since
+ // this is a general API that could be used in unexpected ways, use a
+ // small_map instead of a flat_map to avoid falling over if there are many
+ // tasks.
+ small_map<std::map<TaskId, CancellationFlag*>, 4> task_flags_;
TaskId next_id_;
SequenceChecker sequence_checker_;
- base::WeakPtrFactory<CancelableTaskTracker> weak_factory_;
+ WeakPtrFactory<CancelableTaskTracker> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(CancelableTaskTracker);
};
diff --git a/base/task/cancelable_task_tracker_unittest.cc b/base/task/cancelable_task_tracker_unittest.cc
index fd480f3687..c75adc4bc4 100644
--- a/base/task/cancelable_task_tracker_unittest.cc
+++ b/base/task/cancelable_task_tracker_unittest.cc
@@ -5,7 +5,6 @@
#include "base/task/cancelable_task_tracker.h"
#include <cstddef>
-#include <deque>
#include "base/bind.h"
#include "base/bind_helpers.h"
@@ -13,6 +12,7 @@
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/test/gtest_util.h"
@@ -40,12 +40,12 @@ class CancelableTaskTrackerTest : public testing::Test {
MessageLoop message_loop_;
};
-void AddFailureAt(const tracked_objects::Location& location) {
+void AddFailureAt(const Location& location) {
ADD_FAILURE_AT(location.file_name(), location.line_number());
}
// Returns a closure that fails if run.
-Closure MakeExpectedNotRunClosure(const tracked_objects::Location& location) {
+Closure MakeExpectedNotRunClosure(const Location& location) {
return Bind(&AddFailureAt, location);
}
@@ -55,7 +55,7 @@ Closure MakeExpectedNotRunClosure(const tracked_objects::Location& location) {
// before destruction.
class RunChecker {
public:
- explicit RunChecker(const tracked_objects::Location& location)
+ explicit RunChecker(const Location& location)
: location_(location), called_(false) {}
~RunChecker() {
@@ -67,12 +67,12 @@ class RunChecker {
void Run() { called_ = true; }
private:
- tracked_objects::Location location_;
+ Location location_;
bool called_;
};
// Returns a closure that fails on destruction if it hasn't been run.
-Closure MakeExpectedRunClosure(const tracked_objects::Location& location) {
+Closure MakeExpectedRunClosure(const Location& location) {
return Bind(&RunChecker::Run, Owned(new RunChecker(location)));
}
@@ -166,7 +166,7 @@ TEST_F(CancelableTaskTrackerTest, CancelReplyDifferentThread) {
ASSERT_TRUE(worker_thread.Start());
CancelableTaskTracker::TaskId task_id = task_tracker_.PostTaskAndReply(
- worker_thread.task_runner().get(), FROM_HERE, Bind(&DoNothing),
+ worker_thread.task_runner().get(), FROM_HERE, DoNothing(),
MakeExpectedNotRunClosure(FROM_HERE));
EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
@@ -194,14 +194,14 @@ TEST_F(CancelableTaskTrackerTest, NewTrackedTaskIdDifferentThread) {
Thread other_thread("other thread");
ASSERT_TRUE(other_thread.Start());
other_thread.task_runner()->PostTask(
- FROM_HERE, Bind(&ExpectIsCanceled, is_canceled, false));
+ FROM_HERE, BindOnce(&ExpectIsCanceled, is_canceled, false));
other_thread.Stop();
task_tracker_.TryCancel(task_id);
ASSERT_TRUE(other_thread.Start());
other_thread.task_runner()->PostTask(
- FROM_HERE, Bind(&ExpectIsCanceled, is_canceled, true));
+ FROM_HERE, BindOnce(&ExpectIsCanceled, is_canceled, true));
other_thread.Stop();
}
@@ -268,9 +268,8 @@ TEST_F(CancelableTaskTrackerTest, DestructionCancelsAll) {
EXPECT_FALSE(is_canceled.Run());
}
-// Post a task and cancel it. HasTrackedTasks() should return true
-// from when the task is posted until the (do-nothing) reply task is
-// flushed.
+// Post a task and cancel it. HasTrackedTasks() should return false as soon as
+// TryCancelAll() is called.
TEST_F(CancelableTaskTrackerTest, HasTrackedTasksPost) {
scoped_refptr<TestSimpleTaskRunner> test_task_runner(
new TestSimpleTaskRunner());
@@ -282,17 +281,14 @@ TEST_F(CancelableTaskTrackerTest, HasTrackedTasksPost) {
task_tracker_.TryCancelAll();
- test_task_runner->RunUntilIdle();
-
- EXPECT_TRUE(task_tracker_.HasTrackedTasks());
+ EXPECT_FALSE(task_tracker_.HasTrackedTasks());
+ test_task_runner->RunUntilIdle();
RunCurrentLoopUntilIdle();
-
- EXPECT_FALSE(task_tracker_.HasTrackedTasks());
}
-// Post a task with a reply and cancel it. HasTrackedTasks() should
-// return true from when the task is posted until it is canceled.
+// Post a task with a reply and cancel it. HasTrackedTasks() should return false
+// as soon as TryCancelAll() is called.
TEST_F(CancelableTaskTrackerTest, HasTrackedTasksPostWithReply) {
scoped_refptr<TestSimpleTaskRunner> test_task_runner(
new TestSimpleTaskRunner());
@@ -307,17 +303,14 @@ TEST_F(CancelableTaskTrackerTest, HasTrackedTasksPostWithReply) {
task_tracker_.TryCancelAll();
- test_task_runner->RunUntilIdle();
-
- EXPECT_TRUE(task_tracker_.HasTrackedTasks());
+ EXPECT_FALSE(task_tracker_.HasTrackedTasks());
+ test_task_runner->RunUntilIdle();
RunCurrentLoopUntilIdle();
-
- EXPECT_FALSE(task_tracker_.HasTrackedTasks());
}
-// Create a new tracked task ID. HasTrackedTasks() should return true
-// until the IsCanceledCallback is destroyed.
+// Create a new tracked task ID. HasTrackedTasks() should return false as soon
+// as TryCancelAll() is called.
TEST_F(CancelableTaskTrackerTest, HasTrackedTasksIsCancelled) {
EXPECT_FALSE(task_tracker_.HasTrackedTasks());
@@ -326,10 +319,6 @@ TEST_F(CancelableTaskTrackerTest, HasTrackedTasksIsCancelled) {
task_tracker_.TryCancelAll();
- EXPECT_TRUE(task_tracker_.HasTrackedTasks());
-
- is_canceled.Reset();
-
EXPECT_FALSE(task_tracker_.HasTrackedTasks());
}
@@ -355,8 +344,7 @@ void MaybeRunDeadlyTaskTrackerMemberFunction(
void PostDoNothingTask(CancelableTaskTracker* task_tracker) {
ignore_result(task_tracker->PostTask(
scoped_refptr<TestSimpleTaskRunner>(new TestSimpleTaskRunner()).get(),
- FROM_HERE,
- Bind(&DoNothing)));
+ FROM_HERE, DoNothing()));
}
TEST_F(CancelableTaskTrackerDeathTest, PostFromDifferentThread) {
@@ -364,8 +352,9 @@ TEST_F(CancelableTaskTrackerDeathTest, PostFromDifferentThread) {
ASSERT_TRUE(bad_thread.Start());
bad_thread.task_runner()->PostTask(
- FROM_HERE, Bind(&MaybeRunDeadlyTaskTrackerMemberFunction,
- Unretained(&task_tracker_), Bind(&PostDoNothingTask)));
+ FROM_HERE,
+ BindOnce(&MaybeRunDeadlyTaskTrackerMemberFunction,
+ Unretained(&task_tracker_), Bind(&PostDoNothingTask)));
}
void TryCancel(CancelableTaskTracker::TaskId task_id,
@@ -380,13 +369,14 @@ TEST_F(CancelableTaskTrackerDeathTest, CancelOnDifferentThread) {
Thread bad_thread("bad thread");
ASSERT_TRUE(bad_thread.Start());
- CancelableTaskTracker::TaskId task_id = task_tracker_.PostTask(
- test_task_runner.get(), FROM_HERE, Bind(&DoNothing));
+ CancelableTaskTracker::TaskId task_id =
+ task_tracker_.PostTask(test_task_runner.get(), FROM_HERE, DoNothing());
EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
bad_thread.task_runner()->PostTask(
- FROM_HERE, Bind(&MaybeRunDeadlyTaskTrackerMemberFunction,
- Unretained(&task_tracker_), Bind(&TryCancel, task_id)));
+ FROM_HERE,
+ BindOnce(&MaybeRunDeadlyTaskTrackerMemberFunction,
+ Unretained(&task_tracker_), Bind(&TryCancel, task_id)));
test_task_runner->RunUntilIdle();
}
@@ -398,14 +388,14 @@ TEST_F(CancelableTaskTrackerDeathTest, CancelAllOnDifferentThread) {
Thread bad_thread("bad thread");
ASSERT_TRUE(bad_thread.Start());
- CancelableTaskTracker::TaskId task_id = task_tracker_.PostTask(
- test_task_runner.get(), FROM_HERE, Bind(&DoNothing));
+ CancelableTaskTracker::TaskId task_id =
+ task_tracker_.PostTask(test_task_runner.get(), FROM_HERE, DoNothing());
EXPECT_NE(CancelableTaskTracker::kBadTaskId, task_id);
bad_thread.task_runner()->PostTask(
- FROM_HERE,
- Bind(&MaybeRunDeadlyTaskTrackerMemberFunction, Unretained(&task_tracker_),
- Bind(&CancelableTaskTracker::TryCancelAll)));
+ FROM_HERE, BindOnce(&MaybeRunDeadlyTaskTrackerMemberFunction,
+ Unretained(&task_tracker_),
+ Bind(&CancelableTaskTracker::TryCancelAll)));
test_task_runner->RunUntilIdle();
}
diff --git a/base/task/sequence_manager/enqueue_order.cc b/base/task/sequence_manager/enqueue_order.cc
new file mode 100644
index 0000000000..066ef0382e
--- /dev/null
+++ b/base/task/sequence_manager/enqueue_order.cc
@@ -0,0 +1,17 @@
+// 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.
+
+#include "base/task/sequence_manager/enqueue_order.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+EnqueueOrder::Generator::Generator() : counter_(kFirst) {}
+
+EnqueueOrder::Generator::~Generator() = default;
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/enqueue_order.h b/base/task/sequence_manager/enqueue_order.h
new file mode 100644
index 0000000000..fac1d179b0
--- /dev/null
+++ b/base/task/sequence_manager/enqueue_order.h
@@ -0,0 +1,71 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_ENQUEUE_ORDER_H_
+#define BASE_TASK_SEQUENCE_MANAGER_ENQUEUE_ORDER_H_
+
+#include <stdint.h>
+
+#include <atomic>
+
+#include "base/base_export.h"
+#include "base/macros.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+// 64-bit number which is used to order tasks.
+// SequenceManager assumes this number will never overflow.
+class EnqueueOrder {
+ public:
+ EnqueueOrder() : value_(kNone) {}
+ ~EnqueueOrder() = default;
+
+ static EnqueueOrder none() { return EnqueueOrder(kNone); }
+ static EnqueueOrder blocking_fence() { return EnqueueOrder(kBlockingFence); }
+
+ // It's okay to use EnqueueOrder in boolean expressions keeping in mind
+ // that some non-zero values have a special meaning.
+ operator uint64_t() const { return value_; }
+
+ static EnqueueOrder FromIntForTesting(uint64_t value) {
+ return EnqueueOrder(value);
+ }
+
+ // EnqueueOrder can't be created from a raw number in non-test code.
+ // Generator is used to create it with strictly monotonic guarantee.
+ class BASE_EXPORT Generator {
+ public:
+ Generator();
+ ~Generator();
+
+ // Can be called from any thread.
+ EnqueueOrder GenerateNext() {
+ return EnqueueOrder(std::atomic_fetch_add_explicit(
+ &counter_, uint64_t(1), std::memory_order_relaxed));
+ }
+
+ private:
+ std::atomic<uint64_t> counter_;
+ DISALLOW_COPY_AND_ASSIGN(Generator);
+ };
+
+ private:
+ explicit EnqueueOrder(uint64_t value) : value_(value) {}
+
+ enum SpecialValues : uint64_t {
+ kNone = 0,
+ kBlockingFence = 1,
+ kFirst = 2,
+ };
+
+ uint64_t value_;
+};
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_ENQUEUE_ORDER_H_
diff --git a/base/task/sequence_manager/graceful_queue_shutdown_helper.cc b/base/task/sequence_manager/graceful_queue_shutdown_helper.cc
new file mode 100644
index 0000000000..9a8c893e93
--- /dev/null
+++ b/base/task/sequence_manager/graceful_queue_shutdown_helper.cc
@@ -0,0 +1,42 @@
+// 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/task/sequence_manager/graceful_queue_shutdown_helper.h"
+
+#include "base/task/sequence_manager/task_queue_impl.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+GracefulQueueShutdownHelper::GracefulQueueShutdownHelper()
+ : sequence_manager_deleted_(false) {}
+
+GracefulQueueShutdownHelper::~GracefulQueueShutdownHelper() = default;
+
+void GracefulQueueShutdownHelper::GracefullyShutdownTaskQueue(
+ std::unique_ptr<internal::TaskQueueImpl> task_queue) {
+ AutoLock lock(lock_);
+ if (sequence_manager_deleted_)
+ return;
+ queues_.push_back(std::move(task_queue));
+}
+
+void GracefulQueueShutdownHelper::OnSequenceManagerDeleted() {
+ AutoLock lock(lock_);
+ sequence_manager_deleted_ = true;
+ queues_.clear();
+}
+
+std::vector<std::unique_ptr<internal::TaskQueueImpl>>
+GracefulQueueShutdownHelper::TakeQueues() {
+ AutoLock lock(lock_);
+ std::vector<std::unique_ptr<internal::TaskQueueImpl>> result;
+ result.swap(queues_);
+ return result;
+}
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/graceful_queue_shutdown_helper.h b/base/task/sequence_manager/graceful_queue_shutdown_helper.h
new file mode 100644
index 0000000000..108eb827b2
--- /dev/null
+++ b/base/task/sequence_manager/graceful_queue_shutdown_helper.h
@@ -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.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_GRACEFUL_QUEUE_SHUTDOWN_HELPER_H_
+#define BASE_TASK_SEQUENCE_MANAGER_GRACEFUL_QUEUE_SHUTDOWN_HELPER_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+class TaskQueueImpl;
+
+// Thread-safe helper to shutdown queues from any thread.
+class GracefulQueueShutdownHelper
+ : public RefCountedThreadSafe<GracefulQueueShutdownHelper> {
+ public:
+ GracefulQueueShutdownHelper();
+
+ void GracefullyShutdownTaskQueue(
+ std::unique_ptr<internal::TaskQueueImpl> queue);
+
+ void OnSequenceManagerDeleted();
+
+ std::vector<std::unique_ptr<internal::TaskQueueImpl>> TakeQueues();
+
+ private:
+ // This class is ref-counted so it controls its own lifetime.
+ ~GracefulQueueShutdownHelper();
+ friend class RefCountedThreadSafe<GracefulQueueShutdownHelper>;
+
+ Lock lock_;
+ bool sequence_manager_deleted_;
+ std::vector<std::unique_ptr<internal::TaskQueueImpl>> queues_;
+
+ DISALLOW_COPY_AND_ASSIGN(GracefulQueueShutdownHelper);
+};
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_GRACEFUL_QUEUE_SHUTDOWN_HELPER_H_
diff --git a/base/task/sequence_manager/intrusive_heap.h b/base/task/sequence_manager/intrusive_heap.h
new file mode 100644
index 0000000000..eb2fc8a454
--- /dev/null
+++ b/base/task/sequence_manager/intrusive_heap.h
@@ -0,0 +1,229 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_INTRUSIVE_HEAP_H_
+#define BASE_TASK_SEQUENCE_MANAGER_INTRUSIVE_HEAP_H_
+
+#include <algorithm>
+#include <vector>
+
+#include "base/logging.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+template <typename T>
+class IntrusiveHeap;
+
+// Intended as an opaque wrapper around |index_|.
+class HeapHandle {
+ public:
+ HeapHandle() : index_(0u) {}
+
+ bool IsValid() const { return index_ != 0u; }
+
+ private:
+ template <typename T>
+ friend class IntrusiveHeap;
+
+ HeapHandle(size_t index) : index_(index) {}
+
+ size_t index_;
+};
+
+// A standard min-heap with the following assumptions:
+// 1. T has operator <=
+// 2. T has method void SetHeapHandle(HeapHandle handle)
+// 3. T has method void ClearHeapHandle()
+// 4. T is moveable
+// 5. T is default constructible
+// 6. The heap size never gets terribly big so reclaiming memory on pop/erase
+// isn't a priority.
+//
+// The reason IntrusiveHeap exists is to provide similar performance to
+// std::priority_queue while allowing removal of arbitrary elements.
+template <typename T>
+class IntrusiveHeap {
+ public:
+ IntrusiveHeap() : nodes_(kMinimumHeapSize), size_(0) {}
+
+ ~IntrusiveHeap() {
+ for (size_t i = 1; i <= size_; i++) {
+ MakeHole(i);
+ }
+ }
+
+ bool empty() const { return size_ == 0; }
+
+ size_t size() const { return size_; }
+
+ void Clear() {
+ for (size_t i = 1; i <= size_; i++) {
+ MakeHole(i);
+ }
+ nodes_.resize(kMinimumHeapSize);
+ size_ = 0;
+ }
+
+ const T& Min() const {
+ DCHECK_GE(size_, 1u);
+ return nodes_[1];
+ }
+
+ void Pop() {
+ DCHECK_GE(size_, 1u);
+ MakeHole(1u);
+ size_t top_index = size_--;
+ if (!empty())
+ MoveHoleDownAndFillWithLeafElement(1u, std::move(nodes_[top_index]));
+ }
+
+ void insert(T&& element) {
+ size_++;
+ if (size_ >= nodes_.size())
+ nodes_.resize(nodes_.size() * 2);
+ // Notionally we have a hole in the tree at index |size_|, move this up
+ // to find the right insertion point.
+ MoveHoleUpAndFillWithElement(size_, std::move(element));
+ }
+
+ void erase(HeapHandle handle) {
+ DCHECK_GT(handle.index_, 0u);
+ DCHECK_LE(handle.index_, size_);
+ MakeHole(handle.index_);
+ size_t top_index = size_--;
+ if (empty() || top_index == handle.index_)
+ return;
+ if (nodes_[handle.index_] <= nodes_[top_index]) {
+ MoveHoleDownAndFillWithLeafElement(handle.index_,
+ std::move(nodes_[top_index]));
+ } else {
+ MoveHoleUpAndFillWithElement(handle.index_, std::move(nodes_[top_index]));
+ }
+ }
+
+ void ReplaceMin(T&& element) {
+ // Note |element| might not be a leaf node so we can't use
+ // MoveHoleDownAndFillWithLeafElement.
+ MoveHoleDownAndFillWithElement(1u, std::move(element));
+ }
+
+ void ChangeKey(HeapHandle handle, T&& element) {
+ if (nodes_[handle.index_] <= element) {
+ MoveHoleDownAndFillWithLeafElement(handle.index_, std::move(element));
+ } else {
+ MoveHoleUpAndFillWithElement(handle.index_, std::move(element));
+ }
+ }
+
+ // Caution mutating the heap invalidates the iterators.
+ const T* begin() const { return &nodes_[1u]; }
+ const T* end() const { return begin() + size_; }
+
+ private:
+ enum {
+ // The majority of sets in the scheduler have 0-3 items in them (a few will
+ // have perhaps up to 100), so this means we usually only have to allocate
+ // memory once.
+ kMinimumHeapSize = 4u
+ };
+
+ friend class IntrusiveHeapTest;
+
+ size_t MoveHole(size_t new_hole_pos, size_t old_hole_pos) {
+ DCHECK_GT(new_hole_pos, 0u);
+ DCHECK_LE(new_hole_pos, size_);
+ DCHECK_GT(new_hole_pos, 0u);
+ DCHECK_LE(new_hole_pos, size_);
+ DCHECK_NE(old_hole_pos, new_hole_pos);
+ nodes_[old_hole_pos] = std::move(nodes_[new_hole_pos]);
+ nodes_[old_hole_pos].SetHeapHandle(HeapHandle(old_hole_pos));
+ return new_hole_pos;
+ }
+
+ // Notionally creates a hole in the tree at |index|.
+ void MakeHole(size_t index) {
+ DCHECK_GT(index, 0u);
+ DCHECK_LE(index, size_);
+ nodes_[index].ClearHeapHandle();
+ }
+
+ void FillHole(size_t hole, T&& element) {
+ DCHECK_GT(hole, 0u);
+ DCHECK_LE(hole, size_);
+ nodes_[hole] = std::move(element);
+ nodes_[hole].SetHeapHandle(HeapHandle(hole));
+ DCHECK(std::is_heap(begin(), end(), CompareNodes));
+ }
+
+ // is_heap requires a strict comparator.
+ static bool CompareNodes(const T& a, const T& b) { return !(a <= b); }
+
+ // Moves the |hole| up the tree and when the right position has been found
+ // |element| is moved in.
+ void MoveHoleUpAndFillWithElement(size_t hole, T&& element) {
+ DCHECK_GT(hole, 0u);
+ DCHECK_LE(hole, size_);
+ while (hole >= 2u) {
+ size_t parent_pos = hole / 2;
+ if (nodes_[parent_pos] <= element)
+ break;
+
+ hole = MoveHole(parent_pos, hole);
+ }
+ FillHole(hole, std::move(element));
+ }
+
+ // Moves the |hole| down the tree and when the right position has been found
+ // |element| is moved in.
+ void MoveHoleDownAndFillWithElement(size_t hole, T&& element) {
+ DCHECK_GT(hole, 0u);
+ DCHECK_LE(hole, size_);
+ size_t child_pos = hole * 2;
+ while (child_pos < size_) {
+ if (nodes_[child_pos + 1] <= nodes_[child_pos])
+ child_pos++;
+
+ if (element <= nodes_[child_pos])
+ break;
+
+ hole = MoveHole(child_pos, hole);
+ child_pos *= 2;
+ }
+ if (child_pos == size_ && !(element <= nodes_[child_pos]))
+ hole = MoveHole(child_pos, hole);
+ FillHole(hole, std::move(element));
+ }
+
+ // Moves the |hole| down the tree and when the right position has been found
+ // |leaf_element| is moved in. Faster than MoveHoleDownAndFillWithElement
+ // (it does one key comparison per level instead of two) but only valid for
+ // leaf elements (i.e. one of the max values).
+ void MoveHoleDownAndFillWithLeafElement(size_t hole, T&& leaf_element) {
+ DCHECK_GT(hole, 0u);
+ DCHECK_LE(hole, size_);
+ size_t child_pos = hole * 2;
+ while (child_pos < size_) {
+ size_t second_child = child_pos + 1;
+ if (nodes_[second_child] <= nodes_[child_pos])
+ child_pos = second_child;
+
+ hole = MoveHole(child_pos, hole);
+ child_pos *= 2;
+ }
+ if (child_pos == size_)
+ hole = MoveHole(child_pos, hole);
+ MoveHoleUpAndFillWithElement(hole, std::move(leaf_element));
+ }
+
+ std::vector<T> nodes_; // NOTE we use 1-based indexing
+ size_t size_;
+};
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_INTRUSIVE_HEAP_H_
diff --git a/base/task/sequence_manager/intrusive_heap_unittest.cc b/base/task/sequence_manager/intrusive_heap_unittest.cc
new file mode 100644
index 0000000000..3c1323a76f
--- /dev/null
+++ b/base/task/sequence_manager/intrusive_heap_unittest.cc
@@ -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.
+
+#include "base/task/sequence_manager/intrusive_heap.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+namespace {
+
+struct TestElement {
+ int key;
+ HeapHandle* handle;
+
+ bool operator<=(const TestElement& other) const { return key <= other.key; }
+
+ void SetHeapHandle(HeapHandle h) {
+ if (handle)
+ *handle = h;
+ }
+
+ void ClearHeapHandle() {
+ if (handle)
+ *handle = HeapHandle();
+ }
+};
+
+} // namespace
+
+class IntrusiveHeapTest : public testing::Test {
+ protected:
+ static bool CompareNodes(const TestElement& a, const TestElement& b) {
+ return IntrusiveHeap<TestElement>::CompareNodes(a, b);
+ }
+};
+
+TEST_F(IntrusiveHeapTest, Basic) {
+ IntrusiveHeap<TestElement> heap;
+
+ EXPECT_TRUE(heap.empty());
+ EXPECT_EQ(0u, heap.size());
+}
+
+TEST_F(IntrusiveHeapTest, Clear) {
+ IntrusiveHeap<TestElement> heap;
+ HeapHandle index1;
+
+ heap.insert({11, &index1});
+ EXPECT_EQ(1u, heap.size());
+ EXPECT_TRUE(index1.IsValid());
+
+ heap.Clear();
+ EXPECT_EQ(0u, heap.size());
+ EXPECT_FALSE(index1.IsValid());
+}
+
+TEST_F(IntrusiveHeapTest, Destructor) {
+ HeapHandle index1;
+
+ {
+ IntrusiveHeap<TestElement> heap;
+
+ heap.insert({11, &index1});
+ EXPECT_EQ(1u, heap.size());
+ EXPECT_TRUE(index1.IsValid());
+ }
+
+ EXPECT_FALSE(index1.IsValid());
+}
+
+TEST_F(IntrusiveHeapTest, Min) {
+ IntrusiveHeap<TestElement> heap;
+
+ heap.insert({9, nullptr});
+ heap.insert({10, nullptr});
+ heap.insert({8, nullptr});
+ heap.insert({2, nullptr});
+ heap.insert({7, nullptr});
+ heap.insert({15, nullptr});
+ heap.insert({22, nullptr});
+ heap.insert({3, nullptr});
+
+ EXPECT_FALSE(heap.empty());
+ EXPECT_EQ(8u, heap.size());
+ EXPECT_EQ(2, heap.Min().key);
+}
+
+TEST_F(IntrusiveHeapTest, InsertAscending) {
+ IntrusiveHeap<TestElement> heap;
+ HeapHandle index1;
+
+ for (int i = 0; i < 50; i++)
+ heap.insert({i, nullptr});
+
+ EXPECT_EQ(0, heap.Min().key);
+ EXPECT_EQ(50u, heap.size());
+}
+
+TEST_F(IntrusiveHeapTest, InsertDescending) {
+ IntrusiveHeap<TestElement> heap;
+
+ for (int i = 0; i < 50; i++)
+ heap.insert({50 - i, nullptr});
+
+ EXPECT_EQ(1, heap.Min().key);
+ EXPECT_EQ(50u, heap.size());
+}
+
+TEST_F(IntrusiveHeapTest, HeapIndex) {
+ HeapHandle index5;
+ HeapHandle index4;
+ HeapHandle index3;
+ HeapHandle index2;
+ HeapHandle index1;
+ IntrusiveHeap<TestElement> heap;
+
+ EXPECT_FALSE(index1.IsValid());
+ EXPECT_FALSE(index2.IsValid());
+ EXPECT_FALSE(index3.IsValid());
+ EXPECT_FALSE(index4.IsValid());
+ EXPECT_FALSE(index5.IsValid());
+
+ heap.insert({15, &index5});
+ heap.insert({14, &index4});
+ heap.insert({13, &index3});
+ heap.insert({12, &index2});
+ heap.insert({11, &index1});
+
+ EXPECT_TRUE(index1.IsValid());
+ EXPECT_TRUE(index2.IsValid());
+ EXPECT_TRUE(index3.IsValid());
+ EXPECT_TRUE(index4.IsValid());
+ EXPECT_TRUE(index5.IsValid());
+
+ EXPECT_FALSE(heap.empty());
+}
+
+TEST_F(IntrusiveHeapTest, Pop) {
+ IntrusiveHeap<TestElement> heap;
+ HeapHandle index1;
+ HeapHandle index2;
+
+ heap.insert({11, &index1});
+ heap.insert({12, &index2});
+ EXPECT_EQ(2u, heap.size());
+ EXPECT_TRUE(index1.IsValid());
+ EXPECT_TRUE(index2.IsValid());
+
+ heap.Pop();
+ EXPECT_EQ(1u, heap.size());
+ EXPECT_FALSE(index1.IsValid());
+ EXPECT_TRUE(index2.IsValid());
+
+ heap.Pop();
+ EXPECT_EQ(0u, heap.size());
+ EXPECT_FALSE(index1.IsValid());
+ EXPECT_FALSE(index2.IsValid());
+}
+
+TEST_F(IntrusiveHeapTest, PopMany) {
+ IntrusiveHeap<TestElement> heap;
+
+ for (int i = 0; i < 500; i++)
+ heap.insert({i, nullptr});
+
+ EXPECT_FALSE(heap.empty());
+ EXPECT_EQ(500u, heap.size());
+ for (int i = 0; i < 500; i++) {
+ EXPECT_EQ(i, heap.Min().key);
+ heap.Pop();
+ }
+ EXPECT_TRUE(heap.empty());
+}
+
+TEST_F(IntrusiveHeapTest, Erase) {
+ IntrusiveHeap<TestElement> heap;
+
+ HeapHandle index12;
+
+ heap.insert({15, nullptr});
+ heap.insert({14, nullptr});
+ heap.insert({13, nullptr});
+ heap.insert({12, &index12});
+ heap.insert({11, nullptr});
+
+ EXPECT_EQ(5u, heap.size());
+ EXPECT_TRUE(index12.IsValid());
+ heap.erase(index12);
+ EXPECT_EQ(4u, heap.size());
+ EXPECT_FALSE(index12.IsValid());
+
+ EXPECT_EQ(11, heap.Min().key);
+ heap.Pop();
+ EXPECT_EQ(13, heap.Min().key);
+ heap.Pop();
+ EXPECT_EQ(14, heap.Min().key);
+ heap.Pop();
+ EXPECT_EQ(15, heap.Min().key);
+ heap.Pop();
+ EXPECT_TRUE(heap.empty());
+}
+
+TEST_F(IntrusiveHeapTest, ReplaceMin) {
+ IntrusiveHeap<TestElement> heap;
+
+ for (int i = 0; i < 500; i++)
+ heap.insert({500 - i, nullptr});
+
+ EXPECT_EQ(1, heap.Min().key);
+
+ for (int i = 0; i < 500; i++)
+ heap.ReplaceMin({1000 + i, nullptr});
+
+ EXPECT_EQ(1000, heap.Min().key);
+}
+
+TEST_F(IntrusiveHeapTest, ReplaceMinWithNonLeafNode) {
+ IntrusiveHeap<TestElement> heap;
+
+ for (int i = 0; i < 50; i++) {
+ heap.insert({i, nullptr});
+ heap.insert({200 + i, nullptr});
+ }
+
+ EXPECT_EQ(0, heap.Min().key);
+
+ for (int i = 0; i < 50; i++)
+ heap.ReplaceMin({100 + i, nullptr});
+
+ for (int i = 0; i < 50; i++) {
+ EXPECT_EQ((100 + i), heap.Min().key);
+ heap.Pop();
+ }
+ for (int i = 0; i < 50; i++) {
+ EXPECT_EQ((200 + i), heap.Min().key);
+ heap.Pop();
+ }
+ EXPECT_TRUE(heap.empty());
+}
+
+TEST_F(IntrusiveHeapTest, ReplaceMinCheckAllFinalPositions) {
+ HeapHandle index[100];
+
+ for (int j = -1; j <= 201; j += 2) {
+ IntrusiveHeap<TestElement> heap;
+ for (size_t i = 0; i < 100; i++) {
+ heap.insert({static_cast<int>(i) * 2, &index[i]});
+ }
+
+ heap.ReplaceMin({j, &index[40]});
+
+ int prev = -2;
+ while (!heap.empty()) {
+ DCHECK_GT(heap.Min().key, prev);
+ DCHECK(heap.Min().key == j || (heap.Min().key % 2) == 0);
+ DCHECK_NE(heap.Min().key, 0);
+ prev = heap.Min().key;
+ heap.Pop();
+ }
+ }
+}
+
+TEST_F(IntrusiveHeapTest, ChangeKeyUp) {
+ IntrusiveHeap<TestElement> heap;
+ HeapHandle index[10];
+
+ for (size_t i = 0; i < 10; i++) {
+ heap.insert({static_cast<int>(i) * 2, &index[i]});
+ }
+
+ heap.ChangeKey(index[5], {17, &index[5]});
+
+ std::vector<int> results;
+ while (!heap.empty()) {
+ results.push_back(heap.Min().key);
+ heap.Pop();
+ }
+
+ EXPECT_THAT(results, testing::ElementsAre(0, 2, 4, 6, 8, 12, 14, 16, 17, 18));
+}
+
+TEST_F(IntrusiveHeapTest, ChangeKeyUpButDoesntMove) {
+ IntrusiveHeap<TestElement> heap;
+ HeapHandle index[10];
+
+ for (size_t i = 0; i < 10; i++) {
+ heap.insert({static_cast<int>(i) * 2, &index[i]});
+ }
+
+ heap.ChangeKey(index[5], {11, &index[5]});
+
+ std::vector<int> results;
+ while (!heap.empty()) {
+ results.push_back(heap.Min().key);
+ heap.Pop();
+ }
+
+ EXPECT_THAT(results, testing::ElementsAre(0, 2, 4, 6, 8, 11, 12, 14, 16, 18));
+}
+
+TEST_F(IntrusiveHeapTest, ChangeKeyDown) {
+ IntrusiveHeap<TestElement> heap;
+ HeapHandle index[10];
+
+ for (size_t i = 0; i < 10; i++) {
+ heap.insert({static_cast<int>(i) * 2, &index[i]});
+ }
+
+ heap.ChangeKey(index[5], {1, &index[5]});
+
+ std::vector<int> results;
+ while (!heap.empty()) {
+ results.push_back(heap.Min().key);
+ heap.Pop();
+ }
+
+ EXPECT_THAT(results, testing::ElementsAre(0, 1, 2, 4, 6, 8, 12, 14, 16, 18));
+}
+
+TEST_F(IntrusiveHeapTest, ChangeKeyDownButDoesntMove) {
+ IntrusiveHeap<TestElement> heap;
+ HeapHandle index[10];
+
+ for (size_t i = 0; i < 10; i++) {
+ heap.insert({static_cast<int>(i) * 2, &index[i]});
+ }
+
+ heap.ChangeKey(index[5], {9, &index[5]});
+
+ std::vector<int> results;
+ while (!heap.empty()) {
+ results.push_back(heap.Min().key);
+ heap.Pop();
+ }
+
+ EXPECT_THAT(results, testing::ElementsAre(0, 2, 4, 6, 8, 9, 12, 14, 16, 18));
+}
+
+TEST_F(IntrusiveHeapTest, ChangeKeyCheckAllFinalPositions) {
+ HeapHandle index[100];
+
+ for (int j = -1; j <= 201; j += 2) {
+ IntrusiveHeap<TestElement> heap;
+ for (size_t i = 0; i < 100; i++) {
+ heap.insert({static_cast<int>(i) * 2, &index[i]});
+ }
+
+ heap.ChangeKey(index[40], {j, &index[40]});
+
+ int prev = -2;
+ while (!heap.empty()) {
+ DCHECK_GT(heap.Min().key, prev);
+ DCHECK(heap.Min().key == j || (heap.Min().key % 2) == 0);
+ DCHECK_NE(heap.Min().key, 80);
+ prev = heap.Min().key;
+ heap.Pop();
+ }
+ }
+}
+
+TEST_F(IntrusiveHeapTest, CompareNodes) {
+ TestElement five{5, nullptr}, six{6, nullptr};
+
+ // Check that we have a strict comparator, otherwise std::is_heap()
+ // (used in DCHECK) may fail. See http://crbug.com/661080.
+ EXPECT_FALSE(IntrusiveHeapTest::CompareNodes(six, six));
+
+ EXPECT_FALSE(IntrusiveHeapTest::CompareNodes(five, six));
+ EXPECT_TRUE(IntrusiveHeapTest::CompareNodes(six, five));
+}
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/lazily_deallocated_deque.h b/base/task/sequence_manager/lazily_deallocated_deque.h
new file mode 100644
index 0000000000..7a4d7bad6a
--- /dev/null
+++ b/base/task/sequence_manager/lazily_deallocated_deque.h
@@ -0,0 +1,364 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_LAZILY_DEALLOCATED_DEQUE_H_
+#define BASE_TASK_SEQUENCE_MANAGER_LAZILY_DEALLOCATED_DEQUE_H_
+
+#include <algorithm>
+#include <cmath>
+#include <memory>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/logging.h"
+#include "base/time/time.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+// A LazilyDeallocatedDeque specialized for the SequenceManager's usage
+// patterns. The queue generally grows while tasks are added and then removed
+// until empty and the cycle repeats.
+//
+// The main difference between sequence_manager::LazilyDeallocatedDeque and
+// others is memory management. For performance (memory allocation isn't free)
+// we don't automatically reclaiming memory when the queue becomes empty.
+// Instead we rely on the surrounding code periodically calling
+// MaybeShrinkQueue, ideally when the queue is empty.
+//
+// We keep track of the maximum recent queue size and rate limit
+// MaybeShrinkQueue to avoid unnecessary churn.
+//
+// NB this queue isn't by itself thread safe.
+template <typename T>
+class LazilyDeallocatedDeque {
+ public:
+ enum {
+ // Minimum allocation for a ring. Note a ring of size 4 will only hold up to
+ // 3 elements.
+ kMinimumRingSize = 4,
+
+ // Maximum "wasted" capacity allowed when considering if we should resize
+ // the backing store.
+ kReclaimThreshold = 16,
+
+ // Used to rate limit how frequently MaybeShrinkQueue actually shrinks the
+ // queue.
+ kMinimumShrinkIntervalInSeconds = 5
+ };
+
+ LazilyDeallocatedDeque() {}
+
+ ~LazilyDeallocatedDeque() { clear(); }
+
+ bool empty() const { return size_ == 0; }
+
+ size_t max_size() const { return max_size_; }
+
+ size_t size() const { return size_; }
+
+ size_t capacity() const {
+ size_t capacity = 0;
+ for (const Ring* iter = head_.get(); iter; iter = iter->next_.get()) {
+ capacity += iter->capacity();
+ }
+ return capacity;
+ }
+
+ void clear() {
+ while (head_) {
+ head_ = std::move(head_->next_);
+ }
+
+ tail_ = nullptr;
+ size_ = 0;
+ }
+
+ // Assumed to be an uncommon operation.
+ void push_front(T t) {
+ if (!head_) {
+ head_ = std::make_unique<Ring>(kMinimumRingSize);
+ tail_ = head_.get();
+ }
+
+ // Grow if needed, by the minimum amount.
+ if (!head_->CanPush()) {
+ std::unique_ptr<Ring> new_ring = std::make_unique<Ring>(kMinimumRingSize);
+ new_ring->next_ = std::move(head_);
+ head_ = std::move(new_ring);
+ }
+
+ head_->push_front(std::move(t));
+ max_size_ = std::max(max_size_, ++size_);
+ }
+
+ // Assumed to be a common operation.
+ void push_back(T t) {
+ if (!head_) {
+ head_ = std::make_unique<Ring>(kMinimumRingSize);
+ tail_ = head_.get();
+ }
+
+ // Grow if needed.
+ if (!tail_->CanPush()) {
+ tail_->next_ = std::make_unique<Ring>(tail_->capacity() * 2);
+ tail_ = tail_->next_.get();
+ }
+
+ tail_->push_back(std::move(t));
+ max_size_ = std::max(max_size_, ++size_);
+ }
+
+ T& front() {
+ DCHECK(head_);
+ return head_->front();
+ }
+
+ const T& front() const {
+ DCHECK(head_);
+ return head_->front();
+ }
+
+ T& back() {
+ DCHECK(tail_);
+ return tail_->back();
+ }
+
+ const T& back() const {
+ DCHECK(tail_);
+ return tail_->back();
+ }
+
+ void pop_front() {
+ DCHECK(tail_);
+ DCHECK_GT(size_, 0u);
+ head_->pop_front();
+
+ // If the ring has become empty and we have several rings then, remove the
+ // head one (which we expect to have lower capacity than the remaining
+ // ones).
+ if (head_->empty() && head_->next_) {
+ head_ = std::move(head_->next_);
+ }
+
+ --size_;
+ }
+
+ void swap(LazilyDeallocatedDeque& other) {
+ std::swap(head_, other.head_);
+ std::swap(tail_, other.tail_);
+ std::swap(size_, other.size_);
+ std::swap(max_size_, other.max_size_);
+ std::swap(next_resize_time_, other.next_resize_time_);
+ }
+
+ void MaybeShrinkQueue() {
+ if (!tail_)
+ return;
+
+ DCHECK_GE(max_size_, size_);
+
+ // Rate limit how often we shrink the queue because it's somewhat expensive.
+ TimeTicks current_time = TimeTicks::Now();
+ if (current_time < next_resize_time_)
+ return;
+
+ // Due to the way the Ring works we need 1 more slot than is used.
+ size_t new_capacity = max_size_ + 1;
+ if (new_capacity < kMinimumRingSize)
+ new_capacity = kMinimumRingSize;
+
+ // Reset |max_size_| so that unless usage has spiked up we will consider
+ // reclaiming it next time.
+ max_size_ = size_;
+
+ // Only realloc if the current capacity is sufficiently the observed maximum
+ // size for the previous period.
+ if (new_capacity + kReclaimThreshold >= capacity())
+ return;
+
+ SetCapacity(new_capacity);
+ next_resize_time_ =
+ current_time + TimeDelta::FromSeconds(kMinimumShrinkIntervalInSeconds);
+ }
+
+ void SetCapacity(size_t new_capacity) {
+ std::unique_ptr<Ring> new_ring = std::make_unique<Ring>(new_capacity);
+
+ DCHECK_GE(new_capacity, size_ + 1);
+
+ // Preserve the |size_| which counts down to zero in the while loop.
+ size_t real_size = size_;
+
+ while (!empty()) {
+ DCHECK(new_ring->CanPush());
+ new_ring->push_back(std::move(head_->front()));
+ pop_front();
+ }
+
+ size_ = real_size;
+
+ DCHECK_EQ(head_.get(), tail_);
+ head_ = std::move(new_ring);
+ tail_ = head_.get();
+ }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(LazilyDeallocatedDequeTest, RingPushFront);
+ FRIEND_TEST_ALL_PREFIXES(LazilyDeallocatedDequeTest, RingPushBack);
+ FRIEND_TEST_ALL_PREFIXES(LazilyDeallocatedDequeTest, RingCanPush);
+ FRIEND_TEST_ALL_PREFIXES(LazilyDeallocatedDequeTest, RingPushPopPushPop);
+
+ struct Ring {
+ explicit Ring(size_t capacity)
+ : capacity_(capacity),
+ front_index_(0),
+ back_index_(0),
+ data_(reinterpret_cast<T*>(new char[sizeof(T) * capacity])),
+ next_(nullptr) {
+ DCHECK_GE(capacity_, kMinimumRingSize);
+ }
+
+ ~Ring() {
+ while (!empty()) {
+ pop_front();
+ }
+ delete[] reinterpret_cast<char*>(data_);
+ }
+
+ bool empty() const { return back_index_ == front_index_; }
+
+ size_t capacity() const { return capacity_; }
+
+ bool CanPush() const {
+ return front_index_ != CircularIncrement(back_index_);
+ }
+
+ void push_front(T&& t) {
+ // Mustn't appear to become empty.
+ DCHECK_NE(CircularDecrement(front_index_), back_index_);
+ new (&data_[front_index_]) T(std::move(t));
+ front_index_ = CircularDecrement(front_index_);
+ }
+
+ void push_back(T&& t) {
+ back_index_ = CircularIncrement(back_index_);
+ DCHECK(!empty()); // Mustn't appear to become empty.
+ new (&data_[back_index_]) T(std::move(t));
+ }
+
+ bool CanPop() const { return front_index_ != back_index_; }
+
+ void pop_front() {
+ DCHECK(!empty());
+ front_index_ = CircularIncrement(front_index_);
+ data_[front_index_].~T();
+ }
+
+ T& front() {
+ DCHECK(!empty());
+ return data_[CircularIncrement(front_index_)];
+ }
+
+ const T& front() const {
+ DCHECK(!empty());
+ return data_[CircularIncrement(front_index_)];
+ }
+
+ T& back() {
+ DCHECK(!empty());
+ return data_[back_index_];
+ }
+
+ const T& back() const {
+ DCHECK(!empty());
+ return data_[back_index_];
+ }
+
+ size_t CircularDecrement(size_t index) const {
+ if (index == 0)
+ return capacity_ - 1;
+ return index - 1;
+ }
+
+ size_t CircularIncrement(size_t index) const {
+ DCHECK_LT(index, capacity_);
+ ++index;
+ if (index == capacity_)
+ return 0;
+ return index;
+ }
+
+ size_t capacity_;
+ size_t front_index_;
+ size_t back_index_;
+ T* data_;
+ std::unique_ptr<Ring> next_;
+
+ DISALLOW_COPY_AND_ASSIGN(Ring);
+ };
+
+ public:
+ class Iterator {
+ public:
+ using value_type = T;
+ using pointer = const T*;
+ using reference = const T&;
+
+ const T& operator->() const { return ring_->data_[index_]; }
+ const T& operator*() const { return ring_->data_[index_]; }
+
+ Iterator& operator++() {
+ if (index_ == ring_->back_index_) {
+ ring_ = ring_->next_.get();
+ index_ = 0;
+ } else {
+ index_ = ring_->CircularIncrement(index_);
+ }
+ return *this;
+ }
+
+ operator bool() const { return !!ring_; }
+
+ private:
+ explicit Iterator(const Ring* ring) {
+ if (!ring || ring->empty()) {
+ ring_ = nullptr;
+ index_ = 0;
+ return;
+ }
+
+ ring_ = ring;
+ index_ = ring_->CircularIncrement(ring->front_index_);
+ }
+
+ const Ring* ring_;
+ size_t index_;
+
+ friend class LazilyDeallocatedDeque;
+ };
+
+ Iterator begin() const { return Iterator(head_.get()); }
+
+ Iterator end() const { return Iterator(nullptr); }
+
+ private:
+ // We maintain a list of Ring buffers, to enable us to grow without copying,
+ // but most of the time we aim to have only one active Ring.
+ std::unique_ptr<Ring> head_;
+ Ring* tail_ = nullptr;
+
+ size_t size_ = 0;
+ size_t max_size_ = 0;
+ TimeTicks next_resize_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(LazilyDeallocatedDeque);
+};
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_LAZILY_DEALLOCATED_DEQUE_H_
diff --git a/base/task/sequence_manager/lazily_deallocated_deque_unittest.cc b/base/task/sequence_manager/lazily_deallocated_deque_unittest.cc
new file mode 100644
index 0000000000..2afa048ac9
--- /dev/null
+++ b/base/task/sequence_manager/lazily_deallocated_deque_unittest.cc
@@ -0,0 +1,364 @@
+// 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.
+
+#include "base/task/sequence_manager/lazily_deallocated_deque.h"
+
+#include "base/time/time_override.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+class LazilyDeallocatedDequeTest : public testing::Test {};
+
+TEST_F(LazilyDeallocatedDequeTest, InitiallyEmpty) {
+ LazilyDeallocatedDeque<int> d;
+
+ EXPECT_TRUE(d.empty());
+ EXPECT_EQ(0u, d.size());
+}
+
+TEST_F(LazilyDeallocatedDequeTest, PushBackAndPopFront1) {
+ LazilyDeallocatedDeque<int> d;
+
+ d.push_back(123);
+
+ EXPECT_FALSE(d.empty());
+ EXPECT_EQ(1u, d.size());
+
+ EXPECT_EQ(123, d.front());
+
+ d.pop_front();
+ EXPECT_TRUE(d.empty());
+ EXPECT_EQ(0u, d.size());
+}
+
+TEST_F(LazilyDeallocatedDequeTest, PushBackAndPopFront1000) {
+ LazilyDeallocatedDeque<int> d;
+
+ for (int i = 0; i < 1000; i++) {
+ d.push_back(i);
+ }
+
+ EXPECT_EQ(0, d.front());
+ EXPECT_EQ(999, d.back());
+ EXPECT_EQ(1000u, d.size());
+
+ for (int i = 0; i < 1000; i++) {
+ EXPECT_EQ(i, d.front());
+ d.pop_front();
+ }
+
+ EXPECT_EQ(0u, d.size());
+}
+
+TEST_F(LazilyDeallocatedDequeTest, PushFrontBackAndPopFront1) {
+ LazilyDeallocatedDeque<int> d;
+
+ d.push_front(123);
+
+ EXPECT_FALSE(d.empty());
+ EXPECT_EQ(1u, d.size());
+
+ EXPECT_EQ(123, d.front());
+
+ d.pop_front();
+ EXPECT_TRUE(d.empty());
+ EXPECT_EQ(0u, d.size());
+}
+
+TEST_F(LazilyDeallocatedDequeTest, PushFrontAndPopFront1000) {
+ LazilyDeallocatedDeque<int> d;
+
+ for (int i = 0; i < 1000; i++) {
+ d.push_front(i);
+ }
+
+ EXPECT_EQ(999, d.front());
+ EXPECT_EQ(0, d.back());
+ EXPECT_EQ(1000u, d.size());
+
+ for (int i = 0; i < 1000; i++) {
+ EXPECT_EQ(999 - i, d.front());
+ d.pop_front();
+ }
+
+ EXPECT_EQ(0u, d.size());
+}
+
+TEST_F(LazilyDeallocatedDequeTest, MaybeShrinkQueueWithLargeSizeDrop) {
+ LazilyDeallocatedDeque<int> d;
+
+ for (int i = 0; i < 1000; i++) {
+ d.push_back(i);
+ }
+ EXPECT_EQ(1000u, d.size());
+ EXPECT_EQ(1020u, d.capacity());
+ EXPECT_EQ(1000u, d.max_size());
+
+ // Drop most elements.
+ for (int i = 0; i < 990; i++) {
+ d.pop_front();
+ }
+ EXPECT_EQ(10u, d.size());
+ EXPECT_EQ(512u, d.capacity());
+ EXPECT_EQ(1000u, d.max_size());
+
+ // This won't do anything since the max size is greater than the current
+ // capacity.
+ d.MaybeShrinkQueue();
+ EXPECT_EQ(512u, d.capacity());
+ EXPECT_EQ(10u, d.max_size());
+
+ // This will shrink because the max size is now much less than the current
+ // capacity.
+ d.MaybeShrinkQueue();
+ EXPECT_EQ(11u, d.capacity());
+}
+
+TEST_F(LazilyDeallocatedDequeTest, MaybeShrinkQueueWithSmallSizeDrop) {
+ LazilyDeallocatedDeque<int> d;
+
+ for (int i = 0; i < 1010; i++) {
+ d.push_back(i);
+ }
+ EXPECT_EQ(1010u, d.size());
+ EXPECT_EQ(1020u, d.capacity());
+ EXPECT_EQ(1010u, d.max_size());
+
+ // Drop a couple of elements.
+ d.pop_front();
+ d.pop_front();
+ EXPECT_EQ(1008u, d.size());
+ EXPECT_EQ(1020u, d.capacity());
+ EXPECT_EQ(1010u, d.max_size());
+
+ // This won't do anything since the max size is only slightly lower than the
+ // capacity.
+ EXPECT_EQ(1020u, d.capacity());
+ EXPECT_EQ(1010u, d.max_size());
+
+ // Ditto. Nothing changed so no point shrinking.
+ d.MaybeShrinkQueue();
+ EXPECT_EQ(1008u, d.max_size());
+ EXPECT_EQ(1020u, d.capacity());
+}
+
+TEST_F(LazilyDeallocatedDequeTest, MaybeShrinkQueueToEmpty) {
+ LazilyDeallocatedDeque<int> d;
+
+ for (int i = 0; i < 1000; i++) {
+ d.push_front(i);
+ }
+
+ for (int i = 0; i < 1000; i++) {
+ d.pop_front();
+ }
+
+ d.MaybeShrinkQueue();
+ EXPECT_EQ(0u, d.max_size());
+ EXPECT_EQ(LazilyDeallocatedDeque<int>::kMinimumRingSize, d.capacity());
+}
+
+namespace {
+TimeTicks fake_now;
+}
+
+TEST_F(LazilyDeallocatedDequeTest, MaybeShrinkQueueRateLimiting) {
+ subtle::ScopedTimeClockOverrides time_overrides(
+ nullptr, []() { return fake_now; }, nullptr);
+ LazilyDeallocatedDeque<int> d;
+
+ for (int i = 0; i < 1000; i++) {
+ d.push_back(i);
+ }
+ EXPECT_EQ(1000u, d.size());
+ EXPECT_EQ(1020u, d.capacity());
+ EXPECT_EQ(1000u, d.max_size());
+
+ // Drop some elements.
+ for (int i = 0; i < 100; i++) {
+ d.pop_front();
+ }
+ EXPECT_EQ(900u, d.size());
+ EXPECT_EQ(960u, d.capacity());
+ EXPECT_EQ(1000u, d.max_size());
+
+ // This won't do anything since the max size is greater than the current
+ // capacity.
+ d.MaybeShrinkQueue();
+ EXPECT_EQ(960u, d.capacity());
+ EXPECT_EQ(900u, d.max_size());
+
+ // This will shrink to fit.
+ d.MaybeShrinkQueue();
+ EXPECT_EQ(901u, d.capacity());
+ EXPECT_EQ(900u, d.max_size());
+
+ // Drop some more elements.
+ for (int i = 0; i < 100; i++) {
+ d.pop_front();
+ }
+ EXPECT_EQ(800u, d.size());
+ EXPECT_EQ(901u, d.capacity());
+ EXPECT_EQ(900u, d.max_size());
+
+ // Not enough time has passed so max_size is untouched and not shrunk.
+ d.MaybeShrinkQueue();
+ EXPECT_EQ(900u, d.max_size());
+ EXPECT_EQ(901u, d.capacity());
+
+ // After time passes we re-sample max_size.
+ fake_now += TimeDelta::FromSeconds(
+ LazilyDeallocatedDeque<int>::kMinimumShrinkIntervalInSeconds);
+ d.MaybeShrinkQueue();
+ EXPECT_EQ(800u, d.max_size());
+ EXPECT_EQ(901u, d.capacity());
+
+ // And The next call to MaybeShrinkQueue actually shrinks the queue.
+ d.MaybeShrinkQueue();
+ EXPECT_EQ(800u, d.max_size());
+ EXPECT_EQ(801u, d.capacity());
+}
+
+TEST_F(LazilyDeallocatedDequeTest, Iterators) {
+ LazilyDeallocatedDeque<int> d;
+
+ d.push_back(1);
+ d.push_back(2);
+ d.push_back(3);
+
+ auto iter = d.begin();
+ EXPECT_EQ(1, *iter);
+ EXPECT_NE(++iter, d.end());
+
+ EXPECT_EQ(2, *iter);
+ EXPECT_NE(++iter, d.end());
+
+ EXPECT_EQ(3, *iter);
+ EXPECT_EQ(++iter, d.end());
+}
+
+TEST_F(LazilyDeallocatedDequeTest, PushBackAndFront) {
+ LazilyDeallocatedDeque<int> d;
+
+ int j = 1;
+ for (int i = 0; i < 1000; i++) {
+ d.push_back(j++);
+ d.push_back(j++);
+ d.push_back(j++);
+ d.push_back(j++);
+ d.push_front(-i);
+ }
+
+ for (int i = -999; i < 4000; i++) {
+ EXPECT_EQ(d.front(), i);
+ d.pop_front();
+ }
+}
+
+TEST_F(LazilyDeallocatedDequeTest, SetCapacity) {
+ LazilyDeallocatedDeque<int> d;
+ for (int i = 0; i < 1000; i++) {
+ d.push_back(i);
+ }
+
+ EXPECT_EQ(1020u, d.capacity());
+
+ // We need 1 more spot than the size due to the way the Ring works.
+ d.SetCapacity(1001);
+
+ for (int i = 0; i < 1000; i++) {
+ EXPECT_EQ(d.front(), i);
+ d.pop_front();
+ }
+}
+
+TEST_F(LazilyDeallocatedDequeTest, RingPushFront) {
+ LazilyDeallocatedDeque<int>::Ring r(4);
+
+ r.push_front(1);
+ r.push_front(2);
+ r.push_front(3);
+
+ EXPECT_EQ(3, r.front());
+ EXPECT_EQ(1, r.back());
+}
+
+TEST_F(LazilyDeallocatedDequeTest, RingPushBack) {
+ LazilyDeallocatedDeque<int>::Ring r(4);
+
+ r.push_back(1);
+ r.push_back(2);
+ r.push_back(3);
+
+ EXPECT_EQ(1, r.front());
+ EXPECT_EQ(3, r.back());
+}
+
+TEST_F(LazilyDeallocatedDequeTest, RingCanPush) {
+ LazilyDeallocatedDeque<int>::Ring r1(4);
+ LazilyDeallocatedDeque<int>::Ring r2(4);
+
+ for (int i = 0; i < 3; i++) {
+ EXPECT_TRUE(r1.CanPush());
+ r1.push_back(0);
+
+ EXPECT_TRUE(r2.CanPush());
+ r2.push_back(0);
+ }
+
+ EXPECT_FALSE(r1.CanPush());
+ EXPECT_FALSE(r2.CanPush());
+}
+
+TEST_F(LazilyDeallocatedDequeTest, RingPushPopPushPop) {
+ LazilyDeallocatedDeque<int>::Ring r(4);
+
+ EXPECT_FALSE(r.CanPop());
+ EXPECT_TRUE(r.CanPush());
+ r.push_back(1);
+ EXPECT_TRUE(r.CanPop());
+ EXPECT_TRUE(r.CanPush());
+ r.push_back(2);
+ EXPECT_TRUE(r.CanPush());
+ r.push_back(3);
+ EXPECT_FALSE(r.CanPush());
+
+ EXPECT_TRUE(r.CanPop());
+ EXPECT_EQ(1, r.front());
+ r.pop_front();
+ EXPECT_TRUE(r.CanPop());
+ EXPECT_EQ(2, r.front());
+ r.pop_front();
+ EXPECT_TRUE(r.CanPop());
+ EXPECT_EQ(3, r.front());
+ r.pop_front();
+ EXPECT_FALSE(r.CanPop());
+
+ EXPECT_TRUE(r.CanPush());
+ r.push_back(10);
+ EXPECT_TRUE(r.CanPush());
+ r.push_back(20);
+ EXPECT_TRUE(r.CanPush());
+ r.push_back(30);
+ EXPECT_FALSE(r.CanPush());
+
+ EXPECT_TRUE(r.CanPop());
+ EXPECT_EQ(10, r.front());
+ r.pop_front();
+ EXPECT_TRUE(r.CanPop());
+ EXPECT_EQ(20, r.front());
+ r.pop_front();
+ EXPECT_TRUE(r.CanPop());
+ EXPECT_EQ(30, r.front());
+ r.pop_front();
+
+ EXPECT_FALSE(r.CanPop());
+}
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/lazy_now.cc b/base/task/sequence_manager/lazy_now.cc
new file mode 100644
index 0000000000..b391b32a4e
--- /dev/null
+++ b/base/task/sequence_manager/lazy_now.cc
@@ -0,0 +1,36 @@
+// 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.
+
+#include "base/task/sequence_manager/lazy_now.h"
+
+#include "base/time/tick_clock.h"
+
+namespace base {
+namespace sequence_manager {
+
+LazyNow::LazyNow(TimeTicks now) : tick_clock_(nullptr), now_(now) {}
+
+LazyNow::LazyNow(const TickClock* tick_clock)
+ : tick_clock_(tick_clock), now_() {
+ DCHECK(tick_clock);
+}
+
+LazyNow::LazyNow(LazyNow&& move_from) noexcept
+ : tick_clock_(move_from.tick_clock_), now_(move_from.now_) {
+ move_from.tick_clock_ = nullptr;
+ move_from.now_ = nullopt;
+}
+
+TimeTicks LazyNow::Now() {
+ // It looks tempting to avoid using Optional and to rely on is_null() instead,
+ // but in some test environments clock intentionally starts from zero.
+ if (!now_) {
+ DCHECK(tick_clock_); // It can fire only on use after std::move.
+ now_ = tick_clock_->NowTicks();
+ }
+ return *now_;
+}
+
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/lazy_now.h b/base/task/sequence_manager/lazy_now.h
new file mode 100644
index 0000000000..d9ace8bf24
--- /dev/null
+++ b/base/task/sequence_manager/lazy_now.h
@@ -0,0 +1,41 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_LAZY_NOW_H_
+#define BASE_TASK_SEQUENCE_MANAGER_LAZY_NOW_H_
+
+#include "base/base_export.h"
+#include "base/optional.h"
+#include "base/time/time.h"
+
+namespace base {
+
+class TickClock;
+
+namespace sequence_manager {
+
+// Now() is somewhat expensive so it makes sense not to call Now() unless we
+// really need to and to avoid subsequent calls if already called once.
+// LazyNow objects are expected to be short-living to represent accurate time.
+class BASE_EXPORT LazyNow {
+ public:
+ explicit LazyNow(TimeTicks now);
+ explicit LazyNow(const TickClock* tick_clock);
+
+ LazyNow(LazyNow&& move_from) noexcept;
+
+ // Result will not be updated on any subsesequent calls.
+ TimeTicks Now();
+
+ private:
+ const TickClock* tick_clock_; // Not owned.
+ Optional<TimeTicks> now_;
+
+ DISALLOW_COPY_AND_ASSIGN(LazyNow);
+};
+
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_LAZY_NOW_H_
diff --git a/base/task/sequence_manager/moveable_auto_lock.h b/base/task/sequence_manager/moveable_auto_lock.h
new file mode 100644
index 0000000000..a80d5f8a74
--- /dev/null
+++ b/base/task/sequence_manager/moveable_auto_lock.h
@@ -0,0 +1,41 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_MOVEABLE_AUTO_LOCK_H_
+#define BASE_TASK_SEQUENCE_MANAGER_MOVEABLE_AUTO_LOCK_H_
+
+#include "base/synchronization/lock.h"
+
+namespace base {
+namespace sequence_manager {
+
+class MoveableAutoLock {
+ public:
+ explicit MoveableAutoLock(Lock& lock) : lock_(lock), moved_(false) {
+ lock_.Acquire();
+ }
+
+ MoveableAutoLock(MoveableAutoLock&& other) noexcept
+ : lock_(other.lock_), moved_(other.moved_) {
+ lock_.AssertAcquired();
+ other.moved_ = true;
+ }
+
+ ~MoveableAutoLock() {
+ if (moved_)
+ return;
+ lock_.AssertAcquired();
+ lock_.Release();
+ }
+
+ private:
+ Lock& lock_;
+ bool moved_;
+ DISALLOW_COPY_AND_ASSIGN(MoveableAutoLock);
+};
+
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_MOVEABLE_AUTO_LOCK_H_
diff --git a/base/task/sequence_manager/real_time_domain.cc b/base/task/sequence_manager/real_time_domain.cc
new file mode 100644
index 0000000000..6a6caf094d
--- /dev/null
+++ b/base/task/sequence_manager/real_time_domain.cc
@@ -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.
+
+#include "base/task/sequence_manager/real_time_domain.h"
+
+#include "base/task/sequence_manager/sequence_manager.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+RealTimeDomain::RealTimeDomain() {}
+
+RealTimeDomain::~RealTimeDomain() = default;
+
+LazyNow RealTimeDomain::CreateLazyNow() const {
+ return LazyNow(sequence_manager()->GetTickClock());
+}
+
+TimeTicks RealTimeDomain::Now() const {
+ return sequence_manager()->NowTicks();
+}
+
+Optional<TimeDelta> RealTimeDomain::DelayTillNextTask(LazyNow* lazy_now) {
+ Optional<TimeTicks> next_run_time = NextScheduledRunTime();
+ if (!next_run_time)
+ return nullopt;
+
+ TimeTicks now = lazy_now->Now();
+ if (now >= next_run_time) {
+ // Overdue work needs to be run immediately.
+ return TimeDelta();
+ }
+
+ TimeDelta delay = *next_run_time - now;
+ TRACE_EVENT1("sequence_manager", "RealTimeDomain::DelayTillNextTask",
+ "delay_ms", delay.InMillisecondsF());
+ return delay;
+}
+
+const char* RealTimeDomain::GetName() const {
+ return "RealTimeDomain";
+}
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/real_time_domain.h b/base/task/sequence_manager/real_time_domain.h
new file mode 100644
index 0000000000..4923ebf06e
--- /dev/null
+++ b/base/task/sequence_manager/real_time_domain.h
@@ -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.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_REAL_TIME_DOMAIN_H_
+#define BASE_TASK_SEQUENCE_MANAGER_REAL_TIME_DOMAIN_H_
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/task/sequence_manager/time_domain.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+class BASE_EXPORT RealTimeDomain : public TimeDomain {
+ public:
+ RealTimeDomain();
+ ~RealTimeDomain() override;
+
+ // TimeDomain implementation:
+ LazyNow CreateLazyNow() const override;
+ TimeTicks Now() const override;
+ Optional<TimeDelta> DelayTillNextTask(LazyNow* lazy_now) override;
+
+ protected:
+ const char* GetName() const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RealTimeDomain);
+};
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_REAL_TIME_DOMAIN_H_
diff --git a/base/task/sequence_manager/sequence_manager.cc b/base/task/sequence_manager/sequence_manager.cc
new file mode 100644
index 0000000000..3451f98fe8
--- /dev/null
+++ b/base/task/sequence_manager/sequence_manager.cc
@@ -0,0 +1,26 @@
+// 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.
+
+#include "base/task/sequence_manager/sequence_manager.h"
+
+namespace base {
+namespace sequence_manager {
+
+SequenceManager::MetricRecordingSettings::MetricRecordingSettings() {}
+
+SequenceManager::MetricRecordingSettings::MetricRecordingSettings(
+ bool cpu_time_for_each_task,
+ double task_thread_time_sampling_rate)
+ : records_cpu_time_for_each_task(base::ThreadTicks::IsSupported() &&
+ cpu_time_for_each_task),
+ task_sampling_rate_for_recording_cpu_time(
+ task_thread_time_sampling_rate) {
+ if (records_cpu_time_for_each_task)
+ task_sampling_rate_for_recording_cpu_time = 1;
+ if (!base::ThreadTicks::IsSupported())
+ task_sampling_rate_for_recording_cpu_time = 0;
+}
+
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/sequence_manager.h b/base/task/sequence_manager/sequence_manager.h
new file mode 100644
index 0000000000..41e56ec03a
--- /dev/null
+++ b/base/task/sequence_manager/sequence_manager.h
@@ -0,0 +1,132 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_SEQUENCE_MANAGER_H_
+#define BASE_TASK_SEQUENCE_MANAGER_SEQUENCE_MANAGER_H_
+
+#include <memory>
+#include <utility>
+
+#include "base/message_loop/message_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task/sequence_manager/task_queue_impl.h"
+#include "base/task/sequence_manager/task_time_observer.h"
+
+namespace base {
+namespace sequence_manager {
+
+class TimeDomain;
+
+// SequenceManager manages TaskQueues which have different properties
+// (e.g. priority, common task type) multiplexing all posted tasks into
+// a single backing sequence (currently bound to a single thread, which is
+// refererred as *main thread* in the comments below). SequenceManager
+// implementation can be used in a various ways to apply scheduling logic.
+class SequenceManager {
+ public:
+ class Observer {
+ public:
+ virtual ~Observer() = default;
+ // Called back on the main thread.
+ virtual void OnBeginNestedRunLoop() = 0;
+ virtual void OnExitNestedRunLoop() = 0;
+ };
+
+ struct MetricRecordingSettings {
+ MetricRecordingSettings();
+ // Note: These parameters are desired and MetricRecordingSetting's will
+ // update them for consistency (e.g. setting values to false when
+ // ThreadTicks are not supported).
+ MetricRecordingSettings(bool records_cpu_time_for_each_task,
+ double task_sampling_rate_for_recording_cpu_time);
+
+ // True if cpu time is measured for each task, so the integral
+ // metrics (as opposed to per-task metrics) can be recorded.
+ bool records_cpu_time_for_each_task = false;
+ // The proportion of the tasks for which the cpu time will be
+ // sampled or 0 if this is not enabled.
+ // This value is always 1 if the |records_cpu_time_for_each_task| is true.
+ double task_sampling_rate_for_recording_cpu_time = 0;
+ };
+
+ virtual ~SequenceManager() = default;
+
+ // TODO(kraynov): Bring back CreateOnCurrentThread static method here
+ // when the move is done. It's not here yet to reduce PLATFORM_EXPORT
+ // macros hacking during the move.
+
+ // Must be called on the main thread.
+ // Can be called only once, before creating TaskQueues.
+ // Observer must outlive the SequenceManager.
+ virtual void SetObserver(Observer* observer) = 0;
+
+ // Must be called on the main thread.
+ virtual void AddTaskObserver(MessageLoop::TaskObserver* task_observer) = 0;
+ virtual void RemoveTaskObserver(MessageLoop::TaskObserver* task_observer) = 0;
+ virtual void AddTaskTimeObserver(TaskTimeObserver* task_time_observer) = 0;
+ virtual void RemoveTaskTimeObserver(TaskTimeObserver* task_time_observer) = 0;
+
+ // Registers a TimeDomain with SequenceManager.
+ // TaskQueues must only be created with a registered TimeDomain.
+ // Conversely, any TimeDomain must remain registered until no
+ // TaskQueues (using that TimeDomain) remain.
+ virtual void RegisterTimeDomain(TimeDomain* time_domain) = 0;
+ virtual void UnregisterTimeDomain(TimeDomain* time_domain) = 0;
+
+ virtual TimeDomain* GetRealTimeDomain() const = 0;
+ virtual const TickClock* GetTickClock() const = 0;
+ virtual TimeTicks NowTicks() const = 0;
+
+ // Sets the SingleThreadTaskRunner that will be returned by
+ // ThreadTaskRunnerHandle::Get on the main thread.
+ virtual void SetDefaultTaskRunner(
+ scoped_refptr<SingleThreadTaskRunner> task_runner) = 0;
+
+ // Removes all canceled delayed tasks.
+ virtual void SweepCanceledDelayedTasks() = 0;
+
+ // Returns true if no tasks were executed in TaskQueues that monitor
+ // quiescence since the last call to this method.
+ virtual bool GetAndClearSystemIsQuiescentBit() = 0;
+
+ // Set the number of tasks executed in a single SequenceManager invocation.
+ // Increasing this number reduces the overhead of the tasks dispatching
+ // logic at the cost of a potentially worse latency. 1 by default.
+ virtual void SetWorkBatchSize(int work_batch_size) = 0;
+
+ // Enables crash keys that can be set in the scope of a task which help
+ // to identify the culprit if upcoming work results in a crash.
+ // Key names must be thread-specific to avoid races and corrupted crash dumps.
+ virtual void EnableCrashKeys(const char* file_name_crash_key,
+ const char* function_name_crash_key) = 0;
+
+ // Returns the metric recording configuration for the current SequenceManager.
+ virtual const MetricRecordingSettings& GetMetricRecordingSettings() const = 0;
+
+ // Creates a task queue with the given type, |spec| and args.
+ // Must be called on the main thread.
+ // TODO(scheduler-dev): SequenceManager should not create TaskQueues.
+ template <typename TaskQueueType, typename... Args>
+ scoped_refptr<TaskQueueType> CreateTaskQueue(const TaskQueue::Spec& spec,
+ Args&&... args) {
+ return WrapRefCounted(new TaskQueueType(CreateTaskQueueImpl(spec), spec,
+ std::forward<Args>(args)...));
+ }
+
+ protected:
+ virtual std::unique_ptr<internal::TaskQueueImpl> CreateTaskQueueImpl(
+ const TaskQueue::Spec& spec) = 0;
+};
+
+// Create SequenceManager using MessageLoop on the current thread.
+// Implementation is located in sequence_manager_impl.cc.
+// TODO(scheduler-dev): Rename to TakeOverCurrentThread when we'll stop using
+// MessageLoop and will actually take over a thread.
+BASE_EXPORT std::unique_ptr<SequenceManager>
+CreateSequenceManagerOnCurrentThread();
+
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_SEQUENCE_MANAGER_H_
diff --git a/base/task/sequence_manager/sequence_manager_impl.cc b/base/task/sequence_manager/sequence_manager_impl.cc
new file mode 100644
index 0000000000..7afea9c3fc
--- /dev/null
+++ b/base/task/sequence_manager/sequence_manager_impl.cc
@@ -0,0 +1,724 @@
+// 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.
+
+#include "base/task/sequence_manager/sequence_manager_impl.h"
+
+#include <queue>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bit_cast.h"
+#include "base/compiler_specific.h"
+#include "base/debug/crash_logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/rand_util.h"
+#include "base/task/sequence_manager/real_time_domain.h"
+#include "base/task/sequence_manager/task_time_observer.h"
+#include "base/task/sequence_manager/thread_controller_impl.h"
+#include "base/task/sequence_manager/work_queue.h"
+#include "base/task/sequence_manager/work_queue_sets.h"
+#include "base/time/default_tick_clock.h"
+#include "base/time/tick_clock.h"
+#include "base/trace_event/trace_event.h"
+#include "build/build_config.h"
+
+namespace base {
+namespace sequence_manager {
+
+std::unique_ptr<SequenceManager> CreateSequenceManagerOnCurrentThread() {
+ return internal::SequenceManagerImpl::CreateOnCurrentThread();
+}
+
+namespace internal {
+
+namespace {
+
+constexpr base::TimeDelta kLongTaskTraceEventThreshold =
+ base::TimeDelta::FromMilliseconds(50);
+// Proportion of tasks which will record thread time for metrics.
+const double kTaskSamplingRateForRecordingCPUTime = 0.01;
+// Proprortion of SequenceManagers which will record thread time for each task,
+// enabling advanced metrics.
+const double kThreadSamplingRateForRecordingCPUTime = 0.0001;
+
+// Magic value to protect against memory corruption and bail out
+// early when detected.
+constexpr int kMemoryCorruptionSentinelValue = 0xdeadbeef;
+
+void SweepCanceledDelayedTasksInQueue(
+ internal::TaskQueueImpl* queue,
+ std::map<TimeDomain*, TimeTicks>* time_domain_now) {
+ TimeDomain* time_domain = queue->GetTimeDomain();
+ if (time_domain_now->find(time_domain) == time_domain_now->end())
+ time_domain_now->insert(std::make_pair(time_domain, time_domain->Now()));
+ queue->SweepCanceledDelayedTasks(time_domain_now->at(time_domain));
+}
+
+SequenceManager::MetricRecordingSettings InitializeMetricRecordingSettings() {
+ bool cpu_time_recording_always_on =
+ base::RandDouble() < kThreadSamplingRateForRecordingCPUTime;
+ return SequenceManager::MetricRecordingSettings(
+ cpu_time_recording_always_on, kTaskSamplingRateForRecordingCPUTime);
+}
+
+} // namespace
+
+SequenceManagerImpl::SequenceManagerImpl(
+ std::unique_ptr<internal::ThreadController> controller)
+ : graceful_shutdown_helper_(new internal::GracefulQueueShutdownHelper()),
+ controller_(std::move(controller)),
+ metric_recording_settings_(InitializeMetricRecordingSettings()),
+ memory_corruption_sentinel_(kMemoryCorruptionSentinelValue),
+ weak_factory_(this) {
+ // TODO(altimin): Create a sequence checker here.
+ DCHECK(controller_->RunsTasksInCurrentSequence());
+
+ TRACE_EVENT_WARMUP_CATEGORY("sequence_manager");
+ TRACE_EVENT_WARMUP_CATEGORY(TRACE_DISABLED_BY_DEFAULT("sequence_manager"));
+ TRACE_EVENT_WARMUP_CATEGORY(
+ TRACE_DISABLED_BY_DEFAULT("sequence_manager.debug"));
+ TRACE_EVENT_WARMUP_CATEGORY(
+ TRACE_DISABLED_BY_DEFAULT("sequence_manager.verbose_snapshots"));
+
+ TRACE_EVENT_OBJECT_CREATED_WITH_ID(
+ TRACE_DISABLED_BY_DEFAULT("sequence_manager"), "SequenceManager", this);
+ main_thread_only().selector.SetTaskQueueSelectorObserver(this);
+
+ RegisterTimeDomain(main_thread_only().real_time_domain.get());
+
+ controller_->SetSequencedTaskSource(this);
+ controller_->AddNestingObserver(this);
+}
+
+SequenceManagerImpl::~SequenceManagerImpl() {
+ TRACE_EVENT_OBJECT_DELETED_WITH_ID(
+ TRACE_DISABLED_BY_DEFAULT("sequence_manager"), "SequenceManager", this);
+
+ // TODO(altimin): restore default task runner automatically when
+ // ThreadController is destroyed.
+ controller_->RestoreDefaultTaskRunner();
+
+ for (internal::TaskQueueImpl* queue : main_thread_only().active_queues) {
+ main_thread_only().selector.RemoveQueue(queue);
+ queue->UnregisterTaskQueue();
+ }
+
+ main_thread_only().active_queues.clear();
+ main_thread_only().queues_to_gracefully_shutdown.clear();
+
+ graceful_shutdown_helper_->OnSequenceManagerDeleted();
+
+ main_thread_only().selector.SetTaskQueueSelectorObserver(nullptr);
+ controller_->RemoveNestingObserver(this);
+}
+
+SequenceManagerImpl::AnyThread::AnyThread() = default;
+
+SequenceManagerImpl::AnyThread::~AnyThread() = default;
+
+SequenceManagerImpl::MainThreadOnly::MainThreadOnly()
+ : random_generator(RandUint64()),
+ uniform_distribution(0.0, 1.0),
+ real_time_domain(new internal::RealTimeDomain()) {}
+
+SequenceManagerImpl::MainThreadOnly::~MainThreadOnly() = default;
+
+// static
+std::unique_ptr<SequenceManagerImpl>
+SequenceManagerImpl::CreateOnCurrentThread() {
+ return WrapUnique(
+ new SequenceManagerImpl(internal::ThreadControllerImpl::Create(
+ MessageLoop::current(), DefaultTickClock::GetInstance())));
+}
+
+void SequenceManagerImpl::RegisterTimeDomain(TimeDomain* time_domain) {
+ main_thread_only().time_domains.insert(time_domain);
+ time_domain->OnRegisterWithSequenceManager(this);
+}
+
+void SequenceManagerImpl::UnregisterTimeDomain(TimeDomain* time_domain) {
+ main_thread_only().time_domains.erase(time_domain);
+}
+
+TimeDomain* SequenceManagerImpl::GetRealTimeDomain() const {
+ return main_thread_only().real_time_domain.get();
+}
+
+std::unique_ptr<internal::TaskQueueImpl>
+SequenceManagerImpl::CreateTaskQueueImpl(const TaskQueue::Spec& spec) {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ TimeDomain* time_domain = spec.time_domain
+ ? spec.time_domain
+ : main_thread_only().real_time_domain.get();
+ DCHECK(main_thread_only().time_domains.find(time_domain) !=
+ main_thread_only().time_domains.end());
+ std::unique_ptr<internal::TaskQueueImpl> task_queue =
+ std::make_unique<internal::TaskQueueImpl>(this, time_domain, spec);
+ main_thread_only().active_queues.insert(task_queue.get());
+ main_thread_only().selector.AddQueue(task_queue.get());
+ return task_queue;
+}
+
+void SequenceManagerImpl::SetObserver(Observer* observer) {
+ main_thread_only().observer = observer;
+}
+
+bool SequenceManagerImpl::AddToIncomingImmediateWorkList(
+ internal::TaskQueueImpl* task_queue,
+ internal::EnqueueOrder enqueue_order) {
+ AutoLock lock(any_thread_lock_);
+ // Check if |task_queue| is already in the linked list.
+ if (task_queue->immediate_work_list_storage()->queue)
+ return false;
+
+ // Insert into the linked list.
+ task_queue->immediate_work_list_storage()->queue = task_queue;
+ task_queue->immediate_work_list_storage()->order = enqueue_order;
+ task_queue->immediate_work_list_storage()->next =
+ any_thread().incoming_immediate_work_list;
+ any_thread().incoming_immediate_work_list =
+ task_queue->immediate_work_list_storage();
+ return true;
+}
+
+void SequenceManagerImpl::RemoveFromIncomingImmediateWorkList(
+ internal::TaskQueueImpl* task_queue) {
+ AutoLock lock(any_thread_lock_);
+ internal::IncomingImmediateWorkList** prev =
+ &any_thread().incoming_immediate_work_list;
+ while (*prev) {
+ if ((*prev)->queue == task_queue) {
+ *prev = (*prev)->next;
+ break;
+ }
+ prev = &(*prev)->next;
+ }
+
+ task_queue->immediate_work_list_storage()->next = nullptr;
+ task_queue->immediate_work_list_storage()->queue = nullptr;
+}
+
+void SequenceManagerImpl::UnregisterTaskQueueImpl(
+ std::unique_ptr<internal::TaskQueueImpl> task_queue) {
+ TRACE_EVENT1("sequence_manager", "SequenceManagerImpl::UnregisterTaskQueue",
+ "queue_name", task_queue->GetName());
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+
+ main_thread_only().selector.RemoveQueue(task_queue.get());
+
+ // After UnregisterTaskQueue returns no new tasks can be posted.
+ // It's important to call it first to avoid race condition between removing
+ // the task queue from various lists here and adding it to the same lists
+ // when posting a task.
+ task_queue->UnregisterTaskQueue();
+
+ // Remove |task_queue| from the linked list if present.
+ // This is O(n). We assume this will be a relatively infrequent operation.
+ RemoveFromIncomingImmediateWorkList(task_queue.get());
+
+ // Add |task_queue| to |main_thread_only().queues_to_delete| so we can prevent
+ // it from being freed while any of our structures hold hold a raw pointer to
+ // it.
+ main_thread_only().active_queues.erase(task_queue.get());
+ main_thread_only().queues_to_delete[task_queue.get()] = std::move(task_queue);
+}
+
+void SequenceManagerImpl::ReloadEmptyWorkQueues() {
+ // There are two cases where a queue needs reloading. First, it might be
+ // completely empty and we've just posted a task (this method handles that
+ // case). Secondly if the work queue becomes empty in when calling
+ // WorkQueue::TakeTaskFromWorkQueue (handled there).
+ for (internal::TaskQueueImpl* queue : main_thread_only().queues_to_reload) {
+ queue->ReloadImmediateWorkQueueIfEmpty();
+ }
+}
+
+void SequenceManagerImpl::WakeUpReadyDelayedQueues(LazyNow* lazy_now) {
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
+ "SequenceManagerImpl::WakeUpReadyDelayedQueues");
+
+ for (TimeDomain* time_domain : main_thread_only().time_domains) {
+ if (time_domain == main_thread_only().real_time_domain.get()) {
+ time_domain->WakeUpReadyDelayedQueues(lazy_now);
+ } else {
+ LazyNow time_domain_lazy_now = time_domain->CreateLazyNow();
+ time_domain->WakeUpReadyDelayedQueues(&time_domain_lazy_now);
+ }
+ }
+}
+
+void SequenceManagerImpl::OnBeginNestedRunLoop() {
+ main_thread_only().nesting_depth++;
+ if (main_thread_only().observer)
+ main_thread_only().observer->OnBeginNestedRunLoop();
+}
+
+void SequenceManagerImpl::OnExitNestedRunLoop() {
+ main_thread_only().nesting_depth--;
+ DCHECK_GE(main_thread_only().nesting_depth, 0);
+ if (main_thread_only().nesting_depth == 0) {
+ // While we were nested some non-nestable tasks may have been deferred.
+ // We push them back onto the *front* of their original work queues,
+ // that's why we iterate |non_nestable_task_queue| in FIFO order.
+ while (!main_thread_only().non_nestable_task_queue.empty()) {
+ internal::TaskQueueImpl::DeferredNonNestableTask& non_nestable_task =
+ main_thread_only().non_nestable_task_queue.back();
+ non_nestable_task.task_queue->RequeueDeferredNonNestableTask(
+ std::move(non_nestable_task));
+ main_thread_only().non_nestable_task_queue.pop_back();
+ }
+ }
+ if (main_thread_only().observer)
+ main_thread_only().observer->OnExitNestedRunLoop();
+}
+
+void SequenceManagerImpl::OnQueueHasIncomingImmediateWork(
+ internal::TaskQueueImpl* queue,
+ internal::EnqueueOrder enqueue_order,
+ bool queue_is_blocked) {
+ if (AddToIncomingImmediateWorkList(queue, enqueue_order) && !queue_is_blocked)
+ controller_->ScheduleWork();
+}
+
+void SequenceManagerImpl::MaybeScheduleImmediateWork(
+ const Location& from_here) {
+ controller_->ScheduleWork();
+}
+
+void SequenceManagerImpl::SetNextDelayedDoWork(LazyNow* lazy_now,
+ TimeTicks run_time) {
+ controller_->SetNextDelayedDoWork(lazy_now, run_time);
+}
+
+Optional<PendingTask> SequenceManagerImpl::TakeTask() {
+ CHECK(Validate());
+
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ TRACE_EVENT0("sequence_manager", "SequenceManagerImpl::TakeTask");
+
+ {
+ AutoLock lock(any_thread_lock_);
+ main_thread_only().queues_to_reload.clear();
+
+ for (internal::IncomingImmediateWorkList* iter =
+ any_thread().incoming_immediate_work_list;
+ iter; iter = iter->next) {
+ main_thread_only().queues_to_reload.push_back(iter->queue);
+ iter->queue = nullptr;
+ }
+
+ any_thread().incoming_immediate_work_list = nullptr;
+ }
+
+ // It's important we call ReloadEmptyWorkQueues out side of the lock to
+ // avoid a lock order inversion.
+ ReloadEmptyWorkQueues();
+ LazyNow lazy_now(controller_->GetClock());
+ WakeUpReadyDelayedQueues(&lazy_now);
+
+ while (true) {
+ internal::WorkQueue* work_queue = nullptr;
+ bool should_run =
+ main_thread_only().selector.SelectWorkQueueToService(&work_queue);
+ TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
+ TRACE_DISABLED_BY_DEFAULT("sequence_manager.debug"), "SequenceManager",
+ this, AsValueWithSelectorResult(should_run, work_queue));
+
+ if (!should_run)
+ return nullopt;
+
+ // If the head task was canceled, remove it and run the selector again.
+ if (work_queue->RemoveAllCanceledTasksFromFront())
+ continue;
+
+ if (work_queue->GetFrontTask()->nestable == Nestable::kNonNestable &&
+ main_thread_only().nesting_depth > 0) {
+ // Defer non-nestable work. NOTE these tasks can be arbitrarily delayed so
+ // the additional delay should not be a problem.
+ // Note because we don't delete queues while nested, it's perfectly OK to
+ // store the raw pointer for |queue| here.
+ internal::TaskQueueImpl::DeferredNonNestableTask deferred_task{
+ work_queue->TakeTaskFromWorkQueue(), work_queue->task_queue(),
+ work_queue->queue_type()};
+ main_thread_only().non_nestable_task_queue.push_back(
+ std::move(deferred_task));
+ continue;
+ }
+
+ main_thread_only().task_execution_stack.emplace_back(
+ work_queue->TakeTaskFromWorkQueue(), work_queue->task_queue(),
+ InitializeTaskTiming(work_queue->task_queue()));
+
+ UMA_HISTOGRAM_COUNTS_1000("TaskQueueManager.ActiveQueuesCount",
+ main_thread_only().active_queues.size());
+
+ ExecutingTask& executing_task =
+ *main_thread_only().task_execution_stack.rbegin();
+ NotifyWillProcessTask(&executing_task, &lazy_now);
+ return std::move(executing_task.pending_task);
+ }
+}
+
+void SequenceManagerImpl::DidRunTask() {
+ LazyNow lazy_now(controller_->GetClock());
+ ExecutingTask& executing_task =
+ *main_thread_only().task_execution_stack.rbegin();
+ NotifyDidProcessTask(&executing_task, &lazy_now);
+ main_thread_only().task_execution_stack.pop_back();
+
+ if (main_thread_only().nesting_depth == 0)
+ CleanUpQueues();
+}
+
+TimeDelta SequenceManagerImpl::DelayTillNextTask(LazyNow* lazy_now) {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+
+ // If the selector has non-empty queues we trivially know there is immediate
+ // work to be done.
+ if (!main_thread_only().selector.AllEnabledWorkQueuesAreEmpty())
+ return TimeDelta();
+
+ // Its possible the selectors state is dirty because ReloadEmptyWorkQueues
+ // hasn't been called yet. This check catches the case of fresh incoming work.
+ {
+ AutoLock lock(any_thread_lock_);
+ for (const internal::IncomingImmediateWorkList* iter =
+ any_thread().incoming_immediate_work_list;
+ iter; iter = iter->next) {
+ if (iter->queue->CouldTaskRun(iter->order))
+ return TimeDelta();
+ }
+ }
+
+ // Otherwise we need to find the shortest delay, if any. NB we don't need to
+ // call WakeUpReadyDelayedQueues because it's assumed DelayTillNextTask will
+ // return TimeDelta>() if the delayed task is due to run now.
+ TimeDelta delay_till_next_task = TimeDelta::Max();
+ for (TimeDomain* time_domain : main_thread_only().time_domains) {
+ Optional<TimeDelta> delay = time_domain->DelayTillNextTask(lazy_now);
+ if (!delay)
+ continue;
+
+ if (*delay < delay_till_next_task)
+ delay_till_next_task = *delay;
+ }
+ return delay_till_next_task;
+}
+
+void SequenceManagerImpl::WillQueueTask(
+ internal::TaskQueueImpl::Task* pending_task) {
+ controller_->WillQueueTask(pending_task);
+}
+
+TaskQueue::TaskTiming SequenceManagerImpl::InitializeTaskTiming(
+ internal::TaskQueueImpl* task_queue) {
+ bool records_wall_time =
+ (task_queue->GetShouldNotifyObservers() &&
+ main_thread_only().task_time_observers.might_have_observers()) ||
+ task_queue->RequiresTaskTiming();
+ bool records_thread_time = records_wall_time && ShouldRecordCPUTimeForTask();
+ return TaskQueue::TaskTiming(records_wall_time, records_thread_time);
+}
+
+void SequenceManagerImpl::NotifyWillProcessTask(ExecutingTask* executing_task,
+ LazyNow* time_before_task) {
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
+ "SequenceManagerImpl::NotifyWillProcessTaskObservers");
+ if (executing_task->task_queue->GetQuiescenceMonitored())
+ main_thread_only().task_was_run_on_quiescence_monitored_queue = true;
+
+#if !defined(OS_NACL)
+ debug::SetCrashKeyString(
+ main_thread_only().file_name_crash_key,
+ executing_task->pending_task.posted_from.file_name());
+ debug::SetCrashKeyString(
+ main_thread_only().function_name_crash_key,
+ executing_task->pending_task.posted_from.function_name());
+#endif // OS_NACL
+
+ executing_task->task_timing.RecordTaskStart(time_before_task);
+
+ if (!executing_task->task_queue->GetShouldNotifyObservers())
+ return;
+
+ {
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
+ "SequenceManager.WillProcessTaskObservers");
+ for (auto& observer : main_thread_only().task_observers)
+ observer.WillProcessTask(executing_task->pending_task);
+ }
+
+ {
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
+ "SequenceManager.QueueNotifyWillProcessTask");
+ executing_task->task_queue->NotifyWillProcessTask(
+ executing_task->pending_task);
+ }
+
+ bool notify_time_observers =
+ main_thread_only().task_time_observers.might_have_observers() ||
+ executing_task->task_queue->RequiresTaskTiming();
+
+ if (!notify_time_observers)
+ return;
+
+ if (main_thread_only().nesting_depth == 0) {
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
+ "SequenceManager.WillProcessTaskTimeObservers");
+ for (auto& observer : main_thread_only().task_time_observers)
+ observer.WillProcessTask(executing_task->task_timing.start_time());
+ }
+
+ {
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
+ "SequenceManager.QueueOnTaskStarted");
+ executing_task->task_queue->OnTaskStarted(executing_task->pending_task,
+ executing_task->task_timing);
+ }
+}
+
+void SequenceManagerImpl::NotifyDidProcessTask(ExecutingTask* executing_task,
+ LazyNow* time_after_task) {
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
+ "SequenceManagerImpl::NotifyDidProcessTaskObservers");
+
+ executing_task->task_timing.RecordTaskEnd(time_after_task);
+
+ const TaskQueue::TaskTiming& task_timing = executing_task->task_timing;
+
+ if (!executing_task->task_queue->GetShouldNotifyObservers())
+ return;
+
+ if (task_timing.has_wall_time() && main_thread_only().nesting_depth == 0) {
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
+ "SequenceManager.DidProcessTaskTimeObservers");
+ for (auto& observer : main_thread_only().task_time_observers) {
+ observer.DidProcessTask(task_timing.start_time(), task_timing.end_time());
+ }
+ }
+
+ {
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
+ "SequenceManager.DidProcessTaskObservers");
+ for (auto& observer : main_thread_only().task_observers)
+ observer.DidProcessTask(executing_task->pending_task);
+ }
+
+ {
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
+ "SequenceManager.QueueNotifyDidProcessTask");
+ executing_task->task_queue->NotifyDidProcessTask(
+ executing_task->pending_task);
+ }
+
+ {
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
+ "SequenceManager.QueueOnTaskCompleted");
+ if (task_timing.has_wall_time())
+ executing_task->task_queue->OnTaskCompleted(executing_task->pending_task,
+ task_timing);
+ }
+
+ // TODO(altimin): Move this back to blink.
+ if (task_timing.has_wall_time() &&
+ task_timing.wall_duration() > kLongTaskTraceEventThreshold &&
+ main_thread_only().nesting_depth == 0) {
+ TRACE_EVENT_INSTANT1("blink", "LongTask", TRACE_EVENT_SCOPE_THREAD,
+ "duration", task_timing.wall_duration().InSecondsF());
+ }
+}
+
+void SequenceManagerImpl::SetWorkBatchSize(int work_batch_size) {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ DCHECK_GE(work_batch_size, 1);
+ controller_->SetWorkBatchSize(work_batch_size);
+}
+
+void SequenceManagerImpl::AddTaskObserver(
+ MessageLoop::TaskObserver* task_observer) {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ main_thread_only().task_observers.AddObserver(task_observer);
+}
+
+void SequenceManagerImpl::RemoveTaskObserver(
+ MessageLoop::TaskObserver* task_observer) {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ main_thread_only().task_observers.RemoveObserver(task_observer);
+}
+
+void SequenceManagerImpl::AddTaskTimeObserver(
+ TaskTimeObserver* task_time_observer) {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ main_thread_only().task_time_observers.AddObserver(task_time_observer);
+}
+
+void SequenceManagerImpl::RemoveTaskTimeObserver(
+ TaskTimeObserver* task_time_observer) {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ main_thread_only().task_time_observers.RemoveObserver(task_time_observer);
+}
+
+bool SequenceManagerImpl::GetAndClearSystemIsQuiescentBit() {
+ bool task_was_run =
+ main_thread_only().task_was_run_on_quiescence_monitored_queue;
+ main_thread_only().task_was_run_on_quiescence_monitored_queue = false;
+ return !task_was_run;
+}
+
+internal::EnqueueOrder SequenceManagerImpl::GetNextSequenceNumber() {
+ return enqueue_order_generator_.GenerateNext();
+}
+
+std::unique_ptr<trace_event::ConvertableToTraceFormat>
+SequenceManagerImpl::AsValueWithSelectorResult(
+ bool should_run,
+ internal::WorkQueue* selected_work_queue) const {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ std::unique_ptr<trace_event::TracedValue> state(
+ new trace_event::TracedValue());
+ TimeTicks now = NowTicks();
+ state->BeginArray("active_queues");
+ for (auto* const queue : main_thread_only().active_queues)
+ queue->AsValueInto(now, state.get());
+ state->EndArray();
+ state->BeginArray("queues_to_gracefully_shutdown");
+ for (const auto& pair : main_thread_only().queues_to_gracefully_shutdown)
+ pair.first->AsValueInto(now, state.get());
+ state->EndArray();
+ state->BeginArray("queues_to_delete");
+ for (const auto& pair : main_thread_only().queues_to_delete)
+ pair.first->AsValueInto(now, state.get());
+ state->EndArray();
+ state->BeginDictionary("selector");
+ main_thread_only().selector.AsValueInto(state.get());
+ state->EndDictionary();
+ if (should_run) {
+ state->SetString("selected_queue",
+ selected_work_queue->task_queue()->GetName());
+ state->SetString("work_queue_name", selected_work_queue->name());
+ }
+
+ state->BeginArray("time_domains");
+ for (auto* time_domain : main_thread_only().time_domains)
+ time_domain->AsValueInto(state.get());
+ state->EndArray();
+ {
+ AutoLock lock(any_thread_lock_);
+ state->BeginArray("has_incoming_immediate_work");
+ for (const internal::IncomingImmediateWorkList* iter =
+ any_thread().incoming_immediate_work_list;
+ iter; iter = iter->next) {
+ state->AppendString(iter->queue->GetName());
+ }
+ state->EndArray();
+ }
+ return std::move(state);
+}
+
+void SequenceManagerImpl::OnTaskQueueEnabled(internal::TaskQueueImpl* queue) {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ DCHECK(queue->IsQueueEnabled());
+ // Only schedule DoWork if there's something to do.
+ if (queue->HasTaskToRunImmediately() && !queue->BlockedByFence())
+ MaybeScheduleImmediateWork(FROM_HERE);
+}
+
+void SequenceManagerImpl::SweepCanceledDelayedTasks() {
+ std::map<TimeDomain*, TimeTicks> time_domain_now;
+ for (auto* const queue : main_thread_only().active_queues)
+ SweepCanceledDelayedTasksInQueue(queue, &time_domain_now);
+ for (const auto& pair : main_thread_only().queues_to_gracefully_shutdown)
+ SweepCanceledDelayedTasksInQueue(pair.first, &time_domain_now);
+}
+
+void SequenceManagerImpl::TakeQueuesToGracefullyShutdownFromHelper() {
+ std::vector<std::unique_ptr<internal::TaskQueueImpl>> queues =
+ graceful_shutdown_helper_->TakeQueues();
+ for (std::unique_ptr<internal::TaskQueueImpl>& queue : queues) {
+ main_thread_only().queues_to_gracefully_shutdown[queue.get()] =
+ std::move(queue);
+ }
+}
+
+void SequenceManagerImpl::CleanUpQueues() {
+ TakeQueuesToGracefullyShutdownFromHelper();
+
+ for (auto it = main_thread_only().queues_to_gracefully_shutdown.begin();
+ it != main_thread_only().queues_to_gracefully_shutdown.end();) {
+ if (it->first->IsEmpty()) {
+ UnregisterTaskQueueImpl(std::move(it->second));
+ main_thread_only().active_queues.erase(it->first);
+ main_thread_only().queues_to_gracefully_shutdown.erase(it++);
+ } else {
+ ++it;
+ }
+ }
+ main_thread_only().queues_to_delete.clear();
+}
+
+scoped_refptr<internal::GracefulQueueShutdownHelper>
+SequenceManagerImpl::GetGracefulQueueShutdownHelper() const {
+ return graceful_shutdown_helper_;
+}
+
+WeakPtr<SequenceManagerImpl> SequenceManagerImpl::GetWeakPtr() {
+ return weak_factory_.GetWeakPtr();
+}
+
+void SequenceManagerImpl::SetDefaultTaskRunner(
+ scoped_refptr<SingleThreadTaskRunner> task_runner) {
+ controller_->SetDefaultTaskRunner(task_runner);
+}
+
+const TickClock* SequenceManagerImpl::GetTickClock() const {
+ return controller_->GetClock();
+}
+
+TimeTicks SequenceManagerImpl::NowTicks() const {
+ return controller_->GetClock()->NowTicks();
+}
+
+bool SequenceManagerImpl::ShouldRecordCPUTimeForTask() {
+ return ThreadTicks::IsSupported() &&
+ main_thread_only().uniform_distribution(
+ main_thread_only().random_generator) <
+ metric_recording_settings_
+ .task_sampling_rate_for_recording_cpu_time;
+}
+
+const SequenceManager::MetricRecordingSettings&
+SequenceManagerImpl::GetMetricRecordingSettings() const {
+ return metric_recording_settings_;
+}
+
+MSVC_DISABLE_OPTIMIZE()
+bool SequenceManagerImpl::Validate() {
+ return memory_corruption_sentinel_ == kMemoryCorruptionSentinelValue;
+}
+MSVC_ENABLE_OPTIMIZE()
+
+void SequenceManagerImpl::EnableCrashKeys(
+ const char* file_name_crash_key_name,
+ const char* function_name_crash_key_name) {
+ DCHECK(!main_thread_only().file_name_crash_key);
+ DCHECK(!main_thread_only().function_name_crash_key);
+#if !defined(OS_NACL)
+ main_thread_only().file_name_crash_key = debug::AllocateCrashKeyString(
+ file_name_crash_key_name, debug::CrashKeySize::Size64);
+ main_thread_only().function_name_crash_key = debug::AllocateCrashKeyString(
+ function_name_crash_key_name, debug::CrashKeySize::Size64);
+#endif // OS_NACL
+}
+
+internal::TaskQueueImpl* SequenceManagerImpl::currently_executing_task_queue()
+ const {
+ if (main_thread_only().task_execution_stack.empty())
+ return nullptr;
+ return main_thread_only().task_execution_stack.rbegin()->task_queue;
+}
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/sequence_manager_impl.h b/base/task/sequence_manager/sequence_manager_impl.h
new file mode 100644
index 0000000000..b42dc72798
--- /dev/null
+++ b/base/task/sequence_manager/sequence_manager_impl.h
@@ -0,0 +1,341 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_SEQUENCE_MANAGER_IMPL_H_
+#define BASE_TASK_SEQUENCE_MANAGER_SEQUENCE_MANAGER_IMPL_H_
+
+#include <list>
+#include <map>
+#include <memory>
+#include <random>
+#include <set>
+#include <unordered_map>
+#include <utility>
+
+#include "base/atomic_sequence_num.h"
+#include "base/cancelable_callback.h"
+#include "base/containers/circular_deque.h"
+#include "base/debug/task_annotator.h"
+#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/pending_task.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "base/task/sequence_manager/enqueue_order.h"
+#include "base/task/sequence_manager/graceful_queue_shutdown_helper.h"
+#include "base/task/sequence_manager/moveable_auto_lock.h"
+#include "base/task/sequence_manager/sequence_manager.h"
+#include "base/task/sequence_manager/task_queue_impl.h"
+#include "base/task/sequence_manager/task_queue_selector.h"
+#include "base/task/sequence_manager/thread_controller.h"
+#include "base/threading/thread_checker.h"
+
+namespace base {
+
+namespace debug {
+struct CrashKeyString;
+} // namespace debug
+
+namespace trace_event {
+class ConvertableToTraceFormat;
+} // namespace trace_event
+
+namespace sequence_manager {
+
+class SequenceManagerForTest;
+class TaskQueue;
+class TaskTimeObserver;
+class TimeDomain;
+
+namespace internal {
+
+class RealTimeDomain;
+class TaskQueueImpl;
+
+// The task queue manager provides N task queues and a selector interface for
+// choosing which task queue to service next. Each task queue consists of two
+// sub queues:
+//
+// 1. Incoming task queue. Tasks that are posted get immediately appended here.
+// When a task is appended into an empty incoming queue, the task manager
+// work function (DoWork()) is scheduled to run on the main task runner.
+//
+// 2. Work queue. If a work queue is empty when DoWork() is entered, tasks from
+// the incoming task queue (if any) are moved here. The work queues are
+// registered with the selector as input to the scheduling decision.
+//
+class BASE_EXPORT SequenceManagerImpl
+ : public SequenceManager,
+ public internal::SequencedTaskSource,
+ public internal::TaskQueueSelector::Observer,
+ public RunLoop::NestingObserver {
+ public:
+ using Observer = SequenceManager::Observer;
+
+ ~SequenceManagerImpl() override;
+
+ // Assume direct control over current thread and create a SequenceManager.
+ // This function should be called only once per thread.
+ // This function assumes that a MessageLoop is initialized for
+ // the current thread.
+ static std::unique_ptr<SequenceManagerImpl> CreateOnCurrentThread();
+
+ // SequenceManager implementation:
+ void SetObserver(Observer* observer) override;
+ void AddTaskObserver(MessageLoop::TaskObserver* task_observer) override;
+ void RemoveTaskObserver(MessageLoop::TaskObserver* task_observer) override;
+ void AddTaskTimeObserver(TaskTimeObserver* task_time_observer) override;
+ void RemoveTaskTimeObserver(TaskTimeObserver* task_time_observer) override;
+ void RegisterTimeDomain(TimeDomain* time_domain) override;
+ void UnregisterTimeDomain(TimeDomain* time_domain) override;
+ TimeDomain* GetRealTimeDomain() const override;
+ const TickClock* GetTickClock() const override;
+ TimeTicks NowTicks() const override;
+ void SetDefaultTaskRunner(
+ scoped_refptr<SingleThreadTaskRunner> task_runner) override;
+ void SweepCanceledDelayedTasks() override;
+ bool GetAndClearSystemIsQuiescentBit() override;
+ void SetWorkBatchSize(int work_batch_size) override;
+ void EnableCrashKeys(const char* file_name_crash_key,
+ const char* function_name_crash_key) override;
+ const MetricRecordingSettings& GetMetricRecordingSettings() const override;
+
+ // Implementation of SequencedTaskSource:
+ Optional<PendingTask> TakeTask() override;
+ void DidRunTask() override;
+ TimeDelta DelayTillNextTask(LazyNow* lazy_now) override;
+
+ // Requests that a task to process work is posted on the main task runner.
+ // These tasks are de-duplicated in two buckets: main-thread and all other
+ // threads. This distinction is done to reduce the overhead from locks, we
+ // assume the main-thread path will be hot.
+ void MaybeScheduleImmediateWork(const Location& from_here);
+
+ // Requests that a delayed task to process work is posted on the main task
+ // runner. These delayed tasks are de-duplicated. Must be called on the thread
+ // this class was created on.
+
+ // Schedules next wake-up at the given time, cancels any previous requests.
+ // Use TimeTicks::Max() to cancel a wake-up.
+ // Must be called from a TimeDomain only.
+ void SetNextDelayedDoWork(LazyNow* lazy_now, TimeTicks run_time);
+
+ // Returns the currently executing TaskQueue if any. Must be called on the
+ // thread this class was created on.
+ internal::TaskQueueImpl* currently_executing_task_queue() const;
+
+ // Unregisters a TaskQueue previously created by |NewTaskQueue()|.
+ // No tasks will run on this queue after this call.
+ void UnregisterTaskQueueImpl(
+ std::unique_ptr<internal::TaskQueueImpl> task_queue);
+
+ scoped_refptr<internal::GracefulQueueShutdownHelper>
+ GetGracefulQueueShutdownHelper() const;
+
+ WeakPtr<SequenceManagerImpl> GetWeakPtr();
+
+ protected:
+ // Create a task queue manager where |controller| controls the thread
+ // on which the tasks are eventually run.
+ explicit SequenceManagerImpl(
+ std::unique_ptr<internal::ThreadController> controller);
+
+ friend class internal::TaskQueueImpl;
+ friend class ::base::sequence_manager::SequenceManagerForTest;
+
+ private:
+ enum class ProcessTaskResult {
+ kDeferred,
+ kExecuted,
+ kSequenceManagerDeleted,
+ };
+
+ struct AnyThread {
+ AnyThread();
+ ~AnyThread();
+
+ // Task queues with newly available work on the incoming queue.
+ internal::IncomingImmediateWorkList* incoming_immediate_work_list = nullptr;
+ };
+
+ // SequenceManager maintains a queue of non-nestable tasks since they're
+ // uncommon and allocating an extra deque per TaskQueue will waste the memory.
+ using NonNestableTaskDeque =
+ circular_deque<internal::TaskQueueImpl::DeferredNonNestableTask>;
+
+ // We have to track rentrancy because we support nested runloops but the
+ // selector interface is unaware of those. This struct keeps track off all
+ // task related state needed to make pairs of TakeTask() / DidRunTask() work.
+ struct ExecutingTask {
+ ExecutingTask(internal::TaskQueueImpl::Task&& pending_task,
+ internal::TaskQueueImpl* task_queue,
+ TaskQueue::TaskTiming task_timing)
+ : pending_task(std::move(pending_task)),
+ task_queue(task_queue),
+ task_timing(task_timing) {}
+
+ internal::TaskQueueImpl::Task pending_task;
+ internal::TaskQueueImpl* task_queue = nullptr;
+ TaskQueue::TaskTiming task_timing;
+ };
+
+ struct MainThreadOnly {
+ MainThreadOnly();
+ ~MainThreadOnly();
+
+ int nesting_depth = 0;
+ NonNestableTaskDeque non_nestable_task_queue;
+ // TODO(altimin): Switch to instruction pointer crash key when it's
+ // available.
+ debug::CrashKeyString* file_name_crash_key = nullptr;
+ debug::CrashKeyString* function_name_crash_key = nullptr;
+
+ std::mt19937_64 random_generator;
+ std::uniform_real_distribution<double> uniform_distribution;
+
+ internal::TaskQueueSelector selector;
+ ObserverList<MessageLoop::TaskObserver> task_observers;
+ ObserverList<TaskTimeObserver> task_time_observers;
+ std::set<TimeDomain*> time_domains;
+ std::unique_ptr<internal::RealTimeDomain> real_time_domain;
+
+ // List of task queues managed by this SequenceManager.
+ // - active_queues contains queues that are still running tasks.
+ // Most often they are owned by relevant TaskQueues, but
+ // queues_to_gracefully_shutdown_ are included here too.
+ // - queues_to_gracefully_shutdown contains queues which should be deleted
+ // when they become empty.
+ // - queues_to_delete contains soon-to-be-deleted queues, because some
+ // internal scheduling code does not expect queues to be pulled
+ // from underneath.
+
+ std::set<internal::TaskQueueImpl*> active_queues;
+ std::map<internal::TaskQueueImpl*, std::unique_ptr<internal::TaskQueueImpl>>
+ queues_to_gracefully_shutdown;
+ std::map<internal::TaskQueueImpl*, std::unique_ptr<internal::TaskQueueImpl>>
+ queues_to_delete;
+
+ // Scratch space used to store the contents of
+ // any_thread().incoming_immediate_work_list for use by
+ // ReloadEmptyWorkQueues. We keep hold of this vector to avoid unnecessary
+ // memory allocations.
+ std::vector<internal::TaskQueueImpl*> queues_to_reload;
+
+ bool task_was_run_on_quiescence_monitored_queue = false;
+
+ // Due to nested runloops more than one task can be executing concurrently.
+ std::list<ExecutingTask> task_execution_stack;
+
+ Observer* observer = nullptr; // NOT OWNED
+ };
+
+ // TaskQueueSelector::Observer:
+ void OnTaskQueueEnabled(internal::TaskQueueImpl* queue) override;
+
+ // RunLoop::NestingObserver:
+ void OnBeginNestedRunLoop() override;
+ void OnExitNestedRunLoop() override;
+
+ // Called by the task queue to inform this SequenceManager of a task that's
+ // about to be queued. This SequenceManager may use this opportunity to add
+ // metadata to |pending_task| before it is moved into the queue.
+ void WillQueueTask(internal::TaskQueueImpl::Task* pending_task);
+
+ // Delayed Tasks with run_times <= Now() are enqueued onto the work queue and
+ // reloads any empty work queues.
+ void WakeUpReadyDelayedQueues(LazyNow* lazy_now);
+
+ void NotifyWillProcessTask(ExecutingTask* task, LazyNow* time_before_task);
+ void NotifyDidProcessTask(ExecutingTask* task, LazyNow* time_after_task);
+
+ internal::EnqueueOrder GetNextSequenceNumber();
+
+ std::unique_ptr<trace_event::ConvertableToTraceFormat>
+ AsValueWithSelectorResult(bool should_run,
+ internal::WorkQueue* selected_work_queue) const;
+
+ // Adds |queue| to |any_thread().has_incoming_immediate_work_| and if
+ // |queue_is_blocked| is false it makes sure a DoWork is posted.
+ // Can be called from any thread.
+ void OnQueueHasIncomingImmediateWork(internal::TaskQueueImpl* queue,
+ internal::EnqueueOrder enqueue_order,
+ bool queue_is_blocked);
+
+ // Returns true if |task_queue| was added to the list, or false if it was
+ // already in the list. If |task_queue| was inserted, the |order| is set
+ // with |enqueue_order|.
+ bool AddToIncomingImmediateWorkList(internal::TaskQueueImpl* task_queue,
+ internal::EnqueueOrder enqueue_order);
+ void RemoveFromIncomingImmediateWorkList(internal::TaskQueueImpl* task_queue);
+
+ // Calls |ReloadImmediateWorkQueueIfEmpty| on all queues in
+ // |main_thread_only().queues_to_reload|.
+ void ReloadEmptyWorkQueues();
+
+ std::unique_ptr<internal::TaskQueueImpl> CreateTaskQueueImpl(
+ const TaskQueue::Spec& spec) override;
+
+ void TakeQueuesToGracefullyShutdownFromHelper();
+
+ // Deletes queues marked for deletion and empty queues marked for shutdown.
+ void CleanUpQueues();
+
+ bool ShouldRecordCPUTimeForTask();
+
+ // Determines if wall time or thread time should be recorded for the next
+ // task.
+ TaskQueue::TaskTiming InitializeTaskTiming(
+ internal::TaskQueueImpl* task_queue);
+
+ const scoped_refptr<internal::GracefulQueueShutdownHelper>
+ graceful_shutdown_helper_;
+
+ internal::EnqueueOrder::Generator enqueue_order_generator_;
+
+ std::unique_ptr<internal::ThreadController> controller_;
+
+ mutable Lock any_thread_lock_;
+ AnyThread any_thread_;
+
+ struct AnyThread& any_thread() {
+ any_thread_lock_.AssertAcquired();
+ return any_thread_;
+ }
+ const struct AnyThread& any_thread() const {
+ any_thread_lock_.AssertAcquired();
+ return any_thread_;
+ }
+
+ const MetricRecordingSettings metric_recording_settings_;
+
+ // A check to bail out early during memory corruption.
+ // https://crbug.com/757940
+ bool Validate();
+
+ int32_t memory_corruption_sentinel_;
+
+ THREAD_CHECKER(main_thread_checker_);
+ MainThreadOnly main_thread_only_;
+ MainThreadOnly& main_thread_only() {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ return main_thread_only_;
+ }
+ const MainThreadOnly& main_thread_only() const {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ return main_thread_only_;
+ }
+
+ WeakPtrFactory<SequenceManagerImpl> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SequenceManagerImpl);
+};
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_SEQUENCE_MANAGER_IMPL_H_
diff --git a/base/task/sequence_manager/sequence_manager_impl_unittest.cc b/base/task/sequence_manager/sequence_manager_impl_unittest.cc
new file mode 100644
index 0000000000..e1587ef09c
--- /dev/null
+++ b/base/task/sequence_manager/sequence_manager_impl_unittest.cc
@@ -0,0 +1,3260 @@
+// 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.
+
+#include "base/task/sequence_manager/sequence_manager_impl.h"
+
+#include <stddef.h>
+#include <memory>
+#include <utility>
+
+#include "base/location.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/optional.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task/sequence_manager/real_time_domain.h"
+#include "base/task/sequence_manager/task_queue_impl.h"
+#include "base/task/sequence_manager/task_queue_selector.h"
+#include "base/task/sequence_manager/test/mock_time_domain.h"
+#include "base/task/sequence_manager/test/sequence_manager_for_test.h"
+#include "base/task/sequence_manager/test/test_task_queue.h"
+#include "base/task/sequence_manager/test/test_task_time_observer.h"
+#include "base/task/sequence_manager/thread_controller_with_message_pump_impl.h"
+#include "base/task/sequence_manager/work_queue.h"
+#include "base/task/sequence_manager/work_queue_sets.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/test/test_mock_time_task_runner.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/test/trace_event_analyzer.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/trace_event/blame_context.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::AnyNumber;
+using testing::Contains;
+using testing::ElementsAre;
+using testing::ElementsAreArray;
+using testing::Mock;
+using testing::Not;
+using testing::_;
+using base::sequence_manager::internal::EnqueueOrder;
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+// To avoid symbol collisions in jumbo builds.
+namespace sequence_manager_impl_unittest {
+
+enum class TestType : int {
+ kCustom = 0,
+ kUseMockTaskRunner = 1,
+ kUseMessageLoop = 2,
+ kUseMessagePump = 3,
+};
+
+class SequenceManagerTestBase : public testing::TestWithParam<TestType> {
+ protected:
+ void TearDown() override {
+ // SequenceManager should be deleted before an underlying task runner.
+ manager_.reset();
+ }
+
+ scoped_refptr<TestTaskQueue> CreateTaskQueue(
+ TaskQueue::Spec spec = TaskQueue::Spec("test")) {
+ return manager_->CreateTaskQueue<TestTaskQueue>(spec);
+ }
+
+ void CreateTaskQueues(size_t num_queues) {
+ for (size_t i = 0; i < num_queues; i++)
+ runners_.push_back(CreateTaskQueue());
+ }
+
+ std::unique_ptr<SequenceManagerForTest> manager_;
+ std::vector<scoped_refptr<TestTaskQueue>> runners_;
+ TimeTicks start_time_;
+ TestTaskTimeObserver test_task_time_observer_;
+};
+
+// SequenceManagerImpl uses TestMockTimeTaskRunner which controls
+// both task execution and mock clock.
+// TODO(kraynov): Make this class to support all TestTypes.
+// It will allow us to re-run tests in various environments before we'll
+// eventually move to MessagePump and remove current ThreadControllerImpl.
+class SequenceManagerTest : public SequenceManagerTestBase {
+ public:
+ void DeleteSequenceManagerTask() { manager_.reset(); }
+
+ protected:
+ void SetUp() override {
+ ASSERT_EQ(GetParam(), TestType::kUseMockTaskRunner);
+ test_task_runner_ = WrapRefCounted(new TestMockTimeTaskRunner(
+ TestMockTimeTaskRunner::Type::kBoundToThread));
+ // A null clock triggers some assertions.
+ test_task_runner_->AdvanceMockTickClock(TimeDelta::FromMilliseconds(1));
+ start_time_ = GetTickClock()->NowTicks();
+
+ manager_ =
+ SequenceManagerForTest::Create(nullptr, ThreadTaskRunnerHandle::Get(),
+ test_task_runner_->GetMockTickClock());
+ }
+
+ const TickClock* GetTickClock() {
+ return test_task_runner_->GetMockTickClock();
+ }
+
+ void RunPendingTasks() {
+ // We should only run tasks already posted by that moment.
+ RunLoop run_loop;
+ test_task_runner_->PostTask(FROM_HERE, run_loop.QuitClosure());
+ // TestMockTimeTaskRunner will fast-forward mock clock if necessary.
+ run_loop.Run();
+ }
+
+ // Runs all immediate tasks until there is no more work to do and advances
+ // time if there is a pending delayed task. |per_run_time_callback| is called
+ // when the clock advances.
+ // The only difference to FastForwardUntilNoTasksRemain is that time
+ // advancing isn't driven by the test task runner, but uses time domain's
+ // next scheduled run time instead. It allows us to double-check consistency
+ // and allows to count such bursts of doing work, which is a test subject.
+ void RunUntilManagerIsIdle(RepeatingClosure per_run_time_callback) {
+ for (;;) {
+ // Advance time if we've run out of immediate work to do.
+ if (!manager_->HasImmediateWork()) {
+ LazyNow lazy_now(GetTickClock());
+ Optional<TimeDelta> delay =
+ manager_->GetRealTimeDomain()->DelayTillNextTask(&lazy_now);
+ if (delay) {
+ test_task_runner_->AdvanceMockTickClock(*delay);
+ per_run_time_callback.Run();
+ } else {
+ break;
+ }
+ }
+ RunPendingTasks();
+ }
+ }
+
+ scoped_refptr<TestMockTimeTaskRunner> test_task_runner_;
+};
+
+// SequenceManagerImpl is being initialized with real MessageLoop
+// at cost of less control over a task runner.
+// It also runs a version with experimental MessagePump support.
+// TODO(kraynov): Generalize as many tests as possible to run it
+// in all supported environments.
+class SequenceManagerTestWithMessageLoop : public SequenceManagerTestBase {
+ protected:
+ void SetUp() override {
+ switch (GetParam()) {
+ case TestType::kUseMessageLoop:
+ SetUpWithMessageLoop();
+ break;
+ case TestType::kUseMessagePump:
+ SetUpWithMessagePump();
+ break;
+ default:
+ FAIL();
+ }
+ }
+
+ void SetUpWithMessageLoop() {
+ message_loop_.reset(new MessageLoop());
+ // A null clock triggers some assertions.
+ mock_clock_.Advance(TimeDelta::FromMilliseconds(1));
+ start_time_ = mock_clock_.NowTicks();
+
+ manager_ = SequenceManagerForTest::Create(
+ message_loop_.get(), ThreadTaskRunnerHandle::Get(), &mock_clock_);
+ }
+
+ void SetUpWithMessagePump() {
+ mock_clock_.Advance(TimeDelta::FromMilliseconds(1));
+ start_time_ = mock_clock_.NowTicks();
+ manager_ = std::make_unique<SequenceManagerForTest>(
+ std::make_unique<ThreadControllerWithMessagePumpImpl>(&mock_clock_));
+ // ThreadControllerWithMessagePumpImpl doesn't provide a default tas runner.
+ scoped_refptr<TaskQueue> default_task_queue =
+ manager_->CreateTaskQueue<TestTaskQueue>(TaskQueue::Spec("default"));
+ manager_->SetDefaultTaskRunner(default_task_queue);
+ }
+
+ const TickClock* GetTickClock() { return &mock_clock_; }
+
+ std::unique_ptr<MessageLoop> message_loop_;
+ SimpleTestTickClock mock_clock_;
+};
+
+class SequenceManagerTestWithCustomInitialization
+ : public SequenceManagerTestWithMessageLoop {
+ protected:
+ void SetUp() override { ASSERT_EQ(GetParam(), TestType::kCustom); }
+};
+
+INSTANTIATE_TEST_CASE_P(,
+ SequenceManagerTest,
+ testing::Values(TestType::kUseMockTaskRunner));
+
+INSTANTIATE_TEST_CASE_P(,
+ SequenceManagerTestWithMessageLoop,
+ testing::Values(TestType::kUseMessageLoop,
+ TestType::kUseMessagePump));
+
+INSTANTIATE_TEST_CASE_P(,
+ SequenceManagerTestWithCustomInitialization,
+ testing::Values(TestType::kCustom));
+
+void PostFromNestedRunloop(SingleThreadTaskRunner* runner,
+ std::vector<std::pair<OnceClosure, bool>>* tasks) {
+ for (std::pair<OnceClosure, bool>& pair : *tasks) {
+ if (pair.second) {
+ runner->PostTask(FROM_HERE, std::move(pair.first));
+ } else {
+ runner->PostNonNestableTask(FROM_HERE, std::move(pair.first));
+ }
+ }
+ RunLoop(RunLoop::Type::kNestableTasksAllowed).RunUntilIdle();
+}
+
+void NopTask() {}
+
+class TestCountUsesTimeSource : public TickClock {
+ public:
+ TestCountUsesTimeSource() = default;
+ ~TestCountUsesTimeSource() override = default;
+
+ TimeTicks NowTicks() const override {
+ now_calls_count_++;
+ // Don't return 0, as it triggers some assertions.
+ return TimeTicks() + TimeDelta::FromSeconds(1);
+ }
+
+ int now_calls_count() const { return now_calls_count_; }
+
+ private:
+ mutable int now_calls_count_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(TestCountUsesTimeSource);
+};
+
+TEST_P(SequenceManagerTestWithCustomInitialization,
+ NowCalledMinimumNumberOfTimesToComputeTaskDurations) {
+ message_loop_.reset(new MessageLoop());
+ // This memory is managed by the SequenceManager, but we need to hold a
+ // pointer to this object to read out how many times Now was called.
+ TestCountUsesTimeSource test_count_uses_time_source;
+
+ manager_ = SequenceManagerForTest::Create(
+ nullptr, ThreadTaskRunnerHandle::Get(), &test_count_uses_time_source);
+ manager_->SetWorkBatchSize(6);
+ manager_->AddTaskTimeObserver(&test_task_time_observer_);
+
+ for (size_t i = 0; i < 3; i++)
+ runners_.push_back(CreateTaskQueue());
+
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&NopTask));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&NopTask));
+ runners_[1]->PostTask(FROM_HERE, BindOnce(&NopTask));
+ runners_[1]->PostTask(FROM_HERE, BindOnce(&NopTask));
+ runners_[2]->PostTask(FROM_HERE, BindOnce(&NopTask));
+ runners_[2]->PostTask(FROM_HERE, BindOnce(&NopTask));
+
+ RunLoop().RunUntilIdle();
+ // Now is called each time a task is queued, when first task is started
+ // running, and when a task is completed. 6 * 3 = 18 calls.
+ EXPECT_EQ(18, test_count_uses_time_source.now_calls_count());
+}
+
+void NullTask() {}
+
+void TestTask(uint64_t value, std::vector<EnqueueOrder>* out_result) {
+ out_result->push_back(EnqueueOrder::FromIntForTesting(value));
+}
+
+void DisableQueueTestTask(uint64_t value,
+ std::vector<EnqueueOrder>* out_result,
+ TaskQueue::QueueEnabledVoter* voter) {
+ out_result->push_back(EnqueueOrder::FromIntForTesting(value));
+ voter->SetQueueEnabled(false);
+}
+
+TEST_P(SequenceManagerTest, SingleQueuePosting) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u, 3u));
+}
+
+TEST_P(SequenceManagerTest, MultiQueuePosting) {
+ CreateTaskQueues(3u);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+ runners_[1]->PostTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order));
+ runners_[1]->PostTask(FROM_HERE, BindOnce(&TestTask, 4, &run_order));
+ runners_[2]->PostTask(FROM_HERE, BindOnce(&TestTask, 5, &run_order));
+ runners_[2]->PostTask(FROM_HERE, BindOnce(&TestTask, 6, &run_order));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u, 3u, 4u, 5u, 6u));
+}
+
+TEST_P(SequenceManagerTestWithMessageLoop, NonNestableTaskPosting) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostNonNestableTask(FROM_HERE,
+ BindOnce(&TestTask, 1, &run_order));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(1u));
+}
+
+TEST_P(SequenceManagerTestWithMessageLoop,
+ NonNestableTaskExecutesInExpectedOrder) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 4, &run_order));
+ runners_[0]->PostNonNestableTask(FROM_HERE,
+ BindOnce(&TestTask, 5, &run_order));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u, 3u, 4u, 5u));
+}
+
+TEST_P(SequenceManagerTestWithMessageLoop,
+ NonNestableTasksDoesntExecuteInNestedLoop) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+
+ std::vector<std::pair<OnceClosure, bool>> tasks_to_post_from_nested_loop;
+ tasks_to_post_from_nested_loop.push_back(
+ std::make_pair(BindOnce(&TestTask, 3, &run_order), false));
+ tasks_to_post_from_nested_loop.push_back(
+ std::make_pair(BindOnce(&TestTask, 4, &run_order), false));
+ tasks_to_post_from_nested_loop.push_back(
+ std::make_pair(BindOnce(&TestTask, 5, &run_order), true));
+ tasks_to_post_from_nested_loop.push_back(
+ std::make_pair(BindOnce(&TestTask, 6, &run_order), true));
+
+ runners_[0]->PostTask(
+ FROM_HERE, BindOnce(&PostFromNestedRunloop, RetainedRef(runners_[0]),
+ Unretained(&tasks_to_post_from_nested_loop)));
+
+ RunLoop().RunUntilIdle();
+ // Note we expect tasks 3 & 4 to run last because they're non-nestable.
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u, 5u, 6u, 3u, 4u));
+}
+
+namespace {
+
+void InsertFenceAndPostTestTask(int id,
+ std::vector<EnqueueOrder>* run_order,
+ scoped_refptr<TestTaskQueue> task_queue) {
+ run_order->push_back(EnqueueOrder::FromIntForTesting(id));
+ task_queue->InsertFence(TaskQueue::InsertFencePosition::kNow);
+ task_queue->PostTask(FROM_HERE, BindOnce(&TestTask, id + 1, run_order));
+
+ // Force reload of immediate work queue. In real life the same effect can be
+ // achieved with cross-thread posting.
+ task_queue->GetTaskQueueImpl()->ReloadImmediateWorkQueueIfEmpty();
+}
+
+} // namespace
+
+TEST_P(SequenceManagerTestWithMessageLoop, TaskQueueDisabledFromNestedLoop) {
+ CreateTaskQueues(1u);
+ std::vector<EnqueueOrder> run_order;
+
+ std::vector<std::pair<OnceClosure, bool>> tasks_to_post_from_nested_loop;
+
+ tasks_to_post_from_nested_loop.push_back(
+ std::make_pair(BindOnce(&TestTask, 1, &run_order), false));
+ tasks_to_post_from_nested_loop.push_back(std::make_pair(
+ BindOnce(&InsertFenceAndPostTestTask, 2, &run_order, runners_[0]), true));
+
+ runners_[0]->PostTask(
+ FROM_HERE, BindOnce(&PostFromNestedRunloop, RetainedRef(runners_[0]),
+ Unretained(&tasks_to_post_from_nested_loop)));
+ RunLoop().RunUntilIdle();
+
+ // Task 1 shouldn't run first due to it being non-nestable and queue gets
+ // blocked after task 2. Task 1 runs after existing nested message loop
+ // due to being posted before inserting a fence.
+ // This test checks that breaks when nestable task is pushed into a redo
+ // queue.
+ EXPECT_THAT(run_order, ElementsAre(2u, 1u));
+
+ runners_[0]->RemoveFence();
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(2u, 1u, 3u));
+}
+
+TEST_P(SequenceManagerTest, HasPendingImmediateWork_ImmediateTask) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ EXPECT_FALSE(runners_[0]->HasTaskToRunImmediately());
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ EXPECT_TRUE(runners_[0]->HasTaskToRunImmediately());
+
+ // Move the task into the |immediate_work_queue|.
+ EXPECT_TRUE(runners_[0]->GetTaskQueueImpl()->immediate_work_queue()->Empty());
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
+ runners_[0]->CreateQueueEnabledVoter();
+ voter->SetQueueEnabled(false);
+ RunLoop().RunUntilIdle();
+ EXPECT_FALSE(
+ runners_[0]->GetTaskQueueImpl()->immediate_work_queue()->Empty());
+ EXPECT_TRUE(runners_[0]->HasTaskToRunImmediately());
+
+ // Run the task, making the queue empty.
+ voter->SetQueueEnabled(true);
+ RunLoop().RunUntilIdle();
+ EXPECT_FALSE(runners_[0]->HasTaskToRunImmediately());
+}
+
+TEST_P(SequenceManagerTest, HasPendingImmediateWork_DelayedTask) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ TimeDelta delay(TimeDelta::FromMilliseconds(10));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order),
+ delay);
+ EXPECT_FALSE(runners_[0]->HasTaskToRunImmediately());
+ test_task_runner_->AdvanceMockTickClock(delay);
+ EXPECT_TRUE(runners_[0]->HasTaskToRunImmediately());
+
+ // Move the task into the |delayed_work_queue|.
+ LazyNow lazy_now(GetTickClock());
+ manager_->WakeUpReadyDelayedQueues(&lazy_now);
+ EXPECT_FALSE(runners_[0]->GetTaskQueueImpl()->delayed_work_queue()->Empty());
+ EXPECT_TRUE(runners_[0]->HasTaskToRunImmediately());
+
+ // Run the task, making the queue empty.
+ RunLoop().RunUntilIdle();
+ EXPECT_FALSE(runners_[0]->HasTaskToRunImmediately());
+}
+
+TEST_P(SequenceManagerTest, DelayedTaskPosting) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ TimeDelta delay(TimeDelta::FromMilliseconds(10));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order),
+ delay);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(10),
+ test_task_runner_->NextPendingTaskDelay());
+ EXPECT_FALSE(runners_[0]->HasTaskToRunImmediately());
+ EXPECT_TRUE(run_order.empty());
+
+ // The task doesn't run before the delay has completed.
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(9));
+ EXPECT_TRUE(run_order.empty());
+
+ // After the delay has completed, the task runs normally.
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(1));
+ EXPECT_THAT(run_order, ElementsAre(1u));
+ EXPECT_FALSE(runners_[0]->HasTaskToRunImmediately());
+}
+
+TEST_P(SequenceManagerTest, DelayedTaskExecutedInOneMessageLoopTask) {
+ CreateTaskQueues(1u);
+
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask),
+ TimeDelta::FromMilliseconds(10));
+ RunLoop().RunUntilIdle();
+ EXPECT_EQ(1u, test_task_runner_->GetPendingTaskCount());
+ test_task_runner_->FastForwardUntilNoTasksRemain();
+ EXPECT_EQ(0u, test_task_runner_->GetPendingTaskCount());
+}
+
+TEST_P(SequenceManagerTest, DelayedTaskPosting_MultipleTasks_DecendingOrder) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order),
+ TimeDelta::FromMilliseconds(10));
+
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order),
+ TimeDelta::FromMilliseconds(8));
+
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order),
+ TimeDelta::FromMilliseconds(5));
+
+ EXPECT_EQ(TimeDelta::FromMilliseconds(5),
+ test_task_runner_->NextPendingTaskDelay());
+
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(5));
+ EXPECT_THAT(run_order, ElementsAre(3u));
+ EXPECT_EQ(TimeDelta::FromMilliseconds(3),
+ test_task_runner_->NextPendingTaskDelay());
+
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(3));
+ EXPECT_THAT(run_order, ElementsAre(3u, 2u));
+ EXPECT_EQ(TimeDelta::FromMilliseconds(2),
+ test_task_runner_->NextPendingTaskDelay());
+
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(2));
+ EXPECT_THAT(run_order, ElementsAre(3u, 2u, 1u));
+}
+
+TEST_P(SequenceManagerTest, DelayedTaskPosting_MultipleTasks_AscendingOrder) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order),
+ TimeDelta::FromMilliseconds(1));
+
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order),
+ TimeDelta::FromMilliseconds(5));
+
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order),
+ TimeDelta::FromMilliseconds(10));
+
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1),
+ test_task_runner_->NextPendingTaskDelay());
+
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(1));
+ EXPECT_THAT(run_order, ElementsAre(1u));
+ EXPECT_EQ(TimeDelta::FromMilliseconds(4),
+ test_task_runner_->NextPendingTaskDelay());
+
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(4));
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u));
+ EXPECT_EQ(TimeDelta::FromMilliseconds(5),
+ test_task_runner_->NextPendingTaskDelay());
+
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(5));
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u, 3u));
+}
+
+TEST_P(SequenceManagerTest, PostDelayedTask_SharesUnderlyingDelayedTasks) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ TimeDelta delay(TimeDelta::FromMilliseconds(10));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order),
+ delay);
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order),
+ delay);
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order),
+ delay);
+
+ EXPECT_EQ(1u, test_task_runner_->GetPendingTaskCount());
+}
+
+class TestObject {
+ public:
+ ~TestObject() { destructor_count__++; }
+
+ void Run() { FAIL() << "TestObject::Run should not be called"; }
+
+ static int destructor_count__;
+};
+
+int TestObject::destructor_count__ = 0;
+
+TEST_P(SequenceManagerTest, PendingDelayedTasksRemovedOnShutdown) {
+ CreateTaskQueues(1u);
+
+ TestObject::destructor_count__ = 0;
+
+ TimeDelta delay(TimeDelta::FromMilliseconds(10));
+ runners_[0]->PostDelayedTask(
+ FROM_HERE, BindOnce(&TestObject::Run, Owned(new TestObject())), delay);
+ runners_[0]->PostTask(FROM_HERE,
+ BindOnce(&TestObject::Run, Owned(new TestObject())));
+
+ manager_.reset();
+
+ EXPECT_EQ(2, TestObject::destructor_count__);
+}
+
+TEST_P(SequenceManagerTest, InsertAndRemoveFence) {
+ CreateTaskQueues(1u);
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+
+ std::vector<EnqueueOrder> run_order;
+ // Posting a task when pumping is disabled doesn't result in work getting
+ // posted.
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ EXPECT_FALSE(test_task_runner_->HasPendingTask());
+
+ // However polling still works.
+ EXPECT_TRUE(runners_[0]->HasTaskToRunImmediately());
+
+ // After removing the fence the task runs normally.
+ runners_[0]->RemoveFence();
+ EXPECT_TRUE(test_task_runner_->HasPendingTask());
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(1u));
+}
+
+TEST_P(SequenceManagerTest, RemovingFenceForDisabledQueueDoesNotPostDoWork) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
+ runners_[0]->CreateQueueEnabledVoter();
+ voter->SetQueueEnabled(false);
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+
+ runners_[0]->RemoveFence();
+ EXPECT_FALSE(test_task_runner_->HasPendingTask());
+}
+
+TEST_P(SequenceManagerTest, EnablingFencedQueueDoesNotPostDoWork) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
+ runners_[0]->CreateQueueEnabledVoter();
+ voter->SetQueueEnabled(false);
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+
+ voter->SetQueueEnabled(true);
+ EXPECT_FALSE(test_task_runner_->HasPendingTask());
+}
+
+TEST_P(SequenceManagerTest, DenyRunning_BeforePosting) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
+ runners_[0]->CreateQueueEnabledVoter();
+ voter->SetQueueEnabled(false);
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ EXPECT_FALSE(test_task_runner_->HasPendingTask());
+
+ RunLoop().RunUntilIdle();
+ EXPECT_TRUE(run_order.empty());
+
+ voter->SetQueueEnabled(true);
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(1u));
+}
+
+TEST_P(SequenceManagerTest, DenyRunning_AfterPosting) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
+ runners_[0]->CreateQueueEnabledVoter();
+ EXPECT_TRUE(test_task_runner_->HasPendingTask());
+ voter->SetQueueEnabled(false);
+
+ RunLoop().RunUntilIdle();
+ EXPECT_TRUE(run_order.empty());
+
+ voter->SetQueueEnabled(true);
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(1u));
+}
+
+TEST_P(SequenceManagerTest, DenyRunning_AfterRemovingFence) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
+ runners_[0]->CreateQueueEnabledVoter();
+ voter->SetQueueEnabled(false);
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_TRUE(run_order.empty());
+
+ runners_[0]->RemoveFence();
+ voter->SetQueueEnabled(true);
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(1u));
+}
+
+TEST_P(SequenceManagerTest, RemovingFenceWithDelayedTask) {
+ CreateTaskQueues(1u);
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+
+ std::vector<EnqueueOrder> run_order;
+ // Posting a delayed task when fenced will apply the delay, but won't cause
+ // work to executed afterwards.
+ TimeDelta delay(TimeDelta::FromMilliseconds(10));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order),
+ delay);
+
+ // The task does not run even though it's delay is up.
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(10));
+ EXPECT_TRUE(run_order.empty());
+
+ // Removing the fence causes the task to run.
+ runners_[0]->RemoveFence();
+ EXPECT_TRUE(test_task_runner_->HasPendingTask());
+ RunPendingTasks();
+ EXPECT_THAT(run_order, ElementsAre(1u));
+}
+
+TEST_P(SequenceManagerTest, RemovingFenceWithMultipleDelayedTasks) {
+ CreateTaskQueues(1u);
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+
+ std::vector<EnqueueOrder> run_order;
+ // Posting a delayed task when fenced will apply the delay, but won't cause
+ // work to executed afterwards.
+ TimeDelta delay1(TimeDelta::FromMilliseconds(1));
+ TimeDelta delay2(TimeDelta::FromMilliseconds(10));
+ TimeDelta delay3(TimeDelta::FromMilliseconds(20));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order),
+ delay1);
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order),
+ delay2);
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order),
+ delay3);
+
+ test_task_runner_->AdvanceMockTickClock(TimeDelta::FromMilliseconds(15));
+ RunLoop().RunUntilIdle();
+ EXPECT_TRUE(run_order.empty());
+
+ // Removing the fence causes the ready tasks to run.
+ runners_[0]->RemoveFence();
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u));
+}
+
+TEST_P(SequenceManagerTest, InsertFencePreventsDelayedTasksFromRunning) {
+ CreateTaskQueues(1u);
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+
+ std::vector<EnqueueOrder> run_order;
+ TimeDelta delay(TimeDelta::FromMilliseconds(10));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order),
+ delay);
+
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(10));
+ EXPECT_TRUE(run_order.empty());
+}
+
+TEST_P(SequenceManagerTest, MultipleFences) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order));
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u));
+
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+ // Subsequent tasks should be blocked.
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 4, &run_order));
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u, 3u));
+}
+
+TEST_P(SequenceManagerTest, InsertFenceThenImmediatlyRemoveDoesNotBlock) {
+ CreateTaskQueues(1u);
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+ runners_[0]->RemoveFence();
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u));
+}
+
+TEST_P(SequenceManagerTest, InsertFencePostThenRemoveDoesNotBlock) {
+ CreateTaskQueues(1u);
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+ runners_[0]->RemoveFence();
+
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u));
+}
+
+TEST_P(SequenceManagerTest, MultipleFencesWithInitiallyEmptyQueue) {
+ CreateTaskQueues(1u);
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(1u));
+}
+
+TEST_P(SequenceManagerTest, BlockedByFence) {
+ CreateTaskQueues(1u);
+ EXPECT_FALSE(runners_[0]->BlockedByFence());
+
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+ EXPECT_TRUE(runners_[0]->BlockedByFence());
+
+ runners_[0]->RemoveFence();
+ EXPECT_FALSE(runners_[0]->BlockedByFence());
+
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&NopTask));
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+ EXPECT_FALSE(runners_[0]->BlockedByFence());
+
+ RunLoop().RunUntilIdle();
+ EXPECT_TRUE(runners_[0]->BlockedByFence());
+
+ runners_[0]->RemoveFence();
+ EXPECT_FALSE(runners_[0]->BlockedByFence());
+}
+
+TEST_P(SequenceManagerTest, BlockedByFence_BothTypesOfFence) {
+ CreateTaskQueues(1u);
+
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&NopTask));
+
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+ EXPECT_FALSE(runners_[0]->BlockedByFence());
+
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kBeginningOfTime);
+ EXPECT_TRUE(runners_[0]->BlockedByFence());
+}
+
+namespace {
+
+void RecordTimeTask(std::vector<TimeTicks>* run_times, const TickClock* clock) {
+ run_times->push_back(clock->NowTicks());
+}
+
+void RecordTimeAndQueueTask(
+ std::vector<std::pair<scoped_refptr<TestTaskQueue>, TimeTicks>>* run_times,
+ scoped_refptr<TestTaskQueue> task_queue,
+ const TickClock* clock) {
+ run_times->emplace_back(task_queue, clock->NowTicks());
+}
+
+} // namespace
+
+TEST_P(SequenceManagerTest, DelayedFence_DelayedTasks) {
+ CreateTaskQueues(1u);
+
+ std::vector<TimeTicks> run_times;
+ runners_[0]->PostDelayedTask(
+ FROM_HERE, BindOnce(&RecordTimeTask, &run_times, GetTickClock()),
+ TimeDelta::FromMilliseconds(100));
+ runners_[0]->PostDelayedTask(
+ FROM_HERE, BindOnce(&RecordTimeTask, &run_times, GetTickClock()),
+ TimeDelta::FromMilliseconds(200));
+ runners_[0]->PostDelayedTask(
+ FROM_HERE, BindOnce(&RecordTimeTask, &run_times, GetTickClock()),
+ TimeDelta::FromMilliseconds(300));
+
+ runners_[0]->InsertFenceAt(GetTickClock()->NowTicks() +
+ TimeDelta::FromMilliseconds(250));
+ EXPECT_FALSE(runners_[0]->HasActiveFence());
+
+ test_task_runner_->FastForwardUntilNoTasksRemain();
+
+ EXPECT_TRUE(runners_[0]->HasActiveFence());
+ EXPECT_THAT(run_times,
+ ElementsAre(start_time_ + TimeDelta::FromMilliseconds(100),
+ start_time_ + TimeDelta::FromMilliseconds(200)));
+ run_times.clear();
+
+ runners_[0]->RemoveFence();
+
+ test_task_runner_->FastForwardUntilNoTasksRemain();
+
+ EXPECT_FALSE(runners_[0]->HasActiveFence());
+ EXPECT_THAT(run_times,
+ ElementsAre(start_time_ + TimeDelta::FromMilliseconds(300)));
+}
+
+TEST_P(SequenceManagerTest, DelayedFence_ImmediateTasks) {
+ CreateTaskQueues(1u);
+
+ std::vector<TimeTicks> run_times;
+ runners_[0]->InsertFenceAt(GetTickClock()->NowTicks() +
+ TimeDelta::FromMilliseconds(250));
+
+ for (int i = 0; i < 5; ++i) {
+ runners_[0]->PostTask(
+ FROM_HERE, BindOnce(&RecordTimeTask, &run_times, GetTickClock()));
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(100));
+ if (i < 2) {
+ EXPECT_FALSE(runners_[0]->HasActiveFence());
+ } else {
+ EXPECT_TRUE(runners_[0]->HasActiveFence());
+ }
+ }
+
+ EXPECT_THAT(
+ run_times,
+ ElementsAre(start_time_, start_time_ + TimeDelta::FromMilliseconds(100),
+ start_time_ + TimeDelta::FromMilliseconds(200)));
+ run_times.clear();
+
+ runners_[0]->RemoveFence();
+ test_task_runner_->FastForwardUntilNoTasksRemain();
+
+ EXPECT_THAT(run_times,
+ ElementsAre(start_time_ + TimeDelta::FromMilliseconds(500),
+ start_time_ + TimeDelta::FromMilliseconds(500)));
+}
+
+TEST_P(SequenceManagerTest, DelayedFence_RemovedFenceDoesNotActivate) {
+ CreateTaskQueues(1u);
+
+ std::vector<TimeTicks> run_times;
+ runners_[0]->InsertFenceAt(GetTickClock()->NowTicks() +
+ TimeDelta::FromMilliseconds(250));
+
+ for (int i = 0; i < 3; ++i) {
+ runners_[0]->PostTask(
+ FROM_HERE, BindOnce(&RecordTimeTask, &run_times, GetTickClock()));
+ EXPECT_FALSE(runners_[0]->HasActiveFence());
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(100));
+ }
+
+ EXPECT_TRUE(runners_[0]->HasActiveFence());
+ runners_[0]->RemoveFence();
+
+ for (int i = 0; i < 2; ++i) {
+ runners_[0]->PostTask(
+ FROM_HERE, BindOnce(&RecordTimeTask, &run_times, GetTickClock()));
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(100));
+ EXPECT_FALSE(runners_[0]->HasActiveFence());
+ }
+
+ EXPECT_THAT(
+ run_times,
+ ElementsAre(start_time_, start_time_ + TimeDelta::FromMilliseconds(100),
+ start_time_ + TimeDelta::FromMilliseconds(200),
+ start_time_ + TimeDelta::FromMilliseconds(300),
+ start_time_ + TimeDelta::FromMilliseconds(400)));
+}
+
+TEST_P(SequenceManagerTest, DelayedFence_TakeIncomingImmediateQueue) {
+ // This test checks that everything works correctly when a work queue
+ // is swapped with an immediate incoming queue and a delayed fence
+ // is activated, forcing a different queue to become active.
+ CreateTaskQueues(2u);
+
+ scoped_refptr<TestTaskQueue> queue1 = runners_[0];
+ scoped_refptr<TestTaskQueue> queue2 = runners_[1];
+
+ std::vector<std::pair<scoped_refptr<TestTaskQueue>, TimeTicks>> run_times;
+
+ // Fence ensures that the task posted after advancing time is blocked.
+ queue1->InsertFenceAt(GetTickClock()->NowTicks() +
+ TimeDelta::FromMilliseconds(250));
+
+ // This task should not be blocked and should run immediately after
+ // advancing time at 301ms.
+ queue1->PostTask(FROM_HERE, BindOnce(&RecordTimeAndQueueTask, &run_times,
+ queue1, GetTickClock()));
+ // Force reload of immediate work queue. In real life the same effect can be
+ // achieved with cross-thread posting.
+ queue1->GetTaskQueueImpl()->ReloadImmediateWorkQueueIfEmpty();
+
+ test_task_runner_->AdvanceMockTickClock(TimeDelta::FromMilliseconds(300));
+
+ // This task should be blocked.
+ queue1->PostTask(FROM_HERE, BindOnce(&RecordTimeAndQueueTask, &run_times,
+ queue1, GetTickClock()));
+ // This task on a different runner should run as expected.
+ queue2->PostTask(FROM_HERE, BindOnce(&RecordTimeAndQueueTask, &run_times,
+ queue2, GetTickClock()));
+
+ test_task_runner_->FastForwardUntilNoTasksRemain();
+
+ EXPECT_THAT(
+ run_times,
+ ElementsAre(std::make_pair(
+ queue1, start_time_ + TimeDelta::FromMilliseconds(300)),
+ std::make_pair(
+ queue2, start_time_ + TimeDelta::FromMilliseconds(300))));
+}
+
+namespace {
+
+void ReentrantTestTask(scoped_refptr<SingleThreadTaskRunner> runner,
+ int countdown,
+ std::vector<EnqueueOrder>* out_result) {
+ out_result->push_back(EnqueueOrder::FromIntForTesting(countdown));
+ if (--countdown) {
+ runner->PostTask(
+ FROM_HERE, BindOnce(&ReentrantTestTask, runner, countdown, out_result));
+ }
+}
+
+} // namespace
+
+TEST_P(SequenceManagerTest, ReentrantPosting) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(
+ FROM_HERE, BindOnce(&ReentrantTestTask, runners_[0], 3, &run_order));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(3u, 2u, 1u));
+}
+
+TEST_P(SequenceManagerTest, NoTasksAfterShutdown) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ manager_.reset();
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_TRUE(run_order.empty());
+}
+
+void PostTaskToRunner(scoped_refptr<SingleThreadTaskRunner> runner,
+ std::vector<EnqueueOrder>* run_order) {
+ runner->PostTask(FROM_HERE, BindOnce(&TestTask, 1, run_order));
+}
+
+TEST_P(SequenceManagerTestWithMessageLoop, PostFromThread) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ Thread thread("TestThread");
+ thread.Start();
+ thread.task_runner()->PostTask(
+ FROM_HERE, BindOnce(&PostTaskToRunner, runners_[0], &run_order));
+ thread.Stop();
+
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(1u));
+}
+
+void RePostingTestTask(scoped_refptr<SingleThreadTaskRunner> runner,
+ int* run_count) {
+ (*run_count)++;
+ runner->PostTask(FROM_HERE, BindOnce(&RePostingTestTask,
+ Unretained(runner.get()), run_count));
+}
+
+TEST_P(SequenceManagerTest, DoWorkCantPostItselfMultipleTimes) {
+ CreateTaskQueues(1u);
+
+ int run_count = 0;
+ runners_[0]->PostTask(FROM_HERE,
+ BindOnce(&RePostingTestTask, runners_[0], &run_count));
+
+ RunPendingTasks();
+ EXPECT_EQ(1u, test_task_runner_->GetPendingTaskCount());
+ EXPECT_EQ(1, run_count);
+}
+
+TEST_P(SequenceManagerTestWithMessageLoop, PostFromNestedRunloop) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ std::vector<std::pair<OnceClosure, bool>> tasks_to_post_from_nested_loop;
+ tasks_to_post_from_nested_loop.push_back(
+ std::make_pair(BindOnce(&TestTask, 1, &run_order), true));
+
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 0, &run_order));
+ runners_[0]->PostTask(
+ FROM_HERE, BindOnce(&PostFromNestedRunloop, RetainedRef(runners_[0]),
+ Unretained(&tasks_to_post_from_nested_loop)));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+
+ RunLoop().RunUntilIdle();
+
+ EXPECT_THAT(run_order, ElementsAre(0u, 2u, 1u));
+}
+
+TEST_P(SequenceManagerTest, WorkBatching) {
+ CreateTaskQueues(1u);
+
+ manager_->SetWorkBatchSize(2);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 4, &run_order));
+
+ // Running one task in the host message loop should cause two posted tasks to
+ // get executed.
+ EXPECT_EQ(1u, test_task_runner_->GetPendingTaskCount());
+ RunPendingTasks();
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u));
+
+ // The second task runs the remaining two posted tasks.
+ EXPECT_EQ(1u, test_task_runner_->GetPendingTaskCount());
+ RunPendingTasks();
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u, 3u, 4u));
+}
+
+class MockTaskObserver : public MessageLoop::TaskObserver {
+ public:
+ MOCK_METHOD1(DidProcessTask, void(const PendingTask& task));
+ MOCK_METHOD1(WillProcessTask, void(const PendingTask& task));
+};
+
+TEST_P(SequenceManagerTestWithMessageLoop, TaskObserverAdding) {
+ CreateTaskQueues(1u);
+ MockTaskObserver observer;
+
+ manager_->SetWorkBatchSize(2);
+ manager_->AddTaskObserver(&observer);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+
+ EXPECT_CALL(observer, WillProcessTask(_)).Times(2);
+ EXPECT_CALL(observer, DidProcessTask(_)).Times(2);
+ RunLoop().RunUntilIdle();
+}
+
+TEST_P(SequenceManagerTestWithMessageLoop, TaskObserverRemoving) {
+ CreateTaskQueues(1u);
+ MockTaskObserver observer;
+ manager_->SetWorkBatchSize(2);
+ manager_->AddTaskObserver(&observer);
+ manager_->RemoveTaskObserver(&observer);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+
+ EXPECT_CALL(observer, WillProcessTask(_)).Times(0);
+ EXPECT_CALL(observer, DidProcessTask(_)).Times(0);
+ RunLoop().RunUntilIdle();
+}
+
+void RemoveObserverTask(SequenceManagerImpl* manager,
+ MessageLoop::TaskObserver* observer) {
+ manager->RemoveTaskObserver(observer);
+}
+
+TEST_P(SequenceManagerTestWithMessageLoop, TaskObserverRemovingInsideTask) {
+ CreateTaskQueues(1u);
+ MockTaskObserver observer;
+ manager_->SetWorkBatchSize(3);
+ manager_->AddTaskObserver(&observer);
+
+ runners_[0]->PostTask(
+ FROM_HERE, BindOnce(&RemoveObserverTask, manager_.get(), &observer));
+
+ EXPECT_CALL(observer, WillProcessTask(_)).Times(1);
+ EXPECT_CALL(observer, DidProcessTask(_)).Times(0);
+ RunLoop().RunUntilIdle();
+}
+
+TEST_P(SequenceManagerTestWithMessageLoop, QueueTaskObserverAdding) {
+ CreateTaskQueues(2u);
+ MockTaskObserver observer;
+
+ manager_->SetWorkBatchSize(2);
+ runners_[0]->AddTaskObserver(&observer);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ runners_[1]->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+
+ EXPECT_CALL(observer, WillProcessTask(_)).Times(1);
+ EXPECT_CALL(observer, DidProcessTask(_)).Times(1);
+ RunLoop().RunUntilIdle();
+}
+
+TEST_P(SequenceManagerTestWithMessageLoop, QueueTaskObserverRemoving) {
+ CreateTaskQueues(1u);
+ MockTaskObserver observer;
+ manager_->SetWorkBatchSize(2);
+ runners_[0]->AddTaskObserver(&observer);
+ runners_[0]->RemoveTaskObserver(&observer);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+
+ EXPECT_CALL(observer, WillProcessTask(_)).Times(0);
+ EXPECT_CALL(observer, DidProcessTask(_)).Times(0);
+
+ RunLoop().RunUntilIdle();
+}
+
+void RemoveQueueObserverTask(scoped_refptr<TaskQueue> queue,
+ MessageLoop::TaskObserver* observer) {
+ queue->RemoveTaskObserver(observer);
+}
+
+TEST_P(SequenceManagerTestWithMessageLoop,
+ QueueTaskObserverRemovingInsideTask) {
+ CreateTaskQueues(1u);
+ MockTaskObserver observer;
+ runners_[0]->AddTaskObserver(&observer);
+
+ runners_[0]->PostTask(
+ FROM_HERE, BindOnce(&RemoveQueueObserverTask, runners_[0], &observer));
+
+ EXPECT_CALL(observer, WillProcessTask(_)).Times(1);
+ EXPECT_CALL(observer, DidProcessTask(_)).Times(0);
+ RunLoop().RunUntilIdle();
+}
+
+TEST_P(SequenceManagerTest, ThreadCheckAfterTermination) {
+ CreateTaskQueues(1u);
+ EXPECT_TRUE(runners_[0]->RunsTasksInCurrentSequence());
+ manager_.reset();
+ EXPECT_TRUE(runners_[0]->RunsTasksInCurrentSequence());
+}
+
+TEST_P(SequenceManagerTest, TimeDomain_NextScheduledRunTime) {
+ CreateTaskQueues(2u);
+ test_task_runner_->AdvanceMockTickClock(TimeDelta::FromMicroseconds(10000));
+ LazyNow lazy_now_1(GetTickClock());
+
+ // With no delayed tasks.
+ EXPECT_FALSE(manager_->GetRealTimeDomain()->DelayTillNextTask(&lazy_now_1));
+
+ // With a non-delayed task.
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&NopTask));
+ EXPECT_FALSE(manager_->GetRealTimeDomain()->DelayTillNextTask(&lazy_now_1));
+
+ // With a delayed task.
+ TimeDelta expected_delay = TimeDelta::FromMilliseconds(50);
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), expected_delay);
+ EXPECT_EQ(expected_delay,
+ manager_->GetRealTimeDomain()->DelayTillNextTask(&lazy_now_1));
+
+ // With another delayed task in the same queue with a longer delay.
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask),
+ TimeDelta::FromMilliseconds(100));
+ EXPECT_EQ(expected_delay,
+ manager_->GetRealTimeDomain()->DelayTillNextTask(&lazy_now_1));
+
+ // With another delayed task in the same queue with a shorter delay.
+ expected_delay = TimeDelta::FromMilliseconds(20);
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), expected_delay);
+ EXPECT_EQ(expected_delay,
+ manager_->GetRealTimeDomain()->DelayTillNextTask(&lazy_now_1));
+
+ // With another delayed task in a different queue with a shorter delay.
+ expected_delay = TimeDelta::FromMilliseconds(10);
+ runners_[1]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), expected_delay);
+ EXPECT_EQ(expected_delay,
+ manager_->GetRealTimeDomain()->DelayTillNextTask(&lazy_now_1));
+
+ // Test it updates as time progresses
+ test_task_runner_->AdvanceMockTickClock(expected_delay);
+ LazyNow lazy_now_2(GetTickClock());
+ EXPECT_EQ(TimeDelta(),
+ manager_->GetRealTimeDomain()->DelayTillNextTask(&lazy_now_2));
+}
+
+TEST_P(SequenceManagerTest, TimeDomain_NextScheduledRunTime_MultipleQueues) {
+ CreateTaskQueues(3u);
+
+ TimeDelta delay1 = TimeDelta::FromMilliseconds(50);
+ TimeDelta delay2 = TimeDelta::FromMilliseconds(5);
+ TimeDelta delay3 = TimeDelta::FromMilliseconds(10);
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), delay1);
+ runners_[1]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), delay2);
+ runners_[2]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), delay3);
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&NopTask));
+
+ LazyNow lazy_now(GetTickClock());
+ EXPECT_EQ(delay2,
+ manager_->GetRealTimeDomain()->DelayTillNextTask(&lazy_now));
+}
+
+TEST_P(SequenceManagerTest, DeleteSequenceManagerInsideATask) {
+ CreateTaskQueues(1u);
+
+ runners_[0]->PostTask(
+ FROM_HERE, BindOnce(&SequenceManagerTest::DeleteSequenceManagerTask,
+ Unretained(this)));
+
+ // This should not crash, assuming DoWork detects the SequenceManager has
+ // been deleted.
+ RunLoop().RunUntilIdle();
+}
+
+TEST_P(SequenceManagerTest, GetAndClearSystemIsQuiescentBit) {
+ CreateTaskQueues(3u);
+
+ scoped_refptr<TaskQueue> queue0 =
+ CreateTaskQueue(TaskQueue::Spec("test").SetShouldMonitorQuiescence(true));
+ scoped_refptr<TaskQueue> queue1 =
+ CreateTaskQueue(TaskQueue::Spec("test").SetShouldMonitorQuiescence(true));
+ scoped_refptr<TaskQueue> queue2 = CreateTaskQueue();
+
+ EXPECT_TRUE(manager_->GetAndClearSystemIsQuiescentBit());
+
+ queue0->PostTask(FROM_HERE, BindOnce(&NopTask));
+ RunLoop().RunUntilIdle();
+ EXPECT_FALSE(manager_->GetAndClearSystemIsQuiescentBit());
+ EXPECT_TRUE(manager_->GetAndClearSystemIsQuiescentBit());
+
+ queue1->PostTask(FROM_HERE, BindOnce(&NopTask));
+ RunLoop().RunUntilIdle();
+ EXPECT_FALSE(manager_->GetAndClearSystemIsQuiescentBit());
+ EXPECT_TRUE(manager_->GetAndClearSystemIsQuiescentBit());
+
+ queue2->PostTask(FROM_HERE, BindOnce(&NopTask));
+ RunLoop().RunUntilIdle();
+ EXPECT_TRUE(manager_->GetAndClearSystemIsQuiescentBit());
+
+ queue0->PostTask(FROM_HERE, BindOnce(&NopTask));
+ queue1->PostTask(FROM_HERE, BindOnce(&NopTask));
+ RunLoop().RunUntilIdle();
+ EXPECT_FALSE(manager_->GetAndClearSystemIsQuiescentBit());
+ EXPECT_TRUE(manager_->GetAndClearSystemIsQuiescentBit());
+}
+
+TEST_P(SequenceManagerTest, HasPendingImmediateWork) {
+ CreateTaskQueues(1u);
+
+ EXPECT_FALSE(runners_[0]->HasTaskToRunImmediately());
+ runners_[0]->PostTask(FROM_HERE, BindOnce(NullTask));
+ EXPECT_TRUE(runners_[0]->HasTaskToRunImmediately());
+
+ RunLoop().RunUntilIdle();
+ EXPECT_FALSE(runners_[0]->HasTaskToRunImmediately());
+}
+
+TEST_P(SequenceManagerTest, HasPendingImmediateWork_DelayedTasks) {
+ CreateTaskQueues(1u);
+
+ EXPECT_FALSE(runners_[0]->HasTaskToRunImmediately());
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(NullTask),
+ TimeDelta::FromMilliseconds(12));
+ EXPECT_FALSE(runners_[0]->HasTaskToRunImmediately());
+
+ // Move time forwards until just before the delayed task should run.
+ test_task_runner_->AdvanceMockTickClock(TimeDelta::FromMilliseconds(10));
+ LazyNow lazy_now_1(GetTickClock());
+ manager_->WakeUpReadyDelayedQueues(&lazy_now_1);
+ EXPECT_FALSE(runners_[0]->HasTaskToRunImmediately());
+
+ // Force the delayed task onto the work queue.
+ test_task_runner_->AdvanceMockTickClock(TimeDelta::FromMilliseconds(2));
+ LazyNow lazy_now_2(GetTickClock());
+ manager_->WakeUpReadyDelayedQueues(&lazy_now_2);
+ EXPECT_TRUE(runners_[0]->HasTaskToRunImmediately());
+
+ RunLoop().RunUntilIdle();
+ EXPECT_FALSE(runners_[0]->HasTaskToRunImmediately());
+}
+
+void ExpensiveTestTask(int value,
+ scoped_refptr<TestMockTimeTaskRunner> test_task_runner,
+ std::vector<EnqueueOrder>* out_result) {
+ out_result->push_back(EnqueueOrder::FromIntForTesting(value));
+ test_task_runner->FastForwardBy(TimeDelta::FromMilliseconds(1));
+}
+
+TEST_P(SequenceManagerTest, ImmediateAndDelayedTaskInterleaving) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ TimeDelta delay = TimeDelta::FromMilliseconds(10);
+ for (int i = 10; i < 19; i++) {
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&ExpensiveTestTask, i, test_task_runner_, &run_order), delay);
+ }
+
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(10));
+
+ for (int i = 0; i < 9; i++) {
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&ExpensiveTestTask, i,
+ test_task_runner_, &run_order));
+ }
+
+ test_task_runner_->FastForwardUntilNoTasksRemain();
+
+ // Delayed tasks are not allowed to starve out immediate work which is why
+ // some of the immediate tasks run out of order.
+ uint64_t expected_run_order[] = {10u, 11u, 12u, 13u, 0u, 14u, 15u, 16u, 1u,
+ 17u, 18u, 2u, 3u, 4u, 5u, 6u, 7u, 8u};
+ EXPECT_THAT(run_order, ElementsAreArray(expected_run_order));
+}
+
+TEST_P(SequenceManagerTest,
+ DelayedTaskDoesNotSkipAHeadOfNonDelayedTask_SameQueue) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+ TimeDelta delay = TimeDelta::FromMilliseconds(10);
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order),
+ delay);
+
+ test_task_runner_->AdvanceMockTickClock(delay * 2);
+ RunLoop().RunUntilIdle();
+
+ EXPECT_THAT(run_order, ElementsAre(2u, 3u, 1u));
+}
+
+TEST_P(SequenceManagerTest,
+ DelayedTaskDoesNotSkipAHeadOfNonDelayedTask_DifferentQueues) {
+ CreateTaskQueues(2u);
+
+ std::vector<EnqueueOrder> run_order;
+ TimeDelta delay = TimeDelta::FromMilliseconds(10);
+ runners_[1]->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+ runners_[1]->PostTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order),
+ delay);
+
+ test_task_runner_->AdvanceMockTickClock(delay * 2);
+ RunLoop().RunUntilIdle();
+
+ EXPECT_THAT(run_order, ElementsAre(2u, 3u, 1u));
+}
+
+TEST_P(SequenceManagerTest, DelayedTaskDoesNotSkipAHeadOfShorterDelayedTask) {
+ CreateTaskQueues(2u);
+
+ std::vector<EnqueueOrder> run_order;
+ TimeDelta delay1 = TimeDelta::FromMilliseconds(10);
+ TimeDelta delay2 = TimeDelta::FromMilliseconds(5);
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order),
+ delay1);
+ runners_[1]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order),
+ delay2);
+
+ test_task_runner_->AdvanceMockTickClock(delay1 * 2);
+ RunLoop().RunUntilIdle();
+
+ EXPECT_THAT(run_order, ElementsAre(2u, 1u));
+}
+
+void CheckIsNested(bool* is_nested) {
+ *is_nested = RunLoop::IsNestedOnCurrentThread();
+}
+
+void PostAndQuitFromNestedRunloop(RunLoop* run_loop,
+ SingleThreadTaskRunner* runner,
+ bool* was_nested) {
+ runner->PostTask(FROM_HERE, run_loop->QuitClosure());
+ runner->PostTask(FROM_HERE, BindOnce(&CheckIsNested, was_nested));
+ run_loop->Run();
+}
+
+TEST_P(SequenceManagerTestWithMessageLoop, QuitWhileNested) {
+ // This test makes sure we don't continue running a work batch after a nested
+ // run loop has been exited in the middle of the batch.
+ CreateTaskQueues(1u);
+ manager_->SetWorkBatchSize(2);
+
+ bool was_nested = true;
+ RunLoop run_loop(RunLoop::Type::kNestableTasksAllowed);
+ runners_[0]->PostTask(
+ FROM_HERE, BindOnce(&PostAndQuitFromNestedRunloop, Unretained(&run_loop),
+ RetainedRef(runners_[0]), Unretained(&was_nested)));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_FALSE(was_nested);
+}
+
+class SequenceNumberCapturingTaskObserver : public MessageLoop::TaskObserver {
+ public:
+ // MessageLoop::TaskObserver overrides.
+ void WillProcessTask(const PendingTask& pending_task) override {}
+ void DidProcessTask(const PendingTask& pending_task) override {
+ sequence_numbers_.push_back(pending_task.sequence_num);
+ }
+
+ const std::vector<int>& sequence_numbers() const { return sequence_numbers_; }
+
+ private:
+ std::vector<int> sequence_numbers_;
+};
+
+TEST_P(SequenceManagerTest, SequenceNumSetWhenTaskIsPosted) {
+ CreateTaskQueues(1u);
+
+ SequenceNumberCapturingTaskObserver observer;
+ manager_->AddTaskObserver(&observer);
+
+ // Register four tasks that will run in reverse order.
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order),
+ TimeDelta::FromMilliseconds(30));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order),
+ TimeDelta::FromMilliseconds(20));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order),
+ TimeDelta::FromMilliseconds(10));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 4, &run_order));
+
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(40));
+ ASSERT_THAT(run_order, ElementsAre(4u, 3u, 2u, 1u));
+
+ // The sequence numbers are a one-based monotonically incrememting counter
+ // which should be set when the task is posted rather than when it's enqueued
+ // onto the Incoming queue. This counter starts with 2.
+ EXPECT_THAT(observer.sequence_numbers(), ElementsAre(5, 4, 3, 2));
+
+ manager_->RemoveTaskObserver(&observer);
+}
+
+TEST_P(SequenceManagerTest, NewTaskQueues) {
+ CreateTaskQueues(1u);
+
+ scoped_refptr<TaskQueue> queue1 = CreateTaskQueue();
+ scoped_refptr<TaskQueue> queue2 = CreateTaskQueue();
+ scoped_refptr<TaskQueue> queue3 = CreateTaskQueue();
+
+ ASSERT_NE(queue1, queue2);
+ ASSERT_NE(queue1, queue3);
+ ASSERT_NE(queue2, queue3);
+
+ std::vector<EnqueueOrder> run_order;
+ queue1->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ queue2->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+ queue3->PostTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order));
+ RunLoop().RunUntilIdle();
+
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u, 3u));
+}
+
+TEST_P(SequenceManagerTest, ShutdownTaskQueue) {
+ CreateTaskQueues(1u);
+
+ scoped_refptr<TaskQueue> queue1 = CreateTaskQueue();
+ scoped_refptr<TaskQueue> queue2 = CreateTaskQueue();
+ scoped_refptr<TaskQueue> queue3 = CreateTaskQueue();
+
+ ASSERT_NE(queue1, queue2);
+ ASSERT_NE(queue1, queue3);
+ ASSERT_NE(queue2, queue3);
+
+ std::vector<EnqueueOrder> run_order;
+ queue1->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ queue2->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+ queue3->PostTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order));
+
+ queue2->ShutdownTaskQueue();
+ RunLoop().RunUntilIdle();
+
+ EXPECT_THAT(run_order, ElementsAre(1u, 3u));
+}
+
+TEST_P(SequenceManagerTest, ShutdownTaskQueue_WithDelayedTasks) {
+ CreateTaskQueues(2u);
+
+ // Register three delayed tasks
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order),
+ TimeDelta::FromMilliseconds(10));
+ runners_[1]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order),
+ TimeDelta::FromMilliseconds(20));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order),
+ TimeDelta::FromMilliseconds(30));
+
+ runners_[1]->ShutdownTaskQueue();
+ RunLoop().RunUntilIdle();
+
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(40));
+ ASSERT_THAT(run_order, ElementsAre(1u, 3u));
+}
+
+namespace {
+void ShutdownQueue(scoped_refptr<TaskQueue> queue) {
+ queue->ShutdownTaskQueue();
+}
+} // namespace
+
+TEST_P(SequenceManagerTest, ShutdownTaskQueue_InTasks) {
+ CreateTaskQueues(3u);
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&ShutdownQueue, runners_[1]));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&ShutdownQueue, runners_[2]));
+ runners_[1]->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+ runners_[2]->PostTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order));
+
+ RunLoop().RunUntilIdle();
+ ASSERT_THAT(run_order, ElementsAre(1u));
+}
+
+namespace {
+
+class MockObserver : public SequenceManager::Observer {
+ public:
+ MOCK_METHOD0(OnTriedToExecuteBlockedTask, void());
+ MOCK_METHOD0(OnBeginNestedRunLoop, void());
+ MOCK_METHOD0(OnExitNestedRunLoop, void());
+};
+
+} // namespace
+
+TEST_P(SequenceManagerTestWithMessageLoop, ShutdownTaskQueueInNestedLoop) {
+ CreateTaskQueues(1u);
+
+ // We retain a reference to the task queue even when the manager has deleted
+ // its reference.
+ scoped_refptr<TaskQueue> task_queue = CreateTaskQueue();
+
+ std::vector<bool> log;
+ std::vector<std::pair<OnceClosure, bool>> tasks_to_post_from_nested_loop;
+
+ // Inside a nested run loop, call task_queue->ShutdownTaskQueue, bookended
+ // by calls to HasOneRefTask to make sure the manager doesn't release its
+ // reference until the nested run loop exits.
+ // NB: This first HasOneRefTask is a sanity check.
+ tasks_to_post_from_nested_loop.push_back(
+ std::make_pair(BindOnce(&NopTask), true));
+ tasks_to_post_from_nested_loop.push_back(std::make_pair(
+ BindOnce(&TaskQueue::ShutdownTaskQueue, Unretained(task_queue.get())),
+ true));
+ tasks_to_post_from_nested_loop.push_back(
+ std::make_pair(BindOnce(&NopTask), true));
+ runners_[0]->PostTask(
+ FROM_HERE, BindOnce(&PostFromNestedRunloop, RetainedRef(runners_[0]),
+ Unretained(&tasks_to_post_from_nested_loop)));
+ RunLoop().RunUntilIdle();
+
+ // Just make sure that we don't crash.
+}
+
+TEST_P(SequenceManagerTest, TimeDomainsAreIndependant) {
+ CreateTaskQueues(2u);
+
+ TimeTicks start_time_ticks = manager_->NowTicks();
+ std::unique_ptr<MockTimeDomain> domain_a =
+ std::make_unique<MockTimeDomain>(start_time_ticks);
+ std::unique_ptr<MockTimeDomain> domain_b =
+ std::make_unique<MockTimeDomain>(start_time_ticks);
+ manager_->RegisterTimeDomain(domain_a.get());
+ manager_->RegisterTimeDomain(domain_b.get());
+ runners_[0]->SetTimeDomain(domain_a.get());
+ runners_[1]->SetTimeDomain(domain_b.get());
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order),
+ TimeDelta::FromMilliseconds(10));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order),
+ TimeDelta::FromMilliseconds(20));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order),
+ TimeDelta::FromMilliseconds(30));
+
+ runners_[1]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 4, &run_order),
+ TimeDelta::FromMilliseconds(10));
+ runners_[1]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 5, &run_order),
+ TimeDelta::FromMilliseconds(20));
+ runners_[1]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 6, &run_order),
+ TimeDelta::FromMilliseconds(30));
+
+ domain_b->SetNowTicks(start_time_ticks + TimeDelta::FromMilliseconds(50));
+ manager_->MaybeScheduleImmediateWork(FROM_HERE);
+
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(4u, 5u, 6u));
+
+ domain_a->SetNowTicks(start_time_ticks + TimeDelta::FromMilliseconds(50));
+ manager_->MaybeScheduleImmediateWork(FROM_HERE);
+
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(4u, 5u, 6u, 1u, 2u, 3u));
+
+ runners_[0]->ShutdownTaskQueue();
+ runners_[1]->ShutdownTaskQueue();
+
+ manager_->UnregisterTimeDomain(domain_a.get());
+ manager_->UnregisterTimeDomain(domain_b.get());
+}
+
+TEST_P(SequenceManagerTest, TimeDomainMigration) {
+ CreateTaskQueues(1u);
+
+ TimeTicks start_time_ticks = manager_->NowTicks();
+ std::unique_ptr<MockTimeDomain> domain_a =
+ std::make_unique<MockTimeDomain>(start_time_ticks);
+ manager_->RegisterTimeDomain(domain_a.get());
+ runners_[0]->SetTimeDomain(domain_a.get());
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order),
+ TimeDelta::FromMilliseconds(10));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order),
+ TimeDelta::FromMilliseconds(20));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order),
+ TimeDelta::FromMilliseconds(30));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 4, &run_order),
+ TimeDelta::FromMilliseconds(40));
+
+ domain_a->SetNowTicks(start_time_ticks + TimeDelta::FromMilliseconds(20));
+ manager_->MaybeScheduleImmediateWork(FROM_HERE);
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u));
+
+ std::unique_ptr<MockTimeDomain> domain_b =
+ std::make_unique<MockTimeDomain>(start_time_ticks);
+ manager_->RegisterTimeDomain(domain_b.get());
+ runners_[0]->SetTimeDomain(domain_b.get());
+
+ domain_b->SetNowTicks(start_time_ticks + TimeDelta::FromMilliseconds(50));
+ manager_->MaybeScheduleImmediateWork(FROM_HERE);
+
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u, 3u, 4u));
+
+ runners_[0]->ShutdownTaskQueue();
+
+ manager_->UnregisterTimeDomain(domain_a.get());
+ manager_->UnregisterTimeDomain(domain_b.get());
+}
+
+TEST_P(SequenceManagerTest, TimeDomainMigrationWithIncomingImmediateTasks) {
+ CreateTaskQueues(1u);
+
+ TimeTicks start_time_ticks = manager_->NowTicks();
+ std::unique_ptr<MockTimeDomain> domain_a =
+ std::make_unique<MockTimeDomain>(start_time_ticks);
+ std::unique_ptr<MockTimeDomain> domain_b =
+ std::make_unique<MockTimeDomain>(start_time_ticks);
+ manager_->RegisterTimeDomain(domain_a.get());
+ manager_->RegisterTimeDomain(domain_b.get());
+
+ runners_[0]->SetTimeDomain(domain_a.get());
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ runners_[0]->SetTimeDomain(domain_b.get());
+
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(run_order, ElementsAre(1u));
+
+ runners_[0]->ShutdownTaskQueue();
+
+ manager_->UnregisterTimeDomain(domain_a.get());
+ manager_->UnregisterTimeDomain(domain_b.get());
+}
+
+TEST_P(SequenceManagerTest,
+ PostDelayedTasksReverseOrderAlternatingTimeDomains) {
+ CreateTaskQueues(1u);
+
+ std::vector<EnqueueOrder> run_order;
+
+ std::unique_ptr<internal::RealTimeDomain> domain_a =
+ std::make_unique<internal::RealTimeDomain>();
+ std::unique_ptr<internal::RealTimeDomain> domain_b =
+ std::make_unique<internal::RealTimeDomain>();
+ manager_->RegisterTimeDomain(domain_a.get());
+ manager_->RegisterTimeDomain(domain_b.get());
+
+ runners_[0]->SetTimeDomain(domain_a.get());
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order),
+ TimeDelta::FromMilliseconds(40));
+
+ runners_[0]->SetTimeDomain(domain_b.get());
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order),
+ TimeDelta::FromMilliseconds(30));
+
+ runners_[0]->SetTimeDomain(domain_a.get());
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order),
+ TimeDelta::FromMilliseconds(20));
+
+ runners_[0]->SetTimeDomain(domain_b.get());
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&TestTask, 4, &run_order),
+ TimeDelta::FromMilliseconds(10));
+
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(40));
+ EXPECT_THAT(run_order, ElementsAre(4u, 3u, 2u, 1u));
+
+ runners_[0]->ShutdownTaskQueue();
+
+ manager_->UnregisterTimeDomain(domain_a.get());
+ manager_->UnregisterTimeDomain(domain_b.get());
+}
+
+namespace {
+
+class MockTaskQueueObserver : public TaskQueue::Observer {
+ public:
+ ~MockTaskQueueObserver() override = default;
+
+ MOCK_METHOD2(OnQueueNextWakeUpChanged, void(TaskQueue*, TimeTicks));
+};
+
+} // namespace
+
+TEST_P(SequenceManagerTest, TaskQueueObserver_ImmediateTask) {
+ CreateTaskQueues(1u);
+
+ MockTaskQueueObserver observer;
+ runners_[0]->SetObserver(&observer);
+
+ // We should get a notification when a task is posted on an empty queue.
+ EXPECT_CALL(observer, OnQueueNextWakeUpChanged(runners_[0].get(), _));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&NopTask));
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // But not subsequently.
+ EXPECT_CALL(observer, OnQueueNextWakeUpChanged(_, _)).Times(0);
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&NopTask));
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Unless the immediate work queue is emptied.
+ runners_[0]->GetTaskQueueImpl()->ReloadImmediateWorkQueueIfEmpty();
+ EXPECT_CALL(observer, OnQueueNextWakeUpChanged(runners_[0].get(), _));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&NopTask));
+
+ // Tidy up.
+ runners_[0]->ShutdownTaskQueue();
+}
+
+TEST_P(SequenceManagerTest, TaskQueueObserver_DelayedTask) {
+ CreateTaskQueues(1u);
+
+ TimeTicks start_time = manager_->NowTicks();
+ TimeDelta delay10s(TimeDelta::FromSeconds(10));
+ TimeDelta delay100s(TimeDelta::FromSeconds(100));
+ TimeDelta delay1s(TimeDelta::FromSeconds(1));
+
+ MockTaskQueueObserver observer;
+ runners_[0]->SetObserver(&observer);
+
+ // We should get a notification when a delayed task is posted on an empty
+ // queue.
+ EXPECT_CALL(observer, OnQueueNextWakeUpChanged(runners_[0].get(),
+ start_time + delay10s));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), delay10s);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // We should not get a notification for a longer delay.
+ EXPECT_CALL(observer, OnQueueNextWakeUpChanged(_, _)).Times(0);
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), delay100s);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // We should get a notification for a shorter delay.
+ EXPECT_CALL(observer, OnQueueNextWakeUpChanged(runners_[0].get(),
+ start_time + delay1s));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), delay1s);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
+ runners_[0]->CreateQueueEnabledVoter();
+ voter->SetQueueEnabled(false);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // When a queue has been enabled, we may get a notification if the
+ // TimeDomain's next scheduled wake-up has changed.
+ EXPECT_CALL(observer, OnQueueNextWakeUpChanged(runners_[0].get(),
+ start_time + delay1s));
+ voter->SetQueueEnabled(true);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Tidy up.
+ runners_[0]->ShutdownTaskQueue();
+}
+
+TEST_P(SequenceManagerTest, TaskQueueObserver_DelayedTaskMultipleQueues) {
+ CreateTaskQueues(2u);
+
+ MockTaskQueueObserver observer;
+ runners_[0]->SetObserver(&observer);
+ runners_[1]->SetObserver(&observer);
+
+ TimeTicks start_time = manager_->NowTicks();
+ TimeDelta delay1s(TimeDelta::FromSeconds(1));
+ TimeDelta delay10s(TimeDelta::FromSeconds(10));
+
+ EXPECT_CALL(observer,
+ OnQueueNextWakeUpChanged(runners_[0].get(), start_time + delay1s))
+ .Times(1);
+ EXPECT_CALL(observer, OnQueueNextWakeUpChanged(runners_[1].get(),
+ start_time + delay10s))
+ .Times(1);
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), delay1s);
+ runners_[1]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), delay10s);
+ testing::Mock::VerifyAndClearExpectations(&observer);
+
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter0 =
+ runners_[0]->CreateQueueEnabledVoter();
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter1 =
+ runners_[1]->CreateQueueEnabledVoter();
+
+ // Disabling a queue should not trigger a notification.
+ EXPECT_CALL(observer, OnQueueNextWakeUpChanged(_, _)).Times(0);
+ voter0->SetQueueEnabled(false);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Re-enabling it should should also trigger a notification.
+ EXPECT_CALL(observer, OnQueueNextWakeUpChanged(runners_[0].get(),
+ start_time + delay1s));
+ voter0->SetQueueEnabled(true);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Disabling a queue should not trigger a notification.
+ EXPECT_CALL(observer, OnQueueNextWakeUpChanged(_, _)).Times(0);
+ voter1->SetQueueEnabled(false);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Re-enabling it should should trigger a notification.
+ EXPECT_CALL(observer, OnQueueNextWakeUpChanged(runners_[1].get(),
+ start_time + delay10s));
+ voter1->SetQueueEnabled(true);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Tidy up.
+ EXPECT_CALL(observer, OnQueueNextWakeUpChanged(_, _)).Times(AnyNumber());
+ runners_[0]->ShutdownTaskQueue();
+ runners_[1]->ShutdownTaskQueue();
+}
+
+TEST_P(SequenceManagerTest, TaskQueueObserver_DelayedWorkWhichCanRunNow) {
+ // This test checks that when delayed work becomes available
+ // the notification still fires. This usually happens when time advances
+ // and task becomes available in the middle of the scheduling code.
+ // For this test we rely on the fact that notification dispatching code
+ // is the same in all conditions and just change a time domain to
+ // trigger notification.
+
+ CreateTaskQueues(1u);
+
+ TimeDelta delay1s(TimeDelta::FromSeconds(1));
+ TimeDelta delay10s(TimeDelta::FromSeconds(10));
+
+ MockTaskQueueObserver observer;
+ runners_[0]->SetObserver(&observer);
+
+ // We should get a notification when a delayed task is posted on an empty
+ // queue.
+ EXPECT_CALL(observer, OnQueueNextWakeUpChanged(_, _));
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), delay1s);
+ Mock::VerifyAndClearExpectations(&observer);
+
+ std::unique_ptr<TimeDomain> mock_time_domain =
+ std::make_unique<internal::RealTimeDomain>();
+ manager_->RegisterTimeDomain(mock_time_domain.get());
+
+ test_task_runner_->AdvanceMockTickClock(delay10s);
+
+ EXPECT_CALL(observer, OnQueueNextWakeUpChanged(_, _));
+ runners_[0]->SetTimeDomain(mock_time_domain.get());
+ Mock::VerifyAndClearExpectations(&observer);
+
+ // Tidy up.
+ runners_[0]->ShutdownTaskQueue();
+}
+
+class CancelableTask {
+ public:
+ explicit CancelableTask(const TickClock* clock)
+ : clock_(clock), weak_factory_(this) {}
+
+ void RecordTimeTask(std::vector<TimeTicks>* run_times) {
+ run_times->push_back(clock_->NowTicks());
+ }
+
+ const TickClock* clock_;
+ WeakPtrFactory<CancelableTask> weak_factory_;
+};
+
+TEST_P(SequenceManagerTest, TaskQueueObserver_SweepCanceledDelayedTasks) {
+ CreateTaskQueues(1u);
+
+ MockTaskQueueObserver observer;
+ runners_[0]->SetObserver(&observer);
+
+ TimeTicks start_time = manager_->NowTicks();
+ TimeDelta delay1(TimeDelta::FromSeconds(5));
+ TimeDelta delay2(TimeDelta::FromSeconds(10));
+
+ EXPECT_CALL(observer,
+ OnQueueNextWakeUpChanged(runners_[0].get(), start_time + delay1))
+ .Times(1);
+
+ CancelableTask task1(GetTickClock());
+ CancelableTask task2(GetTickClock());
+ std::vector<TimeTicks> run_times;
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task1.weak_factory_.GetWeakPtr(), &run_times),
+ delay1);
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task2.weak_factory_.GetWeakPtr(), &run_times),
+ delay2);
+
+ task1.weak_factory_.InvalidateWeakPtrs();
+
+ // Sweeping away canceled delayed tasks should trigger a notification.
+ EXPECT_CALL(observer,
+ OnQueueNextWakeUpChanged(runners_[0].get(), start_time + delay2))
+ .Times(1);
+ manager_->SweepCanceledDelayedTasks();
+}
+
+namespace {
+void ChromiumRunloopInspectionTask(
+ scoped_refptr<TestMockTimeTaskRunner> test_task_runner) {
+ // We don't expect more than 1 pending task at any time.
+ EXPECT_GE(1u, test_task_runner->GetPendingTaskCount());
+}
+} // namespace
+
+TEST_P(SequenceManagerTest, NumberOfPendingTasksOnChromiumRunLoop) {
+ CreateTaskQueues(1u);
+
+ // NOTE because tasks posted to the chromiumrun loop are not cancellable, we
+ // will end up with a lot more tasks posted if the delayed tasks were posted
+ // in the reverse order.
+ // TODO(alexclarke): Consider talking to the message pump directly.
+ for (int i = 1; i < 100; i++) {
+ runners_[0]->PostDelayedTask(
+ FROM_HERE, BindOnce(&ChromiumRunloopInspectionTask, test_task_runner_),
+ TimeDelta::FromMilliseconds(i));
+ }
+ test_task_runner_->FastForwardUntilNoTasksRemain();
+}
+
+namespace {
+
+class QuadraticTask {
+ public:
+ QuadraticTask(scoped_refptr<TaskQueue> task_queue,
+ TimeDelta delay,
+ scoped_refptr<TestMockTimeTaskRunner> test_task_runner)
+ : count_(0),
+ task_queue_(task_queue),
+ delay_(delay),
+ test_task_runner_(test_task_runner) {}
+
+ void SetShouldExit(RepeatingCallback<bool()> should_exit) {
+ should_exit_ = should_exit;
+ }
+
+ void Run() {
+ if (should_exit_.Run())
+ return;
+ count_++;
+ task_queue_->PostDelayedTask(
+ FROM_HERE, BindOnce(&QuadraticTask::Run, Unretained(this)), delay_);
+ task_queue_->PostDelayedTask(
+ FROM_HERE, BindOnce(&QuadraticTask::Run, Unretained(this)), delay_);
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(5));
+ }
+
+ int Count() const { return count_; }
+
+ private:
+ int count_;
+ scoped_refptr<TaskQueue> task_queue_;
+ TimeDelta delay_;
+ RepeatingCallback<bool()> should_exit_;
+ scoped_refptr<TestMockTimeTaskRunner> test_task_runner_;
+};
+
+class LinearTask {
+ public:
+ LinearTask(scoped_refptr<TaskQueue> task_queue,
+ TimeDelta delay,
+ scoped_refptr<TestMockTimeTaskRunner> test_task_runner)
+ : count_(0),
+ task_queue_(task_queue),
+ delay_(delay),
+ test_task_runner_(test_task_runner) {}
+
+ void SetShouldExit(RepeatingCallback<bool()> should_exit) {
+ should_exit_ = should_exit;
+ }
+
+ void Run() {
+ if (should_exit_.Run())
+ return;
+ count_++;
+ task_queue_->PostDelayedTask(
+ FROM_HERE, BindOnce(&LinearTask::Run, Unretained(this)), delay_);
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(5));
+ }
+
+ int Count() const { return count_; }
+
+ private:
+ int count_;
+ scoped_refptr<TaskQueue> task_queue_;
+ TimeDelta delay_;
+ RepeatingCallback<bool()> should_exit_;
+ scoped_refptr<TestMockTimeTaskRunner> test_task_runner_;
+};
+
+bool ShouldExit(QuadraticTask* quadratic_task, LinearTask* linear_task) {
+ return quadratic_task->Count() == 1000 || linear_task->Count() == 1000;
+}
+
+} // namespace
+
+TEST_P(SequenceManagerTest,
+ DelayedTasksDontBadlyStarveNonDelayedWork_SameQueue) {
+ CreateTaskQueues(1u);
+
+ QuadraticTask quadratic_delayed_task(
+ runners_[0], TimeDelta::FromMilliseconds(10), test_task_runner_);
+ LinearTask linear_immediate_task(runners_[0], TimeDelta(), test_task_runner_);
+ RepeatingCallback<bool()> should_exit = BindRepeating(
+ ShouldExit, &quadratic_delayed_task, &linear_immediate_task);
+ quadratic_delayed_task.SetShouldExit(should_exit);
+ linear_immediate_task.SetShouldExit(should_exit);
+
+ quadratic_delayed_task.Run();
+ linear_immediate_task.Run();
+
+ test_task_runner_->FastForwardUntilNoTasksRemain();
+
+ double ratio = static_cast<double>(linear_immediate_task.Count()) /
+ static_cast<double>(quadratic_delayed_task.Count());
+
+ EXPECT_GT(ratio, 0.333);
+ EXPECT_LT(ratio, 1.1);
+}
+
+TEST_P(SequenceManagerTest, ImmediateWorkCanStarveDelayedTasks_SameQueue) {
+ CreateTaskQueues(1u);
+
+ QuadraticTask quadratic_immediate_task(runners_[0], TimeDelta(),
+ test_task_runner_);
+ LinearTask linear_delayed_task(runners_[0], TimeDelta::FromMilliseconds(10),
+ test_task_runner_);
+ RepeatingCallback<bool()> should_exit = BindRepeating(
+ &ShouldExit, &quadratic_immediate_task, &linear_delayed_task);
+
+ quadratic_immediate_task.SetShouldExit(should_exit);
+ linear_delayed_task.SetShouldExit(should_exit);
+
+ quadratic_immediate_task.Run();
+ linear_delayed_task.Run();
+
+ test_task_runner_->FastForwardUntilNoTasksRemain();
+
+ double ratio = static_cast<double>(linear_delayed_task.Count()) /
+ static_cast<double>(quadratic_immediate_task.Count());
+
+ // This is by design, we want to enforce a strict ordering in task execution
+ // where by delayed tasks can not skip ahead of non-delayed work.
+ EXPECT_GT(ratio, 0.0);
+ EXPECT_LT(ratio, 0.1);
+}
+
+TEST_P(SequenceManagerTest,
+ DelayedTasksDontBadlyStarveNonDelayedWork_DifferentQueue) {
+ CreateTaskQueues(2u);
+
+ QuadraticTask quadratic_delayed_task(
+ runners_[0], TimeDelta::FromMilliseconds(10), test_task_runner_);
+ LinearTask linear_immediate_task(runners_[1], TimeDelta(), test_task_runner_);
+ RepeatingCallback<bool()> should_exit = BindRepeating(
+ ShouldExit, &quadratic_delayed_task, &linear_immediate_task);
+ quadratic_delayed_task.SetShouldExit(should_exit);
+ linear_immediate_task.SetShouldExit(should_exit);
+
+ quadratic_delayed_task.Run();
+ linear_immediate_task.Run();
+
+ test_task_runner_->FastForwardUntilNoTasksRemain();
+
+ double ratio = static_cast<double>(linear_immediate_task.Count()) /
+ static_cast<double>(quadratic_delayed_task.Count());
+
+ EXPECT_GT(ratio, 0.333);
+ EXPECT_LT(ratio, 1.1);
+}
+
+TEST_P(SequenceManagerTest, ImmediateWorkCanStarveDelayedTasks_DifferentQueue) {
+ CreateTaskQueues(2u);
+
+ QuadraticTask quadratic_immediate_task(runners_[0], TimeDelta(),
+ test_task_runner_);
+ LinearTask linear_delayed_task(runners_[1], TimeDelta::FromMilliseconds(10),
+ test_task_runner_);
+ RepeatingCallback<bool()> should_exit = BindRepeating(
+ &ShouldExit, &quadratic_immediate_task, &linear_delayed_task);
+
+ quadratic_immediate_task.SetShouldExit(should_exit);
+ linear_delayed_task.SetShouldExit(should_exit);
+
+ quadratic_immediate_task.Run();
+ linear_delayed_task.Run();
+
+ test_task_runner_->FastForwardUntilNoTasksRemain();
+
+ double ratio = static_cast<double>(linear_delayed_task.Count()) /
+ static_cast<double>(quadratic_immediate_task.Count());
+
+ // This is by design, we want to enforce a strict ordering in task execution
+ // where by delayed tasks can not skip ahead of non-delayed work.
+ EXPECT_GT(ratio, 0.0);
+ EXPECT_LT(ratio, 0.1);
+}
+
+TEST_P(SequenceManagerTest, CurrentlyExecutingTaskQueue_NoTaskRunning) {
+ CreateTaskQueues(1u);
+
+ EXPECT_EQ(nullptr, manager_->currently_executing_task_queue());
+}
+
+namespace {
+void CurrentlyExecutingTaskQueueTestTask(
+ SequenceManagerImpl* sequence_manager,
+ std::vector<internal::TaskQueueImpl*>* task_sources) {
+ task_sources->push_back(sequence_manager->currently_executing_task_queue());
+}
+} // namespace
+
+TEST_P(SequenceManagerTest, CurrentlyExecutingTaskQueue_TaskRunning) {
+ CreateTaskQueues(2u);
+
+ TestTaskQueue* queue0 = runners_[0].get();
+ TestTaskQueue* queue1 = runners_[1].get();
+
+ std::vector<internal::TaskQueueImpl*> task_sources;
+ queue0->PostTask(FROM_HERE, BindOnce(&CurrentlyExecutingTaskQueueTestTask,
+ manager_.get(), &task_sources));
+ queue1->PostTask(FROM_HERE, BindOnce(&CurrentlyExecutingTaskQueueTestTask,
+ manager_.get(), &task_sources));
+ RunLoop().RunUntilIdle();
+
+ EXPECT_THAT(task_sources, ElementsAre(queue0->GetTaskQueueImpl(),
+ queue1->GetTaskQueueImpl()));
+ EXPECT_EQ(nullptr, manager_->currently_executing_task_queue());
+}
+
+namespace {
+void RunloopCurrentlyExecutingTaskQueueTestTask(
+ SequenceManagerImpl* sequence_manager,
+ std::vector<internal::TaskQueueImpl*>* task_sources,
+ std::vector<std::pair<OnceClosure, TestTaskQueue*>>* tasks) {
+ task_sources->push_back(sequence_manager->currently_executing_task_queue());
+
+ for (std::pair<OnceClosure, TestTaskQueue*>& pair : *tasks) {
+ pair.second->PostTask(FROM_HERE, std::move(pair.first));
+ }
+
+ RunLoop(RunLoop::Type::kNestableTasksAllowed).RunUntilIdle();
+ task_sources->push_back(sequence_manager->currently_executing_task_queue());
+}
+} // namespace
+
+TEST_P(SequenceManagerTestWithMessageLoop,
+ CurrentlyExecutingTaskQueue_NestedLoop) {
+ CreateTaskQueues(3u);
+
+ TestTaskQueue* queue0 = runners_[0].get();
+ TestTaskQueue* queue1 = runners_[1].get();
+ TestTaskQueue* queue2 = runners_[2].get();
+
+ std::vector<internal::TaskQueueImpl*> task_sources;
+ std::vector<std::pair<OnceClosure, TestTaskQueue*>>
+ tasks_to_post_from_nested_loop;
+ tasks_to_post_from_nested_loop.push_back(
+ std::make_pair(BindOnce(&CurrentlyExecutingTaskQueueTestTask,
+ manager_.get(), &task_sources),
+ queue1));
+ tasks_to_post_from_nested_loop.push_back(
+ std::make_pair(BindOnce(&CurrentlyExecutingTaskQueueTestTask,
+ manager_.get(), &task_sources),
+ queue2));
+
+ queue0->PostTask(
+ FROM_HERE,
+ BindOnce(&RunloopCurrentlyExecutingTaskQueueTestTask, manager_.get(),
+ &task_sources, &tasks_to_post_from_nested_loop));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_THAT(
+ task_sources,
+ ElementsAre(queue0->GetTaskQueueImpl(), queue1->GetTaskQueueImpl(),
+ queue2->GetTaskQueueImpl(), queue0->GetTaskQueueImpl()));
+ EXPECT_EQ(nullptr, manager_->currently_executing_task_queue());
+}
+
+TEST_P(SequenceManagerTestWithMessageLoop, BlameContextAttribution) {
+ using trace_analyzer::Query;
+
+ CreateTaskQueues(1u);
+ TestTaskQueue* queue = runners_[0].get();
+
+ trace_analyzer::Start("*");
+ {
+ trace_event::BlameContext blame_context("cat", "name", "type", "scope", 0,
+ nullptr);
+ blame_context.Initialize();
+ queue->SetBlameContext(&blame_context);
+ queue->PostTask(FROM_HERE, BindOnce(&NopTask));
+ RunLoop().RunUntilIdle();
+ }
+ auto analyzer = trace_analyzer::Stop();
+
+ trace_analyzer::TraceEventVector events;
+ Query q = Query::EventPhaseIs(TRACE_EVENT_PHASE_ENTER_CONTEXT) ||
+ Query::EventPhaseIs(TRACE_EVENT_PHASE_LEAVE_CONTEXT);
+ analyzer->FindEvents(q, &events);
+
+ EXPECT_EQ(2u, events.size());
+}
+
+TEST_P(SequenceManagerTest, NoWakeUpsForCanceledDelayedTasks) {
+ CreateTaskQueues(1u);
+
+ TimeTicks start_time = manager_->NowTicks();
+
+ CancelableTask task1(GetTickClock());
+ CancelableTask task2(GetTickClock());
+ CancelableTask task3(GetTickClock());
+ CancelableTask task4(GetTickClock());
+ TimeDelta delay1(TimeDelta::FromSeconds(5));
+ TimeDelta delay2(TimeDelta::FromSeconds(10));
+ TimeDelta delay3(TimeDelta::FromSeconds(15));
+ TimeDelta delay4(TimeDelta::FromSeconds(30));
+ std::vector<TimeTicks> run_times;
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task1.weak_factory_.GetWeakPtr(), &run_times),
+ delay1);
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task2.weak_factory_.GetWeakPtr(), &run_times),
+ delay2);
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task3.weak_factory_.GetWeakPtr(), &run_times),
+ delay3);
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task4.weak_factory_.GetWeakPtr(), &run_times),
+ delay4);
+
+ task2.weak_factory_.InvalidateWeakPtrs();
+ task3.weak_factory_.InvalidateWeakPtrs();
+
+ std::set<TimeTicks> wake_up_times;
+
+ RunUntilManagerIsIdle(BindRepeating(
+ [](std::set<TimeTicks>* wake_up_times, const TickClock* clock) {
+ wake_up_times->insert(clock->NowTicks());
+ },
+ &wake_up_times, GetTickClock()));
+
+ EXPECT_THAT(wake_up_times,
+ ElementsAre(start_time + delay1, start_time + delay4));
+ EXPECT_THAT(run_times, ElementsAre(start_time + delay1, start_time + delay4));
+}
+
+TEST_P(SequenceManagerTest, NoWakeUpsForCanceledDelayedTasksReversePostOrder) {
+ CreateTaskQueues(1u);
+
+ TimeTicks start_time = manager_->NowTicks();
+
+ CancelableTask task1(GetTickClock());
+ CancelableTask task2(GetTickClock());
+ CancelableTask task3(GetTickClock());
+ CancelableTask task4(GetTickClock());
+ TimeDelta delay1(TimeDelta::FromSeconds(5));
+ TimeDelta delay2(TimeDelta::FromSeconds(10));
+ TimeDelta delay3(TimeDelta::FromSeconds(15));
+ TimeDelta delay4(TimeDelta::FromSeconds(30));
+ std::vector<TimeTicks> run_times;
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task4.weak_factory_.GetWeakPtr(), &run_times),
+ delay4);
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task3.weak_factory_.GetWeakPtr(), &run_times),
+ delay3);
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task2.weak_factory_.GetWeakPtr(), &run_times),
+ delay2);
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task1.weak_factory_.GetWeakPtr(), &run_times),
+ delay1);
+
+ task2.weak_factory_.InvalidateWeakPtrs();
+ task3.weak_factory_.InvalidateWeakPtrs();
+
+ std::set<TimeTicks> wake_up_times;
+
+ RunUntilManagerIsIdle(BindRepeating(
+ [](std::set<TimeTicks>* wake_up_times, const TickClock* clock) {
+ wake_up_times->insert(clock->NowTicks());
+ },
+ &wake_up_times, GetTickClock()));
+
+ EXPECT_THAT(wake_up_times,
+ ElementsAre(start_time + delay1, start_time + delay4));
+ EXPECT_THAT(run_times, ElementsAre(start_time + delay1, start_time + delay4));
+}
+
+TEST_P(SequenceManagerTest, TimeDomainWakeUpOnlyCancelledIfAllUsesCancelled) {
+ CreateTaskQueues(1u);
+
+ TimeTicks start_time = manager_->NowTicks();
+
+ CancelableTask task1(GetTickClock());
+ CancelableTask task2(GetTickClock());
+ CancelableTask task3(GetTickClock());
+ CancelableTask task4(GetTickClock());
+ TimeDelta delay1(TimeDelta::FromSeconds(5));
+ TimeDelta delay2(TimeDelta::FromSeconds(10));
+ TimeDelta delay3(TimeDelta::FromSeconds(15));
+ TimeDelta delay4(TimeDelta::FromSeconds(30));
+ std::vector<TimeTicks> run_times;
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task1.weak_factory_.GetWeakPtr(), &run_times),
+ delay1);
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task2.weak_factory_.GetWeakPtr(), &run_times),
+ delay2);
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task3.weak_factory_.GetWeakPtr(), &run_times),
+ delay3);
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task4.weak_factory_.GetWeakPtr(), &run_times),
+ delay4);
+
+ // Post a non-canceled task with |delay3|. So we should still get a wake-up at
+ // |delay3| even though we cancel |task3|.
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask, Unretained(&task3), &run_times),
+ delay3);
+
+ task2.weak_factory_.InvalidateWeakPtrs();
+ task3.weak_factory_.InvalidateWeakPtrs();
+ task1.weak_factory_.InvalidateWeakPtrs();
+
+ std::set<TimeTicks> wake_up_times;
+
+ RunUntilManagerIsIdle(BindRepeating(
+ [](std::set<TimeTicks>* wake_up_times, const TickClock* clock) {
+ wake_up_times->insert(clock->NowTicks());
+ },
+ &wake_up_times, GetTickClock()));
+
+ EXPECT_THAT(wake_up_times,
+ ElementsAre(start_time + delay1, start_time + delay3,
+ start_time + delay4));
+
+ EXPECT_THAT(run_times, ElementsAre(start_time + delay3, start_time + delay4));
+}
+
+TEST_P(SequenceManagerTest, TaskQueueVoters) {
+ CreateTaskQueues(1u);
+
+ // The task queue should be initially enabled.
+ EXPECT_TRUE(runners_[0]->IsQueueEnabled());
+
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter1 =
+ runners_[0]->CreateQueueEnabledVoter();
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter2 =
+ runners_[0]->CreateQueueEnabledVoter();
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter3 =
+ runners_[0]->CreateQueueEnabledVoter();
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter4 =
+ runners_[0]->CreateQueueEnabledVoter();
+
+ // Voters should initially vote for the queue to be enabled.
+ EXPECT_TRUE(runners_[0]->IsQueueEnabled());
+
+ // If any voter wants to disable, the queue is disabled.
+ voter1->SetQueueEnabled(false);
+ EXPECT_FALSE(runners_[0]->IsQueueEnabled());
+
+ // If the voter is deleted then the queue should be re-enabled.
+ voter1.reset();
+ EXPECT_TRUE(runners_[0]->IsQueueEnabled());
+
+ // If any of the remaining voters wants to disable, the queue should be
+ // disabled.
+ voter2->SetQueueEnabled(false);
+ EXPECT_FALSE(runners_[0]->IsQueueEnabled());
+
+ // If another queue votes to disable, nothing happens because it's already
+ // disabled.
+ voter3->SetQueueEnabled(false);
+ EXPECT_FALSE(runners_[0]->IsQueueEnabled());
+
+ // There are two votes to disable, so one of them voting to enable does
+ // nothing.
+ voter2->SetQueueEnabled(true);
+ EXPECT_FALSE(runners_[0]->IsQueueEnabled());
+
+ // IF all queues vote to enable then the queue is enabled.
+ voter3->SetQueueEnabled(true);
+ EXPECT_TRUE(runners_[0]->IsQueueEnabled());
+}
+
+TEST_P(SequenceManagerTest, ShutdownQueueBeforeEnabledVoterDeleted) {
+ CreateTaskQueues(1u);
+
+ scoped_refptr<TaskQueue> queue = CreateTaskQueue();
+
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
+ queue->CreateQueueEnabledVoter();
+
+ voter->SetQueueEnabled(true); // NOP
+ queue->ShutdownTaskQueue();
+
+ // This should complete without DCHECKing.
+ voter.reset();
+}
+
+TEST_P(SequenceManagerTest, ShutdownQueueBeforeDisabledVoterDeleted) {
+ CreateTaskQueues(1u);
+
+ scoped_refptr<TaskQueue> queue = CreateTaskQueue();
+
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
+ queue->CreateQueueEnabledVoter();
+
+ voter->SetQueueEnabled(false);
+ queue->ShutdownTaskQueue();
+
+ // This should complete without DCHECKing.
+ voter.reset();
+}
+
+TEST_P(SequenceManagerTest, SweepCanceledDelayedTasks) {
+ CreateTaskQueues(1u);
+
+ CancelableTask task1(GetTickClock());
+ CancelableTask task2(GetTickClock());
+ CancelableTask task3(GetTickClock());
+ CancelableTask task4(GetTickClock());
+ TimeDelta delay1(TimeDelta::FromSeconds(5));
+ TimeDelta delay2(TimeDelta::FromSeconds(10));
+ TimeDelta delay3(TimeDelta::FromSeconds(15));
+ TimeDelta delay4(TimeDelta::FromSeconds(30));
+ std::vector<TimeTicks> run_times;
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task1.weak_factory_.GetWeakPtr(), &run_times),
+ delay1);
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task2.weak_factory_.GetWeakPtr(), &run_times),
+ delay2);
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task3.weak_factory_.GetWeakPtr(), &run_times),
+ delay3);
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task4.weak_factory_.GetWeakPtr(), &run_times),
+ delay4);
+
+ EXPECT_EQ(4u, runners_[0]->GetNumberOfPendingTasks());
+ task2.weak_factory_.InvalidateWeakPtrs();
+ task3.weak_factory_.InvalidateWeakPtrs();
+ EXPECT_EQ(4u, runners_[0]->GetNumberOfPendingTasks());
+
+ manager_->SweepCanceledDelayedTasks();
+ EXPECT_EQ(2u, runners_[0]->GetNumberOfPendingTasks());
+
+ task1.weak_factory_.InvalidateWeakPtrs();
+ task4.weak_factory_.InvalidateWeakPtrs();
+
+ manager_->SweepCanceledDelayedTasks();
+ EXPECT_EQ(0u, runners_[0]->GetNumberOfPendingTasks());
+}
+
+TEST_P(SequenceManagerTest, DelayTillNextTask) {
+ CreateTaskQueues(2u);
+
+ LazyNow lazy_now(GetTickClock());
+ EXPECT_EQ(TimeDelta::Max(), manager_->DelayTillNextTask(&lazy_now));
+
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask),
+ TimeDelta::FromSeconds(10));
+
+ EXPECT_EQ(TimeDelta::FromSeconds(10), manager_->DelayTillNextTask(&lazy_now));
+
+ runners_[1]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask),
+ TimeDelta::FromSeconds(15));
+
+ EXPECT_EQ(TimeDelta::FromSeconds(10), manager_->DelayTillNextTask(&lazy_now));
+
+ runners_[1]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask),
+ TimeDelta::FromSeconds(5));
+
+ EXPECT_EQ(TimeDelta::FromSeconds(5), manager_->DelayTillNextTask(&lazy_now));
+
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&NopTask));
+
+ EXPECT_EQ(TimeDelta(), manager_->DelayTillNextTask(&lazy_now));
+}
+
+TEST_P(SequenceManagerTest, DelayTillNextTask_Disabled) {
+ CreateTaskQueues(1u);
+
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
+ runners_[0]->CreateQueueEnabledVoter();
+ voter->SetQueueEnabled(false);
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&NopTask));
+
+ LazyNow lazy_now(GetTickClock());
+ EXPECT_EQ(TimeDelta::Max(), manager_->DelayTillNextTask(&lazy_now));
+}
+
+TEST_P(SequenceManagerTest, DelayTillNextTask_Fence) {
+ CreateTaskQueues(1u);
+
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&NopTask));
+
+ LazyNow lazy_now(GetTickClock());
+ EXPECT_EQ(TimeDelta::Max(), manager_->DelayTillNextTask(&lazy_now));
+}
+
+TEST_P(SequenceManagerTest, DelayTillNextTask_FenceUnblocking) {
+ CreateTaskQueues(1u);
+
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&NopTask));
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+
+ LazyNow lazy_now(GetTickClock());
+ EXPECT_EQ(TimeDelta(), manager_->DelayTillNextTask(&lazy_now));
+}
+
+TEST_P(SequenceManagerTest, DelayTillNextTask_DelayedTaskReady) {
+ CreateTaskQueues(1u);
+
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask),
+ TimeDelta::FromSeconds(1));
+
+ test_task_runner_->AdvanceMockTickClock(TimeDelta::FromSeconds(10));
+
+ LazyNow lazy_now(GetTickClock());
+ EXPECT_EQ(TimeDelta(), manager_->DelayTillNextTask(&lazy_now));
+}
+
+namespace {
+void MessageLoopTaskWithDelayedQuit(SimpleTestTickClock* now_src,
+ scoped_refptr<TaskQueue> task_queue) {
+ RunLoop run_loop(RunLoop::Type::kNestableTasksAllowed);
+ task_queue->PostDelayedTask(FROM_HERE, run_loop.QuitClosure(),
+ TimeDelta::FromMilliseconds(100));
+ now_src->Advance(TimeDelta::FromMilliseconds(200));
+ run_loop.Run();
+}
+} // namespace
+
+TEST_P(SequenceManagerTestWithMessageLoop, DelayedTaskRunsInNestedMessageLoop) {
+ CreateTaskQueues(1u);
+ RunLoop run_loop;
+ runners_[0]->PostTask(FROM_HERE,
+ BindOnce(&MessageLoopTaskWithDelayedQuit, &mock_clock_,
+ RetainedRef(runners_[0])));
+ run_loop.RunUntilIdle();
+}
+
+namespace {
+void MessageLoopTaskWithImmediateQuit(OnceClosure non_nested_quit_closure,
+ scoped_refptr<TaskQueue> task_queue) {
+ RunLoop run_loop(RunLoop::Type::kNestableTasksAllowed);
+ // Needed because entering the nested run loop causes a DoWork to get
+ // posted.
+ task_queue->PostTask(FROM_HERE, BindOnce(&NopTask));
+ task_queue->PostTask(FROM_HERE, run_loop.QuitClosure());
+ run_loop.Run();
+ std::move(non_nested_quit_closure).Run();
+}
+} // namespace
+
+TEST_P(SequenceManagerTestWithMessageLoop,
+ DelayedNestedMessageLoopDoesntPreventTasksRunning) {
+ CreateTaskQueues(1u);
+ RunLoop run_loop;
+ runners_[0]->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(&MessageLoopTaskWithImmediateQuit, run_loop.QuitClosure(),
+ RetainedRef(runners_[0])),
+ TimeDelta::FromMilliseconds(100));
+
+ mock_clock_.Advance(TimeDelta::FromMilliseconds(200));
+ run_loop.Run();
+}
+
+TEST_P(SequenceManagerTest, CouldTaskRun_DisableAndReenable) {
+ CreateTaskQueues(1u);
+
+ EnqueueOrder enqueue_order = manager_->GetNextSequenceNumber();
+ EXPECT_TRUE(runners_[0]->GetTaskQueueImpl()->CouldTaskRun(enqueue_order));
+
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
+ runners_[0]->CreateQueueEnabledVoter();
+ voter->SetQueueEnabled(false);
+ EXPECT_FALSE(runners_[0]->GetTaskQueueImpl()->CouldTaskRun(enqueue_order));
+
+ voter->SetQueueEnabled(true);
+ EXPECT_TRUE(runners_[0]->GetTaskQueueImpl()->CouldTaskRun(enqueue_order));
+}
+
+TEST_P(SequenceManagerTest, CouldTaskRun_Fence) {
+ CreateTaskQueues(1u);
+
+ EnqueueOrder enqueue_order = manager_->GetNextSequenceNumber();
+ EXPECT_TRUE(runners_[0]->GetTaskQueueImpl()->CouldTaskRun(enqueue_order));
+
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+ EXPECT_TRUE(runners_[0]->GetTaskQueueImpl()->CouldTaskRun(enqueue_order));
+
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kBeginningOfTime);
+ EXPECT_FALSE(runners_[0]->GetTaskQueueImpl()->CouldTaskRun(enqueue_order));
+
+ runners_[0]->RemoveFence();
+ EXPECT_TRUE(runners_[0]->GetTaskQueueImpl()->CouldTaskRun(enqueue_order));
+}
+
+TEST_P(SequenceManagerTest, CouldTaskRun_FenceBeforeThenAfter) {
+ CreateTaskQueues(1u);
+
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+
+ EnqueueOrder enqueue_order = manager_->GetNextSequenceNumber();
+ EXPECT_FALSE(runners_[0]->GetTaskQueueImpl()->CouldTaskRun(enqueue_order));
+
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kNow);
+ EXPECT_TRUE(runners_[0]->GetTaskQueueImpl()->CouldTaskRun(enqueue_order));
+}
+
+TEST_P(SequenceManagerTest, DelayedDoWorkNotPostedForDisabledQueue) {
+ CreateTaskQueues(1u);
+
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask),
+ TimeDelta::FromMilliseconds(1));
+ ASSERT_TRUE(test_task_runner_->HasPendingTask());
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1),
+ test_task_runner_->NextPendingTaskDelay());
+
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
+ runners_[0]->CreateQueueEnabledVoter();
+ voter->SetQueueEnabled(false);
+ EXPECT_FALSE(test_task_runner_->HasPendingTask());
+
+ voter->SetQueueEnabled(true);
+ ASSERT_TRUE(test_task_runner_->HasPendingTask());
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1),
+ test_task_runner_->NextPendingTaskDelay());
+}
+
+TEST_P(SequenceManagerTest, DisablingQueuesChangesDelayTillNextDoWork) {
+ CreateTaskQueues(3u);
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask),
+ TimeDelta::FromMilliseconds(1));
+ runners_[1]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask),
+ TimeDelta::FromMilliseconds(10));
+ runners_[2]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask),
+ TimeDelta::FromMilliseconds(100));
+
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter0 =
+ runners_[0]->CreateQueueEnabledVoter();
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter1 =
+ runners_[1]->CreateQueueEnabledVoter();
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter2 =
+ runners_[2]->CreateQueueEnabledVoter();
+
+ ASSERT_TRUE(test_task_runner_->HasPendingTask());
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1),
+ test_task_runner_->NextPendingTaskDelay());
+
+ voter0->SetQueueEnabled(false);
+ ASSERT_TRUE(test_task_runner_->HasPendingTask());
+ EXPECT_EQ(TimeDelta::FromMilliseconds(10),
+ test_task_runner_->NextPendingTaskDelay());
+
+ voter1->SetQueueEnabled(false);
+ ASSERT_TRUE(test_task_runner_->HasPendingTask());
+ EXPECT_EQ(TimeDelta::FromMilliseconds(100),
+ test_task_runner_->NextPendingTaskDelay());
+
+ voter2->SetQueueEnabled(false);
+ EXPECT_FALSE(test_task_runner_->HasPendingTask());
+}
+
+TEST_P(SequenceManagerTest, GetNextScheduledWakeUp) {
+ CreateTaskQueues(1u);
+
+ EXPECT_EQ(nullopt, runners_[0]->GetNextScheduledWakeUp());
+
+ TimeTicks start_time = manager_->NowTicks();
+ TimeDelta delay1 = TimeDelta::FromMilliseconds(10);
+ TimeDelta delay2 = TimeDelta::FromMilliseconds(2);
+
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), delay1);
+ EXPECT_EQ(start_time + delay1, runners_[0]->GetNextScheduledWakeUp());
+
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask), delay2);
+ EXPECT_EQ(start_time + delay2, runners_[0]->GetNextScheduledWakeUp());
+
+ // We don't have wake-ups scheduled for disabled queues.
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
+ runners_[0]->CreateQueueEnabledVoter();
+ voter->SetQueueEnabled(false);
+ EXPECT_EQ(nullopt, runners_[0]->GetNextScheduledWakeUp());
+
+ voter->SetQueueEnabled(true);
+ EXPECT_EQ(start_time + delay2, runners_[0]->GetNextScheduledWakeUp());
+
+ // Immediate tasks shouldn't make any difference.
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&NopTask));
+ EXPECT_EQ(start_time + delay2, runners_[0]->GetNextScheduledWakeUp());
+
+ // Neither should fences.
+ runners_[0]->InsertFence(TaskQueue::InsertFencePosition::kBeginningOfTime);
+ EXPECT_EQ(start_time + delay2, runners_[0]->GetNextScheduledWakeUp());
+}
+
+TEST_P(SequenceManagerTest, SetTimeDomainForDisabledQueue) {
+ CreateTaskQueues(1u);
+
+ MockTaskQueueObserver observer;
+ runners_[0]->SetObserver(&observer);
+
+ runners_[0]->PostDelayedTask(FROM_HERE, BindOnce(&NopTask),
+ TimeDelta::FromMilliseconds(1));
+
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> voter =
+ runners_[0]->CreateQueueEnabledVoter();
+ voter->SetQueueEnabled(false);
+
+ // We should not get a notification for a disabled queue.
+ EXPECT_CALL(observer, OnQueueNextWakeUpChanged(_, _)).Times(0);
+
+ std::unique_ptr<MockTimeDomain> domain =
+ std::make_unique<MockTimeDomain>(manager_->NowTicks());
+ manager_->RegisterTimeDomain(domain.get());
+ runners_[0]->SetTimeDomain(domain.get());
+
+ // Tidy up.
+ runners_[0]->ShutdownTaskQueue();
+ manager_->UnregisterTimeDomain(domain.get());
+}
+
+namespace {
+void SetOnTaskHandlers(scoped_refptr<TestTaskQueue> task_queue,
+ int* start_counter,
+ int* complete_counter) {
+ task_queue->GetTaskQueueImpl()->SetOnTaskStartedHandler(BindRepeating(
+ [](int* counter, const TaskQueue::Task& task,
+ const TaskQueue::TaskTiming& task_timing) { ++(*counter); },
+ start_counter));
+ task_queue->GetTaskQueueImpl()->SetOnTaskCompletedHandler(BindRepeating(
+ [](int* counter, const TaskQueue::Task& task,
+ const TaskQueue::TaskTiming& task_timing) { ++(*counter); },
+ complete_counter));
+}
+
+void UnsetOnTaskHandlers(scoped_refptr<TestTaskQueue> task_queue) {
+ task_queue->GetTaskQueueImpl()->SetOnTaskStartedHandler(
+ internal::TaskQueueImpl::OnTaskStartedHandler());
+ task_queue->GetTaskQueueImpl()->SetOnTaskCompletedHandler(
+ internal::TaskQueueImpl::OnTaskStartedHandler());
+}
+} // namespace
+
+TEST_P(SequenceManagerTest, ProcessTasksWithoutTaskTimeObservers) {
+ CreateTaskQueues(1u);
+ int start_counter = 0;
+ int complete_counter = 0;
+ std::vector<EnqueueOrder> run_order;
+ SetOnTaskHandlers(runners_[0], &start_counter, &complete_counter);
+ EXPECT_TRUE(runners_[0]->GetTaskQueueImpl()->RequiresTaskTiming());
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_EQ(start_counter, 3);
+ EXPECT_EQ(complete_counter, 3);
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u, 3u));
+
+ UnsetOnTaskHandlers(runners_[0]);
+ EXPECT_FALSE(runners_[0]->GetTaskQueueImpl()->RequiresTaskTiming());
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 4, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 5, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 6, &run_order));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_EQ(start_counter, 3);
+ EXPECT_EQ(complete_counter, 3);
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u, 3u, 4u, 5u, 6u));
+}
+
+TEST_P(SequenceManagerTest, ProcessTasksWithTaskTimeObservers) {
+ CreateTaskQueues(1u);
+ int start_counter = 0;
+ int complete_counter = 0;
+
+ manager_->AddTaskTimeObserver(&test_task_time_observer_);
+ SetOnTaskHandlers(runners_[0], &start_counter, &complete_counter);
+ EXPECT_TRUE(runners_[0]->GetTaskQueueImpl()->RequiresTaskTiming());
+ std::vector<EnqueueOrder> run_order;
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_EQ(start_counter, 2);
+ EXPECT_EQ(complete_counter, 2);
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u));
+
+ UnsetOnTaskHandlers(runners_[0]);
+ EXPECT_FALSE(runners_[0]->GetTaskQueueImpl()->RequiresTaskTiming());
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 3, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 4, &run_order));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_EQ(start_counter, 2);
+ EXPECT_EQ(complete_counter, 2);
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u, 3u, 4u));
+
+ manager_->RemoveTaskTimeObserver(&test_task_time_observer_);
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 5, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 6, &run_order));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_EQ(start_counter, 2);
+ EXPECT_EQ(complete_counter, 2);
+ EXPECT_FALSE(runners_[0]->GetTaskQueueImpl()->RequiresTaskTiming());
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u, 3u, 4u, 5u, 6u));
+
+ SetOnTaskHandlers(runners_[0], &start_counter, &complete_counter);
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 7, &run_order));
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 8, &run_order));
+
+ RunLoop().RunUntilIdle();
+ EXPECT_EQ(start_counter, 4);
+ EXPECT_EQ(complete_counter, 4);
+ EXPECT_TRUE(runners_[0]->GetTaskQueueImpl()->RequiresTaskTiming());
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u));
+ UnsetOnTaskHandlers(runners_[0]);
+}
+
+TEST_P(SequenceManagerTest, GracefulShutdown) {
+ std::vector<TimeTicks> run_times;
+ scoped_refptr<TestTaskQueue> main_tq = CreateTaskQueue();
+ WeakPtr<TestTaskQueue> main_tq_weak_ptr = main_tq->GetWeakPtr();
+
+ EXPECT_EQ(1u, manager_->ActiveQueuesCount());
+ EXPECT_EQ(0u, manager_->QueuesToShutdownCount());
+ EXPECT_EQ(0u, manager_->QueuesToDeleteCount());
+
+ for (int i = 1; i <= 5; ++i) {
+ main_tq->PostDelayedTask(
+ FROM_HERE, BindOnce(&RecordTimeTask, &run_times, GetTickClock()),
+ TimeDelta::FromMilliseconds(i * 100));
+ }
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(250));
+
+ main_tq = nullptr;
+ // Ensure that task queue went away.
+ EXPECT_FALSE(main_tq_weak_ptr.get());
+
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(1));
+
+ EXPECT_EQ(1u, manager_->ActiveQueuesCount());
+ EXPECT_EQ(1u, manager_->QueuesToShutdownCount());
+ EXPECT_EQ(0u, manager_->QueuesToDeleteCount());
+
+ test_task_runner_->FastForwardUntilNoTasksRemain();
+
+ // Even with TaskQueue gone, tasks are executed.
+ EXPECT_THAT(run_times,
+ ElementsAre(start_time_ + TimeDelta::FromMilliseconds(100),
+ start_time_ + TimeDelta::FromMilliseconds(200),
+ start_time_ + TimeDelta::FromMilliseconds(300),
+ start_time_ + TimeDelta::FromMilliseconds(400),
+ start_time_ + TimeDelta::FromMilliseconds(500)));
+
+ EXPECT_EQ(0u, manager_->ActiveQueuesCount());
+ EXPECT_EQ(0u, manager_->QueuesToShutdownCount());
+ EXPECT_EQ(0u, manager_->QueuesToDeleteCount());
+}
+
+TEST_P(SequenceManagerTest, GracefulShutdown_ManagerDeletedInFlight) {
+ std::vector<TimeTicks> run_times;
+ scoped_refptr<TestTaskQueue> control_tq = CreateTaskQueue();
+ std::vector<scoped_refptr<TestTaskQueue>> main_tqs;
+ std::vector<WeakPtr<TestTaskQueue>> main_tq_weak_ptrs;
+
+ // There might be a race condition - async task queues should be unregistered
+ // first. Increase the number of task queues to surely detect that.
+ // The problem is that pointers are compared in a set and generally for
+ // a small number of allocations value of the pointers increases
+ // monotonically. 100 is large enough to force allocations from different
+ // pages.
+ const int N = 100;
+ for (int i = 0; i < N; ++i) {
+ scoped_refptr<TestTaskQueue> tq = CreateTaskQueue();
+ main_tq_weak_ptrs.push_back(tq->GetWeakPtr());
+ main_tqs.push_back(std::move(tq));
+ }
+
+ for (int i = 1; i <= 5; ++i) {
+ main_tqs[0]->PostDelayedTask(
+ FROM_HERE, BindOnce(&RecordTimeTask, &run_times, GetTickClock()),
+ TimeDelta::FromMilliseconds(i * 100));
+ }
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(250));
+
+ main_tqs.clear();
+ // Ensure that task queues went away.
+ for (int i = 0; i < N; ++i) {
+ EXPECT_FALSE(main_tq_weak_ptrs[i].get());
+ }
+
+ // No leaks should occur when TQM was destroyed before processing
+ // shutdown task and TaskQueueImpl should be safely deleted on a correct
+ // thread.
+ manager_.reset();
+
+ test_task_runner_->FastForwardUntilNoTasksRemain();
+
+ EXPECT_THAT(run_times,
+ ElementsAre(start_time_ + TimeDelta::FromMilliseconds(100),
+ start_time_ + TimeDelta::FromMilliseconds(200)));
+}
+
+TEST_P(SequenceManagerTest,
+ GracefulShutdown_ManagerDeletedWithQueuesToShutdown) {
+ std::vector<TimeTicks> run_times;
+ scoped_refptr<TestTaskQueue> main_tq = CreateTaskQueue();
+ WeakPtr<TestTaskQueue> main_tq_weak_ptr = main_tq->GetWeakPtr();
+
+ EXPECT_EQ(1u, manager_->ActiveQueuesCount());
+ EXPECT_EQ(0u, manager_->QueuesToShutdownCount());
+ EXPECT_EQ(0u, manager_->QueuesToDeleteCount());
+
+ for (int i = 1; i <= 5; ++i) {
+ main_tq->PostDelayedTask(
+ FROM_HERE, BindOnce(&RecordTimeTask, &run_times, GetTickClock()),
+ TimeDelta::FromMilliseconds(i * 100));
+ }
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(250));
+
+ main_tq = nullptr;
+ // Ensure that task queue went away.
+ EXPECT_FALSE(main_tq_weak_ptr.get());
+
+ test_task_runner_->FastForwardBy(TimeDelta::FromMilliseconds(1));
+
+ EXPECT_EQ(1u, manager_->ActiveQueuesCount());
+ EXPECT_EQ(1u, manager_->QueuesToShutdownCount());
+ EXPECT_EQ(0u, manager_->QueuesToDeleteCount());
+
+ // Ensure that all queues-to-gracefully-shutdown are properly unregistered.
+ manager_.reset();
+
+ test_task_runner_->FastForwardUntilNoTasksRemain();
+
+ EXPECT_THAT(run_times,
+ ElementsAre(start_time_ + TimeDelta::FromMilliseconds(100),
+ start_time_ + TimeDelta::FromMilliseconds(200)));
+}
+
+TEST_P(SequenceManagerTestWithCustomInitialization, DefaultTaskRunnerSupport) {
+ MessageLoop message_loop;
+ scoped_refptr<SingleThreadTaskRunner> original_task_runner =
+ message_loop.task_runner();
+ scoped_refptr<SingleThreadTaskRunner> custom_task_runner =
+ MakeRefCounted<TestSimpleTaskRunner>();
+ {
+ std::unique_ptr<SequenceManagerForTest> manager =
+ SequenceManagerForTest::Create(&message_loop,
+ message_loop.task_runner(), nullptr);
+ manager->SetDefaultTaskRunner(custom_task_runner);
+ DCHECK_EQ(custom_task_runner, message_loop.task_runner());
+ }
+ DCHECK_EQ(original_task_runner, message_loop.task_runner());
+}
+
+TEST_P(SequenceManagerTest, CanceledTasksInQueueCantMakeOtherTasksSkipAhead) {
+ CreateTaskQueues(2u);
+
+ CancelableTask task1(GetTickClock());
+ CancelableTask task2(GetTickClock());
+ std::vector<TimeTicks> run_times;
+
+ runners_[0]->PostTask(FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task1.weak_factory_.GetWeakPtr(), &run_times));
+ runners_[0]->PostTask(FROM_HERE,
+ BindOnce(&CancelableTask::RecordTimeTask,
+ task2.weak_factory_.GetWeakPtr(), &run_times));
+
+ std::vector<EnqueueOrder> run_order;
+ runners_[1]->PostTask(FROM_HERE, BindOnce(&TestTask, 1, &run_order));
+
+ runners_[0]->PostTask(FROM_HERE, BindOnce(&TestTask, 2, &run_order));
+
+ task1.weak_factory_.InvalidateWeakPtrs();
+ task2.weak_factory_.InvalidateWeakPtrs();
+ RunLoop().RunUntilIdle();
+
+ EXPECT_THAT(run_order, ElementsAre(1u, 2u));
+}
+
+TEST_P(SequenceManagerTest, TaskQueueDeletedOnAnotherThread) {
+ std::vector<TimeTicks> run_times;
+ scoped_refptr<TestTaskQueue> main_tq = CreateTaskQueue();
+
+ int start_counter = 0;
+ int complete_counter = 0;
+ SetOnTaskHandlers(main_tq, &start_counter, &complete_counter);
+
+ EXPECT_EQ(1u, manager_->ActiveQueuesCount());
+ EXPECT_EQ(0u, manager_->QueuesToShutdownCount());
+ EXPECT_EQ(0u, manager_->QueuesToDeleteCount());
+
+ for (int i = 1; i <= 5; ++i) {
+ main_tq->PostDelayedTask(
+ FROM_HERE, BindOnce(&RecordTimeTask, &run_times, GetTickClock()),
+ TimeDelta::FromMilliseconds(i * 100));
+ }
+
+ // TODO(altimin): do not do this after switching to weak pointer-based
+ // task handlers.
+ UnsetOnTaskHandlers(main_tq);
+
+ WaitableEvent task_queue_deleted(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ std::unique_ptr<Thread> thread = std::make_unique<Thread>("test thread");
+ thread->StartAndWaitForTesting();
+
+ thread->task_runner()->PostTask(
+ FROM_HERE, BindOnce(
+ [](scoped_refptr<SingleThreadTaskRunner> task_queue,
+ WaitableEvent* task_queue_deleted) {
+ task_queue = nullptr;
+ task_queue_deleted->Signal();
+ },
+ std::move(main_tq), &task_queue_deleted));
+ task_queue_deleted.Wait();
+
+ EXPECT_EQ(1u, manager_->ActiveQueuesCount());
+ EXPECT_EQ(1u, manager_->QueuesToShutdownCount());
+ EXPECT_EQ(0u, manager_->QueuesToDeleteCount());
+
+ test_task_runner_->FastForwardUntilNoTasksRemain();
+
+ // Even with TaskQueue gone, tasks are executed.
+ EXPECT_THAT(run_times,
+ ElementsAre(start_time_ + TimeDelta::FromMilliseconds(100),
+ start_time_ + TimeDelta::FromMilliseconds(200),
+ start_time_ + TimeDelta::FromMilliseconds(300),
+ start_time_ + TimeDelta::FromMilliseconds(400),
+ start_time_ + TimeDelta::FromMilliseconds(500)));
+
+ EXPECT_EQ(0u, manager_->ActiveQueuesCount());
+ EXPECT_EQ(0u, manager_->QueuesToShutdownCount());
+ EXPECT_EQ(0u, manager_->QueuesToDeleteCount());
+
+ thread->Stop();
+}
+
+namespace {
+
+void DoNothing() {}
+
+class PostTaskInDestructor {
+ public:
+ explicit PostTaskInDestructor(scoped_refptr<TaskQueue> task_queue)
+ : task_queue_(task_queue) {}
+
+ ~PostTaskInDestructor() {
+ task_queue_->PostTask(FROM_HERE, BindOnce(&DoNothing));
+ }
+
+ void Do() {}
+
+ private:
+ scoped_refptr<TaskQueue> task_queue_;
+};
+
+} // namespace
+
+TEST_P(SequenceManagerTest, TaskQueueUsedInTaskDestructorAfterShutdown) {
+ // This test checks that when a task is posted to a shutdown queue and
+ // destroyed, it can try to post a task to the same queue without deadlocks.
+ scoped_refptr<TestTaskQueue> main_tq = CreateTaskQueue();
+
+ WaitableEvent test_executed(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ std::unique_ptr<Thread> thread = std::make_unique<Thread>("test thread");
+ thread->StartAndWaitForTesting();
+
+ manager_.reset();
+
+ thread->task_runner()->PostTask(
+ FROM_HERE, BindOnce(
+ [](scoped_refptr<SingleThreadTaskRunner> task_queue,
+ std::unique_ptr<PostTaskInDestructor> test_object,
+ WaitableEvent* test_executed) {
+ task_queue->PostTask(FROM_HERE,
+ BindOnce(&PostTaskInDestructor::Do,
+ std::move(test_object)));
+ test_executed->Signal();
+ },
+ main_tq, std::make_unique<PostTaskInDestructor>(main_tq),
+ &test_executed));
+ test_executed.Wait();
+}
+
+} // namespace sequence_manager_impl_unittest
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/sequence_manager_perftest.cc b/base/task/sequence_manager/sequence_manager_perftest.cc
new file mode 100644
index 0000000000..c5cd1a00f1
--- /dev/null
+++ b/base/task/sequence_manager/sequence_manager_perftest.cc
@@ -0,0 +1,306 @@
+// 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/task/sequence_manager/sequence_manager.h"
+
+#include <stddef.h>
+#include <memory>
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/stringprintf.h"
+#include "base/task/sequence_manager/task_queue_impl.h"
+#include "base/task/sequence_manager/test/mock_time_domain.h"
+#include "base/task/sequence_manager/test/sequence_manager_for_test.h"
+#include "base/task/sequence_manager/test/test_task_queue.h"
+#include "base/task/sequence_manager/test/test_task_time_observer.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/default_tick_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_test.h"
+
+namespace base {
+namespace sequence_manager {
+
+// To reduce noise related to the OS timer, we use a mock time domain to
+// fast forward the timers.
+class PerfTestTimeDomain : public MockTimeDomain {
+ public:
+ PerfTestTimeDomain() : MockTimeDomain(TimeTicks::Now()) {}
+ ~PerfTestTimeDomain() override = default;
+
+ Optional<TimeDelta> DelayTillNextTask(LazyNow* lazy_now) override {
+ Optional<TimeTicks> run_time = NextScheduledRunTime();
+ if (!run_time)
+ return nullopt;
+ SetNowTicks(*run_time);
+ // Makes SequenceManager to continue immediately.
+ return TimeDelta();
+ }
+
+ void SetNextDelayedDoWork(LazyNow* lazy_now, TimeTicks run_time) override {
+ // De-dupe DoWorks.
+ if (NumberOfScheduledWakeUps() == 1u)
+ RequestDoWork();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PerfTestTimeDomain);
+};
+
+class SequenceManagerPerfTest : public testing::Test {
+ public:
+ SequenceManagerPerfTest()
+ : num_queues_(0),
+ max_tasks_in_flight_(0),
+ num_tasks_in_flight_(0),
+ num_tasks_to_post_(0),
+ num_tasks_to_run_(0) {}
+
+ void SetUp() override {
+ if (ThreadTicks::IsSupported())
+ ThreadTicks::WaitUntilInitialized();
+ }
+
+ void TearDown() override {
+ queues_.clear();
+ manager_->UnregisterTimeDomain(time_domain_.get());
+ manager_.reset();
+ }
+
+ void Initialize(size_t num_queues) {
+ num_queues_ = num_queues;
+ message_loop_.reset(new MessageLoop());
+ manager_ = SequenceManagerForTest::Create(message_loop_.get(),
+ message_loop_->task_runner(),
+ DefaultTickClock::GetInstance());
+ manager_->AddTaskTimeObserver(&test_task_time_observer_);
+
+ time_domain_.reset(new PerfTestTimeDomain());
+ manager_->RegisterTimeDomain(time_domain_.get());
+
+ for (size_t i = 0; i < num_queues; i++) {
+ queues_.push_back(manager_->CreateTaskQueue<TestTaskQueue>(
+ TaskQueue::Spec("test").SetTimeDomain(time_domain_.get())));
+ }
+
+ delayed_task_closure_ = BindRepeating(
+ &SequenceManagerPerfTest::TestDelayedTask, Unretained(this));
+
+ immediate_task_closure_ = BindRepeating(
+ &SequenceManagerPerfTest::TestImmediateTask, Unretained(this));
+ }
+
+ void TestDelayedTask() {
+ if (--num_tasks_to_run_ == 0) {
+ run_loop_->QuitWhenIdle();
+ return;
+ }
+
+ num_tasks_in_flight_--;
+ // NOTE there are only up to max_tasks_in_flight_ pending delayed tasks at
+ // any one time. Thanks to the lower_num_tasks_to_post going to zero if
+ // there are a lot of tasks in flight, the total number of task in flight at
+ // any one time is very variable.
+ unsigned int lower_num_tasks_to_post =
+ num_tasks_in_flight_ < (max_tasks_in_flight_ / 2) ? 1 : 0;
+ unsigned int max_tasks_to_post =
+ num_tasks_to_post_ % 2 ? lower_num_tasks_to_post : 10;
+ for (unsigned int i = 0;
+ i < max_tasks_to_post && num_tasks_in_flight_ < max_tasks_in_flight_ &&
+ num_tasks_to_post_ > 0;
+ i++) {
+ // Choose a queue weighted towards queue 0.
+ unsigned int queue = num_tasks_to_post_ % (num_queues_ + 1);
+ if (queue == num_queues_) {
+ queue = 0;
+ }
+ // Simulate a mix of short and longer delays.
+ unsigned int delay =
+ num_tasks_to_post_ % 2 ? 1 : (10 + num_tasks_to_post_ % 10);
+ queues_[queue]->PostDelayedTask(FROM_HERE, delayed_task_closure_,
+ TimeDelta::FromMilliseconds(delay));
+ num_tasks_in_flight_++;
+ num_tasks_to_post_--;
+ }
+ }
+
+ void TestImmediateTask() {
+ if (--num_tasks_to_run_ == 0) {
+ run_loop_->QuitWhenIdle();
+ return;
+ }
+
+ num_tasks_in_flight_--;
+ // NOTE there are only up to max_tasks_in_flight_ pending delayed tasks at
+ // any one time. Thanks to the lower_num_tasks_to_post going to zero if
+ // there are a lot of tasks in flight, the total number of task in flight at
+ // any one time is very variable.
+ unsigned int lower_num_tasks_to_post =
+ num_tasks_in_flight_ < (max_tasks_in_flight_ / 2) ? 1 : 0;
+ unsigned int max_tasks_to_post =
+ num_tasks_to_post_ % 2 ? lower_num_tasks_to_post : 10;
+ for (unsigned int i = 0;
+ i < max_tasks_to_post && num_tasks_in_flight_ < max_tasks_in_flight_ &&
+ num_tasks_to_post_ > 0;
+ i++) {
+ // Choose a queue weighted towards queue 0.
+ unsigned int queue = num_tasks_to_post_ % (num_queues_ + 1);
+ if (queue == num_queues_) {
+ queue = 0;
+ }
+ queues_[queue]->PostTask(FROM_HERE, immediate_task_closure_);
+ num_tasks_in_flight_++;
+ num_tasks_to_post_--;
+ }
+ }
+
+ void ResetAndCallTestDelayedTask(unsigned int num_tasks_to_run) {
+ num_tasks_in_flight_ = 1;
+ num_tasks_to_post_ = num_tasks_to_run;
+ num_tasks_to_run_ = num_tasks_to_run;
+ TestDelayedTask();
+ }
+
+ void ResetAndCallTestImmediateTask(unsigned int num_tasks_to_run) {
+ num_tasks_in_flight_ = 1;
+ num_tasks_to_post_ = num_tasks_to_run;
+ num_tasks_to_run_ = num_tasks_to_run;
+ TestImmediateTask();
+ }
+
+ void Benchmark(const std::string& trace, const RepeatingClosure& test_task) {
+ ThreadTicks start = ThreadTicks::Now();
+ ThreadTicks now;
+ unsigned long long num_iterations = 0;
+ do {
+ test_task.Run();
+ run_loop_.reset(new RunLoop());
+ run_loop_->Run();
+ now = ThreadTicks::Now();
+ num_iterations++;
+ } while (now - start < TimeDelta::FromSeconds(5));
+ perf_test::PrintResult(
+ "task", "", trace,
+ (now - start).InMicroseconds() / static_cast<double>(num_iterations),
+ "us/run", true);
+ }
+
+ size_t num_queues_;
+ unsigned int max_tasks_in_flight_;
+ unsigned int num_tasks_in_flight_;
+ unsigned int num_tasks_to_post_;
+ unsigned int num_tasks_to_run_;
+ std::unique_ptr<MessageLoop> message_loop_;
+ std::unique_ptr<SequenceManager> manager_;
+ std::unique_ptr<RunLoop> run_loop_;
+ std::unique_ptr<TimeDomain> time_domain_;
+ std::vector<scoped_refptr<SingleThreadTaskRunner>> queues_;
+ RepeatingClosure delayed_task_closure_;
+ RepeatingClosure immediate_task_closure_;
+ // TODO(alexclarke): parameterize so we can measure with and without a
+ // TaskTimeObserver.
+ TestTaskTimeObserver test_task_time_observer_;
+};
+
+TEST_F(SequenceManagerPerfTest, RunTenThousandDelayedTasks_OneQueue) {
+ if (!ThreadTicks::IsSupported())
+ return;
+ Initialize(1u);
+
+ max_tasks_in_flight_ = 200;
+ Benchmark("run 10000 delayed tasks with one queue",
+ BindRepeating(&SequenceManagerPerfTest::ResetAndCallTestDelayedTask,
+ Unretained(this), 10000));
+}
+
+TEST_F(SequenceManagerPerfTest, RunTenThousandDelayedTasks_FourQueues) {
+ if (!ThreadTicks::IsSupported())
+ return;
+ Initialize(4u);
+
+ max_tasks_in_flight_ = 200;
+ Benchmark("run 10000 delayed tasks with four queues",
+ BindRepeating(&SequenceManagerPerfTest::ResetAndCallTestDelayedTask,
+ Unretained(this), 10000));
+}
+
+TEST_F(SequenceManagerPerfTest, RunTenThousandDelayedTasks_EightQueues) {
+ if (!ThreadTicks::IsSupported())
+ return;
+ Initialize(8u);
+
+ max_tasks_in_flight_ = 200;
+ Benchmark("run 10000 delayed tasks with eight queues",
+ BindRepeating(&SequenceManagerPerfTest::ResetAndCallTestDelayedTask,
+ Unretained(this), 10000));
+}
+
+TEST_F(SequenceManagerPerfTest, RunTenThousandDelayedTasks_ThirtyTwoQueues) {
+ if (!ThreadTicks::IsSupported())
+ return;
+ Initialize(32u);
+
+ max_tasks_in_flight_ = 200;
+ Benchmark("run 10000 delayed tasks with thirty two queues",
+ BindRepeating(&SequenceManagerPerfTest::ResetAndCallTestDelayedTask,
+ Unretained(this), 10000));
+}
+
+TEST_F(SequenceManagerPerfTest, RunTenThousandImmediateTasks_OneQueue) {
+ if (!ThreadTicks::IsSupported())
+ return;
+ Initialize(1u);
+
+ max_tasks_in_flight_ = 200;
+ Benchmark(
+ "run 10000 immediate tasks with one queue",
+ BindRepeating(&SequenceManagerPerfTest::ResetAndCallTestImmediateTask,
+ Unretained(this), 10000));
+}
+
+TEST_F(SequenceManagerPerfTest, RunTenThousandImmediateTasks_FourQueues) {
+ if (!ThreadTicks::IsSupported())
+ return;
+ Initialize(4u);
+
+ max_tasks_in_flight_ = 200;
+ Benchmark(
+ "run 10000 immediate tasks with four queues",
+ BindRepeating(&SequenceManagerPerfTest::ResetAndCallTestImmediateTask,
+ Unretained(this), 10000));
+}
+
+TEST_F(SequenceManagerPerfTest, RunTenThousandImmediateTasks_EightQueues) {
+ if (!ThreadTicks::IsSupported())
+ return;
+ Initialize(8u);
+
+ max_tasks_in_flight_ = 200;
+ Benchmark(
+ "run 10000 immediate tasks with eight queues",
+ BindRepeating(&SequenceManagerPerfTest::ResetAndCallTestImmediateTask,
+ Unretained(this), 10000));
+}
+
+TEST_F(SequenceManagerPerfTest, RunTenThousandImmediateTasks_ThirtyTwoQueues) {
+ if (!ThreadTicks::IsSupported())
+ return;
+ Initialize(32u);
+
+ max_tasks_in_flight_ = 200;
+ Benchmark(
+ "run 10000 immediate tasks with thirty two queues",
+ BindRepeating(&SequenceManagerPerfTest::ResetAndCallTestImmediateTask,
+ Unretained(this), 10000));
+}
+
+// TODO(alexclarke): Add additional tests with different mixes of non-delayed vs
+// delayed tasks.
+
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/sequenced_task_source.h b/base/task/sequence_manager/sequenced_task_source.h
new file mode 100644
index 0000000000..285f85377d
--- /dev/null
+++ b/base/task/sequence_manager/sequenced_task_source.h
@@ -0,0 +1,37 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_SEQUENCED_TASK_SOURCE_H_
+#define BASE_TASK_SEQUENCE_MANAGER_SEQUENCED_TASK_SOURCE_H_
+
+#include "base/optional.h"
+#include "base/pending_task.h"
+#include "base/task/sequence_manager/lazy_now.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+// Interface to pass tasks to ThreadController.
+class SequencedTaskSource {
+ public:
+ // Returns the next task to run from this source or nullopt if
+ // there're no more tasks ready to run. If a task is returned,
+ // DidRunTask() must be invoked before the next call to TakeTask().
+ virtual Optional<PendingTask> TakeTask() = 0;
+
+ // Notifies this source that the task previously obtained
+ // from TakeTask() has been completed.
+ virtual void DidRunTask() = 0;
+
+ // Returns the delay till the next task or TimeDelta::Max()
+ // if there are no tasks left.
+ virtual TimeDelta DelayTillNextTask(LazyNow* lazy_now) = 0;
+};
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_SEQUENCED_TASK_SOURCE_H_
diff --git a/base/task/sequence_manager/task_queue.cc b/base/task/sequence_manager/task_queue.cc
new file mode 100644
index 0000000000..2d3d1525d6
--- /dev/null
+++ b/base/task/sequence_manager/task_queue.cc
@@ -0,0 +1,289 @@
+// 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/task/sequence_manager/task_queue.h"
+
+#include "base/bind.h"
+#include "base/task/sequence_manager/sequence_manager_impl.h"
+#include "base/task/sequence_manager/task_queue_impl.h"
+#include "base/time/time.h"
+
+namespace base {
+namespace sequence_manager {
+
+TaskQueue::TaskQueue(std::unique_ptr<internal::TaskQueueImpl> impl,
+ const TaskQueue::Spec& spec)
+ : impl_(std::move(impl)),
+ thread_id_(PlatformThread::CurrentId()),
+ sequence_manager_(impl_ ? impl_->GetSequenceManagerWeakPtr() : nullptr),
+ graceful_queue_shutdown_helper_(
+ impl_ ? impl_->GetGracefulQueueShutdownHelper() : nullptr) {}
+
+TaskQueue::~TaskQueue() {
+ // scoped_refptr guarantees us that this object isn't used.
+ if (!impl_)
+ return;
+ if (impl_->IsUnregistered())
+ return;
+ graceful_queue_shutdown_helper_->GracefullyShutdownTaskQueue(
+ TakeTaskQueueImpl());
+}
+
+TaskQueue::Task::Task(TaskQueue::PostedTask task, TimeTicks desired_run_time)
+ : PendingTask(task.posted_from,
+ std::move(task.callback),
+ desired_run_time,
+ task.nestable),
+ task_type_(task.task_type) {}
+
+TaskQueue::TaskTiming::TaskTiming(bool has_wall_time, bool has_thread_time)
+ : has_wall_time_(has_wall_time), has_thread_time_(has_thread_time) {}
+
+void TaskQueue::TaskTiming::RecordTaskStart(LazyNow* now) {
+ if (has_wall_time())
+ start_time_ = now->Now();
+ if (has_thread_time())
+ start_thread_time_ = base::ThreadTicks::Now();
+}
+
+void TaskQueue::TaskTiming::RecordTaskEnd(LazyNow* now) {
+ if (has_wall_time())
+ end_time_ = now->Now();
+ if (has_thread_time())
+ end_thread_time_ = base::ThreadTicks::Now();
+}
+
+TaskQueue::PostedTask::PostedTask(OnceClosure callback,
+ Location posted_from,
+ TimeDelta delay,
+ Nestable nestable,
+ int task_type)
+ : callback(std::move(callback)),
+ posted_from(posted_from),
+ delay(delay),
+ nestable(nestable),
+ task_type(task_type) {}
+
+TaskQueue::PostedTask::PostedTask(PostedTask&& move_from)
+ : callback(std::move(move_from.callback)),
+ posted_from(move_from.posted_from),
+ delay(move_from.delay),
+ nestable(move_from.nestable),
+ task_type(move_from.task_type) {}
+
+TaskQueue::PostedTask::~PostedTask() = default;
+
+void TaskQueue::ShutdownTaskQueue() {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ AutoLock lock(impl_lock_);
+ if (!impl_)
+ return;
+ if (!sequence_manager_) {
+ impl_.reset();
+ return;
+ }
+ impl_->SetBlameContext(nullptr);
+ impl_->SetOnTaskStartedHandler(
+ internal::TaskQueueImpl::OnTaskStartedHandler());
+ impl_->SetOnTaskCompletedHandler(
+ internal::TaskQueueImpl::OnTaskCompletedHandler());
+ sequence_manager_->UnregisterTaskQueueImpl(TakeTaskQueueImpl());
+}
+
+bool TaskQueue::RunsTasksInCurrentSequence() const {
+ return IsOnMainThread();
+}
+
+bool TaskQueue::PostDelayedTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay) {
+ return PostTaskWithMetadata(
+ PostedTask(std::move(task), from_here, delay, Nestable::kNestable));
+}
+
+bool TaskQueue::PostNonNestableDelayedTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay) {
+ return PostTaskWithMetadata(
+ PostedTask(std::move(task), from_here, delay, Nestable::kNonNestable));
+}
+
+bool TaskQueue::PostTaskWithMetadata(PostedTask task) {
+ Optional<MoveableAutoLock> lock = AcquireImplReadLockIfNeeded();
+ if (!impl_)
+ return false;
+ internal::TaskQueueImpl::PostTaskResult result(
+ impl_->PostDelayedTask(std::move(task)));
+ if (result.success)
+ return true;
+ // If posting task was unsuccessful then |result| will contain
+ // the original task which should be destructed outside of the lock.
+ lock = nullopt;
+ // Task gets implicitly destructed here.
+ return false;
+}
+
+std::unique_ptr<TaskQueue::QueueEnabledVoter>
+TaskQueue::CreateQueueEnabledVoter() {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ if (!impl_)
+ return nullptr;
+ return impl_->CreateQueueEnabledVoter(this);
+}
+
+bool TaskQueue::IsQueueEnabled() const {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ if (!impl_)
+ return false;
+ return impl_->IsQueueEnabled();
+}
+
+bool TaskQueue::IsEmpty() const {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ if (!impl_)
+ return true;
+ return impl_->IsEmpty();
+}
+
+size_t TaskQueue::GetNumberOfPendingTasks() const {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ if (!impl_)
+ return 0;
+ return impl_->GetNumberOfPendingTasks();
+}
+
+bool TaskQueue::HasTaskToRunImmediately() const {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ if (!impl_)
+ return false;
+ return impl_->HasTaskToRunImmediately();
+}
+
+Optional<TimeTicks> TaskQueue::GetNextScheduledWakeUp() {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ if (!impl_)
+ return nullopt;
+ return impl_->GetNextScheduledWakeUp();
+}
+
+void TaskQueue::SetQueuePriority(TaskQueue::QueuePriority priority) {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ if (!impl_)
+ return;
+ impl_->SetQueuePriority(priority);
+}
+
+TaskQueue::QueuePriority TaskQueue::GetQueuePriority() const {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ if (!impl_)
+ return TaskQueue::QueuePriority::kLowPriority;
+ return impl_->GetQueuePriority();
+}
+
+void TaskQueue::AddTaskObserver(MessageLoop::TaskObserver* task_observer) {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ if (!impl_)
+ return;
+ impl_->AddTaskObserver(task_observer);
+}
+
+void TaskQueue::RemoveTaskObserver(MessageLoop::TaskObserver* task_observer) {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ if (!impl_)
+ return;
+ impl_->RemoveTaskObserver(task_observer);
+}
+
+void TaskQueue::SetTimeDomain(TimeDomain* time_domain) {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ if (!impl_)
+ return;
+ impl_->SetTimeDomain(time_domain);
+}
+
+TimeDomain* TaskQueue::GetTimeDomain() const {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ if (!impl_)
+ return nullptr;
+ return impl_->GetTimeDomain();
+}
+
+void TaskQueue::SetBlameContext(trace_event::BlameContext* blame_context) {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ if (!impl_)
+ return;
+ impl_->SetBlameContext(blame_context);
+}
+
+void TaskQueue::InsertFence(InsertFencePosition position) {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ if (!impl_)
+ return;
+ impl_->InsertFence(position);
+}
+
+void TaskQueue::InsertFenceAt(TimeTicks time) {
+ impl_->InsertFenceAt(time);
+}
+
+void TaskQueue::RemoveFence() {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ if (!impl_)
+ return;
+ impl_->RemoveFence();
+}
+
+bool TaskQueue::HasActiveFence() {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ if (!impl_)
+ return false;
+ return impl_->HasActiveFence();
+}
+
+bool TaskQueue::BlockedByFence() const {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ if (!impl_)
+ return false;
+ return impl_->BlockedByFence();
+}
+
+const char* TaskQueue::GetName() const {
+ auto lock = AcquireImplReadLockIfNeeded();
+ if (!impl_)
+ return "";
+ return impl_->GetName();
+}
+
+void TaskQueue::SetObserver(Observer* observer) {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ if (!impl_)
+ return;
+ if (observer) {
+ // Observer is guaranteed to outlive TaskQueue and TaskQueueImpl lifecycle
+ // is controlled by |this|.
+ impl_->SetOnNextWakeUpChangedCallback(
+ BindRepeating(&TaskQueue::Observer::OnQueueNextWakeUpChanged,
+ Unretained(observer), Unretained(this)));
+ } else {
+ impl_->SetOnNextWakeUpChangedCallback(RepeatingCallback<void(TimeTicks)>());
+ }
+}
+
+bool TaskQueue::IsOnMainThread() const {
+ return thread_id_ == PlatformThread::CurrentId();
+}
+
+Optional<MoveableAutoLock> TaskQueue::AcquireImplReadLockIfNeeded() const {
+ if (IsOnMainThread())
+ return nullopt;
+ return MoveableAutoLock(impl_lock_);
+}
+
+std::unique_ptr<internal::TaskQueueImpl> TaskQueue::TakeTaskQueueImpl() {
+ DCHECK(impl_);
+ return std::move(impl_);
+}
+
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/task_queue.h b/base/task/sequence_manager/task_queue.h
new file mode 100644
index 0000000000..af6b4dd5da
--- /dev/null
+++ b/base/task/sequence_manager/task_queue.h
@@ -0,0 +1,368 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_H_
+#define BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_H_
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/optional.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "base/task/sequence_manager/lazy_now.h"
+#include "base/task/sequence_manager/moveable_auto_lock.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+
+namespace base {
+
+namespace trace_event {
+class BlameContext;
+}
+
+namespace sequence_manager {
+
+namespace internal {
+class GracefulQueueShutdownHelper;
+class SequenceManagerImpl;
+class TaskQueueImpl;
+} // namespace internal
+
+class TimeDomain;
+
+class BASE_EXPORT TaskQueue : public SingleThreadTaskRunner {
+ public:
+ class Observer {
+ public:
+ virtual ~Observer() = default;
+
+ // Notify observer that the time at which this queue wants to run
+ // the next task has changed. |next_wakeup| can be in the past
+ // (e.g. TimeTicks() can be used to notify about immediate work).
+ // Can be called on any thread
+ // All methods but SetObserver, SetTimeDomain and GetTimeDomain can be
+ // called on |queue|.
+ //
+ // TODO(altimin): Make it Optional<TimeTicks> to tell
+ // observer about cancellations.
+ virtual void OnQueueNextWakeUpChanged(TaskQueue* queue,
+ TimeTicks next_wake_up) = 0;
+ };
+
+ // A wrapper around OnceClosure with additional metadata to be passed
+ // to PostTask and plumbed until PendingTask is created.
+ struct BASE_EXPORT PostedTask {
+ PostedTask(OnceClosure callback,
+ Location posted_from,
+ TimeDelta delay = TimeDelta(),
+ Nestable nestable = Nestable::kNestable,
+ int task_type = 0);
+ PostedTask(PostedTask&& move_from);
+ PostedTask(const PostedTask& copy_from) = delete;
+ ~PostedTask();
+
+ OnceClosure callback;
+ Location posted_from;
+ TimeDelta delay;
+ Nestable nestable;
+ int task_type;
+ };
+
+ // Prepare the task queue to get released.
+ // All tasks posted after this call will be discarded.
+ virtual void ShutdownTaskQueue();
+
+ // TODO(scheduler-dev): Could we define a more clear list of priorities?
+ // See https://crbug.com/847858.
+ enum QueuePriority {
+ // Queues with control priority will run before any other queue, and will
+ // explicitly starve other queues. Typically this should only be used for
+ // private queues which perform control operations.
+ kControlPriority,
+
+ // The selector will prioritize highest over high, normal and low; and
+ // high over normal and low; and normal over low. However it will ensure
+ // neither of the lower priority queues can be completely starved by higher
+ // priority tasks. All three of these queues will always take priority over
+ // and can starve the best effort queue.
+ kHighestPriority,
+
+ kHighPriority,
+
+ // Queues with normal priority are the default.
+ kNormalPriority,
+ kLowPriority,
+
+ // Queues with best effort priority will only be run if all other queues are
+ // empty. They can be starved by the other queues.
+ kBestEffortPriority,
+ // Must be the last entry.
+ kQueuePriorityCount,
+ kFirstQueuePriority = kControlPriority,
+ };
+
+ // Can be called on any thread.
+ static const char* PriorityToString(QueuePriority priority);
+
+ // Options for constructing a TaskQueue.
+ struct Spec {
+ explicit Spec(const char* name)
+ : name(name),
+ should_monitor_quiescence(false),
+ time_domain(nullptr),
+ should_notify_observers(true) {}
+
+ Spec SetShouldMonitorQuiescence(bool should_monitor) {
+ should_monitor_quiescence = should_monitor;
+ return *this;
+ }
+
+ Spec SetShouldNotifyObservers(bool run_observers) {
+ should_notify_observers = run_observers;
+ return *this;
+ }
+
+ Spec SetTimeDomain(TimeDomain* domain) {
+ time_domain = domain;
+ return *this;
+ }
+
+ const char* name;
+ bool should_monitor_quiescence;
+ TimeDomain* time_domain;
+ bool should_notify_observers;
+ };
+
+ // Interface to pass per-task metadata to RendererScheduler.
+ class BASE_EXPORT Task : public PendingTask {
+ public:
+ Task(PostedTask posted_task, TimeTicks desired_run_time);
+
+ int task_type() const { return task_type_; }
+
+ private:
+ int task_type_;
+ };
+
+ // Information about task execution.
+ //
+ // Wall-time related methods (start_time, end_time, wall_duration) can be
+ // called only when |has_wall_time()| is true.
+ // Thread-time related mehtods (start_thread_time, end_thread_time,
+ // thread_duration) can be called only when |has_thread_time()| is true.
+ //
+ // start_* should be called after RecordTaskStart.
+ // end_* and *_duration should be called after RecordTaskEnd.
+ class BASE_EXPORT TaskTiming {
+ public:
+ TaskTiming(bool has_wall_time, bool has_thread_time);
+
+ bool has_wall_time() const { return has_wall_time_; }
+ bool has_thread_time() const { return has_thread_time_; }
+
+ base::TimeTicks start_time() const {
+ DCHECK(has_wall_time());
+ return start_time_;
+ }
+ base::TimeTicks end_time() const {
+ DCHECK(has_wall_time());
+ return end_time_;
+ }
+ base::TimeDelta wall_duration() const {
+ DCHECK(has_wall_time());
+ return end_time_ - start_time_;
+ }
+ base::ThreadTicks start_thread_time() const {
+ DCHECK(has_thread_time());
+ return start_thread_time_;
+ }
+ base::ThreadTicks end_thread_time() const {
+ DCHECK(has_thread_time());
+ return end_thread_time_;
+ }
+ base::TimeDelta thread_duration() const {
+ DCHECK(has_thread_time());
+ return end_thread_time_ - start_thread_time_;
+ }
+
+ void RecordTaskStart(LazyNow* now);
+ void RecordTaskEnd(LazyNow* now);
+
+ // Protected for tests.
+ protected:
+ bool has_wall_time_;
+ bool has_thread_time_;
+
+ base::TimeTicks start_time_;
+ base::TimeTicks end_time_;
+ base::ThreadTicks start_thread_time_;
+ base::ThreadTicks end_thread_time_;
+ };
+
+ // An interface that lets the owner vote on whether or not the associated
+ // TaskQueue should be enabled.
+ class QueueEnabledVoter {
+ public:
+ QueueEnabledVoter() = default;
+ virtual ~QueueEnabledVoter() = default;
+
+ // Votes to enable or disable the associated TaskQueue. The TaskQueue will
+ // only be enabled if all the voters agree it should be enabled, or if there
+ // are no voters.
+ // NOTE this must be called on the thread the associated TaskQueue was
+ // created on.
+ virtual void SetQueueEnabled(bool enabled) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QueueEnabledVoter);
+ };
+
+ // Returns an interface that allows the caller to vote on whether or not this
+ // TaskQueue is enabled. The TaskQueue will be enabled if there are no voters
+ // or if all agree it should be enabled.
+ // NOTE this must be called on the thread this TaskQueue was created by.
+ std::unique_ptr<QueueEnabledVoter> CreateQueueEnabledVoter();
+
+ // NOTE this must be called on the thread this TaskQueue was created by.
+ bool IsQueueEnabled() const;
+
+ // Returns true if the queue is completely empty.
+ bool IsEmpty() const;
+
+ // Returns the number of pending tasks in the queue.
+ size_t GetNumberOfPendingTasks() const;
+
+ // Returns true if the queue has work that's ready to execute now.
+ // NOTE: this must be called on the thread this TaskQueue was created by.
+ bool HasTaskToRunImmediately() const;
+
+ // Returns requested run time of next scheduled wake-up for a delayed task
+ // which is not ready to run. If there are no such tasks (immediate tasks
+ // don't count) or the queue is disabled it returns nullopt.
+ // NOTE: this must be called on the thread this TaskQueue was created by.
+ Optional<TimeTicks> GetNextScheduledWakeUp();
+
+ // Can be called on any thread.
+ virtual const char* GetName() const;
+
+ // Set the priority of the queue to |priority|. NOTE this must be called on
+ // the thread this TaskQueue was created by.
+ void SetQueuePriority(QueuePriority priority);
+
+ // Returns the current queue priority.
+ QueuePriority GetQueuePriority() const;
+
+ // These functions can only be called on the same thread that the task queue
+ // manager executes its tasks on.
+ void AddTaskObserver(MessageLoop::TaskObserver* task_observer);
+ void RemoveTaskObserver(MessageLoop::TaskObserver* task_observer);
+
+ // Set the blame context which is entered and left while executing tasks from
+ // this task queue. |blame_context| must be null or outlive this task queue.
+ // Must be called on the thread this TaskQueue was created by.
+ void SetBlameContext(trace_event::BlameContext* blame_context);
+
+ // Removes the task queue from the previous TimeDomain and adds it to
+ // |domain|. This is a moderately expensive operation.
+ void SetTimeDomain(TimeDomain* domain);
+
+ // Returns the queue's current TimeDomain. Can be called from any thread.
+ TimeDomain* GetTimeDomain() const;
+
+ enum class InsertFencePosition {
+ kNow, // Tasks posted on the queue up till this point further may run.
+ // All further tasks are blocked.
+ kBeginningOfTime, // No tasks posted on this queue may run.
+ };
+
+ // Inserts a barrier into the task queue which prevents tasks with an enqueue
+ // order greater than the fence from running until either the fence has been
+ // removed or a subsequent fence has unblocked some tasks within the queue.
+ // Note: delayed tasks get their enqueue order set once their delay has
+ // expired, and non-delayed tasks get their enqueue order set when posted.
+ //
+ // Fences come in three flavours:
+ // - Regular (InsertFence(NOW)) - all tasks posted after this moment
+ // are blocked.
+ // - Fully blocking (InsertFence(kBeginningOfTime)) - all tasks including
+ // already posted are blocked.
+ // - Delayed (InsertFenceAt(timestamp)) - blocks all tasks posted after given
+ // point in time (must be in the future).
+ //
+ // Only one fence can be scheduled at a time. Inserting a new fence
+ // will automatically remove the previous one, regardless of fence type.
+ void InsertFence(InsertFencePosition position);
+ void InsertFenceAt(TimeTicks time);
+
+ // Removes any previously added fence and unblocks execution of any tasks
+ // blocked by it.
+ void RemoveFence();
+
+ // Returns true if the queue has a fence but it isn't necessarily blocking
+ // execution of tasks (it may be the case if tasks enqueue order hasn't
+ // reached the number set for a fence).
+ bool HasActiveFence();
+
+ // Returns true if the queue has a fence which is blocking execution of tasks.
+ bool BlockedByFence() const;
+
+ void SetObserver(Observer* observer);
+
+ // SingleThreadTaskRunner implementation
+ bool RunsTasksInCurrentSequence() const override;
+ bool PostDelayedTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay) override;
+ bool PostNonNestableDelayedTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay) override;
+
+ bool PostTaskWithMetadata(PostedTask task);
+
+ protected:
+ TaskQueue(std::unique_ptr<internal::TaskQueueImpl> impl,
+ const TaskQueue::Spec& spec);
+ ~TaskQueue() override;
+
+ internal::TaskQueueImpl* GetTaskQueueImpl() const { return impl_.get(); }
+
+ private:
+ friend class internal::SequenceManagerImpl;
+ friend class internal::TaskQueueImpl;
+
+ bool IsOnMainThread() const;
+
+ Optional<MoveableAutoLock> AcquireImplReadLockIfNeeded() const;
+
+ // TaskQueue has ownership of an underlying implementation but in certain
+ // cases (e.g. detached frames) their lifetime may diverge.
+ // This method should be used to take away the impl for graceful shutdown.
+ // TaskQueue will disregard any calls or posting tasks thereafter.
+ std::unique_ptr<internal::TaskQueueImpl> TakeTaskQueueImpl();
+
+ // |impl_| can be written to on the main thread but can be read from
+ // any thread.
+ // |impl_lock_| must be acquired when writing to |impl_| or when accessing
+ // it from non-main thread. Reading from the main thread does not require
+ // a lock.
+ mutable Lock impl_lock_;
+ std::unique_ptr<internal::TaskQueueImpl> impl_;
+
+ const PlatformThreadId thread_id_;
+
+ const WeakPtr<internal::SequenceManagerImpl> sequence_manager_;
+
+ const scoped_refptr<internal::GracefulQueueShutdownHelper>
+ graceful_queue_shutdown_helper_;
+
+ THREAD_CHECKER(main_thread_checker_);
+
+ DISALLOW_COPY_AND_ASSIGN(TaskQueue);
+};
+
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_H_
diff --git a/base/task/sequence_manager/task_queue_impl.cc b/base/task/sequence_manager/task_queue_impl.cc
new file mode 100644
index 0000000000..250e8c438c
--- /dev/null
+++ b/base/task/sequence_manager/task_queue_impl.cc
@@ -0,0 +1,1016 @@
+// 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/task/sequence_manager/task_queue_impl.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/strings/stringprintf.h"
+#include "base/task/sequence_manager/sequence_manager_impl.h"
+#include "base/task/sequence_manager/time_domain.h"
+#include "base/task/sequence_manager/work_queue.h"
+#include "base/time/time.h"
+#include "base/trace_event/blame_context.h"
+
+namespace base {
+namespace sequence_manager {
+
+// static
+const char* TaskQueue::PriorityToString(TaskQueue::QueuePriority priority) {
+ switch (priority) {
+ case kControlPriority:
+ return "control";
+ case kHighestPriority:
+ return "highest";
+ case kHighPriority:
+ return "high";
+ case kNormalPriority:
+ return "normal";
+ case kLowPriority:
+ return "low";
+ case kBestEffortPriority:
+ return "best_effort";
+ default:
+ NOTREACHED();
+ return nullptr;
+ }
+}
+
+namespace internal {
+
+TaskQueueImpl::TaskQueueImpl(SequenceManagerImpl* sequence_manager,
+ TimeDomain* time_domain,
+ const TaskQueue::Spec& spec)
+ : name_(spec.name),
+ thread_id_(PlatformThread::CurrentId()),
+ any_thread_(sequence_manager, time_domain),
+ main_thread_only_(sequence_manager, this, time_domain),
+ should_monitor_quiescence_(spec.should_monitor_quiescence),
+ should_notify_observers_(spec.should_notify_observers) {
+ DCHECK(time_domain);
+}
+
+TaskQueueImpl::~TaskQueueImpl() {
+#if DCHECK_IS_ON()
+ AutoLock lock(any_thread_lock_);
+ // NOTE this check shouldn't fire because |SequenceManagerImpl::queues_|
+ // contains a strong reference to this TaskQueueImpl and the
+ // SequenceManagerImpl destructor calls UnregisterTaskQueue on all task
+ // queues.
+ DCHECK(!any_thread().sequence_manager)
+ << "UnregisterTaskQueue must be called first!";
+#endif
+}
+
+TaskQueueImpl::PostTaskResult::PostTaskResult()
+ : success(false), task(OnceClosure(), Location()) {}
+
+TaskQueueImpl::PostTaskResult::PostTaskResult(bool success,
+ TaskQueue::PostedTask task)
+ : success(success), task(std::move(task)) {}
+
+TaskQueueImpl::PostTaskResult::PostTaskResult(PostTaskResult&& move_from)
+ : success(move_from.success), task(std::move(move_from.task)) {}
+
+TaskQueueImpl::PostTaskResult::~PostTaskResult() = default;
+
+TaskQueueImpl::PostTaskResult TaskQueueImpl::PostTaskResult::Success() {
+ return PostTaskResult(true, TaskQueue::PostedTask(OnceClosure(), Location()));
+}
+
+TaskQueueImpl::PostTaskResult TaskQueueImpl::PostTaskResult::Fail(
+ TaskQueue::PostedTask task) {
+ return PostTaskResult(false, std::move(task));
+}
+
+TaskQueueImpl::Task::Task(TaskQueue::PostedTask task,
+ TimeTicks desired_run_time,
+ EnqueueOrder sequence_number)
+ : TaskQueue::Task(std::move(task), desired_run_time) {
+ // It might wrap around to a negative number but it's handled properly.
+ sequence_num = static_cast<int>(sequence_number);
+}
+
+TaskQueueImpl::Task::Task(TaskQueue::PostedTask task,
+ TimeTicks desired_run_time,
+ EnqueueOrder sequence_number,
+ EnqueueOrder enqueue_order)
+ : TaskQueue::Task(std::move(task), desired_run_time),
+ enqueue_order_(enqueue_order) {
+ // It might wrap around to a negative number but it's handled properly.
+ sequence_num = static_cast<int>(sequence_number);
+}
+
+TaskQueueImpl::AnyThread::AnyThread(SequenceManagerImpl* sequence_manager,
+ TimeDomain* time_domain)
+ : sequence_manager(sequence_manager), time_domain(time_domain) {}
+
+TaskQueueImpl::AnyThread::~AnyThread() = default;
+
+TaskQueueImpl::MainThreadOnly::MainThreadOnly(
+ SequenceManagerImpl* sequence_manager,
+ TaskQueueImpl* task_queue,
+ TimeDomain* time_domain)
+ : sequence_manager(sequence_manager),
+ time_domain(time_domain),
+ delayed_work_queue(
+ new WorkQueue(task_queue, "delayed", WorkQueue::QueueType::kDelayed)),
+ immediate_work_queue(new WorkQueue(task_queue,
+ "immediate",
+ WorkQueue::QueueType::kImmediate)),
+ set_index(0),
+ is_enabled_refcount(0),
+ voter_refcount(0),
+ blame_context(nullptr),
+ is_enabled_for_test(true) {}
+
+TaskQueueImpl::MainThreadOnly::~MainThreadOnly() = default;
+
+void TaskQueueImpl::UnregisterTaskQueue() {
+ TaskDeque immediate_incoming_queue;
+
+ {
+ AutoLock lock(any_thread_lock_);
+ AutoLock immediate_incoming_queue_lock(immediate_incoming_queue_lock_);
+
+ if (main_thread_only().time_domain)
+ main_thread_only().time_domain->UnregisterQueue(this);
+
+ if (!any_thread().sequence_manager)
+ return;
+
+ main_thread_only().on_task_completed_handler = OnTaskCompletedHandler();
+ any_thread().time_domain = nullptr;
+ main_thread_only().time_domain = nullptr;
+
+ any_thread().sequence_manager = nullptr;
+ main_thread_only().sequence_manager = nullptr;
+ any_thread().on_next_wake_up_changed_callback =
+ OnNextWakeUpChangedCallback();
+ main_thread_only().on_next_wake_up_changed_callback =
+ OnNextWakeUpChangedCallback();
+ immediate_incoming_queue.swap(immediate_incoming_queue_);
+ }
+
+ // It is possible for a task to hold a scoped_refptr to this, which
+ // will lead to TaskQueueImpl destructor being called when deleting a task.
+ // To avoid use-after-free, we need to clear all fields of a task queue
+ // before starting to delete the tasks.
+ // All work queues and priority queues containing tasks should be moved to
+ // local variables on stack (std::move for unique_ptrs and swap for queues)
+ // before clearing them and deleting tasks.
+
+ // Flush the queues outside of the lock because TSAN complains about a lock
+ // order inversion for tasks that are posted from within a lock, with a
+ // destructor that acquires the same lock.
+
+ std::priority_queue<Task> delayed_incoming_queue;
+ delayed_incoming_queue.swap(main_thread_only().delayed_incoming_queue);
+
+ std::unique_ptr<WorkQueue> immediate_work_queue =
+ std::move(main_thread_only().immediate_work_queue);
+ std::unique_ptr<WorkQueue> delayed_work_queue =
+ std::move(main_thread_only().delayed_work_queue);
+}
+
+const char* TaskQueueImpl::GetName() const {
+ return name_;
+}
+
+bool TaskQueueImpl::RunsTasksInCurrentSequence() const {
+ return PlatformThread::CurrentId() == thread_id_;
+}
+
+TaskQueueImpl::PostTaskResult TaskQueueImpl::PostDelayedTask(
+ TaskQueue::PostedTask task) {
+ if (task.delay.is_zero())
+ return PostImmediateTaskImpl(std::move(task));
+
+ return PostDelayedTaskImpl(std::move(task));
+}
+
+TaskQueueImpl::PostTaskResult TaskQueueImpl::PostImmediateTaskImpl(
+ TaskQueue::PostedTask task) {
+ // Use CHECK instead of DCHECK to crash earlier. See http://crbug.com/711167
+ // for details.
+ CHECK(task.callback);
+ AutoLock lock(any_thread_lock_);
+ if (!any_thread().sequence_manager)
+ return PostTaskResult::Fail(std::move(task));
+
+ EnqueueOrder sequence_number =
+ any_thread().sequence_manager->GetNextSequenceNumber();
+
+ PushOntoImmediateIncomingQueueLocked(Task(std::move(task),
+ any_thread().time_domain->Now(),
+ sequence_number, sequence_number));
+ return PostTaskResult::Success();
+}
+
+TaskQueueImpl::PostTaskResult TaskQueueImpl::PostDelayedTaskImpl(
+ TaskQueue::PostedTask task) {
+ // Use CHECK instead of DCHECK to crash earlier. See http://crbug.com/711167
+ // for details.
+ CHECK(task.callback);
+ DCHECK_GT(task.delay, TimeDelta());
+ if (PlatformThread::CurrentId() == thread_id_) {
+ // Lock-free fast path for delayed tasks posted from the main thread.
+ if (!main_thread_only().sequence_manager)
+ return PostTaskResult::Fail(std::move(task));
+
+ EnqueueOrder sequence_number =
+ main_thread_only().sequence_manager->GetNextSequenceNumber();
+
+ TimeTicks time_domain_now = main_thread_only().time_domain->Now();
+ TimeTicks time_domain_delayed_run_time = time_domain_now + task.delay;
+ PushOntoDelayedIncomingQueueFromMainThread(
+ Task(std::move(task), time_domain_delayed_run_time, sequence_number),
+ time_domain_now);
+ } else {
+ // NOTE posting a delayed task from a different thread is not expected to
+ // be common. This pathway is less optimal than perhaps it could be
+ // because it causes two main thread tasks to be run. Should this
+ // assumption prove to be false in future, we may need to revisit this.
+ AutoLock lock(any_thread_lock_);
+ if (!any_thread().sequence_manager)
+ return PostTaskResult::Fail(std::move(task));
+
+ EnqueueOrder sequence_number =
+ any_thread().sequence_manager->GetNextSequenceNumber();
+
+ TimeTicks time_domain_now = any_thread().time_domain->Now();
+ TimeTicks time_domain_delayed_run_time = time_domain_now + task.delay;
+ PushOntoDelayedIncomingQueueLocked(
+ Task(std::move(task), time_domain_delayed_run_time, sequence_number));
+ }
+ return PostTaskResult::Success();
+}
+
+void TaskQueueImpl::PushOntoDelayedIncomingQueueFromMainThread(
+ Task pending_task,
+ TimeTicks now) {
+ main_thread_only().sequence_manager->WillQueueTask(&pending_task);
+ main_thread_only().delayed_incoming_queue.push(std::move(pending_task));
+
+ LazyNow lazy_now(now);
+ UpdateDelayedWakeUp(&lazy_now);
+
+ TraceQueueSize();
+}
+
+void TaskQueueImpl::PushOntoDelayedIncomingQueueLocked(Task pending_task) {
+ any_thread().sequence_manager->WillQueueTask(&pending_task);
+
+ EnqueueOrder thread_hop_task_sequence_number =
+ any_thread().sequence_manager->GetNextSequenceNumber();
+ // TODO(altimin): Add a copy method to Task to capture metadata here.
+ PushOntoImmediateIncomingQueueLocked(Task(
+ TaskQueue::PostedTask(BindOnce(&TaskQueueImpl::ScheduleDelayedWorkTask,
+ Unretained(this), std::move(pending_task)),
+ FROM_HERE, TimeDelta(), Nestable::kNonNestable,
+ pending_task.task_type()),
+ TimeTicks(), thread_hop_task_sequence_number,
+ thread_hop_task_sequence_number));
+}
+
+void TaskQueueImpl::ScheduleDelayedWorkTask(Task pending_task) {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+ TimeTicks delayed_run_time = pending_task.delayed_run_time;
+ TimeTicks time_domain_now = main_thread_only().time_domain->Now();
+ if (delayed_run_time <= time_domain_now) {
+ // If |delayed_run_time| is in the past then push it onto the work queue
+ // immediately. To ensure the right task ordering we need to temporarily
+ // push it onto the |delayed_incoming_queue|.
+ delayed_run_time = time_domain_now;
+ pending_task.delayed_run_time = time_domain_now;
+ main_thread_only().delayed_incoming_queue.push(std::move(pending_task));
+ LazyNow lazy_now(time_domain_now);
+ WakeUpForDelayedWork(&lazy_now);
+ } else {
+ // If |delayed_run_time| is in the future we can queue it as normal.
+ PushOntoDelayedIncomingQueueFromMainThread(std::move(pending_task),
+ time_domain_now);
+ }
+ TraceQueueSize();
+}
+
+void TaskQueueImpl::PushOntoImmediateIncomingQueueLocked(Task task) {
+ // If the |immediate_incoming_queue| is empty we need a DoWork posted to make
+ // it run.
+ bool was_immediate_incoming_queue_empty;
+
+ EnqueueOrder sequence_number = task.enqueue_order();
+ TimeTicks desired_run_time = task.delayed_run_time;
+
+ {
+ AutoLock lock(immediate_incoming_queue_lock_);
+ was_immediate_incoming_queue_empty = immediate_incoming_queue().empty();
+ any_thread().sequence_manager->WillQueueTask(&task);
+ immediate_incoming_queue().push_back(std::move(task));
+ }
+
+ if (was_immediate_incoming_queue_empty) {
+ // However there's no point posting a DoWork for a blocked queue. NB we can
+ // only tell if it's disabled from the main thread.
+ bool queue_is_blocked =
+ RunsTasksInCurrentSequence() &&
+ (!IsQueueEnabled() || main_thread_only().current_fence);
+ any_thread().sequence_manager->OnQueueHasIncomingImmediateWork(
+ this, sequence_number, queue_is_blocked);
+ if (!any_thread().on_next_wake_up_changed_callback.is_null())
+ any_thread().on_next_wake_up_changed_callback.Run(desired_run_time);
+ }
+
+ TraceQueueSize();
+}
+
+void TaskQueueImpl::ReloadImmediateWorkQueueIfEmpty() {
+ if (!main_thread_only().immediate_work_queue->Empty())
+ return;
+
+ main_thread_only().immediate_work_queue->ReloadEmptyImmediateQueue();
+}
+
+void TaskQueueImpl::ReloadEmptyImmediateQueue(TaskDeque* queue) {
+ DCHECK(queue->empty());
+
+ AutoLock immediate_incoming_queue_lock(immediate_incoming_queue_lock_);
+ queue->swap(immediate_incoming_queue());
+
+ // Activate delayed fence if necessary. This is ideologically similar to
+ // ActivateDelayedFenceIfNeeded, but due to immediate tasks being posted
+ // from any thread we can't generate an enqueue order for the fence there,
+ // so we have to check all immediate tasks and use their enqueue order for
+ // a fence.
+ if (main_thread_only().delayed_fence) {
+ for (const Task& task : *queue) {
+ if (task.delayed_run_time >= main_thread_only().delayed_fence.value()) {
+ main_thread_only().delayed_fence = nullopt;
+ DCHECK(!main_thread_only().current_fence);
+ main_thread_only().current_fence = task.enqueue_order();
+ // Do not trigger WorkQueueSets notification when taking incoming
+ // immediate queue.
+ main_thread_only().immediate_work_queue->InsertFenceSilently(
+ main_thread_only().current_fence);
+ main_thread_only().delayed_work_queue->InsertFenceSilently(
+ main_thread_only().current_fence);
+ break;
+ }
+ }
+ }
+}
+
+bool TaskQueueImpl::IsEmpty() const {
+ if (!main_thread_only().delayed_work_queue->Empty() ||
+ !main_thread_only().delayed_incoming_queue.empty() ||
+ !main_thread_only().immediate_work_queue->Empty()) {
+ return false;
+ }
+
+ AutoLock lock(immediate_incoming_queue_lock_);
+ return immediate_incoming_queue().empty();
+}
+
+size_t TaskQueueImpl::GetNumberOfPendingTasks() const {
+ size_t task_count = 0;
+ task_count += main_thread_only().delayed_work_queue->Size();
+ task_count += main_thread_only().delayed_incoming_queue.size();
+ task_count += main_thread_only().immediate_work_queue->Size();
+
+ AutoLock lock(immediate_incoming_queue_lock_);
+ task_count += immediate_incoming_queue().size();
+ return task_count;
+}
+
+bool TaskQueueImpl::HasTaskToRunImmediately() const {
+ // Any work queue tasks count as immediate work.
+ if (!main_thread_only().delayed_work_queue->Empty() ||
+ !main_thread_only().immediate_work_queue->Empty()) {
+ return true;
+ }
+
+ // Tasks on |delayed_incoming_queue| that could run now, count as
+ // immediate work.
+ if (!main_thread_only().delayed_incoming_queue.empty() &&
+ main_thread_only().delayed_incoming_queue.top().delayed_run_time <=
+ main_thread_only().time_domain->CreateLazyNow().Now()) {
+ return true;
+ }
+
+ // Finally tasks on |immediate_incoming_queue| count as immediate work.
+ AutoLock lock(immediate_incoming_queue_lock_);
+ return !immediate_incoming_queue().empty();
+}
+
+Optional<TaskQueueImpl::DelayedWakeUp>
+TaskQueueImpl::GetNextScheduledWakeUpImpl() {
+ // Note we don't scheduled a wake-up for disabled queues.
+ if (main_thread_only().delayed_incoming_queue.empty() || !IsQueueEnabled())
+ return nullopt;
+
+ return main_thread_only().delayed_incoming_queue.top().delayed_wake_up();
+}
+
+Optional<TimeTicks> TaskQueueImpl::GetNextScheduledWakeUp() {
+ Optional<DelayedWakeUp> wake_up = GetNextScheduledWakeUpImpl();
+ if (!wake_up)
+ return nullopt;
+ return wake_up->time;
+}
+
+void TaskQueueImpl::WakeUpForDelayedWork(LazyNow* lazy_now) {
+ // Enqueue all delayed tasks that should be running now, skipping any that
+ // have been canceled.
+ while (!main_thread_only().delayed_incoming_queue.empty()) {
+ Task& task =
+ const_cast<Task&>(main_thread_only().delayed_incoming_queue.top());
+ if (!task.task || task.task.IsCancelled()) {
+ main_thread_only().delayed_incoming_queue.pop();
+ continue;
+ }
+ if (task.delayed_run_time > lazy_now->Now())
+ break;
+ ActivateDelayedFenceIfNeeded(task.delayed_run_time);
+ task.set_enqueue_order(
+ main_thread_only().sequence_manager->GetNextSequenceNumber());
+ main_thread_only().delayed_work_queue->Push(std::move(task));
+ main_thread_only().delayed_incoming_queue.pop();
+
+ // Normally WakeUpForDelayedWork is called inside DoWork, but it also
+ // can be called elsewhere (e.g. tests and fast-path for posting
+ // delayed tasks). Ensure that there is a DoWork posting. No-op inside
+ // existing DoWork due to DoWork deduplication.
+ if (IsQueueEnabled() || !main_thread_only().current_fence) {
+ main_thread_only().sequence_manager->MaybeScheduleImmediateWork(
+ FROM_HERE);
+ }
+ }
+
+ UpdateDelayedWakeUp(lazy_now);
+}
+
+void TaskQueueImpl::TraceQueueSize() const {
+ bool is_tracing;
+ TRACE_EVENT_CATEGORY_GROUP_ENABLED(
+ TRACE_DISABLED_BY_DEFAULT("sequence_manager"), &is_tracing);
+ if (!is_tracing)
+ return;
+
+ // It's only safe to access the work queues from the main thread.
+ // TODO(alexclarke): We should find another way of tracing this
+ if (PlatformThread::CurrentId() != thread_id_)
+ return;
+
+ AutoLock lock(immediate_incoming_queue_lock_);
+ TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("sequence_manager"), GetName(),
+ immediate_incoming_queue().size() +
+ main_thread_only().immediate_work_queue->Size() +
+ main_thread_only().delayed_work_queue->Size() +
+ main_thread_only().delayed_incoming_queue.size());
+}
+
+void TaskQueueImpl::SetQueuePriority(TaskQueue::QueuePriority priority) {
+ if (!main_thread_only().sequence_manager || priority == GetQueuePriority())
+ return;
+ main_thread_only()
+ .sequence_manager->main_thread_only()
+ .selector.SetQueuePriority(this, priority);
+}
+
+TaskQueue::QueuePriority TaskQueueImpl::GetQueuePriority() const {
+ size_t set_index = immediate_work_queue()->work_queue_set_index();
+ DCHECK_EQ(set_index, delayed_work_queue()->work_queue_set_index());
+ return static_cast<TaskQueue::QueuePriority>(set_index);
+}
+
+void TaskQueueImpl::AsValueInto(TimeTicks now,
+ trace_event::TracedValue* state) const {
+ AutoLock lock(any_thread_lock_);
+ AutoLock immediate_incoming_queue_lock(immediate_incoming_queue_lock_);
+ state->BeginDictionary();
+ state->SetString("name", GetName());
+ if (!main_thread_only().sequence_manager) {
+ state->SetBoolean("unregistered", true);
+ state->EndDictionary();
+ return;
+ }
+ DCHECK(main_thread_only().time_domain);
+ DCHECK(main_thread_only().delayed_work_queue);
+ DCHECK(main_thread_only().immediate_work_queue);
+
+ state->SetString(
+ "task_queue_id",
+ StringPrintf("0x%" PRIx64,
+ static_cast<uint64_t>(reinterpret_cast<uintptr_t>(this))));
+ state->SetBoolean("enabled", IsQueueEnabled());
+ state->SetString("time_domain_name",
+ main_thread_only().time_domain->GetName());
+ state->SetInteger("immediate_incoming_queue_size",
+ immediate_incoming_queue().size());
+ state->SetInteger("delayed_incoming_queue_size",
+ main_thread_only().delayed_incoming_queue.size());
+ state->SetInteger("immediate_work_queue_size",
+ main_thread_only().immediate_work_queue->Size());
+ state->SetInteger("delayed_work_queue_size",
+ main_thread_only().delayed_work_queue->Size());
+
+ if (!main_thread_only().delayed_incoming_queue.empty()) {
+ TimeDelta delay_to_next_task =
+ (main_thread_only().delayed_incoming_queue.top().delayed_run_time -
+ main_thread_only().time_domain->CreateLazyNow().Now());
+ state->SetDouble("delay_to_next_task_ms",
+ delay_to_next_task.InMillisecondsF());
+ }
+ if (main_thread_only().current_fence)
+ state->SetInteger("current_fence", main_thread_only().current_fence);
+ if (main_thread_only().delayed_fence) {
+ state->SetDouble(
+ "delayed_fence_seconds_from_now",
+ (main_thread_only().delayed_fence.value() - now).InSecondsF());
+ }
+
+ bool verbose = false;
+ TRACE_EVENT_CATEGORY_GROUP_ENABLED(
+ TRACE_DISABLED_BY_DEFAULT("sequence_manager.verbose_snapshots"),
+ &verbose);
+
+ if (verbose) {
+ state->BeginArray("immediate_incoming_queue");
+ QueueAsValueInto(immediate_incoming_queue(), now, state);
+ state->EndArray();
+ state->BeginArray("delayed_work_queue");
+ main_thread_only().delayed_work_queue->AsValueInto(now, state);
+ state->EndArray();
+ state->BeginArray("immediate_work_queue");
+ main_thread_only().immediate_work_queue->AsValueInto(now, state);
+ state->EndArray();
+ state->BeginArray("delayed_incoming_queue");
+ QueueAsValueInto(main_thread_only().delayed_incoming_queue, now, state);
+ state->EndArray();
+ }
+ state->SetString("priority", TaskQueue::PriorityToString(GetQueuePriority()));
+ state->EndDictionary();
+}
+
+void TaskQueueImpl::AddTaskObserver(MessageLoop::TaskObserver* task_observer) {
+ main_thread_only().task_observers.AddObserver(task_observer);
+}
+
+void TaskQueueImpl::RemoveTaskObserver(
+ MessageLoop::TaskObserver* task_observer) {
+ main_thread_only().task_observers.RemoveObserver(task_observer);
+}
+
+void TaskQueueImpl::NotifyWillProcessTask(const PendingTask& pending_task) {
+ DCHECK(should_notify_observers_);
+ if (main_thread_only().blame_context)
+ main_thread_only().blame_context->Enter();
+ for (auto& observer : main_thread_only().task_observers)
+ observer.WillProcessTask(pending_task);
+}
+
+void TaskQueueImpl::NotifyDidProcessTask(const PendingTask& pending_task) {
+ DCHECK(should_notify_observers_);
+ for (auto& observer : main_thread_only().task_observers)
+ observer.DidProcessTask(pending_task);
+ if (main_thread_only().blame_context)
+ main_thread_only().blame_context->Leave();
+}
+
+void TaskQueueImpl::SetTimeDomain(TimeDomain* time_domain) {
+ {
+ AutoLock lock(any_thread_lock_);
+ DCHECK(time_domain);
+ // NOTE this is similar to checking |any_thread().sequence_manager| but
+ // the TaskQueueSelectorTests constructs TaskQueueImpl directly with a null
+ // sequence_manager. Instead we check |any_thread().time_domain| which is
+ // another way of asserting that UnregisterTaskQueue has not been called.
+ DCHECK(any_thread().time_domain);
+ if (!any_thread().time_domain)
+ return;
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+ if (time_domain == main_thread_only().time_domain)
+ return;
+
+ any_thread().time_domain = time_domain;
+ }
+
+ main_thread_only().time_domain->UnregisterQueue(this);
+ main_thread_only().time_domain = time_domain;
+
+ LazyNow lazy_now = time_domain->CreateLazyNow();
+ // Clear scheduled wake up to ensure that new notifications are issued
+ // correctly.
+ // TODO(altimin): Remove this when we won't have to support changing time
+ // domains.
+ main_thread_only().scheduled_wake_up = nullopt;
+ UpdateDelayedWakeUp(&lazy_now);
+}
+
+TimeDomain* TaskQueueImpl::GetTimeDomain() const {
+ if (PlatformThread::CurrentId() == thread_id_)
+ return main_thread_only().time_domain;
+
+ AutoLock lock(any_thread_lock_);
+ return any_thread().time_domain;
+}
+
+void TaskQueueImpl::SetBlameContext(trace_event::BlameContext* blame_context) {
+ main_thread_only().blame_context = blame_context;
+}
+
+void TaskQueueImpl::InsertFence(TaskQueue::InsertFencePosition position) {
+ if (!main_thread_only().sequence_manager)
+ return;
+
+ // Only one fence may be present at a time.
+ main_thread_only().delayed_fence = nullopt;
+
+ EnqueueOrder previous_fence = main_thread_only().current_fence;
+ EnqueueOrder current_fence =
+ position == TaskQueue::InsertFencePosition::kNow
+ ? main_thread_only().sequence_manager->GetNextSequenceNumber()
+ : EnqueueOrder::blocking_fence();
+
+ // Tasks posted after this point will have a strictly higher enqueue order
+ // and will be blocked from running.
+ main_thread_only().current_fence = current_fence;
+ bool task_unblocked =
+ main_thread_only().immediate_work_queue->InsertFence(current_fence);
+ task_unblocked |=
+ main_thread_only().delayed_work_queue->InsertFence(current_fence);
+
+ if (!task_unblocked && previous_fence && previous_fence < current_fence) {
+ AutoLock lock(immediate_incoming_queue_lock_);
+ if (!immediate_incoming_queue().empty() &&
+ immediate_incoming_queue().front().enqueue_order() > previous_fence &&
+ immediate_incoming_queue().front().enqueue_order() < current_fence) {
+ task_unblocked = true;
+ }
+ }
+
+ if (IsQueueEnabled() && task_unblocked) {
+ main_thread_only().sequence_manager->MaybeScheduleImmediateWork(FROM_HERE);
+ }
+}
+
+void TaskQueueImpl::InsertFenceAt(TimeTicks time) {
+ // Task queue can have only one fence, delayed or not.
+ RemoveFence();
+ main_thread_only().delayed_fence = time;
+}
+
+void TaskQueueImpl::RemoveFence() {
+ if (!main_thread_only().sequence_manager)
+ return;
+
+ EnqueueOrder previous_fence = main_thread_only().current_fence;
+ main_thread_only().current_fence = EnqueueOrder::none();
+ main_thread_only().delayed_fence = nullopt;
+
+ bool task_unblocked = main_thread_only().immediate_work_queue->RemoveFence();
+ task_unblocked |= main_thread_only().delayed_work_queue->RemoveFence();
+
+ if (!task_unblocked && previous_fence) {
+ AutoLock lock(immediate_incoming_queue_lock_);
+ if (!immediate_incoming_queue().empty() &&
+ immediate_incoming_queue().front().enqueue_order() > previous_fence) {
+ task_unblocked = true;
+ }
+ }
+
+ if (IsQueueEnabled() && task_unblocked) {
+ main_thread_only().sequence_manager->MaybeScheduleImmediateWork(FROM_HERE);
+ }
+}
+
+bool TaskQueueImpl::BlockedByFence() const {
+ if (!main_thread_only().current_fence)
+ return false;
+
+ if (!main_thread_only().immediate_work_queue->BlockedByFence() ||
+ !main_thread_only().delayed_work_queue->BlockedByFence()) {
+ return false;
+ }
+
+ AutoLock lock(immediate_incoming_queue_lock_);
+ if (immediate_incoming_queue().empty())
+ return true;
+
+ return immediate_incoming_queue().front().enqueue_order() >
+ main_thread_only().current_fence;
+}
+
+bool TaskQueueImpl::HasActiveFence() {
+ if (main_thread_only().delayed_fence &&
+ main_thread_only().time_domain->Now() >
+ main_thread_only().delayed_fence.value()) {
+ return true;
+ }
+ return !!main_thread_only().current_fence;
+}
+
+bool TaskQueueImpl::CouldTaskRun(EnqueueOrder enqueue_order) const {
+ if (!IsQueueEnabled())
+ return false;
+
+ if (!main_thread_only().current_fence)
+ return true;
+
+ return enqueue_order < main_thread_only().current_fence;
+}
+
+// static
+void TaskQueueImpl::QueueAsValueInto(const TaskDeque& queue,
+ TimeTicks now,
+ trace_event::TracedValue* state) {
+ for (const Task& task : queue) {
+ TaskAsValueInto(task, now, state);
+ }
+}
+
+// static
+void TaskQueueImpl::QueueAsValueInto(const std::priority_queue<Task>& queue,
+ TimeTicks now,
+ trace_event::TracedValue* state) {
+ // Remove const to search |queue| in the destructive manner. Restore the
+ // content from |visited| later.
+ std::priority_queue<Task>* mutable_queue =
+ const_cast<std::priority_queue<Task>*>(&queue);
+ std::priority_queue<Task> visited;
+ while (!mutable_queue->empty()) {
+ TaskAsValueInto(mutable_queue->top(), now, state);
+ visited.push(std::move(const_cast<Task&>(mutable_queue->top())));
+ mutable_queue->pop();
+ }
+ *mutable_queue = std::move(visited);
+}
+
+// static
+void TaskQueueImpl::TaskAsValueInto(const Task& task,
+ TimeTicks now,
+ trace_event::TracedValue* state) {
+ state->BeginDictionary();
+ state->SetString("posted_from", task.posted_from.ToString());
+ if (task.enqueue_order_set())
+ state->SetInteger("enqueue_order", task.enqueue_order());
+ state->SetInteger("sequence_num", task.sequence_num);
+ state->SetBoolean("nestable", task.nestable == Nestable::kNestable);
+ state->SetBoolean("is_high_res", task.is_high_res);
+ state->SetBoolean("is_cancelled", task.task.IsCancelled());
+ state->SetDouble("delayed_run_time",
+ (task.delayed_run_time - TimeTicks()).InMillisecondsF());
+ state->SetDouble("delayed_run_time_milliseconds_from_now",
+ (task.delayed_run_time - now).InMillisecondsF());
+ state->EndDictionary();
+}
+
+TaskQueueImpl::QueueEnabledVoterImpl::QueueEnabledVoterImpl(
+ scoped_refptr<TaskQueue> task_queue)
+ : task_queue_(task_queue), enabled_(true) {}
+
+TaskQueueImpl::QueueEnabledVoterImpl::~QueueEnabledVoterImpl() {
+ if (task_queue_->GetTaskQueueImpl())
+ task_queue_->GetTaskQueueImpl()->RemoveQueueEnabledVoter(this);
+}
+
+void TaskQueueImpl::QueueEnabledVoterImpl::SetQueueEnabled(bool enabled) {
+ if (enabled_ == enabled)
+ return;
+
+ task_queue_->GetTaskQueueImpl()->OnQueueEnabledVoteChanged(enabled);
+ enabled_ = enabled;
+}
+
+void TaskQueueImpl::RemoveQueueEnabledVoter(
+ const QueueEnabledVoterImpl* voter) {
+ // Bail out if we're being called from TaskQueueImpl::UnregisterTaskQueue.
+ if (!main_thread_only().time_domain)
+ return;
+
+ bool was_enabled = IsQueueEnabled();
+ if (voter->enabled_) {
+ main_thread_only().is_enabled_refcount--;
+ DCHECK_GE(main_thread_only().is_enabled_refcount, 0);
+ }
+
+ main_thread_only().voter_refcount--;
+ DCHECK_GE(main_thread_only().voter_refcount, 0);
+
+ bool is_enabled = IsQueueEnabled();
+ if (was_enabled != is_enabled)
+ EnableOrDisableWithSelector(is_enabled);
+}
+
+bool TaskQueueImpl::IsQueueEnabled() const {
+ // By default is_enabled_refcount and voter_refcount both equal zero.
+ return (main_thread_only().is_enabled_refcount ==
+ main_thread_only().voter_refcount) &&
+ main_thread_only().is_enabled_for_test;
+}
+
+void TaskQueueImpl::OnQueueEnabledVoteChanged(bool enabled) {
+ bool was_enabled = IsQueueEnabled();
+ if (enabled) {
+ main_thread_only().is_enabled_refcount++;
+ DCHECK_LE(main_thread_only().is_enabled_refcount,
+ main_thread_only().voter_refcount);
+ } else {
+ main_thread_only().is_enabled_refcount--;
+ DCHECK_GE(main_thread_only().is_enabled_refcount, 0);
+ }
+
+ bool is_enabled = IsQueueEnabled();
+ if (was_enabled != is_enabled)
+ EnableOrDisableWithSelector(is_enabled);
+}
+
+void TaskQueueImpl::EnableOrDisableWithSelector(bool enable) {
+ if (!main_thread_only().sequence_manager)
+ return;
+
+ LazyNow lazy_now = main_thread_only().time_domain->CreateLazyNow();
+ UpdateDelayedWakeUp(&lazy_now);
+
+ if (enable) {
+ if (HasPendingImmediateWork() &&
+ !main_thread_only().on_next_wake_up_changed_callback.is_null()) {
+ // Delayed work notification will be issued via time domain.
+ main_thread_only().on_next_wake_up_changed_callback.Run(TimeTicks());
+ }
+
+ // Note the selector calls SequenceManager::OnTaskQueueEnabled which posts
+ // a DoWork if needed.
+ main_thread_only()
+ .sequence_manager->main_thread_only()
+ .selector.EnableQueue(this);
+ } else {
+ main_thread_only()
+ .sequence_manager->main_thread_only()
+ .selector.DisableQueue(this);
+ }
+}
+
+std::unique_ptr<TaskQueue::QueueEnabledVoter>
+TaskQueueImpl::CreateQueueEnabledVoter(scoped_refptr<TaskQueue> task_queue) {
+ DCHECK_EQ(task_queue->GetTaskQueueImpl(), this);
+ main_thread_only().voter_refcount++;
+ main_thread_only().is_enabled_refcount++;
+ return std::make_unique<QueueEnabledVoterImpl>(task_queue);
+}
+
+void TaskQueueImpl::SweepCanceledDelayedTasks(TimeTicks now) {
+ if (main_thread_only().delayed_incoming_queue.empty())
+ return;
+
+ // Remove canceled tasks.
+ std::priority_queue<Task> remaining_tasks;
+ while (!main_thread_only().delayed_incoming_queue.empty()) {
+ if (!main_thread_only().delayed_incoming_queue.top().task.IsCancelled()) {
+ remaining_tasks.push(std::move(
+ const_cast<Task&>(main_thread_only().delayed_incoming_queue.top())));
+ }
+ main_thread_only().delayed_incoming_queue.pop();
+ }
+
+ main_thread_only().delayed_incoming_queue = std::move(remaining_tasks);
+
+ LazyNow lazy_now(now);
+ UpdateDelayedWakeUp(&lazy_now);
+}
+
+void TaskQueueImpl::PushImmediateIncomingTaskForTest(
+ TaskQueueImpl::Task&& task) {
+ AutoLock lock(immediate_incoming_queue_lock_);
+ immediate_incoming_queue().push_back(std::move(task));
+}
+
+void TaskQueueImpl::RequeueDeferredNonNestableTask(
+ DeferredNonNestableTask task) {
+ DCHECK(task.task.nestable == Nestable::kNonNestable);
+ // The re-queued tasks have to be pushed onto the front because we'd otherwise
+ // violate the strict monotonically increasing enqueue order within the
+ // WorkQueue. We can't assign them a new enqueue order here because that will
+ // not behave correctly with fences and things will break (e.g Idle TQ).
+ if (task.work_queue_type == WorkQueueType::kDelayed) {
+ main_thread_only().delayed_work_queue->PushNonNestableTaskToFront(
+ std::move(task.task));
+ } else {
+ main_thread_only().immediate_work_queue->PushNonNestableTaskToFront(
+ std::move(task.task));
+ }
+}
+
+void TaskQueueImpl::SetOnNextWakeUpChangedCallback(
+ TaskQueueImpl::OnNextWakeUpChangedCallback callback) {
+#if DCHECK_IS_ON()
+ if (callback) {
+ DCHECK(main_thread_only().on_next_wake_up_changed_callback.is_null())
+ << "Can't assign two different observers to "
+ "blink::scheduler::TaskQueue";
+ }
+#endif
+ AutoLock lock(any_thread_lock_);
+ any_thread().on_next_wake_up_changed_callback = callback;
+ main_thread_only().on_next_wake_up_changed_callback = callback;
+}
+
+void TaskQueueImpl::UpdateDelayedWakeUp(LazyNow* lazy_now) {
+ return UpdateDelayedWakeUpImpl(lazy_now, GetNextScheduledWakeUpImpl());
+}
+
+void TaskQueueImpl::UpdateDelayedWakeUpImpl(
+ LazyNow* lazy_now,
+ Optional<TaskQueueImpl::DelayedWakeUp> wake_up) {
+ if (main_thread_only().scheduled_wake_up == wake_up)
+ return;
+ main_thread_only().scheduled_wake_up = wake_up;
+
+ if (wake_up &&
+ !main_thread_only().on_next_wake_up_changed_callback.is_null() &&
+ !HasPendingImmediateWork()) {
+ main_thread_only().on_next_wake_up_changed_callback.Run(wake_up->time);
+ }
+
+ main_thread_only().time_domain->SetNextWakeUpForQueue(this, wake_up,
+ lazy_now);
+}
+
+void TaskQueueImpl::SetDelayedWakeUpForTesting(
+ Optional<TaskQueueImpl::DelayedWakeUp> wake_up) {
+ LazyNow lazy_now = main_thread_only().time_domain->CreateLazyNow();
+ UpdateDelayedWakeUpImpl(&lazy_now, wake_up);
+}
+
+bool TaskQueueImpl::HasPendingImmediateWork() {
+ // Any work queue tasks count as immediate work.
+ if (!main_thread_only().delayed_work_queue->Empty() ||
+ !main_thread_only().immediate_work_queue->Empty()) {
+ return true;
+ }
+
+ // Finally tasks on |immediate_incoming_queue| count as immediate work.
+ AutoLock lock(immediate_incoming_queue_lock_);
+ return !immediate_incoming_queue().empty();
+}
+
+void TaskQueueImpl::SetOnTaskStartedHandler(
+ TaskQueueImpl::OnTaskStartedHandler handler) {
+ main_thread_only().on_task_started_handler = std::move(handler);
+}
+
+void TaskQueueImpl::OnTaskStarted(const TaskQueue::Task& task,
+ const TaskQueue::TaskTiming& task_timing) {
+ if (!main_thread_only().on_task_started_handler.is_null())
+ main_thread_only().on_task_started_handler.Run(task, task_timing);
+}
+
+void TaskQueueImpl::SetOnTaskCompletedHandler(
+ TaskQueueImpl::OnTaskCompletedHandler handler) {
+ main_thread_only().on_task_completed_handler = std::move(handler);
+}
+
+void TaskQueueImpl::OnTaskCompleted(const TaskQueue::Task& task,
+ const TaskQueue::TaskTiming& task_timing) {
+ if (!main_thread_only().on_task_completed_handler.is_null())
+ main_thread_only().on_task_completed_handler.Run(task, task_timing);
+}
+
+bool TaskQueueImpl::RequiresTaskTiming() const {
+ return !main_thread_only().on_task_started_handler.is_null() ||
+ !main_thread_only().on_task_completed_handler.is_null();
+}
+
+bool TaskQueueImpl::IsUnregistered() const {
+ AutoLock lock(any_thread_lock_);
+ return !any_thread().sequence_manager;
+}
+
+WeakPtr<SequenceManagerImpl> TaskQueueImpl::GetSequenceManagerWeakPtr() {
+ return main_thread_only().sequence_manager->GetWeakPtr();
+}
+
+scoped_refptr<GracefulQueueShutdownHelper>
+TaskQueueImpl::GetGracefulQueueShutdownHelper() {
+ return main_thread_only().sequence_manager->GetGracefulQueueShutdownHelper();
+}
+
+void TaskQueueImpl::SetQueueEnabledForTest(bool enabled) {
+ main_thread_only().is_enabled_for_test = enabled;
+ EnableOrDisableWithSelector(IsQueueEnabled());
+}
+
+void TaskQueueImpl::ActivateDelayedFenceIfNeeded(TimeTicks now) {
+ if (!main_thread_only().delayed_fence)
+ return;
+ if (main_thread_only().delayed_fence.value() > now)
+ return;
+ InsertFence(TaskQueue::InsertFencePosition::kNow);
+ main_thread_only().delayed_fence = nullopt;
+}
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/task_queue_impl.h b/base/task/sequence_manager/task_queue_impl.h
new file mode 100644
index 0000000000..b64dd9fd46
--- /dev/null
+++ b/base/task/sequence_manager/task_queue_impl.h
@@ -0,0 +1,471 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_IMPL_H_
+#define BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_IMPL_H_
+
+#include <stddef.h>
+
+#include <memory>
+#include <set>
+
+#include "base/callback.h"
+#include "base/containers/circular_deque.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/pending_task.h"
+#include "base/task/sequence_manager/enqueue_order.h"
+#include "base/task/sequence_manager/intrusive_heap.h"
+#include "base/task/sequence_manager/lazily_deallocated_deque.h"
+#include "base/task/sequence_manager/sequenced_task_source.h"
+#include "base/task/sequence_manager/task_queue.h"
+#include "base/threading/thread_checker.h"
+#include "base/trace_event/trace_event.h"
+#include "base/trace_event/trace_event_argument.h"
+
+namespace base {
+namespace sequence_manager {
+
+class LazyNow;
+class TimeDomain;
+
+namespace internal {
+
+class SequenceManagerImpl;
+class WorkQueue;
+class WorkQueueSets;
+
+struct IncomingImmediateWorkList {
+ IncomingImmediateWorkList* next = nullptr;
+ TaskQueueImpl* queue = nullptr;
+ internal::EnqueueOrder order;
+};
+
+// TaskQueueImpl has four main queues:
+//
+// Immediate (non-delayed) tasks:
+// |immediate_incoming_queue| - PostTask enqueues tasks here.
+// |immediate_work_queue| - SequenceManager takes immediate tasks here.
+//
+// Delayed tasks
+// |delayed_incoming_queue| - PostDelayedTask enqueues tasks here.
+// |delayed_work_queue| - SequenceManager takes delayed tasks here.
+//
+// The |immediate_incoming_queue| can be accessed from any thread, the other
+// queues are main-thread only. To reduce the overhead of locking,
+// |immediate_work_queue| is swapped with |immediate_incoming_queue| when
+// |immediate_work_queue| becomes empty.
+//
+// Delayed tasks are initially posted to |delayed_incoming_queue| and a wake-up
+// is scheduled with the TimeDomain. When the delay has elapsed, the TimeDomain
+// calls UpdateDelayedWorkQueue and ready delayed tasks are moved into the
+// |delayed_work_queue|. Note the EnqueueOrder (used for ordering) for a delayed
+// task is not set until it's moved into the |delayed_work_queue|.
+//
+// TaskQueueImpl uses the WorkQueueSets and the TaskQueueSelector to implement
+// prioritization. Task selection is done by the TaskQueueSelector and when a
+// queue is selected, it round-robins between the |immediate_work_queue| and
+// |delayed_work_queue|. The reason for this is we want to make sure delayed
+// tasks (normally the most common type) don't starve out immediate work.
+class BASE_EXPORT TaskQueueImpl {
+ public:
+ TaskQueueImpl(SequenceManagerImpl* sequence_manager,
+ TimeDomain* time_domain,
+ const TaskQueue::Spec& spec);
+
+ ~TaskQueueImpl();
+
+ // Represents a time at which a task wants to run. Tasks scheduled for the
+ // same point in time will be ordered by their sequence numbers.
+ struct DelayedWakeUp {
+ TimeTicks time;
+ int sequence_num;
+
+ bool operator!=(const DelayedWakeUp& other) const {
+ return time != other.time || other.sequence_num != sequence_num;
+ }
+
+ bool operator==(const DelayedWakeUp& other) const {
+ return !(*this != other);
+ }
+
+ bool operator<=(const DelayedWakeUp& other) const {
+ if (time == other.time) {
+ // Debug gcc builds can compare an element against itself.
+ DCHECK(sequence_num != other.sequence_num || this == &other);
+ // |PostedTask::sequence_num| is int and might wrap around to
+ // a negative number when casted from EnqueueOrder.
+ // This way of comparison handles that properly.
+ return (sequence_num - other.sequence_num) <= 0;
+ }
+ return time < other.time;
+ }
+ };
+
+ class BASE_EXPORT Task : public TaskQueue::Task {
+ public:
+ Task(TaskQueue::PostedTask task,
+ TimeTicks desired_run_time,
+ EnqueueOrder sequence_number);
+
+ Task(TaskQueue::PostedTask task,
+ TimeTicks desired_run_time,
+ EnqueueOrder sequence_number,
+ EnqueueOrder enqueue_order);
+
+ DelayedWakeUp delayed_wake_up() const {
+ // Since we use |sequence_num| in DelayedWakeUp for ordering purposes
+ // and integer overflow handling is type-sensitive it's worth to protect
+ // it from an unnoticed potential change in the PendingTask base class.
+ static_assert(std::is_same<decltype(sequence_num), int>::value, "");
+ return DelayedWakeUp{delayed_run_time, sequence_num};
+ }
+
+ EnqueueOrder enqueue_order() const {
+ DCHECK(enqueue_order_);
+ return enqueue_order_;
+ }
+
+ void set_enqueue_order(EnqueueOrder enqueue_order) {
+ DCHECK(!enqueue_order_);
+ enqueue_order_ = enqueue_order;
+ }
+
+ bool enqueue_order_set() const { return enqueue_order_; }
+
+ private:
+ // Similar to sequence number, but ultimately the |enqueue_order_| is what
+ // the scheduler uses for task ordering. For immediate tasks |enqueue_order|
+ // is set when posted, but for delayed tasks it's not defined until they are
+ // enqueued on the |delayed_work_queue_|. This is because otherwise delayed
+ // tasks could run before an immediate task posted after the delayed task.
+ EnqueueOrder enqueue_order_;
+ };
+
+ // A result retuned by PostDelayedTask. When scheduler failed to post a task
+ // due to being shutdown a task is returned to be destroyed outside the lock.
+ struct PostTaskResult {
+ PostTaskResult();
+ PostTaskResult(bool success, TaskQueue::PostedTask task);
+ PostTaskResult(PostTaskResult&& move_from);
+ PostTaskResult(const PostTaskResult& copy_from) = delete;
+ ~PostTaskResult();
+
+ static PostTaskResult Success();
+ static PostTaskResult Fail(TaskQueue::PostedTask task);
+
+ bool success;
+ TaskQueue::PostedTask task;
+ };
+
+ // Types of queues TaskQueueImpl is maintaining internally.
+ enum class WorkQueueType { kImmediate, kDelayed };
+
+ // Non-nestable tasks may get deferred but such queue is being maintained on
+ // SequenceManager side, so we need to keep information how to requeue it.
+ struct DeferredNonNestableTask {
+ internal::TaskQueueImpl::Task task;
+ internal::TaskQueueImpl* task_queue;
+ WorkQueueType work_queue_type;
+ };
+
+ using OnNextWakeUpChangedCallback = RepeatingCallback<void(TimeTicks)>;
+ using OnTaskStartedHandler =
+ RepeatingCallback<void(const TaskQueue::Task&,
+ const TaskQueue::TaskTiming&)>;
+ using OnTaskCompletedHandler =
+ RepeatingCallback<void(const TaskQueue::Task&,
+ const TaskQueue::TaskTiming&)>;
+
+ // TaskQueue implementation.
+ const char* GetName() const;
+ bool RunsTasksInCurrentSequence() const;
+ PostTaskResult PostDelayedTask(TaskQueue::PostedTask task);
+ // Require a reference to enclosing task queue for lifetime control.
+ std::unique_ptr<TaskQueue::QueueEnabledVoter> CreateQueueEnabledVoter(
+ scoped_refptr<TaskQueue> owning_task_queue);
+ bool IsQueueEnabled() const;
+ bool IsEmpty() const;
+ size_t GetNumberOfPendingTasks() const;
+ bool HasTaskToRunImmediately() const;
+ Optional<TimeTicks> GetNextScheduledWakeUp();
+ Optional<DelayedWakeUp> GetNextScheduledWakeUpImpl();
+ void SetQueuePriority(TaskQueue::QueuePriority priority);
+ TaskQueue::QueuePriority GetQueuePriority() const;
+ void AddTaskObserver(MessageLoop::TaskObserver* task_observer);
+ void RemoveTaskObserver(MessageLoop::TaskObserver* task_observer);
+ void SetTimeDomain(TimeDomain* time_domain);
+ TimeDomain* GetTimeDomain() const;
+ void SetBlameContext(trace_event::BlameContext* blame_context);
+ void InsertFence(TaskQueue::InsertFencePosition position);
+ void InsertFenceAt(TimeTicks time);
+ void RemoveFence();
+ bool HasActiveFence();
+ bool BlockedByFence() const;
+ // Implementation of TaskQueue::SetObserver.
+ void SetOnNextWakeUpChangedCallback(OnNextWakeUpChangedCallback callback);
+
+ void UnregisterTaskQueue();
+
+ // Returns true if a (potentially hypothetical) task with the specified
+ // |enqueue_order| could run on the queue. Must be called from the main
+ // thread.
+ bool CouldTaskRun(EnqueueOrder enqueue_order) const;
+
+ // Must only be called from the thread this task queue was created on.
+ void ReloadImmediateWorkQueueIfEmpty();
+
+ void AsValueInto(TimeTicks now, trace_event::TracedValue* state) const;
+
+ bool GetQuiescenceMonitored() const { return should_monitor_quiescence_; }
+ bool GetShouldNotifyObservers() const { return should_notify_observers_; }
+
+ void NotifyWillProcessTask(const PendingTask& pending_task);
+ void NotifyDidProcessTask(const PendingTask& pending_task);
+
+ // Check for available tasks in immediate work queues.
+ // Used to check if we need to generate notifications about delayed work.
+ bool HasPendingImmediateWork();
+
+ WorkQueue* delayed_work_queue() {
+ return main_thread_only().delayed_work_queue.get();
+ }
+
+ const WorkQueue* delayed_work_queue() const {
+ return main_thread_only().delayed_work_queue.get();
+ }
+
+ WorkQueue* immediate_work_queue() {
+ return main_thread_only().immediate_work_queue.get();
+ }
+
+ const WorkQueue* immediate_work_queue() const {
+ return main_thread_only().immediate_work_queue.get();
+ }
+
+ // Protected by SequenceManagerImpl's AnyThread lock.
+ IncomingImmediateWorkList* immediate_work_list_storage() {
+ return &immediate_work_list_storage_;
+ }
+
+ // Enqueues any delayed tasks which should be run now on the
+ // |delayed_work_queue|.
+ // Must be called from the main thread.
+ void WakeUpForDelayedWork(LazyNow* lazy_now);
+
+ HeapHandle heap_handle() const { return main_thread_only().heap_handle; }
+
+ void set_heap_handle(HeapHandle heap_handle) {
+ main_thread_only().heap_handle = heap_handle;
+ }
+
+ // Pushes |task| onto the front of the specified work queue. Caution must be
+ // taken with this API because you could easily starve out other work.
+ // TODO(kraynov): Simplify non-nestable task logic https://crbug.com/845437.
+ void RequeueDeferredNonNestableTask(DeferredNonNestableTask task);
+
+ void PushImmediateIncomingTaskForTest(TaskQueueImpl::Task&& task);
+
+ class QueueEnabledVoterImpl : public TaskQueue::QueueEnabledVoter {
+ public:
+ explicit QueueEnabledVoterImpl(scoped_refptr<TaskQueue> task_queue);
+ ~QueueEnabledVoterImpl() override;
+
+ // QueueEnabledVoter implementation.
+ void SetQueueEnabled(bool enabled) override;
+
+ TaskQueueImpl* GetTaskQueueForTest() const {
+ return task_queue_->GetTaskQueueImpl();
+ }
+
+ private:
+ friend class TaskQueueImpl;
+
+ scoped_refptr<TaskQueue> task_queue_;
+ bool enabled_;
+ };
+
+ // Iterates over |delayed_incoming_queue| removing canceled tasks.
+ void SweepCanceledDelayedTasks(TimeTicks now);
+
+ // Allows wrapping TaskQueue to set a handler to subscribe for notifications
+ // about started and completed tasks.
+ void SetOnTaskStartedHandler(OnTaskStartedHandler handler);
+ void OnTaskStarted(const TaskQueue::Task& task,
+ const TaskQueue::TaskTiming& task_timing);
+ void SetOnTaskCompletedHandler(OnTaskCompletedHandler handler);
+ void OnTaskCompleted(const TaskQueue::Task& task,
+ const TaskQueue::TaskTiming& task_timing);
+ bool RequiresTaskTiming() const;
+
+ WeakPtr<SequenceManagerImpl> GetSequenceManagerWeakPtr();
+
+ scoped_refptr<GracefulQueueShutdownHelper> GetGracefulQueueShutdownHelper();
+
+ // Returns true if this queue is unregistered or task queue manager is deleted
+ // and this queue can be safely deleted on any thread.
+ bool IsUnregistered() const;
+
+ // Disables queue for testing purposes, when a QueueEnabledVoter can't be
+ // constructed due to not having TaskQueue.
+ void SetQueueEnabledForTest(bool enabled);
+
+ protected:
+ void SetDelayedWakeUpForTesting(Optional<DelayedWakeUp> wake_up);
+
+ private:
+ friend class WorkQueue;
+ friend class WorkQueueTest;
+
+ struct AnyThread {
+ AnyThread(SequenceManagerImpl* sequence_manager, TimeDomain* time_domain);
+ ~AnyThread();
+
+ // SequenceManagerImpl, TimeDomain and Observer are maintained in two
+ // copies: inside AnyThread and inside MainThreadOnly. They can be changed
+ // only from main thread, so it should be locked before accessing from other
+ // threads.
+ SequenceManagerImpl* sequence_manager;
+ TimeDomain* time_domain;
+ // Callback corresponding to TaskQueue::Observer::OnQueueNextChanged.
+ OnNextWakeUpChangedCallback on_next_wake_up_changed_callback;
+ };
+
+ struct MainThreadOnly {
+ MainThreadOnly(SequenceManagerImpl* sequence_manager,
+ TaskQueueImpl* task_queue,
+ TimeDomain* time_domain);
+ ~MainThreadOnly();
+
+ // Another copy of SequenceManagerImpl, TimeDomain and Observer
+ // for lock-free access from the main thread.
+ // See description inside struct AnyThread for details.
+ SequenceManagerImpl* sequence_manager;
+ TimeDomain* time_domain;
+ // Callback corresponding to TaskQueue::Observer::OnQueueNextChanged.
+ OnNextWakeUpChangedCallback on_next_wake_up_changed_callback;
+
+ std::unique_ptr<WorkQueue> delayed_work_queue;
+ std::unique_ptr<WorkQueue> immediate_work_queue;
+ std::priority_queue<TaskQueueImpl::Task> delayed_incoming_queue;
+ ObserverList<MessageLoop::TaskObserver> task_observers;
+ size_t set_index;
+ HeapHandle heap_handle;
+ int is_enabled_refcount;
+ int voter_refcount;
+ trace_event::BlameContext* blame_context; // Not owned.
+ EnqueueOrder current_fence;
+ Optional<TimeTicks> delayed_fence;
+ OnTaskStartedHandler on_task_started_handler;
+ OnTaskCompletedHandler on_task_completed_handler;
+ // Last reported wake up, used only in UpdateWakeUp to avoid
+ // excessive calls.
+ Optional<DelayedWakeUp> scheduled_wake_up;
+ // If false, queue will be disabled. Used only for tests.
+ bool is_enabled_for_test;
+ };
+
+ PostTaskResult PostImmediateTaskImpl(TaskQueue::PostedTask task);
+ PostTaskResult PostDelayedTaskImpl(TaskQueue::PostedTask task);
+
+ // Push the task onto the |delayed_incoming_queue|. Lock-free main thread
+ // only fast path.
+ void PushOntoDelayedIncomingQueueFromMainThread(Task pending_task,
+ TimeTicks now);
+
+ // Push the task onto the |delayed_incoming_queue|. Slow path from other
+ // threads.
+ void PushOntoDelayedIncomingQueueLocked(Task pending_task);
+
+ void ScheduleDelayedWorkTask(Task pending_task);
+
+ void MoveReadyImmediateTasksToImmediateWorkQueueLocked();
+
+ // Push the task onto the |immediate_incoming_queue| and for auto pumped
+ // queues it calls MaybePostDoWorkOnMainRunner if the Incoming queue was
+ // empty.
+ void PushOntoImmediateIncomingQueueLocked(Task task);
+
+ using TaskDeque = circular_deque<Task>;
+
+ // Extracts all the tasks from the immediate incoming queue and swaps it with
+ // |queue| which must be empty.
+ // Can be called from any thread.
+ void ReloadEmptyImmediateQueue(TaskDeque* queue);
+
+ void TraceQueueSize() const;
+ static void QueueAsValueInto(const TaskDeque& queue,
+ TimeTicks now,
+ trace_event::TracedValue* state);
+ static void QueueAsValueInto(const std::priority_queue<Task>& queue,
+ TimeTicks now,
+ trace_event::TracedValue* state);
+ static void TaskAsValueInto(const Task& task,
+ TimeTicks now,
+ trace_event::TracedValue* state);
+
+ void RemoveQueueEnabledVoter(const QueueEnabledVoterImpl* voter);
+ void OnQueueEnabledVoteChanged(bool enabled);
+ void EnableOrDisableWithSelector(bool enable);
+
+ // Schedules delayed work on time domain and calls the observer.
+ void UpdateDelayedWakeUp(LazyNow* lazy_now);
+ void UpdateDelayedWakeUpImpl(LazyNow* lazy_now,
+ Optional<DelayedWakeUp> wake_up);
+
+ // Activate a delayed fence if a time has come.
+ void ActivateDelayedFenceIfNeeded(TimeTicks now);
+
+ const char* name_;
+
+ const PlatformThreadId thread_id_;
+
+ mutable Lock any_thread_lock_;
+ AnyThread any_thread_;
+ struct AnyThread& any_thread() {
+ any_thread_lock_.AssertAcquired();
+ return any_thread_;
+ }
+ const struct AnyThread& any_thread() const {
+ any_thread_lock_.AssertAcquired();
+ return any_thread_;
+ }
+
+ ThreadChecker main_thread_checker_;
+ MainThreadOnly main_thread_only_;
+ MainThreadOnly& main_thread_only() {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+ return main_thread_only_;
+ }
+ const MainThreadOnly& main_thread_only() const {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+ return main_thread_only_;
+ }
+
+ mutable Lock immediate_incoming_queue_lock_;
+ TaskDeque immediate_incoming_queue_;
+ TaskDeque& immediate_incoming_queue() {
+ immediate_incoming_queue_lock_.AssertAcquired();
+ return immediate_incoming_queue_;
+ }
+ const TaskDeque& immediate_incoming_queue() const {
+ immediate_incoming_queue_lock_.AssertAcquired();
+ return immediate_incoming_queue_;
+ }
+
+ // Protected by SequenceManagerImpl's AnyThread lock.
+ IncomingImmediateWorkList immediate_work_list_storage_;
+
+ const bool should_monitor_quiescence_;
+ const bool should_notify_observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(TaskQueueImpl);
+};
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_IMPL_H_
diff --git a/base/task/sequence_manager/task_queue_selector.cc b/base/task/sequence_manager/task_queue_selector.cc
new file mode 100644
index 0000000000..30a88bd9a9
--- /dev/null
+++ b/base/task/sequence_manager/task_queue_selector.cc
@@ -0,0 +1,407 @@
+// 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/task/sequence_manager/task_queue_selector.h"
+
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/task/sequence_manager/task_queue_impl.h"
+#include "base/task/sequence_manager/work_queue.h"
+#include "base/trace_event/trace_event_argument.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+namespace {
+
+TaskQueueSelectorLogic QueuePriorityToSelectorLogic(
+ TaskQueue::QueuePriority priority) {
+ switch (priority) {
+ case TaskQueue::kControlPriority:
+ return TaskQueueSelectorLogic::kControlPriorityLogic;
+ case TaskQueue::kHighestPriority:
+ return TaskQueueSelectorLogic::kHighestPriorityLogic;
+ case TaskQueue::kHighPriority:
+ return TaskQueueSelectorLogic::kHighPriorityLogic;
+ case TaskQueue::kNormalPriority:
+ return TaskQueueSelectorLogic::kNormalPriorityLogic;
+ case TaskQueue::kLowPriority:
+ return TaskQueueSelectorLogic::kLowPriorityLogic;
+ case TaskQueue::kBestEffortPriority:
+ return TaskQueueSelectorLogic::kBestEffortPriorityLogic;
+ default:
+ NOTREACHED();
+ return TaskQueueSelectorLogic::kCount;
+ }
+}
+
+// Helper function used to report the number of times a selector logic is
+// trigerred. This will create a histogram for the enumerated data.
+void ReportTaskSelectionLogic(TaskQueueSelectorLogic selector_logic) {
+ UMA_HISTOGRAM_ENUMERATION("TaskQueueSelector.TaskServicedPerSelectorLogic",
+ selector_logic, TaskQueueSelectorLogic::kCount);
+}
+
+} // namespace
+
+TaskQueueSelector::TaskQueueSelector()
+ : prioritizing_selector_(this, "enabled"),
+ immediate_starvation_count_(0),
+ high_priority_starvation_score_(0),
+ normal_priority_starvation_score_(0),
+ low_priority_starvation_score_(0),
+ task_queue_selector_observer_(nullptr) {}
+
+TaskQueueSelector::~TaskQueueSelector() = default;
+
+void TaskQueueSelector::AddQueue(internal::TaskQueueImpl* queue) {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+ DCHECK(queue->IsQueueEnabled());
+ prioritizing_selector_.AddQueue(queue, TaskQueue::kNormalPriority);
+}
+
+void TaskQueueSelector::RemoveQueue(internal::TaskQueueImpl* queue) {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+ if (queue->IsQueueEnabled()) {
+ prioritizing_selector_.RemoveQueue(queue);
+ }
+}
+
+void TaskQueueSelector::EnableQueue(internal::TaskQueueImpl* queue) {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+ DCHECK(queue->IsQueueEnabled());
+ prioritizing_selector_.AddQueue(queue, queue->GetQueuePriority());
+ if (task_queue_selector_observer_)
+ task_queue_selector_observer_->OnTaskQueueEnabled(queue);
+}
+
+void TaskQueueSelector::DisableQueue(internal::TaskQueueImpl* queue) {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+ DCHECK(!queue->IsQueueEnabled());
+ prioritizing_selector_.RemoveQueue(queue);
+}
+
+void TaskQueueSelector::SetQueuePriority(internal::TaskQueueImpl* queue,
+ TaskQueue::QueuePriority priority) {
+ DCHECK_LT(priority, TaskQueue::kQueuePriorityCount);
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+ if (queue->IsQueueEnabled()) {
+ prioritizing_selector_.ChangeSetIndex(queue, priority);
+ } else {
+ // Disabled queue is not in any set so we can't use ChangeSetIndex here
+ // and have to assign priority for the queue itself.
+ queue->delayed_work_queue()->AssignSetIndex(priority);
+ queue->immediate_work_queue()->AssignSetIndex(priority);
+ }
+ DCHECK_EQ(priority, queue->GetQueuePriority());
+}
+
+TaskQueue::QueuePriority TaskQueueSelector::NextPriority(
+ TaskQueue::QueuePriority priority) {
+ DCHECK(priority < TaskQueue::kQueuePriorityCount);
+ return static_cast<TaskQueue::QueuePriority>(static_cast<int>(priority) + 1);
+}
+
+TaskQueueSelector::PrioritizingSelector::PrioritizingSelector(
+ TaskQueueSelector* task_queue_selector,
+ const char* name)
+ : task_queue_selector_(task_queue_selector),
+ delayed_work_queue_sets_(TaskQueue::kQueuePriorityCount, name),
+ immediate_work_queue_sets_(TaskQueue::kQueuePriorityCount, name) {}
+
+void TaskQueueSelector::PrioritizingSelector::AddQueue(
+ internal::TaskQueueImpl* queue,
+ TaskQueue::QueuePriority priority) {
+#if DCHECK_IS_ON()
+ DCHECK(!CheckContainsQueueForTest(queue));
+#endif
+ delayed_work_queue_sets_.AddQueue(queue->delayed_work_queue(), priority);
+ immediate_work_queue_sets_.AddQueue(queue->immediate_work_queue(), priority);
+#if DCHECK_IS_ON()
+ DCHECK(CheckContainsQueueForTest(queue));
+#endif
+}
+
+void TaskQueueSelector::PrioritizingSelector::ChangeSetIndex(
+ internal::TaskQueueImpl* queue,
+ TaskQueue::QueuePriority priority) {
+#if DCHECK_IS_ON()
+ DCHECK(CheckContainsQueueForTest(queue));
+#endif
+ delayed_work_queue_sets_.ChangeSetIndex(queue->delayed_work_queue(),
+ priority);
+ immediate_work_queue_sets_.ChangeSetIndex(queue->immediate_work_queue(),
+ priority);
+#if DCHECK_IS_ON()
+ DCHECK(CheckContainsQueueForTest(queue));
+#endif
+}
+
+void TaskQueueSelector::PrioritizingSelector::RemoveQueue(
+ internal::TaskQueueImpl* queue) {
+#if DCHECK_IS_ON()
+ DCHECK(CheckContainsQueueForTest(queue));
+#endif
+ delayed_work_queue_sets_.RemoveQueue(queue->delayed_work_queue());
+ immediate_work_queue_sets_.RemoveQueue(queue->immediate_work_queue());
+
+#if DCHECK_IS_ON()
+ DCHECK(!CheckContainsQueueForTest(queue));
+#endif
+}
+
+bool TaskQueueSelector::PrioritizingSelector::
+ ChooseOldestImmediateTaskWithPriority(TaskQueue::QueuePriority priority,
+ WorkQueue** out_work_queue) const {
+ return immediate_work_queue_sets_.GetOldestQueueInSet(priority,
+ out_work_queue);
+}
+
+bool TaskQueueSelector::PrioritizingSelector::
+ ChooseOldestDelayedTaskWithPriority(TaskQueue::QueuePriority priority,
+ WorkQueue** out_work_queue) const {
+ return delayed_work_queue_sets_.GetOldestQueueInSet(priority, out_work_queue);
+}
+
+bool TaskQueueSelector::PrioritizingSelector::
+ ChooseOldestImmediateOrDelayedTaskWithPriority(
+ TaskQueue::QueuePriority priority,
+ bool* out_chose_delayed_over_immediate,
+ WorkQueue** out_work_queue) const {
+ WorkQueue* immediate_queue;
+ DCHECK_EQ(*out_chose_delayed_over_immediate, false);
+ EnqueueOrder immediate_enqueue_order;
+ if (immediate_work_queue_sets_.GetOldestQueueAndEnqueueOrderInSet(
+ priority, &immediate_queue, &immediate_enqueue_order)) {
+ WorkQueue* delayed_queue;
+ EnqueueOrder delayed_enqueue_order;
+ if (delayed_work_queue_sets_.GetOldestQueueAndEnqueueOrderInSet(
+ priority, &delayed_queue, &delayed_enqueue_order)) {
+ if (immediate_enqueue_order < delayed_enqueue_order) {
+ *out_work_queue = immediate_queue;
+ } else {
+ *out_chose_delayed_over_immediate = true;
+ *out_work_queue = delayed_queue;
+ }
+ } else {
+ *out_work_queue = immediate_queue;
+ }
+ return true;
+ }
+ return delayed_work_queue_sets_.GetOldestQueueInSet(priority, out_work_queue);
+}
+
+bool TaskQueueSelector::PrioritizingSelector::ChooseOldestWithPriority(
+ TaskQueue::QueuePriority priority,
+ bool* out_chose_delayed_over_immediate,
+ WorkQueue** out_work_queue) const {
+ // Select an immediate work queue if we are starving immediate tasks.
+ if (task_queue_selector_->immediate_starvation_count_ >=
+ kMaxDelayedStarvationTasks) {
+ if (ChooseOldestImmediateTaskWithPriority(priority, out_work_queue))
+ return true;
+ return ChooseOldestDelayedTaskWithPriority(priority, out_work_queue);
+ }
+ return ChooseOldestImmediateOrDelayedTaskWithPriority(
+ priority, out_chose_delayed_over_immediate, out_work_queue);
+}
+
+bool TaskQueueSelector::PrioritizingSelector::SelectWorkQueueToService(
+ TaskQueue::QueuePriority max_priority,
+ WorkQueue** out_work_queue,
+ bool* out_chose_delayed_over_immediate) {
+ DCHECK(task_queue_selector_->main_thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(*out_chose_delayed_over_immediate, false);
+
+ // Always service the control queue if it has any work.
+ if (max_priority > TaskQueue::kControlPriority &&
+ ChooseOldestWithPriority(TaskQueue::kControlPriority,
+ out_chose_delayed_over_immediate,
+ out_work_queue)) {
+ ReportTaskSelectionLogic(TaskQueueSelectorLogic::kControlPriorityLogic);
+ return true;
+ }
+
+ // Select from the low priority queue if we are starving it.
+ if (max_priority > TaskQueue::kLowPriority &&
+ task_queue_selector_->low_priority_starvation_score_ >=
+ kMaxLowPriorityStarvationScore &&
+ ChooseOldestWithPriority(TaskQueue::kLowPriority,
+ out_chose_delayed_over_immediate,
+ out_work_queue)) {
+ ReportTaskSelectionLogic(
+ TaskQueueSelectorLogic::kLowPriorityStarvationLogic);
+ return true;
+ }
+
+ // Select from the normal priority queue if we are starving it.
+ if (max_priority > TaskQueue::kNormalPriority &&
+ task_queue_selector_->normal_priority_starvation_score_ >=
+ kMaxNormalPriorityStarvationScore &&
+ ChooseOldestWithPriority(TaskQueue::kNormalPriority,
+ out_chose_delayed_over_immediate,
+ out_work_queue)) {
+ ReportTaskSelectionLogic(
+ TaskQueueSelectorLogic::kNormalPriorityStarvationLogic);
+ return true;
+ }
+
+ // Select from the high priority queue if we are starving it.
+ if (max_priority > TaskQueue::kHighPriority &&
+ task_queue_selector_->high_priority_starvation_score_ >=
+ kMaxHighPriorityStarvationScore &&
+ ChooseOldestWithPriority(TaskQueue::kHighPriority,
+ out_chose_delayed_over_immediate,
+ out_work_queue)) {
+ ReportTaskSelectionLogic(
+ TaskQueueSelectorLogic::kHighPriorityStarvationLogic);
+ return true;
+ }
+
+ // Otherwise choose in priority order.
+ for (TaskQueue::QueuePriority priority = TaskQueue::kHighestPriority;
+ priority < max_priority; priority = NextPriority(priority)) {
+ if (ChooseOldestWithPriority(priority, out_chose_delayed_over_immediate,
+ out_work_queue)) {
+ ReportTaskSelectionLogic(QueuePriorityToSelectorLogic(priority));
+ return true;
+ }
+ }
+ return false;
+}
+
+#if DCHECK_IS_ON() || !defined(NDEBUG)
+bool TaskQueueSelector::PrioritizingSelector::CheckContainsQueueForTest(
+ const internal::TaskQueueImpl* queue) const {
+ bool contains_delayed_work_queue =
+ delayed_work_queue_sets_.ContainsWorkQueueForTest(
+ queue->delayed_work_queue());
+
+ bool contains_immediate_work_queue =
+ immediate_work_queue_sets_.ContainsWorkQueueForTest(
+ queue->immediate_work_queue());
+
+ DCHECK_EQ(contains_delayed_work_queue, contains_immediate_work_queue);
+ return contains_delayed_work_queue;
+}
+#endif
+
+bool TaskQueueSelector::SelectWorkQueueToService(WorkQueue** out_work_queue) {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+ bool chose_delayed_over_immediate = false;
+ bool found_queue = prioritizing_selector_.SelectWorkQueueToService(
+ TaskQueue::kQueuePriorityCount, out_work_queue,
+ &chose_delayed_over_immediate);
+ if (!found_queue)
+ return false;
+
+ // We could use |(*out_work_queue)->task_queue()->GetQueuePriority()| here but
+ // for re-queued non-nestable tasks |task_queue()| returns null.
+ DidSelectQueueWithPriority(static_cast<TaskQueue::QueuePriority>(
+ (*out_work_queue)->work_queue_set_index()),
+ chose_delayed_over_immediate);
+ return true;
+}
+
+void TaskQueueSelector::DidSelectQueueWithPriority(
+ TaskQueue::QueuePriority priority,
+ bool chose_delayed_over_immediate) {
+ switch (priority) {
+ case TaskQueue::kControlPriority:
+ break;
+ case TaskQueue::kHighestPriority:
+ low_priority_starvation_score_ +=
+ HasTasksWithPriority(TaskQueue::kLowPriority)
+ ? kSmallScoreIncrementForLowPriorityStarvation
+ : 0;
+ normal_priority_starvation_score_ +=
+ HasTasksWithPriority(TaskQueue::kNormalPriority)
+ ? kSmallScoreIncrementForNormalPriorityStarvation
+ : 0;
+ high_priority_starvation_score_ +=
+ HasTasksWithPriority(TaskQueue::kHighPriority)
+ ? kSmallScoreIncrementForHighPriorityStarvation
+ : 0;
+ break;
+ case TaskQueue::kHighPriority:
+ low_priority_starvation_score_ +=
+ HasTasksWithPriority(TaskQueue::kLowPriority)
+ ? kLargeScoreIncrementForLowPriorityStarvation
+ : 0;
+ normal_priority_starvation_score_ +=
+ HasTasksWithPriority(TaskQueue::kNormalPriority)
+ ? kLargeScoreIncrementForNormalPriorityStarvation
+ : 0;
+ high_priority_starvation_score_ = 0;
+ break;
+ case TaskQueue::kNormalPriority:
+ low_priority_starvation_score_ +=
+ HasTasksWithPriority(TaskQueue::kLowPriority)
+ ? kLargeScoreIncrementForLowPriorityStarvation
+ : 0;
+ normal_priority_starvation_score_ = 0;
+ break;
+ case TaskQueue::kLowPriority:
+ case TaskQueue::kBestEffortPriority:
+ low_priority_starvation_score_ = 0;
+ high_priority_starvation_score_ = 0;
+ normal_priority_starvation_score_ = 0;
+ break;
+ default:
+ NOTREACHED();
+ }
+ if (chose_delayed_over_immediate) {
+ immediate_starvation_count_++;
+ } else {
+ immediate_starvation_count_ = 0;
+ }
+}
+
+void TaskQueueSelector::AsValueInto(trace_event::TracedValue* state) const {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+ state->SetInteger("high_priority_starvation_score",
+ high_priority_starvation_score_);
+ state->SetInteger("normal_priority_starvation_score",
+ normal_priority_starvation_score_);
+ state->SetInteger("low_priority_starvation_score",
+ low_priority_starvation_score_);
+ state->SetInteger("immediate_starvation_count", immediate_starvation_count_);
+}
+
+void TaskQueueSelector::SetTaskQueueSelectorObserver(Observer* observer) {
+ task_queue_selector_observer_ = observer;
+}
+
+bool TaskQueueSelector::AllEnabledWorkQueuesAreEmpty() const {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+ for (TaskQueue::QueuePriority priority = TaskQueue::kControlPriority;
+ priority < TaskQueue::kQueuePriorityCount;
+ priority = NextPriority(priority)) {
+ if (!prioritizing_selector_.delayed_work_queue_sets()->IsSetEmpty(
+ priority) ||
+ !prioritizing_selector_.immediate_work_queue_sets()->IsSetEmpty(
+ priority)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void TaskQueueSelector::SetImmediateStarvationCountForTest(
+ size_t immediate_starvation_count) {
+ immediate_starvation_count_ = immediate_starvation_count;
+}
+
+bool TaskQueueSelector::HasTasksWithPriority(
+ TaskQueue::QueuePriority priority) {
+ return !prioritizing_selector_.delayed_work_queue_sets()->IsSetEmpty(
+ priority) ||
+ !prioritizing_selector_.immediate_work_queue_sets()->IsSetEmpty(
+ priority);
+}
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/task_queue_selector.h b/base/task/sequence_manager/task_queue_selector.h
new file mode 100644
index 0000000000..182158be3a
--- /dev/null
+++ b/base/task/sequence_manager/task_queue_selector.h
@@ -0,0 +1,225 @@
+// 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_TASK_SEQUENCE_MANAGER_TASK_QUEUE_SELECTOR_H_
+#define BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_SELECTOR_H_
+
+#include <stddef.h>
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/pending_task.h"
+#include "base/task/sequence_manager/task_queue_selector_logic.h"
+#include "base/task/sequence_manager/work_queue_sets.h"
+#include "base/threading/thread_checker.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+// TaskQueueSelector is used by the SchedulerHelper to enable prioritization
+// of particular task queues.
+class BASE_EXPORT TaskQueueSelector {
+ public:
+ TaskQueueSelector();
+ ~TaskQueueSelector();
+
+ // Called to register a queue that can be selected. This function is called
+ // on the main thread.
+ void AddQueue(internal::TaskQueueImpl* queue);
+
+ // The specified work will no longer be considered for selection. This
+ // function is called on the main thread.
+ void RemoveQueue(internal::TaskQueueImpl* queue);
+
+ // Make |queue| eligible for selection. This function is called on the main
+ // thread. Must only be called if |queue| is disabled.
+ void EnableQueue(internal::TaskQueueImpl* queue);
+
+ // Disable selection from |queue|. Must only be called if |queue| is enabled.
+ void DisableQueue(internal::TaskQueueImpl* queue);
+
+ // Called get or set the priority of |queue|.
+ void SetQueuePriority(internal::TaskQueueImpl* queue,
+ TaskQueue::QueuePriority priority);
+
+ // Called to choose the work queue from which the next task should be taken
+ // and run. Return true if |out_work_queue| indicates the queue to service or
+ // false to avoid running any task.
+ //
+ // This function is called on the main thread.
+ bool SelectWorkQueueToService(WorkQueue** out_work_queue);
+
+ // Serialize the selector state for tracing.
+ void AsValueInto(trace_event::TracedValue* state) const;
+
+ class BASE_EXPORT Observer {
+ public:
+ virtual ~Observer() = default;
+
+ // Called when |queue| transitions from disabled to enabled.
+ virtual void OnTaskQueueEnabled(internal::TaskQueueImpl* queue) = 0;
+ };
+
+ // Called once to set the Observer. This function is called
+ // on the main thread. If |observer| is null, then no callbacks will occur.
+ void SetTaskQueueSelectorObserver(Observer* observer);
+
+ // Returns true if all the enabled work queues are empty. Returns false
+ // otherwise.
+ bool AllEnabledWorkQueuesAreEmpty() const;
+
+ protected:
+ class BASE_EXPORT PrioritizingSelector {
+ public:
+ PrioritizingSelector(TaskQueueSelector* task_queue_selector,
+ const char* name);
+
+ void ChangeSetIndex(internal::TaskQueueImpl* queue,
+ TaskQueue::QueuePriority priority);
+ void AddQueue(internal::TaskQueueImpl* queue,
+ TaskQueue::QueuePriority priority);
+ void RemoveQueue(internal::TaskQueueImpl* queue);
+
+ bool SelectWorkQueueToService(TaskQueue::QueuePriority max_priority,
+ WorkQueue** out_work_queue,
+ bool* out_chose_delayed_over_immediate);
+
+ WorkQueueSets* delayed_work_queue_sets() {
+ return &delayed_work_queue_sets_;
+ }
+ WorkQueueSets* immediate_work_queue_sets() {
+ return &immediate_work_queue_sets_;
+ }
+
+ const WorkQueueSets* delayed_work_queue_sets() const {
+ return &delayed_work_queue_sets_;
+ }
+ const WorkQueueSets* immediate_work_queue_sets() const {
+ return &immediate_work_queue_sets_;
+ }
+
+ bool ChooseOldestWithPriority(TaskQueue::QueuePriority priority,
+ bool* out_chose_delayed_over_immediate,
+ WorkQueue** out_work_queue) const;
+
+#if DCHECK_IS_ON() || !defined(NDEBUG)
+ bool CheckContainsQueueForTest(const internal::TaskQueueImpl* queue) const;
+#endif
+
+ private:
+ bool ChooseOldestImmediateTaskWithPriority(
+ TaskQueue::QueuePriority priority,
+ WorkQueue** out_work_queue) const;
+
+ bool ChooseOldestDelayedTaskWithPriority(TaskQueue::QueuePriority priority,
+ WorkQueue** out_work_queue) const;
+
+ // Return true if |out_queue| contains the queue with the oldest pending
+ // task from the set of queues of |priority|, or false if all queues of that
+ // priority are empty. In addition |out_chose_delayed_over_immediate| is set
+ // to true iff we chose a delayed work queue in favour of an immediate work
+ // queue.
+ bool ChooseOldestImmediateOrDelayedTaskWithPriority(
+ TaskQueue::QueuePriority priority,
+ bool* out_chose_delayed_over_immediate,
+ WorkQueue** out_work_queue) const;
+
+ const TaskQueueSelector* task_queue_selector_;
+ WorkQueueSets delayed_work_queue_sets_;
+ WorkQueueSets immediate_work_queue_sets_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrioritizingSelector);
+ };
+
+ // Return true if |out_queue| contains the queue with the oldest pending task
+ // from the set of queues of |priority|, or false if all queues of that
+ // priority are empty. In addition |out_chose_delayed_over_immediate| is set
+ // to true iff we chose a delayed work queue in favour of an immediate work
+ // queue. This method will force select an immediate task if those are being
+ // starved by delayed tasks.
+ void SetImmediateStarvationCountForTest(size_t immediate_starvation_count);
+
+ PrioritizingSelector* prioritizing_selector_for_test() {
+ return &prioritizing_selector_;
+ }
+
+ // Maximum score to accumulate before high priority tasks are run even in
+ // the presence of highest priority tasks.
+ static const size_t kMaxHighPriorityStarvationScore = 3;
+
+ // Increment to be applied to the high priority starvation score when a task
+ // should have only a small effect on the score. E.g. A number of highest
+ // priority tasks must run before the high priority queue is considered
+ // starved.
+ static const size_t kSmallScoreIncrementForHighPriorityStarvation = 1;
+
+ // Maximum score to accumulate before normal priority tasks are run even in
+ // the presence of higher priority tasks i.e. highest and high priority tasks.
+ static const size_t kMaxNormalPriorityStarvationScore = 5;
+
+ // Increment to be applied to the normal priority starvation score when a task
+ // should have a large effect on the score. E.g Only a few high priority
+ // priority tasks must run before the normal priority queue is considered
+ // starved.
+ static const size_t kLargeScoreIncrementForNormalPriorityStarvation = 2;
+
+ // Increment to be applied to the normal priority starvation score when a task
+ // should have only a small effect on the score. E.g. A number of highest
+ // priority tasks must run before the normal priority queue is considered
+ // starved.
+ static const size_t kSmallScoreIncrementForNormalPriorityStarvation = 1;
+
+ // Maximum score to accumulate before low priority tasks are run even in the
+ // presence of highest, high, or normal priority tasks.
+ static const size_t kMaxLowPriorityStarvationScore = 25;
+
+ // Increment to be applied to the low priority starvation score when a task
+ // should have a large effect on the score. E.g. Only a few normal/high
+ // priority tasks must run before the low priority queue is considered
+ // starved.
+ static const size_t kLargeScoreIncrementForLowPriorityStarvation = 5;
+
+ // Increment to be applied to the low priority starvation score when a task
+ // should have only a small effect on the score. E.g. A lot of highest
+ // priority tasks must run before the low priority queue is considered
+ // starved.
+ static const size_t kSmallScoreIncrementForLowPriorityStarvation = 1;
+
+ // Maximum number of delayed tasks tasks which can be run while there's a
+ // waiting non-delayed task.
+ static const size_t kMaxDelayedStarvationTasks = 3;
+
+ private:
+ // Returns the priority which is next after |priority|.
+ static TaskQueue::QueuePriority NextPriority(
+ TaskQueue::QueuePriority priority);
+
+ bool SelectWorkQueueToServiceInternal(WorkQueue** out_work_queue);
+
+ // Called whenever the selector chooses a task queue for execution with the
+ // priority |priority|.
+ void DidSelectQueueWithPriority(TaskQueue::QueuePriority priority,
+ bool chose_delayed_over_immediate);
+
+ // Returns true if there are pending tasks with priority |priority|.
+ bool HasTasksWithPriority(TaskQueue::QueuePriority priority);
+
+ ThreadChecker main_thread_checker_;
+
+ PrioritizingSelector prioritizing_selector_;
+ size_t immediate_starvation_count_;
+ size_t high_priority_starvation_score_;
+ size_t normal_priority_starvation_score_;
+ size_t low_priority_starvation_score_;
+
+ Observer* task_queue_selector_observer_; // Not owned.
+ DISALLOW_COPY_AND_ASSIGN(TaskQueueSelector);
+};
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_SELECTOR_H_
diff --git a/base/task/sequence_manager/task_queue_selector_logic.h b/base/task/sequence_manager/task_queue_selector_logic.h
new file mode 100644
index 0000000000..8cf8933783
--- /dev/null
+++ b/base/task/sequence_manager/task_queue_selector_logic.h
@@ -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.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_SELECTOR_LOGIC_H_
+#define BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_SELECTOR_LOGIC_H_
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+// Used to describe the logic trigerred when a task queue is selected to
+// service.
+// This enum is used for histograms and should not be renumbered.
+enum class TaskQueueSelectorLogic {
+
+ // Selected due to priority rules.
+ kControlPriorityLogic = 0,
+ kHighestPriorityLogic = 1,
+ kHighPriorityLogic = 2,
+ kNormalPriorityLogic = 3,
+ kLowPriorityLogic = 4,
+ kBestEffortPriorityLogic = 5,
+
+ // Selected due to starvation logic.
+ kHighPriorityStarvationLogic = 6,
+ kNormalPriorityStarvationLogic = 7,
+ kLowPriorityStarvationLogic = 8,
+
+ kCount = 9,
+};
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_TASK_QUEUE_SELECTOR_LOGIC_H_
diff --git a/base/task/sequence_manager/task_queue_selector_unittest.cc b/base/task/sequence_manager/task_queue_selector_unittest.cc
new file mode 100644
index 0000000000..c3742a2b2e
--- /dev/null
+++ b/base/task/sequence_manager/task_queue_selector_unittest.cc
@@ -0,0 +1,885 @@
+// 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/task/sequence_manager/task_queue_selector.h"
+
+#include <stddef.h>
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/pending_task.h"
+#include "base/task/sequence_manager/task_queue_impl.h"
+#include "base/task/sequence_manager/test/mock_time_domain.h"
+#include "base/task/sequence_manager/work_queue.h"
+#include "base/task/sequence_manager/work_queue_sets.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+// To avoid symbol collisions in jumbo builds.
+namespace task_queue_selector_unittest {
+
+class MockObserver : public TaskQueueSelector::Observer {
+ public:
+ MockObserver() = default;
+ ~MockObserver() override = default;
+
+ MOCK_METHOD1(OnTaskQueueEnabled, void(internal::TaskQueueImpl*));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockObserver);
+};
+
+class TaskQueueSelectorForTest : public TaskQueueSelector {
+ public:
+ using TaskQueueSelector::prioritizing_selector_for_test;
+ using TaskQueueSelector::PrioritizingSelector;
+ using TaskQueueSelector::SetImmediateStarvationCountForTest;
+
+ // Returns the number of highest priority tasks needed to starve high priority
+ // task.
+ static constexpr size_t NumberOfHighestPriorityToStarveHighPriority() {
+ return (kMaxHighPriorityStarvationScore +
+ kSmallScoreIncrementForHighPriorityStarvation - 1) /
+ kSmallScoreIncrementForHighPriorityStarvation;
+ }
+
+ // Returns the number of highest priority tasks needed to starve normal
+ // priority tasks.
+ static constexpr size_t NumberOfHighestPriorityToStarveNormalPriority() {
+ return (kMaxNormalPriorityStarvationScore +
+ kSmallScoreIncrementForNormalPriorityStarvation - 1) /
+ kSmallScoreIncrementForNormalPriorityStarvation;
+ }
+
+ // Returns the number of high priority tasks needed to starve normal priority
+ // tasks.
+ static constexpr size_t NumberOfHighPriorityToStarveNormalPriority() {
+ return (kMaxNormalPriorityStarvationScore +
+ kLargeScoreIncrementForNormalPriorityStarvation - 1) /
+ kLargeScoreIncrementForNormalPriorityStarvation;
+ }
+
+ // Returns the number of highest priority tasks needed to starve low priority
+ // ones.
+ static constexpr size_t NumberOfHighestPriorityToStarveLowPriority() {
+ return (kMaxLowPriorityStarvationScore +
+ kSmallScoreIncrementForLowPriorityStarvation - 1) /
+ kSmallScoreIncrementForLowPriorityStarvation;
+ }
+
+ // Returns the number of high/normal priority tasks needed to starve low
+ // priority ones.
+ static constexpr size_t NumberOfHighAndNormalPriorityToStarveLowPriority() {
+ return (kMaxLowPriorityStarvationScore +
+ kLargeScoreIncrementForLowPriorityStarvation - 1) /
+ kLargeScoreIncrementForLowPriorityStarvation;
+ }
+};
+
+class TaskQueueSelectorTest : public testing::Test {
+ public:
+ TaskQueueSelectorTest()
+ : test_closure_(BindRepeating(&TaskQueueSelectorTest::TestFunction)) {}
+ ~TaskQueueSelectorTest() override = default;
+
+ TaskQueueSelectorForTest::PrioritizingSelector* prioritizing_selector() {
+ return selector_.prioritizing_selector_for_test();
+ }
+
+ WorkQueueSets* delayed_work_queue_sets() {
+ return prioritizing_selector()->delayed_work_queue_sets();
+ }
+ WorkQueueSets* immediate_work_queue_sets() {
+ return prioritizing_selector()->immediate_work_queue_sets();
+ }
+
+ void PushTasks(const size_t queue_indices[], size_t num_tasks) {
+ std::set<size_t> changed_queue_set;
+ EnqueueOrder::Generator enqueue_order_generator;
+ for (size_t i = 0; i < num_tasks; i++) {
+ changed_queue_set.insert(queue_indices[i]);
+ task_queues_[queue_indices[i]]->immediate_work_queue()->Push(
+ TaskQueueImpl::Task(TaskQueue::PostedTask(test_closure_, FROM_HERE),
+ TimeTicks(), EnqueueOrder(),
+ enqueue_order_generator.GenerateNext()));
+ }
+ }
+
+ void PushTasksWithEnqueueOrder(const size_t queue_indices[],
+ const size_t enqueue_orders[],
+ size_t num_tasks) {
+ std::set<size_t> changed_queue_set;
+ for (size_t i = 0; i < num_tasks; i++) {
+ changed_queue_set.insert(queue_indices[i]);
+ task_queues_[queue_indices[i]]->immediate_work_queue()->Push(
+ TaskQueueImpl::Task(
+ TaskQueue::PostedTask(test_closure_, FROM_HERE), TimeTicks(),
+ EnqueueOrder(),
+ EnqueueOrder::FromIntForTesting(enqueue_orders[i])));
+ }
+ }
+
+ std::vector<size_t> PopTasks() {
+ std::vector<size_t> order;
+ WorkQueue* chosen_work_queue;
+ while (selector_.SelectWorkQueueToService(&chosen_work_queue)) {
+ size_t chosen_queue_index =
+ queue_to_index_map_.find(chosen_work_queue->task_queue())->second;
+ order.push_back(chosen_queue_index);
+ chosen_work_queue->PopTaskForTesting();
+ immediate_work_queue_sets()->OnPopQueue(chosen_work_queue);
+ }
+ return order;
+ }
+
+ static void TestFunction() {}
+
+ protected:
+ void SetUp() final {
+ time_domain_ = std::make_unique<MockTimeDomain>(TimeTicks() +
+ TimeDelta::FromSeconds(1));
+ for (size_t i = 0; i < kTaskQueueCount; i++) {
+ std::unique_ptr<TaskQueueImpl> task_queue =
+ std::make_unique<TaskQueueImpl>(nullptr, time_domain_.get(),
+ TaskQueue::Spec("test"));
+ selector_.AddQueue(task_queue.get());
+ task_queues_.push_back(std::move(task_queue));
+ }
+ for (size_t i = 0; i < kTaskQueueCount; i++) {
+ EXPECT_EQ(TaskQueue::kNormalPriority, task_queues_[i]->GetQueuePriority())
+ << i;
+ queue_to_index_map_.insert(std::make_pair(task_queues_[i].get(), i));
+ }
+ histogram_tester_.reset(new HistogramTester());
+ }
+
+ void TearDown() final {
+ for (std::unique_ptr<TaskQueueImpl>& task_queue : task_queues_) {
+ // Note since this test doesn't have a SequenceManager we need to
+ // manually remove |task_queue| from the |selector_|. Normally
+ // UnregisterTaskQueue would do that.
+ selector_.RemoveQueue(task_queue.get());
+ task_queue->UnregisterTaskQueue();
+ }
+ }
+
+ std::unique_ptr<TaskQueueImpl> NewTaskQueueWithBlockReporting() {
+ return std::make_unique<TaskQueueImpl>(nullptr, time_domain_.get(),
+ TaskQueue::Spec("test"));
+ }
+
+ const size_t kTaskQueueCount = 5;
+ RepeatingClosure test_closure_;
+ TaskQueueSelectorForTest selector_;
+ std::unique_ptr<TimeDomain> time_domain_;
+ std::vector<std::unique_ptr<TaskQueueImpl>> task_queues_;
+ std::map<TaskQueueImpl*, size_t> queue_to_index_map_;
+ std::unique_ptr<HistogramTester> histogram_tester_;
+};
+
+TEST_F(TaskQueueSelectorTest, TestDefaultPriority) {
+ size_t queue_order[] = {4, 3, 2, 1, 0};
+ PushTasks(queue_order, 5);
+ EXPECT_THAT(PopTasks(), testing::ElementsAre(4, 3, 2, 1, 0));
+ EXPECT_EQ(histogram_tester_->GetBucketCount(
+ "TaskQueueSelector.TaskServicedPerSelectorLogic",
+ static_cast<int>(TaskQueueSelectorLogic::kNormalPriorityLogic)),
+ 5);
+}
+
+TEST_F(TaskQueueSelectorTest, TestHighestPriority) {
+ size_t queue_order[] = {0, 1, 2, 3, 4};
+ PushTasks(queue_order, 5);
+ selector_.SetQueuePriority(task_queues_[2].get(),
+ TaskQueue::kHighestPriority);
+ EXPECT_THAT(PopTasks(), ::testing::ElementsAre(2, 0, 1, 3, 4));
+ EXPECT_EQ(
+ histogram_tester_->GetBucketCount(
+ "TaskQueueSelector.TaskServicedPerSelectorLogic",
+ static_cast<int>(TaskQueueSelectorLogic::kHighestPriorityLogic)),
+ 1);
+}
+
+TEST_F(TaskQueueSelectorTest, TestHighPriority) {
+ size_t queue_order[] = {0, 1, 2, 3, 4};
+ PushTasks(queue_order, 5);
+ selector_.SetQueuePriority(task_queues_[2].get(),
+ TaskQueue::kHighestPriority);
+ selector_.SetQueuePriority(task_queues_[1].get(), TaskQueue::kHighPriority);
+ selector_.SetQueuePriority(task_queues_[0].get(), TaskQueue::kLowPriority);
+ EXPECT_THAT(PopTasks(), ::testing::ElementsAre(2, 1, 3, 4, 0));
+ EXPECT_EQ(histogram_tester_->GetBucketCount(
+ "TaskQueueSelector.TaskServicedPerSelectorLogic",
+ static_cast<int>(TaskQueueSelectorLogic::kHighPriorityLogic)),
+ 1);
+}
+
+TEST_F(TaskQueueSelectorTest, TestLowPriority) {
+ size_t queue_order[] = {0, 1, 2, 3, 4};
+ PushTasks(queue_order, 5);
+ selector_.SetQueuePriority(task_queues_[2].get(), TaskQueue::kLowPriority);
+ EXPECT_THAT(PopTasks(), testing::ElementsAre(0, 1, 3, 4, 2));
+ EXPECT_EQ(histogram_tester_->GetBucketCount(
+ "TaskQueueSelector.TaskServicedPerSelectorLogic",
+ static_cast<int>(TaskQueueSelectorLogic::kLowPriorityLogic)),
+ 1);
+}
+
+TEST_F(TaskQueueSelectorTest, TestBestEffortPriority) {
+ size_t queue_order[] = {0, 1, 2, 3, 4};
+ PushTasks(queue_order, 5);
+ selector_.SetQueuePriority(task_queues_[0].get(),
+ TaskQueue::kBestEffortPriority);
+ selector_.SetQueuePriority(task_queues_[2].get(), TaskQueue::kLowPriority);
+ selector_.SetQueuePriority(task_queues_[3].get(),
+ TaskQueue::kHighestPriority);
+ EXPECT_THAT(PopTasks(), ::testing::ElementsAre(3, 1, 4, 2, 0));
+ EXPECT_EQ(
+ histogram_tester_->GetBucketCount(
+ "TaskQueueSelector.TaskServicedPerSelectorLogic",
+ static_cast<int>(TaskQueueSelectorLogic::kBestEffortPriorityLogic)),
+ 1);
+}
+
+TEST_F(TaskQueueSelectorTest, TestControlPriority) {
+ size_t queue_order[] = {0, 1, 2, 3, 4};
+ PushTasks(queue_order, 5);
+ selector_.SetQueuePriority(task_queues_[4].get(),
+ TaskQueue::kControlPriority);
+ EXPECT_EQ(TaskQueue::kControlPriority, task_queues_[4]->GetQueuePriority());
+ selector_.SetQueuePriority(task_queues_[2].get(),
+ TaskQueue::kHighestPriority);
+ EXPECT_EQ(TaskQueue::kHighestPriority, task_queues_[2]->GetQueuePriority());
+ EXPECT_THAT(PopTasks(), ::testing::ElementsAre(4, 2, 0, 1, 3));
+ EXPECT_EQ(
+ histogram_tester_->GetBucketCount(
+ "TaskQueueSelector.TaskServicedPerSelectorLogic",
+ static_cast<int>(TaskQueueSelectorLogic::kControlPriorityLogic)),
+ 1);
+}
+
+TEST_F(TaskQueueSelectorTest, TestObserverWithEnabledQueue) {
+ task_queues_[1]->SetQueueEnabledForTest(false);
+ selector_.DisableQueue(task_queues_[1].get());
+ MockObserver mock_observer;
+ selector_.SetTaskQueueSelectorObserver(&mock_observer);
+ EXPECT_CALL(mock_observer, OnTaskQueueEnabled(_)).Times(1);
+ task_queues_[1]->SetQueueEnabledForTest(true);
+ selector_.EnableQueue(task_queues_[1].get());
+}
+
+TEST_F(TaskQueueSelectorTest,
+ TestObserverWithSetQueuePriorityAndQueueAlreadyEnabled) {
+ selector_.SetQueuePriority(task_queues_[1].get(),
+ TaskQueue::kHighestPriority);
+ MockObserver mock_observer;
+ selector_.SetTaskQueueSelectorObserver(&mock_observer);
+ EXPECT_CALL(mock_observer, OnTaskQueueEnabled(_)).Times(0);
+ selector_.SetQueuePriority(task_queues_[1].get(), TaskQueue::kNormalPriority);
+}
+
+TEST_F(TaskQueueSelectorTest, TestDisableEnable) {
+ MockObserver mock_observer;
+ selector_.SetTaskQueueSelectorObserver(&mock_observer);
+
+ size_t queue_order[] = {0, 1, 2, 3, 4};
+ PushTasks(queue_order, 5);
+ task_queues_[2]->SetQueueEnabledForTest(false);
+ selector_.DisableQueue(task_queues_[2].get());
+ task_queues_[4]->SetQueueEnabledForTest(false);
+ selector_.DisableQueue(task_queues_[4].get());
+ // Disabling a queue should not affect its priority.
+ EXPECT_EQ(TaskQueue::kNormalPriority, task_queues_[2]->GetQueuePriority());
+ EXPECT_EQ(TaskQueue::kNormalPriority, task_queues_[4]->GetQueuePriority());
+ EXPECT_THAT(PopTasks(), testing::ElementsAre(0, 1, 3));
+
+ EXPECT_CALL(mock_observer, OnTaskQueueEnabled(_)).Times(2);
+ task_queues_[2]->SetQueueEnabledForTest(true);
+ selector_.EnableQueue(task_queues_[2].get());
+ selector_.SetQueuePriority(task_queues_[2].get(),
+ TaskQueue::kBestEffortPriority);
+ EXPECT_THAT(PopTasks(), testing::ElementsAre(2));
+ task_queues_[4]->SetQueueEnabledForTest(true);
+ selector_.EnableQueue(task_queues_[4].get());
+ EXPECT_THAT(PopTasks(), testing::ElementsAre(4));
+}
+
+TEST_F(TaskQueueSelectorTest, TestDisableChangePriorityThenEnable) {
+ EXPECT_TRUE(task_queues_[2]->delayed_work_queue()->Empty());
+ EXPECT_TRUE(task_queues_[2]->immediate_work_queue()->Empty());
+
+ task_queues_[2]->SetQueueEnabledForTest(false);
+ selector_.SetQueuePriority(task_queues_[2].get(),
+ TaskQueue::kHighestPriority);
+
+ size_t queue_order[] = {0, 1, 2, 3, 4};
+ PushTasks(queue_order, 5);
+
+ EXPECT_TRUE(task_queues_[2]->delayed_work_queue()->Empty());
+ EXPECT_FALSE(task_queues_[2]->immediate_work_queue()->Empty());
+ task_queues_[2]->SetQueueEnabledForTest(true);
+
+ EXPECT_EQ(TaskQueue::kHighestPriority, task_queues_[2]->GetQueuePriority());
+ EXPECT_THAT(PopTasks(), ::testing::ElementsAre(2, 0, 1, 3, 4));
+}
+
+TEST_F(TaskQueueSelectorTest, TestEmptyQueues) {
+ WorkQueue* chosen_work_queue = nullptr;
+ EXPECT_FALSE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+
+ // Test only disabled queues.
+ size_t queue_order[] = {0};
+ PushTasks(queue_order, 1);
+ task_queues_[0]->SetQueueEnabledForTest(false);
+ selector_.DisableQueue(task_queues_[0].get());
+ EXPECT_FALSE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+
+ // These tests are unusual since there's no TQM. To avoid a later DCHECK when
+ // deleting the task queue, we re-enable the queue here so the selector
+ // doesn't get out of sync.
+ task_queues_[0]->SetQueueEnabledForTest(true);
+ selector_.EnableQueue(task_queues_[0].get());
+}
+
+TEST_F(TaskQueueSelectorTest, TestAge) {
+ size_t enqueue_order[] = {10, 1, 2, 9, 4};
+ size_t queue_order[] = {0, 1, 2, 3, 4};
+ PushTasksWithEnqueueOrder(queue_order, enqueue_order, 5);
+ EXPECT_THAT(PopTasks(), testing::ElementsAre(1, 2, 4, 3, 0));
+}
+
+TEST_F(TaskQueueSelectorTest, TestControlStarvesOthers) {
+ size_t queue_order[] = {0, 1, 2, 3};
+ PushTasks(queue_order, 4);
+ selector_.SetQueuePriority(task_queues_[3].get(),
+ TaskQueue::kControlPriority);
+ selector_.SetQueuePriority(task_queues_[2].get(),
+ TaskQueue::kHighestPriority);
+ selector_.SetQueuePriority(task_queues_[1].get(),
+ TaskQueue::kBestEffortPriority);
+ for (int i = 0; i < 100; i++) {
+ WorkQueue* chosen_work_queue = nullptr;
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+ EXPECT_EQ(task_queues_[3].get(), chosen_work_queue->task_queue());
+ // Don't remove task from queue to simulate all queues still being full.
+ }
+}
+
+TEST_F(TaskQueueSelectorTest, TestHighestPriorityDoesNotStarveHigh) {
+ size_t queue_order[] = {0, 1};
+ PushTasks(queue_order, 2);
+ selector_.SetQueuePriority(task_queues_[0].get(),
+ TaskQueue::kHighestPriority);
+ selector_.SetQueuePriority(task_queues_[1].get(), TaskQueue::kHighPriority);
+
+ size_t counts[] = {0, 0};
+ for (int i = 0; i < 100; i++) {
+ WorkQueue* chosen_work_queue = nullptr;
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+ size_t chosen_queue_index =
+ queue_to_index_map_.find(chosen_work_queue->task_queue())->second;
+ counts[chosen_queue_index]++;
+ // Don't remove task from queue to simulate all queues still being full.
+ }
+ EXPECT_GT(counts[1], 0ul); // Check highest doesn't starve high.
+ EXPECT_GT(counts[0], counts[1]); // Check highest gets more chance to run.
+}
+
+TEST_F(TaskQueueSelectorTest, TestHighestPriorityDoesNotStarveHighOrNormal) {
+ size_t queue_order[] = {0, 1, 2};
+ PushTasks(queue_order, 3);
+ selector_.SetQueuePriority(task_queues_[0].get(),
+ TaskQueue::kHighestPriority);
+ selector_.SetQueuePriority(task_queues_[1].get(), TaskQueue::kHighPriority);
+
+ size_t counts[] = {0, 0, 0};
+ for (int i = 0; i < 100; i++) {
+ WorkQueue* chosen_work_queue = nullptr;
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+ size_t chosen_queue_index =
+ queue_to_index_map_.find(chosen_work_queue->task_queue())->second;
+ counts[chosen_queue_index]++;
+ // Don't remove task from queue to simulate all queues still being full.
+ }
+
+ // Check highest runs more frequently then high.
+ EXPECT_GT(counts[0], counts[1]);
+
+ // Check high runs at least as frequently as normal.
+ EXPECT_GE(counts[1], counts[2]);
+
+ // Check normal isn't starved.
+ EXPECT_GT(counts[2], 0ul);
+}
+
+TEST_F(TaskQueueSelectorTest,
+ TestHighestPriorityDoesNotStarveHighOrNormalOrLow) {
+ size_t queue_order[] = {0, 1, 2, 3};
+ PushTasks(queue_order, 4);
+ selector_.SetQueuePriority(task_queues_[0].get(),
+ TaskQueue::kHighestPriority);
+ selector_.SetQueuePriority(task_queues_[1].get(), TaskQueue::kHighPriority);
+ selector_.SetQueuePriority(task_queues_[3].get(), TaskQueue::kLowPriority);
+
+ size_t counts[] = {0, 0, 0, 0};
+ for (int i = 0; i < 100; i++) {
+ WorkQueue* chosen_work_queue = nullptr;
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+ size_t chosen_queue_index =
+ queue_to_index_map_.find(chosen_work_queue->task_queue())->second;
+ counts[chosen_queue_index]++;
+ // Don't remove task from queue to simulate all queues still being full.
+ }
+
+ // Check highest runs more frequently then high.
+ EXPECT_GT(counts[0], counts[1]);
+
+ // Check high runs at least as frequently as normal.
+ EXPECT_GE(counts[1], counts[2]);
+
+ // Check normal runs more frequently than low.
+ EXPECT_GT(counts[2], counts[3]);
+
+ // Check low isn't starved.
+ EXPECT_GT(counts[3], 0ul);
+}
+
+TEST_F(TaskQueueSelectorTest, TestHighPriorityDoesNotStarveNormal) {
+ size_t queue_order[] = {0, 1};
+ PushTasks(queue_order, 2);
+
+ selector_.SetQueuePriority(task_queues_[0].get(), TaskQueue::kHighPriority);
+
+ size_t counts[] = {0, 0, 0, 0};
+ for (int i = 0; i < 100; i++) {
+ WorkQueue* chosen_work_queue = nullptr;
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+ size_t chosen_queue_index =
+ queue_to_index_map_.find(chosen_work_queue->task_queue())->second;
+ counts[chosen_queue_index]++;
+ // Don't remove task from queue to simulate all queues still being full.
+ }
+
+ // Check high runs more frequently then normal.
+ EXPECT_GT(counts[0], counts[1]);
+
+ // Check low isn't starved.
+ EXPECT_GT(counts[1], 0ul);
+}
+
+TEST_F(TaskQueueSelectorTest, TestHighPriorityDoesNotStarveNormalOrLow) {
+ size_t queue_order[] = {0, 1, 2};
+ PushTasks(queue_order, 3);
+ selector_.SetQueuePriority(task_queues_[0].get(), TaskQueue::kHighPriority);
+ selector_.SetQueuePriority(task_queues_[2].get(), TaskQueue::kLowPriority);
+
+ size_t counts[] = {0, 0, 0};
+ for (int i = 0; i < 100; i++) {
+ WorkQueue* chosen_work_queue = nullptr;
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+ size_t chosen_queue_index =
+ queue_to_index_map_.find(chosen_work_queue->task_queue())->second;
+ counts[chosen_queue_index]++;
+ // Don't remove task from queue to simulate all queues still being full.
+ }
+
+ // Check high runs more frequently than normal.
+ EXPECT_GT(counts[0], counts[1]);
+
+ // Check normal runs more frequently than low.
+ EXPECT_GT(counts[1], counts[2]);
+
+ // Check low isn't starved.
+ EXPECT_GT(counts[2], 0ul);
+}
+
+TEST_F(TaskQueueSelectorTest, TestNormalPriorityDoesNotStarveLow) {
+ size_t queue_order[] = {0, 1, 2};
+ PushTasks(queue_order, 3);
+ selector_.SetQueuePriority(task_queues_[0].get(), TaskQueue::kLowPriority);
+ selector_.SetQueuePriority(task_queues_[1].get(),
+ TaskQueue::kBestEffortPriority);
+ size_t counts[] = {0, 0, 0};
+ for (int i = 0; i < 100; i++) {
+ WorkQueue* chosen_work_queue = nullptr;
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+ size_t chosen_queue_index =
+ queue_to_index_map_.find(chosen_work_queue->task_queue())->second;
+ counts[chosen_queue_index]++;
+ // Don't remove task from queue to simulate all queues still being full.
+ }
+ EXPECT_GT(counts[0], 0ul); // Check normal doesn't starve low.
+ EXPECT_GT(counts[2], counts[0]); // Check normal gets more chance to run.
+ EXPECT_EQ(0ul, counts[1]); // Check best effort is starved.
+}
+
+TEST_F(TaskQueueSelectorTest, TestBestEffortGetsStarved) {
+ size_t queue_order[] = {0, 1};
+ PushTasks(queue_order, 2);
+ selector_.SetQueuePriority(task_queues_[0].get(),
+ TaskQueue::kBestEffortPriority);
+ EXPECT_EQ(TaskQueue::kNormalPriority, task_queues_[1]->GetQueuePriority());
+
+ // Check that normal priority tasks starve best effort.
+ WorkQueue* chosen_work_queue = nullptr;
+ for (int i = 0; i < 100; i++) {
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+ EXPECT_EQ(task_queues_[1].get(), chosen_work_queue->task_queue());
+ // Don't remove task from queue to simulate all queues still being full.
+ }
+
+ // Check that highest priority tasks starve best effort.
+ selector_.SetQueuePriority(task_queues_[1].get(),
+ TaskQueue::kHighestPriority);
+ for (int i = 0; i < 100; i++) {
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+ EXPECT_EQ(task_queues_[1].get(), chosen_work_queue->task_queue());
+ // Don't remove task from queue to simulate all queues still being full.
+ }
+
+ // Check that high priority tasks starve best effort.
+ selector_.SetQueuePriority(task_queues_[1].get(), TaskQueue::kHighPriority);
+ for (int i = 0; i < 100; i++) {
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+ EXPECT_EQ(task_queues_[1].get(), chosen_work_queue->task_queue());
+ // Don't remove task from queue to simulate all queues still being full.
+ }
+
+ // Check that low priority tasks starve best effort.
+ selector_.SetQueuePriority(task_queues_[1].get(), TaskQueue::kLowPriority);
+ for (int i = 0; i < 100; i++) {
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+ EXPECT_EQ(task_queues_[1].get(), chosen_work_queue->task_queue());
+ // Don't remove task from queue to simulate all queues still being full.
+ }
+
+ // Check that control priority tasks starve best effort.
+ selector_.SetQueuePriority(task_queues_[1].get(),
+ TaskQueue::kControlPriority);
+ for (int i = 0; i < 100; i++) {
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+ EXPECT_EQ(task_queues_[1].get(), chosen_work_queue->task_queue());
+ // Don't remove task from queue to simulate all queues still being full.
+ }
+}
+
+TEST_F(TaskQueueSelectorTest,
+ TestHighPriorityStarvationScoreIncreasedOnlyWhenTasksArePresent) {
+ size_t queue_order[] = {0, 1};
+ PushTasks(queue_order, 2);
+ selector_.SetQueuePriority(task_queues_[0].get(),
+ TaskQueue::kHighestPriority);
+ selector_.SetQueuePriority(task_queues_[1].get(),
+ TaskQueue::kHighestPriority);
+
+ // Run a number of highest priority tasks needed to starve high priority
+ // tasks (when present).
+ for (size_t num_tasks = 0;
+ num_tasks <=
+ TaskQueueSelectorForTest::NumberOfHighestPriorityToStarveHighPriority();
+ num_tasks++) {
+ WorkQueue* chosen_work_queue = nullptr;
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+ // Don't remove task from queue to simulate the queue is still full.
+ }
+
+ // Post a high priority task.
+ selector_.SetQueuePriority(task_queues_[1].get(), TaskQueue::kHighPriority);
+ WorkQueue* chosen_work_queue = nullptr;
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+
+ // Check that the high priority task is not considered starved, and thus isn't
+ // processed.
+ EXPECT_NE(
+ static_cast<int>(
+ queue_to_index_map_.find(chosen_work_queue->task_queue())->second),
+ 1);
+}
+
+TEST_F(TaskQueueSelectorTest,
+ TestNormalPriorityStarvationScoreIncreasedOnllWhenTasksArePresent) {
+ size_t queue_order[] = {0, 1};
+ PushTasks(queue_order, 2);
+ selector_.SetQueuePriority(task_queues_[0].get(),
+ TaskQueue::kHighestPriority);
+ selector_.SetQueuePriority(task_queues_[1].get(),
+ TaskQueue::kHighestPriority);
+
+ // Run a number of highest priority tasks needed to starve normal priority
+ // tasks (when present).
+ for (size_t num_tasks = 0;
+ num_tasks <= TaskQueueSelectorForTest::
+ NumberOfHighestPriorityToStarveNormalPriority();
+ num_tasks++) {
+ WorkQueue* chosen_work_queue = nullptr;
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+ // Don't remove task from queue to simulate the queue is still full.
+ }
+
+ selector_.SetQueuePriority(task_queues_[0].get(), TaskQueue::kHighPriority);
+ selector_.SetQueuePriority(task_queues_[1].get(), TaskQueue::kHighPriority);
+
+ // Run a number of high priority tasks needed to starve normal priority
+ // tasks (when present).
+ for (size_t num_tasks = 0;
+ num_tasks <=
+ TaskQueueSelectorForTest::NumberOfHighPriorityToStarveNormalPriority();
+ num_tasks++) {
+ WorkQueue* chosen_work_queue = nullptr;
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+ // Don't remove task from queue to simulate the queue is still full.
+ }
+
+ // Post a normal priority task.
+ selector_.SetQueuePriority(task_queues_[1].get(), TaskQueue::kNormalPriority);
+ WorkQueue* chosen_work_queue = nullptr;
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+
+ // Check that the normal priority task is not considered starved, and thus
+ // isn't processed.
+ EXPECT_NE(
+ static_cast<int>(
+ queue_to_index_map_.find(chosen_work_queue->task_queue())->second),
+ 1);
+}
+
+TEST_F(TaskQueueSelectorTest,
+ TestLowPriorityTaskStarvationOnlyIncreasedWhenTasksArePresent) {
+ size_t queue_order[] = {0, 1};
+ PushTasks(queue_order, 2);
+ selector_.SetQueuePriority(task_queues_[0].get(),
+ TaskQueue::kHighestPriority);
+ selector_.SetQueuePriority(task_queues_[1].get(),
+ TaskQueue::kHighestPriority);
+
+ // Run a number of highest priority tasks needed to starve low priority
+ // tasks (when present).
+ for (size_t num_tasks = 0;
+ num_tasks <=
+ TaskQueueSelectorForTest::NumberOfHighestPriorityToStarveLowPriority();
+ num_tasks++) {
+ WorkQueue* chosen_work_queue = nullptr;
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+ // Don't remove task from queue to simulate the queue is still full.
+ }
+
+ selector_.SetQueuePriority(task_queues_[0].get(), TaskQueue::kHighPriority);
+ selector_.SetQueuePriority(task_queues_[1].get(), TaskQueue::kNormalPriority);
+
+ // Run a number of high/normal priority tasks needed to starve low priority
+ // tasks (when present).
+ for (size_t num_tasks = 0;
+ num_tasks <= TaskQueueSelectorForTest::
+ NumberOfHighAndNormalPriorityToStarveLowPriority();
+ num_tasks++) {
+ WorkQueue* chosen_work_queue = nullptr;
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+ // Don't remove task from queue to simulate the queue is still full.
+ }
+
+ // Post a low priority task.
+ selector_.SetQueuePriority(task_queues_[1].get(), TaskQueue::kLowPriority);
+ WorkQueue* chosen_work_queue = nullptr;
+ ASSERT_TRUE(selector_.SelectWorkQueueToService(&chosen_work_queue));
+
+ // Check that the low priority task is not considered starved, and thus
+ // isn't processed.
+ EXPECT_NE(
+ static_cast<int>(
+ queue_to_index_map_.find(chosen_work_queue->task_queue())->second),
+ 1);
+}
+
+TEST_F(TaskQueueSelectorTest, AllEnabledWorkQueuesAreEmpty) {
+ EXPECT_TRUE(selector_.AllEnabledWorkQueuesAreEmpty());
+ size_t queue_order[] = {0, 1};
+ PushTasks(queue_order, 2);
+
+ EXPECT_FALSE(selector_.AllEnabledWorkQueuesAreEmpty());
+ PopTasks();
+ EXPECT_TRUE(selector_.AllEnabledWorkQueuesAreEmpty());
+}
+
+TEST_F(TaskQueueSelectorTest, AllEnabledWorkQueuesAreEmpty_ControlPriority) {
+ size_t queue_order[] = {0};
+ PushTasks(queue_order, 1);
+
+ selector_.SetQueuePriority(task_queues_[0].get(),
+ TaskQueue::kControlPriority);
+
+ EXPECT_FALSE(selector_.AllEnabledWorkQueuesAreEmpty());
+}
+
+TEST_F(TaskQueueSelectorTest, ChooseOldestWithPriority_Empty) {
+ WorkQueue* chosen_work_queue = nullptr;
+ bool chose_delayed_over_immediate = false;
+ EXPECT_FALSE(prioritizing_selector()->ChooseOldestWithPriority(
+ TaskQueue::kNormalPriority, &chose_delayed_over_immediate,
+ &chosen_work_queue));
+ EXPECT_FALSE(chose_delayed_over_immediate);
+}
+
+TEST_F(TaskQueueSelectorTest, ChooseOldestWithPriority_OnlyDelayed) {
+ task_queues_[0]->delayed_work_queue()->Push(TaskQueueImpl::Task(
+ TaskQueue::PostedTask(test_closure_, FROM_HERE), TimeTicks(),
+ EnqueueOrder(), EnqueueOrder::FromIntForTesting(2)));
+
+ WorkQueue* chosen_work_queue = nullptr;
+ bool chose_delayed_over_immediate = false;
+ EXPECT_TRUE(prioritizing_selector()->ChooseOldestWithPriority(
+ TaskQueue::kNormalPriority, &chose_delayed_over_immediate,
+ &chosen_work_queue));
+ EXPECT_EQ(chosen_work_queue, task_queues_[0]->delayed_work_queue());
+ EXPECT_FALSE(chose_delayed_over_immediate);
+}
+
+TEST_F(TaskQueueSelectorTest, ChooseOldestWithPriority_OnlyImmediate) {
+ task_queues_[0]->immediate_work_queue()->Push(TaskQueueImpl::Task(
+ TaskQueue::PostedTask(test_closure_, FROM_HERE), TimeTicks(),
+ EnqueueOrder(), EnqueueOrder::FromIntForTesting(2)));
+
+ WorkQueue* chosen_work_queue = nullptr;
+ bool chose_delayed_over_immediate = false;
+ EXPECT_TRUE(prioritizing_selector()->ChooseOldestWithPriority(
+ TaskQueue::kNormalPriority, &chose_delayed_over_immediate,
+ &chosen_work_queue));
+ EXPECT_EQ(chosen_work_queue, task_queues_[0]->immediate_work_queue());
+ EXPECT_FALSE(chose_delayed_over_immediate);
+}
+
+TEST_F(TaskQueueSelectorTest, TestObserverWithOneBlockedQueue) {
+ TaskQueueSelectorForTest selector;
+ MockObserver mock_observer;
+ selector.SetTaskQueueSelectorObserver(&mock_observer);
+
+ EXPECT_CALL(mock_observer, OnTaskQueueEnabled(_)).Times(1);
+
+ std::unique_ptr<TaskQueueImpl> task_queue(NewTaskQueueWithBlockReporting());
+ selector.AddQueue(task_queue.get());
+
+ task_queue->SetQueueEnabledForTest(false);
+ selector.DisableQueue(task_queue.get());
+
+ TaskQueueImpl::Task task(TaskQueue::PostedTask(test_closure_, FROM_HERE),
+ TimeTicks(), EnqueueOrder(),
+ EnqueueOrder::FromIntForTesting(2));
+ task_queue->immediate_work_queue()->Push(std::move(task));
+
+ WorkQueue* chosen_work_queue;
+ EXPECT_FALSE(selector.SelectWorkQueueToService(&chosen_work_queue));
+
+ task_queue->SetQueueEnabledForTest(true);
+ selector.EnableQueue(task_queue.get());
+ selector.RemoveQueue(task_queue.get());
+ task_queue->UnregisterTaskQueue();
+}
+
+TEST_F(TaskQueueSelectorTest, TestObserverWithTwoBlockedQueues) {
+ TaskQueueSelectorForTest selector;
+ MockObserver mock_observer;
+ selector.SetTaskQueueSelectorObserver(&mock_observer);
+
+ std::unique_ptr<TaskQueueImpl> task_queue(NewTaskQueueWithBlockReporting());
+ std::unique_ptr<TaskQueueImpl> task_queue2(NewTaskQueueWithBlockReporting());
+ selector.AddQueue(task_queue.get());
+ selector.AddQueue(task_queue2.get());
+
+ task_queue->SetQueueEnabledForTest(false);
+ task_queue2->SetQueueEnabledForTest(false);
+ selector.DisableQueue(task_queue.get());
+ selector.DisableQueue(task_queue2.get());
+
+ selector.SetQueuePriority(task_queue2.get(), TaskQueue::kControlPriority);
+
+ TaskQueueImpl::Task task1(TaskQueue::PostedTask(test_closure_, FROM_HERE),
+ TimeTicks(), EnqueueOrder::FromIntForTesting(2),
+ EnqueueOrder::FromIntForTesting(2));
+ TaskQueueImpl::Task task2(TaskQueue::PostedTask(test_closure_, FROM_HERE),
+ TimeTicks(), EnqueueOrder::FromIntForTesting(3),
+ EnqueueOrder::FromIntForTesting(3));
+ task_queue->immediate_work_queue()->Push(std::move(task1));
+ task_queue2->immediate_work_queue()->Push(std::move(task2));
+
+ WorkQueue* chosen_work_queue;
+ EXPECT_FALSE(selector.SelectWorkQueueToService(&chosen_work_queue));
+ testing::Mock::VerifyAndClearExpectations(&mock_observer);
+
+ EXPECT_CALL(mock_observer, OnTaskQueueEnabled(_)).Times(2);
+
+ task_queue->SetQueueEnabledForTest(true);
+ selector.EnableQueue(task_queue.get());
+
+ selector.RemoveQueue(task_queue.get());
+ task_queue->UnregisterTaskQueue();
+ EXPECT_FALSE(selector.SelectWorkQueueToService(&chosen_work_queue));
+
+ task_queue2->SetQueueEnabledForTest(true);
+ selector.EnableQueue(task_queue2.get());
+ selector.RemoveQueue(task_queue2.get());
+ task_queue2->UnregisterTaskQueue();
+}
+
+struct ChooseOldestWithPriorityTestParam {
+ int delayed_task_enqueue_order;
+ int immediate_task_enqueue_order;
+ int immediate_starvation_count;
+ const char* expected_work_queue_name;
+ bool expected_did_starve_immediate_queue;
+};
+
+static const ChooseOldestWithPriorityTestParam
+ kChooseOldestWithPriorityTestCases[] = {
+ {1, 2, 0, "delayed", true}, {1, 2, 1, "delayed", true},
+ {1, 2, 2, "delayed", true}, {1, 2, 3, "immediate", false},
+ {1, 2, 4, "immediate", false}, {2, 1, 4, "immediate", false},
+ {2, 1, 4, "immediate", false},
+};
+
+class ChooseOldestWithPriorityTest
+ : public TaskQueueSelectorTest,
+ public testing::WithParamInterface<ChooseOldestWithPriorityTestParam> {};
+
+TEST_P(ChooseOldestWithPriorityTest, RoundRobinTest) {
+ task_queues_[0]->immediate_work_queue()->Push(TaskQueueImpl::Task(
+ TaskQueue::PostedTask(test_closure_, FROM_HERE), TimeTicks(),
+ EnqueueOrder::FromIntForTesting(GetParam().immediate_task_enqueue_order),
+ EnqueueOrder::FromIntForTesting(
+ GetParam().immediate_task_enqueue_order)));
+
+ task_queues_[0]->delayed_work_queue()->Push(TaskQueueImpl::Task(
+ TaskQueue::PostedTask(test_closure_, FROM_HERE), TimeTicks(),
+ EnqueueOrder::FromIntForTesting(GetParam().delayed_task_enqueue_order),
+ EnqueueOrder::FromIntForTesting(GetParam().delayed_task_enqueue_order)));
+
+ selector_.SetImmediateStarvationCountForTest(
+ GetParam().immediate_starvation_count);
+
+ WorkQueue* chosen_work_queue = nullptr;
+ bool chose_delayed_over_immediate = false;
+ EXPECT_TRUE(prioritizing_selector()->ChooseOldestWithPriority(
+ TaskQueue::kNormalPriority, &chose_delayed_over_immediate,
+ &chosen_work_queue));
+ EXPECT_EQ(chosen_work_queue->task_queue(), task_queues_[0].get());
+ EXPECT_STREQ(chosen_work_queue->name(), GetParam().expected_work_queue_name);
+ EXPECT_EQ(chose_delayed_over_immediate,
+ GetParam().expected_did_starve_immediate_queue);
+}
+
+INSTANTIATE_TEST_CASE_P(ChooseOldestWithPriorityTest,
+ ChooseOldestWithPriorityTest,
+ testing::ValuesIn(kChooseOldestWithPriorityTestCases));
+
+} // namespace task_queue_selector_unittest
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/task_time_observer.h b/base/task/sequence_manager/task_time_observer.h
new file mode 100644
index 0000000000..151a94119b
--- /dev/null
+++ b/base/task/sequence_manager/task_time_observer.h
@@ -0,0 +1,32 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_TASK_TIME_OBSERVER_H_
+#define BASE_TASK_SEQUENCE_MANAGER_TASK_TIME_OBSERVER_H_
+
+#include "base/time/time.h"
+
+namespace base {
+namespace sequence_manager {
+
+// TaskTimeObserver provides an API for observing completion of tasks.
+class TaskTimeObserver {
+ public:
+ TaskTimeObserver() = default;
+ virtual ~TaskTimeObserver() = default;
+
+ // To be called when task is about to start.
+ virtual void WillProcessTask(TimeTicks start_time) = 0;
+
+ // To be called when task is completed.
+ virtual void DidProcessTask(TimeTicks start_time, TimeTicks end_time) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TaskTimeObserver);
+};
+
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_TASK_TIME_OBSERVER_H_
diff --git a/base/task/sequence_manager/test/fake_task.cc b/base/task/sequence_manager/test/fake_task.cc
new file mode 100644
index 0000000000..2ddd14eaf0
--- /dev/null
+++ b/base/task/sequence_manager/test/fake_task.cc
@@ -0,0 +1,35 @@
+// 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.
+
+#include "base/task/sequence_manager/test/fake_task.h"
+
+namespace base {
+namespace sequence_manager {
+
+FakeTask::FakeTask()
+ : TaskQueue::Task(TaskQueue::PostedTask(OnceClosure(), FROM_HERE),
+ TimeTicks()) {}
+
+FakeTaskTiming::FakeTaskTiming()
+ : TaskTiming(false /* has_wall_time */, false /* has_thread_time */) {}
+
+FakeTaskTiming::FakeTaskTiming(TimeTicks start, TimeTicks end)
+ : FakeTaskTiming() {
+ has_wall_time_ = true;
+ start_time_ = start;
+ end_time_ = end;
+}
+
+FakeTaskTiming::FakeTaskTiming(TimeTicks start,
+ TimeTicks end,
+ ThreadTicks thread_start,
+ ThreadTicks thread_end)
+ : FakeTaskTiming(start, end) {
+ has_thread_time_ = true;
+ start_thread_time_ = thread_start;
+ end_thread_time_ = thread_end;
+}
+
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/test/fake_task.h b/base/task/sequence_manager/test/fake_task.h
new file mode 100644
index 0000000000..54cc3ac04d
--- /dev/null
+++ b/base/task/sequence_manager/test/fake_task.h
@@ -0,0 +1,31 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_TEST_FAKE_TASK_H_
+#define BASE_TASK_SEQUENCE_MANAGER_TEST_FAKE_TASK_H_
+
+#include "base/task/sequence_manager/task_queue.h"
+
+namespace base {
+namespace sequence_manager {
+
+class FakeTask : public TaskQueue::Task {
+ public:
+ FakeTask();
+};
+
+class FakeTaskTiming : public TaskQueue::TaskTiming {
+ public:
+ FakeTaskTiming();
+ FakeTaskTiming(TimeTicks start, TimeTicks end);
+ FakeTaskTiming(TimeTicks start,
+ TimeTicks end,
+ ThreadTicks thread_start,
+ ThreadTicks thread_end);
+};
+
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_TEST_FAKE_TASK_H_
diff --git a/base/task/sequence_manager/test/lazy_thread_controller_for_test.cc b/base/task/sequence_manager/test/lazy_thread_controller_for_test.cc
new file mode 100644
index 0000000000..39a7a1104f
--- /dev/null
+++ b/base/task/sequence_manager/test/lazy_thread_controller_for_test.cc
@@ -0,0 +1,123 @@
+// 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/task/sequence_manager/test/lazy_thread_controller_for_test.h"
+
+#include "base/message_loop/message_loop.h"
+#include "base/time/default_tick_clock.h"
+
+namespace base {
+namespace sequence_manager {
+
+LazyThreadControllerForTest::LazyThreadControllerForTest()
+ : ThreadControllerImpl(MessageLoop::current(),
+ nullptr,
+ DefaultTickClock::GetInstance()),
+ thread_ref_(PlatformThread::CurrentRef()) {
+ if (message_loop_)
+ task_runner_ = message_loop_->task_runner();
+}
+
+LazyThreadControllerForTest::~LazyThreadControllerForTest() = default;
+
+void LazyThreadControllerForTest::EnsureMessageLoop() {
+ if (message_loop_)
+ return;
+ DCHECK(RunsTasksInCurrentSequence());
+ message_loop_ = MessageLoop::current();
+ DCHECK(message_loop_);
+ task_runner_ = message_loop_->task_runner();
+ if (pending_observer_) {
+ RunLoop::AddNestingObserverOnCurrentThread(this);
+ pending_observer_ = false;
+ }
+ if (pending_default_task_runner_) {
+ ThreadControllerImpl::SetDefaultTaskRunner(pending_default_task_runner_);
+ pending_default_task_runner_ = nullptr;
+ }
+}
+
+bool LazyThreadControllerForTest::HasMessageLoop() {
+ return !!message_loop_;
+}
+
+void LazyThreadControllerForTest::AddNestingObserver(
+ RunLoop::NestingObserver* observer) {
+ // While |observer| _could_ be associated with the current thread regardless
+ // of the presence of a MessageLoop, the association is delayed until
+ // EnsureMessageLoop() is invoked. This works around a state issue where
+ // otherwise many tests fail because of the following sequence:
+ // 1) blink::scheduler::CreateRendererSchedulerForTests()
+ // -> SequenceManager::SequenceManager()
+ // -> LazySchedulerMessageLoopDelegateForTests::AddNestingObserver()
+ // 2) Any test framework with a MessageLoop member (and not caring
+ // about the blink scheduler) does:
+ // blink::scheduler::GetSingleThreadTaskRunnerForTesting()->PostTask(
+ // FROM_HERE, an_init_task_with_a_nested_loop);
+ // RunLoop.RunUntilIdle();
+ // 3) |a_task_with_a_nested_loop| triggers
+ // SequenceManager::OnBeginNestedLoop() which:
+ // a) flags any_thread().is_nested = true;
+ // b) posts a task to self, which triggers:
+ // LazySchedulerMessageLoopDelegateForTests::PostDelayedTask()
+ // 4) This self-task in turn triggers SequenceManager::DoWork()
+ // which expects to be the only one to trigger nested loops (doesn't
+ // support SequenceManager::OnBeginNestedLoop() being invoked before
+ // it kicks in), resulting in it hitting:
+ // DCHECK_EQ(any_thread().is_nested, delegate_->IsNested()); (1 vs 0).
+ // TODO(skyostil): fix this convolution as part of http://crbug.com/495659.
+ ThreadControllerImpl::nesting_observer_ = observer;
+ if (!HasMessageLoop()) {
+ DCHECK(!pending_observer_);
+ pending_observer_ = true;
+ return;
+ }
+ RunLoop::AddNestingObserverOnCurrentThread(this);
+}
+
+void LazyThreadControllerForTest::RemoveNestingObserver(
+ RunLoop::NestingObserver* observer) {
+ ThreadControllerImpl::nesting_observer_ = nullptr;
+ if (!HasMessageLoop()) {
+ DCHECK(pending_observer_);
+ pending_observer_ = false;
+ return;
+ }
+ if (MessageLoop::current() != message_loop_)
+ return;
+ RunLoop::RemoveNestingObserverOnCurrentThread(this);
+}
+
+bool LazyThreadControllerForTest::RunsTasksInCurrentSequence() {
+ return thread_ref_ == PlatformThread::CurrentRef();
+}
+
+void LazyThreadControllerForTest::ScheduleWork() {
+ EnsureMessageLoop();
+ ThreadControllerImpl::ScheduleWork();
+}
+
+void LazyThreadControllerForTest::SetNextDelayedDoWork(LazyNow* lazy_now,
+ TimeTicks run_time) {
+ EnsureMessageLoop();
+ ThreadControllerImpl::SetNextDelayedDoWork(lazy_now, run_time);
+}
+
+void LazyThreadControllerForTest::SetDefaultTaskRunner(
+ scoped_refptr<SingleThreadTaskRunner> task_runner) {
+ if (!HasMessageLoop()) {
+ pending_default_task_runner_ = task_runner;
+ return;
+ }
+ ThreadControllerImpl::SetDefaultTaskRunner(task_runner);
+}
+
+void LazyThreadControllerForTest::RestoreDefaultTaskRunner() {
+ pending_default_task_runner_ = nullptr;
+ if (HasMessageLoop() && MessageLoop::current() == message_loop_)
+ ThreadControllerImpl::RestoreDefaultTaskRunner();
+}
+
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/test/lazy_thread_controller_for_test.h b/base/task/sequence_manager/test/lazy_thread_controller_for_test.h
new file mode 100644
index 0000000000..6cc0e523b6
--- /dev/null
+++ b/base/task/sequence_manager/test/lazy_thread_controller_for_test.h
@@ -0,0 +1,53 @@
+// 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_TASK_SEQUENCE_MANAGER_TEST_LAZY_THREAD_CONTROLLER_FOR_TEST_H_
+#define BASE_TASK_SEQUENCE_MANAGER_TEST_LAZY_THREAD_CONTROLLER_FOR_TEST_H_
+
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task/sequence_manager/thread_controller_impl.h"
+#include "base/threading/platform_thread.h"
+
+namespace base {
+namespace sequence_manager {
+
+// This class connects the scheduler to a MessageLoop, but unlike
+// ThreadControllerImpl it allows the message loop to be created lazily
+// after the scheduler has been brought up. This is needed in testing scenarios
+// where Blink is initialized before a MessageLoop has been created.
+//
+// TODO(skyostil): Fix the relevant test suites and remove this class
+// (crbug.com/495659).
+class LazyThreadControllerForTest : public internal::ThreadControllerImpl {
+ public:
+ LazyThreadControllerForTest();
+ ~LazyThreadControllerForTest() override;
+
+ // internal::ThreadControllerImpl:
+ void AddNestingObserver(RunLoop::NestingObserver* observer) override;
+ void RemoveNestingObserver(RunLoop::NestingObserver* observer) override;
+ bool RunsTasksInCurrentSequence() override;
+ void ScheduleWork() override;
+ void SetNextDelayedDoWork(LazyNow* lazy_now, TimeTicks run_time) override;
+ void SetDefaultTaskRunner(
+ scoped_refptr<SingleThreadTaskRunner> task_runner) override;
+ void RestoreDefaultTaskRunner() override;
+
+ private:
+ bool HasMessageLoop();
+ void EnsureMessageLoop();
+
+ PlatformThreadRef thread_ref_;
+
+ bool pending_observer_ = false;
+ scoped_refptr<SingleThreadTaskRunner> pending_default_task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(LazyThreadControllerForTest);
+};
+
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_TEST_LAZY_THREAD_CONTROLLER_FOR_TEST_H_
diff --git a/base/task/sequence_manager/test/mock_time_domain.cc b/base/task/sequence_manager/test/mock_time_domain.cc
new file mode 100644
index 0000000000..b0ced82a70
--- /dev/null
+++ b/base/task/sequence_manager/test/mock_time_domain.cc
@@ -0,0 +1,39 @@
+// 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.
+
+#include "base/task/sequence_manager/test/mock_time_domain.h"
+
+namespace base {
+namespace sequence_manager {
+
+MockTimeDomain::MockTimeDomain(TimeTicks initial_now_ticks)
+ : now_ticks_(initial_now_ticks) {}
+
+MockTimeDomain::~MockTimeDomain() = default;
+
+LazyNow MockTimeDomain::CreateLazyNow() const {
+ return LazyNow(now_ticks_);
+}
+
+TimeTicks MockTimeDomain::Now() const {
+ return now_ticks_;
+}
+
+void MockTimeDomain::SetNowTicks(TimeTicks now_ticks) {
+ now_ticks_ = now_ticks;
+}
+
+Optional<TimeDelta> MockTimeDomain::DelayTillNextTask(LazyNow* lazy_now) {
+ return nullopt;
+}
+
+void MockTimeDomain::SetNextDelayedDoWork(LazyNow* lazy_now,
+ TimeTicks run_time) {}
+
+const char* MockTimeDomain::GetName() const {
+ return "MockTimeDomain";
+}
+
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/test/mock_time_domain.h b/base/task/sequence_manager/test/mock_time_domain.h
new file mode 100644
index 0000000000..0744e696a4
--- /dev/null
+++ b/base/task/sequence_manager/test/mock_time_domain.h
@@ -0,0 +1,38 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_TEST_MOCK_TIME_DOMAIN_H_
+#define BASE_TASK_SEQUENCE_MANAGER_TEST_MOCK_TIME_DOMAIN_H_
+
+#include "base/task/sequence_manager/time_domain.h"
+
+namespace base {
+namespace sequence_manager {
+
+// TimeDomain with a mock clock and not invoking SequenceManager.
+// NOTE: All methods are main thread only.
+class MockTimeDomain : public TimeDomain {
+ public:
+ explicit MockTimeDomain(TimeTicks initial_now_ticks);
+ ~MockTimeDomain() override;
+
+ void SetNowTicks(TimeTicks now_ticks);
+
+ // TimeDomain implementation:
+ LazyNow CreateLazyNow() const override;
+ TimeTicks Now() const override;
+ Optional<TimeDelta> DelayTillNextTask(LazyNow* lazy_now) override;
+ void SetNextDelayedDoWork(LazyNow* lazy_now, TimeTicks run_time) override;
+ const char* GetName() const override;
+
+ private:
+ TimeTicks now_ticks_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockTimeDomain);
+};
+
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_TEST_MOCK_TIME_DOMAIN_H_
diff --git a/base/task/sequence_manager/test/sequence_manager_for_test.cc b/base/task/sequence_manager/test/sequence_manager_for_test.cc
new file mode 100644
index 0000000000..3442957f48
--- /dev/null
+++ b/base/task/sequence_manager/test/sequence_manager_for_test.cc
@@ -0,0 +1,79 @@
+// 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.
+
+#include "base/task/sequence_manager/test/sequence_manager_for_test.h"
+
+#include "base/task/sequence_manager/thread_controller_impl.h"
+
+namespace base {
+namespace sequence_manager {
+
+namespace {
+
+class ThreadControllerForTest : public internal::ThreadControllerImpl {
+ public:
+ ThreadControllerForTest(MessageLoop* message_loop,
+ scoped_refptr<SingleThreadTaskRunner> task_runner,
+ const TickClock* time_source)
+ : ThreadControllerImpl(message_loop,
+ std::move(task_runner),
+ time_source) {}
+
+ void AddNestingObserver(RunLoop::NestingObserver* observer) override {
+ if (!message_loop_)
+ return;
+ ThreadControllerImpl::AddNestingObserver(observer);
+ }
+
+ void RemoveNestingObserver(RunLoop::NestingObserver* observer) override {
+ if (!message_loop_)
+ return;
+ ThreadControllerImpl::RemoveNestingObserver(observer);
+ }
+
+ ~ThreadControllerForTest() override = default;
+};
+
+} // namespace
+
+SequenceManagerForTest::SequenceManagerForTest(
+ std::unique_ptr<internal::ThreadController> thread_controller)
+ : SequenceManagerImpl(std::move(thread_controller)) {}
+
+// static
+std::unique_ptr<SequenceManagerForTest> SequenceManagerForTest::Create(
+ MessageLoop* message_loop,
+ scoped_refptr<SingleThreadTaskRunner> task_runner,
+ const TickClock* clock) {
+ return std::make_unique<SequenceManagerForTest>(
+ std::make_unique<ThreadControllerForTest>(message_loop,
+ std::move(task_runner), clock));
+}
+
+size_t SequenceManagerForTest::ActiveQueuesCount() const {
+ return main_thread_only().active_queues.size();
+}
+
+bool SequenceManagerForTest::HasImmediateWork() const {
+ return !main_thread_only().selector.AllEnabledWorkQueuesAreEmpty();
+}
+
+size_t SequenceManagerForTest::PendingTasksCount() const {
+ size_t task_count = 0;
+ for (auto* const queue : main_thread_only().active_queues)
+ task_count += queue->GetNumberOfPendingTasks();
+ return task_count;
+}
+
+size_t SequenceManagerForTest::QueuesToDeleteCount() const {
+ return main_thread_only().queues_to_delete.size();
+}
+
+size_t SequenceManagerForTest::QueuesToShutdownCount() {
+ TakeQueuesToGracefullyShutdownFromHelper();
+ return main_thread_only().queues_to_gracefully_shutdown.size();
+}
+
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/test/sequence_manager_for_test.h b/base/task/sequence_manager/test/sequence_manager_for_test.h
new file mode 100644
index 0000000000..442c9a8595
--- /dev/null
+++ b/base/task/sequence_manager/test/sequence_manager_for_test.h
@@ -0,0 +1,46 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_TEST_SEQUENCE_MANAGER_FOR_TEST_H_
+#define BASE_TASK_SEQUENCE_MANAGER_TEST_SEQUENCE_MANAGER_FOR_TEST_H_
+
+#include "base/single_thread_task_runner.h"
+#include "base/task/sequence_manager/sequence_manager_impl.h"
+#include "base/time/tick_clock.h"
+
+namespace base {
+
+class MessageLoop;
+
+namespace sequence_manager {
+
+class SequenceManagerForTest : public internal::SequenceManagerImpl {
+ public:
+ explicit SequenceManagerForTest(
+ std::unique_ptr<internal::ThreadController> thread_controller);
+
+ ~SequenceManagerForTest() override = default;
+
+ // Creates SequenceManagerImpl using ThreadControllerImpl constructed with
+ // the given arguments. ThreadControllerImpl is slightly overridden to skip
+ // nesting observers registration if message loop is absent.
+ static std::unique_ptr<SequenceManagerForTest> Create(
+ MessageLoop* message_loop,
+ scoped_refptr<SingleThreadTaskRunner> task_runner,
+ const TickClock* clock);
+
+ size_t ActiveQueuesCount() const;
+ bool HasImmediateWork() const;
+ size_t PendingTasksCount() const;
+ size_t QueuesToDeleteCount() const;
+ size_t QueuesToShutdownCount();
+
+ using internal::SequenceManagerImpl::GetNextSequenceNumber;
+ using internal::SequenceManagerImpl::WakeUpReadyDelayedQueues;
+};
+
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_TEST_SEQUENCE_MANAGER_FOR_TEST_H_
diff --git a/base/task/sequence_manager/test/test_task_queue.cc b/base/task/sequence_manager/test/test_task_queue.cc
new file mode 100644
index 0000000000..19abe11383
--- /dev/null
+++ b/base/task/sequence_manager/test/test_task_queue.cc
@@ -0,0 +1,23 @@
+// 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.
+
+#include "base/task/sequence_manager/test/test_task_queue.h"
+
+#include "base/task/sequence_manager/task_queue_impl.h"
+
+namespace base {
+namespace sequence_manager {
+
+TestTaskQueue::TestTaskQueue(std::unique_ptr<internal::TaskQueueImpl> impl,
+ const TaskQueue::Spec& spec)
+ : TaskQueue(std::move(impl), spec), weak_factory_(this) {}
+
+TestTaskQueue::~TestTaskQueue() = default;
+
+WeakPtr<TestTaskQueue> TestTaskQueue::GetWeakPtr() {
+ return weak_factory_.GetWeakPtr();
+}
+
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/test/test_task_queue.h b/base/task/sequence_manager/test/test_task_queue.h
new file mode 100644
index 0000000000..2f5a64e16d
--- /dev/null
+++ b/base/task/sequence_manager/test/test_task_queue.h
@@ -0,0 +1,33 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_TEST_TEST_TASK_QUEUE_H_
+#define BASE_TASK_SEQUENCE_MANAGER_TEST_TEST_TASK_QUEUE_H_
+
+#include "base/memory/weak_ptr.h"
+#include "base/task/sequence_manager/task_queue.h"
+
+namespace base {
+namespace sequence_manager {
+
+class TestTaskQueue : public TaskQueue {
+ public:
+ explicit TestTaskQueue(std::unique_ptr<internal::TaskQueueImpl> impl,
+ const TaskQueue::Spec& spec);
+
+ using TaskQueue::GetTaskQueueImpl;
+
+ WeakPtr<TestTaskQueue> GetWeakPtr();
+
+ private:
+ ~TestTaskQueue() override; // Ref-counted.
+
+ // Used to ensure that task queue is deleted in tests.
+ WeakPtrFactory<TestTaskQueue> weak_factory_;
+};
+
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_TEST_TEST_TASK_QUEUE_H_
diff --git a/base/task/sequence_manager/test/test_task_time_observer.h b/base/task/sequence_manager/test/test_task_time_observer.h
new file mode 100644
index 0000000000..54e4ff45fa
--- /dev/null
+++ b/base/task/sequence_manager/test/test_task_time_observer.h
@@ -0,0 +1,23 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_TEST_TEST_TASK_TIME_OBSERVER_H_
+#define BASE_TASK_SEQUENCE_MANAGER_TEST_TEST_TASK_TIME_OBSERVER_H_
+
+#include "base/task/sequence_manager/task_time_observer.h"
+#include "base/time/time.h"
+
+namespace base {
+namespace sequence_manager {
+
+class TestTaskTimeObserver : public TaskTimeObserver {
+ public:
+ void WillProcessTask(TimeTicks start_time) override {}
+ void DidProcessTask(TimeTicks start_time, TimeTicks end_time) override {}
+};
+
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_TEST_TEST_TASK_TIME_OBSERVER_H_
diff --git a/base/task/sequence_manager/thread_controller.h b/base/task/sequence_manager/thread_controller.h
new file mode 100644
index 0000000000..539530602b
--- /dev/null
+++ b/base/task/sequence_manager/thread_controller.h
@@ -0,0 +1,85 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_H_
+#define BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_H_
+
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task/sequence_manager/lazy_now.h"
+#include "base/time/time.h"
+
+namespace base {
+
+class TickClock;
+struct PendingTask;
+
+namespace sequence_manager {
+namespace internal {
+
+class SequencedTaskSource;
+
+// Implementation of this interface is used by SequenceManager to schedule
+// actual work to be run. Hopefully we can stop using MessageLoop and this
+// interface will become more concise.
+class ThreadController {
+ public:
+ virtual ~ThreadController() = default;
+
+ // Sets the number of tasks executed in a single invocation of DoWork.
+ // Increasing the batch size can reduce the overhead of yielding back to the
+ // main message loop.
+ virtual void SetWorkBatchSize(int work_batch_size = 1) = 0;
+
+ // Notifies that |pending_task| is about to be enqueued. Needed for tracing
+ // purposes. The impl may use this opportunity add metadata to |pending_task|
+ // before it is moved into the queue.
+ virtual void WillQueueTask(PendingTask* pending_task) = 0;
+
+ // Notify the controller that its associated sequence has immediate work
+ // to run. Shortly after this is called, the thread associated with this
+ // controller will run a task returned by sequence->TakeTask(). Can be called
+ // from any sequence.
+ //
+ // TODO(altimin): Change this to "the thread associated with this
+ // controller will run tasks returned by sequence->TakeTask() until it
+ // returns null or sequence->DidRunTask() returns false" once the
+ // code is changed to work that way.
+ virtual void ScheduleWork() = 0;
+
+ // Notify the controller that SequencedTaskSource will have a delayed work
+ // ready to be run at |run_time|. This call cancels any previously
+ // scheduled delayed work. Can only be called from the main sequence.
+ // NOTE: DelayTillNextTask might return a different value as it also takes
+ // immediate work into account.
+ // TODO(kraynov): Remove |lazy_now| parameter.
+ virtual void SetNextDelayedDoWork(LazyNow* lazy_now, TimeTicks run_time) = 0;
+
+ // Sets the sequenced task source from which to take tasks after
+ // a Schedule*Work() call is made.
+ // Must be called before the first call to Schedule*Work().
+ virtual void SetSequencedTaskSource(SequencedTaskSource*) = 0;
+
+ // TODO(altimin): Get rid of the methods below.
+ // These methods exist due to current integration of SequenceManager
+ // with MessageLoop.
+
+ virtual bool RunsTasksInCurrentSequence() = 0;
+
+ virtual const TickClock* GetClock() = 0;
+
+ virtual void SetDefaultTaskRunner(scoped_refptr<SingleThreadTaskRunner>) = 0;
+
+ virtual void RestoreDefaultTaskRunner() = 0;
+
+ virtual void AddNestingObserver(RunLoop::NestingObserver* observer) = 0;
+
+ virtual void RemoveNestingObserver(RunLoop::NestingObserver* observer) = 0;
+};
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_H_
diff --git a/base/task/sequence_manager/thread_controller_impl.cc b/base/task/sequence_manager/thread_controller_impl.cc
new file mode 100644
index 0000000000..efa80fb053
--- /dev/null
+++ b/base/task/sequence_manager/thread_controller_impl.cc
@@ -0,0 +1,269 @@
+// 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/task/sequence_manager/thread_controller_impl.h"
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/task/sequence_manager/lazy_now.h"
+#include "base/task/sequence_manager/sequenced_task_source.h"
+#include "base/trace_event/trace_event.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+ThreadControllerImpl::ThreadControllerImpl(
+ MessageLoop* message_loop,
+ scoped_refptr<SingleThreadTaskRunner> task_runner,
+ const TickClock* time_source)
+ : message_loop_(message_loop),
+ task_runner_(task_runner),
+ message_loop_task_runner_(message_loop ? message_loop->task_runner()
+ : nullptr),
+ time_source_(time_source),
+ weak_factory_(this) {
+ immediate_do_work_closure_ =
+ BindRepeating(&ThreadControllerImpl::DoWork, weak_factory_.GetWeakPtr(),
+ WorkType::kImmediate);
+ delayed_do_work_closure_ =
+ BindRepeating(&ThreadControllerImpl::DoWork, weak_factory_.GetWeakPtr(),
+ WorkType::kDelayed);
+}
+
+ThreadControllerImpl::~ThreadControllerImpl() = default;
+
+ThreadControllerImpl::AnySequence::AnySequence() = default;
+
+ThreadControllerImpl::AnySequence::~AnySequence() = default;
+
+ThreadControllerImpl::MainSequenceOnly::MainSequenceOnly() = default;
+
+ThreadControllerImpl::MainSequenceOnly::~MainSequenceOnly() = default;
+
+std::unique_ptr<ThreadControllerImpl> ThreadControllerImpl::Create(
+ MessageLoop* message_loop,
+ const TickClock* time_source) {
+ return WrapUnique(new ThreadControllerImpl(
+ message_loop, message_loop->task_runner(), time_source));
+}
+
+void ThreadControllerImpl::SetSequencedTaskSource(
+ SequencedTaskSource* sequence) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(sequence);
+ DCHECK(!sequence_);
+ sequence_ = sequence;
+}
+
+void ThreadControllerImpl::ScheduleWork() {
+ DCHECK(sequence_);
+ AutoLock lock(any_sequence_lock_);
+ // Don't post a DoWork if there's an immediate DoWork in flight or if we're
+ // inside a top level DoWork. We can rely on a continuation being posted as
+ // needed.
+ if (any_sequence().immediate_do_work_posted ||
+ (any_sequence().do_work_running_count > any_sequence().nesting_depth)) {
+ return;
+ }
+ any_sequence().immediate_do_work_posted = true;
+
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
+ "ThreadControllerImpl::ScheduleWork::PostTask");
+ task_runner_->PostTask(FROM_HERE, immediate_do_work_closure_);
+}
+
+void ThreadControllerImpl::SetNextDelayedDoWork(LazyNow* lazy_now,
+ TimeTicks run_time) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(sequence_);
+
+ if (main_sequence_only().next_delayed_do_work == run_time)
+ return;
+
+ // Cancel DoWork if it was scheduled and we set an "infinite" delay now.
+ if (run_time == TimeTicks::Max()) {
+ cancelable_delayed_do_work_closure_.Cancel();
+ main_sequence_only().next_delayed_do_work = TimeTicks::Max();
+ return;
+ }
+
+ // If DoWork is running then we don't need to do anything because it will post
+ // a continuation as needed. Bailing out here is by far the most common case.
+ if (main_sequence_only().do_work_running_count >
+ main_sequence_only().nesting_depth) {
+ return;
+ }
+
+ // If DoWork is about to run then we also don't need to do anything.
+ {
+ AutoLock lock(any_sequence_lock_);
+ if (any_sequence().immediate_do_work_posted)
+ return;
+ }
+
+ base::TimeDelta delay = std::max(TimeDelta(), run_time - lazy_now->Now());
+ TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
+ "ThreadControllerImpl::SetNextDelayedDoWork::PostDelayedTask",
+ "delay_ms", delay.InMillisecondsF());
+
+ main_sequence_only().next_delayed_do_work = run_time;
+ // Reset also causes cancellation of the previous DoWork task.
+ cancelable_delayed_do_work_closure_.Reset(delayed_do_work_closure_);
+ task_runner_->PostDelayedTask(
+ FROM_HERE, cancelable_delayed_do_work_closure_.callback(), delay);
+}
+
+bool ThreadControllerImpl::RunsTasksInCurrentSequence() {
+ return task_runner_->RunsTasksInCurrentSequence();
+}
+
+const TickClock* ThreadControllerImpl::GetClock() {
+ return time_source_;
+}
+
+void ThreadControllerImpl::SetDefaultTaskRunner(
+ scoped_refptr<SingleThreadTaskRunner> task_runner) {
+ if (!message_loop_)
+ return;
+ message_loop_->SetTaskRunner(task_runner);
+}
+
+void ThreadControllerImpl::RestoreDefaultTaskRunner() {
+ if (!message_loop_)
+ return;
+ message_loop_->SetTaskRunner(message_loop_task_runner_);
+}
+
+void ThreadControllerImpl::WillQueueTask(PendingTask* pending_task) {
+ task_annotator_.WillQueueTask("SequenceManager::PostTask", pending_task);
+}
+
+void ThreadControllerImpl::DoWork(WorkType work_type) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(sequence_);
+
+ {
+ AutoLock lock(any_sequence_lock_);
+ if (work_type == WorkType::kImmediate)
+ any_sequence().immediate_do_work_posted = false;
+ any_sequence().do_work_running_count++;
+ }
+
+ main_sequence_only().do_work_running_count++;
+
+ WeakPtr<ThreadControllerImpl> weak_ptr = weak_factory_.GetWeakPtr();
+ // TODO(scheduler-dev): Consider moving to a time based work batch instead.
+ for (int i = 0; i < main_sequence_only().work_batch_size_; i++) {
+ Optional<PendingTask> task = sequence_->TakeTask();
+ if (!task)
+ break;
+
+ TRACE_TASK_EXECUTION("ThreadControllerImpl::DoWork", *task);
+ task_annotator_.RunTask("ThreadControllerImpl::DoWork", &*task);
+
+ if (!weak_ptr)
+ return;
+
+ sequence_->DidRunTask();
+
+ // NOTE: https://crbug.com/828835.
+ // When we're running inside a nested RunLoop it may quit anytime, so any
+ // outstanding pending tasks must run in the outer RunLoop
+ // (see SequenceManagerTestWithMessageLoop.QuitWhileNested test).
+ // Unfortunately, it's MessageLoop who's receving that signal and we can't
+ // know it before we return from DoWork, hence, OnExitNestedRunLoop
+ // will be called later. Since we must implement ThreadController and
+ // SequenceManager in conformance with MessageLoop task runners, we need
+ // to disable this batching optimization while nested.
+ // Implementing RunLoop::Delegate ourselves will help to resolve this issue.
+ if (main_sequence_only().nesting_depth > 0)
+ break;
+ }
+
+ main_sequence_only().do_work_running_count--;
+
+ {
+ AutoLock lock(any_sequence_lock_);
+ any_sequence().do_work_running_count--;
+ DCHECK_GE(any_sequence().do_work_running_count, 0);
+ LazyNow lazy_now(time_source_);
+ TimeDelta delay_till_next_task = sequence_->DelayTillNextTask(&lazy_now);
+ if (delay_till_next_task <= TimeDelta()) {
+ // The next task needs to run immediately, post a continuation if needed.
+ if (!any_sequence().immediate_do_work_posted) {
+ any_sequence().immediate_do_work_posted = true;
+ task_runner_->PostTask(FROM_HERE, immediate_do_work_closure_);
+ }
+ } else if (delay_till_next_task < TimeDelta::Max()) {
+ // The next task needs to run after a delay, post a continuation if
+ // needed.
+ TimeTicks next_task_at = lazy_now.Now() + delay_till_next_task;
+ if (next_task_at != main_sequence_only().next_delayed_do_work) {
+ main_sequence_only().next_delayed_do_work = next_task_at;
+ cancelable_delayed_do_work_closure_.Reset(delayed_do_work_closure_);
+ task_runner_->PostDelayedTask(
+ FROM_HERE, cancelable_delayed_do_work_closure_.callback(),
+ delay_till_next_task);
+ }
+ } else {
+ // There is no next task scheduled.
+ main_sequence_only().next_delayed_do_work = TimeTicks::Max();
+ }
+ }
+}
+
+void ThreadControllerImpl::AddNestingObserver(
+ RunLoop::NestingObserver* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ nesting_observer_ = observer;
+ RunLoop::AddNestingObserverOnCurrentThread(this);
+}
+
+void ThreadControllerImpl::RemoveNestingObserver(
+ RunLoop::NestingObserver* observer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(observer, nesting_observer_);
+ nesting_observer_ = nullptr;
+ RunLoop::RemoveNestingObserverOnCurrentThread(this);
+}
+
+void ThreadControllerImpl::OnBeginNestedRunLoop() {
+ main_sequence_only().nesting_depth++;
+ {
+ // We just entered a nested run loop, make sure there's a DoWork posted or
+ // the system will grind to a halt.
+ AutoLock lock(any_sequence_lock_);
+ any_sequence().nesting_depth++;
+ if (!any_sequence().immediate_do_work_posted) {
+ any_sequence().immediate_do_work_posted = true;
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("sequence_manager"),
+ "ThreadControllerImpl::OnBeginNestedRunLoop::PostTask");
+ task_runner_->PostTask(FROM_HERE, immediate_do_work_closure_);
+ }
+ }
+ if (nesting_observer_)
+ nesting_observer_->OnBeginNestedRunLoop();
+}
+
+void ThreadControllerImpl::OnExitNestedRunLoop() {
+ main_sequence_only().nesting_depth--;
+ {
+ AutoLock lock(any_sequence_lock_);
+ any_sequence().nesting_depth--;
+ DCHECK_GE(any_sequence().nesting_depth, 0);
+ }
+ if (nesting_observer_)
+ nesting_observer_->OnExitNestedRunLoop();
+}
+
+void ThreadControllerImpl::SetWorkBatchSize(int work_batch_size) {
+ main_sequence_only().work_batch_size_ = work_batch_size;
+}
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/thread_controller_impl.h b/base/task/sequence_manager/thread_controller_impl.h
new file mode 100644
index 0000000000..794feefb4b
--- /dev/null
+++ b/base/task/sequence_manager/thread_controller_impl.h
@@ -0,0 +1,130 @@
+// 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_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_IMPL_H_
+#define BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_IMPL_H_
+
+#include "base/cancelable_callback.h"
+#include "base/debug/task_annotator.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/run_loop.h"
+#include "base/sequence_checker.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task/sequence_manager/thread_controller.h"
+
+namespace base {
+
+// TODO(kraynov): https://crbug.com/828835
+// Consider going away from using MessageLoop in the renderer process.
+class MessageLoop;
+
+namespace sequence_manager {
+namespace internal {
+
+// TODO(kraynov): Rename to ThreadControllerWithMessageLoopImpl.
+class BASE_EXPORT ThreadControllerImpl : public ThreadController,
+ public RunLoop::NestingObserver {
+ public:
+ ~ThreadControllerImpl() override;
+
+ static std::unique_ptr<ThreadControllerImpl> Create(
+ MessageLoop* message_loop,
+ const TickClock* time_source);
+
+ // ThreadController:
+ void SetWorkBatchSize(int work_batch_size) override;
+ void WillQueueTask(PendingTask* pending_task) override;
+ void ScheduleWork() override;
+ void SetNextDelayedDoWork(LazyNow* lazy_now, TimeTicks run_time) override;
+ void SetSequencedTaskSource(SequencedTaskSource* sequence) override;
+ bool RunsTasksInCurrentSequence() override;
+ const TickClock* GetClock() override;
+ void SetDefaultTaskRunner(scoped_refptr<SingleThreadTaskRunner>) override;
+ void RestoreDefaultTaskRunner() override;
+ void AddNestingObserver(RunLoop::NestingObserver* observer) override;
+ void RemoveNestingObserver(RunLoop::NestingObserver* observer) override;
+
+ // RunLoop::NestingObserver:
+ void OnBeginNestedRunLoop() override;
+ void OnExitNestedRunLoop() override;
+
+ protected:
+ ThreadControllerImpl(MessageLoop* message_loop,
+ scoped_refptr<SingleThreadTaskRunner> task_runner,
+ const TickClock* time_source);
+
+ // TODO(altimin): Make these const. Blocked on removing
+ // lazy initialisation support.
+ MessageLoop* message_loop_;
+ scoped_refptr<SingleThreadTaskRunner> task_runner_;
+
+ RunLoop::NestingObserver* nesting_observer_ = nullptr;
+
+ private:
+ enum class WorkType { kImmediate, kDelayed };
+
+ void DoWork(WorkType work_type);
+
+ struct AnySequence {
+ AnySequence();
+ ~AnySequence();
+
+ int do_work_running_count = 0;
+ int nesting_depth = 0;
+ bool immediate_do_work_posted = false;
+ };
+
+ mutable Lock any_sequence_lock_;
+ AnySequence any_sequence_;
+
+ struct AnySequence& any_sequence() {
+ any_sequence_lock_.AssertAcquired();
+ return any_sequence_;
+ }
+ const struct AnySequence& any_sequence() const {
+ any_sequence_lock_.AssertAcquired();
+ return any_sequence_;
+ }
+
+ struct MainSequenceOnly {
+ MainSequenceOnly();
+ ~MainSequenceOnly();
+
+ int do_work_running_count = 0;
+ int nesting_depth = 0;
+ int work_batch_size_ = 1;
+
+ TimeTicks next_delayed_do_work = TimeTicks::Max();
+ };
+
+ SEQUENCE_CHECKER(sequence_checker_);
+ MainSequenceOnly main_sequence_only_;
+ MainSequenceOnly& main_sequence_only() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return main_sequence_only_;
+ }
+ const MainSequenceOnly& main_sequence_only() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return main_sequence_only_;
+ }
+
+ scoped_refptr<SingleThreadTaskRunner> message_loop_task_runner_;
+ const TickClock* time_source_;
+ RepeatingClosure immediate_do_work_closure_;
+ RepeatingClosure delayed_do_work_closure_;
+ CancelableClosure cancelable_delayed_do_work_closure_;
+ SequencedTaskSource* sequence_ = nullptr; // Not owned.
+ debug::TaskAnnotator task_annotator_;
+
+ WeakPtrFactory<ThreadControllerImpl> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadControllerImpl);
+};
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_IMPL_H_
diff --git a/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc b/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc
new file mode 100644
index 0000000000..fbed88b404
--- /dev/null
+++ b/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc
@@ -0,0 +1,205 @@
+// 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.
+
+#include "base/task/sequence_manager/thread_controller_with_message_pump_impl.h"
+
+#include "base/auto_reset.h"
+#include "base/message_loop/message_pump_default.h"
+#include "base/time/tick_clock.h"
+#include "base/trace_event/trace_event.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+ThreadControllerWithMessagePumpImpl::ThreadControllerWithMessagePumpImpl(
+ TickClock* time_source)
+ : main_thread_id_(PlatformThread::CurrentId()),
+ pump_(new MessagePumpDefault()),
+ time_source_(time_source) {
+ RunLoop::RegisterDelegateForCurrentThread(this);
+}
+
+ThreadControllerWithMessagePumpImpl::~ThreadControllerWithMessagePumpImpl() {
+ // Destructors of RunLoop::Delegate and ThreadTaskRunnerHandle
+ // will do all the clean-up.
+}
+
+ThreadControllerWithMessagePumpImpl::MainThreadOnly::MainThreadOnly() = default;
+
+ThreadControllerWithMessagePumpImpl::MainThreadOnly::~MainThreadOnly() =
+ default;
+
+void ThreadControllerWithMessagePumpImpl::SetSequencedTaskSource(
+ SequencedTaskSource* task_source) {
+ DCHECK(task_source);
+ DCHECK(!main_thread_only().task_source);
+ main_thread_only().task_source = task_source;
+}
+
+void ThreadControllerWithMessagePumpImpl::SetWorkBatchSize(
+ int work_batch_size) {
+ DCHECK_GE(work_batch_size, 1);
+ main_thread_only().batch_size = work_batch_size;
+}
+
+void ThreadControllerWithMessagePumpImpl::WillQueueTask(
+ PendingTask* pending_task) {
+ task_annotator_.WillQueueTask("ThreadController::Task", pending_task);
+}
+
+void ThreadControllerWithMessagePumpImpl::ScheduleWork() {
+ // Continuation will be posted if necessary.
+ if (RunsTasksInCurrentSequence() && is_doing_work())
+ return;
+
+ pump_->ScheduleWork();
+}
+
+void ThreadControllerWithMessagePumpImpl::SetNextDelayedDoWork(
+ LazyNow* lazy_now,
+ TimeTicks run_time) {
+ if (main_thread_only().next_delayed_work == run_time)
+ return;
+ main_thread_only().next_delayed_work = run_time;
+
+ if (run_time == TimeTicks::Max())
+ return;
+
+ // Continuation will be posted if necessary.
+ if (is_doing_work())
+ return;
+
+ // |lazy_now| will be removed in this method soon.
+ DCHECK_LT(time_source_->NowTicks(), run_time);
+ pump_->ScheduleDelayedWork(run_time);
+}
+
+const TickClock* ThreadControllerWithMessagePumpImpl::GetClock() {
+ return time_source_;
+}
+
+bool ThreadControllerWithMessagePumpImpl::RunsTasksInCurrentSequence() {
+ return main_thread_id_ == PlatformThread::CurrentId();
+}
+
+void ThreadControllerWithMessagePumpImpl::SetDefaultTaskRunner(
+ scoped_refptr<SingleThreadTaskRunner> task_runner) {
+ main_thread_only().thread_task_runner_handle =
+ std::make_unique<ThreadTaskRunnerHandle>(task_runner);
+}
+
+void ThreadControllerWithMessagePumpImpl::RestoreDefaultTaskRunner() {
+ // There's no default task runner unlike with the MessageLoop.
+ main_thread_only().thread_task_runner_handle.reset();
+}
+
+void ThreadControllerWithMessagePumpImpl::AddNestingObserver(
+ RunLoop::NestingObserver* observer) {
+ DCHECK_LE(main_thread_only().run_depth, 1);
+ DCHECK(!main_thread_only().nesting_observer);
+ DCHECK(observer);
+ main_thread_only().nesting_observer = observer;
+}
+
+void ThreadControllerWithMessagePumpImpl::RemoveNestingObserver(
+ RunLoop::NestingObserver* observer) {
+ DCHECK_EQ(main_thread_only().nesting_observer, observer);
+ main_thread_only().nesting_observer = nullptr;
+}
+
+bool ThreadControllerWithMessagePumpImpl::DoWork() {
+ DCHECK(main_thread_only().task_source);
+ bool task_ran = false;
+
+ {
+ AutoReset<int> do_work_scope(&main_thread_only().do_work_depth,
+ main_thread_only().do_work_depth + 1);
+
+ for (int i = 0; i < main_thread_only().batch_size; i++) {
+ Optional<PendingTask> task = main_thread_only().task_source->TakeTask();
+ if (!task)
+ break;
+
+ TRACE_TASK_EXECUTION("ThreadController::Task", *task);
+ task_annotator_.RunTask("ThreadController::Task", &*task);
+ task_ran = true;
+
+ main_thread_only().task_source->DidRunTask();
+
+ if (main_thread_only().quit_do_work) {
+ // When Quit() is called we must stop running the batch because
+ // caller expects per-task granularity.
+ main_thread_only().quit_do_work = false;
+ return true;
+ }
+ }
+ } // DoWorkScope.
+
+ LazyNow lazy_now(time_source_);
+ TimeDelta do_work_delay =
+ main_thread_only().task_source->DelayTillNextTask(&lazy_now);
+ DCHECK_GE(do_work_delay, TimeDelta());
+ // Schedule a continuation.
+ if (do_work_delay.is_zero()) {
+ // Need to run new work immediately.
+ pump_->ScheduleWork();
+ } else if (do_work_delay != TimeDelta::Max()) {
+ SetNextDelayedDoWork(&lazy_now, lazy_now.Now() + do_work_delay);
+ } else {
+ SetNextDelayedDoWork(&lazy_now, TimeTicks::Max());
+ }
+
+ return task_ran;
+}
+
+bool ThreadControllerWithMessagePumpImpl::DoDelayedWork(
+ TimeTicks* next_run_time) {
+ // Delayed work is getting processed in DoWork().
+ return false;
+}
+
+bool ThreadControllerWithMessagePumpImpl::DoIdleWork() {
+ // RunLoop::Delegate knows whether we called Run() or RunUntilIdle().
+ if (ShouldQuitWhenIdle())
+ Quit();
+ return false;
+}
+
+void ThreadControllerWithMessagePumpImpl::Run(bool application_tasks_allowed) {
+ // No system messages are being processed by this class.
+ DCHECK(application_tasks_allowed);
+
+ // We already have a MessagePump::Run() running, so we're in a nested RunLoop.
+ if (main_thread_only().run_depth > 0 && main_thread_only().nesting_observer)
+ main_thread_only().nesting_observer->OnBeginNestedRunLoop();
+
+ {
+ AutoReset<int> run_scope(&main_thread_only().run_depth,
+ main_thread_only().run_depth + 1);
+ // MessagePump::Run() blocks until Quit() called, but previously started
+ // Run() calls continue to block.
+ pump_->Run(this);
+ }
+
+ // We'll soon continue to run an outer MessagePump::Run() loop.
+ if (main_thread_only().run_depth > 0 && main_thread_only().nesting_observer)
+ main_thread_only().nesting_observer->OnExitNestedRunLoop();
+}
+
+void ThreadControllerWithMessagePumpImpl::Quit() {
+ // Interrupt a batch of work.
+ if (is_doing_work())
+ main_thread_only().quit_do_work = true;
+ // If we're in a nested RunLoop, continuation will be posted if necessary.
+ pump_->Quit();
+}
+
+void ThreadControllerWithMessagePumpImpl::EnsureWorkScheduled() {
+ ScheduleWork();
+}
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/thread_controller_with_message_pump_impl.h b/base/task/sequence_manager/thread_controller_with_message_pump_impl.h
new file mode 100644
index 0000000000..c19a2e8992
--- /dev/null
+++ b/base/task/sequence_manager/thread_controller_with_message_pump_impl.h
@@ -0,0 +1,109 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_WITH_MESSAGE_PUMP_IMPL_H_
+#define BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_WITH_MESSAGE_PUMP_IMPL_H_
+
+#include "base/debug/task_annotator.h"
+#include "base/message_loop/message_pump.h"
+#include "base/task/sequence_manager/sequenced_task_source.h"
+#include "base/task/sequence_manager/thread_controller.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+// EXPERIMENTAL ThreadController implementation which doesn't use
+// MessageLoop or a task runner to schedule their DoWork calls.
+// See https://crbug.com/828835.
+class BASE_EXPORT ThreadControllerWithMessagePumpImpl
+ : public ThreadController,
+ public MessagePump::Delegate,
+ public RunLoop::Delegate {
+ public:
+ explicit ThreadControllerWithMessagePumpImpl(TickClock* time_source);
+ ~ThreadControllerWithMessagePumpImpl() override;
+
+ // ThreadController implementation:
+ void SetSequencedTaskSource(SequencedTaskSource* task_source) override;
+ void SetWorkBatchSize(int work_batch_size) override;
+ void WillQueueTask(PendingTask* pending_task) override;
+ void ScheduleWork() override;
+ void SetNextDelayedDoWork(LazyNow* lazy_now, TimeTicks run_time) override;
+ const TickClock* GetClock() override;
+ bool RunsTasksInCurrentSequence() override;
+ void SetDefaultTaskRunner(
+ scoped_refptr<SingleThreadTaskRunner> task_runner) override;
+ void RestoreDefaultTaskRunner() override;
+ void AddNestingObserver(RunLoop::NestingObserver* observer) override;
+ void RemoveNestingObserver(RunLoop::NestingObserver* observer) override;
+
+ private:
+ friend class DoWorkScope;
+ friend class RunScope;
+
+ // MessagePump::Delegate implementation.
+ bool DoWork() override;
+ bool DoDelayedWork(TimeTicks* next_run_time) override;
+ bool DoIdleWork() override;
+
+ // RunLoop::Delegate implementation.
+ void Run(bool application_tasks_allowed) override;
+ void Quit() override;
+ void EnsureWorkScheduled() override;
+
+ struct MainThreadOnly {
+ MainThreadOnly();
+ ~MainThreadOnly();
+
+ SequencedTaskSource* task_source = nullptr; // Not owned.
+ RunLoop::NestingObserver* nesting_observer = nullptr; // Not owned.
+ std::unique_ptr<ThreadTaskRunnerHandle> thread_task_runner_handle;
+
+ // Next delayed DoWork time for scheduling de-duplication purpose.
+ TimeTicks next_delayed_work;
+
+ // Indicates that we should yield DoWork ASAP.
+ bool quit_do_work = false;
+
+ // Number of tasks processed in a single DoWork invocation.
+ int batch_size = 1;
+
+ // Number of RunLoop layers currently running.
+ int run_depth = 0;
+
+ // Number of DoWork running, but only the inner-most one can take tasks.
+ // Must be equal to |run_depth| or |run_depth - 1|.
+ int do_work_depth = 0;
+ };
+
+ MainThreadOnly& main_thread_only() {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ return main_thread_only_;
+ }
+
+ // Returns true if there's a DoWork running on the inner-most nesting layer.
+ bool is_doing_work() const {
+ DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+ return main_thread_only_.do_work_depth == main_thread_only_.run_depth &&
+ main_thread_only_.do_work_depth != 0;
+ }
+
+ MainThreadOnly main_thread_only_;
+ const PlatformThreadId main_thread_id_;
+ std::unique_ptr<MessagePump> pump_;
+ debug::TaskAnnotator task_annotator_;
+ TickClock* time_source_; // Not owned.
+
+ THREAD_CHECKER(main_thread_checker_);
+ DISALLOW_COPY_AND_ASSIGN(ThreadControllerWithMessagePumpImpl);
+};
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_THREAD_CONTROLLER_WITH_MESSAGE_PUMP_IMPL_H_
diff --git a/base/task/sequence_manager/time_domain.cc b/base/task/sequence_manager/time_domain.cc
new file mode 100644
index 0000000000..8f47eb3a23
--- /dev/null
+++ b/base/task/sequence_manager/time_domain.cc
@@ -0,0 +1,136 @@
+// 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/task/sequence_manager/time_domain.h"
+
+#include "base/task/sequence_manager/sequence_manager_impl.h"
+#include "base/task/sequence_manager/task_queue_impl.h"
+#include "base/task/sequence_manager/work_queue.h"
+
+namespace base {
+namespace sequence_manager {
+
+TimeDomain::TimeDomain() : sequence_manager_(nullptr) {}
+
+TimeDomain::~TimeDomain() {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+}
+
+void TimeDomain::OnRegisterWithSequenceManager(
+ internal::SequenceManagerImpl* sequence_manager) {
+ DCHECK(sequence_manager);
+ DCHECK(!sequence_manager_);
+ sequence_manager_ = sequence_manager;
+}
+
+SequenceManager* TimeDomain::sequence_manager() const {
+ DCHECK(sequence_manager_);
+ return sequence_manager_;
+}
+
+// TODO(kraynov): https://crbug.com/857101 Consider making an interface
+// for SequenceManagerImpl which will expose SetNextDelayedDoWork and
+// MaybeScheduleImmediateWork methods to make the functions below pure-virtual.
+
+void TimeDomain::SetNextDelayedDoWork(LazyNow* lazy_now, TimeTicks run_time) {
+ sequence_manager_->SetNextDelayedDoWork(lazy_now, run_time);
+}
+
+void TimeDomain::RequestDoWork() {
+ sequence_manager_->MaybeScheduleImmediateWork(FROM_HERE);
+}
+
+void TimeDomain::UnregisterQueue(internal::TaskQueueImpl* queue) {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(queue->GetTimeDomain(), this);
+ LazyNow lazy_now(CreateLazyNow());
+ SetNextWakeUpForQueue(queue, nullopt, &lazy_now);
+}
+
+void TimeDomain::SetNextWakeUpForQueue(
+ internal::TaskQueueImpl* queue,
+ Optional<internal::TaskQueueImpl::DelayedWakeUp> wake_up,
+ LazyNow* lazy_now) {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(queue->GetTimeDomain(), this);
+ DCHECK(queue->IsQueueEnabled() || !wake_up);
+
+ Optional<TimeTicks> previous_wake_up;
+ if (!delayed_wake_up_queue_.empty())
+ previous_wake_up = delayed_wake_up_queue_.Min().wake_up.time;
+
+ if (wake_up) {
+ // Insert a new wake-up into the heap.
+ if (queue->heap_handle().IsValid()) {
+ // O(log n)
+ delayed_wake_up_queue_.ChangeKey(queue->heap_handle(),
+ {wake_up.value(), queue});
+ } else {
+ // O(log n)
+ delayed_wake_up_queue_.insert({wake_up.value(), queue});
+ }
+ } else {
+ // Remove a wake-up from heap if present.
+ if (queue->heap_handle().IsValid())
+ delayed_wake_up_queue_.erase(queue->heap_handle());
+ }
+
+ Optional<TimeTicks> new_wake_up;
+ if (!delayed_wake_up_queue_.empty())
+ new_wake_up = delayed_wake_up_queue_.Min().wake_up.time;
+
+ // TODO(kraynov): https://crbug.com/857101 Review the relationship with
+ // SequenceManager's time. Right now it's not an issue since
+ // VirtualTimeDomain doesn't invoke SequenceManager itself.
+
+ if (new_wake_up) {
+ if (new_wake_up != previous_wake_up) {
+ // Update the wake-up.
+ SetNextDelayedDoWork(lazy_now, new_wake_up.value());
+ }
+ } else {
+ if (previous_wake_up) {
+ // No new wake-up to be set, cancel the previous one.
+ SetNextDelayedDoWork(lazy_now, TimeTicks::Max());
+ }
+ }
+}
+
+void TimeDomain::WakeUpReadyDelayedQueues(LazyNow* lazy_now) {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+ // Wake up any queues with pending delayed work. Note std::multimap stores
+ // the elements sorted by key, so the begin() iterator points to the earliest
+ // queue to wake-up.
+ while (!delayed_wake_up_queue_.empty() &&
+ delayed_wake_up_queue_.Min().wake_up.time <= lazy_now->Now()) {
+ internal::TaskQueueImpl* queue = delayed_wake_up_queue_.Min().queue;
+ queue->WakeUpForDelayedWork(lazy_now);
+ }
+}
+
+Optional<TimeTicks> TimeDomain::NextScheduledRunTime() const {
+ DCHECK(main_thread_checker_.CalledOnValidThread());
+ if (delayed_wake_up_queue_.empty())
+ return nullopt;
+ return delayed_wake_up_queue_.Min().wake_up.time;
+}
+
+void TimeDomain::AsValueInto(trace_event::TracedValue* state) const {
+ state->BeginDictionary();
+ state->SetString("name", GetName());
+ state->SetInteger("registered_delay_count", delayed_wake_up_queue_.size());
+ if (!delayed_wake_up_queue_.empty()) {
+ TimeDelta delay = delayed_wake_up_queue_.Min().wake_up.time - Now();
+ state->SetDouble("next_delay_ms", delay.InMillisecondsF());
+ }
+ AsValueIntoInternal(state);
+ state->EndDictionary();
+}
+
+void TimeDomain::AsValueIntoInternal(trace_event::TracedValue* state) const {
+ // Can be overriden to trace some additional state.
+}
+
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/time_domain.h b/base/task/sequence_manager/time_domain.h
new file mode 100644
index 0000000000..e9e487bd40
--- /dev/null
+++ b/base/task/sequence_manager/time_domain.h
@@ -0,0 +1,139 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SEQUENCE_MANAGER_TIME_DOMAIN_H_
+#define BASE_TASK_SEQUENCE_MANAGER_TIME_DOMAIN_H_
+
+#include <map>
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/task/sequence_manager/intrusive_heap.h"
+#include "base/task/sequence_manager/lazy_now.h"
+#include "base/task/sequence_manager/task_queue_impl.h"
+#include "base/time/time.h"
+
+namespace base {
+namespace sequence_manager {
+
+class SequenceManager;
+
+namespace internal {
+class SequenceManagerImpl;
+class TaskQueueImpl;
+} // namespace internal
+
+// TimeDomain wakes up TaskQueues when their delayed tasks are due to run.
+// This class allows overrides to enable clock overriding on some TaskQueues
+// (e.g. auto-advancing virtual time, throttled clock, etc).
+//
+// TaskQueue maintains its own next wake-up time and communicates it
+// to the TimeDomain, which aggregates wake-ups across registered TaskQueues
+// into a global wake-up, which ultimately gets passed to the ThreadController.
+class BASE_EXPORT TimeDomain {
+ public:
+ virtual ~TimeDomain();
+
+ // Returns LazyNow in TimeDomain's time.
+ // Can be called from any thread.
+ // TODO(alexclarke): Make this main thread only.
+ virtual LazyNow CreateLazyNow() const = 0;
+
+ // Evaluates TimeDomain's time.
+ // Can be called from any thread.
+ // TODO(alexclarke): Make this main thread only.
+ virtual TimeTicks Now() const = 0;
+
+ // Computes the delay until the time when TimeDomain needs to wake up
+ // some TaskQueue. Specific time domains (e.g. virtual or throttled) may
+ // return TimeDelata() if TaskQueues have any delayed tasks they deem
+ // eligible to run. It's also allowed to advance time domains's internal
+ // clock when this method is called.
+ // Can be called from main thread only.
+ // NOTE: |lazy_now| and the return value are in the SequenceManager's time.
+ virtual Optional<TimeDelta> DelayTillNextTask(LazyNow* lazy_now) = 0;
+
+ void AsValueInto(trace_event::TracedValue* state) const;
+
+ protected:
+ TimeDomain();
+
+ SequenceManager* sequence_manager() const;
+
+ // Returns the earliest scheduled wake up in the TimeDomain's time.
+ Optional<TimeTicks> NextScheduledRunTime() const;
+
+ size_t NumberOfScheduledWakeUps() const {
+ return delayed_wake_up_queue_.size();
+ }
+
+ // Tells SequenceManager to schedule delayed work, use TimeTicks::Max()
+ // to unschedule. Also cancels any previous requests.
+ // May be overriden to control wake ups manually.
+ virtual void SetNextDelayedDoWork(LazyNow* lazy_now, TimeTicks run_time);
+
+ // Tells SequenceManager to schedule immediate work.
+ // May be overriden to control wake ups manually.
+ virtual void RequestDoWork();
+
+ // For implementation-specific tracing.
+ virtual void AsValueIntoInternal(trace_event::TracedValue* state) const;
+ virtual const char* GetName() const = 0;
+
+ private:
+ friend class internal::TaskQueueImpl;
+ friend class internal::SequenceManagerImpl;
+ friend class TestTimeDomain;
+
+ // Called when the TimeDomain is registered.
+ // TODO(kraynov): Pass SequenceManager in the constructor.
+ void OnRegisterWithSequenceManager(
+ internal::SequenceManagerImpl* sequence_manager);
+
+ // Schedule TaskQueue to wake up at certain time, repeating calls with
+ // the same |queue| invalidate previous requests.
+ // Nullopt |wake_up| cancels a previously set wake up for |queue|.
+ // NOTE: |lazy_now| is provided in TimeDomain's time.
+ void SetNextWakeUpForQueue(
+ internal::TaskQueueImpl* queue,
+ Optional<internal::TaskQueueImpl::DelayedWakeUp> wake_up,
+ LazyNow* lazy_now);
+
+ // Remove the TaskQueue from any internal data sctructures.
+ void UnregisterQueue(internal::TaskQueueImpl* queue);
+
+ // Wake up each TaskQueue where the delay has elapsed.
+ void WakeUpReadyDelayedQueues(LazyNow* lazy_now);
+
+ struct ScheduledDelayedWakeUp {
+ internal::TaskQueueImpl::DelayedWakeUp wake_up;
+ internal::TaskQueueImpl* queue;
+
+ bool operator<=(const ScheduledDelayedWakeUp& other) const {
+ return wake_up <= other.wake_up;
+ }
+
+ void SetHeapHandle(internal::HeapHandle handle) {
+ DCHECK(handle.IsValid());
+ queue->set_heap_handle(handle);
+ }
+
+ void ClearHeapHandle() {
+ DCHECK(queue->heap_handle().IsValid());
+ queue->set_heap_handle(internal::HeapHandle());
+ }
+ };
+
+ internal::SequenceManagerImpl* sequence_manager_; // Not owned.
+ internal::IntrusiveHeap<ScheduledDelayedWakeUp> delayed_wake_up_queue_;
+
+ ThreadChecker main_thread_checker_;
+ DISALLOW_COPY_AND_ASSIGN(TimeDomain);
+};
+
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_TIME_DOMAIN_H_
diff --git a/base/task/sequence_manager/time_domain_unittest.cc b/base/task/sequence_manager/time_domain_unittest.cc
new file mode 100644
index 0000000000..951314f5a4
--- /dev/null
+++ b/base/task/sequence_manager/time_domain_unittest.cc
@@ -0,0 +1,324 @@
+// 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/task/sequence_manager/time_domain.h"
+
+#include <memory>
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/task/sequence_manager/sequence_manager_impl.h"
+#include "base/task/sequence_manager/task_queue_impl.h"
+#include "base/task/sequence_manager/work_queue.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::_;
+using testing::AnyNumber;
+using testing::Mock;
+
+namespace base {
+namespace sequence_manager {
+
+class TaskQueueImplForTest : public internal::TaskQueueImpl {
+ public:
+ TaskQueueImplForTest(internal::SequenceManagerImpl* sequence_manager,
+ TimeDomain* time_domain,
+ const TaskQueue::Spec& spec)
+ : TaskQueueImpl(sequence_manager, time_domain, spec) {}
+ ~TaskQueueImplForTest() {}
+
+ using TaskQueueImpl::SetDelayedWakeUpForTesting;
+};
+
+class TestTimeDomain : public TimeDomain {
+ public:
+ TestTimeDomain() : now_(TimeTicks() + TimeDelta::FromSeconds(1)) {}
+
+ ~TestTimeDomain() override = default;
+
+ using TimeDomain::NextScheduledRunTime;
+ using TimeDomain::SetNextWakeUpForQueue;
+ using TimeDomain::UnregisterQueue;
+ using TimeDomain::WakeUpReadyDelayedQueues;
+
+ LazyNow CreateLazyNow() const override { return LazyNow(now_); }
+ TimeTicks Now() const override { return now_; }
+
+ Optional<TimeDelta> DelayTillNextTask(LazyNow* lazy_now) override {
+ return Optional<TimeDelta>();
+ }
+
+ void AsValueIntoInternal(trace_event::TracedValue* state) const override {}
+ const char* GetName() const override { return "Test"; }
+
+ internal::TaskQueueImpl* NextScheduledTaskQueue() const {
+ if (delayed_wake_up_queue_.empty())
+ return nullptr;
+ return delayed_wake_up_queue_.Min().queue;
+ }
+
+ MOCK_METHOD2(SetNextDelayedDoWork,
+ void(LazyNow* lazy_now, TimeTicks run_time));
+
+ void SetNow(TimeTicks now) { now_ = now; }
+
+ private:
+ TimeTicks now_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestTimeDomain);
+};
+
+class TimeDomainTest : public testing::Test {
+ public:
+ void SetUp() final {
+ time_domain_ = WrapUnique(CreateTestTimeDomain());
+ task_queue_ = std::make_unique<TaskQueueImplForTest>(
+ nullptr, time_domain_.get(), TaskQueue::Spec("test"));
+ }
+
+ void TearDown() final {
+ if (task_queue_)
+ task_queue_->UnregisterTaskQueue();
+ }
+
+ virtual TestTimeDomain* CreateTestTimeDomain() {
+ return new TestTimeDomain();
+ }
+
+ std::unique_ptr<TestTimeDomain> time_domain_;
+ std::unique_ptr<TaskQueueImplForTest> task_queue_;
+};
+
+TEST_F(TimeDomainTest, ScheduleWakeUpForQueue) {
+ TimeDelta delay = TimeDelta::FromMilliseconds(10);
+ TimeTicks delayed_runtime = time_domain_->Now() + delay;
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, delayed_runtime));
+ TimeTicks now = time_domain_->Now();
+ LazyNow lazy_now(now);
+ task_queue_->SetDelayedWakeUpForTesting(
+ internal::TaskQueueImpl::DelayedWakeUp{now + delay, 0});
+
+ EXPECT_EQ(delayed_runtime, time_domain_->NextScheduledRunTime());
+
+ EXPECT_EQ(task_queue_.get(), time_domain_->NextScheduledTaskQueue());
+ Mock::VerifyAndClearExpectations(time_domain_.get());
+
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, TimeTicks::Max()))
+ .Times(AnyNumber());
+}
+
+TEST_F(TimeDomainTest, ScheduleWakeUpForQueueSupersedesPreviousWakeUp) {
+ TimeDelta delay1 = TimeDelta::FromMilliseconds(10);
+ TimeDelta delay2 = TimeDelta::FromMilliseconds(100);
+ TimeTicks delayed_runtime1 = time_domain_->Now() + delay1;
+ TimeTicks delayed_runtime2 = time_domain_->Now() + delay2;
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, delayed_runtime1));
+ TimeTicks now = time_domain_->Now();
+ LazyNow lazy_now(now);
+ task_queue_->SetDelayedWakeUpForTesting(
+ internal::TaskQueueImpl::DelayedWakeUp{delayed_runtime1, 0});
+
+ EXPECT_EQ(delayed_runtime1, time_domain_->NextScheduledRunTime());
+
+ Mock::VerifyAndClearExpectations(time_domain_.get());
+
+ // Now schedule a later wake_up, which should replace the previously
+ // requested one.
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, delayed_runtime2));
+ task_queue_->SetDelayedWakeUpForTesting(
+ internal::TaskQueueImpl::DelayedWakeUp{delayed_runtime2, 0});
+
+ EXPECT_EQ(delayed_runtime2, time_domain_->NextScheduledRunTime());
+ Mock::VerifyAndClearExpectations(time_domain_.get());
+
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, TimeTicks::Max()))
+ .Times(AnyNumber());
+}
+
+TEST_F(TimeDomainTest, SetNextDelayedDoWork_OnlyCalledForEarlierTasks) {
+ std::unique_ptr<TaskQueueImplForTest> task_queue2 =
+ std::make_unique<TaskQueueImplForTest>(nullptr, time_domain_.get(),
+ TaskQueue::Spec("test"));
+
+ std::unique_ptr<TaskQueueImplForTest> task_queue3 =
+ std::make_unique<TaskQueueImplForTest>(nullptr, time_domain_.get(),
+ TaskQueue::Spec("test"));
+
+ std::unique_ptr<TaskQueueImplForTest> task_queue4 =
+ std::make_unique<TaskQueueImplForTest>(nullptr, time_domain_.get(),
+ TaskQueue::Spec("test"));
+
+ TimeDelta delay1 = TimeDelta::FromMilliseconds(10);
+ TimeDelta delay2 = TimeDelta::FromMilliseconds(20);
+ TimeDelta delay3 = TimeDelta::FromMilliseconds(30);
+ TimeDelta delay4 = TimeDelta::FromMilliseconds(1);
+
+ // SetNextDelayedDoWork should always be called if there are no other
+ // wake-ups.
+ TimeTicks now = time_domain_->Now();
+ LazyNow lazy_now(now);
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, now + delay1));
+ task_queue_->SetDelayedWakeUpForTesting(
+ internal::TaskQueueImpl::DelayedWakeUp{now + delay1, 0});
+
+ Mock::VerifyAndClearExpectations(time_domain_.get());
+
+ // SetNextDelayedDoWork should not be called when scheduling later tasks.
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, _)).Times(0);
+ task_queue2->SetDelayedWakeUpForTesting(
+ internal::TaskQueueImpl::DelayedWakeUp{now + delay2, 0});
+ task_queue3->SetDelayedWakeUpForTesting(
+ internal::TaskQueueImpl::DelayedWakeUp{now + delay3, 0});
+
+ // SetNextDelayedDoWork should be called when scheduling earlier tasks.
+ Mock::VerifyAndClearExpectations(time_domain_.get());
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, now + delay4));
+ task_queue4->SetDelayedWakeUpForTesting(
+ internal::TaskQueueImpl::DelayedWakeUp{now + delay4, 0});
+
+ Mock::VerifyAndClearExpectations(time_domain_.get());
+
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, _)).Times(2);
+ task_queue2->UnregisterTaskQueue();
+ task_queue3->UnregisterTaskQueue();
+ task_queue4->UnregisterTaskQueue();
+}
+
+TEST_F(TimeDomainTest, UnregisterQueue) {
+ std::unique_ptr<TaskQueueImplForTest> task_queue2_ =
+ std::make_unique<TaskQueueImplForTest>(nullptr, time_domain_.get(),
+ TaskQueue::Spec("test"));
+
+ TimeTicks now = time_domain_->Now();
+ LazyNow lazy_now(now);
+ TimeTicks wake_up1 = now + TimeDelta::FromMilliseconds(10);
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, wake_up1)).Times(1);
+ task_queue_->SetDelayedWakeUpForTesting(
+ internal::TaskQueueImpl::DelayedWakeUp{wake_up1, 0});
+ TimeTicks wake_up2 = now + TimeDelta::FromMilliseconds(100);
+ task_queue2_->SetDelayedWakeUpForTesting(
+ internal::TaskQueueImpl::DelayedWakeUp{wake_up2, 0});
+
+ EXPECT_EQ(task_queue_.get(), time_domain_->NextScheduledTaskQueue());
+
+ testing::Mock::VerifyAndClearExpectations(time_domain_.get());
+
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, wake_up2)).Times(1);
+
+ time_domain_->UnregisterQueue(task_queue_.get());
+ task_queue_ = std::unique_ptr<TaskQueueImplForTest>();
+ EXPECT_EQ(task_queue2_.get(), time_domain_->NextScheduledTaskQueue());
+
+ testing::Mock::VerifyAndClearExpectations(time_domain_.get());
+
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, TimeTicks::Max()))
+ .Times(1);
+
+ time_domain_->UnregisterQueue(task_queue2_.get());
+ EXPECT_FALSE(time_domain_->NextScheduledTaskQueue());
+}
+
+TEST_F(TimeDomainTest, WakeUpReadyDelayedQueues) {
+ TimeDelta delay = TimeDelta::FromMilliseconds(50);
+ TimeTicks now = time_domain_->Now();
+ LazyNow lazy_now_1(now);
+ TimeTicks delayed_runtime = now + delay;
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, delayed_runtime));
+ task_queue_->SetDelayedWakeUpForTesting(
+ internal::TaskQueueImpl::DelayedWakeUp{delayed_runtime, 0});
+
+ EXPECT_EQ(delayed_runtime, time_domain_->NextScheduledRunTime());
+
+ time_domain_->WakeUpReadyDelayedQueues(&lazy_now_1);
+ EXPECT_EQ(delayed_runtime, time_domain_->NextScheduledRunTime());
+
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, TimeTicks::Max()));
+ time_domain_->SetNow(delayed_runtime);
+ LazyNow lazy_now_2(time_domain_->CreateLazyNow());
+ time_domain_->WakeUpReadyDelayedQueues(&lazy_now_2);
+ ASSERT_FALSE(time_domain_->NextScheduledRunTime());
+}
+
+TEST_F(TimeDomainTest, WakeUpReadyDelayedQueuesWithIdenticalRuntimes) {
+ int sequence_num = 0;
+ TimeDelta delay = TimeDelta::FromMilliseconds(50);
+ TimeTicks now = time_domain_->Now();
+ LazyNow lazy_now(now);
+ TimeTicks delayed_runtime = now + delay;
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, delayed_runtime));
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, TimeTicks::Max()));
+
+ std::unique_ptr<TaskQueueImplForTest> task_queue2 =
+ std::make_unique<TaskQueueImplForTest>(nullptr, time_domain_.get(),
+ TaskQueue::Spec("test"));
+
+ task_queue2->SetDelayedWakeUpForTesting(
+ internal::TaskQueueImpl::DelayedWakeUp{delayed_runtime, ++sequence_num});
+ task_queue_->SetDelayedWakeUpForTesting(
+ internal::TaskQueueImpl::DelayedWakeUp{delayed_runtime, ++sequence_num});
+
+ time_domain_->WakeUpReadyDelayedQueues(&lazy_now);
+
+ // The second task queue should wake up first since it has a lower sequence
+ // number.
+ EXPECT_EQ(task_queue2.get(), time_domain_->NextScheduledTaskQueue());
+
+ task_queue2->UnregisterTaskQueue();
+}
+
+TEST_F(TimeDomainTest, CancelDelayedWork) {
+ TimeTicks now = time_domain_->Now();
+ LazyNow lazy_now(now);
+ TimeTicks run_time = now + TimeDelta::FromMilliseconds(20);
+
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, run_time));
+ task_queue_->SetDelayedWakeUpForTesting(
+ internal::TaskQueueImpl::DelayedWakeUp{run_time, 0});
+
+ EXPECT_EQ(task_queue_.get(), time_domain_->NextScheduledTaskQueue());
+
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, TimeTicks::Max()));
+ task_queue_->SetDelayedWakeUpForTesting(nullopt);
+ EXPECT_FALSE(time_domain_->NextScheduledTaskQueue());
+}
+
+TEST_F(TimeDomainTest, CancelDelayedWork_TwoQueues) {
+ std::unique_ptr<TaskQueueImplForTest> task_queue2 =
+ std::make_unique<TaskQueueImplForTest>(nullptr, time_domain_.get(),
+ TaskQueue::Spec("test"));
+
+ TimeTicks now = time_domain_->Now();
+ LazyNow lazy_now(now);
+ TimeTicks run_time1 = now + TimeDelta::FromMilliseconds(20);
+ TimeTicks run_time2 = now + TimeDelta::FromMilliseconds(40);
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, run_time1));
+ task_queue_->SetDelayedWakeUpForTesting(
+ internal::TaskQueueImpl::DelayedWakeUp{run_time1, 0});
+ Mock::VerifyAndClearExpectations(time_domain_.get());
+
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, _)).Times(0);
+ task_queue2->SetDelayedWakeUpForTesting(
+ internal::TaskQueueImpl::DelayedWakeUp{run_time2, 0});
+ Mock::VerifyAndClearExpectations(time_domain_.get());
+
+ EXPECT_EQ(task_queue_.get(), time_domain_->NextScheduledTaskQueue());
+
+ EXPECT_EQ(run_time1, time_domain_->NextScheduledRunTime());
+
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, run_time2));
+ task_queue_->SetDelayedWakeUpForTesting(nullopt);
+ EXPECT_EQ(task_queue2.get(), time_domain_->NextScheduledTaskQueue());
+
+ EXPECT_EQ(run_time2, time_domain_->NextScheduledRunTime());
+
+ Mock::VerifyAndClearExpectations(time_domain_.get());
+ EXPECT_CALL(*time_domain_.get(), SetNextDelayedDoWork(_, _))
+ .Times(AnyNumber());
+
+ // Tidy up.
+ task_queue2->UnregisterTaskQueue();
+}
+
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/work_queue.cc b/base/task/sequence_manager/work_queue.cc
new file mode 100644
index 0000000000..4d95f4b773
--- /dev/null
+++ b/base/task/sequence_manager/work_queue.cc
@@ -0,0 +1,236 @@
+// 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/task/sequence_manager/work_queue.h"
+
+#include "base/task/sequence_manager/work_queue_sets.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+WorkQueue::WorkQueue(TaskQueueImpl* task_queue,
+ const char* name,
+ QueueType queue_type)
+ : task_queue_(task_queue), name_(name), queue_type_(queue_type) {}
+
+void WorkQueue::AsValueInto(TimeTicks now,
+ trace_event::TracedValue* state) const {
+ for (const TaskQueueImpl::Task& task : tasks_) {
+ TaskQueueImpl::TaskAsValueInto(task, now, state);
+ }
+}
+
+WorkQueue::~WorkQueue() {
+ DCHECK(!work_queue_sets_) << task_queue_->GetName() << " : "
+ << work_queue_sets_->GetName() << " : " << name_;
+}
+
+const TaskQueueImpl::Task* WorkQueue::GetFrontTask() const {
+ if (tasks_.empty())
+ return nullptr;
+ return &tasks_.front();
+}
+
+const TaskQueueImpl::Task* WorkQueue::GetBackTask() const {
+ if (tasks_.empty())
+ return nullptr;
+ return &tasks_.back();
+}
+
+bool WorkQueue::BlockedByFence() const {
+ if (!fence_)
+ return false;
+
+ // If the queue is empty then any future tasks will have a higher enqueue
+ // order and will be blocked. The queue is also blocked if the head is past
+ // the fence.
+ return tasks_.empty() || tasks_.front().enqueue_order() >= fence_;
+}
+
+bool WorkQueue::GetFrontTaskEnqueueOrder(EnqueueOrder* enqueue_order) const {
+ if (tasks_.empty() || BlockedByFence())
+ return false;
+ // Quick sanity check.
+ DCHECK_LE(tasks_.front().enqueue_order(), tasks_.back().enqueue_order())
+ << task_queue_->GetName() << " : " << work_queue_sets_->GetName() << " : "
+ << name_;
+ *enqueue_order = tasks_.front().enqueue_order();
+ return true;
+}
+
+void WorkQueue::Push(TaskQueueImpl::Task task) {
+ bool was_empty = tasks_.empty();
+#ifndef NDEBUG
+ DCHECK(task.enqueue_order_set());
+#endif
+
+ // Make sure the |enqueue_order()| is monotonically increasing.
+ DCHECK(was_empty || tasks_.rbegin()->enqueue_order() < task.enqueue_order());
+
+ // Amoritized O(1).
+ tasks_.push_back(std::move(task));
+
+ if (!was_empty)
+ return;
+
+ // If we hit the fence, pretend to WorkQueueSets that we're empty.
+ if (work_queue_sets_ && !BlockedByFence())
+ work_queue_sets_->OnTaskPushedToEmptyQueue(this);
+}
+
+void WorkQueue::PushNonNestableTaskToFront(TaskQueueImpl::Task task) {
+ DCHECK(task.nestable == Nestable::kNonNestable);
+
+ bool was_empty = tasks_.empty();
+ bool was_blocked = BlockedByFence();
+#ifndef NDEBUG
+ DCHECK(task.enqueue_order_set());
+#endif
+
+ if (!was_empty) {
+ // Make sure the |enqueue_order()| is monotonically increasing.
+ DCHECK_LE(task.enqueue_order(), tasks_.front().enqueue_order())
+ << task_queue_->GetName() << " : " << work_queue_sets_->GetName()
+ << " : " << name_;
+ }
+
+ // Amoritized O(1).
+ tasks_.push_front(std::move(task));
+
+ if (!work_queue_sets_)
+ return;
+
+ // Pretend to WorkQueueSets that nothing has changed if we're blocked.
+ if (BlockedByFence())
+ return;
+
+ // Pushing task to front may unblock the fence.
+ if (was_empty || was_blocked) {
+ work_queue_sets_->OnTaskPushedToEmptyQueue(this);
+ } else {
+ work_queue_sets_->OnFrontTaskChanged(this);
+ }
+}
+
+void WorkQueue::ReloadEmptyImmediateQueue() {
+ DCHECK(tasks_.empty());
+
+ task_queue_->ReloadEmptyImmediateQueue(&tasks_);
+ if (tasks_.empty())
+ return;
+
+ // If we hit the fence, pretend to WorkQueueSets that we're empty.
+ if (work_queue_sets_ && !BlockedByFence())
+ work_queue_sets_->OnTaskPushedToEmptyQueue(this);
+}
+
+TaskQueueImpl::Task WorkQueue::TakeTaskFromWorkQueue() {
+ DCHECK(work_queue_sets_);
+ DCHECK(!tasks_.empty());
+
+ TaskQueueImpl::Task pending_task = std::move(tasks_.front());
+ tasks_.pop_front();
+ // NB immediate tasks have a different pipeline to delayed ones.
+ if (queue_type_ == QueueType::kImmediate && tasks_.empty()) {
+ // Short-circuit the queue reload so that OnPopQueue does the right thing.
+ task_queue_->ReloadEmptyImmediateQueue(&tasks_);
+ }
+
+ // OnPopQueue calls GetFrontTaskEnqueueOrder which checks BlockedByFence() so
+ // we don't need to here.
+ work_queue_sets_->OnPopQueue(this);
+ task_queue_->TraceQueueSize();
+ return pending_task;
+}
+
+bool WorkQueue::RemoveAllCanceledTasksFromFront() {
+ DCHECK(work_queue_sets_);
+ bool task_removed = false;
+ while (!tasks_.empty() &&
+ (!tasks_.front().task || tasks_.front().task.IsCancelled())) {
+ tasks_.pop_front();
+ task_removed = true;
+ }
+ if (task_removed) {
+ // NB immediate tasks have a different pipeline to delayed ones.
+ if (queue_type_ == QueueType::kImmediate && tasks_.empty()) {
+ // Short-circuit the queue reload so that OnPopQueue does the right thing.
+ task_queue_->ReloadEmptyImmediateQueue(&tasks_);
+ }
+ work_queue_sets_->OnPopQueue(this);
+ task_queue_->TraceQueueSize();
+ }
+ return task_removed;
+}
+
+void WorkQueue::AssignToWorkQueueSets(WorkQueueSets* work_queue_sets) {
+ work_queue_sets_ = work_queue_sets;
+}
+
+void WorkQueue::AssignSetIndex(size_t work_queue_set_index) {
+ work_queue_set_index_ = work_queue_set_index;
+}
+
+bool WorkQueue::InsertFenceImpl(EnqueueOrder fence) {
+ DCHECK_NE(fence, 0u);
+ DCHECK(fence >= fence_ || fence == EnqueueOrder::blocking_fence());
+ bool was_blocked_by_fence = BlockedByFence();
+ fence_ = fence;
+ return was_blocked_by_fence;
+}
+
+void WorkQueue::InsertFenceSilently(EnqueueOrder fence) {
+ // Ensure that there is no fence present or a new one blocks queue completely.
+ DCHECK(!fence_ || fence_ == EnqueueOrder::blocking_fence());
+ InsertFenceImpl(fence);
+}
+
+bool WorkQueue::InsertFence(EnqueueOrder fence) {
+ bool was_blocked_by_fence = InsertFenceImpl(fence);
+
+ // Moving the fence forward may unblock some tasks.
+ if (work_queue_sets_ && !tasks_.empty() && was_blocked_by_fence &&
+ !BlockedByFence()) {
+ work_queue_sets_->OnTaskPushedToEmptyQueue(this);
+ return true;
+ }
+ // Fence insertion may have blocked all tasks in this work queue.
+ if (BlockedByFence())
+ work_queue_sets_->OnQueueBlocked(this);
+ return false;
+}
+
+bool WorkQueue::RemoveFence() {
+ bool was_blocked_by_fence = BlockedByFence();
+ fence_ = EnqueueOrder::none();
+ if (work_queue_sets_ && !tasks_.empty() && was_blocked_by_fence) {
+ work_queue_sets_->OnTaskPushedToEmptyQueue(this);
+ return true;
+ }
+ return false;
+}
+
+bool WorkQueue::ShouldRunBefore(const WorkQueue* other_queue) const {
+ DCHECK(!tasks_.empty());
+ DCHECK(!other_queue->tasks_.empty());
+ EnqueueOrder enqueue_order;
+ EnqueueOrder other_enqueue_order;
+ bool have_task = GetFrontTaskEnqueueOrder(&enqueue_order);
+ bool have_other_task =
+ other_queue->GetFrontTaskEnqueueOrder(&other_enqueue_order);
+ DCHECK(have_task);
+ DCHECK(have_other_task);
+ return enqueue_order < other_enqueue_order;
+}
+
+void WorkQueue::PopTaskForTesting() {
+ if (tasks_.empty())
+ return;
+ tasks_.pop_front();
+}
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/work_queue.h b/base/task/sequence_manager/work_queue.h
new file mode 100644
index 0000000000..5197949c50
--- /dev/null
+++ b/base/task/sequence_manager/work_queue.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 BASE_TASK_SEQUENCE_MANAGER_WORK_QUEUE_H_
+#define BASE_TASK_SEQUENCE_MANAGER_WORK_QUEUE_H_
+
+#include "base/base_export.h"
+#include "base/task/sequence_manager/enqueue_order.h"
+#include "base/task/sequence_manager/intrusive_heap.h"
+#include "base/task/sequence_manager/sequenced_task_source.h"
+#include "base/task/sequence_manager/task_queue_impl.h"
+#include "base/trace_event/trace_event.h"
+#include "base/trace_event/trace_event_argument.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+class WorkQueueSets;
+
+// This class keeps track of immediate and delayed tasks which are due to run
+// now. It interfaces deeply with WorkQueueSets which keeps track of which queue
+// (with a given priority) contains the oldest task.
+//
+// If a fence is inserted, WorkQueue behaves normally up until
+// TakeTaskFromWorkQueue reaches or exceeds the fence. At that point it the
+// API subset used by WorkQueueSets pretends the WorkQueue is empty until the
+// fence is removed. This functionality is a primitive intended for use by
+// throttling mechanisms.
+class BASE_EXPORT WorkQueue {
+ public:
+ using QueueType = internal::TaskQueueImpl::WorkQueueType;
+
+ // Note |task_queue| can be null if queue_type is kNonNestable.
+ WorkQueue(TaskQueueImpl* task_queue, const char* name, QueueType queue_type);
+ ~WorkQueue();
+
+ // Associates this work queue with the given work queue sets. This must be
+ // called before any tasks can be inserted into this work queue.
+ void AssignToWorkQueueSets(WorkQueueSets* work_queue_sets);
+
+ // Assigns the current set index.
+ void AssignSetIndex(size_t work_queue_set_index);
+
+ void AsValueInto(TimeTicks now, trace_event::TracedValue* state) const;
+
+ // Returns true if the |tasks_| is empty. This method ignores any fences.
+ bool Empty() const { return tasks_.empty(); }
+
+ // If the |tasks_| isn't empty and a fence hasn't been reached,
+ // |enqueue_order| gets set to the enqueue order of the front task and the
+ // function returns true. Otherwise the function returns false.
+ bool GetFrontTaskEnqueueOrder(EnqueueOrder* enqueue_order) const;
+
+ // Returns the first task in this queue or null if the queue is empty. This
+ // method ignores any fences.
+ const TaskQueueImpl::Task* GetFrontTask() const;
+
+ // Returns the last task in this queue or null if the queue is empty. This
+ // method ignores any fences.
+ const TaskQueueImpl::Task* GetBackTask() const;
+
+ // Pushes the task onto the |tasks_| and if a fence hasn't been reached
+ // it informs the WorkQueueSets if the head changed.
+ void Push(TaskQueueImpl::Task task);
+
+ // Pushes the task onto the front of the |tasks_| and if it's before any
+ // fence it informs the WorkQueueSets the head changed. Use with caution this
+ // API can easily lead to task starvation if misused.
+ void PushNonNestableTaskToFront(TaskQueueImpl::Task task);
+
+ // Reloads the empty |tasks_| with
+ // |task_queue_->TakeImmediateIncomingQueue| and if a fence hasn't been
+ // reached it informs the WorkQueueSets if the head changed.
+ void ReloadEmptyImmediateQueue();
+
+ size_t Size() const { return tasks_.size(); }
+
+ // Pulls a task off the |tasks_| and informs the WorkQueueSets. If the
+ // task removed had an enqueue order >= the current fence then WorkQueue
+ // pretends to be empty as far as the WorkQueueSets is concerned.
+ TaskQueueImpl::Task TakeTaskFromWorkQueue();
+
+ // Removes all canceled tasks from the head of the list. Returns true if any
+ // tasks were removed.
+ bool RemoveAllCanceledTasksFromFront();
+
+ const char* name() const { return name_; }
+
+ TaskQueueImpl* task_queue() const { return task_queue_; }
+
+ WorkQueueSets* work_queue_sets() const { return work_queue_sets_; }
+
+ size_t work_queue_set_index() const { return work_queue_set_index_; }
+
+ HeapHandle heap_handle() const { return heap_handle_; }
+
+ void set_heap_handle(HeapHandle handle) { heap_handle_ = handle; }
+
+ QueueType queue_type() const { return queue_type_; }
+
+ // Returns true if the front task in this queue has an older enqueue order
+ // than the front task of |other_queue|. Both queue are assumed to be
+ // non-empty. This method ignores any fences.
+ bool ShouldRunBefore(const WorkQueue* other_queue) const;
+
+ // Submit a fence. When TakeTaskFromWorkQueue encounters a task whose
+ // enqueue_order is >= |fence| then the WorkQueue will start pretending to be.
+ // empty.
+ // Inserting a fence may supersede a previous one and unblock some tasks.
+ // Returns true if any tasks where unblocked, returns false otherwise.
+ bool InsertFence(EnqueueOrder fence);
+
+ // Submit a fence without triggering a WorkQueueSets notification.
+ // Caller must ensure that WorkQueueSets are properly updated.
+ // This method should not be called when a fence is already present.
+ void InsertFenceSilently(EnqueueOrder fence);
+
+ // Removes any fences that where added and if WorkQueue was pretending to be
+ // empty, then the real value is reported to WorkQueueSets. Returns true if
+ // any tasks where unblocked.
+ bool RemoveFence();
+
+ // Returns true if any tasks are blocked by the fence. Returns true if the
+ // queue is empty and fence has been set (i.e. future tasks would be blocked).
+ // Otherwise returns false.
+ bool BlockedByFence() const;
+
+ // Test support function. This should not be used in production code.
+ void PopTaskForTesting();
+
+ private:
+ bool InsertFenceImpl(EnqueueOrder fence);
+
+ TaskQueueImpl::TaskDeque tasks_;
+ WorkQueueSets* work_queue_sets_ = nullptr; // NOT OWNED.
+ TaskQueueImpl* const task_queue_; // NOT OWNED.
+ size_t work_queue_set_index_ = 0;
+ HeapHandle heap_handle_;
+ const char* const name_;
+ EnqueueOrder fence_;
+ const QueueType queue_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkQueue);
+};
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_WORK_QUEUE_H_
diff --git a/base/task/sequence_manager/work_queue_sets.cc b/base/task/sequence_manager/work_queue_sets.cc
new file mode 100644
index 0000000000..e56fc82e0b
--- /dev/null
+++ b/base/task/sequence_manager/work_queue_sets.cc
@@ -0,0 +1,172 @@
+// 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/task/sequence_manager/work_queue_sets.h"
+
+#include "base/logging.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+WorkQueueSets::WorkQueueSets(size_t num_sets, const char* name)
+ : work_queue_heaps_(num_sets), name_(name) {}
+
+WorkQueueSets::~WorkQueueSets() = default;
+
+void WorkQueueSets::AddQueue(WorkQueue* work_queue, size_t set_index) {
+ DCHECK(!work_queue->work_queue_sets());
+ DCHECK_LT(set_index, work_queue_heaps_.size());
+ EnqueueOrder enqueue_order;
+ bool has_enqueue_order = work_queue->GetFrontTaskEnqueueOrder(&enqueue_order);
+ work_queue->AssignToWorkQueueSets(this);
+ work_queue->AssignSetIndex(set_index);
+ if (!has_enqueue_order)
+ return;
+ work_queue_heaps_[set_index].insert({enqueue_order, work_queue});
+}
+
+void WorkQueueSets::RemoveQueue(WorkQueue* work_queue) {
+ DCHECK_EQ(this, work_queue->work_queue_sets());
+ work_queue->AssignToWorkQueueSets(nullptr);
+ HeapHandle heap_handle = work_queue->heap_handle();
+ if (!heap_handle.IsValid())
+ return;
+ size_t set_index = work_queue->work_queue_set_index();
+ DCHECK_LT(set_index, work_queue_heaps_.size());
+ work_queue_heaps_[set_index].erase(heap_handle);
+}
+
+void WorkQueueSets::ChangeSetIndex(WorkQueue* work_queue, size_t set_index) {
+ DCHECK_EQ(this, work_queue->work_queue_sets());
+ DCHECK_LT(set_index, work_queue_heaps_.size());
+ EnqueueOrder enqueue_order;
+ bool has_enqueue_order = work_queue->GetFrontTaskEnqueueOrder(&enqueue_order);
+ size_t old_set = work_queue->work_queue_set_index();
+ DCHECK_LT(old_set, work_queue_heaps_.size());
+ DCHECK_NE(old_set, set_index);
+ work_queue->AssignSetIndex(set_index);
+ if (!has_enqueue_order)
+ return;
+ work_queue_heaps_[old_set].erase(work_queue->heap_handle());
+ work_queue_heaps_[set_index].insert({enqueue_order, work_queue});
+}
+
+void WorkQueueSets::OnFrontTaskChanged(WorkQueue* work_queue) {
+ EnqueueOrder enqueue_order;
+ bool has_enqueue_order = work_queue->GetFrontTaskEnqueueOrder(&enqueue_order);
+ DCHECK(has_enqueue_order);
+ size_t set = work_queue->work_queue_set_index();
+ work_queue_heaps_[set].ChangeKey(work_queue->heap_handle(),
+ {enqueue_order, work_queue});
+}
+
+void WorkQueueSets::OnTaskPushedToEmptyQueue(WorkQueue* work_queue) {
+ // NOTE if this function changes, we need to keep |WorkQueueSets::AddQueue| in
+ // sync.
+ DCHECK_EQ(this, work_queue->work_queue_sets());
+ EnqueueOrder enqueue_order;
+ bool has_enqueue_order = work_queue->GetFrontTaskEnqueueOrder(&enqueue_order);
+ DCHECK(has_enqueue_order);
+ size_t set_index = work_queue->work_queue_set_index();
+ DCHECK_LT(set_index, work_queue_heaps_.size())
+ << " set_index = " << set_index;
+ // |work_queue| should not be in work_queue_heaps_[set_index].
+ DCHECK(!work_queue->heap_handle().IsValid());
+ work_queue_heaps_[set_index].insert({enqueue_order, work_queue});
+}
+
+void WorkQueueSets::OnPopQueue(WorkQueue* work_queue) {
+ // Assume that |work_queue| contains the lowest enqueue_order.
+ size_t set_index = work_queue->work_queue_set_index();
+ DCHECK_EQ(this, work_queue->work_queue_sets());
+ DCHECK_LT(set_index, work_queue_heaps_.size());
+ DCHECK(!work_queue_heaps_[set_index].empty()) << " set_index = " << set_index;
+ DCHECK_EQ(work_queue_heaps_[set_index].Min().value, work_queue)
+ << " set_index = " << set_index;
+ DCHECK(work_queue->heap_handle().IsValid());
+ EnqueueOrder enqueue_order;
+ if (work_queue->GetFrontTaskEnqueueOrder(&enqueue_order)) {
+ // O(log n)
+ work_queue_heaps_[set_index].ReplaceMin({enqueue_order, work_queue});
+ } else {
+ // O(log n)
+ work_queue_heaps_[set_index].Pop();
+ DCHECK(work_queue_heaps_[set_index].empty() ||
+ work_queue_heaps_[set_index].Min().value != work_queue);
+ }
+}
+
+void WorkQueueSets::OnQueueBlocked(WorkQueue* work_queue) {
+ DCHECK_EQ(this, work_queue->work_queue_sets());
+ HeapHandle heap_handle = work_queue->heap_handle();
+ if (!heap_handle.IsValid())
+ return;
+ size_t set_index = work_queue->work_queue_set_index();
+ DCHECK_LT(set_index, work_queue_heaps_.size());
+ work_queue_heaps_[set_index].erase(heap_handle);
+}
+
+bool WorkQueueSets::GetOldestQueueInSet(size_t set_index,
+ WorkQueue** out_work_queue) const {
+ DCHECK_LT(set_index, work_queue_heaps_.size());
+ if (work_queue_heaps_[set_index].empty())
+ return false;
+ *out_work_queue = work_queue_heaps_[set_index].Min().value;
+ DCHECK_EQ(set_index, (*out_work_queue)->work_queue_set_index());
+ DCHECK((*out_work_queue)->heap_handle().IsValid());
+ return true;
+}
+
+bool WorkQueueSets::GetOldestQueueAndEnqueueOrderInSet(
+ size_t set_index,
+ WorkQueue** out_work_queue,
+ EnqueueOrder* out_enqueue_order) const {
+ DCHECK_LT(set_index, work_queue_heaps_.size());
+ if (work_queue_heaps_[set_index].empty())
+ return false;
+ const OldestTaskEnqueueOrder& oldest = work_queue_heaps_[set_index].Min();
+ *out_work_queue = oldest.value;
+ *out_enqueue_order = oldest.key;
+ EnqueueOrder enqueue_order;
+ DCHECK(oldest.value->GetFrontTaskEnqueueOrder(&enqueue_order) &&
+ oldest.key == enqueue_order);
+ return true;
+}
+
+bool WorkQueueSets::IsSetEmpty(size_t set_index) const {
+ DCHECK_LT(set_index, work_queue_heaps_.size())
+ << " set_index = " << set_index;
+ return work_queue_heaps_[set_index].empty();
+}
+
+#if DCHECK_IS_ON() || !defined(NDEBUG)
+bool WorkQueueSets::ContainsWorkQueueForTest(
+ const WorkQueue* work_queue) const {
+ EnqueueOrder enqueue_order;
+ bool has_enqueue_order = work_queue->GetFrontTaskEnqueueOrder(&enqueue_order);
+
+ for (const IntrusiveHeap<OldestTaskEnqueueOrder>& heap : work_queue_heaps_) {
+ for (const OldestTaskEnqueueOrder& heap_value_pair : heap) {
+ if (heap_value_pair.value == work_queue) {
+ DCHECK(has_enqueue_order);
+ DCHECK_EQ(heap_value_pair.key, enqueue_order);
+ DCHECK_EQ(this, work_queue->work_queue_sets());
+ return true;
+ }
+ }
+ }
+
+ if (work_queue->work_queue_sets() == this) {
+ DCHECK(!has_enqueue_order);
+ return true;
+ }
+
+ return false;
+}
+#endif
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/work_queue_sets.h b/base/task/sequence_manager/work_queue_sets.h
new file mode 100644
index 0000000000..01db04084c
--- /dev/null
+++ b/base/task/sequence_manager/work_queue_sets.h
@@ -0,0 +1,102 @@
+// 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_TASK_SEQUENCE_MANAGER_WORK_QUEUE_SETS_H_
+#define BASE_TASK_SEQUENCE_MANAGER_WORK_QUEUE_SETS_H_
+
+#include <map>
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/task/sequence_manager/intrusive_heap.h"
+#include "base/task/sequence_manager/task_queue_impl.h"
+#include "base/task/sequence_manager/work_queue.h"
+#include "base/trace_event/trace_event_argument.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+// There is a WorkQueueSet for each scheduler priority and each WorkQueueSet
+// uses a EnqueueOrderToWorkQueueMap to keep track of which queue in the set has
+// the oldest task (i.e. the one that should be run next if the
+// TaskQueueSelector chooses to run a task a given priority). The reason this
+// works is because std::map is a tree based associative container and all the
+// values are kept in sorted order.
+class BASE_EXPORT WorkQueueSets {
+ public:
+ WorkQueueSets(size_t num_sets, const char* name);
+ ~WorkQueueSets();
+
+ // O(log num queues)
+ void AddQueue(WorkQueue* queue, size_t set_index);
+
+ // O(log num queues)
+ void RemoveQueue(WorkQueue* work_queue);
+
+ // O(log num queues)
+ void ChangeSetIndex(WorkQueue* queue, size_t set_index);
+
+ // O(log num queues)
+ void OnFrontTaskChanged(WorkQueue* queue);
+
+ // O(log num queues)
+ void OnTaskPushedToEmptyQueue(WorkQueue* work_queue);
+
+ // If empty it's O(1) amortized, otherwise it's O(log num queues)
+ // Assumes |work_queue| contains the lowest enqueue order in the set.
+ void OnPopQueue(WorkQueue* work_queue);
+
+ // O(log num queues)
+ void OnQueueBlocked(WorkQueue* work_queue);
+
+ // O(1)
+ bool GetOldestQueueInSet(size_t set_index, WorkQueue** out_work_queue) const;
+
+ // O(1)
+ bool GetOldestQueueAndEnqueueOrderInSet(
+ size_t set_index,
+ WorkQueue** out_work_queue,
+ EnqueueOrder* out_enqueue_order) const;
+
+ // O(1)
+ bool IsSetEmpty(size_t set_index) const;
+
+#if DCHECK_IS_ON() || !defined(NDEBUG)
+ // Note this iterates over everything in |work_queue_heaps_|.
+ // It's intended for use with DCHECKS and for testing
+ bool ContainsWorkQueueForTest(const WorkQueue* queue) const;
+#endif
+
+ const char* GetName() const { return name_; }
+
+ private:
+ struct OldestTaskEnqueueOrder {
+ EnqueueOrder key;
+ WorkQueue* value;
+
+ bool operator<=(const OldestTaskEnqueueOrder& other) const {
+ return key <= other.key;
+ }
+
+ void SetHeapHandle(HeapHandle handle) { value->set_heap_handle(handle); }
+
+ void ClearHeapHandle() { value->set_heap_handle(HeapHandle()); }
+ };
+
+ // For each set |work_queue_heaps_| has a queue of WorkQueue ordered by the
+ // oldest task in each WorkQueue.
+ std::vector<IntrusiveHeap<OldestTaskEnqueueOrder>> work_queue_heaps_;
+ const char* const name_;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkQueueSets);
+};
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
+
+#endif // BASE_TASK_SEQUENCE_MANAGER_WORK_QUEUE_SETS_H_
diff --git a/base/task/sequence_manager/work_queue_sets_unittest.cc b/base/task/sequence_manager/work_queue_sets_unittest.cc
new file mode 100644
index 0000000000..b849eec079
--- /dev/null
+++ b/base/task/sequence_manager/work_queue_sets_unittest.cc
@@ -0,0 +1,328 @@
+// 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/task/sequence_manager/work_queue_sets.h"
+
+#include <stddef.h>
+
+#include "base/memory/ptr_util.h"
+#include "base/task/sequence_manager/work_queue.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace base {
+namespace sequence_manager {
+
+class TimeDomain;
+
+namespace internal {
+
+class WorkQueueSetsTest : public testing::Test {
+ public:
+ void SetUp() override {
+ work_queue_sets_.reset(new WorkQueueSets(kNumSets, "test"));
+ }
+
+ void TearDown() override {
+ for (std::unique_ptr<WorkQueue>& work_queue : work_queues_) {
+ if (work_queue->work_queue_sets())
+ work_queue_sets_->RemoveQueue(work_queue.get());
+ }
+ }
+
+ protected:
+ enum {
+ kNumSets = 5 // An arbitary choice.
+ };
+
+ WorkQueue* NewTaskQueue(const char* queue_name) {
+ WorkQueue* queue =
+ new WorkQueue(nullptr, "test", WorkQueue::QueueType::kImmediate);
+ work_queues_.push_back(WrapUnique(queue));
+ work_queue_sets_->AddQueue(queue, TaskQueue::kControlPriority);
+ return queue;
+ }
+
+ TaskQueueImpl::Task FakeTaskWithEnqueueOrder(int enqueue_order) {
+ TaskQueueImpl::Task fake_task(
+ TaskQueue::PostedTask(BindOnce([] {}), FROM_HERE), TimeTicks(),
+ EnqueueOrder(), EnqueueOrder::FromIntForTesting(enqueue_order));
+ return fake_task;
+ }
+
+ TaskQueueImpl::Task FakeNonNestableTaskWithEnqueueOrder(int enqueue_order) {
+ TaskQueueImpl::Task fake_task(
+ TaskQueue::PostedTask(BindOnce([] {}), FROM_HERE), TimeTicks(),
+ EnqueueOrder(), EnqueueOrder::FromIntForTesting(enqueue_order));
+ fake_task.nestable = Nestable::kNonNestable;
+ return fake_task;
+ }
+
+ std::vector<std::unique_ptr<WorkQueue>> work_queues_;
+ std::unique_ptr<WorkQueueSets> work_queue_sets_;
+};
+
+TEST_F(WorkQueueSetsTest, ChangeSetIndex) {
+ WorkQueue* work_queue = NewTaskQueue("queue");
+ size_t set = TaskQueue::kNormalPriority;
+ work_queue_sets_->ChangeSetIndex(work_queue, set);
+
+ EXPECT_EQ(set, work_queue->work_queue_set_index());
+}
+
+TEST_F(WorkQueueSetsTest, GetOldestQueueInSet_QueueEmpty) {
+ WorkQueue* work_queue = NewTaskQueue("queue");
+ size_t set = TaskQueue::kNormalPriority;
+ work_queue_sets_->ChangeSetIndex(work_queue, set);
+
+ WorkQueue* selected_work_queue;
+ EXPECT_FALSE(
+ work_queue_sets_->GetOldestQueueInSet(set, &selected_work_queue));
+}
+
+TEST_F(WorkQueueSetsTest, OnTaskPushedToEmptyQueue) {
+ WorkQueue* work_queue = NewTaskQueue("queue");
+ size_t set = TaskQueue::kNormalPriority;
+ work_queue_sets_->ChangeSetIndex(work_queue, set);
+
+ WorkQueue* selected_work_queue;
+ EXPECT_FALSE(
+ work_queue_sets_->GetOldestQueueInSet(set, &selected_work_queue));
+
+ // Calls OnTaskPushedToEmptyQueue.
+ work_queue->Push(FakeTaskWithEnqueueOrder(10));
+
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(set, &selected_work_queue));
+ EXPECT_EQ(work_queue, selected_work_queue);
+}
+
+TEST_F(WorkQueueSetsTest, GetOldestQueueInSet_SingleTaskInSet) {
+ WorkQueue* work_queue = NewTaskQueue("queue");
+ work_queue->Push(FakeTaskWithEnqueueOrder(10));
+ size_t set = 1;
+ work_queue_sets_->ChangeSetIndex(work_queue, set);
+
+ WorkQueue* selected_work_queue;
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(set, &selected_work_queue));
+ EXPECT_EQ(work_queue, selected_work_queue);
+}
+
+TEST_F(WorkQueueSetsTest, GetOldestQueueAndEnqueueOrderInSet) {
+ WorkQueue* work_queue = NewTaskQueue("queue");
+ work_queue->Push(FakeTaskWithEnqueueOrder(10));
+ size_t set = 1;
+ work_queue_sets_->ChangeSetIndex(work_queue, set);
+
+ WorkQueue* selected_work_queue;
+ EnqueueOrder enqueue_order;
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueAndEnqueueOrderInSet(
+ set, &selected_work_queue, &enqueue_order));
+ EXPECT_EQ(work_queue, selected_work_queue);
+ EXPECT_EQ(10u, enqueue_order);
+}
+
+TEST_F(WorkQueueSetsTest, GetOldestQueueInSet_MultipleAgesInSet) {
+ WorkQueue* queue1 = NewTaskQueue("queue1");
+ WorkQueue* queue2 = NewTaskQueue("queue2");
+ WorkQueue* queue3 = NewTaskQueue("queue2");
+ queue1->Push(FakeTaskWithEnqueueOrder(6));
+ queue2->Push(FakeTaskWithEnqueueOrder(5));
+ queue3->Push(FakeTaskWithEnqueueOrder(4));
+ size_t set = 2;
+ work_queue_sets_->ChangeSetIndex(queue1, set);
+ work_queue_sets_->ChangeSetIndex(queue2, set);
+ work_queue_sets_->ChangeSetIndex(queue3, set);
+
+ WorkQueue* selected_work_queue;
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(set, &selected_work_queue));
+ EXPECT_EQ(queue3, selected_work_queue);
+}
+
+TEST_F(WorkQueueSetsTest, OnPopQueue) {
+ WorkQueue* queue1 = NewTaskQueue("queue1");
+ WorkQueue* queue2 = NewTaskQueue("queue2");
+ WorkQueue* queue3 = NewTaskQueue("queue3");
+ queue1->Push(FakeTaskWithEnqueueOrder(6));
+ queue2->Push(FakeTaskWithEnqueueOrder(1));
+ queue2->Push(FakeTaskWithEnqueueOrder(3));
+ queue3->Push(FakeTaskWithEnqueueOrder(4));
+ size_t set = 3;
+ work_queue_sets_->ChangeSetIndex(queue1, set);
+ work_queue_sets_->ChangeSetIndex(queue2, set);
+ work_queue_sets_->ChangeSetIndex(queue3, set);
+
+ WorkQueue* selected_work_queue;
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(set, &selected_work_queue));
+ EXPECT_EQ(queue2, selected_work_queue);
+
+ queue2->PopTaskForTesting();
+ work_queue_sets_->OnPopQueue(queue2);
+
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(set, &selected_work_queue));
+ EXPECT_EQ(queue2, selected_work_queue);
+}
+
+TEST_F(WorkQueueSetsTest, OnPopQueue_QueueBecomesEmpty) {
+ WorkQueue* queue1 = NewTaskQueue("queue1");
+ WorkQueue* queue2 = NewTaskQueue("queue2");
+ WorkQueue* queue3 = NewTaskQueue("queue3");
+ queue1->Push(FakeTaskWithEnqueueOrder(6));
+ queue2->Push(FakeTaskWithEnqueueOrder(5));
+ queue3->Push(FakeTaskWithEnqueueOrder(4));
+ size_t set = 4;
+ work_queue_sets_->ChangeSetIndex(queue1, set);
+ work_queue_sets_->ChangeSetIndex(queue2, set);
+ work_queue_sets_->ChangeSetIndex(queue3, set);
+
+ WorkQueue* selected_work_queue;
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(set, &selected_work_queue));
+ EXPECT_EQ(queue3, selected_work_queue);
+
+ queue3->PopTaskForTesting();
+ work_queue_sets_->OnPopQueue(queue3);
+
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(set, &selected_work_queue));
+ EXPECT_EQ(queue2, selected_work_queue);
+}
+
+TEST_F(WorkQueueSetsTest,
+ GetOldestQueueInSet_MultipleAgesInSetIntegerRollover) {
+ WorkQueue* queue1 = NewTaskQueue("queue1");
+ WorkQueue* queue2 = NewTaskQueue("queue2");
+ WorkQueue* queue3 = NewTaskQueue("queue3");
+ queue1->Push(FakeTaskWithEnqueueOrder(0x7ffffff1));
+ queue2->Push(FakeTaskWithEnqueueOrder(0x7ffffff0));
+ queue3->Push(FakeTaskWithEnqueueOrder(-0x7ffffff1));
+ size_t set = 1;
+ work_queue_sets_->ChangeSetIndex(queue1, set);
+ work_queue_sets_->ChangeSetIndex(queue2, set);
+ work_queue_sets_->ChangeSetIndex(queue3, set);
+
+ WorkQueue* selected_work_queue;
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(set, &selected_work_queue));
+ EXPECT_EQ(queue2, selected_work_queue);
+}
+
+TEST_F(WorkQueueSetsTest, GetOldestQueueInSet_MultipleAgesInSet_RemoveQueue) {
+ WorkQueue* queue1 = NewTaskQueue("queue1");
+ WorkQueue* queue2 = NewTaskQueue("queue2");
+ WorkQueue* queue3 = NewTaskQueue("queue3");
+ queue1->Push(FakeTaskWithEnqueueOrder(6));
+ queue2->Push(FakeTaskWithEnqueueOrder(5));
+ queue3->Push(FakeTaskWithEnqueueOrder(4));
+ size_t set = 1;
+ work_queue_sets_->ChangeSetIndex(queue1, set);
+ work_queue_sets_->ChangeSetIndex(queue2, set);
+ work_queue_sets_->ChangeSetIndex(queue3, set);
+ work_queue_sets_->RemoveQueue(queue3);
+
+ WorkQueue* selected_work_queue;
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(set, &selected_work_queue));
+ EXPECT_EQ(queue2, selected_work_queue);
+}
+
+TEST_F(WorkQueueSetsTest, ChangeSetIndex_Complex) {
+ WorkQueue* queue1 = NewTaskQueue("queue1");
+ WorkQueue* queue2 = NewTaskQueue("queue2");
+ WorkQueue* queue3 = NewTaskQueue("queue3");
+ WorkQueue* queue4 = NewTaskQueue("queue4");
+ queue1->Push(FakeTaskWithEnqueueOrder(6));
+ queue2->Push(FakeTaskWithEnqueueOrder(5));
+ queue3->Push(FakeTaskWithEnqueueOrder(4));
+ queue4->Push(FakeTaskWithEnqueueOrder(3));
+ size_t set1 = 1;
+ size_t set2 = 2;
+ work_queue_sets_->ChangeSetIndex(queue1, set1);
+ work_queue_sets_->ChangeSetIndex(queue2, set1);
+ work_queue_sets_->ChangeSetIndex(queue3, set2);
+ work_queue_sets_->ChangeSetIndex(queue4, set2);
+
+ WorkQueue* selected_work_queue;
+ EXPECT_TRUE(
+ work_queue_sets_->GetOldestQueueInSet(set1, &selected_work_queue));
+ EXPECT_EQ(queue2, selected_work_queue);
+
+ EXPECT_TRUE(
+ work_queue_sets_->GetOldestQueueInSet(set2, &selected_work_queue));
+ EXPECT_EQ(queue4, selected_work_queue);
+
+ work_queue_sets_->ChangeSetIndex(queue4, set1);
+
+ EXPECT_TRUE(
+ work_queue_sets_->GetOldestQueueInSet(set1, &selected_work_queue));
+ EXPECT_EQ(queue4, selected_work_queue);
+
+ EXPECT_TRUE(
+ work_queue_sets_->GetOldestQueueInSet(set2, &selected_work_queue));
+ EXPECT_EQ(queue3, selected_work_queue);
+}
+
+TEST_F(WorkQueueSetsTest, IsSetEmpty_NoWork) {
+ size_t set = 2;
+ EXPECT_TRUE(work_queue_sets_->IsSetEmpty(set));
+
+ WorkQueue* work_queue = NewTaskQueue("queue");
+ work_queue_sets_->ChangeSetIndex(work_queue, set);
+ EXPECT_TRUE(work_queue_sets_->IsSetEmpty(set));
+}
+
+TEST_F(WorkQueueSetsTest, IsSetEmpty_Work) {
+ size_t set = 2;
+ EXPECT_TRUE(work_queue_sets_->IsSetEmpty(set));
+
+ WorkQueue* work_queue = NewTaskQueue("queue");
+ work_queue->Push(FakeTaskWithEnqueueOrder(1));
+ work_queue_sets_->ChangeSetIndex(work_queue, set);
+ EXPECT_FALSE(work_queue_sets_->IsSetEmpty(set));
+
+ work_queue->PopTaskForTesting();
+ work_queue_sets_->OnPopQueue(work_queue);
+ EXPECT_TRUE(work_queue_sets_->IsSetEmpty(set));
+}
+
+TEST_F(WorkQueueSetsTest, BlockQueuesByFence) {
+ WorkQueue* queue1 = NewTaskQueue("queue1");
+ WorkQueue* queue2 = NewTaskQueue("queue2");
+
+ queue1->Push(FakeTaskWithEnqueueOrder(6));
+ queue2->Push(FakeTaskWithEnqueueOrder(7));
+ queue1->Push(FakeTaskWithEnqueueOrder(8));
+ queue2->Push(FakeTaskWithEnqueueOrder(9));
+
+ size_t set = TaskQueue::kControlPriority;
+
+ WorkQueue* selected_work_queue;
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(set, &selected_work_queue));
+ EXPECT_EQ(selected_work_queue, queue1);
+
+ queue1->InsertFence(EnqueueOrder::blocking_fence());
+
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(set, &selected_work_queue));
+ EXPECT_EQ(selected_work_queue, queue2);
+}
+
+TEST_F(WorkQueueSetsTest, PushNonNestableTaskToFront) {
+ WorkQueue* queue1 = NewTaskQueue("queue1");
+ WorkQueue* queue2 = NewTaskQueue("queue2");
+ WorkQueue* queue3 = NewTaskQueue("queue3");
+ queue1->Push(FakeTaskWithEnqueueOrder(6));
+ queue2->Push(FakeTaskWithEnqueueOrder(5));
+ queue3->Push(FakeTaskWithEnqueueOrder(4));
+ size_t set = 4;
+ work_queue_sets_->ChangeSetIndex(queue1, set);
+ work_queue_sets_->ChangeSetIndex(queue2, set);
+ work_queue_sets_->ChangeSetIndex(queue3, set);
+
+ WorkQueue* selected_work_queue;
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(set, &selected_work_queue));
+ EXPECT_EQ(queue3, selected_work_queue);
+
+ queue1->PushNonNestableTaskToFront(FakeNonNestableTaskWithEnqueueOrder(2));
+
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(set, &selected_work_queue));
+ EXPECT_EQ(queue1, selected_work_queue);
+}
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task/sequence_manager/work_queue_unittest.cc b/base/task/sequence_manager/work_queue_unittest.cc
new file mode 100644
index 0000000000..a71cebcabc
--- /dev/null
+++ b/base/task/sequence_manager/work_queue_unittest.cc
@@ -0,0 +1,475 @@
+// 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/task/sequence_manager/work_queue.h"
+
+#include <stddef.h>
+#include <memory>
+
+#include "base/bind.h"
+#include "base/task/sequence_manager/real_time_domain.h"
+#include "base/task/sequence_manager/task_queue_impl.h"
+#include "base/task/sequence_manager/work_queue_sets.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace base {
+namespace sequence_manager {
+namespace internal {
+
+namespace {
+
+void NopTask() {}
+
+struct Cancelable {
+ Cancelable() : weak_ptr_factory(this) {}
+
+ void NopTask() {}
+
+ WeakPtrFactory<Cancelable> weak_ptr_factory;
+};
+
+} // namespace
+
+class WorkQueueTest : public testing::Test {
+ public:
+ void SetUp() override {
+ time_domain_.reset(new RealTimeDomain());
+ task_queue_ = std::make_unique<TaskQueueImpl>(nullptr, time_domain_.get(),
+ TaskQueue::Spec("test"));
+
+ work_queue_.reset(new WorkQueue(task_queue_.get(), "test",
+ WorkQueue::QueueType::kImmediate));
+ work_queue_sets_.reset(new WorkQueueSets(1, "test"));
+ work_queue_sets_->AddQueue(work_queue_.get(), 0);
+ }
+
+ void TearDown() override { work_queue_sets_->RemoveQueue(work_queue_.get()); }
+
+ protected:
+ TaskQueueImpl::Task FakeCancelableTaskWithEnqueueOrder(
+ int enqueue_order,
+ WeakPtr<Cancelable> weak_ptr) {
+ TaskQueueImpl::Task fake_task(
+ TaskQueue::PostedTask(BindOnce(&Cancelable::NopTask, weak_ptr),
+ FROM_HERE),
+ TimeTicks(), EnqueueOrder(),
+ EnqueueOrder::FromIntForTesting(enqueue_order));
+ return fake_task;
+ }
+
+ TaskQueueImpl::Task FakeTaskWithEnqueueOrder(int enqueue_order) {
+ TaskQueueImpl::Task fake_task(
+ TaskQueue::PostedTask(BindOnce(&NopTask), FROM_HERE), TimeTicks(),
+ EnqueueOrder(), EnqueueOrder::FromIntForTesting(enqueue_order));
+ return fake_task;
+ }
+
+ TaskQueueImpl::Task FakeNonNestableTaskWithEnqueueOrder(int enqueue_order) {
+ TaskQueueImpl::Task fake_task(
+ TaskQueue::PostedTask(BindOnce(&NopTask), FROM_HERE), TimeTicks(),
+ EnqueueOrder(), EnqueueOrder::FromIntForTesting(enqueue_order));
+ fake_task.nestable = Nestable::kNonNestable;
+ return fake_task;
+ }
+
+ std::unique_ptr<RealTimeDomain> time_domain_;
+ std::unique_ptr<TaskQueueImpl> task_queue_;
+ std::unique_ptr<WorkQueue> work_queue_;
+ std::unique_ptr<WorkQueueSets> work_queue_sets_;
+ std::unique_ptr<TaskQueueImpl::TaskDeque> incoming_queue_;
+};
+
+TEST_F(WorkQueueTest, Empty) {
+ EXPECT_TRUE(work_queue_->Empty());
+ work_queue_->Push(FakeTaskWithEnqueueOrder(1));
+ EXPECT_FALSE(work_queue_->Empty());
+}
+
+TEST_F(WorkQueueTest, Empty_IgnoresFences) {
+ work_queue_->Push(FakeTaskWithEnqueueOrder(1));
+ work_queue_->InsertFence(EnqueueOrder::blocking_fence());
+ EXPECT_FALSE(work_queue_->Empty());
+}
+
+TEST_F(WorkQueueTest, GetFrontTaskEnqueueOrderQueueEmpty) {
+ EnqueueOrder enqueue_order;
+ EXPECT_FALSE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
+}
+
+TEST_F(WorkQueueTest, GetFrontTaskEnqueueOrder) {
+ work_queue_->Push(FakeTaskWithEnqueueOrder(2));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(3));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(4));
+
+ EnqueueOrder enqueue_order;
+ EXPECT_TRUE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
+ EXPECT_EQ(2ull, enqueue_order);
+}
+
+TEST_F(WorkQueueTest, GetFrontTaskQueueEmpty) {
+ EXPECT_EQ(nullptr, work_queue_->GetFrontTask());
+}
+
+TEST_F(WorkQueueTest, GetFrontTask) {
+ work_queue_->Push(FakeTaskWithEnqueueOrder(2));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(3));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(4));
+
+ ASSERT_NE(nullptr, work_queue_->GetFrontTask());
+ EXPECT_EQ(2ull, work_queue_->GetFrontTask()->enqueue_order());
+}
+
+TEST_F(WorkQueueTest, GetBackTask_Empty) {
+ EXPECT_EQ(nullptr, work_queue_->GetBackTask());
+}
+
+TEST_F(WorkQueueTest, GetBackTask) {
+ work_queue_->Push(FakeTaskWithEnqueueOrder(2));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(3));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(4));
+
+ ASSERT_NE(nullptr, work_queue_->GetBackTask());
+ EXPECT_EQ(4ull, work_queue_->GetBackTask()->enqueue_order());
+}
+
+TEST_F(WorkQueueTest, Push) {
+ WorkQueue* work_queue;
+ EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+
+ work_queue_->Push(FakeTaskWithEnqueueOrder(2));
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+ EXPECT_EQ(work_queue_.get(), work_queue);
+}
+
+TEST_F(WorkQueueTest, PushAfterFenceHit) {
+ work_queue_->InsertFence(EnqueueOrder::blocking_fence());
+ WorkQueue* work_queue;
+ EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+
+ work_queue_->Push(FakeTaskWithEnqueueOrder(2));
+ EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+}
+
+TEST_F(WorkQueueTest, PushNonNestableTaskToFront) {
+ WorkQueue* work_queue;
+ EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+
+ work_queue_->PushNonNestableTaskToFront(
+ FakeNonNestableTaskWithEnqueueOrder(3));
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+ EXPECT_EQ(work_queue_.get(), work_queue);
+
+ work_queue_->PushNonNestableTaskToFront(
+ FakeNonNestableTaskWithEnqueueOrder(2));
+
+ EXPECT_EQ(2ull, work_queue_->GetFrontTask()->enqueue_order());
+ EXPECT_EQ(3ull, work_queue_->GetBackTask()->enqueue_order());
+}
+
+TEST_F(WorkQueueTest, PushNonNestableTaskToFrontAfterFenceHit) {
+ work_queue_->InsertFence(EnqueueOrder::blocking_fence());
+ WorkQueue* work_queue;
+ EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+
+ work_queue_->PushNonNestableTaskToFront(
+ FakeNonNestableTaskWithEnqueueOrder(2));
+ EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+}
+
+TEST_F(WorkQueueTest, PushNonNestableTaskToFrontBeforeFenceHit) {
+ work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(3));
+ WorkQueue* work_queue;
+ EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+
+ work_queue_->PushNonNestableTaskToFront(
+ FakeNonNestableTaskWithEnqueueOrder(2));
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+}
+
+TEST_F(WorkQueueTest, ReloadEmptyImmediateQueue) {
+ task_queue_->PushImmediateIncomingTaskForTest(FakeTaskWithEnqueueOrder(2));
+ task_queue_->PushImmediateIncomingTaskForTest(FakeTaskWithEnqueueOrder(3));
+ task_queue_->PushImmediateIncomingTaskForTest(FakeTaskWithEnqueueOrder(4));
+
+ WorkQueue* work_queue;
+ EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+ EXPECT_TRUE(work_queue_->Empty());
+ work_queue_->ReloadEmptyImmediateQueue();
+
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+ EXPECT_FALSE(work_queue_->Empty());
+
+ ASSERT_NE(nullptr, work_queue_->GetFrontTask());
+ EXPECT_EQ(2ull, work_queue_->GetFrontTask()->enqueue_order());
+
+ ASSERT_NE(nullptr, work_queue_->GetBackTask());
+ EXPECT_EQ(4ull, work_queue_->GetBackTask()->enqueue_order());
+}
+
+TEST_F(WorkQueueTest, ReloadEmptyImmediateQueueAfterFenceHit) {
+ work_queue_->InsertFence(EnqueueOrder::blocking_fence());
+ task_queue_->PushImmediateIncomingTaskForTest(FakeTaskWithEnqueueOrder(2));
+ task_queue_->PushImmediateIncomingTaskForTest(FakeTaskWithEnqueueOrder(3));
+ task_queue_->PushImmediateIncomingTaskForTest(FakeTaskWithEnqueueOrder(4));
+
+ WorkQueue* work_queue;
+ EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+ EXPECT_TRUE(work_queue_->Empty());
+ work_queue_->ReloadEmptyImmediateQueue();
+
+ EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+ EXPECT_FALSE(work_queue_->Empty());
+
+ ASSERT_NE(nullptr, work_queue_->GetFrontTask());
+ EXPECT_EQ(2ull, work_queue_->GetFrontTask()->enqueue_order());
+
+ ASSERT_NE(nullptr, work_queue_->GetBackTask());
+ EXPECT_EQ(4ull, work_queue_->GetBackTask()->enqueue_order());
+}
+
+TEST_F(WorkQueueTest, TakeTaskFromWorkQueue) {
+ work_queue_->Push(FakeTaskWithEnqueueOrder(2));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(3));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(4));
+
+ WorkQueue* work_queue;
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+ EXPECT_FALSE(work_queue_->Empty());
+
+ EXPECT_EQ(2ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
+ EXPECT_EQ(3ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
+ EXPECT_EQ(4ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
+
+ EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+ EXPECT_TRUE(work_queue_->Empty());
+}
+
+TEST_F(WorkQueueTest, TakeTaskFromWorkQueue_HitFence) {
+ work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(3));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(2));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(4));
+ EXPECT_FALSE(work_queue_->BlockedByFence());
+
+ WorkQueue* work_queue;
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+ EXPECT_FALSE(work_queue_->Empty());
+ EXPECT_FALSE(work_queue_->BlockedByFence());
+
+ EXPECT_EQ(2ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
+ EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+ EXPECT_FALSE(work_queue_->Empty());
+ EXPECT_TRUE(work_queue_->BlockedByFence());
+}
+
+TEST_F(WorkQueueTest, InsertFenceBeforeEnqueueing) {
+ EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::blocking_fence()));
+ EXPECT_TRUE(work_queue_->BlockedByFence());
+
+ work_queue_->Push(FakeTaskWithEnqueueOrder(2));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(3));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(4));
+
+ EnqueueOrder enqueue_order;
+ EXPECT_FALSE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
+}
+
+TEST_F(WorkQueueTest, InsertFenceAfterEnqueueingNonBlocking) {
+ work_queue_->Push(FakeTaskWithEnqueueOrder(2));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(3));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(4));
+
+ EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(5)));
+ EXPECT_FALSE(work_queue_->BlockedByFence());
+
+ EnqueueOrder enqueue_order;
+ EXPECT_TRUE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
+ EXPECT_EQ(2ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
+}
+
+TEST_F(WorkQueueTest, InsertFenceAfterEnqueueing) {
+ work_queue_->Push(FakeTaskWithEnqueueOrder(2));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(3));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(4));
+
+ // NB in reality a fence will always be greater than any currently enqueued
+ // tasks.
+ EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::blocking_fence()));
+ EXPECT_TRUE(work_queue_->BlockedByFence());
+
+ EnqueueOrder enqueue_order;
+ EXPECT_FALSE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
+}
+
+TEST_F(WorkQueueTest, InsertNewFence) {
+ work_queue_->Push(FakeTaskWithEnqueueOrder(2));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(4));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(5));
+
+ EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(3)));
+ EXPECT_FALSE(work_queue_->BlockedByFence());
+
+ // Note until TakeTaskFromWorkQueue() is called we don't hit the fence.
+ EnqueueOrder enqueue_order;
+ EXPECT_TRUE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
+ EXPECT_EQ(2ull, enqueue_order);
+
+ EXPECT_EQ(2ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
+ EXPECT_FALSE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
+ EXPECT_TRUE(work_queue_->BlockedByFence());
+
+ // Inserting the new fence should temporarily unblock the queue until the new
+ // one is hit.
+ EXPECT_TRUE(work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(6)));
+ EXPECT_FALSE(work_queue_->BlockedByFence());
+
+ EXPECT_TRUE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
+ EXPECT_EQ(4ull, enqueue_order);
+ EXPECT_EQ(4ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
+ EXPECT_TRUE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
+ EXPECT_FALSE(work_queue_->BlockedByFence());
+}
+
+TEST_F(WorkQueueTest, PushWithNonEmptyQueueDoesNotHitFence) {
+ work_queue_->Push(FakeTaskWithEnqueueOrder(1));
+ EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(2)));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(3));
+ EXPECT_FALSE(work_queue_->BlockedByFence());
+}
+
+TEST_F(WorkQueueTest, RemoveFence) {
+ work_queue_->Push(FakeTaskWithEnqueueOrder(2));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(4));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(5));
+ work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(3));
+
+ WorkQueue* work_queue;
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+ EXPECT_FALSE(work_queue_->Empty());
+
+ EXPECT_EQ(2ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
+ EXPECT_FALSE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+ EXPECT_FALSE(work_queue_->Empty());
+ EXPECT_TRUE(work_queue_->BlockedByFence());
+
+ EXPECT_TRUE(work_queue_->RemoveFence());
+ EXPECT_EQ(4ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
+ EXPECT_TRUE(work_queue_sets_->GetOldestQueueInSet(0, &work_queue));
+ EXPECT_FALSE(work_queue_->BlockedByFence());
+}
+
+TEST_F(WorkQueueTest, RemoveFenceButNoFence) {
+ EXPECT_FALSE(work_queue_->RemoveFence());
+}
+
+TEST_F(WorkQueueTest, RemoveFenceNothingUnblocked) {
+ EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::blocking_fence()));
+ EXPECT_TRUE(work_queue_->BlockedByFence());
+
+ EXPECT_FALSE(work_queue_->RemoveFence());
+ EXPECT_FALSE(work_queue_->BlockedByFence());
+}
+
+TEST_F(WorkQueueTest, BlockedByFence) {
+ EXPECT_FALSE(work_queue_->BlockedByFence());
+ EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::blocking_fence()));
+ EXPECT_TRUE(work_queue_->BlockedByFence());
+}
+
+TEST_F(WorkQueueTest, BlockedByFencePopBecomesEmpty) {
+ work_queue_->Push(FakeTaskWithEnqueueOrder(1));
+ EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(2)));
+ EXPECT_FALSE(work_queue_->BlockedByFence());
+
+ EXPECT_EQ(1ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
+ EXPECT_TRUE(work_queue_->BlockedByFence());
+}
+
+TEST_F(WorkQueueTest, BlockedByFencePop) {
+ work_queue_->Push(FakeTaskWithEnqueueOrder(1));
+ EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(2)));
+ EXPECT_FALSE(work_queue_->BlockedByFence());
+
+ work_queue_->Push(FakeTaskWithEnqueueOrder(3));
+ EXPECT_FALSE(work_queue_->BlockedByFence());
+
+ EXPECT_EQ(1ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
+ EXPECT_TRUE(work_queue_->BlockedByFence());
+}
+
+TEST_F(WorkQueueTest, InitiallyEmptyBlockedByFenceNewFenceUnblocks) {
+ EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::blocking_fence()));
+ EXPECT_TRUE(work_queue_->BlockedByFence());
+
+ work_queue_->Push(FakeTaskWithEnqueueOrder(2));
+ EXPECT_TRUE(work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(3)));
+ EXPECT_FALSE(work_queue_->BlockedByFence());
+}
+
+TEST_F(WorkQueueTest, BlockedByFenceNewFenceUnblocks) {
+ work_queue_->Push(FakeTaskWithEnqueueOrder(1));
+ EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(2)));
+ EXPECT_FALSE(work_queue_->BlockedByFence());
+
+ work_queue_->Push(FakeTaskWithEnqueueOrder(3));
+ EXPECT_FALSE(work_queue_->BlockedByFence());
+
+ EXPECT_EQ(1ull, work_queue_->TakeTaskFromWorkQueue().enqueue_order());
+ EXPECT_TRUE(work_queue_->BlockedByFence());
+
+ EXPECT_TRUE(work_queue_->InsertFence(EnqueueOrder::FromIntForTesting(4)));
+ EXPECT_FALSE(work_queue_->BlockedByFence());
+}
+
+TEST_F(WorkQueueTest, InsertFenceAfterEnqueuing) {
+ work_queue_->Push(FakeTaskWithEnqueueOrder(2));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(3));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(4));
+ EXPECT_FALSE(work_queue_->BlockedByFence());
+
+ EXPECT_FALSE(work_queue_->InsertFence(EnqueueOrder::blocking_fence()));
+ EXPECT_TRUE(work_queue_->BlockedByFence());
+
+ EnqueueOrder enqueue_order;
+ EXPECT_FALSE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
+}
+
+TEST_F(WorkQueueTest, RemoveAllCanceledTasksFromFront) {
+ {
+ Cancelable cancelable;
+ work_queue_->Push(FakeCancelableTaskWithEnqueueOrder(
+ 2, cancelable.weak_ptr_factory.GetWeakPtr()));
+ work_queue_->Push(FakeCancelableTaskWithEnqueueOrder(
+ 3, cancelable.weak_ptr_factory.GetWeakPtr()));
+ work_queue_->Push(FakeCancelableTaskWithEnqueueOrder(
+ 4, cancelable.weak_ptr_factory.GetWeakPtr()));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(5));
+ }
+ EXPECT_TRUE(work_queue_->RemoveAllCanceledTasksFromFront());
+
+ EnqueueOrder enqueue_order;
+ EXPECT_TRUE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
+ EXPECT_EQ(5ull, enqueue_order);
+}
+
+TEST_F(WorkQueueTest, RemoveAllCanceledTasksFromFrontTasksNotCanceled) {
+ {
+ Cancelable cancelable;
+ work_queue_->Push(FakeCancelableTaskWithEnqueueOrder(
+ 2, cancelable.weak_ptr_factory.GetWeakPtr()));
+ work_queue_->Push(FakeCancelableTaskWithEnqueueOrder(
+ 3, cancelable.weak_ptr_factory.GetWeakPtr()));
+ work_queue_->Push(FakeCancelableTaskWithEnqueueOrder(
+ 4, cancelable.weak_ptr_factory.GetWeakPtr()));
+ work_queue_->Push(FakeTaskWithEnqueueOrder(5));
+ EXPECT_FALSE(work_queue_->RemoveAllCanceledTasksFromFront());
+
+ EnqueueOrder enqueue_order;
+ EXPECT_TRUE(work_queue_->GetFrontTaskEnqueueOrder(&enqueue_order));
+ EXPECT_EQ(2ull, enqueue_order);
+ }
+}
+
+} // namespace internal
+} // namespace sequence_manager
+} // namespace base
diff --git a/base/task_runner.cc b/base/task_runner.cc
index c3e0574a1b..aae9f9ec4f 100644
--- a/base/task_runner.cc
+++ b/base/task_runner.cc
@@ -22,8 +22,7 @@ class PostTaskAndReplyTaskRunner : public internal::PostTaskAndReplyImpl {
explicit PostTaskAndReplyTaskRunner(TaskRunner* destination);
private:
- bool PostTask(const tracked_objects::Location& from_here,
- OnceClosure task) override;
+ bool PostTask(const Location& from_here, OnceClosure task) override;
// Non-owning.
TaskRunner* destination_;
@@ -34,29 +33,27 @@ PostTaskAndReplyTaskRunner::PostTaskAndReplyTaskRunner(
DCHECK(destination_);
}
-bool PostTaskAndReplyTaskRunner::PostTask(
- const tracked_objects::Location& from_here,
- OnceClosure task) {
+bool PostTaskAndReplyTaskRunner::PostTask(const Location& from_here,
+ OnceClosure task) {
return destination_->PostTask(from_here, std::move(task));
}
} // namespace
-bool TaskRunner::PostTask(const tracked_objects::Location& from_here,
- OnceClosure task) {
+bool TaskRunner::PostTask(const Location& from_here, OnceClosure task) {
return PostDelayedTask(from_here, std::move(task), base::TimeDelta());
}
-bool TaskRunner::PostTaskAndReply(const tracked_objects::Location& from_here,
+bool TaskRunner::PostTaskAndReply(const Location& from_here,
OnceClosure task,
OnceClosure reply) {
return PostTaskAndReplyTaskRunner(this).PostTaskAndReply(
from_here, std::move(task), std::move(reply));
}
-TaskRunner::TaskRunner() {}
+TaskRunner::TaskRunner() = default;
-TaskRunner::~TaskRunner() {}
+TaskRunner::~TaskRunner() = default;
void TaskRunner::OnDestruct() const {
delete this;
diff --git a/base/task_runner.h b/base/task_runner.h
index 0421d564e6..1d302ab0fa 100644
--- a/base/task_runner.h
+++ b/base/task_runner.h
@@ -61,24 +61,32 @@ class BASE_EXPORT TaskRunner
// will not be run.
//
// Equivalent to PostDelayedTask(from_here, task, 0).
- bool PostTask(const tracked_objects::Location& from_here, OnceClosure task);
+ bool PostTask(const Location& from_here, OnceClosure task);
- // Like PostTask, but tries to run the posted task only after
- // |delay_ms| has passed.
- //
- // It is valid for an implementation to ignore |delay_ms|; that is,
- // to have PostDelayedTask behave the same as PostTask.
- virtual bool PostDelayedTask(const tracked_objects::Location& from_here,
+ // Like PostTask, but tries to run the posted task only after |delay_ms|
+ // has passed. Implementations should use a tick clock, rather than wall-
+ // clock time, to implement |delay|.
+ virtual bool PostDelayedTask(const Location& from_here,
OnceClosure task,
base::TimeDelta delay) = 0;
- // Returns true if the current thread is a thread on which a task
- // may be run, and false if no task will be run on the current
- // thread.
+ // Returns true iff tasks posted to this TaskRunner are sequenced
+ // with this call.
//
- // It is valid for an implementation to always return true, or in
- // general to use 'true' as a default value.
- virtual bool RunsTasksOnCurrentThread() const = 0;
+ // In particular:
+ // - Returns true if this is a SequencedTaskRunner to which the
+ // current task was posted.
+ // - Returns true if this is a SequencedTaskRunner bound to the
+ // same sequence as the SequencedTaskRunner to which the current
+ // task was posted.
+ // - Returns true if this is a SingleThreadTaskRunner bound to
+ // the current thread.
+ // TODO(http://crbug.com/665062):
+ // This API doesn't make sense for parallel TaskRunners.
+ // Introduce alternate static APIs for documentation purposes of "this runs
+ // in pool X", have RunsTasksInCurrentSequence() return false for parallel
+ // TaskRunners, and ultimately move this method down to SequencedTaskRunner.
+ virtual bool RunsTasksInCurrentSequence() const = 0;
// Posts |task| on the current TaskRunner. On completion, |reply|
// is posted to the thread that called PostTaskAndReply(). Both
@@ -121,17 +129,13 @@ class BASE_EXPORT TaskRunner
// * The DataLoader object can be deleted while |task| is still running,
// and the reply will cancel itself safely because it is bound to a
// WeakPtr<>.
- bool PostTaskAndReply(const tracked_objects::Location& from_here,
+ bool PostTaskAndReply(const Location& from_here,
OnceClosure task,
OnceClosure reply);
protected:
friend struct TaskRunnerTraits;
- // Only the Windows debug build seems to need this: see
- // http://crbug.com/112250.
- friend class RefCountedThreadSafe<TaskRunner, TaskRunnerTraits>;
-
TaskRunner();
virtual ~TaskRunner();
diff --git a/base/task_runner_util.h b/base/task_runner_util.h
index 7fda07624d..d79f5b85c5 100644
--- a/base/task_runner_util.h
+++ b/base/task_runner_util.h
@@ -28,21 +28,38 @@ namespace base {
// PostTaskAndReplyWithResult(
// target_thread_.task_runner(),
// FROM_HERE,
-// Bind(&DoWorkAndReturn),
-// Bind(&Callback));
+// BindOnce(&DoWorkAndReturn),
+// BindOnce(&Callback));
template <typename TaskReturnType, typename ReplyArgType>
bool PostTaskAndReplyWithResult(TaskRunner* task_runner,
- const tracked_objects::Location& from_here,
- Callback<TaskReturnType()> task,
- Callback<void(ReplyArgType)> reply) {
+ const Location& from_here,
+ OnceCallback<TaskReturnType()> task,
+ OnceCallback<void(ReplyArgType)> reply) {
DCHECK(task);
DCHECK(reply);
TaskReturnType* result = new TaskReturnType();
return task_runner->PostTaskAndReply(
- from_here, base::Bind(&internal::ReturnAsParamAdapter<TaskReturnType>,
- std::move(task), result),
- base::Bind(&internal::ReplyAdapter<TaskReturnType, ReplyArgType>,
- std::move(reply), base::Owned(result)));
+ from_here,
+ BindOnce(&internal::ReturnAsParamAdapter<TaskReturnType>, std::move(task),
+ result),
+ BindOnce(&internal::ReplyAdapter<TaskReturnType, ReplyArgType>,
+ std::move(reply), Owned(result)));
+}
+
+// Callback version of PostTaskAndReplyWithResult above.
+// Though RepeatingCallback is convertible to OnceCallback, we need this since
+// we cannot use template deduction and object conversion at once on the
+// overload resolution.
+// TODO(crbug.com/714018): Update all callers of the Callback version to use
+// OnceCallback.
+template <typename TaskReturnType, typename ReplyArgType>
+bool PostTaskAndReplyWithResult(TaskRunner* task_runner,
+ const Location& from_here,
+ Callback<TaskReturnType()> task,
+ Callback<void(ReplyArgType)> reply) {
+ return PostTaskAndReplyWithResult(
+ task_runner, from_here, OnceCallback<TaskReturnType()>(std::move(task)),
+ OnceCallback<void(ReplyArgType)>(std::move(reply)));
}
} // namespace base
diff --git a/base/task_runner_util_unittest.cc b/base/task_runner_util_unittest.cc
index 1df54362c8..44baad4754 100644
--- a/base/task_runner_util_unittest.cc
+++ b/base/task_runner_util_unittest.cc
@@ -8,6 +8,7 @@
#include "base/bind.h"
#include "base/location.h"
+#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "testing/gtest/include/gtest/gtest.h"
diff --git a/base/task_scheduler/can_schedule_sequence_observer.h b/base/task_scheduler/can_schedule_sequence_observer.h
new file mode 100644
index 0000000000..f2b0551292
--- /dev/null
+++ b/base/task_scheduler/can_schedule_sequence_observer.h
@@ -0,0 +1,27 @@
+// 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_TASK_SCHEDULER_CAN_SCHEDULE_SEQUENCE_OBSERVER_H_
+#define BASE_TASK_SCHEDULER_CAN_SCHEDULE_SEQUENCE_OBSERVER_H_
+
+#include "base/task_scheduler/sequence.h"
+
+namespace base {
+namespace internal {
+
+class CanScheduleSequenceObserver {
+ public:
+ // Called when |sequence| can be scheduled. It is expected that
+ // TaskTracker::RunNextTask() will be called with |sequence| as argument after
+ // this is called.
+ virtual void OnCanScheduleSequence(scoped_refptr<Sequence> sequence) = 0;
+
+ protected:
+ virtual ~CanScheduleSequenceObserver() = default;
+};
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_TASK_SCHEDULER_CAN_SCHEDULE_SEQUENCE_OBSERVER_H_
diff --git a/base/task_scheduler/environment_config.cc b/base/task_scheduler/environment_config.cc
new file mode 100644
index 0000000000..3c76f2fc98
--- /dev/null
+++ b/base/task_scheduler/environment_config.cc
@@ -0,0 +1,45 @@
+// 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/task_scheduler/environment_config.h"
+
+#include "base/synchronization/lock.h"
+#include "base/threading/platform_thread.h"
+#include "build/build_config.h"
+
+namespace base {
+namespace internal {
+
+size_t GetEnvironmentIndexForTraits(const TaskTraits& traits) {
+ const bool is_background =
+ traits.priority() == base::TaskPriority::BACKGROUND;
+ if (traits.may_block() || traits.with_base_sync_primitives())
+ return is_background ? BACKGROUND_BLOCKING : FOREGROUND_BLOCKING;
+ return is_background ? BACKGROUND : FOREGROUND;
+}
+
+bool CanUseBackgroundPriorityForSchedulerWorker() {
+ // When Lock doesn't handle multiple thread priorities, run all
+ // SchedulerWorker with a normal priority to avoid priority inversion when a
+ // thread running with a normal priority tries to acquire a lock held by a
+ // thread running with a background priority.
+ if (!Lock::HandlesMultipleThreadPriorities())
+ return false;
+
+#if !defined(OS_ANDROID)
+ // When thread priority can't be increased, run all threads with a normal
+ // priority to avoid priority inversions on shutdown (TaskScheduler increases
+ // background threads priority to normal on shutdown while resolving remaining
+ // shutdown blocking tasks).
+ //
+ // This is ignored on Android, because it doesn't have a clean shutdown phase.
+ if (!PlatformThread::CanIncreaseCurrentThreadPriority())
+ return false;
+#endif // defined(OS_ANDROID)
+
+ return true;
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/environment_config.h b/base/task_scheduler/environment_config.h
new file mode 100644
index 0000000000..19b685aeb3
--- /dev/null
+++ b/base/task_scheduler/environment_config.h
@@ -0,0 +1,53 @@
+// 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_TASK_SCHEDULER_ENVIRONMENT_CONFIG_H_
+#define BASE_TASK_SCHEDULER_ENVIRONMENT_CONFIG_H_
+
+#include <stddef.h>
+
+#include "base/base_export.h"
+#include "base/task_scheduler/task_traits.h"
+#include "base/threading/thread.h"
+
+namespace base {
+namespace internal {
+
+enum EnvironmentType {
+ FOREGROUND = 0,
+ FOREGROUND_BLOCKING,
+ // Pools will only be created for the environment above on platforms that
+ // don't support SchedulerWorkers running with a background priority.
+ ENVIRONMENT_COUNT_WITHOUT_BACKGROUND_PRIORITY,
+ BACKGROUND = ENVIRONMENT_COUNT_WITHOUT_BACKGROUND_PRIORITY,
+ BACKGROUND_BLOCKING,
+ ENVIRONMENT_COUNT // Always last.
+};
+
+// Order must match the EnvironmentType enum.
+constexpr struct {
+ // The threads and histograms of this environment will be labeled with
+ // the task scheduler name concatenated to this.
+ const char* name_suffix;
+
+ // Preferred priority for threads in this environment; the actual thread
+ // priority depends on shutdown state and platform capabilities.
+ ThreadPriority priority_hint;
+} kEnvironmentParams[] = {
+ {"Foreground", base::ThreadPriority::NORMAL},
+ {"ForegroundBlocking", base::ThreadPriority::NORMAL},
+ {"Background", base::ThreadPriority::BACKGROUND},
+ {"BackgroundBlocking", base::ThreadPriority::BACKGROUND},
+};
+
+size_t BASE_EXPORT GetEnvironmentIndexForTraits(const TaskTraits& traits);
+
+// Returns true if this platform supports having SchedulerWorkers running with a
+// background priority.
+bool BASE_EXPORT CanUseBackgroundPriorityForSchedulerWorker();
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_TASK_SCHEDULER_ENVIRONMENT_CONFIG_H_
diff --git a/base/task_scheduler/lazy_task_runner.cc b/base/task_scheduler/lazy_task_runner.cc
new file mode 100644
index 0000000000..218d02b64a
--- /dev/null
+++ b/base/task_scheduler/lazy_task_runner.cc
@@ -0,0 +1,122 @@
+// 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/task_scheduler/lazy_task_runner.h"
+
+#include <utility>
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/task_scheduler/post_task.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+ScopedLazyTaskRunnerListForTesting* g_scoped_lazy_task_runner_list_for_testing =
+ nullptr;
+} // namespace
+
+template <typename TaskRunnerType, bool com_sta>
+void LazyTaskRunner<TaskRunnerType, com_sta>::Reset() {
+ subtle::AtomicWord state = subtle::Acquire_Load(&state_);
+
+ DCHECK_NE(state, kLazyInstanceStateCreating) << "Race: all threads should be "
+ "unwound in unittests before "
+ "resetting TaskRunners.";
+
+ // Return if no reference is held by this instance.
+ if (!state)
+ return;
+
+ // Release the reference acquired in Get().
+ SequencedTaskRunner* task_runner = reinterpret_cast<TaskRunnerType*>(state);
+ task_runner->Release();
+
+ // Clear the state.
+ subtle::NoBarrier_Store(&state_, 0);
+}
+
+template <>
+scoped_refptr<SequencedTaskRunner>
+LazyTaskRunner<SequencedTaskRunner, false>::Create() {
+ // It is invalid to specify a SingleThreadTaskRunnerThreadMode with a
+ // LazySequencedTaskRunner.
+ DCHECK_EQ(thread_mode_, SingleThreadTaskRunnerThreadMode::SHARED);
+
+ return CreateSequencedTaskRunnerWithTraits(traits_);
+}
+
+template <>
+scoped_refptr<SingleThreadTaskRunner>
+LazyTaskRunner<SingleThreadTaskRunner, false>::Create() {
+ return CreateSingleThreadTaskRunnerWithTraits(traits_, thread_mode_);
+}
+
+#if defined(OS_WIN)
+template <>
+scoped_refptr<SingleThreadTaskRunner>
+LazyTaskRunner<SingleThreadTaskRunner, true>::Create() {
+ return CreateCOMSTATaskRunnerWithTraits(traits_, thread_mode_);
+}
+#endif
+
+// static
+template <typename TaskRunnerType, bool com_sta>
+TaskRunnerType* LazyTaskRunner<TaskRunnerType, com_sta>::CreateRaw(
+ void* void_self) {
+ auto self =
+ reinterpret_cast<LazyTaskRunner<TaskRunnerType, com_sta>*>(void_self);
+
+ scoped_refptr<TaskRunnerType> task_runner = self->Create();
+
+ // Acquire a reference to the TaskRunner. The reference will either
+ // never be released or be released in Reset(). The reference is not
+ // managed by a scoped_refptr because adding a scoped_refptr member to
+ // LazyTaskRunner would prevent its static initialization.
+ task_runner->AddRef();
+
+ // Reset this instance when the current
+ // ScopedLazyTaskRunnerListForTesting is destroyed, if any.
+ if (g_scoped_lazy_task_runner_list_for_testing) {
+ g_scoped_lazy_task_runner_list_for_testing->AddCallback(BindOnce(
+ &LazyTaskRunner<TaskRunnerType, com_sta>::Reset, Unretained(self)));
+ }
+
+ return task_runner.get();
+}
+
+template <typename TaskRunnerType, bool com_sta>
+scoped_refptr<TaskRunnerType> LazyTaskRunner<TaskRunnerType, com_sta>::Get() {
+ return WrapRefCounted(subtle::GetOrCreateLazyPointer(
+ &state_, &LazyTaskRunner<TaskRunnerType, com_sta>::CreateRaw,
+ reinterpret_cast<void*>(this), nullptr, nullptr));
+}
+
+template class LazyTaskRunner<SequencedTaskRunner, false>;
+template class LazyTaskRunner<SingleThreadTaskRunner, false>;
+
+#if defined(OS_WIN)
+template class LazyTaskRunner<SingleThreadTaskRunner, true>;
+#endif
+
+ScopedLazyTaskRunnerListForTesting::ScopedLazyTaskRunnerListForTesting() {
+ DCHECK(!g_scoped_lazy_task_runner_list_for_testing);
+ g_scoped_lazy_task_runner_list_for_testing = this;
+}
+
+ScopedLazyTaskRunnerListForTesting::~ScopedLazyTaskRunnerListForTesting() {
+ internal::AutoSchedulerLock auto_lock(lock_);
+ for (auto& callback : callbacks_)
+ std::move(callback).Run();
+ g_scoped_lazy_task_runner_list_for_testing = nullptr;
+}
+
+void ScopedLazyTaskRunnerListForTesting::AddCallback(OnceClosure callback) {
+ internal::AutoSchedulerLock auto_lock(lock_);
+ callbacks_.push_back(std::move(callback));
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/lazy_task_runner.h b/base/task_scheduler/lazy_task_runner.h
new file mode 100644
index 0000000000..7fcbddff3b
--- /dev/null
+++ b/base/task_scheduler/lazy_task_runner.h
@@ -0,0 +1,218 @@
+// 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_TASK_SCHEDULER_LAZY_TASK_RUNNER_H_
+#define BASE_TASK_SCHEDULER_LAZY_TASK_RUNNER_H_
+
+#include <vector>
+
+#include "base/atomicops.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/lazy_instance_helpers.h"
+#include "base/sequenced_task_runner.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task_scheduler/scheduler_lock.h"
+#include "base/task_scheduler/single_thread_task_runner_thread_mode.h"
+#include "base/task_scheduler/task_traits.h"
+#include "build/build_config.h"
+
+// Lazy(Sequenced|SingleThread|COMSTA)TaskRunner lazily creates a TaskRunner.
+//
+// Lazy(Sequenced|SingleThread|COMSTA)TaskRunner is meant to be instantiated in
+// an anonymous namespace (no static initializer is generated) and used to post
+// tasks to the same sequence/thread from pieces of code that don't have a
+// better way of sharing a TaskRunner. It is important to use this class
+// instead of a self-managed global variable or LazyInstance so that the
+// TaskRunners do not outlive the scope of the ScopedTaskEnvironment in unit
+// tests (otherwise the next test in the same process will die in use-after-
+// frees).
+//
+// IMPORTANT: Only use this API as a last resort. Prefer storing a
+// (Sequenced|SingleThread)TaskRunner returned by
+// base::Create(Sequenced|SingleThread|COMSTA)TaskRunnerWithTraits() as a member
+// on an object accessible by all PostTask() call sites.
+//
+// Example usage 1:
+//
+// namespace {
+// base::LazySequencedTaskRunner g_sequenced_task_runner =
+// LAZY_SEQUENCED_TASK_RUNNER_INITIALIZER(
+// base::TaskTraits(base::MayBlock(),
+// base::TaskPriority::USER_VISIBLE));
+// } // namespace
+//
+// void SequencedFunction() {
+// // Different invocations of this function post to the same
+// // MayBlock() SequencedTaskRunner.
+// g_sequenced_task_runner.Get()->PostTask(FROM_HERE, base::BindOnce(...));
+// }
+//
+// Example usage 2:
+//
+// namespace {
+// base::LazySequencedTaskRunner g_sequenced_task_task_runner =
+// LAZY_SEQUENCED_TASK_RUNNER_INITIALIZER({base::MayBlock()});
+// } // namespace
+//
+// // Code from different files can access the SequencedTaskRunner via this
+// // function.
+// scoped_refptr<base::SequencedTaskRunner> GetTaskRunner() {
+// return g_sequenced_task_runner.Get();
+// }
+
+namespace base {
+
+namespace internal {
+template <typename TaskRunnerType, bool com_sta>
+class BASE_EXPORT LazyTaskRunner;
+} // namespace internal
+
+// Lazy SequencedTaskRunner.
+using LazySequencedTaskRunner =
+ internal::LazyTaskRunner<SequencedTaskRunner, false>;
+
+// Lazy SingleThreadTaskRunner.
+using LazySingleThreadTaskRunner =
+ internal::LazyTaskRunner<SingleThreadTaskRunner, false>;
+
+#if defined(OS_WIN)
+// Lazy COM-STA enabled SingleThreadTaskRunner.
+using LazyCOMSTATaskRunner =
+ internal::LazyTaskRunner<SingleThreadTaskRunner, true>;
+#endif
+
+// Helper macros to generate a variable name by concatenation.
+#define LAZY_TASK_RUNNER_CONCATENATE_INTERNAL2(a, b) a##b
+#define LAZY_TASK_RUNNER_CONCATENATE_INTERNAL(a, b) \
+ LAZY_TASK_RUNNER_CONCATENATE_INTERNAL2(a, b)
+
+// Use the macros below to initialize a LazyTaskRunner. These macros verify that
+// their arguments are constexpr, which is important to prevent the generation
+// of a static initializer.
+
+// |traits| are TaskTraits used when creating the SequencedTaskRunner.
+#define LAZY_SEQUENCED_TASK_RUNNER_INITIALIZER(traits) \
+ base::LazySequencedTaskRunner::CreateInternal(traits); \
+ ALLOW_UNUSED_TYPE constexpr base::TaskTraits \
+ LAZY_TASK_RUNNER_CONCATENATE_INTERNAL(kVerifyTraitsAreConstexpr, \
+ __LINE__) = traits
+
+// |traits| are TaskTraits used when creating the SingleThreadTaskRunner.
+// |thread_mode| specifies whether the SingleThreadTaskRunner can share its
+// thread with other SingleThreadTaskRunners.
+#define LAZY_SINGLE_THREAD_TASK_RUNNER_INITIALIZER(traits, thread_mode) \
+ base::LazySingleThreadTaskRunner::CreateInternal(traits, thread_mode); \
+ ALLOW_UNUSED_TYPE constexpr base::TaskTraits \
+ LAZY_TASK_RUNNER_CONCATENATE_INTERNAL(kVerifyTraitsAreConstexpr, \
+ __LINE__) = traits; \
+ ALLOW_UNUSED_TYPE constexpr base::SingleThreadTaskRunnerThreadMode \
+ LAZY_TASK_RUNNER_CONCATENATE_INTERNAL(kVerifyThreadModeIsConstexpr, \
+ __LINE__) = thread_mode
+
+// |traits| are TaskTraits used when creating the COM STA
+// SingleThreadTaskRunner. |thread_mode| specifies whether the COM STA
+// SingleThreadTaskRunner can share its thread with other
+// SingleThreadTaskRunners.
+#define LAZY_COM_STA_TASK_RUNNER_INITIALIZER(traits, thread_mode) \
+ base::LazyCOMSTATaskRunner::CreateInternal(traits, thread_mode); \
+ ALLOW_UNUSED_TYPE constexpr base::TaskTraits \
+ LAZY_TASK_RUNNER_CONCATENATE_INTERNAL(kVerifyTraitsAreConstexpr, \
+ __LINE__) = traits; \
+ ALLOW_UNUSED_TYPE constexpr base::SingleThreadTaskRunnerThreadMode \
+ LAZY_TASK_RUNNER_CONCATENATE_INTERNAL(kVerifyThreadModeIsConstexpr, \
+ __LINE__) = thread_mode
+
+namespace internal {
+
+template <typename TaskRunnerType, bool com_sta>
+class BASE_EXPORT LazyTaskRunner {
+ public:
+ // Use the macros above rather than a direct call to this.
+ //
+ // |traits| are TaskTraits to use to create the TaskRunner. If this
+ // LazyTaskRunner is specialized to create a SingleThreadTaskRunner,
+ // |thread_mode| specifies whether the SingleThreadTaskRunner can share its
+ // thread with other SingleThreadTaskRunner. Otherwise, it is unused.
+ static constexpr LazyTaskRunner CreateInternal(
+ const TaskTraits& traits,
+ SingleThreadTaskRunnerThreadMode thread_mode =
+ SingleThreadTaskRunnerThreadMode::SHARED) {
+ return LazyTaskRunner(traits, thread_mode);
+ }
+
+ // Returns the TaskRunner held by this instance. Creates it if it didn't
+ // already exist. Thread-safe.
+ scoped_refptr<TaskRunnerType> Get();
+
+ private:
+ constexpr LazyTaskRunner(const TaskTraits& traits,
+ SingleThreadTaskRunnerThreadMode thread_mode =
+ SingleThreadTaskRunnerThreadMode::SHARED)
+ : traits_(traits), thread_mode_(thread_mode) {}
+
+ // Releases the TaskRunner held by this instance.
+ void Reset();
+
+ // Creates and returns a new TaskRunner.
+ scoped_refptr<TaskRunnerType> Create();
+
+ // Creates a new TaskRunner via Create(), adds an explicit ref to it, and
+ // returns it raw. Used as an adapter for lazy instance helpers. Static and
+ // takes |this| as an explicit param to match the void* signature of
+ // GetOrCreateLazyPointer().
+ static TaskRunnerType* CreateRaw(void* void_self);
+
+ // TaskTraits to create the TaskRunner.
+ const TaskTraits traits_;
+
+ // SingleThreadTaskRunnerThreadMode to create the TaskRunner.
+ const SingleThreadTaskRunnerThreadMode thread_mode_;
+
+ // Can have 3 states:
+ // - This instance does not hold a TaskRunner: 0
+ // - This instance is creating a TaskRunner: kLazyInstanceStateCreating
+ // - This instance holds a TaskRunner: Pointer to the TaskRunner.
+ // LazyInstance's internals are reused to handle transition between states.
+ subtle::AtomicWord state_ = 0;
+
+ // No DISALLOW_COPY_AND_ASSIGN since that prevents static initialization with
+ // Visual Studio (warning C4592: 'symbol will be dynamically initialized
+ // (implementation limitation))'.
+};
+
+// When a LazyTaskRunner becomes active (invokes Get()), it adds a callback to
+// the current ScopedLazyTaskRunnerListForTesting, if any. Callbacks run when
+// the ScopedLazyTaskRunnerListForTesting is destroyed. In a test process, a
+// ScopedLazyTaskRunnerListForTesting must be instantiated before any
+// LazyTaskRunner becomes active.
+class BASE_EXPORT ScopedLazyTaskRunnerListForTesting {
+ public:
+ ScopedLazyTaskRunnerListForTesting();
+ ~ScopedLazyTaskRunnerListForTesting();
+
+ private:
+ friend class LazyTaskRunner<SequencedTaskRunner, false>;
+ friend class LazyTaskRunner<SingleThreadTaskRunner, false>;
+
+#if defined(OS_WIN)
+ friend class LazyTaskRunner<SingleThreadTaskRunner, true>;
+#endif
+
+ // Add |callback| to the list of callbacks to run on destruction.
+ void AddCallback(OnceClosure callback);
+
+ // Synchronizes accesses to |callbacks_|.
+ SchedulerLock lock_;
+
+ // List of callbacks to run on destruction.
+ std::vector<OnceClosure> callbacks_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedLazyTaskRunnerListForTesting);
+};
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_TASK_SCHEDULER_LAZY_TASK_RUNNER_H_
diff --git a/base/task_scheduler/lazy_task_runner_unittest.cc b/base/task_scheduler/lazy_task_runner_unittest.cc
new file mode 100644
index 0000000000..3ca09c9b37
--- /dev/null
+++ b/base/task_scheduler/lazy_task_runner_unittest.cc
@@ -0,0 +1,199 @@
+// 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/task_scheduler/lazy_task_runner.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/sequence_checker_impl.h"
+#include "base/task_scheduler/scoped_set_task_priority_for_current_thread.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_checker_impl.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include "base/win/com_init_util.h"
+#endif
+
+namespace base {
+
+namespace {
+
+LazySequencedTaskRunner g_sequenced_task_runner_user_visible =
+ LAZY_SEQUENCED_TASK_RUNNER_INITIALIZER({TaskPriority::USER_VISIBLE});
+LazySequencedTaskRunner g_sequenced_task_runner_user_blocking =
+ LAZY_SEQUENCED_TASK_RUNNER_INITIALIZER({TaskPriority::USER_BLOCKING});
+
+LazySingleThreadTaskRunner g_single_thread_task_runner_user_visible =
+ LAZY_SINGLE_THREAD_TASK_RUNNER_INITIALIZER(
+ {TaskPriority::USER_VISIBLE},
+ SingleThreadTaskRunnerThreadMode::SHARED);
+LazySingleThreadTaskRunner g_single_thread_task_runner_user_blocking =
+ LAZY_SINGLE_THREAD_TASK_RUNNER_INITIALIZER(
+ {TaskPriority::USER_BLOCKING},
+ SingleThreadTaskRunnerThreadMode::SHARED);
+
+#if defined(OS_WIN)
+LazyCOMSTATaskRunner g_com_sta_task_runner_user_visible =
+ LAZY_COM_STA_TASK_RUNNER_INITIALIZER(
+ {TaskPriority::USER_VISIBLE},
+ SingleThreadTaskRunnerThreadMode::SHARED);
+LazyCOMSTATaskRunner g_com_sta_task_runner_user_blocking =
+ LAZY_COM_STA_TASK_RUNNER_INITIALIZER(
+ {TaskPriority::USER_BLOCKING},
+ SingleThreadTaskRunnerThreadMode::SHARED);
+#endif // defined(OS_WIN)
+
+void InitCheckers(SequenceCheckerImpl* sequence_checker,
+ ThreadCheckerImpl* thread_checker) {
+ sequence_checker->DetachFromSequence();
+ EXPECT_TRUE(sequence_checker->CalledOnValidSequence());
+ thread_checker->DetachFromThread();
+ EXPECT_TRUE(thread_checker->CalledOnValidThread());
+}
+
+void ExpectSequencedEnvironment(SequenceCheckerImpl* sequence_checker,
+ ThreadCheckerImpl* thread_checker,
+ TaskPriority expected_priority) {
+ EXPECT_TRUE(sequence_checker->CalledOnValidSequence());
+ EXPECT_FALSE(thread_checker->CalledOnValidThread());
+ EXPECT_EQ(expected_priority, internal::GetTaskPriorityForCurrentThread());
+}
+
+void ExpectSingleThreadEnvironment(SequenceCheckerImpl* sequence_checker,
+ ThreadCheckerImpl* thread_checker,
+ TaskPriority expected_priority
+#if defined(OS_WIN)
+ ,
+ bool expect_com_sta = false
+#endif
+ ) {
+ EXPECT_TRUE(sequence_checker->CalledOnValidSequence());
+ EXPECT_TRUE(thread_checker->CalledOnValidThread());
+ EXPECT_EQ(expected_priority, internal::GetTaskPriorityForCurrentThread());
+
+#if defined(OS_WIN)
+ if (expect_com_sta)
+ win::AssertComApartmentType(win::ComApartmentType::STA);
+#endif
+}
+
+class TaskSchedulerLazyTaskRunnerEnvironmentTest : public testing::Test {
+ protected:
+ TaskSchedulerLazyTaskRunnerEnvironmentTest() = default;
+
+ void TestTaskRunnerEnvironment(scoped_refptr<SequencedTaskRunner> task_runner,
+ bool expect_single_thread,
+ TaskPriority expected_priority
+#if defined(OS_WIN)
+ ,
+ bool expect_com_sta = false
+#endif
+ ) {
+ SequenceCheckerImpl sequence_checker;
+ ThreadCheckerImpl thread_checker;
+ task_runner->PostTask(FROM_HERE,
+ BindOnce(&InitCheckers, Unretained(&sequence_checker),
+ Unretained(&thread_checker)));
+ scoped_task_environment_.RunUntilIdle();
+
+ OnceClosure task =
+ expect_single_thread
+ ? BindOnce(&ExpectSingleThreadEnvironment,
+ Unretained(&sequence_checker),
+ Unretained(&thread_checker), expected_priority
+#if defined(OS_WIN)
+ ,
+ expect_com_sta
+#endif
+ )
+ : BindOnce(&ExpectSequencedEnvironment,
+ Unretained(&sequence_checker),
+ Unretained(&thread_checker), expected_priority);
+ task_runner->PostTask(FROM_HERE, std::move(task));
+ scoped_task_environment_.RunUntilIdle();
+ }
+
+ test::ScopedTaskEnvironment scoped_task_environment_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerLazyTaskRunnerEnvironmentTest);
+};
+
+} // namespace
+
+TEST_F(TaskSchedulerLazyTaskRunnerEnvironmentTest,
+ LazySequencedTaskRunnerUserVisible) {
+ TestTaskRunnerEnvironment(g_sequenced_task_runner_user_visible.Get(), false,
+ TaskPriority::USER_VISIBLE);
+}
+
+TEST_F(TaskSchedulerLazyTaskRunnerEnvironmentTest,
+ LazySequencedTaskRunnerUserBlocking) {
+ TestTaskRunnerEnvironment(g_sequenced_task_runner_user_blocking.Get(), false,
+ TaskPriority::USER_BLOCKING);
+}
+
+TEST_F(TaskSchedulerLazyTaskRunnerEnvironmentTest,
+ LazySingleThreadTaskRunnerUserVisible) {
+ TestTaskRunnerEnvironment(g_single_thread_task_runner_user_visible.Get(),
+ true, TaskPriority::USER_VISIBLE);
+}
+
+TEST_F(TaskSchedulerLazyTaskRunnerEnvironmentTest,
+ LazySingleThreadTaskRunnerUserBlocking) {
+ TestTaskRunnerEnvironment(g_single_thread_task_runner_user_blocking.Get(),
+ true, TaskPriority::USER_BLOCKING);
+}
+
+#if defined(OS_WIN)
+TEST_F(TaskSchedulerLazyTaskRunnerEnvironmentTest,
+ LazyCOMSTATaskRunnerUserVisible) {
+ TestTaskRunnerEnvironment(g_com_sta_task_runner_user_visible.Get(), true,
+ TaskPriority::USER_VISIBLE, true);
+}
+
+TEST_F(TaskSchedulerLazyTaskRunnerEnvironmentTest,
+ LazyCOMSTATaskRunnerUserBlocking) {
+ TestTaskRunnerEnvironment(g_com_sta_task_runner_user_blocking.Get(), true,
+ TaskPriority::USER_BLOCKING, true);
+}
+#endif // defined(OS_WIN)
+
+TEST(TaskSchdulerLazyTaskRunnerTest, LazySequencedTaskRunnerReset) {
+ for (int i = 0; i < 2; ++i) {
+ test::ScopedTaskEnvironment scoped_task_environment;
+ // If the TaskRunner isn't released when the test::ScopedTaskEnvironment
+ // goes out of scope, the second invocation of the line below will access a
+ // deleted TaskScheduler and crash.
+ g_sequenced_task_runner_user_visible.Get()->PostTask(FROM_HERE,
+ DoNothing());
+ }
+}
+
+TEST(TaskSchdulerLazyTaskRunnerTest, LazySingleThreadTaskRunnerReset) {
+ for (int i = 0; i < 2; ++i) {
+ test::ScopedTaskEnvironment scoped_task_environment;
+ // If the TaskRunner isn't released when the test::ScopedTaskEnvironment
+ // goes out of scope, the second invocation of the line below will access a
+ // deleted TaskScheduler and crash.
+ g_single_thread_task_runner_user_visible.Get()->PostTask(FROM_HERE,
+ DoNothing());
+ }
+}
+
+#if defined(OS_WIN)
+TEST(TaskSchdulerLazyTaskRunnerTest, LazyCOMSTATaskRunnerReset) {
+ for (int i = 0; i < 2; ++i) {
+ test::ScopedTaskEnvironment scoped_task_environment;
+ // If the TaskRunner isn't released when the test::ScopedTaskEnvironment
+ // goes out of scope, the second invocation of the line below will access a
+ // deleted TaskScheduler and crash.
+ g_com_sta_task_runner_user_visible.Get()->PostTask(FROM_HERE, DoNothing());
+ }
+}
+#endif // defined(OS_WIN)
+
+} // namespace base
diff --git a/base/task_scheduler/post_task.cc b/base/task_scheduler/post_task.cc
new file mode 100644
index 0000000000..15210a5556
--- /dev/null
+++ b/base/task_scheduler/post_task.cc
@@ -0,0 +1,132 @@
+// 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/task_scheduler/post_task.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/task_scheduler/scoped_set_task_priority_for_current_thread.h"
+#include "base/task_scheduler/task_scheduler.h"
+#include "base/threading/post_task_and_reply_impl.h"
+
+namespace base {
+
+namespace {
+
+class PostTaskAndReplyWithTraitsTaskRunner
+ : public internal::PostTaskAndReplyImpl {
+ public:
+ explicit PostTaskAndReplyWithTraitsTaskRunner(const TaskTraits& traits)
+ : traits_(traits) {}
+
+ private:
+ bool PostTask(const Location& from_here, OnceClosure task) override {
+ PostTaskWithTraits(from_here, traits_, std::move(task));
+ return true;
+ }
+
+ const TaskTraits traits_;
+};
+
+// Returns TaskTraits based on |traits|. If TaskPriority hasn't been set
+// explicitly in |traits|, the returned TaskTraits have the current
+// TaskPriority.
+TaskTraits GetTaskTraitsWithExplicitPriority(const TaskTraits& traits) {
+ if (traits.priority_set_explicitly())
+ return traits;
+ return TaskTraits::Override(traits,
+ {internal::GetTaskPriorityForCurrentThread()});
+}
+
+} // namespace
+
+void PostTask(const Location& from_here, OnceClosure task) {
+ PostDelayedTask(from_here, std::move(task), TimeDelta());
+}
+
+void PostDelayedTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay) {
+ PostDelayedTaskWithTraits(from_here, TaskTraits(), std::move(task), delay);
+}
+
+void PostTaskAndReply(const Location& from_here,
+ OnceClosure task,
+ OnceClosure reply) {
+ PostTaskWithTraitsAndReply(from_here, TaskTraits(), std::move(task),
+ std::move(reply));
+}
+
+void PostTaskWithTraits(const Location& from_here,
+ const TaskTraits& traits,
+ OnceClosure task) {
+ PostDelayedTaskWithTraits(from_here, traits, std::move(task), TimeDelta());
+}
+
+void PostDelayedTaskWithTraits(const Location& from_here,
+ const TaskTraits& traits,
+ OnceClosure task,
+ TimeDelta delay) {
+ DCHECK(TaskScheduler::GetInstance())
+ << "Ref. Prerequisite section of post_task.h.\n\n"
+ "Hint: if this is in a unit test, you're likely merely missing a "
+ "base::test::ScopedTaskEnvironment member in your fixture.\n";
+ TaskScheduler::GetInstance()->PostDelayedTaskWithTraits(
+ from_here, GetTaskTraitsWithExplicitPriority(traits), std::move(task),
+ std::move(delay));
+}
+
+void PostTaskWithTraitsAndReply(const Location& from_here,
+ const TaskTraits& traits,
+ OnceClosure task,
+ OnceClosure reply) {
+ PostTaskAndReplyWithTraitsTaskRunner(traits).PostTaskAndReply(
+ from_here, std::move(task), std::move(reply));
+}
+
+scoped_refptr<TaskRunner> CreateTaskRunnerWithTraits(const TaskTraits& traits) {
+ DCHECK(TaskScheduler::GetInstance())
+ << "Ref. Prerequisite section of post_task.h.\n\n"
+ "Hint: if this is in a unit test, you're likely merely missing a "
+ "base::test::ScopedTaskEnvironment member in your fixture.\n";
+ return TaskScheduler::GetInstance()->CreateTaskRunnerWithTraits(
+ GetTaskTraitsWithExplicitPriority(traits));
+}
+
+scoped_refptr<SequencedTaskRunner> CreateSequencedTaskRunnerWithTraits(
+ const TaskTraits& traits) {
+ DCHECK(TaskScheduler::GetInstance())
+ << "Ref. Prerequisite section of post_task.h.\n\n"
+ "Hint: if this is in a unit test, you're likely merely missing a "
+ "base::test::ScopedTaskEnvironment member in your fixture.\n";
+ return TaskScheduler::GetInstance()->CreateSequencedTaskRunnerWithTraits(
+ GetTaskTraitsWithExplicitPriority(traits));
+}
+
+scoped_refptr<SingleThreadTaskRunner> CreateSingleThreadTaskRunnerWithTraits(
+ const TaskTraits& traits,
+ SingleThreadTaskRunnerThreadMode thread_mode) {
+ DCHECK(TaskScheduler::GetInstance())
+ << "Ref. Prerequisite section of post_task.h.\n\n"
+ "Hint: if this is in a unit test, you're likely merely missing a "
+ "base::test::ScopedTaskEnvironment member in your fixture.\n";
+ return TaskScheduler::GetInstance()->CreateSingleThreadTaskRunnerWithTraits(
+ GetTaskTraitsWithExplicitPriority(traits), thread_mode);
+}
+
+#if defined(OS_WIN)
+scoped_refptr<SingleThreadTaskRunner> CreateCOMSTATaskRunnerWithTraits(
+ const TaskTraits& traits,
+ SingleThreadTaskRunnerThreadMode thread_mode) {
+ DCHECK(TaskScheduler::GetInstance())
+ << "Ref. Prerequisite section of post_task.h.\n\n"
+ "Hint: if this is in a unit test, you're likely merely missing a "
+ "base::test::ScopedTaskEnvironment member in your fixture.\n";
+ return TaskScheduler::GetInstance()->CreateCOMSTATaskRunnerWithTraits(
+ GetTaskTraitsWithExplicitPriority(traits), thread_mode);
+}
+#endif // defined(OS_WIN)
+
+} // namespace base
diff --git a/base/task_scheduler/post_task.h b/base/task_scheduler/post_task.h
new file mode 100644
index 0000000000..d757c8598c
--- /dev/null
+++ b/base/task_scheduler/post_task.h
@@ -0,0 +1,225 @@
+// 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_TASK_SCHEDULER_POST_TASK_H_
+#define BASE_TASK_SCHEDULER_POST_TASK_H_
+
+#include <utility>
+
+#include "base/base_export.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/memory/ref_counted.h"
+#include "base/post_task_and_reply_with_result_internal.h"
+#include "base/sequenced_task_runner.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task_runner.h"
+#include "base/task_scheduler/single_thread_task_runner_thread_mode.h"
+#include "base/task_scheduler/task_traits.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+
+namespace base {
+
+// This is the preferred interface to post tasks to the TaskScheduler.
+//
+// To post a simple one-off task with default traits:
+// PostTask(FROM_HERE, Bind(...));
+//
+// To post a high priority one-off task to respond to a user interaction:
+// PostTaskWithTraits(
+// FROM_HERE,
+// {TaskPriority::USER_BLOCKING},
+// Bind(...));
+//
+// To post tasks that must run in sequence with default traits:
+// scoped_refptr<SequencedTaskRunner> task_runner =
+// CreateSequencedTaskRunnerWithTraits(TaskTraits());
+// task_runner.PostTask(FROM_HERE, Bind(...));
+// task_runner.PostTask(FROM_HERE, Bind(...));
+//
+// To post tasks that may block, must run in sequence and can be skipped on
+// shutdown:
+// scoped_refptr<SequencedTaskRunner> task_runner =
+// CreateSequencedTaskRunnerWithTraits(
+// {MayBlock(), TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
+// task_runner.PostTask(FROM_HERE, Bind(...));
+// task_runner.PostTask(FROM_HERE, Bind(...));
+//
+// The default traits apply to tasks that:
+// (1) don't block (ref. MayBlock() and WithBaseSyncPrimitives()),
+// (2) prefer inheriting the current priority to specifying their own, and
+// (3) can either block shutdown or be skipped on shutdown
+// (TaskScheduler implementation is free to choose a fitting default).
+// Explicit traits must be specified for tasks for which these loose
+// requirements are not sufficient.
+//
+// Tasks posted through functions below will run on threads owned by the
+// registered TaskScheduler (i.e. not on the main thread). Tasks posted through
+// functions below with a delay may be coalesced (i.e. delays may be adjusted to
+// reduce the number of wakeups and hence power consumption).
+//
+// Prerequisite: A TaskScheduler must have been registered for the current
+// process via TaskScheduler::SetInstance() before the functions below are
+// valid. This is typically done during the initialization phase in each
+// process. If your code is not running in that phase, you most likely don't
+// have to worry about this. You will encounter DCHECKs or nullptr dereferences
+// if this is violated. For tests, prefer base::test::ScopedTaskEnvironment.
+
+// Posts |task| to the TaskScheduler. Calling this is equivalent to calling
+// PostTaskWithTraits with plain TaskTraits.
+BASE_EXPORT void PostTask(const Location& from_here, OnceClosure task);
+
+// Posts |task| to the TaskScheduler. |task| will not run before |delay|
+// expires. Calling this is equivalent to calling PostDelayedTaskWithTraits with
+// plain TaskTraits.
+//
+// Use PostDelayedTaskWithTraits to specify a BACKGROUND priority if the task
+// doesn't have to run as soon as |delay| expires.
+BASE_EXPORT void PostDelayedTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay);
+
+// Posts |task| to the TaskScheduler and posts |reply| on the caller's execution
+// context (i.e. same sequence or thread and same TaskTraits if applicable) when
+// |task| completes. Calling this is equivalent to calling
+// PostTaskWithTraitsAndReply with plain TaskTraits. Can only be called when
+// SequencedTaskRunnerHandle::IsSet().
+BASE_EXPORT void PostTaskAndReply(const Location& from_here,
+ OnceClosure task,
+ OnceClosure reply);
+
+// Posts |task| to the TaskScheduler and posts |reply| with the return value of
+// |task| as argument on the caller's execution context (i.e. same sequence or
+// thread and same TaskTraits if applicable) when |task| completes. Calling this
+// is equivalent to calling PostTaskWithTraitsAndReplyWithResult with plain
+// TaskTraits. Can only be called when SequencedTaskRunnerHandle::IsSet().
+template <typename TaskReturnType, typename ReplyArgType>
+void PostTaskAndReplyWithResult(const Location& from_here,
+ OnceCallback<TaskReturnType()> task,
+ OnceCallback<void(ReplyArgType)> reply) {
+ PostTaskWithTraitsAndReplyWithResult(from_here, TaskTraits(), std::move(task),
+ std::move(reply));
+}
+
+// Callback version of PostTaskAndReplyWithResult above.
+// Though RepeatingCallback is convertible to OnceCallback, we need this since
+// we can not use template deduction and object conversion at once on the
+// overload resolution.
+// TODO(tzik): Update all callers of the Callback version to use OnceCallback.
+template <typename TaskReturnType, typename ReplyArgType>
+void PostTaskAndReplyWithResult(const Location& from_here,
+ Callback<TaskReturnType()> task,
+ Callback<void(ReplyArgType)> reply) {
+ PostTaskAndReplyWithResult(
+ from_here, OnceCallback<TaskReturnType()>(std::move(task)),
+ OnceCallback<void(ReplyArgType)>(std::move(reply)));
+}
+
+// Posts |task| with specific |traits| to the TaskScheduler.
+BASE_EXPORT void PostTaskWithTraits(const Location& from_here,
+ const TaskTraits& traits,
+ OnceClosure task);
+
+// Posts |task| with specific |traits| to the TaskScheduler. |task| will not run
+// before |delay| expires.
+//
+// Specify a BACKGROUND priority via |traits| if the task doesn't have to run as
+// soon as |delay| expires.
+BASE_EXPORT void PostDelayedTaskWithTraits(const Location& from_here,
+ const TaskTraits& traits,
+ OnceClosure task,
+ TimeDelta delay);
+
+// Posts |task| with specific |traits| to the TaskScheduler and posts |reply| on
+// the caller's execution context (i.e. same sequence or thread and same
+// TaskTraits if applicable) when |task| completes. Can only be called when
+// SequencedTaskRunnerHandle::IsSet().
+BASE_EXPORT void PostTaskWithTraitsAndReply(const Location& from_here,
+ const TaskTraits& traits,
+ OnceClosure task,
+ OnceClosure reply);
+
+// Posts |task| with specific |traits| to the TaskScheduler and posts |reply|
+// with the return value of |task| as argument on the caller's execution context
+// (i.e. same sequence or thread and same TaskTraits if applicable) when |task|
+// completes. Can only be called when SequencedTaskRunnerHandle::IsSet().
+template <typename TaskReturnType, typename ReplyArgType>
+void PostTaskWithTraitsAndReplyWithResult(
+ const Location& from_here,
+ const TaskTraits& traits,
+ OnceCallback<TaskReturnType()> task,
+ OnceCallback<void(ReplyArgType)> reply) {
+ TaskReturnType* result = new TaskReturnType();
+ return PostTaskWithTraitsAndReply(
+ from_here, traits,
+ BindOnce(&internal::ReturnAsParamAdapter<TaskReturnType>, std::move(task),
+ result),
+ BindOnce(&internal::ReplyAdapter<TaskReturnType, ReplyArgType>,
+ std::move(reply), Owned(result)));
+}
+
+// Callback version of PostTaskWithTraitsAndReplyWithResult above.
+// Though RepeatingCallback is convertible to OnceCallback, we need this since
+// we can not use template deduction and object conversion at once on the
+// overload resolution.
+// TODO(tzik): Update all callers of the Callback version to use OnceCallback.
+template <typename TaskReturnType, typename ReplyArgType>
+void PostTaskWithTraitsAndReplyWithResult(const Location& from_here,
+ const TaskTraits& traits,
+ Callback<TaskReturnType()> task,
+ Callback<void(ReplyArgType)> reply) {
+ PostTaskWithTraitsAndReplyWithResult(
+ from_here, traits, OnceCallback<TaskReturnType()>(std::move(task)),
+ OnceCallback<void(ReplyArgType)>(std::move(reply)));
+}
+
+// Returns a TaskRunner whose PostTask invocations result in scheduling tasks
+// using |traits|. Tasks may run in any order and in parallel.
+BASE_EXPORT scoped_refptr<TaskRunner> CreateTaskRunnerWithTraits(
+ const TaskTraits& traits);
+
+// Returns a SequencedTaskRunner whose PostTask invocations result in scheduling
+// tasks using |traits|. Tasks run one at a time in posting order.
+BASE_EXPORT scoped_refptr<SequencedTaskRunner>
+CreateSequencedTaskRunnerWithTraits(const TaskTraits& traits);
+
+// Returns a SingleThreadTaskRunner whose PostTask invocations result in
+// scheduling tasks using |traits| on a thread determined by |thread_mode|. See
+// base/task_scheduler/single_thread_task_runner_thread_mode.h for |thread_mode|
+// details. Tasks run on a single thread in posting order.
+//
+// If all you need is to make sure that tasks don't run concurrently (e.g.
+// because they access a data structure which is not thread-safe), use
+// CreateSequencedTaskRunnerWithTraits(). Only use this if you rely on a thread-
+// affine API (it might be safer to assume thread-affinity when dealing with
+// under-documented third-party APIs, e.g. other OS') or share data across tasks
+// using thread-local storage.
+BASE_EXPORT scoped_refptr<SingleThreadTaskRunner>
+CreateSingleThreadTaskRunnerWithTraits(
+ const TaskTraits& traits,
+ SingleThreadTaskRunnerThreadMode thread_mode =
+ SingleThreadTaskRunnerThreadMode::SHARED);
+
+#if defined(OS_WIN)
+// Returns a SingleThreadTaskRunner whose PostTask invocations result in
+// scheduling tasks using |traits| in a COM Single-Threaded Apartment on a
+// thread determined by |thread_mode|. See
+// base/task_scheduler/single_thread_task_runner_thread_mode.h for |thread_mode|
+// details. Tasks run in the same Single-Threaded Apartment in posting order for
+// the returned SingleThreadTaskRunner. There is not necessarily a one-to-one
+// correspondence between SingleThreadTaskRunners and Single-Threaded
+// Apartments. The implementation is free to share apartments or create new
+// apartments as necessary. In either case, care should be taken to make sure
+// COM pointers are not smuggled across apartments.
+BASE_EXPORT scoped_refptr<SingleThreadTaskRunner>
+CreateCOMSTATaskRunnerWithTraits(const TaskTraits& traits,
+ SingleThreadTaskRunnerThreadMode thread_mode =
+ SingleThreadTaskRunnerThreadMode::SHARED);
+#endif // defined(OS_WIN)
+
+} // namespace base
+
+#endif // BASE_TASK_SCHEDULER_POST_TASK_H_
diff --git a/base/task_scheduler/scheduler_worker_observer.h b/base/task_scheduler/scheduler_worker_observer.h
new file mode 100644
index 0000000000..5e6fc8fa0a
--- /dev/null
+++ b/base/task_scheduler/scheduler_worker_observer.h
@@ -0,0 +1,27 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SCHEDULER_SCHEDULER_WORKER_OBSERVER_H_
+#define BASE_TASK_SCHEDULER_SCHEDULER_WORKER_OBSERVER_H_
+
+namespace base {
+
+// Interface to observe entry and exit of the main function of a TaskScheduler
+// worker.
+class SchedulerWorkerObserver {
+ public:
+ virtual ~SchedulerWorkerObserver() = default;
+
+ // Invoked at the beginning of the main function of a TaskScheduler worker,
+ // before any task runs.
+ virtual void OnSchedulerWorkerMainEntry() = 0;
+
+ // Invoked at the end of the main function of a TaskScheduler worker, when it
+ // can no longer run tasks.
+ virtual void OnSchedulerWorkerMainExit() = 0;
+};
+
+} // namespace base
+
+#endif // BASE_TASK_SCHEDULER_SCHEDULER_WORKER_OBSERVER_H_
diff --git a/base/task_scheduler/scheduler_worker_pool.cc b/base/task_scheduler/scheduler_worker_pool.cc
new file mode 100644
index 0000000000..4bb5ca70ed
--- /dev/null
+++ b/base/task_scheduler/scheduler_worker_pool.cc
@@ -0,0 +1,219 @@
+// 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/task_scheduler/scheduler_worker_pool.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/lazy_instance.h"
+#include "base/task_scheduler/delayed_task_manager.h"
+#include "base/task_scheduler/task_tracker.h"
+#include "base/threading/thread_local.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+
+// The number of SchedulerWorkerPool that are alive in this process. This
+// variable should only be incremented when the SchedulerWorkerPool instances
+// are brought up (on the main thread; before any tasks are posted) and
+// decremented when the same instances are brought down (i.e., only when unit
+// tests tear down the task environment and never in production). This makes the
+// variable const while worker threads are up and as such it doesn't need to be
+// atomic. It is used to tell when a task is posted from the main thread after
+// the task environment was brought down in unit tests so that
+// SchedulerWorkerPool bound TaskRunners can return false on PostTask, letting
+// such callers know they should complete necessary work synchronously. Note:
+// |!g_active_pools_count| is generally equivalent to
+// |!TaskScheduler::GetInstance()| but has the advantage of being valid in
+// task_scheduler unit tests that don't instantiate a full TaskScheduler.
+int g_active_pools_count = 0;
+
+// SchedulerWorkerPool that owns the current thread, if any.
+LazyInstance<ThreadLocalPointer<const SchedulerWorkerPool>>::Leaky
+ tls_current_worker_pool = LAZY_INSTANCE_INITIALIZER;
+
+const SchedulerWorkerPool* GetCurrentWorkerPool() {
+ return tls_current_worker_pool.Get().Get();
+}
+
+} // namespace
+
+// A task runner that runs tasks in parallel.
+class SchedulerParallelTaskRunner : public TaskRunner {
+ public:
+ // Constructs a SchedulerParallelTaskRunner which can be used to post tasks so
+ // long as |worker_pool| is alive.
+ // TODO(robliao): Find a concrete way to manage |worker_pool|'s memory.
+ SchedulerParallelTaskRunner(const TaskTraits& traits,
+ SchedulerWorkerPool* worker_pool)
+ : traits_(traits), worker_pool_(worker_pool) {
+ DCHECK(worker_pool_);
+ }
+
+ // TaskRunner:
+ bool PostDelayedTask(const Location& from_here,
+ OnceClosure closure,
+ TimeDelta delay) override {
+ if (!g_active_pools_count)
+ return false;
+
+ // Post the task as part of a one-off single-task Sequence.
+ return worker_pool_->PostTaskWithSequence(
+ Task(from_here, std::move(closure), traits_, delay),
+ MakeRefCounted<Sequence>());
+ }
+
+ bool RunsTasksInCurrentSequence() const override {
+ return GetCurrentWorkerPool() == worker_pool_;
+ }
+
+ private:
+ ~SchedulerParallelTaskRunner() override = default;
+
+ const TaskTraits traits_;
+ SchedulerWorkerPool* const worker_pool_;
+
+ DISALLOW_COPY_AND_ASSIGN(SchedulerParallelTaskRunner);
+};
+
+// A task runner that runs tasks in sequence.
+class SchedulerSequencedTaskRunner : public SequencedTaskRunner {
+ public:
+ // Constructs a SchedulerSequencedTaskRunner which can be used to post tasks
+ // so long as |worker_pool| is alive.
+ // TODO(robliao): Find a concrete way to manage |worker_pool|'s memory.
+ SchedulerSequencedTaskRunner(const TaskTraits& traits,
+ SchedulerWorkerPool* worker_pool)
+ : traits_(traits), worker_pool_(worker_pool) {
+ DCHECK(worker_pool_);
+ }
+
+ // SequencedTaskRunner:
+ bool PostDelayedTask(const Location& from_here,
+ OnceClosure closure,
+ TimeDelta delay) override {
+ if (!g_active_pools_count)
+ return false;
+
+ Task task(from_here, std::move(closure), traits_, delay);
+ task.sequenced_task_runner_ref = this;
+
+ // Post the task as part of |sequence_|.
+ return worker_pool_->PostTaskWithSequence(std::move(task), sequence_);
+ }
+
+ bool PostNonNestableDelayedTask(const Location& from_here,
+ OnceClosure closure,
+ base::TimeDelta delay) override {
+ // Tasks are never nested within the task scheduler.
+ return PostDelayedTask(from_here, std::move(closure), delay);
+ }
+
+ bool RunsTasksInCurrentSequence() const override {
+ return sequence_->token() == SequenceToken::GetForCurrentThread();
+ }
+
+ private:
+ ~SchedulerSequencedTaskRunner() override = default;
+
+ // Sequence for all Tasks posted through this TaskRunner.
+ const scoped_refptr<Sequence> sequence_ = MakeRefCounted<Sequence>();
+
+ const TaskTraits traits_;
+ SchedulerWorkerPool* const worker_pool_;
+
+ DISALLOW_COPY_AND_ASSIGN(SchedulerSequencedTaskRunner);
+};
+
+scoped_refptr<TaskRunner> SchedulerWorkerPool::CreateTaskRunnerWithTraits(
+ const TaskTraits& traits) {
+ return MakeRefCounted<SchedulerParallelTaskRunner>(traits, this);
+}
+
+scoped_refptr<SequencedTaskRunner>
+SchedulerWorkerPool::CreateSequencedTaskRunnerWithTraits(
+ const TaskTraits& traits) {
+ return MakeRefCounted<SchedulerSequencedTaskRunner>(traits, this);
+}
+
+bool SchedulerWorkerPool::PostTaskWithSequence(
+ Task task,
+ scoped_refptr<Sequence> sequence) {
+ DCHECK(task.task);
+ DCHECK(sequence);
+
+ if (!task_tracker_->WillPostTask(&task))
+ return false;
+
+ if (task.delayed_run_time.is_null()) {
+ PostTaskWithSequenceNow(std::move(task), std::move(sequence));
+ } else {
+ // Use CHECK instead of DCHECK to crash earlier. See http://crbug.com/711167
+ // for details.
+ CHECK(task.task);
+ delayed_task_manager_->AddDelayedTask(
+ std::move(task), BindOnce(
+ [](scoped_refptr<Sequence> sequence,
+ SchedulerWorkerPool* worker_pool, Task task) {
+ worker_pool->PostTaskWithSequenceNow(
+ std::move(task), std::move(sequence));
+ },
+ std::move(sequence), Unretained(this)));
+ }
+
+ return true;
+}
+
+SchedulerWorkerPool::SchedulerWorkerPool(
+ TrackedRef<TaskTracker> task_tracker,
+ DelayedTaskManager* delayed_task_manager)
+ : task_tracker_(std::move(task_tracker)),
+ delayed_task_manager_(delayed_task_manager) {
+ DCHECK(task_tracker_);
+ DCHECK(delayed_task_manager_);
+ ++g_active_pools_count;
+}
+
+SchedulerWorkerPool::~SchedulerWorkerPool() {
+ --g_active_pools_count;
+ DCHECK_GE(g_active_pools_count, 0);
+}
+
+void SchedulerWorkerPool::BindToCurrentThread() {
+ DCHECK(!GetCurrentWorkerPool());
+ tls_current_worker_pool.Get().Set(this);
+}
+
+void SchedulerWorkerPool::UnbindFromCurrentThread() {
+ DCHECK(GetCurrentWorkerPool());
+ tls_current_worker_pool.Get().Set(nullptr);
+}
+
+void SchedulerWorkerPool::PostTaskWithSequenceNow(
+ Task task,
+ scoped_refptr<Sequence> sequence) {
+ DCHECK(task.task);
+ DCHECK(sequence);
+
+ // Confirm that |task| is ready to run (its delayed run time is either null or
+ // in the past).
+ DCHECK_LE(task.delayed_run_time, TimeTicks::Now());
+
+ const bool sequence_was_empty = sequence->PushTask(std::move(task));
+ if (sequence_was_empty) {
+ // Try to schedule |sequence| if it was empty before |task| was inserted
+ // into it. Otherwise, one of these must be true:
+ // - |sequence| is already scheduled, or,
+ // - The pool is running a Task from |sequence|. The pool is expected to
+ // reschedule |sequence| once it's done running the Task.
+ sequence = task_tracker_->WillScheduleSequence(std::move(sequence), this);
+ if (sequence)
+ OnCanScheduleSequence(std::move(sequence));
+ }
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/scheduler_worker_pool_unittest.cc b/base/task_scheduler/scheduler_worker_pool_unittest.cc
new file mode 100644
index 0000000000..029345743b
--- /dev/null
+++ b/base/task_scheduler/scheduler_worker_pool_unittest.cc
@@ -0,0 +1,343 @@
+// 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/task_scheduler/scheduler_worker_pool.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/location.h"
+#include "base/memory/ref_counted.h"
+#include "base/task_runner.h"
+#include "base/task_scheduler/delayed_task_manager.h"
+#include "base/task_scheduler/scheduler_worker_pool_impl.h"
+#include "base/task_scheduler/scheduler_worker_pool_params.h"
+#include "base/task_scheduler/task_tracker.h"
+#include "base/task_scheduler/task_traits.h"
+#include "base/task_scheduler/test_task_factory.h"
+#include "base/task_scheduler/test_utils.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/simple_thread.h"
+#include "base/threading/thread.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include "base/task_scheduler/platform_native_worker_pool_win.h"
+#endif
+
+namespace base {
+namespace internal {
+
+namespace {
+
+constexpr size_t kMaxTasks = 4;
+// By default, tests allow half of the pool to be used by background tasks.
+constexpr size_t kMaxBackgroundTasks = kMaxTasks / 2;
+constexpr size_t kNumThreadsPostingTasks = 4;
+constexpr size_t kNumTasksPostedPerThread = 150;
+
+enum class PoolType {
+ GENERIC,
+#if defined(OS_WIN)
+ WINDOWS,
+#endif
+};
+
+struct PoolExecutionType {
+ PoolType pool_type;
+ test::ExecutionMode execution_mode;
+};
+
+using PostNestedTask = test::TestTaskFactory::PostNestedTask;
+
+class ThreadPostingTasks : public SimpleThread {
+ public:
+ // Constructs a thread that posts |num_tasks_posted_per_thread| tasks to
+ // |worker_pool| through an |execution_mode| task runner. If
+ // |post_nested_task| is YES, each task posted by this thread posts another
+ // task when it runs.
+ ThreadPostingTasks(SchedulerWorkerPool* worker_pool,
+ test::ExecutionMode execution_mode,
+ PostNestedTask post_nested_task)
+ : SimpleThread("ThreadPostingTasks"),
+ worker_pool_(worker_pool),
+ post_nested_task_(post_nested_task),
+ factory_(test::CreateTaskRunnerWithExecutionMode(worker_pool,
+ execution_mode),
+ execution_mode) {
+ DCHECK(worker_pool_);
+ }
+
+ const test::TestTaskFactory* factory() const { return &factory_; }
+
+ private:
+ void Run() override {
+ EXPECT_FALSE(factory_.task_runner()->RunsTasksInCurrentSequence());
+
+ for (size_t i = 0; i < kNumTasksPostedPerThread; ++i)
+ EXPECT_TRUE(factory_.PostTask(post_nested_task_, Closure()));
+ }
+
+ SchedulerWorkerPool* const worker_pool_;
+ const scoped_refptr<TaskRunner> task_runner_;
+ const PostNestedTask post_nested_task_;
+ test::TestTaskFactory factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadPostingTasks);
+};
+
+class TaskSchedulerWorkerPoolTest
+ : public testing::TestWithParam<PoolExecutionType> {
+ protected:
+ TaskSchedulerWorkerPoolTest()
+ : service_thread_("TaskSchedulerServiceThread") {}
+
+ void SetUp() override {
+ service_thread_.Start();
+ delayed_task_manager_.Start(service_thread_.task_runner());
+ CreateWorkerPool();
+ }
+
+ void TearDown() override {
+ service_thread_.Stop();
+ if (worker_pool_)
+ worker_pool_->JoinForTesting();
+ }
+
+ void CreateWorkerPool() {
+ ASSERT_FALSE(worker_pool_);
+ switch (GetParam().pool_type) {
+ case PoolType::GENERIC:
+ worker_pool_ = std::make_unique<SchedulerWorkerPoolImpl>(
+ "TestWorkerPool", "A", ThreadPriority::NORMAL,
+ task_tracker_.GetTrackedRef(), &delayed_task_manager_);
+ break;
+#if defined(OS_WIN)
+ case PoolType::WINDOWS:
+ worker_pool_ = std::make_unique<PlatformNativeWorkerPoolWin>(
+ task_tracker_.GetTrackedRef(), &delayed_task_manager_);
+ break;
+#endif
+ }
+ ASSERT_TRUE(worker_pool_);
+ }
+
+ void StartWorkerPool() {
+ ASSERT_TRUE(worker_pool_);
+ switch (GetParam().pool_type) {
+ case PoolType::GENERIC: {
+ SchedulerWorkerPoolImpl* scheduler_worker_pool_impl =
+ static_cast<SchedulerWorkerPoolImpl*>(worker_pool_.get());
+ scheduler_worker_pool_impl->Start(
+ SchedulerWorkerPoolParams(kMaxTasks, TimeDelta::Max()),
+ kMaxBackgroundTasks, service_thread_.task_runner(), nullptr,
+ SchedulerWorkerPoolImpl::WorkerEnvironment::NONE);
+ break;
+ }
+#if defined(OS_WIN)
+ case PoolType::WINDOWS: {
+ PlatformNativeWorkerPoolWin* scheduler_worker_pool_windows_impl =
+ static_cast<PlatformNativeWorkerPoolWin*>(worker_pool_.get());
+ scheduler_worker_pool_windows_impl->Start();
+ break;
+ }
+#endif
+ }
+ }
+
+ Thread service_thread_;
+ TaskTracker task_tracker_ = {"Test"};
+ DelayedTaskManager delayed_task_manager_;
+
+ std::unique_ptr<SchedulerWorkerPool> worker_pool_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TaskSchedulerWorkerPoolTest);
+};
+
+void ShouldNotRun() {
+ ADD_FAILURE() << "Ran a task that shouldn't run.";
+}
+
+} // namespace
+
+TEST_P(TaskSchedulerWorkerPoolTest, PostTasks) {
+ StartWorkerPool();
+ // Create threads to post tasks.
+ std::vector<std::unique_ptr<ThreadPostingTasks>> threads_posting_tasks;
+ for (size_t i = 0; i < kNumThreadsPostingTasks; ++i) {
+ threads_posting_tasks.push_back(std::make_unique<ThreadPostingTasks>(
+ worker_pool_.get(), GetParam().execution_mode, PostNestedTask::NO));
+ threads_posting_tasks.back()->Start();
+ }
+
+ // Wait for all tasks to run.
+ for (const auto& thread_posting_tasks : threads_posting_tasks) {
+ thread_posting_tasks->Join();
+ thread_posting_tasks->factory()->WaitForAllTasksToRun();
+ }
+
+ // Flush the task tracker to be sure that no task accesses its TestTaskFactory
+ // after |thread_posting_tasks| is destroyed.
+ task_tracker_.FlushForTesting();
+}
+
+TEST_P(TaskSchedulerWorkerPoolTest, NestedPostTasks) {
+ StartWorkerPool();
+ // Create threads to post tasks. Each task posted by these threads will post
+ // another task when it runs.
+ std::vector<std::unique_ptr<ThreadPostingTasks>> threads_posting_tasks;
+ for (size_t i = 0; i < kNumThreadsPostingTasks; ++i) {
+ threads_posting_tasks.push_back(std::make_unique<ThreadPostingTasks>(
+ worker_pool_.get(), GetParam().execution_mode, PostNestedTask::YES));
+ threads_posting_tasks.back()->Start();
+ }
+
+ // Wait for all tasks to run.
+ for (const auto& thread_posting_tasks : threads_posting_tasks) {
+ thread_posting_tasks->Join();
+ thread_posting_tasks->factory()->WaitForAllTasksToRun();
+ }
+
+ // Flush the task tracker to be sure that no task accesses its TestTaskFactory
+ // after |thread_posting_tasks| is destroyed.
+ task_tracker_.FlushForTesting();
+}
+
+// Verify that a Task can't be posted after shutdown.
+TEST_P(TaskSchedulerWorkerPoolTest, PostTaskAfterShutdown) {
+ StartWorkerPool();
+ auto task_runner = test::CreateTaskRunnerWithExecutionMode(
+ worker_pool_.get(), GetParam().execution_mode);
+ task_tracker_.Shutdown();
+ EXPECT_FALSE(task_runner->PostTask(FROM_HERE, BindOnce(&ShouldNotRun)));
+}
+
+// Verify that posting tasks after the pool was destroyed fails but doesn't
+// crash.
+TEST_P(TaskSchedulerWorkerPoolTest, PostAfterDestroy) {
+ StartWorkerPool();
+ auto task_runner = test::CreateTaskRunnerWithExecutionMode(
+ worker_pool_.get(), GetParam().execution_mode);
+ EXPECT_TRUE(task_runner->PostTask(FROM_HERE, DoNothing()));
+ task_tracker_.Shutdown();
+ worker_pool_->JoinForTesting();
+ worker_pool_.reset();
+ EXPECT_FALSE(task_runner->PostTask(FROM_HERE, BindOnce(&ShouldNotRun)));
+}
+
+// Verify that a Task runs shortly after its delay expires.
+TEST_P(TaskSchedulerWorkerPoolTest, PostDelayedTask) {
+ StartWorkerPool();
+
+ WaitableEvent task_ran(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+
+ auto task_runner = test::CreateTaskRunnerWithExecutionMode(
+ worker_pool_.get(), GetParam().execution_mode);
+
+ // Wait until the task runner is up and running to make sure the test below is
+ // solely timing the delayed task, not bringing up a physical thread.
+ task_runner->PostTask(
+ FROM_HERE, BindOnce(&WaitableEvent::Signal, Unretained(&task_ran)));
+ task_ran.Wait();
+ ASSERT_TRUE(!task_ran.IsSignaled());
+
+ // Post a task with a short delay.
+ TimeTicks start_time = TimeTicks::Now();
+ EXPECT_TRUE(task_runner->PostDelayedTask(
+ FROM_HERE, BindOnce(&WaitableEvent::Signal, Unretained(&task_ran)),
+ TestTimeouts::tiny_timeout()));
+
+ // Wait until the task runs.
+ task_ran.Wait();
+
+ // Expect the task to run after its delay expires, but no more than 250
+ // ms after that.
+ const TimeDelta actual_delay = TimeTicks::Now() - start_time;
+ EXPECT_GE(actual_delay, TestTimeouts::tiny_timeout());
+ EXPECT_LT(actual_delay,
+ TimeDelta::FromMilliseconds(250) + TestTimeouts::tiny_timeout());
+}
+
+// Verify that the RunsTasksInCurrentSequence() method of a SEQUENCED TaskRunner
+// returns false when called from a task that isn't part of the sequence. Note:
+// Tests that use TestTaskFactory already verify that
+// RunsTasksInCurrentSequence() returns true when appropriate so this method
+// complements it to get full coverage of that method.
+TEST_P(TaskSchedulerWorkerPoolTest, SequencedRunsTasksInCurrentSequence) {
+ StartWorkerPool();
+ auto task_runner = test::CreateTaskRunnerWithExecutionMode(
+ worker_pool_.get(), GetParam().execution_mode);
+ auto sequenced_task_runner =
+ worker_pool_->CreateSequencedTaskRunnerWithTraits(TaskTraits());
+
+ WaitableEvent task_ran;
+ task_runner->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](scoped_refptr<TaskRunner> sequenced_task_runner,
+ WaitableEvent* task_ran) {
+ EXPECT_FALSE(sequenced_task_runner->RunsTasksInCurrentSequence());
+ task_ran->Signal();
+ },
+ sequenced_task_runner, Unretained(&task_ran)));
+ task_ran.Wait();
+}
+
+// Verify that tasks posted before Start run after Start.
+TEST_P(TaskSchedulerWorkerPoolTest, PostBeforeStart) {
+ WaitableEvent task_1_running;
+ WaitableEvent task_2_running;
+
+ scoped_refptr<TaskRunner> task_runner =
+ worker_pool_->CreateTaskRunnerWithTraits({WithBaseSyncPrimitives()});
+
+ task_runner->PostTask(
+ FROM_HERE, BindOnce(&WaitableEvent::Signal, Unretained(&task_1_running)));
+ task_runner->PostTask(
+ FROM_HERE, BindOnce(&WaitableEvent::Signal, Unretained(&task_2_running)));
+
+ // Workers should not be created and tasks should not run before the pool is
+ // started. The sleep is to give time for the tasks to potentially run.
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ EXPECT_FALSE(task_1_running.IsSignaled());
+ EXPECT_FALSE(task_2_running.IsSignaled());
+
+ StartWorkerPool();
+
+ // Tasks should run shortly after the pool is started.
+ task_1_running.Wait();
+ task_2_running.Wait();
+
+ task_tracker_.FlushForTesting();
+}
+
+INSTANTIATE_TEST_CASE_P(GenericParallel,
+ TaskSchedulerWorkerPoolTest,
+ ::testing::Values(PoolExecutionType{
+ PoolType::GENERIC, test::ExecutionMode::PARALLEL}));
+INSTANTIATE_TEST_CASE_P(GenericSequenced,
+ TaskSchedulerWorkerPoolTest,
+ ::testing::Values(PoolExecutionType{
+ PoolType::GENERIC,
+ test::ExecutionMode::SEQUENCED}));
+
+#if defined(OS_WIN)
+INSTANTIATE_TEST_CASE_P(WinParallel,
+ TaskSchedulerWorkerPoolTest,
+ ::testing::Values(PoolExecutionType{
+ PoolType::WINDOWS, test::ExecutionMode::PARALLEL}));
+INSTANTIATE_TEST_CASE_P(WinSequenced,
+ TaskSchedulerWorkerPoolTest,
+ ::testing::Values(PoolExecutionType{
+ PoolType::WINDOWS,
+ test::ExecutionMode::SEQUENCED}));
+#endif
+
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/sequence.cc b/base/task_scheduler/sequence.cc
index 9867c1dfd2..3f4c5fecf6 100644
--- a/base/task_scheduler/sequence.cc
+++ b/base/task_scheduler/sequence.cc
@@ -14,44 +14,37 @@ namespace internal {
Sequence::Sequence() = default;
-bool Sequence::PushTask(std::unique_ptr<Task> task) {
- DCHECK(task);
- DCHECK(task->task);
- DCHECK(task->sequenced_time.is_null());
- task->sequenced_time = base::TimeTicks::Now();
+bool Sequence::PushTask(Task task) {
+ // Use CHECK instead of DCHECK to crash earlier. See http://crbug.com/711167
+ // for details.
+ CHECK(task.task);
+ DCHECK(task.sequenced_time.is_null());
+ task.sequenced_time = base::TimeTicks::Now();
AutoSchedulerLock auto_lock(lock_);
- ++num_tasks_per_priority_[static_cast<int>(task->traits.priority())];
+ ++num_tasks_per_priority_[static_cast<int>(task.traits.priority())];
queue_.push(std::move(task));
// Return true if the sequence was empty before the push.
return queue_.size() == 1;
}
-std::unique_ptr<Task> Sequence::TakeTask() {
+Optional<Task> Sequence::TakeTask() {
AutoSchedulerLock auto_lock(lock_);
DCHECK(!queue_.empty());
- DCHECK(queue_.front());
+ DCHECK(queue_.front().task);
- const int priority_index =
- static_cast<int>(queue_.front()->traits.priority());
+ const int priority_index = static_cast<int>(queue_.front().traits.priority());
DCHECK_GT(num_tasks_per_priority_[priority_index], 0U);
--num_tasks_per_priority_[priority_index];
return std::move(queue_.front());
}
-TaskTraits Sequence::PeekTaskTraits() const {
- AutoSchedulerLock auto_lock(lock_);
- DCHECK(!queue_.empty());
- DCHECK(queue_.front());
- return queue_.front()->traits;
-}
-
bool Sequence::Pop() {
AutoSchedulerLock auto_lock(lock_);
DCHECK(!queue_.empty());
- DCHECK(!queue_.front());
+ DCHECK(!queue_.front().task);
queue_.pop();
return queue_.empty();
}
@@ -75,7 +68,7 @@ SequenceSortKey Sequence::GetSortKey() const {
}
// Save the sequenced time of the next task in the sequence.
- next_task_sequenced_time = queue_.front()->sequenced_time;
+ next_task_sequenced_time = queue_.front().sequenced_time;
}
return SequenceSortKey(priority, next_task_sequenced_time);
diff --git a/base/task_scheduler/sequence.h b/base/task_scheduler/sequence.h
index 408d99f9c6..3700b2ebd7 100644
--- a/base/task_scheduler/sequence.h
+++ b/base/task_scheduler/sequence.h
@@ -7,17 +7,16 @@
#include <stddef.h>
-#include <memory>
-#include <queue>
-
#include "base/base_export.h"
+#include "base/containers/queue.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
+#include "base/optional.h"
#include "base/sequence_token.h"
#include "base/task_scheduler/scheduler_lock.h"
#include "base/task_scheduler/sequence_sort_key.h"
#include "base/task_scheduler/task.h"
-#include "base/task_scheduler/task_traits.h"
+#include "base/threading/sequence_local_storage_map.h"
namespace base {
namespace internal {
@@ -46,17 +45,18 @@ class BASE_EXPORT Sequence : public RefCountedThreadSafe<Sequence> {
// Adds |task| in a new slot at the end of the Sequence. Returns true if the
// Sequence was empty before this operation.
- bool PushTask(std::unique_ptr<Task> task);
+ bool PushTask(Task task);
// Transfers ownership of the Task in the front slot of the Sequence to the
// caller. The front slot of the Sequence will be nullptr and remain until
// Pop(). Cannot be called on an empty Sequence or a Sequence whose front slot
// is already nullptr.
- std::unique_ptr<Task> TakeTask();
-
- // Returns the TaskTraits of the Task in front of the Sequence. Cannot be
- // called on an empty Sequence or on a Sequence whose front slot is empty.
- TaskTraits PeekTaskTraits() const;
+ //
+ // Because this method cannot be called on an empty Sequence, the returned
+ // Optional<Task> is never nullptr. An Optional is used in preparation for the
+ // merge between TaskScheduler and TaskQueueManager (in Blink).
+ // https://crbug.com/783309
+ Optional<Task> TakeTask();
// Removes the front slot of the Sequence. The front slot must have been
// emptied by TakeTask() before this is called. Cannot be called on an empty
@@ -70,6 +70,10 @@ class BASE_EXPORT Sequence : public RefCountedThreadSafe<Sequence> {
// Returns a token that uniquely identifies this Sequence.
const SequenceToken& token() const { return token_; }
+ SequenceLocalStorageMap* sequence_local_storage() {
+ return &sequence_local_storage_;
+ }
+
private:
friend class RefCountedThreadSafe<Sequence>;
~Sequence();
@@ -80,12 +84,15 @@ class BASE_EXPORT Sequence : public RefCountedThreadSafe<Sequence> {
mutable SchedulerLock lock_;
// Queue of tasks to execute.
- std::queue<std::unique_ptr<Task>> queue_;
+ base::queue<Task> queue_;
// Number of tasks contained in the Sequence for each priority.
size_t num_tasks_per_priority_[static_cast<int>(TaskPriority::HIGHEST) + 1] =
{};
+ // Holds data stored through the SequenceLocalStorageSlot API.
+ SequenceLocalStorageMap sequence_local_storage_;
+
DISALLOW_COPY_AND_ASSIGN(Sequence);
};
diff --git a/base/task_scheduler/sequence_sort_key.h b/base/task_scheduler/sequence_sort_key.h
index eb8170821e..2e126c5f15 100644
--- a/base/task_scheduler/sequence_sort_key.h
+++ b/base/task_scheduler/sequence_sort_key.h
@@ -18,6 +18,9 @@ class BASE_EXPORT SequenceSortKey final {
SequenceSortKey(TaskPriority priority, TimeTicks next_task_sequenced_time);
TaskPriority priority() const { return priority_; }
+ TimeTicks next_task_sequenced_time() const {
+ return next_task_sequenced_time_;
+ }
bool operator<(const SequenceSortKey& other) const;
bool operator>(const SequenceSortKey& other) const { return other < *this; }
diff --git a/base/task_scheduler/sequence_unittest.cc b/base/task_scheduler/sequence_unittest.cc
index 7093b1e94d..86d1547e48 100644
--- a/base/task_scheduler/sequence_unittest.cc
+++ b/base/task_scheduler/sequence_unittest.cc
@@ -8,10 +8,10 @@
#include "base/bind.h"
#include "base/bind_helpers.h"
-#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/test/gtest_util.h"
#include "base/time/time.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
@@ -19,207 +19,152 @@ namespace internal {
namespace {
-
-class TaskSchedulerSequenceTest : public testing::Test {
+class MockTask {
public:
- TaskSchedulerSequenceTest()
- : task_a_owned_(
- new Task(FROM_HERE,
- Bind(&DoNothing),
- TaskTraits().WithPriority(TaskPriority::BACKGROUND),
- TimeDelta())),
- task_b_owned_(
- new Task(FROM_HERE,
- Bind(&DoNothing),
- TaskTraits().WithPriority(TaskPriority::USER_VISIBLE),
- TimeDelta())),
- task_c_owned_(
- new Task(FROM_HERE,
- Bind(&DoNothing),
- TaskTraits().WithPriority(TaskPriority::USER_BLOCKING),
- TimeDelta())),
- task_d_owned_(
- new Task(FROM_HERE,
- Bind(&DoNothing),
- TaskTraits().WithPriority(TaskPriority::USER_BLOCKING),
- TimeDelta())),
- task_e_owned_(
- new Task(FROM_HERE,
- Bind(&DoNothing),
- TaskTraits().WithPriority(TaskPriority::BACKGROUND),
- TimeDelta())),
- task_a_(task_a_owned_.get()),
- task_b_(task_b_owned_.get()),
- task_c_(task_c_owned_.get()),
- task_d_(task_d_owned_.get()),
- task_e_(task_e_owned_.get()) {}
-
- protected:
- // Tasks to be handed off to a Sequence for testing.
- std::unique_ptr<Task> task_a_owned_;
- std::unique_ptr<Task> task_b_owned_;
- std::unique_ptr<Task> task_c_owned_;
- std::unique_ptr<Task> task_d_owned_;
- std::unique_ptr<Task> task_e_owned_;
-
- // Raw pointers to those same tasks for verification. This is needed because
- // the unique_ptrs above no longer point to the tasks once they have been
- // moved into a Sequence.
- const Task* task_a_;
- const Task* task_b_;
- const Task* task_c_;
- const Task* task_d_;
- const Task* task_e_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(TaskSchedulerSequenceTest);
+ MOCK_METHOD0(Run, void());
};
-} // namespace
+Task CreateTask(MockTask* mock_task) {
+ return Task(FROM_HERE, BindOnce(&MockTask::Run, Unretained(mock_task)),
+ {TaskPriority::BACKGROUND}, TimeDelta());
+}
-TEST_F(TaskSchedulerSequenceTest, PushTakeRemove) {
- scoped_refptr<Sequence> sequence(new Sequence);
+void ExpectMockTask(MockTask* mock_task, Task* task) {
+ EXPECT_CALL(*mock_task, Run());
+ std::move(task->task).Run();
+ testing::Mock::VerifyAndClear(mock_task);
+}
- // Push task A in the sequence. Its sequenced time should be updated and it
- // should be in front of the sequence.
- EXPECT_TRUE(sequence->PushTask(std::move(task_a_owned_)));
- EXPECT_FALSE(task_a_->sequenced_time.is_null());
- EXPECT_EQ(task_a_->traits.priority(), sequence->PeekTaskTraits().priority());
+} // namespace
+
+TEST(TaskSchedulerSequenceTest, PushTakeRemove) {
+ testing::StrictMock<MockTask> mock_task_a;
+ testing::StrictMock<MockTask> mock_task_b;
+ testing::StrictMock<MockTask> mock_task_c;
+ testing::StrictMock<MockTask> mock_task_d;
+ testing::StrictMock<MockTask> mock_task_e;
- // Push task B, C and D in the sequence. Their sequenced time should be
- // updated and task A should always remain in front of the sequence.
- EXPECT_FALSE(sequence->PushTask(std::move(task_b_owned_)));
- EXPECT_FALSE(task_b_->sequenced_time.is_null());
- EXPECT_EQ(task_a_->traits.priority(), sequence->PeekTaskTraits().priority());
+ scoped_refptr<Sequence> sequence = MakeRefCounted<Sequence>();
- EXPECT_FALSE(sequence->PushTask(std::move(task_c_owned_)));
- EXPECT_FALSE(task_c_->sequenced_time.is_null());
- EXPECT_EQ(task_a_->traits.priority(), sequence->PeekTaskTraits().priority());
+ // Push task A in the sequence. PushTask() should return true since it's the
+ // first task->
+ EXPECT_TRUE(sequence->PushTask(CreateTask(&mock_task_a)));
- EXPECT_FALSE(sequence->PushTask(std::move(task_d_owned_)));
- EXPECT_FALSE(task_d_->sequenced_time.is_null());
- EXPECT_EQ(task_a_->traits.priority(), sequence->PeekTaskTraits().priority());
+ // Push task B, C and D in the sequence. PushTask() should return false since
+ // there is already a task in a sequence.
+ EXPECT_FALSE(sequence->PushTask(CreateTask(&mock_task_b)));
+ EXPECT_FALSE(sequence->PushTask(CreateTask(&mock_task_c)));
+ EXPECT_FALSE(sequence->PushTask(CreateTask(&mock_task_d)));
- // Get the task in front of the sequence. It should be task A.
- EXPECT_EQ(task_a_, sequence->TakeTask().get());
+ // Take the task in front of the sequence. It should be task A.
+ Optional<Task> task = sequence->TakeTask();
+ ExpectMockTask(&mock_task_a, &task.value());
+ EXPECT_FALSE(task->sequenced_time.is_null());
// Remove the empty slot. Task B should now be in front.
EXPECT_FALSE(sequence->Pop());
- EXPECT_EQ(task_b_, sequence->TakeTask().get());
+ task = sequence->TakeTask();
+ ExpectMockTask(&mock_task_b, &task.value());
+ EXPECT_FALSE(task->sequenced_time.is_null());
// Remove the empty slot. Task C should now be in front.
EXPECT_FALSE(sequence->Pop());
- EXPECT_EQ(task_c_, sequence->TakeTask().get());
+ task = sequence->TakeTask();
+ ExpectMockTask(&mock_task_c, &task.value());
+ EXPECT_FALSE(task->sequenced_time.is_null());
- // Remove the empty slot. Task D should now be in front.
+ // Remove the empty slot.
EXPECT_FALSE(sequence->Pop());
- EXPECT_EQ(task_d_, sequence->TakeTask().get());
- // Push task E in the sequence. Its sequenced time should be updated.
- EXPECT_FALSE(sequence->PushTask(std::move(task_e_owned_)));
- EXPECT_FALSE(task_e_->sequenced_time.is_null());
+ // Push task E in the sequence.
+ EXPECT_FALSE(sequence->PushTask(CreateTask(&mock_task_e)));
+
+ // Task D should be in front.
+ task = sequence->TakeTask();
+ ExpectMockTask(&mock_task_d, &task.value());
+ EXPECT_FALSE(task->sequenced_time.is_null());
// Remove the empty slot. Task E should now be in front.
EXPECT_FALSE(sequence->Pop());
- EXPECT_EQ(task_e_, sequence->TakeTask().get());
+ task = sequence->TakeTask();
+ ExpectMockTask(&mock_task_e, &task.value());
+ EXPECT_FALSE(task->sequenced_time.is_null());
// Remove the empty slot. The sequence should now be empty.
EXPECT_TRUE(sequence->Pop());
}
-TEST_F(TaskSchedulerSequenceTest, GetSortKey) {
- scoped_refptr<Sequence> sequence(new Sequence);
-
- // Push task A in the sequence. The highest priority is from task A
- // (BACKGROUND). Task A is in front of the sequence.
- sequence->PushTask(std::move(task_a_owned_));
- EXPECT_EQ(SequenceSortKey(TaskPriority::BACKGROUND, task_a_->sequenced_time),
- sequence->GetSortKey());
-
- // Push task B in the sequence. The highest priority is from task B
- // (USER_VISIBLE). Task A is still in front of the sequence.
- sequence->PushTask(std::move(task_b_owned_));
- EXPECT_EQ(
- SequenceSortKey(TaskPriority::USER_VISIBLE, task_a_->sequenced_time),
- sequence->GetSortKey());
-
- // Push task C in the sequence. The highest priority is from task C
- // (USER_BLOCKING). Task A is still in front of the sequence.
- sequence->PushTask(std::move(task_c_owned_));
- EXPECT_EQ(
- SequenceSortKey(TaskPriority::USER_BLOCKING, task_a_->sequenced_time),
- sequence->GetSortKey());
-
- // Push task D in the sequence. The highest priority is from tasks C/D
- // (USER_BLOCKING). Task A is still in front of the sequence.
- sequence->PushTask(std::move(task_d_owned_));
- EXPECT_EQ(
- SequenceSortKey(TaskPriority::USER_BLOCKING, task_a_->sequenced_time),
- sequence->GetSortKey());
-
- // Pop task A. The highest priority is still USER_BLOCKING. The task in front
- // of the sequence is now task B.
- sequence->TakeTask();
- sequence->Pop();
- EXPECT_EQ(
- SequenceSortKey(TaskPriority::USER_BLOCKING, task_b_->sequenced_time),
- sequence->GetSortKey());
-
- // Pop task B. The highest priority is still USER_BLOCKING. The task in front
- // of the sequence is now task C.
- sequence->TakeTask();
- sequence->Pop();
- EXPECT_EQ(
- SequenceSortKey(TaskPriority::USER_BLOCKING, task_c_->sequenced_time),
- sequence->GetSortKey());
-
- // Pop task C. The highest priority is still USER_BLOCKING. The task in front
- // of the sequence is now task D.
- sequence->TakeTask();
- sequence->Pop();
- EXPECT_EQ(
- SequenceSortKey(TaskPriority::USER_BLOCKING, task_d_->sequenced_time),
- sequence->GetSortKey());
-
- // Push task E in the sequence. The highest priority is still USER_BLOCKING.
- // The task in front of the sequence is still task D.
- sequence->PushTask(std::move(task_e_owned_));
- EXPECT_EQ(
- SequenceSortKey(TaskPriority::USER_BLOCKING, task_d_->sequenced_time),
- sequence->GetSortKey());
-
- // Pop task D. The highest priority is now from task E (BACKGROUND). The
- // task in front of the sequence is now task E.
- sequence->TakeTask();
- sequence->Pop();
- EXPECT_EQ(SequenceSortKey(TaskPriority::BACKGROUND, task_e_->sequenced_time),
- sequence->GetSortKey());
+// Verifies the sort key of a sequence that contains one BACKGROUND task.
+TEST(TaskSchedulerSequenceTest, GetSortKeyBackground) {
+ // Create a sequence with a BACKGROUND task.
+ Task background_task(FROM_HERE, DoNothing(), {TaskPriority::BACKGROUND},
+ TimeDelta());
+ scoped_refptr<Sequence> background_sequence = MakeRefCounted<Sequence>();
+ background_sequence->PushTask(std::move(background_task));
+
+ // Get the sort key.
+ const SequenceSortKey background_sort_key = background_sequence->GetSortKey();
+
+ // Take the task from the sequence, so that its sequenced time is available
+ // for the check below.
+ auto take_background_task = background_sequence->TakeTask();
+
+ // Verify the sort key.
+ EXPECT_EQ(TaskPriority::BACKGROUND, background_sort_key.priority());
+ EXPECT_EQ(take_background_task->sequenced_time,
+ background_sort_key.next_task_sequenced_time());
+
+ // Pop for correctness.
+ background_sequence->Pop();
+}
+
+// Same as TaskSchedulerSequenceTest.GetSortKeyBackground, but with a
+// USER_VISIBLE task.
+TEST(TaskSchedulerSequenceTest, GetSortKeyForeground) {
+ // Create a sequence with a USER_VISIBLE task.
+ Task foreground_task(FROM_HERE, DoNothing(), {TaskPriority::USER_VISIBLE},
+ TimeDelta());
+ scoped_refptr<Sequence> foreground_sequence = MakeRefCounted<Sequence>();
+ foreground_sequence->PushTask(std::move(foreground_task));
+
+ // Get the sort key.
+ const SequenceSortKey foreground_sort_key = foreground_sequence->GetSortKey();
+
+ // Take the task from the sequence, so that its sequenced time is available
+ // for the check below.
+ auto take_foreground_task = foreground_sequence->TakeTask();
+
+ // Verify the sort key.
+ EXPECT_EQ(TaskPriority::USER_VISIBLE, foreground_sort_key.priority());
+ EXPECT_EQ(take_foreground_task->sequenced_time,
+ foreground_sort_key.next_task_sequenced_time());
+
+ // Pop for correctness.
+ foreground_sequence->Pop();
}
// Verify that a DCHECK fires if Pop() is called on a sequence whose front slot
// isn't empty.
-TEST_F(TaskSchedulerSequenceTest, PopNonEmptyFrontSlot) {
- scoped_refptr<Sequence> sequence(new Sequence);
- sequence->PushTask(
- MakeUnique<Task>(FROM_HERE, Bind(&DoNothing), TaskTraits(), TimeDelta()));
+TEST(TaskSchedulerSequenceTest, PopNonEmptyFrontSlot) {
+ scoped_refptr<Sequence> sequence = MakeRefCounted<Sequence>();
+ sequence->PushTask(Task(FROM_HERE, DoNothing(), TaskTraits(), TimeDelta()));
EXPECT_DCHECK_DEATH({ sequence->Pop(); });
}
// Verify that a DCHECK fires if TakeTask() is called on a sequence whose front
// slot is empty.
-TEST_F(TaskSchedulerSequenceTest, TakeEmptyFrontSlot) {
- scoped_refptr<Sequence> sequence(new Sequence);
- sequence->PushTask(
- MakeUnique<Task>(FROM_HERE, Bind(&DoNothing), TaskTraits(), TimeDelta()));
+TEST(TaskSchedulerSequenceTest, TakeEmptyFrontSlot) {
+ scoped_refptr<Sequence> sequence = MakeRefCounted<Sequence>();
+ sequence->PushTask(Task(FROM_HERE, DoNothing(), TaskTraits(), TimeDelta()));
EXPECT_TRUE(sequence->TakeTask());
EXPECT_DCHECK_DEATH({ sequence->TakeTask(); });
}
// Verify that a DCHECK fires if TakeTask() is called on an empty sequence.
-TEST_F(TaskSchedulerSequenceTest, TakeEmptySequence) {
- scoped_refptr<Sequence> sequence(new Sequence);
+TEST(TaskSchedulerSequenceTest, TakeEmptySequence) {
+ scoped_refptr<Sequence> sequence = MakeRefCounted<Sequence>();
EXPECT_DCHECK_DEATH({ sequence->TakeTask(); });
}
diff --git a/base/task_scheduler/service_thread.cc b/base/task_scheduler/service_thread.cc
new file mode 100644
index 0000000000..12cd3282dd
--- /dev/null
+++ b/base/task_scheduler/service_thread.cc
@@ -0,0 +1,93 @@
+// 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.
+
+#include "base/task_scheduler/service_thread.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/debug/alias.h"
+#include "base/rand_util.h"
+#include "base/stl_util.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/task_scheduler/task_scheduler.h"
+#include "base/task_scheduler/task_tracker.h"
+#include "base/task_scheduler/task_traits.h"
+#include "build/build_config.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+
+TimeDelta g_heartbeat_for_testing = TimeDelta();
+
+} // namespace
+
+ServiceThread::ServiceThread(const TaskTracker* task_tracker)
+ : Thread("TaskSchedulerServiceThread"), task_tracker_(task_tracker) {}
+
+// static
+void ServiceThread::SetHeartbeatIntervalForTesting(TimeDelta heartbeat) {
+ g_heartbeat_for_testing = heartbeat;
+}
+
+void ServiceThread::Init() {
+ // In unit tests we sometimes do not have a fully functional TaskScheduler
+ // environment, do not perform the heartbeat report in that case since it
+ // relies on such an environment.
+ if (task_tracker_ && TaskScheduler::GetInstance()) {
+// Seemingly causing power regression on Android, disable to see if truly at
+// fault : https://crbug.com/848255
+#if !defined(OS_ANDROID)
+ // Compute the histogram every hour (with a slight offset to drift if that
+ // hour tick happens to line up with specific events). Once per hour per
+ // user was deemed sufficient to gather a reliable metric.
+ constexpr TimeDelta kHeartbeat = TimeDelta::FromMinutes(59);
+
+ heartbeat_latency_timer_.Start(
+ FROM_HERE,
+ g_heartbeat_for_testing.is_zero() ? kHeartbeat
+ : g_heartbeat_for_testing,
+ BindRepeating(&ServiceThread::PerformHeartbeatLatencyReport,
+ Unretained(this)));
+#endif
+ }
+}
+
+NOINLINE void ServiceThread::Run(RunLoop* run_loop) {
+ const int line_number = __LINE__;
+ Thread::Run(run_loop);
+ base::debug::Alias(&line_number);
+}
+
+void ServiceThread::PerformHeartbeatLatencyReport() const {
+ static constexpr TaskTraits kReportedTraits[] = {
+ {TaskPriority::BACKGROUND}, {TaskPriority::BACKGROUND, MayBlock()},
+ {TaskPriority::USER_VISIBLE}, {TaskPriority::USER_VISIBLE, MayBlock()},
+ {TaskPriority::USER_BLOCKING}, {TaskPriority::USER_BLOCKING, MayBlock()}};
+
+ // Only record latency for one set of TaskTraits per report to avoid bias in
+ // the order in which tasks are posted (should we record all at once) as well
+ // as to avoid spinning up many worker threads to process this report if the
+ // scheduler is currently idle (each pool keeps at least one idle thread so a
+ // single task isn't an issue).
+
+ // Invoke RandInt() out-of-line to ensure it's obtained before
+ // TimeTicks::Now().
+ const TaskTraits& profiled_traits =
+ kReportedTraits[RandInt(0, base::size(kReportedTraits) - 1)];
+
+ // Post through the static API to time the full stack. Use a new Now() for
+ // every set of traits in case PostTaskWithTraits() itself is slow.
+ // Bonus: this appraoch also includes the overhead of Bind() in the reported
+ // latency).
+ base::PostTaskWithTraits(
+ FROM_HERE, profiled_traits,
+ BindOnce(&TaskTracker::RecordLatencyHistogram, Unretained(task_tracker_),
+ TaskTracker::LatencyHistogramType::HEARTBEAT_LATENCY,
+ profiled_traits, TimeTicks::Now()));
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/service_thread.h b/base/task_scheduler/service_thread.h
new file mode 100644
index 0000000000..caadc1d1c6
--- /dev/null
+++ b/base/task_scheduler/service_thread.h
@@ -0,0 +1,60 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SCHEDULER_SERVICE_THREAD_H_
+#define BASE_TASK_SCHEDULER_SERVICE_THREAD_H_
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+
+namespace base {
+namespace internal {
+
+class TaskTracker;
+
+// The TaskScheduler's ServiceThread is a mostly idle thread that is responsible
+// for handling async events (e.g. delayed tasks and async I/O). Its role is to
+// merely forward such events to their destination (hence staying mostly idle
+// and highly responsive).
+// It aliases Thread::Run() to enforce that ServiceThread::Run() be on the stack
+// and make it easier to identify the service thread in stack traces.
+class BASE_EXPORT ServiceThread : public Thread {
+ public:
+ // Constructs a ServiceThread which will report latency metrics through
+ // |task_tracker| if non-null. In that case, this ServiceThread will assume a
+ // registered TaskScheduler instance and that |task_tracker| will outlive this
+ // ServiceThread.
+ explicit ServiceThread(const TaskTracker* task_tracker);
+
+ // Overrides the default interval at which |heartbeat_latency_timer_| fires.
+ // Call this with a |heartbeat| of zero to undo the override.
+ // Must not be called while the ServiceThread is running.
+ static void SetHeartbeatIntervalForTesting(TimeDelta heartbeat);
+
+ private:
+ // Thread:
+ void Init() override;
+ void Run(RunLoop* run_loop) override;
+
+ // Kicks off a single async task which will record a histogram on the latency
+ // of a randomly chosen set of TaskTraits.
+ void PerformHeartbeatLatencyReport() const;
+
+ const TaskTracker* const task_tracker_;
+
+ // Fires a recurring heartbeat task to record latency histograms which are
+ // independent from any execution sequence. This is done on the service thread
+ // to avoid all external dependencies (even main thread).
+ base::RepeatingTimer heartbeat_latency_timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ServiceThread);
+};
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_TASK_SCHEDULER_SERVICE_THREAD_H_
diff --git a/base/task_scheduler/service_thread_unittest.cc b/base/task_scheduler/service_thread_unittest.cc
new file mode 100644
index 0000000000..3c4276259b
--- /dev/null
+++ b/base/task_scheduler/service_thread_unittest.cc
@@ -0,0 +1,111 @@
+// 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.
+
+#include "base/task_scheduler/service_thread.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/debug/stack_trace.h"
+#include "base/task_scheduler/task_scheduler.h"
+#include "base/task_scheduler/task_scheduler_impl.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+
+// Verifies that |query| is found on the current stack. Ignores failures if this
+// configuration doesn't have symbols.
+void VerifyHasStringOnStack(const std::string& query) {
+ const std::string stack = debug::StackTrace().ToString();
+ SCOPED_TRACE(stack);
+ const bool found_on_stack = stack.find(query) != std::string::npos;
+ const bool stack_has_symbols =
+ stack.find("SchedulerWorker") != std::string::npos;
+ EXPECT_TRUE(found_on_stack || !stack_has_symbols) << query;
+}
+
+} // namespace
+
+#if defined(OS_POSIX)
+// Many POSIX bots flakily crash on |debug::StackTrace().ToString()|,
+// https://crbug.com/840429.
+#define MAYBE_StackHasIdentifyingFrame DISABLED_StackHasIdentifyingFrame
+#else
+#define MAYBE_StackHasIdentifyingFrame StackHasIdentifyingFrame
+#endif
+
+TEST(TaskSchedulerServiceThreadTest, MAYBE_StackHasIdentifyingFrame) {
+ ServiceThread service_thread(nullptr);
+ service_thread.Start();
+
+ service_thread.task_runner()->PostTask(
+ FROM_HERE, BindOnce(&VerifyHasStringOnStack, "ServiceThread"));
+
+ service_thread.FlushForTesting();
+}
+
+#if defined(OS_ANDROID)
+// The heartbeat latency report has been temporarily disabled on Android per
+// https://crbug.com/848255.
+#define MAYBE_HeartbeatLatencyReport DISABLED_HeartbeatLatencyReport
+#else
+#define MAYBE_HeartbeatLatencyReport HeartbeatLatencyReport
+#endif
+
+// Integration test verifying that a service thread running in a fully
+// integrated TaskScheduler environment results in reporting
+// HeartbeatLatencyMicroseconds metrics.
+TEST(TaskSchedulerServiceThreadIntegrationTest, MAYBE_HeartbeatLatencyReport) {
+ ServiceThread::SetHeartbeatIntervalForTesting(TimeDelta::FromMilliseconds(1));
+
+ TaskScheduler::SetInstance(
+ std::make_unique<internal::TaskSchedulerImpl>("Test"));
+ TaskScheduler::GetInstance()->StartWithDefaultParams();
+
+ static constexpr const char* kExpectedMetrics[] = {
+ "TaskScheduler.HeartbeatLatencyMicroseconds.Test."
+ "UserBlockingTaskPriority",
+ "TaskScheduler.HeartbeatLatencyMicroseconds.Test."
+ "UserBlockingTaskPriority_MayBlock",
+ "TaskScheduler.HeartbeatLatencyMicroseconds.Test."
+ "UserVisibleTaskPriority",
+ "TaskScheduler.HeartbeatLatencyMicroseconds.Test."
+ "UserVisibleTaskPriority_MayBlock",
+ "TaskScheduler.HeartbeatLatencyMicroseconds.Test."
+ "BackgroundTaskPriority",
+ "TaskScheduler.HeartbeatLatencyMicroseconds.Test."
+ "BackgroundTaskPriority_MayBlock"};
+
+ // Each report hits a single histogram above (randomly selected). But 1000
+ // reports should touch all histograms at least once the vast majority of the
+ // time.
+ constexpr TimeDelta kReasonableTimeout = TimeDelta::FromSeconds(1);
+ constexpr TimeDelta kBusyWaitTime = TimeDelta::FromMilliseconds(100);
+
+ const TimeTicks start_time = TimeTicks::Now();
+
+ HistogramTester tester;
+ for (const char* expected_metric : kExpectedMetrics) {
+ while (tester.GetAllSamples(expected_metric).empty()) {
+ if (TimeTicks::Now() - start_time > kReasonableTimeout)
+ LOG(WARNING) << "Waiting a while for " << expected_metric;
+ PlatformThread::Sleep(kBusyWaitTime);
+ }
+ }
+
+ TaskScheduler::GetInstance()->JoinForTesting();
+ TaskScheduler::SetInstance(nullptr);
+
+ ServiceThread::SetHeartbeatIntervalForTesting(TimeDelta());
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/single_thread_task_runner_thread_mode.h b/base/task_scheduler/single_thread_task_runner_thread_mode.h
new file mode 100644
index 0000000000..6ed4228c09
--- /dev/null
+++ b/base/task_scheduler/single_thread_task_runner_thread_mode.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 BASE_TASK_SCHEDULER_SINGLE_THREAD_TASK_RUNNER_THREAD_MODE_H_
+#define BASE_TASK_SCHEDULER_SINGLE_THREAD_TASK_RUNNER_THREAD_MODE_H_
+
+namespace base {
+
+enum class SingleThreadTaskRunnerThreadMode {
+ // Allow the SingleThreadTaskRunner's thread to be shared with others,
+ // allowing for efficient use of thread resources when this
+ // SingleThreadTaskRunner is idle. This is the default mode and is
+ // recommended for most code.
+ SHARED,
+ // Dedicate a single thread for this SingleThreadTaskRunner. No other tasks
+ // from any other source will run on the thread backing the
+ // SingleThreadTaskRunner. Use sparingly as this reserves an entire thread for
+ // this SingleThreadTaskRunner.
+ DEDICATED,
+};
+
+} // namespace base
+
+#endif // BASE_TASK_SCHEDULER_SINGLE_THREAD_TASK_RUNNER_THREAD_MODE_H_
diff --git a/base/task_scheduler/task.cc b/base/task_scheduler/task.cc
index fc513e3e9f..563bb1e5d4 100644
--- a/base/task_scheduler/task.cc
+++ b/base/task_scheduler/task.cc
@@ -6,12 +6,19 @@
#include <utility>
+#include "base/atomic_sequence_num.h"
#include "base/critical_closure.h"
namespace base {
namespace internal {
-Task::Task(const tracked_objects::Location& posted_from,
+namespace {
+
+AtomicSequenceNumber g_sequence_nums_for_tracing;
+
+} // namespace
+
+Task::Task(const Location& posted_from,
OnceClosure task,
const TaskTraits& traits,
TimeDelta delay)
@@ -21,17 +28,39 @@ Task::Task(const tracked_objects::Location& posted_from,
? MakeCriticalClosure(std::move(task))
: std::move(task),
delay.is_zero() ? TimeTicks() : TimeTicks::Now() + delay,
- false), // Not nestable.
- // Prevent a delayed BLOCK_SHUTDOWN task from blocking shutdown before
- // being scheduled by changing its shutdown behavior to SKIP_ON_SHUTDOWN.
- traits(!delay.is_zero() && traits.shutdown_behavior() ==
- TaskShutdownBehavior::BLOCK_SHUTDOWN
- ? TaskTraits(traits).WithShutdownBehavior(
- TaskShutdownBehavior::SKIP_ON_SHUTDOWN)
- : traits),
- delay(delay) {}
+ Nestable::kNonNestable),
+ // Prevent a delayed BLOCK_SHUTDOWN task from blocking shutdown before it
+ // starts running by changing its shutdown behavior to SKIP_ON_SHUTDOWN.
+ traits(
+ (!delay.is_zero() &&
+ traits.shutdown_behavior() == TaskShutdownBehavior::BLOCK_SHUTDOWN)
+ ? TaskTraits::Override(traits,
+ {TaskShutdownBehavior::SKIP_ON_SHUTDOWN})
+ : traits),
+ delay(delay) {
+ // TaskScheduler doesn't use |sequence_num| but tracing (toplevel.flow) relies
+ // on it being unique. While this subtle dependency is a bit overreaching,
+ // TaskScheduler is the only task system that doesn't use |sequence_num| and
+ // the dependent code rarely changes so this isn't worth a big change and
+ // faking it here isn't too bad for now (posting tasks is full of atomic ops
+ // already).
+ this->sequence_num = g_sequence_nums_for_tracing.GetNext();
+}
+
+// This should be "= default but MSVC has trouble with "noexcept = default" in
+// this case.
+Task::Task(Task&& other) noexcept
+ : PendingTask(std::move(other)),
+ traits(other.traits),
+ delay(other.delay),
+ sequenced_time(other.sequenced_time),
+ sequenced_task_runner_ref(std::move(other.sequenced_task_runner_ref)),
+ single_thread_task_runner_ref(
+ std::move(other.single_thread_task_runner_ref)) {}
Task::~Task() = default;
+Task& Task::operator=(Task&& other) = default;
+
} // namespace internal
} // namespace base
diff --git a/base/task_scheduler/task.h b/base/task_scheduler/task.h
index 43095f2ae7..3e937a8a41 100644
--- a/base/task_scheduler/task.h
+++ b/base/task_scheduler/task.h
@@ -27,17 +27,24 @@ struct BASE_EXPORT Task : public PendingTask {
// must expire before the Task runs. If |delay| is non-zero and the shutdown
// behavior in |traits| is BLOCK_SHUTDOWN, the shutdown behavior is
// automatically adjusted to SKIP_ON_SHUTDOWN.
- Task(const tracked_objects::Location& posted_from,
+ Task(const Location& posted_from,
OnceClosure task,
const TaskTraits& traits,
TimeDelta delay);
+
+ // Task is move-only to avoid mistakes that cause reference counts to be
+ // accidentally bumped.
+ Task(Task&& other) noexcept;
+
~Task();
+ Task& operator=(Task&& other);
+
// The TaskTraits of this task.
- const TaskTraits traits;
+ TaskTraits traits;
// The delay that must expire before the task runs.
- const TimeDelta delay;
+ TimeDelta delay;
// The time at which the task was inserted in its sequence. For an undelayed
// task, this happens at post time. For a delayed task, this happens some
@@ -58,8 +65,6 @@ struct BASE_EXPORT Task : public PendingTask {
scoped_refptr<SingleThreadTaskRunner> single_thread_task_runner_ref;
private:
- // Disallow copies to make sure no unnecessary ref-bumps are incurred. Making
- // it move-only would be an option, but isn't necessary for now.
DISALLOW_COPY_AND_ASSIGN(Task);
};
diff --git a/base/task_scheduler/task_scheduler.h b/base/task_scheduler/task_scheduler.h
new file mode 100644
index 0000000000..b07ea28631
--- /dev/null
+++ b/base/task_scheduler/task_scheduler.h
@@ -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.
+
+#ifndef BASE_TASK_SCHEDULER_TASK_SCHEDULER_H_
+#define BASE_TASK_SCHEDULER_TASK_SCHEDULER_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_piece.h"
+#include "base/task_runner.h"
+#include "base/task_scheduler/scheduler_worker_pool_params.h"
+#include "base/task_scheduler/single_thread_task_runner_thread_mode.h"
+#include "base/task_scheduler/task_traits.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+
+namespace gin {
+class V8Platform;
+}
+
+namespace content {
+// Can't use the FRIEND_TEST_ALL_PREFIXES macro because the test is in a
+// different namespace.
+class BrowserMainLoopTest_CreateThreadsInSingleProcess_Test;
+} // namespace content
+
+namespace base {
+
+class HistogramBase;
+class Location;
+class SchedulerWorkerObserver;
+
+// Interface for a task scheduler and static methods to manage the instance used
+// by the post_task.h API.
+//
+// The task scheduler doesn't create threads until Start() is called. Tasks can
+// be posted at any time but will not run until after Start() is called.
+//
+// The instance methods of this class are thread-safe.
+//
+// Note: All base/task_scheduler users should go through post_task.h instead of
+// TaskScheduler except for the one callsite per process which manages the
+// process's instance.
+class BASE_EXPORT TaskScheduler {
+ public:
+ struct BASE_EXPORT InitParams {
+ enum class SharedWorkerPoolEnvironment {
+ // Use the default environment (no environment).
+ DEFAULT,
+#if defined(OS_WIN)
+ // Place the worker in a COM MTA.
+ COM_MTA,
+#endif // defined(OS_WIN)
+ };
+
+ InitParams(
+ const SchedulerWorkerPoolParams& background_worker_pool_params_in,
+ const SchedulerWorkerPoolParams&
+ background_blocking_worker_pool_params_in,
+ const SchedulerWorkerPoolParams& foreground_worker_pool_params_in,
+ const SchedulerWorkerPoolParams&
+ foreground_blocking_worker_pool_params_in,
+ SharedWorkerPoolEnvironment shared_worker_pool_environment_in =
+ SharedWorkerPoolEnvironment::DEFAULT);
+ ~InitParams();
+
+ SchedulerWorkerPoolParams background_worker_pool_params;
+ SchedulerWorkerPoolParams background_blocking_worker_pool_params;
+ SchedulerWorkerPoolParams foreground_worker_pool_params;
+ SchedulerWorkerPoolParams foreground_blocking_worker_pool_params;
+ SharedWorkerPoolEnvironment shared_worker_pool_environment;
+ };
+
+ // Destroying a TaskScheduler is not allowed in production; it is always
+ // leaked. In tests, it should only be destroyed after JoinForTesting() has
+ // returned.
+ virtual ~TaskScheduler() = default;
+
+ // Allows the task scheduler to create threads and run tasks following the
+ // |init_params| specification.
+ //
+ // If specified, |scheduler_worker_observer| will be notified when a worker
+ // enters and exits its main function. It must not be destroyed before
+ // JoinForTesting() has returned (must never be destroyed in production).
+ //
+ // CHECKs on failure.
+ virtual void Start(
+ const InitParams& init_params,
+ SchedulerWorkerObserver* scheduler_worker_observer = nullptr) = 0;
+
+ // Posts |task| with a |delay| and specific |traits|. |delay| can be zero.
+ // For one off tasks that don't require a TaskRunner.
+ virtual void PostDelayedTaskWithTraits(const Location& from_here,
+ const TaskTraits& traits,
+ OnceClosure task,
+ TimeDelta delay) = 0;
+
+ // Returns a TaskRunner whose PostTask invocations result in scheduling tasks
+ // using |traits|. Tasks may run in any order and in parallel.
+ virtual scoped_refptr<TaskRunner> CreateTaskRunnerWithTraits(
+ const TaskTraits& traits) = 0;
+
+ // Returns a SequencedTaskRunner whose PostTask invocations result in
+ // scheduling tasks using |traits|. Tasks run one at a time in posting order.
+ virtual scoped_refptr<SequencedTaskRunner>
+ CreateSequencedTaskRunnerWithTraits(const TaskTraits& traits) = 0;
+
+ // Returns a SingleThreadTaskRunner whose PostTask invocations result in
+ // scheduling tasks using |traits|. Tasks run on a single thread in posting
+ // order.
+ virtual scoped_refptr<SingleThreadTaskRunner>
+ CreateSingleThreadTaskRunnerWithTraits(
+ const TaskTraits& traits,
+ SingleThreadTaskRunnerThreadMode thread_mode) = 0;
+
+#if defined(OS_WIN)
+ // Returns a SingleThreadTaskRunner whose PostTask invocations result in
+ // scheduling tasks using |traits| in a COM Single-Threaded Apartment. Tasks
+ // run in the same Single-Threaded Apartment in posting order for the returned
+ // SingleThreadTaskRunner. There is not necessarily a one-to-one
+ // correspondence between SingleThreadTaskRunners and Single-Threaded
+ // Apartments. The implementation is free to share apartments or create new
+ // apartments as necessary. In either case, care should be taken to make sure
+ // COM pointers are not smuggled across apartments.
+ virtual scoped_refptr<SingleThreadTaskRunner>
+ CreateCOMSTATaskRunnerWithTraits(
+ const TaskTraits& traits,
+ SingleThreadTaskRunnerThreadMode thread_mode) = 0;
+#endif // defined(OS_WIN)
+
+ // Returns a vector of all histograms available in this task scheduler.
+ virtual std::vector<const HistogramBase*> GetHistograms() const = 0;
+
+ // Synchronously shuts down the scheduler. Once this is called, only tasks
+ // posted with the BLOCK_SHUTDOWN behavior will be run. When this returns:
+ // - All SKIP_ON_SHUTDOWN tasks that were already running have completed their
+ // execution.
+ // - All posted BLOCK_SHUTDOWN tasks have completed their execution.
+ // - CONTINUE_ON_SHUTDOWN tasks might still be running.
+ // Note that an implementation can keep threads and other resources alive to
+ // support running CONTINUE_ON_SHUTDOWN after this returns. This can only be
+ // called once.
+ virtual void Shutdown() = 0;
+
+ // Waits until there are no pending undelayed tasks. May be called in tests
+ // to validate that a condition is met after all undelayed tasks have run.
+ //
+ // Does not wait for delayed tasks. Waits for undelayed tasks posted from
+ // other threads during the call. Returns immediately when shutdown completes.
+ virtual void FlushForTesting() = 0;
+
+ // Returns and calls |flush_callback| when there are no incomplete undelayed
+ // tasks. |flush_callback| may be called back on any thread and should not
+ // perform a lot of work. May be used when additional work on the current
+ // thread needs to be performed during a flush. Only one
+ // FlushAsyncForTesting() may be pending at any given time.
+ virtual void FlushAsyncForTesting(OnceClosure flush_callback) = 0;
+
+ // Joins all threads. Tasks that are already running are allowed to complete
+ // their execution. This can only be called once. Using this task scheduler
+ // instance to create task runners or post tasks is not permitted during or
+ // after this call.
+ virtual void JoinForTesting() = 0;
+
+// CreateAndStartWithDefaultParams(), Create(), and SetInstance() register a
+// TaskScheduler to handle tasks posted through the post_task.h API for this
+// process.
+//
+// Processes that need to initialize TaskScheduler with custom params or that
+// need to allow tasks to be posted before the TaskScheduler creates its
+// threads should use Create() followed by Start(). Other processes can use
+// CreateAndStartWithDefaultParams().
+//
+// A registered TaskScheduler is only deleted when a new TaskScheduler is
+// registered. The last registered TaskScheduler is leaked on shutdown. The
+// methods below must not be called when TaskRunners created by a previous
+// TaskScheduler are still alive. The methods are not thread-safe; proper
+// synchronization is required to use the post_task.h API after registering a
+// new TaskScheduler.
+
+#if !defined(OS_NACL)
+ // Creates and starts a task scheduler using default params. |name| is used to
+ // label histograms, it must not be empty. It should identify the component
+ // that calls this. Start() is called by this method; it is invalid to call it
+ // again afterwards. CHECKs on failure. For tests, prefer
+ // base::test::ScopedTaskEnvironment (ensures isolation).
+ static void CreateAndStartWithDefaultParams(StringPiece name);
+
+ // Same as CreateAndStartWithDefaultParams() but allows callers to split the
+ // Create() and StartWithDefaultParams() calls.
+ void StartWithDefaultParams();
+#endif // !defined(OS_NACL)
+
+ // Creates a ready to start task scheduler. |name| is used to label
+ // histograms, it must not be empty. It should identify the component that
+ // creates the TaskScheduler. The task scheduler doesn't create threads until
+ // Start() is called. Tasks can be posted at any time but will not run until
+ // after Start() is called. For tests, prefer
+ // base::test::ScopedTaskEnvironment (ensures isolation).
+ static void Create(StringPiece name);
+
+ // Registers |task_scheduler| to handle tasks posted through the post_task.h
+ // API for this process. For tests, prefer base::test::ScopedTaskEnvironment
+ // (ensures isolation).
+ static void SetInstance(std::unique_ptr<TaskScheduler> task_scheduler);
+
+ // Retrieve the TaskScheduler set via SetInstance() or
+ // CreateAndSet(Simple|Default)TaskScheduler(). This should be used very
+ // rarely; most users of TaskScheduler should use the post_task.h API. In
+ // particular, refrain from doing
+ // if (!TaskScheduler::GetInstance()) {
+ // TaskScheduler::SetInstance(...);
+ // base::PostTask(...);
+ // }
+ // instead make sure to SetInstance() early in one determinstic place in the
+ // process' initialization phase.
+ // In doubt, consult with //base/task_scheduler/OWNERS.
+ static TaskScheduler* GetInstance();
+
+ private:
+ friend class gin::V8Platform;
+ friend class content::BrowserMainLoopTest_CreateThreadsInSingleProcess_Test;
+
+ // Returns the maximum number of non-single-threaded non-blocked tasks posted
+ // with |traits| that can run concurrently in this TaskScheduler. |traits|
+ // can't contain TaskPriority::BACKGROUND.
+ //
+ // Do not use this method. To process n items, post n tasks that each process
+ // 1 item rather than GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated()
+ // tasks that each process
+ // n/GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated() items.
+ //
+ // TODO(fdoray): Remove this method. https://crbug.com/687264
+ virtual int GetMaxConcurrentNonBlockedTasksWithTraitsDeprecated(
+ const TaskTraits& traits) const = 0;
+};
+
+} // namespace base
+
+#endif // BASE_TASK_SCHEDULER_TASK_SCHEDULER_H_
diff --git a/base/task_scheduler/task_traits.cc b/base/task_scheduler/task_traits.cc
index 6acf3244f5..e82e303840 100644
--- a/base/task_scheduler/task_traits.cc
+++ b/base/task_scheduler/task_traits.cc
@@ -9,42 +9,9 @@
#include <ostream>
#include "base/logging.h"
-#include "base/task_scheduler/scoped_set_task_priority_for_current_thread.h"
namespace base {
-// Do not rely on defaults hard-coded below beyond the guarantees described in
-// the header; anything else is subject to change. Tasks should explicitly
-// request defaults if the behavior is critical to the task.
-TaskTraits::TaskTraits()
- : may_block_(false),
- with_base_sync_primitives_(false),
- priority_(internal::GetTaskPriorityForCurrentThread()),
- shutdown_behavior_(TaskShutdownBehavior::SKIP_ON_SHUTDOWN) {}
-
-TaskTraits::~TaskTraits() = default;
-
-TaskTraits& TaskTraits::MayBlock() {
- may_block_ = true;
- return *this;
-}
-
-TaskTraits& TaskTraits::WithBaseSyncPrimitives() {
- with_base_sync_primitives_ = true;
- return *this;
-}
-
-TaskTraits& TaskTraits::WithPriority(TaskPriority priority) {
- priority_ = priority;
- return *this;
-}
-
-TaskTraits& TaskTraits::WithShutdownBehavior(
- TaskShutdownBehavior shutdown_behavior) {
- shutdown_behavior_ = shutdown_behavior;
- return *this;
-}
-
const char* TaskPriorityToString(TaskPriority task_priority) {
switch (task_priority) {
case TaskPriority::BACKGROUND:
diff --git a/base/task_scheduler/task_traits.h b/base/task_scheduler/task_traits.h
index 435fdac9af..a4a41fe724 100644
--- a/base/task_scheduler/task_traits.h
+++ b/base/task_scheduler/task_traits.h
@@ -8,8 +8,10 @@
#include <stdint.h>
#include <iosfwd>
+#include <type_traits>
#include "base/base_export.h"
+#include "base/task_scheduler/task_traits_details.h"
#include "build/build_config.h"
namespace base {
@@ -75,84 +77,155 @@ enum class TaskShutdownBehavior {
BLOCK_SHUTDOWN,
};
-// Describes metadata for a single task or a group of tasks.
+// Tasks with this trait may block. This includes but is not limited to tasks
+// that wait on synchronous file I/O operations: read or write a file from disk,
+// interact with a pipe or a socket, rename or delete a file, enumerate files in
+// a directory, etc. This trait isn't required for the mere use of locks. For
+// tasks that block on base/ synchronization primitives, see the
+// WithBaseSyncPrimitives trait.
+struct MayBlock {};
+
+// DEPRECATED. Use base::ScopedAllowBaseSyncPrimitives(ForTesting) instead.
+//
+// Tasks with this trait will pass base::AssertBaseSyncPrimitivesAllowed(), i.e.
+// will be allowed on the following methods :
+// - base::WaitableEvent::Wait
+// - base::ConditionVariable::Wait
+// - base::PlatformThread::Join
+// - base::PlatformThread::Sleep
+// - base::Process::WaitForExit
+// - base::Process::WaitForExitWithTimeout
+//
+// Tasks should generally not use these methods.
+//
+// Instead of waiting on a WaitableEvent or a ConditionVariable, put the work
+// that should happen after the wait in a callback and post that callback from
+// where the WaitableEvent or ConditionVariable would have been signaled. If
+// something needs to be scheduled after many tasks have executed, use
+// base::BarrierClosure.
+//
+// On Windows, join processes asynchronously using base::win::ObjectWatcher.
+//
+// MayBlock() must be specified in conjunction with this trait if and only if
+// removing usage of methods listed above in the labeled tasks would still
+// result in tasks that may block (per MayBlock()'s definition).
+//
+// In doubt, consult with //base/task_scheduler/OWNERS.
+struct WithBaseSyncPrimitives {};
+
+// Describes immutable metadata for a single task or a group of tasks.
class BASE_EXPORT TaskTraits {
+ private:
+ // ValidTrait ensures TaskTraits' constructor only accepts appropriate types.
+ struct ValidTrait {
+ ValidTrait(TaskPriority) {}
+ ValidTrait(TaskShutdownBehavior) {}
+ ValidTrait(MayBlock) {}
+ ValidTrait(WithBaseSyncPrimitives) {}
+ };
+
public:
- // Constructs a default TaskTraits for tasks that
+ // Invoking this constructor without arguments produces TaskTraits that are
+ // appropriate for tasks that
// (1) don't block (ref. MayBlock() and WithBaseSyncPrimitives()),
// (2) prefer inheriting the current priority to specifying their own, and
// (3) can either block shutdown or be skipped on shutdown
// (TaskScheduler implementation is free to choose a fitting default).
- // Tasks that require stricter guarantees and/or know the specific
- // TaskPriority appropriate for them should highlight those by requesting
- // explicit traits below.
- TaskTraits();
- TaskTraits(const TaskTraits& other) = default;
- TaskTraits& operator=(const TaskTraits& other) = default;
- ~TaskTraits();
-
- // Tasks with this trait may block. This includes but is not limited to tasks
- // that wait on synchronous file I/O operations: read or write a file from
- // disk, interact with a pipe or a socket, rename or delete a file, enumerate
- // files in a directory, etc. This trait isn't required for the mere use of
- // locks. For tasks that block on base/ synchronization primitives, see
- // WithBaseSyncPrimitives().
- TaskTraits& MayBlock();
-
- // Tasks with this trait will pass base::AssertWaitAllowed(), i.e. will be
- // allowed on the following methods :
- // - base::WaitableEvent::Wait
- // - base::ConditionVariable::Wait
- // - base::PlatformThread::Join
- // - base::PlatformThread::Sleep
- // - base::Process::WaitForExit
- // - base::Process::WaitForExitWithTimeout
- //
- // Tasks should generally not use these methods.
- //
- // Instead of waiting on a WaitableEvent or a ConditionVariable, put the work
- // that should happen after the wait in a callback and post that callback from
- // where the WaitableEvent or ConditionVariable would have been signaled. If
- // something needs to be scheduled after many tasks have executed, use
- // base::BarrierClosure.
- //
- // Avoid creating threads. Instead, use
- // base::Create(Sequenced|SingleTreaded)TaskRunnerWithTraits(). If a thread is
- // really needed, make it non-joinable and add cleanup work at the end of the
- // thread's main function (if using base::Thread, override Cleanup()).
//
- // On Windows, join processes asynchronously using base::win::ObjectWatcher.
+ // To get TaskTraits for tasks that require stricter guarantees and/or know
+ // the specific TaskPriority appropriate for them, provide arguments of type
+ // TaskPriority, TaskShutdownBehavior, MayBlock, and/or WithBaseSyncPrimitives
+ // in any order to the constructor.
//
- // MayBlock() must be specified in conjunction with this trait if and only if
- // removing usage of methods listed above in the labeled tasks would still
- // result in tasks that may block (per MayBlock()'s definition).
- //
- // In doubt, consult with //base/task_scheduler/OWNERS.
- TaskTraits& WithBaseSyncPrimitives();
+ // E.g.
+ // constexpr base::TaskTraits default_traits = {};
+ // constexpr base::TaskTraits user_visible_traits =
+ // {base::TaskPriority::USER_VISIBLE};
+ // constexpr base::TaskTraits user_visible_may_block_traits = {
+ // base::TaskPriority::USER_VISIBLE, base::MayBlock()};
+ // constexpr base::TaskTraits other_user_visible_may_block_traits = {
+ // base::MayBlock(), base::TaskPriority::USER_VISIBLE};
+ template <class... ArgTypes,
+ class CheckArgumentsAreValid = internal::InitTypes<
+ decltype(ValidTrait(std::declval<ArgTypes>()))...>>
+ constexpr TaskTraits(ArgTypes... args)
+ : priority_set_explicitly_(
+ internal::HasArgOfType<TaskPriority, ArgTypes...>::value),
+ priority_(internal::GetValueFromArgList(
+ internal::EnumArgGetter<TaskPriority, TaskPriority::USER_VISIBLE>(),
+ args...)),
+ shutdown_behavior_set_explicitly_(
+ internal::HasArgOfType<TaskShutdownBehavior, ArgTypes...>::value),
+ shutdown_behavior_(internal::GetValueFromArgList(
+ internal::EnumArgGetter<TaskShutdownBehavior,
+ TaskShutdownBehavior::SKIP_ON_SHUTDOWN>(),
+ args...)),
+ may_block_(internal::GetValueFromArgList(
+ internal::BooleanArgGetter<MayBlock>(),
+ args...)),
+ with_base_sync_primitives_(internal::GetValueFromArgList(
+ internal::BooleanArgGetter<WithBaseSyncPrimitives>(),
+ args...)) {}
+
+ constexpr TaskTraits(const TaskTraits& other) = default;
+ TaskTraits& operator=(const TaskTraits& other) = default;
- // Applies |priority| to tasks with these traits.
- TaskTraits& WithPriority(TaskPriority priority);
+ // Returns TaskTraits constructed by combining |left| and |right|. If a trait
+ // is specified in both |left| and |right|, the returned TaskTraits will have
+ // the value from |right|.
+ static constexpr TaskTraits Override(const TaskTraits& left,
+ const TaskTraits& right) {
+ return TaskTraits(left, right);
+ }
- // Applies |shutdown_behavior| to tasks with these traits.
- TaskTraits& WithShutdownBehavior(TaskShutdownBehavior shutdown_behavior);
-
- // Returns true if tasks with these traits may block.
- bool may_block() const { return may_block_; }
-
- // Returns true if tasks with these traits may use base/ sync primitives.
- bool with_base_sync_primitives() const { return with_base_sync_primitives_; }
+ // Returns true if the priority was set explicitly.
+ constexpr bool priority_set_explicitly() const {
+ return priority_set_explicitly_;
+ }
// Returns the priority of tasks with these traits.
- TaskPriority priority() const { return priority_; }
+ constexpr TaskPriority priority() const { return priority_; }
+
+ // Returns true if the shutdown behavior was set explicitly.
+ constexpr bool shutdown_behavior_set_explicitly() const {
+ return shutdown_behavior_set_explicitly_;
+ }
// Returns the shutdown behavior of tasks with these traits.
- TaskShutdownBehavior shutdown_behavior() const { return shutdown_behavior_; }
+ constexpr TaskShutdownBehavior shutdown_behavior() const {
+ return shutdown_behavior_;
+ }
+
+ // Returns true if tasks with these traits may block.
+ constexpr bool may_block() const { return may_block_; }
+
+ // Returns true if tasks with these traits may use base/ sync primitives.
+ constexpr bool with_base_sync_primitives() const {
+ return with_base_sync_primitives_;
+ }
private:
- bool may_block_;
- bool with_base_sync_primitives_;
+ constexpr TaskTraits(const TaskTraits& left, const TaskTraits& right)
+ : priority_set_explicitly_(left.priority_set_explicitly_ ||
+ right.priority_set_explicitly_),
+ priority_(right.priority_set_explicitly_ ? right.priority_
+ : left.priority_),
+ shutdown_behavior_set_explicitly_(
+ left.shutdown_behavior_set_explicitly_ ||
+ right.shutdown_behavior_set_explicitly_),
+ shutdown_behavior_(right.shutdown_behavior_set_explicitly_
+ ? right.shutdown_behavior_
+ : left.shutdown_behavior_),
+ may_block_(left.may_block_ || right.may_block_),
+ with_base_sync_primitives_(left.with_base_sync_primitives_ ||
+ right.with_base_sync_primitives_) {}
+
+ bool priority_set_explicitly_;
TaskPriority priority_;
+ bool shutdown_behavior_set_explicitly_;
TaskShutdownBehavior shutdown_behavior_;
+ bool may_block_;
+ bool with_base_sync_primitives_;
};
// Returns string literals for the enums defined in this file. These methods
diff --git a/base/task_scheduler/task_traits_details.h b/base/task_scheduler/task_traits_details.h
new file mode 100644
index 0000000000..05fb60568b
--- /dev/null
+++ b/base/task_scheduler/task_traits_details.h
@@ -0,0 +1,128 @@
+// 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_TASK_SCHEDULER_TASK_TRAITS_DETAILS_H_
+#define BASE_TASK_SCHEDULER_TASK_TRAITS_DETAILS_H_
+
+#include <type_traits>
+#include <utility>
+
+namespace base {
+namespace internal {
+
+// HasArgOfType<CheckedType, ArgTypes...>::value is true iff a type in ArgTypes
+// matches CheckedType.
+template <class...>
+struct HasArgOfType : std::false_type {};
+template <class CheckedType, class FirstArgType, class... ArgTypes>
+struct HasArgOfType<CheckedType, FirstArgType, ArgTypes...>
+ : std::conditional<std::is_same<CheckedType, FirstArgType>::value,
+ std::true_type,
+ HasArgOfType<CheckedType, ArgTypes...>>::type {};
+
+// When the following call is made:
+// GetValueFromArgListImpl(CallFirstTag(), GetterType(), args...);
+// If |args| is empty, the compiler selects the first overload. This overload
+// returns getter.GetDefaultValue(). If |args| is not empty, the compiler
+// prefers using the second overload because the type of the first argument
+// matches exactly. This overload returns getter.GetValueFromArg(first_arg),
+// where |first_arg| is the first element in |args|. If
+// getter.GetValueFromArg(first_arg) isn't defined, the compiler uses the third
+// overload instead. This overload discards the first argument in |args| and
+// makes a recursive call to GetValueFromArgListImpl() with CallFirstTag() as
+// first argument.
+
+// Tag dispatching.
+struct CallSecondTag {};
+struct CallFirstTag : CallSecondTag {};
+
+// Overload 1: Default value.
+template <class GetterType>
+constexpr typename GetterType::ValueType GetValueFromArgListImpl(
+ CallFirstTag,
+ GetterType getter) {
+ return getter.GetDefaultValue();
+}
+
+// Overload 2: Get value from first argument. Check that no argument in |args|
+// has the same type as |first_arg|.
+template <class GetterType,
+ class FirstArgType,
+ class... ArgTypes,
+ class TestGetValueFromArgDefined =
+ decltype(std::declval<GetterType>().GetValueFromArg(
+ std::declval<FirstArgType>()))>
+constexpr typename GetterType::ValueType GetValueFromArgListImpl(
+ CallFirstTag,
+ GetterType getter,
+ const FirstArgType& first_arg,
+ const ArgTypes&... args) {
+ static_assert(!HasArgOfType<FirstArgType, ArgTypes...>::value,
+ "Multiple arguments of the same type were provided to the "
+ "constructor of TaskTraits.");
+ return getter.GetValueFromArg(first_arg);
+}
+
+// Overload 3: Discard first argument.
+template <class GetterType, class FirstArgType, class... ArgTypes>
+constexpr typename GetterType::ValueType GetValueFromArgListImpl(
+ CallSecondTag,
+ GetterType getter,
+ const FirstArgType&,
+ const ArgTypes&... args) {
+ return GetValueFromArgListImpl(CallFirstTag(), getter, args...);
+}
+
+// If there is an argument |arg_of_type| of type Getter::ArgType in |args|,
+// returns getter.GetValueFromArg(arg_of_type). If there are more than one
+// argument of type Getter::ArgType in |args|, generates a compile-time error.
+// Otherwise, returns getter.GetDefaultValue().
+//
+// |getter| must provide:
+//
+// ValueType:
+// The return type of GetValueFromArgListImpl().
+//
+// ArgType:
+// The type of the argument from which GetValueFromArgListImpl() derives its
+// return value.
+//
+// ValueType GetValueFromArg(ArgType):
+// Converts an argument of type ArgType into a value returned by
+// GetValueFromArgListImpl().
+//
+// ValueType GetDefaultValue():
+// Returns the value returned by GetValueFromArgListImpl() if none of its
+// arguments is of type ArgType.
+template <class GetterType, class... ArgTypes>
+constexpr typename GetterType::ValueType GetValueFromArgList(
+ GetterType getter,
+ const ArgTypes&... args) {
+ return GetValueFromArgListImpl(CallFirstTag(), getter, args...);
+}
+
+template <typename ArgType>
+struct BooleanArgGetter {
+ using ValueType = bool;
+ constexpr ValueType GetValueFromArg(ArgType) const { return true; }
+ constexpr ValueType GetDefaultValue() const { return false; }
+};
+
+template <typename ArgType, ArgType DefaultValue>
+struct EnumArgGetter {
+ using ValueType = ArgType;
+ constexpr ValueType GetValueFromArg(ArgType arg) const { return arg; }
+ constexpr ValueType GetDefaultValue() const { return DefaultValue; }
+};
+
+// Allows instantiation of multiple types in one statement. Used to prevent
+// instantiation of the constructor of TaskTraits with inappropriate argument
+// types.
+template <class...>
+struct InitTypes {};
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_TASK_SCHEDULER_TASK_TRAITS_DETAILS_H_
diff --git a/base/task_scheduler/task_traits_unittest.cc b/base/task_scheduler/task_traits_unittest.cc
new file mode 100644
index 0000000000..2a3504844b
--- /dev/null
+++ b/base/task_scheduler/task_traits_unittest.cc
@@ -0,0 +1,175 @@
+// 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/task_scheduler/task_traits.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+TEST(TaskSchedulerTaskTraitsTest, Default) {
+ constexpr TaskTraits traits = {};
+ EXPECT_FALSE(traits.priority_set_explicitly());
+ EXPECT_EQ(TaskPriority::USER_VISIBLE, traits.priority());
+ EXPECT_EQ(TaskShutdownBehavior::SKIP_ON_SHUTDOWN, traits.shutdown_behavior());
+ EXPECT_FALSE(traits.may_block());
+ EXPECT_FALSE(traits.with_base_sync_primitives());
+}
+
+TEST(TaskSchedulerTaskTraitsTest, TaskPriority) {
+ constexpr TaskTraits traits = {TaskPriority::BACKGROUND};
+ EXPECT_TRUE(traits.priority_set_explicitly());
+ EXPECT_EQ(TaskPriority::BACKGROUND, traits.priority());
+ EXPECT_EQ(TaskShutdownBehavior::SKIP_ON_SHUTDOWN, traits.shutdown_behavior());
+ EXPECT_FALSE(traits.may_block());
+ EXPECT_FALSE(traits.with_base_sync_primitives());
+}
+
+TEST(TaskSchedulerTaskTraitsTest, TaskShutdownBehavior) {
+ constexpr TaskTraits traits = {TaskShutdownBehavior::BLOCK_SHUTDOWN};
+ EXPECT_FALSE(traits.priority_set_explicitly());
+ EXPECT_EQ(TaskPriority::USER_VISIBLE, traits.priority());
+ EXPECT_EQ(TaskShutdownBehavior::BLOCK_SHUTDOWN, traits.shutdown_behavior());
+ EXPECT_FALSE(traits.may_block());
+ EXPECT_FALSE(traits.with_base_sync_primitives());
+}
+
+TEST(TaskSchedulerTaskTraitsTest, MayBlock) {
+ constexpr TaskTraits traits = {MayBlock()};
+ EXPECT_FALSE(traits.priority_set_explicitly());
+ EXPECT_EQ(TaskPriority::USER_VISIBLE, traits.priority());
+ EXPECT_EQ(TaskShutdownBehavior::SKIP_ON_SHUTDOWN, traits.shutdown_behavior());
+ EXPECT_TRUE(traits.may_block());
+ EXPECT_FALSE(traits.with_base_sync_primitives());
+}
+
+TEST(TaskSchedulerTaskTraitsTest, WithBaseSyncPrimitives) {
+ constexpr TaskTraits traits = {WithBaseSyncPrimitives()};
+ EXPECT_FALSE(traits.priority_set_explicitly());
+ EXPECT_EQ(TaskPriority::USER_VISIBLE, traits.priority());
+ EXPECT_EQ(TaskShutdownBehavior::SKIP_ON_SHUTDOWN, traits.shutdown_behavior());
+ EXPECT_FALSE(traits.may_block());
+ EXPECT_TRUE(traits.with_base_sync_primitives());
+}
+
+TEST(TaskSchedulerTaskTraitsTest, MultipleTraits) {
+ constexpr TaskTraits traits = {TaskPriority::BACKGROUND,
+ TaskShutdownBehavior::BLOCK_SHUTDOWN,
+ MayBlock(), WithBaseSyncPrimitives()};
+ EXPECT_TRUE(traits.priority_set_explicitly());
+ EXPECT_EQ(TaskPriority::BACKGROUND, traits.priority());
+ EXPECT_EQ(TaskShutdownBehavior::BLOCK_SHUTDOWN, traits.shutdown_behavior());
+ EXPECT_TRUE(traits.may_block());
+ EXPECT_TRUE(traits.with_base_sync_primitives());
+}
+
+TEST(TaskSchedulerTaskTraitsTest, Copy) {
+ constexpr TaskTraits traits = {TaskPriority::BACKGROUND,
+ TaskShutdownBehavior::BLOCK_SHUTDOWN,
+ MayBlock(), WithBaseSyncPrimitives()};
+ constexpr TaskTraits traits_copy(traits);
+ EXPECT_EQ(traits.priority_set_explicitly(),
+ traits_copy.priority_set_explicitly());
+ EXPECT_EQ(traits.priority(), traits_copy.priority());
+ EXPECT_EQ(traits.shutdown_behavior(), traits_copy.shutdown_behavior());
+ EXPECT_EQ(traits.may_block(), traits_copy.may_block());
+ EXPECT_EQ(traits.with_base_sync_primitives(),
+ traits_copy.with_base_sync_primitives());
+}
+
+TEST(TaskSchedulerTaskTraitsTest, OverridePriority) {
+ constexpr TaskTraits left = {TaskPriority::BACKGROUND};
+ constexpr TaskTraits right = {TaskPriority::USER_BLOCKING};
+ constexpr TaskTraits overridden = TaskTraits::Override(left, right);
+ EXPECT_TRUE(overridden.priority_set_explicitly());
+ EXPECT_EQ(TaskPriority::USER_BLOCKING, overridden.priority());
+ EXPECT_FALSE(overridden.shutdown_behavior_set_explicitly());
+ EXPECT_EQ(TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
+ overridden.shutdown_behavior());
+ EXPECT_FALSE(overridden.may_block());
+ EXPECT_FALSE(overridden.with_base_sync_primitives());
+}
+
+TEST(TaskSchedulerTaskTraitsTest, OverrideShutdownBehavior) {
+ constexpr TaskTraits left = {TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN};
+ constexpr TaskTraits right = {TaskShutdownBehavior::BLOCK_SHUTDOWN};
+ constexpr TaskTraits overridden = TaskTraits::Override(left, right);
+ EXPECT_FALSE(overridden.priority_set_explicitly());
+ EXPECT_EQ(TaskPriority::USER_VISIBLE, overridden.priority());
+ EXPECT_TRUE(overridden.shutdown_behavior_set_explicitly());
+ EXPECT_EQ(TaskShutdownBehavior::BLOCK_SHUTDOWN,
+ overridden.shutdown_behavior());
+ EXPECT_FALSE(overridden.may_block());
+ EXPECT_FALSE(overridden.with_base_sync_primitives());
+}
+
+TEST(TaskSchedulerTaskTraitsTest, OverrideMayBlock) {
+ {
+ constexpr TaskTraits left = {MayBlock()};
+ constexpr TaskTraits right = {};
+ constexpr TaskTraits overridden = TaskTraits::Override(left, right);
+ EXPECT_FALSE(overridden.priority_set_explicitly());
+ EXPECT_EQ(TaskPriority::USER_VISIBLE, overridden.priority());
+ EXPECT_FALSE(overridden.shutdown_behavior_set_explicitly());
+ EXPECT_EQ(TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
+ overridden.shutdown_behavior());
+ EXPECT_TRUE(overridden.may_block());
+ EXPECT_FALSE(overridden.with_base_sync_primitives());
+ }
+ {
+ constexpr TaskTraits left = {};
+ constexpr TaskTraits right = {MayBlock()};
+ constexpr TaskTraits overridden = TaskTraits::Override(left, right);
+ EXPECT_FALSE(overridden.priority_set_explicitly());
+ EXPECT_EQ(TaskPriority::USER_VISIBLE, overridden.priority());
+ EXPECT_FALSE(overridden.shutdown_behavior_set_explicitly());
+ EXPECT_EQ(TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
+ overridden.shutdown_behavior());
+ EXPECT_TRUE(overridden.may_block());
+ EXPECT_FALSE(overridden.with_base_sync_primitives());
+ }
+}
+
+TEST(TaskSchedulerTaskTraitsTest, OverrideWithBaseSyncPrimitives) {
+ {
+ constexpr TaskTraits left = {WithBaseSyncPrimitives()};
+ constexpr TaskTraits right = {};
+ constexpr TaskTraits overridden = TaskTraits::Override(left, right);
+ EXPECT_FALSE(overridden.priority_set_explicitly());
+ EXPECT_EQ(TaskPriority::USER_VISIBLE, overridden.priority());
+ EXPECT_FALSE(overridden.shutdown_behavior_set_explicitly());
+ EXPECT_EQ(TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
+ overridden.shutdown_behavior());
+ EXPECT_FALSE(overridden.may_block());
+ EXPECT_TRUE(overridden.with_base_sync_primitives());
+ }
+ {
+ constexpr TaskTraits left = {};
+ constexpr TaskTraits right = {WithBaseSyncPrimitives()};
+ constexpr TaskTraits overridden = TaskTraits::Override(left, right);
+ EXPECT_FALSE(overridden.priority_set_explicitly());
+ EXPECT_EQ(TaskPriority::USER_VISIBLE, overridden.priority());
+ EXPECT_FALSE(overridden.shutdown_behavior_set_explicitly());
+ EXPECT_EQ(TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
+ overridden.shutdown_behavior());
+ EXPECT_FALSE(overridden.may_block());
+ EXPECT_TRUE(overridden.with_base_sync_primitives());
+ }
+}
+
+TEST(TaskSchedulerTaskTraitsTest, OverrideMultipleTraits) {
+ constexpr TaskTraits left = {MayBlock(), TaskPriority::BACKGROUND,
+ TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN};
+ constexpr TaskTraits right = {WithBaseSyncPrimitives(),
+ TaskPriority::USER_BLOCKING};
+ constexpr TaskTraits overridden = TaskTraits::Override(left, right);
+ EXPECT_TRUE(overridden.priority_set_explicitly());
+ EXPECT_EQ(right.priority(), overridden.priority());
+ EXPECT_TRUE(overridden.shutdown_behavior_set_explicitly());
+ EXPECT_EQ(left.shutdown_behavior(), overridden.shutdown_behavior());
+ EXPECT_TRUE(overridden.may_block());
+ EXPECT_TRUE(overridden.with_base_sync_primitives());
+}
+
+} // namespace base
diff --git a/base/task_scheduler/task_traits_unittest.nc b/base/task_scheduler/task_traits_unittest.nc
new file mode 100644
index 0000000000..97f9c4b394
--- /dev/null
+++ b/base/task_scheduler/task_traits_unittest.nc
@@ -0,0 +1,31 @@
+// 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.
+
+// This is a "No Compile Test" suite.
+// http://dev.chromium.org/developers/testing/no-compile-tests
+
+#include "base/task_scheduler/task_traits.h"
+
+namespace base {
+
+#if defined(NCTEST_TASK_TRAITS_MULTIPLE_MAY_BLOCK) // [r"Multiple arguments of the same type were provided to the constructor of TaskTraits."]
+constexpr TaskTraits traits = {MayBlock(), MayBlock()};
+#elif defined(NCTEST_TASK_TRAITS_MULTIPLE_WITH_BASE_SYNC_PRIMITIVES) // [r"Multiple arguments of the same type were provided to the constructor of TaskTraits."]
+constexpr TaskTraits traits = {WithBaseSyncPrimitives(),
+ WithBaseSyncPrimitives()};
+#elif defined(NCTEST_TASK_TRAITS_MULTIPLE_TASK_PRIORITY) // [r"Multiple arguments of the same type were provided to the constructor of TaskTraits."]
+constexpr TaskTraits traits = {TaskPriority::BACKGROUND,
+ TaskPriority::USER_BLOCKING};
+#elif defined(NCTEST_TASK_TRAITS_MULTIPLE_SHUTDOWN_BEHAVIOR) // [r"Multiple arguments of the same type were provided to the constructor of TaskTraits."]
+constexpr TaskTraits traits = {TaskShutdownBehavior::BLOCK_SHUTDOWN,
+ TaskShutdownBehavior::BLOCK_SHUTDOWN};
+#elif defined(NCTEST_TASK_TRAITS_MULTIPLE_SAME_TYPE_MIX) // [r"Multiple arguments of the same type were provided to the constructor of TaskTraits."]
+constexpr TaskTraits traits = {TaskShutdownBehavior::BLOCK_SHUTDOWN,
+ MayBlock(),
+ TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN};
+#elif defined(NCTEST_TASK_TRAITS_INVALID_TYPE) // [r"no matching constructor for initialization of 'const base::TaskTraits'"]
+constexpr TaskTraits traits = {TaskShutdownBehavior::BLOCK_SHUTDOWN, true};
+#endif
+
+} // namespace base
diff --git a/base/task_scheduler/test_utils.cc b/base/task_scheduler/test_utils.cc
new file mode 100644
index 0000000000..eb509f8efb
--- /dev/null
+++ b/base/task_scheduler/test_utils.cc
@@ -0,0 +1,45 @@
+// 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/task_scheduler/test_utils.h"
+
+#include <utility>
+
+#include "base/task_scheduler/scheduler_worker_pool.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace internal {
+namespace test {
+
+MockSchedulerWorkerObserver::MockSchedulerWorkerObserver() = default;
+MockSchedulerWorkerObserver::~MockSchedulerWorkerObserver() = default;
+
+scoped_refptr<Sequence> CreateSequenceWithTask(Task task) {
+ scoped_refptr<Sequence> sequence = MakeRefCounted<Sequence>();
+ sequence->PushTask(std::move(task));
+ return sequence;
+}
+
+scoped_refptr<TaskRunner> CreateTaskRunnerWithExecutionMode(
+ SchedulerWorkerPool* worker_pool,
+ test::ExecutionMode execution_mode) {
+ // Allow tasks posted to the returned TaskRunner to wait on a WaitableEvent.
+ const TaskTraits traits = {WithBaseSyncPrimitives()};
+ switch (execution_mode) {
+ case test::ExecutionMode::PARALLEL:
+ return worker_pool->CreateTaskRunnerWithTraits(traits);
+ case test::ExecutionMode::SEQUENCED:
+ return worker_pool->CreateSequencedTaskRunnerWithTraits(traits);
+ default:
+ // Fall through.
+ break;
+ }
+ ADD_FAILURE() << "Unexpected ExecutionMode";
+ return nullptr;
+}
+
+} // namespace test
+} // namespace internal
+} // namespace base
diff --git a/base/task_scheduler/test_utils.h b/base/task_scheduler/test_utils.h
index dbd1227f52..42e4eed13b 100644
--- a/base/task_scheduler/test_utils.h
+++ b/base/task_scheduler/test_utils.h
@@ -5,14 +5,46 @@
#ifndef BASE_TASK_SCHEDULER_TEST_UTILS_H_
#define BASE_TASK_SCHEDULER_TEST_UTILS_H_
+#include "base/memory/ref_counted.h"
+#include "base/task_runner.h"
+#include "base/task_scheduler/scheduler_worker_observer.h"
+#include "base/task_scheduler/sequence.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
namespace base {
namespace internal {
+
+class SchedulerWorkerPool;
+struct Task;
+
namespace test {
+class MockSchedulerWorkerObserver : public SchedulerWorkerObserver {
+ public:
+ MockSchedulerWorkerObserver();
+ ~MockSchedulerWorkerObserver();
+
+ MOCK_METHOD0(OnSchedulerWorkerMainEntry, void());
+ MOCK_METHOD0(OnSchedulerWorkerMainExit, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockSchedulerWorkerObserver);
+};
+
// An enumeration of possible task scheduler TaskRunner types. Used to
// parametrize relevant task_scheduler tests.
enum class ExecutionMode { PARALLEL, SEQUENCED, SINGLE_THREADED };
+// Creates a Sequence and pushes |task| to it. Returns that sequence.
+scoped_refptr<Sequence> CreateSequenceWithTask(Task task);
+
+// Creates a TaskRunner that posts tasks to |worker_pool| with the
+// |execution_mode| execution mode and the WithBaseSyncPrimitives() trait.
+// Caveat: this does not support ExecutionMode::SINGLE_THREADED.
+scoped_refptr<TaskRunner> CreateTaskRunnerWithExecutionMode(
+ SchedulerWorkerPool* worker_pool,
+ test::ExecutionMode execution_mode);
+
} // namespace test
} // namespace internal
} // namespace base
diff --git a/base/task_scheduler/tracked_ref.h b/base/task_scheduler/tracked_ref.h
new file mode 100644
index 0000000000..4c68622e34
--- /dev/null
+++ b/base/task_scheduler/tracked_ref.h
@@ -0,0 +1,171 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TASK_SCHEDULER_TRACKED_REF_H_
+#define BASE_TASK_SCHEDULER_TRACKED_REF_H_
+
+#include <memory>
+
+#include "base/atomic_ref_count.h"
+#include "base/gtest_prod_util.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/synchronization/waitable_event.h"
+
+namespace base {
+namespace internal {
+
+// TrackedRefs are effectively a ref-counting scheme for objects that have a
+// single owner.
+//
+// Deletion is still controlled by the single owner but ~T() itself will block
+// until all the TrackedRefs handed by its TrackedRefFactory have been released
+// (by ~TrackedRef<T>()).
+//
+// Just like WeakPtrFactory: TrackedRefFactory<T> should be the last member of T
+// to ensure ~TrackedRefFactory<T>() runs first in ~T().
+//
+// The owner of a T should hence be certain that the last TrackedRefs to T are
+// already gone or on their way out before destroying it or ~T() will hang
+// (indicating a bug in the tear down logic -- proper refcounting on the other
+// hand would result in a leak).
+//
+// TrackedRefFactory only makes sense to use on types that are always leaked in
+// production but need to be torn down in tests (blocking destruction is
+// impractical in production -- ref. ScopedAllowBaseSyncPrimitivesForTesting
+// below).
+//
+// Why would we ever need such a thing? In task_scheduler there is a clear
+// ownership hierarchy with mostly single owners and little refcounting. In
+// production nothing is ever torn down so this isn't a problem. In tests
+// however we must JoinForTesting(). At that point, all the raw back T* refs
+// used by the worker threads are problematic because they can result in use-
+// after-frees if a worker outlives the deletion of its corresponding
+// TaskScheduler/TaskTracker/SchedulerWorkerPool/etc.
+//
+// JoinForTesting() isn't so hard when all workers are managed. But with cleanup
+// semantics (reclaiming a worker who's been idle for too long) it becomes
+// tricky because workers can go unaccounted for before they exit their main
+// (https://crbug.com/827615).
+//
+// For that reason and to clearly document the ownership model, task_scheduler
+// uses TrackedRefs.
+//
+// On top of being a clearer ownership model than proper refcounting, a hang in
+// tear down in a test with out-of-order tear down logic is much preferred to
+// letting its worker thread and associated constructs outlive the test
+// (potentially resulting in flakes in unrelated tests running later in the same
+// process).
+//
+// Note: While there's nothing task_scheduler specific about TrackedRefs it
+// requires an ownership model where all the TrackedRefs are released on other
+// threads in sync with ~T(). This isn't a typical use case beyond shutting down
+// TaskScheduler in tests and as such this is kept internal here for now.
+
+template <class T>
+class TrackedRefFactory;
+
+// TrackedRef<T> can be used like a T*.
+template <class T>
+class TrackedRef {
+ public:
+ // Moveable and copyable.
+ TrackedRef(TrackedRef<T>&& other)
+ : ptr_(other.ptr_), factory_(other.factory_) {
+ // Null out |other_|'s factory so its destructor doesn't decrement
+ // |live_tracked_refs_|.
+ other.factory_ = nullptr;
+ }
+ TrackedRef(const TrackedRef<T>& other)
+ : ptr_(other.ptr_), factory_(other.factory_) {
+ factory_->live_tracked_refs_.Increment();
+ }
+
+ // Intentionally not assignable for now because it makes the logic slightly
+ // convoluted and it's not a use case that makes sense for the types using
+ // this at the moment.
+ TrackedRef& operator=(TrackedRef<T>&& other) = delete;
+ TrackedRef& operator=(const TrackedRef<T>& other) = delete;
+
+ ~TrackedRef() {
+ if (factory_ && !factory_->live_tracked_refs_.Decrement()) {
+ DCHECK(factory_->ready_to_destroy_);
+ DCHECK(!factory_->ready_to_destroy_->IsSignaled());
+ factory_->ready_to_destroy_->Signal();
+ }
+ }
+
+ T& operator*() const { return *ptr_; }
+
+ T* operator->() const { return ptr_; }
+
+ explicit operator bool() const { return ptr_ != nullptr; }
+
+ private:
+ friend class TrackedRefFactory<T>;
+
+ TrackedRef(T* ptr, TrackedRefFactory<T>* factory)
+ : ptr_(ptr), factory_(factory) {
+ factory_->live_tracked_refs_.Increment();
+ }
+
+ T* ptr_;
+ TrackedRefFactory<T>* factory_;
+};
+
+// TrackedRefFactory<T> should be the last member of T.
+template <class T>
+class TrackedRefFactory {
+ public:
+ TrackedRefFactory(T* ptr)
+ : ptr_(ptr), self_ref_(WrapUnique(new TrackedRef<T>(ptr_, this))) {
+ DCHECK(ptr_);
+ }
+
+ ~TrackedRefFactory() {
+ // Enter the destruction phase.
+ ready_to_destroy_ = std::make_unique<WaitableEvent>();
+
+ // Release self-ref (if this was the last one it will signal the event right
+ // away).
+ self_ref_.reset();
+
+ ready_to_destroy_->Wait();
+ }
+
+ TrackedRef<T> GetTrackedRef() {
+ // TrackedRefs cannot be obtained after |live_tracked_refs_| has already
+ // reached zero. In other words, the owner of a TrackedRefFactory shouldn't
+ // vend new TrackedRefs while it's being destroyed (owners of TrackedRefs
+ // may still copy/move their refs around during the destruction phase).
+ DCHECK(!live_tracked_refs_.IsZero());
+ return TrackedRef<T>(ptr_, this);
+ }
+
+ private:
+ friend class TrackedRef<T>;
+ FRIEND_TEST_ALL_PREFIXES(TrackedRefTest, CopyAndMoveSemantics);
+
+ T* const ptr_;
+
+ // The number of live TrackedRefs vended by this factory.
+ AtomicRefCount live_tracked_refs_{0};
+
+ // Non-null during the destruction phase. Signaled once |live_tracked_refs_|
+ // reaches 0. Note: while this could a direct member, only initializing it in
+ // the destruction phase avoids keeping a handle open for the entire session.
+ std::unique_ptr<WaitableEvent> ready_to_destroy_;
+
+ // TrackedRefFactory holds a TrackedRef as well to prevent
+ // |live_tracked_refs_| from ever reaching zero before ~TrackedRefFactory().
+ std::unique_ptr<TrackedRef<T>> self_ref_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrackedRefFactory);
+};
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_TASK_SCHEDULER_TRACKED_REF_H_
diff --git a/base/task_scheduler/tracked_ref_unittest.cc b/base/task_scheduler/tracked_ref_unittest.cc
new file mode 100644
index 0000000000..b793c07b6b
--- /dev/null
+++ b/base/task_scheduler/tracked_ref_unittest.cc
@@ -0,0 +1,150 @@
+// 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.
+
+#include "base/task_scheduler/tracked_ref.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/synchronization/atomic_flag.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+
+class ObjectWithTrackedRefs {
+ public:
+ ObjectWithTrackedRefs() : tracked_ref_factory_(this) {}
+ ~ObjectWithTrackedRefs() { under_destruction_.Set(); }
+
+ TrackedRef<ObjectWithTrackedRefs> GetTrackedRef() {
+ return tracked_ref_factory_.GetTrackedRef();
+ }
+
+ bool under_destruction() const { return under_destruction_.IsSet(); }
+
+ private:
+ // True once ~ObjectWithTrackedRefs() has been initiated.
+ AtomicFlag under_destruction_;
+
+ TrackedRefFactory<ObjectWithTrackedRefs> tracked_ref_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ObjectWithTrackedRefs);
+};
+
+} // namespace
+
+// Test that an object with a TrackedRefFactory can be destroyed by a single
+// owner but that its destruction will be blocked on the TrackedRefs being
+// released.
+TEST(TrackedRefTest, TrackedRefObjectDeletion) {
+ Thread thread("TrackedRefTestThread");
+ thread.Start();
+
+ std::unique_ptr<ObjectWithTrackedRefs> obj =
+ std::make_unique<ObjectWithTrackedRefs>();
+
+ TimeTicks begin = TimeTicks::Now();
+
+ thread.task_runner()->PostDelayedTask(
+ FROM_HERE,
+ BindOnce(
+ [](TrackedRef<ObjectWithTrackedRefs> obj) {
+ // By the time this kicks in, the object should already be under
+ // destruction, but blocked on this TrackedRef being released. This
+ // is technically racy (main thread has to run |obj.reset()| and
+ // this thread has to observe the side-effects before this delayed
+ // task fires). If this ever flakes this expectation could be turned
+ // into a while(!obj->under_destruction()); but until that's proven
+ // flaky in practice, this expectation is more readable and
+ // diagnosable then a hang.
+ EXPECT_TRUE(obj->under_destruction());
+ },
+ obj->GetTrackedRef()),
+ TestTimeouts::tiny_timeout());
+
+ // This should kick off destruction but block until the above task resolves
+ // and releases the TrackedRef.
+ obj.reset();
+ EXPECT_GE(TimeTicks::Now() - begin, TestTimeouts::tiny_timeout());
+}
+
+TEST(TrackedRefTest, ManyThreadsRacing) {
+ constexpr int kNumThreads = 16;
+ std::vector<std::unique_ptr<Thread>> threads;
+ for (int i = 0; i < kNumThreads; ++i) {
+ threads.push_back(std::make_unique<Thread>("TrackedRefTestThread"));
+ threads.back()->StartAndWaitForTesting();
+ }
+
+ std::unique_ptr<ObjectWithTrackedRefs> obj =
+ std::make_unique<ObjectWithTrackedRefs>();
+
+ // Send a TrackedRef to each thread.
+ for (auto& thread : threads) {
+ thread->task_runner()->PostTask(
+ FROM_HERE, BindOnce(
+ [](TrackedRef<ObjectWithTrackedRefs> obj) {
+ // Confirm it's still safe to
+ // dereference |obj| (and, bonus, that
+ // playing with TrackedRefs some more
+ // isn't problematic).
+ EXPECT_TRUE(obj->GetTrackedRef());
+ },
+ obj->GetTrackedRef()));
+ }
+
+ // Initiate destruction racily with the above tasks' execution (they will
+ // crash if TrackedRefs aren't WAI).
+ obj.reset();
+}
+
+// Test that instantiating and deleting a TrackedRefFactory without ever taking
+// a TrackedRef on it is fine.
+TEST(TrackedRefTest, NoTrackedRefs) {
+ ObjectWithTrackedRefs obj;
+}
+
+namespace {
+void ConsumesTrackedRef(TrackedRef<ObjectWithTrackedRefs> obj) {}
+} // namespace
+
+// Test that destroying a TrackedRefFactory which had TrackedRefs in the past
+// that are already gone is WAI.
+TEST(TrackedRefTest, NoPendingTrackedRefs) {
+ ObjectWithTrackedRefs obj;
+ ConsumesTrackedRef(obj.GetTrackedRef());
+}
+
+TEST(TrackedRefTest, CopyAndMoveSemantics) {
+ struct Foo {
+ Foo() : factory(this) {}
+ TrackedRefFactory<Foo> factory;
+ };
+ Foo foo;
+
+ EXPECT_EQ(1, foo.factory.live_tracked_refs_.SubtleRefCountForDebug());
+
+ {
+ TrackedRef<Foo> plain = foo.factory.GetTrackedRef();
+ EXPECT_EQ(2, foo.factory.live_tracked_refs_.SubtleRefCountForDebug());
+
+ TrackedRef<Foo> copy_constructed(plain);
+ EXPECT_EQ(3, foo.factory.live_tracked_refs_.SubtleRefCountForDebug());
+
+ TrackedRef<Foo> moved_constructed(std::move(copy_constructed));
+ EXPECT_EQ(3, foo.factory.live_tracked_refs_.SubtleRefCountForDebug());
+ }
+
+ EXPECT_EQ(1, foo.factory.live_tracked_refs_.SubtleRefCountForDebug());
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/template_util.h b/base/template_util.h
index 10154dbbeb..8544aa2945 100644
--- a/base/template_util.h
+++ b/base/template_util.h
@@ -7,22 +7,13 @@
#include <stddef.h>
#include <iosfwd>
+#include <iterator>
#include <type_traits>
#include <utility>
+#include <vector>
#include "build/build_config.h"
-// This hacks around libstdc++ 4.6 missing stuff in type_traits, while we need
-// to support it.
-#define CR_GLIBCXX_4_7_0 20120322
-#define CR_GLIBCXX_4_5_4 20120702
-#define CR_GLIBCXX_4_6_4 20121127
-#if defined(__GLIBCXX__) && \
- (__GLIBCXX__ < CR_GLIBCXX_4_7_0 || __GLIBCXX__ == CR_GLIBCXX_4_5_4 || \
- __GLIBCXX__ == CR_GLIBCXX_4_6_4)
-#define CR_USE_FALLBACKS_FOR_OLD_GLIBCXX
-#endif
-
// Some versions of libstdc++ have partial support for type_traits, but misses
// a smaller subset while removing some of the older non-standard stuff. Assume
// that all versions below 5.0 fall in this category, along with one 5.0
@@ -53,6 +44,25 @@ template <class T> struct is_non_const_reference<const T&> : std::false_type {};
namespace internal {
+// Implementation detail of base::void_t below.
+template <typename...>
+struct make_void {
+ using type = void;
+};
+
+} // namespace internal
+
+// base::void_t is an implementation of std::void_t from C++17.
+//
+// We use |base::internal::make_void| as a helper struct to avoid a C++14
+// defect:
+// http://en.cppreference.com/w/cpp/types/void_t
+// http://open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1558
+template <typename... Ts>
+using void_t = typename ::base::internal::make_void<Ts...>::type;
+
+namespace internal {
+
// Uses expression SFINAE to detect whether using operator<< would work.
template <typename T, typename = void>
struct SupportsOstreamOperator : std::false_type {};
@@ -62,35 +72,23 @@ struct SupportsOstreamOperator<T,
<< std::declval<T>()))>
: std::true_type {};
-} // namespace internal
+// Used to detech whether the given type is an iterator. This is normally used
+// with std::enable_if to provide disambiguation for functions that take
+// templatzed iterators as input.
+template <typename T, typename = void>
+struct is_iterator : std::false_type {};
-// underlying_type produces the integer type backing an enum type.
-// TODO(crbug.com/554293): Remove this when all platforms have this in the std
-// namespace.
-#if defined(CR_USE_FALLBACKS_FOR_OLD_GLIBCXX)
-template <typename T>
-struct underlying_type {
- using type = __underlying_type(T);
-};
-#else
template <typename T>
-using underlying_type = std::underlying_type<T>;
-#endif
+struct is_iterator<T,
+ void_t<typename std::iterator_traits<T>::iterator_category>>
+ : std::true_type {};
-// TODO(crbug.com/554293): Remove this when all platforms have this in the std
-// namespace.
-#if defined(CR_USE_FALLBACKS_FOR_OLD_GLIBCXX)
-template <class T>
-using is_trivially_destructible = std::has_trivial_destructor<T>;
-#else
-template <class T>
-using is_trivially_destructible = std::is_trivially_destructible<T>;
-#endif
+} // namespace internal
// is_trivially_copyable is especially hard to get right.
// - Older versions of libstdc++ will fail to have it like they do for other
-// type traits. In this case we should provide it based on compiler
-// intrinsics. This is covered by the CR_USE_FALLBACKS_FOR_OLD_GLIBCXX define.
+// type traits. This has become a subset of the second point, but used to be
+// handled independently.
// - An experimental release of gcc includes most of type_traits but misses
// is_trivially_copyable, so we still have to avoid using libstdc++ in this
// case, which is covered by CR_USE_FALLBACKS_FOR_OLD_EXPERIMENTAL_GLIBCXX.
@@ -111,8 +109,7 @@ using is_trivially_destructible = std::is_trivially_destructible<T>;
// TODO(crbug.com/554293): Remove this when all platforms have this in the std
// namespace and it works with gcc as needed.
-#if defined(CR_USE_FALLBACKS_FOR_OLD_GLIBCXX) || \
- defined(CR_USE_FALLBACKS_FOR_OLD_EXPERIMENTAL_GLIBCXX) || \
+#if defined(CR_USE_FALLBACKS_FOR_OLD_EXPERIMENTAL_GLIBCXX) || \
defined(CR_USE_FALLBACKS_FOR_GCC_WITH_LIBCXX)
template <typename T>
struct is_trivially_copyable {
@@ -122,7 +119,8 @@ struct is_trivially_copyable {
#if _GNUC_VER >= 501
static constexpr bool value = __is_trivially_copyable(T);
#else
- static constexpr bool value = __has_trivial_copy(T);
+ static constexpr bool value =
+ __has_trivial_copy(T) && __has_trivial_destructor(T);
#endif
};
#else
@@ -130,9 +128,25 @@ template <class T>
using is_trivially_copyable = std::is_trivially_copyable<T>;
#endif
+#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 7
+// Workaround for g++7 and earlier family.
+// Due to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80654, without this
+// Optional<std::vector<T>> where T is non-copyable causes a compile error.
+// As we know it is not trivially copy constructible, explicitly declare so.
+template <typename T>
+struct is_trivially_copy_constructible
+ : std::is_trivially_copy_constructible<T> {};
+
+template <typename... T>
+struct is_trivially_copy_constructible<std::vector<T...>> : std::false_type {};
+#else
+// Otherwise use std::is_trivially_copy_constructible as is.
+template <typename T>
+using is_trivially_copy_constructible = std::is_trivially_copy_constructible<T>;
+#endif
+
} // namespace base
-#undef CR_USE_FALLBACKS_FOR_OLD_GLIBCXX
#undef CR_USE_FALLBACKS_FOR_GCC_WITH_LIBCXX
#undef CR_USE_FALLBACKS_FOR_OLD_EXPERIMENTAL_GLIBCXX
diff --git a/base/template_util_unittest.cc b/base/template_util_unittest.cc
index e34a25b042..2c42445f78 100644
--- a/base/template_util_unittest.cc
+++ b/base/template_util_unittest.cc
@@ -6,6 +6,8 @@
#include <string>
+#include "base/containers/flat_tree.h"
+#include "base/test/move_only_int.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
@@ -69,28 +71,36 @@ static_assert(
internal::SupportsOstreamOperator<const StructWithOperator&>::value,
"struct with operator<< should be printable by const ref");
-// underlying type of enums
-static_assert(std::is_integral<underlying_type<SimpleEnum>::type>::value,
- "simple enum must have some integral type");
-static_assert(
- std::is_same<underlying_type<EnumWithExplicitType>::type, uint64_t>::value,
- "explicit type must be detected");
-static_assert(std::is_same<underlying_type<ScopedEnum>::type, int>::value,
- "scoped enum defaults to int");
+// base::is_trivially_copyable
+class TrivialCopy {
+ public:
+ TrivialCopy(int d) : data_(d) {}
+
+ protected:
+ int data_;
+};
-struct TriviallyDestructible {
- int field;
+class TrivialCopyButWithDestructor : public TrivialCopy {
+ public:
+ TrivialCopyButWithDestructor(int d) : TrivialCopy(d) {}
+ ~TrivialCopyButWithDestructor() { data_ = 0; }
};
-class NonTriviallyDestructible {
- ~NonTriviallyDestructible() {}
+static_assert(base::is_trivially_copyable<TrivialCopy>::value,
+ "TrivialCopy should be detected as trivially copyable");
+static_assert(!base::is_trivially_copyable<TrivialCopyButWithDestructor>::value,
+ "TrivialCopyButWithDestructor should not be detected as "
+ "trivially copyable");
+
+class NoCopy {
+ public:
+ NoCopy(const NoCopy&) = delete;
};
-static_assert(is_trivially_destructible<int>::value, "IsTriviallyDestructible");
-static_assert(is_trivially_destructible<TriviallyDestructible>::value,
- "IsTriviallyDestructible");
-static_assert(!is_trivially_destructible<NonTriviallyDestructible>::value,
- "IsTriviallyDestructible");
+static_assert(
+ !base::is_trivially_copy_constructible<std::vector<NoCopy>>::value,
+ "is_trivially_copy_constructible<std::vector<T>> must be compiled.");
} // namespace
+
} // namespace base
diff --git a/base/test/DEPS b/base/test/DEPS
deleted file mode 100644
index 5827c268b0..0000000000
--- a/base/test/DEPS
+++ /dev/null
@@ -1,3 +0,0 @@
-include_rules = [
- "+third_party/libxml",
-]
diff --git a/base/test/android/java_handler_thread_helpers.cc b/base/test/android/java_handler_thread_helpers.cc
new file mode 100644
index 0000000000..925dc9d8e9
--- /dev/null
+++ b/base/test/android/java_handler_thread_helpers.cc
@@ -0,0 +1,39 @@
+// 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/test/android/java_handler_thread_helpers.h"
+
+#include "base/android/java_handler_thread.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/synchronization/waitable_event.h"
+#include "jni/JavaHandlerThreadHelpers_jni.h"
+
+namespace base {
+namespace android {
+
+// static
+std::unique_ptr<JavaHandlerThread> JavaHandlerThreadHelpers::CreateJavaFirst() {
+ return std::make_unique<JavaHandlerThread>(
+ Java_JavaHandlerThreadHelpers_testAndGetJavaHandlerThread(
+ base::android::AttachCurrentThread()));
+}
+
+// static
+void JavaHandlerThreadHelpers::ThrowExceptionAndAbort(WaitableEvent* event) {
+ JNIEnv* env = AttachCurrentThread();
+ Java_JavaHandlerThreadHelpers_throwException(env);
+ DCHECK(HasException(env));
+ base::MessageLoopCurrentForUI::Get()->Abort();
+ event->Signal();
+}
+
+// static
+bool JavaHandlerThreadHelpers::IsExceptionTestException(
+ ScopedJavaLocalRef<jthrowable> exception) {
+ JNIEnv* env = AttachCurrentThread();
+ return Java_JavaHandlerThreadHelpers_isExceptionTestException(env, exception);
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/test/android/java_handler_thread_helpers.h b/base/test/android/java_handler_thread_helpers.h
new file mode 100644
index 0000000000..5f05cbc684
--- /dev/null
+++ b/base/test/android/java_handler_thread_helpers.h
@@ -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.
+
+#ifndef BASE_ANDROID_JAVA_HANDLER_THREAD_FOR_TESTING_H_
+#define BASE_ANDROID_JAVA_HANDLER_THREAD_FOR_TESTING_H_
+
+#include <jni.h>
+
+#include <memory>
+
+#include "base/android/scoped_java_ref.h"
+
+namespace base {
+
+class WaitableEvent;
+
+namespace android {
+
+class JavaHandlerThread;
+
+// Test-only helpers for working with JavaHandlerThread.
+class JavaHandlerThreadHelpers {
+ public:
+ // Create the Java peer first and test that it works before connecting to the
+ // native object.
+ static std::unique_ptr<JavaHandlerThread> CreateJavaFirst();
+
+ static void ThrowExceptionAndAbort(WaitableEvent* event);
+
+ static bool IsExceptionTestException(
+ ScopedJavaLocalRef<jthrowable> exception);
+
+ private:
+ JavaHandlerThreadHelpers() = default;
+ ~JavaHandlerThreadHelpers() = default;
+};
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_JAVA_HANDLER_THREAD_FOR_TESTING_H_
diff --git a/base/test/android/javatests/src/org/chromium/base/test/params/ParameterProvider.java b/base/test/android/javatests/src/org/chromium/base/test/params/ParameterProvider.java
new file mode 100644
index 0000000000..9bf27bd176
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/params/ParameterProvider.java
@@ -0,0 +1,11 @@
+// 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.
+
+package org.chromium.base.test.params;
+
+/**
+ * Generator to use generate arguments for parameterized test methods.
+ * @see ParameterAnnotations.UseMethodParameter
+ */
+public interface ParameterProvider { Iterable<ParameterSet> getParameters(); }
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/AnnotationProcessingUtils.java b/base/test/android/javatests/src/org/chromium/base/test/util/AnnotationProcessingUtils.java
new file mode 100644
index 0000000000..d335412e1e
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/AnnotationProcessingUtils.java
@@ -0,0 +1,259 @@
+// 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.
+
+package org.chromium.base.test.util;
+
+import android.support.annotation.Nullable;
+
+import org.junit.runner.Description;
+
+import org.chromium.base.VisibleForTesting;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+
+/**
+ * Utility class to help with processing annotations, going around the code to collect them, etc.
+ */
+public abstract class AnnotationProcessingUtils {
+ /**
+ * Returns the closest instance of the requested annotation or null if there is none.
+ * See {@link AnnotationExtractor} for context of "closest".
+ */
+ @SuppressWarnings("unchecked")
+ public static <A extends Annotation> A getAnnotation(Description description, Class<A> clazz) {
+ AnnotationExtractor extractor = new AnnotationExtractor(clazz);
+ return (A) extractor.getClosest(extractor.getMatchingAnnotations(description));
+ }
+
+ /**
+ * Returns the closest instance of the requested annotation or null if there is none.
+ * See {@link AnnotationExtractor} for context of "closest".
+ */
+ @SuppressWarnings("unchecked")
+ public static <A extends Annotation> A getAnnotation(AnnotatedElement element, Class<A> clazz) {
+ AnnotationExtractor extractor = new AnnotationExtractor(clazz);
+ return (A) extractor.getClosest(extractor.getMatchingAnnotations(element));
+ }
+
+ /** See {@link AnnotationExtractor} for details about the output sorting order. */
+ @SuppressWarnings("unchecked")
+ public static <A extends Annotation> List<A> getAnnotations(
+ Description description, Class<A> annotationType) {
+ return (List<A>) new AnnotationExtractor(annotationType)
+ .getMatchingAnnotations(description);
+ }
+
+ /** See {@link AnnotationExtractor} for details about the output sorting order. */
+ @SuppressWarnings("unchecked")
+ public static <A extends Annotation> List<A> getAnnotations(
+ AnnotatedElement annotatedElement, Class<A> annotationType) {
+ return (List<A>) new AnnotationExtractor(annotationType)
+ .getMatchingAnnotations(annotatedElement);
+ }
+
+ private static boolean isChromiumAnnotation(Annotation annotation) {
+ Package pkg = annotation.annotationType().getPackage();
+ return pkg != null && pkg.getName().startsWith("org.chromium");
+ }
+
+ /**
+ * Processes various types of annotated elements ({@link Class}es, {@link Annotation}s,
+ * {@link Description}s, etc.) and extracts the targeted annotations from it. The output will be
+ * sorted in BFS-like order.
+ *
+ * For example, for a method we would get in reverse order:
+ * - the method annotations
+ * - the meta-annotations present on the method annotations,
+ * - the class annotations
+ * - the meta-annotations present on the class annotations,
+ * - the annotations present on the super class,
+ * - the meta-annotations present on the super class annotations,
+ * - etc.
+ *
+ * When multiple annotations are targeted, if more than one is picked up at a given level (for
+ * example directly on the method), they will be returned in the reverse order that they were
+ * provided to the constructor.
+ *
+ * Note: We return the annotations in reverse order because we assume that if some processing
+ * is going to be made on related annotations, the later annotations would likely override
+ * modifications made by the former.
+ *
+ * Note: While resolving meta annotations, we don't expand the explorations to annotations types
+ * that have already been visited. Please file a bug and assign to dgn@ if you think it caused
+ * an issue.
+ */
+ public static class AnnotationExtractor {
+ private final List<Class<? extends Annotation>> mAnnotationTypes;
+ private final Comparator<Class<? extends Annotation>> mAnnotationTypeComparator;
+ private final Comparator<Annotation> mAnnotationComparator;
+
+ @SafeVarargs
+ public AnnotationExtractor(Class<? extends Annotation>... additionalTypes) {
+ this(Arrays.asList(additionalTypes));
+ }
+
+ public AnnotationExtractor(List<Class<? extends Annotation>> additionalTypes) {
+ assert !additionalTypes.isEmpty();
+ mAnnotationTypes = Collections.unmodifiableList(additionalTypes);
+ mAnnotationTypeComparator =
+ (t1, t2) -> mAnnotationTypes.indexOf(t1) - mAnnotationTypes.indexOf(t2);
+ mAnnotationComparator = (t1, t2)
+ -> mAnnotationTypeComparator.compare(t1.annotationType(), t2.annotationType());
+ }
+
+ public List<Annotation> getMatchingAnnotations(Description description) {
+ return getMatchingAnnotations(new AnnotatedNode.DescriptionNode(description));
+ }
+
+ public List<Annotation> getMatchingAnnotations(AnnotatedElement annotatedElement) {
+ AnnotatedNode annotatedNode;
+ if (annotatedElement instanceof Method) {
+ annotatedNode = new AnnotatedNode.MethodNode((Method) annotatedElement);
+ } else if (annotatedElement instanceof Class) {
+ annotatedNode = new AnnotatedNode.ClassNode((Class) annotatedElement);
+ } else {
+ throw new IllegalArgumentException("Unsupported type for " + annotatedElement);
+ }
+
+ return getMatchingAnnotations(annotatedNode);
+ }
+
+ /**
+ * For a given list obtained from the extractor, returns the {@link Annotation} that would
+ * be closest from the extraction point, or {@code null} if the list is empty.
+ */
+ @Nullable
+ public Annotation getClosest(List<Annotation> annotationList) {
+ return annotationList.isEmpty() ? null : annotationList.get(annotationList.size() - 1);
+ }
+
+ @VisibleForTesting
+ Comparator<Class<? extends Annotation>> getTypeComparator() {
+ return mAnnotationTypeComparator;
+ }
+
+ private List<Annotation> getMatchingAnnotations(AnnotatedNode annotatedNode) {
+ List<Annotation> collectedAnnotations = new ArrayList<>();
+ Queue<Annotation> workingSet = new LinkedList<>();
+ Set<Class<? extends Annotation>> visited = new HashSet<>();
+
+ AnnotatedNode currentAnnotationLayer = annotatedNode;
+ while (currentAnnotationLayer != null) {
+ queueAnnotations(currentAnnotationLayer.getAnnotations(), workingSet);
+
+ while (!workingSet.isEmpty()) {
+ sweepAnnotations(collectedAnnotations, workingSet, visited);
+ }
+
+ currentAnnotationLayer = currentAnnotationLayer.getParent();
+ }
+
+ return collectedAnnotations;
+ }
+
+ private void queueAnnotations(List<Annotation> annotations, Queue<Annotation> workingSet) {
+ Collections.sort(annotations, mAnnotationComparator);
+ workingSet.addAll(annotations);
+ }
+
+ private void sweepAnnotations(List<Annotation> collectedAnnotations,
+ Queue<Annotation> workingSet, Set<Class<? extends Annotation>> visited) {
+ // 1. Grab node at the front of the working set.
+ Annotation annotation = workingSet.remove();
+
+ // 2. If it's an annotation of interest, put it aside for the output.
+ if (mAnnotationTypes.contains(annotation.annotationType())) {
+ collectedAnnotations.add(0, annotation);
+ }
+
+ // 3. Check if we can get skip some redundant iterations and avoid cycles.
+ if (!visited.add(annotation.annotationType())) return;
+ if (!isChromiumAnnotation(annotation)) return;
+
+ // 4. Expand the working set
+ queueAnnotations(Arrays.asList(annotation.annotationType().getDeclaredAnnotations()),
+ workingSet);
+ }
+ }
+
+ /**
+ * Abstraction to hide differences between Class, Method and Description with regards to their
+ * annotations and what should be analyzed next.
+ */
+ private static abstract class AnnotatedNode {
+ @Nullable
+ abstract AnnotatedNode getParent();
+
+ abstract List<Annotation> getAnnotations();
+
+ static class DescriptionNode extends AnnotatedNode {
+ final Description mDescription;
+
+ DescriptionNode(Description description) {
+ mDescription = description;
+ }
+
+ @Nullable
+ @Override
+ AnnotatedNode getParent() {
+ return new ClassNode(mDescription.getTestClass());
+ }
+
+ @Override
+ List<Annotation> getAnnotations() {
+ return new ArrayList<>(mDescription.getAnnotations());
+ }
+ }
+
+ static class ClassNode extends AnnotatedNode {
+ final Class<?> mClass;
+
+ ClassNode(Class<?> clazz) {
+ mClass = clazz;
+ }
+
+ @Nullable
+ @Override
+ AnnotatedNode getParent() {
+ Class<?> superClass = mClass.getSuperclass();
+ return superClass == null ? null : new ClassNode(superClass);
+ }
+
+ @Override
+ List<Annotation> getAnnotations() {
+ return Arrays.asList(mClass.getDeclaredAnnotations());
+ }
+ }
+
+ static class MethodNode extends AnnotatedNode {
+ final Method mMethod;
+
+ MethodNode(Method method) {
+ mMethod = method;
+ }
+
+ @Nullable
+ @Override
+ AnnotatedNode getParent() {
+ return new ClassNode(mMethod.getDeclaringClass());
+ }
+
+ @Override
+ List<Annotation> getAnnotations() {
+ return Arrays.asList(mMethod.getDeclaredAnnotations());
+ }
+ }
+ }
+}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/AnnotationRule.java b/base/test/android/javatests/src/org/chromium/base/test/util/AnnotationRule.java
new file mode 100644
index 0000000000..a361ac3ce2
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/AnnotationRule.java
@@ -0,0 +1,139 @@
+// 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.base.test.util;
+
+import android.support.annotation.CallSuper;
+import android.support.annotation.Nullable;
+
+import org.junit.rules.ExternalResource;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Test rule that collects specific annotations to help with test set up and tear down. It is set up
+ * with a list of annotations to look for and exposes the ones picked up on the test through
+ * {@link #getAnnotations()} and related methods.
+ *
+ * Note: The rule always apply, whether it picked up annotations or not.
+ *
+ * Usage:
+ *
+ * <pre>
+ * public class Test {
+ * &#64;Rule
+ * public AnnotationRule rule = new AnnotationRule(Foo.class) {
+ * &#64;Override
+ * protected void before() { ... }
+ *
+ * &#64;Override
+ * protected void after() { ... }
+ * };
+ *
+ * &#64;Test
+ * &#64;Foo
+ * public void myTest() { ... }
+ * }
+ * </pre>
+ *
+ * It can also be used to trigger for multiple annotations:
+ *
+ * <pre>
+ * &#64;DisableFoo
+ * public class Test {
+ * &#64;Rule
+ * public AnnotationRule rule = new AnnotationRule(EnableFoo.class, DisableFoo.class) {
+ * &#64;Override
+ * protected void before() {
+ * // Loops through all the picked up annotations. For myTest(), it would process
+ * // DisableFoo first, then EnableFoo.
+ * for (Annotation annotation : getAnnotations()) {
+ * if (annotation instanceof EnableFoo) { ... }
+ * else if (annotation instanceof DisableFoo) { ... }
+ * }
+ * }
+ *
+ * &#64;Override
+ * protected void after() {
+ * // For myTest(), would return EnableFoo as it's directly set on the method.
+ * Annotation a = getClosestAnnotation();
+ * ...
+ * }
+ * };
+ *
+ * &#64;Test
+ * &#64;EnableFoo
+ * public void myTest() { ... }
+ * }
+ * </pre>
+ *
+ * @see AnnotationProcessingUtils.AnnotationExtractor
+ */
+public abstract class AnnotationRule extends ExternalResource {
+ private final AnnotationProcessingUtils.AnnotationExtractor mAnnotationExtractor;
+ private List<Annotation> mCollectedAnnotations;
+ private Description mTestDescription;
+
+ @SafeVarargs
+ public AnnotationRule(Class<? extends Annotation> firstAnnotationType,
+ Class<? extends Annotation>... additionalTypes) {
+ List<Class<? extends Annotation>> mAnnotationTypes = new ArrayList<>();
+ mAnnotationTypes.add(firstAnnotationType);
+ mAnnotationTypes.addAll(Arrays.asList(additionalTypes));
+ mAnnotationExtractor = new AnnotationProcessingUtils.AnnotationExtractor(mAnnotationTypes);
+ }
+
+ @CallSuper
+ @Override
+ public Statement apply(Statement base, Description description) {
+ mTestDescription = description;
+
+ mCollectedAnnotations = mAnnotationExtractor.getMatchingAnnotations(description);
+
+ // Return the wrapped statement to execute before() and after().
+ return super.apply(base, description);
+ }
+
+ /** @return {@link Description} of the current test. */
+ protected Description getTestDescription() {
+ return mTestDescription;
+ }
+
+ /**
+ * @return The collected annotations that match the declared type(s).
+ * @throws NullPointerException if this is called before annotations have been collected,
+ * which happens when the rule is applied to the {@link Statement}.
+ */
+ protected List<Annotation> getAnnotations() {
+ return Collections.unmodifiableList(mCollectedAnnotations);
+ }
+
+ /**
+ * @return The closest annotation matching the provided type, or {@code null} if there is none.
+ */
+ @SuppressWarnings("unchecked")
+ protected @Nullable <A extends Annotation> A getAnnotation(Class<A> annnotationType) {
+ ListIterator<Annotation> iteratorFromEnd =
+ mCollectedAnnotations.listIterator(mCollectedAnnotations.size());
+ while (iteratorFromEnd.hasPrevious()) {
+ Annotation annotation = iteratorFromEnd.previous();
+ if (annnotationType.isAssignableFrom(annotation.annotationType())) {
+ return (A) annotation;
+ }
+ }
+ return null;
+ }
+
+ protected @Nullable Annotation getClosestAnnotation() {
+ if (mCollectedAnnotations.isEmpty()) return null;
+ return mCollectedAnnotations.get(mCollectedAnnotations.size() - 1);
+ }
+}
diff --git a/base/test/android/junit/src/org/chromium/base/test/util/AnnotationProcessingUtilsTest.java b/base/test/android/junit/src/org/chromium/base/test/util/AnnotationProcessingUtilsTest.java
new file mode 100644
index 0000000000..9acd141e02
--- /dev/null
+++ b/base/test/android/junit/src/org/chromium/base/test/util/AnnotationProcessingUtilsTest.java
@@ -0,0 +1,377 @@
+// 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.
+
+package org.chromium.base.test.util;
+
+import static org.hamcrest.Matchers.contains;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.junit.runner.Description.createTestDescription;
+
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+
+import org.chromium.base.test.util.AnnotationProcessingUtils.AnnotationExtractor;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+/** Test for {@link AnnotationProcessingUtils}. */
+@RunWith(BlockJUnit4ClassRunner.class)
+public class AnnotationProcessingUtilsTest {
+ @Test
+ public void testGetTargetAnnotation_NotOnClassNorMethod() {
+ TargetAnnotation retrievedAnnotation;
+
+ retrievedAnnotation = AnnotationProcessingUtils.getAnnotation(
+ createTestDescription(
+ ClassWithoutTargetAnnotation.class, "methodWithoutAnnotation"),
+ TargetAnnotation.class);
+ assertNull(retrievedAnnotation);
+ }
+
+ @Test
+ public void testGetTargetAnnotation_NotOnClassButOnMethod() {
+ TargetAnnotation retrievedAnnotation;
+
+ retrievedAnnotation = AnnotationProcessingUtils.getAnnotation(
+ getTest(ClassWithoutTargetAnnotation.class, "methodWithTargetAnnotation"),
+ TargetAnnotation.class);
+ assertNotNull(retrievedAnnotation);
+ }
+
+ @Test
+ public void testGetTargetAnnotation_NotOnClassDifferentOneOnMethod() {
+ TargetAnnotation retrievedAnnotation;
+
+ retrievedAnnotation = AnnotationProcessingUtils.getAnnotation(
+ getTest(ClassWithoutTargetAnnotation.class, "methodWithAnnotatedAnnotation"),
+ TargetAnnotation.class);
+ assertNull(retrievedAnnotation);
+ }
+
+ @Test
+ public void testGetTargetAnnotation_OnClassButNotOnMethod() {
+ TargetAnnotation retrievedAnnotation;
+
+ retrievedAnnotation = AnnotationProcessingUtils.getAnnotation(
+ getTest(ClassWithAnnotation.class, "methodWithoutAnnotation"),
+ TargetAnnotation.class);
+ assertNotNull(retrievedAnnotation);
+ assertEquals(Location.Class, retrievedAnnotation.value());
+ }
+
+ @Test
+ public void testGetTargetAnnotation_OnClassAndMethod() {
+ TargetAnnotation retrievedAnnotation;
+
+ retrievedAnnotation = AnnotationProcessingUtils.getAnnotation(
+ getTest(ClassWithAnnotation.class, "methodWithTargetAnnotation"),
+ TargetAnnotation.class);
+ assertNotNull(retrievedAnnotation);
+ assertEquals(Location.Method, retrievedAnnotation.value());
+ }
+
+ @Test
+ @Ignore("Rules not supported yet.")
+ public void testGetTargetAnnotation_OnRuleButNotOnMethod() {
+ TargetAnnotation retrievedAnnotation;
+
+ retrievedAnnotation = AnnotationProcessingUtils.getAnnotation(
+ getTest(ClassWithRule.class, "methodWithoutAnnotation"), TargetAnnotation.class);
+ assertNotNull(retrievedAnnotation);
+ assertEquals(Location.Rule, retrievedAnnotation.value());
+ }
+
+ @Test
+ @Ignore("Rules not supported yet.")
+ public void testGetTargetAnnotation_OnRuleAndMethod() {
+ TargetAnnotation retrievedAnnotation;
+
+ retrievedAnnotation = AnnotationProcessingUtils.getAnnotation(
+ getTest(ClassWithRule.class, "methodWithTargetAnnotation"), TargetAnnotation.class);
+ assertNotNull(retrievedAnnotation);
+ assertEquals(Location.Method, retrievedAnnotation.value());
+ }
+
+ @Test
+ public void testGetMetaAnnotation_Indirectly() {
+ MetaAnnotation retrievedAnnotation;
+
+ retrievedAnnotation = AnnotationProcessingUtils.getAnnotation(
+ getTest(ClassWithoutTargetAnnotation.class, "methodWithAnnotatedAnnotation"),
+ MetaAnnotation.class);
+ assertNotNull(retrievedAnnotation);
+ }
+
+ @Test
+ public void testGetAllTargetAnnotations() {
+ List<TargetAnnotation> retrievedAnnotations;
+
+ retrievedAnnotations = AnnotationProcessingUtils.getAnnotations(
+ getTest(ClassWithAnnotation.class, "methodWithTargetAnnotation"),
+ TargetAnnotation.class);
+ assertEquals(2, retrievedAnnotations.size());
+ assertEquals(Location.Class, retrievedAnnotations.get(0).value());
+ assertEquals(Location.Method, retrievedAnnotations.get(1).value());
+ }
+
+ @Test
+ public void testGetAllTargetAnnotations_OnParentClass() {
+ List<TargetAnnotation> retrievedAnnotations;
+
+ retrievedAnnotations = AnnotationProcessingUtils.getAnnotations(
+ getTest(DerivedClassWithoutAnnotation.class, "newMethodWithoutAnnotation"),
+ TargetAnnotation.class);
+ assertEquals(1, retrievedAnnotations.size());
+ assertEquals(Location.Class, retrievedAnnotations.get(0).value());
+ }
+
+ @Test
+ public void testGetAllTargetAnnotations_OnDerivedMethodAndParentClass() {
+ List<TargetAnnotation> retrievedAnnotations;
+
+ retrievedAnnotations = AnnotationProcessingUtils.getAnnotations(
+ getTest(DerivedClassWithoutAnnotation.class, "newMethodWithTargetAnnotation"),
+ TargetAnnotation.class);
+ assertEquals(2, retrievedAnnotations.size());
+ assertEquals(Location.Class, retrievedAnnotations.get(0).value());
+ assertEquals(Location.DerivedMethod, retrievedAnnotations.get(1).value());
+ }
+
+ @Test
+ public void testGetAllTargetAnnotations_OnDerivedMethodAndParentClassAndMethod() {
+ List<TargetAnnotation> retrievedAnnotations;
+
+ retrievedAnnotations = AnnotationProcessingUtils.getAnnotations(
+ getTest(DerivedClassWithoutAnnotation.class, "methodWithTargetAnnotation"),
+ TargetAnnotation.class);
+ // We should not look at the base implementation of the method. Mostly it should not happen
+ // in the context of tests.
+ assertEquals(2, retrievedAnnotations.size());
+ assertEquals(Location.Class, retrievedAnnotations.get(0).value());
+ assertEquals(Location.DerivedMethod, retrievedAnnotations.get(1).value());
+ }
+
+ @Test
+ public void testGetAllTargetAnnotations_OnDerivedParentAndParentClass() {
+ List<TargetAnnotation> retrievedAnnotations;
+
+ retrievedAnnotations = AnnotationProcessingUtils.getAnnotations(
+ getTest(DerivedClassWithAnnotation.class, "methodWithoutAnnotation"),
+ TargetAnnotation.class);
+ assertEquals(2, retrievedAnnotations.size());
+ assertEquals(Location.Class, retrievedAnnotations.get(0).value());
+ assertEquals(Location.DerivedClass, retrievedAnnotations.get(1).value());
+ }
+
+ @Test
+ public void testGetAllAnnotations() {
+ List<Annotation> annotations;
+
+ AnnotationExtractor annotationExtractor = new AnnotationExtractor(
+ TargetAnnotation.class, MetaAnnotation.class, AnnotatedAnnotation.class);
+ annotations = annotationExtractor.getMatchingAnnotations(
+ getTest(DerivedClassWithAnnotation.class, "methodWithTwoAnnotations"));
+ assertEquals(5, annotations.size());
+
+ // Retrieved annotation order:
+ // On Parent Class
+ assertEquals(TargetAnnotation.class, annotations.get(0).annotationType());
+ assertEquals(Location.Class, ((TargetAnnotation) annotations.get(0)).value());
+
+ // On Class
+ assertEquals(TargetAnnotation.class, annotations.get(1).annotationType());
+ assertEquals(Location.DerivedClass, ((TargetAnnotation) annotations.get(1)).value());
+
+ // Meta-annotations from method
+ assertEquals(MetaAnnotation.class, annotations.get(2).annotationType());
+
+ // On Method
+ assertEquals(AnnotatedAnnotation.class, annotations.get(3).annotationType());
+ assertEquals(TargetAnnotation.class, annotations.get(4).annotationType());
+ assertEquals(Location.DerivedMethod, ((TargetAnnotation) annotations.get(4)).value());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testAnnotationExtractorSortOrder_UnknownAnnotations() {
+ AnnotationExtractor annotationExtractor = new AnnotationExtractor(Target.class);
+ Comparator<Class<? extends Annotation>> comparator =
+ annotationExtractor.getTypeComparator();
+ List<Class<? extends Annotation>> testList =
+ Arrays.asList(Rule.class, Test.class, Override.class, Target.class, Rule.class);
+ testList.sort(comparator);
+ assertThat("Unknown annotations should not be reordered and come before the known ones.",
+ testList,
+ contains(Rule.class, Test.class, Override.class, Rule.class, Target.class));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testAnnotationExtractorSortOrder_KnownAnnotations() {
+ AnnotationExtractor annotationExtractor =
+ new AnnotationExtractor(Test.class, Target.class, Rule.class);
+ Comparator<Class<? extends Annotation>> comparator =
+ annotationExtractor.getTypeComparator();
+ List<Class<? extends Annotation>> testList =
+ Arrays.asList(Rule.class, Test.class, Override.class, Target.class, Rule.class);
+ testList.sort(comparator);
+ assertThat(
+ "Known annotations should be sorted in the same order as provided to the extractor",
+ testList,
+ contains(Override.class, Test.class, Target.class, Rule.class, Rule.class));
+ }
+
+ private static Description getTest(Class<?> klass, String testName) {
+ Description description = null;
+ try {
+ description = new DummyTestRunner(klass).describe(testName);
+ } catch (InitializationError initializationError) {
+ initializationError.printStackTrace();
+ fail("DummyTestRunner initialization failed:" + initializationError.getMessage());
+ }
+ if (description == null) {
+ fail("Not test named '" + testName + "' in class" + klass.getSimpleName());
+ }
+ return description;
+ }
+
+ // region Test Data: Annotations and dummy test classes
+ private enum Location { Unspecified, Class, Method, Rule, DerivedClass, DerivedMethod }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.TYPE, ElementType.METHOD})
+ private @interface TargetAnnotation {
+ Location value() default Location.Unspecified;
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD})
+ private @interface MetaAnnotation {}
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.TYPE, ElementType.METHOD})
+ @MetaAnnotation
+ private @interface AnnotatedAnnotation {}
+
+ private @interface SimpleAnnotation {}
+
+ @SimpleAnnotation
+ private static class ClassWithoutTargetAnnotation {
+ @Test
+ public void methodWithoutAnnotation() {}
+
+ @Test
+ @TargetAnnotation
+ public void methodWithTargetAnnotation() {}
+
+ @Test
+ @AnnotatedAnnotation
+ public void methodWithAnnotatedAnnotation() {}
+ }
+
+ @TargetAnnotation(Location.Class)
+ private static class ClassWithAnnotation {
+ @Test
+ public void methodWithoutAnnotation() {}
+
+ @Test
+ @TargetAnnotation(Location.Method)
+ public void methodWithTargetAnnotation() {}
+
+ @Test
+ @MetaAnnotation
+ public void methodWithMetaAnnotation() {}
+
+ @Test
+ @AnnotatedAnnotation
+ public void methodWithAnnotatedAnnotation() {}
+ }
+
+ private static class DerivedClassWithoutAnnotation extends ClassWithAnnotation {
+ @Test
+ public void newMethodWithoutAnnotation() {}
+
+ @Test
+ @TargetAnnotation(Location.DerivedMethod)
+ public void newMethodWithTargetAnnotation() {}
+
+ @Test
+ @Override
+ @TargetAnnotation(Location.DerivedMethod)
+ public void methodWithTargetAnnotation() {}
+ }
+
+ @TargetAnnotation(Location.DerivedClass)
+ private static class DerivedClassWithAnnotation extends ClassWithAnnotation {
+ @Test
+ public void newMethodWithoutAnnotation() {}
+
+ @Test
+ @AnnotatedAnnotation
+ @TargetAnnotation(Location.DerivedMethod)
+ public void methodWithTwoAnnotations() {}
+ }
+
+ private static class ClassWithRule {
+ @Rule
+ Rule1 mRule = new Rule1();
+
+ @Test
+ public void methodWithoutAnnotation() {}
+
+ @Test
+ @TargetAnnotation
+ public void methodWithTargetAnnotation() {}
+ }
+
+ @TargetAnnotation(Location.Rule)
+ @MetaAnnotation
+ private static class Rule1 implements TestRule {
+ @Override
+ public Statement apply(Statement statement, Description description) {
+ return null;
+ }
+ }
+
+ private static class DummyTestRunner extends BlockJUnit4ClassRunner {
+ public DummyTestRunner(Class<?> klass) throws InitializationError {
+ super(klass);
+ }
+
+ @Override
+ protected void collectInitializationErrors(List<Throwable> errors) {
+ // Do nothing. BlockJUnit4ClassRunner requires the class to be public, but we don't
+ // want/need it.
+ }
+
+ public Description describe(String testName) {
+ List<FrameworkMethod> tests = getTestClass().getAnnotatedMethods(Test.class);
+ for (FrameworkMethod testMethod : tests) {
+ if (testMethod.getName().equals(testName)) return describeChild(testMethod);
+ }
+ return null;
+ }
+ }
+
+ // endregion
+ }
diff --git a/base/test/android/url_utils.cc b/base/test/android/url_utils.cc
new file mode 100644
index 0000000000..7d2a8ed0d7
--- /dev/null
+++ b/base/test/android/url_utils.cc
@@ -0,0 +1,24 @@
+// 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/test/android/url_utils.h"
+
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "jni/UrlUtils_jni.h"
+
+namespace base {
+namespace android {
+
+FilePath GetIsolatedTestRoot() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> jtest_data_dir =
+ Java_UrlUtils_getIsolatedTestRoot(env);
+ base::FilePath test_data_dir(
+ base::android::ConvertJavaStringToUTF8(env, jtest_data_dir));
+ return test_data_dir;
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/test/android/url_utils.h b/base/test/android/url_utils.h
new file mode 100644
index 0000000000..3769bd2064
--- /dev/null
+++ b/base/test/android/url_utils.h
@@ -0,0 +1,23 @@
+// 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_TEST_ANDROID_URL_UTILS_H_
+#define BASE_TEST_ANDROID_URL_UTILS_H_
+
+#include <jni.h>
+
+#include "base/base_export.h"
+#include "base/files/file_path.h"
+
+namespace base {
+namespace android {
+
+// Returns the root of the test data directory. This function will call into
+// Java class UrlUtils through JNI bridge.
+BASE_EXPORT FilePath GetIsolatedTestRoot();
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_TEST_ANDROID_URL_UTILS_H_
diff --git a/base/test/bind_test_util.h b/base/test/bind_test_util.h
new file mode 100644
index 0000000000..0dfcb462cf
--- /dev/null
+++ b/base/test/bind_test_util.h
@@ -0,0 +1,36 @@
+// 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_TEST_BIND_TEST_UTIL_H_
+#define BASE_TEST_BIND_TEST_UTIL_H_
+
+#include "base/bind.h"
+
+namespace base {
+namespace internal {
+
+template <typename F, typename Signature>
+struct BindLambdaHelper;
+
+template <typename F, typename R, typename... Args>
+struct BindLambdaHelper<F, R(Args...)> {
+ static R Run(const std::decay_t<F>& f, Args... args) {
+ return f(std::forward<Args>(args)...);
+ }
+};
+
+} // namespace internal
+
+// A variant of Bind() that can bind capturing lambdas for testing.
+// This doesn't support extra arguments binding as the lambda itself can do.
+template <typename F>
+decltype(auto) BindLambdaForTesting(F&& f) {
+ using Signature = internal::ExtractCallableRunType<std::decay_t<F>>;
+ return BindRepeating(&internal::BindLambdaHelper<F, Signature>::Run,
+ std::forward<F>(f));
+}
+
+} // namespace base
+
+#endif // BASE_TEST_BIND_TEST_UTIL_H_
diff --git a/base/test/copy_only_int.h b/base/test/copy_only_int.h
new file mode 100644
index 0000000000..4e482c932d
--- /dev/null
+++ b/base/test/copy_only_int.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 BASE_TEST_COPY_ONLY_INT_H_
+#define BASE_TEST_COPY_ONLY_INT_H_
+
+#include "base/macros.h"
+
+namespace base {
+
+// A copy-only (not moveable) class that holds an integer. This is designed for
+// testing containers. See also MoveOnlyInt.
+class CopyOnlyInt {
+ public:
+ explicit CopyOnlyInt(int data = 1) : data_(data) {}
+ CopyOnlyInt(const CopyOnlyInt& other) = default;
+ ~CopyOnlyInt() { data_ = 0; }
+
+ friend bool operator==(const CopyOnlyInt& lhs, const CopyOnlyInt& rhs) {
+ return lhs.data_ == rhs.data_;
+ }
+
+ friend bool operator!=(const CopyOnlyInt& lhs, const CopyOnlyInt& rhs) {
+ return !operator==(lhs, rhs);
+ }
+
+ friend bool operator<(const CopyOnlyInt& lhs, const CopyOnlyInt& rhs) {
+ return lhs.data_ < rhs.data_;
+ }
+
+ friend bool operator>(const CopyOnlyInt& lhs, const CopyOnlyInt& rhs) {
+ return rhs < lhs;
+ }
+
+ friend bool operator<=(const CopyOnlyInt& lhs, const CopyOnlyInt& rhs) {
+ return !(rhs < lhs);
+ }
+
+ friend bool operator>=(const CopyOnlyInt& lhs, const CopyOnlyInt& rhs) {
+ return !(lhs < rhs);
+ }
+
+ int data() const { return data_; }
+
+ private:
+ volatile int data_;
+
+ CopyOnlyInt(CopyOnlyInt&&) = delete;
+ CopyOnlyInt& operator=(CopyOnlyInt&) = delete;
+};
+
+} // namespace base
+
+#endif // BASE_TEST_COPY_ONLY_INT_H_
diff --git a/base/test/data/file_util/.gitattributes b/base/test/data/file_util/.gitattributes
new file mode 100644
index 0000000000..07998b9ec3
--- /dev/null
+++ b/base/test/data/file_util/.gitattributes
@@ -0,0 +1,2 @@
+/blank_line_crlf.txt -text
+/crlf.txt -text \ No newline at end of file
diff --git a/base/test/data/json/bom_feff.json b/base/test/data/json/bom_feff.json
index b05ae5083e..5908d678b1 100644
--- a/base/test/data/json/bom_feff.json
+++ b/base/test/data/json/bom_feff.json
@@ -1,10 +1,10 @@
-{
- "appName": {
- "message": "Gmail",
- "description": "App name."
- },
- "appDesc": {
- "message": "بريد إلكتروني يوفر إمكانية البحث مع مقدار أقل من الرسائل غير المرغوب فيها.",
- "description":"App description."
- }
+{
+ "appName": {
+ "message": "Gmail",
+ "description": "App name."
+ },
+ "appDesc": {
+ "message": "بريد إلكتروني يوفر إمكانية البحث مع مقدار أقل من الرسائل غير المرغوب فيها.",
+ "description":"App description."
+ }
} \ No newline at end of file
diff --git a/base/test/fontconfig_util_linux.cc b/base/test/fontconfig_util_linux.cc
new file mode 100644
index 0000000000..6848893f2f
--- /dev/null
+++ b/base/test/fontconfig_util_linux.cc
@@ -0,0 +1,423 @@
+// 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/test/fontconfig_util_linux.h"
+
+#include <fontconfig/fontconfig.h>
+
+#include "base/base_paths.h"
+#include "base/environment.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+
+namespace base {
+
+namespace {
+
+const char kFontsConfTemplate[] = R"(<?xml version="1.0"?>
+<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
+<fontconfig>
+
+ <!-- Cache location. -->
+ <cachedir>$1</cachedir>
+
+ <!-- GCS-synced fonts. -->
+ <dir>$2</dir>
+
+ <!-- Default properties. -->
+ <match target="font">
+ <edit name="embeddedbitmap" mode="append_last">
+ <bool>false</bool>
+ </edit>
+ </match>
+
+ <!-- TODO(thomasanderson): Figure out why this is necessary. -->
+ <match target="pattern">
+ <test name="family" compare="eq">
+ <string>Tinos</string>
+ </test>
+ <test name="prgname" compare="eq">
+ <string>chromevox_tests</string>
+ </test>
+ <edit name="hintstyle" mode="assign">
+ <const>hintslight</const>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>Times</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Tinos</string>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>sans</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>DejaVu Sans</string>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>sans serif</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Arimo</string>
+ </edit>
+ </match>
+
+ <!-- Some layout tests specify Helvetica as a family and we need to make sure
+ that we don't fallback to Tinos for them -->
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>Helvetica</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Arimo</string>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>sans-serif</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Arimo</string>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>serif</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Tinos</string>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>mono</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Cousine</string>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>monospace</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Cousine</string>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>Courier</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Cousine</string>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>cursive</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Comic Sans MS</string>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>fantasy</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Impact</string>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>Monaco</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Tinos</string>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>Arial</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Arimo</string>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>Courier New</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Cousine</string>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>Georgia</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Gelasio</string>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>Times New Roman</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Tinos</string>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test qual="any" name="family">
+ <string>Verdana</string>
+ </test>
+ <!-- NOT metrically compatible! -->
+ <edit name="family" mode="assign">
+ <string>Arimo</string>
+ </edit>
+ </match>
+
+ <!-- TODO(thomasanderson): Move these configs to be test-specific. -->
+ <match target="pattern">
+ <test name="family" compare="eq">
+ <string>NonAntiAliasedSans</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Arimo</string>
+ </edit>
+ <edit name="antialias" mode="assign">
+ <bool>false</bool>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test name="family" compare="eq">
+ <string>SlightHintedGeorgia</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Gelasio</string>
+ </edit>
+ <edit name="hintstyle" mode="assign">
+ <const>hintslight</const>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test name="family" compare="eq">
+ <string>NonHintedSans</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Arimo</string>
+ </edit>
+ <!-- These deliberately contradict each other. The 'hinting' preference
+ should take priority -->
+ <edit name="hintstyle" mode="assign">
+ <const>hintfull</const>
+ </edit>
+ <edit name="hinting" mode="assign">
+ <bool>false</bool>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test name="family" compare="eq">
+ <string>AutohintedSerif</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Arimo</string>
+ </edit>
+ <edit name="autohint" mode="assign">
+ <bool>true</bool>
+ </edit>
+ <edit name="hintstyle" mode="assign">
+ <const>hintmedium</const>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test name="family" compare="eq">
+ <string>HintedSerif</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Arimo</string>
+ </edit>
+ <edit name="autohint" mode="assign">
+ <bool>false</bool>
+ </edit>
+ <edit name="hintstyle" mode="assign">
+ <const>hintmedium</const>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test name="family" compare="eq">
+ <string>FullAndAutoHintedSerif</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Arimo</string>
+ </edit>
+ <edit name="autohint" mode="assign">
+ <bool>true</bool>
+ </edit>
+ <edit name="hintstyle" mode="assign">
+ <const>hintfull</const>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test name="family" compare="eq">
+ <string>SubpixelEnabledArial</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Arimo</string>
+ </edit>
+ <edit name="rgba" mode="assign">
+ <const>rgb</const>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test name="family" compare="eq">
+ <string>SubpixelDisabledArial</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Arimo</string>
+ </edit>
+ <edit name="rgba" mode="assign">
+ <const>none</const>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <!-- FontConfig doesn't currently provide a well-defined way to turn on
+ subpixel positioning. This is just an arbitrary pattern to use after
+ turning subpixel positioning on globally to ensure that we don't have
+ issues with our style getting cached for other tests. -->
+ <test name="family" compare="eq">
+ <string>SubpixelPositioning</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Tinos</string>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <!-- See comments above -->
+ <test name="family" compare="eq">
+ <string>SubpixelPositioningAhem</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>ahem</string>
+ </edit>
+ </match>
+
+ <match target="pattern">
+ <test name="family" compare="eq">
+ <string>SlightHintedTimesNewRoman</string>
+ </test>
+ <edit name="family" mode="assign">
+ <string>Tinos</string>
+ </edit>
+ <edit name="hintstyle" mode="assign">
+ <const>hintslight</const>
+ </edit>
+ </match>
+
+ <!-- When we encounter a character that the current font doesn't
+ support, gfx::GetFallbackFontForChar() returns the first font
+ that does have a glyph for the character. The list of fonts is
+ sorted by a pattern that includes the current locale, but doesn't
+ include a font family (which means that the fallback font depends
+ on the locale but not on the current font).
+
+ DejaVu Sans is commonly the only font that supports some
+ characters, such as "⇧", and even when other candidates are
+ available, DejaVu Sans is commonly first among them, because of
+ the way Fontconfig is ordinarily configured. For example, the
+ configuration in the Fonconfig source lists DejaVu Sans under the
+ sans-serif generic family, and appends sans-serif to patterns
+ that don't already include a generic family (such as the pattern
+ in gfx::GetFallbackFontForChar()).
+
+ To get the same fallback font in the layout tests, we could
+ duplicate this configuration here, or more directly, simply
+ append DejaVu Sans to all patterns. -->
+ <match target="pattern">
+ <edit name="family" mode="append_last">
+ <string>DejaVu Sans</string>
+ </edit>
+ </match>
+
+</fontconfig>
+)";
+
+} // namespace
+
+void SetUpFontconfig() {
+ // TODO(thomasanderson): Use FONTCONFIG_SYSROOT to avoid having to write
+ // a new fonts.conf with updated paths.
+ std::unique_ptr<Environment> env = Environment::Create();
+ if (!env->HasVar("FONTCONFIG_FILE")) {
+ // fonts.conf must be generated on-the-fly since it contains absolute paths
+ // which may be different if
+ // 1. The user moves/renames their build directory (or any parent dirs).
+ // 2. The build directory is mapped on a swarming bot at a location
+ // different from the one the buildbot used.
+ FilePath dir_module;
+ PathService::Get(DIR_MODULE, &dir_module);
+ FilePath font_cache = dir_module.Append("fontconfig_caches");
+ FilePath test_fonts = dir_module.Append("test_fonts");
+ std::string fonts_conf = ReplaceStringPlaceholders(
+ kFontsConfTemplate, {font_cache.value(), test_fonts.value()}, nullptr);
+
+ // Write the data to a different file and then atomically rename it to
+ // fonts.conf. This avoids the file being in a bad state when different
+ // parallel tests call this function at the same time.
+ FilePath fonts_conf_file_temp;
+ if(!CreateTemporaryFileInDir(dir_module, &fonts_conf_file_temp))
+ CHECK(CreateTemporaryFile(&fonts_conf_file_temp));
+ CHECK(
+ WriteFile(fonts_conf_file_temp, fonts_conf.c_str(), fonts_conf.size()));
+ FilePath fonts_conf_file = dir_module.Append("fonts.conf");
+ if (ReplaceFile(fonts_conf_file_temp, fonts_conf_file, nullptr))
+ env->SetVar("FONTCONFIG_FILE", fonts_conf_file.value());
+ else
+ env->SetVar("FONTCONFIG_FILE", fonts_conf_file_temp.value());
+ }
+
+ CHECK(FcInit());
+}
+
+void TearDownFontconfig() {
+ FcFini();
+}
+
+} // namespace base
diff --git a/base/test/fontconfig_util_linux.h b/base/test/fontconfig_util_linux.h
new file mode 100644
index 0000000000..3122526d6e
--- /dev/null
+++ b/base/test/fontconfig_util_linux.h
@@ -0,0 +1,18 @@
+// 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_TEST_FONTCONFIG_UTIL_LINUX_H_
+#define BASE_TEST_FONTCONFIG_UTIL_LINUX_H_
+
+namespace base {
+
+// Initializes Fontconfig with a custom configuration suitable for tests.
+void SetUpFontconfig();
+
+// Deinitializes Fontconfig.
+void TearDownFontconfig();
+
+} // namespace base
+
+#endif // BASE_TEST_FONTCONFIG_UTIL_LINUX_H_
diff --git a/base/test/generate_fontconfig_caches.cc b/base/test/generate_fontconfig_caches.cc
new file mode 100644
index 0000000000..f12eb48106
--- /dev/null
+++ b/base/test/generate_fontconfig_caches.cc
@@ -0,0 +1,24 @@
+// 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.
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/test/fontconfig_util_linux.h"
+
+int main(void) {
+ base::SetUpFontconfig();
+ base::TearDownFontconfig();
+
+ base::FilePath dir_module;
+ CHECK(base::PathService::Get(base::DIR_MODULE, &dir_module));
+ base::FilePath fontconfig_caches = dir_module.Append("fontconfig_caches");
+ CHECK(base::DirectoryExists(fontconfig_caches));
+ base::FilePath stamp = fontconfig_caches.Append("STAMP");
+ CHECK_EQ(0, base::WriteFile(stamp, "", 0));
+
+ return 0;
+}
diff --git a/base/test/gtest_util.cc b/base/test/gtest_util.cc
index 6da902da2e..e5d38f44ae 100644
--- a/base/test/gtest_util.cc
+++ b/base/test/gtest_util.cc
@@ -16,8 +16,7 @@
namespace base {
-TestIdentifier::TestIdentifier() {
-}
+TestIdentifier::TestIdentifier() = default;
TestIdentifier::TestIdentifier(const TestIdentifier& other) = default;
@@ -85,7 +84,7 @@ bool ReadTestNamesFromFile(const FilePath& path,
std::vector<base::TestIdentifier> result;
for (base::ListValue::iterator i = tests->begin(); i != tests->end(); ++i) {
base::DictionaryValue* test = nullptr;
- if (!(*i)->GetAsDictionary(&test))
+ if (!i->GetAsDictionary(&test))
return false;
TestIdentifier test_data;
diff --git a/base/test/gtest_util.h b/base/test/gtest_util.h
index 8dfb1f236f..df2bce92ef 100644
--- a/base/test/gtest_util.h
+++ b/base/test/gtest_util.h
@@ -33,24 +33,6 @@
#else
// DCHECK_IS_ON() && defined(GTEST_HAS_DEATH_TEST) && !defined(OS_ANDROID)
-// Macro copied from gtest-death-test-internal.h as it's (1) internal for now
-// and (2) only defined if !GTEST_HAS_DEATH_TEST which is only a subset of the
-// conditions in which it's needed here.
-// TODO(gab): Expose macro in upstream gtest repo for consumers like us that
-// want more specific death tests and remove this hack.
-# define GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, terminator) \
- GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
- if (::testing::internal::AlwaysTrue()) { \
- GTEST_LOG_(WARNING) \
- << "Death tests are not supported on this platform.\n" \
- << "Statement '" #statement "' cannot be verified."; \
- } else if (::testing::internal::AlwaysFalse()) { \
- ::testing::internal::RE::PartialMatch(".*", (regex)); \
- GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
- terminator; \
- } else \
- ::testing::Message()
-
#define EXPECT_DCHECK_DEATH(statement) \
GTEST_UNSUPPORTED_DEATH_TEST(statement, "Check failed", )
#define ASSERT_DCHECK_DEATH(statement) \
diff --git a/base/test/metrics/histogram_enum_reader.cc b/base/test/metrics/histogram_enum_reader.cc
new file mode 100644
index 0000000000..c1fb379732
--- /dev/null
+++ b/base/test/metrics/histogram_enum_reader.cc
@@ -0,0 +1,156 @@
+// 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.
+
+#include "base/test/metrics/histogram_enum_reader.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/libxml/chromium/libxml_utils.h"
+
+namespace base {
+namespace {
+
+// This is a helper function to the ReadEnumFromHistogramsXml().
+// Extracts single enum (with integer values) from histograms.xml.
+// Expects |reader| to point at given enum.
+// Returns map { value => label } on success, and nullopt on failure.
+Optional<HistogramEnumEntryMap> ParseEnumFromHistogramsXml(
+ const std::string& enum_name,
+ XmlReader* reader) {
+ int entries_index = -1;
+
+ HistogramEnumEntryMap result;
+ bool success = true;
+
+ while (true) {
+ const std::string node_name = reader->NodeName();
+ if (node_name == "enum" && reader->IsClosingElement())
+ break;
+
+ if (node_name == "int") {
+ ++entries_index;
+ std::string value_str;
+ std::string label;
+ const bool has_value = reader->NodeAttribute("value", &value_str);
+ const bool has_label = reader->NodeAttribute("label", &label);
+ if (!has_value) {
+ ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
+ << entries_index << ", label='" << label
+ << "'): No 'value' attribute.";
+ success = false;
+ }
+ if (!has_label) {
+ ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
+ << entries_index << ", value_str='" << value_str
+ << "'): No 'label' attribute.";
+ success = false;
+ }
+
+ HistogramBase::Sample value;
+ if (has_value && !StringToInt(value_str, &value)) {
+ ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
+ << entries_index << ", label='" << label
+ << "', value_str='" << value_str
+ << "'): 'value' attribute is not integer.";
+ success = false;
+ }
+ if (result.count(value)) {
+ ADD_FAILURE() << "Bad " << enum_name << " enum entry (at index "
+ << entries_index << ", label='" << label
+ << "', value_str='" << value_str
+ << "'): duplicate value '" << value_str
+ << "' found in enum. The previous one has label='"
+ << result[value] << "'.";
+ success = false;
+ }
+ if (success)
+ result[value] = label;
+ }
+ // All enum entries are on the same level, so it is enough to iterate
+ // until possible.
+ reader->Next();
+ }
+ if (success)
+ return result;
+ return nullopt;
+}
+
+} // namespace
+
+Optional<HistogramEnumEntryMap> ReadEnumFromEnumsXml(
+ const std::string& enum_name) {
+ FilePath src_root;
+ if (!PathService::Get(DIR_SOURCE_ROOT, &src_root)) {
+ ADD_FAILURE() << "Failed to get src root.";
+ return nullopt;
+ }
+
+ base::FilePath enums_xml = src_root.AppendASCII("tools")
+ .AppendASCII("metrics")
+ .AppendASCII("histograms")
+ .AppendASCII("enums.xml");
+ if (!PathExists(enums_xml)) {
+ ADD_FAILURE() << "enums.xml file does not exist.";
+ return nullopt;
+ }
+
+ XmlReader enums_xml_reader;
+ if (!enums_xml_reader.LoadFile(enums_xml.MaybeAsASCII())) {
+ ADD_FAILURE() << "Failed to load enums.xml";
+ return nullopt;
+ }
+
+ Optional<HistogramEnumEntryMap> result;
+
+ // Implement simple depth first search.
+ while (true) {
+ const std::string node_name = enums_xml_reader.NodeName();
+ if (node_name == "enum") {
+ std::string name;
+ if (enums_xml_reader.NodeAttribute("name", &name) && name == enum_name) {
+ if (result.has_value()) {
+ ADD_FAILURE() << "Duplicate enum '" << enum_name
+ << "' found in enums.xml";
+ return nullopt;
+ }
+
+ const bool got_into_enum = enums_xml_reader.Read();
+ if (!got_into_enum) {
+ ADD_FAILURE() << "Bad enum '" << enum_name
+ << "' (looks empty) found in enums.xml.";
+ return nullopt;
+ }
+
+ result = ParseEnumFromHistogramsXml(enum_name, &enums_xml_reader);
+ if (!result.has_value()) {
+ ADD_FAILURE() << "Bad enum '" << enum_name
+ << "' found in histograms.xml (format error).";
+ return nullopt;
+ }
+ }
+ }
+ // Go deeper if possible (stops at the closing tag of the deepest node).
+ if (enums_xml_reader.Read())
+ continue;
+
+ // Try next node on the same level (skips closing tag).
+ if (enums_xml_reader.Next())
+ continue;
+
+ // Go up until next node on the same level exists.
+ while (enums_xml_reader.Depth() && !enums_xml_reader.SkipToElement()) {
+ }
+
+ // Reached top. histograms.xml consists of the single top level node
+ // 'histogram-configuration', so this is the end.
+ if (!enums_xml_reader.Depth())
+ break;
+ }
+ return result;
+}
+
+} // namespace base
diff --git a/base/test/metrics/histogram_enum_reader.h b/base/test/metrics/histogram_enum_reader.h
new file mode 100644
index 0000000000..ca036c6fc1
--- /dev/null
+++ b/base/test/metrics/histogram_enum_reader.h
@@ -0,0 +1,31 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TEST_METRICS_HISTOGRAM_ENUM_READER_H_
+#define BASE_TEST_METRICS_HISTOGRAM_ENUM_READER_H_
+
+#include <map>
+#include <string>
+
+#include "base/metrics/histogram_base.h"
+#include "base/optional.h"
+
+namespace base {
+
+using HistogramEnumEntryMap = std::map<HistogramBase::Sample, std::string>;
+
+// Find and read the enum with the given |enum_name| (with integer values) from
+// tools/metrics/histograms/enums.xml.
+//
+// Returns map { value => label } so that:
+// <int value="9" label="enable-pinch-virtual-viewport"/>
+// becomes:
+// { 9 => "enable-pinch-virtual-viewport" }
+// Returns empty base::nullopt on failure.
+base::Optional<HistogramEnumEntryMap> ReadEnumFromEnumsXml(
+ const std::string& enum_name);
+
+} // namespace base
+
+#endif // BASE_TEST_METRICS_HISTOGRAM_ENUM_READER_H_
diff --git a/base/test/metrics/histogram_enum_reader_unittest.cc b/base/test/metrics/histogram_enum_reader_unittest.cc
new file mode 100644
index 0000000000..ef2fc4446f
--- /dev/null
+++ b/base/test/metrics/histogram_enum_reader_unittest.cc
@@ -0,0 +1,31 @@
+// 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.
+
+#include "base/test/metrics/histogram_enum_reader.h"
+
+#include "base/optional.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+TEST(HistogramEnumReaderTest, SanityChecks) {
+ {
+ // NOTE: This results in a dependency on the enums.xml file, but to
+ // otherwise inject content would circumvent a lot of the logic of the
+ // method and add additional complexity. "Boolean" is hopefully a pretty
+ // stable enum.
+ Optional<HistogramEnumEntryMap> results = ReadEnumFromEnumsXml("Boolean");
+ ASSERT_TRUE(results);
+ EXPECT_EQ("False", results->at(0));
+ EXPECT_EQ("True", results->at(1));
+ }
+
+ {
+ Optional<HistogramEnumEntryMap> results =
+ ReadEnumFromEnumsXml("TheWorstNameForAnEnum");
+ ASSERT_FALSE(results);
+ }
+}
+
+} // namespace base
diff --git a/base/test/metrics/histogram_tester.cc b/base/test/metrics/histogram_tester.cc
new file mode 100644
index 0000000000..0202f7c79e
--- /dev/null
+++ b/base/test/metrics/histogram_tester.cc
@@ -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.
+
+#include "base/test/metrics/histogram_tester.h"
+
+#include <stddef.h>
+
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/metrics/sample_map.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+HistogramTester::HistogramTester() {
+ // Record any histogram data that exists when the object is created so it can
+ // be subtracted later.
+ for (const auto* const histogram : StatisticsRecorder::GetHistograms()) {
+ histograms_snapshot_[histogram->histogram_name()] =
+ histogram->SnapshotSamples();
+ }
+}
+
+HistogramTester::~HistogramTester() = default;
+
+void HistogramTester::ExpectUniqueSample(
+ const std::string& name,
+ HistogramBase::Sample sample,
+ HistogramBase::Count expected_count) const {
+ HistogramBase* histogram = StatisticsRecorder::FindHistogram(name);
+ EXPECT_NE(nullptr, histogram)
+ << "Histogram \"" << name << "\" does not exist.";
+
+ if (histogram) {
+ std::unique_ptr<HistogramSamples> samples = histogram->SnapshotSamples();
+ CheckBucketCount(name, sample, expected_count, *samples);
+ CheckTotalCount(name, expected_count, *samples);
+ }
+}
+
+void HistogramTester::ExpectBucketCount(
+ const std::string& name,
+ HistogramBase::Sample sample,
+ HistogramBase::Count expected_count) const {
+ HistogramBase* histogram = StatisticsRecorder::FindHistogram(name);
+ EXPECT_NE(nullptr, histogram)
+ << "Histogram \"" << name << "\" does not exist.";
+
+ if (histogram) {
+ std::unique_ptr<HistogramSamples> samples = histogram->SnapshotSamples();
+ CheckBucketCount(name, sample, expected_count, *samples);
+ }
+}
+
+void HistogramTester::ExpectTotalCount(const std::string& name,
+ HistogramBase::Count count) const {
+ HistogramBase* histogram = StatisticsRecorder::FindHistogram(name);
+ if (histogram) {
+ std::unique_ptr<HistogramSamples> samples = histogram->SnapshotSamples();
+ CheckTotalCount(name, count, *samples);
+ } else {
+ // No histogram means there were zero samples.
+ EXPECT_EQ(count, 0) << "Histogram \"" << name << "\" does not exist.";
+ }
+}
+
+void HistogramTester::ExpectTimeBucketCount(const std::string& name,
+ TimeDelta sample,
+ HistogramBase::Count count) const {
+ ExpectBucketCount(name, sample.InMilliseconds(), count);
+}
+
+std::vector<Bucket> HistogramTester::GetAllSamples(
+ const std::string& name) const {
+ std::vector<Bucket> samples;
+ std::unique_ptr<HistogramSamples> snapshot =
+ GetHistogramSamplesSinceCreation(name);
+ if (snapshot) {
+ for (auto it = snapshot->Iterator(); !it->Done(); it->Next()) {
+ HistogramBase::Sample sample;
+ HistogramBase::Count count;
+ it->Get(&sample, nullptr, &count);
+ samples.push_back(Bucket(sample, count));
+ }
+ }
+ return samples;
+}
+
+HistogramBase::Count HistogramTester::GetBucketCount(
+ const std::string& name,
+ HistogramBase::Sample sample) const {
+ HistogramBase* histogram = StatisticsRecorder::FindHistogram(name);
+ EXPECT_NE(nullptr, histogram)
+ << "Histogram \"" << name << "\" does not exist.";
+ HistogramBase::Count count = 0;
+ if (histogram) {
+ std::unique_ptr<HistogramSamples> samples = histogram->SnapshotSamples();
+ GetBucketCountForSamples(name, sample, *samples, &count);
+ }
+ return count;
+}
+
+void HistogramTester::GetBucketCountForSamples(
+ const std::string& name,
+ HistogramBase::Sample sample,
+ const HistogramSamples& samples,
+ HistogramBase::Count* count) const {
+ *count = samples.GetCount(sample);
+ auto histogram_data = histograms_snapshot_.find(name);
+ if (histogram_data != histograms_snapshot_.end())
+ *count -= histogram_data->second->GetCount(sample);
+}
+
+HistogramTester::CountsMap HistogramTester::GetTotalCountsForPrefix(
+ const std::string& prefix) const {
+ EXPECT_TRUE(prefix.find('.') != std::string::npos)
+ << "|prefix| ought to contain at least one period, to avoid matching too"
+ << " many histograms.";
+
+ CountsMap result;
+
+ // Find candidate matches by using the logic built into GetSnapshot().
+ for (const HistogramBase* histogram : StatisticsRecorder::GetHistograms()) {
+ if (!StartsWith(histogram->histogram_name(), prefix,
+ CompareCase::SENSITIVE)) {
+ continue;
+ }
+ std::unique_ptr<HistogramSamples> new_samples =
+ GetHistogramSamplesSinceCreation(histogram->histogram_name());
+ // Omit unchanged histograms from the result.
+ if (new_samples->TotalCount()) {
+ result[histogram->histogram_name()] = new_samples->TotalCount();
+ }
+ }
+ return result;
+}
+
+std::unique_ptr<HistogramSamples>
+HistogramTester::GetHistogramSamplesSinceCreation(
+ const std::string& histogram_name) const {
+ HistogramBase* histogram = StatisticsRecorder::FindHistogram(histogram_name);
+ // Whether the histogram exists or not may not depend on the current test
+ // calling this method, but rather on which tests ran before and possibly
+ // generated a histogram or not (see http://crbug.com/473689). To provide a
+ // response which is independent of the previously run tests, this method
+ // creates empty samples in the absence of the histogram, rather than
+ // returning null.
+ if (!histogram) {
+ return std::unique_ptr<HistogramSamples>(
+ new SampleMap(HashMetricName(histogram_name)));
+ }
+ std::unique_ptr<HistogramSamples> named_samples =
+ histogram->SnapshotSamples();
+ auto original_samples_it = histograms_snapshot_.find(histogram_name);
+ if (original_samples_it != histograms_snapshot_.end())
+ named_samples->Subtract(*original_samples_it->second.get());
+ return named_samples;
+}
+
+std::string HistogramTester::GetAllHistogramsRecorded() const {
+ std::string output;
+
+ for (const auto* const histogram : StatisticsRecorder::GetHistograms()) {
+ std::unique_ptr<HistogramSamples> named_samples =
+ histogram->SnapshotSamples();
+
+ for (const auto& histogram_data : histograms_snapshot_) {
+ if (histogram_data.first == histogram->histogram_name())
+ named_samples->Subtract(*histogram_data.second);
+ }
+
+ if (named_samples->TotalCount()) {
+ auto current_count = histogram->SnapshotSamples()->TotalCount();
+ StringAppendF(&output, "Histogram: %s recorded %d new samples.\n",
+ histogram->histogram_name(), named_samples->TotalCount());
+ if (current_count != named_samples->TotalCount()) {
+ StringAppendF(&output,
+ "WARNING: There were samples recorded to this histogram "
+ "before tester instantiation.\n");
+ }
+ histogram->WriteAscii(&output);
+ StringAppendF(&output, "\n");
+ }
+ }
+
+ return output;
+}
+
+void HistogramTester::CheckBucketCount(const std::string& name,
+ HistogramBase::Sample sample,
+ HistogramBase::Count expected_count,
+ const HistogramSamples& samples) const {
+ int actual_count;
+ GetBucketCountForSamples(name, sample, samples, &actual_count);
+
+ EXPECT_EQ(expected_count, actual_count)
+ << "Histogram \"" << name
+ << "\" does not have the right number of samples (" << expected_count
+ << ") in the expected bucket (" << sample << "). It has (" << actual_count
+ << ").";
+}
+
+void HistogramTester::CheckTotalCount(const std::string& name,
+ HistogramBase::Count expected_count,
+ const HistogramSamples& samples) const {
+ int actual_count = samples.TotalCount();
+ auto histogram_data = histograms_snapshot_.find(name);
+ if (histogram_data != histograms_snapshot_.end())
+ actual_count -= histogram_data->second->TotalCount();
+
+ EXPECT_EQ(expected_count, actual_count)
+ << "Histogram \"" << name
+ << "\" does not have the right total number of samples ("
+ << expected_count << "). It has (" << actual_count << ").";
+}
+
+bool Bucket::operator==(const Bucket& other) const {
+ return min == other.min && count == other.count;
+}
+
+void PrintTo(const Bucket& bucket, std::ostream* os) {
+ *os << "Bucket " << bucket.min << ": " << bucket.count;
+}
+
+} // namespace base
diff --git a/base/test/metrics/histogram_tester.h b/base/test/metrics/histogram_tester.h
new file mode 100644
index 0000000000..b44c80a3af
--- /dev/null
+++ b/base/test/metrics/histogram_tester.h
@@ -0,0 +1,179 @@
+// 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_TEST_METRICS_HISTOGRAM_TESTER_H_
+#define BASE_TEST_METRICS_HISTOGRAM_TESTER_H_
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_base.h"
+#include "base/time/time.h"
+
+namespace base {
+
+struct Bucket;
+class HistogramSamples;
+
+// HistogramTester provides a simple interface for examining histograms, UMA
+// or otherwise. Tests can use this interface to verify that histogram data is
+// getting logged as intended.
+//
+// Note: When using this class from a browser test, one might have to call
+// SubprocessMetricsProvider::MergeHistogramDeltasForTesting() to sync the
+// histogram data between the renderer and browser processes. If it is in a
+// content browser test, then content::FetchHistogramsFromChildProcesses()
+// should be used to achieve that.
+class HistogramTester {
+ public:
+ using CountsMap = std::map<std::string, HistogramBase::Count>;
+
+ // Takes a snapshot of all current histograms counts.
+ HistogramTester();
+ ~HistogramTester();
+
+ // We know the exact number of samples in a bucket, and that no other bucket
+ // should have samples. Measures the diff from the snapshot taken when this
+ // object was constructed.
+ void ExpectUniqueSample(const std::string& name,
+ HistogramBase::Sample sample,
+ HistogramBase::Count expected_count) const;
+ template <typename T>
+ void ExpectUniqueSample(const std::string& name,
+ T sample,
+ HistogramBase::Count expected_count) const {
+ ExpectUniqueSample(name, static_cast<HistogramBase::Sample>(sample),
+ expected_count);
+ }
+
+ // We know the exact number of samples in a bucket, but other buckets may
+ // have samples as well. Measures the diff from the snapshot taken when this
+ // object was constructed.
+ void ExpectBucketCount(const std::string& name,
+ HistogramBase::Sample sample,
+ HistogramBase::Count expected_count) const;
+ template <typename T>
+ void ExpectBucketCount(const std::string& name,
+ T sample,
+ HistogramBase::Count expected_count) const {
+ ExpectBucketCount(name, static_cast<HistogramBase::Sample>(sample),
+ expected_count);
+ }
+
+ // We don't know the values of the samples, but we know how many there are.
+ // This measures the diff from the snapshot taken when this object was
+ // constructed.
+ void ExpectTotalCount(const std::string& name,
+ HistogramBase::Count count) const;
+
+ // We know exact number of samples for buckets corresponding to a time
+ // interval. Other intervals may have samples too.
+ void ExpectTimeBucketCount(const std::string& name,
+ TimeDelta sample,
+ HistogramBase::Count count) const;
+
+ // Returns a list of all of the buckets recorded since creation of this
+ // object, as vector<Bucket>, where the Bucket represents the min boundary of
+ // the bucket and the count of samples recorded to that bucket since creation.
+ //
+ // Example usage, using gMock:
+ // EXPECT_THAT(histogram_tester.GetAllSamples("HistogramName"),
+ // ElementsAre(Bucket(1, 5), Bucket(2, 10), Bucket(3, 5)));
+ //
+ // If you build the expected list programmatically, you can use ContainerEq:
+ // EXPECT_THAT(histogram_tester.GetAllSamples("HistogramName"),
+ // ContainerEq(expected_buckets));
+ //
+ // or EXPECT_EQ if you prefer not to depend on gMock, at the expense of a
+ // slightly less helpful failure message:
+ // EXPECT_EQ(expected_buckets,
+ // histogram_tester.GetAllSamples("HistogramName"));
+ std::vector<Bucket> GetAllSamples(const std::string& name) const;
+
+ // Returns the value of the |sample| bucket for ths histogram |name|.
+ HistogramBase::Count GetBucketCount(const std::string& name,
+ HistogramBase::Sample sample) const;
+
+ // Finds histograms whose names start with |prefix|, and returns them along
+ // with the counts of any samples added since the creation of this object.
+ // Histograms that are unchanged are omitted from the result. The return value
+ // is a map whose keys are the histogram name, and whose values are the sample
+ // count.
+ //
+ // This is useful for cases where the code under test is choosing among a
+ // family of related histograms and incrementing one of them. Typically you
+ // should pass the result of this function directly to EXPECT_THAT.
+ //
+ // Example usage, using gmock (which produces better failure messages):
+ // #include "testing/gmock/include/gmock/gmock.h"
+ // ...
+ // base::HistogramTester::CountsMap expected_counts;
+ // expected_counts["MyMetric.A"] = 1;
+ // expected_counts["MyMetric.B"] = 1;
+ // EXPECT_THAT(histogram_tester.GetTotalCountsForPrefix("MyMetric."),
+ // testing::ContainerEq(expected_counts));
+ CountsMap GetTotalCountsForPrefix(const std::string& prefix) const;
+
+ // Access a modified HistogramSamples containing only what has been logged
+ // to the histogram since the creation of this object.
+ std::unique_ptr<HistogramSamples> GetHistogramSamplesSinceCreation(
+ const std::string& histogram_name) const;
+
+ // Dumps all histograms that have had new samples added to them into a string,
+ // for debugging purposes. Note: this will dump the entire contents of any
+ // modified histograms and not just the modified buckets.
+ std::string GetAllHistogramsRecorded() const;
+
+ private:
+ // Verifies and asserts that value in the |sample| bucket matches the
+ // |expected_count|. The bucket's current value is determined from |samples|
+ // and is modified based on the snapshot stored for histogram |name|.
+ void CheckBucketCount(const std::string& name,
+ HistogramBase::Sample sample,
+ Histogram::Count expected_count,
+ const HistogramSamples& samples) const;
+
+ // Verifies that the total number of values recorded for the histogram |name|
+ // is |expected_count|. This is checked against |samples| minus the snapshot
+ // that was taken for |name|.
+ void CheckTotalCount(const std::string& name,
+ Histogram::Count expected_count,
+ const HistogramSamples& samples) const;
+
+ // Sets the value for |count| to be the value in the |sample| bucket. The
+ // bucket's current value is determined from |samples| and is modified based
+ // on the snapshot stored for histogram |name|.
+ void GetBucketCountForSamples(const std::string& name,
+ HistogramBase::Sample sample,
+ const HistogramSamples& samples,
+ HistogramBase::Count* count) const;
+
+ // Used to determine the histogram changes made during this instance's
+ // lifecycle.
+ std::map<std::string, std::unique_ptr<HistogramSamples>> histograms_snapshot_;
+
+ DISALLOW_COPY_AND_ASSIGN(HistogramTester);
+};
+
+struct Bucket {
+ Bucket(HistogramBase::Sample min, HistogramBase::Count count)
+ : min(min), count(count) {}
+
+ bool operator==(const Bucket& other) const;
+
+ HistogramBase::Sample min;
+ HistogramBase::Count count;
+};
+
+void PrintTo(const Bucket& value, std::ostream* os);
+
+} // namespace base
+
+#endif // BASE_TEST_METRICS_HISTOGRAM_TESTER_H_
diff --git a/base/test/mock_entropy_provider.cc b/base/test/mock_entropy_provider.cc
index 5ebf19a7c7..f3fd2a481e 100644
--- a/base/test/mock_entropy_provider.cc
+++ b/base/test/mock_entropy_provider.cc
@@ -9,7 +9,7 @@ namespace base {
MockEntropyProvider::MockEntropyProvider() : entropy_value_(0.5) {}
MockEntropyProvider::MockEntropyProvider(double entropy_value)
: entropy_value_(entropy_value) {}
-MockEntropyProvider::~MockEntropyProvider() {}
+MockEntropyProvider::~MockEntropyProvider() = default;
double MockEntropyProvider::GetEntropyForTrial(
const std::string& trial_name,
diff --git a/base/test/move_only_int.h b/base/test/move_only_int.h
new file mode 100644
index 0000000000..6e90983624
--- /dev/null
+++ b/base/test/move_only_int.h
@@ -0,0 +1,68 @@
+// 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_TEST_MOVE_ONLY_INT_H_
+#define BASE_TEST_MOVE_ONLY_INT_H_
+
+#include "base/macros.h"
+
+namespace base {
+
+// A move-only class that holds an integer. This is designed for testing
+// containers. See also CopyOnlyInt.
+class MoveOnlyInt {
+ public:
+ explicit MoveOnlyInt(int data = 1) : data_(data) {}
+ MoveOnlyInt(MoveOnlyInt&& other) : data_(other.data_) { other.data_ = 0; }
+ ~MoveOnlyInt() { data_ = 0; }
+
+ MoveOnlyInt& operator=(MoveOnlyInt&& other) {
+ data_ = other.data_;
+ other.data_ = 0;
+ return *this;
+ }
+
+ friend bool operator==(const MoveOnlyInt& lhs, const MoveOnlyInt& rhs) {
+ return lhs.data_ == rhs.data_;
+ }
+
+ friend bool operator!=(const MoveOnlyInt& lhs, const MoveOnlyInt& rhs) {
+ return !operator==(lhs, rhs);
+ }
+
+ friend bool operator<(const MoveOnlyInt& lhs, int rhs) {
+ return lhs.data_ < rhs;
+ }
+
+ friend bool operator<(int lhs, const MoveOnlyInt& rhs) {
+ return lhs < rhs.data_;
+ }
+
+ friend bool operator<(const MoveOnlyInt& lhs, const MoveOnlyInt& rhs) {
+ return lhs.data_ < rhs.data_;
+ }
+
+ friend bool operator>(const MoveOnlyInt& lhs, const MoveOnlyInt& rhs) {
+ return rhs < lhs;
+ }
+
+ friend bool operator<=(const MoveOnlyInt& lhs, const MoveOnlyInt& rhs) {
+ return !(rhs < lhs);
+ }
+
+ friend bool operator>=(const MoveOnlyInt& lhs, const MoveOnlyInt& rhs) {
+ return !(lhs < rhs);
+ }
+
+ int data() const { return data_; }
+
+ private:
+ volatile int data_;
+
+ DISALLOW_COPY_AND_ASSIGN(MoveOnlyInt);
+};
+
+} // namespace base
+
+#endif // BASE_TEST_MOVE_ONLY_INT_H_
diff --git a/base/test/multiprocess_test.cc b/base/test/multiprocess_test.cc
index 3e09e69399..48af3226a0 100644
--- a/base/test/multiprocess_test.cc
+++ b/base/test/multiprocess_test.cc
@@ -8,15 +8,15 @@
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
+#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
namespace base {
#if !defined(OS_ANDROID) && !defined(__ANDROID__) && !defined(__ANDROID_HOST__)
-SpawnChildResult SpawnMultiProcessTestChild(
- const std::string& procname,
- const CommandLine& base_command_line,
- const LaunchOptions& options) {
+Process SpawnMultiProcessTestChild(const std::string& procname,
+ const CommandLine& base_command_line,
+ const LaunchOptions& options) {
CommandLine command_line(base_command_line);
// TODO(viettrungluu): See comment above |MakeCmdLine()| in the header file.
// This is a temporary hack, since |MakeCmdLine()| has to provide a full
@@ -24,9 +24,7 @@ SpawnChildResult SpawnMultiProcessTestChild(
if (!command_line.HasSwitch(switches::kTestChildProcess))
command_line.AppendSwitchASCII(switches::kTestChildProcess, procname);
- SpawnChildResult result;
- result.process = LaunchProcess(command_line, options);
- return result;
+ return LaunchProcess(command_line, options);
}
bool WaitForMultiprocessTestChildExit(const Process& process,
@@ -44,6 +42,7 @@ bool TerminateMultiProcessTestChild(const Process& process,
#endif // !OS_ANDROID && !__ANDROID__ && !__ANDROID_HOST__
CommandLine GetMultiProcessTestChildBaseCommandLine() {
+ base::ScopedAllowBlockingForTesting allow_blocking;
CommandLine cmd_line = *CommandLine::ForCurrentProcess();
cmd_line.SetProgram(MakeAbsoluteFilePath(cmd_line.GetProgram()));
return cmd_line;
@@ -51,12 +50,11 @@ CommandLine GetMultiProcessTestChildBaseCommandLine() {
// MultiProcessTest ------------------------------------------------------------
-MultiProcessTest::MultiProcessTest() {
-}
+MultiProcessTest::MultiProcessTest() = default;
// Don't compile on ARC.
#if 0
-SpawnChildResult MultiProcessTest::SpawnChild(const std::string& procname) {
+Process MultiProcessTest::SpawnChild(const std::string& procname) {
LaunchOptions options;
#if defined(OS_WIN)
options.start_hidden = true;
@@ -64,9 +62,8 @@ SpawnChildResult MultiProcessTest::SpawnChild(const std::string& procname) {
return SpawnChildWithOptions(procname, options);
}
-SpawnChildResult MultiProcessTest::SpawnChildWithOptions(
- const std::string& procname,
- const LaunchOptions& options) {
+Process MultiProcessTest::SpawnChildWithOptions(const std::string& procname,
+ const LaunchOptions& options) {
return SpawnMultiProcessTestChild(procname, MakeCmdLine(procname), options);
}
#endif
diff --git a/base/test/multiprocess_test.h b/base/test/multiprocess_test.h
index f0027d9458..7c00d37215 100644
--- a/base/test/multiprocess_test.h
+++ b/base/test/multiprocess_test.h
@@ -17,17 +17,6 @@ namespace base {
class CommandLine;
-struct SpawnChildResult {
- SpawnChildResult() {}
- SpawnChildResult(SpawnChildResult&& other) = default;
-
- SpawnChildResult& operator=(SpawnChildResult&& other) = default;
-
- Process process;
-
- DISALLOW_COPY_AND_ASSIGN(SpawnChildResult);
-};
-
// Helpers to spawn a child for a multiprocess test and execute a designated
// function. Use these when you already have another base class for your test
// fixture, but you want (some) of your tests to be multiprocess (otherwise you
@@ -44,10 +33,9 @@ struct SpawnChildResult {
// // Maybe set some options (e.g., |start_hidden| on Windows)....
//
// // Start a child process and run |a_test_func|.
-// SpawnChildResult result =
+// base::Process test_child_process =
// base::SpawnMultiProcessTestChild("a_test_func", command_line,
// options);
-// base::Process test_child_process = std::move(result.process);
//
// // Do stuff involving |test_child_process| and the child process....
//
@@ -73,9 +61,9 @@ struct SpawnChildResult {
// |command_line| should be as provided by
// |GetMultiProcessTestChildBaseCommandLine()| (below), possibly with arguments
// added. Note: On Windows, you probably want to set |options.start_hidden|.
-SpawnChildResult SpawnMultiProcessTestChild(const std::string& procname,
- const CommandLine& command_line,
- const LaunchOptions& options);
+Process SpawnMultiProcessTestChild(const std::string& procname,
+ const CommandLine& command_line,
+ const LaunchOptions& options);
// Gets the base command line for |SpawnMultiProcessTestChild()|. To this, you
// may add any flags needed for your child process.
@@ -132,13 +120,13 @@ class MultiProcessTest : public PlatformTest {
// }
//
// Returns the child process.
- SpawnChildResult SpawnChild(const std::string& procname);
+ Process SpawnChild(const std::string& procname);
// Run a child process using the given launch options.
//
// Note: On Windows, you probably want to set |options.start_hidden|.
- SpawnChildResult SpawnChildWithOptions(const std::string& procname,
- const LaunchOptions& options);
+ Process SpawnChildWithOptions(const std::string& procname,
+ const LaunchOptions& options);
// Set up the command line used to spawn the child process.
// Override this to add things to the command line (calling this first in the
diff --git a/base/test/multiprocess_test_android.cc b/base/test/multiprocess_test_android.cc
index a1b8fcbfc0..41085932c1 100644
--- a/base/test/multiprocess_test_android.cc
+++ b/base/test/multiprocess_test_android.cc
@@ -7,7 +7,6 @@
#include <string.h>
#include <vector>
-#include "base/android/context_utils.h"
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/scoped_java_ref.h"
@@ -25,20 +24,17 @@ namespace base {
// - All options except |fds_to_remap| are ignored.
//
// NOTE: This MUST NOT run on the main thread of the NativeTest application.
-SpawnChildResult SpawnMultiProcessTestChild(
- const std::string& procname,
- const CommandLine& base_command_line,
- const LaunchOptions& options) {
+Process SpawnMultiProcessTestChild(const std::string& procname,
+ const CommandLine& base_command_line,
+ const LaunchOptions& options) {
JNIEnv* env = android::AttachCurrentThread();
DCHECK(env);
std::vector<int> fd_keys;
std::vector<int> fd_fds;
- if (options.fds_to_remap) {
- for (auto& iter : *options.fds_to_remap) {
- fd_keys.push_back(iter.second);
- fd_fds.push_back(iter.first);
- }
+ for (auto& iter : options.fds_to_remap) {
+ fd_keys.push_back(iter.second);
+ fd_fds.push_back(iter.first);
}
android::ScopedJavaLocalRef<jobjectArray> fds =
@@ -54,11 +50,8 @@ SpawnChildResult SpawnMultiProcessTestChild(
android::ScopedJavaLocalRef<jobjectArray> j_argv =
android::ToJavaArrayOfStrings(env, command_line.argv());
jint pid = android::Java_MultiprocessTestClientLauncher_launchClient(
- env, android::GetApplicationContext(), j_argv, fds);
-
- SpawnChildResult result;
- result.process = Process(pid);
- return result;
+ env, j_argv, fds);
+ return Process(pid);
}
bool WaitForMultiprocessTestChildExit(const Process& process,
@@ -69,8 +62,7 @@ bool WaitForMultiprocessTestChildExit(const Process& process,
base::android::ScopedJavaLocalRef<jobject> result_code =
android::Java_MultiprocessTestClientLauncher_waitForMainToReturn(
- env, android::GetApplicationContext(), process.Pid(),
- static_cast<int32_t>(timeout.InMilliseconds()));
+ env, process.Pid(), static_cast<int32_t>(timeout.InMilliseconds()));
if (result_code.is_null() ||
Java_MainReturnCodeResult_hasTimedOut(env, result_code)) {
return false;
@@ -88,7 +80,7 @@ bool TerminateMultiProcessTestChild(const Process& process,
DCHECK(env);
return android::Java_MultiprocessTestClientLauncher_terminate(
- env, android::GetApplicationContext(), process.Pid(), exit_code, wait);
+ env, process.Pid(), exit_code, wait);
}
} // namespace base
diff --git a/base/test/opaque_ref_counted.cc b/base/test/opaque_ref_counted.cc
deleted file mode 100644
index 36253e5ef9..0000000000
--- a/base/test/opaque_ref_counted.cc
+++ /dev/null
@@ -1,59 +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/test/opaque_ref_counted.h"
-
-#include "base/macros.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace base {
-
-class OpaqueRefCounted : public RefCounted<OpaqueRefCounted> {
- public:
- OpaqueRefCounted() = default;
-
- int Return42() { return 42; }
-
- private:
- friend class RefCounted<OpaqueRefCounted>;
- ~OpaqueRefCounted() = default;
-
- DISALLOW_COPY_AND_ASSIGN(OpaqueRefCounted);
-};
-
-class OpaqueRefCountedThreadSafe
- : public RefCounted<OpaqueRefCountedThreadSafe> {
- public:
- OpaqueRefCountedThreadSafe() = default;
-
- int Return42() { return 42; }
-
- private:
- friend class RefCounted<OpaqueRefCountedThreadSafe>;
- ~OpaqueRefCountedThreadSafe() = default;
-
- DISALLOW_COPY_AND_ASSIGN(OpaqueRefCountedThreadSafe);
-};
-
-scoped_refptr<OpaqueRefCounted> MakeOpaqueRefCounted() {
- return new OpaqueRefCounted();
-}
-
-void TestOpaqueRefCounted(scoped_refptr<OpaqueRefCounted> p) {
- EXPECT_EQ(42, p->Return42());
-}
-
-scoped_refptr<OpaqueRefCountedThreadSafe> MakeOpaqueRefCountedThreadSafe() {
- return new OpaqueRefCountedThreadSafe();
-}
-
-void TestOpaqueRefCountedThreadSafe(
- scoped_refptr<OpaqueRefCountedThreadSafe> p) {
- EXPECT_EQ(42, p->Return42());
-}
-
-} // namespace base
-
-template class scoped_refptr<base::OpaqueRefCounted>;
-template class scoped_refptr<base::OpaqueRefCountedThreadSafe>;
diff --git a/base/test/opaque_ref_counted.h b/base/test/opaque_ref_counted.h
deleted file mode 100644
index c0ddc87fe1..0000000000
--- a/base/test/opaque_ref_counted.h
+++ /dev/null
@@ -1,29 +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_TEST_OPAQUE_REF_COUNTED_H_
-#define BASE_TEST_OPAQUE_REF_COUNTED_H_
-
-#include "base/memory/ref_counted.h"
-
-namespace base {
-
-// OpaqueRefCounted is a test class for scoped_refptr to ensure it still works
-// when the pointed-to type is opaque (i.e., incomplete).
-class OpaqueRefCounted;
-class OpaqueRefCountedThreadSafe;
-
-// Test functions that return and accept scoped_refptr<OpaqueRefCounted> values.
-scoped_refptr<OpaqueRefCounted> MakeOpaqueRefCounted();
-void TestOpaqueRefCounted(scoped_refptr<OpaqueRefCounted> p);
-scoped_refptr<OpaqueRefCountedThreadSafe> MakeOpaqueRefCountedThreadSafe();
-void TestOpaqueRefCountedThreadSafe(
- scoped_refptr<OpaqueRefCountedThreadSafe> p);
-
-} // namespace base
-
-extern template class scoped_refptr<base::OpaqueRefCounted>;
-extern template class scoped_refptr<base::OpaqueRefCountedThreadSafe>;
-
-#endif // BASE_TEST_OPAQUE_REF_COUNTED_H_
diff --git a/base/test/scoped_environment_variable_override.cc b/base/test/scoped_environment_variable_override.cc
new file mode 100644
index 0000000000..4b7b387141
--- /dev/null
+++ b/base/test/scoped_environment_variable_override.cc
@@ -0,0 +1,33 @@
+// 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/test/scoped_environment_variable_override.h"
+
+#include "base/environment.h"
+
+namespace base {
+namespace test {
+
+ScopedEnvironmentVariableOverride::ScopedEnvironmentVariableOverride(
+ const std::string& variable_name,
+ const std::string& value)
+ : environment_(Environment::Create()),
+ variable_name_(variable_name),
+ overridden_(false),
+ was_set_(false) {
+ was_set_ = environment_->GetVar(variable_name, &old_value_);
+ overridden_ = environment_->SetVar(variable_name, value);
+}
+
+ScopedEnvironmentVariableOverride::~ScopedEnvironmentVariableOverride() {
+ if (overridden_) {
+ if (was_set_)
+ environment_->SetVar(variable_name_, old_value_);
+ else
+ environment_->UnSetVar(variable_name_);
+ }
+}
+
+} // namespace test
+} // namespace base
diff --git a/base/test/scoped_environment_variable_override.h b/base/test/scoped_environment_variable_override.h
new file mode 100644
index 0000000000..b05b5f9a40
--- /dev/null
+++ b/base/test/scoped_environment_variable_override.h
@@ -0,0 +1,40 @@
+// 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_TEST_SCOPED_ENVIRONMENT_VARIABLE_OVERRIDE_H_
+#define BASE_TEST_SCOPED_ENVIRONMENT_VARIABLE_OVERRIDE_H_
+
+#include <memory>
+#include <string>
+
+namespace base {
+
+class Environment;
+
+namespace test {
+
+// Helper class to override |variable_name| environment variable to |value| for
+// the lifetime of this class. Upon destruction, the previous value is restored.
+class ScopedEnvironmentVariableOverride final {
+ public:
+ ScopedEnvironmentVariableOverride(const std::string& variable_name,
+ const std::string& value);
+ ~ScopedEnvironmentVariableOverride();
+
+ base::Environment* GetEnv() { return environment_.get(); }
+ bool IsOverridden() { return overridden_; }
+ bool WasSet() { return was_set_; }
+
+ private:
+ std::unique_ptr<Environment> environment_;
+ std::string variable_name_;
+ bool overridden_;
+ bool was_set_;
+ std::string old_value_;
+};
+
+} // namespace test
+} // namespace base
+
+#endif // BASE_TEST_SCOPED_ENVIRONMENT_VARIABLE_OVERRIDE_H_
diff --git a/base/test/scoped_feature_list.cc b/base/test/scoped_feature_list.cc
index f0f3f4edfb..5b3b2f658a 100644
--- a/base/test/scoped_feature_list.cc
+++ b/base/test/scoped_feature_list.cc
@@ -4,70 +4,228 @@
#include "base/test/scoped_feature_list.h"
-#include <string>
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "base/metrics/field_trial_param_associator.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
namespace base {
namespace test {
namespace {
-static std::string GetFeatureString(
- const std::initializer_list<base::Feature>& features) {
- std::string output;
- for (const base::Feature& feature : features) {
- if (!output.empty())
- output += ",";
- output += feature.name;
+std::vector<StringPiece> GetFeatureVector(
+ const std::vector<Feature>& features) {
+ std::vector<StringPiece> output;
+ for (const Feature& feature : features) {
+ output.push_back(feature.name);
}
+
return output;
}
+// Extracts a feature name from a feature state string. For example, given
+// the input "*MyLovelyFeature<SomeFieldTrial", returns "MyLovelyFeature".
+StringPiece GetFeatureName(StringPiece feature) {
+ StringPiece feature_name = feature;
+
+ // Remove default info.
+ if (feature_name.starts_with("*"))
+ feature_name = feature_name.substr(1);
+
+ // Remove field_trial info.
+ std::size_t index = feature_name.find("<");
+ if (index != std::string::npos)
+ feature_name = feature_name.substr(0, index);
+
+ return feature_name;
+}
+
+struct Features {
+ std::vector<StringPiece> enabled_feature_list;
+ std::vector<StringPiece> disabled_feature_list;
+};
+
+// Merges previously-specified feature overrides with those passed into one of
+// the Init() methods. |features| should be a list of features previously
+// overridden to be in the |override_state|. |merged_features| should contain
+// the enabled and disabled features passed into the Init() method, plus any
+// overrides merged as a result of previous calls to this function.
+void OverrideFeatures(const std::string& features,
+ FeatureList::OverrideState override_state,
+ Features* merged_features) {
+ std::vector<StringPiece> features_list =
+ SplitStringPiece(features, ",", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+
+ for (StringPiece feature : features_list) {
+ StringPiece feature_name = GetFeatureName(feature);
+
+ if (ContainsValue(merged_features->enabled_feature_list, feature_name) ||
+ ContainsValue(merged_features->disabled_feature_list, feature_name))
+ continue;
+
+ if (override_state == FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE) {
+ merged_features->enabled_feature_list.push_back(feature);
+ } else {
+ DCHECK_EQ(override_state,
+ FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE);
+ merged_features->disabled_feature_list.push_back(feature);
+ }
+ }
+}
+
} // namespace
-ScopedFeatureList::ScopedFeatureList() {}
+ScopedFeatureList::ScopedFeatureList() = default;
ScopedFeatureList::~ScopedFeatureList() {
- if (original_feature_list_) {
- base::FeatureList::ClearInstanceForTesting();
- base::FeatureList::RestoreInstanceForTesting(
- std::move(original_feature_list_));
+ // If one of the Init() functions was never called, don't reset anything.
+ if (!init_called_)
+ return;
+
+ if (field_trial_override_) {
+ base::FieldTrialParamAssociator::GetInstance()->ClearParamsForTesting(
+ field_trial_override_->trial_name(),
+ field_trial_override_->group_name());
}
+
+ FeatureList::ClearInstanceForTesting();
+ if (original_feature_list_)
+ FeatureList::RestoreInstanceForTesting(std::move(original_feature_list_));
}
void ScopedFeatureList::Init() {
- std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
+ std::unique_ptr<FeatureList> feature_list(new FeatureList);
feature_list->InitializeFromCommandLine(std::string(), std::string());
InitWithFeatureList(std::move(feature_list));
}
-void ScopedFeatureList::InitWithFeatures(
- const std::initializer_list<base::Feature>& enabled_features,
- const std::initializer_list<base::Feature>& disabled_features) {
- InitFromCommandLine(GetFeatureString(enabled_features),
- GetFeatureString(disabled_features));
-}
-
void ScopedFeatureList::InitWithFeatureList(
std::unique_ptr<FeatureList> feature_list) {
DCHECK(!original_feature_list_);
- original_feature_list_ = base::FeatureList::ClearInstanceForTesting();
- base::FeatureList::SetInstance(std::move(feature_list));
+ original_feature_list_ = FeatureList::ClearInstanceForTesting();
+ FeatureList::SetInstance(std::move(feature_list));
+ init_called_ = true;
}
void ScopedFeatureList::InitFromCommandLine(
const std::string& enable_features,
const std::string& disable_features) {
- std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
+ std::unique_ptr<FeatureList> feature_list(new FeatureList);
feature_list->InitializeFromCommandLine(enable_features, disable_features);
InitWithFeatureList(std::move(feature_list));
}
-void ScopedFeatureList::InitAndEnableFeature(const base::Feature& feature) {
- InitFromCommandLine(feature.name, std::string());
+void ScopedFeatureList::InitWithFeatures(
+ const std::vector<Feature>& enabled_features,
+ const std::vector<Feature>& disabled_features) {
+ InitWithFeaturesAndFieldTrials(enabled_features, {}, disabled_features);
}
-void ScopedFeatureList::InitAndDisableFeature(const base::Feature& feature) {
- InitFromCommandLine(std::string(), feature.name);
+void ScopedFeatureList::InitAndEnableFeature(const Feature& feature) {
+ InitWithFeaturesAndFieldTrials({feature}, {}, {});
+}
+
+void ScopedFeatureList::InitAndEnableFeatureWithFieldTrialOverride(
+ const Feature& feature,
+ FieldTrial* trial) {
+ InitWithFeaturesAndFieldTrials({feature}, {trial}, {});
+}
+
+void ScopedFeatureList::InitAndDisableFeature(const Feature& feature) {
+ InitWithFeaturesAndFieldTrials({}, {}, {feature});
+}
+
+void ScopedFeatureList::InitWithFeatureState(const Feature& feature,
+ bool enabled) {
+ if (enabled) {
+ InitAndEnableFeature(feature);
+ } else {
+ InitAndDisableFeature(feature);
+ }
+}
+
+void ScopedFeatureList::InitWithFeaturesAndFieldTrials(
+ const std::vector<Feature>& enabled_features,
+ const std::vector<FieldTrial*>& trials_for_enabled_features,
+ const std::vector<Feature>& disabled_features) {
+ DCHECK_LE(trials_for_enabled_features.size(), enabled_features.size());
+
+ Features merged_features;
+ merged_features.enabled_feature_list = GetFeatureVector(enabled_features);
+ merged_features.disabled_feature_list = GetFeatureVector(disabled_features);
+
+ FeatureList* feature_list = FeatureList::GetInstance();
+
+ // |current_enabled_features| and |current_disabled_features| must declare out
+ // of if scope to avoid them out of scope before JoinString calls because
+ // |merged_features| may contains StringPiece which holding pointer points to
+ // |current_enabled_features| and |current_disabled_features|.
+ std::string current_enabled_features;
+ std::string current_disabled_features;
+ if (feature_list) {
+ FeatureList::GetInstance()->GetFeatureOverrides(&current_enabled_features,
+ &current_disabled_features);
+ OverrideFeatures(current_enabled_features,
+ FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE,
+ &merged_features);
+ OverrideFeatures(current_disabled_features,
+ FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE,
+ &merged_features);
+ }
+
+ // Add the field trial overrides. This assumes that |enabled_features| are at
+ // the begining of |merged_features.enabled_feature_list|, in the same order.
+ std::vector<FieldTrial*>::const_iterator trial_it =
+ trials_for_enabled_features.begin();
+ auto feature_it = merged_features.enabled_feature_list.begin();
+ std::vector<std::unique_ptr<std::string>> features_with_trial;
+ features_with_trial.reserve(trials_for_enabled_features.size());
+ while (trial_it != trials_for_enabled_features.end()) {
+ features_with_trial.push_back(std::make_unique<std::string>(
+ feature_it->as_string() + "<" + (*trial_it)->trial_name()));
+ // |features_with_trial| owns the string, and feature_it points to it.
+ *feature_it = *(features_with_trial.back());
+ ++trial_it;
+ ++feature_it;
+ }
+
+ std::string enabled = JoinString(merged_features.enabled_feature_list, ",");
+ std::string disabled = JoinString(merged_features.disabled_feature_list, ",");
+ InitFromCommandLine(enabled, disabled);
+}
+
+void ScopedFeatureList::InitAndEnableFeatureWithParameters(
+ const Feature& feature,
+ const std::map<std::string, std::string>& feature_parameters) {
+ if (!FieldTrialList::IsGlobalSetForTesting()) {
+ field_trial_list_ = std::make_unique<base::FieldTrialList>(nullptr);
+ }
+
+ // TODO(crbug.com/794021) Remove this unique field trial name hack when there
+ // is a cleaner solution.
+ // Ensure that each call to this method uses a distinct field trial name.
+ // Otherwise, nested calls might fail due to the shared FieldTrialList
+ // already having the field trial registered.
+ static int num_calls = 0;
+ ++num_calls;
+ std::string kTrialName =
+ "scoped_feature_list_trial_name" + base::NumberToString(num_calls);
+ std::string kTrialGroup = "scoped_feature_list_trial_group";
+
+ field_trial_override_ =
+ base::FieldTrialList::CreateFieldTrial(kTrialName, kTrialGroup);
+ DCHECK(field_trial_override_);
+ FieldTrialParamAssociator::GetInstance()->AssociateFieldTrialParams(
+ kTrialName, kTrialGroup, feature_parameters);
+ InitAndEnableFeatureWithFieldTrialOverride(feature,
+ field_trial_override_.get());
}
} // namespace test
diff --git a/base/test/scoped_feature_list.h b/base/test/scoped_feature_list.h
index 99e07f5374..6e13543ff1 100644
--- a/base/test/scoped_feature_list.h
+++ b/base/test/scoped_feature_list.h
@@ -5,9 +5,14 @@
#ifndef BASE_TEST_SCOPED_FEATURE_LIST_H_
#define BASE_TEST_SCOPED_FEATURE_LIST_H_
-#include <initializer_list>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
#include "base/feature_list.h"
+#include "base/memory/ref_counted.h"
+#include "base/metrics/field_trial.h"
namespace base {
namespace test {
@@ -17,38 +22,98 @@ namespace test {
// Note: Re-using the same object is not allowed. To reset the feature
// list and initialize it anew, destroy an existing scoped list and init
// a new one.
+//
+// ScopedFeatureList needs to be initialized (via one of Init... methods)
+// before running code that inspects the state of features. In practice this
+// means:
+// - In browser tests, one of Init... methods should be called from the
+// overriden ::testing::Test::SetUp method. For example:
+// void SetUp() override {
+// scoped_feature_list_.InitAndEnableFeature(features::kMyFeatureHere);
+// InProcessBrowserTest::SetUp();
+// }
class ScopedFeatureList final {
public:
ScopedFeatureList();
~ScopedFeatureList();
+ // WARNING: This method will reset any globally configured features to their
+ // default values, which can hide feature interaction bugs. Please use
+ // sparingly. https://crbug.com/713390
// Initializes and registers a FeatureList instance with no overrides.
void Init();
+ // WARNING: This method will reset any globally configured features to their
+ // default values, which can hide feature interaction bugs. Please use
+ // sparingly. https://crbug.com/713390
// Initializes and registers the given FeatureList instance.
void InitWithFeatureList(std::unique_ptr<FeatureList> feature_list);
- // Initializes and registers a FeatureList instance with the given enabled
- // and disabled features.
- void InitWithFeatures(
- const std::initializer_list<base::Feature>& enabled_features,
- const std::initializer_list<base::Feature>& disabled_features);
-
- // Initializes and registers a FeatureList instance with the given
+ // WARNING: This method will reset any globally configured features to their
+ // default values, which can hide feature interaction bugs. Please use
+ // sparingly. https://crbug.com/713390
+ // Initializes and registers a FeatureList instance with only the given
// enabled and disabled features (comma-separated names).
void InitFromCommandLine(const std::string& enable_features,
const std::string& disable_features);
- // Initializes and registers a FeatureList instance enabling a single
- // feature.
- void InitAndEnableFeature(const base::Feature& feature);
+ // Initializes and registers a FeatureList instance based on present
+ // FeatureList and overridden with the given enabled and disabled features.
+ // Any feature overrides already present in the global FeatureList will
+ // continue to apply, unless they conflict with the overrides passed into this
+ // method. This is important for testing potentially unexpected feature
+ // interactions.
+ void InitWithFeatures(const std::vector<Feature>& enabled_features,
+ const std::vector<Feature>& disabled_features);
+
+ // Initializes and registers a FeatureList instance based on present
+ // FeatureList and overridden with single enabled feature.
+ void InitAndEnableFeature(const Feature& feature);
+
+ // Initializes and registers a FeatureList instance based on present
+ // FeatureList and overridden with single enabled feature and associated field
+ // trial parameters.
+ // Note: this creates a scoped global field trial list if there is not
+ // currently one.
+ void InitAndEnableFeatureWithParameters(
+ const Feature& feature,
+ const std::map<std::string, std::string>& feature_parameters);
- // Initializes and registers a FeatureList instance disabling a single
- // feature.
- void InitAndDisableFeature(const base::Feature& feature);
+ // Initializes and registers a FeatureList instance based on present
+ // FeatureList and overridden with single disabled feature.
+ void InitAndDisableFeature(const Feature& feature);
+
+ // Initializes and registers a FeatureList instance based on present
+ // FeatureList and overriden with a single feature either enabled or
+ // disabled depending on |enabled|.
+ void InitWithFeatureState(const Feature& feature, bool enabled);
private:
+ // Initializes and registers a FeatureList instance based on present
+ // FeatureList and overridden with the given enabled and disabled features.
+ // Any feature overrides already present in the global FeatureList will
+ // continue to apply, unless they conflict with the overrides passed into this
+ // method.
+ // Field trials will apply to the enabled features, in the same order. The
+ // number of trials must be less (or equal) than the number of enabled
+ // features.
+ // Trials are expected to outlive the ScopedFeatureList.
+ void InitWithFeaturesAndFieldTrials(
+ const std::vector<Feature>& enabled_features,
+ const std::vector<FieldTrial*>& trials_for_enabled_features,
+ const std::vector<Feature>& disabled_features);
+
+ // Initializes and registers a FeatureList instance based on present
+ // FeatureList and overridden with single enabled feature and associated field
+ // trial override.
+ // |trial| is expected to outlive the ScopedFeatureList.
+ void InitAndEnableFeatureWithFieldTrialOverride(const Feature& feature,
+ FieldTrial* trial);
+
+ bool init_called_ = false;
std::unique_ptr<FeatureList> original_feature_list_;
+ scoped_refptr<FieldTrial> field_trial_override_;
+ std::unique_ptr<base::FieldTrialList> field_trial_list_;
DISALLOW_COPY_AND_ASSIGN(ScopedFeatureList);
};
diff --git a/base/test/scoped_feature_list_unittest.cc b/base/test/scoped_feature_list_unittest.cc
new file mode 100644
index 0000000000..44c44f4954
--- /dev/null
+++ b/base/test/scoped_feature_list_unittest.cc
@@ -0,0 +1,308 @@
+// 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/test/scoped_feature_list.h"
+
+#include <map>
+#include <string>
+#include <utility>
+
+#include "base/metrics/field_trial.h"
+#include "base/metrics/field_trial_params.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace test {
+
+namespace {
+
+const Feature kTestFeature1{"TestFeature1", FEATURE_DISABLED_BY_DEFAULT};
+const Feature kTestFeature2{"TestFeature2", FEATURE_DISABLED_BY_DEFAULT};
+
+void ExpectFeatures(const std::string& enabled_features,
+ const std::string& disabled_features) {
+ FeatureList* list = FeatureList::GetInstance();
+ std::string actual_enabled_features;
+ std::string actual_disabled_features;
+
+ list->GetFeatureOverrides(&actual_enabled_features,
+ &actual_disabled_features);
+
+ EXPECT_EQ(enabled_features, actual_enabled_features);
+ EXPECT_EQ(disabled_features, actual_disabled_features);
+}
+
+} // namespace
+
+class ScopedFeatureListTest : public testing::Test {
+ public:
+ ScopedFeatureListTest() {
+ // Clear default feature list.
+ std::unique_ptr<FeatureList> feature_list(new FeatureList);
+ feature_list->InitializeFromCommandLine(std::string(), std::string());
+ original_feature_list_ = FeatureList::ClearInstanceForTesting();
+ FeatureList::SetInstance(std::move(feature_list));
+ }
+
+ ~ScopedFeatureListTest() override {
+ // Restore feature list.
+ if (original_feature_list_) {
+ FeatureList::ClearInstanceForTesting();
+ FeatureList::RestoreInstanceForTesting(std::move(original_feature_list_));
+ }
+ }
+
+ private:
+ // Save the present FeatureList and restore it after test finish.
+ std::unique_ptr<FeatureList> original_feature_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedFeatureListTest);
+};
+
+TEST_F(ScopedFeatureListTest, BasicScoped) {
+ ExpectFeatures(std::string(), std::string());
+ EXPECT_FALSE(FeatureList::IsEnabled(kTestFeature1));
+ {
+ test::ScopedFeatureList feature_list1;
+ feature_list1.InitFromCommandLine("TestFeature1", std::string());
+ ExpectFeatures("TestFeature1", std::string());
+ EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature1));
+ }
+ ExpectFeatures(std::string(), std::string());
+ EXPECT_FALSE(FeatureList::IsEnabled(kTestFeature1));
+}
+
+TEST_F(ScopedFeatureListTest, EnableWithFeatureParameters) {
+ const char kParam1[] = "param_1";
+ const char kParam2[] = "param_2";
+ const char kValue1[] = "value_1";
+ const char kValue2[] = "value_2";
+ std::map<std::string, std::string> parameters;
+ parameters[kParam1] = kValue1;
+ parameters[kParam2] = kValue2;
+
+ ExpectFeatures(std::string(), std::string());
+ EXPECT_EQ(nullptr, FeatureList::GetFieldTrial(kTestFeature1));
+ EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam1));
+ EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam2));
+ FieldTrial::ActiveGroups active_groups;
+ FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
+ EXPECT_EQ(0u, active_groups.size());
+
+ {
+ test::ScopedFeatureList feature_list;
+
+ feature_list.InitAndEnableFeatureWithParameters(kTestFeature1, parameters);
+ EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature1));
+ EXPECT_EQ(kValue1,
+ GetFieldTrialParamValueByFeature(kTestFeature1, kParam1));
+ EXPECT_EQ(kValue2,
+ GetFieldTrialParamValueByFeature(kTestFeature1, kParam2));
+ active_groups.clear();
+ FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
+ EXPECT_EQ(1u, active_groups.size());
+ }
+
+ ExpectFeatures(std::string(), std::string());
+ EXPECT_EQ(nullptr, FeatureList::GetFieldTrial(kTestFeature1));
+ EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam1));
+ EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam2));
+ active_groups.clear();
+ FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
+ EXPECT_EQ(0u, active_groups.size());
+}
+
+TEST_F(ScopedFeatureListTest, OverrideWithFeatureParameters) {
+ FieldTrialList field_trial_list(nullptr);
+ scoped_refptr<FieldTrial> trial =
+ FieldTrialList::CreateFieldTrial("foo", "bar");
+ const char kParam[] = "param_1";
+ const char kValue[] = "value_1";
+ std::map<std::string, std::string> parameters;
+ parameters[kParam] = kValue;
+
+ test::ScopedFeatureList feature_list1;
+ feature_list1.InitFromCommandLine("TestFeature1<foo,TestFeature2",
+ std::string());
+
+ // Check initial state.
+ ExpectFeatures("TestFeature1<foo,TestFeature2", std::string());
+ EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature1));
+ EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature2));
+ EXPECT_EQ(trial.get(), FeatureList::GetFieldTrial(kTestFeature1));
+ EXPECT_EQ(nullptr, FeatureList::GetFieldTrial(kTestFeature2));
+ EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam));
+ EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature2, kParam));
+
+ {
+ // Override feature with existing field trial.
+ test::ScopedFeatureList feature_list2;
+
+ feature_list2.InitAndEnableFeatureWithParameters(kTestFeature1, parameters);
+ EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature1));
+ EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature2));
+ EXPECT_EQ(kValue, GetFieldTrialParamValueByFeature(kTestFeature1, kParam));
+ EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature2, kParam));
+ EXPECT_NE(trial.get(), FeatureList::GetFieldTrial(kTestFeature1));
+ EXPECT_NE(nullptr, FeatureList::GetFieldTrial(kTestFeature1));
+ EXPECT_EQ(nullptr, FeatureList::GetFieldTrial(kTestFeature2));
+ }
+
+ // Check that initial state is restored.
+ ExpectFeatures("TestFeature1<foo,TestFeature2", std::string());
+ EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature1));
+ EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature2));
+ EXPECT_EQ(trial.get(), FeatureList::GetFieldTrial(kTestFeature1));
+ EXPECT_EQ(nullptr, FeatureList::GetFieldTrial(kTestFeature2));
+ EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam));
+ EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature2, kParam));
+
+ {
+ // Override feature with no existing field trial.
+ test::ScopedFeatureList feature_list2;
+
+ feature_list2.InitAndEnableFeatureWithParameters(kTestFeature2, parameters);
+ EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature1));
+ EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature2));
+ EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam));
+ EXPECT_EQ(kValue, GetFieldTrialParamValueByFeature(kTestFeature2, kParam));
+ EXPECT_EQ(trial.get(), FeatureList::GetFieldTrial(kTestFeature1));
+ EXPECT_NE(nullptr, FeatureList::GetFieldTrial(kTestFeature2));
+ }
+
+ // Check that initial state is restored.
+ ExpectFeatures("TestFeature1<foo,TestFeature2", std::string());
+ EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature1));
+ EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature2));
+ EXPECT_EQ(trial.get(), FeatureList::GetFieldTrial(kTestFeature1));
+ EXPECT_EQ(nullptr, FeatureList::GetFieldTrial(kTestFeature2));
+ EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam));
+ EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature2, kParam));
+}
+
+TEST_F(ScopedFeatureListTest, EnableFeatureOverrideDisable) {
+ test::ScopedFeatureList feature_list1;
+ feature_list1.InitWithFeatures({}, {kTestFeature1});
+
+ {
+ test::ScopedFeatureList feature_list2;
+ feature_list2.InitWithFeatures({kTestFeature1}, {});
+ ExpectFeatures("TestFeature1", std::string());
+ }
+}
+
+TEST_F(ScopedFeatureListTest, FeatureOverrideNotMakeDuplicate) {
+ test::ScopedFeatureList feature_list1;
+ feature_list1.InitWithFeatures({}, {kTestFeature1});
+
+ {
+ test::ScopedFeatureList feature_list2;
+ feature_list2.InitWithFeatures({}, {kTestFeature1});
+ ExpectFeatures(std::string(), "TestFeature1");
+ }
+}
+
+TEST_F(ScopedFeatureListTest, FeatureOverrideFeatureWithDefault) {
+ test::ScopedFeatureList feature_list1;
+ feature_list1.InitFromCommandLine("*TestFeature1", std::string());
+
+ {
+ test::ScopedFeatureList feature_list2;
+ feature_list2.InitWithFeatures({kTestFeature1}, {});
+ ExpectFeatures("TestFeature1", std::string());
+ }
+}
+
+TEST_F(ScopedFeatureListTest, FeatureOverrideFeatureWithDefault2) {
+ test::ScopedFeatureList feature_list1;
+ feature_list1.InitFromCommandLine("*TestFeature1", std::string());
+
+ {
+ test::ScopedFeatureList feature_list2;
+ feature_list2.InitWithFeatures({}, {kTestFeature1});
+ ExpectFeatures(std::string(), "TestFeature1");
+ }
+}
+
+TEST_F(ScopedFeatureListTest, FeatureOverrideFeatureWithEnabledFieldTrial) {
+ test::ScopedFeatureList feature_list1;
+
+ std::unique_ptr<FeatureList> feature_list(new FeatureList);
+ FieldTrialList field_trial_list(nullptr);
+ FieldTrial* trial = FieldTrialList::CreateFieldTrial("TrialExample", "A");
+ feature_list->RegisterFieldTrialOverride(
+ kTestFeature1.name, FeatureList::OVERRIDE_ENABLE_FEATURE, trial);
+ feature_list1.InitWithFeatureList(std::move(feature_list));
+
+ {
+ test::ScopedFeatureList feature_list2;
+ feature_list2.InitWithFeatures({kTestFeature1}, {});
+ ExpectFeatures("TestFeature1", std::string());
+ }
+}
+
+TEST_F(ScopedFeatureListTest, FeatureOverrideFeatureWithDisabledFieldTrial) {
+ test::ScopedFeatureList feature_list1;
+
+ std::unique_ptr<FeatureList> feature_list(new FeatureList);
+ FieldTrialList field_trial_list(nullptr);
+ FieldTrial* trial = FieldTrialList::CreateFieldTrial("TrialExample", "A");
+ feature_list->RegisterFieldTrialOverride(
+ kTestFeature1.name, FeatureList::OVERRIDE_DISABLE_FEATURE, trial);
+ feature_list1.InitWithFeatureList(std::move(feature_list));
+
+ {
+ test::ScopedFeatureList feature_list2;
+ feature_list2.InitWithFeatures({kTestFeature1}, {});
+ ExpectFeatures("TestFeature1", std::string());
+ }
+}
+
+TEST_F(ScopedFeatureListTest, FeatureOverrideKeepsOtherExistingFeature) {
+ test::ScopedFeatureList feature_list1;
+ feature_list1.InitWithFeatures({}, {kTestFeature1});
+
+ {
+ test::ScopedFeatureList feature_list2;
+ feature_list2.InitWithFeatures({}, {kTestFeature2});
+ EXPECT_FALSE(FeatureList::IsEnabled(kTestFeature1));
+ EXPECT_FALSE(FeatureList::IsEnabled(kTestFeature2));
+ }
+}
+
+TEST_F(ScopedFeatureListTest, FeatureOverrideKeepsOtherExistingFeature2) {
+ test::ScopedFeatureList feature_list1;
+ feature_list1.InitWithFeatures({}, {kTestFeature1});
+
+ {
+ test::ScopedFeatureList feature_list2;
+ feature_list2.InitWithFeatures({kTestFeature2}, {});
+ ExpectFeatures("TestFeature2", "TestFeature1");
+ }
+}
+
+TEST_F(ScopedFeatureListTest, FeatureOverrideKeepsOtherExistingDefaultFeature) {
+ test::ScopedFeatureList feature_list1;
+ feature_list1.InitFromCommandLine("*TestFeature1", std::string());
+
+ {
+ test::ScopedFeatureList feature_list2;
+ feature_list2.InitWithFeatures({}, {kTestFeature2});
+ ExpectFeatures("*TestFeature1", "TestFeature2");
+ }
+}
+
+TEST_F(ScopedFeatureListTest, ScopedFeatureListIsNoopWhenNotInitialized) {
+ test::ScopedFeatureList feature_list1;
+ feature_list1.InitFromCommandLine("*TestFeature1", std::string());
+
+ // A ScopedFeatureList on which Init() is not called should not reset things
+ // when going out of scope.
+ { test::ScopedFeatureList feature_list2; }
+
+ ExpectFeatures("*TestFeature1", std::string());
+}
+
+} // namespace test
+} // namespace base
diff --git a/base/test/scoped_locale.cc b/base/test/scoped_locale.cc
index 35b3fbe679..c0182842b6 100644
--- a/base/test/scoped_locale.cc
+++ b/base/test/scoped_locale.cc
@@ -11,9 +11,9 @@
namespace base {
ScopedLocale::ScopedLocale(const std::string& locale) {
- prev_locale_ = setlocale(LC_ALL, NULL);
- EXPECT_TRUE(setlocale(LC_ALL, locale.c_str()) != NULL) <<
- "Failed to set locale: " << locale;
+ prev_locale_ = setlocale(LC_ALL, nullptr);
+ EXPECT_TRUE(setlocale(LC_ALL, locale.c_str()) != nullptr)
+ << "Failed to set locale: " << locale;
}
ScopedLocale::~ScopedLocale() {
diff --git a/base/test/scoped_task_environment.cc b/base/test/scoped_task_environment.cc
new file mode 100644
index 0000000000..3d580b0820
--- /dev/null
+++ b/base/test/scoped_task_environment.cc
@@ -0,0 +1,346 @@
+// 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/test/scoped_task_environment.h"
+
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/task_scheduler/task_scheduler.h"
+#include "base/task_scheduler/task_scheduler_impl.h"
+#include "base/test/test_mock_time_task_runner.h"
+#include "base/threading/sequence_local_storage_map.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+
+#if defined(OS_POSIX)
+#include "base/files/file_descriptor_watcher_posix.h"
+#endif
+
+namespace base {
+namespace test {
+
+namespace {
+
+std::unique_ptr<MessageLoop> CreateMessageLoopForMainThreadType(
+ ScopedTaskEnvironment::MainThreadType main_thread_type) {
+ switch (main_thread_type) {
+ case ScopedTaskEnvironment::MainThreadType::DEFAULT:
+ return std::make_unique<MessageLoop>(MessageLoop::TYPE_DEFAULT);
+ case ScopedTaskEnvironment::MainThreadType::MOCK_TIME:
+ return nullptr;
+ case ScopedTaskEnvironment::MainThreadType::UI:
+ return std::make_unique<MessageLoop>(MessageLoop::TYPE_UI);
+ case ScopedTaskEnvironment::MainThreadType::IO:
+ return std::make_unique<MessageLoop>(MessageLoop::TYPE_IO);
+ }
+ NOTREACHED();
+ return nullptr;
+}
+
+} // namespace
+
+class ScopedTaskEnvironment::TestTaskTracker
+ : public internal::TaskSchedulerImpl::TaskTrackerImpl {
+ public:
+ TestTaskTracker();
+
+ // Allow running tasks.
+ void AllowRunTasks();
+
+ // Disallow running tasks. Returns true on success; success requires there to
+ // be no tasks currently running. Returns false if >0 tasks are currently
+ // running. Prior to returning false, it will attempt to block until at least
+ // one task has completed (in an attempt to avoid callers busy-looping
+ // DisallowRunTasks() calls with the same set of slowly ongoing tasks). This
+ // block attempt will also have a short timeout (in an attempt to prevent the
+ // fallout of blocking: if the only task remaining is blocked on the main
+ // thread, waiting for it to complete results in a deadlock...).
+ bool DisallowRunTasks();
+
+ private:
+ friend class ScopedTaskEnvironment;
+
+ // internal::TaskSchedulerImpl::TaskTrackerImpl:
+ void RunOrSkipTask(internal::Task task,
+ internal::Sequence* sequence,
+ bool can_run_task) override;
+
+ // Synchronizes accesses to members below.
+ Lock lock_;
+
+ // True if running tasks is allowed.
+ bool can_run_tasks_ = true;
+
+ // Signaled when |can_run_tasks_| becomes true.
+ ConditionVariable can_run_tasks_cv_;
+
+ // Signaled when a task is completed.
+ ConditionVariable task_completed_;
+
+ // Number of tasks that are currently running.
+ int num_tasks_running_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(TestTaskTracker);
+};
+
+ScopedTaskEnvironment::ScopedTaskEnvironment(
+ MainThreadType main_thread_type,
+ ExecutionMode execution_control_mode)
+ : execution_control_mode_(execution_control_mode),
+ message_loop_(CreateMessageLoopForMainThreadType(main_thread_type)),
+ mock_time_task_runner_(
+ main_thread_type == MainThreadType::MOCK_TIME
+ ? MakeRefCounted<TestMockTimeTaskRunner>(
+ TestMockTimeTaskRunner::Type::kBoundToThread)
+ : nullptr),
+ slsm_for_mock_time_(
+ main_thread_type == MainThreadType::MOCK_TIME
+ ? std::make_unique<internal::SequenceLocalStorageMap>()
+ : nullptr),
+ slsm_registration_for_mock_time_(
+ main_thread_type == MainThreadType::MOCK_TIME
+ ? std::make_unique<
+ internal::ScopedSetSequenceLocalStorageMapForCurrentThread>(
+ slsm_for_mock_time_.get())
+ : nullptr),
+#if defined(OS_POSIX)
+ file_descriptor_watcher_(
+ main_thread_type == MainThreadType::IO
+ ? std::make_unique<FileDescriptorWatcher>(
+ static_cast<MessageLoopForIO*>(message_loop_.get()))
+ : nullptr),
+#endif // defined(OS_POSIX)
+ task_tracker_(new TestTaskTracker()) {
+ CHECK(!TaskScheduler::GetInstance());
+
+ // Instantiate a TaskScheduler with 2 threads in each of its 4 pools. Threads
+ // stay alive even when they don't have work.
+ // Each pool uses two threads to prevent deadlocks in unit tests that have a
+ // sequence that uses WithBaseSyncPrimitives() to wait on the result of
+ // another sequence. This isn't perfect (doesn't solve wait chains) but solves
+ // the basic use case for now.
+ // TODO(fdoray/jeffreyhe): Make the TaskScheduler dynamically replace blocked
+ // threads and get rid of this limitation. http://crbug.com/738104
+ constexpr int kMaxThreads = 2;
+ const TimeDelta kSuggestedReclaimTime = TimeDelta::Max();
+ const SchedulerWorkerPoolParams worker_pool_params(kMaxThreads,
+ kSuggestedReclaimTime);
+ TaskScheduler::SetInstance(std::make_unique<internal::TaskSchedulerImpl>(
+ "ScopedTaskEnvironment", WrapUnique(task_tracker_)));
+ task_scheduler_ = TaskScheduler::GetInstance();
+ TaskScheduler::GetInstance()->Start({worker_pool_params, worker_pool_params,
+ worker_pool_params, worker_pool_params});
+
+ if (execution_control_mode_ == ExecutionMode::QUEUED)
+ CHECK(task_tracker_->DisallowRunTasks());
+}
+
+ScopedTaskEnvironment::~ScopedTaskEnvironment() {
+ // Ideally this would RunLoop().RunUntilIdle() here to catch any errors or
+ // infinite post loop in the remaining work but this isn't possible right now
+ // because base::~MessageLoop() didn't use to do this and adding it here would
+ // make the migration away from MessageLoop that much harder.
+ CHECK_EQ(TaskScheduler::GetInstance(), task_scheduler_);
+ // Without FlushForTesting(), DeleteSoon() and ReleaseSoon() tasks could be
+ // skipped, resulting in memory leaks.
+ task_tracker_->AllowRunTasks();
+ TaskScheduler::GetInstance()->FlushForTesting();
+ TaskScheduler::GetInstance()->Shutdown();
+ TaskScheduler::GetInstance()->JoinForTesting();
+ // Destroying TaskScheduler state can result in waiting on worker threads.
+ // Make sure this is allowed to avoid flaking tests that have disallowed waits
+ // on their main thread.
+ ScopedAllowBaseSyncPrimitivesForTesting allow_waits_to_destroy_task_tracker;
+ TaskScheduler::SetInstance(nullptr);
+}
+
+scoped_refptr<base::SingleThreadTaskRunner>
+ScopedTaskEnvironment::GetMainThreadTaskRunner() {
+ if (message_loop_)
+ return message_loop_->task_runner();
+ DCHECK(mock_time_task_runner_);
+ return mock_time_task_runner_;
+}
+
+bool ScopedTaskEnvironment::MainThreadHasPendingTask() const {
+ if (message_loop_)
+ return !message_loop_->IsIdleForTesting();
+ DCHECK(mock_time_task_runner_);
+ return mock_time_task_runner_->HasPendingTask();
+}
+
+void ScopedTaskEnvironment::RunUntilIdle() {
+ // TODO(gab): This can be heavily simplified to essentially:
+ // bool HasMainThreadTasks() {
+ // if (message_loop_)
+ // return !message_loop_->IsIdleForTesting();
+ // return mock_time_task_runner_->NextPendingTaskDelay().is_zero();
+ // }
+ // while (task_tracker_->HasIncompleteTasks() || HasMainThreadTasks()) {
+ // base::RunLoop().RunUntilIdle();
+ // // Avoid busy-looping.
+ // if (task_tracker_->HasIncompleteTasks())
+ // PlatformThread::Sleep(TimeDelta::FromMilliSeconds(1));
+ // }
+ // Challenge: HasMainThreadTasks() requires support for proper
+ // IncomingTaskQueue::IsIdleForTesting() (check all queues).
+ //
+ // Other than that it works because once |task_tracker_->HasIncompleteTasks()|
+ // is false we know for sure that the only thing that can make it true is a
+ // main thread task (ScopedTaskEnvironment owns all the threads). As such we
+ // can't racily see it as false on the main thread and be wrong as if it the
+ // main thread sees the atomic count at zero, it's the only one that can make
+ // it go up. And the only thing that can make it go up on the main thread are
+ // main thread tasks and therefore we're done if there aren't any left.
+ //
+ // This simplification further allows simplification of DisallowRunTasks().
+ //
+ // This can also be simplified even further once TaskTracker becomes directly
+ // aware of main thread tasks. https://crbug.com/660078.
+
+ for (;;) {
+ task_tracker_->AllowRunTasks();
+
+ // First run as many tasks as possible on the main thread in parallel with
+ // tasks in TaskScheduler. This increases likelihood of TSAN catching
+ // threading errors and eliminates possibility of hangs should a
+ // TaskScheduler task synchronously block on a main thread task
+ // (TaskScheduler::FlushForTesting() can't be used here for that reason).
+ RunLoop().RunUntilIdle();
+
+ // Then halt TaskScheduler. DisallowRunTasks() failing indicates that there
+ // were TaskScheduler tasks currently running. In that case, try again from
+ // top when DisallowRunTasks() yields control back to this thread as they
+ // may have posted main thread tasks.
+ if (!task_tracker_->DisallowRunTasks())
+ continue;
+
+ // Once TaskScheduler is halted. Run any remaining main thread tasks (which
+ // may have been posted by TaskScheduler tasks that completed between the
+ // above main thread RunUntilIdle() and TaskScheduler DisallowRunTasks()).
+ // Note: this assumes that no main thread task synchronously blocks on a
+ // TaskScheduler tasks (it certainly shouldn't); this call could otherwise
+ // hang.
+ RunLoop().RunUntilIdle();
+
+ // The above RunUntilIdle() guarantees there are no remaining main thread
+ // tasks (the TaskScheduler being halted during the last RunUntilIdle() is
+ // key as it prevents a task being posted to it racily with it determining
+ // it had no work remaining). Therefore, we're done if there is no more work
+ // on TaskScheduler either (there can be TaskScheduler work remaining if
+ // DisallowRunTasks() preempted work and/or the last RunUntilIdle() posted
+ // more TaskScheduler tasks).
+ // Note: this last |if| couldn't be turned into a |do {} while();|. A
+ // conditional loop makes it such that |continue;| results in checking the
+ // condition (not unconditionally loop again) which would be incorrect for
+ // the above logic as it'd then be possible for a TaskScheduler task to be
+ // running during the DisallowRunTasks() test, causing it to fail, but then
+ // post to the main thread and complete before the loop's condition is
+ // verified which could result in HasIncompleteUndelayedTasksForTesting()
+ // returning false and the loop erroneously exiting with a pending task on
+ // the main thread.
+ if (!task_tracker_->HasIncompleteUndelayedTasksForTesting())
+ break;
+ }
+
+ // The above loop always ends with running tasks being disallowed. Re-enable
+ // parallel execution before returning unless in ExecutionMode::QUEUED.
+ if (execution_control_mode_ != ExecutionMode::QUEUED)
+ task_tracker_->AllowRunTasks();
+}
+
+void ScopedTaskEnvironment::FastForwardBy(TimeDelta delta) {
+ DCHECK(mock_time_task_runner_);
+ mock_time_task_runner_->FastForwardBy(delta);
+}
+
+void ScopedTaskEnvironment::FastForwardUntilNoTasksRemain() {
+ DCHECK(mock_time_task_runner_);
+ mock_time_task_runner_->FastForwardUntilNoTasksRemain();
+}
+
+const TickClock* ScopedTaskEnvironment::GetMockTickClock() {
+ DCHECK(mock_time_task_runner_);
+ return mock_time_task_runner_->GetMockTickClock();
+}
+
+std::unique_ptr<TickClock> ScopedTaskEnvironment::DeprecatedGetMockTickClock() {
+ DCHECK(mock_time_task_runner_);
+ return mock_time_task_runner_->DeprecatedGetMockTickClock();
+}
+
+size_t ScopedTaskEnvironment::GetPendingMainThreadTaskCount() const {
+ DCHECK(mock_time_task_runner_);
+ return mock_time_task_runner_->GetPendingTaskCount();
+}
+
+TimeDelta ScopedTaskEnvironment::NextMainThreadPendingTaskDelay() const {
+ DCHECK(mock_time_task_runner_);
+ return mock_time_task_runner_->NextPendingTaskDelay();
+}
+
+ScopedTaskEnvironment::TestTaskTracker::TestTaskTracker()
+ : internal::TaskSchedulerImpl::TaskTrackerImpl("ScopedTaskEnvironment"),
+ can_run_tasks_cv_(&lock_),
+ task_completed_(&lock_) {}
+
+void ScopedTaskEnvironment::TestTaskTracker::AllowRunTasks() {
+ AutoLock auto_lock(lock_);
+ can_run_tasks_ = true;
+ can_run_tasks_cv_.Broadcast();
+}
+
+bool ScopedTaskEnvironment::TestTaskTracker::DisallowRunTasks() {
+ AutoLock auto_lock(lock_);
+
+ // Can't disallow run task if there are tasks running.
+ if (num_tasks_running_ > 0) {
+ // Attempt to wait a bit so that the caller doesn't busy-loop with the same
+ // set of pending work. A short wait is required to avoid deadlock
+ // scenarios. See DisallowRunTasks()'s declaration for more details.
+ task_completed_.TimedWait(TimeDelta::FromMilliseconds(1));
+ return false;
+ }
+
+ can_run_tasks_ = false;
+ return true;
+}
+
+void ScopedTaskEnvironment::TestTaskTracker::RunOrSkipTask(
+ internal::Task task,
+ internal::Sequence* sequence,
+ bool can_run_task) {
+ {
+ AutoLock auto_lock(lock_);
+
+ while (!can_run_tasks_)
+ can_run_tasks_cv_.Wait();
+
+ ++num_tasks_running_;
+ }
+
+ internal::TaskSchedulerImpl::TaskTrackerImpl::RunOrSkipTask(
+ std::move(task), sequence, can_run_task);
+
+ {
+ AutoLock auto_lock(lock_);
+
+ CHECK_GT(num_tasks_running_, 0);
+ CHECK(can_run_tasks_);
+
+ --num_tasks_running_;
+
+ task_completed_.Broadcast();
+ }
+}
+
+} // namespace test
+} // namespace base
diff --git a/base/test/scoped_task_environment.h b/base/test/scoped_task_environment.h
new file mode 100644
index 0000000000..f9523b3138
--- /dev/null
+++ b/base/test/scoped_task_environment.h
@@ -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.
+
+#ifndef BASE_TEST_SCOPED_TASK_ENVIRONMENT_H_
+#define BASE_TEST_SCOPED_TASK_ENVIRONMENT_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task_scheduler/lazy_task_runner.h"
+#include "build/build_config.h"
+
+namespace base {
+
+namespace internal {
+class ScopedSetSequenceLocalStorageMapForCurrentThread;
+class SequenceLocalStorageMap;
+} // namespace internal
+
+class FileDescriptorWatcher;
+class MessageLoop;
+class TaskScheduler;
+class TestMockTimeTaskRunner;
+class TickClock;
+
+namespace test {
+
+// ScopedTaskEnvironment allows usage of these APIs within its scope:
+// - (Thread|Sequenced)TaskRunnerHandle, on the thread where it lives
+// - base/task_scheduler/post_task.h, on any thread
+//
+// Tests that need either of these APIs should instantiate a
+// ScopedTaskEnvironment.
+//
+// Tasks posted to the (Thread|Sequenced)TaskRunnerHandle run synchronously when
+// RunLoop::Run(UntilIdle) or ScopedTaskEnvironment::RunUntilIdle is called on
+// the thread where the ScopedTaskEnvironment lives.
+//
+// Tasks posted through base/task_scheduler/post_task.h run on dedicated
+// threads. If ExecutionMode is QUEUED, they run when RunUntilIdle() or
+// ~ScopedTaskEnvironment is called. If ExecutionMode is ASYNC, they run
+// as they are posted.
+//
+// All methods of ScopedTaskEnvironment must be called from the same thread.
+//
+// Usage:
+//
+// class MyTestFixture : public testing::Test {
+// public:
+// (...)
+//
+// protected:
+// // Must be the first member (or at least before any member that cares
+// // about tasks) to be initialized first and destroyed last. protected
+// // instead of private visibility will allow controlling the task
+// // environment (e.g. clock) once such features are added (see design doc
+// // below for details), until then it at least doesn't hurt :).
+// base::test::ScopedTaskEnvironment scoped_task_environment_;
+//
+// // Other members go here (or further below in private section.)
+// };
+//
+// Design and future improvements documented in
+// https://docs.google.com/document/d/1QabRo8c7D9LsYY3cEcaPQbOCLo8Tu-6VLykYXyl3Pkk/edit
+class ScopedTaskEnvironment {
+ public:
+ enum class MainThreadType {
+ // The main thread doesn't pump system messages.
+ DEFAULT,
+ // The main thread doesn't pump system messages and uses a mock clock for
+ // delayed tasks (controllable via FastForward*() methods).
+ // TODO(gab): Make this the default |main_thread_type|.
+ // TODO(gab): Also mock the TaskScheduler's clock simultaneously (this
+ // currently only mocks the main thread's clock).
+ MOCK_TIME,
+ // The main thread pumps UI messages.
+ UI,
+ // The main thread pumps asynchronous IO messages and supports the
+ // FileDescriptorWatcher API on POSIX.
+ IO,
+ };
+
+ enum class ExecutionMode {
+ // Tasks are queued and only executed when RunUntilIdle() is explicitly
+ // called.
+ QUEUED,
+ // Tasks run as they are posted. RunUntilIdle() can still be used to block
+ // until done.
+ ASYNC,
+ };
+
+ ScopedTaskEnvironment(
+ MainThreadType main_thread_type = MainThreadType::DEFAULT,
+ ExecutionMode execution_control_mode = ExecutionMode::ASYNC);
+
+ // Waits until no undelayed TaskScheduler tasks remain. Then, unregisters the
+ // TaskScheduler and the (Thread|Sequenced)TaskRunnerHandle.
+ ~ScopedTaskEnvironment();
+
+ // Returns a TaskRunner that schedules tasks on the main thread.
+ scoped_refptr<base::SingleThreadTaskRunner> GetMainThreadTaskRunner();
+
+ // Returns whether the main thread's TaskRunner has pending tasks.
+ bool MainThreadHasPendingTask() const;
+
+ // Runs tasks until both the (Thread|Sequenced)TaskRunnerHandle and the
+ // TaskScheduler's non-delayed queues are empty.
+ void RunUntilIdle();
+
+ // Only valid for instances with a MOCK_TIME MainThreadType. Fast-forwards
+ // virtual time by |delta|, causing all tasks on the main thread with a
+ // remaining delay less than or equal to |delta| to be executed before this
+ // returns. |delta| must be non-negative.
+ // TODO(gab): Make this apply to TaskScheduler delayed tasks as well
+ // (currently only main thread time is mocked).
+ void FastForwardBy(TimeDelta delta);
+
+ // Only valid for instances with a MOCK_TIME MainThreadType.
+ // Short for FastForwardBy(TimeDelta::Max()).
+ void FastForwardUntilNoTasksRemain();
+
+ // Only valid for instances with a MOCK_TIME MainThreadType. Returns a
+ // TickClock whose time is updated by FastForward(By|UntilNoTasksRemain).
+ const TickClock* GetMockTickClock();
+ std::unique_ptr<TickClock> DeprecatedGetMockTickClock();
+
+ // Only valid for instances with a MOCK_TIME MainThreadType.
+ // Returns the number of pending tasks of the main thread's TaskRunner.
+ size_t GetPendingMainThreadTaskCount() const;
+
+ // Only valid for instances with a MOCK_TIME MainThreadType.
+ // Returns the delay until the next delayed pending task of the main thread's
+ // TaskRunner.
+ TimeDelta NextMainThreadPendingTaskDelay() const;
+
+ private:
+ class TestTaskTracker;
+
+ const ExecutionMode execution_control_mode_;
+
+ // Exactly one of these will be non-null to provide the task environment on
+ // the main thread. Users of this class should NOT rely on the presence of a
+ // MessageLoop beyond (Thread|Sequenced)TaskRunnerHandle and RunLoop as
+ // the backing implementation of each MainThreadType may change over time.
+ const std::unique_ptr<MessageLoop> message_loop_;
+ const scoped_refptr<TestMockTimeTaskRunner> mock_time_task_runner_;
+
+ // Non-null in MOCK_TIME, where an explicit SequenceLocalStorageMap needs to
+ // be provided. TODO(gab): This can be removed once mock time support is added
+ // to MessageLoop directly.
+ const std::unique_ptr<internal::SequenceLocalStorageMap> slsm_for_mock_time_;
+ const std::unique_ptr<
+ internal::ScopedSetSequenceLocalStorageMapForCurrentThread>
+ slsm_registration_for_mock_time_;
+
+#if defined(OS_POSIX)
+ // Enables the FileDescriptorWatcher API iff running a MainThreadType::IO.
+ const std::unique_ptr<FileDescriptorWatcher> file_descriptor_watcher_;
+#endif
+
+ const TaskScheduler* task_scheduler_ = nullptr;
+
+ // Owned by |task_scheduler_|.
+ TestTaskTracker* const task_tracker_;
+
+ // Ensures destruction of lazy TaskRunners when this is destroyed.
+ internal::ScopedLazyTaskRunnerListForTesting
+ scoped_lazy_task_runner_list_for_testing_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedTaskEnvironment);
+};
+
+} // namespace test
+} // namespace base
+
+#endif // BASE_TEST_SCOPED_ASYNC_TASK_SCHEDULER_H_
diff --git a/base/test/scoped_task_environment_unittest.cc b/base/test/scoped_task_environment_unittest.cc
new file mode 100644
index 0000000000..478fa5ee39
--- /dev/null
+++ b/base/test/scoped_task_environment_unittest.cc
@@ -0,0 +1,324 @@
+// 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/test/scoped_task_environment.h"
+
+#include <memory>
+
+#include "base/atomicops.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/synchronization/atomic_flag.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/sequence_local_storage_slot.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/tick_clock.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_POSIX)
+#include <unistd.h>
+#include "base/files/file_descriptor_watcher_posix.h"
+#endif // defined(OS_POSIX)
+
+namespace base {
+namespace test {
+
+namespace {
+
+class ScopedTaskEnvironmentTest
+ : public testing::TestWithParam<ScopedTaskEnvironment::MainThreadType> {};
+
+void VerifyRunUntilIdleDidNotReturnAndSetFlag(
+ AtomicFlag* run_until_idle_returned,
+ AtomicFlag* task_ran) {
+ EXPECT_FALSE(run_until_idle_returned->IsSet());
+ task_ran->Set();
+}
+
+void RunUntilIdleTest(
+ ScopedTaskEnvironment::MainThreadType main_thread_type,
+ ScopedTaskEnvironment::ExecutionMode execution_control_mode) {
+ AtomicFlag run_until_idle_returned;
+ ScopedTaskEnvironment scoped_task_environment(main_thread_type,
+ execution_control_mode);
+
+ AtomicFlag first_main_thread_task_ran;
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, BindOnce(&VerifyRunUntilIdleDidNotReturnAndSetFlag,
+ Unretained(&run_until_idle_returned),
+ Unretained(&first_main_thread_task_ran)));
+
+ AtomicFlag first_task_scheduler_task_ran;
+ PostTask(FROM_HERE, BindOnce(&VerifyRunUntilIdleDidNotReturnAndSetFlag,
+ Unretained(&run_until_idle_returned),
+ Unretained(&first_task_scheduler_task_ran)));
+
+ AtomicFlag second_task_scheduler_task_ran;
+ AtomicFlag second_main_thread_task_ran;
+ PostTaskAndReply(FROM_HERE,
+ BindOnce(&VerifyRunUntilIdleDidNotReturnAndSetFlag,
+ Unretained(&run_until_idle_returned),
+ Unretained(&second_task_scheduler_task_ran)),
+ BindOnce(&VerifyRunUntilIdleDidNotReturnAndSetFlag,
+ Unretained(&run_until_idle_returned),
+ Unretained(&second_main_thread_task_ran)));
+
+ scoped_task_environment.RunUntilIdle();
+ run_until_idle_returned.Set();
+
+ EXPECT_TRUE(first_main_thread_task_ran.IsSet());
+ EXPECT_TRUE(first_task_scheduler_task_ran.IsSet());
+ EXPECT_TRUE(second_task_scheduler_task_ran.IsSet());
+ EXPECT_TRUE(second_main_thread_task_ran.IsSet());
+}
+
+} // namespace
+
+TEST_P(ScopedTaskEnvironmentTest, QueuedRunUntilIdle) {
+ RunUntilIdleTest(GetParam(), ScopedTaskEnvironment::ExecutionMode::QUEUED);
+}
+
+TEST_P(ScopedTaskEnvironmentTest, AsyncRunUntilIdle) {
+ RunUntilIdleTest(GetParam(), ScopedTaskEnvironment::ExecutionMode::ASYNC);
+}
+
+// Verify that tasks posted to an ExecutionMode::QUEUED ScopedTaskEnvironment do
+// not run outside of RunUntilIdle().
+TEST_P(ScopedTaskEnvironmentTest, QueuedTasksDoNotRunOutsideOfRunUntilIdle) {
+ ScopedTaskEnvironment scoped_task_environment(
+ GetParam(), ScopedTaskEnvironment::ExecutionMode::QUEUED);
+
+ AtomicFlag run_until_idle_called;
+ PostTask(FROM_HERE, BindOnce(
+ [](AtomicFlag* run_until_idle_called) {
+ EXPECT_TRUE(run_until_idle_called->IsSet());
+ },
+ Unretained(&run_until_idle_called)));
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ run_until_idle_called.Set();
+ scoped_task_environment.RunUntilIdle();
+
+ AtomicFlag other_run_until_idle_called;
+ PostTask(FROM_HERE, BindOnce(
+ [](AtomicFlag* other_run_until_idle_called) {
+ EXPECT_TRUE(other_run_until_idle_called->IsSet());
+ },
+ Unretained(&other_run_until_idle_called)));
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout());
+ other_run_until_idle_called.Set();
+ scoped_task_environment.RunUntilIdle();
+}
+
+// Verify that a task posted to an ExecutionMode::ASYNC ScopedTaskEnvironment
+// can run without a call to RunUntilIdle().
+TEST_P(ScopedTaskEnvironmentTest, AsyncTasksRunAsTheyArePosted) {
+ ScopedTaskEnvironment scoped_task_environment(
+ GetParam(), ScopedTaskEnvironment::ExecutionMode::ASYNC);
+
+ WaitableEvent task_ran(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ PostTask(FROM_HERE,
+ BindOnce([](WaitableEvent* task_ran) { task_ran->Signal(); },
+ Unretained(&task_ran)));
+ task_ran.Wait();
+}
+
+// Verify that a task posted to an ExecutionMode::ASYNC ScopedTaskEnvironment
+// after a call to RunUntilIdle() can run without another call to
+// RunUntilIdle().
+TEST_P(ScopedTaskEnvironmentTest,
+ AsyncTasksRunAsTheyArePostedAfterRunUntilIdle) {
+ ScopedTaskEnvironment scoped_task_environment(
+ GetParam(), ScopedTaskEnvironment::ExecutionMode::ASYNC);
+
+ scoped_task_environment.RunUntilIdle();
+
+ WaitableEvent task_ran(WaitableEvent::ResetPolicy::MANUAL,
+ WaitableEvent::InitialState::NOT_SIGNALED);
+ PostTask(FROM_HERE,
+ BindOnce([](WaitableEvent* task_ran) { task_ran->Signal(); },
+ Unretained(&task_ran)));
+ task_ran.Wait();
+}
+
+TEST_P(ScopedTaskEnvironmentTest, DelayedTasks) {
+ // Use a QUEUED execution-mode environment, so that no tasks are actually
+ // executed until RunUntilIdle()/FastForwardBy() are invoked.
+ ScopedTaskEnvironment scoped_task_environment(
+ GetParam(), ScopedTaskEnvironment::ExecutionMode::QUEUED);
+
+ subtle::Atomic32 counter = 0;
+
+ constexpr base::TimeDelta kShortTaskDelay = TimeDelta::FromDays(1);
+ // Should run only in MOCK_TIME environment when time is fast-forwarded.
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ Bind(
+ [](subtle::Atomic32* counter) {
+ subtle::NoBarrier_AtomicIncrement(counter, 4);
+ },
+ Unretained(&counter)),
+ kShortTaskDelay);
+ // TODO(gab): This currently doesn't run because the TaskScheduler's clock
+ // isn't mocked but it should be.
+ PostDelayedTask(FROM_HERE,
+ Bind(
+ [](subtle::Atomic32* counter) {
+ subtle::NoBarrier_AtomicIncrement(counter, 128);
+ },
+ Unretained(&counter)),
+ kShortTaskDelay);
+
+ constexpr base::TimeDelta kLongTaskDelay = TimeDelta::FromDays(7);
+ // Same as first task, longer delays to exercise
+ // FastForwardUntilNoTasksRemain().
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ Bind(
+ [](subtle::Atomic32* counter) {
+ subtle::NoBarrier_AtomicIncrement(counter, 8);
+ },
+ Unretained(&counter)),
+ TimeDelta::FromDays(5));
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ Bind(
+ [](subtle::Atomic32* counter) {
+ subtle::NoBarrier_AtomicIncrement(counter, 16);
+ },
+ Unretained(&counter)),
+ kLongTaskDelay);
+
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, Bind(
+ [](subtle::Atomic32* counter) {
+ subtle::NoBarrier_AtomicIncrement(counter, 1);
+ },
+ Unretained(&counter)));
+ PostTask(FROM_HERE, Bind(
+ [](subtle::Atomic32* counter) {
+ subtle::NoBarrier_AtomicIncrement(counter, 2);
+ },
+ Unretained(&counter)));
+
+ // This expectation will fail flakily if the preceding PostTask() is executed
+ // asynchronously, indicating a problem with the QUEUED execution mode.
+ int expected_value = 0;
+ EXPECT_EQ(expected_value, counter);
+
+ // RunUntilIdle() should process non-delayed tasks only in all queues.
+ scoped_task_environment.RunUntilIdle();
+ expected_value += 1;
+ expected_value += 2;
+ EXPECT_EQ(expected_value, counter);
+
+ if (GetParam() == ScopedTaskEnvironment::MainThreadType::MOCK_TIME) {
+ // Delay inferior to the delay of the first posted task.
+ constexpr base::TimeDelta kInferiorTaskDelay = TimeDelta::FromSeconds(1);
+ static_assert(kInferiorTaskDelay < kShortTaskDelay,
+ "|kInferiorTaskDelay| should be "
+ "set to a value inferior to the first posted task's delay.");
+ scoped_task_environment.FastForwardBy(kInferiorTaskDelay);
+ EXPECT_EQ(expected_value, counter);
+
+ scoped_task_environment.FastForwardBy(kShortTaskDelay - kInferiorTaskDelay);
+ expected_value += 4;
+ EXPECT_EQ(expected_value, counter);
+
+ scoped_task_environment.FastForwardUntilNoTasksRemain();
+ expected_value += 8;
+ expected_value += 16;
+ EXPECT_EQ(expected_value, counter);
+ }
+}
+
+// Regression test for https://crbug.com/824770.
+TEST_P(ScopedTaskEnvironmentTest, SupportsSequenceLocalStorageOnMainThread) {
+ ScopedTaskEnvironment scoped_task_environment(
+ GetParam(), ScopedTaskEnvironment::ExecutionMode::ASYNC);
+
+ SequenceLocalStorageSlot<int> sls_slot;
+ sls_slot.Set(5);
+ EXPECT_EQ(5, sls_slot.Get());
+}
+
+#if defined(OS_POSIX)
+TEST_F(ScopedTaskEnvironmentTest, SupportsFileDescriptorWatcherOnIOMainThread) {
+ ScopedTaskEnvironment scoped_task_environment(
+ ScopedTaskEnvironment::MainThreadType::IO,
+ ScopedTaskEnvironment::ExecutionMode::ASYNC);
+
+ int pipe_fds_[2];
+ ASSERT_EQ(0, pipe(pipe_fds_));
+
+ RunLoop run_loop;
+
+ // The write end of a newly created pipe is immediately writable.
+ auto controller = FileDescriptorWatcher::WatchWritable(
+ pipe_fds_[1], run_loop.QuitClosure());
+
+ // This will hang if the notification doesn't occur as expected.
+ run_loop.Run();
+}
+#endif // defined(OS_POSIX)
+
+// Verify that the TickClock returned by
+// |ScopedTaskEnvironment::GetMockTickClock| gets updated when the
+// FastForward(By|UntilNoTasksRemain) functions are called.
+TEST_F(ScopedTaskEnvironmentTest, FastForwardAdvanceTickClock) {
+ // Use a QUEUED execution-mode environment, so that no tasks are actually
+ // executed until RunUntilIdle()/FastForwardBy() are invoked.
+ ScopedTaskEnvironment scoped_task_environment(
+ ScopedTaskEnvironment::MainThreadType::MOCK_TIME,
+ ScopedTaskEnvironment::ExecutionMode::QUEUED);
+
+ constexpr base::TimeDelta kShortTaskDelay = TimeDelta::FromDays(1);
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, base::DoNothing(),
+ kShortTaskDelay);
+
+ constexpr base::TimeDelta kLongTaskDelay = TimeDelta::FromDays(7);
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(FROM_HERE, base::DoNothing(),
+ kLongTaskDelay);
+
+ const base::TickClock* tick_clock =
+ scoped_task_environment.GetMockTickClock();
+ base::TimeTicks tick_clock_ref = tick_clock->NowTicks();
+
+ // Make sure that |FastForwardBy| advances the clock.
+ scoped_task_environment.FastForwardBy(kShortTaskDelay);
+ EXPECT_EQ(kShortTaskDelay, tick_clock->NowTicks() - tick_clock_ref);
+
+ // Make sure that |FastForwardUntilNoTasksRemain| advances the clock.
+ scoped_task_environment.FastForwardUntilNoTasksRemain();
+ EXPECT_EQ(kLongTaskDelay, tick_clock->NowTicks() - tick_clock_ref);
+
+ // Fast-forwarding to a time at which there's no tasks should also advance the
+ // clock.
+ scoped_task_environment.FastForwardBy(kLongTaskDelay);
+ EXPECT_EQ(kLongTaskDelay * 2, tick_clock->NowTicks() - tick_clock_ref);
+}
+
+INSTANTIATE_TEST_CASE_P(
+ MainThreadDefault,
+ ScopedTaskEnvironmentTest,
+ ::testing::Values(ScopedTaskEnvironment::MainThreadType::DEFAULT));
+INSTANTIATE_TEST_CASE_P(
+ MainThreadMockTime,
+ ScopedTaskEnvironmentTest,
+ ::testing::Values(ScopedTaskEnvironment::MainThreadType::MOCK_TIME));
+INSTANTIATE_TEST_CASE_P(
+ MainThreadUI,
+ ScopedTaskEnvironmentTest,
+ ::testing::Values(ScopedTaskEnvironment::MainThreadType::UI));
+INSTANTIATE_TEST_CASE_P(
+ MainThreadIO,
+ ScopedTaskEnvironmentTest,
+ ::testing::Values(ScopedTaskEnvironment::MainThreadType::IO));
+
+} // namespace test
+} // namespace base
diff --git a/base/test/sequenced_worker_pool_owner.cc b/base/test/sequenced_worker_pool_owner.cc
deleted file mode 100644
index 324d071352..0000000000
--- a/base/test/sequenced_worker_pool_owner.cc
+++ /dev/null
@@ -1,65 +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/sequenced_worker_pool_owner.h"
-
-#include "base/location.h"
-#include "base/message_loop/message_loop.h"
-#include "base/single_thread_task_runner.h"
-
-namespace base {
-
-SequencedWorkerPoolOwner::SequencedWorkerPoolOwner(
- size_t max_threads,
- const std::string& thread_name_prefix)
- : constructor_message_loop_(MessageLoop::current()),
- pool_(new SequencedWorkerPool(max_threads,
- thread_name_prefix,
- TaskPriority::USER_VISIBLE,
- this)),
- has_work_call_count_(0) {}
-
-SequencedWorkerPoolOwner::~SequencedWorkerPoolOwner() {
- pool_->Shutdown();
- pool_ = NULL;
-
- // Spin the current message loop until SWP destruction verified in OnDestruct.
- exit_loop_.Run();
-}
-
-const scoped_refptr<SequencedWorkerPool>& SequencedWorkerPoolOwner::pool()
- const {
- return pool_;
-}
-
-void SequencedWorkerPoolOwner::SetWillWaitForShutdownCallback(
- const Closure& callback) {
- will_wait_for_shutdown_callback_ = callback;
-}
-
-int SequencedWorkerPoolOwner::has_work_call_count() const {
- AutoLock lock(has_work_lock_);
- return has_work_call_count_;
-}
-
-void SequencedWorkerPoolOwner::OnHasWork() {
- AutoLock lock(has_work_lock_);
- ++has_work_call_count_;
-}
-
-void SequencedWorkerPoolOwner::WillWaitForShutdown() {
- if (!will_wait_for_shutdown_callback_.is_null()) {
- will_wait_for_shutdown_callback_.Run();
-
- // Release the reference to the callback to prevent retain cycles.
- will_wait_for_shutdown_callback_ = Closure();
- }
-}
-
-void SequencedWorkerPoolOwner::OnDestruct() {
- constructor_message_loop_->task_runner()->PostTask(FROM_HERE,
- exit_loop_.QuitClosure());
-}
-
-} // namespace base
diff --git a/base/test/sequenced_worker_pool_owner.h b/base/test/sequenced_worker_pool_owner.h
deleted file mode 100644
index 28a6cf070a..0000000000
--- a/base/test/sequenced_worker_pool_owner.h
+++ /dev/null
@@ -1,71 +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_TEST_SEQUENCED_WORKER_POOL_OWNER_H_
-#define BASE_TEST_SEQUENCED_WORKER_POOL_OWNER_H_
-
-#include <stddef.h>
-
-#include <cstddef>
-#include <string>
-
-#include "base/callback.h"
-#include "base/compiler_specific.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/run_loop.h"
-#include "base/synchronization/lock.h"
-#include "base/threading/sequenced_worker_pool.h"
-
-namespace base {
-
-class MessageLoop;
-
-// Wrapper around SequencedWorkerPool for testing that blocks destruction
-// until the pool is actually destroyed. This is so that a
-// SequencedWorkerPool from one test doesn't outlive its test and cause
-// strange races with other tests that touch global stuff (like histograms and
-// logging). However, this requires that nothing else on this thread holds a
-// ref to the pool when the SequencedWorkerPoolOwner is destroyed.
-//
-// This class calls Shutdown on the owned SequencedWorkerPool in the destructor.
-// Tests may themselves call Shutdown earlier to test shutdown behavior.
-class SequencedWorkerPoolOwner : public SequencedWorkerPool::TestingObserver {
- public:
- SequencedWorkerPoolOwner(size_t max_threads,
- const std::string& thread_name_prefix);
-
- ~SequencedWorkerPoolOwner() override;
-
- // Don't change the returned pool's testing observer.
- const scoped_refptr<SequencedWorkerPool>& pool() const;
-
- // The given callback will be called on WillWaitForShutdown().
- void SetWillWaitForShutdownCallback(const Closure& callback);
-
- int has_work_call_count() const;
-
- private:
- // SequencedWorkerPool::TestingObserver implementation.
- void OnHasWork() override;
- void WillWaitForShutdown() override;
- void OnDestruct() override;
-
- // Used to run the current thread's message loop until the
- // SequencedWorkerPool's destruction has been verified.
- base::RunLoop exit_loop_;
- MessageLoop* const constructor_message_loop_;
-
- scoped_refptr<SequencedWorkerPool> pool_;
- Closure will_wait_for_shutdown_callback_;
-
- mutable Lock has_work_lock_;
- int has_work_call_count_;
-
- DISALLOW_COPY_AND_ASSIGN(SequencedWorkerPoolOwner);
-};
-
-} // namespace base
-
-#endif // BASE_TEST_SEQUENCED_WORKER_POOL_OWNER_H_
diff --git a/base/test/simple_test_clock.cc b/base/test/simple_test_clock.cc
index a2bdc2ac3d..7486d79358 100644
--- a/base/test/simple_test_clock.cc
+++ b/base/test/simple_test_clock.cc
@@ -6,11 +6,11 @@
namespace base {
-SimpleTestClock::SimpleTestClock() {}
+SimpleTestClock::SimpleTestClock() = default;
-SimpleTestClock::~SimpleTestClock() {}
+SimpleTestClock::~SimpleTestClock() = default;
-Time SimpleTestClock::Now() {
+Time SimpleTestClock::Now() const {
AutoLock lock(lock_);
return now_;
}
diff --git a/base/test/simple_test_clock.h b/base/test/simple_test_clock.h
index a70f99c1ca..0cbcf08263 100644
--- a/base/test/simple_test_clock.h
+++ b/base/test/simple_test_clock.h
@@ -21,7 +21,7 @@ class SimpleTestClock : public Clock {
SimpleTestClock();
~SimpleTestClock() override;
- Time Now() override;
+ Time Now() const override;
// Advances the clock by |delta|.
void Advance(TimeDelta delta);
@@ -31,7 +31,7 @@ class SimpleTestClock : public Clock {
private:
// Protects |now_|.
- Lock lock_;
+ mutable Lock lock_;
Time now_;
};
diff --git a/base/test/simple_test_tick_clock.cc b/base/test/simple_test_tick_clock.cc
index c6375bd6be..7ee3401ddb 100644
--- a/base/test/simple_test_tick_clock.cc
+++ b/base/test/simple_test_tick_clock.cc
@@ -8,11 +8,11 @@
namespace base {
-SimpleTestTickClock::SimpleTestTickClock() {}
+SimpleTestTickClock::SimpleTestTickClock() = default;
-SimpleTestTickClock::~SimpleTestTickClock() {}
+SimpleTestTickClock::~SimpleTestTickClock() = default;
-TimeTicks SimpleTestTickClock::NowTicks() {
+TimeTicks SimpleTestTickClock::NowTicks() const {
AutoLock lock(lock_);
return now_ticks_;
}
diff --git a/base/test/simple_test_tick_clock.h b/base/test/simple_test_tick_clock.h
index f2f7581e31..923eba4a9a 100644
--- a/base/test/simple_test_tick_clock.h
+++ b/base/test/simple_test_tick_clock.h
@@ -21,7 +21,7 @@ class SimpleTestTickClock : public TickClock {
SimpleTestTickClock();
~SimpleTestTickClock() override;
- TimeTicks NowTicks() override;
+ TimeTicks NowTicks() const override;
// Advances the clock by |delta|, which must not be negative.
void Advance(TimeDelta delta);
@@ -31,7 +31,7 @@ class SimpleTestTickClock : public TickClock {
private:
// Protects |now_ticks_|.
- Lock lock_;
+ mutable Lock lock_;
TimeTicks now_ticks_;
};
diff --git a/base/test/test_child_process.cc b/base/test/test_child_process.cc
new file mode 100644
index 0000000000..ce15856116
--- /dev/null
+++ b/base/test/test_child_process.cc
@@ -0,0 +1,43 @@
+// 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.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// Simple testing command, used to exercise child process launcher calls.
+//
+// Usage:
+// echo_test_helper [-x exit_code] arg0 arg1 arg2...
+// Prints arg0..n to stdout with space delimiters between args,
+// returning "exit_code" if -x is specified.
+//
+// echo_test_helper -e env_var
+// Prints the environmental variable |env_var| to stdout.
+int main(int argc, char** argv) {
+ if (strcmp(argv[1], "-e") == 0) {
+ if (argc != 3) {
+ return 1;
+ }
+
+ const char* env = getenv(argv[2]);
+ if (env != NULL) {
+ printf("%s", env);
+ }
+ } else {
+ int return_code = 0;
+ int start_idx = 1;
+
+ if (strcmp(argv[1], "-x") == 0) {
+ return_code = atoi(argv[2]);
+ start_idx = 3;
+ }
+
+ for (int i = start_idx; i < argc; ++i) {
+ printf((i < argc - 1 ? "%s " : "%s"), argv[i]);
+ }
+
+ return return_code;
+ }
+}
diff --git a/base/test/test_file_util.cc b/base/test/test_file_util.cc
index 40b25f01aa..8dafc58a7d 100644
--- a/base/test/test_file_util.cc
+++ b/base/test/test_file_util.cc
@@ -20,9 +20,4 @@ bool EvictFileFromSystemCacheWithRetry(const FilePath& path) {
return false;
}
-// Declared in base/files/file_path.h.
-void PrintTo(const FilePath& path, std::ostream* out) {
- *out << path.value();
-}
-
} // namespace base
diff --git a/base/test/test_file_util_linux.cc b/base/test/test_file_util_linux.cc
index 0ef5c0a157..cf8b056272 100644
--- a/base/test/test_file_util_linux.cc
+++ b/base/test/test_file_util_linux.cc
@@ -9,11 +9,42 @@
#include <sys/types.h>
#include <unistd.h>
+#if defined(OS_ANDROID)
+#include <asm/unistd.h>
+#include <errno.h>
+#include <linux/fadvise.h>
+#include <sys/syscall.h>
+#endif
+
#include "base/files/file_path.h"
#include "base/files/scoped_file.h"
namespace base {
+// Inconveniently, the NDK doesn't provide for posix_fadvise
+// until native API level = 21, which we don't use yet, so provide a wrapper, at
+// least on ARM32
+#if defined(OS_ANDROID) && __ANDROID_API__ < 21
+
+namespace {
+int posix_fadvise(int fd, off_t offset, off_t len, int advice) {
+#if defined(ARCH_CPU_ARMEL)
+ // Note that the syscall argument order on ARM is different from the C
+ // function; this is helpfully documented in the Linux posix_fadvise manpage.
+ return syscall(__NR_arm_fadvise64_64, fd, advice,
+ 0, // Upper 32-bits for offset
+ offset,
+ 0, // Upper 32-bits for length
+ len);
+#endif
+ NOTIMPLEMENTED();
+ return ENOSYS;
+}
+
+} // namespace
+
+#endif // OS_ANDROID
+
bool EvictFileFromSystemCache(const FilePath& file) {
ScopedFD fd(open(file.value().c_str(), O_RDONLY));
if (!fd.is_valid())
diff --git a/base/test/test_file_util_posix.cc b/base/test/test_file_util_posix.cc
index b81728318c..87290fb3a2 100644
--- a/base/test/test_file_util_posix.cc
+++ b/base/test/test_file_util_posix.cc
@@ -43,7 +43,7 @@ void* GetPermissionInfo(const FilePath& path, size_t* length) {
struct stat stat_buf;
if (stat(path.value().c_str(), &stat_buf) != 0)
- return NULL;
+ return nullptr;
*length = sizeof(mode_t);
mode_t* mode = new mode_t;
@@ -79,7 +79,7 @@ bool DieFileDie(const FilePath& file, bool recurse) {
return DeleteFile(file, recurse);
}
-#if !defined(OS_LINUX) && !defined(OS_MACOSX)
+#if !defined(OS_LINUX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
bool EvictFileFromSystemCache(const FilePath& file) {
// There doesn't seem to be a POSIX way to cool the disk cache.
NOTIMPLEMENTED();
@@ -96,9 +96,9 @@ bool MakeFileUnwritable(const FilePath& path) {
}
FilePermissionRestorer::FilePermissionRestorer(const FilePath& path)
- : path_(path), info_(NULL), length_(0) {
+ : path_(path), info_(nullptr), length_(0) {
info_ = GetPermissionInfo(path_, &length_);
- DCHECK(info_ != NULL);
+ DCHECK(info_ != nullptr);
DCHECK_NE(0u, length_);
}
diff --git a/base/test/test_io_thread.cc b/base/test/test_io_thread.cc
index ce4a8d10de..1b206589e9 100644
--- a/base/test/test_io_thread.cc
+++ b/base/test/test_io_thread.cc
@@ -5,6 +5,7 @@
#include "base/test/test_io_thread.h"
#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
namespace base {
@@ -37,9 +38,8 @@ void TestIOThread::Stop() {
io_thread_started_ = false;
}
-void TestIOThread::PostTask(const tracked_objects::Location& from_here,
- const base::Closure& task) {
- task_runner()->PostTask(from_here, task);
+void TestIOThread::PostTask(const Location& from_here, base::OnceClosure task) {
+ task_runner()->PostTask(from_here, std::move(task));
}
} // namespace base
diff --git a/base/test/test_io_thread.h b/base/test/test_io_thread.h
index 5d3885e81c..a55a06340c 100644
--- a/base/test/test_io_thread.h
+++ b/base/test/test_io_thread.h
@@ -38,8 +38,7 @@ class TestIOThread {
void Stop();
// Post |task| to the IO thread.
- void PostTask(const tracked_objects::Location& from_here,
- const base::Closure& task);
+ void PostTask(const Location& from_here, base::OnceClosure task);
base::MessageLoopForIO* message_loop() {
return static_cast<base::MessageLoopForIO*>(io_thread_.message_loop());
diff --git a/base/test/test_mock_time_task_runner.cc b/base/test/test_mock_time_task_runner.cc
index a236acffa1..f96a6b7781 100644
--- a/base/test/test_mock_time_task_runner.cc
+++ b/base/test/test_mock_time_task_runner.cc
@@ -6,71 +6,124 @@
#include <utility>
+#include "base/containers/circular_deque.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/threading/thread_task_runner_handle.h"
-#include "base/time/clock.h"
-#include "base/time/tick_clock.h"
namespace base {
-
namespace {
-// MockTickClock --------------------------------------------------------------
-
-// TickClock that always returns the then-current mock time ticks of
-// |task_runner| as the current time ticks.
-class MockTickClock : public TickClock {
+// LegacyMockTickClock and LegacyMockClock are used by deprecated APIs of
+// TestMockTimeTaskRunner. They will be removed after updating callers of
+// GetMockClock() and GetMockTickClock() to GetMockClockPtr() and
+// GetMockTickClockPtr().
+class LegacyMockTickClock : public TickClock {
public:
- explicit MockTickClock(
- scoped_refptr<const TestMockTimeTaskRunner> task_runner);
+ explicit LegacyMockTickClock(
+ scoped_refptr<const TestMockTimeTaskRunner> task_runner)
+ : task_runner_(std::move(task_runner)) {}
// TickClock:
- TimeTicks NowTicks() override;
+ TimeTicks NowTicks() const override { return task_runner_->NowTicks(); }
private:
scoped_refptr<const TestMockTimeTaskRunner> task_runner_;
- DISALLOW_COPY_AND_ASSIGN(MockTickClock);
+ DISALLOW_COPY_AND_ASSIGN(LegacyMockTickClock);
};
-MockTickClock::MockTickClock(
- scoped_refptr<const TestMockTimeTaskRunner> task_runner)
- : task_runner_(task_runner) {
-}
-
-TimeTicks MockTickClock::NowTicks() {
- return task_runner_->NowTicks();
-}
-
-// MockClock ------------------------------------------------------------------
-
-// Clock that always returns the then-current mock time of |task_runner| as the
-// current time.
-class MockClock : public Clock {
+class LegacyMockClock : public Clock {
public:
- explicit MockClock(scoped_refptr<const TestMockTimeTaskRunner> task_runner);
+ explicit LegacyMockClock(
+ scoped_refptr<const TestMockTimeTaskRunner> task_runner)
+ : task_runner_(std::move(task_runner)) {}
// Clock:
- Time Now() override;
+ Time Now() const override { return task_runner_->Now(); }
private:
scoped_refptr<const TestMockTimeTaskRunner> task_runner_;
- DISALLOW_COPY_AND_ASSIGN(MockClock);
+ DISALLOW_COPY_AND_ASSIGN(LegacyMockClock);
};
-MockClock::MockClock(scoped_refptr<const TestMockTimeTaskRunner> task_runner)
- : task_runner_(task_runner) {
-}
+} // namespace
-Time MockClock::Now() {
- return task_runner_->Now();
-}
+// A SingleThreadTaskRunner which forwards everything to its |target_|. This
+// serves two purposes:
+// 1) If a ThreadTaskRunnerHandle owned by TestMockTimeTaskRunner were to be
+// set to point to that TestMockTimeTaskRunner, a reference cycle would
+// result. As |target_| here is a non-refcounting raw pointer, the cycle is
+// broken.
+// 2) Since SingleThreadTaskRunner is ref-counted, it's quite easy for it to
+// accidentally get captured between tests in a singleton somewhere.
+// Indirecting via NonOwningProxyTaskRunner permits TestMockTimeTaskRunner
+// to be cleaned up (removing the RunLoop::Delegate in the kBoundToThread
+// mode), and to also cleanly flag any actual attempts to use the leaked
+// task runner.
+class TestMockTimeTaskRunner::NonOwningProxyTaskRunner
+ : public SingleThreadTaskRunner {
+ public:
+ explicit NonOwningProxyTaskRunner(SingleThreadTaskRunner* target)
+ : target_(target) {
+ DCHECK(target_);
+ }
-} // namespace
+ // Detaches this NonOwningProxyTaskRunner instance from its |target_|. It is
+ // invalid to post tasks after this point but RunsTasksInCurrentSequence()
+ // will still pass on the original thread for convenience with legacy code.
+ void Detach() {
+ AutoLock scoped_lock(lock_);
+ target_ = nullptr;
+ }
+
+ // SingleThreadTaskRunner:
+ bool RunsTasksInCurrentSequence() const override {
+ AutoLock scoped_lock(lock_);
+ if (target_)
+ return target_->RunsTasksInCurrentSequence();
+ return thread_checker_.CalledOnValidThread();
+ }
+
+ bool PostDelayedTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay) override {
+ AutoLock scoped_lock(lock_);
+ if (target_)
+ return target_->PostDelayedTask(from_here, std::move(task), delay);
+
+ // The associated TestMockTimeTaskRunner is dead, so fail this PostTask.
+ return false;
+ }
+
+ bool PostNonNestableDelayedTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay) override {
+ AutoLock scoped_lock(lock_);
+ if (target_) {
+ return target_->PostNonNestableDelayedTask(from_here, std::move(task),
+ delay);
+ }
+
+ // The associated TestMockTimeTaskRunner is dead, so fail this PostTask.
+ return false;
+ }
+
+ private:
+ friend class RefCountedThreadSafe<NonOwningProxyTaskRunner>;
+ ~NonOwningProxyTaskRunner() override = default;
+
+ mutable Lock lock_;
+ SingleThreadTaskRunner* target_; // guarded by lock_
+
+ // Used to implement RunsTasksInCurrentSequence, without relying on |target_|.
+ ThreadCheckerImpl thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(NonOwningProxyTaskRunner);
+};
// TestMockTimeTaskRunner::TestOrderedPendingTask -----------------------------
@@ -80,7 +133,7 @@ Time MockClock::Now() {
struct TestMockTimeTaskRunner::TestOrderedPendingTask
: public base::TestPendingTask {
TestOrderedPendingTask();
- TestOrderedPendingTask(const tracked_objects::Location& location,
+ TestOrderedPendingTask(const Location& location,
OnceClosure task,
TimeTicks post_time,
TimeDelta delay,
@@ -105,7 +158,7 @@ TestMockTimeTaskRunner::TestOrderedPendingTask::TestOrderedPendingTask(
TestOrderedPendingTask&&) = default;
TestMockTimeTaskRunner::TestOrderedPendingTask::TestOrderedPendingTask(
- const tracked_objects::Location& location,
+ const Location& location,
OnceClosure task,
TimeTicks post_time,
TimeDelta delay,
@@ -118,8 +171,8 @@ TestMockTimeTaskRunner::TestOrderedPendingTask::TestOrderedPendingTask(
nestability),
ordinal(ordinal) {}
-TestMockTimeTaskRunner::TestOrderedPendingTask::~TestOrderedPendingTask() {
-}
+TestMockTimeTaskRunner::TestOrderedPendingTask::~TestOrderedPendingTask() =
+ default;
TestMockTimeTaskRunner::TestOrderedPendingTask&
TestMockTimeTaskRunner::TestOrderedPendingTask::operator=(
@@ -128,7 +181,7 @@ TestMockTimeTaskRunner::TestOrderedPendingTask::operator=(
// TestMockTimeTaskRunner -----------------------------------------------------
// TODO(gab): This should also set the SequenceToken for the current thread.
-// Ref. TestMockTimeTaskRunner::RunsTasksOnCurrentThread().
+// Ref. TestMockTimeTaskRunner::RunsTasksInCurrentSequence().
TestMockTimeTaskRunner::ScopedContext::ScopedContext(
scoped_refptr<TestMockTimeTaskRunner> scope)
: on_destroy_(ThreadTaskRunnerHandle::OverrideForTesting(scope)) {
@@ -145,26 +198,41 @@ bool TestMockTimeTaskRunner::TemporalOrder::operator()(
return first_task.GetTimeToRun() > second_task.GetTimeToRun();
}
-TestMockTimeTaskRunner::TestMockTimeTaskRunner()
- : now_(Time::UnixEpoch()), next_task_ordinal_(0) {
-}
+TestMockTimeTaskRunner::TestMockTimeTaskRunner(Type type)
+ : TestMockTimeTaskRunner(Time::UnixEpoch(), TimeTicks(), type) {}
TestMockTimeTaskRunner::TestMockTimeTaskRunner(Time start_time,
- TimeTicks start_ticks)
- : now_(Time::UnixEpoch()), now_ticks_(start_ticks), next_task_ordinal_(0) {}
+ TimeTicks start_ticks,
+ Type type)
+ : now_(start_time),
+ now_ticks_(start_ticks),
+ tasks_lock_cv_(&tasks_lock_),
+ proxy_task_runner_(MakeRefCounted<NonOwningProxyTaskRunner>(this)),
+ mock_clock_(this) {
+ if (type == Type::kBoundToThread) {
+ RunLoop::RegisterDelegateForCurrentThread(this);
+ thread_task_runner_handle_ =
+ std::make_unique<ThreadTaskRunnerHandle>(proxy_task_runner_);
+ }
+}
TestMockTimeTaskRunner::~TestMockTimeTaskRunner() {
+ proxy_task_runner_->Detach();
}
void TestMockTimeTaskRunner::FastForwardBy(TimeDelta delta) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_GE(delta, TimeDelta());
- const TimeTicks original_now_ticks = now_ticks_;
+ const TimeTicks original_now_ticks = NowTicks();
ProcessAllTasksNoLaterThan(delta);
ForwardClocksUntilTickTime(original_now_ticks + delta);
}
+void TestMockTimeTaskRunner::AdvanceMockTickClock(TimeDelta delta) {
+ ForwardClocksUntilTickTime(NowTicks() + delta);
+}
+
void TestMockTimeTaskRunner::RunUntilIdle() {
DCHECK(thread_checker_.CalledOnValidThread());
ProcessAllTasksNoLaterThan(TimeDelta());
@@ -176,57 +244,102 @@ void TestMockTimeTaskRunner::FastForwardUntilNoTasksRemain() {
}
void TestMockTimeTaskRunner::ClearPendingTasks() {
- DCHECK(thread_checker_.CalledOnValidThread());
AutoLock scoped_lock(tasks_lock_);
- while (!tasks_.empty())
- tasks_.pop();
+ // This is repeated in case task destruction triggers further tasks.
+ while (!tasks_.empty()) {
+ TaskPriorityQueue cleanup_tasks;
+ tasks_.swap(cleanup_tasks);
+
+ // Destroy task objects with |tasks_lock_| released. Task deletion can cause
+ // calls to NonOwningProxyTaskRunner::RunsTasksInCurrentSequence()
+ // (e.g. for DCHECKs), which causes |NonOwningProxyTaskRunner::lock_| to be
+ // grabbed.
+ //
+ // On the other hand, calls from NonOwningProxyTaskRunner::PostTask ->
+ // TestMockTimeTaskRunner::PostTask acquire locks as
+ // |NonOwningProxyTaskRunner::lock_| followed by |tasks_lock_|, so it's
+ // desirable to avoid the reverse order, for deadlock freedom.
+ AutoUnlock scoped_unlock(tasks_lock_);
+ while (!cleanup_tasks.empty())
+ cleanup_tasks.pop();
+ }
}
Time TestMockTimeTaskRunner::Now() const {
- DCHECK(thread_checker_.CalledOnValidThread());
+ AutoLock scoped_lock(tasks_lock_);
return now_;
}
TimeTicks TestMockTimeTaskRunner::NowTicks() const {
- DCHECK(thread_checker_.CalledOnValidThread());
+ AutoLock scoped_lock(tasks_lock_);
return now_ticks_;
}
-std::unique_ptr<Clock> TestMockTimeTaskRunner::GetMockClock() const {
+std::unique_ptr<Clock> TestMockTimeTaskRunner::DeprecatedGetMockClock() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return std::make_unique<LegacyMockClock>(this);
+}
+
+Clock* TestMockTimeTaskRunner::GetMockClock() const {
DCHECK(thread_checker_.CalledOnValidThread());
- return MakeUnique<MockClock>(this);
+ return &mock_clock_;
}
-std::unique_ptr<TickClock> TestMockTimeTaskRunner::GetMockTickClock() const {
+std::unique_ptr<TickClock> TestMockTimeTaskRunner::DeprecatedGetMockTickClock()
+ const {
DCHECK(thread_checker_.CalledOnValidThread());
- return MakeUnique<MockTickClock>(this);
+ return std::make_unique<LegacyMockTickClock>(this);
}
-std::deque<TestPendingTask> TestMockTimeTaskRunner::TakePendingTasks() {
+const TickClock* TestMockTimeTaskRunner::GetMockTickClock() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return &mock_clock_;
+}
+
+base::circular_deque<TestPendingTask>
+TestMockTimeTaskRunner::TakePendingTasks() {
AutoLock scoped_lock(tasks_lock_);
- std::deque<TestPendingTask> tasks;
+ base::circular_deque<TestPendingTask> tasks;
while (!tasks_.empty()) {
// It's safe to remove const and consume |task| here, since |task| is not
// used for ordering the item.
- tasks.push_back(
- std::move(const_cast<TestOrderedPendingTask&>(tasks_.top())));
+ if (!tasks_.top().task.IsCancelled()) {
+ tasks.push_back(
+ std::move(const_cast<TestOrderedPendingTask&>(tasks_.top())));
+ }
tasks_.pop();
}
return tasks;
}
-bool TestMockTimeTaskRunner::HasPendingTask() const {
+bool TestMockTimeTaskRunner::HasPendingTask() {
DCHECK(thread_checker_.CalledOnValidThread());
+ AutoLock scoped_lock(tasks_lock_);
+ while (!tasks_.empty() && tasks_.top().task.IsCancelled())
+ tasks_.pop();
return !tasks_.empty();
}
-size_t TestMockTimeTaskRunner::GetPendingTaskCount() const {
+size_t TestMockTimeTaskRunner::GetPendingTaskCount() {
DCHECK(thread_checker_.CalledOnValidThread());
+ AutoLock scoped_lock(tasks_lock_);
+ TaskPriorityQueue preserved_tasks;
+ while (!tasks_.empty()) {
+ if (!tasks_.top().task.IsCancelled()) {
+ preserved_tasks.push(
+ std::move(const_cast<TestOrderedPendingTask&>(tasks_.top())));
+ }
+ tasks_.pop();
+ }
+ tasks_.swap(preserved_tasks);
return tasks_.size();
}
-TimeDelta TestMockTimeTaskRunner::NextPendingTaskDelay() const {
+TimeDelta TestMockTimeTaskRunner::NextPendingTaskDelay() {
DCHECK(thread_checker_.CalledOnValidThread());
+ AutoLock scoped_lock(tasks_lock_);
+ while (!tasks_.empty() && tasks_.top().task.IsCancelled())
+ tasks_.pop();
return tasks_.empty() ? TimeDelta::Max()
: tasks_.top().GetTimeToRun() - now_ticks_;
}
@@ -234,32 +347,28 @@ TimeDelta TestMockTimeTaskRunner::NextPendingTaskDelay() const {
// TODO(gab): Combine |thread_checker_| with a SequenceToken to differentiate
// between tasks running in the scope of this TestMockTimeTaskRunner and other
// task runners sharing this thread. http://crbug.com/631186
-bool TestMockTimeTaskRunner::RunsTasksOnCurrentThread() const {
+bool TestMockTimeTaskRunner::RunsTasksInCurrentSequence() const {
return thread_checker_.CalledOnValidThread();
}
-bool TestMockTimeTaskRunner::PostDelayedTask(
- const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay) {
+bool TestMockTimeTaskRunner::PostDelayedTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay) {
AutoLock scoped_lock(tasks_lock_);
tasks_.push(TestOrderedPendingTask(from_here, std::move(task), now_ticks_,
delay, next_task_ordinal_++,
TestPendingTask::NESTABLE));
+ tasks_lock_cv_.Signal();
return true;
}
bool TestMockTimeTaskRunner::PostNonNestableDelayedTask(
- const tracked_objects::Location& from_here,
+ const Location& from_here,
OnceClosure task,
TimeDelta delay) {
return PostDelayedTask(from_here, std::move(task), delay);
}
-bool TestMockTimeTaskRunner::IsElapsingStopped() {
- return false;
-}
-
void TestMockTimeTaskRunner::OnBeforeSelectingTask() {
// Empty default implementation.
}
@@ -280,16 +389,19 @@ void TestMockTimeTaskRunner::ProcessAllTasksNoLaterThan(TimeDelta max_delta) {
// unit tests. Make sure this TestMockTimeTaskRunner's tasks run in its scope.
ScopedClosureRunner undo_override;
if (!ThreadTaskRunnerHandle::IsSet() ||
- ThreadTaskRunnerHandle::Get() != this) {
- undo_override = ThreadTaskRunnerHandle::OverrideForTesting(this);
+ ThreadTaskRunnerHandle::Get() != proxy_task_runner_.get()) {
+ undo_override =
+ ThreadTaskRunnerHandle::OverrideForTesting(proxy_task_runner_.get());
}
- const TimeTicks original_now_ticks = now_ticks_;
- while (!IsElapsingStopped()) {
+ const TimeTicks original_now_ticks = NowTicks();
+ while (!quit_run_loop_) {
OnBeforeSelectingTask();
TestPendingTask task_info;
if (!DequeueNextTask(original_now_ticks, max_delta, &task_info))
break;
+ if (task_info.task.IsCancelled())
+ continue;
// If tasks were posted with a negative delay, task_info.GetTimeToRun() will
// be less than |now_ticks_|. ForwardClocksUntilTickTime() takes care of not
// moving the clock backwards in this case.
@@ -301,17 +413,21 @@ void TestMockTimeTaskRunner::ProcessAllTasksNoLaterThan(TimeDelta max_delta) {
void TestMockTimeTaskRunner::ForwardClocksUntilTickTime(TimeTicks later_ticks) {
DCHECK(thread_checker_.CalledOnValidThread());
- if (later_ticks <= now_ticks_)
- return;
+ {
+ AutoLock scoped_lock(tasks_lock_);
+ if (later_ticks <= now_ticks_)
+ return;
- now_ += later_ticks - now_ticks_;
- now_ticks_ = later_ticks;
+ now_ += later_ticks - now_ticks_;
+ now_ticks_ = later_ticks;
+ }
OnAfterTimePassed();
}
bool TestMockTimeTaskRunner::DequeueNextTask(const TimeTicks& reference,
const TimeDelta& max_delta,
TestPendingTask* next_task) {
+ DCHECK(thread_checker_.CalledOnValidThread());
AutoLock scoped_lock(tasks_lock_);
if (!tasks_.empty() &&
(tasks_.top().GetTimeToRun() - reference) <= max_delta) {
@@ -324,4 +440,58 @@ bool TestMockTimeTaskRunner::DequeueNextTask(const TimeTicks& reference,
return false;
}
+void TestMockTimeTaskRunner::Run(bool application_tasks_allowed) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Since TestMockTimeTaskRunner doesn't process system messages: there's no
+ // hope for anything but an application task to call Quit(). If this RunLoop
+ // can't process application tasks (i.e. disallowed by default in nested
+ // RunLoops) it's guaranteed to hang...
+ DCHECK(application_tasks_allowed)
+ << "This is a nested RunLoop instance and needs to be of "
+ "Type::kNestableTasksAllowed.";
+
+ while (!quit_run_loop_) {
+ RunUntilIdle();
+ if (quit_run_loop_ || ShouldQuitWhenIdle())
+ break;
+
+ // Peek into |tasks_| to perform one of two things:
+ // A) If there are no remaining tasks, wait until one is posted and
+ // restart from the top.
+ // B) If there is a remaining delayed task. Fast-forward to reach the next
+ // round of tasks.
+ TimeDelta auto_fast_forward_by;
+ {
+ AutoLock scoped_lock(tasks_lock_);
+ if (tasks_.empty()) {
+ while (tasks_.empty())
+ tasks_lock_cv_.Wait();
+ continue;
+ }
+ auto_fast_forward_by = tasks_.top().GetTimeToRun() - now_ticks_;
+ }
+ FastForwardBy(auto_fast_forward_by);
+ }
+ quit_run_loop_ = false;
+}
+
+void TestMockTimeTaskRunner::Quit() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ quit_run_loop_ = true;
+}
+
+void TestMockTimeTaskRunner::EnsureWorkScheduled() {
+ // Nothing to do: TestMockTimeTaskRunner::Run() will always process tasks and
+ // doesn't need an extra kick on nested runs.
+}
+
+TimeTicks TestMockTimeTaskRunner::MockClock::NowTicks() const {
+ return task_runner_->NowTicks();
+}
+
+Time TestMockTimeTaskRunner::MockClock::Now() const {
+ return task_runner_->Now();
+}
+
} // namespace base
diff --git a/base/test/test_mock_time_task_runner.h b/base/test/test_mock_time_task_runner.h
index 2f892f52cc..dcfbcbdae7 100644
--- a/base/test/test_mock_time_task_runner.h
+++ b/base/test/test_mock_time_task_runner.h
@@ -7,33 +7,36 @@
#include <stddef.h>
-#include <deque>
#include <memory>
#include <queue>
#include <vector>
#include "base/callback.h"
#include "base/callback_helpers.h"
+#include "base/containers/circular_deque.h"
#include "base/macros.h"
+#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
+#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "base/test/test_pending_task.h"
#include "base/threading/thread_checker_impl.h"
+#include "base/time/clock.h"
+#include "base/time/tick_clock.h"
#include "base/time/time.h"
namespace base {
-class Clock;
-class TickClock;
+class ThreadTaskRunnerHandle;
// Runs pending tasks in the order of the tasks' post time + delay, and keeps
// track of a mock (virtual) tick clock time that can be fast-forwarded.
//
// TestMockTimeTaskRunner has the following properties:
//
-// - Methods RunsTasksOnCurrentThread() and Post[Delayed]Task() can be called
-// from any thread, but the rest of the methods must be called on the same
-// thread the TaskRunner was created on.
+// - Methods RunsTasksInCurrentSequence() and Post[Delayed]Task() can be
+// called from any thread, but the rest of the methods must be called on
+// the same thread the TestMockTimeTaskRunner was created on.
// - It allows for reentrancy, in that it handles the running of tasks that in
// turn call back into it (e.g., to post more tasks).
// - Tasks are stored in a priority queue, and executed in the increasing
@@ -43,17 +46,34 @@ class TickClock;
// posted task delays and fast-forward increments is still representable by
// a TimeDelta, and that adding this delta to the starting values of Time
// and TickTime is still within their respective range.
-// - Tasks aren't guaranteed to be destroyed immediately after they're run.
+//
+// A TestMockTimeTaskRunner of Type::kBoundToThread has the following additional
+// properties:
+// - Thread/SequencedTaskRunnerHandle refers to it on its thread.
+// - It can be driven by a RunLoop on the thread it was created on.
+// RunLoop::Run() will result in running non-delayed tasks until idle and
+// then, if RunLoop::QuitWhenIdle() wasn't invoked, fast-forwarding time to
+// the next delayed task and looping again. And so on, until either
+// RunLoop::Quit() is invoked (quits immediately after the current task) or
+// RunLoop::QuitWhenIdle() is invoked (quits before having to fast forward
+// time once again). Should RunLoop::Run() process all tasks (including
+// delayed ones), it will block until more are posted. As usual,
+// RunLoop::RunUntilIdle() is equivalent to RunLoop::Run() followed by an
+// immediate RunLoop::QuitWhenIdle().
+// -
//
// This is a slightly more sophisticated version of TestSimpleTaskRunner, in
// that it supports running delayed tasks in the correct temporal order.
-class TestMockTimeTaskRunner : public SingleThreadTaskRunner {
+class TestMockTimeTaskRunner : public SingleThreadTaskRunner,
+ public RunLoop::Delegate {
public:
// Everything that is executed in the scope of a ScopedContext will behave as
// though it ran under |scope| (i.e. ThreadTaskRunnerHandle,
- // RunsTasksOnCurrentThread, etc.). This allows the test body to be all in one
- // block when multiple TestMockTimeTaskRunners share the main thread. For
- // example:
+ // RunsTasksInCurrentSequence, etc.). This allows the test body to be all in
+ // one block when multiple TestMockTimeTaskRunners share the main thread.
+ // Note: RunLoop isn't supported: will DCHECK if used inside a ScopedContext.
+ //
+ // For example:
//
// class ExampleFixture {
// protected:
@@ -86,7 +106,7 @@ class TestMockTimeTaskRunner : public SingleThreadTaskRunner {
public:
// Note: |scope| is ran until idle as part of this constructor to ensure
// that anything which runs in the underlying scope runs after any already
- // pending tasks (the contrary would break the SequencedTraskRunner
+ // pending tasks (the contrary would break the SequencedTaskRunner
// contract).
explicit ScopedContext(scoped_refptr<TestMockTimeTaskRunner> scope);
~ScopedContext();
@@ -96,18 +116,34 @@ class TestMockTimeTaskRunner : public SingleThreadTaskRunner {
DISALLOW_COPY_AND_ASSIGN(ScopedContext);
};
+ enum class Type {
+ // A TestMockTimeTaskRunner which can only be driven directly through its
+ // API. Thread/SequencedTaskRunnerHandle will refer to it only in the scope
+ // of its tasks.
+ kStandalone,
+ // A TestMockTimeTaskRunner which will associate to the thread it is created
+ // on, enabling RunLoop to drive it and making
+ // Thread/SequencedTaskRunnerHandle refer to it on that thread.
+ kBoundToThread,
+ };
+
// Constructs an instance whose virtual time will start at the Unix epoch, and
// whose time ticks will start at zero.
- TestMockTimeTaskRunner();
+ TestMockTimeTaskRunner(Type type = Type::kStandalone);
// Constructs an instance starting at the given virtual time and time ticks.
- TestMockTimeTaskRunner(Time start_time, TimeTicks start_ticks);
+ TestMockTimeTaskRunner(Time start_time,
+ TimeTicks start_ticks,
+ Type type = Type::kStandalone);
// Fast-forwards virtual time by |delta|, causing all tasks with a remaining
// delay less than or equal to |delta| to be executed. |delta| must be
// non-negative.
void FastForwardBy(TimeDelta delta);
+ // Fast-forwards virtual time by |delta| but not causing any task execution.
+ void AdvanceMockTickClock(TimeDelta delta);
+
// Fast-forwards virtual time just until all tasks are executed.
void FastForwardUntilNoTasksRemain();
@@ -127,34 +163,36 @@ class TestMockTimeTaskRunner : public SingleThreadTaskRunner {
// Returns a Clock that uses the virtual time of |this| as its time source.
// The returned Clock will hold a reference to |this|.
- std::unique_ptr<Clock> GetMockClock() const;
+ // TODO(tzik): Remove DeprecatedGetMockClock() after updating all callers to
+ // use non-owning Clock.
+ std::unique_ptr<Clock> DeprecatedGetMockClock() const;
+ Clock* GetMockClock() const;
// Returns a TickClock that uses the virtual time ticks of |this| as its tick
// source. The returned TickClock will hold a reference to |this|.
- std::unique_ptr<TickClock> GetMockTickClock() const;
+ // TODO(tzik): Replace Remove DeprecatedGetMockTickClock() after updating all
+ // callers to use non-owning TickClock.
+ std::unique_ptr<TickClock> DeprecatedGetMockTickClock() const;
+ const TickClock* GetMockTickClock() const;
- std::deque<TestPendingTask> TakePendingTasks();
- bool HasPendingTask() const;
- size_t GetPendingTaskCount() const;
- TimeDelta NextPendingTaskDelay() const;
+ // Cancelled pending tasks get pruned automatically.
+ base::circular_deque<TestPendingTask> TakePendingTasks();
+ bool HasPendingTask();
+ size_t GetPendingTaskCount();
+ TimeDelta NextPendingTaskDelay();
// SingleThreadTaskRunner:
- bool RunsTasksOnCurrentThread() const override;
- bool PostDelayedTask(const tracked_objects::Location& from_here,
+ bool RunsTasksInCurrentSequence() const override;
+ bool PostDelayedTask(const Location& from_here,
OnceClosure task,
TimeDelta delay) override;
- bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here,
+ bool PostNonNestableDelayedTask(const Location& from_here,
OnceClosure task,
TimeDelta delay) override;
protected:
~TestMockTimeTaskRunner() override;
- // Whether the elapsing of virtual time is stopped or not. Subclasses can
- // override this method to perform early exits from a running task runner.
- // Defaults to always return false.
- virtual bool IsElapsingStopped();
-
// Called before the next task to run is selected, so that subclasses have a
// last chance to make sure all tasks are posted.
virtual void OnBeforeSelectingTask();
@@ -168,6 +206,27 @@ class TestMockTimeTaskRunner : public SingleThreadTaskRunner {
virtual void OnAfterTaskRun();
private:
+ class NonOwningProxyTaskRunner;
+
+ // MockClock implements TickClock and Clock. Always returns the then-current
+ // mock time of |task_runner| as the current time or time ticks.
+ class MockClock : public TickClock, public Clock {
+ public:
+ explicit MockClock(TestMockTimeTaskRunner* task_runner)
+ : task_runner_(task_runner) {}
+
+ // TickClock:
+ TimeTicks NowTicks() const override;
+
+ // Clock:
+ Time Now() const override;
+
+ private:
+ TestMockTimeTaskRunner* task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockClock);
+ };
+
struct TestOrderedPendingTask;
// Predicate that defines a strict weak temporal ordering of tasks.
@@ -199,7 +258,12 @@ class TestMockTimeTaskRunner : public SingleThreadTaskRunner {
const TimeDelta& max_delta,
TestPendingTask* next_task);
- // Also used for non-dcheck logic (RunsTasksOnCurrentThread()) and as such
+ // RunLoop::Delegate:
+ void Run(bool application_tasks_allowed) override;
+ void Quit() override;
+ void EnsureWorkScheduled() override;
+
+ // Also used for non-dcheck logic (RunsTasksInCurrentSequence()) and as such
// needs to be a ThreadCheckerImpl.
ThreadCheckerImpl thread_checker_;
@@ -212,9 +276,19 @@ class TestMockTimeTaskRunner : public SingleThreadTaskRunner {
// The ordinal to use for the next task. Must only be accessed while the
// |tasks_lock_| is held.
- size_t next_task_ordinal_;
+ size_t next_task_ordinal_ = 0;
+
+ mutable Lock tasks_lock_;
+ ConditionVariable tasks_lock_cv_;
+
+ const scoped_refptr<NonOwningProxyTaskRunner> proxy_task_runner_;
+ std::unique_ptr<ThreadTaskRunnerHandle> thread_task_runner_handle_;
+
+ // Set to true in RunLoop::Delegate::Quit() to signal the topmost
+ // RunLoop::Delegate::Run() instance to stop, reset to false when it does.
+ bool quit_run_loop_ = false;
- Lock tasks_lock_;
+ mutable MockClock mock_clock_;
DISALLOW_COPY_AND_ASSIGN(TestMockTimeTaskRunner);
};
diff --git a/base/test/test_mock_time_task_runner_unittest.cc b/base/test/test_mock_time_task_runner_unittest.cc
new file mode 100644
index 0000000000..299a6facd5
--- /dev/null
+++ b/base/test/test_mock_time_task_runner_unittest.cc
@@ -0,0 +1,290 @@
+// 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/test/test_mock_time_task_runner.h"
+
+#include "base/cancelable_callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/run_loop.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/gtest_util.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+// Basic usage should work the same from default and bound
+// TestMockTimeTaskRunners.
+TEST(TestMockTimeTaskRunnerTest, Basic) {
+ static constexpr TestMockTimeTaskRunner::Type kTestCases[] = {
+ TestMockTimeTaskRunner::Type::kStandalone,
+ TestMockTimeTaskRunner::Type::kBoundToThread};
+
+ for (auto type : kTestCases) {
+ SCOPED_TRACE(static_cast<int>(type));
+
+ auto mock_time_task_runner = MakeRefCounted<TestMockTimeTaskRunner>(type);
+ int counter = 0;
+
+ mock_time_task_runner->PostTask(
+ FROM_HERE,
+ base::Bind([](int* counter) { *counter += 1; }, Unretained(&counter)));
+ mock_time_task_runner->PostTask(
+ FROM_HERE,
+ base::Bind([](int* counter) { *counter += 32; }, Unretained(&counter)));
+ mock_time_task_runner->PostDelayedTask(
+ FROM_HERE,
+ base::Bind([](int* counter) { *counter += 256; }, Unretained(&counter)),
+ TimeDelta::FromSeconds(3));
+ mock_time_task_runner->PostDelayedTask(
+ FROM_HERE,
+ base::Bind([](int* counter) { *counter += 64; }, Unretained(&counter)),
+ TimeDelta::FromSeconds(1));
+ mock_time_task_runner->PostDelayedTask(
+ FROM_HERE,
+ base::Bind([](int* counter) { *counter += 1024; },
+ Unretained(&counter)),
+ TimeDelta::FromMinutes(20));
+ mock_time_task_runner->PostDelayedTask(
+ FROM_HERE,
+ base::Bind([](int* counter) { *counter += 4096; },
+ Unretained(&counter)),
+ TimeDelta::FromDays(20));
+
+ int expected_value = 0;
+ EXPECT_EQ(expected_value, counter);
+ mock_time_task_runner->RunUntilIdle();
+ expected_value += 1;
+ expected_value += 32;
+ EXPECT_EQ(expected_value, counter);
+
+ mock_time_task_runner->RunUntilIdle();
+ EXPECT_EQ(expected_value, counter);
+
+ mock_time_task_runner->FastForwardBy(TimeDelta::FromSeconds(1));
+ expected_value += 64;
+ EXPECT_EQ(expected_value, counter);
+
+ mock_time_task_runner->FastForwardBy(TimeDelta::FromSeconds(5));
+ expected_value += 256;
+ EXPECT_EQ(expected_value, counter);
+
+ mock_time_task_runner->FastForwardUntilNoTasksRemain();
+ expected_value += 1024;
+ expected_value += 4096;
+ EXPECT_EQ(expected_value, counter);
+ }
+}
+
+// A default TestMockTimeTaskRunner shouldn't result in a thread association.
+TEST(TestMockTimeTaskRunnerTest, DefaultUnbound) {
+ auto unbound_mock_time_task_runner = MakeRefCounted<TestMockTimeTaskRunner>();
+ EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
+ EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
+ EXPECT_DEATH_IF_SUPPORTED({ RunLoop().RunUntilIdle(); }, "");
+}
+
+TEST(TestMockTimeTaskRunnerTest, RunLoopDriveableWhenBound) {
+ auto bound_mock_time_task_runner = MakeRefCounted<TestMockTimeTaskRunner>(
+ TestMockTimeTaskRunner::Type::kBoundToThread);
+
+ int counter = 0;
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind([](int* counter) { *counter += 1; }, Unretained(&counter)));
+ ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind([](int* counter) { *counter += 32; }, Unretained(&counter)));
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind([](int* counter) { *counter += 256; }, Unretained(&counter)),
+ TimeDelta::FromSeconds(3));
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind([](int* counter) { *counter += 64; }, Unretained(&counter)),
+ TimeDelta::FromSeconds(1));
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind([](int* counter) { *counter += 1024; }, Unretained(&counter)),
+ TimeDelta::FromMinutes(20));
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind([](int* counter) { *counter += 4096; }, Unretained(&counter)),
+ TimeDelta::FromDays(20));
+
+ int expected_value = 0;
+ EXPECT_EQ(expected_value, counter);
+ RunLoop().RunUntilIdle();
+ expected_value += 1;
+ expected_value += 32;
+ EXPECT_EQ(expected_value, counter);
+
+ RunLoop().RunUntilIdle();
+ EXPECT_EQ(expected_value, counter);
+
+ {
+ RunLoop run_loop;
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), TimeDelta::FromSeconds(1));
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind([](int* counter) { *counter += 8192; },
+ Unretained(&counter)),
+ TimeDelta::FromSeconds(1));
+
+ // The QuitClosure() should be ordered between the 64 and the 8192
+ // increments and should preempt the latter.
+ run_loop.Run();
+ expected_value += 64;
+ EXPECT_EQ(expected_value, counter);
+
+ // Running until idle should process the 8192 increment whose delay has
+ // expired in the previous Run().
+ RunLoop().RunUntilIdle();
+ expected_value += 8192;
+ EXPECT_EQ(expected_value, counter);
+ }
+
+ {
+ RunLoop run_loop;
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitWhenIdleClosure(), TimeDelta::FromSeconds(5));
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind([](int* counter) { *counter += 16384; },
+ Unretained(&counter)),
+ TimeDelta::FromSeconds(5));
+
+ // The QuitWhenIdleClosure() shouldn't preempt equally delayed tasks and as
+ // such the 16384 increment should be processed before quitting.
+ run_loop.Run();
+ expected_value += 256;
+ expected_value += 16384;
+ EXPECT_EQ(expected_value, counter);
+ }
+
+ // Process the remaining tasks (note: do not mimic this elsewhere,
+ // TestMockTimeTaskRunner::FastForwardUntilNoTasksRemain() is a better API to
+ // do this, this is just done here for the purpose of extensively testing the
+ // RunLoop approach).
+ RunLoop run_loop;
+ ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitWhenIdleClosure(), TimeDelta::FromDays(50));
+
+ run_loop.Run();
+ expected_value += 1024;
+ expected_value += 4096;
+ EXPECT_EQ(expected_value, counter);
+}
+
+TEST(TestMockTimeTaskRunnerTest, AvoidCaptureWhenBound) {
+ // Make sure that capturing the active task runner --- which sometimes happens
+ // unknowingly due to ThreadsafeObserverList deep within some singleton ---
+ // does not keep the entire TestMockTimeTaskRunner alive, as in bound mode
+ // that's a RunLoop::Delegate, and leaking that renders any further tests that
+ // need RunLoop support unrunnable.
+ //
+ // (This used to happen when code run from ProcessAllTasksNoLaterThan grabbed
+ // the runner.).
+ scoped_refptr<SingleThreadTaskRunner> captured;
+ {
+ auto task_runner = MakeRefCounted<TestMockTimeTaskRunner>(
+ TestMockTimeTaskRunner::Type::kBoundToThread);
+
+ task_runner->PostTask(FROM_HERE, base::BindLambdaForTesting([&]() {
+ captured = ThreadTaskRunnerHandle::Get();
+ }));
+ task_runner->RunUntilIdle();
+ }
+
+ {
+ // This should not complain about RunLoop::Delegate already existing.
+ auto task_runner2 = MakeRefCounted<TestMockTimeTaskRunner>(
+ TestMockTimeTaskRunner::Type::kBoundToThread);
+ }
+}
+
+// Regression test that receiving the quit-when-idle signal when already empty
+// works as intended (i.e. that |TestMockTimeTaskRunner::tasks_lock_cv| is
+// properly signaled).
+TEST(TestMockTimeTaskRunnerTest, RunLoopQuitFromIdle) {
+ auto bound_mock_time_task_runner = MakeRefCounted<TestMockTimeTaskRunner>(
+ TestMockTimeTaskRunner::Type::kBoundToThread);
+
+ Thread quitting_thread("quitting thread");
+ quitting_thread.Start();
+
+ RunLoop run_loop;
+ quitting_thread.task_runner()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitWhenIdleClosure(), TestTimeouts::tiny_timeout());
+ run_loop.Run();
+}
+
+TEST(TestMockTimeTaskRunnerTest, TakePendingTasks) {
+ auto task_runner = MakeRefCounted<TestMockTimeTaskRunner>();
+ task_runner->PostTask(FROM_HERE, Bind([]() {}));
+ EXPECT_TRUE(task_runner->HasPendingTask());
+ EXPECT_EQ(1u, task_runner->TakePendingTasks().size());
+ EXPECT_FALSE(task_runner->HasPendingTask());
+}
+
+TEST(TestMockTimeTaskRunnerTest, CancelPendingTask) {
+ auto task_runner = MakeRefCounted<TestMockTimeTaskRunner>();
+ CancelableClosure task1(Bind([]() {}));
+ task_runner->PostDelayedTask(FROM_HERE, task1.callback(),
+ TimeDelta::FromSeconds(1));
+ EXPECT_TRUE(task_runner->HasPendingTask());
+ EXPECT_EQ(1u, task_runner->GetPendingTaskCount());
+ EXPECT_EQ(TimeDelta::FromSeconds(1), task_runner->NextPendingTaskDelay());
+ task1.Cancel();
+ EXPECT_FALSE(task_runner->HasPendingTask());
+
+ CancelableClosure task2(Bind([]() {}));
+ task_runner->PostDelayedTask(FROM_HERE, task2.callback(),
+ TimeDelta::FromSeconds(1));
+ task2.Cancel();
+ EXPECT_EQ(0u, task_runner->GetPendingTaskCount());
+
+ CancelableClosure task3(Bind([]() {}));
+ task_runner->PostDelayedTask(FROM_HERE, task3.callback(),
+ TimeDelta::FromSeconds(1));
+ task3.Cancel();
+ EXPECT_EQ(TimeDelta::Max(), task_runner->NextPendingTaskDelay());
+
+ CancelableClosure task4(Bind([]() {}));
+ task_runner->PostDelayedTask(FROM_HERE, task4.callback(),
+ TimeDelta::FromSeconds(1));
+ task4.Cancel();
+ EXPECT_TRUE(task_runner->TakePendingTasks().empty());
+}
+
+TEST(TestMockTimeTaskRunnerTest, NoFastForwardToCancelledTask) {
+ auto task_runner = MakeRefCounted<TestMockTimeTaskRunner>();
+ TimeTicks start_time = task_runner->NowTicks();
+ CancelableClosure task(Bind([]() {}));
+ task_runner->PostDelayedTask(FROM_HERE, task.callback(),
+ TimeDelta::FromSeconds(1));
+ EXPECT_EQ(TimeDelta::FromSeconds(1), task_runner->NextPendingTaskDelay());
+ task.Cancel();
+ task_runner->FastForwardUntilNoTasksRemain();
+ EXPECT_EQ(start_time, task_runner->NowTicks());
+}
+
+TEST(TestMockTimeTaskRunnerTest, AdvanceMockTickClockDoesNotRunTasks) {
+ auto task_runner = MakeRefCounted<TestMockTimeTaskRunner>();
+ TimeTicks start_time = task_runner->NowTicks();
+ task_runner->PostTask(FROM_HERE, BindOnce([]() { ADD_FAILURE(); }));
+ task_runner->PostDelayedTask(FROM_HERE, BindOnce([]() { ADD_FAILURE(); }),
+ TimeDelta::FromSeconds(1));
+
+ task_runner->AdvanceMockTickClock(TimeDelta::FromSeconds(3));
+ EXPECT_EQ(start_time + TimeDelta::FromSeconds(3), task_runner->NowTicks());
+ EXPECT_EQ(2u, task_runner->GetPendingTaskCount());
+}
+
+} // namespace base
diff --git a/base/test/test_pending_task.cc b/base/test/test_pending_task.cc
index fcc48a8980..1000132df9 100644
--- a/base/test/test_pending_task.cc
+++ b/base/test/test_pending_task.cc
@@ -11,7 +11,7 @@ namespace base {
TestPendingTask::TestPendingTask() : nestability(NESTABLE) {}
-TestPendingTask::TestPendingTask(const tracked_objects::Location& location,
+TestPendingTask::TestPendingTask(const Location& location,
OnceClosure task,
TimeTicks post_time,
TimeDelta delay,
@@ -36,7 +36,7 @@ bool TestPendingTask::ShouldRunBefore(const TestPendingTask& other) const {
return GetTimeToRun() < other.GetTimeToRun();
}
-TestPendingTask::~TestPendingTask() {}
+TestPendingTask::~TestPendingTask() = default;
// Unsupported in libchrome.
#if 0
diff --git a/base/test/test_pending_task.h b/base/test/test_pending_task.h
index f8e8c798b8..589070cfa9 100644
--- a/base/test/test_pending_task.h
+++ b/base/test/test_pending_task.h
@@ -23,7 +23,7 @@ struct TestPendingTask {
TestPendingTask();
TestPendingTask(TestPendingTask&& other);
- TestPendingTask(const tracked_objects::Location& location,
+ TestPendingTask(const Location& location,
OnceClosure task,
TimeTicks post_time,
TimeDelta delay,
@@ -53,7 +53,7 @@ struct TestPendingTask {
// - std::sort.
bool ShouldRunBefore(const TestPendingTask& other) const;
- tracked_objects::Location location;
+ Location location;
OnceClosure task;
TimeTicks post_time;
TimeDelta delay;
diff --git a/base/test/test_shared_memory_util.cc b/base/test/test_shared_memory_util.cc
new file mode 100644
index 0000000000..a923fd3cd2
--- /dev/null
+++ b/base/test/test_shared_memory_util.cc
@@ -0,0 +1,187 @@
+// 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/test/test_shared_memory_util.h"
+
+#include <gtest/gtest.h>
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/logging.h"
+#include "build/build_config.h"
+
+#if defined(OS_POSIX) && !defined(OS_NACL)
+#include <errno.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#endif
+
+#if defined(OS_FUCHSIA)
+#include <zircon/process.h>
+#include <zircon/rights.h>
+#include <zircon/syscalls.h>
+#endif
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include <mach/mach_vm.h>
+#endif
+
+#if defined(OS_WIN)
+#include <aclapi.h>
+#endif
+
+namespace base {
+
+#if !defined(OS_NACL)
+
+static const size_t kDataSize = 1024;
+
+// Common routine used with Posix file descriptors. Check that shared memory
+// file descriptor |fd| does not allow writable mappings. Return true on
+// success, false otherwise.
+#if defined(OS_POSIX)
+static bool CheckReadOnlySharedMemoryFdPosix(int fd) {
+// Note that the error on Android is EPERM, unlike other platforms where
+// it will be EACCES.
+#if defined(OS_ANDROID)
+ const int kExpectedErrno = EPERM;
+#else
+ const int kExpectedErrno = EACCES;
+#endif
+ errno = 0;
+ void* address =
+ mmap(nullptr, kDataSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ const bool success = (address != nullptr) && (address != MAP_FAILED);
+ if (success) {
+ LOG(ERROR) << "mmap() should have failed!";
+ munmap(address, kDataSize); // Cleanup.
+ return false;
+ }
+ if (errno != kExpectedErrno) {
+ LOG(ERROR) << "Expected mmap() to return " << kExpectedErrno
+ << " but returned " << errno << ": " << strerror(errno) << "\n";
+ return false;
+ }
+ return true;
+}
+#endif // OS_POSIX && !OS_FUCHSIA
+
+#if defined(OS_FUCHSIA)
+// Fuchsia specific implementation.
+bool CheckReadOnlySharedMemoryFuchsiaHandle(zx_handle_t handle) {
+ const uint32_t flags = ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE;
+ uintptr_t addr;
+ const zx_handle_t root = zx_vmar_root_self();
+ const zx_status_t status =
+ zx_vmar_map(root, 0, handle, 0U, kDataSize, flags, &addr);
+ if (status == ZX_OK) {
+ LOG(ERROR) << "zx_vmar_map() should have failed!";
+ zx_vmar_unmap(root, addr, kDataSize);
+ return false;
+ }
+ if (status != ZX_ERR_ACCESS_DENIED) {
+ LOG(ERROR) << "Expected zx_vmar_map() to return " << ZX_ERR_ACCESS_DENIED
+ << " (ZX_ERR_ACCESS_DENIED) but returned " << status << "\n";
+ return false;
+ }
+ return true;
+}
+
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+bool CheckReadOnlySharedMemoryMachPort(mach_port_t memory_object) {
+ mach_vm_address_t memory;
+ const kern_return_t kr = mach_vm_map(
+ mach_task_self(), &memory, kDataSize, 0, VM_FLAGS_ANYWHERE, memory_object,
+ 0, FALSE, VM_PROT_READ | VM_PROT_WRITE,
+ VM_PROT_READ | VM_PROT_WRITE | VM_PROT_IS_MASK, VM_INHERIT_NONE);
+ if (kr == KERN_SUCCESS) {
+ LOG(ERROR) << "mach_vm_map() should have failed!";
+ mach_vm_deallocate(mach_task_self(), memory, kDataSize); // Cleanup.
+ return false;
+ }
+ return true;
+}
+
+#elif defined(OS_WIN)
+bool CheckReadOnlySharedMemoryWindowsHandle(HANDLE handle) {
+ void* memory =
+ MapViewOfFile(handle, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, kDataSize);
+ if (memory != nullptr) {
+ LOG(ERROR) << "MapViewOfFile() should have failed!";
+ UnmapViewOfFile(memory);
+ return false;
+ }
+ return true;
+}
+#endif
+
+bool CheckReadOnlySharedMemoryHandleForTesting(SharedMemoryHandle handle) {
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // For OSX, the code has to deal with both POSIX and MACH handles.
+ if (handle.type_ == SharedMemoryHandle::POSIX)
+ return CheckReadOnlySharedMemoryFdPosix(handle.file_descriptor_.fd);
+ else
+ return CheckReadOnlySharedMemoryMachPort(handle.memory_object_);
+#elif defined(OS_FUCHSIA)
+ return CheckReadOnlySharedMemoryFuchsiaHandle(handle.GetHandle());
+#elif defined(OS_WIN)
+ return CheckReadOnlySharedMemoryWindowsHandle(handle.GetHandle());
+#else
+ return CheckReadOnlySharedMemoryFdPosix(handle.GetHandle());
+#endif
+}
+
+bool CheckReadOnlyPlatformSharedMemoryRegionForTesting(
+ subtle::PlatformSharedMemoryRegion region) {
+ if (region.GetMode() != subtle::PlatformSharedMemoryRegion::Mode::kReadOnly) {
+ LOG(ERROR) << "Expected region mode is "
+ << static_cast<int>(
+ subtle::PlatformSharedMemoryRegion::Mode::kReadOnly)
+ << " but actual is " << static_cast<int>(region.GetMode());
+ return false;
+ }
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ return CheckReadOnlySharedMemoryMachPort(region.GetPlatformHandle());
+#elif defined(OS_FUCHSIA)
+ return CheckReadOnlySharedMemoryFuchsiaHandle(region.GetPlatformHandle());
+#elif defined(OS_WIN)
+ return CheckReadOnlySharedMemoryWindowsHandle(region.GetPlatformHandle());
+#elif defined(OS_ANDROID)
+ return CheckReadOnlySharedMemoryFdPosix(region.GetPlatformHandle());
+#else
+ return CheckReadOnlySharedMemoryFdPosix(region.GetPlatformHandle().fd);
+#endif
+}
+
+#endif // !OS_NACL
+
+WritableSharedMemoryMapping MapForTesting(
+ subtle::PlatformSharedMemoryRegion* region) {
+ return MapAtForTesting(region, 0, region->GetSize());
+}
+
+WritableSharedMemoryMapping MapAtForTesting(
+ subtle::PlatformSharedMemoryRegion* region,
+ off_t offset,
+ size_t size) {
+ void* memory = nullptr;
+ size_t mapped_size = 0;
+ if (!region->MapAt(offset, size, &memory, &mapped_size))
+ return {};
+
+ return WritableSharedMemoryMapping(memory, size, mapped_size,
+ region->GetGUID());
+}
+
+template <>
+std::pair<ReadOnlySharedMemoryRegion, WritableSharedMemoryMapping>
+CreateMappedRegion(size_t size) {
+ MappedReadOnlyRegion mapped_region = ReadOnlySharedMemoryRegion::Create(size);
+ return {std::move(mapped_region.region), std::move(mapped_region.mapping)};
+}
+
+} // namespace base
diff --git a/base/test/test_shared_memory_util.h b/base/test/test_shared_memory_util.h
new file mode 100644
index 0000000000..d89f11d72a
--- /dev/null
+++ b/base/test/test_shared_memory_util.h
@@ -0,0 +1,56 @@
+// 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_TEST_TEST_SHARED_MEMORY_UTIL_H_
+#define BASE_TEST_TEST_SHARED_MEMORY_UTIL_H_
+
+#include "base/memory/platform_shared_memory_region.h"
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/memory/shared_memory_handle.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+// Check that the shared memory |handle| cannot be used to perform
+// a writable mapping with low-level system APIs like mmap(). Return true
+// in case of success (i.e. writable mappings are _not_ allowed), or false
+// otherwise.
+bool CheckReadOnlySharedMemoryHandleForTesting(SharedMemoryHandle handle);
+
+bool CheckReadOnlyPlatformSharedMemoryRegionForTesting(
+ subtle::PlatformSharedMemoryRegion region);
+
+// Creates a scoped mapping from a PlatformSharedMemoryRegion. It's useful for
+// PlatformSharedMemoryRegion testing to not leak mapped memory.
+// WritableSharedMemoryMapping is used for wrapping because it has max
+// capabilities but the actual permission depends on the |region|'s mode.
+// This must not be used in production where PlatformSharedMemoryRegion should
+// be wrapped with {Writable,Unsafe,ReadOnly}SharedMemoryRegion.
+WritableSharedMemoryMapping MapAtForTesting(
+ subtle::PlatformSharedMemoryRegion* region,
+ off_t offset,
+ size_t size);
+
+WritableSharedMemoryMapping MapForTesting(
+ subtle::PlatformSharedMemoryRegion* region);
+
+template <typename SharedMemoryRegionType>
+std::pair<SharedMemoryRegionType, WritableSharedMemoryMapping>
+CreateMappedRegion(size_t size) {
+ SharedMemoryRegionType region = SharedMemoryRegionType::Create(size);
+ WritableSharedMemoryMapping mapping = region.Map();
+ return {std::move(region), std::move(mapping)};
+}
+
+// Template specialization of CreateMappedRegion<>() for
+// the ReadOnlySharedMemoryRegion. We need this because
+// ReadOnlySharedMemoryRegion::Create() has a different return type.
+template <>
+std::pair<ReadOnlySharedMemoryRegion, WritableSharedMemoryMapping>
+CreateMappedRegion(size_t size);
+
+} // namespace base
+
+#endif // BASE_TEST_TEST_SHARED_MEMORY_UTIL_H_
diff --git a/base/test/test_simple_task_runner.cc b/base/test/test_simple_task_runner.cc
index 4280a0de62..91c68618df 100644
--- a/base/test/test_simple_task_runner.cc
+++ b/base/test/test_simple_task_runner.cc
@@ -16,10 +16,9 @@ TestSimpleTaskRunner::TestSimpleTaskRunner() = default;
TestSimpleTaskRunner::~TestSimpleTaskRunner() = default;
-bool TestSimpleTaskRunner::PostDelayedTask(
- const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay) {
+bool TestSimpleTaskRunner::PostDelayedTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay) {
AutoLock auto_lock(lock_);
pending_tasks_.push_back(TestPendingTask(from_here, std::move(task),
TimeTicks(), delay,
@@ -27,10 +26,9 @@ bool TestSimpleTaskRunner::PostDelayedTask(
return true;
}
-bool TestSimpleTaskRunner::PostNonNestableDelayedTask(
- const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay) {
+bool TestSimpleTaskRunner::PostNonNestableDelayedTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay) {
AutoLock auto_lock(lock_);
pending_tasks_.push_back(TestPendingTask(from_here, std::move(task),
TimeTicks(), delay,
@@ -41,11 +39,11 @@ bool TestSimpleTaskRunner::PostNonNestableDelayedTask(
// TODO(gab): Use SequenceToken here to differentiate between tasks running in
// the scope of this TestSimpleTaskRunner and other task runners sharing this
// thread. http://crbug.com/631186
-bool TestSimpleTaskRunner::RunsTasksOnCurrentThread() const {
+bool TestSimpleTaskRunner::RunsTasksInCurrentSequence() const {
return thread_ref_ == PlatformThread::CurrentRef();
}
-std::deque<TestPendingTask> TestSimpleTaskRunner::TakePendingTasks() {
+base::circular_deque<TestPendingTask> TestSimpleTaskRunner::TakePendingTasks() {
AutoLock auto_lock(lock_);
return std::move(pending_tasks_);
}
@@ -76,10 +74,10 @@ void TestSimpleTaskRunner::ClearPendingTasks() {
}
void TestSimpleTaskRunner::RunPendingTasks() {
- DCHECK(RunsTasksOnCurrentThread());
+ DCHECK(RunsTasksInCurrentSequence());
// Swap with a local variable to avoid re-entrancy problems.
- std::deque<TestPendingTask> tasks_to_run;
+ base::circular_deque<TestPendingTask> tasks_to_run;
{
AutoLock auto_lock(lock_);
tasks_to_run.swap(pending_tasks_);
@@ -98,9 +96,8 @@ void TestSimpleTaskRunner::RunPendingTasks() {
}
void TestSimpleTaskRunner::RunUntilIdle() {
- while (!pending_tasks_.empty()) {
+ while (HasPendingTask())
RunPendingTasks();
- }
}
} // namespace base
diff --git a/base/test/test_simple_task_runner.h b/base/test/test_simple_task_runner.h
index f46e065e47..bff8ee5cd2 100644
--- a/base/test/test_simple_task_runner.h
+++ b/base/test/test_simple_task_runner.h
@@ -5,10 +5,9 @@
#ifndef BASE_TEST_TEST_SIMPLE_TASK_RUNNER_H_
#define BASE_TEST_TEST_SIMPLE_TASK_RUNNER_H_
-#include <deque>
-
#include "base/callback.h"
#include "base/compiler_specific.h"
+#include "base/containers/circular_deque.h"
#include "base/macros.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/lock.h"
@@ -43,16 +42,16 @@ class TestSimpleTaskRunner : public SingleThreadTaskRunner {
TestSimpleTaskRunner();
// SingleThreadTaskRunner implementation.
- bool PostDelayedTask(const tracked_objects::Location& from_here,
+ bool PostDelayedTask(const Location& from_here,
OnceClosure task,
TimeDelta delay) override;
- bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here,
+ bool PostNonNestableDelayedTask(const Location& from_here,
OnceClosure task,
TimeDelta delay) override;
- bool RunsTasksOnCurrentThread() const override;
+ bool RunsTasksInCurrentSequence() const override;
- std::deque<TestPendingTask> TakePendingTasks();
+ base::circular_deque<TestPendingTask> TakePendingTasks();
size_t NumPendingTasks() const;
bool HasPendingTask() const;
base::TimeDelta NextPendingTaskDelay() const;
@@ -80,7 +79,7 @@ class TestSimpleTaskRunner : public SingleThreadTaskRunner {
// Synchronizes access to |pending_tasks_|.
mutable Lock lock_;
- std::deque<TestPendingTask> pending_tasks_;
+ base::circular_deque<TestPendingTask> pending_tasks_;
DISALLOW_COPY_AND_ASSIGN(TestSimpleTaskRunner);
};
diff --git a/base/test/test_switches.cc b/base/test/test_switches.cc
index 817a38edb1..a35bdd86bf 100644
--- a/base/test/test_switches.cc
+++ b/base/test/test_switches.cc
@@ -26,6 +26,10 @@ const char switches::kTestLauncherForceRunBrokenTests[] =
// Path to file containing test filter (one pattern per line).
const char switches::kTestLauncherFilterFile[] = "test-launcher-filter-file";
+// Whether the test launcher should launch in "interactive mode", which disables
+// timeouts (and may have other effects for specific test types).
+const char switches::kTestLauncherInteractive[] = "test-launcher-interactive";
+
// Number of parallel test launcher jobs.
const char switches::kTestLauncherJobs[] = "test-launcher-jobs";
@@ -56,6 +60,11 @@ const char switches::kTestLauncherPrintWritablePath[] =
const char switches::kTestLauncherShardIndex[] =
"test-launcher-shard-index";
+// Limit of test part results in the output. Default limit is 10.
+// Negative value will completely disable limit.
+const char switches::kTestLauncherTestPartResultsLimit[] =
+ "test-launcher-test-part-results-limit";
+
// Total number of shards. Must be the same for all shards.
const char switches::kTestLauncherTotalShards[] =
"test-launcher-total-shards";
diff --git a/base/test/test_switches.h b/base/test/test_switches.h
index 88ef0ced77..6baba30819 100644
--- a/base/test/test_switches.h
+++ b/base/test/test_switches.h
@@ -14,6 +14,7 @@ extern const char kTestLauncherBotMode[];
extern const char kTestLauncherDebugLauncher[];
extern const char kTestLauncherForceRunBrokenTests[];
extern const char kTestLauncherFilterFile[];
+extern const char kTestLauncherInteractive[];
extern const char kTestLauncherJobs[];
extern const char kTestLauncherListTests[];
extern const char kTestLauncherOutput[];
@@ -22,6 +23,7 @@ extern const char kTestLauncherSummaryOutput[];
extern const char kTestLauncherPrintTestStdio[];
extern const char kTestLauncherPrintWritablePath[];
extern const char kTestLauncherShardIndex[];
+extern const char kTestLauncherTestPartResultsLimit[];
extern const char kTestLauncherTotalShards[];
extern const char kTestLauncherTimeout[];
extern const char kTestLauncherTrace[];
diff --git a/base/test/test_timeouts.cc b/base/test/test_timeouts.cc
index 0dc0f49dee..dd5acbca4a 100644
--- a/base/test/test_timeouts.cc
+++ b/base/test/test_timeouts.cc
@@ -15,25 +15,6 @@
namespace {
-// ASan/TSan/MSan instrument each memory access. This may slow the execution
-// down significantly.
-#if defined(MEMORY_SANITIZER)
-// For MSan the slowdown depends heavily on the value of msan_track_origins GYP
-// flag. The multiplier below corresponds to msan_track_origins=1.
-static const int kTimeoutMultiplier = 6;
-#elif defined(ADDRESS_SANITIZER) && defined(OS_WIN)
-// Asan/Win has not been optimized yet, give it a higher
-// timeout multiplier. See http://crbug.com/412471
-static const int kTimeoutMultiplier = 3;
-#elif defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) || \
- defined(SYZYASAN)
-static const int kTimeoutMultiplier = 2;
-#else
-static const int kTimeoutMultiplier = 1;
-#endif
-
-const int kAlmostInfiniteTimeoutMs = 100000000;
-
// Sets value to the greatest of:
// 1) value's current value multiplied by kTimeoutMultiplier (assuming
// InitializeTimeout is called only once per value).
@@ -42,35 +23,39 @@ const int kAlmostInfiniteTimeoutMs = 100000000;
// by kTimeoutMultiplier.
void InitializeTimeout(const char* switch_name, int min_value, int* value) {
DCHECK(value);
+ int command_line_timeout = 0;
if (base::CommandLine::ForCurrentProcess()->HasSwitch(switch_name)) {
std::string string_value(base::CommandLine::ForCurrentProcess()->
GetSwitchValueASCII(switch_name));
- int timeout;
- if (string_value == TestTimeouts::kNoTimeoutSwitchValue)
- timeout = kAlmostInfiniteTimeoutMs;
- else
- base::StringToInt(string_value, &timeout);
- *value = std::max(*value, timeout);
+ if (!base::StringToInt(string_value, &command_line_timeout)) {
+ LOG(FATAL) << "Timeout value \"" << string_value << "\" was parsed as "
+ << command_line_timeout;
+ }
}
- *value *= kTimeoutMultiplier;
- *value = std::max(*value, min_value);
-}
-// Sets value to the greatest of:
-// 1) value's current value multiplied by kTimeoutMultiplier.
-// 2) 0
-// 3) the numerical value given by switch_name on the command line multiplied
-// by kTimeoutMultiplier.
-void InitializeTimeout(const char* switch_name, int* value) {
- InitializeTimeout(switch_name, 0, value);
+#if defined(MEMORY_SANITIZER)
+ // ASan/TSan/MSan instrument each memory access. This may slow the execution
+ // down significantly.
+ // For MSan the slowdown depends heavily on the value of msan_track_origins
+ // build flag. The multiplier below corresponds to msan_track_origins = 1.
+ constexpr int kTimeoutMultiplier = 6;
+#elif defined(ADDRESS_SANITIZER) && defined(OS_WIN)
+ // ASan/Win has not been optimized yet, give it a higher
+ // timeout multiplier. See http://crbug.com/412471
+ constexpr int kTimeoutMultiplier = 3;
+#elif defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER)
+ constexpr int kTimeoutMultiplier = 2;
+#else
+ constexpr int kTimeoutMultiplier = 1;
+#endif
+
+ *value = std::max(std::max(*value, command_line_timeout) * kTimeoutMultiplier,
+ min_value);
}
} // namespace
// static
-constexpr const char TestTimeouts::kNoTimeoutSwitchValue[];
-
-// static
bool TestTimeouts::initialized_ = false;
// The timeout values should increase in the order they appear in this block.
@@ -87,10 +72,7 @@ int TestTimeouts::test_launcher_timeout_ms_ = 45000;
// static
void TestTimeouts::Initialize() {
- if (initialized_) {
- NOTREACHED();
- return;
- }
+ DCHECK(!initialized_);
initialized_ = true;
if (base::debug::BeingDebugged()) {
@@ -100,10 +82,26 @@ void TestTimeouts::Initialize() {
// Note that these timeouts MUST be initialized in the correct order as
// per the CHECKS below.
- InitializeTimeout(switches::kTestTinyTimeout, &tiny_timeout_ms_);
- InitializeTimeout(switches::kUiTestActionTimeout,
- base::debug::BeingDebugged() ? kAlmostInfiniteTimeoutMs
- : tiny_timeout_ms_,
+
+ InitializeTimeout(switches::kTestTinyTimeout, 0, &tiny_timeout_ms_);
+
+ // All timeouts other than the "tiny" one should be set to very large values
+ // when in a debugger or when run interactively, so that tests will not get
+ // auto-terminated. By setting the UI test action timeout to at least this
+ // value, we guarantee the subsequent timeouts will be this large also.
+ // Setting the "tiny" timeout to a large value as well would make some tests
+ // hang (because it's used as a task-posting delay). In particular this
+ // causes problems for some iOS device tests, which are always run inside a
+ // debugger (thus BeingDebugged() is true even on the bots).
+ int min_ui_test_action_timeout = tiny_timeout_ms_;
+ if (base::debug::BeingDebugged() ||
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kTestLauncherInteractive)) {
+ constexpr int kVeryLargeTimeoutMs = 100'000'000;
+ min_ui_test_action_timeout = kVeryLargeTimeoutMs;
+ }
+
+ InitializeTimeout(switches::kUiTestActionTimeout, min_ui_test_action_timeout,
&action_timeout_ms_);
InitializeTimeout(switches::kUiTestActionMaxTimeout, action_timeout_ms_,
&action_max_timeout_ms_);
@@ -113,8 +111,7 @@ void TestTimeouts::Initialize() {
&test_launcher_timeout_ms_);
// The timeout values should be increasing in the right order.
- CHECK(tiny_timeout_ms_ <= action_timeout_ms_);
- CHECK(action_timeout_ms_ <= action_max_timeout_ms_);
-
- CHECK(action_timeout_ms_ <= test_launcher_timeout_ms_);
+ CHECK_LE(tiny_timeout_ms_, action_timeout_ms_);
+ CHECK_LE(action_timeout_ms_, action_max_timeout_ms_);
+ CHECK_LE(action_timeout_ms_, test_launcher_timeout_ms_);
}
diff --git a/base/test/test_timeouts.h b/base/test/test_timeouts.h
index 9d42eb91fe..71983ed495 100644
--- a/base/test/test_timeouts.h
+++ b/base/test/test_timeouts.h
@@ -10,17 +10,16 @@
#include "base/time/time.h"
// Returns common timeouts to use in tests. Makes it possible to adjust
-// the timeouts for different environments (like Valgrind).
+// the timeouts for different environments (like TSan).
class TestTimeouts {
public:
- // Argument that can be passed on the command line to indicate "no timeout".
- static constexpr const char kNoTimeoutSwitchValue[] = "-1";
-
// Initializes the timeouts. Non thread-safe. Should be called exactly once
// by the test suite.
static void Initialize();
- // Timeout for actions that are expected to finish "almost instantly".
+ // Timeout for actions that are expected to finish "almost instantly". This
+ // is used in various tests to post delayed tasks and usually functions more
+ // like a delay value than a timeout.
static base::TimeDelta tiny_timeout() {
DCHECK(initialized_);
return base::TimeDelta::FromMilliseconds(tiny_timeout_ms_);
diff --git a/base/third_party/icu/LICENSE b/base/third_party/icu/LICENSE
index 40282f4949..2882e4ebda 100644
--- a/base/third_party/icu/LICENSE
+++ b/base/third_party/icu/LICENSE
@@ -1,9 +1,50 @@
-ICU License - ICU 1.8.1 and later
+COPYRIGHT AND PERMISSION NOTICE (ICU 58 and later)
-COPYRIGHT AND PERMISSION NOTICE
+Copyright © 1991-2017 Unicode, Inc. All rights reserved.
+Distributed under the Terms of Use in http://www.unicode.org/copyright.html
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Unicode data files and any associated documentation
+(the "Data Files") or Unicode software and any associated documentation
+(the "Software") to deal in the Data Files or Software
+without restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, and/or sell copies of
+the Data Files or Software, and to permit persons to whom the Data Files
+or Software are furnished to do so, provided that either
+(a) this copyright and permission notice appear with all copies
+of the Data Files or Software, or
+(b) this copyright and permission notice appear in associated
+Documentation.
+
+THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS
+NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
+DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THE DATA FILES OR SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder
+shall not be used in advertising or otherwise to promote the sale,
+use or other dealings in these Data Files or Software without prior
+written authorization of the copyright holder.
-Copyright (c) 1995-2009 International Business Machines Corporation and others
+---------------------
+Third-Party Software Licenses
+
+This section contains third-party software notices and/or additional
+terms for licensed third-party software components included within ICU
+libraries.
+
+1. ICU License - ICU 1.8.1 to ICU 57.1
+
+COPYRIGHT AND PERMISSION NOTICE
+
+Copyright (c) 1995-2016 International Business Machines Corporation and others
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining
@@ -30,3 +71,6 @@ Except as contained in this notice, the name of a copyright holder
shall not be used in advertising or otherwise to promote the sale, use
or other dealings in this Software without prior written authorization
of the copyright holder.
+
+All trademarks and registered trademarks mentioned herein are the
+property of their respective owners.
diff --git a/base/third_party/icu/README.chromium b/base/third_party/icu/README.chromium
index 6a9a15aac8..297e89a2ed 100644
--- a/base/third_party/icu/README.chromium
+++ b/base/third_party/icu/README.chromium
@@ -1,16 +1,17 @@
Name: ICU
URL: http://site.icu-project.org/
-License: MIT
+Version: 60
+License: Unicode
License File: NOT_SHIPPED
-This file has the relevant components from ICU copied to handle basic
-UTF8/16/32 conversions. Components are copied from utf.h utf8.h utf16.h and
-utf_impl.c
-
-The same module appears in third_party/icu, so we don't repeat the license
-file here.
+This file has the relevant components from ICU copied to handle basic UTF8/16/32
+conversions. Components are copied from umachine.h, utf.h, utf8.h, and utf16.h
+into icu_utf.h, and from utf_impl.cpp into icu_utf.cc.
The main change is that U_/U8_/U16_ prefixes have been replaced with
CBU_/CBU8_/CBU16_ (for "Chrome Base") to avoid confusion with the "real" ICU
macros should ICU be in use on the system. For the same reason, the functions
and types have been put in the "base_icu" namespace.
+
+Note that this license file is marked as NOT_SHIPPED, since a more complete
+ICU license is included from //third_party/icu/README.chromium
diff --git a/base/third_party/icu/icu_utf.cc b/base/third_party/icu/icu_utf.cc
index 2b67c5d9c2..a3262b04d3 100644
--- a/base/third_party/icu/icu_utf.cc
+++ b/base/third_party/icu/icu_utf.cc
@@ -1,12 +1,14 @@
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
/*
******************************************************************************
*
-* Copyright (C) 1999-2006, International Business Machines
+* Copyright (C) 1999-2012, International Business Machines
* Corporation and others. All Rights Reserved.
*
******************************************************************************
-* file name: utf_impl.c
-* encoding: US-ASCII
+* file name: utf_impl.cpp
+* encoding: UTF-8
* tab size: 8 (not used)
* indentation:4
*
@@ -21,99 +23,41 @@
namespace base_icu {
-/**
- * UTF8_ERROR_VALUE_1 and UTF8_ERROR_VALUE_2 are special error values for UTF-8,
- * which need 1 or 2 bytes in UTF-8:
- * \code
- * U+0015 = NAK = Negative Acknowledge, C0 control character
- * U+009f = highest C1 control character
- * \endcode
- *
- * These are used by UTF8_..._SAFE macros so that they can return an error value
- * that needs the same number of code units (bytes) as were seen by
- * a macro. They should be tested with UTF_IS_ERROR() or UTF_IS_VALID().
- *
- * @deprecated ICU 2.4. Obsolete, see utf_old.h.
- */
-#define CBUTF8_ERROR_VALUE_1 0x15
-
-/**
- * See documentation on UTF8_ERROR_VALUE_1 for details.
- *
- * @deprecated ICU 2.4. Obsolete, see utf_old.h.
- */
-#define CBUTF8_ERROR_VALUE_2 0x9f
-
-
-/**
- * Error value for all UTFs. This code point value will be set by macros with e>
- * checking if an error is detected.
- *
- * @deprecated ICU 2.4. Obsolete, see utf_old.h.
- */
-#define CBUTF_ERROR_VALUE 0xffff
-
-/*
- * This table could be replaced on many machines by
- * a few lines of assembler code using an
- * "index of first 0-bit from msb" instruction and
- * one or two more integer instructions.
- *
- * For example, on an i386, do something like
- * - MOV AL, leadByte
- * - NOT AL (8-bit, leave b15..b8==0..0, reverse only b7..b0)
- * - MOV AH, 0
- * - BSR BX, AX (16-bit)
- * - MOV AX, 6 (result)
- * - JZ finish (ZF==1 if leadByte==0xff)
- * - SUB AX, BX (result)
- * -finish:
- * (BSR: Bit Scan Reverse, scans for a 1-bit, starting from the MSB)
- *
- * In Unicode, all UTF-8 byte sequences with more than 4 bytes are illegal;
- * lead bytes above 0xf4 are illegal.
- * We keep them in this table for skipping long ISO 10646-UTF-8 sequences.
- */
-const uint8_t utf8_countTrailBytes[256] =
- {
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3,
- 3, 3, /* illegal in Unicode */
- 4, 4, 4, 4, /* illegal in Unicode */
- 5, 5, /* illegal in Unicode */
- 0, 0 /* illegal bytes 0xfe and 0xff */
-};
-
-static const UChar32
-utf8_minLegal[4]={ 0, 0x80, 0x800, 0x10000 };
+// source/common/utf_impl.cpp
static const UChar32
utf8_errorValue[6]={
- CBUTF8_ERROR_VALUE_1, CBUTF8_ERROR_VALUE_2, CBUTF_ERROR_VALUE, 0x10ffff,
- 0x3ffffff, 0x7fffffff
+ // Same values as UTF8_ERROR_VALUE_1, UTF8_ERROR_VALUE_2, UTF_ERROR_VALUE,
+ // but without relying on the obsolete unicode/utf_old.h.
+ 0x15, 0x9f, 0xffff,
+ 0x10ffff
};
+static UChar32
+errorValue(int32_t count, int8_t strict) {
+ if(strict>=0) {
+ return utf8_errorValue[count];
+ } else if(strict==-3) {
+ return 0xfffd;
+ } else {
+ return CBU_SENTINEL;
+ }
+}
+
/*
- * Handle the non-inline part of the U8_NEXT() macro and its obsolete sibling
- * UTF8_NEXT_CHAR_SAFE().
+ * Handle the non-inline part of the U8_NEXT() and U8_NEXT_FFFD() macros
+ * and their obsolete sibling UTF8_NEXT_CHAR_SAFE().
+ *
+ * U8_NEXT() supports NUL-terminated strings indicated via length<0.
*
* The "strict" parameter controls the error behavior:
- * <0 "Safe" behavior of U8_NEXT(): All illegal byte sequences yield a negative
- * code point result.
+ * <0 "Safe" behavior of U8_NEXT():
+ * -1: All illegal byte sequences yield U_SENTINEL=-1.
+ * -2: Same as -1, except for lenient treatment of surrogate code points as legal.
+ * Some implementations use this for roundtripping of
+ * Unicode 16-bit strings that are not well-formed UTF-16, that is, they
+ * contain unpaired surrogates.
+ * -3: All illegal byte sequences yield U+FFFD.
* 0 Obsolete "safe" behavior of UTF8_NEXT_CHAR_SAFE(..., FALSE):
* All illegal byte sequences yield a positive code point such that this
* result code point would be encoded with the same number of bytes as
@@ -122,104 +66,64 @@ utf8_errorValue[6]={
* Same as the obsolete "safe" behavior, but non-characters are also treated
* like illegal sequences.
*
- * The special negative (<0) value -2 is used for lenient treatment of surrogate
- * code points as legal. Some implementations use this for roundtripping of
- * Unicode 16-bit strings that are not well-formed UTF-16, that is, they
- * contain unpaired surrogates.
- *
* Note that a UBool is the same as an int8_t.
*/
-UChar32 utf8_nextCharSafeBody(const uint8_t* s,
- int32_t* pi,
- int32_t length,
- UChar32 c,
- UBool strict) {
- int32_t i = *pi;
- uint8_t count = CBU8_COUNT_TRAIL_BYTES(c);
- if((i)+count<=(length)) {
- uint8_t trail, illegal = 0;
-
- CBU8_MASK_LEAD_BYTE((c), count);
- /* count==0 for illegally leading trail bytes and the illegal bytes 0xfe and 0xff */
- switch(count) {
- /* each branch falls through to the next one */
- case 5:
- case 4:
- /* count>=4 is always illegal: no more than 3 trail bytes in Unicode's UTF-8 */
- illegal=1;
- break;
- case 3:
- trail=s[(i)++];
- (c)=((c)<<6)|(trail&0x3f);
- if(c<0x110) {
- illegal|=(trail&0xc0)^0x80;
- } else {
- /* code point>0x10ffff, outside Unicode */
- illegal=1;
- break;
- }
- case 2:
- trail=s[(i)++];
- (c)=((c)<<6)|(trail&0x3f);
- illegal|=(trail&0xc0)^0x80;
- case 1:
- trail=s[(i)++];
- (c)=((c)<<6)|(trail&0x3f);
- illegal|=(trail&0xc0)^0x80;
- break;
- case 0:
- if(strict>=0) {
- return CBUTF8_ERROR_VALUE_1;
- } else {
- return CBU_SENTINEL;
+UChar32
+utf8_nextCharSafeBody(const uint8_t *s, int32_t *pi, int32_t length, UChar32 c, UBool strict) {
+ // *pi is one after byte c.
+ int32_t i=*pi;
+ // length can be negative for NUL-terminated strings: Read and validate one byte at a time.
+ if(i==length || c>0xf4) {
+ // end of string, or not a lead byte
+ } else if(c>=0xf0) {
+ // Test for 4-byte sequences first because
+ // U8_NEXT() handles shorter valid sequences inline.
+ uint8_t t1=s[i], t2, t3;
+ c&=7;
+ if(CBU8_IS_VALID_LEAD4_AND_T1(c, t1) &&
+ ++i!=length && (t2=s[i]-0x80)<=0x3f &&
+ ++i!=length && (t3=s[i]-0x80)<=0x3f) {
+ ++i;
+ c=(c<<18)|((t1&0x3f)<<12)|(t2<<6)|t3;
+ // strict: forbid non-characters like U+fffe
+ if(strict<=0 || !CBU_IS_UNICODE_NONCHAR(c)) {
+ *pi=i;
+ return c;
}
- /* no default branch to optimize switch() - all values are covered */
}
-
- /*
- * All the error handling should return a value
- * that needs count bytes so that UTF8_GET_CHAR_SAFE() works right.
- *
- * Starting with Unicode 3.0.1, non-shortest forms are illegal.
- * Starting with Unicode 3.2, surrogate code points must not be
- * encoded in UTF-8, and there are no irregular sequences any more.
- *
- * U8_ macros (new in ICU 2.4) return negative values for error conditions.
- */
-
- /* correct sequence - all trail bytes have (b7..b6)==(10)? */
- /* illegal is also set if count>=4 */
- if(illegal || (c)<utf8_minLegal[count] || (CBU_IS_SURROGATE(c) && strict!=-2)) {
- /* error handling */
- uint8_t errorCount = count;
- /* don't go beyond this sequence */
- i=*pi;
- while(count>0 && CBU8_IS_TRAIL(s[i])) {
- ++(i);
- --count;
+ } else if(c>=0xe0) {
+ c&=0xf;
+ if(strict!=-2) {
+ uint8_t t1=s[i], t2;
+ if(CBU8_IS_VALID_LEAD3_AND_T1(c, t1) &&
+ ++i!=length && (t2=s[i]-0x80)<=0x3f) {
+ ++i;
+ c=(c<<12)|((t1&0x3f)<<6)|t2;
+ // strict: forbid non-characters like U+fffe
+ if(strict<=0 || !CBU_IS_UNICODE_NONCHAR(c)) {
+ *pi=i;
+ return c;
+ }
}
- if(strict>=0) {
- c=utf8_errorValue[errorCount-count];
- } else {
- c=CBU_SENTINEL;
+ } else {
+ // strict=-2 -> lenient: allow surrogates
+ uint8_t t1=s[i]-0x80, t2;
+ if(t1<=0x3f && (c>0 || t1>=0x20) &&
+ ++i!=length && (t2=s[i]-0x80)<=0x3f) {
+ *pi=i+1;
+ return (c<<12)|(t1<<6)|t2;
}
- } else if((strict)>0 && CBU_IS_UNICODE_NONCHAR(c)) {
- /* strict: forbid non-characters like U+fffe */
- c=utf8_errorValue[count];
}
- } else /* too few bytes left */ {
- /* error handling */
- int32_t i0 = i;
- /* don't just set (i)=(length) in case there is an illegal sequence */
- while((i)<(length) && CBU8_IS_TRAIL(s[i])) {
- ++(i);
+ } else if(c>=0xc2) {
+ uint8_t t1=s[i]-0x80;
+ if(t1<=0x3f) {
+ *pi=i+1;
+ return ((c-0xc0)<<6)|t1;
}
- if(strict>=0) {
- c=utf8_errorValue[i-i0];
- } else {
- c=CBU_SENTINEL;
- }
- }
+ } // else 0x80<=c<0xc2 is not a lead byte
+
+ /* error handling */
+ c=errorValue(i-*pi, strict);
*pi=i;
return c;
}
diff --git a/base/third_party/icu/icu_utf.h b/base/third_party/icu/icu_utf.h
index 4370fdec15..2ba82316c2 100644
--- a/base/third_party/icu/icu_utf.h
+++ b/base/third_party/icu/icu_utf.h
@@ -1,17 +1,12 @@
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
/*
-*******************************************************************************
+******************************************************************************
*
-* Copyright (C) 1999-2004, International Business Machines
+* Copyright (C) 1999-2015, International Business Machines
* Corporation and others. All Rights Reserved.
*
-*******************************************************************************
-* file name: utf.h
-* encoding: US-ASCII
-* tab size: 8 (not used)
-* indentation:4
-*
-* created on: 1999sep09
-* created by: Markus W. Scherer
+******************************************************************************
*/
#ifndef BASE_THIRD_PARTY_ICU_ICU_UTF_H_
@@ -21,12 +16,29 @@
namespace base_icu {
-typedef int32_t UChar32;
-typedef uint16_t UChar;
+// source/common/unicode/umachine.h
+
+/** The ICU boolean type @stable ICU 2.0 */
typedef int8_t UBool;
-// General ---------------------------------------------------------------------
-// from utf.h
+/**
+ * Define UChar32 as a type for single Unicode code points.
+ * UChar32 is a signed 32-bit integer (same as int32_t).
+ *
+ * The Unicode code point range is 0..0x10ffff.
+ * All other values (negative or >=0x110000) are illegal as Unicode code points.
+ * They may be used as sentinel values to indicate "done", "error"
+ * or similar non-code point conditions.
+ *
+ * Before ICU 2.4 (Jitterbug 2146), UChar32 was defined
+ * to be wchar_t if that is 32 bits wide (wchar_t may be signed or unsigned)
+ * or else to be uint32_t.
+ * That is, the definition of UChar32 was platform-dependent.
+ *
+ * @see U_SENTINEL
+ * @stable ICU 2.4
+ */
+typedef int32_t UChar32;
/**
* This value is intended for sentinel values for APIs that
@@ -34,7 +46,7 @@ typedef int8_t UBool;
* It is outside of the Unicode code point range 0..0x10ffff.
*
* For example, a "done" or "error" value in a new API
- * could be indicated with CBU_SENTINEL.
+ * could be indicated with U_SENTINEL.
*
* ICU APIs designed before ICU 2.4 usually define service-specific "done"
* values, mostly 0xffff.
@@ -48,15 +60,17 @@ typedef int8_t UBool;
*/
#define CBU_SENTINEL (-1)
+// source/common/unicode/utf.h
+
/**
* Is this code point a Unicode noncharacter?
* @param c 32-bit code point
* @return TRUE or FALSE
* @stable ICU 2.4
*/
-#define CBU_IS_UNICODE_NONCHAR(c) \
- ((c) >= 0xfdd0 && ((uint32_t)(c) <= 0xfdef || ((c)&0xfffe) == 0xfffe) && \
- (uint32_t)(c) <= 0x10ffff)
+#define CBU_IS_UNICODE_NONCHAR(c) \
+ ((c)>=0xfdd0 && \
+ ((c)<=0xfdef || ((c)&0xfffe)==0xfffe) && (c)<=0x10ffff)
/**
* Is c a Unicode code point value (0..U+10ffff)
@@ -75,10 +89,9 @@ typedef int8_t UBool;
* @return TRUE or FALSE
* @stable ICU 2.4
*/
-#define CBU_IS_UNICODE_CHAR(c) \
- ((uint32_t)(c) < 0xd800 || \
- ((uint32_t)(c) > 0xdfff && (uint32_t)(c) <= 0x10ffff && \
- !CBU_IS_UNICODE_NONCHAR(c)))
+#define CBU_IS_UNICODE_CHAR(c) \
+ ((uint32_t)(c)<0xd800 || \
+ (0xdfff<(c) && (c)<=0x10ffff && !CBU_IS_UNICODE_NONCHAR(c)))
/**
* Is this code point a surrogate (U+d800..U+dfff)?
@@ -97,24 +110,56 @@ typedef int8_t UBool;
*/
#define CBU_IS_SURROGATE_LEAD(c) (((c)&0x400)==0)
+// source/common/unicode/utf8.h
-// UTF-8 macros ----------------------------------------------------------------
-// from utf8.h
+/**
+ * Internal bit vector for 3-byte UTF-8 validity check, for use in U8_IS_VALID_LEAD3_AND_T1.
+ * Each bit indicates whether one lead byte + first trail byte pair starts a valid sequence.
+ * Lead byte E0..EF bits 3..0 are used as byte index,
+ * first trail byte bits 7..5 are used as bit index into that byte.
+ * @see U8_IS_VALID_LEAD3_AND_T1
+ * @internal
+ */
+#define CBU8_LEAD3_T1_BITS "\x20\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x10\x30\x30"
-extern const uint8_t utf8_countTrailBytes[256];
+/**
+ * Internal 3-byte UTF-8 validity check.
+ * Non-zero if lead byte E0..EF and first trail byte 00..FF start a valid sequence.
+ * @internal
+ */
+#define CBU8_IS_VALID_LEAD3_AND_T1(lead, t1) (CBU8_LEAD3_T1_BITS[(lead)&0xf]&(1<<((uint8_t)(t1)>>5)))
/**
- * Count the trail bytes for a UTF-8 lead byte.
+ * Internal bit vector for 4-byte UTF-8 validity check, for use in U8_IS_VALID_LEAD4_AND_T1.
+ * Each bit indicates whether one lead byte + first trail byte pair starts a valid sequence.
+ * First trail byte bits 7..4 are used as byte index,
+ * lead byte F0..F4 bits 2..0 are used as bit index into that byte.
+ * @see U8_IS_VALID_LEAD4_AND_T1
* @internal
*/
-#define CBU8_COUNT_TRAIL_BYTES(leadByte) \
- (base_icu::utf8_countTrailBytes[(uint8_t)leadByte])
+#define CBU8_LEAD4_T1_BITS "\x00\x00\x00\x00\x00\x00\x00\x00\x1E\x0F\x0F\x0F\x00\x00\x00\x00"
/**
- * Mask a UTF-8 lead byte, leave only the lower bits that form part of the code point value.
+ * Internal 4-byte UTF-8 validity check.
+ * Non-zero if lead byte F0..F4 and first trail byte 00..FF start a valid sequence.
* @internal
*/
-#define CBU8_MASK_LEAD_BYTE(leadByte, countTrailBytes) ((leadByte)&=(1<<(6-(countTrailBytes)))-1)
+#define CBU8_IS_VALID_LEAD4_AND_T1(lead, t1) (CBU8_LEAD4_T1_BITS[(uint8_t)(t1)>>4]&(1<<((lead)&7)))
+
+/**
+ * Function for handling "next code point" with error-checking.
+ *
+ * This is internal since it is not meant to be called directly by external clie
+nts;
+ * however it is U_STABLE (not U_INTERNAL) since it is called by public macros i
+n this
+ * file and thus must remain stable, and should not be hidden when other interna
+l
+ * functions are hidden (otherwise public macros would fail to compile).
+ * @internal
+ */
+UChar32
+utf8_nextCharSafeBody(const uint8_t *s, int32_t *pi, int32_t length, ::base_icu::UChar32 c, ::base_icu::UBool strict);
/**
* Does this code unit (byte) encode a code point by itself (US-ASCII 0..0x7f)?
@@ -125,20 +170,20 @@ extern const uint8_t utf8_countTrailBytes[256];
#define CBU8_IS_SINGLE(c) (((c)&0x80)==0)
/**
- * Is this code unit (byte) a UTF-8 lead byte?
+ * Is this code unit (byte) a UTF-8 lead byte? (0xC2..0xF4)
* @param c 8-bit code unit (byte)
* @return TRUE or FALSE
* @stable ICU 2.4
*/
-#define CBU8_IS_LEAD(c) ((uint8_t)((c)-0xc0) < 0x3e)
+#define CBU8_IS_LEAD(c) ((uint8_t)((c)-0xc2)<=0x32)
/**
- * Is this code unit (byte) a UTF-8 trail byte?
+ * Is this code unit (byte) a UTF-8 trail byte? (0x80..0xBF)
* @param c 8-bit code unit (byte)
* @return TRUE or FALSE
* @stable ICU 2.4
*/
-#define CBU8_IS_TRAIL(c) (((c)&0xc0)==0x80)
+#define CBU8_IS_TRAIL(c) ((int8_t)(c)<-0x40)
/**
* How many code units (bytes) are used for the UTF-8 encoding
@@ -147,16 +192,16 @@ extern const uint8_t utf8_countTrailBytes[256];
* @return 1..4, or 0 if c is a surrogate or not a Unicode code point
* @stable ICU 2.4
*/
-#define CBU8_LENGTH(c) \
- ((uint32_t)(c) <= 0x7f \
- ? 1 \
- : ((uint32_t)(c) <= 0x7ff \
- ? 2 \
- : ((uint32_t)(c) <= 0xd7ff \
- ? 3 \
- : ((uint32_t)(c) <= 0xdfff || (uint32_t)(c) > 0x10ffff \
- ? 0 \
- : ((uint32_t)(c) <= 0xffff ? 3 : 4)))))
+#define CBU8_LENGTH(c) \
+ ((uint32_t)(c)<=0x7f ? 1 : \
+ ((uint32_t)(c)<=0x7ff ? 2 : \
+ ((uint32_t)(c)<=0xd7ff ? 3 : \
+ ((uint32_t)(c)<=0xdfff || (uint32_t)(c)>0x10ffff ? 0 : \
+ ((uint32_t)(c)<=0xffff ? 3 : 4)\
+ ) \
+ ) \
+ ) \
+ )
/**
* The maximum number of UTF-8 code units (bytes) per Unicode code point (U+0000..U+10ffff).
@@ -166,82 +211,82 @@ extern const uint8_t utf8_countTrailBytes[256];
#define CBU8_MAX_LENGTH 4
/**
- * Function for handling "next code point" with error-checking.
- * @internal
- */
-UChar32 utf8_nextCharSafeBody(const uint8_t* s,
- int32_t* pi,
- int32_t length,
- UChar32 c,
- UBool strict);
-
-/**
* Get a code point from a string at a code point boundary offset,
* and advance the offset to the next code point boundary.
* (Post-incrementing forward iteration.)
* "Safe" macro, checks for illegal sequences and for string boundaries.
*
+ * The length can be negative for a NUL-terminated string.
+ *
* The offset may point to the lead byte of a multi-byte sequence,
* in which case the macro will read the whole sequence.
* If the offset points to a trail byte or an illegal UTF-8 sequence, then
* c is set to a negative value.
*
* @param s const uint8_t * string
- * @param i string offset, i<length
- * @param length string length
+ * @param i int32_t string offset, must be i<length
+ * @param length int32_t string length
* @param c output UChar32 variable, set to <0 in case of an error
- * @see CBU8_NEXT_UNSAFE
+ * @see U8_NEXT_UNSAFE
* @stable ICU 2.4
*/
-#define CBU8_NEXT(s, i, length, c) \
- { \
- (c) = (s)[(i)++]; \
- if (((uint8_t)(c)) >= 0x80) { \
- if (CBU8_IS_LEAD(c)) { \
- (c) = base_icu::utf8_nextCharSafeBody((const uint8_t*)s, &(i), \
- (int32_t)(length), c, -1); \
- } else { \
- (c) = CBU_SENTINEL; \
- } \
- } \
- }
+#define CBU8_NEXT(s, i, length, c) { \
+ (c)=(uint8_t)(s)[(i)++]; \
+ if(!CBU8_IS_SINGLE(c)) { \
+ uint8_t __t1, __t2; \
+ if( /* handle U+0800..U+FFFF inline */ \
+ (0xe0<=(c) && (c)<0xf0) && \
+ (((i)+1)<(length) || (length)<0) && \
+ CBU8_IS_VALID_LEAD3_AND_T1((c), __t1=(s)[i]) && \
+ (__t2=(s)[(i)+1]-0x80)<=0x3f) { \
+ (c)=(((c)&0xf)<<12)|((__t1&0x3f)<<6)|__t2; \
+ (i)+=2; \
+ } else if( /* handle U+0080..U+07FF inline */ \
+ ((c)<0xe0 && (c)>=0xc2) && \
+ ((i)!=(length)) && \
+ (__t1=(s)[i]-0x80)<=0x3f) { \
+ (c)=(((c)&0x1f)<<6)|__t1; \
+ ++(i); \
+ } else { \
+ /* function call for "complicated" and error cases */ \
+ (c)=::base_icu::utf8_nextCharSafeBody((const uint8_t *)s, &(i), (length), c, -1); \
+ } \
+ } \
+}
/**
* Append a code point to a string, overwriting 1 to 4 bytes.
* The offset points to the current end of the string contents
* and is advanced (post-increment).
- * "Unsafe" macro, assumes a valid code point and sufficient space in the
- * string.
+ * "Unsafe" macro, assumes a valid code point and sufficient space in the string.
* Otherwise, the result is undefined.
*
* @param s const uint8_t * string buffer
* @param i string offset
* @param c code point to append
- * @see CBU8_APPEND
+ * @see U8_APPEND
* @stable ICU 2.4
*/
-#define CBU8_APPEND_UNSAFE(s, i, c) \
- { \
- if ((uint32_t)(c) <= 0x7f) { \
- (s)[(i)++] = (uint8_t)(c); \
- } else { \
- if ((uint32_t)(c) <= 0x7ff) { \
- (s)[(i)++] = (uint8_t)(((c) >> 6) | 0xc0); \
- } else { \
- if ((uint32_t)(c) <= 0xffff) { \
- (s)[(i)++] = (uint8_t)(((c) >> 12) | 0xe0); \
- } else { \
- (s)[(i)++] = (uint8_t)(((c) >> 18) | 0xf0); \
- (s)[(i)++] = (uint8_t)((((c) >> 12) & 0x3f) | 0x80); \
- } \
- (s)[(i)++] = (uint8_t)((((c) >> 6) & 0x3f) | 0x80); \
- } \
- (s)[(i)++] = (uint8_t)(((c)&0x3f) | 0x80); \
- } \
- }
-
-// UTF-16 macros ---------------------------------------------------------------
-// from utf16.h
+#define CBU8_APPEND_UNSAFE(s, i, c) { \
+ if((uint32_t)(c)<=0x7f) { \
+ (s)[(i)++]=(uint8_t)(c); \
+ } else { \
+ if((uint32_t)(c)<=0x7ff) { \
+ (s)[(i)++]=(uint8_t)(((c)>>6)|0xc0); \
+ } else { \
+ if((uint32_t)(c)<=0xffff) { \
+ (s)[(i)++]=(uint8_t)(((c)>>12)|0xe0); \
+ } else { \
+ (s)[(i)++]=(uint8_t)(((c)>>18)|0xf0); \
+ (s)[(i)++]=(uint8_t)((((c)>>12)&0x3f)|0x80); \
+ } \
+ (s)[(i)++]=(uint8_t)((((c)>>6)&0x3f)|0x80); \
+ } \
+ (s)[(i)++]=(uint8_t)(((c)&0x3f)|0x80); \
+ } \
+}
+
+// source/common/unicode/utf16.h
/**
* Does this code unit alone encode a code point (BMP, not a surrogate)?
@@ -285,7 +330,7 @@ UChar32 utf8_nextCharSafeBody(const uint8_t* s,
#define CBU16_IS_SURROGATE_LEAD(c) (((c)&0x400)==0)
/**
- * Helper constant for CBU16_GET_SUPPLEMENTARY.
+ * Helper constant for U16_GET_SUPPLEMENTARY.
* @internal
*/
#define CBU16_SURROGATE_OFFSET ((0xd800<<10UL)+0xdc00-0x10000)
@@ -302,8 +347,7 @@ UChar32 utf8_nextCharSafeBody(const uint8_t* s,
* @stable ICU 2.4
*/
#define CBU16_GET_SUPPLEMENTARY(lead, trail) \
- (((base_icu::UChar32)(lead)<<10UL)+(base_icu::UChar32)(trail)-CBU16_SURROGATE_OFFSET)
-
+ (((::base_icu::UChar32)(lead)<<10UL)+(::base_icu::UChar32)(trail)-CBU16_SURROGATE_OFFSET)
/**
* Get the lead surrogate (0xd800..0xdbff) for a
@@ -312,8 +356,7 @@ UChar32 utf8_nextCharSafeBody(const uint8_t* s,
* @return lead surrogate (U+d800..U+dbff) for supplementary
* @stable ICU 2.4
*/
-#define CBU16_LEAD(supplementary) \
- (base_icu::UChar)(((supplementary)>>10)+0xd7c0)
+#define CBU16_LEAD(supplementary) (::base_icu::UChar)(((supplementary)>>10)+0xd7c0)
/**
* Get the trail surrogate (0xdc00..0xdfff) for a
@@ -322,8 +365,7 @@ UChar32 utf8_nextCharSafeBody(const uint8_t* s,
* @return trail surrogate (U+dc00..U+dfff) for supplementary
* @stable ICU 2.4
*/
-#define CBU16_TRAIL(supplementary) \
- (base_icu::UChar)(((supplementary)&0x3ff)|0xdc00)
+#define CBU16_TRAIL(supplementary) (::base_icu::UChar)(((supplementary)&0x3ff)|0xdc00)
/**
* How many 16-bit code units are used to encode this Unicode code point? (1 or 2)
@@ -332,7 +374,7 @@ UChar32 utf8_nextCharSafeBody(const uint8_t* s,
* @return 1 or 2
* @stable ICU 2.4
*/
-#define CBU16_LENGTH(c) ((uint32_t)(c) <= 0xffff ? 1 : 2)
+#define CBU16_LENGTH(c) ((uint32_t)(c)<=0xffff ? 1 : 2)
/**
* The maximum number of 16-bit code units per Unicode code point (U+0000..U+10ffff).
@@ -347,30 +389,31 @@ UChar32 utf8_nextCharSafeBody(const uint8_t* s,
* (Post-incrementing forward iteration.)
* "Safe" macro, handles unpaired surrogates and checks for string boundaries.
*
+ * The length can be negative for a NUL-terminated string.
+ *
* The offset may point to the lead surrogate unit
* for a supplementary code point, in which case the macro will read
* the following trail surrogate as well.
* If the offset points to a trail surrogate or
- * to a single, unpaired lead surrogate, then that itself
- * will be returned as the code point.
+ * to a single, unpaired lead surrogate, then c is set to that unpaired surrogate.
*
* @param s const UChar * string
- * @param i string offset, i<length
+ * @param i string offset, must be i<length
* @param length string length
* @param c output UChar32 variable
+ * @see U16_NEXT_UNSAFE
* @stable ICU 2.4
*/
-#define CBU16_NEXT(s, i, length, c) \
- { \
- (c) = (s)[(i)++]; \
- if (CBU16_IS_LEAD(c)) { \
- uint16_t __c2; \
- if ((i) < (length) && CBU16_IS_TRAIL(__c2 = (s)[(i)])) { \
- ++(i); \
- (c) = CBU16_GET_SUPPLEMENTARY((c), __c2); \
- } \
- } \
- }
+#define CBU16_NEXT(s, i, length, c) { \
+ (c)=(s)[(i)++]; \
+ if(CBU16_IS_LEAD(c)) { \
+ uint16_t __c2; \
+ if((i)!=(length) && CBU16_IS_TRAIL(__c2=(s)[(i)])) { \
+ ++(i); \
+ (c)=CBU16_GET_SUPPLEMENTARY((c), __c2); \
+ } \
+ } \
+}
/**
* Append a code point to a string, overwriting 1 or 2 code units.
@@ -382,18 +425,17 @@ UChar32 utf8_nextCharSafeBody(const uint8_t* s,
* @param s const UChar * string buffer
* @param i string offset
* @param c code point to append
- * @see CBU16_APPEND
+ * @see U16_APPEND
* @stable ICU 2.4
*/
-#define CBU16_APPEND_UNSAFE(s, i, c) \
- { \
- if ((uint32_t)(c) <= 0xffff) { \
- (s)[(i)++] = (uint16_t)(c); \
- } else { \
- (s)[(i)++] = (uint16_t)(((c) >> 10) + 0xd7c0); \
- (s)[(i)++] = (uint16_t)(((c)&0x3ff) | 0xdc00); \
- } \
- }
+#define CBU16_APPEND_UNSAFE(s, i, c) { \
+ if((uint32_t)(c)<=0xffff) { \
+ (s)[(i)++]=(uint16_t)(c); \
+ } else { \
+ (s)[(i)++]=(uint16_t)(((c)>>10)+0xd7c0); \
+ (s)[(i)++]=(uint16_t)(((c)&0x3ff)|0xdc00); \
+ } \
+}
} // namesapce base_icu
diff --git a/base/thread_annotations.h b/base/thread_annotations.h
new file mode 100644
index 0000000000..ba7168b13f
--- /dev/null
+++ b/base/thread_annotations.h
@@ -0,0 +1,238 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This header file contains macro definitions for thread safety annotations
+// that allow developers to document the locking policies of multi-threaded
+// code. The annotations can also help program analysis tools to identify
+// potential thread safety issues.
+//
+// Note that the annotations we use are described as deprecated in the Clang
+// documentation, linked below. E.g. we use EXCLUSIVE_LOCKS_REQUIRED where the
+// Clang docs use REQUIRES.
+//
+// http://clang.llvm.org/docs/ThreadSafetyAnalysis.html
+//
+// We use the deprecated Clang annotations to match Abseil (relevant header
+// linked below) and its ecosystem of libraries. We will follow Abseil with
+// respect to upgrading to more modern annotations.
+//
+// https://github.com/abseil/abseil-cpp/blob/master/absl/base/thread_annotations.h
+//
+// These annotations are implemented using compiler attributes. Using the macros
+// defined here instead of raw attributes allow for portability and future
+// compatibility.
+//
+// When referring to mutexes in the arguments of the attributes, you should
+// use variable names or more complex expressions (e.g. my_object->mutex_)
+// that evaluate to a concrete mutex object whenever possible. If the mutex
+// you want to refer to is not in scope, you may use a member pointer
+// (e.g. &MyClass::mutex_) to refer to a mutex in some (unknown) object.
+
+#ifndef THREAD_ANNOTATIONS_H_
+#define THREAD_ANNOTATIONS_H_
+
+#if defined(__clang__)
+#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
+#else
+#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op
+#endif
+
+// GUARDED_BY()
+//
+// Documents if a shared field or global variable needs to be protected by a
+// mutex. GUARDED_BY() allows the user to specify a particular mutex that
+// should be held when accessing the annotated variable.
+//
+// Example:
+//
+// Mutex mu;
+// int p1 GUARDED_BY(mu);
+#define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
+
+// PT_GUARDED_BY()
+//
+// Documents if the memory location pointed to by a pointer should be guarded
+// by a mutex when dereferencing the pointer.
+//
+// Example:
+// Mutex mu;
+// int *p1 PT_GUARDED_BY(mu);
+//
+// Note that a pointer variable to a shared memory location could itself be a
+// shared variable.
+//
+// Example:
+//
+// // `q`, guarded by `mu1`, points to a shared memory location that is
+// // guarded by `mu2`:
+// int *q GUARDED_BY(mu1) PT_GUARDED_BY(mu2);
+#define PT_GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))
+
+// ACQUIRED_AFTER() / ACQUIRED_BEFORE()
+//
+// Documents the acquisition order between locks that can be held
+// simultaneously by a thread. For any two locks that need to be annotated
+// to establish an acquisition order, only one of them needs the annotation.
+// (i.e. You don't have to annotate both locks with both ACQUIRED_AFTER
+// and ACQUIRED_BEFORE.)
+//
+// Example:
+//
+// Mutex m1;
+// Mutex m2 ACQUIRED_AFTER(m1);
+#define ACQUIRED_AFTER(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))
+
+#define ACQUIRED_BEFORE(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))
+
+// EXCLUSIVE_LOCKS_REQUIRED() / SHARED_LOCKS_REQUIRED()
+//
+// Documents a function that expects a mutex to be held prior to entry.
+// The mutex is expected to be held both on entry to, and exit from, the
+// function.
+//
+// Example:
+//
+// Mutex mu1, mu2;
+// int a GUARDED_BY(mu1);
+// int b GUARDED_BY(mu2);
+//
+// void foo() EXCLUSIVE_LOCKS_REQUIRED(mu1, mu2) { ... };
+#define EXCLUSIVE_LOCKS_REQUIRED(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(exclusive_locks_required(__VA_ARGS__))
+
+#define SHARED_LOCKS_REQUIRED(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(shared_locks_required(__VA_ARGS__))
+
+// LOCKS_EXCLUDED()
+//
+// Documents the locks acquired in the body of the function. These locks
+// cannot be held when calling this function (as Abseil's `Mutex` locks are
+// non-reentrant).
+#define LOCKS_EXCLUDED(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))
+
+// LOCK_RETURNED()
+//
+// Documents a function that returns a mutex without acquiring it. For example,
+// a public getter method that returns a pointer to a private mutex should
+// be annotated with LOCK_RETURNED.
+#define LOCK_RETURNED(x) THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))
+
+// LOCKABLE
+//
+// Documents if a class/type is a lockable type (such as the `Mutex` class).
+#define LOCKABLE THREAD_ANNOTATION_ATTRIBUTE__(lockable)
+
+// SCOPED_LOCKABLE
+//
+// Documents if a class does RAII locking (such as the `MutexLock` class).
+// The constructor should use `LOCK_FUNCTION()` to specify the mutex that is
+// acquired, and the destructor should use `UNLOCK_FUNCTION()` with no
+// arguments; the analysis will assume that the destructor unlocks whatever the
+// constructor locked.
+#define SCOPED_LOCKABLE THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
+
+// EXCLUSIVE_LOCK_FUNCTION()
+//
+// Documents functions that acquire a lock in the body of a function, and do
+// not release it.
+#define EXCLUSIVE_LOCK_FUNCTION(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(exclusive_lock_function(__VA_ARGS__))
+
+// SHARED_LOCK_FUNCTION()
+//
+// Documents functions that acquire a shared (reader) lock in the body of a
+// function, and do not release it.
+#define SHARED_LOCK_FUNCTION(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(shared_lock_function(__VA_ARGS__))
+
+// UNLOCK_FUNCTION()
+//
+// Documents functions that expect a lock to be held on entry to the function,
+// and release it in the body of the function.
+#define UNLOCK_FUNCTION(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(unlock_function(__VA_ARGS__))
+
+// EXCLUSIVE_TRYLOCK_FUNCTION() / SHARED_TRYLOCK_FUNCTION()
+//
+// Documents functions that try to acquire a lock, and return success or failure
+// (or a non-boolean value that can be interpreted as a boolean).
+// The first argument should be `true` for functions that return `true` on
+// success, or `false` for functions that return `false` on success. The second
+// argument specifies the mutex that is locked on success. If unspecified, this
+// mutex is assumed to be `this`.
+#define EXCLUSIVE_TRYLOCK_FUNCTION(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(exclusive_trylock_function(__VA_ARGS__))
+
+#define SHARED_TRYLOCK_FUNCTION(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(shared_trylock_function(__VA_ARGS__))
+
+// ASSERT_EXCLUSIVE_LOCK() / ASSERT_SHARED_LOCK()
+//
+// Documents functions that dynamically check to see if a lock is held, and fail
+// if it is not held.
+#define ASSERT_EXCLUSIVE_LOCK(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(assert_exclusive_lock(__VA_ARGS__))
+
+#define ASSERT_SHARED_LOCK(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_lock(__VA_ARGS__))
+
+// NO_THREAD_SAFETY_ANALYSIS
+//
+// Turns off thread safety checking within the body of a particular function.
+// This annotation is used to mark functions that are known to be correct, but
+// the locking behavior is more complicated than the analyzer can handle.
+#define NO_THREAD_SAFETY_ANALYSIS \
+ THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
+
+//------------------------------------------------------------------------------
+// Tool-Supplied Annotations
+//------------------------------------------------------------------------------
+
+// TS_UNCHECKED should be placed around lock expressions that are not valid
+// C++ syntax, but which are present for documentation purposes. These
+// annotations will be ignored by the analysis.
+#define TS_UNCHECKED(x) ""
+
+// TS_FIXME is used to mark lock expressions that are not valid C++ syntax.
+// It is used by automated tools to mark and disable invalid expressions.
+// The annotation should either be fixed, or changed to TS_UNCHECKED.
+#define TS_FIXME(x) ""
+
+// Like NO_THREAD_SAFETY_ANALYSIS, this turns off checking within the body of
+// a particular function. However, this attribute is used to mark functions
+// that are incorrect and need to be fixed. It is used by automated tools to
+// avoid breaking the build when the analysis is updated.
+// Code owners are expected to eventually fix the routine.
+#define NO_THREAD_SAFETY_ANALYSIS_FIXME NO_THREAD_SAFETY_ANALYSIS
+
+// Similar to NO_THREAD_SAFETY_ANALYSIS_FIXME, this macro marks a GUARDED_BY
+// annotation that needs to be fixed, because it is producing thread safety
+// warning. It disables the GUARDED_BY.
+#define GUARDED_BY_FIXME(x)
+
+// Disables warnings for a single read operation. This can be used to avoid
+// warnings when it is known that the read is not actually involved in a race,
+// but the compiler cannot confirm that.
+#define TS_UNCHECKED_READ(x) thread_safety_analysis::ts_unchecked_read(x)
+
+namespace thread_safety_analysis {
+
+// Takes a reference to a guarded data member, and returns an unguarded
+// reference.
+template <typename T>
+inline const T& ts_unchecked_read(const T& v) NO_THREAD_SAFETY_ANALYSIS {
+ return v;
+}
+
+template <typename T>
+inline T& ts_unchecked_read(T& v) NO_THREAD_SAFETY_ANALYSIS {
+ return v;
+}
+
+} // namespace thread_safety_analysis
+
+#endif // _BASE_THREAD_ANNOTATIONS_H_
diff --git a/base/thread_annotations_unittest.cc b/base/thread_annotations_unittest.cc
new file mode 100644
index 0000000000..b4aafef72a
--- /dev/null
+++ b/base/thread_annotations_unittest.cc
@@ -0,0 +1,58 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "thread_annotations.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class LOCKABLE Lock {
+ public:
+ void Acquire() EXCLUSIVE_LOCK_FUNCTION() {}
+ void Release() UNLOCK_FUNCTION() {}
+};
+
+class SCOPED_LOCKABLE AutoLock {
+ public:
+ AutoLock(Lock& lock) EXCLUSIVE_LOCK_FUNCTION(lock) : lock_(lock) {
+ lock.Acquire();
+ }
+ ~AutoLock() UNLOCK_FUNCTION() { lock_.Release(); }
+
+ private:
+ Lock& lock_;
+};
+
+class ThreadSafe {
+ public:
+ void ExplicitIncrement();
+ void ImplicitIncrement();
+
+ private:
+ Lock lock_;
+ int counter_ GUARDED_BY(lock_);
+};
+
+void ThreadSafe::ExplicitIncrement() {
+ lock_.Acquire();
+ ++counter_;
+ lock_.Release();
+}
+
+void ThreadSafe::ImplicitIncrement() {
+ AutoLock auto_lock(lock_);
+ counter_++;
+}
+
+TEST(ThreadAnnotationsTest, ExplicitIncrement) {
+ ThreadSafe thread_safe;
+ thread_safe.ExplicitIncrement();
+}
+TEST(ThreadAnnotationsTest, ImplicitIncrement) {
+ ThreadSafe thread_safe;
+ thread_safe.ImplicitIncrement();
+}
+
+} // anonymous namespace
diff --git a/base/thread_annotations_unittest.nc b/base/thread_annotations_unittest.nc
new file mode 100644
index 0000000000..ea64a7e0d5
--- /dev/null
+++ b/base/thread_annotations_unittest.nc
@@ -0,0 +1,71 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This is a "No Compile Test" suite.
+// https://dev.chromium.org/developers/testing/no-compile-tests
+
+#include "base/thread_annotations.h"
+
+namespace {
+
+class LOCKABLE Lock {
+ public:
+ void Acquire() EXCLUSIVE_LOCK_FUNCTION() {}
+ void Release() UNLOCK_FUNCTION() {}
+};
+
+class SCOPED_LOCKABLE AutoLock {
+ public:
+ AutoLock(Lock& lock) EXCLUSIVE_LOCK_FUNCTION(lock) : lock_(lock) {
+ lock.Acquire();
+ }
+ ~AutoLock() UNLOCK_FUNCTION() { lock_.Release(); }
+
+ private:
+ Lock& lock_;
+};
+class ThreadSafe {
+ public:
+ void BuggyIncrement();
+ private:
+ Lock lock_;
+ int counter_ GUARDED_BY(lock_);
+};
+
+#if defined(NCTEST_LOCK_WITHOUT_UNLOCK) // [r"fatal error: mutex 'lock_' is still held at the end of function"]
+
+void ThreadSafe::BuggyIncrement() {
+ lock_.Acquire();
+ ++counter_;
+ // Forgot to release the lock.
+}
+
+#elif defined(NCTEST_ACCESS_WITHOUT_LOCK) // [r"fatal error: writing variable 'counter_' requires holding mutex 'lock_' exclusively"]
+
+void ThreadSafe::BuggyIncrement() {
+ // Member access without holding the lock guarding it.
+ ++counter_;
+}
+
+#elif defined(NCTEST_ACCESS_WITHOUT_SCOPED_LOCK) // [r"fatal error: writing variable 'counter_' requires holding mutex 'lock_' exclusively"]
+
+void ThreadSafe::BuggyIncrement() {
+ {
+ AutoLock auto_lock(lock_);
+ // The AutoLock will go out of scope before the guarded member access.
+ }
+ ++counter_;
+}
+
+#elif defined(NCTEST_GUARDED_BY_WRONG_TYPE) // [r"fatal error: 'guarded_by' attribute requires arguments whose type is annotated"]
+
+int not_lockable;
+int global_counter GUARDED_BY(not_lockable);
+
+// Defined to avoid link error.
+void ThreadSafe::BuggyIncrement() { }
+
+#endif
+
+} // anonymous namespace
diff --git a/base/threading/non_thread_safe.h b/base/threading/non_thread_safe.h
deleted file mode 100644
index 64ae8e4da9..0000000000
--- a/base/threading/non_thread_safe.h
+++ /dev/null
@@ -1,62 +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_THREADING_NON_THREAD_SAFE_H_
-#define BASE_THREADING_NON_THREAD_SAFE_H_
-
-// Classes deriving from NonThreadSafe may need to suppress MSVC warning 4275:
-// non dll-interface class 'Bar' used as base for dll-interface class 'Foo'.
-// There is a specific macro to do it: NON_EXPORTED_BASE(), defined in
-// compiler_specific.h
-#include "base/compiler_specific.h"
-#include "base/logging.h"
-#include "base/threading/non_thread_safe_impl.h"
-
-namespace base {
-
-// Do nothing implementation of NonThreadSafe, for release mode.
-//
-// Note: You should almost always use the NonThreadSafe class to get
-// the right version of the class for your build configuration.
-class NonThreadSafeDoNothing {
- public:
- bool CalledOnValidThread() const {
- return true;
- }
-
- protected:
- ~NonThreadSafeDoNothing() {}
- void DetachFromThread() {}
-};
-
-// NonThreadSafe is a helper class used to help verify that methods of a
-// class are called from the same thread. One can inherit from this class
-// and use CalledOnValidThread() to verify.
-//
-// This is intended to be used with classes that appear to be thread safe, but
-// aren't. For example, a service or a singleton like the preferences system.
-//
-// Example:
-// class MyClass : public base::NonThreadSafe {
-// public:
-// void Foo() {
-// DCHECK(CalledOnValidThread());
-// ... (do stuff) ...
-// }
-// }
-//
-// Note that base::ThreadChecker offers identical functionality to
-// NonThreadSafe, but does not require inheritance. In general, it is preferable
-// to have a base::ThreadChecker as a member, rather than inherit from
-// NonThreadSafe. For more details about when to choose one over the other, see
-// the documentation for base::ThreadChecker.
-#if DCHECK_IS_ON()
-typedef NonThreadSafeImpl NonThreadSafe;
-#else
-typedef NonThreadSafeDoNothing NonThreadSafe;
-#endif // DCHECK_IS_ON()
-
-} // namespace base
-
-#endif // BASE_THREADING_NON_THREAD_SAFE_H_
diff --git a/base/threading/non_thread_safe_impl.cc b/base/threading/non_thread_safe_impl.cc
deleted file mode 100644
index 7e729d9ee4..0000000000
--- a/base/threading/non_thread_safe_impl.cc
+++ /dev/null
@@ -1,23 +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/threading/non_thread_safe_impl.h"
-
-#include "base/logging.h"
-
-namespace base {
-
-bool NonThreadSafeImpl::CalledOnValidThread() const {
- return thread_checker_.CalledOnValidThread();
-}
-
-NonThreadSafeImpl::~NonThreadSafeImpl() {
- DCHECK(CalledOnValidThread());
-}
-
-void NonThreadSafeImpl::DetachFromThread() {
- thread_checker_.DetachFromThread();
-}
-
-} // namespace base
diff --git a/base/threading/non_thread_safe_impl.h b/base/threading/non_thread_safe_impl.h
deleted file mode 100644
index a3a356df4a..0000000000
--- a/base/threading/non_thread_safe_impl.h
+++ /dev/null
@@ -1,39 +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_THREADING_NON_THREAD_SAFE_IMPL_H_
-#define BASE_THREADING_NON_THREAD_SAFE_IMPL_H_
-
-#include "base/base_export.h"
-#include "base/threading/thread_checker_impl.h"
-
-namespace base {
-
-// Full implementation of NonThreadSafe, for debug mode or for occasional
-// temporary use in release mode e.g. when you need to CHECK on a thread
-// bug that only occurs in the wild.
-//
-// Note: You should almost always use the NonThreadSafe class to get
-// the right version of the class for your build configuration.
-class BASE_EXPORT NonThreadSafeImpl {
- public:
- bool CalledOnValidThread() const;
-
- protected:
- ~NonThreadSafeImpl();
-
- // Changes the thread that is checked for in CalledOnValidThread. The next
- // call to CalledOnValidThread will attach this class to a new thread. It is
- // up to the NonThreadSafe derived class to decide to expose this or not.
- // This may be useful when an object may be created on one thread and then
- // used exclusively on another thread.
- void DetachFromThread();
-
- private:
- ThreadCheckerImpl thread_checker_;
-};
-
-} // namespace base
-
-#endif // BASE_THREADING_NON_THREAD_SAFE_IMPL_H_
diff --git a/base/threading/non_thread_safe_unittest.cc b/base/threading/non_thread_safe_unittest.cc
deleted file mode 100644
index 7776228f2f..0000000000
--- a/base/threading/non_thread_safe_unittest.cc
+++ /dev/null
@@ -1,148 +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/threading/non_thread_safe.h"
-
-#include <memory>
-
-#include "base/logging.h"
-#include "base/macros.h"
-#include "base/test/gtest_util.h"
-#include "base/threading/simple_thread.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace base {
-
-namespace {
-
-// Simple class to exersice the basics of NonThreadSafe.
-// Both the destructor and DoStuff should verify that they were
-// called on the same thread as the constructor.
-class NonThreadSafeClass : public NonThreadSafe {
- public:
- NonThreadSafeClass() {}
-
- // Verifies that it was called on the same thread as the constructor.
- void DoStuff() {
- DCHECK(CalledOnValidThread());
- }
-
- void DetachFromThread() {
- NonThreadSafe::DetachFromThread();
- }
-
- static void MethodOnDifferentThreadImpl();
- static void DestructorOnDifferentThreadImpl();
-
- private:
- DISALLOW_COPY_AND_ASSIGN(NonThreadSafeClass);
-};
-
-// Calls NonThreadSafeClass::DoStuff on another thread.
-class CallDoStuffOnThread : public SimpleThread {
- public:
- explicit CallDoStuffOnThread(NonThreadSafeClass* non_thread_safe_class)
- : SimpleThread("call_do_stuff_on_thread"),
- non_thread_safe_class_(non_thread_safe_class) {
- }
-
- void Run() override { non_thread_safe_class_->DoStuff(); }
-
- private:
- NonThreadSafeClass* non_thread_safe_class_;
-
- DISALLOW_COPY_AND_ASSIGN(CallDoStuffOnThread);
-};
-
-// Deletes NonThreadSafeClass on a different thread.
-class DeleteNonThreadSafeClassOnThread : public SimpleThread {
- public:
- explicit DeleteNonThreadSafeClassOnThread(
- NonThreadSafeClass* non_thread_safe_class)
- : SimpleThread("delete_non_thread_safe_class_on_thread"),
- non_thread_safe_class_(non_thread_safe_class) {
- }
-
- void Run() override { non_thread_safe_class_.reset(); }
-
- private:
- std::unique_ptr<NonThreadSafeClass> non_thread_safe_class_;
-
- DISALLOW_COPY_AND_ASSIGN(DeleteNonThreadSafeClassOnThread);
-};
-
-} // namespace
-
-TEST(NonThreadSafeTest, CallsAllowedOnSameThread) {
- std::unique_ptr<NonThreadSafeClass> non_thread_safe_class(
- new NonThreadSafeClass);
-
- // Verify that DoStuff doesn't assert.
- non_thread_safe_class->DoStuff();
-
- // Verify that the destructor doesn't assert.
- non_thread_safe_class.reset();
-}
-
-TEST(NonThreadSafeTest, DetachThenDestructOnDifferentThread) {
- std::unique_ptr<NonThreadSafeClass> non_thread_safe_class(
- new NonThreadSafeClass);
-
- // Verify that the destructor doesn't assert when called on a different thread
- // after a detach.
- non_thread_safe_class->DetachFromThread();
- DeleteNonThreadSafeClassOnThread delete_on_thread(
- non_thread_safe_class.release());
-
- delete_on_thread.Start();
- delete_on_thread.Join();
-}
-
-void NonThreadSafeClass::MethodOnDifferentThreadImpl() {
- std::unique_ptr<NonThreadSafeClass> non_thread_safe_class(
- new NonThreadSafeClass);
-
- // Verify that DoStuff asserts in debug builds only when called
- // on a different thread.
- CallDoStuffOnThread call_on_thread(non_thread_safe_class.get());
-
- call_on_thread.Start();
- call_on_thread.Join();
-}
-
-#if DCHECK_IS_ON()
-TEST(NonThreadSafeDeathTest, MethodNotAllowedOnDifferentThreadInDebug) {
- ASSERT_DCHECK_DEATH({ NonThreadSafeClass::MethodOnDifferentThreadImpl(); });
-}
-#else
-TEST(NonThreadSafeTest, MethodAllowedOnDifferentThreadInRelease) {
- NonThreadSafeClass::MethodOnDifferentThreadImpl();
-}
-#endif // DCHECK_IS_ON()
-
-void NonThreadSafeClass::DestructorOnDifferentThreadImpl() {
- std::unique_ptr<NonThreadSafeClass> non_thread_safe_class(
- new NonThreadSafeClass);
-
- // Verify that the destructor asserts in debug builds only
- // when called on a different thread.
- DeleteNonThreadSafeClassOnThread delete_on_thread(
- non_thread_safe_class.release());
-
- delete_on_thread.Start();
- delete_on_thread.Join();
-}
-
-#if DCHECK_IS_ON()
-TEST(NonThreadSafeDeathTest, DestructorNotAllowedOnDifferentThreadInDebug) {
- ASSERT_DCHECK_DEATH(
- { NonThreadSafeClass::DestructorOnDifferentThreadImpl(); });
-}
-#else
-TEST(NonThreadSafeTest, DestructorAllowedOnDifferentThreadInRelease) {
- NonThreadSafeClass::DestructorOnDifferentThreadImpl();
-}
-#endif // DCHECK_IS_ON()
-
-} // namespace base
diff --git a/base/threading/platform_thread.h b/base/threading/platform_thread.h
index 8c0d8e4432..faeb858b7c 100644
--- a/base/threading/platform_thread.h
+++ b/base/threading/platform_thread.h
@@ -17,7 +17,9 @@
#include "build/build_config.h"
#if defined(OS_WIN)
-#include <windows.h>
+#include "base/win/windows_types.h"
+#elif defined(OS_FUCHSIA)
+#include <zircon/types.h>
#elif defined(OS_MACOSX)
#include <mach/mach_types.h>
#elif defined(OS_POSIX)
@@ -30,6 +32,8 @@ namespace base {
// Used for logging. Always an integer value.
#if defined(OS_WIN)
typedef DWORD PlatformThreadId;
+#elif defined(OS_FUCHSIA)
+typedef zx_handle_t PlatformThreadId;
#elif defined(OS_MACOSX)
typedef mach_port_t PlatformThreadId;
#elif defined(OS_POSIX)
@@ -48,16 +52,12 @@ class PlatformThreadRef {
public:
#if defined(OS_WIN)
typedef DWORD RefType;
-#elif defined(OS_POSIX)
+#else // OS_POSIX
typedef pthread_t RefType;
#endif
- PlatformThreadRef()
- : id_(0) {
- }
+ constexpr PlatformThreadRef() : id_(0) {}
- explicit PlatformThreadRef(RefType id)
- : id_(id) {
- }
+ explicit constexpr PlatformThreadRef(RefType id) : id_(id) {}
bool operator==(PlatformThreadRef other) const {
return id_ == other.id_;
@@ -77,13 +77,13 @@ class PlatformThreadHandle {
public:
#if defined(OS_WIN)
typedef void* Handle;
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
typedef pthread_t Handle;
#endif
- PlatformThreadHandle() : handle_(0) {}
+ constexpr PlatformThreadHandle() : handle_(0) {}
- explicit PlatformThreadHandle(Handle handle) : handle_(handle) {}
+ explicit constexpr PlatformThreadHandle(Handle handle) : handle_(handle) {}
bool is_equal(const PlatformThreadHandle& other) const {
return handle_ == other.handle_;
@@ -126,7 +126,7 @@ class BASE_EXPORT PlatformThread {
virtual void ThreadMain() = 0;
protected:
- virtual ~Delegate() {}
+ virtual ~Delegate() = default;
};
// Gets the current thread id, which may be useful for logging purposes.
@@ -200,10 +200,16 @@ class BASE_EXPORT PlatformThread {
// priority of the current thread.
static bool CanIncreaseCurrentThreadPriority();
- // Toggles the current thread's priority at runtime. A thread may not be able
- // to raise its priority back up after lowering it if the process does not
- // have a proper permission, e.g. CAP_SYS_NICE on Linux. A thread may not be
- // able to lower its priority back down after raising it to REALTIME_AUDIO.
+ // Toggles the current thread's priority at runtime.
+ //
+ // A thread may not be able to raise its priority back up after lowering it if
+ // the process does not have a proper permission, e.g. CAP_SYS_NICE on Linux.
+ // A thread may not be able to lower its priority back down after raising it
+ // to REALTIME_AUDIO.
+ //
+ // This function must not be called from the main thread on Mac. This is to
+ // avoid performance regressions (https://crbug.com/601270).
+ //
// Since changing other threads' priority is not permitted in favor of
// security, this interface is restricted to change only the current thread
// priority (https://crbug.com/399473).
diff --git a/base/threading/platform_thread_linux.cc b/base/threading/platform_thread_linux.cc
index 92fbda5ee1..190acedf7c 100644
--- a/base/threading/platform_thread_linux.cc
+++ b/base/threading/platform_thread_linux.cc
@@ -14,10 +14,9 @@
#include "base/strings/string_number_conversions.h"
#include "base/threading/platform_thread_internal_posix.h"
#include "base/threading/thread_id_name_manager.h"
-#include "base/tracked_objects.h"
#include "build/build_config.h"
-#if !defined(OS_NACL)
+#if !defined(OS_NACL) && !defined(OS_AIX)
#include <pthread.h>
#include <sys/prctl.h>
#include <sys/resource.h>
@@ -127,10 +126,9 @@ bool GetCurrentThreadPriorityForPlatform(ThreadPriority* priority) {
// static
void PlatformThread::SetName(const std::string& name) {
- ThreadIdNameManager::GetInstance()->SetName(CurrentId(), name);
- tracked_objects::ThreadData::InitializeThreadContext(name);
+ ThreadIdNameManager::GetInstance()->SetName(name);
-#if !defined(OS_NACL)
+#if !defined(OS_NACL) && !defined(OS_AIX)
// On linux we can get the thread names to show up in the debugger by setting
// the process name for the LWP. We don't want to do this for the main
// thread because that would rename the process, causing tools like killall
@@ -147,10 +145,10 @@ void PlatformThread::SetName(const std::string& name) {
// We expect EPERM failures in sandboxed processes, just ignore those.
if (err < 0 && errno != EPERM)
DPLOG(ERROR) << "prctl(PR_SET_NAME)";
-#endif // !defined(OS_NACL)
+#endif // !defined(OS_NACL) && !defined(OS_AIX)
}
-#if !defined(OS_NACL)
+#if !defined(OS_NACL) && !defined(OS_AIX)
// static
void PlatformThread::SetThreadPriority(PlatformThreadId thread_id,
ThreadPriority priority) {
@@ -167,7 +165,7 @@ void PlatformThread::SetThreadPriority(PlatformThreadId thread_id,
<< nice_setting;
}
}
-#endif // !defined(OS_NACL)
+#endif // !defined(OS_NACL) && !defined(OS_AIX)
void InitThreading() {}
diff --git a/base/threading/platform_thread_posix.cc b/base/threading/platform_thread_posix.cc
index 9a6a2bb999..2466b784d1 100644
--- a/base/threading/platform_thread_posix.cc
+++ b/base/threading/platform_thread_posix.cc
@@ -9,7 +9,6 @@
#include <sched.h>
#include <stddef.h>
#include <stdint.h>
-#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
@@ -28,6 +27,12 @@
#include <sys/syscall.h>
#endif
+#if defined(OS_FUCHSIA)
+#include <zircon/process.h>
+#else
+#include <sys/resource.h>
+#endif
+
namespace base {
void InitThreading();
@@ -38,7 +43,7 @@ namespace {
struct ThreadParams {
ThreadParams()
- : delegate(NULL), joinable(false), priority(ThreadPriority::NORMAL) {}
+ : delegate(nullptr), joinable(false), priority(ThreadPriority::NORMAL) {}
PlatformThread::Delegate* delegate;
bool joinable;
@@ -75,7 +80,7 @@ void* ThreadFunc(void* params) {
PlatformThread::CurrentId());
base::TerminateOnThread();
- return NULL;
+ return nullptr;
}
bool CreateThread(size_t stack_size,
@@ -137,6 +142,8 @@ PlatformThreadId PlatformThread::CurrentId() {
return syscall(__NR_gettid);
#elif defined(OS_ANDROID)
return gettid();
+#elif defined(OS_FUCHSIA)
+ return zx_thread_self();
#elif defined(OS_SOLARIS) || defined(OS_QNX)
return pthread_self();
#elif defined(OS_NACL) && defined(__GLIBC__)
@@ -144,7 +151,9 @@ PlatformThreadId PlatformThread::CurrentId() {
#elif defined(OS_NACL) && !defined(__GLIBC__)
// Pointers are 32-bits in NaCl.
return reinterpret_cast<int32_t>(pthread_self());
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) && defined(OS_AIX)
+ return pthread_self();
+#elif defined(OS_POSIX) && !defined(OS_AIX)
return reinterpret_cast<int64_t>(pthread_self());
#endif
}
@@ -217,8 +226,8 @@ void PlatformThread::Join(PlatformThreadHandle thread_handle) {
// Joining another thread may block the current thread for a long time, since
// the thread referred to by |thread_handle| may still be running long-lived /
// blocking tasks.
- base::ThreadRestrictions::AssertIOAllowed();
- CHECK_EQ(0, pthread_join(thread_handle.platform_handle(), NULL));
+ AssertBlockingAllowed();
+ CHECK_EQ(0, pthread_join(thread_handle.platform_handle(), nullptr));
}
// static
@@ -226,8 +235,9 @@ void PlatformThread::Detach(PlatformThreadHandle thread_handle) {
CHECK_EQ(0, pthread_detach(thread_handle.platform_handle()));
}
-// Mac has its own Set/GetCurrentThreadPriority() implementations.
-#if !defined(OS_MACOSX)
+// Mac and Fuchsia have their own Set/GetCurrentThreadPriority()
+// implementations.
+#if !defined(OS_MACOSX) && !defined(OS_FUCHSIA)
// static
bool PlatformThread::CanIncreaseCurrentThreadPriority() {
@@ -290,6 +300,6 @@ ThreadPriority PlatformThread::GetCurrentThreadPriority() {
#endif // !defined(OS_NACL)
}
-#endif // !defined(OS_MACOSX)
+#endif // !defined(OS_MACOSX) && !defined(OS_FUCHSIA)
} // namespace base
diff --git a/base/threading/platform_thread_unittest.cc b/base/threading/platform_thread_unittest.cc
index 0febf8ba9b..10c45bccf1 100644
--- a/base/threading/platform_thread_unittest.cc
+++ b/base/threading/platform_thread_unittest.cc
@@ -259,14 +259,24 @@ class ThreadPriorityTestThread : public FunctionTestThread {
// Test changing a created thread's priority (which has different semantics on
// some platforms).
-TEST(PlatformThreadTest, ThreadPriorityCurrentThread) {
+#if defined(OS_FUCHSIA)
+// TODO(crbug.com/851759): Thread priorities are not implemented in Fuchsia.
+#define MAYBE_ThreadPriorityCurrentThread DISABLED_ThreadPriorityCurrentThread
+#else
+#define MAYBE_ThreadPriorityCurrentThread ThreadPriorityCurrentThread
+#endif
+TEST(PlatformThreadTest, MAYBE_ThreadPriorityCurrentThread) {
const bool increase_priority_allowed =
PlatformThread::CanIncreaseCurrentThreadPriority();
- if (increase_priority_allowed) {
- // Bump the priority in order to verify that new threads are started with
- // normal priority.
+
+// Bump the priority in order to verify that new threads are started with normal
+// priority. Skip this on Mac since this platform doesn't allow changing the
+// priority of the main thread. Also skip this on platforms that don't allow
+// increasing the priority of a thread.
+#if !defined(OS_MACOSX)
+ if (increase_priority_allowed)
PlatformThread::SetCurrentThreadPriority(ThreadPriority::DISPLAY);
- }
+#endif
// Toggle each supported priority on the thread and confirm it affects it.
for (size_t i = 0; i < arraysize(kThreadPriorityTestValues); ++i) {
@@ -290,10 +300,10 @@ TEST(PlatformThreadTest, ThreadPriorityCurrentThread) {
}
}
-// Test for a function defined in platform_thread_internal_posix.cc. On OSX and
-// iOS, platform_thread_internal_posix.cc is not compiled, so these platforms
-// are excluded here, too.
-#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS)
+// This tests internal PlatformThread APIs used under some POSIX platforms,
+// with the exception of Mac OS X, iOS and Fuchsia.
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS) && \
+ !defined(OS_FUCHSIA)
TEST(PlatformThreadTest, GetNiceValueToThreadPriority) {
using internal::NiceValueToThreadPriority;
using internal::kThreadPriorityToNiceValueMap;
@@ -348,6 +358,16 @@ TEST(PlatformThreadTest, GetNiceValueToThreadPriority) {
EXPECT_EQ(ThreadPriority::REALTIME_AUDIO,
NiceValueToThreadPriority(kLowestNiceValue));
}
-#endif
+#endif // defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS) &&
+ // !defined(OS_FUCHSIA)
+
+TEST(PlatformThreadTest, SetHugeThreadName) {
+ // Construct an excessively long thread name.
+ std::string long_name(1024, 'a');
+
+ // SetName has no return code, so just verify that implementations
+ // don't [D]CHECK().
+ PlatformThread::SetName(long_name);
+}
} // namespace base
diff --git a/base/threading/post_task_and_reply_impl.cc b/base/threading/post_task_and_reply_impl.cc
index cddb8981ad..5aacdada67 100644
--- a/base/threading/post_task_and_reply_impl.cc
+++ b/base/threading/post_task_and_reply_impl.cc
@@ -10,7 +10,6 @@
#include "base/debug/leak_annotations.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
-#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
#include "base/threading/sequenced_task_runner_handle.h"
@@ -18,83 +17,109 @@ namespace base {
namespace {
-// This relay class remembers the sequence that it was created on, and ensures
-// that both the |task| and |reply| Closures are deleted on this same sequence.
-// Also, |task| is guaranteed to be deleted before |reply| is run or deleted.
-//
-// If RunReplyAndSelfDestruct() doesn't run because the originating execution
-// context is no longer available, then the |task| and |reply| Closures are
-// leaked. Leaking is considered preferable to having a thread-safetey
-// violations caused by invoking the Closure destructor on the wrong sequence.
class PostTaskAndReplyRelay {
public:
- PostTaskAndReplyRelay(const tracked_objects::Location& from_here,
+ PostTaskAndReplyRelay(const Location& from_here,
OnceClosure task,
OnceClosure reply)
- : sequence_checker_(),
- from_here_(from_here),
- origin_task_runner_(SequencedTaskRunnerHandle::Get()),
- reply_(std::move(reply)),
- task_(std::move(task)) {}
+ : from_here_(from_here),
+ task_(std::move(task)),
+ reply_(std::move(reply)) {}
+ PostTaskAndReplyRelay(PostTaskAndReplyRelay&&) = default;
~PostTaskAndReplyRelay() {
- DCHECK(sequence_checker_.CalledOnValidSequence());
+ if (reply_) {
+ // This can run:
+ // 1) On origin sequence, when:
+ // 1a) Posting |task_| fails.
+ // 1b) |reply_| is cancelled before running.
+ // 1c) The DeleteSoon() below is scheduled.
+ // 2) On destination sequence, when:
+ // 2a) |task_| is cancelled before running.
+ // 2b) Posting |reply_| fails.
+
+ if (!reply_task_runner_->RunsTasksInCurrentSequence()) {
+ // Case 2a) or 2b).
+ //
+ // Destroy callbacks asynchronously on |reply_task_runner| since their
+ // destructors can rightfully be affine to it. As always, DeleteSoon()
+ // might leak its argument if the target execution environment is
+ // shutdown (e.g. MessageLoop deleted, TaskScheduler shutdown).
+ //
+ // Note: while it's obvious why |reply_| can be affine to
+ // |reply_task_runner|, the reason that |task_| can also be affine to it
+ // is that it if neither tasks ran, |task_| may still hold an object
+ // which was intended to be moved to |reply_| when |task_| ran (such an
+ // object's destruction can be affine to |reply_task_runner_| -- e.g.
+ // https://crbug.com/829122).
+ auto relay_to_delete =
+ std::make_unique<PostTaskAndReplyRelay>(std::move(*this));
+ ANNOTATE_LEAKING_OBJECT_PTR(relay_to_delete.get());
+ reply_task_runner_->DeleteSoon(from_here_, std::move(relay_to_delete));
+ }
+
+ // Case 1a), 1b), 1c).
+ //
+ // Callbacks will be destroyed synchronously at the end of this scope.
+ } else {
+ // This can run when both callbacks have run or have been moved to another
+ // PostTaskAndReplyRelay instance. If |reply_| is null, |task_| must be
+ // null too.
+ DCHECK(!task_);
+ }
}
- void RunTaskAndPostReply() {
- std::move(task_).Run();
- origin_task_runner_->PostTask(
- from_here_, Bind(&PostTaskAndReplyRelay::RunReplyAndSelfDestruct,
- base::Unretained(this)));
- }
+ // No assignment operator because of const members.
+ PostTaskAndReplyRelay& operator=(PostTaskAndReplyRelay&&) = delete;
- private:
- void RunReplyAndSelfDestruct() {
- DCHECK(sequence_checker_.CalledOnValidSequence());
+ // Static function is used because it is not possible to bind a method call to
+ // a non-pointer type.
+ static void RunTaskAndPostReply(PostTaskAndReplyRelay relay) {
+ DCHECK(relay.task_);
+ std::move(relay.task_).Run();
- // Ensure |task_| has already been released before |reply_| to ensure that
- // no one accidentally depends on |task_| keeping one of its arguments alive
- // while |reply_| is executing.
- DCHECK(!task_);
+ // Keep a reference to the reply TaskRunner for the PostTask() call before
+ // |relay| is moved into a callback.
+ scoped_refptr<SequencedTaskRunner> reply_task_runner =
+ relay.reply_task_runner_;
- std::move(reply_).Run();
+ reply_task_runner->PostTask(
+ relay.from_here_,
+ BindOnce(&PostTaskAndReplyRelay::RunReply, std::move(relay)));
+ }
- // Cue mission impossible theme.
- delete this;
+ private:
+ // Static function is used because it is not possible to bind a method call to
+ // a non-pointer type.
+ static void RunReply(PostTaskAndReplyRelay relay) {
+ DCHECK(!relay.task_);
+ DCHECK(relay.reply_);
+ std::move(relay.reply_).Run();
}
- const SequenceChecker sequence_checker_;
- const tracked_objects::Location from_here_;
- const scoped_refptr<SequencedTaskRunner> origin_task_runner_;
- OnceClosure reply_;
+ const Location from_here_;
OnceClosure task_;
+ OnceClosure reply_;
+ const scoped_refptr<SequencedTaskRunner> reply_task_runner_ =
+ SequencedTaskRunnerHandle::Get();
+
+ DISALLOW_COPY_AND_ASSIGN(PostTaskAndReplyRelay);
};
} // namespace
namespace internal {
-bool PostTaskAndReplyImpl::PostTaskAndReply(
- const tracked_objects::Location& from_here,
- OnceClosure task,
- OnceClosure reply) {
- DCHECK(!task.is_null()) << from_here.ToString();
- DCHECK(!reply.is_null()) << from_here.ToString();
- PostTaskAndReplyRelay* relay =
- new PostTaskAndReplyRelay(from_here, std::move(task), std::move(reply));
- // PostTaskAndReplyRelay self-destructs after executing |reply|. On the flip
- // side though, it is intentionally leaked if the |task| doesn't complete
- // before the origin sequence stops executing tasks. Annotate |relay| as leaky
- // to avoid having to suppress every callsite which happens to flakily trigger
- // this race.
- ANNOTATE_LEAKING_OBJECT_PTR(relay);
- if (!PostTask(from_here, Bind(&PostTaskAndReplyRelay::RunTaskAndPostReply,
- Unretained(relay)))) {
- delete relay;
- return false;
- }
+bool PostTaskAndReplyImpl::PostTaskAndReply(const Location& from_here,
+ OnceClosure task,
+ OnceClosure reply) {
+ DCHECK(task) << from_here.ToString();
+ DCHECK(reply) << from_here.ToString();
- return true;
+ return PostTask(from_here,
+ BindOnce(&PostTaskAndReplyRelay::RunTaskAndPostReply,
+ PostTaskAndReplyRelay(from_here, std::move(task),
+ std::move(reply))));
}
} // namespace internal
diff --git a/base/threading/post_task_and_reply_impl.h b/base/threading/post_task_and_reply_impl.h
index 00aee6d0ed..54038ceecd 100644
--- a/base/threading/post_task_and_reply_impl.h
+++ b/base/threading/post_task_and_reply_impl.h
@@ -2,8 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// This file contains the implementation shared by
-// TaskRunner::PostTaskAndReply and WorkerPool::PostTaskAndReply.
+// This file contains the implementation for TaskRunner::PostTaskAndReply.
#ifndef BASE_THREADING_POST_TASK_AND_REPLY_IMPL_H_
#define BASE_THREADING_POST_TASK_AND_REPLY_IMPL_H_
@@ -19,22 +18,26 @@ namespace internal {
// custom execution context.
//
// If you're looking for a concrete implementation of PostTaskAndReply, you
-// probably want base::TaskRunner, or you may want base::WorkerPool.
+// probably want base::TaskRunner or base/task_scheduler/post_task.h
class BASE_EXPORT PostTaskAndReplyImpl {
public:
virtual ~PostTaskAndReplyImpl() = default;
- // Posts |task| by calling PostTask(). On completion, |reply| is posted to the
- // sequence or thread that called this. Can only be called when
- // SequencedTaskRunnerHandle::IsSet(). Both |task| and |reply| are guaranteed
- // to be deleted on the sequence or thread that called this.
- bool PostTaskAndReply(const tracked_objects::Location& from_here,
+ // Posts |task| by calling PostTask(). On completion, posts |reply| to the
+ // origin sequence. Can only be called when
+ // SequencedTaskRunnerHandle::IsSet(). Each callback is deleted synchronously
+ // after running, or scheduled for asynchronous deletion on the origin
+ // sequence if it can't run (e.g. if a TaskRunner skips it on shutdown). See
+ // SequencedTaskRunner::DeleteSoon() for when objects scheduled for
+ // asynchronous deletion can be leaked. Note: All //base task posting APIs
+ // require callbacks to support deletion on the posting sequence if they can't
+ // be scheduled.
+ bool PostTaskAndReply(const Location& from_here,
OnceClosure task,
OnceClosure reply);
private:
- virtual bool PostTask(const tracked_objects::Location& from_here,
- OnceClosure task) = 0;
+ virtual bool PostTask(const Location& from_here, OnceClosure task) = 0;
};
} // namespace internal
diff --git a/base/threading/scoped_blocking_call.cc b/base/threading/scoped_blocking_call.cc
new file mode 100644
index 0000000000..1d2931cb49
--- /dev/null
+++ b/base/threading/scoped_blocking_call.cc
@@ -0,0 +1,72 @@
+// 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/threading/scoped_blocking_call.h"
+
+#include "base/lazy_instance.h"
+#include "base/threading/thread_local.h"
+
+namespace base {
+
+namespace {
+
+LazyInstance<ThreadLocalPointer<internal::BlockingObserver>>::Leaky
+ tls_blocking_observer = LAZY_INSTANCE_INITIALIZER;
+
+// Last ScopedBlockingCall instantiated on this thread.
+LazyInstance<ThreadLocalPointer<ScopedBlockingCall>>::Leaky
+ tls_last_scoped_blocking_call = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+ScopedBlockingCall::ScopedBlockingCall(BlockingType blocking_type)
+ : blocking_observer_(tls_blocking_observer.Get().Get()),
+ previous_scoped_blocking_call_(tls_last_scoped_blocking_call.Get().Get()),
+ is_will_block_(blocking_type == BlockingType::WILL_BLOCK ||
+ (previous_scoped_blocking_call_ &&
+ previous_scoped_blocking_call_->is_will_block_)) {
+ tls_last_scoped_blocking_call.Get().Set(this);
+
+ if (blocking_observer_) {
+ if (!previous_scoped_blocking_call_) {
+ blocking_observer_->BlockingStarted(blocking_type);
+ } else if (blocking_type == BlockingType::WILL_BLOCK &&
+ !previous_scoped_blocking_call_->is_will_block_) {
+ blocking_observer_->BlockingTypeUpgraded();
+ }
+ }
+}
+
+ScopedBlockingCall::~ScopedBlockingCall() {
+ DCHECK_EQ(this, tls_last_scoped_blocking_call.Get().Get());
+ tls_last_scoped_blocking_call.Get().Set(previous_scoped_blocking_call_);
+ if (blocking_observer_ && !previous_scoped_blocking_call_)
+ blocking_observer_->BlockingEnded();
+}
+
+namespace internal {
+
+void SetBlockingObserverForCurrentThread(BlockingObserver* blocking_observer) {
+ DCHECK(!tls_blocking_observer.Get().Get());
+ tls_blocking_observer.Get().Set(blocking_observer);
+}
+
+void ClearBlockingObserverForTesting() {
+ tls_blocking_observer.Get().Set(nullptr);
+}
+
+ScopedClearBlockingObserverForTesting::ScopedClearBlockingObserverForTesting()
+ : blocking_observer_(tls_blocking_observer.Get().Get()) {
+ tls_blocking_observer.Get().Set(nullptr);
+}
+
+ScopedClearBlockingObserverForTesting::
+ ~ScopedClearBlockingObserverForTesting() {
+ DCHECK(!tls_blocking_observer.Get().Get());
+ tls_blocking_observer.Get().Set(blocking_observer_);
+}
+
+} // namespace internal
+
+} // namespace base
diff --git a/base/threading/scoped_blocking_call.h b/base/threading/scoped_blocking_call.h
new file mode 100644
index 0000000000..e376c308c5
--- /dev/null
+++ b/base/threading/scoped_blocking_call.h
@@ -0,0 +1,140 @@
+// 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_THREADING_SCOPED_BLOCKING_CALL_H
+#define BASE_THREADING_SCOPED_BLOCKING_CALL_H
+
+#include "base/base_export.h"
+#include "base/logging.h"
+
+namespace base {
+
+// BlockingType indicates the likelihood that a blocking call will actually
+// block.
+enum class BlockingType {
+ // The call might block (e.g. file I/O that might hit in memory cache).
+ MAY_BLOCK,
+ // The call will definitely block (e.g. cache already checked and now pinging
+ // server synchronously).
+ WILL_BLOCK
+};
+
+namespace internal {
+class BlockingObserver;
+}
+
+// This class must be instantiated in every scope where a blocking call is made.
+// CPU usage should be minimal within that scope. //base APIs that block
+// instantiate their own ScopedBlockingCall; it is not necessary to instantiate
+// another ScopedBlockingCall in the scope where these APIs are used.
+//
+// Good:
+// Data data;
+// {
+// ScopedBlockingCall scoped_blocking_call(BlockingType::WILL_BLOCK);
+// data = GetDataFromNetwork();
+// }
+// CPUIntensiveProcessing(data);
+//
+// Bad:
+// ScopedBlockingCall scoped_blocking_call(BlockingType::WILL_BLOCK);
+// Data data = GetDataFromNetwork();
+// CPUIntensiveProcessing(data); // CPU usage within a ScopedBlockingCall.
+//
+// Good:
+// Data a;
+// Data b;
+// {
+// ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
+// a = GetDataFromMemoryCacheOrNetwork();
+// b = GetDataFromMemoryCacheOrNetwork();
+// }
+// CPUIntensiveProcessing(a);
+// CPUIntensiveProcessing(b);
+//
+// Bad:
+// ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
+// Data a = GetDataFromMemoryCacheOrNetwork();
+// Data b = GetDataFromMemoryCacheOrNetwork();
+// CPUIntensiveProcessing(a); // CPU usage within a ScopedBlockingCall.
+// CPUIntensiveProcessing(b); // CPU usage within a ScopedBlockingCall.
+//
+// Good:
+// base::WaitableEvent waitable_event(...);
+// waitable_event.Wait();
+//
+// Bad:
+// base::WaitableEvent waitable_event(...);
+// ScopedBlockingCall scoped_blocking_call(BlockingType::WILL_BLOCK);
+// waitable_event.Wait(); // Wait() instantiates its own ScopedBlockingCall.
+//
+// When a ScopedBlockingCall is instantiated from a TaskScheduler parallel or
+// sequenced task, the thread pool size is incremented to compensate for the
+// blocked thread (more or less aggressively depending on BlockingType).
+class BASE_EXPORT ScopedBlockingCall {
+ public:
+ ScopedBlockingCall(BlockingType blocking_type);
+ ~ScopedBlockingCall();
+
+ private:
+ internal::BlockingObserver* const blocking_observer_;
+
+ // Previous ScopedBlockingCall instantiated on this thread.
+ ScopedBlockingCall* const previous_scoped_blocking_call_;
+
+ // Whether the BlockingType of the current thread was WILL_BLOCK after this
+ // ScopedBlockingCall was instantiated.
+ const bool is_will_block_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedBlockingCall);
+};
+
+namespace internal {
+
+// Interface for an observer to be informed when a thread enters or exits
+// the scope of ScopedBlockingCall objects.
+class BASE_EXPORT BlockingObserver {
+ public:
+ virtual ~BlockingObserver() = default;
+
+ // Invoked when a ScopedBlockingCall is instantiated on the observed thread
+ // where there wasn't an existing ScopedBlockingCall.
+ virtual void BlockingStarted(BlockingType blocking_type) = 0;
+
+ // Invoked when a WILL_BLOCK ScopedBlockingCall is instantiated on the
+ // observed thread where there was a MAY_BLOCK ScopedBlockingCall but not a
+ // WILL_BLOCK ScopedBlockingCall.
+ virtual void BlockingTypeUpgraded() = 0;
+
+ // Invoked when the last ScopedBlockingCall on the observed thread is
+ // destroyed.
+ virtual void BlockingEnded() = 0;
+};
+
+// Registers |blocking_observer| on the current thread. It is invalid to call
+// this on a thread where there is an active ScopedBlockingCall.
+BASE_EXPORT void SetBlockingObserverForCurrentThread(
+ BlockingObserver* blocking_observer);
+
+BASE_EXPORT void ClearBlockingObserverForTesting();
+
+// Unregisters the |blocking_observer| on the current thread within its scope.
+// Used in TaskScheduler tests to prevent calls to //base sync primitives from
+// affecting the thread pool capacity.
+class BASE_EXPORT ScopedClearBlockingObserverForTesting {
+ public:
+ ScopedClearBlockingObserverForTesting();
+ ~ScopedClearBlockingObserverForTesting();
+
+ private:
+ BlockingObserver* const blocking_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedClearBlockingObserverForTesting);
+};
+
+} // namespace internal
+
+} // namespace base
+
+#endif // BASE_THREADING_SCOPED_BLOCKING_CALL_H
diff --git a/base/threading/scoped_blocking_call_unittest.cc b/base/threading/scoped_blocking_call_unittest.cc
new file mode 100644
index 0000000000..5e030f3518
--- /dev/null
+++ b/base/threading/scoped_blocking_call_unittest.cc
@@ -0,0 +1,134 @@
+// 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/threading/scoped_blocking_call.h"
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/test/gtest_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+class MockBlockingObserver : public internal::BlockingObserver {
+ public:
+ MockBlockingObserver() = default;
+
+ MOCK_METHOD1(BlockingStarted, void(BlockingType));
+ MOCK_METHOD0(BlockingTypeUpgraded, void());
+ MOCK_METHOD0(BlockingEnded, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockBlockingObserver);
+};
+
+class ScopedBlockingCallTest : public testing::Test {
+ protected:
+ ScopedBlockingCallTest() {
+ internal::SetBlockingObserverForCurrentThread(&observer_);
+ }
+
+ ~ScopedBlockingCallTest() override {
+ internal::ClearBlockingObserverForTesting();
+ }
+
+ testing::StrictMock<MockBlockingObserver> observer_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedBlockingCallTest);
+};
+
+} // namespace
+
+TEST_F(ScopedBlockingCallTest, MayBlock) {
+ EXPECT_CALL(observer_, BlockingStarted(BlockingType::MAY_BLOCK));
+ ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
+ testing::Mock::VerifyAndClear(&observer_);
+ EXPECT_CALL(observer_, BlockingEnded());
+}
+
+TEST_F(ScopedBlockingCallTest, WillBlock) {
+ EXPECT_CALL(observer_, BlockingStarted(BlockingType::WILL_BLOCK));
+ ScopedBlockingCall scoped_blocking_call(BlockingType::WILL_BLOCK);
+ testing::Mock::VerifyAndClear(&observer_);
+ EXPECT_CALL(observer_, BlockingEnded());
+}
+
+TEST_F(ScopedBlockingCallTest, MayBlockWillBlock) {
+ EXPECT_CALL(observer_, BlockingStarted(BlockingType::MAY_BLOCK));
+ ScopedBlockingCall scoped_blocking_call_a(BlockingType::MAY_BLOCK);
+ testing::Mock::VerifyAndClear(&observer_);
+
+ {
+ EXPECT_CALL(observer_, BlockingTypeUpgraded());
+ ScopedBlockingCall scoped_blocking_call_b(BlockingType::WILL_BLOCK);
+ testing::Mock::VerifyAndClear(&observer_);
+ }
+
+ EXPECT_CALL(observer_, BlockingEnded());
+}
+
+TEST_F(ScopedBlockingCallTest, WillBlockMayBlock) {
+ EXPECT_CALL(observer_, BlockingStarted(BlockingType::WILL_BLOCK));
+ ScopedBlockingCall scoped_blocking_call_a(BlockingType::WILL_BLOCK);
+ testing::Mock::VerifyAndClear(&observer_);
+
+ { ScopedBlockingCall scoped_blocking_call_b(BlockingType::MAY_BLOCK); }
+
+ EXPECT_CALL(observer_, BlockingEnded());
+}
+
+TEST_F(ScopedBlockingCallTest, MayBlockMayBlock) {
+ EXPECT_CALL(observer_, BlockingStarted(BlockingType::MAY_BLOCK));
+ ScopedBlockingCall scoped_blocking_call_a(BlockingType::MAY_BLOCK);
+ testing::Mock::VerifyAndClear(&observer_);
+
+ { ScopedBlockingCall scoped_blocking_call_b(BlockingType::MAY_BLOCK); }
+
+ EXPECT_CALL(observer_, BlockingEnded());
+}
+
+TEST_F(ScopedBlockingCallTest, WillBlockWillBlock) {
+ EXPECT_CALL(observer_, BlockingStarted(BlockingType::WILL_BLOCK));
+ ScopedBlockingCall scoped_blocking_call_a(BlockingType::WILL_BLOCK);
+ testing::Mock::VerifyAndClear(&observer_);
+
+ { ScopedBlockingCall scoped_blocking_call_b(BlockingType::WILL_BLOCK); }
+
+ EXPECT_CALL(observer_, BlockingEnded());
+}
+
+TEST_F(ScopedBlockingCallTest, MayBlockWillBlockTwice) {
+ EXPECT_CALL(observer_, BlockingStarted(BlockingType::MAY_BLOCK));
+ ScopedBlockingCall scoped_blocking_call_a(BlockingType::MAY_BLOCK);
+ testing::Mock::VerifyAndClear(&observer_);
+
+ {
+ EXPECT_CALL(observer_, BlockingTypeUpgraded());
+ ScopedBlockingCall scoped_blocking_call_b(BlockingType::WILL_BLOCK);
+ testing::Mock::VerifyAndClear(&observer_);
+
+ {
+ ScopedBlockingCall scoped_blocking_call_c(BlockingType::MAY_BLOCK);
+ ScopedBlockingCall scoped_blocking_call_d(BlockingType::WILL_BLOCK);
+ }
+ }
+
+ EXPECT_CALL(observer_, BlockingEnded());
+}
+
+TEST(ScopedBlockingCallDestructionOrderTest, InvalidDestructionOrder) {
+ auto scoped_blocking_call_a =
+ std::make_unique<ScopedBlockingCall>(BlockingType::WILL_BLOCK);
+ auto scoped_blocking_call_b =
+ std::make_unique<ScopedBlockingCall>(BlockingType::WILL_BLOCK);
+
+ EXPECT_DCHECK_DEATH({ scoped_blocking_call_a.reset(); });
+}
+
+} // namespace base
diff --git a/base/threading/sequence_local_storage_map.cc b/base/threading/sequence_local_storage_map.cc
new file mode 100644
index 0000000000..2837aa0340
--- /dev/null
+++ b/base/threading/sequence_local_storage_map.cc
@@ -0,0 +1,105 @@
+// 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/threading/sequence_local_storage_map.h"
+
+#include <utility>
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/threading/thread_local.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+LazyInstance<ThreadLocalPointer<SequenceLocalStorageMap>>::Leaky
+ tls_current_sequence_local_storage = LAZY_INSTANCE_INITIALIZER;
+} // namespace
+
+SequenceLocalStorageMap::SequenceLocalStorageMap() = default;
+
+SequenceLocalStorageMap::~SequenceLocalStorageMap() = default;
+
+ScopedSetSequenceLocalStorageMapForCurrentThread::
+ ScopedSetSequenceLocalStorageMapForCurrentThread(
+ SequenceLocalStorageMap* sequence_local_storage) {
+ DCHECK(!tls_current_sequence_local_storage.Get().Get());
+ tls_current_sequence_local_storage.Get().Set(sequence_local_storage);
+}
+
+ScopedSetSequenceLocalStorageMapForCurrentThread::
+ ~ScopedSetSequenceLocalStorageMapForCurrentThread() {
+ tls_current_sequence_local_storage.Get().Set(nullptr);
+}
+
+SequenceLocalStorageMap& SequenceLocalStorageMap::GetForCurrentThread() {
+ SequenceLocalStorageMap* current_sequence_local_storage =
+ tls_current_sequence_local_storage.Get().Get();
+
+ DCHECK(current_sequence_local_storage)
+ << "SequenceLocalStorageSlot cannot be used because no "
+ "SequenceLocalStorageMap was stored in TLS. Use "
+ "ScopedSetSequenceLocalStorageMapForCurrentThread to store a "
+ "SequenceLocalStorageMap object in TLS.";
+
+ return *current_sequence_local_storage;
+}
+
+void* SequenceLocalStorageMap::Get(int slot_id) {
+ const auto it = sls_map_.find(slot_id);
+ if (it == sls_map_.end())
+ return nullptr;
+ return it->second.value();
+}
+
+void SequenceLocalStorageMap::Set(
+ int slot_id,
+ SequenceLocalStorageMap::ValueDestructorPair value_destructor_pair) {
+ auto it = sls_map_.find(slot_id);
+
+ if (it == sls_map_.end())
+ sls_map_.emplace(slot_id, std::move(value_destructor_pair));
+ else
+ it->second = std::move(value_destructor_pair);
+
+ // The maximum number of entries in the map is 256. This can be adjusted, but
+ // will require reviewing the choice of data structure for the map.
+ DCHECK_LE(sls_map_.size(), 256U);
+}
+
+SequenceLocalStorageMap::ValueDestructorPair::ValueDestructorPair(
+ void* value,
+ DestructorFunc* destructor)
+ : value_(value), destructor_(destructor) {}
+
+SequenceLocalStorageMap::ValueDestructorPair::~ValueDestructorPair() {
+ if (value_)
+ destructor_(value_);
+}
+
+SequenceLocalStorageMap::ValueDestructorPair::ValueDestructorPair(
+ ValueDestructorPair&& value_destructor_pair)
+ : value_(value_destructor_pair.value_),
+ destructor_(value_destructor_pair.destructor_) {
+ value_destructor_pair.value_ = nullptr;
+}
+
+SequenceLocalStorageMap::ValueDestructorPair&
+SequenceLocalStorageMap::ValueDestructorPair::operator=(
+ ValueDestructorPair&& value_destructor_pair) {
+ // Destroy |value_| before overwriting it with a new value.
+ if (value_)
+ destructor_(value_);
+
+ value_ = value_destructor_pair.value_;
+ destructor_ = value_destructor_pair.destructor_;
+
+ value_destructor_pair.value_ = nullptr;
+
+ return *this;
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/threading/sequence_local_storage_map.h b/base/threading/sequence_local_storage_map.h
new file mode 100644
index 0000000000..8b9155c5b2
--- /dev/null
+++ b/base/threading/sequence_local_storage_map.h
@@ -0,0 +1,90 @@
+// 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_THREADING_SEQUENCE_LOCAL_STORAGE_MAP_H_
+#define BASE_THREADING_SEQUENCE_LOCAL_STORAGE_MAP_H_
+
+#include "base/base_export.h"
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+
+namespace base {
+namespace internal {
+
+// A SequenceLocalStorageMap holds (slot_id) -> (value, destructor) items for a
+// sequence. When a task runs, it is expected that a pointer to its sequence's
+// SequenceLocalStorageMap is set in TLS using
+// ScopedSetSequenceMapLocalStorageForCurrentThread. When a
+// SequenceLocalStorageMap is destroyed, it invokes the destructors associated
+// with values stored within it.
+// The Get() and Set() methods should not be accessed directly.
+// Use SequenceLocalStorageSlot to Get() and Set() values in the current
+// sequence's SequenceLocalStorageMap.
+class BASE_EXPORT SequenceLocalStorageMap {
+ public:
+ SequenceLocalStorageMap();
+ ~SequenceLocalStorageMap();
+
+ // Returns the SequenceLocalStorage bound to the current thread. It is invalid
+ // to call this outside the scope of a
+ // ScopedSetSequenceLocalStorageForCurrentThread.
+ static SequenceLocalStorageMap& GetForCurrentThread();
+
+ // Holds a pointer to a value alongside a destructor for this pointer.
+ // Calls the destructor on the value upon destruction.
+ class BASE_EXPORT ValueDestructorPair {
+ public:
+ using DestructorFunc = void(void*);
+
+ ValueDestructorPair(void* value, DestructorFunc* destructor);
+ ~ValueDestructorPair();
+
+ ValueDestructorPair(ValueDestructorPair&& value_destructor_pair);
+
+ ValueDestructorPair& operator=(ValueDestructorPair&& value_destructor_pair);
+
+ void* value() const { return value_; }
+
+ private:
+ void* value_;
+ DestructorFunc* destructor_;
+
+ DISALLOW_COPY_AND_ASSIGN(ValueDestructorPair);
+ };
+
+ // Returns the value stored in |slot_id| or nullptr if no value was stored.
+ void* Get(int slot_id);
+
+ // Stores |value_destructor_pair| in |slot_id|. Overwrites and destroys any
+ // previously stored value.
+ void Set(int slot_id, ValueDestructorPair value_destructor_pair);
+
+ private:
+ // Map from slot id to ValueDestructorPair.
+ // flat_map was chosen because there are expected to be relatively few entries
+ // in the map. For low number of entries, flat_map is known to perform better
+ // than other map implementations.
+ base::flat_map<int, ValueDestructorPair> sls_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(SequenceLocalStorageMap);
+};
+
+// Within the scope of this object,
+// SequenceLocalStorageMap::GetForCurrentThread() will return a reference to the
+// SequenceLocalStorageMap object passed to the constructor. There can be only
+// one ScopedSetSequenceLocalStorageMapForCurrentThread instance per scope.
+class BASE_EXPORT ScopedSetSequenceLocalStorageMapForCurrentThread {
+ public:
+ ScopedSetSequenceLocalStorageMapForCurrentThread(
+ SequenceLocalStorageMap* sequence_local_storage);
+
+ ~ScopedSetSequenceLocalStorageMapForCurrentThread();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedSetSequenceLocalStorageMapForCurrentThread);
+};
+} // namespace internal
+} // namespace base
+
+#endif // BASE_THREADING_SEQUENCE_LOCAL_STORAGE_MAP_H_
diff --git a/base/threading/sequence_local_storage_map_unittest.cc b/base/threading/sequence_local_storage_map_unittest.cc
new file mode 100644
index 0000000000..a45bbc3454
--- /dev/null
+++ b/base/threading/sequence_local_storage_map_unittest.cc
@@ -0,0 +1,117 @@
+// 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/threading/sequence_local_storage_map.h"
+
+#include <memory>
+#include <utility>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+
+constexpr int kSlotId = 1;
+
+class SetOnDestroy {
+ public:
+ SetOnDestroy(bool* was_destroyed_ptr)
+ : was_destroyed_ptr_(was_destroyed_ptr) {
+ DCHECK(was_destroyed_ptr_);
+ DCHECK(!(*was_destroyed_ptr_));
+ }
+ ~SetOnDestroy() {
+ DCHECK(!(*was_destroyed_ptr_));
+ *was_destroyed_ptr_ = true;
+ }
+
+ private:
+ bool* const was_destroyed_ptr_;
+
+ DISALLOW_COPY_AND_ASSIGN(SetOnDestroy);
+};
+
+template <typename T, typename... Args>
+SequenceLocalStorageMap::ValueDestructorPair CreateValueDestructorPair(
+ Args... args) {
+ T* value = new T(args...);
+ SequenceLocalStorageMap::ValueDestructorPair::DestructorFunc* destructor =
+ [](void* ptr) { std::default_delete<T>()(static_cast<T*>(ptr)); };
+
+ SequenceLocalStorageMap::ValueDestructorPair value_destructor_pair{
+ value, destructor};
+
+ return value_destructor_pair;
+}
+
+} // namespace
+
+// Verify that setting a value in the SequenceLocalStorageMap, then getting
+// it will yield the same value.
+TEST(SequenceLocalStorageMapTest, SetGet) {
+ SequenceLocalStorageMap sequence_local_storage_map;
+ ScopedSetSequenceLocalStorageMapForCurrentThread
+ scoped_sequence_local_storage_map(&sequence_local_storage_map);
+
+ SequenceLocalStorageMap::ValueDestructorPair value_destructor_pair =
+ CreateValueDestructorPair<int>(5);
+
+ sequence_local_storage_map.Set(kSlotId, std::move(value_destructor_pair));
+
+ EXPECT_EQ(*static_cast<int*>(sequence_local_storage_map.Get(kSlotId)), 5);
+}
+
+// Verify that the destructor is called on a value stored in the
+// SequenceLocalStorageMap when SequenceLocalStorageMap is destroyed.
+TEST(SequenceLocalStorageMapTest, Destructor) {
+ bool set_on_destruction = false;
+
+ {
+ SequenceLocalStorageMap sequence_local_storage_map;
+ ScopedSetSequenceLocalStorageMapForCurrentThread
+ scoped_sequence_local_storage_map(&sequence_local_storage_map);
+
+ SequenceLocalStorageMap::ValueDestructorPair value_destructor_pair =
+ CreateValueDestructorPair<SetOnDestroy>(&set_on_destruction);
+
+ sequence_local_storage_map.Set(kSlotId, std::move(value_destructor_pair));
+ }
+
+ EXPECT_TRUE(set_on_destruction);
+}
+
+// Verify that overwriting a value already in the SequenceLocalStorageMap
+// calls value's destructor.
+TEST(SequenceLocalStorageMapTest, DestructorCalledOnSetOverwrite) {
+ bool set_on_destruction = false;
+ bool set_on_destruction2 = false;
+ {
+ SequenceLocalStorageMap sequence_local_storage_map;
+ ScopedSetSequenceLocalStorageMapForCurrentThread
+ scoped_sequence_local_storage_map(&sequence_local_storage_map);
+
+ SequenceLocalStorageMap::ValueDestructorPair value_destructor_pair =
+ CreateValueDestructorPair<SetOnDestroy>(&set_on_destruction);
+ SequenceLocalStorageMap::ValueDestructorPair value_destructor_pair2 =
+ CreateValueDestructorPair<SetOnDestroy>(&set_on_destruction2);
+
+ sequence_local_storage_map.Set(kSlotId, std::move(value_destructor_pair));
+
+ ASSERT_FALSE(set_on_destruction);
+
+ // Overwrites the old value in the slot.
+ sequence_local_storage_map.Set(kSlotId, std::move(value_destructor_pair2));
+
+ // Destructor should've been called for the old value in the slot, and not
+ // yet called for the new value.
+ EXPECT_TRUE(set_on_destruction);
+ EXPECT_FALSE(set_on_destruction2);
+ }
+ EXPECT_TRUE(set_on_destruction2);
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/threading/sequence_local_storage_slot.cc b/base/threading/sequence_local_storage_slot.cc
new file mode 100644
index 0000000000..b7db40bb4f
--- /dev/null
+++ b/base/threading/sequence_local_storage_slot.cc
@@ -0,0 +1,26 @@
+// 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/threading/sequence_local_storage_slot.h"
+
+#include <limits>
+
+#include "base/atomic_sequence_num.h"
+#include "base/logging.h"
+
+namespace base {
+namespace internal {
+
+namespace {
+AtomicSequenceNumber g_sequence_local_storage_slot_generator;
+} // namespace
+
+int GetNextSequenceLocalStorageSlotNumber() {
+ int slot_id = g_sequence_local_storage_slot_generator.GetNext();
+ DCHECK_LT(slot_id, std::numeric_limits<int>::max());
+ return slot_id;
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/threading/sequence_local_storage_slot.h b/base/threading/sequence_local_storage_slot.h
new file mode 100644
index 0000000000..315df7dbe1
--- /dev/null
+++ b/base/threading/sequence_local_storage_slot.h
@@ -0,0 +1,105 @@
+// 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_THREADING_SEQUENCE_LOCAL_STORAGE_SLOT_H_
+#define BASE_THREADING_SEQUENCE_LOCAL_STORAGE_SLOT_H_
+
+#include <memory>
+#include <utility>
+
+#include "base/base_export.h"
+#include "base/threading/sequence_local_storage_map.h"
+
+namespace base {
+
+namespace internal {
+BASE_EXPORT int GetNextSequenceLocalStorageSlotNumber();
+}
+
+// SequenceLocalStorageSlot allows arbitrary values to be stored and retrieved
+// from a sequence. Values are deleted when the sequence is deleted.
+//
+// Example usage:
+//
+// namespace {
+// base::LazyInstance<SequenceLocalStorageSlot<int>> sls_value;
+// }
+//
+// void Read() {
+// int value = sls_value.Get().Get();
+// ...
+// }
+//
+// void Write() {
+// sls_value.Get().Set(42);
+// }
+//
+// void PostTasks() {
+// // Since Read() runs on the same sequence as Write(), it
+// // will read the value "42". A Read() running on a different
+// // sequence would not see that value.
+// scoped_refptr<base::SequencedTaskRunner> task_runner = ...;
+// task_runner->PostTask(FROM_HERE, base::BindOnce(&Write));
+// task_runner->PostTask(FROM_HERE, base::BindOnce(&Read));
+// }
+//
+// SequenceLocalStorageSlot must be used within the scope of a
+// ScopedSetSequenceLocalStorageMapForCurrentThread object.
+// Note: this is true on all TaskScheduler workers and on threads bound to a
+// MessageLoop.
+template <typename T, typename Deleter = std::default_delete<T>>
+class SequenceLocalStorageSlot {
+ public:
+ SequenceLocalStorageSlot()
+ : slot_id_(internal::GetNextSequenceLocalStorageSlotNumber()) {}
+ ~SequenceLocalStorageSlot() = default;
+
+ // Get the sequence-local value stored in this slot. Returns a
+ // default-constructed value if no value was previously set.
+ T& Get() {
+ void* value =
+ internal::SequenceLocalStorageMap::GetForCurrentThread().Get(slot_id_);
+
+ // Sets and returns a default-constructed value if no value was previously
+ // set.
+ if (!value) {
+ Set(T());
+ return Get();
+ }
+ return *(static_cast<T*>(value));
+ }
+
+ // Set this slot's sequence-local value to |value|.
+ // Note that if T is expensive to copy, it may be more appropriate to instead
+ // store a std::unique_ptr<T>. This is enforced by the
+ // DISALLOW_COPY_AND_ASSIGN style rather than directly by this class however.
+ void Set(T value) {
+ // Allocates the |value| with new rather than std::make_unique.
+ // Since SequenceLocalStorageMap needs to store values of various types
+ // within the same map, the type of value_destructor_pair.value is void*
+ // (std::unique_ptr<void> is invalid). Memory is freed by calling
+ // |value_destructor_pair.destructor| in the destructor of
+ // ValueDestructorPair which is invoked when the value is overwritten by
+ // another call to SequenceLocalStorageMap::Set or when the
+ // SequenceLocalStorageMap is deleted.
+ T* value_ptr = new T(std::move(value));
+
+ internal::SequenceLocalStorageMap::ValueDestructorPair::DestructorFunc*
+ destructor = [](void* ptr) { Deleter()(static_cast<T*>(ptr)); };
+
+ internal::SequenceLocalStorageMap::ValueDestructorPair
+ value_destructor_pair(value_ptr, destructor);
+
+ internal::SequenceLocalStorageMap::GetForCurrentThread().Set(
+ slot_id_, std::move(value_destructor_pair));
+ }
+
+ private:
+ // |slot_id_| is used as a key in SequenceLocalStorageMap
+ const int slot_id_;
+ DISALLOW_COPY_AND_ASSIGN(SequenceLocalStorageSlot);
+};
+
+} // namespace base
+#endif // BASE_THREADING_SEQUENCE_LOCAL_STORAGE_SLOT_H_
diff --git a/base/threading/sequence_local_storage_slot_unittest.cc b/base/threading/sequence_local_storage_slot_unittest.cc
new file mode 100644
index 0000000000..4a9f6a914c
--- /dev/null
+++ b/base/threading/sequence_local_storage_slot_unittest.cc
@@ -0,0 +1,143 @@
+// 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/threading/sequence_local_storage_slot.h"
+
+#include <utility>
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/threading/sequence_local_storage_map.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+class SequenceLocalStorageSlotTest : public testing::Test {
+ protected:
+ SequenceLocalStorageSlotTest()
+ : scoped_sequence_local_storage_(&sequence_local_storage_) {}
+
+ internal::SequenceLocalStorageMap sequence_local_storage_;
+ internal::ScopedSetSequenceLocalStorageMapForCurrentThread
+ scoped_sequence_local_storage_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SequenceLocalStorageSlotTest);
+};
+
+} // namespace
+
+// Verify that a value stored with Set() can be retrieved with Get().
+TEST_F(SequenceLocalStorageSlotTest, GetSet) {
+ SequenceLocalStorageSlot<int> slot;
+ slot.Set(5);
+ EXPECT_EQ(slot.Get(), 5);
+}
+
+// Verify that setting an object in a SequenceLocalStorageSlot creates a copy
+// of that object independent of the original one.
+TEST_F(SequenceLocalStorageSlotTest, SetObjectIsIndependent) {
+ bool should_be_false = false;
+
+ SequenceLocalStorageSlot<bool> slot;
+
+ slot.Set(should_be_false);
+
+ EXPECT_FALSE(slot.Get());
+ slot.Get() = true;
+ EXPECT_TRUE(slot.Get());
+
+ EXPECT_NE(should_be_false, slot.Get());
+}
+
+// Verify that multiple slots work and that calling Get after overwriting
+// a value in a slot yields the new value.
+TEST_F(SequenceLocalStorageSlotTest, GetSetMultipleSlots) {
+ SequenceLocalStorageSlot<int> slot1;
+ SequenceLocalStorageSlot<int> slot2;
+ SequenceLocalStorageSlot<int> slot3;
+
+ slot1.Set(1);
+ slot2.Set(2);
+ slot3.Set(3);
+
+ EXPECT_EQ(slot1.Get(), 1);
+ EXPECT_EQ(slot2.Get(), 2);
+ EXPECT_EQ(slot3.Get(), 3);
+
+ slot3.Set(4);
+ slot2.Set(5);
+ slot1.Set(6);
+
+ EXPECT_EQ(slot3.Get(), 4);
+ EXPECT_EQ(slot2.Get(), 5);
+ EXPECT_EQ(slot1.Get(), 6);
+}
+
+// Verify that changing the the value returned by Get() changes the value
+// in sequence local storage.
+TEST_F(SequenceLocalStorageSlotTest, GetReferenceModifiable) {
+ SequenceLocalStorageSlot<bool> slot;
+ slot.Set(false);
+ slot.Get() = true;
+ EXPECT_TRUE(slot.Get());
+}
+
+// Verify that a move-only type can be stored in sequence local storage.
+TEST_F(SequenceLocalStorageSlotTest, SetGetWithMoveOnlyType) {
+ std::unique_ptr<int> int_unique_ptr = std::make_unique<int>(5);
+
+ SequenceLocalStorageSlot<std::unique_ptr<int>> slot;
+ slot.Set(std::move(int_unique_ptr));
+
+ EXPECT_EQ(*slot.Get(), 5);
+}
+
+// Verify that a Get() without a previous Set() on a slot returns a
+// default-constructed value.
+TEST_F(SequenceLocalStorageSlotTest, GetWithoutSetDefaultConstructs) {
+ struct DefaultConstructable {
+ int x = 0x12345678;
+ };
+
+ SequenceLocalStorageSlot<DefaultConstructable> slot;
+
+ EXPECT_EQ(slot.Get().x, 0x12345678);
+}
+
+// Verify that a Get() without a previous Set() on a slot with a POD-type
+// returns a default-constructed value.
+// Note: this test could be flaky and give a false pass. If it's flaky, the test
+// might've "passed" because the memory for the slot happened to be zeroed.
+TEST_F(SequenceLocalStorageSlotTest, GetWithoutSetDefaultConstructsPOD) {
+ SequenceLocalStorageSlot<void*> slot;
+
+ EXPECT_EQ(slot.Get(), nullptr);
+}
+
+// Verify that the value of a slot is specific to a SequenceLocalStorageMap
+TEST(SequenceLocalStorageSlotMultipleMapTest, SetGetMultipleMapsOneSlot) {
+ SequenceLocalStorageSlot<unsigned int> slot;
+ internal::SequenceLocalStorageMap sequence_local_storage_maps[5];
+
+ // Set the value of the slot to be the index of the current
+ // SequenceLocalStorageMaps in the vector
+ for (unsigned int i = 0; i < arraysize(sequence_local_storage_maps); ++i) {
+ internal::ScopedSetSequenceLocalStorageMapForCurrentThread
+ scoped_sequence_local_storage(&sequence_local_storage_maps[i]);
+
+ slot.Set(i);
+ }
+
+ for (unsigned int i = 0; i < arraysize(sequence_local_storage_maps); ++i) {
+ internal::ScopedSetSequenceLocalStorageMapForCurrentThread
+ scoped_sequence_local_storage(&sequence_local_storage_maps[i]);
+
+ EXPECT_EQ(slot.Get(), i);
+ }
+}
+
+} // namespace base
diff --git a/base/threading/sequenced_task_runner_handle.cc b/base/threading/sequenced_task_runner_handle.cc
index 90f68b33ab..e6920f5f68 100644
--- a/base/threading/sequenced_task_runner_handle.cc
+++ b/base/threading/sequenced_task_runner_handle.cc
@@ -8,7 +8,6 @@
#include "base/lazy_instance.h"
#include "base/logging.h"
-#include "base/threading/sequenced_worker_pool.h"
#include "base/threading/thread_local.h"
#include "base/threading/thread_task_runner_handle.h"
@@ -17,69 +16,44 @@ namespace base {
namespace {
LazyInstance<ThreadLocalPointer<SequencedTaskRunnerHandle>>::Leaky
- lazy_tls_ptr = LAZY_INSTANCE_INITIALIZER;
+ sequenced_task_runner_tls = LAZY_INSTANCE_INITIALIZER;
} // namespace
// static
scoped_refptr<SequencedTaskRunner> SequencedTaskRunnerHandle::Get() {
- // Return the registered SingleThreadTaskRunner, if any. This must be at the
- // top so that a SingleThreadTaskRunner has priority over a
- // SequencedTaskRunner (RLZ registers both on the same thread despite that
- // being prevented by DCHECKs).
- // TODO(fdoray): Move this to the bottom once RLZ stops registering a
- // SingleThreadTaskRunner and a SequencedTaskRunner on the same thread.
- // https://crbug.com/618530#c14
- if (ThreadTaskRunnerHandle::IsSet()) {
- // Various modes of setting SequencedTaskRunnerHandle don't combine.
- DCHECK(!lazy_tls_ptr.Pointer()->Get());
- DCHECK(!SequencedWorkerPool::GetSequenceTokenForCurrentThread().IsValid());
-
- return ThreadTaskRunnerHandle::Get();
- }
-
// Return the registered SequencedTaskRunner, if any.
- const SequencedTaskRunnerHandle* handle = lazy_tls_ptr.Pointer()->Get();
- if (handle) {
- // Various modes of setting SequencedTaskRunnerHandle don't combine.
- DCHECK(!SequencedWorkerPool::GetSequenceTokenForCurrentThread().IsValid());
-
+ const SequencedTaskRunnerHandle* handle =
+ sequenced_task_runner_tls.Pointer()->Get();
+ if (handle)
return handle->task_runner_;
- }
- // If we are on a worker thread for a SequencedBlockingPool that is running a
- // sequenced task, return a SequencedTaskRunner for it.
- scoped_refptr<SequencedWorkerPool> pool =
- SequencedWorkerPool::GetWorkerPoolForCurrentThread();
- DCHECK(pool);
- SequencedWorkerPool::SequenceToken sequence_token =
- SequencedWorkerPool::GetSequenceTokenForCurrentThread();
- DCHECK(sequence_token.IsValid());
- scoped_refptr<SequencedTaskRunner> sequenced_task_runner(
- pool->GetSequencedTaskRunner(sequence_token));
- DCHECK(sequenced_task_runner->RunsTasksOnCurrentThread());
- return sequenced_task_runner;
+ // Note if you hit this: the problem is the lack of a sequenced context. The
+ // ThreadTaskRunnerHandle is just the last attempt at finding such a context.
+ CHECK(ThreadTaskRunnerHandle::IsSet())
+ << "Error: This caller requires a sequenced context (i.e. the "
+ "current task needs to run from a SequencedTaskRunner).";
+ return ThreadTaskRunnerHandle::Get();
}
// static
bool SequencedTaskRunnerHandle::IsSet() {
- return lazy_tls_ptr.Pointer()->Get() ||
- SequencedWorkerPool::GetSequenceTokenForCurrentThread().IsValid() ||
+ return sequenced_task_runner_tls.Pointer()->Get() ||
ThreadTaskRunnerHandle::IsSet();
}
SequencedTaskRunnerHandle::SequencedTaskRunnerHandle(
scoped_refptr<SequencedTaskRunner> task_runner)
: task_runner_(std::move(task_runner)) {
- DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
DCHECK(!SequencedTaskRunnerHandle::IsSet());
- lazy_tls_ptr.Pointer()->Set(this);
+ sequenced_task_runner_tls.Pointer()->Set(this);
}
SequencedTaskRunnerHandle::~SequencedTaskRunnerHandle() {
- DCHECK(task_runner_->RunsTasksOnCurrentThread());
- DCHECK_EQ(lazy_tls_ptr.Pointer()->Get(), this);
- lazy_tls_ptr.Pointer()->Set(nullptr);
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK_EQ(sequenced_task_runner_tls.Pointer()->Get(), this);
+ sequenced_task_runner_tls.Pointer()->Set(nullptr);
}
} // namespace base
diff --git a/base/threading/sequenced_task_runner_handle.h b/base/threading/sequenced_task_runner_handle.h
index b7f4bae8aa..f55cee5a24 100644
--- a/base/threading/sequenced_task_runner_handle.h
+++ b/base/threading/sequenced_task_runner_handle.h
@@ -24,10 +24,7 @@ class BASE_EXPORT SequencedTaskRunnerHandle {
// a) A SequencedTaskRunner has been assigned to the current thread by
// instantiating a SequencedTaskRunnerHandle.
// b) The current thread has a ThreadTaskRunnerHandle (which includes any
- // thread that has a MessageLoop associated with it), or
- // c) The current thread is a worker thread belonging to a SequencedWorkerPool
- // *and* is currently running a sequenced task (note: not supporting
- // unsequenced tasks is intentional: https://crbug.com/618043#c4).
+ // thread that has a MessageLoop associated with it).
static bool IsSet();
// Binds |task_runner| to the current thread.
diff --git a/base/threading/sequenced_worker_pool.cc b/base/threading/sequenced_worker_pool.cc
deleted file mode 100644
index 1d8a67c2e0..0000000000
--- a/base/threading/sequenced_worker_pool.cc
+++ /dev/null
@@ -1,1680 +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/threading/sequenced_worker_pool.h"
-
-#include <stdint.h>
-
-#include <list>
-#include <map>
-#include <memory>
-#include <set>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "base/atomic_sequence_num.h"
-#include "base/callback.h"
-#include "base/compiler_specific.h"
-#include "base/critical_closure.h"
-#include "base/debug/dump_without_crashing.h"
-#include "base/lazy_instance.h"
-#include "base/logging.h"
-#include "base/macros.h"
-#include "base/memory/ptr_util.h"
-#include "base/stl_util.h"
-#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"
-#include "base/threading/thread_local.h"
-#include "base/threading/thread_restrictions.h"
-#include "base/time/time.h"
-#include "base/trace_event/trace_event.h"
-#include "base/tracked_objects.h"
-#include "base/tracking_info.h"
-#include "build/build_config.h"
-
-#if defined(OS_MACOSX)
-#include "base/mac/scoped_nsautorelease_pool.h"
-#elif defined(OS_WIN)
-#include "base/win/scoped_com_initializer.h"
-#endif
-
-#if !defined(OS_NACL)
-#include "base/metrics/histogram_macros.h"
-#endif
-
-namespace base {
-
-namespace {
-
-// An enum representing the state of all pools. A non-test process should only
-// ever transition from POST_TASK_DISABLED to one of the active states. A test
-// process may transition from one of the active states to POST_TASK_DISABLED
-// when DisableForProcessForTesting() is called.
-//
-// External memory synchronization is required to call a method that reads
-// |g_all_pools_state| after calling a method that modifies it.
-//
-// TODO(gab): Remove this if http://crbug.com/622400 fails (SequencedWorkerPool
-// will be phased out completely otherwise).
-enum class AllPoolsState {
- POST_TASK_DISABLED,
- USE_WORKER_POOL,
- REDIRECTED_TO_TASK_SCHEDULER,
-};
-
-// TODO(fdoray): Change the initial state to POST_TASK_DISABLED. It is initially
-// USE_WORKER_POOL to avoid a revert of the CL that adds
-// debug::DumpWithoutCrashing() in case of waterfall failures.
-AllPoolsState g_all_pools_state = AllPoolsState::USE_WORKER_POOL;
-
-TaskPriority g_max_task_priority = TaskPriority::HIGHEST;
-
-struct SequencedTask : public TrackingInfo {
- SequencedTask()
- : sequence_token_id(0),
- trace_id(0),
- sequence_task_number(0),
- shutdown_behavior(SequencedWorkerPool::BLOCK_SHUTDOWN) {}
-
- explicit SequencedTask(const tracked_objects::Location& from_here)
- : base::TrackingInfo(from_here, TimeTicks()),
- sequence_token_id(0),
- trace_id(0),
- sequence_task_number(0),
- shutdown_behavior(SequencedWorkerPool::BLOCK_SHUTDOWN) {}
-
- ~SequencedTask() {}
-
- SequencedTask(SequencedTask&&) = default;
- SequencedTask& operator=(SequencedTask&&) = default;
-
- int sequence_token_id;
- int trace_id;
- int64_t sequence_task_number;
- SequencedWorkerPool::WorkerShutdown shutdown_behavior;
- tracked_objects::Location posted_from;
- OnceClosure task;
-
- // Non-delayed tasks and delayed tasks are managed together by time-to-run
- // order. We calculate the time by adding the posted time and the given delay.
- TimeTicks time_to_run;
-};
-
-struct SequencedTaskLessThan {
- public:
- bool operator()(const SequencedTask& lhs, const SequencedTask& rhs) const {
- if (lhs.time_to_run < rhs.time_to_run)
- return true;
-
- if (lhs.time_to_run > rhs.time_to_run)
- return false;
-
- // If the time happen to match, then we use the sequence number to decide.
- return lhs.sequence_task_number < rhs.sequence_task_number;
- }
-};
-
-// Create a process-wide unique ID to represent this task in trace events. This
-// will be mangled with a Process ID hash to reduce the likelyhood of colliding
-// with MessageLoop pointers on other processes.
-uint64_t GetTaskTraceID(const SequencedTask& task, void* pool) {
- return (static_cast<uint64_t>(task.trace_id) << 32) |
- static_cast<uint64_t>(reinterpret_cast<intptr_t>(pool));
-}
-
-// SequencedWorkerPoolTaskRunner ---------------------------------------------
-// A TaskRunner which posts tasks to a SequencedWorkerPool with a
-// fixed ShutdownBehavior.
-//
-// Note that this class is RefCountedThreadSafe (inherited from TaskRunner).
-class SequencedWorkerPoolTaskRunner : public TaskRunner {
- public:
- SequencedWorkerPoolTaskRunner(
- scoped_refptr<SequencedWorkerPool> pool,
- SequencedWorkerPool::WorkerShutdown shutdown_behavior);
-
- // TaskRunner implementation
- bool PostDelayedTask(const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay) override;
- bool RunsTasksOnCurrentThread() const override;
-
- private:
- ~SequencedWorkerPoolTaskRunner() override;
-
- const scoped_refptr<SequencedWorkerPool> pool_;
-
- const SequencedWorkerPool::WorkerShutdown shutdown_behavior_;
-
- DISALLOW_COPY_AND_ASSIGN(SequencedWorkerPoolTaskRunner);
-};
-
-SequencedWorkerPoolTaskRunner::SequencedWorkerPoolTaskRunner(
- scoped_refptr<SequencedWorkerPool> pool,
- SequencedWorkerPool::WorkerShutdown shutdown_behavior)
- : pool_(std::move(pool)), shutdown_behavior_(shutdown_behavior) {}
-
-SequencedWorkerPoolTaskRunner::~SequencedWorkerPoolTaskRunner() {
-}
-
-bool SequencedWorkerPoolTaskRunner::PostDelayedTask(
- const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay) {
- if (delay.is_zero()) {
- return pool_->PostWorkerTaskWithShutdownBehavior(from_here, std::move(task),
- shutdown_behavior_);
- }
- return pool_->PostDelayedWorkerTask(from_here, std::move(task), delay);
-}
-
-bool SequencedWorkerPoolTaskRunner::RunsTasksOnCurrentThread() const {
- return pool_->RunsTasksOnCurrentThread();
-}
-
-} // namespace
-
-// SequencedWorkerPool::PoolSequencedTaskRunner ------------------------------
-// A SequencedTaskRunner which posts tasks to a SequencedWorkerPool with a
-// fixed sequence token.
-//
-// Note that this class is RefCountedThreadSafe (inherited from TaskRunner).
-class SequencedWorkerPool::PoolSequencedTaskRunner
- : public SequencedTaskRunner {
- public:
- PoolSequencedTaskRunner(
- scoped_refptr<SequencedWorkerPool> pool,
- SequencedWorkerPool::SequenceToken token,
- SequencedWorkerPool::WorkerShutdown shutdown_behavior);
-
- // TaskRunner implementation
- bool PostDelayedTask(const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay) override;
- bool RunsTasksOnCurrentThread() const override;
-
- // SequencedTaskRunner implementation
- bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay) override;
-
- private:
- ~PoolSequencedTaskRunner() override;
-
- const scoped_refptr<SequencedWorkerPool> pool_;
-
- const SequencedWorkerPool::SequenceToken token_;
-
- const SequencedWorkerPool::WorkerShutdown shutdown_behavior_;
-
- DISALLOW_COPY_AND_ASSIGN(PoolSequencedTaskRunner);
-};
-
-SequencedWorkerPool::PoolSequencedTaskRunner::
- PoolSequencedTaskRunner(
- scoped_refptr<SequencedWorkerPool> pool,
- SequencedWorkerPool::SequenceToken token,
- SequencedWorkerPool::WorkerShutdown shutdown_behavior)
- : pool_(std::move(pool)),
- token_(token),
- shutdown_behavior_(shutdown_behavior) {}
-
-SequencedWorkerPool::PoolSequencedTaskRunner::
- ~PoolSequencedTaskRunner() = default;
-
-bool SequencedWorkerPool::PoolSequencedTaskRunner::PostDelayedTask(
- const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay) {
- if (delay.is_zero()) {
- return pool_->PostSequencedWorkerTaskWithShutdownBehavior(
- token_, from_here, std::move(task), shutdown_behavior_);
- }
- return pool_->PostDelayedSequencedWorkerTask(token_, from_here,
- std::move(task), delay);
-}
-
-bool SequencedWorkerPool::PoolSequencedTaskRunner::
- RunsTasksOnCurrentThread() const {
- return pool_->IsRunningSequenceOnCurrentThread(token_);
-}
-
-bool SequencedWorkerPool::PoolSequencedTaskRunner::PostNonNestableDelayedTask(
- const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay) {
- // There's no way to run nested tasks, so simply forward to
- // PostDelayedTask.
- return PostDelayedTask(from_here, std::move(task), delay);
-}
-
-// Worker ---------------------------------------------------------------------
-
-class SequencedWorkerPool::Worker : public SimpleThread {
- public:
- // Hold a (cyclic) ref to |worker_pool|, since we want to keep it
- // around as long as we are running.
- Worker(scoped_refptr<SequencedWorkerPool> worker_pool,
- int thread_number,
- const std::string& thread_name_prefix);
- ~Worker() override;
-
- // SimpleThread implementation. This actually runs the background thread.
- void Run() override;
-
- // Gets the worker for the current thread out of thread-local storage.
- static Worker* GetForCurrentThread();
-
- // Indicates that a task is about to be run. The parameters provide
- // additional metainformation about the task being run.
- void set_running_task_info(SequenceToken token,
- WorkerShutdown shutdown_behavior) {
- is_processing_task_ = true;
- task_sequence_token_ = token;
- task_shutdown_behavior_ = shutdown_behavior;
-
- // It is dangerous for tasks with CONTINUE_ON_SHUTDOWN to access a class
- // that implements a non-leaky base::Singleton because they are generally
- // destroyed before the process terminates via an AtExitManager
- // registration. This will trigger a DCHECK to warn of such cases. See the
- // comment about CONTINUE_ON_SHUTDOWN for more details.
- ThreadRestrictions::SetSingletonAllowed(task_shutdown_behavior_ !=
- CONTINUE_ON_SHUTDOWN);
- }
-
- // Indicates that the task has finished running.
- void reset_running_task_info() { is_processing_task_ = false; }
-
- // Whether the worker is processing a task.
- bool is_processing_task() { return is_processing_task_; }
-
- SequenceToken task_sequence_token() const {
- DCHECK(is_processing_task_);
- return task_sequence_token_;
- }
-
- WorkerShutdown task_shutdown_behavior() const {
- DCHECK(is_processing_task_);
- return task_shutdown_behavior_;
- }
-
- scoped_refptr<SequencedWorkerPool> worker_pool() const {
- return worker_pool_;
- }
-
- private:
- static LazyInstance<ThreadLocalPointer<SequencedWorkerPool::Worker>>::Leaky
- lazy_tls_ptr_;
-
- scoped_refptr<SequencedWorkerPool> worker_pool_;
- // The sequence token of the task being processed. Only valid when
- // is_processing_task_ is true.
- SequenceToken task_sequence_token_;
- // The shutdown behavior of the task being processed. Only valid when
- // is_processing_task_ is true.
- WorkerShutdown task_shutdown_behavior_;
- // Whether the Worker is processing a task.
- bool is_processing_task_;
-
- DISALLOW_COPY_AND_ASSIGN(Worker);
-};
-
-// Inner ----------------------------------------------------------------------
-
-class SequencedWorkerPool::Inner {
- public:
- // Take a raw pointer to |worker| to avoid cycles (since we're owned
- // by it).
- Inner(SequencedWorkerPool* worker_pool,
- size_t max_threads,
- const std::string& thread_name_prefix,
- base::TaskPriority task_priority,
- TestingObserver* observer);
-
- ~Inner();
-
- static SequenceToken GetSequenceToken();
-
- SequenceToken GetNamedSequenceToken(const std::string& name);
-
- // This function accepts a name and an ID. If the name is null, the
- // token ID is used. This allows us to implement the optional name lookup
- // from a single function without having to enter the lock a separate time.
- bool PostTask(const std::string* optional_token_name,
- SequenceToken sequence_token,
- WorkerShutdown shutdown_behavior,
- const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay);
-
- bool RunsTasksOnCurrentThread() const;
-
- bool IsRunningSequenceOnCurrentThread(SequenceToken sequence_token) const;
-
- void CleanupForTesting();
-
- void SignalHasWorkForTesting();
-
- int GetWorkSignalCountForTesting() const;
-
- void Shutdown(int max_blocking_tasks_after_shutdown);
-
- bool IsShutdownInProgress();
-
- // Runs the worker loop on the background thread.
- void ThreadLoop(Worker* this_worker);
-
- private:
- enum GetWorkStatus {
- GET_WORK_FOUND,
- GET_WORK_NOT_FOUND,
- GET_WORK_WAIT,
- };
-
- enum CleanupState {
- CLEANUP_REQUESTED,
- CLEANUP_STARTING,
- CLEANUP_RUNNING,
- CLEANUP_FINISHING,
- CLEANUP_DONE,
- };
-
- // Clears ScheduledTasks in |tasks_to_delete| while ensuring that
- // |this_worker| has the desired task info context during ~ScheduledTask() to
- // allow sequence-checking.
- void DeleteWithoutLock(std::vector<SequencedTask>* tasks_to_delete,
- Worker* this_worker);
-
- // Helper used by PostTask() to complete the work when redirection is on.
- // Returns true if the task may run at some point in the future and false if
- // it will definitely not run.
- // Coalesce upon resolution of http://crbug.com/622400.
- bool PostTaskToTaskScheduler(SequencedTask sequenced, const TimeDelta& delay);
-
- // Returns the TaskScheduler TaskRunner for the specified |sequence_token_id|
- // and |traits|.
- scoped_refptr<TaskRunner> GetTaskSchedulerTaskRunner(
- int sequence_token_id,
- const TaskTraits& traits);
-
- // Called from within the lock, this converts the given token name into a
- // token ID, creating a new one if necessary.
- int LockedGetNamedTokenID(const std::string& name);
-
- // Called from within the lock, this returns the next sequence task number.
- int64_t LockedGetNextSequenceTaskNumber();
-
- // Gets new task. There are 3 cases depending on the return value:
- //
- // 1) If the return value is |GET_WORK_FOUND|, |task| is filled in and should
- // be run immediately.
- // 2) If the return value is |GET_WORK_NOT_FOUND|, there are no tasks to run,
- // and |task| is not filled in. In this case, the caller should wait until
- // a task is posted.
- // 3) If the return value is |GET_WORK_WAIT|, there are no tasks to run
- // immediately, and |task| is not filled in. Likewise, |wait_time| is
- // filled in the time to wait until the next task to run. In this case, the
- // caller should wait the time.
- //
- // In any case, the calling code should clear the given
- // delete_these_outside_lock vector the next time the lock is released.
- // See the implementation for a more detailed description.
- GetWorkStatus GetWork(SequencedTask* task,
- TimeDelta* wait_time,
- std::vector<SequencedTask>* delete_these_outside_lock);
-
- void HandleCleanup();
-
- // Peforms init and cleanup around running the given task. WillRun...
- // returns the value from PrepareToStartAdditionalThreadIfNecessary.
- // The calling code should call FinishStartingAdditionalThread once the
- // lock is released if the return values is nonzero.
- int WillRunWorkerTask(const SequencedTask& task);
- void DidRunWorkerTask(const SequencedTask& task);
-
- // Returns true if there are no threads currently running the given
- // sequence token.
- bool IsSequenceTokenRunnable(int sequence_token_id) const;
-
- // Checks if all threads are busy and the addition of one more could run an
- // additional task waiting in the queue. This must be called from within
- // the lock.
- //
- // If another thread is helpful, this will mark the thread as being in the
- // process of starting and returns the index of the new thread which will be
- // 0 or more. The caller should then call FinishStartingAdditionalThread to
- // complete initialization once the lock is released.
- //
- // If another thread is not necessary, return 0;
- //
- // See the implementedion for more.
- int PrepareToStartAdditionalThreadIfHelpful();
-
- // The second part of thread creation after
- // PrepareToStartAdditionalThreadIfHelpful with the thread number it
- // generated. This actually creates the thread and should be called outside
- // the lock to avoid blocking important work starting a thread in the lock.
- void FinishStartingAdditionalThread(int thread_number);
-
- // Signal |has_work_| and increment |has_work_signal_count_|.
- void SignalHasWork();
-
- // Checks whether there is work left that's blocking shutdown. Must be
- // called inside the lock.
- bool CanShutdown() const;
-
- SequencedWorkerPool* const worker_pool_;
-
- // The last sequence number used. Managed by GetSequenceToken, since this
- // only does threadsafe increment operations, you do not need to hold the
- // lock. This is class-static to make SequenceTokens issued by
- // GetSequenceToken unique across SequencedWorkerPool instances.
- static base::StaticAtomicSequenceNumber g_last_sequence_number_;
-
- // This lock protects |everything in this class|. Do not read or modify
- // anything without holding this lock. Do not block while holding this
- // lock.
- mutable Lock lock_;
-
- // Condition variable that is waited on by worker threads until new
- // tasks are posted or shutdown starts.
- ConditionVariable has_work_cv_;
-
- // Condition variable that is waited on by non-worker threads (in
- // Shutdown()) until CanShutdown() goes to true.
- ConditionVariable can_shutdown_cv_;
-
- // The maximum number of worker threads we'll create.
- const size_t max_threads_;
-
- const std::string thread_name_prefix_;
-
- // Associates all known sequence token names with their IDs.
- std::map<std::string, int> named_sequence_tokens_;
-
- // Owning pointers to all threads we've created so far, indexed by
- // ID. Since we lazily create threads, this may be less than
- // max_threads_ and will be initially empty.
- using ThreadMap = std::map<PlatformThreadId, std::unique_ptr<Worker>>;
- ThreadMap threads_;
-
- // Set to true when we're in the process of creating another thread.
- // See PrepareToStartAdditionalThreadIfHelpful for more.
- bool thread_being_created_;
-
- // Number of threads currently waiting for work.
- size_t waiting_thread_count_;
-
- // Number of threads currently running tasks that have the BLOCK_SHUTDOWN
- // or SKIP_ON_SHUTDOWN flag set.
- size_t blocking_shutdown_thread_count_;
-
- // A set of all pending tasks in time-to-run order. These are tasks that are
- // either waiting for a thread to run on, waiting for their time to run,
- // or blocked on a previous task in their sequence. We have to iterate over
- // the tasks by time-to-run order, so we use the set instead of the
- // traditional priority_queue.
- typedef std::set<SequencedTask, SequencedTaskLessThan> PendingTaskSet;
- PendingTaskSet pending_tasks_;
-
- // The next sequence number for a new sequenced task.
- int64_t next_sequence_task_number_;
-
- // Number of tasks in the pending_tasks_ list that are marked as blocking
- // shutdown.
- size_t blocking_shutdown_pending_task_count_;
-
- // Lists all sequence tokens currently executing.
- std::set<int> current_sequences_;
-
- // An ID for each posted task to distinguish the task from others in traces.
- int trace_id_;
-
- // Set when Shutdown is called and no further tasks should be
- // allowed, though we may still be running existing tasks.
- bool shutdown_called_;
-
- // The number of new BLOCK_SHUTDOWN tasks that may be posted after Shudown()
- // has been called.
- int max_blocking_tasks_after_shutdown_;
-
- // State used to cleanup for testing, all guarded by lock_.
- CleanupState cleanup_state_;
- size_t cleanup_idlers_;
- ConditionVariable cleanup_cv_;
-
- TestingObserver* const testing_observer_;
-
- // Members below are used for the experimental redirection to TaskScheduler.
- // TODO(gab): Remove these if http://crbug.com/622400 fails
- // (SequencedWorkerPool will be phased out completely otherwise).
-
- // The TaskPriority to be used for SequencedWorkerPool tasks redirected to the
- // TaskScheduler as an experiment (unused otherwise).
- const base::TaskPriority task_priority_;
-
- // A map of SequenceToken IDs to TaskScheduler TaskRunners used to redirect
- // sequenced tasks to the TaskScheduler.
- std::unordered_map<int, scoped_refptr<TaskRunner>> sequenced_task_runner_map_;
-
- // TaskScheduler TaskRunners to redirect unsequenced tasks to the
- // TaskScheduler. Indexed by TaskShutdownBehavior.
- scoped_refptr<TaskRunner> unsequenced_task_runners_[3];
-
- // A dummy TaskRunner obtained from TaskScheduler with the same TaskTraits as
- // used by this SequencedWorkerPool to query for RunsTasksOnCurrentThread().
- // Mutable so it can be lazily instantiated from RunsTasksOnCurrentThread().
- mutable scoped_refptr<TaskRunner> runs_tasks_on_verifier_;
-
- DISALLOW_COPY_AND_ASSIGN(Inner);
-};
-
-// Worker definitions ---------------------------------------------------------
-
-SequencedWorkerPool::Worker::Worker(
- scoped_refptr<SequencedWorkerPool> worker_pool,
- int thread_number,
- const std::string& prefix)
- : SimpleThread(prefix + StringPrintf("Worker%d", thread_number)),
- worker_pool_(std::move(worker_pool)),
- task_shutdown_behavior_(BLOCK_SHUTDOWN),
- is_processing_task_(false) {
- DCHECK_EQ(AllPoolsState::USE_WORKER_POOL, g_all_pools_state);
- Start();
-}
-
-SequencedWorkerPool::Worker::~Worker() {
-}
-
-void SequencedWorkerPool::Worker::Run() {
- DCHECK_EQ(AllPoolsState::USE_WORKER_POOL, g_all_pools_state);
-
-#if defined(OS_WIN)
- win::ScopedCOMInitializer com_initializer;
-#endif
-
- // Store a pointer to this worker in thread local storage for static function
- // access.
- DCHECK(!lazy_tls_ptr_.Get().Get());
- lazy_tls_ptr_.Get().Set(this);
-
- // Just jump back to the Inner object to run the thread, since it has all the
- // tracking information and queues. It might be more natural to implement
- // using DelegateSimpleThread and have Inner implement the Delegate to avoid
- // having these worker objects at all, but that method lacks the ability to
- // send thread-specific information easily to the thread loop.
- worker_pool_->inner_->ThreadLoop(this);
- // Release our cyclic reference once we're done.
- worker_pool_ = nullptr;
-}
-
-// static
-SequencedWorkerPool::Worker*
-SequencedWorkerPool::Worker::GetForCurrentThread() {
- // Don't construct lazy instance on check.
- if (lazy_tls_ptr_ == nullptr)
- return nullptr;
-
- return lazy_tls_ptr_.Get().Get();
-}
-
-// static
-LazyInstance<ThreadLocalPointer<SequencedWorkerPool::Worker>>::Leaky
- SequencedWorkerPool::Worker::lazy_tls_ptr_ = LAZY_INSTANCE_INITIALIZER;
-
-// Inner definitions ---------------------------------------------------------
-
-SequencedWorkerPool::Inner::Inner(SequencedWorkerPool* worker_pool,
- size_t max_threads,
- const std::string& thread_name_prefix,
- base::TaskPriority task_priority,
- TestingObserver* observer)
- : worker_pool_(worker_pool),
- lock_(),
- has_work_cv_(&lock_),
- can_shutdown_cv_(&lock_),
- max_threads_(max_threads),
- thread_name_prefix_(thread_name_prefix),
- thread_being_created_(false),
- waiting_thread_count_(0),
- blocking_shutdown_thread_count_(0),
- next_sequence_task_number_(0),
- blocking_shutdown_pending_task_count_(0),
- trace_id_(0),
- shutdown_called_(false),
- max_blocking_tasks_after_shutdown_(0),
- cleanup_state_(CLEANUP_DONE),
- cleanup_idlers_(0),
- cleanup_cv_(&lock_),
- testing_observer_(observer),
- task_priority_(static_cast<int>(task_priority) <=
- static_cast<int>(g_max_task_priority)
- ? task_priority
- : g_max_task_priority) {
- DCHECK_GT(max_threads_, 1U);
-}
-
-SequencedWorkerPool::Inner::~Inner() {
- // You must call Shutdown() before destroying the pool.
- DCHECK(shutdown_called_);
-
- // Need to explicitly join with the threads before they're destroyed or else
- // they will be running when our object is half torn down.
- for (ThreadMap::iterator it = threads_.begin(); it != threads_.end(); ++it)
- it->second->Join();
- threads_.clear();
-
- if (testing_observer_)
- testing_observer_->OnDestruct();
-}
-
-// static
-SequencedWorkerPool::SequenceToken
-SequencedWorkerPool::Inner::GetSequenceToken() {
- // Need to add one because StaticAtomicSequenceNumber starts at zero, which
- // is used as a sentinel value in SequenceTokens.
- return SequenceToken(g_last_sequence_number_.GetNext() + 1);
-}
-
-SequencedWorkerPool::SequenceToken
-SequencedWorkerPool::Inner::GetNamedSequenceToken(const std::string& name) {
- AutoLock lock(lock_);
- return SequenceToken(LockedGetNamedTokenID(name));
-}
-
-bool SequencedWorkerPool::Inner::PostTask(
- const std::string* optional_token_name,
- SequenceToken sequence_token,
- WorkerShutdown shutdown_behavior,
- const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay) {
- DCHECK(task);
-
- // TODO(fdoray): Uncomment this DCHECK. It is initially commented to avoid a
- // revert of the CL that adds debug::DumpWithoutCrashing() if it fails on the
- // waterfall. https://crbug.com/622400
- // DCHECK_NE(AllPoolsState::POST_TASK_DISABLED, g_all_pools_state);
- if (g_all_pools_state == AllPoolsState::POST_TASK_DISABLED)
- debug::DumpWithoutCrashing();
-
- DCHECK(delay.is_zero() || shutdown_behavior == SKIP_ON_SHUTDOWN);
- SequencedTask sequenced(from_here);
- sequenced.sequence_token_id = sequence_token.id_;
- sequenced.shutdown_behavior = shutdown_behavior;
- sequenced.posted_from = from_here;
- sequenced.task = shutdown_behavior == BLOCK_SHUTDOWN
- ? base::MakeCriticalClosure(std::move(task))
- : std::move(task);
- sequenced.time_to_run = TimeTicks::Now() + delay;
-
- int create_thread_id = 0;
- {
- AutoLock lock(lock_);
-
- if (shutdown_called_) {
- // Don't allow a new task to be posted if it doesn't block shutdown.
- if (shutdown_behavior != BLOCK_SHUTDOWN)
- return false;
-
- // If the current thread is running a task, and that task doesn't block
- // shutdown, then it shouldn't be allowed to post any more tasks.
- ThreadMap::const_iterator found =
- threads_.find(PlatformThread::CurrentId());
- if (found != threads_.end() && found->second->is_processing_task() &&
- found->second->task_shutdown_behavior() != BLOCK_SHUTDOWN) {
- return false;
- }
-
- if (max_blocking_tasks_after_shutdown_ <= 0) {
- DLOG(WARNING) << "BLOCK_SHUTDOWN task disallowed";
- return false;
- }
- max_blocking_tasks_after_shutdown_ -= 1;
- }
-
- // The trace_id is used for identifying the task in about:tracing.
- sequenced.trace_id = trace_id_++;
-
- TRACE_EVENT_WITH_FLOW0(TRACE_DISABLED_BY_DEFAULT("toplevel.flow"),
- "SequencedWorkerPool::Inner::PostTask",
- TRACE_ID_MANGLE(GetTaskTraceID(sequenced, static_cast<void*>(this))),
- TRACE_EVENT_FLAG_FLOW_OUT);
-
- sequenced.sequence_task_number = LockedGetNextSequenceTaskNumber();
-
- // Now that we have the lock, apply the named token rules.
- 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));
-
- if (shutdown_behavior == BLOCK_SHUTDOWN)
- blocking_shutdown_pending_task_count_++;
-
- create_thread_id = PrepareToStartAdditionalThreadIfHelpful();
-#if 0
- }
-#endif
- }
-
- // Use != REDIRECTED_TO_TASK_SCHEDULER instead of == USE_WORKER_POOL to ensure
- // correct behavior if a task is posted to a SequencedWorkerPool before
- // Enable(WithRedirectionToTaskScheduler)ForProcess() in a non-DCHECK build.
- if (g_all_pools_state != AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER) {
- // Actually start the additional thread or signal an existing one outside
- // the lock.
- if (create_thread_id)
- FinishStartingAdditionalThread(create_thread_id);
- else
- SignalHasWork();
- }
-
-#if DCHECK_IS_ON()
- {
- AutoLock lock_for_dcheck(lock_);
- // Some variables are exposed in both modes for convenience but only really
- // intended for one of them at runtime, confirm exclusive usage here.
- if (g_all_pools_state == AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER) {
- DCHECK(pending_tasks_.empty());
- DCHECK_EQ(0, create_thread_id);
- } else {
- DCHECK(sequenced_task_runner_map_.empty());
- }
- }
-#endif // DCHECK_IS_ON()
-
- return true;
-}
-
-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();
-
- // Confirm that the TaskScheduler's shutdown behaviors use the same
- // underlying values as SequencedWorkerPool.
- static_assert(
- static_cast<int>(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN) ==
- static_cast<int>(CONTINUE_ON_SHUTDOWN),
- "TaskShutdownBehavior and WorkerShutdown enum mismatch for "
- "CONTINUE_ON_SHUTDOWN.");
- static_assert(static_cast<int>(TaskShutdownBehavior::SKIP_ON_SHUTDOWN) ==
- static_cast<int>(SKIP_ON_SHUTDOWN),
- "TaskShutdownBehavior and WorkerShutdown enum mismatch for "
- "SKIP_ON_SHUTDOWN.");
- static_assert(static_cast<int>(TaskShutdownBehavior::BLOCK_SHUTDOWN) ==
- static_cast<int>(BLOCK_SHUTDOWN),
- "TaskShutdownBehavior and WorkerShutdown enum mismatch for "
- "BLOCK_SHUTDOWN.");
-
- const TaskShutdownBehavior task_shutdown_behavior =
- static_cast<TaskShutdownBehavior>(sequenced.shutdown_behavior);
- const TaskTraits traits = TaskTraits()
- .MayBlock()
- .WithBaseSyncPrimitives()
- .WithPriority(task_priority_)
- .WithShutdownBehavior(task_shutdown_behavior);
- return GetTaskSchedulerTaskRunner(sequenced.sequence_token_id, traits)
- ->PostDelayedTask(sequenced.posted_from, std::move(sequenced.task),
- delay);
-#endif
-}
-
-scoped_refptr<TaskRunner>
-SequencedWorkerPool::Inner::GetTaskSchedulerTaskRunner(
- int sequence_token_id,
- const TaskTraits& traits) {
-#if 1
- NOTREACHED();
- return scoped_refptr<TaskRunner>();
-#else
- DCHECK_EQ(AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER, g_all_pools_state);
-
- lock_.AssertAcquired();
-
- static_assert(
- static_cast<int>(TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN) == 0,
- "TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN must be equal to 0 to be "
- "used as an index in |unsequenced_task_runners_|.");
- static_assert(static_cast<int>(TaskShutdownBehavior::SKIP_ON_SHUTDOWN) == 1,
- "TaskShutdownBehavior::SKIP_ON_SHUTDOWN must be equal to 1 to "
- "be used as an index in |unsequenced_task_runners_|.");
- static_assert(static_cast<int>(TaskShutdownBehavior::BLOCK_SHUTDOWN) == 2,
- "TaskShutdownBehavior::BLOCK_SHUTDOWN must be equal to 2 to be "
- "used as an index in |unsequenced_task_runners_|.");
- static_assert(arraysize(unsequenced_task_runners_) == 3,
- "The size of |unsequenced_task_runners_| doesn't match the "
- "number of shutdown behaviors.");
-
- scoped_refptr<TaskRunner>& task_runner =
- sequence_token_id ? sequenced_task_runner_map_[sequence_token_id]
- : unsequenced_task_runners_[static_cast<int>(
- traits.shutdown_behavior())];
-
- // TODO(fdoray): DCHECK that all tasks posted to the same sequence have the
- // same shutdown behavior.
-
- if (!task_runner) {
- task_runner = sequence_token_id
- ? CreateSequencedTaskRunnerWithTraits(traits)
- : CreateTaskRunnerWithTraits(traits);
- }
-
- 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());
- }
-}
-
-bool SequencedWorkerPool::Inner::IsRunningSequenceOnCurrentThread(
- SequenceToken sequence_token) const {
- DCHECK(sequence_token.IsValid());
-
- AutoLock lock(lock_);
-
- if (g_all_pools_state == AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER) {
- const auto sequenced_task_runner_it =
- sequenced_task_runner_map_.find(sequence_token.id_);
- return sequenced_task_runner_it != sequenced_task_runner_map_.end() &&
- sequenced_task_runner_it->second->RunsTasksOnCurrentThread();
- } else {
- ThreadMap::const_iterator found =
- threads_.find(PlatformThread::CurrentId());
- return found != threads_.end() && found->second->is_processing_task() &&
- sequence_token.Equals(found->second->task_sequence_token());
- }
-}
-
-// See https://code.google.com/p/chromium/issues/detail?id=168415
-void SequencedWorkerPool::Inner::CleanupForTesting() {
- DCHECK_NE(g_all_pools_state, AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER);
- AutoLock lock(lock_);
- CHECK_EQ(CLEANUP_DONE, cleanup_state_);
- if (shutdown_called_)
- return;
- if (pending_tasks_.empty() && waiting_thread_count_ == threads_.size())
- return;
- cleanup_state_ = CLEANUP_REQUESTED;
- cleanup_idlers_ = 0;
- has_work_cv_.Signal();
- while (cleanup_state_ != CLEANUP_DONE)
- cleanup_cv_.Wait();
-}
-
-void SequencedWorkerPool::Inner::SignalHasWorkForTesting() {
- SignalHasWork();
-}
-
-void SequencedWorkerPool::Inner::Shutdown(
- int max_new_blocking_tasks_after_shutdown) {
- DCHECK_GE(max_new_blocking_tasks_after_shutdown, 0);
- {
- AutoLock lock(lock_);
- // Cleanup and Shutdown should not be called concurrently.
- CHECK_EQ(CLEANUP_DONE, cleanup_state_);
- if (shutdown_called_)
- return;
- shutdown_called_ = true;
-
- max_blocking_tasks_after_shutdown_ = max_new_blocking_tasks_after_shutdown;
-
- if (g_all_pools_state != AllPoolsState::USE_WORKER_POOL)
- return;
-
- // Tickle the threads. This will wake up a waiting one so it will know that
- // it can exit, which in turn will wake up any other waiting ones.
- SignalHasWork();
-
- // There are no pending or running tasks blocking shutdown, we're done.
- if (CanShutdown())
- return;
- }
-
- // If we're here, then something is blocking shutdown. So wait for
- // CanShutdown() to go to true.
-
- if (testing_observer_)
- testing_observer_->WillWaitForShutdown();
-
-#if !defined(OS_NACL)
- TimeTicks shutdown_wait_begin = TimeTicks::Now();
-#endif
-
- {
- base::ThreadRestrictions::ScopedAllowWait allow_wait;
- AutoLock lock(lock_);
- while (!CanShutdown())
- can_shutdown_cv_.Wait();
- }
-#if !defined(OS_NACL)
- UMA_HISTOGRAM_TIMES("SequencedWorkerPool.ShutdownDelayTime",
- TimeTicks::Now() - shutdown_wait_begin);
-#endif
-}
-
-bool SequencedWorkerPool::Inner::IsShutdownInProgress() {
- AutoLock lock(lock_);
- return shutdown_called_;
-}
-
-void SequencedWorkerPool::Inner::ThreadLoop(Worker* this_worker) {
- DCHECK_EQ(AllPoolsState::USE_WORKER_POOL, g_all_pools_state);
- {
- AutoLock lock(lock_);
- DCHECK(thread_being_created_);
- thread_being_created_ = false;
- auto result = threads_.insert(
- std::make_pair(this_worker->tid(), WrapUnique(this_worker)));
- DCHECK(result.second);
-
- while (true) {
-#if defined(OS_MACOSX)
- base::mac::ScopedNSAutoreleasePool autorelease_pool;
-#endif
-
- HandleCleanup();
-
- // See GetWork for what delete_these_outside_lock is doing.
- SequencedTask task;
- TimeDelta wait_time;
- std::vector<SequencedTask> delete_these_outside_lock;
- GetWorkStatus status =
- GetWork(&task, &wait_time, &delete_these_outside_lock);
- if (status == GET_WORK_FOUND) {
- TRACE_TASK_EXECUTION("SequencedWorkerPool::Inner::ThreadLoop", task);
- TRACE_EVENT_WITH_FLOW0(TRACE_DISABLED_BY_DEFAULT("toplevel.flow"),
- "SequencedWorkerPool::Inner::PostTask",
- TRACE_ID_MANGLE(GetTaskTraceID(task, static_cast<void*>(this))),
- TRACE_EVENT_FLAG_FLOW_IN);
- int new_thread_id = WillRunWorkerTask(task);
- {
- AutoUnlock unlock(lock_);
- // There may be more work available, so wake up another
- // worker thread. (Technically not required, since we
- // already get a signal for each new task, but it doesn't
- // hurt.)
- SignalHasWork();
- DeleteWithoutLock(&delete_these_outside_lock, this_worker);
-
- // Complete thread creation outside the lock if necessary.
- if (new_thread_id)
- FinishStartingAdditionalThread(new_thread_id);
-
- this_worker->set_running_task_info(
- SequenceToken(task.sequence_token_id), task.shutdown_behavior);
-
- tracked_objects::TaskStopwatch stopwatch;
- stopwatch.Start();
- std::move(task.task).Run();
- stopwatch.Stop();
-
- tracked_objects::ThreadData::TallyRunOnNamedThreadIfTracking(
- task, stopwatch);
-
- // Make sure our task is erased outside the lock for the
- // same reason we do this with delete_these_oustide_lock.
- // Also, do it before calling reset_running_task_info() so
- // that sequence-checking from within the task's destructor
- // still works.
- DCHECK(!task.task);
-
- this_worker->reset_running_task_info();
- }
- DidRunWorkerTask(task); // Must be done inside the lock.
- } else if (cleanup_state_ == CLEANUP_RUNNING) {
- switch (status) {
- case GET_WORK_WAIT: {
- AutoUnlock unlock(lock_);
- DeleteWithoutLock(&delete_these_outside_lock, this_worker);
- }
- break;
- case GET_WORK_NOT_FOUND:
- CHECK(delete_these_outside_lock.empty());
- cleanup_state_ = CLEANUP_FINISHING;
- cleanup_cv_.Broadcast();
- break;
- default:
- NOTREACHED();
- }
- } else {
- // When we're terminating and there's no more work, we can
- // shut down, other workers can complete any pending or new tasks.
- // We can get additional tasks posted after shutdown_called_ is set
- // but only worker threads are allowed to post tasks at that time, and
- // the workers responsible for posting those tasks will be available
- // to run them. Also, there may be some tasks stuck behind running
- // ones with the same sequence token, but additional threads won't
- // help this case.
- if (shutdown_called_ && blocking_shutdown_pending_task_count_ == 0) {
- AutoUnlock unlock(lock_);
- DeleteWithoutLock(&delete_these_outside_lock, this_worker);
- break;
- }
-
- // No work was found, but there are tasks that need deletion. The
- // deletion must happen outside of the lock.
- if (delete_these_outside_lock.size()) {
- AutoUnlock unlock(lock_);
- DeleteWithoutLock(&delete_these_outside_lock, this_worker);
-
- // Since the lock has been released, |status| may no longer be
- // accurate. It might read GET_WORK_WAIT even if there are tasks
- // ready to perform work. Jump to the top of the loop to recalculate
- // |status|.
- continue;
- }
-
- waiting_thread_count_++;
-
- switch (status) {
- case GET_WORK_NOT_FOUND:
- has_work_cv_.Wait();
- break;
- case GET_WORK_WAIT:
- has_work_cv_.TimedWait(wait_time);
- break;
- default:
- NOTREACHED();
- }
- waiting_thread_count_--;
- }
- // |delete_these_outside_lock| should have been cleared via
- // DeleteWithoutLock() above already.
- DCHECK(delete_these_outside_lock.empty());
- }
- } // Release lock_.
-
- // We noticed we should exit. Wake up the next worker so it knows it should
- // exit as well (because the Shutdown() code only signals once).
- SignalHasWork();
-
- // Possibly unblock shutdown.
- can_shutdown_cv_.Signal();
-}
-
-void SequencedWorkerPool::Inner::DeleteWithoutLock(
- std::vector<SequencedTask>* tasks_to_delete,
- Worker* this_worker) {
- while (!tasks_to_delete->empty()) {
- const SequencedTask& deleted_task = tasks_to_delete->back();
- this_worker->set_running_task_info(
- SequenceToken(deleted_task.sequence_token_id),
- deleted_task.shutdown_behavior);
- tasks_to_delete->pop_back();
- }
- this_worker->reset_running_task_info();
-}
-
-void SequencedWorkerPool::Inner::HandleCleanup() {
- DCHECK_EQ(AllPoolsState::USE_WORKER_POOL, g_all_pools_state);
-
- lock_.AssertAcquired();
- if (cleanup_state_ == CLEANUP_DONE)
- return;
- if (cleanup_state_ == CLEANUP_REQUESTED) {
- // We win, we get to do the cleanup as soon as the others wise up and idle.
- cleanup_state_ = CLEANUP_STARTING;
- while (thread_being_created_ ||
- cleanup_idlers_ != threads_.size() - 1) {
- has_work_cv_.Signal();
- cleanup_cv_.Wait();
- }
- cleanup_state_ = CLEANUP_RUNNING;
- return;
- }
- if (cleanup_state_ == CLEANUP_STARTING) {
- // Another worker thread is cleaning up, we idle here until thats done.
- ++cleanup_idlers_;
- cleanup_cv_.Broadcast();
- while (cleanup_state_ != CLEANUP_FINISHING) {
- cleanup_cv_.Wait();
- }
- --cleanup_idlers_;
- cleanup_cv_.Broadcast();
- return;
- }
- if (cleanup_state_ == CLEANUP_FINISHING) {
- // We wait for all idlers to wake up prior to being DONE.
- while (cleanup_idlers_ != 0) {
- cleanup_cv_.Broadcast();
- cleanup_cv_.Wait();
- }
- if (cleanup_state_ == CLEANUP_FINISHING) {
- cleanup_state_ = CLEANUP_DONE;
- cleanup_cv_.Signal();
- }
- return;
- }
-}
-
-int SequencedWorkerPool::Inner::LockedGetNamedTokenID(
- const std::string& name) {
- lock_.AssertAcquired();
- DCHECK(!name.empty());
-
- std::map<std::string, int>::const_iterator found =
- named_sequence_tokens_.find(name);
- if (found != named_sequence_tokens_.end())
- return found->second; // Got an existing one.
-
- // Create a new one for this name.
- SequenceToken result = GetSequenceToken();
- named_sequence_tokens_.insert(std::make_pair(name, result.id_));
- return result.id_;
-}
-
-int64_t SequencedWorkerPool::Inner::LockedGetNextSequenceTaskNumber() {
- lock_.AssertAcquired();
- // We assume that we never create enough tasks to wrap around.
- return next_sequence_task_number_++;
-}
-
-SequencedWorkerPool::Inner::GetWorkStatus SequencedWorkerPool::Inner::GetWork(
- SequencedTask* task,
- TimeDelta* wait_time,
- std::vector<SequencedTask>* delete_these_outside_lock) {
- DCHECK_EQ(AllPoolsState::USE_WORKER_POOL, g_all_pools_state);
-
- lock_.AssertAcquired();
-
- // Find the next task with a sequence token that's not currently in use.
- // If the token is in use, that means another thread is running something
- // in that sequence, and we can't run it without going out-of-order.
- //
- // This algorithm is simple and fair, but inefficient in some cases. For
- // example, say somebody schedules 1000 slow tasks with the same sequence
- // number. We'll have to go through all those tasks each time we feel like
- // there might be work to schedule. If this proves to be a problem, we
- // should make this more efficient.
- //
- // One possible enhancement would be to keep a map from sequence ID to a
- // list of pending but currently blocked SequencedTasks for that ID.
- // When a worker finishes a task of one sequence token, it can pick up the
- // next one from that token right away.
- //
- // This may lead to starvation if there are sufficient numbers of sequences
- // in use. To alleviate this, we could add an incrementing priority counter
- // to each SequencedTask. Then maintain a priority_queue of all runnable
- // tasks, sorted by priority counter. When a sequenced task is completed
- // we would pop the head element off of that tasks pending list and add it
- // to the priority queue. Then we would run the first item in the priority
- // queue.
-
- GetWorkStatus status = GET_WORK_NOT_FOUND;
- int unrunnable_tasks = 0;
- PendingTaskSet::iterator i = pending_tasks_.begin();
- // We assume that the loop below doesn't take too long and so we can just do
- // a single call to TimeTicks::Now().
- const TimeTicks current_time = TimeTicks::Now();
- while (i != pending_tasks_.end()) {
- if (!IsSequenceTokenRunnable(i->sequence_token_id)) {
- unrunnable_tasks++;
- ++i;
- continue;
- }
-
- if (shutdown_called_ && i->shutdown_behavior != BLOCK_SHUTDOWN) {
- // We're shutting down and the task we just found isn't blocking
- // shutdown. Delete it and get more work.
- //
- // Note that we do not want to delete unrunnable tasks. Deleting a task
- // can have side effects (like freeing some objects) and deleting a task
- // that's supposed to run after one that's currently running could cause
- // an obscure crash.
- //
- // We really want to delete these tasks outside the lock in case the
- // closures are holding refs to objects that want to post work from their
- // destructors (which would deadlock). The closures are internally
- // refcounted, so we just need to keep a copy of them alive until the lock
- // is exited. The calling code can just clear() the vector they passed to
- // us once the lock is exited to make this happen.
- //
- // The const_cast here is safe since the object is erased from
- // |pending_tasks_| soon after the move.
- delete_these_outside_lock->push_back(
- std::move(const_cast<SequencedTask&>(*i)));
- pending_tasks_.erase(i++);
- continue;
- }
-
- if (i->time_to_run > current_time) {
- // The time to run has not come yet.
- *wait_time = i->time_to_run - current_time;
- status = GET_WORK_WAIT;
- if (cleanup_state_ == CLEANUP_RUNNING) {
- // Deferred tasks are deleted when cleaning up, see Inner::ThreadLoop.
- // The const_cast here is safe since the object is erased from
- // |pending_tasks_| soon after the move.
- delete_these_outside_lock->push_back(
- std::move(const_cast<SequencedTask&>(*i)));
- pending_tasks_.erase(i);
- }
- break;
- }
-
- // Found a runnable task. The const_cast is safe here since the object is
- // erased from |pending_tasks_| soon after the move.
- *task = std::move(const_cast<SequencedTask&>(*i));
- pending_tasks_.erase(i);
- if (task->shutdown_behavior == BLOCK_SHUTDOWN) {
- blocking_shutdown_pending_task_count_--;
- }
-
- status = GET_WORK_FOUND;
- break;
- }
-
- return status;
-}
-
-int SequencedWorkerPool::Inner::WillRunWorkerTask(const SequencedTask& task) {
- DCHECK_EQ(AllPoolsState::USE_WORKER_POOL, g_all_pools_state);
-
- lock_.AssertAcquired();
-
- // Mark the task's sequence number as in use.
- if (task.sequence_token_id)
- current_sequences_.insert(task.sequence_token_id);
-
- // Ensure that threads running tasks posted with either SKIP_ON_SHUTDOWN
- // or BLOCK_SHUTDOWN will prevent shutdown until that task or thread
- // completes.
- if (task.shutdown_behavior != CONTINUE_ON_SHUTDOWN)
- blocking_shutdown_thread_count_++;
-
- // We just picked up a task. Since StartAdditionalThreadIfHelpful only
- // creates a new thread if there is no free one, there is a race when posting
- // tasks that many tasks could have been posted before a thread started
- // running them, so only one thread would have been created. So we also check
- // whether we should create more threads after removing our task from the
- // queue, which also has the nice side effect of creating the workers from
- // background threads rather than the main thread of the app.
- //
- // If another thread wasn't created, we want to wake up an existing thread
- // if there is one waiting to pick up the next task.
- //
- // Note that we really need to do this *before* running the task, not
- // after. Otherwise, if more than one task is posted, the creation of the
- // second thread (since we only create one at a time) will be blocked by
- // the execution of the first task, which could be arbitrarily long.
- return PrepareToStartAdditionalThreadIfHelpful();
-}
-
-void SequencedWorkerPool::Inner::DidRunWorkerTask(const SequencedTask& task) {
- DCHECK_EQ(AllPoolsState::USE_WORKER_POOL, g_all_pools_state);
-
- lock_.AssertAcquired();
-
- if (task.shutdown_behavior != CONTINUE_ON_SHUTDOWN) {
- DCHECK_GT(blocking_shutdown_thread_count_, 0u);
- blocking_shutdown_thread_count_--;
- }
-
- if (task.sequence_token_id)
- current_sequences_.erase(task.sequence_token_id);
-}
-
-bool SequencedWorkerPool::Inner::IsSequenceTokenRunnable(
- int sequence_token_id) const {
- DCHECK_NE(AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER, g_all_pools_state);
-
- lock_.AssertAcquired();
- return !sequence_token_id ||
- current_sequences_.find(sequence_token_id) ==
- current_sequences_.end();
-}
-
-int SequencedWorkerPool::Inner::PrepareToStartAdditionalThreadIfHelpful() {
- DCHECK_NE(AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER, g_all_pools_state);
-
- lock_.AssertAcquired();
- // How thread creation works:
- //
- // We'de like to avoid creating threads with the lock held. However, we
- // need to be sure that we have an accurate accounting of the threads for
- // proper Joining and deltion on shutdown.
- //
- // We need to figure out if we need another thread with the lock held, which
- // is what this function does. It then marks us as in the process of creating
- // a thread. When we do shutdown, we wait until the thread_being_created_
- // flag is cleared, which ensures that the new thread is properly added to
- // all the data structures and we can't leak it. Once shutdown starts, we'll
- // refuse to create more threads or they would be leaked.
- //
- // Note that this creates a mostly benign race condition on shutdown that
- // will cause fewer workers to be created than one would expect. It isn't
- // much of an issue in real life, but affects some tests. Since we only spawn
- // one worker at a time, the following sequence of events can happen:
- //
- // 1. Main thread posts a bunch of unrelated tasks that would normally be
- // run on separate threads.
- // 2. The first task post causes us to start a worker. Other tasks do not
- // cause a worker to start since one is pending.
- // 3. Main thread initiates shutdown.
- // 4. No more threads are created since the shutdown_called_ flag is set.
- //
- // The result is that one may expect that max_threads_ workers to be created
- // given the workload, but in reality fewer may be created because the
- // sequence of thread creation on the background threads is racing with the
- // shutdown call.
- if (!shutdown_called_ &&
- !thread_being_created_ &&
- cleanup_state_ == CLEANUP_DONE &&
- threads_.size() < max_threads_ &&
- waiting_thread_count_ == 0) {
- // We could use an additional thread if there's work to be done.
- for (PendingTaskSet::const_iterator i = pending_tasks_.begin();
- i != pending_tasks_.end(); ++i) {
- if (IsSequenceTokenRunnable(i->sequence_token_id)) {
- // Found a runnable task, mark the thread as being started.
- thread_being_created_ = true;
- return static_cast<int>(threads_.size() + 1);
- }
- }
- }
- return 0;
-}
-
-void SequencedWorkerPool::Inner::FinishStartingAdditionalThread(
- int thread_number) {
- DCHECK_EQ(AllPoolsState::USE_WORKER_POOL, g_all_pools_state);
-
- // Called outside of the lock.
- DCHECK_GT(thread_number, 0);
-
- // The worker is assigned to the list when the thread actually starts, which
- // will manage the memory of the pointer.
- new Worker(worker_pool_, thread_number, thread_name_prefix_);
-}
-
-void SequencedWorkerPool::Inner::SignalHasWork() {
- DCHECK_NE(AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER, g_all_pools_state);
-
- has_work_cv_.Signal();
- if (testing_observer_) {
- testing_observer_->OnHasWork();
- }
-}
-
-bool SequencedWorkerPool::Inner::CanShutdown() const {
- DCHECK_EQ(AllPoolsState::USE_WORKER_POOL, g_all_pools_state);
- lock_.AssertAcquired();
- // See PrepareToStartAdditionalThreadIfHelpful for how thread creation works.
- return !thread_being_created_ &&
- blocking_shutdown_thread_count_ == 0 &&
- blocking_shutdown_pending_task_count_ == 0;
-}
-
-base::StaticAtomicSequenceNumber
-SequencedWorkerPool::Inner::g_last_sequence_number_;
-
-// SequencedWorkerPool --------------------------------------------------------
-
-std::string SequencedWorkerPool::SequenceToken::ToString() const {
- return base::StringPrintf("[%d]", id_);
-}
-
-// static
-SequencedWorkerPool::SequenceToken
-SequencedWorkerPool::GetSequenceTokenForCurrentThread() {
- Worker* worker = Worker::GetForCurrentThread();
- if (!worker)
- return SequenceToken();
-
- return worker->task_sequence_token();
-}
-
-// static
-scoped_refptr<SequencedWorkerPool>
-SequencedWorkerPool::GetWorkerPoolForCurrentThread() {
- Worker* worker = Worker::GetForCurrentThread();
- if (!worker)
- return nullptr;
-
- return worker->worker_pool();
-}
-
-// static
-void SequencedWorkerPool::EnableForProcess() {
- // 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.
- // DCHECK_EQ(AllPoolsState::POST_TASK_DISABLED, g_all_pools_state);
- g_all_pools_state = AllPoolsState::USE_WORKER_POOL;
-}
-
-// 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.
- // DCHECK_EQ(AllPoolsState::POST_TASK_DISABLED, g_all_pools_state);
- DCHECK(TaskScheduler::GetInstance());
- g_all_pools_state = AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER;
- g_max_task_priority = max_task_priority;
-#endif
-}
-
-// static
-void SequencedWorkerPool::DisableForProcessForTesting() {
- g_all_pools_state = AllPoolsState::POST_TASK_DISABLED;
-}
-
-// static
-bool SequencedWorkerPool::IsEnabled() {
- return g_all_pools_state != AllPoolsState::POST_TASK_DISABLED;
-}
-
-SequencedWorkerPool::SequencedWorkerPool(size_t max_threads,
- const std::string& thread_name_prefix,
- base::TaskPriority task_priority)
- : constructor_task_runner_(SequencedTaskRunnerHandle::Get()),
- inner_(new Inner(this,
- max_threads,
- thread_name_prefix,
- task_priority,
- NULL)) {}
-
-SequencedWorkerPool::SequencedWorkerPool(size_t max_threads,
- const std::string& thread_name_prefix,
- base::TaskPriority task_priority,
- TestingObserver* observer)
- : constructor_task_runner_(SequencedTaskRunnerHandle::Get()),
- inner_(new Inner(this,
- max_threads,
- thread_name_prefix,
- task_priority,
- observer)) {}
-
-SequencedWorkerPool::~SequencedWorkerPool() {}
-
-void SequencedWorkerPool::OnDestruct() const {
- // Avoid deleting ourselves on a worker thread (which would deadlock).
- if (RunsTasksOnCurrentThread()) {
- constructor_task_runner_->DeleteSoon(FROM_HERE, this);
- } else {
- delete this;
- }
-}
-
-// static
-SequencedWorkerPool::SequenceToken SequencedWorkerPool::GetSequenceToken() {
- return Inner::GetSequenceToken();
-}
-
-SequencedWorkerPool::SequenceToken SequencedWorkerPool::GetNamedSequenceToken(
- const std::string& name) {
- return inner_->GetNamedSequenceToken(name);
-}
-
-scoped_refptr<SequencedTaskRunner> SequencedWorkerPool::GetSequencedTaskRunner(
- SequenceToken token) {
- return GetSequencedTaskRunnerWithShutdownBehavior(token, BLOCK_SHUTDOWN);
-}
-
-scoped_refptr<SequencedTaskRunner>
-SequencedWorkerPool::GetSequencedTaskRunnerWithShutdownBehavior(
- SequenceToken token, WorkerShutdown shutdown_behavior) {
- return new PoolSequencedTaskRunner(
- this, token, shutdown_behavior);
-}
-
-scoped_refptr<TaskRunner>
-SequencedWorkerPool::GetTaskRunnerWithShutdownBehavior(
- WorkerShutdown shutdown_behavior) {
- return new SequencedWorkerPoolTaskRunner(this, shutdown_behavior);
-}
-
-bool SequencedWorkerPool::PostWorkerTask(
- const tracked_objects::Location& from_here,
- OnceClosure task) {
- return inner_->PostTask(NULL, SequenceToken(), BLOCK_SHUTDOWN, from_here,
- std::move(task), TimeDelta());
-}
-
-bool SequencedWorkerPool::PostDelayedWorkerTask(
- const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay) {
- WorkerShutdown shutdown_behavior =
- delay.is_zero() ? BLOCK_SHUTDOWN : SKIP_ON_SHUTDOWN;
- return inner_->PostTask(NULL, SequenceToken(), shutdown_behavior, from_here,
- std::move(task), delay);
-}
-
-bool SequencedWorkerPool::PostWorkerTaskWithShutdownBehavior(
- const tracked_objects::Location& from_here,
- OnceClosure task,
- WorkerShutdown shutdown_behavior) {
- return inner_->PostTask(NULL, SequenceToken(), shutdown_behavior, from_here,
- std::move(task), TimeDelta());
-}
-
-bool SequencedWorkerPool::PostSequencedWorkerTask(
- SequenceToken sequence_token,
- const tracked_objects::Location& from_here,
- OnceClosure task) {
- return inner_->PostTask(NULL, sequence_token, BLOCK_SHUTDOWN, from_here,
- std::move(task), TimeDelta());
-}
-
-bool SequencedWorkerPool::PostDelayedSequencedWorkerTask(
- SequenceToken sequence_token,
- const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay) {
- WorkerShutdown shutdown_behavior =
- delay.is_zero() ? BLOCK_SHUTDOWN : SKIP_ON_SHUTDOWN;
- return inner_->PostTask(NULL, sequence_token, shutdown_behavior, from_here,
- std::move(task), delay);
-}
-
-bool SequencedWorkerPool::PostNamedSequencedWorkerTask(
- const std::string& token_name,
- const tracked_objects::Location& from_here,
- OnceClosure task) {
- DCHECK(!token_name.empty());
- return inner_->PostTask(&token_name, SequenceToken(), BLOCK_SHUTDOWN,
- from_here, std::move(task), TimeDelta());
-}
-
-bool SequencedWorkerPool::PostSequencedWorkerTaskWithShutdownBehavior(
- SequenceToken sequence_token,
- const tracked_objects::Location& from_here,
- OnceClosure task,
- WorkerShutdown shutdown_behavior) {
- return inner_->PostTask(NULL, sequence_token, shutdown_behavior, from_here,
- std::move(task), TimeDelta());
-}
-
-bool SequencedWorkerPool::PostDelayedTask(
- const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay) {
- return PostDelayedWorkerTask(from_here, std::move(task), delay);
-}
-
-bool SequencedWorkerPool::RunsTasksOnCurrentThread() const {
- return inner_->RunsTasksOnCurrentThread();
-}
-
-void SequencedWorkerPool::FlushForTesting() {
- 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();
- }
-}
-
-void SequencedWorkerPool::SignalHasWorkForTesting() {
- inner_->SignalHasWorkForTesting();
-}
-
-void SequencedWorkerPool::Shutdown(int max_new_blocking_tasks_after_shutdown) {
- DCHECK(constructor_task_runner_->RunsTasksOnCurrentThread());
- inner_->Shutdown(max_new_blocking_tasks_after_shutdown);
-}
-
-bool SequencedWorkerPool::IsShutdownInProgress() {
- return inner_->IsShutdownInProgress();
-}
-
-bool SequencedWorkerPool::IsRunningSequenceOnCurrentThread(
- SequenceToken sequence_token) const {
- return inner_->IsRunningSequenceOnCurrentThread(sequence_token);
-}
-
-} // namespace base
diff --git a/base/threading/sequenced_worker_pool.h b/base/threading/sequenced_worker_pool.h
deleted file mode 100644
index e577e1be11..0000000000
--- a/base/threading/sequenced_worker_pool.h
+++ /dev/null
@@ -1,418 +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_THREADING_SEQUENCED_WORKER_POOL_H_
-#define BASE_THREADING_SEQUENCED_WORKER_POOL_H_
-
-#include <stddef.h>
-
-#include <cstddef>
-#include <memory>
-#include <string>
-
-#include "base/base_export.h"
-#include "base/callback.h"
-#include "base/compiler_specific.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/task_runner.h"
-#include "base/task_scheduler/task_traits.h"
-
-namespace tracked_objects {
-class Location;
-} // namespace tracked_objects
-
-namespace base {
-
-class SequencedTaskRunner;
-
-template <class T> class DeleteHelper;
-
-// A worker thread pool that enforces ordering between sets of tasks. It also
-// allows you to specify what should happen to your tasks on shutdown.
-//
-// To enforce ordering, get a unique sequence token from the pool and post all
-// tasks you want to order with the token. All tasks with the same token are
-// guaranteed to execute serially, though not necessarily on the same thread.
-// This means that:
-//
-// - No two tasks with the same token will run at the same time.
-//
-// - Given two tasks T1 and T2 with the same token such that T2 will
-// run after T1, then T2 will start after T1 is destroyed.
-//
-// - If T2 will run after T1, then all memory changes in T1 and T1's
-// destruction will be visible to T2.
-//
-// Example:
-// SequencedWorkerPool::SequenceToken token = pool.GetSequenceToken();
-// pool.PostSequencedWorkerTask(token, SequencedWorkerPool::SKIP_ON_SHUTDOWN,
-// FROM_HERE, base::Bind(...));
-// pool.PostSequencedWorkerTask(token, SequencedWorkerPool::SKIP_ON_SHUTDOWN,
-// FROM_HERE, base::Bind(...));
-//
-// You can make named sequence tokens to make it easier to share a token
-// across different components.
-//
-// You can also post tasks to the pool without ordering using PostWorkerTask.
-// These will be executed in an unspecified order. The order of execution
-// between tasks with different sequence tokens is also unspecified.
-//
-// You must call EnableForProcess() or
-// EnableWithRedirectionToTaskSchedulerForProcess() before starting to post
-// tasks to a process' SequencedWorkerPools.
-//
-// This class may be leaked on shutdown to facilitate fast shutdown. The
-// expected usage, however, is to call Shutdown(), which correctly accounts
-// for CONTINUE_ON_SHUTDOWN behavior and is required for BLOCK_SHUTDOWN
-// behavior.
-//
-// Implementation note: This does not use a base::WorkerPool since that does
-// not enforce shutdown semantics or allow us to specify how many worker
-// threads to run. For the typical use case of random background work, we don't
-// necessarily want to be super aggressive about creating threads.
-//
-// Note that SequencedWorkerPool is RefCountedThreadSafe (inherited
-// from TaskRunner).
-//
-// Test-only code should wrap this in a base::SequencedWorkerPoolOwner to avoid
-// memory leaks. See http://crbug.com/273800
-class BASE_EXPORT SequencedWorkerPool : public TaskRunner {
- public:
- // Defines what should happen to a task posted to the worker pool on
- // shutdown.
- enum WorkerShutdown {
- // Tasks posted with this mode which have not run at shutdown will be
- // deleted rather than run, and any tasks with this mode running at
- // shutdown will be ignored (the worker thread will not be joined).
- //
- // This option provides a nice way to post stuff you don't want blocking
- // shutdown. For example, you might be doing a slow DNS lookup and if it's
- // blocked on the OS, you may not want to stop shutdown, since the result
- // doesn't really matter at that point.
- //
- // However, you need to be very careful what you do in your callback when
- // you use this option. Since the thread will continue to run until the OS
- // terminates the process, the app can be in the process of tearing down
- // when you're running. This means any singletons or global objects you
- // use may suddenly become invalid out from under you. For this reason,
- // it's best to use this only for slow but simple operations like the DNS
- // example.
- CONTINUE_ON_SHUTDOWN,
-
- // Tasks posted with this mode that have not started executing at
- // shutdown will be deleted rather than executed. However, any tasks that
- // have already begun executing when shutdown is called will be allowed
- // to continue, and will block shutdown until completion.
- //
- // Note: Because Shutdown() may block while these tasks are executing,
- // care must be taken to ensure that they do not block on the thread that
- // called Shutdown(), as this may lead to deadlock.
- SKIP_ON_SHUTDOWN,
-
- // Tasks posted with this mode will block shutdown until they're
- // executed. Since this can have significant performance implications,
- // use sparingly.
- //
- // Generally, this should be used only for user data, for example, a task
- // writing a preference file.
- //
- // If a task is posted during shutdown, it will not get run since the
- // workers may already be stopped. In this case, the post operation will
- // fail (return false) and the task will be deleted.
- BLOCK_SHUTDOWN,
- };
-
- // Opaque identifier that defines sequencing of tasks posted to the worker
- // pool.
- class BASE_EXPORT SequenceToken {
- public:
- SequenceToken() : id_(0) {}
- ~SequenceToken() {}
-
- bool Equals(const SequenceToken& other) const {
- return id_ == other.id_;
- }
-
- // Returns false if current thread is executing an unsequenced task.
- bool IsValid() const {
- return id_ != 0;
- }
-
- // Returns a string representation of this token. This method should only be
- // used for debugging.
- std::string ToString() const;
-
- private:
- friend class SequencedWorkerPool;
-
- explicit SequenceToken(int id) : id_(id) {}
-
- int id_;
- };
-
- // Allows tests to perform certain actions.
- class TestingObserver {
- public:
- virtual ~TestingObserver() {}
- virtual void OnHasWork() = 0;
- virtual void WillWaitForShutdown() = 0;
- virtual void OnDestruct() = 0;
- };
-
- // Gets the SequencedToken of the current thread.
- // If current thread is not a SequencedWorkerPool worker thread or is running
- // an unsequenced task, returns an invalid SequenceToken.
- static SequenceToken GetSequenceTokenForCurrentThread();
-
- // Returns the SequencedWorkerPool that owns this thread, or null if the
- // current thread is not a SequencedWorkerPool worker thread.
- //
- // Always returns nullptr when SequencedWorkerPool is redirected to
- // TaskScheduler.
- //
- // DEPRECATED. Use SequencedTaskRunnerHandle::Get() instead. Consequentially
- // the only remaining use case is in sequenced_task_runner_handle.cc to
- // implement that and will soon be removed along with SequencedWorkerPool:
- // http://crbug.com/622400.
- static scoped_refptr<SequencedWorkerPool> GetWorkerPoolForCurrentThread();
-
- // Returns a unique token that can be used to sequence tasks posted to
- // PostSequencedWorkerTask(). Valid tokens are always nonzero.
- static SequenceToken GetSequenceToken();
-
- // Enables posting tasks to this process' SequencedWorkerPools. Cannot be
- // called if already enabled. This is not thread-safe; proper synchronization
- // is required to use any SequencedWorkerPool method after calling this.
- static void EnableForProcess();
-
- // Same as EnableForProcess(), but tasks are redirected to the registered
- // TaskScheduler. All redirections' TaskPriority will be capped to
- // |max_task_priority|. There must be a registered TaskScheduler when this is
- // called.
- // TODO(gab): Remove this if http://crbug.com/622400 fails
- // (SequencedWorkerPool will be phased out completely otherwise).
- static void EnableWithRedirectionToTaskSchedulerForProcess(
- TaskPriority max_task_priority = TaskPriority::HIGHEST);
-
- // Disables posting tasks to this process' SequencedWorkerPools. Calling this
- // while there are active SequencedWorkerPools is not supported. This is not
- // thread-safe; proper synchronization is required to use any
- // SequencedWorkerPool method after calling this.
- static void DisableForProcessForTesting();
-
- // Returns true if posting tasks to this process' SequencedWorkerPool is
- // enabled (with or without redirection to TaskScheduler).
- static bool IsEnabled();
-
- // When constructing a SequencedWorkerPool, there must be a
- // ThreadTaskRunnerHandle on the current thread unless you plan to
- // deliberately leak it.
-
- // Constructs a SequencedWorkerPool which will lazily create up to
- // |max_threads| and a prefix for the thread name to aid in debugging.
- // |max_threads| must be greater than 1. |task_priority| will be used to hint
- // base::TaskScheduler for an experiment in which all SequencedWorkerPool
- // tasks will be redirected to it in processes where a base::TaskScheduler was
- // instantiated.
- SequencedWorkerPool(size_t max_threads,
- const std::string& thread_name_prefix,
- base::TaskPriority task_priority);
-
- // Like above, but with |observer| for testing. Does not take ownership of
- // |observer|.
- SequencedWorkerPool(size_t max_threads,
- const std::string& thread_name_prefix,
- base::TaskPriority task_priority,
- TestingObserver* observer);
-
- // Returns the sequence token associated with the given name. Calling this
- // function multiple times with the same string will always produce the
- // same sequence token. If the name has not been used before, a new token
- // will be created.
- SequenceToken GetNamedSequenceToken(const std::string& name);
-
- // Returns a SequencedTaskRunner wrapper which posts to this
- // SequencedWorkerPool using the given sequence token. Tasks with nonzero
- // delay are posted with SKIP_ON_SHUTDOWN behavior and tasks with zero delay
- // are posted with BLOCK_SHUTDOWN behavior.
- scoped_refptr<SequencedTaskRunner> GetSequencedTaskRunner(
- SequenceToken token) WARN_UNUSED_RESULT;
-
- // Returns a SequencedTaskRunner wrapper which posts to this
- // SequencedWorkerPool using the given sequence token. Tasks with nonzero
- // delay are posted with SKIP_ON_SHUTDOWN behavior and tasks with zero delay
- // are posted with the given shutdown behavior.
- scoped_refptr<SequencedTaskRunner> GetSequencedTaskRunnerWithShutdownBehavior(
- SequenceToken token,
- WorkerShutdown shutdown_behavior) WARN_UNUSED_RESULT;
-
- // Returns a TaskRunner wrapper which posts to this SequencedWorkerPool using
- // the given shutdown behavior. Tasks with nonzero delay are posted with
- // SKIP_ON_SHUTDOWN behavior and tasks with zero delay are posted with the
- // given shutdown behavior.
- scoped_refptr<TaskRunner> GetTaskRunnerWithShutdownBehavior(
- WorkerShutdown shutdown_behavior) WARN_UNUSED_RESULT;
-
- // Posts the given task for execution in the worker pool. Tasks posted with
- // this function will execute in an unspecified order on a background thread.
- // Returns true if the task was posted. If your tasks have ordering
- // requirements, see PostSequencedWorkerTask().
- //
- // This class will attempt to delete tasks that aren't run
- // (non-block-shutdown semantics) but can't guarantee that this happens. If
- // all worker threads are busy running CONTINUE_ON_SHUTDOWN tasks, there
- // will be no workers available to delete these tasks. And there may be
- // tasks with the same sequence token behind those CONTINUE_ON_SHUTDOWN
- // tasks. Deleting those tasks before the previous one has completed could
- // cause nondeterministic crashes because the task could be keeping some
- // objects alive which do work in their destructor, which could voilate the
- // assumptions of the running task.
- //
- // The task will be guaranteed to run to completion before shutdown
- // (BLOCK_SHUTDOWN semantics).
- //
- // Returns true if the task was posted successfully. This may fail during
- // shutdown regardless of the specified ShutdownBehavior.
- bool PostWorkerTask(const tracked_objects::Location& from_here,
- OnceClosure task);
-
- // Same as PostWorkerTask but allows a delay to be specified (although doing
- // so changes the shutdown behavior). The task will be run after the given
- // delay has elapsed.
- //
- // If the delay is nonzero, the task won't be guaranteed to run to completion
- // before shutdown (SKIP_ON_SHUTDOWN semantics) to avoid shutdown hangs.
- // If the delay is zero, this behaves exactly like PostWorkerTask, i.e. the
- // task will be guaranteed to run to completion before shutdown
- // (BLOCK_SHUTDOWN semantics).
- bool PostDelayedWorkerTask(const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay);
-
- // Same as PostWorkerTask but allows specification of the shutdown behavior.
- bool PostWorkerTaskWithShutdownBehavior(
- const tracked_objects::Location& from_here,
- OnceClosure task,
- WorkerShutdown shutdown_behavior);
-
- // Like PostWorkerTask above, but provides sequencing semantics. This means
- // that tasks posted with the same sequence token (see GetSequenceToken())
- // are guaranteed to execute in order. This is useful in cases where you're
- // doing operations that may depend on previous ones, like appending to a
- // file.
- //
- // The task will be guaranteed to run to completion before shutdown
- // (BLOCK_SHUTDOWN semantics).
- //
- // Returns true if the task was posted successfully. This may fail during
- // shutdown regardless of the specified ShutdownBehavior.
- bool PostSequencedWorkerTask(SequenceToken sequence_token,
- const tracked_objects::Location& from_here,
- OnceClosure task);
-
- // Like PostSequencedWorkerTask above, but allows you to specify a named
- // token, which saves an extra call to GetNamedSequenceToken.
- bool PostNamedSequencedWorkerTask(const std::string& token_name,
- const tracked_objects::Location& from_here,
- OnceClosure task);
-
- // Same as PostSequencedWorkerTask but allows a delay to be specified
- // (although doing so changes the shutdown behavior). The task will be run
- // after the given delay has elapsed.
- //
- // If the delay is nonzero, the task won't be guaranteed to run to completion
- // before shutdown (SKIP_ON_SHUTDOWN semantics) to avoid shutdown hangs.
- // If the delay is zero, this behaves exactly like PostSequencedWorkerTask,
- // i.e. the task will be guaranteed to run to completion before shutdown
- // (BLOCK_SHUTDOWN semantics).
- bool PostDelayedSequencedWorkerTask(
- SequenceToken sequence_token,
- const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay);
-
- // Same as PostSequencedWorkerTask but allows specification of the shutdown
- // behavior.
- bool PostSequencedWorkerTaskWithShutdownBehavior(
- SequenceToken sequence_token,
- const tracked_objects::Location& from_here,
- OnceClosure task,
- WorkerShutdown shutdown_behavior);
-
- // TaskRunner implementation. Forwards to PostDelayedWorkerTask().
- bool PostDelayedTask(const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay) override;
- bool RunsTasksOnCurrentThread() const override;
-
- // Blocks until all pending tasks are complete. This should only be called in
- // unit tests when you want to validate something that should have happened.
- // Does not wait for delayed tasks. If redirection to TaskScheduler is
- // disabled, delayed tasks are deleted. If redirection to TaskScheduler is
- // enabled, this will wait for all tasks posted to TaskScheduler (not just
- // tasks posted to this SequencedWorkerPool).
- //
- // Note that calling this will not prevent other threads from posting work to
- // the queue while the calling thread is waiting on Flush(). In this case,
- // Flush will return only when there's no more work in the queue. Normally,
- // this doesn't come up since in a test, all the work is being posted from
- // the main thread.
- //
- // TODO(gab): Remove mentions of TaskScheduler in this comment if
- // http://crbug.com/622400 fails.
- void FlushForTesting();
-
- // Spuriously signal that there is work to be done.
- void SignalHasWorkForTesting();
-
- // Implements the worker pool shutdown. This should be called during app
- // shutdown, and will discard/join with appropriate tasks before returning.
- // After this call, subsequent calls to post tasks will fail.
- //
- // Must be called from the same thread this object was constructed on.
- void Shutdown() { Shutdown(0); }
-
- // A variant that allows an arbitrary number of new blocking tasks to be
- // posted during shutdown. The tasks cannot be posted within the execution
- // context of tasks whose shutdown behavior is not BLOCKING_SHUTDOWN. Once
- // the limit is reached, subsequent calls to post task fail in all cases.
- // Must be called from the same thread this object was constructed on.
- void Shutdown(int max_new_blocking_tasks_after_shutdown);
-
- // Check if Shutdown was called for given threading pool. This method is used
- // for aborting time consuming operation to avoid blocking shutdown.
- //
- // Can be called from any thread.
- bool IsShutdownInProgress();
-
- protected:
- ~SequencedWorkerPool() override;
-
- void OnDestruct() const override;
-
- private:
- friend class RefCountedThreadSafe<SequencedWorkerPool>;
- friend class DeleteHelper<SequencedWorkerPool>;
-
- class Inner;
- class PoolSequencedTaskRunner;
- class Worker;
-
- // Returns true if the current thread is processing a task with the given
- // sequence_token.
- bool IsRunningSequenceOnCurrentThread(SequenceToken sequence_token) const;
-
- const scoped_refptr<SequencedTaskRunner> constructor_task_runner_;
-
- // Avoid pulling in too many headers by putting (almost) everything
- // into |inner_|.
- const std::unique_ptr<Inner> inner_;
-
- DISALLOW_COPY_AND_ASSIGN(SequencedWorkerPool);
-};
-
-} // namespace base
-
-#endif // BASE_THREADING_SEQUENCED_WORKER_POOL_H_
diff --git a/base/threading/simple_thread.cc b/base/threading/simple_thread.cc
index 9eb443afab..4b260627d9 100644
--- a/base/threading/simple_thread.cc
+++ b/base/threading/simple_thread.cc
@@ -28,27 +28,39 @@ SimpleThread::~SimpleThread() {
}
void SimpleThread::Start() {
- DCHECK(!HasBeenStarted()) << "Tried to Start a thread multiple times.";
- bool success =
- options_.joinable
- ? PlatformThread::CreateWithPriority(options_.stack_size, this,
- &thread_, options_.priority)
- : PlatformThread::CreateNonJoinableWithPriority(
- options_.stack_size, this, options_.priority);
- DCHECK(success);
+ StartAsync();
ThreadRestrictions::ScopedAllowWait allow_wait;
event_.Wait(); // Wait for the thread to complete initialization.
}
void SimpleThread::Join() {
DCHECK(options_.joinable) << "A non-joinable thread can't be joined.";
- DCHECK(HasBeenStarted()) << "Tried to Join a never-started thread.";
+ DCHECK(HasStartBeenAttempted()) << "Tried to Join a never-started thread.";
DCHECK(!HasBeenJoined()) << "Tried to Join a thread multiple times.";
+ BeforeJoin();
PlatformThread::Join(thread_);
thread_ = PlatformThreadHandle();
joined_ = true;
}
+void SimpleThread::StartAsync() {
+ DCHECK(!HasStartBeenAttempted()) << "Tried to Start a thread multiple times.";
+ start_called_ = true;
+ BeforeStart();
+ bool success =
+ options_.joinable
+ ? PlatformThread::CreateWithPriority(options_.stack_size, this,
+ &thread_, options_.priority)
+ : PlatformThread::CreateNonJoinableWithPriority(
+ options_.stack_size, this, options_.priority);
+ CHECK(success);
+}
+
+PlatformThreadId SimpleThread::tid() {
+ DCHECK(HasBeenStarted());
+ return tid_;
+}
+
bool SimpleThread::HasBeenStarted() {
ThreadRestrictions::ScopedAllowWait allow_wait;
return event_.IsSignaled();
@@ -65,6 +77,7 @@ void SimpleThread::ThreadMain() {
// We've initialized our new thread, signal that we're done to Start().
event_.Signal();
+ BeforeRun();
Run();
}
@@ -119,7 +132,7 @@ void DelegateSimpleThreadPool::JoinAll() {
DCHECK(!threads_.empty()) << "JoinAll() called with no outstanding threads.";
// Tell all our threads to quit their worker loop.
- AddWork(NULL, num_threads_);
+ AddWork(nullptr, num_threads_);
// Join and destroy all the worker threads.
for (int i = 0; i < num_threads_; ++i) {
@@ -140,7 +153,7 @@ void DelegateSimpleThreadPool::AddWork(Delegate* delegate, int repeat_count) {
}
void DelegateSimpleThreadPool::Run() {
- Delegate* work = NULL;
+ Delegate* work = nullptr;
while (true) {
dry_.Wait();
diff --git a/base/threading/simple_thread.h b/base/threading/simple_thread.h
index f9f5e91045..976f55789b 100644
--- a/base/threading/simple_thread.h
+++ b/base/threading/simple_thread.h
@@ -42,12 +42,12 @@
#include <stddef.h>
-#include <queue>
#include <string>
#include <vector>
#include "base/base_export.h"
#include "base/compiler_specific.h"
+#include "base/containers/queue.h"
#include "base/macros.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
@@ -90,25 +90,56 @@ class BASE_EXPORT SimpleThread : public PlatformThread::Delegate {
~SimpleThread() override;
- virtual void Start();
- virtual void Join();
+ // Starts the thread and returns only after the thread has started and
+ // initialized (i.e. ThreadMain() has been called).
+ void Start();
+
+ // Joins the thread. If StartAsync() was used to start the thread, then this
+ // first waits for the thread to start cleanly, then it joins.
+ void Join();
+
+ // Starts the thread, but returns immediately, without waiting for the thread
+ // to have initialized first (i.e. this does not wait for ThreadMain() to have
+ // been run first).
+ void StartAsync();
// Subclasses should override the Run method.
virtual void Run() = 0;
- // Return the thread id, only valid after Start().
- PlatformThreadId tid() { return tid_; }
+ // Returns the thread id, only valid after the thread has started. If the
+ // thread was started using Start(), then this will be valid after the call to
+ // Start(). If StartAsync() was used to start the thread, then this must not
+ // be called before HasBeenStarted() returns True.
+ PlatformThreadId tid();
- // Return True if Start() has ever been called.
+ // Returns True if the thread has been started and initialized (i.e. if
+ // ThreadMain() has run). If the thread was started with StartAsync(), but it
+ // hasn't been initialized yet (i.e. ThreadMain() has not run), then this will
+ // return False.
bool HasBeenStarted();
- // Return True if Join() has ever been called.
+ // Returns True if Join() has ever been called.
bool HasBeenJoined() { return joined_; }
+ // Returns true if Start() or StartAsync() has been called.
+ bool HasStartBeenAttempted() { return start_called_; }
+
// Overridden from PlatformThread::Delegate:
void ThreadMain() override;
private:
+ // This is called just before the thread is started. This is called regardless
+ // of whether Start() or StartAsync() is used to start the thread.
+ virtual void BeforeStart() {}
+
+ // This is called just after the thread has been initialized and just before
+ // Run() is called. This is called on the newly started thread.
+ virtual void BeforeRun() {}
+
+ // This is called just before the thread is joined. The thread is started and
+ // has been initialized before this is called.
+ virtual void BeforeJoin() {}
+
const std::string name_prefix_;
std::string name_;
const Options options_;
@@ -116,6 +147,8 @@ class BASE_EXPORT SimpleThread : public PlatformThread::Delegate {
WaitableEvent event_; // Signaled if Start() was ever called.
PlatformThreadId tid_ = kInvalidThreadId; // The backing thread's id.
bool joined_ = false; // True if Join has been called.
+ // Set to true when the platform-thread creation has started.
+ bool start_called_ = false;
DISALLOW_COPY_AND_ASSIGN(SimpleThread);
};
@@ -187,7 +220,7 @@ class BASE_EXPORT DelegateSimpleThreadPool
const std::string name_prefix_;
int num_threads_;
std::vector<DelegateSimpleThread*> threads_;
- std::queue<Delegate*> delegates_;
+ base::queue<Delegate*> delegates_;
base::Lock lock_; // Locks delegates_
WaitableEvent dry_; // Not signaled when there is no work to do.
diff --git a/base/threading/simple_thread_unittest.cc b/base/threading/simple_thread_unittest.cc
index 0e52500c52..4e618f9c6a 100644
--- a/base/threading/simple_thread_unittest.cc
+++ b/base/threading/simple_thread_unittest.cc
@@ -19,7 +19,7 @@ namespace {
class SetIntRunner : public DelegateSimpleThread::Delegate {
public:
SetIntRunner(int* ptr, int val) : ptr_(ptr), val_(val) { }
- ~SetIntRunner() override {}
+ ~SetIntRunner() override = default;
private:
void Run() override { *ptr_ = val_; }
@@ -69,7 +69,7 @@ class ControlledRunner : public DelegateSimpleThread::Delegate {
class WaitEventRunner : public DelegateSimpleThread::Delegate {
public:
explicit WaitEventRunner(WaitableEvent* event) : event_(event) { }
- ~WaitEventRunner() override {}
+ ~WaitEventRunner() override = default;
private:
void Run() override {
diff --git a/base/threading/thread.cc b/base/threading/thread.cc
index 0aeed2a9e4..97e160f91e 100644
--- a/base/threading/thread.cc
+++ b/base/threading/thread.cc
@@ -156,7 +156,7 @@ void Thread::FlushForTesting() {
WaitableEvent done(WaitableEvent::ResetPolicy::AUTOMATIC,
WaitableEvent::InitialState::NOT_SIGNALED);
task_runner()->PostTask(FROM_HERE,
- Bind(&WaitableEvent::Signal, Unretained(&done)));
+ BindOnce(&WaitableEvent::Signal, Unretained(&done)));
done.Wait();
}
@@ -210,7 +210,7 @@ void Thread::StopSoon() {
}
task_runner()->PostTask(
- FROM_HERE, base::Bind(&Thread::ThreadQuitHelper, Unretained(this)));
+ FROM_HERE, base::BindOnce(&Thread::ThreadQuitHelper, Unretained(this)));
}
void Thread::DetachFromSequence() {
@@ -225,6 +225,11 @@ PlatformThreadId Thread::GetThreadId() const {
return id_;
}
+PlatformThreadHandle Thread::GetThreadHandle() const {
+ AutoLock lock(thread_lock_);
+ return thread_;
+}
+
bool Thread::IsRunning() const {
// TODO(gab): Fix improper usage of this API (http://crbug.com/629139) and
// enable this check.
@@ -303,9 +308,8 @@ void Thread::ThreadMain() {
// Allow threads running a MessageLoopForIO to use FileDescriptorWatcher API.
std::unique_ptr<FileDescriptorWatcher> file_descriptor_watcher;
if (MessageLoopForIO::IsCurrent()) {
- DCHECK_EQ(message_loop_, MessageLoopForIO::current());
- file_descriptor_watcher.reset(
- new FileDescriptorWatcher(MessageLoopForIO::current()));
+ file_descriptor_watcher.reset(new FileDescriptorWatcher(
+ static_cast<MessageLoopForIO*>(message_loop_)));
}
#endif
diff --git a/base/threading/thread.h b/base/threading/thread.h
index 01f7d8e250..9fbdcb8852 100644
--- a/base/threading/thread.h
+++ b/base/threading/thread.h
@@ -28,6 +28,9 @@ namespace base {
class MessagePump;
class RunLoop;
+// IMPORTANT: Instead of creating a base::Thread, consider using
+// base::Create(Sequenced|SingleThread)TaskRunnerWithTraits().
+//
// A simple thread abstraction that establishes a MessageLoop on a new thread.
// The consumer uses the MessageLoop of the thread to cause code to execute on
// the thread. When this object is destroyed the thread is terminated. All
@@ -40,7 +43,7 @@ class RunLoop;
//
// (1) Thread::CleanUp()
// (2) MessageLoop::~MessageLoop
-// (3.b) MessageLoop::DestructionObserver::WillDestroyCurrentMessageLoop
+// (3.b) MessageLoopCurrent::DestructionObserver::WillDestroyCurrentMessageLoop
//
// This API is not thread-safe: unless indicated otherwise its methods are only
// valid from the owning sequence (which is the one from which Start() is
@@ -242,6 +245,15 @@ class BASE_EXPORT Thread : PlatformThread::Delegate {
// This method is thread-safe.
PlatformThreadId GetThreadId() const;
+ // Returns the current thread handle. If called before Start*() returns or
+ // after Stop() returns, an empty thread handle will be returned.
+ //
+ // This method is thread-safe.
+ //
+ // TODO(robliao): Remove this when it no longer needs to be temporarily
+ // exposed for http://crbug.com/717380.
+ PlatformThreadHandle GetThreadHandle() const;
+
// Returns true if the thread has been started, and not yet stopped.
bool IsRunning() const;
@@ -259,6 +271,9 @@ class BASE_EXPORT Thread : PlatformThread::Delegate {
static bool GetThreadWasQuitProperly();
// Bind this Thread to an existing MessageLoop instead of starting a new one.
+ // TODO(gab): Remove this after ios/ has undergone the same surgery as
+ // BrowserThreadImpl (ref.
+ // https://chromium-review.googlesource.com/c/chromium/src/+/969104).
void SetMessageLoop(MessageLoop* message_loop);
bool using_external_message_loop() const {
diff --git a/base/threading/thread_checker.h b/base/threading/thread_checker.h
index 1d4eb1c7b0..6799e25813 100644
--- a/base/threading/thread_checker.h
+++ b/base/threading/thread_checker.h
@@ -5,61 +5,91 @@
#ifndef BASE_THREADING_THREAD_CHECKER_H_
#define BASE_THREADING_THREAD_CHECKER_H_
+#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/threading/thread_checker_impl.h"
+// ThreadChecker is a helper class used to help verify that some methods of a
+// class are called from the same thread (for thread-affinity).
+//
+// Use the macros below instead of the ThreadChecker directly so that the unused
+// member doesn't result in an extra byte (four when padded) per instance in
+// production.
+//
+// Usage of this class should be *rare* as most classes require thread-safety
+// but not thread-affinity. Prefer base::SequenceChecker to verify thread-safe
+// access.
+//
+// Thread-affinity checks should only be required in classes that use thread-
+// local-storage or a third-party API that does.
+//
+// Prefer to encode the minimum requirements of each class instead of the
+// environment it happens to run in today. e.g. if a class requires thread-
+// safety but not thread-affinity, use a SequenceChecker even if it happens to
+// run on a SingleThreadTaskRunner today. That makes it easier to understand
+// what would need to change to turn that SingleThreadTaskRunner into a
+// SequencedTaskRunner for ease of scheduling as well as minimizes side-effects
+// if that change is made.
+//
+// Usage:
+// class MyClass {
+// public:
+// MyClass() {
+// // It's sometimes useful to detach on construction for objects that are
+// // constructed in one place and forever after used from another
+// // thread.
+// DETACH_FROM_THREAD(my_thread_checker_);
+// }
+//
+// ~MyClass() {
+// // ThreadChecker doesn't automatically check it's destroyed on origin
+// // thread for the same reason it's sometimes detached in the
+// // constructor. It's okay to destroy off thread if the owner otherwise
+// // knows usage on the associated thread is done. If you're not
+// // detaching in the constructor, you probably want to explicitly check
+// // in the destructor.
+// DCHECK_CALLED_ON_VALID_THREAD(my_thread_checker_);
+// }
+//
+// void MyMethod() {
+// DCHECK_CALLED_ON_VALID_THREAD(my_thread_checker_);
+// ... (do stuff) ...
+// }
+//
+// private:
+// THREAD_CHECKER(my_thread_checker_);
+// }
+
+#if DCHECK_IS_ON()
+#define THREAD_CHECKER(name) base::ThreadChecker name
+#define DCHECK_CALLED_ON_VALID_THREAD(name) DCHECK((name).CalledOnValidThread())
+#define DETACH_FROM_THREAD(name) (name).DetachFromThread()
+#else // DCHECK_IS_ON()
+#define THREAD_CHECKER(name)
+#define DCHECK_CALLED_ON_VALID_THREAD(name) EAT_STREAM_PARAMETERS
+#define DETACH_FROM_THREAD(name)
+#endif // DCHECK_IS_ON()
+
namespace base {
// Do nothing implementation, for use in release mode.
//
-// Note: You should almost always use the ThreadChecker class to get the
-// right version for your build configuration.
+// Note: You should almost always use the ThreadChecker class (through the above
+// macros) to get the right version for your build configuration.
class ThreadCheckerDoNothing {
public:
- bool CalledOnValidThread() const WARN_UNUSED_RESULT {
- return true;
- }
-
+ ThreadCheckerDoNothing() = default;
+ bool CalledOnValidThread() const WARN_UNUSED_RESULT { return true; }
void DetachFromThread() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ThreadCheckerDoNothing);
};
-// ThreadChecker is a helper class used to help verify that some methods of a
-// class are called from the same thread. It provides identical functionality to
-// base::NonThreadSafe, but it is meant to be held as a member variable, rather
-// than inherited from base::NonThreadSafe.
-//
-// While inheriting from base::NonThreadSafe may give a clear indication about
-// the thread-safety of a class, it may also lead to violations of the style
-// guide with regard to multiple inheritance. The choice between having a
-// ThreadChecker member and inheriting from base::NonThreadSafe should be based
-// on whether:
-// - Derived classes need to know the thread they belong to, as opposed to
-// having that functionality fully encapsulated in the base class.
-// - Derived classes should be able to reassign the base class to another
-// thread, via DetachFromThread.
-//
-// If neither of these are true, then having a ThreadChecker member and calling
-// CalledOnValidThread is the preferable solution.
-//
-// Example:
-// class MyClass {
-// public:
-// void Foo() {
-// DCHECK(thread_checker_.CalledOnValidThread());
-// ... (do stuff) ...
-// }
-//
-// private:
-// ThreadChecker thread_checker_;
-// }
-//
-// Note that, when enabled, CalledOnValidThread() returns false when called from
-// tasks posted to SingleThreadTaskRunners bound to different sequences, even if
-// the tasks happen to run on the same thread (e.g. two independent TaskRunners
-// with ExecutionMode::SINGLE_THREADED on the TaskScheduler that happen to share
-// a thread).
-//
-// In Release mode, CalledOnValidThread will always return true.
+// Note that ThreadCheckerImpl::CalledOnValidThread() returns false when called
+// from tasks posted to SingleThreadTaskRunners bound to different sequences,
+// even if the tasks happen to run on the same thread (e.g. two independent
+// SingleThreadTaskRunners on the TaskScheduler that happen to share a thread).
#if DCHECK_IS_ON()
class ThreadChecker : public ThreadCheckerImpl {
};
diff --git a/base/threading/thread_checker_impl.h b/base/threading/thread_checker_impl.h
index 13193d1299..103dfe7192 100644
--- a/base/threading/thread_checker_impl.h
+++ b/base/threading/thread_checker_impl.h
@@ -45,9 +45,9 @@ class BASE_EXPORT ThreadCheckerImpl {
// TaskToken for which CalledOnValidThread() always returns true. This allows
// CalledOnValidThread() to return true when called multiple times from the
// same task, even if it's not running in a single-threaded context itself
- // (allowing usage of ThreadChecker/NonThreadSafe objects on the stack in the
- // scope of one-off tasks). Note: CalledOnValidThread() may return true even
- // if the current TaskToken is not equal to this.
+ // (allowing usage of ThreadChecker objects on the stack in the scope of one-
+ // off tasks). Note: CalledOnValidThread() may return true even if the current
+ // TaskToken is not equal to this.
mutable TaskToken task_token_;
// SequenceToken for which CalledOnValidThread() may return true. Used to
diff --git a/base/threading/thread_checker_unittest.cc b/base/threading/thread_checker_unittest.cc
index 96455e66c7..5fbbc5284a 100644
--- a/base/threading/thread_checker_unittest.cc
+++ b/base/threading/thread_checker_unittest.cc
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "base/threading/thread_checker.h"
+
#include <memory>
#include "base/bind.h"
@@ -9,9 +11,9 @@
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/sequence_token.h"
+#include "base/test/gtest_util.h"
#include "base/test/test_simple_task_runner.h"
#include "base/threading/simple_thread.h"
-#include "base/threading/thread_checker_impl.h"
#include "base/threading/thread_task_runner_handle.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -61,7 +63,7 @@ void ExpectNotCalledOnValidThreadWithSequenceTokenAndThreadTaskRunnerHandle(
ThreadCheckerImpl* thread_checker,
SequenceToken sequence_token) {
ThreadTaskRunnerHandle thread_task_runner_handle(
- make_scoped_refptr(new TestSimpleTaskRunner));
+ MakeRefCounted<TestSimpleTaskRunner>());
ScopedSetSequenceTokenForCurrentThread
scoped_set_sequence_token_for_current_thread(sequence_token);
ExpectNotCalledOnValidThread(thread_checker);
@@ -77,7 +79,7 @@ TEST(ThreadCheckerTest, AllowedSameThreadNoSequenceToken) {
TEST(ThreadCheckerTest,
AllowedSameThreadAndSequenceDifferentTasksWithThreadTaskRunnerHandle) {
ThreadTaskRunnerHandle thread_task_runner_handle(
- make_scoped_refptr(new TestSimpleTaskRunner));
+ MakeRefCounted<TestSimpleTaskRunner>());
std::unique_ptr<ThreadCheckerImpl> thread_checker;
const SequenceToken sequence_token = SequenceToken::Create();
@@ -128,7 +130,7 @@ TEST(ThreadCheckerTest, DisallowedDifferentThreadsNoSequenceToken) {
TEST(ThreadCheckerTest, DisallowedDifferentThreadsSameSequence) {
ThreadTaskRunnerHandle thread_task_runner_handle(
- make_scoped_refptr(new TestSimpleTaskRunner));
+ MakeRefCounted<TestSimpleTaskRunner>());
const SequenceToken sequence_token(SequenceToken::Create());
ScopedSetSequenceTokenForCurrentThread
@@ -145,7 +147,7 @@ TEST(ThreadCheckerTest, DisallowedSameThreadDifferentSequence) {
std::unique_ptr<ThreadCheckerImpl> thread_checker;
ThreadTaskRunnerHandle thread_task_runner_handle(
- make_scoped_refptr(new TestSimpleTaskRunner));
+ MakeRefCounted<TestSimpleTaskRunner>());
{
ScopedSetSequenceTokenForCurrentThread
@@ -178,7 +180,7 @@ TEST(ThreadCheckerTest, DetachFromThread) {
TEST(ThreadCheckerTest, DetachFromThreadWithSequenceToken) {
ThreadTaskRunnerHandle thread_task_runner_handle(
- make_scoped_refptr(new TestSimpleTaskRunner));
+ MakeRefCounted<TestSimpleTaskRunner>());
ScopedSetSequenceTokenForCurrentThread
scoped_set_sequence_token_for_current_thread(SequenceToken::Create());
ThreadCheckerImpl thread_checker;
@@ -192,4 +194,52 @@ TEST(ThreadCheckerTest, DetachFromThreadWithSequenceToken) {
EXPECT_FALSE(thread_checker.CalledOnValidThread());
}
+namespace {
+
+// This fixture is a helper for unit testing the thread checker macros as it is
+// not possible to inline ExpectDeathOnOtherThread() and
+// ExpectNoDeathOnOtherThreadAfterDetach() as lambdas since binding
+// |Unretained(&my_sequence_checker)| wouldn't compile on non-dcheck builds
+// where it won't be defined.
+class ThreadCheckerMacroTest : public testing::Test {
+ public:
+ ThreadCheckerMacroTest() = default;
+
+ void ExpectDeathOnOtherThread() {
+#if DCHECK_IS_ON()
+ EXPECT_DCHECK_DEATH({ DCHECK_CALLED_ON_VALID_THREAD(my_thread_checker_); });
+#else
+ // Happily no-ops on non-dcheck builds.
+ DCHECK_CALLED_ON_VALID_THREAD(my_thread_checker_);
+#endif
+ }
+
+ void ExpectNoDeathOnOtherThreadAfterDetach() {
+ DCHECK_CALLED_ON_VALID_THREAD(my_thread_checker_);
+ DCHECK_CALLED_ON_VALID_THREAD(my_thread_checker_)
+ << "Make sure it compiles when DCHECK is off";
+ }
+
+ protected:
+ THREAD_CHECKER(my_thread_checker_);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ThreadCheckerMacroTest);
+};
+
+} // namespace
+
+TEST_F(ThreadCheckerMacroTest, Macros) {
+ THREAD_CHECKER(my_thread_checker);
+
+ RunCallbackOnNewThreadSynchronously(Bind(
+ &ThreadCheckerMacroTest::ExpectDeathOnOtherThread, Unretained(this)));
+
+ DETACH_FROM_THREAD(my_thread_checker_);
+
+ RunCallbackOnNewThreadSynchronously(
+ Bind(&ThreadCheckerMacroTest::ExpectNoDeathOnOtherThreadAfterDetach,
+ Unretained(this)));
+}
+
} // namespace base
diff --git a/base/threading/thread_collision_warner.h b/base/threading/thread_collision_warner.h
index 4699a910dd..b6993f64d9 100644
--- a/base/threading/thread_collision_warner.h
+++ b/base/threading/thread_collision_warner.h
@@ -133,12 +133,12 @@ namespace base {
// used. During the unit tests is used another class that doesn't "DCHECK"
// in case of collision (check thread_collision_warner_unittests.cc)
struct BASE_EXPORT AsserterBase {
- virtual ~AsserterBase() {}
+ virtual ~AsserterBase() = default;
virtual void warn() = 0;
};
struct BASE_EXPORT DCheckAsserter : public AsserterBase {
- ~DCheckAsserter() override {}
+ ~DCheckAsserter() override = default;
void warn() override;
};
@@ -166,7 +166,7 @@ class BASE_EXPORT ThreadCollisionWarner {
warner_->EnterSelf();
}
- ~Check() {}
+ ~Check() = default;
private:
ThreadCollisionWarner* warner_;
diff --git a/base/threading/thread_collision_warner_unittest.cc b/base/threading/thread_collision_warner_unittest.cc
index 71447efd73..cd56768c61 100644
--- a/base/threading/thread_collision_warner_unittest.cc
+++ b/base/threading/thread_collision_warner_unittest.cc
@@ -46,7 +46,7 @@ class AssertReporter : public base::AsserterBase {
void warn() override { failed_ = true; }
- ~AssertReporter() override {}
+ ~AssertReporter() override = default;
bool fail_state() const { return failed_; }
void reset() { failed_ = false; }
diff --git a/base/threading/thread_id_name_manager.cc b/base/threading/thread_id_name_manager.cc
index c0ca42da30..1548828063 100644
--- a/base/threading/thread_id_name_manager.cc
+++ b/base/threading/thread_id_name_manager.cc
@@ -9,7 +9,9 @@
#include "base/logging.h"
#include "base/memory/singleton.h"
+#include "base/no_destructor.h"
#include "base/strings/string_util.h"
+#include "base/threading/thread_local.h"
// Unsupported in libchrome.
// #include "base/trace_event/heap_profiler_allocation_context_tracker.h"
@@ -19,19 +21,21 @@ namespace {
static const char kDefaultName[] = "";
static std::string* g_default_name;
+ThreadLocalStorage::Slot& GetThreadNameTLS() {
+ static base::NoDestructor<base::ThreadLocalStorage::Slot> thread_name_tls;
+ return *thread_name_tls;
+}
}
ThreadIdNameManager::ThreadIdNameManager()
- : main_process_name_(NULL),
- main_process_id_(kInvalidThreadId) {
+ : main_process_name_(nullptr), main_process_id_(kInvalidThreadId) {
g_default_name = new std::string(kDefaultName);
AutoLock locked(lock_);
name_to_interned_name_[kDefaultName] = g_default_name;
}
-ThreadIdNameManager::~ThreadIdNameManager() {
-}
+ThreadIdNameManager::~ThreadIdNameManager() = default;
ThreadIdNameManager* ThreadIdNameManager::GetInstance() {
return Singleton<ThreadIdNameManager,
@@ -50,9 +54,14 @@ void ThreadIdNameManager::RegisterThread(PlatformThreadHandle::Handle handle,
name_to_interned_name_[kDefaultName];
}
-void ThreadIdNameManager::SetName(PlatformThreadId id,
- const std::string& name) {
- std::string* leaked_str = NULL;
+void ThreadIdNameManager::InstallSetNameCallback(SetNameCallback callback) {
+ AutoLock locked(lock_);
+ set_name_callback_ = std::move(callback);
+}
+
+void ThreadIdNameManager::SetName(const std::string& name) {
+ PlatformThreadId id = PlatformThread::CurrentId();
+ std::string* leaked_str = nullptr;
{
AutoLock locked(lock_);
NameToInternedNameMap::iterator iter = name_to_interned_name_.find(name);
@@ -66,6 +75,11 @@ void ThreadIdNameManager::SetName(PlatformThreadId id,
ThreadIdToHandleMap::iterator id_to_handle_iter =
thread_id_to_handle_.find(id);
+ GetThreadNameTLS().Set(const_cast<char*>(leaked_str->c_str()));
+ if (set_name_callback_) {
+ set_name_callback_.Run(leaked_str->c_str());
+ }
+
// The main thread of a process will not be created as a Thread object which
// means there is no PlatformThreadHandler registered.
if (id_to_handle_iter == thread_id_to_handle_.end()) {
@@ -83,7 +97,7 @@ void ThreadIdNameManager::SetName(PlatformThreadId id,
// ThreadIdNameManager itself when holding the lock.
// Unsupported in libchrome.
// trace_event::AllocationContextTracker::SetCurrentThreadName(
- // leaked_str->c_str());
+ // leaked_str->c_str());
}
const char* ThreadIdNameManager::GetName(PlatformThreadId id) {
@@ -102,6 +116,11 @@ const char* ThreadIdNameManager::GetName(PlatformThreadId id) {
return handle_to_name_iter->second->c_str();
}
+const char* ThreadIdNameManager::GetNameForCurrentThread() {
+ const char* name = reinterpret_cast<const char*>(GetThreadNameTLS().Get());
+ return name ? name : kDefaultName;
+}
+
void ThreadIdNameManager::RemoveName(PlatformThreadHandle::Handle handle,
PlatformThreadId id) {
AutoLock locked(lock_);
diff --git a/base/threading/thread_id_name_manager.h b/base/threading/thread_id_name_manager.h
index f469b605e4..f17dc1a4e8 100644
--- a/base/threading/thread_id_name_manager.h
+++ b/base/threading/thread_id_name_manager.h
@@ -9,6 +9,7 @@
#include <string>
#include "base/base_export.h"
+#include "base/callback.h"
#include "base/macros.h"
#include "base/synchronization/lock.h"
#include "base/threading/platform_thread.h"
@@ -27,12 +28,21 @@ class BASE_EXPORT ThreadIdNameManager {
// Register the mapping between a thread |id| and |handle|.
void RegisterThread(PlatformThreadHandle::Handle handle, PlatformThreadId id);
- // Set the name for the given id.
- void SetName(PlatformThreadId id, const std::string& name);
+ // The callback is called on the thread, immediately after the name is set.
+ // |name| is a pointer to a C string that is guaranteed to remain valid for
+ // the duration of the process.
+ using SetNameCallback = base::RepeatingCallback<void(const char* name)>;
+ void InstallSetNameCallback(SetNameCallback callback);
+
+ // Set the name for the current thread.
+ void SetName(const std::string& name);
// Get the name for the given id.
const char* GetName(PlatformThreadId id);
+ // Unlike |GetName|, this method using TLS and avoids touching |lock_|.
+ const char* GetNameForCurrentThread();
+
// Remove the name for the given id.
void RemoveName(PlatformThreadHandle::Handle handle, PlatformThreadId id);
@@ -60,6 +70,8 @@ class BASE_EXPORT ThreadIdNameManager {
std::string* main_process_name_;
PlatformThreadId main_process_id_;
+ SetNameCallback set_name_callback_;
+
DISALLOW_COPY_AND_ASSIGN(ThreadIdNameManager);
};
diff --git a/base/threading/thread_local_storage.cc b/base/threading/thread_local_storage.cc
index 90ae69e90e..c9f8c620ed 100644
--- a/base/threading/thread_local_storage.cc
+++ b/base/threading/thread_local_storage.cc
@@ -69,9 +69,47 @@ namespace {
base::subtle::Atomic32 g_native_tls_key =
PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES;
+// The OS TLS slot has three states:
+// * kUninitialized: Any call to Slot::Get()/Set() will create the base
+// per-thread TLS state. On POSIX, kUninitialized must be 0.
+// * [Memory Address]: Raw pointer to the base per-thread TLS state.
+// * kDestroyed: The base per-thread TLS state has been freed.
+//
+// Final States:
+// * Windows: kDestroyed. Windows does not iterate through the OS TLS to clean
+// up the values.
+// * POSIX: kUninitialized. POSIX iterates through TLS until all slots contain
+// nullptr.
+//
+// More details on this design:
+// We need some type of thread-local state to indicate that the TLS system has
+// been destroyed. To do so, we leverage the multi-pass nature of destruction
+// of pthread_key.
+//
+// a) After destruction of TLS system, we set the pthread_key to a sentinel
+// kDestroyed.
+// b) All calls to Slot::Get() DCHECK that the state is not kDestroyed, and
+// any system which might potentially invoke Slot::Get() after destruction
+// of TLS must check ThreadLocalStorage::ThreadIsBeingDestroyed().
+// c) After a full pass of the pthread_keys, on the next invocation of
+// ConstructTlsVector(), we'll then set the key to nullptr.
+// d) At this stage, the TLS system is back in its uninitialized state.
+// e) If in the second pass of destruction of pthread_keys something were to
+// re-initialize TLS [this should never happen! Since the only code which
+// uses Chrome TLS is Chrome controlled, we should really be striving for
+// single-pass destruction], then TLS will be re-initialized and then go
+// through the 2-pass destruction system again. Everything should just
+// work (TM).
+
+// The consumers of kUninitialized and kDestroyed expect void*, since that's
+// what the API exposes on both POSIX and Windows.
+void* const kUninitialized = nullptr;
+
+// A sentinel value to indicate that the TLS system has been destroyed.
+void* const kDestroyed = reinterpret_cast<void*>(1);
+
// The maximum number of slots in our thread local storage stack.
constexpr int kThreadLocalStorageSize = 256;
-constexpr int kInvalidSlotValue = -1;
enum TlsStatus {
FREE,
@@ -140,7 +178,7 @@ TlsVectorEntry* ConstructTlsVector() {
key = base::subtle::NoBarrier_Load(&g_native_tls_key);
}
}
- CHECK(!PlatformThreadLocalStorage::GetTLSValue(key));
+ CHECK_EQ(PlatformThreadLocalStorage::GetTLSValue(key), kUninitialized);
// Some allocators, such as TCMalloc, make use of thread local storage. As a
// result, any attempt to call new (or malloc) will lazily cause such a system
@@ -163,6 +201,16 @@ TlsVectorEntry* ConstructTlsVector() {
}
void OnThreadExitInternal(TlsVectorEntry* tls_data) {
+ // This branch is for POSIX, where this function is called twice. The first
+ // pass calls dtors and sets state to kDestroyed. The second pass sets
+ // kDestroyed to kUninitialized.
+ if (tls_data == kDestroyed) {
+ PlatformThreadLocalStorage::TLSKey key =
+ base::subtle::NoBarrier_Load(&g_native_tls_key);
+ PlatformThreadLocalStorage::SetTLSValue(key, kUninitialized);
+ return;
+ }
+
DCHECK(tls_data);
// Some allocators, such as TCMalloc, use TLS. As a result, when a thread
// terminates, one of the destructor calls we make may be to shut down an
@@ -222,7 +270,7 @@ void OnThreadExitInternal(TlsVectorEntry* tls_data) {
}
// Remove our stack allocated vector.
- PlatformThreadLocalStorage::SetTLSValue(key, nullptr);
+ PlatformThreadLocalStorage::SetTLSValue(key, kDestroyed);
}
} // namespace
@@ -238,12 +286,17 @@ void PlatformThreadLocalStorage::OnThreadExit() {
if (key == PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES)
return;
void *tls_data = GetTLSValue(key);
+
+ // On Windows, thread destruction callbacks are only invoked once per module,
+ // so there should be no way that this could be invoked twice.
+ DCHECK_NE(tls_data, kDestroyed);
+
// Maybe we have never initialized TLS for this thread.
- if (!tls_data)
+ if (tls_data == kUninitialized)
return;
OnThreadExitInternal(static_cast<TlsVectorEntry*>(tls_data));
}
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
void PlatformThreadLocalStorage::OnThreadExit(void* value) {
OnThreadExitInternal(static_cast<TlsVectorEntry*>(value));
}
@@ -262,17 +315,23 @@ void PlatformThreadLocalStorage::ForceFreeTLS() {
} // namespace internal
-void ThreadLocalStorage::StaticSlot::Initialize(TLSDestructorFunc destructor) {
+bool ThreadLocalStorage::HasBeenDestroyed() {
+ PlatformThreadLocalStorage::TLSKey key =
+ base::subtle::NoBarrier_Load(&g_native_tls_key);
+ if (key == PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES)
+ return false;
+ return PlatformThreadLocalStorage::GetTLSValue(key) == kDestroyed;
+}
+
+void ThreadLocalStorage::Slot::Initialize(TLSDestructorFunc destructor) {
PlatformThreadLocalStorage::TLSKey key =
base::subtle::NoBarrier_Load(&g_native_tls_key);
if (key == PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES ||
- !PlatformThreadLocalStorage::GetTLSValue(key)) {
+ PlatformThreadLocalStorage::GetTLSValue(key) == kUninitialized) {
ConstructTlsVector();
}
// Grab a new slot.
- slot_ = kInvalidSlotValue;
- version_ = 0;
{
base::AutoLock auto_lock(*GetTLSMetadataLock());
for (int i = 0; i < kThreadLocalStorageSize; ++i) {
@@ -287,6 +346,7 @@ void ThreadLocalStorage::StaticSlot::Initialize(TLSDestructorFunc destructor) {
g_tls_metadata[slot_candidate].status = TlsStatus::IN_USE;
g_tls_metadata[slot_candidate].destructor = destructor;
g_last_assigned_slot = slot_candidate;
+ DCHECK_EQ(kInvalidSlotValue, slot_);
slot_ = slot_candidate;
version_ = g_tls_metadata[slot_candidate].version;
break;
@@ -295,12 +355,9 @@ void ThreadLocalStorage::StaticSlot::Initialize(TLSDestructorFunc destructor) {
}
CHECK_NE(slot_, kInvalidSlotValue);
CHECK_LT(slot_, kThreadLocalStorageSize);
-
- // Setup our destructor.
- base::subtle::Release_Store(&initialized_, 1);
}
-void ThreadLocalStorage::StaticSlot::Free() {
+void ThreadLocalStorage::Slot::Free() {
DCHECK_NE(slot_, kInvalidSlotValue);
DCHECK_LT(slot_, kThreadLocalStorageSize);
{
@@ -310,15 +367,15 @@ void ThreadLocalStorage::StaticSlot::Free() {
++(g_tls_metadata[slot_].version);
}
slot_ = kInvalidSlotValue;
- base::subtle::Release_Store(&initialized_, 0);
}
-void* ThreadLocalStorage::StaticSlot::Get() const {
+void* ThreadLocalStorage::Slot::Get() const {
TlsVectorEntry* tls_data = static_cast<TlsVectorEntry*>(
PlatformThreadLocalStorage::GetTLSValue(
base::subtle::NoBarrier_Load(&g_native_tls_key)));
+ DCHECK_NE(tls_data, kDestroyed);
if (!tls_data)
- tls_data = ConstructTlsVector();
+ return nullptr;
DCHECK_NE(slot_, kInvalidSlotValue);
DCHECK_LT(slot_, kThreadLocalStorageSize);
// Version mismatches means this slot was previously freed.
@@ -327,10 +384,11 @@ void* ThreadLocalStorage::StaticSlot::Get() const {
return tls_data[slot_].data;
}
-void ThreadLocalStorage::StaticSlot::Set(void* value) {
+void ThreadLocalStorage::Slot::Set(void* value) {
TlsVectorEntry* tls_data = static_cast<TlsVectorEntry*>(
PlatformThreadLocalStorage::GetTLSValue(
base::subtle::NoBarrier_Load(&g_native_tls_key)));
+ DCHECK_NE(tls_data, kDestroyed);
if (!tls_data)
tls_data = ConstructTlsVector();
DCHECK_NE(slot_, kInvalidSlotValue);
@@ -340,19 +398,11 @@ void ThreadLocalStorage::StaticSlot::Set(void* value) {
}
ThreadLocalStorage::Slot::Slot(TLSDestructorFunc destructor) {
- tls_slot_.Initialize(destructor);
+ Initialize(destructor);
}
ThreadLocalStorage::Slot::~Slot() {
- tls_slot_.Free();
-}
-
-void* ThreadLocalStorage::Slot::Get() const {
- return tls_slot_.Get();
-}
-
-void ThreadLocalStorage::Slot::Set(void* value) {
- tls_slot_.Set(value);
+ Free();
}
} // namespace base
diff --git a/base/threading/thread_local_storage.h b/base/threading/thread_local_storage.h
index c5c7759efc..d6b62c189d 100644
--- a/base/threading/thread_local_storage.h
+++ b/base/threading/thread_local_storage.h
@@ -13,15 +13,35 @@
#include "build/build_config.h"
#if defined(OS_WIN)
-#include <windows.h>
-#elif defined(OS_POSIX)
+#include "base/win/windows_types.h"
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
#include <pthread.h>
#endif
+namespace heap_profiling {
+class ScopedAllowAlloc;
+} // namespace heap_profiling
+
+namespace ui {
+class TLSDestructionCheckerForX11;
+}
+
namespace base {
+class SamplingHeapProfiler;
+
+namespace debug {
+class GlobalActivityTracker;
+} // namespace debug
+
+namespace trace_event {
+class MallocDumpProvider;
+} // namespace trace_event
+
namespace internal {
+class ThreadLocalStorageTestInternal;
+
// WARNING: You should *NOT* use this class directly.
// PlatformThreadLocalStorage is a low-level abstraction of the OS's TLS
// interface. Instead, you should use one of the following:
@@ -34,7 +54,7 @@ class BASE_EXPORT PlatformThreadLocalStorage {
#if defined(OS_WIN)
typedef unsigned long TLSKey;
enum : unsigned { TLS_KEY_OUT_OF_INDEXES = TLS_OUT_OF_INDEXES };
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
typedef pthread_key_t TLSKey;
// The following is a "reserved key" which is used in our generic Chromium
// ThreadLocalStorage implementation. We expect that an OS will not return
@@ -56,7 +76,13 @@ class BASE_EXPORT PlatformThreadLocalStorage {
// SetTLSValue().
static void FreeTLS(TLSKey key);
static void SetTLSValue(TLSKey key, void* value);
- static void* GetTLSValue(TLSKey key);
+ static void* GetTLSValue(TLSKey key) {
+#if defined(OS_WIN)
+ return TlsGetValue(key);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ return pthread_getspecific(key);
+#endif
+ }
// Each platform (OS implementation) is required to call this method on each
// terminating thread when the thread is about to terminate. This method
@@ -70,7 +96,7 @@ class BASE_EXPORT PlatformThreadLocalStorage {
// Since Windows which doesn't support TLS destructor, the implementation
// should use GetTLSValue() to retrieve the value of TLS slot.
static void OnThreadExit();
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
// |Value| is the data stored in TLS slot, The implementation can't use
// GetTLSValue() to retrieve the value of slot as it has already been reset
// in Posix.
@@ -91,34 +117,28 @@ class BASE_EXPORT PlatformThreadLocalStorage {
// an API for portability.
class BASE_EXPORT ThreadLocalStorage {
public:
-
// Prototype for the TLS destructor function, which can be optionally used to
// cleanup thread local storage on thread exit. 'value' is the data that is
// stored in thread local storage.
typedef void (*TLSDestructorFunc)(void* value);
- // StaticSlot uses its own struct initializer-list style static
- // 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 TLS_INITIALIZER {0}
-
- // A key representing one value stored in TLS.
- // Initialize like
- // ThreadLocalStorage::StaticSlot my_slot = TLS_INITIALIZER;
- // If you're not using a static variable, use the convenience class
- // ThreadLocalStorage::Slot (below) instead.
- struct BASE_EXPORT StaticSlot {
- // Set up the TLS slot. Called by the constructor.
- // 'destructor' is a pointer to a function to perform per-thread cleanup of
- // this object. If set to NULL, no cleanup is done for this TLS slot.
- void Initialize(TLSDestructorFunc destructor);
-
- // Free a previously allocated TLS 'slot'.
- // If a destructor was set for this slot, removes
- // the destructor so that remaining threads exiting
- // will not free data.
- void Free();
+ // A key representing one value stored in TLS. Use as a class member or a
+ // local variable. If you need a static storage duration variable, use the
+ // following pattern with a NoDestructor<Slot>:
+ // void MyDestructorFunc(void* value);
+ // ThreadLocalStorage::Slot& ImportantContentTLS() {
+ // static NoDestructor<ThreadLocalStorage::Slot> important_content_tls(
+ // &MyDestructorFunc);
+ // return *important_content_tls;
+ // }
+ class BASE_EXPORT Slot final {
+ public:
+ // |destructor| is a pointer to a function to perform per-thread cleanup of
+ // this object. If set to nullptr, no cleanup is done for this TLS slot.
+ explicit Slot(TLSDestructorFunc destructor = nullptr);
+ // If a destructor was set for this slot, removes the destructor so that
+ // remaining threads exiting will not free data.
+ ~Slot();
// Get the thread-local value stored in slot 'slot'.
// Values are guaranteed to initially be zero.
@@ -128,37 +148,34 @@ class BASE_EXPORT ThreadLocalStorage {
// value 'value'.
void Set(void* value);
- bool initialized() const {
- return base::subtle::Acquire_Load(&initialized_) != 0;
- }
-
- // The internals of this struct should be considered private.
- base::subtle::Atomic32 initialized_;
- int slot_;
- uint32_t version_;
- };
-
- // A convenience wrapper around StaticSlot with a constructor. Can be used
- // as a member variable.
- class BASE_EXPORT Slot {
- public:
- explicit Slot(TLSDestructorFunc destructor = NULL);
- ~Slot();
-
- // Get the thread-local value stored in this slot.
- // Values are guaranteed to initially be zero.
- void* Get() const;
-
- // Set the slot's thread-local value to |value|.
- void Set(void* value);
-
private:
- StaticSlot tls_slot_;
+ void Initialize(TLSDestructorFunc destructor);
+ void Free();
+
+ static constexpr int kInvalidSlotValue = -1;
+ int slot_ = kInvalidSlotValue;
+ uint32_t version_ = 0;
DISALLOW_COPY_AND_ASSIGN(Slot);
};
private:
+ // In most cases, most callers should not need access to HasBeenDestroyed().
+ // If you are working in code that runs during thread destruction, contact the
+ // base OWNERs for advice and then make a friend request.
+ //
+ // Returns |true| if Chrome's implementation of TLS has been destroyed during
+ // thread destruction. Attempting to call Slot::Get() during destruction is
+ // disallowed and will hit a DCHECK. Any code that relies on TLS during thread
+ // destruction must first check this method before calling Slot::Get().
+ friend class base::SamplingHeapProfiler;
+ friend class base::internal::ThreadLocalStorageTestInternal;
+ friend class base::trace_event::MallocDumpProvider;
+ friend class debug::GlobalActivityTracker;
+ friend class heap_profiling::ScopedAllowAlloc;
+ friend class ui::TLSDestructionCheckerForX11;
+ static bool HasBeenDestroyed();
+
DISALLOW_COPY_AND_ASSIGN(ThreadLocalStorage);
};
diff --git a/base/threading/thread_local_storage_posix.cc b/base/threading/thread_local_storage_posix.cc
index ebaf4005d3..89edeee1d2 100644
--- a/base/threading/thread_local_storage_posix.cc
+++ b/base/threading/thread_local_storage_posix.cc
@@ -20,10 +20,6 @@ void PlatformThreadLocalStorage::FreeTLS(TLSKey key) {
DCHECK_EQ(ret, 0);
}
-void* PlatformThreadLocalStorage::GetTLSValue(TLSKey key) {
- return pthread_getspecific(key);
-}
-
void PlatformThreadLocalStorage::SetTLSValue(TLSKey key, void* value) {
int ret = pthread_setspecific(key, value);
DCHECK_EQ(ret, 0);
diff --git a/base/threading/thread_local_storage_unittest.cc b/base/threading/thread_local_storage_unittest.cc
index 335252b18e..9062ff0c7f 100644
--- a/base/threading/thread_local_storage_unittest.cc
+++ b/base/threading/thread_local_storage_unittest.cc
@@ -2,14 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "base/threading/thread_local_storage.h"
+
#if defined(OS_WIN)
#include <windows.h>
#include <process.h>
#endif
#include "base/macros.h"
+#include "base/no_destructor.h"
#include "base/threading/simple_thread.h"
-#include "base/threading/thread_local_storage.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -21,6 +23,22 @@
namespace base {
+#if defined(OS_POSIX)
+
+namespace internal {
+
+// This class is friended by ThreadLocalStorage.
+class ThreadLocalStorageTestInternal {
+ public:
+ static bool HasBeenDestroyed() {
+ return ThreadLocalStorage::HasBeenDestroyed();
+ }
+};
+
+} // namespace internal
+
+#endif // defined(OS_POSIX)
+
namespace {
const int kInitialTlsValue = 0x5555;
@@ -28,25 +46,31 @@ const int kFinalTlsValue = 0x7777;
// How many times must a destructor be called before we really are done.
const int kNumberDestructorCallRepetitions = 3;
-static ThreadLocalStorage::StaticSlot tls_slot = TLS_INITIALIZER;
+void ThreadLocalStorageCleanup(void* value);
+
+ThreadLocalStorage::Slot& TLSSlot() {
+ static NoDestructor<ThreadLocalStorage::Slot> slot(
+ &ThreadLocalStorageCleanup);
+ return *slot;
+}
class ThreadLocalStorageRunner : public DelegateSimpleThread::Delegate {
public:
explicit ThreadLocalStorageRunner(int* tls_value_ptr)
: tls_value_ptr_(tls_value_ptr) {}
- ~ThreadLocalStorageRunner() override {}
+ ~ThreadLocalStorageRunner() override = default;
void Run() override {
*tls_value_ptr_ = kInitialTlsValue;
- tls_slot.Set(tls_value_ptr_);
+ TLSSlot().Set(tls_value_ptr_);
- int *ptr = static_cast<int*>(tls_slot.Get());
+ int* ptr = static_cast<int*>(TLSSlot().Get());
EXPECT_EQ(ptr, tls_value_ptr_);
EXPECT_EQ(*ptr, kInitialTlsValue);
*tls_value_ptr_ = 0;
- ptr = static_cast<int*>(tls_slot.Get());
+ ptr = static_cast<int*>(TLSSlot().Get());
EXPECT_EQ(ptr, tls_value_ptr_);
EXPECT_EQ(*ptr, 0);
@@ -69,9 +93,108 @@ void ThreadLocalStorageCleanup(void *value) {
ASSERT_GE(kFinalTlsValue + kNumberDestructorCallRepetitions, *ptr);
--*ptr; // Move closer to our target.
// Tell tls that we're not done with this thread, and still need destruction.
- tls_slot.Set(value);
+ TLSSlot().Set(value);
+}
+
+#if defined(OS_POSIX)
+constexpr intptr_t kDummyValue = 0xABCD;
+constexpr size_t kKeyCount = 20;
+
+// The order in which pthread keys are destructed is not specified by the POSIX
+// specification. Hopefully, of the 20 keys we create, some of them should be
+// destroyed after the TLS key is destroyed.
+class UseTLSDuringDestructionRunner {
+ public:
+ UseTLSDuringDestructionRunner() = default;
+
+ // The order in which pthread_key destructors are called is not well defined.
+ // Hopefully, by creating 10 both before and after initializing TLS on the
+ // thread, at least 1 will be called after TLS destruction.
+ void Run() {
+ ASSERT_FALSE(internal::ThreadLocalStorageTestInternal::HasBeenDestroyed());
+
+ // Create 10 pthread keys before initializing TLS on the thread.
+ size_t slot_index = 0;
+ for (; slot_index < 10; ++slot_index) {
+ CreateTlsKeyWithDestructor(slot_index);
+ }
+
+ // Initialize the Chrome TLS system. It's possible that base::Thread has
+ // already initialized Chrome TLS, but we don't rely on that.
+ slot_.Set(reinterpret_cast<void*>(kDummyValue));
+
+ // Create 10 pthread keys after initializing TLS on the thread.
+ for (; slot_index < kKeyCount; ++slot_index) {
+ CreateTlsKeyWithDestructor(slot_index);
+ }
+ }
+
+ bool teardown_works_correctly() { return teardown_works_correctly_; }
+
+ private:
+ struct TLSState {
+ pthread_key_t key;
+ bool* teardown_works_correctly;
+ };
+
+ // The POSIX TLS destruction API takes as input a single C-function, which is
+ // called with the current |value| of a (key, value) pair. We need this
+ // function to do two things: set the |value| to nullptr, which requires
+ // knowing the associated |key|, and update the |teardown_works_correctly_|
+ // state.
+ //
+ // To accomplish this, we set the value to an instance of TLSState, which
+ // contains |key| as well as a pointer to |teardown_works_correctly|.
+ static void ThreadLocalDestructor(void* value) {
+ TLSState* state = static_cast<TLSState*>(value);
+ int result = pthread_setspecific(state->key, nullptr);
+ ASSERT_EQ(result, 0);
+
+ // If this path is hit, then the thread local destructor was called after
+ // the Chrome-TLS destructor and the internal state was updated correctly.
+ // No further checks are necessary.
+ if (internal::ThreadLocalStorageTestInternal::HasBeenDestroyed()) {
+ *(state->teardown_works_correctly) = true;
+ return;
+ }
+
+ // If this path is hit, then the thread local destructor was called before
+ // the Chrome-TLS destructor is hit. The ThreadLocalStorage::Slot should
+ // still function correctly.
+ ASSERT_EQ(reinterpret_cast<intptr_t>(slot_.Get()), kDummyValue);
+ }
+
+ void CreateTlsKeyWithDestructor(size_t index) {
+ ASSERT_LT(index, kKeyCount);
+
+ tls_states_[index].teardown_works_correctly = &teardown_works_correctly_;
+ int result = pthread_key_create(
+ &(tls_states_[index].key),
+ UseTLSDuringDestructionRunner::ThreadLocalDestructor);
+ ASSERT_EQ(result, 0);
+
+ result = pthread_setspecific(tls_states_[index].key, &tls_states_[index]);
+ ASSERT_EQ(result, 0);
+ }
+
+ static base::ThreadLocalStorage::Slot slot_;
+ bool teardown_works_correctly_ = false;
+ TLSState tls_states_[kKeyCount];
+
+ DISALLOW_COPY_AND_ASSIGN(UseTLSDuringDestructionRunner);
+};
+
+base::ThreadLocalStorage::Slot UseTLSDuringDestructionRunner::slot_;
+
+void* UseTLSTestThreadRun(void* input) {
+ UseTLSDuringDestructionRunner* runner =
+ static_cast<UseTLSDuringDestructionRunner*>(input);
+ runner->Run();
+ return nullptr;
}
+#endif // defined(OS_POSIX)
+
} // namespace
TEST(ThreadLocalStorageTest, Basics) {
@@ -104,8 +227,6 @@ TEST(ThreadLocalStorageTest, MAYBE_TLSDestructors) {
ThreadLocalStorageRunner* thread_delegates[kNumThreads];
DelegateSimpleThread* threads[kNumThreads];
- tls_slot.Initialize(ThreadLocalStorageCleanup);
-
// Spawn the threads.
for (int index = 0; index < kNumThreads; index++) {
values[index] = kInitialTlsValue;
@@ -124,7 +245,6 @@ TEST(ThreadLocalStorageTest, MAYBE_TLSDestructors) {
// Verify that the destructor was called and that we reset.
EXPECT_EQ(values[index], kFinalTlsValue);
}
- tls_slot.Free(); // Stop doing callbacks to cleanup threads.
}
TEST(ThreadLocalStorageTest, TLSReclaim) {
@@ -137,4 +257,22 @@ TEST(ThreadLocalStorageTest, TLSReclaim) {
}
}
+#if defined(OS_POSIX)
+// Unlike POSIX, Windows does not iterate through the OS TLS to cleanup any
+// values there. Instead a per-module thread destruction function is called.
+// However, it is not possible to perform a check after this point (as the code
+// is detached from the thread), so this check remains POSIX only.
+TEST(ThreadLocalStorageTest, UseTLSDuringDestruction) {
+ UseTLSDuringDestructionRunner runner;
+ pthread_t thread;
+ int result = pthread_create(&thread, nullptr, UseTLSTestThreadRun, &runner);
+ ASSERT_EQ(result, 0);
+
+ result = pthread_join(thread, nullptr);
+ ASSERT_EQ(result, 0);
+
+ EXPECT_TRUE(runner.teardown_works_correctly());
+}
+#endif // defined(OS_POSIX)
+
} // namespace base
diff --git a/base/threading/thread_local_unittest.cc b/base/threading/thread_local_unittest.cc
index cdc1ca6f56..54f2ad236b 100644
--- a/base/threading/thread_local_unittest.cc
+++ b/base/threading/thread_local_unittest.cc
@@ -20,7 +20,7 @@ class ThreadLocalTesterBase : public base::DelegateSimpleThreadPool::Delegate {
: tlp_(tlp),
done_(done) {
}
- ~ThreadLocalTesterBase() override {}
+ ~ThreadLocalTesterBase() override = default;
protected:
TLPType* tlp_;
@@ -30,10 +30,8 @@ class ThreadLocalTesterBase : public base::DelegateSimpleThreadPool::Delegate {
class SetThreadLocal : public ThreadLocalTesterBase {
public:
SetThreadLocal(TLPType* tlp, base::WaitableEvent* done)
- : ThreadLocalTesterBase(tlp, done),
- val_(NULL) {
- }
- ~SetThreadLocal() override {}
+ : ThreadLocalTesterBase(tlp, done), val_(nullptr) {}
+ ~SetThreadLocal() override = default;
void set_value(char* val) { val_ = val; }
@@ -50,10 +48,8 @@ class SetThreadLocal : public ThreadLocalTesterBase {
class GetThreadLocal : public ThreadLocalTesterBase {
public:
GetThreadLocal(TLPType* tlp, base::WaitableEvent* done)
- : ThreadLocalTesterBase(tlp, done),
- ptr_(NULL) {
- }
- ~GetThreadLocal() override {}
+ : ThreadLocalTesterBase(tlp, done), ptr_(nullptr) {}
+ ~GetThreadLocal() override = default;
void set_ptr(char** ptr) { ptr_ = ptr; }
@@ -93,14 +89,13 @@ TEST(ThreadLocalTest, Pointer) {
done.Reset();
tp1.AddWork(&getter);
done.Wait();
- EXPECT_EQ(static_cast<char*>(NULL), tls_val);
+ EXPECT_EQ(static_cast<char*>(nullptr), tls_val);
tls_val = kBogusPointer;
done.Reset();
tp2.AddWork(&getter);
done.Wait();
- EXPECT_EQ(static_cast<char*>(NULL), tls_val);
-
+ EXPECT_EQ(static_cast<char*>(nullptr), tls_val);
SetThreadLocal setter(&tlp, &done);
setter.set_value(kBogusPointer);
@@ -110,7 +105,7 @@ TEST(ThreadLocalTest, Pointer) {
tp1.AddWork(&setter);
done.Wait();
- tls_val = NULL;
+ tls_val = nullptr;
done.Reset();
tp1.AddWork(&getter);
done.Wait();
@@ -121,7 +116,7 @@ TEST(ThreadLocalTest, Pointer) {
done.Reset();
tp2.AddWork(&getter);
done.Wait();
- EXPECT_EQ(static_cast<char*>(NULL), tls_val);
+ EXPECT_EQ(static_cast<char*>(nullptr), tls_val);
// Set thread 2 to kBogusPointer + 1.
setter.set_value(kBogusPointer + 1);
@@ -130,14 +125,14 @@ TEST(ThreadLocalTest, Pointer) {
tp2.AddWork(&setter);
done.Wait();
- tls_val = NULL;
+ tls_val = nullptr;
done.Reset();
tp2.AddWork(&getter);
done.Wait();
EXPECT_EQ(kBogusPointer + 1, tls_val);
// Make sure thread 1 is still kBogusPointer.
- tls_val = NULL;
+ tls_val = nullptr;
done.Reset();
tp1.AddWork(&getter);
done.Wait();
diff --git a/base/threading/thread_restrictions.cc b/base/threading/thread_restrictions.cc
index 8dd7743332..36c64b51be 100644
--- a/base/threading/thread_restrictions.cc
+++ b/base/threading/thread_restrictions.cc
@@ -14,34 +14,122 @@ namespace base {
namespace {
-LazyInstance<ThreadLocalBoolean>::Leaky
- g_io_disallowed = LAZY_INSTANCE_INITIALIZER;
+LazyInstance<ThreadLocalBoolean>::Leaky g_blocking_disallowed =
+ LAZY_INSTANCE_INITIALIZER;
LazyInstance<ThreadLocalBoolean>::Leaky
g_singleton_disallowed = LAZY_INSTANCE_INITIALIZER;
-LazyInstance<ThreadLocalBoolean>::Leaky
- g_wait_disallowed = LAZY_INSTANCE_INITIALIZER;
+LazyInstance<ThreadLocalBoolean>::Leaky g_base_sync_primitives_disallowed =
+ LAZY_INSTANCE_INITIALIZER;
} // namespace
-// static
-bool ThreadRestrictions::SetIOAllowed(bool allowed) {
- bool previous_disallowed = g_io_disallowed.Get().Get();
- g_io_disallowed.Get().Set(!allowed);
- return !previous_disallowed;
+void AssertBlockingAllowed() {
+ DCHECK(!g_blocking_disallowed.Get().Get())
+ << "Function marked as blocking was called from a scope that disallows "
+ "blocking! If this task is running inside the TaskScheduler, it needs "
+ "to have MayBlock() in its TaskTraits. Otherwise, consider making "
+ "this blocking work asynchronous or, as a last resort, you may use "
+ "ScopedAllowBlocking (see its documentation for best practices).";
+}
+
+void DisallowBlocking() {
+ g_blocking_disallowed.Get().Set(true);
+}
+
+ScopedDisallowBlocking::ScopedDisallowBlocking()
+ : was_disallowed_(g_blocking_disallowed.Get().Get()) {
+ g_blocking_disallowed.Get().Set(true);
+}
+
+ScopedDisallowBlocking::~ScopedDisallowBlocking() {
+ DCHECK(g_blocking_disallowed.Get().Get());
+ g_blocking_disallowed.Get().Set(was_disallowed_);
+}
+
+ScopedAllowBlocking::ScopedAllowBlocking()
+ : was_disallowed_(g_blocking_disallowed.Get().Get()) {
+ g_blocking_disallowed.Get().Set(false);
+}
+
+ScopedAllowBlocking::~ScopedAllowBlocking() {
+ DCHECK(!g_blocking_disallowed.Get().Get());
+ g_blocking_disallowed.Get().Set(was_disallowed_);
+}
+
+void DisallowBaseSyncPrimitives() {
+ g_base_sync_primitives_disallowed.Get().Set(true);
+}
+
+ScopedAllowBaseSyncPrimitives::ScopedAllowBaseSyncPrimitives()
+ : was_disallowed_(g_base_sync_primitives_disallowed.Get().Get()) {
+ DCHECK(!g_blocking_disallowed.Get().Get())
+ << "To allow //base sync primitives in a scope where blocking is "
+ "disallowed use ScopedAllowBaseSyncPrimitivesOutsideBlockingScope.";
+ g_base_sync_primitives_disallowed.Get().Set(false);
+}
+
+ScopedAllowBaseSyncPrimitives::~ScopedAllowBaseSyncPrimitives() {
+ DCHECK(!g_base_sync_primitives_disallowed.Get().Get());
+ g_base_sync_primitives_disallowed.Get().Set(was_disallowed_);
+}
+
+ScopedAllowBaseSyncPrimitivesOutsideBlockingScope::
+ ScopedAllowBaseSyncPrimitivesOutsideBlockingScope()
+ : was_disallowed_(g_base_sync_primitives_disallowed.Get().Get()) {
+ g_base_sync_primitives_disallowed.Get().Set(false);
+}
+
+ScopedAllowBaseSyncPrimitivesOutsideBlockingScope::
+ ~ScopedAllowBaseSyncPrimitivesOutsideBlockingScope() {
+ DCHECK(!g_base_sync_primitives_disallowed.Get().Get());
+ g_base_sync_primitives_disallowed.Get().Set(was_disallowed_);
+}
+
+ScopedAllowBaseSyncPrimitivesForTesting::
+ ScopedAllowBaseSyncPrimitivesForTesting()
+ : was_disallowed_(g_base_sync_primitives_disallowed.Get().Get()) {
+ g_base_sync_primitives_disallowed.Get().Set(false);
+}
+
+ScopedAllowBaseSyncPrimitivesForTesting::
+ ~ScopedAllowBaseSyncPrimitivesForTesting() {
+ DCHECK(!g_base_sync_primitives_disallowed.Get().Get());
+ g_base_sync_primitives_disallowed.Get().Set(was_disallowed_);
+}
+
+namespace internal {
+
+void AssertBaseSyncPrimitivesAllowed() {
+ DCHECK(!g_base_sync_primitives_disallowed.Get().Get())
+ << "Waiting on a //base sync primitive is not allowed on this thread to "
+ "prevent jank and deadlock. If waiting on a //base sync primitive is "
+ "unavoidable, do it within the scope of a "
+ "ScopedAllowBaseSyncPrimitives. If in a test, "
+ "use ScopedAllowBaseSyncPrimitivesForTesting.";
+}
+
+void ResetThreadRestrictionsForTesting() {
+ g_blocking_disallowed.Get().Set(false);
+ g_singleton_disallowed.Get().Set(false);
+ g_base_sync_primitives_disallowed.Get().Set(false);
+}
+
+} // namespace internal
+
+ThreadRestrictions::ScopedAllowIO::ScopedAllowIO()
+ : was_allowed_(SetIOAllowed(true)) {}
+
+ThreadRestrictions::ScopedAllowIO::~ScopedAllowIO() {
+ SetIOAllowed(was_allowed_);
}
// static
-void ThreadRestrictions::AssertIOAllowed() {
- if (g_io_disallowed.Get().Get()) {
- NOTREACHED() <<
- "Function marked as IO-only was called from a thread that "
- "disallows IO! If this thread really should be allowed to "
- "make IO calls, adjust the call to "
- "base::ThreadRestrictions::SetIOAllowed() in this thread's "
- "startup.";
- }
+bool ThreadRestrictions::SetIOAllowed(bool allowed) {
+ bool previous_disallowed = g_blocking_disallowed.Get().Get();
+ g_blocking_disallowed.Get().Set(!allowed);
+ return !previous_disallowed;
}
// static
@@ -67,23 +155,22 @@ void ThreadRestrictions::AssertSingletonAllowed() {
// static
void ThreadRestrictions::DisallowWaiting() {
- g_wait_disallowed.Get().Set(true);
-}
-
-// static
-void ThreadRestrictions::AssertWaitAllowed() {
- if (g_wait_disallowed.Get().Get()) {
- NOTREACHED() << "Waiting is not allowed to be used on this thread to "
- << "prevent jank and deadlock.";
- }
+ DisallowBaseSyncPrimitives();
}
bool ThreadRestrictions::SetWaitAllowed(bool allowed) {
- bool previous_disallowed = g_wait_disallowed.Get().Get();
- g_wait_disallowed.Get().Set(!allowed);
+ bool previous_disallowed = g_base_sync_primitives_disallowed.Get().Get();
+ g_base_sync_primitives_disallowed.Get().Set(!allowed);
return !previous_disallowed;
}
+ThreadRestrictions::ScopedAllowWait::ScopedAllowWait()
+ : was_allowed_(SetWaitAllowed(true)) {}
+
+ThreadRestrictions::ScopedAllowWait::~ScopedAllowWait() {
+ SetWaitAllowed(was_allowed_);
+}
+
} // namespace base
#endif // DCHECK_IS_ON()
diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h
index 8f3beb1d1a..705ba4ddee 100644
--- a/base/threading/thread_restrictions.h
+++ b/base/threading/thread_restrictions.h
@@ -6,13 +6,20 @@
#define BASE_THREADING_THREAD_RESTRICTIONS_H_
#include "base/base_export.h"
+#include "base/gtest_prod_util.h"
#include "base/logging.h"
#include "base/macros.h"
class BrowserProcessImpl;
class HistogramSynchronizer;
class NativeBackendKWallet;
-class ScopedAllowWaitForLegacyWebViewApi;
+class KeyStorageLinux;
+
+namespace android_webview {
+class AwFormDatabaseService;
+class CookieManager;
+class ScopedAllowInitGLBindings;
+}
namespace cc {
class CompletionEvent;
@@ -30,16 +37,27 @@ class Predictor;
namespace content {
class BrowserGpuChannelHostFactory;
class BrowserGpuMemoryBufferManager;
+class BrowserMainLoop;
+class BrowserProcessSubThread;
class BrowserShutdownProfileDumper;
-class BrowserSurfaceViewManager;
class BrowserTestBase;
+class CategorizedWorkerPool;
+class GpuProcessTransportFactory;
class NestedMessagePumpAndroid;
class ScopedAllowWaitForAndroidLayoutTests;
class ScopedAllowWaitForDebugURL;
+class SessionStorageDatabase;
class SoftwareOutputDeviceMus;
+class ServiceWorkerSubresourceLoader;
+class SynchronousCompositor;
+class SynchronousCompositorHost;
+class SynchronousCompositorSyncCallBridge;
class TextInputClientMac;
-class CategorizedWorkerPool;
} // namespace content
+namespace cronet {
+class CronetPrefsManager;
+class CronetURLRequestContext;
+} // namespace cronet
namespace dbus {
class Bus;
}
@@ -47,21 +65,40 @@ namespace disk_cache {
class BackendImpl;
class InFlightIO;
}
+namespace functions {
+class ExecScriptScopedAllowBaseSyncPrimitives;
+}
namespace gpu {
class GpuChannelHost;
}
+namespace leveldb {
+class LevelDBMojoProxy;
+}
+namespace media {
+class AudioInputDevice;
+class BlockingUrlProtocol;
+}
+namespace midi {
+class TaskService; // https://crbug.com/796830
+}
namespace mojo {
+class CoreLibraryInitializer;
class SyncCallRestrictions;
-namespace edk {
+namespace core {
class ScopedIPCSupport;
}
}
+namespace rlz_lib {
+class FinancialPing;
+}
namespace ui {
class CommandBufferClientImpl;
class CommandBufferLocal;
class GpuState;
+class MaterialDesignController;
}
namespace net {
+class MultiThreadedCertVerifierScopedAllowBaseSyncPrimitives;
class NetworkChangeNotifierMac;
namespace internal {
class AddressTrackerLinux;
@@ -72,6 +109,18 @@ namespace remoting {
class AutoThread;
}
+namespace resource_coordinator {
+class TabManagerDelegate;
+}
+
+namespace service_manager {
+class ServiceProcessLauncher;
+}
+
+namespace shell_integration {
+class LaunchXdgUtilityScopedAllowBaseSyncPrimitives;
+}
+
namespace ui {
class WindowResizeHelperMac;
}
@@ -80,6 +129,14 @@ namespace views {
class ScreenMus;
}
+namespace viz {
+class HostGpuMemoryBufferManager;
+}
+
+namespace webrtc {
+class DesktopConfigurationMonitor;
+}
+
namespace base {
namespace android {
@@ -90,47 +147,274 @@ namespace internal {
class TaskTracker;
}
-class SequencedWorkerPool;
+class GetAppOutputScopedAllowBaseSyncPrimitives;
class SimpleThread;
+class StackSamplingProfiler;
class Thread;
class ThreadTestHelper;
-// Certain behavior is disallowed on certain threads. ThreadRestrictions helps
-// enforce these rules. Examples of such rules:
+#if DCHECK_IS_ON()
+#define INLINE_IF_DCHECK_IS_OFF BASE_EXPORT
+#define EMPTY_BODY_IF_DCHECK_IS_OFF
+#else
+#define INLINE_IF_DCHECK_IS_OFF inline
+#define EMPTY_BODY_IF_DCHECK_IS_OFF \
+ {}
+#endif
+
+// A "blocking call" refers to any call that causes the calling thread to wait
+// off-CPU. It includes but is not limited to calls that wait on synchronous
+// file I/O operations: read or write a file from disk, interact with a pipe or
+// a socket, rename or delete a file, enumerate files in a directory, etc.
+// Acquiring a low contention lock is not considered a blocking call.
+
+// Asserts that blocking calls are allowed in the current scope.
//
-// * Do not do blocking IO (makes the thread janky)
-// * Do not access Singleton/LazyInstance (may lead to shutdown crashes)
+// Style tip: It's best if you put AssertBlockingAllowed() checks as close to
+// the blocking call as possible. For example:
//
-// Here's more about how the protection works:
+// void ReadFile() {
+// PreWork();
//
-// 1) If a thread should not be allowed to make IO calls, mark it:
-// base::ThreadRestrictions::SetIOAllowed(false);
-// By default, threads *are* allowed to make IO calls.
-// In Chrome browser code, IO calls should be proxied to the File thread.
+// base::AssertBlockingAllowed();
+// fopen(...);
+//
+// PostWork();
+// }
+//
+// void Bar() {
+// ReadFile();
+// }
+//
+// void Foo() {
+// Bar();
+// }
+INLINE_IF_DCHECK_IS_OFF void AssertBlockingAllowed()
+ EMPTY_BODY_IF_DCHECK_IS_OFF;
+
+// Disallows blocking on the current thread.
+INLINE_IF_DCHECK_IS_OFF void DisallowBlocking() EMPTY_BODY_IF_DCHECK_IS_OFF;
+
+// Disallows blocking calls within its scope.
+class BASE_EXPORT ScopedDisallowBlocking {
+ public:
+ ScopedDisallowBlocking() EMPTY_BODY_IF_DCHECK_IS_OFF;
+ ~ScopedDisallowBlocking() EMPTY_BODY_IF_DCHECK_IS_OFF;
+
+ private:
+#if DCHECK_IS_ON()
+ const bool was_disallowed_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedDisallowBlocking);
+};
+
+// ScopedAllowBlocking(ForTesting) allow blocking calls within a scope where
+// they are normally disallowed.
+//
+// Avoid using this. Prefer making blocking calls from tasks posted to
+// base::TaskScheduler with base::MayBlock().
+//
+// Where unavoidable, put ScopedAllow* instances in the narrowest scope possible
+// in the caller making the blocking call but no further down. That is: if a
+// Cleanup() method needs to do a blocking call, document Cleanup() as blocking
+// and add a ScopedAllowBlocking instance in callers that can't avoid making
+// this call from a context where blocking is banned, as such:
+// void Client::MyMethod() {
+// (...)
+// {
+// // Blocking is okay here because XYZ.
+// ScopedAllowBlocking allow_blocking;
+// my_foo_->Cleanup();
+// }
+// (...)
+// }
+//
+// // This method can block.
+// void Foo::Cleanup() {
+// // Do NOT add the ScopedAllowBlocking in Cleanup() directly as that hides
+// // its blocking nature from unknowing callers and defeats the purpose of
+// // these checks.
+// FlushStateToDisk();
+// }
+//
+// Note: In rare situations where the blocking call is an implementation detail
+// (i.e. the impl makes a call that invokes AssertBlockingAllowed() but it
+// somehow knows that in practice this will not block), it might be okay to hide
+// the ScopedAllowBlocking instance in the impl with a comment explaining why
+// that's okay.
+class BASE_EXPORT ScopedAllowBlocking {
+ private:
+ // This can only be instantiated by friends. Use ScopedAllowBlockingForTesting
+ // in unit tests to avoid the friend requirement.
+ FRIEND_TEST_ALL_PREFIXES(ThreadRestrictionsTest, ScopedAllowBlocking);
+ friend class android_webview::ScopedAllowInitGLBindings;
+ friend class content::BrowserProcessSubThread;
+ friend class content::GpuProcessTransportFactory;
+ friend class cronet::CronetPrefsManager;
+ friend class cronet::CronetURLRequestContext;
+ friend class media::AudioInputDevice;
+ friend class mojo::CoreLibraryInitializer;
+ friend class resource_coordinator::TabManagerDelegate; // crbug.com/778703
+ friend class ui::MaterialDesignController;
+ friend class ScopedAllowBlockingForTesting;
+ friend class StackSamplingProfiler;
+
+ ScopedAllowBlocking() EMPTY_BODY_IF_DCHECK_IS_OFF;
+ ~ScopedAllowBlocking() EMPTY_BODY_IF_DCHECK_IS_OFF;
+
+#if DCHECK_IS_ON()
+ const bool was_disallowed_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedAllowBlocking);
+};
+
+class ScopedAllowBlockingForTesting {
+ public:
+ ScopedAllowBlockingForTesting() {}
+ ~ScopedAllowBlockingForTesting() {}
+
+ private:
+#if DCHECK_IS_ON()
+ ScopedAllowBlocking scoped_allow_blocking_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedAllowBlockingForTesting);
+};
+
+// "Waiting on a //base sync primitive" refers to calling one of these methods:
+// - base::WaitableEvent::*Wait*
+// - base::ConditionVariable::*Wait*
+// - base::Process::WaitForExit*
+
+// Disallows waiting on a //base sync primitive on the current thread.
+INLINE_IF_DCHECK_IS_OFF void DisallowBaseSyncPrimitives()
+ EMPTY_BODY_IF_DCHECK_IS_OFF;
+
+// ScopedAllowBaseSyncPrimitives(ForTesting)(OutsideBlockingScope) allow waiting
+// on a //base sync primitive within a scope where this is normally disallowed.
//
-// 2) If a function makes a call that will go out to disk, check whether the
-// current thread is allowed:
-// base::ThreadRestrictions::AssertIOAllowed();
+// Avoid using this.
//
+// Instead of waiting on a WaitableEvent or a ConditionVariable, put the work
+// that should happen after the wait in a callback and post that callback from
+// where the WaitableEvent or ConditionVariable would have been signaled. If
+// something needs to be scheduled after many tasks have executed, use
+// base::BarrierClosure.
//
-// Style tip: where should you put AssertIOAllowed checks? It's best
-// if you put them as close to the disk access as possible, at the
-// lowest level. This rule is simple to follow and helps catch all
-// callers. For example, if your function GoDoSomeBlockingDiskCall()
-// only calls other functions in Chrome and not fopen(), you should go
-// add the AssertIOAllowed checks in the helper functions.
+// On Windows, join processes asynchronously using base::win::ObjectWatcher.
+
+// This can only be used in a scope where blocking is allowed.
+class BASE_EXPORT ScopedAllowBaseSyncPrimitives {
+ private:
+ // This can only be instantiated by friends. Use
+ // ScopedAllowBaseSyncPrimitivesForTesting in unit tests to avoid the friend
+ // requirement.
+ FRIEND_TEST_ALL_PREFIXES(ThreadRestrictionsTest,
+ ScopedAllowBaseSyncPrimitives);
+ FRIEND_TEST_ALL_PREFIXES(ThreadRestrictionsTest,
+ ScopedAllowBaseSyncPrimitivesResetsState);
+ FRIEND_TEST_ALL_PREFIXES(ThreadRestrictionsTest,
+ ScopedAllowBaseSyncPrimitivesWithBlockingDisallowed);
+ friend class base::GetAppOutputScopedAllowBaseSyncPrimitives;
+ friend class content::BrowserProcessSubThread;
+ friend class content::SessionStorageDatabase;
+ friend class functions::ExecScriptScopedAllowBaseSyncPrimitives;
+ friend class leveldb::LevelDBMojoProxy;
+ friend class media::BlockingUrlProtocol;
+ friend class mojo::core::ScopedIPCSupport;
+ friend class net::MultiThreadedCertVerifierScopedAllowBaseSyncPrimitives;
+ friend class rlz_lib::FinancialPing;
+ friend class shell_integration::LaunchXdgUtilityScopedAllowBaseSyncPrimitives;
+ friend class webrtc::DesktopConfigurationMonitor;
+ friend class content::ServiceWorkerSubresourceLoader;
+ friend class viz::HostGpuMemoryBufferManager;
+
+ ScopedAllowBaseSyncPrimitives() EMPTY_BODY_IF_DCHECK_IS_OFF;
+ ~ScopedAllowBaseSyncPrimitives() EMPTY_BODY_IF_DCHECK_IS_OFF;
+
+#if DCHECK_IS_ON()
+ const bool was_disallowed_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedAllowBaseSyncPrimitives);
+};
+
+// This can be used in a scope where blocking is disallowed.
+class BASE_EXPORT ScopedAllowBaseSyncPrimitivesOutsideBlockingScope {
+ private:
+ // This can only be instantiated by friends. Use
+ // ScopedAllowBaseSyncPrimitivesForTesting in unit tests to avoid the friend
+ // requirement.
+ FRIEND_TEST_ALL_PREFIXES(ThreadRestrictionsTest,
+ ScopedAllowBaseSyncPrimitivesOutsideBlockingScope);
+ FRIEND_TEST_ALL_PREFIXES(
+ ThreadRestrictionsTest,
+ ScopedAllowBaseSyncPrimitivesOutsideBlockingScopeResetsState);
+ friend class ::KeyStorageLinux;
+ friend class content::SynchronousCompositor;
+ friend class content::SynchronousCompositorHost;
+ friend class content::SynchronousCompositorSyncCallBridge;
+ friend class midi::TaskService; // https://crbug.com/796830
+ // Not used in production yet, https://crbug.com/844078.
+ friend class service_manager::ServiceProcessLauncher;
+
+ ScopedAllowBaseSyncPrimitivesOutsideBlockingScope()
+ EMPTY_BODY_IF_DCHECK_IS_OFF;
+ ~ScopedAllowBaseSyncPrimitivesOutsideBlockingScope()
+ EMPTY_BODY_IF_DCHECK_IS_OFF;
+
+#if DCHECK_IS_ON()
+ const bool was_disallowed_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedAllowBaseSyncPrimitivesOutsideBlockingScope);
+};
+
+// This can be used in tests without being a friend of
+// ScopedAllowBaseSyncPrimitives(OutsideBlockingScope).
+class BASE_EXPORT ScopedAllowBaseSyncPrimitivesForTesting {
+ public:
+ ScopedAllowBaseSyncPrimitivesForTesting() EMPTY_BODY_IF_DCHECK_IS_OFF;
+ ~ScopedAllowBaseSyncPrimitivesForTesting() EMPTY_BODY_IF_DCHECK_IS_OFF;
+
+ private:
+#if DCHECK_IS_ON()
+ const bool was_disallowed_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedAllowBaseSyncPrimitivesForTesting);
+};
+
+namespace internal {
+
+// Asserts that waiting on a //base sync primitive is allowed in the current
+// scope.
+INLINE_IF_DCHECK_IS_OFF void AssertBaseSyncPrimitivesAllowed()
+ EMPTY_BODY_IF_DCHECK_IS_OFF;
+
+// Resets all thread restrictions on the current thread.
+INLINE_IF_DCHECK_IS_OFF void ResetThreadRestrictionsForTesting()
+ EMPTY_BODY_IF_DCHECK_IS_OFF;
+
+} // namespace internal
class BASE_EXPORT ThreadRestrictions {
public:
// Constructing a ScopedAllowIO temporarily allows IO for the current
// thread. Doing this is almost certainly always incorrect.
+ //
+ // DEPRECATED. Use ScopedAllowBlocking(ForTesting).
class BASE_EXPORT ScopedAllowIO {
public:
- ScopedAllowIO() { previous_value_ = SetIOAllowed(true); }
- ~ScopedAllowIO() { SetIOAllowed(previous_value_); }
+ ScopedAllowIO() EMPTY_BODY_IF_DCHECK_IS_OFF;
+ ~ScopedAllowIO() EMPTY_BODY_IF_DCHECK_IS_OFF;
+
private:
- // Whether IO is allowed when the ScopedAllowIO was constructed.
- bool previous_value_;
+#if DCHECK_IS_ON()
+ const bool was_allowed_;
+#endif
DISALLOW_COPY_AND_ASSIGN(ScopedAllowIO);
};
@@ -139,13 +423,10 @@ class BASE_EXPORT ThreadRestrictions {
// Set whether the current thread to make IO calls.
// Threads start out in the *allowed* state.
// Returns the previous value.
+ //
+ // DEPRECATED. Use ScopedAllowBlocking(ForTesting) or ScopedDisallowBlocking.
static bool SetIOAllowed(bool allowed);
- // Check whether the current thread is allowed to make IO calls,
- // and DCHECK if not. See the block comment above the class for
- // a discussion of where to add these checks.
- static void AssertIOAllowed();
-
// Set whether the current thread can use singletons. Returns the previous
// value.
static bool SetSingletonAllowed(bool allowed);
@@ -156,47 +437,44 @@ class BASE_EXPORT ThreadRestrictions {
// Disable waiting on the current thread. Threads start out in the *allowed*
// state. Returns the previous value.
+ //
+ // DEPRECATED. Use DisallowBaseSyncPrimitives.
static void DisallowWaiting();
-
- // Check whether the current thread is allowed to wait, and DCHECK if not.
- static void AssertWaitAllowed();
#else
// Inline the empty definitions of these functions so that they can be
// compiled out.
static bool SetIOAllowed(bool allowed) { return true; }
- static void AssertIOAllowed() {}
static bool SetSingletonAllowed(bool allowed) { return true; }
static void AssertSingletonAllowed() {}
static void DisallowWaiting() {}
- static void AssertWaitAllowed() {}
#endif
private:
- // DO NOT ADD ANY OTHER FRIEND STATEMENTS, talk to jam or brettw first.
+ // DO NOT ADD ANY OTHER FRIEND STATEMENTS.
// BEGIN ALLOWED USAGE.
+ friend class android_webview::AwFormDatabaseService;
+ friend class android_webview::CookieManager;
+ friend class base::StackSamplingProfiler;
+ friend class content::BrowserMainLoop;
friend class content::BrowserShutdownProfileDumper;
- friend class content::BrowserSurfaceViewManager;
friend class content::BrowserTestBase;
friend class content::NestedMessagePumpAndroid;
friend class content::ScopedAllowWaitForAndroidLayoutTests;
friend class content::ScopedAllowWaitForDebugURL;
friend class ::HistogramSynchronizer;
friend class internal::TaskTracker;
- friend class ::ScopedAllowWaitForLegacyWebViewApi;
friend class cc::CompletionEvent;
friend class cc::SingleThreadTaskGraphRunner;
friend class content::CategorizedWorkerPool;
friend class remoting::AutoThread;
friend class ui::WindowResizeHelperMac;
friend class MessagePumpDefault;
- friend class SequencedWorkerPool;
friend class SimpleThread;
friend class Thread;
friend class ThreadTestHelper;
friend class PlatformThread;
friend class android::JavaHandlerThread;
friend class mojo::SyncCallRestrictions;
- friend class mojo::edk::ScopedIPCSupport;
friend class ui::CommandBufferClientImpl;
friend class ui::CommandBufferLocal;
friend class ui::GpuState;
@@ -226,6 +504,7 @@ class BASE_EXPORT ThreadRestrictions {
// END USAGE THAT NEEDS TO BE FIXED.
#if DCHECK_IS_ON()
+ // DEPRECATED. Use ScopedAllowBaseSyncPrimitives.
static bool SetWaitAllowed(bool allowed);
#else
static bool SetWaitAllowed(bool allowed) { return true; }
@@ -233,16 +512,18 @@ class BASE_EXPORT ThreadRestrictions {
// Constructing a ScopedAllowWait temporarily allows waiting on the current
// thread. Doing this is almost always incorrect, which is why we limit who
- // can use this through friend. If you find yourself needing to use this, find
- // another way. Talk to jam or brettw.
+ // can use this through friend.
+ //
+ // DEPRECATED. Use ScopedAllowBaseSyncPrimitives.
class BASE_EXPORT ScopedAllowWait {
public:
- ScopedAllowWait() { previous_value_ = SetWaitAllowed(true); }
- ~ScopedAllowWait() { SetWaitAllowed(previous_value_); }
+ ScopedAllowWait() EMPTY_BODY_IF_DCHECK_IS_OFF;
+ ~ScopedAllowWait() EMPTY_BODY_IF_DCHECK_IS_OFF;
+
private:
- // Whether singleton use is allowed when the ScopedAllowWait was
- // constructed.
- bool previous_value_;
+#if DCHECK_IS_ON()
+ const bool was_allowed_;
+#endif
DISALLOW_COPY_AND_ASSIGN(ScopedAllowWait);
};
diff --git a/base/threading/thread_restrictions_unittest.cc b/base/threading/thread_restrictions_unittest.cc
new file mode 100644
index 0000000000..a957a9a2b4
--- /dev/null
+++ b/base/threading/thread_restrictions_unittest.cc
@@ -0,0 +1,137 @@
+// 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/threading/thread_restrictions.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/test/gtest_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+class ThreadRestrictionsTest : public testing::Test {
+ public:
+ ThreadRestrictionsTest() = default;
+ ~ThreadRestrictionsTest() override {
+ internal::ResetThreadRestrictionsForTesting();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ThreadRestrictionsTest);
+};
+
+} // namespace
+
+TEST_F(ThreadRestrictionsTest, BlockingAllowedByDefault) {
+ AssertBlockingAllowed();
+}
+
+TEST_F(ThreadRestrictionsTest, ScopedDisallowBlocking) {
+ {
+ ScopedDisallowBlocking scoped_disallow_blocking;
+ EXPECT_DCHECK_DEATH({ AssertBlockingAllowed(); });
+ }
+ AssertBlockingAllowed();
+}
+
+TEST_F(ThreadRestrictionsTest, ScopedAllowBlocking) {
+ ScopedDisallowBlocking scoped_disallow_blocking;
+ {
+ ScopedAllowBlocking scoped_allow_blocking;
+ AssertBlockingAllowed();
+ }
+ EXPECT_DCHECK_DEATH({ AssertBlockingAllowed(); });
+}
+
+TEST_F(ThreadRestrictionsTest, ScopedAllowBlockingForTesting) {
+ ScopedDisallowBlocking scoped_disallow_blocking;
+ {
+ ScopedAllowBlockingForTesting scoped_allow_blocking_for_testing;
+ AssertBlockingAllowed();
+ }
+ EXPECT_DCHECK_DEATH({ AssertBlockingAllowed(); });
+}
+
+TEST_F(ThreadRestrictionsTest, BaseSyncPrimitivesAllowedByDefault) {}
+
+TEST_F(ThreadRestrictionsTest, DisallowBaseSyncPrimitives) {
+ DisallowBaseSyncPrimitives();
+ EXPECT_DCHECK_DEATH({ internal::AssertBaseSyncPrimitivesAllowed(); });
+}
+
+TEST_F(ThreadRestrictionsTest, ScopedAllowBaseSyncPrimitives) {
+ DisallowBaseSyncPrimitives();
+ ScopedAllowBaseSyncPrimitives scoped_allow_base_sync_primitives;
+ internal::AssertBaseSyncPrimitivesAllowed();
+}
+
+TEST_F(ThreadRestrictionsTest, ScopedAllowBaseSyncPrimitivesResetsState) {
+ DisallowBaseSyncPrimitives();
+ { ScopedAllowBaseSyncPrimitives scoped_allow_base_sync_primitives; }
+ EXPECT_DCHECK_DEATH({ internal::AssertBaseSyncPrimitivesAllowed(); });
+}
+
+TEST_F(ThreadRestrictionsTest,
+ ScopedAllowBaseSyncPrimitivesWithBlockingDisallowed) {
+ ScopedDisallowBlocking scoped_disallow_blocking;
+ DisallowBaseSyncPrimitives();
+
+ // This should DCHECK because blocking is not allowed in this scope
+ // and OutsideBlockingScope is not passed to the constructor.
+ EXPECT_DCHECK_DEATH(
+ { ScopedAllowBaseSyncPrimitives scoped_allow_base_sync_primitives; });
+}
+
+TEST_F(ThreadRestrictionsTest,
+ ScopedAllowBaseSyncPrimitivesOutsideBlockingScope) {
+ ScopedDisallowBlocking scoped_disallow_blocking;
+ DisallowBaseSyncPrimitives();
+ ScopedAllowBaseSyncPrimitivesOutsideBlockingScope
+ scoped_allow_base_sync_primitives;
+ internal::AssertBaseSyncPrimitivesAllowed();
+}
+
+TEST_F(ThreadRestrictionsTest,
+ ScopedAllowBaseSyncPrimitivesOutsideBlockingScopeResetsState) {
+ DisallowBaseSyncPrimitives();
+ {
+ ScopedAllowBaseSyncPrimitivesOutsideBlockingScope
+ scoped_allow_base_sync_primitives;
+ }
+ EXPECT_DCHECK_DEATH({ internal::AssertBaseSyncPrimitivesAllowed(); });
+}
+
+TEST_F(ThreadRestrictionsTest, ScopedAllowBaseSyncPrimitivesForTesting) {
+ DisallowBaseSyncPrimitives();
+ ScopedAllowBaseSyncPrimitivesForTesting
+ scoped_allow_base_sync_primitives_for_testing;
+ internal::AssertBaseSyncPrimitivesAllowed();
+}
+
+TEST_F(ThreadRestrictionsTest,
+ ScopedAllowBaseSyncPrimitivesForTestingResetsState) {
+ DisallowBaseSyncPrimitives();
+ {
+ ScopedAllowBaseSyncPrimitivesForTesting
+ scoped_allow_base_sync_primitives_for_testing;
+ }
+ EXPECT_DCHECK_DEATH({ internal::AssertBaseSyncPrimitivesAllowed(); });
+}
+
+TEST_F(ThreadRestrictionsTest,
+ ScopedAllowBaseSyncPrimitivesForTestingWithBlockingDisallowed) {
+ ScopedDisallowBlocking scoped_disallow_blocking;
+ DisallowBaseSyncPrimitives();
+ // This should not DCHECK.
+ ScopedAllowBaseSyncPrimitivesForTesting
+ scoped_allow_base_sync_primitives_for_testing;
+}
+
+} // namespace base
diff --git a/base/threading/thread_task_runner_handle.cc b/base/threading/thread_task_runner_handle.cc
index d71cabb135..314b303929 100644
--- a/base/threading/thread_task_runner_handle.cc
+++ b/base/threading/thread_task_runner_handle.cc
@@ -10,6 +10,7 @@
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_local.h"
@@ -18,20 +19,22 @@ namespace base {
namespace {
base::LazyInstance<base::ThreadLocalPointer<ThreadTaskRunnerHandle>>::Leaky
- lazy_tls_ptr = LAZY_INSTANCE_INITIALIZER;
+ thread_task_runner_tls = LAZY_INSTANCE_INITIALIZER;
} // namespace
// static
scoped_refptr<SingleThreadTaskRunner> ThreadTaskRunnerHandle::Get() {
- ThreadTaskRunnerHandle* current = lazy_tls_ptr.Pointer()->Get();
- DCHECK(current);
+ ThreadTaskRunnerHandle* current = thread_task_runner_tls.Pointer()->Get();
+ CHECK(current) << "Error: This caller requires a single-threaded context "
+ "(i.e. the current task needs to run from a "
+ "SingleThreadTaskRunner).";
return current->task_runner_;
}
// static
bool ThreadTaskRunnerHandle::IsSet() {
- return !!lazy_tls_ptr.Pointer()->Get();
+ return !!thread_task_runner_tls.Pointer()->Get();
}
// static
@@ -51,22 +54,27 @@ ScopedClosureRunner ThreadTaskRunnerHandle::OverrideForTesting(
DCHECK(!SequencedTaskRunnerHandle::IsSet() || IsSet());
if (!IsSet()) {
- std::unique_ptr<ThreadTaskRunnerHandle> top_level_ttrh =
- MakeUnique<ThreadTaskRunnerHandle>(std::move(overriding_task_runner));
- return ScopedClosureRunner(base::Bind(
+ auto top_level_ttrh = std::make_unique<ThreadTaskRunnerHandle>(
+ std::move(overriding_task_runner));
+ return ScopedClosureRunner(base::BindOnce(
[](std::unique_ptr<ThreadTaskRunnerHandle> ttrh_to_release) {},
- base::Passed(&top_level_ttrh)));
+ std::move(top_level_ttrh)));
}
- ThreadTaskRunnerHandle* ttrh = lazy_tls_ptr.Pointer()->Get();
+ ThreadTaskRunnerHandle* ttrh = thread_task_runner_tls.Pointer()->Get();
// Swap the two (and below bind |overriding_task_runner|, which is now the
// previous one, as the |task_runner_to_restore|).
ttrh->task_runner_.swap(overriding_task_runner);
- return ScopedClosureRunner(base::Bind(
+ auto no_running_during_override =
+ std::make_unique<RunLoop::ScopedDisallowRunningForTesting>();
+
+ return ScopedClosureRunner(base::BindOnce(
[](scoped_refptr<SingleThreadTaskRunner> task_runner_to_restore,
- SingleThreadTaskRunner* expected_task_runner_before_restore) {
- ThreadTaskRunnerHandle* ttrh = lazy_tls_ptr.Pointer()->Get();
+ SingleThreadTaskRunner* expected_task_runner_before_restore,
+ std::unique_ptr<RunLoop::ScopedDisallowRunningForTesting>
+ no_running_during_override) {
+ ThreadTaskRunnerHandle* ttrh = thread_task_runner_tls.Pointer()->Get();
DCHECK_EQ(expected_task_runner_before_restore, ttrh->task_runner_.get())
<< "Nested overrides must expire their ScopedClosureRunners "
@@ -74,8 +82,9 @@ ScopedClosureRunner ThreadTaskRunnerHandle::OverrideForTesting(
ttrh->task_runner_.swap(task_runner_to_restore);
},
- base::Passed(&overriding_task_runner),
- base::Unretained(ttrh->task_runner_.get())));
+ std::move(overriding_task_runner),
+ base::Unretained(ttrh->task_runner_.get()),
+ std::move(no_running_during_override)));
}
ThreadTaskRunnerHandle::ThreadTaskRunnerHandle(
@@ -85,13 +94,13 @@ ThreadTaskRunnerHandle::ThreadTaskRunnerHandle(
// No SequencedTaskRunnerHandle (which includes ThreadTaskRunnerHandles)
// should already be set for this thread.
DCHECK(!SequencedTaskRunnerHandle::IsSet());
- lazy_tls_ptr.Pointer()->Set(this);
+ thread_task_runner_tls.Pointer()->Set(this);
}
ThreadTaskRunnerHandle::~ThreadTaskRunnerHandle() {
DCHECK(task_runner_->BelongsToCurrentThread());
- DCHECK_EQ(lazy_tls_ptr.Pointer()->Get(), this);
- lazy_tls_ptr.Pointer()->Set(nullptr);
+ DCHECK_EQ(thread_task_runner_tls.Pointer()->Get(), this);
+ thread_task_runner_tls.Pointer()->Set(nullptr);
}
} // namespace base
diff --git a/base/threading/thread_task_runner_handle.h b/base/threading/thread_task_runner_handle.h
index 7ae85e6dcf..f6b71d7b20 100644
--- a/base/threading/thread_task_runner_handle.h
+++ b/base/threading/thread_task_runner_handle.h
@@ -7,6 +7,7 @@
#include "base/base_export.h"
#include "base/callback_helpers.h"
+#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/single_thread_task_runner.h"
@@ -17,7 +18,7 @@ namespace base {
// in thread-local storage. Callers can then retrieve the TaskRunner
// for the current thread by calling ThreadTaskRunnerHandle::Get().
// At most one TaskRunner may be bound to each thread at a time.
-// Prefer SequenceTaskRunnerHandle to this unless thread affinity is required.
+// Prefer SequencedTaskRunnerHandle to this unless thread affinity is required.
class BASE_EXPORT ThreadTaskRunnerHandle {
public:
// Gets the SingleThreadTaskRunner for the current thread.
@@ -36,7 +37,8 @@ class BASE_EXPORT ThreadTaskRunnerHandle {
// tests where multiple task runners can share the main thread for simplicity
// and determinism.
static ScopedClosureRunner OverrideForTesting(
- scoped_refptr<SingleThreadTaskRunner> overriding_task_runner);
+ scoped_refptr<SingleThreadTaskRunner> overriding_task_runner)
+ WARN_UNUSED_RESULT;
// Binds |task_runner| to the current thread. |task_runner| must belong
// to the current thread for this to succeed.
diff --git a/base/threading/thread_unittest.cc b/base/threading/thread_unittest.cc
index 0cb964e8f7..d90b1f9e88 100644
--- a/base/threading/thread_unittest.cc
+++ b/base/threading/thread_unittest.cc
@@ -7,6 +7,7 @@
#include <stddef.h>
#include <stdint.h>
+#include <utility>
#include <vector>
#include "base/bind.h"
@@ -14,6 +15,7 @@
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_current.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/waitable_event.h"
@@ -99,7 +101,7 @@ class CaptureToEventList : public Thread {
// Observer that writes a value into |event_list| when a message loop has been
// destroyed.
class CapturingDestructionObserver
- : public base::MessageLoop::DestructionObserver {
+ : public base::MessageLoopCurrent::DestructionObserver {
public:
// |event_list| must remain valid throughout the observer's lifetime.
explicit CapturingDestructionObserver(EventList* event_list)
@@ -109,7 +111,7 @@ class CapturingDestructionObserver
// DestructionObserver implementation:
void WillDestroyCurrentMessageLoop() override {
event_list_->push_back(THREAD_EVENT_MESSAGE_LOOP_DESTROYED);
- event_list_ = NULL;
+ event_list_ = nullptr;
}
private:
@@ -120,8 +122,8 @@ class CapturingDestructionObserver
// Task that adds a destruction observer to the current message loop.
void RegisterDestructionObserver(
- base::MessageLoop::DestructionObserver* observer) {
- base::MessageLoop::current()->AddDestructionObserver(observer);
+ base::MessageLoopCurrent::DestructionObserver* observer) {
+ base::MessageLoopCurrent::Get()->AddDestructionObserver(observer);
}
// Task that calls GetThreadId() of |thread|, stores the result into |id|, then
@@ -155,8 +157,9 @@ TEST_F(ThreadTest, StartWithOptions_StackSize) {
base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
- a.task_runner()->PostTask(FROM_HERE, base::Bind(&base::WaitableEvent::Signal,
- base::Unretained(&event)));
+ a.task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&base::WaitableEvent::Signal, base::Unretained(&event)));
event.Wait();
}
@@ -188,9 +191,9 @@ TEST_F(ThreadTest, StartWithOptions_NonJoinable) {
base::WaitableEvent block_event(
base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
- a->task_runner()->PostTask(
- FROM_HERE,
- base::Bind(&base::WaitableEvent::Wait, base::Unretained(&block_event)));
+ a->task_runner()->PostTask(FROM_HERE,
+ base::BindOnce(&base::WaitableEvent::Wait,
+ base::Unretained(&block_event)));
a->StopSoon();
EXPECT_TRUE(a->IsRunning());
@@ -215,11 +218,11 @@ TEST_F(ThreadTest, TwoTasksOnJoinableThread) {
// destroyed. We do this by dispatching a sleep event before the
// event that will toggle our sentinel value.
a.task_runner()->PostTask(
- FROM_HERE, base::Bind(static_cast<void (*)(base::TimeDelta)>(
- &base::PlatformThread::Sleep),
- base::TimeDelta::FromMilliseconds(20)));
+ FROM_HERE, base::BindOnce(static_cast<void (*)(base::TimeDelta)>(
+ &base::PlatformThread::Sleep),
+ base::TimeDelta::FromMilliseconds(20)));
a.task_runner()->PostTask(FROM_HERE,
- base::Bind(&ToggleValue, &was_invoked));
+ base::BindOnce(&ToggleValue, &was_invoked));
}
EXPECT_TRUE(was_invoked);
}
@@ -285,8 +288,8 @@ TEST_F(ThreadTest, DISABLED_StopOnNonOwningThreadIsDeath) {
b.Start();
EXPECT_DCHECK_DEATH({
// Stopping |a| on |b| isn't allowed.
- b.task_runner()->PostTask(FROM_HERE,
- base::Bind(&Thread::Stop, base::Unretained(&a)));
+ b.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&Thread::Stop, base::Unretained(&a)));
// Block here so the DCHECK on |b| always happens in this scope.
base::PlatformThread::Sleep(base::TimeDelta::Max());
});
@@ -294,7 +297,7 @@ TEST_F(ThreadTest, DISABLED_StopOnNonOwningThreadIsDeath) {
TEST_F(ThreadTest, TransferOwnershipAndStop) {
std::unique_ptr<Thread> a =
- base::MakeUnique<Thread>("TransferOwnershipAndStop");
+ std::make_unique<Thread>("TransferOwnershipAndStop");
EXPECT_TRUE(a->StartAndWaitForTesting());
EXPECT_TRUE(a->IsRunning());
@@ -307,13 +310,13 @@ TEST_F(ThreadTest, TransferOwnershipAndStop) {
// a->DetachFromSequence() should allow |b| to use |a|'s Thread API.
a->DetachFromSequence();
b.task_runner()->PostTask(
- FROM_HERE, base::Bind(
+ FROM_HERE, base::BindOnce(
[](std::unique_ptr<Thread> thread_to_stop,
base::WaitableEvent* event_to_signal) -> void {
thread_to_stop->Stop();
event_to_signal->Signal();
},
- base::Passed(&a), base::Unretained(&event)));
+ std::move(a), base::Unretained(&event)));
event.Wait();
}
@@ -361,9 +364,9 @@ TEST_F(ThreadTest, StartTwiceNonJoinableNotAllowed) {
base::WaitableEvent last_task_event(
base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
- a->task_runner()->PostTask(FROM_HERE,
- base::Bind(&base::WaitableEvent::Signal,
- base::Unretained(&last_task_event)));
+ a->task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&base::WaitableEvent::Signal,
+ base::Unretained(&last_task_event)));
// StopSoon() is non-blocking, Yield() to |a|, wait for last task to be
// processed and a little more for QuitWhenIdle() to unwind before considering
@@ -399,7 +402,8 @@ TEST_F(ThreadTest, ThreadId) {
base::WaitableEvent::InitialState::NOT_SIGNALED);
base::PlatformThreadId id_from_new_thread;
a.task_runner()->PostTask(
- FROM_HERE, base::Bind(ReturnThreadId, &a, &id_from_new_thread, &event));
+ FROM_HERE,
+ base::BindOnce(ReturnThreadId, &a, &id_from_new_thread, &event));
// Call GetThreadId() on the current thread before calling event.Wait() so
// that this test can find a race issue with TSAN.
@@ -443,7 +447,7 @@ TEST_F(ThreadTest, SleepInsideInit) {
//
// (1) Thread::CleanUp()
// (2) MessageLoop::~MessageLoop()
-// MessageLoop::DestructionObservers called.
+// MessageLoopCurrent::DestructionObservers called.
TEST_F(ThreadTest, CleanUp) {
EventList captured_events;
CapturingDestructionObserver loop_destruction_observer(&captured_events);
@@ -458,8 +462,9 @@ TEST_F(ThreadTest, CleanUp) {
// Register an observer that writes into |captured_events| once the
// thread's message loop is destroyed.
t.task_runner()->PostTask(
- FROM_HERE, base::Bind(&RegisterDestructionObserver,
- base::Unretained(&loop_destruction_observer)));
+ FROM_HERE,
+ base::BindOnce(&RegisterDestructionObserver,
+ base::Unretained(&loop_destruction_observer)));
// Upon leaving this scope, the thread is deleted.
}
@@ -503,7 +508,8 @@ TEST_F(ThreadTest, FlushForTesting) {
for (size_t i = 0; i < kNumSleepTasks; ++i) {
a.task_runner()->PostTask(
- FROM_HERE, base::Bind(&base::PlatformThread::Sleep, kSleepPerTestTask));
+ FROM_HERE,
+ base::BindOnce(&base::PlatformThread::Sleep, kSleepPerTestTask));
}
// All tasks should have executed, as reflected by the elapsed time.
@@ -557,7 +563,7 @@ TEST_F(ThreadTest, ExternalMessageLoop) {
bool ran = false;
a.task_runner()->PostTask(
- FROM_HERE, base::Bind([](bool* toggled) { *toggled = true; }, &ran));
+ FROM_HERE, base::BindOnce([](bool* toggled) { *toggled = true; }, &ran));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(ran);
diff --git a/base/threading/worker_pool.cc b/base/threading/worker_pool.cc
deleted file mode 100644
index 26ff10f1f5..0000000000
--- a/base/threading/worker_pool.cc
+++ /dev/null
@@ -1,125 +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/threading/worker_pool.h"
-
-#include <utility>
-
-#include "base/bind.h"
-#include "base/compiler_specific.h"
-#include "base/debug/leak_annotations.h"
-#include "base/macros.h"
-#include "base/task_runner.h"
-#include "base/threading/post_task_and_reply_impl.h"
-#include "base/tracked_objects.h"
-
-namespace base {
-
-namespace {
-
-class PostTaskAndReplyWorkerPool : public internal::PostTaskAndReplyImpl {
- public:
- explicit PostTaskAndReplyWorkerPool(bool task_is_slow)
- : task_is_slow_(task_is_slow) {
- }
- ~PostTaskAndReplyWorkerPool() override = default;
-
- private:
- bool PostTask(const tracked_objects::Location& from_here,
- OnceClosure task) override {
- return WorkerPool::PostTask(from_here, std::move(task), task_is_slow_);
- }
-
- bool task_is_slow_;
-};
-
-// WorkerPoolTaskRunner ---------------------------------------------
-// A TaskRunner which posts tasks to a WorkerPool with a
-// fixed ShutdownBehavior.
-//
-// Note that this class is RefCountedThreadSafe (inherited from TaskRunner).
-class WorkerPoolTaskRunner : public TaskRunner {
- public:
- explicit WorkerPoolTaskRunner(bool tasks_are_slow);
-
- // TaskRunner implementation
- bool PostDelayedTask(const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay) override;
- bool RunsTasksOnCurrentThread() const override;
-
- private:
- ~WorkerPoolTaskRunner() override;
-
- // Helper function for posting a delayed task. Asserts that the delay is
- // zero because non-zero delays are not supported.
- bool PostDelayedTaskAssertZeroDelay(
- const tracked_objects::Location& from_here,
- OnceClosure task,
- base::TimeDelta delay);
-
- const bool tasks_are_slow_;
-
- DISALLOW_COPY_AND_ASSIGN(WorkerPoolTaskRunner);
-};
-
-WorkerPoolTaskRunner::WorkerPoolTaskRunner(bool tasks_are_slow)
- : tasks_are_slow_(tasks_are_slow) {
-}
-
-WorkerPoolTaskRunner::~WorkerPoolTaskRunner() {
-}
-
-bool WorkerPoolTaskRunner::PostDelayedTask(
- const tracked_objects::Location& from_here,
- OnceClosure task,
- TimeDelta delay) {
- return PostDelayedTaskAssertZeroDelay(from_here, std::move(task), delay);
-}
-
-bool WorkerPoolTaskRunner::RunsTasksOnCurrentThread() const {
- return WorkerPool::RunsTasksOnCurrentThread();
-}
-
-bool WorkerPoolTaskRunner::PostDelayedTaskAssertZeroDelay(
- const tracked_objects::Location& from_here,
- OnceClosure task,
- base::TimeDelta delay) {
- DCHECK_EQ(delay.InMillisecondsRoundedUp(), 0)
- << "WorkerPoolTaskRunner does not support non-zero delays";
- return WorkerPool::PostTask(from_here, std::move(task), tasks_are_slow_);
-}
-
-struct TaskRunnerHolder {
- TaskRunnerHolder() {
- taskrunners_[0] = new WorkerPoolTaskRunner(false);
- taskrunners_[1] = new WorkerPoolTaskRunner(true);
- }
- scoped_refptr<TaskRunner> taskrunners_[2];
-};
-
-} // namespace
-
-bool WorkerPool::PostTaskAndReply(const tracked_objects::Location& from_here,
- OnceClosure task,
- OnceClosure reply,
- bool task_is_slow) {
- // Do not report PostTaskAndReplyRelay leaks in tests. There's nothing we can
- // do about them because WorkerPool doesn't have a flushing API.
- // http://crbug.com/248513
- // http://crbug.com/290897
- // Note: this annotation does not cover tasks posted through a TaskRunner.
- ANNOTATE_SCOPED_MEMORY_LEAK;
- return PostTaskAndReplyWorkerPool(task_is_slow)
- .PostTaskAndReply(from_here, std::move(task), std::move(reply));
-}
-
-// static
-const scoped_refptr<TaskRunner>&
-WorkerPool::GetTaskRunner(bool tasks_are_slow) {
- static auto* task_runner_holder = new TaskRunnerHolder();
- return task_runner_holder->taskrunners_[tasks_are_slow];
-}
-
-} // namespace base
diff --git a/base/threading/worker_pool.h b/base/threading/worker_pool.h
deleted file mode 100644
index d1c666d2f9..0000000000
--- a/base/threading/worker_pool.h
+++ /dev/null
@@ -1,59 +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_THREADING_WORKER_POOL_H_
-#define BASE_THREADING_WORKER_POOL_H_
-
-#include "base/base_export.h"
-#include "base/callback.h"
-#include "base/memory/ref_counted.h"
-
-namespace tracked_objects {
-class Location;
-} // namespace tracked_objects
-
-namespace base {
-
-class TaskRunner;
-
-// This is a facility that runs tasks that don't require a specific thread or
-// a message loop.
-//
-// WARNING: This shouldn't be used unless absolutely necessary. We don't wait
-// for the worker pool threads to finish on shutdown, so the tasks running
-// inside the pool must be extremely careful about other objects they access
-// (MessageLoops, Singletons, etc). During shutdown these object may no longer
-// exist.
-class BASE_EXPORT WorkerPool {
- public:
- // This function posts |task| to run on a worker thread. |task_is_slow|
- // should be used for tasks that will take a long time to execute. Returns
- // false if |task| could not be posted to a worker thread. Regardless of
- // return value, ownership of |task| is transferred to the worker pool.
- static bool PostTask(const tracked_objects::Location& from_here,
- OnceClosure task,
- bool task_is_slow);
-
- // Just like TaskRunner::PostTaskAndReply, except the destination
- // for |task| is a worker thread and you can specify |task_is_slow| just
- // like you can for PostTask above.
- static bool PostTaskAndReply(const tracked_objects::Location& from_here,
- OnceClosure task,
- OnceClosure reply,
- bool task_is_slow);
-
- // Return true if the current thread is one that this WorkerPool runs tasks
- // on. (Note that if the Windows worker pool is used without going through
- // this WorkerPool interface, RunsTasksOnCurrentThread would return false on
- // those threads.)
- static bool RunsTasksOnCurrentThread();
-
- // Get a TaskRunner wrapper which posts to the WorkerPool using the given
- // |task_is_slow| behavior.
- static const scoped_refptr<TaskRunner>& GetTaskRunner(bool task_is_slow);
-};
-
-} // namespace base
-
-#endif // BASE_THREADING_WORKER_POOL_H_
diff --git a/base/threading/worker_pool_posix.cc b/base/threading/worker_pool_posix.cc
deleted file mode 100644
index 851480adda..0000000000
--- a/base/threading/worker_pool_posix.cc
+++ /dev/null
@@ -1,190 +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/threading/worker_pool_posix.h"
-
-#include <stddef.h>
-
-#include <utility>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/lazy_instance.h"
-#include "base/logging.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/strings/stringprintf.h"
-#include "base/threading/platform_thread.h"
-#include "base/threading/thread_local.h"
-#include "base/threading/worker_pool.h"
-#include "base/trace_event/trace_event.h"
-#include "base/tracked_objects.h"
-
-using tracked_objects::TrackedTime;
-
-namespace base {
-
-namespace {
-
-base::LazyInstance<ThreadLocalBoolean>::Leaky
- g_worker_pool_running_on_this_thread = LAZY_INSTANCE_INITIALIZER;
-
-const int kIdleSecondsBeforeExit = 10 * 60;
-
-#if defined(OS_MACOSX)
-// On Mac OS X a background thread's default stack size is 512Kb. We need at
-// least 1MB for compilation tasks in V8, so increase this default.
-const int kStackSize = 1 * 1024 * 1024;
-#else
-const int kStackSize = 0;
-#endif
-
-class WorkerPoolImpl {
- public:
- WorkerPoolImpl();
-
- // WorkerPoolImpl is only instantiated as a leaky LazyInstance, so the
- // destructor is never called.
- ~WorkerPoolImpl() = delete;
-
- void PostTask(const tracked_objects::Location& from_here,
- base::OnceClosure task,
- bool task_is_slow);
-
- private:
- scoped_refptr<base::PosixDynamicThreadPool> pool_;
-};
-
-WorkerPoolImpl::WorkerPoolImpl()
- : pool_(new base::PosixDynamicThreadPool("WorkerPool",
- kIdleSecondsBeforeExit)) {}
-
-void WorkerPoolImpl::PostTask(const tracked_objects::Location& from_here,
- base::OnceClosure task,
- bool task_is_slow) {
- pool_->PostTask(from_here, std::move(task));
-}
-
-base::LazyInstance<WorkerPoolImpl>::Leaky g_lazy_worker_pool =
- LAZY_INSTANCE_INITIALIZER;
-
-class WorkerThread : public PlatformThread::Delegate {
- public:
- WorkerThread(const std::string& name_prefix,
- base::PosixDynamicThreadPool* pool)
- : name_prefix_(name_prefix), pool_(pool) {}
-
- void ThreadMain() override;
-
- private:
- const std::string name_prefix_;
- scoped_refptr<base::PosixDynamicThreadPool> pool_;
-
- DISALLOW_COPY_AND_ASSIGN(WorkerThread);
-};
-
-void WorkerThread::ThreadMain() {
- g_worker_pool_running_on_this_thread.Get().Set(true);
- const std::string name = base::StringPrintf("%s/%d", name_prefix_.c_str(),
- PlatformThread::CurrentId());
- // Note |name.c_str()| must remain valid for for the whole life of the thread.
- PlatformThread::SetName(name);
-
- for (;;) {
- PendingTask pending_task = pool_->WaitForTask();
- if (pending_task.task.is_null())
- break;
- TRACE_TASK_EXECUTION("WorkerThread::ThreadMain::Run", pending_task);
-
- tracked_objects::TaskStopwatch stopwatch;
- stopwatch.Start();
- std::move(pending_task.task).Run();
- stopwatch.Stop();
-
- tracked_objects::ThreadData::TallyRunOnWorkerThreadIfTracking(
- pending_task.birth_tally, pending_task.time_posted, stopwatch);
- }
-
- // The WorkerThread is non-joinable, so it deletes itself.
- delete this;
-}
-
-} // namespace
-
-// static
-bool WorkerPool::PostTask(const tracked_objects::Location& from_here,
- base::OnceClosure task,
- bool task_is_slow) {
- g_lazy_worker_pool.Pointer()->PostTask(from_here, std::move(task),
- task_is_slow);
- return true;
-}
-
-// static
-bool WorkerPool::RunsTasksOnCurrentThread() {
- return g_worker_pool_running_on_this_thread.Get().Get();
-}
-
-PosixDynamicThreadPool::PosixDynamicThreadPool(const std::string& name_prefix,
- int idle_seconds_before_exit)
- : name_prefix_(name_prefix),
- idle_seconds_before_exit_(idle_seconds_before_exit),
- pending_tasks_available_cv_(&lock_),
- num_idle_threads_(0) {}
-
-PosixDynamicThreadPool::~PosixDynamicThreadPool() {
- while (!pending_tasks_.empty())
- pending_tasks_.pop();
-}
-
-void PosixDynamicThreadPool::PostTask(
- const tracked_objects::Location& from_here,
- base::OnceClosure task) {
- PendingTask pending_task(from_here, std::move(task));
- AddTask(&pending_task);
-}
-
-void PosixDynamicThreadPool::AddTask(PendingTask* pending_task) {
- DCHECK(pending_task);
- DCHECK(pending_task->task);
- AutoLock locked(lock_);
-
- pending_tasks_.push(std::move(*pending_task));
-
- // We have enough worker threads.
- if (static_cast<size_t>(num_idle_threads_) >= pending_tasks_.size()) {
- pending_tasks_available_cv_.Signal();
- } else {
- // The new PlatformThread will take ownership of the WorkerThread object,
- // which will delete itself on exit.
- WorkerThread* worker = new WorkerThread(name_prefix_, this);
- PlatformThread::CreateNonJoinable(kStackSize, worker);
- }
-}
-
-PendingTask PosixDynamicThreadPool::WaitForTask() {
- AutoLock locked(lock_);
-
- if (pending_tasks_.empty()) { // No work available, wait for work.
- num_idle_threads_++;
- if (num_idle_threads_cv_.get())
- num_idle_threads_cv_->Signal();
- pending_tasks_available_cv_.TimedWait(
- TimeDelta::FromSeconds(idle_seconds_before_exit_));
- num_idle_threads_--;
- if (num_idle_threads_cv_.get())
- num_idle_threads_cv_->Signal();
- if (pending_tasks_.empty()) {
- // We waited for work, but there's still no work. Return NULL to signal
- // the thread to terminate.
- return PendingTask(FROM_HERE, base::Closure());
- }
- }
-
- PendingTask pending_task = std::move(pending_tasks_.front());
- pending_tasks_.pop();
- return pending_task;
-}
-
-} // namespace base
diff --git a/base/threading/worker_pool_posix.h b/base/threading/worker_pool_posix.h
deleted file mode 100644
index 0b10adf8f3..0000000000
--- a/base/threading/worker_pool_posix.h
+++ /dev/null
@@ -1,90 +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.
-//
-// The thread pool used in the POSIX implementation of WorkerPool dynamically
-// adds threads as necessary to handle all tasks. It keeps old threads around
-// for a period of time to allow them to be reused. After this waiting period,
-// the threads exit. This thread pool uses non-joinable threads, therefore
-// worker threads are not joined during process shutdown. This means that
-// potentially long running tasks (such as DNS lookup) do not block process
-// shutdown, but also means that process shutdown may "leak" objects. Note that
-// although PosixDynamicThreadPool spawns the worker threads and manages the
-// task queue, it does not own the worker threads. The worker threads ask the
-// PosixDynamicThreadPool for work and eventually clean themselves up. The
-// worker threads all maintain scoped_refptrs to the PosixDynamicThreadPool
-// instance, which prevents PosixDynamicThreadPool from disappearing before all
-// worker threads exit. The owner of PosixDynamicThreadPool should likewise
-// maintain a scoped_refptr to the PosixDynamicThreadPool instance.
-//
-// NOTE: The classes defined in this file are only meant for use by the POSIX
-// implementation of WorkerPool. No one else should be using these classes.
-// These symbols are exported in a header purely for testing purposes.
-
-#ifndef BASE_THREADING_WORKER_POOL_POSIX_H_
-#define BASE_THREADING_WORKER_POOL_POSIX_H_
-
-#include <memory>
-#include <queue>
-#include <string>
-
-#include "base/callback.h"
-#include "base/location.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/pending_task.h"
-#include "base/synchronization/condition_variable.h"
-#include "base/synchronization/lock.h"
-#include "base/threading/platform_thread.h"
-#include "base/tracked_objects.h"
-
-namespace base {
-
-class BASE_EXPORT PosixDynamicThreadPool
- : public RefCountedThreadSafe<PosixDynamicThreadPool> {
- public:
- class PosixDynamicThreadPoolPeer;
-
- // All worker threads will share the same |name_prefix|. They will exit after
- // |idle_seconds_before_exit|.
- PosixDynamicThreadPool(const std::string& name_prefix,
- int idle_seconds_before_exit);
-
- // Adds |task| to the thread pool.
- void PostTask(const tracked_objects::Location& from_here, OnceClosure task);
-
- // Worker thread method to wait for up to |idle_seconds_before_exit| for more
- // work from the thread pool. Returns NULL if no work is available.
- PendingTask WaitForTask();
-
- private:
- friend class RefCountedThreadSafe<PosixDynamicThreadPool>;
- friend class PosixDynamicThreadPoolPeer;
-
- ~PosixDynamicThreadPool();
-
- // Adds pending_task to the thread pool. This function will clear
- // |pending_task->task|.
- void AddTask(PendingTask* pending_task);
-
- const std::string name_prefix_;
- const int idle_seconds_before_exit_;
-
- Lock lock_; // Protects all the variables below.
-
- // Signal()s worker threads to let them know more tasks are available.
- // Also used for Broadcast()'ing to worker threads to let them know the pool
- // is being deleted and they can exit.
- ConditionVariable pending_tasks_available_cv_;
- int num_idle_threads_;
- TaskQueue pending_tasks_;
- // Only used for tests to ensure correct thread ordering. It will always be
- // NULL in non-test code.
- std::unique_ptr<ConditionVariable> num_idle_threads_cv_;
-
- DISALLOW_COPY_AND_ASSIGN(PosixDynamicThreadPool);
-};
-
-} // namespace base
-
-#endif // BASE_THREADING_WORKER_POOL_POSIX_H_
diff --git a/base/threading/worker_pool_posix_unittest.cc b/base/threading/worker_pool_posix_unittest.cc
deleted file mode 100644
index b4e8b58520..0000000000
--- a/base/threading/worker_pool_posix_unittest.cc
+++ /dev/null
@@ -1,250 +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/threading/worker_pool_posix.h"
-
-#include <set>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/macros.h"
-#include "base/synchronization/condition_variable.h"
-#include "base/synchronization/lock.h"
-#include "base/synchronization/waitable_event.h"
-#include "base/threading/platform_thread.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace base {
-
-// Peer class to provide passthrough access to PosixDynamicThreadPool internals.
-class PosixDynamicThreadPool::PosixDynamicThreadPoolPeer {
- public:
- explicit PosixDynamicThreadPoolPeer(PosixDynamicThreadPool* pool)
- : pool_(pool) {}
-
- Lock* lock() { return &pool_->lock_; }
- ConditionVariable* pending_tasks_available_cv() {
- return &pool_->pending_tasks_available_cv_;
- }
- const std::queue<PendingTask>& pending_tasks() const {
- return pool_->pending_tasks_;
- }
- int num_idle_threads() const { return pool_->num_idle_threads_; }
- ConditionVariable* num_idle_threads_cv() {
- return pool_->num_idle_threads_cv_.get();
- }
- void set_num_idle_threads_cv(ConditionVariable* cv) {
- pool_->num_idle_threads_cv_.reset(cv);
- }
-
- private:
- PosixDynamicThreadPool* pool_;
-
- DISALLOW_COPY_AND_ASSIGN(PosixDynamicThreadPoolPeer);
-};
-
-namespace {
-
-// IncrementingTask's main purpose is to increment a counter. It also updates a
-// set of unique thread ids, and signals a ConditionVariable on completion.
-// Note that since it does not block, there is no way to control the number of
-// threads used if more than one IncrementingTask is consecutively posted to the
-// thread pool, since the first one might finish executing before the subsequent
-// PostTask() calls get invoked.
-void IncrementingTask(Lock* counter_lock,
- int* counter,
- Lock* unique_threads_lock,
- std::set<PlatformThreadId>* unique_threads) {
- {
- base::AutoLock locked(*unique_threads_lock);
- unique_threads->insert(PlatformThread::CurrentId());
- }
- base::AutoLock locked(*counter_lock);
- (*counter)++;
-}
-
-// BlockingIncrementingTask is a simple wrapper around IncrementingTask that
-// allows for waiting at the start of Run() for a WaitableEvent to be signalled.
-struct BlockingIncrementingTaskArgs {
- Lock* counter_lock;
- int* counter;
- Lock* unique_threads_lock;
- std::set<PlatformThreadId>* unique_threads;
- Lock* num_waiting_to_start_lock;
- int* num_waiting_to_start;
- ConditionVariable* num_waiting_to_start_cv;
- base::WaitableEvent* start;
-};
-
-void BlockingIncrementingTask(const BlockingIncrementingTaskArgs& args) {
- {
- base::AutoLock num_waiting_to_start_locked(*args.num_waiting_to_start_lock);
- (*args.num_waiting_to_start)++;
- }
- args.num_waiting_to_start_cv->Signal();
- args.start->Wait();
- IncrementingTask(args.counter_lock, args.counter, args.unique_threads_lock,
- args.unique_threads);
-}
-
-class PosixDynamicThreadPoolTest : public testing::Test {
- protected:
- PosixDynamicThreadPoolTest()
- : pool_(new base::PosixDynamicThreadPool("dynamic_pool", 60 * 60)),
- peer_(pool_.get()),
- counter_(0),
- num_waiting_to_start_(0),
- num_waiting_to_start_cv_(&num_waiting_to_start_lock_),
- start_(WaitableEvent::ResetPolicy::MANUAL,
- WaitableEvent::InitialState::NOT_SIGNALED) {}
-
- void SetUp() override {
- peer_.set_num_idle_threads_cv(new ConditionVariable(peer_.lock()));
- }
-
- void WaitForTasksToStart(int num_tasks) {
- base::AutoLock num_waiting_to_start_locked(num_waiting_to_start_lock_);
- while (num_waiting_to_start_ < num_tasks) {
- num_waiting_to_start_cv_.Wait();
- }
- }
-
- void WaitForIdleThreads(int num_idle_threads) {
- base::AutoLock pool_locked(*peer_.lock());
- while (peer_.num_idle_threads() < num_idle_threads) {
- peer_.num_idle_threads_cv()->Wait();
- }
- }
-
- base::Closure CreateNewIncrementingTaskCallback() {
- return base::Bind(&IncrementingTask, &counter_lock_, &counter_,
- &unique_threads_lock_, &unique_threads_);
- }
-
- base::Closure CreateNewBlockingIncrementingTaskCallback() {
- BlockingIncrementingTaskArgs args = {
- &counter_lock_, &counter_, &unique_threads_lock_, &unique_threads_,
- &num_waiting_to_start_lock_, &num_waiting_to_start_,
- &num_waiting_to_start_cv_, &start_
- };
- return base::Bind(&BlockingIncrementingTask, args);
- }
-
- scoped_refptr<base::PosixDynamicThreadPool> pool_;
- base::PosixDynamicThreadPool::PosixDynamicThreadPoolPeer peer_;
- Lock counter_lock_;
- int counter_;
- Lock unique_threads_lock_;
- std::set<PlatformThreadId> unique_threads_;
- Lock num_waiting_to_start_lock_;
- int num_waiting_to_start_;
- ConditionVariable num_waiting_to_start_cv_;
- base::WaitableEvent start_;
-};
-
-} // namespace
-
-TEST_F(PosixDynamicThreadPoolTest, Basic) {
- EXPECT_EQ(0, peer_.num_idle_threads());
- EXPECT_EQ(0U, unique_threads_.size());
- EXPECT_EQ(0U, peer_.pending_tasks().size());
-
- // Add one task and wait for it to be completed.
- pool_->PostTask(FROM_HERE, CreateNewIncrementingTaskCallback());
-
- WaitForIdleThreads(1);
-
- EXPECT_EQ(1U, unique_threads_.size()) <<
- "There should be only one thread allocated for one task.";
- EXPECT_EQ(1, counter_);
-}
-
-TEST_F(PosixDynamicThreadPoolTest, ReuseIdle) {
- // Add one task and wait for it to be completed.
- pool_->PostTask(FROM_HERE, CreateNewIncrementingTaskCallback());
-
- WaitForIdleThreads(1);
-
- // Add another 2 tasks. One should reuse the existing worker thread.
- pool_->PostTask(FROM_HERE, CreateNewBlockingIncrementingTaskCallback());
- pool_->PostTask(FROM_HERE, CreateNewBlockingIncrementingTaskCallback());
-
- WaitForTasksToStart(2);
- start_.Signal();
- WaitForIdleThreads(2);
-
- EXPECT_EQ(2U, unique_threads_.size());
- EXPECT_EQ(2, peer_.num_idle_threads());
- EXPECT_EQ(3, counter_);
-}
-
-TEST_F(PosixDynamicThreadPoolTest, TwoActiveTasks) {
- // Add two blocking tasks.
- pool_->PostTask(FROM_HERE, CreateNewBlockingIncrementingTaskCallback());
- pool_->PostTask(FROM_HERE, CreateNewBlockingIncrementingTaskCallback());
-
- EXPECT_EQ(0, counter_) << "Blocking tasks should not have started yet.";
-
- WaitForTasksToStart(2);
- start_.Signal();
- WaitForIdleThreads(2);
-
- EXPECT_EQ(2U, unique_threads_.size());
- EXPECT_EQ(2, peer_.num_idle_threads()) << "Existing threads are now idle.";
- EXPECT_EQ(2, counter_);
-}
-
-TEST_F(PosixDynamicThreadPoolTest, Complex) {
- // Add two non blocking tasks and wait for them to finish.
- pool_->PostTask(FROM_HERE, CreateNewIncrementingTaskCallback());
-
- WaitForIdleThreads(1);
-
- // Add two blocking tasks, start them simultaneously, and wait for them to
- // finish.
- pool_->PostTask(FROM_HERE, CreateNewBlockingIncrementingTaskCallback());
- pool_->PostTask(FROM_HERE, CreateNewBlockingIncrementingTaskCallback());
-
- WaitForTasksToStart(2);
- start_.Signal();
- WaitForIdleThreads(2);
-
- EXPECT_EQ(3, counter_);
- EXPECT_EQ(2, peer_.num_idle_threads());
- EXPECT_EQ(2U, unique_threads_.size());
-
- // Wake up all idle threads so they can exit.
- {
- base::AutoLock locked(*peer_.lock());
- while (peer_.num_idle_threads() > 0) {
- peer_.pending_tasks_available_cv()->Signal();
- peer_.num_idle_threads_cv()->Wait();
- }
- }
-
- // Add another non blocking task. There are no threads to reuse.
- pool_->PostTask(FROM_HERE, CreateNewIncrementingTaskCallback());
- WaitForIdleThreads(1);
-
- // The POSIX implementation of PlatformThread::CurrentId() uses pthread_self()
- // which is not guaranteed to be unique after a thread joins. The OS X
- // implemntation of pthread_self() returns the address of the pthread_t, which
- // is merely a malloc()ed pointer stored in the first TLS slot. When a thread
- // joins and that structure is freed, the block of memory can be put on the
- // OS free list, meaning the same address could be reused in a subsequent
- // allocation. This in fact happens when allocating in a loop as this test
- // does.
- //
- // Because there are two concurrent threads, there's at least the guarantee
- // of having two unique thread IDs in the set. But after those two threads are
- // joined, the next-created thread can get a re-used ID if the allocation of
- // the pthread_t structure is taken from the free list. Therefore, there can
- // be either 2 or 3 unique thread IDs in the set at this stage in the test.
- EXPECT_TRUE(unique_threads_.size() >= 2 && unique_threads_.size() <= 3)
- << "unique_threads_.size() = " << unique_threads_.size();
- EXPECT_EQ(1, peer_.num_idle_threads());
- EXPECT_EQ(4, counter_);
-}
-
-} // namespace base
diff --git a/base/threading/worker_pool_unittest.cc b/base/threading/worker_pool_unittest.cc
deleted file mode 100644
index ef4bed136e..0000000000
--- a/base/threading/worker_pool_unittest.cc
+++ /dev/null
@@ -1,118 +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/threading/worker_pool.h"
-
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/location.h"
-#include "base/message_loop/message_loop.h"
-#include "base/run_loop.h"
-#include "base/synchronization/waitable_event.h"
-#include "base/test/test_timeouts.h"
-#include "base/threading/thread_checker_impl.h"
-#include "base/time/time.h"
-#include "build/build_config.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "testing/platform_test.h"
-
-typedef PlatformTest WorkerPoolTest;
-
-namespace base {
-
-namespace {
-
-class PostTaskAndReplyTester
- : public base::RefCountedThreadSafe<PostTaskAndReplyTester> {
- public:
- PostTaskAndReplyTester()
- : finished_(false),
- test_event_(WaitableEvent::ResetPolicy::AUTOMATIC,
- WaitableEvent::InitialState::NOT_SIGNALED) {}
-
- void RunTest() {
- ASSERT_TRUE(thread_checker_.CalledOnValidThread());
- WorkerPool::PostTaskAndReply(
- FROM_HERE,
- base::Bind(&PostTaskAndReplyTester::OnWorkerThread, this),
- base::Bind(&PostTaskAndReplyTester::OnOriginalThread, this),
- false);
-
- test_event_.Wait();
- }
-
- void OnWorkerThread() {
- // We're not on the original thread.
- EXPECT_FALSE(thread_checker_.CalledOnValidThread());
-
- test_event_.Signal();
- }
-
- void OnOriginalThread() {
- EXPECT_TRUE(thread_checker_.CalledOnValidThread());
- finished_ = true;
- }
-
- bool finished() const {
- return finished_;
- }
-
- private:
- friend class base::RefCountedThreadSafe<PostTaskAndReplyTester>;
- ~PostTaskAndReplyTester() {}
-
- bool finished_;
- WaitableEvent test_event_;
-
- // The Impl version performs its checks even in release builds.
- ThreadCheckerImpl thread_checker_;
-};
-
-} // namespace
-
-TEST_F(WorkerPoolTest, PostTask) {
- WaitableEvent test_event(WaitableEvent::ResetPolicy::AUTOMATIC,
- WaitableEvent::InitialState::NOT_SIGNALED);
- WaitableEvent long_test_event(WaitableEvent::ResetPolicy::AUTOMATIC,
- WaitableEvent::InitialState::NOT_SIGNALED);
-
- WorkerPool::PostTask(FROM_HERE,
- base::Bind(&WaitableEvent::Signal,
- base::Unretained(&test_event)),
- false);
- WorkerPool::PostTask(FROM_HERE,
- base::Bind(&WaitableEvent::Signal,
- base::Unretained(&long_test_event)),
- true);
-
- test_event.Wait();
- long_test_event.Wait();
-}
-
-#if defined(OS_WIN) || defined(OS_LINUX)
-// Flaky on Windows and Linux (http://crbug.com/130337)
-#define MAYBE_PostTaskAndReply DISABLED_PostTaskAndReply
-#else
-#define MAYBE_PostTaskAndReply PostTaskAndReply
-#endif
-
-TEST_F(WorkerPoolTest, MAYBE_PostTaskAndReply) {
- MessageLoop message_loop;
- scoped_refptr<PostTaskAndReplyTester> tester(new PostTaskAndReplyTester());
- tester->RunTest();
-
- const TimeDelta kMaxDuration = TestTimeouts::tiny_timeout();
- TimeTicks start = TimeTicks::Now();
- while (!tester->finished() && TimeTicks::Now() - start < kMaxDuration) {
-#if defined(OS_IOS)
- // Ensure that the other thread has a chance to run even on a single-core
- // device.
- pthread_yield_np();
-#endif
- RunLoop().RunUntilIdle();
- }
- EXPECT_TRUE(tester->finished());
-}
-
-} // namespace base
diff --git a/base/time/clock.cc b/base/time/clock.cc
index 34dc37e38b..9e3f27122e 100644
--- a/base/time/clock.cc
+++ b/base/time/clock.cc
@@ -6,6 +6,6 @@
namespace base {
-Clock::~Clock() {}
+Clock::~Clock() = default;
} // namespace base
diff --git a/base/time/clock.h b/base/time/clock.h
index 507a850ff0..166cb2eaa9 100644
--- a/base/time/clock.h
+++ b/base/time/clock.h
@@ -32,7 +32,7 @@ class BASE_EXPORT Clock {
// Now() must be safe to call from any thread. The caller cannot
// make any ordering assumptions about the returned Time. For
// example, the system clock may change to an earlier time.
- virtual Time Now() = 0;
+ virtual Time Now() const = 0;
};
} // namespace base
diff --git a/base/time/default_clock.cc b/base/time/default_clock.cc
index 5f70114bb8..aa08f52bfe 100644
--- a/base/time/default_clock.cc
+++ b/base/time/default_clock.cc
@@ -4,12 +4,20 @@
#include "base/time/default_clock.h"
+#include "base/lazy_instance.h"
+
namespace base {
-DefaultClock::~DefaultClock() {}
+DefaultClock::~DefaultClock() = default;
-Time DefaultClock::Now() {
+Time DefaultClock::Now() const {
return Time::Now();
}
+// static
+DefaultClock* DefaultClock::GetInstance() {
+ static LazyInstance<DefaultClock>::Leaky instance = LAZY_INSTANCE_INITIALIZER;
+ return instance.Pointer();
+}
+
} // namespace base
diff --git a/base/time/default_clock.h b/base/time/default_clock.h
index 0b8250e539..a0e175bcfa 100644
--- a/base/time/default_clock.h
+++ b/base/time/default_clock.h
@@ -17,7 +17,10 @@ class BASE_EXPORT DefaultClock : public Clock {
~DefaultClock() override;
// Simply returns Time::Now().
- Time Now() override;
+ Time Now() const override;
+
+ // Returns a shared instance of DefaultClock. This is thread-safe.
+ static DefaultClock* GetInstance();
};
} // namespace base
diff --git a/base/time/default_tick_clock.cc b/base/time/default_tick_clock.cc
index ce62fcc3d1..188c3cf921 100644
--- a/base/time/default_tick_clock.cc
+++ b/base/time/default_tick_clock.cc
@@ -4,12 +4,20 @@
#include "base/time/default_tick_clock.h"
+#include "base/no_destructor.h"
+
namespace base {
-DefaultTickClock::~DefaultTickClock() {}
+DefaultTickClock::~DefaultTickClock() = default;
-TimeTicks DefaultTickClock::NowTicks() {
+TimeTicks DefaultTickClock::NowTicks() const {
return TimeTicks::Now();
}
+// static
+const DefaultTickClock* DefaultTickClock::GetInstance() {
+ static const base::NoDestructor<DefaultTickClock> default_tick_clock;
+ return default_tick_clock.get();
+}
+
} // namespace base
diff --git a/base/time/default_tick_clock.h b/base/time/default_tick_clock.h
index cb041e6124..78f8a99733 100644
--- a/base/time/default_tick_clock.h
+++ b/base/time/default_tick_clock.h
@@ -6,7 +6,6 @@
#define BASE_TIME_DEFAULT_TICK_CLOCK_H_
#include "base/base_export.h"
-#include "base/compiler_specific.h"
#include "base/time/tick_clock.h"
namespace base {
@@ -17,7 +16,10 @@ class BASE_EXPORT DefaultTickClock : public TickClock {
~DefaultTickClock() override;
// Simply returns TimeTicks::Now().
- TimeTicks NowTicks() override;
+ TimeTicks NowTicks() const override;
+
+ // Returns a shared instance of DefaultTickClock. This is thread-safe.
+ static const DefaultTickClock* GetInstance();
};
} // namespace base
diff --git a/base/time/pr_time_unittest.cc b/base/time/pr_time_unittest.cc
index 3f1a348ae8..6fce4ab1a8 100644
--- a/base/time/pr_time_unittest.cc
+++ b/base/time/pr_time_unittest.cc
@@ -75,13 +75,12 @@ TEST_F(PRTimeTest, ParseTimeTest1) {
time_t current_time = 0;
time(&current_time);
- const int BUFFER_SIZE = 64;
- struct tm local_time = {0};
- char time_buf[BUFFER_SIZE] = {0};
+ struct tm local_time = {};
+ char time_buf[64] = {};
#if defined(OS_WIN)
localtime_s(&local_time, &current_time);
asctime_s(time_buf, arraysize(time_buf), &local_time);
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
localtime_r(&current_time, &local_time);
asctime_r(&local_time, time_buf);
#endif
diff --git a/base/time/tick_clock.cc b/base/time/tick_clock.cc
index 495805c26a..79e396de61 100644
--- a/base/time/tick_clock.cc
+++ b/base/time/tick_clock.cc
@@ -6,6 +6,6 @@
namespace base {
-TickClock::~TickClock() {}
+TickClock::~TickClock() = default;
} // namespace base
diff --git a/base/time/tick_clock.h b/base/time/tick_clock.h
index f7aba53743..dc57354a25 100644
--- a/base/time/tick_clock.h
+++ b/base/time/tick_clock.h
@@ -32,7 +32,7 @@ class BASE_EXPORT TickClock {
// assume that NowTicks() is monotonic (but not strictly monotonic).
// In other words, the returned TimeTicks will never decrease with
// time, although they might "stand still".
- virtual TimeTicks NowTicks() = 0;
+ virtual TimeTicks NowTicks() const = 0;
};
} // namespace base
diff --git a/base/time/time.cc b/base/time/time.cc
index d1c6a4783c..8529bb0f48 100644
--- a/base/time/time.cc
+++ b/base/time/time.cc
@@ -10,15 +10,31 @@
#include <ostream>
#include <sstream>
-#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/macros.h"
+#include "base/no_destructor.h"
#include "base/strings/stringprintf.h"
#include "base/third_party/nspr/prtime.h"
+#include "base/time/time_override.h"
#include "build/build_config.h"
namespace base {
+namespace internal {
+
+TimeNowFunction g_time_now_function = &subtle::TimeNowIgnoringOverride;
+
+TimeNowFunction g_time_now_from_system_time_function =
+ &subtle::TimeNowFromSystemTimeIgnoringOverride;
+
+TimeTicksNowFunction g_time_ticks_now_function =
+ &subtle::TimeTicksNowIgnoringOverride;
+
+ThreadTicksNowFunction g_thread_ticks_now_function =
+ &subtle::ThreadTicksNowIgnoringOverride;
+
+} // namespace internal
+
// TimeDelta ------------------------------------------------------------------
int TimeDelta::InDays() const {
@@ -29,6 +45,19 @@ int TimeDelta::InDays() const {
return static_cast<int>(delta_ / Time::kMicrosecondsPerDay);
}
+int TimeDelta::InDaysFloored() const {
+ if (is_max()) {
+ // Preserve max to prevent overflow.
+ return std::numeric_limits<int>::max();
+ }
+ int result = delta_ / Time::kMicrosecondsPerDay;
+ int64_t remainder = delta_ - (result * Time::kMicrosecondsPerDay);
+ if (remainder < 0) {
+ --result; // Use floor(), not trunc() rounding behavior.
+ }
+ return result;
+}
+
int TimeDelta::InHours() const {
if (is_max()) {
// Preserve max to prevent overflow.
@@ -82,8 +111,12 @@ int64_t TimeDelta::InMillisecondsRoundedUp() const {
// Preserve max to prevent overflow.
return std::numeric_limits<int64_t>::max();
}
- return (delta_ + Time::kMicrosecondsPerMillisecond - 1) /
- Time::kMicrosecondsPerMillisecond;
+ int64_t result = delta_ / Time::kMicrosecondsPerMillisecond;
+ int64_t remainder = delta_ - (result * Time::kMicrosecondsPerMillisecond);
+ if (remainder > 0) {
+ ++result; // Use ceil(), not trunc() rounding behavior.
+ }
+ return result;
}
int64_t TimeDelta::InMicroseconds() const {
@@ -94,6 +127,22 @@ int64_t TimeDelta::InMicroseconds() const {
return delta_;
}
+double TimeDelta::InMicrosecondsF() const {
+ if (is_max()) {
+ // Preserve max to prevent overflow.
+ return std::numeric_limits<double>::infinity();
+ }
+ return static_cast<double>(delta_);
+}
+
+int64_t TimeDelta::InNanoseconds() const {
+ if (is_max()) {
+ // Preserve max to prevent overflow.
+ return std::numeric_limits<int64_t>::max();
+ }
+ return delta_ * Time::kNanosecondsPerMicrosecond;
+}
+
namespace time_internal {
int64_t SaturatedAdd(TimeDelta delta, int64_t value) {
@@ -103,7 +152,7 @@ int64_t SaturatedAdd(TimeDelta delta, int64_t value) {
return rv.ValueOrDie();
// Positive RHS overflows. Negative RHS underflows.
if (value < 0)
- return -std::numeric_limits<int64_t>::max();
+ return std::numeric_limits<int64_t>::min();
return std::numeric_limits<int64_t>::max();
}
@@ -115,7 +164,7 @@ int64_t SaturatedSub(TimeDelta delta, int64_t value) {
// Negative RHS overflows. Positive RHS underflows.
if (value < 0)
return std::numeric_limits<int64_t>::max();
- return -std::numeric_limits<int64_t>::max();
+ return std::numeric_limits<int64_t>::min();
}
} // namespace time_internal
@@ -127,6 +176,26 @@ std::ostream& operator<<(std::ostream& os, TimeDelta time_delta) {
// Time -----------------------------------------------------------------------
// static
+Time Time::Now() {
+ return internal::g_time_now_function();
+}
+
+// static
+Time Time::NowFromSystemTime() {
+ // Just use g_time_now_function because it returns the system time.
+ return internal::g_time_now_from_system_time_function();
+}
+
+// static
+Time Time::FromDeltaSinceWindowsEpoch(TimeDelta delta) {
+ return Time(delta.InMicroseconds());
+}
+
+TimeDelta Time::ToDeltaSinceWindowsEpoch() const {
+ return TimeDelta::FromMicroseconds(us_);
+}
+
+// static
Time Time::FromTimeT(time_t tt) {
if (tt == 0)
return Time(); // Preserve 0 so we can tell it doesn't exist.
@@ -242,7 +311,7 @@ Time Time::LocalMidnight() const {
bool Time::FromStringInternal(const char* time_string,
bool is_local,
Time* parsed_time) {
- DCHECK((time_string != NULL) && (parsed_time != NULL));
+ DCHECK((time_string != nullptr) && (parsed_time != nullptr));
if (time_string[0] == '\0')
return false;
@@ -281,27 +350,20 @@ std::ostream& operator<<(std::ostream& os, Time time) {
exploded.millisecond);
}
-// Local helper class to hold the conversion from Time to TickTime at the
-// time of the Unix epoch.
-class UnixEpochSingleton {
- public:
- UnixEpochSingleton()
- : unix_epoch_(TimeTicks::Now() - (Time::Now() - Time::UnixEpoch())) {}
-
- TimeTicks unix_epoch() const { return unix_epoch_; }
-
- private:
- const TimeTicks unix_epoch_;
+// TimeTicks ------------------------------------------------------------------
- DISALLOW_COPY_AND_ASSIGN(UnixEpochSingleton);
-};
-
-static LazyInstance<UnixEpochSingleton>::Leaky
- leaky_unix_epoch_singleton_instance = LAZY_INSTANCE_INITIALIZER;
+// static
+TimeTicks TimeTicks::Now() {
+ return internal::g_time_ticks_now_function();
+}
-// Static
+// static
TimeTicks TimeTicks::UnixEpoch() {
- return leaky_unix_epoch_singleton_instance.Get().unix_epoch();
+ static const base::NoDestructor<base::TimeTicks> epoch([]() {
+ return subtle::TimeTicksNowIgnoringOverride() -
+ (subtle::TimeNowIgnoringOverride() - Time::UnixEpoch());
+ }());
+ return *epoch;
}
TimeTicks TimeTicks::SnappedToNextTick(TimeTicks tick_phase,
@@ -327,6 +389,13 @@ std::ostream& operator<<(std::ostream& os, TimeTicks time_ticks) {
return os << as_time_delta.InMicroseconds() << " bogo-microseconds";
}
+// ThreadTicks ----------------------------------------------------------------
+
+// static
+ThreadTicks ThreadTicks::Now() {
+ return internal::g_thread_ticks_now_function();
+}
+
std::ostream& operator<<(std::ostream& os, ThreadTicks thread_ticks) {
const TimeDelta as_time_delta = thread_ticks - ThreadTicks();
return os << as_time_delta.InMicroseconds() << " bogo-thread-microseconds";
diff --git a/base/time/time.h b/base/time/time.h
index 46aa9454d9..b3c77e7289 100644
--- a/base/time/time.h
+++ b/base/time/time.h
@@ -15,7 +15,7 @@
//
// TimeTicks and ThreadTicks represent an abstract time that is most of the time
// incrementing, for use in measuring time durations. Internally, they are
-// represented in microseconds. They can not be converted to a human-readable
+// represented in microseconds. They cannot be converted to a human-readable
// time, but are guaranteed not to decrease (unlike the Time class). Note that
// TimeTicks may "stand still" (e.g., if the computer is suspended), and
// ThreadTicks will "stand still" whenever the thread has been de-scheduled by
@@ -33,11 +33,11 @@
//
// So many choices! Which time class should you use? Examples:
//
-// Time: Interpreting the wall-clock time provided by a remote
-// system. Detecting whether cached resources have
-// expired. Providing the user with a display of the current date
-// and time. Determining the amount of time between events across
-// re-boots of the machine.
+// Time: Interpreting the wall-clock time provided by a remote system.
+// Detecting whether cached resources have expired. Providing the
+// user with a display of the current date and time. Determining
+// the amount of time between events across re-boots of the
+// machine.
//
// TimeTicks: Tracking the amount of time a task runs. Executing delayed
// tasks at the right time. Computing presentation timestamps.
@@ -63,22 +63,28 @@
#include "base/numerics/safe_math.h"
#include "build/build_config.h"
+#if defined(OS_FUCHSIA)
+#include <zircon/types.h>
+#endif
+
#if defined(OS_MACOSX)
#include <CoreFoundation/CoreFoundation.h>
// Avoid Mac system header macro leak.
#undef TYPE_BOOL
#endif
-#if defined(OS_POSIX)
+#if defined(OS_ANDROID)
+#include <jni.h>
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
#include <unistd.h>
#include <sys/time.h>
#endif
#if defined(OS_WIN)
-// For FILETIME in FromFileTime, until it moves to a new converter class.
-// See TODO(iyengar) below.
-#include <windows.h>
#include "base/gtest_prod_util.h"
+#include "base/win/windows_types.h"
#endif
namespace base {
@@ -102,8 +108,7 @@ BASE_EXPORT int64_t SaturatedSub(TimeDelta delta, int64_t value);
class BASE_EXPORT TimeDelta {
public:
- TimeDelta() : delta_(0) {
- }
+ constexpr TimeDelta() : delta_(0) {}
// Converts units of time to TimeDeltas.
static constexpr TimeDelta FromDays(int days);
@@ -111,35 +116,49 @@ class BASE_EXPORT TimeDelta {
static constexpr TimeDelta FromMinutes(int minutes);
static constexpr TimeDelta FromSeconds(int64_t secs);
static constexpr TimeDelta FromMilliseconds(int64_t ms);
+ static constexpr TimeDelta FromMicroseconds(int64_t us);
+ static constexpr TimeDelta FromNanoseconds(int64_t ns);
static constexpr TimeDelta FromSecondsD(double secs);
static constexpr TimeDelta FromMillisecondsD(double ms);
- static constexpr TimeDelta FromMicroseconds(int64_t us);
-#if defined(OS_POSIX)
- static TimeDelta FromTimeSpec(const timespec& ts);
-#endif
+ static constexpr TimeDelta FromMicrosecondsD(double us);
+ static constexpr TimeDelta FromNanosecondsD(double ns);
#if defined(OS_WIN)
static TimeDelta FromQPCValue(LONGLONG qpc_value);
+ static TimeDelta FromFileTime(FILETIME ft);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ static TimeDelta FromTimeSpec(const timespec& ts);
#endif
// Converts an integer value representing TimeDelta to a class. This is used
// when deserializing a |TimeDelta| structure, using a value known to be
// compatible. It is not provided as a constructor because the integer type
// may be unclear from the perspective of a caller.
- static TimeDelta FromInternalValue(int64_t delta) { return TimeDelta(delta); }
+ //
+ // DEPRECATED - Do not use in new code. http://crbug.com/634507
+ static constexpr TimeDelta FromInternalValue(int64_t delta) {
+ return TimeDelta(delta);
+ }
// Returns the maximum time delta, which should be greater than any reasonable
// time delta we might compare it to. Adding or subtracting the maximum time
// delta to a time or another time delta has an undefined result.
static constexpr TimeDelta Max();
+ // Returns the minimum time delta, which should be less than than any
+ // reasonable time delta we might compare it to. Adding or subtracting the
+ // minimum time delta to a time or another time delta has an undefined result.
+ static constexpr TimeDelta Min();
+
// Returns the internal numeric value of the TimeDelta object. Please don't
// use this and do arithmetic on it, as it is more error prone than using the
// provided operators.
// For serializing, use FromInternalValue to reconstitute.
- int64_t ToInternalValue() const { return delta_; }
+ //
+ // DEPRECATED - Do not use in new code. http://crbug.com/634507
+ constexpr int64_t ToInternalValue() const { return delta_; }
// Returns the magnitude (absolute value) of this TimeDelta.
- TimeDelta magnitude() const {
+ constexpr TimeDelta magnitude() const {
// Some toolchains provide an incomplete C++11 implementation and lack an
// int64_t overload for std::abs(). The following is a simple branchless
// implementation:
@@ -148,23 +167,27 @@ class BASE_EXPORT TimeDelta {
}
// Returns true if the time delta is zero.
- bool is_zero() const {
- return delta_ == 0;
- }
+ constexpr bool is_zero() const { return delta_ == 0; }
- // Returns true if the time delta is the maximum time delta.
- bool is_max() const { return delta_ == std::numeric_limits<int64_t>::max(); }
+ // Returns true if the time delta is the maximum/minimum time delta.
+ constexpr bool is_max() const {
+ return delta_ == std::numeric_limits<int64_t>::max();
+ }
+ constexpr bool is_min() const {
+ return delta_ == std::numeric_limits<int64_t>::min();
+ }
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
struct timespec ToTimeSpec() const;
#endif
- // Returns the time delta in some unit. The F versions return a floating
- // point value, the "regular" versions return a rounded-down value.
- //
- // InMillisecondsRoundedUp() instead returns an integer that is rounded up
- // to the next full millisecond.
+ // Returns the time delta in some unit. The InXYZF versions return a floating
+ // point value. The InXYZ versions return a truncated value (aka rounded
+ // towards zero, std::trunc() behavior). The InXYZFloored() versions round to
+ // lesser integers (std::floor() behavior). The XYZRoundedUp() versions round
+ // up to greater integers (std::ceil() behavior).
int InDays() const;
+ int InDaysFloored() const;
int InHours() const;
int InMinutes() const;
double InSecondsF() const;
@@ -173,13 +196,18 @@ class BASE_EXPORT TimeDelta {
int64_t InMilliseconds() const;
int64_t InMillisecondsRoundedUp() const;
int64_t InMicroseconds() const;
+ double InMicrosecondsF() const;
+ int64_t InNanoseconds() const;
- TimeDelta& operator=(TimeDelta other) {
+ constexpr TimeDelta& operator=(TimeDelta other) {
delta_ = other.delta_;
return *this;
}
- // Computations with other deltas.
+ // Computations with other deltas. Can easily be made constexpr with C++17 but
+ // hard to do until then per limitations around
+ // __builtin_(add|sub)_overflow in safe_math_clang_gcc_impl.h :
+ // https://chromium-review.googlesource.com/c/chromium/src/+/873352#message-59594ab70827795a67e0780404adf37b4b6c2f14
TimeDelta operator+(TimeDelta other) const {
return TimeDelta(time_internal::SaturatedAdd(*this, other.delta_));
}
@@ -193,12 +221,12 @@ class BASE_EXPORT TimeDelta {
TimeDelta& operator-=(TimeDelta other) {
return *this = (*this - other);
}
- TimeDelta operator-() const {
- return TimeDelta(-delta_);
- }
+ constexpr TimeDelta operator-() const { return TimeDelta(-delta_); }
- // Computations with numeric types.
- template<typename T>
+ // Computations with numeric types. operator*() isn't constexpr because of a
+ // limitation around __builtin_mul_overflow (but operator/(1.0/a) works for
+ // |a|'s of "reasonable" size -- i.e. that don't risk overflow).
+ template <typename T>
TimeDelta operator*(T a) const {
CheckedNumeric<int64_t> rv(delta_);
rv *= a;
@@ -206,11 +234,11 @@ class BASE_EXPORT TimeDelta {
return TimeDelta(rv.ValueOrDie());
// Matched sign overflows. Mismatched sign underflows.
if ((delta_ < 0) ^ (a < 0))
- return TimeDelta(-std::numeric_limits<int64_t>::max());
+ return TimeDelta(std::numeric_limits<int64_t>::min());
return TimeDelta(std::numeric_limits<int64_t>::max());
}
- template<typename T>
- TimeDelta operator/(T a) const {
+ template <typename T>
+ constexpr TimeDelta operator/(T a) const {
CheckedNumeric<int64_t> rv(delta_);
rv /= a;
if (rv.IsValid())
@@ -218,20 +246,20 @@ class BASE_EXPORT TimeDelta {
// Matched sign overflows. Mismatched sign underflows.
// Special case to catch divide by zero.
if ((delta_ < 0) ^ (a <= 0))
- return TimeDelta(-std::numeric_limits<int64_t>::max());
+ return TimeDelta(std::numeric_limits<int64_t>::min());
return TimeDelta(std::numeric_limits<int64_t>::max());
}
- template<typename T>
+ template <typename T>
TimeDelta& operator*=(T a) {
return *this = (*this * a);
}
- template<typename T>
- TimeDelta& operator/=(T a) {
+ template <typename T>
+ constexpr TimeDelta& operator/=(T a) {
return *this = (*this / a);
}
- int64_t operator/(TimeDelta a) const { return delta_ / a.delta_; }
- TimeDelta operator%(TimeDelta a) const {
+ constexpr int64_t operator/(TimeDelta a) const { return delta_ / a.delta_; }
+ constexpr TimeDelta operator%(TimeDelta a) const {
return TimeDelta(delta_ % a.delta_);
}
@@ -280,8 +308,8 @@ class BASE_EXPORT TimeDelta {
int64_t delta_;
};
-template<typename T>
-inline TimeDelta operator*(T a, TimeDelta td) {
+template <typename T>
+TimeDelta operator*(T a, TimeDelta td) {
return td * a;
}
@@ -327,18 +355,26 @@ class TimeBase {
return us_ == 0;
}
- // Returns true if this object represents the maximum time.
+ // Returns true if this object represents the maximum/minimum time.
bool is_max() const { return us_ == std::numeric_limits<int64_t>::max(); }
+ bool is_min() const { return us_ == std::numeric_limits<int64_t>::min(); }
- // Returns the maximum time, which should be greater than any reasonable time
- // with which we might compare it.
+ // Returns the maximum/minimum times, which should be greater/less than than
+ // any reasonable time with which we might compare it.
static TimeClass Max() {
return TimeClass(std::numeric_limits<int64_t>::max());
}
+ static TimeClass Min() {
+ return TimeClass(std::numeric_limits<int64_t>::min());
+ }
+
// For serializing only. Use FromInternalValue() to reconstitute. Please don't
// use this and do arithmetic on it, as it is more error prone than using the
// provided operators.
+ //
+ // DEPRECATED - Do not use in new code. For serializing Time values, prefer
+ // Time::ToDeltaSinceWindowsEpoch().InMicroseconds(). http://crbug.com/634507
int64_t ToInternalValue() const { return us_; }
// The amount of time since the origin (or "zero") point. This is a syntactic
@@ -395,14 +431,8 @@ class TimeBase {
return us_ >= other.us_;
}
- // Converts an integer value representing TimeClass to a class. This is used
- // when deserializing a |TimeClass| structure, using a value known to be
- // compatible. It is not provided as a constructor because the integer type
- // may be unclear from the perspective of a caller.
- static TimeClass FromInternalValue(int64_t us) { return TimeClass(us); }
-
protected:
- explicit TimeBase(int64_t us) : us_(us) {}
+ constexpr explicit TimeBase(int64_t us) : us_(us) {}
// Time value in a microsecond timebase.
int64_t us_;
@@ -421,22 +451,46 @@ inline TimeClass operator+(TimeDelta delta, TimeClass t) {
// monotonically non-decreasing and are subject to large amounts of skew.
class BASE_EXPORT Time : public time_internal::TimeBase<Time> {
public:
- // The representation of Jan 1, 1970 UTC in microseconds since the
- // platform-dependent epoch.
- static const int64_t kTimeTToMicrosecondsOffset;
-
-#if !defined(OS_WIN)
- // On Mac & Linux, this value is the delta from the Windows epoch of 1601 to
- // the Posix delta of 1970. This is used for migrating between the old
- // 1970-based epochs to the new 1601-based ones. It should be removed from
- // this global header and put in the platform-specific ones when we remove the
- // migration code.
- static const int64_t kWindowsEpochDeltaMicroseconds;
-#else
+ // Offset of UNIX epoch (1970-01-01 00:00:00 UTC) from Windows FILETIME epoch
+ // (1601-01-01 00:00:00 UTC), in microseconds. This value is derived from the
+ // following: ((1970-1601)*365+89)*24*60*60*1000*1000, where 89 is the number
+ // of leap year days between 1601 and 1970: (1970-1601)/4 excluding 1700,
+ // 1800, and 1900.
+ static constexpr int64_t kTimeTToMicrosecondsOffset =
+ INT64_C(11644473600000000);
+
+#if defined(OS_WIN)
// To avoid overflow in QPC to Microseconds calculations, since we multiply
// by kMicrosecondsPerSecond, then the QPC value should not exceed
// (2^63 - 1) / 1E6. If it exceeds that threshold, we divide then multiply.
- enum : int64_t{kQPCOverflowThreshold = 0x8637BD05AF7};
+ static constexpr int64_t kQPCOverflowThreshold = INT64_C(0x8637BD05AF7);
+#endif
+
+// kExplodedMinYear and kExplodedMaxYear define the platform-specific limits
+// for values passed to FromUTCExploded() and FromLocalExploded(). Those
+// functions will return false if passed values outside these limits. The limits
+// are inclusive, meaning that the API should support all dates within a given
+// limit year.
+#if defined(OS_WIN)
+ static constexpr int kExplodedMinYear = 1601;
+ static constexpr int kExplodedMaxYear = 30827;
+#elif defined(OS_IOS)
+ static constexpr int kExplodedMinYear = std::numeric_limits<int>::min();
+ static constexpr int kExplodedMaxYear = std::numeric_limits<int>::max();
+#elif defined(OS_MACOSX)
+ static constexpr int kExplodedMinYear = 1902;
+ static constexpr int kExplodedMaxYear = std::numeric_limits<int>::max();
+#elif defined(OS_ANDROID)
+ // Though we use 64-bit time APIs on both 32 and 64 bit Android, some OS
+ // versions like KitKat (ARM but not x86 emulator) can't handle some early
+ // dates (e.g. before 1170). So we set min conservatively here.
+ static constexpr int kExplodedMinYear = 1902;
+ static constexpr int kExplodedMaxYear = std::numeric_limits<int>::max();
+#else
+ static constexpr int kExplodedMinYear =
+ (sizeof(time_t) == 4 ? 1902 : std::numeric_limits<int>::min());
+ static constexpr int kExplodedMaxYear =
+ (sizeof(time_t) == 4 ? 2037 : std::numeric_limits<int>::max());
#endif
// Represents an exploded time that can be formatted nicely. This is kind of
@@ -460,8 +514,7 @@ class BASE_EXPORT Time : public time_internal::TimeBase<Time> {
};
// Contains the NULL time. Use Time::Now() to get the current time.
- Time() : TimeBase(0) {
- }
+ constexpr Time() : TimeBase(0) {}
// Returns the time for epoch in Unix-like system (Jan 1, 1970).
static Time UnixEpoch();
@@ -477,6 +530,20 @@ class BASE_EXPORT Time : public time_internal::TimeBase<Time> {
// For timing sensitive unittests, this function should be used.
static Time NowFromSystemTime();
+ // Converts to/from TimeDeltas relative to the Windows epoch (1601-01-01
+ // 00:00:00 UTC). Prefer these methods for opaque serialization and
+ // deserialization of time values, e.g.
+ //
+ // // Serialization:
+ // base::Time last_updated = ...;
+ // SaveToDatabase(last_updated.ToDeltaSinceWindowsEpoch().InMicroseconds());
+ //
+ // // Deserialization:
+ // base::Time last_updated = base::Time::FromDeltaSinceWindowsEpoch(
+ // base::TimeDelta::FromMicroseconds(LoadFromDatabase()));
+ static Time FromDeltaSinceWindowsEpoch(TimeDelta delta);
+ TimeDelta ToDeltaSinceWindowsEpoch() const;
+
// Converts to/from time_t in UTC and a Time class.
static Time FromTimeT(time_t tt);
time_t ToTimeT() const;
@@ -489,7 +556,7 @@ class BASE_EXPORT Time : public time_internal::TimeBase<Time> {
static Time FromDoubleT(double dt);
double ToDoubleT() const;
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Converts the timespec structure to time. MacOS X 10.8.3 (and tentatively,
// earlier versions) will have the |ts|'s tv_nsec component zeroed out,
// having a 1 second resolution, which agrees with
@@ -503,12 +570,13 @@ class BASE_EXPORT Time : public time_internal::TimeBase<Time> {
static Time FromJsTime(double ms_since_epoch);
double ToJsTime() const;
- // Converts to/from Java convention for times, a number of
- // milliseconds since the epoch.
+ // Converts to/from Java convention for times, a number of milliseconds since
+ // the epoch. Because the Java format has less resolution, converting to Java
+ // time is a lossy operation.
static Time FromJavaTime(int64_t ms_since_epoch);
int64_t ToJavaTime() const;
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
static Time FromTimeVal(struct timeval t);
struct timeval ToTimeVal() const;
#endif
@@ -543,7 +611,19 @@ class BASE_EXPORT Time : public time_internal::TimeBase<Time> {
// This is provided for testing only, and is not tracked in a thread-safe
// way.
static bool IsHighResolutionTimerInUse();
-#endif
+
+ // The following two functions are used to report the fraction of elapsed time
+ // that the high resolution timer is activated.
+ // ResetHighResolutionTimerUsage() resets the cumulative usage and starts the
+ // measurement interval and GetHighResolutionTimerUsage() returns the
+ // percentage of time since the reset that the high resolution timer was
+ // activated.
+ // ResetHighResolutionTimerUsage() must be called at least once before calling
+ // GetHighResolutionTimerUsage(); otherwise the usage result would be
+ // undefined.
+ static void ResetHighResolutionTimerUsage();
+ static double GetHighResolutionTimerUsage();
+#endif // defined(OS_WIN)
// Converts an exploded structure representing either the local time or UTC
// into a Time class. Returns false on a failure when, for example, a day of
@@ -587,10 +667,19 @@ class BASE_EXPORT Time : public time_internal::TimeBase<Time> {
// midnight on that day.
Time LocalMidnight() const;
+ // Converts an integer value representing Time to a class. This may be used
+ // when deserializing a |Time| structure, using a value known to be
+ // compatible. It is not provided as a constructor because the integer type
+ // may be unclear from the perspective of a caller.
+ //
+ // DEPRECATED - Do not use in new code. For deserializing Time values, prefer
+ // Time::FromDeltaSinceWindowsEpoch(). http://crbug.com/634507
+ static constexpr Time FromInternalValue(int64_t us) { return Time(us); }
+
private:
friend class time_internal::TimeBase<Time>;
- explicit Time(int64_t us) : TimeBase(us) {}
+ constexpr explicit Time(int64_t us) : TimeBase(us) {}
// Explodes the given time to either local time |is_local = true| or UTC
// |is_local = false|.
@@ -652,6 +741,16 @@ constexpr TimeDelta TimeDelta::FromMilliseconds(int64_t ms) {
}
// static
+constexpr TimeDelta TimeDelta::FromMicroseconds(int64_t us) {
+ return TimeDelta(us);
+}
+
+// static
+constexpr TimeDelta TimeDelta::FromNanoseconds(int64_t ns) {
+ return TimeDelta(ns / Time::kNanosecondsPerMicrosecond);
+}
+
+// static
constexpr TimeDelta TimeDelta::FromSecondsD(double secs) {
return FromDouble(secs * Time::kMicrosecondsPerSecond);
}
@@ -662,8 +761,13 @@ constexpr TimeDelta TimeDelta::FromMillisecondsD(double ms) {
}
// static
-constexpr TimeDelta TimeDelta::FromMicroseconds(int64_t us) {
- return TimeDelta(us);
+constexpr TimeDelta TimeDelta::FromMicrosecondsD(double us) {
+ return FromDouble(us);
+}
+
+// static
+constexpr TimeDelta TimeDelta::FromNanosecondsD(double ns) {
+ return FromDouble(ns / Time::kNanosecondsPerMicrosecond);
}
// static
@@ -672,31 +776,30 @@ constexpr TimeDelta TimeDelta::Max() {
}
// static
+constexpr TimeDelta TimeDelta::Min() {
+ return TimeDelta(std::numeric_limits<int64_t>::min());
+}
+
+// static
constexpr TimeDelta TimeDelta::FromDouble(double value) {
// TODO(crbug.com/612601): Use saturated_cast<int64_t>(value) once we sort out
// the Min() behavior.
return value > std::numeric_limits<int64_t>::max()
? Max()
- : value < -std::numeric_limits<int64_t>::max()
- ? -Max()
+ : value < std::numeric_limits<int64_t>::min()
+ ? Min()
: TimeDelta(static_cast<int64_t>(value));
}
// static
constexpr TimeDelta TimeDelta::FromProduct(int64_t value,
int64_t positive_value) {
- return (
-#if !defined(_PREFAST_) || !defined(OS_WIN)
- // Avoid internal compiler errors in /analyze builds with VS 2015
- // update 3.
- // https://connect.microsoft.com/VisualStudio/feedback/details/2870865
- DCHECK(positive_value > 0),
-#endif
- value > std::numeric_limits<int64_t>::max() / positive_value
- ? Max()
- : value < -std::numeric_limits<int64_t>::max() / positive_value
- ? -Max()
- : TimeDelta(value * positive_value));
+ DCHECK(positive_value > 0);
+ return value > std::numeric_limits<int64_t>::max() / positive_value
+ ? Max()
+ : value < std::numeric_limits<int64_t>::min() / positive_value
+ ? Min()
+ : TimeDelta(value * positive_value);
}
// For logging use only.
@@ -709,6 +812,7 @@ class BASE_EXPORT TimeTicks : public time_internal::TimeBase<TimeTicks> {
public:
// The underlying clock used to generate new TimeTicks.
enum class Clock {
+ FUCHSIA_ZX_CLOCK_MONOTONIC,
LINUX_CLOCK_MONOTONIC,
IOS_CF_ABSOLUTE_TIME_MINUS_KERN_BOOTTIME,
MAC_MACH_ABSOLUTE_TIME,
@@ -716,8 +820,7 @@ class BASE_EXPORT TimeTicks : public time_internal::TimeBase<TimeTicks> {
WIN_ROLLOVER_PROTECTED_TIME_GET_TIME
};
- TimeTicks() : TimeBase(0) {
- }
+ constexpr TimeTicks() : TimeBase(0) {}
// Platform-dependent tick count representing "right now." When
// IsHighResolution() returns false, the resolution of the clock could be
@@ -738,6 +841,12 @@ class BASE_EXPORT TimeTicks : public time_internal::TimeBase<TimeTicks> {
// considered to have an ambiguous ordering.)
static bool IsConsistentAcrossProcesses() WARN_UNUSED_RESULT;
+#if defined(OS_FUCHSIA)
+ // Converts between TimeTicks and an ZX_CLOCK_MONOTONIC zx_time_t value.
+ static TimeTicks FromZxTime(zx_time_t nanos_since_boot);
+ zx_time_t ToZxTime() const;
+#endif
+
#if defined(OS_WIN)
// Translates an absolute QPC timestamp into a TimeTicks value. The returned
// value has the same origin as Now(). Do NOT attempt to use this if
@@ -749,6 +858,14 @@ class BASE_EXPORT TimeTicks : public time_internal::TimeBase<TimeTicks> {
static TimeTicks FromMachAbsoluteTime(uint64_t mach_absolute_time);
#endif // defined(OS_MACOSX) && !defined(OS_IOS)
+#if defined(OS_ANDROID)
+ // Converts to TimeTicks the value obtained from SystemClock.uptimeMillis().
+ // Note: this convertion may be non-monotonic in relation to previously
+ // obtained TimeTicks::Now() values because of the truncation (to
+ // milliseconds) performed by uptimeMillis().
+ static TimeTicks FromUptimeMillis(jlong uptime_millis_value);
+#endif
+
// Get an estimate of the TimeTick value at the time of the UnixEpoch. Because
// Time and TimeTicks respond differently to user-set time and NTP
// adjustments, this number is only an estimate. Nevertheless, this can be
@@ -770,6 +887,17 @@ class BASE_EXPORT TimeTicks : public time_internal::TimeBase<TimeTicks> {
// logging purposes.
static Clock GetClock();
+ // Converts an integer value representing TimeTicks to a class. This may be
+ // used when deserializing a |TimeTicks| structure, using a value known to be
+ // compatible. It is not provided as a constructor because the integer type
+ // may be unclear from the perspective of a caller.
+ //
+ // DEPRECATED - Do not use in new code. For deserializing TimeTicks values,
+ // prefer TimeTicks + TimeDelta(). http://crbug.com/634507
+ static constexpr TimeTicks FromInternalValue(int64_t us) {
+ return TimeTicks(us);
+ }
+
#if defined(OS_WIN)
protected:
typedef DWORD (*TickFunctionType)(void);
@@ -781,7 +909,7 @@ class BASE_EXPORT TimeTicks : public time_internal::TimeBase<TimeTicks> {
// Please use Now() to create a new object. This is for internal use
// and testing.
- explicit TimeTicks(int64_t us) : TimeBase(us) {}
+ constexpr explicit TimeTicks(int64_t us) : TimeBase(us) {}
};
// For logging use only.
@@ -799,7 +927,8 @@ class BASE_EXPORT ThreadTicks : public time_internal::TimeBase<ThreadTicks> {
// Returns true if ThreadTicks::Now() is supported on this system.
static bool IsSupported() WARN_UNUSED_RESULT {
#if (defined(_POSIX_THREAD_CPUTIME) && (_POSIX_THREAD_CPUTIME >= 0)) || \
- (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_ANDROID)
+ (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_ANDROID) || \
+ defined(OS_FUCHSIA)
return true;
#elif defined(OS_WIN)
return IsSupportedWin();
@@ -832,12 +961,23 @@ class BASE_EXPORT ThreadTicks : public time_internal::TimeBase<ThreadTicks> {
static ThreadTicks GetForThread(const PlatformThreadHandle& thread_handle);
#endif
+ // Converts an integer value representing ThreadTicks to a class. This may be
+ // used when deserializing a |ThreadTicks| structure, using a value known to
+ // be compatible. It is not provided as a constructor because the integer type
+ // may be unclear from the perspective of a caller.
+ //
+ // DEPRECATED - Do not use in new code. For deserializing ThreadTicks values,
+ // prefer ThreadTicks + TimeDelta(). http://crbug.com/634507
+ static constexpr ThreadTicks FromInternalValue(int64_t us) {
+ return ThreadTicks(us);
+ }
+
private:
friend class time_internal::TimeBase<ThreadTicks>;
// Please use Now() or GetForThread() to create a new object. This is for
// internal use and testing.
- explicit ThreadTicks(int64_t us) : TimeBase(us) {}
+ constexpr explicit ThreadTicks(int64_t us) : TimeBase(us) {}
#if defined(OS_WIN)
FRIEND_TEST_ALL_PREFIXES(TimeTicks, TSCTicksPerSecond);
diff --git a/base/time/time_android.cc b/base/time/time_android.cc
new file mode 100644
index 0000000000..e0c4914afd
--- /dev/null
+++ b/base/time/time_android.cc
@@ -0,0 +1,26 @@
+// 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.
+
+#include "base/time/time.h"
+
+namespace base {
+
+// static
+TimeTicks TimeTicks::FromUptimeMillis(jlong uptime_millis_value) {
+ // The implementation of the SystemClock.uptimeMillis() in AOSP uses the same
+ // clock as base::TimeTicks::Now(): clock_gettime(CLOCK_MONOTONIC), see in
+ // platform/system/code:
+ // 1. libutils/SystemClock.cpp
+ // 2. libutils/Timers.cpp
+ //
+ // We are not aware of any motivations for Android OEMs to modify the AOSP
+ // implementation of either uptimeMillis() or clock_gettime(CLOCK_MONOTONIC),
+ // so we assume that there are no such customizations.
+ //
+ // Under these assumptions the conversion is as safe as copying the value of
+ // base::TimeTicks::Now() with a loss of sub-millisecond precision.
+ return TimeTicks(uptime_millis_value * Time::kMicrosecondsPerMillisecond);
+}
+
+} // namespace base
diff --git a/base/time/time_conversion_posix.cc b/base/time/time_conversion_posix.cc
new file mode 100644
index 0000000000..ba0a2b2966
--- /dev/null
+++ b/base/time/time_conversion_posix.cc
@@ -0,0 +1,67 @@
+// 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/time/time.h"
+
+#include <stdint.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include <limits>
+
+#include "base/logging.h"
+
+namespace base {
+
+// static
+TimeDelta TimeDelta::FromTimeSpec(const timespec& ts) {
+ return TimeDelta(ts.tv_sec * Time::kMicrosecondsPerSecond +
+ ts.tv_nsec / Time::kNanosecondsPerMicrosecond);
+}
+
+struct timespec TimeDelta::ToTimeSpec() const {
+ int64_t microseconds = InMicroseconds();
+ time_t seconds = 0;
+ if (microseconds >= Time::kMicrosecondsPerSecond) {
+ seconds = InSeconds();
+ microseconds -= seconds * Time::kMicrosecondsPerSecond;
+ }
+ struct timespec result = {
+ seconds,
+ static_cast<long>(microseconds * Time::kNanosecondsPerMicrosecond)};
+ return result;
+}
+
+// static
+Time Time::FromTimeVal(struct timeval t) {
+ DCHECK_LT(t.tv_usec, static_cast<int>(Time::kMicrosecondsPerSecond));
+ DCHECK_GE(t.tv_usec, 0);
+ if (t.tv_usec == 0 && t.tv_sec == 0)
+ return Time();
+ if (t.tv_usec == static_cast<suseconds_t>(Time::kMicrosecondsPerSecond) - 1 &&
+ t.tv_sec == std::numeric_limits<time_t>::max())
+ return Max();
+ return Time((static_cast<int64_t>(t.tv_sec) * Time::kMicrosecondsPerSecond) +
+ t.tv_usec + kTimeTToMicrosecondsOffset);
+}
+
+struct timeval Time::ToTimeVal() const {
+ struct timeval result;
+ if (is_null()) {
+ result.tv_sec = 0;
+ result.tv_usec = 0;
+ return result;
+ }
+ if (is_max()) {
+ result.tv_sec = std::numeric_limits<time_t>::max();
+ result.tv_usec = static_cast<suseconds_t>(Time::kMicrosecondsPerSecond) - 1;
+ return result;
+ }
+ int64_t us = us_ - kTimeTToMicrosecondsOffset;
+ result.tv_sec = us / Time::kMicrosecondsPerSecond;
+ result.tv_usec = us % Time::kMicrosecondsPerSecond;
+ return result;
+}
+
+} // namespace base
diff --git a/base/time/time_exploded_posix.cc b/base/time/time_exploded_posix.cc
new file mode 100644
index 0000000000..627c6b4f87
--- /dev/null
+++ b/base/time/time_exploded_posix.cc
@@ -0,0 +1,300 @@
+// 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/time/time.h"
+
+#include <stdint.h>
+#include <sys/time.h>
+#include <time.h>
+#if defined(OS_ANDROID) && !defined(__LP64__)
+#include <time64.h>
+#endif
+#include <unistd.h>
+
+#include <limits>
+
+#include "base/numerics/safe_math.h"
+#include "base/synchronization/lock.h"
+#include "build/build_config.h"
+
+#if defined(OS_ANDROID)
+#include "base/os_compat_android.h"
+#elif defined(OS_NACL)
+#include "base/os_compat_nacl.h"
+#endif
+
+#if defined(OS_MACOSX)
+static_assert(sizeof(time_t) >= 8, "Y2038 problem!");
+#endif
+
+namespace {
+
+// This prevents a crash on traversing the environment global and looking up
+// the 'TZ' variable in libc. See: crbug.com/390567.
+base::Lock* GetSysTimeToTimeStructLock() {
+ static auto* lock = new base::Lock();
+ return lock;
+}
+
+// Define a system-specific SysTime that wraps either to a time_t or
+// a time64_t depending on the host system, and associated convertion.
+// See crbug.com/162007
+#if defined(OS_ANDROID) && !defined(__LP64__)
+typedef time64_t SysTime;
+
+SysTime SysTimeFromTimeStruct(struct tm* timestruct, bool is_local) {
+ base::AutoLock locked(*GetSysTimeToTimeStructLock());
+ if (is_local)
+ return mktime64(timestruct);
+ else
+ return timegm64(timestruct);
+}
+
+void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) {
+ base::AutoLock locked(*GetSysTimeToTimeStructLock());
+ if (is_local)
+ localtime64_r(&t, timestruct);
+ else
+ gmtime64_r(&t, timestruct);
+}
+
+#elif defined(OS_AIX)
+
+// The function timegm is not available on AIX.
+time_t aix_timegm(struct tm* tm) {
+ time_t ret;
+ char* tz;
+
+ tz = getenv("TZ");
+ if (tz) {
+ tz = strdup(tz);
+ }
+ setenv("TZ", "GMT0", 1);
+ tzset();
+ ret = mktime(tm);
+ if (tz) {
+ setenv("TZ", tz, 1);
+ free(tz);
+ } else {
+ unsetenv("TZ");
+ }
+ tzset();
+ return ret;
+}
+
+typedef time_t SysTime;
+
+SysTime SysTimeFromTimeStruct(struct tm* timestruct, bool is_local) {
+ base::AutoLock locked(*GetSysTimeToTimeStructLock());
+ if (is_local)
+ return mktime(timestruct);
+ else
+ return aix_timegm(timestruct);
+}
+
+void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) {
+ base::AutoLock locked(*GetSysTimeToTimeStructLock());
+ if (is_local)
+ localtime_r(&t, timestruct);
+ else
+ gmtime_r(&t, timestruct);
+}
+
+#else // OS_ANDROID && !__LP64__
+typedef time_t SysTime;
+
+SysTime SysTimeFromTimeStruct(struct tm* timestruct, bool is_local) {
+ base::AutoLock locked(*GetSysTimeToTimeStructLock());
+ if (is_local)
+ return mktime(timestruct);
+ else
+ return timegm(timestruct);
+}
+
+void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) {
+ base::AutoLock locked(*GetSysTimeToTimeStructLock());
+ if (is_local)
+ localtime_r(&t, timestruct);
+ else
+ gmtime_r(&t, timestruct);
+}
+#endif // OS_ANDROID
+
+} // namespace
+
+namespace base {
+
+void Time::Explode(bool is_local, Exploded* exploded) const {
+ // Time stores times with microsecond resolution, but Exploded only carries
+ // millisecond resolution, so begin by being lossy. Adjust from Windows
+ // epoch (1601) to Unix epoch (1970);
+ int64_t microseconds = us_ - kTimeTToMicrosecondsOffset;
+ // The following values are all rounded towards -infinity.
+ int64_t milliseconds; // Milliseconds since epoch.
+ SysTime seconds; // Seconds since epoch.
+ int millisecond; // Exploded millisecond value (0-999).
+ if (microseconds >= 0) {
+ // Rounding towards -infinity <=> rounding towards 0, in this case.
+ milliseconds = microseconds / kMicrosecondsPerMillisecond;
+ seconds = milliseconds / kMillisecondsPerSecond;
+ millisecond = milliseconds % kMillisecondsPerSecond;
+ } else {
+ // Round these *down* (towards -infinity).
+ milliseconds = (microseconds - kMicrosecondsPerMillisecond + 1) /
+ kMicrosecondsPerMillisecond;
+ seconds =
+ (milliseconds - kMillisecondsPerSecond + 1) / kMillisecondsPerSecond;
+ // Make this nonnegative (and between 0 and 999 inclusive).
+ millisecond = milliseconds % kMillisecondsPerSecond;
+ if (millisecond < 0)
+ millisecond += kMillisecondsPerSecond;
+ }
+
+ struct tm timestruct;
+ SysTimeToTimeStruct(seconds, &timestruct, is_local);
+
+ exploded->year = timestruct.tm_year + 1900;
+ exploded->month = timestruct.tm_mon + 1;
+ exploded->day_of_week = timestruct.tm_wday;
+ exploded->day_of_month = timestruct.tm_mday;
+ exploded->hour = timestruct.tm_hour;
+ exploded->minute = timestruct.tm_min;
+ exploded->second = timestruct.tm_sec;
+ exploded->millisecond = millisecond;
+}
+
+// static
+bool Time::FromExploded(bool is_local, const Exploded& exploded, Time* time) {
+ CheckedNumeric<int> month = exploded.month;
+ month--;
+ CheckedNumeric<int> year = exploded.year;
+ year -= 1900;
+ if (!month.IsValid() || !year.IsValid()) {
+ *time = Time(0);
+ return false;
+ }
+
+ struct tm timestruct;
+ timestruct.tm_sec = exploded.second;
+ timestruct.tm_min = exploded.minute;
+ timestruct.tm_hour = exploded.hour;
+ timestruct.tm_mday = exploded.day_of_month;
+ timestruct.tm_mon = month.ValueOrDie();
+ timestruct.tm_year = year.ValueOrDie();
+ timestruct.tm_wday = exploded.day_of_week; // mktime/timegm ignore this
+ timestruct.tm_yday = 0; // mktime/timegm ignore this
+ timestruct.tm_isdst = -1; // attempt to figure it out
+#if !defined(OS_NACL) && !defined(OS_SOLARIS) && !defined(OS_AIX)
+ timestruct.tm_gmtoff = 0; // not a POSIX field, so mktime/timegm ignore
+ timestruct.tm_zone = nullptr; // not a POSIX field, so mktime/timegm ignore
+#endif
+
+ SysTime seconds;
+
+ // Certain exploded dates do not really exist due to daylight saving times,
+ // and this causes mktime() to return implementation-defined values when
+ // tm_isdst is set to -1. On Android, the function will return -1, while the
+ // C libraries of other platforms typically return a liberally-chosen value.
+ // Handling this requires the special code below.
+
+ // SysTimeFromTimeStruct() modifies the input structure, save current value.
+ struct tm timestruct0 = timestruct;
+
+ seconds = SysTimeFromTimeStruct(&timestruct, is_local);
+ if (seconds == -1) {
+ // Get the time values with tm_isdst == 0 and 1, then select the closest one
+ // to UTC 00:00:00 that isn't -1.
+ timestruct = timestruct0;
+ timestruct.tm_isdst = 0;
+ int64_t seconds_isdst0 = SysTimeFromTimeStruct(&timestruct, is_local);
+
+ timestruct = timestruct0;
+ timestruct.tm_isdst = 1;
+ int64_t seconds_isdst1 = SysTimeFromTimeStruct(&timestruct, is_local);
+
+ // seconds_isdst0 or seconds_isdst1 can be -1 for some timezones.
+ // E.g. "CLST" (Chile Summer Time) returns -1 for 'tm_isdt == 1'.
+ if (seconds_isdst0 < 0)
+ seconds = seconds_isdst1;
+ else if (seconds_isdst1 < 0)
+ seconds = seconds_isdst0;
+ else
+ seconds = std::min(seconds_isdst0, seconds_isdst1);
+ }
+
+ // Handle overflow. Clamping the range to what mktime and timegm might
+ // return is the best that can be done here. It's not ideal, but it's better
+ // than failing here or ignoring the overflow case and treating each time
+ // overflow as one second prior to the epoch.
+ int64_t milliseconds = 0;
+ if (seconds == -1 && (exploded.year < 1969 || exploded.year > 1970)) {
+ // If exploded.year is 1969 or 1970, take -1 as correct, with the
+ // time indicating 1 second prior to the epoch. (1970 is allowed to handle
+ // time zone and DST offsets.) Otherwise, return the most future or past
+ // time representable. Assumes the time_t epoch is 1970-01-01 00:00:00 UTC.
+ //
+ // The minimum and maximum representible times that mktime and timegm could
+ // return are used here instead of values outside that range to allow for
+ // proper round-tripping between exploded and counter-type time
+ // representations in the presence of possible truncation to time_t by
+ // division and use with other functions that accept time_t.
+ //
+ // When representing the most distant time in the future, add in an extra
+ // 999ms to avoid the time being less than any other possible value that
+ // this function can return.
+
+ // On Android, SysTime is int64_t, special care must be taken to avoid
+ // overflows.
+ const int64_t min_seconds = (sizeof(SysTime) < sizeof(int64_t))
+ ? std::numeric_limits<SysTime>::min()
+ : std::numeric_limits<int32_t>::min();
+ const int64_t max_seconds = (sizeof(SysTime) < sizeof(int64_t))
+ ? std::numeric_limits<SysTime>::max()
+ : std::numeric_limits<int32_t>::max();
+ if (exploded.year < 1969) {
+ milliseconds = min_seconds * kMillisecondsPerSecond;
+ } else {
+ milliseconds = max_seconds * kMillisecondsPerSecond;
+ milliseconds += (kMillisecondsPerSecond - 1);
+ }
+ } else {
+ base::CheckedNumeric<int64_t> checked_millis = seconds;
+ checked_millis *= kMillisecondsPerSecond;
+ checked_millis += exploded.millisecond;
+ if (!checked_millis.IsValid()) {
+ *time = base::Time(0);
+ return false;
+ }
+ milliseconds = checked_millis.ValueOrDie();
+ }
+
+ // Adjust from Unix (1970) to Windows (1601) epoch avoiding overflows.
+ base::CheckedNumeric<int64_t> checked_microseconds_win_epoch = milliseconds;
+ checked_microseconds_win_epoch *= kMicrosecondsPerMillisecond;
+ checked_microseconds_win_epoch += kTimeTToMicrosecondsOffset;
+ if (!checked_microseconds_win_epoch.IsValid()) {
+ *time = base::Time(0);
+ return false;
+ }
+ base::Time converted_time(checked_microseconds_win_epoch.ValueOrDie());
+
+ // If |exploded.day_of_month| is set to 31 on a 28-30 day month, it will
+ // return the first day of the next month. Thus round-trip the time and
+ // compare the initial |exploded| with |utc_to_exploded| time.
+ base::Time::Exploded to_exploded;
+ if (!is_local)
+ converted_time.UTCExplode(&to_exploded);
+ else
+ converted_time.LocalExplode(&to_exploded);
+
+ if (ExplodedMostlyEquals(to_exploded, exploded)) {
+ *time = converted_time;
+ return true;
+ }
+
+ *time = Time(0);
+ return false;
+}
+
+} // namespace base
diff --git a/base/time/time_now_posix.cc b/base/time/time_now_posix.cc
new file mode 100644
index 0000000000..5427836883
--- /dev/null
+++ b/base/time/time_now_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.
+
+#include "base/time/time.h"
+
+#include <stdint.h>
+#include <sys/time.h>
+#include <time.h>
+#if defined(OS_ANDROID) && !defined(__LP64__)
+#include <time64.h>
+#endif
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "base/numerics/safe_math.h"
+#include "base/time/time_override.h"
+#include "build/build_config.h"
+
+// Ensure the Fuchsia and Mac builds do not include this module. Instead,
+// non-POSIX implementation is used for sampling the system clocks.
+#if defined(OS_FUCHSIA) || defined(OS_MACOSX)
+#error "This implementation is for POSIX platforms other than Fuchsia or Mac."
+#endif
+
+namespace {
+
+int64_t ConvertTimespecToMicros(const struct timespec& ts) {
+ // On 32-bit systems, the calculation cannot overflow int64_t.
+ // 2**32 * 1000000 + 2**64 / 1000 < 2**63
+ if (sizeof(ts.tv_sec) <= 4 && sizeof(ts.tv_nsec) <= 8) {
+ int64_t result = ts.tv_sec;
+ result *= base::Time::kMicrosecondsPerSecond;
+ result += (ts.tv_nsec / base::Time::kNanosecondsPerMicrosecond);
+ return result;
+ } else {
+ base::CheckedNumeric<int64_t> result(ts.tv_sec);
+ result *= base::Time::kMicrosecondsPerSecond;
+ result += (ts.tv_nsec / base::Time::kNanosecondsPerMicrosecond);
+ return result.ValueOrDie();
+ }
+}
+
+// Helper function to get results from clock_gettime() and convert to a
+// microsecond timebase. Minimum requirement is MONOTONIC_CLOCK to be supported
+// on the system. FreeBSD 6 has CLOCK_MONOTONIC but defines
+// _POSIX_MONOTONIC_CLOCK to -1.
+#if (defined(OS_POSIX) && defined(_POSIX_MONOTONIC_CLOCK) && \
+ _POSIX_MONOTONIC_CLOCK >= 0) || \
+ defined(OS_BSD) || defined(OS_ANDROID)
+int64_t ClockNow(clockid_t clk_id) {
+ struct timespec ts;
+ CHECK(clock_gettime(clk_id, &ts) == 0);
+ return ConvertTimespecToMicros(ts);
+}
+#else // _POSIX_MONOTONIC_CLOCK
+#error No usable tick clock function on this platform.
+#endif // _POSIX_MONOTONIC_CLOCK
+
+} // namespace
+
+namespace base {
+
+// Time -----------------------------------------------------------------------
+
+namespace subtle {
+Time TimeNowIgnoringOverride() {
+ struct timeval tv;
+ struct timezone tz = {0, 0}; // UTC
+ CHECK(gettimeofday(&tv, &tz) == 0);
+ // Combine seconds and microseconds in a 64-bit field containing microseconds
+ // since the epoch. That's enough for nearly 600 centuries. Adjust from
+ // Unix (1970) to Windows (1601) epoch.
+ return Time() + TimeDelta::FromMicroseconds(
+ (tv.tv_sec * Time::kMicrosecondsPerSecond + tv.tv_usec) +
+ Time::kTimeTToMicrosecondsOffset);
+}
+
+Time TimeNowFromSystemTimeIgnoringOverride() {
+ // Just use TimeNowIgnoringOverride() because it returns the system time.
+ return TimeNowIgnoringOverride();
+}
+} // namespace subtle
+
+// TimeTicks ------------------------------------------------------------------
+
+namespace subtle {
+TimeTicks TimeTicksNowIgnoringOverride() {
+ return TimeTicks() + TimeDelta::FromMicroseconds(ClockNow(CLOCK_MONOTONIC));
+}
+} // namespace subtle
+
+// static
+TimeTicks::Clock TimeTicks::GetClock() {
+ return Clock::LINUX_CLOCK_MONOTONIC;
+}
+
+// static
+bool TimeTicks::IsHighResolution() {
+ return true;
+}
+
+// static
+bool TimeTicks::IsConsistentAcrossProcesses() {
+ return true;
+}
+
+// ThreadTicks ----------------------------------------------------------------
+
+namespace subtle {
+ThreadTicks ThreadTicksNowIgnoringOverride() {
+#if (defined(_POSIX_THREAD_CPUTIME) && (_POSIX_THREAD_CPUTIME >= 0)) || \
+ defined(OS_ANDROID)
+ return ThreadTicks() +
+ TimeDelta::FromMicroseconds(ClockNow(CLOCK_THREAD_CPUTIME_ID));
+#else
+ NOTREACHED();
+ return ThreadTicks();
+#endif
+}
+} // namespace subtle
+
+} // namespace base
diff --git a/base/time/time_override.cc b/base/time/time_override.cc
new file mode 100644
index 0000000000..09692b5ca6
--- /dev/null
+++ b/base/time/time_override.cc
@@ -0,0 +1,45 @@
+// 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.
+
+#include "base/time/time_override.h"
+
+namespace base {
+namespace subtle {
+
+#if DCHECK_IS_ON()
+// static
+bool ScopedTimeClockOverrides::overrides_active_ = false;
+#endif
+
+ScopedTimeClockOverrides::ScopedTimeClockOverrides(
+ TimeNowFunction time_override,
+ TimeTicksNowFunction time_ticks_override,
+ ThreadTicksNowFunction thread_ticks_override) {
+#if DCHECK_IS_ON()
+ DCHECK(!overrides_active_);
+ overrides_active_ = true;
+#endif
+ if (time_override) {
+ internal::g_time_now_function = time_override;
+ internal::g_time_now_from_system_time_function = time_override;
+ }
+ if (time_ticks_override)
+ internal::g_time_ticks_now_function = time_ticks_override;
+ if (thread_ticks_override)
+ internal::g_thread_ticks_now_function = thread_ticks_override;
+}
+
+ScopedTimeClockOverrides::~ScopedTimeClockOverrides() {
+ internal::g_time_now_function = &TimeNowIgnoringOverride;
+ internal::g_time_now_from_system_time_function =
+ &TimeNowFromSystemTimeIgnoringOverride;
+ internal::g_time_ticks_now_function = &TimeTicksNowIgnoringOverride;
+ internal::g_thread_ticks_now_function = &ThreadTicksNowIgnoringOverride;
+#if DCHECK_IS_ON()
+ overrides_active_ = false;
+#endif
+}
+
+} // namespace subtle
+} // namespace base
diff --git a/base/time/time_override.h b/base/time/time_override.h
new file mode 100644
index 0000000000..1586a87078
--- /dev/null
+++ b/base/time/time_override.h
@@ -0,0 +1,74 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TIME_TIME_OVERRIDE_H_
+#define BASE_TIME_TIME_OVERRIDE_H_
+
+#include "base/base_export.h"
+#include "base/time/time.h"
+
+namespace base {
+
+using TimeNowFunction = decltype(&Time::Now);
+using TimeTicksNowFunction = decltype(&TimeTicks::Now);
+using ThreadTicksNowFunction = decltype(&ThreadTicks::Now);
+
+// Time overrides should be used with extreme caution. Discuss with //base/time
+// OWNERS before adding a new one.
+namespace subtle {
+
+// Override the return value of Time::Now and Time::NowFromSystemTime /
+// TimeTicks::Now / ThreadTicks::Now to emulate time, e.g. for tests or to
+// modify progression of time. Note that the override should be set while
+// single-threaded and before the first call to Now() to avoid threading issues
+// and inconsistencies in returned values. Nested overrides are not allowed.
+class BASE_EXPORT ScopedTimeClockOverrides {
+ public:
+ // Pass |nullptr| for any override if it shouldn't be overriden.
+ ScopedTimeClockOverrides(TimeNowFunction time_override,
+ TimeTicksNowFunction time_ticks_override,
+ ThreadTicksNowFunction thread_ticks_override);
+
+ // Restores the platform default Now() functions.
+ ~ScopedTimeClockOverrides();
+
+ private:
+#if DCHECK_IS_ON()
+ static bool overrides_active_;
+#endif
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ScopedTimeClockOverrides);
+};
+
+// These methods return the platform default Time::Now / TimeTicks::Now /
+// ThreadTicks::Now values even while an override is in place. These methods
+// should only be used in places where emulated time should be disregarded. For
+// example, they can be used to implement test timeouts for tests that may
+// override time.
+BASE_EXPORT Time TimeNowIgnoringOverride();
+BASE_EXPORT Time TimeNowFromSystemTimeIgnoringOverride();
+BASE_EXPORT TimeTicks TimeTicksNowIgnoringOverride();
+BASE_EXPORT ThreadTicks ThreadTicksNowIgnoringOverride();
+
+} // namespace subtle
+
+namespace internal {
+
+// These function pointers are used by platform-independent implementations of
+// the Now() methods and ScopedTimeClockOverrides. They are set to point to the
+// respective NowIgnoringOverride functions by default, but can also be set by
+// platform-specific code to select a default implementation at runtime, thereby
+// avoiding the indirection via the NowIgnoringOverride functions. Note that the
+// pointers can be overridden and later reset to the NowIgnoringOverride
+// functions by ScopedTimeClockOverrides.
+extern TimeNowFunction g_time_now_function;
+extern TimeNowFunction g_time_now_from_system_time_function;
+extern TimeTicksNowFunction g_time_ticks_now_function;
+extern ThreadTicksNowFunction g_thread_ticks_now_function;
+
+} // namespace internal
+
+} // namespace base
+
+#endif // BASE_TIME_TIME_OVERRIDE_H_
diff --git a/base/time/time_posix.cc b/base/time/time_posix.cc
deleted file mode 100644
index 2cceb0c610..0000000000
--- a/base/time/time_posix.cc
+++ /dev/null
@@ -1,429 +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/time/time.h"
-
-#include <stdint.h>
-#include <sys/time.h>
-#include <time.h>
-#if defined(OS_ANDROID) && !defined(__LP64__)
-#include <time64.h>
-#endif
-#include <unistd.h>
-
-#include <limits>
-#include <ostream>
-
-#include "base/logging.h"
-#include "base/numerics/safe_math.h"
-#include "build/build_config.h"
-
-#if defined(OS_ANDROID)
-#include "base/os_compat_android.h"
-#elif defined(OS_NACL)
-#include "base/os_compat_nacl.h"
-#endif
-
-#if !defined(OS_MACOSX)
-#include "base/synchronization/lock.h"
-#endif
-
-namespace {
-
-#if !defined(OS_MACOSX)
-// This prevents a crash on traversing the environment global and looking up
-// the 'TZ' variable in libc. See: crbug.com/390567.
-base::Lock* GetSysTimeToTimeStructLock() {
- static auto* lock = new base::Lock();
- return lock;
-}
-
-// Define a system-specific SysTime that wraps either to a time_t or
-// a time64_t depending on the host system, and associated convertion.
-// See crbug.com/162007
-#if defined(OS_ANDROID) && !defined(__LP64__)
-typedef time64_t SysTime;
-
-SysTime SysTimeFromTimeStruct(struct tm* timestruct, bool is_local) {
- base::AutoLock locked(*GetSysTimeToTimeStructLock());
- if (is_local)
- return mktime64(timestruct);
- else
- return timegm64(timestruct);
-}
-
-void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) {
- base::AutoLock locked(*GetSysTimeToTimeStructLock());
- if (is_local)
- localtime64_r(&t, timestruct);
- else
- gmtime64_r(&t, timestruct);
-}
-
-#else // OS_ANDROID && !__LP64__
-typedef time_t SysTime;
-
-SysTime SysTimeFromTimeStruct(struct tm* timestruct, bool is_local) {
- base::AutoLock locked(*GetSysTimeToTimeStructLock());
- if (is_local)
- return mktime(timestruct);
- else
- return timegm(timestruct);
-}
-
-void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) {
- base::AutoLock locked(*GetSysTimeToTimeStructLock());
- if (is_local)
- localtime_r(&t, timestruct);
- else
- gmtime_r(&t, timestruct);
-}
-#endif // OS_ANDROID
-
-int64_t ConvertTimespecToMicros(const struct timespec& ts) {
- // On 32-bit systems, the calculation cannot overflow int64_t.
- // 2**32 * 1000000 + 2**64 / 1000 < 2**63
- if (sizeof(ts.tv_sec) <= 4 && sizeof(ts.tv_nsec) <= 8) {
- int64_t result = ts.tv_sec;
- result *= base::Time::kMicrosecondsPerSecond;
- result += (ts.tv_nsec / base::Time::kNanosecondsPerMicrosecond);
- return result;
- } else {
- base::CheckedNumeric<int64_t> result(ts.tv_sec);
- result *= base::Time::kMicrosecondsPerSecond;
- result += (ts.tv_nsec / base::Time::kNanosecondsPerMicrosecond);
- return result.ValueOrDie();
- }
-}
-
-// Helper function to get results from clock_gettime() and convert to a
-// microsecond timebase. Minimum requirement is MONOTONIC_CLOCK to be supported
-// on the system. FreeBSD 6 has CLOCK_MONOTONIC but defines
-// _POSIX_MONOTONIC_CLOCK to -1.
-#if (defined(OS_POSIX) && \
- defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0) || \
- defined(OS_BSD) || defined(OS_ANDROID)
-int64_t ClockNow(clockid_t clk_id) {
- struct timespec ts;
- if (clock_gettime(clk_id, &ts) != 0) {
- NOTREACHED() << "clock_gettime(" << clk_id << ") failed.";
- return 0;
- }
- return ConvertTimespecToMicros(ts);
-}
-#else // _POSIX_MONOTONIC_CLOCK
-#error No usable tick clock function on this platform.
-#endif // _POSIX_MONOTONIC_CLOCK
-#endif // !defined(OS_MACOSX)
-
-} // namespace
-
-namespace base {
-
-// static
-TimeDelta TimeDelta::FromTimeSpec(const timespec& ts) {
- return TimeDelta(ts.tv_sec * Time::kMicrosecondsPerSecond +
- ts.tv_nsec / Time::kNanosecondsPerMicrosecond);
-}
-
-struct timespec TimeDelta::ToTimeSpec() const {
- int64_t microseconds = InMicroseconds();
- time_t seconds = 0;
- if (microseconds >= Time::kMicrosecondsPerSecond) {
- seconds = InSeconds();
- microseconds -= seconds * Time::kMicrosecondsPerSecond;
- }
- struct timespec result =
- {seconds,
- static_cast<long>(microseconds * Time::kNanosecondsPerMicrosecond)};
- return result;
-}
-
-#if !defined(OS_MACOSX)
-// The Time routines in this file use standard POSIX routines, or almost-
-// standard routines in the case of timegm. We need to use a Mach-specific
-// function for TimeTicks::Now() on Mac OS X.
-
-// Time -----------------------------------------------------------------------
-
-// Windows uses a Gregorian epoch of 1601. We need to match this internally
-// so that our time representations match across all platforms. See bug 14734.
-// irb(main):010:0> Time.at(0).getutc()
-// => Thu Jan 01 00:00:00 UTC 1970
-// irb(main):011:0> Time.at(-11644473600).getutc()
-// => Mon Jan 01 00:00:00 UTC 1601
-static const int64_t kWindowsEpochDeltaSeconds = INT64_C(11644473600);
-
-// static
-const int64_t Time::kWindowsEpochDeltaMicroseconds =
- kWindowsEpochDeltaSeconds * Time::kMicrosecondsPerSecond;
-
-// Some functions in time.cc use time_t directly, so we provide an offset
-// to convert from time_t (Unix epoch) and internal (Windows epoch).
-// static
-const int64_t Time::kTimeTToMicrosecondsOffset = kWindowsEpochDeltaMicroseconds;
-
-// static
-Time Time::Now() {
- struct timeval tv;
- struct timezone tz = { 0, 0 }; // UTC
- if (gettimeofday(&tv, &tz) != 0) {
- DCHECK(0) << "Could not determine time of day";
- PLOG(ERROR) << "Call to gettimeofday failed.";
- // Return null instead of uninitialized |tv| value, which contains random
- // garbage data. This may result in the crash seen in crbug.com/147570.
- return Time();
- }
- // Combine seconds and microseconds in a 64-bit field containing microseconds
- // since the epoch. That's enough for nearly 600 centuries. Adjust from
- // Unix (1970) to Windows (1601) epoch.
- return Time((tv.tv_sec * kMicrosecondsPerSecond + tv.tv_usec) +
- kWindowsEpochDeltaMicroseconds);
-}
-
-// static
-Time Time::NowFromSystemTime() {
- // Just use Now() because Now() returns the system time.
- return Now();
-}
-
-void Time::Explode(bool is_local, Exploded* exploded) const {
- // Time stores times with microsecond resolution, but Exploded only carries
- // millisecond resolution, so begin by being lossy. Adjust from Windows
- // epoch (1601) to Unix epoch (1970);
- int64_t microseconds = us_ - kWindowsEpochDeltaMicroseconds;
- // The following values are all rounded towards -infinity.
- int64_t milliseconds; // Milliseconds since epoch.
- SysTime seconds; // Seconds since epoch.
- int millisecond; // Exploded millisecond value (0-999).
- if (microseconds >= 0) {
- // Rounding towards -infinity <=> rounding towards 0, in this case.
- milliseconds = microseconds / kMicrosecondsPerMillisecond;
- seconds = milliseconds / kMillisecondsPerSecond;
- millisecond = milliseconds % kMillisecondsPerSecond;
- } else {
- // Round these *down* (towards -infinity).
- milliseconds = (microseconds - kMicrosecondsPerMillisecond + 1) /
- kMicrosecondsPerMillisecond;
- seconds = (milliseconds - kMillisecondsPerSecond + 1) /
- kMillisecondsPerSecond;
- // Make this nonnegative (and between 0 and 999 inclusive).
- millisecond = milliseconds % kMillisecondsPerSecond;
- if (millisecond < 0)
- millisecond += kMillisecondsPerSecond;
- }
-
- struct tm timestruct;
- SysTimeToTimeStruct(seconds, &timestruct, is_local);
-
- exploded->year = timestruct.tm_year + 1900;
- exploded->month = timestruct.tm_mon + 1;
- exploded->day_of_week = timestruct.tm_wday;
- exploded->day_of_month = timestruct.tm_mday;
- exploded->hour = timestruct.tm_hour;
- exploded->minute = timestruct.tm_min;
- exploded->second = timestruct.tm_sec;
- exploded->millisecond = millisecond;
-}
-
-// static
-bool Time::FromExploded(bool is_local, const Exploded& exploded, Time* time) {
- CheckedNumeric<int> month = exploded.month;
- month--;
- CheckedNumeric<int> year = exploded.year;
- year -= 1900;
- if (!month.IsValid() || !year.IsValid()) {
- *time = Time(0);
- return false;
- }
-
- struct tm timestruct;
- timestruct.tm_sec = exploded.second;
- timestruct.tm_min = exploded.minute;
- timestruct.tm_hour = exploded.hour;
- timestruct.tm_mday = exploded.day_of_month;
- timestruct.tm_mon = month.ValueOrDie();
- timestruct.tm_year = year.ValueOrDie();
- timestruct.tm_wday = exploded.day_of_week; // mktime/timegm ignore this
- timestruct.tm_yday = 0; // mktime/timegm ignore this
- timestruct.tm_isdst = -1; // attempt to figure it out
-#if !defined(OS_NACL) && !defined(OS_SOLARIS)
- timestruct.tm_gmtoff = 0; // not a POSIX field, so mktime/timegm ignore
- timestruct.tm_zone = NULL; // not a POSIX field, so mktime/timegm ignore
-#endif
-
- SysTime seconds;
-
- // Certain exploded dates do not really exist due to daylight saving times,
- // and this causes mktime() to return implementation-defined values when
- // tm_isdst is set to -1. On Android, the function will return -1, while the
- // C libraries of other platforms typically return a liberally-chosen value.
- // Handling this requires the special code below.
-
- // SysTimeFromTimeStruct() modifies the input structure, save current value.
- struct tm timestruct0 = timestruct;
-
- seconds = SysTimeFromTimeStruct(&timestruct, is_local);
- if (seconds == -1) {
- // Get the time values with tm_isdst == 0 and 1, then select the closest one
- // to UTC 00:00:00 that isn't -1.
- timestruct = timestruct0;
- timestruct.tm_isdst = 0;
- int64_t seconds_isdst0 = SysTimeFromTimeStruct(&timestruct, is_local);
-
- timestruct = timestruct0;
- timestruct.tm_isdst = 1;
- int64_t seconds_isdst1 = SysTimeFromTimeStruct(&timestruct, is_local);
-
- // seconds_isdst0 or seconds_isdst1 can be -1 for some timezones.
- // E.g. "CLST" (Chile Summer Time) returns -1 for 'tm_isdt == 1'.
- if (seconds_isdst0 < 0)
- seconds = seconds_isdst1;
- else if (seconds_isdst1 < 0)
- seconds = seconds_isdst0;
- else
- seconds = std::min(seconds_isdst0, seconds_isdst1);
- }
-
- // Handle overflow. Clamping the range to what mktime and timegm might
- // return is the best that can be done here. It's not ideal, but it's better
- // than failing here or ignoring the overflow case and treating each time
- // overflow as one second prior to the epoch.
- int64_t milliseconds = 0;
- if (seconds == -1 &&
- (exploded.year < 1969 || exploded.year > 1970)) {
- // If exploded.year is 1969 or 1970, take -1 as correct, with the
- // time indicating 1 second prior to the epoch. (1970 is allowed to handle
- // time zone and DST offsets.) Otherwise, return the most future or past
- // time representable. Assumes the time_t epoch is 1970-01-01 00:00:00 UTC.
- //
- // The minimum and maximum representible times that mktime and timegm could
- // return are used here instead of values outside that range to allow for
- // proper round-tripping between exploded and counter-type time
- // representations in the presence of possible truncation to time_t by
- // division and use with other functions that accept time_t.
- //
- // When representing the most distant time in the future, add in an extra
- // 999ms to avoid the time being less than any other possible value that
- // this function can return.
-
- // On Android, SysTime is int64_t, special care must be taken to avoid
- // overflows.
- const int64_t min_seconds = (sizeof(SysTime) < sizeof(int64_t))
- ? std::numeric_limits<SysTime>::min()
- : std::numeric_limits<int32_t>::min();
- const int64_t max_seconds = (sizeof(SysTime) < sizeof(int64_t))
- ? std::numeric_limits<SysTime>::max()
- : std::numeric_limits<int32_t>::max();
- if (exploded.year < 1969) {
- milliseconds = min_seconds * kMillisecondsPerSecond;
- } else {
- milliseconds = max_seconds * kMillisecondsPerSecond;
- milliseconds += (kMillisecondsPerSecond - 1);
- }
- } else {
- base::CheckedNumeric<int64_t> checked_millis = seconds;
- checked_millis *= kMillisecondsPerSecond;
- checked_millis += exploded.millisecond;
- if (!checked_millis.IsValid()) {
- *time = base::Time(0);
- return false;
- }
- milliseconds = checked_millis.ValueOrDie();
- }
-
- // Adjust from Unix (1970) to Windows (1601) epoch avoiding overflows.
- base::CheckedNumeric<int64_t> checked_microseconds_win_epoch = milliseconds;
- checked_microseconds_win_epoch *= kMicrosecondsPerMillisecond;
- checked_microseconds_win_epoch += kWindowsEpochDeltaMicroseconds;
- if (!checked_microseconds_win_epoch.IsValid()) {
- *time = base::Time(0);
- return false;
- }
- base::Time converted_time(checked_microseconds_win_epoch.ValueOrDie());
-
- // If |exploded.day_of_month| is set to 31 on a 28-30 day month, it will
- // return the first day of the next month. Thus round-trip the time and
- // compare the initial |exploded| with |utc_to_exploded| time.
- base::Time::Exploded to_exploded;
- if (!is_local)
- converted_time.UTCExplode(&to_exploded);
- else
- converted_time.LocalExplode(&to_exploded);
-
- if (ExplodedMostlyEquals(to_exploded, exploded)) {
- *time = converted_time;
- return true;
- }
-
- *time = Time(0);
- return false;
-}
-
-// TimeTicks ------------------------------------------------------------------
-// static
-TimeTicks TimeTicks::Now() {
- return TimeTicks(ClockNow(CLOCK_MONOTONIC));
-}
-
-// static
-TimeTicks::Clock TimeTicks::GetClock() {
- return Clock::LINUX_CLOCK_MONOTONIC;
-}
-
-// static
-bool TimeTicks::IsHighResolution() {
- return true;
-}
-
-// static
-bool TimeTicks::IsConsistentAcrossProcesses() {
- return true;
-}
-
-// static
-ThreadTicks ThreadTicks::Now() {
-#if (defined(_POSIX_THREAD_CPUTIME) && (_POSIX_THREAD_CPUTIME >= 0)) || \
- defined(OS_ANDROID)
- return ThreadTicks(ClockNow(CLOCK_THREAD_CPUTIME_ID));
-#else
- NOTREACHED();
- return ThreadTicks();
-#endif
-}
-
-#endif // !OS_MACOSX
-
-// static
-Time Time::FromTimeVal(struct timeval t) {
- DCHECK_LT(t.tv_usec, static_cast<int>(Time::kMicrosecondsPerSecond));
- DCHECK_GE(t.tv_usec, 0);
- if (t.tv_usec == 0 && t.tv_sec == 0)
- return Time();
- if (t.tv_usec == static_cast<suseconds_t>(Time::kMicrosecondsPerSecond) - 1 &&
- t.tv_sec == std::numeric_limits<time_t>::max())
- return Max();
- return Time((static_cast<int64_t>(t.tv_sec) * Time::kMicrosecondsPerSecond) +
- t.tv_usec + kTimeTToMicrosecondsOffset);
-}
-
-struct timeval Time::ToTimeVal() const {
- struct timeval result;
- if (is_null()) {
- result.tv_sec = 0;
- result.tv_usec = 0;
- return result;
- }
- if (is_max()) {
- result.tv_sec = std::numeric_limits<time_t>::max();
- result.tv_usec = static_cast<suseconds_t>(Time::kMicrosecondsPerSecond) - 1;
- return result;
- }
- int64_t us = us_ - kTimeTToMicrosecondsOffset;
- result.tv_sec = us / Time::kMicrosecondsPerSecond;
- result.tv_usec = us % Time::kMicrosecondsPerSecond;
- return result;
-}
-
-} // namespace base
diff --git a/base/time/time_to_iso8601.cc b/base/time/time_to_iso8601.cc
new file mode 100644
index 0000000000..27e7bfc284
--- /dev/null
+++ b/base/time/time_to_iso8601.cc
@@ -0,0 +1,20 @@
+// 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.
+
+#include "base/time/time_to_iso8601.h"
+
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+
+namespace base {
+
+std::string TimeToISO8601(const Time& t) {
+ Time::Exploded exploded;
+ t.UTCExplode(&exploded);
+ return StringPrintf("%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", exploded.year,
+ exploded.month, exploded.day_of_month, exploded.hour,
+ exploded.minute, exploded.second, exploded.millisecond);
+}
+
+} // namespace base
diff --git a/base/time/time_to_iso8601.h b/base/time/time_to_iso8601.h
new file mode 100644
index 0000000000..264348424c
--- /dev/null
+++ b/base/time/time_to_iso8601.h
@@ -0,0 +1,20 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_TIME_TIME_TO_ISO8601_H_
+#define BASE_TIME_TIME_TO_ISO8601_H_
+
+#include <string>
+
+#include "base/base_export.h"
+
+namespace base {
+
+class Time;
+
+BASE_EXPORT std::string TimeToISO8601(const base::Time& t);
+
+} // namespace base
+
+#endif // BASE_TIME_TIME_TO_ISO8601_H_
diff --git a/base/time/time_unittest.cc b/base/time/time_unittest.cc
index 8906c3bee1..2c106e54f2 100644
--- a/base/time/time_unittest.cc
+++ b/base/time/time_unittest.cc
@@ -9,14 +9,24 @@
#include <limits>
#include <string>
+#include "base/build_time.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/stringprintf.h"
#include "base/threading/platform_thread.h"
+#include "base/time/time_override.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
+#if defined(OS_ANDROID)
+#include "base/android/jni_android.h"
+#elif defined(OS_IOS)
+#include "base/ios/ios_util.h"
+#elif defined(OS_WIN)
+#include <windows.h>
+#endif
+
namespace base {
namespace {
@@ -111,14 +121,90 @@ class TimeTest : public testing::Test {
Time comparison_time_pdt_;
};
-// Test conversions to/from time_t and exploding/unexploding.
+// Test conversion to/from TimeDeltas elapsed since the Windows epoch.
+// Conversions should be idempotent and non-lossy.
+TEST_F(TimeTest, DeltaSinceWindowsEpoch) {
+ const TimeDelta delta = TimeDelta::FromMicroseconds(123);
+ EXPECT_EQ(delta,
+ Time::FromDeltaSinceWindowsEpoch(delta).ToDeltaSinceWindowsEpoch());
+
+ const Time now = Time::Now();
+ const Time actual =
+ Time::FromDeltaSinceWindowsEpoch(now.ToDeltaSinceWindowsEpoch());
+ EXPECT_EQ(now, actual);
+
+ // Null times should remain null after a round-trip conversion. This is an
+ // important invariant for the common use case of serialization +
+ // deserialization.
+ const Time should_be_null =
+ Time::FromDeltaSinceWindowsEpoch(Time().ToDeltaSinceWindowsEpoch());
+ EXPECT_TRUE(should_be_null.is_null());
+}
+
+// Test conversion to/from time_t.
TEST_F(TimeTest, TimeT) {
+ EXPECT_EQ(10, Time().FromTimeT(10).ToTimeT());
+ EXPECT_EQ(10.0, Time().FromTimeT(10).ToDoubleT());
+
+ // Conversions of 0 should stay 0.
+ EXPECT_EQ(0, Time().ToTimeT());
+ EXPECT_EQ(0, Time::FromTimeT(0).ToInternalValue());
+}
+
+// Test conversions to/from time_t and exploding/unexploding (utc time).
+TEST_F(TimeTest, UTCTimeT) {
+ // C library time and exploded time.
+ time_t now_t_1 = time(nullptr);
+ struct tm tms;
+#if defined(OS_WIN)
+ gmtime_s(&tms, &now_t_1);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ gmtime_r(&now_t_1, &tms);
+#endif
+
+ // Convert to ours.
+ Time our_time_1 = Time::FromTimeT(now_t_1);
+ Time::Exploded exploded;
+ our_time_1.UTCExplode(&exploded);
+
+ // This will test both our exploding and our time_t -> Time conversion.
+ EXPECT_EQ(tms.tm_year + 1900, exploded.year);
+ EXPECT_EQ(tms.tm_mon + 1, exploded.month);
+ EXPECT_EQ(tms.tm_mday, exploded.day_of_month);
+ EXPECT_EQ(tms.tm_hour, exploded.hour);
+ EXPECT_EQ(tms.tm_min, exploded.minute);
+ EXPECT_EQ(tms.tm_sec, exploded.second);
+
+ // Convert exploded back to the time struct.
+ Time our_time_2;
+ EXPECT_TRUE(Time::FromUTCExploded(exploded, &our_time_2));
+ EXPECT_TRUE(our_time_1 == our_time_2);
+
+ time_t now_t_2 = our_time_2.ToTimeT();
+ EXPECT_EQ(now_t_1, now_t_2);
+}
+
+// Test conversions to/from time_t and exploding/unexploding (local time).
+TEST_F(TimeTest, LocalTimeT) {
+#if defined(OS_IOS) && TARGET_OS_SIMULATOR
+ // The function CFTimeZoneCopySystem() fails to determine the system timezone
+ // when running iOS 11.0 simulator on an host running High Sierra and return
+ // the "GMT" timezone. This causes Time::LocalExplode and localtime_r values
+ // to differ by the local timezone offset. Disable the test if simulating
+ // iOS 10.0 as it is not possible to check the version of the host mac.
+ // TODO(crbug.com/782033): remove this once support for iOS pre-11.0 is
+ // dropped or when the bug in CFTimeZoneCopySystem() is fixed.
+ if (ios::IsRunningOnIOS10OrLater() && !ios::IsRunningOnIOS11OrLater()) {
+ return;
+ }
+#endif
+
// C library time and exploded time.
- time_t now_t_1 = time(NULL);
+ time_t now_t_1 = time(nullptr);
struct tm tms;
#if defined(OS_WIN)
localtime_s(&tms, &now_t_1);
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
localtime_r(&now_t_1, &tms);
#endif
@@ -142,13 +228,6 @@ TEST_F(TimeTest, TimeT) {
time_t now_t_2 = our_time_2.ToTimeT();
EXPECT_EQ(now_t_1, now_t_2);
-
- EXPECT_EQ(10, Time().FromTimeT(10).ToTimeT());
- EXPECT_EQ(10.0, Time().FromTimeT(10).ToDoubleT());
-
- // Conversions of 0 should stay 0.
- EXPECT_EQ(0, Time().ToTimeT());
- EXPECT_EQ(0, Time::FromTimeT(0).ToInternalValue());
}
// Test conversions to/from javascript time.
@@ -161,13 +240,13 @@ TEST_F(TimeTest, JsTime) {
EXPECT_EQ(800730.0, t.ToJsTime());
}
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
TEST_F(TimeTest, FromTimeVal) {
Time now = Time::Now();
Time also_now = Time::FromTimeVal(now.ToTimeVal());
EXPECT_EQ(now, also_now);
}
-#endif // OS_POSIX
+#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
TEST_F(TimeTest, FromExplodedWithMilliseconds) {
// Some platform implementations of FromExploded are liable to drop
@@ -227,13 +306,12 @@ TEST_F(TimeTest, ParseTimeTest1) {
time_t current_time = 0;
time(&current_time);
- const int BUFFER_SIZE = 64;
- struct tm local_time = {0};
- char time_buf[BUFFER_SIZE] = {0};
+ struct tm local_time = {};
+ char time_buf[64] = {};
#if defined(OS_WIN)
localtime_s(&local_time, &current_time);
asctime_s(time_buf, arraysize(time_buf), &local_time);
-#elif defined(OS_POSIX)
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
localtime_r(&current_time, &local_time);
asctime_r(&local_time, time_buf);
#endif
@@ -569,7 +647,7 @@ TEST_F(TimeTest, MaxConversions) {
EXPECT_TRUE(t.is_max());
EXPECT_EQ(std::numeric_limits<time_t>::max(), t.ToTimeT());
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
struct timeval tval;
tval.tv_sec = std::numeric_limits<time_t>::max();
tval.tv_usec = static_cast<suseconds_t>(Time::kMicrosecondsPerSecond) - 1;
@@ -631,6 +709,115 @@ TEST_F(TimeTest, FromLocalExplodedCrashOnAndroid) {
}
#endif // OS_ANDROID
+TEST_F(TimeTest, FromExploded_MinMax) {
+ Time::Exploded exploded = {0};
+ exploded.month = 1;
+ exploded.day_of_month = 1;
+
+ Time parsed_time;
+
+ if (Time::kExplodedMinYear != std::numeric_limits<int>::min()) {
+ exploded.year = Time::kExplodedMinYear;
+ EXPECT_TRUE(Time::FromUTCExploded(exploded, &parsed_time));
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ // On Windows, January 1, 1601 00:00:00 is actually the null time.
+ EXPECT_FALSE(parsed_time.is_null());
+#endif
+
+#if !defined(OS_ANDROID) && !defined(OS_MACOSX)
+ // The dates earlier than |kExplodedMinYear| that don't work are OS version
+ // dependent on Android and Mac (for example, macOS 10.13 seems to support
+ // dates before 1902).
+ exploded.year--;
+ EXPECT_FALSE(Time::FromUTCExploded(exploded, &parsed_time));
+ EXPECT_TRUE(parsed_time.is_null());
+#endif
+ }
+
+ if (Time::kExplodedMaxYear != std::numeric_limits<int>::max()) {
+ exploded.year = Time::kExplodedMaxYear;
+ exploded.month = 12;
+ exploded.day_of_month = 31;
+ exploded.hour = 23;
+ exploded.minute = 59;
+ exploded.second = 59;
+ exploded.millisecond = 999;
+ EXPECT_TRUE(Time::FromUTCExploded(exploded, &parsed_time));
+ EXPECT_FALSE(parsed_time.is_null());
+
+ exploded.year++;
+ EXPECT_FALSE(Time::FromUTCExploded(exploded, &parsed_time));
+ EXPECT_TRUE(parsed_time.is_null());
+ }
+}
+
+class TimeOverride {
+ public:
+ static Time Now() {
+ now_time_ += TimeDelta::FromSeconds(1);
+ return now_time_;
+ }
+
+ static Time now_time_;
+};
+
+// static
+Time TimeOverride::now_time_;
+
+TEST_F(TimeTest, NowOverride) {
+ TimeOverride::now_time_ = Time::UnixEpoch();
+
+ // Choose a reference time that we know to be in the past but close to now.
+ Time build_time = GetBuildTime();
+
+ // Override is not active. All Now() methods should return a time greater than
+ // the build time.
+ EXPECT_LT(build_time, Time::Now());
+ EXPECT_GT(Time::Max(), Time::Now());
+ EXPECT_LT(build_time, subtle::TimeNowIgnoringOverride());
+ EXPECT_GT(Time::Max(), subtle::TimeNowIgnoringOverride());
+ EXPECT_LT(build_time, Time::NowFromSystemTime());
+ EXPECT_GT(Time::Max(), Time::NowFromSystemTime());
+ EXPECT_LT(build_time, subtle::TimeNowFromSystemTimeIgnoringOverride());
+ EXPECT_GT(Time::Max(), subtle::TimeNowFromSystemTimeIgnoringOverride());
+
+ {
+ // Set override.
+ subtle::ScopedTimeClockOverrides overrides(&TimeOverride::Now, nullptr,
+ nullptr);
+
+ // Overridden value is returned and incremented when Now() or
+ // NowFromSystemTime() is called.
+ EXPECT_EQ(Time::UnixEpoch() + TimeDelta::FromSeconds(1), Time::Now());
+ EXPECT_EQ(Time::UnixEpoch() + TimeDelta::FromSeconds(2), Time::Now());
+ EXPECT_EQ(Time::UnixEpoch() + TimeDelta::FromSeconds(3),
+ Time::NowFromSystemTime());
+ EXPECT_EQ(Time::UnixEpoch() + TimeDelta::FromSeconds(4),
+ Time::NowFromSystemTime());
+
+ // IgnoringOverride methods still return real time.
+ EXPECT_LT(build_time, subtle::TimeNowIgnoringOverride());
+ EXPECT_GT(Time::Max(), subtle::TimeNowIgnoringOverride());
+ EXPECT_LT(build_time, subtle::TimeNowFromSystemTimeIgnoringOverride());
+ EXPECT_GT(Time::Max(), subtle::TimeNowFromSystemTimeIgnoringOverride());
+
+ // IgnoringOverride methods didn't call NowOverrideClock::Now().
+ EXPECT_EQ(Time::UnixEpoch() + TimeDelta::FromSeconds(5), Time::Now());
+ EXPECT_EQ(Time::UnixEpoch() + TimeDelta::FromSeconds(6),
+ Time::NowFromSystemTime());
+ }
+
+ // All methods return real time again.
+ EXPECT_LT(build_time, Time::Now());
+ EXPECT_GT(Time::Max(), Time::Now());
+ EXPECT_LT(build_time, subtle::TimeNowIgnoringOverride());
+ EXPECT_GT(Time::Max(), subtle::TimeNowIgnoringOverride());
+ EXPECT_LT(build_time, Time::NowFromSystemTime());
+ EXPECT_GT(Time::Max(), Time::NowFromSystemTime());
+ EXPECT_LT(build_time, subtle::TimeNowFromSystemTimeIgnoringOverride());
+ EXPECT_GT(Time::Max(), subtle::TimeNowFromSystemTimeIgnoringOverride());
+}
+
TEST(TimeTicks, Deltas) {
for (int index = 0; index < 50; index++) {
TimeTicks ticks_start = TimeTicks::Now();
@@ -699,14 +886,110 @@ TEST(TimeTicks, HighRes) {
HighResClockTest(&TimeTicks::Now);
}
-// Fails frequently on Android http://crbug.com/352633 with:
-// Expected: (delta_thread.InMicroseconds()) > (0), actual: 0 vs 0
-#if defined(OS_ANDROID)
-#define MAYBE_ThreadNow DISABLED_ThreadNow
+class TimeTicksOverride {
+ public:
+ static TimeTicks Now() {
+ now_ticks_ += TimeDelta::FromSeconds(1);
+ return now_ticks_;
+ }
+
+ static TimeTicks now_ticks_;
+};
+
+// static
+TimeTicks TimeTicksOverride::now_ticks_;
+
+TEST(TimeTicks, NowOverride) {
+ TimeTicksOverride::now_ticks_ = TimeTicks::Min();
+
+ // Override is not active. All Now() methods should return a sensible value.
+ EXPECT_LT(TimeTicks::Min(), TimeTicks::UnixEpoch());
+ EXPECT_LT(TimeTicks::UnixEpoch(), TimeTicks::Now());
+ EXPECT_GT(TimeTicks::Max(), TimeTicks::Now());
+ EXPECT_LT(TimeTicks::UnixEpoch(), subtle::TimeTicksNowIgnoringOverride());
+ EXPECT_GT(TimeTicks::Max(), subtle::TimeTicksNowIgnoringOverride());
+
+ {
+ // Set override.
+ subtle::ScopedTimeClockOverrides overrides(nullptr, &TimeTicksOverride::Now,
+ nullptr);
+
+ // Overridden value is returned and incremented when Now() is called.
+ EXPECT_EQ(TimeTicks::Min() + TimeDelta::FromSeconds(1), TimeTicks::Now());
+ EXPECT_EQ(TimeTicks::Min() + TimeDelta::FromSeconds(2), TimeTicks::Now());
+
+ // NowIgnoringOverride() still returns real ticks.
+ EXPECT_LT(TimeTicks::UnixEpoch(), subtle::TimeTicksNowIgnoringOverride());
+ EXPECT_GT(TimeTicks::Max(), subtle::TimeTicksNowIgnoringOverride());
+
+ // IgnoringOverride methods didn't call NowOverrideTickClock::NowTicks().
+ EXPECT_EQ(TimeTicks::Min() + TimeDelta::FromSeconds(3), TimeTicks::Now());
+ }
+
+ // All methods return real ticks again.
+ EXPECT_LT(TimeTicks::UnixEpoch(), TimeTicks::Now());
+ EXPECT_GT(TimeTicks::Max(), TimeTicks::Now());
+ EXPECT_LT(TimeTicks::UnixEpoch(), subtle::TimeTicksNowIgnoringOverride());
+ EXPECT_GT(TimeTicks::Max(), subtle::TimeTicksNowIgnoringOverride());
+}
+
+class ThreadTicksOverride {
+ public:
+ static ThreadTicks Now() {
+ now_ticks_ += TimeDelta::FromSeconds(1);
+ return now_ticks_;
+ }
+
+ static ThreadTicks now_ticks_;
+};
+
+// static
+ThreadTicks ThreadTicksOverride::now_ticks_;
+
+// IOS doesn't support ThreadTicks::Now().
+#if defined(OS_IOS)
+#define MAYBE_NowOverride DISABLED_NowOverride
#else
-#define MAYBE_ThreadNow ThreadNow
+#define MAYBE_NowOverride NowOverride
#endif
-TEST(ThreadTicks, MAYBE_ThreadNow) {
+TEST(ThreadTicks, MAYBE_NowOverride) {
+ ThreadTicksOverride::now_ticks_ = ThreadTicks::Min();
+
+ // Override is not active. All Now() methods should return a sensible value.
+ ThreadTicks initial_thread_ticks = ThreadTicks::Now();
+ EXPECT_LE(initial_thread_ticks, ThreadTicks::Now());
+ EXPECT_GT(ThreadTicks::Max(), ThreadTicks::Now());
+ EXPECT_LE(initial_thread_ticks, subtle::ThreadTicksNowIgnoringOverride());
+ EXPECT_GT(ThreadTicks::Max(), subtle::ThreadTicksNowIgnoringOverride());
+
+ {
+ // Set override.
+ subtle::ScopedTimeClockOverrides overrides(nullptr, nullptr,
+ &ThreadTicksOverride::Now);
+
+ // Overridden value is returned and incremented when Now() is called.
+ EXPECT_EQ(ThreadTicks::Min() + TimeDelta::FromSeconds(1),
+ ThreadTicks::Now());
+ EXPECT_EQ(ThreadTicks::Min() + TimeDelta::FromSeconds(2),
+ ThreadTicks::Now());
+
+ // NowIgnoringOverride() still returns real ticks.
+ EXPECT_LE(initial_thread_ticks, subtle::ThreadTicksNowIgnoringOverride());
+ EXPECT_GT(ThreadTicks::Max(), subtle::ThreadTicksNowIgnoringOverride());
+
+ // IgnoringOverride methods didn't call NowOverrideTickClock::NowTicks().
+ EXPECT_EQ(ThreadTicks::Min() + TimeDelta::FromSeconds(3),
+ ThreadTicks::Now());
+ }
+
+ // All methods return real ticks again.
+ EXPECT_LE(initial_thread_ticks, ThreadTicks::Now());
+ EXPECT_GT(ThreadTicks::Max(), ThreadTicks::Now());
+ EXPECT_LE(initial_thread_ticks, subtle::ThreadTicksNowIgnoringOverride());
+ EXPECT_GT(ThreadTicks::Max(), subtle::ThreadTicksNowIgnoringOverride());
+}
+
+TEST(ThreadTicks, ThreadNow) {
if (ThreadTicks::IsSupported()) {
ThreadTicks::WaitUntilInitialized();
TimeTicks begin = TimeTicks::Now();
@@ -720,7 +1003,7 @@ TEST(ThreadTicks, MAYBE_ThreadNow) {
TimeDelta delta = end - begin;
TimeDelta delta_thread = end_thread - begin_thread;
// Make sure that some thread time have elapsed.
- EXPECT_GT(delta_thread.InMicroseconds(), 0);
+ EXPECT_GE(delta_thread.InMicroseconds(), 0);
// But the thread time is at least 9ms less than clock time.
TimeDelta difference = delta - delta_thread;
EXPECT_GE(difference.InMicroseconds(), 9000);
@@ -783,6 +1066,28 @@ TEST(TimeTicks, SnappedToNextTickOverflow) {
.ToInternalValue());
}
+#if defined(OS_ANDROID)
+TEST(TimeTicks, Android_FromUptimeMillis_ClocksMatch) {
+ JNIEnv* const env = android::AttachCurrentThread();
+ android::ScopedJavaLocalRef<jclass> clazz(
+ android::GetClass(env, "android/os/SystemClock"));
+ ASSERT_TRUE(clazz.obj());
+ const jmethodID method_id =
+ android::MethodID::Get<android::MethodID::TYPE_STATIC>(
+ env, clazz.obj(), "uptimeMillis", "()J");
+ ASSERT_FALSE(!method_id);
+ // Subtract 1ms from the expected lower bound to allow millisecon-level
+ // truncation performed in uptimeMillis().
+ const TimeTicks lower_bound_ticks =
+ TimeTicks::Now() - TimeDelta::FromMilliseconds(1);
+ const TimeTicks converted_ticks = TimeTicks::FromUptimeMillis(
+ env->CallStaticLongMethod(clazz.obj(), method_id));
+ const TimeTicks upper_bound_ticks = TimeTicks::Now();
+ EXPECT_LE(lower_bound_ticks, converted_ticks);
+ EXPECT_GE(upper_bound_ticks, converted_ticks);
+}
+#endif // OS_ANDROID
+
TEST(TimeDelta, FromAndIn) {
// static_assert also checks that the contained expression is a constant
// expression, meaning all its components are suitable for initializing global
@@ -799,22 +1104,66 @@ TEST(TimeDelta, FromAndIn) {
static_assert(
TimeDelta::FromMillisecondsD(2.5) == TimeDelta::FromMicroseconds(2500),
"");
- EXPECT_EQ(13, TimeDelta::FromDays(13).InDays());
- EXPECT_EQ(13, TimeDelta::FromHours(13).InHours());
- EXPECT_EQ(13, TimeDelta::FromMinutes(13).InMinutes());
- EXPECT_EQ(13, TimeDelta::FromSeconds(13).InSeconds());
- EXPECT_EQ(13.0, TimeDelta::FromSeconds(13).InSecondsF());
- EXPECT_EQ(13, TimeDelta::FromMilliseconds(13).InMilliseconds());
- EXPECT_EQ(13.0, TimeDelta::FromMilliseconds(13).InMillisecondsF());
- EXPECT_EQ(13, TimeDelta::FromSecondsD(13.1).InSeconds());
- EXPECT_EQ(13.1, TimeDelta::FromSecondsD(13.1).InSecondsF());
- EXPECT_EQ(13, TimeDelta::FromMillisecondsD(13.3).InMilliseconds());
- EXPECT_EQ(13.3, TimeDelta::FromMillisecondsD(13.3).InMillisecondsF());
- EXPECT_EQ(13, TimeDelta::FromMicroseconds(13).InMicroseconds());
- EXPECT_EQ(3.456, TimeDelta::FromMillisecondsD(3.45678).InMillisecondsF());
-}
-
-#if defined(OS_POSIX)
+ EXPECT_EQ(TimeDelta::FromDays(13).InDays(), 13);
+ EXPECT_EQ(TimeDelta::FromHours(13).InHours(), 13);
+ EXPECT_EQ(TimeDelta::FromMinutes(13).InMinutes(), 13);
+ EXPECT_EQ(TimeDelta::FromSeconds(13).InSeconds(), 13);
+ EXPECT_EQ(TimeDelta::FromSeconds(13).InSecondsF(), 13.0);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(13).InMilliseconds(), 13);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(13).InMillisecondsF(), 13.0);
+ EXPECT_EQ(TimeDelta::FromSecondsD(13.1).InSeconds(), 13);
+ EXPECT_EQ(TimeDelta::FromSecondsD(13.1).InSecondsF(), 13.1);
+ EXPECT_EQ(TimeDelta::FromMillisecondsD(13.3).InMilliseconds(), 13);
+ EXPECT_EQ(TimeDelta::FromMillisecondsD(13.3).InMillisecondsF(), 13.3);
+ EXPECT_EQ(TimeDelta::FromMicroseconds(13).InMicroseconds(), 13);
+ EXPECT_EQ(TimeDelta::FromMicrosecondsD(13.3).InMicroseconds(), 13);
+ EXPECT_EQ(TimeDelta::FromMillisecondsD(3.45678).InMillisecondsF(), 3.456);
+ EXPECT_EQ(TimeDelta::FromNanoseconds(12345).InNanoseconds(), 12000);
+ EXPECT_EQ(TimeDelta::FromNanosecondsD(12345.678).InNanoseconds(), 12000);
+}
+
+TEST(TimeDelta, InRoundsTowardsZero) {
+ EXPECT_EQ(TimeDelta::FromHours(23).InDays(), 0);
+ EXPECT_EQ(TimeDelta::FromHours(-23).InDays(), 0);
+ EXPECT_EQ(TimeDelta::FromMinutes(59).InHours(), 0);
+ EXPECT_EQ(TimeDelta::FromMinutes(-59).InHours(), 0);
+ EXPECT_EQ(TimeDelta::FromSeconds(59).InMinutes(), 0);
+ EXPECT_EQ(TimeDelta::FromSeconds(-59).InMinutes(), 0);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(999).InSeconds(), 0);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(-999).InSeconds(), 0);
+ EXPECT_EQ(TimeDelta::FromMicroseconds(999).InMilliseconds(), 0);
+ EXPECT_EQ(TimeDelta::FromMicroseconds(-999).InMilliseconds(), 0);
+}
+
+TEST(TimeDelta, InDaysFloored) {
+ EXPECT_EQ(TimeDelta::FromHours(-25).InDaysFloored(), -2);
+ EXPECT_EQ(TimeDelta::FromHours(-24).InDaysFloored(), -1);
+ EXPECT_EQ(TimeDelta::FromHours(-23).InDaysFloored(), -1);
+
+ EXPECT_EQ(TimeDelta::FromHours(-1).InDaysFloored(), -1);
+ EXPECT_EQ(TimeDelta::FromHours(0).InDaysFloored(), 0);
+ EXPECT_EQ(TimeDelta::FromHours(1).InDaysFloored(), 0);
+
+ EXPECT_EQ(TimeDelta::FromHours(23).InDaysFloored(), 0);
+ EXPECT_EQ(TimeDelta::FromHours(24).InDaysFloored(), 1);
+ EXPECT_EQ(TimeDelta::FromHours(25).InDaysFloored(), 1);
+}
+
+TEST(TimeDelta, InMillisecondsRoundedUp) {
+ EXPECT_EQ(TimeDelta::FromMicroseconds(-1001).InMillisecondsRoundedUp(), -1);
+ EXPECT_EQ(TimeDelta::FromMicroseconds(-1000).InMillisecondsRoundedUp(), -1);
+ EXPECT_EQ(TimeDelta::FromMicroseconds(-999).InMillisecondsRoundedUp(), 0);
+
+ EXPECT_EQ(TimeDelta::FromMicroseconds(-1).InMillisecondsRoundedUp(), 0);
+ EXPECT_EQ(TimeDelta::FromMicroseconds(0).InMillisecondsRoundedUp(), 0);
+ EXPECT_EQ(TimeDelta::FromMicroseconds(1).InMillisecondsRoundedUp(), 1);
+
+ EXPECT_EQ(TimeDelta::FromMicroseconds(999).InMillisecondsRoundedUp(), 1);
+ EXPECT_EQ(TimeDelta::FromMicroseconds(1000).InMillisecondsRoundedUp(), 1);
+ EXPECT_EQ(TimeDelta::FromMicroseconds(1001).InMillisecondsRoundedUp(), 2);
+}
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
TEST(TimeDelta, TimeSpecConversion) {
TimeDelta delta = TimeDelta::FromSeconds(0);
struct timespec result = delta.ToTimeSpec();
@@ -840,7 +1189,7 @@ TEST(TimeDelta, TimeSpecConversion) {
EXPECT_EQ(result.tv_nsec, 1000);
EXPECT_EQ(delta, TimeDelta::FromTimeSpec(result));
}
-#endif // OS_POSIX
+#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
// Our internal time format is serialized in things like databases, so it's
// important that it's consistent across all our platforms. We use the 1601
@@ -874,211 +1223,285 @@ std::string AnyToString(Any any) {
}
TEST(TimeDelta, Magnitude) {
- const int64_t zero = 0;
- EXPECT_EQ(TimeDelta::FromMicroseconds(zero),
- TimeDelta::FromMicroseconds(zero).magnitude());
-
- const int64_t one = 1;
- const int64_t negative_one = -1;
- EXPECT_EQ(TimeDelta::FromMicroseconds(one),
- TimeDelta::FromMicroseconds(one).magnitude());
- EXPECT_EQ(TimeDelta::FromMicroseconds(one),
- TimeDelta::FromMicroseconds(negative_one).magnitude());
-
- const int64_t max_int64_minus_one = std::numeric_limits<int64_t>::max() - 1;
- const int64_t min_int64_plus_two = std::numeric_limits<int64_t>::min() + 2;
- EXPECT_EQ(TimeDelta::FromMicroseconds(max_int64_minus_one),
- TimeDelta::FromMicroseconds(max_int64_minus_one).magnitude());
- EXPECT_EQ(TimeDelta::FromMicroseconds(max_int64_minus_one),
- TimeDelta::FromMicroseconds(min_int64_plus_two).magnitude());
-}
-
-TEST(TimeDelta, Max) {
- TimeDelta max = TimeDelta::Max();
- EXPECT_TRUE(max.is_max());
- EXPECT_EQ(max, TimeDelta::Max());
- EXPECT_GT(max, TimeDelta::FromDays(100 * 365));
- EXPECT_GT(max, TimeDelta());
-}
+ constexpr int64_t zero = 0;
+ static_assert(TimeDelta::FromMicroseconds(zero) ==
+ TimeDelta::FromMicroseconds(zero).magnitude(),
+ "");
-bool IsMin(TimeDelta delta) {
- return (-delta).is_max();
+ constexpr int64_t one = 1;
+ constexpr int64_t negative_one = -1;
+ static_assert(TimeDelta::FromMicroseconds(one) ==
+ TimeDelta::FromMicroseconds(one).magnitude(),
+ "");
+ static_assert(TimeDelta::FromMicroseconds(one) ==
+ TimeDelta::FromMicroseconds(negative_one).magnitude(),
+ "");
+
+ constexpr int64_t max_int64_minus_one =
+ std::numeric_limits<int64_t>::max() - 1;
+ constexpr int64_t min_int64_plus_two =
+ std::numeric_limits<int64_t>::min() + 2;
+ static_assert(
+ TimeDelta::FromMicroseconds(max_int64_minus_one) ==
+ TimeDelta::FromMicroseconds(max_int64_minus_one).magnitude(),
+ "");
+ static_assert(TimeDelta::FromMicroseconds(max_int64_minus_one) ==
+ TimeDelta::FromMicroseconds(min_int64_plus_two).magnitude(),
+ "");
}
-TEST(TimeDelta, MaxConversions) {
- TimeDelta t = TimeDelta::Max();
- EXPECT_EQ(std::numeric_limits<int64_t>::max(), t.ToInternalValue());
+TEST(TimeDelta, ZeroMinMax) {
+ constexpr TimeDelta kZero;
+ static_assert(kZero.is_zero(), "");
- EXPECT_EQ(std::numeric_limits<int>::max(), t.InDays());
- EXPECT_EQ(std::numeric_limits<int>::max(), t.InHours());
- EXPECT_EQ(std::numeric_limits<int>::max(), t.InMinutes());
- EXPECT_EQ(std::numeric_limits<double>::infinity(), t.InSecondsF());
- EXPECT_EQ(std::numeric_limits<int64_t>::max(), t.InSeconds());
- EXPECT_EQ(std::numeric_limits<double>::infinity(), t.InMillisecondsF());
- EXPECT_EQ(std::numeric_limits<int64_t>::max(), t.InMilliseconds());
- EXPECT_EQ(std::numeric_limits<int64_t>::max(), t.InMillisecondsRoundedUp());
+ constexpr TimeDelta kMax = TimeDelta::Max();
+ static_assert(kMax.is_max(), "");
+ static_assert(kMax == TimeDelta::Max(), "");
+ static_assert(kMax > TimeDelta::FromDays(100 * 365), "");
+ static_assert(kMax > kZero, "");
- t = TimeDelta::FromDays(std::numeric_limits<int>::max());
- EXPECT_TRUE(t.is_max());
+ constexpr TimeDelta kMin = TimeDelta::Min();
+ static_assert(kMin.is_min(), "");
+ static_assert(kMin == TimeDelta::Min(), "");
+ static_assert(kMin < TimeDelta::FromDays(-100 * 365), "");
+ static_assert(kMin < kZero, "");
+}
- t = TimeDelta::FromHours(std::numeric_limits<int>::max());
- EXPECT_TRUE(t.is_max());
+TEST(TimeDelta, MaxConversions) {
+ // static_assert also confirms constexpr works as intended.
+ constexpr TimeDelta kMax = TimeDelta::Max();
+ static_assert(kMax.ToInternalValue() == std::numeric_limits<int64_t>::max(),
+ "");
+ EXPECT_EQ(kMax.InDays(), std::numeric_limits<int>::max());
+ EXPECT_EQ(kMax.InHours(), std::numeric_limits<int>::max());
+ EXPECT_EQ(kMax.InMinutes(), std::numeric_limits<int>::max());
+ EXPECT_EQ(kMax.InSecondsF(), std::numeric_limits<double>::infinity());
+ EXPECT_EQ(kMax.InSeconds(), std::numeric_limits<int64_t>::max());
+ EXPECT_EQ(kMax.InMillisecondsF(), std::numeric_limits<double>::infinity());
+ EXPECT_EQ(kMax.InMilliseconds(), std::numeric_limits<int64_t>::max());
+ EXPECT_EQ(kMax.InMillisecondsRoundedUp(), std::numeric_limits<int64_t>::max());
+
+ static_assert(TimeDelta::FromDays(std::numeric_limits<int>::max()).is_max(),
+ "");
- t = TimeDelta::FromMinutes(std::numeric_limits<int>::max());
- EXPECT_TRUE(t.is_max());
+ static_assert(TimeDelta::FromHours(std::numeric_limits<int>::max()).is_max(),
+ "");
- int64_t max_int = std::numeric_limits<int64_t>::max();
+ static_assert(
+ TimeDelta::FromMinutes(std::numeric_limits<int>::max()).is_max(), "");
- t = TimeDelta::FromSeconds(max_int / Time::kMicrosecondsPerSecond + 1);
- EXPECT_TRUE(t.is_max());
+ constexpr int64_t max_int = std::numeric_limits<int64_t>::max();
+ constexpr int64_t min_int = std::numeric_limits<int64_t>::min();
- t = TimeDelta::FromMilliseconds(max_int / Time::kMillisecondsPerSecond + 1);
- EXPECT_TRUE(t.is_max());
+ static_assert(
+ TimeDelta::FromSeconds(max_int / Time::kMicrosecondsPerSecond + 1)
+ .is_max(),
+ "");
- t = TimeDelta::FromMicroseconds(max_int);
- EXPECT_TRUE(t.is_max());
+ static_assert(
+ TimeDelta::FromMilliseconds(max_int / Time::kMillisecondsPerSecond + 1)
+ .is_max(),
+ "");
- t = TimeDelta::FromSeconds(-max_int / Time::kMicrosecondsPerSecond - 1);
- EXPECT_TRUE(IsMin(t));
+ static_assert(TimeDelta::FromMicroseconds(max_int).is_max(), "");
+
+ static_assert(
+ TimeDelta::FromSeconds(min_int / Time::kMicrosecondsPerSecond - 1)
+ .is_min(),
+ "");
- t = TimeDelta::FromMilliseconds(-max_int / Time::kMillisecondsPerSecond - 1);
- EXPECT_TRUE(IsMin(t));
+ static_assert(
+ TimeDelta::FromMilliseconds(min_int / Time::kMillisecondsPerSecond - 1)
+ .is_min(),
+ "");
- t = TimeDelta::FromMicroseconds(-max_int);
- EXPECT_TRUE(IsMin(t));
+ static_assert(TimeDelta::FromMicroseconds(min_int).is_min(), "");
- t = -TimeDelta::FromMicroseconds(std::numeric_limits<int64_t>::min());
- EXPECT_FALSE(IsMin(t));
+ static_assert(
+ TimeDelta::FromMicroseconds(std::numeric_limits<int64_t>::min()).is_min(),
+ "");
- t = TimeDelta::FromSecondsD(std::numeric_limits<double>::infinity());
- EXPECT_TRUE(t.is_max());
+ // Floating point arithmetic resulting in infinity isn't constexpr in C++14.
+ EXPECT_TRUE(TimeDelta::FromSecondsD(std::numeric_limits<double>::infinity())
+ .is_max());
- double max_d = max_int;
+ // Note that max_int/min_int will be rounded when converted to doubles - they
+ // can't be exactly represented.
+ constexpr double max_d = static_cast<double>(max_int);
+ constexpr double min_d = static_cast<double>(min_int);
- t = TimeDelta::FromSecondsD(max_d / Time::kMicrosecondsPerSecond + 1);
- EXPECT_TRUE(t.is_max());
+ static_assert(
+ TimeDelta::FromSecondsD(max_d / Time::kMicrosecondsPerSecond + 1)
+ .is_max(),
+ "");
- t = TimeDelta::FromMillisecondsD(std::numeric_limits<double>::infinity());
- EXPECT_TRUE(t.is_max());
+ // Floating point arithmetic resulting in infinity isn't constexpr in C++14.
+ EXPECT_TRUE(
+ TimeDelta::FromMillisecondsD(std::numeric_limits<double>::infinity())
+ .is_max());
- t = TimeDelta::FromMillisecondsD(max_d / Time::kMillisecondsPerSecond * 2);
- EXPECT_TRUE(t.is_max());
+ static_assert(
+ TimeDelta::FromMillisecondsD(max_d / Time::kMillisecondsPerSecond * 2)
+ .is_max(),
+ "");
- t = TimeDelta::FromSecondsD(-max_d / Time::kMicrosecondsPerSecond - 1);
- EXPECT_TRUE(IsMin(t));
+ static_assert(
+ TimeDelta::FromSecondsD(min_d / Time::kMicrosecondsPerSecond - 1)
+ .is_min(),
+ "");
- t = TimeDelta::FromMillisecondsD(-max_d / Time::kMillisecondsPerSecond * 2);
- EXPECT_TRUE(IsMin(t));
+ static_assert(
+ TimeDelta::FromMillisecondsD(min_d / Time::kMillisecondsPerSecond * 2)
+ .is_min(),
+ "");
}
TEST(TimeDelta, NumericOperators) {
- double d = 0.5;
+ constexpr double d = 0.5;
EXPECT_EQ(TimeDelta::FromMilliseconds(500),
- TimeDelta::FromMilliseconds(1000) * d);
- EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
- TimeDelta::FromMilliseconds(1000) / d);
+ (TimeDelta::FromMilliseconds(1000) * d));
+ static_assert(TimeDelta::FromMilliseconds(2000) ==
+ (TimeDelta::FromMilliseconds(1000) / d),
+ "");
EXPECT_EQ(TimeDelta::FromMilliseconds(500),
- TimeDelta::FromMilliseconds(1000) *= d);
- EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
- TimeDelta::FromMilliseconds(1000) /= d);
+ (TimeDelta::FromMilliseconds(1000) *= d));
+ static_assert(TimeDelta::FromMilliseconds(2000) ==
+ (TimeDelta::FromMilliseconds(1000) /= d),
+ "");
EXPECT_EQ(TimeDelta::FromMilliseconds(500),
- d * TimeDelta::FromMilliseconds(1000));
+ (d * TimeDelta::FromMilliseconds(1000)));
- float f = 0.5;
+ constexpr float f = 0.5;
EXPECT_EQ(TimeDelta::FromMilliseconds(500),
- TimeDelta::FromMilliseconds(1000) * f);
- EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
- TimeDelta::FromMilliseconds(1000) / f);
+ (TimeDelta::FromMilliseconds(1000) * f));
+ static_assert(TimeDelta::FromMilliseconds(2000) ==
+ (TimeDelta::FromMilliseconds(1000) / f),
+ "");
EXPECT_EQ(TimeDelta::FromMilliseconds(500),
- TimeDelta::FromMilliseconds(1000) *= f);
- EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
- TimeDelta::FromMilliseconds(1000) /= f);
+ (TimeDelta::FromMilliseconds(1000) *= f));
+ static_assert(TimeDelta::FromMilliseconds(2000) ==
+ (TimeDelta::FromMilliseconds(1000) /= f),
+ "");
EXPECT_EQ(TimeDelta::FromMilliseconds(500),
- f * TimeDelta::FromMilliseconds(1000));
+ (f * TimeDelta::FromMilliseconds(1000)));
- int i = 2;
+ constexpr int i = 2;
EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
- TimeDelta::FromMilliseconds(1000) * i);
- EXPECT_EQ(TimeDelta::FromMilliseconds(500),
- TimeDelta::FromMilliseconds(1000) / i);
+ (TimeDelta::FromMilliseconds(1000) * i));
+ static_assert(TimeDelta::FromMilliseconds(500) ==
+ (TimeDelta::FromMilliseconds(1000) / i),
+ "");
EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
- TimeDelta::FromMilliseconds(1000) *= i);
- EXPECT_EQ(TimeDelta::FromMilliseconds(500),
- TimeDelta::FromMilliseconds(1000) /= i);
+ (TimeDelta::FromMilliseconds(1000) *= i));
+ static_assert(TimeDelta::FromMilliseconds(500) ==
+ (TimeDelta::FromMilliseconds(1000) /= i),
+ "");
EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
- i * TimeDelta::FromMilliseconds(1000));
+ (i * TimeDelta::FromMilliseconds(1000)));
- int64_t i64 = 2;
+ constexpr int64_t i64 = 2;
EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
- TimeDelta::FromMilliseconds(1000) * i64);
- EXPECT_EQ(TimeDelta::FromMilliseconds(500),
- TimeDelta::FromMilliseconds(1000) / i64);
+ (TimeDelta::FromMilliseconds(1000) * i64));
+ static_assert(TimeDelta::FromMilliseconds(500) ==
+ (TimeDelta::FromMilliseconds(1000) / i64),
+ "");
EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
- TimeDelta::FromMilliseconds(1000) *= i64);
- EXPECT_EQ(TimeDelta::FromMilliseconds(500),
- TimeDelta::FromMilliseconds(1000) /= i64);
+ (TimeDelta::FromMilliseconds(1000) *= i64));
+ static_assert(TimeDelta::FromMilliseconds(500) ==
+ (TimeDelta::FromMilliseconds(1000) /= i64),
+ "");
EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
- i64 * TimeDelta::FromMilliseconds(1000));
+ (i64 * TimeDelta::FromMilliseconds(1000)));
EXPECT_EQ(TimeDelta::FromMilliseconds(500),
- TimeDelta::FromMilliseconds(1000) * 0.5);
- EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
- TimeDelta::FromMilliseconds(1000) / 0.5);
+ (TimeDelta::FromMilliseconds(1000) * 0.5));
+ static_assert(TimeDelta::FromMilliseconds(2000) ==
+ (TimeDelta::FromMilliseconds(1000) / 0.5),
+ "");
EXPECT_EQ(TimeDelta::FromMilliseconds(500),
- TimeDelta::FromMilliseconds(1000) *= 0.5);
- EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
- TimeDelta::FromMilliseconds(1000) /= 0.5);
+ (TimeDelta::FromMilliseconds(1000) *= 0.5));
+ static_assert(TimeDelta::FromMilliseconds(2000) ==
+ (TimeDelta::FromMilliseconds(1000) /= 0.5),
+ "");
EXPECT_EQ(TimeDelta::FromMilliseconds(500),
- 0.5 * TimeDelta::FromMilliseconds(1000));
+ (0.5 * TimeDelta::FromMilliseconds(1000)));
EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
- TimeDelta::FromMilliseconds(1000) * 2);
- EXPECT_EQ(TimeDelta::FromMilliseconds(500),
- TimeDelta::FromMilliseconds(1000) / 2);
+ (TimeDelta::FromMilliseconds(1000) * 2));
+ static_assert(TimeDelta::FromMilliseconds(500) ==
+ (TimeDelta::FromMilliseconds(1000) / 2),
+ "");
EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
- TimeDelta::FromMilliseconds(1000) *= 2);
- EXPECT_EQ(TimeDelta::FromMilliseconds(500),
- TimeDelta::FromMilliseconds(1000) /= 2);
+ (TimeDelta::FromMilliseconds(1000) *= 2));
+ static_assert(TimeDelta::FromMilliseconds(500) ==
+ (TimeDelta::FromMilliseconds(1000) /= 2),
+ "");
EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
- 2 * TimeDelta::FromMilliseconds(1000));
+ (2 * TimeDelta::FromMilliseconds(1000)));
+}
+
+// Basic test of operators between TimeDeltas (without overflow -- next test
+// handles overflow).
+TEST(TimeDelta, TimeDeltaOperators) {
+ constexpr TimeDelta kElevenSeconds = TimeDelta::FromSeconds(11);
+ constexpr TimeDelta kThreeSeconds = TimeDelta::FromSeconds(3);
+
+ EXPECT_EQ(TimeDelta::FromSeconds(14), kElevenSeconds + kThreeSeconds);
+ EXPECT_EQ(TimeDelta::FromSeconds(14), kThreeSeconds + kElevenSeconds);
+ EXPECT_EQ(TimeDelta::FromSeconds(8), kElevenSeconds - kThreeSeconds);
+ EXPECT_EQ(TimeDelta::FromSeconds(-8), kThreeSeconds - kElevenSeconds);
+ static_assert(3 == kElevenSeconds / kThreeSeconds, "");
+ static_assert(0 == kThreeSeconds / kElevenSeconds, "");
+ static_assert(TimeDelta::FromSeconds(2) == kElevenSeconds % kThreeSeconds,
+ "");
}
TEST(TimeDelta, Overflows) {
- // Some sanity checks.
- EXPECT_TRUE(TimeDelta::Max().is_max());
- EXPECT_TRUE(IsMin(-TimeDelta::Max()));
- EXPECT_GT(TimeDelta(), -TimeDelta::Max());
+ // Some sanity checks. static_assert's used were possible to verify constexpr
+ // evaluation at the same time.
+ static_assert(TimeDelta::Max().is_max(), "");
+ static_assert(-TimeDelta::Max() < TimeDelta(), "");
+ static_assert(-TimeDelta::Max() > TimeDelta::Min(), "");
+ static_assert(TimeDelta() > -TimeDelta::Max(), "");
TimeDelta large_delta = TimeDelta::Max() - TimeDelta::FromMilliseconds(1);
TimeDelta large_negative = -large_delta;
EXPECT_GT(TimeDelta(), large_negative);
EXPECT_FALSE(large_delta.is_max());
- EXPECT_FALSE(IsMin(-large_negative));
- TimeDelta one_second = TimeDelta::FromSeconds(1);
+ EXPECT_FALSE((-large_negative).is_min());
+ constexpr TimeDelta kOneSecond = TimeDelta::FromSeconds(1);
// Test +, -, * and / operators.
- EXPECT_TRUE((large_delta + one_second).is_max());
- EXPECT_TRUE(IsMin(large_negative + (-one_second)));
- EXPECT_TRUE(IsMin(large_negative - one_second));
- EXPECT_TRUE((large_delta - (-one_second)).is_max());
+ EXPECT_TRUE((large_delta + kOneSecond).is_max());
+ EXPECT_TRUE((large_negative + (-kOneSecond)).is_min());
+ EXPECT_TRUE((large_negative - kOneSecond).is_min());
+ EXPECT_TRUE((large_delta - (-kOneSecond)).is_max());
EXPECT_TRUE((large_delta * 2).is_max());
- EXPECT_TRUE(IsMin(large_delta * -2));
+ EXPECT_TRUE((large_delta * -2).is_min());
EXPECT_TRUE((large_delta / 0.5).is_max());
- EXPECT_TRUE(IsMin(large_delta / -0.5));
+ EXPECT_TRUE((large_delta / -0.5).is_min());
+
+ // Test that double conversions overflow to infinity.
+ EXPECT_EQ((large_delta + kOneSecond).InSecondsF(),
+ std::numeric_limits<double>::infinity());
+ EXPECT_EQ((large_delta + kOneSecond).InMillisecondsF(),
+ std::numeric_limits<double>::infinity());
+ EXPECT_EQ((large_delta + kOneSecond).InMicrosecondsF(),
+ std::numeric_limits<double>::infinity());
// Test +=, -=, *= and /= operators.
TimeDelta delta = large_delta;
- delta += one_second;
+ delta += kOneSecond;
EXPECT_TRUE(delta.is_max());
delta = large_negative;
- delta += -one_second;
- EXPECT_TRUE(IsMin(delta));
+ delta += -kOneSecond;
+ EXPECT_TRUE((delta).is_min());
delta = large_negative;
- delta -= one_second;
- EXPECT_TRUE(IsMin(delta));
+ delta -= kOneSecond;
+ EXPECT_TRUE((delta).is_min());
delta = large_delta;
- delta -= -one_second;
+ delta -= -kOneSecond;
EXPECT_TRUE(delta.is_max());
delta = large_delta;
@@ -1086,14 +1509,14 @@ TEST(TimeDelta, Overflows) {
EXPECT_TRUE(delta.is_max());
delta = large_negative;
delta *= 1.5;
- EXPECT_TRUE(IsMin(delta));
+ EXPECT_TRUE((delta).is_min());
delta = large_delta;
delta /= 0.5;
EXPECT_TRUE(delta.is_max());
delta = large_negative;
delta /= 0.5;
- EXPECT_TRUE(IsMin(delta));
+ EXPECT_TRUE((delta).is_min());
// Test operations with Time and TimeTicks.
EXPECT_TRUE((large_delta + Time::Now()).is_max());
@@ -1102,12 +1525,12 @@ TEST(TimeDelta, Overflows) {
EXPECT_TRUE((TimeTicks::Now() + large_delta).is_max());
Time time_now = Time::Now();
- EXPECT_EQ(one_second, (time_now + one_second) - time_now);
- EXPECT_EQ(-one_second, (time_now - one_second) - time_now);
+ EXPECT_EQ(kOneSecond, (time_now + kOneSecond) - time_now);
+ EXPECT_EQ(-kOneSecond, (time_now - kOneSecond) - time_now);
TimeTicks ticks_now = TimeTicks::Now();
- EXPECT_EQ(-one_second, (ticks_now - one_second) - ticks_now);
- EXPECT_EQ(one_second, (ticks_now + one_second) - ticks_now);
+ EXPECT_EQ(-kOneSecond, (ticks_now - kOneSecond) - ticks_now);
+ EXPECT_EQ(kOneSecond, (ticks_now + kOneSecond) - ticks_now);
}
TEST(TimeDeltaLogging, DCheckEqCompiles) {
@@ -1115,18 +1538,18 @@ TEST(TimeDeltaLogging, DCheckEqCompiles) {
}
TEST(TimeDeltaLogging, EmptyIsZero) {
- TimeDelta zero;
- EXPECT_EQ("0 s", AnyToString(zero));
+ constexpr TimeDelta kZero;
+ EXPECT_EQ("0 s", AnyToString(kZero));
}
TEST(TimeDeltaLogging, FiveHundredMs) {
- TimeDelta five_hundred_ms = TimeDelta::FromMilliseconds(500);
- EXPECT_EQ("0.5 s", AnyToString(five_hundred_ms));
+ constexpr TimeDelta kFiveHundredMs = TimeDelta::FromMilliseconds(500);
+ EXPECT_EQ("0.5 s", AnyToString(kFiveHundredMs));
}
TEST(TimeDeltaLogging, MinusTenSeconds) {
- TimeDelta minus_ten_seconds = TimeDelta::FromSeconds(-10);
- EXPECT_EQ("-10 s", AnyToString(minus_ten_seconds));
+ constexpr TimeDelta kMinusTenSeconds = TimeDelta::FromSeconds(-10);
+ EXPECT_EQ("-10 s", AnyToString(kMinusTenSeconds));
}
TEST(TimeDeltaLogging, DoesNotMessUpFormattingFlags) {
diff --git a/base/timer/elapsed_timer.cc b/base/timer/elapsed_timer.cc
index f2a2f7113e..ca86ccd4da 100644
--- a/base/timer/elapsed_timer.cc
+++ b/base/timer/elapsed_timer.cc
@@ -10,6 +10,14 @@ ElapsedTimer::ElapsedTimer() {
begin_ = TimeTicks::Now();
}
+ElapsedTimer::ElapsedTimer(ElapsedTimer&& other) {
+ begin_ = other.begin_;
+}
+
+void ElapsedTimer::operator=(ElapsedTimer&& other) {
+ begin_ = other.begin_;
+}
+
TimeDelta ElapsedTimer::Elapsed() const {
return TimeTicks::Now() - begin_;
}
diff --git a/base/timer/elapsed_timer.h b/base/timer/elapsed_timer.h
index 592858a6e5..9dfa12ccfe 100644
--- a/base/timer/elapsed_timer.h
+++ b/base/timer/elapsed_timer.h
@@ -15,6 +15,9 @@ namespace base {
class BASE_EXPORT ElapsedTimer {
public:
ElapsedTimer();
+ ElapsedTimer(ElapsedTimer&& other);
+
+ void operator=(ElapsedTimer&& other);
// Returns the time elapsed since object construction.
TimeDelta Elapsed() const;
diff --git a/base/timer/hi_res_timer_manager.h b/base/timer/hi_res_timer_manager.h
index 21cdfafb6c..bfa316d87c 100644
--- a/base/timer/hi_res_timer_manager.h
+++ b/base/timer/hi_res_timer_manager.h
@@ -7,7 +7,10 @@
#include "base/base_export.h"
#include "base/macros.h"
+#include "base/memory/ref_counted.h"
#include "base/power_monitor/power_observer.h"
+#include "base/timer/timer.h"
+#include "build/build_config.h"
namespace base {
@@ -18,8 +21,10 @@ class BASE_EXPORT HighResolutionTimerManager : public base::PowerObserver {
HighResolutionTimerManager();
~HighResolutionTimerManager() override;
- // base::PowerObserver method.
+ // base::PowerObserver methods.
void OnPowerStateChange(bool on_battery_power) override;
+ void OnSuspend() override;
+ void OnResume() override;
// Returns true if the hi resolution clock could be used right now.
bool hi_res_clock_available() const { return hi_res_clock_available_; }
@@ -30,6 +35,11 @@ class BASE_EXPORT HighResolutionTimerManager : public base::PowerObserver {
bool hi_res_clock_available_;
+#if defined(OS_WIN)
+ // Timer for polling the high resolution timer usage.
+ base::RepeatingTimer timer_;
+#endif
+
DISALLOW_COPY_AND_ASSIGN(HighResolutionTimerManager);
};
diff --git a/base/timer/hi_res_timer_manager_posix.cc b/base/timer/hi_res_timer_manager_posix.cc
index d2f152c8c0..d2a3aa56cf 100644
--- a/base/timer/hi_res_timer_manager_posix.cc
+++ b/base/timer/hi_res_timer_manager_posix.cc
@@ -12,12 +12,15 @@ HighResolutionTimerManager::HighResolutionTimerManager()
: hi_res_clock_available_(false) {
}
-HighResolutionTimerManager::~HighResolutionTimerManager() {
-}
+HighResolutionTimerManager::~HighResolutionTimerManager() = default;
void HighResolutionTimerManager::OnPowerStateChange(bool on_battery_power) {
}
+void HighResolutionTimerManager::OnSuspend() {}
+
+void HighResolutionTimerManager::OnResume() {}
+
void HighResolutionTimerManager::UseHiResClock(bool use) {
}
diff --git a/base/timer/hi_res_timer_manager_unittest.cc b/base/timer/hi_res_timer_manager_unittest.cc
index a0b0f9350c..43f607adb7 100644
--- a/base/timer/hi_res_timer_manager_unittest.cc
+++ b/base/timer/hi_res_timer_manager_unittest.cc
@@ -7,9 +7,9 @@
#include <memory>
#include <utility>
-#include "base/message_loop/message_loop.h"
#include "base/power_monitor/power_monitor.h"
#include "base/power_monitor/power_monitor_device_source.h"
+#include "base/test/scoped_task_environment.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -21,7 +21,8 @@ TEST(HiResTimerManagerTest, ToggleOnOff) {
// The power monitor creates Window to receive power notifications from
// Windows, which makes this test flaky if you run while the machine
// goes in or out of AC power.
- base::MessageLoop loop(base::MessageLoop::TYPE_UI);
+ test::ScopedTaskEnvironment scoped_task_environment(
+ test::ScopedTaskEnvironment::MainThreadType::UI);
std::unique_ptr<base::PowerMonitorSource> power_monitor_source(
new base::PowerMonitorDeviceSource());
std::unique_ptr<base::PowerMonitor> power_monitor(
diff --git a/base/timer/mock_timer.cc b/base/timer/mock_timer.cc
index 296071e8e3..e55bf14448 100644
--- a/base/timer/mock_timer.cc
+++ b/base/timer/mock_timer.cc
@@ -4,60 +4,80 @@
#include "base/timer/mock_timer.h"
+#include "base/test/test_simple_task_runner.h"
+
namespace base {
-MockTimer::MockTimer(bool retain_user_task, bool is_repeating)
- : Timer(retain_user_task, is_repeating),
- is_running_(false) {
+namespace {
+
+void FlushPendingTasks(TestSimpleTaskRunner* task_runner) {
+ // Do not use TestSimpleTaskRunner::RunPendingTasks() here. As RunPendingTasks
+ // overrides ThreadTaskRunnerHandle when it runs tasks, tasks posted by timer
+ // tasks to TTRH go to |test_task_runner_|, though they should be posted to
+ // the original task runner.
+ // Do not use TestSimpleTaskRunner::RunPendingTasks(), as its overridden
+ // ThreadTaskRunnerHandle causes unexpected side effects.
+ for (TestPendingTask& task : task_runner->TakePendingTasks())
+ std::move(task.task).Run();
+}
+
+} // namespace
+
+MockOneShotTimer::MockOneShotTimer()
+ : OneShotTimer(&clock_),
+ test_task_runner_(MakeRefCounted<TestSimpleTaskRunner>()) {
+ OneShotTimer::SetTaskRunner(test_task_runner_);
}
-MockTimer::MockTimer(const tracked_objects::Location& posted_from,
- TimeDelta delay,
- const base::Closure& user_task,
- bool is_repeating)
- : Timer(true, is_repeating),
- delay_(delay),
- is_running_(false) {
+MockOneShotTimer::~MockOneShotTimer() = default;
+
+void MockOneShotTimer::SetTaskRunner(
+ scoped_refptr<SequencedTaskRunner> task_runner) {
+ NOTREACHED() << "MockOneShotTimer doesn't support SetTaskRunner().";
}
-MockTimer::~MockTimer() {
+void MockOneShotTimer::Fire() {
+ DCHECK(IsRunning());
+ clock_.Advance(std::max(TimeDelta(), desired_run_time() - clock_.NowTicks()));
+ FlushPendingTasks(test_task_runner_.get());
}
-bool MockTimer::IsRunning() const {
- return is_running_;
+MockRepeatingTimer::MockRepeatingTimer()
+ : RepeatingTimer(&clock_),
+ test_task_runner_(MakeRefCounted<TestSimpleTaskRunner>()) {
+ RepeatingTimer::SetTaskRunner(test_task_runner_);
}
-base::TimeDelta MockTimer::GetCurrentDelay() const {
- return delay_;
+MockRepeatingTimer::~MockRepeatingTimer() = default;
+
+void MockRepeatingTimer::SetTaskRunner(
+ scoped_refptr<SequencedTaskRunner> task_runner) {
+ NOTREACHED() << "MockRepeatingTimer doesn't support SetTaskRunner().";
}
-void MockTimer::Start(const tracked_objects::Location& posted_from,
- TimeDelta delay,
- const base::Closure& user_task) {
- delay_ = delay;
- user_task_ = user_task;
- Reset();
+void MockRepeatingTimer::Fire() {
+ DCHECK(IsRunning());
+ clock_.Advance(std::max(TimeDelta(), desired_run_time() - clock_.NowTicks()));
+ FlushPendingTasks(test_task_runner_.get());
}
-void MockTimer::Stop() {
- is_running_ = false;
- if (!retain_user_task())
- user_task_.Reset();
+MockRetainingOneShotTimer::MockRetainingOneShotTimer()
+ : RetainingOneShotTimer(&clock_),
+ test_task_runner_(MakeRefCounted<TestSimpleTaskRunner>()) {
+ RetainingOneShotTimer::SetTaskRunner(test_task_runner_);
}
-void MockTimer::Reset() {
- DCHECK(!user_task_.is_null());
- is_running_ = true;
+MockRetainingOneShotTimer::~MockRetainingOneShotTimer() = default;
+
+void MockRetainingOneShotTimer::SetTaskRunner(
+ scoped_refptr<SequencedTaskRunner> task_runner) {
+ NOTREACHED() << "MockRetainingOneShotTimer doesn't support SetTaskRunner().";
}
-void MockTimer::Fire() {
- DCHECK(is_running_);
- base::Closure old_task = user_task_;
- if (is_repeating())
- Reset();
- else
- Stop();
- old_task.Run();
+void MockRetainingOneShotTimer::Fire() {
+ DCHECK(IsRunning());
+ clock_.Advance(std::max(TimeDelta(), desired_run_time() - clock_.NowTicks()));
+ FlushPendingTasks(test_task_runner_.get());
}
} // namespace base
diff --git a/base/timer/mock_timer.h b/base/timer/mock_timer.h
index e18a5c0489..a2263d4e78 100644
--- a/base/timer/mock_timer.h
+++ b/base/timer/mock_timer.h
@@ -5,35 +5,70 @@
#ifndef BASE_TIMER_MOCK_TIMER_H_
#define BASE_TIMER_MOCK_TIMER_H_
+#include "base/test/simple_test_tick_clock.h"
#include "base/timer/timer.h"
namespace base {
-class BASE_EXPORT MockTimer : public Timer {
+class TestSimpleTaskRunner;
+
+// A mock implementation of base::Timer which requires being explicitly
+// Fire()'d.
+// Prefer using ScopedTaskEnvironment::MOCK_TIME + FastForward*() to this when
+// possible.
+class MockOneShotTimer : public OneShotTimer {
+ public:
+ MockOneShotTimer();
+ ~MockOneShotTimer() override;
+
+ // Testing method.
+ void Fire();
+
+ private:
+ // Timer implementation.
+ // MockOneShotTimer doesn't support SetTaskRunner. Do not use this.
+ void SetTaskRunner(scoped_refptr<SequencedTaskRunner> task_runner) override;
+
+ SimpleTestTickClock clock_;
+ scoped_refptr<TestSimpleTaskRunner> test_task_runner_;
+};
+
+// See MockOneShotTimer's comment. Prefer using
+// ScopedTaskEnvironment::MOCK_TIME.
+class MockRepeatingTimer : public RepeatingTimer {
public:
- MockTimer(bool retain_user_task, bool is_repeating);
- MockTimer(const tracked_objects::Location& posted_from,
- TimeDelta delay,
- const base::Closure& user_task,
- bool is_repeating);
- ~MockTimer() override;
-
- // base::Timer implementation.
- bool IsRunning() const override;
- base::TimeDelta GetCurrentDelay() const override;
- void Start(const tracked_objects::Location& posted_from,
- base::TimeDelta delay,
- const base::Closure& user_task) override;
- void Stop() override;
- void Reset() override;
-
- // Testing methods.
+ MockRepeatingTimer();
+ ~MockRepeatingTimer() override;
+
+ // Testing method.
void Fire();
private:
- base::Closure user_task_;
- TimeDelta delay_;
- bool is_running_;
+ // Timer implementation.
+ // MockRepeatingTimer doesn't support SetTaskRunner. Do not use this.
+ void SetTaskRunner(scoped_refptr<SequencedTaskRunner> task_runner) override;
+
+ SimpleTestTickClock clock_;
+ scoped_refptr<TestSimpleTaskRunner> test_task_runner_;
+};
+
+// See MockOneShotTimer's comment. Prefer using
+// ScopedTaskEnvironment::MOCK_TIME.
+class MockRetainingOneShotTimer : public RetainingOneShotTimer {
+ public:
+ MockRetainingOneShotTimer();
+ ~MockRetainingOneShotTimer() override;
+
+ // Testing method.
+ void Fire();
+
+ private:
+ // Timer implementation.
+ // MockRetainingOneShotTimer doesn't support SetTaskRunner. Do not use this.
+ void SetTaskRunner(scoped_refptr<SequencedTaskRunner> task_runner) override;
+
+ SimpleTestTickClock clock_;
+ scoped_refptr<TestSimpleTaskRunner> test_task_runner_;
};
} // namespace base
diff --git a/base/timer/mock_timer_unittest.cc b/base/timer/mock_timer_unittest.cc
index a38981513a..45d0388e65 100644
--- a/base/timer/mock_timer_unittest.cc
+++ b/base/timer/mock_timer_unittest.cc
@@ -15,7 +15,7 @@ void CallMeMaybe(int *number) {
TEST(MockTimerTest, FiresOnce) {
int calls = 0;
- base::MockTimer timer(false, false);
+ base::MockOneShotTimer timer;
base::TimeDelta delay = base::TimeDelta::FromSeconds(2);
timer.Start(FROM_HERE, delay,
base::Bind(&CallMeMaybe,
@@ -29,7 +29,7 @@ TEST(MockTimerTest, FiresOnce) {
TEST(MockTimerTest, FiresRepeatedly) {
int calls = 0;
- base::MockTimer timer(true, true);
+ base::MockRepeatingTimer timer;
base::TimeDelta delay = base::TimeDelta::FromSeconds(2);
timer.Start(FROM_HERE, delay,
base::Bind(&CallMeMaybe,
@@ -44,7 +44,7 @@ TEST(MockTimerTest, FiresRepeatedly) {
TEST(MockTimerTest, Stops) {
int calls = 0;
- base::MockTimer timer(true, true);
+ base::MockRepeatingTimer timer;
base::TimeDelta delay = base::TimeDelta::FromSeconds(2);
timer.Start(FROM_HERE, delay,
base::Bind(&CallMeMaybe,
@@ -56,24 +56,21 @@ TEST(MockTimerTest, Stops) {
class HasWeakPtr : public base::SupportsWeakPtr<HasWeakPtr> {
public:
- HasWeakPtr() {}
- virtual ~HasWeakPtr() {}
+ HasWeakPtr() = default;
+ virtual ~HasWeakPtr() = default;
private:
DISALLOW_COPY_AND_ASSIGN(HasWeakPtr);
};
-void DoNothingWithWeakPtr(HasWeakPtr* has_weak_ptr) {
-}
-
TEST(MockTimerTest, DoesNotRetainClosure) {
HasWeakPtr *has_weak_ptr = new HasWeakPtr();
base::WeakPtr<HasWeakPtr> weak_ptr(has_weak_ptr->AsWeakPtr());
- base::MockTimer timer(false, false);
+ base::MockOneShotTimer timer;
base::TimeDelta delay = base::TimeDelta::FromSeconds(2);
ASSERT_TRUE(weak_ptr.get());
timer.Start(FROM_HERE, delay,
- base::Bind(&DoNothingWithWeakPtr,
+ base::Bind(base::DoNothing::Repeatedly<HasWeakPtr*>(),
base::Owned(has_weak_ptr)));
ASSERT_TRUE(weak_ptr.get());
timer.Fire();
diff --git a/base/timer/timer.cc b/base/timer/timer.cc
index 6ec18f1814..0c269713a0 100644
--- a/base/timer/timer.cc
+++ b/base/timer/timer.cc
@@ -11,16 +11,14 @@
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
-#include "base/single_thread_task_runner.h"
#include "base/threading/platform_thread.h"
-#include "base/threading/thread_task_runner_handle.h"
+#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/tick_clock.h"
namespace base {
-// BaseTimerTaskInternal is a simple delegate for scheduling a callback to
-// Timer in the thread's default task runner. It also handles the following
-// edge cases:
+// BaseTimerTaskInternal is a simple delegate for scheduling a callback to Timer
+// on the current sequence. It also handles the following edge cases:
// - deleted by the task runner.
// - abandoned (orphaned) by Timer.
class BaseTimerTaskInternal {
@@ -34,99 +32,131 @@ class BaseTimerTaskInternal {
// destructed. If so, don't leave Timer with a dangling pointer
// to this.
if (timer_)
- timer_->StopAndAbandon();
+ timer_->AbandonAndStop();
}
void Run() {
- // timer_ is NULL if we were abandoned.
+ // |timer_| is nullptr if we were abandoned.
if (!timer_)
return;
- // *this will be deleted by the task runner, so Timer needs to
- // forget us:
- timer_->scheduled_task_ = NULL;
+ // |this| will be deleted by the task runner, so Timer needs to forget us:
+ timer_->scheduled_task_ = nullptr;
- // Although Timer should not call back into *this, let's clear
- // the timer_ member first to be pedantic.
+ // Although Timer should not call back into |this|, let's clear |timer_|
+ // first to be pedantic.
Timer* timer = timer_;
- timer_ = NULL;
+ timer_ = nullptr;
timer->RunScheduledTask();
}
- // The task remains in the MessageLoop queue, but nothing will happen when it
- // runs.
- void Abandon() {
- timer_ = NULL;
- }
+ // The task remains in the queue, but nothing will happen when it runs.
+ void Abandon() { timer_ = nullptr; }
private:
Timer* timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(BaseTimerTaskInternal);
};
Timer::Timer(bool retain_user_task, bool is_repeating)
: Timer(retain_user_task, is_repeating, nullptr) {}
-Timer::Timer(bool retain_user_task, bool is_repeating, TickClock* tick_clock)
+Timer::Timer(bool retain_user_task,
+ bool is_repeating,
+ const TickClock* tick_clock)
: scheduled_task_(nullptr),
- thread_id_(0),
is_repeating_(is_repeating),
retain_user_task_(retain_user_task),
tick_clock_(tick_clock),
- is_running_(false) {}
+ is_running_(false) {
+ // It is safe for the timer to be created on a different thread/sequence than
+ // the one from which the timer APIs are called. The first call to the
+ // checker's CalledOnValidSequence() method will re-bind the checker, and
+ // later calls will verify that the same task runner is used.
+ origin_sequence_checker_.DetachFromSequence();
+}
-Timer::Timer(const tracked_objects::Location& posted_from,
+Timer::Timer(const Location& posted_from,
TimeDelta delay,
const base::Closure& user_task,
bool is_repeating)
: Timer(posted_from, delay, user_task, is_repeating, nullptr) {}
-Timer::Timer(const tracked_objects::Location& posted_from,
+Timer::Timer(const Location& posted_from,
TimeDelta delay,
const base::Closure& user_task,
bool is_repeating,
- TickClock* tick_clock)
+ const TickClock* tick_clock)
: scheduled_task_(nullptr),
posted_from_(posted_from),
delay_(delay),
user_task_(user_task),
- thread_id_(0),
is_repeating_(is_repeating),
retain_user_task_(true),
tick_clock_(tick_clock),
- is_running_(false) {}
+ is_running_(false) {
+ // See comment in other constructor.
+ origin_sequence_checker_.DetachFromSequence();
+}
Timer::~Timer() {
- StopAndAbandon();
+ DCHECK(origin_sequence_checker_.CalledOnValidSequence());
+ AbandonAndStop();
}
bool Timer::IsRunning() const {
+ DCHECK(origin_sequence_checker_.CalledOnValidSequence());
return is_running_;
}
TimeDelta Timer::GetCurrentDelay() const {
+ DCHECK(origin_sequence_checker_.CalledOnValidSequence());
return delay_;
}
-void Timer::SetTaskRunner(scoped_refptr<SingleThreadTaskRunner> task_runner) {
- // Do not allow changing the task runner once something has been scheduled.
- DCHECK_EQ(thread_id_, 0);
+void Timer::SetTaskRunner(scoped_refptr<SequencedTaskRunner> task_runner) {
+ // Do not allow changing the task runner when the Timer is running.
+ // Don't check for |origin_sequence_checker_.CalledOnValidSequence()| here to
+ // allow the use case of constructing the Timer and immediatetly invoking
+ // SetTaskRunner() before starting it (CalledOnValidSequence() would undo the
+ // DetachFromSequence() from the constructor). The |!is_running| check kind of
+ // verifies the same thing (and TSAN should catch callers that do it wrong but
+ // somehow evade all debug checks).
+ DCHECK(!is_running_);
task_runner_.swap(task_runner);
}
-void Timer::Start(const tracked_objects::Location& posted_from,
+void Timer::Start(const Location& posted_from,
TimeDelta delay,
const base::Closure& user_task) {
- SetTaskInfo(posted_from, delay, user_task);
+ DCHECK(origin_sequence_checker_.CalledOnValidSequence());
+
+ posted_from_ = posted_from;
+ delay_ = delay;
+ user_task_ = user_task;
+
Reset();
}
void Timer::Stop() {
+ // TODO(gab): Enable this when it's no longer called racily from
+ // RunScheduledTask(): https://crbug.com/587199.
+ // DCHECK(origin_sequence_checker_.CalledOnValidSequence());
+
is_running_ = false;
+
+ // It's safe to destroy or restart Timer on another sequence after Stop().
+ origin_sequence_checker_.DetachFromSequence();
+
if (!retain_user_task_)
user_task_.Reset();
+ // No more member accesses here: |this| could be deleted after freeing
+ // |user_task_|.
}
void Timer::Reset() {
+ DCHECK(origin_sequence_checker_.CalledOnValidSequence());
DCHECK(!user_task_.is_null());
// If there's no pending task, start one up and return.
@@ -135,70 +165,74 @@ void Timer::Reset() {
return;
}
- // Set the new desired_run_time_.
+ // Set the new |desired_run_time_|.
if (delay_ > TimeDelta::FromMicroseconds(0))
desired_run_time_ = Now() + delay_;
else
desired_run_time_ = TimeTicks();
// We can use the existing scheduled task if it arrives before the new
- // desired_run_time_.
+ // |desired_run_time_|.
if (desired_run_time_ >= scheduled_run_time_) {
is_running_ = true;
return;
}
- // We can't reuse the scheduled_task_, so abandon it and post a new one.
+ // We can't reuse the |scheduled_task_|, so abandon it and post a new one.
AbandonScheduledTask();
PostNewScheduledTask(delay_);
}
TimeTicks Timer::Now() const {
+ // TODO(gab): Enable this when it's no longer called racily from
+ // RunScheduledTask(): https://crbug.com/587199.
+ // DCHECK(origin_sequence_checker_.CalledOnValidSequence());
return tick_clock_ ? tick_clock_->NowTicks() : TimeTicks::Now();
}
-void Timer::SetTaskInfo(const tracked_objects::Location& posted_from,
- TimeDelta delay,
- const base::Closure& user_task) {
- posted_from_ = posted_from;
- delay_ = delay;
- user_task_ = user_task;
-}
-
void Timer::PostNewScheduledTask(TimeDelta delay) {
- DCHECK(scheduled_task_ == NULL);
+ // TODO(gab): Enable this when it's no longer called racily from
+ // RunScheduledTask(): https://crbug.com/587199.
+ // DCHECK(origin_sequence_checker_.CalledOnValidSequence());
+ DCHECK(!scheduled_task_);
is_running_ = true;
scheduled_task_ = new BaseTimerTaskInternal(this);
if (delay > TimeDelta::FromMicroseconds(0)) {
- GetTaskRunner()->PostDelayedTask(posted_from_,
- base::Bind(&BaseTimerTaskInternal::Run, base::Owned(scheduled_task_)),
+ // TODO(gab): Posting BaseTimerTaskInternal::Run to another sequence makes
+ // this code racy. https://crbug.com/587199
+ GetTaskRunner()->PostDelayedTask(
+ posted_from_,
+ base::BindOnce(&BaseTimerTaskInternal::Run,
+ base::Owned(scheduled_task_)),
delay);
scheduled_run_time_ = desired_run_time_ = Now() + delay;
} else {
GetTaskRunner()->PostTask(posted_from_,
- base::Bind(&BaseTimerTaskInternal::Run, base::Owned(scheduled_task_)));
+ base::BindOnce(&BaseTimerTaskInternal::Run,
+ base::Owned(scheduled_task_)));
scheduled_run_time_ = desired_run_time_ = TimeTicks();
}
- // Remember the thread ID that posts the first task -- this will be verified
- // later when the task is abandoned to detect misuse from multiple threads.
- if (!thread_id_)
- thread_id_ = static_cast<int>(PlatformThread::CurrentId());
}
-scoped_refptr<SingleThreadTaskRunner> Timer::GetTaskRunner() {
- return task_runner_.get() ? task_runner_ : ThreadTaskRunnerHandle::Get();
+scoped_refptr<SequencedTaskRunner> Timer::GetTaskRunner() {
+ return task_runner_.get() ? task_runner_ : SequencedTaskRunnerHandle::Get();
}
void Timer::AbandonScheduledTask() {
- DCHECK(thread_id_ == 0 ||
- thread_id_ == static_cast<int>(PlatformThread::CurrentId()));
+ // TODO(gab): Enable this when it's no longer called racily from
+ // RunScheduledTask() -> Stop(): https://crbug.com/587199.
+ // DCHECK(origin_sequence_checker_.CalledOnValidSequence());
if (scheduled_task_) {
scheduled_task_->Abandon();
- scheduled_task_ = NULL;
+ scheduled_task_ = nullptr;
}
}
void Timer::RunScheduledTask() {
+ // TODO(gab): Enable this when it's no longer called racily:
+ // https://crbug.com/587199.
+ // DCHECK(origin_sequence_checker_.CalledOnValidSequence());
+
// Task may have been disabled.
if (!is_running_)
return;
@@ -206,10 +240,10 @@ void Timer::RunScheduledTask() {
// First check if we need to delay the task because of a new target time.
if (desired_run_time_ > scheduled_run_time_) {
// Now() can be expensive, so only call it if we know the user has changed
- // the desired_run_time_.
+ // the |desired_run_time_|.
TimeTicks now = Now();
// Task runner may have called us late anyway, so only post a continuation
- // task if the desired_run_time_ is in the future.
+ // task if the |desired_run_time_| is in the future.
if (desired_run_time_ > now) {
// Post a new task to span the remaining time.
PostNewScheduledTask(desired_run_time_ - now);
@@ -218,7 +252,7 @@ void Timer::RunScheduledTask() {
}
// Make a local copy of the task to run. The Stop method will reset the
- // user_task_ member if retain_user_task_ is false.
+ // |user_task_| member if |retain_user_task_| is false.
base::Closure task = user_task_;
if (is_repeating_)
@@ -228,7 +262,18 @@ void Timer::RunScheduledTask() {
task.Run();
- // No more member accesses here: *this could be deleted at this point.
+ // No more member accesses here: |this| could be deleted at this point.
+}
+
+void OneShotTimer::FireNow() {
+ DCHECK(origin_sequence_checker_.CalledOnValidSequence());
+ DCHECK(!task_runner_) << "FireNow() is incompatible with SetTaskRunner()";
+ DCHECK(IsRunning());
+
+ OnceClosure task = user_task();
+ Stop();
+ DCHECK(!user_task());
+ std::move(task).Run();
}
} // namespace base
diff --git a/base/timer/timer.h b/base/timer/timer.h
index 8aac279def..14ba3e3fef 100644
--- a/base/timer/timer.h
+++ b/base/timer/timer.h
@@ -35,11 +35,23 @@
// Both OneShotTimer and RepeatingTimer also support a Reset method, which
// allows you to easily defer the timer event until the timer delay passes once
// again. So, in the above example, if 0.5 seconds have already passed,
-// calling Reset on timer_ would postpone DoStuff by another 1 second. In
+// calling Reset on |timer_| would postpone DoStuff by another 1 second. In
// other words, Reset is shorthand for calling Stop and then Start again with
// the same arguments.
//
-// NOTE: These APIs are not thread safe. Always call from the same thread.
+// These APIs are not thread safe. All methods must be called from the same
+// sequence (not necessarily the construction sequence), except for the
+// destructor and SetTaskRunner().
+// - The destructor may be called from any sequence when the timer is not
+// running and there is no scheduled task active, i.e. when Start() has never
+// been called or after AbandonAndStop() has been called.
+// - SetTaskRunner() may be called from any sequence when the timer is not
+// running, i.e. when Start() has never been called or Stop() has been called
+// since the last Start().
+//
+// By default, the scheduled tasks will be run on the same sequence that the
+// Timer was *started on*, but this can be changed *prior* to Start() via
+// SetTaskRunner().
#ifndef BASE_TIMER_TIMER_H_
#define BASE_TIMER_TIMER_H_
@@ -57,65 +69,86 @@
#include "base/callback.h"
#include "base/location.h"
#include "base/macros.h"
+#include "base/sequence_checker_impl.h"
+#include "base/sequenced_task_runner.h"
#include "base/time/time.h"
namespace base {
class BaseTimerTaskInternal;
-class SingleThreadTaskRunner;
class TickClock;
//-----------------------------------------------------------------------------
-// This class wraps MessageLoop::PostDelayedTask to manage delayed and repeating
-// tasks. It must be destructed on the same thread that starts tasks. There are
-// DCHECKs in place to verify this.
+// This class wraps TaskRunner::PostDelayedTask to manage delayed and repeating
+// tasks. See meta comment above for thread-safety requirements.
//
class BASE_EXPORT Timer {
public:
- // Construct a timer in repeating or one-shot mode. Start or SetTaskInfo must
- // be called later to set task info. |retain_user_task| determines whether the
- // user_task is retained or reset when it runs or stops. If |tick_clock| is
- // provided, it is used instead of TimeTicks::Now() to get TimeTicks when
- // scheduling tasks.
+ // Construct a timer in repeating or one-shot mode. Start must be called later
+ // to set task info. |retain_user_task| determines whether the user_task is
+ // retained or reset when it runs or stops. If |tick_clock| is provided, it is
+ // used instead of TimeTicks::Now() to get TimeTicks when scheduling tasks.
Timer(bool retain_user_task, bool is_repeating);
- Timer(bool retain_user_task, bool is_repeating, TickClock* tick_clock);
+ Timer(bool retain_user_task, bool is_repeating, const TickClock* tick_clock);
// Construct a timer with retained task info. If |tick_clock| is provided, it
// is used instead of TimeTicks::Now() to get TimeTicks when scheduling tasks.
- Timer(const tracked_objects::Location& posted_from,
+ Timer(const Location& posted_from,
TimeDelta delay,
const base::Closure& user_task,
bool is_repeating);
- Timer(const tracked_objects::Location& posted_from,
+ Timer(const Location& posted_from,
TimeDelta delay,
const base::Closure& user_task,
bool is_repeating,
- TickClock* tick_clock);
+ const TickClock* tick_clock);
virtual ~Timer();
// Returns true if the timer is running (i.e., not stopped).
- virtual bool IsRunning() const;
+ bool IsRunning() const;
// Returns the current delay for this timer.
- virtual TimeDelta GetCurrentDelay() const;
+ TimeDelta GetCurrentDelay() const;
// Set the task runner on which the task should be scheduled. This method can
- // only be called before any tasks have been scheduled. The task runner must
- // run tasks on the same thread the timer is used on.
- virtual void SetTaskRunner(scoped_refptr<SingleThreadTaskRunner> task_runner);
+ // only be called before any tasks have been scheduled. If |task_runner| runs
+ // tasks on a different sequence than the sequence owning this Timer,
+ // |user_task_| will be posted to it when the Timer fires (note that this
+ // means |user_task_| can run after ~Timer() and should support that).
+ virtual void SetTaskRunner(scoped_refptr<SequencedTaskRunner> task_runner);
// Start the timer to run at the given |delay| from now. If the timer is
// already running, it will be replaced to call the given |user_task|.
- virtual void Start(const tracked_objects::Location& posted_from,
+ virtual void Start(const Location& posted_from,
TimeDelta delay,
const base::Closure& user_task);
+ // Start the timer to run at the given |delay| from now. If the timer is
+ // already running, it will be replaced to call a task formed from
+ // |reviewer->*method|.
+ template <class Receiver>
+ void Start(const Location& posted_from,
+ TimeDelta delay,
+ Receiver* receiver,
+ void (Receiver::*method)()) {
+ Start(posted_from, delay,
+ base::BindRepeating(method, base::Unretained(receiver)));
+ }
+
// Call this method to stop and cancel the timer. It is a no-op if the timer
// is not running.
virtual void Stop();
- // Call this method to reset the timer delay. The user_task_ must be set. If
+ // Stop running task (if any) and abandon scheduled task (if any).
+ void AbandonAndStop() {
+ AbandonScheduledTask();
+
+ Stop();
+ // No more member accesses here: |this| could be deleted at this point.
+ }
+
+ // Call this method to reset the timer delay. The |user_task_| must be set. If
// the timer is not running, this will start it by posting a task.
virtual void Reset();
@@ -126,148 +159,137 @@ class BASE_EXPORT Timer {
// Returns the current tick count.
TimeTicks Now() const;
- // Used to initiate a new delayed task. This has the side-effect of disabling
- // scheduled_task_ if it is non-null.
- void SetTaskInfo(const tracked_objects::Location& posted_from,
- TimeDelta delay,
- const base::Closure& user_task);
-
void set_user_task(const Closure& task) { user_task_ = task; }
void set_desired_run_time(TimeTicks desired) { desired_run_time_ = desired; }
void set_is_running(bool running) { is_running_ = running; }
- const tracked_objects::Location& posted_from() const { return posted_from_; }
- bool retain_user_task() const { return retain_user_task_; }
- bool is_repeating() const { return is_repeating_; }
- bool is_running() const { return is_running_; }
+ const Location& posted_from() const { return posted_from_; }
+
+ // The task runner on which the task should be scheduled. If it is null, the
+ // task runner for the current sequence will be used.
+ scoped_refptr<SequencedTaskRunner> task_runner_;
+
+ // Timer isn't thread-safe and must only be used on its origin sequence
+ // (sequence on which it was started). Once fully Stop()'ed it may be
+ // destroyed or restarted on another sequence.
+ SequenceChecker origin_sequence_checker_;
private:
friend class BaseTimerTaskInternal;
- // Allocates a new scheduled_task_ and posts it on the current MessageLoop
- // with the given |delay|. scheduled_task_ must be NULL. scheduled_run_time_
- // and desired_run_time_ are reset to Now() + delay.
+ // Allocates a new |scheduled_task_| and posts it on the current sequence with
+ // the given |delay|. |scheduled_task_| must be null. |scheduled_run_time_|
+ // and |desired_run_time_| are reset to Now() + delay.
void PostNewScheduledTask(TimeDelta delay);
// Returns the task runner on which the task should be scheduled. If the
- // corresponding task_runner_ field is null, the task runner for the current
- // thread is returned.
- scoped_refptr<SingleThreadTaskRunner> GetTaskRunner();
+ // corresponding |task_runner_| field is null, the task runner for the current
+ // sequence is returned.
+ scoped_refptr<SequencedTaskRunner> GetTaskRunner();
- // Disable scheduled_task_ and abandon it so that it no longer refers back to
- // this object.
+ // Disable |scheduled_task_| and abandon it so that it no longer refers back
+ // to this object.
void AbandonScheduledTask();
- // Called by BaseTimerTaskInternal when the MessageLoop runs it.
+ // Called by BaseTimerTaskInternal when the delayed task fires.
void RunScheduledTask();
- // Stop running task (if any) and abandon scheduled task (if any).
- void StopAndAbandon() {
- AbandonScheduledTask();
-
- Stop();
- // No more member accesses here: |this| could be deleted at this point.
- }
-
- // When non-NULL, the scheduled_task_ is waiting in the MessageLoop to call
- // RunScheduledTask() at scheduled_run_time_.
+ // When non-null, the |scheduled_task_| was posted to call RunScheduledTask()
+ // at |scheduled_run_time_|.
BaseTimerTaskInternal* scheduled_task_;
- // The task runner on which the task should be scheduled. If it is null, the
- // task runner for the current thread should be used.
- scoped_refptr<SingleThreadTaskRunner> task_runner_;
-
// Location in user code.
- tracked_objects::Location posted_from_;
+ Location posted_from_;
// Delay requested by user.
TimeDelta delay_;
- // user_task_ is what the user wants to be run at desired_run_time_.
+ // |user_task_| is what the user wants to be run at |desired_run_time_|.
base::Closure user_task_;
- // The estimated time that the MessageLoop will run the scheduled_task_ that
- // will call RunScheduledTask(). This time can be a "zero" TimeTicks if the
- // task must be run immediately.
+ // The time at which |scheduled_task_| is expected to fire. This time can be a
+ // "zero" TimeTicks if the task must be run immediately.
TimeTicks scheduled_run_time_;
- // The desired run time of user_task_. The user may update this at any time,
- // even if their previous request has not run yet. If desired_run_time_ is
- // greater than scheduled_run_time_, a continuation task will be posted to
+ // The desired run time of |user_task_|. The user may update this at any time,
+ // even if their previous request has not run yet. If |desired_run_time_| is
+ // greater than |scheduled_run_time_|, a continuation task will be posted to
// wait for the remaining time. This allows us to reuse the pending task so as
- // not to flood the MessageLoop with orphaned tasks when the user code
+ // not to flood the delayed queues with orphaned tasks when the user code
// excessively Stops and Starts the timer. This time can be a "zero" TimeTicks
// if the task must be run immediately.
TimeTicks desired_run_time_;
- // Thread ID of current MessageLoop for verifying single-threaded usage.
- int thread_id_;
-
// Repeating timers automatically post the task again before calling the task
// callback.
const bool is_repeating_;
- // If true, hold on to the user_task_ closure object for reuse.
+ // If true, hold on to the |user_task_| closure object for reuse.
const bool retain_user_task_;
// The tick clock used to calculate the run time for scheduled tasks.
- TickClock* const tick_clock_;
+ const TickClock* const tick_clock_;
- // If true, user_task_ is scheduled to run sometime in the future.
+ // If true, |user_task_| is scheduled to run sometime in the future.
bool is_running_;
DISALLOW_COPY_AND_ASSIGN(Timer);
};
//-----------------------------------------------------------------------------
-// This class is an implementation detail of OneShotTimer and RepeatingTimer.
-// Please do not use this class directly.
-class BaseTimerMethodPointer : public Timer {
+// A simple, one-shot timer. See usage notes at the top of the file.
+class BASE_EXPORT OneShotTimer : public Timer {
public:
- // This is here to work around the fact that Timer::Start is "hidden" by the
- // Start definition below, rather than being overloaded.
- // TODO(tim): We should remove uses of BaseTimerMethodPointer::Start below
- // and convert callers to use the base::Closure version in Timer::Start,
- // see bug 148832.
- using Timer::Start;
-
- enum RepeatMode { ONE_SHOT, REPEATING };
- BaseTimerMethodPointer(RepeatMode mode, TickClock* tick_clock)
- : Timer(mode == REPEATING, mode == REPEATING, tick_clock) {}
+ OneShotTimer() : OneShotTimer(nullptr) {}
+ explicit OneShotTimer(const TickClock* tick_clock)
+ : Timer(false, false, tick_clock) {}
- // Start the timer to run at the given |delay| from now. If the timer is
- // already running, it will be replaced to call a task formed from
- // |reviewer->*method|.
- template <class Receiver>
- void Start(const tracked_objects::Location& posted_from,
- TimeDelta delay,
- Receiver* receiver,
- void (Receiver::*method)()) {
- Timer::Start(posted_from, delay,
- base::Bind(method, base::Unretained(receiver)));
- }
+ // Run the scheduled task immediately, and stop the timer. The timer needs to
+ // be running.
+ void FireNow();
};
//-----------------------------------------------------------------------------
-// A simple, one-shot timer. See usage notes at the top of the file.
-class OneShotTimer : public BaseTimerMethodPointer {
+// A simple, repeating timer. See usage notes at the top of the file.
+class RepeatingTimer : public Timer {
public:
- OneShotTimer() : OneShotTimer(nullptr) {}
- explicit OneShotTimer(TickClock* tick_clock)
- : BaseTimerMethodPointer(ONE_SHOT, tick_clock) {}
+ RepeatingTimer() : RepeatingTimer(nullptr) {}
+ explicit RepeatingTimer(const TickClock* tick_clock)
+ : Timer(true, true, tick_clock) {}
+
+ RepeatingTimer(const Location& posted_from,
+ TimeDelta delay,
+ RepeatingClosure user_task)
+ : Timer(posted_from, delay, std::move(user_task), true) {}
+ RepeatingTimer(const Location& posted_from,
+ TimeDelta delay,
+ RepeatingClosure user_task,
+ const TickClock* tick_clock)
+ : Timer(posted_from, delay, std::move(user_task), true, tick_clock) {}
};
//-----------------------------------------------------------------------------
-// A simple, repeating timer. See usage notes at the top of the file.
-class RepeatingTimer : public BaseTimerMethodPointer {
+// A simple, one-shot timer with the retained user task. See usage notes at the
+// top of the file.
+class RetainingOneShotTimer : public Timer {
public:
- RepeatingTimer() : RepeatingTimer(nullptr) {}
- explicit RepeatingTimer(TickClock* tick_clock)
- : BaseTimerMethodPointer(REPEATING, tick_clock) {}
+ RetainingOneShotTimer() : RetainingOneShotTimer(nullptr) {}
+ explicit RetainingOneShotTimer(const TickClock* tick_clock)
+ : Timer(true, false, tick_clock) {}
+
+ RetainingOneShotTimer(const Location& posted_from,
+ TimeDelta delay,
+ RepeatingClosure user_task)
+ : Timer(posted_from, delay, std::move(user_task), false) {}
+ RetainingOneShotTimer(const Location& posted_from,
+ TimeDelta delay,
+ RepeatingClosure user_task,
+ const TickClock* tick_clock)
+ : Timer(posted_from, delay, std::move(user_task), false, tick_clock) {}
};
//-----------------------------------------------------------------------------
// A Delay timer is like The Button from Lost. Once started, you have to keep
-// calling Reset otherwise it will call the given method in the MessageLoop
-// thread.
+// calling Reset otherwise it will call the given method on the sequence it was
+// initially Reset() from.
//
// Once created, it is inactive until Reset is called. Once |delay| seconds have
// passed since the last call to Reset, the callback is made. Once the callback
@@ -275,37 +297,33 @@ class RepeatingTimer : public BaseTimerMethodPointer {
//
// If destroyed, the timeout is canceled and will not occur even if already
// inflight.
-class DelayTimer : protected Timer {
+class DelayTimer {
public:
template <class Receiver>
- DelayTimer(const tracked_objects::Location& posted_from,
+ DelayTimer(const Location& posted_from,
TimeDelta delay,
Receiver* receiver,
void (Receiver::*method)())
: DelayTimer(posted_from, delay, receiver, method, nullptr) {}
template <class Receiver>
- DelayTimer(const tracked_objects::Location& posted_from,
+ DelayTimer(const Location& posted_from,
TimeDelta delay,
Receiver* receiver,
void (Receiver::*method)(),
- TickClock* tick_clock)
- : Timer(posted_from,
- delay,
- base::Bind(method, base::Unretained(receiver)),
- false,
- tick_clock) {}
-
- void Reset() override;
-};
+ const TickClock* tick_clock)
+ : timer_(posted_from,
+ delay,
+ BindRepeating(method, Unretained(receiver)),
+ tick_clock) {}
+
+ void Reset() { timer_.Reset(); }
-// This class has a templated method so it can not be exported without failing
-// to link in MSVC. But clang-plugin does not allow inline definitions of
-// virtual methods, so the inline definition lives in the header file here
-// to satisfy both.
-inline void DelayTimer::Reset() {
- Timer::Reset();
-}
+ private:
+ RetainingOneShotTimer timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(DelayTimer);
+};
} // namespace base
diff --git a/base/timer/timer_unittest.cc b/base/timer/timer_unittest.cc
index 69338eb211..aaab237d11 100644
--- a/base/timer/timer_unittest.cc
+++ b/base/timer/timer_unittest.cc
@@ -17,14 +17,13 @@
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/sequenced_task_runner.h"
-#include "base/single_thread_task_runner.h"
#include "base/synchronization/waitable_event.h"
-#include "base/test/sequenced_worker_pool_owner.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/test/scoped_task_environment.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/threading/platform_thread.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread.h"
-#include "base/threading/thread_task_runner_handle.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "build/build_config.h"
@@ -85,7 +84,7 @@ class OneShotTimerTesterBase {
}
}
- std::unique_ptr<OneShotTimer> timer_ = MakeUnique<OneShotTimer>();
+ std::unique_ptr<OneShotTimer> timer_ = std::make_unique<OneShotTimer>();
private:
WaitableEvent* const did_run_;
@@ -108,14 +107,14 @@ class OneShotTimerTester : public OneShotTimerTesterBase {
~OneShotTimerTester() override = default;
- void SetTaskRunner(scoped_refptr<SingleThreadTaskRunner> task_runner) {
+ void SetTaskRunner(scoped_refptr<SequencedTaskRunner> task_runner) {
timer_->SetTaskRunner(std::move(task_runner));
// Run() will be invoked on |task_runner| but |run_loop_|'s QuitClosure
// needs to run on this thread (where the MessageLoop lives).
- quit_closure_ =
- Bind(IgnoreResult(&SingleThreadTaskRunner::PostTask),
- ThreadTaskRunnerHandle::Get(), FROM_HERE, run_loop_.QuitClosure());
+ quit_closure_ = Bind(IgnoreResult(&SequencedTaskRunner::PostTask),
+ SequencedTaskRunnerHandle::Get(), FROM_HERE,
+ run_loop_.QuitClosure());
}
// Blocks until Run() executes and confirms that Run() didn't fire before
@@ -421,7 +420,7 @@ TEST(TimerTest, OneShotTimer_CustomTaskRunner) {
OneShotTimerTester f(&did_run);
f.SetTaskRunner(other_thread.task_runner());
f.Start();
- EXPECT_TRUE(f.IsRunning());
+ EXPECT_TRUE(f.IsRunning() || did_run.IsSignaled());
f.WaitAndConfirmTimerFiredAfterDelay();
EXPECT_TRUE(did_run.IsSignaled());
@@ -436,11 +435,10 @@ TEST(TimerTest, OneShotTimer_CustomTaskRunner) {
TEST(TimerTest, OneShotTimerWithTickClock) {
scoped_refptr<TestMockTimeTaskRunner> task_runner(
new TestMockTimeTaskRunner(Time::Now(), TimeTicks::Now()));
- std::unique_ptr<TickClock> tick_clock(task_runner->GetMockTickClock());
MessageLoop message_loop;
message_loop.SetTaskRunner(task_runner);
Receiver receiver;
- OneShotTimer timer(tick_clock.get());
+ OneShotTimer timer(task_runner->GetMockTickClock());
timer.Start(FROM_HERE, TimeDelta::FromSeconds(1),
Bind(&Receiver::OnCalled, Unretained(&receiver)));
task_runner->FastForwardBy(TimeDelta::FromSeconds(1));
@@ -478,12 +476,11 @@ TEST(TimerTest, RepeatingTimerZeroDelay_Cancel) {
TEST(TimerTest, RepeatingTimerWithTickClock) {
scoped_refptr<TestMockTimeTaskRunner> task_runner(
new TestMockTimeTaskRunner(Time::Now(), TimeTicks::Now()));
- std::unique_ptr<TickClock> tick_clock(task_runner->GetMockTickClock());
MessageLoop message_loop;
message_loop.SetTaskRunner(task_runner);
Receiver receiver;
const int expected_times_called = 10;
- RepeatingTimer timer(tick_clock.get());
+ RepeatingTimer timer(task_runner->GetMockTickClock());
timer.Start(FROM_HERE, TimeDelta::FromSeconds(1),
Bind(&Receiver::OnCalled, Unretained(&receiver)));
task_runner->FastForwardBy(TimeDelta::FromSeconds(expected_times_called));
@@ -519,12 +516,11 @@ TEST(TimerTest, DelayTimer_Deleted) {
TEST(TimerTest, DelayTimerWithTickClock) {
scoped_refptr<TestMockTimeTaskRunner> task_runner(
new TestMockTimeTaskRunner(Time::Now(), TimeTicks::Now()));
- std::unique_ptr<TickClock> tick_clock(task_runner->GetMockTickClock());
MessageLoop message_loop;
message_loop.SetTaskRunner(task_runner);
Receiver receiver;
DelayTimer timer(FROM_HERE, TimeDelta::FromSeconds(1), &receiver,
- &Receiver::OnCalled, tick_clock.get());
+ &Receiver::OnCalled, task_runner->GetMockTickClock());
task_runner->FastForwardBy(TimeDelta::FromMilliseconds(999));
EXPECT_FALSE(receiver.WasCalled());
timer.Reset();
@@ -668,6 +664,8 @@ TEST(TimerTest, RetainNonRepeatIsRunning) {
EXPECT_TRUE(timer.IsRunning());
}
+//-----------------------------------------------------------------------------
+
namespace {
bool g_callback_happened1 = false;
@@ -680,12 +678,12 @@ void ClearAllCallbackHappened() {
void SetCallbackHappened1() {
g_callback_happened1 = true;
- MessageLoop::current()->QuitWhenIdle();
+ RunLoop::QuitCurrentWhenIdleDeprecated();
}
void SetCallbackHappened2() {
g_callback_happened2 = true;
- MessageLoop::current()->QuitWhenIdle();
+ RunLoop::QuitCurrentWhenIdleDeprecated();
}
} // namespace
@@ -721,4 +719,186 @@ TEST(TimerTest, ContinuationReset) {
}
}
+namespace {
+
+// Fixture for tests requiring ScopedTaskEnvironment. Includes a WaitableEvent
+// so that cases may Wait() on one thread and Signal() (explicitly, or
+// implicitly via helper methods) on another.
+class TimerSequenceTest : public testing::Test {
+ public:
+ TimerSequenceTest()
+ : event_(WaitableEvent::ResetPolicy::AUTOMATIC,
+ WaitableEvent::InitialState::NOT_SIGNALED) {}
+
+ // Block until Signal() is called on another thread.
+ void Wait() { event_.Wait(); }
+
+ void Signal() { event_.Signal(); }
+
+ // Helper to augment a task with a subsequent call to Signal().
+ Closure TaskWithSignal(const Closure& task) {
+ return Bind(&TimerSequenceTest::RunTaskAndSignal, Unretained(this), task);
+ }
+
+ // Create the timer.
+ void CreateTimer() { timer_.reset(new OneShotTimer); }
+
+ // Schedule an event on the timer.
+ void StartTimer(TimeDelta delay, const Closure& task) {
+ timer_->Start(FROM_HERE, delay, task);
+ }
+
+ void SetTaskRunnerForTimer(scoped_refptr<SequencedTaskRunner> task_runner) {
+ timer_->SetTaskRunner(std::move(task_runner));
+ }
+
+ // Tell the timer to abandon the task.
+ void AbandonTask() {
+ EXPECT_TRUE(timer_->IsRunning());
+ // Reset() to call Timer::AbandonScheduledTask()
+ timer_->Reset();
+ EXPECT_TRUE(timer_->IsRunning());
+ timer_->Stop();
+ EXPECT_FALSE(timer_->IsRunning());
+ }
+
+ static void VerifyAffinity(const SequencedTaskRunner* task_runner) {
+ EXPECT_TRUE(task_runner->RunsTasksInCurrentSequence());
+ }
+
+ // Delete the timer.
+ void DeleteTimer() { timer_.reset(); }
+
+ private:
+ void RunTaskAndSignal(const Closure& task) {
+ task.Run();
+ Signal();
+ }
+
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ WaitableEvent event_;
+ std::unique_ptr<OneShotTimer> timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(TimerSequenceTest);
+};
+
+} // namespace
+
+TEST_F(TimerSequenceTest, OneShotTimerTaskOnPoolSequence) {
+ scoped_refptr<SequencedTaskRunner> task_runner =
+ base::CreateSequencedTaskRunnerWithTraits({});
+
+ base::RunLoop run_loop_;
+
+ // Timer is created on this thread.
+ CreateTimer();
+
+ // Task will execute on a pool thread.
+ SetTaskRunnerForTimer(task_runner);
+ StartTimer(TimeDelta::FromMilliseconds(1),
+ Bind(IgnoreResult(&SequencedTaskRunner::PostTask),
+ SequencedTaskRunnerHandle::Get(), FROM_HERE,
+ run_loop_.QuitClosure()));
+
+ // Spin the loop so that the delayed task fires on it, which will forward it
+ // to |task_runner|. And since the Timer's task is one that posts back to this
+ // MessageLoop to quit, we finally unblock.
+ run_loop_.Run();
+
+ // Timer will be destroyed on this thread.
+ DeleteTimer();
+}
+
+TEST_F(TimerSequenceTest, OneShotTimerUsedOnPoolSequence) {
+ scoped_refptr<SequencedTaskRunner> task_runner =
+ base::CreateSequencedTaskRunnerWithTraits({});
+
+ // Timer is created on this thread.
+ CreateTimer();
+
+ // Task will be scheduled from a pool thread.
+ task_runner->PostTask(
+ FROM_HERE, BindOnce(&TimerSequenceTest::StartTimer, Unretained(this),
+ TimeDelta::FromMilliseconds(1),
+ Bind(&TimerSequenceTest::Signal, Unretained(this))));
+ Wait();
+
+ // Timer must be destroyed on pool thread, too.
+ task_runner->PostTask(
+ FROM_HERE,
+ TaskWithSignal(Bind(&TimerSequenceTest::DeleteTimer, Unretained(this))));
+ Wait();
+}
+
+TEST_F(TimerSequenceTest, OneShotTimerTwoSequencesAbandonTask) {
+ scoped_refptr<SequencedTaskRunner> task_runner1 =
+ base::CreateSequencedTaskRunnerWithTraits({});
+ scoped_refptr<SequencedTaskRunner> task_runner2 =
+ base::CreateSequencedTaskRunnerWithTraits({});
+
+ // Create timer on sequence #1.
+ task_runner1->PostTask(
+ FROM_HERE,
+ TaskWithSignal(Bind(&TimerSequenceTest::CreateTimer, Unretained(this))));
+ Wait();
+
+ // And tell it to execute on a different sequence (#2).
+ task_runner1->PostTask(
+ FROM_HERE, TaskWithSignal(Bind(&TimerSequenceTest::SetTaskRunnerForTimer,
+ Unretained(this), task_runner2)));
+ Wait();
+
+ // Task will be scheduled from sequence #1.
+ task_runner1->PostTask(
+ FROM_HERE, BindOnce(&TimerSequenceTest::StartTimer, Unretained(this),
+ TimeDelta::FromHours(1), DoNothing()));
+
+ // Abandon task - must be called from scheduling sequence (#1).
+ task_runner1->PostTask(
+ FROM_HERE,
+ TaskWithSignal(Bind(&TimerSequenceTest::AbandonTask, Unretained(this))));
+ Wait();
+
+ // Timer must be destroyed on the sequence it was scheduled from (#1).
+ task_runner1->PostTask(
+ FROM_HERE,
+ TaskWithSignal(Bind(&TimerSequenceTest::DeleteTimer, Unretained(this))));
+ Wait();
+}
+
+TEST_F(TimerSequenceTest, OneShotTimerUsedAndTaskedOnDifferentSequences) {
+ scoped_refptr<SequencedTaskRunner> task_runner1 =
+ base::CreateSequencedTaskRunnerWithTraits({});
+ scoped_refptr<SequencedTaskRunner> task_runner2 =
+ base::CreateSequencedTaskRunnerWithTraits({});
+
+ // Create timer on sequence #1.
+ task_runner1->PostTask(
+ FROM_HERE,
+ TaskWithSignal(Bind(&TimerSequenceTest::CreateTimer, Unretained(this))));
+ Wait();
+
+ // And tell it to execute on a different sequence (#2).
+ task_runner1->PostTask(
+ FROM_HERE, TaskWithSignal(Bind(&TimerSequenceTest::SetTaskRunnerForTimer,
+ Unretained(this), task_runner2)));
+ Wait();
+
+ // Task will be scheduled from sequence #1.
+ task_runner1->PostTask(
+ FROM_HERE,
+ BindOnce(&TimerSequenceTest::StartTimer, Unretained(this),
+ TimeDelta::FromMilliseconds(1),
+ TaskWithSignal(Bind(&TimerSequenceTest::VerifyAffinity,
+ Unretained(task_runner2.get())))));
+
+ Wait();
+
+ // Timer must be destroyed on the sequence it was scheduled from (#1).
+ task_runner1->PostTask(
+ FROM_HERE,
+ TaskWithSignal(Bind(&TimerSequenceTest::DeleteTimer, Unretained(this))));
+ Wait();
+}
+
} // namespace base
diff --git a/base/trace_event/common/trace_event_common.h b/base/trace_event/common/trace_event_common.h
index e87665b8cd..e2a5ca0c8d 100644
--- a/base/trace_event/common/trace_event_common.h
+++ b/base/trace_event/common/trace_event_common.h
@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#ifndef BASE_TRACE_EVENT_COMMON_TRACE_EVENT_COMMON_H_
+#define BASE_TRACE_EVENT_COMMON_TRACE_EVENT_COMMON_H_
+
// This header file defines the set of trace_event macros without specifying
// how the events actually get collected and stored. If you need to expose trace
// events to some other universe, you can copy-and-paste this file as well as
@@ -189,6 +192,8 @@
// trace points would carry a significant performance cost of acquiring a lock
// and resolving the category.
+// Check that nobody includes this file directly. Clients are supposed to
+// include the surrounding "trace_event.h" of their project instead.
#if defined(TRACE_EVENT0)
#error "Another copy of this file has already been included."
#endif
@@ -258,6 +263,12 @@
TRACE_EVENT_PHASE_INSTANT, category_group, name, timestamp, \
TRACE_EVENT_FLAG_NONE | scope)
+#define TRACE_EVENT_INSTANT_WITH_TIMESTAMP1(category_group, name, scope, \
+ timestamp, arg_name, arg_val) \
+ INTERNAL_TRACE_EVENT_ADD_WITH_TIMESTAMP( \
+ TRACE_EVENT_PHASE_INSTANT, category_group, name, timestamp, \
+ TRACE_EVENT_FLAG_NONE | scope, arg_name, arg_val)
+
// Records a single BEGIN event called "name" immediately, with 0, 1 or 2
// associated arguments. If the category is not enabled, then this
// does nothing.
@@ -353,6 +364,12 @@
TRACE_EVENT_PHASE_MARK, category_group, name, timestamp, \
TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val)
+#define TRACE_EVENT_MARK_WITH_TIMESTAMP2( \
+ category_group, name, timestamp, arg1_name, arg1_val, arg2_name, arg2_val) \
+ INTERNAL_TRACE_EVENT_ADD_WITH_TIMESTAMP( \
+ TRACE_EVENT_PHASE_MARK, category_group, name, timestamp, \
+ TRACE_EVENT_FLAG_NONE, arg1_name, arg1_val, arg2_name, arg2_val)
+
#define TRACE_EVENT_COPY_MARK(category_group, name) \
INTERNAL_TRACE_EVENT_ADD(TRACE_EVENT_PHASE_MARK, category_group, name, \
TRACE_EVENT_FLAG_COPY)
@@ -673,6 +690,11 @@
TRACE_EVENT_PHASE_ASYNC_END, category_group, name, id, \
TRACE_EVENT_API_CURRENT_THREAD_ID, timestamp, TRACE_EVENT_FLAG_NONE, \
arg1_name, arg1_val, arg2_name, arg2_val)
+#define TRACE_EVENT_COPY_ASYNC_END_WITH_TIMESTAMP0(category_group, name, id, \
+ timestamp) \
+ INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP( \
+ TRACE_EVENT_PHASE_ASYNC_END, category_group, name, id, \
+ TRACE_EVENT_API_CURRENT_THREAD_ID, timestamp, TRACE_EVENT_FLAG_COPY)
// NESTABLE_ASYNC_* APIs are used to describe an async operation, which can
// be nested within a NESTABLE_ASYNC event and/or have inner NESTABLE_ASYNC
@@ -771,13 +793,22 @@
INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP( \
TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN, category_group, name, id, \
TRACE_EVENT_API_CURRENT_THREAD_ID, timestamp, TRACE_EVENT_FLAG_NONE)
-
#define TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(category_group, name, \
id, timestamp) \
INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP( \
TRACE_EVENT_PHASE_NESTABLE_ASYNC_END, category_group, name, id, \
TRACE_EVENT_API_CURRENT_THREAD_ID, timestamp, TRACE_EVENT_FLAG_NONE)
-
+#define TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP1( \
+ category_group, name, id, timestamp, arg1_name, arg1_val) \
+ INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP( \
+ TRACE_EVENT_PHASE_NESTABLE_ASYNC_END, category_group, name, id, \
+ TRACE_EVENT_API_CURRENT_THREAD_ID, timestamp, TRACE_EVENT_FLAG_NONE, \
+ arg1_name, arg1_val)
+#define TRACE_EVENT_NESTABLE_ASYNC_INSTANT_WITH_TIMESTAMP0( \
+ category_group, name, id, timestamp) \
+ INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP( \
+ TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT, category_group, name, id, \
+ TRACE_EVENT_API_CURRENT_THREAD_ID, timestamp, TRACE_EVENT_FLAG_NONE)
#define TRACE_EVENT_COPY_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0( \
category_group, name, id, timestamp) \
INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMP( \
@@ -997,6 +1028,14 @@
} \
} while (0)
+// Macro for getting the real base::TimeTicks::Now() which can be overridden in
+// headless when VirtualTime is enabled.
+#define TRACE_TIME_TICKS_NOW() INTERNAL_TRACE_TIME_TICKS_NOW()
+
+// Macro for getting the real base::Time::Now() which can be overridden in
+// headless when VirtualTime is enabled.
+#define TRACE_TIME_NOW() INTERNAL_TRACE_TIME_NOW()
+
// Notes regarding the following definitions:
// New values can be added and propagated to third party libraries, but existing
// definitions must never be changed, because third party libraries may use old
@@ -1071,3 +1110,5 @@
#define TRACE_EVENT_SCOPE_NAME_GLOBAL ('g')
#define TRACE_EVENT_SCOPE_NAME_PROCESS ('p')
#define TRACE_EVENT_SCOPE_NAME_THREAD ('t')
+
+#endif // BASE_TRACE_EVENT_COMMON_TRACE_EVENT_COMMON_H_
diff --git a/base/trace_event/heap_profiler.h b/base/trace_event/heap_profiler.h
index a9cfcfde05..cf12fec3bb 100644
--- a/base/trace_event/heap_profiler.h
+++ b/base/trace_event/heap_profiler.h
@@ -41,6 +41,11 @@ class HeapProfilerScopedTaskExecutionTracker {
#define TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION \
trace_event_internal::HeapProfilerScopedTaskExecutionTracker
+// Scoped tracker that tracks the given program counter as a native stack frame
+// in the heap profiler.
+#define TRACE_HEAP_PROFILER_API_SCOPED_WITH_PROGRAM_COUNTER \
+ trace_event_internal::HeapProfilerScopedStackFrame
+
// A scoped ignore event used to tell heap profiler to ignore all the
// allocations in the scope. It is useful to exclude allocations made for
// tracing from the heap profiler dumps.
@@ -78,6 +83,31 @@ class HeapProfilerScopedTaskExecutionTracker {
const char* context_;
};
+class HeapProfilerScopedStackFrame {
+ public:
+ inline explicit HeapProfilerScopedStackFrame(const void* program_counter)
+ : program_counter_(program_counter) {
+ using base::trace_event::AllocationContextTracker;
+ if (UNLIKELY(AllocationContextTracker::capture_mode() ==
+ AllocationContextTracker::CaptureMode::MIXED_STACK)) {
+ AllocationContextTracker::GetInstanceForCurrentThread()
+ ->PushNativeStackFrame(program_counter_);
+ }
+ }
+
+ inline ~HeapProfilerScopedStackFrame() {
+ using base::trace_event::AllocationContextTracker;
+ if (UNLIKELY(AllocationContextTracker::capture_mode() ==
+ AllocationContextTracker::CaptureMode::MIXED_STACK)) {
+ AllocationContextTracker::GetInstanceForCurrentThread()
+ ->PopNativeStackFrame(program_counter_);
+ }
+ }
+
+ private:
+ const void* const program_counter_;
+};
+
class BASE_EXPORT HeapProfilerScopedIgnore {
public:
inline HeapProfilerScopedIgnore() {
diff --git a/base/trace_event/trace_event.h b/base/trace_event/trace_event.h
index 9abb8887b0..1ce76d9d2a 100644
--- a/base/trace_event/trace_event.h
+++ b/base/trace_event/trace_event.h
@@ -52,8 +52,10 @@ class TraceLog {
#include <string>
#include "base/atomicops.h"
+#include "base/debug/debugging_buildflags.h"
#include "base/macros.h"
#include "base/time/time.h"
+#include "base/time/time_override.h"
#include "base/trace_event/common/trace_event_common.h"
#include "base/trace_event/heap_profiler.h"
#include "base/trace_event/trace_category.h"
@@ -87,7 +89,7 @@ class TraceLog {
// TRACE_ID_WITH_SCOPE("BlinkResourceID", resourceID));
//
// Also, it is possible to prepend the ID with another number, like the process
-// ID. This is useful in creatin IDs that are unique among all processes. To do
+// ID. This is useful in creating IDs that are unique among all processes. To do
// that, pass two numbers after the scope string instead of one. For example,
//
// TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(
@@ -220,6 +222,16 @@ class TraceLog {
#define TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION \
base::trace_event::TraceLog::GetInstance()->UpdateTraceEventDuration
+// Set the duration field of a COMPLETE trace event.
+// void TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION_EXPLICIT(
+// const unsigned char* category_group_enabled,
+// const char* name,
+// base::trace_event::TraceEventHandle id,
+// const TimeTicks& now,
+// const ThreadTicks* thread_now)
+#define TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION_EXPLICIT \
+ base::trace_event::TraceLog::GetInstance()->UpdateTraceEventDurationExplicit
+
// Adds a metadata event to the trace log. The |AppendValueAsTraceFormat| method
// on the convertable value will be called at flush time.
// TRACE_EVENT_API_ADD_METADATA_EVENT(
@@ -275,6 +287,17 @@ class TraceLog {
INTERNAL_TRACE_EVENT_UID(atomic), \
INTERNAL_TRACE_EVENT_UID(category_group_enabled));
+// Implementation detail: internal macro to return unoverridden
+// base::TimeTicks::Now(). This is important because in headless VirtualTime can
+// override base:TimeTicks::Now().
+#define INTERNAL_TRACE_TIME_TICKS_NOW() \
+ base::subtle::TimeTicksNowIgnoringOverride()
+
+// Implementation detail: internal macro to return unoverridden
+// base::Time::Now(). This is important because in headless VirtualTime can
+// override base:TimeTicks::Now().
+#define INTERNAL_TRACE_TIME_NOW() base::subtle::TimeNowIgnoringOverride()
+
// Implementation detail: internal macro to create static category and add
// event if the category is enabled.
#define INTERNAL_TRACE_EVENT_ADD(phase, category_group, name, flags, ...) \
@@ -376,6 +399,32 @@ class TraceLog {
} \
} while (0)
+// Implementation detail: internal macro to create static category and add
+// event if the category is enabled.
+#define INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMPS( \
+ category_group, name, id, thread_id, begin_timestamp, end_timestamp, \
+ thread_end_timestamp, flags, ...) \
+ do { \
+ INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \
+ if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED()) { \
+ trace_event_internal::TraceID trace_event_trace_id((id)); \
+ unsigned int trace_event_flags = \
+ flags | trace_event_trace_id.id_flags(); \
+ const unsigned char* uid_category_group_enabled = \
+ INTERNAL_TRACE_EVENT_UID(category_group_enabled); \
+ auto handle = \
+ trace_event_internal::AddTraceEventWithThreadIdAndTimestamp( \
+ TRACE_EVENT_PHASE_COMPLETE, uid_category_group_enabled, name, \
+ trace_event_trace_id.scope(), trace_event_trace_id.raw_id(), \
+ thread_id, begin_timestamp, \
+ trace_event_flags | TRACE_EVENT_FLAG_EXPLICIT_TIMESTAMP, \
+ trace_event_internal::kNoId, ##__VA_ARGS__); \
+ TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION_EXPLICIT( \
+ uid_category_group_enabled, name, handle, end_timestamp, \
+ thread_end_timestamp); \
+ } \
+ } while (0)
+
// The linked ID will not be mangled.
#define INTERNAL_TRACE_EVENT_ADD_LINK_IDS(category_group, name, id1, id2) \
do { \
@@ -427,14 +476,35 @@ class TraceLog {
INTERNAL_TRACE_EVENT_UID(ScopedContext) \
INTERNAL_TRACE_EVENT_UID(scoped_context)(context);
+#if BUILDFLAG(ENABLE_LOCATION_SOURCE)
+
// Implementation detail: internal macro to trace a task execution with the
// location where it was posted from.
+//
+// This implementation is for when location sources are available.
+// TODO(ssid): The program counter of the current task should be added here.
#define INTERNAL_TRACE_TASK_EXECUTION(run_function, task) \
TRACE_EVENT2("toplevel", run_function, "src_file", \
(task).posted_from.file_name(), "src_func", \
(task).posted_from.function_name()); \
TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION INTERNAL_TRACE_EVENT_UID( \
- task_event)((task).posted_from.file_name());
+ task_event)((task).posted_from.file_name()); \
+ TRACE_HEAP_PROFILER_API_SCOPED_WITH_PROGRAM_COUNTER \
+ INTERNAL_TRACE_EVENT_UID(task_pc_event)((task).posted_from.program_counter());
+
+#else
+
+// TODO(http://crbug.com760702) remove file name and just pass the program
+// counter to the heap profiler macro.
+// TODO(ssid): The program counter of the current task should be added here.
+#define INTERNAL_TRACE_TASK_EXECUTION(run_function, task) \
+ TRACE_EVENT1("toplevel", run_function, "src", (task).posted_from.ToString()) \
+ TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION INTERNAL_TRACE_EVENT_UID( \
+ task_event)((task).posted_from.file_name()); \
+ TRACE_HEAP_PROFILER_API_SCOPED_WITH_PROGRAM_COUNTER \
+ INTERNAL_TRACE_EVENT_UID(task_pc_event)((task).posted_from.program_counter());
+
+#endif
namespace trace_event_internal {
@@ -452,6 +522,9 @@ class BASE_EXPORT TraceID {
// Can be combined with WithScope.
class LocalId {
public:
+ explicit LocalId(const void* raw_id)
+ : raw_id_(static_cast<unsigned long long>(
+ reinterpret_cast<uintptr_t>(raw_id))) {}
explicit LocalId(unsigned long long raw_id) : raw_id_(raw_id) {}
unsigned long long raw_id() const { return raw_id_; }
private:
@@ -854,7 +927,7 @@ static inline base::trace_event::TraceEventHandle AddTraceEvent(
unsigned int flags,
unsigned long long bind_id) {
const int thread_id = static_cast<int>(base::PlatformThread::CurrentId());
- const base::TimeTicks now = base::TimeTicks::Now();
+ const base::TimeTicks now = TRACE_TIME_TICKS_NOW();
return AddTraceEventWithThreadIdAndTimestamp(
phase, category_group_enabled, name, scope, id, thread_id, now, flags,
bind_id);
@@ -895,7 +968,7 @@ static inline base::trace_event::TraceEventHandle AddTraceEvent(
const char* arg1_name,
const ARG1_TYPE& arg1_val) {
int thread_id = static_cast<int>(base::PlatformThread::CurrentId());
- base::TimeTicks now = base::TimeTicks::Now();
+ base::TimeTicks now = TRACE_TIME_TICKS_NOW();
return AddTraceEventWithThreadIdAndTimestamp(
phase, category_group_enabled, name, scope, id, thread_id, now, flags,
bind_id, arg1_name, arg1_val);
@@ -913,7 +986,7 @@ static inline base::trace_event::TraceEventHandle AddTraceEvent(
const char* arg1_name,
std::unique_ptr<ARG1_CONVERTABLE_TYPE> arg1_val) {
int thread_id = static_cast<int>(base::PlatformThread::CurrentId());
- base::TimeTicks now = base::TimeTicks::Now();
+ base::TimeTicks now = TRACE_TIME_TICKS_NOW();
return AddTraceEventWithThreadIdAndTimestamp(
phase, category_group_enabled, name, scope, id, thread_id, now, flags,
bind_id, arg1_name, std::move(arg1_val));
@@ -960,7 +1033,7 @@ static inline base::trace_event::TraceEventHandle AddTraceEvent(
const char* arg2_name,
const ARG2_TYPE& arg2_val) {
int thread_id = static_cast<int>(base::PlatformThread::CurrentId());
- base::TimeTicks now = base::TimeTicks::Now();
+ base::TimeTicks now = TRACE_TIME_TICKS_NOW();
return AddTraceEventWithThreadIdAndTimestamp(
phase, category_group_enabled, name, scope, id, thread_id, now, flags,
bind_id, arg1_name, std::move(arg1_val), arg2_name, arg2_val);
@@ -980,7 +1053,7 @@ static inline base::trace_event::TraceEventHandle AddTraceEvent(
const char* arg2_name,
std::unique_ptr<ARG2_CONVERTABLE_TYPE> arg2_val) {
int thread_id = static_cast<int>(base::PlatformThread::CurrentId());
- base::TimeTicks now = base::TimeTicks::Now();
+ base::TimeTicks now = TRACE_TIME_TICKS_NOW();
return AddTraceEventWithThreadIdAndTimestamp(
phase, category_group_enabled, name, scope, id, thread_id, now, flags,
bind_id, arg1_name, arg1_val, arg2_name, std::move(arg2_val));
@@ -1000,7 +1073,7 @@ static inline base::trace_event::TraceEventHandle AddTraceEvent(
const char* arg2_name,
std::unique_ptr<ARG2_CONVERTABLE_TYPE> arg2_val) {
int thread_id = static_cast<int>(base::PlatformThread::CurrentId());
- base::TimeTicks now = base::TimeTicks::Now();
+ base::TimeTicks now = TRACE_TIME_TICKS_NOW();
return AddTraceEventWithThreadIdAndTimestamp(
phase, category_group_enabled, name, scope, id, thread_id, now, flags,
bind_id, arg1_name, std::move(arg1_val), arg2_name, std::move(arg2_val));
@@ -1020,7 +1093,7 @@ static inline base::trace_event::TraceEventHandle AddTraceEvent(
const char* arg2_name,
const ARG2_TYPE& arg2_val) {
int thread_id = static_cast<int>(base::PlatformThread::CurrentId());
- base::TimeTicks now = base::TimeTicks::Now();
+ base::TimeTicks now = TRACE_TIME_TICKS_NOW();
return AddTraceEventWithThreadIdAndTimestamp(
phase, category_group_enabled, name, scope, id, thread_id, now, flags,
bind_id, arg1_name, arg1_val, arg2_name, arg2_val);
diff --git a/base/tracked_objects.cc b/base/tracked_objects.cc
deleted file mode 100644
index 1507c0986c..0000000000
--- a/base/tracked_objects.cc
+++ /dev/null
@@ -1,1085 +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/tracked_objects.h"
-
-#include <ctype.h>
-#include <limits.h>
-#include <stdlib.h>
-
-#include "base/atomicops.h"
-#include "base/base_switches.h"
-#include "base/command_line.h"
-#include "base/compiler_specific.h"
-#include "base/debug/leak_annotations.h"
-#include "base/logging.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/numerics/safe_conversions.h"
-#include "base/numerics/safe_math.h"
-#include "base/process/process_handle.h"
-#include "base/third_party/valgrind/memcheck.h"
-#include "base/threading/worker_pool.h"
-#include "base/tracking_info.h"
-#include "build/build_config.h"
-
-using base::TimeDelta;
-
-namespace base {
-class TimeDelta;
-}
-
-namespace tracked_objects {
-
-namespace {
-
-constexpr char kWorkerThreadSanitizedName[] = "WorkerThread-*";
-
-// When ThreadData is first initialized, should we start in an ACTIVE state to
-// record all of the startup-time tasks, or should we start up DEACTIVATED, so
-// that we only record after parsing the command line flag --enable-tracking.
-// Note that the flag may force either state, so this really controls only the
-// period of time up until that flag is parsed. If there is no flag seen, then
-// this state may prevail for much or all of the process lifetime.
-const ThreadData::Status kInitialStartupState = ThreadData::PROFILING_ACTIVE;
-
-// Possible states of the profiler timing enabledness.
-enum {
- UNDEFINED_TIMING,
- ENABLED_TIMING,
- DISABLED_TIMING,
-};
-
-// State of the profiler timing enabledness.
-base::subtle::Atomic32 g_profiler_timing_enabled = UNDEFINED_TIMING;
-
-// Returns whether profiler timing is enabled. The default is true, but this
-// may be overridden by a command-line flag. Some platforms may
-// programmatically set this command-line flag to the "off" value if it's not
-// specified.
-// This in turn can be overridden by explicitly calling
-// ThreadData::EnableProfilerTiming, say, based on a field trial.
-inline bool IsProfilerTimingEnabled() {
- // Reading |g_profiler_timing_enabled| is done without barrier because
- // multiple initialization is not an issue while the barrier can be relatively
- // costly given that this method is sometimes called in a tight loop.
- base::subtle::Atomic32 current_timing_enabled =
- base::subtle::NoBarrier_Load(&g_profiler_timing_enabled);
- if (current_timing_enabled == UNDEFINED_TIMING) {
- if (!base::CommandLine::InitializedForCurrentProcess())
- return true;
- current_timing_enabled =
- (base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
- switches::kProfilerTiming) ==
- switches::kProfilerTimingDisabledValue)
- ? DISABLED_TIMING
- : ENABLED_TIMING;
- base::subtle::NoBarrier_Store(&g_profiler_timing_enabled,
- current_timing_enabled);
- }
- return current_timing_enabled == ENABLED_TIMING;
-}
-
-// Sanitize a thread name by replacing trailing sequence of digits with "*".
-// Examples:
-// 1. "BrowserBlockingWorker1/23857" => "BrowserBlockingWorker1/*"
-// 2. "Chrome_IOThread" => "Chrome_IOThread"
-std::string SanitizeThreadName(const std::string& thread_name) {
- size_t i = thread_name.length();
-
- while (i > 0 && isdigit(thread_name[i - 1]))
- --i;
-
- if (i == thread_name.length())
- return thread_name;
-
- return thread_name.substr(0, i) + '*';
-}
-
-} // namespace
-
-//------------------------------------------------------------------------------
-// DeathData tallies durations when a death takes place.
-
-DeathData::DeathData()
- : count_(0),
- sample_probability_count_(0),
- run_duration_sum_(0),
- queue_duration_sum_(0),
- run_duration_max_(0),
- queue_duration_max_(0),
- alloc_ops_(0),
- free_ops_(0),
- allocated_bytes_(0),
- freed_bytes_(0),
- alloc_overhead_bytes_(0),
- max_allocated_bytes_(0),
- run_duration_sample_(0),
- queue_duration_sample_(0),
- last_phase_snapshot_(nullptr) {}
-
-DeathData::DeathData(const DeathData& other)
- : count_(other.count_),
- sample_probability_count_(other.sample_probability_count_),
- run_duration_sum_(other.run_duration_sum_),
- queue_duration_sum_(other.queue_duration_sum_),
- run_duration_max_(other.run_duration_max_),
- queue_duration_max_(other.queue_duration_max_),
- alloc_ops_(other.alloc_ops_),
- free_ops_(other.free_ops_),
- allocated_bytes_(other.allocated_bytes_),
- freed_bytes_(other.freed_bytes_),
- alloc_overhead_bytes_(other.alloc_overhead_bytes_),
- max_allocated_bytes_(other.max_allocated_bytes_),
- run_duration_sample_(other.run_duration_sample_),
- queue_duration_sample_(other.queue_duration_sample_),
- last_phase_snapshot_(nullptr) {
- // This constructor will be used by std::map when adding new DeathData values
- // to the map. At that point, last_phase_snapshot_ is still NULL, so we don't
- // need to worry about ownership transfer.
- DCHECK(other.last_phase_snapshot_ == nullptr);
-}
-
-DeathData::~DeathData() {
- while (last_phase_snapshot_) {
- const DeathDataPhaseSnapshot* snapshot = last_phase_snapshot_;
- last_phase_snapshot_ = snapshot->prev;
- delete snapshot;
- }
-}
-
-// TODO(jar): I need to see if this macro to optimize branching is worth using.
-//
-// This macro has no branching, so it is surely fast, and is equivalent to:
-// if (assign_it)
-// target = source;
-// We use a macro rather than a template to force this to inline.
-// Related code for calculating max is discussed on the web.
-#define CONDITIONAL_ASSIGN(assign_it, target, source) \
- ((target) ^= ((target) ^ (source)) & -static_cast<int32_t>(assign_it))
-
-void DeathData::RecordDurations(const int32_t queue_duration,
- const int32_t run_duration,
- const uint32_t random_number) {
- // We'll just clamp at INT_MAX, but we should note this in the UI as such.
- if (count_ < INT_MAX)
- base::subtle::NoBarrier_Store(&count_, count_ + 1);
-
- int sample_probability_count =
- base::subtle::NoBarrier_Load(&sample_probability_count_);
- if (sample_probability_count < INT_MAX)
- ++sample_probability_count;
- base::subtle::NoBarrier_Store(&sample_probability_count_,
- sample_probability_count);
-
- base::subtle::NoBarrier_Store(&queue_duration_sum_,
- queue_duration_sum_ + queue_duration);
- base::subtle::NoBarrier_Store(&run_duration_sum_,
- run_duration_sum_ + run_duration);
-
- if (queue_duration_max() < queue_duration)
- base::subtle::NoBarrier_Store(&queue_duration_max_, queue_duration);
- if (run_duration_max() < run_duration)
- base::subtle::NoBarrier_Store(&run_duration_max_, run_duration);
-
- // Take a uniformly distributed sample over all durations ever supplied during
- // the current profiling phase.
- // The probability that we (instead) use this new sample is
- // 1/sample_probability_count_. This results in a completely uniform selection
- // of the sample (at least when we don't clamp sample_probability_count_...
- // but that should be inconsequentially likely). We ignore the fact that we
- // correlated our selection of a sample to the run and queue times (i.e., we
- // used them to generate random_number).
- CHECK_GT(sample_probability_count, 0);
- if (0 == (random_number % sample_probability_count)) {
- base::subtle::NoBarrier_Store(&queue_duration_sample_, queue_duration);
- base::subtle::NoBarrier_Store(&run_duration_sample_, run_duration);
- }
-}
-
-void DeathData::RecordAllocations(const uint32_t alloc_ops,
- const uint32_t free_ops,
- const uint32_t allocated_bytes,
- const uint32_t freed_bytes,
- const uint32_t alloc_overhead_bytes,
- const uint32_t max_allocated_bytes) {
- // Use saturating arithmetic.
- SaturatingMemberAdd(alloc_ops, &alloc_ops_);
- SaturatingMemberAdd(free_ops, &free_ops_);
- SaturatingMemberAdd(allocated_bytes, &allocated_bytes_);
- SaturatingMemberAdd(freed_bytes, &freed_bytes_);
- SaturatingMemberAdd(alloc_overhead_bytes, &alloc_overhead_bytes_);
-
- int32_t max = base::saturated_cast<int32_t>(max_allocated_bytes);
- if (max > max_allocated_bytes_)
- base::subtle::NoBarrier_Store(&max_allocated_bytes_, max);
-}
-
-void DeathData::OnProfilingPhaseCompleted(int profiling_phase) {
- // Snapshotting and storing current state.
- last_phase_snapshot_ =
- new DeathDataPhaseSnapshot(profiling_phase, *this, last_phase_snapshot_);
-
- // Not touching fields for which a delta can be computed by comparing with a
- // snapshot from the previous phase. Resetting other fields. Sample values
- // will be reset upon next death recording because sample_probability_count_
- // is set to 0.
- // We avoid resetting to 0 in favor of deltas whenever possible. The reason
- // is that for incrementable fields, resetting to 0 from the snapshot thread
- // potentially in parallel with incrementing in the death thread may result in
- // significant data corruption that has a potential to grow with time. Not
- // resetting incrementable fields and using deltas will cause any
- // off-by-little corruptions to be likely fixed at the next snapshot.
- // The max values are not incrementable, and cannot be deduced using deltas
- // for a given phase. Hence, we have to reset them to 0. But the potential
- // damage is limited to getting the previous phase's max to apply for the next
- // phase, and the error doesn't have a potential to keep growing with new
- // resets.
- // sample_probability_count_ is incrementable, but must be reset to 0 at the
- // phase end, so that we start a new uniformly randomized sample selection
- // after the reset. These fields are updated using atomics. However, race
- // conditions are possible since these are updated individually and not
- // together atomically, resulting in the values being mutually inconsistent.
- // The damage is limited to selecting a wrong sample, which is not something
- // that can cause accumulating or cascading effects.
- // If there were no inconsistencies caused by race conditions, we never send a
- // sample for the previous phase in the next phase's snapshot because
- // ThreadData::SnapshotExecutedTasks doesn't send deltas with 0 count.
- base::subtle::NoBarrier_Store(&sample_probability_count_, 0);
- base::subtle::NoBarrier_Store(&run_duration_max_, 0);
- base::subtle::NoBarrier_Store(&queue_duration_max_, 0);
-}
-
-void DeathData::SaturatingMemberAdd(const uint32_t addend,
- base::subtle::Atomic32* sum) {
- // Bail quick if no work or already saturated.
- if (addend == 0U || *sum == INT_MAX)
- return;
-
- base::CheckedNumeric<int32_t> new_sum = *sum;
- new_sum += addend;
- base::subtle::NoBarrier_Store(sum, new_sum.ValueOrDefault(INT_MAX));
-}
-
-//------------------------------------------------------------------------------
-DeathDataSnapshot::DeathDataSnapshot()
- : count(-1),
- run_duration_sum(-1),
- run_duration_max(-1),
- run_duration_sample(-1),
- queue_duration_sum(-1),
- queue_duration_max(-1),
- queue_duration_sample(-1),
- alloc_ops(-1),
- free_ops(-1),
- allocated_bytes(-1),
- freed_bytes(-1),
- alloc_overhead_bytes(-1),
- max_allocated_bytes(-1) {}
-
-DeathDataSnapshot::DeathDataSnapshot(int count,
- int32_t run_duration_sum,
- int32_t run_duration_max,
- int32_t run_duration_sample,
- int32_t queue_duration_sum,
- int32_t queue_duration_max,
- int32_t queue_duration_sample,
- int32_t alloc_ops,
- int32_t free_ops,
- int32_t allocated_bytes,
- int32_t freed_bytes,
- int32_t alloc_overhead_bytes,
- int32_t max_allocated_bytes)
- : count(count),
- run_duration_sum(run_duration_sum),
- run_duration_max(run_duration_max),
- run_duration_sample(run_duration_sample),
- queue_duration_sum(queue_duration_sum),
- queue_duration_max(queue_duration_max),
- queue_duration_sample(queue_duration_sample),
- alloc_ops(alloc_ops),
- free_ops(free_ops),
- allocated_bytes(allocated_bytes),
- freed_bytes(freed_bytes),
- alloc_overhead_bytes(alloc_overhead_bytes),
- max_allocated_bytes(max_allocated_bytes) {}
-
-DeathDataSnapshot::DeathDataSnapshot(const DeathData& death_data)
- : count(death_data.count()),
- run_duration_sum(death_data.run_duration_sum()),
- run_duration_max(death_data.run_duration_max()),
- run_duration_sample(death_data.run_duration_sample()),
- queue_duration_sum(death_data.queue_duration_sum()),
- queue_duration_max(death_data.queue_duration_max()),
- queue_duration_sample(death_data.queue_duration_sample()),
- alloc_ops(death_data.alloc_ops()),
- free_ops(death_data.free_ops()),
- allocated_bytes(death_data.allocated_bytes()),
- freed_bytes(death_data.freed_bytes()),
- alloc_overhead_bytes(death_data.alloc_overhead_bytes()),
- max_allocated_bytes(death_data.max_allocated_bytes()) {}
-
-DeathDataSnapshot::DeathDataSnapshot(const DeathDataSnapshot& death_data) =
- default;
-
-DeathDataSnapshot::~DeathDataSnapshot() {
-}
-
-DeathDataSnapshot DeathDataSnapshot::Delta(
- const DeathDataSnapshot& older) const {
- return DeathDataSnapshot(
- count - older.count, run_duration_sum - older.run_duration_sum,
- run_duration_max, run_duration_sample,
- queue_duration_sum - older.queue_duration_sum, queue_duration_max,
- queue_duration_sample, alloc_ops - older.alloc_ops,
- free_ops - older.free_ops, allocated_bytes - older.allocated_bytes,
- freed_bytes - older.freed_bytes,
- alloc_overhead_bytes - older.alloc_overhead_bytes, max_allocated_bytes);
-}
-
-//------------------------------------------------------------------------------
-BirthOnThread::BirthOnThread(const Location& location,
- const ThreadData& current)
- : location_(location),
- birth_thread_(&current) {
-}
-
-//------------------------------------------------------------------------------
-BirthOnThreadSnapshot::BirthOnThreadSnapshot() {
-}
-
-BirthOnThreadSnapshot::BirthOnThreadSnapshot(const BirthOnThread& birth)
- : location(birth.location()),
- sanitized_thread_name(birth.birth_thread()->sanitized_thread_name()) {}
-
-BirthOnThreadSnapshot::~BirthOnThreadSnapshot() {
-}
-
-//------------------------------------------------------------------------------
-Births::Births(const Location& location, const ThreadData& current)
- : BirthOnThread(location, current),
- birth_count_(1) { }
-
-int Births::birth_count() const { return birth_count_; }
-
-void Births::RecordBirth() { ++birth_count_; }
-
-//------------------------------------------------------------------------------
-// ThreadData maintains the central data for all births and deaths on a single
-// thread.
-
-// TODO(jar): We should pull all these static vars together, into a struct, and
-// optimize layout so that we benefit from locality of reference during accesses
-// to them.
-
-// static
-ThreadData::NowFunction* ThreadData::now_function_for_testing_ = NULL;
-
-// A TLS slot which points to the ThreadData instance for the current thread.
-// We do a fake initialization here (zeroing out data), and then the real
-// in-place construction happens when we call tls_index_.Initialize().
-// static
-base::ThreadLocalStorage::StaticSlot ThreadData::tls_index_ = TLS_INITIALIZER;
-
-// static
-int ThreadData::cleanup_count_ = 0;
-
-// static
-int ThreadData::incarnation_counter_ = 0;
-
-// static
-ThreadData* ThreadData::all_thread_data_list_head_ = NULL;
-
-// static
-ThreadData* ThreadData::first_retired_thread_data_ = NULL;
-
-// static
-base::LazyInstance<base::Lock>::Leaky
- ThreadData::list_lock_ = LAZY_INSTANCE_INITIALIZER;
-
-// static
-base::subtle::Atomic32 ThreadData::status_ = ThreadData::UNINITIALIZED;
-
-ThreadData::ThreadData(const std::string& sanitized_thread_name)
- : next_(NULL),
- next_retired_thread_data_(NULL),
- sanitized_thread_name_(sanitized_thread_name),
- incarnation_count_for_pool_(-1),
- current_stopwatch_(NULL) {
- DCHECK(sanitized_thread_name_.empty() ||
- !isdigit(sanitized_thread_name_.back()));
- PushToHeadOfList(); // Which sets real incarnation_count_for_pool_.
-}
-
-ThreadData::~ThreadData() {
-}
-
-void ThreadData::PushToHeadOfList() {
- // Toss in a hint of randomness (atop the uniniitalized value).
- (void)VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE(&random_number_,
- sizeof(random_number_));
- MSAN_UNPOISON(&random_number_, sizeof(random_number_));
- random_number_ += static_cast<uint32_t>(this - static_cast<ThreadData*>(0));
- random_number_ ^= (Now() - TrackedTime()).InMilliseconds();
-
- DCHECK(!next_);
- base::AutoLock lock(*list_lock_.Pointer());
- incarnation_count_for_pool_ = incarnation_counter_;
- next_ = all_thread_data_list_head_;
- all_thread_data_list_head_ = this;
-}
-
-// static
-ThreadData* ThreadData::first() {
- base::AutoLock lock(*list_lock_.Pointer());
- return all_thread_data_list_head_;
-}
-
-ThreadData* ThreadData::next() const { return next_; }
-
-// static
-void ThreadData::InitializeThreadContext(const std::string& thread_name) {
- if (base::WorkerPool::RunsTasksOnCurrentThread())
- return;
- DCHECK_NE(thread_name, kWorkerThreadSanitizedName);
- EnsureTlsInitialization();
- ThreadData* current_thread_data =
- reinterpret_cast<ThreadData*>(tls_index_.Get());
- if (current_thread_data)
- return; // Browser tests instigate this.
- current_thread_data =
- GetRetiredOrCreateThreadData(SanitizeThreadName(thread_name));
- tls_index_.Set(current_thread_data);
-}
-
-// static
-ThreadData* ThreadData::Get() {
- if (!tls_index_.initialized())
- return NULL; // For unittests only.
- ThreadData* registered = reinterpret_cast<ThreadData*>(tls_index_.Get());
- if (registered)
- return registered;
-
- // We must be a worker thread, since we didn't pre-register.
- ThreadData* worker_thread_data =
- GetRetiredOrCreateThreadData(kWorkerThreadSanitizedName);
- tls_index_.Set(worker_thread_data);
- return worker_thread_data;
-}
-
-// static
-void ThreadData::OnThreadTermination(void* thread_data) {
- DCHECK(thread_data); // TLS should *never* call us with a NULL.
- // We must NOT do any allocations during this callback. There is a chance
- // that the allocator is no longer active on this thread.
- reinterpret_cast<ThreadData*>(thread_data)->OnThreadTerminationCleanup();
-}
-
-void ThreadData::OnThreadTerminationCleanup() {
- // We must NOT do any allocations during this callback. There is a chance that
- // the allocator is no longer active on this thread.
-
- // The list_lock_ was created when we registered the callback, so it won't be
- // allocated here despite the lazy reference.
- base::AutoLock lock(*list_lock_.Pointer());
- if (incarnation_counter_ != incarnation_count_for_pool_)
- return; // ThreadData was constructed in an earlier unit test.
- ++cleanup_count_;
-
- // Add this ThreadData to a retired list so that it can be reused by a thread
- // with the same name sanitized name in the future.
- // |next_retired_thread_data_| is expected to be nullptr for a ThreadData
- // associated with an active thread.
- DCHECK(!next_retired_thread_data_);
- next_retired_thread_data_ = first_retired_thread_data_;
- first_retired_thread_data_ = this;
-}
-
-// static
-void ThreadData::Snapshot(int current_profiling_phase,
- ProcessDataSnapshot* process_data_snapshot) {
- // Get an unchanging copy of a ThreadData list.
- ThreadData* my_list = ThreadData::first();
-
- // Gather data serially.
- // This hackish approach *can* get some slightly corrupt tallies, as we are
- // grabbing values without the protection of a lock, but it has the advantage
- // of working even with threads that don't have message loops. If a user
- // sees any strangeness, they can always just run their stats gathering a
- // second time.
- BirthCountMap birth_counts;
- for (ThreadData* thread_data = my_list; thread_data;
- thread_data = thread_data->next()) {
- thread_data->SnapshotExecutedTasks(current_profiling_phase,
- &process_data_snapshot->phased_snapshots,
- &birth_counts);
- }
-
- // Add births that are still active -- i.e. objects that have tallied a birth,
- // but have not yet tallied a matching death, and hence must be either
- // running, queued up, or being held in limbo for future posting.
- auto* current_phase_tasks =
- &process_data_snapshot->phased_snapshots[current_profiling_phase].tasks;
- for (const auto& birth_count : birth_counts) {
- if (birth_count.second > 0) {
- current_phase_tasks->push_back(
- TaskSnapshot(BirthOnThreadSnapshot(*birth_count.first),
- DeathDataSnapshot(birth_count.second, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0),
- "Still_Alive"));
- }
- }
-}
-
-// static
-void ThreadData::OnProfilingPhaseCompleted(int profiling_phase) {
- // Get an unchanging copy of a ThreadData list.
- ThreadData* my_list = ThreadData::first();
-
- // Add snapshots for all instances of death data in all threads serially.
- // This hackish approach *can* get some slightly corrupt tallies, as we are
- // grabbing values without the protection of a lock, but it has the advantage
- // of working even with threads that don't have message loops. Any corruption
- // shouldn't cause "cascading damage" to anything else (in later phases).
- for (ThreadData* thread_data = my_list; thread_data;
- thread_data = thread_data->next()) {
- thread_data->OnProfilingPhaseCompletedOnThread(profiling_phase);
- }
-}
-
-Births* ThreadData::TallyABirth(const Location& location) {
- BirthMap::iterator it = birth_map_.find(location);
- Births* child;
- if (it != birth_map_.end()) {
- child = it->second;
- child->RecordBirth();
- } else {
- child = new Births(location, *this); // Leak this.
- // Lock since the map may get relocated now, and other threads sometimes
- // snapshot it (but they lock before copying it).
- base::AutoLock lock(map_lock_);
- birth_map_[location] = child;
- }
-
- return child;
-}
-
-void ThreadData::TallyADeath(const Births& births,
- int32_t queue_duration,
- const TaskStopwatch& stopwatch) {
- int32_t run_duration = stopwatch.RunDurationMs();
-
- // Stir in some randomness, plus add constant in case durations are zero.
- const uint32_t kSomePrimeNumber = 2147483647;
- random_number_ += queue_duration + run_duration + kSomePrimeNumber;
- // An address is going to have some randomness to it as well ;-).
- random_number_ ^=
- static_cast<uint32_t>(&births - reinterpret_cast<Births*>(0));
-
- DeathMap::iterator it = death_map_.find(&births);
- DeathData* death_data;
- if (it != death_map_.end()) {
- death_data = &it->second;
- } else {
- base::AutoLock lock(map_lock_); // Lock as the map may get relocated now.
- death_data = &death_map_[&births];
- } // Release lock ASAP.
- death_data->RecordDurations(queue_duration, run_duration, random_number_);
-
-#if BUILDFLAG(ENABLE_MEMORY_TASK_PROFILER)
- if (stopwatch.heap_tracking_enabled()) {
- base::debug::ThreadHeapUsage heap_usage = stopwatch.heap_usage().usage();
- // Saturate the 64 bit counts on conversion to 32 bit storage.
- death_data->RecordAllocations(
- base::saturated_cast<int32_t>(heap_usage.alloc_ops),
- base::saturated_cast<int32_t>(heap_usage.free_ops),
- base::saturated_cast<int32_t>(heap_usage.alloc_bytes),
- base::saturated_cast<int32_t>(heap_usage.free_bytes),
- base::saturated_cast<int32_t>(heap_usage.alloc_overhead_bytes),
- base::saturated_cast<int32_t>(heap_usage.max_allocated_bytes));
- }
-#endif
-}
-
-// static
-Births* ThreadData::TallyABirthIfActive(const Location& location) {
- if (!TrackingStatus())
- return NULL;
- ThreadData* current_thread_data = Get();
- if (!current_thread_data)
- return NULL;
- return current_thread_data->TallyABirth(location);
-}
-
-// static
-void ThreadData::TallyRunOnNamedThreadIfTracking(
- const base::TrackingInfo& completed_task,
- const TaskStopwatch& stopwatch) {
- // Even if we have been DEACTIVATED, we will process any pending births so
- // that our data structures (which counted the outstanding births) remain
- // consistent.
- const Births* births = completed_task.birth_tally;
- if (!births)
- return;
- ThreadData* current_thread_data = stopwatch.GetThreadData();
- if (!current_thread_data)
- return;
-
- // Watch out for a race where status_ is changing, and hence one or both
- // of start_of_run or end_of_run is zero. In that case, we didn't bother to
- // get a time value since we "weren't tracking" and we were trying to be
- // efficient by not calling for a genuine time value. For simplicity, we'll
- // use a default zero duration when we can't calculate a true value.
- TrackedTime start_of_run = stopwatch.StartTime();
- int32_t queue_duration = 0;
- if (!start_of_run.is_null()) {
- queue_duration = (start_of_run - completed_task.EffectiveTimePosted())
- .InMilliseconds();
- }
- current_thread_data->TallyADeath(*births, queue_duration, stopwatch);
-}
-
-// static
-void ThreadData::TallyRunOnWorkerThreadIfTracking(
- const Births* births,
- const TrackedTime& time_posted,
- const TaskStopwatch& stopwatch) {
- // Even if we have been DEACTIVATED, we will process any pending births so
- // that our data structures (which counted the outstanding births) remain
- // consistent.
- if (!births)
- return;
-
- // TODO(jar): Support the option to coalesce all worker-thread activity under
- // one ThreadData instance that uses locks to protect *all* access. This will
- // reduce memory (making it provably bounded), but run incrementally slower
- // (since we'll use locks on TallyABirth and TallyADeath). The good news is
- // that the locks on TallyADeath will be *after* the worker thread has run,
- // and hence nothing will be waiting for the completion (... besides some
- // other thread that might like to run). Also, the worker threads tasks are
- // generally longer, and hence the cost of the lock may perchance be amortized
- // over the long task's lifetime.
- ThreadData* current_thread_data = stopwatch.GetThreadData();
- if (!current_thread_data)
- return;
-
- TrackedTime start_of_run = stopwatch.StartTime();
- int32_t queue_duration = 0;
- if (!start_of_run.is_null()) {
- queue_duration = (start_of_run - time_posted).InMilliseconds();
- }
- current_thread_data->TallyADeath(*births, queue_duration, stopwatch);
-}
-
-// static
-void ThreadData::TallyRunInAScopedRegionIfTracking(
- const Births* births,
- const TaskStopwatch& stopwatch) {
- // Even if we have been DEACTIVATED, we will process any pending births so
- // that our data structures (which counted the outstanding births) remain
- // consistent.
- if (!births)
- return;
-
- ThreadData* current_thread_data = stopwatch.GetThreadData();
- if (!current_thread_data)
- return;
-
- int32_t queue_duration = 0;
- current_thread_data->TallyADeath(*births, queue_duration, stopwatch);
-}
-
-void ThreadData::SnapshotExecutedTasks(
- int current_profiling_phase,
- PhasedProcessDataSnapshotMap* phased_snapshots,
- BirthCountMap* birth_counts) {
- // Get copy of data, so that the data will not change during the iterations
- // and processing.
- BirthMap birth_map;
- DeathsSnapshot deaths;
- SnapshotMaps(current_profiling_phase, &birth_map, &deaths);
-
- for (const auto& birth : birth_map) {
- (*birth_counts)[birth.second] += birth.second->birth_count();
- }
-
- for (const auto& death : deaths) {
- (*birth_counts)[death.first] -= death.first->birth_count();
-
- // For the current death data, walk through all its snapshots, starting from
- // the current one, then from the previous profiling phase etc., and for
- // each snapshot calculate the delta between the snapshot and the previous
- // phase, if any. Store the deltas in the result.
- for (const DeathDataPhaseSnapshot* phase = &death.second; phase;
- phase = phase->prev) {
- const DeathDataSnapshot& death_data =
- phase->prev ? phase->death_data.Delta(phase->prev->death_data)
- : phase->death_data;
-
- if (death_data.count > 0) {
- (*phased_snapshots)[phase->profiling_phase].tasks.push_back(
- TaskSnapshot(BirthOnThreadSnapshot(*death.first), death_data,
- sanitized_thread_name()));
- }
- }
- }
-}
-
-// This may be called from another thread.
-void ThreadData::SnapshotMaps(int profiling_phase,
- BirthMap* birth_map,
- DeathsSnapshot* deaths) {
- base::AutoLock lock(map_lock_);
-
- for (const auto& birth : birth_map_)
- (*birth_map)[birth.first] = birth.second;
-
- for (const auto& death : death_map_) {
- deaths->push_back(std::make_pair(
- death.first,
- DeathDataPhaseSnapshot(profiling_phase, death.second,
- death.second.last_phase_snapshot())));
- }
-}
-
-void ThreadData::OnProfilingPhaseCompletedOnThread(int profiling_phase) {
- base::AutoLock lock(map_lock_);
-
- for (auto& death : death_map_) {
- death.second.OnProfilingPhaseCompleted(profiling_phase);
- }
-}
-
-void ThreadData::EnsureTlsInitialization() {
- if (base::subtle::Acquire_Load(&status_) >= DEACTIVATED)
- return; // Someone else did the initialization.
- // Due to racy lazy initialization in tests, we'll need to recheck status_
- // after we acquire the lock.
-
- // Ensure that we don't double initialize tls. We are called when single
- // threaded in the product, but some tests may be racy and lazy about our
- // initialization.
- base::AutoLock lock(*list_lock_.Pointer());
- if (base::subtle::Acquire_Load(&status_) >= DEACTIVATED)
- return; // Someone raced in here and beat us.
-
- // Perform the "real" TLS initialization now, and leave it intact through
- // process termination.
- if (!tls_index_.initialized()) { // Testing may have initialized this.
- DCHECK_EQ(base::subtle::NoBarrier_Load(&status_), UNINITIALIZED);
- tls_index_.Initialize(&ThreadData::OnThreadTermination);
- DCHECK(tls_index_.initialized());
- } else {
- // TLS was initialzed for us earlier.
- DCHECK_EQ(base::subtle::NoBarrier_Load(&status_), DORMANT_DURING_TESTS);
- }
-
- // Incarnation counter is only significant to testing, as it otherwise will
- // never again change in this process.
- ++incarnation_counter_;
-
- // The lock is not critical for setting status_, but it doesn't hurt. It also
- // ensures that if we have a racy initialization, that we'll bail as soon as
- // we get the lock earlier in this method.
- base::subtle::Release_Store(&status_, kInitialStartupState);
- DCHECK(base::subtle::NoBarrier_Load(&status_) != UNINITIALIZED);
-}
-
-// static
-void ThreadData::InitializeAndSetTrackingStatus(Status status) {
- DCHECK_GE(status, DEACTIVATED);
- DCHECK_LE(status, PROFILING_ACTIVE);
-
- EnsureTlsInitialization(); // No-op if already initialized.
-
- if (status > DEACTIVATED)
- status = PROFILING_ACTIVE;
-
- base::subtle::Release_Store(&status_, status);
-}
-
-// static
-ThreadData::Status ThreadData::status() {
- return static_cast<ThreadData::Status>(base::subtle::Acquire_Load(&status_));
-}
-
-// static
-bool ThreadData::TrackingStatus() {
- return base::subtle::Acquire_Load(&status_) > DEACTIVATED;
-}
-
-// static
-void ThreadData::EnableProfilerTiming() {
- base::subtle::NoBarrier_Store(&g_profiler_timing_enabled, ENABLED_TIMING);
-}
-
-// static
-TrackedTime ThreadData::Now() {
- if (now_function_for_testing_)
- return TrackedTime::FromMilliseconds((*now_function_for_testing_)());
- if (IsProfilerTimingEnabled() && TrackingStatus())
- return TrackedTime::Now();
- return TrackedTime(); // Super fast when disabled, or not compiled.
-}
-
-// static
-void ThreadData::EnsureCleanupWasCalled(int major_threads_shutdown_count) {
- base::AutoLock lock(*list_lock_.Pointer());
-
- // TODO(jar): until this is working on XP, don't run the real test.
-#if 0
- // Verify that we've at least shutdown/cleanup the major namesd threads. The
- // caller should tell us how many thread shutdowns should have taken place by
- // now.
- CHECK_GT(cleanup_count_, major_threads_shutdown_count);
-#endif
-}
-
-// static
-void ThreadData::ShutdownSingleThreadedCleanup(bool leak) {
- // This is only called from test code, where we need to cleanup so that
- // additional tests can be run.
- // We must be single threaded... but be careful anyway.
- InitializeAndSetTrackingStatus(DEACTIVATED);
-
- ThreadData* thread_data_list;
- {
- base::AutoLock lock(*list_lock_.Pointer());
- thread_data_list = all_thread_data_list_head_;
- all_thread_data_list_head_ = NULL;
- ++incarnation_counter_;
- // To be clean, break apart the retired worker list (though we leak them).
- while (first_retired_thread_data_) {
- ThreadData* thread_data = first_retired_thread_data_;
- first_retired_thread_data_ = thread_data->next_retired_thread_data_;
- thread_data->next_retired_thread_data_ = nullptr;
- }
- }
-
- // Put most global static back in pristine shape.
- cleanup_count_ = 0;
- tls_index_.Set(NULL);
- // Almost UNINITIALIZED.
- base::subtle::Release_Store(&status_, DORMANT_DURING_TESTS);
-
- // To avoid any chance of racing in unit tests, which is the only place we
- // call this function, we may sometimes leak all the data structures we
- // recovered, as they may still be in use on threads from prior tests!
- if (leak) {
- ThreadData* thread_data = thread_data_list;
- while (thread_data) {
- ANNOTATE_LEAKING_OBJECT_PTR(thread_data);
- thread_data = thread_data->next();
- }
- return;
- }
-
- // When we want to cleanup (on a single thread), here is what we do.
-
- // Do actual recursive delete in all ThreadData instances.
- while (thread_data_list) {
- ThreadData* next_thread_data = thread_data_list;
- thread_data_list = thread_data_list->next();
-
- for (BirthMap::iterator it = next_thread_data->birth_map_.begin();
- next_thread_data->birth_map_.end() != it; ++it)
- delete it->second; // Delete the Birth Records.
- delete next_thread_data; // Includes all Death Records.
- }
-}
-
-// static
-ThreadData* ThreadData::GetRetiredOrCreateThreadData(
- const std::string& sanitized_thread_name) {
- SCOPED_UMA_HISTOGRAM_TIMER("TrackedObjects.GetRetiredOrCreateThreadData");
-
- {
- base::AutoLock lock(*list_lock_.Pointer());
- ThreadData** pcursor = &first_retired_thread_data_;
- ThreadData* cursor = first_retired_thread_data_;
-
- // Assuming that there aren't more than a few tens of retired ThreadData
- // instances, this lookup should be quick compared to the thread creation
- // time. Retired ThreadData instances cannot be stored in a map because
- // insertions are done from OnThreadTerminationCleanup() where allocations
- // are not allowed.
- //
- // Note: Test processes may have more than a few tens of retired ThreadData
- // instances.
- while (cursor) {
- if (cursor->sanitized_thread_name() == sanitized_thread_name) {
- DCHECK_EQ(*pcursor, cursor);
- *pcursor = cursor->next_retired_thread_data_;
- cursor->next_retired_thread_data_ = nullptr;
- return cursor;
- }
- pcursor = &cursor->next_retired_thread_data_;
- cursor = cursor->next_retired_thread_data_;
- }
- }
-
- return new ThreadData(sanitized_thread_name);
-}
-
-//------------------------------------------------------------------------------
-TaskStopwatch::TaskStopwatch()
- : wallclock_duration_ms_(0),
- current_thread_data_(NULL),
- excluded_duration_ms_(0),
- parent_(NULL) {
-#if DCHECK_IS_ON()
- state_ = CREATED;
- child_ = NULL;
-#endif
-#if BUILDFLAG(ENABLE_MEMORY_TASK_PROFILER)
- heap_tracking_enabled_ =
- base::debug::ThreadHeapUsageTracker::IsHeapTrackingEnabled();
-#endif
-}
-
-TaskStopwatch::~TaskStopwatch() {
-#if DCHECK_IS_ON()
- DCHECK(state_ != RUNNING);
- DCHECK(child_ == NULL);
-#endif
-}
-
-void TaskStopwatch::Start() {
-#if DCHECK_IS_ON()
- DCHECK(state_ == CREATED);
- state_ = RUNNING;
-#endif
-
- start_time_ = ThreadData::Now();
-#if BUILDFLAG(ENABLE_MEMORY_TASK_PROFILER)
- if (heap_tracking_enabled_)
- heap_usage_.Start();
-#endif
-
- current_thread_data_ = ThreadData::Get();
- if (!current_thread_data_)
- return;
-
- parent_ = current_thread_data_->current_stopwatch_;
-#if DCHECK_IS_ON()
- if (parent_) {
- DCHECK(parent_->state_ == RUNNING);
- DCHECK(parent_->child_ == NULL);
- parent_->child_ = this;
- }
-#endif
- current_thread_data_->current_stopwatch_ = this;
-}
-
-void TaskStopwatch::Stop() {
- const TrackedTime end_time = ThreadData::Now();
-#if DCHECK_IS_ON()
- DCHECK(state_ == RUNNING);
- state_ = STOPPED;
- DCHECK(child_ == NULL);
-#endif
-#if BUILDFLAG(ENABLE_MEMORY_TASK_PROFILER)
- if (heap_tracking_enabled_)
- heap_usage_.Stop(true);
-#endif
-
- if (!start_time_.is_null() && !end_time.is_null()) {
- wallclock_duration_ms_ = (end_time - start_time_).InMilliseconds();
- }
-
- if (!current_thread_data_)
- return;
-
- DCHECK(current_thread_data_->current_stopwatch_ == this);
- current_thread_data_->current_stopwatch_ = parent_;
- if (!parent_)
- return;
-
-#if DCHECK_IS_ON()
- DCHECK(parent_->state_ == RUNNING);
- DCHECK(parent_->child_ == this);
- parent_->child_ = NULL;
-#endif
- parent_->excluded_duration_ms_ += wallclock_duration_ms_;
- parent_ = NULL;
-}
-
-TrackedTime TaskStopwatch::StartTime() const {
-#if DCHECK_IS_ON()
- DCHECK(state_ != CREATED);
-#endif
-
- return start_time_;
-}
-
-int32_t TaskStopwatch::RunDurationMs() const {
-#if DCHECK_IS_ON()
- DCHECK(state_ == STOPPED);
-#endif
-
- return wallclock_duration_ms_ - excluded_duration_ms_;
-}
-
-ThreadData* TaskStopwatch::GetThreadData() const {
-#if DCHECK_IS_ON()
- DCHECK(state_ != CREATED);
-#endif
-
- return current_thread_data_;
-}
-
-//------------------------------------------------------------------------------
-// DeathDataPhaseSnapshot
-
-DeathDataPhaseSnapshot::DeathDataPhaseSnapshot(
- int profiling_phase,
- const DeathData& death,
- const DeathDataPhaseSnapshot* prev)
- : profiling_phase(profiling_phase), death_data(death), prev(prev) {}
-
-//------------------------------------------------------------------------------
-// TaskSnapshot
-
-TaskSnapshot::TaskSnapshot() {
-}
-
-TaskSnapshot::TaskSnapshot(const BirthOnThreadSnapshot& birth,
- const DeathDataSnapshot& death_data,
- const std::string& death_sanitized_thread_name)
- : birth(birth),
- death_data(death_data),
- death_sanitized_thread_name(death_sanitized_thread_name) {}
-
-TaskSnapshot::~TaskSnapshot() {
-}
-
-//------------------------------------------------------------------------------
-// ProcessDataPhaseSnapshot
-
-ProcessDataPhaseSnapshot::ProcessDataPhaseSnapshot() {
-}
-
-ProcessDataPhaseSnapshot::ProcessDataPhaseSnapshot(
- const ProcessDataPhaseSnapshot& other) = default;
-
-ProcessDataPhaseSnapshot::~ProcessDataPhaseSnapshot() {
-}
-
-//------------------------------------------------------------------------------
-// ProcessDataPhaseSnapshot
-
-ProcessDataSnapshot::ProcessDataSnapshot()
-#if !defined(OS_NACL)
- : process_id(base::GetCurrentProcId()) {
-#else
- : process_id(base::kNullProcessId) {
-#endif
-}
-
-ProcessDataSnapshot::ProcessDataSnapshot(const ProcessDataSnapshot& other) =
- default;
-
-ProcessDataSnapshot::~ProcessDataSnapshot() {
-}
-
-} // namespace tracked_objects
diff --git a/base/tracked_objects.h b/base/tracked_objects.h
deleted file mode 100644
index 36caec3c6e..0000000000
--- a/base/tracked_objects.h
+++ /dev/null
@@ -1,898 +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_TRACKED_OBJECTS_H_
-#define BASE_TRACKED_OBJECTS_H_
-
-#include <stdint.h>
-
-#include <map>
-#include <set>
-#include <stack>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "base/allocator/features.h"
-#include "base/atomicops.h"
-#include "base/base_export.h"
-#include "base/containers/hash_tables.h"
-#include "base/debug/debugging_flags.h"
-#include "base/debug/thread_heap_usage_tracker.h"
-#include "base/gtest_prod_util.h"
-#include "base/lazy_instance.h"
-#include "base/location.h"
-#include "base/macros.h"
-#include "base/process/process_handle.h"
-#include "base/profiler/tracked_time.h"
-#include "base/synchronization/lock.h"
-#include "base/threading/thread_checker.h"
-#include "base/threading/thread_local_storage.h"
-
-namespace base {
-struct TrackingInfo;
-}
-
-// TrackedObjects provides a database of stats about objects (generally Tasks)
-// that are tracked. Tracking means their birth, death, duration, birth thread,
-// death thread, and birth place are recorded. This data is carefully spread
-// across a series of objects so that the counts and times can be rapidly
-// updated without (usually) having to lock the data, and hence there is usually
-// very little contention caused by the tracking. The data can be viewed via
-// the about:profiler URL, with a variety of sorting and filtering choices.
-//
-// These classes serve as the basis of a profiler of sorts for the Tasks system.
-// As a result, design decisions were made to maximize speed, by minimizing
-// recurring allocation/deallocation, lock contention and data copying. In the
-// "stable" state, which is reached relatively quickly, there is no separate
-// marginal allocation cost associated with construction or destruction of
-// tracked objects, no locks are generally employed, and probably the largest
-// computational cost is associated with obtaining start and stop times for
-// instances as they are created and destroyed.
-//
-// The following describes the life cycle of tracking an instance.
-//
-// First off, when the instance is created, the FROM_HERE macro is expanded
-// to specify the birth place (file, line, function) where the instance was
-// created. That data is used to create a transient Location instance
-// encapsulating the above triple of information. The strings (like __FILE__)
-// are passed around by reference, with the assumption that they are static, and
-// will never go away. This ensures that the strings can be dealt with as atoms
-// with great efficiency (i.e., copying of strings is never needed, and
-// comparisons for equality can be based on pointer comparisons).
-//
-// Next, a Births instance is constructed or found. A Births instance records
-// (in a base class BirthOnThread) references to the static data provided in a
-// Location instance, as well as a pointer to the ThreadData bound to the thread
-// on which the birth takes place (see discussion on ThreadData below). There is
-// at most one Births instance for each Location / ThreadData pair. The derived
-// Births class contains slots for recording statistics about all instances born
-// at the same location. Statistics currently include only the count of
-// instances constructed.
-//
-// Since the base class BirthOnThread contains only constant data, it can be
-// freely accessed by any thread at any time. The statistics must be handled
-// more carefully; they are updated exclusively by the single thread to which
-// the ThreadData is bound at a given time.
-//
-// For Tasks, having now either constructed or found the Births instance
-// described above, a pointer to the Births instance is then recorded into the
-// PendingTask structure. This fact alone is very useful in debugging, when
-// there is a question of where an instance came from. In addition, the birth
-// time is also recorded and used to later evaluate the lifetime duration of the
-// whole Task. As a result of the above embedding, we can find out a Task's
-// location of birth, and name of birth thread, without using any locks, as all
-// that data is constant across the life of the process.
-//
-// The above work *could* also be done for any other object as well by calling
-// TallyABirthIfActive() and TallyRunOnNamedThreadIfTracking() as appropriate.
-//
-// The upper bound for the amount of memory used in the above data structures is
-// the product of the number of ThreadData instances and the number of
-// Locations. Fortunately, Locations are often created on a single thread and
-// the memory utilization is actually fairly restrained.
-//
-// Lastly, when an instance is deleted, the final tallies of statistics are
-// carefully accumulated. That tallying writes into slots (members) in a
-// collection of DeathData instances. For each Births / death ThreadData pair,
-// there is a DeathData instance to record the additional death count, as well
-// as to accumulate the run-time and queue-time durations for the instance as it
-// is destroyed (dies). Since a ThreadData is bound to at most one thread at a
-// time, there is no need to lock such DeathData instances. (i.e., these
-// accumulated stats in a DeathData instance are exclusively updated by the
-// singular owning thread).
-//
-// With the above life cycle description complete, the major remaining detail is
-// explaining how existing Births and DeathData instances are found to avoid
-// redundant allocations.
-//
-// A ThreadData instance maintains maps of Births and DeathData instances. The
-// Births map is indexed by Location and the DeathData map is indexed by
-// Births*. As noted earlier, we can compare Locations very efficiently as we
-// consider the underlying data (file, function, line) to be atoms, and hence
-// pointer comparison is used rather than (slow) string comparisons.
-//
-// The first time that a thread calls ThreadData::InitializeThreadContext() or
-// ThreadData::Get(), a ThreadData instance is bound to it and stored in TLS. If
-// a ThreadData bound to a terminated thread with the same sanitized name (i.e.
-// name without trailing digits) as the current thread is available, it is
-// reused. Otherwise, a new ThreadData instance is instantiated. Since a
-// ThreadData is bound to at most one thread at a time, there is no need to
-// acquire a lock to access its maps. Over time, a ThreadData may be bound to
-// different threads that share the same sanitized name.
-//
-// We maintain a list of all ThreadData instances for the current process. Each
-// ThreadData instance has a pointer to the next one. A static member of
-// ThreadData provides a pointer to the first item on this global list, and
-// access via that all_thread_data_list_head_ item requires the use of the
-// list_lock_.
-//
-// When new ThreadData instances are added to the global list, they are pre-
-// pended, which ensures that any prior acquisition of the list is valid (i.e.,
-// the holder can iterate over it without fear of it changing, or the necessity
-// of using an additional lock. Iterations are actually pretty rare (used
-// primarily for cleanup, or snapshotting data for display), so this lock has
-// very little global performance impact.
-//
-// The above description tries to define the high performance (run time)
-// portions of these classes. After gathering statistics, calls instigated
-// by visiting about:profiler will assemble and aggregate data for display. The
-// following data structures are used for producing such displays. They are
-// not performance critical, and their only major constraint is that they should
-// be able to run concurrently with ongoing augmentation of the birth and death
-// data.
-//
-// This header also exports collection of classes that provide "snapshotted"
-// representations of the core tracked_objects:: classes. These snapshotted
-// representations are designed for safe transmission of the tracked_objects::
-// data across process boundaries. Each consists of:
-// (1) a default constructor, to support the IPC serialization macros,
-// (2) a constructor that extracts data from the type being snapshotted, and
-// (3) the snapshotted data.
-//
-// For a given birth location, information about births is spread across data
-// structures that are asynchronously changing on various threads. For
-// serialization and display purposes, we need to construct TaskSnapshot
-// instances for each combination of birth thread, death thread, and location,
-// along with the count of such lifetimes. We gather such data into a
-// TaskSnapshot instances, so that such instances can be sorted and
-// aggregated (and remain frozen during our processing).
-//
-// Profiling consists of phases. The concrete phase in the sequence of phases
-// is identified by its 0-based index.
-//
-// The ProcessDataPhaseSnapshot struct is a serialized representation of the
-// list of ThreadData objects for a process for a concrete profiling phase. It
-// holds a set of TaskSnapshots. The statistics in a snapshot are gathered
-// asynhcronously relative to their ongoing updates.
-// It is possible, though highly unlikely, that stats could be incorrectly
-// recorded by this process (all data is held in 32 bit ints, but we are not
-// atomically collecting all data, so we could have count that does not, for
-// example, match with the number of durations we accumulated). The advantage
-// to having fast (non-atomic) updates of the data outweighs the minimal risk of
-// a singular corrupt statistic snapshot (only the snapshot could be corrupt,
-// not the underlying and ongoing statistic). In contrast, pointer data that
-// is accessed during snapshotting is completely invariant, and hence is
-// perfectly acquired (i.e., no potential corruption, and no risk of a bad
-// memory reference).
-//
-// TODO(jar): We can implement a Snapshot system that *tries* to grab the
-// snapshots on the source threads *when* they have SingleThreadTaskRunners
-// available (worker threads don't have SingleThreadTaskRunners, and hence
-// gathering from them will continue to be asynchronous). We had an
-// implementation of this in the past, but the difficulty is dealing with
-// threads being terminated. We can *try* to post a task to threads that have a
-// SingleThreadTaskRunner and check if that succeeds (will fail if the thread
-// has been terminated). This *might* be valuable when we are collecting data
-// for upload via UMA (where correctness of data may be more significant than
-// for a single screen of about:profiler).
-//
-// TODO(jar): We need to store DataCollections, and provide facilities for
-// taking the difference between two gathered DataCollections. For now, we're
-// just adding a hack that Reset()s to zero all counts and stats. This is also
-// done in a slightly thread-unsafe fashion, as the resetting is done
-// asynchronously relative to ongoing updates (but all data is 32 bit in size).
-// For basic profiling, this will work "most of the time," and should be
-// sufficient... but storing away DataCollections is the "right way" to do this.
-// We'll accomplish this via JavaScript storage of snapshots, and then we'll
-// remove the Reset() methods. We may also need a short-term-max value in
-// DeathData that is reset (as synchronously as possible) during each snapshot.
-// This will facilitate displaying a max value for each snapshot period.
-
-namespace tracked_objects {
-
-//------------------------------------------------------------------------------
-// For a specific thread, and a specific birth place, the collection of all
-// death info (with tallies for each death thread, to prevent access conflicts).
-class ThreadData;
-class BASE_EXPORT BirthOnThread {
- public:
- BirthOnThread(const Location& location, const ThreadData& current);
-
- const Location& location() const { return location_; }
- const ThreadData* birth_thread() const { return birth_thread_; }
-
- private:
- // File/lineno of birth. This defines the essence of the task, as the context
- // of the birth (construction) often tell what the item is for. This field
- // is const, and hence safe to access from any thread.
- const Location location_;
-
- // The thread that records births into this object. Only this thread is
- // allowed to update birth_count_ (which changes over time).
- const ThreadData* const birth_thread_;
-
- DISALLOW_COPY_AND_ASSIGN(BirthOnThread);
-};
-
-//------------------------------------------------------------------------------
-// A "snapshotted" representation of the BirthOnThread class.
-
-struct BASE_EXPORT BirthOnThreadSnapshot {
- BirthOnThreadSnapshot();
- explicit BirthOnThreadSnapshot(const BirthOnThread& birth);
- ~BirthOnThreadSnapshot();
-
- LocationSnapshot location;
- std::string sanitized_thread_name;
-};
-
-//------------------------------------------------------------------------------
-// A class for accumulating counts of births (without bothering with a map<>).
-
-class BASE_EXPORT Births: public BirthOnThread {
- public:
- Births(const Location& location, const ThreadData& current);
-
- int birth_count() const;
-
- // When we have a birth we update the count for this birthplace.
- void RecordBirth();
-
- private:
- // The number of births on this thread for our location_.
- int birth_count_;
-
- DISALLOW_COPY_AND_ASSIGN(Births);
-};
-
-class DeathData;
-
-//------------------------------------------------------------------------------
-// A "snapshotted" representation of the DeathData class.
-
-struct BASE_EXPORT DeathDataSnapshot {
- DeathDataSnapshot();
-
- // Constructs the snapshot from individual values.
- // The alternative would be taking a DeathData parameter, but this would
- // create a loop since DeathData indirectly refers DeathDataSnapshot. Passing
- // a wrapper structure as a param or using an empty constructor for
- // snapshotting DeathData would be less efficient.
- DeathDataSnapshot(int count,
- int32_t run_duration_sum,
- int32_t run_duration_max,
- int32_t run_duration_sample,
- int32_t queue_duration_sum,
- int32_t queue_duration_max,
- int32_t queue_duration_sample,
- int32_t alloc_ops,
- int32_t free_ops,
- int32_t allocated_bytes,
- int32_t freed_bytes,
- int32_t alloc_overhead_bytes,
- int32_t max_allocated_bytes);
- DeathDataSnapshot(const DeathData& death_data);
- DeathDataSnapshot(const DeathDataSnapshot& other);
- ~DeathDataSnapshot();
-
- // Calculates and returns the delta between this snapshot and an earlier
- // snapshot of the same task |older|.
- DeathDataSnapshot Delta(const DeathDataSnapshot& older) const;
-
- int count;
- int32_t run_duration_sum;
- int32_t run_duration_max;
- int32_t run_duration_sample;
- int32_t queue_duration_sum;
- int32_t queue_duration_max;
- int32_t queue_duration_sample;
-
- int32_t alloc_ops;
- int32_t free_ops;
- int32_t allocated_bytes;
- int32_t freed_bytes;
- int32_t alloc_overhead_bytes;
- int32_t max_allocated_bytes;
-};
-
-//------------------------------------------------------------------------------
-// A "snapshotted" representation of the DeathData for a particular profiling
-// phase. Used as an element of the list of phase snapshots owned by DeathData.
-
-struct DeathDataPhaseSnapshot {
- DeathDataPhaseSnapshot(int profiling_phase,
- const DeathData& death_data,
- const DeathDataPhaseSnapshot* prev);
-
- // Profiling phase at which completion this snapshot was taken.
- int profiling_phase;
-
- // Death data snapshot.
- DeathDataSnapshot death_data;
-
- // Pointer to a snapshot from the previous phase.
- const DeathDataPhaseSnapshot* prev;
-};
-
-//------------------------------------------------------------------------------
-// Information about deaths of a task on a given thread, called "death thread".
-// Access to members of this class is never protected by a lock. The fields
-// are accessed in such a way that corruptions resulting from race conditions
-// are not significant, and don't accumulate as a result of multiple accesses.
-// All invocations of DeathData::OnProfilingPhaseCompleted and
-// ThreadData::SnapshotMaps (which takes DeathData snapshot) in a given process
-// must be called from the same thread. It doesn't matter what thread it is, but
-// it's important the same thread is used as a snapshot thread during the whole
-// process lifetime. All fields except sample_probability_count_ can be
-// snapshotted.
-
-class BASE_EXPORT DeathData {
- public:
- DeathData();
- DeathData(const DeathData& other);
- ~DeathData();
-
- // Update stats for a task destruction (death) that had a Run() time of
- // |duration|, and has had a queueing delay of |queue_duration|.
- void RecordDurations(const int32_t queue_duration,
- const int32_t run_duration,
- const uint32_t random_number);
-
- // Update stats for a task destruction that performed |alloc_ops|
- // allocations, |free_ops| frees, allocated |allocated_bytes| bytes, freed
- // |freed_bytes|, where an estimated |alloc_overhead_bytes| went to heap
- // overhead, and where at most |max_allocated_bytes| were outstanding at any
- // one time.
- // Note that |alloc_overhead_bytes|/|alloc_ops| yields the average estimated
- // heap overhead of allocations in the task, and |allocated_bytes|/|alloc_ops|
- // yields the average size of allocation.
- // Note also that |allocated_bytes|-|freed_bytes| yields the net heap memory
- // usage of the task, which can be negative.
- void RecordAllocations(const uint32_t alloc_ops,
- const uint32_t free_ops,
- const uint32_t allocated_bytes,
- const uint32_t freed_bytes,
- const uint32_t alloc_overhead_bytes,
- const uint32_t max_allocated_bytes);
-
- // Metrics and past snapshots accessors, used only for serialization and in
- // tests.
- int count() const { return base::subtle::NoBarrier_Load(&count_); }
- int32_t run_duration_sum() const {
- return base::subtle::NoBarrier_Load(&run_duration_sum_);
- }
- int32_t run_duration_max() const {
- return base::subtle::NoBarrier_Load(&run_duration_max_);
- }
- int32_t run_duration_sample() const {
- return base::subtle::NoBarrier_Load(&run_duration_sample_);
- }
- int32_t queue_duration_sum() const {
- return base::subtle::NoBarrier_Load(&queue_duration_sum_);
- }
- int32_t queue_duration_max() const {
- return base::subtle::NoBarrier_Load(&queue_duration_max_);
- }
- int32_t queue_duration_sample() const {
- return base::subtle::NoBarrier_Load(&queue_duration_sample_);
- }
- int32_t alloc_ops() const {
- return base::subtle::NoBarrier_Load(&alloc_ops_);
- }
- int32_t free_ops() const { return base::subtle::NoBarrier_Load(&free_ops_); }
- int32_t allocated_bytes() const {
- return base::subtle::NoBarrier_Load(&allocated_bytes_);
- }
- int32_t freed_bytes() const {
- return base::subtle::NoBarrier_Load(&freed_bytes_);
- }
- int32_t alloc_overhead_bytes() const {
- return base::subtle::NoBarrier_Load(&alloc_overhead_bytes_);
- }
- int32_t max_allocated_bytes() const {
- return base::subtle::NoBarrier_Load(&max_allocated_bytes_);
- }
- const DeathDataPhaseSnapshot* last_phase_snapshot() const {
- return last_phase_snapshot_;
- }
-
- // Called when the current profiling phase, identified by |profiling_phase|,
- // ends.
- // Must be called only on the snapshot thread.
- void OnProfilingPhaseCompleted(int profiling_phase);
-
- private:
- // A saturating addition operation for member variables. This elides the
- // use of atomic-primitive reads for members that are only written on the
- // owning thread.
- static void SaturatingMemberAdd(const uint32_t addend,
- base::subtle::Atomic32* sum);
-
- // Members are ordered from most regularly read and updated, to least
- // frequently used. This might help a bit with cache lines.
- // Number of runs seen (divisor for calculating averages).
- // Can be incremented only on the death thread.
- base::subtle::Atomic32 count_;
-
- // Count used in determining probability of selecting exec/queue times from a
- // recorded death as samples.
- // Gets incremented only on the death thread, but can be set to 0 by
- // OnProfilingPhaseCompleted() on the snapshot thread.
- base::subtle::Atomic32 sample_probability_count_;
-
- // Basic tallies, used to compute averages. Can be incremented only on the
- // death thread.
- base::subtle::Atomic32 run_duration_sum_;
- base::subtle::Atomic32 queue_duration_sum_;
- // Max values, used by local visualization routines. These are often read,
- // but rarely updated. The max values get assigned only on the death thread,
- // but these fields can be set to 0 by OnProfilingPhaseCompleted() on the
- // snapshot thread.
- base::subtle::Atomic32 run_duration_max_;
- base::subtle::Atomic32 queue_duration_max_;
-
- // The cumulative number of allocation and free operations.
- base::subtle::Atomic32 alloc_ops_;
- base::subtle::Atomic32 free_ops_;
-
- // The number of bytes allocated by the task.
- base::subtle::Atomic32 allocated_bytes_;
-
- // The number of bytes freed by the task.
- base::subtle::Atomic32 freed_bytes_;
-
- // The cumulative number of overhead bytes. Where available this yields an
- // estimate of the heap overhead for allocations.
- base::subtle::Atomic32 alloc_overhead_bytes_;
-
- // The high-watermark for the number of outstanding heap allocated bytes.
- base::subtle::Atomic32 max_allocated_bytes_;
-
- // Samples, used by crowd sourcing gatherers. These are almost never read,
- // and rarely updated. They can be modified only on the death thread.
- base::subtle::Atomic32 run_duration_sample_;
- base::subtle::Atomic32 queue_duration_sample_;
-
- // Snapshot of this death data made at the last profiling phase completion, if
- // any. DeathData owns the whole list starting with this pointer.
- // Can be accessed only on the snapshot thread.
- const DeathDataPhaseSnapshot* last_phase_snapshot_;
-
- DISALLOW_ASSIGN(DeathData);
-};
-
-//------------------------------------------------------------------------------
-// A temporary collection of data that can be sorted and summarized. It is
-// gathered (carefully) from many threads. Instances are held in arrays and
-// processed, filtered, and rendered.
-// The source of this data was collected on many threads, and is asynchronously
-// changing. The data in this instance is not asynchronously changing.
-
-struct BASE_EXPORT TaskSnapshot {
- TaskSnapshot();
- TaskSnapshot(const BirthOnThreadSnapshot& birth,
- const DeathDataSnapshot& death_data,
- const std::string& death_sanitized_thread_name);
- ~TaskSnapshot();
-
- BirthOnThreadSnapshot birth;
- // Delta between death data for a thread for a certain profiling phase and the
- // snapshot for the pervious phase, if any. Otherwise, just a snapshot.
- DeathDataSnapshot death_data;
- std::string death_sanitized_thread_name;
-};
-
-//------------------------------------------------------------------------------
-// For each thread, we have a ThreadData that stores all tracking info generated
-// on this thread. This prevents the need for locking as data accumulates.
-// We use ThreadLocalStorage to quickly identfy the current ThreadData context.
-// We also have a linked list of ThreadData instances, and that list is used to
-// harvest data from all existing instances.
-
-struct ProcessDataPhaseSnapshot;
-struct ProcessDataSnapshot;
-class BASE_EXPORT TaskStopwatch;
-
-// Map from profiling phase number to the process-wide snapshotted
-// representation of the list of ThreadData objects that died during the given
-// phase.
-typedef std::map<int, ProcessDataPhaseSnapshot> PhasedProcessDataSnapshotMap;
-
-class BASE_EXPORT ThreadData {
- public:
- // Current allowable states of the tracking system. The states can vary
- // between ACTIVE and DEACTIVATED, but can never go back to UNINITIALIZED.
- enum Status {
- UNINITIALIZED, // Pristine, link-time state before running.
- DORMANT_DURING_TESTS, // Only used during testing.
- DEACTIVATED, // No longer recording profiling.
- PROFILING_ACTIVE, // Recording profiles.
- STATUS_LAST = PROFILING_ACTIVE
- };
-
- typedef base::hash_map<Location, Births*, Location::Hash> BirthMap;
- typedef std::map<const Births*, DeathData> DeathMap;
-
- // Initialize the current thread context with a new instance of ThreadData.
- // This is used by all threads that have names, and should be explicitly
- // set *before* any births on the threads have taken place.
- static void InitializeThreadContext(const std::string& thread_name);
-
- // Using Thread Local Store, find the current instance for collecting data.
- // If an instance does not exist, construct one (and remember it for use on
- // this thread.
- // This may return NULL if the system is disabled for any reason.
- static ThreadData* Get();
-
- // Fills |process_data_snapshot| with phased snapshots of all profiling
- // phases, including the current one, identified by |current_profiling_phase|.
- // |current_profiling_phase| is necessary because a child process can start
- // after several phase-changing events, so it needs to receive the current
- // phase number from the browser process to fill the correct entry for the
- // current phase in the |process_data_snapshot| map.
- static void Snapshot(int current_profiling_phase,
- ProcessDataSnapshot* process_data_snapshot);
-
- // Called when the current profiling phase, identified by |profiling_phase|,
- // ends.
- // |profiling_phase| is necessary because a child process can start after
- // several phase-changing events, so it needs to receive the phase number from
- // the browser process to fill the correct entry in the
- // completed_phases_snapshots_ map.
- static void OnProfilingPhaseCompleted(int profiling_phase);
-
- // Finds (or creates) a place to count births from the given location in this
- // thread, and increment that tally.
- // TallyABirthIfActive will returns NULL if the birth cannot be tallied.
- static Births* TallyABirthIfActive(const Location& location);
-
- // Records the end of a timed run of an object. The |completed_task| contains
- // a pointer to a Births, the time_posted, and a delayed_start_time if any.
- // The |start_of_run| indicates when we started to perform the run of the
- // task. The delayed_start_time is non-null for tasks that were posted as
- // delayed tasks, and it indicates when the task should have run (i.e., when
- // it should have posted out of the timer queue, and into the work queue.
- // The |end_of_run| was just obtained by a call to Now() (just after the task
- // finished). It is provided as an argument to help with testing.
- static void TallyRunOnNamedThreadIfTracking(
- const base::TrackingInfo& completed_task,
- const TaskStopwatch& stopwatch);
-
- // Record the end of a timed run of an object. The |birth| is the record for
- // the instance, the |time_posted| records that instant, which is presumed to
- // be when the task was posted into a queue to run on a worker thread.
- // The |start_of_run| is when the worker thread started to perform the run of
- // the task.
- // The |end_of_run| was just obtained by a call to Now() (just after the task
- // finished).
- static void TallyRunOnWorkerThreadIfTracking(const Births* births,
- const TrackedTime& time_posted,
- const TaskStopwatch& stopwatch);
-
- // Record the end of execution in region, generally corresponding to a scope
- // being exited.
- static void TallyRunInAScopedRegionIfTracking(const Births* births,
- const TaskStopwatch& stopwatch);
-
- const std::string& sanitized_thread_name() const {
- return sanitized_thread_name_;
- }
-
- // Initializes all statics if needed (this initialization call should be made
- // while we are single threaded).
- static void EnsureTlsInitialization();
-
- // Sets internal status_.
- // If |status| is false, then status_ is set to DEACTIVATED.
- // If |status| is true, then status_ is set to PROFILING_ACTIVE.
- static void InitializeAndSetTrackingStatus(Status status);
-
- static Status status();
-
- // Indicate if any sort of profiling is being done (i.e., we are more than
- // DEACTIVATED).
- static bool TrackingStatus();
-
- // Enables profiler timing.
- static void EnableProfilerTiming();
-
- // Provide a time function that does nothing (runs fast) when we don't have
- // the profiler enabled. It will generally be optimized away when it is
- // ifdef'ed to be small enough (allowing the profiler to be "compiled out" of
- // the code).
- static TrackedTime Now();
-
- // This function can be called at process termination to validate that thread
- // cleanup routines have been called for at least some number of named
- // threads.
- static void EnsureCleanupWasCalled(int major_threads_shutdown_count);
-
- private:
- friend class TaskStopwatch;
- // Allow only tests to call ShutdownSingleThreadedCleanup. We NEVER call it
- // in production code.
- // TODO(jar): Make this a friend in DEBUG only, so that the optimizer has a
- // better change of optimizing (inlining? etc.) private methods (knowing that
- // there will be no need for an external entry point).
- friend class TrackedObjectsTest;
- FRIEND_TEST_ALL_PREFIXES(TrackedObjectsTest, MinimalStartupShutdown);
- FRIEND_TEST_ALL_PREFIXES(TrackedObjectsTest, TinyStartupShutdown);
-
- // Type for an alternate timer function (testing only).
- typedef unsigned int NowFunction();
-
- typedef std::map<const BirthOnThread*, int> BirthCountMap;
- typedef std::vector<std::pair<const Births*, DeathDataPhaseSnapshot>>
- DeathsSnapshot;
-
- explicit ThreadData(const std::string& sanitized_thread_name);
- ~ThreadData();
-
- // Push this instance to the head of all_thread_data_list_head_, linking it to
- // the previous head. This is performed after each construction, and leaves
- // the instance permanently on that list.
- void PushToHeadOfList();
-
- // (Thread safe) Get start of list of all ThreadData instances using the lock.
- static ThreadData* first();
-
- // Iterate through the null terminated list of ThreadData instances.
- ThreadData* next() const;
-
-
- // In this thread's data, record a new birth.
- Births* TallyABirth(const Location& location);
-
- // Find a place to record a death on this thread.
- void TallyADeath(const Births& births,
- int32_t queue_duration,
- const TaskStopwatch& stopwatch);
-
- // Snapshots (under a lock) the profiled data for the tasks for this thread
- // and writes all of the executed tasks' data -- i.e. the data for all
- // profiling phases (including the current one: |current_profiling_phase|) for
- // the tasks with with entries in the death_map_ -- into |phased_snapshots|.
- // Also updates the |birth_counts| tally for each task to keep track of the
- // number of living instances of the task -- that is, each task maps to the
- // number of births for the task that have not yet been balanced by a death.
- void SnapshotExecutedTasks(int current_profiling_phase,
- PhasedProcessDataSnapshotMap* phased_snapshots,
- BirthCountMap* birth_counts);
-
- // Using our lock, make a copy of the specified maps. This call may be made
- // on non-local threads, which necessitate the use of the lock to prevent
- // the map(s) from being reallocated while they are copied.
- void SnapshotMaps(int profiling_phase,
- BirthMap* birth_map,
- DeathsSnapshot* deaths);
-
- // Called for this thread when the current profiling phase, identified by
- // |profiling_phase|, ends.
- void OnProfilingPhaseCompletedOnThread(int profiling_phase);
-
- // This method is called by the TLS system when a thread terminates.
- // The argument may be NULL if this thread has never tracked a birth or death.
- static void OnThreadTermination(void* thread_data);
-
- // This method should be called when a worker thread terminates, so that we
- // can save all the thread data into a cache of reusable ThreadData instances.
- void OnThreadTerminationCleanup();
-
- // Cleans up data structures, and returns statics to near pristine (mostly
- // uninitialized) state. If there is any chance that other threads are still
- // using the data structures, then the |leak| argument should be passed in as
- // true, and the data structures (birth maps, death maps, ThreadData
- // insntances, etc.) will be leaked and not deleted. If you have joined all
- // threads since the time that InitializeAndSetTrackingStatus() was called,
- // then you can pass in a |leak| value of false, and this function will
- // delete recursively all data structures, starting with the list of
- // ThreadData instances.
- static void ShutdownSingleThreadedCleanup(bool leak);
-
- // Returns a ThreadData instance for a thread whose sanitized name is
- // |sanitized_thread_name|. The returned instance may have been extracted from
- // the list of retired ThreadData instances or newly allocated.
- static ThreadData* GetRetiredOrCreateThreadData(
- const std::string& sanitized_thread_name);
-
- // When non-null, this specifies an external function that supplies monotone
- // increasing time functcion.
- static NowFunction* now_function_for_testing_;
-
- // We use thread local store to identify which ThreadData to interact with.
- static base::ThreadLocalStorage::StaticSlot tls_index_;
-
- // Linked list of ThreadData instances that were associated with threads that
- // have been terminated and that have not been associated with a new thread
- // since then. This is only accessed while |list_lock_| is held.
- static ThreadData* first_retired_thread_data_;
-
- // Link to the most recently created instance (starts a null terminated list).
- // The list is traversed by about:profiler when it needs to snapshot data.
- // This is only accessed while list_lock_ is held.
- static ThreadData* all_thread_data_list_head_;
-
- // The number of times TLS has called us back to cleanup a ThreadData
- // instance. This is only accessed while list_lock_ is held.
- static int cleanup_count_;
-
- // Incarnation sequence number, indicating how many times (during unittests)
- // we've either transitioned out of UNINITIALIZED, or into that state. This
- // value is only accessed while the list_lock_ is held.
- static int incarnation_counter_;
-
- // Protection for access to all_thread_data_list_head_, and to
- // unregistered_thread_data_pool_. This lock is leaked at shutdown.
- // The lock is very infrequently used, so we can afford to just make a lazy
- // instance and be safe.
- static base::LazyInstance<base::Lock>::Leaky list_lock_;
-
- // We set status_ to SHUTDOWN when we shut down the tracking service.
- static base::subtle::Atomic32 status_;
-
- // Link to next instance (null terminated list). Used to globally track all
- // registered instances (corresponds to all registered threads where we keep
- // data). Only modified in the constructor.
- ThreadData* next_;
-
- // Pointer to another retired ThreadData instance. This value is nullptr if
- // this is associated with an active thread.
- ThreadData* next_retired_thread_data_;
-
- // The name of the thread that is being recorded, with all trailing digits
- // replaced with a single "*" character.
- const std::string sanitized_thread_name_;
-
- // A map used on each thread to keep track of Births on this thread.
- // This map should only be accessed on the thread it was constructed on.
- // When a snapshot is needed, this structure can be locked in place for the
- // duration of the snapshotting activity.
- BirthMap birth_map_;
-
- // Similar to birth_map_, this records informations about death of tracked
- // instances (i.e., when a tracked instance was destroyed on this thread).
- // It is locked before changing, and hence other threads may access it by
- // locking before reading it.
- DeathMap death_map_;
-
- // Lock to protect *some* access to BirthMap and DeathMap. The maps are
- // regularly read and written on this thread, but may only be read from other
- // threads. To support this, we acquire this lock if we are writing from this
- // thread, or reading from another thread. For reading from this thread we
- // don't need a lock, as there is no potential for a conflict since the
- // writing is only done from this thread.
- mutable base::Lock map_lock_;
-
- // A random number that we used to select decide which sample to keep as a
- // representative sample in each DeathData instance. We can't start off with
- // much randomness (because we can't call RandInt() on all our threads), so
- // we stir in more and more as we go.
- uint32_t random_number_;
-
- // Record of what the incarnation_counter_ was when this instance was created.
- // If the incarnation_counter_ has changed, then we avoid pushing into the
- // pool (this is only critical in tests which go through multiple
- // incarnations).
- int incarnation_count_for_pool_;
-
- // Most recently started (i.e. most nested) stopwatch on the current thread,
- // if it exists; NULL otherwise.
- TaskStopwatch* current_stopwatch_;
-
- DISALLOW_COPY_AND_ASSIGN(ThreadData);
-};
-
-//------------------------------------------------------------------------------
-// Stopwatch to measure task run time or simply create a time interval that will
-// be subtracted from the current most nested task's run time. Stopwatches
-// coordinate with the stopwatches in which they are nested to avoid
-// double-counting nested tasks run times.
-
-class BASE_EXPORT TaskStopwatch {
- public:
- // Starts the stopwatch.
- TaskStopwatch();
- ~TaskStopwatch();
-
- // Starts stopwatch.
- void Start();
-
- // Stops stopwatch.
- void Stop();
-
- // Returns the start time.
- TrackedTime StartTime() const;
-
- // Task's duration is calculated as the wallclock duration between starting
- // and stopping this stopwatch, minus the wallclock durations of any other
- // instances that are immediately nested in this one, started and stopped on
- // this thread during that period.
- int32_t RunDurationMs() const;
-
-#if BUILDFLAG(ENABLE_MEMORY_TASK_PROFILER)
- const base::debug::ThreadHeapUsageTracker& heap_usage() const {
- return heap_usage_;
- }
- bool heap_tracking_enabled() const { return heap_tracking_enabled_; }
-#endif
-
- // Returns tracking info for the current thread.
- ThreadData* GetThreadData() const;
-
- private:
- // Time when the stopwatch was started.
- TrackedTime start_time_;
-
-#if BUILDFLAG(ENABLE_MEMORY_TASK_PROFILER)
- base::debug::ThreadHeapUsageTracker heap_usage_;
- bool heap_tracking_enabled_;
-#endif
-
- // Wallclock duration of the task.
- int32_t wallclock_duration_ms_;
-
- // Tracking info for the current thread.
- ThreadData* current_thread_data_;
-
- // Sum of wallclock durations of all stopwatches that were directly nested in
- // this one.
- int32_t excluded_duration_ms_;
-
- // Stopwatch which was running on our thread when this stopwatch was started.
- // That preexisting stopwatch must be adjusted to the exclude the wallclock
- // duration of this stopwatch.
- TaskStopwatch* parent_;
-
-#if DCHECK_IS_ON()
- // State of the stopwatch. Stopwatch is first constructed in a created state
- // state, then is optionally started/stopped, then destructed.
- enum { CREATED, RUNNING, STOPPED } state_;
-
- // Currently running stopwatch that is directly nested in this one, if such
- // stopwatch exists. NULL otherwise.
- TaskStopwatch* child_;
-#endif
-};
-
-//------------------------------------------------------------------------------
-// A snapshotted representation of the list of ThreadData objects for a process,
-// for a single profiling phase.
-
-struct BASE_EXPORT ProcessDataPhaseSnapshot {
- public:
- ProcessDataPhaseSnapshot();
- ProcessDataPhaseSnapshot(const ProcessDataPhaseSnapshot& other);
- ~ProcessDataPhaseSnapshot();
-
- std::vector<TaskSnapshot> tasks;
-};
-
-//------------------------------------------------------------------------------
-// A snapshotted representation of the list of ThreadData objects for a process,
-// for all profiling phases, including the current one.
-
-struct BASE_EXPORT ProcessDataSnapshot {
- public:
- ProcessDataSnapshot();
- ProcessDataSnapshot(const ProcessDataSnapshot& other);
- ~ProcessDataSnapshot();
-
- PhasedProcessDataSnapshotMap phased_snapshots;
- base::ProcessId process_id;
-};
-
-} // namespace tracked_objects
-
-#endif // BASE_TRACKED_OBJECTS_H_
diff --git a/base/tracked_objects_unittest.cc b/base/tracked_objects_unittest.cc
deleted file mode 100644
index f208e3c981..0000000000
--- a/base/tracked_objects_unittest.cc
+++ /dev/null
@@ -1,1375 +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.
-
-// Test of classes in the tracked_objects.h classes.
-
-#include "base/tracked_objects.h"
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <memory>
-
-#include "base/macros.h"
-#include "base/process/process_handle.h"
-#include "base/strings/stringprintf.h"
-#include "base/threading/thread.h"
-#include "base/time/time.h"
-#include "base/tracking_info.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-const int kLineNumber = 1776;
-const char kFile[] = "FixedUnitTestFileName";
-const char kWorkerThreadName[] = "WorkerThread-*";
-const char kMainThreadName[] = "SomeMainThreadName";
-const char kStillAlive[] = "Still_Alive";
-
-const int32_t kAllocOps = 23;
-const int32_t kFreeOps = 27;
-const int32_t kAllocatedBytes = 59934;
-const int32_t kFreedBytes = 2 * kAllocatedBytes;
-const int32_t kAllocOverheadBytes = kAllocOps * 8;
-const int32_t kMaxAllocatedBytes = kAllocatedBytes / 2;
-
-namespace tracked_objects {
-
-class TrackedObjectsTest : public testing::Test {
- protected:
- TrackedObjectsTest() {
- // On entry, leak any database structures in case they are still in use by
- // prior threads.
- ThreadData::ShutdownSingleThreadedCleanup(true);
-
- test_time_ = 0;
- ThreadData::now_function_for_testing_ = &TrackedObjectsTest::GetTestTime;
- }
-
- ~TrackedObjectsTest() override {
- // We should not need to leak any structures we create, since we are
- // single threaded, and carefully accounting for items.
- ThreadData::ShutdownSingleThreadedCleanup(false);
- }
-
- // Reset the profiler state.
- void Reset() {
- ThreadData::ShutdownSingleThreadedCleanup(false);
- test_time_ = 0;
- }
-
- // Simulate a birth on the thread named |thread_name|, at the given
- // |location|.
- void TallyABirth(const Location& location, const std::string& thread_name) {
- // If the |thread_name| is empty, we don't initialize system with a thread
- // name, so we're viewed as a worker thread.
- if (!thread_name.empty())
- ThreadData::InitializeThreadContext(kMainThreadName);
-
- // Do not delete |birth|. We don't own it.
- Births* birth = ThreadData::TallyABirthIfActive(location);
-
- if (ThreadData::status() == ThreadData::DEACTIVATED)
- EXPECT_EQ(reinterpret_cast<Births*>(NULL), birth);
- else
- EXPECT_NE(reinterpret_cast<Births*>(NULL), birth);
- }
-
- // Helper function to verify the most common test expectations.
- void ExpectSimpleProcessData(const ProcessDataSnapshot& process_data,
- const std::string& function_name,
- const std::string& birth_thread,
- const std::string& death_thread,
- int count,
- int run_ms,
- int queue_ms) {
- ASSERT_EQ(1u, process_data.phased_snapshots.size());
- auto it = process_data.phased_snapshots.find(0);
- ASSERT_TRUE(it != process_data.phased_snapshots.end());
- const ProcessDataPhaseSnapshot& process_data_phase = it->second;
-
- ASSERT_EQ(1u, process_data_phase.tasks.size());
-
- EXPECT_EQ(kFile, process_data_phase.tasks[0].birth.location.file_name);
- EXPECT_EQ(function_name,
- process_data_phase.tasks[0].birth.location.function_name);
- EXPECT_EQ(kLineNumber,
- process_data_phase.tasks[0].birth.location.line_number);
-
- EXPECT_EQ(birth_thread,
- process_data_phase.tasks[0].birth.sanitized_thread_name);
-
- EXPECT_EQ(count, process_data_phase.tasks[0].death_data.count);
- EXPECT_EQ(count * run_ms,
- process_data_phase.tasks[0].death_data.run_duration_sum);
- EXPECT_EQ(run_ms, process_data_phase.tasks[0].death_data.run_duration_max);
- EXPECT_EQ(run_ms,
- process_data_phase.tasks[0].death_data.run_duration_sample);
- EXPECT_EQ(count * queue_ms,
- process_data_phase.tasks[0].death_data.queue_duration_sum);
- EXPECT_EQ(queue_ms,
- process_data_phase.tasks[0].death_data.queue_duration_max);
- EXPECT_EQ(queue_ms,
- process_data_phase.tasks[0].death_data.queue_duration_sample);
-
- EXPECT_EQ(death_thread,
- process_data_phase.tasks[0].death_sanitized_thread_name);
-
- EXPECT_EQ(base::GetCurrentProcId(), process_data.process_id);
- }
-
- // Sets time that will be returned by ThreadData::Now().
- static void SetTestTime(unsigned int test_time) { test_time_ = test_time; }
-
- int GetNumThreadData() {
- int num_thread_data = 0;
- ThreadData* current = ThreadData::first();
- while (current) {
- ++num_thread_data;
- current = current->next();
- }
- return num_thread_data;
- }
-
- private:
- // Returns test time in milliseconds.
- static unsigned int GetTestTime() { return test_time_; }
-
- // Test time in milliseconds.
- static unsigned int test_time_;
-};
-
-// static
-unsigned int TrackedObjectsTest::test_time_;
-
-TEST_F(TrackedObjectsTest, TaskStopwatchNoStartStop) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- // Check that creating and destroying a stopwatch without starting it doesn't
- // crash.
- TaskStopwatch stopwatch;
-}
-
-TEST_F(TrackedObjectsTest, MinimalStartupShutdown) {
- // Minimal test doesn't even create any tasks.
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- EXPECT_FALSE(ThreadData::first()); // No activity even on this thread.
- ThreadData* data = ThreadData::Get();
- EXPECT_TRUE(ThreadData::first()); // Now class was constructed.
- ASSERT_TRUE(data);
- EXPECT_FALSE(data->next());
- EXPECT_EQ(data, ThreadData::Get());
- ThreadData::BirthMap birth_map;
- ThreadData::DeathsSnapshot deaths;
- data->SnapshotMaps(0, &birth_map, &deaths);
- EXPECT_EQ(0u, birth_map.size());
- EXPECT_EQ(0u, deaths.size());
-
- // Clean up with no leaking.
- Reset();
-
- // Do it again, just to be sure we reset state completely.
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
- EXPECT_FALSE(ThreadData::first()); // No activity even on this thread.
- data = ThreadData::Get();
- EXPECT_TRUE(ThreadData::first()); // Now class was constructed.
- ASSERT_TRUE(data);
- EXPECT_FALSE(data->next());
- EXPECT_EQ(data, ThreadData::Get());
- birth_map.clear();
- deaths.clear();
- data->SnapshotMaps(0, &birth_map, &deaths);
- EXPECT_EQ(0u, birth_map.size());
- EXPECT_EQ(0u, deaths.size());
-}
-
-TEST_F(TrackedObjectsTest, TinyStartupShutdown) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- // Instigate tracking on a single tracked object, on our thread.
- const char kFunction[] = "TinyStartupShutdown";
- Location location(kFunction, kFile, kLineNumber, NULL);
- ThreadData::TallyABirthIfActive(location);
-
- ThreadData* data = ThreadData::first();
- ASSERT_TRUE(data);
- EXPECT_FALSE(data->next());
- EXPECT_EQ(data, ThreadData::Get());
- ThreadData::BirthMap birth_map;
- ThreadData::DeathsSnapshot deaths;
- data->SnapshotMaps(0, &birth_map, &deaths);
- EXPECT_EQ(1u, birth_map.size()); // 1 birth location.
- EXPECT_EQ(1, birth_map.begin()->second->birth_count()); // 1 birth.
- EXPECT_EQ(0u, deaths.size()); // No deaths.
-
-
- // Now instigate another birth, while we are timing the run of the first
- // execution.
- // Create a child (using the same birth location).
- // TrackingInfo will call TallyABirth() during construction.
- const int32_t start_time = 1;
- base::TimeTicks kBogusBirthTime = base::TimeTicks() +
- base::TimeDelta::FromMilliseconds(start_time);
- base::TrackingInfo pending_task(location, kBogusBirthTime);
- SetTestTime(1);
- TaskStopwatch stopwatch;
- stopwatch.Start();
- // Finally conclude the outer run.
- const int32_t time_elapsed = 1000;
- SetTestTime(start_time + time_elapsed);
- stopwatch.Stop();
-
- ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, stopwatch);
-
- birth_map.clear();
- deaths.clear();
- data->SnapshotMaps(0, &birth_map, &deaths);
- EXPECT_EQ(1u, birth_map.size()); // 1 birth location.
- EXPECT_EQ(2, birth_map.begin()->second->birth_count()); // 2 births.
- EXPECT_EQ(1u, deaths.size()); // 1 location.
- EXPECT_EQ(1, deaths.begin()->second.death_data.count); // 1 death.
-
- // The births were at the same location as the one known death.
- EXPECT_EQ(birth_map.begin()->second, deaths.begin()->first);
-
- ProcessDataSnapshot process_data;
- ThreadData::Snapshot(0, &process_data);
-
- ASSERT_EQ(1u, process_data.phased_snapshots.size());
- auto it = process_data.phased_snapshots.find(0);
- ASSERT_TRUE(it != process_data.phased_snapshots.end());
- const ProcessDataPhaseSnapshot& process_data_phase = it->second;
- ASSERT_EQ(1u, process_data_phase.tasks.size());
- EXPECT_EQ(kFile, process_data_phase.tasks[0].birth.location.file_name);
- EXPECT_EQ(kFunction,
- process_data_phase.tasks[0].birth.location.function_name);
- EXPECT_EQ(kLineNumber,
- process_data_phase.tasks[0].birth.location.line_number);
- EXPECT_EQ(kWorkerThreadName,
- process_data_phase.tasks[0].birth.sanitized_thread_name);
- EXPECT_EQ(1, process_data_phase.tasks[0].death_data.count);
- EXPECT_EQ(time_elapsed,
- process_data_phase.tasks[0].death_data.run_duration_sum);
- EXPECT_EQ(time_elapsed,
- process_data_phase.tasks[0].death_data.run_duration_max);
- EXPECT_EQ(time_elapsed,
- process_data_phase.tasks[0].death_data.run_duration_sample);
- EXPECT_EQ(0, process_data_phase.tasks[0].death_data.queue_duration_sum);
- EXPECT_EQ(0, process_data_phase.tasks[0].death_data.queue_duration_max);
- EXPECT_EQ(0, process_data_phase.tasks[0].death_data.queue_duration_sample);
- EXPECT_EQ(kWorkerThreadName,
- process_data_phase.tasks[0].death_sanitized_thread_name);
-}
-
-TEST_F(TrackedObjectsTest, DeathDataTestRecordDurations) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- std::unique_ptr<DeathData> data(new DeathData());
- ASSERT_NE(data, nullptr);
- EXPECT_EQ(data->run_duration_sum(), 0);
- EXPECT_EQ(data->run_duration_max(), 0);
- EXPECT_EQ(data->run_duration_sample(), 0);
- EXPECT_EQ(data->queue_duration_sum(), 0);
- EXPECT_EQ(data->queue_duration_max(), 0);
- EXPECT_EQ(data->queue_duration_sample(), 0);
- EXPECT_EQ(data->count(), 0);
- EXPECT_EQ(nullptr, data->last_phase_snapshot());
-
- int32_t run_ms = 42;
- int32_t queue_ms = 8;
-
- const int kUnrandomInt = 0; // Fake random int that ensure we sample data.
- data->RecordDurations(queue_ms, run_ms, kUnrandomInt);
- EXPECT_EQ(data->run_duration_sum(), run_ms);
- EXPECT_EQ(data->run_duration_max(), run_ms);
- EXPECT_EQ(data->run_duration_sample(), run_ms);
- EXPECT_EQ(data->queue_duration_sum(), queue_ms);
- EXPECT_EQ(data->queue_duration_max(), queue_ms);
- EXPECT_EQ(data->queue_duration_sample(), queue_ms);
- EXPECT_EQ(data->count(), 1);
- EXPECT_EQ(nullptr, data->last_phase_snapshot());
-
- data->RecordDurations(queue_ms, run_ms, kUnrandomInt);
- EXPECT_EQ(data->run_duration_sum(), run_ms + run_ms);
- EXPECT_EQ(data->run_duration_max(), run_ms);
- EXPECT_EQ(data->run_duration_sample(), run_ms);
- EXPECT_EQ(data->queue_duration_sum(), queue_ms + queue_ms);
- EXPECT_EQ(data->queue_duration_max(), queue_ms);
- EXPECT_EQ(data->queue_duration_sample(), queue_ms);
- EXPECT_EQ(data->count(), 2);
- EXPECT_EQ(nullptr, data->last_phase_snapshot());
-}
-
-TEST_F(TrackedObjectsTest, DeathDataTestRecordAllocations) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- std::unique_ptr<DeathData> data(new DeathData());
- ASSERT_NE(data, nullptr);
-
- EXPECT_EQ(data->alloc_ops(), 0);
- EXPECT_EQ(data->free_ops(), 0);
- EXPECT_EQ(data->allocated_bytes(), 0);
- EXPECT_EQ(data->freed_bytes(), 0);
- EXPECT_EQ(data->alloc_overhead_bytes(), 0);
- EXPECT_EQ(data->max_allocated_bytes(), 0);
-
- EXPECT_EQ(nullptr, data->last_phase_snapshot());
-
- data->RecordAllocations(kAllocOps, kFreeOps, kAllocatedBytes, kFreedBytes,
- kAllocOverheadBytes, kMaxAllocatedBytes);
- EXPECT_EQ(data->alloc_ops(), kAllocOps);
- EXPECT_EQ(data->free_ops(), kFreeOps);
- EXPECT_EQ(data->allocated_bytes(), kAllocatedBytes);
- EXPECT_EQ(data->freed_bytes(), kFreedBytes);
- EXPECT_EQ(data->alloc_overhead_bytes(), kAllocOverheadBytes);
- EXPECT_EQ(data->max_allocated_bytes(), kMaxAllocatedBytes);
-
- // Record another batch, with a smaller max.
- const int32_t kSmallerMaxAllocatedBytes = kMaxAllocatedBytes / 2;
- data->RecordAllocations(kAllocOps, kFreeOps, kAllocatedBytes, kFreedBytes,
- kAllocOverheadBytes, kSmallerMaxAllocatedBytes);
- EXPECT_EQ(data->alloc_ops(), 2 * kAllocOps);
- EXPECT_EQ(data->free_ops(), 2 * kFreeOps);
- EXPECT_EQ(data->allocated_bytes(), 2 * kAllocatedBytes);
- EXPECT_EQ(data->freed_bytes(), 2 * kFreedBytes);
- EXPECT_EQ(data->alloc_overhead_bytes(), 2 * kAllocOverheadBytes);
- EXPECT_EQ(data->max_allocated_bytes(), kMaxAllocatedBytes);
-
- // Now with a larger max.
- const int32_t kLargerMaxAllocatedBytes = kMaxAllocatedBytes * 2;
- data->RecordAllocations(kAllocOps, kFreeOps, kAllocatedBytes, kFreedBytes,
- kAllocOverheadBytes, kLargerMaxAllocatedBytes);
- EXPECT_EQ(data->alloc_ops(), 3 * kAllocOps);
- EXPECT_EQ(data->free_ops(), 3 * kFreeOps);
- EXPECT_EQ(data->allocated_bytes(), 3 * kAllocatedBytes);
- EXPECT_EQ(data->freed_bytes(), 3 * kFreedBytes);
- EXPECT_EQ(data->alloc_overhead_bytes(), 3 * kAllocOverheadBytes);
- EXPECT_EQ(data->max_allocated_bytes(), kLargerMaxAllocatedBytes);
-
- // Saturate everything.
- data->RecordAllocations(INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX, INT_MAX);
- EXPECT_EQ(data->alloc_ops(), INT_MAX);
- EXPECT_EQ(data->free_ops(), INT_MAX);
- EXPECT_EQ(data->allocated_bytes(), INT_MAX);
- EXPECT_EQ(data->freed_bytes(), INT_MAX);
- EXPECT_EQ(data->alloc_overhead_bytes(), INT_MAX);
- EXPECT_EQ(data->max_allocated_bytes(), INT_MAX);
-}
-
-TEST_F(TrackedObjectsTest, DeathDataTest2Phases) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- std::unique_ptr<DeathData> data(new DeathData());
- ASSERT_NE(data, nullptr);
-
- const int32_t run_ms = 42;
- const int32_t queue_ms = 8;
-
- const int kUnrandomInt = 0; // Fake random int that ensure we sample data.
- data->RecordDurations(queue_ms, run_ms, kUnrandomInt);
- data->RecordDurations(queue_ms, run_ms, kUnrandomInt);
-
- data->RecordAllocations(kAllocOps, kFreeOps, kAllocatedBytes, kFreedBytes,
- kAllocOverheadBytes, kMaxAllocatedBytes);
-
- data->OnProfilingPhaseCompleted(123);
- EXPECT_EQ(data->run_duration_sum(), run_ms + run_ms);
- EXPECT_EQ(data->run_duration_max(), 0);
- EXPECT_EQ(data->run_duration_sample(), run_ms);
- EXPECT_EQ(data->queue_duration_sum(), queue_ms + queue_ms);
- EXPECT_EQ(data->queue_duration_max(), 0);
- EXPECT_EQ(data->queue_duration_sample(), queue_ms);
- EXPECT_EQ(data->count(), 2);
-
- EXPECT_EQ(data->alloc_ops(), kAllocOps);
- EXPECT_EQ(data->free_ops(), kFreeOps);
- EXPECT_EQ(data->allocated_bytes(), kAllocatedBytes);
- EXPECT_EQ(data->freed_bytes(), kFreedBytes);
- EXPECT_EQ(data->alloc_overhead_bytes(), kAllocOverheadBytes);
- EXPECT_EQ(data->max_allocated_bytes(), kMaxAllocatedBytes);
-
- ASSERT_NE(nullptr, data->last_phase_snapshot());
- EXPECT_EQ(123, data->last_phase_snapshot()->profiling_phase);
- EXPECT_EQ(2, data->last_phase_snapshot()->death_data.count);
- EXPECT_EQ(2 * run_ms,
- data->last_phase_snapshot()->death_data.run_duration_sum);
- EXPECT_EQ(run_ms, data->last_phase_snapshot()->death_data.run_duration_max);
- EXPECT_EQ(run_ms,
- data->last_phase_snapshot()->death_data.run_duration_sample);
- EXPECT_EQ(2 * queue_ms,
- data->last_phase_snapshot()->death_data.queue_duration_sum);
- EXPECT_EQ(queue_ms,
- data->last_phase_snapshot()->death_data.queue_duration_max);
- EXPECT_EQ(queue_ms,
- data->last_phase_snapshot()->death_data.queue_duration_sample);
-
- EXPECT_EQ(kAllocOps, data->last_phase_snapshot()->death_data.alloc_ops);
- EXPECT_EQ(kFreeOps, data->last_phase_snapshot()->death_data.free_ops);
- EXPECT_EQ(kAllocatedBytes,
- data->last_phase_snapshot()->death_data.allocated_bytes);
- EXPECT_EQ(kFreedBytes, data->last_phase_snapshot()->death_data.freed_bytes);
- EXPECT_EQ(kAllocOverheadBytes,
- data->last_phase_snapshot()->death_data.alloc_overhead_bytes);
- EXPECT_EQ(kMaxAllocatedBytes,
- data->last_phase_snapshot()->death_data.max_allocated_bytes);
-
- EXPECT_EQ(nullptr, data->last_phase_snapshot()->prev);
-
- const int32_t run_ms1 = 21;
- const int32_t queue_ms1 = 4;
-
- data->RecordDurations(queue_ms1, run_ms1, kUnrandomInt);
- data->RecordAllocations(kAllocOps, kFreeOps, kAllocatedBytes, kFreedBytes,
- kAllocOverheadBytes, kMaxAllocatedBytes);
-
- EXPECT_EQ(data->run_duration_sum(), run_ms + run_ms + run_ms1);
- EXPECT_EQ(data->run_duration_max(), run_ms1);
- EXPECT_EQ(data->run_duration_sample(), run_ms1);
- EXPECT_EQ(data->queue_duration_sum(), queue_ms + queue_ms + queue_ms1);
- EXPECT_EQ(data->queue_duration_max(), queue_ms1);
- EXPECT_EQ(data->queue_duration_sample(), queue_ms1);
- EXPECT_EQ(data->count(), 3);
-
- EXPECT_EQ(data->alloc_ops(), 2 * kAllocOps);
- EXPECT_EQ(data->free_ops(), 2 * kFreeOps);
- EXPECT_EQ(data->allocated_bytes(), 2 * kAllocatedBytes);
- EXPECT_EQ(data->freed_bytes(), 2 * kFreedBytes);
- EXPECT_EQ(data->alloc_overhead_bytes(), 2 * kAllocOverheadBytes);
- EXPECT_EQ(data->max_allocated_bytes(), kMaxAllocatedBytes);
-
- ASSERT_NE(nullptr, data->last_phase_snapshot());
- EXPECT_EQ(123, data->last_phase_snapshot()->profiling_phase);
- EXPECT_EQ(2, data->last_phase_snapshot()->death_data.count);
- EXPECT_EQ(2 * run_ms,
- data->last_phase_snapshot()->death_data.run_duration_sum);
- EXPECT_EQ(run_ms, data->last_phase_snapshot()->death_data.run_duration_max);
- EXPECT_EQ(run_ms,
- data->last_phase_snapshot()->death_data.run_duration_sample);
- EXPECT_EQ(2 * queue_ms,
- data->last_phase_snapshot()->death_data.queue_duration_sum);
- EXPECT_EQ(queue_ms,
- data->last_phase_snapshot()->death_data.queue_duration_max);
- EXPECT_EQ(queue_ms,
- data->last_phase_snapshot()->death_data.queue_duration_sample);
-
- EXPECT_EQ(kAllocOps, data->last_phase_snapshot()->death_data.alloc_ops);
- EXPECT_EQ(kFreeOps, data->last_phase_snapshot()->death_data.free_ops);
- EXPECT_EQ(kAllocatedBytes,
- data->last_phase_snapshot()->death_data.allocated_bytes);
- EXPECT_EQ(kFreedBytes, data->last_phase_snapshot()->death_data.freed_bytes);
- EXPECT_EQ(kAllocOverheadBytes,
- data->last_phase_snapshot()->death_data.alloc_overhead_bytes);
- EXPECT_EQ(kMaxAllocatedBytes,
- data->last_phase_snapshot()->death_data.max_allocated_bytes);
-
- EXPECT_EQ(nullptr, data->last_phase_snapshot()->prev);
-}
-
-TEST_F(TrackedObjectsTest, Delta) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- DeathDataSnapshot snapshot;
- snapshot.count = 10;
- snapshot.run_duration_sum = 100;
- snapshot.run_duration_max = 50;
- snapshot.run_duration_sample = 25;
- snapshot.queue_duration_sum = 200;
- snapshot.queue_duration_max = 101;
- snapshot.queue_duration_sample = 26;
-
- snapshot.alloc_ops = 95;
- snapshot.free_ops = 90;
- snapshot.allocated_bytes = 10240;
- snapshot.freed_bytes = 4096;
- snapshot.alloc_overhead_bytes = 950;
- snapshot.max_allocated_bytes = 10240;
-
- DeathDataSnapshot older_snapshot;
- older_snapshot.count = 2;
- older_snapshot.run_duration_sum = 95;
- older_snapshot.run_duration_max = 48;
- older_snapshot.run_duration_sample = 22;
- older_snapshot.queue_duration_sum = 190;
- older_snapshot.queue_duration_max = 99;
- older_snapshot.queue_duration_sample = 21;
-
- older_snapshot.alloc_ops = 45;
- older_snapshot.free_ops = 40;
- older_snapshot.allocated_bytes = 4096;
- older_snapshot.freed_bytes = 2048;
- older_snapshot.alloc_overhead_bytes = 450;
- older_snapshot.max_allocated_bytes = 10200;
-
- const DeathDataSnapshot& delta = snapshot.Delta(older_snapshot);
- EXPECT_EQ(8, delta.count);
- EXPECT_EQ(5, delta.run_duration_sum);
- EXPECT_EQ(50, delta.run_duration_max);
- EXPECT_EQ(25, delta.run_duration_sample);
- EXPECT_EQ(10, delta.queue_duration_sum);
- EXPECT_EQ(101, delta.queue_duration_max);
- EXPECT_EQ(26, delta.queue_duration_sample);
-
- EXPECT_EQ(50, delta.alloc_ops);
- EXPECT_EQ(50, delta.free_ops);
- EXPECT_EQ(6144, delta.allocated_bytes);
- EXPECT_EQ(2048, delta.freed_bytes);
- EXPECT_EQ(500, delta.alloc_overhead_bytes);
- EXPECT_EQ(10240, delta.max_allocated_bytes);
-}
-
-TEST_F(TrackedObjectsTest, DeactivatedBirthOnlyToSnapshotWorkerThread) {
- // Start in the deactivated state.
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::DEACTIVATED);
-
- const char kFunction[] = "DeactivatedBirthOnlyToSnapshotWorkerThread";
- Location location(kFunction, kFile, kLineNumber, NULL);
- TallyABirth(location, std::string());
-
- ProcessDataSnapshot process_data;
- ThreadData::Snapshot(0, &process_data);
-
- ASSERT_EQ(1u, process_data.phased_snapshots.size());
-
- auto it = process_data.phased_snapshots.find(0);
- ASSERT_TRUE(it != process_data.phased_snapshots.end());
- const ProcessDataPhaseSnapshot& process_data_phase = it->second;
-
- ASSERT_EQ(0u, process_data_phase.tasks.size());
-
- EXPECT_EQ(base::GetCurrentProcId(), process_data.process_id);
-}
-
-TEST_F(TrackedObjectsTest, DeactivatedBirthOnlyToSnapshotMainThread) {
- // Start in the deactivated state.
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::DEACTIVATED);
-
- const char kFunction[] = "DeactivatedBirthOnlyToSnapshotMainThread";
- Location location(kFunction, kFile, kLineNumber, NULL);
- TallyABirth(location, kMainThreadName);
-
- ProcessDataSnapshot process_data;
- ThreadData::Snapshot(0, &process_data);
-
- ASSERT_EQ(1u, process_data.phased_snapshots.size());
-
- auto it = process_data.phased_snapshots.find(0);
- ASSERT_TRUE(it != process_data.phased_snapshots.end());
- const ProcessDataPhaseSnapshot& process_data_phase = it->second;
-
- ASSERT_EQ(0u, process_data_phase.tasks.size());
-
- EXPECT_EQ(base::GetCurrentProcId(), process_data.process_id);
-}
-
-TEST_F(TrackedObjectsTest, BirthOnlyToSnapshotWorkerThread) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- const char kFunction[] = "BirthOnlyToSnapshotWorkerThread";
- Location location(kFunction, kFile, kLineNumber, NULL);
- TallyABirth(location, std::string());
-
- ProcessDataSnapshot process_data;
- ThreadData::Snapshot(0, &process_data);
- ExpectSimpleProcessData(process_data, kFunction, kWorkerThreadName,
- kStillAlive, 1, 0, 0);
-}
-
-TEST_F(TrackedObjectsTest, BirthOnlyToSnapshotMainThread) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- const char kFunction[] = "BirthOnlyToSnapshotMainThread";
- Location location(kFunction, kFile, kLineNumber, NULL);
- TallyABirth(location, kMainThreadName);
-
- ProcessDataSnapshot process_data;
- ThreadData::Snapshot(0, &process_data);
- ExpectSimpleProcessData(process_data, kFunction, kMainThreadName, kStillAlive,
- 1, 0, 0);
-}
-
-TEST_F(TrackedObjectsTest, LifeCycleToSnapshotMainThread) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- const char kFunction[] = "LifeCycleToSnapshotMainThread";
- Location location(kFunction, kFile, kLineNumber, NULL);
- TallyABirth(location, kMainThreadName);
-
- const TrackedTime kTimePosted = TrackedTime::FromMilliseconds(1);
- const base::TimeTicks kDelayedStartTime = base::TimeTicks();
- // TrackingInfo will call TallyABirth() during construction.
- base::TrackingInfo pending_task(location, kDelayedStartTime);
- pending_task.time_posted = kTimePosted; // Overwrite implied Now().
-
- const unsigned int kStartOfRun = 5;
- const unsigned int kEndOfRun = 7;
- SetTestTime(kStartOfRun);
- TaskStopwatch stopwatch;
- stopwatch.Start();
- SetTestTime(kEndOfRun);
- stopwatch.Stop();
-
- ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, stopwatch);
-
- ProcessDataSnapshot process_data;
- ThreadData::Snapshot(0, &process_data);
- ExpectSimpleProcessData(process_data, kFunction, kMainThreadName,
- kMainThreadName, 1, 2, 4);
-}
-
-TEST_F(TrackedObjectsTest, TwoPhases) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- const char kFunction[] = "TwoPhases";
- Location location(kFunction, kFile, kLineNumber, NULL);
- TallyABirth(location, kMainThreadName);
-
- const TrackedTime kTimePosted = TrackedTime::FromMilliseconds(1);
- const base::TimeTicks kDelayedStartTime = base::TimeTicks();
- // TrackingInfo will call TallyABirth() during construction.
- base::TrackingInfo pending_task(location, kDelayedStartTime);
- pending_task.time_posted = kTimePosted; // Overwrite implied Now().
-
- const unsigned int kStartOfRun = 5;
- const unsigned int kEndOfRun = 7;
- SetTestTime(kStartOfRun);
- TaskStopwatch stopwatch;
- stopwatch.Start();
- SetTestTime(kEndOfRun);
- stopwatch.Stop();
-
- ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, stopwatch);
-
- ThreadData::OnProfilingPhaseCompleted(0);
-
- TallyABirth(location, kMainThreadName);
-
- const TrackedTime kTimePosted1 = TrackedTime::FromMilliseconds(9);
- const base::TimeTicks kDelayedStartTime1 = base::TimeTicks();
- // TrackingInfo will call TallyABirth() during construction.
- base::TrackingInfo pending_task1(location, kDelayedStartTime1);
- pending_task1.time_posted = kTimePosted1; // Overwrite implied Now().
-
- const unsigned int kStartOfRun1 = 11;
- const unsigned int kEndOfRun1 = 21;
- SetTestTime(kStartOfRun1);
- TaskStopwatch stopwatch1;
- stopwatch1.Start();
- SetTestTime(kEndOfRun1);
- stopwatch1.Stop();
-
- ThreadData::TallyRunOnNamedThreadIfTracking(pending_task1, stopwatch1);
-
- ProcessDataSnapshot process_data;
- ThreadData::Snapshot(1, &process_data);
-
- ASSERT_EQ(2u, process_data.phased_snapshots.size());
-
- auto it0 = process_data.phased_snapshots.find(0);
- ASSERT_TRUE(it0 != process_data.phased_snapshots.end());
- const ProcessDataPhaseSnapshot& process_data_phase0 = it0->second;
-
- ASSERT_EQ(1u, process_data_phase0.tasks.size());
-
- EXPECT_EQ(kFile, process_data_phase0.tasks[0].birth.location.file_name);
- EXPECT_EQ(kFunction,
- process_data_phase0.tasks[0].birth.location.function_name);
- EXPECT_EQ(kLineNumber,
- process_data_phase0.tasks[0].birth.location.line_number);
-
- EXPECT_EQ(kMainThreadName,
- process_data_phase0.tasks[0].birth.sanitized_thread_name);
-
- EXPECT_EQ(1, process_data_phase0.tasks[0].death_data.count);
- EXPECT_EQ(2, process_data_phase0.tasks[0].death_data.run_duration_sum);
- EXPECT_EQ(2, process_data_phase0.tasks[0].death_data.run_duration_max);
- EXPECT_EQ(2, process_data_phase0.tasks[0].death_data.run_duration_sample);
- EXPECT_EQ(4, process_data_phase0.tasks[0].death_data.queue_duration_sum);
- EXPECT_EQ(4, process_data_phase0.tasks[0].death_data.queue_duration_max);
- EXPECT_EQ(4, process_data_phase0.tasks[0].death_data.queue_duration_sample);
-
- EXPECT_EQ(kMainThreadName,
- process_data_phase0.tasks[0].death_sanitized_thread_name);
-
- auto it1 = process_data.phased_snapshots.find(1);
- ASSERT_TRUE(it1 != process_data.phased_snapshots.end());
- const ProcessDataPhaseSnapshot& process_data_phase1 = it1->second;
-
- ASSERT_EQ(1u, process_data_phase1.tasks.size());
-
- EXPECT_EQ(kFile, process_data_phase1.tasks[0].birth.location.file_name);
- EXPECT_EQ(kFunction,
- process_data_phase1.tasks[0].birth.location.function_name);
- EXPECT_EQ(kLineNumber,
- process_data_phase1.tasks[0].birth.location.line_number);
-
- EXPECT_EQ(kMainThreadName,
- process_data_phase1.tasks[0].birth.sanitized_thread_name);
-
- EXPECT_EQ(1, process_data_phase1.tasks[0].death_data.count);
- EXPECT_EQ(10, process_data_phase1.tasks[0].death_data.run_duration_sum);
- EXPECT_EQ(10, process_data_phase1.tasks[0].death_data.run_duration_max);
- EXPECT_EQ(10, process_data_phase1.tasks[0].death_data.run_duration_sample);
- EXPECT_EQ(2, process_data_phase1.tasks[0].death_data.queue_duration_sum);
- EXPECT_EQ(2, process_data_phase1.tasks[0].death_data.queue_duration_max);
- EXPECT_EQ(2, process_data_phase1.tasks[0].death_data.queue_duration_sample);
-
- EXPECT_EQ(kMainThreadName,
- process_data_phase1.tasks[0].death_sanitized_thread_name);
-
- EXPECT_EQ(base::GetCurrentProcId(), process_data.process_id);
-}
-
-TEST_F(TrackedObjectsTest, ThreePhases) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- const char kFunction[] = "ThreePhases";
- Location location(kFunction, kFile, kLineNumber, NULL);
-
- // Phase 0
- {
- TallyABirth(location, kMainThreadName);
-
- // TrackingInfo will call TallyABirth() during construction.
- SetTestTime(10);
- base::TrackingInfo pending_task(location, base::TimeTicks());
-
- SetTestTime(17);
- TaskStopwatch stopwatch;
- stopwatch.Start();
- SetTestTime(23);
- stopwatch.Stop();
-
- ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, stopwatch);
- }
-
- ThreadData::OnProfilingPhaseCompleted(0);
-
- // Phase 1
- {
- TallyABirth(location, kMainThreadName);
-
- SetTestTime(30);
- base::TrackingInfo pending_task(location, base::TimeTicks());
-
- SetTestTime(35);
- TaskStopwatch stopwatch;
- stopwatch.Start();
- SetTestTime(39);
- stopwatch.Stop();
-
- ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, stopwatch);
- }
-
- ThreadData::OnProfilingPhaseCompleted(1);
-
- // Phase 2
- {
- TallyABirth(location, kMainThreadName);
-
- // TrackingInfo will call TallyABirth() during construction.
- SetTestTime(40);
- base::TrackingInfo pending_task(location, base::TimeTicks());
-
- SetTestTime(43);
- TaskStopwatch stopwatch;
- stopwatch.Start();
- SetTestTime(45);
- stopwatch.Stop();
-
- ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, stopwatch);
- }
-
- // Snapshot and check results.
- ProcessDataSnapshot process_data;
- ThreadData::Snapshot(2, &process_data);
-
- ASSERT_EQ(3u, process_data.phased_snapshots.size());
-
- auto it0 = process_data.phased_snapshots.find(0);
- ASSERT_TRUE(it0 != process_data.phased_snapshots.end());
- const ProcessDataPhaseSnapshot& process_data_phase0 = it0->second;
-
- ASSERT_EQ(1u, process_data_phase0.tasks.size());
-
- EXPECT_EQ(kFile, process_data_phase0.tasks[0].birth.location.file_name);
- EXPECT_EQ(kFunction,
- process_data_phase0.tasks[0].birth.location.function_name);
- EXPECT_EQ(kLineNumber,
- process_data_phase0.tasks[0].birth.location.line_number);
-
- EXPECT_EQ(kMainThreadName,
- process_data_phase0.tasks[0].birth.sanitized_thread_name);
-
- EXPECT_EQ(1, process_data_phase0.tasks[0].death_data.count);
- EXPECT_EQ(6, process_data_phase0.tasks[0].death_data.run_duration_sum);
- EXPECT_EQ(6, process_data_phase0.tasks[0].death_data.run_duration_max);
- EXPECT_EQ(6, process_data_phase0.tasks[0].death_data.run_duration_sample);
- EXPECT_EQ(7, process_data_phase0.tasks[0].death_data.queue_duration_sum);
- EXPECT_EQ(7, process_data_phase0.tasks[0].death_data.queue_duration_max);
- EXPECT_EQ(7, process_data_phase0.tasks[0].death_data.queue_duration_sample);
-
- EXPECT_EQ(kMainThreadName,
- process_data_phase0.tasks[0].death_sanitized_thread_name);
-
- auto it1 = process_data.phased_snapshots.find(1);
- ASSERT_TRUE(it1 != process_data.phased_snapshots.end());
- const ProcessDataPhaseSnapshot& process_data_phase1 = it1->second;
-
- ASSERT_EQ(1u, process_data_phase1.tasks.size());
-
- EXPECT_EQ(kFile, process_data_phase1.tasks[0].birth.location.file_name);
- EXPECT_EQ(kFunction,
- process_data_phase1.tasks[0].birth.location.function_name);
- EXPECT_EQ(kLineNumber,
- process_data_phase1.tasks[0].birth.location.line_number);
-
- EXPECT_EQ(kMainThreadName,
- process_data_phase1.tasks[0].birth.sanitized_thread_name);
-
- EXPECT_EQ(1, process_data_phase1.tasks[0].death_data.count);
- EXPECT_EQ(4, process_data_phase1.tasks[0].death_data.run_duration_sum);
- EXPECT_EQ(4, process_data_phase1.tasks[0].death_data.run_duration_max);
- EXPECT_EQ(4, process_data_phase1.tasks[0].death_data.run_duration_sample);
- EXPECT_EQ(5, process_data_phase1.tasks[0].death_data.queue_duration_sum);
- EXPECT_EQ(5, process_data_phase1.tasks[0].death_data.queue_duration_max);
- EXPECT_EQ(5, process_data_phase1.tasks[0].death_data.queue_duration_sample);
-
- EXPECT_EQ(kMainThreadName,
- process_data_phase1.tasks[0].death_sanitized_thread_name);
-
- auto it2 = process_data.phased_snapshots.find(2);
- ASSERT_TRUE(it2 != process_data.phased_snapshots.end());
- const ProcessDataPhaseSnapshot& process_data_phase2 = it2->second;
-
- ASSERT_EQ(1u, process_data_phase2.tasks.size());
-
- EXPECT_EQ(kFile, process_data_phase2.tasks[0].birth.location.file_name);
- EXPECT_EQ(kFunction,
- process_data_phase2.tasks[0].birth.location.function_name);
- EXPECT_EQ(kLineNumber,
- process_data_phase2.tasks[0].birth.location.line_number);
-
- EXPECT_EQ(kMainThreadName,
- process_data_phase2.tasks[0].birth.sanitized_thread_name);
-
- EXPECT_EQ(1, process_data_phase2.tasks[0].death_data.count);
- EXPECT_EQ(2, process_data_phase2.tasks[0].death_data.run_duration_sum);
- EXPECT_EQ(2, process_data_phase2.tasks[0].death_data.run_duration_max);
- EXPECT_EQ(2, process_data_phase2.tasks[0].death_data.run_duration_sample);
- EXPECT_EQ(3, process_data_phase2.tasks[0].death_data.queue_duration_sum);
- EXPECT_EQ(3, process_data_phase2.tasks[0].death_data.queue_duration_max);
- EXPECT_EQ(3, process_data_phase2.tasks[0].death_data.queue_duration_sample);
-
- EXPECT_EQ(kMainThreadName,
- process_data_phase2.tasks[0].death_sanitized_thread_name);
-
- EXPECT_EQ(base::GetCurrentProcId(), process_data.process_id);
-}
-
-TEST_F(TrackedObjectsTest, TwoPhasesSecondEmpty) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- const char kFunction[] = "TwoPhasesSecondEmpty";
- Location location(kFunction, kFile, kLineNumber, NULL);
- ThreadData::InitializeThreadContext(kMainThreadName);
-
- const TrackedTime kTimePosted = TrackedTime::FromMilliseconds(1);
- const base::TimeTicks kDelayedStartTime = base::TimeTicks();
- // TrackingInfo will call TallyABirth() during construction.
- base::TrackingInfo pending_task(location, kDelayedStartTime);
- pending_task.time_posted = kTimePosted; // Overwrite implied Now().
-
- const unsigned int kStartOfRun = 5;
- const unsigned int kEndOfRun = 7;
- SetTestTime(kStartOfRun);
- TaskStopwatch stopwatch;
- stopwatch.Start();
- SetTestTime(kEndOfRun);
- stopwatch.Stop();
-
- ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, stopwatch);
-
- ThreadData::OnProfilingPhaseCompleted(0);
-
- ProcessDataSnapshot process_data;
- ThreadData::Snapshot(1, &process_data);
-
- ASSERT_EQ(2u, process_data.phased_snapshots.size());
-
- auto it0 = process_data.phased_snapshots.find(0);
- ASSERT_TRUE(it0 != process_data.phased_snapshots.end());
- const ProcessDataPhaseSnapshot& process_data_phase0 = it0->second;
-
- ASSERT_EQ(1u, process_data_phase0.tasks.size());
-
- EXPECT_EQ(kFile, process_data_phase0.tasks[0].birth.location.file_name);
- EXPECT_EQ(kFunction,
- process_data_phase0.tasks[0].birth.location.function_name);
- EXPECT_EQ(kLineNumber,
- process_data_phase0.tasks[0].birth.location.line_number);
-
- EXPECT_EQ(kMainThreadName,
- process_data_phase0.tasks[0].birth.sanitized_thread_name);
-
- EXPECT_EQ(1, process_data_phase0.tasks[0].death_data.count);
- EXPECT_EQ(2, process_data_phase0.tasks[0].death_data.run_duration_sum);
- EXPECT_EQ(2, process_data_phase0.tasks[0].death_data.run_duration_max);
- EXPECT_EQ(2, process_data_phase0.tasks[0].death_data.run_duration_sample);
- EXPECT_EQ(4, process_data_phase0.tasks[0].death_data.queue_duration_sum);
- EXPECT_EQ(4, process_data_phase0.tasks[0].death_data.queue_duration_max);
- EXPECT_EQ(4, process_data_phase0.tasks[0].death_data.queue_duration_sample);
-
- EXPECT_EQ(kMainThreadName,
- process_data_phase0.tasks[0].death_sanitized_thread_name);
-
- auto it1 = process_data.phased_snapshots.find(1);
- ASSERT_TRUE(it1 != process_data.phased_snapshots.end());
- const ProcessDataPhaseSnapshot& process_data_phase1 = it1->second;
-
- ASSERT_EQ(0u, process_data_phase1.tasks.size());
-
- EXPECT_EQ(base::GetCurrentProcId(), process_data.process_id);
-}
-
-TEST_F(TrackedObjectsTest, TwoPhasesFirstEmpty) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- ThreadData::OnProfilingPhaseCompleted(0);
-
- const char kFunction[] = "TwoPhasesSecondEmpty";
- Location location(kFunction, kFile, kLineNumber, NULL);
- ThreadData::InitializeThreadContext(kMainThreadName);
-
- const TrackedTime kTimePosted = TrackedTime::FromMilliseconds(1);
- const base::TimeTicks kDelayedStartTime = base::TimeTicks();
- // TrackingInfo will call TallyABirth() during construction.
- base::TrackingInfo pending_task(location, kDelayedStartTime);
- pending_task.time_posted = kTimePosted; // Overwrite implied Now().
-
- const unsigned int kStartOfRun = 5;
- const unsigned int kEndOfRun = 7;
- SetTestTime(kStartOfRun);
- TaskStopwatch stopwatch;
- stopwatch.Start();
- SetTestTime(kEndOfRun);
- stopwatch.Stop();
-
- ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, stopwatch);
-
- ProcessDataSnapshot process_data;
- ThreadData::Snapshot(1, &process_data);
-
- ASSERT_EQ(1u, process_data.phased_snapshots.size());
-
- auto it1 = process_data.phased_snapshots.find(1);
- ASSERT_TRUE(it1 != process_data.phased_snapshots.end());
- const ProcessDataPhaseSnapshot& process_data_phase1 = it1->second;
-
- ASSERT_EQ(1u, process_data_phase1.tasks.size());
-
- EXPECT_EQ(kFile, process_data_phase1.tasks[0].birth.location.file_name);
- EXPECT_EQ(kFunction,
- process_data_phase1.tasks[0].birth.location.function_name);
- EXPECT_EQ(kLineNumber,
- process_data_phase1.tasks[0].birth.location.line_number);
-
- EXPECT_EQ(kMainThreadName,
- process_data_phase1.tasks[0].birth.sanitized_thread_name);
-
- EXPECT_EQ(1, process_data_phase1.tasks[0].death_data.count);
- EXPECT_EQ(2, process_data_phase1.tasks[0].death_data.run_duration_sum);
- EXPECT_EQ(2, process_data_phase1.tasks[0].death_data.run_duration_max);
- EXPECT_EQ(2, process_data_phase1.tasks[0].death_data.run_duration_sample);
- EXPECT_EQ(4, process_data_phase1.tasks[0].death_data.queue_duration_sum);
- EXPECT_EQ(4, process_data_phase1.tasks[0].death_data.queue_duration_max);
- EXPECT_EQ(4, process_data_phase1.tasks[0].death_data.queue_duration_sample);
-
- EXPECT_EQ(kMainThreadName,
- process_data_phase1.tasks[0].death_sanitized_thread_name);
-
- EXPECT_EQ(base::GetCurrentProcId(), process_data.process_id);
-}
-
-// We will deactivate tracking after the birth, and before the death, and
-// demonstrate that the lifecycle is completely tallied. This ensures that
-// our tallied births are matched by tallied deaths (except for when the
-// task is still running, or is queued).
-TEST_F(TrackedObjectsTest, LifeCycleMidDeactivatedToSnapshotMainThread) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- const char kFunction[] = "LifeCycleMidDeactivatedToSnapshotMainThread";
- Location location(kFunction, kFile, kLineNumber, NULL);
- TallyABirth(location, kMainThreadName);
-
- const TrackedTime kTimePosted = TrackedTime::FromMilliseconds(1);
- const base::TimeTicks kDelayedStartTime = base::TimeTicks();
- // TrackingInfo will call TallyABirth() during construction.
- base::TrackingInfo pending_task(location, kDelayedStartTime);
- pending_task.time_posted = kTimePosted; // Overwrite implied Now().
-
- // Turn off tracking now that we have births.
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::DEACTIVATED);
-
- const unsigned int kStartOfRun = 5;
- const unsigned int kEndOfRun = 7;
- SetTestTime(kStartOfRun);
- TaskStopwatch stopwatch;
- stopwatch.Start();
- SetTestTime(kEndOfRun);
- stopwatch.Stop();
-
- ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, stopwatch);
-
- ProcessDataSnapshot process_data;
- ThreadData::Snapshot(0, &process_data);
- ExpectSimpleProcessData(process_data, kFunction, kMainThreadName,
- kMainThreadName, 1, 2, 4);
-}
-
-// We will deactivate tracking before starting a life cycle, and neither
-// the birth nor the death will be recorded.
-TEST_F(TrackedObjectsTest, LifeCyclePreDeactivatedToSnapshotMainThread) {
- // Start in the deactivated state.
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::DEACTIVATED);
-
- const char kFunction[] = "LifeCyclePreDeactivatedToSnapshotMainThread";
- Location location(kFunction, kFile, kLineNumber, NULL);
- TallyABirth(location, kMainThreadName);
-
- const TrackedTime kTimePosted = TrackedTime::FromMilliseconds(1);
- const base::TimeTicks kDelayedStartTime = base::TimeTicks();
- // TrackingInfo will call TallyABirth() during construction.
- base::TrackingInfo pending_task(location, kDelayedStartTime);
- pending_task.time_posted = kTimePosted; // Overwrite implied Now().
-
- const unsigned int kStartOfRun = 5;
- const unsigned int kEndOfRun = 7;
- SetTestTime(kStartOfRun);
- TaskStopwatch stopwatch;
- stopwatch.Start();
- SetTestTime(kEndOfRun);
- stopwatch.Stop();
-
- ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, stopwatch);
-
- ProcessDataSnapshot process_data;
- ThreadData::Snapshot(0, &process_data);
-
- ASSERT_EQ(1u, process_data.phased_snapshots.size());
-
- auto it = process_data.phased_snapshots.find(0);
- ASSERT_TRUE(it != process_data.phased_snapshots.end());
- const ProcessDataPhaseSnapshot& process_data_phase = it->second;
-
- ASSERT_EQ(0u, process_data_phase.tasks.size());
-
- EXPECT_EQ(base::GetCurrentProcId(), process_data.process_id);
-}
-
-TEST_F(TrackedObjectsTest, TwoLives) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- const char kFunction[] = "TwoLives";
- Location location(kFunction, kFile, kLineNumber, NULL);
- TallyABirth(location, kMainThreadName);
-
- const TrackedTime kTimePosted = TrackedTime::FromMilliseconds(1);
- const base::TimeTicks kDelayedStartTime = base::TimeTicks();
- // TrackingInfo will call TallyABirth() during construction.
- base::TrackingInfo pending_task(location, kDelayedStartTime);
- pending_task.time_posted = kTimePosted; // Overwrite implied Now().
-
- const unsigned int kStartOfRun = 5;
- const unsigned int kEndOfRun = 7;
- SetTestTime(kStartOfRun);
- TaskStopwatch stopwatch;
- stopwatch.Start();
- SetTestTime(kEndOfRun);
- stopwatch.Stop();
-
- ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, stopwatch);
-
- // TrackingInfo will call TallyABirth() during construction.
- base::TrackingInfo pending_task2(location, kDelayedStartTime);
- pending_task2.time_posted = kTimePosted; // Overwrite implied Now().
- SetTestTime(kStartOfRun);
- TaskStopwatch stopwatch2;
- stopwatch2.Start();
- SetTestTime(kEndOfRun);
- stopwatch2.Stop();
-
- ThreadData::TallyRunOnNamedThreadIfTracking(pending_task2, stopwatch2);
-
- ProcessDataSnapshot process_data;
- ThreadData::Snapshot(0, &process_data);
- ExpectSimpleProcessData(process_data, kFunction, kMainThreadName,
- kMainThreadName, 2, 2, 4);
-}
-
-TEST_F(TrackedObjectsTest, DifferentLives) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- // Use a well named thread.
- ThreadData::InitializeThreadContext(kMainThreadName);
- const char kFunction[] = "DifferentLives";
- Location location(kFunction, kFile, kLineNumber, NULL);
-
- const TrackedTime kTimePosted = TrackedTime::FromMilliseconds(1);
- const base::TimeTicks kDelayedStartTime = base::TimeTicks();
- // TrackingInfo will call TallyABirth() during construction.
- base::TrackingInfo pending_task(location, kDelayedStartTime);
- pending_task.time_posted = kTimePosted; // Overwrite implied Now().
-
- const unsigned int kStartOfRun = 5;
- const unsigned int kEndOfRun = 7;
- SetTestTime(kStartOfRun);
- TaskStopwatch stopwatch;
- stopwatch.Start();
- SetTestTime(kEndOfRun);
- stopwatch.Stop();
-
- ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, stopwatch);
-
- const int kSecondFakeLineNumber = 999;
- Location second_location(kFunction, kFile, kSecondFakeLineNumber, NULL);
-
- // TrackingInfo will call TallyABirth() during construction.
- base::TrackingInfo pending_task2(second_location, kDelayedStartTime);
- pending_task2.time_posted = kTimePosted; // Overwrite implied Now().
-
- ProcessDataSnapshot process_data;
- ThreadData::Snapshot(0, &process_data);
-
- ASSERT_EQ(1u, process_data.phased_snapshots.size());
- auto it = process_data.phased_snapshots.find(0);
- ASSERT_TRUE(it != process_data.phased_snapshots.end());
- const ProcessDataPhaseSnapshot& process_data_phase = it->second;
-
- ASSERT_EQ(2u, process_data_phase.tasks.size());
-
- EXPECT_EQ(kFile, process_data_phase.tasks[0].birth.location.file_name);
- EXPECT_EQ(kFunction,
- process_data_phase.tasks[0].birth.location.function_name);
- EXPECT_EQ(kLineNumber,
- process_data_phase.tasks[0].birth.location.line_number);
- EXPECT_EQ(kMainThreadName,
- process_data_phase.tasks[0].birth.sanitized_thread_name);
- EXPECT_EQ(1, process_data_phase.tasks[0].death_data.count);
- EXPECT_EQ(2, process_data_phase.tasks[0].death_data.run_duration_sum);
- EXPECT_EQ(2, process_data_phase.tasks[0].death_data.run_duration_max);
- EXPECT_EQ(2, process_data_phase.tasks[0].death_data.run_duration_sample);
- EXPECT_EQ(4, process_data_phase.tasks[0].death_data.queue_duration_sum);
- EXPECT_EQ(4, process_data_phase.tasks[0].death_data.queue_duration_max);
- EXPECT_EQ(4, process_data_phase.tasks[0].death_data.queue_duration_sample);
- EXPECT_EQ(kMainThreadName,
- process_data_phase.tasks[0].death_sanitized_thread_name);
- EXPECT_EQ(kFile, process_data_phase.tasks[1].birth.location.file_name);
- EXPECT_EQ(kFunction,
- process_data_phase.tasks[1].birth.location.function_name);
- EXPECT_EQ(kSecondFakeLineNumber,
- process_data_phase.tasks[1].birth.location.line_number);
- EXPECT_EQ(kMainThreadName,
- process_data_phase.tasks[1].birth.sanitized_thread_name);
- EXPECT_EQ(1, process_data_phase.tasks[1].death_data.count);
- EXPECT_EQ(0, process_data_phase.tasks[1].death_data.run_duration_sum);
- EXPECT_EQ(0, process_data_phase.tasks[1].death_data.run_duration_max);
- EXPECT_EQ(0, process_data_phase.tasks[1].death_data.run_duration_sample);
- EXPECT_EQ(0, process_data_phase.tasks[1].death_data.queue_duration_sum);
- EXPECT_EQ(0, process_data_phase.tasks[1].death_data.queue_duration_max);
- EXPECT_EQ(0, process_data_phase.tasks[1].death_data.queue_duration_sample);
- EXPECT_EQ(kStillAlive,
- process_data_phase.tasks[1].death_sanitized_thread_name);
- EXPECT_EQ(base::GetCurrentProcId(), process_data.process_id);
-}
-
-TEST_F(TrackedObjectsTest, TaskWithNestedExclusion) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- const char kFunction[] = "TaskWithNestedExclusion";
- Location location(kFunction, kFile, kLineNumber, NULL);
- TallyABirth(location, kMainThreadName);
-
- const TrackedTime kTimePosted = TrackedTime::FromMilliseconds(1);
- const base::TimeTicks kDelayedStartTime = base::TimeTicks();
- // TrackingInfo will call TallyABirth() during construction.
- base::TrackingInfo pending_task(location, kDelayedStartTime);
- pending_task.time_posted = kTimePosted; // Overwrite implied Now().
-
- SetTestTime(5);
- TaskStopwatch task_stopwatch;
- task_stopwatch.Start();
- {
- SetTestTime(8);
- TaskStopwatch exclusion_stopwatch;
- exclusion_stopwatch.Start();
- SetTestTime(12);
- exclusion_stopwatch.Stop();
- }
- SetTestTime(15);
- task_stopwatch.Stop();
-
- ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, task_stopwatch);
-
- ProcessDataSnapshot process_data;
- ThreadData::Snapshot(0, &process_data);
- ExpectSimpleProcessData(process_data, kFunction, kMainThreadName,
- kMainThreadName, 1, 6, 4);
-}
-
-TEST_F(TrackedObjectsTest, TaskWith2NestedExclusions) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- const char kFunction[] = "TaskWith2NestedExclusions";
- Location location(kFunction, kFile, kLineNumber, NULL);
- TallyABirth(location, kMainThreadName);
-
- const TrackedTime kTimePosted = TrackedTime::FromMilliseconds(1);
- const base::TimeTicks kDelayedStartTime = base::TimeTicks();
- // TrackingInfo will call TallyABirth() during construction.
- base::TrackingInfo pending_task(location, kDelayedStartTime);
- pending_task.time_posted = kTimePosted; // Overwrite implied Now().
-
- SetTestTime(5);
- TaskStopwatch task_stopwatch;
- task_stopwatch.Start();
- {
- SetTestTime(8);
- TaskStopwatch exclusion_stopwatch;
- exclusion_stopwatch.Start();
- SetTestTime(12);
- exclusion_stopwatch.Stop();
-
- SetTestTime(15);
- TaskStopwatch exclusion_stopwatch2;
- exclusion_stopwatch2.Start();
- SetTestTime(18);
- exclusion_stopwatch2.Stop();
- }
- SetTestTime(25);
- task_stopwatch.Stop();
-
- ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, task_stopwatch);
-
- ProcessDataSnapshot process_data;
- ThreadData::Snapshot(0, &process_data);
- ExpectSimpleProcessData(process_data, kFunction, kMainThreadName,
- kMainThreadName, 1, 13, 4);
-}
-
-TEST_F(TrackedObjectsTest, TaskWithNestedExclusionWithNestedTask) {
- ThreadData::InitializeAndSetTrackingStatus(ThreadData::PROFILING_ACTIVE);
-
- const char kFunction[] = "TaskWithNestedExclusionWithNestedTask";
- Location location(kFunction, kFile, kLineNumber, NULL);
-
- const int kSecondFakeLineNumber = 999;
-
- TallyABirth(location, kMainThreadName);
-
- const TrackedTime kTimePosted = TrackedTime::FromMilliseconds(1);
- const base::TimeTicks kDelayedStartTime = base::TimeTicks();
- // TrackingInfo will call TallyABirth() during construction.
- base::TrackingInfo pending_task(location, kDelayedStartTime);
- pending_task.time_posted = kTimePosted; // Overwrite implied Now().
-
- SetTestTime(5);
- TaskStopwatch task_stopwatch;
- task_stopwatch.Start();
- {
- SetTestTime(8);
- TaskStopwatch exclusion_stopwatch;
- exclusion_stopwatch.Start();
- {
- Location second_location(kFunction, kFile, kSecondFakeLineNumber, NULL);
- base::TrackingInfo nested_task(second_location, kDelayedStartTime);
- // Overwrite implied Now().
- nested_task.time_posted = TrackedTime::FromMilliseconds(8);
- SetTestTime(9);
- TaskStopwatch nested_task_stopwatch;
- nested_task_stopwatch.Start();
- SetTestTime(11);
- nested_task_stopwatch.Stop();
- ThreadData::TallyRunOnNamedThreadIfTracking(
- nested_task, nested_task_stopwatch);
- }
- SetTestTime(12);
- exclusion_stopwatch.Stop();
- }
- SetTestTime(15);
- task_stopwatch.Stop();
-
- ThreadData::TallyRunOnNamedThreadIfTracking(pending_task, task_stopwatch);
-
- ProcessDataSnapshot process_data;
- ThreadData::Snapshot(0, &process_data);
-
- ASSERT_EQ(1u, process_data.phased_snapshots.size());
- auto it = process_data.phased_snapshots.find(0);
- ASSERT_TRUE(it != process_data.phased_snapshots.end());
- const ProcessDataPhaseSnapshot& process_data_phase = it->second;
-
- // The order in which the two task follow is platform-dependent.
- int t0 =
- (process_data_phase.tasks[0].birth.location.line_number == kLineNumber)
- ? 0
- : 1;
- int t1 = 1 - t0;
-
- ASSERT_EQ(2u, process_data_phase.tasks.size());
- EXPECT_EQ(kFile, process_data_phase.tasks[t0].birth.location.file_name);
- EXPECT_EQ(kFunction,
- process_data_phase.tasks[t0].birth.location.function_name);
- EXPECT_EQ(kLineNumber,
- process_data_phase.tasks[t0].birth.location.line_number);
- EXPECT_EQ(kMainThreadName,
- process_data_phase.tasks[t0].birth.sanitized_thread_name);
- EXPECT_EQ(1, process_data_phase.tasks[t0].death_data.count);
- EXPECT_EQ(6, process_data_phase.tasks[t0].death_data.run_duration_sum);
- EXPECT_EQ(6, process_data_phase.tasks[t0].death_data.run_duration_max);
- EXPECT_EQ(6, process_data_phase.tasks[t0].death_data.run_duration_sample);
- EXPECT_EQ(4, process_data_phase.tasks[t0].death_data.queue_duration_sum);
- EXPECT_EQ(4, process_data_phase.tasks[t0].death_data.queue_duration_max);
- EXPECT_EQ(4, process_data_phase.tasks[t0].death_data.queue_duration_sample);
- EXPECT_EQ(kMainThreadName,
- process_data_phase.tasks[t0].death_sanitized_thread_name);
- EXPECT_EQ(kFile, process_data_phase.tasks[t1].birth.location.file_name);
- EXPECT_EQ(kFunction,
- process_data_phase.tasks[t1].birth.location.function_name);
- EXPECT_EQ(kSecondFakeLineNumber,
- process_data_phase.tasks[t1].birth.location.line_number);
- EXPECT_EQ(kMainThreadName,
- process_data_phase.tasks[t1].birth.sanitized_thread_name);
- EXPECT_EQ(1, process_data_phase.tasks[t1].death_data.count);
- EXPECT_EQ(2, process_data_phase.tasks[t1].death_data.run_duration_sum);
- EXPECT_EQ(2, process_data_phase.tasks[t1].death_data.run_duration_max);
- EXPECT_EQ(2, process_data_phase.tasks[t1].death_data.run_duration_sample);
- EXPECT_EQ(1, process_data_phase.tasks[t1].death_data.queue_duration_sum);
- EXPECT_EQ(1, process_data_phase.tasks[t1].death_data.queue_duration_max);
- EXPECT_EQ(1, process_data_phase.tasks[t1].death_data.queue_duration_sample);
- EXPECT_EQ(kMainThreadName,
- process_data_phase.tasks[t1].death_sanitized_thread_name);
- EXPECT_EQ(base::GetCurrentProcId(), process_data.process_id);
-}
-
-// Repetitively create and stop named threads. Verify that the number of
-// instantiated ThreadData instance is equal to the number of different
-// sanitized thread names used in the test.
-TEST_F(TrackedObjectsTest, ReuseRetiredThreadData) {
- const char* const kThreadNames[] = {"Foo%d", "Bar%d", "123Dummy%d",
- "456Dummy%d", "%d"};
- constexpr int kNumIterations = 10;
- EXPECT_EQ(0, GetNumThreadData());
-
- for (int i = 0; i < kNumIterations; ++i) {
- for (const char* thread_name : kThreadNames) {
- base::Thread thread(base::StringPrintf(thread_name, i));
- EXPECT_TRUE(thread.Start());
- }
- }
-
- // Expect one ThreadData instance for each element in |kThreadNames| and one
- // ThreadData instance for the main thread.
- EXPECT_EQ(static_cast<int>(arraysize(kThreadNames) + 1), GetNumThreadData());
-}
-
-} // namespace tracked_objects
diff --git a/base/tracking_info.cc b/base/tracking_info.cc
deleted file mode 100644
index c02b2f4758..0000000000
--- a/base/tracking_info.cc
+++ /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.
-
-#include "base/tracking_info.h"
-
-#include <stddef.h>
-#include "base/tracked_objects.h"
-
-namespace base {
-
-TrackingInfo::TrackingInfo()
- : birth_tally(NULL) {
-}
-
-TrackingInfo::TrackingInfo(
- const tracked_objects::Location& posted_from,
- base::TimeTicks delayed_run_time)
- : birth_tally(
- tracked_objects::ThreadData::TallyABirthIfActive(posted_from)),
- time_posted(tracked_objects::ThreadData::Now()),
- delayed_run_time(delayed_run_time) {
-}
-
-TrackingInfo::~TrackingInfo() {}
-
-} // namespace base
-
diff --git a/base/tracking_info.h b/base/tracking_info.h
deleted file mode 100644
index 6c3bcd1a51..0000000000
--- a/base/tracking_info.h
+++ /dev/null
@@ -1,58 +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.
-
-// This is a simple struct with tracking information that is stored
-// with a PendingTask (when message_loop is handling the task).
-// Only the information that is shared with the profiler in tracked_objects
-// are included in this structure.
-
-
-#ifndef BASE_TRACKING_INFO_H_
-#define BASE_TRACKING_INFO_H_
-
-#include "base/base_export.h"
-#include "base/profiler/tracked_time.h"
-#include "base/time/time.h"
-
-namespace tracked_objects {
-class Location;
-class Births;
-}
-
-namespace base {
-
-// This structure is copied around by value.
-struct BASE_EXPORT TrackingInfo {
- TrackingInfo();
- TrackingInfo(const tracked_objects::Location& posted_from,
- base::TimeTicks delayed_run_time);
- ~TrackingInfo();
-
- // To avoid conflating our stats with the delay duration in a PostDelayedTask,
- // we identify such tasks, and replace their post_time with the time they
- // were scheduled (requested?) to emerge from the delayed task queue. This
- // means that queuing delay for such tasks will show how long they went
- // unserviced, after they *could* be serviced. This is the same stat as we
- // have for non-delayed tasks, and we consistently call it queuing delay.
- tracked_objects::TrackedTime EffectiveTimePosted() const {
- return delayed_run_time.is_null()
- ? time_posted
- : tracked_objects::TrackedTime(delayed_run_time);
- }
-
- // Record of location and thread that the task came from.
- tracked_objects::Births* birth_tally;
-
- // Time when the related task was posted. Note that this value may be empty
- // if task profiling is disabled, and should only be used in conjunction with
- // profiling-related reporting.
- tracked_objects::TrackedTime time_posted;
-
- // The time when the task should be run.
- base::TimeTicks delayed_run_time;
-};
-
-} // namespace base
-
-#endif // BASE_TRACKING_INFO_H_
diff --git a/base/tuple.h b/base/tuple.h
index 34fd789976..58681d515a 100644
--- a/base/tuple.h
+++ b/base/tuple.h
@@ -27,51 +27,12 @@
#include <stddef.h>
#include <tuple>
+#include <utility>
#include "build/build_config.h"
namespace base {
-// Index sequences
-//
-// Minimal clone of the similarly-named C++14 functionality.
-
-template <size_t...>
-struct IndexSequence {};
-
-template <size_t... Ns>
-struct MakeIndexSequenceImpl;
-
-template <size_t... Ns>
-struct MakeIndexSequenceImpl<0, Ns...> {
- using Type = IndexSequence<Ns...>;
-};
-
-template <size_t N, size_t... Ns>
-struct MakeIndexSequenceImpl<N, Ns...>
- : MakeIndexSequenceImpl<N - 1, N - 1, Ns...> {};
-
-// std::get() in <=libstdc++-4.6 returns an lvalue-reference for
-// rvalue-reference of a tuple, where an rvalue-reference is expected.
-template <size_t I, typename... Ts>
-typename std::tuple_element<I, std::tuple<Ts...>>::type&& get(
- std::tuple<Ts...>&& t) {
- using ElemType = typename std::tuple_element<I, std::tuple<Ts...>>::type;
- return std::forward<ElemType>(std::get<I>(t));
-}
-
-template <size_t I, typename T>
-auto get(T& t) -> decltype(std::get<I>(t)) {
- return std::get<I>(t);
-}
-
-template <size_t N>
-using MakeIndexSequence = typename MakeIndexSequenceImpl<N>::Type;
-
-template <typename T>
-using MakeIndexSequenceForTuple =
- MakeIndexSequence<std::tuple_size<typename std::decay<T>::type>::value>;
-
// Dispatchers ----------------------------------------------------------------
//
// Helper functions that call the given method on an object, with the unpacked
@@ -87,16 +48,17 @@ template <typename ObjT, typename Method, typename Tuple, size_t... Ns>
inline void DispatchToMethodImpl(const ObjT& obj,
Method method,
Tuple&& args,
- IndexSequence<Ns...>) {
- (obj->*method)(base::get<Ns>(std::forward<Tuple>(args))...);
+ std::index_sequence<Ns...>) {
+ (obj->*method)(std::get<Ns>(std::forward<Tuple>(args))...);
}
template <typename ObjT, typename Method, typename Tuple>
inline void DispatchToMethod(const ObjT& obj,
Method method,
Tuple&& args) {
+ constexpr size_t size = std::tuple_size<std::decay_t<Tuple>>::value;
DispatchToMethodImpl(obj, method, std::forward<Tuple>(args),
- MakeIndexSequenceForTuple<Tuple>());
+ std::make_index_sequence<size>());
}
// Static Dispatchers with no out params.
@@ -104,14 +66,15 @@ inline void DispatchToMethod(const ObjT& obj,
template <typename Function, typename Tuple, size_t... Ns>
inline void DispatchToFunctionImpl(Function function,
Tuple&& args,
- IndexSequence<Ns...>) {
- (*function)(base::get<Ns>(std::forward<Tuple>(args))...);
+ std::index_sequence<Ns...>) {
+ (*function)(std::get<Ns>(std::forward<Tuple>(args))...);
}
template <typename Function, typename Tuple>
inline void DispatchToFunction(Function function, Tuple&& args) {
+ constexpr size_t size = std::tuple_size<std::decay_t<Tuple>>::value;
DispatchToFunctionImpl(function, std::forward<Tuple>(args),
- MakeIndexSequenceForTuple<Tuple>());
+ std::make_index_sequence<size>());
}
// Dispatchers with out parameters.
@@ -126,9 +89,9 @@ inline void DispatchToMethodImpl(const ObjT& obj,
Method method,
InTuple&& in,
OutTuple* out,
- IndexSequence<InNs...>,
- IndexSequence<OutNs...>) {
- (obj->*method)(base::get<InNs>(std::forward<InTuple>(in))...,
+ std::index_sequence<InNs...>,
+ std::index_sequence<OutNs...>) {
+ (obj->*method)(std::get<InNs>(std::forward<InTuple>(in))...,
&std::get<OutNs>(*out)...);
}
@@ -137,9 +100,11 @@ inline void DispatchToMethod(const ObjT& obj,
Method method,
InTuple&& in,
OutTuple* out) {
+ constexpr size_t in_size = std::tuple_size<std::decay_t<InTuple>>::value;
+ constexpr size_t out_size = std::tuple_size<OutTuple>::value;
DispatchToMethodImpl(obj, method, std::forward<InTuple>(in), out,
- MakeIndexSequenceForTuple<InTuple>(),
- MakeIndexSequenceForTuple<OutTuple>());
+ std::make_index_sequence<in_size>(),
+ std::make_index_sequence<out_size>());
}
} // namespace base
diff --git a/base/tuple_unittest.cc b/base/tuple_unittest.cc
index 6f90c29220..4b38797e0e 100644
--- a/base/tuple_unittest.cc
+++ b/base/tuple_unittest.cc
@@ -16,14 +16,14 @@ void DoAdd(int a, int b, int c, int* res) {
}
struct Addy {
- Addy() { }
+ Addy() = default;
void DoAdd(int a, int b, int c, int d, int* res) {
*res = a + b + c + d;
}
};
struct Addz {
- Addz() { }
+ Addz() = default;
void DoAdd(int a, int b, int c, int d, int e, int* res) {
*res = a + b + c + d + e;
}
@@ -39,6 +39,7 @@ TEST(TupleTest, Basic) {
std::make_tuple(1, static_cast<const char*>("wee"));
ALLOW_UNUSED_LOCAL(t2);
std::tuple<int, int, int> t3(1, 2, 3);
+ ALLOW_UNUSED_LOCAL(t3);
std::tuple<int, int, int, int*> t4(1, 2, 3, &std::get<0>(t1));
std::tuple<int, int, int, int, int*> t5(1, 2, 3, 4, &std::get<0>(t4));
std::tuple<int, int, int, int, int, int*> t6(1, 2, 3, 4, 5, &std::get<0>(t4));
@@ -67,7 +68,7 @@ namespace {
struct CopyLogger {
CopyLogger() { ++TimesConstructed; }
CopyLogger(const CopyLogger& tocopy) { ++TimesConstructed; ++TimesCopied; }
- ~CopyLogger() { }
+ ~CopyLogger() = default;
static int TimesCopied;
static int TimesConstructed;
@@ -95,7 +96,7 @@ TEST(TupleTest, Copying) {
// Creating the tuple should copy the class to store internally in the tuple.
std::tuple<CopyLogger, CopyLogger*, bool*> tuple(logger, &logger, &res);
- std::get<1>(tuple) = &std::get<0>(tuple);
+ std::get<CopyLogger*>(tuple) = &std::get<CopyLogger>(tuple);
EXPECT_EQ(2, CopyLogger::TimesConstructed);
EXPECT_EQ(1, CopyLogger::TimesCopied);
@@ -114,30 +115,4 @@ TEST(TupleTest, Copying) {
EXPECT_EQ(2, CopyLogger::TimesCopied);
}
-TEST(TupleTest, Get) {
- int i = 1;
- int j = 2;
- std::tuple<int, int&, int&&> t(3, i, std::move(j));
- EXPECT_TRUE((std::is_same<int&, decltype(base::get<0>(t))>::value));
- EXPECT_EQ(3, base::get<0>(t));
-
- EXPECT_TRUE((std::is_same<int&, decltype(base::get<1>(t))>::value));
- EXPECT_EQ(1, base::get<1>(t));
-
- EXPECT_TRUE((std::is_same<int&, decltype(base::get<2>(t))>::value));
- EXPECT_EQ(2, base::get<2>(t));
-
- EXPECT_TRUE((std::is_same<int&&,
- decltype(base::get<0>(std::move(t)))>::value));
- EXPECT_EQ(3, base::get<0>(std::move(t)));
-
- EXPECT_TRUE((std::is_same<int&,
- decltype(base::get<1>(std::move(t)))>::value));
- EXPECT_EQ(1, base::get<1>(std::move(t)));
-
- EXPECT_TRUE((std::is_same<int&&,
- decltype(base::get<2>(std::move(t)))>::value));
- EXPECT_EQ(2, base::get<2>(std::move(t)));
-}
-
} // namespace base
diff --git a/base/unguessable_token.cc b/base/unguessable_token.cc
index cd9830e686..0d8aad39cb 100644
--- a/base/unguessable_token.cc
+++ b/base/unguessable_token.cc
@@ -14,7 +14,7 @@ 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_);
+ return base::StringPrintf("%016" PRIX64 "%016" PRIX64, high_, low_);
}
// static
@@ -35,7 +35,7 @@ UnguessableToken UnguessableToken::Deserialize(uint64_t high, uint64_t low) {
}
std::ostream& operator<<(std::ostream& out, const UnguessableToken& token) {
- return out << token.ToString();
+ return out << "(" << token.ToString() << ")";
}
} // namespace base
diff --git a/base/unguessable_token.h b/base/unguessable_token.h
index 9f38783a3c..6858e22a4c 100644
--- a/base/unguessable_token.h
+++ b/base/unguessable_token.h
@@ -19,11 +19,17 @@ namespace base {
struct UnguessableTokenHash;
// A UnguessableToken is an 128-bit token generated from a cryptographically
-// strong random source.
+// strong random source. It can be used as part of a larger aggregate type,
+// or as an ID in and of itself.
//
-// 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.
+// UnguessableToken can be used to implement "Capability-Based Security".
+// In other words, UnguessableToken can be used when the resource associated
+// with the ID needs to be protected against manipulation by other untrusted
+// agents in the system, and there is no other convenient way to verify the
+// authority of the agent to do so (because the resource is part of a table
+// shared across processes, for instance). In such a scheme, knowledge of the
+// token value in and of itself is sufficient proof of authority to carry out
+// an operation against the associated resource.
//
// Use Create() for creating new UnguessableTokens.
//
@@ -51,7 +57,7 @@ class BASE_EXPORT UnguessableToken {
uint64_t GetHighForSerialization() const {
DCHECK(!is_empty());
return high_;
- };
+ }
// NOTE: Serializing an empty UnguessableToken is an illegal operation.
uint64_t GetLowForSerialization() const {
@@ -61,6 +67,7 @@ class BASE_EXPORT UnguessableToken {
bool is_empty() const { return high_ == 0 && low_ == 0; }
+ // Hex representation of the unguessable token.
std::string ToString() const;
explicit operator bool() const { return !is_empty(); }
diff --git a/base/value_iterators.cc b/base/value_iterators.cc
new file mode 100644
index 0000000000..ba9c73072f
--- /dev/null
+++ b/base/value_iterators.cc
@@ -0,0 +1,228 @@
+// 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/value_iterators.h"
+
+namespace base {
+
+namespace detail {
+
+// ----------------------------------------------------------------------------
+// dict_iterator.
+
+dict_iterator::pointer::pointer(const reference& ref) : ref_(ref) {}
+
+dict_iterator::pointer::pointer(const pointer& ptr) = default;
+
+dict_iterator::dict_iterator(DictStorage::iterator dict_iter)
+ : dict_iter_(dict_iter) {}
+
+dict_iterator::dict_iterator(const dict_iterator& dict_iter) = default;
+
+dict_iterator& dict_iterator::operator=(const dict_iterator& dict_iter) =
+ default;
+
+dict_iterator::~dict_iterator() = default;
+
+dict_iterator::reference dict_iterator::operator*() {
+ return {dict_iter_->first, *dict_iter_->second};
+}
+
+dict_iterator::pointer dict_iterator::operator->() {
+ return pointer(operator*());
+}
+
+dict_iterator& dict_iterator::operator++() {
+ ++dict_iter_;
+ return *this;
+}
+
+dict_iterator dict_iterator::operator++(int) {
+ dict_iterator tmp(*this);
+ ++dict_iter_;
+ return tmp;
+}
+
+dict_iterator& dict_iterator::operator--() {
+ --dict_iter_;
+ return *this;
+}
+
+dict_iterator dict_iterator::operator--(int) {
+ dict_iterator tmp(*this);
+ --dict_iter_;
+ return tmp;
+}
+
+bool operator==(const dict_iterator& lhs, const dict_iterator& rhs) {
+ return lhs.dict_iter_ == rhs.dict_iter_;
+}
+
+bool operator!=(const dict_iterator& lhs, const dict_iterator& rhs) {
+ return !(lhs == rhs);
+}
+
+// ----------------------------------------------------------------------------
+// const_dict_iterator.
+
+const_dict_iterator::pointer::pointer(const reference& ref) : ref_(ref) {}
+
+const_dict_iterator::pointer::pointer(const pointer& ptr) = default;
+
+const_dict_iterator::const_dict_iterator(DictStorage::const_iterator dict_iter)
+ : dict_iter_(dict_iter) {}
+
+const_dict_iterator::const_dict_iterator(const const_dict_iterator& dict_iter) =
+ default;
+
+const_dict_iterator& const_dict_iterator::operator=(
+ const const_dict_iterator& dict_iter) = default;
+
+const_dict_iterator::~const_dict_iterator() = default;
+
+const_dict_iterator::reference const_dict_iterator::operator*() const {
+ return {dict_iter_->first, *dict_iter_->second};
+}
+
+const_dict_iterator::pointer const_dict_iterator::operator->() const {
+ return pointer(operator*());
+}
+
+const_dict_iterator& const_dict_iterator::operator++() {
+ ++dict_iter_;
+ return *this;
+}
+
+const_dict_iterator const_dict_iterator::operator++(int) {
+ const_dict_iterator tmp(*this);
+ ++dict_iter_;
+ return tmp;
+}
+
+const_dict_iterator& const_dict_iterator::operator--() {
+ --dict_iter_;
+ return *this;
+}
+
+const_dict_iterator const_dict_iterator::operator--(int) {
+ const_dict_iterator tmp(*this);
+ --dict_iter_;
+ return tmp;
+}
+
+bool operator==(const const_dict_iterator& lhs,
+ const const_dict_iterator& rhs) {
+ return lhs.dict_iter_ == rhs.dict_iter_;
+}
+
+bool operator!=(const const_dict_iterator& lhs,
+ const const_dict_iterator& rhs) {
+ return !(lhs == rhs);
+}
+
+// ----------------------------------------------------------------------------
+// dict_iterator_proxy.
+
+dict_iterator_proxy::dict_iterator_proxy(DictStorage* storage)
+ : storage_(storage) {}
+
+dict_iterator_proxy::iterator dict_iterator_proxy::begin() {
+ return iterator(storage_->begin());
+}
+
+dict_iterator_proxy::const_iterator dict_iterator_proxy::begin() const {
+ return const_iterator(storage_->begin());
+}
+
+dict_iterator_proxy::iterator dict_iterator_proxy::end() {
+ return iterator(storage_->end());
+}
+
+dict_iterator_proxy::const_iterator dict_iterator_proxy::end() const {
+ return const_iterator(storage_->end());
+}
+
+dict_iterator_proxy::reverse_iterator dict_iterator_proxy::rbegin() {
+ return reverse_iterator(end());
+}
+
+dict_iterator_proxy::const_reverse_iterator dict_iterator_proxy::rbegin()
+ const {
+ return const_reverse_iterator(end());
+}
+
+dict_iterator_proxy::reverse_iterator dict_iterator_proxy::rend() {
+ return reverse_iterator(begin());
+}
+
+dict_iterator_proxy::const_reverse_iterator dict_iterator_proxy::rend() const {
+ return const_reverse_iterator(begin());
+}
+
+dict_iterator_proxy::const_iterator dict_iterator_proxy::cbegin() const {
+ return const_iterator(begin());
+}
+
+dict_iterator_proxy::const_iterator dict_iterator_proxy::cend() const {
+ return const_iterator(end());
+}
+
+dict_iterator_proxy::const_reverse_iterator dict_iterator_proxy::crbegin()
+ const {
+ return const_reverse_iterator(rbegin());
+}
+
+dict_iterator_proxy::const_reverse_iterator dict_iterator_proxy::crend() const {
+ return const_reverse_iterator(rend());
+}
+
+// ----------------------------------------------------------------------------
+// const_dict_iterator_proxy.
+
+const_dict_iterator_proxy::const_dict_iterator_proxy(const DictStorage* storage)
+ : storage_(storage) {}
+
+const_dict_iterator_proxy::const_iterator const_dict_iterator_proxy::begin()
+ const {
+ return const_iterator(storage_->begin());
+}
+
+const_dict_iterator_proxy::const_iterator const_dict_iterator_proxy::end()
+ const {
+ return const_iterator(storage_->end());
+}
+
+const_dict_iterator_proxy::const_reverse_iterator
+const_dict_iterator_proxy::rbegin() const {
+ return const_reverse_iterator(end());
+}
+
+const_dict_iterator_proxy::const_reverse_iterator
+const_dict_iterator_proxy::rend() const {
+ return const_reverse_iterator(begin());
+}
+
+const_dict_iterator_proxy::const_iterator const_dict_iterator_proxy::cbegin()
+ const {
+ return const_iterator(begin());
+}
+
+const_dict_iterator_proxy::const_iterator const_dict_iterator_proxy::cend()
+ const {
+ return const_iterator(end());
+}
+
+const_dict_iterator_proxy::const_reverse_iterator
+const_dict_iterator_proxy::crbegin() const {
+ return const_reverse_iterator(rbegin());
+}
+
+const_dict_iterator_proxy::const_reverse_iterator
+const_dict_iterator_proxy::crend() const {
+ return const_reverse_iterator(rend());
+}
+
+} // namespace detail
+
+} // namespace base
diff --git a/base/value_iterators.h b/base/value_iterators.h
new file mode 100644
index 0000000000..2e05127bf5
--- /dev/null
+++ b/base/value_iterators.h
@@ -0,0 +1,194 @@
+// 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_VALUE_ITERATORS_H_
+#define BASE_VALUE_ITERATORS_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/base_export.h"
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+
+namespace base {
+
+class Value;
+
+namespace detail {
+
+using DictStorage = base::flat_map<std::string, std::unique_ptr<Value>>;
+
+// This iterator closely resembles DictStorage::iterator, with one
+// important exception. It abstracts the underlying unique_ptr away, meaning its
+// value_type is std::pair<const std::string, Value>. It's reference type is a
+// std::pair<const std::string&, Value&>, so that callers have read-write
+// access without incurring a copy.
+class BASE_EXPORT dict_iterator {
+ public:
+ using difference_type = DictStorage::iterator::difference_type;
+ using value_type = std::pair<const std::string, Value>;
+ using reference = std::pair<const std::string&, Value&>;
+ using iterator_category = std::bidirectional_iterator_tag;
+
+ class pointer {
+ public:
+ explicit pointer(const reference& ref);
+ pointer(const pointer& ptr);
+ pointer& operator=(const pointer& ptr) = delete;
+
+ reference* operator->() { return &ref_; }
+
+ private:
+ reference ref_;
+ };
+
+ explicit dict_iterator(DictStorage::iterator dict_iter);
+ dict_iterator(const dict_iterator& dict_iter);
+ dict_iterator& operator=(const dict_iterator& dict_iter);
+ ~dict_iterator();
+
+ reference operator*();
+ pointer operator->();
+
+ dict_iterator& operator++();
+ dict_iterator operator++(int);
+ dict_iterator& operator--();
+ dict_iterator operator--(int);
+
+ BASE_EXPORT friend bool operator==(const dict_iterator& lhs,
+ const dict_iterator& rhs);
+ BASE_EXPORT friend bool operator!=(const dict_iterator& lhs,
+ const dict_iterator& rhs);
+
+ private:
+ DictStorage::iterator dict_iter_;
+};
+
+// This iterator closely resembles DictStorage::const_iterator, with one
+// important exception. It abstracts the underlying unique_ptr away, meaning its
+// value_type is std::pair<const std::string, Value>. It's reference type is a
+// std::pair<const std::string&, const Value&>, so that callers have read-only
+// access without incurring a copy.
+class BASE_EXPORT const_dict_iterator {
+ public:
+ using difference_type = DictStorage::const_iterator::difference_type;
+ using value_type = std::pair<const std::string, Value>;
+ using reference = std::pair<const std::string&, const Value&>;
+ using iterator_category = std::bidirectional_iterator_tag;
+
+ class pointer {
+ public:
+ explicit pointer(const reference& ref);
+ pointer(const pointer& ptr);
+ pointer& operator=(const pointer& ptr) = delete;
+
+ const reference* operator->() const { return &ref_; }
+
+ private:
+ const reference ref_;
+ };
+
+ explicit const_dict_iterator(DictStorage::const_iterator dict_iter);
+ const_dict_iterator(const const_dict_iterator& dict_iter);
+ const_dict_iterator& operator=(const const_dict_iterator& dict_iter);
+ ~const_dict_iterator();
+
+ reference operator*() const;
+ pointer operator->() const;
+
+ const_dict_iterator& operator++();
+ const_dict_iterator operator++(int);
+ const_dict_iterator& operator--();
+ const_dict_iterator operator--(int);
+
+ BASE_EXPORT friend bool operator==(const const_dict_iterator& lhs,
+ const const_dict_iterator& rhs);
+ BASE_EXPORT friend bool operator!=(const const_dict_iterator& lhs,
+ const const_dict_iterator& rhs);
+
+ private:
+ DictStorage::const_iterator dict_iter_;
+};
+
+// This class wraps the various |begin| and |end| methods of the underlying
+// DictStorage in dict_iterators and const_dict_iterators. This allows callers
+// to use this class for easy iteration over the underlying values, granting
+// them either read-only or read-write access, depending on the
+// const-qualification.
+class BASE_EXPORT dict_iterator_proxy {
+ public:
+ using key_type = DictStorage::key_type;
+ using mapped_type = DictStorage::mapped_type::element_type;
+ using value_type = std::pair<key_type, mapped_type>;
+ using key_compare = DictStorage::key_compare;
+ using size_type = DictStorage::size_type;
+ using difference_type = DictStorage::difference_type;
+
+ using iterator = dict_iterator;
+ using const_iterator = const_dict_iterator;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ explicit dict_iterator_proxy(DictStorage* storage);
+
+ iterator begin();
+ const_iterator begin() const;
+ iterator end();
+ const_iterator end() const;
+
+ reverse_iterator rbegin();
+ const_reverse_iterator rbegin() const;
+ reverse_iterator rend();
+ const_reverse_iterator rend() const;
+
+ const_dict_iterator cbegin() const;
+ const_dict_iterator cend() const;
+ const_reverse_iterator crbegin() const;
+ const_reverse_iterator crend() const;
+
+ private:
+ DictStorage* storage_;
+};
+
+// This class wraps the various const |begin| and |end| methods of the
+// underlying DictStorage in const_dict_iterators. This allows callers to use
+// this class for easy iteration over the underlying values, granting them
+// either read-only access.
+class BASE_EXPORT const_dict_iterator_proxy {
+ public:
+ using key_type = const DictStorage::key_type;
+ using mapped_type = const DictStorage::mapped_type::element_type;
+ using value_type = std::pair<key_type, mapped_type>;
+ using key_compare = DictStorage::key_compare;
+ using size_type = DictStorage::size_type;
+ using difference_type = DictStorage::difference_type;
+
+ using iterator = const_dict_iterator;
+ using const_iterator = const_dict_iterator;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ explicit const_dict_iterator_proxy(const DictStorage* storage);
+
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ const_reverse_iterator rbegin() const;
+ const_reverse_iterator rend() const;
+
+ const_iterator cbegin() const;
+ const_iterator cend() const;
+ const_reverse_iterator crbegin() const;
+ const_reverse_iterator crend() const;
+
+ private:
+ const DictStorage* storage_;
+};
+} // namespace detail
+
+} // namespace base
+
+#endif // BASE_VALUE_ITERATORS_H_
diff --git a/base/value_iterators_unittest.cc b/base/value_iterators_unittest.cc
new file mode 100644
index 0000000000..ed8618289b
--- /dev/null
+++ b/base/value_iterators_unittest.cc
@@ -0,0 +1,335 @@
+// 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/value_iterators.h"
+
+#include <type_traits>
+
+#include "base/memory/ptr_util.h"
+#include "base/values.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace detail {
+
+namespace {
+
+// Implementation of std::equal variant that is missing in C++11.
+template <class BinaryPredicate, class InputIterator1, class InputIterator2>
+bool are_equal(InputIterator1 first1,
+ InputIterator1 last1,
+ InputIterator2 first2,
+ InputIterator2 last2,
+ BinaryPredicate pred) {
+ for (; first1 != last1 && first2 != last2; ++first1, ++first2) {
+ if (!pred(*first1, *first2))
+ return false;
+ }
+ return first1 == last1 && first2 == last2;
+}
+
+} // namespace
+
+TEST(ValueIteratorsTest, SameDictStorage) {
+ static_assert(std::is_same<Value::DictStorage, DictStorage>::value,
+ "DictStorage differs between Value and Value Iterators.");
+}
+
+TEST(ValueIteratorsTest, IsAssignable) {
+ static_assert(
+ !std::is_assignable<dict_iterator::reference::first_type, std::string>(),
+ "Can assign strings to dict_iterator");
+
+ static_assert(
+ std::is_assignable<dict_iterator::reference::second_type, Value>(),
+ "Can't assign Values to dict_iterator");
+
+ static_assert(!std::is_assignable<const_dict_iterator::reference::first_type,
+ std::string>(),
+ "Can assign strings to const_dict_iterator");
+
+ static_assert(
+ !std::is_assignable<const_dict_iterator::reference::second_type, Value>(),
+ "Can assign Values to const_dict_iterator");
+}
+
+TEST(ValueIteratorsTest, DictIteratorOperatorStar) {
+ DictStorage storage;
+ storage.emplace("0", std::make_unique<Value>(0));
+
+ using iterator = dict_iterator;
+ iterator iter(storage.begin());
+ EXPECT_EQ("0", (*iter).first);
+ EXPECT_EQ(Value(0), (*iter).second);
+
+ (*iter).second = Value(1);
+ EXPECT_EQ(Value(1), *storage["0"]);
+}
+
+TEST(ValueIteratorsTest, DictIteratorOperatorArrow) {
+ DictStorage storage;
+ storage.emplace("0", std::make_unique<Value>(0));
+
+ using iterator = dict_iterator;
+ iterator iter(storage.begin());
+ EXPECT_EQ("0", iter->first);
+ EXPECT_EQ(Value(0), iter->second);
+
+ iter->second = Value(1);
+ EXPECT_EQ(Value(1), *storage["0"]);
+}
+
+TEST(ValueIteratorsTest, DictIteratorPreIncrement) {
+ DictStorage storage;
+ storage.emplace("0", std::make_unique<Value>(0));
+ storage.emplace("1", std::make_unique<Value>(1));
+
+ using iterator = dict_iterator;
+ iterator iter(storage.begin());
+ EXPECT_EQ("0", iter->first);
+ EXPECT_EQ(Value(0), iter->second);
+
+ iterator& iter_ref = ++iter;
+ EXPECT_EQ(&iter, &iter_ref);
+
+ EXPECT_EQ("1", iter_ref->first);
+ EXPECT_EQ(Value(1), iter_ref->second);
+}
+
+TEST(ValueIteratorsTest, DictIteratorPostIncrement) {
+ DictStorage storage;
+ storage.emplace("0", std::make_unique<Value>(0));
+ storage.emplace("1", std::make_unique<Value>(1));
+
+ using iterator = dict_iterator;
+ iterator iter(storage.begin());
+ iterator iter_old = iter++;
+
+ EXPECT_EQ("0", iter_old->first);
+ EXPECT_EQ(Value(0), iter_old->second);
+
+ EXPECT_EQ("1", iter->first);
+ EXPECT_EQ(Value(1), iter->second);
+}
+
+TEST(ValueIteratorsTest, DictIteratorPreDecrement) {
+ DictStorage storage;
+ storage.emplace("0", std::make_unique<Value>(0));
+ storage.emplace("1", std::make_unique<Value>(1));
+
+ using iterator = dict_iterator;
+ iterator iter(++storage.begin());
+ EXPECT_EQ("1", iter->first);
+ EXPECT_EQ(Value(1), iter->second);
+
+ iterator& iter_ref = --iter;
+ EXPECT_EQ(&iter, &iter_ref);
+
+ EXPECT_EQ("0", iter_ref->first);
+ EXPECT_EQ(Value(0), iter_ref->second);
+}
+
+TEST(ValueIteratorsTest, DictIteratorPostDecrement) {
+ DictStorage storage;
+ storage.emplace("0", std::make_unique<Value>(0));
+ storage.emplace("1", std::make_unique<Value>(1));
+
+ using iterator = dict_iterator;
+ iterator iter(++storage.begin());
+ iterator iter_old = iter--;
+
+ EXPECT_EQ("1", iter_old->first);
+ EXPECT_EQ(Value(1), iter_old->second);
+
+ EXPECT_EQ("0", iter->first);
+ EXPECT_EQ(Value(0), iter->second);
+}
+
+TEST(ValueIteratorsTest, DictIteratorOperatorEQ) {
+ DictStorage storage;
+ using iterator = dict_iterator;
+ EXPECT_EQ(iterator(storage.begin()), iterator(storage.begin()));
+ EXPECT_EQ(iterator(storage.end()), iterator(storage.end()));
+}
+
+TEST(ValueIteratorsTest, DictIteratorOperatorNE) {
+ DictStorage storage;
+ storage.emplace("0", std::make_unique<Value>(0));
+
+ using iterator = dict_iterator;
+ EXPECT_NE(iterator(storage.begin()), iterator(storage.end()));
+}
+
+TEST(ValueIteratorsTest, ConstDictIteratorOperatorStar) {
+ DictStorage storage;
+ storage.emplace("0", std::make_unique<Value>(0));
+
+ using iterator = const_dict_iterator;
+ iterator iter(storage.begin());
+ EXPECT_EQ("0", (*iter).first);
+ EXPECT_EQ(Value(0), (*iter).second);
+}
+
+TEST(ValueIteratorsTest, ConstDictIteratorOperatorArrow) {
+ DictStorage storage;
+ storage.emplace("0", std::make_unique<Value>(0));
+
+ using iterator = const_dict_iterator;
+ iterator iter(storage.begin());
+ EXPECT_EQ("0", iter->first);
+ EXPECT_EQ(Value(0), iter->second);
+}
+
+TEST(ValueIteratorsTest, ConstDictIteratorPreIncrement) {
+ DictStorage storage;
+ storage.emplace("0", std::make_unique<Value>(0));
+ storage.emplace("1", std::make_unique<Value>(1));
+
+ using iterator = const_dict_iterator;
+ iterator iter(storage.begin());
+ EXPECT_EQ("0", iter->first);
+ EXPECT_EQ(Value(0), iter->second);
+
+ iterator& iter_ref = ++iter;
+ EXPECT_EQ(&iter, &iter_ref);
+
+ EXPECT_EQ("1", iter_ref->first);
+ EXPECT_EQ(Value(1), iter_ref->second);
+}
+
+TEST(ValueIteratorsTest, ConstDictIteratorPostIncrement) {
+ DictStorage storage;
+ storage.emplace("0", std::make_unique<Value>(0));
+ storage.emplace("1", std::make_unique<Value>(1));
+
+ using iterator = const_dict_iterator;
+ iterator iter(storage.begin());
+ iterator iter_old = iter++;
+
+ EXPECT_EQ("0", iter_old->first);
+ EXPECT_EQ(Value(0), iter_old->second);
+
+ EXPECT_EQ("1", iter->first);
+ EXPECT_EQ(Value(1), iter->second);
+}
+
+TEST(ValueIteratorsTest, ConstDictIteratorPreDecrement) {
+ DictStorage storage;
+ storage.emplace("0", std::make_unique<Value>(0));
+ storage.emplace("1", std::make_unique<Value>(1));
+
+ using iterator = const_dict_iterator;
+ iterator iter(++storage.begin());
+ EXPECT_EQ("1", iter->first);
+ EXPECT_EQ(Value(1), iter->second);
+
+ iterator& iter_ref = --iter;
+ EXPECT_EQ(&iter, &iter_ref);
+
+ EXPECT_EQ("0", iter_ref->first);
+ EXPECT_EQ(Value(0), iter_ref->second);
+}
+
+TEST(ValueIteratorsTest, ConstDictIteratorPostDecrement) {
+ DictStorage storage;
+ storage.emplace("0", std::make_unique<Value>(0));
+ storage.emplace("1", std::make_unique<Value>(1));
+
+ using iterator = const_dict_iterator;
+ iterator iter(++storage.begin());
+ iterator iter_old = iter--;
+
+ EXPECT_EQ("1", iter_old->first);
+ EXPECT_EQ(Value(1), iter_old->second);
+
+ EXPECT_EQ("0", iter->first);
+ EXPECT_EQ(Value(0), iter->second);
+}
+
+TEST(ValueIteratorsTest, ConstDictIteratorOperatorEQ) {
+ DictStorage storage;
+ using iterator = const_dict_iterator;
+ EXPECT_EQ(iterator(storage.begin()), iterator(storage.begin()));
+ EXPECT_EQ(iterator(storage.end()), iterator(storage.end()));
+}
+
+TEST(ValueIteratorsTest, ConstDictIteratorOperatorNE) {
+ DictStorage storage;
+ storage.emplace("0", std::make_unique<Value>(0));
+
+ using iterator = const_dict_iterator;
+ EXPECT_NE(iterator(storage.begin()), iterator(storage.end()));
+}
+
+TEST(ValueIteratorsTest, DictIteratorProxy) {
+ DictStorage storage;
+ storage.emplace("null", std::make_unique<Value>(Value::Type::NONE));
+ storage.emplace("bool", std::make_unique<Value>(Value::Type::BOOLEAN));
+ storage.emplace("int", std::make_unique<Value>(Value::Type::INTEGER));
+ storage.emplace("double", std::make_unique<Value>(Value::Type::DOUBLE));
+ storage.emplace("string", std::make_unique<Value>(Value::Type::STRING));
+ storage.emplace("blob", std::make_unique<Value>(Value::Type::BINARY));
+ storage.emplace("dict", std::make_unique<Value>(Value::Type::DICTIONARY));
+ storage.emplace("list", std::make_unique<Value>(Value::Type::LIST));
+
+ using iterator = const_dict_iterator;
+ using iterator_proxy = dict_iterator_proxy;
+ iterator_proxy proxy(&storage);
+
+ auto equal_to = [](const DictStorage::value_type& lhs,
+ const iterator::reference& rhs) {
+ return std::tie(lhs.first, *lhs.second) == std::tie(rhs.first, rhs.second);
+ };
+
+ EXPECT_TRUE(are_equal(storage.begin(), storage.end(), proxy.begin(),
+ proxy.end(), equal_to));
+
+ EXPECT_TRUE(are_equal(storage.rbegin(), storage.rend(), proxy.rbegin(),
+ proxy.rend(), equal_to));
+
+ EXPECT_TRUE(are_equal(storage.cbegin(), storage.cend(), proxy.cbegin(),
+ proxy.cend(), equal_to));
+
+ EXPECT_TRUE(are_equal(storage.crbegin(), storage.crend(), proxy.crbegin(),
+ proxy.crend(), equal_to));
+}
+
+TEST(ValueIteratorsTest, ConstDictIteratorProxy) {
+ DictStorage storage;
+ storage.emplace("null", std::make_unique<Value>(Value::Type::NONE));
+ storage.emplace("bool", std::make_unique<Value>(Value::Type::BOOLEAN));
+ storage.emplace("int", std::make_unique<Value>(Value::Type::INTEGER));
+ storage.emplace("double", std::make_unique<Value>(Value::Type::DOUBLE));
+ storage.emplace("string", std::make_unique<Value>(Value::Type::STRING));
+ storage.emplace("blob", std::make_unique<Value>(Value::Type::BINARY));
+ storage.emplace("dict", std::make_unique<Value>(Value::Type::DICTIONARY));
+ storage.emplace("list", std::make_unique<Value>(Value::Type::LIST));
+
+ using iterator = const_dict_iterator;
+ using iterator_proxy = const_dict_iterator_proxy;
+ iterator_proxy proxy(&storage);
+
+ auto equal_to = [](const DictStorage::value_type& lhs,
+ const iterator::reference& rhs) {
+ return std::tie(lhs.first, *lhs.second) == std::tie(rhs.first, rhs.second);
+ };
+
+ EXPECT_TRUE(are_equal(storage.begin(), storage.end(), proxy.begin(),
+ proxy.end(), equal_to));
+
+ EXPECT_TRUE(are_equal(storage.rbegin(), storage.rend(), proxy.rbegin(),
+ proxy.rend(), equal_to));
+
+ EXPECT_TRUE(are_equal(storage.cbegin(), storage.cend(), proxy.cbegin(),
+ proxy.cend(), equal_to));
+
+ EXPECT_TRUE(are_equal(storage.crbegin(), storage.crend(), proxy.crbegin(),
+ proxy.crend(), equal_to));
+}
+
+} // namespace detail
+
+} // namespace base
diff --git a/base/values.cc b/base/values.cc
index b5e44e68dd..a48af5eb61 100644
--- a/base/values.cc
+++ b/base/values.cc
@@ -8,14 +8,18 @@
#include <algorithm>
#include <cmath>
+#include <new>
#include <ostream>
#include <utility>
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
+#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
+// Unsupported in libchrome
+// #include "base/trace_event/memory_usage_estimator.h"
namespace base {
@@ -32,17 +36,15 @@ std::unique_ptr<Value> CopyWithoutEmptyChildren(const Value& node);
// Make a deep copy of |node|, but don't include empty lists or dictionaries
// in the copy. It's possible for this function to return NULL and it
// expects |node| to always be non-NULL.
-std::unique_ptr<ListValue> CopyListWithoutEmptyChildren(const ListValue& list) {
- std::unique_ptr<ListValue> copy;
- for (const auto& entry : list) {
- std::unique_ptr<Value> child_copy = CopyWithoutEmptyChildren(*entry);
- if (child_copy) {
- if (!copy)
- copy.reset(new ListValue);
- copy->Append(std::move(child_copy));
- }
+std::unique_ptr<Value> CopyListWithoutEmptyChildren(const Value& list) {
+ Value copy(Value::Type::LIST);
+ for (const auto& entry : list.GetList()) {
+ std::unique_ptr<Value> child_copy = CopyWithoutEmptyChildren(entry);
+ if (child_copy)
+ copy.GetList().push_back(std::move(*child_copy));
}
- return copy;
+ return copy.GetList().empty() ? nullptr
+ : std::make_unique<Value>(std::move(copy));
}
std::unique_ptr<DictionaryValue> CopyDictionaryWithoutEmptyChildren(
@@ -52,7 +54,7 @@ std::unique_ptr<DictionaryValue> CopyDictionaryWithoutEmptyChildren(
std::unique_ptr<Value> child_copy = CopyWithoutEmptyChildren(it.value());
if (child_copy) {
if (!copy)
- copy.reset(new DictionaryValue);
+ copy = std::make_unique<DictionaryValue>();
copy->SetWithoutPathExpansion(it.key(), std::move(child_copy));
}
}
@@ -60,7 +62,7 @@ std::unique_ptr<DictionaryValue> CopyDictionaryWithoutEmptyChildren(
}
std::unique_ptr<Value> CopyWithoutEmptyChildren(const Value& node) {
- switch (node.GetType()) {
+ switch (node.type()) {
case Value::Type::LIST:
return CopyListWithoutEmptyChildren(static_cast<const ListValue&>(node));
@@ -69,26 +71,26 @@ std::unique_ptr<Value> CopyWithoutEmptyChildren(const Value& node) {
static_cast<const DictionaryValue&>(node));
default:
- return MakeUnique<Value>(node);
+ return std::make_unique<Value>(node.Clone());
}
}
} // namespace
// static
-std::unique_ptr<Value> Value::CreateNullValue() {
- return WrapUnique(new Value(Type::NONE));
+std::unique_ptr<Value> Value::CreateWithCopiedBuffer(const char* buffer,
+ size_t size) {
+ return std::make_unique<Value>(BlobStorage(buffer, buffer + size));
}
// static
-std::unique_ptr<BinaryValue> BinaryValue::CreateWithCopiedBuffer(
- const char* buffer,
- size_t size) {
- return MakeUnique<BinaryValue>(std::vector<char>(buffer, buffer + size));
+Value Value::FromUniquePtrValue(std::unique_ptr<Value> val) {
+ return std::move(*val);
}
-Value::Value(const Value& that) {
- InternalCopyConstructFrom(that);
+// static
+std::unique_ptr<Value> Value::ToUniquePtrValue(Value val) {
+ return std::make_unique<Value>(std::move(val));
}
Value::Value(Value&& that) noexcept {
@@ -113,16 +115,16 @@ Value::Value(Type type) : type_(type) {
double_value_ = 0.0;
return;
case Type::STRING:
- string_value_.Init();
+ new (&string_value_) std::string();
return;
case Type::BINARY:
- binary_value_.Init();
+ new (&binary_value_) BlobStorage();
return;
case Type::DICTIONARY:
- dict_ptr_.Init(MakeUnique<DictStorage>());
+ new (&dict_) DictStorage();
return;
case Type::LIST:
- list_.Init();
+ new (&list_) ListStorage();
return;
}
}
@@ -139,59 +141,76 @@ Value::Value(double in_double) : type_(Type::DOUBLE), double_value_(in_double) {
}
}
-Value::Value(const char* in_string) : type_(Type::STRING) {
- string_value_.Init(in_string);
- DCHECK(IsStringUTF8(*string_value_));
-}
+Value::Value(const char* in_string) : Value(std::string(in_string)) {}
-Value::Value(const std::string& in_string) : type_(Type::STRING) {
- string_value_.Init(in_string);
- DCHECK(IsStringUTF8(*string_value_));
-}
+Value::Value(StringPiece in_string) : Value(std::string(in_string)) {}
-Value::Value(std::string&& in_string) noexcept : type_(Type::STRING) {
- string_value_.Init(std::move(in_string));
- DCHECK(IsStringUTF8(*string_value_));
+Value::Value(std::string&& in_string) noexcept
+ : type_(Type::STRING), string_value_(std::move(in_string)) {
+ DCHECK(IsStringUTF8(string_value_));
}
-Value::Value(const char16* in_string) : type_(Type::STRING) {
- string_value_.Init(UTF16ToUTF8(in_string));
-}
+Value::Value(const char16* in_string16) : Value(StringPiece16(in_string16)) {}
-Value::Value(const string16& in_string) : type_(Type::STRING) {
- string_value_.Init(UTF16ToUTF8(in_string));
-}
+Value::Value(StringPiece16 in_string16) : Value(UTF16ToUTF8(in_string16)) {}
-Value::Value(StringPiece in_string) : Value(in_string.as_string()) {}
+Value::Value(const BlobStorage& in_blob)
+ : type_(Type::BINARY), binary_value_(in_blob) {}
-Value::Value(const std::vector<char>& in_blob) : type_(Type::BINARY) {
- binary_value_.Init(in_blob);
-}
+Value::Value(BlobStorage&& in_blob) noexcept
+ : type_(Type::BINARY), binary_value_(std::move(in_blob)) {}
-Value::Value(std::vector<char>&& in_blob) noexcept : type_(Type::BINARY) {
- binary_value_.Init(std::move(in_blob));
+Value::Value(const DictStorage& in_dict) : type_(Type::DICTIONARY), dict_() {
+ dict_.reserve(in_dict.size());
+ for (const auto& it : in_dict) {
+ dict_.try_emplace(dict_.end(), it.first,
+ std::make_unique<Value>(it.second->Clone()));
+ }
}
-Value& Value::operator=(const Value& that) {
- if (type_ == that.type_) {
- InternalCopyAssignFromSameType(that);
- } else {
- // This is not a self assignment because the type_ doesn't match.
- InternalCleanup();
- InternalCopyConstructFrom(that);
- }
+Value::Value(DictStorage&& in_dict) noexcept
+ : type_(Type::DICTIONARY), dict_(std::move(in_dict)) {}
- return *this;
+Value::Value(const ListStorage& in_list) : type_(Type::LIST), list_() {
+ list_.reserve(in_list.size());
+ for (const auto& val : in_list)
+ list_.emplace_back(val.Clone());
}
+Value::Value(ListStorage&& in_list) noexcept
+ : type_(Type::LIST), list_(std::move(in_list)) {}
+
Value& Value::operator=(Value&& that) noexcept {
- DCHECK(this != &that) << "attempt to self move assign.";
InternalCleanup();
InternalMoveConstructFrom(std::move(that));
return *this;
}
+Value Value::Clone() const {
+ switch (type_) {
+ case Type::NONE:
+ return Value();
+ case Type::BOOLEAN:
+ return Value(bool_value_);
+ case Type::INTEGER:
+ return Value(int_value_);
+ case Type::DOUBLE:
+ return Value(double_value_);
+ case Type::STRING:
+ return Value(string_value_);
+ case Type::BINARY:
+ return Value(binary_value_);
+ case Type::DICTIONARY:
+ return Value(dict_);
+ case Type::LIST:
+ return Value(list_);
+ }
+
+ NOTREACHED();
+ return Value();
+}
+
Value::~Value() {
InternalCleanup();
}
@@ -224,20 +243,202 @@ double Value::GetDouble() const {
const std::string& Value::GetString() const {
CHECK(is_string());
- return *string_value_;
+ return string_value_;
}
-const std::vector<char>& Value::GetBlob() const {
+const Value::BlobStorage& Value::GetBlob() const {
CHECK(is_blob());
- return *binary_value_;
+ return binary_value_;
+}
+
+Value::ListStorage& Value::GetList() {
+ CHECK(is_list());
+ return list_;
+}
+
+const Value::ListStorage& Value::GetList() const {
+ CHECK(is_list());
+ return list_;
+}
+
+Value* Value::FindKey(StringPiece key) {
+ return const_cast<Value*>(static_cast<const Value*>(this)->FindKey(key));
+}
+
+const Value* Value::FindKey(StringPiece key) const {
+ CHECK(is_dict());
+ auto found = dict_.find(key);
+ if (found == dict_.end())
+ return nullptr;
+ return found->second.get();
+}
+
+Value* Value::FindKeyOfType(StringPiece key, Type type) {
+ return const_cast<Value*>(
+ static_cast<const Value*>(this)->FindKeyOfType(key, type));
+}
+
+const Value* Value::FindKeyOfType(StringPiece key, Type type) const {
+ const Value* result = FindKey(key);
+ if (!result || result->type() != type)
+ return nullptr;
+ return result;
+}
+
+bool Value::RemoveKey(StringPiece key) {
+ CHECK(is_dict());
+ // NOTE: Can't directly return dict_->erase(key) due to MSVC warning C4800.
+ return dict_.erase(key) != 0;
+}
+
+Value* Value::SetKey(StringPiece key, Value value) {
+ CHECK(is_dict());
+ // NOTE: We can't use |insert_or_assign| here, as only |try_emplace| does
+ // an explicit conversion from StringPiece to std::string if necessary.
+ auto val_ptr = std::make_unique<Value>(std::move(value));
+ auto result = dict_.try_emplace(key, std::move(val_ptr));
+ if (!result.second) {
+ // val_ptr is guaranteed to be still intact at this point.
+ result.first->second = std::move(val_ptr);
+ }
+ return result.first->second.get();
+}
+
+Value* Value::SetKey(std::string&& key, Value value) {
+ CHECK(is_dict());
+ return dict_
+ .insert_or_assign(std::move(key),
+ std::make_unique<Value>(std::move(value)))
+ .first->second.get();
+}
+
+Value* Value::SetKey(const char* key, Value value) {
+ return SetKey(StringPiece(key), std::move(value));
+}
+
+Value* Value::FindPath(std::initializer_list<StringPiece> path) {
+ return const_cast<Value*>(const_cast<const Value*>(this)->FindPath(path));
+}
+
+Value* Value::FindPath(span<const StringPiece> path) {
+ return const_cast<Value*>(const_cast<const Value*>(this)->FindPath(path));
+}
+
+const Value* Value::FindPath(std::initializer_list<StringPiece> path) const {
+ DCHECK_GE(path.size(), 2u) << "Use FindKey() for a path of length 1.";
+ return FindPath(make_span(path.begin(), path.size()));
+}
+
+const Value* Value::FindPath(span<const StringPiece> path) const {
+ const Value* cur = this;
+ for (const StringPiece component : path) {
+ if (!cur->is_dict() || (cur = cur->FindKey(component)) == nullptr)
+ return nullptr;
+ }
+ return cur;
+}
+
+Value* Value::FindPathOfType(std::initializer_list<StringPiece> path,
+ Type type) {
+ return const_cast<Value*>(
+ const_cast<const Value*>(this)->FindPathOfType(path, type));
+}
+
+Value* Value::FindPathOfType(span<const StringPiece> path, Type type) {
+ return const_cast<Value*>(
+ const_cast<const Value*>(this)->FindPathOfType(path, type));
+}
+
+const Value* Value::FindPathOfType(std::initializer_list<StringPiece> path,
+ Type type) const {
+ DCHECK_GE(path.size(), 2u) << "Use FindKeyOfType() for a path of length 1.";
+ return FindPathOfType(make_span(path.begin(), path.size()), type);
+}
+
+const Value* Value::FindPathOfType(span<const StringPiece> path,
+ Type type) const {
+ const Value* result = FindPath(path);
+ if (!result || result->type() != type)
+ return nullptr;
+ return result;
}
-size_t Value::GetSize() const {
- return GetBlob().size();
+Value* Value::SetPath(std::initializer_list<StringPiece> path, Value value) {
+ DCHECK_GE(path.size(), 2u) << "Use SetKey() for a path of length 1.";
+ return SetPath(make_span(path.begin(), path.size()), std::move(value));
+}
+
+Value* Value::SetPath(span<const StringPiece> path, Value value) {
+ DCHECK_NE(path.begin(), path.end()); // Can't be empty path.
+
+ // Walk/construct intermediate dictionaries. The last element requires
+ // special handling so skip it in this loop.
+ Value* cur = this;
+ const StringPiece* cur_path = path.begin();
+ for (; (cur_path + 1) < path.end(); ++cur_path) {
+ if (!cur->is_dict())
+ return nullptr;
+
+ // Use lower_bound to avoid doing the search twice for missing keys.
+ const StringPiece path_component = *cur_path;
+ auto found = cur->dict_.lower_bound(path_component);
+ if (found == cur->dict_.end() || found->first != path_component) {
+ // No key found, insert one.
+ auto inserted = cur->dict_.try_emplace(
+ found, path_component, std::make_unique<Value>(Type::DICTIONARY));
+ cur = inserted->second.get();
+ } else {
+ cur = found->second.get();
+ }
+ }
+
+ // "cur" will now contain the last dictionary to insert or replace into.
+ if (!cur->is_dict())
+ return nullptr;
+ return cur->SetKey(*cur_path, std::move(value));
}
-const char* Value::GetBuffer() const {
- return GetBlob().data();
+bool Value::RemovePath(std::initializer_list<StringPiece> path) {
+ DCHECK_GE(path.size(), 2u) << "Use RemoveKey() for a path of length 1.";
+ return RemovePath(make_span(path.begin(), path.size()));
+}
+
+bool Value::RemovePath(span<const StringPiece> path) {
+ if (!is_dict() || path.empty())
+ return false;
+
+ if (path.size() == 1)
+ return RemoveKey(path[0]);
+
+ auto found = dict_.find(path[0]);
+ if (found == dict_.end() || !found->second->is_dict())
+ return false;
+
+ bool removed = found->second->RemovePath(path.subspan(1));
+ if (removed && found->second->dict_.empty())
+ dict_.erase(found);
+
+ return removed;
+}
+
+Value::dict_iterator_proxy Value::DictItems() {
+ CHECK(is_dict());
+ return dict_iterator_proxy(&dict_);
+}
+
+Value::const_dict_iterator_proxy Value::DictItems() const {
+ CHECK(is_dict());
+ return const_dict_iterator_proxy(&dict_);
+}
+
+size_t Value::DictSize() const {
+ CHECK(is_dict());
+ return dict_.size();
+}
+
+bool Value::DictEmpty() const {
+ CHECK(is_dict());
+ return dict_.empty();
}
bool Value::GetAsBoolean(bool* out_value) const {
@@ -270,7 +471,7 @@ bool Value::GetAsDouble(double* out_value) const {
bool Value::GetAsString(std::string* out_value) const {
if (out_value && is_string()) {
- *out_value = *string_value_;
+ *out_value = string_value_;
return true;
}
return is_string();
@@ -278,7 +479,7 @@ bool Value::GetAsString(std::string* out_value) const {
bool Value::GetAsString(string16* out_value) const {
if (out_value && is_string()) {
- *out_value = UTF8ToUTF16(*string_value_);
+ *out_value = UTF8ToUTF16(string_value_);
return true;
}
return is_string();
@@ -294,20 +495,12 @@ bool Value::GetAsString(const Value** out_value) const {
bool Value::GetAsString(StringPiece* out_value) const {
if (out_value && is_string()) {
- *out_value = *string_value_;
+ *out_value = string_value_;
return true;
}
return is_string();
}
-bool Value::GetAsBinary(const BinaryValue** out_value) const {
- if (out_value && is_blob()) {
- *out_value = this;
- return true;
- }
- return is_blob();
-}
-
bool Value::GetAsList(ListValue** out_value) {
if (out_value && is_list()) {
*out_value = static_cast<ListValue*>(this);
@@ -341,11 +534,11 @@ bool Value::GetAsDictionary(const DictionaryValue** out_value) const {
}
Value* Value::DeepCopy() const {
- return new Value(*this);
+ return new Value(Clone());
}
std::unique_ptr<Value> Value::CreateDeepCopy() const {
- return MakeUnique<Value>(*this);
+ return std::make_unique<Value>(Clone());
}
bool operator==(const Value& lhs, const Value& rhs) {
@@ -362,28 +555,22 @@ bool operator==(const Value& lhs, const Value& rhs) {
case Value::Type::DOUBLE:
return lhs.double_value_ == rhs.double_value_;
case Value::Type::STRING:
- return *lhs.string_value_ == *rhs.string_value_;
+ return lhs.string_value_ == rhs.string_value_;
case Value::Type::BINARY:
- return *lhs.binary_value_ == *rhs.binary_value_;
+ return lhs.binary_value_ == rhs.binary_value_;
// TODO(crbug.com/646113): Clean this up when DictionaryValue and ListValue
// are completely inlined.
case Value::Type::DICTIONARY:
- if ((*lhs.dict_ptr_)->size() != (*rhs.dict_ptr_)->size())
+ if (lhs.dict_.size() != rhs.dict_.size())
return false;
- return std::equal(std::begin(**lhs.dict_ptr_), std::end(**lhs.dict_ptr_),
- std::begin(**rhs.dict_ptr_),
- [](const Value::DictStorage::value_type& u,
- const Value::DictStorage::value_type& v) {
+ return std::equal(std::begin(lhs.dict_), std::end(lhs.dict_),
+ std::begin(rhs.dict_),
+ [](const auto& u, const auto& v) {
return std::tie(u.first, *u.second) ==
std::tie(v.first, *v.second);
});
case Value::Type::LIST:
- if (lhs.list_->size() != rhs.list_->size())
- return false;
- return std::equal(
- std::begin(*lhs.list_), std::end(*lhs.list_), std::begin(*rhs.list_),
- [](const Value::ListStorage::value_type& u,
- const Value::ListStorage::value_type& v) { return *u == *v; });
+ return lhs.list_ == rhs.list_;
}
NOTREACHED();
@@ -408,25 +595,21 @@ bool operator<(const Value& lhs, const Value& rhs) {
case Value::Type::DOUBLE:
return lhs.double_value_ < rhs.double_value_;
case Value::Type::STRING:
- return *lhs.string_value_ < *rhs.string_value_;
+ return lhs.string_value_ < rhs.string_value_;
case Value::Type::BINARY:
- return *lhs.binary_value_ < *rhs.binary_value_;
+ return lhs.binary_value_ < rhs.binary_value_;
// TODO(crbug.com/646113): Clean this up when DictionaryValue and ListValue
// are completely inlined.
case Value::Type::DICTIONARY:
return std::lexicographical_compare(
- std::begin(**lhs.dict_ptr_), std::end(**lhs.dict_ptr_),
- std::begin(**rhs.dict_ptr_), std::end(**rhs.dict_ptr_),
+ std::begin(lhs.dict_), std::end(lhs.dict_), std::begin(rhs.dict_),
+ std::end(rhs.dict_),
[](const Value::DictStorage::value_type& u,
const Value::DictStorage::value_type& v) {
return std::tie(u.first, *u.second) < std::tie(v.first, *v.second);
});
case Value::Type::LIST:
- return std::lexicographical_compare(
- std::begin(*lhs.list_), std::end(*lhs.list_), std::begin(*rhs.list_),
- std::end(*rhs.list_),
- [](const Value::ListStorage::value_type& u,
- const Value::ListStorage::value_type& v) { return *u < *v; });
+ return lhs.list_ < rhs.list_;
}
NOTREACHED();
@@ -450,21 +633,28 @@ bool Value::Equals(const Value* other) const {
return *this == *other;
}
-// static
-bool Value::Equals(const Value* a, const Value* b) {
- if ((a == NULL) && (b == NULL))
- return true;
- if ((a == NULL) ^ (b == NULL))
- return false;
- return *a == *b;
-}
+// Unsupported in libchrome
+// size_t Value::EstimateMemoryUsage() const {
+// switch (type_) {
+// case Type::STRING:
+// return base::trace_event::EstimateMemoryUsage(string_value_);
+// case Type::BINARY:
+// return base::trace_event::EstimateMemoryUsage(binary_value_);
+// case Type::DICTIONARY:
+// return base::trace_event::EstimateMemoryUsage(dict_);
+// case Type::LIST:
+// return base::trace_event::EstimateMemoryUsage(list_);
+// default:
+// return 0;
+// }
+// }
+
+void Value::InternalMoveConstructFrom(Value&& that) {
+ type_ = that.type_;
-void Value::InternalCopyFundamentalValue(const Value& that) {
switch (type_) {
case Type::NONE:
- // Nothing to do.
return;
-
case Type::BOOLEAN:
bool_value_ = that.bool_value_;
return;
@@ -474,106 +664,17 @@ void Value::InternalCopyFundamentalValue(const Value& that) {
case Type::DOUBLE:
double_value_ = that.double_value_;
return;
-
- default:
- NOTREACHED();
- }
-}
-
-void Value::InternalCopyConstructFrom(const Value& that) {
- type_ = that.type_;
-
- switch (type_) {
- case Type::NONE:
- case Type::BOOLEAN:
- case Type::INTEGER:
- case Type::DOUBLE:
- InternalCopyFundamentalValue(that);
- return;
-
- case Type::STRING:
- string_value_.Init(*that.string_value_);
- return;
- case Type::BINARY:
- binary_value_.Init(*that.binary_value_);
- return;
- // DictStorage and ListStorage are move-only types due to the presence of
- // unique_ptrs. This is why the explicit copy of every element is necessary
- // here.
- // TODO(crbug.com/646113): Clean this up when DictStorage and ListStorage
- // can be copied directly.
- case Type::DICTIONARY:
- dict_ptr_.Init(MakeUnique<DictStorage>());
- for (const auto& it : **that.dict_ptr_) {
- (*dict_ptr_)
- ->emplace_hint((*dict_ptr_)->end(), it.first,
- MakeUnique<Value>(*it.second));
- }
- return;
- case Type::LIST:
- list_.Init();
- list_->reserve(that.list_->size());
- for (const auto& it : *that.list_)
- list_->push_back(MakeUnique<Value>(*it));
- return;
- }
-}
-
-void Value::InternalMoveConstructFrom(Value&& that) {
- type_ = that.type_;
-
- switch (type_) {
- case Type::NONE:
- case Type::BOOLEAN:
- case Type::INTEGER:
- case Type::DOUBLE:
- InternalCopyFundamentalValue(that);
- return;
-
- case Type::STRING:
- string_value_.InitFromMove(std::move(that.string_value_));
- return;
- case Type::BINARY:
- binary_value_.InitFromMove(std::move(that.binary_value_));
- return;
- case Type::DICTIONARY:
- dict_ptr_.InitFromMove(std::move(that.dict_ptr_));
- return;
- case Type::LIST:
- list_.InitFromMove(std::move(that.list_));
- return;
- }
-}
-
-void Value::InternalCopyAssignFromSameType(const Value& that) {
- // TODO(crbug.com/646113): make this a DCHECK once base::Value does not have
- // subclasses.
- CHECK_EQ(type_, that.type_);
-
- switch (type_) {
- case Type::NONE:
- case Type::BOOLEAN:
- case Type::INTEGER:
- case Type::DOUBLE:
- InternalCopyFundamentalValue(that);
- return;
-
case Type::STRING:
- *string_value_ = *that.string_value_;
+ new (&string_value_) std::string(std::move(that.string_value_));
return;
case Type::BINARY:
- *binary_value_ = *that.binary_value_;
+ new (&binary_value_) BlobStorage(std::move(that.binary_value_));
return;
- // DictStorage and ListStorage are move-only types due to the presence of
- // unique_ptrs. This is why the explicit call to the copy constructor is
- // necessary here.
- // TODO(crbug.com/646113): Clean this up when DictStorage and ListStorage
- // can be copied directly.
case Type::DICTIONARY:
- *dict_ptr_ = std::move(*Value(that).dict_ptr_);
+ new (&dict_) DictStorage(std::move(that.dict_));
return;
case Type::LIST:
- *list_ = std::move(*Value(that).list_);
+ new (&list_) ListStorage(std::move(that.list_));
return;
}
}
@@ -588,16 +689,16 @@ void Value::InternalCleanup() {
return;
case Type::STRING:
- string_value_.Destroy();
+ string_value_.~basic_string();
return;
case Type::BINARY:
- binary_value_.Destroy();
+ binary_value_.~BlobStorage();
return;
case Type::DICTIONARY:
- dict_ptr_.Destroy();
+ dict_.~DictStorage();
return;
case Type::LIST:
- list_.Destroy();
+ list_.~ListStorage();
return;
}
}
@@ -616,101 +717,89 @@ std::unique_ptr<DictionaryValue> DictionaryValue::From(
}
DictionaryValue::DictionaryValue() : Value(Type::DICTIONARY) {}
+DictionaryValue::DictionaryValue(const DictStorage& in_dict) : Value(in_dict) {}
+DictionaryValue::DictionaryValue(DictStorage&& in_dict) noexcept
+ : Value(std::move(in_dict)) {}
bool DictionaryValue::HasKey(StringPiece key) const {
DCHECK(IsStringUTF8(key));
- auto current_entry = (*dict_ptr_)->find(key.as_string());
- DCHECK((current_entry == (*dict_ptr_)->end()) || current_entry->second);
- return current_entry != (*dict_ptr_)->end();
+ auto current_entry = dict_.find(key);
+ DCHECK((current_entry == dict_.end()) || current_entry->second);
+ return current_entry != dict_.end();
}
void DictionaryValue::Clear() {
- (*dict_ptr_)->clear();
+ dict_.clear();
}
-void DictionaryValue::Set(StringPiece path, std::unique_ptr<Value> in_value) {
+Value* DictionaryValue::Set(StringPiece path, std::unique_ptr<Value> in_value) {
DCHECK(IsStringUTF8(path));
DCHECK(in_value);
StringPiece current_path(path);
- DictionaryValue* current_dictionary = this;
+ Value* current_dictionary = this;
for (size_t delimiter_position = current_path.find('.');
delimiter_position != StringPiece::npos;
delimiter_position = current_path.find('.')) {
// Assume that we're indexing into a dictionary.
StringPiece key = current_path.substr(0, delimiter_position);
- DictionaryValue* child_dictionary = nullptr;
- if (!current_dictionary->GetDictionary(key, &child_dictionary)) {
- child_dictionary = new DictionaryValue;
- current_dictionary->SetWithoutPathExpansion(
- key, base::WrapUnique(child_dictionary));
+ Value* child_dictionary =
+ current_dictionary->FindKeyOfType(key, Type::DICTIONARY);
+ if (!child_dictionary) {
+ child_dictionary =
+ current_dictionary->SetKey(key, Value(Type::DICTIONARY));
}
current_dictionary = child_dictionary;
current_path = current_path.substr(delimiter_position + 1);
}
- current_dictionary->SetWithoutPathExpansion(current_path,
- std::move(in_value));
-}
-
-void DictionaryValue::Set(StringPiece path, Value* in_value) {
- Set(path, WrapUnique(in_value));
-}
-
-void DictionaryValue::SetBoolean(StringPiece path, bool in_value) {
- Set(path, new Value(in_value));
-}
-
-void DictionaryValue::SetInteger(StringPiece path, int in_value) {
- Set(path, new Value(in_value));
-}
-
-void DictionaryValue::SetDouble(StringPiece path, double in_value) {
- Set(path, new Value(in_value));
-}
-
-void DictionaryValue::SetString(StringPiece path, StringPiece in_value) {
- Set(path, new Value(in_value));
+ return static_cast<DictionaryValue*>(current_dictionary)
+ ->SetWithoutPathExpansion(current_path, std::move(in_value));
}
-void DictionaryValue::SetString(StringPiece path, const string16& in_value) {
- Set(path, new Value(in_value));
+Value* DictionaryValue::SetBoolean(StringPiece path, bool in_value) {
+ return Set(path, std::make_unique<Value>(in_value));
}
-void DictionaryValue::SetWithoutPathExpansion(StringPiece key,
- std::unique_ptr<Value> in_value) {
- (**dict_ptr_)[key.as_string()] = std::move(in_value);
+Value* DictionaryValue::SetInteger(StringPiece path, int in_value) {
+ return Set(path, std::make_unique<Value>(in_value));
}
-void DictionaryValue::SetWithoutPathExpansion(StringPiece key,
- Value* in_value) {
- SetWithoutPathExpansion(key, WrapUnique(in_value));
+Value* DictionaryValue::SetDouble(StringPiece path, double in_value) {
+ return Set(path, std::make_unique<Value>(in_value));
}
-void DictionaryValue::SetBooleanWithoutPathExpansion(StringPiece path,
- bool in_value) {
- SetWithoutPathExpansion(path, base::MakeUnique<base::Value>(in_value));
+Value* DictionaryValue::SetString(StringPiece path, StringPiece in_value) {
+ return Set(path, std::make_unique<Value>(in_value));
}
-void DictionaryValue::SetIntegerWithoutPathExpansion(StringPiece path,
- int in_value) {
- SetWithoutPathExpansion(path, base::MakeUnique<base::Value>(in_value));
+Value* DictionaryValue::SetString(StringPiece path, const string16& in_value) {
+ return Set(path, std::make_unique<Value>(in_value));
}
-void DictionaryValue::SetDoubleWithoutPathExpansion(StringPiece path,
- double in_value) {
- SetWithoutPathExpansion(path, base::MakeUnique<base::Value>(in_value));
+DictionaryValue* DictionaryValue::SetDictionary(
+ StringPiece path,
+ std::unique_ptr<DictionaryValue> in_value) {
+ return static_cast<DictionaryValue*>(Set(path, std::move(in_value)));
}
-void DictionaryValue::SetStringWithoutPathExpansion(StringPiece path,
- StringPiece in_value) {
- SetWithoutPathExpansion(path, base::MakeUnique<base::Value>(in_value));
+ListValue* DictionaryValue::SetList(StringPiece path,
+ std::unique_ptr<ListValue> in_value) {
+ return static_cast<ListValue*>(Set(path, std::move(in_value)));
}
-void DictionaryValue::SetStringWithoutPathExpansion(StringPiece path,
- const string16& in_value) {
- SetWithoutPathExpansion(path, base::MakeUnique<base::Value>(in_value));
+Value* DictionaryValue::SetWithoutPathExpansion(
+ StringPiece key,
+ std::unique_ptr<Value> in_value) {
+ // NOTE: We can't use |insert_or_assign| here, as only |try_emplace| does
+ // an explicit conversion from StringPiece to std::string if necessary.
+ auto result = dict_.try_emplace(key, std::move(in_value));
+ if (!result.second) {
+ // in_value is guaranteed to be still intact at this point.
+ result.first->second = std::move(in_value);
+ }
+ return result.first->second.get();
}
bool DictionaryValue::Get(StringPiece path,
@@ -721,7 +810,7 @@ bool DictionaryValue::Get(StringPiece path,
for (size_t delimiter_position = current_path.find('.');
delimiter_position != std::string::npos;
delimiter_position = current_path.find('.')) {
- const DictionaryValue* child_dictionary = NULL;
+ const DictionaryValue* child_dictionary = nullptr;
if (!current_dictionary->GetDictionaryWithoutPathExpansion(
current_path.substr(0, delimiter_position), &child_dictionary)) {
return false;
@@ -797,10 +886,10 @@ bool DictionaryValue::GetStringASCII(StringPiece path,
}
bool DictionaryValue::GetBinary(StringPiece path,
- const BinaryValue** out_value) const {
+ const Value** out_value) const {
const Value* value;
bool result = Get(path, &value);
- if (!result || !value->IsType(Type::BINARY))
+ if (!result || !value->is_blob())
return false;
if (out_value)
@@ -809,17 +898,16 @@ bool DictionaryValue::GetBinary(StringPiece path,
return true;
}
-bool DictionaryValue::GetBinary(StringPiece path, BinaryValue** out_value) {
+bool DictionaryValue::GetBinary(StringPiece path, Value** out_value) {
return static_cast<const DictionaryValue&>(*this).GetBinary(
- path,
- const_cast<const BinaryValue**>(out_value));
+ path, const_cast<const Value**>(out_value));
}
bool DictionaryValue::GetDictionary(StringPiece path,
const DictionaryValue** out_value) const {
const Value* value;
bool result = Get(path, &value);
- if (!result || !value->IsType(Type::DICTIONARY))
+ if (!result || !value->is_dict())
return false;
if (out_value)
@@ -839,7 +927,7 @@ bool DictionaryValue::GetList(StringPiece path,
const ListValue** out_value) const {
const Value* value;
bool result = Get(path, &value);
- if (!result || !value->IsType(Type::LIST))
+ if (!result || !value->is_list())
return false;
if (out_value)
@@ -857,8 +945,8 @@ bool DictionaryValue::GetList(StringPiece path, ListValue** out_value) {
bool DictionaryValue::GetWithoutPathExpansion(StringPiece key,
const Value** out_value) const {
DCHECK(IsStringUTF8(key));
- auto entry_iterator = (*dict_ptr_)->find(key.as_string());
- if (entry_iterator == (*dict_ptr_)->end())
+ auto entry_iterator = dict_.find(key);
+ if (entry_iterator == dict_.end())
return false;
if (out_value)
@@ -924,7 +1012,7 @@ bool DictionaryValue::GetDictionaryWithoutPathExpansion(
const DictionaryValue** out_value) const {
const Value* value;
bool result = GetWithoutPathExpansion(key, &value);
- if (!result || !value->IsType(Type::DICTIONARY))
+ if (!result || !value->is_dict())
return false;
if (out_value)
@@ -948,7 +1036,7 @@ bool DictionaryValue::GetListWithoutPathExpansion(
const ListValue** out_value) const {
const Value* value;
bool result = GetWithoutPathExpansion(key, &value);
- if (!result || !value->IsType(Type::LIST))
+ if (!result || !value->is_list())
return false;
if (out_value)
@@ -986,13 +1074,13 @@ bool DictionaryValue::RemoveWithoutPathExpansion(
StringPiece key,
std::unique_ptr<Value>* out_value) {
DCHECK(IsStringUTF8(key));
- auto entry_iterator = (*dict_ptr_)->find(key.as_string());
- if (entry_iterator == (*dict_ptr_)->end())
+ auto entry_iterator = dict_.find(key);
+ if (entry_iterator == dict_.end())
return false;
if (out_value)
*out_value = std::move(entry_iterator->second);
- (*dict_ptr_)->erase(entry_iterator);
+ dict_.erase(entry_iterator);
return true;
}
@@ -1005,13 +1093,13 @@ bool DictionaryValue::RemovePath(StringPiece path,
return RemoveWithoutPathExpansion(path, out_value);
StringPiece subdict_path = path.substr(0, delimiter_position);
- DictionaryValue* subdict = NULL;
+ DictionaryValue* subdict = nullptr;
if (!GetDictionary(subdict_path, &subdict))
return false;
result = subdict->RemovePath(path.substr(delimiter_position + 1),
out_value);
if (result && subdict->empty())
- RemoveWithoutPathExpansion(subdict_path, NULL);
+ RemoveWithoutPathExpansion(subdict_path, nullptr);
return result;
}
@@ -1021,7 +1109,7 @@ std::unique_ptr<DictionaryValue> DictionaryValue::DeepCopyWithoutEmptyChildren()
std::unique_ptr<DictionaryValue> copy =
CopyDictionaryWithoutEmptyChildren(*this);
if (!copy)
- copy.reset(new DictionaryValue);
+ copy = std::make_unique<DictionaryValue>();
return copy;
}
@@ -1030,7 +1118,7 @@ void DictionaryValue::MergeDictionary(const DictionaryValue* dictionary) {
for (DictionaryValue::Iterator it(*dictionary); !it.IsAtEnd(); it.Advance()) {
const Value* merge_value = &it.value();
// Check whether we have to merge dictionaries.
- if (merge_value->IsType(Value::Type::DICTIONARY)) {
+ if (merge_value->is_dict()) {
DictionaryValue* sub_dict;
if (GetDictionaryWithoutPathExpansion(it.key(), &sub_dict)) {
sub_dict->MergeDictionary(
@@ -1039,28 +1127,28 @@ void DictionaryValue::MergeDictionary(const DictionaryValue* dictionary) {
}
}
// All other cases: Make a copy and hook it up.
- SetWithoutPathExpansion(it.key(), MakeUnique<Value>(*merge_value));
+ SetKey(it.key(), merge_value->Clone());
}
}
void DictionaryValue::Swap(DictionaryValue* other) {
CHECK(other->is_dict());
- dict_ptr_->swap(*(other->dict_ptr_));
+ dict_.swap(other->dict_);
}
DictionaryValue::Iterator::Iterator(const DictionaryValue& target)
- : target_(target), it_((*target.dict_ptr_)->begin()) {}
+ : target_(target), it_(target.dict_.begin()) {}
DictionaryValue::Iterator::Iterator(const Iterator& other) = default;
-DictionaryValue::Iterator::~Iterator() {}
+DictionaryValue::Iterator::~Iterator() = default;
DictionaryValue* DictionaryValue::DeepCopy() const {
- return new DictionaryValue(*this);
+ return new DictionaryValue(dict_);
}
std::unique_ptr<DictionaryValue> DictionaryValue::CreateDeepCopy() const {
- return MakeUnique<DictionaryValue>(*this);
+ return std::make_unique<DictionaryValue>(dict_);
}
///////////////////// ListValue ////////////////////
@@ -1076,38 +1164,35 @@ std::unique_ptr<ListValue> ListValue::From(std::unique_ptr<Value> value) {
}
ListValue::ListValue() : Value(Type::LIST) {}
+ListValue::ListValue(const ListStorage& in_list) : Value(in_list) {}
+ListValue::ListValue(ListStorage&& in_list) noexcept
+ : Value(std::move(in_list)) {}
void ListValue::Clear() {
- list_->clear();
+ list_.clear();
}
-bool ListValue::Set(size_t index, Value* in_value) {
- return Set(index, WrapUnique(in_value));
+void ListValue::Reserve(size_t n) {
+ list_.reserve(n);
}
bool ListValue::Set(size_t index, std::unique_ptr<Value> in_value) {
if (!in_value)
return false;
- if (index >= list_->size()) {
- // Pad out any intermediate indexes with null settings
- while (index > list_->size())
- Append(CreateNullValue());
- Append(std::move(in_value));
- } else {
- // TODO(dcheng): remove this DCHECK once the raw pointer version is removed?
- DCHECK((*list_)[index] != in_value);
- (*list_)[index] = std::move(in_value);
- }
+ if (index >= list_.size())
+ list_.resize(index + 1);
+
+ list_[index] = std::move(*in_value);
return true;
}
bool ListValue::Get(size_t index, const Value** out_value) const {
- if (index >= list_->size())
+ if (index >= list_.size())
return false;
if (out_value)
- *out_value = (*list_)[index].get();
+ *out_value = &list_[index];
return true;
}
@@ -1158,29 +1243,11 @@ bool ListValue::GetString(size_t index, string16* out_value) const {
return value->GetAsString(out_value);
}
-bool ListValue::GetBinary(size_t index, const BinaryValue** out_value) const {
- const Value* value;
- bool result = Get(index, &value);
- if (!result || !value->IsType(Type::BINARY))
- return false;
-
- if (out_value)
- *out_value = value;
-
- return true;
-}
-
-bool ListValue::GetBinary(size_t index, BinaryValue** out_value) {
- return static_cast<const ListValue&>(*this).GetBinary(
- index,
- const_cast<const BinaryValue**>(out_value));
-}
-
bool ListValue::GetDictionary(size_t index,
const DictionaryValue** out_value) const {
const Value* value;
bool result = Get(index, &value);
- if (!result || !value->IsType(Type::DICTIONARY))
+ if (!result || !value->is_dict())
return false;
if (out_value)
@@ -1198,7 +1265,7 @@ bool ListValue::GetDictionary(size_t index, DictionaryValue** out_value) {
bool ListValue::GetList(size_t index, const ListValue** out_value) const {
const Value* value;
bool result = Get(index, &value);
- if (!result || !value->IsType(Type::LIST))
+ if (!result || !value->is_list())
return false;
if (out_value)
@@ -1214,127 +1281,111 @@ bool ListValue::GetList(size_t index, ListValue** out_value) {
}
bool ListValue::Remove(size_t index, std::unique_ptr<Value>* out_value) {
- if (index >= list_->size())
+ if (index >= list_.size())
return false;
if (out_value)
- *out_value = std::move((*list_)[index]);
+ *out_value = std::make_unique<Value>(std::move(list_[index]));
- list_->erase(list_->begin() + index);
+ list_.erase(list_.begin() + index);
return true;
}
bool ListValue::Remove(const Value& value, size_t* index) {
- for (auto it = list_->begin(); it != list_->end(); ++it) {
- if (**it == value) {
- size_t previous_index = it - list_->begin();
- list_->erase(it);
+ auto it = std::find(list_.begin(), list_.end(), value);
- if (index)
- *index = previous_index;
- return true;
- }
- }
- return false;
+ if (it == list_.end())
+ return false;
+
+ if (index)
+ *index = std::distance(list_.begin(), it);
+
+ list_.erase(it);
+ return true;
}
ListValue::iterator ListValue::Erase(iterator iter,
std::unique_ptr<Value>* out_value) {
if (out_value)
- *out_value = std::move(*ListStorage::iterator(iter));
+ *out_value = std::make_unique<Value>(std::move(*iter));
- return list_->erase(iter);
+ return list_.erase(iter);
}
void ListValue::Append(std::unique_ptr<Value> in_value) {
- list_->push_back(std::move(in_value));
-}
-
-#if !defined(OS_LINUX)
-void ListValue::Append(Value* in_value) {
- DCHECK(in_value);
- Append(WrapUnique(in_value));
+ list_.push_back(std::move(*in_value));
}
-#endif
void ListValue::AppendBoolean(bool in_value) {
- Append(MakeUnique<Value>(in_value));
+ list_.emplace_back(in_value);
}
void ListValue::AppendInteger(int in_value) {
- Append(MakeUnique<Value>(in_value));
+ list_.emplace_back(in_value);
}
void ListValue::AppendDouble(double in_value) {
- Append(MakeUnique<Value>(in_value));
+ list_.emplace_back(in_value);
}
void ListValue::AppendString(StringPiece in_value) {
- Append(MakeUnique<Value>(in_value));
+ list_.emplace_back(in_value);
}
void ListValue::AppendString(const string16& in_value) {
- Append(MakeUnique<Value>(in_value));
+ list_.emplace_back(in_value);
}
void ListValue::AppendStrings(const std::vector<std::string>& in_values) {
- for (std::vector<std::string>::const_iterator it = in_values.begin();
- it != in_values.end(); ++it) {
- AppendString(*it);
- }
+ list_.reserve(list_.size() + in_values.size());
+ for (const auto& in_value : in_values)
+ list_.emplace_back(in_value);
}
void ListValue::AppendStrings(const std::vector<string16>& in_values) {
- for (std::vector<string16>::const_iterator it = in_values.begin();
- it != in_values.end(); ++it) {
- AppendString(*it);
- }
+ list_.reserve(list_.size() + in_values.size());
+ for (const auto& in_value : in_values)
+ list_.emplace_back(in_value);
}
bool ListValue::AppendIfNotPresent(std::unique_ptr<Value> in_value) {
DCHECK(in_value);
- for (const auto& entry : *list_) {
- if (*entry == *in_value)
- return false;
- }
- list_->push_back(std::move(in_value));
+ if (ContainsValue(list_, *in_value))
+ return false;
+
+ list_.push_back(std::move(*in_value));
return true;
}
bool ListValue::Insert(size_t index, std::unique_ptr<Value> in_value) {
DCHECK(in_value);
- if (index > list_->size())
+ if (index > list_.size())
return false;
- list_->insert(list_->begin() + index, std::move(in_value));
+ list_.insert(list_.begin() + index, std::move(*in_value));
return true;
}
ListValue::const_iterator ListValue::Find(const Value& value) const {
- return std::find_if(list_->begin(), list_->end(),
- [&value](const std::unique_ptr<Value>& entry) {
- return *entry == value;
- });
+ return std::find(list_.begin(), list_.end(), value);
}
void ListValue::Swap(ListValue* other) {
CHECK(other->is_list());
- list_->swap(*(other->list_));
+ list_.swap(other->list_);
}
ListValue* ListValue::DeepCopy() const {
- return new ListValue(*this);
+ return new ListValue(list_);
}
std::unique_ptr<ListValue> ListValue::CreateDeepCopy() const {
- return MakeUnique<ListValue>(*this);
+ return std::make_unique<ListValue>(list_);
}
-ValueSerializer::~ValueSerializer() {
-}
+ValueSerializer::~ValueSerializer() = default;
-ValueDeserializer::~ValueDeserializer() {
-}
+ValueDeserializer::~ValueDeserializer() = default;
std::ostream& operator<<(std::ostream& out, const Value& value) {
std::string json;
diff --git a/base/values.h b/base/values.h
index 925152dbee..85113d99be 100644
--- a/base/values.h
+++ b/base/values.h
@@ -13,6 +13,10 @@
// numbers. Writing JSON with such types would violate the spec. If you need
// something like this, either use a double or make a string value containing
// the number you want.
+//
+// NOTE: A Value parameter that is always a Value::STRING should just be passed
+// as a std::string. Similarly for Values that are always Value::DICTIONARY
+// (should be flat_map), Value::LIST (should be std::vector), et cetera.
#ifndef BASE_VALUES_H_
#define BASE_VALUES_H_
@@ -28,28 +32,57 @@
#include <vector>
#include "base/base_export.h"
-#include "base/compiler_specific.h"
+#include "base/containers/flat_map.h"
+#include "base/containers/span.h"
#include "base/macros.h"
-#include "base/memory/manual_constructor.h"
#include "base/strings/string16.h"
#include "base/strings/string_piece.h"
+#include "base/value_iterators.h"
namespace base {
class DictionaryValue;
class ListValue;
class Value;
-using BinaryValue = Value;
// The Value class is the base class for Values. A Value can be instantiated
-// via the Create*Value() factory methods, or by directly creating instances of
-// the subclasses.
+// via passing the appropriate type or backing storage to the constructor.
//
// See the file-level comment above for more information.
+//
+// base::Value is currently in the process of being refactored. Design doc:
+// https://docs.google.com/document/d/1uDLu5uTRlCWePxQUEHc8yNQdEoE1BDISYdpggWEABnw
+//
+// Previously (which is how most code that currently exists is written), Value
+// used derived types to implement the individual data types, and base::Value
+// was just a base class to refer to them. This required everything be heap
+// allocated.
+//
+// OLD WAY:
+//
+// std::unique_ptr<base::Value> GetFoo() {
+// std::unique_ptr<DictionaryValue> dict;
+// dict->SetString("mykey", foo);
+// return dict;
+// }
+//
+// The new design makes base::Value a variant type that holds everything in
+// a union. It is now recommended to pass by value with std::move rather than
+// use heap allocated values. The DictionaryValue and ListValue subclasses
+// exist only as a compatibility shim that we're in the process of removing.
+//
+// NEW WAY:
+//
+// base::Value GetFoo() {
+// base::Value dict(base::Value::Type::DICTIONARY);
+// dict.SetKey("mykey", base::Value(foo));
+// return dict;
+// }
class BASE_EXPORT Value {
public:
- using DictStorage = std::map<std::string, std::unique_ptr<Value>>;
- using ListStorage = std::vector<std::unique_ptr<Value>>;
+ using BlobStorage = std::vector<char>;
+ using DictStorage = flat_map<std::string, std::unique_ptr<Value>>;
+ using ListStorage = std::vector<Value>;
enum class Type {
NONE = 0,
@@ -63,41 +96,49 @@ class BASE_EXPORT Value {
// Note: Do not add more types. See the file-level comment above for why.
};
- static std::unique_ptr<Value> CreateNullValue();
-
// For situations where you want to keep ownership of your buffer, this
// factory method creates a new BinaryValue by copying the contents of the
// buffer that's passed in.
- // DEPRECATED, use MakeUnique<Value>(const std::vector<char>&) instead.
+ // DEPRECATED, use std::make_unique<Value>(const BlobStorage&) instead.
// TODO(crbug.com/646113): Delete this and migrate callsites.
- static std::unique_ptr<BinaryValue> CreateWithCopiedBuffer(const char* buffer,
- size_t size);
+ static std::unique_ptr<Value> CreateWithCopiedBuffer(const char* buffer,
+ size_t size);
+
+ // Adaptors for converting from the old way to the new way and vice versa.
+ static Value FromUniquePtrValue(std::unique_ptr<Value> val);
+ static std::unique_ptr<Value> ToUniquePtrValue(Value val);
- Value(const Value& that);
Value(Value&& that) noexcept;
Value() noexcept; // A null value.
+
+ // Value's copy constructor and copy assignment operator are deleted. Use this
+ // to obtain a deep copy explicitly.
+ Value Clone() const;
+
explicit Value(Type type);
explicit Value(bool in_bool);
explicit Value(int in_int);
explicit Value(double in_double);
// Value(const char*) and Value(const char16*) are required despite
- // Value(const std::string&) and Value(const string16&) because otherwise the
+ // Value(StringPiece) and Value(StringPiece16) because otherwise the
// compiler will choose the Value(bool) constructor for these arguments.
// Value(std::string&&) allow for efficient move construction.
- // Value(StringPiece) exists due to many callsites passing StringPieces as
- // arguments.
explicit Value(const char* in_string);
- explicit Value(const std::string& in_string);
- explicit Value(std::string&& in_string) noexcept;
- explicit Value(const char16* in_string);
- explicit Value(const string16& in_string);
explicit Value(StringPiece in_string);
+ explicit Value(std::string&& in_string) noexcept;
+ explicit Value(const char16* in_string16);
+ explicit Value(StringPiece16 in_string16);
+
+ explicit Value(const BlobStorage& in_blob);
+ explicit Value(BlobStorage&& in_blob) noexcept;
+
+ explicit Value(const DictStorage& in_dict);
+ explicit Value(DictStorage&& in_dict) noexcept;
- explicit Value(const std::vector<char>& in_blob);
- explicit Value(std::vector<char>&& in_blob) noexcept;
+ explicit Value(const ListStorage& in_list);
+ explicit Value(ListStorage&& in_list) noexcept;
- Value& operator=(const Value& that);
Value& operator=(Value&& that) noexcept;
~Value();
@@ -106,15 +147,10 @@ class BASE_EXPORT Value {
static const char* GetTypeName(Type type);
// Returns the type of the value stored by the current Value object.
- // Each type will be implemented by only one subclass of Value, so it's
- // safe to use the Type to determine whether you can cast from
- // Value* to (Implementing Class)*. Also, a Value object never changes
- // its type after construction.
- Type GetType() const { return type_; } // DEPRECATED, use type().
Type type() const { return type_; }
// Returns true if the current object represents a given type.
- bool IsType(Type type) const { return type == type_; }
+ bool is_none() const { return type() == Type::NONE; }
bool is_bool() const { return type() == Type::BOOLEAN; }
bool is_int() const { return type() == Type::INTEGER; }
bool is_double() const { return type() == Type::DOUBLE; }
@@ -128,24 +164,160 @@ class BASE_EXPORT Value {
int GetInt() const;
double GetDouble() const; // Implicitly converts from int if necessary.
const std::string& GetString() const;
- const std::vector<char>& GetBlob() const;
-
- size_t GetSize() const; // DEPRECATED, use GetBlob().size() instead.
- const char* GetBuffer() const; // DEPRECATED, use GetBlob().data() instead.
+ const BlobStorage& GetBlob() const;
+
+ ListStorage& GetList();
+ const ListStorage& GetList() const;
+
+ // |FindKey| looks up |key| in the underlying dictionary. If found, it returns
+ // a pointer to the element. Otherwise it returns nullptr.
+ // returned. Callers are expected to perform a check against null before using
+ // the pointer.
+ // Note: This fatally asserts if type() is not Type::DICTIONARY.
+ //
+ // Example:
+ // auto* found = FindKey("foo");
+ Value* FindKey(StringPiece key);
+ const Value* FindKey(StringPiece key) const;
+
+ // |FindKeyOfType| is similar to |FindKey|, but it also requires the found
+ // value to have type |type|. If no type is found, or the found value is of a
+ // different type nullptr is returned.
+ // Callers are expected to perform a check against null before using the
+ // pointer.
+ // Note: This fatally asserts if type() is not Type::DICTIONARY.
+ //
+ // Example:
+ // auto* found = FindKey("foo", Type::DOUBLE);
+ Value* FindKeyOfType(StringPiece key, Type type);
+ const Value* FindKeyOfType(StringPiece key, Type type) const;
+
+ // |SetKey| looks up |key| in the underlying dictionary and sets the mapped
+ // value to |value|. If |key| could not be found, a new element is inserted.
+ // A pointer to the modified item is returned.
+ // Note: This fatally asserts if type() is not Type::DICTIONARY.
+ //
+ // Example:
+ // SetKey("foo", std::move(myvalue));
+ Value* SetKey(StringPiece key, Value value);
+ // This overload results in a performance improvement for std::string&&.
+ Value* SetKey(std::string&& key, Value value);
+ // This overload is necessary to avoid ambiguity for const char* arguments.
+ Value* SetKey(const char* key, Value value);
+
+ // This attemps to remove the value associated with |key|. In case of failure,
+ // e.g. the key does not exist, |false| is returned and the underlying
+ // dictionary is not changed. In case of success, |key| is deleted from the
+ // dictionary and the method returns |true|.
+ // Note: This fatally asserts if type() is not Type::DICTIONARY.
+ //
+ // Example:
+ // bool success = RemoveKey("foo");
+ bool RemoveKey(StringPiece key);
+
+ // Searches a hierarchy of dictionary values for a given value. If a path
+ // of dictionaries exist, returns the item at that path. If any of the path
+ // components do not exist or if any but the last path components are not
+ // dictionaries, returns nullptr.
+ //
+ // The type of the leaf Value is not checked.
+ //
+ // Implementation note: This can't return an iterator because the iterator
+ // will actually be into another Value, so it can't be compared to iterators
+ // from this one (in particular, the DictItems().end() iterator).
+ //
+ // Example:
+ // auto* found = FindPath({"foo", "bar"});
+ //
+ // std::vector<StringPiece> components = ...
+ // auto* found = FindPath(components);
+ //
+ // Note: If there is only one component in the path, use FindKey() instead.
+ Value* FindPath(std::initializer_list<StringPiece> path);
+ Value* FindPath(span<const StringPiece> path);
+ const Value* FindPath(std::initializer_list<StringPiece> path) const;
+ const Value* FindPath(span<const StringPiece> path) const;
+
+ // Like FindPath() but will only return the value if the leaf Value type
+ // matches the given type. Will return nullptr otherwise.
+ //
+ // Note: If there is only one component in the path, use FindKeyOfType()
+ // instead.
+ Value* FindPathOfType(std::initializer_list<StringPiece> path, Type type);
+ Value* FindPathOfType(span<const StringPiece> path, Type type);
+ const Value* FindPathOfType(std::initializer_list<StringPiece> path,
+ Type type) const;
+ const Value* FindPathOfType(span<const StringPiece> path, Type type) const;
+
+ // Sets the given path, expanding and creating dictionary keys as necessary.
+ //
+ // If the current value is not a dictionary, the function returns nullptr. If
+ // path components do not exist, they will be created. If any but the last
+ // components matches a value that is not a dictionary, the function will fail
+ // (it will not overwrite the value) and return nullptr. The last path
+ // component will be unconditionally overwritten if it exists, and created if
+ // it doesn't.
+ //
+ // Example:
+ // value.SetPath({"foo", "bar"}, std::move(myvalue));
+ //
+ // std::vector<StringPiece> components = ...
+ // value.SetPath(components, std::move(myvalue));
+ //
+ // Note: If there is only one component in the path, use SetKey() instead.
+ Value* SetPath(std::initializer_list<StringPiece> path, Value value);
+ Value* SetPath(span<const StringPiece> path, Value value);
+
+ // Tries to remove a Value at the given path.
+ //
+ // If the current value is not a dictionary or any path components does not
+ // exist, this operation fails, leaves underlying Values untouched and returns
+ // |false|. In case intermediate dictionaries become empty as a result of this
+ // path removal, they will be removed as well.
+ //
+ // Example:
+ // bool success = value.RemovePath({"foo", "bar"});
+ //
+ // std::vector<StringPiece> components = ...
+ // bool success = value.RemovePath(components);
+ //
+ // Note: If there is only one component in the path, use RemoveKey() instead.
+ bool RemovePath(std::initializer_list<StringPiece> path);
+ bool RemovePath(span<const StringPiece> path);
+
+ using dict_iterator_proxy = detail::dict_iterator_proxy;
+ using const_dict_iterator_proxy = detail::const_dict_iterator_proxy;
+
+ // |DictItems| returns a proxy object that exposes iterators to the underlying
+ // dictionary. These are intended for iteration over all items in the
+ // dictionary and are compatible with for-each loops and standard library
+ // algorithms.
+ // Note: This fatally asserts if type() is not Type::DICTIONARY.
+ dict_iterator_proxy DictItems();
+ const_dict_iterator_proxy DictItems() const;
+
+ // Returns the size of the dictionary, and if the dictionary is empty.
+ // Note: This fatally asserts if type() is not Type::DICTIONARY.
+ size_t DictSize() const;
+ bool DictEmpty() const;
// These methods allow the convenient retrieval of the contents of the Value.
// If the current object can be converted into the given type, the value is
// returned through the |out_value| parameter and true is returned;
// otherwise, false is returned and |out_value| is unchanged.
+ // DEPRECATED, use GetBool() instead.
bool GetAsBoolean(bool* out_value) const;
+ // DEPRECATED, use GetInt() instead.
bool GetAsInteger(int* out_value) const;
+ // DEPRECATED, use GetDouble() instead.
bool GetAsDouble(double* out_value) const;
+ // DEPRECATED, use GetString() instead.
bool GetAsString(std::string* out_value) const;
bool GetAsString(string16* out_value) const;
bool GetAsString(const Value** out_value) const;
bool GetAsString(StringPiece* out_value) const;
- bool GetAsBinary(const BinaryValue** out_value) const;
// ListValue::From is the equivalent for std::unique_ptr conversions.
+ // DEPRECATED, use GetList() instead.
bool GetAsList(ListValue** out_value);
bool GetAsList(const ListValue** out_value) const;
// DictionaryValue::From is the equivalent for std::unique_ptr conversions.
@@ -157,10 +329,11 @@ class BASE_EXPORT Value {
// to the copy. The caller gets ownership of the copy, of course.
// Subclasses return their own type directly in their overrides;
// this works because C++ supports covariant return types.
- // DEPRECATED, use Value's copy constructor instead.
+ // DEPRECATED, use Value::Clone() instead.
// TODO(crbug.com/646113): Delete this and migrate callsites.
Value* DeepCopy() const;
- // Preferred version of DeepCopy. TODO(estade): remove the above.
+ // DEPRECATED, use Value::Clone() instead.
+ // TODO(crbug.com/646113): Delete this and migrate callsites.
std::unique_ptr<Value> CreateDeepCopy() const;
// Comparison operators so that Values can easily be used with standard
@@ -177,11 +350,9 @@ class BASE_EXPORT Value {
// TODO(crbug.com/646113): Delete this and migrate callsites.
bool Equals(const Value* other) const;
- // Compares if two Value objects have equal contents. Can handle NULLs.
- // NULLs are considered equal but different from Value::CreateNullValue().
- // DEPRECATED, use operator==(const Value& lhs, const Value& rhs) instead.
- // TODO(crbug.com/646113): Delete this and migrate callsites.
- static bool Equals(const Value* a, const Value* b);
+ // Estimates dynamic memory usage.
+ // See base/trace_event/memory_usage_estimator.h for more info.
+ // size_t EstimateMemoryUsage() const;
protected:
// TODO(crbug.com/646113): Make these private once DictionaryValue and
@@ -192,21 +363,17 @@ class BASE_EXPORT Value {
bool bool_value_;
int int_value_;
double double_value_;
- ManualConstructor<std::string> string_value_;
- ManualConstructor<std::vector<char>> binary_value_;
- // For current gcc and clang sizeof(DictStorage) = 48, which would result
- // in sizeof(Value) = 56 if DictStorage was stack allocated. Allocating it
- // on the heap results in sizeof(Value) = 40 for all of gcc, clang and MSVC.
- ManualConstructor<std::unique_ptr<DictStorage>> dict_ptr_;
- ManualConstructor<ListStorage> list_;
+ std::string string_value_;
+ BlobStorage binary_value_;
+ DictStorage dict_;
+ ListStorage list_;
};
private:
- void InternalCopyFundamentalValue(const Value& that);
- void InternalCopyConstructFrom(const Value& that);
void InternalMoveConstructFrom(Value&& that);
- void InternalCopyAssignFromSameType(const Value& that);
void InternalCleanup();
+
+ DISALLOW_COPY_AND_ASSIGN(Value);
};
// DictionaryValue provides a key-value dictionary with (optional) "path"
@@ -214,19 +381,25 @@ class BASE_EXPORT Value {
// are |std::string|s and should be UTF-8 encoded.
class BASE_EXPORT DictionaryValue : public Value {
public:
+ using const_iterator = DictStorage::const_iterator;
+ using iterator = DictStorage::iterator;
+
// Returns |value| if it is a dictionary, nullptr otherwise.
static std::unique_ptr<DictionaryValue> From(std::unique_ptr<Value> value);
DictionaryValue();
+ explicit DictionaryValue(const DictStorage& in_dict);
+ explicit DictionaryValue(DictStorage&& in_dict) noexcept;
// Returns true if the current dictionary has a value for the given key.
+ // DEPRECATED, use Value::FindKey(key) instead.
bool HasKey(StringPiece key) const;
// Returns the number of Values in this dictionary.
- size_t size() const { return (*dict_ptr_)->size(); }
+ size_t size() const { return dict_.size(); }
// Returns whether the dictionary is empty.
- bool empty() const { return (*dict_ptr_)->empty(); }
+ bool empty() const { return dict_.empty(); }
// Clears any current contents of this dictionary.
void Clear();
@@ -238,32 +411,33 @@ class BASE_EXPORT DictionaryValue : public Value {
// If the key at any step of the way doesn't exist, or exists but isn't
// a DictionaryValue, a new DictionaryValue will be created and attached
// to the path in that location. |in_value| must be non-null.
- void Set(StringPiece path, std::unique_ptr<Value> in_value);
- // Deprecated version of the above. TODO(estade): remove.
- void Set(StringPiece path, Value* in_value);
+ // Returns a pointer to the inserted value.
+ // DEPRECATED, use Value::SetPath(path, value) instead.
+ Value* Set(StringPiece path, std::unique_ptr<Value> in_value);
// Convenience forms of Set(). These methods will replace any existing
// value at that path, even if it has a different type.
- void SetBoolean(StringPiece path, bool in_value);
- void SetInteger(StringPiece path, int in_value);
- void SetDouble(StringPiece path, double in_value);
- void SetString(StringPiece path, StringPiece in_value);
- void SetString(StringPiece path, const string16& in_value);
+ // DEPRECATED, use Value::SetPath(path, Value(bool)) instead.
+ Value* SetBoolean(StringPiece path, bool in_value);
+ // DEPRECATED, use Value::SetPath(path, Value(int)) instead.
+ Value* SetInteger(StringPiece path, int in_value);
+ // DEPRECATED, use Value::SetPath(path, Value(double)) instead.
+ Value* SetDouble(StringPiece path, double in_value);
+ // DEPRECATED, use Value::SetPath(path, Value(StringPiece)) instead.
+ Value* SetString(StringPiece path, StringPiece in_value);
+ // DEPRECATED, use Value::SetPath(path, Value(const string& 16)) instead.
+ Value* SetString(StringPiece path, const string16& in_value);
+ // DEPRECATED, use Value::SetPath(path, Value(Type::DICTIONARY)) instead.
+ DictionaryValue* SetDictionary(StringPiece path,
+ std::unique_ptr<DictionaryValue> in_value);
+ // DEPRECATED, use Value::SetPath(path, Value(Type::LIST)) instead.
+ ListValue* SetList(StringPiece path, std::unique_ptr<ListValue> in_value);
// Like Set(), but without special treatment of '.'. This allows e.g. URLs to
// be used as paths.
- void SetWithoutPathExpansion(StringPiece key,
- std::unique_ptr<Value> in_value);
- // Deprecated version of the above. TODO(estade): remove.
- void SetWithoutPathExpansion(StringPiece key, Value* in_value);
-
- // Convenience forms of SetWithoutPathExpansion().
- void SetBooleanWithoutPathExpansion(StringPiece path, bool in_value);
- void SetIntegerWithoutPathExpansion(StringPiece path, int in_value);
- void SetDoubleWithoutPathExpansion(StringPiece path, double in_value);
- void SetStringWithoutPathExpansion(StringPiece path, StringPiece in_value);
- void SetStringWithoutPathExpansion(StringPiece path,
- const string16& in_value);
+ // DEPRECATED, use Value::SetKey(key, value) instead.
+ Value* SetWithoutPathExpansion(StringPiece key,
+ std::unique_ptr<Value> in_value);
// Gets the Value associated with the given path starting from this object.
// A path has the form "<key>" or "<key>.<key>.[...]", where "." indexes
@@ -273,47 +447,72 @@ class BASE_EXPORT DictionaryValue : public Value {
// Otherwise, it will return false and |out_value| will be untouched.
// Note that the dictionary always owns the value that's returned.
// |out_value| is optional and will only be set if non-NULL.
+ // DEPRECATED, use Value::FindPath(path) instead.
bool Get(StringPiece path, const Value** out_value) const;
+ // DEPRECATED, use Value::FindPath(path) instead.
bool Get(StringPiece path, Value** out_value);
// These are convenience forms of Get(). The value will be retrieved
// and the return value will be true if the path is valid and the value at
// the end of the path can be returned in the form specified.
// |out_value| is optional and will only be set if non-NULL.
+ // DEPRECATED, use Value::FindPath(path) and Value::GetBool() instead.
bool GetBoolean(StringPiece path, bool* out_value) const;
+ // DEPRECATED, use Value::FindPath(path) and Value::GetInt() instead.
bool GetInteger(StringPiece path, int* out_value) const;
// Values of both type Type::INTEGER and Type::DOUBLE can be obtained as
// doubles.
+ // DEPRECATED, use Value::FindPath(path) and Value::GetDouble() instead.
bool GetDouble(StringPiece path, double* out_value) const;
+ // DEPRECATED, use Value::FindPath(path) and Value::GetString() instead.
bool GetString(StringPiece path, std::string* out_value) const;
+ // DEPRECATED, use Value::FindPath(path) and Value::GetString() instead.
bool GetString(StringPiece path, string16* out_value) const;
+ // DEPRECATED, use Value::FindPath(path) and Value::GetString() instead.
bool GetStringASCII(StringPiece path, std::string* out_value) const;
- bool GetBinary(StringPiece path, const BinaryValue** out_value) const;
- bool GetBinary(StringPiece path, BinaryValue** out_value);
+ // DEPRECATED, use Value::FindPath(path) and Value::GetBlob() instead.
+ bool GetBinary(StringPiece path, const Value** out_value) const;
+ // DEPRECATED, use Value::FindPath(path) and Value::GetBlob() instead.
+ bool GetBinary(StringPiece path, Value** out_value);
+ // DEPRECATED, use Value::FindPath(path) and Value's Dictionary API instead.
bool GetDictionary(StringPiece path,
const DictionaryValue** out_value) const;
+ // DEPRECATED, use Value::FindPath(path) and Value's Dictionary API instead.
bool GetDictionary(StringPiece path, DictionaryValue** out_value);
+ // DEPRECATED, use Value::FindPath(path) and Value::GetList() instead.
bool GetList(StringPiece path, const ListValue** out_value) const;
+ // DEPRECATED, use Value::FindPath(path) and Value::GetList() instead.
bool GetList(StringPiece path, ListValue** out_value);
// Like Get(), but without special treatment of '.'. This allows e.g. URLs to
// be used as paths.
+ // DEPRECATED, use Value::FindKey(key) instead.
bool GetWithoutPathExpansion(StringPiece key, const Value** out_value) const;
+ // DEPRECATED, use Value::FindKey(key) instead.
bool GetWithoutPathExpansion(StringPiece key, Value** out_value);
+ // DEPRECATED, use Value::FindKey(key) and Value::GetBool() instead.
bool GetBooleanWithoutPathExpansion(StringPiece key, bool* out_value) const;
+ // DEPRECATED, use Value::FindKey(key) and Value::GetInt() instead.
bool GetIntegerWithoutPathExpansion(StringPiece key, int* out_value) const;
+ // DEPRECATED, use Value::FindKey(key) and Value::GetDouble() instead.
bool GetDoubleWithoutPathExpansion(StringPiece key, double* out_value) const;
+ // DEPRECATED, use Value::FindKey(key) and Value::GetString() instead.
bool GetStringWithoutPathExpansion(StringPiece key,
std::string* out_value) const;
+ // DEPRECATED, use Value::FindKey(key) and Value::GetString() instead.
bool GetStringWithoutPathExpansion(StringPiece key,
string16* out_value) const;
+ // DEPRECATED, use Value::FindKey(key) and Value's Dictionary API instead.
bool GetDictionaryWithoutPathExpansion(
StringPiece key,
const DictionaryValue** out_value) const;
+ // DEPRECATED, use Value::FindKey(key) and Value's Dictionary API instead.
bool GetDictionaryWithoutPathExpansion(StringPiece key,
DictionaryValue** out_value);
+ // DEPRECATED, use Value::FindKey(key) and Value::GetList() instead.
bool GetListWithoutPathExpansion(StringPiece key,
const ListValue** out_value) const;
+ // DEPRECATED, use Value::FindKey(key) and Value::GetList() instead.
bool GetListWithoutPathExpansion(StringPiece key, ListValue** out_value);
// Removes the Value with the specified path from this dictionary (or one
@@ -322,17 +521,22 @@ class BASE_EXPORT DictionaryValue : public Value {
// |out_value|. If |out_value| is NULL, the removed value will be deleted.
// This method returns true if |path| is a valid path; otherwise it will
// return false and the DictionaryValue object will be unchanged.
+ // DEPRECATED, use Value::RemovePath(path) instead.
bool Remove(StringPiece path, std::unique_ptr<Value>* out_value);
// Like Remove(), but without special treatment of '.'. This allows e.g. URLs
// to be used as paths.
+ // DEPRECATED, use Value::RemoveKey(key) instead.
bool RemoveWithoutPathExpansion(StringPiece key,
std::unique_ptr<Value>* out_value);
// Removes a path, clearing out all dictionaries on |path| that remain empty
// after removing the value at |path|.
+ // DEPRECATED, use Value::RemovePath(path) instead.
bool RemovePath(StringPiece path, std::unique_ptr<Value>* out_value);
+ using Value::RemovePath; // DictionaryValue::RemovePath shadows otherwise.
+
// Makes a copy of |this| but doesn't include empty dictionaries and lists in
// the copy. This never returns NULL, even if |this| itself is empty.
std::unique_ptr<DictionaryValue> DeepCopyWithoutEmptyChildren() const;
@@ -349,13 +553,14 @@ class BASE_EXPORT DictionaryValue : public Value {
// This class provides an iterator over both keys and values in the
// dictionary. It can't be used to modify the dictionary.
+ // DEPRECATED, use Value::DictItems() instead.
class BASE_EXPORT Iterator {
public:
explicit Iterator(const DictionaryValue& target);
Iterator(const Iterator& other);
~Iterator();
- bool IsAtEnd() const { return it_ == (*target_.dict_ptr_)->end(); }
+ bool IsAtEnd() const { return it_ == target_.dict_.end(); }
void Advance() { ++it_; }
const std::string& key() const { return it_->first; }
@@ -366,10 +571,20 @@ class BASE_EXPORT DictionaryValue : public Value {
DictStorage::const_iterator it_;
};
- // DEPRECATED, use DictionaryValue's copy constructor instead.
+ // Iteration.
+ // DEPRECATED, use Value::DictItems() instead.
+ iterator begin() { return dict_.begin(); }
+ iterator end() { return dict_.end(); }
+
+ // DEPRECATED, use Value::DictItems() instead.
+ const_iterator begin() const { return dict_.begin(); }
+ const_iterator end() const { return dict_.end(); }
+
+ // DEPRECATED, use Value::Clone() instead.
// TODO(crbug.com/646113): Delete this and migrate callsites.
DictionaryValue* DeepCopy() const;
- // Preferred version of DeepCopy. TODO(estade): remove the above.
+ // DEPRECATED, use Value::Clone() instead.
+ // TODO(crbug.com/646113): Delete this and migrate callsites.
std::unique_ptr<DictionaryValue> CreateDeepCopy() const;
};
@@ -383,29 +598,38 @@ class BASE_EXPORT ListValue : public Value {
static std::unique_ptr<ListValue> From(std::unique_ptr<Value> value);
ListValue();
+ explicit ListValue(const ListStorage& in_list);
+ explicit ListValue(ListStorage&& in_list) noexcept;
// Clears the contents of this ListValue
+ // DEPRECATED, use GetList()::clear() instead.
void Clear();
// Returns the number of Values in this list.
- size_t GetSize() const { return list_->size(); }
+ // DEPRECATED, use GetList()::size() instead.
+ size_t GetSize() const { return list_.size(); }
// Returns whether the list is empty.
- bool empty() const { return list_->empty(); }
+ // DEPRECATED, use GetList()::empty() instead.
+ bool empty() const { return list_.empty(); }
+
+ // Reserves storage for at least |n| values.
+ // DEPRECATED, use GetList()::reserve() instead.
+ void Reserve(size_t n);
// Sets the list item at the given index to be the Value specified by
// the value given. If the index beyond the current end of the list, null
// Values will be used to pad out the list.
// Returns true if successful, or false if the index was negative or
// the value is a null pointer.
- bool Set(size_t index, Value* in_value);
- // Preferred version of the above. TODO(estade): remove the above.
+ // DEPRECATED, use GetList()::operator[] instead.
bool Set(size_t index, std::unique_ptr<Value> in_value);
// Gets the Value at the given index. Modifies |out_value| (and returns true)
// only if the index falls within the current list range.
// Note that the list always owns the Value passed out via |out_value|.
// |out_value| is optional and will only be set if non-NULL.
+ // DEPRECATED, use GetList()::operator[] instead.
bool Get(size_t index, const Value** out_value) const;
bool Get(size_t index, Value** out_value);
@@ -413,17 +637,23 @@ class BASE_EXPORT ListValue : public Value {
// only if the index is valid and the Value at that index can be returned
// in the specified form.
// |out_value| is optional and will only be set if non-NULL.
+ // DEPRECATED, use GetList()::operator[]::GetBool() instead.
bool GetBoolean(size_t index, bool* out_value) const;
+ // DEPRECATED, use GetList()::operator[]::GetInt() instead.
bool GetInteger(size_t index, int* out_value) const;
// Values of both type Type::INTEGER and Type::DOUBLE can be obtained as
// doubles.
+ // DEPRECATED, use GetList()::operator[]::GetDouble() instead.
bool GetDouble(size_t index, double* out_value) const;
+ // DEPRECATED, use GetList()::operator[]::GetString() instead.
bool GetString(size_t index, std::string* out_value) const;
bool GetString(size_t index, string16* out_value) const;
- bool GetBinary(size_t index, const BinaryValue** out_value) const;
- bool GetBinary(size_t index, BinaryValue** out_value);
+
bool GetDictionary(size_t index, const DictionaryValue** out_value) const;
bool GetDictionary(size_t index, DictionaryValue** out_value);
+
+ using Value::GetList;
+ // DEPRECATED, use GetList()::operator[]::GetList() instead.
bool GetList(size_t index, const ListValue** out_value) const;
bool GetList(size_t index, ListValue** out_value);
@@ -432,62 +662,73 @@ class BASE_EXPORT ListValue : public Value {
// passed out via |out_value|. If |out_value| is NULL, the removed value will
// be deleted. This method returns true if |index| is valid; otherwise
// it will return false and the ListValue object will be unchanged.
+ // DEPRECATED, use GetList()::erase() instead.
bool Remove(size_t index, std::unique_ptr<Value>* out_value);
// Removes the first instance of |value| found in the list, if any, and
// deletes it. |index| is the location where |value| was found. Returns false
// if not found.
+ // DEPRECATED, use GetList()::erase() instead.
bool Remove(const Value& value, size_t* index);
// Removes the element at |iter|. If |out_value| is NULL, the value will be
// deleted, otherwise ownership of the value is passed back to the caller.
// Returns an iterator pointing to the location of the element that
// followed the erased element.
+ // DEPRECATED, use GetList()::erase() instead.
iterator Erase(iterator iter, std::unique_ptr<Value>* out_value);
// Appends a Value to the end of the list.
+ // DEPRECATED, use GetList()::push_back() instead.
void Append(std::unique_ptr<Value> in_value);
-#if !defined(OS_LINUX)
- // Deprecated version of the above. TODO(estade): remove.
- void Append(Value* in_value);
-#endif
// Convenience forms of Append.
+ // DEPRECATED, use GetList()::emplace_back() instead.
void AppendBoolean(bool in_value);
void AppendInteger(int in_value);
void AppendDouble(double in_value);
void AppendString(StringPiece in_value);
void AppendString(const string16& in_value);
+ // DEPRECATED, use GetList()::emplace_back() in a loop instead.
void AppendStrings(const std::vector<std::string>& in_values);
void AppendStrings(const std::vector<string16>& in_values);
// Appends a Value if it's not already present. Returns true if successful,
// or false if the value was already
+ // DEPRECATED, use std::find() with GetList()::push_back() instead.
bool AppendIfNotPresent(std::unique_ptr<Value> in_value);
// Insert a Value at index.
// Returns true if successful, or false if the index was out of range.
+ // DEPRECATED, use GetList()::insert() instead.
bool Insert(size_t index, std::unique_ptr<Value> in_value);
// Searches for the first instance of |value| in the list using the Equals
// method of the Value type.
// Returns a const_iterator to the found item or to end() if none exists.
+ // DEPRECATED, use std::find() instead.
const_iterator Find(const Value& value) const;
// Swaps contents with the |other| list.
+ // DEPRECATED, use GetList()::swap() instead.
void Swap(ListValue* other);
// Iteration.
- iterator begin() { return list_->begin(); }
- iterator end() { return list_->end(); }
+ // DEPRECATED, use GetList()::begin() instead.
+ iterator begin() { return list_.begin(); }
+ // DEPRECATED, use GetList()::end() instead.
+ iterator end() { return list_.end(); }
- const_iterator begin() const { return list_->begin(); }
- const_iterator end() const { return list_->end(); }
+ // DEPRECATED, use GetList()::begin() instead.
+ const_iterator begin() const { return list_.begin(); }
+ // DEPRECATED, use GetList()::end() instead.
+ const_iterator end() const { return list_.end(); }
- // DEPRECATED, use ListValue's copy constructor instead.
+ // DEPRECATED, use Value::Clone() instead.
// TODO(crbug.com/646113): Delete this and migrate callsites.
ListValue* DeepCopy() const;
- // Preferred version of DeepCopy. TODO(estade): remove DeepCopy.
+ // DEPRECATED, use Value::Clone() instead.
+ // TODO(crbug.com/646113): Delete this and migrate callsites.
std::unique_ptr<ListValue> CreateDeepCopy() const;
};
diff --git a/base/values_unittest.cc b/base/values_unittest.cc
index 6c1f017095..b8efac7b15 100644
--- a/base/values_unittest.cc
+++ b/base/values_unittest.cc
@@ -6,6 +6,7 @@
#include <stddef.h>
+#include <functional>
#include <limits>
#include <memory>
#include <string>
@@ -13,9 +14,12 @@
#include <utility>
#include <vector>
+#include "base/containers/adapters.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string16.h"
+#include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
@@ -28,10 +32,16 @@ TEST(ValuesTest, TestNothrow) {
static_assert(std::is_nothrow_constructible<Value, std::string&&>::value,
"IsNothrowMoveConstructibleFromString");
static_assert(
- std::is_nothrow_constructible<Value, std::vector<char>&&>::value,
+ std::is_nothrow_constructible<Value, Value::BlobStorage&&>::value,
"IsNothrowMoveConstructibleFromBlob");
+ static_assert(
+ std::is_nothrow_constructible<Value, Value::ListStorage&&>::value,
+ "IsNothrowMoveConstructibleFromList");
static_assert(std::is_nothrow_move_assignable<Value>::value,
"IsNothrowMoveAssignable");
+ static_assert(
+ std::is_nothrow_constructible<ListValue, Value::ListStorage&&>::value,
+ "ListIsNothrowMoveConstructibleFromList");
}
// Group of tests for the value constructors.
@@ -64,14 +74,14 @@ TEST(ValuesTest, ConstructStringFromConstCharPtr) {
EXPECT_EQ("foobar", value.GetString());
}
-TEST(ValuesTest, ConstructStringFromStdStringConstRef) {
+TEST(ValuesTest, ConstructStringFromStringPiece) {
std::string str = "foobar";
- Value value(str);
+ Value value{StringPiece(str)};
EXPECT_EQ(Value::Type::STRING, value.type());
EXPECT_EQ("foobar", value.GetString());
}
-TEST(ValuesTest, ConstructStringFromStdStringRefRef) {
+TEST(ValuesTest, ConstructStringFromStdStringRRef) {
std::string str = "foobar";
Value value(std::move(str));
EXPECT_EQ(Value::Type::STRING, value.type());
@@ -85,24 +95,18 @@ TEST(ValuesTest, ConstructStringFromConstChar16Ptr) {
EXPECT_EQ("foobar", value.GetString());
}
-TEST(ValuesTest, ConstructStringFromString16) {
+TEST(ValuesTest, ConstructStringFromStringPiece16) {
string16 str = ASCIIToUTF16("foobar");
- Value value(str);
- EXPECT_EQ(Value::Type::STRING, value.type());
- EXPECT_EQ("foobar", value.GetString());
-}
-
-TEST(ValuesTest, ConstructStringFromStringPiece) {
- StringPiece str = "foobar";
- Value value(str);
+ Value value{StringPiece16(str)};
EXPECT_EQ(Value::Type::STRING, value.type());
EXPECT_EQ("foobar", value.GetString());
}
TEST(ValuesTest, ConstructBinary) {
- BinaryValue value(std::vector<char>({0xF, 0x0, 0x0, 0xB, 0xA, 0x2}));
+ Value value(Value::BlobStorage({0xF, 0x0, 0x0, 0xB, 0xA, 0x2}));
EXPECT_EQ(Value::Type::BINARY, value.type());
- EXPECT_EQ(std::vector<char>({0xF, 0x0, 0x0, 0xB, 0xA, 0x2}), value.GetBlob());
+ EXPECT_EQ(Value::BlobStorage({0xF, 0x0, 0x0, 0xB, 0xA, 0x2}),
+ value.GetBlob());
}
TEST(ValuesTest, ConstructDict) {
@@ -110,130 +114,152 @@ TEST(ValuesTest, ConstructDict) {
EXPECT_EQ(Value::Type::DICTIONARY, value.type());
}
+TEST(ValuesTest, ConstructDictFromStorage) {
+ Value::DictStorage storage;
+ storage.emplace("foo", std::make_unique<Value>("bar"));
+ {
+ DictionaryValue value(storage);
+ EXPECT_EQ(Value::Type::DICTIONARY, value.type());
+ EXPECT_EQ(Value::Type::STRING, value.FindKey("foo")->type());
+ EXPECT_EQ("bar", value.FindKey("foo")->GetString());
+ }
+
+ *storage["foo"] = base::Value("baz");
+ {
+ DictionaryValue value(std::move(storage));
+ EXPECT_EQ(Value::Type::DICTIONARY, value.type());
+ EXPECT_EQ(Value::Type::STRING, value.FindKey("foo")->type());
+ EXPECT_EQ("baz", value.FindKey("foo")->GetString());
+ }
+}
+
TEST(ValuesTest, ConstructList) {
ListValue value;
EXPECT_EQ(Value::Type::LIST, value.type());
}
+TEST(ValuesTest, ConstructListFromStorage) {
+ Value::ListStorage storage;
+ storage.emplace_back("foo");
+ {
+ ListValue value(storage);
+ EXPECT_EQ(Value::Type::LIST, value.type());
+ EXPECT_EQ(1u, value.GetList().size());
+ EXPECT_EQ(Value::Type::STRING, value.GetList()[0].type());
+ EXPECT_EQ("foo", value.GetList()[0].GetString());
+ }
+
+ storage.back() = base::Value("bar");
+ {
+ ListValue value(std::move(storage));
+ EXPECT_EQ(Value::Type::LIST, value.type());
+ EXPECT_EQ(1u, value.GetList().size());
+ EXPECT_EQ(Value::Type::STRING, value.GetList()[0].type());
+ EXPECT_EQ("bar", value.GetList()[0].GetString());
+ }
+}
+
// Group of tests for the copy constructors and copy-assigmnent. For equality
// checks comparisons of the interesting fields are done instead of relying on
// Equals being correct.
TEST(ValuesTest, CopyBool) {
Value true_value(true);
- Value copied_true_value(true_value);
+ Value copied_true_value(true_value.Clone());
EXPECT_EQ(true_value.type(), copied_true_value.type());
EXPECT_EQ(true_value.GetBool(), copied_true_value.GetBool());
Value false_value(false);
- Value copied_false_value(false_value);
+ Value copied_false_value(false_value.Clone());
EXPECT_EQ(false_value.type(), copied_false_value.type());
EXPECT_EQ(false_value.GetBool(), copied_false_value.GetBool());
Value blank;
- blank = true_value;
+ blank = true_value.Clone();
EXPECT_EQ(true_value.type(), blank.type());
EXPECT_EQ(true_value.GetBool(), blank.GetBool());
- blank = false_value;
+ blank = false_value.Clone();
EXPECT_EQ(false_value.type(), blank.type());
EXPECT_EQ(false_value.GetBool(), blank.GetBool());
}
TEST(ValuesTest, CopyInt) {
Value value(74);
- Value copied_value(value);
+ Value copied_value(value.Clone());
EXPECT_EQ(value.type(), copied_value.type());
EXPECT_EQ(value.GetInt(), copied_value.GetInt());
Value blank;
- blank = value;
+ blank = value.Clone();
EXPECT_EQ(value.type(), blank.type());
EXPECT_EQ(value.GetInt(), blank.GetInt());
}
TEST(ValuesTest, CopyDouble) {
Value value(74.896);
- Value copied_value(value);
+ Value copied_value(value.Clone());
EXPECT_EQ(value.type(), copied_value.type());
EXPECT_EQ(value.GetDouble(), copied_value.GetDouble());
Value blank;
- blank = value;
+ blank = value.Clone();
EXPECT_EQ(value.type(), blank.type());
EXPECT_EQ(value.GetDouble(), blank.GetDouble());
}
TEST(ValuesTest, CopyString) {
Value value("foobar");
- Value copied_value(value);
+ Value copied_value(value.Clone());
EXPECT_EQ(value.type(), copied_value.type());
EXPECT_EQ(value.GetString(), copied_value.GetString());
Value blank;
- blank = value;
+ blank = value.Clone();
EXPECT_EQ(value.type(), blank.type());
EXPECT_EQ(value.GetString(), blank.GetString());
}
TEST(ValuesTest, CopyBinary) {
- BinaryValue value(std::vector<char>({0xF, 0x0, 0x0, 0xB, 0xA, 0x2}));
- BinaryValue copied_value(value);
+ Value value(Value::BlobStorage({0xF, 0x0, 0x0, 0xB, 0xA, 0x2}));
+ Value copied_value(value.Clone());
EXPECT_EQ(value.type(), copied_value.type());
EXPECT_EQ(value.GetBlob(), copied_value.GetBlob());
Value blank;
- blank = value;
+ blank = value.Clone();
EXPECT_EQ(value.type(), blank.type());
EXPECT_EQ(value.GetBlob(), blank.GetBlob());
}
TEST(ValuesTest, CopyDictionary) {
- // TODO(crbug.com/646113): Clean this up once DictionaryValue switched to
- // value semantics.
- int copy;
- DictionaryValue value;
- value.SetInteger("Int", 123);
-
- DictionaryValue copied_value(value);
- copied_value.GetInteger("Int", &copy);
-
- EXPECT_EQ(value.type(), copied_value.type());
- EXPECT_EQ(123, copy);
+ Value::DictStorage storage;
+ storage.emplace("Int", std::make_unique<Value>(123));
+ Value value(std::move(storage));
- auto blank = MakeUnique<Value>();
+ Value copied_value(value.Clone());
+ EXPECT_EQ(value, copied_value);
- *blank = value;
- EXPECT_EQ(Value::Type::DICTIONARY, blank->type());
-
- static_cast<DictionaryValue*>(blank.get())->GetInteger("Int", &copy);
- EXPECT_EQ(123, copy);
+ Value blank;
+ blank = value.Clone();
+ EXPECT_EQ(value, blank);
}
TEST(ValuesTest, CopyList) {
- // TODO(crbug.com/646113): Clean this up once ListValue switched to
- // value semantics.
- int copy;
- ListValue value;
- value.AppendInteger(123);
+ Value::ListStorage storage;
+ storage.emplace_back(123);
+ Value value(std::move(storage));
- ListValue copied_value(value);
- copied_value.GetInteger(0, &copy);
-
- EXPECT_EQ(value.type(), copied_value.type());
- EXPECT_EQ(123, copy);
+ Value copied_value(value.Clone());
+ EXPECT_EQ(value, copied_value);
- auto blank = MakeUnique<Value>();
-
- *blank = value;
- EXPECT_EQ(Value::Type::LIST, blank->type());
-
- static_cast<ListValue*>(blank.get())->GetInteger(0, &copy);
- EXPECT_EQ(123, copy);
+ Value blank;
+ blank = value.Clone();
+ EXPECT_EQ(value, blank);
}
// Group of tests for the move constructors and move-assigmnent.
@@ -299,55 +325,368 @@ TEST(ValuesTest, MoveString) {
}
TEST(ValuesTest, MoveBinary) {
- const std::vector<char> buffer = {0xF, 0x0, 0x0, 0xB, 0xA, 0x2};
- BinaryValue value(buffer);
- BinaryValue moved_value(std::move(value));
+ const Value::BlobStorage buffer = {0xF, 0x0, 0x0, 0xB, 0xA, 0x2};
+ Value value(buffer);
+ Value moved_value(std::move(value));
EXPECT_EQ(Value::Type::BINARY, moved_value.type());
EXPECT_EQ(buffer, moved_value.GetBlob());
Value blank;
- blank = BinaryValue(buffer);
+ blank = Value(buffer);
EXPECT_EQ(Value::Type::BINARY, blank.type());
EXPECT_EQ(buffer, blank.GetBlob());
}
-TEST(ValuesTest, MoveDictionary) {
- // TODO(crbug.com/646113): Clean this up once DictionaryValue switched to
- // value semantics.
- int move;
- DictionaryValue value;
- value.SetInteger("Int", 123);
-
- DictionaryValue moved_value(std::move(value));
- moved_value.GetInteger("Int", &move);
+TEST(ValuesTest, MoveConstructDictionary) {
+ Value::DictStorage storage;
+ storage.emplace("Int", std::make_unique<Value>(123));
+ Value value(std::move(storage));
+ Value moved_value(std::move(value));
EXPECT_EQ(Value::Type::DICTIONARY, moved_value.type());
- EXPECT_EQ(123, move);
+ EXPECT_EQ(123, moved_value.FindKey("Int")->GetInt());
+}
- Value blank;
+TEST(ValuesTest, MoveAssignDictionary) {
+ Value::DictStorage storage;
+ storage.emplace("Int", std::make_unique<Value>(123));
- blank = DictionaryValue();
+ Value blank;
+ blank = Value(std::move(storage));
EXPECT_EQ(Value::Type::DICTIONARY, blank.type());
+ EXPECT_EQ(123, blank.FindKey("Int")->GetInt());
}
TEST(ValuesTest, MoveList) {
- // TODO(crbug.com/646113): Clean this up once ListValue switched to
- // value semantics.
- int move;
- ListValue value;
- value.AppendInteger(123);
-
- ListValue moved_value(std::move(value));
- moved_value.GetInteger(0, &move);
-
+ Value::ListStorage storage;
+ storage.emplace_back(123);
+ Value value(storage);
+ Value moved_value(std::move(value));
EXPECT_EQ(Value::Type::LIST, moved_value.type());
- EXPECT_EQ(123, move);
+ EXPECT_EQ(123, moved_value.GetList().back().GetInt());
Value blank;
-
- blank = ListValue();
+ blank = Value(std::move(storage));
EXPECT_EQ(Value::Type::LIST, blank.type());
+ EXPECT_EQ(123, blank.GetList().back().GetInt());
+}
+
+TEST(ValuesTest, FindKey) {
+ Value::DictStorage storage;
+ storage.emplace("foo", std::make_unique<Value>("bar"));
+ Value dict(std::move(storage));
+ EXPECT_NE(nullptr, dict.FindKey("foo"));
+ EXPECT_EQ(nullptr, dict.FindKey("baz"));
+
+ // Single not found key.
+ bool found = dict.FindKey("notfound");
+ EXPECT_FALSE(found);
+}
+
+TEST(ValuesTest, FindKeyChangeValue) {
+ Value::DictStorage storage;
+ storage.emplace("foo", std::make_unique<Value>("bar"));
+ Value dict(std::move(storage));
+ Value* found = dict.FindKey("foo");
+ EXPECT_NE(nullptr, found);
+ EXPECT_EQ("bar", found->GetString());
+
+ *found = Value(123);
+ EXPECT_EQ(123, dict.FindKey("foo")->GetInt());
+}
+
+TEST(ValuesTest, FindKeyConst) {
+ Value::DictStorage storage;
+ storage.emplace("foo", std::make_unique<Value>("bar"));
+ const Value dict(std::move(storage));
+ EXPECT_NE(nullptr, dict.FindKey("foo"));
+ EXPECT_EQ(nullptr, dict.FindKey("baz"));
+}
+
+TEST(ValuesTest, FindKeyOfType) {
+ Value::DictStorage storage;
+ storage.emplace("null", std::make_unique<Value>(Value::Type::NONE));
+ storage.emplace("bool", std::make_unique<Value>(Value::Type::BOOLEAN));
+ storage.emplace("int", std::make_unique<Value>(Value::Type::INTEGER));
+ storage.emplace("double", std::make_unique<Value>(Value::Type::DOUBLE));
+ storage.emplace("string", std::make_unique<Value>(Value::Type::STRING));
+ storage.emplace("blob", std::make_unique<Value>(Value::Type::BINARY));
+ storage.emplace("list", std::make_unique<Value>(Value::Type::LIST));
+ storage.emplace("dict", std::make_unique<Value>(Value::Type::DICTIONARY));
+
+ Value dict(std::move(storage));
+ EXPECT_NE(nullptr, dict.FindKeyOfType("null", Value::Type::NONE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("null", Value::Type::BOOLEAN));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("null", Value::Type::INTEGER));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("null", Value::Type::DOUBLE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("null", Value::Type::STRING));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("null", Value::Type::BINARY));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("null", Value::Type::LIST));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("null", Value::Type::DICTIONARY));
+
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("bool", Value::Type::NONE));
+ EXPECT_NE(nullptr, dict.FindKeyOfType("bool", Value::Type::BOOLEAN));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("bool", Value::Type::INTEGER));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("bool", Value::Type::DOUBLE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("bool", Value::Type::STRING));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("bool", Value::Type::BINARY));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("bool", Value::Type::LIST));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("bool", Value::Type::DICTIONARY));
+
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("int", Value::Type::NONE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("int", Value::Type::BOOLEAN));
+ EXPECT_NE(nullptr, dict.FindKeyOfType("int", Value::Type::INTEGER));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("int", Value::Type::DOUBLE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("int", Value::Type::STRING));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("int", Value::Type::BINARY));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("int", Value::Type::LIST));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("int", Value::Type::DICTIONARY));
+
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("double", Value::Type::NONE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("double", Value::Type::BOOLEAN));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("double", Value::Type::INTEGER));
+ EXPECT_NE(nullptr, dict.FindKeyOfType("double", Value::Type::DOUBLE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("double", Value::Type::STRING));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("double", Value::Type::BINARY));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("double", Value::Type::LIST));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("double", Value::Type::DICTIONARY));
+
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("string", Value::Type::NONE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("string", Value::Type::BOOLEAN));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("string", Value::Type::INTEGER));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("string", Value::Type::DOUBLE));
+ EXPECT_NE(nullptr, dict.FindKeyOfType("string", Value::Type::STRING));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("string", Value::Type::BINARY));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("string", Value::Type::LIST));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("string", Value::Type::DICTIONARY));
+
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("blob", Value::Type::NONE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("blob", Value::Type::BOOLEAN));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("blob", Value::Type::INTEGER));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("blob", Value::Type::DOUBLE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("blob", Value::Type::STRING));
+ EXPECT_NE(nullptr, dict.FindKeyOfType("blob", Value::Type::BINARY));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("blob", Value::Type::LIST));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("blob", Value::Type::DICTIONARY));
+
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("list", Value::Type::NONE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("list", Value::Type::BOOLEAN));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("list", Value::Type::INTEGER));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("list", Value::Type::DOUBLE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("list", Value::Type::STRING));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("list", Value::Type::BINARY));
+ EXPECT_NE(nullptr, dict.FindKeyOfType("list", Value::Type::LIST));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("list", Value::Type::DICTIONARY));
+
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("dict", Value::Type::NONE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("dict", Value::Type::BOOLEAN));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("dict", Value::Type::INTEGER));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("dict", Value::Type::DOUBLE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("dict", Value::Type::STRING));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("dict", Value::Type::BINARY));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("dict", Value::Type::LIST));
+ EXPECT_NE(nullptr, dict.FindKeyOfType("dict", Value::Type::DICTIONARY));
+}
+
+TEST(ValuesTest, FindKeyOfTypeConst) {
+ Value::DictStorage storage;
+ storage.emplace("null", std::make_unique<Value>(Value::Type::NONE));
+ storage.emplace("bool", std::make_unique<Value>(Value::Type::BOOLEAN));
+ storage.emplace("int", std::make_unique<Value>(Value::Type::INTEGER));
+ storage.emplace("double", std::make_unique<Value>(Value::Type::DOUBLE));
+ storage.emplace("string", std::make_unique<Value>(Value::Type::STRING));
+ storage.emplace("blob", std::make_unique<Value>(Value::Type::BINARY));
+ storage.emplace("list", std::make_unique<Value>(Value::Type::LIST));
+ storage.emplace("dict", std::make_unique<Value>(Value::Type::DICTIONARY));
+
+ const Value dict(std::move(storage));
+ EXPECT_NE(nullptr, dict.FindKeyOfType("null", Value::Type::NONE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("null", Value::Type::BOOLEAN));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("null", Value::Type::INTEGER));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("null", Value::Type::DOUBLE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("null", Value::Type::STRING));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("null", Value::Type::BINARY));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("null", Value::Type::LIST));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("null", Value::Type::DICTIONARY));
+
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("bool", Value::Type::NONE));
+ EXPECT_NE(nullptr, dict.FindKeyOfType("bool", Value::Type::BOOLEAN));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("bool", Value::Type::INTEGER));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("bool", Value::Type::DOUBLE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("bool", Value::Type::STRING));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("bool", Value::Type::BINARY));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("bool", Value::Type::LIST));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("bool", Value::Type::DICTIONARY));
+
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("int", Value::Type::NONE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("int", Value::Type::BOOLEAN));
+ EXPECT_NE(nullptr, dict.FindKeyOfType("int", Value::Type::INTEGER));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("int", Value::Type::DOUBLE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("int", Value::Type::STRING));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("int", Value::Type::BINARY));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("int", Value::Type::LIST));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("int", Value::Type::DICTIONARY));
+
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("double", Value::Type::NONE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("double", Value::Type::BOOLEAN));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("double", Value::Type::INTEGER));
+ EXPECT_NE(nullptr, dict.FindKeyOfType("double", Value::Type::DOUBLE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("double", Value::Type::STRING));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("double", Value::Type::BINARY));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("double", Value::Type::LIST));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("double", Value::Type::DICTIONARY));
+
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("string", Value::Type::NONE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("string", Value::Type::BOOLEAN));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("string", Value::Type::INTEGER));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("string", Value::Type::DOUBLE));
+ EXPECT_NE(nullptr, dict.FindKeyOfType("string", Value::Type::STRING));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("string", Value::Type::BINARY));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("string", Value::Type::LIST));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("string", Value::Type::DICTIONARY));
+
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("blob", Value::Type::NONE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("blob", Value::Type::BOOLEAN));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("blob", Value::Type::INTEGER));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("blob", Value::Type::DOUBLE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("blob", Value::Type::STRING));
+ EXPECT_NE(nullptr, dict.FindKeyOfType("blob", Value::Type::BINARY));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("blob", Value::Type::LIST));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("blob", Value::Type::DICTIONARY));
+
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("list", Value::Type::NONE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("list", Value::Type::BOOLEAN));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("list", Value::Type::INTEGER));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("list", Value::Type::DOUBLE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("list", Value::Type::STRING));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("list", Value::Type::BINARY));
+ EXPECT_NE(nullptr, dict.FindKeyOfType("list", Value::Type::LIST));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("list", Value::Type::DICTIONARY));
+
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("dict", Value::Type::NONE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("dict", Value::Type::BOOLEAN));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("dict", Value::Type::INTEGER));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("dict", Value::Type::DOUBLE));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("dict", Value::Type::STRING));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("dict", Value::Type::BINARY));
+ EXPECT_EQ(nullptr, dict.FindKeyOfType("dict", Value::Type::LIST));
+ EXPECT_NE(nullptr, dict.FindKeyOfType("dict", Value::Type::DICTIONARY));
+}
+
+TEST(ValuesTest, SetKey) {
+ Value::DictStorage storage;
+ storage.emplace("null", std::make_unique<Value>(Value::Type::NONE));
+ storage.emplace("bool", std::make_unique<Value>(Value::Type::BOOLEAN));
+ storage.emplace("int", std::make_unique<Value>(Value::Type::INTEGER));
+ storage.emplace("double", std::make_unique<Value>(Value::Type::DOUBLE));
+ storage.emplace("string", std::make_unique<Value>(Value::Type::STRING));
+ storage.emplace("blob", std::make_unique<Value>(Value::Type::BINARY));
+ storage.emplace("list", std::make_unique<Value>(Value::Type::LIST));
+ storage.emplace("dict", std::make_unique<Value>(Value::Type::DICTIONARY));
+
+ Value dict(Value::Type::DICTIONARY);
+ dict.SetKey(StringPiece("null"), Value(Value::Type::NONE));
+ dict.SetKey(StringPiece("bool"), Value(Value::Type::BOOLEAN));
+ dict.SetKey(std::string("int"), Value(Value::Type::INTEGER));
+ dict.SetKey(std::string("double"), Value(Value::Type::DOUBLE));
+ dict.SetKey(std::string("string"), Value(Value::Type::STRING));
+ dict.SetKey("blob", Value(Value::Type::BINARY));
+ dict.SetKey("list", Value(Value::Type::LIST));
+ dict.SetKey("dict", Value(Value::Type::DICTIONARY));
+
+ EXPECT_EQ(Value(std::move(storage)), dict);
+}
+
+TEST(ValuesTest, FindPath) {
+ // Construct a dictionary path {root}.foo.bar = 123
+ Value foo(Value::Type::DICTIONARY);
+ foo.SetKey("bar", Value(123));
+
+ Value root(Value::Type::DICTIONARY);
+ root.SetKey("foo", std::move(foo));
+
+ // No key (stupid but well-defined and takes work to prevent).
+ Value* found = root.FindPath(std::vector<StringPiece>{});
+ EXPECT_EQ(&root, found);
+
+ // Double key, second not found.
+ found = root.FindPath(std::vector<StringPiece>{"foo", "notfound"});
+ EXPECT_FALSE(found);
+
+ // Double key, found.
+ found = root.FindPath(std::vector<StringPiece>{"foo", "bar"});
+ EXPECT_TRUE(found);
+ EXPECT_TRUE(found->is_int());
+ EXPECT_EQ(123, found->GetInt());
+}
+
+TEST(ValuesTest, SetPath) {
+ Value root(Value::Type::DICTIONARY);
+
+ Value* inserted = root.SetPath({"one", "two"}, Value(123));
+ Value* found = root.FindPathOfType({"one", "two"}, Value::Type::INTEGER);
+ ASSERT_TRUE(found);
+ EXPECT_EQ(inserted, found);
+ EXPECT_EQ(123, found->GetInt());
+
+ inserted = root.SetPath(std::vector<StringPiece>{"foo", "bar"}, Value(123));
+ found = root.FindPathOfType({"foo", "bar"}, Value::Type::INTEGER);
+ ASSERT_TRUE(found);
+ EXPECT_EQ(inserted, found);
+ EXPECT_EQ(123, found->GetInt());
+
+ // Overwrite with a different value.
+ root.SetPath({"foo", "bar"}, Value("hello"));
+ found = root.FindPathOfType(std::vector<StringPiece>{"foo", "bar"},
+ Value::Type::STRING);
+ ASSERT_TRUE(found);
+ EXPECT_EQ("hello", found->GetString());
+
+ // Can't change existing non-dictionary keys to dictionaries.
+ found =
+ root.SetPath(std::vector<StringPiece>{"foo", "bar", "baz"}, Value(123));
+ EXPECT_FALSE(found);
+}
+
+TEST(ValuesTest, RemoveKey) {
+ Value root(Value::Type::DICTIONARY);
+ root.SetKey("one", Value(123));
+
+ // Removal of missing key should fail.
+ EXPECT_FALSE(root.RemoveKey("two"));
+
+ // Removal of existing key should succeed.
+ EXPECT_TRUE(root.RemoveKey("one"));
+
+ // Second removal of previously existing key should fail.
+ EXPECT_FALSE(root.RemoveKey("one"));
+}
+
+TEST(ValuesTest, RemovePath) {
+ Value root(Value::Type::DICTIONARY);
+ root.SetPath({"one", "two", "three"}, Value(123));
+
+ // Removal of missing key should fail.
+ EXPECT_FALSE(root.RemovePath({"one", "two", "four"}));
+
+ // Removal of existing key should succeed.
+ EXPECT_TRUE(root.RemovePath({"one", "two", "three"}));
+
+ // Second removal of previously existing key should fail.
+ EXPECT_FALSE(root.RemovePath({"one", "two", "three"}));
+
+ // Intermediate empty dictionaries should be cleared.
+ EXPECT_FALSE(root.FindKey("one"));
+
+ root.SetPath({"one", "two", "three"}, Value(123));
+ root.SetPath({"one", "two", "four"}, Value(124));
+
+ EXPECT_TRUE(root.RemovePath(std::vector<StringPiece>{"one", "two", "three"}));
+ // Intermediate non-empty dictionaries should be kept.
+ EXPECT_TRUE(root.FindKey("one"));
+ EXPECT_TRUE(root.FindPath({"one", "two"}));
+ EXPECT_TRUE(root.FindPath({"one", "two", "four"}));
}
TEST(ValuesTest, Basic) {
@@ -357,11 +696,11 @@ TEST(ValuesTest, Basic) {
ASSERT_FALSE(settings.GetString("global.homepage", &homepage));
ASSERT_EQ(std::string("http://google.com"), homepage);
- ASSERT_FALSE(settings.Get("global", NULL));
+ ASSERT_FALSE(settings.Get("global", nullptr));
settings.SetBoolean("global", true);
- ASSERT_TRUE(settings.Get("global", NULL));
+ ASSERT_TRUE(settings.Get("global", nullptr));
settings.SetString("global.homepage", "http://scurvy.com");
- ASSERT_TRUE(settings.Get("global", NULL));
+ ASSERT_TRUE(settings.Get("global", nullptr));
homepage = "http://google.com";
ASSERT_TRUE(settings.GetString("global.homepage", &homepage));
ASSERT_EQ(std::string("http://scurvy.com"), homepage);
@@ -395,13 +734,13 @@ TEST(ValuesTest, Basic) {
TEST(ValuesTest, List) {
std::unique_ptr<ListValue> mixed_list(new ListValue());
- mixed_list->Set(0, MakeUnique<Value>(true));
- mixed_list->Set(1, MakeUnique<Value>(42));
- mixed_list->Set(2, MakeUnique<Value>(88.8));
- mixed_list->Set(3, MakeUnique<Value>("foo"));
+ mixed_list->Set(0, std::make_unique<Value>(true));
+ mixed_list->Set(1, std::make_unique<Value>(42));
+ mixed_list->Set(2, std::make_unique<Value>(88.8));
+ mixed_list->Set(3, std::make_unique<Value>("foo"));
ASSERT_EQ(4u, mixed_list->GetSize());
- Value *value = NULL;
+ Value* value = nullptr;
bool bool_value = false;
int int_value = 0;
double double_value = 0.0;
@@ -437,55 +776,50 @@ TEST(ValuesTest, List) {
base::Value not_found_value(false);
ASSERT_NE(mixed_list->end(), mixed_list->Find(sought_value));
- ASSERT_TRUE((*mixed_list->Find(sought_value))->GetAsInteger(&int_value));
+ ASSERT_TRUE((*mixed_list->Find(sought_value)).GetAsInteger(&int_value));
ASSERT_EQ(42, int_value);
ASSERT_EQ(mixed_list->end(), mixed_list->Find(not_found_value));
}
TEST(ValuesTest, BinaryValue) {
// Default constructor creates a BinaryValue with a buffer of size 0.
- auto binary = MakeUnique<Value>(Value::Type::BINARY);
+ auto binary = std::make_unique<Value>(Value::Type::BINARY);
ASSERT_TRUE(binary.get());
- ASSERT_EQ(0U, binary->GetSize());
+ ASSERT_TRUE(binary->GetBlob().empty());
// Test the common case of a non-empty buffer
- std::vector<char> buffer(15);
+ Value::BlobStorage buffer(15);
char* original_buffer = buffer.data();
- binary.reset(new BinaryValue(std::move(buffer)));
+ binary.reset(new Value(std::move(buffer)));
ASSERT_TRUE(binary.get());
- ASSERT_TRUE(binary->GetBuffer());
- ASSERT_EQ(original_buffer, binary->GetBuffer());
- ASSERT_EQ(15U, binary->GetSize());
+ ASSERT_TRUE(binary->GetBlob().data());
+ ASSERT_EQ(original_buffer, binary->GetBlob().data());
+ ASSERT_EQ(15U, binary->GetBlob().size());
char stack_buffer[42];
memset(stack_buffer, '!', 42);
- binary = BinaryValue::CreateWithCopiedBuffer(stack_buffer, 42);
+ binary = Value::CreateWithCopiedBuffer(stack_buffer, 42);
ASSERT_TRUE(binary.get());
- ASSERT_TRUE(binary->GetBuffer());
- ASSERT_NE(stack_buffer, binary->GetBuffer());
- ASSERT_EQ(42U, binary->GetSize());
- ASSERT_EQ(0, memcmp(stack_buffer, binary->GetBuffer(), binary->GetSize()));
-
- // Test overloaded GetAsBinary.
- Value* narrow_value = binary.get();
- const BinaryValue* narrow_binary = NULL;
- ASSERT_TRUE(narrow_value->GetAsBinary(&narrow_binary));
- EXPECT_EQ(binary.get(), narrow_binary);
+ ASSERT_TRUE(binary->GetBlob().data());
+ ASSERT_NE(stack_buffer, binary->GetBlob().data());
+ ASSERT_EQ(42U, binary->GetBlob().size());
+ ASSERT_EQ(0, memcmp(stack_buffer, binary->GetBlob().data(),
+ binary->GetBlob().size()));
}
TEST(ValuesTest, StringValue) {
// Test overloaded StringValue constructor.
std::unique_ptr<Value> narrow_value(new Value("narrow"));
ASSERT_TRUE(narrow_value.get());
- ASSERT_TRUE(narrow_value->IsType(Value::Type::STRING));
+ ASSERT_TRUE(narrow_value->is_string());
std::unique_ptr<Value> utf16_value(new Value(ASCIIToUTF16("utf16")));
ASSERT_TRUE(utf16_value.get());
- ASSERT_TRUE(utf16_value->IsType(Value::Type::STRING));
+ ASSERT_TRUE(utf16_value->is_string());
// Test overloaded GetAsString.
std::string narrow = "http://google.com";
string16 utf16 = ASCIIToUTF16("http://google.com");
- const Value* string_value = NULL;
+ const Value* string_value = nullptr;
ASSERT_TRUE(narrow_value->GetAsString(&narrow));
ASSERT_TRUE(narrow_value->GetAsString(&utf16));
ASSERT_TRUE(narrow_value->GetAsString(&string_value));
@@ -501,14 +835,14 @@ TEST(ValuesTest, StringValue) {
ASSERT_EQ(string_value->GetString(), narrow);
// Don't choke on NULL values.
- ASSERT_TRUE(narrow_value->GetAsString(static_cast<string16*>(NULL)));
- ASSERT_TRUE(narrow_value->GetAsString(static_cast<std::string*>(NULL)));
- ASSERT_TRUE(narrow_value->GetAsString(static_cast<const Value**>(NULL)));
+ ASSERT_TRUE(narrow_value->GetAsString(static_cast<string16*>(nullptr)));
+ ASSERT_TRUE(narrow_value->GetAsString(static_cast<std::string*>(nullptr)));
+ ASSERT_TRUE(narrow_value->GetAsString(static_cast<const Value**>(nullptr)));
}
TEST(ValuesTest, ListDeletion) {
ListValue list;
- list.Append(MakeUnique<Value>());
+ list.Append(std::make_unique<Value>());
EXPECT_FALSE(list.empty());
list.Clear();
EXPECT_TRUE(list.empty());
@@ -519,7 +853,7 @@ TEST(ValuesTest, ListRemoval) {
{
ListValue list;
- list.Append(MakeUnique<Value>());
+ list.Append(std::make_unique<Value>());
EXPECT_EQ(1U, list.GetSize());
EXPECT_FALSE(list.Remove(std::numeric_limits<size_t>::max(),
&removed_item));
@@ -532,18 +866,18 @@ TEST(ValuesTest, ListRemoval) {
{
ListValue list;
- list.Append(MakeUnique<Value>());
- EXPECT_TRUE(list.Remove(0, NULL));
+ list.Append(std::make_unique<Value>());
+ EXPECT_TRUE(list.Remove(0, nullptr));
EXPECT_EQ(0U, list.GetSize());
}
{
ListValue list;
- auto value = MakeUnique<Value>();
- Value* original_value = value.get();
+ auto value = std::make_unique<Value>();
+ Value original_value = value->Clone();
list.Append(std::move(value));
size_t index = 0;
- list.Remove(*original_value, &index);
+ list.Remove(original_value, &index);
EXPECT_EQ(0U, index);
EXPECT_EQ(0U, list.GetSize());
}
@@ -552,10 +886,71 @@ TEST(ValuesTest, ListRemoval) {
TEST(ValuesTest, DictionaryDeletion) {
std::string key = "test";
DictionaryValue dict;
- dict.Set(key, MakeUnique<Value>());
+ dict.Set(key, std::make_unique<Value>());
EXPECT_FALSE(dict.empty());
+ EXPECT_FALSE(dict.DictEmpty());
+ EXPECT_EQ(1U, dict.DictSize());
dict.Clear();
EXPECT_TRUE(dict.empty());
+ EXPECT_TRUE(dict.DictEmpty());
+ EXPECT_EQ(0U, dict.DictSize());
+}
+
+TEST(ValuesTest, DictionarySetReturnsPointer) {
+ {
+ DictionaryValue dict;
+ Value* blank_ptr = dict.Set("foo.bar", std::make_unique<base::Value>());
+ EXPECT_EQ(Value::Type::NONE, blank_ptr->type());
+ }
+
+ {
+ DictionaryValue dict;
+ Value* blank_ptr = dict.SetWithoutPathExpansion(
+ "foo.bar", std::make_unique<base::Value>());
+ EXPECT_EQ(Value::Type::NONE, blank_ptr->type());
+ }
+
+ {
+ DictionaryValue dict;
+ Value* int_ptr = dict.SetInteger("foo.bar", 42);
+ EXPECT_EQ(Value::Type::INTEGER, int_ptr->type());
+ EXPECT_EQ(42, int_ptr->GetInt());
+ }
+
+ {
+ DictionaryValue dict;
+ Value* double_ptr = dict.SetDouble("foo.bar", 3.142);
+ EXPECT_EQ(Value::Type::DOUBLE, double_ptr->type());
+ EXPECT_EQ(3.142, double_ptr->GetDouble());
+ }
+
+ {
+ DictionaryValue dict;
+ Value* string_ptr = dict.SetString("foo.bar", "foo");
+ EXPECT_EQ(Value::Type::STRING, string_ptr->type());
+ EXPECT_EQ("foo", string_ptr->GetString());
+ }
+
+ {
+ DictionaryValue dict;
+ Value* string16_ptr = dict.SetString("foo.bar", ASCIIToUTF16("baz"));
+ EXPECT_EQ(Value::Type::STRING, string16_ptr->type());
+ EXPECT_EQ("baz", string16_ptr->GetString());
+ }
+
+ {
+ DictionaryValue dict;
+ DictionaryValue* dict_ptr = dict.SetDictionary(
+ "foo.bar", std::make_unique<base::DictionaryValue>());
+ EXPECT_EQ(Value::Type::DICTIONARY, dict_ptr->type());
+ }
+
+ {
+ DictionaryValue dict;
+ ListValue* list_ptr =
+ dict.SetList("foo.bar", std::make_unique<base::ListValue>());
+ EXPECT_EQ(Value::Type::LIST, list_ptr->type());
+ }
}
TEST(ValuesTest, DictionaryRemoval) {
@@ -564,27 +959,34 @@ TEST(ValuesTest, DictionaryRemoval) {
{
DictionaryValue dict;
- dict.Set(key, MakeUnique<Value>());
+ EXPECT_EQ(0U, dict.DictSize());
+ EXPECT_TRUE(dict.DictEmpty());
+ dict.Set(key, std::make_unique<Value>());
EXPECT_TRUE(dict.HasKey(key));
EXPECT_FALSE(dict.Remove("absent key", &removed_item));
+ EXPECT_EQ(1U, dict.DictSize());
+ EXPECT_FALSE(dict.DictEmpty());
+
EXPECT_TRUE(dict.Remove(key, &removed_item));
EXPECT_FALSE(dict.HasKey(key));
ASSERT_TRUE(removed_item);
+ EXPECT_EQ(0U, dict.DictSize());
+ EXPECT_TRUE(dict.DictEmpty());
}
{
DictionaryValue dict;
- dict.Set(key, MakeUnique<Value>());
+ dict.Set(key, std::make_unique<Value>());
EXPECT_TRUE(dict.HasKey(key));
- EXPECT_TRUE(dict.Remove(key, NULL));
+ EXPECT_TRUE(dict.Remove(key, nullptr));
EXPECT_FALSE(dict.HasKey(key));
}
}
TEST(ValuesTest, DictionaryWithoutPathExpansion) {
DictionaryValue dict;
- dict.Set("this.is.expanded", Value::CreateNullValue());
- dict.SetWithoutPathExpansion("this.isnt.expanded", Value::CreateNullValue());
+ dict.Set("this.is.expanded", std::make_unique<Value>());
+ dict.SetWithoutPathExpansion("this.isnt.expanded", std::make_unique<Value>());
EXPECT_FALSE(dict.HasKey("this.is.expanded"));
EXPECT_TRUE(dict.HasKey("this"));
@@ -600,15 +1002,15 @@ TEST(ValuesTest, DictionaryWithoutPathExpansion) {
EXPECT_FALSE(dict.Get("this.isnt.expanded", &value3));
Value* value4;
ASSERT_TRUE(dict.GetWithoutPathExpansion("this.isnt.expanded", &value4));
- EXPECT_EQ(Value::Type::NONE, value4->GetType());
+ EXPECT_EQ(Value::Type::NONE, value4->type());
}
// Tests the deprecated version of SetWithoutPathExpansion.
// TODO(estade): remove.
TEST(ValuesTest, DictionaryWithoutPathExpansionDeprecated) {
DictionaryValue dict;
- dict.Set("this.is.expanded", Value::CreateNullValue());
- dict.SetWithoutPathExpansion("this.isnt.expanded", Value::CreateNullValue());
+ dict.Set("this.is.expanded", std::make_unique<Value>());
+ dict.SetWithoutPathExpansion("this.isnt.expanded", std::make_unique<Value>());
EXPECT_FALSE(dict.HasKey("this.is.expanded"));
EXPECT_TRUE(dict.HasKey("this"));
@@ -624,7 +1026,7 @@ TEST(ValuesTest, DictionaryWithoutPathExpansionDeprecated) {
EXPECT_FALSE(dict.Get("this.isnt.expanded", &value3));
Value* value4;
ASSERT_TRUE(dict.GetWithoutPathExpansion("this.isnt.expanded", &value4));
- EXPECT_EQ(Value::Type::NONE, value4->GetType());
+ EXPECT_EQ(Value::Type::NONE, value4->type());
}
TEST(ValuesTest, DictionaryRemovePath) {
@@ -635,108 +1037,92 @@ TEST(ValuesTest, DictionaryRemovePath) {
std::unique_ptr<Value> removed_item;
EXPECT_TRUE(dict.RemovePath("a.long.way.down", &removed_item));
ASSERT_TRUE(removed_item);
- EXPECT_TRUE(removed_item->IsType(base::Value::Type::INTEGER));
+ EXPECT_TRUE(removed_item->is_int());
EXPECT_FALSE(dict.HasKey("a.long.way.down"));
EXPECT_FALSE(dict.HasKey("a.long.way"));
- EXPECT_TRUE(dict.Get("a.long.key.path", NULL));
+ EXPECT_TRUE(dict.Get("a.long.key.path", nullptr));
removed_item.reset();
EXPECT_FALSE(dict.RemovePath("a.long.way.down", &removed_item));
EXPECT_FALSE(removed_item);
- EXPECT_TRUE(dict.Get("a.long.key.path", NULL));
+ EXPECT_TRUE(dict.Get("a.long.key.path", nullptr));
removed_item.reset();
EXPECT_TRUE(dict.RemovePath("a.long.key.path", &removed_item));
ASSERT_TRUE(removed_item);
- EXPECT_TRUE(removed_item->IsType(base::Value::Type::BOOLEAN));
+ EXPECT_TRUE(removed_item->is_bool());
EXPECT_TRUE(dict.empty());
}
TEST(ValuesTest, DeepCopy) {
DictionaryValue original_dict;
- std::unique_ptr<Value> scoped_null = Value::CreateNullValue();
- Value* original_null = scoped_null.get();
- original_dict.Set("null", std::move(scoped_null));
- std::unique_ptr<Value> scoped_bool(new Value(true));
- Value* original_bool = scoped_bool.get();
- original_dict.Set("bool", std::move(scoped_bool));
- std::unique_ptr<Value> scoped_int(new Value(42));
- Value* original_int = scoped_int.get();
- original_dict.Set("int", std::move(scoped_int));
- std::unique_ptr<Value> scoped_double(new Value(3.14));
- Value* original_double = scoped_double.get();
- original_dict.Set("double", std::move(scoped_double));
- std::unique_ptr<Value> scoped_string(new Value("hello"));
- Value* original_string = scoped_string.get();
- original_dict.Set("string", std::move(scoped_string));
- std::unique_ptr<Value> scoped_string16(new Value(ASCIIToUTF16("hello16")));
- Value* original_string16 = scoped_string16.get();
- original_dict.Set("string16", std::move(scoped_string16));
-
- std::vector<char> original_buffer(42, '!');
- std::unique_ptr<BinaryValue> scoped_binary(
- new BinaryValue(std::move(original_buffer)));
- BinaryValue* original_binary = scoped_binary.get();
- original_dict.Set("binary", std::move(scoped_binary));
-
- std::unique_ptr<ListValue> scoped_list(new ListValue());
- Value* original_list = scoped_list.get();
- std::unique_ptr<Value> scoped_list_element_0(new Value(0));
- Value* original_list_element_0 = scoped_list_element_0.get();
- scoped_list->Append(std::move(scoped_list_element_0));
- std::unique_ptr<Value> scoped_list_element_1(new Value(1));
- Value* original_list_element_1 = scoped_list_element_1.get();
- scoped_list->Append(std::move(scoped_list_element_1));
- original_dict.Set("list", std::move(scoped_list));
-
- std::unique_ptr<DictionaryValue> scoped_nested_dictionary(
- new DictionaryValue());
- Value* original_nested_dictionary = scoped_nested_dictionary.get();
- scoped_nested_dictionary->SetString("key", "value");
- original_dict.Set("dictionary", std::move(scoped_nested_dictionary));
-
- auto copy_dict = MakeUnique<DictionaryValue>(original_dict);
+ Value* null_weak = original_dict.Set("null", std::make_unique<Value>());
+ Value* bool_weak = original_dict.Set("bool", std::make_unique<Value>(true));
+ Value* int_weak = original_dict.Set("int", std::make_unique<Value>(42));
+ Value* double_weak =
+ original_dict.Set("double", std::make_unique<Value>(3.14));
+ Value* string_weak =
+ original_dict.Set("string", std::make_unique<Value>("hello"));
+ Value* string16_weak = original_dict.Set(
+ "string16", std::make_unique<Value>(ASCIIToUTF16("hello16")));
+
+ Value* binary_weak = original_dict.Set(
+ "binary", std::make_unique<Value>(Value::BlobStorage(42, '!')));
+
+ Value::ListStorage storage;
+ storage.emplace_back(0);
+ storage.emplace_back(1);
+ Value* list_weak =
+ original_dict.Set("list", std::make_unique<Value>(std::move(storage)));
+ Value* list_element_0_weak = &list_weak->GetList()[0];
+ Value* list_element_1_weak = &list_weak->GetList()[1];
+
+ DictionaryValue* dict_weak = original_dict.SetDictionary(
+ "dictionary", std::make_unique<DictionaryValue>());
+ dict_weak->SetString("key", "value");
+
+ auto copy_dict = original_dict.CreateDeepCopy();
ASSERT_TRUE(copy_dict.get());
ASSERT_NE(copy_dict.get(), &original_dict);
- Value* copy_null = NULL;
+ Value* copy_null = nullptr;
ASSERT_TRUE(copy_dict->Get("null", &copy_null));
ASSERT_TRUE(copy_null);
- ASSERT_NE(copy_null, original_null);
- ASSERT_TRUE(copy_null->IsType(Value::Type::NONE));
+ ASSERT_NE(copy_null, null_weak);
+ ASSERT_TRUE(copy_null->is_none());
- Value* copy_bool = NULL;
+ Value* copy_bool = nullptr;
ASSERT_TRUE(copy_dict->Get("bool", &copy_bool));
ASSERT_TRUE(copy_bool);
- ASSERT_NE(copy_bool, original_bool);
- ASSERT_TRUE(copy_bool->IsType(Value::Type::BOOLEAN));
+ ASSERT_NE(copy_bool, bool_weak);
+ ASSERT_TRUE(copy_bool->is_bool());
bool copy_bool_value = false;
ASSERT_TRUE(copy_bool->GetAsBoolean(&copy_bool_value));
ASSERT_TRUE(copy_bool_value);
- Value* copy_int = NULL;
+ Value* copy_int = nullptr;
ASSERT_TRUE(copy_dict->Get("int", &copy_int));
ASSERT_TRUE(copy_int);
- ASSERT_NE(copy_int, original_int);
- ASSERT_TRUE(copy_int->IsType(Value::Type::INTEGER));
+ ASSERT_NE(copy_int, int_weak);
+ ASSERT_TRUE(copy_int->is_int());
int copy_int_value = 0;
ASSERT_TRUE(copy_int->GetAsInteger(&copy_int_value));
ASSERT_EQ(42, copy_int_value);
- Value* copy_double = NULL;
+ Value* copy_double = nullptr;
ASSERT_TRUE(copy_dict->Get("double", &copy_double));
ASSERT_TRUE(copy_double);
- ASSERT_NE(copy_double, original_double);
- ASSERT_TRUE(copy_double->IsType(Value::Type::DOUBLE));
+ ASSERT_NE(copy_double, double_weak);
+ ASSERT_TRUE(copy_double->is_double());
double copy_double_value = 0;
ASSERT_TRUE(copy_double->GetAsDouble(&copy_double_value));
ASSERT_EQ(3.14, copy_double_value);
- Value* copy_string = NULL;
+ Value* copy_string = nullptr;
ASSERT_TRUE(copy_dict->Get("string", &copy_string));
ASSERT_TRUE(copy_string);
- ASSERT_NE(copy_string, original_string);
- ASSERT_TRUE(copy_string->IsType(Value::Type::STRING));
+ ASSERT_NE(copy_string, string_weak);
+ ASSERT_TRUE(copy_string->is_string());
std::string copy_string_value;
string16 copy_string16_value;
ASSERT_TRUE(copy_string->GetAsString(&copy_string_value));
@@ -744,32 +1130,30 @@ TEST(ValuesTest, DeepCopy) {
ASSERT_EQ(std::string("hello"), copy_string_value);
ASSERT_EQ(ASCIIToUTF16("hello"), copy_string16_value);
- Value* copy_string16 = NULL;
+ Value* copy_string16 = nullptr;
ASSERT_TRUE(copy_dict->Get("string16", &copy_string16));
ASSERT_TRUE(copy_string16);
- ASSERT_NE(copy_string16, original_string16);
- ASSERT_TRUE(copy_string16->IsType(Value::Type::STRING));
+ ASSERT_NE(copy_string16, string16_weak);
+ ASSERT_TRUE(copy_string16->is_string());
ASSERT_TRUE(copy_string16->GetAsString(&copy_string_value));
ASSERT_TRUE(copy_string16->GetAsString(&copy_string16_value));
ASSERT_EQ(std::string("hello16"), copy_string_value);
ASSERT_EQ(ASCIIToUTF16("hello16"), copy_string16_value);
- Value* copy_binary = NULL;
+ Value* copy_binary = nullptr;
ASSERT_TRUE(copy_dict->Get("binary", &copy_binary));
ASSERT_TRUE(copy_binary);
- ASSERT_NE(copy_binary, original_binary);
- ASSERT_TRUE(copy_binary->IsType(Value::Type::BINARY));
- ASSERT_NE(original_binary->GetBuffer(), copy_binary->GetBuffer());
- ASSERT_EQ(original_binary->GetSize(), copy_binary->GetSize());
- ASSERT_EQ(0, memcmp(original_binary->GetBuffer(), copy_binary->GetBuffer(),
- original_binary->GetSize()));
-
- Value* copy_value = NULL;
+ ASSERT_NE(copy_binary, binary_weak);
+ ASSERT_TRUE(copy_binary->is_blob());
+ ASSERT_NE(binary_weak->GetBlob().data(), copy_binary->GetBlob().data());
+ ASSERT_EQ(binary_weak->GetBlob(), copy_binary->GetBlob());
+
+ Value* copy_value = nullptr;
ASSERT_TRUE(copy_dict->Get("list", &copy_value));
ASSERT_TRUE(copy_value);
- ASSERT_NE(copy_value, original_list);
- ASSERT_TRUE(copy_value->IsType(Value::Type::LIST));
- ListValue* copy_list = NULL;
+ ASSERT_NE(copy_value, list_weak);
+ ASSERT_TRUE(copy_value->is_list());
+ ListValue* copy_list = nullptr;
ASSERT_TRUE(copy_value->GetAsList(&copy_list));
ASSERT_TRUE(copy_list);
ASSERT_EQ(2U, copy_list->GetSize());
@@ -777,7 +1161,7 @@ TEST(ValuesTest, DeepCopy) {
Value* copy_list_element_0;
ASSERT_TRUE(copy_list->Get(0, &copy_list_element_0));
ASSERT_TRUE(copy_list_element_0);
- ASSERT_NE(copy_list_element_0, original_list_element_0);
+ ASSERT_NE(copy_list_element_0, list_element_0_weak);
int copy_list_element_0_value;
ASSERT_TRUE(copy_list_element_0->GetAsInteger(&copy_list_element_0_value));
ASSERT_EQ(0, copy_list_element_0_value);
@@ -785,25 +1169,25 @@ TEST(ValuesTest, DeepCopy) {
Value* copy_list_element_1;
ASSERT_TRUE(copy_list->Get(1, &copy_list_element_1));
ASSERT_TRUE(copy_list_element_1);
- ASSERT_NE(copy_list_element_1, original_list_element_1);
+ ASSERT_NE(copy_list_element_1, list_element_1_weak);
int copy_list_element_1_value;
ASSERT_TRUE(copy_list_element_1->GetAsInteger(&copy_list_element_1_value));
ASSERT_EQ(1, copy_list_element_1_value);
- copy_value = NULL;
+ copy_value = nullptr;
ASSERT_TRUE(copy_dict->Get("dictionary", &copy_value));
ASSERT_TRUE(copy_value);
- ASSERT_NE(copy_value, original_nested_dictionary);
- ASSERT_TRUE(copy_value->IsType(Value::Type::DICTIONARY));
- DictionaryValue* copy_nested_dictionary = NULL;
+ ASSERT_NE(copy_value, dict_weak);
+ ASSERT_TRUE(copy_value->is_dict());
+ DictionaryValue* copy_nested_dictionary = nullptr;
ASSERT_TRUE(copy_value->GetAsDictionary(&copy_nested_dictionary));
ASSERT_TRUE(copy_nested_dictionary);
EXPECT_TRUE(copy_nested_dictionary->HasKey("key"));
}
TEST(ValuesTest, Equals) {
- std::unique_ptr<Value> null1(Value::CreateNullValue());
- std::unique_ptr<Value> null2(Value::CreateNullValue());
+ auto null1 = std::make_unique<Value>();
+ auto null2 = std::make_unique<Value>();
EXPECT_NE(null1.get(), null2.get());
EXPECT_EQ(*null1, *null2);
@@ -816,56 +1200,32 @@ TEST(ValuesTest, Equals) {
dv.SetDouble("c", 2.5);
dv.SetString("d1", "string");
dv.SetString("d2", ASCIIToUTF16("http://google.com"));
- dv.Set("e", Value::CreateNullValue());
+ dv.Set("e", std::make_unique<Value>());
- auto copy = MakeUnique<DictionaryValue>(dv);
+ auto copy = dv.CreateDeepCopy();
EXPECT_EQ(dv, *copy);
std::unique_ptr<ListValue> list(new ListValue);
- ListValue* original_list = list.get();
- list->Append(Value::CreateNullValue());
+ list->Append(std::make_unique<Value>());
list->Append(WrapUnique(new DictionaryValue));
- auto list_copy = MakeUnique<Value>(*list);
+ auto list_copy = std::make_unique<Value>(list->Clone());
- dv.Set("f", std::move(list));
+ ListValue* list_weak = dv.SetList("f", std::move(list));
EXPECT_NE(dv, *copy);
copy->Set("f", std::move(list_copy));
EXPECT_EQ(dv, *copy);
- original_list->Append(MakeUnique<Value>(true));
+ list_weak->Append(std::make_unique<Value>(true));
EXPECT_NE(dv, *copy);
// Check if Equals detects differences in only the keys.
- copy = MakeUnique<DictionaryValue>(dv);
+ copy = dv.CreateDeepCopy();
EXPECT_EQ(dv, *copy);
- copy->Remove("a", NULL);
+ copy->Remove("a", nullptr);
copy->SetBoolean("aa", false);
EXPECT_NE(dv, *copy);
}
-TEST(ValuesTest, StaticEquals) {
- std::unique_ptr<Value> null1(Value::CreateNullValue());
- std::unique_ptr<Value> null2(Value::CreateNullValue());
- EXPECT_TRUE(Value::Equals(null1.get(), null2.get()));
- EXPECT_TRUE(Value::Equals(NULL, NULL));
-
- std::unique_ptr<Value> i42(new Value(42));
- std::unique_ptr<Value> j42(new Value(42));
- std::unique_ptr<Value> i17(new Value(17));
- EXPECT_TRUE(Value::Equals(i42.get(), i42.get()));
- EXPECT_TRUE(Value::Equals(j42.get(), i42.get()));
- EXPECT_TRUE(Value::Equals(i42.get(), j42.get()));
- EXPECT_FALSE(Value::Equals(i42.get(), i17.get()));
- EXPECT_FALSE(Value::Equals(i42.get(), NULL));
- EXPECT_FALSE(Value::Equals(NULL, i42.get()));
-
- // NULL and Value::CreateNullValue() are intentionally different: We need
- // support for NULL as a return value for "undefined" without caring for
- // ownership of the pointer.
- EXPECT_FALSE(Value::Equals(null1.get(), NULL));
- EXPECT_FALSE(Value::Equals(NULL, null1.get()));
-}
-
TEST(ValuesTest, Comparisons) {
// Test None Values.
Value null1;
@@ -918,8 +1278,8 @@ TEST(ValuesTest, Comparisons) {
EXPECT_FALSE(string1 >= string2);
// Test Binary Values.
- Value binary1(std::vector<char>{0x01});
- Value binary2(std::vector<char>{0x02});
+ Value binary1(Value::BlobStorage{0x01});
+ Value binary2(Value::BlobStorage{0x02});
EXPECT_FALSE(binary1 == binary2);
EXPECT_NE(binary1, binary2);
EXPECT_LT(binary1, binary2);
@@ -972,8 +1332,15 @@ TEST(ValuesTest, Comparisons) {
EXPECT_FALSE(int_dict1 >= int_dict2);
// Test Values of different types.
- std::vector<Value> values = {null1, bool1, int1, double1,
- string1, binary1, int_dict1, int_list1};
+ std::vector<Value> values;
+ values.emplace_back(std::move(null1));
+ values.emplace_back(std::move(bool1));
+ values.emplace_back(std::move(int1));
+ values.emplace_back(std::move(double1));
+ values.emplace_back(std::move(string1));
+ values.emplace_back(std::move(binary1));
+ values.emplace_back(std::move(int_dict1));
+ values.emplace_back(std::move(int_list1));
for (size_t i = 0; i < values.size(); ++i) {
for (size_t j = i + 1; j < values.size(); ++j) {
EXPECT_FALSE(values[i] == values[j]);
@@ -988,73 +1355,55 @@ TEST(ValuesTest, Comparisons) {
TEST(ValuesTest, DeepCopyCovariantReturnTypes) {
DictionaryValue original_dict;
- std::unique_ptr<Value> scoped_null(Value::CreateNullValue());
- Value* original_null = scoped_null.get();
- original_dict.Set("null", std::move(scoped_null));
- std::unique_ptr<Value> scoped_bool(new Value(true));
- Value* original_bool = scoped_bool.get();
- original_dict.Set("bool", std::move(scoped_bool));
- std::unique_ptr<Value> scoped_int(new Value(42));
- Value* original_int = scoped_int.get();
- original_dict.Set("int", std::move(scoped_int));
- std::unique_ptr<Value> scoped_double(new Value(3.14));
- Value* original_double = scoped_double.get();
- original_dict.Set("double", std::move(scoped_double));
- std::unique_ptr<Value> scoped_string(new Value("hello"));
- Value* original_string = scoped_string.get();
- original_dict.Set("string", std::move(scoped_string));
- std::unique_ptr<Value> scoped_string16(new Value(ASCIIToUTF16("hello16")));
- Value* original_string16 = scoped_string16.get();
- original_dict.Set("string16", std::move(scoped_string16));
-
- std::vector<char> original_buffer(42, '!');
- std::unique_ptr<BinaryValue> scoped_binary(
- new BinaryValue(std::move(original_buffer)));
- Value* original_binary = scoped_binary.get();
- original_dict.Set("binary", std::move(scoped_binary));
-
- std::unique_ptr<ListValue> scoped_list(new ListValue());
- Value* original_list = scoped_list.get();
- std::unique_ptr<Value> scoped_list_element_0(new Value(0));
- scoped_list->Append(std::move(scoped_list_element_0));
- std::unique_ptr<Value> scoped_list_element_1(new Value(1));
- scoped_list->Append(std::move(scoped_list_element_1));
- original_dict.Set("list", std::move(scoped_list));
-
- auto copy_dict = MakeUnique<Value>(original_dict);
- auto copy_null = MakeUnique<Value>(*original_null);
- auto copy_bool = MakeUnique<Value>(*original_bool);
- auto copy_int = MakeUnique<Value>(*original_int);
- auto copy_double = MakeUnique<Value>(*original_double);
- auto copy_string = MakeUnique<Value>(*original_string);
- auto copy_string16 = MakeUnique<Value>(*original_string16);
- auto copy_binary = MakeUnique<Value>(*original_binary);
- auto copy_list = MakeUnique<Value>(*original_list);
+ Value* null_weak = original_dict.SetKey("null", Value());
+ Value* bool_weak = original_dict.SetKey("bool", Value(true));
+ Value* int_weak = original_dict.SetKey("int", Value(42));
+ Value* double_weak = original_dict.SetKey("double", Value(3.14));
+ Value* string_weak = original_dict.SetKey("string", Value("hello"));
+ Value* string16_weak =
+ original_dict.SetKey("string16", Value(ASCIIToUTF16("hello16")));
+ Value* binary_weak =
+ original_dict.SetKey("binary", Value(Value::BlobStorage(42, '!')));
+
+ Value::ListStorage storage;
+ storage.emplace_back(0);
+ storage.emplace_back(1);
+ Value* list_weak = original_dict.SetKey("list", Value(std::move(storage)));
+
+ auto copy_dict = std::make_unique<Value>(original_dict.Clone());
+ auto copy_null = std::make_unique<Value>(null_weak->Clone());
+ auto copy_bool = std::make_unique<Value>(bool_weak->Clone());
+ auto copy_int = std::make_unique<Value>(int_weak->Clone());
+ auto copy_double = std::make_unique<Value>(double_weak->Clone());
+ auto copy_string = std::make_unique<Value>(string_weak->Clone());
+ auto copy_string16 = std::make_unique<Value>(string16_weak->Clone());
+ auto copy_binary = std::make_unique<Value>(binary_weak->Clone());
+ auto copy_list = std::make_unique<Value>(list_weak->Clone());
EXPECT_EQ(original_dict, *copy_dict);
- EXPECT_EQ(*original_null, *copy_null);
- EXPECT_EQ(*original_bool, *copy_bool);
- EXPECT_EQ(*original_int, *copy_int);
- EXPECT_EQ(*original_double, *copy_double);
- EXPECT_EQ(*original_string, *copy_string);
- EXPECT_EQ(*original_string16, *copy_string16);
- EXPECT_EQ(*original_binary, *copy_binary);
- EXPECT_EQ(*original_list, *copy_list);
+ EXPECT_EQ(*null_weak, *copy_null);
+ EXPECT_EQ(*bool_weak, *copy_bool);
+ EXPECT_EQ(*int_weak, *copy_int);
+ EXPECT_EQ(*double_weak, *copy_double);
+ EXPECT_EQ(*string_weak, *copy_string);
+ EXPECT_EQ(*string16_weak, *copy_string16);
+ EXPECT_EQ(*binary_weak, *copy_binary);
+ EXPECT_EQ(*list_weak, *copy_list);
}
TEST(ValuesTest, RemoveEmptyChildren) {
- std::unique_ptr<DictionaryValue> root(new DictionaryValue);
+ auto root = std::make_unique<DictionaryValue>();
// Remove empty lists and dictionaries.
- root->Set("empty_dict", WrapUnique(new DictionaryValue));
- root->Set("empty_list", WrapUnique(new ListValue));
+ root->Set("empty_dict", std::make_unique<DictionaryValue>());
+ root->Set("empty_list", std::make_unique<ListValue>());
root->SetWithoutPathExpansion("a.b.c.d.e",
- WrapUnique(new DictionaryValue));
+ std::make_unique<DictionaryValue>());
root = root->DeepCopyWithoutEmptyChildren();
EXPECT_TRUE(root->empty());
// Make sure we don't prune too much.
root->SetBoolean("bool", true);
- root->Set("empty_dict", WrapUnique(new DictionaryValue));
+ root->Set("empty_dict", std::make_unique<DictionaryValue>());
root->SetString("empty_string", std::string());
root = root->DeepCopyWithoutEmptyChildren();
EXPECT_EQ(2U, root->size());
@@ -1066,22 +1415,22 @@ TEST(ValuesTest, RemoveEmptyChildren) {
// Nested test cases. These should all reduce back to the bool and string
// set above.
{
- root->Set("a.b.c.d.e", WrapUnique(new DictionaryValue));
+ root->Set("a.b.c.d.e", std::make_unique<DictionaryValue>());
root = root->DeepCopyWithoutEmptyChildren();
EXPECT_EQ(2U, root->size());
}
{
- std::unique_ptr<DictionaryValue> inner(new DictionaryValue);
- inner->Set("empty_dict", WrapUnique(new DictionaryValue));
- inner->Set("empty_list", WrapUnique(new ListValue));
+ auto inner = std::make_unique<DictionaryValue>();
+ inner->Set("empty_dict", std::make_unique<DictionaryValue>());
+ inner->Set("empty_list", std::make_unique<ListValue>());
root->Set("dict_with_empty_children", std::move(inner));
root = root->DeepCopyWithoutEmptyChildren();
EXPECT_EQ(2U, root->size());
}
{
- std::unique_ptr<ListValue> inner(new ListValue);
- inner->Append(WrapUnique(new DictionaryValue));
- inner->Append(WrapUnique(new ListValue));
+ auto inner = std::make_unique<ListValue>();
+ inner->Append(std::make_unique<DictionaryValue>());
+ inner->Append(std::make_unique<ListValue>());
root->Set("list_with_empty_children", std::move(inner));
root = root->DeepCopyWithoutEmptyChildren();
EXPECT_EQ(2U, root->size());
@@ -1089,13 +1438,13 @@ TEST(ValuesTest, RemoveEmptyChildren) {
// Nested with siblings.
{
- std::unique_ptr<ListValue> inner(new ListValue());
- inner->Append(WrapUnique(new DictionaryValue));
- inner->Append(WrapUnique(new ListValue));
+ auto inner = std::make_unique<ListValue>();
+ inner->Append(std::make_unique<DictionaryValue>());
+ inner->Append(std::make_unique<ListValue>());
root->Set("list_with_empty_children", std::move(inner));
- std::unique_ptr<DictionaryValue> inner2(new DictionaryValue);
- inner2->Set("empty_dict", WrapUnique(new DictionaryValue));
- inner2->Set("empty_list", WrapUnique(new ListValue));
+ auto inner2 = std::make_unique<DictionaryValue>();
+ inner2->Set("empty_dict", std::make_unique<DictionaryValue>());
+ inner2->Set("empty_list", std::make_unique<ListValue>());
root->Set("dict_with_empty_children", std::move(inner2));
root = root->DeepCopyWithoutEmptyChildren();
EXPECT_EQ(2U, root->size());
@@ -1103,10 +1452,10 @@ TEST(ValuesTest, RemoveEmptyChildren) {
// Make sure nested values don't get pruned.
{
- std::unique_ptr<ListValue> inner(new ListValue);
- std::unique_ptr<ListValue> inner2(new ListValue);
- inner2->Append(MakeUnique<Value>("hello"));
- inner->Append(WrapUnique(new DictionaryValue));
+ auto inner = std::make_unique<ListValue>();
+ auto inner2 = std::make_unique<ListValue>();
+ inner2->Append(std::make_unique<Value>("hello"));
+ inner->Append(std::make_unique<DictionaryValue>());
inner->Append(std::move(inner2));
root->Set("list_with_empty_children", std::move(inner));
root = root->DeepCopyWithoutEmptyChildren();
@@ -1204,7 +1553,7 @@ TEST(ValuesTest, DictionaryIterator) {
}
Value value1("value1");
- dict.Set("key1", MakeUnique<Value>(value1));
+ dict.SetKey("key1", value1.Clone());
bool seen1 = false;
for (DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) {
EXPECT_FALSE(seen1);
@@ -1215,7 +1564,7 @@ TEST(ValuesTest, DictionaryIterator) {
EXPECT_TRUE(seen1);
Value value2("value2");
- dict.Set("key2", MakeUnique<Value>(value2));
+ dict.SetKey("key2", value2.Clone());
bool seen2 = seen1 = false;
for (DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) {
if (it.key() == "key1") {
@@ -1234,6 +1583,43 @@ TEST(ValuesTest, DictionaryIterator) {
EXPECT_TRUE(seen2);
}
+TEST(ValuesTest, StdDictionaryIterator) {
+ DictionaryValue dict;
+ for (auto it = dict.begin(); it != dict.end(); ++it) {
+ ADD_FAILURE();
+ }
+
+ Value value1("value1");
+ dict.SetKey("key1", value1.Clone());
+ bool seen1 = false;
+ for (const auto& it : dict) {
+ EXPECT_FALSE(seen1);
+ EXPECT_EQ("key1", it.first);
+ EXPECT_EQ(value1, *it.second);
+ seen1 = true;
+ }
+ EXPECT_TRUE(seen1);
+
+ Value value2("value2");
+ dict.SetKey("key2", value2.Clone());
+ bool seen2 = seen1 = false;
+ for (const auto& it : dict) {
+ if (it.first == "key1") {
+ EXPECT_FALSE(seen1);
+ EXPECT_EQ(value1, *it.second);
+ seen1 = true;
+ } else if (it.first == "key2") {
+ EXPECT_FALSE(seen2);
+ EXPECT_EQ(value2, *it.second);
+ seen2 = true;
+ } else {
+ ADD_FAILURE();
+ }
+ }
+ EXPECT_TRUE(seen1);
+ EXPECT_TRUE(seen2);
+}
+
// DictionaryValue/ListValue's Get*() methods should accept NULL as an out-value
// and still return true/false based on success.
TEST(ValuesTest, GetWithNullOutValue) {
@@ -1244,279 +1630,293 @@ TEST(ValuesTest, GetWithNullOutValue) {
Value int_value(1234);
Value double_value(12.34567);
Value string_value("foo");
- BinaryValue binary_value(Value::Type::BINARY);
+ Value binary_value(Value::Type::BINARY);
DictionaryValue dict_value;
ListValue list_value;
- main_dict.Set("bool", MakeUnique<Value>(bool_value));
- main_dict.Set("int", MakeUnique<Value>(int_value));
- main_dict.Set("double", MakeUnique<Value>(double_value));
- main_dict.Set("string", MakeUnique<Value>(string_value));
- main_dict.Set("binary", MakeUnique<Value>(binary_value));
- main_dict.Set("dict", MakeUnique<Value>(dict_value));
- main_dict.Set("list", MakeUnique<Value>(list_value));
-
- main_list.Append(MakeUnique<Value>(bool_value));
- main_list.Append(MakeUnique<Value>(int_value));
- main_list.Append(MakeUnique<Value>(double_value));
- main_list.Append(MakeUnique<Value>(string_value));
- main_list.Append(MakeUnique<Value>(binary_value));
- main_list.Append(MakeUnique<Value>(dict_value));
- main_list.Append(MakeUnique<Value>(list_value));
-
- EXPECT_TRUE(main_dict.Get("bool", NULL));
- EXPECT_TRUE(main_dict.Get("int", NULL));
- EXPECT_TRUE(main_dict.Get("double", NULL));
- EXPECT_TRUE(main_dict.Get("string", NULL));
- EXPECT_TRUE(main_dict.Get("binary", NULL));
- EXPECT_TRUE(main_dict.Get("dict", NULL));
- EXPECT_TRUE(main_dict.Get("list", NULL));
- EXPECT_FALSE(main_dict.Get("DNE", NULL));
-
- EXPECT_TRUE(main_dict.GetBoolean("bool", NULL));
- EXPECT_FALSE(main_dict.GetBoolean("int", NULL));
- EXPECT_FALSE(main_dict.GetBoolean("double", NULL));
- EXPECT_FALSE(main_dict.GetBoolean("string", NULL));
- EXPECT_FALSE(main_dict.GetBoolean("binary", NULL));
- EXPECT_FALSE(main_dict.GetBoolean("dict", NULL));
- EXPECT_FALSE(main_dict.GetBoolean("list", NULL));
- EXPECT_FALSE(main_dict.GetBoolean("DNE", NULL));
-
- EXPECT_FALSE(main_dict.GetInteger("bool", NULL));
- EXPECT_TRUE(main_dict.GetInteger("int", NULL));
- EXPECT_FALSE(main_dict.GetInteger("double", NULL));
- EXPECT_FALSE(main_dict.GetInteger("string", NULL));
- EXPECT_FALSE(main_dict.GetInteger("binary", NULL));
- EXPECT_FALSE(main_dict.GetInteger("dict", NULL));
- EXPECT_FALSE(main_dict.GetInteger("list", NULL));
- EXPECT_FALSE(main_dict.GetInteger("DNE", NULL));
+ main_dict.SetKey("bool", bool_value.Clone());
+ main_dict.SetKey("int", int_value.Clone());
+ main_dict.SetKey("double", double_value.Clone());
+ main_dict.SetKey("string", string_value.Clone());
+ main_dict.SetKey("binary", binary_value.Clone());
+ main_dict.SetKey("dict", dict_value.Clone());
+ main_dict.SetKey("list", list_value.Clone());
+
+ main_list.Append(std::make_unique<Value>(bool_value.Clone()));
+ main_list.Append(std::make_unique<Value>(int_value.Clone()));
+ main_list.Append(std::make_unique<Value>(double_value.Clone()));
+ main_list.Append(std::make_unique<Value>(string_value.Clone()));
+ main_list.Append(std::make_unique<Value>(binary_value.Clone()));
+ main_list.Append(std::make_unique<Value>(dict_value.Clone()));
+ main_list.Append(std::make_unique<Value>(list_value.Clone()));
+
+ EXPECT_TRUE(main_dict.Get("bool", nullptr));
+ EXPECT_TRUE(main_dict.Get("int", nullptr));
+ EXPECT_TRUE(main_dict.Get("double", nullptr));
+ EXPECT_TRUE(main_dict.Get("string", nullptr));
+ EXPECT_TRUE(main_dict.Get("binary", nullptr));
+ EXPECT_TRUE(main_dict.Get("dict", nullptr));
+ EXPECT_TRUE(main_dict.Get("list", nullptr));
+ EXPECT_FALSE(main_dict.Get("DNE", nullptr));
+
+ EXPECT_TRUE(main_dict.GetBoolean("bool", nullptr));
+ EXPECT_FALSE(main_dict.GetBoolean("int", nullptr));
+ EXPECT_FALSE(main_dict.GetBoolean("double", nullptr));
+ EXPECT_FALSE(main_dict.GetBoolean("string", nullptr));
+ EXPECT_FALSE(main_dict.GetBoolean("binary", nullptr));
+ EXPECT_FALSE(main_dict.GetBoolean("dict", nullptr));
+ EXPECT_FALSE(main_dict.GetBoolean("list", nullptr));
+ EXPECT_FALSE(main_dict.GetBoolean("DNE", nullptr));
+
+ EXPECT_FALSE(main_dict.GetInteger("bool", nullptr));
+ EXPECT_TRUE(main_dict.GetInteger("int", nullptr));
+ EXPECT_FALSE(main_dict.GetInteger("double", nullptr));
+ EXPECT_FALSE(main_dict.GetInteger("string", nullptr));
+ EXPECT_FALSE(main_dict.GetInteger("binary", nullptr));
+ EXPECT_FALSE(main_dict.GetInteger("dict", nullptr));
+ EXPECT_FALSE(main_dict.GetInteger("list", nullptr));
+ EXPECT_FALSE(main_dict.GetInteger("DNE", nullptr));
// Both int and double values can be obtained from GetDouble.
- EXPECT_FALSE(main_dict.GetDouble("bool", NULL));
- EXPECT_TRUE(main_dict.GetDouble("int", NULL));
- EXPECT_TRUE(main_dict.GetDouble("double", NULL));
- EXPECT_FALSE(main_dict.GetDouble("string", NULL));
- EXPECT_FALSE(main_dict.GetDouble("binary", NULL));
- EXPECT_FALSE(main_dict.GetDouble("dict", NULL));
- EXPECT_FALSE(main_dict.GetDouble("list", NULL));
- EXPECT_FALSE(main_dict.GetDouble("DNE", NULL));
-
- EXPECT_FALSE(main_dict.GetString("bool", static_cast<std::string*>(NULL)));
- EXPECT_FALSE(main_dict.GetString("int", static_cast<std::string*>(NULL)));
- EXPECT_FALSE(main_dict.GetString("double", static_cast<std::string*>(NULL)));
- EXPECT_TRUE(main_dict.GetString("string", static_cast<std::string*>(NULL)));
- EXPECT_FALSE(main_dict.GetString("binary", static_cast<std::string*>(NULL)));
- EXPECT_FALSE(main_dict.GetString("dict", static_cast<std::string*>(NULL)));
- EXPECT_FALSE(main_dict.GetString("list", static_cast<std::string*>(NULL)));
- EXPECT_FALSE(main_dict.GetString("DNE", static_cast<std::string*>(NULL)));
-
- EXPECT_FALSE(main_dict.GetString("bool", static_cast<string16*>(NULL)));
- EXPECT_FALSE(main_dict.GetString("int", static_cast<string16*>(NULL)));
- EXPECT_FALSE(main_dict.GetString("double", static_cast<string16*>(NULL)));
- EXPECT_TRUE(main_dict.GetString("string", static_cast<string16*>(NULL)));
- EXPECT_FALSE(main_dict.GetString("binary", static_cast<string16*>(NULL)));
- EXPECT_FALSE(main_dict.GetString("dict", static_cast<string16*>(NULL)));
- EXPECT_FALSE(main_dict.GetString("list", static_cast<string16*>(NULL)));
- EXPECT_FALSE(main_dict.GetString("DNE", static_cast<string16*>(NULL)));
-
- EXPECT_FALSE(main_dict.GetBinary("bool", NULL));
- EXPECT_FALSE(main_dict.GetBinary("int", NULL));
- EXPECT_FALSE(main_dict.GetBinary("double", NULL));
- EXPECT_FALSE(main_dict.GetBinary("string", NULL));
- EXPECT_TRUE(main_dict.GetBinary("binary", NULL));
- EXPECT_FALSE(main_dict.GetBinary("dict", NULL));
- EXPECT_FALSE(main_dict.GetBinary("list", NULL));
- EXPECT_FALSE(main_dict.GetBinary("DNE", NULL));
-
- EXPECT_FALSE(main_dict.GetDictionary("bool", NULL));
- EXPECT_FALSE(main_dict.GetDictionary("int", NULL));
- EXPECT_FALSE(main_dict.GetDictionary("double", NULL));
- EXPECT_FALSE(main_dict.GetDictionary("string", NULL));
- EXPECT_FALSE(main_dict.GetDictionary("binary", NULL));
- EXPECT_TRUE(main_dict.GetDictionary("dict", NULL));
- EXPECT_FALSE(main_dict.GetDictionary("list", NULL));
- EXPECT_FALSE(main_dict.GetDictionary("DNE", NULL));
-
- EXPECT_FALSE(main_dict.GetList("bool", NULL));
- EXPECT_FALSE(main_dict.GetList("int", NULL));
- EXPECT_FALSE(main_dict.GetList("double", NULL));
- EXPECT_FALSE(main_dict.GetList("string", NULL));
- EXPECT_FALSE(main_dict.GetList("binary", NULL));
- EXPECT_FALSE(main_dict.GetList("dict", NULL));
- EXPECT_TRUE(main_dict.GetList("list", NULL));
- EXPECT_FALSE(main_dict.GetList("DNE", NULL));
-
- EXPECT_TRUE(main_dict.GetWithoutPathExpansion("bool", NULL));
- EXPECT_TRUE(main_dict.GetWithoutPathExpansion("int", NULL));
- EXPECT_TRUE(main_dict.GetWithoutPathExpansion("double", NULL));
- EXPECT_TRUE(main_dict.GetWithoutPathExpansion("string", NULL));
- EXPECT_TRUE(main_dict.GetWithoutPathExpansion("binary", NULL));
- EXPECT_TRUE(main_dict.GetWithoutPathExpansion("dict", NULL));
- EXPECT_TRUE(main_dict.GetWithoutPathExpansion("list", NULL));
- EXPECT_FALSE(main_dict.GetWithoutPathExpansion("DNE", NULL));
-
- EXPECT_TRUE(main_dict.GetBooleanWithoutPathExpansion("bool", NULL));
- EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("int", NULL));
- EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("double", NULL));
- EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("string", NULL));
- EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("binary", NULL));
- EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("dict", NULL));
- EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("list", NULL));
- EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("DNE", NULL));
-
- EXPECT_FALSE(main_dict.GetIntegerWithoutPathExpansion("bool", NULL));
- EXPECT_TRUE(main_dict.GetIntegerWithoutPathExpansion("int", NULL));
- EXPECT_FALSE(main_dict.GetIntegerWithoutPathExpansion("double", NULL));
- EXPECT_FALSE(main_dict.GetIntegerWithoutPathExpansion("string", NULL));
- EXPECT_FALSE(main_dict.GetIntegerWithoutPathExpansion("binary", NULL));
- EXPECT_FALSE(main_dict.GetIntegerWithoutPathExpansion("dict", NULL));
- EXPECT_FALSE(main_dict.GetIntegerWithoutPathExpansion("list", NULL));
- EXPECT_FALSE(main_dict.GetIntegerWithoutPathExpansion("DNE", NULL));
-
- EXPECT_FALSE(main_dict.GetDoubleWithoutPathExpansion("bool", NULL));
- EXPECT_TRUE(main_dict.GetDoubleWithoutPathExpansion("int", NULL));
- EXPECT_TRUE(main_dict.GetDoubleWithoutPathExpansion("double", NULL));
- EXPECT_FALSE(main_dict.GetDoubleWithoutPathExpansion("string", NULL));
- EXPECT_FALSE(main_dict.GetDoubleWithoutPathExpansion("binary", NULL));
- EXPECT_FALSE(main_dict.GetDoubleWithoutPathExpansion("dict", NULL));
- EXPECT_FALSE(main_dict.GetDoubleWithoutPathExpansion("list", NULL));
- EXPECT_FALSE(main_dict.GetDoubleWithoutPathExpansion("DNE", NULL));
+ EXPECT_FALSE(main_dict.GetDouble("bool", nullptr));
+ EXPECT_TRUE(main_dict.GetDouble("int", nullptr));
+ EXPECT_TRUE(main_dict.GetDouble("double", nullptr));
+ EXPECT_FALSE(main_dict.GetDouble("string", nullptr));
+ EXPECT_FALSE(main_dict.GetDouble("binary", nullptr));
+ EXPECT_FALSE(main_dict.GetDouble("dict", nullptr));
+ EXPECT_FALSE(main_dict.GetDouble("list", nullptr));
+ EXPECT_FALSE(main_dict.GetDouble("DNE", nullptr));
+
+ EXPECT_FALSE(main_dict.GetString("bool", static_cast<std::string*>(nullptr)));
+ EXPECT_FALSE(main_dict.GetString("int", static_cast<std::string*>(nullptr)));
+ EXPECT_FALSE(
+ main_dict.GetString("double", static_cast<std::string*>(nullptr)));
+ EXPECT_TRUE(
+ main_dict.GetString("string", static_cast<std::string*>(nullptr)));
+ EXPECT_FALSE(
+ main_dict.GetString("binary", static_cast<std::string*>(nullptr)));
+ EXPECT_FALSE(main_dict.GetString("dict", static_cast<std::string*>(nullptr)));
+ EXPECT_FALSE(main_dict.GetString("list", static_cast<std::string*>(nullptr)));
+ EXPECT_FALSE(main_dict.GetString("DNE", static_cast<std::string*>(nullptr)));
+
+ EXPECT_FALSE(main_dict.GetString("bool", static_cast<string16*>(nullptr)));
+ EXPECT_FALSE(main_dict.GetString("int", static_cast<string16*>(nullptr)));
+ EXPECT_FALSE(main_dict.GetString("double", static_cast<string16*>(nullptr)));
+ EXPECT_TRUE(main_dict.GetString("string", static_cast<string16*>(nullptr)));
+ EXPECT_FALSE(main_dict.GetString("binary", static_cast<string16*>(nullptr)));
+ EXPECT_FALSE(main_dict.GetString("dict", static_cast<string16*>(nullptr)));
+ EXPECT_FALSE(main_dict.GetString("list", static_cast<string16*>(nullptr)));
+ EXPECT_FALSE(main_dict.GetString("DNE", static_cast<string16*>(nullptr)));
+
+ EXPECT_FALSE(main_dict.GetBinary("bool", nullptr));
+ EXPECT_FALSE(main_dict.GetBinary("int", nullptr));
+ EXPECT_FALSE(main_dict.GetBinary("double", nullptr));
+ EXPECT_FALSE(main_dict.GetBinary("string", nullptr));
+ EXPECT_TRUE(main_dict.GetBinary("binary", nullptr));
+ EXPECT_FALSE(main_dict.GetBinary("dict", nullptr));
+ EXPECT_FALSE(main_dict.GetBinary("list", nullptr));
+ EXPECT_FALSE(main_dict.GetBinary("DNE", nullptr));
+
+ EXPECT_FALSE(main_dict.GetDictionary("bool", nullptr));
+ EXPECT_FALSE(main_dict.GetDictionary("int", nullptr));
+ EXPECT_FALSE(main_dict.GetDictionary("double", nullptr));
+ EXPECT_FALSE(main_dict.GetDictionary("string", nullptr));
+ EXPECT_FALSE(main_dict.GetDictionary("binary", nullptr));
+ EXPECT_TRUE(main_dict.GetDictionary("dict", nullptr));
+ EXPECT_FALSE(main_dict.GetDictionary("list", nullptr));
+ EXPECT_FALSE(main_dict.GetDictionary("DNE", nullptr));
+
+ EXPECT_FALSE(main_dict.GetList("bool", nullptr));
+ EXPECT_FALSE(main_dict.GetList("int", nullptr));
+ EXPECT_FALSE(main_dict.GetList("double", nullptr));
+ EXPECT_FALSE(main_dict.GetList("string", nullptr));
+ EXPECT_FALSE(main_dict.GetList("binary", nullptr));
+ EXPECT_FALSE(main_dict.GetList("dict", nullptr));
+ EXPECT_TRUE(main_dict.GetList("list", nullptr));
+ EXPECT_FALSE(main_dict.GetList("DNE", nullptr));
+
+ EXPECT_TRUE(main_dict.GetWithoutPathExpansion("bool", nullptr));
+ EXPECT_TRUE(main_dict.GetWithoutPathExpansion("int", nullptr));
+ EXPECT_TRUE(main_dict.GetWithoutPathExpansion("double", nullptr));
+ EXPECT_TRUE(main_dict.GetWithoutPathExpansion("string", nullptr));
+ EXPECT_TRUE(main_dict.GetWithoutPathExpansion("binary", nullptr));
+ EXPECT_TRUE(main_dict.GetWithoutPathExpansion("dict", nullptr));
+ EXPECT_TRUE(main_dict.GetWithoutPathExpansion("list", nullptr));
+ EXPECT_FALSE(main_dict.GetWithoutPathExpansion("DNE", nullptr));
+
+ EXPECT_TRUE(main_dict.GetBooleanWithoutPathExpansion("bool", nullptr));
+ EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("int", nullptr));
+ EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("double", nullptr));
+ EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("string", nullptr));
+ EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("binary", nullptr));
+ EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("dict", nullptr));
+ EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("list", nullptr));
+ EXPECT_FALSE(main_dict.GetBooleanWithoutPathExpansion("DNE", nullptr));
+
+ EXPECT_FALSE(main_dict.GetIntegerWithoutPathExpansion("bool", nullptr));
+ EXPECT_TRUE(main_dict.GetIntegerWithoutPathExpansion("int", nullptr));
+ EXPECT_FALSE(main_dict.GetIntegerWithoutPathExpansion("double", nullptr));
+ EXPECT_FALSE(main_dict.GetIntegerWithoutPathExpansion("string", nullptr));
+ EXPECT_FALSE(main_dict.GetIntegerWithoutPathExpansion("binary", nullptr));
+ EXPECT_FALSE(main_dict.GetIntegerWithoutPathExpansion("dict", nullptr));
+ EXPECT_FALSE(main_dict.GetIntegerWithoutPathExpansion("list", nullptr));
+ EXPECT_FALSE(main_dict.GetIntegerWithoutPathExpansion("DNE", nullptr));
+
+ EXPECT_FALSE(main_dict.GetDoubleWithoutPathExpansion("bool", nullptr));
+ EXPECT_TRUE(main_dict.GetDoubleWithoutPathExpansion("int", nullptr));
+ EXPECT_TRUE(main_dict.GetDoubleWithoutPathExpansion("double", nullptr));
+ EXPECT_FALSE(main_dict.GetDoubleWithoutPathExpansion("string", nullptr));
+ EXPECT_FALSE(main_dict.GetDoubleWithoutPathExpansion("binary", nullptr));
+ EXPECT_FALSE(main_dict.GetDoubleWithoutPathExpansion("dict", nullptr));
+ EXPECT_FALSE(main_dict.GetDoubleWithoutPathExpansion("list", nullptr));
+ EXPECT_FALSE(main_dict.GetDoubleWithoutPathExpansion("DNE", nullptr));
EXPECT_FALSE(main_dict.GetStringWithoutPathExpansion(
- "bool", static_cast<std::string*>(NULL)));
+ "bool", static_cast<std::string*>(nullptr)));
EXPECT_FALSE(main_dict.GetStringWithoutPathExpansion(
- "int", static_cast<std::string*>(NULL)));
+ "int", static_cast<std::string*>(nullptr)));
EXPECT_FALSE(main_dict.GetStringWithoutPathExpansion(
- "double", static_cast<std::string*>(NULL)));
+ "double", static_cast<std::string*>(nullptr)));
EXPECT_TRUE(main_dict.GetStringWithoutPathExpansion(
- "string", static_cast<std::string*>(NULL)));
+ "string", static_cast<std::string*>(nullptr)));
EXPECT_FALSE(main_dict.GetStringWithoutPathExpansion(
- "binary", static_cast<std::string*>(NULL)));
+ "binary", static_cast<std::string*>(nullptr)));
EXPECT_FALSE(main_dict.GetStringWithoutPathExpansion(
- "dict", static_cast<std::string*>(NULL)));
+ "dict", static_cast<std::string*>(nullptr)));
EXPECT_FALSE(main_dict.GetStringWithoutPathExpansion(
- "list", static_cast<std::string*>(NULL)));
+ "list", static_cast<std::string*>(nullptr)));
EXPECT_FALSE(main_dict.GetStringWithoutPathExpansion(
- "DNE", static_cast<std::string*>(NULL)));
+ "DNE", static_cast<std::string*>(nullptr)));
EXPECT_FALSE(main_dict.GetStringWithoutPathExpansion(
- "bool", static_cast<string16*>(NULL)));
+ "bool", static_cast<string16*>(nullptr)));
EXPECT_FALSE(main_dict.GetStringWithoutPathExpansion(
- "int", static_cast<string16*>(NULL)));
+ "int", static_cast<string16*>(nullptr)));
EXPECT_FALSE(main_dict.GetStringWithoutPathExpansion(
- "double", static_cast<string16*>(NULL)));
+ "double", static_cast<string16*>(nullptr)));
EXPECT_TRUE(main_dict.GetStringWithoutPathExpansion(
- "string", static_cast<string16*>(NULL)));
+ "string", static_cast<string16*>(nullptr)));
EXPECT_FALSE(main_dict.GetStringWithoutPathExpansion(
- "binary", static_cast<string16*>(NULL)));
+ "binary", static_cast<string16*>(nullptr)));
EXPECT_FALSE(main_dict.GetStringWithoutPathExpansion(
- "dict", static_cast<string16*>(NULL)));
+ "dict", static_cast<string16*>(nullptr)));
EXPECT_FALSE(main_dict.GetStringWithoutPathExpansion(
- "list", static_cast<string16*>(NULL)));
+ "list", static_cast<string16*>(nullptr)));
EXPECT_FALSE(main_dict.GetStringWithoutPathExpansion(
- "DNE", static_cast<string16*>(NULL)));
+ "DNE", static_cast<string16*>(nullptr)));
// There is no GetBinaryWithoutPathExpansion for some reason, but if there
// were it should be tested here...
- EXPECT_FALSE(main_dict.GetDictionaryWithoutPathExpansion("bool", NULL));
- EXPECT_FALSE(main_dict.GetDictionaryWithoutPathExpansion("int", NULL));
- EXPECT_FALSE(main_dict.GetDictionaryWithoutPathExpansion("double", NULL));
- EXPECT_FALSE(main_dict.GetDictionaryWithoutPathExpansion("string", NULL));
- EXPECT_FALSE(main_dict.GetDictionaryWithoutPathExpansion("binary", NULL));
- EXPECT_TRUE(main_dict.GetDictionaryWithoutPathExpansion("dict", NULL));
- EXPECT_FALSE(main_dict.GetDictionaryWithoutPathExpansion("list", NULL));
- EXPECT_FALSE(main_dict.GetDictionaryWithoutPathExpansion("DNE", NULL));
-
- EXPECT_FALSE(main_dict.GetListWithoutPathExpansion("bool", NULL));
- EXPECT_FALSE(main_dict.GetListWithoutPathExpansion("int", NULL));
- EXPECT_FALSE(main_dict.GetListWithoutPathExpansion("double", NULL));
- EXPECT_FALSE(main_dict.GetListWithoutPathExpansion("string", NULL));
- EXPECT_FALSE(main_dict.GetListWithoutPathExpansion("binary", NULL));
- EXPECT_FALSE(main_dict.GetListWithoutPathExpansion("dict", NULL));
- EXPECT_TRUE(main_dict.GetListWithoutPathExpansion("list", NULL));
- EXPECT_FALSE(main_dict.GetListWithoutPathExpansion("DNE", NULL));
-
- EXPECT_TRUE(main_list.Get(0, NULL));
- EXPECT_TRUE(main_list.Get(1, NULL));
- EXPECT_TRUE(main_list.Get(2, NULL));
- EXPECT_TRUE(main_list.Get(3, NULL));
- EXPECT_TRUE(main_list.Get(4, NULL));
- EXPECT_TRUE(main_list.Get(5, NULL));
- EXPECT_TRUE(main_list.Get(6, NULL));
- EXPECT_FALSE(main_list.Get(7, NULL));
-
- EXPECT_TRUE(main_list.GetBoolean(0, NULL));
- EXPECT_FALSE(main_list.GetBoolean(1, NULL));
- EXPECT_FALSE(main_list.GetBoolean(2, NULL));
- EXPECT_FALSE(main_list.GetBoolean(3, NULL));
- EXPECT_FALSE(main_list.GetBoolean(4, NULL));
- EXPECT_FALSE(main_list.GetBoolean(5, NULL));
- EXPECT_FALSE(main_list.GetBoolean(6, NULL));
- EXPECT_FALSE(main_list.GetBoolean(7, NULL));
-
- EXPECT_FALSE(main_list.GetInteger(0, NULL));
- EXPECT_TRUE(main_list.GetInteger(1, NULL));
- EXPECT_FALSE(main_list.GetInteger(2, NULL));
- EXPECT_FALSE(main_list.GetInteger(3, NULL));
- EXPECT_FALSE(main_list.GetInteger(4, NULL));
- EXPECT_FALSE(main_list.GetInteger(5, NULL));
- EXPECT_FALSE(main_list.GetInteger(6, NULL));
- EXPECT_FALSE(main_list.GetInteger(7, NULL));
-
- EXPECT_FALSE(main_list.GetDouble(0, NULL));
- EXPECT_TRUE(main_list.GetDouble(1, NULL));
- EXPECT_TRUE(main_list.GetDouble(2, NULL));
- EXPECT_FALSE(main_list.GetDouble(3, NULL));
- EXPECT_FALSE(main_list.GetDouble(4, NULL));
- EXPECT_FALSE(main_list.GetDouble(5, NULL));
- EXPECT_FALSE(main_list.GetDouble(6, NULL));
- EXPECT_FALSE(main_list.GetDouble(7, NULL));
-
- EXPECT_FALSE(main_list.GetString(0, static_cast<std::string*>(NULL)));
- EXPECT_FALSE(main_list.GetString(1, static_cast<std::string*>(NULL)));
- EXPECT_FALSE(main_list.GetString(2, static_cast<std::string*>(NULL)));
- EXPECT_TRUE(main_list.GetString(3, static_cast<std::string*>(NULL)));
- EXPECT_FALSE(main_list.GetString(4, static_cast<std::string*>(NULL)));
- EXPECT_FALSE(main_list.GetString(5, static_cast<std::string*>(NULL)));
- EXPECT_FALSE(main_list.GetString(6, static_cast<std::string*>(NULL)));
- EXPECT_FALSE(main_list.GetString(7, static_cast<std::string*>(NULL)));
-
- EXPECT_FALSE(main_list.GetString(0, static_cast<string16*>(NULL)));
- EXPECT_FALSE(main_list.GetString(1, static_cast<string16*>(NULL)));
- EXPECT_FALSE(main_list.GetString(2, static_cast<string16*>(NULL)));
- EXPECT_TRUE(main_list.GetString(3, static_cast<string16*>(NULL)));
- EXPECT_FALSE(main_list.GetString(4, static_cast<string16*>(NULL)));
- EXPECT_FALSE(main_list.GetString(5, static_cast<string16*>(NULL)));
- EXPECT_FALSE(main_list.GetString(6, static_cast<string16*>(NULL)));
- EXPECT_FALSE(main_list.GetString(7, static_cast<string16*>(NULL)));
-
- EXPECT_FALSE(main_list.GetBinary(0, NULL));
- EXPECT_FALSE(main_list.GetBinary(1, NULL));
- EXPECT_FALSE(main_list.GetBinary(2, NULL));
- EXPECT_FALSE(main_list.GetBinary(3, NULL));
- EXPECT_TRUE(main_list.GetBinary(4, NULL));
- EXPECT_FALSE(main_list.GetBinary(5, NULL));
- EXPECT_FALSE(main_list.GetBinary(6, NULL));
- EXPECT_FALSE(main_list.GetBinary(7, NULL));
-
- EXPECT_FALSE(main_list.GetDictionary(0, NULL));
- EXPECT_FALSE(main_list.GetDictionary(1, NULL));
- EXPECT_FALSE(main_list.GetDictionary(2, NULL));
- EXPECT_FALSE(main_list.GetDictionary(3, NULL));
- EXPECT_FALSE(main_list.GetDictionary(4, NULL));
- EXPECT_TRUE(main_list.GetDictionary(5, NULL));
- EXPECT_FALSE(main_list.GetDictionary(6, NULL));
- EXPECT_FALSE(main_list.GetDictionary(7, NULL));
-
- EXPECT_FALSE(main_list.GetList(0, NULL));
- EXPECT_FALSE(main_list.GetList(1, NULL));
- EXPECT_FALSE(main_list.GetList(2, NULL));
- EXPECT_FALSE(main_list.GetList(3, NULL));
- EXPECT_FALSE(main_list.GetList(4, NULL));
- EXPECT_FALSE(main_list.GetList(5, NULL));
- EXPECT_TRUE(main_list.GetList(6, NULL));
- EXPECT_FALSE(main_list.GetList(7, NULL));
+ EXPECT_FALSE(main_dict.GetDictionaryWithoutPathExpansion("bool", nullptr));
+ EXPECT_FALSE(main_dict.GetDictionaryWithoutPathExpansion("int", nullptr));
+ EXPECT_FALSE(main_dict.GetDictionaryWithoutPathExpansion("double", nullptr));
+ EXPECT_FALSE(main_dict.GetDictionaryWithoutPathExpansion("string", nullptr));
+ EXPECT_FALSE(main_dict.GetDictionaryWithoutPathExpansion("binary", nullptr));
+ EXPECT_TRUE(main_dict.GetDictionaryWithoutPathExpansion("dict", nullptr));
+ EXPECT_FALSE(main_dict.GetDictionaryWithoutPathExpansion("list", nullptr));
+ EXPECT_FALSE(main_dict.GetDictionaryWithoutPathExpansion("DNE", nullptr));
+
+ EXPECT_FALSE(main_dict.GetListWithoutPathExpansion("bool", nullptr));
+ EXPECT_FALSE(main_dict.GetListWithoutPathExpansion("int", nullptr));
+ EXPECT_FALSE(main_dict.GetListWithoutPathExpansion("double", nullptr));
+ EXPECT_FALSE(main_dict.GetListWithoutPathExpansion("string", nullptr));
+ EXPECT_FALSE(main_dict.GetListWithoutPathExpansion("binary", nullptr));
+ EXPECT_FALSE(main_dict.GetListWithoutPathExpansion("dict", nullptr));
+ EXPECT_TRUE(main_dict.GetListWithoutPathExpansion("list", nullptr));
+ EXPECT_FALSE(main_dict.GetListWithoutPathExpansion("DNE", nullptr));
+
+ EXPECT_TRUE(main_list.Get(0, nullptr));
+ EXPECT_TRUE(main_list.Get(1, nullptr));
+ EXPECT_TRUE(main_list.Get(2, nullptr));
+ EXPECT_TRUE(main_list.Get(3, nullptr));
+ EXPECT_TRUE(main_list.Get(4, nullptr));
+ EXPECT_TRUE(main_list.Get(5, nullptr));
+ EXPECT_TRUE(main_list.Get(6, nullptr));
+ EXPECT_FALSE(main_list.Get(7, nullptr));
+
+ EXPECT_TRUE(main_list.GetBoolean(0, nullptr));
+ EXPECT_FALSE(main_list.GetBoolean(1, nullptr));
+ EXPECT_FALSE(main_list.GetBoolean(2, nullptr));
+ EXPECT_FALSE(main_list.GetBoolean(3, nullptr));
+ EXPECT_FALSE(main_list.GetBoolean(4, nullptr));
+ EXPECT_FALSE(main_list.GetBoolean(5, nullptr));
+ EXPECT_FALSE(main_list.GetBoolean(6, nullptr));
+ EXPECT_FALSE(main_list.GetBoolean(7, nullptr));
+
+ EXPECT_FALSE(main_list.GetInteger(0, nullptr));
+ EXPECT_TRUE(main_list.GetInteger(1, nullptr));
+ EXPECT_FALSE(main_list.GetInteger(2, nullptr));
+ EXPECT_FALSE(main_list.GetInteger(3, nullptr));
+ EXPECT_FALSE(main_list.GetInteger(4, nullptr));
+ EXPECT_FALSE(main_list.GetInteger(5, nullptr));
+ EXPECT_FALSE(main_list.GetInteger(6, nullptr));
+ EXPECT_FALSE(main_list.GetInteger(7, nullptr));
+
+ EXPECT_FALSE(main_list.GetDouble(0, nullptr));
+ EXPECT_TRUE(main_list.GetDouble(1, nullptr));
+ EXPECT_TRUE(main_list.GetDouble(2, nullptr));
+ EXPECT_FALSE(main_list.GetDouble(3, nullptr));
+ EXPECT_FALSE(main_list.GetDouble(4, nullptr));
+ EXPECT_FALSE(main_list.GetDouble(5, nullptr));
+ EXPECT_FALSE(main_list.GetDouble(6, nullptr));
+ EXPECT_FALSE(main_list.GetDouble(7, nullptr));
+
+ EXPECT_FALSE(main_list.GetString(0, static_cast<std::string*>(nullptr)));
+ EXPECT_FALSE(main_list.GetString(1, static_cast<std::string*>(nullptr)));
+ EXPECT_FALSE(main_list.GetString(2, static_cast<std::string*>(nullptr)));
+ EXPECT_TRUE(main_list.GetString(3, static_cast<std::string*>(nullptr)));
+ EXPECT_FALSE(main_list.GetString(4, static_cast<std::string*>(nullptr)));
+ EXPECT_FALSE(main_list.GetString(5, static_cast<std::string*>(nullptr)));
+ EXPECT_FALSE(main_list.GetString(6, static_cast<std::string*>(nullptr)));
+ EXPECT_FALSE(main_list.GetString(7, static_cast<std::string*>(nullptr)));
+
+ EXPECT_FALSE(main_list.GetString(0, static_cast<string16*>(nullptr)));
+ EXPECT_FALSE(main_list.GetString(1, static_cast<string16*>(nullptr)));
+ EXPECT_FALSE(main_list.GetString(2, static_cast<string16*>(nullptr)));
+ EXPECT_TRUE(main_list.GetString(3, static_cast<string16*>(nullptr)));
+ EXPECT_FALSE(main_list.GetString(4, static_cast<string16*>(nullptr)));
+ EXPECT_FALSE(main_list.GetString(5, static_cast<string16*>(nullptr)));
+ EXPECT_FALSE(main_list.GetString(6, static_cast<string16*>(nullptr)));
+ EXPECT_FALSE(main_list.GetString(7, static_cast<string16*>(nullptr)));
+
+ EXPECT_FALSE(main_list.GetDictionary(0, nullptr));
+ EXPECT_FALSE(main_list.GetDictionary(1, nullptr));
+ EXPECT_FALSE(main_list.GetDictionary(2, nullptr));
+ EXPECT_FALSE(main_list.GetDictionary(3, nullptr));
+ EXPECT_FALSE(main_list.GetDictionary(4, nullptr));
+ EXPECT_TRUE(main_list.GetDictionary(5, nullptr));
+ EXPECT_FALSE(main_list.GetDictionary(6, nullptr));
+ EXPECT_FALSE(main_list.GetDictionary(7, nullptr));
+
+ EXPECT_FALSE(main_list.GetList(0, nullptr));
+ EXPECT_FALSE(main_list.GetList(1, nullptr));
+ EXPECT_FALSE(main_list.GetList(2, nullptr));
+ EXPECT_FALSE(main_list.GetList(3, nullptr));
+ EXPECT_FALSE(main_list.GetList(4, nullptr));
+ EXPECT_FALSE(main_list.GetList(5, nullptr));
+ EXPECT_TRUE(main_list.GetList(6, nullptr));
+ EXPECT_FALSE(main_list.GetList(7, nullptr));
+}
+
+TEST(ValuesTest, SelfSwap) {
+ base::Value test(1);
+ std::swap(test, test);
+ EXPECT_EQ(1, test.GetInt());
+}
+
+TEST(ValuesTest, FromToUniquePtrValue) {
+ std::unique_ptr<DictionaryValue> dict = std::make_unique<DictionaryValue>();
+ dict->SetString("name", "Froogle");
+ dict->SetString("url", "http://froogle.com");
+ Value dict_copy = dict->Clone();
+
+ Value dict_converted = Value::FromUniquePtrValue(std::move(dict));
+ EXPECT_EQ(dict_copy, dict_converted);
+
+ std::unique_ptr<Value> val =
+ Value::ToUniquePtrValue(std::move(dict_converted));
+ EXPECT_EQ(dict_copy, *val);
}
} // namespace base
diff --git a/base/version.cc b/base/version.cc
index ca97a84222..3a54607610 100644
--- a/base/version.cc
+++ b/base/version.cc
@@ -77,13 +77,11 @@ int CompareVersionComponents(const std::vector<uint32_t>& components1,
} // namespace
-Version::Version() {
-}
+Version::Version() = default;
Version::Version(const Version& other) = default;
-Version::~Version() {
-}
+Version::~Version() = default;
Version::Version(const std::string& version_str) {
std::vector<uint32_t> parsed;
@@ -93,7 +91,8 @@ Version::Version(const std::string& version_str) {
components_.swap(parsed);
}
-Version::Version(std::vector<uint32_t> components) : components_(components) {}
+Version::Version(std::vector<uint32_t> components)
+ : components_(std::move(components)) {}
bool Version::IsValid() const {
return (!components_.empty());
diff --git a/base/version_unittest.cc b/base/version_unittest.cc
index 4ca784fc11..285ca9cc43 100644
--- a/base/version_unittest.cc
+++ b/base/version_unittest.cc
@@ -91,24 +91,31 @@ TEST(VersionTest, Compare) {
const char* rhs;
int expected;
} cases[] = {
- {"1.0", "1.0", 0},
- {"1.0", "0.0", 1},
- {"1.0", "2.0", -1},
- {"1.0", "1.1", -1},
- {"1.1", "1.0", 1},
- {"1.0", "1.0.1", -1},
- {"1.1", "1.0.1", 1},
- {"1.1", "1.0.1", 1},
- {"1.0.0", "1.0", 0},
- {"1.0.3", "1.0.20", -1},
- {"11.0.10", "15.007.20011", -1},
- {"11.0.10", "15.5.28.130162", -1},
+ {"1.0", "1.0", 0},
+ {"1.0", "0.0", 1},
+ {"1.0", "2.0", -1},
+ {"1.0", "1.1", -1},
+ {"1.1", "1.0", 1},
+ {"1.0", "1.0.1", -1},
+ {"1.1", "1.0.1", 1},
+ {"1.1", "1.0.1", 1},
+ {"1.0.0", "1.0", 0},
+ {"1.0.3", "1.0.20", -1},
+ {"11.0.10", "15.007.20011", -1},
+ {"11.0.10", "15.5.28.130162", -1},
+ {"15.5.28.130162", "15.5.28.130162", 0},
};
for (size_t i = 0; i < arraysize(cases); ++i) {
base::Version lhs(cases[i].lhs);
base::Version rhs(cases[i].rhs);
EXPECT_EQ(lhs.CompareTo(rhs), cases[i].expected) <<
cases[i].lhs << " ? " << cases[i].rhs;
+ // CompareToWildcardString() should have same behavior as CompareTo() when
+ // no wildcards are present.
+ EXPECT_EQ(lhs.CompareToWildcardString(cases[i].rhs), cases[i].expected)
+ << cases[i].lhs << " ? " << cases[i].rhs;
+ EXPECT_EQ(rhs.CompareToWildcardString(cases[i].lhs), -cases[i].expected)
+ << cases[i].lhs << " ? " << cases[i].rhs;
// Test comparison operators
switch (cases[i].expected) {
diff --git a/base/vlog.cc b/base/vlog.cc
index c00e63185a..fbe18976fa 100644
--- a/base/vlog.cc
+++ b/base/vlog.cc
@@ -49,7 +49,7 @@ VlogInfo::VlogInfo(const std::string& v_switch,
const std::string& vmodule_switch,
int* min_log_level)
: min_log_level_(min_log_level) {
- DCHECK(min_log_level != NULL);
+ DCHECK_NE(min_log_level, nullptr);
int vlog_level = 0;
if (!v_switch.empty()) {
@@ -78,7 +78,7 @@ VlogInfo::VlogInfo(const std::string& v_switch,
}
}
-VlogInfo::~VlogInfo() {}
+VlogInfo::~VlogInfo() = default;
namespace {
diff --git a/build/android/gyp/util/build_utils.py b/build/android/gyp/util/build_utils.py
index abd2dfc80f..426de038d9 100644
--- a/build/android/gyp/util/build_utils.py
+++ b/build/android/gyp/util/build_utils.py
@@ -2,14 +2,16 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import ast
+"""Contains common helpers for GN action()s."""
+
+import collections
import contextlib
+import filecmp
import fnmatch
import json
import os
import pipes
import re
-import shlex
import shutil
import stat
import subprocess
@@ -17,6 +19,9 @@ import sys
import tempfile
import zipfile
+# Any new non-system import must be added to:
+# //build/config/android/internal_rules.gni
+
# Some clients do not add //build/android/gyp to PYTHONPATH.
import md5_check # pylint: disable=relative-import
@@ -25,19 +30,19 @@ import md5_check # pylint: disable=relative-import
# 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))
+sys.path.insert(0, os.path.join(os.path.dirname(__file__),
+ os.pardir, os.pardir))
-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:.*:<dir>_*:' +
- '!CVS:!thumbs.db:!picasa.ini:!*~:!*.d.stamp')
+# Definition copied from pylib/constants/__init__.py to avoid adding
+# a dependency on pylib.
+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)))
+
HERMETIC_TIMESTAMP = (2001, 1, 1, 0, 0, 0)
_HERMETIC_FILE_ATTR = (0644 << 16L)
@@ -80,11 +85,10 @@ def FindInDirectory(directory, filename_filter):
return files
-def FindInDirectories(directories, filename_filter):
- all_files = []
- for directory in directories:
- all_files.extend(FindInDirectory(directory, filename_filter))
- return all_files
+def ReadBuildVars(path):
+ """Parses a build_vars.txt into a dict."""
+ with open(path) as f:
+ return dict(l.rstrip().split('=', 1) for l in f)
def ParseGnList(gn_string):
@@ -128,9 +132,39 @@ def WriteJson(obj, path, only_if_changed=False):
outfile.write(new_dump)
-def ReadJson(path):
- with open(path, 'r') as jsonfile:
- return json.load(jsonfile)
+@contextlib.contextmanager
+def AtomicOutput(path, only_if_changed=True):
+ """Helper to prevent half-written outputs.
+
+ Args:
+ path: Path to the final output file, which will be written atomically.
+ only_if_changed: If True (the default), do not touch the filesystem
+ if the content has not changed.
+ Returns:
+ A python context manager that yelds a NamedTemporaryFile instance
+ that must be used by clients to write the data to. On exit, the
+ manager will try to replace the final output file with the
+ temporary one if necessary. The temporary file is always destroyed
+ on exit.
+ Example:
+ with build_utils.AtomicOutput(output_path) as tmp_file:
+ subprocess.check_call(['prog', '--output', tmp_file.name])
+ """
+ # Create in same directory to ensure same filesystem when moving.
+ with tempfile.NamedTemporaryFile(suffix=os.path.basename(path),
+ dir=os.path.dirname(path),
+ delete=False) as f:
+ try:
+ yield f
+
+ # file should be closed before comparison/move.
+ f.close()
+ if not (only_if_changed and os.path.exists(path) and
+ filecmp.cmp(f.name, path)):
+ shutil.move(f.name, path)
+ finally:
+ if os.path.exists(f.name):
+ os.unlink(f.name)
class CalledProcessError(Exception):
@@ -200,19 +234,14 @@ def IsTimeStale(output, inputs):
return False
-def IsDeviceReady():
- device_state = CheckOutput(['adb', 'get-state'])
- return device_state.strip() == 'device'
-
-
-def CheckZipPath(name):
+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):
+def _IsSymlink(zip_file, name):
zi = zip_file.getinfo(name)
# The two high-order bytes of ZipInfo.external_attr represent
@@ -234,20 +263,21 @@ def ExtractAll(zip_path, path=None, no_clobber=True, pattern=None,
with zipfile.ZipFile(zip_path) as z:
for name in z.namelist():
if name.endswith('/'):
+ MakeDirectory(os.path.join(path, name))
continue
if pattern is not None:
if not fnmatch.fnmatch(name, pattern):
continue
if predicate and not predicate(name):
continue
- CheckZipPath(name)
+ _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):
+ if _IsSymlink(z, name):
dest = os.path.join(path, name)
MakeDirectory(os.path.dirname(dest))
os.symlink(z.read(name), dest)
@@ -268,12 +298,12 @@ def AddToZipHermetic(zip_file, zip_path, src_path=None, data=None,
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
+ compress: Whether to enable compression. Default is taken from ZipFile
constructor.
"""
assert (src_path is None) != (data is None), (
'|src_path| and |data| are mutually exclusive.')
- CheckZipPath(zip_path)
+ _CheckZipPath(zip_path)
zipinfo = zipfile.ZipInfo(filename=zip_path, date_time=HERMETIC_TIMESTAMP)
zipinfo.external_attr = _HERMETIC_FILE_ATTR
@@ -300,13 +330,15 @@ def AddToZipHermetic(zip_file, zip_path, src_path=None, data=None,
zip_file.writestr(zipinfo, data, compress_type)
-def DoZip(inputs, output, base_dir=None):
+def DoZip(inputs, output, base_dir=None, compress_fn=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.
+ compress_fn: Applied to each input to determine whether or not to compress.
+ By default, items will be |zipfile.ZIP_STORED|.
"""
input_tuples = []
for tup in inputs:
@@ -318,16 +350,19 @@ def DoZip(inputs, output, base_dir=None):
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)
+ compress = compress_fn(zip_path) if compress_fn else None
+ AddToZipHermetic(outfile, zip_path, src_path=fs_path, compress=compress)
-def ZipDir(output, base_dir):
+def ZipDir(output, base_dir, compress_fn=None):
"""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)
+
+ with AtomicOutput(output) as f:
+ DoZip(inputs, f, base_dir, compress_fn=compress_fn)
def MatchesGlob(path, filters):
@@ -335,8 +370,16 @@ def MatchesGlob(path, filters):
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)
+def MergeZips(output, input_zips, path_transform=None):
+ """Combines all files from |input_zips| into |output|.
+
+ Args:
+ output: Path or ZipFile instance to add files to.
+ input_zips: Iterable of paths to zip files to merge.
+ path_transform: Called for each entry path. Returns a new path, or None to
+ skip the file.
+ """
+ path_transform = path_transform or (lambda p: p)
added_names = set()
output_is_already_open = not isinstance(output, basestring)
@@ -347,16 +390,19 @@ def MergeZips(output, inputs, exclude_patterns=None, path_transform=None):
out_zip = zipfile.ZipFile(output, 'w')
try:
- for in_file in inputs:
+ for in_file in input_zips:
with zipfile.ZipFile(in_file, 'r') as in_zip:
+ # ijar creates zips with null CRCs.
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)
+ dst_name = path_transform(info.filename)
+ if not dst_name:
+ continue
already_added = dst_name in added_names
- if not already_added and not MatchesGlob(dst_name, exclude_patterns):
+ if not already_added:
AddToZipHermetic(out_zip, dst_name, data=in_zip.read(info),
compress=info.compress_type != zipfile.ZIP_STORED)
added_names.add(dst_name)
@@ -365,65 +411,47 @@ def MergeZips(output, inputs, exclude_patterns=None, path_transform=None):
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.
+ There should be no cycles in the dependency graph (crashes if cycles exist).
Args:
- top: a list of the top level nodes
- deps_func: A function that takes a node and returns its direct dependencies.
+ top: A list of the top level nodes
+ deps_func: A function that takes a node and returns a list of 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():
+ # Find all deps depth-first, maintaining original order in the case of ties.
+ deps_map = collections.OrderedDict()
+ def discover(nodes):
+ for node in nodes:
+ if node in deps_map:
+ continue
+ deps = deps_func(node)
+ discover(deps)
+ deps_map[node] = deps
+
+ discover(top)
+ return deps_map.keys()
+
+
+def _ComputePythonDependencies():
"""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()
-
+ _ForceLazyModulesToLoad()
+ module_paths = (m.__file__ for m in sys.modules.itervalues()
+ if m is not None and hasattr(m, '__file__'))
abs_module_paths = map(os.path.abspath, module_paths)
- assert os.path.isabs(host_paths.DIR_SOURCE_ROOT)
+ assert os.path.isabs(DIR_SOURCE_ROOT)
non_system_module_paths = [
- p for p in abs_module_paths if p.startswith(host_paths.DIR_SOURCE_ROOT)]
+ p for p in abs_module_paths if p.startswith(DIR_SOURCE_ROOT)]
def ConvertPycToPy(s):
if s.endswith('.pyc'):
return s[:-1]
@@ -434,14 +462,7 @@ def GetPythonDependencies():
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():
+def _ForceLazyModulesToLoad():
"""Forces any lazily imported modules to fully load themselves.
Inspecting the modules' __file__ attribute causes lazily imported modules
@@ -472,7 +493,7 @@ 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
+ inputs = _ComputePythonDependencies() + inputs
MakeDirectory(os.path.dirname(depfile_path))
# Ninja does not support multiple outputs in depfiles.
with open(depfile_path, 'w') as depfile:
@@ -510,7 +531,8 @@ def ExpandFileArgs(args):
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)
+ with open(file_path) as f:
+ file_jsons[file_path] = json.load(f)
expansion = file_jsons[file_path]
for k in lookup_path[1:]:
@@ -538,12 +560,12 @@ def ReadSourcesList(sources_list_file_name):
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.
+ pass_changes=False, depfile_deps=None,
+ add_pydeps=True):
+ """Wraps md5_check.CallAndRecordIfStale() and writes a depfile if applicable.
- Depfiles and stamp files are automatically added to output_paths when present
- in the |options| argument. They are then created after |function| is called.
+ Depfiles 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
@@ -559,25 +581,19 @@ def CallAndWriteDepfileIfStale(function, options, record_path=None,
python_deps = None
if hasattr(options, 'depfile') and options.depfile:
- python_deps = GetPythonDependencies()
+ python_deps = _ComputePythonDependencies()
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)
+ all_depfile_deps = list(python_deps) if add_pydeps else []
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,
diff --git a/build/android/pylib/constants/__init__.py b/build/android/pylib/constants/__init__.py
index 916ee27556..2101b9c1ca 100644
--- a/build/android/pylib/constants/__init__.py
+++ b/build/android/pylib/constants/__init__.py
@@ -95,17 +95,14 @@ 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 = '24.0.2'
+ANDROID_SDK_VERSION = version_codes.OREO_MR1
+ANDROID_SDK_BUILD_TOOLS_VERSION = '27.0.3'
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')
+ 'third_party', 'android_ndk')
PROGUARD_ROOT = os.path.join(DIR_SOURCE_ROOT, 'third_party', 'proguard')
@@ -118,6 +115,15 @@ UPSTREAM_FLAKINESS_SERVER = 'test-results.appspot.com'
# TODO(jbudorick): Remove once unused.
DEVICE_LOCAL_PROPERTIES_PATH = '/data/local.prop'
+# Configure ubsan to print stack traces in the format understood by "stack" so
+# that they will be symbolized, and disable signal handlers because they
+# interfere with the breakpad and sandbox tests.
+# This value is duplicated in
+# base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
+UBSAN_OPTIONS = (
+ 'print_stacktrace=1 stack_trace_format=\'#%n pc %o %m\' '
+ 'handle_segv=0 handle_sigbus=0 handle_sigfpe=0')
+
# TODO(jbudorick): Rework this into testing/buildbot/
PYTHON_UNIT_TEST_SUITES = {
'pylib_py_unittests': {
@@ -135,6 +141,7 @@ PYTHON_UNIT_TEST_SUITES = {
'test_modules': [
'java_cpp_enum_tests',
'java_google_api_keys_tests',
+ 'extract_unwind_tables_tests',
]
},
}
@@ -146,77 +153,113 @@ VALID_TEST_TYPES = ['gtest', 'instrumentation', 'junit', 'linker', 'monkey',
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
+ """Set the BUILDTYPE environment variable.
+
+ NOTE: Using this function is deprecated, in favor of SetOutputDirectory(),
+ it is still maintained for a few scripts that typically call it
+ to implement their --release and --debug command-line options.
+ When writing a new script, consider supporting an --output-dir or
+ --chromium-output-dir option instead, and calling SetOutputDirectory()
+ instead.
-def SetBuildDirectory(build_directory):
- os.environ['CHROMIUM_OUT_DIR'] = build_directory
+ NOTE: If CHROMIUM_OUTPUT_DIR if defined, or if SetOutputDirectory() was
+ called previously, this will be completely ignored.
+ """
+ chromium_output_dir = os.environ.get('CHROMIUM_OUTPUT_DIR')
+ if chromium_output_dir:
+ logging.warning(
+ 'SetBuildType("%s") ignored since CHROMIUM_OUTPUT_DIR is already '
+ 'defined as (%s)', build_type, chromium_output_dir)
+ os.environ['BUILDTYPE'] = build_type
def SetOutputDirectory(output_directory):
+ """Set the Chromium output directory.
+
+ This must be called early by scripts that rely on GetOutDirectory() or
+ CheckOutputDirectory(). Typically by providing an --output-dir or
+ --chromium-output-dir option.
+ """
os.environ['CHROMIUM_OUTPUT_DIR'] = output_directory
-def GetOutDirectory(build_type=None):
- """Returns the out directory where the output binaries are built.
+# The message that is printed when the Chromium output directory cannot
+# be found. Note that CHROMIUM_OUT_DIR and BUILDTYPE are not mentioned
+# intentionally to encourage the use of CHROMIUM_OUTPUT_DIR instead.
+_MISSING_OUTPUT_DIR_MESSAGE = '\
+The Chromium output directory could not be found. Please use an option such as \
+--output-directory to provide it (see --help for details). Otherwise, \
+define the CHROMIUM_OUTPUT_DIR environment variable.'
+
+
+def GetOutDirectory():
+ """Returns the Chromium build output directory.
- Args:
- build_type: Build type, generally 'Debug' or 'Release'. Defaults to the
- globally set build type environment variable BUILDTYPE.
+ NOTE: This is determined in the following way:
+ - From a previous call to SetOutputDirectory()
+ - Otherwise, from the CHROMIUM_OUTPUT_DIR env variable, if it is defined.
+ - Otherwise, from the current Chromium source directory, and a previous
+ call to SetBuildType() or the BUILDTYPE env variable, in combination
+ with the optional CHROMIUM_OUT_DIR env variable.
"""
if 'CHROMIUM_OUTPUT_DIR' in os.environ:
return os.path.abspath(os.path.join(
DIR_SOURCE_ROOT, os.environ.get('CHROMIUM_OUTPUT_DIR')))
+ build_type = os.environ.get('BUILDTYPE')
+ if not build_type:
+ raise EnvironmentError(_MISSING_OUTPUT_DIR_MESSAGE)
+
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))
+ build_type))
def CheckOutputDirectory():
- """Checks that CHROMIUM_OUT_DIR or CHROMIUM_OUTPUT_DIR is set.
+ """Checks that the Chromium output directory is set, or can be found.
+
+ If it is not already set, this will also perform a little auto-detection:
+
+ - If the current directory contains a build.ninja file, use it as
+ the output directory.
- If neither are set, but the current working directory is a build directory,
- then CHROMIUM_OUTPUT_DIR is set to the current working directory.
+ - If CHROME_HEADLESS is defined in the environment (e.g. on a bot),
+ look if there is a single output directory under DIR_SOURCE_ROOT/out/,
+ and if so, use it as the output 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()
+ if output_dir:
+ return
+
+ build_type = os.environ.get('BUILDTYPE')
+ if build_type and len(build_type) > 1:
+ return
+
+ # 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)
+ return
+
+ # When running on bots, see if the output directory is obvious.
+ # TODO(http://crbug.com/833808): Get rid of this by ensuring bots always set
+ # CHROMIUM_OUTPUT_DIR correctly.
+ if os.environ.get('CHROME_HEADLESS'):
+ dirs = glob.glob(os.path.join(DIR_SOURCE_ROOT, 'out', '*', 'build.ninja'))
+ if len(dirs) == 1:
+ SetOutputDirectory(dirs[0])
+ return
+
+ raise Exception(
+ 'Chromium output directory not set, and CHROME_HEADLESS detected. ' +
+ 'However, multiple out dirs exist: %r' % dirs)
+
+ raise Exception(_MISSING_OUTPUT_DIR_MESSAGE)
# Exit codes
diff --git a/build/android/pylib/constants/host_paths.py b/build/android/pylib/constants/host_paths.py
index 98aa53dd0b..9ebf8d5054 100644
--- a/build/android/pylib/constants/host_paths.py
+++ b/build/android/pylib/constants/host_paths.py
@@ -6,6 +6,8 @@ import contextlib
import os
import sys
+from pylib import constants
+
DIR_SOURCE_ROOT = os.environ.get(
'CHECKOUT_SOURCE_ROOT',
os.path.abspath(os.path.join(os.path.dirname(__file__),
@@ -36,3 +38,56 @@ def SysPath(path, position=None):
sys.path.pop()
else:
sys.path.remove(path)
+
+
+# Map of CPU architecture name to (toolchain_name, binprefix) pairs.
+# TODO(digit): Use the build_vars.txt file generated by gn.
+_TOOL_ARCH_MAP = {
+ 'arm': ('arm-linux-androideabi-4.9', 'arm-linux-androideabi'),
+ 'arm64': ('aarch64-linux-android-4.9', 'aarch64-linux-android'),
+ 'x86': ('x86-4.9', 'i686-linux-android'),
+ 'x86_64': ('x86_64-4.9', 'x86_64-linux-android'),
+ 'x64': ('x86_64-4.9', 'x86_64-linux-android'),
+ 'mips': ('mipsel-linux-android-4.9', 'mipsel-linux-android'),
+}
+
+# Cache used to speed up the results of ToolPath()
+# Maps (arch, tool_name) pairs to fully qualified program paths.
+# Useful because ToolPath() is called repeatedly for demangling C++ symbols.
+_cached_tool_paths = {}
+
+
+def ToolPath(tool, cpu_arch):
+ """Return a fully qualifed path to an arch-specific toolchain program.
+
+ Args:
+ tool: Unprefixed toolchain program name (e.g. 'objdump')
+ cpu_arch: Target CPU architecture (e.g. 'arm64')
+ Returns:
+ Fully qualified path (e.g. ..../aarch64-linux-android-objdump')
+ Raises:
+ Exception if the toolchain could not be found.
+ """
+ tool_path = _cached_tool_paths.get((tool, cpu_arch))
+ if tool_path:
+ return tool_path
+
+ toolchain_source, toolchain_prefix = _TOOL_ARCH_MAP.get(
+ cpu_arch, (None, None))
+ if not toolchain_source:
+ raise Exception('Could not find tool chain for ' + cpu_arch)
+
+ toolchain_subdir = (
+ 'toolchains/%s/prebuilt/linux-x86_64/bin' % toolchain_source)
+
+ tool_path = os.path.join(constants.ANDROID_NDK_ROOT,
+ toolchain_subdir,
+ toolchain_prefix + '-' + tool)
+
+ _cached_tool_paths[(tool, cpu_arch)] = tool_path
+ return tool_path
+
+
+def GetAaptPath():
+ """Returns the path to the 'aapt' executable."""
+ return os.path.join(constants.ANDROID_SDK_TOOLS, 'aapt')
diff --git a/build/build_config.h b/build/build_config.h
index 5ee96b18a1..d669ea750e 100644
--- a/build/build_config.h
+++ b/build/build_config.h
@@ -82,6 +82,8 @@
#endif
#elif defined(_WIN32)
#define OS_WIN 1
+#elif defined(__Fuchsia__)
+#define OS_FUCHSIA 1
#elif defined(__FreeBSD__)
#define OS_FREEBSD 1
#elif defined(__NetBSD__)
@@ -92,13 +94,15 @@
#define OS_SOLARIS 1
#elif defined(__QNXNTO__)
#define OS_QNX 1
+#elif defined(_AIX)
+#define OS_AIX 1
+#elif defined(__asmjs__)
+#define OS_ASMJS
#else
#error Please add support for your platform in build/build_config.h
#endif
-
-#if defined(USE_OPENSSL_CERTS) && defined(USE_NSS_CERTS)
-#error Cannot use both OpenSSL and NSS for certificates
-#endif
+// NOTE: Adding a new port? Please follow
+// https://chromium.googlesource.com/chromium/src/+/master/docs/new_port_policy.md
// For access to standard BSD features, use OS_BSD instead of a
// more specific macro.
@@ -108,10 +112,10 @@
// 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_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)
+#if defined(OS_AIX) || defined(OS_ANDROID) || defined(OS_ASMJS) || \
+ defined(OS_FREEBSD) || defined(OS_LINUX) || defined(OS_MACOSX) || \
+ defined(OS_NACL) || defined(OS_NETBSD) || defined(OS_OPENBSD) || \
+ defined(OS_QNX) || defined(OS_SOLARIS)
#define OS_POSIX 1
#endif
@@ -154,21 +158,16 @@
#define ARCH_CPU_S390 1
#define ARCH_CPU_31_BITS 1
#define ARCH_CPU_BIG_ENDIAN 1
-#elif defined(__PPC64__) && defined(__BIG_ENDIAN__)
+#elif (defined(__PPC64__) || defined(__PPC__)) && 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__)
+#elif defined(__PPC64__)
#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
@@ -179,7 +178,7 @@
#define ARCH_CPU_ARM64 1
#define ARCH_CPU_64_BITS 1
#define ARCH_CPU_LITTLE_ENDIAN 1
-#elif defined(__pnacl__)
+#elif defined(__pnacl__) || defined(__asmjs__)
#define ARCH_CPU_32_BITS 1
#define ARCH_CPU_LITTLE_ENDIAN 1
#elif defined(__MIPSEL__)
@@ -194,6 +193,18 @@
#define ARCH_CPU_32_BITS 1
#define ARCH_CPU_LITTLE_ENDIAN 1
#endif
+#elif defined(__MIPSEB__)
+#if defined(__LP64__)
+#define ARCH_CPU_MIPS_FAMILY 1
+#define ARCH_CPU_MIPS64 1
+#define ARCH_CPU_64_BITS 1
+#define ARCH_CPU_BIG_ENDIAN 1
+#else
+#define ARCH_CPU_MIPS_FAMILY 1
+#define ARCH_CPU_MIPS 1
+#define ARCH_CPU_32_BITS 1
+#define ARCH_CPU_BIG_ENDIAN 1
+#endif
#else
#error Please add support for your architecture in build/build_config.h
#endif
@@ -201,12 +212,12 @@
// Type detection for wchar_t.
#if defined(OS_WIN)
#define WCHAR_T_IS_UTF16
-#elif defined(OS_POSIX) && defined(COMPILER_GCC) && \
- defined(__WCHAR_MAX__) && \
+#elif defined(OS_FUCHSIA)
+#define WCHAR_T_IS_UTF32
+#elif defined(OS_POSIX) && defined(COMPILER_GCC) && defined(__WCHAR_MAX__) && \
(__WCHAR_MAX__ == 0x7fffffff || __WCHAR_MAX__ == 0xffffffff)
#define WCHAR_T_IS_UTF32
-#elif defined(OS_POSIX) && defined(COMPILER_GCC) && \
- defined(__WCHAR_MAX__) && \
+#elif defined(OS_POSIX) && defined(COMPILER_GCC) && defined(__WCHAR_MAX__) && \
(__WCHAR_MAX__ == 0x7fff || __WCHAR_MAX__ == 0xffff)
// On Posix, we'll detect short wchar_t, but projects aren't guaranteed to
// compile in this mode (in particular, Chrome doesn't). This is intended for
diff --git a/build/gn_helpers.py b/build/gn_helpers.py
index 33cc5786d9..a9d1e2ee91 100644
--- a/build/gn_helpers.py
+++ b/build/gn_helpers.py
@@ -182,7 +182,7 @@ class GNValueParser(object):
- 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
+ variables preceded by $) are not supported and will be returned as
literals.
- GN lists ('[1, "asdf", 3]') will be converted to Python lists.
diff --git a/components/timers/DEPS b/components/timers/DEPS
deleted file mode 100644
index 413f57b96c..0000000000
--- a/components/timers/DEPS
+++ /dev/null
@@ -1,10 +0,0 @@
-include_rules = [
- # This directory is shared with Chrome OS, which only links against
- # base/. We don't want any other dependencies to creep in.
- "-build",
- "-content",
- "-library_loaders",
- "-net",
- "-third_party",
- "-url",
-] \ No newline at end of file
diff --git a/components/timers/alarm_timer_chromeos.cc b/components/timers/alarm_timer_chromeos.cc
index 601b411bd7..0b43134f1d 100644
--- a/components/timers/alarm_timer_chromeos.cc
+++ b/components/timers/alarm_timer_chromeos.cc
@@ -8,56 +8,51 @@
#include <sys/timerfd.h>
#include <algorithm>
+#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/debug/task_annotator.h"
#include "base/files/file_util.h"
#include "base/logging.h"
-#include "base/memory/ptr_util.h"
#include "base/pending_task.h"
#include "base/trace_event/trace_event.h"
namespace timers {
-AlarmTimer::AlarmTimer(bool retain_user_task, bool is_repeating)
- : base::Timer(retain_user_task, is_repeating),
- alarm_fd_(timerfd_create(CLOCK_REALTIME_ALARM, 0)),
- weak_factory_(this) {}
+SimpleAlarmTimer::SimpleAlarmTimer()
+ : alarm_fd_(timerfd_create(CLOCK_REALTIME_ALARM, 0)), weak_factory_(this) {}
-AlarmTimer::~AlarmTimer() {
- DCHECK(origin_task_runner_->RunsTasksOnCurrentThread());
+SimpleAlarmTimer::~SimpleAlarmTimer() {
+ DCHECK(origin_task_runner_->RunsTasksInCurrentSequence());
Stop();
}
-void AlarmTimer::Stop() {
- DCHECK(origin_task_runner_->RunsTasksOnCurrentThread());
+void SimpleAlarmTimer::Stop() {
+ DCHECK(origin_task_runner_->RunsTasksInCurrentSequence());
- if (!base::Timer::is_running())
+ if (!IsRunning())
return;
if (!CanWakeFromSuspend()) {
- base::Timer::Stop();
+ base::RetainingOneShotTimer::Stop();
return;
}
// Cancel any previous callbacks.
weak_factory_.InvalidateWeakPtrs();
- base::Timer::set_is_running(false);
+ base::RetainingOneShotTimer::set_is_running(false);
alarm_fd_watcher_.reset();
pending_task_.reset();
-
- if (!base::Timer::retain_user_task())
- base::Timer::set_user_task(base::Closure());
}
-void AlarmTimer::Reset() {
- DCHECK(origin_task_runner_->RunsTasksOnCurrentThread());
- DCHECK(!base::Timer::user_task().is_null());
+void SimpleAlarmTimer::Reset() {
+ DCHECK(origin_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!base::RetainingOneShotTimer::user_task().is_null());
if (!CanWakeFromSuspend()) {
- base::Timer::Reset();
+ base::RetainingOneShotTimer::Reset();
return;
}
@@ -66,15 +61,16 @@ void AlarmTimer::Reset() {
alarm_fd_watcher_.reset();
// Ensure that the delay is not negative.
- const base::TimeDelta delay =
- std::max(base::TimeDelta(), base::Timer::GetCurrentDelay());
+ const base::TimeDelta delay = std::max(
+ base::TimeDelta(), base::RetainingOneShotTimer::GetCurrentDelay());
// Set up the pending task.
- base::Timer::set_desired_run_time(
+ base::RetainingOneShotTimer::set_desired_run_time(
delay.is_zero() ? base::TimeTicks() : base::TimeTicks::Now() + delay);
- pending_task_ = base::MakeUnique<base::PendingTask>(
- base::Timer::posted_from(), base::Timer::user_task(),
- base::Timer::desired_run_time(), true /* nestable */);
+ pending_task_ = std::make_unique<base::PendingTask>(
+ base::RetainingOneShotTimer::posted_from(),
+ base::RetainingOneShotTimer::user_task(),
+ base::RetainingOneShotTimer::desired_run_time());
// Set |alarm_fd_| to be signaled when the delay expires. If the delay is
// zero, |alarm_fd_| will never be signaled. This overrides the previous
@@ -88,27 +84,28 @@ void AlarmTimer::Reset() {
PLOG(ERROR) << "Error while setting alarm time. Timer will not fire";
// The timer is running.
- base::Timer::set_is_running(true);
+ base::RetainingOneShotTimer::set_is_running(true);
// If the delay is zero, post the task now.
if (delay.is_zero()) {
origin_task_runner_->PostTask(
- FROM_HERE,
- base::Bind(&AlarmTimer::OnTimerFired, weak_factory_.GetWeakPtr()));
+ FROM_HERE, base::BindOnce(&SimpleAlarmTimer::OnTimerFired,
+ weak_factory_.GetWeakPtr()));
} else {
// Otherwise, if the delay is not zero, generate a tracing event to indicate
// that the task was posted and watch |alarm_fd_|.
- base::debug::TaskAnnotator().DidQueueTask("AlarmTimer::Reset",
- *pending_task_);
+ base::debug::TaskAnnotator().WillQueueTask("SimpleAlarmTimer::Reset",
+ pending_task_.get());
alarm_fd_watcher_ = base::FileDescriptorWatcher::WatchReadable(
- alarm_fd_, base::Bind(&AlarmTimer::OnAlarmFdReadableWithoutBlocking,
- weak_factory_.GetWeakPtr()));
+ alarm_fd_,
+ base::BindRepeating(&SimpleAlarmTimer::OnAlarmFdReadableWithoutBlocking,
+ weak_factory_.GetWeakPtr()));
}
}
-void AlarmTimer::OnAlarmFdReadableWithoutBlocking() {
- DCHECK(origin_task_runner_->RunsTasksOnCurrentThread());
- DCHECK(base::Timer::IsRunning());
+void SimpleAlarmTimer::OnAlarmFdReadableWithoutBlocking() {
+ DCHECK(origin_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(base::RetainingOneShotTimer::IsRunning());
// Read from |alarm_fd_| to ack the event.
char val[sizeof(uint64_t)];
@@ -118,52 +115,29 @@ void AlarmTimer::OnAlarmFdReadableWithoutBlocking() {
OnTimerFired();
}
-void AlarmTimer::OnTimerFired() {
- DCHECK(origin_task_runner_->RunsTasksOnCurrentThread());
- DCHECK(base::Timer::IsRunning());
+void SimpleAlarmTimer::OnTimerFired() {
+ DCHECK(origin_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(base::RetainingOneShotTimer::IsRunning());
DCHECK(pending_task_.get());
// Take ownership of the PendingTask to prevent it from being deleted if the
- // AlarmTimer is deleted.
+ // SimpleAlarmTimer is deleted.
const auto pending_user_task = std::move(pending_task_);
- base::WeakPtr<AlarmTimer> weak_ptr = weak_factory_.GetWeakPtr();
+ base::WeakPtr<SimpleAlarmTimer> weak_ptr = weak_factory_.GetWeakPtr();
// Run the task.
- TRACE_TASK_EXECUTION("AlarmTimer::OnTimerFired", *pending_user_task);
- base::debug::TaskAnnotator().RunTask("AlarmTimer::Reset",
+ TRACE_TASK_EXECUTION("SimpleAlarmTimer::OnTimerFired", *pending_user_task);
+ base::debug::TaskAnnotator().RunTask("SimpleAlarmTimer::Reset",
pending_user_task.get());
- // If the timer wasn't deleted, stopped or reset by the callback, reset or
- // stop it.
- if (weak_ptr.get()) {
- if (base::Timer::is_repeating())
- Reset();
- else
- Stop();
- }
+ // If the timer wasn't deleted, stopped or reset by the callback, stop it.
+ if (weak_ptr)
+ Stop();
}
-bool AlarmTimer::CanWakeFromSuspend() const {
+bool SimpleAlarmTimer::CanWakeFromSuspend() const {
return alarm_fd_ != -1;
}
-OneShotAlarmTimer::OneShotAlarmTimer() : AlarmTimer(false, false) {
-}
-
-OneShotAlarmTimer::~OneShotAlarmTimer() {
-}
-
-RepeatingAlarmTimer::RepeatingAlarmTimer() : AlarmTimer(true, true) {
-}
-
-RepeatingAlarmTimer::~RepeatingAlarmTimer() {
-}
-
-SimpleAlarmTimer::SimpleAlarmTimer() : AlarmTimer(true, false) {
-}
-
-SimpleAlarmTimer::~SimpleAlarmTimer() {
-}
-
} // namespace timers
diff --git a/components/timers/alarm_timer_chromeos.h b/components/timers/alarm_timer_chromeos.h
index d861aeeda0..1ff689ec06 100644
--- a/components/timers/alarm_timer_chromeos.h
+++ b/components/timers/alarm_timer_chromeos.h
@@ -9,7 +9,7 @@
#include "base/files/file_descriptor_watcher_posix.h"
#include "base/macros.h"
-#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/time.h"
@@ -24,23 +24,25 @@ namespace timers {
// suspended state. For example, this is useful for running tasks that are
// needed for maintaining network connectivity, like sending heartbeat messages.
// Currently, this feature is only available on Chrome OS systems running linux
-// version 3.11 or higher. On all other platforms, the AlarmTimer behaves
+// version 3.11 or higher. On all other platforms, the SimpleAlarmTimer behaves
// exactly the same way as a regular Timer.
//
-// An AlarmTimer instance can only be used from the sequence on which it was
-// instantiated. Start() and Stop() must be called from a thread that supports
-// FileDescriptorWatcher.
-class AlarmTimer : public base::Timer {
+// A SimpleAlarmTimer instance can only be used from the sequence on which it
+// was instantiated. Start() and Stop() must be called from a thread that
+// supports FileDescriptorWatcher.
+//
+// A SimpleAlarmTimer only fires once but remembers the task that it was given
+// even after it has fired. Useful if you want to run the same task multiple
+// times but not at a regular interval.
+class SimpleAlarmTimer : public base::RetainingOneShotTimer {
public:
- ~AlarmTimer() override;
+ SimpleAlarmTimer();
+ ~SimpleAlarmTimer() override;
// Timer overrides.
void Stop() override;
void Reset() override;
- protected:
- AlarmTimer(bool retain_user_task, bool is_repeating);
-
private:
// Called when |alarm_fd_| is readable without blocking. Reads data from
// |alarm_fd_| and calls OnTimerFired().
@@ -70,37 +72,9 @@ class AlarmTimer : public base::Timer {
std::unique_ptr<base::PendingTask> pending_task_;
// Used to invalidate pending callbacks.
- base::WeakPtrFactory<AlarmTimer> weak_factory_;
-
- DISALLOW_COPY_AND_ASSIGN(AlarmTimer);
-};
+ base::WeakPtrFactory<SimpleAlarmTimer> weak_factory_;
-// As its name suggests, a OneShotAlarmTimer runs a given task once. It does
-// not remember the task that was given to it after it has fired and does not
-// repeat. Useful for fire-and-forget tasks.
-class OneShotAlarmTimer : public AlarmTimer {
- public:
- OneShotAlarmTimer();
- ~OneShotAlarmTimer() override;
-};
-
-// A RepeatingAlarmTimer takes a task and delay and repeatedly runs the task
-// using the specified delay as an interval between the runs until it is
-// explicitly stopped. It remembers both the task and the delay it was given
-// after it fires.
-class RepeatingAlarmTimer : public AlarmTimer {
- public:
- RepeatingAlarmTimer();
- ~RepeatingAlarmTimer() override;
-};
-
-// A SimpleAlarmTimer only fires once but remembers the task that it was given
-// even after it has fired. Useful if you want to run the same task multiple
-// times but not at a regular interval.
-class SimpleAlarmTimer : public AlarmTimer {
- public:
- SimpleAlarmTimer();
- ~SimpleAlarmTimer() override;
+ DISALLOW_COPY_AND_ASSIGN(SimpleAlarmTimer);
};
} // namespace timers
diff --git a/crypto/apple_keychain.h b/crypto/apple_keychain.h
index 1037b7eccd..01f8d285e1 100644
--- a/crypto/apple_keychain.h
+++ b/crypto/apple_keychain.h
@@ -11,14 +11,14 @@
#include "build/build_config.h"
#include "crypto/crypto_export.h"
-#if defined (OS_IOS)
-typedef void* SecKeychainRef;
-typedef void* SecKeychainItemRef;
-typedef void SecKeychainAttributeList;
-#endif
-
namespace crypto {
+#if defined(OS_IOS)
+using AppleSecKeychainItemRef = void*;
+#else
+using AppleSecKeychainItemRef = SecKeychainItemRef;
+#endif
+
// Wraps the KeychainServices API in a very thin layer, to allow it to be
// mocked out for testing.
@@ -32,72 +32,26 @@ class CRYPTO_EXPORT AppleKeychain {
AppleKeychain();
virtual ~AppleKeychain();
- virtual OSStatus FindGenericPassword(CFTypeRef keychainOrArray,
- UInt32 serviceNameLength,
+ virtual OSStatus FindGenericPassword(UInt32 serviceNameLength,
const char* serviceName,
UInt32 accountNameLength,
const char* accountName,
UInt32* passwordLength,
void** passwordData,
- SecKeychainItemRef* itemRef) const;
+ AppleSecKeychainItemRef* itemRef) const;
- virtual OSStatus ItemFreeContent(SecKeychainAttributeList* attrList,
- void* data) const;
+ virtual OSStatus ItemFreeContent(void* data) const;
- virtual OSStatus AddGenericPassword(SecKeychainRef keychain,
- UInt32 serviceNameLength,
+ virtual OSStatus AddGenericPassword(UInt32 serviceNameLength,
const char* serviceName,
UInt32 accountNameLength,
const char* accountName,
UInt32 passwordLength,
const void* passwordData,
- SecKeychainItemRef* itemRef) const;
+ AppleSecKeychainItemRef* itemRef) const;
#if !defined(OS_IOS)
- virtual OSStatus ItemCopyAttributesAndData(
- SecKeychainItemRef itemRef,
- SecKeychainAttributeInfo* info,
- SecItemClass* itemClass,
- SecKeychainAttributeList** attrList,
- UInt32* length,
- void** outData) const;
-
- virtual OSStatus ItemModifyAttributesAndData(
- SecKeychainItemRef itemRef,
- const SecKeychainAttributeList* attrList,
- UInt32 length,
- const void* data) const;
-
- virtual OSStatus ItemFreeAttributesAndData(SecKeychainAttributeList* attrList,
- void* data) const;
-
- virtual OSStatus ItemDelete(SecKeychainItemRef itemRef) const;
-
- virtual OSStatus SearchCreateFromAttributes(
- CFTypeRef keychainOrArray,
- SecItemClass itemClass,
- const SecKeychainAttributeList* attrList,
- SecKeychainSearchRef* searchRef) const;
-
- virtual OSStatus SearchCopyNext(SecKeychainSearchRef searchRef,
- SecKeychainItemRef* itemRef) const;
-
- virtual OSStatus AddInternetPassword(SecKeychainRef keychain,
- UInt32 serverNameLength,
- const char* serverName,
- UInt32 securityDomainLength,
- const char* securityDomain,
- UInt32 accountNameLength,
- const char* accountName,
- UInt32 pathLength, const char* path,
- UInt16 port, SecProtocolType protocol,
- SecAuthenticationType authenticationType,
- UInt32 passwordLength,
- const void* passwordData,
- SecKeychainItemRef* itemRef) const;
-
- // Calls CFRelease on the given ref, after checking that |ref| is non-NULL.
- virtual void Free(CFTypeRef ref) const;
+ virtual OSStatus ItemDelete(AppleSecKeychainItemRef itemRef) const;
#endif // !defined(OS_IOS)
private:
diff --git a/crypto/ec_private_key.h b/crypto/ec_private_key.h
index 432019be5d..c087d238f9 100644
--- a/crypto/ec_private_key.h
+++ b/crypto/ec_private_key.h
@@ -47,8 +47,7 @@ class CRYPTO_EXPORT ECPrivateKey {
// This function is deprecated. Use CreateFromPrivateKeyInfo for new code.
// See https://crbug.com/603319.
static std::unique_ptr<ECPrivateKey> CreateFromEncryptedPrivateKeyInfo(
- const std::vector<uint8_t>& encrypted_private_key_info,
- const std::vector<uint8_t>& subject_public_key_info);
+ const std::vector<uint8_t>& encrypted_private_key_info);
// Returns a copy of the object.
std::unique_ptr<ECPrivateKey> Copy() const;
diff --git a/crypto/hmac.cc b/crypto/hmac.cc
index bf89e182d9..bb47081c7f 100644
--- a/crypto/hmac.cc
+++ b/crypto/hmac.cc
@@ -49,16 +49,11 @@ bool HMAC::Init(const unsigned char* key, size_t key_length) {
return true;
}
-bool HMAC::Init(SymmetricKey* key) {
- std::string raw_key;
- bool result = key->GetRawKey(&raw_key) && Init(raw_key);
- // Zero out key copy. This might get optimized away, but one can hope.
- // Using std::string to store key info at all is a larger problem.
- std::fill(raw_key.begin(), raw_key.end(), 0);
- return result;
+bool HMAC::Init(const SymmetricKey* key) {
+ return Init(key->key());
}
-bool HMAC::Sign(const base::StringPiece& data,
+bool HMAC::Sign(base::StringPiece data,
unsigned char* digest,
size_t digest_length) const {
DCHECK(initialized_);
@@ -70,15 +65,14 @@ bool HMAC::Sign(const base::StringPiece& data,
data.size(), result.safe_buffer(), nullptr);
}
-bool HMAC::Verify(const base::StringPiece& data,
- const base::StringPiece& digest) const {
+bool HMAC::Verify(base::StringPiece data, base::StringPiece digest) const {
if (digest.size() != DigestLength())
return false;
return VerifyTruncated(data, digest);
}
-bool HMAC::VerifyTruncated(const base::StringPiece& data,
- const base::StringPiece& digest) const {
+bool HMAC::VerifyTruncated(base::StringPiece data,
+ base::StringPiece digest) const {
if (digest.empty())
return false;
size_t digest_length = DigestLength();
diff --git a/crypto/hmac.h b/crypto/hmac.h
index 24213338cc..760a97341a 100644
--- a/crypto/hmac.h
+++ b/crypto/hmac.h
@@ -53,11 +53,11 @@ class CRYPTO_EXPORT HMAC {
// Initializes this instance using |key|. Call Init
// only once. It returns false on the second or later calls.
- bool Init(SymmetricKey* key) WARN_UNUSED_RESULT;
+ bool Init(const SymmetricKey* key) WARN_UNUSED_RESULT;
// Initializes this instance using |key|. Call Init only once. It returns
// false on the second or later calls.
- bool Init(const base::StringPiece& key) WARN_UNUSED_RESULT {
+ bool Init(base::StringPiece key) WARN_UNUSED_RESULT {
return Init(reinterpret_cast<const unsigned char*>(key.data()),
key.size());
}
@@ -65,7 +65,8 @@ class CRYPTO_EXPORT HMAC {
// Calculates the HMAC for the message in |data| using the algorithm supplied
// to the constructor and the key supplied to the Init method. The HMAC is
// returned in |digest|, which has |digest_length| bytes of storage available.
- bool Sign(const base::StringPiece& data, unsigned char* digest,
+ bool Sign(base::StringPiece data,
+ unsigned char* digest,
size_t digest_length) const WARN_UNUSED_RESULT;
// Verifies that the HMAC for the message in |data| equals the HMAC provided
@@ -75,14 +76,13 @@ class CRYPTO_EXPORT HMAC {
// comparisons may result in side-channel disclosures, such as timing, that
// undermine the cryptographic integrity. |digest| must be exactly
// |DigestLength()| bytes long.
- bool Verify(const base::StringPiece& data,
- const base::StringPiece& digest) const WARN_UNUSED_RESULT;
+ bool Verify(base::StringPiece data,
+ base::StringPiece digest) const WARN_UNUSED_RESULT;
// Verifies a truncated HMAC, behaving identical to Verify(), except
// that |digest| is allowed to be smaller than |DigestLength()|.
- bool VerifyTruncated(
- const base::StringPiece& data,
- const base::StringPiece& digest) const WARN_UNUSED_RESULT;
+ bool VerifyTruncated(base::StringPiece data,
+ base::StringPiece digest) const WARN_UNUSED_RESULT;
private:
HashAlgorithm hash_alg_;
diff --git a/crypto/nss_crypto_module_delegate.h b/crypto/nss_crypto_module_delegate.h
index cf08f2859f..cb87070516 100644
--- a/crypto/nss_crypto_module_delegate.h
+++ b/crypto/nss_crypto_module_delegate.h
@@ -7,8 +7,7 @@
#include <string>
-#include "base/callback_forward.h"
-#include "crypto/scoped_nss_types.h"
+#include "base/memory/ref_counted.h"
namespace crypto {
@@ -17,9 +16,9 @@ namespace crypto {
// user data argument (|wincx|) to relevant NSS functions, which the global
// password handler will call to do the actual work. This delegate should only
// be used in NSS calls on worker threads due to the blocking nature.
-class CryptoModuleBlockingPasswordDelegate {
+class CryptoModuleBlockingPasswordDelegate
+ : public base::RefCountedThreadSafe<CryptoModuleBlockingPasswordDelegate> {
public:
- virtual ~CryptoModuleBlockingPasswordDelegate() {}
// Return a value suitable for passing to the |wincx| argument of relevant NSS
// functions. This should be used instead of passing the object pointer
@@ -35,16 +34,11 @@ class CryptoModuleBlockingPasswordDelegate {
// user entered.
virtual std::string RequestPassword(const std::string& slot_name, bool retry,
bool* cancelled) = 0;
-};
-// Extends CryptoModuleBlockingPasswordDelegate with the ability to return a
-// slot in which to act. (Eg, which slot to store a generated key in.)
-class NSSCryptoModuleDelegate : public CryptoModuleBlockingPasswordDelegate {
- public:
- ~NSSCryptoModuleDelegate() override {}
+ protected:
+ friend class base::RefCountedThreadSafe<CryptoModuleBlockingPasswordDelegate>;
- // Get the slot to store the generated key.
- virtual ScopedPK11Slot RequestSlot() = 0;
+ virtual ~CryptoModuleBlockingPasswordDelegate() {}
};
} // namespace crypto
diff --git a/crypto/nss_util.cc b/crypto/nss_util.cc
index 5ed2fa0674..30ca5a61a1 100644
--- a/crypto/nss_util.cc
+++ b/crypto/nss_util.cc
@@ -12,46 +12,35 @@
#include <prtime.h>
#include <secmod.h>
+#include <map>
#include <memory>
#include <utility>
-
-#include "base/location.h"
-#include "base/single_thread_task_runner.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "crypto/nss_util_internal.h"
-
-#if defined(OS_OPENBSD)
-#include <sys/mount.h>
-#include <sys/param.h>
-#endif
-
-#if defined(OS_CHROMEOS)
-#include <dlfcn.h>
-#endif
-
-#include <map>
#include <vector>
#include "base/base_paths.h"
#include "base/bind.h"
-#include "base/cpu.h"
#include "base/debug/alias.h"
#include "base/debug/stack_trace.h"
-#include "base/environment.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/lazy_instance.h"
+#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
-#include "base/native_library.h"
#include "base/path_service.h"
#include "base/strings/stringprintf.h"
-#include "base/synchronization/lock.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_checker.h"
#include "base/threading/thread_restrictions.h"
-#include "base/threading/worker_pool.h"
+#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "crypto/nss_crypto_module_delegate.h"
+#include "crypto/nss_util_internal.h"
+
+#if defined(OS_CHROMEOS)
+#include <dlfcn.h>
+#endif
namespace crypto {
@@ -84,7 +73,7 @@ std::string GetNSSErrorMessage() {
#if !defined(OS_CHROMEOS)
base::FilePath GetDefaultConfigDirectory() {
base::FilePath dir;
- PathService::Get(base::DIR_HOME, &dir);
+ base::PathService::Get(base::DIR_HOME, &dir);
if (dir.empty()) {
LOG(ERROR) << "Failed to get home directory.";
return dir;
@@ -97,7 +86,7 @@ base::FilePath GetDefaultConfigDirectory() {
DVLOG(2) << "DefaultConfigDirectory: " << dir.value();
return dir;
}
-#endif // !defined(IS_CHROMEOS)
+#endif // !defined(OS_CHROMEOS)
// On non-Chrome OS platforms, return the default config directory. On Chrome OS
// test images, return a read-only directory with fake root CA certs (which are
@@ -135,37 +124,6 @@ char* PKCS11PasswordFunc(PK11SlotInfo* slot, PRBool retry, void* arg) {
return nullptr;
}
-// NSS creates a local cache of the sqlite database if it detects that the
-// filesystem the database is on is much slower than the local disk. The
-// detection doesn't work with the latest versions of sqlite, such as 3.6.22
-// (NSS bug https://bugzilla.mozilla.org/show_bug.cgi?id=578561). So we set
-// the NSS environment variable NSS_SDB_USE_CACHE to "yes" to override NSS's
-// detection when database_dir is on NFS. See http://crbug.com/48585.
-//
-// Because this function sets an environment variable it must be run before we
-// go multi-threaded.
-void UseLocalCacheOfNSSDatabaseIfNFS(const base::FilePath& database_dir) {
- bool db_on_nfs = false;
-#if defined(OS_LINUX)
- base::FileSystemType fs_type = base::FILE_SYSTEM_UNKNOWN;
- if (base::GetFileSystemType(database_dir, &fs_type))
- db_on_nfs = (fs_type == base::FILE_SYSTEM_NFS);
-#elif defined(OS_OPENBSD)
- struct statfs buf;
- if (statfs(database_dir.value().c_str(), &buf) == 0)
- db_on_nfs = (strcmp(buf.f_fstypename, MOUNT_NFS) == 0);
-#else
- NOTIMPLEMENTED();
-#endif
-
- if (db_on_nfs) {
- std::unique_ptr<base::Environment> env(base::Environment::Create());
- static const char kUseCacheEnvVar[] = "NSS_SDB_USE_CACHE";
- if (!env->HasVar(kUseCacheEnvVar))
- env->SetVar(kUseCacheEnvVar, "yes");
- }
-}
-
// A singleton to initialize/deinitialize NSPR.
// Separate from the NSS singleton because we initialize NSPR on the UI thread.
// Now that we're leaking the singleton, we could merge back with the NSS
@@ -178,15 +136,10 @@ class NSPRInitSingleton {
PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
}
- // NOTE(willchan): We don't actually execute this code since we leak NSS to
- // prevent non-joinable threads from using NSS after it's already been shut
- // down.
- ~NSPRInitSingleton() {
- PL_ArenaFinish();
- PRStatus prstatus = PR_Cleanup();
- if (prstatus != PR_SUCCESS)
- LOG(ERROR) << "PR_Cleanup failed; was NSPR initialized on wrong thread?";
- }
+ // NOTE(willchan): We don't actually cleanup on destruction since we leak NSS
+ // to prevent non-joinable threads from using NSS after it's already been
+ // shut down.
+ ~NSPRInitSingleton() = delete;
};
base::LazyInstance<NSPRInitSingleton>::Leaky
@@ -223,11 +176,11 @@ class ChromeOSUserData {
}
ScopedPK11Slot GetPrivateSlot(
- const base::Callback<void(ScopedPK11Slot)>& callback) {
+ base::OnceCallback<void(ScopedPK11Slot)> callback) {
if (private_slot_)
return ScopedPK11Slot(PK11_ReferenceSlot(private_slot_.get()));
if (!callback.is_null())
- tpm_ready_callback_list_.push_back(callback);
+ tpm_ready_callback_list_.push_back(std::move(callback));
return ScopedPK11Slot();
}
@@ -240,7 +193,8 @@ class ChromeOSUserData {
for (SlotReadyCallbackList::iterator i = callback_list.begin();
i != callback_list.end();
++i) {
- (*i).Run(ScopedPK11Slot(PK11_ReferenceSlot(private_slot_.get())));
+ std::move(*i).Run(
+ ScopedPK11Slot(PK11_ReferenceSlot(private_slot_.get())));
}
}
@@ -258,7 +212,7 @@ class ChromeOSUserData {
bool private_slot_initialization_started_;
- typedef std::vector<base::Callback<void(ScopedPK11Slot)> >
+ typedef std::vector<base::OnceCallback<void(ScopedPK11Slot)>>
SlotReadyCallbackList;
SlotReadyCallbackList tpm_ready_callback_list_;
};
@@ -339,7 +293,7 @@ class NSSInitSingleton {
void InitializeTPMTokenAndSystemSlot(
int system_slot_id,
- const base::Callback<void(bool)>& callback) {
+ base::OnceCallback<void(bool)> callback) {
DCHECK(thread_checker_.CalledOnValidThread());
// Should not be called while there is already an initialization in
// progress.
@@ -347,7 +301,7 @@ class NSSInitSingleton {
// If EnableTPMTokenForNSS hasn't been called, return false.
if (!tpm_token_enabled_for_nss_) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(callback, false));
+ FROM_HERE, base::BindOnce(std::move(callback), false));
return;
}
@@ -355,8 +309,8 @@ class NSSInitSingleton {
// Note that only |tpm_slot_| is checked, since |chaps_module_| could be
// nullptr in tests while |tpm_slot_| has been set to the test DB.
if (tpm_slot_) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- base::Bind(callback, true));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), true));
return;
}
@@ -365,25 +319,28 @@ class NSSInitSingleton {
std::unique_ptr<TPMModuleAndSlot> tpm_args(
new TPMModuleAndSlot(chaps_module_));
TPMModuleAndSlot* tpm_args_ptr = tpm_args.get();
- if (base::WorkerPool::PostTaskAndReply(
- FROM_HERE,
- base::Bind(&NSSInitSingleton::InitializeTPMTokenOnWorkerThread,
+ base::PostTaskWithTraitsAndReply(
+ FROM_HERE,
+ {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+ base::BindOnce(&NSSInitSingleton::InitializeTPMTokenInThreadPool,
system_slot_id, tpm_args_ptr),
- base::Bind(&NSSInitSingleton::OnInitializedTPMTokenAndSystemSlot,
+ base::BindOnce(&NSSInitSingleton::OnInitializedTPMTokenAndSystemSlot,
base::Unretained(this), // NSSInitSingleton is leaky
- callback, base::Passed(&tpm_args)),
- true /* task_is_slow */)) {
- initializing_tpm_token_ = true;
- } else {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(callback, false));
- }
+ std::move(callback), std::move(tpm_args)));
+ initializing_tpm_token_ = true;
}
- static void InitializeTPMTokenOnWorkerThread(CK_SLOT_ID token_slot_id,
- TPMModuleAndSlot* tpm_args) {
- // This tries to load the Chaps module so NSS can talk to the hardware
- // TPM.
+ static void InitializeTPMTokenInThreadPool(CK_SLOT_ID token_slot_id,
+ TPMModuleAndSlot* tpm_args) {
+ // NSS functions may reenter //net via extension hooks. If the reentered
+ // code needs to synchronously wait for a task to run but the thread pool in
+ // which that task must run doesn't have enough threads to schedule it, a
+ // deadlock occurs. To prevent that, the base::ScopedBlockingCall below
+ // increments the thread pool capacity for the duration of the TPM
+ // initialization.
+ base::ScopedBlockingCall scoped_blocking_call(
+ base::BlockingType::WILL_BLOCK);
+
if (!tpm_args->chaps_module) {
ScopedChapsLoadFixup chaps_loader;
@@ -400,12 +357,12 @@ class NSSInitSingleton {
}
if (tpm_args->chaps_module) {
tpm_args->tpm_slot =
- GetTPMSlotForIdOnWorkerThread(tpm_args->chaps_module, token_slot_id);
+ GetTPMSlotForIdInThreadPool(tpm_args->chaps_module, token_slot_id);
}
}
void OnInitializedTPMTokenAndSystemSlot(
- const base::Callback<void(bool)>& callback,
+ base::OnceCallback<void(bool)> callback,
std::unique_ptr<TPMModuleAndSlot> tpm_args) {
DCHECK(thread_checker_.CalledOnValidThread());
DVLOG(2) << "Loaded chaps: " << !!tpm_args->chaps_module
@@ -423,7 +380,7 @@ class NSSInitSingleton {
if (tpm_slot_)
RunAndClearTPMReadyCallbackList();
- callback.Run(!!tpm_slot_);
+ std::move(callback).Run(!!tpm_slot_);
}
void RunAndClearTPMReadyCallbackList() {
@@ -432,11 +389,11 @@ class NSSInitSingleton {
for (TPMReadyCallbackList::iterator i = callback_list.begin();
i != callback_list.end();
++i) {
- i->Run();
+ std::move(*i).Run();
}
}
- bool IsTPMTokenReady(const base::Closure& callback) {
+ bool IsTPMTokenReady(base::OnceClosure callback) {
if (!callback.is_null()) {
// Cannot DCHECK in the general case yet, but since the callback is
// a new addition to the API, DCHECK to make sure at least the new uses
@@ -452,7 +409,7 @@ class NSSInitSingleton {
return true;
if (!callback.is_null())
- tpm_ready_callback_list_.push_back(callback);
+ tpm_ready_callback_list_.push_back(std::move(callback));
return false;
}
@@ -460,7 +417,7 @@ class NSSInitSingleton {
// Note that CK_SLOT_ID is an unsigned long, but cryptohome gives us the slot
// id as an int. This should be safe since this is only used with chaps, which
// we also control.
- static crypto::ScopedPK11Slot GetTPMSlotForIdOnWorkerThread(
+ static crypto::ScopedPK11Slot GetTPMSlotForIdInThreadPool(
SECMODModule* chaps_module,
CK_SLOT_ID slot_id) {
DCHECK(chaps_module);
@@ -490,7 +447,7 @@ class NSSInitSingleton {
"%s %s", kUserNSSDatabaseName, username_hash.c_str());
ScopedPK11Slot public_slot(OpenPersistentNSSDBForPath(db_name, path));
chromeos_user_map_[username_hash] =
- base::MakeUnique<ChromeOSUserData>(std::move(public_slot));
+ std::make_unique<ChromeOSUserData>(std::move(public_slot));
return true;
}
@@ -525,14 +482,14 @@ class NSSInitSingleton {
std::unique_ptr<TPMModuleAndSlot> tpm_args(
new TPMModuleAndSlot(chaps_module_));
TPMModuleAndSlot* tpm_args_ptr = tpm_args.get();
- base::WorkerPool::PostTaskAndReply(
+ base::PostTaskWithTraitsAndReply(
FROM_HERE,
- base::Bind(&NSSInitSingleton::InitializeTPMTokenOnWorkerThread, slot_id,
- tpm_args_ptr),
- base::Bind(&NSSInitSingleton::OnInitializedTPMForChromeOSUser,
- base::Unretained(this), // NSSInitSingleton is leaky
- username_hash, base::Passed(&tpm_args)),
- true /* task_is_slow */);
+ {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+ base::BindOnce(&NSSInitSingleton::InitializeTPMTokenInThreadPool,
+ slot_id, tpm_args_ptr),
+ base::BindOnce(&NSSInitSingleton::OnInitializedTPMForChromeOSUser,
+ base::Unretained(this), // NSSInitSingleton is leaky
+ username_hash, std::move(tpm_args)));
}
void OnInitializedTPMForChromeOSUser(
@@ -553,6 +510,12 @@ class NSSInitSingleton {
DCHECK(chromeos_user_map_[username_hash]->
private_slot_initialization_started());
+ if (prepared_test_private_slot_) {
+ chromeos_user_map_[username_hash]->SetPrivateSlot(
+ std::move(prepared_test_private_slot_));
+ return;
+ }
+
chromeos_user_map_[username_hash]->SetPrivateSlot(
chromeos_user_map_[username_hash]->GetPublicSlot());
}
@@ -575,21 +538,22 @@ class NSSInitSingleton {
ScopedPK11Slot GetPrivateSlotForChromeOSUser(
const std::string& username_hash,
- const base::Callback<void(ScopedPK11Slot)>& callback) {
+ base::OnceCallback<void(ScopedPK11Slot)> callback) {
DCHECK(thread_checker_.CalledOnValidThread());
if (username_hash.empty()) {
DVLOG(2) << "empty username_hash";
if (!callback.is_null()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(callback, base::Passed(ScopedPK11Slot())));
+ FROM_HERE, base::BindOnce(std::move(callback), ScopedPK11Slot()));
}
return ScopedPK11Slot();
}
DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
- return chromeos_user_map_[username_hash]->GetPrivateSlot(callback);
+ return chromeos_user_map_[username_hash]->GetPrivateSlot(
+ std::move(callback));
}
void CloseChromeOSUserForTesting(const std::string& username_hash) {
@@ -600,6 +564,8 @@ class NSSInitSingleton {
}
void SetSystemKeySlotForTesting(ScopedPK11Slot slot) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
// Ensure that a previous value of test_system_slot_ is not overwritten.
// Unsetting, i.e. setting a nullptr, however is allowed.
DCHECK(!slot || !test_system_slot_);
@@ -611,6 +577,15 @@ class NSSInitSingleton {
tpm_slot_.reset();
}
}
+
+ void SetPrivateSoftwareSlotForChromeOSUserForTesting(ScopedPK11Slot slot) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Ensure that a previous value of prepared_test_private_slot_ is not
+ // overwritten. Unsetting, i.e. setting a nullptr, however is allowed.
+ DCHECK(!slot || !prepared_test_private_slot_);
+ prepared_test_private_slot_ = std::move(slot);
+ }
#endif // defined(OS_CHROMEOS)
#if !defined(OS_CHROMEOS)
@@ -627,35 +602,31 @@ class NSSInitSingleton {
#if defined(OS_CHROMEOS)
void GetSystemNSSKeySlotCallback(
- const base::Callback<void(ScopedPK11Slot)>& callback) {
- callback.Run(ScopedPK11Slot(PK11_ReferenceSlot(tpm_slot_.get())));
+ base::OnceCallback<void(ScopedPK11Slot)> callback) {
+ std::move(callback).Run(
+ ScopedPK11Slot(PK11_ReferenceSlot(tpm_slot_.get())));
}
ScopedPK11Slot GetSystemNSSKeySlot(
- const base::Callback<void(ScopedPK11Slot)>& callback) {
+ base::OnceCallback<void(ScopedPK11Slot)> callback) {
DCHECK(thread_checker_.CalledOnValidThread());
// TODO(mattm): chromeos::TPMTokenloader always calls
// InitializeTPMTokenAndSystemSlot with slot 0. If the system slot is
// disabled, tpm_slot_ will be the first user's slot instead. Can that be
// detected and return nullptr instead?
- base::Closure wrapped_callback;
+ base::OnceClosure wrapped_callback;
if (!callback.is_null()) {
- wrapped_callback =
- base::Bind(&NSSInitSingleton::GetSystemNSSKeySlotCallback,
- base::Unretained(this) /* singleton is leaky */,
- callback);
+ wrapped_callback = base::BindOnce(
+ &NSSInitSingleton::GetSystemNSSKeySlotCallback,
+ base::Unretained(this) /* singleton is leaky */, std::move(callback));
}
- if (IsTPMTokenReady(wrapped_callback))
+ if (IsTPMTokenReady(std::move(wrapped_callback)))
return ScopedPK11Slot(PK11_ReferenceSlot(tpm_slot_.get()));
return ScopedPK11Slot();
}
#endif
- base::Lock* write_lock() {
- return &write_lock_;
- }
-
private:
friend struct base::LazyInstanceTraitsBase<NSSInitSingleton>;
@@ -664,22 +635,24 @@ class NSSInitSingleton {
initializing_tpm_token_(false),
chaps_module_(nullptr),
root_(nullptr) {
+ // Initializing NSS causes us to do blocking IO.
+ // Temporarily allow it until we fix
+ // http://code.google.com/p/chromium/issues/detail?id=59847
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+
// It's safe to construct on any thread, since LazyInstance will prevent any
// other threads from accessing until the constructor is done.
thread_checker_.DetachFromThread();
EnsureNSPRInit();
- // We *must* have NSS >= 3.14.3.
- static_assert(
- (NSS_VMAJOR == 3 && NSS_VMINOR == 14 && NSS_VPATCH >= 3) ||
- (NSS_VMAJOR == 3 && NSS_VMINOR > 14) ||
- (NSS_VMAJOR > 3),
- "nss version check failed");
+ // We *must* have NSS >= 3.26 at compile time.
+ static_assert((NSS_VMAJOR == 3 && NSS_VMINOR >= 26) || (NSS_VMAJOR > 3),
+ "nss version check failed");
// Also check the run-time NSS version.
// NSS_VersionCheck is a >= check, not strict equality.
- if (!NSS_VersionCheck("3.14.3")) {
- LOG(FATAL) << "NSS_VersionCheck(\"3.14.3\") failed. NSS >= 3.14.3 is "
+ if (!NSS_VersionCheck("3.26")) {
+ LOG(FATAL) << "NSS_VersionCheck(\"3.26\") failed. NSS >= 3.26 is "
"required. Please upgrade to the latest NSS, and if you "
"still get this error, contact your distribution "
"maintainer.";
@@ -688,11 +661,6 @@ class NSSInitSingleton {
SECStatus status = SECFailure;
base::FilePath database_dir = GetInitialConfigDirectory();
if (!database_dir.empty()) {
- // This duplicates the work which should have been done in
- // EarlySetupForNSSInit. However, this function is idempotent so
- // there's no harm done.
- UseLocalCacheOfNSSDatabaseIfNFS(database_dir);
-
// Initialize with a persistent database (likely, ~/.pki/nssdb).
// Use "sql:" which can be shared by multiple processes safely.
std::string nss_config_dir =
@@ -740,32 +708,10 @@ class NSSInitSingleton {
0, NSS_USE_ALG_IN_CERT_SIGNATURE);
}
- // NOTE(willchan): We don't actually execute this code since we leak NSS to
- // prevent non-joinable threads from using NSS after it's already been shut
- // down.
- ~NSSInitSingleton() {
-#if defined(OS_CHROMEOS)
- chromeos_user_map_.clear();
-#endif
- tpm_slot_.reset();
- if (root_) {
- SECMOD_UnloadUserModule(root_);
- SECMOD_DestroyModule(root_);
- root_ = nullptr;
- }
- if (chaps_module_) {
- SECMOD_UnloadUserModule(chaps_module_);
- SECMOD_DestroyModule(chaps_module_);
- chaps_module_ = nullptr;
- }
-
- SECStatus status = NSS_Shutdown();
- if (status != SECSuccess) {
- // We VLOG(1) because this failure is relatively harmless (leaking, but
- // we're shutting down anyway).
- VLOG(1) << "NSS_Shutdown failed; see http://crbug.com/4609";
- }
- }
+ // NOTE(willchan): We don't actually cleanup on destruction since we leak NSS
+ // to prevent non-joinable threads from using NSS after it's already been
+ // shut down.
+ ~NSSInitSingleton() = delete;
// Load nss's built-in root certs.
SECMODModule* InitDefaultRootCerts() {
@@ -809,7 +755,7 @@ class NSSInitSingleton {
bool tpm_token_enabled_for_nss_;
bool initializing_tpm_token_;
- typedef std::vector<base::Closure> TPMReadyCallbackList;
+ typedef std::vector<base::OnceClosure> TPMReadyCallbackList;
TPMReadyCallbackList tpm_ready_callback_list_;
SECMODModule* chaps_module_;
crypto::ScopedPK11Slot tpm_slot_;
@@ -817,10 +763,8 @@ class NSSInitSingleton {
#if defined(OS_CHROMEOS)
std::map<std::string, std::unique_ptr<ChromeOSUserData>> chromeos_user_map_;
ScopedPK11Slot test_system_slot_;
+ ScopedPK11Slot prepared_test_private_slot_;
#endif
- // TODO(davidben): When https://bugzilla.mozilla.org/show_bug.cgi?id=564011
- // is fixed, we will no longer need the lock.
- base::Lock write_lock_;
base::ThreadChecker thread_checker_;
};
@@ -846,21 +790,11 @@ ScopedPK11Slot OpenSoftwareNSSDB(const base::FilePath& path,
return ScopedPK11Slot(db_slot);
}
-void EarlySetupForNSSInit() {
- base::FilePath database_dir = GetInitialConfigDirectory();
- if (!database_dir.empty())
- UseLocalCacheOfNSSDatabaseIfNFS(database_dir);
-}
-
void EnsureNSPRInit() {
g_nspr_singleton.Get();
}
void EnsureNSSInit() {
- // Initializing SSL causes us to do blocking IO.
- // Temporarily allow it until we fix
- // http://code.google.com/p/chromium/issues/detail?id=59847
- base::ThreadRestrictions::ScopedAllowIO allow_io;
g_nss_singleton.Get();
}
@@ -868,23 +802,6 @@ bool CheckNSSVersion(const char* version) {
return !!NSS_VersionCheck(version);
}
-base::Lock* GetNSSWriteLock() {
- return g_nss_singleton.Get().write_lock();
-}
-
-AutoNSSWriteLock::AutoNSSWriteLock() : lock_(GetNSSWriteLock()) {
- // May be nullptr if the lock is not needed in our version of NSS.
- if (lock_)
- lock_->Acquire();
-}
-
-AutoNSSWriteLock::~AutoNSSWriteLock() {
- if (lock_) {
- lock_->AssertAcquired();
- lock_->Release();
- }
-}
-
AutoSECMODListReadLock::AutoSECMODListReadLock()
: lock_(SECMOD_GetDefaultModuleListLock()) {
SECMOD_GetReadLock(lock_);
@@ -896,8 +813,8 @@ AutoSECMODListReadLock::~AutoSECMODListReadLock() {
#if defined(OS_CHROMEOS)
ScopedPK11Slot GetSystemNSSKeySlot(
- const base::Callback<void(ScopedPK11Slot)>& callback) {
- return g_nss_singleton.Get().GetSystemNSSKeySlot(callback);
+ base::OnceCallback<void(ScopedPK11Slot)> callback) {
+ return g_nss_singleton.Get().GetSystemNSSKeySlot(std::move(callback));
}
void SetSystemKeySlotForTesting(ScopedPK11Slot slot) {
@@ -912,15 +829,14 @@ bool IsTPMTokenEnabledForNSS() {
return g_nss_singleton.Get().IsTPMTokenEnabledForNSS();
}
-bool IsTPMTokenReady(const base::Closure& callback) {
- return g_nss_singleton.Get().IsTPMTokenReady(callback);
+bool IsTPMTokenReady(base::OnceClosure callback) {
+ return g_nss_singleton.Get().IsTPMTokenReady(std::move(callback));
}
-void InitializeTPMTokenAndSystemSlot(
- int token_slot_id,
- const base::Callback<void(bool)>& callback) {
+void InitializeTPMTokenAndSystemSlot(int token_slot_id,
+ base::OnceCallback<void(bool)> callback) {
g_nss_singleton.Get().InitializeTPMTokenAndSystemSlot(token_slot_id,
- callback);
+ std::move(callback));
}
bool InitializeNSSForChromeOSUser(const std::string& username_hash,
@@ -956,14 +872,19 @@ ScopedPK11Slot GetPublicSlotForChromeOSUser(const std::string& username_hash) {
ScopedPK11Slot GetPrivateSlotForChromeOSUser(
const std::string& username_hash,
- const base::Callback<void(ScopedPK11Slot)>& callback) {
- return g_nss_singleton.Get().GetPrivateSlotForChromeOSUser(username_hash,
- callback);
+ base::OnceCallback<void(ScopedPK11Slot)> callback) {
+ return g_nss_singleton.Get().GetPrivateSlotForChromeOSUser(
+ username_hash, std::move(callback));
}
void CloseChromeOSUserForTesting(const std::string& username_hash) {
g_nss_singleton.Get().CloseChromeOSUserForTesting(username_hash);
}
+
+void SetPrivateSoftwareSlotForChromeOSUserForTesting(ScopedPK11Slot slot) {
+ g_nss_singleton.Get().SetPrivateSoftwareSlotForChromeOSUserForTesting(
+ std::move(slot));
+}
#endif // defined(OS_CHROMEOS)
base::Time PRTimeToBaseTime(PRTime prtime) {
diff --git a/crypto/nss_util.h b/crypto/nss_util.h
index 5c34fc8f07..d0691641ff 100644
--- a/crypto/nss_util.h
+++ b/crypto/nss_util.h
@@ -14,7 +14,6 @@
#include "crypto/crypto_export.h"
namespace base {
-class Lock;
class Time;
} // namespace base
@@ -23,11 +22,6 @@ class Time;
// initialization functions.
namespace crypto {
-// EarlySetupForNSSInit performs lightweight setup which must occur before the
-// process goes multithreaded. This does not initialise NSS. For test, see
-// EnsureNSSInit.
-CRYPTO_EXPORT void EarlySetupForNSSInit();
-
// Initialize NRPR if it isn't already initialized. This function is
// thread-safe, and NSPR will only ever be initialized once.
CRYPTO_EXPORT void EnsureNSPRInit();
@@ -57,7 +51,7 @@ CRYPTO_EXPORT bool IsTPMTokenEnabledForNSS();
// If |callback| is non-null and the function returns false, the |callback| will
// be run once the TPM is ready. |callback| will never be run if the function
// returns true.
-CRYPTO_EXPORT bool IsTPMTokenReady(const base::Closure& callback)
+CRYPTO_EXPORT bool IsTPMTokenReady(base::OnceClosure callback)
WARN_UNUSED_RESULT;
// Initialize the TPM token and system slot. The |callback| will run on the same
@@ -67,7 +61,7 @@ CRYPTO_EXPORT bool IsTPMTokenReady(const base::Closure& callback)
// |callback| has been run.
CRYPTO_EXPORT void InitializeTPMTokenAndSystemSlot(
int system_slot_id,
- const base::Callback<void(bool)>& callback);
+ base::OnceCallback<void(bool)> callback);
#endif
// Convert a NSS PRTime value into a base::Time object.
@@ -78,27 +72,6 @@ CRYPTO_EXPORT base::Time PRTimeToBaseTime(int64_t prtime);
// We use a int64_t instead of PRTime here to avoid depending on NSPR headers.
CRYPTO_EXPORT int64_t BaseTimeToPRTime(base::Time time);
-// NSS has a bug which can cause a deadlock or stall in some cases when writing
-// to the certDB and keyDB. It also has a bug which causes concurrent key pair
-// generations to scribble over each other. To work around this, we synchronize
-// writes to the NSS databases with a global lock. The lock is hidden beneath a
-// function for easy disabling when the bug is fixed. Callers should allow for
-// it to return NULL in the future.
-//
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=564011
-base::Lock* GetNSSWriteLock();
-
-// A helper class that acquires the NSS write Lock while the AutoNSSWriteLock
-// is in scope.
-class CRYPTO_EXPORT AutoNSSWriteLock {
- public:
- AutoNSSWriteLock();
- ~AutoNSSWriteLock();
- private:
- base::Lock *lock_;
- DISALLOW_COPY_AND_ASSIGN(AutoNSSWriteLock);
-};
-
} // namespace crypto
#endif // CRYPTO_NSS_UTIL_H_
diff --git a/crypto/nss_util_internal.h b/crypto/nss_util_internal.h
index 080ac1026d..56e22c6a36 100644
--- a/crypto/nss_util_internal.h
+++ b/crypto/nss_util_internal.h
@@ -53,7 +53,7 @@ class CRYPTO_EXPORT AutoSECMODListReadLock {
// loaded and |callback| is non-null, the |callback| will be run once the slot
// is loaded.
CRYPTO_EXPORT ScopedPK11Slot GetSystemNSSKeySlot(
- const base::Callback<void(ScopedPK11Slot)>& callback) WARN_UNUSED_RESULT;
+ base::OnceCallback<void(ScopedPK11Slot)> callback) WARN_UNUSED_RESULT;
// Sets the test system slot to |slot|, which means that |slot| will be exposed
// through |GetSystemNSSKeySlot| and |IsTPMTokenReady| will return true.
@@ -102,12 +102,23 @@ CRYPTO_EXPORT ScopedPK11Slot GetPublicSlotForChromeOSUser(
// is loaded.
CRYPTO_EXPORT ScopedPK11Slot GetPrivateSlotForChromeOSUser(
const std::string& username_hash,
- const base::Callback<void(ScopedPK11Slot)>& callback) WARN_UNUSED_RESULT;
+ base::OnceCallback<void(ScopedPK11Slot)> callback) WARN_UNUSED_RESULT;
// Closes the NSS DB for |username_hash| that was previously opened by the
// *Initialize*ForChromeOSUser functions.
CRYPTO_EXPORT void CloseChromeOSUserForTesting(
const std::string& username_hash);
+
+// Sets the slot which should be used as private slot for the next
+// |InitializePrivateSoftwareSlotForChromeOSUser| called. This is intended for
+// simulating a separate private slot in Chrome OS browser tests.
+// As a sanity check, it is recommended to check that the private slot of the
+// profile's certificate database is set to |slot| when the profile is
+// available, because |slot| will be used as private slot for whichever profile
+// is initialized next.
+CRYPTO_EXPORT void SetPrivateSoftwareSlotForChromeOSUserForTesting(
+ ScopedPK11Slot slot);
+
#endif // defined(OS_CHROMEOS)
} // namespace crypto
diff --git a/crypto/openssl_util.cc b/crypto/openssl_util.cc
index 2349d42db3..05f066ba74 100644
--- a/crypto/openssl_util.cc
+++ b/crypto/openssl_util.cc
@@ -48,15 +48,13 @@ void EnsureOpenSSLInit() {
#endif
}
-void ClearOpenSSLERRStack(const tracked_objects::Location& location) {
+void ClearOpenSSLERRStack(const base::Location& location) {
if (DCHECK_IS_ON() && VLOG_IS_ON(1)) {
uint32_t error_num = ERR_peek_error();
if (error_num == 0)
return;
- std::string message;
- location.Write(true, true, &message);
- DVLOG(1) << "OpenSSL ERR_get_error stack from " << message;
+ DVLOG(1) << "OpenSSL ERR_get_error stack from " << location.ToString();
ERR_print_errors_cb(&OpenSSLErrorCallback, NULL);
} else {
ERR_clear_error();
diff --git a/crypto/openssl_util.h b/crypto/openssl_util.h
index 54f06d337f..56ee54d237 100644
--- a/crypto/openssl_util.h
+++ b/crypto/openssl_util.h
@@ -63,8 +63,7 @@ CRYPTO_EXPORT void EnsureOpenSSLInit();
// Drains the OpenSSL ERR_get_error stack. On a debug build the error codes
// are send to VLOG(1), on a release build they are disregarded. In most
// cases you should pass FROM_HERE as the |location|.
-CRYPTO_EXPORT void ClearOpenSSLERRStack(
- const tracked_objects::Location& location);
+CRYPTO_EXPORT void ClearOpenSSLERRStack(const base::Location& location);
// Place an instance of this class on the call stack to automatically clear
// the OpenSSL error stack on function exit.
@@ -73,7 +72,7 @@ class OpenSSLErrStackTracer {
// Pass FROM_HERE as |location|, to help track the source of OpenSSL error
// messages. Note any diagnostic emitted will be tagged with the location of
// the constructor call as it's not possible to trace a destructor's callsite.
- explicit OpenSSLErrStackTracer(const tracked_objects::Location& location)
+ explicit OpenSSLErrStackTracer(const base::Location& location)
: location_(location) {
EnsureOpenSSLInit();
}
@@ -82,7 +81,7 @@ class OpenSSLErrStackTracer {
}
private:
- const tracked_objects::Location location_;
+ const base::Location location_;
DISALLOW_IMPLICIT_CONSTRUCTORS(OpenSSLErrStackTracer);
};
diff --git a/crypto/p224.cc b/crypto/p224.cc
index 685a335743..2ac07127ac 100644
--- a/crypto/p224.cc
+++ b/crypto/p224.cc
@@ -648,7 +648,7 @@ namespace crypto {
namespace p224 {
-bool Point::SetFromString(const base::StringPiece& in) {
+bool Point::SetFromString(base::StringPiece in) {
if (in.size() != 2*28)
return false;
const uint32_t* inwords = reinterpret_cast<const uint32_t*>(in.data());
diff --git a/crypto/p224.h b/crypto/p224.h
index e9a53a9ae8..f02e657b83 100644
--- a/crypto/p224.h
+++ b/crypto/p224.h
@@ -28,7 +28,7 @@ struct CRYPTO_EXPORT Point {
// representation. The external point representation is an (x, y) pair of a
// point on the curve. Each field element is represented as a big-endian
// number < p.
- bool SetFromString(const base::StringPiece& in);
+ bool SetFromString(base::StringPiece in);
// ToString returns an external representation of the Point.
std::string ToString() const;
diff --git a/crypto/p224_spake.cc b/crypto/p224_spake.cc
index 7275a45bc6..e31bc38aa9 100644
--- a/crypto/p224_spake.cc
+++ b/crypto/p224_spake.cc
@@ -100,10 +100,9 @@ const crypto::p224::Point kN = {
namespace crypto {
-P224EncryptedKeyExchange::P224EncryptedKeyExchange(
- PeerType peer_type, const base::StringPiece& password)
- : state_(kStateInitial),
- is_server_(peer_type == kPeerTypeServer) {
+P224EncryptedKeyExchange::P224EncryptedKeyExchange(PeerType peer_type,
+ base::StringPiece password)
+ : state_(kStateInitial), is_server_(peer_type == kPeerTypeServer) {
memset(&x_, 0, sizeof(x_));
memset(&expected_authenticator_, 0, sizeof(expected_authenticator_));
@@ -150,7 +149,7 @@ const std::string& P224EncryptedKeyExchange::GetNextMessage() {
}
P224EncryptedKeyExchange::Result P224EncryptedKeyExchange::ProcessMessage(
- const base::StringPiece& message) {
+ base::StringPiece message) {
if (state_ == kStateRecvHash) {
// This is the final state of the protocol: we are reading the peer's
// authentication hash and checking that it matches the one that we expect.
diff --git a/crypto/p224_spake.h b/crypto/p224_spake.h
index b5cc70ae9e..823bd459d4 100644
--- a/crypto/p224_spake.h
+++ b/crypto/p224_spake.h
@@ -56,8 +56,7 @@ class CRYPTO_EXPORT P224EncryptedKeyExchange {
// password: secret session password. Both parties to the
// authentication must pass the same value. For the case of a
// TLS connection, see RFC 5705.
- P224EncryptedKeyExchange(PeerType peer_type,
- const base::StringPiece& password);
+ P224EncryptedKeyExchange(PeerType peer_type, base::StringPiece password);
// GetNextMessage returns a byte string which must be passed to the other
// party in the authentication.
@@ -65,7 +64,7 @@ class CRYPTO_EXPORT P224EncryptedKeyExchange {
// ProcessMessage processes a message which must have been generated by a
// call to GetNextMessage() by the other party.
- Result ProcessMessage(const base::StringPiece& message);
+ Result ProcessMessage(base::StringPiece message);
// In the event that ProcessMessage() returns kResultFailed, error will
// return a human readable error message.
diff --git a/crypto/rsa_private_key.cc b/crypto/rsa_private_key.cc
index 075f5e4041..ab8027ca3c 100644
--- a/crypto/rsa_private_key.cc
+++ b/crypto/rsa_private_key.cc
@@ -61,14 +61,13 @@ std::unique_ptr<RSAPrivateKey> RSAPrivateKey::CreateFromKey(EVP_PKEY* key) {
if (EVP_PKEY_type(key->type) != EVP_PKEY_RSA)
return nullptr;
std::unique_ptr<RSAPrivateKey> copy(new RSAPrivateKey);
- EVP_PKEY_up_ref(key);
- copy->key_.reset(key);
+ copy->key_ = bssl::UpRef(key);
return copy;
}
-RSAPrivateKey::RSAPrivateKey() {}
+RSAPrivateKey::RSAPrivateKey() = default;
-RSAPrivateKey::~RSAPrivateKey() {}
+RSAPrivateKey::~RSAPrivateKey() = default;
std::unique_ptr<RSAPrivateKey> RSAPrivateKey::Copy() const {
std::unique_ptr<RSAPrivateKey> copy(new RSAPrivateKey);
diff --git a/crypto/scoped_test_nss_db.cc b/crypto/scoped_test_nss_db.cc
index b334109e03..e2d4eb52eb 100644
--- a/crypto/scoped_test_nss_db.cc
+++ b/crypto/scoped_test_nss_db.cc
@@ -18,7 +18,7 @@ ScopedTestNSSDB::ScopedTestNSSDB() {
// NSS is allowed to do IO on the current thread since dispatching
// to a dedicated thread would still have the affect of blocking
// the current thread, due to NSS's internal locking requirements
- base::ThreadRestrictions::ScopedAllowIO allow_io;
+ base::ScopedAllowBlockingForTesting allow_blocking;
if (!temp_dir_.CreateUniqueTempDir())
return;
@@ -44,19 +44,10 @@ ScopedTestNSSDB::~ScopedTestNSSDB() {
CERT_DestroyCertList(cert_list);
}
- // Don't close when NSS is < 3.15.1, because it would require an additional
- // sleep for 1 second after closing the database, due to
- // http://bugzil.la/875601.
- if (!NSS_VersionCheck("3.15.1")) {
- LOG(ERROR) << "NSS version is < 3.15.1, test DB will not be closed.";
- temp_dir_.Take();
- return;
- }
-
// NSS is allowed to do IO on the current thread since dispatching
// to a dedicated thread would still have the affect of blocking
// the current thread, due to NSS's internal locking requirements
- base::ThreadRestrictions::ScopedAllowIO allow_io;
+ base::ScopedAllowBlockingForTesting allow_blocking;
if (slot_) {
SECStatus status = SECMOD_CloseUserDB(slot_.get());
diff --git a/crypto/secure_hash.cc b/crypto/secure_hash.cc
index d47f783c05..0acbbde05f 100644
--- a/crypto/secure_hash.cc
+++ b/crypto/secure_hash.cc
@@ -46,7 +46,7 @@ class SecureHashSHA256 : public SecureHash {
}
std::unique_ptr<SecureHash> Clone() const override {
- return base::MakeUnique<SecureHashSHA256>(*this);
+ return std::make_unique<SecureHashSHA256>(*this);
}
size_t GetHashLength() const override { return SHA256_DIGEST_LENGTH; }
@@ -60,7 +60,7 @@ class SecureHashSHA256 : public SecureHash {
std::unique_ptr<SecureHash> SecureHash::Create(Algorithm algorithm) {
switch (algorithm) {
case SHA256:
- return base::MakeUnique<SecureHashSHA256>();
+ return std::make_unique<SecureHashSHA256>();
default:
NOTIMPLEMENTED();
return nullptr;
diff --git a/crypto/secure_hash.h b/crypto/secure_hash.h
index 30b9fdc5f2..b97487b5ca 100644
--- a/crypto/secure_hash.h
+++ b/crypto/secure_hash.h
@@ -15,7 +15,8 @@
namespace crypto {
// A wrapper to calculate secure hashes incrementally, allowing to
-// be used when the full input is not known in advance.
+// be used when the full input is not known in advance. The end result will the
+// same as if we have the full input in advance.
class CRYPTO_EXPORT SecureHash {
public:
enum Algorithm {
diff --git a/crypto/secure_hash_unittest.cc b/crypto/secure_hash_unittest.cc
index cb9f585232..b54a83701c 100644
--- a/crypto/secure_hash_unittest.cc
+++ b/crypto/secure_hash_unittest.cc
@@ -80,3 +80,28 @@ TEST(SecureHashTest, TestLength) {
crypto::SecureHash::Create(crypto::SecureHash::SHA256));
EXPECT_EQ(crypto::kSHA256Length, ctx->GetHashLength());
}
+
+TEST(SecureHashTest, Equality) {
+ std::string input1(10001, 'a'); // 'a' repeated 10001 times
+ std::string input2(10001, 'd'); // 'd' repeated 10001 times
+
+ uint8_t output1[crypto::kSHA256Length];
+ uint8_t output2[crypto::kSHA256Length];
+
+ // Call Update() twice on input1 and input2.
+ std::unique_ptr<crypto::SecureHash> ctx1(
+ crypto::SecureHash::Create(crypto::SecureHash::SHA256));
+ ctx1->Update(input1.data(), input1.size());
+ ctx1->Update(input2.data(), input2.size());
+ ctx1->Finish(output1, sizeof(output1));
+
+ // Call Update() once one input1 + input2 (concatenation).
+ std::unique_ptr<crypto::SecureHash> ctx2(
+ crypto::SecureHash::Create(crypto::SecureHash::SHA256));
+ std::string input3 = input1 + input2;
+ ctx2->Update(input3.data(), input3.size());
+ ctx2->Finish(output2, sizeof(output2));
+
+ // The hash should be the same.
+ EXPECT_EQ(0, memcmp(output1, output2, crypto::kSHA256Length));
+}
diff --git a/crypto/sha2.cc b/crypto/sha2.cc
index 1b302b34f6..aa1b6d0d06 100644
--- a/crypto/sha2.cc
+++ b/crypto/sha2.cc
@@ -13,15 +13,15 @@
namespace crypto {
-void SHA256HashString(const base::StringPiece& str, void* output, size_t len) {
+void SHA256HashString(base::StringPiece str, void* output, size_t len) {
std::unique_ptr<SecureHash> ctx(SecureHash::Create(SecureHash::SHA256));
ctx->Update(str.data(), str.length());
ctx->Finish(output, len);
}
-std::string SHA256HashString(const base::StringPiece& str) {
+std::string SHA256HashString(base::StringPiece str) {
std::string output(kSHA256Length, 0);
- SHA256HashString(str, base::string_as_array(&output), output.size());
+ SHA256HashString(str, base::data(output), output.size());
return output;
}
diff --git a/crypto/sha2.h b/crypto/sha2.h
index d575815f88..f41224ee8d 100644
--- a/crypto/sha2.h
+++ b/crypto/sha2.h
@@ -23,12 +23,13 @@ static const size_t kSHA256Length = 32; // Length in bytes of a SHA-256 hash.
// Computes the SHA-256 hash of the input string 'str' and stores the first
// 'len' bytes of the hash in the output buffer 'output'. If 'len' > 32,
// only 32 bytes (the full hash) are stored in the 'output' buffer.
-CRYPTO_EXPORT void SHA256HashString(const base::StringPiece& str,
- void* output, size_t len);
+CRYPTO_EXPORT void SHA256HashString(base::StringPiece str,
+ void* output,
+ size_t len);
// Convenience version of the above that returns the result in a 32-byte
// string.
-CRYPTO_EXPORT std::string SHA256HashString(const base::StringPiece& str);
+CRYPTO_EXPORT std::string SHA256HashString(base::StringPiece str);
} // namespace crypto
diff --git a/crypto/signature_creator_unittest.cc b/crypto/signature_creator_unittest.cc
index 2f135cc709..1048170a77 100644
--- a/crypto/signature_creator_unittest.cc
+++ b/crypto/signature_creator_unittest.cc
@@ -44,12 +44,10 @@ TEST(SignatureCreatorTest, BasicTest) {
ASSERT_TRUE(key_original->ExportPublicKey(&public_key_info));
crypto::SignatureVerifier verifier;
- ASSERT_TRUE(verifier.VerifyInit(
- crypto::SignatureVerifier::RSA_PKCS1_SHA1, &signature.front(),
- signature.size(), &public_key_info.front(), public_key_info.size()));
+ ASSERT_TRUE(verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1,
+ signature, public_key_info));
- verifier.VerifyUpdate(reinterpret_cast<const uint8_t*>(data.c_str()),
- data.size());
+ verifier.VerifyUpdate(base::as_bytes(base::make_span(data)));
ASSERT_TRUE(verifier.VerifyFinal());
}
@@ -78,12 +76,10 @@ TEST(SignatureCreatorTest, SignDigestTest) {
// Verify the input data.
crypto::SignatureVerifier verifier;
- ASSERT_TRUE(verifier.VerifyInit(
- crypto::SignatureVerifier::RSA_PKCS1_SHA1, &signature.front(),
- signature.size(), &public_key_info.front(), public_key_info.size()));
+ ASSERT_TRUE(verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1,
+ signature, public_key_info));
- verifier.VerifyUpdate(reinterpret_cast<const uint8_t*>(data.c_str()),
- data.size());
+ verifier.VerifyUpdate(base::as_bytes(base::make_span(data)));
ASSERT_TRUE(verifier.VerifyFinal());
}
@@ -113,11 +109,9 @@ TEST(SignatureCreatorTest, SignSHA256DigestTest) {
// Verify the input data.
crypto::SignatureVerifier verifier;
- ASSERT_TRUE(verifier.VerifyInit(
- crypto::SignatureVerifier::RSA_PKCS1_SHA256, &signature.front(),
- signature.size(), &public_key_info.front(), public_key_info.size()));
+ ASSERT_TRUE(verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA256,
+ signature, public_key_info));
- verifier.VerifyUpdate(reinterpret_cast<const uint8_t*>(data.c_str()),
- data.size());
+ verifier.VerifyUpdate(base::as_bytes(base::make_span(data)));
ASSERT_TRUE(verifier.VerifyFinal());
}
diff --git a/crypto/signature_verifier.h b/crypto/signature_verifier.h
index f1ea58062c..1a44dd5fb8 100644
--- a/crypto/signature_verifier.h
+++ b/crypto/signature_verifier.h
@@ -10,29 +10,24 @@
#include <memory>
#include <vector>
+#include "base/containers/span.h"
#include "build/build_config.h"
#include "crypto/crypto_export.h"
-typedef struct env_md_st EVP_MD;
-typedef struct evp_pkey_ctx_st EVP_PKEY_CTX;
-
namespace crypto {
// The SignatureVerifier class verifies a signature using a bare public key
// (as opposed to a certificate).
class CRYPTO_EXPORT SignatureVerifier {
public:
- // The set of supported hash functions. Extend as required.
- enum HashAlgorithm {
- SHA1,
- SHA256,
- };
-
// The set of supported signature algorithms. Extend as required.
enum SignatureAlgorithm {
RSA_PKCS1_SHA1,
RSA_PKCS1_SHA256,
ECDSA_SHA256,
+ // This is RSA-PSS with SHA-256 as both signing hash and MGF-1 hash, and the
+ // salt length matching the hash length.
+ RSA_PSS_SHA256,
};
SignatureVerifier();
@@ -42,7 +37,6 @@ class CRYPTO_EXPORT SignatureVerifier {
// Initiates a signature verification operation. This should be followed
// by one or more VerifyUpdate calls and a VerifyFinal call.
- // NOTE: for RSA-PSS signatures, use VerifyInitRSAPSS instead.
//
// The signature is encoded according to the signature algorithm.
//
@@ -53,37 +47,11 @@ class CRYPTO_EXPORT SignatureVerifier {
// algorithm AlgorithmIdentifier,
// subjectPublicKey BIT STRING }
bool VerifyInit(SignatureAlgorithm signature_algorithm,
- const uint8_t* signature,
- int signature_len,
- const uint8_t* public_key_info,
- 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.
- //
- // The RSA-PSS signature algorithm parameters are specified with the
- // |hash_alg|, |mask_hash_alg|, and |salt_len| arguments.
- //
- // An RSA-PSS signature is a nonnegative integer encoded as a byte string
- // (of the same length as the RSA modulus) in big-endian byte order. It
- // must not be further encoded in an ASN.1 BIT STRING.
- //
- // The public key is specified as a DER encoded ASN.1 SubjectPublicKeyInfo
- // structure, which contains not only the public key but also its type
- // (algorithm):
- // SubjectPublicKeyInfo ::= SEQUENCE {
- // algorithm AlgorithmIdentifier,
- // subjectPublicKey BIT STRING }
- bool VerifyInitRSAPSS(HashAlgorithm hash_alg,
- HashAlgorithm mask_hash_alg,
- int salt_len,
- const uint8_t* signature,
- int signature_len,
- const uint8_t* public_key_info,
- int public_key_info_len);
+ base::span<const uint8_t> signature,
+ base::span<const uint8_t> public_key_info);
// Feeds a piece of the data to the signature verifier.
- void VerifyUpdate(const uint8_t* data_part, int data_part_len);
+ void VerifyUpdate(base::span<const uint8_t> data_part);
// Concludes a signature verification operation. Returns true if the
// signature is valid. Returns false if the signature is invalid or an
@@ -91,14 +59,6 @@ class CRYPTO_EXPORT SignatureVerifier {
bool VerifyFinal();
private:
- bool CommonInit(int pkey_type,
- const EVP_MD* digest,
- const uint8_t* signature,
- int signature_len,
- const uint8_t* public_key_info,
- int public_key_info_len,
- EVP_PKEY_CTX** pkey_ctx);
-
void Reset();
std::vector<uint8_t> signature_;
diff --git a/crypto/signature_verifier_unittest.cc b/crypto/signature_verifier_unittest.cc
index 2cda4596a1..a3077ef3f4 100644
--- a/crypto/signature_verifier_unittest.cc
+++ b/crypto/signature_verifier_unittest.cc
@@ -188,958 +188,224 @@ TEST(SignatureVerifierTest, BasicTest) {
// We use the signature verifier to perform four signature verification
// tests.
crypto::SignatureVerifier verifier;
- bool ok;
// Test 1: feed all of the data to the verifier at once (a single
// VerifyUpdate call).
- ok = verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1, signature,
- sizeof(signature), public_key_info,
- sizeof(public_key_info));
- EXPECT_TRUE(ok);
- verifier.VerifyUpdate(tbs_certificate, sizeof(tbs_certificate));
- ok = verifier.VerifyFinal();
- EXPECT_TRUE(ok);
+ EXPECT_TRUE(verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1,
+ signature, public_key_info));
+ verifier.VerifyUpdate(tbs_certificate);
+ EXPECT_TRUE(verifier.VerifyFinal());
// Test 2: feed the data to the verifier in three parts (three VerifyUpdate
// calls).
- ok = verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1, signature,
- sizeof(signature), public_key_info,
- sizeof(public_key_info));
- EXPECT_TRUE(ok);
- verifier.VerifyUpdate(tbs_certificate, 256);
- verifier.VerifyUpdate(tbs_certificate + 256, 256);
- verifier.VerifyUpdate(tbs_certificate + 512, sizeof(tbs_certificate) - 512);
- ok = verifier.VerifyFinal();
- EXPECT_TRUE(ok);
+ EXPECT_TRUE(verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1,
+ signature, public_key_info));
+ verifier.VerifyUpdate(base::make_span(tbs_certificate, 256));
+ verifier.VerifyUpdate(base::make_span(tbs_certificate + 256, 256));
+ verifier.VerifyUpdate(
+ base::make_span(tbs_certificate + 512, sizeof(tbs_certificate) - 512));
+ EXPECT_TRUE(verifier.VerifyFinal());
// Test 3: verify the signature with incorrect data.
uint8_t bad_tbs_certificate[sizeof(tbs_certificate)];
memcpy(bad_tbs_certificate, tbs_certificate, sizeof(tbs_certificate));
bad_tbs_certificate[10] += 1; // Corrupt one byte of the data.
- ok = verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1, signature,
- sizeof(signature), public_key_info,
- sizeof(public_key_info));
- EXPECT_TRUE(ok);
- verifier.VerifyUpdate(bad_tbs_certificate, sizeof(bad_tbs_certificate));
- ok = verifier.VerifyFinal();
- EXPECT_FALSE(ok);
+ EXPECT_TRUE(verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1,
+ signature, public_key_info));
+ verifier.VerifyUpdate(bad_tbs_certificate);
+ EXPECT_FALSE(verifier.VerifyFinal());
// Test 4: verify a bad signature.
uint8_t bad_signature[sizeof(signature)];
memcpy(bad_signature, signature, sizeof(signature));
bad_signature[10] += 1; // Corrupt one byte of the signature.
- ok = verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1,
- bad_signature, sizeof(bad_signature),
- public_key_info, sizeof(public_key_info));
-
- // A crypto library (e.g., NSS) may detect that the signature is corrupted
- // and cause VerifyInit to return false, so it is fine for 'ok' to be false.
- if (ok) {
- verifier.VerifyUpdate(tbs_certificate, sizeof(tbs_certificate));
- ok = verifier.VerifyFinal();
- EXPECT_FALSE(ok);
- }
+ EXPECT_TRUE(verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1,
+ bad_signature, public_key_info));
+ verifier.VerifyUpdate(tbs_certificate);
+ EXPECT_FALSE(verifier.VerifyFinal());
// Test 5: import an invalid key.
uint8_t bad_public_key_info[sizeof(public_key_info)];
memcpy(bad_public_key_info, public_key_info, sizeof(public_key_info));
bad_public_key_info[0] += 1; // Corrupt part of the SPKI syntax.
- ok = verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1, signature,
- sizeof(signature), bad_public_key_info,
- sizeof(bad_public_key_info));
- EXPECT_FALSE(ok);
+ EXPECT_FALSE(verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1,
+ signature, bad_public_key_info));
// Test 6: import a key with extra data.
uint8_t long_public_key_info[sizeof(public_key_info) + 5];
memset(long_public_key_info, 0, sizeof(long_public_key_info));
memcpy(long_public_key_info, public_key_info, sizeof(public_key_info));
- ok = verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1, signature,
- sizeof(signature), long_public_key_info,
- sizeof(long_public_key_info));
- EXPECT_FALSE(ok);
+ EXPECT_FALSE(verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1,
+ signature, long_public_key_info));
}
-//////////////////////////////////////////////////////////////////////
-//
-// RSA-PSS signature verification known answer test
-//
-//////////////////////////////////////////////////////////////////////
-
-// The following RSA-PSS signature test vectors come from the pss-vect.txt
-// file downloaded from
-// ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip.
+// The following RSA-PSS tests were generated via the following OpenSSL
+// commands:
//
-// For each key, 6 random messages of length between 1 and 256 octets have
-// been RSASSA-PSS signed.
+// clang-format off
+// openssl genrsa -f4 -out key.pem 2048
+// openssl rsa -in key.pem -pubout -outform der | xxd -i > spki.txt
+// openssl rand -out message 50
+// xxd -i message > message.txt
+// openssl dgst -sign key.pem -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:32 < message | xxd -i > sig-good.txt
+// openssl dgst -sign key.pem -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:33 < message | xxd -i > sig-bad-saltlen.txt
+// clang-format on
+
+namespace {
+
+// This is the public key corresponding to the following private key.
//
-// Hash function: SHA-1
-// Mask generation function: MGF1 with SHA-1
-// Salt length: 20 octets
-
-// Example 1: A 1024-bit RSA Key Pair"
-
-// RSA modulus n:
-static const char rsa_modulus_n_1[] =
- "a5 6e 4a 0e 70 10 17 58 9a 51 87 dc 7e a8 41 d1 "
- "56 f2 ec 0e 36 ad 52 a4 4d fe b1 e6 1f 7a d9 91 "
- "d8 c5 10 56 ff ed b1 62 b4 c0 f2 83 a1 2a 88 a3 "
- "94 df f5 26 ab 72 91 cb b3 07 ce ab fc e0 b1 df "
- "d5 cd 95 08 09 6d 5b 2b 8b 6d f5 d6 71 ef 63 77 "
- "c0 92 1c b2 3c 27 0a 70 e2 59 8e 6f f8 9d 19 f1 "
- "05 ac c2 d3 f0 cb 35 f2 92 80 e1 38 6b 6f 64 c4 "
- "ef 22 e1 e1 f2 0d 0c e8 cf fb 22 49 bd 9a 21 37 ";
-// RSA public exponent e: "
-static const char rsa_public_exponent_e_1[] =
- "01 00 01 ";
-
-// RSASSA-PSS Signature Example 1.1
-// Message to be signed:
-static const char message_1_1[] =
- "cd c8 7d a2 23 d7 86 df 3b 45 e0 bb bc 72 13 26 "
- "d1 ee 2a f8 06 cc 31 54 75 cc 6f 0d 9c 66 e1 b6 "
- "23 71 d4 5c e2 39 2e 1a c9 28 44 c3 10 10 2f 15 "
- "6a 0d 8d 52 c1 f4 c4 0b a3 aa 65 09 57 86 cb 76 "
- "97 57 a6 56 3b a9 58 fe d0 bc c9 84 e8 b5 17 a3 "
- "d5 f5 15 b2 3b 8a 41 e7 4a a8 67 69 3f 90 df b0 "
- "61 a6 e8 6d fa ae e6 44 72 c0 0e 5f 20 94 57 29 "
- "cb eb e7 7f 06 ce 78 e0 8f 40 98 fb a4 1f 9d 61 "
- "93 c0 31 7e 8b 60 d4 b6 08 4a cb 42 d2 9e 38 08 "
- "a3 bc 37 2d 85 e3 31 17 0f cb f7 cc 72 d0 b7 1c "
- "29 66 48 b3 a4 d1 0f 41 62 95 d0 80 7a a6 25 ca "
- "b2 74 4f d9 ea 8f d2 23 c4 25 37 02 98 28 bd 16 "
- "be 02 54 6f 13 0f d2 e3 3b 93 6d 26 76 e0 8a ed "
- "1b 73 31 8b 75 0a 01 67 d0 ";
-// Salt:
-static const char salt_1_1[] =
- "de e9 59 c7 e0 64 11 36 14 20 ff 80 18 5e d5 7f "
- "3e 67 76 af ";
-// Signature:
-static const char signature_1_1[] =
- "90 74 30 8f b5 98 e9 70 1b 22 94 38 8e 52 f9 71 "
- "fa ac 2b 60 a5 14 5a f1 85 df 52 87 b5 ed 28 87 "
- "e5 7c e7 fd 44 dc 86 34 e4 07 c8 e0 e4 36 0b c2 "
- "26 f3 ec 22 7f 9d 9e 54 63 8e 8d 31 f5 05 12 15 "
- "df 6e bb 9c 2f 95 79 aa 77 59 8a 38 f9 14 b5 b9 "
- "c1 bd 83 c4 e2 f9 f3 82 a0 d0 aa 35 42 ff ee 65 "
- "98 4a 60 1b c6 9e b2 8d eb 27 dc a1 2c 82 c2 d4 "
- "c3 f6 6c d5 00 f1 ff 2b 99 4d 8a 4e 30 cb b3 3c ";
-
-// RSASSA-PSS Signature Example 1.2
-// Message to be signed:
-static const char message_1_2[] =
- "85 13 84 cd fe 81 9c 22 ed 6c 4c cb 30 da eb 5c "
- "f0 59 bc 8e 11 66 b7 e3 53 0c 4c 23 3e 2b 5f 8f "
- "71 a1 cc a5 82 d4 3e cc 72 b1 bc a1 6d fc 70 13 "
- "22 6b 9e ";
-// Salt:
-static const char salt_1_2[] =
- "ef 28 69 fa 40 c3 46 cb 18 3d ab 3d 7b ff c9 8f "
- "d5 6d f4 2d ";
-// Signature:
-static const char signature_1_2[] =
- "3e f7 f4 6e 83 1b f9 2b 32 27 41 42 a5 85 ff ce "
- "fb dc a7 b3 2a e9 0d 10 fb 0f 0c 72 99 84 f0 4e "
- "f2 9a 9d f0 78 07 75 ce 43 73 9b 97 83 83 90 db "
- "0a 55 05 e6 3d e9 27 02 8d 9d 29 b2 19 ca 2c 45 "
- "17 83 25 58 a5 5d 69 4a 6d 25 b9 da b6 60 03 c4 "
- "cc cd 90 78 02 19 3b e5 17 0d 26 14 7d 37 b9 35 "
- "90 24 1b e5 1c 25 05 5f 47 ef 62 75 2c fb e2 14 "
- "18 fa fe 98 c2 2c 4d 4d 47 72 4f db 56 69 e8 43 ";
-
-// RSASSA-PSS Signature Example 1.3
-// Message to be signed:
-static const char message_1_3[] =
- "a4 b1 59 94 17 61 c4 0c 6a 82 f2 b8 0d 1b 94 f5 "
- "aa 26 54 fd 17 e1 2d 58 88 64 67 9b 54 cd 04 ef "
- "8b d0 30 12 be 8d c3 7f 4b 83 af 79 63 fa ff 0d "
- "fa 22 54 77 43 7c 48 01 7f f2 be 81 91 cf 39 55 "
- "fc 07 35 6e ab 3f 32 2f 7f 62 0e 21 d2 54 e5 db "
- "43 24 27 9f e0 67 e0 91 0e 2e 81 ca 2c ab 31 c7 "
- "45 e6 7a 54 05 8e b5 0d 99 3c db 9e d0 b4 d0 29 "
- "c0 6d 21 a9 4c a6 61 c3 ce 27 fa e1 d6 cb 20 f4 "
- "56 4d 66 ce 47 67 58 3d 0e 5f 06 02 15 b5 90 17 "
- "be 85 ea 84 89 39 12 7b d8 c9 c4 d4 7b 51 05 6c "
- "03 1c f3 36 f1 7c 99 80 f3 b8 f5 b9 b6 87 8e 8b "
- "79 7a a4 3b 88 26 84 33 3e 17 89 3f e9 ca a6 aa "
- "29 9f 7e d1 a1 8e e2 c5 48 64 b7 b2 b9 9b 72 61 "
- "8f b0 25 74 d1 39 ef 50 f0 19 c9 ee f4 16 97 13 "
- "38 e7 d4 70 ";
-// Salt:
-static const char salt_1_3[] =
- "71 0b 9c 47 47 d8 00 d4 de 87 f1 2a fd ce 6d f1 "
- "81 07 cc 77 ";
-// Signature:
-static const char signature_1_3[] =
- "66 60 26 fb a7 1b d3 e7 cf 13 15 7c c2 c5 1a 8e "
- "4a a6 84 af 97 78 f9 18 49 f3 43 35 d1 41 c0 01 "
- "54 c4 19 76 21 f9 62 4a 67 5b 5a bc 22 ee 7d 5b "
- "aa ff aa e1 c9 ba ca 2c c3 73 b3 f3 3e 78 e6 14 "
- "3c 39 5a 91 aa 7f ac a6 64 eb 73 3a fd 14 d8 82 "
- "72 59 d9 9a 75 50 fa ca 50 1e f2 b0 4e 33 c2 3a "
- "a5 1f 4b 9e 82 82 ef db 72 8c c0 ab 09 40 5a 91 "
- "60 7c 63 69 96 1b c8 27 0d 2d 4f 39 fc e6 12 b1 ";
-
-// RSASSA-PSS Signature Example 1.4
-// Message to be signed:
-static const char message_1_4[] =
- "bc 65 67 47 fa 9e af b3 f0 ";
-// Salt:
-static const char salt_1_4[] =
- "05 6f 00 98 5d e1 4d 8e f5 ce a9 e8 2f 8c 27 be "
- "f7 20 33 5e ";
-// Signature:
-static const char signature_1_4[] =
- "46 09 79 3b 23 e9 d0 93 62 dc 21 bb 47 da 0b 4f "
- "3a 76 22 64 9a 47 d4 64 01 9b 9a ea fe 53 35 9c "
- "17 8c 91 cd 58 ba 6b cb 78 be 03 46 a7 bc 63 7f "
- "4b 87 3d 4b ab 38 ee 66 1f 19 96 34 c5 47 a1 ad "
- "84 42 e0 3d a0 15 b1 36 e5 43 f7 ab 07 c0 c1 3e "
- "42 25 b8 de 8c ce 25 d4 f6 eb 84 00 f8 1f 7e 18 "
- "33 b7 ee 6e 33 4d 37 09 64 ca 79 fd b8 72 b4 d7 "
- "52 23 b5 ee b0 81 01 59 1f b5 32 d1 55 a6 de 87 ";
-
-// RSASSA-PSS Signature Example 1.5
-// Message to be signed:
-static const char message_1_5[] =
- "b4 55 81 54 7e 54 27 77 0c 76 8e 8b 82 b7 55 64 "
- "e0 ea 4e 9c 32 59 4d 6b ff 70 65 44 de 0a 87 76 "
- "c7 a8 0b 45 76 55 0e ee 1b 2a ca bc 7e 8b 7d 3e "
- "f7 bb 5b 03 e4 62 c1 10 47 ea dd 00 62 9a e5 75 "
- "48 0a c1 47 0f e0 46 f1 3a 2b f5 af 17 92 1d c4 "
- "b0 aa 8b 02 be e6 33 49 11 65 1d 7f 85 25 d1 0f "
- "32 b5 1d 33 be 52 0d 3d df 5a 70 99 55 a3 df e7 "
- "82 83 b9 e0 ab 54 04 6d 15 0c 17 7f 03 7f dc cc "
- "5b e4 ea 5f 68 b5 e5 a3 8c 9d 7e dc cc c4 97 5f "
- "45 5a 69 09 b4 ";
-// Salt:
-static const char salt_1_5[] =
- "80 e7 0f f8 6a 08 de 3e c6 09 72 b3 9b 4f bf dc "
- "ea 67 ae 8e ";
-// Signature:
-static const char signature_1_5[] =
- "1d 2a ad 22 1c a4 d3 1d df 13 50 92 39 01 93 98 "
- "e3 d1 4b 32 dc 34 dc 5a f4 ae ae a3 c0 95 af 73 "
- "47 9c f0 a4 5e 56 29 63 5a 53 a0 18 37 76 15 b1 "
- "6c b9 b1 3b 3e 09 d6 71 eb 71 e3 87 b8 54 5c 59 "
- "60 da 5a 64 77 6e 76 8e 82 b2 c9 35 83 bf 10 4c "
- "3f db 23 51 2b 7b 4e 89 f6 33 dd 00 63 a5 30 db "
- "45 24 b0 1c 3f 38 4c 09 31 0e 31 5a 79 dc d3 d6 "
- "84 02 2a 7f 31 c8 65 a6 64 e3 16 97 8b 75 9f ad ";
-
-// RSASSA-PSS Signature Example 1.6
-// Message to be signed:
-static const char message_1_6[] =
- "10 aa e9 a0 ab 0b 59 5d 08 41 20 7b 70 0d 48 d7 "
- "5f ae dd e3 b7 75 cd 6b 4c c8 8a e0 6e 46 94 ec "
- "74 ba 18 f8 52 0d 4f 5e a6 9c bb e7 cc 2b eb a4 "
- "3e fd c1 02 15 ac 4e b3 2d c3 02 a1 f5 3d c6 c4 "
- "35 22 67 e7 93 6c fe bf 7c 8d 67 03 57 84 a3 90 "
- "9f a8 59 c7 b7 b5 9b 8e 39 c5 c2 34 9f 18 86 b7 "
- "05 a3 02 67 d4 02 f7 48 6a b4 f5 8c ad 5d 69 ad "
- "b1 7a b8 cd 0c e1 ca f5 02 5a f4 ae 24 b1 fb 87 "
- "94 c6 07 0c c0 9a 51 e2 f9 91 13 11 e3 87 7d 00 "
- "44 c7 1c 57 a9 93 39 50 08 80 6b 72 3a c3 83 73 "
- "d3 95 48 18 18 52 8c 1e 70 53 73 92 82 05 35 29 "
- "51 0e 93 5c d0 fa 77 b8 fa 53 cc 2d 47 4b d4 fb "
- "3c c5 c6 72 d6 ff dc 90 a0 0f 98 48 71 2c 4b cf "
- "e4 6c 60 57 36 59 b1 1e 64 57 e8 61 f0 f6 04 b6 "
- "13 8d 14 4f 8c e4 e2 da 73 ";
-// Salt:
-static const char salt_1_6[] =
- "a8 ab 69 dd 80 1f 00 74 c2 a1 fc 60 64 98 36 c6 "
- "16 d9 96 81 ";
-// Signature:
-static const char signature_1_6[] =
- "2a 34 f6 12 5e 1f 6b 0b f9 71 e8 4f bd 41 c6 32 "
- "be 8f 2c 2a ce 7d e8 b6 92 6e 31 ff 93 e9 af 98 "
- "7f bc 06 e5 1e 9b e1 4f 51 98 f9 1f 3f 95 3b d6 "
- "7d a6 0a 9d f5 97 64 c3 dc 0f e0 8e 1c be f0 b7 "
- "5f 86 8d 10 ad 3f ba 74 9f ef 59 fb 6d ac 46 a0 "
- "d6 e5 04 36 93 31 58 6f 58 e4 62 8f 39 aa 27 89 "
- "82 54 3b c0 ee b5 37 dc 61 95 80 19 b3 94 fb 27 "
- "3f 21 58 58 a0 a0 1a c4 d6 50 b9 55 c6 7f 4c 58 ";
-
-// Example 9: A 1536-bit RSA Key Pair
-
-// RSA modulus n:
-static const char rsa_modulus_n_9[] =
- "e6 bd 69 2a c9 66 45 79 04 03 fd d0 f5 be b8 b9 "
- "bf 92 ed 10 00 7f c3 65 04 64 19 dd 06 c0 5c 5b "
- "5b 2f 48 ec f9 89 e4 ce 26 91 09 97 9c bb 40 b4 "
- "a0 ad 24 d2 24 83 d1 ee 31 5a d4 cc b1 53 42 68 "
- "35 26 91 c5 24 f6 dd 8e 6c 29 d2 24 cf 24 69 73 "
- "ae c8 6c 5b f6 b1 40 1a 85 0d 1b 9a d1 bb 8c bc "
- "ec 47 b0 6f 0f 8c 7f 45 d3 fc 8f 31 92 99 c5 43 "
- "3d db c2 b3 05 3b 47 de d2 ec d4 a4 ca ef d6 14 "
- "83 3d c8 bb 62 2f 31 7e d0 76 b8 05 7f e8 de 3f "
- "84 48 0a d5 e8 3e 4a 61 90 4a 4f 24 8f b3 97 02 "
- "73 57 e1 d3 0e 46 31 39 81 5c 6f d4 fd 5a c5 b8 "
- "17 2a 45 23 0e cb 63 18 a0 4f 14 55 d8 4e 5a 8b ";
-// RSA public exponent e:
-static const char rsa_public_exponent_e_9[] =
- "01 00 01 ";
-
-// RSASSA-PSS Signature Example 9.1
-// Message to be signed:
-static const char message_9_1[] =
- "a8 8e 26 58 55 e9 d7 ca 36 c6 87 95 f0 b3 1b 59 "
- "1c d6 58 7c 71 d0 60 a0 b3 f7 f3 ea ef 43 79 59 "
- "22 02 8b c2 b6 ad 46 7c fc 2d 7f 65 9c 53 85 aa "
- "70 ba 36 72 cd de 4c fe 49 70 cc 79 04 60 1b 27 "
- "88 72 bf 51 32 1c 4a 97 2f 3c 95 57 0f 34 45 d4 "
- "f5 79 80 e0 f2 0d f5 48 46 e6 a5 2c 66 8f 12 88 "
- "c0 3f 95 00 6e a3 2f 56 2d 40 d5 2a f9 fe b3 2f "
- "0f a0 6d b6 5b 58 8a 23 7b 34 e5 92 d5 5c f9 79 "
- "f9 03 a6 42 ef 64 d2 ed 54 2a a8 c7 7d c1 dd 76 "
- "2f 45 a5 93 03 ed 75 e5 41 ca 27 1e 2b 60 ca 70 "
- "9e 44 fa 06 61 13 1e 8d 5d 41 63 fd 8d 39 85 66 "
- "ce 26 de 87 30 e7 2f 9c ca 73 76 41 c2 44 15 94 "
- "20 63 70 28 df 0a 18 07 9d 62 08 ea 8b 47 11 a2 "
- "c7 50 f5 ";
-// Salt:
-static const char salt_9_1[] =
- "c0 a4 25 31 3d f8 d7 56 4b d2 43 4d 31 15 23 d5 "
- "25 7e ed 80 ";
-// Signature:
-static const char signature_9_1[] =
- "58 61 07 22 6c 3c e0 13 a7 c8 f0 4d 1a 6a 29 59 "
- "bb 4b 8e 20 5b a4 3a 27 b5 0f 12 41 11 bc 35 ef "
- "58 9b 03 9f 59 32 18 7c b6 96 d7 d9 a3 2c 0c 38 "
- "30 0a 5c dd a4 83 4b 62 d2 eb 24 0a f3 3f 79 d1 "
- "3d fb f0 95 bf 59 9e 0d 96 86 94 8c 19 64 74 7b "
- "67 e8 9c 9a ba 5c d8 50 16 23 6f 56 6c c5 80 2c "
- "b1 3e ad 51 bc 7c a6 be f3 b9 4d cb db b1 d5 70 "
- "46 97 71 df 0e 00 b1 a8 a0 67 77 47 2d 23 16 27 "
- "9e da e8 64 74 66 8d 4e 1e ff f9 5f 1d e6 1c 60 "
- "20 da 32 ae 92 bb f1 65 20 fe f3 cf 4d 88 f6 11 "
- "21 f2 4b bd 9f e9 1b 59 ca f1 23 5b 2a 93 ff 81 "
- "fc 40 3a dd f4 eb de a8 49 34 a9 cd af 8e 1a 9e ";
-
-// RSASSA-PSS Signature Example 9.2
-// Message to be signed:
-static const char message_9_2[] =
- "c8 c9 c6 af 04 ac da 41 4d 22 7e f2 3e 08 20 c3 "
- "73 2c 50 0d c8 72 75 e9 5b 0d 09 54 13 99 3c 26 "
- "58 bc 1d 98 85 81 ba 87 9c 2d 20 1f 14 cb 88 ce "
- "d1 53 a0 19 69 a7 bf 0a 7b e7 9c 84 c1 48 6b c1 "
- "2b 3f a6 c5 98 71 b6 82 7c 8c e2 53 ca 5f ef a8 "
- "a8 c6 90 bf 32 6e 8e 37 cd b9 6d 90 a8 2e ba b6 "
- "9f 86 35 0e 18 22 e8 bd 53 6a 2e ";
-// Salt:
-static const char salt_9_2[] =
- "b3 07 c4 3b 48 50 a8 da c2 f1 5f 32 e3 78 39 ef "
- "8c 5c 0e 91 ";
-// Signature:
-static const char signature_9_2[] =
- "80 b6 d6 43 25 52 09 f0 a4 56 76 38 97 ac 9e d2 "
- "59 d4 59 b4 9c 28 87 e5 88 2e cb 44 34 cf d6 6d "
- "d7 e1 69 93 75 38 1e 51 cd 7f 55 4f 2c 27 17 04 "
- "b3 99 d4 2b 4b e2 54 0a 0e ca 61 95 1f 55 26 7f "
- "7c 28 78 c1 22 84 2d ad b2 8b 01 bd 5f 8c 02 5f "
- "7e 22 84 18 a6 73 c0 3d 6b c0 c7 36 d0 a2 95 46 "
- "bd 67 f7 86 d9 d6 92 cc ea 77 8d 71 d9 8c 20 63 "
- "b7 a7 10 92 18 7a 4d 35 af 10 81 11 d8 3e 83 ea "
- "e4 6c 46 aa 34 27 7e 06 04 45 89 90 37 88 f1 d5 "
- "e7 ce e2 5f b4 85 e9 29 49 11 88 14 d6 f2 c3 ee "
- "36 14 89 01 6f 32 7f b5 bc 51 7e b5 04 70 bf fa "
- "1a fa 5f 4c e9 aa 0c e5 b8 ee 19 bf 55 01 b9 58 ";
-
-// RSASSA-PSS Signature Example 9.3
-// Message to be signed:
-static const char message_9_3[] =
- "0a fa d4 2c cd 4f c6 06 54 a5 50 02 d2 28 f5 2a "
- "4a 5f e0 3b 8b bb 08 ca 82 da ca 55 8b 44 db e1 "
- "26 6e 50 c0 e7 45 a3 6d 9d 29 04 e3 40 8a bc d1 "
- "fd 56 99 94 06 3f 4a 75 cc 72 f2 fe e2 a0 cd 89 "
- "3a 43 af 1c 5b 8b 48 7d f0 a7 16 10 02 4e 4f 6d "
- "df 9f 28 ad 08 13 c1 aa b9 1b cb 3c 90 64 d5 ff "
- "74 2d ef fe a6 57 09 41 39 36 9e 5e a6 f4 a9 63 "
- "19 a5 cc 82 24 14 5b 54 50 62 75 8f ef d1 fe 34 "
- "09 ae 16 92 59 c6 cd fd 6b 5f 29 58 e3 14 fa ec "
- "be 69 d2 ca ce 58 ee 55 17 9a b9 b3 e6 d1 ec c1 "
- "4a 55 7c 5f eb e9 88 59 52 64 fc 5d a1 c5 71 46 "
- "2e ca 79 8a 18 a1 a4 94 0c da b4 a3 e9 20 09 cc "
- "d4 2e 1e 94 7b 13 14 e3 22 38 a2 de ce 7d 23 a8 "
- "9b 5b 30 c7 51 fd 0a 4a 43 0d 2c 54 85 94 ";
-// Salt:
-static const char salt_9_3[] =
- "9a 2b 00 7e 80 97 8b bb 19 2c 35 4e b7 da 9a ed "
- "fc 74 db f5 ";
-// Signature:
-static const char signature_9_3[] =
- "48 44 08 f3 89 8c d5 f5 34 83 f8 08 19 ef bf 27 "
- "08 c3 4d 27 a8 b2 a6 fa e8 b3 22 f9 24 02 37 f9 "
- "81 81 7a ca 18 46 f1 08 4d aa 6d 7c 07 95 f6 e5 "
- "bf 1a f5 9c 38 e1 85 84 37 ce 1f 7e c4 19 b9 8c "
- "87 36 ad f6 dd 9a 00 b1 80 6d 2b d3 ad 0a 73 77 "
- "5e 05 f5 2d fe f3 a5 9a b4 b0 81 43 f0 df 05 cd "
- "1a d9 d0 4b ec ec a6 da a4 a2 12 98 03 e2 00 cb "
- "c7 77 87 ca f4 c1 d0 66 3a 6c 59 87 b6 05 95 20 "
- "19 78 2c af 2e c1 42 6d 68 fb 94 ed 1d 4b e8 16 "
- "a7 ed 08 1b 77 e6 ab 33 0b 3f fc 07 38 20 fe cd "
- "e3 72 7f cb e2 95 ee 61 a0 50 a3 43 65 86 37 c3 "
- "fd 65 9c fb 63 73 6d e3 2d 9f 90 d3 c2 f6 3e ca ";
-
-// RSASSA-PSS Signature Example 9.4
-// Message to be signed:
-static const char message_9_4[] =
- "1d fd 43 b4 6c 93 db 82 62 9b da e2 bd 0a 12 b8 "
- "82 ea 04 c3 b4 65 f5 cf 93 02 3f 01 05 96 26 db "
- "be 99 f2 6b b1 be 94 9d dd d1 6d c7 f3 de bb 19 "
- "a1 94 62 7f 0b 22 44 34 df 7d 87 00 e9 e9 8b 06 "
- "e3 60 c1 2f db e3 d1 9f 51 c9 68 4e b9 08 9e cb "
- "b0 a2 f0 45 03 99 d3 f5 9e ac 72 94 08 5d 04 4f "
- "53 93 c6 ce 73 74 23 d8 b8 6c 41 53 70 d3 89 e3 "
- "0b 9f 0a 3c 02 d2 5d 00 82 e8 ad 6f 3f 1e f2 4a "
- "45 c3 cf 82 b3 83 36 70 63 a4 d4 61 3e 42 64 f0 "
- "1b 2d ac 2e 5a a4 20 43 f8 fb 5f 69 fa 87 1d 14 "
- "fb 27 3e 76 7a 53 1c 40 f0 2f 34 3b c2 fb 45 a0 "
- "c7 e0 f6 be 25 61 92 3a 77 21 1d 66 a6 e2 db b4 "
- "3c 36 63 50 be ae 22 da 3a c2 c1 f5 07 70 96 fc "
- "b5 c4 bf 25 5f 75 74 35 1a e0 b1 e1 f0 36 32 81 "
- "7c 08 56 d4 a8 ba 97 af bd c8 b8 58 55 40 2b c5 "
- "69 26 fc ec 20 9f 9e a8 ";
-// Salt:
-static const char salt_9_4[] =
- "70 f3 82 bd df 4d 5d 2d d8 8b 3b c7 b7 30 8b e6 "
- "32 b8 40 45 ";
-// Signature:
-static const char signature_9_4[] =
- "84 eb eb 48 1b e5 98 45 b4 64 68 ba fb 47 1c 01 "
- "12 e0 2b 23 5d 84 b5 d9 11 cb d1 92 6e e5 07 4a "
- "e0 42 44 95 cb 20 e8 23 08 b8 eb b6 5f 41 9a 03 "
- "fb 40 e7 2b 78 98 1d 88 aa d1 43 05 36 85 17 2c "
- "97 b2 9c 8b 7b f0 ae 73 b5 b2 26 3c 40 3d a0 ed "
- "2f 80 ff 74 50 af 78 28 eb 8b 86 f0 02 8b d2 a8 "
- "b1 76 a4 d2 28 cc ce a1 83 94 f2 38 b0 9f f7 58 "
- "cc 00 bc 04 30 11 52 35 57 42 f2 82 b5 4e 66 3a "
- "91 9e 70 9d 8d a2 4a de 55 00 a7 b9 aa 50 22 6e "
- "0c a5 29 23 e6 c2 d8 60 ec 50 ff 48 0f a5 74 77 "
- "e8 2b 05 65 f4 37 9f 79 c7 72 d5 c2 da 80 af 9f "
- "bf 32 5e ce 6f c2 0b 00 96 16 14 be e8 9a 18 3e ";
-
-// RSASSA-PSS Signature Example 9.5
-// Message to be signed:
-static const char message_9_5[] =
- "1b dc 6e 7c 98 fb 8c f5 4e 9b 09 7b 66 a8 31 e9 "
- "cf e5 2d 9d 48 88 44 8e e4 b0 97 80 93 ba 1d 7d "
- "73 ae 78 b3 a6 2b a4 ad 95 cd 28 9c cb 9e 00 52 "
- "26 bb 3d 17 8b cc aa 82 1f b0 44 a4 e2 1e e9 76 "
- "96 c1 4d 06 78 c9 4c 2d ae 93 b0 ad 73 92 22 18 "
- "55 3d aa 7e 44 eb e5 77 25 a7 a4 5c c7 2b 9b 21 "
- "38 a6 b1 7c 8d b4 11 ce 82 79 ee 12 41 af f0 a8 "
- "be c6 f7 7f 87 ed b0 c6 9c b2 72 36 e3 43 5a 80 "
- "0b 19 2e 4f 11 e5 19 e3 fe 30 fc 30 ea cc ca 4f "
- "bb 41 76 90 29 bf 70 8e 81 7a 9e 68 38 05 be 67 "
- "fa 10 09 84 68 3b 74 83 8e 3b cf fa 79 36 6e ed "
- "1d 48 1c 76 72 91 18 83 8f 31 ba 8a 04 8a 93 c1 "
- "be 44 24 59 8e 8d f6 32 8b 7a 77 88 0a 3f 9c 7e "
- "2e 8d fc a8 eb 5a 26 fb 86 bd c5 56 d4 2b be 01 "
- "d9 fa 6e d8 06 46 49 1c 93 41 ";
-// Salt:
-static const char salt_9_5[] =
- "d6 89 25 7a 86 ef fa 68 21 2c 5e 0c 61 9e ca 29 "
- "5f b9 1b 67 ";
-// Signature:
-static const char signature_9_5[] =
- "82 10 2d f8 cb 91 e7 17 99 19 a0 4d 26 d3 35 d6 "
- "4f bc 2f 87 2c 44 83 39 43 24 1d e8 45 48 10 27 "
- "4c df 3d b5 f4 2d 42 3d b1 52 af 71 35 f7 01 42 "
- "0e 39 b4 94 a6 7c bf d1 9f 91 19 da 23 3a 23 da "
- "5c 64 39 b5 ba 0d 2b c3 73 ee e3 50 70 01 37 8d "
- "4a 40 73 85 6b 7f e2 ab a0 b5 ee 93 b2 7f 4a fe "
- "c7 d4 d1 20 92 1c 83 f6 06 76 5b 02 c1 9e 4d 6a "
- "1a 3b 95 fa 4c 42 29 51 be 4f 52 13 10 77 ef 17 "
- "17 97 29 cd df bd b5 69 50 db ac ee fe 78 cb 16 "
- "64 0a 09 9e a5 6d 24 38 9e ef 10 f8 fe cb 31 ba "
- "3e a3 b2 27 c0 a8 66 98 bb 89 e3 e9 36 39 05 bf "
- "22 77 7b 2a 3a a5 21 b6 5b 4c ef 76 d8 3b de 4c ";
-
-// RSASSA-PSS Signature Example 9.6
-// Message to be signed:
-static const char message_9_6[] =
- "88 c7 a9 f1 36 04 01 d9 0e 53 b1 01 b6 1c 53 25 "
- "c3 c7 5d b1 b4 11 fb eb 8e 83 0b 75 e9 6b 56 67 "
- "0a d2 45 40 4e 16 79 35 44 ee 35 4b c6 13 a9 0c "
- "c9 84 87 15 a7 3d b5 89 3e 7f 6d 27 98 15 c0 c1 "
- "de 83 ef 8e 29 56 e3 a5 6e d2 6a 88 8d 7a 9c dc "
- "d0 42 f4 b1 6b 7f a5 1e f1 a0 57 36 62 d1 6a 30 "
- "2d 0e c5 b2 85 d2 e0 3a d9 65 29 c8 7b 3d 37 4d "
- "b3 72 d9 5b 24 43 d0 61 b6 b1 a3 50 ba 87 80 7e "
- "d0 83 af d1 eb 05 c3 f5 2f 4e ba 5e d2 22 77 14 "
- "fd b5 0b 9d 9d 9d d6 81 4f 62 f6 27 2f cd 5c db "
- "ce 7a 9e f7 97 ";
-// Salt:
-static const char salt_9_6[] =
- "c2 5f 13 bf 67 d0 81 67 1a 04 81 a1 f1 82 0d 61 "
- "3b ba 22 76 ";
-// Signature:
-static const char signature_9_6[] =
- "a7 fd b0 d2 59 16 5c a2 c8 8d 00 bb f1 02 8a 86 "
- "7d 33 76 99 d0 61 19 3b 17 a9 64 8e 14 cc bb aa "
- "de ac aa cd ec 81 5e 75 71 29 4e bb 8a 11 7a f2 "
- "05 fa 07 8b 47 b0 71 2c 19 9e 3a d0 51 35 c5 04 "
- "c2 4b 81 70 51 15 74 08 02 48 79 92 ff d5 11 d4 "
- "af c6 b8 54 49 1e b3 f0 dd 52 31 39 54 2f f1 5c "
- "31 01 ee 85 54 35 17 c6 a3 c7 94 17 c6 7e 2d d9 "
- "aa 74 1e 9a 29 b0 6d cb 59 3c 23 36 b3 67 0a e3 "
- "af ba c7 c3 e7 6e 21 54 73 e8 66 e3 38 ca 24 4d "
- "e0 0b 62 62 4d 6b 94 26 82 2c ea e9 f8 cc 46 08 "
- "95 f4 12 50 07 3f d4 5c 5a 1e 7b 42 5c 20 4a 42 "
- "3a 69 91 59 f6 90 3e 71 0b 37 a7 bb 2b c8 04 9f ";
-
-// Example 10: A 2048-bit RSA Key Pair
-
-// RSA modulus n:
-static const char rsa_modulus_n_10[] =
- "a5 dd 86 7a c4 cb 02 f9 0b 94 57 d4 8c 14 a7 70 "
- "ef 99 1c 56 c3 9c 0e c6 5f d1 1a fa 89 37 ce a5 "
- "7b 9b e7 ac 73 b4 5c 00 17 61 5b 82 d6 22 e3 18 "
- "75 3b 60 27 c0 fd 15 7b e1 2f 80 90 fe e2 a7 ad "
- "cd 0e ef 75 9f 88 ba 49 97 c7 a4 2d 58 c9 aa 12 "
- "cb 99 ae 00 1f e5 21 c1 3b b5 43 14 45 a8 d5 ae "
- "4f 5e 4c 7e 94 8a c2 27 d3 60 40 71 f2 0e 57 7e "
- "90 5f be b1 5d fa f0 6d 1d e5 ae 62 53 d6 3a 6a "
- "21 20 b3 1a 5d a5 da bc 95 50 60 0e 20 f2 7d 37 "
- "39 e2 62 79 25 fe a3 cc 50 9f 21 df f0 4e 6e ea "
- "45 49 c5 40 d6 80 9f f9 30 7e ed e9 1f ff 58 73 "
- "3d 83 85 a2 37 d6 d3 70 5a 33 e3 91 90 09 92 07 "
- "0d f7 ad f1 35 7c f7 e3 70 0c e3 66 7d e8 3f 17 "
- "b8 df 17 78 db 38 1d ce 09 cb 4a d0 58 a5 11 00 "
- "1a 73 81 98 ee 27 cf 55 a1 3b 75 45 39 90 65 82 "
- "ec 8b 17 4b d5 8d 5d 1f 3d 76 7c 61 37 21 ae 05 ";
-// RSA public exponent e:
-static const char rsa_public_exponent_e_10[] =
- "01 00 01 ";
-
-// RSASSA-PSS Signature Example 10.1
-// Message to be signed:
-static const char message_10_1[] =
- "88 31 77 e5 12 6b 9b e2 d9 a9 68 03 27 d5 37 0c "
- "6f 26 86 1f 58 20 c4 3d a6 7a 3a d6 09 ";
-// Salt:
-static const char salt_10_1[] =
- "04 e2 15 ee 6f f9 34 b9 da 70 d7 73 0c 87 34 ab "
- "fc ec de 89 ";
-// Signature:
-static const char signature_10_1[] =
- "82 c2 b1 60 09 3b 8a a3 c0 f7 52 2b 19 f8 73 54 "
- "06 6c 77 84 7a bf 2a 9f ce 54 2d 0e 84 e9 20 c5 "
- "af b4 9f fd fd ac e1 65 60 ee 94 a1 36 96 01 14 "
- "8e ba d7 a0 e1 51 cf 16 33 17 91 a5 72 7d 05 f2 "
- "1e 74 e7 eb 81 14 40 20 69 35 d7 44 76 5a 15 e7 "
- "9f 01 5c b6 6c 53 2c 87 a6 a0 59 61 c8 bf ad 74 "
- "1a 9a 66 57 02 28 94 39 3e 72 23 73 97 96 c0 2a "
- "77 45 5d 0f 55 5b 0e c0 1d df 25 9b 62 07 fd 0f "
- "d5 76 14 ce f1 a5 57 3b aa ff 4e c0 00 69 95 16 "
- "59 b8 5f 24 30 0a 25 16 0c a8 52 2d c6 e6 72 7e "
- "57 d0 19 d7 e6 36 29 b8 fe 5e 89 e2 5c c1 5b eb "
- "3a 64 75 77 55 92 99 28 0b 9b 28 f7 9b 04 09 00 "
- "0b e2 5b bd 96 40 8b a3 b4 3c c4 86 18 4d d1 c8 "
- "e6 25 53 fa 1a f4 04 0f 60 66 3d e7 f5 e4 9c 04 "
- "38 8e 25 7f 1c e8 9c 95 da b4 8a 31 5d 9b 66 b1 "
- "b7 62 82 33 87 6f f2 38 52 30 d0 70 d0 7e 16 66 ";
-
-// RSASSA-PSS Signature Example 10.2
-// Message to be signed:
-static const char message_10_2[] =
- "dd 67 0a 01 46 58 68 ad c9 3f 26 13 19 57 a5 0c "
- "52 fb 77 7c db aa 30 89 2c 9e 12 36 11 64 ec 13 "
- "97 9d 43 04 81 18 e4 44 5d b8 7b ee 58 dd 98 7b "
- "34 25 d0 20 71 d8 db ae 80 70 8b 03 9d bb 64 db "
- "d1 de 56 57 d9 fe d0 c1 18 a5 41 43 74 2e 0f f3 "
- "c8 7f 74 e4 58 57 64 7a f3 f7 9e b0 a1 4c 9d 75 "
- "ea 9a 1a 04 b7 cf 47 8a 89 7a 70 8f d9 88 f4 8e "
- "80 1e db 0b 70 39 df 8c 23 bb 3c 56 f4 e8 21 ac ";
-// Salt:
-static const char salt_10_2[] =
- "8b 2b dd 4b 40 fa f5 45 c7 78 dd f9 bc 1a 49 cb "
- "57 f9 b7 1b ";
-// Signature:
-static const char signature_10_2[] =
- "14 ae 35 d9 dd 06 ba 92 f7 f3 b8 97 97 8a ed 7c "
- "d4 bf 5f f0 b5 85 a4 0b d4 6c e1 b4 2c d2 70 30 "
- "53 bb 90 44 d6 4e 81 3d 8f 96 db 2d d7 00 7d 10 "
- "11 8f 6f 8f 84 96 09 7a d7 5e 1f f6 92 34 1b 28 "
- "92 ad 55 a6 33 a1 c5 5e 7f 0a 0a d5 9a 0e 20 3a "
- "5b 82 78 ae c5 4d d8 62 2e 28 31 d8 71 74 f8 ca "
- "ff 43 ee 6c 46 44 53 45 d8 4a 59 65 9b fb 92 ec "
- "d4 c8 18 66 86 95 f3 47 06 f6 68 28 a8 99 59 63 "
- "7f 2b f3 e3 25 1c 24 bd ba 4d 4b 76 49 da 00 22 "
- "21 8b 11 9c 84 e7 9a 65 27 ec 5b 8a 5f 86 1c 15 "
- "99 52 e2 3e c0 5e 1e 71 73 46 fa ef e8 b1 68 68 "
- "25 bd 2b 26 2f b2 53 10 66 c0 de 09 ac de 2e 42 "
- "31 69 07 28 b5 d8 5e 11 5a 2f 6b 92 b7 9c 25 ab "
- "c9 bd 93 99 ff 8b cf 82 5a 52 ea 1f 56 ea 76 dd "
- "26 f4 3b aa fa 18 bf a9 2a 50 4c bd 35 69 9e 26 "
- "d1 dc c5 a2 88 73 85 f3 c6 32 32 f0 6f 32 44 c3 ";
-
-// RSASSA-PSS Signature Example 10.3
-// Message to be signed:
-static const char message_10_3[] =
- "48 b2 b6 a5 7a 63 c8 4c ea 85 9d 65 c6 68 28 4b "
- "08 d9 6b dc aa be 25 2d b0 e4 a9 6c b1 ba c6 01 "
- "93 41 db 6f be fb 8d 10 6b 0e 90 ed a6 bc c6 c6 "
- "26 2f 37 e7 ea 9c 7e 5d 22 6b d7 df 85 ec 5e 71 "
- "ef ff 2f 54 c5 db 57 7f f7 29 ff 91 b8 42 49 1d "
- "e2 74 1d 0c 63 16 07 df 58 6b 90 5b 23 b9 1a f1 "
- "3d a1 23 04 bf 83 ec a8 a7 3e 87 1f f9 db ";
-// Salt:
-static const char salt_10_3[] =
- "4e 96 fc 1b 39 8f 92 b4 46 71 01 0c 0d c3 ef d6 "
- "e2 0c 2d 73 ";
-// Signature:
-static const char signature_10_3[] =
- "6e 3e 4d 7b 6b 15 d2 fb 46 01 3b 89 00 aa 5b bb "
- "39 39 cf 2c 09 57 17 98 70 42 02 6e e6 2c 74 c5 "
- "4c ff d5 d7 d5 7e fb bf 95 0a 0f 5c 57 4f a0 9d "
- "3f c1 c9 f5 13 b0 5b 4f f5 0d d8 df 7e df a2 01 "
- "02 85 4c 35 e5 92 18 01 19 a7 0c e5 b0 85 18 2a "
- "a0 2d 9e a2 aa 90 d1 df 03 f2 da ae 88 5b a2 f5 "
- "d0 5a fd ac 97 47 6f 06 b9 3b 5b c9 4a 1a 80 aa "
- "91 16 c4 d6 15 f3 33 b0 98 89 2b 25 ff ac e2 66 "
- "f5 db 5a 5a 3b cc 10 a8 24 ed 55 aa d3 5b 72 78 "
- "34 fb 8c 07 da 28 fc f4 16 a5 d9 b2 22 4f 1f 8b "
- "44 2b 36 f9 1e 45 6f de a2 d7 cf e3 36 72 68 de "
- "03 07 a4 c7 4e 92 41 59 ed 33 39 3d 5e 06 55 53 "
- "1c 77 32 7b 89 82 1b de df 88 01 61 c7 8c d4 19 "
- "6b 54 19 f7 ac c3 f1 3e 5e bf 16 1b 6e 7c 67 24 "
- "71 6c a3 3b 85 c2 e2 56 40 19 2a c2 85 96 51 d5 "
- "0b de 7e b9 76 e5 1c ec 82 8b 98 b6 56 3b 86 bb ";
-
-// RSASSA-PSS Signature Example 10.4
-// Message to be signed:
-static const char message_10_4[] =
- "0b 87 77 c7 f8 39 ba f0 a6 4b bb db c5 ce 79 75 "
- "5c 57 a2 05 b8 45 c1 74 e2 d2 e9 05 46 a0 89 c4 "
- "e6 ec 8a df fa 23 a7 ea 97 ba e6 b6 5d 78 2b 82 "
- "db 5d 2b 5a 56 d2 2a 29 a0 5e 7c 44 33 e2 b8 2a "
- "62 1a bb a9 0a dd 05 ce 39 3f c4 8a 84 05 42 45 "
- "1a ";
-// Salt:
-static const char salt_10_4[] =
- "c7 cd 69 8d 84 b6 51 28 d8 83 5e 3a 8b 1e b0 e0 "
- "1c b5 41 ec ";
-// Signature:
-static const char signature_10_4[] =
- "34 04 7f f9 6c 4d c0 dc 90 b2 d4 ff 59 a1 a3 61 "
- "a4 75 4b 25 5d 2e e0 af 7d 8b f8 7c 9b c9 e7 dd "
- "ee de 33 93 4c 63 ca 1c 0e 3d 26 2c b1 45 ef 93 "
- "2a 1f 2c 0a 99 7a a6 a3 4f 8e ae e7 47 7d 82 cc "
- "f0 90 95 a6 b8 ac ad 38 d4 ee c9 fb 7e ab 7a d0 "
- "2d a1 d1 1d 8e 54 c1 82 5e 55 bf 58 c2 a2 32 34 "
- "b9 02 be 12 4f 9e 90 38 a8 f6 8f a4 5d ab 72 f6 "
- "6e 09 45 bf 1d 8b ac c9 04 4c 6f 07 09 8c 9f ce "
- "c5 8a 3a ab 10 0c 80 51 78 15 5f 03 0a 12 4c 45 "
- "0e 5a cb da 47 d0 e4 f1 0b 80 a2 3f 80 3e 77 4d "
- "02 3b 00 15 c2 0b 9f 9b be 7c 91 29 63 38 d5 ec "
- "b4 71 ca fb 03 20 07 b6 7a 60 be 5f 69 50 4a 9f "
- "01 ab b3 cb 46 7b 26 0e 2b ce 86 0b e8 d9 5b f9 "
- "2c 0c 8e 14 96 ed 1e 52 85 93 a4 ab b6 df 46 2d "
- "de 8a 09 68 df fe 46 83 11 68 57 a2 32 f5 eb f6 "
- "c8 5b e2 38 74 5a d0 f3 8f 76 7a 5f db f4 86 fb ";
-
-// RSASSA-PSS Signature Example 10.5
-// Message to be signed:
-static const char message_10_5[] =
- "f1 03 6e 00 8e 71 e9 64 da dc 92 19 ed 30 e1 7f "
- "06 b4 b6 8a 95 5c 16 b3 12 b1 ed df 02 8b 74 97 "
- "6b ed 6b 3f 6a 63 d4 e7 78 59 24 3c 9c cc dc 98 "
- "01 65 23 ab b0 24 83 b3 55 91 c3 3a ad 81 21 3b "
- "b7 c7 bb 1a 47 0a ab c1 0d 44 25 6c 4d 45 59 d9 "
- "16 ";
-// Salt:
-static const char salt_10_5[] =
- "ef a8 bf f9 62 12 b2 f4 a3 f3 71 a1 0d 57 41 52 "
- "65 5f 5d fb ";
-// Signature:
-static const char signature_10_5[] =
- "7e 09 35 ea 18 f4 d6 c1 d1 7c e8 2e b2 b3 83 6c "
- "55 b3 84 58 9c e1 9d fe 74 33 63 ac 99 48 d1 f3 "
- "46 b7 bf dd fe 92 ef d7 8a db 21 fa ef c8 9a de "
- "42 b1 0f 37 40 03 fe 12 2e 67 42 9a 1c b8 cb d1 "
- "f8 d9 01 45 64 c4 4d 12 01 16 f4 99 0f 1a 6e 38 "
- "77 4c 19 4b d1 b8 21 32 86 b0 77 b0 49 9d 2e 7b "
- "3f 43 4a b1 22 89 c5 56 68 4d ee d7 81 31 93 4b "
- "b3 dd 65 37 23 6f 7c 6f 3d cb 09 d4 76 be 07 72 "
- "1e 37 e1 ce ed 9b 2f 7b 40 68 87 bd 53 15 73 05 "
- "e1 c8 b4 f8 4d 73 3b c1 e1 86 fe 06 cc 59 b6 ed "
- "b8 f4 bd 7f fe fd f4 f7 ba 9c fb 9d 57 06 89 b5 "
- "a1 a4 10 9a 74 6a 69 08 93 db 37 99 25 5a 0c b9 "
- "21 5d 2d 1c d4 90 59 0e 95 2e 8c 87 86 aa 00 11 "
- "26 52 52 47 0c 04 1d fb c3 ee c7 c3 cb f7 1c 24 "
- "86 9d 11 5c 0c b4 a9 56 f5 6d 53 0b 80 ab 58 9a "
- "cf ef c6 90 75 1d df 36 e8 d3 83 f8 3c ed d2 cc ";
-
-// RSASSA-PSS Signature Example 10.6
-// Message to be signed:
-static const char message_10_6[] =
- "25 f1 08 95 a8 77 16 c1 37 45 0b b9 51 9d fa a1 "
- "f2 07 fa a9 42 ea 88 ab f7 1e 9c 17 98 00 85 b5 "
- "55 ae ba b7 62 64 ae 2a 3a b9 3c 2d 12 98 11 91 "
- "dd ac 6f b5 94 9e b3 6a ee 3c 5d a9 40 f0 07 52 "
- "c9 16 d9 46 08 fa 7d 97 ba 6a 29 15 b6 88 f2 03 "
- "23 d4 e9 d9 68 01 d8 9a 72 ab 58 92 dc 21 17 c0 "
- "74 34 fc f9 72 e0 58 cf 8c 41 ca 4b 4f f5 54 f7 "
- "d5 06 8a d3 15 5f ce d0 f3 12 5b c0 4f 91 93 37 "
- "8a 8f 5c 4c 3b 8c b4 dd 6d 1c c6 9d 30 ec ca 6e "
- "aa 51 e3 6a 05 73 0e 9e 34 2e 85 5b af 09 9d ef "
- "b8 af d7 ";
-// Salt:
-static const char salt_10_6[] =
- "ad 8b 15 23 70 36 46 22 4b 66 0b 55 08 85 91 7c "
- "a2 d1 df 28 ";
-// Signature:
-static const char signature_10_6[] =
- "6d 3b 5b 87 f6 7e a6 57 af 21 f7 54 41 97 7d 21 "
- "80 f9 1b 2c 5f 69 2d e8 29 55 69 6a 68 67 30 d9 "
- "b9 77 8d 97 07 58 cc b2 60 71 c2 20 9f fb d6 12 "
- "5b e2 e9 6e a8 1b 67 cb 9b 93 08 23 9f da 17 f7 "
- "b2 b6 4e cd a0 96 b6 b9 35 64 0a 5a 1c b4 2a 91 "
- "55 b1 c9 ef 7a 63 3a 02 c5 9f 0d 6e e5 9b 85 2c "
- "43 b3 50 29 e7 3c 94 0f f0 41 0e 8f 11 4e ed 46 "
- "bb d0 fa e1 65 e4 2b e2 52 8a 40 1c 3b 28 fd 81 "
- "8e f3 23 2d ca 9f 4d 2a 0f 51 66 ec 59 c4 23 96 "
- "d6 c1 1d bc 12 15 a5 6f a1 71 69 db 95 75 34 3e "
- "f3 4f 9d e3 2a 49 cd c3 17 49 22 f2 29 c2 3e 18 "
- "e4 5d f9 35 31 19 ec 43 19 ce dc e7 a1 7c 64 08 "
- "8c 1f 6f 52 be 29 63 41 00 b3 91 9d 38 f3 d1 ed "
- "94 e6 89 1e 66 a7 3b 8f b8 49 f5 87 4d f5 94 59 "
- "e2 98 c7 bb ce 2e ee 78 2a 19 5a a6 6f e2 d0 73 "
- "2b 25 e5 95 f5 7d 3e 06 1b 1f c3 e4 06 3b f9 8f ";
-
-struct SignatureExample {
- const char* message;
- const char* salt;
- const char* signature;
+// -----BEGIN RSA PRIVATE KEY-----
+// MIIEowIBAAKCAQEArg5NXFRQQ5QU7dcqqIjZwL4qy4AaJNSPfSPvXmFbK0hDXdp6
+// PdOZ2Wd+lQLZwb7ZQ2ZdqHVK3kZ2sVUlFmngIoEXNhVg+gW2zGPZ1YemwBMdZ/NW
+// V2xTX7Y3RrdR/kSccd9ByRTHKb+BCJ2XN5pHu91+LFchahW0lVPHz9DkBPUCThM2
+// I4ZosM3+AcO93RrrcbiQdpuY60Lfg9ZX7+1clM7zhiuOjWNY+FLN4+j4Ec8isiis
+// /V1LQyxRZ2t29kto47UJKu0Li7gUvEE1PS/nXBVgEqcSEBBKXa4ahsTqKWJAwvEH
+// xaH1t2qhVO1IHcf9FSv5k1T47H7XMLpO2OCPrwIDAQABAoIBAQCXA4exTOHa0Dcc
+// aGv1j87GAPimWX3VaKsaGzyKuZNdSTRR0MXwsI+yZa4Y4UFHbSuZ483s499SXPaM
+// Q2CLQs8ZgME/xmq+YojIavXL4wcVbUA9OY43CaCI0VLCQzmbj7HgxqCQMzvdh+8P
+// J5PUxUHpyHG5TNuL7EsiqG8bapT7ip2+IpKrKjr18gn3k0k9mLNJxK9Qr+CJphwo
+// eJgq0Kcjx3bfgDEpPzyvdd+J3e+jclOTYbk2HwJ0FVCrfgJedHFIWUytZoM5783g
+// knXzgDyKs65aUDjc/opidXp3WOqfNJUPSiofPYPdYQ26UI0vztL5MBkWCpl+d/55
+// BqxCdDlhAoGBANm90DFUca+7LdnDgj8mWtUIzr+XVzSD9tzOIpcjiPwEnxk8RHrM
+// aMHCAKZpbsnX/ikdc2I1OsirPgNFh1q30xgL7oCadzxlwfXnEM0Nff8RJKtN+yI6
+// +nRoOCDGCHBsaa2wyMYRbnanyRDLPIOP4eGQ6Hz/LQJBvhjRyTXlrUU/AoGBAMyj
+// ec1ySnlJ2S0JqPBCk14dRsEs/zStgFz1Wdmk7TMRBPUMyhWf8JwNrU5Ppm9biJMo
+// MKwkiFjzv/us4ne3wFRsTiKj4uiIwfji7/N2VpbEXSDGtonrX7hES4wQ/s+qr8XJ
+// 8ykHrZ9rPOY2lBhxOo+VYE3U6aspAY/qwK8WyumRAoGAMdl+/Iw0quLTkHNuMj75
+// tKQbkUl4sZE0x0B6Mtfz2J7GPeTKWMLLiPB9bZvdvWAx0//mFqnRF3f87orQfjhv
+// n6W7qL20ZqN1UHLiKc/Y9LhcCMwFnsSZ6mSh1P8Bl5t6ZkV+8bmz7H5lTe75n7Ul
+// JZsjXtqc11NtzgjZY/l9PckCgYAH6ZI+FVs30VkqWqNDlu9nxi4ELh84BDVgYsQ0
+// nCHnxZKxfusZZvPAtO6shnvi9mETf4xSO59iARq9OnQPOPWgzgc/Y6LUZuVJIE0y
+// 1rKGZdVL/SL1tjofP9TD96xCj1D4jtRuE7Ps5BKYvCeBwm8HOjldCQx357/9to/4
+// tSLnYQKBgG7STr94Slb3/BzzMxdMLCum1PH73/+IFxu1J0cXxYLP2zEhcSgqGIwm
+// aMgdu1L9eE6lJM99AWTEkpcUz8UFwNt+hZW+lZZpex77RMgRl9VCiwid1BNBSU/o
+// +lT1mlpDrPCNOge9n6Qvy5waBugB8uNS86w0UImYiKZr+8IQ4EdE
+// -----END RSA PRIVATE KEY-----
+const uint8_t kPSSPublicKey[] = {
+ 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
+ 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xae, 0x0e, 0x4d,
+ 0x5c, 0x54, 0x50, 0x43, 0x94, 0x14, 0xed, 0xd7, 0x2a, 0xa8, 0x88, 0xd9,
+ 0xc0, 0xbe, 0x2a, 0xcb, 0x80, 0x1a, 0x24, 0xd4, 0x8f, 0x7d, 0x23, 0xef,
+ 0x5e, 0x61, 0x5b, 0x2b, 0x48, 0x43, 0x5d, 0xda, 0x7a, 0x3d, 0xd3, 0x99,
+ 0xd9, 0x67, 0x7e, 0x95, 0x02, 0xd9, 0xc1, 0xbe, 0xd9, 0x43, 0x66, 0x5d,
+ 0xa8, 0x75, 0x4a, 0xde, 0x46, 0x76, 0xb1, 0x55, 0x25, 0x16, 0x69, 0xe0,
+ 0x22, 0x81, 0x17, 0x36, 0x15, 0x60, 0xfa, 0x05, 0xb6, 0xcc, 0x63, 0xd9,
+ 0xd5, 0x87, 0xa6, 0xc0, 0x13, 0x1d, 0x67, 0xf3, 0x56, 0x57, 0x6c, 0x53,
+ 0x5f, 0xb6, 0x37, 0x46, 0xb7, 0x51, 0xfe, 0x44, 0x9c, 0x71, 0xdf, 0x41,
+ 0xc9, 0x14, 0xc7, 0x29, 0xbf, 0x81, 0x08, 0x9d, 0x97, 0x37, 0x9a, 0x47,
+ 0xbb, 0xdd, 0x7e, 0x2c, 0x57, 0x21, 0x6a, 0x15, 0xb4, 0x95, 0x53, 0xc7,
+ 0xcf, 0xd0, 0xe4, 0x04, 0xf5, 0x02, 0x4e, 0x13, 0x36, 0x23, 0x86, 0x68,
+ 0xb0, 0xcd, 0xfe, 0x01, 0xc3, 0xbd, 0xdd, 0x1a, 0xeb, 0x71, 0xb8, 0x90,
+ 0x76, 0x9b, 0x98, 0xeb, 0x42, 0xdf, 0x83, 0xd6, 0x57, 0xef, 0xed, 0x5c,
+ 0x94, 0xce, 0xf3, 0x86, 0x2b, 0x8e, 0x8d, 0x63, 0x58, 0xf8, 0x52, 0xcd,
+ 0xe3, 0xe8, 0xf8, 0x11, 0xcf, 0x22, 0xb2, 0x28, 0xac, 0xfd, 0x5d, 0x4b,
+ 0x43, 0x2c, 0x51, 0x67, 0x6b, 0x76, 0xf6, 0x4b, 0x68, 0xe3, 0xb5, 0x09,
+ 0x2a, 0xed, 0x0b, 0x8b, 0xb8, 0x14, 0xbc, 0x41, 0x35, 0x3d, 0x2f, 0xe7,
+ 0x5c, 0x15, 0x60, 0x12, 0xa7, 0x12, 0x10, 0x10, 0x4a, 0x5d, 0xae, 0x1a,
+ 0x86, 0xc4, 0xea, 0x29, 0x62, 0x40, 0xc2, 0xf1, 0x07, 0xc5, 0xa1, 0xf5,
+ 0xb7, 0x6a, 0xa1, 0x54, 0xed, 0x48, 0x1d, 0xc7, 0xfd, 0x15, 0x2b, 0xf9,
+ 0x93, 0x54, 0xf8, 0xec, 0x7e, 0xd7, 0x30, 0xba, 0x4e, 0xd8, 0xe0, 0x8f,
+ 0xaf, 0x02, 0x03, 0x01, 0x00, 0x01,
};
-struct PSSTestVector {
- const char* modulus_n;
- const char* public_exponent_e;
- SignatureExample example[6];
+const uint8_t kPSSMessage[] = {
+ 0x1e, 0x70, 0xbd, 0xeb, 0x24, 0xf2, 0x9d, 0x05, 0xc5, 0xb5,
+ 0xf4, 0xca, 0xe6, 0x1d, 0x01, 0x97, 0x29, 0xf4, 0xe0, 0x7c,
+ 0xfd, 0xcc, 0x97, 0x8d, 0xc2, 0xbb, 0x2d, 0x9b, 0x6b, 0x45,
+ 0x06, 0xbd, 0x2c, 0x66, 0x10, 0x42, 0x73, 0x8d, 0x88, 0x9b,
+ 0x18, 0xcc, 0xcb, 0x7e, 0x43, 0x23, 0x06, 0xe9, 0x8f, 0x8f,
};
-static const PSSTestVector pss_test[] = {
- {
- rsa_modulus_n_1,
- rsa_public_exponent_e_1,
- {
- { message_1_1, salt_1_1, signature_1_1 },
- { message_1_2, salt_1_2, signature_1_2 },
- { message_1_3, salt_1_3, signature_1_3 },
- { message_1_4, salt_1_4, signature_1_4 },
- { message_1_5, salt_1_5, signature_1_5 },
- { message_1_6, salt_1_6, signature_1_6 },
- }
- },
- {
- rsa_modulus_n_9,
- rsa_public_exponent_e_9,
- {
- { message_9_1, salt_9_1, signature_9_1 },
- { message_9_2, salt_9_2, signature_9_2 },
- { message_9_3, salt_9_3, signature_9_3 },
- { message_9_4, salt_9_4, signature_9_4 },
- { message_9_5, salt_9_5, signature_9_5 },
- { message_9_6, salt_9_6, signature_9_6 },
- }
- },
- {
- rsa_modulus_n_10,
- rsa_public_exponent_e_10,
- {
- { message_10_1, salt_10_1, signature_10_1 },
- { message_10_2, salt_10_2, signature_10_2 },
- { message_10_3, salt_10_3, signature_10_3 },
- { message_10_4, salt_10_4, signature_10_4 },
- { message_10_5, salt_10_5, signature_10_5 },
- { message_10_6, salt_10_6, signature_10_6 },
- }
- },
+const uint8_t kPSSSignatureGood[] = {
+ 0x12, 0xa7, 0x6d, 0x9e, 0x8a, 0xea, 0x28, 0xe0, 0x3f, 0x6f, 0x5a, 0xa4,
+ 0x1b, 0x6a, 0x0a, 0x14, 0xba, 0xfa, 0x84, 0xf6, 0xb7, 0x3c, 0xc9, 0xd6,
+ 0x84, 0xab, 0x1e, 0x77, 0x88, 0x53, 0x95, 0x43, 0x8e, 0x73, 0xe4, 0x21,
+ 0xab, 0x69, 0xb2, 0x0c, 0x73, 0x4d, 0x98, 0x42, 0xbd, 0x65, 0xa2, 0x95,
+ 0x0d, 0x76, 0xb2, 0xbd, 0xe5, 0x9a, 0x6e, 0x9f, 0x72, 0x7f, 0xdd, 0x1e,
+ 0x9f, 0xda, 0xc8, 0x2e, 0xa3, 0xe6, 0x28, 0x03, 0x98, 0x5c, 0x13, 0xa7,
+ 0x7d, 0x4e, 0xde, 0xea, 0x35, 0x1b, 0x35, 0x7e, 0xaa, 0x14, 0xf9, 0xfb,
+ 0xac, 0x61, 0xd0, 0x44, 0x20, 0xd5, 0x52, 0x5b, 0x92, 0x8f, 0xe7, 0x37,
+ 0xa2, 0x72, 0x7d, 0xe6, 0x0d, 0x81, 0x63, 0xcc, 0x0f, 0xbd, 0xde, 0x25,
+ 0xe3, 0x3f, 0x89, 0x1b, 0x39, 0x64, 0xfa, 0x21, 0x1d, 0x0f, 0x9b, 0x8a,
+ 0xc1, 0xad, 0x03, 0x49, 0x96, 0xff, 0x9f, 0x2d, 0x83, 0xee, 0x2d, 0x2a,
+ 0x1e, 0xc5, 0x73, 0x9f, 0x5b, 0xde, 0xcb, 0xaf, 0x02, 0xbd, 0xc5, 0x9b,
+ 0x78, 0xb9, 0x8e, 0x01, 0x75, 0x3c, 0xc9, 0x6e, 0x7d, 0x3e, 0x61, 0x62,
+ 0xc4, 0x8c, 0x9e, 0x76, 0xed, 0x52, 0x5e, 0x80, 0x89, 0xa7, 0x75, 0x5e,
+ 0xc6, 0x34, 0x97, 0x22, 0x40, 0xb5, 0x0c, 0x77, 0x09, 0x8c, 0xa8, 0xe9,
+ 0xf6, 0x8d, 0xc0, 0x10, 0x78, 0x92, 0xa9, 0xc6, 0x68, 0xa3, 0x57, 0x6e,
+ 0x73, 0xb5, 0x73, 0x8d, 0x8e, 0x21, 0xb1, 0xf3, 0xd0, 0x0a, 0x40, 0x68,
+ 0xfc, 0x3c, 0xeb, 0xd3, 0x48, 0x4a, 0x44, 0xbd, 0xc0, 0x40, 0x5d, 0x9b,
+ 0x40, 0x6f, 0x45, 0x98, 0x2b, 0xae, 0x58, 0xe8, 0x9d, 0x34, 0x49, 0xd2,
+ 0xec, 0xdc, 0xd5, 0x98, 0xb4, 0x87, 0x8a, 0xcc, 0x41, 0x3e, 0xd7, 0xe6,
+ 0x21, 0xd6, 0x4c, 0x89, 0xf1, 0xf4, 0x77, 0x40, 0x3f, 0x9a, 0x28, 0x25,
+ 0x55, 0x7c, 0xf5, 0x0c,
};
-static uint8_t HexDigitValue(char digit) {
- if ('0' <= digit && digit <= '9')
- return digit - '0';
- if ('a' <= digit && digit <= 'f')
- return digit - 'a' + 10;
- return digit - 'A' + 10;
-}
-
-static bool DecodeTestInput(const char* in, std::vector<uint8_t>* out) {
- out->clear();
- while (in[0] != '\0') {
- if (!isxdigit(in[0]) || !isxdigit(in[1]) || in[2] != ' ')
- return false;
- uint8_t octet = HexDigitValue(in[0]) * 16 + HexDigitValue(in[1]);
- out->push_back(octet);
- in += 3;
- }
- return true;
-}
-
-// PrependASN1Length prepends an ASN.1 serialized length to the beginning of
-// |out|.
-static void PrependASN1Length(std::vector<uint8_t>* out, size_t len) {
- if (len < 128) {
- out->insert(out->begin(), static_cast<uint8_t>(len));
- } else if (len < 256) {
- out->insert(out->begin(), static_cast<uint8_t>(len));
- out->insert(out->begin(), 0x81);
- } else if (len < 0x10000) {
- out->insert(out->begin(), static_cast<uint8_t>(len));
- out->insert(out->begin(), static_cast<uint8_t>(len >> 8));
- out->insert(out->begin(), 0x82);
- } else {
- CHECK(false) << "ASN.1 length not handled: " << len;
- }
-}
-
-static bool EncodeRSAPublicKey(const std::vector<uint8_t>& modulus_n,
- const std::vector<uint8_t>& public_exponent_e,
- std::vector<uint8_t>* public_key_info) {
- // The public key is specified as the following ASN.1 structure:
- // SubjectPublicKeyInfo ::= SEQUENCE {
- // algorithm AlgorithmIdentifier,
- // subjectPublicKey BIT STRING }
- //
- // The algorithm is specified as the following ASN.1 structure:
- // AlgorithmIdentifier ::= SEQUENCE {
- // algorithm OBJECT IDENTIFIER,
- // parameters ANY DEFINED BY algorithm OPTIONAL }
- //
- // An RSA public key is specified as the following ASN.1 structure:
- // RSAPublicKey ::= SEQUENCE {
- // modulus INTEGER, -- n
- // publicExponent INTEGER -- e
- // }
- static const uint8_t kIntegerTag = 0x02;
- static const uint8_t kBitStringTag = 0x03;
- static const uint8_t kSequenceTag = 0x30;
- public_key_info->clear();
-
- // Encode the public exponent e as an INTEGER.
- public_key_info->insert(public_key_info->begin(),
- public_exponent_e.begin(),
- public_exponent_e.end());
- PrependASN1Length(public_key_info, public_exponent_e.size());
- public_key_info->insert(public_key_info->begin(), kIntegerTag);
-
- // Encode the modulus n as an INTEGER.
- public_key_info->insert(public_key_info->begin(),
- modulus_n.begin(), modulus_n.end());
- size_t modulus_size = modulus_n.size();
- if (modulus_n[0] & 0x80) {
- public_key_info->insert(public_key_info->begin(), 0x00);
- modulus_size++;
- }
- PrependASN1Length(public_key_info, modulus_size);
- public_key_info->insert(public_key_info->begin(), kIntegerTag);
-
- // Encode the RSAPublicKey SEQUENCE.
- PrependASN1Length(public_key_info, public_key_info->size());
- public_key_info->insert(public_key_info->begin(), kSequenceTag);
-
- // Encode the BIT STRING.
- // Number of unused bits.
- public_key_info->insert(public_key_info->begin(), 0x00);
- PrependASN1Length(public_key_info, public_key_info->size());
- public_key_info->insert(public_key_info->begin(), kBitStringTag);
-
- // Encode the AlgorithmIdentifier.
- static const uint8_t algorithm[] = {
- 0x30, 0x0d, // a SEQUENCE of length 13
- 0x06, 0x09, // an OBJECT IDENTIFIER of length 9
- 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
- };
- public_key_info->insert(public_key_info->begin(),
- algorithm, algorithm + sizeof(algorithm));
-
- // Encode the outermost SEQUENCE.
- PrependASN1Length(public_key_info, public_key_info->size());
- public_key_info->insert(public_key_info->begin(), kSequenceTag);
+const uint8_t kPSSSignatureBadSaltLength[] = {
+ 0x6e, 0x61, 0xbe, 0x8a, 0x82, 0xbd, 0xed, 0xc6, 0xe4, 0x33, 0x91, 0xa4,
+ 0x43, 0x57, 0x51, 0x7e, 0xa8, 0x18, 0xbf, 0x20, 0x98, 0xbc, 0x04, 0x50,
+ 0x06, 0x1b, 0x0b, 0xb6, 0x43, 0xde, 0x58, 0x7f, 0x6b, 0xa5, 0x5e, 0x9d,
+ 0xd1, 0x75, 0x03, 0xf5, 0x19, 0x8d, 0xdb, 0x2c, 0xd2, 0x9a, 0xf9, 0xbd,
+ 0x82, 0x8d, 0x32, 0x9d, 0x7d, 0x70, 0x6f, 0x81, 0x95, 0x60, 0x1d, 0x62,
+ 0x72, 0xf3, 0x95, 0x5b, 0x7a, 0x66, 0x7f, 0x45, 0x94, 0x0c, 0x07, 0xc8,
+ 0xa7, 0x64, 0x38, 0x57, 0x1a, 0x64, 0x64, 0xf1, 0xe0, 0x45, 0xfe, 0x00,
+ 0x11, 0x90, 0x57, 0x95, 0x15, 0x21, 0x10, 0x85, 0xc0, 0xbe, 0x53, 0x5b,
+ 0x3b, 0xa3, 0x57, 0x99, 0x2b, 0x94, 0x6b, 0xbf, 0xa5, 0x55, 0x7d, 0x5a,
+ 0xcb, 0xa2, 0x73, 0x6b, 0x5f, 0x7b, 0x3f, 0x10, 0x90, 0xd1, 0x26, 0x72,
+ 0x5e, 0xad, 0xd1, 0x34, 0xe8, 0x8a, 0x33, 0xeb, 0xd2, 0xbf, 0x54, 0x92,
+ 0xeb, 0x7c, 0xb9, 0x97, 0x80, 0x5a, 0x46, 0xc4, 0xbd, 0xf5, 0x7e, 0xd6,
+ 0x20, 0x90, 0x92, 0xcb, 0x37, 0x85, 0x9d, 0x81, 0x0a, 0xd0, 0xa5, 0x73,
+ 0x17, 0x7e, 0xe2, 0x91, 0xef, 0x35, 0x55, 0xc9, 0x5e, 0x87, 0x84, 0x11,
+ 0xa4, 0x36, 0xf0, 0x2a, 0xa7, 0x7a, 0x83, 0x1d, 0x7a, 0x90, 0x69, 0x22,
+ 0x5d, 0x3b, 0x30, 0x48, 0x46, 0xd2, 0xd3, 0x49, 0x23, 0x64, 0xa4, 0x6d,
+ 0xd1, 0xef, 0xb9, 0x1b, 0xa4, 0xd1, 0x92, 0xdd, 0x8c, 0xb2, 0xaa, 0x9f,
+ 0x6a, 0x2c, 0xc9, 0xdb, 0xa7, 0x35, 0x66, 0x92, 0x8b, 0x73, 0x11, 0x70,
+ 0x2b, 0xf4, 0x34, 0x3f, 0x9e, 0x15, 0x3e, 0xc0, 0xac, 0x78, 0x6f, 0x74,
+ 0x8a, 0x6b, 0xe4, 0xf2, 0x7b, 0x10, 0xca, 0x01, 0x3a, 0x3a, 0x88, 0x39,
+ 0x34, 0xa8, 0x52, 0x4a, 0x76, 0x50, 0xef, 0xdb, 0x91, 0x3c, 0x4a, 0x5c,
+ 0xe5, 0x43, 0x6f, 0x8e,
+};
- return true;
-}
+} // namespace
TEST(SignatureVerifierTest, VerifyRSAPSS) {
- for (unsigned int i = 0; i < arraysize(pss_test); i++) {
- SCOPED_TRACE(i);
- std::vector<uint8_t> modulus_n;
- std::vector<uint8_t> public_exponent_e;
- ASSERT_TRUE(DecodeTestInput(pss_test[i].modulus_n, &modulus_n));
- ASSERT_TRUE(DecodeTestInput(pss_test[i].public_exponent_e,
- &public_exponent_e));
- std::vector<uint8_t> public_key_info;
- ASSERT_TRUE(EncodeRSAPublicKey(modulus_n, public_exponent_e,
- &public_key_info));
-
- for (unsigned int j = 0; j < arraysize(pss_test[i].example); j++) {
- SCOPED_TRACE(j);
- std::vector<uint8_t> message;
- std::vector<uint8_t> salt;
- std::vector<uint8_t> signature;
- ASSERT_TRUE(DecodeTestInput(pss_test[i].example[j].message, &message));
- ASSERT_TRUE(DecodeTestInput(pss_test[i].example[j].salt, &salt));
- ASSERT_TRUE(DecodeTestInput(pss_test[i].example[j].signature,
- &signature));
-
- crypto::SignatureVerifier verifier;
- bool ok;
-
- // Positive test.
- ok = verifier.VerifyInitRSAPSS(crypto::SignatureVerifier::SHA1,
- crypto::SignatureVerifier::SHA1,
- salt.size(),
- &signature[0], signature.size(),
- &public_key_info[0],
- public_key_info.size());
- ASSERT_TRUE(ok);
- verifier.VerifyUpdate(&message[0], message.size());
- ok = verifier.VerifyFinal();
- EXPECT_TRUE(ok);
-
- // Modify the first byte of the message.
- ok = verifier.VerifyInitRSAPSS(crypto::SignatureVerifier::SHA1,
- crypto::SignatureVerifier::SHA1,
- salt.size(),
- &signature[0], signature.size(),
- &public_key_info[0],
- public_key_info.size());
- ASSERT_TRUE(ok);
- message[0] += 1;
- verifier.VerifyUpdate(&message[0], message.size());
- message[0] -= 1;
- ok = verifier.VerifyFinal();
- EXPECT_FALSE(ok);
-
- // Truncate the message.
- ASSERT_FALSE(message.empty());
- ok = verifier.VerifyInitRSAPSS(crypto::SignatureVerifier::SHA1,
- crypto::SignatureVerifier::SHA1,
- salt.size(),
- &signature[0], signature.size(),
- &public_key_info[0],
- public_key_info.size());
- ASSERT_TRUE(ok);
- verifier.VerifyUpdate(&message[0], message.size() - 1);
- ok = verifier.VerifyFinal();
- EXPECT_FALSE(ok);
-
- // Corrupt the signature.
- signature[0] += 1;
- ok = verifier.VerifyInitRSAPSS(crypto::SignatureVerifier::SHA1,
- crypto::SignatureVerifier::SHA1,
- salt.size(),
- &signature[0], signature.size(),
- &public_key_info[0],
- public_key_info.size());
- signature[0] -= 1;
- ASSERT_TRUE(ok);
- verifier.VerifyUpdate(&message[0], message.size());
- ok = verifier.VerifyFinal();
- EXPECT_FALSE(ok);
- }
+ // Verify the test vector.
+ crypto::SignatureVerifier verifier;
+ ASSERT_TRUE(verifier.VerifyInit(crypto::SignatureVerifier::RSA_PSS_SHA256,
+ kPSSSignatureGood, kPSSPublicKey));
+ verifier.VerifyUpdate(kPSSMessage);
+ EXPECT_TRUE(verifier.VerifyFinal());
+
+ // Verify the test vector byte-by-byte.
+ ASSERT_TRUE(verifier.VerifyInit(crypto::SignatureVerifier::RSA_PSS_SHA256,
+ kPSSSignatureGood, kPSSPublicKey));
+ for (uint8_t b : kPSSMessage) {
+ verifier.VerifyUpdate(base::make_span(&b, 1));
}
+ EXPECT_TRUE(verifier.VerifyFinal());
+
+ // The bad salt length does not verify.
+ ASSERT_TRUE(verifier.VerifyInit(crypto::SignatureVerifier::RSA_PSS_SHA256,
+ kPSSSignatureBadSaltLength, kPSSPublicKey));
+ verifier.VerifyUpdate(kPSSMessage);
+ EXPECT_FALSE(verifier.VerifyFinal());
+
+ // Corrupt the message.
+ std::vector<uint8_t> message(std::begin(kPSSMessage), std::end(kPSSMessage));
+ message[0] ^= 1;
+ ASSERT_TRUE(verifier.VerifyInit(crypto::SignatureVerifier::RSA_PSS_SHA256,
+ kPSSSignatureGood, kPSSPublicKey));
+ verifier.VerifyUpdate(message);
+ EXPECT_FALSE(verifier.VerifyFinal());
+
+ // Corrupt the signature.
+ std::vector<uint8_t> signature(std::begin(kPSSSignatureGood),
+ std::end(kPSSSignatureGood));
+ signature[0] ^= 1;
+ ASSERT_TRUE(verifier.VerifyInit(crypto::SignatureVerifier::RSA_PSS_SHA256,
+ signature, kPSSPublicKey));
+ verifier.VerifyUpdate(kPSSMessage);
+ EXPECT_FALSE(verifier.VerifyFinal());
}
diff --git a/crypto/symmetric_key.cc b/crypto/symmetric_key.cc
index 6a19f84089..55ffb72fb8 100644
--- a/crypto/symmetric_key.cc
+++ b/crypto/symmetric_key.cc
@@ -100,11 +100,6 @@ std::unique_ptr<SymmetricKey> SymmetricKey::Import(Algorithm algorithm,
return key;
}
-bool SymmetricKey::GetRawKey(std::string* raw_key) {
- *raw_key = key_;
- return true;
-}
-
SymmetricKey::SymmetricKey() = default;
} // namespace crypto
diff --git a/crypto/symmetric_key.h b/crypto/symmetric_key.h
index 7494634b5e..9803cdcf24 100644
--- a/crypto/symmetric_key.h
+++ b/crypto/symmetric_key.h
@@ -50,17 +50,13 @@ class CRYPTO_EXPORT SymmetricKey {
// Imports an array of key bytes in |raw_key|. This key may have been
// generated by GenerateRandomKey or DeriveKeyFromPassword and exported with
- // GetRawKey, or via another compatible method. The key must be of suitable
- // size for use with |algorithm|. The caller owns the returned SymmetricKey.
+ // key(). The key must be of suitable size for use with |algorithm|.
+ // The caller owns the returned SymmetricKey.
static std::unique_ptr<SymmetricKey> Import(Algorithm algorithm,
const std::string& raw_key);
- const std::string& key() { return key_; }
-
- // Extracts the raw key from the platform specific data.
- // Warning: |raw_key| holds the raw key as bytes and thus must be handled
- // carefully.
- bool GetRawKey(std::string* raw_key);
+ // Returns the raw platform specific key data.
+ const std::string& key() const { return key_; }
private:
SymmetricKey();
diff --git a/crypto/symmetric_key_unittest.cc b/crypto/symmetric_key_unittest.cc
index d954761d75..02f12e5def 100644
--- a/crypto/symmetric_key_unittest.cc
+++ b/crypto/symmetric_key_unittest.cc
@@ -15,36 +15,27 @@ TEST(SymmetricKeyTest, GenerateRandomKey) {
std::unique_ptr<crypto::SymmetricKey> key(
crypto::SymmetricKey::GenerateRandomKey(crypto::SymmetricKey::AES, 256));
ASSERT_TRUE(key);
- std::string raw_key;
- EXPECT_TRUE(key->GetRawKey(&raw_key));
- EXPECT_EQ(32U, raw_key.size());
+ EXPECT_EQ(32U, key->key().size());
// Do it again and check that the keys are different.
// (Note: this has a one-in-10^77 chance of failure!)
std::unique_ptr<crypto::SymmetricKey> key2(
crypto::SymmetricKey::GenerateRandomKey(crypto::SymmetricKey::AES, 256));
ASSERT_TRUE(key2);
- std::string raw_key2;
- EXPECT_TRUE(key2->GetRawKey(&raw_key2));
- EXPECT_EQ(32U, raw_key2.size());
- EXPECT_NE(raw_key, raw_key2);
+ EXPECT_EQ(32U, key2->key().size());
+ EXPECT_NE(key->key(), key2->key());
}
TEST(SymmetricKeyTest, ImportGeneratedKey) {
std::unique_ptr<crypto::SymmetricKey> key1(
crypto::SymmetricKey::GenerateRandomKey(crypto::SymmetricKey::AES, 256));
ASSERT_TRUE(key1);
- std::string raw_key1;
- EXPECT_TRUE(key1->GetRawKey(&raw_key1));
std::unique_ptr<crypto::SymmetricKey> key2(
- crypto::SymmetricKey::Import(crypto::SymmetricKey::AES, raw_key1));
+ crypto::SymmetricKey::Import(crypto::SymmetricKey::AES, key1->key()));
ASSERT_TRUE(key2);
- std::string raw_key2;
- EXPECT_TRUE(key2->GetRawKey(&raw_key2));
-
- EXPECT_EQ(raw_key1, raw_key2);
+ EXPECT_EQ(key1->key(), key2->key());
}
TEST(SymmetricKeyTest, ImportDerivedKey) {
@@ -52,17 +43,12 @@ TEST(SymmetricKeyTest, ImportDerivedKey) {
crypto::SymmetricKey::DeriveKeyFromPassword(
crypto::SymmetricKey::HMAC_SHA1, "password", "somesalt", 1024, 160));
ASSERT_TRUE(key1);
- std::string raw_key1;
- EXPECT_TRUE(key1->GetRawKey(&raw_key1));
- std::unique_ptr<crypto::SymmetricKey> key2(
- crypto::SymmetricKey::Import(crypto::SymmetricKey::HMAC_SHA1, raw_key1));
+ std::unique_ptr<crypto::SymmetricKey> key2(crypto::SymmetricKey::Import(
+ crypto::SymmetricKey::HMAC_SHA1, key1->key()));
ASSERT_TRUE(key2);
- std::string raw_key2;
- EXPECT_TRUE(key2->GetRawKey(&raw_key2));
-
- EXPECT_EQ(raw_key1, raw_key2);
+ EXPECT_EQ(key1->key(), key2->key());
}
struct PBKDF2TestVector {
@@ -86,8 +72,7 @@ TEST_P(SymmetricKeyDeriveKeyFromPasswordTest, DeriveKeyFromPassword) {
test_data.rounds, test_data.key_size_in_bits));
ASSERT_TRUE(key);
- std::string raw_key;
- key->GetRawKey(&raw_key);
+ const std::string& raw_key = key->key();
EXPECT_EQ(test_data.key_size_in_bits / 8, raw_key.size());
EXPECT_EQ(test_data.expected,
base::ToLowerASCII(base::HexEncode(raw_key.data(),
diff --git a/dbus/DEPS b/dbus/DEPS
deleted file mode 100644
index 97db67c0df..0000000000
--- a/dbus/DEPS
+++ /dev/null
@@ -1,3 +0,0 @@
-include_rules = [
- "+third_party/protobuf",
-]
diff --git a/dbus/bus.cc b/dbus/bus.cc
index a86971736f..e62058e0dc 100644
--- a/dbus/bus.cc
+++ b/dbus/bus.cc
@@ -6,9 +6,12 @@
#include <stddef.h>
+#include <memory>
+
#include "base/bind.h"
+#include "base/files/file_descriptor_watcher_posix.h"
#include "base/logging.h"
-#include "base/message_loop/message_loop.h"
+#include "base/memory/weak_ptr.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread.h"
@@ -42,14 +45,13 @@ const char kServiceNameOwnerChangeMatchRule[] =
// The class is used for watching the file descriptor used for D-Bus
// communication.
-class Watch : public base::MessagePumpLibevent::Watcher {
+class Watch {
public:
- explicit Watch(DBusWatch* watch)
- : raw_watch_(watch), file_descriptor_watcher_(FROM_HERE) {
- dbus_watch_set_data(raw_watch_, this, NULL);
+ explicit Watch(DBusWatch* watch) : raw_watch_(watch) {
+ dbus_watch_set_data(raw_watch_, this, nullptr);
}
- ~Watch() override { dbus_watch_set_data(raw_watch_, NULL, NULL); }
+ ~Watch() { dbus_watch_set_data(raw_watch_, nullptr, nullptr); }
// Returns true if the underlying file descriptor is ready to be watched.
bool IsReadyToBeWatched() {
@@ -59,62 +61,55 @@ class Watch : public base::MessagePumpLibevent::Watcher {
// Starts watching the underlying file descriptor.
void StartWatching() {
const int file_descriptor = dbus_watch_get_unix_fd(raw_watch_);
- const int flags = dbus_watch_get_flags(raw_watch_);
-
- base::MessageLoopForIO::Mode mode = base::MessageLoopForIO::WATCH_READ;
- if ((flags & DBUS_WATCH_READABLE) && (flags & DBUS_WATCH_WRITABLE))
- mode = base::MessageLoopForIO::WATCH_READ_WRITE;
- else if (flags & DBUS_WATCH_READABLE)
- mode = base::MessageLoopForIO::WATCH_READ;
- else if (flags & DBUS_WATCH_WRITABLE)
- mode = base::MessageLoopForIO::WATCH_WRITE;
- else
- NOTREACHED();
-
- const bool persistent = true; // Watch persistently.
- const bool success = base::MessageLoopForIO::current()->WatchFileDescriptor(
- file_descriptor, persistent, mode, &file_descriptor_watcher_, this);
- CHECK(success) << "Unable to allocate memory";
+ const unsigned int flags = dbus_watch_get_flags(raw_watch_);
+
+ // Using base::Unretained(this) is safe because watches are automatically
+ // canceled when |read_watcher_| and |write_watcher_| are destroyed.
+ if (flags & DBUS_WATCH_READABLE) {
+ read_watcher_ = base::FileDescriptorWatcher::WatchReadable(
+ file_descriptor,
+ base::Bind(&Watch::OnFileReady, base::Unretained(this),
+ DBUS_WATCH_READABLE));
+ }
+ if (flags & DBUS_WATCH_WRITABLE) {
+ write_watcher_ = base::FileDescriptorWatcher::WatchWritable(
+ file_descriptor,
+ base::Bind(&Watch::OnFileReady, base::Unretained(this),
+ DBUS_WATCH_WRITABLE));
+ }
}
// Stops watching the underlying file descriptor.
void StopWatching() {
- file_descriptor_watcher_.StopWatchingFileDescriptor();
+ read_watcher_.reset();
+ write_watcher_.reset();
}
private:
- // Implement MessagePumpLibevent::Watcher.
- void OnFileCanReadWithoutBlocking(int file_descriptor) override {
- const bool success = dbus_watch_handle(raw_watch_, DBUS_WATCH_READABLE);
- CHECK(success) << "Unable to allocate memory";
- }
-
- // Implement MessagePumpLibevent::Watcher.
- void OnFileCanWriteWithoutBlocking(int file_descriptor) override {
- const bool success = dbus_watch_handle(raw_watch_, DBUS_WATCH_WRITABLE);
- CHECK(success) << "Unable to allocate memory";
+ void OnFileReady(unsigned int flags) {
+ CHECK(dbus_watch_handle(raw_watch_, flags)) << "Unable to allocate memory";
}
DBusWatch* raw_watch_;
- base::MessagePumpLibevent::FileDescriptorWatcher file_descriptor_watcher_;
+ std::unique_ptr<base::FileDescriptorWatcher::Controller> read_watcher_;
+ std::unique_ptr<base::FileDescriptorWatcher::Controller> write_watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(Watch);
};
// The class is used for monitoring the timeout used for D-Bus method
// calls.
-//
-// Unlike Watch, Timeout is a ref counted object, to ensure that |this| of
-// the object is is alive when HandleTimeout() is called. It's unlikely
-// but it may be possible that HandleTimeout() is called after
-// Bus::OnRemoveTimeout(). That's why we don't simply delete the object in
-// Bus::OnRemoveTimeout().
-class Timeout : public base::RefCountedThreadSafe<Timeout> {
+class Timeout {
public:
explicit Timeout(DBusTimeout* timeout)
- : raw_timeout_(timeout),
- monitoring_is_active_(false),
- is_completed(false) {
- dbus_timeout_set_data(raw_timeout_, this, NULL);
- AddRef(); // Balanced on Complete().
+ : raw_timeout_(timeout), weak_ptr_factory_(this) {
+ // Associated |this| with the underlying DBusTimeout.
+ dbus_timeout_set_data(raw_timeout_, this, nullptr);
+ }
+
+ ~Timeout() {
+ // Remove the association between |this| and the |raw_timeout_|.
+ dbus_timeout_set_data(raw_timeout_, nullptr, nullptr);
}
// Returns true if the timeout is ready to be monitored.
@@ -126,54 +121,27 @@ class Timeout : public base::RefCountedThreadSafe<Timeout> {
void StartMonitoring(Bus* bus) {
bus->GetDBusTaskRunner()->PostDelayedTask(
FROM_HERE,
- base::Bind(&Timeout::HandleTimeout, this),
+ base::Bind(&Timeout::HandleTimeout, weak_ptr_factory_.GetWeakPtr()),
GetInterval());
- monitoring_is_active_ = true;
}
// Stops monitoring the timeout.
- void StopMonitoring() {
- // We cannot take back the delayed task we posted in
- // StartMonitoring(), so we just mark the monitoring is inactive now.
- monitoring_is_active_ = false;
- }
+ void StopMonitoring() { weak_ptr_factory_.InvalidateWeakPtrs(); }
- // Returns the interval.
base::TimeDelta GetInterval() {
return base::TimeDelta::FromMilliseconds(
dbus_timeout_get_interval(raw_timeout_));
}
- // Cleans up the raw_timeout and marks that timeout is completed.
- // See the class comment above for why we are doing this.
- void Complete() {
- dbus_timeout_set_data(raw_timeout_, NULL, NULL);
- is_completed = true;
- Release();
- }
-
private:
- friend class base::RefCountedThreadSafe<Timeout>;
- ~Timeout() {
- }
+ // Calls DBus to handle the timeout.
+ void HandleTimeout() { CHECK(dbus_timeout_handle(raw_timeout_)); }
- // Handles the timeout.
- void HandleTimeout() {
- // If the timeout is marked completed, we should do nothing. This can
- // occur if this function is called after Bus::OnRemoveTimeout().
- if (is_completed)
- return;
- // Skip if monitoring is canceled.
- if (!monitoring_is_active_)
- return;
+ DBusTimeout* raw_timeout_;
- const bool success = dbus_timeout_handle(raw_timeout_);
- CHECK(success) << "Unable to allocate memory";
- }
+ base::WeakPtrFactory<Timeout> weak_ptr_factory_;
- DBusTimeout* raw_timeout_;
- bool monitoring_is_active_;
- bool is_completed;
+ DISALLOW_COPY_AND_ASSIGN(Timeout);
};
} // namespace
@@ -183,8 +151,7 @@ Bus::Options::Options()
connection_type(PRIVATE) {
}
-Bus::Options::~Options() {
-}
+Bus::Options::~Options() = default;
Bus::Bus(const Options& options)
: bus_type_(options.bus_type),
@@ -192,7 +159,7 @@ Bus::Bus(const Options& options)
dbus_task_runner_(options.dbus_task_runner),
on_shutdown_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED),
- connection_(NULL),
+ connection_(nullptr),
origin_thread_id_(base::PlatformThread::CurrentId()),
async_operations_set_up_(false),
shutdown_completed_(false),
@@ -281,7 +248,7 @@ void Bus::RemoveObjectProxyInternal(scoped_refptr<ObjectProxy> object_proxy,
const base::Closure& callback) {
AssertOnDBusThread();
- object_proxy.get()->Detach();
+ object_proxy->Detach();
GetOriginTaskRunner()->PostTask(FROM_HERE, callback);
}
@@ -393,10 +360,10 @@ void Bus::RemoveObjectManagerInternalHelper(
scoped_refptr<dbus::ObjectManager> object_manager,
const base::Closure& callback) {
AssertOnOriginThread();
- DCHECK(object_manager.get());
+ DCHECK(object_manager);
// Release the object manager and run the callback.
- object_manager = NULL;
+ object_manager = nullptr;
callback.Run();
}
@@ -515,13 +482,13 @@ void Bus::ShutdownAndBlock() {
dbus_connection_unref(connection_);
}
- connection_ = NULL;
+ connection_ = nullptr;
shutdown_completed_ = true;
}
void Bus::ShutdownOnDBusThreadAndBlock() {
AssertOnOriginThread();
- DCHECK(dbus_task_runner_.get());
+ DCHECK(dbus_task_runner_);
GetDBusTaskRunner()->PostTask(
FROM_HERE,
@@ -627,27 +594,18 @@ bool Bus::SetUpAsyncOperations() {
// be called when the incoming data is ready.
ProcessAllIncomingDataIfAny();
- bool success = dbus_connection_set_watch_functions(connection_,
- &Bus::OnAddWatchThunk,
- &Bus::OnRemoveWatchThunk,
- &Bus::OnToggleWatchThunk,
- this,
- NULL);
+ bool success = dbus_connection_set_watch_functions(
+ connection_, &Bus::OnAddWatchThunk, &Bus::OnRemoveWatchThunk,
+ &Bus::OnToggleWatchThunk, this, nullptr);
CHECK(success) << "Unable to allocate memory";
- success = dbus_connection_set_timeout_functions(connection_,
- &Bus::OnAddTimeoutThunk,
- &Bus::OnRemoveTimeoutThunk,
- &Bus::OnToggleTimeoutThunk,
- this,
- NULL);
+ success = dbus_connection_set_timeout_functions(
+ connection_, &Bus::OnAddTimeoutThunk, &Bus::OnRemoveTimeoutThunk,
+ &Bus::OnToggleTimeoutThunk, this, nullptr);
CHECK(success) << "Unable to allocate memory";
dbus_connection_set_dispatch_status_function(
- connection_,
- &Bus::OnDispatchStatusChangedThunk,
- this,
- NULL);
+ connection_, &Bus::OnDispatchStatusChangedThunk, this, nullptr);
async_operations_set_up_ = true;
@@ -697,8 +655,8 @@ void Bus::AddFilterFunction(DBusHandleMessageFunction filter_function,
return;
}
- const bool success = dbus_connection_add_filter(
- connection_, filter_function, user_data, NULL);
+ const bool success = dbus_connection_add_filter(connection_, filter_function,
+ user_data, nullptr);
CHECK(success) << "Unable to allocate memory";
filter_functions_added_.insert(filter_data_pair);
}
@@ -827,19 +785,19 @@ void Bus::ProcessAllIncomingDataIfAny() {
}
base::TaskRunner* Bus::GetDBusTaskRunner() {
- if (dbus_task_runner_.get())
+ if (dbus_task_runner_)
return dbus_task_runner_.get();
else
return GetOriginTaskRunner();
}
base::TaskRunner* Bus::GetOriginTaskRunner() {
- DCHECK(origin_task_runner_.get());
+ DCHECK(origin_task_runner_);
return origin_task_runner_.get();
}
bool Bus::HasDBusThread() {
- return dbus_task_runner_.get() != NULL;
+ return dbus_task_runner_ != nullptr;
}
void Bus::AssertOnOriginThread() {
@@ -847,10 +805,10 @@ void Bus::AssertOnOriginThread() {
}
void Bus::AssertOnDBusThread() {
- base::ThreadRestrictions::AssertIOAllowed();
+ base::AssertBlockingAllowed();
- if (dbus_task_runner_.get()) {
- DCHECK(dbus_task_runner_->RunsTasksOnCurrentThread());
+ if (dbus_task_runner_) {
+ DCHECK(dbus_task_runner_->RunsTasksInCurrentSequence());
} else {
AssertOnOriginThread();
}
@@ -1048,20 +1006,16 @@ void Bus::OnToggleWatch(DBusWatch* raw_watch) {
AssertOnDBusThread();
Watch* watch = static_cast<Watch*>(dbus_watch_get_data(raw_watch));
- if (watch->IsReadyToBeWatched()) {
+ if (watch->IsReadyToBeWatched())
watch->StartWatching();
- } else {
- // It's safe to call this if StartWatching() wasn't called, per
- // message_pump_libevent.h.
+ else
watch->StopWatching();
- }
}
dbus_bool_t Bus::OnAddTimeout(DBusTimeout* raw_timeout) {
AssertOnDBusThread();
- // timeout will be deleted when raw_timeout is removed in
- // OnRemoveTimeoutThunk().
+ // |timeout| will be deleted by OnRemoveTimeoutThunk().
Timeout* timeout = new Timeout(raw_timeout);
if (timeout->IsReadyToBeMonitored()) {
timeout->StartMonitoring(this);
@@ -1074,7 +1028,7 @@ void Bus::OnRemoveTimeout(DBusTimeout* raw_timeout) {
AssertOnDBusThread();
Timeout* timeout = static_cast<Timeout*>(dbus_timeout_get_data(raw_timeout));
- timeout->Complete();
+ delete timeout;
--num_pending_timeouts_;
}
diff --git a/dbus/bus.h b/dbus/bus.h
index 59a19720ec..c2c2685afd 100644
--- a/dbus/bus.h
+++ b/dbus/bus.h
@@ -86,7 +86,7 @@ class ObjectProxy;
// dbus::MethodCall method_call(interface_name, method_name);
// std::unique_ptr<dbus::Response> response(
// object_proxy.CallMethodAndBlock(&method_call, timeout_ms));
-// if (response.get() != NULL) { // Success.
+// if (response.get() != nullptr) { // Success.
// ...
// }
//
@@ -583,7 +583,7 @@ class CHROME_DBUS_EXPORT Bus : public base::RefCountedThreadSafe<Bus> {
std::string GetConnectionName();
// Returns true if the bus is connected to D-Bus.
- bool is_connected() { return connection_ != NULL; }
+ bool is_connected() { return connection_ != nullptr; }
protected:
// This is protected, so we can define sub classes.
@@ -702,8 +702,7 @@ class CHROME_DBUS_EXPORT Bus : public base::RefCountedThreadSafe<Bus> {
// match rules are counted in a map.
std::map<std::string, int> match_rules_added_;
std::set<ObjectPath> registered_object_paths_;
- std::set<std::pair<DBusHandleMessageFunction, void*> >
- filter_functions_added_;
+ std::set<std::pair<DBusHandleMessageFunction, void*>> filter_functions_added_;
// ObjectProxyTable is used to hold the object proxies created by the
// bus object. Key is a pair; the first part is a concatenated string of
@@ -711,21 +710,21 @@ class CHROME_DBUS_EXPORT Bus : public base::RefCountedThreadSafe<Bus> {
// "org.chromium.TestService/org/chromium/TestObject".
// The second part is the ObjectProxy::Options for the proxy.
typedef std::map<std::pair<std::string, int>,
- scoped_refptr<dbus::ObjectProxy> > ObjectProxyTable;
+ scoped_refptr<dbus::ObjectProxy>> ObjectProxyTable;
ObjectProxyTable object_proxy_table_;
// ExportedObjectTable is used to hold the exported objects created by
// the bus object. Key is a concatenated string of service name +
// object path, like "org.chromium.TestService/org/chromium/TestObject".
typedef std::map<const dbus::ObjectPath,
- scoped_refptr<dbus::ExportedObject> > ExportedObjectTable;
+ scoped_refptr<dbus::ExportedObject>> ExportedObjectTable;
ExportedObjectTable exported_object_table_;
// ObjectManagerTable is used to hold the object managers created by the
// bus object. Key is a concatenated string of service name + object path,
// like "org.chromium.TestService/org/chromium/TestObject".
typedef std::map<std::string,
- scoped_refptr<dbus::ObjectManager> > ObjectManagerTable;
+ scoped_refptr<dbus::ObjectManager>> ObjectManagerTable;
ObjectManagerTable object_manager_table_;
// A map of NameOwnerChanged signals to listen for and the callbacks to run
@@ -734,7 +733,7 @@ class CHROME_DBUS_EXPORT Bus : public base::RefCountedThreadSafe<Bus> {
// Key: Service name
// Value: Vector of callbacks. Unique and expected to be small. Not using
// std::set here because base::Callbacks don't have a '<' operator.
- typedef std::map<std::string, std::vector<GetServiceOwnerCallback> >
+ typedef std::map<std::string, std::vector<GetServiceOwnerCallback>>
ServiceOwnerChangedListenerMap;
ServiceOwnerChangedListenerMap service_owner_changed_listener_map_;
diff --git a/dbus/exported_object.cc b/dbus/exported_object.cc
index 0024df6c28..d6c91b6d20 100644
--- a/dbus/exported_object.cc
+++ b/dbus/exported_object.cc
@@ -10,8 +10,8 @@
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
-#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram_macros.h"
+#include "base/task_runner.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "dbus/bus.h"
@@ -34,6 +34,7 @@ ExportedObject::ExportedObject(Bus* bus,
: bus_(bus),
object_path_(object_path),
object_is_registered_(false) {
+ LOG_IF(FATAL, !object_path_.IsValid()) << object_path_.value();
}
ExportedObject::~ExportedObject() {
@@ -94,7 +95,7 @@ void ExportedObject::SendSignal(Signal* signal) {
dbus_message_ref(signal_message);
const base::TimeTicks start_time = base::TimeTicks::Now();
- if (bus_->GetDBusTaskRunner()->RunsTasksOnCurrentThread()) {
+ if (bus_->GetDBusTaskRunner()->RunsTasksInCurrentSequence()) {
// The Chrome OS power manager doesn't use a dedicated TaskRunner for
// sending DBus messages. Sending signals asynchronously can cause an
// inversion in the message order if the power manager calls
@@ -190,7 +191,10 @@ DBusHandlerResult ExportedObject::HandleMessage(
DBusConnection* connection,
DBusMessage* raw_message) {
bus_->AssertOnDBusThread();
- DCHECK_EQ(DBUS_MESSAGE_TYPE_METHOD_CALL, dbus_message_get_type(raw_message));
+ // ExportedObject only handles method calls. Ignore other message types (e.g.
+ // signal).
+ if (dbus_message_get_type(raw_message) != DBUS_MESSAGE_TYPE_METHOD_CALL)
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
// raw_message will be unrefed on exit of the function. Increment the
// reference so we can use it in MethodCall.
@@ -219,12 +223,10 @@ DBusHandlerResult ExportedObject::HandleMessage(
const base::TimeTicks start_time = base::TimeTicks::Now();
if (bus_->HasDBusThread()) {
// Post a task to run the method in the origin thread.
- bus_->GetOriginTaskRunner()->PostTask(FROM_HERE,
- base::Bind(&ExportedObject::RunMethod,
- this,
- iter->second,
- base::Passed(&method_call),
- start_time));
+ bus_->GetOriginTaskRunner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&ExportedObject::RunMethod, this, iter->second,
+ std::move(method_call), start_time));
} else {
// If the D-Bus thread is not used, just call the method directly.
MethodCall* method = method_call.get();
@@ -258,12 +260,9 @@ void ExportedObject::SendResponse(base::TimeTicks start_time,
DCHECK(method_call);
if (bus_->HasDBusThread()) {
bus_->GetDBusTaskRunner()->PostTask(
- FROM_HERE,
- base::Bind(&ExportedObject::OnMethodCompleted,
- this,
- base::Passed(&method_call),
- base::Passed(&response),
- start_time));
+ FROM_HERE, base::BindOnce(&ExportedObject::OnMethodCompleted, this,
+ std::move(method_call), std::move(response),
+ start_time));
} else {
OnMethodCompleted(std::move(method_call), std::move(response), start_time);
}
@@ -289,12 +288,12 @@ void ExportedObject::OnMethodCompleted(std::unique_ptr<MethodCall> method_call,
std::unique_ptr<ErrorResponse> error_response(ErrorResponse::FromMethodCall(
method_call.get(), DBUS_ERROR_FAILED,
"error occurred in " + method_call->GetMember()));
- bus_->Send(error_response->raw_message(), NULL);
+ bus_->Send(error_response->raw_message(), nullptr);
return;
}
// The method call was successful.
- bus_->Send(response->raw_message(), NULL);
+ bus_->Send(response->raw_message(), nullptr);
// Record time spent to handle the the method call. Don't include failures.
UMA_HISTOGRAM_TIMES("DBus.ExportedMethodHandleTime",
diff --git a/dbus/message.cc b/dbus/message.cc
index c8663f72ad..7409033552 100644
--- a/dbus/message.cc
+++ b/dbus/message.cc
@@ -13,12 +13,7 @@
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "dbus/object_path.h"
-
-#if defined(USE_SYSTEM_PROTOBUF)
-#include <google/protobuf/message_lite.h>
-#else
#include "third_party/protobuf/src/google/protobuf/message_lite.h"
-#endif
namespace {
@@ -52,9 +47,7 @@ bool IsDBusTypeUnixFdSupported() {
return major >= 1 && minor >= 4;
}
-Message::Message()
- : raw_message_(NULL) {
-}
+Message::Message() : raw_message_(nullptr) {}
Message::~Message() {
if (raw_message_)
@@ -150,14 +143,14 @@ std::string Message::ToStringInternal(const std::string& indent,
uint64_t value = 0;
if (!reader->PopUint64(&value))
return kBrokenMessage;
- output += (indent + "uint64_t " + base::Uint64ToString(value) + "\n");
+ output += (indent + "uint64_t " + base::NumberToString(value) + "\n");
break;
}
case DOUBLE: {
double value = 0;
if (!reader->PopDouble(&value))
return kBrokenMessage;
- output += indent + "double " + base::DoubleToString(value) + "\n";
+ output += indent + "double " + base::NumberToString(value) + "\n";
break;
}
case STRING: {
@@ -225,8 +218,8 @@ std::string Message::ToStringInternal(const std::string& indent,
base::ScopedFD file_descriptor;
if (!reader->PopFileDescriptor(&file_descriptor))
return kBrokenMessage;
- output += indent + "fd#" +
- base::IntToString(file_descriptor.get()) + "\n";
+ output +=
+ indent + "fd#" + base::IntToString(file_descriptor.get()) + "\n";
break;
}
default:
@@ -349,21 +342,20 @@ uint32_t Message::GetReplySerial() {
//
MethodCall::MethodCall(const std::string& interface_name,
- const std::string& method_name)
- : Message() {
+ const std::string& method_name) {
Init(dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL));
CHECK(SetInterface(interface_name));
CHECK(SetMember(method_name));
}
-MethodCall::MethodCall() : Message() {
-}
+MethodCall::MethodCall() = default;
-MethodCall* MethodCall::FromRawMessage(DBusMessage* raw_message) {
+std::unique_ptr<MethodCall> MethodCall::FromRawMessage(
+ DBusMessage* raw_message) {
DCHECK_EQ(DBUS_MESSAGE_TYPE_METHOD_CALL, dbus_message_get_type(raw_message));
- MethodCall* method_call = new MethodCall;
+ std::unique_ptr<MethodCall> method_call(new MethodCall());
method_call->Init(raw_message);
return method_call;
}
@@ -372,21 +364,19 @@ MethodCall* MethodCall::FromRawMessage(DBusMessage* raw_message) {
// Signal implementation.
//
Signal::Signal(const std::string& interface_name,
- const std::string& method_name)
- : Message() {
+ const std::string& method_name) {
Init(dbus_message_new(DBUS_MESSAGE_TYPE_SIGNAL));
CHECK(SetInterface(interface_name));
CHECK(SetMember(method_name));
}
-Signal::Signal() : Message() {
-}
+Signal::Signal() = default;
-Signal* Signal::FromRawMessage(DBusMessage* raw_message) {
+std::unique_ptr<Signal> Signal::FromRawMessage(DBusMessage* raw_message) {
DCHECK_EQ(DBUS_MESSAGE_TYPE_SIGNAL, dbus_message_get_type(raw_message));
- Signal* signal = new Signal;
+ std::unique_ptr<Signal> signal(new Signal());
signal->Init(raw_message);
return signal;
}
@@ -395,26 +385,25 @@ Signal* Signal::FromRawMessage(DBusMessage* raw_message) {
// Response implementation.
//
-Response::Response() : Message() {
-}
+Response::Response() = default;
std::unique_ptr<Response> Response::FromRawMessage(DBusMessage* raw_message) {
DCHECK_EQ(DBUS_MESSAGE_TYPE_METHOD_RETURN,
dbus_message_get_type(raw_message));
- std::unique_ptr<Response> response(new Response);
+ std::unique_ptr<Response> response(new Response());
response->Init(raw_message);
return response;
}
std::unique_ptr<Response> Response::FromMethodCall(MethodCall* method_call) {
- std::unique_ptr<Response> response(new Response);
+ std::unique_ptr<Response> response(new Response());
response->Init(dbus_message_new_method_return(method_call->raw_message()));
return response;
}
std::unique_ptr<Response> Response::CreateEmpty() {
- std::unique_ptr<Response> response(new Response);
+ std::unique_ptr<Response> response(new Response());
response->Init(dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_RETURN));
return response;
}
@@ -423,14 +412,13 @@ std::unique_ptr<Response> Response::CreateEmpty() {
// ErrorResponse implementation.
//
-ErrorResponse::ErrorResponse() : Response() {
-}
+ErrorResponse::ErrorResponse() = default;
std::unique_ptr<ErrorResponse> ErrorResponse::FromRawMessage(
DBusMessage* raw_message) {
DCHECK_EQ(DBUS_MESSAGE_TYPE_ERROR, dbus_message_get_type(raw_message));
- std::unique_ptr<ErrorResponse> response(new ErrorResponse);
+ std::unique_ptr<ErrorResponse> response(new ErrorResponse());
response->Init(raw_message);
return response;
}
@@ -439,10 +427,9 @@ std::unique_ptr<ErrorResponse> ErrorResponse::FromMethodCall(
MethodCall* method_call,
const std::string& error_name,
const std::string& error_message) {
- std::unique_ptr<ErrorResponse> response(new ErrorResponse);
- response->Init(dbus_message_new_error(method_call->raw_message(),
- error_name.c_str(),
- error_message.c_str()));
+ std::unique_ptr<ErrorResponse> response(new ErrorResponse());
+ response->Init(dbus_message_new_error(
+ method_call->raw_message(), error_name.c_str(), error_message.c_str()));
return response;
}
@@ -451,15 +438,13 @@ std::unique_ptr<ErrorResponse> ErrorResponse::FromMethodCall(
//
MessageWriter::MessageWriter(Message* message)
- : message_(message),
- container_is_open_(false) {
+ : message_(message), container_is_open_(false) {
memset(&raw_message_iter_, 0, sizeof(raw_message_iter_));
if (message)
dbus_message_iter_init_append(message_->raw_message(), &raw_message_iter_);
}
-MessageWriter::~MessageWriter() {
-}
+MessageWriter::~MessageWriter() = default;
void MessageWriter::AppendByte(uint8_t value) {
AppendBasic(DBUS_TYPE_BYTE, &value);
@@ -471,7 +456,7 @@ void MessageWriter::AppendBool(bool value) {
// dbus_message_iter_append_basic() used in AppendBasic() expects four
// bytes for DBUS_TYPE_BOOLEAN, so we must pass a dbus_bool_t, instead
// of a bool, to AppendBasic().
- dbus_bool_t dbus_value = value;
+ dbus_bool_t dbus_value = value ? 1 : 0;
AppendBasic(DBUS_TYPE_BOOLEAN, &dbus_value);
}
@@ -532,9 +517,7 @@ void MessageWriter::OpenArray(const std::string& signature,
DCHECK(!container_is_open_);
const bool success = dbus_message_iter_open_container(
- &raw_message_iter_,
- DBUS_TYPE_ARRAY,
- signature.c_str(),
+ &raw_message_iter_, DBUS_TYPE_ARRAY, signature.c_str(),
&writer->raw_message_iter_);
CHECK(success) << "Unable to allocate memory";
container_is_open_ = true;
@@ -545,9 +528,7 @@ void MessageWriter::OpenVariant(const std::string& signature,
DCHECK(!container_is_open_);
const bool success = dbus_message_iter_open_container(
- &raw_message_iter_,
- DBUS_TYPE_VARIANT,
- signature.c_str(),
+ &raw_message_iter_, DBUS_TYPE_VARIANT, signature.c_str(),
&writer->raw_message_iter_);
CHECK(success) << "Unable to allocate memory";
container_is_open_ = true;
@@ -556,11 +537,10 @@ void MessageWriter::OpenVariant(const std::string& signature,
void MessageWriter::OpenStruct(MessageWriter* writer) {
DCHECK(!container_is_open_);
- const bool success = dbus_message_iter_open_container(
- &raw_message_iter_,
- DBUS_TYPE_STRUCT,
- NULL, // Signature should be NULL.
- &writer->raw_message_iter_);
+ const bool success =
+ dbus_message_iter_open_container(&raw_message_iter_, DBUS_TYPE_STRUCT,
+ nullptr, // Signature should be nullptr.
+ &writer->raw_message_iter_);
CHECK(success) << "Unable to allocate memory";
container_is_open_ = true;
}
@@ -568,11 +548,10 @@ void MessageWriter::OpenStruct(MessageWriter* writer) {
void MessageWriter::OpenDictEntry(MessageWriter* writer) {
DCHECK(!container_is_open_);
- const bool success = dbus_message_iter_open_container(
- &raw_message_iter_,
- DBUS_TYPE_DICT_ENTRY,
- NULL, // Signature should be NULL.
- &writer->raw_message_iter_);
+ const bool success =
+ dbus_message_iter_open_container(&raw_message_iter_, DBUS_TYPE_DICT_ENTRY,
+ nullptr, // Signature should be nullptr.
+ &writer->raw_message_iter_);
CHECK(success) << "Unable to allocate memory";
container_is_open_ = true;
}
@@ -591,9 +570,30 @@ void MessageWriter::AppendArrayOfBytes(const uint8_t* values, size_t length) {
MessageWriter array_writer(message_);
OpenArray("y", &array_writer);
const bool success = dbus_message_iter_append_fixed_array(
- &(array_writer.raw_message_iter_),
- DBUS_TYPE_BYTE,
- &values,
+ &(array_writer.raw_message_iter_), DBUS_TYPE_BYTE, &values,
+ static_cast<int>(length));
+ CHECK(success) << "Unable to allocate memory";
+ CloseContainer(&array_writer);
+}
+
+void MessageWriter::AppendArrayOfInt32s(const int32_t* values, size_t length) {
+ DCHECK(!container_is_open_);
+ MessageWriter array_writer(message_);
+ OpenArray("i", &array_writer);
+ const bool success = dbus_message_iter_append_fixed_array(
+ &(array_writer.raw_message_iter_), DBUS_TYPE_INT32, &values,
+ static_cast<int>(length));
+ CHECK(success) << "Unable to allocate memory";
+ CloseContainer(&array_writer);
+}
+
+void MessageWriter::AppendArrayOfUint32s(const uint32_t* values,
+ size_t length) {
+ DCHECK(!container_is_open_);
+ MessageWriter array_writer(message_);
+ OpenArray("u", &array_writer);
+ const bool success = dbus_message_iter_append_fixed_array(
+ &(array_writer.raw_message_iter_), DBUS_TYPE_UINT32, &values,
static_cast<int>(length));
CHECK(success) << "Unable to allocate memory";
CloseContainer(&array_writer);
@@ -604,9 +604,7 @@ void MessageWriter::AppendArrayOfDoubles(const double* values, size_t length) {
MessageWriter array_writer(message_);
OpenArray("d", &array_writer);
const bool success = dbus_message_iter_append_fixed_array(
- &(array_writer.raw_message_iter_),
- DBUS_TYPE_DOUBLE,
- &values,
+ &(array_writer.raw_message_iter_), DBUS_TYPE_DOUBLE, &values,
static_cast<int>(length));
CHECK(success) << "Unable to allocate memory";
CloseContainer(&array_writer);
@@ -697,8 +695,8 @@ void MessageWriter::AppendVariantOfObjectPath(const ObjectPath& value) {
void MessageWriter::AppendBasic(int dbus_type, const void* value) {
DCHECK(!container_is_open_);
- const bool success = dbus_message_iter_append_basic(
- &raw_message_iter_, dbus_type, value);
+ const bool success =
+ dbus_message_iter_append_basic(&raw_message_iter_, dbus_type, value);
// dbus_message_iter_append_basic() fails only when there is not enough
// memory. We don't return this error as there is nothing we can do when
// it fails to allocate memory for a byte etc.
@@ -723,16 +721,13 @@ void MessageWriter::AppendFileDescriptor(int value) {
// MessageReader implementation.
//
-MessageReader::MessageReader(Message* message)
- : message_(message) {
+MessageReader::MessageReader(Message* message) : message_(message) {
memset(&raw_message_iter_, 0, sizeof(raw_message_iter_));
if (message)
dbus_message_iter_init(message_->raw_message(), &raw_message_iter_);
}
-
-MessageReader::~MessageReader() {
-}
+MessageReader::~MessageReader() = default;
bool MessageReader::HasMoreData() {
const int dbus_type = dbus_message_iter_get_arg_type(&raw_message_iter_);
@@ -782,7 +777,7 @@ bool MessageReader::PopDouble(double* value) {
}
bool MessageReader::PopString(std::string* value) {
- char* tmp_value = NULL;
+ char* tmp_value = nullptr;
const bool success = PopBasic(DBUS_TYPE_STRING, &tmp_value);
if (success)
value->assign(tmp_value);
@@ -790,7 +785,7 @@ bool MessageReader::PopString(std::string* value) {
}
bool MessageReader::PopObjectPath(ObjectPath* value) {
- char* tmp_value = NULL;
+ char* tmp_value = nullptr;
const bool success = PopBasic(DBUS_TYPE_OBJECT_PATH, &tmp_value);
if (success)
*value = ObjectPath(tmp_value);
@@ -816,23 +811,62 @@ bool MessageReader::PopVariant(MessageReader* sub_reader) {
bool MessageReader::PopArrayOfBytes(const uint8_t** bytes, size_t* length) {
MessageReader array_reader(message_);
if (!PopArray(&array_reader))
- return false;
+ return false;
// An empty array is allowed.
if (!array_reader.HasMoreData()) {
*length = 0;
- *bytes = NULL;
+ *bytes = nullptr;
return true;
}
if (!array_reader.CheckDataType(DBUS_TYPE_BYTE))
return false;
int int_length = 0;
- dbus_message_iter_get_fixed_array(&array_reader.raw_message_iter_,
- bytes,
+ dbus_message_iter_get_fixed_array(&array_reader.raw_message_iter_, bytes,
&int_length);
*length = static_cast<size_t>(int_length);
return true;
}
+bool MessageReader::PopArrayOfInt32s(const int32_t** signed_ints,
+ size_t* length) {
+ MessageReader array_reader(message_);
+ if (!PopArray(&array_reader))
+ return false;
+ // An empty array is allowed.
+ if (!array_reader.HasMoreData()) {
+ *length = 0;
+ *signed_ints = nullptr;
+ return true;
+ }
+ if (!array_reader.CheckDataType(DBUS_TYPE_INT32))
+ return false;
+ int int_length = 0;
+ dbus_message_iter_get_fixed_array(&array_reader.raw_message_iter_,
+ signed_ints, &int_length);
+ *length = static_cast<size_t>(int_length);
+ return true;
+}
+
+bool MessageReader::PopArrayOfUint32s(const uint32_t** unsigned_ints,
+ size_t* length) {
+ MessageReader array_reader(message_);
+ if (!PopArray(&array_reader))
+ return false;
+ // An empty array is allowed.
+ if (!array_reader.HasMoreData()) {
+ *length = 0;
+ *unsigned_ints = nullptr;
+ return true;
+ }
+ if (!array_reader.CheckDataType(DBUS_TYPE_UINT32))
+ return false;
+ int int_length = 0;
+ dbus_message_iter_get_fixed_array(&array_reader.raw_message_iter_,
+ unsigned_ints, &int_length);
+ *length = static_cast<size_t>(int_length);
+ return true;
+}
+
bool MessageReader::PopArrayOfDoubles(const double** doubles, size_t* length) {
MessageReader array_reader(message_);
if (!PopArray(&array_reader))
@@ -845,15 +879,13 @@ bool MessageReader::PopArrayOfDoubles(const double** doubles, size_t* length) {
if (!array_reader.CheckDataType(DBUS_TYPE_DOUBLE))
return false;
int int_length = 0;
- dbus_message_iter_get_fixed_array(&array_reader.raw_message_iter_,
- doubles,
+ dbus_message_iter_get_fixed_array(&array_reader.raw_message_iter_, doubles,
&int_length);
*length = static_cast<size_t>(int_length);
return true;
}
-bool MessageReader::PopArrayOfStrings(
- std::vector<std::string> *strings) {
+bool MessageReader::PopArrayOfStrings(std::vector<std::string>* strings) {
strings->clear();
MessageReader array_reader(message_);
if (!PopArray(&array_reader))
@@ -868,7 +900,7 @@ bool MessageReader::PopArrayOfStrings(
}
bool MessageReader::PopArrayOfObjectPaths(
- std::vector<ObjectPath> *object_paths) {
+ std::vector<ObjectPath>* object_paths) {
object_paths->clear();
MessageReader array_reader(message_);
if (!PopArray(&array_reader))
@@ -884,8 +916,8 @@ bool MessageReader::PopArrayOfObjectPaths(
bool MessageReader::PopArrayOfBytesAsProto(
google::protobuf::MessageLite* protobuf) {
- DCHECK(protobuf != NULL);
- const char* serialized_buf = NULL;
+ DCHECK(protobuf);
+ const char* serialized_buf = nullptr;
size_t buf_size = 0;
if (!PopArrayOfBytes(reinterpret_cast<const uint8_t**>(&serialized_buf),
&buf_size)) {
@@ -940,7 +972,7 @@ bool MessageReader::PopVariantOfDouble(double* value) {
}
bool MessageReader::PopVariantOfString(std::string* value) {
- char* tmp_value = NULL;
+ char* tmp_value = nullptr;
const bool success = PopVariantOfBasic(DBUS_TYPE_STRING, &tmp_value);
if (success)
value->assign(tmp_value);
@@ -948,7 +980,7 @@ bool MessageReader::PopVariantOfString(std::string* value) {
}
bool MessageReader::PopVariantOfObjectPath(ObjectPath* value) {
- char* tmp_value = NULL;
+ char* tmp_value = nullptr;
const bool success = PopVariantOfBasic(DBUS_TYPE_OBJECT_PATH, &tmp_value);
if (success)
*value = ObjectPath(tmp_value);
@@ -973,8 +1005,7 @@ std::string MessageReader::GetDataSignature() {
bool MessageReader::CheckDataType(int dbus_type) {
const int actual_type = dbus_message_iter_get_arg_type(&raw_message_iter_);
if (actual_type != dbus_type) {
- VLOG(1) << "Type " << dbus_type << " is expected but got "
- << actual_type;
+ VLOG(1) << "Type " << dbus_type << " is expected but got " << actual_type;
return false;
}
return true;
@@ -997,8 +1028,7 @@ bool MessageReader::PopContainer(int dbus_type, MessageReader* sub_reader) {
if (!CheckDataType(dbus_type))
return false;
- dbus_message_iter_recurse(&raw_message_iter_,
- &sub_reader->raw_message_iter_);
+ dbus_message_iter_recurse(&raw_message_iter_, &sub_reader->raw_message_iter_);
dbus_message_iter_next(&raw_message_iter_);
return true;
}
diff --git a/dbus/message.h b/dbus/message.h
index 256a8428c5..6ae6f24f48 100644
--- a/dbus/message.h
+++ b/dbus/message.h
@@ -26,7 +26,6 @@ class MessageLite;
} // namespace protobuf
} // namespace google
-
namespace dbus {
class MessageWriter;
@@ -34,7 +33,7 @@ class MessageReader;
// DBUS_TYPE_UNIX_FD was added in D-Bus version 1.4
#if !defined(DBUS_TYPE_UNIX_FD)
-#define DBUS_TYPE_UNIX_FD ((int) 'h')
+#define DBUS_TYPE_UNIX_FD ((int)'h')
#endif
// Returns true if Unix FD passing is supported in libdbus.
@@ -61,7 +60,7 @@ class CHROME_DBUS_EXPORT Message {
MESSAGE_METHOD_RETURN = DBUS_MESSAGE_TYPE_METHOD_RETURN,
MESSAGE_SIGNAL = DBUS_MESSAGE_TYPE_SIGNAL,
MESSAGE_ERROR = DBUS_MESSAGE_TYPE_ERROR,
- };
+ };
// The data type used in the D-Bus type system. See the comment at
// MessageType for why we are redefining data types here.
@@ -154,13 +153,11 @@ class CHROME_DBUS_EXPORT MethodCall : public Message {
// MethodCall method_call(DBUS_INTERFACE_INTROSPECTABLE, "Get");
//
// The constructor creates the internal raw message.
- MethodCall(const std::string& interface_name,
- const std::string& method_name);
+ MethodCall(const std::string& interface_name, const std::string& method_name);
// Returns a newly created MethodCall from the given raw message of the
- // type DBUS_MESSAGE_TYPE_METHOD_CALL. The caller must delete the
- // returned object. Takes the ownership of |raw_message|.
- static MethodCall* FromRawMessage(DBusMessage* raw_message);
+ // type DBUS_MESSAGE_TYPE_METHOD_CALL. Takes the ownership of |raw_message|.
+ static std::unique_ptr<MethodCall> FromRawMessage(DBusMessage* raw_message);
private:
// Creates a method call message. The internal raw message is NULL.
@@ -183,13 +180,11 @@ class CHROME_DBUS_EXPORT Signal : public Message {
// Signal signal(DBUS_INTERFACE_INTROSPECTABLE, "PropertiesChanged");
//
// The constructor creates the internal raw_message_.
- Signal(const std::string& interface_name,
- const std::string& method_name);
+ Signal(const std::string& interface_name, const std::string& method_name);
// Returns a newly created SIGNAL from the given raw message of the type
- // DBUS_MESSAGE_TYPE_SIGNAL. The caller must delete the returned
- // object. Takes the ownership of |raw_message|.
- static Signal* FromRawMessage(DBusMessage* raw_message);
+ // DBUS_MESSAGE_TYPE_SIGNAL. Takes the ownership of |raw_message|.
+ static std::unique_ptr<Signal> FromRawMessage(DBusMessage* raw_message);
private:
// Creates a signal message. The internal raw message is NULL.
@@ -226,7 +221,7 @@ class CHROME_DBUS_EXPORT Response : public Message {
// ErrorResponse is a type of message used to return an error to the
// caller of a method.
-class CHROME_DBUS_EXPORT ErrorResponse: public Response {
+class CHROME_DBUS_EXPORT ErrorResponse : public Response {
public:
// Returns a newly created Response from the given raw message of the
// type DBUS_MESSAGE_TYPE_METHOD_RETURN. Takes the ownership of |raw_message|.
@@ -317,6 +312,12 @@ class CHROME_DBUS_EXPORT MessageWriter {
// function.
void AppendArrayOfBytes(const uint8_t* values, size_t length);
+ // Appends array of int32_ts.
+ void AppendArrayOfInt32s(const int32_t* values, size_t length);
+
+ // Appends array of uint32_ts.
+ void AppendArrayOfUint32s(const uint32_t* values, size_t length);
+
// Appends the array of doubles. Used for audio mixer matrix doubles.
void AppendArrayOfDoubles(const double* values, size_t length);
@@ -334,8 +335,8 @@ class CHROME_DBUS_EXPORT MessageWriter {
// into an array of bytes before communication, since protocol buffers are not
// a native dbus type. On the receiving size the array of bytes needs to be
// read and deserialized into a protocol buffer of the correct type. There are
- // methods in MessageReader to assist in this. Return true on succes and fail
- // when serialization is not successful.
+ // methods in MessageReader to assist in this. Return true on success and
+ // false when serialization fails.
bool AppendProtoAsArrayOfBytes(const google::protobuf::MessageLite& protobuf);
// Appends the byte wrapped in a variant data container. Variants are
@@ -423,6 +424,12 @@ class CHROME_DBUS_EXPORT MessageReader {
// after the MessageReader is destroyed.
bool PopArrayOfBytes(const uint8_t** bytes, size_t* length);
+ // Gets the array of int32_ts at the current iterator position.
+ bool PopArrayOfInt32s(const int32_t** signed_ints, size_t* length);
+
+ // Gets the array of uint32_ts at the current iterator position.
+ bool PopArrayOfUint32s(const uint32_t** unsigned_ints, size_t* length);
+
// Gets the array of doubles at the current iterator position.
bool PopArrayOfDoubles(const double** doubles, size_t* length);
@@ -487,7 +494,7 @@ class CHROME_DBUS_EXPORT MessageReader {
bool CheckDataType(int dbus_type);
// Helper function used to implement PopByte() etc.
- bool PopBasic(int dbus_type, void *value);
+ bool PopBasic(int dbus_type, void* value);
// Helper function used to implement PopArray() etc.
bool PopContainer(int dbus_type, MessageReader* sub_reader);
diff --git a/dbus/mock_bus.cc b/dbus/mock_bus.cc
index 9e76454685..30cffb00c7 100644
--- a/dbus/mock_bus.cc
+++ b/dbus/mock_bus.cc
@@ -11,7 +11,6 @@ namespace dbus {
MockBus::MockBus(const Bus::Options& options) : Bus(options) {
}
-MockBus::~MockBus() {
-}
+MockBus::~MockBus() = default;
} // namespace dbus
diff --git a/dbus/mock_bus.h b/dbus/mock_bus.h
index 40b090b156..216bc64bd0 100644
--- a/dbus/mock_bus.h
+++ b/dbus/mock_bus.h
@@ -48,10 +48,12 @@ class MockBus : public Bus {
DBusPendingCall** pending_call,
int timeout_ms));
MOCK_METHOD2(Send, void(DBusMessage* request, uint32_t* serial));
- MOCK_METHOD2(AddFilter, void(DBusHandleMessageFunction handle_message,
- void* user_data));
- MOCK_METHOD2(RemoveFilter, void(DBusHandleMessageFunction handle_message,
- void* user_data));
+ MOCK_METHOD2(AddFilterFunction,
+ void(DBusHandleMessageFunction filter_function,
+ void* user_data));
+ MOCK_METHOD2(RemoveFilterFunction,
+ void(DBusHandleMessageFunction filter_function,
+ void* user_data));
MOCK_METHOD2(AddMatch, void(const std::string& match_rule,
DBusError* error));
MOCK_METHOD2(RemoveMatch, bool(const std::string& match_rule,
@@ -68,7 +70,7 @@ class MockBus : public Bus {
MOCK_METHOD0(AssertOnDBusThread, void());
protected:
- virtual ~MockBus();
+ ~MockBus() override;
};
} // namespace dbus
diff --git a/dbus/mock_exported_object.cc b/dbus/mock_exported_object.cc
index ff507ddf58..f8a6703441 100644
--- a/dbus/mock_exported_object.cc
+++ b/dbus/mock_exported_object.cc
@@ -11,7 +11,6 @@ MockExportedObject::MockExportedObject(Bus* bus,
: ExportedObject(bus, object_path) {
}
-MockExportedObject::~MockExportedObject() {
-}
+MockExportedObject::~MockExportedObject() = default;
} // namespace dbus
diff --git a/dbus/mock_exported_object.h b/dbus/mock_exported_object.h
index 80ca9512ff..99c363f9b5 100644
--- a/dbus/mock_exported_object.h
+++ b/dbus/mock_exported_object.h
@@ -32,7 +32,7 @@ class MockExportedObject : public ExportedObject {
MOCK_METHOD0(Unregister, void());
protected:
- virtual ~MockExportedObject();
+ ~MockExportedObject() override;
};
} // namespace dbus
diff --git a/dbus/mock_object_manager.cc b/dbus/mock_object_manager.cc
deleted file mode 100644
index dcba78e6e2..0000000000
--- a/dbus/mock_object_manager.cc
+++ /dev/null
@@ -1,18 +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 "dbus/mock_object_manager.h"
-
-namespace dbus {
-
-MockObjectManager::MockObjectManager(Bus* bus,
- const std::string& service_name,
- const ObjectPath& object_path)
- : ObjectManager(bus, service_name, object_path) {
-}
-
-MockObjectManager::~MockObjectManager() {
-}
-
-} // namespace dbus
diff --git a/dbus/mock_object_manager.h b/dbus/mock_object_manager.h
deleted file mode 100644
index 2318e497ea..0000000000
--- a/dbus/mock_object_manager.h
+++ /dev/null
@@ -1,41 +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.
-
-#ifndef DBUS_MOCK_OBJECT_MANAGER_H_
-#define DBUS_MOCK_OBJECT_MANAGER_H_
-
-#include <string>
-
-#include "dbus/message.h"
-#include "dbus/object_manager.h"
-#include "dbus/object_path.h"
-#include "dbus/object_proxy.h"
-#include "testing/gmock/include/gmock/gmock.h"
-
-namespace dbus {
-
-// Mock for ObjectManager.
-class MockObjectManager : public ObjectManager {
- public:
- MockObjectManager(Bus* bus,
- const std::string& service_name,
- const ObjectPath& object_path);
-
- MOCK_METHOD2(RegisterInterface, void(const std::string&,
- Interface*));
- MOCK_METHOD1(UnregisterInterface, void(const std::string&));
- MOCK_METHOD0(GetObjects, std::vector<ObjectPath>());
- MOCK_METHOD1(GetObjectsWithInterface,
- std::vector<ObjectPath>(const std::string&));
- MOCK_METHOD1(GetObjectProxy, ObjectProxy*(const ObjectPath&));
- MOCK_METHOD2(GetProperties, PropertySet*(const ObjectPath&,
- const std::string&));
-
- protected:
- virtual ~MockObjectManager();
-};
-
-} // namespace dbus
-
-#endif // DBUS_MOCK_OBJECT_MANAGER_H_
diff --git a/dbus/mock_object_proxy.cc b/dbus/mock_object_proxy.cc
index 7e26f01ba5..4929486396 100644
--- a/dbus/mock_object_proxy.cc
+++ b/dbus/mock_object_proxy.cc
@@ -12,7 +12,37 @@ MockObjectProxy::MockObjectProxy(Bus* bus,
: ObjectProxy(bus, service_name, object_path, DEFAULT_OPTIONS) {
}
-MockObjectProxy::~MockObjectProxy() {
+MockObjectProxy::~MockObjectProxy() = default;
+
+void MockObjectProxy::CallMethod(MethodCall* method_call,
+ int timeout_ms,
+ ResponseCallback callback) {
+ DoCallMethod(method_call, timeout_ms, &callback);
+}
+
+void MockObjectProxy::CallMethodWithErrorResponse(
+ MethodCall* method_call,
+ int timeout_ms,
+ ResponseOrErrorCallback callback) {
+ DoCallMethodWithErrorResponse(method_call, timeout_ms, &callback);
+}
+
+void MockObjectProxy::CallMethodWithErrorCallback(
+ MethodCall* method_call,
+ int timeout_ms,
+ ResponseCallback callback,
+ ErrorCallback error_callback) {
+ DoCallMethodWithErrorCallback(method_call, timeout_ms, &callback,
+ &error_callback);
+}
+
+void MockObjectProxy::ConnectToSignal(
+ const std::string& interface_name,
+ const std::string& signal_name,
+ SignalCallback signal_callback,
+ OnConnectedCallback on_connected_callback) {
+ DoConnectToSignal(interface_name, signal_name, signal_callback,
+ &on_connected_callback);
}
} // namespace dbus
diff --git a/dbus/mock_object_proxy.h b/dbus/mock_object_proxy.h
index 17d2a9f0f4..009d9b7721 100644
--- a/dbus/mock_object_proxy.h
+++ b/dbus/mock_object_proxy.h
@@ -5,6 +5,7 @@
#ifndef DBUS_MOCK_OBJECT_PROXY_H_
#define DBUS_MOCK_OBJECT_PROXY_H_
+#include <memory>
#include <string>
#include "dbus/message.h"
@@ -21,45 +22,63 @@ class MockObjectProxy : public ObjectProxy {
const std::string& service_name,
const ObjectPath& object_path);
- // GMock doesn't support the return type of std::unique_ptr<> because
- // std::unique_ptr is uncopyable. This is a workaround which defines
- // |MockCallMethodAndBlock| as a mock method and makes
- // |CallMethodAndBlock| call the mocked method. Use |MockCallMethodAndBlock|
- // for setting/testing expectations.
- MOCK_METHOD3(MockCallMethodAndBlockWithErrorDetails,
- Response*(MethodCall* method_call,
- int timeout_ms,
- ScopedDBusError* error));
- std::unique_ptr<Response> CallMethodAndBlockWithErrorDetails(
- MethodCall* method_call,
- int timeout_ms,
- ScopedDBusError* error) override {
- return std::unique_ptr<Response>(
- MockCallMethodAndBlockWithErrorDetails(method_call, timeout_ms, error));
- }
- MOCK_METHOD2(MockCallMethodAndBlock, Response*(MethodCall* method_call,
- int timeout_ms));
- std::unique_ptr<Response> CallMethodAndBlock(MethodCall* method_call,
- int timeout_ms) override {
- return std::unique_ptr<Response>(
- MockCallMethodAndBlock(method_call, timeout_ms));
- }
- MOCK_METHOD3(CallMethod, void(MethodCall* method_call,
- int timeout_ms,
- ResponseCallback callback));
- MOCK_METHOD4(CallMethodWithErrorCallback, void(MethodCall* method_call,
- int timeout_ms,
- ResponseCallback callback,
- ErrorCallback error_callback));
- MOCK_METHOD4(ConnectToSignal,
+ MOCK_METHOD3(CallMethodAndBlockWithErrorDetails,
+ std::unique_ptr<Response>(MethodCall* method_call,
+ int timeout_ms,
+ ScopedDBusError* error));
+ MOCK_METHOD2(CallMethodAndBlock,
+ std::unique_ptr<Response>(MethodCall* method_call,
+ int timeout_ms));
+
+ // This method is not mockable because it takes a move-only argument. To work
+ // around this, CallMethod() implementation here calls DoCallMethod() which is
+ // mockable.
+ void CallMethod(MethodCall* method_call,
+ int timeout_ms,
+ ResponseCallback callback) override;
+ MOCK_METHOD3(DoCallMethod,
+ void(MethodCall* method_call,
+ int timeout_ms,
+ ResponseCallback* callback));
+
+ // This method is not mockable because it takes a move-only argument. To work
+ // around this, CallMethodWithErrorResponse() implementation here calls
+ // DoCallMethodWithErrorResponse() which is mockable.
+ void CallMethodWithErrorResponse(MethodCall* method_call,
+ int timeout_ms,
+ ResponseOrErrorCallback callback) override;
+ MOCK_METHOD3(DoCallMethodWithErrorResponse,
+ void(MethodCall* method_call,
+ int timeout_ms,
+ ResponseOrErrorCallback* callback));
+
+ // This method is not mockable because it takes a move-only argument. To work
+ // around this, CallMethodWithErrorCallback() implementation here calls
+ // DoCallMethodWithErrorCallback() which is mockable.
+ void CallMethodWithErrorCallback(MethodCall* method_call,
+ int timeout_ms,
+ ResponseCallback callback,
+ ErrorCallback error_callback) override;
+ MOCK_METHOD4(DoCallMethodWithErrorCallback,
+ void(MethodCall* method_call,
+ int timeout_ms,
+ ResponseCallback* callback,
+ ErrorCallback* error_callback));
+
+ // This method is not mockable because it takes a move-only argument. To work
+ // around this, ConnectToSignal() implementation here calls
+ // DoConnectToSignal() which is mockable.
+ void ConnectToSignal(const std::string& interface_name,
+ const std::string& signal_name,
+ SignalCallback signal_callback,
+ OnConnectedCallback on_connected_callback) override;
+ MOCK_METHOD4(DoConnectToSignal,
void(const std::string& interface_name,
const std::string& signal_name,
SignalCallback signal_callback,
- OnConnectedCallback on_connected_callback));
+ OnConnectedCallback* on_connected_callback));
MOCK_METHOD1(SetNameOwnerChangedCallback,
void(NameOwnerChangedCallback callback));
- MOCK_METHOD1(WaitForServiceToBeAvailable,
- void(WaitForServiceToBeAvailableCallback callback));
MOCK_METHOD0(Detach, void());
protected:
diff --git a/dbus/object_manager.cc b/dbus/object_manager.cc
index 3a39cd6fb6..90d7337d26 100644
--- a/dbus/object_manager.cc
+++ b/dbus/object_manager.cc
@@ -23,11 +23,10 @@
namespace dbus {
ObjectManager::Object::Object()
- : object_proxy(NULL) {
+ : object_proxy(nullptr) {
}
-ObjectManager::Object::~Object() {
-}
+ObjectManager::Object::~Object() = default;
ObjectManager::ObjectManager(Bus* bus,
const std::string& service_name,
@@ -38,6 +37,7 @@ ObjectManager::ObjectManager(Bus* bus,
setup_success_(false),
cleanup_called_(false),
weak_ptr_factory_(this) {
+ LOG_IF(FATAL, !object_path_.IsValid()) << object_path_.value();
DVLOG(1) << "Creating ObjectManager for " << service_name_
<< " " << object_path_.value();
DCHECK(bus_);
@@ -115,7 +115,7 @@ std::vector<ObjectPath> ObjectManager::GetObjectsWithInterface(
ObjectProxy* ObjectManager::GetObjectProxy(const ObjectPath& object_path) {
ObjectMap::iterator iter = object_map_.find(object_path);
if (iter == object_map_.end())
- return NULL;
+ return nullptr;
Object* object = iter->second;
return object->object_proxy;
@@ -125,13 +125,13 @@ PropertySet* ObjectManager::GetProperties(const ObjectPath& object_path,
const std::string& interface_name) {
ObjectMap::iterator iter = object_map_.find(object_path);
if (iter == object_map_.end())
- return NULL;
+ return nullptr;
Object* object = iter->second;
Object::PropertiesMap::iterator piter =
object->properties_map.find(interface_name);
if (piter == object->properties_map.end())
- return NULL;
+ return nullptr;
return piter->second;
}
@@ -349,14 +349,14 @@ void ObjectManager::NotifyPropertiesChangedHelper(
}
void ObjectManager::OnGetManagedObjects(Response* response) {
- if (response != NULL) {
+ if (response != nullptr) {
MessageReader reader(response);
- MessageReader array_reader(NULL);
+ MessageReader array_reader(nullptr);
if (!reader.PopArray(&array_reader))
return;
while (array_reader.HasMoreData()) {
- MessageReader dict_entry_reader(NULL);
+ MessageReader dict_entry_reader(nullptr);
ObjectPath object_path;
if (!array_reader.PopDictEntry(&dict_entry_reader) ||
!dict_entry_reader.PopObjectPath(&object_path))
@@ -421,12 +421,12 @@ void ObjectManager::InterfacesRemovedConnected(
void ObjectManager::UpdateObject(const ObjectPath& object_path,
MessageReader* reader) {
DCHECK(reader);
- MessageReader array_reader(NULL);
+ MessageReader array_reader(nullptr);
if (!reader->PopArray(&array_reader))
return;
while (array_reader.HasMoreData()) {
- MessageReader dict_entry_reader(NULL);
+ MessageReader dict_entry_reader(nullptr);
std::string interface_name;
if (!array_reader.PopDictEntry(&dict_entry_reader) ||
!dict_entry_reader.PopString(&interface_name))
diff --git a/dbus/object_manager.h b/dbus/object_manager.h
index 842a1378a2..035d5693d3 100644
--- a/dbus/object_manager.h
+++ b/dbus/object_manager.h
@@ -41,7 +41,7 @@
// dbus::Property<std::string> name;
// dbus::Property<uint16_t> version;
// dbus::Property<dbus::ObjectPath> parent;
-// dbus::Property<std::vector<std::string> > children;
+// dbus::Property<std::vector<std::string>> children;
//
// Properties(dbus::ObjectProxy* object_proxy,
// const PropertyChangedCallback callback)
@@ -192,35 +192,35 @@ public:
// interface named in |interface_name|. That object's CreateProperties()
// method will be used to create instances of dbus::PropertySet* when
// required.
- void RegisterInterface(const std::string& interface_name,
- Interface* interface);
+ virtual void RegisterInterface(const std::string& interface_name,
+ Interface* interface);
// Unregister the implementation class for the D-Bus interface named in
// |interface_name|, objects and properties of this interface will be
// ignored.
- void UnregisterInterface(const std::string& interface_name);
+ virtual void UnregisterInterface(const std::string& interface_name);
// Returns a list of object paths, in an undefined order, of objects known
// to this manager.
- std::vector<ObjectPath> GetObjects();
+ virtual std::vector<ObjectPath> GetObjects();
// Returns the list of object paths, in an undefined order, of objects
// implementing the interface named in |interface_name| known to this manager.
- std::vector<ObjectPath> GetObjectsWithInterface(
+ virtual std::vector<ObjectPath> GetObjectsWithInterface(
const std::string& interface_name);
// Returns a ObjectProxy pointer for the given |object_path|. Unlike
// the equivalent method on Bus this will return NULL if the object
- // manager has not been informed of that object's existance.
- ObjectProxy* GetObjectProxy(const ObjectPath& object_path);
+ // manager has not been informed of that object's existence.
+ virtual ObjectProxy* GetObjectProxy(const ObjectPath& object_path);
// Returns a PropertySet* pointer for the given |object_path| and
// |interface_name|, or NULL if the object manager has not been informed of
- // that object's existance or the interface's properties. The caller should
+ // that object's existence or the interface's properties. The caller should
// cast the returned pointer to the appropriate type, e.g.:
// static_cast<Properties*>(GetProperties(object_path, my_interface));
- PropertySet* GetProperties(const ObjectPath& object_path,
- const std::string& interface_name);
+ virtual PropertySet* GetProperties(const ObjectPath& object_path,
+ const std::string& interface_name);
// Instructs the object manager to refresh its list of managed objects;
// automatically called by the D-Bus thread manager, there should never be
diff --git a/dbus/object_proxy.cc b/dbus/object_proxy.cc
index 15f20c76c0..35835fbdc3 100644
--- a/dbus/object_proxy.cc
+++ b/dbus/object_proxy.cc
@@ -8,11 +8,13 @@
#include <utility>
#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/debug/leak_annotations.h"
#include "base/logging.h"
-#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
+#include "base/task_runner.h"
#include "base/task_runner_util.h"
#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h"
@@ -27,29 +29,87 @@ namespace dbus {
namespace {
-const char kErrorServiceUnknown[] = "org.freedesktop.DBus.Error.ServiceUnknown";
-const char kErrorObjectUnknown[] = "org.freedesktop.DBus.Error.UnknownObject";
+constexpr char kErrorServiceUnknown[] =
+ "org.freedesktop.DBus.Error.ServiceUnknown";
+constexpr char kErrorObjectUnknown[] =
+ "org.freedesktop.DBus.Error.UnknownObject";
// Used for success ratio histograms. 1 for success, 0 for failure.
-const int kSuccessRatioHistogramMaxValue = 2;
+constexpr int kSuccessRatioHistogramMaxValue = 2;
// The path of D-Bus Object sending NameOwnerChanged signal.
-const char kDBusSystemObjectPath[] = "/org/freedesktop/DBus";
+constexpr char kDBusSystemObjectPath[] = "/org/freedesktop/DBus";
// The D-Bus Object interface.
-const char kDBusSystemObjectInterface[] = "org.freedesktop.DBus";
+constexpr char kDBusSystemObjectInterface[] = "org.freedesktop.DBus";
// The D-Bus Object address.
-const char kDBusSystemObjectAddress[] = "org.freedesktop.DBus";
+constexpr char kDBusSystemObjectAddress[] = "org.freedesktop.DBus";
// The NameOwnerChanged member in |kDBusSystemObjectInterface|.
-const char kNameOwnerChangedMember[] = "NameOwnerChanged";
+constexpr char kNameOwnerChangedMember[] = "NameOwnerChanged";
-// An empty function used for ObjectProxy::EmptyResponseCallback().
-void EmptyResponseCallbackBody(Response* /*response*/) {
+} // namespace
+
+ObjectProxy::ReplyCallbackHolder::ReplyCallbackHolder(
+ scoped_refptr<base::TaskRunner> origin_task_runner,
+ ResponseOrErrorCallback callback)
+ : origin_task_runner_(origin_task_runner), callback_(std::move(callback)) {
+ DCHECK(origin_task_runner_.get());
+ DCHECK(!callback_.is_null());
+}
+
+ObjectProxy::ReplyCallbackHolder::ReplyCallbackHolder(
+ ReplyCallbackHolder&& other) = default;
+
+ObjectProxy::ReplyCallbackHolder::~ReplyCallbackHolder() {
+ if (callback_.is_null()) {
+ // This is the regular case.
+ // CallMethod and its family creates this object on the origin thread,
+ // PostTask()s to the D-Bus thread for actual D-Bus communication,
+ // then PostTask()s back to the origin thread to invoke the |callback_|.
+ // At that timing, the ownership of callback should be released via
+ // ReleaseCallback().
+ // Otherwise, this instance was moved to another one. Do nothing in
+ // either case.
+ return;
+ }
+
+ // The only case where |origin_task_runner_| becomes nullptr is that
+ // this is moved. In such a case, |callback_| should be nullptr, too, so it
+ // should be handled above. Thus, here |origin_task_runner_| must not be
+ // nullptr.
+ DCHECK(origin_task_runner_.get());
+
+ if (origin_task_runner_->RunsTasksInCurrentSequence()) {
+ // Destroyed on the origin thread. This happens when PostTask()ing to
+ // the D-Bus thread fails. The |callback_| can be destroyed on the
+ // current thread safely. Do nothing here, and let member destruction
+ // destroy the callback.
+ return;
+ }
+
+ // Here is on D-Bus thread, so try to PostTask() to destroy the callback.
+ // to the origin thread.
+ // The |origin_task_runner_| may already have stopped. E.g., on Chrome's
+ // shutdown the message loop of the UI thread (= the origin thread) stops
+ // before D-Bus threaed's. In such a case, PostTask() fails. Because we
+ // cannot do much thing here, instead, simply leak the callback rather than
+ // destroying it on the D-Bus thread, which could be unexpected from the
+ // direct or indirect caller of CallMethod.
+ auto* callback_to_be_deleted =
+ new ResponseOrErrorCallback(std::move(callback_));
+ ANNOTATE_LEAKING_OBJECT_PTR(callback_to_be_deleted);
+ origin_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&base::DeletePointer<ResponseOrErrorCallback>,
+ callback_to_be_deleted));
}
-} // namespace
+ObjectProxy::ResponseOrErrorCallback
+ObjectProxy::ReplyCallbackHolder::ReleaseCallback() {
+ DCHECK(origin_task_runner_->RunsTasksInCurrentSequence());
+ return std::move(callback_);
+}
ObjectProxy::ObjectProxy(Bus* bus,
const std::string& service_name,
@@ -60,6 +120,7 @@ ObjectProxy::ObjectProxy(Bus* bus,
object_path_(object_path),
ignore_service_unknown_errors_(
options & IGNORE_SERVICE_UNKNOWN_ERRORS) {
+ LOG_IF(FATAL, !object_path_.IsValid()) << object_path_.value();
}
ObjectProxy::~ObjectProxy() {
@@ -118,33 +179,33 @@ std::unique_ptr<Response> ObjectProxy::CallMethodAndBlock(
void ObjectProxy::CallMethod(MethodCall* method_call,
int timeout_ms,
ResponseCallback callback) {
- CallMethodWithErrorCallback(method_call, timeout_ms, callback,
- base::Bind(&ObjectProxy::OnCallMethodError,
- this,
- method_call->GetInterface(),
- method_call->GetMember(),
- callback));
+ auto internal_callback = base::BindOnce(
+ &ObjectProxy::OnCallMethod, this, method_call->GetInterface(),
+ method_call->GetMember(), std::move(callback));
+
+ CallMethodWithErrorResponse(method_call, timeout_ms,
+ std::move(internal_callback));
}
-void ObjectProxy::CallMethodWithErrorCallback(MethodCall* method_call,
- int timeout_ms,
- ResponseCallback callback,
- ErrorCallback error_callback) {
+void ObjectProxy::CallMethodWithErrorResponse(
+ MethodCall* method_call,
+ int timeout_ms,
+ ResponseOrErrorCallback callback) {
bus_->AssertOnOriginThread();
const base::TimeTicks start_time = base::TimeTicks::Now();
+ ReplyCallbackHolder callback_holder(bus_->GetOriginTaskRunner(),
+ std::move(callback));
+
if (!method_call->SetDestination(service_name_) ||
!method_call->SetPath(object_path_)) {
- // In case of a failure, run the error callback with NULL.
- DBusMessage* response_message = NULL;
- base::Closure task = base::Bind(&ObjectProxy::RunResponseCallback,
- this,
- callback,
- error_callback,
- start_time,
- response_message);
- bus_->GetOriginTaskRunner()->PostTask(FROM_HERE, task);
+ // In case of a failure, run the error callback with nullptr.
+ base::OnceClosure task =
+ base::BindOnce(&ObjectProxy::RunResponseOrErrorCallback, this,
+ std::move(callback_holder), start_time,
+ nullptr /* response */, nullptr /* error_response */);
+ bus_->GetOriginTaskRunner()->PostTask(FROM_HERE, std::move(task));
return;
}
@@ -154,19 +215,34 @@ void ObjectProxy::CallMethodWithErrorCallback(MethodCall* method_call,
DBusMessage* request_message = method_call->raw_message();
dbus_message_ref(request_message);
- base::Closure task = base::Bind(&ObjectProxy::StartAsyncMethodCall,
- this,
- timeout_ms,
- request_message,
- callback,
- error_callback,
- start_time);
statistics::AddSentMethodCall(service_name_,
method_call->GetInterface(),
method_call->GetMember());
// Wait for the response in the D-Bus thread.
- bus_->GetDBusTaskRunner()->PostTask(FROM_HERE, task);
+ base::OnceClosure task =
+ base::BindOnce(&ObjectProxy::StartAsyncMethodCall, this, timeout_ms,
+ request_message, std::move(callback_holder), start_time);
+ bus_->GetDBusTaskRunner()->PostTask(FROM_HERE, std::move(task));
+}
+
+void ObjectProxy::CallMethodWithErrorCallback(MethodCall* method_call,
+ int timeout_ms,
+ ResponseCallback callback,
+ ErrorCallback error_callback) {
+ auto internal_callback = base::BindOnce(
+ [](ResponseCallback callback, ErrorCallback error_callback,
+ Response* response, ErrorResponse* error_response) {
+ if (response) {
+ std::move(callback).Run(response);
+ } else {
+ std::move(error_callback).Run(error_response);
+ }
+ },
+ std::move(callback), std::move(error_callback));
+
+ CallMethodWithErrorResponse(method_call, timeout_ms,
+ std::move(internal_callback));
}
void ObjectProxy::ConnectToSignal(const std::string& interface_name,
@@ -178,16 +254,17 @@ void ObjectProxy::ConnectToSignal(const std::string& interface_name,
if (bus_->HasDBusThread()) {
base::PostTaskAndReplyWithResult(
bus_->GetDBusTaskRunner(), FROM_HERE,
- base::Bind(&ObjectProxy::ConnectToSignalInternal, this, interface_name,
- signal_name, signal_callback),
- base::Bind(on_connected_callback, interface_name, signal_name));
+ base::BindOnce(&ObjectProxy::ConnectToSignalInternal, this,
+ interface_name, signal_name, signal_callback),
+ base::BindOnce(std::move(on_connected_callback), interface_name,
+ signal_name));
} else {
// If the bus doesn't have a dedicated dbus thread we need to call
// ConnectToSignalInternal directly otherwise we might miss a signal
// that is currently queued if we do a PostTask.
const bool success =
ConnectToSignalInternal(interface_name, signal_name, signal_callback);
- on_connected_callback.Run(interface_name, signal_name, success);
+ std::move(on_connected_callback).Run(interface_name, signal_name, success);
}
}
@@ -202,10 +279,10 @@ void ObjectProxy::WaitForServiceToBeAvailable(
WaitForServiceToBeAvailableCallback callback) {
bus_->AssertOnOriginThread();
- wait_for_service_to_be_available_callbacks_.push_back(callback);
+ wait_for_service_to_be_available_callbacks_.push_back(std::move(callback));
bus_->GetDBusTaskRunner()->PostTask(
FROM_HERE,
- base::Bind(&ObjectProxy::WaitForServiceToBeAvailableInternal, this));
+ base::BindOnce(&ObjectProxy::WaitForServiceToBeAvailableInternal, this));
}
void ObjectProxy::Detach() {
@@ -231,161 +308,118 @@ void ObjectProxy::Detach() {
pending_calls_.clear();
}
-// static
-ObjectProxy::ResponseCallback ObjectProxy::EmptyResponseCallback() {
- return base::Bind(&EmptyResponseCallbackBody);
-}
-
-ObjectProxy::OnPendingCallIsCompleteData::OnPendingCallIsCompleteData(
- ObjectProxy* in_object_proxy,
- ResponseCallback in_response_callback,
- ErrorCallback in_error_callback,
- base::TimeTicks in_start_time)
- : object_proxy(in_object_proxy),
- response_callback(in_response_callback),
- error_callback(in_error_callback),
- start_time(in_start_time) {
-}
-
-ObjectProxy::OnPendingCallIsCompleteData::~OnPendingCallIsCompleteData() {
-}
-
void ObjectProxy::StartAsyncMethodCall(int timeout_ms,
DBusMessage* request_message,
- ResponseCallback response_callback,
- ErrorCallback error_callback,
+ ReplyCallbackHolder callback_holder,
base::TimeTicks start_time) {
bus_->AssertOnDBusThread();
if (!bus_->Connect() || !bus_->SetUpAsyncOperations()) {
- // In case of a failure, run the error callback with NULL.
- DBusMessage* response_message = NULL;
- base::Closure task = base::Bind(&ObjectProxy::RunResponseCallback,
- this,
- response_callback,
- error_callback,
- start_time,
- response_message);
- bus_->GetOriginTaskRunner()->PostTask(FROM_HERE, task);
+ // In case of a failure, run the error callback with nullptr.
+ base::OnceClosure task =
+ base::BindOnce(&ObjectProxy::RunResponseOrErrorCallback, this,
+ std::move(callback_holder), start_time,
+ nullptr /* response */, nullptr /* error_response */);
+ bus_->GetOriginTaskRunner()->PostTask(FROM_HERE, std::move(task));
dbus_message_unref(request_message);
return;
}
- DBusPendingCall* pending_call = NULL;
-
- bus_->SendWithReply(request_message, &pending_call, timeout_ms);
-
- // Prepare the data we'll be passing to OnPendingCallIsCompleteThunk().
- // The data will be deleted in OnPendingCallIsCompleteThunk().
- OnPendingCallIsCompleteData* data =
- new OnPendingCallIsCompleteData(this, response_callback, error_callback,
- start_time);
+ DBusPendingCall* dbus_pending_call = nullptr;
+ bus_->SendWithReply(request_message, &dbus_pending_call, timeout_ms);
+ using PendingCallback =
+ base::OnceCallback<void(DBusPendingCall * pending_call)>;
// This returns false only when unable to allocate memory.
const bool success = dbus_pending_call_set_notify(
- pending_call,
- &ObjectProxy::OnPendingCallIsCompleteThunk,
- data,
- &DeleteVoidPointer<OnPendingCallIsCompleteData>);
+ dbus_pending_call,
+ [](DBusPendingCall* pending_call, void* user_data) {
+ std::move(*static_cast<PendingCallback*>(user_data)).Run(pending_call);
+ },
+ // PendingCallback instance is owned by libdbus.
+ new PendingCallback(base::BindOnce(&ObjectProxy::OnPendingCallIsComplete,
+ this, std::move(callback_holder),
+ start_time)),
+ [](void* user_data) { delete static_cast<PendingCallback*>(user_data); });
CHECK(success) << "Unable to allocate memory";
- pending_calls_.insert(pending_call);
+ pending_calls_.insert(dbus_pending_call);
// It's now safe to unref the request message.
dbus_message_unref(request_message);
}
-void ObjectProxy::OnPendingCallIsComplete(DBusPendingCall* pending_call,
- ResponseCallback response_callback,
- ErrorCallback error_callback,
- base::TimeTicks start_time) {
+void ObjectProxy::OnPendingCallIsComplete(ReplyCallbackHolder callback_holder,
+ base::TimeTicks start_time,
+ DBusPendingCall* pending_call) {
bus_->AssertOnDBusThread();
DBusMessage* response_message = dbus_pending_call_steal_reply(pending_call);
- base::Closure task = base::Bind(&ObjectProxy::RunResponseCallback,
- this,
- response_callback,
- error_callback,
- start_time,
- response_message);
- bus_->GetOriginTaskRunner()->PostTask(FROM_HERE, task);
+
+ // Either |response| or |error_response| takes ownership of the
+ // |response_message|.
+ std::unique_ptr<Response> response;
+ std::unique_ptr<ErrorResponse> error_response;
+ if (dbus_message_get_type(response_message) == DBUS_MESSAGE_TYPE_ERROR) {
+ error_response = ErrorResponse::FromRawMessage(response_message);
+ } else {
+ response = Response::FromRawMessage(response_message);
+ }
+
+ base::OnceClosure task =
+ base::BindOnce(&ObjectProxy::RunResponseOrErrorCallback, this,
+ std::move(callback_holder), start_time, response.get(),
+ error_response.get());
+
+ // The message should be deleted on the D-Bus thread for a complicated
+ // reason:
+ //
+ // libdbus keeps track of the number of bytes in the incoming message
+ // queue to ensure that the data size in the queue is manageable. The
+ // bookkeeping is partly done via dbus_message_unref(), and immediately
+ // asks the client code (Chrome) to stop monitoring the underlying
+ // socket, if the number of bytes exceeds a certian number, which is set
+ // to 63MB, per dbus-transport.cc:
+ //
+ // /* Try to default to something that won't totally hose the system,
+ // * but doesn't impose too much of a limitation.
+ // */
+ // transport->max_live_messages_size = _DBUS_ONE_MEGABYTE * 63;
+ //
+ // The monitoring of the socket is done on the D-Bus thread (see Watch
+ // class in bus.cc), hence we should stop the monitoring on D-Bus thread.
+ bus_->GetOriginTaskRunner()->PostTaskAndReply(
+ FROM_HERE, std::move(task),
+ base::BindOnce(
+ [](Response* response, ErrorResponse* error_response) {
+ // Do nothing.
+ },
+ base::Owned(response.release()),
+ base::Owned(error_response.release())));
// Remove the pending call from the set.
pending_calls_.erase(pending_call);
dbus_pending_call_unref(pending_call);
}
-void ObjectProxy::RunResponseCallback(ResponseCallback response_callback,
- ErrorCallback error_callback,
- base::TimeTicks start_time,
- DBusMessage* response_message) {
+void ObjectProxy::RunResponseOrErrorCallback(
+ ReplyCallbackHolder callback_holder,
+ base::TimeTicks start_time,
+ Response* response,
+ ErrorResponse* error_response) {
bus_->AssertOnOriginThread();
+ callback_holder.ReleaseCallback().Run(response, error_response);
- bool method_call_successful = false;
- if (!response_message) {
- // The response is not received.
- error_callback.Run(NULL);
- } else if (dbus_message_get_type(response_message) ==
- DBUS_MESSAGE_TYPE_ERROR) {
- // This will take |response_message| and release (unref) it.
- std::unique_ptr<ErrorResponse> error_response(
- ErrorResponse::FromRawMessage(response_message));
- error_callback.Run(error_response.get());
- // Delete the message on the D-Bus thread. See below for why.
- bus_->GetDBusTaskRunner()->PostTask(
- FROM_HERE,
- base::Bind(&base::DeletePointer<ErrorResponse>,
- error_response.release()));
- } else {
- // This will take |response_message| and release (unref) it.
- std::unique_ptr<Response> response(
- Response::FromRawMessage(response_message));
- // The response is successfully received.
- response_callback.Run(response.get());
- // The message should be deleted on the D-Bus thread for a complicated
- // reason:
- //
- // libdbus keeps track of the number of bytes in the incoming message
- // queue to ensure that the data size in the queue is manageable. The
- // bookkeeping is partly done via dbus_message_unref(), and immediately
- // asks the client code (Chrome) to stop monitoring the underlying
- // socket, if the number of bytes exceeds a certian number, which is set
- // to 63MB, per dbus-transport.cc:
- //
- // /* Try to default to something that won't totally hose the system,
- // * but doesn't impose too much of a limitation.
- // */
- // transport->max_live_messages_size = _DBUS_ONE_MEGABYTE * 63;
- //
- // The monitoring of the socket is done on the D-Bus thread (see Watch
- // class in bus.cc), hence we should stop the monitoring from D-Bus
- // thread, not from the current thread here, which is likely UI thread.
- bus_->GetDBusTaskRunner()->PostTask(
- FROM_HERE,
- base::Bind(&base::DeletePointer<Response>, response.release()));
-
- method_call_successful = true;
+ if (response) {
// Record time spent for the method call. Don't include failures.
UMA_HISTOGRAM_TIMES("DBus.AsyncMethodCallTime",
base::TimeTicks::Now() - start_time);
}
// Record if the method call is successful, or not. 1 if successful.
- UMA_HISTOGRAM_ENUMERATION("DBus.AsyncMethodCallSuccess",
- method_call_successful,
+ UMA_HISTOGRAM_ENUMERATION("DBus.AsyncMethodCallSuccess", response ? 1 : 0,
kSuccessRatioHistogramMaxValue);
}
-void ObjectProxy::OnPendingCallIsCompleteThunk(DBusPendingCall* pending_call,
- void* user_data) {
- OnPendingCallIsCompleteData* data =
- reinterpret_cast<OnPendingCallIsCompleteData*>(user_data);
- ObjectProxy* self = data->object_proxy;
- self->OnPendingCallIsComplete(pending_call,
- data->response_callback,
- data->error_callback,
- data->start_time);
-}
-
bool ObjectProxy::ConnectToNameOwnerChangedSignal() {
bus_->AssertOnDBusThread();
@@ -428,10 +462,10 @@ bool ObjectProxy::ConnectToSignalInternal(const std::string& interface_name,
GetAbsoluteMemberName(interface_name, signal_name);
// Add a match rule so the signal goes through HandleMessage().
- const std::string match_rule =
- base::StringPrintf("type='signal', interface='%s', path='%s'",
- interface_name.c_str(),
- object_path_.value().c_str());
+ const std::string match_rule = base::StringPrintf(
+ "type='signal', sender='%s', interface='%s', path='%s'",
+ service_name_.c_str(), interface_name.c_str(),
+ object_path_.value().c_str());
return AddMatchRuleWithCallback(match_rule,
absolute_signal_name,
signal_callback);
@@ -444,8 +478,8 @@ void ObjectProxy::WaitForServiceToBeAvailableInternal() {
const bool service_is_ready = false;
bus_->GetOriginTaskRunner()->PostTask(
FROM_HERE,
- base::Bind(&ObjectProxy::RunWaitForServiceToBeAvailableCallbacks,
- this, service_is_ready));
+ base::BindOnce(&ObjectProxy::RunWaitForServiceToBeAvailableCallbacks,
+ this, service_is_ready));
return;
}
@@ -453,8 +487,8 @@ void ObjectProxy::WaitForServiceToBeAvailableInternal() {
if (service_is_available) { // Service is already available.
bus_->GetOriginTaskRunner()->PostTask(
FROM_HERE,
- base::Bind(&ObjectProxy::RunWaitForServiceToBeAvailableCallbacks,
- this, service_is_available));
+ base::BindOnce(&ObjectProxy::RunWaitForServiceToBeAvailableCallbacks,
+ this, service_is_available));
return;
}
}
@@ -503,7 +537,6 @@ DBusHandlerResult ObjectProxy::HandleMessage(
std::string sender = signal->GetSender();
if (service_name_owner_ != sender) {
LOG(ERROR) << "Rejecting a message from a wrong sender.";
- UMA_HISTOGRAM_COUNTS("DBus.RejectedSignalCount", 1);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
@@ -542,7 +575,7 @@ void ObjectProxy::RunMethod(base::TimeTicks start_time,
iter->Run(signal);
// Delete the message on the D-Bus thread. See comments in
- // RunResponseCallback().
+ // RunResponseOrErrorCallback().
bus_->GetDBusTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&base::DeletePointer<Signal>, signal));
@@ -583,21 +616,30 @@ void ObjectProxy::LogMethodCallFailure(
LOG(ERROR) << msg.str();
}
-void ObjectProxy::OnCallMethodError(const std::string& interface_name,
- const std::string& method_name,
- ResponseCallback response_callback,
- ErrorResponse* error_response) {
+void ObjectProxy::OnCallMethod(const std::string& interface_name,
+ const std::string& method_name,
+ ResponseCallback response_callback,
+ Response* response,
+ ErrorResponse* error_response) {
+ if (response) {
+ // Method call was successful.
+ std::move(response_callback).Run(response);
+ return;
+ }
+ // Method call failed.
+ std::string error_name;
+ std::string error_message;
if (error_response) {
// Error message may contain the error message as string.
+ error_name = error_response->GetErrorName();
MessageReader reader(error_response);
- std::string error_message;
reader.PopString(&error_message);
- LogMethodCallFailure(interface_name,
- method_name,
- error_response->GetErrorName(),
- error_message);
+ } else {
+ error_name = "unknown error type";
}
- response_callback.Run(NULL);
+ LogMethodCallFailure(interface_name, method_name, error_name, error_message);
+
+ std::move(response_callback).Run(nullptr);
}
bool ObjectProxy::AddMatchRuleWithCallback(
@@ -711,7 +753,7 @@ void ObjectProxy::RunWaitForServiceToBeAvailableCallbacks(
std::vector<WaitForServiceToBeAvailableCallback> callbacks;
callbacks.swap(wait_for_service_to_be_available_callbacks_);
for (size_t i = 0; i < callbacks.size(); ++i)
- callbacks[i].Run(service_is_available);
+ std::move(callbacks[i]).Run(service_is_available);
}
} // namespace dbus
diff --git a/dbus/object_proxy.h b/dbus/object_proxy.h
index 5de390461e..22e44f1d64 100644
--- a/dbus/object_proxy.h
+++ b/dbus/object_proxy.h
@@ -21,6 +21,10 @@
#include "dbus/dbus_export.h"
#include "dbus/object_path.h"
+namespace base {
+class TaskRunner;
+} // namespace base
+
namespace dbus {
class Bus;
@@ -68,30 +72,38 @@ class CHROME_DBUS_EXPORT ObjectProxy
// Called when an error response is returned or no response is returned.
// Used for CallMethodWithErrorCallback().
- typedef base::Callback<void(ErrorResponse*)> ErrorCallback;
+ using ErrorCallback = base::OnceCallback<void(ErrorResponse*)>;
// Called when the response is returned. Used for CallMethod().
- typedef base::Callback<void(Response*)> ResponseCallback;
+ using ResponseCallback = base::OnceCallback<void(Response*)>;
+
+ // Called when the response is returned or an error occurs. Used for
+ // CallMethodWithErrorResponse().
+ // Note that even in error case, ErrorResponse* may be nullptr.
+ // E.g. out-of-memory error is found in libdbus, or the connection of
+ // |bus_| is not yet established.
+ using ResponseOrErrorCallback =
+ base::OnceCallback<void(Response*, ErrorResponse*)>;
// Called when a signal is received. Signal* is the incoming signal.
- typedef base::Callback<void (Signal*)> SignalCallback;
+ using SignalCallback = base::Callback<void(Signal*)>;
// Called when NameOwnerChanged signal is received.
- typedef base::Callback<void(
- const std::string& old_owner,
- const std::string& new_owner)> NameOwnerChangedCallback;
+ using NameOwnerChangedCallback =
+ base::Callback<void(const std::string& old_owner,
+ const std::string& new_owner)>;
// Called when the service becomes available.
- typedef base::Callback<void(
- bool service_is_available)> WaitForServiceToBeAvailableCallback;
+ using WaitForServiceToBeAvailableCallback =
+ base::OnceCallback<void(bool service_is_available)>;
// Called when the object proxy is connected to the signal.
// Parameters:
// - the interface name.
// - the signal name.
// - whether it was successful or not.
- typedef base::Callback<void (const std::string&, const std::string&, bool)>
- OnConnectedCallback;
+ using OnConnectedCallback =
+ base::OnceCallback<void(const std::string&, const std::string&, bool)>;
// Calls the method of the remote object and blocks until the response
// is returned. Returns NULL on error with the error details specified
@@ -115,12 +127,10 @@ class CHROME_DBUS_EXPORT ObjectProxy
// |callback| will be called in the origin thread, once the method call
// is complete. As it's called in the origin thread, |callback| can
// safely reference objects in the origin thread (i.e. UI thread in most
- // cases). If the caller is not interested in the response from the
- // method (i.e. calling a method that does not return a value),
- // EmptyResponseCallback() can be passed to the |callback| parameter.
+ // cases).
//
// If the method call is successful, a pointer to Response object will
- // be passed to the callback. If unsuccessful, NULL will be passed to
+ // be passed to the callback. If unsuccessful, nullptr will be passed to
// the callback.
//
// Must be called in the origin thread.
@@ -130,12 +140,24 @@ class CHROME_DBUS_EXPORT ObjectProxy
// Requests to call the method of the remote object.
//
+ // This is almost as same as CallMethod() defined above.
+ // The difference is that, the |callback| can take ErrorResponse.
+ // In case of error, ErrorResponse object is passed to the |callback|
+ // if the remote object returned an error, or nullptr if a response was not
+ // received at all (e.g., D-Bus connection is not established). In either
+ // error case, Response* should be nullptr.
+ virtual void CallMethodWithErrorResponse(MethodCall* method_call,
+ int timeout_ms,
+ ResponseOrErrorCallback callback);
+
+ // DEPRECATED. Please use CallMethodWithErrorResponse() instead.
+ // TODO(hidehiko): Remove this when migration is done.
+ // Requests to call the method of the remote object.
+ //
// |callback| and |error_callback| will be called in the origin thread, once
// the method call is complete. As it's called in the origin thread,
// |callback| can safely reference objects in the origin thread (i.e.
- // UI thread in most cases). If the caller is not interested in the response
- // from the method (i.e. calling a method that does not return a value),
- // EmptyResponseCallback() can be passed to the |callback| parameter.
+ // UI thread in most cases).
//
// If the method call is successful, |callback| will be invoked with a
// Response object. If unsuccessful, |error_callback| will be invoked with an
@@ -190,10 +212,6 @@ class CHROME_DBUS_EXPORT ObjectProxy
const ObjectPath& object_path() const { return object_path_; }
- // Returns an empty callback that does nothing. Can be used for
- // CallMethod().
- static ResponseCallback EmptyResponseCallback();
-
protected:
// This is protected, so we can define sub classes.
virtual ~ObjectProxy();
@@ -201,44 +219,50 @@ class CHROME_DBUS_EXPORT ObjectProxy
private:
friend class base::RefCountedThreadSafe<ObjectProxy>;
- // Struct of data we'll be passing from StartAsyncMethodCall() to
- // OnPendingCallIsCompleteThunk().
- struct OnPendingCallIsCompleteData {
- OnPendingCallIsCompleteData(ObjectProxy* in_object_proxy,
- ResponseCallback in_response_callback,
- ErrorCallback error_callback,
- base::TimeTicks start_time);
- ~OnPendingCallIsCompleteData();
-
- ObjectProxy* object_proxy;
- ResponseCallback response_callback;
- ErrorCallback error_callback;
- base::TimeTicks start_time;
+ // Callback passed to CallMethod and its family should be deleted on the
+ // origin thread in any cases. This class manages the work.
+ class ReplyCallbackHolder {
+ public:
+ // Designed to be created on the origin thread.
+ // Both |origin_task_runner| and |callback| must not be null.
+ ReplyCallbackHolder(scoped_refptr<base::TaskRunner> origin_task_runner,
+ ResponseOrErrorCallback callback);
+
+ // This is movable to be bound to an OnceCallback.
+ ReplyCallbackHolder(ReplyCallbackHolder&& other);
+
+ // |callback_| needs to be destroyed on the origin thread.
+ // If this is not destroyed on non-origin thread, it PostTask()s the
+ // callback to the origin thread for destroying.
+ ~ReplyCallbackHolder();
+
+ // Returns |callback_| with releasing its ownership.
+ // This must be called on the origin thread.
+ ResponseOrErrorCallback ReleaseCallback();
+
+ private:
+ scoped_refptr<base::TaskRunner> origin_task_runner_;
+ ResponseOrErrorCallback callback_;
+ DISALLOW_COPY_AND_ASSIGN(ReplyCallbackHolder);
};
// Starts the async method call. This is a helper function to implement
// CallMethod().
void StartAsyncMethodCall(int timeout_ms,
DBusMessage* request_message,
- ResponseCallback response_callback,
- ErrorCallback error_callback,
+ ReplyCallbackHolder callback_holder,
base::TimeTicks start_time);
// Called when the pending call is complete.
- void OnPendingCallIsComplete(DBusPendingCall* pending_call,
- ResponseCallback response_callback,
- ErrorCallback error_callback,
- base::TimeTicks start_time);
-
- // Runs the response callback with the given response object.
- void RunResponseCallback(ResponseCallback response_callback,
- ErrorCallback error_callback,
- base::TimeTicks start_time,
- DBusMessage* response_message);
+ void OnPendingCallIsComplete(ReplyCallbackHolder callback_holder,
+ base::TimeTicks start_time,
+ DBusPendingCall* pending_call);
- // Redirects the function call to OnPendingCallIsComplete().
- static void OnPendingCallIsCompleteThunk(DBusPendingCall* pending_call,
- void* user_data);
+ // Runs the ResponseOrErrorCallback with the given response object.
+ void RunResponseOrErrorCallback(ReplyCallbackHolder callback_holderk,
+ base::TimeTicks start_time,
+ Response* response,
+ ErrorResponse* error_response);
// Connects to NameOwnerChanged signal.
bool ConnectToNameOwnerChangedSignal();
@@ -272,11 +296,14 @@ class CHROME_DBUS_EXPORT ObjectProxy
const base::StringPiece& error_name,
const base::StringPiece& error_message) const;
- // Used as ErrorCallback by CallMethod().
- void OnCallMethodError(const std::string& interface_name,
- const std::string& method_name,
- ResponseCallback response_callback,
- ErrorResponse* error_response);
+ // Used as ResponseOrErrorCallback by CallMethod().
+ // Logs error message, and drops |error_response| from the arguments to pass
+ // |response_callback|.
+ void OnCallMethod(const std::string& interface_name,
+ const std::string& method_name,
+ ResponseCallback response_callback,
+ Response* response,
+ ErrorResponse* error_response);
// Adds the match rule to the bus and associate the callback with the signal.
bool AddMatchRuleWithCallback(const std::string& match_rule,
@@ -310,7 +337,7 @@ class CHROME_DBUS_EXPORT ObjectProxy
// The method table where keys are absolute signal names (i.e. interface
// name + signal name), and values are lists of the corresponding callbacks.
- typedef std::map<std::string, std::vector<SignalCallback> > MethodTable;
+ using MethodTable = std::map<std::string, std::vector<SignalCallback>>;
MethodTable method_table_;
// The callback called when NameOwnerChanged signal is received.
diff --git a/dbus/property.cc b/dbus/property.cc
index 0351c4a59a..e1fb05ac7b 100644
--- a/dbus/property.cc
+++ b/dbus/property.cc
@@ -23,7 +23,7 @@ namespace dbus {
PropertyBase::PropertyBase() : property_set_(nullptr), is_valid_(false) {}
-PropertyBase::~PropertyBase() {}
+PropertyBase::~PropertyBase() = default;
void PropertyBase::Init(PropertySet* property_set, const std::string& name) {
DCHECK(!property_set_);
@@ -45,8 +45,7 @@ PropertySet::PropertySet(
property_changed_callback_(property_changed_callback),
weak_ptr_factory_(this) {}
-PropertySet::~PropertySet() {
-}
+PropertySet::~PropertySet() = default;
void PropertySet::RegisterProperty(const std::string& name,
PropertyBase* property) {
@@ -230,12 +229,12 @@ void PropertySet::OnSet(PropertyBase* property,
bool PropertySet::UpdatePropertiesFromReader(MessageReader* reader) {
DCHECK(reader);
- MessageReader array_reader(NULL);
+ MessageReader array_reader(nullptr);
if (!reader->PopArray(&array_reader))
return false;
while (array_reader.HasMoreData()) {
- MessageReader dict_entry_reader(NULL);
+ MessageReader dict_entry_reader(nullptr);
if (array_reader.PopDictEntry(&dict_entry_reader))
UpdatePropertyFromReader(&dict_entry_reader);
}
@@ -270,7 +269,7 @@ bool PropertySet::UpdatePropertyFromReader(MessageReader* reader) {
bool PropertySet::InvalidatePropertiesFromReader(MessageReader* reader) {
DCHECK(reader);
- MessageReader array_reader(NULL);
+ MessageReader array_reader(nullptr);
if (!reader->PopArray(&array_reader))
return false;
@@ -489,13 +488,13 @@ void Property<ObjectPath>::AppendSetValueToWriter(MessageWriter* writer) {
}
//
-// Property<std::vector<std::string> > specialization.
+// Property<std::vector<std::string>> specialization.
//
template <>
-bool Property<std::vector<std::string> >::PopValueFromReader(
+bool Property<std::vector<std::string>>::PopValueFromReader(
MessageReader* reader) {
- MessageReader variant_reader(NULL);
+ MessageReader variant_reader(nullptr);
if (!reader->PopVariant(&variant_reader))
return false;
@@ -504,22 +503,22 @@ bool Property<std::vector<std::string> >::PopValueFromReader(
}
template <>
-void Property<std::vector<std::string> >::AppendSetValueToWriter(
+void Property<std::vector<std::string>>::AppendSetValueToWriter(
MessageWriter* writer) {
- MessageWriter variant_writer(NULL);
+ MessageWriter variant_writer(nullptr);
writer->OpenVariant("as", &variant_writer);
variant_writer.AppendArrayOfStrings(set_value_);
writer->CloseContainer(&variant_writer);
}
//
-// Property<std::vector<ObjectPath> > specialization.
+// Property<std::vector<ObjectPath>> specialization.
//
template <>
-bool Property<std::vector<ObjectPath> >::PopValueFromReader(
+bool Property<std::vector<ObjectPath>>::PopValueFromReader(
MessageReader* reader) {
- MessageReader variant_reader(NULL);
+ MessageReader variant_reader(nullptr);
if (!reader->PopVariant(&variant_reader))
return false;
@@ -528,26 +527,26 @@ bool Property<std::vector<ObjectPath> >::PopValueFromReader(
}
template <>
-void Property<std::vector<ObjectPath> >::AppendSetValueToWriter(
+void Property<std::vector<ObjectPath>>::AppendSetValueToWriter(
MessageWriter* writer) {
- MessageWriter variant_writer(NULL);
+ MessageWriter variant_writer(nullptr);
writer->OpenVariant("ao", &variant_writer);
variant_writer.AppendArrayOfObjectPaths(set_value_);
writer->CloseContainer(&variant_writer);
}
//
-// Property<std::vector<uint8_t> > specialization.
+// Property<std::vector<uint8_t>> specialization.
//
template <>
bool Property<std::vector<uint8_t>>::PopValueFromReader(MessageReader* reader) {
- MessageReader variant_reader(NULL);
+ MessageReader variant_reader(nullptr);
if (!reader->PopVariant(&variant_reader))
return false;
value_.clear();
- const uint8_t* bytes = NULL;
+ const uint8_t* bytes = nullptr;
size_t length = 0;
if (!variant_reader.PopArrayOfBytes(&bytes, &length))
return false;
@@ -558,7 +557,7 @@ bool Property<std::vector<uint8_t>>::PopValueFromReader(MessageReader* reader) {
template <>
void Property<std::vector<uint8_t>>::AppendSetValueToWriter(
MessageWriter* writer) {
- MessageWriter variant_writer(NULL);
+ MessageWriter variant_writer(nullptr);
writer->OpenVariant("ay", &variant_writer);
variant_writer.AppendArrayOfBytes(set_value_.data(), set_value_.size());
writer->CloseContainer(&variant_writer);
@@ -571,14 +570,14 @@ void Property<std::vector<uint8_t>>::AppendSetValueToWriter(
template <>
bool Property<std::map<std::string, std::string>>::PopValueFromReader(
MessageReader* reader) {
- MessageReader variant_reader(NULL);
- MessageReader array_reader(NULL);
+ MessageReader variant_reader(nullptr);
+ MessageReader array_reader(nullptr);
if (!reader->PopVariant(&variant_reader) ||
!variant_reader.PopArray(&array_reader))
return false;
value_.clear();
while (array_reader.HasMoreData()) {
- dbus::MessageReader dict_entry_reader(NULL);
+ dbus::MessageReader dict_entry_reader(nullptr);
if (!array_reader.PopDictEntry(&dict_entry_reader))
return false;
std::string key;
@@ -594,12 +593,12 @@ bool Property<std::map<std::string, std::string>>::PopValueFromReader(
template <>
void Property<std::map<std::string, std::string>>::AppendSetValueToWriter(
MessageWriter* writer) {
- MessageWriter variant_writer(NULL);
- MessageWriter dict_writer(NULL);
+ MessageWriter variant_writer(nullptr);
+ MessageWriter dict_writer(nullptr);
writer->OpenVariant("a{ss}", &variant_writer);
variant_writer.OpenArray("{ss}", &dict_writer);
for (const auto& pair : set_value_) {
- dbus::MessageWriter entry_writer(NULL);
+ dbus::MessageWriter entry_writer(nullptr);
dict_writer.OpenDictEntry(&entry_writer);
entry_writer.AppendString(pair.first);
entry_writer.AppendString(pair.second);
@@ -617,20 +616,20 @@ void Property<std::map<std::string, std::string>>::AppendSetValueToWriter(
template <>
bool Property<std::vector<std::pair<std::vector<uint8_t>, uint16_t>>>::
PopValueFromReader(MessageReader* reader) {
- MessageReader variant_reader(NULL);
- MessageReader array_reader(NULL);
+ MessageReader variant_reader(nullptr);
+ MessageReader array_reader(nullptr);
if (!reader->PopVariant(&variant_reader) ||
!variant_reader.PopArray(&array_reader))
return false;
value_.clear();
while (array_reader.HasMoreData()) {
- dbus::MessageReader struct_reader(NULL);
+ dbus::MessageReader struct_reader(nullptr);
if (!array_reader.PopStruct(&struct_reader))
return false;
std::pair<std::vector<uint8_t>, uint16_t> entry;
- const uint8_t* bytes = NULL;
+ const uint8_t* bytes = nullptr;
size_t length = 0;
if (!struct_reader.PopArrayOfBytes(&bytes, &length))
return false;
@@ -645,8 +644,8 @@ bool Property<std::vector<std::pair<std::vector<uint8_t>, uint16_t>>>::
template <>
void Property<std::vector<std::pair<std::vector<uint8_t>, uint16_t>>>::
AppendSetValueToWriter(MessageWriter* writer) {
- MessageWriter variant_writer(NULL);
- MessageWriter array_writer(NULL);
+ MessageWriter variant_writer(nullptr);
+ MessageWriter array_writer(nullptr);
writer->OpenVariant("a(ayq)", &variant_writer);
variant_writer.OpenArray("(ayq)", &array_writer);
for (const auto& pair : set_value_) {
@@ -662,13 +661,13 @@ void Property<std::vector<std::pair<std::vector<uint8_t>, uint16_t>>>::
}
//
-// Property<std::unordered_map<std::string, std::vector<uint8_t>>>
+// Property<std::map<std::string, std::vector<uint8_t>>>
// specialization.
//
template <>
-bool Property<std::unordered_map<std::string, std::vector<uint8_t>>>::
- PopValueFromReader(MessageReader* reader) {
+bool Property<std::map<std::string, std::vector<uint8_t>>>::PopValueFromReader(
+ MessageReader* reader) {
MessageReader variant_reader(nullptr);
MessageReader dict_reader(nullptr);
if (!reader->PopVariant(&variant_reader) ||
@@ -698,7 +697,7 @@ bool Property<std::unordered_map<std::string, std::vector<uint8_t>>>::
}
template <>
-void Property<std::unordered_map<std::string, std::vector<uint8_t>>>::
+void Property<std::map<std::string, std::vector<uint8_t>>>::
AppendSetValueToWriter(MessageWriter* writer) {
MessageWriter variant_writer(nullptr);
MessageWriter dict_writer(nullptr);
@@ -726,13 +725,13 @@ void Property<std::unordered_map<std::string, std::vector<uint8_t>>>::
}
//
-// Property<std::unordered_map<uint16_t, std::vector<uint8_t>>>
+// Property<std::map<uint16_t, std::vector<uint8_t>>>
// specialization.
//
template <>
-bool Property<std::unordered_map<uint16_t, std::vector<uint8_t>>>::
- PopValueFromReader(MessageReader* reader) {
+bool Property<std::map<uint16_t, std::vector<uint8_t>>>::PopValueFromReader(
+ MessageReader* reader) {
MessageReader variant_reader(nullptr);
MessageReader dict_reader(nullptr);
if (!reader->PopVariant(&variant_reader) ||
@@ -762,8 +761,8 @@ bool Property<std::unordered_map<uint16_t, std::vector<uint8_t>>>::
}
template <>
-void Property<std::unordered_map<uint16_t, std::vector<uint8_t>>>::
- AppendSetValueToWriter(MessageWriter* writer) {
+void Property<std::map<uint16_t, std::vector<uint8_t>>>::AppendSetValueToWriter(
+ MessageWriter* writer) {
MessageWriter variant_writer(nullptr);
MessageWriter dict_writer(nullptr);
@@ -800,12 +799,12 @@ template class Property<uint64_t>;
template class Property<double>;
template class Property<std::string>;
template class Property<ObjectPath>;
-template class Property<std::vector<std::string> >;
-template class Property<std::vector<ObjectPath> >;
+template class Property<std::vector<std::string>>;
+template class Property<std::vector<ObjectPath>>;
template class Property<std::vector<uint8_t>>;
template class Property<std::map<std::string, std::string>>;
template class Property<std::vector<std::pair<std::vector<uint8_t>, uint16_t>>>;
-template class Property<std::unordered_map<std::string, std::vector<uint8_t>>>;
-template class Property<std::unordered_map<uint16_t, std::vector<uint8_t>>>;
+template class Property<std::map<std::string, std::vector<uint8_t>>>;
+template class Property<std::map<uint16_t, std::vector<uint8_t>>>;
} // namespace dbus
diff --git a/dbus/property.h b/dbus/property.h
index 0559ea0554..41d6c24148 100644
--- a/dbus/property.h
+++ b/dbus/property.h
@@ -9,7 +9,6 @@
#include <map>
#include <string>
-#include <unordered_map>
#include <utility>
#include <vector>
@@ -41,7 +40,7 @@
// dbus::Property<std::string> name;
// dbus::Property<uint16_t> version;
// dbus::Property<dbus::ObjectPath> parent;
-// dbus::Property<std::vector<std::string> > children;
+// dbus::Property<std::vector<std::string>> children;
//
// Properties(dbus::ObjectProxy* object_proxy,
// const PropertyChangedCallback callback)
@@ -62,7 +61,7 @@
//
// Example (continued):
//
-// typedef std::map<std::pair<dbus::ObjectProxy*, Properties*> > Object;
+// typedef std::map<std::pair<dbus::ObjectProxy*, Properties*>> Object;
// typedef std::map<dbus::ObjectPath, Object> ObjectMap;
// ObjectMap object_map_;
//
@@ -613,25 +612,25 @@ extern template class CHROME_DBUS_EXPORT
template <>
CHROME_DBUS_EXPORT bool
-Property<std::unordered_map<std::string, std::vector<uint8_t>>>::
- PopValueFromReader(MessageReader* reader);
+Property<std::map<std::string, std::vector<uint8_t>>>::PopValueFromReader(
+ MessageReader* reader);
template <>
CHROME_DBUS_EXPORT void
-Property<std::unordered_map<std::string, std::vector<uint8_t>>>::
- AppendSetValueToWriter(MessageWriter* writer);
+Property<std::map<std::string, std::vector<uint8_t>>>::AppendSetValueToWriter(
+ MessageWriter* writer);
extern template class CHROME_DBUS_EXPORT
- Property<std::unordered_map<std::string, std::vector<uint8_t>>>;
+ Property<std::map<std::string, std::vector<uint8_t>>>;
template <>
CHROME_DBUS_EXPORT bool
-Property<std::unordered_map<uint16_t, std::vector<uint8_t>>>::
- PopValueFromReader(MessageReader* reader);
+Property<std::map<uint16_t, std::vector<uint8_t>>>::PopValueFromReader(
+ MessageReader* reader);
template <>
CHROME_DBUS_EXPORT void
-Property<std::unordered_map<uint16_t, std::vector<uint8_t>>>::
- AppendSetValueToWriter(MessageWriter* writer);
+Property<std::map<uint16_t, std::vector<uint8_t>>>::AppendSetValueToWriter(
+ MessageWriter* writer);
extern template class CHROME_DBUS_EXPORT
- Property<std::unordered_map<uint16_t, std::vector<uint8_t>>>;
+ Property<std::map<uint16_t, std::vector<uint8_t>>>;
#pragma GCC diagnostic pop
diff --git a/dbus/util.h b/dbus/util.h
index b05834ddd6..b983b6fdb8 100644
--- a/dbus/util.h
+++ b/dbus/util.h
@@ -23,13 +23,6 @@ CHROME_DBUS_EXPORT std::string GetAbsoluteMemberName(
const std::string& interface_name,
const std::string& member_name);
-// Similar to base::DeletePointer, but takes void* as an argument.
-// Used as DBusFreeFunction.
-template<typename T>
-void DeleteVoidPointer(void* memory) {
- delete static_cast<T*>(memory);
-}
-
} // namespace dbus
#endif // DBUS_UTIL_H_
diff --git a/dbus/values_util.cc b/dbus/values_util.cc
index 1e035c9cfe..d2bcb97772 100644
--- a/dbus/values_util.cc
+++ b/dbus/values_util.cc
@@ -4,11 +4,11 @@
#include "dbus/values_util.h"
+#include <memory>
#include <utility>
#include "base/json/json_writer.h"
#include "base/logging.h"
-#include "base/memory/ptr_util.h"
#include "base/values.h"
#include "dbus/message.h"
@@ -38,7 +38,7 @@ bool PopDictionaryEntries(MessageReader* reader,
base::DictionaryValue* dictionary_value) {
while (reader->HasMoreData()) {
DCHECK_EQ(Message::DICT_ENTRY, reader->GetDataType());
- MessageReader entry_reader(NULL);
+ MessageReader entry_reader(nullptr);
if (!reader->PopDictEntry(&entry_reader))
return false;
// Get key as a string.
@@ -66,7 +66,7 @@ bool PopDictionaryEntries(MessageReader* reader,
// Gets the D-Bus type signature for the value.
std::string GetTypeSignature(const base::Value& value) {
- switch (value.GetType()) {
+ switch (value.type()) {
case base::Value::Type::BOOLEAN:
return "b";
case base::Value::Type::INTEGER:
@@ -82,7 +82,7 @@ std::string GetTypeSignature(const base::Value& value) {
case base::Value::Type::LIST:
return "av";
default:
- DLOG(ERROR) << "Unexpected type " << value.GetType();
+ DLOG(ERROR) << "Unexpected type " << value.type();
return std::string();
}
}
@@ -98,37 +98,37 @@ std::unique_ptr<base::Value> PopDataAsValue(MessageReader* reader) {
case Message::BYTE: {
uint8_t value = 0;
if (reader->PopByte(&value))
- result = base::MakeUnique<base::Value>(value);
+ result = std::make_unique<base::Value>(value);
break;
}
case Message::BOOL: {
bool value = false;
if (reader->PopBool(&value))
- result = base::MakeUnique<base::Value>(value);
+ result = std::make_unique<base::Value>(value);
break;
}
case Message::INT16: {
int16_t value = 0;
if (reader->PopInt16(&value))
- result = base::MakeUnique<base::Value>(value);
+ result = std::make_unique<base::Value>(value);
break;
}
case Message::UINT16: {
uint16_t value = 0;
if (reader->PopUint16(&value))
- result = base::MakeUnique<base::Value>(value);
+ result = std::make_unique<base::Value>(value);
break;
}
case Message::INT32: {
int32_t value = 0;
if (reader->PopInt32(&value))
- result = base::MakeUnique<base::Value>(value);
+ result = std::make_unique<base::Value>(value);
break;
}
case Message::UINT32: {
uint32_t value = 0;
if (reader->PopUint32(&value)) {
- result = base::MakeUnique<base::Value>(static_cast<double>(value));
+ result = std::make_unique<base::Value>(static_cast<double>(value));
}
break;
}
@@ -137,7 +137,7 @@ std::unique_ptr<base::Value> PopDataAsValue(MessageReader* reader) {
if (reader->PopInt64(&value)) {
DLOG_IF(WARNING, !IsExactlyRepresentableByDouble(value)) <<
value << " is not exactly representable by double";
- result = base::MakeUnique<base::Value>(static_cast<double>(value));
+ result = std::make_unique<base::Value>(static_cast<double>(value));
}
break;
}
@@ -146,26 +146,26 @@ std::unique_ptr<base::Value> PopDataAsValue(MessageReader* reader) {
if (reader->PopUint64(&value)) {
DLOG_IF(WARNING, !IsExactlyRepresentableByDouble(value)) <<
value << " is not exactly representable by double";
- result = base::MakeUnique<base::Value>(static_cast<double>(value));
+ result = std::make_unique<base::Value>(static_cast<double>(value));
}
break;
}
case Message::DOUBLE: {
double value = 0;
if (reader->PopDouble(&value))
- result = base::MakeUnique<base::Value>(value);
+ result = std::make_unique<base::Value>(value);
break;
}
case Message::STRING: {
std::string value;
if (reader->PopString(&value))
- result = base::MakeUnique<base::Value>(value);
+ result = std::make_unique<base::Value>(value);
break;
}
case Message::OBJECT_PATH: {
ObjectPath value;
if (reader->PopObjectPath(&value))
- result = base::MakeUnique<base::Value>(value.value());
+ result = std::make_unique<base::Value>(value.value());
break;
}
case Message::UNIX_FD: {
@@ -174,7 +174,7 @@ std::unique_ptr<base::Value> PopDataAsValue(MessageReader* reader) {
break;
}
case Message::ARRAY: {
- MessageReader sub_reader(NULL);
+ MessageReader sub_reader(nullptr);
if (reader->PopArray(&sub_reader)) {
// If the type of the array's element is DICT_ENTRY, create a
// DictionaryValue, otherwise create a ListValue.
@@ -192,7 +192,7 @@ std::unique_ptr<base::Value> PopDataAsValue(MessageReader* reader) {
break;
}
case Message::STRUCT: {
- MessageReader sub_reader(NULL);
+ MessageReader sub_reader(nullptr);
if (reader->PopStruct(&sub_reader)) {
std::unique_ptr<base::ListValue> list_value(new base::ListValue);
if (PopListElements(&sub_reader, list_value.get()))
@@ -205,7 +205,7 @@ std::unique_ptr<base::Value> PopDataAsValue(MessageReader* reader) {
NOTREACHED();
break;
case Message::VARIANT: {
- MessageReader sub_reader(NULL);
+ MessageReader sub_reader(nullptr);
if (reader->PopVariant(&sub_reader))
result = PopDataAsValue(&sub_reader);
break;
@@ -215,7 +215,7 @@ std::unique_ptr<base::Value> PopDataAsValue(MessageReader* reader) {
}
void AppendBasicTypeValueData(MessageWriter* writer, const base::Value& value) {
- switch (value.GetType()) {
+ switch (value.type()) {
case base::Value::Type::BOOLEAN: {
bool bool_value = false;
bool success = value.GetAsBoolean(&bool_value);
@@ -245,29 +245,29 @@ void AppendBasicTypeValueData(MessageWriter* writer, const base::Value& value) {
break;
}
default:
- DLOG(ERROR) << "Unexpected type " << value.GetType();
+ DLOG(ERROR) << "Unexpected type " << value.type();
break;
}
}
void AppendBasicTypeValueDataAsVariant(MessageWriter* writer,
const base::Value& value) {
- MessageWriter sub_writer(NULL);
+ MessageWriter sub_writer(nullptr);
writer->OpenVariant(GetTypeSignature(value), &sub_writer);
AppendBasicTypeValueData(&sub_writer, value);
writer->CloseContainer(&sub_writer);
}
void AppendValueData(MessageWriter* writer, const base::Value& value) {
- switch (value.GetType()) {
+ switch (value.type()) {
case base::Value::Type::DICTIONARY: {
- const base::DictionaryValue* dictionary = NULL;
+ const base::DictionaryValue* dictionary = nullptr;
value.GetAsDictionary(&dictionary);
- dbus::MessageWriter array_writer(NULL);
+ dbus::MessageWriter array_writer(nullptr);
writer->OpenArray("{sv}", &array_writer);
for (base::DictionaryValue::Iterator iter(*dictionary);
!iter.IsAtEnd(); iter.Advance()) {
- dbus::MessageWriter dict_entry_writer(NULL);
+ dbus::MessageWriter dict_entry_writer(nullptr);
array_writer.OpenDictEntry(&dict_entry_writer);
dict_entry_writer.AppendString(iter.key());
AppendValueDataAsVariant(&dict_entry_writer, iter.value());
@@ -277,12 +277,12 @@ void AppendValueData(MessageWriter* writer, const base::Value& value) {
break;
}
case base::Value::Type::LIST: {
- const base::ListValue* list = NULL;
+ const base::ListValue* list = nullptr;
value.GetAsList(&list);
- dbus::MessageWriter array_writer(NULL);
+ dbus::MessageWriter array_writer(nullptr);
writer->OpenArray("v", &array_writer);
for (const auto& value : *list) {
- AppendValueDataAsVariant(&array_writer, *value);
+ AppendValueDataAsVariant(&array_writer, value);
}
writer->CloseContainer(&array_writer);
break;
@@ -294,12 +294,12 @@ void AppendValueData(MessageWriter* writer, const base::Value& value) {
AppendBasicTypeValueData(writer, value);
break;
default:
- DLOG(ERROR) << "Unexpected type: " << value.GetType();
+ DLOG(ERROR) << "Unexpected type: " << value.type();
}
}
void AppendValueDataAsVariant(MessageWriter* writer, const base::Value& value) {
- MessageWriter variant_writer(NULL);
+ MessageWriter variant_writer(nullptr);
writer->OpenVariant(GetTypeSignature(value), &variant_writer);
AppendValueData(&variant_writer, value);
writer->CloseContainer(&variant_writer);
diff --git a/device/bluetooth/bluetooth_advertisement.cc b/device/bluetooth/bluetooth_advertisement.cc
index 05b0e52d22..4ff9f4e0fa 100644
--- a/device/bluetooth/bluetooth_advertisement.cc
+++ b/device/bluetooth/bluetooth_advertisement.cc
@@ -10,8 +10,7 @@ BluetoothAdvertisement::Data::Data(AdvertisementType type)
: type_(type), include_tx_power_(false) {
}
-BluetoothAdvertisement::Data::~Data() {
-}
+BluetoothAdvertisement::Data::~Data() = default;
BluetoothAdvertisement::Data::Data()
: type_(ADVERTISEMENT_TYPE_BROADCAST), include_tx_power_(false) {
@@ -29,9 +28,7 @@ void BluetoothAdvertisement::RemoveObserver(
observers_.RemoveObserver(observer);
}
-BluetoothAdvertisement::BluetoothAdvertisement() {
-}
-BluetoothAdvertisement::~BluetoothAdvertisement() {
-}
+BluetoothAdvertisement::BluetoothAdvertisement() = default;
+BluetoothAdvertisement::~BluetoothAdvertisement() = default;
} // namespace device
diff --git a/device/bluetooth/bluetooth_advertisement.h b/device/bluetooth/bluetooth_advertisement.h
index 412baa72ee..d379f72453 100644
--- a/device/bluetooth/bluetooth_advertisement.h
+++ b/device/bluetooth/bluetooth_advertisement.h
@@ -17,6 +17,7 @@
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/observer_list.h"
+#include "build/build_config.h"
#include "device/bluetooth/bluetooth_export.h"
namespace device {
@@ -37,9 +38,10 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdvertisement
// is not registered.
ERROR_ADVERTISEMENT_INVALID_LENGTH, // Advertisement is not of a valid
// length.
-#if defined(OS_CHROMEOS) || defined(OS_LINUX)
+#if defined(OS_LINUX)
ERROR_INVALID_ADVERTISEMENT_INTERVAL, // Advertisement interval specified
// is out of valid range.
+ ERROR_RESET_ADVERTISING, // Error while resetting advertising.
#endif
INVALID_ADVERTISEMENT_ERROR_CODE
};
diff --git a/device/bluetooth/bluetooth_uuid.cc b/device/bluetooth/bluetooth_uuid.cc
index b35094deba..88e2736339 100644
--- a/device/bluetooth/bluetooth_uuid.cc
+++ b/device/bluetooth/bluetooth_uuid.cc
@@ -7,7 +7,15 @@
#include <stddef.h>
#include "base/logging.h"
+#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+
+#if defined(OS_WIN)
+#include <objbase.h>
+
+#include "base/strings/string16.h"
+#endif // defined(OS_WIN)
namespace device {
@@ -69,11 +77,37 @@ BluetoothUUID::BluetoothUUID(const std::string& uuid) {
GetCanonicalUuid(uuid, &value_, &canonical_value_, &format_);
}
+#if defined(OS_WIN)
+BluetoothUUID::BluetoothUUID(GUID uuid) {
+ // 36 chars for UUID + 2 chars for braces + 1 char for null-terminator.
+ constexpr int kBufferSize = 39;
+ wchar_t buffer[kBufferSize];
+ int result = ::StringFromGUID2(uuid, buffer, kBufferSize);
+ DCHECK_EQ(kBufferSize, result);
+ DCHECK_EQ('{', buffer[0]);
+ DCHECK_EQ('}', buffer[37]);
+
+ GetCanonicalUuid(base::WideToUTF8(base::WStringPiece(buffer).substr(1, 36)),
+ &value_, &canonical_value_, &format_);
+ DCHECK_EQ(kFormat128Bit, format_);
+}
+#endif // defined(OS_WIN)
+
BluetoothUUID::BluetoothUUID() : format_(kFormatInvalid) {
}
-BluetoothUUID::~BluetoothUUID() {
+BluetoothUUID::~BluetoothUUID() = default;
+
+#if defined(OS_WIN)
+// static
+GUID BluetoothUUID::GetCanonicalValueAsGUID(base::StringPiece uuid) {
+ DCHECK_EQ(36u, uuid.size());
+ base::string16 braced_uuid = L'{' + base::UTF8ToWide(uuid) + L'}';
+ GUID guid;
+ CHECK_EQ(NOERROR, ::CLSIDFromString(braced_uuid.data(), &guid));
+ return guid;
}
+#endif // defined(OS_WIN)
bool BluetoothUUID::IsValid() const {
return format_ != kFormatInvalid;
diff --git a/device/bluetooth/bluetooth_uuid.h b/device/bluetooth/bluetooth_uuid.h
index 8487f6a2da..c262414835 100644
--- a/device/bluetooth/bluetooth_uuid.h
+++ b/device/bluetooth/bluetooth_uuid.h
@@ -7,8 +7,15 @@
#include <string>
+#include "build/build_config.h"
#include "device/bluetooth/bluetooth_export.h"
+#if defined(OS_WIN)
+#include <rpc.h>
+
+#include "base/strings/string_piece_forward.h"
+#endif // defined(OS_WIN)
+
namespace device {
// Opaque wrapper around a Bluetooth UUID. Instances of UUID represent the
@@ -42,6 +49,12 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothUUID {
// after construction.
explicit BluetoothUUID(const std::string& uuid);
+#if defined(OS_WIN)
+ // Windows exclusive constructor converting a GUID structure to a
+ // BluetoothUUID. This will always result in a 128 bit Format.
+ explicit BluetoothUUID(GUID uuid);
+#endif // defined(OS_WIN)
+
// 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
@@ -49,6 +62,11 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothUUID {
BluetoothUUID();
virtual ~BluetoothUUID();
+#if defined(OS_WIN)
+ // The canonical UUID string format is device::BluetoothUUID.value().
+ static GUID GetCanonicalValueAsGUID(base::StringPiece uuid);
+#endif // defined(OS_WIN)
+
// Returns true, if the UUID is in a valid canonical format.
bool IsValid() const;
diff --git a/device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.cc b/device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.cc
index ee6cbf6112..6b651d618e 100644
--- a/device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.cc
+++ b/device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.cc
@@ -12,7 +12,7 @@
namespace bluez {
BluetoothServiceAttributeValueBlueZ::BluetoothServiceAttributeValueBlueZ()
- : type_(NULLTYPE), size_(0), value_(base::Value::CreateNullValue()) {}
+ : type_(NULLTYPE), size_(0), value_(std::make_unique<base::Value>()) {}
BluetoothServiceAttributeValueBlueZ::BluetoothServiceAttributeValueBlueZ(
Type type,
@@ -40,7 +40,7 @@ operator=(const BluetoothServiceAttributeValueBlueZ& attribute) {
size_ = attribute.size_;
if (attribute.type_ == SEQUENCE) {
value_ = nullptr;
- sequence_ = base::MakeUnique<Sequence>(*attribute.sequence_);
+ sequence_ = std::make_unique<Sequence>(*attribute.sequence_);
} else {
value_ = attribute.value_->CreateDeepCopy();
sequence_ = nullptr;
@@ -49,6 +49,7 @@ operator=(const BluetoothServiceAttributeValueBlueZ& attribute) {
return *this;
}
-BluetoothServiceAttributeValueBlueZ::~BluetoothServiceAttributeValueBlueZ() {}
+BluetoothServiceAttributeValueBlueZ::~BluetoothServiceAttributeValueBlueZ() =
+ default;
} // namespace bluez
diff --git a/gen/mojo/common/common_custom_types__type_mappings b/gen/mojo/common/common_custom_types__type_mappings
deleted file mode 100644
index 2e5cd5d93c..0000000000
--- a/gen/mojo/common/common_custom_types__type_mappings
+++ /dev/null
@@ -1,193 +0,0 @@
-{
- "c++": {
- "mojo.common.mojom.Value": {
- "hashable": false,
- "typename": "std::unique_ptr<base::Value>",
- "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<base::ListValue>",
- "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<base::DictionaryValue>",
- "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
index 0a4fcfa84e..9631ce8fd2 100644
--- a/ipc/ipc.mojom
+++ b/ipc/ipc.mojom
@@ -4,33 +4,27 @@
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;
-};
+import "mojo/public/mojom/base/big_buffer.mojom";
+import "mojo/public/interfaces/bindings/native_struct.mojom";
// A placeholder interface type since we don't yet support generic associated
// message pipe handles.
interface GenericInterface {};
+// Typemapped such that arbitrarily large IPC::Message objects can be sent and
+// received with minimal copying.
+struct Message {
+ mojo_base.mojom.BigBuffer buffer;
+ array<mojo.native.SerializedHandle>? handles;
+};
+
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<uint8> data, array<SerializedHandle>? handles);
+ Receive(Message message);
// Requests a Channel-associated interface.
GetAssociatedInterface(string name, associated GenericInterface& request);
@@ -38,3 +32,4 @@ interface Channel {
// A strictly nominal interface used to identify Channel bootstrap requests.
interface ChannelBootstrap {};
+
diff --git a/ipc/ipc_buildflags.h b/ipc/ipc_buildflags.h
new file mode 100644
index 0000000000..e3cbaee072
--- /dev/null
+++ b/ipc/ipc_buildflags.h
@@ -0,0 +1,8 @@
+#ifndef CPP_IPC_BUILDFLAGS_H_
+#define CPP_IPC_BUILDFLAGS_H_
+
+#include <build/buildflag.h>
+
+#define BUILDFLAG_INTERNAL_IPC_MESSAGE_LOG_ENABLED() (0)
+
+#endif // CPP_IPC_BUILDFLAGS_H_
diff --git a/ipc/ipc_channel.cc b/ipc/ipc_channel.cc
new file mode 100644
index 0000000000..284d79bb3f
--- /dev/null
+++ b/ipc/ipc_channel.cc
@@ -0,0 +1,51 @@
+// 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_channel.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+
+#include "base/atomic_sequence_num.h"
+#include "base/rand_util.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+
+namespace {
+
+// Global atomic used to guarantee channel IDs are unique.
+base::AtomicSequenceNumber g_last_id;
+
+} // namespace
+
+namespace IPC {
+
+// static
+constexpr size_t Channel::kMaximumMessageSize;
+
+// static
+std::string Channel::GenerateUniqueRandomChannelID() {
+ // Note: the string must start with the current process id, this is how
+ // some child processes determine the pid of the parent.
+ //
+ // This is composed of a unique incremental identifier, the process ID of
+ // the creator, an identifier for the child instance, and a strong random
+ // component. The strong random component prevents other processes from
+ // hijacking or squatting on predictable channel names.
+#if defined(OS_NACL_NONSFI)
+ // The seccomp sandbox disallows use of getpid(), so we provide a
+ // dummy PID.
+ int process_id = -1;
+#else
+ int process_id = base::GetCurrentProcId();
+#endif
+ return base::StringPrintf("%d.%u.%d",
+ process_id,
+ g_last_id.GetNext(),
+ base::RandInt(0, std::numeric_limits<int32_t>::max()));
+}
+
+} // namespace IPC
diff --git a/ipc/ipc_channel.h b/ipc/ipc_channel.h
new file mode 100644
index 0000000000..5b0221cb57
--- /dev/null
+++ b/ipc/ipc_channel.h
@@ -0,0 +1,266 @@
+// 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_H_
+#define IPC_IPC_CHANNEL_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/component_export.h"
+#include "base/files/scoped_file.h"
+#include "base/memory/ref_counted.h"
+#include "base/process/process.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "ipc/ipc.mojom.h"
+#include "ipc/ipc_channel_handle.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_sender.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
+#include "mojo/public/cpp/bindings/associated_interface_request.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+#include "mojo/public/cpp/bindings/thread_safe_interface_ptr.h"
+
+#if defined(OS_POSIX)
+#include <sys/types.h>
+#endif
+
+namespace IPC {
+
+class Listener;
+
+//------------------------------------------------------------------------------
+// See
+// http://www.chromium.org/developers/design-documents/inter-process-communication
+// for overview of IPC in Chromium.
+
+// Channels are implemented using mojo message pipes on all platforms other
+// than NaCl.
+
+class COMPONENT_EXPORT(IPC) Channel : public Sender {
+ // Security tests need access to the pipe handle.
+ friend class ChannelTest;
+
+ public:
+ // Flags to test modes
+ enum ModeFlags {
+ MODE_NO_FLAG = 0x0,
+ MODE_SERVER_FLAG = 0x1,
+ MODE_CLIENT_FLAG = 0x2,
+ };
+
+ // Some Standard Modes
+ // TODO(morrita): These are under deprecation work. You should use Create*()
+ // functions instead.
+ enum Mode {
+ MODE_NONE = MODE_NO_FLAG,
+ MODE_SERVER = MODE_SERVER_FLAG,
+ MODE_CLIENT = MODE_CLIENT_FLAG,
+ };
+
+ // Messages internal to the IPC implementation are defined here.
+ // Uses Maximum value of message type (uint16_t), to avoid conflicting
+ // with normal message types, which are enumeration constants starting from 0.
+ enum {
+ // The Hello message is sent by the peer when the channel is connected.
+ // The message contains just the process id (pid).
+ // The message has a special routing_id (MSG_ROUTING_NONE)
+ // and type (HELLO_MESSAGE_TYPE).
+ HELLO_MESSAGE_TYPE = UINT16_MAX,
+ // The CLOSE_FD_MESSAGE_TYPE is used in the IPC class to
+ // work around a bug in sendmsg() on Mac. When an FD is sent
+ // over the socket, a CLOSE_FD_MESSAGE is sent with hops = 2.
+ // The client will return the message with hops = 1, *after* it
+ // has received the message that contains the FD. When we
+ // receive it again on the sender side, we close the FD.
+ CLOSE_FD_MESSAGE_TYPE = HELLO_MESSAGE_TYPE - 1
+ };
+
+ // Helper interface a Channel may implement to expose support for associated
+ // Mojo interfaces.
+ class COMPONENT_EXPORT(IPC) AssociatedInterfaceSupport {
+ public:
+ using GenericAssociatedInterfaceFactory =
+ base::Callback<void(mojo::ScopedInterfaceEndpointHandle)>;
+
+ virtual ~AssociatedInterfaceSupport() {}
+
+ // Returns a ThreadSafeForwarded for this channel which can be used to
+ // safely send mojom::Channel requests from arbitrary threads.
+ virtual std::unique_ptr<mojo::ThreadSafeForwarder<mojom::Channel>>
+ CreateThreadSafeChannel() = 0;
+
+ // Adds an interface factory to this channel for interface |name|. Must be
+ // safe to call from any thread.
+ virtual void AddGenericAssociatedInterface(
+ const std::string& name,
+ const GenericAssociatedInterfaceFactory& factory) = 0;
+
+ // Requests an associated interface from the remote endpoint.
+ virtual void GetGenericRemoteAssociatedInterface(
+ const std::string& name,
+ mojo::ScopedInterfaceEndpointHandle handle) = 0;
+
+ // Template helper to add an interface factory to this channel.
+ template <typename Interface>
+ using AssociatedInterfaceFactory =
+ base::Callback<void(mojo::AssociatedInterfaceRequest<Interface>)>;
+ template <typename Interface>
+ void AddAssociatedInterface(
+ const AssociatedInterfaceFactory<Interface>& factory) {
+ AddGenericAssociatedInterface(
+ Interface::Name_,
+ base::Bind(&BindAssociatedInterfaceRequest<Interface>, factory));
+ }
+
+ // Template helper to request a remote associated interface.
+ template <typename Interface>
+ void GetRemoteAssociatedInterface(
+ mojo::AssociatedInterfacePtr<Interface>* proxy) {
+ auto request = mojo::MakeRequest(proxy);
+ GetGenericRemoteAssociatedInterface(
+ Interface::Name_, request.PassHandle());
+ }
+
+ private:
+ template <typename Interface>
+ static void BindAssociatedInterfaceRequest(
+ const AssociatedInterfaceFactory<Interface>& factory,
+ mojo::ScopedInterfaceEndpointHandle handle) {
+ factory.Run(
+ mojo::AssociatedInterfaceRequest<Interface>(std::move(handle)));
+ }
+ };
+
+ // The maximum message size in bytes. Attempting to receive a message of this
+ // size or bigger results in a channel error.
+ static constexpr size_t kMaximumMessageSize = 128 * 1024 * 1024;
+
+ // Amount of data to read at once from the pipe.
+ static const size_t kReadBufferSize = 4 * 1024;
+
+ // Maximum persistent read buffer size. Read buffer can grow larger to
+ // accommodate large messages, but it's recommended to shrink back to this
+ // value because it fits 99.9% of all messages (see issue 529940 for data).
+ static const size_t kMaximumReadBufferSize = 64 * 1024;
+
+ // Initialize a Channel.
+ //
+ // |channel_handle| identifies the communication Channel. For POSIX, if
+ // the file descriptor in the channel handle is != -1, the channel takes
+ // ownership of the file descriptor and will close it appropriately, otherwise
+ // it will create a new descriptor internally.
+ // |listener| receives a callback on the current thread for each newly
+ // received message.
+ //
+ // There are four type of modes how channels operate:
+ //
+ // - Server and named server: In these modes, the Channel is
+ // responsible for settingb up the IPC object
+ // - An "open" named server: It accepts connections from ANY client.
+ // The caller must then implement their own access-control based on the
+ // client process' user Id.
+ // - Client and named client: In these mode, the Channel merely
+ // connects to the already established IPC object.
+ //
+ // Each mode has its own Create*() API to create the Channel object.
+ static std::unique_ptr<Channel> Create(
+ const IPC::ChannelHandle& channel_handle,
+ Mode mode,
+ Listener* listener);
+
+ static std::unique_ptr<Channel> CreateClient(
+ const IPC::ChannelHandle& channel_handle,
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner);
+
+ static std::unique_ptr<Channel> CreateServer(
+ const IPC::ChannelHandle& channel_handle,
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner);
+
+ ~Channel() override;
+
+ // Connect the pipe. On the server side, this will initiate
+ // waiting for connections. On the client, it attempts to
+ // connect to a pre-existing pipe. Note, calling Connect()
+ // will not block the calling thread and may complete
+ // asynchronously.
+ //
+ // The subclass implementation must call WillConnect() at the beginning of its
+ // implementation.
+ virtual bool Connect() WARN_UNUSED_RESULT = 0;
+
+ // Pause the channel. Subsequent sends will be queued internally until
+ // Unpause() is called and the channel is flushed either by Unpause() or a
+ // subsequent call to Flush().
+ virtual void Pause();
+
+ // Unpause the channel. This allows subsequent Send() calls to transmit
+ // messages immediately, without queueing. If |flush| is true, any messages
+ // queued while paused will be flushed immediately upon unpausing. Otherwise
+ // you must call Flush() explicitly.
+ //
+ // Not all implementations support Unpause(). See ConnectPaused() above for
+ // details.
+ virtual void Unpause(bool flush);
+
+ // Manually flush the pipe. This is only useful exactly once, and only after
+ // a call to Unpause(false), in order to explicitly flush out any
+ // messages which were queued prior to unpausing.
+ //
+ // Not all implementations support Flush(). See ConnectPaused() above for
+ // details.
+ virtual void Flush();
+
+ // Close this Channel explicitly. May be called multiple times.
+ // On POSIX calling close on an IPC channel that listens for connections will
+ // cause it to close any accepted connections, and it will stop listening for
+ // new connections. If you just want to close the currently accepted
+ // connection and listen for new ones, use ResetToAcceptingConnectionState.
+ virtual void Close() = 0;
+
+ // Gets a helper for associating Mojo interfaces with this Channel.
+ //
+ // NOTE: Not all implementations support this.
+ virtual AssociatedInterfaceSupport* GetAssociatedInterfaceSupport();
+
+ // Overridden from ipc::Sender.
+ // Send a message over the Channel to the listener on the other end.
+ //
+ // |message| must be allocated using operator new. This object will be
+ // deleted once the contents of the Message have been sent.
+ bool Send(Message* message) override = 0;
+
+#if !defined(OS_NACL_SFI)
+ // Generates a channel ID that's non-predictable and unique.
+ static std::string GenerateUniqueRandomChannelID();
+#endif
+
+#if defined(OS_LINUX)
+ // Sandboxed processes live in a PID namespace, so when sending the IPC hello
+ // message from client to server we need to send the PID from the global
+ // PID namespace.
+ static void SetGlobalPid(int pid);
+ static int GetGlobalPid();
+#endif
+
+ protected:
+ // Subclasses must call this method at the beginning of their implementation
+ // of Connect().
+ void WillConnect();
+
+ private:
+ bool did_start_connect_ = false;
+};
+
+} // namespace IPC
+
+#endif // IPC_IPC_CHANNEL_H_
diff --git a/ipc/ipc_channel_common.cc b/ipc/ipc_channel_common.cc
new file mode 100644
index 0000000000..7afb12d9b0
--- /dev/null
+++ b/ipc/ipc_channel_common.cc
@@ -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.
+
+#include "build/build_config.h"
+#include "ipc/ipc_channel.h"
+#include "ipc/ipc_channel_mojo.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace IPC {
+
+#if defined(OS_LINUX)
+
+namespace {
+int g_global_pid = 0;
+}
+
+// static
+void Channel::SetGlobalPid(int pid) {
+ g_global_pid = pid;
+}
+
+// static
+int Channel::GetGlobalPid() {
+ return g_global_pid;
+}
+
+#endif // defined(OS_LINUX)
+
+// static
+std::unique_ptr<Channel> Channel::CreateClient(
+ const IPC::ChannelHandle& channel_handle,
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner) {
+#if defined(OS_NACL_SFI)
+ return Channel::Create(channel_handle, Channel::MODE_CLIENT, listener);
+#else
+ DCHECK(channel_handle.is_mojo_channel_handle());
+ return ChannelMojo::Create(
+ mojo::ScopedMessagePipeHandle(channel_handle.mojo_handle),
+ Channel::MODE_CLIENT, listener, ipc_task_runner,
+ base::ThreadTaskRunnerHandle::Get());
+#endif
+}
+
+// static
+std::unique_ptr<Channel> Channel::CreateServer(
+ const IPC::ChannelHandle& channel_handle,
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner) {
+#if defined(OS_NACL_SFI)
+ return Channel::Create(channel_handle, Channel::MODE_SERVER, listener);
+#else
+ DCHECK(channel_handle.is_mojo_channel_handle());
+ return ChannelMojo::Create(
+ mojo::ScopedMessagePipeHandle(channel_handle.mojo_handle),
+ Channel::MODE_SERVER, listener, ipc_task_runner,
+ base::ThreadTaskRunnerHandle::Get());
+#endif
+}
+
+Channel::~Channel() = default;
+
+Channel::AssociatedInterfaceSupport* Channel::GetAssociatedInterfaceSupport() {
+ return nullptr;
+}
+
+void Channel::Pause() { NOTREACHED(); }
+
+void Channel::Unpause(bool flush) { NOTREACHED(); }
+
+void Channel::Flush() { NOTREACHED(); }
+
+void Channel::WillConnect() {
+ did_start_connect_ = true;
+}
+
+} // namespace IPC
diff --git a/ipc/ipc_channel_factory.cc b/ipc/ipc_channel_factory.cc
new file mode 100644
index 0000000000..655c0eb918
--- /dev/null
+++ b/ipc/ipc_channel_factory.cc
@@ -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.
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "ipc/ipc_channel_factory.h"
+#include "ipc/ipc_channel_mojo.h"
+
+namespace IPC {
+
+namespace {
+
+class PlatformChannelFactory : public ChannelFactory {
+ public:
+ PlatformChannelFactory(
+ ChannelHandle handle,
+ Channel::Mode mode,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner)
+ : handle_(handle), mode_(mode), ipc_task_runner_(ipc_task_runner) {}
+
+ std::unique_ptr<Channel> BuildChannel(Listener* listener) override {
+#if defined(OS_NACL_SFI)
+ return Channel::Create(handle_, mode_, listener);
+#else
+ DCHECK(handle_.is_mojo_channel_handle());
+ return ChannelMojo::Create(
+ mojo::ScopedMessagePipeHandle(handle_.mojo_handle), mode_, listener,
+ ipc_task_runner_, base::ThreadTaskRunnerHandle::Get());
+#endif
+ }
+
+ scoped_refptr<base::SingleThreadTaskRunner> GetIPCTaskRunner() override {
+ return ipc_task_runner_;
+ }
+
+ private:
+ ChannelHandle handle_;
+ Channel::Mode mode_;
+ scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformChannelFactory);
+};
+
+} // namespace
+
+// static
+std::unique_ptr<ChannelFactory> ChannelFactory::Create(
+ const ChannelHandle& handle,
+ Channel::Mode mode,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner) {
+ return std::make_unique<PlatformChannelFactory>(handle, mode,
+ ipc_task_runner);
+}
+
+} // namespace IPC
diff --git a/ipc/ipc_channel_factory.h b/ipc/ipc_channel_factory.h
new file mode 100644
index 0000000000..03d9626e59
--- /dev/null
+++ b/ipc/ipc_channel_factory.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 IPC_IPC_CHANNEL_FACTORY_H_
+#define IPC_IPC_CHANNEL_FACTORY_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/component_export.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "ipc/ipc_channel.h"
+
+namespace IPC {
+
+// Encapsulates how a Channel is created. A ChannelFactory can be
+// passed to the constructor of ChannelProxy or SyncChannel to tell them
+// how to create underlying channel.
+class COMPONENT_EXPORT(IPC) ChannelFactory {
+ public:
+ // Creates a factory for "native" channel built through
+ // IPC::Channel::Create().
+ static std::unique_ptr<ChannelFactory> Create(
+ const ChannelHandle& handle,
+ Channel::Mode mode,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner);
+
+ virtual ~ChannelFactory() { }
+ virtual std::unique_ptr<Channel> BuildChannel(Listener* listener) = 0;
+ virtual scoped_refptr<base::SingleThreadTaskRunner> GetIPCTaskRunner() = 0;
+};
+
+} // namespace IPC
+
+#endif // IPC_IPC_CHANNEL_FACTORY_H_
diff --git a/ipc/ipc_channel_mojo.cc b/ipc/ipc_channel_mojo.cc
new file mode 100644
index 0000000000..cb10b0915b
--- /dev/null
+++ b/ipc/ipc_channel_mojo.cc
@@ -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.
+
+#include "ipc/ipc_channel_mojo.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/process/process_handle.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "ipc/ipc_listener.h"
+#include "ipc/ipc_logging.h"
+#include "ipc/ipc_message_attachment_set.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_mojo_bootstrap.h"
+#include "ipc/ipc_mojo_handle_attachment.h"
+#include "ipc/native_handle_type_converters.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/system/platform_handle.h"
+
+namespace IPC {
+
+namespace {
+
+class MojoChannelFactory : public ChannelFactory {
+ public:
+ MojoChannelFactory(
+ mojo::ScopedMessagePipeHandle handle,
+ Channel::Mode mode,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& proxy_task_runner)
+ : handle_(std::move(handle)),
+ mode_(mode),
+ ipc_task_runner_(ipc_task_runner),
+ proxy_task_runner_(proxy_task_runner) {}
+
+ std::unique_ptr<Channel> BuildChannel(Listener* listener) override {
+ return ChannelMojo::Create(std::move(handle_), mode_, listener,
+ ipc_task_runner_, proxy_task_runner_);
+ }
+
+ scoped_refptr<base::SingleThreadTaskRunner> GetIPCTaskRunner() override {
+ return ipc_task_runner_;
+ }
+
+ private:
+ mojo::ScopedMessagePipeHandle handle_;
+ const Channel::Mode mode_;
+ scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner_;
+ scoped_refptr<base::SingleThreadTaskRunner> proxy_task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(MojoChannelFactory);
+};
+
+base::ProcessId GetSelfPID() {
+#if defined(OS_LINUX)
+ if (int global_pid = Channel::GetGlobalPid())
+ return global_pid;
+#endif // OS_LINUX
+#if defined(OS_NACL)
+ return -1;
+#else
+ return base::GetCurrentProcId();
+#endif // defined(OS_NACL)
+}
+
+} // namespace
+
+//------------------------------------------------------------------------------
+
+// static
+std::unique_ptr<ChannelMojo> ChannelMojo::Create(
+ mojo::ScopedMessagePipeHandle handle,
+ Mode mode,
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& proxy_task_runner) {
+ return base::WrapUnique(new ChannelMojo(std::move(handle), mode, listener,
+ ipc_task_runner, proxy_task_runner));
+}
+
+// static
+std::unique_ptr<ChannelFactory> ChannelMojo::CreateServerFactory(
+ mojo::ScopedMessagePipeHandle handle,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& proxy_task_runner) {
+ return std::make_unique<MojoChannelFactory>(
+ std::move(handle), Channel::MODE_SERVER, ipc_task_runner,
+ proxy_task_runner);
+}
+
+// static
+std::unique_ptr<ChannelFactory> ChannelMojo::CreateClientFactory(
+ mojo::ScopedMessagePipeHandle handle,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& proxy_task_runner) {
+ return std::make_unique<MojoChannelFactory>(
+ std::move(handle), Channel::MODE_CLIENT, ipc_task_runner,
+ proxy_task_runner);
+}
+
+ChannelMojo::ChannelMojo(
+ mojo::ScopedMessagePipeHandle handle,
+ Mode mode,
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& proxy_task_runner)
+ : task_runner_(ipc_task_runner),
+ pipe_(handle.get()),
+ listener_(listener),
+ weak_factory_(this) {
+ weak_ptr_ = weak_factory_.GetWeakPtr();
+ bootstrap_ = MojoBootstrap::Create(std::move(handle), mode, ipc_task_runner,
+ proxy_task_runner);
+}
+
+void ChannelMojo::ForwardMessageFromThreadSafePtr(mojo::Message message) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ if (!message_reader_ || !message_reader_->sender().is_bound())
+ return;
+ message_reader_->sender().internal_state()->ForwardMessage(
+ std::move(message));
+}
+
+void ChannelMojo::ForwardMessageWithResponderFromThreadSafePtr(
+ mojo::Message message,
+ std::unique_ptr<mojo::MessageReceiver> responder) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ if (!message_reader_ || !message_reader_->sender().is_bound())
+ return;
+ message_reader_->sender().internal_state()->ForwardMessageWithResponder(
+ std::move(message), std::move(responder));
+}
+
+ChannelMojo::~ChannelMojo() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ Close();
+}
+
+bool ChannelMojo::Connect() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ WillConnect();
+
+ mojom::ChannelAssociatedPtr sender;
+ mojom::ChannelAssociatedRequest receiver;
+ bootstrap_->Connect(&sender, &receiver);
+
+ DCHECK(!message_reader_);
+ sender->SetPeerPid(GetSelfPID());
+ message_reader_.reset(new internal::MessagePipeReader(
+ pipe_, std::move(sender), std::move(receiver), this));
+ return true;
+}
+
+void ChannelMojo::Pause() {
+ bootstrap_->Pause();
+}
+
+void ChannelMojo::Unpause(bool flush) {
+ bootstrap_->Unpause();
+ if (flush)
+ Flush();
+}
+
+void ChannelMojo::Flush() {
+ bootstrap_->Flush();
+}
+
+void ChannelMojo::Close() {
+ // NOTE: The MessagePipeReader's destructor may re-enter this function. Use
+ // caution when changing this method.
+ std::unique_ptr<internal::MessagePipeReader> reader =
+ std::move(message_reader_);
+ reader.reset();
+
+ base::AutoLock lock(associated_interface_lock_);
+ associated_interfaces_.clear();
+}
+
+void ChannelMojo::OnPipeError() {
+ DCHECK(task_runner_);
+ if (task_runner_->RunsTasksInCurrentSequence()) {
+ listener_->OnChannelError();
+ } else {
+ task_runner_->PostTask(FROM_HERE,
+ base::Bind(&ChannelMojo::OnPipeError, weak_ptr_));
+ }
+}
+
+void ChannelMojo::OnAssociatedInterfaceRequest(
+ const std::string& name,
+ mojo::ScopedInterfaceEndpointHandle handle) {
+ GenericAssociatedInterfaceFactory factory;
+ {
+ base::AutoLock locker(associated_interface_lock_);
+ auto iter = associated_interfaces_.find(name);
+ if (iter != associated_interfaces_.end())
+ factory = iter->second;
+ }
+
+ if (!factory.is_null())
+ factory.Run(std::move(handle));
+ else
+ listener_->OnAssociatedInterfaceRequest(name, std::move(handle));
+}
+
+bool ChannelMojo::Send(Message* message) {
+ DVLOG(2) << "sending message @" << message << " on channel @" << this
+ << " with type " << message->type();
+#if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
+ Logging::GetInstance()->OnSendMessage(message);
+#endif
+
+ std::unique_ptr<Message> scoped_message = base::WrapUnique(message);
+ if (!message_reader_)
+ return false;
+
+ // Comment copied from ipc_channel_posix.cc:
+ // We can't close the pipe here, because calling OnChannelError may destroy
+ // this object, and that would be bad if we are called from Send(). Instead,
+ // we return false and hope the caller will close the pipe. If they do not,
+ // the pipe will still be closed next time OnFileCanReadWithoutBlocking is
+ // called.
+ //
+ // With Mojo, there's no OnFileCanReadWithoutBlocking, but we expect the
+ // pipe's connection error handler will be invoked in its place.
+ return message_reader_->Send(std::move(scoped_message));
+}
+
+Channel::AssociatedInterfaceSupport*
+ChannelMojo::GetAssociatedInterfaceSupport() { return this; }
+
+std::unique_ptr<mojo::ThreadSafeForwarder<mojom::Channel>>
+ChannelMojo::CreateThreadSafeChannel() {
+ return std::make_unique<mojo::ThreadSafeForwarder<mojom::Channel>>(
+ task_runner_,
+ base::Bind(&ChannelMojo::ForwardMessageFromThreadSafePtr, weak_ptr_),
+ base::Bind(&ChannelMojo::ForwardMessageWithResponderFromThreadSafePtr,
+ weak_ptr_),
+ *bootstrap_->GetAssociatedGroup());
+}
+
+void ChannelMojo::OnPeerPidReceived(int32_t peer_pid) {
+ listener_->OnChannelConnected(peer_pid);
+}
+
+void ChannelMojo::OnMessageReceived(const Message& message) {
+ TRACE_EVENT2("ipc,toplevel", "ChannelMojo::OnMessageReceived",
+ "class", IPC_MESSAGE_ID_CLASS(message.type()),
+ "line", IPC_MESSAGE_ID_LINE(message.type()));
+ listener_->OnMessageReceived(message);
+ if (message.dispatch_error())
+ listener_->OnBadMessageReceived(message);
+}
+
+void ChannelMojo::OnBrokenDataReceived() {
+ listener_->OnBadMessageReceived(Message());
+}
+
+// static
+MojoResult ChannelMojo::ReadFromMessageAttachmentSet(
+ Message* message,
+ base::Optional<std::vector<mojo::native::SerializedHandlePtr>>* handles) {
+ DCHECK(!*handles);
+
+ MojoResult result = MOJO_RESULT_OK;
+ if (!message->HasAttachments())
+ return result;
+
+ std::vector<mojo::native::SerializedHandlePtr> output_handles;
+ MessageAttachmentSet* set = message->attachment_set();
+
+ for (unsigned i = 0; result == MOJO_RESULT_OK && i < set->size(); ++i) {
+ auto attachment = set->GetAttachmentAt(i);
+ auto serialized_handle = mojo::native::SerializedHandle::New();
+ serialized_handle->the_handle = attachment->TakeMojoHandle();
+ serialized_handle->type =
+ mojo::ConvertTo<mojo::native::SerializedHandle::Type>(
+ attachment->GetType());
+ output_handles.emplace_back(std::move(serialized_handle));
+ }
+ set->CommitAllDescriptors();
+
+ if (!output_handles.empty())
+ *handles = std::move(output_handles);
+
+ return result;
+}
+
+// static
+MojoResult ChannelMojo::WriteToMessageAttachmentSet(
+ base::Optional<std::vector<mojo::native::SerializedHandlePtr>> handles,
+ Message* message) {
+ if (!handles)
+ return MOJO_RESULT_OK;
+ for (size_t i = 0; i < handles->size(); ++i) {
+ auto& handle = handles->at(i);
+ scoped_refptr<MessageAttachment> unwrapped_attachment =
+ MessageAttachment::CreateFromMojoHandle(
+ std::move(handle->the_handle),
+ mojo::ConvertTo<MessageAttachment::Type>(handle->type));
+ if (!unwrapped_attachment) {
+ DLOG(WARNING) << "Pipe failed to unwrap handles.";
+ return MOJO_RESULT_UNKNOWN;
+ }
+
+ bool ok = message->attachment_set()->AddAttachment(
+ std::move(unwrapped_attachment));
+ DCHECK(ok);
+ if (!ok) {
+ LOG(ERROR) << "Failed to add new Mojo handle.";
+ return MOJO_RESULT_UNKNOWN;
+ }
+ }
+ return MOJO_RESULT_OK;
+}
+
+void ChannelMojo::AddGenericAssociatedInterface(
+ const std::string& name,
+ const GenericAssociatedInterfaceFactory& factory) {
+ base::AutoLock locker(associated_interface_lock_);
+ auto result = associated_interfaces_.insert({ name, factory });
+ DCHECK(result.second);
+}
+
+void ChannelMojo::GetGenericRemoteAssociatedInterface(
+ const std::string& name,
+ mojo::ScopedInterfaceEndpointHandle handle) {
+ if (message_reader_) {
+ message_reader_->GetRemoteInterface(name, std::move(handle));
+ } else {
+ // Attach the associated interface to a disconnected pipe, so that the
+ // associated interface pointer can be used to make calls (which are
+ // dropped).
+ mojo::AssociateWithDisconnectedPipe(std::move(handle));
+ }
+}
+
+} // namespace IPC
diff --git a/ipc/ipc_channel_mojo.h b/ipc/ipc_channel_mojo.h
new file mode 100644
index 0000000000..ba769f8afe
--- /dev/null
+++ b/ipc/ipc_channel_mojo.h
@@ -0,0 +1,143 @@
+// 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 IPC_IPC_CHANNEL_MOJO_H_
+#define IPC_IPC_CHANNEL_MOJO_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "base/task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "ipc/ipc.mojom.h"
+#include "ipc/ipc_channel.h"
+#include "ipc/ipc_channel_factory.h"
+#include "ipc/ipc_message_pipe_reader.h"
+#include "ipc/ipc_mojo_bootstrap.h"
+#include "mojo/public/cpp/bindings/thread_safe_interface_ptr.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace IPC {
+
+// Mojo-based IPC::Channel implementation over a Mojo message pipe.
+//
+// ChannelMojo builds a Mojo MessagePipe using the provided message pipe
+// |handle| and builds an associated interface for each direction on the
+// channel.
+//
+// TODO(morrita): Add APIs to create extra MessagePipes to let
+// Mojo-based objects talk over this Channel.
+//
+class COMPONENT_EXPORT(IPC) ChannelMojo
+ : public Channel,
+ public Channel::AssociatedInterfaceSupport,
+ public internal::MessagePipeReader::Delegate {
+ public:
+ // Creates a ChannelMojo.
+ static std::unique_ptr<ChannelMojo> Create(
+ mojo::ScopedMessagePipeHandle handle,
+ Mode mode,
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& proxy_task_runner);
+
+ // Create a factory object for ChannelMojo.
+ // The factory is used to create Mojo-based ChannelProxy family.
+ // |host| must not be null.
+ static std::unique_ptr<ChannelFactory> CreateServerFactory(
+ mojo::ScopedMessagePipeHandle handle,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& proxy_task_runner);
+
+ static std::unique_ptr<ChannelFactory> CreateClientFactory(
+ mojo::ScopedMessagePipeHandle handle,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& proxy_task_runner);
+
+ ~ChannelMojo() override;
+
+ // Channel implementation
+ bool Connect() override;
+ void Pause() override;
+ void Unpause(bool flush) override;
+ void Flush() override;
+ void Close() override;
+ bool Send(Message* message) override;
+ Channel::AssociatedInterfaceSupport* GetAssociatedInterfaceSupport() override;
+
+ // These access protected API of IPC::Message, which has ChannelMojo
+ // as a friend class.
+ static MojoResult WriteToMessageAttachmentSet(
+ base::Optional<std::vector<mojo::native::SerializedHandlePtr>> handles,
+ Message* message);
+ static MojoResult ReadFromMessageAttachmentSet(
+ Message* message,
+ base::Optional<std::vector<mojo::native::SerializedHandlePtr>>* handles);
+
+ // MessagePipeReader::Delegate
+ void OnPeerPidReceived(int32_t peer_pid) override;
+ void OnMessageReceived(const Message& message) override;
+ void OnBrokenDataReceived() override;
+ void OnPipeError() override;
+ void OnAssociatedInterfaceRequest(
+ const std::string& name,
+ mojo::ScopedInterfaceEndpointHandle handle) override;
+
+ private:
+ ChannelMojo(
+ mojo::ScopedMessagePipeHandle handle,
+ Mode mode,
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& proxy_task_runner);
+
+ void ForwardMessageFromThreadSafePtr(mojo::Message message);
+ void ForwardMessageWithResponderFromThreadSafePtr(
+ mojo::Message message,
+ std::unique_ptr<mojo::MessageReceiver> responder);
+
+ // Channel::AssociatedInterfaceSupport:
+ std::unique_ptr<mojo::ThreadSafeForwarder<mojom::Channel>>
+ CreateThreadSafeChannel() override;
+ void AddGenericAssociatedInterface(
+ const std::string& name,
+ const GenericAssociatedInterfaceFactory& factory) override;
+ void GetGenericRemoteAssociatedInterface(
+ const std::string& name,
+ mojo::ScopedInterfaceEndpointHandle handle) override;
+
+ base::WeakPtr<ChannelMojo> weak_ptr_;
+
+ // A TaskRunner which runs tasks on the ChannelMojo's owning thread.
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ const mojo::MessagePipeHandle pipe_;
+ std::unique_ptr<MojoBootstrap> bootstrap_;
+ Listener* listener_;
+
+ std::unique_ptr<internal::MessagePipeReader> message_reader_;
+
+ base::Lock associated_interface_lock_;
+ std::map<std::string, GenericAssociatedInterfaceFactory>
+ associated_interfaces_;
+
+ base::WeakPtrFactory<ChannelMojo> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChannelMojo);
+};
+
+} // namespace IPC
+
+#endif // IPC_IPC_CHANNEL_MOJO_H_
diff --git a/ipc/ipc_channel_mojo_unittest.cc b/ipc/ipc_channel_mojo_unittest.cc
new file mode 100644
index 0000000000..96b45591eb
--- /dev/null
+++ b/ipc/ipc_channel_mojo_unittest.cc
@@ -0,0 +1,1791 @@
+// 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 "ipc/ipc_channel_mojo.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <utility>
+
+#include "base/base_paths.h"
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/containers/queue.h"
+#include "base/files/file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/platform_shared_memory_region.h"
+#include "base/memory/shared_memory.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/message_loop/message_loop.h"
+#include "base/optional.h"
+#include "base/path_service.h"
+#include "base/pickle.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/test_io_thread.h"
+#include "base/test/test_shared_memory_util.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_utils.h"
+#include "ipc/ipc_mojo_handle_attachment.h"
+#include "ipc/ipc_mojo_message_helper.h"
+#include "ipc/ipc_mojo_param_traits.h"
+#include "ipc/ipc_sync_channel.h"
+#include "ipc/ipc_sync_message.h"
+#include "ipc/ipc_test.mojom.h"
+#include "ipc/ipc_test_base.h"
+#include "ipc/ipc_test_channel_listener.h"
+#include "mojo/core/embedder/embedder.h"
+#include "mojo/public/cpp/bindings/lib/validation_errors.h"
+#include "mojo/public/cpp/system/wait.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include "base/file_descriptor_posix.h"
+#include "ipc/ipc_platform_file_attachment_posix.h"
+#endif
+
+namespace {
+
+void SendString(IPC::Sender* sender, const std::string& str) {
+ IPC::Message* message = new IPC::Message(0, 2, IPC::Message::PRIORITY_NORMAL);
+ message->WriteString(str);
+ ASSERT_TRUE(sender->Send(message));
+}
+
+void SendValue(IPC::Sender* sender, int32_t value) {
+ IPC::Message* message = new IPC::Message(0, 2, IPC::Message::PRIORITY_NORMAL);
+ message->WriteInt(value);
+ ASSERT_TRUE(sender->Send(message));
+}
+
+class ListenerThatExpectsOK : public IPC::Listener {
+ public:
+ explicit ListenerThatExpectsOK(base::OnceClosure quit_closure)
+ : received_ok_(false), quit_closure_(std::move(quit_closure)) {}
+
+ ~ListenerThatExpectsOK() override = default;
+
+ bool OnMessageReceived(const IPC::Message& message) override {
+ base::PickleIterator iter(message);
+ std::string should_be_ok;
+ EXPECT_TRUE(iter.ReadString(&should_be_ok));
+ EXPECT_EQ(should_be_ok, "OK");
+ received_ok_ = true;
+ std::move(quit_closure_).Run();
+ return true;
+ }
+
+ void OnChannelError() override {
+ // The connection should be healthy while the listener is waiting
+ // message. An error can occur after that because the peer
+ // process dies.
+ CHECK(received_ok_);
+ }
+
+ static void SendOK(IPC::Sender* sender) { SendString(sender, "OK"); }
+
+ private:
+ bool received_ok_;
+ base::OnceClosure quit_closure_;
+};
+
+class TestListenerBase : public IPC::Listener {
+ public:
+ explicit TestListenerBase(base::OnceClosure quit_closure)
+ : quit_closure_(std::move(quit_closure)) {}
+
+ ~TestListenerBase() override = default;
+ void OnChannelError() override { RunQuitClosure(); }
+
+ void set_sender(IPC::Sender* sender) { sender_ = sender; }
+ IPC::Sender* sender() const { return sender_; }
+ void RunQuitClosure() {
+ if (quit_closure_)
+ std::move(quit_closure_).Run();
+ }
+
+ private:
+ IPC::Sender* sender_ = nullptr;
+ base::OnceClosure quit_closure_;
+};
+
+using IPCChannelMojoTest = IPCChannelMojoTestBase;
+
+class TestChannelListenerWithExtraExpectations
+ : public IPC::TestChannelListener {
+ public:
+ TestChannelListenerWithExtraExpectations() : is_connected_called_(false) {}
+
+ void OnChannelConnected(int32_t peer_pid) override {
+ IPC::TestChannelListener::OnChannelConnected(peer_pid);
+ EXPECT_TRUE(base::kNullProcessId != peer_pid);
+ is_connected_called_ = true;
+ }
+
+ bool is_connected_called() const { return is_connected_called_; }
+
+ private:
+ bool is_connected_called_;
+};
+
+TEST_F(IPCChannelMojoTest, ConnectedFromClient) {
+ Init("IPCChannelMojoTestClient");
+
+ // Set up IPC channel and start client.
+ TestChannelListenerWithExtraExpectations listener;
+ CreateChannel(&listener);
+ listener.Init(sender());
+ ASSERT_TRUE(ConnectChannel());
+
+ IPC::TestChannelListener::SendOneMessage(sender(), "hello from parent");
+
+ base::RunLoop().Run();
+
+ channel()->Close();
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ EXPECT_TRUE(listener.is_connected_called());
+ EXPECT_TRUE(listener.HasSentAll());
+
+ DestroyChannel();
+}
+
+// A long running process that connects to us
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(IPCChannelMojoTestClient) {
+ TestChannelListenerWithExtraExpectations listener;
+ Connect(&listener);
+ listener.Init(channel());
+
+ IPC::TestChannelListener::SendOneMessage(channel(), "hello from child");
+ base::RunLoop().Run();
+ EXPECT_TRUE(listener.is_connected_called());
+ EXPECT_TRUE(listener.HasSentAll());
+
+ Close();
+}
+
+class ListenerExpectingErrors : public TestListenerBase {
+ public:
+ ListenerExpectingErrors(base::OnceClosure quit_closure)
+ : TestListenerBase(std::move(quit_closure)), has_error_(false) {}
+
+ bool OnMessageReceived(const IPC::Message& message) override { return true; }
+
+ void OnChannelError() override {
+ has_error_ = true;
+ TestListenerBase::OnChannelError();
+ }
+
+ bool has_error() const { return has_error_; }
+
+ private:
+ bool has_error_;
+};
+
+class ListenerThatQuits : public IPC::Listener {
+ public:
+ explicit ListenerThatQuits(base::OnceClosure quit_closure)
+ : quit_closure_(std::move(quit_closure)) {}
+
+ bool OnMessageReceived(const IPC::Message& message) override { return true; }
+
+ void OnChannelConnected(int32_t peer_pid) override {
+ std::move(quit_closure_).Run();
+ }
+
+ private:
+ base::OnceClosure quit_closure_;
+};
+
+// A long running process that connects to us.
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(IPCChannelMojoErraticTestClient) {
+ base::RunLoop run_loop;
+ ListenerThatQuits listener(run_loop.QuitClosure());
+ Connect(&listener);
+
+ run_loop.Run();
+
+ Close();
+}
+
+TEST_F(IPCChannelMojoTest, SendFailWithPendingMessages) {
+ Init("IPCChannelMojoErraticTestClient");
+
+ // Set up IPC channel and start client.
+ base::RunLoop run_loop;
+ ListenerExpectingErrors listener(run_loop.QuitClosure());
+ CreateChannel(&listener);
+ ASSERT_TRUE(ConnectChannel());
+
+ // This matches a value in mojo/edk/system/constants.h
+ const int kMaxMessageNumBytes = 4 * 1024 * 1024;
+ std::string overly_large_data(kMaxMessageNumBytes, '*');
+ // This messages are queued as pending.
+ for (size_t i = 0; i < 10; ++i) {
+ IPC::TestChannelListener::SendOneMessage(sender(),
+ overly_large_data.c_str());
+ }
+
+ run_loop.Run();
+
+ channel()->Close();
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ EXPECT_TRUE(listener.has_error());
+
+ DestroyChannel();
+}
+
+class ListenerThatBindsATestStructPasser : public IPC::Listener,
+ public IPC::mojom::TestStructPasser {
+ public:
+ ListenerThatBindsATestStructPasser() : binding_(this) {}
+
+ bool OnMessageReceived(const IPC::Message& message) override { return true; }
+
+ void OnChannelConnected(int32_t peer_pid) override {}
+
+ void OnChannelError() override { NOTREACHED(); }
+
+ void OnAssociatedInterfaceRequest(
+ const std::string& interface_name,
+ mojo::ScopedInterfaceEndpointHandle handle) override {
+ CHECK_EQ(interface_name, IPC::mojom::TestStructPasser::Name_);
+ binding_.Bind(
+ IPC::mojom::TestStructPasserAssociatedRequest(std::move(handle)));
+ }
+
+ private:
+ // IPC::mojom::TestStructPasser:
+ void Pass(IPC::mojom::TestStructPtr) override { NOTREACHED(); }
+
+ mojo::AssociatedBinding<IPC::mojom::TestStructPasser> binding_;
+};
+
+class ListenerThatExpectsNoError : public IPC::Listener {
+ public:
+ ListenerThatExpectsNoError(base::OnceClosure connect_closure,
+ base::OnceClosure quit_closure)
+ : connect_closure_(std::move(connect_closure)),
+ quit_closure_(std::move(quit_closure)) {}
+
+ bool OnMessageReceived(const IPC::Message& message) override {
+ base::PickleIterator iter(message);
+ std::string should_be_ok;
+ EXPECT_TRUE(iter.ReadString(&should_be_ok));
+ EXPECT_EQ(should_be_ok, "OK");
+ std::move(quit_closure_).Run();
+ return true;
+ }
+
+ void OnChannelConnected(int32_t peer_pid) override {
+ std::move(connect_closure_).Run();
+ }
+
+ void OnChannelError() override { NOTREACHED(); }
+
+ private:
+ base::OnceClosure connect_closure_;
+ base::OnceClosure quit_closure_;
+};
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(
+ IPCChannelMojoNoImplicitChanelClosureClient) {
+ base::RunLoop wait_to_connect_loop;
+ base::RunLoop wait_to_quit_loop;
+ ListenerThatExpectsNoError listener(wait_to_connect_loop.QuitClosure(),
+ wait_to_quit_loop.QuitClosure());
+ Connect(&listener);
+ wait_to_connect_loop.Run();
+
+ IPC::mojom::TestStructPasserAssociatedPtr passer;
+ channel()->GetAssociatedInterfaceSupport()->GetRemoteAssociatedInterface(
+ &passer);
+
+ // This avoids hitting DCHECKs in the serialization code meant to stop us from
+ // making such "mistakes" as the one we're about to make below.
+ mojo::internal::SerializationWarningObserverForTesting suppress_those_dchecks;
+
+ // Send an invalid message. The TestStruct argument is not allowed to be null.
+ // This will elicit a validation error in the parent process, but should not
+ // actually disconnect the channel.
+ passer->Pass(nullptr);
+
+ // Wait until the parent says it's OK to quit, so it has time to verify its
+ // expected behavior.
+ wait_to_quit_loop.Run();
+
+ Close();
+}
+
+TEST_F(IPCChannelMojoTest, NoImplicitChannelClosure) {
+ // Verifies that OnChannelError is not invoked due to conditions other than
+ // peer closure (e.g. a malformed inbound message). Instead we should always
+ // be able to handle validation errors via Mojo bad message reporting.
+
+ // NOTE: We can't create a RunLoop before Init() is called, but we have to set
+ // the default ProcessErrorCallback (which we want to reference the RunLoop)
+ // before Init() launches a child process. Hence the base::Optional here.
+ base::Optional<base::RunLoop> wait_for_error_loop;
+ bool process_error_received = false;
+ mojo::core::SetDefaultProcessErrorCallback(
+ base::BindLambdaForTesting([&](const std::string&) {
+ process_error_received = true;
+ wait_for_error_loop->Quit();
+ }));
+
+ Init("IPCChannelMojoNoImplicitChanelClosureClient");
+
+ wait_for_error_loop.emplace();
+ ListenerThatBindsATestStructPasser listener;
+ CreateChannel(&listener);
+ ASSERT_TRUE(ConnectChannel());
+
+ wait_for_error_loop->Run();
+ EXPECT_TRUE(process_error_received);
+
+ // Tell the child it can quit and wait for it to shut down.
+ ListenerThatExpectsOK::SendOK(channel());
+ EXPECT_TRUE(WaitForClientShutdown());
+ DestroyChannel();
+}
+
+struct TestingMessagePipe {
+ TestingMessagePipe() {
+ EXPECT_EQ(MOJO_RESULT_OK, mojo::CreateMessagePipe(nullptr, &self, &peer));
+ }
+
+ mojo::ScopedMessagePipeHandle self;
+ mojo::ScopedMessagePipeHandle peer;
+};
+
+class HandleSendingHelper {
+ public:
+ static std::string GetSendingFileContent() { return "Hello"; }
+
+ static void WritePipe(IPC::Message* message, TestingMessagePipe* pipe) {
+ std::string content = HandleSendingHelper::GetSendingFileContent();
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mojo::WriteMessageRaw(pipe->self.get(), &content[0],
+ static_cast<uint32_t>(content.size()),
+ nullptr, 0, 0));
+ EXPECT_TRUE(IPC::MojoMessageHelper::WriteMessagePipeTo(
+ message, std::move(pipe->peer)));
+ }
+
+ static void WritePipeThenSend(IPC::Sender* sender, TestingMessagePipe* pipe) {
+ IPC::Message* message =
+ new IPC::Message(0, 2, IPC::Message::PRIORITY_NORMAL);
+ WritePipe(message, pipe);
+ ASSERT_TRUE(sender->Send(message));
+ }
+
+ static void ReadReceivedPipe(const IPC::Message& message,
+ base::PickleIterator* iter) {
+ mojo::ScopedMessagePipeHandle pipe;
+ EXPECT_TRUE(
+ IPC::MojoMessageHelper::ReadMessagePipeFrom(&message, iter, &pipe));
+ std::vector<uint8_t> content;
+
+ ASSERT_EQ(MOJO_RESULT_OK,
+ mojo::Wait(pipe.get(), MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ mojo::ReadMessageRaw(pipe.get(), &content, nullptr, 0));
+ EXPECT_EQ(std::string(content.begin(), content.end()),
+ GetSendingFileContent());
+ }
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ static base::FilePath GetSendingFilePath(const base::FilePath& dir_path) {
+ return dir_path.Append("ListenerThatExpectsFile.txt");
+ }
+
+ static void WriteFile(IPC::Message* message, base::File& file) {
+ std::string content = GetSendingFileContent();
+ file.WriteAtCurrentPos(content.data(), content.size());
+ file.Flush();
+ message->WriteAttachment(new IPC::internal::PlatformFileAttachment(
+ base::ScopedFD(file.TakePlatformFile())));
+ }
+
+ static void WriteFileThenSend(IPC::Sender* sender, base::File& file) {
+ IPC::Message* message =
+ new IPC::Message(0, 2, IPC::Message::PRIORITY_NORMAL);
+ WriteFile(message, file);
+ ASSERT_TRUE(sender->Send(message));
+ }
+
+ static void WriteFileAndPipeThenSend(IPC::Sender* sender,
+ base::File& file,
+ TestingMessagePipe* pipe) {
+ IPC::Message* message =
+ new IPC::Message(0, 2, IPC::Message::PRIORITY_NORMAL);
+ WriteFile(message, file);
+ WritePipe(message, pipe);
+ ASSERT_TRUE(sender->Send(message));
+ }
+
+ static void ReadReceivedFile(const IPC::Message& message,
+ base::PickleIterator* iter) {
+ scoped_refptr<base::Pickle::Attachment> attachment;
+ EXPECT_TRUE(message.ReadAttachment(iter, &attachment));
+ EXPECT_EQ(
+ IPC::MessageAttachment::Type::PLATFORM_FILE,
+ static_cast<IPC::MessageAttachment*>(attachment.get())->GetType());
+ base::File file(
+ static_cast<IPC::internal::PlatformFileAttachment*>(attachment.get())
+ ->TakePlatformFile());
+ std::string content(GetSendingFileContent().size(), ' ');
+ file.Read(0, &content[0], content.size());
+ EXPECT_EQ(content, GetSendingFileContent());
+ }
+#endif
+};
+
+class ListenerThatExpectsMessagePipe : public TestListenerBase {
+ public:
+ ListenerThatExpectsMessagePipe(base::OnceClosure quit_closure)
+ : TestListenerBase(std::move(quit_closure)) {}
+
+ ~ListenerThatExpectsMessagePipe() override = default;
+
+ bool OnMessageReceived(const IPC::Message& message) override {
+ base::PickleIterator iter(message);
+ HandleSendingHelper::ReadReceivedPipe(message, &iter);
+ ListenerThatExpectsOK::SendOK(sender());
+ return true;
+ }
+};
+
+TEST_F(IPCChannelMojoTest, SendMessagePipe) {
+ Init("IPCChannelMojoTestSendMessagePipeClient");
+
+ base::RunLoop run_loop;
+ ListenerThatExpectsOK listener(run_loop.QuitClosure());
+ CreateChannel(&listener);
+ ASSERT_TRUE(ConnectChannel());
+
+ TestingMessagePipe pipe;
+ HandleSendingHelper::WritePipeThenSend(channel(), &pipe);
+
+ run_loop.Run();
+ channel()->Close();
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ DestroyChannel();
+}
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(IPCChannelMojoTestSendMessagePipeClient) {
+ base::RunLoop run_loop;
+ ListenerThatExpectsMessagePipe listener(run_loop.QuitClosure());
+ Connect(&listener);
+ listener.set_sender(channel());
+
+ run_loop.Run();
+
+ Close();
+}
+
+void ReadOK(mojo::MessagePipeHandle pipe) {
+ std::vector<uint8_t> should_be_ok;
+ CHECK_EQ(MOJO_RESULT_OK, mojo::Wait(pipe, MOJO_HANDLE_SIGNAL_READABLE));
+ CHECK_EQ(MOJO_RESULT_OK,
+ mojo::ReadMessageRaw(pipe, &should_be_ok, nullptr, 0));
+ EXPECT_EQ("OK", std::string(should_be_ok.begin(), should_be_ok.end()));
+}
+
+void WriteOK(mojo::MessagePipeHandle pipe) {
+ std::string ok("OK");
+ CHECK_EQ(MOJO_RESULT_OK,
+ mojo::WriteMessageRaw(pipe, &ok[0], static_cast<uint32_t>(ok.size()),
+ nullptr, 0, 0));
+}
+
+class ListenerThatExpectsMessagePipeUsingParamTrait : public TestListenerBase {
+ public:
+ explicit ListenerThatExpectsMessagePipeUsingParamTrait(
+ base::OnceClosure quit_closure,
+ bool receiving_valid)
+ : TestListenerBase(std::move(quit_closure)),
+ receiving_valid_(receiving_valid) {}
+
+ ~ListenerThatExpectsMessagePipeUsingParamTrait() override = default;
+
+ bool OnMessageReceived(const IPC::Message& message) override {
+ base::PickleIterator iter(message);
+ mojo::MessagePipeHandle handle;
+ EXPECT_TRUE(IPC::ParamTraits<mojo::MessagePipeHandle>::Read(&message, &iter,
+ &handle));
+ EXPECT_EQ(handle.is_valid(), receiving_valid_);
+ if (receiving_valid_) {
+ ReadOK(handle);
+ MojoClose(handle.value());
+ }
+
+ ListenerThatExpectsOK::SendOK(sender());
+ return true;
+ }
+
+ private:
+ bool receiving_valid_;
+};
+
+class ParamTraitMessagePipeClient : public IpcChannelMojoTestClient {
+ public:
+ void RunTest(bool receiving_valid_handle) {
+ base::RunLoop run_loop;
+ ListenerThatExpectsMessagePipeUsingParamTrait listener(
+ run_loop.QuitClosure(), receiving_valid_handle);
+ Connect(&listener);
+ listener.set_sender(channel());
+
+ run_loop.Run();
+
+ Close();
+ }
+};
+
+TEST_F(IPCChannelMojoTest, ParamTraitValidMessagePipe) {
+ Init("ParamTraitValidMessagePipeClient");
+
+ base::RunLoop run_loop;
+ ListenerThatExpectsOK listener(run_loop.QuitClosure());
+ CreateChannel(&listener);
+ ASSERT_TRUE(ConnectChannel());
+
+ TestingMessagePipe pipe;
+
+ std::unique_ptr<IPC::Message> message(new IPC::Message());
+ IPC::ParamTraits<mojo::MessagePipeHandle>::Write(message.get(),
+ pipe.peer.release());
+ WriteOK(pipe.self.get());
+
+ channel()->Send(message.release());
+ run_loop.Run();
+ channel()->Close();
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ DestroyChannel();
+}
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT_WITH_CUSTOM_FIXTURE(
+ ParamTraitValidMessagePipeClient,
+ ParamTraitMessagePipeClient) {
+ RunTest(true);
+}
+
+TEST_F(IPCChannelMojoTest, ParamTraitInvalidMessagePipe) {
+ Init("ParamTraitInvalidMessagePipeClient");
+
+ base::RunLoop run_loop;
+ ListenerThatExpectsOK listener(run_loop.QuitClosure());
+ CreateChannel(&listener);
+ ASSERT_TRUE(ConnectChannel());
+
+ mojo::MessagePipeHandle invalid_handle;
+ std::unique_ptr<IPC::Message> message(new IPC::Message());
+ IPC::ParamTraits<mojo::MessagePipeHandle>::Write(message.get(),
+ invalid_handle);
+
+ channel()->Send(message.release());
+ run_loop.Run();
+ channel()->Close();
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ DestroyChannel();
+}
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT_WITH_CUSTOM_FIXTURE(
+ ParamTraitInvalidMessagePipeClient,
+ ParamTraitMessagePipeClient) {
+ RunTest(false);
+}
+
+TEST_F(IPCChannelMojoTest, SendFailAfterClose) {
+ Init("IPCChannelMojoTestSendOkClient");
+
+ base::RunLoop run_loop;
+ ListenerThatExpectsOK listener(run_loop.QuitClosure());
+ CreateChannel(&listener);
+ ASSERT_TRUE(ConnectChannel());
+
+ run_loop.Run();
+ channel()->Close();
+ ASSERT_FALSE(channel()->Send(new IPC::Message()));
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ DestroyChannel();
+}
+
+class ListenerSendingOneOk : public TestListenerBase {
+ public:
+ ListenerSendingOneOk(base::OnceClosure quit_closure)
+ : TestListenerBase(std::move(quit_closure)) {}
+
+ bool OnMessageReceived(const IPC::Message& message) override { return true; }
+
+ void OnChannelConnected(int32_t peer_pid) override {
+ ListenerThatExpectsOK::SendOK(sender());
+ RunQuitClosure();
+ }
+};
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(IPCChannelMojoTestSendOkClient) {
+ base::RunLoop run_loop;
+ ListenerSendingOneOk listener(run_loop.QuitClosure());
+ Connect(&listener);
+ listener.set_sender(channel());
+
+ run_loop.Run();
+
+ Close();
+}
+
+class ListenerWithSimpleAssociatedInterface
+ : public IPC::Listener,
+ public IPC::mojom::SimpleTestDriver {
+ public:
+ static const int kNumMessages;
+
+ explicit ListenerWithSimpleAssociatedInterface(base::OnceClosure quit_closure)
+ : quit_closure_(std::move(quit_closure)), binding_(this) {}
+
+ ~ListenerWithSimpleAssociatedInterface() override = default;
+
+ bool OnMessageReceived(const IPC::Message& message) override {
+ base::PickleIterator iter(message);
+ int32_t should_be_expected;
+ EXPECT_TRUE(iter.ReadInt(&should_be_expected));
+ EXPECT_EQ(should_be_expected, next_expected_value_);
+ num_messages_received_++;
+ return true;
+ }
+
+ void OnChannelError() override { CHECK(!quit_closure_); }
+
+ void RegisterInterfaceFactory(IPC::Channel* channel) {
+ channel->GetAssociatedInterfaceSupport()->AddAssociatedInterface(
+ base::BindRepeating(&ListenerWithSimpleAssociatedInterface::BindRequest,
+ base::Unretained(this)));
+ }
+
+ private:
+ // IPC::mojom::SimpleTestDriver:
+ void ExpectValue(int32_t value) override {
+ next_expected_value_ = value;
+ }
+
+ void GetExpectedValue(GetExpectedValueCallback callback) override {
+ NOTREACHED();
+ }
+
+ void RequestValue(RequestValueCallback callback) override { NOTREACHED(); }
+
+ void RequestQuit(RequestQuitCallback callback) override {
+ EXPECT_EQ(kNumMessages, num_messages_received_);
+ std::move(callback).Run();
+ std::move(quit_closure_).Run();
+ }
+
+ void BindRequest(IPC::mojom::SimpleTestDriverAssociatedRequest request) {
+ DCHECK(!binding_.is_bound());
+ binding_.Bind(std::move(request));
+ }
+
+ int32_t next_expected_value_ = 0;
+ int num_messages_received_ = 0;
+ base::OnceClosure quit_closure_;
+
+ mojo::AssociatedBinding<IPC::mojom::SimpleTestDriver> binding_;
+};
+
+const int ListenerWithSimpleAssociatedInterface::kNumMessages = 1000;
+
+class ListenerSendingAssociatedMessages : public IPC::Listener {
+ public:
+ explicit ListenerSendingAssociatedMessages(base::OnceClosure quit_closure)
+ : quit_closure_(std::move(quit_closure)) {}
+
+ bool OnMessageReceived(const IPC::Message& message) override { return true; }
+
+ void OnChannelConnected(int32_t peer_pid) override {
+ DCHECK(channel_);
+ channel_->GetAssociatedInterfaceSupport()->GetRemoteAssociatedInterface(
+ &driver_);
+
+ // Send a bunch of interleaved messages, alternating between the associated
+ // interface and a legacy IPC::Message.
+ for (int i = 0; i < ListenerWithSimpleAssociatedInterface::kNumMessages;
+ ++i) {
+ driver_->ExpectValue(i);
+ SendValue(channel_, i);
+ }
+ driver_->RequestQuit(base::BindOnce(
+ &ListenerSendingAssociatedMessages::OnQuitAck, base::Unretained(this)));
+ }
+
+ void set_channel(IPC::Channel* channel) { channel_ = channel; }
+
+ private:
+ void OnQuitAck() { std::move(quit_closure_).Run(); }
+
+ IPC::Channel* channel_ = nullptr;
+ IPC::mojom::SimpleTestDriverAssociatedPtr driver_;
+ base::OnceClosure quit_closure_;
+};
+
+TEST_F(IPCChannelMojoTest, SimpleAssociatedInterface) {
+ Init("SimpleAssociatedInterfaceClient");
+
+ base::RunLoop run_loop;
+ ListenerWithSimpleAssociatedInterface listener(run_loop.QuitClosure());
+ CreateChannel(&listener);
+ ASSERT_TRUE(ConnectChannel());
+
+ listener.RegisterInterfaceFactory(channel());
+
+ run_loop.Run();
+ channel()->Close();
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ DestroyChannel();
+}
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(SimpleAssociatedInterfaceClient) {
+ base::RunLoop run_loop;
+ ListenerSendingAssociatedMessages listener(run_loop.QuitClosure());
+ Connect(&listener);
+ listener.set_channel(channel());
+
+ run_loop.Run();
+
+ Close();
+}
+
+class ChannelProxyRunner {
+ public:
+ ChannelProxyRunner(mojo::ScopedMessagePipeHandle handle,
+ bool for_server)
+ : for_server_(for_server),
+ handle_(std::move(handle)),
+ io_thread_("ChannelProxyRunner IO thread"),
+ never_signaled_(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED) {
+ }
+
+ void CreateProxy(IPC::Listener* listener) {
+ io_thread_.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
+ proxy_ = IPC::SyncChannel::Create(listener, io_thread_.task_runner(),
+ base::ThreadTaskRunnerHandle::Get(),
+ &never_signaled_);
+ }
+
+ void RunProxy() {
+ std::unique_ptr<IPC::ChannelFactory> factory;
+ if (for_server_) {
+ factory = IPC::ChannelMojo::CreateServerFactory(
+ std::move(handle_), io_thread_.task_runner(),
+ base::ThreadTaskRunnerHandle::Get());
+ } else {
+ factory = IPC::ChannelMojo::CreateClientFactory(
+ std::move(handle_), io_thread_.task_runner(),
+ base::ThreadTaskRunnerHandle::Get());
+ }
+ proxy_->Init(std::move(factory), true);
+ }
+
+ IPC::ChannelProxy* proxy() { return proxy_.get(); }
+
+ private:
+ const bool for_server_;
+
+ mojo::ScopedMessagePipeHandle handle_;
+ base::Thread io_thread_;
+ base::WaitableEvent never_signaled_;
+ std::unique_ptr<IPC::ChannelProxy> proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChannelProxyRunner);
+};
+
+class IPCChannelProxyMojoTest : public IPCChannelMojoTestBase {
+ public:
+ void Init(const std::string& client_name) {
+ IPCChannelMojoTestBase::Init(client_name);
+ runner_.reset(new ChannelProxyRunner(TakeHandle(), true));
+ }
+ void CreateProxy(IPC::Listener* listener) { runner_->CreateProxy(listener); }
+ void RunProxy() {
+ runner_->RunProxy();
+ }
+ void DestroyProxy() {
+ runner_.reset();
+ base::RunLoop().RunUntilIdle();
+ }
+
+ IPC::ChannelProxy* proxy() { return runner_->proxy(); }
+
+ private:
+ std::unique_ptr<ChannelProxyRunner> runner_;
+};
+
+class ListenerWithSimpleProxyAssociatedInterface
+ : public IPC::Listener,
+ public IPC::mojom::SimpleTestDriver {
+ public:
+ static const int kNumMessages;
+
+ explicit ListenerWithSimpleProxyAssociatedInterface(
+ base::OnceClosure quit_closure)
+ : quit_closure_(std::move(quit_closure)), binding_(this) {}
+
+ ~ListenerWithSimpleProxyAssociatedInterface() override = default;
+
+ bool OnMessageReceived(const IPC::Message& message) override {
+ base::PickleIterator iter(message);
+ int32_t should_be_expected;
+ EXPECT_TRUE(iter.ReadInt(&should_be_expected));
+ EXPECT_EQ(should_be_expected, next_expected_value_);
+ num_messages_received_++;
+ return true;
+ }
+
+ void OnChannelError() override { CHECK(!quit_closure_); }
+
+ void OnAssociatedInterfaceRequest(
+ const std::string& interface_name,
+ mojo::ScopedInterfaceEndpointHandle handle) override {
+ DCHECK_EQ(interface_name, IPC::mojom::SimpleTestDriver::Name_);
+ binding_.Bind(
+ IPC::mojom::SimpleTestDriverAssociatedRequest(std::move(handle)));
+ }
+
+ bool received_all_messages() const {
+ return num_messages_received_ == kNumMessages && !quit_closure_;
+ }
+
+ private:
+ // IPC::mojom::SimpleTestDriver:
+ void ExpectValue(int32_t value) override {
+ next_expected_value_ = value;
+ }
+
+ void GetExpectedValue(GetExpectedValueCallback callback) override {
+ std::move(callback).Run(next_expected_value_);
+ }
+
+ void RequestValue(RequestValueCallback callback) override { NOTREACHED(); }
+
+ void RequestQuit(RequestQuitCallback callback) override {
+ std::move(callback).Run();
+ binding_.Close();
+ std::move(quit_closure_).Run();
+ }
+
+ void BindRequest(IPC::mojom::SimpleTestDriverAssociatedRequest request) {
+ DCHECK(!binding_.is_bound());
+ binding_.Bind(std::move(request));
+ }
+
+ int32_t next_expected_value_ = 0;
+ int num_messages_received_ = 0;
+ base::OnceClosure quit_closure_;
+
+ mojo::AssociatedBinding<IPC::mojom::SimpleTestDriver> binding_;
+};
+
+const int ListenerWithSimpleProxyAssociatedInterface::kNumMessages = 1000;
+
+TEST_F(IPCChannelProxyMojoTest, ProxyThreadAssociatedInterface) {
+ Init("ProxyThreadAssociatedInterfaceClient");
+
+ base::RunLoop run_loop;
+ ListenerWithSimpleProxyAssociatedInterface listener(run_loop.QuitClosure());
+ CreateProxy(&listener);
+ RunProxy();
+
+ run_loop.Run();
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ EXPECT_TRUE(listener.received_all_messages());
+
+ DestroyProxy();
+}
+
+class ChannelProxyClient {
+ public:
+ void Init(mojo::ScopedMessagePipeHandle handle) {
+ runner_.reset(new ChannelProxyRunner(std::move(handle), false));
+ }
+
+ void CreateProxy(IPC::Listener* listener) { runner_->CreateProxy(listener); }
+
+ void RunProxy() { runner_->RunProxy(); }
+
+ void DestroyProxy() {
+ runner_.reset();
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void RequestQuitAndWaitForAck(IPC::mojom::SimpleTestDriver* driver) {
+ base::RunLoop loop;
+ driver->RequestQuit(loop.QuitClosure());
+ loop.Run();
+ }
+
+ IPC::ChannelProxy* proxy() { return runner_->proxy(); }
+
+ private:
+ base::MessageLoop message_loop_;
+ std::unique_ptr<ChannelProxyRunner> runner_;
+};
+
+class DummyListener : public IPC::Listener {
+ public:
+ // IPC::Listener
+ bool OnMessageReceived(const IPC::Message& message) override { return true; }
+};
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT_WITH_CUSTOM_FIXTURE(
+ ProxyThreadAssociatedInterfaceClient,
+ ChannelProxyClient) {
+ DummyListener listener;
+ CreateProxy(&listener);
+ RunProxy();
+
+ // Send a bunch of interleaved messages, alternating between the associated
+ // interface and a legacy IPC::Message.
+ IPC::mojom::SimpleTestDriverAssociatedPtr driver;
+ proxy()->GetRemoteAssociatedInterface(&driver);
+ for (int i = 0; i < ListenerWithSimpleProxyAssociatedInterface::kNumMessages;
+ ++i) {
+ driver->ExpectValue(i);
+ SendValue(proxy(), i);
+ }
+ base::RunLoop run_loop;
+ driver->RequestQuit(run_loop.QuitClosure());
+ run_loop.Run();
+
+ DestroyProxy();
+}
+
+class ListenerWithIndirectProxyAssociatedInterface
+ : public IPC::Listener,
+ public IPC::mojom::IndirectTestDriver,
+ public IPC::mojom::PingReceiver {
+ public:
+ ListenerWithIndirectProxyAssociatedInterface()
+ : driver_binding_(this), ping_receiver_binding_(this) {}
+ ~ListenerWithIndirectProxyAssociatedInterface() override = default;
+
+ // IPC::Listener:
+ bool OnMessageReceived(const IPC::Message& message) override { return true; }
+
+ void OnAssociatedInterfaceRequest(
+ const std::string& interface_name,
+ mojo::ScopedInterfaceEndpointHandle handle) override {
+ DCHECK(!driver_binding_.is_bound());
+ DCHECK_EQ(interface_name, IPC::mojom::IndirectTestDriver::Name_);
+ driver_binding_.Bind(
+ IPC::mojom::IndirectTestDriverAssociatedRequest(std::move(handle)));
+ }
+
+ void set_ping_handler(const base::RepeatingClosure& handler) {
+ ping_handler_ = handler;
+ }
+
+ private:
+ // IPC::mojom::IndirectTestDriver:
+ void GetPingReceiver(
+ IPC::mojom::PingReceiverAssociatedRequest request) override {
+ ping_receiver_binding_.Bind(std::move(request));
+ }
+
+ // IPC::mojom::PingReceiver:
+ void Ping(PingCallback callback) override {
+ std::move(callback).Run();
+ ping_handler_.Run();
+ }
+
+ mojo::AssociatedBinding<IPC::mojom::IndirectTestDriver> driver_binding_;
+ mojo::AssociatedBinding<IPC::mojom::PingReceiver> ping_receiver_binding_;
+
+ base::RepeatingClosure ping_handler_;
+};
+
+TEST_F(IPCChannelProxyMojoTest, ProxyThreadAssociatedInterfaceIndirect) {
+ // Tests that we can pipeline interface requests and subsequent messages
+ // targeting proxy thread bindings, and the channel will still dispatch
+ // messages appropriately.
+
+ Init("ProxyThreadAssociatedInterfaceIndirectClient");
+
+ ListenerWithIndirectProxyAssociatedInterface listener;
+ CreateProxy(&listener);
+ RunProxy();
+
+ base::RunLoop loop;
+ listener.set_ping_handler(loop.QuitClosure());
+ loop.Run();
+
+ EXPECT_TRUE(WaitForClientShutdown());
+
+ DestroyProxy();
+}
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT_WITH_CUSTOM_FIXTURE(
+ ProxyThreadAssociatedInterfaceIndirectClient,
+ ChannelProxyClient) {
+ DummyListener listener;
+ CreateProxy(&listener);
+ RunProxy();
+
+ // Use an interface requested via another interface. On the remote end both
+ // interfaces are bound on the proxy thread. This ensures that the Ping
+ // message we send will still be dispatched properly even though the remote
+ // endpoint may not have been bound yet by the time the message is initially
+ // processed on the IO thread.
+ IPC::mojom::IndirectTestDriverAssociatedPtr driver;
+ IPC::mojom::PingReceiverAssociatedPtr ping_receiver;
+ proxy()->GetRemoteAssociatedInterface(&driver);
+ driver->GetPingReceiver(mojo::MakeRequest(&ping_receiver));
+
+ base::RunLoop loop;
+ ping_receiver->Ping(loop.QuitClosure());
+ loop.Run();
+
+ DestroyProxy();
+}
+
+class ListenerWithSyncAssociatedInterface
+ : public IPC::Listener,
+ public IPC::mojom::SimpleTestDriver {
+ public:
+ ListenerWithSyncAssociatedInterface() : binding_(this) {}
+ ~ListenerWithSyncAssociatedInterface() override = default;
+
+ void set_sync_sender(IPC::Sender* sync_sender) { sync_sender_ = sync_sender; }
+
+ void RunUntilQuitRequested() {
+ base::RunLoop loop;
+ quit_closure_ = loop.QuitClosure();
+ loop.Run();
+ }
+
+ void CloseBinding() { binding_.Close(); }
+
+ void set_response_value(int32_t response) {
+ response_value_ = response;
+ }
+
+ private:
+ // IPC::mojom::SimpleTestDriver:
+ void ExpectValue(int32_t value) override {
+ next_expected_value_ = value;
+ }
+
+ void GetExpectedValue(GetExpectedValueCallback callback) override {
+ std::move(callback).Run(next_expected_value_);
+ }
+
+ void RequestValue(RequestValueCallback callback) override {
+ std::move(callback).Run(response_value_);
+ }
+
+ void RequestQuit(RequestQuitCallback callback) override {
+ std::move(quit_closure_).Run();
+ std::move(callback).Run();
+ }
+
+ // IPC::Listener:
+ bool OnMessageReceived(const IPC::Message& message) override {
+ EXPECT_EQ(0u, message.type());
+ EXPECT_TRUE(message.is_sync());
+ EXPECT_TRUE(message.should_unblock());
+ std::unique_ptr<IPC::Message> reply(
+ IPC::SyncMessage::GenerateReply(&message));
+ reply->WriteInt(response_value_);
+ DCHECK(sync_sender_);
+ EXPECT_TRUE(sync_sender_->Send(reply.release()));
+ return true;
+ }
+
+ void OnAssociatedInterfaceRequest(
+ const std::string& interface_name,
+ mojo::ScopedInterfaceEndpointHandle handle) override {
+ DCHECK(!binding_.is_bound());
+ DCHECK_EQ(interface_name, IPC::mojom::SimpleTestDriver::Name_);
+ binding_.Bind(
+ IPC::mojom::SimpleTestDriverAssociatedRequest(std::move(handle)));
+ }
+
+ void BindRequest(IPC::mojom::SimpleTestDriverAssociatedRequest request) {
+ DCHECK(!binding_.is_bound());
+ binding_.Bind(std::move(request));
+ }
+
+ IPC::Sender* sync_sender_ = nullptr;
+ int32_t next_expected_value_ = 0;
+ int32_t response_value_ = 0;
+ base::OnceClosure quit_closure_;
+
+ mojo::AssociatedBinding<IPC::mojom::SimpleTestDriver> binding_;
+};
+
+class SyncReplyReader : public IPC::MessageReplyDeserializer {
+ public:
+ explicit SyncReplyReader(int32_t* storage) : storage_(storage) {}
+ ~SyncReplyReader() override = default;
+
+ private:
+ // IPC::MessageReplyDeserializer:
+ bool SerializeOutputParameters(const IPC::Message& message,
+ base::PickleIterator iter) override {
+ if (!iter.ReadInt(storage_))
+ return false;
+ return true;
+ }
+
+ int32_t* storage_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncReplyReader);
+};
+
+TEST_F(IPCChannelProxyMojoTest, SyncAssociatedInterface) {
+ Init("SyncAssociatedInterface");
+
+ ListenerWithSyncAssociatedInterface listener;
+ CreateProxy(&listener);
+ listener.set_sync_sender(proxy());
+ RunProxy();
+
+ // Run the client's simple sanity check to completion.
+ listener.RunUntilQuitRequested();
+
+ // Verify that we can send a sync IPC and service an incoming sync request
+ // while waiting on it
+ listener.set_response_value(42);
+ IPC::mojom::SimpleTestClientAssociatedPtr client;
+ proxy()->GetRemoteAssociatedInterface(&client);
+ int32_t received_value;
+ EXPECT_TRUE(client->RequestValue(&received_value));
+ EXPECT_EQ(42, received_value);
+
+ // Do it again. This time the client will send a classical sync IPC to us
+ // while we wait.
+ received_value = 0;
+ EXPECT_TRUE(client->RequestValue(&received_value));
+ EXPECT_EQ(42, received_value);
+
+ // Now make a classical sync IPC request to the client. It will send a
+ // sync associated interface message to us while we wait.
+ received_value = 0;
+ std::unique_ptr<IPC::SyncMessage> request(
+ new IPC::SyncMessage(0, 0, IPC::Message::PRIORITY_NORMAL,
+ new SyncReplyReader(&received_value)));
+ EXPECT_TRUE(proxy()->Send(request.release()));
+ EXPECT_EQ(42, received_value);
+
+ listener.CloseBinding();
+ EXPECT_TRUE(WaitForClientShutdown());
+
+ DestroyProxy();
+}
+
+class SimpleTestClientImpl : public IPC::mojom::SimpleTestClient,
+ public IPC::Listener {
+ public:
+ SimpleTestClientImpl() : binding_(this) {}
+
+ void set_driver(IPC::mojom::SimpleTestDriver* driver) { driver_ = driver; }
+ void set_sync_sender(IPC::Sender* sync_sender) { sync_sender_ = sync_sender; }
+
+ void WaitForValueRequest() {
+ run_loop_.reset(new base::RunLoop);
+ run_loop_->Run();
+ }
+
+ void UseSyncSenderForRequest(bool use_sync_sender) {
+ use_sync_sender_ = use_sync_sender;
+ }
+
+ private:
+ // IPC::mojom::SimpleTestClient:
+ void RequestValue(RequestValueCallback callback) override {
+ int32_t response = 0;
+ if (use_sync_sender_) {
+ std::unique_ptr<IPC::SyncMessage> reply(new IPC::SyncMessage(
+ 0, 0, IPC::Message::PRIORITY_NORMAL, new SyncReplyReader(&response)));
+ EXPECT_TRUE(sync_sender_->Send(reply.release()));
+ } else {
+ DCHECK(driver_);
+ EXPECT_TRUE(driver_->RequestValue(&response));
+ }
+
+ std::move(callback).Run(response);
+
+ DCHECK(run_loop_);
+ run_loop_->Quit();
+ }
+
+ // IPC::Listener:
+ bool OnMessageReceived(const IPC::Message& message) override {
+ int32_t response;
+ DCHECK(driver_);
+ EXPECT_TRUE(driver_->RequestValue(&response));
+ std::unique_ptr<IPC::Message> reply(
+ IPC::SyncMessage::GenerateReply(&message));
+ reply->WriteInt(response);
+ EXPECT_TRUE(sync_sender_->Send(reply.release()));
+
+ DCHECK(run_loop_);
+ run_loop_->Quit();
+ return true;
+ }
+
+ void OnAssociatedInterfaceRequest(
+ const std::string& interface_name,
+ mojo::ScopedInterfaceEndpointHandle handle) override {
+ DCHECK(!binding_.is_bound());
+ DCHECK_EQ(interface_name, IPC::mojom::SimpleTestClient::Name_);
+
+ binding_.Bind(
+ IPC::mojom::SimpleTestClientAssociatedRequest(std::move(handle)));
+ }
+
+ bool use_sync_sender_ = false;
+ mojo::AssociatedBinding<IPC::mojom::SimpleTestClient> binding_;
+ IPC::Sender* sync_sender_ = nullptr;
+ IPC::mojom::SimpleTestDriver* driver_ = nullptr;
+ std::unique_ptr<base::RunLoop> run_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleTestClientImpl);
+};
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT_WITH_CUSTOM_FIXTURE(SyncAssociatedInterface,
+ ChannelProxyClient) {
+ SimpleTestClientImpl client_impl;
+ CreateProxy(&client_impl);
+ client_impl.set_sync_sender(proxy());
+ RunProxy();
+
+ IPC::mojom::SimpleTestDriverAssociatedPtr driver;
+ proxy()->GetRemoteAssociatedInterface(&driver);
+ client_impl.set_driver(driver.get());
+
+ // Simple sync message sanity check.
+ driver->ExpectValue(42);
+ int32_t expected_value = 0;
+ EXPECT_TRUE(driver->GetExpectedValue(&expected_value));
+ EXPECT_EQ(42, expected_value);
+ RequestQuitAndWaitForAck(driver.get());
+
+ // Wait for the test driver to perform a sync call test with our own sync
+ // associated interface message nested inside.
+ client_impl.UseSyncSenderForRequest(false);
+ client_impl.WaitForValueRequest();
+
+ // Wait for the test driver to perform a sync call test with our own classical
+ // sync IPC nested inside.
+ client_impl.UseSyncSenderForRequest(true);
+ client_impl.WaitForValueRequest();
+
+ // Wait for the test driver to perform a classical sync IPC request, with our
+ // own sync associated interface message nested inside.
+ client_impl.UseSyncSenderForRequest(false);
+ client_impl.WaitForValueRequest();
+
+ DestroyProxy();
+}
+
+TEST_F(IPCChannelProxyMojoTest, Pause) {
+ // Ensures that pausing a channel elicits the expected behavior when sending
+ // messages, unpausing, sending more messages, and then manually flushing.
+ // Specifically a sequence like:
+ //
+ // Connect()
+ // Send(A)
+ // Pause()
+ // Send(B)
+ // Send(C)
+ // Unpause(false)
+ // Send(D)
+ // Send(E)
+ // Flush()
+ //
+ // must result in the other end receiving messages A, D, E, B, D; in that
+ // order.
+ //
+ // This behavior is required by some consumers of IPC::Channel, and it is not
+ // sufficient to leave this up to the consumer to implement since associated
+ // interface requests and messages also need to be queued according to the
+ // same policy.
+ Init("CreatePausedClient");
+
+ DummyListener listener;
+ CreateProxy(&listener);
+ RunProxy();
+
+ // This message must be sent immediately since the channel is unpaused.
+ SendValue(proxy(), 1);
+
+ proxy()->Pause();
+
+ // These messages must be queued internally since the channel is paused.
+ SendValue(proxy(), 2);
+ SendValue(proxy(), 3);
+
+ proxy()->Unpause(false /* flush */);
+
+ // These messages must be sent immediately since the channel is unpaused.
+ SendValue(proxy(), 4);
+ SendValue(proxy(), 5);
+
+ // Now we flush the previously queued messages.
+ proxy()->Flush();
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ DestroyProxy();
+}
+
+class ExpectValueSequenceListener : public IPC::Listener {
+ public:
+ ExpectValueSequenceListener(base::queue<int32_t>* expected_values,
+ base::OnceClosure quit_closure)
+ : expected_values_(expected_values),
+ quit_closure_(std::move(quit_closure)) {}
+ ~ExpectValueSequenceListener() override = default;
+
+ // IPC::Listener:
+ bool OnMessageReceived(const IPC::Message& message) override {
+ DCHECK(!expected_values_->empty());
+ base::PickleIterator iter(message);
+ int32_t should_be_expected;
+ EXPECT_TRUE(iter.ReadInt(&should_be_expected));
+ EXPECT_EQ(expected_values_->front(), should_be_expected);
+ expected_values_->pop();
+ if (expected_values_->empty())
+ std::move(quit_closure_).Run();
+ return true;
+ }
+
+ private:
+ base::queue<int32_t>* expected_values_;
+ base::OnceClosure quit_closure_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExpectValueSequenceListener);
+};
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT_WITH_CUSTOM_FIXTURE(CreatePausedClient,
+ ChannelProxyClient) {
+ base::queue<int32_t> expected_values;
+ base::RunLoop run_loop;
+ ExpectValueSequenceListener listener(&expected_values,
+ run_loop.QuitClosure());
+ CreateProxy(&listener);
+ expected_values.push(1);
+ expected_values.push(4);
+ expected_values.push(5);
+ expected_values.push(2);
+ expected_values.push(3);
+ RunProxy();
+ run_loop.Run();
+ EXPECT_TRUE(expected_values.empty());
+ DestroyProxy();
+}
+
+TEST_F(IPCChannelProxyMojoTest, AssociatedRequestClose) {
+ Init("DropAssociatedRequest");
+
+ DummyListener listener;
+ CreateProxy(&listener);
+ RunProxy();
+
+ IPC::mojom::AssociatedInterfaceVendorAssociatedPtr vendor;
+ proxy()->GetRemoteAssociatedInterface(&vendor);
+ IPC::mojom::SimpleTestDriverAssociatedPtr tester;
+ vendor->GetTestInterface(mojo::MakeRequest(&tester));
+ base::RunLoop run_loop;
+ tester.set_connection_error_handler(run_loop.QuitClosure());
+ run_loop.Run();
+
+ proxy()->GetRemoteAssociatedInterface(&tester);
+ EXPECT_TRUE(WaitForClientShutdown());
+ DestroyProxy();
+}
+
+class AssociatedInterfaceDroppingListener : public IPC::Listener {
+ public:
+ AssociatedInterfaceDroppingListener(base::OnceClosure callback)
+ : callback_(std::move(callback)) {}
+ bool OnMessageReceived(const IPC::Message& message) override { return false; }
+
+ void OnAssociatedInterfaceRequest(
+ const std::string& interface_name,
+ mojo::ScopedInterfaceEndpointHandle handle) override {
+ if (interface_name == IPC::mojom::SimpleTestDriver::Name_)
+ std::move(callback_).Run();
+ }
+
+ private:
+ base::OnceClosure callback_;
+};
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT_WITH_CUSTOM_FIXTURE(DropAssociatedRequest,
+ ChannelProxyClient) {
+ base::RunLoop run_loop;
+ AssociatedInterfaceDroppingListener listener(run_loop.QuitClosure());
+ CreateProxy(&listener);
+ RunProxy();
+ run_loop.Run();
+ DestroyProxy();
+}
+
+#if !defined(OS_MACOSX)
+// TODO(wez): On Mac we need to set up a MachPortBroker before we can transfer
+// Mach ports (which underpin Sharedmemory on Mac) across IPC.
+
+class ListenerThatExpectsSharedMemory : public TestListenerBase {
+ public:
+ ListenerThatExpectsSharedMemory(base::OnceClosure quit_closure)
+ : TestListenerBase(std::move(quit_closure)) {}
+
+ bool OnMessageReceived(const IPC::Message& message) override {
+ base::PickleIterator iter(message);
+
+ base::SharedMemoryHandle shared_memory;
+ EXPECT_TRUE(IPC::ReadParam(&message, &iter, &shared_memory));
+ EXPECT_TRUE(shared_memory.IsValid());
+ shared_memory.Close();
+
+ ListenerThatExpectsOK::SendOK(sender());
+ return true;
+ }
+};
+
+TEST_F(IPCChannelMojoTest, SendSharedMemory) {
+ Init("IPCChannelMojoTestSendSharedMemoryClient");
+
+ // Create some shared-memory to share.
+ base::SharedMemoryCreateOptions options;
+ options.size = 1004;
+
+ base::SharedMemory shmem;
+ ASSERT_TRUE(shmem.Create(options));
+
+ // Create a success listener, and launch the child process.
+ base::RunLoop run_loop;
+ ListenerThatExpectsOK listener(run_loop.QuitClosure());
+ CreateChannel(&listener);
+ ASSERT_TRUE(ConnectChannel());
+
+ // Send the child process an IPC with |shmem| attached, to verify
+ // that is is correctly wrapped, transferred and unwrapped.
+ IPC::Message* message = new IPC::Message(0, 2, IPC::Message::PRIORITY_NORMAL);
+ IPC::WriteParam(message, shmem.handle());
+ ASSERT_TRUE(channel()->Send(message));
+
+ run_loop.Run();
+
+ channel()->Close();
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ DestroyChannel();
+}
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(IPCChannelMojoTestSendSharedMemoryClient) {
+ base::RunLoop run_loop;
+ ListenerThatExpectsSharedMemory listener(run_loop.QuitClosure());
+ Connect(&listener);
+ listener.set_sender(channel());
+
+ run_loop.Run();
+
+ Close();
+}
+
+template <class SharedMemoryRegionType>
+class IPCChannelMojoSharedMemoryRegionTypedTest : public IPCChannelMojoTest {};
+
+struct WritableRegionTraits {
+ using RegionType = base::WritableSharedMemoryRegion;
+ static const char kClientName[];
+};
+const char WritableRegionTraits::kClientName[] =
+ "IPCChannelMojoTestSendWritableSharedMemoryRegionClient";
+struct UnsafeRegionTraits {
+ using RegionType = base::UnsafeSharedMemoryRegion;
+ static const char kClientName[];
+};
+const char UnsafeRegionTraits::kClientName[] =
+ "IPCChannelMojoTestSendUnsafeSharedMemoryRegionClient";
+struct ReadOnlyRegionTraits {
+ using RegionType = base::ReadOnlySharedMemoryRegion;
+ static const char kClientName[];
+};
+const char ReadOnlyRegionTraits::kClientName[] =
+ "IPCChannelMojoTestSendReadOnlySharedMemoryRegionClient";
+
+typedef ::testing::
+ Types<WritableRegionTraits, UnsafeRegionTraits, ReadOnlyRegionTraits>
+ AllSharedMemoryRegionTraits;
+TYPED_TEST_CASE(IPCChannelMojoSharedMemoryRegionTypedTest,
+ AllSharedMemoryRegionTraits);
+
+template <class SharedMemoryRegionType>
+class ListenerThatExpectsSharedMemoryRegion : public TestListenerBase {
+ public:
+ explicit ListenerThatExpectsSharedMemoryRegion(base::OnceClosure quit_closure)
+ : TestListenerBase(std::move(quit_closure)) {}
+
+ bool OnMessageReceived(const IPC::Message& message) override {
+ base::PickleIterator iter(message);
+
+ SharedMemoryRegionType region;
+ EXPECT_TRUE(IPC::ReadParam(&message, &iter, &region));
+ EXPECT_TRUE(region.IsValid());
+
+ // Verify the shared memory region has expected content.
+ typename SharedMemoryRegionType::MappingType mapping = region.Map();
+ std::string content = HandleSendingHelper::GetSendingFileContent();
+ EXPECT_EQ(0, memcmp(mapping.memory(), content.data(), content.size()));
+
+ ListenerThatExpectsOK::SendOK(sender());
+ return true;
+ }
+};
+
+TYPED_TEST(IPCChannelMojoSharedMemoryRegionTypedTest, Send) {
+ this->Init(TypeParam::kClientName);
+
+ const size_t size = 1004;
+ typename TypeParam::RegionType region;
+ base::WritableSharedMemoryMapping mapping;
+ std::tie(region, mapping) =
+ base::CreateMappedRegion<typename TypeParam::RegionType>(size);
+
+ std::string content = HandleSendingHelper::GetSendingFileContent();
+ memcpy(mapping.memory(), content.data(), content.size());
+
+ // Create a success listener, and launch the child process.
+ base::RunLoop run_loop;
+ ListenerThatExpectsOK listener(run_loop.QuitClosure());
+ this->CreateChannel(&listener);
+ ASSERT_TRUE(this->ConnectChannel());
+
+ // Send the child process an IPC with |shmem| attached, to verify
+ // that is is correctly wrapped, transferred and unwrapped.
+ IPC::Message* message = new IPC::Message(0, 2, IPC::Message::PRIORITY_NORMAL);
+ IPC::WriteParam(message, region);
+ ASSERT_TRUE(this->channel()->Send(message));
+
+ run_loop.Run();
+
+ this->channel()->Close();
+
+ EXPECT_TRUE(this->WaitForClientShutdown());
+ EXPECT_FALSE(region.IsValid());
+ this->DestroyChannel();
+}
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(
+ IPCChannelMojoTestSendWritableSharedMemoryRegionClient) {
+ base::RunLoop run_loop;
+ ListenerThatExpectsSharedMemoryRegion<base::WritableSharedMemoryRegion>
+ listener(run_loop.QuitClosure());
+ Connect(&listener);
+ listener.set_sender(channel());
+
+ run_loop.Run();
+
+ Close();
+}
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(
+ IPCChannelMojoTestSendUnsafeSharedMemoryRegionClient) {
+ base::RunLoop run_loop;
+ ListenerThatExpectsSharedMemoryRegion<base::UnsafeSharedMemoryRegion>
+ listener(run_loop.QuitClosure());
+ Connect(&listener);
+ listener.set_sender(channel());
+
+ run_loop.Run();
+
+ Close();
+}
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(
+ IPCChannelMojoTestSendReadOnlySharedMemoryRegionClient) {
+ base::RunLoop run_loop;
+ ListenerThatExpectsSharedMemoryRegion<base::ReadOnlySharedMemoryRegion>
+ listener(run_loop.QuitClosure());
+ Connect(&listener);
+ listener.set_sender(channel());
+
+ run_loop.Run();
+
+ Close();
+}
+#endif // !defined(OS_MACOSX)
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+class ListenerThatExpectsFile : public TestListenerBase {
+ public:
+ explicit ListenerThatExpectsFile(base::OnceClosure quit_closure)
+ : TestListenerBase(std::move(quit_closure)) {}
+
+ bool OnMessageReceived(const IPC::Message& message) override {
+ base::PickleIterator iter(message);
+ HandleSendingHelper::ReadReceivedFile(message, &iter);
+ ListenerThatExpectsOK::SendOK(sender());
+ return true;
+ }
+};
+
+TEST_F(IPCChannelMojoTest, SendPlatformFile) {
+ Init("IPCChannelMojoTestSendPlatformFileClient");
+
+ base::RunLoop run_loop;
+ ListenerThatExpectsOK listener(run_loop.QuitClosure());
+ CreateChannel(&listener);
+ ASSERT_TRUE(ConnectChannel());
+
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::File file(HandleSendingHelper::GetSendingFilePath(temp_dir.GetPath()),
+ base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE |
+ base::File::FLAG_READ);
+ HandleSendingHelper::WriteFileThenSend(channel(), file);
+ run_loop.Run();
+
+ channel()->Close();
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ DestroyChannel();
+}
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(IPCChannelMojoTestSendPlatformFileClient) {
+ base::RunLoop run_loop;
+ ListenerThatExpectsFile listener(run_loop.QuitClosure());
+ Connect(&listener);
+ listener.set_sender(channel());
+
+ run_loop.Run();
+
+ Close();
+}
+
+class ListenerThatExpectsFileAndMessagePipe : public TestListenerBase {
+ public:
+ explicit ListenerThatExpectsFileAndMessagePipe(base::OnceClosure quit_closure)
+ : TestListenerBase(std::move(quit_closure)) {}
+
+ ~ListenerThatExpectsFileAndMessagePipe() override = default;
+
+ bool OnMessageReceived(const IPC::Message& message) override {
+ base::PickleIterator iter(message);
+ HandleSendingHelper::ReadReceivedFile(message, &iter);
+ HandleSendingHelper::ReadReceivedPipe(message, &iter);
+ ListenerThatExpectsOK::SendOK(sender());
+ return true;
+ }
+};
+
+TEST_F(IPCChannelMojoTest, SendPlatformFileAndMessagePipe) {
+ Init("IPCChannelMojoTestSendPlatformFileAndMessagePipeClient");
+
+ base::RunLoop run_loop;
+ ListenerThatExpectsOK listener(run_loop.QuitClosure());
+ CreateChannel(&listener);
+ ASSERT_TRUE(ConnectChannel());
+
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::File file(HandleSendingHelper::GetSendingFilePath(temp_dir.GetPath()),
+ base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE |
+ base::File::FLAG_READ);
+ TestingMessagePipe pipe;
+ HandleSendingHelper::WriteFileAndPipeThenSend(channel(), file, &pipe);
+
+ run_loop.Run();
+ channel()->Close();
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ DestroyChannel();
+}
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(
+ IPCChannelMojoTestSendPlatformFileAndMessagePipeClient) {
+ base::RunLoop run_loop;
+ ListenerThatExpectsFileAndMessagePipe listener(run_loop.QuitClosure());
+ Connect(&listener);
+ listener.set_sender(channel());
+
+ run_loop.Run();
+
+ Close();
+}
+
+#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+#if defined(OS_LINUX)
+
+const base::ProcessId kMagicChildId = 54321;
+
+class ListenerThatVerifiesPeerPid : public TestListenerBase {
+ public:
+ explicit ListenerThatVerifiesPeerPid(base::OnceClosure quit_closure)
+ : TestListenerBase(std::move(quit_closure)) {}
+
+ void OnChannelConnected(int32_t peer_pid) override {
+ EXPECT_EQ(peer_pid, kMagicChildId);
+ RunQuitClosure();
+ }
+
+ bool OnMessageReceived(const IPC::Message& message) override {
+ NOTREACHED();
+ return true;
+ }
+};
+
+TEST_F(IPCChannelMojoTest, VerifyGlobalPid) {
+ Init("IPCChannelMojoTestVerifyGlobalPidClient");
+
+ base::RunLoop run_loop;
+ ListenerThatVerifiesPeerPid listener(run_loop.QuitClosure());
+ CreateChannel(&listener);
+ ASSERT_TRUE(ConnectChannel());
+
+ run_loop.Run();
+ channel()->Close();
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ DestroyChannel();
+}
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(IPCChannelMojoTestVerifyGlobalPidClient) {
+ IPC::Channel::SetGlobalPid(kMagicChildId);
+
+ base::RunLoop run_loop;
+ ListenerThatQuits listener(run_loop.QuitClosure());
+ Connect(&listener);
+
+ run_loop.Run();
+
+ Close();
+}
+
+#endif // OS_LINUX
+
+} // namespace
diff --git a/ipc/ipc_channel_nacl.cc b/ipc/ipc_channel_nacl.cc
new file mode 100644
index 0000000000..190da45453
--- /dev/null
+++ b/ipc/ipc_channel_nacl.cc
@@ -0,0 +1,396 @@
+// 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_channel_nacl.h"
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_pump_for_io.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "base/task_runner_util.h"
+#include "base/threading/simple_thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "ipc/ipc_listener.h"
+#include "ipc/ipc_logging.h"
+#include "ipc/ipc_message_attachment_set.h"
+#include "ipc/ipc_platform_file_attachment_posix.h"
+#include "native_client/src/public/imc_syscalls.h"
+#include "native_client/src/public/imc_types.h"
+
+namespace IPC {
+
+struct MessageContents {
+ std::vector<char> data;
+ std::vector<int> fds;
+};
+
+namespace {
+
+bool ReadDataOnReaderThread(int pipe, MessageContents* contents) {
+ DCHECK(pipe >= 0);
+ if (pipe < 0)
+ return false;
+
+ contents->data.resize(Channel::kReadBufferSize);
+ contents->fds.resize(NACL_ABI_IMC_DESC_MAX);
+
+ NaClAbiNaClImcMsgIoVec iov = { &contents->data[0], contents->data.size() };
+ NaClAbiNaClImcMsgHdr msg = {
+ &iov, 1, &contents->fds[0], contents->fds.size()
+ };
+
+ int bytes_read = imc_recvmsg(pipe, &msg, 0);
+
+ if (bytes_read <= 0) {
+ // NaClIPCAdapter::BlockingReceive returns -1 when the pipe closes (either
+ // due to error or for regular shutdown).
+ contents->data.clear();
+ contents->fds.clear();
+ return false;
+ }
+ DCHECK(bytes_read);
+ // Resize the buffers down to the number of bytes and fds we actually read.
+ contents->data.resize(bytes_read);
+ contents->fds.resize(msg.desc_length);
+ return true;
+}
+
+} // namespace
+
+// static
+constexpr size_t Channel::kMaximumMessageSize;
+
+class ChannelNacl::ReaderThreadRunner
+ : public base::DelegateSimpleThread::Delegate {
+ public:
+ // |pipe|: A file descriptor from which we will read using imc_recvmsg.
+ // |data_read_callback|: A callback we invoke (on the main thread) when we
+ // have read data.
+ // |failure_callback|: A callback we invoke when we have a failure reading
+ // from |pipe|.
+ // |main_message_loop|: A proxy for the main thread, where we will invoke the
+ // above callbacks.
+ ReaderThreadRunner(
+ int pipe,
+ base::Callback<void(std::unique_ptr<MessageContents>)> data_read_callback,
+ base::Callback<void()> failure_callback,
+ scoped_refptr<base::SingleThreadTaskRunner> main_task_runner);
+
+ // DelegateSimpleThread implementation. Reads data from the pipe in a loop
+ // until either we are told to quit or a read fails.
+ void Run() override;
+
+ private:
+ int pipe_;
+ base::Callback<void(std::unique_ptr<MessageContents>)> data_read_callback_;
+ base::Callback<void ()> failure_callback_;
+ scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReaderThreadRunner);
+};
+
+ChannelNacl::ReaderThreadRunner::ReaderThreadRunner(
+ int pipe,
+ base::Callback<void(std::unique_ptr<MessageContents>)> data_read_callback,
+ base::Callback<void()> failure_callback,
+ scoped_refptr<base::SingleThreadTaskRunner> main_task_runner)
+ : pipe_(pipe),
+ data_read_callback_(data_read_callback),
+ failure_callback_(failure_callback),
+ main_task_runner_(main_task_runner) {}
+
+void ChannelNacl::ReaderThreadRunner::Run() {
+ while (true) {
+ std::unique_ptr<MessageContents> msg_contents(new MessageContents);
+ bool success = ReadDataOnReaderThread(pipe_, msg_contents.get());
+ if (success) {
+ main_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(data_read_callback_, base::Passed(&msg_contents)));
+ } else {
+ main_task_runner_->PostTask(FROM_HERE, failure_callback_);
+ // Because the read failed, we know we're going to quit. Don't bother
+ // trying to read again.
+ return;
+ }
+ }
+}
+
+ChannelNacl::ChannelNacl(const IPC::ChannelHandle& channel_handle,
+ Mode mode,
+ Listener* listener)
+ : ChannelReader(listener),
+ mode_(mode),
+ waiting_connect_(true),
+ pipe_(-1),
+ weak_ptr_factory_(this) {
+ if (!CreatePipe(channel_handle)) {
+ // The pipe may have been closed already.
+ const char *modestr = (mode_ & MODE_SERVER_FLAG) ? "server" : "client";
+ LOG(WARNING) << "Unable to create pipe in " << modestr << " mode";
+ }
+}
+
+ChannelNacl::~ChannelNacl() {
+ CleanUp();
+ Close();
+}
+
+bool ChannelNacl::Connect() {
+ WillConnect();
+
+ if (pipe_ == -1) {
+ DLOG(WARNING) << "Channel creation failed";
+ return false;
+ }
+
+ // Note that Connect is called on the "Channel" thread (i.e., the same thread
+ // where Channel::Send will be called, and the same thread that should receive
+ // messages). The constructor might be invoked on another thread (see
+ // ChannelProxy for an example of that). Therefore, we must wait until Connect
+ // is called to decide which SingleThreadTaskRunner to pass to
+ // ReaderThreadRunner.
+ reader_thread_runner_.reset(new ReaderThreadRunner(
+ pipe_,
+ base::Bind(&ChannelNacl::DidRecvMsg, weak_ptr_factory_.GetWeakPtr()),
+ base::Bind(&ChannelNacl::ReadDidFail, weak_ptr_factory_.GetWeakPtr()),
+ base::ThreadTaskRunnerHandle::Get()));
+ reader_thread_.reset(
+ new base::DelegateSimpleThread(reader_thread_runner_.get(),
+ "ipc_channel_nacl reader thread"));
+ reader_thread_->Start();
+ waiting_connect_ = false;
+ // If there were any messages queued before connection, send them.
+ ProcessOutgoingMessages();
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&ChannelNacl::CallOnChannelConnected,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ return true;
+}
+
+void ChannelNacl::Close() {
+ // For now, we assume that at shutdown, the reader thread will be woken with
+ // a failure (see NaClIPCAdapter::BlockingRead and CloseChannel). Or... we
+ // might simply be killed with no chance to clean up anyway :-).
+ // If untrusted code tries to close the channel prior to shutdown, it's likely
+ // to hang.
+ // TODO(dmichael): Can we do anything smarter here to make sure the reader
+ // thread wakes up and quits?
+ reader_thread_->Join();
+ close(pipe_);
+ pipe_ = -1;
+ reader_thread_runner_.reset();
+ reader_thread_.reset();
+ read_queue_.clear();
+ output_queue_.clear();
+}
+
+bool ChannelNacl::Send(Message* message) {
+ DCHECK(!message->HasAttachments());
+ DVLOG(2) << "sending message @" << message << " on channel @" << this
+ << " with type " << message->type();
+ std::unique_ptr<Message> message_ptr(message);
+
+#if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
+ Logging::GetInstance()->OnSendMessage(message_ptr.get());
+#endif // BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
+
+ TRACE_EVENT_WITH_FLOW0(TRACE_DISABLED_BY_DEFAULT("ipc.flow"),
+ "ChannelNacl::Send",
+ message->header()->flags,
+ TRACE_EVENT_FLAG_FLOW_OUT);
+ output_queue_.push_back(std::move(message_ptr));
+ if (!waiting_connect_)
+ return ProcessOutgoingMessages();
+
+ return true;
+}
+
+void ChannelNacl::DidRecvMsg(std::unique_ptr<MessageContents> contents) {
+ // Close sets the pipe to -1. It's possible we'll get a buffer sent to us from
+ // the reader thread after Close is called. If so, we ignore it.
+ if (pipe_ == -1)
+ return;
+
+ auto data = std::make_unique<std::vector<char>>();
+ data->swap(contents->data);
+ read_queue_.push_back(std::move(data));
+
+ input_attachments_.reserve(contents->fds.size());
+ for (int fd : contents->fds) {
+ input_attachments_.push_back(
+ new internal::PlatformFileAttachment(base::ScopedFD(fd)));
+ }
+ contents->fds.clear();
+
+ // In POSIX, we would be told when there are bytes to read by implementing
+ // OnFileCanReadWithoutBlocking in MessagePumpForIO::FdWatcher. In NaCl, we
+ // instead know at this point because the reader thread posted some data to
+ // us.
+ ProcessIncomingMessages();
+}
+
+void ChannelNacl::ReadDidFail() {
+ Close();
+}
+
+bool ChannelNacl::CreatePipe(
+ const IPC::ChannelHandle& channel_handle) {
+ DCHECK(pipe_ == -1);
+
+ // There's one possible case in NaCl:
+ // 1) It's a channel wrapping a pipe that is given to us.
+ // We don't support these:
+ // 2) It's for a named channel.
+ // 3) It's for a client that we implement ourself.
+ // 4) It's the initial IPC channel.
+
+ if (channel_handle.socket.fd == -1) {
+ NOTIMPLEMENTED();
+ return false;
+ }
+ pipe_ = channel_handle.socket.fd;
+ return true;
+}
+
+bool ChannelNacl::ProcessOutgoingMessages() {
+ DCHECK(!waiting_connect_); // Why are we trying to send messages if there's
+ // no connection?
+ if (output_queue_.empty())
+ return true;
+
+ if (pipe_ == -1)
+ return false;
+
+ // Write out all the messages. The trusted implementation is guaranteed to not
+ // block. See NaClIPCAdapter::Send for the implementation of imc_sendmsg.
+ while (!output_queue_.empty()) {
+ std::unique_ptr<Message> msg = std::move(output_queue_.front());
+ output_queue_.pop_front();
+
+ const size_t num_fds = msg->attachment_set()->size();
+ DCHECK(num_fds <= MessageAttachmentSet::kMaxDescriptorsPerMessage);
+ std::vector<int> fds;
+ fds.reserve(num_fds);
+ for (size_t i = 0; i < num_fds; i++) {
+ scoped_refptr<MessageAttachment> attachment =
+ msg->attachment_set()->GetAttachmentAt(i);
+ DCHECK_EQ(MessageAttachment::Type::PLATFORM_FILE, attachment->GetType());
+ fds.push_back(static_cast<internal::PlatformFileAttachment&>(*attachment)
+ .TakePlatformFile());
+ }
+
+ NaClAbiNaClImcMsgIoVec iov = {
+ const_cast<void*>(msg->data()), msg->size()
+ };
+ NaClAbiNaClImcMsgHdr msgh = {&iov, 1, fds.data(), num_fds};
+ ssize_t bytes_written = imc_sendmsg(pipe_, &msgh, 0);
+
+ DCHECK(bytes_written); // The trusted side shouldn't return 0.
+ if (bytes_written < 0) {
+ // The trusted side should only ever give us an error of EPIPE. We
+ // should never be interrupted, nor should we get EAGAIN.
+ DCHECK(errno == EPIPE);
+ Close();
+ PLOG(ERROR) << "pipe_ error on "
+ << pipe_
+ << " Currently writing message of size: "
+ << msg->size();
+ return false;
+ } else {
+ msg->attachment_set()->CommitAllDescriptors();
+ }
+
+ // Message sent OK!
+ DVLOG(2) << "sent message @" << msg.get() << " with type " << msg->type()
+ << " on fd " << pipe_;
+ }
+ return true;
+}
+
+void ChannelNacl::CallOnChannelConnected() {
+ listener()->OnChannelConnected(-1);
+}
+
+ChannelNacl::ReadState ChannelNacl::ReadData(
+ char* buffer,
+ int buffer_len,
+ int* bytes_read) {
+ *bytes_read = 0;
+ if (pipe_ == -1)
+ return READ_FAILED;
+ if (read_queue_.empty())
+ return READ_PENDING;
+ while (!read_queue_.empty() && *bytes_read < buffer_len) {
+ std::vector<char>* vec = read_queue_.front().get();
+ size_t bytes_to_read = buffer_len - *bytes_read;
+ if (vec->size() <= bytes_to_read) {
+ // We can read and discard the entire vector.
+ std::copy(vec->begin(), vec->end(), buffer + *bytes_read);
+ *bytes_read += vec->size();
+ read_queue_.pop_front();
+ } else {
+ // Read all the bytes we can and discard them from the front of the
+ // vector. (This can be slowish, since erase has to move the back of the
+ // vector to the front, but it's hopefully a temporary hack and it keeps
+ // the code simple).
+ std::copy(vec->begin(), vec->begin() + bytes_to_read,
+ buffer + *bytes_read);
+ vec->erase(vec->begin(), vec->begin() + bytes_to_read);
+ *bytes_read += bytes_to_read;
+ }
+ }
+ return READ_SUCCEEDED;
+}
+
+bool ChannelNacl::ShouldDispatchInputMessage(Message* msg) {
+ return true;
+}
+
+bool ChannelNacl::GetAttachments(Message* msg) {
+ uint16_t header_fds = msg->header()->num_fds;
+ CHECK(header_fds == input_attachments_.size());
+ if (header_fds == 0)
+ return true; // Nothing to do.
+
+ for (auto& attachment : input_attachments_) {
+ msg->attachment_set()->AddAttachment(std::move(attachment));
+ }
+ input_attachments_.clear();
+ return true;
+}
+
+bool ChannelNacl::DidEmptyInputBuffers() {
+ // When the input data buffer is empty, the attachments should be too.
+ return input_attachments_.empty();
+}
+
+void ChannelNacl::HandleInternalMessage(const Message& msg) {
+ // The trusted side IPC::Channel should handle the "hello" handshake; we
+ // should not receive the "Hello" message.
+ NOTREACHED();
+}
+
+// Channel's methods
+
+// static
+std::unique_ptr<Channel> Channel::Create(
+ const IPC::ChannelHandle& channel_handle,
+ Mode mode,
+ Listener* listener) {
+ return std::make_unique<ChannelNacl>(channel_handle, mode, listener);
+}
+
+} // namespace IPC
diff --git a/ipc/ipc_channel_nacl.h b/ipc/ipc_channel_nacl.h
new file mode 100644
index 0000000000..06e7f6a271
--- /dev/null
+++ b/ipc/ipc_channel_nacl.h
@@ -0,0 +1,114 @@
+// 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_NACL_H_
+#define IPC_IPC_CHANNEL_NACL_H_
+
+#include <memory>
+#include <string>
+
+#include "base/containers/circular_deque.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/process/process.h"
+#include "base/threading/simple_thread.h"
+#include "ipc/ipc_channel.h"
+#include "ipc/ipc_channel_reader.h"
+
+namespace IPC {
+
+class MessageAttachment;
+
+// Contains the results from one call to imc_recvmsg (data and file
+// descriptors).
+struct MessageContents;
+
+// Similar to the ChannelPosix but for Native Client code.
+// This is somewhat different because sendmsg/recvmsg here do not follow POSIX
+// semantics. Instead, they are implemented by a custom embedding of
+// NaClDescCustom. See NaClIPCAdapter for the trusted-side implementation.
+//
+// We don't need to worry about complicated set up and READWRITE mode for
+// sharing handles. We also currently do not support passing file descriptors or
+// named pipes, and we use background threads to emulate signaling when we can
+// read or write without blocking.
+class ChannelNacl : public Channel,
+ public internal::ChannelReader {
+ public:
+ // Mirror methods of Channel, see ipc_channel.h for description.
+ ChannelNacl(const IPC::ChannelHandle& channel_handle,
+ Mode mode,
+ Listener* listener);
+ ~ChannelNacl() override;
+
+ // Channel implementation.
+ bool Connect() override;
+ void Close() override;
+ bool Send(Message* message) override;
+
+ // Posted to the main thread by ReaderThreadRunner.
+ void DidRecvMsg(std::unique_ptr<MessageContents> contents);
+ void ReadDidFail();
+
+ private:
+ class ReaderThreadRunner;
+
+ bool CreatePipe(const IPC::ChannelHandle& channel_handle);
+ bool ProcessOutgoingMessages();
+ void CallOnChannelConnected();
+
+ // ChannelReader implementation.
+ ReadState ReadData(char* buffer,
+ int buffer_len,
+ int* bytes_read) override;
+ bool ShouldDispatchInputMessage(Message* msg) override;
+ bool GetAttachments(Message* msg) override;
+ bool DidEmptyInputBuffers() override;
+ void HandleInternalMessage(const Message& msg) override;
+
+ Mode mode_;
+ bool waiting_connect_;
+
+ // The pipe used for communication.
+ int pipe_;
+
+ // We use a thread for reading, so that we can simply block on reading and
+ // post the received data back to the main thread to be properly interleaved
+ // with other tasks in the MessagePump.
+ //
+ // imc_recvmsg supports non-blocking reads, but there's no easy way to be
+ // informed when a write or read can be done without blocking (this is handled
+ // by libevent in Posix).
+ std::unique_ptr<ReaderThreadRunner> reader_thread_runner_;
+ std::unique_ptr<base::DelegateSimpleThread> reader_thread_;
+
+ // IPC::ChannelReader expects to be able to call ReadData on us to
+ // synchronously read data waiting in the pipe's buffer without blocking.
+ // Since we can't do that (see 1 and 2 above), the reader thread does blocking
+ // reads and posts the data over to the main thread in MessageContents. Each
+ // MessageContents object is the result of one call to "imc_recvmsg".
+ // DidRecvMsg breaks the MessageContents out in to the data and the file
+ // descriptors, and puts them on these two queues.
+ // TODO(dmichael): There's probably a more efficient way to emulate this with
+ // a circular buffer or something, so we don't have to do so
+ // many heap allocations. But it maybe isn't worth
+ // the trouble given that we probably want to implement 1 and
+ // 2 above in NaCl eventually.
+ // When ReadData is called, it pulls the bytes out of this queue in order.
+ base::circular_deque<std::unique_ptr<std::vector<char>>> read_queue_;
+ // Queue of file descriptor attachments extracted from imc_recvmsg messages.
+ std::vector<scoped_refptr<MessageAttachment>> input_attachments_;
+
+ // This queue is used when a message is sent prior to Connect having been
+ // called. Normally after we're connected, the queue is empty.
+ base::circular_deque<std::unique_ptr<Message>> output_queue_;
+
+ base::WeakPtrFactory<ChannelNacl> weak_ptr_factory_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ChannelNacl);
+};
+
+} // namespace IPC
+
+#endif // IPC_IPC_CHANNEL_NACL_H_
diff --git a/ipc/ipc_channel_proxy.cc b/ipc/ipc_channel_proxy.cc
new file mode 100644
index 0000000000..52cba0221c
--- /dev/null
+++ b/ipc/ipc_channel_proxy.cc
@@ -0,0 +1,583 @@
+// 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_channel_proxy.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/location.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 "build/build_config.h"
+#include "ipc/ipc_channel_factory.h"
+#include "ipc/ipc_listener.h"
+#include "ipc/ipc_logging.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/message_filter.h"
+#include "ipc/message_filter_router.h"
+
+namespace IPC {
+
+//------------------------------------------------------------------------------
+
+ChannelProxy::Context::Context(
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& listener_task_runner)
+ : listener_task_runner_(listener_task_runner),
+ listener_(listener),
+ ipc_task_runner_(ipc_task_runner),
+ channel_connected_called_(false),
+ message_filter_router_(new MessageFilterRouter()),
+ peer_pid_(base::kNullProcessId) {
+ DCHECK(ipc_task_runner_.get());
+ // The Listener thread where Messages are handled must be a separate thread
+ // to avoid oversubscribing the IO thread. If you trigger this error, you
+ // need to either:
+ // 1) Create the ChannelProxy on a different thread, or
+ // 2) Just use Channel
+ // Note, we currently make an exception for a NULL listener. That usage
+ // basically works, but is outside the intent of ChannelProxy. This support
+ // will disappear, so please don't rely on it. See crbug.com/364241
+ DCHECK(!listener || (ipc_task_runner_.get() != listener_task_runner_.get()));
+}
+
+ChannelProxy::Context::~Context() = default;
+
+void ChannelProxy::Context::ClearIPCTaskRunner() {
+ ipc_task_runner_ = NULL;
+}
+
+void ChannelProxy::Context::CreateChannel(
+ std::unique_ptr<ChannelFactory> factory) {
+ base::AutoLock l(channel_lifetime_lock_);
+ DCHECK(!channel_);
+ DCHECK_EQ(factory->GetIPCTaskRunner(), ipc_task_runner_);
+ channel_ = factory->BuildChannel(this);
+
+ Channel::AssociatedInterfaceSupport* support =
+ channel_->GetAssociatedInterfaceSupport();
+ if (support) {
+ thread_safe_channel_ = support->CreateThreadSafeChannel();
+
+ base::AutoLock l(pending_filters_lock_);
+ for (auto& entry : pending_io_thread_interfaces_)
+ support->AddGenericAssociatedInterface(entry.first, entry.second);
+ pending_io_thread_interfaces_.clear();
+ }
+}
+
+bool ChannelProxy::Context::TryFilters(const Message& message) {
+ DCHECK(message_filter_router_);
+#if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
+ Logging* logger = Logging::GetInstance();
+ if (logger->Enabled())
+ logger->OnPreDispatchMessage(message);
+#endif
+
+ if (message_filter_router_->TryFilters(message)) {
+ if (message.dispatch_error()) {
+ listener_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Context::OnDispatchBadMessage, this, message));
+ }
+#if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
+ if (logger->Enabled())
+ logger->OnPostDispatchMessage(message);
+#endif
+ return true;
+ }
+ return false;
+}
+
+// Called on the IPC::Channel thread
+void ChannelProxy::Context::PauseChannel() {
+ DCHECK(channel_);
+ channel_->Pause();
+}
+
+// Called on the IPC::Channel thread
+void ChannelProxy::Context::UnpauseChannel(bool flush) {
+ DCHECK(channel_);
+ channel_->Unpause(flush);
+}
+
+// Called on the IPC::Channel thread
+void ChannelProxy::Context::FlushChannel() {
+ DCHECK(channel_);
+ channel_->Flush();
+}
+
+// Called on the IPC::Channel thread
+bool ChannelProxy::Context::OnMessageReceived(const Message& message) {
+ // First give a chance to the filters to process this message.
+ if (!TryFilters(message))
+ OnMessageReceivedNoFilter(message);
+ return true;
+}
+
+// Called on the IPC::Channel thread
+bool ChannelProxy::Context::OnMessageReceivedNoFilter(const Message& message) {
+ listener_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Context::OnDispatchMessage, this, message));
+ return true;
+}
+
+// Called on the IPC::Channel thread
+void ChannelProxy::Context::OnChannelConnected(int32_t peer_pid) {
+ // We cache off the peer_pid so it can be safely accessed from both threads.
+ {
+ base::AutoLock l(peer_pid_lock_);
+ peer_pid_ = peer_pid;
+ }
+
+ // Add any pending filters. This avoids a race condition where someone
+ // creates a ChannelProxy, calls AddFilter, and then right after starts the
+ // peer process. The IO thread could receive a message before the task to add
+ // the filter is run on the IO thread.
+ OnAddFilter();
+
+ // See above comment about using listener_task_runner_ here.
+ listener_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Context::OnDispatchConnected, this));
+}
+
+// Called on the IPC::Channel thread
+void ChannelProxy::Context::OnChannelError() {
+ for (size_t i = 0; i < filters_.size(); ++i)
+ filters_[i]->OnChannelError();
+
+ // See above comment about using listener_task_runner_ here.
+ listener_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Context::OnDispatchError, this));
+}
+
+// Called on the IPC::Channel thread
+void ChannelProxy::Context::OnAssociatedInterfaceRequest(
+ const std::string& interface_name,
+ mojo::ScopedInterfaceEndpointHandle handle) {
+ listener_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Context::OnDispatchAssociatedInterfaceRequest,
+ this, interface_name, base::Passed(&handle)));
+}
+
+// Called on the IPC::Channel thread
+void ChannelProxy::Context::OnChannelOpened() {
+ DCHECK(channel_ != NULL);
+
+ // Assume a reference to ourselves on behalf of this thread. This reference
+ // will be released when we are closed.
+ AddRef();
+
+ if (!channel_->Connect()) {
+ OnChannelError();
+ return;
+ }
+
+ for (size_t i = 0; i < filters_.size(); ++i)
+ filters_[i]->OnFilterAdded(channel_.get());
+}
+
+// Called on the IPC::Channel thread
+void ChannelProxy::Context::OnChannelClosed() {
+ // It's okay for IPC::ChannelProxy::Close to be called more than once, which
+ // would result in this branch being taken.
+ if (!channel_)
+ return;
+
+ for (auto& filter : pending_filters_) {
+ filter->OnChannelClosing();
+ filter->OnFilterRemoved();
+ }
+ for (auto& filter : filters_) {
+ filter->OnChannelClosing();
+ filter->OnFilterRemoved();
+ }
+
+ // We don't need the filters anymore.
+ message_filter_router_->Clear();
+ filters_.clear();
+ // We don't need the lock, because at this point, the listener thread can't
+ // access it any more.
+ pending_filters_.clear();
+
+ ClearChannel();
+
+ // Balance with the reference taken during startup. This may result in
+ // self-destruction.
+ Release();
+}
+
+void ChannelProxy::Context::Clear() {
+ listener_ = NULL;
+}
+
+// Called on the IPC::Channel thread
+void ChannelProxy::Context::OnSendMessage(std::unique_ptr<Message> message) {
+ if (!channel_) {
+ OnChannelClosed();
+ return;
+ }
+
+ if (!channel_->Send(message.release()))
+ OnChannelError();
+}
+
+// Called on the IPC::Channel thread
+void ChannelProxy::Context::OnAddFilter() {
+ // Our OnChannelConnected method has not yet been called, so we can't be
+ // sure that channel_ is valid yet. When OnChannelConnected *is* called,
+ // it invokes OnAddFilter, so any pending filter(s) will be added at that
+ // time.
+ // No lock necessary for |peer_pid_| because it is only modified on this
+ // thread.
+ if (peer_pid_ == base::kNullProcessId)
+ return;
+
+ std::vector<scoped_refptr<MessageFilter> > new_filters;
+ {
+ base::AutoLock auto_lock(pending_filters_lock_);
+ new_filters.swap(pending_filters_);
+ }
+
+ for (size_t i = 0; i < new_filters.size(); ++i) {
+ filters_.push_back(new_filters[i]);
+
+ message_filter_router_->AddFilter(new_filters[i].get());
+
+ // The channel has already been created and connected, so we need to
+ // inform the filters right now.
+ new_filters[i]->OnFilterAdded(channel_.get());
+ new_filters[i]->OnChannelConnected(peer_pid_);
+ }
+}
+
+// Called on the IPC::Channel thread
+void ChannelProxy::Context::OnRemoveFilter(MessageFilter* filter) {
+ // No lock necessary for |peer_pid_| because it is only modified on this
+ // thread.
+ if (peer_pid_ == base::kNullProcessId) {
+ // The channel is not yet connected, so any filters are still pending.
+ base::AutoLock auto_lock(pending_filters_lock_);
+ for (size_t i = 0; i < pending_filters_.size(); ++i) {
+ if (pending_filters_[i].get() == filter) {
+ filter->OnFilterRemoved();
+ pending_filters_.erase(pending_filters_.begin() + i);
+ return;
+ }
+ }
+ return;
+ }
+ if (!channel_)
+ return; // The filters have already been deleted.
+
+ message_filter_router_->RemoveFilter(filter);
+
+ for (size_t i = 0; i < filters_.size(); ++i) {
+ if (filters_[i].get() == filter) {
+ filter->OnFilterRemoved();
+ filters_.erase(filters_.begin() + i);
+ return;
+ }
+ }
+
+ NOTREACHED() << "filter to be removed not found";
+}
+
+// Called on the listener's thread
+void ChannelProxy::Context::AddFilter(MessageFilter* filter) {
+ base::AutoLock auto_lock(pending_filters_lock_);
+ pending_filters_.push_back(base::WrapRefCounted(filter));
+ ipc_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Context::OnAddFilter, this));
+}
+
+// Called on the listener's thread
+void ChannelProxy::Context::OnDispatchMessage(const Message& message) {
+ if (!listener_)
+ return;
+
+ OnDispatchConnected();
+
+#if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
+ Logging* logger = Logging::GetInstance();
+ if (message.type() == IPC_LOGGING_ID) {
+ logger->OnReceivedLoggingMessage(message);
+ return;
+ }
+
+ if (logger->Enabled())
+ logger->OnPreDispatchMessage(message);
+#endif
+
+ listener_->OnMessageReceived(message);
+ if (message.dispatch_error())
+ listener_->OnBadMessageReceived(message);
+
+#if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
+ if (logger->Enabled())
+ logger->OnPostDispatchMessage(message);
+#endif
+}
+
+// Called on the listener's thread
+void ChannelProxy::Context::OnDispatchConnected() {
+ if (channel_connected_called_)
+ return;
+
+ base::ProcessId peer_pid;
+ {
+ base::AutoLock l(peer_pid_lock_);
+ peer_pid = peer_pid_;
+ }
+ channel_connected_called_ = true;
+ if (listener_)
+ listener_->OnChannelConnected(peer_pid);
+}
+
+// Called on the listener's thread
+void ChannelProxy::Context::OnDispatchError() {
+ if (listener_)
+ listener_->OnChannelError();
+}
+
+// Called on the listener's thread
+void ChannelProxy::Context::OnDispatchBadMessage(const Message& message) {
+ if (listener_)
+ listener_->OnBadMessageReceived(message);
+}
+
+// Called on the listener's thread
+void ChannelProxy::Context::OnDispatchAssociatedInterfaceRequest(
+ const std::string& interface_name,
+ mojo::ScopedInterfaceEndpointHandle handle) {
+ if (listener_)
+ listener_->OnAssociatedInterfaceRequest(interface_name, std::move(handle));
+}
+
+void ChannelProxy::Context::ClearChannel() {
+ base::AutoLock l(channel_lifetime_lock_);
+ channel_.reset();
+}
+
+void ChannelProxy::Context::AddGenericAssociatedInterfaceForIOThread(
+ const std::string& name,
+ const GenericAssociatedInterfaceFactory& factory) {
+ base::AutoLock l(channel_lifetime_lock_);
+ if (!channel_) {
+ base::AutoLock l(pending_filters_lock_);
+ pending_io_thread_interfaces_.emplace_back(name, factory);
+ return;
+ }
+ Channel::AssociatedInterfaceSupport* support =
+ channel_->GetAssociatedInterfaceSupport();
+ if (support)
+ support->AddGenericAssociatedInterface(name, factory);
+}
+
+void ChannelProxy::Context::Send(Message* message) {
+ ipc_task_runner()->PostTask(
+ FROM_HERE, base::Bind(&ChannelProxy::Context::OnSendMessage, this,
+ base::Passed(base::WrapUnique(message))));
+}
+
+//-----------------------------------------------------------------------------
+
+// static
+std::unique_ptr<ChannelProxy> ChannelProxy::Create(
+ const IPC::ChannelHandle& channel_handle,
+ Channel::Mode mode,
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& listener_task_runner) {
+ std::unique_ptr<ChannelProxy> channel(
+ new ChannelProxy(listener, ipc_task_runner, listener_task_runner));
+ channel->Init(channel_handle, mode, true);
+ return channel;
+}
+
+// static
+std::unique_ptr<ChannelProxy> ChannelProxy::Create(
+ std::unique_ptr<ChannelFactory> factory,
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& listener_task_runner) {
+ std::unique_ptr<ChannelProxy> channel(
+ new ChannelProxy(listener, ipc_task_runner, listener_task_runner));
+ channel->Init(std::move(factory), true);
+ return channel;
+}
+
+ChannelProxy::ChannelProxy(Context* context)
+ : context_(context), did_init_(false) {
+#if defined(ENABLE_IPC_FUZZER)
+ outgoing_message_filter_ = NULL;
+#endif
+}
+
+ChannelProxy::ChannelProxy(
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& listener_task_runner)
+ : context_(new Context(listener, ipc_task_runner, listener_task_runner)),
+ did_init_(false) {
+#if defined(ENABLE_IPC_FUZZER)
+ outgoing_message_filter_ = NULL;
+#endif
+}
+
+ChannelProxy::~ChannelProxy() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ Close();
+}
+
+void ChannelProxy::Init(const IPC::ChannelHandle& channel_handle,
+ Channel::Mode mode,
+ bool create_pipe_now) {
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ // When we are creating a server on POSIX, we need its file descriptor
+ // to be created immediately so that it can be accessed and passed
+ // to other processes. Forcing it to be created immediately avoids
+ // race conditions that may otherwise arise.
+ if (mode & Channel::MODE_SERVER_FLAG) {
+ create_pipe_now = true;
+ }
+#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
+ Init(
+ ChannelFactory::Create(channel_handle, mode, context_->ipc_task_runner()),
+ create_pipe_now);
+}
+
+void ChannelProxy::Init(std::unique_ptr<ChannelFactory> factory,
+ bool create_pipe_now) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!did_init_);
+
+ if (create_pipe_now) {
+ // Create the channel immediately. This effectively sets up the
+ // low-level pipe so that the client can connect. Without creating
+ // the pipe immediately, it is possible for a listener to attempt
+ // to connect and get an error since the pipe doesn't exist yet.
+ context_->CreateChannel(std::move(factory));
+ } else {
+ context_->ipc_task_runner()->PostTask(
+ FROM_HERE, base::Bind(&Context::CreateChannel, context_,
+ base::Passed(&factory)));
+ }
+
+ // complete initialization on the background thread
+ context_->ipc_task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&Context::OnChannelOpened, context_));
+
+ did_init_ = true;
+ OnChannelInit();
+}
+
+void ChannelProxy::Pause() {
+ context_->ipc_task_runner()->PostTask(
+ FROM_HERE, base::Bind(&Context::PauseChannel, context_));
+}
+
+void ChannelProxy::Unpause(bool flush) {
+ context_->ipc_task_runner()->PostTask(
+ FROM_HERE, base::Bind(&Context::UnpauseChannel, context_, flush));
+}
+
+void ChannelProxy::Flush() {
+ context_->ipc_task_runner()->PostTask(
+ FROM_HERE, base::Bind(&Context::FlushChannel, context_));
+}
+
+void ChannelProxy::Close() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Clear the backpointer to the listener so that any pending calls to
+ // Context::OnDispatchMessage or OnDispatchError will be ignored. It is
+ // possible that the channel could be closed while it is receiving messages!
+ context_->Clear();
+
+ if (context_->ipc_task_runner()) {
+ context_->ipc_task_runner()->PostTask(
+ FROM_HERE, base::Bind(&Context::OnChannelClosed, context_));
+ }
+}
+
+bool ChannelProxy::Send(Message* message) {
+ DCHECK(!message->is_sync()) << "Need to use IPC::SyncChannel";
+ SendInternal(message);
+ return true;
+}
+
+void ChannelProxy::SendInternal(Message* message) {
+ DCHECK(did_init_);
+
+ // TODO(alexeypa): add DCHECK(CalledOnValidThread()) here. Currently there are
+ // tests that call Send() from a wrong thread. See http://crbug.com/163523.
+
+#ifdef ENABLE_IPC_FUZZER
+ // In IPC fuzzing builds, it is possible to define a filter to apply to
+ // outgoing messages. It will either rewrite the message and return a new
+ // one, freeing the original, or return the message unchanged.
+ if (outgoing_message_filter())
+ message = outgoing_message_filter()->Rewrite(message);
+#endif
+
+#if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
+ Logging::GetInstance()->OnSendMessage(message);
+#endif
+
+ // See https://crbug.com/766032. This is to ensure that senders of oversized
+ // messages can be caught more easily in the wild.
+ CHECK_LE(message->size(), Channel::kMaximumMessageSize);
+
+ context_->Send(message);
+}
+
+void ChannelProxy::AddFilter(MessageFilter* filter) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ context_->AddFilter(filter);
+}
+
+void ChannelProxy::RemoveFilter(MessageFilter* filter) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ context_->ipc_task_runner()->PostTask(
+ FROM_HERE, base::Bind(&Context::OnRemoveFilter, context_,
+ base::RetainedRef(filter)));
+}
+
+void ChannelProxy::AddGenericAssociatedInterfaceForIOThread(
+ const std::string& name,
+ const GenericAssociatedInterfaceFactory& factory) {
+ context()->AddGenericAssociatedInterfaceForIOThread(name, factory);
+}
+
+void ChannelProxy::GetGenericRemoteAssociatedInterface(
+ const std::string& name,
+ mojo::ScopedInterfaceEndpointHandle handle) {
+ DCHECK(did_init_);
+ context()->thread_safe_channel().GetAssociatedInterface(
+ name, mojom::GenericInterfaceAssociatedRequest(std::move(handle)));
+}
+
+void ChannelProxy::ClearIPCTaskRunner() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ context()->ClearIPCTaskRunner();
+}
+
+void ChannelProxy::OnChannelInit() {
+}
+
+//-----------------------------------------------------------------------------
+
+} // namespace IPC
diff --git a/ipc/ipc_channel_proxy.h b/ipc/ipc_channel_proxy.h
new file mode 100644
index 0000000000..7ac409c6c0
--- /dev/null
+++ b/ipc/ipc_channel_proxy.h
@@ -0,0 +1,425 @@
+// 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_PROXY_H_
+#define IPC_IPC_CHANNEL_PROXY_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/component_export.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequence_checker.h"
+#include "base/synchronization/lock.h"
+#include "build/build_config.h"
+#include "ipc/ipc_channel.h"
+#include "ipc/ipc_channel_handle.h"
+#include "ipc/ipc_listener.h"
+#include "ipc/ipc_sender.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
+#include "mojo/public/cpp/bindings/associated_interface_request.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+#include "mojo/public/cpp/bindings/thread_safe_interface_ptr.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace IPC {
+
+class ChannelFactory;
+class MessageFilter;
+class MessageFilterRouter;
+
+//-----------------------------------------------------------------------------
+// IPC::ChannelProxy
+//
+// This class is a helper class that is useful when you wish to run an IPC
+// channel on a background thread. It provides you with the option of either
+// handling IPC messages on that background thread or having them dispatched to
+// your main thread (the thread on which the IPC::ChannelProxy is created).
+//
+// The API for an IPC::ChannelProxy is very similar to that of an IPC::Channel.
+// When you send a message to an IPC::ChannelProxy, the message is routed to
+// the background thread, where it is then passed to the IPC::Channel's Send
+// method. This means that you can send a message from your thread and your
+// message will be sent over the IPC channel when possible instead of being
+// delayed until your thread returns to its message loop. (Often IPC messages
+// will queue up on the IPC::Channel when there is a lot of traffic, and the
+// channel will not get cycles to flush its message queue until the thread, on
+// which it is running, returns to its message loop.)
+//
+// An IPC::ChannelProxy can have a MessageFilter associated with it, which will
+// be notified of incoming messages on the IPC::Channel's thread. This gives
+// the consumer of IPC::ChannelProxy the ability to respond to incoming
+// messages on this background thread instead of on their own thread, which may
+// be bogged down with other processing. The result can be greatly improved
+// latency for messages that can be handled on a background thread.
+//
+// The consumer of IPC::ChannelProxy is responsible for allocating the Thread
+// instance where the IPC::Channel will be created and operated.
+//
+// Thread-safe send
+//
+// If a particular |Channel| implementation has a thread-safe |Send()| operation
+// then ChannelProxy skips the inter-thread hop and calls |Send()| directly. In
+// this case the |channel_| variable is touched by multiple threads so
+// |channel_lifetime_lock_| is used to protect it. The locking overhead is only
+// paid if the underlying channel supports thread-safe |Send|.
+//
+class COMPONENT_EXPORT(IPC) ChannelProxy : public Sender {
+ public:
+#if defined(ENABLE_IPC_FUZZER)
+ // Interface for a filter to be imposed on outgoing messages which can
+ // re-write the message. Used for testing.
+ class OutgoingMessageFilter {
+ public:
+ virtual Message* Rewrite(Message* message) = 0;
+ };
+#endif
+
+ // Initializes a channel proxy. The channel_handle and mode parameters are
+ // passed directly to the underlying IPC::Channel. The listener is called on
+ // the thread that creates the ChannelProxy. The filter's OnMessageReceived
+ // method is called on the thread where the IPC::Channel is running. The
+ // filter may be null if the consumer is not interested in handling messages
+ // on the background thread. Any message not handled by the filter will be
+ // dispatched to the listener. The given task runner correspond to a thread
+ // on which IPC::Channel is created and used (e.g. IO thread).
+ static std::unique_ptr<ChannelProxy> Create(
+ const IPC::ChannelHandle& channel_handle,
+ Channel::Mode mode,
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& listener_task_runner);
+
+ static std::unique_ptr<ChannelProxy> Create(
+ std::unique_ptr<ChannelFactory> factory,
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& listener_task_runner);
+
+ // Constructs a ChannelProxy without initializing it.
+ ChannelProxy(
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& listener_task_runner);
+
+ ~ChannelProxy() override;
+
+ // Initializes the channel proxy. Only call this once to initialize a channel
+ // proxy that was not initialized in its constructor. If |create_pipe_now| is
+ // true, the pipe is created synchronously. Otherwise it's created on the IO
+ // thread.
+ void Init(const IPC::ChannelHandle& channel_handle,
+ Channel::Mode mode,
+ bool create_pipe_now);
+ void Init(std::unique_ptr<ChannelFactory> factory,
+ bool create_pipe_now);
+
+ // Pause the channel. Subsequent calls to Send() will be internally queued
+ // until Unpause() is called. Queued messages will not be sent until the
+ // channel is flushed.
+ void Pause();
+
+ // Unpause the channel. If |flush| is true the channel will be flushed as soon
+ // as it's unpaused (see Flush() below.) Otherwise you must explicitly call
+ // Flush() to flush messages which were queued while the channel was paused.
+ void Unpause(bool flush);
+
+ // Flush the channel. This sends any messages which were queued before calling
+ // Connect. Only useful if Unpause(false) was called previously.
+ void Flush();
+
+ // Close the IPC::Channel. This operation completes asynchronously, once the
+ // background thread processes the command to close the channel. It is ok to
+ // call this method multiple times. Redundant calls are ignored.
+ //
+ // WARNING: MessageFilter objects held by the ChannelProxy is also
+ // released asynchronously, and it may in fact have its final reference
+ // released on the background thread. The caller should be careful to deal
+ // with / allow for this possibility.
+ void Close();
+
+ // Send a message asynchronously. The message is routed to the background
+ // thread where it is passed to the IPC::Channel's Send method.
+ bool Send(Message* message) override;
+
+ // Used to intercept messages as they are received on the background thread.
+ //
+ // Ordinarily, messages sent to the ChannelProxy are routed to the matching
+ // listener on the worker thread. This API allows code to intercept messages
+ // before they are sent to the worker thread.
+ // If you call this before the target process is launched, then you're
+ // guaranteed to not miss any messages. But if you call this anytime after,
+ // then some messages might be missed since the filter is added internally on
+ // the IO thread.
+ void AddFilter(MessageFilter* filter);
+ void RemoveFilter(MessageFilter* filter);
+
+ using GenericAssociatedInterfaceFactory =
+ base::Callback<void(mojo::ScopedInterfaceEndpointHandle)>;
+
+ // Adds a generic associated interface factory to bind incoming interface
+ // requests directly on the IO thread. MUST be called either before Init() or
+ // before the remote end of the Channel is able to send messages (e.g. before
+ // its process is launched.)
+ void AddGenericAssociatedInterfaceForIOThread(
+ const std::string& name,
+ const GenericAssociatedInterfaceFactory& factory);
+
+ template <typename Interface>
+ using AssociatedInterfaceFactory =
+ base::Callback<void(mojo::AssociatedInterfaceRequest<Interface>)>;
+
+ // Helper to bind an IO-thread associated interface factory, inferring the
+ // interface name from the callback argument's type. MUST be called before
+ // Init().
+ template <typename Interface>
+ void AddAssociatedInterfaceForIOThread(
+ const AssociatedInterfaceFactory<Interface>& factory) {
+ AddGenericAssociatedInterfaceForIOThread(
+ Interface::Name_,
+ base::Bind(&ChannelProxy::BindAssociatedInterfaceRequest<Interface>,
+ factory));
+ }
+
+ // Requests an associated interface from the remote endpoint.
+ void GetGenericRemoteAssociatedInterface(
+ const std::string& name,
+ mojo::ScopedInterfaceEndpointHandle handle);
+
+ // Template helper to request associated interfaces from the remote endpoint.
+ template <typename Interface>
+ void GetRemoteAssociatedInterface(
+ mojo::AssociatedInterfacePtr<Interface>* proxy) {
+ auto request = mojo::MakeRequest(proxy);
+ GetGenericRemoteAssociatedInterface(Interface::Name_, request.PassHandle());
+ }
+
+#if defined(ENABLE_IPC_FUZZER)
+ void set_outgoing_message_filter(OutgoingMessageFilter* filter) {
+ outgoing_message_filter_ = filter;
+ }
+#endif
+
+ // Creates a ThreadSafeAssociatedInterfacePtr for |Interface|. This object
+ // may be used to send messages on the interface from any thread and those
+ // messages will remain ordered with respect to other messages sent on the
+ // same thread over other ThreadSafeAssociatedInterfacePtrs associated with
+ // the same Channel.
+ template <typename Interface>
+ void GetThreadSafeRemoteAssociatedInterface(
+ scoped_refptr<mojo::ThreadSafeAssociatedInterfacePtr<Interface>>*
+ out_ptr) {
+ mojo::AssociatedInterfacePtrInfo<Interface> ptr_info;
+ auto request = mojo::MakeRequest(&ptr_info);
+ GetGenericRemoteAssociatedInterface(Interface::Name_, request.PassHandle());
+ *out_ptr = mojo::ThreadSafeAssociatedInterfacePtr<Interface>::Create(
+ std::move(ptr_info), ipc_task_runner());
+ }
+
+ base::SingleThreadTaskRunner* ipc_task_runner() const {
+ return context_->ipc_task_runner();
+ }
+
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner_refptr()
+ const {
+ return context_->ipc_task_runner_refptr();
+ }
+
+ // Called to clear the pointer to the IPC task runner when it's going away.
+ void ClearIPCTaskRunner();
+
+ protected:
+ class Context;
+ // A subclass uses this constructor if it needs to add more information
+ // to the internal state.
+ explicit ChannelProxy(Context* context);
+
+ // Used internally to hold state that is referenced on the IPC thread.
+ class Context : public base::RefCountedThreadSafe<Context>,
+ public Listener {
+ public:
+ Context(Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>&
+ listener_task_runner);
+ void ClearIPCTaskRunner();
+ base::SingleThreadTaskRunner* ipc_task_runner() const {
+ return ipc_task_runner_.get();
+ }
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner_refptr()
+ const {
+ return ipc_task_runner_;
+ }
+
+ scoped_refptr<base::SingleThreadTaskRunner> listener_task_runner() {
+ return listener_task_runner_;
+ }
+
+ // Dispatches a message on the listener thread.
+ void OnDispatchMessage(const Message& message);
+
+ // Sends |message| from appropriate thread.
+ void Send(Message* message);
+
+ protected:
+ friend class base::RefCountedThreadSafe<Context>;
+ ~Context() override;
+
+ // IPC::Listener methods:
+ bool OnMessageReceived(const Message& message) override;
+ void OnChannelConnected(int32_t peer_pid) override;
+ void OnChannelError() override;
+ void OnAssociatedInterfaceRequest(
+ const std::string& interface_name,
+ mojo::ScopedInterfaceEndpointHandle handle) override;
+
+ // Like OnMessageReceived but doesn't try the filters.
+ bool OnMessageReceivedNoFilter(const Message& message);
+
+ // Gives the filters a chance at processing |message|.
+ // Returns true if the message was processed, false otherwise.
+ bool TryFilters(const Message& message);
+
+ void PauseChannel();
+ void UnpauseChannel(bool flush);
+ void FlushChannel();
+
+ // Like Open and Close, but called on the IPC thread.
+ virtual void OnChannelOpened();
+ virtual void OnChannelClosed();
+
+ // Called on the consumers thread when the ChannelProxy is closed. At that
+ // point the consumer is telling us that they don't want to receive any
+ // more messages, so we honor that wish by forgetting them!
+ virtual void Clear();
+
+ private:
+ friend class ChannelProxy;
+ friend class IpcSecurityTestUtil;
+
+ // Create the Channel
+ void CreateChannel(std::unique_ptr<ChannelFactory> factory);
+
+ // Methods called on the IO thread.
+ void OnSendMessage(std::unique_ptr<Message> message_ptr);
+ void OnAddFilter();
+ void OnRemoveFilter(MessageFilter* filter);
+
+ // Methods called on the listener thread.
+ void AddFilter(MessageFilter* filter);
+ void OnDispatchConnected();
+ void OnDispatchError();
+ void OnDispatchBadMessage(const Message& message);
+ void OnDispatchAssociatedInterfaceRequest(
+ const std::string& interface_name,
+ mojo::ScopedInterfaceEndpointHandle handle);
+
+ void ClearChannel();
+
+ mojom::Channel& thread_safe_channel() {
+ return thread_safe_channel_->proxy();
+ }
+
+ void AddGenericAssociatedInterfaceForIOThread(
+ const std::string& name,
+ const GenericAssociatedInterfaceFactory& factory);
+
+ scoped_refptr<base::SingleThreadTaskRunner> listener_task_runner_;
+ Listener* listener_;
+
+ // List of filters. This is only accessed on the IPC thread.
+ std::vector<scoped_refptr<MessageFilter> > filters_;
+ scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner_;
+
+ // Note, channel_ may be set on the Listener thread or the IPC thread.
+ // But once it has been set, it must only be read or cleared on the IPC
+ // thread.
+ // One exception is the thread-safe send. See the class comment.
+ std::unique_ptr<Channel> channel_;
+ bool channel_connected_called_;
+
+ // Lock for |channel_| value. This is only relevant in the context of
+ // thread-safe send.
+ base::Lock channel_lifetime_lock_;
+
+ // Routes a given message to a proper subset of |filters_|, depending
+ // on which message classes a filter might support.
+ std::unique_ptr<MessageFilterRouter> message_filter_router_;
+
+ // Holds filters between the AddFilter call on the listerner thread and the
+ // IPC thread when they're added to filters_.
+ std::vector<scoped_refptr<MessageFilter> > pending_filters_;
+ // Lock for pending_filters_.
+ base::Lock pending_filters_lock_;
+
+ // Cached copy of the peer process ID. Set on IPC but read on both IPC and
+ // listener threads.
+ base::ProcessId peer_pid_;
+ base::Lock peer_pid_lock_;
+
+ // A thread-safe mojom::Channel interface we use to make remote interface
+ // requests from the proxy thread.
+ std::unique_ptr<mojo::ThreadSafeForwarder<mojom::Channel>>
+ thread_safe_channel_;
+
+ // Holds associated interface binders added by
+ // AddGenericAssociatedInterfaceForIOThread until the underlying channel has
+ // been initialized.
+ base::Lock pending_io_thread_interfaces_lock_;
+ std::vector<std::pair<std::string, GenericAssociatedInterfaceFactory>>
+ pending_io_thread_interfaces_;
+ };
+
+ Context* context() { return context_.get(); }
+
+#if defined(ENABLE_IPC_FUZZER)
+ OutgoingMessageFilter* outgoing_message_filter() const {
+ return outgoing_message_filter_;
+ }
+#endif
+
+ bool did_init() const { return did_init_; }
+
+ // A Send() which doesn't DCHECK if the message is synchronous.
+ void SendInternal(Message* message);
+
+ private:
+ friend class IpcSecurityTestUtil;
+
+ template <typename Interface>
+ static void BindAssociatedInterfaceRequest(
+ const AssociatedInterfaceFactory<Interface>& factory,
+ mojo::ScopedInterfaceEndpointHandle handle) {
+ factory.Run(mojo::AssociatedInterfaceRequest<Interface>(std::move(handle)));
+ }
+
+ // Always called once immediately after Init.
+ virtual void OnChannelInit();
+
+ // By maintaining this indirection (ref-counted) to our internal state, we
+ // can safely be destroyed while the background thread continues to do stuff
+ // that involves this data.
+ scoped_refptr<Context> context_;
+
+ // Whether the channel has been initialized.
+ bool did_init_;
+
+#if defined(ENABLE_IPC_FUZZER)
+ OutgoingMessageFilter* outgoing_message_filter_;
+#endif
+
+ SEQUENCE_CHECKER(sequence_checker_);
+};
+
+} // namespace IPC
+
+#endif // IPC_IPC_CHANNEL_PROXY_H_
diff --git a/ipc/ipc_channel_proxy_unittest.cc b/ipc/ipc_channel_proxy_unittest.cc
new file mode 100644
index 0000000000..2bf18d440f
--- /dev/null
+++ b/ipc/ipc_channel_proxy_unittest.cc
@@ -0,0 +1,437 @@
+// 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 "build/build_config.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <memory>
+
+#include "base/message_loop/message_loop.h"
+#include "base/pickle.h"
+#include "base/run_loop.h"
+#include "base/threading/thread.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_test_base.h"
+#include "ipc/message_filter.h"
+
+// Get basic type definitions.
+#define IPC_MESSAGE_IMPL
+#include "ipc/ipc_channel_proxy_unittest_messages.h"
+
+// Generate constructors.
+#include "ipc/struct_constructor_macros.h"
+#include "ipc/ipc_channel_proxy_unittest_messages.h"
+
+// Generate destructors.
+#include "ipc/struct_destructor_macros.h"
+#include "ipc/ipc_channel_proxy_unittest_messages.h"
+
+// Generate param traits write methods.
+#include "ipc/param_traits_write_macros.h"
+namespace IPC {
+#include "ipc/ipc_channel_proxy_unittest_messages.h"
+} // namespace IPC
+
+// Generate param traits read methods.
+#include "ipc/param_traits_read_macros.h"
+namespace IPC {
+#include "ipc/ipc_channel_proxy_unittest_messages.h"
+} // namespace IPC
+
+// Generate param traits log methods.
+#include "ipc/param_traits_log_macros.h"
+namespace IPC {
+#include "ipc/ipc_channel_proxy_unittest_messages.h"
+} // namespace IPC
+
+
+namespace {
+
+void CreateRunLoopAndRun(base::RunLoop** run_loop_ptr) {
+ base::RunLoop run_loop;
+ *run_loop_ptr = &run_loop;
+ run_loop.Run();
+ *run_loop_ptr = nullptr;
+}
+
+class QuitListener : public IPC::Listener {
+ public:
+ QuitListener() = default;
+
+ bool OnMessageReceived(const IPC::Message& message) override {
+ IPC_BEGIN_MESSAGE_MAP(QuitListener, message)
+ IPC_MESSAGE_HANDLER(WorkerMsg_Quit, OnQuit)
+ IPC_MESSAGE_HANDLER(TestMsg_BadMessage, OnBadMessage)
+ IPC_END_MESSAGE_MAP()
+ return true;
+ }
+
+ void OnBadMessageReceived(const IPC::Message& message) override {
+ bad_message_received_ = true;
+ }
+
+ void OnChannelError() override { CHECK(quit_message_received_); }
+
+ void OnQuit() {
+ quit_message_received_ = true;
+ run_loop_->QuitWhenIdle();
+ }
+
+ void OnBadMessage(const BadType& bad_type) {
+ // Should never be called since IPC wouldn't be deserialized correctly.
+ CHECK(false);
+ }
+
+ bool bad_message_received_ = false;
+ bool quit_message_received_ = false;
+ base::RunLoop* run_loop_ = nullptr;
+};
+
+class ChannelReflectorListener : public IPC::Listener {
+ public:
+ ChannelReflectorListener() = default;
+
+ void Init(IPC::Channel* channel) {
+ DCHECK(!channel_);
+ channel_ = channel;
+ }
+
+ bool OnMessageReceived(const IPC::Message& message) override {
+ IPC_BEGIN_MESSAGE_MAP(ChannelReflectorListener, message)
+ IPC_MESSAGE_HANDLER(TestMsg_Bounce, OnTestBounce)
+ IPC_MESSAGE_HANDLER(TestMsg_SendBadMessage, OnSendBadMessage)
+ IPC_MESSAGE_HANDLER(AutomationMsg_Bounce, OnAutomationBounce)
+ IPC_MESSAGE_HANDLER(WorkerMsg_Bounce, OnBounce)
+ IPC_MESSAGE_HANDLER(WorkerMsg_Quit, OnQuit)
+ IPC_END_MESSAGE_MAP()
+ return true;
+ }
+
+ void OnTestBounce() {
+ channel_->Send(new TestMsg_Bounce());
+ }
+
+ void OnSendBadMessage() {
+ channel_->Send(new TestMsg_BadMessage(BadType()));
+ }
+
+ void OnAutomationBounce() { channel_->Send(new AutomationMsg_Bounce()); }
+
+ void OnBounce() {
+ channel_->Send(new WorkerMsg_Bounce());
+ }
+
+ void OnQuit() {
+ channel_->Send(new WorkerMsg_Quit());
+ run_loop_->QuitWhenIdle();
+ }
+
+ base::RunLoop* run_loop_ = nullptr;
+
+ private:
+ IPC::Channel* channel_ = nullptr;
+};
+
+class MessageCountFilter : public IPC::MessageFilter {
+ public:
+ enum FilterEvent {
+ NONE,
+ FILTER_ADDED,
+ CHANNEL_CONNECTED,
+ CHANNEL_ERROR,
+ CHANNEL_CLOSING,
+ FILTER_REMOVED
+ };
+
+ MessageCountFilter() = default;
+ MessageCountFilter(uint32_t supported_message_class)
+ : supported_message_class_(supported_message_class),
+ is_global_filter_(false) {}
+
+ void OnFilterAdded(IPC::Channel* channel) override {
+ EXPECT_TRUE(channel);
+ EXPECT_EQ(NONE, last_filter_event_);
+ last_filter_event_ = FILTER_ADDED;
+ }
+
+ void OnChannelConnected(int32_t peer_pid) override {
+ EXPECT_EQ(FILTER_ADDED, last_filter_event_);
+ EXPECT_NE(static_cast<int32_t>(base::kNullProcessId), peer_pid);
+ last_filter_event_ = CHANNEL_CONNECTED;
+ }
+
+ void OnChannelError() override {
+ EXPECT_EQ(CHANNEL_CONNECTED, last_filter_event_);
+ last_filter_event_ = CHANNEL_ERROR;
+ }
+
+ void OnChannelClosing() override {
+ // We may or may not have gotten OnChannelError; if not, the last event has
+ // to be OnChannelConnected.
+ EXPECT_NE(FILTER_REMOVED, last_filter_event_);
+ if (last_filter_event_ != CHANNEL_ERROR)
+ EXPECT_EQ(CHANNEL_CONNECTED, last_filter_event_);
+ last_filter_event_ = CHANNEL_CLOSING;
+ }
+
+ void OnFilterRemoved() override {
+ // A filter may be removed at any time, even before the channel is connected
+ // (and thus before OnFilterAdded is ever able to dispatch.) The only time
+ // we won't see OnFilterRemoved is immediately after OnFilterAdded, because
+ // OnChannelConnected is always the next event to fire after that.
+ EXPECT_NE(FILTER_ADDED, last_filter_event_);
+ last_filter_event_ = FILTER_REMOVED;
+ }
+
+ bool OnMessageReceived(const IPC::Message& message) override {
+ // We should always get the OnFilterAdded and OnChannelConnected events
+ // prior to any messages.
+ EXPECT_EQ(CHANNEL_CONNECTED, last_filter_event_);
+
+ if (!is_global_filter_) {
+ EXPECT_EQ(supported_message_class_, IPC_MESSAGE_CLASS(message));
+ }
+ ++messages_received_;
+
+ if (!message_filtering_enabled_)
+ return false;
+
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(MessageCountFilter, message)
+ IPC_MESSAGE_HANDLER(TestMsg_BadMessage, OnBadMessage)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+ }
+
+ void OnBadMessage(const BadType& bad_type) {
+ // Should never be called since IPC wouldn't be deserialized correctly.
+ CHECK(false);
+ }
+
+ bool GetSupportedMessageClasses(
+ std::vector<uint32_t>* supported_message_classes) const override {
+ if (is_global_filter_)
+ return false;
+ supported_message_classes->push_back(supported_message_class_);
+ return true;
+ }
+
+ void set_message_filtering_enabled(bool enabled) {
+ message_filtering_enabled_ = enabled;
+ }
+
+ size_t messages_received() const { return messages_received_; }
+ FilterEvent last_filter_event() const { return last_filter_event_; }
+
+ private:
+ ~MessageCountFilter() override = default;
+
+ size_t messages_received_ = 0;
+ uint32_t supported_message_class_ = 0;
+ bool is_global_filter_ = true;
+
+ FilterEvent last_filter_event_ = NONE;
+ bool message_filtering_enabled_ = false;
+};
+
+class IPCChannelProxyTest : public IPCChannelMojoTestBase {
+ public:
+ IPCChannelProxyTest() = default;
+ ~IPCChannelProxyTest() override = default;
+
+ void SetUp() override {
+ IPCChannelMojoTestBase::SetUp();
+
+ Init("ChannelProxyClient");
+
+ thread_.reset(new base::Thread("ChannelProxyTestServerThread"));
+ base::Thread::Options options;
+ options.message_loop_type = base::MessageLoop::TYPE_IO;
+ thread_->StartWithOptions(options);
+
+ listener_.reset(new QuitListener());
+ channel_proxy_ = IPC::ChannelProxy::Create(
+ TakeHandle().release(), IPC::Channel::MODE_SERVER, listener_.get(),
+ thread_->task_runner(), base::ThreadTaskRunnerHandle::Get());
+ }
+
+ void TearDown() override {
+ channel_proxy_.reset();
+ thread_.reset();
+ listener_.reset();
+ IPCChannelMojoTestBase::TearDown();
+ }
+
+ void SendQuitMessageAndWaitForIdle() {
+ sender()->Send(new WorkerMsg_Quit);
+ CreateRunLoopAndRun(&listener_->run_loop_);
+ EXPECT_TRUE(WaitForClientShutdown());
+ }
+
+ bool DidListenerGetBadMessage() {
+ return listener_->bad_message_received_;
+ }
+
+ IPC::ChannelProxy* channel_proxy() { return channel_proxy_.get(); }
+ IPC::Sender* sender() { return channel_proxy_.get(); }
+
+ private:
+ std::unique_ptr<base::Thread> thread_;
+ std::unique_ptr<QuitListener> listener_;
+ std::unique_ptr<IPC::ChannelProxy> channel_proxy_;
+};
+
+TEST_F(IPCChannelProxyTest, MessageClassFilters) {
+ // Construct a filter per message class.
+ std::vector<scoped_refptr<MessageCountFilter>> class_filters;
+ class_filters.push_back(
+ base::MakeRefCounted<MessageCountFilter>(TestMsgStart));
+ class_filters.push_back(
+ base::MakeRefCounted<MessageCountFilter>(AutomationMsgStart));
+ for (size_t i = 0; i < class_filters.size(); ++i)
+ channel_proxy()->AddFilter(class_filters[i].get());
+
+ // Send a message for each class; each filter should receive just one message.
+ sender()->Send(new TestMsg_Bounce);
+ sender()->Send(new AutomationMsg_Bounce);
+
+ // Send some messages not assigned to a specific or valid message class.
+ sender()->Send(new WorkerMsg_Bounce);
+
+ // Each filter should have received just the one sent message of the
+ // corresponding class.
+ SendQuitMessageAndWaitForIdle();
+ for (size_t i = 0; i < class_filters.size(); ++i)
+ EXPECT_EQ(1U, class_filters[i]->messages_received());
+}
+
+TEST_F(IPCChannelProxyTest, GlobalAndMessageClassFilters) {
+ // Add a class and global filter.
+ scoped_refptr<MessageCountFilter> class_filter(
+ new MessageCountFilter(TestMsgStart));
+ class_filter->set_message_filtering_enabled(false);
+ channel_proxy()->AddFilter(class_filter.get());
+
+ scoped_refptr<MessageCountFilter> global_filter(new MessageCountFilter());
+ global_filter->set_message_filtering_enabled(false);
+ channel_proxy()->AddFilter(global_filter.get());
+
+ // A message of class Test should be seen by both the global filter and
+ // Test-specific filter.
+ sender()->Send(new TestMsg_Bounce);
+
+ // A message of a different class should be seen only by the global filter.
+ sender()->Send(new AutomationMsg_Bounce);
+
+ // Flush all messages.
+ SendQuitMessageAndWaitForIdle();
+
+ // The class filter should have received only the class-specific message.
+ EXPECT_EQ(1U, class_filter->messages_received());
+
+ // The global filter should have received both messages, as well as the final
+ // QUIT message.
+ EXPECT_EQ(3U, global_filter->messages_received());
+}
+
+TEST_F(IPCChannelProxyTest, FilterRemoval) {
+ // Add a class and global filter.
+ scoped_refptr<MessageCountFilter> class_filter(
+ new MessageCountFilter(TestMsgStart));
+ scoped_refptr<MessageCountFilter> global_filter(new MessageCountFilter());
+
+ // Add and remove both types of filters.
+ channel_proxy()->AddFilter(class_filter.get());
+ channel_proxy()->AddFilter(global_filter.get());
+ channel_proxy()->RemoveFilter(global_filter.get());
+ channel_proxy()->RemoveFilter(class_filter.get());
+
+ // Send some messages; they should not be seen by either filter.
+ sender()->Send(new TestMsg_Bounce);
+ sender()->Send(new AutomationMsg_Bounce);
+
+ // Ensure that the filters were removed and did not receive any messages.
+ SendQuitMessageAndWaitForIdle();
+ EXPECT_EQ(MessageCountFilter::FILTER_REMOVED,
+ global_filter->last_filter_event());
+ EXPECT_EQ(MessageCountFilter::FILTER_REMOVED,
+ class_filter->last_filter_event());
+ EXPECT_EQ(0U, class_filter->messages_received());
+ EXPECT_EQ(0U, global_filter->messages_received());
+}
+
+TEST_F(IPCChannelProxyTest, BadMessageOnListenerThread) {
+ scoped_refptr<MessageCountFilter> class_filter(
+ new MessageCountFilter(TestMsgStart));
+ class_filter->set_message_filtering_enabled(false);
+ channel_proxy()->AddFilter(class_filter.get());
+
+ sender()->Send(new TestMsg_SendBadMessage());
+
+ SendQuitMessageAndWaitForIdle();
+ EXPECT_TRUE(DidListenerGetBadMessage());
+}
+
+TEST_F(IPCChannelProxyTest, BadMessageOnIPCThread) {
+ scoped_refptr<MessageCountFilter> class_filter(
+ new MessageCountFilter(TestMsgStart));
+ class_filter->set_message_filtering_enabled(true);
+ channel_proxy()->AddFilter(class_filter.get());
+
+ sender()->Send(new TestMsg_SendBadMessage());
+
+ SendQuitMessageAndWaitForIdle();
+ EXPECT_TRUE(DidListenerGetBadMessage());
+}
+
+class IPCChannelBadMessageTest : public IPCChannelMojoTestBase {
+ public:
+ void SetUp() override {
+ IPCChannelMojoTestBase::SetUp();
+
+ Init("ChannelProxyClient");
+
+ listener_.reset(new QuitListener());
+ CreateChannel(listener_.get());
+ ASSERT_TRUE(ConnectChannel());
+ }
+
+ void TearDown() override {
+ IPCChannelMojoTestBase::TearDown();
+ listener_.reset();
+ }
+
+ void SendQuitMessageAndWaitForIdle() {
+ sender()->Send(new WorkerMsg_Quit);
+ CreateRunLoopAndRun(&listener_->run_loop_);
+ EXPECT_TRUE(WaitForClientShutdown());
+ }
+
+ bool DidListenerGetBadMessage() {
+ return listener_->bad_message_received_;
+ }
+
+ private:
+ std::unique_ptr<QuitListener> listener_;
+};
+
+TEST_F(IPCChannelBadMessageTest, BadMessage) {
+ sender()->Send(new TestMsg_SendBadMessage());
+ SendQuitMessageAndWaitForIdle();
+ EXPECT_TRUE(DidListenerGetBadMessage());
+}
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(ChannelProxyClient) {
+ ChannelReflectorListener listener;
+ Connect(&listener);
+ listener.Init(channel());
+
+ CreateRunLoopAndRun(&listener.run_loop_);
+
+ Close();
+}
+
+} // namespace
diff --git a/ipc/ipc_channel_proxy_unittest_messages.h b/ipc/ipc_channel_proxy_unittest_messages.h
new file mode 100644
index 0000000000..ae4505982b
--- /dev/null
+++ b/ipc/ipc_channel_proxy_unittest_messages.h
@@ -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 "ipc/ipc_message_macros.h"
+
+// Singly-included section for enums and custom IPC traits.
+#ifndef IPC_CHANNEL_PROXY_UNITTEST_MESSAGES_H_
+#define IPC_CHANNEL_PROXY_UNITTEST_MESSAGES_H_
+
+class BadType {
+ public:
+ BadType() {}
+};
+
+namespace IPC {
+
+template <>
+struct ParamTraits<BadType> {
+ static void Write(base::Pickle* m, const BadType& p) {}
+ static bool Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ BadType* r) {
+ return false;
+ }
+ static void Log(const BadType& p, std::string* l) {}
+};
+
+}
+
+#endif // IPC_CHANNEL_PROXY_UNITTEST_MESSAGES_H_
+
+#undef IPC_MESSAGE_START
+#define IPC_MESSAGE_START TestMsgStart
+IPC_MESSAGE_CONTROL0(TestMsg_Bounce)
+IPC_MESSAGE_CONTROL0(TestMsg_SendBadMessage)
+IPC_MESSAGE_CONTROL1(TestMsg_BadMessage, BadType)
+
+#undef IPC_MESSAGE_START
+#define IPC_MESSAGE_START AutomationMsgStart
+IPC_MESSAGE_CONTROL0(AutomationMsg_Bounce)
+
+#undef IPC_MESSAGE_START
+#define IPC_MESSAGE_START WorkerMsgStart
+IPC_MESSAGE_CONTROL0(WorkerMsg_Bounce)
+IPC_MESSAGE_CONTROL0(WorkerMsg_Quit)
diff --git a/ipc/ipc_channel_reader.cc b/ipc/ipc_channel_reader.cc
new file mode 100644
index 0000000000..e4331afaee
--- /dev/null
+++ b/ipc/ipc_channel_reader.cc
@@ -0,0 +1,215 @@
+// 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_channel_reader.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "ipc/ipc_listener.h"
+#include "ipc/ipc_logging.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_attachment_set.h"
+#include "ipc/ipc_message_macros.h"
+
+namespace IPC {
+namespace internal {
+
+#if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
+
+namespace {
+std::string GetMessageText(const Message& message) {
+ std::string name;
+ Logging::GetInstance()->GetMessageText(
+ message.type(), &name, &message, nullptr);
+ return name;
+}
+} // namespace
+
+#define EMIT_TRACE_EVENT(message) \
+ TRACE_EVENT_WITH_FLOW1( \
+ "ipc,toplevel", "ChannelReader::DispatchInputData", \
+ (message).flags(), TRACE_EVENT_FLAG_FLOW_IN, "name", \
+ GetMessageText(message));
+#else
+#define EMIT_TRACE_EVENT(message) \
+ TRACE_EVENT_WITH_FLOW2("ipc,toplevel", "ChannelReader::DispatchInputData", \
+ (message).flags(), TRACE_EVENT_FLAG_FLOW_IN, "class", \
+ IPC_MESSAGE_ID_CLASS((message).type()), "line", \
+ IPC_MESSAGE_ID_LINE((message).type()));
+#endif // BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
+
+ChannelReader::ChannelReader(Listener* listener)
+ : listener_(listener),
+ max_input_buffer_size_(Channel::kMaximumReadBufferSize) {
+ memset(input_buf_, 0, sizeof(input_buf_));
+}
+
+ChannelReader::~ChannelReader() = default;
+
+ChannelReader::DispatchState ChannelReader::ProcessIncomingMessages() {
+ while (true) {
+ int bytes_read = 0;
+ ReadState read_state = ReadData(input_buf_, Channel::kReadBufferSize,
+ &bytes_read);
+ if (read_state == READ_FAILED)
+ return DISPATCH_ERROR;
+ if (read_state == READ_PENDING)
+ return DISPATCH_FINISHED;
+
+ DCHECK(bytes_read > 0);
+ if (!TranslateInputData(input_buf_, bytes_read))
+ return DISPATCH_ERROR;
+ }
+}
+
+ChannelReader::DispatchState ChannelReader::AsyncReadComplete(int bytes_read) {
+ if (!TranslateInputData(input_buf_, bytes_read))
+ return DISPATCH_ERROR;
+
+ return DISPATCH_FINISHED;
+}
+
+bool ChannelReader::IsInternalMessage(const Message& m) {
+ return m.routing_id() == MSG_ROUTING_NONE &&
+ m.type() >= Channel::CLOSE_FD_MESSAGE_TYPE &&
+ m.type() <= Channel::HELLO_MESSAGE_TYPE;
+}
+
+bool ChannelReader::IsHelloMessage(const Message& m) {
+ return m.routing_id() == MSG_ROUTING_NONE &&
+ m.type() == Channel::HELLO_MESSAGE_TYPE;
+}
+
+void ChannelReader::CleanUp() {
+}
+
+void ChannelReader::DispatchMessage(Message* m) {
+ EMIT_TRACE_EVENT(*m);
+ listener_->OnMessageReceived(*m);
+ HandleDispatchError(*m);
+}
+
+bool ChannelReader::TranslateInputData(const char* input_data,
+ int input_data_len) {
+ const char* p;
+ const char* end;
+
+ // Possibly combine with the overflow buffer to make a larger buffer.
+ if (input_overflow_buf_.empty()) {
+ p = input_data;
+ end = input_data + input_data_len;
+ } else {
+ if (!CheckMessageSize(input_overflow_buf_.size() + input_data_len))
+ return false;
+ input_overflow_buf_.append(input_data, input_data_len);
+ p = input_overflow_buf_.data();
+ end = p + input_overflow_buf_.size();
+ }
+
+ size_t next_message_size = 0;
+
+ // Dispatch all complete messages in the data buffer.
+ while (p < end) {
+ Message::NextMessageInfo info;
+ Message::FindNext(p, end, &info);
+ if (info.message_found) {
+ int pickle_len = static_cast<int>(info.pickle_end - p);
+ Message translated_message(p, pickle_len);
+
+ if (!HandleTranslatedMessage(&translated_message))
+ return false;
+
+ p = info.message_end;
+ } else {
+ // Last message is partial.
+ next_message_size = info.message_size;
+ if (!CheckMessageSize(next_message_size))
+ return false;
+ break;
+ }
+ }
+
+ // Account for the case where last message's byte is in the next data chunk.
+ size_t next_message_buffer_size = next_message_size ?
+ next_message_size + Channel::kReadBufferSize - 1:
+ 0;
+
+ // Save any partial data in the overflow buffer.
+ if (p != input_overflow_buf_.data())
+ input_overflow_buf_.assign(p, end - p);
+
+ if (!input_overflow_buf_.empty()) {
+ // We have something in the overflow buffer, which means that we will
+ // append the next data chunk (instead of parsing it directly). So we
+ // resize the buffer to fit the next message, to avoid repeatedly
+ // growing the buffer as we receive all message' data chunks.
+ if (next_message_buffer_size > input_overflow_buf_.capacity()) {
+ input_overflow_buf_.reserve(next_message_buffer_size);
+ }
+ }
+
+ // Trim the buffer if we can
+ if (next_message_buffer_size < max_input_buffer_size_ &&
+ input_overflow_buf_.size() < max_input_buffer_size_ &&
+ input_overflow_buf_.capacity() > max_input_buffer_size_) {
+ // std::string doesn't really have a method to shrink capacity to
+ // a specific value, so we have to swap with another string.
+ std::string trimmed_buf;
+ trimmed_buf.reserve(max_input_buffer_size_);
+ if (trimmed_buf.capacity() > max_input_buffer_size_) {
+ // Since we don't control how much space reserve() actually reserves,
+ // we have to go other way around and change the max size to avoid
+ // getting into the outer if() again.
+ max_input_buffer_size_ = trimmed_buf.capacity();
+ }
+ trimmed_buf.assign(input_overflow_buf_.data(),
+ input_overflow_buf_.size());
+ input_overflow_buf_.swap(trimmed_buf);
+ }
+
+ if (input_overflow_buf_.empty() && !DidEmptyInputBuffers())
+ return false;
+ return true;
+}
+
+bool ChannelReader::HandleTranslatedMessage(Message* translated_message) {
+ // Immediately handle internal messages.
+ if (IsInternalMessage(*translated_message)) {
+ EMIT_TRACE_EVENT(*translated_message);
+ HandleInternalMessage(*translated_message);
+ HandleDispatchError(*translated_message);
+ return true;
+ }
+
+ return HandleExternalMessage(translated_message);
+}
+
+bool ChannelReader::HandleExternalMessage(Message* external_message) {
+ if (!GetAttachments(external_message))
+ return false;
+
+ DispatchMessage(external_message);
+ return true;
+}
+
+void ChannelReader::HandleDispatchError(const Message& message) {
+ if (message.dispatch_error())
+ listener_->OnBadMessageReceived(message);
+}
+
+bool ChannelReader::CheckMessageSize(size_t size) {
+ if (size <= Channel::kMaximumMessageSize) {
+ return true;
+ }
+ input_overflow_buf_.clear();
+ LOG(ERROR) << "IPC message is too big: " << size;
+ return false;
+}
+
+} // namespace internal
+} // namespace IPC
diff --git a/ipc/ipc_channel_reader.h b/ipc/ipc_channel_reader.h
new file mode 100644
index 0000000000..49d20f2c34
--- /dev/null
+++ b/ipc/ipc_channel_reader.h
@@ -0,0 +1,167 @@
+// 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_READER_H_
+#define IPC_IPC_CHANNEL_READER_H_
+
+#include <stddef.h>
+
+#include <set>
+
+#include "base/component_export.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "ipc/ipc_channel.h"
+
+namespace IPC {
+namespace internal {
+
+// This class provides common pipe reading functionality for the
+// platform-specific IPC channel implementations.
+//
+// It does the common input buffer management and message dispatch, while the
+// platform-specific parts provide the pipe management through a virtual
+// interface implemented on a per-platform basis.
+//
+// Note that there is no "writer" corresponding to this because the code for
+// writing to the channel is much simpler and has very little common
+// functionality that would benefit from being factored out. If we add
+// something like that in the future, it would be more appropriate to add it
+// here (and rename appropriately) rather than writing a different class.
+class COMPONENT_EXPORT(IPC) ChannelReader {
+ public:
+ explicit ChannelReader(Listener* listener);
+ virtual ~ChannelReader();
+
+ void set_listener(Listener* listener) { listener_ = listener; }
+
+ // This type is returned by ProcessIncomingMessages to indicate the effect of
+ // the method.
+ enum DispatchState {
+ // All messages were successfully dispatched, or there were no messages to
+ // dispatch.
+ DISPATCH_FINISHED,
+ // There was a channel error.
+ DISPATCH_ERROR,
+ // Dispatching messages is blocked on receiving more information from the
+ // broker.
+ DISPATCH_WAITING_ON_BROKER,
+ };
+
+ // Call to process messages received from the IPC connection and dispatch
+ // them.
+ DispatchState ProcessIncomingMessages();
+
+ // Handles asynchronously read data.
+ //
+ // Optionally call this after returning READ_PENDING from ReadData to
+ // indicate that buffer was filled with the given number of bytes of
+ // data. See ReadData for more.
+ DispatchState AsyncReadComplete(int bytes_read);
+
+ // Returns true if the given message is internal to the IPC implementation,
+ // like the "hello" message sent on channel set-up.
+ bool IsInternalMessage(const Message& m);
+
+ // Returns true if the given message is an Hello message
+ // sent on channel set-up.
+ bool IsHelloMessage(const Message& m);
+
+ protected:
+ enum ReadState { READ_SUCCEEDED, READ_FAILED, READ_PENDING };
+
+ Listener* listener() const { return listener_; }
+
+ // Subclasses should call this method in their destructor to give this class a
+ // chance to clean up state that might be dependent on subclass members.
+ void CleanUp();
+
+ // Populates the given buffer with data from the pipe.
+ //
+ // Returns the state of the read. On READ_SUCCESS, the number of bytes
+ // read will be placed into |*bytes_read| (which can be less than the
+ // buffer size). On READ_FAILED, the channel will be closed.
+ //
+ // If the return value is READ_PENDING, it means that there was no data
+ // ready for reading. The implementation is then responsible for either
+ // calling AsyncReadComplete with the number of bytes read into the
+ // buffer, or ProcessIncomingMessages to try the read again (depending
+ // on whether the platform's async I/O is "try again" or "write
+ // asynchronously into your buffer").
+ virtual ReadState ReadData(char* buffer, int buffer_len, int* bytes_read) = 0;
+
+ // Loads the required file desciptors into the given message. Returns true
+ // on success. False means a fatal channel error.
+ //
+ // This will read from the input_fds_ and read more handles from the FD
+ // pipe if necessary.
+ virtual bool ShouldDispatchInputMessage(Message* msg) = 0;
+
+ // Overridden by subclasses to get attachments that are sent alongside the IPC
+ // channel.
+ // Returns true on success. False means a fatal channel error.
+ virtual bool GetAttachments(Message* msg) = 0;
+
+ // Performs post-dispatch checks. Called when all input buffers are empty,
+ // though there could be more data ready to be read from the OS.
+ virtual bool DidEmptyInputBuffers() = 0;
+
+ // Handles internal messages, like the hello message sent on channel startup.
+ virtual void HandleInternalMessage(const Message& msg) = 0;
+
+ // Exposed for testing purposes only.
+ virtual void DispatchMessage(Message* m);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ChannelReaderTest, AttachmentAlreadyBrokered);
+ FRIEND_TEST_ALL_PREFIXES(ChannelReaderTest, AttachmentNotYetBrokered);
+ FRIEND_TEST_ALL_PREFIXES(ChannelReaderTest, ResizeOverflowBuffer);
+ FRIEND_TEST_ALL_PREFIXES(ChannelReaderTest, InvalidMessageSize);
+ FRIEND_TEST_ALL_PREFIXES(ChannelReaderTest, TrimBuffer);
+
+ // Takes the data received from the IPC channel and translates it into
+ // Messages. Complete messages are passed to HandleTranslatedMessage().
+ // Returns |false| on unrecoverable error.
+ bool TranslateInputData(const char* input_data, int input_data_len);
+
+ // Internal messages and messages bound for the attachment broker are
+ // immediately dispatched. Other messages are passed to
+ // HandleExternalMessage().
+ // Returns |false| on unrecoverable error.
+ bool HandleTranslatedMessage(Message* translated_message);
+
+ // Populates the message with brokered and non-brokered attachments. If
+ // possible, the message is immediately dispatched. Otherwise, a deep copy of
+ // the message is added to |queued_messages_|. |blocked_ids_| are updated if
+ // necessary.
+ bool HandleExternalMessage(Message* external_message);
+
+ // If there was a dispatch error, informs |listener_|.
+ void HandleDispatchError(const Message& message);
+
+ // Checks that |size| is a valid message size. Has side effects if it's not.
+ bool CheckMessageSize(size_t size);
+
+ Listener* listener_;
+
+ // We read from the pipe into this buffer. Managed by DispatchInputData, do
+ // not access directly outside that function.
+ char input_buf_[Channel::kReadBufferSize];
+
+ // Large messages that span multiple pipe buffers, get built-up using
+ // this buffer.
+ std::string input_overflow_buf_;
+
+ // Maximum overflow buffer size, see Channel::kMaximumReadBufferSize.
+ // This is not a constant because we update it to reflect the reality
+ // of std::string::reserve() implementation.
+ size_t max_input_buffer_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChannelReader);
+};
+
+} // namespace internal
+} // namespace IPC
+
+#endif // IPC_IPC_CHANNEL_READER_H_
diff --git a/ipc/ipc_channel_reader_unittest.cc b/ipc/ipc_channel_reader_unittest.cc
new file mode 100644
index 0000000000..a3d80dc922
--- /dev/null
+++ b/ipc/ipc_channel_reader_unittest.cc
@@ -0,0 +1,249 @@
+// 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 "build/build_config.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+#include <memory>
+#include <set>
+
+#include "base/run_loop.h"
+#include "ipc/ipc_channel_reader.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace IPC {
+namespace internal {
+
+namespace {
+
+class MockChannelReader : public ChannelReader {
+ public:
+ MockChannelReader()
+ : ChannelReader(nullptr), last_dispatched_message_(nullptr) {}
+
+ ReadState ReadData(char* buffer, int buffer_len, int* bytes_read) override {
+ if (data_.empty())
+ return READ_PENDING;
+
+ size_t read_len = std::min(static_cast<size_t>(buffer_len), data_.size());
+ memcpy(buffer, data_.data(), read_len);
+ *bytes_read = static_cast<int>(read_len);
+ data_.erase(0, read_len);
+ return READ_SUCCEEDED;
+ }
+
+ bool ShouldDispatchInputMessage(Message* msg) override { return true; }
+
+ bool GetAttachments(Message* msg) override { return true; }
+
+ bool DidEmptyInputBuffers() override { return true; }
+
+ void HandleInternalMessage(const Message& msg) override {}
+
+ void DispatchMessage(Message* m) override { last_dispatched_message_ = m; }
+
+ Message* get_last_dispatched_message() { return last_dispatched_message_; }
+
+ void AppendData(const void* data, size_t size) {
+ data_.append(static_cast<const char*>(data), size);
+ }
+
+ void AppendMessageData(const Message& message) {
+ AppendData(message.data(), message.size());
+ }
+
+ private:
+ Message* last_dispatched_message_;
+ std::string data_;
+};
+
+class ExposedMessage: public Message {
+ public:
+ using Message::Header;
+ using Message::header;
+};
+
+// Payload that makes messages large
+const size_t LargePayloadSize = Channel::kMaximumReadBufferSize * 3 / 2;
+
+} // namespace
+
+// We can determine message size from its header (and hence resize the buffer)
+// only when attachment broker is not used, see IPC::Message::FindNext().
+
+TEST(ChannelReaderTest, ResizeOverflowBuffer) {
+ MockChannelReader reader;
+
+ ExposedMessage::Header header = {};
+
+ header.payload_size = 128 * 1024;
+ EXPECT_LT(reader.input_overflow_buf_.capacity(), header.payload_size);
+ EXPECT_TRUE(reader.TranslateInputData(
+ reinterpret_cast<const char*>(&header), sizeof(header)));
+
+ // Once message header is available we resize overflow buffer to
+ // fit the entire message.
+ EXPECT_GE(reader.input_overflow_buf_.capacity(), header.payload_size);
+}
+
+TEST(ChannelReaderTest, InvalidMessageSize) {
+ MockChannelReader reader;
+
+ ExposedMessage::Header header = {};
+
+ size_t capacity_before = reader.input_overflow_buf_.capacity();
+
+ // Message is slightly larger than maximum allowed size
+ header.payload_size = Channel::kMaximumMessageSize + 1;
+ EXPECT_FALSE(reader.TranslateInputData(
+ reinterpret_cast<const char*>(&header), sizeof(header)));
+ EXPECT_LE(reader.input_overflow_buf_.capacity(), capacity_before);
+
+ // Payload size is negative, overflow is detected by Pickle::PeekNext()
+ header.payload_size = static_cast<uint32_t>(-1);
+ EXPECT_FALSE(reader.TranslateInputData(
+ reinterpret_cast<const char*>(&header), sizeof(header)));
+ EXPECT_LE(reader.input_overflow_buf_.capacity(), capacity_before);
+
+ // Payload size is maximum int32_t value
+ header.payload_size = std::numeric_limits<int32_t>::max();
+ EXPECT_FALSE(reader.TranslateInputData(
+ reinterpret_cast<const char*>(&header), sizeof(header)));
+ EXPECT_LE(reader.input_overflow_buf_.capacity(), capacity_before);
+}
+
+TEST(ChannelReaderTest, TrimBuffer) {
+ // ChannelReader uses std::string as a buffer, and calls reserve()
+ // to trim it to kMaximumReadBufferSize. However, an implementation
+ // is free to actually reserve a larger amount.
+ size_t trimmed_buffer_size;
+ {
+ std::string buf;
+ buf.reserve(Channel::kMaximumReadBufferSize);
+ trimmed_buffer_size = buf.capacity();
+ }
+
+ // Buffer is trimmed after message is processed.
+ {
+ MockChannelReader reader;
+
+ Message message;
+ message.WriteString(std::string(LargePayloadSize, 'X'));
+
+ // Sanity check
+ EXPECT_TRUE(message.size() > trimmed_buffer_size);
+
+ // Initially buffer is small
+ EXPECT_LE(reader.input_overflow_buf_.capacity(), trimmed_buffer_size);
+
+ // Write and process large message
+ reader.AppendMessageData(message);
+ EXPECT_EQ(ChannelReader::DISPATCH_FINISHED,
+ reader.ProcessIncomingMessages());
+
+ // After processing large message buffer is trimmed
+ EXPECT_EQ(reader.input_overflow_buf_.capacity(), trimmed_buffer_size);
+ }
+
+ // Buffer is trimmed only after entire message is processed.
+ {
+ MockChannelReader reader;
+
+ ExposedMessage message;
+ message.WriteString(std::string(LargePayloadSize, 'X'));
+
+ // Write and process message header
+ reader.AppendData(message.header(), sizeof(ExposedMessage::Header));
+ EXPECT_EQ(ChannelReader::DISPATCH_FINISHED,
+ reader.ProcessIncomingMessages());
+
+ // We determined message size for the message from its header, so
+ // we resized the buffer to fit.
+ EXPECT_GE(reader.input_overflow_buf_.capacity(), message.size());
+
+ // Write and process payload
+ reader.AppendData(message.payload(), message.payload_size());
+ EXPECT_EQ(ChannelReader::DISPATCH_FINISHED,
+ reader.ProcessIncomingMessages());
+
+ // But once we process the message, we trim the buffer
+ EXPECT_EQ(reader.input_overflow_buf_.capacity(), trimmed_buffer_size);
+ }
+
+ // Buffer is not trimmed if the next message is also large.
+ {
+ MockChannelReader reader;
+
+ // Write large message
+ Message message1;
+ message1.WriteString(std::string(LargePayloadSize * 2, 'X'));
+ reader.AppendMessageData(message1);
+
+ // Write header for the next large message
+ ExposedMessage message2;
+ message2.WriteString(std::string(LargePayloadSize, 'Y'));
+ reader.AppendData(message2.header(), sizeof(ExposedMessage::Header));
+
+ // Process messages
+ EXPECT_EQ(ChannelReader::DISPATCH_FINISHED,
+ reader.ProcessIncomingMessages());
+
+ // We determined message size for the second (partial) message, so
+ // we resized the buffer to fit.
+ EXPECT_GE(reader.input_overflow_buf_.capacity(), message1.size());
+ }
+
+ // Buffer resized appropriately if next message is larger than the first.
+ // (Similar to the test above except for the order of messages.)
+ {
+ MockChannelReader reader;
+
+ // Write large message
+ Message message1;
+ message1.WriteString(std::string(LargePayloadSize, 'Y'));
+ reader.AppendMessageData(message1);
+
+ // Write header for the next even larger message
+ ExposedMessage message2;
+ message2.WriteString(std::string(LargePayloadSize * 2, 'X'));
+ reader.AppendData(message2.header(), sizeof(ExposedMessage::Header));
+
+ // Process messages
+ EXPECT_EQ(ChannelReader::DISPATCH_FINISHED,
+ reader.ProcessIncomingMessages());
+
+ // We determined message size for the second (partial) message, and
+ // resized the buffer to fit it.
+ EXPECT_GE(reader.input_overflow_buf_.capacity(), message2.size());
+ }
+
+ // Buffer is not trimmed if we've just resized it to accommodate large
+ // incoming message.
+ {
+ MockChannelReader reader;
+
+ // Write small message
+ Message message1;
+ message1.WriteString(std::string(11, 'X'));
+ reader.AppendMessageData(message1);
+
+ // Write header for the next large message
+ ExposedMessage message2;
+ message2.WriteString(std::string(LargePayloadSize, 'Y'));
+ reader.AppendData(message2.header(), sizeof(ExposedMessage::Header));
+
+ EXPECT_EQ(ChannelReader::DISPATCH_FINISHED,
+ reader.ProcessIncomingMessages());
+
+ // We determined message size for the second (partial) message, so
+ // we resized the buffer to fit.
+ EXPECT_GE(reader.input_overflow_buf_.capacity(), message2.size());
+ }
+}
+
+} // namespace internal
+} // namespace IPC
diff --git a/ipc/ipc_cpu_perftest.cc b/ipc/ipc_cpu_perftest.cc
new file mode 100644
index 0000000000..6cb8dfcee3
--- /dev/null
+++ b/ipc/ipc_cpu_perftest.cc
@@ -0,0 +1,417 @@
+// 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 <memory>
+
+#include "base/message_loop/message_loop.h"
+#include "base/process/process_metrics.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/perf_log.h"
+#include "base/timer/timer.h"
+#include "ipc/ipc_channel_proxy.h"
+#include "ipc/ipc_perftest_messages.h"
+#include "ipc/ipc_perftest_util.h"
+#include "ipc/ipc_sync_channel.h"
+#include "ipc/ipc_test.mojom.h"
+#include "ipc/ipc_test_base.h"
+#include "mojo/core/test/mojo_test_base.h"
+#include "mojo/core/test/multiprocess_test_helper.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace IPC {
+namespace {
+
+struct TestParams {
+ TestParams() = default;
+ TestParams(size_t in_message_size,
+ size_t in_frames_per_second,
+ size_t in_messages_per_frame,
+ size_t in_duration_in_seconds)
+ : message_size(in_message_size),
+ frames_per_second(in_frames_per_second),
+ messages_per_frame(in_messages_per_frame),
+ duration_in_seconds(in_duration_in_seconds) {}
+
+ size_t message_size;
+ size_t frames_per_second;
+ size_t messages_per_frame;
+ size_t duration_in_seconds;
+};
+
+std::vector<TestParams> GetDefaultTestParams() {
+ std::vector<TestParams> list;
+ list.push_back({144, 20, 10, 10});
+ list.push_back({144, 60, 10, 10});
+ return list;
+}
+
+std::string GetLogTitle(const std::string& label, const TestParams& params) {
+ return base::StringPrintf(
+ "%s_MsgSize_%zu_FrmPerSec_%zu_MsgPerFrm_%zu", label.c_str(),
+ params.message_size, params.frames_per_second, params.messages_per_frame);
+}
+
+base::TimeDelta GetFrameTime(size_t frames_per_second) {
+ return base::TimeDelta::FromSecondsD(1.0 / frames_per_second);
+}
+
+class PerfCpuLogger {
+ public:
+ explicit PerfCpuLogger(base::StringPiece test_name)
+ : test_name_(test_name),
+ process_metrics_(base::ProcessMetrics::CreateCurrentProcessMetrics()) {
+ process_metrics_->GetPlatformIndependentCPUUsage();
+ }
+
+ ~PerfCpuLogger() {
+ double result = process_metrics_->GetPlatformIndependentCPUUsage();
+ base::LogPerfResult(test_name_.c_str(), result, "%");
+ }
+
+ private:
+ std::string test_name_;
+ std::unique_ptr<base::ProcessMetrics> process_metrics_;
+
+ DISALLOW_COPY_AND_ASSIGN(PerfCpuLogger);
+};
+
+MULTIPROCESS_TEST_MAIN(MojoPerfTestClientTestChildMain) {
+ MojoPerfTestClient client;
+ int rv = mojo::core::test::MultiprocessTestHelper::RunClientMain(
+ base::Bind(&MojoPerfTestClient::Run, base::Unretained(&client)),
+ true /* pass_pipe_ownership_to_main */);
+
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+
+ return rv;
+}
+
+class ChannelSteadyPingPongListener : public Listener {
+ public:
+ ChannelSteadyPingPongListener() = default;
+
+ ~ChannelSteadyPingPongListener() override = default;
+
+ void Init(Sender* sender) {
+ DCHECK(!sender_);
+ sender_ = sender;
+ }
+
+ void SetTestParams(const TestParams& params,
+ const std::string& label,
+ bool sync,
+ const base::Closure& quit_closure) {
+ params_ = params;
+ label_ = label;
+ sync_ = sync;
+ quit_closure_ = quit_closure;
+ payload_ = std::string(params.message_size, 'a');
+ }
+
+ bool OnMessageReceived(const Message& message) override {
+ CHECK(sender_);
+
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(ChannelSteadyPingPongListener, message)
+ IPC_MESSAGE_HANDLER(TestMsg_Hello, OnHello)
+ IPC_MESSAGE_HANDLER(TestMsg_Ping, OnPing)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+ }
+
+ void OnHello() {
+ cpu_logger_ = std::make_unique<PerfCpuLogger>(GetLogTitle(label_, params_));
+
+ frame_count_down_ = params_.frames_per_second * params_.duration_in_seconds;
+
+ timer_.Start(FROM_HERE, GetFrameTime(params_.frames_per_second), this,
+ &ChannelSteadyPingPongListener::StartPingPong);
+ }
+
+ void StartPingPong() {
+ if (sync_) {
+ base::TimeTicks before = base::TimeTicks::Now();
+ for (count_down_ = params_.messages_per_frame; count_down_ > 0;
+ --count_down_) {
+ std::string response;
+ sender_->Send(new TestMsg_SyncPing(payload_, &response));
+ DCHECK_EQ(response, payload_);
+ }
+
+ if (base::TimeTicks::Now() - before >
+ GetFrameTime(params_.frames_per_second)) {
+ LOG(ERROR) << "Frame " << frame_count_down_
+ << " wasn't able to complete on time!";
+ }
+
+ CHECK_GT(frame_count_down_, 0);
+ frame_count_down_--;
+ if (frame_count_down_ == 0)
+ StopPingPong();
+ } else {
+ if (count_down_ != 0) {
+ LOG(ERROR) << "Frame " << frame_count_down_
+ << " wasn't able to complete on time!";
+ } else {
+ SendPong();
+ }
+ count_down_ = params_.messages_per_frame;
+ }
+ }
+
+ void StopPingPong() {
+ cpu_logger_.reset();
+ timer_.AbandonAndStop();
+ quit_closure_.Run();
+ }
+
+ void OnPing(const std::string& payload) {
+ // Include message deserialization in latency.
+ DCHECK_EQ(payload_.size(), payload.size());
+
+ CHECK_GT(count_down_, 0);
+ count_down_--;
+ if (count_down_ > 0) {
+ SendPong();
+ } else {
+ CHECK_GT(frame_count_down_, 0);
+ frame_count_down_--;
+ if (frame_count_down_ == 0)
+ StopPingPong();
+ }
+ }
+
+ void SendPong() { sender_->Send(new TestMsg_Ping(payload_)); }
+
+ private:
+ Sender* sender_ = nullptr;
+ TestParams params_;
+ std::string payload_;
+ std::string label_;
+ bool sync_ = false;
+
+ int count_down_ = 0;
+ int frame_count_down_ = 0;
+
+ base::RepeatingTimer timer_;
+ std::unique_ptr<PerfCpuLogger> cpu_logger_;
+
+ base::Closure quit_closure_;
+};
+
+class ChannelSteadyPingPongTest : public IPCChannelMojoTestBase {
+ public:
+ ChannelSteadyPingPongTest() = default;
+ ~ChannelSteadyPingPongTest() override = default;
+
+ void RunPingPongServer(const std::string& label, bool sync) {
+ Init("MojoPerfTestClient");
+
+ // Set up IPC channel and start client.
+ ChannelSteadyPingPongListener listener;
+
+ std::unique_ptr<ChannelProxy> channel_proxy;
+ std::unique_ptr<base::WaitableEvent> shutdown_event;
+
+ if (sync) {
+ shutdown_event = std::make_unique<base::WaitableEvent>(
+ base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ channel_proxy = IPC::SyncChannel::Create(
+ TakeHandle().release(), IPC::Channel::MODE_SERVER, &listener,
+ GetIOThreadTaskRunner(), base::ThreadTaskRunnerHandle::Get(), false,
+ shutdown_event.get());
+ } else {
+ channel_proxy = IPC::ChannelProxy::Create(
+ TakeHandle().release(), IPC::Channel::MODE_SERVER, &listener,
+ GetIOThreadTaskRunner(), base::ThreadTaskRunnerHandle::Get());
+ }
+ listener.Init(channel_proxy.get());
+
+ LockThreadAffinity thread_locker(kSharedCore);
+ std::vector<TestParams> params_list = GetDefaultTestParams();
+ for (const auto& params : params_list) {
+ base::RunLoop run_loop;
+
+ listener.SetTestParams(params, label, sync,
+ run_loop.QuitWhenIdleClosure());
+
+ // This initial message will kick-start the ping-pong of messages.
+ channel_proxy->Send(new TestMsg_Hello);
+
+ run_loop.Run();
+ }
+
+ // Send quit message.
+ channel_proxy->Send(new TestMsg_Quit);
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ channel_proxy.reset();
+ }
+};
+
+TEST_F(ChannelSteadyPingPongTest, AsyncPingPong) {
+ RunPingPongServer("IPC_CPU_Async", false);
+}
+
+TEST_F(ChannelSteadyPingPongTest, SyncPingPong) {
+ RunPingPongServer("IPC_CPU_Sync", true);
+}
+
+class MojoSteadyPingPongTest : public mojo::core::test::MojoTestBase {
+ public:
+ MojoSteadyPingPongTest() = default;
+
+ protected:
+ void RunPingPongServer(MojoHandle mp, const std::string& label, bool sync) {
+ label_ = label;
+ sync_ = sync;
+
+ mojo::MessagePipeHandle mp_handle(mp);
+ mojo::ScopedMessagePipeHandle scoped_mp(mp_handle);
+ ping_receiver_.Bind(IPC::mojom::ReflectorPtrInfo(std::move(scoped_mp), 0u));
+
+ LockThreadAffinity thread_locker(kSharedCore);
+ std::vector<TestParams> params_list = GetDefaultTestParams();
+ for (const auto& params : params_list) {
+ params_ = params;
+ payload_ = std::string(params.message_size, 'a');
+
+ ping_receiver_->Ping("hello", base::Bind(&MojoSteadyPingPongTest::OnHello,
+ base::Unretained(this)));
+ base::RunLoop run_loop;
+ quit_closure_ = run_loop.QuitWhenIdleClosure();
+ run_loop.Run();
+ }
+
+ ping_receiver_->Quit();
+
+ ignore_result(ping_receiver_.PassInterface().PassHandle().release());
+ }
+
+ void OnHello(const std::string& value) {
+ cpu_logger_ = std::make_unique<PerfCpuLogger>(GetLogTitle(label_, params_));
+
+ frame_count_down_ = params_.frames_per_second * params_.duration_in_seconds;
+
+ timer_.Start(FROM_HERE, GetFrameTime(params_.frames_per_second), this,
+ &MojoSteadyPingPongTest::StartPingPong);
+ }
+
+ void StartPingPong() {
+ if (sync_) {
+ base::TimeTicks before = base::TimeTicks::Now();
+ for (count_down_ = params_.messages_per_frame; count_down_ > 0;
+ --count_down_) {
+ std::string response;
+ ping_receiver_->SyncPing(payload_, &response);
+ DCHECK_EQ(response, payload_);
+ }
+
+ if (base::TimeTicks::Now() - before >
+ GetFrameTime(params_.frames_per_second)) {
+ LOG(ERROR) << "Frame " << frame_count_down_
+ << " wasn't able to complete on time!";
+ }
+
+ CHECK_GT(frame_count_down_, 0);
+ frame_count_down_--;
+ if (frame_count_down_ == 0)
+ StopPingPong();
+ } else {
+ if (count_down_ != 0) {
+ LOG(ERROR) << "Frame " << frame_count_down_
+ << " wasn't able to complete on time!";
+ } else {
+ SendPing();
+ }
+ count_down_ = params_.messages_per_frame;
+ }
+ }
+
+ void StopPingPong() {
+ cpu_logger_.reset();
+ timer_.AbandonAndStop();
+ quit_closure_.Run();
+ }
+
+ void OnPong(const std::string& value) {
+ // Include message deserialization in latency.
+ DCHECK_EQ(payload_.size(), value.size());
+
+ CHECK_GT(count_down_, 0);
+ count_down_--;
+ if (count_down_ > 0) {
+ SendPing();
+ } else {
+ CHECK_GT(frame_count_down_, 0);
+ frame_count_down_--;
+ if (frame_count_down_ == 0)
+ StopPingPong();
+ }
+ }
+
+ void SendPing() {
+ ping_receiver_->Ping(payload_, base::Bind(&MojoSteadyPingPongTest::OnPong,
+ base::Unretained(this)));
+ }
+
+ static int RunPingPongClient(MojoHandle mp) {
+ mojo::MessagePipeHandle mp_handle(mp);
+ mojo::ScopedMessagePipeHandle scoped_mp(mp_handle);
+
+ LockThreadAffinity thread_locker(kSharedCore);
+ base::RunLoop run_loop;
+ ReflectorImpl impl(std::move(scoped_mp), run_loop.QuitWhenIdleClosure());
+ run_loop.Run();
+ return 0;
+ }
+
+ private:
+ TestParams params_;
+ std::string payload_;
+ std::string label_;
+ bool sync_ = false;
+
+ IPC::mojom::ReflectorPtr ping_receiver_;
+
+ int count_down_ = 0;
+ int frame_count_down_ = 0;
+
+ base::RepeatingTimer timer_;
+ std::unique_ptr<PerfCpuLogger> cpu_logger_;
+
+ base::Closure quit_closure_;
+
+ DISALLOW_COPY_AND_ASSIGN(MojoSteadyPingPongTest);
+};
+
+DEFINE_TEST_CLIENT_WITH_PIPE(PingPongClient, MojoSteadyPingPongTest, h) {
+ base::MessageLoop main_message_loop;
+ return RunPingPongClient(h);
+}
+
+// Similar to ChannelSteadyPingPongTest above, but uses a Mojo interface
+// instead of raw IPC::Messages.
+TEST_F(MojoSteadyPingPongTest, AsyncPingPong) {
+ RunTestClient("PingPongClient", [&](MojoHandle h) {
+ base::MessageLoop main_message_loop;
+ RunPingPongServer(h, "Mojo_CPU_Async", false);
+ });
+}
+
+TEST_F(MojoSteadyPingPongTest, SyncPingPong) {
+ RunTestClient("PingPongClient", [&](MojoHandle h) {
+ base::MessageLoop main_message_loop;
+ RunPingPongServer(h, "Mojo_CPU_Sync", true);
+ });
+}
+
+} // namespace
+} // namespace IPC
diff --git a/ipc/ipc_export.h b/ipc/ipc_export.h
deleted file mode 100644
index e1cbe8891f..0000000000
--- a/ipc/ipc_export.h
+++ /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.
-
-#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_fuzzing_tests.cc b/ipc/ipc_fuzzing_tests.cc
new file mode 100644
index 0000000000..ca8827b12b
--- /dev/null
+++ b/ipc/ipc_fuzzing_tests.cc
@@ -0,0 +1,364 @@
+// 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 <stdint.h>
+#include <stdio.h>
+
+#include <limits>
+#include <memory>
+#include <sstream>
+#include <string>
+
+#include "base/run_loop.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/platform_thread.h"
+#include "build/build_config.h"
+#include "ipc/ipc_test_base.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// IPC messages for testing ----------------------------------------------------
+
+#define IPC_MESSAGE_IMPL
+#include "ipc/ipc_message_macros.h"
+
+#define IPC_MESSAGE_START TestMsgStart
+
+// Generic message class that is an int followed by a string16.
+IPC_MESSAGE_CONTROL2(MsgClassIS, int, base::string16)
+
+// Generic message class that is a string16 followed by an int.
+IPC_MESSAGE_CONTROL2(MsgClassSI, base::string16, int)
+
+// Message to create a mutex in the IPC server, using the received name.
+IPC_MESSAGE_CONTROL2(MsgDoMutex, base::string16, int)
+
+// Used to generate an ID for a message that should not exist.
+IPC_MESSAGE_CONTROL0(MsgUnhandled)
+
+// -----------------------------------------------------------------------------
+
+namespace {
+
+TEST(IPCMessageIntegrity, ReadBeyondBufferStr) {
+ // This was BUG 984408.
+ uint32_t v1 = std::numeric_limits<uint32_t>::max() - 1;
+ int v2 = 666;
+ IPC::Message m(0, 1, IPC::Message::PRIORITY_NORMAL);
+ m.WriteInt(v1);
+ m.WriteInt(v2);
+
+ base::PickleIterator iter(m);
+ std::string vs;
+ EXPECT_FALSE(iter.ReadString(&vs));
+}
+
+TEST(IPCMessageIntegrity, ReadBeyondBufferStr16) {
+ // This was BUG 984408.
+ uint32_t v1 = std::numeric_limits<uint32_t>::max() - 1;
+ int v2 = 777;
+ IPC::Message m(0, 1, IPC::Message::PRIORITY_NORMAL);
+ m.WriteInt(v1);
+ m.WriteInt(v2);
+
+ base::PickleIterator iter(m);
+ base::string16 vs;
+ EXPECT_FALSE(iter.ReadString16(&vs));
+}
+
+TEST(IPCMessageIntegrity, ReadBytesBadIterator) {
+ // This was BUG 1035467.
+ IPC::Message m(0, 1, IPC::Message::PRIORITY_NORMAL);
+ m.WriteInt(1);
+ m.WriteInt(2);
+
+ base::PickleIterator iter(m);
+ const char* data = NULL;
+ EXPECT_TRUE(iter.ReadBytes(&data, sizeof(int)));
+}
+
+TEST(IPCMessageIntegrity, ReadVectorNegativeSize) {
+ // A slight variation of BUG 984408. Note that the pickling of vector<char>
+ // has a specialized template which is not vulnerable to this bug. So here
+ // try to hit the non-specialized case vector<P>.
+ IPC::Message m(0, 1, IPC::Message::PRIORITY_NORMAL);
+ m.WriteInt(-1); // This is the count of elements.
+ m.WriteInt(1);
+ m.WriteInt(2);
+ m.WriteInt(3);
+
+ std::vector<double> vec;
+ base::PickleIterator iter(m);
+ EXPECT_FALSE(ReadParam(&m, &iter, &vec));
+}
+
+#if defined(OS_ANDROID)
+#define MAYBE_ReadVectorTooLarge1 DISABLED_ReadVectorTooLarge1
+#else
+#define MAYBE_ReadVectorTooLarge1 ReadVectorTooLarge1
+#endif
+TEST(IPCMessageIntegrity, MAYBE_ReadVectorTooLarge1) {
+ // This was BUG 1006367. This is the large but positive length case. Again
+ // we try to hit the non-specialized case vector<P>.
+ IPC::Message m(0, 1, IPC::Message::PRIORITY_NORMAL);
+ m.WriteInt(0x21000003); // This is the count of elements.
+ m.WriteInt64(1);
+ m.WriteInt64(2);
+
+ std::vector<int64_t> vec;
+ base::PickleIterator iter(m);
+ EXPECT_FALSE(ReadParam(&m, &iter, &vec));
+}
+
+TEST(IPCMessageIntegrity, ReadVectorTooLarge2) {
+ // This was BUG 1006367. This is the large but positive with an additional
+ // integer overflow when computing the actual byte size. Again we try to hit
+ // the non-specialized case vector<P>.
+ IPC::Message m(0, 1, IPC::Message::PRIORITY_NORMAL);
+ m.WriteInt(0x71000000); // This is the count of elements.
+ m.WriteInt64(1);
+ m.WriteInt64(2);
+
+ std::vector<int64_t> vec;
+ base::PickleIterator iter(m);
+ EXPECT_FALSE(ReadParam(&m, &iter, &vec));
+}
+
+// This test needs ~20 seconds in Debug mode, or ~4 seconds in Release mode.
+// See http://crbug.com/741866 for details.
+TEST(IPCMessageIntegrity, DISABLED_ReadVectorTooLarge3) {
+ base::Pickle pickle;
+ IPC::WriteParam(&pickle, 256 * 1024 * 1024);
+ IPC::WriteParam(&pickle, 0);
+ IPC::WriteParam(&pickle, 1);
+ IPC::WriteParam(&pickle, 2);
+
+ base::PickleIterator iter(pickle);
+ std::vector<int> vec;
+ EXPECT_FALSE(IPC::ReadParam(&pickle, &iter, &vec));
+}
+
+class SimpleListener : public IPC::Listener {
+ public:
+ SimpleListener() : other_(NULL) {
+ }
+ void Init(IPC::Sender* s) {
+ other_ = s;
+ }
+ protected:
+ IPC::Sender* other_;
+};
+
+enum {
+ FUZZER_ROUTING_ID = 5
+};
+
+// The fuzzer server class. It runs in a child process and expects
+// only two IPC calls; after that it exits the message loop which
+// terminates the child process.
+class FuzzerServerListener : public SimpleListener {
+ public:
+ FuzzerServerListener() : message_count_(2), pending_messages_(0) {
+ }
+ bool OnMessageReceived(const IPC::Message& msg) override {
+ if (msg.routing_id() == MSG_ROUTING_CONTROL) {
+ ++pending_messages_;
+ IPC_BEGIN_MESSAGE_MAP(FuzzerServerListener, msg)
+ IPC_MESSAGE_HANDLER(MsgClassIS, OnMsgClassISMessage)
+ IPC_MESSAGE_HANDLER(MsgClassSI, OnMsgClassSIMessage)
+ IPC_END_MESSAGE_MAP()
+ if (pending_messages_) {
+ // Probably a problem de-serializing the message.
+ ReplyMsgNotHandled(msg.type());
+ }
+ }
+ return true;
+ }
+
+ private:
+ void OnMsgClassISMessage(int value, const base::string16& text) {
+ UseData(MsgClassIS::ID, value, text);
+ RoundtripAckReply(FUZZER_ROUTING_ID, MsgClassIS::ID, value);
+ Cleanup();
+ }
+
+ void OnMsgClassSIMessage(const base::string16& text, int value) {
+ UseData(MsgClassSI::ID, value, text);
+ RoundtripAckReply(FUZZER_ROUTING_ID, MsgClassSI::ID, value);
+ Cleanup();
+ }
+
+ bool RoundtripAckReply(int routing, uint32_t type_id, int reply) {
+ IPC::Message* message = new IPC::Message(routing, type_id,
+ IPC::Message::PRIORITY_NORMAL);
+ message->WriteInt(reply + 1);
+ message->WriteInt(reply);
+ return other_->Send(message);
+ }
+
+ void Cleanup() {
+ --message_count_;
+ --pending_messages_;
+ if (0 == message_count_)
+ base::RunLoop::QuitCurrentWhenIdleDeprecated();
+ }
+
+ void ReplyMsgNotHandled(uint32_t type_id) {
+ RoundtripAckReply(FUZZER_ROUTING_ID, MsgUnhandled::ID, type_id);
+ Cleanup();
+ }
+
+ void UseData(int caller, int value, const base::string16& text) {
+ std::ostringstream os;
+ os << "IPC fuzzer:" << caller << " [" << value << " "
+ << base::UTF16ToUTF8(text) << "]\n";
+ std::string output = os.str();
+ LOG(WARNING) << output;
+ }
+
+ int message_count_;
+ int pending_messages_;
+};
+
+class FuzzerClientListener : public SimpleListener {
+ public:
+ FuzzerClientListener() : last_msg_(NULL) {
+ }
+
+ bool OnMessageReceived(const IPC::Message& msg) override {
+ last_msg_ = new IPC::Message(msg);
+ base::RunLoop::QuitCurrentWhenIdleDeprecated();
+ return true;
+ }
+
+ bool ExpectMessage(int value, uint32_t type_id) {
+ if (!MsgHandlerInternal(type_id))
+ return false;
+ int msg_value1 = 0;
+ int msg_value2 = 0;
+ base::PickleIterator iter(*last_msg_);
+ if (!iter.ReadInt(&msg_value1))
+ return false;
+ if (!iter.ReadInt(&msg_value2))
+ return false;
+ if ((msg_value2 + 1) != msg_value1)
+ return false;
+ if (msg_value2 != value)
+ return false;
+
+ delete last_msg_;
+ last_msg_ = NULL;
+ return true;
+ }
+
+ bool ExpectMsgNotHandled(uint32_t type_id) {
+ return ExpectMessage(type_id, MsgUnhandled::ID);
+ }
+
+ private:
+ bool MsgHandlerInternal(uint32_t type_id) {
+ base::RunLoop().Run();
+ if (NULL == last_msg_)
+ return false;
+ if (FUZZER_ROUTING_ID != last_msg_->routing_id())
+ return false;
+ return (type_id == last_msg_->type());
+ }
+
+ IPC::Message* last_msg_;
+};
+
+// Runs the fuzzing server child mode. Returns when the preset number of
+// messages have been received.
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(FuzzServerClient) {
+ FuzzerServerListener listener;
+ Connect(&listener);
+ listener.Init(channel());
+ base::RunLoop().Run();
+ Close();
+}
+
+using IPCFuzzingTest = IPCChannelMojoTestBase;
+
+// This test makes sure that the FuzzerClientListener and FuzzerServerListener
+// are working properly by generating two well formed IPC calls.
+TEST_F(IPCFuzzingTest, SanityTest) {
+ Init("FuzzServerClient");
+
+ FuzzerClientListener listener;
+ CreateChannel(&listener);
+ listener.Init(channel());
+ ASSERT_TRUE(ConnectChannel());
+
+ IPC::Message* msg = NULL;
+ int value = 43;
+ msg = new MsgClassIS(value, base::ASCIIToUTF16("expect 43"));
+ sender()->Send(msg);
+ EXPECT_TRUE(listener.ExpectMessage(value, MsgClassIS::ID));
+
+ msg = new MsgClassSI(base::ASCIIToUTF16("expect 44"), ++value);
+ sender()->Send(msg);
+ EXPECT_TRUE(listener.ExpectMessage(value, MsgClassSI::ID));
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ DestroyChannel();
+}
+
+// This test uses a payload that is smaller than expected. This generates an
+// error while unpacking the IPC buffer. Right after we generate another valid
+// IPC to make sure framing is working properly.
+TEST_F(IPCFuzzingTest, MsgBadPayloadShort) {
+ Init("FuzzServerClient");
+
+ FuzzerClientListener listener;
+ CreateChannel(&listener);
+ listener.Init(channel());
+ ASSERT_TRUE(ConnectChannel());
+
+ IPC::Message* msg = new IPC::Message(MSG_ROUTING_CONTROL, MsgClassIS::ID,
+ IPC::Message::PRIORITY_NORMAL);
+ msg->WriteInt(666);
+ sender()->Send(msg);
+ EXPECT_TRUE(listener.ExpectMsgNotHandled(MsgClassIS::ID));
+
+ msg = new MsgClassSI(base::ASCIIToUTF16("expect one"), 1);
+ sender()->Send(msg);
+ EXPECT_TRUE(listener.ExpectMessage(1, MsgClassSI::ID));
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ DestroyChannel();
+}
+
+// This test uses a payload that has too many arguments, but so the payload size
+// is big enough so the unpacking routine does not generate an error as in the
+// case of MsgBadPayloadShort test. This test does not pinpoint a flaw (per se)
+// as by design we don't carry type information on the IPC message.
+TEST_F(IPCFuzzingTest, MsgBadPayloadArgs) {
+ Init("FuzzServerClient");
+
+ FuzzerClientListener listener;
+ CreateChannel(&listener);
+ listener.Init(channel());
+ ASSERT_TRUE(ConnectChannel());
+
+ IPC::Message* msg = new IPC::Message(MSG_ROUTING_CONTROL, MsgClassSI::ID,
+ IPC::Message::PRIORITY_NORMAL);
+ msg->WriteString16(base::ASCIIToUTF16("d"));
+ msg->WriteInt(0);
+ msg->WriteInt(0x65); // Extra argument.
+
+ sender()->Send(msg);
+ EXPECT_TRUE(listener.ExpectMessage(0, MsgClassSI::ID));
+
+ // Now send a well formed message to make sure the receiver wasn't
+ // thrown out of sync by the extra argument.
+ msg = new MsgClassIS(3, base::ASCIIToUTF16("expect three"));
+ sender()->Send(msg);
+ EXPECT_TRUE(listener.ExpectMessage(3, MsgClassIS::ID));
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ DestroyChannel();
+}
+
+} // namespace
diff --git a/ipc/ipc_listener.h b/ipc/ipc_listener.h
index d7ad75c321..398bd4167a 100644
--- a/ipc/ipc_listener.h
+++ b/ipc/ipc_listener.h
@@ -9,8 +9,8 @@
#include <string>
+#include "base/component_export.h"
#include "build/build_config.h"
-#include "ipc/ipc_export.h"
#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
namespace IPC {
@@ -18,7 +18,7 @@ namespace IPC {
class Message;
// Implemented by consumers of a Channel to receive messages.
-class IPC_EXPORT Listener {
+class COMPONENT_EXPORT(IPC) Listener {
public:
// Called when a message is received. Returns true iff the message was
// handled.
@@ -41,7 +41,7 @@ class IPC_EXPORT Listener {
const std::string& interface_name,
mojo::ScopedInterfaceEndpointHandle handle) {}
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// Called on the server side when a channel that listens for connections
// denies an attempt to connect.
virtual void OnChannelDenied() {}
@@ -49,7 +49,7 @@ class IPC_EXPORT Listener {
// 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
+#endif // OS_POSIX || OS_FUCHSIA
protected:
virtual ~Listener() {}
diff --git a/ipc/ipc_logging.cc b/ipc/ipc_logging.cc
new file mode 100644
index 0000000000..aad1b73326
--- /dev/null
+++ b/ipc/ipc_logging.cc
@@ -0,0 +1,313 @@
+// 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_logging.h"
+
+#if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
+#define IPC_MESSAGE_MACROS_LOG_ENABLED
+#endif
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "ipc/ipc_message_utils.h"
+#include "ipc/ipc_sender.h"
+#include "ipc/ipc_sync_message.h"
+
+#if defined(OS_POSIX)
+#include <unistd.h>
+#endif
+
+#if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
+
+using base::Time;
+
+namespace IPC {
+
+const int kLogSendDelayMs = 100;
+
+// We use a pointer to the function table to avoid any linker dependencies on
+// all the traits used as IPC message parameters.
+LogFunctionMap* Logging::log_function_map_;
+
+Logging::Logging()
+ : enabled_(false),
+ enabled_on_stderr_(false),
+ enabled_color_(false),
+ queue_invoke_later_pending_(false),
+ sender_(NULL),
+ main_thread_(base::ThreadTaskRunnerHandle::Get()),
+ consumer_(NULL) {
+#if defined(OS_WIN)
+ // getenv triggers an unsafe warning. Simply check how big of a buffer
+ // would be needed to fetch the value to see if the enviornment variable is
+ // set.
+ size_t requiredSize = 0;
+ getenv_s(&requiredSize, NULL, 0, "CHROME_IPC_LOGGING");
+ bool logging_env_var_set = (requiredSize != 0);
+ if (requiredSize <= 6) {
+ char buffer[6];
+ getenv_s(&requiredSize, buffer, sizeof(buffer), "CHROME_IPC_LOGGING");
+ if (requiredSize && !strncmp("color", buffer, 6))
+ enabled_color_ = true;
+ }
+#else // !defined(OS_WIN)
+ const char* ipc_logging = getenv("CHROME_IPC_LOGGING");
+ bool logging_env_var_set = (ipc_logging != NULL);
+ if (ipc_logging && !strcmp(ipc_logging, "color"))
+ enabled_color_ = true;
+#endif //defined(OS_WIN)
+ if (logging_env_var_set) {
+ enabled_ = true;
+ enabled_on_stderr_ = true;
+ }
+}
+
+Logging::~Logging() {
+}
+
+Logging* Logging::GetInstance() {
+ return base::Singleton<Logging>::get();
+}
+
+void Logging::SetConsumer(Consumer* consumer) {
+ consumer_ = consumer;
+}
+
+void Logging::Enable() {
+ enabled_ = true;
+}
+
+void Logging::Disable() {
+ enabled_ = false;
+}
+
+void Logging::OnSendLogs() {
+ queue_invoke_later_pending_ = false;
+ if (!sender_)
+ return;
+
+ Message* msg = new Message(
+ MSG_ROUTING_CONTROL, IPC_LOGGING_ID, Message::PRIORITY_NORMAL);
+ WriteParam(msg, queued_logs_);
+ queued_logs_.clear();
+ sender_->Send(msg);
+}
+
+void Logging::SetIPCSender(IPC::Sender* sender) {
+ sender_ = sender;
+}
+
+void Logging::OnReceivedLoggingMessage(const Message& message) {
+ std::vector<LogData> data;
+ base::PickleIterator iter(message);
+ if (!ReadParam(&message, &iter, &data))
+ return;
+
+ for (size_t i = 0; i < data.size(); ++i) {
+ Log(data[i]);
+ }
+}
+
+void Logging::OnSendMessage(Message* message) {
+ if (!Enabled())
+ return;
+
+ if (message->is_reply()) {
+ LogData* data = message->sync_log_data();
+ if (!data)
+ return;
+
+ // This is actually the delayed reply to a sync message. Create a string
+ // of the output parameters, add it to the LogData that was earlier stashed
+ // with the reply, and log the result.
+ GenerateLogData(*message, data, true);
+ Log(*data);
+ delete data;
+ message->set_sync_log_data(NULL);
+ } else {
+ // If the time has already been set (i.e. by ChannelProxy), keep that time
+ // instead as it's more accurate.
+ if (!message->sent_time())
+ message->set_sent_time(Time::Now().ToInternalValue());
+ }
+}
+
+void Logging::OnPreDispatchMessage(const Message& message) {
+ message.set_received_time(Time::Now().ToInternalValue());
+}
+
+void Logging::OnPostDispatchMessage(const Message& message) {
+ if (!Enabled() ||
+ !message.sent_time() ||
+ !message.received_time() ||
+ message.dont_log())
+ return;
+
+ LogData data;
+ GenerateLogData(message, &data, true);
+
+ if (main_thread_->BelongsToCurrentThread()) {
+ Log(data);
+ } else {
+ main_thread_->PostTask(
+ FROM_HERE, base::Bind(&Logging::Log, base::Unretained(this), data));
+ }
+}
+
+void Logging::GetMessageText(uint32_t type, std::string* name,
+ const Message* message,
+ std::string* params) {
+ if (!log_function_map_)
+ return;
+
+ LogFunctionMap::iterator it = log_function_map_->find(type);
+ if (it == log_function_map_->end()) {
+ if (name) {
+ *name = "[UNKNOWN MSG ";
+ *name += base::IntToString(type);
+ *name += " ]";
+ }
+ return;
+ }
+
+ (*it->second)(name, message, params);
+}
+
+const char* Logging::ANSIEscape(ANSIColor color) {
+ if (!enabled_color_)
+ return "";
+ switch (color) {
+ case ANSI_COLOR_RESET:
+ return "\033[m";
+ case ANSI_COLOR_BLACK:
+ return "\033[0;30m";
+ case ANSI_COLOR_RED:
+ return "\033[0;31m";
+ case ANSI_COLOR_GREEN:
+ return "\033[0;32m";
+ case ANSI_COLOR_YELLOW:
+ return "\033[0;33m";
+ case ANSI_COLOR_BLUE:
+ return "\033[0;34m";
+ case ANSI_COLOR_MAGENTA:
+ return "\033[0;35m";
+ case ANSI_COLOR_CYAN:
+ return "\033[0;36m";
+ case ANSI_COLOR_WHITE:
+ return "\033[0;37m";
+ }
+ return "";
+}
+
+Logging::ANSIColor Logging::DelayColor(double delay) {
+ if (delay < 0.1)
+ return ANSI_COLOR_GREEN;
+ if (delay < 0.25)
+ return ANSI_COLOR_BLACK;
+ if (delay < 0.5)
+ return ANSI_COLOR_YELLOW;
+ return ANSI_COLOR_RED;
+}
+
+void Logging::Log(const LogData& data) {
+ if (consumer_) {
+ // We're in the browser process.
+ consumer_->Log(data);
+ } else {
+ // We're in the renderer or plugin processes.
+ if (sender_) {
+ queued_logs_.push_back(data);
+ if (!queue_invoke_later_pending_) {
+ queue_invoke_later_pending_ = true;
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, base::Bind(&Logging::OnSendLogs, base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(kLogSendDelayMs));
+ }
+ }
+ }
+ if (enabled_on_stderr_) {
+ std::string message_name;
+ if (data.message_name.empty()) {
+ message_name = base::StringPrintf("[unknown type %d]", data.type);
+ } else {
+ message_name = data.message_name;
+ }
+ double receive_delay =
+ (Time::FromInternalValue(data.receive) -
+ Time::FromInternalValue(data.sent)).InSecondsF();
+ double dispatch_delay =
+ (Time::FromInternalValue(data.dispatch) -
+ Time::FromInternalValue(data.sent)).InSecondsF();
+ fprintf(stderr,
+ "ipc %d %s %s%s %s%s\n %18.5f %s%18.5f %s%18.5f%s\n",
+ data.routing_id,
+ data.flags.c_str(),
+ ANSIEscape(sender_ ? ANSI_COLOR_BLUE : ANSI_COLOR_CYAN),
+ message_name.c_str(),
+ ANSIEscape(ANSI_COLOR_RESET),
+ data.params.c_str(),
+ Time::FromInternalValue(data.sent).ToDoubleT(),
+ ANSIEscape(DelayColor(receive_delay)),
+ Time::FromInternalValue(data.receive).ToDoubleT(),
+ ANSIEscape(DelayColor(dispatch_delay)),
+ Time::FromInternalValue(data.dispatch).ToDoubleT(),
+ ANSIEscape(ANSI_COLOR_RESET)
+ );
+ }
+}
+
+void GenerateLogData(const Message& message, LogData* data, bool get_params) {
+ if (message.is_reply()) {
+ // "data" should already be filled in.
+ std::string params;
+ Logging::GetMessageText(data->type, NULL, &message, &params);
+
+ if (!data->params.empty() && !params.empty())
+ data->params += ", ";
+
+ data->flags += " DR";
+
+ data->params += params;
+ } else {
+ std::string flags;
+ if (message.is_sync())
+ flags = "S";
+
+ if (message.is_reply())
+ flags += "R";
+
+ if (message.is_reply_error())
+ flags += "E";
+
+ std::string params, message_name;
+ Logging::GetMessageText(message.type(), &message_name, &message,
+ get_params ? &params : NULL);
+
+ data->routing_id = message.routing_id();
+ data->type = message.type();
+ data->flags = flags;
+ data->sent = message.sent_time();
+ data->receive = message.received_time();
+ data->dispatch = Time::Now().ToInternalValue();
+ data->params = params;
+ data->message_name = message_name;
+ }
+}
+
+}
+
+#endif // BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
diff --git a/ipc/ipc_logging.h b/ipc/ipc_logging.h
new file mode 100644
index 0000000000..eee32f62ed
--- /dev/null
+++ b/ipc/ipc_logging.h
@@ -0,0 +1,128 @@
+// 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_LOGGING_H_
+#define IPC_IPC_LOGGING_H_
+
+#include "ipc/ipc_buildflags.h"
+
+#if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
+
+#include <stdint.h>
+#include <vector>
+
+#include "base/component_export.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/singleton.h"
+#include "base/single_thread_task_runner.h"
+#include "ipc/ipc_message.h"
+
+// Logging function. |name| is a string in ASCII and |params| is a string in
+// UTF-8.
+typedef void (*LogFunction)(std::string* name,
+ const IPC::Message* msg,
+ std::string* params);
+
+typedef base::hash_map<uint32_t, LogFunction > LogFunctionMap;
+
+namespace IPC {
+
+class Message;
+class Sender;
+
+// One instance per process. Needs to be created on the main thread (the UI
+// thread in the browser) but OnPreDispatchMessage/OnPostDispatchMessage
+// can be called on other threads.
+class COMPONENT_EXPORT(IPC) Logging {
+ public:
+ // Implemented by consumers of log messages.
+ class Consumer {
+ public:
+ virtual void Log(const LogData& data) = 0;
+
+ protected:
+ virtual ~Consumer() {}
+ };
+
+ void SetConsumer(Consumer* consumer);
+
+ ~Logging();
+ static Logging* GetInstance();
+
+ // Enable and Disable are NOT cross-process; they only affect the
+ // current thread/process. If you want to modify the value for all
+ // processes, perhaps your intent is to call
+ // g_browser_process->SetIPCLoggingEnabled().
+ void Enable();
+ void Disable();
+ bool Enabled() const { return enabled_; }
+
+ // Called by child processes to give the logger object the channel to send
+ // logging data to the browser process.
+ void SetIPCSender(Sender* sender);
+
+ // Called in the browser process when logging data from a child process is
+ // received.
+ void OnReceivedLoggingMessage(const Message& message);
+
+ void OnSendMessage(Message* message);
+ void OnPreDispatchMessage(const Message& message);
+ void OnPostDispatchMessage(const Message& message);
+
+ // Like the *MsgLog functions declared for each message class, except this
+ // calls the correct one based on the message type automatically. Defined in
+ // ipc_logging.cc.
+ static void GetMessageText(uint32_t type, std::string* name,
+ const Message* message, std::string* params);
+
+ static void set_log_function_map(LogFunctionMap* functions) {
+ log_function_map_ = functions;
+ }
+
+ static LogFunctionMap* log_function_map() {
+ return log_function_map_;
+ }
+
+ private:
+ typedef enum {
+ ANSI_COLOR_RESET = -1,
+ ANSI_COLOR_BLACK,
+ ANSI_COLOR_RED,
+ ANSI_COLOR_GREEN,
+ ANSI_COLOR_YELLOW,
+ ANSI_COLOR_BLUE,
+ ANSI_COLOR_MAGENTA,
+ ANSI_COLOR_CYAN,
+ ANSI_COLOR_WHITE
+ } ANSIColor;
+ const char* ANSIEscape(ANSIColor color);
+ ANSIColor DelayColor(double delay);
+
+ friend struct base::DefaultSingletonTraits<Logging>;
+ Logging();
+
+ void OnSendLogs();
+ void Log(const LogData& data);
+
+ bool enabled_;
+ bool enabled_on_stderr_; // only used on POSIX for now
+ bool enabled_color_; // only used on POSIX for now
+
+ std::vector<LogData> queued_logs_;
+ bool queue_invoke_later_pending_;
+
+ Sender* sender_;
+ scoped_refptr<base::SingleThreadTaskRunner> main_thread_;
+
+ Consumer* consumer_;
+
+ static LogFunctionMap* log_function_map_;
+};
+
+} // namespace IPC
+
+#endif // BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
+
+#endif // IPC_IPC_LOGGING_H_
diff --git a/ipc/ipc_message.cc b/ipc/ipc_message.cc
index f5e9ac7a8b..1023729316 100644
--- a/ipc/ipc_message.cc
+++ b/ipc/ipc_message.cc
@@ -21,7 +21,7 @@
namespace {
-base::StaticAtomicSequenceNumber g_ref_num;
+base::AtomicSequenceNumber 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
@@ -44,13 +44,12 @@ namespace IPC {
//------------------------------------------------------------------------------
-Message::~Message() {
-}
+Message::~Message() = default;
Message::Message() : base::Pickle(sizeof(Header)) {
header()->routing = header()->type = 0;
header()->flags = GetRefNumUpper24();
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
header()->num_fds = 0;
header()->pad = 0;
#endif
@@ -63,7 +62,7 @@ Message::Message(int32_t routing_id, uint32_t type, PriorityValue priority)
header()->type = type;
DCHECK((priority & 0xffffff00) == 0);
header()->flags = priority | GetRefNumUpper24();
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
header()->num_fds = 0;
header()->pad = 0;
#endif
@@ -82,7 +81,7 @@ Message::Message(const Message& other) : base::Pickle(other) {
void Message::Init() {
dispatch_error_ = false;
-#ifdef IPC_MESSAGE_LOG_ENABLED
+#if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
received_time_ = 0;
dont_log_ = false;
log_data_ = NULL;
@@ -109,7 +108,7 @@ void Message::EnsureMessageAttachmentSet() {
attachment_set_ = new MessageAttachmentSet;
}
-#ifdef IPC_MESSAGE_LOG_ENABLED
+#if BUILDFLAG(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;
@@ -128,12 +127,12 @@ int64_t Message::sent_time() const {
void Message::set_received_time(int64_t time) const {
received_time_ = time;
}
-#endif
+#endif // BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
Message::NextMessageInfo::NextMessageInfo()
: message_size(0), message_found(false), pickle_end(nullptr),
message_end(nullptr) {}
-Message::NextMessageInfo::~NextMessageInfo() {}
+Message::NextMessageInfo::~NextMessageInfo() = default;
// static
void Message::FindNext(const char* range_start,
@@ -168,13 +167,10 @@ bool Message::WriteAttachment(
scoped_refptr<base::Pickle::Attachment> attachment) {
size_t index;
bool success = attachment_set()->AddAttachment(
- make_scoped_refptr(static_cast<MessageAttachment*>(attachment.get())),
+ base::WrapRefCounted(static_cast<MessageAttachment*>(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<int>(index));
diff --git a/ipc/ipc_message.h b/ipc/ipc_message.h
index 43e9ae39c1..bbbc14efca 100644
--- a/ipc/ipc_message.h
+++ b/ipc/ipc_message.h
@@ -15,11 +15,14 @@
#include "base/pickle.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
-#include "ipc/ipc_export.h"
+#include "ipc/ipc_buildflags.h"
+#include "ipc/ipc_message_support_export.h"
-#if !defined(NDEBUG)
-#define IPC_MESSAGE_LOG_ENABLED
-#endif
+namespace mojo {
+namespace internal {
+struct UnmappedNativeStructSerializerImpl;
+}
+} // namespace mojo
namespace IPC {
@@ -32,7 +35,7 @@ class ChannelReader;
struct LogData;
class MessageAttachmentSet;
-class IPC_EXPORT Message : public base::Pickle {
+class IPC_MESSAGE_SUPPORT_EXPORT Message : public base::Pickle {
public:
enum PriorityValue {
PRIORITY_LOW = 1,
@@ -69,6 +72,8 @@ class IPC_EXPORT Message : public base::Pickle {
Message(const Message& other);
Message& operator=(const Message& other);
+ bool IsValid() const { return header_size() == sizeof(Header) && header(); }
+
PriorityValue priority() const {
return static_cast<PriorityValue>(header()->flags & PRIORITY_MASK);
}
@@ -169,7 +174,7 @@ class IPC_EXPORT Message : public base::Pickle {
// The static method FindNext() returns several pieces of information, which
// are aggregated into an instance of this struct.
- struct IPC_EXPORT NextMessageInfo {
+ struct IPC_MESSAGE_SUPPORT_EXPORT NextMessageInfo {
NextMessageInfo();
~NextMessageInfo();
@@ -207,7 +212,7 @@ class IPC_EXPORT Message : public base::Pickle {
// Returns true if there are any attachment in this message.
bool HasAttachments() const override;
-#ifdef IPC_MESSAGE_LOG_ENABLED
+#if BUILDFLAG(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);
@@ -237,12 +242,14 @@ class IPC_EXPORT Message : public base::Pickle {
friend class MessageReplyDeserializer;
friend class SyncMessage;
+ friend struct mojo::internal::UnmappedNativeStructSerializerImpl;
+
#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)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
uint16_t num_fds; // the number of descriptors included with this message
uint16_t pad; // explicitly initialize this to appease valgrind
#endif
@@ -275,7 +282,7 @@ class IPC_EXPORT Message : public base::Pickle {
return attachment_set_.get();
}
-#ifdef IPC_MESSAGE_LOG_ENABLED
+#if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
// Used for logging.
mutable int64_t received_time_;
mutable std::string output_params_;
diff --git a/ipc/ipc_message_attachment.cc b/ipc/ipc_message_attachment.cc
index 83440ae8e0..55f0d3c504 100644
--- a/ipc/ipc_message_attachment.cc
+++ b/ipc/ipc_message_attachment.cc
@@ -4,12 +4,153 @@
#include "ipc/ipc_message_attachment.h"
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "ipc/ipc_mojo_handle_attachment.h"
+#include "mojo/public/cpp/system/platform_handle.h"
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include "base/posix/eintr_wrapper.h"
+#include "ipc/ipc_platform_file_attachment_posix.h"
+#endif
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include "ipc/mach_port_attachment_mac.h"
+#endif
+
+#if defined(OS_WIN)
+#include "ipc/handle_attachment_win.h"
+#endif
+
+#if defined(OS_FUCHSIA)
+#include "ipc/handle_attachment_fuchsia.h"
+#endif
+
namespace IPC {
-MessageAttachment::MessageAttachment() {
+namespace {
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+base::ScopedFD TakeOrDupFile(internal::PlatformFileAttachment* attachment) {
+ return attachment->Owns()
+ ? base::ScopedFD(attachment->TakePlatformFile())
+ : base::ScopedFD(HANDLE_EINTR(dup(attachment->file())));
+}
+#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+} // namespace
+
+MessageAttachment::MessageAttachment() = default;
+
+MessageAttachment::~MessageAttachment() = default;
+
+mojo::ScopedHandle MessageAttachment::TakeMojoHandle() {
+ switch (GetType()) {
+ case Type::MOJO_HANDLE:
+ return static_cast<internal::MojoHandleAttachment*>(this)->TakeHandle();
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ case Type::PLATFORM_FILE: {
+ // We dup() the handles in IPC::Message to transmit.
+ // IPC::MessageAttachmentSet has intricate lifetime semantics for FDs, so
+ // just to dup()-and-own them is the safest option.
+ base::ScopedFD file =
+ TakeOrDupFile(static_cast<internal::PlatformFileAttachment*>(this));
+ if (!file.is_valid()) {
+ DPLOG(WARNING) << "Failed to dup FD to transmit.";
+ return mojo::ScopedHandle();
+ }
+ return mojo::WrapPlatformFile(file.release());
+ }
+#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ case Type::MACH_PORT: {
+ auto* attachment = static_cast<internal::MachPortAttachmentMac*>(this);
+ MojoPlatformHandle platform_handle = {
+ sizeof(platform_handle), MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT,
+ static_cast<uint64_t>(attachment->get_mach_port())};
+ MojoHandle wrapped_handle;
+ if (MojoWrapPlatformHandle(&platform_handle, nullptr, &wrapped_handle) !=
+ MOJO_RESULT_OK) {
+ return mojo::ScopedHandle();
+ }
+ attachment->reset_mach_port_ownership();
+ return mojo::MakeScopedHandle(mojo::Handle(wrapped_handle));
+ }
+#elif defined(OS_FUCHSIA)
+ case Type::FUCHSIA_HANDLE: {
+ auto* attachment = static_cast<internal::HandleAttachmentFuchsia*>(this);
+ MojoPlatformHandle platform_handle = {
+ sizeof(platform_handle), MOJO_PLATFORM_HANDLE_TYPE_FUCHSIA_HANDLE,
+ static_cast<uint64_t>(attachment->Take())};
+ MojoHandle wrapped_handle;
+ if (MojoWrapPlatformHandle(&platform_handle, nullptr, &wrapped_handle) !=
+ MOJO_RESULT_OK) {
+ return mojo::ScopedHandle();
+ }
+ return mojo::MakeScopedHandle(mojo::Handle(wrapped_handle));
+ }
+#elif defined(OS_WIN)
+ case Type::WIN_HANDLE:
+ return mojo::WrapPlatformFile(
+ static_cast<internal::HandleAttachmentWin*>(this)->Take());
+#endif
+ default:
+ break;
+ }
+ NOTREACHED();
+ return mojo::ScopedHandle();
}
-MessageAttachment::~MessageAttachment() {
+// static
+scoped_refptr<MessageAttachment> MessageAttachment::CreateFromMojoHandle(
+ mojo::ScopedHandle handle,
+ Type type) {
+ if (type == Type::MOJO_HANDLE)
+ return new internal::MojoHandleAttachment(std::move(handle));
+
+ MojoPlatformHandle platform_handle = {sizeof(platform_handle), 0, 0};
+ MojoResult unwrap_result = MojoUnwrapPlatformHandle(
+ handle.release().value(), nullptr, &platform_handle);
+ if (unwrap_result != MOJO_RESULT_OK)
+ return nullptr;
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ if (type == Type::PLATFORM_FILE) {
+ base::PlatformFile file = base::kInvalidPlatformFile;
+ if (platform_handle.type == MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR)
+ file = static_cast<base::PlatformFile>(platform_handle.value);
+ return new internal::PlatformFileAttachment(file);
+ }
+#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ if (type == Type::MACH_PORT) {
+ mach_port_t mach_port = MACH_PORT_NULL;
+ if (platform_handle.type == MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT)
+ mach_port = static_cast<mach_port_t>(platform_handle.value);
+ return new internal::MachPortAttachmentMac(
+ mach_port, internal::MachPortAttachmentMac::FROM_WIRE);
+ }
+#elif defined(OS_FUCHSIA)
+ if (type == Type::FUCHSIA_HANDLE) {
+ zx::handle handle;
+ if (platform_handle.type == MOJO_PLATFORM_HANDLE_TYPE_FUCHSIA_HANDLE)
+ handle.reset(static_cast<zx_handle_t>(platform_handle.value));
+ return new internal::HandleAttachmentFuchsia(std::move(handle));
+ }
+#elif defined(OS_WIN)
+ if (type == Type::WIN_HANDLE) {
+ base::PlatformFile handle = base::kInvalidPlatformFile;
+ if (platform_handle.type == MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE)
+ handle = reinterpret_cast<base::PlatformFile>(platform_handle.value);
+ return new internal::HandleAttachmentWin(
+ handle, internal::HandleAttachmentWin::FROM_WIRE);
+ }
+#endif
+ NOTREACHED();
+ return nullptr;
}
} // namespace IPC
diff --git a/ipc/ipc_message_attachment.h b/ipc/ipc_message_attachment.h
index 9ff1de8c32..9c92f37b5a 100644
--- a/ipc/ipc_message_attachment.h
+++ b/ipc/ipc_message_attachment.h
@@ -10,19 +10,32 @@
#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"
+#include "ipc/ipc_message_support_export.h"
+#include "mojo/public/cpp/system/handle.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 {
+class IPC_MESSAGE_SUPPORT_EXPORT MessageAttachment
+ : public base::Pickle::Attachment {
public:
- using Type = mojom::SerializedHandle::Type;
+ enum class Type {
+ MOJO_HANDLE,
+ PLATFORM_FILE,
+ WIN_HANDLE,
+ MACH_PORT,
+ FUCHSIA_HANDLE,
+ };
+
+ static scoped_refptr<MessageAttachment> CreateFromMojoHandle(
+ mojo::ScopedHandle handle,
+ Type type);
virtual Type GetType() const = 0;
+ mojo::ScopedHandle TakeMojoHandle();
+
protected:
friend class base::RefCountedThreadSafe<MessageAttachment>;
MessageAttachment();
diff --git a/ipc/ipc_message_attachment_set.cc b/ipc/ipc_message_attachment_set.cc
index b9a990da8b..24c93a64bd 100644
--- a/ipc/ipc_message_attachment_set.cc
+++ b/ipc/ipc_message_attachment_set.cc
@@ -62,7 +62,7 @@ unsigned MessageAttachmentSet::size() const {
bool MessageAttachmentSet::AddAttachment(
scoped_refptr<MessageAttachment> attachment,
size_t* index) {
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
if (attachment->GetType() == MessageAttachment::Type::PLATFORM_FILE &&
num_descriptors() == kMaxDescriptorsPerMessage) {
DLOG(WARNING) << "Cannot add file descriptor. MessageAttachmentSet full.";
@@ -75,6 +75,7 @@ bool MessageAttachmentSet::AddAttachment(
case MessageAttachment::Type::MOJO_HANDLE:
case MessageAttachment::Type::WIN_HANDLE:
case MessageAttachment::Type::MACH_PORT:
+ case MessageAttachment::Type::FUCHSIA_HANDLE:
attachments_.push_back(attachment);
*index = attachments_.size() - 1;
return true;
@@ -110,14 +111,9 @@ scoped_refptr<MessageAttachment> MessageAttachmentSet::GetAttachmentAt(
//
// 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;
+ DLOG(WARNING) << "Attempted to double-read a message attachment, "
+ "returning a nullptr";
}
if (index != consumed_descriptor_highwater_)
diff --git a/ipc/ipc_message_attachment_set.h b/ipc/ipc_message_attachment_set.h
index de37211435..7a5b2a9db0 100644
--- a/ipc/ipc_message_attachment_set.h
+++ b/ipc/ipc_message_attachment_set.h
@@ -12,7 +12,7 @@
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "build/build_config.h"
-#include "ipc/ipc_export.h"
+#include "ipc/ipc_message_support_export.h"
namespace IPC {
@@ -26,7 +26,7 @@ class MessageAttachment;
// For ChannelNacl under SFI NaCl, only Type::PLATFORM_FILE is supported. In
// that case, the FD is sent over socket.
// -----------------------------------------------------------------------------
-class IPC_EXPORT MessageAttachmentSet
+class IPC_MESSAGE_SUPPORT_EXPORT MessageAttachmentSet
: public base::RefCountedThreadSafe<MessageAttachmentSet> {
public:
MessageAttachmentSet();
@@ -58,7 +58,7 @@ class IPC_EXPORT MessageAttachmentSet
// auto-close.
void CommitAllDescriptors();
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// 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
@@ -68,7 +68,7 @@ class IPC_EXPORT MessageAttachmentSet
// 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
+#endif // OS_POSIX || OS_FUCHSIA
// ---------------------------------------------------------------------------
diff --git a/ipc/ipc_message_attachment_set_posix_unittest.cc b/ipc/ipc_message_attachment_set_posix_unittest.cc
new file mode 100644
index 0000000000..a339cc3d56
--- /dev/null
+++ b/ipc/ipc_message_attachment_set_posix_unittest.cc
@@ -0,0 +1,152 @@
+// 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.
+
+// This test is POSIX only.
+
+#include "ipc/ipc_message_attachment_set.h"
+
+#include <fcntl.h>
+#include <stddef.h>
+#include <unistd.h>
+
+#include "base/posix/eintr_wrapper.h"
+#include "build/build_config.h"
+#include "ipc/ipc_platform_file_attachment_posix.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace IPC {
+namespace {
+
+// Get a safe file descriptor for test purposes.
+int GetSafeFd() {
+ return open("/dev/null", O_RDONLY);
+}
+
+// Returns true if fd was already closed. Closes fd if not closed.
+bool VerifyClosed(int fd) {
+ const int duped = HANDLE_EINTR(dup(fd));
+ if (duped != -1) {
+ EXPECT_NE(IGNORE_EINTR(close(duped)), -1);
+ EXPECT_NE(IGNORE_EINTR(close(fd)), -1);
+ return false;
+ }
+ return true;
+}
+
+int GetFdAt(MessageAttachmentSet* set, int id) {
+ return static_cast<internal::PlatformFileAttachment&>(
+ *set->GetAttachmentAt(id))
+ .TakePlatformFile();
+}
+
+// The MessageAttachmentSet will try and close some of the descriptor numbers
+// which we given it. This is the base descriptor value. It's great enough such
+// that no real descriptor will accidently be closed.
+static const int kFDBase = 50000;
+
+TEST(MessageAttachmentSet, BasicAdd) {
+ scoped_refptr<MessageAttachmentSet> set(new MessageAttachmentSet);
+
+ ASSERT_EQ(set->size(), 0u);
+ ASSERT_TRUE(set->empty());
+ ASSERT_TRUE(
+ set->AddAttachment(new internal::PlatformFileAttachment(kFDBase)));
+ ASSERT_EQ(set->size(), 1u);
+ ASSERT_TRUE(!set->empty());
+
+ // Empties the set and stops a warning about deleting a set with unconsumed
+ // descriptors
+ set->CommitAllDescriptors();
+}
+
+TEST(MessageAttachmentSet, BasicAddAndClose) {
+ scoped_refptr<MessageAttachmentSet> set(new MessageAttachmentSet);
+
+ ASSERT_EQ(set->size(), 0u);
+ ASSERT_TRUE(set->empty());
+ const int fd = GetSafeFd();
+ ASSERT_TRUE(set->AddAttachment(
+ new internal::PlatformFileAttachment(base::ScopedFD(fd))));
+ ASSERT_EQ(set->size(), 1u);
+ ASSERT_TRUE(!set->empty());
+
+ set->CommitAllDescriptors();
+
+ ASSERT_TRUE(VerifyClosed(fd));
+}
+TEST(MessageAttachmentSet, MaxSize) {
+ scoped_refptr<MessageAttachmentSet> set(new MessageAttachmentSet);
+
+ for (size_t i = 0; i < MessageAttachmentSet::kMaxDescriptorsPerMessage; ++i)
+ ASSERT_TRUE(set->AddAttachment(
+ new internal::PlatformFileAttachment(kFDBase + 1 + i)));
+
+ ASSERT_TRUE(
+ !set->AddAttachment(new internal::PlatformFileAttachment(kFDBase)));
+
+ set->CommitAllDescriptors();
+}
+
+TEST(MessageAttachmentSet, WalkInOrder) {
+ scoped_refptr<MessageAttachmentSet> set(new MessageAttachmentSet);
+
+ ASSERT_TRUE(
+ set->AddAttachment(new internal::PlatformFileAttachment(kFDBase)));
+ ASSERT_TRUE(
+ set->AddAttachment(new internal::PlatformFileAttachment(kFDBase + 1)));
+ ASSERT_TRUE(
+ set->AddAttachment(new internal::PlatformFileAttachment(kFDBase + 2)));
+
+ ASSERT_EQ(GetFdAt(set.get(), 0), kFDBase);
+ ASSERT_EQ(GetFdAt(set.get(), 1), kFDBase + 1);
+ ASSERT_EQ(GetFdAt(set.get(), 2), kFDBase + 2);
+ ASSERT_FALSE(set->GetAttachmentAt(0));
+
+ set->CommitAllDescriptors();
+}
+
+TEST(MessageAttachmentSet, WalkWrongOrder) {
+ scoped_refptr<MessageAttachmentSet> set(new MessageAttachmentSet);
+
+ ASSERT_TRUE(
+ set->AddAttachment(new internal::PlatformFileAttachment(kFDBase)));
+ ASSERT_TRUE(
+ set->AddAttachment(new internal::PlatformFileAttachment(kFDBase + 1)));
+ ASSERT_TRUE(
+ set->AddAttachment(new internal::PlatformFileAttachment(kFDBase + 2)));
+
+ ASSERT_EQ(GetFdAt(set.get(), 0), kFDBase);
+ ASSERT_FALSE(set->GetAttachmentAt(2));
+
+ set->CommitAllDescriptors();
+}
+
+#if defined(OS_ANDROID)
+#define MAYBE_DontClose DISABLED_DontClose
+#else
+#define MAYBE_DontClose DontClose
+#endif
+TEST(MessageAttachmentSet, MAYBE_DontClose) {
+ scoped_refptr<MessageAttachmentSet> set(new MessageAttachmentSet);
+
+ const int fd = GetSafeFd();
+ ASSERT_TRUE(set->AddAttachment(new internal::PlatformFileAttachment(fd)));
+ set->CommitAllDescriptors();
+
+ ASSERT_FALSE(VerifyClosed(fd));
+}
+
+TEST(MessageAttachmentSet, DoClose) {
+ scoped_refptr<MessageAttachmentSet> set(new MessageAttachmentSet);
+
+ const int fd = GetSafeFd();
+ ASSERT_TRUE(set->AddAttachment(
+ new internal::PlatformFileAttachment(base::ScopedFD(fd))));
+ set->CommitAllDescriptors();
+
+ ASSERT_TRUE(VerifyClosed(fd));
+}
+
+} // namespace
+} // namespace IPC
diff --git a/ipc/ipc_message_macros.h b/ipc/ipc_message_macros.h
new file mode 100644
index 0000000000..b9e5b91679
--- /dev/null
+++ b/ipc/ipc_message_macros.h
@@ -0,0 +1,560 @@
+// 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.
+
+// Defining IPC Messages
+//
+// Your IPC messages will be defined by macros inside of an XXX_messages.h
+// header file. Most of the time, the system can automatically generate all
+// of messaging mechanism from these definitions, but sometimes some manual
+// coding is required. In these cases, you will also have an XXX_messages.cc
+// implementation file as well.
+//
+// The senders of your messages will include your XXX_messages.h file to
+// get the full set of definitions they need to send your messages.
+//
+// Each XXX_messages.h file must be registered with the IPC system. This
+// requires adding two things:
+// - An XXXMsgStart value to the IPCMessageStart enum in ipc_message_start.h
+// - An inclusion of XXX_messages.h file in a message generator .h file
+//
+// The XXXMsgStart value is an enumeration that ensures uniqueness for
+// each different message file. Later, you will use this inside your
+// XXX_messages.h file before invoking message declaration macros:
+// #define IPC_MESSAGE_START XXXMsgStart
+// ( ... your macro invocations go here ... )
+//
+// Message Generator Files
+//
+// A message generator .h header file pulls in all other message-declaring
+// headers for a given component. It is included by a message generator
+// .cc file, which is where all the generated code will wind up. Typically,
+// you will use an existing generator (e.g. common_message_generator.cc
+// in /chrome/common), but there are circumstances where you may add a
+// new one.
+//
+// In the rare circumstances where you can't re-use an existing file,
+// your YYY_message_generator.cc file for a component YYY would contain
+// the following code:
+// // Get basic type definitions.
+// #define IPC_MESSAGE_IMPL
+// #include "path/to/YYY_message_generator.h"
+// // Generate constructors.
+// #include "ipc/struct_constructor_macros.h"
+// #include "path/to/YYY_message_generator.h"
+// // Generate destructors.
+// #include "ipc/struct_destructor_macros.h"
+// #include "path/to/YYY_message_generator.h"
+// // Generate param traits write methods.
+// #include "ipc/param_traits_write_macros.h"
+// namespace IPC {
+// #include "path/to/YYY_message_generator.h"
+// } // namespace IPC
+// // Generate param traits read methods.
+// #include "ipc/param_traits_read_macros.h"
+// namespace IPC {
+// #include "path/to/YYY_message_generator.h"
+// } // namespace IPC
+// // Generate param traits log methods.
+// #include "ipc/param_traits_log_macros.h"
+// namespace IPC {
+// #include "path/to/YYY_message_generator.h"
+// } // namespace IPC
+//
+// In cases where manual generation is required, in your XXX_messages.cc
+// file, put the following after all the includes for param types:
+// #define IPC_MESSAGE_IMPL
+// #include "XXX_messages.h"
+// (... implementation of traits not auto-generated ...)
+//
+// Multiple Inclusion
+//
+// The XXX_messages.h file will be multiply-included by the
+// YYY_message_generator.cc file, so your XXX_messages file can't be
+// guarded in the usual manner. Ideally, there will be no need for any
+// inclusion guard, since the XXX_messages.h file should consist solely
+// of inclusions of other headers (which are self-guarding) and IPC
+// macros (which are multiply evaluating).
+//
+// Note that #pragma once cannot be used here; doing so would mark the whole
+// file as being singly-included. Since your XXX_messages.h file is only
+// partially-guarded, care must be taken to ensure that it is only included
+// by other .cc files (and the YYY_message_generator.h file). Including an
+// XXX_messages.h file in some other .h file may result in duplicate
+// declarations and a compilation failure.
+//
+// Type Declarations
+//
+// It is generally a bad idea to have type definitions in a XXX_messages.h
+// file; most likely the typedef will then be used in the message, as opposed
+// to the struct itself. Later, an IPC message dispatcher will need to call
+// a function taking that type, and that function is declared in some other
+// header. Thus, in order to get the type definition, the other header
+// would have to include the XXX_messages.h file, violating the rule above
+// about not including XXX_messages.h file in other .h files.
+//
+// One approach here is to move these type definitions to another (guarded)
+// .h file and include this second .h in your XXX_messages.h file. This
+// is still less than ideal, because the dispatched function would have to
+// redeclare the typedef or include this second header. This may be
+// reasonable in a few cases.
+//
+// Failing all of the above, then you will want to bracket the smallest
+// possible section of your XXX_messages.h file containing these types
+// with an include guard macro. Be aware that providing an incomplete
+// class type declaration to avoid pulling in a long chain of headers is
+// acceptable when your XXX_messages.h header is being included by the
+// message sending caller's code, but not when the YYY_message_generator.c
+// is building the messages. In addition, due to the multiple inclusion
+// restriction, these type ought to be guarded. Follow a convention like:
+// #ifndef SOME_GUARD_MACRO
+// #define SOME_GUARD_MACRO
+// class some_class; // One incomplete class declaration
+// class_some_other_class; // Another incomplete class declaration
+// #endif // SOME_GUARD_MACRO
+// #ifdef IPC_MESSAGE_IMPL
+// #include "path/to/some_class.h" // Full class declaration
+// #include "path/to/some_other_class.h" // Full class declaration
+// #endif // IPC_MESSAGE_IMPL
+// (.. IPC macros using some_class and some_other_class ...)
+//
+// Macro Invocations
+//
+// You will use IPC message macro invocations for three things:
+// - New struct definitions for IPC
+// - Registering existing struct and enum definitions with IPC
+// - Defining the messages themselves
+//
+// New structs are defined with IPC_STRUCT_BEGIN(), IPC_STRUCT_MEMBER(),
+// IPC_STRUCT_END() family of macros. These cause the XXX_messages.h
+// to proclaim equivalent struct declarations for use by callers, as well
+// as later registering the type with the message generation. Note that
+// IPC_STRUCT_MEMBER() is only permitted inside matching calls to
+// IPC_STRUCT_BEGIN() / IPC_STRUCT_END(). There is also an
+// IPC_STRUCT_BEGIN_WITH_PARENT(), which behaves like IPC_STRUCT_BEGIN(),
+// but also accommodates structs that inherit from other structs.
+//
+// Externally-defined structs are registered with IPC_STRUCT_TRAITS_BEGIN(),
+// IPC_STRUCT_TRAITS_MEMBER(), and IPC_STRUCT_TRAITS_END() macros. These
+// cause registration of the types with message generation only.
+// There's also IPC_STRUCT_TRAITS_PARENT, which is used to register a parent
+// class (whose own traits are already defined). Note that
+// IPC_STRUCT_TRAITS_MEMBER() and IPC_STRUCT_TRAITS_PARENT are only permitted
+// inside matching calls to IPC_STRUCT_TRAITS_BEGIN() /
+// IPC_STRUCT_TRAITS_END().
+//
+// Enum types are registered with a single IPC_ENUM_TRAITS_VALIDATE() macro.
+// There is no need to enumerate each value to the IPC mechanism. Instead,
+// pass an expression in terms of the parameter |value| to provide
+// range-checking. For convenience, the IPC_ENUM_TRAITS() is provided which
+// performs no checking, passing everything including out-of-range values.
+// Its use is discouraged. The IPC_ENUM_TRAITS_MAX_VALUE() macro can be used
+// for the typical case where the enum must be in the range 0..maxvalue
+// inclusive. The IPC_ENUM_TRAITS_MIN_MAX_VALUE() macro can be used for the
+// less typical case where the enum must be in the range minvalue..maxvalue
+// inclusive.
+//
+// Do not place semicolons following these IPC_ macro invocations. There
+// is no reason to expect that their expansion corresponds one-to-one with
+// C++ statements.
+//
+// Once the types have been declared / registered, message definitions follow.
+// "Sync" messages are just synchronous calls, the Send() call doesn't return
+// until a reply comes back. To declare a sync message, use the IPC_SYNC_
+// macros. The numbers at the end show how many input/output parameters there
+// are (i.e. 1_2 is 1 in, 2 out). Input parameters are first, followed by
+// output parameters. The caller uses Send([route id, ], in1, &out1, &out2).
+// The receiver's handler function will be
+// void OnSyncMessageName(const type1& in1, type2* out1, type3* out2)
+//
+// A caller can also send a synchronous message, while the receiver can respond
+// at a later time. This is transparent from the sender's side. The receiver
+// needs to use a different handler that takes in a IPC::Message* as the output
+// type, stash the message, and when it has the data it can Send the message.
+//
+// Use the IPC_MESSAGE_HANDLER_DELAY_REPLY macro instead of IPC_MESSAGE_HANDLER
+// IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_SyncMessageName,
+// OnSyncMessageName)
+// Unlike IPC_MESSAGE_HANDLER which works with IPC_BEGIN_MESSAGE_MAP as well as
+// IPC_BEGIN_MESSAGE_MAP_WITH_PARAM, one needs to use
+// IPC_MESSAGE_HANDLER_WITH_PARAM_DELAY_REPLY to properly handle the param.
+//
+// The handler function will look like:
+// void OnSyncMessageName(const type1& in1, IPC::Message* reply_msg);
+//
+// Receiver stashes the IPC::Message* pointer, and when it's ready, it does:
+// ViewHostMsg_SyncMessageName::WriteReplyParams(reply_msg, out1, out2);
+// Send(reply_msg);
+
+// Files that want to export their ipc messages should do
+// #undef IPC_MESSAGE_EXPORT
+// #define IPC_MESSAGE_EXPORT VISIBILITY_MACRO
+// after including this header, but before using any of the macros below.
+// (This needs to be before the include guard.)
+#undef IPC_MESSAGE_EXPORT
+#define IPC_MESSAGE_EXPORT
+
+#ifndef IPC_IPC_MESSAGE_MACROS_H_
+#define IPC_IPC_MESSAGE_MACROS_H_
+
+#include <stdint.h>
+
+#include <tuple>
+
+#include "base/export_template.h"
+#include "ipc/ipc_message_templates.h"
+#include "ipc/ipc_message_utils.h"
+#include "ipc/param_traits_macros.h"
+
+// Convenience macro for defining structs without inheritance. Should not need
+// to be subsequently redefined.
+#define IPC_STRUCT_BEGIN(struct_name) \
+ IPC_STRUCT_BEGIN_WITH_PARENT(struct_name, IPC::NoParams)
+
+// Macros for defining structs. Will be subsequently redefined.
+#define IPC_STRUCT_BEGIN_WITH_PARENT(struct_name, parent) \
+ struct struct_name; \
+ IPC_STRUCT_TRAITS_BEGIN(struct_name) \
+ IPC_STRUCT_TRAITS_END() \
+ struct IPC_MESSAGE_EXPORT struct_name : parent { \
+ struct_name(); \
+ struct_name(const struct_name&) = default; \
+ struct_name(struct_name&&) = default; \
+ struct_name& operator=(const struct_name&) = default; \
+ struct_name& operator=(struct_name&&) = default; \
+ ~struct_name();
+// Optional variadic parameters specify the default value for this struct
+// member. They are passed through to the constructor for |type|.
+#define IPC_STRUCT_MEMBER(type, name, ...) type name;
+#define IPC_STRUCT_END() };
+
+// Message macros collect arguments and funnel them into the common message
+// generation macro. These should never be redefined.
+
+// Asynchronous messages have only in parameters and are declared like:
+// IPC_MESSAGE_CONTROL(FooMsg, int, float)
+#define IPC_MESSAGE_CONTROL(msg_class, ...) \
+ IPC_MESSAGE_DECL(msg_class, CONTROL, IPC_TUPLE(__VA_ARGS__), void)
+#define IPC_MESSAGE_ROUTED(msg_class, ...) \
+ IPC_MESSAGE_DECL(msg_class, ROUTED, IPC_TUPLE(__VA_ARGS__), void)
+
+// Synchronous messages have both in and out parameters, so the lists need to
+// be parenthesized to disambiguate:
+// IPC_SYNC_MESSAGE_CONTROL(BarMsg, (int, int), (bool))
+//
+// Implementation detail: The parentheses supplied by the caller for
+// disambiguation are also used to trigger the IPC_TUPLE invocations below,
+// so "IPC_TUPLE in" and "IPC_TUPLE out" are intentional.
+#define IPC_SYNC_MESSAGE_CONTROL(msg_class, in, out) \
+ IPC_MESSAGE_DECL(msg_class, CONTROL, IPC_TUPLE in, IPC_TUPLE out)
+#define IPC_SYNC_MESSAGE_ROUTED(msg_class, in, out) \
+ IPC_MESSAGE_DECL(msg_class, ROUTED, IPC_TUPLE in, IPC_TUPLE out)
+
+#define IPC_TUPLE(...) IPC::CheckedTuple<__VA_ARGS__>::Tuple
+
+#define IPC_MESSAGE_DECL(msg_name, kind, in_tuple, out_tuple) \
+ struct IPC_MESSAGE_EXPORT msg_name##_Meta { \
+ using InTuple = in_tuple; \
+ using OutTuple = out_tuple; \
+ enum { ID = IPC_MESSAGE_ID() }; \
+ static const IPC::MessageKind kKind = IPC::MessageKind::kind; \
+ static const char kName[]; \
+ }; \
+ extern template class EXPORT_TEMPLATE_DECLARE(IPC_MESSAGE_EXPORT) \
+ IPC::MessageT<msg_name##_Meta>; \
+ using msg_name = IPC::MessageT<msg_name##_Meta>; \
+ IPC_MESSAGE_EXTRA(msg_name)
+
+#if defined(IPC_MESSAGE_IMPL)
+
+// "Implementation" inclusion provides the explicit template definition
+// for msg_name.
+#define IPC_MESSAGE_EXTRA(msg_name) \
+ const char msg_name##_Meta::kName[] = #msg_name; \
+ IPC_MESSAGE_DEFINE_KIND(msg_name) \
+ template class EXPORT_TEMPLATE_DEFINE(IPC_MESSAGE_EXPORT) \
+ IPC::MessageT<msg_name##_Meta>;
+
+// MSVC has an intentionally non-compliant "feature" that results in LNK2005
+// ("symbol already defined") errors if we provide an out-of-line definition
+// for kKind. Microsoft's official response is to test for _MSC_EXTENSIONS:
+// https://connect.microsoft.com/VisualStudio/feedback/details/786583/
+#if defined(_MSC_EXTENSIONS)
+#define IPC_MESSAGE_DEFINE_KIND(msg_name)
+#else
+#define IPC_MESSAGE_DEFINE_KIND(msg_name) \
+ const IPC::MessageKind msg_name##_Meta::kKind;
+#endif
+
+#elif defined(IPC_MESSAGE_MACROS_LOG_ENABLED)
+
+#ifndef IPC_LOG_TABLE_ADD_ENTRY
+#error You need to define IPC_LOG_TABLE_ADD_ENTRY(msg_id, logger)
+#endif
+
+// "Log table" inclusion produces extra logging registration code.
+#define IPC_MESSAGE_EXTRA(msg_name) \
+ class LoggerRegisterHelper##msg_name { \
+ public: \
+ LoggerRegisterHelper##msg_name() { \
+ const uint32_t msg_id = static_cast<uint32_t>(msg_name::ID); \
+ IPC_LOG_TABLE_ADD_ENTRY(msg_id, msg_name::Log); \
+ } \
+ }; \
+ LoggerRegisterHelper##msg_name g_LoggerRegisterHelper##msg_name;
+
+#else
+
+// Normal inclusion produces nothing extra.
+#define IPC_MESSAGE_EXTRA(msg_name)
+
+#endif // defined(IPC_MESSAGE_IMPL)
+
+// Message IDs
+// Note: we currently use __LINE__ to give unique IDs to messages within
+// a file. They're globally unique since each file defines its own
+// IPC_MESSAGE_START.
+#define IPC_MESSAGE_ID() ((IPC_MESSAGE_START << 16) + __LINE__)
+#define IPC_MESSAGE_ID_CLASS(id) ((id) >> 16)
+#define IPC_MESSAGE_ID_LINE(id) ((id) & 0xffff)
+
+// Message crackers and handlers. Usage:
+//
+// bool MyClass::OnMessageReceived(const IPC::Message& msg) {
+// bool handled = true;
+// IPC_BEGIN_MESSAGE_MAP(MyClass, msg)
+// IPC_MESSAGE_HANDLER(MsgClassOne, OnMsgClassOne)
+// ...more handlers here ...
+// IPC_MESSAGE_HANDLER(MsgClassTen, OnMsgClassTen)
+// IPC_MESSAGE_UNHANDLED(handled = false)
+// IPC_END_MESSAGE_MAP()
+// return handled;
+// }
+
+
+#define IPC_BEGIN_MESSAGE_MAP(class_name, msg) \
+ { \
+ typedef class_name _IpcMessageHandlerClass ALLOW_UNUSED_TYPE; \
+ void* param__ = NULL; \
+ (void)param__; \
+ const IPC::Message& ipc_message__ = msg; \
+ switch (ipc_message__.type()) {
+
+#define IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(class_name, msg, param) \
+ { \
+ typedef class_name _IpcMessageHandlerClass ALLOW_UNUSED_TYPE; \
+ decltype(param) param__ = param; \
+ const IPC::Message& ipc_message__ = msg; \
+ switch (ipc_message__.type()) {
+
+#define IPC_MESSAGE_FORWARD(msg_class, obj, member_func) \
+ case msg_class::ID: { \
+ if (!msg_class::Dispatch(&ipc_message__, obj, this, param__, \
+ &member_func)) \
+ ipc_message__.set_dispatch_error(); \
+ } \
+ break;
+
+#define IPC_MESSAGE_HANDLER(msg_class, member_func) \
+ IPC_MESSAGE_FORWARD(msg_class, this, _IpcMessageHandlerClass::member_func)
+
+#define IPC_MESSAGE_FORWARD_DELAY_REPLY(msg_class, obj, member_func) \
+ case msg_class::ID: { \
+ if (!msg_class::DispatchDelayReply(&ipc_message__, obj, param__, \
+ &member_func)) \
+ ipc_message__.set_dispatch_error(); \
+ } \
+ break;
+
+#define IPC_MESSAGE_HANDLER_DELAY_REPLY(msg_class, member_func) \
+ IPC_MESSAGE_FORWARD_DELAY_REPLY(msg_class, this, \
+ _IpcMessageHandlerClass::member_func)
+
+#define IPC_MESSAGE_FORWARD_WITH_PARAM_DELAY_REPLY(msg_class, obj, \
+ member_func) \
+ case msg_class::ID: { \
+ if (!msg_class::DispatchWithParamDelayReply(&ipc_message__, obj, param__, \
+ &member_func)) \
+ ipc_message__.set_dispatch_error(); \
+ } \
+ break;
+
+#define IPC_MESSAGE_HANDLER_WITH_PARAM_DELAY_REPLY(msg_class, member_func) \
+ IPC_MESSAGE_FORWARD_WITH_PARAM_DELAY_REPLY( \
+ msg_class, this, _IpcMessageHandlerClass::member_func)
+
+#define IPC_MESSAGE_HANDLER_GENERIC(msg_class, code) \
+ case msg_class::ID: { \
+ code; \
+ } \
+ break;
+
+#define IPC_REPLY_HANDLER(func) \
+ case IPC_REPLY_ID: { \
+ func(ipc_message__); \
+ } \
+ break;
+
+
+#define IPC_MESSAGE_UNHANDLED(code) \
+ default: { \
+ code; \
+ } \
+ break;
+
+#define IPC_MESSAGE_UNHANDLED_ERROR() \
+ IPC_MESSAGE_UNHANDLED(NOTREACHED() << \
+ "Invalid message with type = " << \
+ ipc_message__.type())
+
+#define IPC_END_MESSAGE_MAP() \
+ } \
+}
+
+// This corresponds to an enum value from IPCMessageStart.
+#define IPC_MESSAGE_CLASS(message) IPC_MESSAGE_ID_CLASS((message).type())
+
+// Deprecated legacy macro names.
+// TODO(mdempsky): Replace uses with generic names.
+
+#define IPC_MESSAGE_CONTROL0(msg) IPC_MESSAGE_CONTROL(msg)
+#define IPC_MESSAGE_CONTROL1(msg, a) IPC_MESSAGE_CONTROL(msg, a)
+#define IPC_MESSAGE_CONTROL2(msg, a, b) IPC_MESSAGE_CONTROL(msg, a, b)
+#define IPC_MESSAGE_CONTROL3(msg, a, b, c) IPC_MESSAGE_CONTROL(msg, a, b, c)
+#define IPC_MESSAGE_CONTROL4(msg, a, b, c, d) \
+ IPC_MESSAGE_CONTROL(msg, a, b, c, d)
+#define IPC_MESSAGE_CONTROL5(msg, a, b, c, d, e) \
+ IPC_MESSAGE_CONTROL(msg, a, b, c, d, e)
+
+#define IPC_MESSAGE_ROUTED0(msg) IPC_MESSAGE_ROUTED(msg)
+#define IPC_MESSAGE_ROUTED1(msg, a) IPC_MESSAGE_ROUTED(msg, a)
+#define IPC_MESSAGE_ROUTED2(msg, a, b) IPC_MESSAGE_ROUTED(msg, a, b)
+#define IPC_MESSAGE_ROUTED3(msg, a, b, c) IPC_MESSAGE_ROUTED(msg, a, b, c)
+#define IPC_MESSAGE_ROUTED4(msg, a, b, c, d) IPC_MESSAGE_ROUTED(msg, a, b, c, d)
+#define IPC_MESSAGE_ROUTED5(msg, a, b, c, d, e) \
+ IPC_MESSAGE_ROUTED(msg, a, b, c, d, e)
+
+#define IPC_SYNC_MESSAGE_CONTROL0_0(msg) IPC_SYNC_MESSAGE_CONTROL(msg, (), ())
+#define IPC_SYNC_MESSAGE_CONTROL0_1(msg, a) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (), (a))
+#define IPC_SYNC_MESSAGE_CONTROL0_2(msg, a, b) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (), (a, b))
+#define IPC_SYNC_MESSAGE_CONTROL0_3(msg, a, b, c) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (), (a, b, c))
+#define IPC_SYNC_MESSAGE_CONTROL0_4(msg, a, b, c, d) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (), (a, b, c, d))
+#define IPC_SYNC_MESSAGE_CONTROL1_0(msg, a) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a), ())
+#define IPC_SYNC_MESSAGE_CONTROL1_1(msg, a, b) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a), (b))
+#define IPC_SYNC_MESSAGE_CONTROL1_2(msg, a, b, c) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a), (b, c))
+#define IPC_SYNC_MESSAGE_CONTROL1_3(msg, a, b, c, d) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a), (b, c, d))
+#define IPC_SYNC_MESSAGE_CONTROL1_4(msg, a, b, c, d, e) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a), (b, c, d, e))
+#define IPC_SYNC_MESSAGE_CONTROL2_0(msg, a, b) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b), ())
+#define IPC_SYNC_MESSAGE_CONTROL2_1(msg, a, b, c) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b), (c))
+#define IPC_SYNC_MESSAGE_CONTROL2_2(msg, a, b, c, d) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b), (c, d))
+#define IPC_SYNC_MESSAGE_CONTROL2_3(msg, a, b, c, d, e) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b), (c, d, e))
+#define IPC_SYNC_MESSAGE_CONTROL2_4(msg, a, b, c, d, e, f) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b), (c, d, e, f))
+#define IPC_SYNC_MESSAGE_CONTROL3_0(msg, a, b, c) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b, c), ())
+#define IPC_SYNC_MESSAGE_CONTROL3_1(msg, a, b, c, d) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b, c), (d))
+#define IPC_SYNC_MESSAGE_CONTROL3_2(msg, a, b, c, d, e) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b, c), (d, e))
+#define IPC_SYNC_MESSAGE_CONTROL3_3(msg, a, b, c, d, e, f) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b, c), (d, e, f))
+#define IPC_SYNC_MESSAGE_CONTROL3_4(msg, a, b, c, d, e, f, g) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b, c), (d, e, f, g))
+#define IPC_SYNC_MESSAGE_CONTROL4_0(msg, a, b, c, d) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b, c, d), ())
+#define IPC_SYNC_MESSAGE_CONTROL4_1(msg, a, b, c, d, e) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b, c, d), (e))
+#define IPC_SYNC_MESSAGE_CONTROL4_2(msg, a, b, c, d, e, f) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b, c, d), (e, f))
+#define IPC_SYNC_MESSAGE_CONTROL4_3(msg, a, b, c, d, e, f, g) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b, c, d), (e, f, g))
+#define IPC_SYNC_MESSAGE_CONTROL4_4(msg, a, b, c, d, e, f, g, h) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b, c, d), (e, f, g, h))
+#define IPC_SYNC_MESSAGE_CONTROL5_0(msg, a, b, c, d, e) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b, c, d, e), ())
+#define IPC_SYNC_MESSAGE_CONTROL5_1(msg, a, b, c, d, e, f) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b, c, d, e), (f))
+#define IPC_SYNC_MESSAGE_CONTROL5_2(msg, a, b, c, d, e, f, g) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b, c, d, e), (f, g))
+#define IPC_SYNC_MESSAGE_CONTROL5_3(msg, a, b, c, d, e, f, g, h) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b, c, d, e), (f, g, h))
+#define IPC_SYNC_MESSAGE_CONTROL5_4(msg, a, b, c, d, e, f, g, h, i) \
+ IPC_SYNC_MESSAGE_CONTROL(msg, (a, b, c, d, e), (f, g, h, i))
+
+#define IPC_SYNC_MESSAGE_ROUTED0_0(msg) IPC_SYNC_MESSAGE_ROUTED(msg, (), ())
+#define IPC_SYNC_MESSAGE_ROUTED0_1(msg, a) IPC_SYNC_MESSAGE_ROUTED(msg, (), (a))
+#define IPC_SYNC_MESSAGE_ROUTED0_2(msg, a, b) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (), (a, b))
+#define IPC_SYNC_MESSAGE_ROUTED0_3(msg, a, b, c) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (), (a, b, c))
+#define IPC_SYNC_MESSAGE_ROUTED0_4(msg, a, b, c, d) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (), (a, b, c, d))
+#define IPC_SYNC_MESSAGE_ROUTED1_0(msg, a) IPC_SYNC_MESSAGE_ROUTED(msg, (a), ())
+#define IPC_SYNC_MESSAGE_ROUTED1_1(msg, a, b) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a), (b))
+#define IPC_SYNC_MESSAGE_ROUTED1_2(msg, a, b, c) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a), (b, c))
+#define IPC_SYNC_MESSAGE_ROUTED1_3(msg, a, b, c, d) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a), (b, c, d))
+#define IPC_SYNC_MESSAGE_ROUTED1_4(msg, a, b, c, d, e) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a), (b, c, d, e))
+#define IPC_SYNC_MESSAGE_ROUTED2_0(msg, a, b) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b), ())
+#define IPC_SYNC_MESSAGE_ROUTED2_1(msg, a, b, c) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b), (c))
+#define IPC_SYNC_MESSAGE_ROUTED2_2(msg, a, b, c, d) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b), (c, d))
+#define IPC_SYNC_MESSAGE_ROUTED2_3(msg, a, b, c, d, e) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b), (c, d, e))
+#define IPC_SYNC_MESSAGE_ROUTED2_4(msg, a, b, c, d, e, f) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b), (c, d, e, f))
+#define IPC_SYNC_MESSAGE_ROUTED3_0(msg, a, b, c) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b, c), ())
+#define IPC_SYNC_MESSAGE_ROUTED3_1(msg, a, b, c, d) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b, c), (d))
+#define IPC_SYNC_MESSAGE_ROUTED3_2(msg, a, b, c, d, e) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b, c), (d, e))
+#define IPC_SYNC_MESSAGE_ROUTED3_3(msg, a, b, c, d, e, f) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b, c), (d, e, f))
+#define IPC_SYNC_MESSAGE_ROUTED3_4(msg, a, b, c, d, e, f, g) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b, c), (d, e, f, g))
+#define IPC_SYNC_MESSAGE_ROUTED4_0(msg, a, b, c, d) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b, c, d), ())
+#define IPC_SYNC_MESSAGE_ROUTED4_1(msg, a, b, c, d, e) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b, c, d), (e))
+#define IPC_SYNC_MESSAGE_ROUTED4_2(msg, a, b, c, d, e, f) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b, c, d), (e, f))
+#define IPC_SYNC_MESSAGE_ROUTED4_3(msg, a, b, c, d, e, f, g) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b, c, d), (e, f, g))
+#define IPC_SYNC_MESSAGE_ROUTED4_4(msg, a, b, c, d, e, f, g, h) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b, c, d), (e, f, g, h))
+#define IPC_SYNC_MESSAGE_ROUTED5_0(msg, a, b, c, d, e) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b, c, d, e), ())
+#define IPC_SYNC_MESSAGE_ROUTED5_1(msg, a, b, c, d, e, f) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b, c, d, e), (f))
+#define IPC_SYNC_MESSAGE_ROUTED5_2(msg, a, b, c, d, e, f, g) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b, c, d, e), (f, g))
+#define IPC_SYNC_MESSAGE_ROUTED5_3(msg, a, b, c, d, e, f, g, h) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b, c, d, e), (f, g, h))
+#define IPC_SYNC_MESSAGE_ROUTED5_4(msg, a, b, c, d, e, f, g, h, i) \
+ IPC_SYNC_MESSAGE_ROUTED(msg, (a, b, c, d, e), (f, g, h, i))
+
+#endif // IPC_IPC_MESSAGE_MACROS_H_
+
+// Clean up IPC_MESSAGE_START in this unguarded section so that the
+// XXX_messages.h files need not do so themselves. This makes the
+// XXX_messages.h files easier to write.
+#undef IPC_MESSAGE_START
diff --git a/ipc/ipc_message_null_macros.h b/ipc/ipc_message_null_macros.h
new file mode 100644
index 0000000000..6eab78419b
--- /dev/null
+++ b/ipc/ipc_message_null_macros.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.
+
+// No include guard, may be included multiple times.
+
+// NULL out all the macros that need NULLing, so that multiple includes of
+// the XXXX_messages_internal.h files will not generate noise.
+#undef IPC_STRUCT_BEGIN_WITH_PARENT
+#undef IPC_STRUCT_MEMBER
+#undef IPC_STRUCT_END
+#undef IPC_STRUCT_TRAITS_BEGIN
+#undef IPC_STRUCT_TRAITS_MEMBER
+#undef IPC_STRUCT_TRAITS_PARENT
+#undef IPC_STRUCT_TRAITS_END
+#undef IPC_ENUM_TRAITS_VALIDATE
+#undef IPC_MESSAGE_DECL
+
+#define IPC_STRUCT_BEGIN_WITH_PARENT(struct_name, parent)
+#define IPC_STRUCT_MEMBER(type, name, ...)
+#define IPC_STRUCT_END()
+#define IPC_STRUCT_TRAITS_BEGIN(struct_name)
+#define IPC_STRUCT_TRAITS_MEMBER(name)
+#define IPC_STRUCT_TRAITS_PARENT(type)
+#define IPC_STRUCT_TRAITS_END()
+#define IPC_ENUM_TRAITS_VALIDATE(enum_name, validation_expression)
+#define IPC_MESSAGE_DECL(...)
diff --git a/ipc/ipc_message_pipe_reader.cc b/ipc/ipc_message_pipe_reader.cc
new file mode 100644
index 0000000000..b055d413f4
--- /dev/null
+++ b/ipc/ipc_message_pipe_reader.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 "ipc/ipc_message_pipe_reader.h"
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "ipc/ipc_channel_mojo.h"
+#include "mojo/public/cpp/bindings/message.h"
+
+namespace IPC {
+namespace internal {
+
+MessagePipeReader::MessagePipeReader(
+ mojo::MessagePipeHandle pipe,
+ mojom::ChannelAssociatedPtr sender,
+ mojo::AssociatedInterfaceRequest<mojom::Channel> receiver,
+ MessagePipeReader::Delegate* delegate)
+ : delegate_(delegate),
+ sender_(std::move(sender)),
+ binding_(this, std::move(receiver)) {
+ sender_.set_connection_error_handler(
+ base::Bind(&MessagePipeReader::OnPipeError, base::Unretained(this),
+ MOJO_RESULT_FAILED_PRECONDITION));
+ binding_.set_connection_error_handler(
+ base::Bind(&MessagePipeReader::OnPipeError, base::Unretained(this),
+ MOJO_RESULT_FAILED_PRECONDITION));
+}
+
+MessagePipeReader::~MessagePipeReader() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // The pipe should be closed before deletion.
+}
+
+void MessagePipeReader::Close() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ sender_.reset();
+ if (binding_.is_bound())
+ binding_.Close();
+}
+
+bool MessagePipeReader::Send(std::unique_ptr<Message> message) {
+ CHECK(message->IsValid());
+ TRACE_EVENT_WITH_FLOW0(TRACE_DISABLED_BY_DEFAULT("ipc.flow"),
+ "MessagePipeReader::Send", message->flags(),
+ TRACE_EVENT_FLAG_FLOW_OUT);
+ base::Optional<std::vector<mojo::native::SerializedHandlePtr>> handles;
+ MojoResult result = MOJO_RESULT_OK;
+ result = ChannelMojo::ReadFromMessageAttachmentSet(message.get(), &handles);
+ if (result != MOJO_RESULT_OK)
+ return false;
+
+ if (!sender_)
+ return false;
+
+ sender_->Receive(MessageView(*message, std::move(handles)));
+ DVLOG(4) << "Send " << message->type() << ": " << message->size();
+ return true;
+}
+
+void MessagePipeReader::GetRemoteInterface(
+ const std::string& name,
+ mojo::ScopedInterfaceEndpointHandle handle) {
+ if (!sender_.is_bound())
+ return;
+ sender_->GetAssociatedInterface(
+ name, mojom::GenericInterfaceAssociatedRequest(std::move(handle)));
+}
+
+void MessagePipeReader::SetPeerPid(int32_t peer_pid) {
+ delegate_->OnPeerPidReceived(peer_pid);
+}
+
+void MessagePipeReader::Receive(MessageView message_view) {
+ if (!message_view.size()) {
+ delegate_->OnBrokenDataReceived();
+ return;
+ }
+ Message message(message_view.data(), message_view.size());
+ if (!message.IsValid()) {
+ delegate_->OnBrokenDataReceived();
+ return;
+ }
+
+ DVLOG(4) << "Receive " << message.type() << ": " << message.size();
+ MojoResult write_result = ChannelMojo::WriteToMessageAttachmentSet(
+ message_view.TakeHandles(), &message);
+ if (write_result != MOJO_RESULT_OK) {
+ OnPipeError(write_result);
+ return;
+ }
+
+ TRACE_EVENT_WITH_FLOW0(TRACE_DISABLED_BY_DEFAULT("ipc.flow"),
+ "MessagePipeReader::Receive",
+ message.flags(),
+ TRACE_EVENT_FLAG_FLOW_IN);
+ delegate_->OnMessageReceived(message);
+}
+
+void MessagePipeReader::GetAssociatedInterface(
+ const std::string& name,
+ mojom::GenericInterfaceAssociatedRequest request) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (delegate_)
+ delegate_->OnAssociatedInterfaceRequest(name, request.PassHandle());
+}
+
+void MessagePipeReader::OnPipeError(MojoResult error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ Close();
+
+ // NOTE: The delegate call below may delete |this|.
+ if (delegate_)
+ delegate_->OnPipeError();
+}
+
+} // namespace internal
+} // namespace IPC
diff --git a/ipc/ipc_message_pipe_reader.h b/ipc/ipc_message_pipe_reader.h
new file mode 100644
index 0000000000..e1c3fd494c
--- /dev/null
+++ b/ipc/ipc_message_pipe_reader.h
@@ -0,0 +1,114 @@
+// 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 IPC_IPC_MESSAGE_PIPE_READER_H_
+#define IPC_IPC_MESSAGE_PIPE_READER_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <vector>
+
+#include "base/atomicops.h"
+#include "base/compiler_specific.h"
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "base/process/process_handle.h"
+#include "base/threading/thread_checker.h"
+#include "ipc/ipc.mojom.h"
+#include "ipc/ipc_message.h"
+#include "mojo/public/cpp/bindings/associated_binding.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace IPC {
+namespace internal {
+
+// A helper class to handle bytestream directly over mojo::MessagePipe
+// in template-method pattern. MessagePipeReader manages the lifetime
+// of given MessagePipe and participates the event loop, and
+// read the stream and call the client when it is ready.
+//
+// Each client has to:
+//
+// * Provide a subclass implemenation of a specific use of a MessagePipe
+// and implement callbacks.
+// * Create the subclass instance with a MessagePipeHandle.
+// The constructor automatically start listening on the pipe.
+//
+// All functions must be called on the IO thread, except for Send(), which can
+// be called on any thread. All |Delegate| functions will be called on the IO
+// thread.
+//
+class COMPONENT_EXPORT(IPC) MessagePipeReader : public mojom::Channel {
+ public:
+ class Delegate {
+ public:
+ virtual void OnPeerPidReceived(int32_t peer_pid) = 0;
+ virtual void OnMessageReceived(const Message& message) = 0;
+ virtual void OnBrokenDataReceived() = 0;
+ virtual void OnPipeError() = 0;
+ virtual void OnAssociatedInterfaceRequest(
+ const std::string& name,
+ mojo::ScopedInterfaceEndpointHandle handle) = 0;
+ };
+
+ // Builds a reader that reads messages from |receive_handle| and lets
+ // |delegate| know.
+ //
+ // |pipe| is the message pipe handle corresponding to the channel's master
+ // interface. This is the message pipe underlying both |sender| and
+ // |receiver|.
+ //
+ // Both |sender| and |receiver| must be non-null.
+ //
+ // Note that MessagePipeReader doesn't delete |delegate|.
+ MessagePipeReader(mojo::MessagePipeHandle pipe,
+ mojom::ChannelAssociatedPtr sender,
+ mojo::AssociatedInterfaceRequest<mojom::Channel> receiver,
+ Delegate* delegate);
+ ~MessagePipeReader() override;
+
+ // Close and destroy the MessagePipe.
+ void Close();
+
+ // Return true if the MessagePipe is alive.
+ bool IsValid() { return sender_.is_bound(); }
+
+ // Sends an IPC::Message to the other end of the pipe. Safe to call from any
+ // thread.
+ bool Send(std::unique_ptr<Message> message);
+
+ // Requests an associated interface from the other end of the pipe.
+ void GetRemoteInterface(const std::string& name,
+ mojo::ScopedInterfaceEndpointHandle handle);
+
+ mojom::ChannelAssociatedPtr& sender() { return sender_; }
+
+ protected:
+ void OnPipeClosed();
+ void OnPipeError(MojoResult error);
+
+ private:
+ // mojom::Channel:
+ void SetPeerPid(int32_t peer_pid) override;
+ void Receive(MessageView message_view) override;
+ void GetAssociatedInterface(
+ const std::string& name,
+ mojom::GenericInterfaceAssociatedRequest request) override;
+
+ // |delegate_| is null once the message pipe is closed.
+ Delegate* delegate_;
+ mojom::ChannelAssociatedPtr sender_;
+ mojo::AssociatedBinding<mojom::Channel> binding_;
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessagePipeReader);
+};
+
+} // namespace internal
+} // namespace IPC
+
+#endif // IPC_IPC_MESSAGE_PIPE_READER_H_
diff --git a/ipc/ipc_message_protobuf_utils.h b/ipc/ipc_message_protobuf_utils.h
new file mode 100644
index 0000000000..0168b150fc
--- /dev/null
+++ b/ipc/ipc_message_protobuf_utils.h
@@ -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.
+
+#ifndef IPC_IPC_MESSAGE_PROTOBUF_UTILS_H_
+#define IPC_IPC_MESSAGE_PROTOBUF_UTILS_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_NACL_NONSFI)
+static_assert(false,
+ "ipc_message_protobuf_utils is not able to work with "
+ "nacl_nonsfi configuration.");
+#endif
+
+#include "base/pickle.h"
+#include "ipc/ipc_param_traits.h"
+#include "ipc/ipc_message_utils.h"
+#include "third_party/protobuf/src/google/protobuf/repeated_field.h"
+
+namespace IPC {
+
+template <class RepeatedFieldLike, class StorageType>
+struct RepeatedFieldParamTraits {
+ typedef RepeatedFieldLike param_type;
+ static void Write(base::Pickle* m, const param_type& p) {
+ WriteParam(m, p.size());
+ for (int i = 0; i < p.size(); i++)
+ WriteParam(m, p.Get(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;
+ // Avoid integer overflow / assertion failure in Reserve() function.
+ if (INT_MAX / sizeof(StorageType) <= static_cast<size_t>(size))
+ return false;
+ r->Reserve(size);
+ for (int i = 0; i < size; i++) {
+ if (!ReadParam(m, iter, r->Add()))
+ return false;
+ }
+ return true;
+ }
+ static void Log(const param_type& p, std::string* l) {
+ for (int i = 0; i < p.size(); ++i) {
+ if (i != 0)
+ l->append(" ");
+ LogParam(p.Get(i), l);
+ }
+ }
+};
+
+template <class P>
+struct ParamTraits<google::protobuf::RepeatedField<P>> :
+ RepeatedFieldParamTraits<google::protobuf::RepeatedField<P>, P> {};
+
+template <class P>
+struct ParamTraits<google::protobuf::RepeatedPtrField<P>> :
+ RepeatedFieldParamTraits<google::protobuf::RepeatedPtrField<P>, void*> {};
+
+} // namespace IPC
+
+#endif // IPC_IPC_MESSAGE_PROTOBUF_UTILS_H_
diff --git a/ipc/ipc_message_protobuf_utils_unittest.cc b/ipc/ipc_message_protobuf_utils_unittest.cc
new file mode 100644
index 0000000000..61421c6e42
--- /dev/null
+++ b/ipc/ipc_message_protobuf_utils_unittest.cc
@@ -0,0 +1,170 @@
+// 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 "build/build_config.h"
+
+#if defined(OS_NACL_NONSFI)
+static_assert(false,
+ "ipc_message_protobuf_utils is not able to work with nacl_nonsfi "
+ "configuration.");
+#endif
+
+#include "ipc/ipc_message_protobuf_utils.h"
+
+#include <initializer_list>
+
+#include "ipc/test_proto.pb.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<ipc_message_utils_test::TestMessage1> {
+ typedef ipc_message_utils_test::TestMessage1 param_type;
+ static void Write(base::Pickle* m, const param_type& p) {
+ WriteParam(m, p.number());
+ }
+ static bool Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r) {
+ int number;
+ if (!iter->ReadInt(&number))
+ return false;
+ r->set_number(number);
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<ipc_message_utils_test::TestMessage2> {
+ typedef ipc_message_utils_test::TestMessage2 param_type;
+ static void Write(base::Pickle* m, const param_type& p) {
+ WriteParam(m, p.numbers());
+ WriteParam(m, p.strings());
+ WriteParam(m, p.messages());
+ }
+ static bool Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r) {
+ return ReadParam(m, iter, r->mutable_numbers()) &&
+ ReadParam(m, iter, r->mutable_strings()) &&
+ ReadParam(m, iter, r->mutable_messages());
+ }
+};
+
+namespace {
+
+template <class P1, class P2>
+void AssertEqual(const P1& left, const P2& right) {
+ ASSERT_EQ(left, right);
+}
+
+template<>
+void AssertEqual(const int& left,
+ const ipc_message_utils_test::TestMessage1& right) {
+ ASSERT_EQ(left, right.number());
+}
+
+template <template<class> class RepeatedFieldLike, class P1, class P2>
+void AssertRepeatedFieldEquals(std::initializer_list<P1> expected,
+ const RepeatedFieldLike<P2>& fields) {
+ ASSERT_EQ(static_cast<int>(expected.size()), fields.size());
+ auto it = expected.begin();
+ int i = 0;
+ for (; it != expected.end(); it++, i++) {
+ AssertEqual(*it, fields.Get(i));
+ }
+}
+
+TEST(IPCMessageRepeatedFieldUtilsTest, RepeatedFieldShouldBeSerialized) {
+ ipc_message_utils_test::TestMessage2 message;
+ message.add_numbers(1);
+ message.add_numbers(100);
+ message.add_strings("abc");
+ message.add_strings("def");
+ message.add_messages()->set_number(1000);
+ message.add_messages()->set_number(10000);
+
+ base::Pickle pickle;
+ IPC::WriteParam(&pickle, message);
+
+ base::PickleIterator iter(pickle);
+ ipc_message_utils_test::TestMessage2 output;
+ ASSERT_TRUE(IPC::ReadParam(&pickle, &iter, &output));
+
+ AssertRepeatedFieldEquals({1, 100}, output.numbers());
+ AssertRepeatedFieldEquals({"abc", "def"}, output.strings());
+ AssertRepeatedFieldEquals({1000, 10000}, output.messages());
+}
+
+TEST(IPCMessageRepeatedFieldUtilsTest,
+ PartialEmptyRepeatedFieldShouldBeSerialized) {
+ ipc_message_utils_test::TestMessage2 message;
+ message.add_numbers(1);
+ message.add_numbers(100);
+ message.add_messages()->set_number(1000);
+ message.add_messages()->set_number(10000);
+
+ base::Pickle pickle;
+ IPC::WriteParam(&pickle, message);
+
+ base::PickleIterator iter(pickle);
+ ipc_message_utils_test::TestMessage2 output;
+ ASSERT_TRUE(IPC::ReadParam(&pickle, &iter, &output));
+
+ AssertRepeatedFieldEquals({1, 100}, output.numbers());
+ ASSERT_EQ(0, output.strings_size());
+ AssertRepeatedFieldEquals({1000, 10000}, output.messages());
+}
+
+TEST(IPCMessageRepeatedFieldUtilsTest, EmptyRepeatedFieldShouldBeSerialized) {
+ ipc_message_utils_test::TestMessage2 message;
+
+ base::Pickle pickle;
+ IPC::WriteParam(&pickle, message);
+
+ base::PickleIterator iter(pickle);
+ ipc_message_utils_test::TestMessage2 output;
+ ASSERT_TRUE(IPC::ReadParam(&pickle, &iter, &output));
+
+ ASSERT_EQ(0, output.numbers_size());
+ ASSERT_EQ(0, output.strings_size());
+ ASSERT_EQ(0, output.messages_size());
+}
+
+TEST(IPCMessageRepeatedFieldUtilsTest,
+ InvalidPickleShouldNotCrashRepeatedFieldDeserialization) {
+ base::Pickle pickle;
+ IPC::WriteParam(&pickle, INT_MAX);
+ IPC::WriteParam(&pickle, 0);
+ IPC::WriteParam(&pickle, INT_MAX);
+ IPC::WriteParam(&pickle, std::string());
+ IPC::WriteParam(&pickle, 0);
+
+ base::PickleIterator iter(pickle);
+ ipc_message_utils_test::TestMessage2 output;
+ ASSERT_FALSE(IPC::ReadParam(&pickle, &iter, &output));
+}
+
+// This test needs ~20 seconds in Debug mode, or ~4 seconds in Release mode.
+// See http://crbug.com/741866 for details.
+TEST(IPCMessageRepeatedFieldUtilsTest,
+ DISABLED_InvalidPickleShouldNotCrashRepeatedFieldDeserialization2) {
+ base::Pickle pickle;
+ IPC::WriteParam(&pickle, 256 * 1024 * 1024);
+ IPC::WriteParam(&pickle, 0);
+ IPC::WriteParam(&pickle, INT_MAX);
+ IPC::WriteParam(&pickle, std::string());
+ IPC::WriteParam(&pickle, 0);
+
+ base::PickleIterator iter(pickle);
+ ipc_message_utils_test::TestMessage2 output;
+ ASSERT_FALSE(IPC::ReadParam(&pickle, &iter, &output));
+}
+
+} // namespace
+
+} // namespace IPC
diff --git a/ipc/ipc_message_start.h b/ipc/ipc_message_start.h
index 13686355ef..9c829d3cfe 100644
--- a/ipc/ipc_message_start.h
+++ b/ipc/ipc_message_start.h
@@ -8,95 +8,57 @@
// 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.
+//
+// You should no longer be adding any new message classes. Instead, use mojo
+// for all new work.
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,
@@ -104,18 +66,7 @@ enum IPCMessageStart {
// internal code. Contact gunsch@ before changing/removing.
CastCryptoMsgStart,
CastChannelMsgStart,
- DataReductionProxyStart,
- ChromeAppBannerMsgStart,
- AttachmentBrokerMsgStart,
- RenderProcessMsgStart,
- PageLoadMetricsMsgStart,
- MemoryMsgStart,
IPCTestMsgStart,
- ArcInstanceMsgStart,
- ArcInstanceHostMsgStart,
- DistillerMsgStart,
- ArcCameraMsgStart,
- DWriteFontProxyMsgStart,
MediaPlayerDelegateMsgStart,
SurfaceViewManagerMsgStart,
ExtensionWorkerMsgStart,
diff --git a/ipc/ipc_message_support_export.h b/ipc/ipc_message_support_export.h
new file mode 100644
index 0000000000..abfa67afcb
--- /dev/null
+++ b/ipc/ipc_message_support_export.h
@@ -0,0 +1,31 @@
+// 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 IPC_IPC_MESSAGE_SUPPORT_EXPORT_H_
+#define IPC_IPC_MESSAGE_SUPPORT_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(IPC_MESSAGE_SUPPORT_IMPL)
+#define IPC_MESSAGE_SUPPORT_EXPORT __declspec(dllexport)
+#else
+#define IPC_MESSAGE_SUPPORT_EXPORT __declspec(dllimport)
+#endif // defined(IPC_MESSAGE_SUPPORT_IMPL)
+
+#else // defined(WIN32)
+
+#if defined(IPC_MESSAGE_SUPPORT_IMPL)
+#define IPC_MESSAGE_SUPPORT_EXPORT __attribute__((visibility("default")))
+#else
+#define IPC_MESSAGE_SUPPORT_EXPORT
+#endif
+
+#endif
+
+#else // defined(COMPONENT_BUILD)
+#define IPC_MESSAGE_SUPPORT_EXPORT
+#endif
+
+#endif // IPC_IPC_MESSAGE_SUPPORT_EXPORT_H_
diff --git a/ipc/ipc_message_templates.h b/ipc/ipc_message_templates.h
new file mode 100644
index 0000000000..c856ed9810
--- /dev/null
+++ b/ipc/ipc_message_templates.h
@@ -0,0 +1,269 @@
+// 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_MESSAGE_TEMPLATES_H_
+#define IPC_IPC_MESSAGE_TEMPLATES_H_
+
+#include <stdint.h>
+
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/trace_event/trace_event.h"
+#include "base/tuple.h"
+#include "build/build_config.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_utils.h"
+
+namespace IPC {
+
+template <typename Tuple, size_t... Ns>
+auto TupleForwardImpl(Tuple&& tuple, std::index_sequence<Ns...>) -> decltype(
+ std::forward_as_tuple(std::get<Ns>(std::forward<Tuple>(tuple))...)) {
+ return std::forward_as_tuple(std::get<Ns>(std::forward<Tuple>(tuple))...);
+}
+
+// Transforms std::tuple contents to the forwarding form.
+// Example:
+// std::tuple<int, int&, const int&, int&&>&&
+// -> std::tuple<int&&, int&, const int&, int&&>.
+// const std::tuple<int, const int&, int&&>&
+// -> std::tuple<const int&, int&, const int&, int&>.
+//
+// TupleForward(std::make_tuple(a, b, c)) is equivalent to
+// std::forward_as_tuple(a, b, c).
+template <typename Tuple>
+auto TupleForward(Tuple&& tuple) -> decltype(TupleForwardImpl(
+ std::forward<Tuple>(tuple),
+ std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>())) {
+ return TupleForwardImpl(
+ std::forward<Tuple>(tuple),
+ std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>());
+}
+
+// This function is for all the async IPCs that don't pass an extra parameter
+// using IPC_BEGIN_MESSAGE_MAP_WITH_PARAM.
+template <typename ObjT, typename Method, typename P, typename Tuple>
+void DispatchToMethod(ObjT* obj, Method method, P*, Tuple&& tuple) {
+ base::DispatchToMethod(obj, method, std::forward<Tuple>(tuple));
+}
+
+template <typename ObjT,
+ typename Method,
+ typename P,
+ typename Tuple,
+ size_t... Ns>
+void DispatchToMethodImpl(ObjT* obj,
+ Method method,
+ P* parameter,
+ Tuple&& tuple,
+ std::index_sequence<Ns...>) {
+ (obj->*method)(parameter, std::get<Ns>(std::forward<Tuple>(tuple))...);
+}
+
+// The following function is for async IPCs which have a dispatcher with an
+// extra parameter specified using IPC_BEGIN_MESSAGE_MAP_WITH_PARAM.
+template <typename ObjT, typename P, typename... Args, typename Tuple>
+std::enable_if_t<sizeof...(Args) == std::tuple_size<std::decay_t<Tuple>>::value>
+DispatchToMethod(ObjT* obj,
+ void (ObjT::*method)(P*, Args...),
+ P* parameter,
+ Tuple&& tuple) {
+ constexpr size_t size = std::tuple_size<std::decay_t<Tuple>>::value;
+ DispatchToMethodImpl(obj, method, parameter, std::forward<Tuple>(tuple),
+ std::make_index_sequence<size>());
+}
+
+enum class MessageKind {
+ CONTROL,
+ ROUTED,
+};
+
+// Routing is a helper struct so MessageT's private common constructor has a
+// different type signature than the public "int32_t routing_id" one.
+struct Routing {
+ explicit Routing(int32_t id) : id(id) {}
+ int32_t id;
+};
+
+// We want to restrict MessageT's constructors so that a routing_id is always
+// provided for ROUTED messages and never provided for CONTROL messages, so
+// use the SFINAE technique from N4387's "Implementation Hint" section.
+#if defined(COMPILER_MSVC)
+// MSVC 2013 doesn't support default arguments for template member functions
+// of templated classes, so there we have to rely on the DCHECKs instead.
+// TODO(mdempsky): Reevaluate once MSVC 2015.
+#define IPC_MESSAGET_SFINAE(x)
+#else
+#define IPC_MESSAGET_SFINAE(x) \
+ template <bool X = (x), typename std::enable_if<X, bool>::type = false>
+#endif
+
+// MessageT is the common template used for all user-defined message types.
+// It's intended to be used via the macros defined in ipc_message_macros.h.
+template <typename Meta,
+ typename InTuple = typename Meta::InTuple,
+ typename OutTuple = typename Meta::OutTuple>
+class MessageT;
+
+// Asynchronous message partial specialization.
+template <typename Meta, typename... Ins>
+class MessageT<Meta, std::tuple<Ins...>, void> : public Message {
+ public:
+ using Param = std::tuple<Ins...>;
+ enum { ID = Meta::ID };
+
+ // TODO(mdempsky): Remove. Uses of MyMessage::Schema::Param can be replaced
+ // with just MyMessage::Param.
+ using Schema = MessageT;
+
+ IPC_MESSAGET_SFINAE(Meta::kKind == MessageKind::CONTROL)
+ MessageT(const Ins&... ins) : MessageT(Routing(MSG_ROUTING_CONTROL), ins...) {
+ DCHECK(Meta::kKind == MessageKind::CONTROL) << Meta::kName;
+ }
+
+ IPC_MESSAGET_SFINAE(Meta::kKind == MessageKind::ROUTED)
+ MessageT(int32_t routing_id, const Ins&... ins)
+ : MessageT(Routing(routing_id), ins...) {
+ DCHECK(Meta::kKind == MessageKind::ROUTED) << Meta::kName;
+ }
+
+ static bool Read(const Message* msg, Param* p);
+ static void Log(std::string* name, const Message* msg, std::string* l);
+
+ template <class T, class S, class P, class Method>
+ static bool Dispatch(const Message* msg,
+ T* obj,
+ S* sender,
+ P* parameter,
+ Method func) {
+ TRACE_EVENT0("ipc", Meta::kName);
+ Param p;
+ if (Read(msg, &p)) {
+ DispatchToMethod(obj, func, parameter, std::move(p));
+ return true;
+ }
+ return false;
+ }
+
+ private:
+ MessageT(Routing routing, const Ins&... ins);
+};
+
+// Synchronous message partial specialization.
+template <typename Meta, typename... Ins, typename... Outs>
+class MessageT<Meta, std::tuple<Ins...>, std::tuple<Outs...>>
+ : public SyncMessage {
+ public:
+ using SendParam = std::tuple<Ins...>;
+ using ReplyParam = std::tuple<Outs...>;
+ enum { ID = Meta::ID };
+
+ // TODO(mdempsky): Remove. Uses of MyMessage::Schema::{Send,Reply}Param can
+ // be replaced with just MyMessage::{Send,Reply}Param.
+ using Schema = MessageT;
+
+ IPC_MESSAGET_SFINAE(Meta::kKind == MessageKind::CONTROL)
+ MessageT(const Ins&... ins, Outs*... outs)
+ : MessageT(Routing(MSG_ROUTING_CONTROL), ins..., outs...) {
+ DCHECK(Meta::kKind == MessageKind::CONTROL) << Meta::kName;
+ }
+
+ IPC_MESSAGET_SFINAE(Meta::kKind == MessageKind::ROUTED)
+ MessageT(int32_t routing_id, const Ins&... ins, Outs*... outs)
+ : MessageT(Routing(routing_id), ins..., outs...) {
+ DCHECK(Meta::kKind == MessageKind::ROUTED) << Meta::kName;
+ }
+
+ static bool ReadSendParam(const Message* msg, SendParam* p);
+ static bool ReadReplyParam(const Message* msg, ReplyParam* p);
+ static void WriteReplyParams(Message* reply, const Outs&... outs);
+ static void Log(std::string* name, const Message* msg, std::string* l);
+
+ template <class T, class S, class P, class Method>
+ static bool Dispatch(const Message* msg,
+ T* obj,
+ S* sender,
+ P* /* parameter */,
+ Method func) {
+ TRACE_EVENT0("ipc", Meta::kName);
+ SendParam send_params;
+ bool ok = ReadSendParam(msg, &send_params);
+ Message* reply = SyncMessage::GenerateReply(msg);
+ if (!ok) {
+ NOTREACHED() << "Error deserializing message " << msg->type();
+ reply->set_reply_error();
+ sender->Send(reply);
+ return false;
+ }
+
+ ReplyParam reply_params;
+ base::DispatchToMethod(obj, func, std::move(send_params), &reply_params);
+ WriteParam(reply, reply_params);
+ LogReplyParamsToMessage(reply_params, msg);
+ sender->Send(reply);
+ return true;
+ }
+
+ template <class T, class P, class Method>
+ static bool DispatchDelayReply(const Message* msg,
+ T* obj,
+ P* /* parameter */,
+ Method func) {
+ TRACE_EVENT0("ipc", Meta::kName);
+ SendParam send_params;
+ bool ok = ReadSendParam(msg, &send_params);
+ Message* reply = SyncMessage::GenerateReply(msg);
+ if (!ok) {
+ NOTREACHED() << "Error deserializing message " << msg->type();
+ reply->set_reply_error();
+ obj->Send(reply);
+ return false;
+ }
+
+ std::tuple<Message&> t = std::tie(*reply);
+ ConnectMessageAndReply(msg, reply);
+ base::DispatchToMethod(obj, func, std::move(send_params), &t);
+ return true;
+ }
+
+ template <class T, class P, class Method>
+ static bool DispatchWithParamDelayReply(const Message* msg,
+ T* obj,
+ P* parameter,
+ Method func) {
+ TRACE_EVENT0("ipc", Meta::kName);
+ SendParam send_params;
+ bool ok = ReadSendParam(msg, &send_params);
+ Message* reply = SyncMessage::GenerateReply(msg);
+ if (!ok) {
+ NOTREACHED() << "Error deserializing message " << msg->type();
+ reply->set_reply_error();
+ obj->Send(reply);
+ return false;
+ }
+
+ std::tuple<Message&> t = std::tie(*reply);
+ ConnectMessageAndReply(msg, reply);
+ std::tuple<P*> parameter_tuple(parameter);
+ base::DispatchToMethod(
+ obj, func,
+ std::tuple_cat(std::move(parameter_tuple), TupleForward(send_params)),
+ &t);
+ return true;
+ }
+
+ private:
+ MessageT(Routing routing, const Ins&... ins, Outs*... outs);
+};
+
+} // namespace IPC
+
+#if defined(IPC_MESSAGE_IMPL)
+#include "ipc/ipc_message_templates_impl.h"
+#endif
+
+#endif // IPC_IPC_MESSAGE_TEMPLATES_H_
diff --git a/ipc/ipc_message_templates_impl.h b/ipc/ipc_message_templates_impl.h
new file mode 100644
index 0000000000..192d2efb93
--- /dev/null
+++ b/ipc/ipc_message_templates_impl.h
@@ -0,0 +1,112 @@
+// 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_MESSAGE_TEMPLATES_IMPL_H_
+#define IPC_IPC_MESSAGE_TEMPLATES_IMPL_H_
+
+#include <tuple>
+
+namespace IPC {
+
+template <typename... Ts>
+class ParamDeserializer : public MessageReplyDeserializer {
+ public:
+ explicit ParamDeserializer(const std::tuple<Ts&...>& out) : out_(out) {}
+
+ bool SerializeOutputParameters(const IPC::Message& msg,
+ base::PickleIterator iter) override {
+ return ReadParam(&msg, &iter, &out_);
+ }
+
+ std::tuple<Ts&...> out_;
+};
+
+template <typename Meta, typename... Ins>
+MessageT<Meta, std::tuple<Ins...>, void>::MessageT(Routing routing,
+ const Ins&... ins)
+ : Message(routing.id, ID, PRIORITY_NORMAL) {
+ WriteParam(this, std::tie(ins...));
+}
+
+template <typename Meta, typename... Ins>
+bool MessageT<Meta, std::tuple<Ins...>, void>::Read(const Message* msg,
+ Param* p) {
+ base::PickleIterator iter(*msg);
+ return ReadParam(msg, &iter, p);
+}
+
+template <typename Meta, typename... Ins>
+void MessageT<Meta, std::tuple<Ins...>, void>::Log(std::string* name,
+ const Message* msg,
+ std::string* l) {
+ if (name)
+ *name = Meta::kName;
+ if (!msg || !l)
+ return;
+ Param p;
+ if (Read(msg, &p))
+ LogParam(p, l);
+}
+
+template <typename Meta, typename... Ins, typename... Outs>
+MessageT<Meta, std::tuple<Ins...>, std::tuple<Outs...>>::MessageT(
+ Routing routing,
+ const Ins&... ins,
+ Outs*... outs)
+ : SyncMessage(
+ routing.id,
+ ID,
+ PRIORITY_NORMAL,
+ new ParamDeserializer<Outs...>(std::tie(*outs...))) {
+ WriteParam(this, std::tie(ins...));
+}
+
+template <typename Meta, typename... Ins, typename... Outs>
+bool MessageT<Meta, std::tuple<Ins...>, std::tuple<Outs...>>::ReadSendParam(
+ const Message* msg,
+ SendParam* p) {
+ base::PickleIterator iter = SyncMessage::GetDataIterator(msg);
+ return ReadParam(msg, &iter, p);
+}
+
+template <typename Meta, typename... Ins, typename... Outs>
+bool MessageT<Meta, std::tuple<Ins...>, std::tuple<Outs...>>::ReadReplyParam(
+ const Message* msg,
+ ReplyParam* p) {
+ base::PickleIterator iter = SyncMessage::GetDataIterator(msg);
+ return ReadParam(msg, &iter, p);
+}
+
+template <typename Meta, typename... Ins, typename... Outs>
+void MessageT<Meta,
+ std::tuple<Ins...>,
+ std::tuple<Outs...>>::WriteReplyParams(Message* reply,
+ const Outs&... outs) {
+ WriteParam(reply, std::tie(outs...));
+}
+
+template <typename Meta, typename... Ins, typename... Outs>
+void MessageT<Meta, std::tuple<Ins...>, std::tuple<Outs...>>::Log(
+ std::string* name,
+ const Message* msg,
+ std::string* l) {
+ if (name)
+ *name = Meta::kName;
+ if (!msg || !l)
+ return;
+ if (msg->is_sync()) {
+ SendParam p;
+ if (ReadSendParam(msg, &p))
+ LogParam(p, l);
+ AddOutputParamsToLog(msg, l);
+ } else {
+ ReplyParam p;
+ if (ReadReplyParam(msg, &p))
+ LogParam(p, l);
+ }
+}
+
+} // namespace IPC
+
+#endif // IPC_IPC_MESSAGE_TEMPLATES_IMPL_H_
diff --git a/ipc/ipc_message_unittest.cc b/ipc/ipc_message_unittest.cc
new file mode 100644
index 0000000000..5d9da31715
--- /dev/null
+++ b/ipc/ipc_message_unittest.cc
@@ -0,0 +1,281 @@
+// 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 <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <limits>
+#include <memory>
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "ipc/ipc_message_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// IPC messages for testing ----------------------------------------------------
+
+#define IPC_MESSAGE_IMPL
+#include "ipc/ipc_message_macros.h"
+
+#define IPC_MESSAGE_START TestMsgStart
+
+IPC_MESSAGE_CONTROL0(TestMsgClassEmpty)
+
+IPC_MESSAGE_CONTROL1(TestMsgClassI, int)
+
+IPC_SYNC_MESSAGE_CONTROL1_1(TestMsgClassIS, int, std::string)
+
+namespace IPC {
+
+TEST(IPCMessageTest, BasicMessageTest) {
+ int v1 = 10;
+ std::string v2("foobar");
+ base::string16 v3(base::ASCIIToUTF16("hello world"));
+
+ IPC::Message m(0, 1, IPC::Message::PRIORITY_NORMAL);
+ m.WriteInt(v1);
+ m.WriteString(v2);
+ m.WriteString16(v3);
+
+ base::PickleIterator iter(m);
+
+ int vi;
+ std::string vs;
+ base::string16 vs16;
+
+ EXPECT_TRUE(iter.ReadInt(&vi));
+ EXPECT_EQ(v1, vi);
+
+ EXPECT_TRUE(iter.ReadString(&vs));
+ EXPECT_EQ(v2, vs);
+
+ EXPECT_TRUE(iter.ReadString16(&vs16));
+ EXPECT_EQ(v3, vs16);
+
+ // should fail
+ EXPECT_FALSE(iter.ReadInt(&vi));
+ EXPECT_FALSE(iter.ReadString(&vs));
+ EXPECT_FALSE(iter.ReadString16(&vs16));
+}
+
+TEST(IPCMessageTest, ListValue) {
+ base::ListValue input;
+ input.AppendDouble(42.42);
+ input.AppendString("forty");
+ input.Append(std::make_unique<base::Value>());
+
+ IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+ IPC::WriteParam(&msg, input);
+
+ base::ListValue output;
+ base::PickleIterator iter(msg);
+ EXPECT_TRUE(IPC::ReadParam(&msg, &iter, &output));
+
+ EXPECT_TRUE(input.Equals(&output));
+
+ // Also test the corrupt case.
+ IPC::Message bad_msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+ bad_msg.WriteInt(99);
+ iter = base::PickleIterator(bad_msg);
+ EXPECT_FALSE(IPC::ReadParam(&bad_msg, &iter, &output));
+}
+
+TEST(IPCMessageTest, DictionaryValue) {
+ base::DictionaryValue input;
+ input.Set("null", std::make_unique<base::Value>());
+ input.SetBoolean("bool", true);
+ input.SetInteger("int", 42);
+ input.SetKey("int.with.dot", base::Value(43));
+
+ auto subdict = std::make_unique<base::DictionaryValue>();
+ subdict->SetString("str", "forty two");
+ subdict->SetBoolean("bool", false);
+
+ auto sublist = std::make_unique<base::ListValue>();
+ sublist->AppendDouble(42.42);
+ sublist->AppendString("forty");
+ sublist->AppendString("two");
+ subdict->Set("list", std::move(sublist));
+
+ input.Set("dict", std::move(subdict));
+
+ IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+ IPC::WriteParam(&msg, input);
+
+ base::DictionaryValue output;
+ base::PickleIterator iter(msg);
+ EXPECT_TRUE(IPC::ReadParam(&msg, &iter, &output));
+
+ EXPECT_TRUE(input.Equals(&output));
+
+ // Also test the corrupt case.
+ IPC::Message bad_msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+ bad_msg.WriteInt(99);
+ iter = base::PickleIterator(bad_msg);
+ EXPECT_FALSE(IPC::ReadParam(&bad_msg, &iter, &output));
+}
+
+TEST(IPCMessageTest, FindNext) {
+ IPC::Message message;
+ message.WriteString("Goooooooogle");
+ message.WriteInt(111);
+
+ std::vector<char> message_data(message.size() + 7);
+ memcpy(message_data.data(), message.data(), message.size());
+
+ const char* data_start = message_data.data();
+ const char* data_end = data_start + message.size();
+
+ IPC::Message::NextMessageInfo next;
+
+ // Data range contains the entire message plus some extra bytes
+ IPC::Message::FindNext(data_start, data_end + 1, &next);
+ EXPECT_TRUE(next.message_found);
+ EXPECT_EQ(next.message_size, message.size());
+ EXPECT_EQ(next.pickle_end, data_end);
+ EXPECT_EQ(next.message_end, data_end);
+
+ // Data range exactly contains the entire message
+ IPC::Message::FindNext(data_start, data_end, &next);
+ EXPECT_TRUE(next.message_found);
+ EXPECT_EQ(next.message_size, message.size());
+ EXPECT_EQ(next.pickle_end, data_end);
+ EXPECT_EQ(next.message_end, data_end);
+
+ // Data range doesn't contain the entire message
+ // (but contains the message header)
+ IPC::Message::FindNext(data_start, data_end - 1, &next);
+ EXPECT_FALSE(next.message_found);
+ EXPECT_EQ(next.message_size, message.size());
+
+ // Data range doesn't contain the message header
+ // (but contains the pickle header)
+ IPC::Message::FindNext(data_start,
+ data_start + sizeof(IPC::Message::Header) - 1,
+ &next);
+ EXPECT_FALSE(next.message_found);
+ EXPECT_EQ(next.message_size, 0u);
+
+ // Data range doesn't contain the pickle header
+ IPC::Message::FindNext(data_start,
+ data_start + sizeof(base::Pickle::Header) - 1,
+ &next);
+ EXPECT_FALSE(next.message_found);
+ EXPECT_EQ(next.message_size, 0u);
+}
+
+TEST(IPCMessageTest, FindNextOverflow) {
+ IPC::Message message;
+ message.WriteString("Data");
+ message.WriteInt(777);
+
+ const char* data_start = reinterpret_cast<const char*>(message.data());
+ const char* data_end = data_start + message.size();
+
+ IPC::Message::NextMessageInfo next;
+
+ // Payload size is negative (defeats 'start + size > end' check)
+ message.header()->payload_size = static_cast<uint32_t>(-1);
+ IPC::Message::FindNext(data_start, data_end, &next);
+ EXPECT_FALSE(next.message_found);
+ if (sizeof(size_t) > sizeof(uint32_t)) {
+ // No overflow, just insane message size
+ EXPECT_EQ(next.message_size,
+ message.header()->payload_size + sizeof(IPC::Message::Header));
+ } else {
+ // Actual overflow, reported as max size_t
+ EXPECT_EQ(next.message_size, std::numeric_limits<size_t>::max());
+ }
+
+ // Payload size is max positive integer (defeats size < 0 check, while
+ // still potentially causing overflow down the road).
+ message.header()->payload_size = std::numeric_limits<int32_t>::max();
+ IPC::Message::FindNext(data_start, data_end, &next);
+ EXPECT_FALSE(next.message_found);
+ EXPECT_EQ(next.message_size,
+ message.header()->payload_size + sizeof(IPC::Message::Header));
+}
+
+namespace {
+
+class IPCMessageParameterTest : public testing::Test {
+ public:
+ IPCMessageParameterTest() : extra_param_("extra_param"), called_(false) {}
+
+ bool OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(IPCMessageParameterTest, message,
+ &extra_param_)
+ IPC_MESSAGE_HANDLER(TestMsgClassEmpty, OnEmpty)
+ IPC_MESSAGE_HANDLER(TestMsgClassI, OnInt)
+ //IPC_MESSAGE_HANDLER(TestMsgClassIS, OnSync)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+
+ return handled;
+ }
+
+ void OnEmpty(std::string* extra_param) {
+ EXPECT_EQ(extra_param, &extra_param_);
+ called_ = true;
+ }
+
+ void OnInt(std::string* extra_param, int foo) {
+ EXPECT_EQ(extra_param, &extra_param_);
+ EXPECT_EQ(foo, 42);
+ called_ = true;
+ }
+
+ /* TODO: handle sync IPCs
+ void OnSync(std::string* extra_param, int foo, std::string* out) {
+ EXPECT_EQ(extra_param, &extra_param_);
+ EXPECT_EQ(foo, 42);
+ called_ = true;
+ *out = std::string("out");
+ }
+
+ bool Send(IPC::Message* reply) {
+ delete reply;
+ return true;
+ }*/
+
+ std::string extra_param_;
+ bool called_;
+};
+
+} // namespace
+
+TEST_F(IPCMessageParameterTest, EmptyDispatcherWithParam) {
+ TestMsgClassEmpty message;
+ EXPECT_TRUE(OnMessageReceived(message));
+ EXPECT_TRUE(called_);
+}
+
+#if defined(OS_ANDROID)
+#define MAYBE_OneIntegerWithParam DISABLED_OneIntegerWithParam
+#else
+#define MAYBE_OneIntegerWithParam OneIntegerWithParam
+#endif
+TEST_F(IPCMessageParameterTest, MAYBE_OneIntegerWithParam) {
+ TestMsgClassI message(42);
+ EXPECT_TRUE(OnMessageReceived(message));
+ EXPECT_TRUE(called_);
+}
+
+/* TODO: handle sync IPCs
+TEST_F(IPCMessageParameterTest, Sync) {
+ std::string output;
+ TestMsgClassIS message(42, &output);
+ EXPECT_TRUE(OnMessageReceived(message));
+ EXPECT_TRUE(called_);
+ EXPECT_EQ(output, std::string("out"));
+}*/
+
+} // namespace IPC
diff --git a/ipc/ipc_message_utils.cc b/ipc/ipc_message_utils.cc
index bf8daa5d97..01c37988cb 100644
--- a/ipc/ipc_message_utils.cc
+++ b/ipc/ipc_message_utils.cc
@@ -9,6 +9,7 @@
#include "base/files/file_path.h"
#include "base/json/json_writer.h"
+#include "base/memory/ptr_util.h"
#include "base/strings/nullable_string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
@@ -21,15 +22,6 @@
#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
@@ -37,6 +29,21 @@
#if defined(OS_WIN)
#include <tchar.h>
#include "ipc/handle_win.h"
+#include "ipc/ipc_platform_file.h"
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include "base/file_descriptor_posix.h"
+#include "ipc/ipc_platform_file_attachment_posix.h"
+#endif
+
+#if defined(OS_FUCHSIA)
+#include "ipc/handle_fuchsia.h"
+#endif
+
+#if defined(OS_ANDROID)
+#include "base/android/scoped_hardware_buffer_handle.h"
+#include "ipc/ipc_mojo_handle_attachment.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "mojo/public/cpp/system/scope_to_message_pipe.h"
#endif
namespace IPC {
@@ -51,7 +58,7 @@ void LogBytes(const std::vector<CharType>& data, std::string* out) {
// 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
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
// 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) {
@@ -71,72 +78,9 @@ void LogBytes(const std::vector<CharType>& data, std::string* out) {
bool ReadValue(const base::Pickle* m,
base::PickleIterator* iter,
- base::Value** value,
+ std::unique_ptr<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<int>(value->GetSize()));
- break;
- }
- case base::Value::Type::DICTIONARY: {
- sizer->AddInt();
- const base::DictionaryValue* dict =
- static_cast<const base::DictionaryValue*>(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<const base::ListValue*>(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) {
@@ -144,9 +88,9 @@ void WriteValue(base::Pickle* m, const base::Value* value, int recursion) {
return;
}
- m->WriteInt(static_cast<int>(value->GetType()));
+ m->WriteInt(static_cast<int>(value->type()));
- switch (value->GetType()) {
+ switch (value->type()) {
case base::Value::Type::NONE:
break;
case base::Value::Type::BOOLEAN: {
@@ -178,14 +122,15 @@ void WriteValue(base::Pickle* m, const base::Value* value, int recursion) {
break;
}
case base::Value::Type::BINARY: {
- m->WriteData(value->GetBuffer(), static_cast<int>(value->GetSize()));
+ m->WriteData(value->GetBlob().data(),
+ base::checked_cast<int>(value->GetBlob().size()));
break;
}
case base::Value::Type::DICTIONARY: {
const base::DictionaryValue* dict =
static_cast<const base::DictionaryValue*>(value);
- WriteParam(m, static_cast<int>(dict->size()));
+ WriteParam(m, base::checked_cast<int>(dict->size()));
for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd();
it.Advance()) {
@@ -196,9 +141,9 @@ void WriteValue(base::Pickle* m, const base::Value* value, int recursion) {
}
case base::Value::Type::LIST: {
const base::ListValue* list = static_cast<const base::ListValue*>(value);
- WriteParam(m, static_cast<int>(list->GetSize()));
+ WriteParam(m, base::checked_cast<int>(list->GetSize()));
for (const auto& entry : *list) {
- WriteValue(m, entry.get(), recursion + 1);
+ WriteValue(m, &entry, recursion + 1);
}
break;
}
@@ -217,11 +162,11 @@ bool ReadDictionaryValue(const base::Pickle* m,
for (int i = 0; i < size; ++i) {
std::string key;
- base::Value* subval;
+ std::unique_ptr<base::Value> subval;
if (!ReadParam(m, iter, &key) ||
!ReadValue(m, iter, &subval, recursion + 1))
return false;
- value->SetWithoutPathExpansion(key, subval);
+ value->SetWithoutPathExpansion(key, std::move(subval));
}
return true;
@@ -238,10 +183,10 @@ bool ReadListValue(const base::Pickle* m,
return false;
for (int i = 0; i < size; ++i) {
- base::Value* subval;
+ std::unique_ptr<base::Value> subval;
if (!ReadValue(m, iter, &subval, recursion + 1))
return false;
- value->Set(i, subval);
+ value->Set(i, std::move(subval));
}
return true;
@@ -249,7 +194,7 @@ bool ReadListValue(const base::Pickle* m,
bool ReadValue(const base::Pickle* m,
base::PickleIterator* iter,
- base::Value** value,
+ std::unique_ptr<base::Value>* value,
int recursion) {
if (recursion > kMaxRecursionDepth) {
LOG(ERROR) << "Max recursion depth hit in ReadValue.";
@@ -262,34 +207,34 @@ bool ReadValue(const base::Pickle* m,
switch (static_cast<base::Value::Type>(type)) {
case base::Value::Type::NONE:
- *value = base::Value::CreateNullValue().release();
- break;
+ *value = std::make_unique<base::Value>();
+ break;
case base::Value::Type::BOOLEAN: {
bool val;
if (!ReadParam(m, iter, &val))
return false;
- *value = new base::Value(val);
+ *value = std::make_unique<base::Value>(val);
break;
}
case base::Value::Type::INTEGER: {
int val;
if (!ReadParam(m, iter, &val))
return false;
- *value = new base::Value(val);
+ *value = std::make_unique<base::Value>(val);
break;
}
case base::Value::Type::DOUBLE: {
double val;
if (!ReadParam(m, iter, &val))
return false;
- *value = new base::Value(val);
+ *value = std::make_unique<base::Value>(val);
break;
}
case base::Value::Type::STRING: {
std::string val;
if (!ReadParam(m, iter, &val))
return false;
- *value = new base::Value(val);
+ *value = std::make_unique<base::Value>(std::move(val));
break;
}
case base::Value::Type::BINARY: {
@@ -297,23 +242,21 @@ bool ReadValue(const base::Pickle* m,
int length;
if (!iter->ReadData(&data, &length))
return false;
- std::unique_ptr<base::BinaryValue> val =
- base::BinaryValue::CreateWithCopiedBuffer(data, length);
- *value = val.release();
+ *value = base::Value::CreateWithCopiedBuffer(data, length);
break;
}
case base::Value::Type::DICTIONARY: {
- std::unique_ptr<base::DictionaryValue> val(new base::DictionaryValue());
- if (!ReadDictionaryValue(m, iter, val.get(), recursion))
+ base::DictionaryValue val;
+ if (!ReadDictionaryValue(m, iter, &val, recursion))
return false;
- *value = val.release();
+ *value = std::make_unique<base::Value>(std::move(val));
break;
}
case base::Value::Type::LIST: {
- std::unique_ptr<base::ListValue> val(new base::ListValue());
- if (!ReadListValue(m, iter, val.get(), recursion))
+ base::ListValue val;
+ if (!ReadListValue(m, iter, &val, recursion))
return false;
- *value = val.release();
+ *value = std::make_unique<base::Value>(std::move(val));
break;
}
default:
@@ -337,18 +280,12 @@ LogData::LogData()
LogData::LogData(const LogData& other) = default;
-LogData::~LogData() {
-}
+LogData::~LogData() = default;
void ParamTraits<bool>::Log(const param_type& p, std::string* l) {
l->append(p ? "true" : "false");
}
-void ParamTraits<signed char>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- sizer->AddBytes(sizeof(param_type));
-}
-
void ParamTraits<signed char>::Write(base::Pickle* m, const param_type& p) {
m->WriteBytes(&p, sizeof(param_type));
}
@@ -367,11 +304,6 @@ void ParamTraits<signed char>::Log(const param_type& p, std::string* l) {
l->append(base::IntToString(p));
}
-void ParamTraits<unsigned char>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- sizer->AddBytes(sizeof(param_type));
-}
-
void ParamTraits<unsigned char>::Write(base::Pickle* m, const param_type& p) {
m->WriteBytes(&p, sizeof(param_type));
}
@@ -390,11 +322,6 @@ void ParamTraits<unsigned char>::Log(const param_type& p, std::string* l) {
l->append(base::UintToString(p));
}
-void ParamTraits<unsigned short>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- sizer->AddBytes(sizeof(param_type));
-}
-
void ParamTraits<unsigned short>::Write(base::Pickle* m, const param_type& p) {
m->WriteBytes(&p, sizeof(param_type));
}
@@ -410,45 +337,40 @@ bool ParamTraits<unsigned short>::Read(const base::Pickle* m,
}
void ParamTraits<unsigned short>::Log(const param_type& p, std::string* l) {
- l->append(base::UintToString(p));
+ l->append(base::NumberToString(p));
}
void ParamTraits<int>::Log(const param_type& p, std::string* l) {
- l->append(base::IntToString(p));
+ l->append(base::NumberToString(p));
}
void ParamTraits<unsigned int>::Log(const param_type& p, std::string* l) {
- l->append(base::UintToString(p));
+ l->append(base::NumberToString(p));
}
-#if defined(OS_WIN) || defined(OS_LINUX) || \
+#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_FUCHSIA) || \
(defined(OS_ANDROID) && defined(ARCH_CPU_64_BITS))
void ParamTraits<long>::Log(const param_type& p, std::string* l) {
- l->append(base::Int64ToString(static_cast<int64_t>(p)));
+ l->append(base::NumberToString(p));
}
void ParamTraits<unsigned long>::Log(const param_type& p, std::string* l) {
- l->append(base::Uint64ToString(static_cast<uint64_t>(p)));
+ l->append(base::NumberToString(p));
}
#endif
void ParamTraits<long long>::Log(const param_type& p, std::string* l) {
- l->append(base::Int64ToString(static_cast<int64_t>(p)));
+ l->append(base::NumberToString(p));
}
void ParamTraits<unsigned long long>::Log(const param_type& p, std::string* l) {
- l->append(base::Uint64ToString(p));
+ l->append(base::NumberToString(p));
}
void ParamTraits<float>::Log(const param_type& p, std::string* l) {
l->append(base::StringPrintf("%e", p));
}
-void ParamTraits<double>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- sizer->AddBytes(sizeof(param_type));
-}
-
void ParamTraits<double>::Write(base::Pickle* m, const param_type& p) {
m->WriteBytes(reinterpret_cast<const char*>(&p), sizeof(param_type));
}
@@ -478,17 +400,12 @@ void ParamTraits<base::string16>::Log(const param_type& p, std::string* l) {
l->append(base::UTF16ToUTF8(p));
}
-void ParamTraits<std::vector<char>>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- sizer->AddData(static_cast<int>(p.size()));
-}
-
void ParamTraits<std::vector<char>>::Write(base::Pickle* m,
const param_type& p) {
if (p.empty()) {
m->WriteData(NULL, 0);
} else {
- m->WriteData(&p.front(), static_cast<int>(p.size()));
+ m->WriteData(&p.front(), base::checked_cast<int>(p.size()));
}
}
@@ -509,18 +426,13 @@ void ParamTraits<std::vector<char> >::Log(const param_type& p, std::string* l) {
LogBytes(p, l);
}
-void ParamTraits<std::vector<unsigned char>>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- sizer->AddData(static_cast<int>(p.size()));
-}
-
void ParamTraits<std::vector<unsigned char>>::Write(base::Pickle* m,
const param_type& p) {
if (p.empty()) {
m->WriteData(NULL, 0);
} else {
m->WriteData(reinterpret_cast<const char*>(&p.front()),
- static_cast<int>(p.size()));
+ base::checked_cast<int>(p.size()));
}
}
@@ -542,16 +454,9 @@ void ParamTraits<std::vector<unsigned char> >::Log(const param_type& p,
LogBytes(p, l);
}
-void ParamTraits<std::vector<bool>>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- GetParamSize(sizer, static_cast<int>(p.size()));
- for (size_t i = 0; i < p.size(); ++i)
- GetParamSize(sizer, static_cast<bool>(p[i]));
-}
-
void ParamTraits<std::vector<bool>>::Write(base::Pickle* m,
const param_type& p) {
- WriteParam(m, static_cast<int>(p.size()));
+ WriteParam(m, base::checked_cast<int>(p.size()));
// Cast to bool below is required because libc++'s
// vector<bool>::const_reference is different from bool, and we want to avoid
// writing an extra specialization of ParamTraits for it.
@@ -584,11 +489,6 @@ void ParamTraits<std::vector<bool> >::Log(const param_type& p, std::string* l) {
}
}
-void ParamTraits<base::DictionaryValue>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- GetValueSize(sizer, &p, 0);
-}
-
void ParamTraits<base::DictionaryValue>::Write(base::Pickle* m,
const param_type& p) {
WriteValue(m, &p, 0);
@@ -612,16 +512,11 @@ void ParamTraits<base::DictionaryValue>::Log(const param_type& p,
l->append(json);
}
-#if defined(OS_POSIX)
-void ParamTraits<base::FileDescriptor>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- GetParamSize(sizer, p.fd >= 0);
- if (p.fd >= 0)
- sizer->AddAttachment();
-}
-
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
void ParamTraits<base::FileDescriptor>::Write(base::Pickle* m,
const param_type& p) {
+ // This serialization must be kept in sync with
+ // nacl_message_scanner.cc:WriteHandle().
const bool valid = p.fd >= 0;
WriteParam(m, valid);
@@ -647,7 +542,6 @@ bool ParamTraits<base::FileDescriptor>::Read(const base::Pickle* m,
if (!ReadParam(m, iter, &valid))
return false;
- // TODO(morrita): Seems like this should return false.
if (!valid)
return true;
@@ -675,119 +569,530 @@ void ParamTraits<base::FileDescriptor>::Log(const param_type& p,
l->append(base::StringPrintf("FD(%d)", p.fd));
}
}
-#endif // defined(OS_POSIX)
+#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
-#if defined(OS_MACOSX) && !defined(OS_IOS)
-void ParamTraits<base::SharedMemoryHandle>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- GetParamSize(sizer, p.GetMemoryObject());
- uint32_t dummy = 0;
- GetParamSize(sizer, dummy);
+#if defined(OS_ANDROID)
+void ParamTraits<AHardwareBuffer*>::Write(base::Pickle* m,
+ const param_type& p) {
+ const bool is_valid = p != nullptr;
+ WriteParam(m, is_valid);
+ if (!is_valid)
+ return;
+
+ // Assume ownership of the input AHardwareBuffer.
+ auto handle = base::android::ScopedHardwareBufferHandle::Adopt(p);
+
+ // We must keep a ref to the AHardwareBuffer alive until the receiver has
+ // acquired its own reference. We do this by sending a message pipe handle
+ // along with the buffer. When the receiver deserializes (or even if they
+ // die without ever reading the message) their end of the pipe will be
+ // closed. We will eventually detect this and release the AHB reference.
+ mojo::MessagePipe tracking_pipe;
+ m->WriteAttachment(new internal::MojoHandleAttachment(
+ mojo::ScopedHandle::From(std::move(tracking_pipe.handle0))));
+ WriteParam(m,
+ base::FileDescriptor(handle.SerializeAsFileDescriptor().release(),
+ true /* auto_close */));
+
+ // Pass ownership of the input handle to our tracking pipe to keep the AHB
+ // alive long enough to be deserialized by the receiver.
+ mojo::ScopeToMessagePipe(std::move(handle), std::move(tracking_pipe.handle1));
}
+bool ParamTraits<AHardwareBuffer*>::Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r) {
+ *r = nullptr;
+
+ bool is_valid;
+ if (!ReadParam(m, iter, &is_valid))
+ return false;
+ if (!is_valid)
+ return true;
+
+ scoped_refptr<base::Pickle::Attachment> tracking_pipe_attachment;
+ if (!m->ReadAttachment(iter, &tracking_pipe_attachment))
+ return false;
+
+ // We keep this alive until the AHB is safely deserialized below. When this
+ // goes out of scope, the sender holding the other end of this pipe will treat
+ // this handle closure as a signal that it's safe to release their AHB
+ // keepalive ref.
+ mojo::ScopedHandle tracking_pipe =
+ static_cast<MessageAttachment*>(tracking_pipe_attachment.get())
+ ->TakeMojoHandle();
+
+ base::FileDescriptor descriptor;
+ if (!ReadParam(m, iter, &descriptor))
+ return false;
+
+ // NOTE: It is valid to deserialize an invalid FileDescriptor, so the success
+ // of |ReadParam()| above does not imply that |descriptor| is valid.
+ base::ScopedFD scoped_fd(descriptor.fd);
+ if (!scoped_fd.is_valid())
+ return false;
+
+ *r = base::android::ScopedHardwareBufferHandle::DeserializeFromFileDescriptor(
+ std::move(scoped_fd))
+ .Take();
+ return true;
+}
+
+void ParamTraits<AHardwareBuffer*>::Log(const param_type& p, std::string* l) {
+ l->append(base::StringPrintf("AHardwareBuffer(%p)", p));
+}
+#endif // defined(OS_ANDROID)
+
void ParamTraits<base::SharedMemoryHandle>::Write(base::Pickle* m,
const param_type& p) {
+ // This serialization must be kept in sync with
+ // nacl_message_scanner.cc:WriteHandle().
+ const bool valid = p.IsValid();
+ WriteParam(m, valid);
+
+ if (!valid)
+ return;
+
+#if defined(OS_WIN)
+ HandleWin handle_win(p.GetHandle());
+ WriteParam(m, handle_win);
+#elif defined(OS_FUCHSIA)
+ HandleFuchsia handle_fuchsia(p.GetHandle());
+ WriteParam(m, handle_fuchsia);
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
MachPortMac mach_port_mac(p.GetMemoryObject());
- ParamTraits<MachPortMac>::Write(m, mach_port_mac);
- size_t size = 0;
- bool result = p.GetSize(&size);
- DCHECK(result);
- ParamTraits<uint32_t>::Write(m, static_cast<uint32_t>(size));
+ WriteParam(m, mach_port_mac);
+#elif defined(OS_POSIX)
+#if defined(OS_ANDROID)
+ WriteParam(m, p.IsReadOnly());
+
+ // Ensure the region is read-only before sending it through IPC.
+ if (p.IsReadOnly()) {
+ if (!p.IsRegionReadOnly()) {
+ LOG(ERROR) << "Sending unsealed read-only region through IPC";
+ p.SetRegionReadOnly();
+ }
+ }
+#endif
+ if (p.OwnershipPassesToIPC()) {
+ if (!m->WriteAttachment(new internal::PlatformFileAttachment(
+ base::ScopedFD(p.GetHandle()))))
+ NOTREACHED();
+ } else {
+ if (!m->WriteAttachment(
+ new internal::PlatformFileAttachment(p.GetHandle())))
+ NOTREACHED();
+ }
+#endif
+#if (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN)
// If the caller intended to pass ownership to the IPC stack, release a
// reference.
if (p.OwnershipPassesToIPC())
p.Close();
+#endif
+
+ DCHECK(!p.GetGUID().is_empty());
+ WriteParam(m, p.GetGUID());
+ WriteParam(m, static_cast<uint64_t>(p.GetSize()));
}
bool ParamTraits<base::SharedMemoryHandle>::Read(const base::Pickle* m,
base::PickleIterator* iter,
param_type* r) {
+ *r = base::SharedMemoryHandle();
+
+ bool valid;
+ if (!ReadParam(m, iter, &valid))
+ return false;
+ if (!valid)
+ return true;
+
+#if defined(OS_WIN)
+ HandleWin handle_win;
+ if (!ReadParam(m, iter, &handle_win))
+ return false;
+#elif defined(OS_FUCHSIA)
+ HandleFuchsia handle_fuchsia;
+ if (!ReadParam(m, iter, &handle_fuchsia))
+ return false;
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
MachPortMac mach_port_mac;
- if (!ParamTraits<MachPortMac>::Read(m, iter, &mach_port_mac))
+ if (!ReadParam(m, iter, &mach_port_mac))
+ return false;
+#elif defined(OS_POSIX)
+#if defined(OS_ANDROID)
+ bool is_read_only = false;
+ if (!ReadParam(m, iter, &is_read_only))
+ return false;
+#endif
+ scoped_refptr<base::Pickle::Attachment> attachment;
+ if (!m->ReadAttachment(iter, &attachment))
return false;
- uint32_t size;
- if (!ParamTraits<uint32_t>::Read(m, iter, &size))
+ if (static_cast<MessageAttachment*>(attachment.get())->GetType() !=
+ MessageAttachment::Type::PLATFORM_FILE) {
return false;
+ }
+#endif
+ base::UnguessableToken guid;
+ uint64_t size;
+ if (!ReadParam(m, iter, &guid) || !ReadParam(m, iter, &size) ||
+ !base::IsValueInRangeForNumericType<size_t>(size)) {
+ return false;
+ }
+
+#if defined(OS_WIN)
+ *r = base::SharedMemoryHandle(handle_win.get_handle(),
+ static_cast<size_t>(size), guid);
+#elif defined(OS_FUCHSIA)
+ *r = base::SharedMemoryHandle(handle_fuchsia.get_handle(),
+ static_cast<size_t>(size), guid);
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
*r = base::SharedMemoryHandle(mach_port_mac.get_mach_port(),
- static_cast<size_t>(size),
- base::GetCurrentProcId());
+ static_cast<size_t>(size), guid);
+#elif defined(OS_POSIX)
+ *r = base::SharedMemoryHandle(
+ base::FileDescriptor(
+ static_cast<internal::PlatformFileAttachment*>(attachment.get())
+ ->TakePlatformFile(),
+ true),
+ static_cast<size_t>(size), guid);
+#endif
+
+#if defined(OS_ANDROID)
+ if (is_read_only)
+ r->SetReadOnly();
+#endif
+
return true;
}
void ParamTraits<base::SharedMemoryHandle>::Log(const param_type& p,
std::string* l) {
+#if defined(OS_WIN)
+ l->append("HANDLE: ");
+ LogParam(p.GetHandle(), l);
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
l->append("Mach port: ");
LogParam(p.GetMemoryObject(), l);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ l->append("FD: ");
+ LogParam(p.GetHandle(), l);
+#endif
+
+ l->append("GUID: ");
+ LogParam(p.GetGUID(), l);
+ l->append("size: ");
+ LogParam(static_cast<uint64_t>(p.GetSize()), l);
+#if defined(OS_ANDROID)
+ l->append("read-only: ");
+ LogParam(p.IsReadOnly(), l);
+#endif
}
-#elif defined(OS_WIN)
-void ParamTraits<base::SharedMemoryHandle>::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<base::ReadOnlySharedMemoryRegion>::Write(base::Pickle* m,
+ const param_type& p) {
+ base::subtle::PlatformSharedMemoryRegion handle =
+ base::ReadOnlySharedMemoryRegion::TakeHandleForSerialization(
+ std::move(const_cast<param_type&>(p)));
+ WriteParam(m, std::move(handle));
}
-void ParamTraits<base::SharedMemoryHandle>::Write(base::Pickle* m,
- const param_type& p) {
- m->WriteBool(p.NeedsBrokering());
+bool ParamTraits<base::ReadOnlySharedMemoryRegion>::Read(
+ const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r) {
+ base::subtle::PlatformSharedMemoryRegion handle;
+ if (!ReadParam(m, iter, &handle))
+ return false;
- if (p.NeedsBrokering()) {
- HandleWin handle_win(p.GetHandle(), HandleWin::DUPLICATE);
- ParamTraits<HandleWin>::Write(m, handle_win);
+ *r = base::ReadOnlySharedMemoryRegion::Deserialize(std::move(handle));
+ return true;
+}
- // 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()));
+void ParamTraits<base::ReadOnlySharedMemoryRegion>::Log(const param_type& p,
+ std::string* l) {
+ *l = "<base::ReadOnlySharedMemoryRegion>";
+ // TODO(alexilin): currently there is no way to access underlying handle
+ // without destructing a ReadOnlySharedMemoryRegion instance.
+}
+
+void ParamTraits<base::WritableSharedMemoryRegion>::Write(base::Pickle* m,
+ const param_type& p) {
+ base::subtle::PlatformSharedMemoryRegion handle =
+ base::WritableSharedMemoryRegion::TakeHandleForSerialization(
+ std::move(const_cast<param_type&>(p)));
+ WriteParam(m, std::move(handle));
+}
+
+bool ParamTraits<base::WritableSharedMemoryRegion>::Read(
+ const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r) {
+ base::subtle::PlatformSharedMemoryRegion handle;
+ if (!ReadParam(m, iter, &handle))
+ return false;
+
+ *r = base::WritableSharedMemoryRegion::Deserialize(std::move(handle));
+ return true;
+}
+
+void ParamTraits<base::WritableSharedMemoryRegion>::Log(const param_type& p,
+ std::string* l) {
+ *l = "<base::WritableSharedMemoryRegion>";
+ // TODO(alexilin): currently there is no way to access underlying handle
+ // without destructing a ReadOnlySharedMemoryRegion instance.
+}
+
+void ParamTraits<base::UnsafeSharedMemoryRegion>::Write(base::Pickle* m,
+ const param_type& p) {
+ base::subtle::PlatformSharedMemoryRegion handle =
+ base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
+ std::move(const_cast<param_type&>(p)));
+ WriteParam(m, std::move(handle));
+}
+
+bool ParamTraits<base::UnsafeSharedMemoryRegion>::Read(
+ const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r) {
+ base::subtle::PlatformSharedMemoryRegion handle;
+ if (!ReadParam(m, iter, &handle))
+ return false;
+
+ *r = base::UnsafeSharedMemoryRegion::Deserialize(std::move(handle));
+ return true;
+}
+
+void ParamTraits<base::UnsafeSharedMemoryRegion>::Log(const param_type& p,
+ std::string* l) {
+ *l = "<base::UnsafeSharedMemoryRegion>";
+ // TODO(alexilin): currently there is no way to access underlying handle
+ // without destructing a ReadOnlySharedMemoryRegion instance.
+}
+
+void ParamTraits<base::subtle::PlatformSharedMemoryRegion>::Write(
+ base::Pickle* m,
+ const param_type& p) {
+ // This serialization must be kept in sync with
+ // nacl_message_scanner.cc::WriteHandle().
+ const bool valid = p.IsValid();
+ WriteParam(m, valid);
+
+ if (!valid)
+ return;
+
+ WriteParam(m, p.GetMode());
+ WriteParam(m, static_cast<uint64_t>(p.GetSize()));
+ WriteParam(m, p.GetGUID());
+
+#if defined(OS_WIN)
+ base::win::ScopedHandle h = const_cast<param_type&>(p).PassPlatformHandle();
+ HandleWin handle_win(h.Take());
+ WriteParam(m, handle_win);
+#elif defined(OS_FUCHSIA)
+ zx::handle h = const_cast<param_type&>(p).PassPlatformHandle();
+ HandleFuchsia handle_fuchsia(h.release());
+ WriteParam(m, handle_fuchsia);
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ base::mac::ScopedMachSendRight h =
+ const_cast<param_type&>(p).PassPlatformHandle();
+ MachPortMac mach_port_mac(h.release());
+ WriteParam(m, mach_port_mac);
+#elif defined(OS_ANDROID)
+ m->WriteAttachment(new internal::PlatformFileAttachment(
+ base::ScopedFD(const_cast<param_type&>(p).PassPlatformHandle())));
+#elif defined(OS_POSIX)
+ base::subtle::ScopedFDPair h =
+ const_cast<param_type&>(p).PassPlatformHandle();
+ m->WriteAttachment(new internal::PlatformFileAttachment(std::move(h.fd)));
+ if (p.GetMode() ==
+ base::subtle::PlatformSharedMemoryRegion::Mode::kWritable) {
+ m->WriteAttachment(
+ new internal::PlatformFileAttachment(std::move(h.readonly_fd)));
}
+#endif
}
-bool ParamTraits<base::SharedMemoryHandle>::Read(const base::Pickle* m,
- base::PickleIterator* iter,
- param_type* r) {
- bool needs_brokering;
- if (!iter->ReadBool(&needs_brokering))
+bool ParamTraits<base::subtle::PlatformSharedMemoryRegion>::Read(
+ const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r) {
+ bool valid;
+ if (!ReadParam(m, iter, &valid))
+ return false;
+ if (!valid) {
+ *r = base::subtle::PlatformSharedMemoryRegion();
+ return true;
+ }
+
+ base::subtle::PlatformSharedMemoryRegion::Mode mode;
+ uint64_t shm_size;
+ base::UnguessableToken guid;
+ if (!ReadParam(m, iter, &mode) || !ReadParam(m, iter, &shm_size) ||
+ !base::IsValueInRangeForNumericType<size_t>(shm_size) ||
+ !ReadParam(m, iter, &guid)) {
return false;
+ }
+ size_t size = static_cast<size_t>(shm_size);
- if (needs_brokering) {
- HandleWin handle_win;
- if (!ParamTraits<HandleWin>::Read(m, iter, &handle_win))
+#if defined(OS_WIN)
+ HandleWin handle_win;
+ if (!ReadParam(m, iter, &handle_win))
+ return false;
+ *r = base::subtle::PlatformSharedMemoryRegion::Take(
+ base::win::ScopedHandle(handle_win.get_handle()), mode, size, guid);
+#elif defined(OS_FUCHSIA)
+ HandleFuchsia handle_fuchsia;
+ if (!ReadParam(m, iter, &handle_fuchsia))
+ return false;
+ *r = base::subtle::PlatformSharedMemoryRegion::Take(
+ zx::vmo(handle_fuchsia.get_handle()), mode, size, guid);
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ MachPortMac mach_port_mac;
+ if (!ReadParam(m, iter, &mach_port_mac))
+ return false;
+ *r = base::subtle::PlatformSharedMemoryRegion::Take(
+ base::mac::ScopedMachSendRight(mach_port_mac.get_mach_port()), mode, size,
+ guid);
+#elif defined(OS_POSIX)
+ scoped_refptr<base::Pickle::Attachment> attachment;
+ if (!m->ReadAttachment(iter, &attachment))
+ return false;
+ if (static_cast<MessageAttachment*>(attachment.get())->GetType() !=
+ MessageAttachment::Type::PLATFORM_FILE) {
+ return false;
+ }
+
+#if defined(OS_ANDROID)
+ *r = base::subtle::PlatformSharedMemoryRegion::Take(
+ base::ScopedFD(
+ static_cast<internal::PlatformFileAttachment*>(attachment.get())
+ ->TakePlatformFile()),
+ mode, size, guid);
+#else
+ scoped_refptr<base::Pickle::Attachment> readonly_attachment;
+ if (mode == base::subtle::PlatformSharedMemoryRegion::Mode::kWritable) {
+ if (!m->ReadAttachment(iter, &readonly_attachment))
return false;
- *r = base::SharedMemoryHandle(handle_win.get_handle(),
- base::GetCurrentProcId());
- return true;
+
+ if (static_cast<MessageAttachment*>(readonly_attachment.get())->GetType() !=
+ MessageAttachment::Type::PLATFORM_FILE) {
+ return false;
+ }
}
+ *r = base::subtle::PlatformSharedMemoryRegion::Take(
+ base::subtle::ScopedFDPair(
+ base::ScopedFD(
+ static_cast<internal::PlatformFileAttachment*>(attachment.get())
+ ->TakePlatformFile()),
+ readonly_attachment
+ ? base::ScopedFD(static_cast<internal::PlatformFileAttachment*>(
+ readonly_attachment.get())
+ ->TakePlatformFile())
+ : base::ScopedFD()),
+ mode, size, guid);
+#endif // defined(OS_ANDROID)
+
+#endif
+
+ return true;
+}
+
+void ParamTraits<base::subtle::PlatformSharedMemoryRegion>::Log(
+ const param_type& p,
+ std::string* l) {
+#if defined(OS_FUCHSIA) || defined(OS_WIN)
+ l->append("Handle: ");
+ LogParam(p.GetPlatformHandle(), l);
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ l->append("Mach port: ");
+ LogParam(p.GetPlatformHandle(), l);
+#elif defined(OS_ANDROID)
+ l->append("FD: ");
+ LogParam(p.GetPlatformHandle(), l);
+#elif defined(OS_POSIX)
+ base::subtle::FDPair h = p.GetPlatformHandle();
+ l->append("FD: ");
+ LogParam(h.fd, l);
+ l->append("Read-only FD: ");
+ LogParam(h.readonly_fd, l);
+#endif
+
+ l->append("Mode: ");
+ LogParam(p.GetMode(), l);
+ l->append("size: ");
+ LogParam(static_cast<uint64_t>(p.GetSize()), l);
+ l->append("GUID: ");
+ LogParam(p.GetGUID(), l);
+}
- int handle_int;
- if (!iter->ReadInt(&handle_int))
+void ParamTraits<base::subtle::PlatformSharedMemoryRegion::Mode>::Write(
+ base::Pickle* m,
+ const param_type& value) {
+ DCHECK(static_cast<int>(value) >= 0 &&
+ static_cast<int>(value) <= static_cast<int>(param_type::kMaxValue));
+ m->WriteInt(static_cast<int>(value));
+}
+
+bool ParamTraits<base::subtle::PlatformSharedMemoryRegion::Mode>::Read(
+ const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* p) {
+ int value;
+ if (!iter->ReadInt(&value))
+ return false;
+ if (!(static_cast<int>(value) >= 0 &&
+ static_cast<int>(value) <= static_cast<int>(param_type::kMaxValue))) {
return false;
- HANDLE handle = LongToHandle(handle_int);
- *r = base::SharedMemoryHandle(handle, base::GetCurrentProcId());
+ }
+ *p = static_cast<param_type>(value);
return true;
}
-void ParamTraits<base::SharedMemoryHandle>::Log(const param_type& p,
- std::string* l) {
- LogParam(p.GetHandle(), l);
- l->append(" needs brokering: ");
- LogParam(p.NeedsBrokering(), l);
+void ParamTraits<base::subtle::PlatformSharedMemoryRegion::Mode>::Log(
+ const param_type& p,
+ std::string* l) {
+ LogParam(static_cast<int>(p), l);
}
-#endif // defined(OS_MACOSX) && !defined(OS_IOS)
-void ParamTraits<base::FilePath>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- p.GetSizeForPickle(sizer);
+#if defined(OS_WIN)
+void ParamTraits<PlatformFileForTransit>::Write(base::Pickle* m,
+ const param_type& p) {
+ m->WriteBool(p.IsValid());
+ if (p.IsValid()) {
+ HandleWin handle_win(p.GetHandle());
+ ParamTraits<HandleWin>::Write(m, handle_win);
+ ::CloseHandle(p.GetHandle());
+ }
}
+bool ParamTraits<PlatformFileForTransit>::Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r) {
+ bool is_valid;
+ if (!iter->ReadBool(&is_valid))
+ return false;
+ if (!is_valid) {
+ *r = PlatformFileForTransit();
+ return true;
+ }
+
+ HandleWin handle_win;
+ if (!ParamTraits<HandleWin>::Read(m, iter, &handle_win))
+ return false;
+ *r = PlatformFileForTransit(handle_win.get_handle());
+ return true;
+}
+
+void ParamTraits<PlatformFileForTransit>::Log(const param_type& p,
+ std::string* l) {
+ LogParam(p.GetHandle(), l);
+}
+#endif // defined(OS_WIN)
+
void ParamTraits<base::FilePath>::Write(base::Pickle* m, const param_type& p) {
p.WriteToPickle(m);
}
@@ -802,11 +1107,6 @@ void ParamTraits<base::FilePath>::Log(const param_type& p, std::string* l) {
ParamTraits<base::FilePath::StringType>::Log(p.value(), l);
}
-void ParamTraits<base::ListValue>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- GetValueSize(sizer, &p, 0);
-}
-
void ParamTraits<base::ListValue>::Write(base::Pickle* m, const param_type& p) {
WriteValue(m, &p, 0);
}
@@ -828,12 +1128,6 @@ void ParamTraits<base::ListValue>::Log(const param_type& p, std::string* l) {
l->append(json);
}
-void ParamTraits<base::NullableString16>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- GetParamSize(sizer, p.string());
- GetParamSize(sizer, p.is_null());
-}
-
void ParamTraits<base::NullableString16>::Write(base::Pickle* m,
const param_type& p) {
WriteParam(m, p.string());
@@ -862,15 +1156,6 @@ void ParamTraits<base::NullableString16>::Log(const param_type& p,
l->append(")");
}
-void ParamTraits<base::File::Info>::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<base::File::Info>::Write(base::Pickle* m,
const param_type& p) {
WriteParam(m, p.size);
@@ -911,11 +1196,6 @@ void ParamTraits<base::File::Info>::Log(const param_type& p,
l->append(")");
}
-void ParamTraits<base::Time>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- sizer->AddInt64();
-}
-
void ParamTraits<base::Time>::Write(base::Pickle* m, const param_type& p) {
ParamTraits<int64_t>::Write(m, p.ToInternalValue());
}
@@ -934,11 +1214,6 @@ void ParamTraits<base::Time>::Log(const param_type& p, std::string* l) {
ParamTraits<int64_t>::Log(p.ToInternalValue(), l);
}
-void ParamTraits<base::TimeDelta>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- sizer->AddInt64();
-}
-
void ParamTraits<base::TimeDelta>::Write(base::Pickle* m, const param_type& p) {
ParamTraits<int64_t>::Write(m, p.ToInternalValue());
}
@@ -958,11 +1233,6 @@ void ParamTraits<base::TimeDelta>::Log(const param_type& p, std::string* l) {
ParamTraits<int64_t>::Log(p.ToInternalValue(), l);
}
-void ParamTraits<base::TimeTicks>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- sizer->AddInt64();
-}
-
void ParamTraits<base::TimeTicks>::Write(base::Pickle* m, const param_type& p) {
ParamTraits<int64_t>::Write(m, p.ToInternalValue());
}
@@ -987,11 +1257,6 @@ void ParamTraits<base::TimeTicks>::Log(const param_type& p, std::string* l) {
static_assert(sizeof(base::UnguessableToken) == 2 * sizeof(uint64_t),
"base::UnguessableToken should be of size 2 * sizeof(uint64_t).");
-void ParamTraits<base::UnguessableToken>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- sizer->AddBytes(2 * sizeof(uint64_t));
-}
-
void ParamTraits<base::UnguessableToken>::Write(base::Pickle* m,
const param_type& p) {
DCHECK(!p.is_empty());
@@ -1021,15 +1286,6 @@ void ParamTraits<base::UnguessableToken>::Log(const param_type& p,
l->append(p.ToString());
}
-void ParamTraits<IPC::ChannelHandle>::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<IPC::ChannelHandle>::Write(base::Pickle* m,
const param_type& p) {
#if defined(OS_NACL_SFI)
@@ -1060,19 +1316,6 @@ void ParamTraits<IPC::ChannelHandle>::Log(const param_type& p,
l->append(")");
}
-void ParamTraits<LogData>::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<LogData>::Write(base::Pickle* m, const param_type& p) {
WriteParam(m, p.channel);
WriteParam(m, p.routing_id);
@@ -1105,7 +1348,7 @@ void ParamTraits<LogData>::Log(const param_type& p, std::string* l) {
}
void ParamTraits<Message>::Write(base::Pickle* m, const Message& p) {
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// We don't serialize the file descriptors in the nested message, so there
// better not be any.
DCHECK(!p.HasAttachments());
@@ -1142,7 +1385,8 @@ bool ParamTraits<Message>::Read(const base::Pickle* m,
return false;
r->SetHeaderValues(static_cast<int32_t>(routing_id), type, flags);
- return r->WriteBytes(payload, payload_size);
+ r->WriteBytes(payload, payload_size);
+ return true;
}
void ParamTraits<Message>::Log(const Message& p, std::string* l) {
@@ -1150,11 +1394,6 @@ void ParamTraits<Message>::Log(const Message& p, std::string* l) {
}
#if defined(OS_WIN)
-void ParamTraits<HANDLE>::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<HANDLE>::Write(base::Pickle* m, const param_type& p) {
@@ -1175,40 +1414,6 @@ void ParamTraits<HANDLE>::Log(const param_type& p, std::string* l) {
l->append(base::StringPrintf("0x%p", p));
}
-void ParamTraits<LOGFONT>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- sizer->AddData(sizeof(LOGFONT));
-}
-
-void ParamTraits<LOGFONT>::Write(base::Pickle* m, const param_type& p) {
- m->WriteData(reinterpret_cast<const char*>(&p), sizeof(LOGFONT));
-}
-
-bool ParamTraits<LOGFONT>::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<LOGFONT*>(const_cast<char*>(data));
- if (_tcsnlen(font->lfFaceName, LF_FACESIZE) < LF_FACESIZE) {
- memcpy(r, data, sizeof(LOGFONT));
- return true;
- }
- }
-
- NOTREACHED();
- return false;
-}
-
-void ParamTraits<LOGFONT>::Log(const param_type& p, std::string* l) {
- l->append(base::StringPrintf("<LOGFONT>"));
-}
-
-void ParamTraits<MSG>::GetSize(base::PickleSizer* sizer, const param_type& p) {
- sizer->AddData(sizeof(MSG));
-}
-
void ParamTraits<MSG>::Write(base::Pickle* m, const param_type& p) {
m->WriteData(reinterpret_cast<const char*>(&p), sizeof(MSG));
}
diff --git a/ipc/ipc_message_utils.h b/ipc/ipc_message_utils.h
index 2d51c984aa..00a164a439 100644
--- a/ipc/ipc_message_utils.h
+++ b/ipc/ipc_message_utils.h
@@ -15,12 +15,21 @@
#include <set>
#include <string>
#include <tuple>
+#include <unordered_map>
#include <vector>
+#include "base/component_export.h"
+#include "base/containers/flat_map.h"
#include "base/containers/small_map.h"
#include "base/containers/stack_container.h"
#include "base/files/file.h"
#include "base/format_macros.h"
+#include "base/memory/platform_shared_memory_region.h"
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/memory/shared_memory_handle.h"
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "base/memory/writable_shared_memory_region.h"
+#include "base/numerics/safe_conversions.h"
#include "base/optional.h"
#include "base/strings/string16.h"
#include "base/strings/string_util.h"
@@ -30,6 +39,10 @@
#include "ipc/ipc_param_traits.h"
#include "ipc/ipc_sync_message.h"
+#if defined(OS_ANDROID)
+#include "base/android/scoped_hardware_buffer_handle.h"
+#endif
+
namespace base {
class DictionaryValue;
class FilePath;
@@ -40,19 +53,19 @@ 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;
+#if defined(OS_WIN)
+class PlatformFileForTransit;
+#endif
+
// -----------------------------------------------------------------------------
// How we send IPC message logs across channels.
-struct IPC_EXPORT LogData {
+struct COMPONENT_EXPORT(IPC) LogData {
LogData();
LogData(const LogData& other);
~LogData();
@@ -84,12 +97,6 @@ struct CheckedTuple {
typedef std::tuple<Ts...> Tuple;
};
-template <class P>
-static inline void GetParamSize(base::PickleSizer* sizer, const P& p) {
- typedef typename SimilarTypeTraits<P>::Type Type;
- ParamTraits<Type>::GetSize(sizer, static_cast<const Type&>(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)
@@ -122,22 +129,18 @@ static inline void LogParam(const P& p, std::string* l) {
template <>
struct ParamTraits<bool> {
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);
+ COMPONENT_EXPORT(IPC) static void Log(const param_type& p, std::string* l);
};
template <>
-struct IPC_EXPORT ParamTraits<signed char> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<signed char> {
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,
@@ -146,9 +149,8 @@ struct IPC_EXPORT ParamTraits<signed char> {
};
template <>
-struct IPC_EXPORT ParamTraits<unsigned char> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<unsigned char> {
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,
@@ -157,9 +159,8 @@ struct IPC_EXPORT ParamTraits<unsigned char> {
};
template <>
-struct IPC_EXPORT ParamTraits<unsigned short> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<unsigned short> {
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,
@@ -170,31 +171,25 @@ struct IPC_EXPORT ParamTraits<unsigned short> {
template <>
struct ParamTraits<int> {
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);
+ COMPONENT_EXPORT(IPC) static void Log(const param_type& p, std::string* l);
};
template <>
struct ParamTraits<unsigned int> {
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<int*>(r));
}
- IPC_EXPORT static void Log(const param_type& p, std::string* l);
+ COMPONENT_EXPORT(IPC) 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
@@ -205,17 +200,14 @@ struct ParamTraits<unsigned int> {
// 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.
+// 3) Android 64 bit and Fuchsia also have 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) || \
+#if defined(OS_WIN) || defined(OS_LINUX) || defined(OS_FUCHSIA) || \
(defined(OS_ANDROID) && defined(ARCH_CPU_64_BITS))
template <>
struct ParamTraits<long> {
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);
}
@@ -224,15 +216,12 @@ struct ParamTraits<long> {
param_type* r) {
return iter->ReadLong(r);
}
- IPC_EXPORT static void Log(const param_type& p, std::string* l);
+ COMPONENT_EXPORT(IPC) static void Log(const param_type& p, std::string* l);
};
template <>
struct ParamTraits<unsigned long> {
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);
}
@@ -241,16 +230,13 @@ struct ParamTraits<unsigned long> {
param_type* r) {
return iter->ReadLong(reinterpret_cast<long*>(r));
}
- IPC_EXPORT static void Log(const param_type& p, std::string* l);
+ COMPONENT_EXPORT(IPC) static void Log(const param_type& p, std::string* l);
};
#endif
template <>
struct ParamTraits<long long> {
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<int64_t>(p));
}
@@ -259,33 +245,27 @@ struct ParamTraits<long long> {
param_type* r) {
return iter->ReadInt64(reinterpret_cast<int64_t*>(r));
}
- IPC_EXPORT static void Log(const param_type& p, std::string* l);
+ COMPONENT_EXPORT(IPC) static void Log(const param_type& p, std::string* l);
};
template <>
struct ParamTraits<unsigned long long> {
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<int64_t*>(r));
}
- IPC_EXPORT static void Log(const param_type& p, std::string* l);
+ COMPONENT_EXPORT(IPC) 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<float> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<float> {
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,
@@ -296,9 +276,8 @@ struct IPC_EXPORT ParamTraits<float> {
};
template <>
-struct IPC_EXPORT ParamTraits<double> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<double> {
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,
@@ -309,10 +288,6 @@ struct IPC_EXPORT ParamTraits<double> {
template <class P, size_t Size>
struct ParamTraits<P[Size]> {
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);
@@ -342,24 +317,18 @@ struct ParamTraits<P[Size]> {
template <>
struct ParamTraits<std::string> {
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);
+ COMPONENT_EXPORT(IPC) static void Log(const param_type& p, std::string* l);
};
template <>
struct ParamTraits<base::string16> {
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);
}
@@ -368,13 +337,12 @@ struct ParamTraits<base::string16> {
param_type* r) {
return iter->ReadString16(r);
}
- IPC_EXPORT static void Log(const param_type& p, std::string* l);
+ COMPONENT_EXPORT(IPC) static void Log(const param_type& p, std::string* l);
};
template <>
-struct IPC_EXPORT ParamTraits<std::vector<char> > {
+struct COMPONENT_EXPORT(IPC) ParamTraits<std::vector<char>> {
typedef std::vector<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*,
base::PickleIterator* iter,
@@ -383,9 +351,8 @@ struct IPC_EXPORT ParamTraits<std::vector<char> > {
};
template <>
-struct IPC_EXPORT ParamTraits<std::vector<unsigned char> > {
+struct COMPONENT_EXPORT(IPC) ParamTraits<std::vector<unsigned char>> {
typedef std::vector<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,
@@ -394,9 +361,8 @@ struct IPC_EXPORT ParamTraits<std::vector<unsigned char> > {
};
template <>
-struct IPC_EXPORT ParamTraits<std::vector<bool> > {
+struct COMPONENT_EXPORT(IPC) ParamTraits<std::vector<bool>> {
typedef std::vector<bool> 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,
@@ -407,13 +373,8 @@ struct IPC_EXPORT ParamTraits<std::vector<bool> > {
template <class P>
struct ParamTraits<std::vector<P>> {
typedef std::vector<P> param_type;
- static void GetSize(base::PickleSizer* sizer, const param_type& p) {
- GetParamSize(sizer, static_cast<int>(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<int>(p.size()));
+ WriteParam(m, base::checked_cast<int>(p.size()));
for (size_t i = 0; i < p.size(); i++)
WriteParam(m, p[i]);
}
@@ -446,14 +407,8 @@ struct ParamTraits<std::vector<P>> {
template <class P>
struct ParamTraits<std::set<P> > {
typedef std::set<P> param_type;
- static void GetSize(base::PickleSizer* sizer, const param_type& p) {
- GetParamSize(sizer, static_cast<int>(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<int>(p.size()));
+ WriteParam(m, base::checked_cast<int>(p.size()));
typename param_type::const_iterator iter;
for (iter = p.begin(); iter != p.end(); ++iter)
WriteParam(m, *iter);
@@ -480,20 +435,42 @@ struct ParamTraits<std::set<P> > {
template <class K, class V, class C, class A>
struct ParamTraits<std::map<K, V, C, A> > {
typedef std::map<K, V, C, A> param_type;
- static void GetSize(base::PickleSizer* sizer, const param_type& p) {
- GetParamSize(sizer, static_cast<int>(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, base::checked_cast<int>(p.size()));
+ for (const auto& iter : p) {
+ 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("<std::map>");
+ }
+};
+
+template <class K, class V, class C, class A>
+struct ParamTraits<std::unordered_map<K, V, C, A>> {
+ typedef std::unordered_map<K, V, C, A> param_type;
static void Write(base::Pickle* m, const param_type& p) {
- WriteParam(m, static_cast<int>(p.size()));
- typename param_type::const_iterator iter;
- for (iter = p.begin(); iter != p.end(); ++iter) {
- WriteParam(m, iter->first);
- WriteParam(m, iter->second);
+ WriteParam(m, base::checked_cast<int>(p.size()));
+ for (const auto& iter : p) {
+ WriteParam(m, iter.first);
+ WriteParam(m, iter.second);
}
}
static bool Read(const base::Pickle* m,
@@ -513,17 +490,13 @@ struct ParamTraits<std::map<K, V, C, A> > {
return true;
}
static void Log(const param_type& p, std::string* l) {
- l->append("<std::map>");
+ l->append("<std::unordered_map>");
}
};
template <class A, class B>
struct ParamTraits<std::pair<A, B> > {
typedef std::pair<A, B> 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);
@@ -545,9 +518,8 @@ struct ParamTraits<std::pair<A, B> > {
// Base ParamTraits ------------------------------------------------------------
template <>
-struct IPC_EXPORT ParamTraits<base::DictionaryValue> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<base::DictionaryValue> {
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,
@@ -555,7 +527,7 @@ struct IPC_EXPORT ParamTraits<base::DictionaryValue> {
static void Log(const param_type& p, std::string* l);
};
-#if defined(OS_POSIX)
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
// 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
@@ -571,35 +543,106 @@ struct IPC_EXPORT ParamTraits<base::DictionaryValue> {
// 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<base::FileDescriptor> {
+template <>
+struct COMPONENT_EXPORT(IPC) ParamTraits<base::FileDescriptor> {
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)
+#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
-#if (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN)
template <>
-struct IPC_EXPORT ParamTraits<base::SharedMemoryHandle> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<base::SharedMemoryHandle> {
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)
+
+#if defined(OS_ANDROID)
+template <>
+struct COMPONENT_EXPORT(IPC) ParamTraits<AHardwareBuffer*> {
+ typedef AHardwareBuffer* param_type;
+ 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
template <>
-struct IPC_EXPORT ParamTraits<base::FilePath> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<base::ReadOnlySharedMemoryRegion> {
+ typedef base::ReadOnlySharedMemoryRegion param_type;
+ 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 COMPONENT_EXPORT(IPC) ParamTraits<base::WritableSharedMemoryRegion> {
+ typedef base::WritableSharedMemoryRegion param_type;
+ 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 COMPONENT_EXPORT(IPC) ParamTraits<base::UnsafeSharedMemoryRegion> {
+ typedef base::UnsafeSharedMemoryRegion param_type;
+ 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 COMPONENT_EXPORT(IPC)
+ ParamTraits<base::subtle::PlatformSharedMemoryRegion> {
+ typedef base::subtle::PlatformSharedMemoryRegion param_type;
+ 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 COMPONENT_EXPORT(IPC)
+ ParamTraits<base::subtle::PlatformSharedMemoryRegion::Mode> {
+ typedef base::subtle::PlatformSharedMemoryRegion::Mode param_type;
+ 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_WIN)
+template <>
+struct COMPONENT_EXPORT(IPC) ParamTraits<PlatformFileForTransit> {
+ typedef PlatformFileForTransit param_type;
+ 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)
+
+template <>
+struct COMPONENT_EXPORT(IPC) ParamTraits<base::FilePath> {
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,
@@ -608,9 +651,8 @@ struct IPC_EXPORT ParamTraits<base::FilePath> {
};
template <>
-struct IPC_EXPORT ParamTraits<base::ListValue> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<base::ListValue> {
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,
@@ -619,9 +661,8 @@ struct IPC_EXPORT ParamTraits<base::ListValue> {
};
template <>
-struct IPC_EXPORT ParamTraits<base::NullableString16> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<base::NullableString16> {
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,
@@ -630,9 +671,8 @@ struct IPC_EXPORT ParamTraits<base::NullableString16> {
};
template <>
-struct IPC_EXPORT ParamTraits<base::File::Info> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<base::File::Info> {
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,
@@ -653,9 +693,8 @@ struct SimilarTypeTraits<HWND> {
#endif // defined(OS_WIN)
template <>
-struct IPC_EXPORT ParamTraits<base::Time> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<base::Time> {
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,
@@ -664,9 +703,8 @@ struct IPC_EXPORT ParamTraits<base::Time> {
};
template <>
-struct IPC_EXPORT ParamTraits<base::TimeDelta> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<base::TimeDelta> {
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,
@@ -675,9 +713,8 @@ struct IPC_EXPORT ParamTraits<base::TimeDelta> {
};
template <>
-struct IPC_EXPORT ParamTraits<base::TimeTicks> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<base::TimeTicks> {
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,
@@ -686,9 +723,8 @@ struct IPC_EXPORT ParamTraits<base::TimeTicks> {
};
template <>
-struct IPC_EXPORT ParamTraits<base::UnguessableToken> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<base::UnguessableToken> {
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,
@@ -699,7 +735,6 @@ struct IPC_EXPORT ParamTraits<base::UnguessableToken> {
template <>
struct ParamTraits<std::tuple<>> {
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,
@@ -710,161 +745,60 @@ struct ParamTraits<std::tuple<>> {
}
};
-template <class A>
-struct ParamTraits<std::tuple<A>> {
- typedef std::tuple<A> 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 <typename T, int index, int count>
+struct TupleParamTraitsHelper {
+ using Next = TupleParamTraitsHelper<T, index + 1, count>;
-template <class A, class B>
-struct ParamTraits<std::tuple<A, B>> {
- typedef std::tuple<A, B> 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 void Write(base::Pickle* m, const T& p) {
+ WriteParam(m, std::get<index>(p));
+ Next::Write(m, 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 <class A, class B, class C>
-struct ParamTraits<std::tuple<A, B, C>> {
- typedef std::tuple<A, B, C> 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, T* r) {
+ return ReadParam(m, iter, &std::get<index>(*r)) && Next::Read(m, iter, r);
}
- 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);
+
+ static void Log(const T& p, std::string* l) {
+ LogParam(std::get<index>(p), l);
+ if (index < count - 1)
+ l->append(", ");
+ Next::Log(p, l);
}
};
-template <class A, class B, class C, class D>
-struct ParamTraits<std::tuple<A, B, C, D>> {
- typedef std::tuple<A, B, C, D> 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 <typename T, int index>
+struct TupleParamTraitsHelper<T, index, index> {
+ static void Write(base::Pickle* m, const T& p) {}
+ static bool Read(const base::Pickle* m, base::PickleIterator* iter, T* r) {
+ return true;
}
+ static void Log(const T& p, std::string* l) {}
};
-template <class A, class B, class C, class D, class E>
-struct ParamTraits<std::tuple<A, B, C, D, E>> {
- typedef std::tuple<A, B, C, D, E> 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));
- }
+template <typename... Args>
+struct ParamTraits<std::tuple<Args...>> {
+ using param_type = std::tuple<Args...>;
+ using Helper =
+ TupleParamTraitsHelper<param_type, 0, std::tuple_size<param_type>::value>;
+
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));
+ Helper::Write(m, 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);
+ return Helper::Read(m, iter, r);
}
+
+ static void Log(const param_type& p, std::string* l) { Helper::Log(p, l); }
};
template <class P, size_t stack_capacity>
struct ParamTraits<base::StackVector<P, stack_capacity> > {
typedef base::StackVector<P, stack_capacity> param_type;
- static void GetSize(base::PickleSizer* sizer, const param_type& p) {
- GetParamSize(sizer, static_cast<int>(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<int>(p->size()));
+ WriteParam(m, base::checked_cast<int>(p->size()));
for (size_t i = 0; i < p->size(); i++)
WriteParam(m, p[i]);
}
@@ -899,20 +833,12 @@ template <typename NormalMap,
int kArraySize,
typename EqualKey,
typename MapInit>
-struct ParamTraits<base::SmallMap<NormalMap, kArraySize, EqualKey, MapInit> > {
- typedef base::SmallMap<NormalMap, kArraySize, EqualKey, MapInit> 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<int>(p.size()));
- typename param_type::const_iterator iter;
- for (iter = p.begin(); iter != p.end(); ++iter) {
- GetParamSize(sizer, iter->first);
- GetParamSize(sizer, iter->second);
- }
- }
+struct ParamTraits<base::small_map<NormalMap, kArraySize, EqualKey, MapInit>> {
+ using param_type = base::small_map<NormalMap, kArraySize, EqualKey, MapInit>;
+ using K = typename param_type::key_type;
+ using V = typename param_type::data_type;
static void Write(base::Pickle* m, const param_type& p) {
- WriteParam(m, static_cast<int>(p.size()));
+ WriteParam(m, base::checked_cast<int>(p.size()));
typename param_type::const_iterator iter;
for (iter = p.begin(); iter != p.end(); ++iter) {
WriteParam(m, iter->first);
@@ -936,19 +862,51 @@ struct ParamTraits<base::SmallMap<NormalMap, kArraySize, EqualKey, MapInit> > {
return true;
}
static void Log(const param_type& p, std::string* l) {
- l->append("<base::SmallMap>");
+ l->append("<base::small_map>");
+ }
+};
+
+template <class Key, class Mapped, class Compare>
+struct ParamTraits<base::flat_map<Key, Mapped, Compare>> {
+ using param_type = base::flat_map<Key, Mapped, Compare>;
+ static void Write(base::Pickle* m, const param_type& p) {
+ DCHECK(base::IsValueInRangeForNumericType<int>(p.size()));
+ WriteParam(m, base::checked_cast<int>(p.size()));
+ for (const auto& iter : p) {
+ 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;
+
+ // Construct by creating in a vector and moving into the flat_map. Properly
+ // serialized flat_maps will be in-order so this will be O(n). Incorrectly
+ // serialized ones will still be handled properly.
+ std::vector<typename param_type::value_type> vect;
+ vect.resize(size);
+ for (int i = 0; i < size; ++i) {
+ if (!ReadParam(m, iter, &vect[i].first))
+ return false;
+ if (!ReadParam(m, iter, &vect[i].second))
+ return false;
+ }
+
+ *r = param_type(std::move(vect), base::KEEP_FIRST_OF_DUPES);
+ return true;
+ }
+ static void Log(const param_type& p, std::string* l) {
+ l->append("<base::flat_map>");
}
};
template <class P>
struct ParamTraits<std::unique_ptr<P>> {
typedef std::unique_ptr<P> 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);
@@ -985,12 +943,6 @@ struct ParamTraits<std::unique_ptr<P>> {
template <class P>
struct ParamTraits<base::Optional<P>> {
typedef base::Optional<P> param_type;
- static void GetSize(base::PickleSizer* sizer, const param_type& p) {
- const bool is_set = static_cast<bool>(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<bool>(p);
WriteParam(m, is_set);
@@ -1024,10 +976,9 @@ struct ParamTraits<base::Optional<P>> {
// 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<IPC::ChannelHandle> {
+template <>
+struct COMPONENT_EXPORT(IPC) ParamTraits<IPC::ChannelHandle> {
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,
@@ -1036,9 +987,8 @@ struct IPC_EXPORT ParamTraits<IPC::ChannelHandle> {
};
template <>
-struct IPC_EXPORT ParamTraits<LogData> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<LogData> {
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,
@@ -1047,7 +997,7 @@ struct IPC_EXPORT ParamTraits<LogData> {
};
template <>
-struct IPC_EXPORT ParamTraits<Message> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<Message> {
static void Write(base::Pickle* m, const Message& p);
static bool Read(const base::Pickle* m,
base::PickleIterator* iter,
@@ -1059,20 +1009,8 @@ struct IPC_EXPORT ParamTraits<Message> {
#if defined(OS_WIN)
template <>
-struct IPC_EXPORT ParamTraits<HANDLE> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<HANDLE> {
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<LOGFONT> {
- 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,
@@ -1081,9 +1019,8 @@ struct IPC_EXPORT ParamTraits<LOGFONT> {
};
template <>
-struct IPC_EXPORT ParamTraits<MSG> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<MSG> {
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,
@@ -1096,11 +1033,10 @@ struct IPC_EXPORT ParamTraits<MSG> {
// Generic message subclasses
// defined in ipc_logging.cc
-IPC_EXPORT void GenerateLogData(const Message& message,
- LogData* data,
- bool get_params);
+COMPONENT_EXPORT(IPC)
+void GenerateLogData(const Message& message, LogData* data, bool get_params);
-#if defined(IPC_MESSAGE_LOG_ENABLED)
+#if BUILDFLAG(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())
diff --git a/ipc/ipc_message_utils_unittest.cc b/ipc/ipc_message_utils_unittest.cc
new file mode 100644
index 0000000000..2b1c2c285a
--- /dev/null
+++ b/ipc/ipc_message_utils_unittest.cc
@@ -0,0 +1,240 @@
+// 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 <stddef.h>
+#include <stdint.h>
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "base/json/json_reader.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/shared_memory.h"
+#include "base/test/test_shared_memory_util.h"
+#include "base/unguessable_token.h"
+#include "ipc/ipc_channel_handle.h"
+#include "ipc/ipc_message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace IPC {
+namespace {
+
+// Tests nesting of messages as parameters to other messages.
+TEST(IPCMessageUtilsTest, NestedMessages) {
+ int32_t nested_routing = 12;
+ uint32_t nested_type = 78;
+ int nested_content = 456789;
+ Message::PriorityValue nested_priority = Message::PRIORITY_HIGH;
+ Message nested_msg(nested_routing, nested_type, nested_priority);
+ nested_msg.set_sync();
+ ParamTraits<int>::Write(&nested_msg, nested_content);
+
+ // Outer message contains the nested one as its parameter.
+ int32_t outer_routing = 91;
+ uint32_t outer_type = 88;
+ Message::PriorityValue outer_priority = Message::PRIORITY_NORMAL;
+ Message outer_msg(outer_routing, outer_type, outer_priority);
+ ParamTraits<Message>::Write(&outer_msg, nested_msg);
+
+ // Read back the nested message.
+ base::PickleIterator iter(outer_msg);
+ IPC::Message result_msg;
+ ASSERT_TRUE(ParamTraits<Message>::Read(&outer_msg, &iter, &result_msg));
+
+ // Verify nested message headers.
+ EXPECT_EQ(nested_msg.routing_id(), result_msg.routing_id());
+ EXPECT_EQ(nested_msg.type(), result_msg.type());
+ EXPECT_EQ(nested_msg.priority(), result_msg.priority());
+ EXPECT_EQ(nested_msg.flags(), result_msg.flags());
+
+ // Verify nested message content
+ base::PickleIterator nested_iter(nested_msg);
+ int result_content = 0;
+ ASSERT_TRUE(ParamTraits<int>::Read(&nested_msg, &nested_iter,
+ &result_content));
+ EXPECT_EQ(nested_content, result_content);
+
+ // Try reading past the ends for both messages and make sure it fails.
+ IPC::Message dummy;
+ ASSERT_FALSE(ParamTraits<Message>::Read(&outer_msg, &iter, &dummy));
+ ASSERT_FALSE(ParamTraits<int>::Read(&nested_msg, &nested_iter,
+ &result_content));
+}
+
+// Tests that detection of various bad parameters is working correctly.
+TEST(IPCMessageUtilsTest, ParameterValidation) {
+ base::FilePath::StringType ok_string(FILE_PATH_LITERAL("hello"), 5);
+ base::FilePath::StringType bad_string(FILE_PATH_LITERAL("hel\0o"), 5);
+
+ // Change this if ParamTraits<FilePath>::Write() changes.
+ IPC::Message message;
+ ParamTraits<base::FilePath::StringType>::Write(&message, ok_string);
+ ParamTraits<base::FilePath::StringType>::Write(&message, bad_string);
+
+ base::PickleIterator iter(message);
+ base::FilePath ok_path;
+ base::FilePath bad_path;
+ ASSERT_TRUE(ParamTraits<base::FilePath>::Read(&message, &iter, &ok_path));
+ ASSERT_FALSE(ParamTraits<base::FilePath>::Read(&message, &iter, &bad_path));
+}
+
+
+TEST(IPCMessageUtilsTest, StackVector) {
+ static const size_t stack_capacity = 5;
+ base::StackVector<double, stack_capacity> stack_vector;
+ for (size_t i = 0; i < 2 * stack_capacity; i++)
+ stack_vector->push_back(i * 2.0);
+
+ IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+ IPC::WriteParam(&msg, stack_vector);
+
+ base::StackVector<double, stack_capacity> output;
+ base::PickleIterator iter(msg);
+ EXPECT_TRUE(IPC::ReadParam(&msg, &iter, &output));
+ for (size_t i = 0; i < 2 * stack_capacity; i++)
+ EXPECT_EQ(stack_vector[i], output[i]);
+}
+
+TEST(IPCMessageUtilsTest, MojoChannelHandle) {
+ mojo::MessagePipe message_pipe;
+ IPC::ChannelHandle channel_handle(message_pipe.handle0.release());
+
+ IPC::Message message;
+ IPC::WriteParam(&message, channel_handle);
+
+ base::PickleIterator iter(message);
+ IPC::ChannelHandle result_handle;
+ EXPECT_TRUE(IPC::ReadParam(&message, &iter, &result_handle));
+ EXPECT_EQ(channel_handle.mojo_handle, result_handle.mojo_handle);
+}
+
+TEST(IPCMessageUtilsTest, OptionalUnset) {
+ base::Optional<int> opt;
+ base::Pickle pickle;
+ IPC::WriteParam(&pickle, opt);
+
+ std::string log;
+ IPC::LogParam(opt, &log);
+ EXPECT_EQ("(unset)", log);
+
+ base::Optional<int> unserialized_opt;
+ base::PickleIterator iter(pickle);
+ EXPECT_TRUE(IPC::ReadParam(&pickle, &iter, &unserialized_opt));
+ EXPECT_FALSE(unserialized_opt);
+}
+
+TEST(IPCMessageUtilsTest, OptionalSet) {
+ base::Optional<int> opt(10);
+ base::Pickle pickle;
+ IPC::WriteParam(&pickle, opt);
+
+ std::string log;
+ IPC::LogParam(opt, &log);
+ EXPECT_EQ("10", log);
+
+ base::Optional<int> unserialized_opt;
+ base::PickleIterator iter(pickle);
+ EXPECT_TRUE(IPC::ReadParam(&pickle, &iter, &unserialized_opt));
+ EXPECT_TRUE(unserialized_opt);
+ EXPECT_EQ(opt.value(), unserialized_opt.value());
+}
+
+TEST(IPCMessageUtilsTest, SharedMemoryHandle) {
+ base::SharedMemoryCreateOptions options;
+ options.size = 1004;
+ base::SharedMemory shmem;
+ ASSERT_TRUE(shmem.Create(options));
+
+ base::SharedMemoryHandle pre_pickle = shmem.handle().Duplicate();
+ ASSERT_TRUE(pre_pickle.IsValid());
+
+ IPC::Message message;
+ IPC::WriteParam(&message, pre_pickle);
+
+ base::SharedMemoryHandle post_pickle;
+ base::PickleIterator iter(message);
+ EXPECT_TRUE(IPC::ReadParam(&message, &iter, &post_pickle));
+ EXPECT_EQ(pre_pickle.GetGUID(), post_pickle.GetGUID());
+ EXPECT_EQ(pre_pickle.GetSize(), post_pickle.GetSize());
+}
+
+template <typename SharedMemoryRegionType>
+class SharedMemoryRegionTypedTest : public ::testing::Test {};
+
+typedef ::testing::Types<base::WritableSharedMemoryRegion,
+ base::UnsafeSharedMemoryRegion,
+ base::ReadOnlySharedMemoryRegion>
+ AllSharedMemoryRegionTypes;
+TYPED_TEST_CASE(SharedMemoryRegionTypedTest, AllSharedMemoryRegionTypes);
+
+TYPED_TEST(SharedMemoryRegionTypedTest, WriteAndRead) {
+ const size_t size = 2314;
+ TypeParam pre_pickle;
+ base::WritableSharedMemoryMapping pre_mapping;
+ std::tie(pre_pickle, pre_mapping) = base::CreateMappedRegion<TypeParam>(size);
+ const size_t pre_size = pre_pickle.GetSize();
+
+ const std::string content = "Hello, world!";
+ memcpy(pre_mapping.memory(), content.data(), content.size());
+
+ IPC::Message message;
+ IPC::WriteParam(&message, pre_pickle);
+ EXPECT_FALSE(pre_pickle.IsValid());
+
+ TypeParam post_pickle;
+ base::PickleIterator iter(message);
+ EXPECT_TRUE(IPC::ReadParam(&message, &iter, &post_pickle));
+ EXPECT_EQ(pre_size, post_pickle.GetSize());
+ typename TypeParam::MappingType post_mapping = post_pickle.Map();
+ EXPECT_EQ(pre_mapping.guid(), post_mapping.guid());
+ EXPECT_EQ(0, memcmp(pre_mapping.memory(), post_mapping.memory(),
+ post_pickle.GetSize()));
+}
+
+TYPED_TEST(SharedMemoryRegionTypedTest, InvalidRegion) {
+ TypeParam pre_pickle;
+ EXPECT_FALSE(pre_pickle.IsValid());
+
+ IPC::Message message;
+ IPC::WriteParam(&message, pre_pickle);
+
+ TypeParam post_pickle;
+ base::PickleIterator iter(message);
+ EXPECT_TRUE(IPC::ReadParam(&message, &iter, &post_pickle));
+ EXPECT_FALSE(post_pickle.IsValid());
+}
+
+TEST(IPCMessageUtilsTest, UnguessableTokenTest) {
+ base::UnguessableToken token = base::UnguessableToken::Create();
+ base::Pickle pickle;
+ IPC::WriteParam(&pickle, token);
+
+ std::string log;
+ IPC::LogParam(token, &log);
+ EXPECT_EQ(token.ToString(), log);
+
+ base::UnguessableToken deserialized_token;
+ base::PickleIterator iter(pickle);
+ EXPECT_TRUE(IPC::ReadParam(&pickle, &iter, &deserialized_token));
+ EXPECT_EQ(token, deserialized_token);
+}
+
+TEST(IPCMessageUtilsTest, FlatMap) {
+ base::flat_map<std::string, int> input;
+ input["foo"] = 42;
+ input["bar"] = 96;
+
+ base::Pickle pickle;
+ IPC::WriteParam(&pickle, input);
+
+ base::PickleIterator iter(pickle);
+ base::flat_map<std::string, int> output;
+ EXPECT_TRUE(IPC::ReadParam(&pickle, &iter, &output));
+
+ EXPECT_EQ(input, output);
+}
+
+} // namespace
+} // namespace IPC
diff --git a/ipc/ipc_mojo_bootstrap.cc b/ipc/ipc_mojo_bootstrap.cc
new file mode 100644
index 0000000000..08b15a06fe
--- /dev/null
+++ b/ipc/ipc_mojo_bootstrap.cc
@@ -0,0 +1,1035 @@
+// 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 "ipc/ipc_mojo_bootstrap.h"
+
+#include <inttypes.h>
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/queue.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/no_destructor.h"
+#include "base/sequenced_task_runner.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/trace_event/memory_allocator_dump.h"
+#include "base/trace_event/memory_dump_manager.h"
+#include "base/trace_event/memory_dump_provider.h"
+#include "ipc/ipc_channel.h"
+#include "mojo/public/cpp/bindings/associated_group.h"
+#include "mojo/public/cpp/bindings/associated_group_controller.h"
+#include "mojo/public/cpp/bindings/connector.h"
+#include "mojo/public/cpp/bindings/interface_endpoint_client.h"
+#include "mojo/public/cpp/bindings/interface_endpoint_controller.h"
+#include "mojo/public/cpp/bindings/interface_id.h"
+#include "mojo/public/cpp/bindings/message.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/sequence_local_sync_event_watcher.h"
+
+namespace IPC {
+
+namespace {
+
+class ChannelAssociatedGroupController;
+
+// Used to track some internal Channel state in pursuit of message leaks.
+//
+// TODO(https://crbug.com/813045): Remove this.
+class ControllerMemoryDumpProvider
+ : public base::trace_event::MemoryDumpProvider {
+ public:
+ ControllerMemoryDumpProvider() {
+ base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
+ this, "IPCChannel", nullptr);
+ }
+
+ ~ControllerMemoryDumpProvider() override {
+ base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
+ this);
+ }
+
+ void AddController(ChannelAssociatedGroupController* controller) {
+ base::AutoLock lock(lock_);
+ controllers_.insert(controller);
+ }
+
+ void RemoveController(ChannelAssociatedGroupController* controller) {
+ base::AutoLock lock(lock_);
+ controllers_.erase(controller);
+ }
+
+ // base::trace_event::MemoryDumpProvider:
+ bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
+ base::trace_event::ProcessMemoryDump* pmd) override;
+
+ private:
+ base::Lock lock_;
+ std::set<ChannelAssociatedGroupController*> controllers_;
+
+ DISALLOW_COPY_AND_ASSIGN(ControllerMemoryDumpProvider);
+};
+
+ControllerMemoryDumpProvider& GetMemoryDumpProvider() {
+ static base::NoDestructor<ControllerMemoryDumpProvider> provider;
+ return *provider;
+}
+
+class ChannelAssociatedGroupController
+ : public mojo::AssociatedGroupController,
+ public mojo::MessageReceiver,
+ public mojo::PipeControlMessageHandlerDelegate {
+ public:
+ ChannelAssociatedGroupController(
+ bool set_interface_id_namespace_bit,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& proxy_task_runner)
+ : task_runner_(ipc_task_runner),
+ proxy_task_runner_(proxy_task_runner),
+ set_interface_id_namespace_bit_(set_interface_id_namespace_bit),
+ filters_(this),
+ control_message_handler_(this),
+ control_message_proxy_thunk_(this),
+ control_message_proxy_(&control_message_proxy_thunk_) {
+ thread_checker_.DetachFromThread();
+ control_message_handler_.SetDescription(
+ "IPC::mojom::Bootstrap [master] PipeControlMessageHandler");
+ filters_.Append<mojo::MessageHeaderValidator>(
+ "IPC::mojom::Bootstrap [master] MessageHeaderValidator");
+
+ GetMemoryDumpProvider().AddController(this);
+ }
+
+ size_t GetQueuedMessageCount() {
+ base::AutoLock lock(outgoing_messages_lock_);
+ return outgoing_messages_.size();
+ }
+
+ void Bind(mojo::ScopedMessagePipeHandle handle) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(task_runner_->BelongsToCurrentThread());
+
+ connector_.reset(new mojo::Connector(
+ std::move(handle), mojo::Connector::SINGLE_THREADED_SEND,
+ task_runner_));
+ connector_->set_incoming_receiver(&filters_);
+ connector_->set_connection_error_handler(
+ base::Bind(&ChannelAssociatedGroupController::OnPipeError,
+ base::Unretained(this)));
+ connector_->set_enforce_errors_from_incoming_receiver(false);
+ connector_->SetWatcherHeapProfilerTag("IPC Channel");
+ }
+
+ void Pause() {
+ DCHECK(!paused_);
+ paused_ = true;
+ }
+
+ void Unpause() {
+ DCHECK(paused_);
+ paused_ = false;
+ }
+
+ void FlushOutgoingMessages() {
+ std::vector<mojo::Message> outgoing_messages;
+ {
+ base::AutoLock lock(outgoing_messages_lock_);
+ std::swap(outgoing_messages, outgoing_messages_);
+ }
+ for (auto& message : outgoing_messages)
+ SendMessage(&message);
+ }
+
+ void CreateChannelEndpoints(mojom::ChannelAssociatedPtr* sender,
+ mojom::ChannelAssociatedRequest* receiver) {
+ mojo::InterfaceId sender_id, receiver_id;
+ if (set_interface_id_namespace_bit_) {
+ sender_id = 1 | mojo::kInterfaceIdNamespaceMask;
+ receiver_id = 1;
+ } else {
+ sender_id = 1;
+ receiver_id = 1 | mojo::kInterfaceIdNamespaceMask;
+ }
+
+ {
+ base::AutoLock locker(lock_);
+ Endpoint* sender_endpoint = new Endpoint(this, sender_id);
+ Endpoint* receiver_endpoint = new Endpoint(this, receiver_id);
+ endpoints_.insert({ sender_id, sender_endpoint });
+ endpoints_.insert({ receiver_id, receiver_endpoint });
+ sender_endpoint->set_handle_created();
+ receiver_endpoint->set_handle_created();
+ }
+
+ mojo::ScopedInterfaceEndpointHandle sender_handle =
+ CreateScopedInterfaceEndpointHandle(sender_id);
+ mojo::ScopedInterfaceEndpointHandle receiver_handle =
+ CreateScopedInterfaceEndpointHandle(receiver_id);
+
+ sender->Bind(mojom::ChannelAssociatedPtrInfo(std::move(sender_handle), 0));
+ *receiver = mojom::ChannelAssociatedRequest(std::move(receiver_handle));
+ }
+
+ void ShutDown() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ shut_down_ = true;
+ connector_->CloseMessagePipe();
+ OnPipeError();
+ connector_.reset();
+
+ base::AutoLock lock(outgoing_messages_lock_);
+ outgoing_messages_.clear();
+ }
+
+ // mojo::AssociatedGroupController:
+ mojo::InterfaceId AssociateInterface(
+ mojo::ScopedInterfaceEndpointHandle handle_to_send) override {
+ if (!handle_to_send.pending_association())
+ return mojo::kInvalidInterfaceId;
+
+ uint32_t id = 0;
+ {
+ base::AutoLock locker(lock_);
+ do {
+ if (next_interface_id_ >= mojo::kInterfaceIdNamespaceMask)
+ next_interface_id_ = 2;
+ id = next_interface_id_++;
+ if (set_interface_id_namespace_bit_)
+ id |= mojo::kInterfaceIdNamespaceMask;
+ } while (ContainsKey(endpoints_, id));
+
+ Endpoint* endpoint = new Endpoint(this, id);
+ if (encountered_error_)
+ endpoint->set_peer_closed();
+ endpoint->set_handle_created();
+ endpoints_.insert({id, endpoint});
+ }
+
+ if (!NotifyAssociation(&handle_to_send, id)) {
+ // The peer handle of |handle_to_send|, which is supposed to join this
+ // associated group, has been closed.
+ {
+ base::AutoLock locker(lock_);
+ Endpoint* endpoint = FindEndpoint(id);
+ if (endpoint)
+ MarkClosedAndMaybeRemove(endpoint);
+ }
+
+ control_message_proxy_.NotifyPeerEndpointClosed(
+ id, handle_to_send.disconnect_reason());
+ }
+ return id;
+ }
+
+ mojo::ScopedInterfaceEndpointHandle CreateLocalEndpointHandle(
+ mojo::InterfaceId id) override {
+ if (!mojo::IsValidInterfaceId(id))
+ return mojo::ScopedInterfaceEndpointHandle();
+
+ // Unless it is the master ID, |id| is from the remote side and therefore
+ // its namespace bit is supposed to be different than the value that this
+ // router would use.
+ if (!mojo::IsMasterInterfaceId(id) &&
+ set_interface_id_namespace_bit_ ==
+ mojo::HasInterfaceIdNamespaceBitSet(id)) {
+ return mojo::ScopedInterfaceEndpointHandle();
+ }
+
+ base::AutoLock locker(lock_);
+ bool inserted = false;
+ Endpoint* endpoint = FindOrInsertEndpoint(id, &inserted);
+ if (inserted) {
+ DCHECK(!endpoint->handle_created());
+ if (encountered_error_)
+ endpoint->set_peer_closed();
+ } else {
+ if (endpoint->handle_created())
+ return mojo::ScopedInterfaceEndpointHandle();
+ }
+
+ endpoint->set_handle_created();
+ return CreateScopedInterfaceEndpointHandle(id);
+ }
+
+ void CloseEndpointHandle(
+ mojo::InterfaceId id,
+ const base::Optional<mojo::DisconnectReason>& reason) override {
+ if (!mojo::IsValidInterfaceId(id))
+ return;
+ {
+ base::AutoLock locker(lock_);
+ DCHECK(ContainsKey(endpoints_, id));
+ Endpoint* endpoint = endpoints_[id].get();
+ DCHECK(!endpoint->client());
+ DCHECK(!endpoint->closed());
+ MarkClosedAndMaybeRemove(endpoint);
+ }
+
+ if (!mojo::IsMasterInterfaceId(id) || reason)
+ control_message_proxy_.NotifyPeerEndpointClosed(id, reason);
+ }
+
+ mojo::InterfaceEndpointController* AttachEndpointClient(
+ const mojo::ScopedInterfaceEndpointHandle& handle,
+ mojo::InterfaceEndpointClient* client,
+ scoped_refptr<base::SequencedTaskRunner> runner) override {
+ const mojo::InterfaceId id = handle.id();
+
+ DCHECK(mojo::IsValidInterfaceId(id));
+ DCHECK(client);
+
+ base::AutoLock locker(lock_);
+ DCHECK(ContainsKey(endpoints_, id));
+
+ Endpoint* endpoint = endpoints_[id].get();
+ endpoint->AttachClient(client, std::move(runner));
+
+ if (endpoint->peer_closed())
+ NotifyEndpointOfError(endpoint, true /* force_async */);
+
+ return endpoint;
+ }
+
+ void DetachEndpointClient(
+ const mojo::ScopedInterfaceEndpointHandle& handle) override {
+ const mojo::InterfaceId id = handle.id();
+
+ DCHECK(mojo::IsValidInterfaceId(id));
+
+ base::AutoLock locker(lock_);
+ DCHECK(ContainsKey(endpoints_, id));
+
+ Endpoint* endpoint = endpoints_[id].get();
+ endpoint->DetachClient();
+ }
+
+ void RaiseError() override {
+ // We ignore errors on channel endpoints, leaving the pipe open. There are
+ // good reasons for this:
+ //
+ // * We should never close a channel endpoint in either process as long as
+ // the child process is still alive. The child's endpoint should only be
+ // closed implicitly by process death, and the browser's endpoint should
+ // only be closed after the child process is confirmed to be dead. Crash
+ // reporting logic in Chrome relies on this behavior in order to do the
+ // right thing.
+ //
+ // * There are two interesting conditions under which RaiseError() can be
+ // implicitly reached: an incoming message fails validation, or the
+ // local endpoint drops a response callback without calling it.
+ //
+ // * In the validation case, we also report the message as bad, and this
+ // will imminently trigger the common bad-IPC path in the browser,
+ // causing the browser to kill the offending renderer.
+ //
+ // * In the dropped response callback case, the net result of ignoring the
+ // issue is generally innocuous. While indicative of programmer error,
+ // it's not a severe failure and is already covered by separate DCHECKs.
+ //
+ // See https://crbug.com/861607 for additional discussion.
+ }
+
+ bool PrefersSerializedMessages() override { return true; }
+
+ private:
+ class Endpoint;
+ class ControlMessageProxyThunk;
+ friend class Endpoint;
+ friend class ControlMessageProxyThunk;
+
+ // MessageWrapper objects are always destroyed under the controller's lock. On
+ // destruction, if the message it wrappers contains
+ // ScopedInterfaceEndpointHandles (which cannot be destructed under the
+ // controller's lock), the wrapper unlocks to clean them up.
+ class MessageWrapper {
+ public:
+ MessageWrapper() = default;
+
+ MessageWrapper(ChannelAssociatedGroupController* controller,
+ mojo::Message message)
+ : controller_(controller), value_(std::move(message)) {}
+
+ MessageWrapper(MessageWrapper&& other)
+ : controller_(other.controller_), value_(std::move(other.value_)) {}
+
+ ~MessageWrapper() {
+ if (value_.associated_endpoint_handles()->empty())
+ return;
+
+ controller_->lock_.AssertAcquired();
+ {
+ base::AutoUnlock unlocker(controller_->lock_);
+ value_.mutable_associated_endpoint_handles()->clear();
+ }
+ }
+
+ MessageWrapper& operator=(MessageWrapper&& other) {
+ controller_ = other.controller_;
+ value_ = std::move(other.value_);
+ return *this;
+ }
+
+ mojo::Message& value() { return value_; }
+
+ private:
+ ChannelAssociatedGroupController* controller_ = nullptr;
+ mojo::Message value_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageWrapper);
+ };
+
+ class Endpoint : public base::RefCountedThreadSafe<Endpoint>,
+ public mojo::InterfaceEndpointController {
+ public:
+ Endpoint(ChannelAssociatedGroupController* controller, mojo::InterfaceId id)
+ : controller_(controller), id_(id) {}
+
+ mojo::InterfaceId id() const { return id_; }
+
+ bool closed() const {
+ controller_->lock_.AssertAcquired();
+ return closed_;
+ }
+
+ void set_closed() {
+ controller_->lock_.AssertAcquired();
+ closed_ = true;
+ }
+
+ bool peer_closed() const {
+ controller_->lock_.AssertAcquired();
+ return peer_closed_;
+ }
+
+ void set_peer_closed() {
+ controller_->lock_.AssertAcquired();
+ peer_closed_ = true;
+ }
+
+ bool handle_created() const {
+ controller_->lock_.AssertAcquired();
+ return handle_created_;
+ }
+
+ void set_handle_created() {
+ controller_->lock_.AssertAcquired();
+ handle_created_ = true;
+ }
+
+ const base::Optional<mojo::DisconnectReason>& disconnect_reason() const {
+ return disconnect_reason_;
+ }
+
+ void set_disconnect_reason(
+ const base::Optional<mojo::DisconnectReason>& disconnect_reason) {
+ disconnect_reason_ = disconnect_reason;
+ }
+
+ base::SequencedTaskRunner* task_runner() const {
+ return task_runner_.get();
+ }
+
+ mojo::InterfaceEndpointClient* client() const {
+ controller_->lock_.AssertAcquired();
+ return client_;
+ }
+
+ void AttachClient(mojo::InterfaceEndpointClient* client,
+ scoped_refptr<base::SequencedTaskRunner> runner) {
+ controller_->lock_.AssertAcquired();
+ DCHECK(!client_);
+ DCHECK(!closed_);
+ DCHECK(runner->RunsTasksInCurrentSequence());
+
+ task_runner_ = std::move(runner);
+ client_ = client;
+ }
+
+ void DetachClient() {
+ controller_->lock_.AssertAcquired();
+ DCHECK(client_);
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!closed_);
+
+ task_runner_ = nullptr;
+ client_ = nullptr;
+ sync_watcher_.reset();
+ }
+
+ uint32_t EnqueueSyncMessage(MessageWrapper message) {
+ controller_->lock_.AssertAcquired();
+ uint32_t id = GenerateSyncMessageId();
+ sync_messages_.emplace(id, std::move(message));
+ SignalSyncMessageEvent();
+ return id;
+ }
+
+ void SignalSyncMessageEvent() {
+ controller_->lock_.AssertAcquired();
+
+ if (sync_watcher_)
+ sync_watcher_->SignalEvent();
+ }
+
+ MessageWrapper PopSyncMessage(uint32_t id) {
+ controller_->lock_.AssertAcquired();
+ if (sync_messages_.empty() || sync_messages_.front().first != id)
+ return MessageWrapper();
+ MessageWrapper message = std::move(sync_messages_.front().second);
+ sync_messages_.pop();
+ return message;
+ }
+
+ // mojo::InterfaceEndpointController:
+ bool SendMessage(mojo::Message* message) override {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ message->set_interface_id(id_);
+ return controller_->SendMessage(message);
+ }
+
+ void AllowWokenUpBySyncWatchOnSameThread() override {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ EnsureSyncWatcherExists();
+ sync_watcher_->AllowWokenUpBySyncWatchOnSameSequence();
+ }
+
+ bool SyncWatch(const bool* should_stop) override {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ // It's not legal to make sync calls from the master endpoint's thread,
+ // and in fact they must only happen from the proxy task runner.
+ DCHECK(!controller_->task_runner_->BelongsToCurrentThread());
+ DCHECK(controller_->proxy_task_runner_->BelongsToCurrentThread());
+
+ EnsureSyncWatcherExists();
+ return sync_watcher_->SyncWatch(should_stop);
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<Endpoint>;
+
+ ~Endpoint() override {
+ controller_->lock_.AssertAcquired();
+ DCHECK(!client_);
+ DCHECK(closed_);
+ DCHECK(peer_closed_);
+ DCHECK(!sync_watcher_);
+ }
+
+ void OnSyncMessageEventReady() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ scoped_refptr<Endpoint> keepalive(this);
+ scoped_refptr<AssociatedGroupController> controller_keepalive(
+ controller_);
+ base::AutoLock locker(controller_->lock_);
+ bool more_to_process = false;
+ if (!sync_messages_.empty()) {
+ MessageWrapper message_wrapper =
+ std::move(sync_messages_.front().second);
+ sync_messages_.pop();
+
+ bool dispatch_succeeded;
+ mojo::InterfaceEndpointClient* client = client_;
+ {
+ base::AutoUnlock unlocker(controller_->lock_);
+ dispatch_succeeded =
+ client->HandleIncomingMessage(&message_wrapper.value());
+ }
+
+ if (!sync_messages_.empty())
+ more_to_process = true;
+
+ if (!dispatch_succeeded)
+ controller_->RaiseError();
+ }
+
+ if (!more_to_process)
+ sync_watcher_->ResetEvent();
+
+ // If there are no queued sync messages and the peer has closed, there
+ // there won't be incoming sync messages in the future. If any
+ // SyncWatch() calls are on the stack for this endpoint, resetting the
+ // watcher will allow them to exit as the stack undwinds.
+ if (!more_to_process && peer_closed_)
+ sync_watcher_.reset();
+ }
+
+ void EnsureSyncWatcherExists() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ if (sync_watcher_)
+ return;
+
+ base::AutoLock locker(controller_->lock_);
+ sync_watcher_ = std::make_unique<mojo::SequenceLocalSyncEventWatcher>(
+ base::BindRepeating(&Endpoint::OnSyncMessageEventReady,
+ base::Unretained(this)));
+ if (peer_closed_ || !sync_messages_.empty())
+ SignalSyncMessageEvent();
+ }
+
+ uint32_t GenerateSyncMessageId() {
+ // Overflow is fine.
+ uint32_t id = next_sync_message_id_++;
+ DCHECK(sync_messages_.empty() || sync_messages_.front().first != id);
+ return id;
+ }
+
+ ChannelAssociatedGroupController* const controller_;
+ const mojo::InterfaceId id_;
+
+ bool closed_ = false;
+ bool peer_closed_ = false;
+ bool handle_created_ = false;
+ base::Optional<mojo::DisconnectReason> disconnect_reason_;
+ mojo::InterfaceEndpointClient* client_ = nullptr;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+ std::unique_ptr<mojo::SequenceLocalSyncEventWatcher> sync_watcher_;
+ base::queue<std::pair<uint32_t, MessageWrapper>> sync_messages_;
+ uint32_t next_sync_message_id_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(Endpoint);
+ };
+
+ class ControlMessageProxyThunk : public MessageReceiver {
+ public:
+ explicit ControlMessageProxyThunk(
+ ChannelAssociatedGroupController* controller)
+ : controller_(controller) {}
+
+ private:
+ // MessageReceiver:
+ bool Accept(mojo::Message* message) override {
+ return controller_->SendMessage(message);
+ }
+
+ ChannelAssociatedGroupController* controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(ControlMessageProxyThunk);
+ };
+
+ ~ChannelAssociatedGroupController() override {
+ DCHECK(!connector_);
+
+ base::AutoLock locker(lock_);
+ for (auto iter = endpoints_.begin(); iter != endpoints_.end();) {
+ Endpoint* endpoint = iter->second.get();
+ ++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());
+ MarkClosedAndMaybeRemove(endpoint);
+ } else {
+ MarkPeerClosedAndMaybeRemove(endpoint);
+ }
+ }
+
+ DCHECK(endpoints_.empty());
+
+ GetMemoryDumpProvider().RemoveController(this);
+ }
+
+ bool SendMessage(mojo::Message* message) {
+ if (task_runner_->BelongsToCurrentThread()) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!connector_ || paused_) {
+ if (!shut_down_) {
+ base::AutoLock lock(outgoing_messages_lock_);
+ outgoing_messages_.emplace_back(std::move(*message));
+ }
+ return true;
+ }
+ return connector_->Accept(message);
+ } else {
+ // Do a message size check here so we don't lose valuable stack
+ // information to the task scheduler.
+ CHECK_LE(message->data_num_bytes(), Channel::kMaximumMessageSize);
+
+ // We always post tasks to the master endpoint thread when called from
+ // other threads in order to simulate IPC::ChannelProxy::Send behavior.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &ChannelAssociatedGroupController::SendMessageOnMasterThread,
+ this, base::Passed(message)));
+ return true;
+ }
+ }
+
+ void SendMessageOnMasterThread(mojo::Message message) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!SendMessage(&message))
+ RaiseError();
+ }
+
+ void OnPipeError() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // We keep |this| alive here because it's possible for the notifications
+ // below to release all other references.
+ scoped_refptr<ChannelAssociatedGroupController> keepalive(this);
+
+ base::AutoLock locker(lock_);
+ encountered_error_ = true;
+
+ std::vector<scoped_refptr<Endpoint>> endpoints_to_notify;
+ for (auto iter = endpoints_.begin(); iter != endpoints_.end();) {
+ Endpoint* endpoint = iter->second.get();
+ ++iter;
+
+ if (endpoint->client())
+ endpoints_to_notify.push_back(endpoint);
+
+ MarkPeerClosedAndMaybeRemove(endpoint);
+ }
+
+ for (auto& endpoint : endpoints_to_notify) {
+ // Because a notification may in turn detach any endpoint, we have to
+ // check each client again here.
+ if (endpoint->client())
+ NotifyEndpointOfError(endpoint.get(), false /* force_async */);
+ }
+ }
+
+ void NotifyEndpointOfError(Endpoint* endpoint, bool force_async) {
+ lock_.AssertAcquired();
+ DCHECK(endpoint->task_runner() && endpoint->client());
+ if (endpoint->task_runner()->RunsTasksInCurrentSequence() && !force_async) {
+ mojo::InterfaceEndpointClient* client = endpoint->client();
+ base::Optional<mojo::DisconnectReason> reason(
+ endpoint->disconnect_reason());
+
+ base::AutoUnlock unlocker(lock_);
+ client->NotifyError(reason);
+ } else {
+ endpoint->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&ChannelAssociatedGroupController::
+ NotifyEndpointOfErrorOnEndpointThread,
+ this, endpoint->id(), base::Unretained(endpoint)));
+ }
+ }
+
+ void NotifyEndpointOfErrorOnEndpointThread(mojo::InterfaceId id,
+ Endpoint* endpoint) {
+ base::AutoLock locker(lock_);
+ auto iter = endpoints_.find(id);
+ if (iter == endpoints_.end() || iter->second.get() != endpoint)
+ return;
+ if (!endpoint->client())
+ return;
+
+ DCHECK(endpoint->task_runner()->RunsTasksInCurrentSequence());
+ NotifyEndpointOfError(endpoint, false /* force_async */);
+ }
+
+ void MarkClosedAndMaybeRemove(Endpoint* endpoint) {
+ lock_.AssertAcquired();
+ endpoint->set_closed();
+ if (endpoint->closed() && endpoint->peer_closed())
+ endpoints_.erase(endpoint->id());
+ }
+
+ void MarkPeerClosedAndMaybeRemove(Endpoint* endpoint) {
+ lock_.AssertAcquired();
+ endpoint->set_peer_closed();
+ endpoint->SignalSyncMessageEvent();
+ if (endpoint->closed() && endpoint->peer_closed())
+ endpoints_.erase(endpoint->id());
+ }
+
+ Endpoint* FindOrInsertEndpoint(mojo::InterfaceId id, bool* inserted) {
+ lock_.AssertAcquired();
+ DCHECK(!inserted || !*inserted);
+
+ Endpoint* endpoint = FindEndpoint(id);
+ if (!endpoint) {
+ endpoint = new Endpoint(this, id);
+ endpoints_.insert({id, endpoint});
+ if (inserted)
+ *inserted = true;
+ }
+ return endpoint;
+ }
+
+ Endpoint* FindEndpoint(mojo::InterfaceId id) {
+ lock_.AssertAcquired();
+ auto iter = endpoints_.find(id);
+ return iter != endpoints_.end() ? iter->second.get() : nullptr;
+ }
+
+ // mojo::MessageReceiver:
+ bool Accept(mojo::Message* message) override {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!message->DeserializeAssociatedEndpointHandles(this))
+ return false;
+
+ if (mojo::PipeControlMessageHandler::IsPipeControlMessage(message))
+ return control_message_handler_.Accept(message);
+
+ mojo::InterfaceId id = message->interface_id();
+ DCHECK(mojo::IsValidInterfaceId(id));
+
+ base::AutoLock locker(lock_);
+ Endpoint* endpoint = FindEndpoint(id);
+ if (!endpoint)
+ return true;
+
+ mojo::InterfaceEndpointClient* client = endpoint->client();
+ if (!client || !endpoint->task_runner()->RunsTasksInCurrentSequence()) {
+ // No client has been bound yet or the client runs tasks on another
+ // thread. We assume the other thread must always be the one on which
+ // |proxy_task_runner_| runs tasks, since that's the only valid scenario.
+ //
+ // If the client is not yet bound, it must be bound by the time this task
+ // runs or else it's programmer error.
+ DCHECK(proxy_task_runner_);
+
+ if (message->has_flag(mojo::Message::kFlagIsSync)) {
+ MessageWrapper message_wrapper(this, std::move(*message));
+ // Sync messages may need to be handled by the endpoint if it's blocking
+ // on a sync reply. We pass ownership of the message to the endpoint's
+ // sync message queue. If the endpoint was blocking, it will dequeue the
+ // message and dispatch it. Otherwise the posted |AcceptSyncMessage()|
+ // call will dequeue the message and dispatch it.
+ uint32_t message_id =
+ endpoint->EnqueueSyncMessage(std::move(message_wrapper));
+ proxy_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&ChannelAssociatedGroupController::AcceptSyncMessage,
+ this, id, message_id));
+ return true;
+ }
+
+ proxy_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&ChannelAssociatedGroupController::AcceptOnProxyThread,
+ this, base::Passed(message)));
+ return true;
+ }
+
+ // We do not expect to receive sync responses on the master endpoint thread.
+ // If it's happening, it's a bug.
+ DCHECK(!message->has_flag(mojo::Message::kFlagIsSync) ||
+ !message->has_flag(mojo::Message::kFlagIsResponse));
+
+ base::AutoUnlock unlocker(lock_);
+ return client->HandleIncomingMessage(message);
+ }
+
+ void AcceptOnProxyThread(mojo::Message message) {
+ DCHECK(proxy_task_runner_->BelongsToCurrentThread());
+
+ mojo::InterfaceId id = message.interface_id();
+ DCHECK(mojo::IsValidInterfaceId(id) && !mojo::IsMasterInterfaceId(id));
+
+ base::AutoLock locker(lock_);
+ Endpoint* endpoint = FindEndpoint(id);
+ if (!endpoint)
+ return;
+
+ mojo::InterfaceEndpointClient* client = endpoint->client();
+ if (!client)
+ return;
+
+ DCHECK(endpoint->task_runner()->RunsTasksInCurrentSequence());
+
+ // Sync messages should never make their way to this method.
+ DCHECK(!message.has_flag(mojo::Message::kFlagIsSync));
+
+ bool result = false;
+ {
+ base::AutoUnlock unlocker(lock_);
+ result = client->HandleIncomingMessage(&message);
+ }
+
+ if (!result)
+ RaiseError();
+ }
+
+ void AcceptSyncMessage(mojo::InterfaceId interface_id, uint32_t message_id) {
+ DCHECK(proxy_task_runner_->BelongsToCurrentThread());
+
+ base::AutoLock locker(lock_);
+ Endpoint* endpoint = FindEndpoint(interface_id);
+ if (!endpoint)
+ return;
+
+ // Careful, if the endpoint is detached its members are cleared. Check for
+ // that before dereferencing.
+ mojo::InterfaceEndpointClient* client = endpoint->client();
+ if (!client)
+ return;
+
+ DCHECK(endpoint->task_runner()->RunsTasksInCurrentSequence());
+ MessageWrapper message_wrapper = endpoint->PopSyncMessage(message_id);
+
+ // The message must have already been dequeued by the endpoint waking up
+ // from a sync wait. Nothing to do.
+ if (message_wrapper.value().IsNull())
+ return;
+
+ bool result = false;
+ {
+ base::AutoUnlock unlocker(lock_);
+ result = client->HandleIncomingMessage(&message_wrapper.value());
+ }
+
+ if (!result)
+ RaiseError();
+ }
+
+ // mojo::PipeControlMessageHandlerDelegate:
+ bool OnPeerAssociatedEndpointClosed(
+ mojo::InterfaceId id,
+ const base::Optional<mojo::DisconnectReason>& reason) override {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ scoped_refptr<ChannelAssociatedGroupController> keepalive(this);
+ base::AutoLock locker(lock_);
+ scoped_refptr<Endpoint> endpoint = FindOrInsertEndpoint(id, nullptr);
+ if (reason)
+ endpoint->set_disconnect_reason(reason);
+ if (!endpoint->peer_closed()) {
+ if (endpoint->client())
+ NotifyEndpointOfError(endpoint.get(), false /* force_async */);
+ MarkPeerClosedAndMaybeRemove(endpoint.get());
+ }
+
+ return true;
+ }
+
+ // Checked in places which must be run on the master endpoint's thread.
+ base::ThreadChecker thread_checker_;
+
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ scoped_refptr<base::SingleThreadTaskRunner> proxy_task_runner_;
+ const bool set_interface_id_namespace_bit_;
+ bool paused_ = false;
+ std::unique_ptr<mojo::Connector> connector_;
+ mojo::FilterChain filters_;
+ mojo::PipeControlMessageHandler control_message_handler_;
+ ControlMessageProxyThunk control_message_proxy_thunk_;
+
+ // NOTE: It is unsafe to call into this object while holding |lock_|.
+ mojo::PipeControlMessageProxy control_message_proxy_;
+
+ // Guards access to |outgoing_messages_| only. Used to support memory dumps
+ // which may be triggered from any thread.
+ base::Lock outgoing_messages_lock_;
+
+ // Outgoing messages that were sent before this controller was bound to a
+ // real message pipe.
+ std::vector<mojo::Message> outgoing_messages_;
+
+ // Guards the fields below for thread-safe access.
+ base::Lock lock_;
+
+ bool encountered_error_ = false;
+ bool shut_down_ = false;
+
+ // ID #1 is reserved for the mojom::Channel interface.
+ uint32_t next_interface_id_ = 2;
+
+ std::map<uint32_t, scoped_refptr<Endpoint>> endpoints_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChannelAssociatedGroupController);
+};
+
+bool ControllerMemoryDumpProvider::OnMemoryDump(
+ const base::trace_event::MemoryDumpArgs& args,
+ base::trace_event::ProcessMemoryDump* pmd) {
+ base::AutoLock lock(lock_);
+ for (auto* controller : controllers_) {
+ base::trace_event::MemoryAllocatorDump* dump = pmd->CreateAllocatorDump(
+ base::StringPrintf("mojo/queued_ipc_channel_message/0x%" PRIxPTR,
+ reinterpret_cast<uintptr_t>(controller)));
+ dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameObjectCount,
+ base::trace_event::MemoryAllocatorDump::kUnitsObjects,
+ controller->GetQueuedMessageCount());
+ }
+
+ return true;
+}
+
+class MojoBootstrapImpl : public MojoBootstrap {
+ public:
+ MojoBootstrapImpl(
+ mojo::ScopedMessagePipeHandle handle,
+ const scoped_refptr<ChannelAssociatedGroupController> controller)
+ : controller_(controller),
+ associated_group_(controller),
+ handle_(std::move(handle)) {}
+
+ ~MojoBootstrapImpl() override {
+ controller_->ShutDown();
+ }
+
+ private:
+ void Connect(mojom::ChannelAssociatedPtr* sender,
+ mojom::ChannelAssociatedRequest* receiver) override {
+ controller_->Bind(std::move(handle_));
+ controller_->CreateChannelEndpoints(sender, receiver);
+ }
+
+ void Pause() override {
+ controller_->Pause();
+ }
+
+ void Unpause() override {
+ controller_->Unpause();
+ }
+
+ void Flush() override {
+ controller_->FlushOutgoingMessages();
+ }
+
+ mojo::AssociatedGroup* GetAssociatedGroup() override {
+ return &associated_group_;
+ }
+
+ scoped_refptr<ChannelAssociatedGroupController> controller_;
+ mojo::AssociatedGroup associated_group_;
+
+ mojo::ScopedMessagePipeHandle handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(MojoBootstrapImpl);
+};
+
+} // namespace
+
+// static
+std::unique_ptr<MojoBootstrap> MojoBootstrap::Create(
+ mojo::ScopedMessagePipeHandle handle,
+ Channel::Mode mode,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& proxy_task_runner) {
+ return std::make_unique<MojoBootstrapImpl>(
+ std::move(handle),
+ new ChannelAssociatedGroupController(mode == Channel::MODE_SERVER,
+ ipc_task_runner, proxy_task_runner));
+}
+
+} // namespace IPC
diff --git a/ipc/ipc_mojo_bootstrap.h b/ipc/ipc_mojo_bootstrap.h
new file mode 100644
index 0000000000..5aba8a3584
--- /dev/null
+++ b/ipc/ipc_mojo_bootstrap.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 IPC_IPC_MOJO_BOOTSTRAP_H_
+#define IPC_IPC_MOJO_BOOTSTRAP_H_
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "build/build_config.h"
+#include "ipc/ipc.mojom.h"
+#include "ipc/ipc_channel.h"
+#include "ipc/ipc_listener.h"
+#include "mojo/public/cpp/bindings/associated_group.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace IPC {
+
+// MojoBootstrap establishes a pair of associated interfaces between two
+// processes in Chrome.
+//
+// Clients should implement MojoBootstrap::Delegate to get the associated pipes
+// from MojoBootstrap object.
+//
+// This lives on IO thread other than Create(), which can be called from
+// UI thread as Channel::Create() can be.
+class COMPONENT_EXPORT(IPC) MojoBootstrap {
+ public:
+ virtual ~MojoBootstrap() {}
+
+ // Create the MojoBootstrap instance, using |handle| as the message pipe, in
+ // mode as specified by |mode|. The result is passed to |delegate|.
+ static std::unique_ptr<MojoBootstrap> Create(
+ mojo::ScopedMessagePipeHandle handle,
+ Channel::Mode mode,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& proxy_task_runner);
+
+ // Start the handshake over the underlying message pipe.
+ virtual void Connect(mojom::ChannelAssociatedPtr* sender,
+ mojom::ChannelAssociatedRequest* receiver) = 0;
+
+ // Stop transmitting messages and start queueing them instead.
+ virtual void Pause() = 0;
+
+ // Stop queuing new messages and start transmitting them instead.
+ virtual void Unpause() = 0;
+
+ // Flush outgoing messages which were queued before Start().
+ virtual void Flush() = 0;
+
+ virtual mojo::AssociatedGroup* GetAssociatedGroup() = 0;
+
+ enum { kMaxOutgoingMessagesSizeForTesting = 100000u };
+};
+
+} // namespace IPC
+
+#endif // IPC_IPC_MOJO_BOOTSTRAP_H_
diff --git a/ipc/ipc_mojo_bootstrap_unittest.cc b/ipc/ipc_mojo_bootstrap_unittest.cc
new file mode 100644
index 0000000000..77728967f9
--- /dev/null
+++ b/ipc/ipc_mojo_bootstrap_unittest.cc
@@ -0,0 +1,207 @@
+// 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 "ipc/ipc_mojo_bootstrap.h"
+
+#include <cstdint>
+#include <memory>
+#include <utility>
+
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "ipc/ipc.mojom.h"
+#include "ipc/ipc_test_base.h"
+#include "mojo/core/test/multiprocess_test_helper.h"
+#include "mojo/public/cpp/bindings/associated_binding.h"
+
+namespace {
+
+constexpr int32_t kTestServerPid = 42;
+constexpr int32_t kTestClientPid = 4242;
+
+class Connection {
+ public:
+ explicit Connection(std::unique_ptr<IPC::MojoBootstrap> bootstrap,
+ int32_t sender_id)
+ : bootstrap_(std::move(bootstrap)) {
+ bootstrap_->Connect(&sender_, &receiver_);
+ sender_->SetPeerPid(sender_id);
+ }
+
+ void TakeReceiver(IPC::mojom::ChannelAssociatedRequest* receiver) {
+ *receiver = std::move(receiver_);
+ }
+
+ IPC::mojom::ChannelAssociatedPtr& GetSender() { return sender_; }
+
+ private:
+ IPC::mojom::ChannelAssociatedPtr sender_;
+ IPC::mojom::ChannelAssociatedRequest receiver_;
+ std::unique_ptr<IPC::MojoBootstrap> bootstrap_;
+};
+
+class PeerPidReceiver : public IPC::mojom::Channel {
+ public:
+ enum class MessageExpectation {
+ kNotExpected,
+ kExpectedValid,
+ kExpectedInvalid
+ };
+
+ PeerPidReceiver(
+ IPC::mojom::ChannelAssociatedRequest request,
+ const base::Closure& on_peer_pid_set,
+ MessageExpectation message_expectation = MessageExpectation::kNotExpected)
+ : binding_(this, std::move(request)),
+ on_peer_pid_set_(on_peer_pid_set),
+ message_expectation_(message_expectation) {
+ binding_.set_connection_error_handler(disconnect_run_loop_.QuitClosure());
+ }
+
+ ~PeerPidReceiver() override {
+ bool expected_message =
+ message_expectation_ != MessageExpectation::kNotExpected;
+ EXPECT_EQ(expected_message, received_message_);
+ }
+
+ // mojom::Channel:
+ void SetPeerPid(int32_t pid) override {
+ peer_pid_ = pid;
+ on_peer_pid_set_.Run();
+ }
+
+ void Receive(IPC::MessageView message_view) override {
+ ASSERT_NE(MessageExpectation::kNotExpected, message_expectation_);
+ received_message_ = true;
+
+ IPC::Message message(message_view.data(), message_view.size());
+ bool expected_valid =
+ message_expectation_ == MessageExpectation::kExpectedValid;
+ EXPECT_EQ(expected_valid, message.IsValid());
+ }
+
+ void GetAssociatedInterface(
+ const std::string& name,
+ IPC::mojom::GenericInterfaceAssociatedRequest request) override {}
+
+ int32_t peer_pid() const { return peer_pid_; }
+
+ void RunUntilDisconnect() { disconnect_run_loop_.Run(); }
+
+ private:
+ mojo::AssociatedBinding<IPC::mojom::Channel> binding_;
+ const base::Closure on_peer_pid_set_;
+ MessageExpectation message_expectation_;
+ int32_t peer_pid_ = -1;
+ bool received_message_ = false;
+ base::RunLoop disconnect_run_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(PeerPidReceiver);
+};
+
+class IPCMojoBootstrapTest : public testing::Test {
+ protected:
+ mojo::core::test::MultiprocessTestHelper helper_;
+};
+
+TEST_F(IPCMojoBootstrapTest, Connect) {
+ base::MessageLoop message_loop;
+ Connection connection(
+ IPC::MojoBootstrap::Create(
+ helper_.StartChild("IPCMojoBootstrapTestClient"),
+ IPC::Channel::MODE_SERVER, base::ThreadTaskRunnerHandle::Get(),
+ base::ThreadTaskRunnerHandle::Get()),
+ kTestServerPid);
+
+ IPC::mojom::ChannelAssociatedRequest receiver;
+ connection.TakeReceiver(&receiver);
+
+ base::RunLoop run_loop;
+ PeerPidReceiver impl(std::move(receiver), run_loop.QuitClosure());
+ run_loop.Run();
+
+ EXPECT_EQ(kTestClientPid, impl.peer_pid());
+
+ impl.RunUntilDisconnect();
+ EXPECT_TRUE(helper_.WaitForChildTestShutdown());
+}
+
+// A long running process that connects to us.
+MULTIPROCESS_TEST_MAIN_WITH_SETUP(
+ IPCMojoBootstrapTestClientTestChildMain,
+ ::mojo::core::test::MultiprocessTestHelper::ChildSetup) {
+ base::MessageLoop message_loop;
+ Connection connection(
+ IPC::MojoBootstrap::Create(
+ std::move(mojo::core::test::MultiprocessTestHelper::primordial_pipe),
+ IPC::Channel::MODE_CLIENT, base::ThreadTaskRunnerHandle::Get(),
+ base::ThreadTaskRunnerHandle::Get()),
+ kTestClientPid);
+
+ IPC::mojom::ChannelAssociatedRequest receiver;
+ connection.TakeReceiver(&receiver);
+
+ base::RunLoop run_loop;
+ PeerPidReceiver impl(std::move(receiver), run_loop.QuitClosure());
+ run_loop.Run();
+
+ EXPECT_EQ(kTestServerPid, impl.peer_pid());
+
+ return 0;
+}
+
+TEST_F(IPCMojoBootstrapTest, ReceiveEmptyMessage) {
+ base::MessageLoop message_loop;
+ Connection connection(
+ IPC::MojoBootstrap::Create(
+ helper_.StartChild("IPCMojoBootstrapTestEmptyMessage"),
+ IPC::Channel::MODE_SERVER, base::ThreadTaskRunnerHandle::Get(),
+ base::ThreadTaskRunnerHandle::Get()),
+ kTestServerPid);
+
+ IPC::mojom::ChannelAssociatedRequest receiver;
+ connection.TakeReceiver(&receiver);
+
+ base::RunLoop run_loop;
+ PeerPidReceiver impl(std::move(receiver), run_loop.QuitClosure(),
+ PeerPidReceiver::MessageExpectation::kExpectedInvalid);
+ run_loop.Run();
+
+ // Wait for the Channel to be disconnected so we can reasonably assert that
+ // the child's empty message must have been received before we pass the test.
+ impl.RunUntilDisconnect();
+
+ EXPECT_TRUE(helper_.WaitForChildTestShutdown());
+}
+
+// A long running process that connects to us.
+MULTIPROCESS_TEST_MAIN_WITH_SETUP(
+ IPCMojoBootstrapTestEmptyMessageTestChildMain,
+ ::mojo::core::test::MultiprocessTestHelper::ChildSetup) {
+ base::MessageLoop message_loop;
+ Connection connection(
+ IPC::MojoBootstrap::Create(
+ std::move(mojo::core::test::MultiprocessTestHelper::primordial_pipe),
+ IPC::Channel::MODE_CLIENT, base::ThreadTaskRunnerHandle::Get(),
+ base::ThreadTaskRunnerHandle::Get()),
+ kTestClientPid);
+
+ IPC::mojom::ChannelAssociatedRequest receiver;
+ connection.TakeReceiver(&receiver);
+ auto& sender = connection.GetSender();
+
+ uint8_t data = 0;
+ sender->Receive(
+ IPC::MessageView(mojo_base::BigBufferView(base::make_span(&data, 0)),
+ base::nullopt /* handles */));
+
+ base::RunLoop run_loop;
+ PeerPidReceiver impl(std::move(receiver), run_loop.QuitClosure());
+ run_loop.Run();
+
+ return 0;
+}
+
+} // namespace
diff --git a/ipc/ipc_mojo_handle_attachment.cc b/ipc/ipc_mojo_handle_attachment.cc
index e3421c3e88..0e974a7fd7 100644
--- a/ipc/ipc_mojo_handle_attachment.cc
+++ b/ipc/ipc_mojo_handle_attachment.cc
@@ -14,8 +14,7 @@ namespace internal {
MojoHandleAttachment::MojoHandleAttachment(mojo::ScopedHandle handle)
: handle_(std::move(handle)) {}
-MojoHandleAttachment::~MojoHandleAttachment() {
-}
+MojoHandleAttachment::~MojoHandleAttachment() = default;
MessageAttachment::Type MojoHandleAttachment::GetType() const {
return Type::MOJO_HANDLE;
diff --git a/ipc/ipc_mojo_handle_attachment.h b/ipc/ipc_mojo_handle_attachment.h
index d6152769ef..94e40599ae 100644
--- a/ipc/ipc_mojo_handle_attachment.h
+++ b/ipc/ipc_mojo_handle_attachment.h
@@ -8,8 +8,8 @@
#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 "ipc/ipc_message_support_export.h"
#include "mojo/public/cpp/system/handle.h"
namespace IPC {
@@ -20,7 +20,8 @@ namespace internal {
// 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 {
+class IPC_MESSAGE_SUPPORT_EXPORT MojoHandleAttachment
+ : public MessageAttachment {
public:
explicit MojoHandleAttachment(mojo::ScopedHandle handle);
diff --git a/ipc/ipc_mojo_message_helper.cc b/ipc/ipc_mojo_message_helper.cc
index a87a2d694d..f3170950e1 100644
--- a/ipc/ipc_mojo_message_helper.cc
+++ b/ipc/ipc_mojo_message_helper.cc
@@ -33,7 +33,7 @@ bool MojoMessageHelper::ReadMessagePipeFrom(
MessageAttachment::Type type =
static_cast<MessageAttachment*>(attachment.get())->GetType();
if (type != MessageAttachment::Type::MOJO_HANDLE) {
- LOG(ERROR) << "Unxpected attachment type:" << type;
+ LOG(ERROR) << "Unxpected attachment type:" << static_cast<int>(type);
return false;
}
@@ -45,7 +45,6 @@ bool MojoMessageHelper::ReadMessagePipeFrom(
return true;
}
-MojoMessageHelper::MojoMessageHelper() {
-}
+MojoMessageHelper::MojoMessageHelper() = default;
} // namespace IPC
diff --git a/ipc/ipc_mojo_message_helper.h b/ipc/ipc_mojo_message_helper.h
index 4a71b5c96c..156c667829 100644
--- a/ipc/ipc_mojo_message_helper.h
+++ b/ipc/ipc_mojo_message_helper.h
@@ -5,14 +5,14 @@
#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 "ipc/ipc_message_support_export.h"
#include "mojo/public/cpp/system/message_pipe.h"
namespace IPC {
// Reads and writes |mojo::MessagePipe| from/to |Message|.
-class IPC_EXPORT MojoMessageHelper {
+class IPC_MESSAGE_SUPPORT_EXPORT MojoMessageHelper {
public:
static bool WriteMessagePipeTo(base::Pickle* message,
mojo::ScopedMessagePipeHandle handle);
diff --git a/ipc/ipc_mojo_param_traits.cc b/ipc/ipc_mojo_param_traits.cc
index 189af3511d..5eeb9f0fbb 100644
--- a/ipc/ipc_mojo_param_traits.cc
+++ b/ipc/ipc_mojo_param_traits.cc
@@ -5,17 +5,11 @@
#include "ipc/ipc_mojo_param_traits.h"
#include "ipc/ipc_message_utils.h"
+#include "ipc/ipc_mojo_handle_attachment.h"
#include "ipc/ipc_mojo_message_helper.h"
namespace IPC {
-void ParamTraits<mojo::MessagePipeHandle>::GetSize(base::PickleSizer* sizer,
- const param_type& p) {
- GetParamSize(sizer, p.is_valid());
- if (p.is_valid())
- sizer->AddAttachment();
-}
-
void ParamTraits<mojo::MessagePipeHandle>::Write(base::Pickle* m,
const param_type& p) {
WriteParam(m, p.is_valid());
@@ -47,4 +41,54 @@ void ParamTraits<mojo::MessagePipeHandle>::Log(const param_type& p,
l->append(")");
}
+void ParamTraits<mojo::DataPipeConsumerHandle>::Write(base::Pickle* m,
+ const param_type& p) {
+ WriteParam(m, p.is_valid());
+ if (!p.is_valid())
+ return;
+
+ m->WriteAttachment(new internal::MojoHandleAttachment(
+ mojo::ScopedHandle::From(mojo::ScopedDataPipeConsumerHandle(p))));
+}
+
+bool ParamTraits<mojo::DataPipeConsumerHandle>::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;
+
+ scoped_refptr<base::Pickle::Attachment> attachment;
+ if (!m->ReadAttachment(iter, &attachment)) {
+ DLOG(ERROR) << "Failed to read attachment for message pipe.";
+ return false;
+ }
+
+ MessageAttachment::Type type =
+ static_cast<MessageAttachment*>(attachment.get())->GetType();
+ if (type != MessageAttachment::Type::MOJO_HANDLE) {
+ DLOG(ERROR) << "Unexpected attachment type:" << static_cast<int>(type);
+ return false;
+ }
+
+ mojo::ScopedDataPipeConsumerHandle handle;
+ handle.reset(mojo::DataPipeConsumerHandle(
+ static_cast<internal::MojoHandleAttachment*>(attachment.get())
+ ->TakeHandle()
+ .release()
+ .value()));
+ DCHECK(handle.is_valid());
+ *r = handle.release();
+ return true;
+}
+
+void ParamTraits<mojo::DataPipeConsumerHandle>::Log(const param_type& p,
+ std::string* l) {
+ l->append("mojo::DataPipeConsumerHandle(");
+ LogParam(p.value(), l);
+ l->append(")");
+}
+
} // namespace IPC
diff --git a/ipc/ipc_mojo_param_traits.h b/ipc/ipc_mojo_param_traits.h
index 39be43e217..5907befd00 100644
--- a/ipc/ipc_mojo_param_traits.h
+++ b/ipc/ipc_mojo_param_traits.h
@@ -7,28 +7,37 @@
#include <string>
-#include "ipc/ipc_export.h"
+#include "base/component_export.h"
#include "ipc/ipc_param_traits.h"
+#include "mojo/public/cpp/system/data_pipe.h"
#include "mojo/public/cpp/system/message_pipe.h"
namespace base {
class Pickle;
class PickleIterator;
-class PickleSizer;
}
namespace IPC {
template <>
-struct IPC_EXPORT ParamTraits<mojo::MessagePipeHandle> {
+struct COMPONENT_EXPORT(IPC) ParamTraits<mojo::MessagePipeHandle> {
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);
};
+template <>
+struct COMPONENT_EXPORT(IPC) ParamTraits<mojo::DataPipeConsumerHandle> {
+ typedef mojo::DataPipeConsumerHandle param_type;
+ 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_mojo_perftest.cc b/ipc/ipc_mojo_perftest.cc
new file mode 100644
index 0000000000..c53a78f097
--- /dev/null
+++ b/ipc/ipc_mojo_perftest.cc
@@ -0,0 +1,864 @@
+// 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 <stddef.h>
+#include <memory>
+
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/process/process_metrics.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/perf_time_logger.h"
+#include "base/threading/thread.h"
+#include "build/build_config.h"
+#include "ipc/ipc_channel_mojo.h"
+#include "ipc/ipc_perftest_messages.h"
+#include "ipc/ipc_perftest_util.h"
+#include "ipc/ipc_sync_channel.h"
+#include "ipc/ipc_test.mojom.h"
+#include "ipc/ipc_test_base.h"
+#include "mojo/core/embedder/embedder.h"
+#include "mojo/core/test/mojo_test_base.h"
+#include "mojo/core/test/multiprocess_test_helper.h"
+#include "mojo/public/cpp/bindings/associated_binding_set.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace IPC {
+namespace {
+
+class PerformanceChannelListener : public Listener {
+ public:
+ explicit PerformanceChannelListener(const std::string& label)
+ : label_(label),
+ sender_(NULL),
+ msg_count_(0),
+ msg_size_(0),
+ sync_(false),
+ count_down_(0) {
+ VLOG(1) << "Server listener up";
+ }
+
+ ~PerformanceChannelListener() override { VLOG(1) << "Server listener down"; }
+
+ void Init(Sender* sender) {
+ DCHECK(!sender_);
+ sender_ = sender;
+ }
+
+ // Call this before running the message loop.
+ void SetTestParams(int msg_count, size_t msg_size, bool sync) {
+ DCHECK_EQ(0, count_down_);
+ msg_count_ = msg_count;
+ msg_size_ = msg_size;
+ sync_ = sync;
+ count_down_ = msg_count_;
+ payload_ = std::string(msg_size_, 'a');
+ }
+
+ bool OnMessageReceived(const Message& message) override {
+ CHECK(sender_);
+
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(PerformanceChannelListener, message)
+ IPC_MESSAGE_HANDLER(TestMsg_Hello, OnHello)
+ IPC_MESSAGE_HANDLER(TestMsg_Ping, OnPing)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+ }
+
+ void OnHello() {
+ // Start timing on hello.
+ DCHECK(!perf_logger_.get());
+ std::string test_name =
+ base::StringPrintf("IPC_%s_Perf_%dx_%u", label_.c_str(), msg_count_,
+ static_cast<unsigned>(msg_size_));
+ perf_logger_.reset(new base::PerfTimeLogger(test_name.c_str()));
+ if (sync_) {
+ for (; count_down_ > 0; --count_down_) {
+ std::string response;
+ sender_->Send(new TestMsg_SyncPing(payload_, &response));
+ DCHECK_EQ(response, payload_);
+ }
+ perf_logger_.reset();
+ base::RunLoop::QuitCurrentWhenIdleDeprecated();
+ } else {
+ SendPong();
+ }
+ }
+
+ void OnPing(const std::string& payload) {
+ // Include message deserialization in latency.
+ DCHECK_EQ(payload_.size(), payload.size());
+
+ CHECK(count_down_ > 0);
+ count_down_--;
+ if (count_down_ == 0) {
+ perf_logger_.reset(); // Stop the perf timer now.
+ base::RunLoop::QuitCurrentWhenIdleDeprecated();
+ return;
+ }
+
+ SendPong();
+ }
+
+ void SendPong() { sender_->Send(new TestMsg_Ping(payload_)); }
+
+ private:
+ std::string label_;
+ Sender* sender_;
+ int msg_count_;
+ size_t msg_size_;
+ bool sync_;
+
+ int count_down_;
+ std::string payload_;
+ std::unique_ptr<base::PerfTimeLogger> perf_logger_;
+};
+
+class PingPongTestParams {
+ public:
+ PingPongTestParams(size_t size, int count)
+ : message_size_(size), message_count_(count) {}
+
+ size_t message_size() const { return message_size_; }
+ int message_count() const { return message_count_; }
+
+ private:
+ size_t message_size_;
+ int message_count_;
+};
+
+class InterfacePassingTestParams {
+ public:
+ InterfacePassingTestParams(size_t rounds, size_t num_interfaces)
+ : rounds_(rounds), num_interfaces_(num_interfaces) {}
+
+ size_t rounds() const { return rounds_; }
+ size_t num_interfaces() const { return num_interfaces_; }
+
+ private:
+ size_t rounds_;
+ size_t num_interfaces_;
+};
+
+#ifdef NDEBUG
+const int kMultiplier = 100;
+#else
+ // Debug builds on Windows run these tests orders of magnitude more slowly.
+const int kMultiplier = 1;
+#endif
+
+std::vector<PingPongTestParams> GetDefaultTestParams() {
+ // Test several sizes. We use 12^N for message size, and limit the message
+ // count to keep the test duration reasonable.
+ std::vector<PingPongTestParams> list;
+ list.push_back(PingPongTestParams(12, 500 * kMultiplier));
+ list.push_back(PingPongTestParams(144, 500 * kMultiplier));
+ list.push_back(PingPongTestParams(1728, 500 * kMultiplier));
+ list.push_back(PingPongTestParams(20736, 120 * kMultiplier));
+ list.push_back(PingPongTestParams(248832, 10 * kMultiplier));
+ return list;
+}
+
+std::vector<InterfacePassingTestParams> GetDefaultInterfacePassingTestParams() {
+ std::vector<InterfacePassingTestParams> list;
+ list.push_back({500 * kMultiplier, 0});
+ list.push_back({500 * kMultiplier, 1});
+ list.push_back({500 * kMultiplier, 2});
+ list.push_back({500 * kMultiplier, 4});
+ list.push_back({500 * kMultiplier, 8});
+ return list;
+}
+
+class MojoChannelPerfTest : public IPCChannelMojoTestBase {
+ public:
+ MojoChannelPerfTest() = default;
+ ~MojoChannelPerfTest() override = default;
+
+ void RunTestChannelProxyPingPong() {
+ Init("MojoPerfTestClient");
+
+ // Set up IPC channel and start client.
+ PerformanceChannelListener listener("ChannelProxy");
+ auto channel_proxy = IPC::ChannelProxy::Create(
+ TakeHandle().release(), IPC::Channel::MODE_SERVER, &listener,
+ GetIOThreadTaskRunner(), base::ThreadTaskRunnerHandle::Get());
+ listener.Init(channel_proxy.get());
+
+ LockThreadAffinity thread_locker(kSharedCore);
+ std::vector<PingPongTestParams> params = GetDefaultTestParams();
+ for (size_t i = 0; i < params.size(); i++) {
+ listener.SetTestParams(params[i].message_count(),
+ params[i].message_size(), false);
+
+ // This initial message will kick-start the ping-pong of messages.
+ channel_proxy->Send(new TestMsg_Hello);
+
+ // Run message loop.
+ base::RunLoop().Run();
+ }
+
+ // Send quit message.
+ channel_proxy->Send(new TestMsg_Quit);
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ channel_proxy.reset();
+ }
+
+ void RunTestChannelProxySyncPing() {
+ Init("MojoPerfTestClient");
+
+ // Set up IPC channel and start client.
+ PerformanceChannelListener listener("ChannelProxy");
+ base::WaitableEvent shutdown_event(
+ base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ auto channel_proxy = IPC::SyncChannel::Create(
+ TakeHandle().release(), IPC::Channel::MODE_SERVER, &listener,
+ GetIOThreadTaskRunner(), base::ThreadTaskRunnerHandle::Get(), false,
+ &shutdown_event);
+ listener.Init(channel_proxy.get());
+
+ LockThreadAffinity thread_locker(kSharedCore);
+ std::vector<PingPongTestParams> params = GetDefaultTestParams();
+ for (size_t i = 0; i < params.size(); i++) {
+ listener.SetTestParams(params[i].message_count(),
+ params[i].message_size(), true);
+
+ // This initial message will kick-start the ping-pong of messages.
+ channel_proxy->Send(new TestMsg_Hello);
+
+ // Run message loop.
+ base::RunLoop().Run();
+ }
+
+ // Send quit message.
+ channel_proxy->Send(new TestMsg_Quit);
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ channel_proxy.reset();
+ }
+};
+
+TEST_F(MojoChannelPerfTest, ChannelProxyPingPong) {
+ RunTestChannelProxyPingPong();
+
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+}
+
+TEST_F(MojoChannelPerfTest, ChannelProxySyncPing) {
+ RunTestChannelProxySyncPing();
+
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+}
+
+MULTIPROCESS_TEST_MAIN(MojoPerfTestClientTestChildMain) {
+ MojoPerfTestClient client;
+ int rv = mojo::core::test::MultiprocessTestHelper::RunClientMain(
+ base::Bind(&MojoPerfTestClient::Run, base::Unretained(&client)),
+ true /* pass_pipe_ownership_to_main */);
+
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+
+ return rv;
+}
+
+class MojoInterfacePerfTest : public mojo::core::test::MojoTestBase {
+ public:
+ MojoInterfacePerfTest() : message_count_(0), count_down_(0) {}
+
+ protected:
+ void RunPingPongServer(MojoHandle mp, const std::string& label) {
+ label_ = label;
+
+ mojo::MessagePipeHandle mp_handle(mp);
+ mojo::ScopedMessagePipeHandle scoped_mp(mp_handle);
+ ping_receiver_.Bind(IPC::mojom::ReflectorPtrInfo(std::move(scoped_mp), 0u));
+
+ LockThreadAffinity thread_locker(kSharedCore);
+ std::vector<PingPongTestParams> params = GetDefaultTestParams();
+ for (size_t i = 0; i < params.size(); i++) {
+ ping_receiver_->Ping("hello", base::Bind(&MojoInterfacePerfTest::OnPong,
+ base::Unretained(this)));
+ message_count_ = count_down_ = params[i].message_count();
+ payload_ = std::string(params[i].message_size(), 'a');
+
+ base::RunLoop().Run();
+ }
+
+ ping_receiver_->Quit();
+
+ ignore_result(ping_receiver_.PassInterface().PassHandle().release());
+ }
+
+ void OnPong(const std::string& value) {
+ if (value == "hello") {
+ DCHECK(!perf_logger_.get());
+ std::string test_name =
+ base::StringPrintf("IPC_%s_Perf_%dx_%zu", label_.c_str(),
+ message_count_, payload_.size());
+ perf_logger_.reset(new base::PerfTimeLogger(test_name.c_str()));
+ } else {
+ DCHECK_EQ(payload_.size(), value.size());
+
+ CHECK(count_down_ > 0);
+ count_down_--;
+ if (count_down_ == 0) {
+ perf_logger_.reset();
+ base::RunLoop::QuitCurrentWhenIdleDeprecated();
+ return;
+ }
+ }
+
+ if (sync_) {
+ for (int i = 0; i < count_down_; ++i) {
+ std::string response;
+ ping_receiver_->SyncPing(payload_, &response);
+ DCHECK_EQ(response, payload_);
+ }
+ perf_logger_.reset();
+ base::RunLoop::QuitCurrentWhenIdleDeprecated();
+ } else {
+ ping_receiver_->Ping(payload_, base::Bind(&MojoInterfacePerfTest::OnPong,
+ base::Unretained(this)));
+ }
+ }
+
+ static int RunPingPongClient(MojoHandle mp) {
+ mojo::MessagePipeHandle mp_handle(mp);
+ mojo::ScopedMessagePipeHandle scoped_mp(mp_handle);
+
+ LockThreadAffinity thread_locker(kSharedCore);
+ // In single process mode, this is running in a task and by default other
+ // tasks (in particular, the binding) won't run. To keep the single process
+ // and multi-process code paths the same, enable nestable tasks.
+ base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
+ ReflectorImpl impl(std::move(scoped_mp), run_loop.QuitWhenIdleClosure());
+ run_loop.Run();
+ return 0;
+ }
+
+ bool sync_ = false;
+
+ private:
+ int message_count_;
+ int count_down_;
+ std::string label_;
+ std::string payload_;
+ IPC::mojom::ReflectorPtr ping_receiver_;
+ std::unique_ptr<base::PerfTimeLogger> perf_logger_;
+
+ DISALLOW_COPY_AND_ASSIGN(MojoInterfacePerfTest);
+};
+
+class InterfacePassingTestDriverImpl : public mojom::InterfacePassingTestDriver,
+ public mojom::PingReceiver {
+ public:
+ InterfacePassingTestDriverImpl(mojo::ScopedMessagePipeHandle handle,
+ const base::Closure& quit_closure)
+ : binding_(this,
+ mojom::InterfacePassingTestDriverRequest(std::move(handle))),
+ quit_closure_(quit_closure) {}
+ ~InterfacePassingTestDriverImpl() override {
+ ignore_result(binding_.Unbind().PassMessagePipe().release());
+ }
+
+ private:
+ // mojom::InterfacePassingTestDriver implementation:
+ void Init(InitCallback callback) override { std::move(callback).Run(); }
+
+ void GetPingReceiver(std::vector<mojom::PingReceiverRequest> requests,
+ GetPingReceiverCallback callback) override {
+ for (auto& request : requests)
+ ping_receiver_bindings_.AddBinding(this, std::move(request));
+ ping_receiver_bindings_.CloseAllBindings();
+ std::move(callback).Run();
+ }
+
+ void GetAssociatedPingReceiver(
+ std::vector<mojom::PingReceiverAssociatedRequest> requests,
+ GetAssociatedPingReceiverCallback callback) override {
+ for (auto& request : requests)
+ ping_receiver_associated_bindings_.AddBinding(this, std::move(request));
+ ping_receiver_associated_bindings_.CloseAllBindings();
+ std::move(callback).Run();
+ }
+
+ void Quit() override {
+ if (quit_closure_)
+ quit_closure_.Run();
+ }
+
+ // mojom::PingReceiver implementation:
+ void Ping(PingCallback callback) override { std::move(callback).Run(); }
+
+ mojo::BindingSet<mojom::PingReceiver> ping_receiver_bindings_;
+ mojo::AssociatedBindingSet<mojom::PingReceiver>
+ ping_receiver_associated_bindings_;
+ mojo::Binding<mojom::InterfacePassingTestDriver> binding_;
+
+ base::Closure quit_closure_;
+};
+
+class MojoInterfacePassingPerfTest : public mojo::core::test::MojoTestBase {
+ public:
+ MojoInterfacePassingPerfTest() = default;
+
+ protected:
+ void RunInterfacePassingServer(MojoHandle mp,
+ const std::string& label,
+ bool associated) {
+ label_ = label;
+ associated_ = associated;
+
+ mojo::MessagePipeHandle mp_handle(mp);
+ mojo::ScopedMessagePipeHandle scoped_mp(mp_handle);
+ driver_ptr_.Bind(
+ mojom::InterfacePassingTestDriverPtrInfo(std::move(scoped_mp), 0u));
+
+ auto params = GetDefaultInterfacePassingTestParams();
+
+ LockThreadAffinity thread_locker(kSharedCore);
+ for (size_t i = 0; i < params.size(); ++i) {
+ driver_ptr_->Init(
+ base::Bind(&MojoInterfacePassingPerfTest::OnInitCallback,
+ base::Unretained(this)));
+ rounds_ = count_down_ = params[i].rounds();
+ num_interfaces_ = params[i].num_interfaces();
+
+ base::RunLoop run_loop;
+ quit_closure_ = run_loop.QuitWhenIdleClosure();
+ run_loop.Run();
+ }
+
+ driver_ptr_->Quit();
+
+ ignore_result(driver_ptr_.PassInterface().PassHandle().release());
+ }
+
+ void OnInitCallback() {
+ DCHECK(!perf_logger_.get());
+ std::string test_name = base::StringPrintf(
+ "IPC_%s_Perf_%zux_%zu", label_.c_str(), rounds_, num_interfaces_);
+ perf_logger_.reset(new base::PerfTimeLogger(test_name.c_str()));
+
+ DoNextRound();
+ }
+
+ void DoNextRound() {
+ if (associated_) {
+ std::vector<mojom::PingReceiverAssociatedPtr> associated_interfaces(
+ num_interfaces_);
+
+ std::vector<mojom::PingReceiverAssociatedRequest> requests(
+ num_interfaces_);
+ for (size_t i = 0; i < num_interfaces_; ++i) {
+ requests[i] = mojo::MakeRequest(&associated_interfaces[i]);
+ // Force the interface pointer to do full initialization.
+ associated_interfaces[i].get();
+ }
+
+ driver_ptr_->GetAssociatedPingReceiver(
+ std::move(requests),
+ base::Bind(&MojoInterfacePassingPerfTest::OnGetReceiverCallback,
+ base::Unretained(this)));
+ } else {
+ std::vector<mojom::PingReceiverPtr> interfaces(num_interfaces_);
+
+ std::vector<mojom::PingReceiverRequest> requests(num_interfaces_);
+ for (size_t i = 0; i < num_interfaces_; ++i) {
+ requests[i] = mojo::MakeRequest(&interfaces[i]);
+ // Force the interface pointer to do full initialization.
+ interfaces[i].get();
+ }
+
+ driver_ptr_->GetPingReceiver(
+ std::move(requests),
+ base::Bind(&MojoInterfacePassingPerfTest::OnGetReceiverCallback,
+ base::Unretained(this)));
+ }
+ }
+
+ void OnGetReceiverCallback() {
+ CHECK_GT(count_down_, 0u);
+ count_down_--;
+
+ if (count_down_ == 0) {
+ perf_logger_.reset();
+ quit_closure_.Run();
+ return;
+ }
+
+ DoNextRound();
+ }
+
+ static int RunInterfacePassingClient(MojoHandle mp) {
+ mojo::MessagePipeHandle mp_handle(mp);
+ mojo::ScopedMessagePipeHandle scoped_mp(mp_handle);
+
+ LockThreadAffinity thread_locker(kSharedCore);
+ // In single process mode, this is running in a task and by default other
+ // tasks (in particular, the binding) won't run. To keep the single process
+ // and multi-process code paths the same, enable nestable tasks.
+ base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
+ InterfacePassingTestDriverImpl impl(std::move(scoped_mp),
+ run_loop.QuitWhenIdleClosure());
+ run_loop.Run();
+ return 0;
+ }
+
+ private:
+ size_t rounds_ = 0;
+ size_t count_down_ = 0;
+ size_t num_interfaces_ = 0;
+ std::string label_;
+ bool associated_ = false;
+ std::unique_ptr<base::PerfTimeLogger> perf_logger_;
+
+ mojom::InterfacePassingTestDriverPtr driver_ptr_;
+
+ base::Closure quit_closure_;
+
+ DISALLOW_COPY_AND_ASSIGN(MojoInterfacePassingPerfTest);
+};
+
+DEFINE_TEST_CLIENT_WITH_PIPE(InterfacePassingClient,
+ MojoInterfacePassingPerfTest,
+ h) {
+ base::MessageLoop main_message_loop;
+ return RunInterfacePassingClient(h);
+}
+
+enum class InProcessMessageMode {
+ kSerialized,
+ kUnserialized,
+};
+
+template <class TestBase>
+class InProcessPerfTest
+ : public TestBase,
+ public testing::WithParamInterface<InProcessMessageMode> {
+ public:
+ InProcessPerfTest() {
+ switch (GetParam()) {
+ case InProcessMessageMode::kSerialized:
+ mojo::Connector::OverrideDefaultSerializationBehaviorForTesting(
+ mojo::Connector::OutgoingSerializationMode::kEager,
+ mojo::Connector::IncomingSerializationMode::kDispatchAsIs);
+ break;
+ case InProcessMessageMode::kUnserialized:
+ mojo::Connector::OverrideDefaultSerializationBehaviorForTesting(
+ mojo::Connector::OutgoingSerializationMode::kLazy,
+ mojo::Connector::IncomingSerializationMode::kDispatchAsIs);
+ break;
+ }
+ }
+};
+
+using MojoInProcessInterfacePerfTest = InProcessPerfTest<MojoInterfacePerfTest>;
+using MojoInProcessInterfacePassingPerfTest =
+ InProcessPerfTest<MojoInterfacePassingPerfTest>;
+
+DEFINE_TEST_CLIENT_WITH_PIPE(PingPongClient, MojoInterfacePerfTest, h) {
+ base::MessageLoop main_message_loop;
+ return RunPingPongClient(h);
+}
+
+// Similar to MojoChannelPerfTest above, but uses a Mojo interface instead of
+// raw IPC::Messages.
+TEST_F(MojoInterfacePerfTest, MultiprocessPingPong) {
+ RunTestClient("PingPongClient", [&](MojoHandle h) {
+ base::MessageLoop main_message_loop;
+ RunPingPongServer(h, "Multiprocess");
+ });
+}
+
+TEST_F(MojoInterfacePerfTest, MultiprocessSyncPing) {
+ sync_ = true;
+ RunTestClient("PingPongClient", [&](MojoHandle h) {
+ base::MessageLoop main_message_loop;
+ RunPingPongServer(h, "MultiprocessSync");
+ });
+}
+
+TEST_F(MojoInterfacePassingPerfTest, MultiprocessInterfacePassing) {
+ RunTestClient("InterfacePassingClient", [&](MojoHandle h) {
+ base::MessageLoop main_message_loop;
+ RunInterfacePassingServer(h, "InterfacePassing", false /* associated */);
+ });
+}
+
+TEST_F(MojoInterfacePassingPerfTest, MultiprocessAssociatedInterfacePassing) {
+ RunTestClient("InterfacePassingClient", [&](MojoHandle h) {
+ base::MessageLoop main_message_loop;
+ RunInterfacePassingServer(h, "AssociatedInterfacePassing",
+ true /* associated*/);
+ });
+}
+
+// A single process version of the above test.
+TEST_P(MojoInProcessInterfacePerfTest, MultiThreadPingPong) {
+ 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));
+
+ base::MessageLoop main_message_loop;
+ RunPingPongServer(server_handle, "SingleProcess");
+}
+
+TEST_P(MojoInProcessInterfacePerfTest, SingleThreadPingPong) {
+ MojoHandle server_handle, client_handle;
+ CreateMessagePipe(&server_handle, &client_handle);
+
+ base::MessageLoop main_message_loop;
+ mojo::MessagePipeHandle mp_handle(client_handle);
+ mojo::ScopedMessagePipeHandle scoped_mp(mp_handle);
+ LockThreadAffinity thread_locker(kSharedCore);
+ ReflectorImpl impl(std::move(scoped_mp), base::Closure());
+
+ RunPingPongServer(server_handle, "SingleProcess");
+}
+
+INSTANTIATE_TEST_CASE_P(,
+ MojoInProcessInterfacePerfTest,
+ testing::Values(InProcessMessageMode::kSerialized,
+ InProcessMessageMode::kUnserialized));
+
+TEST_P(MojoInProcessInterfacePassingPerfTest, MultiThreadInterfacePassing) {
+ MojoHandle server_handle, client_handle;
+ CreateMessagePipe(&server_handle, &client_handle);
+
+ base::Thread client_thread("InterfacePassingClient");
+ client_thread.Start();
+ client_thread.task_runner()->PostTask(
+ FROM_HERE, base::Bind(base::IgnoreResult(&RunInterfacePassingClient),
+ client_handle));
+
+ base::MessageLoop main_message_loop;
+ RunInterfacePassingServer(server_handle, "SingleProcess",
+ false /* associated */);
+}
+
+TEST_P(MojoInProcessInterfacePassingPerfTest,
+ MultiThreadAssociatedInterfacePassing) {
+ MojoHandle server_handle, client_handle;
+ CreateMessagePipe(&server_handle, &client_handle);
+
+ base::Thread client_thread("InterfacePassingClient");
+ client_thread.Start();
+ client_thread.task_runner()->PostTask(
+ FROM_HERE, base::Bind(base::IgnoreResult(&RunInterfacePassingClient),
+ client_handle));
+
+ base::MessageLoop main_message_loop;
+ RunInterfacePassingServer(server_handle, "SingleProcess",
+ true /* associated */);
+}
+
+TEST_P(MojoInProcessInterfacePassingPerfTest, SingleThreadInterfacePassing) {
+ MojoHandle server_handle, client_handle;
+ CreateMessagePipe(&server_handle, &client_handle);
+
+ base::MessageLoop main_message_loop;
+ mojo::MessagePipeHandle mp_handle(client_handle);
+ mojo::ScopedMessagePipeHandle scoped_mp(mp_handle);
+ LockThreadAffinity thread_locker(kSharedCore);
+ InterfacePassingTestDriverImpl impl(std::move(scoped_mp), base::Closure());
+
+ RunInterfacePassingServer(server_handle, "SingleProcess",
+ false /* associated */);
+}
+
+TEST_P(MojoInProcessInterfacePassingPerfTest,
+ SingleThreadAssociatedInterfacePassing) {
+ MojoHandle server_handle, client_handle;
+ CreateMessagePipe(&server_handle, &client_handle);
+
+ base::MessageLoop main_message_loop;
+ mojo::MessagePipeHandle mp_handle(client_handle);
+ mojo::ScopedMessagePipeHandle scoped_mp(mp_handle);
+ LockThreadAffinity thread_locker(kSharedCore);
+ InterfacePassingTestDriverImpl impl(std::move(scoped_mp), base::Closure());
+
+ RunInterfacePassingServer(server_handle, "SingleProcess",
+ true /* associated */);
+}
+
+INSTANTIATE_TEST_CASE_P(,
+ MojoInProcessInterfacePassingPerfTest,
+ testing::Values(InProcessMessageMode::kSerialized,
+ InProcessMessageMode::kUnserialized));
+
+class CallbackPerfTest : public testing::Test {
+ public:
+ CallbackPerfTest()
+ : client_thread_("PingPongClient"), message_count_(0), count_down_(0) {}
+
+ protected:
+ void RunMultiThreadPingPongServer() {
+ client_thread_.Start();
+
+ LockThreadAffinity thread_locker(kSharedCore);
+ std::vector<PingPongTestParams> params = GetDefaultTestParams();
+ for (size_t i = 0; i < params.size(); i++) {
+ std::string hello("hello");
+ client_thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&CallbackPerfTest::Ping, base::Unretained(this), hello));
+ message_count_ = count_down_ = params[i].message_count();
+ payload_ = std::string(params[i].message_size(), 'a');
+
+ base::RunLoop().Run();
+ }
+ }
+
+ void Ping(const std::string& value) {
+ main_message_loop_.task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&CallbackPerfTest::OnPong, base::Unretained(this), value));
+ }
+
+ void OnPong(const std::string& value) {
+ if (value == "hello") {
+ DCHECK(!perf_logger_.get());
+ std::string test_name =
+ base::StringPrintf("Callback_MultiProcess_Perf_%dx_%zu",
+ message_count_, payload_.size());
+ perf_logger_.reset(new base::PerfTimeLogger(test_name.c_str()));
+ } else {
+ DCHECK_EQ(payload_.size(), value.size());
+
+ CHECK(count_down_ > 0);
+ count_down_--;
+ if (count_down_ == 0) {
+ perf_logger_.reset();
+ base::RunLoop::QuitCurrentWhenIdleDeprecated();
+ return;
+ }
+ }
+
+ client_thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&CallbackPerfTest::Ping, base::Unretained(this), payload_));
+ }
+
+ void RunSingleThreadNoPostTaskPingPongServer() {
+ LockThreadAffinity thread_locker(kSharedCore);
+ std::vector<PingPongTestParams> params = GetDefaultTestParams();
+ base::Callback<void(const std::string&, int,
+ const base::Callback<void(const std::string&, int)>&)>
+ ping = base::Bind(&CallbackPerfTest::SingleThreadPingNoPostTask,
+ base::Unretained(this));
+ for (size_t i = 0; i < params.size(); i++) {
+ payload_ = std::string(params[i].message_size(), 'a');
+ std::string test_name =
+ base::StringPrintf("Callback_SingleThreadNoPostTask_Perf_%dx_%zu",
+ params[i].message_count(), payload_.size());
+ perf_logger_.reset(new base::PerfTimeLogger(test_name.c_str()));
+ for (int j = 0; j < params[i].message_count(); ++j) {
+ ping.Run(payload_, j,
+ base::Bind(&CallbackPerfTest::SingleThreadPongNoPostTask,
+ base::Unretained(this)));
+ }
+ perf_logger_.reset();
+ }
+ }
+
+ void SingleThreadPingNoPostTask(
+ const std::string& value,
+ int i,
+ const base::Callback<void(const std::string&, int)>& pong) {
+ pong.Run(value, i);
+ }
+
+ void SingleThreadPongNoPostTask(const std::string& value, int i) {}
+
+ void RunSingleThreadPostTaskPingPongServer() {
+ LockThreadAffinity thread_locker(kSharedCore);
+ std::vector<PingPongTestParams> params = GetDefaultTestParams();
+ for (size_t i = 0; i < params.size(); i++) {
+ std::string hello("hello");
+ base::MessageLoopCurrent::Get()->task_runner()->PostTask(
+ FROM_HERE, base::Bind(&CallbackPerfTest::SingleThreadPingPostTask,
+ base::Unretained(this), hello));
+ message_count_ = count_down_ = params[i].message_count();
+ payload_ = std::string(params[i].message_size(), 'a');
+
+ base::RunLoop().Run();
+ }
+ }
+
+ void SingleThreadPingPostTask(const std::string& value) {
+ base::MessageLoopCurrent::Get()->task_runner()->PostTask(
+ FROM_HERE, base::Bind(&CallbackPerfTest::SingleThreadPongPostTask,
+ base::Unretained(this), value));
+ }
+
+ void SingleThreadPongPostTask(const std::string& value) {
+ if (value == "hello") {
+ DCHECK(!perf_logger_.get());
+ std::string test_name =
+ base::StringPrintf("Callback_SingleThreadPostTask_Perf_%dx_%zu",
+ message_count_, payload_.size());
+ perf_logger_.reset(new base::PerfTimeLogger(test_name.c_str()));
+ } else {
+ DCHECK_EQ(payload_.size(), value.size());
+
+ CHECK(count_down_ > 0);
+ count_down_--;
+ if (count_down_ == 0) {
+ perf_logger_.reset();
+ base::RunLoop::QuitCurrentWhenIdleDeprecated();
+ return;
+ }
+ }
+
+ base::MessageLoopCurrent::Get()->task_runner()->PostTask(
+ FROM_HERE, base::Bind(&CallbackPerfTest::SingleThreadPingPostTask,
+ base::Unretained(this), payload_));
+ }
+
+ private:
+ base::Thread client_thread_;
+ base::MessageLoop main_message_loop_;
+ int message_count_;
+ int count_down_;
+ std::string payload_;
+ std::unique_ptr<base::PerfTimeLogger> perf_logger_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallbackPerfTest);
+};
+
+// Sends the same data as above using PostTask to a different thread instead of
+// IPCs for comparison.
+TEST_F(CallbackPerfTest, MultiThreadPingPong) {
+ RunMultiThreadPingPongServer();
+}
+
+// Sends the same data as above using PostTask to the same thread.
+TEST_F(CallbackPerfTest, SingleThreadPostTaskPingPong) {
+ RunSingleThreadPostTaskPingPongServer();
+}
+
+// Sends the same data as above without using PostTask to the same thread.
+TEST_F(CallbackPerfTest, SingleThreadNoPostTaskPingPong) {
+ RunSingleThreadNoPostTaskPingPongServer();
+}
+
+} // namespace
+} // namespace IPC
diff --git a/ipc/ipc_param_traits.h b/ipc/ipc_param_traits.h
index 45e975c30a..9aaeb5e506 100644
--- a/ipc/ipc_param_traits.h
+++ b/ipc/ipc_param_traits.h
@@ -9,8 +9,19 @@
// a data type is read, written and logged in the IPC system.
namespace IPC {
+namespace internal {
+
+template <typename T>
+struct AlwaysFalse {
+ static const bool value = false;
+};
+
+} // namespace internal
template <class P> struct ParamTraits {
+ static_assert(internal::AlwaysFalse<P>::value,
+ "Cannot find the IPC::ParamTraits specialization. Did you "
+ "forget to include the corresponding header file?");
};
template <class P>
diff --git a/ipc/ipc_perftest_messages.cc b/ipc/ipc_perftest_messages.cc
new file mode 100644
index 0000000000..4a84dae226
--- /dev/null
+++ b/ipc/ipc_perftest_messages.cc
@@ -0,0 +1,7 @@
+// 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.
+
+// Get basic type definitions.
+#define IPC_MESSAGE_IMPL
+#include "ipc/ipc_perftest_messages.h"
diff --git a/ipc/ipc_perftest_messages.h b/ipc/ipc_perftest_messages.h
new file mode 100644
index 0000000000..1dc0890574
--- /dev/null
+++ b/ipc/ipc_perftest_messages.h
@@ -0,0 +1,16 @@
+// 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.
+
+// Multiply-included message header, no traditional include guard.
+
+#include <string>
+
+#include "ipc/ipc_message_macros.h"
+
+#define IPC_MESSAGE_START TestMsgStart
+
+IPC_MESSAGE_CONTROL0(TestMsg_Hello)
+IPC_MESSAGE_CONTROL0(TestMsg_Quit)
+IPC_MESSAGE_CONTROL1(TestMsg_Ping, std::string)
+IPC_SYNC_MESSAGE_CONTROL1_1(TestMsg_SyncPing, std::string, std::string)
diff --git a/ipc/ipc_perftest_util.cc b/ipc/ipc_perftest_util.cc
new file mode 100644
index 0000000000..3d49189940
--- /dev/null
+++ b/ipc/ipc_perftest_util.cc
@@ -0,0 +1,145 @@
+// 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 "ipc/ipc_perftest_util.h"
+
+#include "base/logging.h"
+#include "base/run_loop.h"
+#include "ipc/ipc_channel_proxy.h"
+#include "ipc/ipc_perftest_messages.h"
+#include "mojo/core/embedder/embedder.h"
+#include "mojo/core/test/multiprocess_test_helper.h"
+
+namespace IPC {
+
+scoped_refptr<base::SingleThreadTaskRunner> GetIOThreadTaskRunner() {
+ scoped_refptr<base::TaskRunner> runner = mojo::core::GetIOTaskRunner();
+ return scoped_refptr<base::SingleThreadTaskRunner>(
+ static_cast<base::SingleThreadTaskRunner*>(runner.get()));
+}
+
+ChannelReflectorListener::ChannelReflectorListener() : channel_(NULL) {
+ VLOG(1) << "Client listener up";
+}
+
+ChannelReflectorListener::~ChannelReflectorListener() {
+ VLOG(1) << "Client listener down";
+}
+
+void ChannelReflectorListener::Init(Sender* channel,
+ const base::Closure& quit_closure) {
+ DCHECK(!channel_);
+ channel_ = channel;
+ quit_closure_ = quit_closure;
+}
+
+bool ChannelReflectorListener::OnMessageReceived(const Message& message) {
+ CHECK(channel_);
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(ChannelReflectorListener, message)
+ IPC_MESSAGE_HANDLER(TestMsg_Hello, OnHello)
+ IPC_MESSAGE_HANDLER(TestMsg_Ping, OnPing)
+ IPC_MESSAGE_HANDLER(TestMsg_SyncPing, OnSyncPing)
+ IPC_MESSAGE_HANDLER(TestMsg_Quit, OnQuit)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void ChannelReflectorListener::OnHello() {
+ channel_->Send(new TestMsg_Hello);
+}
+
+void ChannelReflectorListener::OnPing(const std::string& payload) {
+ channel_->Send(new TestMsg_Ping(payload));
+}
+
+void ChannelReflectorListener::OnSyncPing(const std::string& payload,
+ std::string* response) {
+ *response = payload;
+}
+
+void ChannelReflectorListener::OnQuit() {
+ quit_closure_.Run();
+}
+
+void ChannelReflectorListener::Send(IPC::Message* message) {
+ channel_->Send(message);
+}
+
+LockThreadAffinity::LockThreadAffinity(int cpu_number)
+ : affinity_set_ok_(false) {
+#if defined(OS_WIN)
+ const DWORD_PTR thread_mask = static_cast<DWORD_PTR>(1) << cpu_number;
+ old_affinity_ = SetThreadAffinityMask(GetCurrentThread(), thread_mask);
+ affinity_set_ok_ = old_affinity_ != 0;
+#elif defined(OS_LINUX)
+ cpu_set_t cpuset;
+ CPU_ZERO(&cpuset);
+ CPU_SET(cpu_number, &cpuset);
+ auto get_result = sched_getaffinity(0, sizeof(old_cpuset_), &old_cpuset_);
+ DCHECK_EQ(0, get_result);
+ auto set_result = sched_setaffinity(0, sizeof(cpuset), &cpuset);
+ // Check for get_result failure, even though it should always succeed.
+ affinity_set_ok_ = (set_result == 0) && (get_result == 0);
+#endif
+ if (!affinity_set_ok_)
+ LOG(WARNING) << "Failed to set thread affinity to CPU " << cpu_number;
+}
+
+LockThreadAffinity::~LockThreadAffinity() {
+ if (!affinity_set_ok_)
+ return;
+#if defined(OS_WIN)
+ auto set_result = SetThreadAffinityMask(GetCurrentThread(), old_affinity_);
+ DCHECK_NE(0u, set_result);
+#elif defined(OS_LINUX)
+ auto set_result = sched_setaffinity(0, sizeof(old_cpuset_), &old_cpuset_);
+ DCHECK_EQ(0, set_result);
+#endif
+}
+
+MojoPerfTestClient::MojoPerfTestClient()
+ : listener_(new ChannelReflectorListener()) {
+ mojo::core::test::MultiprocessTestHelper::ChildSetup();
+}
+
+MojoPerfTestClient::~MojoPerfTestClient() = default;
+
+int MojoPerfTestClient::Run(MojoHandle handle) {
+ handle_ = mojo::MakeScopedHandle(mojo::MessagePipeHandle(handle));
+ LockThreadAffinity thread_locker(kSharedCore);
+
+ base::RunLoop run_loop;
+ std::unique_ptr<ChannelProxy> channel = IPC::ChannelProxy::Create(
+ handle_.release(), Channel::MODE_CLIENT, listener_.get(),
+ GetIOThreadTaskRunner(), base::ThreadTaskRunnerHandle::Get());
+ listener_->Init(channel.get(), run_loop.QuitWhenIdleClosure());
+ run_loop.Run();
+ return 0;
+}
+
+ReflectorImpl::ReflectorImpl(mojo::ScopedMessagePipeHandle handle,
+ const base::Closure& quit_closure)
+ : quit_closure_(quit_closure),
+ binding_(this, IPC::mojom::ReflectorRequest(std::move(handle))) {}
+
+ReflectorImpl::~ReflectorImpl() {
+ ignore_result(binding_.Unbind().PassMessagePipe().release());
+}
+
+void ReflectorImpl::Ping(const std::string& value, PingCallback callback) {
+ std::move(callback).Run(value);
+}
+
+void ReflectorImpl::SyncPing(const std::string& value, PingCallback callback) {
+ std::move(callback).Run(value);
+}
+
+void ReflectorImpl::Quit() {
+ if (quit_closure_)
+ quit_closure_.Run();
+}
+
+} // namespace IPC
diff --git a/ipc/ipc_perftest_util.h b/ipc/ipc_perftest_util.h
new file mode 100644
index 0000000000..3c9ceeb2bf
--- /dev/null
+++ b/ipc/ipc_perftest_util.h
@@ -0,0 +1,119 @@
+// 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 IPC_IPC_PERFTEST_UTIL_H_
+#define IPC_IPC_PERFTEST_UTIL_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/process/process_metrics.h"
+#include "base/single_thread_task_runner.h"
+#include "build/build_config.h"
+#include "ipc/ipc_channel.h"
+#include "ipc/ipc_listener.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_sender.h"
+#include "ipc/ipc_test.mojom.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace IPC {
+
+scoped_refptr<base::SingleThreadTaskRunner> GetIOThreadTaskRunner();
+
+// This channel listener just replies to all messages with the exact same
+// message. It assumes each message has one string parameter. When the string
+// "quit" is sent, it will exit.
+class ChannelReflectorListener : public Listener {
+ public:
+ ChannelReflectorListener();
+
+ ~ChannelReflectorListener() override;
+
+ void Init(Sender* channel, const base::Closure& quit_closure);
+
+ bool OnMessageReceived(const Message& message) override;
+
+ void OnHello();
+
+ void OnPing(const std::string& payload);
+
+ void OnSyncPing(const std::string& payload, std::string* response);
+
+ void OnQuit();
+
+ void Send(IPC::Message* message);
+
+ private:
+ Sender* channel_;
+ base::Closure quit_closure_;
+};
+
+// This class locks the current thread to a particular CPU core. This is
+// important because otherwise the different threads and processes of these
+// tests end up on different CPU cores which means that all of the cores are
+// lightly loaded so the OS (Windows and Linux) fails to ramp up the CPU
+// frequency, leading to unpredictable and often poor performance.
+class LockThreadAffinity {
+ public:
+ explicit LockThreadAffinity(int cpu_number);
+
+ ~LockThreadAffinity();
+
+ private:
+ bool affinity_set_ok_;
+#if defined(OS_WIN)
+ DWORD_PTR old_affinity_;
+#elif defined(OS_LINUX)
+ cpu_set_t old_cpuset_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(LockThreadAffinity);
+};
+
+// Avoid core 0 due to conflicts with Intel's Power Gadget.
+// Setting thread affinity will fail harmlessly on single/dual core machines.
+const int kSharedCore = 2;
+
+class MojoPerfTestClient {
+ public:
+ MojoPerfTestClient();
+
+ ~MojoPerfTestClient();
+
+ int Run(MojoHandle handle);
+
+ private:
+ base::MessageLoop main_message_loop_;
+ std::unique_ptr<ChannelReflectorListener> listener_;
+ std::unique_ptr<Channel> channel_;
+ mojo::ScopedMessagePipeHandle handle_;
+};
+
+class ReflectorImpl : public IPC::mojom::Reflector {
+ public:
+ explicit ReflectorImpl(mojo::ScopedMessagePipeHandle handle,
+ const base::Closure& quit_closure);
+
+ ~ReflectorImpl() override;
+
+ private:
+ // IPC::mojom::Reflector:
+ void Ping(const std::string& value, PingCallback callback) override;
+
+ void SyncPing(const std::string& value, PingCallback callback) override;
+
+ void Quit() override;
+
+ base::Closure quit_closure_;
+ mojo::Binding<IPC::mojom::Reflector> binding_;
+};
+
+} // namespace IPC
+
+#endif // IPC_IPC_PERFTEST_UTIL_H_
diff --git a/ipc/ipc_platform_file.cc b/ipc/ipc_platform_file.cc
new file mode 100644
index 0000000000..3a8198e38c
--- /dev/null
+++ b/ipc/ipc_platform_file.cc
@@ -0,0 +1,76 @@
+// 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 "build/build_config.h"
+#include "ipc/ipc_platform_file.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <unistd.h>
+
+#include "base/posix/eintr_wrapper.h"
+#endif
+
+namespace IPC {
+
+#if defined(OS_WIN)
+PlatformFileForTransit::PlatformFileForTransit() : handle_(nullptr) {}
+
+PlatformFileForTransit::PlatformFileForTransit(HANDLE handle)
+ : handle_(handle) {}
+
+bool PlatformFileForTransit::operator==(
+ const PlatformFileForTransit& platform_file) const {
+ return handle_ == platform_file.handle_;
+}
+
+bool PlatformFileForTransit::operator!=(
+ const PlatformFileForTransit& platform_file) const {
+ return !(*this == platform_file);
+}
+
+HANDLE PlatformFileForTransit::GetHandle() const {
+ return handle_;
+}
+
+bool PlatformFileForTransit::IsValid() const {
+ return handle_ != nullptr;
+}
+
+#endif // defined(OS_WIN)
+
+PlatformFileForTransit GetPlatformFileForTransit(base::PlatformFile handle,
+ bool close_source_handle) {
+#if defined(OS_WIN)
+ HANDLE raw_handle = INVALID_HANDLE_VALUE;
+ DWORD options = DUPLICATE_SAME_ACCESS;
+ if (close_source_handle)
+ options |= DUPLICATE_CLOSE_SOURCE;
+ if (handle == INVALID_HANDLE_VALUE ||
+ !::DuplicateHandle(::GetCurrentProcess(), handle, ::GetCurrentProcess(),
+ &raw_handle, 0, FALSE, options)) {
+ return IPC::InvalidPlatformFileForTransit();
+ }
+
+ return IPC::PlatformFileForTransit(raw_handle);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ // If asked to close the source, we can simply re-use the source fd instead of
+ // dup()ing and close()ing.
+ // When we're not closing the source, we need to duplicate the handle and take
+ // ownership of that. The reason is that this function is often used to
+ // generate IPC messages, and the handle must remain valid until it's sent to
+ // the other process from the I/O thread. Without the dup, calling code might
+ // close the source handle before the message is sent, creating a race
+ // condition.
+ int fd = close_source_handle ? handle : HANDLE_EINTR(::dup(handle));
+ return base::FileDescriptor(fd, true);
+#endif
+}
+
+PlatformFileForTransit TakePlatformFileForTransit(base::File file) {
+ return GetPlatformFileForTransit(file.TakePlatformFile(), true);
+}
+
+} // namespace IPC
diff --git a/ipc/ipc_platform_file.h b/ipc/ipc_platform_file.h
new file mode 100644
index 0000000000..e174ee83d8
--- /dev/null
+++ b/ipc/ipc_platform_file.h
@@ -0,0 +1,86 @@
+// 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_PLATFORM_FILE_H_
+#define IPC_IPC_PLATFORM_FILE_H_
+
+#include "base/files/file.h"
+#include "base/process/process.h"
+#include "build/build_config.h"
+#include "ipc/ipc_message_support_export.h"
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include "base/file_descriptor_posix.h"
+#endif
+
+namespace IPC {
+
+#if defined(OS_WIN)
+class IPC_MESSAGE_SUPPORT_EXPORT PlatformFileForTransit {
+ public:
+ // Creates an invalid platform file.
+ PlatformFileForTransit();
+
+ // Creates a platform file that takes unofficial ownership of |handle|. Note
+ // that ownership is not handled by a Scoped* class due to usage patterns of
+ // this class and its POSIX counterpart [base::FileDescriptor]. When this
+ // class is used as an input to an IPC message, the IPC subsystem will close
+ // |handle|. When this class is used as the output from an IPC message, the
+ // receiver is expected to take ownership of |handle|.
+ explicit PlatformFileForTransit(HANDLE handle);
+
+ // Comparison operators.
+ bool operator==(const PlatformFileForTransit& platform_file) const;
+ bool operator!=(const PlatformFileForTransit& platform_file) const;
+
+ HANDLE GetHandle() const;
+ bool IsValid() const;
+
+ private:
+ HANDLE handle_;
+};
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+typedef base::FileDescriptor PlatformFileForTransit;
+#endif
+
+inline PlatformFileForTransit InvalidPlatformFileForTransit() {
+#if defined(OS_WIN)
+ return PlatformFileForTransit();
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ return base::FileDescriptor();
+#endif
+}
+
+inline base::PlatformFile PlatformFileForTransitToPlatformFile(
+ const PlatformFileForTransit& transit) {
+#if defined(OS_WIN)
+ return transit.GetHandle();
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ return transit.fd;
+#endif
+}
+
+inline base::File PlatformFileForTransitToFile(
+ const PlatformFileForTransit& transit) {
+#if defined(OS_WIN)
+ return base::File(transit.GetHandle());
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ return base::File(transit.fd);
+#endif
+}
+
+// Creates a new handle that can be passed through IPC. The result must be
+// passed to the IPC layer as part of a message, or else it will leak.
+IPC_MESSAGE_SUPPORT_EXPORT PlatformFileForTransit
+GetPlatformFileForTransit(base::PlatformFile file, bool close_source_handle);
+
+// Creates a new handle that can be passed through IPC. The result must be
+// passed to the IPC layer as part of a message, or else it will leak.
+// Note that this function takes ownership of |file|.
+IPC_MESSAGE_SUPPORT_EXPORT PlatformFileForTransit
+TakePlatformFileForTransit(base::File file);
+
+} // namespace IPC
+
+#endif // IPC_IPC_PLATFORM_FILE_H_
diff --git a/ipc/ipc_platform_file_attachment_posix.cc b/ipc/ipc_platform_file_attachment_posix.cc
index 7111cfa0bb..bd6e7b5cac 100644
--- a/ipc/ipc_platform_file_attachment_posix.cc
+++ b/ipc/ipc_platform_file_attachment_posix.cc
@@ -16,8 +16,7 @@ PlatformFileAttachment::PlatformFileAttachment(base::PlatformFile file)
PlatformFileAttachment::PlatformFileAttachment(base::ScopedFD file)
: file_(file.get()), owning_(std::move(file)) {}
-PlatformFileAttachment::~PlatformFileAttachment() {
-}
+PlatformFileAttachment::~PlatformFileAttachment() = default;
MessageAttachment::Type PlatformFileAttachment::GetType() const {
return Type::PLATFORM_FILE;
diff --git a/ipc/ipc_platform_file_attachment_posix.h b/ipc/ipc_platform_file_attachment_posix.h
index 9b0790093b..945c345574 100644
--- a/ipc/ipc_platform_file_attachment_posix.h
+++ b/ipc/ipc_platform_file_attachment_posix.h
@@ -5,8 +5,8 @@
#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"
+#include "ipc/ipc_message_support_export.h"
namespace IPC {
namespace internal {
@@ -15,7 +15,8 @@ namespace internal {
// 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 {
+class IPC_MESSAGE_SUPPORT_EXPORT PlatformFileAttachment
+ : public MessageAttachment {
public:
// Non-owning constructor
explicit PlatformFileAttachment(base::PlatformFile file);
diff --git a/ipc/ipc_security_test_util.cc b/ipc/ipc_security_test_util.cc
new file mode 100644
index 0000000000..4ae5a06096
--- /dev/null
+++ b/ipc/ipc_security_test_util.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 "ipc/ipc_security_test_util.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/run_loop.h"
+#include "ipc/ipc_channel_proxy.h"
+
+namespace IPC {
+
+void IpcSecurityTestUtil::PwnMessageReceived(ChannelProxy* channel,
+ const IPC::Message& message) {
+ base::RunLoop run_loop;
+ base::Closure inject_message = base::Bind(
+ base::IgnoreResult(&IPC::ChannelProxy::Context::OnMessageReceived),
+ channel->context(), message);
+ channel->context()->ipc_task_runner()->PostTaskAndReply(
+ FROM_HERE, inject_message, run_loop.QuitClosure());
+ run_loop.Run();
+}
+
+} // namespace IPC
diff --git a/ipc/ipc_security_test_util.h b/ipc/ipc_security_test_util.h
new file mode 100644
index 0000000000..f0840bc020
--- /dev/null
+++ b/ipc/ipc_security_test_util.h
@@ -0,0 +1,40 @@
+// 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 IPC_IPC_SECURITY_TEST_UTIL_H_
+#define IPC_IPC_SECURITY_TEST_UTIL_H_
+
+#include "base/macros.h"
+
+namespace IPC {
+
+class ChannelProxy;
+class Message;
+
+class IpcSecurityTestUtil {
+ public:
+ // Enables testing of security exploit scenarios where a compromised child
+ // process can send a malicious message of an arbitrary type.
+ //
+ // This function will post the message to the IPC channel's thread, where it
+ // is offered to the channel's listeners. Afterwards, a reply task is posted
+ // back to the current thread. This function blocks until the reply task is
+ // received. For messages forwarded back to the current thread, we won't
+ // return until after the message has been handled here.
+ //
+ // Use this only for testing security bugs in a browsertest; other uses are
+ // likely perilous. Unit tests should be using IPC::TestSink which has an
+ // OnMessageReceived method you can call directly. Non-security browsertests
+ // should just exercise the child process's normal codepaths to send messages.
+ static void PwnMessageReceived(ChannelProxy* channel, const Message& message);
+
+ private:
+ IpcSecurityTestUtil(); // Not instantiable.
+
+ DISALLOW_COPY_AND_ASSIGN(IpcSecurityTestUtil);
+};
+
+} // namespace IPC
+
+#endif // IPC_IPC_SECURITY_TEST_UTIL_H_
diff --git a/ipc/ipc_send_fds_test.cc b/ipc/ipc_send_fds_test.cc
new file mode 100644
index 0000000000..6ff5e24ef1
--- /dev/null
+++ b/ipc/ipc_send_fds_test.cc
@@ -0,0 +1,218 @@
+// 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 "build/build_config.h"
+
+#if defined(OS_POSIX)
+#if defined(OS_MACOSX)
+extern "C" {
+#include <sandbox.h>
+};
+#endif
+#include <fcntl.h>
+#include <stddef.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <memory>
+#include <queue>
+
+#include "base/callback.h"
+#include "base/file_descriptor_posix.h"
+#include "base/location.h"
+#include "base/pickle.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "ipc/ipc_message_attachment_set.h"
+#include "ipc/ipc_message_utils.h"
+#include "ipc/ipc_test_base.h"
+
+#if defined(OS_POSIX)
+#include "base/macros.h"
+#endif
+
+#if defined(OS_MACOSX)
+#include "sandbox/mac/seatbelt.h"
+#endif
+
+namespace {
+
+const unsigned kNumFDsToSend = 7; // per message
+const unsigned kNumMessages = 20;
+const char* kDevZeroPath = "/dev/zero";
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+static_assert(kNumFDsToSend ==
+ IPC::MessageAttachmentSet::kMaxDescriptorsPerMessage,
+ "The number of FDs to send must be kMaxDescriptorsPerMessage.");
+#endif
+
+class MyChannelDescriptorListenerBase : public IPC::Listener {
+ public:
+ bool OnMessageReceived(const IPC::Message& message) override {
+ base::PickleIterator iter(message);
+ base::FileDescriptor descriptor;
+ while (IPC::ParamTraits<base::FileDescriptor>::Read(
+ &message, &iter, &descriptor)) {
+ HandleFD(descriptor.fd);
+ }
+ return true;
+ }
+
+ protected:
+ virtual void HandleFD(int fd) = 0;
+};
+
+class MyChannelDescriptorListener : public MyChannelDescriptorListenerBase {
+ public:
+ explicit MyChannelDescriptorListener(ino_t expected_inode_num)
+ : MyChannelDescriptorListenerBase(),
+ expected_inode_num_(expected_inode_num),
+ num_fds_received_(0) {
+ }
+
+ unsigned num_fds_received() const {
+ return num_fds_received_;
+ }
+
+ void OnChannelError() override {
+ base::RunLoop::QuitCurrentWhenIdleDeprecated();
+ }
+
+ protected:
+ void HandleFD(int fd) override {
+ ASSERT_GE(fd, 0);
+ // Check that we can read from the FD.
+ char buf;
+ ssize_t amt_read = read(fd, &buf, 1);
+ ASSERT_EQ(amt_read, 1);
+ ASSERT_EQ(buf, 0); // /dev/zero always reads 0 bytes.
+
+ struct stat st;
+ ASSERT_EQ(fstat(fd, &st), 0);
+
+ ASSERT_EQ(close(fd), 0);
+
+ // Compare inode numbers to check that the file sent over the wire is
+ // actually the one expected.
+ ASSERT_EQ(expected_inode_num_, st.st_ino);
+
+ ++num_fds_received_;
+ if (num_fds_received_ == kNumFDsToSend * kNumMessages)
+ base::RunLoop::QuitCurrentWhenIdleDeprecated();
+ }
+
+ private:
+ ino_t expected_inode_num_;
+ unsigned num_fds_received_;
+};
+
+class IPCSendFdsTest : public IPCChannelMojoTestBase {
+ protected:
+ void RunServer() {
+ // Set up IPC channel and start client.
+ MyChannelDescriptorListener listener(-1);
+ CreateChannel(&listener);
+ ASSERT_TRUE(ConnectChannel());
+
+ for (unsigned i = 0; i < kNumMessages; ++i) {
+ IPC::Message* message =
+ new IPC::Message(0, 3, IPC::Message::PRIORITY_NORMAL);
+ for (unsigned j = 0; j < kNumFDsToSend; ++j) {
+ const int fd = open(kDevZeroPath, O_RDONLY);
+ ASSERT_GE(fd, 0);
+ base::FileDescriptor descriptor(fd, true);
+ IPC::ParamTraits<base::FileDescriptor>::Write(message, descriptor);
+ }
+ ASSERT_TRUE(sender()->Send(message));
+ }
+
+ // Run message loop.
+ base::RunLoop().Run();
+
+ // Close the channel so the client's OnChannelError() gets fired.
+ channel()->Close();
+
+ EXPECT_TRUE(WaitForClientShutdown());
+ DestroyChannel();
+ }
+};
+
+TEST_F(IPCSendFdsTest, DescriptorTest) {
+ Init("SendFdsClient");
+ RunServer();
+}
+
+class SendFdsTestClientFixture : public IpcChannelMojoTestClient {
+ protected:
+ void SendFdsClientCommon(const std::string& test_client_name,
+ ino_t expected_inode_num) {
+ MyChannelDescriptorListener listener(expected_inode_num);
+
+ // Set up IPC channel.
+ Connect(&listener);
+
+ // Run message loop.
+ base::RunLoop().Run();
+
+ // Verify that the message loop was exited due to getting the correct number
+ // of descriptors, and not because of the channel closing unexpectedly.
+ EXPECT_EQ(kNumFDsToSend * kNumMessages, listener.num_fds_received());
+
+ Close();
+ }
+};
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT_WITH_CUSTOM_FIXTURE(
+ SendFdsClient,
+ SendFdsTestClientFixture) {
+ struct stat st;
+ int fd = open(kDevZeroPath, O_RDONLY);
+ fstat(fd, &st);
+ EXPECT_GE(IGNORE_EINTR(close(fd)), 0);
+ SendFdsClientCommon("SendFdsClient", st.st_ino);
+}
+
+#if defined(OS_MACOSX)
+// Test that FDs are correctly sent to a sandboxed process.
+// TODO(port): Make this test cross-platform.
+TEST_F(IPCSendFdsTest, DescriptorTestSandboxed) {
+ Init("SendFdsSandboxedClient");
+ RunServer();
+}
+
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT_WITH_CUSTOM_FIXTURE(
+ SendFdsSandboxedClient,
+ SendFdsTestClientFixture) {
+ struct stat st;
+ const int fd = open(kDevZeroPath, O_RDONLY);
+ fstat(fd, &st);
+ ASSERT_LE(0, IGNORE_EINTR(close(fd)));
+
+ // Enable the sandbox.
+ char* error_buff = NULL;
+ int error = sandbox::Seatbelt::Init(
+ sandbox::Seatbelt::kProfilePureComputation, SANDBOX_NAMED, &error_buff);
+ ASSERT_EQ(0, error);
+ ASSERT_FALSE(error_buff);
+
+ sandbox::Seatbelt::FreeError(error_buff);
+
+ // Make sure sandbox is really enabled.
+ ASSERT_EQ(-1, open(kDevZeroPath, O_RDONLY))
+ << "Sandbox wasn't properly enabled";
+
+ // See if we can receive a file descriptor.
+ SendFdsClientCommon("SendFdsSandboxedClient", st.st_ino);
+}
+#endif // defined(OS_MACOSX)
+
+} // namespace
+
+#endif // defined(OS_POSIX)
diff --git a/ipc/ipc_sender.h b/ipc/ipc_sender.h
new file mode 100644
index 0000000000..98872f8e51
--- /dev/null
+++ b/ipc/ipc_sender.h
@@ -0,0 +1,28 @@
+// 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_SENDER_H_
+#define IPC_IPC_SENDER_H_
+
+#include "base/component_export.h"
+
+namespace IPC {
+
+class Message;
+
+class COMPONENT_EXPORT(IPC) Sender {
+ public:
+ // Sends the given IPC message. The implementor takes ownership of the
+ // given Message regardless of whether or not this method succeeds. This
+ // is done to make this method easier to use. Returns true on success and
+ // false otherwise.
+ virtual bool Send(Message* msg) = 0;
+
+ protected:
+ virtual ~Sender() {}
+};
+
+} // namespace IPC
+
+#endif // IPC_IPC_SENDER_H_
diff --git a/ipc/ipc_sync_channel.cc b/ipc/ipc_sync_channel.cc
new file mode 100644
index 0000000000..589a1a0c60
--- /dev/null
+++ b/ipc/ipc_sync_channel.cc
@@ -0,0 +1,730 @@
+// 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_sync_channel.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <utility>
+
+#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/run_loop.h"
+#include "base/sequenced_task_runner.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread_local.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/trace_event/trace_event.h"
+#include "ipc/ipc_channel_factory.h"
+#include "ipc/ipc_logging.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_sync_message.h"
+#include "mojo/public/cpp/bindings/sync_event_watcher.h"
+
+using base::WaitableEvent;
+
+namespace IPC {
+
+namespace {
+
+// A generic callback used when watching handles synchronously. Sets |*signal|
+// to true.
+void OnEventReady(bool* signal) {
+ *signal = true;
+}
+
+base::LazyInstance<std::unique_ptr<base::WaitableEvent>>::Leaky
+ g_pump_messages_event = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// When we're blocked in a Send(), we need to process incoming synchronous
+// messages right away because it could be blocking our reply (either
+// directly from the same object we're calling, or indirectly through one or
+// more other channels). That means that in SyncContext's OnMessageReceived,
+// we need to process sync message right away if we're blocked. However a
+// simple check isn't sufficient, because the listener thread can be in the
+// process of calling Send.
+// To work around this, when SyncChannel filters a sync message, it sets
+// an event that the listener thread waits on during its Send() call. This
+// allows us to dispatch incoming sync messages when blocked. The race
+// condition is handled because if Send is in the process of being called, it
+// will check the event. In case the listener thread isn't sending a message,
+// we queue a task on the listener thread to dispatch the received messages.
+// The messages are stored in this queue object that's shared among all
+// SyncChannel objects on the same thread (since one object can receive a
+// sync message while another one is blocked).
+
+class SyncChannel::ReceivedSyncMsgQueue :
+ public base::RefCountedThreadSafe<ReceivedSyncMsgQueue> {
+ public:
+ // SyncChannel::WaitForReplyWithNestedMessageLoop may be re-entered, i.e. we
+ // may nest waiting message loops arbitrarily deep on the SyncChannel's
+ // thread. Every such operation has a corresponding WaitableEvent to be
+ // watched which, when signalled for IPC completion, breaks out of the loop.
+ // A reference to the innermost (i.e. topmost) watcher is held in
+ // |ReceivedSyncMsgQueue::top_send_done_event_watcher_|.
+ //
+ // NestedSendDoneWatcher provides a simple scoper which is used by
+ // WaitForReplyWithNestedMessageLoop to begin watching a new local "send done"
+ // event, preserving the previous topmost state on the local stack until the
+ // new inner loop is broken. If yet another subsequent nested loop is started
+ // therein the process is repeated again in the new inner stack frame, and so
+ // on.
+ //
+ // When this object is destroyed on stack unwind, the previous topmost state
+ // is swapped back into |ReceivedSyncMsgQueue::top_send_done_event_watcher_|,
+ // and its watch is resumed immediately.
+ class NestedSendDoneWatcher {
+ public:
+ NestedSendDoneWatcher(SyncChannel::SyncContext* context,
+ base::RunLoop* run_loop,
+ scoped_refptr<base::SequencedTaskRunner> task_runner)
+ : sync_msg_queue_(context->received_sync_msgs()),
+ outer_state_(sync_msg_queue_->top_send_done_event_watcher_),
+ event_(context->GetSendDoneEvent()),
+ callback_(
+ base::BindOnce(&SyncChannel::SyncContext::OnSendDoneEventSignaled,
+ context,
+ run_loop)),
+ task_runner_(std::move(task_runner)) {
+ sync_msg_queue_->top_send_done_event_watcher_ = this;
+ if (outer_state_)
+ outer_state_->StopWatching();
+ StartWatching();
+ }
+
+ ~NestedSendDoneWatcher() {
+ sync_msg_queue_->top_send_done_event_watcher_ = outer_state_;
+ if (outer_state_)
+ outer_state_->StartWatching();
+ }
+
+ private:
+ void Run(WaitableEvent* event) {
+ DCHECK(callback_);
+ std::move(callback_).Run(event);
+ }
+
+ void StartWatching() {
+ watcher_.StartWatching(
+ event_,
+ base::BindOnce(&NestedSendDoneWatcher::Run, base::Unretained(this)),
+ task_runner_);
+ }
+
+ void StopWatching() { watcher_.StopWatching(); }
+
+ ReceivedSyncMsgQueue* const sync_msg_queue_;
+ NestedSendDoneWatcher* const outer_state_;
+
+ base::WaitableEvent* const event_;
+ base::WaitableEventWatcher::EventCallback callback_;
+ base::WaitableEventWatcher watcher_;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(NestedSendDoneWatcher);
+ };
+
+ // Returns the ReceivedSyncMsgQueue instance for this thread, creating one
+ // if necessary. Call RemoveContext on the same thread when done.
+ static ReceivedSyncMsgQueue* AddContext() {
+ // We want one ReceivedSyncMsgQueue per listener thread (i.e. since multiple
+ // SyncChannel objects can block the same thread).
+ ReceivedSyncMsgQueue* rv = lazy_tls_ptr_.Pointer()->Get();
+ if (!rv) {
+ rv = new ReceivedSyncMsgQueue();
+ ReceivedSyncMsgQueue::lazy_tls_ptr_.Pointer()->Set(rv);
+ }
+ rv->listener_count_++;
+ return rv;
+ }
+
+ // Prevents messages from being dispatched immediately when the dispatch event
+ // is signaled. Instead, |*dispatch_flag| will be set.
+ void BlockDispatch(bool* dispatch_flag) { dispatch_flag_ = dispatch_flag; }
+
+ // Allows messages to be dispatched immediately when the dispatch event is
+ // signaled.
+ void UnblockDispatch() { dispatch_flag_ = nullptr; }
+
+ // Called on IPC thread when a synchronous message or reply arrives.
+ void QueueMessage(const Message& msg, SyncChannel::SyncContext* context) {
+ bool was_task_pending;
+ {
+ base::AutoLock auto_lock(message_lock_);
+
+ was_task_pending = task_pending_;
+ task_pending_ = true;
+
+ // We set the event in case the listener thread is blocked (or is about
+ // to). In case it's not, the PostTask dispatches the messages.
+ message_queue_.push_back(QueuedMessage(new Message(msg), context));
+ message_queue_version_++;
+ }
+
+ dispatch_event_.Signal();
+ if (!was_task_pending) {
+ listener_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&ReceivedSyncMsgQueue::DispatchMessagesTask,
+ this, base::RetainedRef(context)));
+ }
+ }
+
+ void QueueReply(const Message &msg, SyncChannel::SyncContext* context) {
+ received_replies_.push_back(QueuedMessage(new Message(msg), context));
+ }
+
+ // Called on the listener's thread to process any queues synchronous
+ // messages.
+ void DispatchMessagesTask(SyncContext* context) {
+ {
+ base::AutoLock auto_lock(message_lock_);
+ task_pending_ = false;
+ }
+ context->DispatchMessages();
+ }
+
+ // Dispatches any queued incoming sync messages. If |dispatching_context| is
+ // not null, messages which target a restricted dispatch channel will only be
+ // dispatched if |dispatching_context| belongs to the same restricted dispatch
+ // group as that channel. If |dispatching_context| is null, all queued
+ // messages are dispatched.
+ void DispatchMessages(SyncContext* dispatching_context) {
+ bool first_time = true;
+ uint32_t expected_version = 0;
+ SyncMessageQueue::iterator it;
+ while (true) {
+ Message* message = nullptr;
+ scoped_refptr<SyncChannel::SyncContext> context;
+ {
+ base::AutoLock auto_lock(message_lock_);
+ if (first_time || message_queue_version_ != expected_version) {
+ it = message_queue_.begin();
+ first_time = false;
+ }
+ for (; it != message_queue_.end(); it++) {
+ int message_group = it->context->restrict_dispatch_group();
+ if (message_group == kRestrictDispatchGroup_None ||
+ (dispatching_context &&
+ message_group ==
+ dispatching_context->restrict_dispatch_group())) {
+ message = it->message;
+ context = it->context;
+ it = message_queue_.erase(it);
+ message_queue_version_++;
+ expected_version = message_queue_version_;
+ break;
+ }
+ }
+ }
+
+ if (message == nullptr)
+ break;
+ context->OnDispatchMessage(*message);
+ delete message;
+ }
+ }
+
+ // SyncChannel calls this in its destructor.
+ void RemoveContext(SyncContext* context) {
+ base::AutoLock auto_lock(message_lock_);
+
+ SyncMessageQueue::iterator iter = message_queue_.begin();
+ while (iter != message_queue_.end()) {
+ if (iter->context.get() == context) {
+ delete iter->message;
+ iter = message_queue_.erase(iter);
+ message_queue_version_++;
+ } else {
+ iter++;
+ }
+ }
+
+ if (--listener_count_ == 0) {
+ DCHECK(lazy_tls_ptr_.Pointer()->Get());
+ lazy_tls_ptr_.Pointer()->Set(nullptr);
+ sync_dispatch_watcher_.reset();
+ }
+ }
+
+ base::WaitableEvent* dispatch_event() { return &dispatch_event_; }
+ base::SingleThreadTaskRunner* listener_task_runner() {
+ return listener_task_runner_.get();
+ }
+
+ // Holds a pointer to the per-thread ReceivedSyncMsgQueue object.
+ static base::LazyInstance<base::ThreadLocalPointer<ReceivedSyncMsgQueue>>::
+ DestructorAtExit lazy_tls_ptr_;
+
+ // Called on the ipc thread to check if we can unblock any current Send()
+ // calls based on a queued reply.
+ void DispatchReplies() {
+ for (size_t i = 0; i < received_replies_.size(); ++i) {
+ Message* message = received_replies_[i].message;
+ if (received_replies_[i].context->TryToUnblockListener(message)) {
+ delete message;
+ received_replies_.erase(received_replies_.begin() + i);
+ return;
+ }
+ }
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<ReceivedSyncMsgQueue>;
+
+ // See the comment in SyncChannel::SyncChannel for why this event is created
+ // as manual reset.
+ ReceivedSyncMsgQueue()
+ : message_queue_version_(0),
+ dispatch_event_(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED),
+ listener_task_runner_(base::ThreadTaskRunnerHandle::Get()),
+ sync_dispatch_watcher_(std::make_unique<mojo::SyncEventWatcher>(
+ &dispatch_event_,
+ base::Bind(&ReceivedSyncMsgQueue::OnDispatchEventReady,
+ base::Unretained(this)))) {
+ sync_dispatch_watcher_->AllowWokenUpBySyncWatchOnSameThread();
+ }
+
+ ~ReceivedSyncMsgQueue() = default;
+
+ void OnDispatchEventReady() {
+ if (dispatch_flag_) {
+ *dispatch_flag_ = true;
+ return;
+ }
+
+ // We were woken up during a sync wait, but no specific SyncChannel is
+ // currently waiting. i.e., some other Mojo interface on this thread is
+ // waiting for a response. Since we don't support anything analogous to
+ // restricted dispatch on Mojo interfaces, in this case it's safe to
+ // dispatch sync messages for any context.
+ DispatchMessages(nullptr);
+ }
+
+ // Holds information about a queued synchronous message or reply.
+ struct QueuedMessage {
+ QueuedMessage(Message* m, SyncContext* c) : message(m), context(c) { }
+ Message* message;
+ scoped_refptr<SyncChannel::SyncContext> context;
+ };
+
+ typedef std::list<QueuedMessage> SyncMessageQueue;
+ SyncMessageQueue message_queue_;
+
+ // Used to signal DispatchMessages to rescan
+ uint32_t message_queue_version_ = 0;
+
+ std::vector<QueuedMessage> received_replies_;
+
+ // Signaled when we get a synchronous message that we must respond to, as the
+ // sender needs its reply before it can reply to our original synchronous
+ // message.
+ base::WaitableEvent dispatch_event_;
+ scoped_refptr<base::SingleThreadTaskRunner> listener_task_runner_;
+ base::Lock message_lock_;
+ bool task_pending_ = false;
+ int listener_count_ = 0;
+
+ // The current NestedSendDoneWatcher for this thread, if we're currently
+ // in a SyncChannel::WaitForReplyWithNestedMessageLoop. See
+ // NestedSendDoneWatcher comments for more details.
+ NestedSendDoneWatcher* top_send_done_event_watcher_ = nullptr;
+
+ // If not null, the address of a flag to set when the dispatch event signals,
+ // in lieu of actually dispatching messages. This is used by
+ // SyncChannel::WaitForReply to restrict the scope of queued messages we're
+ // allowed to process while it's waiting.
+ bool* dispatch_flag_ = nullptr;
+
+ // Watches |dispatch_event_| during all sync handle watches on this thread.
+ std::unique_ptr<mojo::SyncEventWatcher> sync_dispatch_watcher_;
+};
+
+base::LazyInstance<base::ThreadLocalPointer<
+ SyncChannel::ReceivedSyncMsgQueue>>::DestructorAtExit
+ SyncChannel::ReceivedSyncMsgQueue::lazy_tls_ptr_ =
+ LAZY_INSTANCE_INITIALIZER;
+
+SyncChannel::SyncContext::SyncContext(
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& listener_task_runner,
+ WaitableEvent* shutdown_event)
+ : ChannelProxy::Context(listener, ipc_task_runner, listener_task_runner),
+ received_sync_msgs_(ReceivedSyncMsgQueue::AddContext()),
+ shutdown_event_(shutdown_event),
+ restrict_dispatch_group_(kRestrictDispatchGroup_None) {}
+
+void SyncChannel::SyncContext::OnSendDoneEventSignaled(
+ base::RunLoop* nested_loop,
+ base::WaitableEvent* event) {
+ DCHECK_EQ(GetSendDoneEvent(), event);
+ nested_loop->Quit();
+}
+
+SyncChannel::SyncContext::~SyncContext() {
+ while (!deserializers_.empty())
+ Pop();
+}
+
+// Adds information about an outgoing sync message to the context so that
+// we know how to deserialize the reply. Returns |true| if the message was added
+// to the context or |false| if it was rejected (e.g. due to shutdown.)
+bool SyncChannel::SyncContext::Push(SyncMessage* sync_msg) {
+ // Create the tracking information for this message. This object is stored
+ // by value since all members are pointers that are cheap to copy. These
+ // pointers are cleaned up in the Pop() function.
+ //
+ // The event is created as manual reset because in between Signal and
+ // OnObjectSignalled, another Send can happen which would stop the watcher
+ // from being called. The event would get watched later, when the nested
+ // Send completes, so the event will need to remain set.
+ base::AutoLock auto_lock(deserializers_lock_);
+ if (reject_new_deserializers_)
+ return false;
+ PendingSyncMsg pending(
+ SyncMessage::GetMessageId(*sync_msg), sync_msg->GetReplyDeserializer(),
+ new base::WaitableEvent(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED));
+ deserializers_.push_back(pending);
+ return true;
+}
+
+bool SyncChannel::SyncContext::Pop() {
+ bool result;
+ {
+ base::AutoLock auto_lock(deserializers_lock_);
+ PendingSyncMsg msg = deserializers_.back();
+ delete msg.deserializer;
+ delete msg.done_event;
+ msg.done_event = nullptr;
+ deserializers_.pop_back();
+ result = msg.send_result;
+ }
+
+ // We got a reply to a synchronous Send() call that's blocking the listener
+ // thread. However, further down the call stack there could be another
+ // blocking Send() call, whose reply we received after we made this last
+ // Send() call. So check if we have any queued replies available that
+ // can now unblock the listener thread.
+ ipc_task_runner()->PostTask(
+ FROM_HERE, base::Bind(&ReceivedSyncMsgQueue::DispatchReplies,
+ received_sync_msgs_));
+
+ return result;
+}
+
+base::WaitableEvent* SyncChannel::SyncContext::GetSendDoneEvent() {
+ base::AutoLock auto_lock(deserializers_lock_);
+ return deserializers_.back().done_event;
+}
+
+base::WaitableEvent* SyncChannel::SyncContext::GetDispatchEvent() {
+ return received_sync_msgs_->dispatch_event();
+}
+
+void SyncChannel::SyncContext::DispatchMessages() {
+ received_sync_msgs_->DispatchMessages(this);
+}
+
+bool SyncChannel::SyncContext::TryToUnblockListener(const Message* msg) {
+ base::AutoLock auto_lock(deserializers_lock_);
+ if (deserializers_.empty() ||
+ !SyncMessage::IsMessageReplyTo(*msg, deserializers_.back().id)) {
+ return false;
+ }
+
+ if (!msg->is_reply_error()) {
+ bool send_result = deserializers_.back().deserializer->
+ SerializeOutputParameters(*msg);
+ deserializers_.back().send_result = send_result;
+ DVLOG_IF(1, !send_result) << "Couldn't deserialize reply message";
+ } else {
+ DVLOG(1) << "Received error reply";
+ }
+
+ base::WaitableEvent* done_event = deserializers_.back().done_event;
+ TRACE_EVENT_FLOW_BEGIN0(
+ TRACE_DISABLED_BY_DEFAULT("ipc.flow"),
+ "SyncChannel::SyncContext::TryToUnblockListener", done_event);
+
+ done_event->Signal();
+
+ return true;
+}
+
+void SyncChannel::SyncContext::Clear() {
+ CancelPendingSends();
+ received_sync_msgs_->RemoveContext(this);
+ Context::Clear();
+}
+
+bool SyncChannel::SyncContext::OnMessageReceived(const Message& msg) {
+ // Give the filters a chance at processing this message.
+ if (TryFilters(msg))
+ return true;
+
+ if (TryToUnblockListener(&msg))
+ return true;
+
+ if (msg.is_reply()) {
+ received_sync_msgs_->QueueReply(msg, this);
+ return true;
+ }
+
+ if (msg.should_unblock()) {
+ received_sync_msgs_->QueueMessage(msg, this);
+ return true;
+ }
+
+ return Context::OnMessageReceivedNoFilter(msg);
+}
+
+void SyncChannel::SyncContext::OnChannelError() {
+ CancelPendingSends();
+ shutdown_watcher_.StopWatching();
+ Context::OnChannelError();
+}
+
+void SyncChannel::SyncContext::OnChannelOpened() {
+ shutdown_watcher_.StartWatching(
+ shutdown_event_,
+ base::Bind(&SyncChannel::SyncContext::OnShutdownEventSignaled,
+ base::Unretained(this)),
+ base::SequencedTaskRunnerHandle::Get());
+ Context::OnChannelOpened();
+}
+
+void SyncChannel::SyncContext::OnChannelClosed() {
+ CancelPendingSends();
+ shutdown_watcher_.StopWatching();
+ Context::OnChannelClosed();
+}
+
+void SyncChannel::SyncContext::CancelPendingSends() {
+ base::AutoLock auto_lock(deserializers_lock_);
+ reject_new_deserializers_ = true;
+ PendingSyncMessageQueue::iterator iter;
+ DVLOG(1) << "Canceling pending sends";
+ for (iter = deserializers_.begin(); iter != deserializers_.end(); iter++) {
+ TRACE_EVENT_FLOW_BEGIN0(TRACE_DISABLED_BY_DEFAULT("ipc.flow"),
+ "SyncChannel::SyncContext::CancelPendingSends",
+ iter->done_event);
+ iter->done_event->Signal();
+ }
+}
+
+void SyncChannel::SyncContext::OnShutdownEventSignaled(WaitableEvent* event) {
+ DCHECK_EQ(event, shutdown_event_);
+
+ // Process shut down before we can get a reply to a synchronous message.
+ // Cancel pending Send calls, which will end up setting the send done event.
+ CancelPendingSends();
+}
+
+// static
+std::unique_ptr<SyncChannel> SyncChannel::Create(
+ const IPC::ChannelHandle& channel_handle,
+ Channel::Mode mode,
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& listener_task_runner,
+ bool create_pipe_now,
+ base::WaitableEvent* shutdown_event) {
+ std::unique_ptr<SyncChannel> channel =
+ Create(listener, ipc_task_runner, listener_task_runner, shutdown_event);
+ channel->Init(channel_handle, mode, create_pipe_now);
+ return channel;
+}
+
+// static
+std::unique_ptr<SyncChannel> SyncChannel::Create(
+ std::unique_ptr<ChannelFactory> factory,
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& listener_task_runner,
+ bool create_pipe_now,
+ base::WaitableEvent* shutdown_event) {
+ std::unique_ptr<SyncChannel> channel =
+ Create(listener, ipc_task_runner, listener_task_runner, shutdown_event);
+ channel->Init(std::move(factory), create_pipe_now);
+ return channel;
+}
+
+// static
+std::unique_ptr<SyncChannel> SyncChannel::Create(
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& listener_task_runner,
+ WaitableEvent* shutdown_event) {
+ return base::WrapUnique(new SyncChannel(
+ listener, ipc_task_runner, listener_task_runner, shutdown_event));
+}
+
+SyncChannel::SyncChannel(
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& listener_task_runner,
+ WaitableEvent* shutdown_event)
+ : ChannelProxy(new SyncContext(listener,
+ ipc_task_runner,
+ listener_task_runner,
+ shutdown_event)),
+ sync_handle_registry_(mojo::SyncHandleRegistry::current()) {
+ // The current (listener) thread must be distinct from the IPC thread, or else
+ // sending synchronous messages will deadlock.
+ DCHECK_NE(ipc_task_runner.get(), base::ThreadTaskRunnerHandle::Get().get());
+ StartWatching();
+}
+
+SyncChannel::~SyncChannel() = default;
+
+void SyncChannel::SetRestrictDispatchChannelGroup(int group) {
+ sync_context()->set_restrict_dispatch_group(group);
+}
+
+scoped_refptr<SyncMessageFilter> SyncChannel::CreateSyncMessageFilter() {
+ scoped_refptr<SyncMessageFilter> filter = new SyncMessageFilter(
+ sync_context()->shutdown_event());
+ AddFilter(filter.get());
+ if (!did_init())
+ pre_init_sync_message_filters_.push_back(filter);
+ return filter;
+}
+
+bool SyncChannel::Send(Message* message) {
+#if BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
+ std::string name;
+ Logging::GetInstance()->GetMessageText(
+ message->type(), &name, message, nullptr);
+ TRACE_EVENT1("ipc", "SyncChannel::Send", "name", name);
+#else
+ TRACE_EVENT2("ipc", "SyncChannel::Send",
+ "class", IPC_MESSAGE_ID_CLASS(message->type()),
+ "line", IPC_MESSAGE_ID_LINE(message->type()));
+#endif
+ if (!message->is_sync()) {
+ ChannelProxy::SendInternal(message);
+ return true;
+ }
+
+ SyncMessage* sync_msg = static_cast<SyncMessage*>(message);
+ bool pump_messages = sync_msg->ShouldPumpMessages();
+
+ // *this* might get deleted in WaitForReply.
+ scoped_refptr<SyncContext> context(sync_context());
+ if (!context->Push(sync_msg)) {
+ DVLOG(1) << "Channel is shutting down. Dropping sync message.";
+ delete message;
+ return false;
+ }
+
+ ChannelProxy::SendInternal(message);
+
+ // Wait for reply, or for any other incoming synchronous messages.
+ // |this| might get deleted, so only call static functions at this point.
+ scoped_refptr<mojo::SyncHandleRegistry> registry = sync_handle_registry_;
+ WaitForReply(registry.get(), context.get(), pump_messages);
+
+ TRACE_EVENT_FLOW_END0(TRACE_DISABLED_BY_DEFAULT("ipc.flow"),
+ "SyncChannel::Send", context->GetSendDoneEvent());
+
+ return context->Pop();
+}
+
+void SyncChannel::WaitForReply(mojo::SyncHandleRegistry* registry,
+ SyncContext* context,
+ bool pump_messages) {
+ context->DispatchMessages();
+
+ base::WaitableEvent* pump_messages_event = nullptr;
+ if (pump_messages) {
+ if (!g_pump_messages_event.Get()) {
+ g_pump_messages_event.Get() = std::make_unique<base::WaitableEvent>(
+ base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::SIGNALED);
+ }
+ pump_messages_event = g_pump_messages_event.Get().get();
+ }
+
+ while (true) {
+ bool dispatch = false;
+ bool send_done = false;
+ bool should_pump_messages = false;
+ base::Closure on_send_done_callback = base::Bind(&OnEventReady, &send_done);
+ registry->RegisterEvent(context->GetSendDoneEvent(), on_send_done_callback);
+
+ base::Closure on_pump_messages_callback;
+ if (pump_messages_event) {
+ on_pump_messages_callback =
+ base::Bind(&OnEventReady, &should_pump_messages);
+ registry->RegisterEvent(pump_messages_event, on_pump_messages_callback);
+ }
+
+ const bool* stop_flags[] = { &dispatch, &send_done, &should_pump_messages };
+ context->received_sync_msgs()->BlockDispatch(&dispatch);
+ registry->Wait(stop_flags, 3);
+ context->received_sync_msgs()->UnblockDispatch();
+
+ registry->UnregisterEvent(context->GetSendDoneEvent(),
+ on_send_done_callback);
+ if (pump_messages_event)
+ registry->UnregisterEvent(pump_messages_event, on_pump_messages_callback);
+
+ if (dispatch) {
+ // We're waiting for a reply, but we received a blocking synchronous call.
+ // We must process it to avoid potential deadlocks.
+ context->GetDispatchEvent()->Reset();
+ context->DispatchMessages();
+ continue;
+ }
+
+ if (should_pump_messages)
+ WaitForReplyWithNestedMessageLoop(context); // Run a nested run loop.
+
+ break;
+ }
+}
+
+void SyncChannel::WaitForReplyWithNestedMessageLoop(SyncContext* context) {
+ base::RunLoop nested_loop(base::RunLoop::Type::kNestableTasksAllowed);
+ ReceivedSyncMsgQueue::NestedSendDoneWatcher watcher(
+ context, &nested_loop, context->listener_task_runner());
+ nested_loop.Run();
+}
+
+void SyncChannel::OnDispatchEventSignaled(base::WaitableEvent* event) {
+ DCHECK_EQ(sync_context()->GetDispatchEvent(), event);
+ sync_context()->GetDispatchEvent()->Reset();
+
+ StartWatching();
+
+ // NOTE: May delete |this|.
+ sync_context()->DispatchMessages();
+}
+
+void SyncChannel::StartWatching() {
+ // |dispatch_watcher_| watches the event asynchronously, only dispatching
+ // messages once the listener thread is unblocked and pumping its task queue.
+ // The ReceivedSyncMsgQueue also watches this event and may dispatch
+ // immediately if woken up by a message which it's allowed to dispatch.
+ dispatch_watcher_.StartWatching(
+ sync_context()->GetDispatchEvent(),
+ base::BindOnce(&SyncChannel::OnDispatchEventSignaled,
+ base::Unretained(this)),
+ sync_context()->listener_task_runner());
+}
+
+void SyncChannel::OnChannelInit() {
+ pre_init_sync_message_filters_.clear();
+}
+
+} // namespace IPC
diff --git a/ipc/ipc_sync_channel.h b/ipc/ipc_sync_channel.h
new file mode 100644
index 0000000000..a54dbaa599
--- /dev/null
+++ b/ipc/ipc_sync_channel.h
@@ -0,0 +1,263 @@
+// 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_CHANNEL_H_
+#define IPC_IPC_SYNC_CHANNEL_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/component_export.h"
+#include "base/containers/circular_deque.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event_watcher.h"
+#include "ipc/ipc_channel_handle.h"
+#include "ipc/ipc_channel_proxy.h"
+#include "ipc/ipc_sync_message.h"
+#include "ipc/ipc_sync_message_filter.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+
+namespace base {
+class RunLoop;
+class WaitableEvent;
+};
+
+namespace mojo {
+class SyncHandleRegistry;
+}
+
+namespace IPC {
+
+class ChannelFactory;
+class SyncMessage;
+
+// This is similar to ChannelProxy, with the added feature of supporting sending
+// synchronous messages.
+//
+// Overview of how the sync channel works
+// --------------------------------------
+// When the sending thread sends a synchronous message, we create a bunch
+// of tracking info (created in Send, stored in the PendingSyncMsg
+// structure) associated with the message that we identify by the unique
+// "MessageId" on the SyncMessage. Among the things we save is the
+// "Deserializer" which is provided by the sync message. This object is in
+// charge of reading the parameters from the reply message and putting them in
+// the output variables provided by its caller.
+//
+// The info gets stashed in a queue since we could have a nested stack of sync
+// messages (each side could send sync messages in response to sync messages,
+// so it works like calling a function). The message is sent to the I/O thread
+// for dispatch and the original thread blocks waiting for the reply.
+//
+// SyncContext maintains the queue in a threadsafe way and listens for replies
+// on the I/O thread. When a reply comes in that matches one of the messages
+// it's looking for (using the unique message ID), it will execute the
+// deserializer stashed from before, and unblock the original thread.
+//
+//
+// Significant complexity results from the fact that messages are still coming
+// in while the original thread is blocked. Normal async messages are queued
+// and dispatched after the blocking call is complete. Sync messages must
+// be dispatched in a reentrant manner to avoid deadlock.
+//
+//
+// Note that care must be taken that the lifetime of the ipc_thread argument
+// is more than this object. If the message loop goes away while this object
+// is running and it's used to send a message, then it will use the invalid
+// message loop pointer to proxy it to the ipc thread.
+class COMPONENT_EXPORT(IPC) SyncChannel : public ChannelProxy {
+ public:
+ enum RestrictDispatchGroup {
+ kRestrictDispatchGroup_None = 0,
+ };
+
+ // Creates and initializes a sync channel. If create_pipe_now is specified,
+ // the channel will be initialized synchronously.
+ // The naming pattern follows IPC::Channel.
+ static std::unique_ptr<SyncChannel> Create(
+ const IPC::ChannelHandle& channel_handle,
+ IPC::Channel::Mode mode,
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& listener_task_runner,
+ bool create_pipe_now,
+ base::WaitableEvent* shutdown_event);
+
+ static std::unique_ptr<SyncChannel> Create(
+ std::unique_ptr<ChannelFactory> factory,
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& listener_task_runner,
+ bool create_pipe_now,
+ base::WaitableEvent* shutdown_event);
+
+ // Creates an uninitialized sync channel. Call ChannelProxy::Init to
+ // initialize the channel. This two-step setup allows message filters to be
+ // added before any messages are sent or received.
+ static std::unique_ptr<SyncChannel> Create(
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& listener_task_runner,
+ base::WaitableEvent* shutdown_event);
+
+ ~SyncChannel() override;
+
+ bool Send(Message* message) override;
+
+ // Sets the dispatch group for this channel, to only allow re-entrant dispatch
+ // of messages to other channels in the same group.
+ //
+ // Normally, any unblocking message coming from any channel can be dispatched
+ // when any (possibly other) channel is blocked on sending a message. This is
+ // needed in some cases to unblock certain loops (e.g. necessary when some
+ // processes share a window hierarchy), but may cause re-entrancy issues in
+ // some cases where such loops are not possible. This flags allows the tagging
+ // of some particular channels to only re-enter in known correct cases.
+ //
+ // Incoming messages on channels belonging to a group that is not
+ // kRestrictDispatchGroup_None will only be dispatched while a sync message is
+ // being sent on a channel of the *same* group.
+ // Incoming messages belonging to the kRestrictDispatchGroup_None group (the
+ // default) will be dispatched in any case.
+ void SetRestrictDispatchChannelGroup(int group);
+
+ // Creates a new IPC::SyncMessageFilter and adds it to this SyncChannel.
+ // This should be used instead of directly constructing a new
+ // SyncMessageFilter.
+ scoped_refptr<IPC::SyncMessageFilter> CreateSyncMessageFilter();
+
+ protected:
+ class ReceivedSyncMsgQueue;
+ friend class ReceivedSyncMsgQueue;
+
+ // SyncContext holds the per object data for SyncChannel, so that SyncChannel
+ // can be deleted while it's being used in a different thread. See
+ // ChannelProxy::Context for more information.
+ class SyncContext : public Context {
+ public:
+ SyncContext(
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& listener_task_runner,
+ base::WaitableEvent* shutdown_event);
+
+ // Adds information about an outgoing sync message to the context so that
+ // we know how to deserialize the reply.
+ bool Push(SyncMessage* sync_msg);
+
+ // Cleanly remove the top deserializer (and throw it away). Returns the
+ // result of the Send call for that message.
+ bool Pop();
+
+ // Returns a Mojo Event that signals when a sync send is complete or timed
+ // out or the process shut down.
+ base::WaitableEvent* GetSendDoneEvent();
+
+ // Returns a Mojo Event that signals when an incoming message that's not the
+ // pending reply needs to get dispatched (by calling DispatchMessages.)
+ base::WaitableEvent* GetDispatchEvent();
+
+ void DispatchMessages();
+
+ // Checks if the given message is blocking the listener thread because of a
+ // synchronous send. If it is, the thread is unblocked and true is
+ // returned. Otherwise the function returns false.
+ bool TryToUnblockListener(const Message* msg);
+
+ base::WaitableEvent* shutdown_event() { return shutdown_event_; }
+
+ ReceivedSyncMsgQueue* received_sync_msgs() {
+ return received_sync_msgs_.get();
+ }
+
+ void set_restrict_dispatch_group(int group) {
+ restrict_dispatch_group_ = group;
+ }
+
+ int restrict_dispatch_group() const {
+ return restrict_dispatch_group_;
+ }
+
+ void OnSendDoneEventSignaled(base::RunLoop* nested_loop,
+ base::WaitableEvent* event);
+
+ private:
+ ~SyncContext() override;
+ // ChannelProxy methods that we override.
+
+ // Called on the listener thread.
+ void Clear() override;
+
+ // Called on the IPC thread.
+ bool OnMessageReceived(const Message& msg) override;
+ void OnChannelError() override;
+ void OnChannelOpened() override;
+ void OnChannelClosed() override;
+
+ // Cancels all pending Send calls.
+ void CancelPendingSends();
+
+ void OnShutdownEventSignaled(base::WaitableEvent* event);
+
+ using PendingSyncMessageQueue = base::circular_deque<PendingSyncMsg>;
+ PendingSyncMessageQueue deserializers_;
+ bool reject_new_deserializers_ = false;
+ base::Lock deserializers_lock_;
+
+ scoped_refptr<ReceivedSyncMsgQueue> received_sync_msgs_;
+
+ base::WaitableEvent* shutdown_event_;
+ base::WaitableEventWatcher shutdown_watcher_;
+ base::WaitableEventWatcher::EventCallback shutdown_watcher_callback_;
+ int restrict_dispatch_group_;
+ };
+
+ private:
+ SyncChannel(
+ Listener* listener,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ipc_task_runner,
+ const scoped_refptr<base::SingleThreadTaskRunner>& listener_task_runner,
+ base::WaitableEvent* shutdown_event);
+
+ void OnDispatchEventSignaled(base::WaitableEvent* event);
+
+ SyncContext* sync_context() {
+ return reinterpret_cast<SyncContext*>(context());
+ }
+
+ // Both these functions wait for a reply, timeout or process shutdown. The
+ // latter one also runs a nested run loop in the meantime.
+ static void WaitForReply(mojo::SyncHandleRegistry* registry,
+ SyncContext* context,
+ bool pump_messages);
+
+ // Runs a nested run loop until a reply arrives, times out, or the process
+ // shuts down.
+ static void WaitForReplyWithNestedMessageLoop(SyncContext* context);
+
+ // Starts the dispatch watcher.
+ void StartWatching();
+
+ // ChannelProxy overrides:
+ void OnChannelInit() override;
+
+ scoped_refptr<mojo::SyncHandleRegistry> sync_handle_registry_;
+
+ // Used to signal events between the IPC and listener threads.
+ base::WaitableEventWatcher dispatch_watcher_;
+ base::WaitableEventWatcher::EventCallback dispatch_watcher_callback_;
+
+ // Tracks SyncMessageFilters created before complete channel initialization.
+ std::vector<scoped_refptr<SyncMessageFilter>> pre_init_sync_message_filters_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncChannel);
+};
+
+} // namespace IPC
+
+#endif // IPC_IPC_SYNC_CHANNEL_H_
diff --git a/ipc/ipc_sync_channel_unittest.cc b/ipc/ipc_sync_channel_unittest.cc
new file mode 100644
index 0000000000..f5134a5110
--- /dev/null
+++ b/ipc/ipc_sync_channel_unittest.cc
@@ -0,0 +1,1884 @@
+// 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_sync_channel.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#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/process/process_handle.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "ipc/ipc_listener.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_sender.h"
+#include "ipc/ipc_sync_message_filter.h"
+#include "ipc/ipc_sync_message_unittest.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::WaitableEvent;
+
+namespace IPC {
+namespace {
+
+// Base class for a "process" with listener and IPC threads.
+class Worker : public Listener, public Sender {
+ public:
+ // Will create a channel without a name.
+ Worker(Channel::Mode mode,
+ const std::string& thread_name,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : done_(
+ new WaitableEvent(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED)),
+ channel_created_(
+ new WaitableEvent(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED)),
+ channel_handle_(std::move(channel_handle)),
+ mode_(mode),
+ ipc_thread_((thread_name + "_ipc").c_str()),
+ listener_thread_((thread_name + "_listener").c_str()),
+ overrided_thread_(NULL),
+ shutdown_event_(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED),
+ is_shutdown_(false) {}
+
+ // Will create a named channel and use this name for the threads' name.
+ Worker(mojo::ScopedMessagePipeHandle channel_handle, Channel::Mode mode)
+ : done_(
+ new WaitableEvent(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED)),
+ channel_created_(
+ new WaitableEvent(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED)),
+ channel_handle_(std::move(channel_handle)),
+ mode_(mode),
+ ipc_thread_("ipc thread"),
+ listener_thread_("listener thread"),
+ overrided_thread_(NULL),
+ shutdown_event_(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED),
+ is_shutdown_(false) {}
+
+ ~Worker() override {
+ // Shutdown() must be called before destruction.
+ CHECK(is_shutdown_);
+ }
+ bool Send(Message* msg) override { return channel_->Send(msg); }
+ void WaitForChannelCreation() { channel_created_->Wait(); }
+ void CloseChannel() {
+ DCHECK(ListenerThread()->task_runner()->BelongsToCurrentThread());
+ channel_->Close();
+ }
+ void Start() {
+ StartThread(&listener_thread_, base::MessageLoop::TYPE_DEFAULT);
+ ListenerThread()->task_runner()->PostTask(
+ FROM_HERE, base::Bind(&Worker::OnStart, base::Unretained(this)));
+ }
+ void Shutdown() {
+ // The IPC thread needs to outlive SyncChannel. We can't do this in
+ // ~Worker(), since that'll reset the vtable pointer (to Worker's), which
+ // may result in a race conditions. See http://crbug.com/25841.
+ WaitableEvent listener_done(
+ base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED),
+ ipc_done(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ ListenerThread()->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&Worker::OnListenerThreadShutdown1, base::Unretained(this),
+ &listener_done, &ipc_done));
+ listener_done.Wait();
+ ipc_done.Wait();
+ ipc_thread_.Stop();
+ listener_thread_.Stop();
+ is_shutdown_ = true;
+ }
+ void OverrideThread(base::Thread* overrided_thread) {
+ DCHECK(overrided_thread_ == NULL);
+ overrided_thread_ = overrided_thread;
+ }
+ bool SendAnswerToLife(bool pump, bool succeed) {
+ int answer = 0;
+ SyncMessage* msg = new SyncChannelTestMsg_AnswerToLife(&answer);
+ if (pump)
+ msg->EnableMessagePumping();
+ bool result = Send(msg);
+ DCHECK_EQ(result, succeed);
+ DCHECK_EQ(answer, (succeed ? 42 : 0));
+ return result;
+ }
+ bool SendDouble(bool pump, bool succeed) {
+ int answer = 0;
+ SyncMessage* msg = new SyncChannelTestMsg_Double(5, &answer);
+ if (pump)
+ msg->EnableMessagePumping();
+ bool result = Send(msg);
+ DCHECK_EQ(result, succeed);
+ DCHECK_EQ(answer, (succeed ? 10 : 0));
+ return result;
+ }
+ mojo::MessagePipeHandle TakeChannelHandle() {
+ DCHECK(channel_handle_.is_valid());
+ return channel_handle_.release();
+ }
+ Channel::Mode mode() { return mode_; }
+ WaitableEvent* done_event() { return done_.get(); }
+ WaitableEvent* shutdown_event() { return &shutdown_event_; }
+ void ResetChannel() { channel_.reset(); }
+ // Derived classes need to call this when they've completed their part of
+ // the test.
+ void Done() { done_->Signal(); }
+
+ protected:
+ SyncChannel* channel() { return channel_.get(); }
+ // Functions for derived classes to implement if they wish.
+ virtual void Run() { }
+ virtual void OnAnswer(int* answer) { NOTREACHED(); }
+ virtual void OnAnswerDelay(Message* reply_msg) {
+ // The message handler map below can only take one entry for
+ // SyncChannelTestMsg_AnswerToLife, so since some classes want
+ // the normal version while other want the delayed reply, we
+ // call the normal version if the derived class didn't override
+ // this function.
+ int answer;
+ OnAnswer(&answer);
+ SyncChannelTestMsg_AnswerToLife::WriteReplyParams(reply_msg, answer);
+ Send(reply_msg);
+ }
+ virtual void OnDouble(int in, int* out) { NOTREACHED(); }
+ virtual void OnDoubleDelay(int in, Message* reply_msg) {
+ int result;
+ OnDouble(in, &result);
+ SyncChannelTestMsg_Double::WriteReplyParams(reply_msg, result);
+ Send(reply_msg);
+ }
+
+ virtual void OnNestedTestMsg(Message* reply_msg) {
+ NOTREACHED();
+ }
+
+ virtual SyncChannel* CreateChannel() {
+ std::unique_ptr<SyncChannel> channel = SyncChannel::Create(
+ TakeChannelHandle(), mode_, this, ipc_thread_.task_runner(),
+ base::ThreadTaskRunnerHandle::Get(), true, &shutdown_event_);
+ return channel.release();
+ }
+
+ base::Thread* ListenerThread() {
+ return overrided_thread_ ? overrided_thread_ : &listener_thread_;
+ }
+
+ const base::Thread& ipc_thread() const { return ipc_thread_; }
+
+ private:
+ // Called on the listener thread to create the sync channel.
+ void OnStart() {
+ // Link ipc_thread_, listener_thread_ and channel_ altogether.
+ StartThread(&ipc_thread_, base::MessageLoop::TYPE_IO);
+ channel_.reset(CreateChannel());
+ channel_created_->Signal();
+ Run();
+ }
+
+ void OnListenerThreadShutdown1(WaitableEvent* listener_event,
+ WaitableEvent* ipc_event) {
+ // SyncChannel needs to be destructed on the thread that it was created on.
+ channel_.reset();
+
+ base::RunLoop().RunUntilIdle();
+
+ ipc_thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&Worker::OnIPCThreadShutdown, base::Unretained(this),
+ listener_event, ipc_event));
+ }
+
+ void OnIPCThreadShutdown(WaitableEvent* listener_event,
+ WaitableEvent* ipc_event) {
+ base::RunLoop().RunUntilIdle();
+ ipc_event->Signal();
+
+ listener_thread_.task_runner()->PostTask(
+ FROM_HERE, base::Bind(&Worker::OnListenerThreadShutdown2,
+ base::Unretained(this), listener_event));
+ }
+
+ void OnListenerThreadShutdown2(WaitableEvent* listener_event) {
+ base::RunLoop().RunUntilIdle();
+ listener_event->Signal();
+ }
+
+ bool OnMessageReceived(const Message& message) override {
+ IPC_BEGIN_MESSAGE_MAP(Worker, message)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(SyncChannelTestMsg_Double, OnDoubleDelay)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(SyncChannelTestMsg_AnswerToLife,
+ OnAnswerDelay)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(SyncChannelNestedTestMsg_String,
+ OnNestedTestMsg)
+ IPC_END_MESSAGE_MAP()
+ return true;
+ }
+
+ void StartThread(base::Thread* thread, base::MessageLoop::Type type) {
+ base::Thread::Options options;
+ options.message_loop_type = type;
+ thread->StartWithOptions(options);
+ }
+
+ std::unique_ptr<WaitableEvent> done_;
+ std::unique_ptr<WaitableEvent> channel_created_;
+ mojo::ScopedMessagePipeHandle channel_handle_;
+ Channel::Mode mode_;
+ std::unique_ptr<SyncChannel> channel_;
+ base::Thread ipc_thread_;
+ base::Thread listener_thread_;
+ base::Thread* overrided_thread_;
+
+ base::WaitableEvent shutdown_event_;
+
+ bool is_shutdown_;
+
+ DISALLOW_COPY_AND_ASSIGN(Worker);
+};
+
+
+// Starts the test with the given workers. This function deletes the workers
+// when it's done.
+void RunTest(std::vector<Worker*> workers) {
+ // First we create the workers that are channel servers, or else the other
+ // workers' channel initialization might fail because the pipe isn't created..
+ for (size_t i = 0; i < workers.size(); ++i) {
+ if (workers[i]->mode() & Channel::MODE_SERVER_FLAG) {
+ workers[i]->Start();
+ workers[i]->WaitForChannelCreation();
+ }
+ }
+
+ // now create the clients
+ for (size_t i = 0; i < workers.size(); ++i) {
+ if (workers[i]->mode() & Channel::MODE_CLIENT_FLAG)
+ workers[i]->Start();
+ }
+
+ // wait for all the workers to finish
+ for (size_t i = 0; i < workers.size(); ++i)
+ workers[i]->done_event()->Wait();
+
+ for (size_t i = 0; i < workers.size(); ++i) {
+ workers[i]->Shutdown();
+ delete workers[i];
+ }
+}
+
+class IPCSyncChannelTest : public testing::Test {
+ private:
+ base::MessageLoop message_loop_;
+};
+
+//------------------------------------------------------------------------------
+
+class SimpleServer : public Worker {
+ public:
+ SimpleServer(bool pump_during_send,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(Channel::MODE_SERVER,
+ "simpler_server",
+ std::move(channel_handle)),
+ pump_during_send_(pump_during_send) {}
+ void Run() override {
+ SendAnswerToLife(pump_during_send_, true);
+ Done();
+ }
+
+ bool pump_during_send_;
+};
+
+class SimpleClient : public Worker {
+ public:
+ explicit SimpleClient(mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(Channel::MODE_CLIENT,
+ "simple_client",
+ std::move(channel_handle)) {}
+
+ void OnAnswer(int* answer) override {
+ *answer = 42;
+ Done();
+ }
+};
+
+void Simple(bool pump_during_send) {
+ std::vector<Worker*> workers;
+ mojo::MessagePipe pipe;
+ workers.push_back(
+ new SimpleServer(pump_during_send, std::move(pipe.handle0)));
+ workers.push_back(new SimpleClient(std::move(pipe.handle1)));
+ RunTest(workers);
+}
+
+#if defined(OS_ANDROID)
+#define MAYBE_Simple DISABLED_Simple
+#else
+#define MAYBE_Simple Simple
+#endif
+// Tests basic synchronous call
+TEST_F(IPCSyncChannelTest, MAYBE_Simple) {
+ Simple(false);
+ Simple(true);
+}
+
+//------------------------------------------------------------------------------
+
+// Worker classes which override how the sync channel is created to use the
+// two-step initialization (calling the lightweight constructor and then
+// ChannelProxy::Init separately) process.
+class TwoStepServer : public Worker {
+ public:
+ TwoStepServer(bool create_pipe_now,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(Channel::MODE_SERVER,
+ "simpler_server",
+ std::move(channel_handle)),
+ create_pipe_now_(create_pipe_now) {}
+
+ void Run() override {
+ SendAnswerToLife(false, true);
+ Done();
+ }
+
+ SyncChannel* CreateChannel() override {
+ SyncChannel* channel =
+ SyncChannel::Create(TakeChannelHandle(), mode(), this,
+ ipc_thread().task_runner(),
+ base::ThreadTaskRunnerHandle::Get(),
+ create_pipe_now_, shutdown_event())
+ .release();
+ return channel;
+ }
+
+ bool create_pipe_now_;
+};
+
+class TwoStepClient : public Worker {
+ public:
+ TwoStepClient(bool create_pipe_now,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(Channel::MODE_CLIENT,
+ "simple_client",
+ std::move(channel_handle)),
+ create_pipe_now_(create_pipe_now) {}
+
+ void OnAnswer(int* answer) override {
+ *answer = 42;
+ Done();
+ }
+
+ SyncChannel* CreateChannel() override {
+ SyncChannel* channel =
+ SyncChannel::Create(TakeChannelHandle(), mode(), this,
+ ipc_thread().task_runner(),
+ base::ThreadTaskRunnerHandle::Get(),
+ create_pipe_now_, shutdown_event())
+ .release();
+ return channel;
+ }
+
+ bool create_pipe_now_;
+};
+
+void TwoStep(bool create_server_pipe_now, bool create_client_pipe_now) {
+ std::vector<Worker*> workers;
+ mojo::MessagePipe pipe;
+ workers.push_back(
+ new TwoStepServer(create_server_pipe_now, std::move(pipe.handle0)));
+ workers.push_back(
+ new TwoStepClient(create_client_pipe_now, std::move(pipe.handle1)));
+ RunTest(workers);
+}
+
+// Tests basic two-step initialization, where you call the lightweight
+// constructor then Init.
+TEST_F(IPCSyncChannelTest, TwoStepInitialization) {
+ TwoStep(false, false);
+ TwoStep(false, true);
+ TwoStep(true, false);
+ TwoStep(true, true);
+}
+
+//------------------------------------------------------------------------------
+
+class DelayClient : public Worker {
+ public:
+ explicit DelayClient(mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(Channel::MODE_CLIENT,
+ "delay_client",
+ std::move(channel_handle)) {}
+
+ void OnAnswerDelay(Message* reply_msg) override {
+ SyncChannelTestMsg_AnswerToLife::WriteReplyParams(reply_msg, 42);
+ Send(reply_msg);
+ Done();
+ }
+};
+
+void DelayReply(bool pump_during_send) {
+ std::vector<Worker*> workers;
+ mojo::MessagePipe pipe;
+ workers.push_back(
+ new SimpleServer(pump_during_send, std::move(pipe.handle0)));
+ workers.push_back(new DelayClient(std::move(pipe.handle1)));
+ RunTest(workers);
+}
+
+// Tests that asynchronous replies work
+TEST_F(IPCSyncChannelTest, DelayReply) {
+ DelayReply(false);
+ DelayReply(true);
+}
+
+//------------------------------------------------------------------------------
+
+class NoHangServer : public Worker {
+ public:
+ NoHangServer(WaitableEvent* got_first_reply,
+ bool pump_during_send,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(Channel::MODE_SERVER,
+ "no_hang_server",
+ std::move(channel_handle)),
+ got_first_reply_(got_first_reply),
+ pump_during_send_(pump_during_send) {}
+ void Run() override {
+ SendAnswerToLife(pump_during_send_, true);
+ got_first_reply_->Signal();
+
+ SendAnswerToLife(pump_during_send_, false);
+ Done();
+ }
+
+ WaitableEvent* got_first_reply_;
+ bool pump_during_send_;
+};
+
+class NoHangClient : public Worker {
+ public:
+ NoHangClient(WaitableEvent* got_first_reply,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(Channel::MODE_CLIENT,
+ "no_hang_client",
+ std::move(channel_handle)),
+ got_first_reply_(got_first_reply) {}
+
+ void OnAnswerDelay(Message* reply_msg) override {
+ // Use the DELAY_REPLY macro so that we can force the reply to be sent
+ // before this function returns (when the channel will be reset).
+ SyncChannelTestMsg_AnswerToLife::WriteReplyParams(reply_msg, 42);
+ Send(reply_msg);
+ got_first_reply_->Wait();
+ CloseChannel();
+ Done();
+ }
+
+ WaitableEvent* got_first_reply_;
+};
+
+void NoHang(bool pump_during_send) {
+ WaitableEvent got_first_reply(
+ base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ std::vector<Worker*> workers;
+ mojo::MessagePipe pipe;
+ workers.push_back(new NoHangServer(&got_first_reply, pump_during_send,
+ std::move(pipe.handle0)));
+ workers.push_back(
+ new NoHangClient(&got_first_reply, std::move(pipe.handle1)));
+ RunTest(workers);
+}
+
+// Tests that caller doesn't hang if receiver dies
+TEST_F(IPCSyncChannelTest, NoHang) {
+ NoHang(false);
+ NoHang(true);
+}
+
+//------------------------------------------------------------------------------
+
+class UnblockServer : public Worker {
+ public:
+ UnblockServer(bool pump_during_send,
+ bool delete_during_send,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(Channel::MODE_SERVER,
+ "unblock_server",
+ std::move(channel_handle)),
+ pump_during_send_(pump_during_send),
+ delete_during_send_(delete_during_send) {}
+ void Run() override {
+ if (delete_during_send_) {
+ // Use custom code since race conditions mean the answer may or may not be
+ // available.
+ int answer = 0;
+ SyncMessage* msg = new SyncChannelTestMsg_AnswerToLife(&answer);
+ if (pump_during_send_)
+ msg->EnableMessagePumping();
+ Send(msg);
+ } else {
+ SendAnswerToLife(pump_during_send_, true);
+ }
+ Done();
+ }
+
+ void OnDoubleDelay(int in, Message* reply_msg) override {
+ SyncChannelTestMsg_Double::WriteReplyParams(reply_msg, in * 2);
+ Send(reply_msg);
+ if (delete_during_send_)
+ ResetChannel();
+ }
+
+ bool pump_during_send_;
+ bool delete_during_send_;
+};
+
+class UnblockClient : public Worker {
+ public:
+ UnblockClient(bool pump_during_send,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(Channel::MODE_CLIENT,
+ "unblock_client",
+ std::move(channel_handle)),
+ pump_during_send_(pump_during_send) {}
+
+ void OnAnswer(int* answer) override {
+ SendDouble(pump_during_send_, true);
+ *answer = 42;
+ Done();
+ }
+
+ bool pump_during_send_;
+};
+
+void Unblock(bool server_pump, bool client_pump, bool delete_during_send) {
+ std::vector<Worker*> workers;
+ mojo::MessagePipe pipe;
+ workers.push_back(new UnblockServer(server_pump, delete_during_send,
+ std::move(pipe.handle0)));
+ workers.push_back(new UnblockClient(client_pump, std::move(pipe.handle1)));
+ RunTest(workers);
+}
+
+// Tests that the caller unblocks to answer a sync message from the receiver.
+TEST_F(IPCSyncChannelTest, Unblock) {
+ Unblock(false, false, false);
+ Unblock(false, true, false);
+ Unblock(true, false, false);
+ Unblock(true, true, false);
+}
+
+//------------------------------------------------------------------------------
+
+#if defined(OS_ANDROID)
+#define MAYBE_ChannelDeleteDuringSend DISABLED_ChannelDeleteDuringSend
+#else
+#define MAYBE_ChannelDeleteDuringSend ChannelDeleteDuringSend
+#endif
+// Tests that the the SyncChannel object can be deleted during a Send.
+TEST_F(IPCSyncChannelTest, MAYBE_ChannelDeleteDuringSend) {
+ Unblock(false, false, true);
+ Unblock(false, true, true);
+ Unblock(true, false, true);
+ Unblock(true, true, true);
+}
+
+//------------------------------------------------------------------------------
+
+class RecursiveServer : public Worker {
+ public:
+ RecursiveServer(bool expected_send_result,
+ bool pump_first,
+ bool pump_second,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(Channel::MODE_SERVER,
+ "recursive_server",
+ std::move(channel_handle)),
+ expected_send_result_(expected_send_result),
+ pump_first_(pump_first),
+ pump_second_(pump_second) {}
+ void Run() override {
+ SendDouble(pump_first_, expected_send_result_);
+ Done();
+ }
+
+ void OnDouble(int in, int* out) override {
+ *out = in * 2;
+ SendAnswerToLife(pump_second_, expected_send_result_);
+ }
+
+ bool expected_send_result_, pump_first_, pump_second_;
+};
+
+class RecursiveClient : public Worker {
+ public:
+ RecursiveClient(bool pump_during_send,
+ bool close_channel,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(Channel::MODE_CLIENT,
+ "recursive_client",
+ std::move(channel_handle)),
+ pump_during_send_(pump_during_send),
+ close_channel_(close_channel) {}
+
+ void OnDoubleDelay(int in, Message* reply_msg) override {
+ SendDouble(pump_during_send_, !close_channel_);
+ if (close_channel_) {
+ delete reply_msg;
+ } else {
+ SyncChannelTestMsg_Double::WriteReplyParams(reply_msg, in * 2);
+ Send(reply_msg);
+ }
+ Done();
+ }
+
+ void OnAnswerDelay(Message* reply_msg) override {
+ if (close_channel_) {
+ delete reply_msg;
+ CloseChannel();
+ } else {
+ SyncChannelTestMsg_AnswerToLife::WriteReplyParams(reply_msg, 42);
+ Send(reply_msg);
+ }
+ }
+
+ bool pump_during_send_, close_channel_;
+};
+
+void Recursive(
+ bool server_pump_first, bool server_pump_second, bool client_pump) {
+ std::vector<Worker*> workers;
+ mojo::MessagePipe pipe;
+ workers.push_back(new RecursiveServer(
+ true, server_pump_first, server_pump_second, std::move(pipe.handle0)));
+ workers.push_back(
+ new RecursiveClient(client_pump, false, std::move(pipe.handle1)));
+ RunTest(workers);
+}
+
+// Tests a server calling Send while another Send is pending.
+TEST_F(IPCSyncChannelTest, Recursive) {
+ Recursive(false, false, false);
+ Recursive(false, false, true);
+ Recursive(false, true, false);
+ Recursive(false, true, true);
+ Recursive(true, false, false);
+ Recursive(true, false, true);
+ Recursive(true, true, false);
+ Recursive(true, true, true);
+}
+
+//------------------------------------------------------------------------------
+
+void RecursiveNoHang(
+ bool server_pump_first, bool server_pump_second, bool client_pump) {
+ std::vector<Worker*> workers;
+ mojo::MessagePipe pipe;
+ workers.push_back(new RecursiveServer(
+ false, server_pump_first, server_pump_second, std::move(pipe.handle0)));
+ workers.push_back(
+ new RecursiveClient(client_pump, true, std::move(pipe.handle1)));
+ RunTest(workers);
+}
+
+// Tests that if a caller makes a sync call during an existing sync call and
+// the receiver dies, neither of the Send() calls hang.
+TEST_F(IPCSyncChannelTest, RecursiveNoHang) {
+ RecursiveNoHang(false, false, false);
+ RecursiveNoHang(false, false, true);
+ RecursiveNoHang(false, true, false);
+ RecursiveNoHang(false, true, true);
+ RecursiveNoHang(true, false, false);
+ RecursiveNoHang(true, false, true);
+ RecursiveNoHang(true, true, false);
+ RecursiveNoHang(true, true, true);
+}
+
+//------------------------------------------------------------------------------
+
+class MultipleServer1 : public Worker {
+ public:
+ MultipleServer1(bool pump_during_send,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(std::move(channel_handle), Channel::MODE_SERVER),
+ pump_during_send_(pump_during_send) {}
+
+ void Run() override {
+ SendDouble(pump_during_send_, true);
+ Done();
+ }
+
+ bool pump_during_send_;
+};
+
+class MultipleClient1 : public Worker {
+ public:
+ MultipleClient1(WaitableEvent* client1_msg_received,
+ WaitableEvent* client1_can_reply,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(std::move(channel_handle), Channel::MODE_CLIENT),
+ client1_msg_received_(client1_msg_received),
+ client1_can_reply_(client1_can_reply) {}
+
+ void OnDouble(int in, int* out) override {
+ client1_msg_received_->Signal();
+ *out = in * 2;
+ client1_can_reply_->Wait();
+ Done();
+ }
+
+ private:
+ WaitableEvent *client1_msg_received_, *client1_can_reply_;
+};
+
+class MultipleServer2 : public Worker {
+ public:
+ explicit MultipleServer2(mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(std::move(channel_handle), Channel::MODE_SERVER) {}
+
+ void OnAnswer(int* result) override {
+ *result = 42;
+ Done();
+ }
+};
+
+class MultipleClient2 : public Worker {
+ public:
+ MultipleClient2(WaitableEvent* client1_msg_received,
+ WaitableEvent* client1_can_reply,
+ bool pump_during_send,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(std::move(channel_handle), Channel::MODE_CLIENT),
+ client1_msg_received_(client1_msg_received),
+ client1_can_reply_(client1_can_reply),
+ pump_during_send_(pump_during_send) {}
+
+ void Run() override {
+ client1_msg_received_->Wait();
+ SendAnswerToLife(pump_during_send_, true);
+ client1_can_reply_->Signal();
+ Done();
+ }
+
+ private:
+ WaitableEvent *client1_msg_received_, *client1_can_reply_;
+ bool pump_during_send_;
+};
+
+void Multiple(bool server_pump, bool client_pump) {
+ std::vector<Worker*> workers;
+
+ // A shared worker thread so that server1 and server2 run on one thread.
+ base::Thread worker_thread("Multiple");
+ ASSERT_TRUE(worker_thread.Start());
+
+ // Server1 sends a sync msg to client1, which blocks the reply until
+ // server2 (which runs on the same worker thread as server1) responds
+ // to a sync msg from client2.
+ WaitableEvent client1_msg_received(
+ base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ WaitableEvent client1_can_reply(
+ base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+ Worker* worker;
+
+ mojo::MessagePipe pipe1, pipe2;
+ worker = new MultipleServer2(std::move(pipe2.handle0));
+ worker->OverrideThread(&worker_thread);
+ workers.push_back(worker);
+
+ worker = new MultipleClient2(&client1_msg_received, &client1_can_reply,
+ client_pump, std::move(pipe2.handle1));
+ workers.push_back(worker);
+
+ worker = new MultipleServer1(server_pump, std::move(pipe1.handle0));
+ worker->OverrideThread(&worker_thread);
+ workers.push_back(worker);
+
+ worker = new MultipleClient1(&client1_msg_received, &client1_can_reply,
+ std::move(pipe1.handle1));
+ workers.push_back(worker);
+
+ RunTest(workers);
+}
+
+// Tests that multiple SyncObjects on the same listener thread can unblock each
+// other.
+TEST_F(IPCSyncChannelTest, Multiple) {
+ Multiple(false, false);
+ Multiple(false, true);
+ Multiple(true, false);
+ Multiple(true, true);
+}
+
+//------------------------------------------------------------------------------
+
+// This class provides server side functionality to test the case where
+// multiple sync channels are in use on the same thread on the client and
+// nested calls are issued.
+class QueuedReplyServer : public Worker {
+ public:
+ QueuedReplyServer(base::Thread* listener_thread,
+ mojo::ScopedMessagePipeHandle channel_handle,
+ const std::string& reply_text)
+ : Worker(std::move(channel_handle), Channel::MODE_SERVER),
+ reply_text_(reply_text) {
+ Worker::OverrideThread(listener_thread);
+ }
+
+ void OnNestedTestMsg(Message* reply_msg) override {
+ VLOG(1) << __FUNCTION__ << " Sending reply: " << reply_text_;
+ SyncChannelNestedTestMsg_String::WriteReplyParams(reply_msg, reply_text_);
+ Send(reply_msg);
+ Done();
+ }
+
+ private:
+ std::string reply_text_;
+};
+
+// The QueuedReplyClient class provides functionality to test the case where
+// multiple sync channels are in use on the same thread and they make nested
+// sync calls, i.e. while the first channel waits for a response it makes a
+// sync call on another channel.
+// The callstack should unwind correctly, i.e. the outermost call should
+// complete first, and so on.
+class QueuedReplyClient : public Worker {
+ public:
+ QueuedReplyClient(base::Thread* listener_thread,
+ mojo::ScopedMessagePipeHandle channel_handle,
+ const std::string& expected_text,
+ bool pump_during_send)
+ : Worker(std::move(channel_handle), Channel::MODE_CLIENT),
+ pump_during_send_(pump_during_send),
+ expected_text_(expected_text) {
+ Worker::OverrideThread(listener_thread);
+ }
+
+ void Run() override {
+ std::string response;
+ SyncMessage* msg = new SyncChannelNestedTestMsg_String(&response);
+ if (pump_during_send_)
+ msg->EnableMessagePumping();
+ bool result = Send(msg);
+ DCHECK(result);
+ DCHECK_EQ(response, expected_text_);
+
+ VLOG(1) << __FUNCTION__ << " Received reply: " << response;
+ Done();
+ }
+
+ private:
+ bool pump_during_send_;
+ std::string expected_text_;
+};
+
+void QueuedReply(bool client_pump) {
+ std::vector<Worker*> workers;
+
+ // A shared worker thread for servers
+ base::Thread server_worker_thread("QueuedReply_ServerListener");
+ ASSERT_TRUE(server_worker_thread.Start());
+
+ base::Thread client_worker_thread("QueuedReply_ClientListener");
+ ASSERT_TRUE(client_worker_thread.Start());
+
+ Worker* worker;
+
+ mojo::MessagePipe pipe1, pipe2;
+ worker = new QueuedReplyServer(&server_worker_thread,
+ std::move(pipe1.handle0), "Got first message");
+ workers.push_back(worker);
+
+ worker = new QueuedReplyServer(
+ &server_worker_thread, std::move(pipe2.handle0), "Got second message");
+ workers.push_back(worker);
+
+ worker =
+ new QueuedReplyClient(&client_worker_thread, std::move(pipe1.handle1),
+ "Got first message", client_pump);
+ workers.push_back(worker);
+
+ worker =
+ new QueuedReplyClient(&client_worker_thread, std::move(pipe2.handle1),
+ "Got second message", client_pump);
+ workers.push_back(worker);
+
+ RunTest(workers);
+}
+
+// While a blocking send is in progress, the listener thread might answer other
+// synchronous messages. This tests that if during the response to another
+// message the reply to the original messages comes, it is queued up correctly
+// and the original Send is unblocked later.
+// We also test that the send call stacks unwind correctly when the channel
+// pumps messages while waiting for a response.
+TEST_F(IPCSyncChannelTest, QueuedReply) {
+ QueuedReply(false);
+ QueuedReply(true);
+}
+
+//------------------------------------------------------------------------------
+
+class ChattyClient : public Worker {
+ public:
+ explicit ChattyClient(mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(Channel::MODE_CLIENT,
+ "chatty_client",
+ std::move(channel_handle)) {}
+
+ void OnAnswer(int* answer) override {
+ // The PostMessage limit is 10k. Send 20% more than that.
+ const int kMessageLimit = 10000;
+ const int kMessagesToSend = kMessageLimit * 120 / 100;
+ for (int i = 0; i < kMessagesToSend; ++i) {
+ if (!SendDouble(false, true))
+ break;
+ }
+ *answer = 42;
+ Done();
+ }
+};
+
+void ChattyServer(bool pump_during_send) {
+ std::vector<Worker*> workers;
+ mojo::MessagePipe pipe;
+ workers.push_back(
+ new UnblockServer(pump_during_send, false, std::move(pipe.handle0)));
+ workers.push_back(new ChattyClient(std::move(pipe.handle1)));
+ RunTest(workers);
+}
+
+#if defined(OS_ANDROID)
+// Times out.
+#define MAYBE_ChattyServer DISABLED_ChattyServer
+#else
+#define MAYBE_ChattyServer ChattyServer
+#endif
+// Tests http://b/1093251 - that sending lots of sync messages while
+// the receiver is waiting for a sync reply does not overflow the PostMessage
+// queue.
+TEST_F(IPCSyncChannelTest, MAYBE_ChattyServer) {
+ ChattyServer(false);
+}
+
+#if defined(OS_ANDROID)
+// Times out.
+#define MAYBE_ChattyServerPumpDuringSend DISABLED_ChattyServerPumpDuringSend
+#else
+#define MAYBE_ChattyServerPumpDuringSend ChattyServerPumpDuringSend
+#endif
+TEST_F(IPCSyncChannelTest, MAYBE_ChattyServerPumpDuringSend) {
+ ChattyServer(true);
+}
+
+//------------------------------------------------------------------------------
+
+void NestedCallback(Worker* server) {
+ // Sleep a bit so that we wake up after the reply has been received.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(250));
+ server->SendAnswerToLife(true, true);
+}
+
+bool timeout_occurred = false;
+
+void TimeoutCallback() {
+ timeout_occurred = true;
+}
+
+class DoneEventRaceServer : public Worker {
+ public:
+ explicit DoneEventRaceServer(mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(Channel::MODE_SERVER,
+ "done_event_race_server",
+ std::move(channel_handle)) {}
+
+ void Run() override {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&NestedCallback, base::Unretained(this)));
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, base::Bind(&TimeoutCallback),
+ base::TimeDelta::FromSeconds(9));
+ // Even though we have a timeout on the Send, it will succeed since for this
+ // bug, the reply message comes back and is deserialized, however the done
+ // event wasn't set. So we indirectly use the timeout task to notice if a
+ // timeout occurred.
+ SendAnswerToLife(true, true);
+ DCHECK(!timeout_occurred);
+ Done();
+ }
+};
+
+#if defined(OS_ANDROID)
+#define MAYBE_DoneEventRace DISABLED_DoneEventRace
+#else
+#define MAYBE_DoneEventRace DoneEventRace
+#endif
+// Tests http://b/1474092 - that if after the done_event is set but before
+// OnObjectSignaled is called another message is sent out, then after its
+// reply comes back OnObjectSignaled will be called for the first message.
+TEST_F(IPCSyncChannelTest, MAYBE_DoneEventRace) {
+ std::vector<Worker*> workers;
+ mojo::MessagePipe pipe;
+ workers.push_back(new DoneEventRaceServer(std::move(pipe.handle0)));
+ workers.push_back(new SimpleClient(std::move(pipe.handle1)));
+ RunTest(workers);
+}
+
+//------------------------------------------------------------------------------
+
+class TestSyncMessageFilter : public SyncMessageFilter {
+ public:
+ TestSyncMessageFilter(
+ base::WaitableEvent* shutdown_event,
+ Worker* worker,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner)
+ : SyncMessageFilter(shutdown_event),
+ worker_(worker),
+ task_runner_(task_runner) {}
+
+ void OnFilterAdded(Channel* channel) override {
+ SyncMessageFilter::OnFilterAdded(channel);
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&TestSyncMessageFilter::SendMessageOnHelperThread, this));
+ }
+
+ void SendMessageOnHelperThread() {
+ int answer = 0;
+ bool result = Send(new SyncChannelTestMsg_AnswerToLife(&answer));
+ DCHECK(result);
+ DCHECK_EQ(answer, 42);
+
+ worker_->Done();
+ }
+
+ private:
+ ~TestSyncMessageFilter() override = default;
+
+ Worker* worker_;
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+};
+
+class SyncMessageFilterServer : public Worker {
+ public:
+ explicit SyncMessageFilterServer(mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(Channel::MODE_SERVER,
+ "sync_message_filter_server",
+ std::move(channel_handle)),
+ thread_("helper_thread") {
+ base::Thread::Options options;
+ options.message_loop_type = base::MessageLoop::TYPE_DEFAULT;
+ thread_.StartWithOptions(options);
+ filter_ = new TestSyncMessageFilter(shutdown_event(), this,
+ thread_.task_runner());
+ }
+
+ void Run() override {
+ channel()->AddFilter(filter_.get());
+ }
+
+ base::Thread thread_;
+ scoped_refptr<TestSyncMessageFilter> filter_;
+};
+
+// This class provides functionality to test the case that a Send on the sync
+// channel does not crash after the channel has been closed.
+class ServerSendAfterClose : public Worker {
+ public:
+ explicit ServerSendAfterClose(mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(Channel::MODE_SERVER,
+ "simpler_server",
+ std::move(channel_handle)),
+ send_result_(true) {}
+
+ bool SendDummy() {
+ ListenerThread()->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(&ServerSendAfterClose::Send),
+ base::Unretained(this), new SyncChannelTestMsg_NoArgs));
+ return true;
+ }
+
+ bool send_result() const {
+ return send_result_;
+ }
+
+ private:
+ void Run() override {
+ CloseChannel();
+ Done();
+ }
+
+ bool Send(Message* msg) override {
+ send_result_ = Worker::Send(msg);
+ Done();
+ return send_result_;
+ }
+
+ bool send_result_;
+};
+
+// Tests basic synchronous call
+TEST_F(IPCSyncChannelTest, SyncMessageFilter) {
+ std::vector<Worker*> workers;
+ mojo::MessagePipe pipe;
+ workers.push_back(new SyncMessageFilterServer(std::move(pipe.handle0)));
+ workers.push_back(new SimpleClient(std::move(pipe.handle1)));
+ RunTest(workers);
+}
+
+// Test the case when the channel is closed and a Send is attempted after that.
+TEST_F(IPCSyncChannelTest, SendAfterClose) {
+ mojo::MessagePipe pipe;
+ ServerSendAfterClose server(std::move(pipe.handle0));
+ server.Start();
+
+ server.done_event()->Wait();
+ server.done_event()->Reset();
+
+ server.SendDummy();
+ server.done_event()->Wait();
+
+ EXPECT_FALSE(server.send_result());
+
+ server.Shutdown();
+}
+
+//------------------------------------------------------------------------------
+
+class RestrictedDispatchServer : public Worker {
+ public:
+ RestrictedDispatchServer(WaitableEvent* sent_ping_event,
+ WaitableEvent* wait_event,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(std::move(channel_handle), Channel::MODE_SERVER),
+ sent_ping_event_(sent_ping_event),
+ wait_event_(wait_event) {}
+
+ void OnDoPing(int ping) {
+ // Send an asynchronous message that unblocks the caller.
+ Message* msg = new SyncChannelTestMsg_Ping(ping);
+ msg->set_unblock(true);
+ Send(msg);
+ // Signal the event after the message has been sent on the channel, on the
+ // IPC thread.
+ ipc_thread().task_runner()->PostTask(
+ FROM_HERE, base::Bind(&RestrictedDispatchServer::OnPingSent,
+ base::Unretained(this)));
+ }
+
+ void OnPingTTL(int ping, int* out) {
+ *out = ping;
+ wait_event_->Wait();
+ }
+
+ base::Thread* ListenerThread() { return Worker::ListenerThread(); }
+
+ private:
+ bool OnMessageReceived(const Message& message) override {
+ IPC_BEGIN_MESSAGE_MAP(RestrictedDispatchServer, message)
+ IPC_MESSAGE_HANDLER(SyncChannelTestMsg_NoArgs, OnNoArgs)
+ IPC_MESSAGE_HANDLER(SyncChannelTestMsg_PingTTL, OnPingTTL)
+ IPC_MESSAGE_HANDLER(SyncChannelTestMsg_Done, Done)
+ IPC_END_MESSAGE_MAP()
+ return true;
+ }
+
+ void OnPingSent() {
+ sent_ping_event_->Signal();
+ }
+
+ void OnNoArgs() { }
+ WaitableEvent* sent_ping_event_;
+ WaitableEvent* wait_event_;
+};
+
+class NonRestrictedDispatchServer : public Worker {
+ public:
+ NonRestrictedDispatchServer(WaitableEvent* signal_event,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(std::move(channel_handle), Channel::MODE_SERVER),
+ signal_event_(signal_event) {}
+
+ base::Thread* ListenerThread() { return Worker::ListenerThread(); }
+
+ void OnDoPingTTL(int ping) {
+ int value = 0;
+ Send(new SyncChannelTestMsg_PingTTL(ping, &value));
+ signal_event_->Signal();
+ }
+
+ private:
+ bool OnMessageReceived(const Message& message) override {
+ IPC_BEGIN_MESSAGE_MAP(NonRestrictedDispatchServer, message)
+ IPC_MESSAGE_HANDLER(SyncChannelTestMsg_NoArgs, OnNoArgs)
+ IPC_MESSAGE_HANDLER(SyncChannelTestMsg_Done, Done)
+ IPC_END_MESSAGE_MAP()
+ return true;
+ }
+
+ void OnNoArgs() { }
+ WaitableEvent* signal_event_;
+};
+
+class RestrictedDispatchClient : public Worker {
+ public:
+ RestrictedDispatchClient(
+ WaitableEvent* sent_ping_event,
+ RestrictedDispatchServer* server,
+ NonRestrictedDispatchServer* server2,
+ int* success,
+ mojo::ScopedMessagePipeHandle restricted_channel_handle,
+ mojo::ScopedMessagePipeHandle non_restricted_channel_handle)
+ : Worker(std::move(restricted_channel_handle), Channel::MODE_CLIENT),
+ ping_(0),
+ server_(server),
+ server2_(server2),
+ success_(success),
+ sent_ping_event_(sent_ping_event),
+ non_restricted_channel_handle_(
+ std::move(non_restricted_channel_handle)) {}
+
+ void Run() override {
+ // Incoming messages from our channel should only be dispatched when we
+ // send a message on that same channel.
+ channel()->SetRestrictDispatchChannelGroup(1);
+
+ server_->ListenerThread()->task_runner()->PostTask(
+ FROM_HERE, base::Bind(&RestrictedDispatchServer::OnDoPing,
+ base::Unretained(server_), 1));
+ sent_ping_event_->Wait();
+ Send(new SyncChannelTestMsg_NoArgs);
+ if (ping_ == 1)
+ ++*success_;
+ else
+ LOG(ERROR) << "Send failed to dispatch incoming message on same channel";
+
+ non_restricted_channel_ = SyncChannel::Create(
+ non_restricted_channel_handle_.release(), IPC::Channel::MODE_CLIENT,
+ this, ipc_thread().task_runner(), base::ThreadTaskRunnerHandle::Get(),
+ true, shutdown_event());
+
+ server_->ListenerThread()->task_runner()->PostTask(
+ FROM_HERE, base::Bind(&RestrictedDispatchServer::OnDoPing,
+ base::Unretained(server_), 2));
+ sent_ping_event_->Wait();
+ // Check that the incoming message is *not* dispatched when sending on the
+ // non restricted channel.
+ // TODO(piman): there is a possibility of a false positive race condition
+ // here, if the message that was posted on the server-side end of the pipe
+ // is not visible yet on the client side, but I don't know how to solve this
+ // without hooking into the internals of SyncChannel. I haven't seen it in
+ // practice (i.e. not setting SetRestrictDispatchToSameChannel does cause
+ // the following to fail).
+ non_restricted_channel_->Send(new SyncChannelTestMsg_NoArgs);
+ if (ping_ == 1)
+ ++*success_;
+ else
+ LOG(ERROR) << "Send dispatched message from restricted channel";
+
+ Send(new SyncChannelTestMsg_NoArgs);
+ if (ping_ == 2)
+ ++*success_;
+ else
+ LOG(ERROR) << "Send failed to dispatch incoming message on same channel";
+
+ // Check that the incoming message on the non-restricted channel is
+ // dispatched when sending on the restricted channel.
+ server2_->ListenerThread()->task_runner()->PostTask(
+ FROM_HERE, base::Bind(&NonRestrictedDispatchServer::OnDoPingTTL,
+ base::Unretained(server2_), 3));
+ int value = 0;
+ Send(new SyncChannelTestMsg_PingTTL(4, &value));
+ if (ping_ == 3 && value == 4)
+ ++*success_;
+ else
+ LOG(ERROR) << "Send failed to dispatch message from unrestricted channel";
+
+ non_restricted_channel_->Send(new SyncChannelTestMsg_Done);
+ non_restricted_channel_.reset();
+ Send(new SyncChannelTestMsg_Done);
+ Done();
+ }
+
+ private:
+ bool OnMessageReceived(const Message& message) override {
+ IPC_BEGIN_MESSAGE_MAP(RestrictedDispatchClient, message)
+ IPC_MESSAGE_HANDLER(SyncChannelTestMsg_Ping, OnPing)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(SyncChannelTestMsg_PingTTL, OnPingTTL)
+ IPC_END_MESSAGE_MAP()
+ return true;
+ }
+
+ void OnPing(int ping) {
+ ping_ = ping;
+ }
+
+ void OnPingTTL(int ping, IPC::Message* reply) {
+ ping_ = ping;
+ // This message comes from the NonRestrictedDispatchServer, we have to send
+ // the reply back manually.
+ SyncChannelTestMsg_PingTTL::WriteReplyParams(reply, ping);
+ non_restricted_channel_->Send(reply);
+ }
+
+ int ping_;
+ RestrictedDispatchServer* server_;
+ NonRestrictedDispatchServer* server2_;
+ int* success_;
+ WaitableEvent* sent_ping_event_;
+ std::unique_ptr<SyncChannel> non_restricted_channel_;
+ mojo::ScopedMessagePipeHandle non_restricted_channel_handle_;
+};
+
+TEST_F(IPCSyncChannelTest, RestrictedDispatch) {
+ WaitableEvent sent_ping_event(
+ base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ WaitableEvent wait_event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ mojo::MessagePipe restricted_pipe, non_restricted_pipe;
+ RestrictedDispatchServer* server = new RestrictedDispatchServer(
+ &sent_ping_event, &wait_event, std::move(restricted_pipe.handle0));
+ NonRestrictedDispatchServer* server2 = new NonRestrictedDispatchServer(
+ &wait_event, std::move(non_restricted_pipe.handle0));
+
+ int success = 0;
+ std::vector<Worker*> workers;
+ workers.push_back(server);
+ workers.push_back(server2);
+ workers.push_back(
+ new RestrictedDispatchClient(&sent_ping_event, server, server2, &success,
+ std::move(restricted_pipe.handle1),
+ std::move(non_restricted_pipe.handle1)));
+ RunTest(workers);
+ EXPECT_EQ(4, success);
+}
+
+//------------------------------------------------------------------------------
+
+// This test case inspired by crbug.com/108491
+// We create two servers that use the same ListenerThread but have
+// SetRestrictDispatchToSameChannel set to true.
+// We create clients, then use some specific WaitableEvent wait/signalling to
+// ensure that messages get dispatched in a way that causes a deadlock due to
+// a nested dispatch and an eligible message in a higher-level dispatch's
+// delayed_queue. Specifically, we start with client1 about so send an
+// unblocking message to server1, while the shared listener thread for the
+// servers server1 and server2 is about to send a non-unblocking message to
+// client1. At the same time, client2 will be about to send an unblocking
+// message to server2. Server1 will handle the client1->server1 message by
+// telling server2 to send a non-unblocking message to client2.
+// What should happen is that the send to server2 should find the pending,
+// same-context client2->server2 message to dispatch, causing client2 to
+// unblock then handle the server2->client2 message, so that the shared
+// servers' listener thread can then respond to the client1->server1 message.
+// Then client1 can handle the non-unblocking server1->client1 message.
+// The old code would end up in a state where the server2->client2 message is
+// sent, but the client2->server2 message (which is eligible for dispatch, and
+// which is what client2 is waiting for) is stashed in a local delayed_queue
+// that has server1's channel context, causing a deadlock.
+// WaitableEvents in the events array are used to:
+// event 0: indicate to client1 that server listener is in OnDoServerTask
+// event 1: indicate to client1 that client2 listener is in OnDoClient2Task
+// event 2: indicate to server1 that client2 listener is in OnDoClient2Task
+// event 3: indicate to client2 that server listener is in OnDoServerTask
+
+class RestrictedDispatchDeadlockServer : public Worker {
+ public:
+ RestrictedDispatchDeadlockServer(int server_num,
+ WaitableEvent* server_ready_event,
+ WaitableEvent** events,
+ RestrictedDispatchDeadlockServer* peer,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(std::move(channel_handle), Channel::MODE_SERVER),
+ server_num_(server_num),
+ server_ready_event_(server_ready_event),
+ events_(events),
+ peer_(peer) {}
+
+ void OnDoServerTask() {
+ events_[3]->Signal();
+ events_[2]->Wait();
+ events_[0]->Signal();
+ SendMessageToClient();
+ }
+
+ void Run() override {
+ channel()->SetRestrictDispatchChannelGroup(1);
+ server_ready_event_->Signal();
+ }
+
+ base::Thread* ListenerThread() { return Worker::ListenerThread(); }
+
+ private:
+ bool OnMessageReceived(const Message& message) override {
+ IPC_BEGIN_MESSAGE_MAP(RestrictedDispatchDeadlockServer, message)
+ IPC_MESSAGE_HANDLER(SyncChannelTestMsg_NoArgs, OnNoArgs)
+ IPC_MESSAGE_HANDLER(SyncChannelTestMsg_Done, Done)
+ IPC_END_MESSAGE_MAP()
+ return true;
+ }
+
+ void OnNoArgs() {
+ if (server_num_ == 1) {
+ DCHECK(peer_ != NULL);
+ peer_->SendMessageToClient();
+ }
+ }
+
+ void SendMessageToClient() {
+ Message* msg = new SyncChannelTestMsg_NoArgs;
+ msg->set_unblock(false);
+ DCHECK(!msg->should_unblock());
+ Send(msg);
+ }
+
+ int server_num_;
+ WaitableEvent* server_ready_event_;
+ WaitableEvent** events_;
+ RestrictedDispatchDeadlockServer* peer_;
+};
+
+class RestrictedDispatchDeadlockClient2 : public Worker {
+ public:
+ RestrictedDispatchDeadlockClient2(
+ RestrictedDispatchDeadlockServer* server,
+ WaitableEvent* server_ready_event,
+ WaitableEvent** events,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(std::move(channel_handle), Channel::MODE_CLIENT),
+ server_ready_event_(server_ready_event),
+ events_(events),
+ received_msg_(false),
+ received_noarg_reply_(false),
+ done_issued_(false) {}
+
+ void Run() override {
+ server_ready_event_->Wait();
+ }
+
+ void OnDoClient2Task() {
+ events_[3]->Wait();
+ events_[1]->Signal();
+ events_[2]->Signal();
+ DCHECK(received_msg_ == false);
+
+ Message* message = new SyncChannelTestMsg_NoArgs;
+ message->set_unblock(true);
+ Send(message);
+ received_noarg_reply_ = true;
+ }
+
+ base::Thread* ListenerThread() { return Worker::ListenerThread(); }
+ private:
+ bool OnMessageReceived(const Message& message) override {
+ IPC_BEGIN_MESSAGE_MAP(RestrictedDispatchDeadlockClient2, message)
+ IPC_MESSAGE_HANDLER(SyncChannelTestMsg_NoArgs, OnNoArgs)
+ IPC_END_MESSAGE_MAP()
+ return true;
+ }
+
+ void OnNoArgs() {
+ received_msg_ = true;
+ PossiblyDone();
+ }
+
+ void PossiblyDone() {
+ if (received_noarg_reply_ && received_msg_) {
+ DCHECK(done_issued_ == false);
+ done_issued_ = true;
+ Send(new SyncChannelTestMsg_Done);
+ Done();
+ }
+ }
+
+ WaitableEvent* server_ready_event_;
+ WaitableEvent** events_;
+ bool received_msg_;
+ bool received_noarg_reply_;
+ bool done_issued_;
+};
+
+class RestrictedDispatchDeadlockClient1 : public Worker {
+ public:
+ RestrictedDispatchDeadlockClient1(
+ RestrictedDispatchDeadlockServer* server,
+ RestrictedDispatchDeadlockClient2* peer,
+ WaitableEvent* server_ready_event,
+ WaitableEvent** events,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(std::move(channel_handle), Channel::MODE_CLIENT),
+ server_(server),
+ peer_(peer),
+ server_ready_event_(server_ready_event),
+ events_(events),
+ received_msg_(false),
+ received_noarg_reply_(false),
+ done_issued_(false) {}
+
+ void Run() override {
+ server_ready_event_->Wait();
+ server_->ListenerThread()->task_runner()->PostTask(
+ FROM_HERE, base::Bind(&RestrictedDispatchDeadlockServer::OnDoServerTask,
+ base::Unretained(server_)));
+ peer_->ListenerThread()->task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&RestrictedDispatchDeadlockClient2::OnDoClient2Task,
+ base::Unretained(peer_)));
+ events_[0]->Wait();
+ events_[1]->Wait();
+ DCHECK(received_msg_ == false);
+
+ Message* message = new SyncChannelTestMsg_NoArgs;
+ message->set_unblock(true);
+ Send(message);
+ received_noarg_reply_ = true;
+ PossiblyDone();
+ }
+
+ private:
+ bool OnMessageReceived(const Message& message) override {
+ IPC_BEGIN_MESSAGE_MAP(RestrictedDispatchDeadlockClient1, message)
+ IPC_MESSAGE_HANDLER(SyncChannelTestMsg_NoArgs, OnNoArgs)
+ IPC_END_MESSAGE_MAP()
+ return true;
+ }
+
+ void OnNoArgs() {
+ received_msg_ = true;
+ PossiblyDone();
+ }
+
+ void PossiblyDone() {
+ if (received_noarg_reply_ && received_msg_) {
+ DCHECK(done_issued_ == false);
+ done_issued_ = true;
+ Send(new SyncChannelTestMsg_Done);
+ Done();
+ }
+ }
+
+ RestrictedDispatchDeadlockServer* server_;
+ RestrictedDispatchDeadlockClient2* peer_;
+ WaitableEvent* server_ready_event_;
+ WaitableEvent** events_;
+ bool received_msg_;
+ bool received_noarg_reply_;
+ bool done_issued_;
+};
+
+TEST_F(IPCSyncChannelTest, RestrictedDispatchDeadlock) {
+ std::vector<Worker*> workers;
+
+ // A shared worker thread so that server1 and server2 run on one thread.
+ base::Thread worker_thread("RestrictedDispatchDeadlock");
+ ASSERT_TRUE(worker_thread.Start());
+
+ WaitableEvent server1_ready(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ WaitableEvent server2_ready(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+ WaitableEvent event0(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ WaitableEvent event1(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ WaitableEvent event2(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ WaitableEvent event3(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ WaitableEvent* events[4] = {&event0, &event1, &event2, &event3};
+
+ RestrictedDispatchDeadlockServer* server1;
+ RestrictedDispatchDeadlockServer* server2;
+ RestrictedDispatchDeadlockClient1* client1;
+ RestrictedDispatchDeadlockClient2* client2;
+
+ mojo::MessagePipe pipe1, pipe2;
+ server2 = new RestrictedDispatchDeadlockServer(
+ 2, &server2_ready, events, NULL, std::move(pipe2.handle0));
+ server2->OverrideThread(&worker_thread);
+ workers.push_back(server2);
+
+ client2 = new RestrictedDispatchDeadlockClient2(
+ server2, &server2_ready, events, std::move(pipe2.handle1));
+ workers.push_back(client2);
+
+ server1 = new RestrictedDispatchDeadlockServer(
+ 1, &server1_ready, events, server2, std::move(pipe1.handle0));
+ server1->OverrideThread(&worker_thread);
+ workers.push_back(server1);
+
+ client1 = new RestrictedDispatchDeadlockClient1(
+ server1, client2, &server1_ready, events, std::move(pipe1.handle1));
+ workers.push_back(client1);
+
+ RunTest(workers);
+}
+
+//------------------------------------------------------------------------------
+
+// This test case inspired by crbug.com/120530
+// We create 4 workers that pipe to each other W1->W2->W3->W4->W1 then we send a
+// message that recurses through 3, 4 or 5 steps to make sure, say, W1 can
+// re-enter when called from W4 while it's sending a message to W2.
+// The first worker drives the whole test so it must be treated specially.
+
+class RestrictedDispatchPipeWorker : public Worker {
+ public:
+ RestrictedDispatchPipeWorker(mojo::ScopedMessagePipeHandle channel_handle1,
+ WaitableEvent* event1,
+ mojo::ScopedMessagePipeHandle channel_handle2,
+ WaitableEvent* event2,
+ int group,
+ int* success)
+ : Worker(std::move(channel_handle1), Channel::MODE_SERVER),
+ event1_(event1),
+ event2_(event2),
+ other_channel_handle_(std::move(channel_handle2)),
+ group_(group),
+ success_(success) {}
+
+ void OnPingTTL(int ping, int* ret) {
+ *ret = 0;
+ if (!ping)
+ return;
+ other_channel_->Send(new SyncChannelTestMsg_PingTTL(ping - 1, ret));
+ ++*ret;
+ }
+
+ void OnDone() {
+ if (is_first())
+ return;
+ other_channel_->Send(new SyncChannelTestMsg_Done);
+ other_channel_.reset();
+ Done();
+ }
+
+ void Run() override {
+ channel()->SetRestrictDispatchChannelGroup(group_);
+ if (is_first())
+ event1_->Signal();
+ event2_->Wait();
+ other_channel_ = SyncChannel::Create(
+ other_channel_handle_.release(), IPC::Channel::MODE_CLIENT, this,
+ ipc_thread().task_runner(), base::ThreadTaskRunnerHandle::Get(), true,
+ shutdown_event());
+ other_channel_->SetRestrictDispatchChannelGroup(group_);
+ if (!is_first()) {
+ event1_->Signal();
+ return;
+ }
+ *success_ = 0;
+ int value = 0;
+ OnPingTTL(3, &value);
+ *success_ += (value == 3);
+ OnPingTTL(4, &value);
+ *success_ += (value == 4);
+ OnPingTTL(5, &value);
+ *success_ += (value == 5);
+ other_channel_->Send(new SyncChannelTestMsg_Done);
+ other_channel_.reset();
+ Done();
+ }
+
+ bool is_first() { return !!success_; }
+
+ private:
+ bool OnMessageReceived(const Message& message) override {
+ IPC_BEGIN_MESSAGE_MAP(RestrictedDispatchPipeWorker, message)
+ IPC_MESSAGE_HANDLER(SyncChannelTestMsg_PingTTL, OnPingTTL)
+ IPC_MESSAGE_HANDLER(SyncChannelTestMsg_Done, OnDone)
+ IPC_END_MESSAGE_MAP()
+ return true;
+ }
+
+ std::unique_ptr<SyncChannel> other_channel_;
+ WaitableEvent* event1_;
+ WaitableEvent* event2_;
+ mojo::ScopedMessagePipeHandle other_channel_handle_;
+ int group_;
+ int* success_;
+};
+
+#if defined(OS_ANDROID)
+#define MAYBE_RestrictedDispatch4WayDeadlock \
+ DISABLED_RestrictedDispatch4WayDeadlock
+#else
+#define MAYBE_RestrictedDispatch4WayDeadlock RestrictedDispatch4WayDeadlock
+#endif
+TEST_F(IPCSyncChannelTest, MAYBE_RestrictedDispatch4WayDeadlock) {
+ int success = 0;
+ std::vector<Worker*> workers;
+ WaitableEvent event0(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ WaitableEvent event1(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ WaitableEvent event2(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ WaitableEvent event3(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ mojo::MessagePipe pipe0, pipe1, pipe2, pipe3;
+ workers.push_back(new RestrictedDispatchPipeWorker(
+ std::move(pipe0.handle0), &event0, std::move(pipe1.handle1), &event1, 1,
+ &success));
+ workers.push_back(new RestrictedDispatchPipeWorker(
+ std::move(pipe1.handle0), &event1, std::move(pipe2.handle1), &event2, 2,
+ NULL));
+ workers.push_back(new RestrictedDispatchPipeWorker(
+ std::move(pipe2.handle0), &event2, std::move(pipe3.handle1), &event3, 3,
+ NULL));
+ workers.push_back(new RestrictedDispatchPipeWorker(
+ std::move(pipe3.handle0), &event3, std::move(pipe0.handle1), &event0, 4,
+ NULL));
+ RunTest(workers);
+ EXPECT_EQ(3, success);
+}
+
+//------------------------------------------------------------------------------
+
+// This test case inspired by crbug.com/122443
+// We want to make sure a reply message with the unblock flag set correctly
+// behaves as a reply, not a regular message.
+// We have 3 workers. Server1 will send a message to Server2 (which will block),
+// during which it will dispatch a message comming from Client, at which point
+// it will send another message to Server2. While sending that second message it
+// will receive a reply from Server1 with the unblock flag.
+
+class ReentrantReplyServer1 : public Worker {
+ public:
+ ReentrantReplyServer1(WaitableEvent* server_ready,
+ mojo::ScopedMessagePipeHandle channel_handle1,
+ mojo::ScopedMessagePipeHandle channel_handle2)
+ : Worker(std::move(channel_handle1), Channel::MODE_SERVER),
+ server_ready_(server_ready),
+ other_channel_handle_(std::move(channel_handle2)) {}
+
+ void Run() override {
+ server2_channel_ = SyncChannel::Create(
+ other_channel_handle_.release(), IPC::Channel::MODE_CLIENT, this,
+ ipc_thread().task_runner(), base::ThreadTaskRunnerHandle::Get(), true,
+ shutdown_event());
+ server_ready_->Signal();
+ Message* msg = new SyncChannelTestMsg_Reentrant1();
+ server2_channel_->Send(msg);
+ server2_channel_.reset();
+ Done();
+ }
+
+ private:
+ bool OnMessageReceived(const Message& message) override {
+ IPC_BEGIN_MESSAGE_MAP(ReentrantReplyServer1, message)
+ IPC_MESSAGE_HANDLER(SyncChannelTestMsg_Reentrant2, OnReentrant2)
+ IPC_REPLY_HANDLER(OnReply)
+ IPC_END_MESSAGE_MAP()
+ return true;
+ }
+
+ void OnReentrant2() {
+ Message* msg = new SyncChannelTestMsg_Reentrant3();
+ server2_channel_->Send(msg);
+ }
+
+ void OnReply(const Message& message) {
+ // If we get here, the Send() will never receive the reply (thus would
+ // hang), so abort instead.
+ LOG(FATAL) << "Reply message was dispatched";
+ }
+
+ WaitableEvent* server_ready_;
+ std::unique_ptr<SyncChannel> server2_channel_;
+ mojo::ScopedMessagePipeHandle other_channel_handle_;
+};
+
+class ReentrantReplyServer2 : public Worker {
+ public:
+ ReentrantReplyServer2(mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(std::move(channel_handle), Channel::MODE_SERVER), reply_(NULL) {}
+
+ private:
+ bool OnMessageReceived(const Message& message) override {
+ IPC_BEGIN_MESSAGE_MAP(ReentrantReplyServer2, message)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(
+ SyncChannelTestMsg_Reentrant1, OnReentrant1)
+ IPC_MESSAGE_HANDLER(SyncChannelTestMsg_Reentrant3, OnReentrant3)
+ IPC_END_MESSAGE_MAP()
+ return true;
+ }
+
+ void OnReentrant1(Message* reply) {
+ DCHECK(!reply_);
+ reply_ = reply;
+ }
+
+ void OnReentrant3() {
+ DCHECK(reply_);
+ Message* reply = reply_;
+ reply_ = NULL;
+ reply->set_unblock(true);
+ Send(reply);
+ Done();
+ }
+
+ Message* reply_;
+};
+
+class ReentrantReplyClient : public Worker {
+ public:
+ ReentrantReplyClient(WaitableEvent* server_ready,
+ mojo::ScopedMessagePipeHandle channel_handle)
+ : Worker(std::move(channel_handle), Channel::MODE_CLIENT),
+ server_ready_(server_ready) {}
+
+ void Run() override {
+ server_ready_->Wait();
+ Send(new SyncChannelTestMsg_Reentrant2());
+ Done();
+ }
+
+ private:
+ WaitableEvent* server_ready_;
+};
+
+TEST_F(IPCSyncChannelTest, ReentrantReply) {
+ std::vector<Worker*> workers;
+ WaitableEvent server_ready(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ mojo::MessagePipe pipe1, pipe2;
+ workers.push_back(new ReentrantReplyServer2(std::move(pipe2.handle0)));
+ workers.push_back(new ReentrantReplyServer1(
+ &server_ready, std::move(pipe1.handle0), std::move(pipe2.handle1)));
+ workers.push_back(
+ new ReentrantReplyClient(&server_ready, std::move(pipe1.handle1)));
+ RunTest(workers);
+}
+
+} // namespace
+} // namespace IPC
diff --git a/ipc/ipc_sync_message.cc b/ipc/ipc_sync_message.cc
new file mode 100644
index 0000000000..d6a59c8192
--- /dev/null
+++ b/ipc/ipc_sync_message.cc
@@ -0,0 +1,116 @@
+// 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_sync_message.h"
+
+#include <stdint.h>
+
+#include "base/atomic_sequence_num.h"
+#include "base/logging.h"
+#include "build/build_config.h"
+
+namespace {
+
+base::AtomicSequenceNumber g_next_id;
+
+} // namespace
+
+namespace IPC {
+
+#define kSyncMessageHeaderSize 4
+
+SyncMessage::SyncMessage(int32_t routing_id,
+ uint32_t type,
+ PriorityValue priority,
+ MessageReplyDeserializer* deserializer)
+ : Message(routing_id, type, priority),
+ deserializer_(deserializer) {
+ set_sync();
+ set_unblock(true);
+
+ // Add synchronous message data before the message payload.
+ SyncHeader header;
+ header.message_id = g_next_id.GetNext();
+ WriteSyncHeader(this, header);
+}
+
+SyncMessage::~SyncMessage() = default;
+
+MessageReplyDeserializer* SyncMessage::GetReplyDeserializer() {
+ DCHECK(deserializer_.get());
+ return deserializer_.release();
+}
+
+bool SyncMessage::IsMessageReplyTo(const Message& msg, int request_id) {
+ if (!msg.is_reply())
+ return false;
+
+ return GetMessageId(msg) == request_id;
+}
+
+base::PickleIterator SyncMessage::GetDataIterator(const Message* msg) {
+ base::PickleIterator iter(*msg);
+ if (!iter.SkipBytes(kSyncMessageHeaderSize))
+ return base::PickleIterator();
+ else
+ return iter;
+}
+
+int SyncMessage::GetMessageId(const Message& msg) {
+ if (!msg.is_sync() && !msg.is_reply())
+ return 0;
+
+ SyncHeader header;
+ if (!ReadSyncHeader(msg, &header))
+ return 0;
+
+ return header.message_id;
+}
+
+Message* SyncMessage::GenerateReply(const Message* msg) {
+ DCHECK(msg->is_sync());
+
+ Message* reply = new Message(msg->routing_id(), IPC_REPLY_ID,
+ msg->priority());
+ reply->set_reply();
+
+ SyncHeader header;
+
+ // use the same message id, but this time reply bit is set
+ header.message_id = GetMessageId(*msg);
+ WriteSyncHeader(reply, header);
+
+ return reply;
+}
+
+bool SyncMessage::ReadSyncHeader(const Message& msg, SyncHeader* header) {
+ DCHECK(msg.is_sync() || msg.is_reply());
+
+ base::PickleIterator iter(msg);
+ bool result = iter.ReadInt(&header->message_id);
+ if (!result) {
+ NOTREACHED();
+ return false;
+ }
+
+ return true;
+}
+
+bool SyncMessage::WriteSyncHeader(Message* msg, const SyncHeader& header) {
+ DCHECK(msg->is_sync() || msg->is_reply());
+ DCHECK(msg->payload_size() == 0);
+ msg->WriteInt(header.message_id);
+
+ // Note: if you add anything here, you need to update kSyncMessageHeaderSize.
+ DCHECK(kSyncMessageHeaderSize == msg->payload_size());
+
+ return true;
+}
+
+
+bool MessageReplyDeserializer::SerializeOutputParameters(const Message& msg) {
+ return SerializeOutputParameters(msg, SyncMessage::GetDataIterator(&msg));
+}
+
+} // namespace IPC
diff --git a/ipc/ipc_sync_message.h b/ipc/ipc_sync_message.h
index 7f0555134a..cfac2ded12 100644
--- a/ipc/ipc_sync_message.h
+++ b/ipc/ipc_sync_message.h
@@ -8,7 +8,7 @@
#include <stdint.h>
#if defined(OS_WIN)
-#include <windows.h>
+#include "base/win/windows_types.h"
#endif
#include <memory>
@@ -16,6 +16,7 @@
#include "build/build_config.h"
#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_support_export.h"
namespace base {
class WaitableEvent;
@@ -25,7 +26,7 @@ namespace IPC {
class MessageReplyDeserializer;
-class IPC_EXPORT SyncMessage : public Message {
+class IPC_MESSAGE_SUPPORT_EXPORT SyncMessage : public Message {
public:
SyncMessage(int32_t routing_id,
uint32_t type,
@@ -79,7 +80,7 @@ class IPC_EXPORT SyncMessage : public Message {
};
// Used to deserialize parameters from a reply to a synchronous message
-class IPC_EXPORT MessageReplyDeserializer {
+class IPC_MESSAGE_SUPPORT_EXPORT MessageReplyDeserializer {
public:
virtual ~MessageReplyDeserializer() {}
bool SerializeOutputParameters(const Message& msg);
diff --git a/ipc/ipc_sync_message_filter.cc b/ipc/ipc_sync_message_filter.cc
new file mode 100644
index 0000000000..d8cac7b2b8
--- /dev/null
+++ b/ipc/ipc_sync_message_filter.cc
@@ -0,0 +1,198 @@
+// 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_sync_message_filter.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "ipc/ipc_channel.h"
+#include "ipc/ipc_sync_message.h"
+#include "mojo/public/cpp/bindings/sync_handle_registry.h"
+
+namespace IPC {
+
+namespace {
+
+// A generic callback used when watching handles synchronously. Sets |*signal|
+// to true.
+void OnEventReady(bool* signal) {
+ *signal = true;
+}
+
+} // namespace
+
+bool SyncMessageFilter::Send(Message* message) {
+ if (!message->is_sync()) {
+ {
+ base::AutoLock auto_lock(lock_);
+ if (!io_task_runner_.get()) {
+ pending_messages_.emplace_back(base::WrapUnique(message));
+ return true;
+ }
+ }
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&SyncMessageFilter::SendOnIOThread, this, message));
+ return true;
+ }
+
+ base::WaitableEvent done_event(
+ base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ PendingSyncMsg pending_message(
+ SyncMessage::GetMessageId(*message),
+ static_cast<SyncMessage*>(message)->GetReplyDeserializer(),
+ &done_event);
+
+ {
+ base::AutoLock auto_lock(lock_);
+ // Can't use this class on the main thread or else it can lead to deadlocks.
+ // Also by definition, can't use this on IO thread since we're blocking it.
+ if (base::ThreadTaskRunnerHandle::IsSet()) {
+ DCHECK(base::ThreadTaskRunnerHandle::Get() != listener_task_runner_);
+ DCHECK(base::ThreadTaskRunnerHandle::Get() != io_task_runner_);
+ }
+ pending_sync_messages_.insert(&pending_message);
+
+ if (io_task_runner_.get()) {
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&SyncMessageFilter::SendOnIOThread, this, message));
+ } else {
+ pending_messages_.emplace_back(base::WrapUnique(message));
+ }
+ }
+
+ bool done = false;
+ bool shutdown = false;
+ scoped_refptr<mojo::SyncHandleRegistry> registry =
+ mojo::SyncHandleRegistry::current();
+ auto on_shutdown_callback = base::Bind(&OnEventReady, &shutdown);
+ auto on_done_callback = base::Bind(&OnEventReady, &done);
+ registry->RegisterEvent(shutdown_event_, on_shutdown_callback);
+ registry->RegisterEvent(&done_event, on_done_callback);
+
+ const bool* stop_flags[] = { &done, &shutdown };
+ registry->Wait(stop_flags, 2);
+ if (done) {
+ TRACE_EVENT_FLOW_END0(TRACE_DISABLED_BY_DEFAULT("ipc.flow"),
+ "SyncMessageFilter::Send", &done_event);
+ }
+
+ registry->UnregisterEvent(shutdown_event_, on_shutdown_callback);
+ registry->UnregisterEvent(&done_event, on_done_callback);
+
+ {
+ base::AutoLock auto_lock(lock_);
+ delete pending_message.deserializer;
+ pending_sync_messages_.erase(&pending_message);
+ }
+
+ return pending_message.send_result;
+}
+
+void SyncMessageFilter::OnFilterAdded(Channel* channel) {
+ std::vector<std::unique_ptr<Message>> pending_messages;
+ {
+ base::AutoLock auto_lock(lock_);
+ channel_ = channel;
+
+ io_task_runner_ = base::ThreadTaskRunnerHandle::Get();
+ std::swap(pending_messages_, pending_messages);
+ }
+ for (auto& msg : pending_messages)
+ SendOnIOThread(msg.release());
+}
+
+void SyncMessageFilter::OnChannelError() {
+ base::AutoLock auto_lock(lock_);
+ channel_ = nullptr;
+ SignalAllEvents();
+}
+
+void SyncMessageFilter::OnChannelClosing() {
+ base::AutoLock auto_lock(lock_);
+ channel_ = nullptr;
+ SignalAllEvents();
+}
+
+bool SyncMessageFilter::OnMessageReceived(const Message& message) {
+ base::AutoLock auto_lock(lock_);
+ for (PendingSyncMessages::iterator iter = pending_sync_messages_.begin();
+ iter != pending_sync_messages_.end(); ++iter) {
+ if (SyncMessage::IsMessageReplyTo(message, (*iter)->id)) {
+ if (!message.is_reply_error()) {
+ (*iter)->send_result =
+ (*iter)->deserializer->SerializeOutputParameters(message);
+ }
+ TRACE_EVENT_FLOW_BEGIN0(TRACE_DISABLED_BY_DEFAULT("ipc.flow"),
+ "SyncMessageFilter::OnMessageReceived",
+ (*iter)->done_event);
+ (*iter)->done_event->Signal();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+SyncMessageFilter::SyncMessageFilter(base::WaitableEvent* shutdown_event)
+ : channel_(nullptr),
+ listener_task_runner_(base::ThreadTaskRunnerHandle::Get()),
+ shutdown_event_(shutdown_event) {}
+
+SyncMessageFilter::~SyncMessageFilter() = default;
+
+void SyncMessageFilter::SendOnIOThread(Message* message) {
+ if (channel_) {
+ channel_->Send(message);
+ return;
+ }
+
+ if (message->is_sync()) {
+ // We don't know which thread sent it, but it doesn't matter, just signal
+ // them all.
+ base::AutoLock auto_lock(lock_);
+ SignalAllEvents();
+ }
+
+ delete message;
+}
+
+void SyncMessageFilter::SignalAllEvents() {
+ lock_.AssertAcquired();
+ for (PendingSyncMessages::iterator iter = pending_sync_messages_.begin();
+ iter != pending_sync_messages_.end(); ++iter) {
+ TRACE_EVENT_FLOW_BEGIN0(TRACE_DISABLED_BY_DEFAULT("ipc.flow"),
+ "SyncMessageFilter::SignalAllEvents",
+ (*iter)->done_event);
+ (*iter)->done_event->Signal();
+ }
+}
+
+void SyncMessageFilter::GetGenericRemoteAssociatedInterface(
+ const std::string& interface_name,
+ mojo::ScopedInterfaceEndpointHandle handle) {
+ base::AutoLock auto_lock(lock_);
+ DCHECK(io_task_runner_ && io_task_runner_->BelongsToCurrentThread());
+ if (!channel_) {
+ // Attach the associated interface to a disconnected pipe, so that the
+ // associated interface pointer can be used to make calls (which are
+ // dropped).
+ mojo::AssociateWithDisconnectedPipe(std::move(handle));
+ return;
+ }
+
+ Channel::AssociatedInterfaceSupport* support =
+ channel_->GetAssociatedInterfaceSupport();
+ support->GetGenericRemoteAssociatedInterface(
+ interface_name, std::move(handle));
+}
+
+} // namespace IPC
diff --git a/ipc/ipc_sync_message_filter.h b/ipc/ipc_sync_message_filter.h
new file mode 100644
index 0000000000..01e6cca3c4
--- /dev/null
+++ b/ipc/ipc_sync_message_filter.h
@@ -0,0 +1,101 @@
+// 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_FILTER_H_
+#define IPC_IPC_SYNC_MESSAGE_FILTER_H_
+
+#include <set>
+#include <vector>
+
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "ipc/ipc_sender.h"
+#include "ipc/ipc_sync_message.h"
+#include "ipc/message_filter.h"
+#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
+#include "mojo/public/cpp/bindings/associated_interface_request.h"
+#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+class WaitableEvent;
+}
+
+namespace IPC {
+class SyncChannel;
+
+// This MessageFilter allows sending synchronous IPC messages from a thread
+// other than the listener thread associated with the SyncChannel. It does not
+// support fancy features that SyncChannel does, such as handling recursion or
+// receiving messages while waiting for a response. Note that this object can
+// be used to send simultaneous synchronous messages from different threads.
+class COMPONENT_EXPORT(IPC) SyncMessageFilter : public MessageFilter,
+ public Sender {
+ public:
+ // Sender implementation.
+ bool Send(Message* message) override;
+
+ // MessageFilter implementation.
+ void OnFilterAdded(Channel* channel) override;
+ void OnChannelError() override;
+ void OnChannelClosing() override;
+ bool OnMessageReceived(const Message& message) override;
+
+ // Binds an associated interface proxy to an interface in the browser process.
+ // Interfaces acquired through this method are associated with the IPC Channel
+ // and as such retain FIFO with legacy IPC messages.
+ //
+ // NOTE: This must ONLY be called on the Channel's thread, after
+ // OnFilterAdded.
+ template <typename Interface>
+ void GetRemoteAssociatedInterface(
+ mojo::AssociatedInterfacePtr<Interface>* proxy) {
+ auto request = mojo::MakeRequest(proxy);
+ GetGenericRemoteAssociatedInterface(Interface::Name_, request.PassHandle());
+ }
+
+ protected:
+ explicit SyncMessageFilter(base::WaitableEvent* shutdown_event);
+ ~SyncMessageFilter() override;
+
+ private:
+ friend class SyncChannel;
+
+ void SendOnIOThread(Message* message);
+ // Signal all the pending sends as done, used in an error condition.
+ void SignalAllEvents();
+
+ // NOTE: This must ONLY be called on the Channel's thread.
+ void GetGenericRemoteAssociatedInterface(
+ const std::string& interface_name,
+ mojo::ScopedInterfaceEndpointHandle handle);
+
+ // The channel to which this filter was added.
+ Channel* channel_;
+
+ // The process's main thread.
+ scoped_refptr<base::SingleThreadTaskRunner> listener_task_runner_;
+
+ // The message loop where the Channel lives.
+ scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
+
+ typedef std::set<PendingSyncMsg*> PendingSyncMessages;
+ PendingSyncMessages pending_sync_messages_;
+
+ // Messages waiting to be delivered after IO initialization.
+ std::vector<std::unique_ptr<Message>> pending_messages_;
+
+ // Locks data members above.
+ base::Lock lock_;
+
+ base::WaitableEvent* const shutdown_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncMessageFilter);
+};
+
+} // namespace IPC
+
+#endif // IPC_IPC_SYNC_MESSAGE_FILTER_H_
diff --git a/ipc/ipc_sync_message_unittest.cc b/ipc/ipc_sync_message_unittest.cc
new file mode 100644
index 0000000000..c8edb0c50d
--- /dev/null
+++ b/ipc/ipc_sync_message_unittest.cc
@@ -0,0 +1,317 @@
+// 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.
+//
+// Unit test to make sure that the serialization of synchronous IPC messages
+// works. This ensures that the macros and templates were defined correctly.
+// Doesn't test the IPC channel mechanism.
+
+#include "base/logging.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#define IPC_MESSAGE_IMPL
+#include "ipc/ipc_sync_message_unittest.h"
+
+namespace {
+
+static IPC::Message* g_reply;
+
+class TestMessageReceiver {
+ public:
+
+ void On_0_1(bool* out1) {
+ *out1 = false;
+ }
+
+ void On_0_2(bool* out1, int* out2) {
+ *out1 = true;
+ *out2 = 2;
+ }
+
+ void On_0_3(bool* out1, int* out2, std::string* out3) {
+ *out1 = false;
+ *out2 = 3;
+ *out3 = "0_3";
+ }
+
+ void On_1_1(int in1, bool* out1) {
+ DCHECK_EQ(1, in1);
+ *out1 = true;
+ }
+
+ void On_1_2(bool in1, bool* out1, int* out2) {
+ DCHECK(!in1);
+ *out1 = true;
+ *out2 = 12;
+ }
+
+ void On_1_3(int in1, std::string* out1, int* out2, bool* out3) {
+ DCHECK_EQ(3, in1);
+ *out1 = "1_3";
+ *out2 = 13;
+ *out3 = false;
+ }
+
+ void On_2_1(int in1, bool in2, bool* out1) {
+ DCHECK_EQ(1, in1);
+ DCHECK(!in2);
+ *out1 = true;
+ }
+
+ void On_2_2(bool in1, int in2, bool* out1, int* out2) {
+ DCHECK(!in1);
+ DCHECK_EQ(2, in2);
+ *out1 = true;
+ *out2 = 22;
+ }
+
+ void On_2_3(int in1, bool in2, std::string* out1, int* out2, bool* out3) {
+ DCHECK_EQ(3, in1);
+ DCHECK(in2);
+ *out1 = "2_3";
+ *out2 = 23;
+ *out3 = false;
+ }
+
+ void On_3_1(int in1, bool in2, const std::string& in3, bool* out1) {
+ DCHECK_EQ(1, in1);
+ DCHECK(!in2);
+ DCHECK_EQ("3_1", in3);
+ *out1 = true;
+ }
+
+ void On_3_2(const std::string& in1,
+ bool in2,
+ int in3,
+ bool* out1,
+ int* out2) {
+ DCHECK_EQ("3_2", in1);
+ DCHECK(!in2);
+ DCHECK_EQ(2, in3);
+ *out1 = true;
+ *out2 = 32;
+ }
+
+ void On_3_3(int in1,
+ const std::string& in2,
+ bool in3,
+ std::string* out1,
+ int* out2,
+ bool* out3) {
+ DCHECK_EQ(3, in1);
+ DCHECK_EQ("3_3", in2);
+ DCHECK(in3);
+ *out1 = "3_3";
+ *out2 = 33;
+ *out3 = false;
+ }
+
+ void On_3_4(bool in1,
+ int in2,
+ const std::string& in3,
+ int* out1,
+ bool* out2,
+ std::string* out3,
+ bool* out4) {
+ DCHECK(in1);
+ DCHECK_EQ(3, in2);
+ DCHECK_EQ("3_4", in3);
+ *out1 = 34;
+ *out2 = true;
+ *out3 = "3_4";
+ *out4 = false;
+ }
+
+ bool Send(IPC::Message* message) {
+ // gets the reply message, stash in global
+ DCHECK(g_reply == NULL);
+ g_reply = message;
+ return true;
+ }
+
+ bool OnMessageReceived(const IPC::Message& msg) {
+ IPC_BEGIN_MESSAGE_MAP(TestMessageReceiver, msg)
+ IPC_MESSAGE_HANDLER(Msg_C_0_1, On_0_1)
+ IPC_MESSAGE_HANDLER(Msg_C_0_2, On_0_2)
+ IPC_MESSAGE_HANDLER(Msg_C_0_3, On_0_3)
+ IPC_MESSAGE_HANDLER(Msg_C_1_1, On_1_1)
+ IPC_MESSAGE_HANDLER(Msg_C_1_2, On_1_2)
+ IPC_MESSAGE_HANDLER(Msg_C_1_3, On_1_3)
+ IPC_MESSAGE_HANDLER(Msg_C_2_1, On_2_1)
+ IPC_MESSAGE_HANDLER(Msg_C_2_2, On_2_2)
+ IPC_MESSAGE_HANDLER(Msg_C_2_3, On_2_3)
+ IPC_MESSAGE_HANDLER(Msg_C_3_1, On_3_1)
+ IPC_MESSAGE_HANDLER(Msg_C_3_2, On_3_2)
+ IPC_MESSAGE_HANDLER(Msg_C_3_3, On_3_3)
+ IPC_MESSAGE_HANDLER(Msg_C_3_4, On_3_4)
+ IPC_MESSAGE_HANDLER(Msg_R_0_1, On_0_1)
+ IPC_MESSAGE_HANDLER(Msg_R_0_2, On_0_2)
+ IPC_MESSAGE_HANDLER(Msg_R_0_3, On_0_3)
+ IPC_MESSAGE_HANDLER(Msg_R_1_1, On_1_1)
+ IPC_MESSAGE_HANDLER(Msg_R_1_2, On_1_2)
+ IPC_MESSAGE_HANDLER(Msg_R_1_3, On_1_3)
+ IPC_MESSAGE_HANDLER(Msg_R_2_1, On_2_1)
+ IPC_MESSAGE_HANDLER(Msg_R_2_2, On_2_2)
+ IPC_MESSAGE_HANDLER(Msg_R_2_3, On_2_3)
+ IPC_MESSAGE_HANDLER(Msg_R_3_1, On_3_1)
+ IPC_MESSAGE_HANDLER(Msg_R_3_2, On_3_2)
+ IPC_MESSAGE_HANDLER(Msg_R_3_3, On_3_3)
+ IPC_MESSAGE_HANDLER(Msg_R_3_4, On_3_4)
+ IPC_END_MESSAGE_MAP()
+ return true;
+ }
+
+};
+
+void Send(IPC::SyncMessage* msg) {
+ static TestMessageReceiver receiver;
+
+ IPC::MessageReplyDeserializer* reply_serializer = msg->GetReplyDeserializer();
+ DCHECK(reply_serializer != NULL);
+
+ // "send" the message
+ receiver.OnMessageReceived(*msg);
+ delete msg;
+
+ // get the reply message from the global, and deserialize the output
+ // parameters into the output pointers.
+ DCHECK(g_reply != NULL);
+ bool result = reply_serializer->SerializeOutputParameters(*g_reply);
+ DCHECK(result);
+ delete g_reply;
+ g_reply = NULL;
+ delete reply_serializer;
+}
+
+TEST(IPCSyncMessageTest, Main) {
+ bool bool1 = true;
+ int int1 = 0;
+ std::string string1;
+
+ Send(new Msg_C_0_1(&bool1));
+ DCHECK(!bool1);
+
+ Send(new Msg_C_0_2(&bool1, &int1));
+ DCHECK(bool1);
+ DCHECK_EQ(2, int1);
+
+ Send(new Msg_C_0_3(&bool1, &int1, &string1));
+ DCHECK(!bool1);
+ DCHECK_EQ(3, int1);
+ DCHECK_EQ("0_3", string1);
+
+ bool1 = false;
+ Send(new Msg_C_1_1(1, &bool1));
+ DCHECK(bool1);
+
+ bool1 = false;
+ Send(new Msg_C_1_2(false, &bool1, &int1));
+ DCHECK(bool1);
+ DCHECK_EQ(12, int1);
+
+ bool1 = true;
+ Send(new Msg_C_1_3(3, &string1, &int1, &bool1));
+ DCHECK_EQ("1_3", string1);
+ DCHECK_EQ(13, int1);
+ DCHECK(!bool1);
+
+ bool1 = false;
+ Send(new Msg_C_2_1(1, false, &bool1));
+ DCHECK(bool1);
+
+ bool1 = false;
+ Send(new Msg_C_2_2(false, 2, &bool1, &int1));
+ DCHECK(bool1);
+ DCHECK_EQ(22, int1);
+
+ bool1 = true;
+ Send(new Msg_C_2_3(3, true, &string1, &int1, &bool1));
+ DCHECK_EQ("2_3", string1);
+ DCHECK_EQ(23, int1);
+ DCHECK(!bool1);
+
+ bool1 = false;
+ Send(new Msg_C_3_1(1, false, "3_1", &bool1));
+ DCHECK(bool1);
+
+ bool1 = false;
+ Send(new Msg_C_3_2("3_2", false, 2, &bool1, &int1));
+ DCHECK(bool1);
+ DCHECK_EQ(32, int1);
+
+ bool1 = true;
+ Send(new Msg_C_3_3(3, "3_3", true, &string1, &int1, &bool1));
+ DCHECK_EQ("3_3", string1);
+ DCHECK_EQ(33, int1);
+ DCHECK(!bool1);
+
+ bool1 = false;
+ bool bool2 = true;
+ Send(new Msg_C_3_4(true, 3, "3_4", &int1, &bool1, &string1, &bool2));
+ DCHECK_EQ(34, int1);
+ DCHECK(bool1);
+ DCHECK_EQ("3_4", string1);
+ DCHECK(!bool2);
+
+ // Routed messages, just a copy of the above but with extra routing paramater
+ Send(new Msg_R_0_1(0, &bool1));
+ DCHECK(!bool1);
+
+ Send(new Msg_R_0_2(0, &bool1, &int1));
+ DCHECK(bool1);
+ DCHECK_EQ(2, int1);
+
+ Send(new Msg_R_0_3(0, &bool1, &int1, &string1));
+ DCHECK(!bool1);
+ DCHECK_EQ(3, int1);
+ DCHECK_EQ("0_3", string1);
+
+ bool1 = false;
+ Send(new Msg_R_1_1(0, 1, &bool1));
+ DCHECK(bool1);
+
+ bool1 = false;
+ Send(new Msg_R_1_2(0, false, &bool1, &int1));
+ DCHECK(bool1);
+ DCHECK_EQ(12, int1);
+
+ bool1 = true;
+ Send(new Msg_R_1_3(0, 3, &string1, &int1, &bool1));
+ DCHECK_EQ("1_3", string1);
+ DCHECK_EQ(13, int1);
+ DCHECK(!bool1);
+
+ bool1 = false;
+ Send(new Msg_R_2_1(0, 1, false, &bool1));
+ DCHECK(bool1);
+
+ bool1 = false;
+ Send(new Msg_R_2_2(0, false, 2, &bool1, &int1));
+ DCHECK(bool1);
+ DCHECK_EQ(22, int1);
+
+ bool1 = true;
+ Send(new Msg_R_2_3(0, 3, true, &string1, &int1, &bool1));
+ DCHECK(!bool1);
+ DCHECK_EQ("2_3", string1);
+ DCHECK_EQ(23, int1);
+
+ bool1 = false;
+ Send(new Msg_R_3_1(0, 1, false, "3_1", &bool1));
+ DCHECK(bool1);
+
+ bool1 = false;
+ Send(new Msg_R_3_2(0, "3_2", false, 2, &bool1, &int1));
+ DCHECK(bool1);
+ DCHECK_EQ(32, int1);
+
+ bool1 = true;
+ Send(new Msg_R_3_3(0, 3, "3_3", true, &string1, &int1, &bool1));
+ DCHECK_EQ("3_3", string1);
+ DCHECK_EQ(33, int1);
+ DCHECK(!bool1);
+}
+
+} // namespace
diff --git a/ipc/ipc_sync_message_unittest.h b/ipc/ipc_sync_message_unittest.h
new file mode 100644
index 0000000000..114aca6fb6
--- /dev/null
+++ b/ipc/ipc_sync_message_unittest.h
@@ -0,0 +1,120 @@
+// 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 <string>
+
+#include "ipc/ipc_message_macros.h"
+
+#define IPC_MESSAGE_START TestMsgStart
+
+IPC_SYNC_MESSAGE_CONTROL0_0(SyncChannelTestMsg_NoArgs)
+
+IPC_SYNC_MESSAGE_CONTROL0_1(SyncChannelTestMsg_AnswerToLife,
+ int /* answer */)
+
+IPC_SYNC_MESSAGE_CONTROL1_1(SyncChannelTestMsg_Double,
+ int /* in */,
+ int /* out */)
+
+IPC_SYNC_MESSAGE_CONTROL0_1(SyncChannelNestedTestMsg_String,
+ std::string)
+
+// out1 is false
+IPC_SYNC_MESSAGE_CONTROL0_1(Msg_C_0_1, bool)
+
+// out1 is true, out2 is 2
+IPC_SYNC_MESSAGE_CONTROL0_2(Msg_C_0_2, bool, int)
+
+// out1 is false, out2 is 3, out3 is "0_3"
+IPC_SYNC_MESSAGE_CONTROL0_3(Msg_C_0_3, bool, int, std::string)
+
+// in1 must be 1, out1 is true
+IPC_SYNC_MESSAGE_CONTROL1_1(Msg_C_1_1, int, bool)
+
+// in1 must be false, out1 is true, out2 is 12
+IPC_SYNC_MESSAGE_CONTROL1_2(Msg_C_1_2, bool, bool, int)
+
+// in1 must be 3, out1 is "1_3", out2 is 13, out3 is false
+IPC_SYNC_MESSAGE_CONTROL1_3(Msg_C_1_3, int, std::string, int, bool)
+
+// in1 must be 1, in2 must be false, out1 is true
+IPC_SYNC_MESSAGE_CONTROL2_1(Msg_C_2_1, int, bool, bool)
+
+// in1 must be false, in2 must be 2, out1 is true, out2 is 22
+IPC_SYNC_MESSAGE_CONTROL2_2(Msg_C_2_2, bool, int, bool, int)
+
+// in1 must be 3, in2 must be true, out1 is "2_3", out2 is 23, out3 is false
+IPC_SYNC_MESSAGE_CONTROL2_3(Msg_C_2_3, int, bool, std::string, int, bool)
+
+// in1 must be 1, in2 must be false, in3 must be "3_1", out1 is true
+IPC_SYNC_MESSAGE_CONTROL3_1(Msg_C_3_1, int, bool, std::string, bool)
+
+// in1 must be "3_3", in2 must be false, in3 must be 2, out1 is true, out2 is
+// 32
+IPC_SYNC_MESSAGE_CONTROL3_2(Msg_C_3_2, std::string, bool, int, bool, int)
+
+// in1 must be 3, in2 must be "3_3", in3 must be true, out1 is "3_3", out2 is
+// 33, out3 is false
+IPC_SYNC_MESSAGE_CONTROL3_3(Msg_C_3_3, int, std::string, bool, std::string,
+ int, bool)
+
+// in1 must be true, in2 must be 3, in3 must be "3_4", out1 is 34, out2 is
+// true, out3 is "3_4", out3 is false
+IPC_SYNC_MESSAGE_CONTROL3_4(Msg_C_3_4, bool, int, std::string, int, bool,
+ std::string, bool)
+
+// NOTE: routed messages are just a copy of the above...
+
+// out1 is false
+IPC_SYNC_MESSAGE_ROUTED0_1(Msg_R_0_1, bool)
+
+// out1 is true, out2 is 2
+IPC_SYNC_MESSAGE_ROUTED0_2(Msg_R_0_2, bool, int)
+
+// out1 is false, out2 is 3, out3 is "0_3"
+IPC_SYNC_MESSAGE_ROUTED0_3(Msg_R_0_3, bool, int, std::string)
+
+// in1 must be 1, out1 is true
+IPC_SYNC_MESSAGE_ROUTED1_1(Msg_R_1_1, int, bool)
+
+// in1 must be false, out1 is true, out2 is 12
+IPC_SYNC_MESSAGE_ROUTED1_2(Msg_R_1_2, bool, bool, int)
+
+// in1 must be 3, out1 is "1_3", out2 is 13, out3 is false
+IPC_SYNC_MESSAGE_ROUTED1_3(Msg_R_1_3, int, std::string, int, bool)
+
+// in1 must be 1, in2 must be false, out1 is true
+IPC_SYNC_MESSAGE_ROUTED2_1(Msg_R_2_1, int, bool, bool)
+
+// in1 must be false, in2 must be 2, out1 is true, out2 is 22
+IPC_SYNC_MESSAGE_ROUTED2_2(Msg_R_2_2, bool, int, bool, int)
+
+// in1 must be 3, in2 must be true, out1 is "2_3", out2 is 23, out3 is false
+IPC_SYNC_MESSAGE_ROUTED2_3(Msg_R_2_3, int, bool, std::string, int, bool)
+
+// in1 must be 1, in2 must be false, in3 must be "3_1", out1 is true
+IPC_SYNC_MESSAGE_ROUTED3_1(Msg_R_3_1, int, bool, std::string, bool)
+
+// in1 must be "3_3", in2 must be false, in3 must be 2, out1 is true, out2
+// is 32
+IPC_SYNC_MESSAGE_ROUTED3_2(Msg_R_3_2, std::string, bool, int, bool, int)
+
+// in1 must be 3, in2 must be "3_3", in3 must be true, out1 is "3_3", out2 is
+// 33, out3 is false
+IPC_SYNC_MESSAGE_ROUTED3_3(Msg_R_3_3, int, std::string, bool, std::string,
+ int, bool)
+
+// in1 must be true, in2 must be 3, in3 must be "3_4", out1 is 34, out2 is
+// true, out3 is "3_4", out4 is false
+IPC_SYNC_MESSAGE_ROUTED3_4(Msg_R_3_4, bool, int, std::string, int, bool,
+ std::string, bool)
+
+IPC_MESSAGE_CONTROL1(SyncChannelTestMsg_Ping, int)
+IPC_SYNC_MESSAGE_CONTROL1_1(SyncChannelTestMsg_PingTTL, int, int)
+IPC_SYNC_MESSAGE_CONTROL0_0(SyncChannelTestMsg_Done)
+
+// Messages for ReentrantReply test.
+IPC_SYNC_MESSAGE_CONTROL0_0(SyncChannelTestMsg_Reentrant1)
+IPC_SYNC_MESSAGE_CONTROL0_0(SyncChannelTestMsg_Reentrant2)
+IPC_SYNC_MESSAGE_CONTROL0_0(SyncChannelTestMsg_Reentrant3)
diff --git a/ipc/ipc_test_base.cc b/ipc/ipc_test_base.cc
new file mode 100644
index 0000000000..419419483c
--- /dev/null
+++ b/ipc/ipc_test_base.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 "ipc/ipc_test_base.h"
+
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "ipc/ipc_channel_mojo.h"
+
+IPCChannelMojoTestBase::IPCChannelMojoTestBase() = default;
+IPCChannelMojoTestBase::~IPCChannelMojoTestBase() = default;
+
+void IPCChannelMojoTestBase::Init(const std::string& test_client_name) {
+ InitWithCustomMessageLoop(test_client_name,
+ std::make_unique<base::MessageLoop>());
+}
+
+void IPCChannelMojoTestBase::InitWithCustomMessageLoop(
+ const std::string& test_client_name,
+ std::unique_ptr<base::MessageLoop> message_loop) {
+ handle_ = helper_.StartChild(test_client_name);
+ message_loop_ = std::move(message_loop);
+}
+
+bool IPCChannelMojoTestBase::WaitForClientShutdown() {
+ return helper_.WaitForChildTestShutdown();
+}
+
+void IPCChannelMojoTestBase::TearDown() {
+ if (message_loop_)
+ base::RunLoop().RunUntilIdle();
+}
+
+void IPCChannelMojoTestBase::CreateChannel(IPC::Listener* listener) {
+ channel_ = IPC::ChannelMojo::Create(
+ TakeHandle(), IPC::Channel::MODE_SERVER, listener,
+ base::ThreadTaskRunnerHandle::Get(), base::ThreadTaskRunnerHandle::Get());
+}
+
+bool IPCChannelMojoTestBase::ConnectChannel() {
+ return channel_->Connect();
+}
+
+void IPCChannelMojoTestBase::DestroyChannel() {
+ channel_.reset();
+}
+
+mojo::ScopedMessagePipeHandle IPCChannelMojoTestBase::TakeHandle() {
+ return std::move(handle_);
+}
+
+IpcChannelMojoTestClient::IpcChannelMojoTestClient() = default;
+
+IpcChannelMojoTestClient::~IpcChannelMojoTestClient() = default;
+
+void IpcChannelMojoTestClient::Init(mojo::ScopedMessagePipeHandle handle) {
+ handle_ = std::move(handle);
+}
+
+void IpcChannelMojoTestClient::Connect(IPC::Listener* listener) {
+ channel_ = IPC::ChannelMojo::Create(
+ std::move(handle_), IPC::Channel::MODE_CLIENT, listener,
+ base::ThreadTaskRunnerHandle::Get(), base::ThreadTaskRunnerHandle::Get());
+ CHECK(channel_->Connect());
+}
+
+void IpcChannelMojoTestClient::Close() {
+ channel_->Close();
+
+ base::RunLoop run_loop;
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ run_loop.QuitClosure());
+ run_loop.Run();
+}
diff --git a/ipc/ipc_test_base.h b/ipc/ipc_test_base.h
new file mode 100644
index 0000000000..2555611c95
--- /dev/null
+++ b/ipc/ipc_test_base.h
@@ -0,0 +1,111 @@
+// 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_TEST_BASE_H_
+#define IPC_IPC_TEST_BASE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/process/process.h"
+#include "base/test/multiprocess_test.h"
+#include "build/build_config.h"
+#include "ipc/ipc_channel.h"
+#include "ipc/ipc_channel_factory.h"
+#include "ipc/ipc_channel_proxy.h"
+#include "mojo/core/test/mojo_test_base.h"
+#include "mojo/core/test/multiprocess_test_helper.h"
+
+namespace base {
+class MessageLoop;
+}
+
+class IPCChannelMojoTestBase : public testing::Test {
+ public:
+ IPCChannelMojoTestBase();
+ ~IPCChannelMojoTestBase() override;
+
+ void Init(const std::string& test_client_name);
+ void InitWithCustomMessageLoop(
+ const std::string& test_client_name,
+ std::unique_ptr<base::MessageLoop> message_loop);
+
+ bool WaitForClientShutdown();
+
+ void TearDown() override;
+
+ void CreateChannel(IPC::Listener* listener);
+
+ bool ConnectChannel();
+
+ void DestroyChannel();
+
+ IPC::Sender* sender() { return channel(); }
+ IPC::Channel* channel() { return channel_.get(); }
+ const base::Process& client_process() const { return helper_.test_child(); }
+
+ protected:
+ mojo::ScopedMessagePipeHandle TakeHandle();
+
+ private:
+ std::unique_ptr<base::MessageLoop> message_loop_;
+
+ mojo::ScopedMessagePipeHandle handle_;
+ mojo::core::test::MultiprocessTestHelper helper_;
+
+ std::unique_ptr<IPC::Channel> channel_;
+
+ DISALLOW_COPY_AND_ASSIGN(IPCChannelMojoTestBase);
+};
+
+class IpcChannelMojoTestClient {
+ public:
+ IpcChannelMojoTestClient();
+ ~IpcChannelMojoTestClient();
+
+ void Init(mojo::ScopedMessagePipeHandle handle);
+
+ void Connect(IPC::Listener* listener);
+
+ void Close();
+
+ IPC::Channel* channel() const { return channel_.get(); }
+
+ private:
+ base::MessageLoopForIO main_message_loop_;
+ mojo::ScopedMessagePipeHandle handle_;
+ std::unique_ptr<IPC::Channel> channel_;
+};
+
+// Use this to declare the client side for tests using IPCChannelMojoTestBase
+// when a custom test fixture class is required in the client. |test_base| must
+// be derived from IpcChannelMojoTestClient.
+#define DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT_WITH_CUSTOM_FIXTURE(client_name, \
+ test_base) \
+ class client_name##_MainFixture : public test_base { \
+ public: \
+ void Main(); \
+ }; \
+ MULTIPROCESS_TEST_MAIN_WITH_SETUP( \
+ client_name##TestChildMain, \
+ ::mojo::core::test::MultiprocessTestHelper::ChildSetup) { \
+ client_name##_MainFixture test; \
+ test.Init( \
+ std::move(mojo::core::test::MultiprocessTestHelper::primordial_pipe)); \
+ test.Main(); \
+ return (::testing::Test::HasFatalFailure() || \
+ ::testing::Test::HasNonfatalFailure()) \
+ ? 1 \
+ : 0; \
+ } \
+ void client_name##_MainFixture::Main()
+
+// Use this to declare the client side for tests using IPCChannelMojoTestBase.
+#define DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(client_name) \
+ DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT_WITH_CUSTOM_FIXTURE( \
+ client_name, IpcChannelMojoTestClient)
+
+#endif // IPC_IPC_TEST_BASE_H_
diff --git a/ipc/ipc_test_channel_listener.cc b/ipc/ipc_test_channel_listener.cc
new file mode 100644
index 0000000000..611bfe835a
--- /dev/null
+++ b/ipc/ipc_test_channel_listener.cc
@@ -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.
+
+#include "ipc/ipc_test_channel_listener.h"
+
+#include "base/run_loop.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_sender.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace IPC {
+
+// static
+void TestChannelListener::SendOneMessage(IPC::Sender* sender,
+ const char* text) {
+ static int message_index = 0;
+
+ IPC::Message* message = new IPC::Message(0,
+ 2,
+ IPC::Message::PRIORITY_NORMAL);
+ message->WriteInt(message_index++);
+ message->WriteString(std::string(text));
+
+ // Make sure we can handle large messages.
+ char junk[kLongMessageStringNumBytes];
+ memset(junk, 'a', sizeof(junk)-1);
+ junk[sizeof(junk)-1] = 0;
+ message->WriteString(std::string(junk));
+
+ sender->Send(message);
+}
+
+
+bool TestChannelListener::OnMessageReceived(const IPC::Message& message) {
+ base::PickleIterator iter(message);
+
+ int ignored;
+ EXPECT_TRUE(iter.ReadInt(&ignored));
+ std::string data;
+ EXPECT_TRUE(iter.ReadString(&data));
+ std::string big_string;
+ EXPECT_TRUE(iter.ReadString(&big_string));
+ EXPECT_EQ(kLongMessageStringNumBytes - 1, big_string.length());
+
+ SendNextMessage();
+ return true;
+}
+
+void TestChannelListener::OnChannelError() {
+ // There is a race when closing the channel so the last message may be lost.
+ EXPECT_LE(messages_left_, 1);
+ base::RunLoop::QuitCurrentWhenIdleDeprecated();
+}
+
+void TestChannelListener::SendNextMessage() {
+ if (--messages_left_ <= 0)
+ base::RunLoop::QuitCurrentWhenIdleDeprecated();
+ else
+ SendOneMessage(sender_, "Foo");
+}
+
+}
diff --git a/ipc/ipc_test_channel_listener.h b/ipc/ipc_test_channel_listener.h
new file mode 100644
index 0000000000..75635f7db3
--- /dev/null
+++ b/ipc/ipc_test_channel_listener.h
@@ -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.
+
+#ifndef IPC_IPC_TEST_CHANNEL_LISTENER_H_
+#define IPC_IPC_TEST_CHANNEL_LISTENER_H_
+
+#include <stddef.h>
+
+#include "ipc/ipc_listener.h"
+
+namespace IPC {
+
+class Sender;
+
+// A generic listener that expects messages of a certain type (see
+// OnMessageReceived()), and either sends a generic response or quits after the
+// 50th message (or on channel error).
+class TestChannelListener : public Listener {
+ public:
+ static const size_t kLongMessageStringNumBytes = 50000;
+ static void SendOneMessage(Sender* sender, const char* text);
+
+ TestChannelListener() : sender_(NULL), messages_left_(50) {}
+ ~TestChannelListener() override {}
+
+ bool OnMessageReceived(const Message& message) override;
+ void OnChannelError() override;
+
+ void Init(Sender* s) {
+ sender_ = s;
+ }
+
+ bool HasSentAll() const { return 0 == messages_left_; }
+
+ protected:
+ void SendNextMessage();
+
+ private:
+ Sender* sender_;
+ int messages_left_;
+};
+
+}
+
+#endif // IPC_IPC_TEST_CHANNEL_LISTENER_H_
diff --git a/ipc/ipc_test_message_generator.cc b/ipc/ipc_test_message_generator.cc
new file mode 100644
index 0000000000..c0e9c03b32
--- /dev/null
+++ b/ipc/ipc_test_message_generator.cc
@@ -0,0 +1,33 @@
+// 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.
+
+// Get basic type definitions.
+#define IPC_MESSAGE_IMPL
+#include "ipc/ipc_test_message_generator.h"
+
+// Generate constructors.
+#include "ipc/struct_constructor_macros.h"
+#include "ipc/ipc_test_message_generator.h"
+
+// Generate destructors.
+#include "ipc/struct_destructor_macros.h"
+#include "ipc/ipc_test_message_generator.h"
+
+// Generate param traits write methods.
+#include "ipc/param_traits_write_macros.h"
+namespace IPC {
+#include "ipc/ipc_test_message_generator.h"
+} // namespace IPC
+
+// Generate param traits read methods.
+#include "ipc/param_traits_read_macros.h"
+namespace IPC {
+#include "ipc/ipc_test_message_generator.h"
+} // namespace IPC
+
+// Generate param traits log methods.
+#include "ipc/param_traits_log_macros.h"
+namespace IPC {
+#include "ipc/ipc_test_message_generator.h"
+} // namespace IPC
diff --git a/ipc/ipc_test_message_generator.h b/ipc/ipc_test_message_generator.h
new file mode 100644
index 0000000000..169a85ec32
--- /dev/null
+++ b/ipc/ipc_test_message_generator.h
@@ -0,0 +1,7 @@
+// 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.
+
+// Multiply-included file, hence no include guard.
+
+#include "ipc/ipc_test_messages.h"
diff --git a/ipc/ipc_test_messages.h b/ipc/ipc_test_messages.h
new file mode 100644
index 0000000000..a61d1b013d
--- /dev/null
+++ b/ipc/ipc_test_messages.h
@@ -0,0 +1,38 @@
+// 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.
+
+// Multiply-included file, no traditional include guard.
+#include "build/build_config.h"
+
+#include "ipc/ipc_message_macros.h"
+#define IPC_MESSAGE_START IPCTestMsgStart
+
+#if defined(OS_WIN)
+#include "base/memory/shared_memory_handle.h"
+#include "ipc/handle_win.h"
+
+IPC_MESSAGE_CONTROL3(TestHandleWinMsg, int, IPC::HandleWin, int)
+IPC_MESSAGE_CONTROL2(TestTwoHandleWinMsg, IPC::HandleWin, IPC::HandleWin)
+IPC_MESSAGE_CONTROL1(TestSharedMemoryHandleMsg1, base::SharedMemoryHandle)
+#endif // defined(OS_WIN)
+
+#if defined(OS_MACOSX)
+#include "base/file_descriptor_posix.h"
+#include "base/memory/shared_memory_handle.h"
+
+IPC_MESSAGE_CONTROL3(TestSharedMemoryHandleMsg1,
+ int,
+ base::SharedMemoryHandle,
+ int)
+IPC_MESSAGE_CONTROL2(TestSharedMemoryHandleMsg2,
+ base::SharedMemoryHandle,
+ base::SharedMemoryHandle)
+IPC_MESSAGE_CONTROL4(TestSharedMemoryHandleMsg3,
+ base::FileDescriptor,
+ base::SharedMemoryHandle,
+ base::FileDescriptor,
+ base::SharedMemoryHandle)
+IPC_MESSAGE_CONTROL1(TestSharedMemoryHandleMsg4, int)
+
+#endif // defined(OS_MACOSX)
diff --git a/ipc/ipc_test_sink.cc b/ipc/ipc_test_sink.cc
new file mode 100644
index 0000000000..6aa80785a0
--- /dev/null
+++ b/ipc/ipc_test_sink.cc
@@ -0,0 +1,86 @@
+// 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_test_sink.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "build/build_config.h"
+#include "ipc/ipc_listener.h"
+#include "ipc/ipc_message.h"
+
+namespace IPC {
+
+TestSink::TestSink() = default;
+
+TestSink::~TestSink() = default;
+
+bool TestSink::Send(Message* message) {
+ OnMessageReceived(*message);
+ delete message;
+ return true;
+}
+
+bool TestSink::Connect() {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+void TestSink::Close() {
+ NOTIMPLEMENTED();
+}
+
+bool TestSink::OnMessageReceived(const Message& msg) {
+ for (auto& observer : filter_list_) {
+ if (observer.OnMessageReceived(msg))
+ return true;
+ }
+
+ // No filter handled the message, so store it.
+ messages_.push_back(Message(msg));
+ return true;
+}
+
+void TestSink::ClearMessages() {
+ messages_.clear();
+}
+
+const Message* TestSink::GetMessageAt(size_t index) const {
+ if (index >= messages_.size())
+ return NULL;
+ return &messages_[index];
+}
+
+const Message* TestSink::GetFirstMessageMatching(uint32_t id) const {
+ for (size_t i = 0; i < messages_.size(); i++) {
+ if (messages_[i].type() == id)
+ return &messages_[i];
+ }
+ return NULL;
+}
+
+const Message* TestSink::GetUniqueMessageMatching(uint32_t id) const {
+ size_t found_index = 0;
+ size_t found_count = 0;
+ for (size_t i = 0; i < messages_.size(); i++) {
+ if (messages_[i].type() == id) {
+ found_count++;
+ found_index = i;
+ }
+ }
+ if (found_count != 1)
+ return NULL; // Didn't find a unique one.
+ return &messages_[found_index];
+}
+
+void TestSink::AddFilter(Listener* filter) {
+ filter_list_.AddObserver(filter);
+}
+
+void TestSink::RemoveFilter(Listener* filter) {
+ filter_list_.RemoveObserver(filter);
+}
+
+} // namespace IPC
diff --git a/ipc/ipc_test_sink.h b/ipc/ipc_test_sink.h
new file mode 100644
index 0000000000..96f5e556b0
--- /dev/null
+++ b/ipc/ipc_test_sink.h
@@ -0,0 +1,141 @@
+// 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_TEST_SINK_H_
+#define IPC_IPC_TEST_SINK_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <utility>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "build/build_config.h"
+#include "ipc/ipc_channel.h"
+
+namespace IPC {
+
+class Message;
+
+// This test sink provides a "sink" for IPC messages that are sent. It allows
+// the caller to query messages received in various different ways. It is
+// designed for tests for objects that use the IPC system.
+//
+// Typical usage:
+//
+// test_sink.ClearMessages();
+// do_something();
+//
+// // We should have gotten exactly one update state message.
+// EXPECT_TRUE(test_sink.GetUniqeMessageMatching(ViewHostMsg_Update::ID));
+// // ...and no start load messages.
+// EXPECT_FALSE(test_sink.GetFirstMessageMatching(ViewHostMsg_Start::ID));
+//
+// // Now inspect a message. This assumes a message that was declared like
+// // this: IPC_MESSAGE_ROUTED2(ViewMsg_Foo, bool, int)
+// IPC::Message* msg = test_sink.GetFirstMessageMatching(ViewMsg_Foo::ID));
+// ASSERT_TRUE(msg);
+// bool first_param;
+// int second_param;
+// ViewMsg_Foo::Read(msg, &first_param, &second_param);
+//
+// // Go on to the next phase of the test.
+// test_sink.ClearMessages();
+//
+// To read a sync reply, do this:
+//
+// IPC::Message* msg = test_sink.GetUniqueMessageMatching(IPC_REPLY_ID);
+// ASSERT_TRUE(msg);
+// base::TupleTypes<ViewHostMsg_Foo::ReplyParam>::ValueTuple reply_data;
+// EXPECT_TRUE(ViewHostMsg_Foo::ReadReplyParam(msg, &reply_data));
+//
+// You can also register to be notified when messages are posted to the sink.
+// This can be useful if you need to wait for a particular message that will
+// be posted asynchronously. Example usage:
+//
+// class MyListener : public IPC::Listener {
+// public:
+// MyListener(const base::Closure& closure)
+// : message_received_closure_(closure) {}
+// virtual bool OnMessageReceived(const IPC::Message& msg) {
+// <do something with the message>
+// message_received_closure_.Run();
+// return false; // to store the message in the sink, or true to drop it
+// }
+// private:
+// base::Closure message_received_closure_;
+// };
+//
+// base::RunLoop run_loop;
+// MyListener listener(run_loop.QuitClosure());
+// test_sink.AddFilter(&listener);
+// StartSomeAsynchronousProcess(&test_sink);
+// run_loop.Run();
+// <inspect the results>
+// ...
+//
+// To hook up the sink, all you need to do is call OnMessageReceived when a
+// message is received.
+class TestSink : public Channel {
+ public:
+ TestSink();
+ ~TestSink() override;
+
+ // Interface in IPC::Channel. This copies the message to the sink and then
+ // deletes it.
+ bool Send(IPC::Message* message) override;
+ bool Connect() override WARN_UNUSED_RESULT;
+ void Close() override;
+
+ // Used by the source of the messages to send the message to the sink. This
+ // will make a copy of the message and store it in the list.
+ bool OnMessageReceived(const Message& msg);
+
+ // Returns the number of messages in the queue.
+ size_t message_count() const { return messages_.size(); }
+
+ // Clears the message queue of saved messages.
+ void ClearMessages();
+
+ // Returns the message at the given index in the queue. The index may be out
+ // of range, in which case the return value is NULL. The returned pointer will
+ // only be valid until another message is received or the list is cleared.
+ const Message* GetMessageAt(size_t index) const;
+
+ // Returns the first message with the given ID in the queue. If there is no
+ // message with the given ID, returns NULL. The returned pointer will only be
+ // valid until another message is received or the list is cleared.
+ const Message* GetFirstMessageMatching(uint32_t id) const;
+
+ // Returns the message with the given ID in the queue. If there is no such
+ // message or there is more than one of that message, this will return NULL
+ // (with the expectation that you'll do an ASSERT_TRUE() on the result).
+ // The returned pointer will only be valid until another message is received
+ // or the list is cleared.
+ const Message* GetUniqueMessageMatching(uint32_t id) const;
+
+ // Adds the given listener as a filter to the TestSink.
+ // When a message is received by the TestSink, it will be dispatched to
+ // the filters, in the order they were added. If a filter returns true
+ // from OnMessageReceived, subsequent filters will not receive the message
+ // and the TestSink will not store it.
+ void AddFilter(Listener* filter);
+
+ // Removes the given filter from the TestSink.
+ void RemoveFilter(Listener* filter);
+
+ private:
+ // The actual list of received messages.
+ std::vector<Message> messages_;
+ base::ObserverList<Listener> filter_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestSink);
+};
+
+} // namespace IPC
+
+#endif // IPC_IPC_TEST_SINK_H_
diff --git a/ipc/message_filter.cc b/ipc/message_filter.cc
new file mode 100644
index 0000000000..52378f5457
--- /dev/null
+++ b/ipc/message_filter.cc
@@ -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.
+
+#include "ipc/message_filter.h"
+
+#include <stdint.h>
+
+#include "base/memory/ref_counted.h"
+#include "ipc/ipc_channel.h"
+
+namespace IPC {
+
+MessageFilter::MessageFilter() = default;
+
+void MessageFilter::OnFilterAdded(Channel* channel) {}
+
+void MessageFilter::OnFilterRemoved() {}
+
+void MessageFilter::OnChannelConnected(int32_t peer_pid) {}
+
+void MessageFilter::OnChannelError() {}
+
+void MessageFilter::OnChannelClosing() {}
+
+bool MessageFilter::OnMessageReceived(const Message& message) {
+ return false;
+}
+
+bool MessageFilter::GetSupportedMessageClasses(
+ std::vector<uint32_t>* /*supported_message_classes*/) const {
+ return false;
+}
+
+MessageFilter::~MessageFilter() = default;
+
+} // namespace IPC
diff --git a/ipc/message_filter.h b/ipc/message_filter.h
new file mode 100644
index 0000000000..f879fa5de3
--- /dev/null
+++ b/ipc/message_filter.h
@@ -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.
+
+#ifndef IPC_MESSAGE_FILTER_H_
+#define IPC_MESSAGE_FILTER_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/component_export.h"
+#include "base/memory/ref_counted.h"
+#include "ipc/ipc_channel.h"
+
+namespace IPC {
+
+class Message;
+
+// A class that receives messages on the thread where the IPC channel is
+// running. It can choose to prevent the default action for an IPC message.
+class COMPONENT_EXPORT(IPC) MessageFilter
+ : public base::RefCountedThreadSafe<MessageFilter> {
+ public:
+ MessageFilter();
+
+ // Called on the background thread to provide the filter with access to the
+ // channel. Called when the IPC channel is initialized or when AddFilter
+ // is called if the channel is already initialized.
+ virtual void OnFilterAdded(Channel* channel);
+
+ // Called on the background thread when the filter has been removed from
+ // the ChannelProxy and when the Channel is closing. After a filter is
+ // removed, it will not be called again.
+ virtual void OnFilterRemoved();
+
+ // Called to inform the filter that the IPC channel is connected and we
+ // have received the internal Hello message from the peer.
+ virtual void OnChannelConnected(int32_t peer_pid);
+
+ // Called when there is an error on the channel, typically that the channel
+ // has been closed.
+ virtual void OnChannelError();
+
+ // Called to inform the filter that the IPC channel will be destroyed.
+ // OnFilterRemoved is called immediately after this.
+ virtual void OnChannelClosing();
+
+ // Return true to indicate that the message was handled, or false to let
+ // the message be handled in the default way.
+ virtual bool OnMessageReceived(const Message& message);
+
+ // Called to query the Message classes supported by the filter. Return
+ // false to indicate that all message types should reach the filter, or true
+ // if the resulting contents of |supported_message_classes| may be used to
+ // selectively offer messages of a particular class to the filter.
+ virtual bool GetSupportedMessageClasses(
+ std::vector<uint32_t>* supported_message_classes) const;
+
+ protected:
+ virtual ~MessageFilter();
+
+ private:
+ friend class base::RefCountedThreadSafe<MessageFilter>;
+};
+
+} // namespace IPC
+
+#endif // IPC_MESSAGE_FILTER_H_
diff --git a/ipc/message_filter_router.cc b/ipc/message_filter_router.cc
new file mode 100644
index 0000000000..f56279eb2e
--- /dev/null
+++ b/ipc/message_filter_router.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 "ipc/message_filter_router.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_message_utils.h"
+#include "ipc/message_filter.h"
+
+namespace IPC {
+
+namespace {
+
+bool TryFiltersImpl(MessageFilterRouter::MessageFilters& filters,
+ const IPC::Message& message) {
+ for (size_t i = 0; i < filters.size(); ++i) {
+ if (filters[i]->OnMessageReceived(message)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool RemoveFilterImpl(MessageFilterRouter::MessageFilters& filters,
+ MessageFilter* filter) {
+ MessageFilterRouter::MessageFilters::iterator it =
+ std::remove(filters.begin(), filters.end(), filter);
+ if (it == filters.end())
+ return false;
+
+ filters.erase(it, filters.end());
+ return true;
+}
+
+bool ValidMessageClass(int message_class) {
+ return message_class >= 0 && message_class < LastIPCMsgStart;
+}
+
+} // namespace
+
+MessageFilterRouter::MessageFilterRouter() = default;
+MessageFilterRouter::~MessageFilterRouter() = default;
+
+void MessageFilterRouter::AddFilter(MessageFilter* filter) {
+ // Determine if the filter should be applied to all messages, or only
+ // messages of a certain class.
+ std::vector<uint32_t> supported_message_classes;
+ if (filter->GetSupportedMessageClasses(&supported_message_classes)) {
+ DCHECK(!supported_message_classes.empty());
+ for (size_t i = 0; i < supported_message_classes.size(); ++i) {
+ const int message_class = supported_message_classes[i];
+ DCHECK(ValidMessageClass(message_class));
+ // Safely ignore repeated subscriptions to a given message class for the
+ // current filter being added.
+ if (!message_class_filters_[message_class].empty() &&
+ message_class_filters_[message_class].back() == filter) {
+ continue;
+ }
+ message_class_filters_[message_class].push_back(filter);
+ }
+ } else {
+ global_filters_.push_back(filter);
+ }
+}
+
+void MessageFilterRouter::RemoveFilter(MessageFilter* filter) {
+ if (RemoveFilterImpl(global_filters_, filter))
+ return;
+
+ for (size_t i = 0; i < arraysize(message_class_filters_); ++i)
+ RemoveFilterImpl(message_class_filters_[i], filter);
+}
+
+bool MessageFilterRouter::TryFilters(const Message& message) {
+ if (TryFiltersImpl(global_filters_, message))
+ return true;
+
+ const int message_class = IPC_MESSAGE_CLASS(message);
+ if (!ValidMessageClass(message_class))
+ return false;
+
+ return TryFiltersImpl(message_class_filters_[message_class], message);
+}
+
+void MessageFilterRouter::Clear() {
+ global_filters_.clear();
+ for (size_t i = 0; i < arraysize(message_class_filters_); ++i)
+ message_class_filters_[i].clear();
+}
+
+} // namespace IPC
diff --git a/ipc/message_filter_router.h b/ipc/message_filter_router.h
new file mode 100644
index 0000000000..183b8ebc8a
--- /dev/null
+++ b/ipc/message_filter_router.h
@@ -0,0 +1,42 @@
+// 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 IPC_MESSAGE_FILTER_ROUTER_H_
+#define IPC_MESSAGE_FILTER_ROUTER_H_
+
+#include <vector>
+
+#include "ipc/ipc_message_start.h"
+
+namespace IPC {
+
+class Message;
+class MessageFilter;
+
+class MessageFilterRouter {
+ public:
+ typedef std::vector<MessageFilter*> MessageFilters;
+
+ MessageFilterRouter();
+ ~MessageFilterRouter();
+
+ void AddFilter(MessageFilter* filter);
+ void RemoveFilter(MessageFilter* filter);
+ bool TryFilters(const Message& message);
+ void Clear();
+
+ private:
+ // List of global and selective filters; a given filter will exist in either
+ // |message_global_filters_| OR |message_class_filters_|, but not both.
+ // Note that |message_global_filters_| will be given first offering of any
+ // given message. It's the filter implementer and installer's
+ // responsibility to ensure that a filter is either global or selective to
+ // ensure proper message filtering order.
+ MessageFilters global_filters_;
+ MessageFilters message_class_filters_[LastIPCMsgStart];
+};
+
+} // namespace IPC
+
+#endif // IPC_MESSAGE_FILTER_ROUTER_H_
diff --git a/ipc/message_mojom_traits.cc b/ipc/message_mojom_traits.cc
new file mode 100644
index 0000000000..4aab9248e9
--- /dev/null
+++ b/ipc/message_mojom_traits.cc
@@ -0,0 +1,40 @@
+// 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.
+
+#include "ipc/message_mojom_traits.h"
+
+#include "mojo/public/cpp/base/big_buffer_mojom_traits.h"
+
+namespace mojo {
+
+// static
+mojo_base::BigBufferView
+StructTraits<IPC::mojom::MessageDataView, IPC::MessageView>::buffer(
+ IPC::MessageView& view) {
+ return view.TakeBufferView();
+}
+
+// static
+base::Optional<std::vector<mojo::native::SerializedHandlePtr>>
+StructTraits<IPC::mojom::MessageDataView, IPC::MessageView>::handles(
+ IPC::MessageView& view) {
+ return view.TakeHandles();
+}
+
+// static
+bool StructTraits<IPC::mojom::MessageDataView, IPC::MessageView>::Read(
+ IPC::mojom::MessageDataView data,
+ IPC::MessageView* out) {
+ mojo_base::BigBufferView buffer_view;
+ if (!data.ReadBuffer(&buffer_view))
+ return false;
+ base::Optional<std::vector<mojo::native::SerializedHandlePtr>> handles;
+ if (!data.ReadHandles(&handles))
+ return false;
+
+ *out = IPC::MessageView(std::move(buffer_view), std::move(handles));
+ return true;
+}
+
+} // namespace mojo
diff --git a/ipc/message_mojom_traits.h b/ipc/message_mojom_traits.h
new file mode 100644
index 0000000000..617ffbe373
--- /dev/null
+++ b/ipc/message_mojom_traits.h
@@ -0,0 +1,31 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IPC_MESSAGE_MOJOM_TRAITS_H_
+#define IPC_MESSAGE_MOJOM_TRAITS_H_
+
+#include <vector>
+
+#include "base/optional.h"
+#include "ipc/ipc.mojom-shared.h"
+#include "ipc/message_view.h"
+#include "mojo/public/cpp/base/big_buffer.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "mojo/public/interfaces/bindings/native_struct.mojom.h"
+
+namespace mojo {
+
+template <>
+class StructTraits<IPC::mojom::MessageDataView, IPC::MessageView> {
+ public:
+ static mojo_base::BigBufferView buffer(IPC::MessageView& view);
+ static base::Optional<std::vector<mojo::native::SerializedHandlePtr>> handles(
+ IPC::MessageView& view);
+
+ static bool Read(IPC::mojom::MessageDataView data, IPC::MessageView* out);
+};
+
+} // namespace mojo
+
+#endif // IPC_MESSAGE_MOJOM_TRAITS_H_
diff --git a/ipc/message_router.cc b/ipc/message_router.cc
new file mode 100644
index 0000000000..3e6aac61d5
--- /dev/null
+++ b/ipc/message_router.cc
@@ -0,0 +1,59 @@
+// 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/message_router.h"
+
+#include "ipc/ipc_message.h"
+
+namespace IPC {
+
+MessageRouter::MessageRouter() = default;
+
+MessageRouter::~MessageRouter() = default;
+
+bool MessageRouter::OnControlMessageReceived(const IPC::Message& msg) {
+ NOTREACHED()
+ << "should override in subclass if you care about control messages";
+ return false;
+}
+
+bool MessageRouter::Send(IPC::Message* msg) {
+ NOTREACHED()
+ << "should override in subclass if you care about sending messages";
+ return false;
+}
+
+bool MessageRouter::AddRoute(int32_t routing_id, IPC::Listener* listener) {
+ if (routes_.Lookup(routing_id)) {
+ DLOG(ERROR) << "duplicate routing ID";
+ return false;
+ }
+ routes_.AddWithID(listener, routing_id);
+ return true;
+}
+
+void MessageRouter::RemoveRoute(int32_t routing_id) {
+ routes_.Remove(routing_id);
+}
+
+Listener* MessageRouter::GetRoute(int32_t routing_id) {
+ return routes_.Lookup(routing_id);
+}
+
+bool MessageRouter::OnMessageReceived(const IPC::Message& msg) {
+ if (msg.routing_id() == MSG_ROUTING_CONTROL)
+ return OnControlMessageReceived(msg);
+
+ return RouteMessage(msg);
+}
+
+bool MessageRouter::RouteMessage(const IPC::Message& msg) {
+ IPC::Listener* listener = routes_.Lookup(msg.routing_id());
+ if (!listener)
+ return false;
+
+ return listener->OnMessageReceived(msg);
+}
+
+} // namespace IPC
diff --git a/ipc/message_router.h b/ipc/message_router.h
new file mode 100644
index 0000000000..731c4ebce5
--- /dev/null
+++ b/ipc/message_router.h
@@ -0,0 +1,74 @@
+// 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_MESSAGE_ROUTER_H_
+#define IPC_MESSAGE_ROUTER_H_
+
+#include <stdint.h>
+
+#include "base/component_export.h"
+#include "base/containers/id_map.h"
+#include "base/macros.h"
+#include "ipc/ipc_listener.h"
+#include "ipc/ipc_sender.h"
+
+// The MessageRouter handles all incoming messages sent to it by routing them
+// to the correct listener. Routing is based on the Message's routing ID.
+// Since routing IDs are typically assigned asynchronously by the browser
+// process, the MessageRouter has the notion of pending IDs for listeners that
+// have not yet been assigned a routing ID.
+//
+// When a message arrives, the routing ID is used to index the set of routes to
+// find a listener. If a listener is found, then the message is passed to it.
+// Otherwise, the message is ignored if its routing ID is not equal to
+// MSG_ROUTING_CONTROL.
+//
+// The MessageRouter supports the IPC::Sender interface for outgoing messages,
+// but does not define a meaningful implementation of it. The subclass of
+// MessageRouter is intended to provide that if appropriate.
+//
+// The MessageRouter can be used as a concrete class provided its Send method
+// is not called and it does not receive any control messages.
+
+namespace IPC {
+
+class COMPONENT_EXPORT(IPC) MessageRouter : public Listener, public Sender {
+ public:
+ MessageRouter();
+ ~MessageRouter() override;
+
+ // Implemented by subclasses to handle control messages
+ virtual bool OnControlMessageReceived(const Message& msg);
+
+ // Listener implementation:
+ bool OnMessageReceived(const Message& msg) override;
+
+ // Like OnMessageReceived, except it only handles routed messages. Returns
+ // true if the message was dispatched, or false if there was no listener for
+ // that route id.
+ virtual bool RouteMessage(const Message& msg);
+
+ // Sender implementation:
+ bool Send(Message* msg) override;
+
+ // Called to add a listener for a particular message routing ID.
+ // Returns true if succeeded.
+ bool AddRoute(int32_t routing_id, Listener* listener);
+
+ // Called to remove a listener for a particular message routing ID.
+ void RemoveRoute(int32_t routing_id);
+
+ // Returns the Listener associated with |routing_id|.
+ Listener* GetRoute(int32_t routing_id);
+
+ private:
+ // A list of all listeners with assigned routing IDs.
+ base::IDMap<Listener*> routes_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageRouter);
+};
+
+} // namespace IPC
+
+#endif // IPC_MESSAGE_ROUTER_H_
diff --git a/ipc/message_view.cc b/ipc/message_view.cc
new file mode 100644
index 0000000000..d6ff4fb05f
--- /dev/null
+++ b/ipc/message_view.cc
@@ -0,0 +1,30 @@
+// 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.
+
+#include "ipc/message_view.h"
+
+namespace IPC {
+
+MessageView::MessageView() = default;
+
+MessageView::MessageView(
+ const Message& message,
+ base::Optional<std::vector<mojo::native::SerializedHandlePtr>> handles)
+ : buffer_view_(base::make_span<const uint8_t>(
+ static_cast<const uint8_t*>(message.data()),
+ message.size())),
+ handles_(std::move(handles)) {}
+
+MessageView::MessageView(
+ mojo_base::BigBufferView buffer_view,
+ base::Optional<std::vector<mojo::native::SerializedHandlePtr>> handles)
+ : buffer_view_(std::move(buffer_view)), handles_(std::move(handles)) {}
+
+MessageView::MessageView(MessageView&&) = default;
+
+MessageView::~MessageView() = default;
+
+MessageView& MessageView::operator=(MessageView&&) = default;
+
+} // namespace IPC
diff --git a/ipc/message_view.h b/ipc/message_view.h
new file mode 100644
index 0000000000..23e5da6e16
--- /dev/null
+++ b/ipc/message_view.h
@@ -0,0 +1,56 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IPC_MESSAGE_VIEW_H_
+#define IPC_MESSAGE_VIEW_H_
+
+#include <vector>
+
+#include "base/component_export.h"
+#include "base/containers/span.h"
+#include "base/macros.h"
+#include "ipc/ipc_message.h"
+#include "mojo/public/cpp/base/big_buffer.h"
+#include "mojo/public/interfaces/bindings/native_struct.mojom.h"
+
+namespace IPC {
+
+class COMPONENT_EXPORT(IPC_MOJOM) MessageView {
+ public:
+ MessageView();
+ MessageView(
+ const Message& message,
+ base::Optional<std::vector<mojo::native::SerializedHandlePtr>> handles);
+ MessageView(
+ mojo_base::BigBufferView buffer_view,
+ base::Optional<std::vector<mojo::native::SerializedHandlePtr>> handles);
+ MessageView(MessageView&&);
+ ~MessageView();
+
+ MessageView& operator=(MessageView&&);
+
+ const char* data() const {
+ return reinterpret_cast<const char*>(buffer_view_.data().data());
+ }
+
+ uint32_t size() const {
+ return static_cast<uint32_t>(buffer_view_.data().size());
+ }
+
+ mojo_base::BigBufferView TakeBufferView() { return std::move(buffer_view_); }
+
+ base::Optional<std::vector<mojo::native::SerializedHandlePtr>> TakeHandles() {
+ return std::move(handles_);
+ }
+
+ private:
+ mojo_base::BigBufferView buffer_view_;
+ base::Optional<std::vector<mojo::native::SerializedHandlePtr>> handles_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageView);
+};
+
+} // namespace IPC
+
+#endif // IPC_MESSAGE_VIEW_H_
diff --git a/ipc/native_handle_type_converters.cc b/ipc/native_handle_type_converters.cc
new file mode 100644
index 0000000000..a0391b6911
--- /dev/null
+++ b/ipc/native_handle_type_converters.cc
@@ -0,0 +1,49 @@
+// 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 "ipc/native_handle_type_converters.h"
+
+namespace mojo {
+
+// static
+IPC::MessageAttachment::Type
+TypeConverter<IPC::MessageAttachment::Type, native::SerializedHandle_Type>::
+ Convert(native::SerializedHandle_Type type) {
+ switch (type) {
+ case native::SerializedHandle_Type::MOJO_HANDLE:
+ return IPC::MessageAttachment::Type::MOJO_HANDLE;
+ case native::SerializedHandle_Type::PLATFORM_FILE:
+ return IPC::MessageAttachment::Type::PLATFORM_FILE;
+ case native::SerializedHandle_Type::WIN_HANDLE:
+ return IPC::MessageAttachment::Type::WIN_HANDLE;
+ case native::SerializedHandle_Type::MACH_PORT:
+ return IPC::MessageAttachment::Type::MACH_PORT;
+ case native::SerializedHandle_Type::FUCHSIA_HANDLE:
+ return IPC::MessageAttachment::Type::FUCHSIA_HANDLE;
+ }
+ NOTREACHED();
+ return IPC::MessageAttachment::Type::MOJO_HANDLE;
+}
+
+// static
+native::SerializedHandle_Type TypeConverter<
+ native::SerializedHandle_Type,
+ IPC::MessageAttachment::Type>::Convert(IPC::MessageAttachment::Type type) {
+ switch (type) {
+ case IPC::MessageAttachment::Type::MOJO_HANDLE:
+ return native::SerializedHandle_Type::MOJO_HANDLE;
+ case IPC::MessageAttachment::Type::PLATFORM_FILE:
+ return native::SerializedHandle_Type::PLATFORM_FILE;
+ case IPC::MessageAttachment::Type::WIN_HANDLE:
+ return native::SerializedHandle_Type::WIN_HANDLE;
+ case IPC::MessageAttachment::Type::MACH_PORT:
+ return native::SerializedHandle_Type::MACH_PORT;
+ case IPC::MessageAttachment::Type::FUCHSIA_HANDLE:
+ return native::SerializedHandle_Type::FUCHSIA_HANDLE;
+ }
+ NOTREACHED();
+ return native::SerializedHandle_Type::MOJO_HANDLE;
+}
+
+} // namespace mojo
diff --git a/ipc/native_handle_type_converters.h b/ipc/native_handle_type_converters.h
new file mode 100644
index 0000000000..81d26c5a99
--- /dev/null
+++ b/ipc/native_handle_type_converters.h
@@ -0,0 +1,30 @@
+// 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 IPC_NATIVE_HANDLE_CONVERTER_H_
+#define IPC_NATIVE_HANDLE_CONVERTER_H_
+
+#include "ipc/ipc_message_attachment.h"
+#include "mojo/public/cpp/bindings/type_converter.h" // nogncheck
+#include "mojo/public/interfaces/bindings/native_struct.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct TypeConverter<IPC::MessageAttachment::Type,
+ native::SerializedHandle_Type> {
+ static IPC::MessageAttachment::Type Convert(
+ native::SerializedHandle_Type type);
+};
+
+template <>
+struct TypeConverter<native::SerializedHandle_Type,
+ IPC::MessageAttachment::Type> {
+ static native::SerializedHandle_Type Convert(
+ IPC::MessageAttachment::Type type);
+};
+
+} // namespace mojo
+
+#endif // IPC_NATIVE_HANDLE_CONVERTER_H_
diff --git a/ipc/param_traits_log_macros.h b/ipc/param_traits_log_macros.h
new file mode 100644
index 0000000000..fa50bac68b
--- /dev/null
+++ b/ipc/param_traits_log_macros.h
@@ -0,0 +1,52 @@
+// 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_PARAM_TRAITS_LOG_MACROS_H_
+#define IPC_PARAM_TRAITS_LOG_MACROS_H_
+
+#include <string>
+
+// Null out all the macros that need nulling.
+#include "ipc/ipc_message_null_macros.h"
+
+// STRUCT declarations cause corresponding STRUCT_TRAITS declarations to occur.
+#undef IPC_STRUCT_BEGIN_WITH_PARENT
+#undef IPC_STRUCT_MEMBER
+#undef IPC_STRUCT_END
+#define IPC_STRUCT_BEGIN_WITH_PARENT(struct_name, parent) \
+ IPC_STRUCT_TRAITS_BEGIN(struct_name)
+#define IPC_STRUCT_MEMBER(type, name, ...) IPC_STRUCT_TRAITS_MEMBER(name)
+#define IPC_STRUCT_END() IPC_STRUCT_TRAITS_END()
+
+// Set up so next include will generate log methods.
+#undef IPC_STRUCT_TRAITS_BEGIN
+#undef IPC_STRUCT_TRAITS_MEMBER
+#undef IPC_STRUCT_TRAITS_PARENT
+#undef IPC_STRUCT_TRAITS_END
+#define IPC_STRUCT_TRAITS_BEGIN(struct_name) \
+ void ParamTraits<struct_name>::Log(const param_type& p, std::string* l) { \
+ bool needs_comma = false; \
+ l->append("(");
+#define IPC_STRUCT_TRAITS_MEMBER(name) \
+ if (needs_comma) \
+ l->append(", "); \
+ LogParam(p.name, l); \
+ needs_comma = true;
+#define IPC_STRUCT_TRAITS_PARENT(type) \
+ if (needs_comma) \
+ l->append(", "); \
+ ParamTraits<type>::Log(p, l); \
+ needs_comma = true;
+#define IPC_STRUCT_TRAITS_END() \
+ l->append(")"); \
+ }
+
+#undef IPC_ENUM_TRAITS_VALIDATE
+#define IPC_ENUM_TRAITS_VALIDATE(enum_name, validation_expression) \
+ void ParamTraits<enum_name>::Log(const param_type& p, std::string* l) { \
+ LogParam(static_cast<int>(p), l); \
+ }
+
+#endif // IPC_PARAM_TRAITS_LOG_MACROS_H_
+
diff --git a/ipc/param_traits_macros.h b/ipc/param_traits_macros.h
new file mode 100644
index 0000000000..7c4cc69275
--- /dev/null
+++ b/ipc/param_traits_macros.h
@@ -0,0 +1,65 @@
+// 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_PARAM_TRAITS_MACROS_H_
+#define IPC_PARAM_TRAITS_MACROS_H_
+
+#include <string>
+
+// Traits generation for structs.
+#define IPC_STRUCT_TRAITS_BEGIN(struct_name) \
+ namespace IPC { \
+ template <> \
+ struct IPC_MESSAGE_EXPORT ParamTraits<struct_name> { \
+ typedef struct_name param_type; \
+ static void Write(base::Pickle* m, const param_type& p); \
+ static bool Read(const base::Pickle* m, \
+ base::PickleIterator* iter, \
+ param_type* p); \
+ static void Log(const param_type& p, std::string* l); \
+ }; \
+ }
+
+#define IPC_STRUCT_TRAITS_MEMBER(name)
+#define IPC_STRUCT_TRAITS_PARENT(type)
+#define IPC_STRUCT_TRAITS_END()
+
+// Convenience macro for defining enumerated type traits for types which are
+// not range-checked by the IPC system. The author of the message handlers
+// is responsible for all validation. This macro should not need to be
+// subsequently redefined.
+#define IPC_ENUM_TRAITS(type) \
+ IPC_ENUM_TRAITS_VALIDATE(type, true)
+
+// Convenience macro for defining enumerated type traits for types which are
+// range-checked by the IPC system to be in the range of 0..maxvalue inclusive.
+// This macro should not need to be subsequently redefined.
+#define IPC_ENUM_TRAITS_MAX_VALUE(type, maxvalue) \
+ IPC_ENUM_TRAITS_MIN_MAX_VALUE(type, 0, maxvalue)
+
+// Convenience macro for defining enumerated type traits for types which are
+// range-checked by the IPC system to be in the range of minvalue..maxvalue
+// inclusive. This macro should not need to be subsequently redefined.
+// TODO(tsepez): Cast to std::underlying_type<>::type once that is permitted.
+#define IPC_ENUM_TRAITS_MIN_MAX_VALUE(type, minvalue, maxvalue) \
+ IPC_ENUM_TRAITS_VALIDATE( \
+ type, (static_cast<int>(value) >= static_cast<int>(minvalue) && \
+ static_cast<int>(value) <= static_cast<int>(maxvalue)))
+
+// Traits generation for enums. This macro may be redefined later.
+#define IPC_ENUM_TRAITS_VALIDATE(enum_name, validation_expression) \
+ namespace IPC { \
+ template <> \
+ struct IPC_MESSAGE_EXPORT ParamTraits<enum_name> { \
+ typedef enum_name param_type; \
+ static void Write(base::Pickle* m, const param_type& p); \
+ static bool Read(const base::Pickle* m, \
+ base::PickleIterator* iter, \
+ param_type* p); \
+ static void Log(const param_type& p, std::string* l); \
+ }; \
+ }
+
+#endif // IPC_PARAM_TRAITS_MACROS_H_
+
diff --git a/ipc/param_traits_read_macros.h b/ipc/param_traits_read_macros.h
new file mode 100644
index 0000000000..45e635285a
--- /dev/null
+++ b/ipc/param_traits_read_macros.h
@@ -0,0 +1,47 @@
+// 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_PARAM_TRAITS_READ_MACROS_H_
+#define IPC_PARAM_TRAITS_READ_MACROS_H_
+
+// Null out all the macros that need nulling.
+#include "ipc/ipc_message_null_macros.h"
+
+// STRUCT declarations cause corresponding STRUCT_TRAITS declarations to occur.
+#undef IPC_STRUCT_BEGIN_WITH_PARENT
+#undef IPC_STRUCT_MEMBER
+#undef IPC_STRUCT_END
+#define IPC_STRUCT_BEGIN_WITH_PARENT(struct_name, parent) \
+ IPC_STRUCT_TRAITS_BEGIN(struct_name)
+#define IPC_STRUCT_MEMBER(type, name, ...) IPC_STRUCT_TRAITS_MEMBER(name)
+#define IPC_STRUCT_END() IPC_STRUCT_TRAITS_END()
+
+// Set up so next include will generate read methods.
+#undef IPC_STRUCT_TRAITS_BEGIN
+#undef IPC_STRUCT_TRAITS_MEMBER
+#undef IPC_STRUCT_TRAITS_PARENT
+#undef IPC_STRUCT_TRAITS_END
+#define IPC_STRUCT_TRAITS_BEGIN(struct_name) \
+ bool ParamTraits<struct_name>::Read( \
+ const base::Pickle* m, base::PickleIterator* iter, param_type* p) { \
+ return
+#define IPC_STRUCT_TRAITS_MEMBER(name) ReadParam(m, iter, &p->name) &&
+#define IPC_STRUCT_TRAITS_PARENT(type) ParamTraits<type>::Read(m, iter, p) &&
+#define IPC_STRUCT_TRAITS_END() 1; }
+
+#undef IPC_ENUM_TRAITS_VALIDATE
+#define IPC_ENUM_TRAITS_VALIDATE(enum_name, validation_expression) \
+ bool ParamTraits<enum_name>::Read( \
+ const base::Pickle* m, base::PickleIterator* iter, param_type* p) { \
+ int value; \
+ if (!iter->ReadInt(&value)) \
+ return false; \
+ if (!(validation_expression)) \
+ return false; \
+ *p = static_cast<param_type>(value); \
+ return true; \
+ }
+
+#endif // IPC_PARAM_TRAITS_READ_MACROS_H_
+
diff --git a/ipc/param_traits_write_macros.h b/ipc/param_traits_write_macros.h
new file mode 100644
index 0000000000..a1aca4374e
--- /dev/null
+++ b/ipc/param_traits_write_macros.h
@@ -0,0 +1,40 @@
+// 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_PARAM_TRAITS_WRITE_MACROS_H_
+#define IPC_PARAM_TRAITS_WRITE_MACROS_H_
+
+// Null out all the macros that need nulling.
+#include "ipc/ipc_message_null_macros.h"
+
+// STRUCT declarations cause corresponding STRUCT_TRAITS declarations to occur.
+#undef IPC_STRUCT_BEGIN_WITH_PARENT
+#undef IPC_STRUCT_MEMBER
+#undef IPC_STRUCT_END
+#define IPC_STRUCT_BEGIN_WITH_PARENT(struct_name, parent) \
+ IPC_STRUCT_TRAITS_BEGIN(struct_name)
+#define IPC_STRUCT_MEMBER(type, name, ...) IPC_STRUCT_TRAITS_MEMBER(name)
+#define IPC_STRUCT_END() IPC_STRUCT_TRAITS_END()
+
+// Set up so next include will generate write methods.
+#undef IPC_STRUCT_TRAITS_BEGIN
+#undef IPC_STRUCT_TRAITS_MEMBER
+#undef IPC_STRUCT_TRAITS_PARENT
+#undef IPC_STRUCT_TRAITS_END
+#define IPC_STRUCT_TRAITS_BEGIN(struct_name) \
+ void ParamTraits<struct_name>::Write(base::Pickle* m, const param_type& p) {
+#define IPC_STRUCT_TRAITS_MEMBER(name) WriteParam(m, p.name);
+#define IPC_STRUCT_TRAITS_PARENT(type) ParamTraits<type>::Write(m, p);
+#define IPC_STRUCT_TRAITS_END() }
+
+#undef IPC_ENUM_TRAITS_VALIDATE
+#define IPC_ENUM_TRAITS_VALIDATE(enum_name, validation_expression) \
+ void ParamTraits<enum_name>::Write(base::Pickle* m, \
+ const param_type& value) { \
+ DCHECK(validation_expression); \
+ m->WriteInt(static_cast<int>(value)); \
+ }
+
+#endif // IPC_PARAM_TRAITS_WRITE_MACROS_H_
+
diff --git a/ipc/run_all_perftests.cc b/ipc/run_all_perftests.cc
new file mode 100644
index 0000000000..b5948e1375
--- /dev/null
+++ b/ipc/run_all_perftests.cc
@@ -0,0 +1,25 @@
+// 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.
+
+// Copied from mojo/core/test/run_all_perftests.cc.
+
+#include "base/command_line.h"
+#include "base/test/perf_test_suite.h"
+#include "base/test/test_io_thread.h"
+#include "mojo/core/embedder/embedder.h"
+#include "mojo/core/embedder/scoped_ipc_support.h"
+#include "mojo/core/test/test_support_impl.h"
+
+int main(int argc, char** argv) {
+ base::PerfTestSuite test(argc, argv);
+
+ mojo::core::Init();
+ base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart);
+ mojo::core::ScopedIPCSupport ipc_support(
+ test_io_thread.task_runner(),
+ mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);
+ mojo::test::TestSupport::Init(new mojo::core::test::TestSupportImpl());
+
+ return test.Run();
+}
diff --git a/ipc/run_all_unittests.cc b/ipc/run_all_unittests.cc
new file mode 100644
index 0000000000..bca29c6b7d
--- /dev/null
+++ b/ipc/run_all_unittests.cc
@@ -0,0 +1,35 @@
+// 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/bind.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 "build/build_config.h"
+#include "mojo/core/embedder/embedder.h"
+#include "mojo/core/embedder/scoped_ipc_support.h"
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include "base/mac/mach_port_broker.h"
+#endif
+
+int main(int argc, char** argv) {
+ base::TestSuite test_suite(argc, argv);
+ mojo::core::Init();
+ base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart);
+ mojo::core::ScopedIPCSupport ipc_support(
+ test_io_thread.task_runner(),
+ mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ base::MachPortBroker mach_broker("mojo_test");
+ CHECK(mach_broker.Init());
+ mojo::core::SetMachPortProvider(&mach_broker);
+#endif
+
+ return base::LaunchUnitTests(
+ argc, argv,
+ base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite)));
+}
diff --git a/ipc/struct_constructor_macros.h b/ipc/struct_constructor_macros.h
new file mode 100644
index 0000000000..43feab863b
--- /dev/null
+++ b/ipc/struct_constructor_macros.h
@@ -0,0 +1,21 @@
+// 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_STRUCT_CONSTRUCTOR_MACROS_H_
+#define IPC_STRUCT_CONSTRUCTOR_MACROS_H_
+
+// Null out all the macros that need nulling.
+#include "ipc/ipc_message_null_macros.h"
+
+// Set up so next include will generate constructors.
+#undef IPC_STRUCT_BEGIN_WITH_PARENT
+#undef IPC_STRUCT_MEMBER
+#undef IPC_STRUCT_END
+#define IPC_STRUCT_BEGIN_WITH_PARENT(struct_name, parent) \
+ struct_name::struct_name() : parent()
+#define IPC_STRUCT_MEMBER(type, name, ...) , name(__VA_ARGS__)
+#define IPC_STRUCT_END() {}
+
+#endif // IPC_STRUCT_CONSTRUCTOR_MACROS_H_
+
diff --git a/ipc/struct_destructor_macros.h b/ipc/struct_destructor_macros.h
new file mode 100644
index 0000000000..ccb46f9493
--- /dev/null
+++ b/ipc/struct_destructor_macros.h
@@ -0,0 +1,17 @@
+// 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_STRUCT_DESTRUCTOR_MACROS_H_
+#define IPC_STRUCT_DESTRUCTOR_MACROS_H_
+
+// Null out all the macros that need nulling.
+#include "ipc/ipc_message_null_macros.h"
+
+// Set up so next include will generate destructors.
+#undef IPC_STRUCT_BEGIN_WITH_PARENT
+#define IPC_STRUCT_BEGIN_WITH_PARENT(struct_name, parent) \
+ struct_name::~struct_name() {}
+
+#endif // IPC_STRUCT_DESTRUCTOR_MACROS_H_
+
diff --git a/ipc/sync_socket_unittest.cc b/ipc/sync_socket_unittest.cc
new file mode 100644
index 0000000000..f9db649a3a
--- /dev/null
+++ b/ipc/sync_socket_unittest.cc
@@ -0,0 +1,306 @@
+// 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/sync_socket.h"
+
+#include <stddef.h>
+#include <stdio.h>
+#include <memory>
+#include <sstream>
+#include <string>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread.h"
+#include "build/build_config.h"
+#include "ipc/ipc_test_base.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_POSIX)
+#include "base/file_descriptor_posix.h"
+#endif
+
+// IPC messages for testing ----------------------------------------------------
+
+#define IPC_MESSAGE_IMPL
+#include "ipc/ipc_message_macros.h"
+
+#define IPC_MESSAGE_START TestMsgStart
+
+// Message class to pass a base::SyncSocket::Handle to another process. This
+// is not as easy as it sounds, because of the differences in transferring
+// Windows HANDLEs versus posix file descriptors.
+#if defined(OS_WIN)
+IPC_MESSAGE_CONTROL1(MsgClassSetHandle, base::SyncSocket::Handle)
+#elif defined(OS_POSIX)
+IPC_MESSAGE_CONTROL1(MsgClassSetHandle, base::FileDescriptor)
+#endif
+
+// Message class to pass a response to the server.
+IPC_MESSAGE_CONTROL1(MsgClassResponse, std::string)
+
+// Message class to tell the server to shut down.
+IPC_MESSAGE_CONTROL0(MsgClassShutdown)
+
+// -----------------------------------------------------------------------------
+
+namespace {
+
+const char kHelloString[] = "Hello, SyncSocket Client";
+const size_t kHelloStringLength = arraysize(kHelloString);
+
+// The SyncSocket server listener class processes two sorts of
+// messages from the client.
+class SyncSocketServerListener : public IPC::Listener {
+ public:
+ SyncSocketServerListener() : chan_(NULL) {
+ }
+
+ void Init(IPC::Channel* chan) {
+ chan_ = chan;
+ }
+
+ bool OnMessageReceived(const IPC::Message& msg) override {
+ if (msg.routing_id() == MSG_ROUTING_CONTROL) {
+ IPC_BEGIN_MESSAGE_MAP(SyncSocketServerListener, msg)
+ IPC_MESSAGE_HANDLER(MsgClassSetHandle, OnMsgClassSetHandle)
+ IPC_MESSAGE_HANDLER(MsgClassShutdown, OnMsgClassShutdown)
+ IPC_END_MESSAGE_MAP()
+ }
+ return true;
+ }
+
+ private:
+ // This sort of message is sent first, causing the transfer of
+ // the handle for the SyncSocket. This message sends a buffer
+ // on the SyncSocket and then sends a response to the client.
+#if defined(OS_WIN)
+ void OnMsgClassSetHandle(const base::SyncSocket::Handle handle) {
+ SetHandle(handle);
+ }
+#elif defined(OS_POSIX)
+ void OnMsgClassSetHandle(const base::FileDescriptor& fd_struct) {
+ SetHandle(fd_struct.fd);
+ }
+#else
+# error "What platform?"
+#endif // defined(OS_WIN)
+
+ void SetHandle(base::SyncSocket::Handle handle) {
+ base::SyncSocket sync_socket(handle);
+ EXPECT_EQ(sync_socket.Send(kHelloString, kHelloStringLength),
+ kHelloStringLength);
+ IPC::Message* msg = new MsgClassResponse(kHelloString);
+ EXPECT_TRUE(chan_->Send(msg));
+ }
+
+ // When the client responds, it sends back a shutdown message,
+ // which causes the message loop to exit.
+ void OnMsgClassShutdown() { base::RunLoop::QuitCurrentWhenIdleDeprecated(); }
+
+ IPC::Channel* chan_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncSocketServerListener);
+};
+
+// Runs the fuzzing server child mode. Returns when the preset number of
+// messages have been received.
+DEFINE_IPC_CHANNEL_MOJO_TEST_CLIENT(SyncSocketServerClient) {
+ SyncSocketServerListener listener;
+ Connect(&listener);
+ listener.Init(channel());
+ base::RunLoop().Run();
+ Close();
+}
+
+// The SyncSocket client listener only processes one sort of message,
+// a response from the server.
+class SyncSocketClientListener : public IPC::Listener {
+ public:
+ SyncSocketClientListener() = default;
+
+ void Init(base::SyncSocket* socket, IPC::Channel* chan) {
+ socket_ = socket;
+ chan_ = chan;
+ }
+
+ bool OnMessageReceived(const IPC::Message& msg) override {
+ if (msg.routing_id() == MSG_ROUTING_CONTROL) {
+ IPC_BEGIN_MESSAGE_MAP(SyncSocketClientListener, msg)
+ IPC_MESSAGE_HANDLER(MsgClassResponse, OnMsgClassResponse)
+ IPC_END_MESSAGE_MAP()
+ }
+ return true;
+ }
+
+ private:
+ // When a response is received from the server, it sends the same
+ // string as was written on the SyncSocket. These are compared
+ // and a shutdown message is sent back to the server.
+ void OnMsgClassResponse(const std::string& str) {
+ // We rely on the order of sync_socket.Send() and chan_->Send() in
+ // the SyncSocketServerListener object.
+ EXPECT_EQ(kHelloStringLength, socket_->Peek());
+ char buf[kHelloStringLength];
+ socket_->Receive(static_cast<void*>(buf), kHelloStringLength);
+ EXPECT_EQ(strcmp(str.c_str(), buf), 0);
+ // After receiving from the socket there should be no bytes left.
+ EXPECT_EQ(0U, socket_->Peek());
+ IPC::Message* msg = new MsgClassShutdown();
+ EXPECT_TRUE(chan_->Send(msg));
+ base::RunLoop::QuitCurrentWhenIdleDeprecated();
+ }
+
+ base::SyncSocket* socket_;
+ IPC::Channel* chan_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncSocketClientListener);
+};
+
+using SyncSocketTest = IPCChannelMojoTestBase;
+
+TEST_F(SyncSocketTest, SanityTest) {
+ Init("SyncSocketServerClient");
+
+ SyncSocketClientListener listener;
+ CreateChannel(&listener);
+ // Create a pair of SyncSockets.
+ base::SyncSocket pair[2];
+ base::SyncSocket::CreatePair(&pair[0], &pair[1]);
+ // Immediately after creation there should be no pending bytes.
+ EXPECT_EQ(0U, pair[0].Peek());
+ EXPECT_EQ(0U, pair[1].Peek());
+ base::SyncSocket::Handle target_handle;
+ // Connect the channel and listener.
+ ASSERT_TRUE(ConnectChannel());
+ listener.Init(&pair[0], channel());
+#if defined(OS_WIN)
+ // On windows we need to duplicate the handle into the server process.
+ BOOL retval = DuplicateHandle(GetCurrentProcess(), pair[1].handle(),
+ client_process().Handle(), &target_handle,
+ 0, FALSE, DUPLICATE_SAME_ACCESS);
+ EXPECT_TRUE(retval);
+ // Set up a message to pass the handle to the server.
+ IPC::Message* msg = new MsgClassSetHandle(target_handle);
+#else
+ target_handle = pair[1].handle();
+ // Set up a message to pass the handle to the server.
+ base::FileDescriptor filedesc(target_handle, false);
+ IPC::Message* msg = new MsgClassSetHandle(filedesc);
+#endif // defined(OS_WIN)
+ EXPECT_TRUE(sender()->Send(msg));
+ // Use the current thread as the I/O thread.
+ base::RunLoop().Run();
+ // Shut down.
+ pair[0].Close();
+ pair[1].Close();
+ EXPECT_TRUE(WaitForClientShutdown());
+ DestroyChannel();
+}
+
+// A blocking read operation that will block the thread until it receives
+// |length| bytes of packets or Shutdown() is called on another thread.
+static void BlockingRead(base::SyncSocket* socket, char* buf,
+ size_t length, size_t* received) {
+ DCHECK(buf != NULL);
+ // Notify the parent thread that we're up and running.
+ socket->Send(kHelloString, kHelloStringLength);
+ *received = socket->Receive(buf, length);
+}
+
+// Tests that we can safely end a blocking Receive operation on one thread
+// from another thread by disconnecting (but not closing) the socket.
+TEST_F(SyncSocketTest, DisconnectTest) {
+ base::CancelableSyncSocket pair[2];
+ ASSERT_TRUE(base::CancelableSyncSocket::CreatePair(&pair[0], &pair[1]));
+
+ base::Thread worker("BlockingThread");
+ worker.Start();
+
+ // Try to do a blocking read from one of the sockets on the worker thread.
+ char buf[0xff];
+ size_t received = 1U; // Initialize to an unexpected value.
+ worker.task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&BlockingRead, &pair[0], &buf[0], arraysize(buf), &received));
+
+ // Wait for the worker thread to say hello.
+ char hello[kHelloStringLength] = {0};
+ pair[1].Receive(&hello[0], sizeof(hello));
+ EXPECT_EQ(0, strcmp(hello, kHelloString));
+ // Give the worker a chance to start Receive().
+ base::PlatformThread::YieldCurrentThread();
+
+ // Now shut down the socket that the thread is issuing a blocking read on
+ // which should cause Receive to return with an error.
+ pair[0].Shutdown();
+
+ worker.Stop();
+
+ EXPECT_EQ(0U, received);
+}
+
+// Tests that read is a blocking operation.
+TEST_F(SyncSocketTest, BlockingReceiveTest) {
+ base::CancelableSyncSocket pair[2];
+ ASSERT_TRUE(base::CancelableSyncSocket::CreatePair(&pair[0], &pair[1]));
+
+ base::Thread worker("BlockingThread");
+ worker.Start();
+
+ // Try to do a blocking read from one of the sockets on the worker thread.
+ char buf[kHelloStringLength] = {0};
+ size_t received = 1U; // Initialize to an unexpected value.
+ worker.task_runner()->PostTask(FROM_HERE,
+ base::Bind(&BlockingRead, &pair[0], &buf[0],
+ kHelloStringLength, &received));
+
+ // Wait for the worker thread to say hello.
+ char hello[kHelloStringLength] = {0};
+ pair[1].Receive(&hello[0], sizeof(hello));
+ EXPECT_EQ(0, strcmp(hello, kHelloString));
+ // Give the worker a chance to start Receive().
+ base::PlatformThread::YieldCurrentThread();
+
+ // Send a message to the socket on the blocking thead, it should free the
+ // socket from Receive().
+ pair[1].Send(kHelloString, kHelloStringLength);
+ worker.Stop();
+
+ // Verify the socket has received the message.
+ EXPECT_TRUE(strcmp(buf, kHelloString) == 0);
+ EXPECT_EQ(kHelloStringLength, received);
+}
+
+// Tests that the write operation is non-blocking and returns immediately
+// when there is insufficient space in the socket's buffer.
+TEST_F(SyncSocketTest, NonBlockingWriteTest) {
+ base::CancelableSyncSocket pair[2];
+ ASSERT_TRUE(base::CancelableSyncSocket::CreatePair(&pair[0], &pair[1]));
+
+ // Fill up the buffer for one of the socket, Send() should not block the
+ // thread even when the buffer is full.
+ while (pair[0].Send(kHelloString, kHelloStringLength) != 0) {}
+
+ // Data should be avialble on another socket.
+ size_t bytes_in_buffer = pair[1].Peek();
+ EXPECT_NE(bytes_in_buffer, 0U);
+
+ // No more data can be written to the buffer since socket has been full,
+ // verify that the amount of avialble data on another socket is unchanged.
+ EXPECT_EQ(0U, pair[0].Send(kHelloString, kHelloStringLength));
+ EXPECT_EQ(bytes_in_buffer, pair[1].Peek());
+
+ // Read from another socket to free some space for a new write.
+ char hello[kHelloStringLength] = {0};
+ pair[1].Receive(&hello[0], sizeof(hello));
+
+ // Should be able to write more data to the buffer now.
+ EXPECT_EQ(kHelloStringLength, pair[0].Send(kHelloString, kHelloStringLength));
+}
+
+} // namespace
diff --git a/libchrome_tools/jni_registration_generator_helper.sh b/libchrome_tools/jni_registration_generator_helper.sh
new file mode 100755
index 0000000000..bc200d0a19
--- /dev/null
+++ b/libchrome_tools/jni_registration_generator_helper.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+#
+# 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.
+#
+# 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
+
+"${jni_generator}" --sources_files=<(printf "%q\n" "${files[@]}") "${args[@]}"
diff --git a/libchrome_tools/mojom_source_generator.sh b/libchrome_tools/mojom_source_generator.sh
deleted file mode 100755
index a0feb1f65c..0000000000
--- a/libchrome_tools/mojom_source_generator.sh
+++ /dev/null
@@ -1,144 +0,0 @@
-#!/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=<abs_path> --package=<package_directory>
-# --output_dir=<output_directory>
-# [<extra_args_for_bindings_generator>] <list_of_mojom_files>
-
-set -e
-
-args=()
-files=()
-includes=()
-gen_dirs=()
-
-mojom_bindings_generator=""
-package=""
-output_dir=""
-generators=""
-
-# 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+=("--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})"
- ;;
- -I=*)
- includes+=("$(get_abs_path "${arg#'-I='}")")
- ;;
- --*)
- args+=("${arg}")
- ;;
- *.mojom)
- # Add all .mojom files directly as files.
- files+=("$(get_abs_path ${arg})")
- ;;
- *.p)
- # Get the gen/ dir of any pickle (.p) file so that the bindings
- # generator
- # can get the correct path.
- gen_dirs+=("$(get_abs_path "${arg}" | sed -e 's@/gen/.*$@/gen@')")
- esac
-done
-
-# Add the current package as include path, and then rewrite all the include
-# paths so that the bindings generator can relativize the paths correctly.
-includes+=("$(pwd)/${package}")
-includes=($(printf -- "%q\n" "${includes[@]}" | sed -e 's/.*/-I=&:&/'))
-
-# Remove duplicates from the list of gen/ directories that contain the pickle
-# files.
-if [[ "${#gen_dirs[@]}" -ge 1 ]]; then
- gen_dirs=($(printf -- "--gen_dir=%q\n" "${gen_dirs[@]}" | sort -u))
-fi
-
-"${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`/$package/}"
- rel_dir="${rel_path%/*}"
-
- mkdir -p "${output_dir}/${rel_dir}"
-
- "${mojom_bindings_generator}" --use_bundled_pylibs generate \
- -o "${output_dir}" "${args[@]}" \
- --bytecode_path="${bytecode_path}" \
- "${gen_dirs[@]}" \
- -d "${package}" \
- "${includes[@]}" \
- --generators="${generators}" "${file}"
- if [[ "${generators}" =~ .*c\+\+.* ]] ; then
- "${mojom_bindings_generator}" --use_bundled_pylibs generate \
- -o "${output_dir}" \
- --generate_non_variant_code "${args[@]}" \
- "${gen_dirs[@]}" \
- -d "${package}" \
- "${includes[@]}" \
- --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/580fcef.patch b/libchrome_tools/patch/580fcef.patch
new file mode 100644
index 0000000000..cff82bc633
--- /dev/null
+++ b/libchrome_tools/patch/580fcef.patch
@@ -0,0 +1,112 @@
+From 580fcef90ab970ad37ea9f7059373f773b3e55d2 Mon Sep 17 00:00:00 2001
+From: Jakub Pawlowski <jpawlowski@google.com>
+Date: Fri, 03 Aug 2018 08:46:10 +0000
+Subject: [PATCH] Fix data_types_definition.tmpl
+
+This template can generate code that does not compile, if there are
+multiple fields with different versions where at least one has version 0
+and end up being laid out in a way that does not match the ordinal order
+
+Sample Mojo file:
+// Describes ARC package.
+struct ArcPackageInfo {
+ string package_name;
+ int32 package_version;
+ int64 last_backup_android_id;
+ int64 last_backup_time;
+ bool sync; // true if package installation should be synced
+ [MinVersion=11] bool system; // true if package is system package.
+ // true if package registers VPNService intent.
+ [MinVersion=25] bool vpn_provider;
+};
+
+Sample badly generated code (no closing "}" for last if):
+
+ @SuppressWarnings("unchecked")
+ public static ArcPackageInfo decode(org.chromium.mojo.bindings.Decoder decoder0) {
+ if (decoder0 == null) {
+ return null;
+ }
+ decoder0.increaseStackDepth();
+ ArcPackageInfo result;
+ try {
+ org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY);
+ final int elementsOrVersion = mainDataHeader.elementsOrVersion;
+ result = new ArcPackageInfo(elementsOrVersion);
+ {
+
+ result.packageName = decoder0.readString(8, false);
+ }
+ {
+
+ result.packageVersion = decoder0.readInt(16);
+ }
+ {
+
+ result.sync = decoder0.readBoolean(20, 0);
+ }
+ if (elementsOrVersion >= 11) {
+ {
+
+ result.system = decoder0.readBoolean(20, 1);
+ }
+ }
+ if (elementsOrVersion >= 25) {
+ {
+
+ result.vpnProvider = decoder0.readBoolean(20, 2);
+ }
+ }
+ if (elementsOrVersion >= 0) {
+ {
+
+ result.lastBackupAndroidId = decoder0.readLong(24);
+ }
+ {
+
+ result.lastBackupTime = decoder0.readLong(32);
+ }
+ } finally {
+ decoder0.decreaseStackDepth();
+ }
+ return result;
+ }
+
+Change-Id: I4c1b573a71b20cc6a0828a2cceff6bbfbb4ac5bc
+Reviewed-on: https://chromium-review.googlesource.com/1158702
+Reviewed-by: Luis Hector Chavez <lhchavez@chromium.org>
+Reviewed-by: Ken Rockot <rockot@chromium.org>
+Commit-Queue: Jakub x Jakub Pawlowski <jpawlowski@google.com>
+Cr-Commit-Position: refs/heads/master@{#580480}
+---
+
+diff --git a/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl
+index 59c6fee..7af57bd 100644
+--- a/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl
++++ b/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl
+@@ -175,6 +175,7 @@
+ org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY);
+ final int elementsOrVersion = mainDataHeader.elementsOrVersion;
+ result = new {{struct|name}}(elementsOrVersion);
++
+ {%- set prev_ver = [0] %}
+ {%- for byte in struct.bytes %}
+ {%- for packed_field in byte.packed_fields %}
+@@ -183,7 +184,9 @@
+ }
+ {%- endif %}
+ {%- set _ = prev_ver.append(packed_field.min_version) %}
++{%- if prev_ver[-1] != 0 %}
+ if (elementsOrVersion >= {{packed_field.min_version}}) {
++{%- endif %}
+ {%- endif %}
+ {
+ {{decode('result.' ~ packed_field.field|name, packed_field.field.kind, 8+packed_field.offset, packed_field.bit)|indent(16)}}
+@@ -193,6 +196,7 @@
+ {%- if prev_ver[-1] != 0 %}
+ }
+ {%- endif %}
++
+ } finally {
+ decoder0.decreaseStackDepth();
+ }
diff --git a/libchrome_tools/patch/8fbafc9.patch b/libchrome_tools/patch/8fbafc9.patch
new file mode 100644
index 0000000000..d872f9e79d
--- /dev/null
+++ b/libchrome_tools/patch/8fbafc9.patch
@@ -0,0 +1,25 @@
+From 8fbafc974b92d26780d7e2a8c36856ff689e8f6b Mon Sep 17 00:00:00 2001
+From: Jakub Pawlowski <jpawlowski@google.com>
+Date: Thu, 27 Sep 2018 19:10:56 +0000
+Subject: [PATCH] Add missing include for condition variable in base/test/test_mock_time_task_runner.h
+
+Change-Id: Ia6b77632aab5df842c8878fba6fc96bf405a28de
+Reviewed-on: https://chromium-review.googlesource.com/1249488
+Reviewed-by: Luis Hector Chavez <lhchavez@chromium.org>
+Reviewed-by: Gabriel Charette <gab@chromium.org>
+Commit-Queue: Gabriel Charette <gab@chromium.org>
+Cr-Commit-Position: refs/heads/master@{#594806}
+---
+
+diff --git a/base/test/test_mock_time_task_runner.h b/base/test/test_mock_time_task_runner.h
+index dd7274c..dcfbcbd 100644
+--- a/base/test/test_mock_time_task_runner.h
++++ b/base/test/test_mock_time_task_runner.h
+@@ -17,6 +17,7 @@
+ #include "base/macros.h"
+ #include "base/run_loop.h"
+ #include "base/single_thread_task_runner.h"
++#include "base/synchronization/condition_variable.h"
+ #include "base/synchronization/lock.h"
+ #include "base/test/test_pending_task.h"
+ #include "base/threading/thread_checker_impl.h"
diff --git a/libchrome_tools/patch/ContextUtils.patch b/libchrome_tools/patch/ContextUtils.patch
new file mode 100644
index 0000000000..6e1a484f74
--- /dev/null
+++ b/libchrome_tools/patch/ContextUtils.patch
@@ -0,0 +1,18 @@
+diff --git a/base/android/java/src/org/chromium/base/ContextUtils.java b/base/android/java/src/org/chromium/base/ContextUtils.java
+index 8284cd1..c648e01 100644
+--- a/base/android/java/src/org/chromium/base/ContextUtils.java
++++ b/base/android/java/src/org/chromium/base/ContextUtils.java
+@@ -100,9 +100,10 @@ public class ContextUtils {
+ // that use Robolectric and set the application context manually. Instead of changing all
+ // tests that do so, the call was put here instead.
+ // TODO(mheikal): Require param to be of type Application
+- if (appContext instanceof Application) {
+- ApplicationStatus.initialize((Application) appContext);
+- }
++ // Disabled on libchrome
++ // if (appContext instanceof Application) {
++ // ApplicationStatus.initialize((Application) appContext);
++ // }
+ initJavaSideApplicationContext(appContext);
+ Holder.sSharedPreferences = fetchAppSharedPreferences();
+ }
diff --git a/ThreadLocalStorage-Add-a-function-to-destroy-pthread.patch b/libchrome_tools/patch/ThreadLocalStorage-Add-a-function-to-destroy-pthread.patch
index 2c214379b3..2c214379b3 100644
--- a/ThreadLocalStorage-Add-a-function-to-destroy-pthread.patch
+++ b/libchrome_tools/patch/ThreadLocalStorage-Add-a-function-to-destroy-pthread.patch
diff --git a/libchrome_tools/patch/allocator_shim.patch b/libchrome_tools/patch/allocator_shim.patch
index e87f614ece..f5fe9e8013 100644
--- a/libchrome_tools/patch/allocator_shim.patch
+++ b/libchrome_tools/patch/allocator_shim.patch
@@ -2,7 +2,7 @@
--- a/base/allocator/allocator_shim.cc
+++ b/base/allocator/allocator_shim.cc
-@@ -299,7 +299,7 @@ ALWAYS_INLINE void ShimFreeDefiniteSize(
+@@ -285,7 +285,7 @@ ALWAYS_INLINE void ShimFreeDefiniteSize(void* ptr, size_t size, void* context) {
#include "base/allocator/allocator_shim_override_cpp_symbols.h"
#endif
diff --git a/libchrome_tools/patch/buildflag_header.patch b/libchrome_tools/patch/buildflag_header.patch
index 69cc417b7d..55abdc044a 100644
--- a/libchrome_tools/patch/buildflag_header.patch
+++ b/libchrome_tools/patch/buildflag_header.patch
@@ -2,26 +2,50 @@
# 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"
++++ b/base/allocator/buildflags.h
+@@ -0,0 +1,5 @@
++#ifndef BASE_ALLOCATOR_BUILDFLAGS_H_
++#define BASE_ALLOCATOR_BUILDFLAGS_H_
++#include "build/buildflag.h"
++#define BUILDFLAG_INTERNAL_USE_ALLOCATOR_SHIM() (0)
++#endif // BASE_ALLOCATOR_BUILDFLAGS_H_
+--- /dev/null
++++ b/base/android/java/src/org/chromium/base/BuildConfig.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.
+
-+#ifndef BASE_ALLOCATOR_FEATURES_H_
-+#define BASE_ALLOCATOR_FEATURES_H_
++package org.chromium.base;
+
-+#include "build/buildflag.h"
++/**
++ * Build configuration. Generated on a per-target basis.
++ */
++public class BuildConfig {
++
++
++ public static final String FIREBASE_APP_ID = "";
+
-+#if defined(__APPLE__) || defined(ANDROID)
-+#define BUILDFLAG_INTERNAL_USE_EXPERIMENTAL_ALLOCATOR_SHIM() (0)
-+#else
-+#define BUILDFLAG_INTERNAL_USE_EXPERIMENTAL_ALLOCATOR_SHIM() (1)
-+#endif
++ public static final boolean DCHECK_IS_ON = false;
+
-+#endif // BASE_ALLOCATOR_FEATURES_H_
++ // The ID of the android string resource that stores the product version.
++ // This layer of indirection is necessary to make the resource dependency
++ // optional for android_apk targets/base_java (ex. for cronet).
++ public static final int R_STRING_PRODUCT_VERSION = 0;
++}
--- /dev/null
-+++ b/base/debug/debugging_flags.h
-@@ -0,0 +1,8 @@
++++ b/base/cfi_buildflags.h
+@@ -0,0 +1,7 @@
++// Generated by build/write_buildflag_header.py
++// From "base_debugging_flags"
++#ifndef BASE_CFI_BUILDFLAGS_H_
++#define BASE_CFI_BUILDFLAGS_H_
++#include "build/buildflag.h"
++#define BUILDFLAG_INTERNAL_CFI_ICALL_CHECK() (0)
++#endif // BASE_CFI_BUILDFLAGS_H_
+--- /dev/null
++++ b/base/debug/debugging_buildflags.h
+@@ -0,0 +1,12 @@
+// Generated by build/write_buildflag_header.py
+// From "base_debugging_flags"
+#ifndef BASE_DEBUG_DEBUGGING_FLAGS_H_
@@ -29,4 +53,45 @@
+#include "build/buildflag.h"
+#define BUILDFLAG_INTERNAL_ENABLE_PROFILING() (0)
+#define BUILDFLAG_INTERNAL_ENABLE_MEMORY_TASK_PROFILER() (0)
++#define BUILDFLAG_INTERNAL_CAN_UNWIND_WITH_FRAME_POINTERS() (0)
++#define BUILDFLAG_INTERNAL_ENABLE_LOCATION_SOURCE() (0)
++#define BUILDFLAG_INTERNAL_CFI_ENFORCEMENT_TRAP() (0)
++#define BUILDFLAG_INTERNAL_ENABLE_MUTEX_PRIORITY_INHERITANCE() (0)
+#endif // BASE_DEBUG_DEBUGGING_FLAGS_H_
+--- /dev/null
++++ b/base/memory/protected_memory_buildflags.h
+@@ -0,0 +1,7 @@
++// Generated by build/write_buildflag_header.py
++// From "base_debugging_flags"
++#ifndef BASE_PROTECTED_MEMORY_BUILDFLAGS_H_
++#define BASE_PROTECTED_MEMORY_BUILDFLAGS_H_
++#include "build/buildflag.h"
++#define BUILDFLAG_INTERNAL_USE_LLD() (0)
++#endif // BASE_PROTECTED_MEMORY_BUILDFLAGS_H_
+--- /dev/null
++++ b/base/synchronization/synchronization_buildflags.h
+@@ -0,0 +1,4 @@
++#ifndef BASE_SYNCHRONIZATION_BUILDFLAGS_H_
++#define BASE_SYNCHRONIZATION_BUILDFLAGS_H_
++#include "build/buildflag.h"
++#endif // BASE_SYNCHRONIZATION_BUILDFLAGS_H_
+--- /dev/null
++++ b/ipc/ipc_buildflags.h
+@@ -0,0 +1,8 @@
++#ifndef CPP_IPC_BUILDFLAGS_H_
++#define CPP_IPC_BUILDFLAGS_H_
++
++#include <build/buildflag.h>
++
++#define BUILDFLAG_INTERNAL_IPC_MESSAGE_LOG_ENABLED() (0)
++
++#endif // CPP_IPC_BUILDFLAGS_H_
+--- /dev/null
++++ b/mojo/public/cpp/bindings/mojo_buildflags.h
+@@ -0,0 +1,6 @@
++#ifndef CPP_MOJO_BUILD_FLAGS_H_
++#define CPP_MOJO_BUILD_FLAGS_H_
++
++#include <build/buildflag.h>
++#define BUILDFLAG_INTERNAL_MOJO_TRACE_ENABLED() (0)
++#endif // CPP_MOJO_BUILD_FLAGS_H_
diff --git a/libchrome_tools/patch/c7ce19d.patch b/libchrome_tools/patch/c7ce19d.patch
new file mode 100644
index 0000000000..1d6b9d9682
--- /dev/null
+++ b/libchrome_tools/patch/c7ce19d.patch
@@ -0,0 +1,28 @@
+From c7ce19d52a7e6f3e69e66107650992765da559b7 Mon Sep 17 00:00:00 2001
+From: Jakub Pawlowski <jpawlowski@google.com>
+Date: Mon, 06 Aug 2018 03:06:46 +0000
+Subject: [PATCH] Make LAZY_INSTANCE_INITIALIZER -Wmissing-field-initializers friendly
+
+If libbase is compiled with -Wmissing-field-initializers this is causing
+warning to be generated.
+
+Change-Id: I446160d4c94bb59dd23f2f151004a8bfaeae832d
+Reviewed-on: https://chromium-review.googlesource.com/1161927
+Reviewed-by: Gabriel Charette <gab@chromium.org>
+Commit-Queue: Luis Hector Chavez <lhchavez@chromium.org>
+Cr-Commit-Position: refs/heads/master@{#580794}
+---
+
+diff --git a/base/lazy_instance.h b/base/lazy_instance.h
+index 36d3158..4449373 100644
+--- a/base/lazy_instance.h
++++ b/base/lazy_instance.h
+@@ -55,7 +55,7 @@
+
+ // LazyInstance uses its own struct initializer-list style static
+ // initialization, which does not require a constructor.
+-#define LAZY_INSTANCE_INITIALIZER {0}
++#define LAZY_INSTANCE_INITIALIZER {}
+
+ namespace base {
+
diff --git a/libchrome_tools/patch/dmg_fp.patch b/libchrome_tools/patch/dmg_fp.patch
index 50427716c0..95cc67d006 100644
--- a/libchrome_tools/patch/dmg_fp.patch
+++ b/libchrome_tools/patch/dmg_fp.patch
@@ -3,18 +3,18 @@
--- a/base/strings/string_number_conversions.cc
+++ b/base/strings/string_number_conversions.cc
-@@ -15,7 +15,6 @@
- #include "base/logging.h"
+@@ -16,7 +16,6 @@
#include "base/numerics/safe_math.h"
#include "base/scoped_clear_errno.h"
+ #include "base/strings/utf_string_conversions.h"
-#include "base/third_party/dmg_fp/dmg_fp.h"
namespace base {
-@@ -369,10 +368,18 @@ string16 SizeTToString16(size_t value) {
+@@ -361,20 +360,35 @@ string16 NumberToString16(unsigned long long value) {
}
- std::string DoubleToString(double value) {
+ std::string NumberToString(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);
@@ -33,17 +33,39 @@
+ return ret;
}
- bool StringToInt(const StringPiece& input, int* output) {
-@@ -416,14 +423,10 @@ bool StringToSizeT(const StringPiece16&
+ base::string16 NumberToString16(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);
++ auto tmp = std::to_string(value);
++ base::string16 ret(tmp.c_str(), tmp.c_str() + tmp.length());
+
+- // The number will be ASCII. This creates the string using the "input
+- // iterator" variant which promotes from 8-bit to 16-bit via "=".
+- return base::string16(&buffer[0], &buffer[strlen(buffer)]);
++ // 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(StringPiece input, int* output) {
+@@ -418,14 +432,10 @@ bool StringToSizeT(StringPiece16 input, size_t* output) {
}
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;
+ char* endptr = nullptr;
- *output = dmg_fp::strtod(input.c_str(), &endptr);
-+ char* endptr = nullptr;
+ *output = strtod(input.c_str(), &endptr);
// Cases to return false:
@@ -51,7 +73,7 @@
// - 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
+@@ -433,10 +443,11 @@ bool StringToDouble(const std::string& input, double* output) {
// 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
@@ -66,27 +88,9 @@
}
// 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
+@@ -754,20 +754,8 @@ TEST(StringNumberConversionsTest, StringToDouble) {
{"9e999", HUGE_VAL, false},
{"9e1999", HUGE_VAL, false},
{"9e19999", HUGE_VAL, false},
@@ -109,7 +113,7 @@
{"1e-2", 0.01, true},
{"42 ", 42.0, false},
{" 1e-2", 0.01, false},
-@@ -795,7 +783,8 @@ TEST(StringNumberConversionsTest, String
+@@ -797,7 +785,8 @@ TEST(StringNumberConversionsTest, StringToDouble) {
for (size_t i = 0; i < arraysize(cases); ++i) {
double output;
errno = 1;
@@ -119,7 +123,7 @@
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
+@@ -818,13 +807,13 @@ TEST(StringNumberConversionsTest, DoubleToString) {
double input;
const char* expected;
} cases[] = {
@@ -139,18 +143,18 @@
};
for (size_t i = 0; i < arraysize(cases); ++i) {
-@@ -833,12 +822,12 @@ TEST(StringNumberConversionsTest, Double
+@@ -836,12 +825,12 @@ TEST(StringNumberConversionsTest, DoubleToString) {
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));
+- EXPECT_EQ("1335179083776", NumberToString(input));
++ EXPECT_EQ("1335179083776.0", NumberToString(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));
+- EXPECT_EQ("1334890332160", NumberToString(input));
++ EXPECT_EQ("1334890332160.0", NumberToString(input));
}
TEST(StringNumberConversionsTest, HexEncode) {
diff --git a/libchrome_tools/patch/file_path_mojom.patch b/libchrome_tools/patch/file_path_mojom.patch
new file mode 100644
index 0000000000..b61d62649b
--- /dev/null
+++ b/libchrome_tools/patch/file_path_mojom.patch
@@ -0,0 +1,19 @@
+diff --git a/mojo/public/mojom/base/file_path.mojom b/mojo/public/mojom/base/file_path.mojom
+index 674d8cd..097b37e 100644
+--- a/mojo/public/mojom/base/file_path.mojom
++++ b/mojo/public/mojom/base/file_path.mojom
+@@ -5,7 +5,13 @@
+ module mojo_base.mojom;
+
+ struct FilePath {
+- [EnableIf=file_path_is_string]
++ // In chrome, ninja have a goal that can define file_path_is_string for a set
++ // of mojom files.
++ // In android we don't have such ability ,one would have to add
++ // "--enable_feature file_path_is_string" to all targets generating pickle
++ // files, headers and sources, and also in all project including them.
++ // Faster solution was to just remove this "EnableIf" definition in libchrome.
++ // [EnableIf=file_path_is_string]
+ string path;
+
+ // This duplicates the contents of mojo_base.mojom.String16. String16 isn't
diff --git a/libchrome_tools/patch/file_posix.patch b/libchrome_tools/patch/file_posix.patch
index c7428e347a..6fe3af80c3 100644
--- a/libchrome_tools/patch/file_posix.patch
+++ b/libchrome_tools/patch/file_posix.patch
@@ -2,7 +2,7 @@
--- a/base/files/file_posix.cc
+++ b/base/files/file_posix.cc
-@@ -185,7 +185,9 @@ int64_t File::Seek(Whence whence, int64_
+@@ -189,7 +189,9 @@ int64_t File::Seek(Whence whence, int64_t offset) {
SCOPED_FILE_TRACE_WITH_SIZE("Seek", offset);
@@ -13,3 +13,12 @@
static_assert(sizeof(int64_t) == sizeof(off64_t), "off64_t must be 64 bits");
return lseek64(file_.get(), static_cast<off64_t>(offset),
static_cast<int>(whence));
+@@ -275,7 +277,7 @@ int File::Write(int64_t offset, const char* data, int size) {
+ int bytes_written = 0;
+ int rv;
+ do {
+-#if defined(OS_ANDROID)
++#if _FILE_OFFSET_BITS != 64 || defined(__BIONIC__)
+ // In case __USE_FILE_OFFSET64 is not used, we need to call pwrite64()
+ // instead of pwrite().
+ static_assert(sizeof(int64_t) == sizeof(off64_t), \ No newline at end of file
diff --git a/libchrome_tools/patch/handle_table.patch b/libchrome_tools/patch/handle_table.patch
new file mode 100644
index 0000000000..354fac81de
--- /dev/null
+++ b/libchrome_tools/patch/handle_table.patch
@@ -0,0 +1,176 @@
+diff --git a/mojo/core/handle_table.cc b/mojo/core/handle_table.cc
+index 62419a9..e039c71 100644
+--- a/mojo/core/handle_table.cc
++++ b/mojo/core/handle_table.cc
+@@ -8,35 +8,35 @@
+
+ #include <limits>
+
+-#include "base/trace_event/memory_dump_manager.h"
++// #include "base/trace_event/memory_dump_manager.h"
+
+ namespace mojo {
+ namespace core {
+
+ namespace {
+
+-const char* GetNameForDispatcherType(Dispatcher::Type type) {
+- switch (type) {
+- case Dispatcher::Type::UNKNOWN:
+- return "unknown";
+- case Dispatcher::Type::MESSAGE_PIPE:
+- return "message_pipe";
+- case Dispatcher::Type::DATA_PIPE_PRODUCER:
+- return "data_pipe_producer";
+- case Dispatcher::Type::DATA_PIPE_CONSUMER:
+- return "data_pipe_consumer";
+- case Dispatcher::Type::SHARED_BUFFER:
+- return "shared_buffer";
+- case Dispatcher::Type::WATCHER:
+- return "watcher";
+- case Dispatcher::Type::PLATFORM_HANDLE:
+- return "platform_handle";
+- case Dispatcher::Type::INVITATION:
+- return "invitation";
+- }
+- NOTREACHED();
+- return "unknown";
+-}
++// const char* GetNameForDispatcherType(Dispatcher::Type type) {
++// switch (type) {
++// case Dispatcher::Type::UNKNOWN:
++// return "unknown";
++// case Dispatcher::Type::MESSAGE_PIPE:
++// return "message_pipe";
++// case Dispatcher::Type::DATA_PIPE_PRODUCER:
++// return "data_pipe_producer";
++// case Dispatcher::Type::DATA_PIPE_CONSUMER:
++// return "data_pipe_consumer";
++// case Dispatcher::Type::SHARED_BUFFER:
++// return "shared_buffer";
++// case Dispatcher::Type::WATCHER:
++// return "watcher";
++// case Dispatcher::Type::PLATFORM_HANDLE:
++// return "platform_handle";
++// case Dispatcher::Type::INVITATION:
++// return "invitation";
++// }
++// NOTREACHED();
++// return "unknown";
++// }
+
+ } // namespace
+
+@@ -158,38 +158,38 @@ void HandleTable::GetActiveHandlesForTest(std::vector<MojoHandle>* handles) {
+ }
+
+ // MemoryDumpProvider implementation.
+-bool HandleTable::OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
+- base::trace_event::ProcessMemoryDump* pmd) {
+- // Create entries for all relevant dispatcher types to ensure they are present
+- // in the final dump.
+- std::map<Dispatcher::Type, int> handle_count;
+- handle_count[Dispatcher::Type::MESSAGE_PIPE];
+- handle_count[Dispatcher::Type::DATA_PIPE_PRODUCER];
+- handle_count[Dispatcher::Type::DATA_PIPE_CONSUMER];
+- handle_count[Dispatcher::Type::SHARED_BUFFER];
+- handle_count[Dispatcher::Type::WATCHER];
+- handle_count[Dispatcher::Type::PLATFORM_HANDLE];
+- handle_count[Dispatcher::Type::INVITATION];
+-
+- // Count the number of each dispatcher type.
+- {
+- base::AutoLock lock(GetLock());
+- for (const auto& entry : handles_) {
+- ++handle_count[entry.second.dispatcher->GetType()];
+- }
+- }
+-
+- for (const auto& entry : handle_count) {
+- base::trace_event::MemoryAllocatorDump* inner_dump =
+- pmd->CreateAllocatorDump(std::string("mojo/") +
+- GetNameForDispatcherType(entry.first));
+- inner_dump->AddScalar(
+- base::trace_event::MemoryAllocatorDump::kNameObjectCount,
+- base::trace_event::MemoryAllocatorDump::kUnitsObjects, entry.second);
+- }
+-
+- return true;
+-}
++// bool HandleTable::OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
++// base::trace_event::ProcessMemoryDump* pmd) {
++// // Create entries for all relevant dispatcher types to ensure they are present
++// // in the final dump.
++// std::map<Dispatcher::Type, int> handle_count;
++// handle_count[Dispatcher::Type::MESSAGE_PIPE];
++// handle_count[Dispatcher::Type::DATA_PIPE_PRODUCER];
++// handle_count[Dispatcher::Type::DATA_PIPE_CONSUMER];
++// handle_count[Dispatcher::Type::SHARED_BUFFER];
++// handle_count[Dispatcher::Type::WATCHER];
++// handle_count[Dispatcher::Type::PLATFORM_HANDLE];
++// handle_count[Dispatcher::Type::INVITATION];
++
++// // Count the number of each dispatcher type.
++// {
++// base::AutoLock lock(GetLock());
++// for (const auto& entry : handles_) {
++// ++handle_count[entry.second.dispatcher->GetType()];
++// }
++// }
++
++// for (const auto& entry : handle_count) {
++// base::trace_event::MemoryAllocatorDump* inner_dump =
++// pmd->CreateAllocatorDump(std::string("mojo/") +
++// GetNameForDispatcherType(entry.first));
++// inner_dump->AddScalar(
++// base::trace_event::MemoryAllocatorDump::kNameObjectCount,
++// base::trace_event::MemoryAllocatorDump::kUnitsObjects, entry.second);
++// }
++
++// return true;
++// }
+
+ HandleTable::Entry::Entry() {}
+
+diff --git a/mojo/core/handle_table.h b/mojo/core/handle_table.h
+index 234bdac..2e0edf7 100644
+--- a/mojo/core/handle_table.h
++++ b/mojo/core/handle_table.h
+@@ -13,7 +13,7 @@
+ #include "base/gtest_prod_util.h"
+ #include "base/macros.h"
+ #include "base/synchronization/lock.h"
+-#include "base/trace_event/memory_dump_provider.h"
++// #include "base/trace_event/memory_dump_provider.h"
+ #include "mojo/core/dispatcher.h"
+ #include "mojo/core/system_impl_export.h"
+ #include "mojo/public/c/system/types.h"
+@@ -21,11 +21,10 @@
+ namespace mojo {
+ namespace core {
+
+-class MOJO_SYSTEM_IMPL_EXPORT HandleTable
+- : public base::trace_event::MemoryDumpProvider {
++class MOJO_SYSTEM_IMPL_EXPORT HandleTable {
+ public:
+ HandleTable();
+- ~HandleTable() override;
++ ~HandleTable();
+
+ // HandleTable is thread-hostile. All access should be gated by GetLock().
+ base::Lock& GetLock();
+@@ -58,11 +57,11 @@ class MOJO_SYSTEM_IMPL_EXPORT HandleTable
+ void GetActiveHandlesForTest(std::vector<MojoHandle>* handles);
+
+ private:
+- FRIEND_TEST_ALL_PREFIXES(HandleTableTest, OnMemoryDump);
++ // FRIEND_TEST_ALL_PREFIXES(HandleTableTest, OnMemoryDump);
+
+ // MemoryDumpProvider implementation.
+- bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
+- base::trace_event::ProcessMemoryDump* pmd) override;
++ // bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
++ // base::trace_event::ProcessMemoryDump* pmd) override;
+
+ struct Entry {
+ Entry();
diff --git a/libchrome_tools/patch/hash.patch b/libchrome_tools/patch/hash.patch
index 71e13d70e2..34a74d68c3 100644
--- a/libchrome_tools/patch/hash.patch
+++ b/libchrome_tools/patch/hash.patch
@@ -2,7 +2,7 @@
--- a/base/hash.cc
+++ b/base/hash.cc
-@@ -4,19 +4,13 @@
+@@ -4,10 +4,12 @@
#include "base/hash.h"
@@ -11,18 +11,11 @@
-// Note: This algorithm is also in Blink under Source/wtf/StringHasher.h.
-extern "C" uint32_t SuperFastHash(const char* data, int len);
+#include <functional>
-
- namespace base {
-
--uint32_t SuperFastHash(const char* data, size_t length) {
-- if (length > static_cast<size_t>(std::numeric_limits<int>::max())) {
-- NOTREACHED();
-- return 0;
-- }
-- return ::SuperFastHash(data, static_cast<int>(length));
++
+uint32_t SuperFastHash(const char* data, size_t len) {
+ std::hash<std::string> hash_fn;
+ return hash_fn(std::string(data, len));
- }
++}
+
+ namespace base {
- } // namespace base
diff --git a/libchrome_tools/patch/jni_registration_generator.patch b/libchrome_tools/patch/jni_registration_generator.patch
new file mode 100644
index 0000000000..e385c6522a
--- /dev/null
+++ b/libchrome_tools/patch/jni_registration_generator.patch
@@ -0,0 +1,13 @@
+diff --git a/base/android/jni_generator/jni_registration_generator.py b/base/android/jni_generator/jni_registration_generator.py
+index dec56ee..8c545f6 100755
+--- a/base/android/jni_generator/jni_registration_generator.py
++++ b/base/android/jni_generator/jni_registration_generator.py
+@@ -316,7 +316,7 @@ def main(argv):
+ help='The output file path.')
+ arg_parser.add_argument('--no_register_java',
+ help='A list of Java files which should be ignored '
+- 'by the parser.')
++ 'by the parser.', default=[])
+ args = arg_parser.parse_args(build_utils.ExpandFileArgs(argv[1:]))
+ args.sources_files = build_utils.ParseGnList(args.sources_files)
+
diff --git a/libchrome_tools/patch/lazy_instance.patch b/libchrome_tools/patch/lazy_instance.patch
deleted file mode 100644
index d144137a67..0000000000
--- a/libchrome_tools/patch/lazy_instance.patch
+++ /dev/null
@@ -1,18 +0,0 @@
-# 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/logging.patch b/libchrome_tools/patch/logging.patch
index 8deece9b86..3a42e4c82f 100644
--- a/libchrome_tools/patch/logging.patch
+++ b/libchrome_tools/patch/logging.patch
@@ -1,19 +1,17 @@
+diff --git a/base/logging.cc b/base/logging.cc
+index 8eabda0..112afb8 100644
--- a/base/logging.cc
+++ b/base/logging.cc
-@@ -71,7 +71,11 @@ typedef pthread_mutex_t* MutexHandle;
- #include "base/posix/safe_strerror.h"
+@@ -58,7 +58,7 @@ typedef HANDLE MutexHandle;
+ #include <zircon/syscalls.h>
#endif
-#if defined(OS_ANDROID)
-+#if !defined(OS_ANDROID)
-+#include "base/files/file_path.h"
-+#endif
-+
+#if defined(OS_ANDROID) || defined(__ANDROID__)
#include <android/log.h>
#endif
-@@ -358,21 +362,23 @@ bool BaseInitLoggingImpl(const LoggingSe
+@@ -407,21 +407,23 @@ bool BaseInitLoggingImpl(const LoggingSettings& settings) {
// Can log only to the system debug log.
CHECK_EQ(settings.logging_dest & ~LOG_TO_SYSTEM_DEBUG_LOG, 0);
#endif
@@ -52,16 +50,16 @@
}
g_logging_destination = settings.logging_dest;
-@@ -668,7 +674,7 @@ LogMessage::~LogMessage() {
-
- asl_send(asl_client.get(), asl_message.get());
+@@ -755,7 +757,7 @@ LogMessage::~LogMessage() {
+ str_newline.c_str());
+ #endif // defined(USE_ASL)
}
-#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() {
+@@ -772,7 +774,16 @@ LogMessage::~LogMessage() {
priority = ANDROID_LOG_FATAL;
break;
}
diff --git a/libchrome_tools/patch/macros.patch b/libchrome_tools/patch/macros.patch
index f57ec8867d..0d608bb305 100644
--- a/libchrome_tools/patch/macros.patch
+++ b/libchrome_tools/patch/macros.patch
@@ -1,6 +1,8 @@
+diff --git a/base/macros.h b/base/macros.h
+index 3064a1b..8685117 100644
--- a/base/macros.h
+++ b/base/macros.h
-@@ -12,19 +12,32 @@
+@@ -12,6 +12,13 @@
#include <stddef.h> // For size_t.
@@ -11,6 +13,12 @@
+
+// We define following macros conditionally as they may be defined by another libraries.
+
+ // Distinguish mips32.
+ #if defined(__mips__) && (_MIPS_SIM == _ABIO32) && !defined(__mips32__)
+ #define __mips32__
+@@ -23,23 +30,31 @@
+ #endif
+
// Put this in the declarations for a class to be uncopyable.
+#if !defined(DISALLOW_COPY)
#define DISALLOW_COPY(TypeName) \
@@ -19,24 +27,19 @@
// Put this in the declarations for a class to be unassignable.
+#if !defined(DISALLOW_ASSIGN)
- #define DISALLOW_ASSIGN(TypeName) \
- void operator=(const TypeName&) = delete
+ #define DISALLOW_ASSIGN(TypeName) TypeName& 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.
+ // Put this in the declarations for a class to be uncopyable and unassignable.
+#if !defined(DISALLOW_COPY_AND_ASSIGN)
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
- TypeName(const TypeName&) = delete; \
- void operator=(const TypeName&) = delete
+ DISALLOW_COPY(TypeName); \
+ DISALLOW_ASSIGN(TypeName)
+#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.
+ // This is especially useful for classes containing only static methods.
+#if !defined(DISALLOW_IMPLICIT_CONSTRUCTORS)
#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
TypeName() = delete; \
@@ -45,10 +48,10 @@
// 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.
+@@ -53,8 +68,10 @@
+ //
+ // DEPRECATED, please use base::size(array) instead.
+ // TODO(https://crbug.com/837308): Replace existing arraysize usages.
+#if !defined(arraysize)
template <typename T, size_t N> char (&ArraySizeHelper(T (&array)[N]))[N];
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
@@ -56,14 +59,14 @@
// 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.
+@@ -83,8 +100,10 @@ namespace base {
+ // return *instance;
+ // }
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+#if !defined(CR_DEFINE_STATIC_LOCAL)
#define CR_DEFINE_STATIC_LOCAL(type, name, arguments) \
static type& name = *new type arguments
+#endif
- } // base
-
+ // Workaround for MSVC, which expands __VA_ARGS__ as one macro argument. To
+ // work around this bug, wrap the entire expression in this macro...
diff --git a/libchrome_tools/patch/memory_linux.patch b/libchrome_tools/patch/memory_linux.patch
new file mode 100644
index 0000000000..09afb42d12
--- /dev/null
+++ b/libchrome_tools/patch/memory_linux.patch
@@ -0,0 +1,17 @@
+# this file used to have a bunch of __libc_* extern definitions, android complains only about this one as missing
+
+diff --git a/base/process/memory_linux.cc b/base/process/memory_linux.cc
+index 171753c..11e482b 100644
+--- a/base/process/memory_linux.cc
++++ b/base/process/memory_linux.cc
+@@ -22,6 +22,10 @@
+ #include "third_party/tcmalloc/gperftools-2.0/chromium/src/gperftools/tcmalloc.h"
+ #endif
+
++extern "C" {
++void* __libc_malloc(size_t size);
++}
++
+ namespace base {
+
+ size_t g_oom_size = 0U;
diff --git a/libchrome_tools/patch/message_loop.patch b/libchrome_tools/patch/message_loop.patch
index 0ab36300eb..c06107a949 100644
--- a/libchrome_tools/patch/message_loop.patch
+++ b/libchrome_tools/patch/message_loop.patch
@@ -1,13 +1,23 @@
--- 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<MessageLoopForIO*>(loop);
- }
+@@ -28,6 +28,11 @@
+ #include "base/time/time.h"
+ #include "build/build_config.h"
+
++// Just in libchrome
++namespace brillo {
++class BaseMessageLoop;
++}
++
+ namespace base {
+
+ class ThreadTaskRunnerHandle;
+@@ -214,6 +219,8 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate,
+ void BindToCurrentThread();
+
+ private:
++ //only in libchrome
++ friend class brillo::BaseMessageLoop;
+ friend class internal::IncomingTaskQueue;
+ friend class MessageLoopCurrent;
+ friend class MessageLoopCurrentForIO;
diff --git a/libchrome_tools/patch/message_loop_unittest.patch b/libchrome_tools/patch/message_loop_unittest.patch
new file mode 100644
index 0000000000..cc5b5de95d
--- /dev/null
+++ b/libchrome_tools/patch/message_loop_unittest.patch
@@ -0,0 +1,125 @@
+diff --git a/base/message_loop/message_loop_unittest.cc b/base/message_loop/message_loop_unittest.cc
+index 1a21fc5..7743479 100644
+--- a/base/message_loop/message_loop_unittest.cc
++++ b/base/message_loop/message_loop_unittest.cc
+@@ -22,7 +22,8 @@
+ #include "base/run_loop.h"
+ #include "base/single_thread_task_runner.h"
+ #include "base/synchronization/waitable_event.h"
+-#include "base/task_scheduler/task_scheduler.h"
++// Unsupported in libchrome
++// #include "base/task_scheduler/task_scheduler.h"
+ #include "base/test/gtest_util.h"
+ #include "base/test/test_simple_task_runner.h"
+ #include "base/test/test_timeouts.h"
+@@ -260,7 +261,8 @@ void PostNTasks(int posts_remaining) {
+
+ enum class TaskSchedulerAvailability {
+ NO_TASK_SCHEDULER,
+- WITH_TASK_SCHEDULER,
++ // Unsupported in libchrome.
++ // WITH_TASK_SCHEDULER,
+ };
+
+ std::string TaskSchedulerAvailabilityToString(
+@@ -268,8 +270,9 @@ std::string TaskSchedulerAvailabilityToString(
+ switch (availability) {
+ case TaskSchedulerAvailability::NO_TASK_SCHEDULER:
+ return "NoTaskScheduler";
+- case TaskSchedulerAvailability::WITH_TASK_SCHEDULER:
+- return "WithTaskScheduler";
++ // Unsupported in libchrome.
++ // case TaskSchedulerAvailability::WITH_TASK_SCHEDULER:
++ // return "WithTaskScheduler";
+ }
+ NOTREACHED();
+ return "Unknown";
+@@ -282,11 +285,16 @@ class MessageLoopTest
+ ~MessageLoopTest() override = default;
+
+ void SetUp() override {
++ // Unsupported in libchrome.
++#if 0
+ if (GetParam() == TaskSchedulerAvailability::WITH_TASK_SCHEDULER)
+ TaskScheduler::CreateAndStartWithDefaultParams("MessageLoopTest");
++#endif
+ }
+
+ void TearDown() override {
++ // Unsupported in libchrome.
++#if 0
+ if (GetParam() == TaskSchedulerAvailability::WITH_TASK_SCHEDULER) {
+ // Failure to call FlushForTesting() could result in task leaks as tasks
+ // are skipped on shutdown.
+@@ -295,6 +303,7 @@ class MessageLoopTest
+ base::TaskScheduler::GetInstance()->JoinForTesting();
+ base::TaskScheduler::SetInstance(nullptr);
+ }
++#endif
+ }
+
+ static std::string ParamInfoToString(
+@@ -776,13 +785,18 @@ class MessageLoopTypedTest
+ ~MessageLoopTypedTest() = default;
+
+ void SetUp() override {
++// Unsupported in libchrome.
++#if 0
+ if (GetTaskSchedulerAvailability() ==
+ TaskSchedulerAvailability::WITH_TASK_SCHEDULER) {
+ TaskScheduler::CreateAndStartWithDefaultParams("MessageLoopTypedTest");
+ }
++#endif
+ }
+
+ void TearDown() override {
++// Unsupported in libchrome.
++#if 0
+ if (GetTaskSchedulerAvailability() ==
+ TaskSchedulerAvailability::WITH_TASK_SCHEDULER) {
+ // Failure to call FlushForTesting() could result in task leaks as tasks
+@@ -792,6 +806,7 @@ class MessageLoopTypedTest
+ base::TaskScheduler::GetInstance()->JoinForTesting();
+ base::TaskScheduler::SetInstance(nullptr);
+ }
++#endif
+ }
+
+ static std::string ParamInfoToString(
+@@ -1769,8 +1784,10 @@ INSTANTIATE_TEST_CASE_P(
+ TaskSchedulerAvailability::NO_TASK_SCHEDULER),
+ MessageLoopTypedTestParams(
+ MessageLoop::TYPE_UI,
+- TaskSchedulerAvailability::NO_TASK_SCHEDULER),
+- MessageLoopTypedTestParams(
++ TaskSchedulerAvailability::NO_TASK_SCHEDULER)
++// Unsupported in libchrome.
++#if 0
++ ,MessageLoopTypedTestParams(
+ MessageLoop::TYPE_DEFAULT,
+ TaskSchedulerAvailability::WITH_TASK_SCHEDULER),
+ MessageLoopTypedTestParams(
+@@ -1778,7 +1795,9 @@ INSTANTIATE_TEST_CASE_P(
+ TaskSchedulerAvailability::WITH_TASK_SCHEDULER),
+ MessageLoopTypedTestParams(
+ MessageLoop::TYPE_UI,
+- TaskSchedulerAvailability::WITH_TASK_SCHEDULER)),
++ TaskSchedulerAvailability::WITH_TASK_SCHEDULER)
++#endif
++ ),
+ MessageLoopTypedTest::ParamInfoToString);
+
+ #if defined(OS_WIN)
+@@ -2210,8 +2229,10 @@ TEST_P(MessageLoopTest, SequenceLocalStorageDifferentMessageLoops) {
+ INSTANTIATE_TEST_CASE_P(
+ ,
+ MessageLoopTest,
+- ::testing::Values(TaskSchedulerAvailability::NO_TASK_SCHEDULER,
+- TaskSchedulerAvailability::WITH_TASK_SCHEDULER),
++ ::testing::Values(TaskSchedulerAvailability::NO_TASK_SCHEDULER
++ // Unsupported in libchrome
++ //, TaskSchedulerAvailability::WITH_TASK_SCHEDULER
++ ),
+ MessageLoopTest::ParamInfoToString);
+
+ namespace {
diff --git a/libchrome_tools/patch/message_pump_for_ui.patch b/libchrome_tools/patch/message_pump_for_ui.patch
new file mode 100644
index 0000000000..1cf6aa0d43
--- /dev/null
+++ b/libchrome_tools/patch/message_pump_for_ui.patch
@@ -0,0 +1,28 @@
+diff --git a/base/message_loop/message_pump_for_ui.h b/base/message_loop/message_pump_for_ui.h
+index 6ee02b0..c661166 100644
+--- a/base/message_loop/message_pump_for_ui.h
++++ b/base/message_loop/message_pump_for_ui.h
+@@ -18,9 +18,9 @@
+ #include "base/message_loop/message_pump.h"
+ #elif defined(OS_NACL) || defined(OS_AIX)
+ // No MessagePumpForUI, see below.
+-#elif defined(USE_GLIB)
++#elif defined(USE_GLIB) && !defined(ANDROID)
+ #include "base/message_loop/message_pump_glib.h"
+-#elif defined(OS_LINUX) || defined(OS_BSD)
++#elif defined(OS_LINUX) || defined(OS_BSD)|| defined(ANDROID)
+ #include "base/message_loop/message_pump_libevent.h"
+ #elif defined(OS_FUCHSIA)
+ #include "base/message_loop/message_pump_fuchsia.h"
+@@ -42,9 +42,9 @@ using MessagePumpForUI = MessagePump;
+ #elif defined(OS_NACL) || defined(OS_AIX)
+ // Currently NaCl and AIX don't have a MessagePumpForUI.
+ // TODO(abarth): Figure out if we need this.
+-#elif defined(USE_GLIB)
++#elif defined(USE_GLIB) && !defined(ANDROID)
+ using MessagePumpForUI = MessagePumpGlib;
+-#elif defined(OS_LINUX) || defined(OS_BSD)
++#elif defined(OS_LINUX) || defined(OS_BSD) || defined(ANDROID)
+ using MessagePumpForUI = MessagePumpLibevent;
+ #elif defined(OS_FUCHSIA)
+ using MessagePumpForUI = MessagePumpFuchsia;
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
deleted file mode 100644
index f5ac205ce3..0000000000
--- a/libchrome_tools/patch/mojo-Add-a-way-to-handle-unhandled-RuntimeExceptions.patch
+++ /dev/null
@@ -1,129 +0,0 @@
-From 4256ecec730fdf5a41f34e11c0641e072971cb8c Mon Sep 17 00:00:00 2001
-From: Luis Hector Chavez <lhchavez@google.com>
-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 <rockot@chromium.org>
-Commit-Queue: Luis Hector Chavez <lhchavez@chromium.org>
-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<MessagePipeHandle
- ReadMessageResult readResult = result.getValue();
- assert readResult != null;
- if (receiver != null) {
-- boolean accepted = receiver.accept(
-- new Message(ByteBuffer.wrap(readResult.mData), readResult.mHandles));
-+ boolean accepted;
-+ try {
-+ accepted = receiver.accept(
-+ new Message(ByteBuffer.wrap(readResult.mData), readResult.mHandles));
-+ } catch (RuntimeException e) {
-+ // The DefaultExceptionHandler will decide whether any uncaught exception will
-+ // close the connection or not.
-+ accepted =
-+ ExceptionHandler.DefaultExceptionHandler.getInstance().handleException(e);
-+ }
- return new ResultAnd<Boolean>(result.getMojoResult(), accepted);
- }
- return new ResultAnd<Boolean>(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
deleted file mode 100644
index 082d17eba6..0000000000
--- a/libchrome_tools/patch/mojo-Avoid-a-crash-when-NodeController-pending-invit.patch
+++ /dev/null
@@ -1,44 +0,0 @@
-From 30a0b449c8f7036c300d808db96b391220b7698f Mon Sep 17 00:00:00 2001
-From: Luis Hector Chavez <lhchavez@google.com>
-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 <rockot@chromium.org>
-Commit-Queue: Luis Hector Chavez <lhchavez@chromium.org>
-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
index d86b735380..8040fbe9ec 100644
--- a/libchrome_tools/patch/mojo.patch
+++ b/libchrome_tools/patch/mojo.patch
@@ -1,230 +1,15 @@
# 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 <jni.h>
-
--#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 <stddef.h>
- #include <stdint.h>
-
--#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 <stddef.h>
- #include <stdint.h>
-
--#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<common::mojom::Ungue
- };
-
- template <>
--struct StructTraits<common::mojom::TimeDeltaDataView, base::TimeDelta> {
-- 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<common::mojom::FileDataView, base::File> {
- 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<int, int>(fd, kAndroidClientHandleDescriptor));
---- a/mojo/edk/system/ports/node.cc
-+++ b/mojo/edk/system/ports/node.cc
-@@ -803,7 +803,7 @@ scoped_refptr<Port> 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) {
+@@ -253,7 +253,11 @@ 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;
++ if (java_throwable)
++ LOG(FATAL) << GetJavaExceptionInfo(env, java_throwable);
+ else
+ LOG(FATAL) << "Unhandled exception";
}
@@ -232,288 +17,22 @@
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
+@@ -25,8 +25,16 @@ 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))
+-sys.path.append(os.path.join(os.path.dirname(__file__),
+- os.pardir, 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<base::Value>",
-+ "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<base::ListValue>",
-+ "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<base::DictionaryValue>",
-+ "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<common::mojom::TimeDataView, base::Time> {
-+ static int64_t internal_value(const base::Time& time) {
-+ return time.since_origin().InMicroseconds();
-+ }
++#sys.path.append(os.path.join(os.path.dirname(__file__),
++# os.pardir, os.pardir, os.pardir))
++sys.path.insert(0, os.path.join(os.path.dirname(__file__),
++ os.pardir, os.pardir))
+
-+ static bool Read(common::mojom::TimeDataView data, base::Time* time) {
-+ *time =
-+ base::Time() + base::TimeDelta::FromMicroseconds(data.internal_value());
-+ return true;
-+ }
-+};
-+
-+template <>
-+struct StructTraits<common::mojom::TimeDeltaDataView, base::TimeDelta> {
-+ 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<common::mojom::TimeTicksDataView, base::TimeTicks> {
-+ 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_
+ import gn_helpers
+
+ # Definition copied from pylib/constants/__init__.py to avoid adding
diff --git a/libchrome_tools/patch/mojom_disable_trace_and_mem_dump.patch b/libchrome_tools/patch/mojom_disable_trace_and_mem_dump.patch
new file mode 100644
index 0000000000..9724e1d354
--- /dev/null
+++ b/libchrome_tools/patch/mojom_disable_trace_and_mem_dump.patch
@@ -0,0 +1,299 @@
+diff --git a/mojo/core/core.cc b/mojo/core/core.cc
+index 8422ec2..3ffa640 100644
+--- a/mojo/core/core.cc
++++ b/mojo/core/core.cc
+@@ -21,7 +21,7 @@
+ #include "base/strings/string_piece.h"
+ #include "base/threading/thread_task_runner_handle.h"
+ #include "base/time/time.h"
+-#include "base/trace_event/memory_dump_manager.h"
++// #include "base/trace_event/memory_dump_manager.h"
+ #include "build/build_config.h"
+ #include "mojo/core/channel.h"
+ #include "mojo/core/configuration.h"
+@@ -127,8 +127,8 @@ void RunMojoProcessErrorHandler(ProcessDisconnectHandler* disconnect_handler,
+
+ Core::Core() {
+ handles_.reset(new HandleTable);
+- base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
+- handles_.get(), "MojoHandleTable", nullptr);
++ // base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
++ // handles_.get(), "MojoHandleTable", nullptr);
+ }
+
+ Core::~Core() {
+@@ -142,8 +142,8 @@ Core::~Core() {
+ base::BindOnce(&Core::PassNodeControllerToIOThread,
+ base::Passed(&node_controller_)));
+ }
+- base::trace_event::MemoryDumpManager::GetInstance()
+- ->UnregisterAndDeleteDumpProviderSoon(std::move(handles_));
++ // base::trace_event::MemoryDumpManager::GetInstance()
++ // ->UnregisterAndDeleteDumpProviderSoon(std::move(handles_));
+ }
+
+ void Core::SetIOTaskRunner(scoped_refptr<base::TaskRunner> io_task_runner) {
+diff --git a/mojo/core/user_message_impl.cc b/mojo/core/user_message_impl.cc
+index d4a4da1..9cb8284 100644
+--- a/mojo/core/user_message_impl.cc
++++ b/mojo/core/user_message_impl.cc
+@@ -13,10 +13,10 @@
+ #include "base/no_destructor.h"
+ #include "base/numerics/safe_conversions.h"
+ #include "base/numerics/safe_math.h"
+-#include "base/trace_event/memory_allocator_dump.h"
+-#include "base/trace_event/memory_dump_manager.h"
+-#include "base/trace_event/memory_dump_provider.h"
+-#include "base/trace_event/trace_event.h"
++// #include "base/trace_event/memory_allocator_dump.h"
++// #include "base/trace_event/memory_dump_manager.h"
++// #include "base/trace_event/memory_dump_provider.h"
++// #include "base/trace_event/trace_event.h"
+ #include "mojo/core/core.h"
+ #include "mojo/core/node_channel.h"
+ #include "mojo/core/node_controller.h"
+@@ -271,36 +271,36 @@ void DecrementMessageCount() {
+ base::subtle::NoBarrier_AtomicIncrement(&g_message_count, -1);
+ }
+
+-class MessageMemoryDumpProvider : public base::trace_event::MemoryDumpProvider {
+- public:
+- MessageMemoryDumpProvider() {
+- base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
+- this, "MojoMessages", nullptr);
+- }
+-
+- ~MessageMemoryDumpProvider() override {
+- base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
+- this);
+- }
+-
+- private:
+- // base::trace_event::MemoryDumpProvider:
+- bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
+- base::trace_event::ProcessMemoryDump* pmd) override {
+- auto* dump = pmd->CreateAllocatorDump("mojo/messages");
+- dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameObjectCount,
+- base::trace_event::MemoryAllocatorDump::kUnitsObjects,
+- base::subtle::NoBarrier_Load(&g_message_count));
+- return true;
+- }
+-
+- DISALLOW_COPY_AND_ASSIGN(MessageMemoryDumpProvider);
+-};
+-
+-void EnsureMemoryDumpProviderExists() {
+- static base::NoDestructor<MessageMemoryDumpProvider> provider;
+- ALLOW_UNUSED_LOCAL(provider);
+-}
++// class MessageMemoryDumpProvider : public base::trace_event::MemoryDumpProvider {
++// public:
++// MessageMemoryDumpProvider() {
++// base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
++// this, "MojoMessages", nullptr);
++// }
++
++// ~MessageMemoryDumpProvider() override {
++// base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
++// this);
++// }
++
++// private:
++// // base::trace_event::MemoryDumpProvider:
++// bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
++// base::trace_event::ProcessMemoryDump* pmd) override {
++// auto* dump = pmd->CreateAllocatorDump("mojo/messages");
++// dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameObjectCount,
++// base::trace_event::MemoryAllocatorDump::kUnitsObjects,
++// base::subtle::NoBarrier_Load(&g_message_count));
++// return true;
++// }
++
++// DISALLOW_COPY_AND_ASSIGN(MessageMemoryDumpProvider);
++// };
++
++// void EnsureMemoryDumpProviderExists() {
++// static base::NoDestructor<MessageMemoryDumpProvider> provider;
++// ALLOW_UNUSED_LOCAL(provider);
++// }
+
+ } // namespace
+
+@@ -648,7 +648,7 @@ void UserMessageImpl::FailHandleSerializationForTesting(bool fail) {
+
+ UserMessageImpl::UserMessageImpl(ports::UserMessageEvent* message_event)
+ : ports::UserMessage(&kUserMessageTypeInfo), message_event_(message_event) {
+- EnsureMemoryDumpProviderExists();
++ // EnsureMemoryDumpProviderExists();
+ IncrementMessageCount();
+ }
+
+@@ -667,7 +667,7 @@ UserMessageImpl::UserMessageImpl(ports::UserMessageEvent* message_event,
+ header_size_(header_size),
+ user_payload_(user_payload),
+ user_payload_size_(user_payload_size) {
+- EnsureMemoryDumpProviderExists();
++ // EnsureMemoryDumpProviderExists();
+ IncrementMessageCount();
+ }
+
+diff --git a/mojo/public/cpp/bindings/lib/message_dumper.cc b/mojo/public/cpp/bindings/lib/message_dumper.cc
+index f187e45..35696bb 100644
+--- a/mojo/public/cpp/bindings/lib/message_dumper.cc
++++ b/mojo/public/cpp/bindings/lib/message_dumper.cc
+@@ -22,33 +22,33 @@ base::FilePath& DumpDirectory() {
+ return *dump_directory;
+ }
+
+-void WriteMessage(uint32_t identifier,
+- const mojo::MessageDumper::MessageEntry& entry) {
+- static uint64_t num = 0;
+-
+- if (!entry.interface_name)
+- return;
+-
+- base::FilePath message_directory =
+- DumpDirectory()
+- .AppendASCII(entry.interface_name)
+- .AppendASCII(base::NumberToString(identifier));
+-
+- if (!base::DirectoryExists(message_directory) &&
+- !base::CreateDirectory(message_directory)) {
+- LOG(ERROR) << "Failed to create" << message_directory.value();
+- return;
+- }
+-
+- std::string filename =
+- base::NumberToString(num++) + "." + entry.method_name + ".mojomsg";
+- base::FilePath path = message_directory.AppendASCII(filename);
+- base::File file(path,
+- base::File::FLAG_WRITE | base::File::FLAG_CREATE_ALWAYS);
+-
+- file.WriteAtCurrentPos(reinterpret_cast<const char*>(entry.data_bytes.data()),
+- static_cast<int>(entry.data_bytes.size()));
+-}
++// void WriteMessage(uint32_t identifier,
++// const mojo::MessageDumper::MessageEntry& entry) {
++// static uint64_t num = 0;
++
++// if (!entry.interface_name)
++// return;
++
++// base::FilePath message_directory =
++// DumpDirectory()
++// .AppendASCII(entry.interface_name)
++// .AppendASCII(base::NumberToString(identifier));
++
++// if (!base::DirectoryExists(message_directory) &&
++// !base::CreateDirectory(message_directory)) {
++// LOG(ERROR) << "Failed to create" << message_directory.value();
++// return;
++// }
++
++// std::string filename =
++// base::NumberToString(num++) + "." + entry.method_name + ".mojomsg";
++// base::FilePath path = message_directory.AppendASCII(filename);
++// base::File file(path,
++// base::File::FLAG_WRITE | base::File::FLAG_CREATE_ALWAYS);
++
++// file.WriteAtCurrentPos(reinterpret_cast<const char*>(entry.data_bytes.data()),
++// static_cast<int>(entry.data_bytes.size()));
++// }
+
+ } // namespace
+
+@@ -71,17 +71,17 @@ MessageDumper::MessageDumper() : identifier_(base::RandUint64()) {}
+ MessageDumper::~MessageDumper() {}
+
+ bool MessageDumper::Accept(mojo::Message* message) {
+- MessageEntry entry(message->data(), message->data_num_bytes(),
+- message->interface_name(), message->method_name());
++ // MessageEntry entry(message->data(), message->data_num_bytes(),
++ // "unknown interface", "unknown name");
+
+- static base::NoDestructor<scoped_refptr<base::TaskRunner>> task_runner(
+- base::CreateSequencedTaskRunnerWithTraits(
+- {base::MayBlock(), base::TaskPriority::USER_BLOCKING,
+- base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}));
++ // static base::NoDestructor<scoped_refptr<base::TaskRunner>> task_runner(
++ // base::CreateSequencedTaskRunnerWithTraits(
++ // {base::MayBlock(), base::TaskPriority::USER_BLOCKING,
++ // base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}));
+
+- (*task_runner)
+- ->PostTask(FROM_HERE,
+- base::BindOnce(&WriteMessage, identifier_, std::move(entry)));
++ // (*task_runner)
++ // ->PostTask(FROM_HERE,
++ // base::BindOnce(&WriteMessage, identifier_, std::move(entry)));
+ return true;
+ }
+
+diff --git a/mojo/public/cpp/system/file_data_pipe_producer.cc b/mojo/public/cpp/system/file_data_pipe_producer.cc
+index 842fe8f..6038bbe 100644
+--- a/mojo/public/cpp/system/file_data_pipe_producer.cc
++++ b/mojo/public/cpp/system/file_data_pipe_producer.cc
+@@ -266,13 +266,15 @@ void FileDataPipeProducer::WriteFromPath(const base::FilePath& path,
+
+ void FileDataPipeProducer::InitializeNewRequest(CompletionCallback callback) {
+ DCHECK(!file_sequence_state_);
+- auto file_task_runner = base::CreateSequencedTaskRunnerWithTraits(
+- {base::MayBlock(), base::TaskPriority::BACKGROUND});
+- file_sequence_state_ = new FileSequenceState(
+- std::move(producer_), file_task_runner,
+- base::BindOnce(&FileDataPipeProducer::OnWriteComplete,
+- weak_factory_.GetWeakPtr(), std::move(callback)),
+- base::SequencedTaskRunnerHandle::Get(), std::move(observer_));
++
++ LOG(FATAL) << "unsupported in libchrome";
++ // auto file_task_runner = base::CreateSequencedTaskRunnerWithTraits(
++ // {base::MayBlock(), base::TaskPriority::BACKGROUND});
++ // file_sequence_state_ = new FileSequenceState(
++ // std::move(producer_), file_task_runner,
++ // base::BindOnce(&FileDataPipeProducer::OnWriteComplete,
++ // weak_factory_.GetWeakPtr(), std::move(callback)),
++ // base::SequencedTaskRunnerHandle::Get(), std::move(observer_));
+ }
+
+ void FileDataPipeProducer::OnWriteComplete(
+diff --git a/mojo/public/tools/bindings/mojom_bindings_generator.py b/mojo/public/tools/bindings/mojom_bindings_generator.py
+index affbe79..57a8031 100755
+--- a/mojo/public/tools/bindings/mojom_bindings_generator.py
++++ b/mojo/public/tools/bindings/mojom_bindings_generator.py
+@@ -174,7 +174,8 @@ class MojomProcessor(object):
+ MakeImportStackMessage(imported_filename_stack + [rel_filename.path])
+ sys.exit(1)
+
+- tree = _UnpickleAST(_GetPicklePath(rel_filename, args.output_dir))
++ tree = _UnpickleAST(_FindPicklePath(rel_filename, args.gen_directories +
++ [args.output_dir]))
+ dirname = os.path.dirname(rel_filename.path)
+
+ # Process all our imports first and collect the module object for each.
+@@ -256,6 +257,16 @@ def _Generate(args, remaining_args):
+ return 0
+
+
++def _FindPicklePath(rel_filename, search_dirs):
++ filename, _ = os.path.splitext(rel_filename.relative_path())
++ pickle_path = filename + '.p'
++ for search_dir in search_dirs:
++ path = os.path.join(search_dir, pickle_path)
++ if os.path.isfile(path):
++ return path
++ raise Exception("%s: Error: Could not find file in %r" % (pickle_path, search_dirs))
++
++
+ def _GetPicklePath(rel_filename, output_dir):
+ filename, _ = os.path.splitext(rel_filename.relative_path())
+ pickle_path = filename + '.p'
+@@ -402,6 +413,9 @@ def main():
+ metavar="GENERATORS",
+ default="c++,javascript,java",
+ help="comma-separated list of generators")
++ generate_parser.add_argument(
++ "--gen_dir", dest="gen_directories", action="append", metavar="directory",
++ default=[], help="add a directory to be searched for the syntax trees.")
+ generate_parser.add_argument(
+ "-I", dest="import_directories", action="append", metavar="directory",
+ default=[],
diff --git a/libchrome_tools/patch/observer_list_unittest.patch b/libchrome_tools/patch/observer_list_unittest.patch
new file mode 100644
index 0000000000..8b1c5b5ae1
--- /dev/null
+++ b/libchrome_tools/patch/observer_list_unittest.patch
@@ -0,0 +1,65 @@
+diff --git a/base/observer_list_unittest.cc b/base/observer_list_unittest.cc
+index 1470b90..50d7e7e 100644
+--- a/base/observer_list_unittest.cc
++++ b/base/observer_list_unittest.cc
+@@ -17,9 +17,11 @@
+ #include "base/run_loop.h"
+ #include "base/sequenced_task_runner.h"
+ #include "base/single_thread_task_runner.h"
++#include "base/strings/string_piece.h"
+ #include "base/synchronization/waitable_event.h"
+-#include "base/task_scheduler/post_task.h"
+-#include "base/task_scheduler/task_scheduler.h"
++// TaskScheduler not supported in libchrome
++// #include "base/task_scheduler/post_task.h"
++// #include "base/task_scheduler/task_scheduler.h"
+ #include "base/test/gtest_util.h"
+ #include "base/test/scoped_task_environment.h"
+ #include "base/threading/platform_thread.h"
+@@ -690,6 +692,8 @@ class SequenceVerificationObserver : public Foo {
+ } // namespace
+
+ // Verify that observers are notified on the correct sequence.
++// TaskScheduler not supported in libchrome
++#if 0
+ TEST(ObserverListThreadSafeTest, NotificationOnValidSequence) {
+ test::ScopedTaskEnvironment scoped_task_environment;
+
+@@ -717,9 +721,12 @@ TEST(ObserverListThreadSafeTest, NotificationOnValidSequence) {
+ EXPECT_TRUE(observer_1.called_on_valid_sequence());
+ EXPECT_TRUE(observer_2.called_on_valid_sequence());
+ }
++#endif
+
+ // Verify that when an observer is added to a NOTIFY_ALL ObserverListThreadSafe
+ // from a notification, it is itself notified.
++// TaskScheduler not supported in libchrome
++#if 0
+ TEST(ObserverListThreadSafeTest, AddObserverFromNotificationNotifyAll) {
+ test::ScopedTaskEnvironment scoped_task_environment;
+ auto observer_list = MakeRefCounted<ObserverListThreadSafe<Foo>>();
+@@ -737,6 +744,7 @@ TEST(ObserverListThreadSafeTest, AddObserverFromNotificationNotifyAll) {
+
+ EXPECT_EQ(1, observer_added_from_notification.GetValue());
+ }
++#endif
+
+ namespace {
+
+@@ -769,6 +777,8 @@ class RemoveWhileNotificationIsRunningObserver : public Foo {
+
+ // Verify that there is no crash when an observer is removed while it is being
+ // notified.
++// TaskScheduler not supported in libchrome
++#if 0
+ TEST(ObserverListThreadSafeTest, RemoveWhileNotificationIsRunning) {
+ auto observer_list = MakeRefCounted<ObserverListThreadSafe<Foo>>();
+ RemoveWhileNotificationIsRunningObserver observer;
+@@ -793,6 +803,7 @@ TEST(ObserverListThreadSafeTest, RemoveWhileNotificationIsRunning) {
+
+ observer.Unblock();
+ }
++#endif
+
+ TEST(ObserverListTest, Existing) {
+ ObserverList<Foo> observer_list(ObserverListPolicy::EXISTING_ONLY);
diff --git a/libchrome_tools/patch/path_service.patch b/libchrome_tools/patch/path_service.patch
index 75b30dc044..466635dce8 100644
--- a/libchrome_tools/patch/path_service.patch
+++ b/libchrome_tools/patch/path_service.patch
@@ -31,15 +31,16 @@
}
--- a/base/files/file_util_posix.cc
+++ b/base/files/file_util_posix.cc
-@@ -533,6 +533,8 @@ bool GetTempDir(FilePath* path) {
- } else {
+@@ -594,6 +594,9 @@ bool GetTempDir(FilePath* path) {
+
#if defined(OS_ANDROID)
- return PathService::Get(base::DIR_CACHE, path);
+ return PathService::Get(DIR_CACHE, path);
+#elif defined(__ANDROID__)
-+ *path = FilePath("/data/local/tmp");
++ *path = FilePath("/data/local/tmp");
++ return true;
#else
- *path = FilePath("/tmp");
- #endif
+ *path = FilePath("/tmp");
+ return true;
--- a/base/json/json_reader_unittest.cc
+++ b/base/json/json_reader_unittest.cc
@@ -567,7 +567,7 @@ TEST(JSONReaderTest, Reading) {
diff --git a/libchrome_tools/patch/shared_memory_handle.patch b/libchrome_tools/patch/shared_memory_handle.patch
new file mode 100644
index 0000000000..fb2efafe7e
--- /dev/null
+++ b/libchrome_tools/patch/shared_memory_handle.patch
@@ -0,0 +1,22 @@
+diff --git a/base/memory/shared_memory_handle.h b/base/memory/shared_memory_handle.h
+index dd3d47a..7367188 100644
+--- a/base/memory/shared_memory_handle.h
++++ b/base/memory/shared_memory_handle.h
+@@ -146,7 +146,7 @@ class BASE_EXPORT SharedMemoryHandle {
+ int Release();
+ #endif
+
+-#if defined(OS_ANDROID)
++#if defined(OS_ANDROID) || defined(__ANDROID__)
+ // Marks the current file descriptor as read-only, for the purpose of
+ // mapping. This is independent of the region's read-only status.
+ void SetReadOnly() { read_only_ = true; }
+@@ -218,7 +218,7 @@ class BASE_EXPORT SharedMemoryHandle {
+ bool ownership_passes_to_ipc_ = false;
+ };
+ };
+-#elif defined(OS_ANDROID)
++#elif defined(OS_ANDROID) || defined(__ANDROID__)
+ friend class SharedMemory;
+
+ FileDescriptor file_descriptor_;
diff --git a/libchrome_tools/patch/shared_memory_mapping.patch b/libchrome_tools/patch/shared_memory_mapping.patch
new file mode 100644
index 0000000000..f5362f0d50
--- /dev/null
+++ b/libchrome_tools/patch/shared_memory_mapping.patch
@@ -0,0 +1,33 @@
+diff --git a/base/memory/shared_memory_mapping.cc b/base/memory/shared_memory_mapping.cc
+index 005e3fc..2b42928 100644
+--- a/base/memory/shared_memory_mapping.cc
++++ b/base/memory/shared_memory_mapping.cc
+@@ -7,7 +7,8 @@
+ #include <utility>
+
+ #include "base/logging.h"
+-#include "base/memory/shared_memory_tracker.h"
++// Unsupported in libchrome
++// #include "base/memory/shared_memory_tracker.h"
+ #include "base/unguessable_token.h"
+ #include "build/build_config.h"
+
+@@ -62,14 +63,15 @@ SharedMemoryMapping::SharedMemoryMapping(void* memory,
+ size_t mapped_size,
+ const UnguessableToken& guid)
+ : memory_(memory), size_(size), mapped_size_(mapped_size), guid_(guid) {
+- SharedMemoryTracker::GetInstance()->IncrementMemoryUsage(*this);
++ // Unsupported in libchrome.
++ // SharedMemoryTracker::GetInstance()->IncrementMemoryUsage(*this);
+ }
+
+ void SharedMemoryMapping::Unmap() {
+ if (!IsValid())
+ return;
+-
+- SharedMemoryTracker::GetInstance()->DecrementMemoryUsage(*this);
++ // Unsupported in libchrome.
++ // SharedMemoryTracker::GetInstance()->DecrementMemoryUsage(*this);
+ #if defined(OS_WIN)
+ if (!UnmapViewOfFile(memory_))
+ DPLOG(ERROR) << "UnmapViewOfFile";
diff --git a/libchrome_tools/patch/shared_memory_posix.patch b/libchrome_tools/patch/shared_memory_posix.patch
index abae9f455d..169bbc8fbc 100644
--- a/libchrome_tools/patch/shared_memory_posix.patch
+++ b/libchrome_tools/patch/shared_memory_posix.patch
@@ -1,6 +1,8 @@
+diff --git a/base/memory/shared_memory_posix.cc b/base/memory/shared_memory_posix.cc
+index e1289e7e1da0..aa718957cf26 100644
--- a/base/memory/shared_memory_posix.cc
+++ b/base/memory/shared_memory_posix.cc
-@@ -27,6 +27,8 @@
+@@ -29,6 +30,8 @@
#if defined(OS_ANDROID)
#include "base/os_compat_android.h"
@@ -9,25 +11,25 @@
#include "third_party/ashmem/ashmem.h"
#endif
-@@ -96,7 +98,7 @@ bool SharedMemory::CreateAndMapAnonymous
+@@ -80,7 +83,7 @@ bool SharedMemory::CreateAndMapAnonymous(size_t size) {
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_);
+
+ // Chromium mostly only uses the unique/private shmem as specified by
+ // "name == L"". The exception is in the StatsTable.
+@@ -252,7 +255,7 @@ bool SharedMemory::Open(const std::string& name, bool read_only) {
+ FileDescriptor(readonly_mapped_file, false), 0, shm_.GetGUID());
+ return result;
}
-#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 (!shm_.IsValid())
+@@ -264,7 +267,7 @@ bool SharedMemory::MapAt(off_t offset, size_t bytes) {
if (memory_)
return false;
@@ -36,21 +38,53 @@
// 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() {
- }
+@@ -277,19 +280,19 @@ bool SharedMemory::MapAt(off_t offset, size_t bytes) {
+
+ // Sanity check. This shall catch invalid uses of the SharedMemory APIs
+ // but will not protect against direct mmap() attempts.
+- if (shm_.IsReadOnly()) {
+- // Use a DCHECK() to call writable mappings with read-only descriptors
+- // in debug builds immediately. Return an error for release builds
+- // or during unit-testing (assuming a ScopedLogAssertHandler was installed).
+- DCHECK(read_only_)
+- << "Trying to map a region writable with a read-only descriptor.";
+- if (!read_only_) {
+- return false;
+- }
+- if (!shm_.SetRegionReadOnly()) { // Ensure the region is read-only.
+- return false;
+- }
+- }
++ // if (shm_.IsReadOnly()) {
++ // // Use a DCHECK() to call writable mappings with read-only descriptors
++ // // in debug builds immediately. Return an error for release builds
++ // // or during unit-testing (assuming a ScopedLogAssertHandler was installed).
++ // DCHECK(read_only_)
++ // << "Trying to map a region writable with a read-only descriptor.";
++ // if (!read_only_) {
++ // return false;
++ // }
++ // if (!shm_.SetRegionReadOnly()) { // Ensure the region is read-only.
++ // return false;
++ // }
++ // }
+ #endif
+
+ memory_ = mmap(nullptr, bytes, PROT_READ | (read_only_ ? 0 : PROT_WRITE),
+@@ -334,7 +339,7 @@ SharedMemoryHandle SharedMemory::TakeHandle() {
+ return handle_copy;
}
-#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;
+ void SharedMemory::Close() {
+ if (shm_.IsValid()) {
+ shm_.Close();
+@@ -374,6 +379,6 @@ SharedMemoryHandle SharedMemory::GetReadOnlyHandle() const {
+ CHECK(readonly_shm_.IsValid());
+ return readonly_shm_.Duplicate();
}
-#endif // !defined(OS_ANDROID)
+#endif // !defined(OS_ANDROID) && !defined(__ANDROID__)
- bool SharedMemory::ShareToProcessCommon(ProcessHandle process,
- SharedMemoryHandle* new_handle,
+ } // namespace base
diff --git a/libchrome_tools/patch/ssl.patch b/libchrome_tools/patch/ssl.patch
index f4a2f8f83e..d502081e02 100644
--- a/libchrome_tools/patch/ssl.patch
+++ b/libchrome_tools/patch/ssl.patch
@@ -79,48 +79,4 @@
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
index 989380b5da..f4b8df4dd1 100644
--- a/libchrome_tools/patch/statfs_f_type.patch
+++ b/libchrome_tools/patch/statfs_f_type.patch
@@ -26,7 +26,7 @@
break;
--- a/base/sys_info_posix.cc
+++ b/base/sys_info_posix.cc
-@@ -85,7 +85,10 @@ bool IsStatsZeroIfUnlimited(const base::
+@@ -90,7 +90,10 @@ bool IsStatsZeroIfUnlimited(const base::FilePath& path) {
if (HANDLE_EINTR(statfs(path.value().c_str(), &stats)) != 0)
return false;
diff --git a/libchrome_tools/patch/subprocess.patch b/libchrome_tools/patch/subprocess.patch
index ff1d02d3dd..bbd819ff47 100644
--- a/libchrome_tools/patch/subprocess.patch
+++ b/libchrome_tools/patch/subprocess.patch
@@ -1,19 +1,19 @@
--- a/base/process/process_metrics_unittest.cc
+++ b/base/process/process_metrics_unittest.cc
-@@ -549,6 +549,9 @@ MULTIPROCESS_TEST_MAIN(ChildMain) {
+@@ -569,6 +569,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) {
+ TEST(ProcessMetricsTest, GetChildOpenFdCount) {
ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
-@@ -562,9 +565,23 @@ TEST(ProcessMetricsTest, GetOpenFdCount)
+@@ -582,9 +585,23 @@ TEST(ProcessMetricsTest, GetChildOpenFdCount) {
std::unique_ptr<ProcessMetrics> metrics(
- ProcessMetrics::CreateProcessMetrics(spawn_child.process.Handle()));
+ ProcessMetrics::CreateProcessMetrics(child.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
@@ -28,25 +28,25 @@
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(1));
+ }
+ EXPECT_EQ(0, open_fds);
- ASSERT_TRUE(spawn_child.process.Terminate(0, true));
+ ASSERT_TRUE(child.Terminate(0, true));
}
+#endif // !defined(__ANDROID__)
+
#endif // defined(OS_LINUX)
- } // namespace debug
+ #if defined(OS_ANDROID) || defined(OS_LINUX)
--- a/base/test/multiprocess_test.cc
+++ b/base/test/multiprocess_test.cc
-@@ -12,7 +12,7 @@
+@@ -13,7 +13,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
+ Process SpawnMultiProcessTestChild(const std::string& procname,
+ const CommandLine& base_command_line,
+ const LaunchOptions& options) {
+@@ -39,7 +39,7 @@ bool TerminateMultiProcessTestChild(const Process& process,
return process.Terminate(exit_code, wait);
}
@@ -54,18 +54,18 @@
+#endif // !OS_ANDROID && !__ANDROID__ && !__ANDROID_HOST__
CommandLine GetMultiProcessTestChildBaseCommandLine() {
- CommandLine cmd_line = *CommandLine::ForCurrentProcess();
-@@ -54,6 +54,8 @@ CommandLine GetMultiProcessTestChildBase
- MultiProcessTest::MultiProcessTest() {
- }
+ base::ScopedAllowBlockingForTesting allow_blocking;
+@@ -52,6 +52,8 @@ CommandLine GetMultiProcessTestChildBaseCommandLine() {
+
+ MultiProcessTest::MultiProcessTest() = default;
+// Don't compile on ARC.
+#if 0
- SpawnChildResult MultiProcessTest::SpawnChild(const std::string& procname) {
+ Process MultiProcessTest::SpawnChild(const std::string& procname) {
LaunchOptions options;
#if defined(OS_WIN)
-@@ -67,6 +69,7 @@ SpawnChildResult MultiProcessTest::Spawn
- const LaunchOptions& options) {
+@@ -64,6 +66,7 @@ Process MultiProcessTest::SpawnChildWithOptions(const std::string& procname,
+ const LaunchOptions& options) {
return SpawnMultiProcessTestChild(procname, MakeCmdLine(procname), options);
}
+#endif
diff --git a/libchrome_tools/patch/task_annotator.patch b/libchrome_tools/patch/task_annotator.patch
new file mode 100644
index 0000000000..52d21c6e74
--- /dev/null
+++ b/libchrome_tools/patch/task_annotator.patch
@@ -0,0 +1,12 @@
+diff --git a/base/debug/task_annotator.h b/base/debug/task_annotator.h
+index fedca7d..9ff5c7b 100644
+--- a/base/debug/task_annotator.h
++++ b/base/debug/task_annotator.h
+@@ -20,6 +20,7 @@ class BASE_EXPORT TaskAnnotator {
+ public:
+ class ObserverForTesting {
+ public:
++ virtual ~ObserverForTesting() = default;
+ // Invoked just before RunTask() in the scope in which the task is about to
+ // be executed.
+ virtual void BeforeRunTask(const PendingTask* pending_task) = 0;
diff --git a/libchrome_tools/patch/task_scheduler.patch b/libchrome_tools/patch/task_scheduler.patch
deleted file mode 100644
index 39a4ead16d..0000000000
--- a/libchrome_tools/patch/task_scheduler.patch
+++ /dev/null
@@ -1,121 +0,0 @@
-# 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<TaskRunner>
- SequencedWorkerPool::Inner::GetTaskSchedulerTaskRunner(
- int sequence_token_id,
- const TaskTraits& traits) {
-+#if 1
-+ NOTREACHED();
-+ return scoped_refptr<TaskRunner>();
-+#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
deleted file mode 100644
index 496e68f305..0000000000
--- a/libchrome_tools/patch/time.patch
+++ /dev/null
@@ -1,19 +0,0 @@
-# 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<TimeClass*>(this));
diff --git a/libchrome_tools/patch/trace_event.patch b/libchrome_tools/patch/trace_event.patch
index 514ef53bbc..43dc0524fb 100644
--- a/libchrome_tools/patch/trace_event.patch
+++ b/libchrome_tools/patch/trace_event.patch
@@ -133,19 +133,19 @@
output += ")";
return output;
}
---- a/base/threading/thread_id_name_manager.cc
+---- 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"
+@@ -12,7 +12,8 @@
+ #include "base/no_destructor.h"
#include "base/strings/string_util.h"
+ #include "base/threading/thread_local.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
+@@ -94,8 +95,9 @@ void ThreadIdNameManager::SetName(const std::string& name) {
// 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.
@@ -153,15 +153,15 @@
- leaked_str->c_str());
+ // Unsupported in libchrome.
+ // trace_event::AllocationContextTracker::SetCurrentThreadName(
-+ // leaked_str->c_str());
++ // 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"
+@@ -16,7 +16,8 @@
#include "base/logging.h"
+ #include "base/macros.h"
#include "base/memory/shared_memory_helper.h"
-#include "base/memory/shared_memory_tracker.h"
+// Unsupported in libchrome.
@@ -169,7 +169,7 @@
#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
+@@ -302,7 +305,8 @@ bool SharedMemory::MapAt(off_t offset, size_t bytes) {
DCHECK_EQ(0U,
reinterpret_cast<uintptr_t>(memory_) &
(SharedMemory::MAP_MINIMUM_ALIGNMENT - 1));
@@ -177,15 +177,15 @@
+ // Unsupported in libchrome.
+ // SharedMemoryTracker::GetInstance()->IncrementMemoryUsage(*this);
} else {
- memory_ = NULL;
+ memory_ = nullptr;
}
-@@ -301,7 +305,8 @@ bool SharedMemory::Unmap() {
+@@ -314,7 +318,8 @@ bool SharedMemory::Unmap() {
+ if (!memory_)
return false;
- munmap(memory_, mapped_size_);
- SharedMemoryTracker::GetInstance()->DecrementMemoryUsage(*this);
+ // Unsupported in libchrome.
+ // SharedMemoryTracker::GetInstance()->DecrementMemoryUsage(*this);
- memory_ = NULL;
+ munmap(memory_, mapped_size_);
+ memory_ = nullptr;
mapped_size_ = 0;
- return true;
diff --git a/libchrome_tools/patch/values.patch b/libchrome_tools/patch/values.patch
new file mode 100644
index 0000000000..7e36b490cc
--- /dev/null
+++ b/libchrome_tools/patch/values.patch
@@ -0,0 +1,63 @@
+# we don't support trace_event on libchrome
+
+--- a/base/values.cc
++++ b/base/values.cc
+@@ -18,7 +18,8 @@
+ #include "base/stl_util.h"
+ #include "base/strings/string_util.h"
+ #include "base/strings/utf_string_conversions.h"
+-#include "base/trace_event/memory_usage_estimator.h"
++// Unsupported in libchrome
++// #include "base/trace_event/memory_usage_estimator.h"
+
+ namespace base {
+
+@@ -632,20 +633,21 @@ bool Value::Equals(const Value* other) const {
+ return *this == *other;
+ }
+
+-size_t Value::EstimateMemoryUsage() const {
+- switch (type_) {
+- case Type::STRING:
+- return base::trace_event::EstimateMemoryUsage(string_value_);
+- case Type::BINARY:
+- return base::trace_event::EstimateMemoryUsage(binary_value_);
+- case Type::DICTIONARY:
+- return base::trace_event::EstimateMemoryUsage(dict_);
+- case Type::LIST:
+- return base::trace_event::EstimateMemoryUsage(list_);
+- default:
+- return 0;
+- }
+-}
++// Unsupported in libchrome
++// size_t Value::EstimateMemoryUsage() const {
++// switch (type_) {
++// case Type::STRING:
++// return base::trace_event::EstimateMemoryUsage(string_value_);
++// case Type::BINARY:
++// return base::trace_event::EstimateMemoryUsage(binary_value_);
++// case Type::DICTIONARY:
++// return base::trace_event::EstimateMemoryUsage(dict_);
++// case Type::LIST:
++// return base::trace_event::EstimateMemoryUsage(list_);
++// default:
++// return 0;
++// }
++// }
+
+ void Value::InternalMoveConstructFrom(Value&& that) {
+ type_ = that.type_;
+
+--- a/base/values.h
++++ b/base/values.h
+@@ -352,7 +352,7 @@ class BASE_EXPORT Value {
+
+ // Estimates dynamic memory usage.
+ // See base/trace_event/memory_usage_estimator.h for more info.
+- size_t EstimateMemoryUsage() const;
++ // size_t EstimateMemoryUsage() const;
+
+ protected:
+ // TODO(crbug.com/646113): Make these private once DictionaryValue and
+
diff --git a/libchrome_tools/update_libchrome.py b/libchrome_tools/update_libchrome.py
index e6155a07d3..b2f8a92d50 100644
--- a/libchrome_tools/update_libchrome.py
+++ b/libchrome_tools/update_libchrome.py
@@ -48,8 +48,6 @@ _LIBCHROME_ROOT = os.path.dirname(_TOOLS_DIR)
# 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/',
@@ -67,24 +65,32 @@ _IMPORT_BLACKLIST = [
'SConstruct',
'libmojo.pc.in',
'testrunner.cc',
+ '*/DEPS',
# No Chromium OWNERS should be imported.
'*/OWNERS',
- # libchrome_tools is out of the update target.
+ # libchrome_tools and soong are out of the update target.
'libchrome_tools/*',
+ 'soong/*',
- # No internal directories.
+ # 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',
+ 'base/allocator/buildflags.h',
+ 'base/android/java/src/org/chromium/base/BuildConfig.java',
+ 'base/cfi_buildflags.h',
+ 'base/debug/debugging_buildflags.h',
+ 'base/memory/protected_memory_buildflags.h',
+ 'base/synchronization/synchronization_buildflags.h',
'gen/*',
+ 'ipc/ipc_buildflags.h',
# 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/*',
@@ -138,7 +144,7 @@ def _clean_existing_dir(output_root):
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')):
+ if (not os.path.isdir(target_path) or path in ('.git', 'libchrome_tools', 'soong')):
continue
shutil.rmtree(target_path)
diff --git a/mojo/BUILD.gn b/mojo/BUILD.gn
index 070e2d1c31..f6cd5b6c6c 100644
--- a/mojo/BUILD.gn
+++ b/mojo/BUILD.gn
@@ -3,13 +3,13 @@
# found in the LICENSE file.
import("//build/config/ui.gni")
+import("//testing/test.gni")
group("mojo") {
# Meta-target, don't link into production code.
testonly = true
deps = [
":tests",
- "//mojo/common",
]
if (!(is_linux && current_cpu == "x86")) {
@@ -17,7 +17,7 @@ group("mojo") {
}
if (is_android) {
- deps += [ "//mojo/android" ]
+ deps += [ "//mojo/public/java/system" ]
}
deps += [ "//services/service_manager:all" ]
@@ -26,16 +26,44 @@ group("mojo") {
group("tests") {
testonly = true
deps = [
+ ":mojo_perftests",
+ ":mojo_unittests",
"//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",
]
}
+
+test("mojo_unittests") {
+ deps = [
+ "//mojo/core:test_sources",
+ "//mojo/core/test:run_all_unittests",
+ "//mojo/public/cpp/base:tests",
+ "//mojo/public/cpp/bindings/tests",
+ "//mojo/public/cpp/platform/tests",
+ "//mojo/public/cpp/system/tests",
+ ]
+}
+
+test("mojo_perftests") {
+ deps = [
+ "//mojo/core/test:run_all_perftests",
+ "//mojo/core/test:test_support",
+ "//mojo/public/c/system/tests:perftests",
+ "//mojo/public/cpp/bindings/tests:perftests",
+ ]
+
+ if (!is_ios) {
+ sources = [
+ "//mojo/core/message_pipe_perftest.cc",
+ ]
+
+ deps += [
+ "//base",
+ "//base/test:test_support",
+ "//mojo/core:embedder_internal",
+ "//mojo/core:test_utils",
+ "//mojo/core/embedder",
+ "//testing/gtest",
+ ]
+ }
+}
diff --git a/mojo/DEPS b/mojo/DEPS
deleted file mode 100644
index 49d7fd30d6..0000000000
--- a/mojo/DEPS
+++ /dev/null
@@ -1,7 +0,0 @@
-include_rules = [
- "+base",
- "+build",
- "+testing",
-
- "+services/service_manager",
-]
diff --git a/mojo/README.md b/mojo/README.md
index e1e7583fc8..46d14fa4c3 100644
--- a/mojo/README.md
+++ b/mojo/README.md
@@ -1,5 +1,4 @@
-# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo
-This document is a subset of the [Mojo documentation](/mojo).
+# Mojo
[TOC]
@@ -10,99 +9,135 @@ 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).
+[**Mojom IDL and bindings generator**](/mojo/public/tools/bindings/README.md).
If you're looking for information on creating and/or connecting to services, see
-the top-level [Services documentation](/services).
+the top-level [Services documentation](/services/README.md).
For specific details regarding the conversion of old things to new things, check
-out [Converting Legacy Chrome IPC To Mojo](/ipc).
+out [Converting Legacy Chrome IPC To Mojo](/ipc/README.md).
## System Overview
-Mojo is a layered collection of runtime libraries providing a platform-agnostic
+Mojo is a 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:
+The documentation here is segmented according to the different libraries
+comprising Mojo. 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)
+![Mojo Library Layering: Core on bottom, language bindings on top, public system support APIs in the middle](https://docs.google.com/drawings/d/1RwhzKblXUZw-zhy_KDVobAYprYSqxZzopXTUsbwzDPw/pub?w=570&h=324)
-## 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.
+## Mojo Core
+In order to use any of the more interesting high-level support libraries like
+the System APIs or Bindings APIs, a process must first initialize Mojo Core.
+This is a one-time initialization which remains active for the remainder of the
+process's lifetime. There are two ways to initialize Mojo Core: via the Embedder
+API, or through a dynamically linked library.
-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.
+### Embedding
+Many processes to be interconnected via Mojo are **embedders**, meaning that
+they statically link against the `//mojo/core/embedder` target and initialize
+Mojo support within each process by calling `mojo::core::Init()`. See
+[**Mojo Core Embedder API**](/mojo/core/embedder/README.md) for more details.
+
+This is a reasonable option when you can guarantee that all interconnected
+process binaries are linking against precisely the same revision of Mojo Core.
+To support other scenarios, use dynamic linking.
+
+## Dynamic Linking
+On some platforms, it's also possible for applications to rely on a
+dynamically-linked Mojo Core library (`libmojo_core.so` or `mojo_core.dll`)
+instead of statically linking against Mojo Core.
+
+In order to take advantage of this mechanism, the corresponding library must be
+present in either:
+
+ - The working directory of the application
+ - A directory named by the `MOJO_CORE_LIBRARY_PATH` environment variable
+ - A directory named explicitly by the application at runtime
+
+Instead of calling `mojo::core::Init()` as embedders do, an application using
+dynamic Mojo Core instead calls `MojoInitialize()` from the C System API. This
+call will attempt to locate (see above) and load a Mojo Core library to support
+subsequent Mojo API usage within the process.
+
+Note that the Mojo Core shared library presents a stable, forward-compatible C
+ABI which can support all current and future versions of the higher-level,
+public (and not binary-stable) System and Bindings APIs.
## 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**.
+Once Mojo is initialized within a process, the public
+[**C System API**](/mojo/public/c/system/README.md) is usable on any thread for
+the remainder of the process's lifetime. This is a lightweight API with a
+relatively small, stable, forward-compatible ABI, comprising the total public
+API surface of the Mojo Core library.
+
+This API is rarely used directly, but it is the foundation upon which all
+higher-level Mojo APIs are built. It exposes the fundamental capabilities to
+create and interact Mojo primitives like **message pipes**, **data pipes**, and
+**shared buffers**, as well as APIs to help bootstrap connections among
+processes.
+
+## Platform Support API
+Mojo provides a small collection of abstractions around platform-specific IPC
+primitives to facilitate bootstrapping Mojo IPC between two processes. See the
+[Platform API](/mojo/public/cpp/platform/README.md) documentation for details.
## 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
+The [**C++ System API**](/mojo/public/cpp/system/README.md) 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. :)
+The [**JavaScript System API**](/third_party/blink/renderer/core/mojo/README.md)
+exposes the Mojo primitives to JavaScript, covering all basic functionality of the
+low-level C API.
### 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.
+The [**Java System API**](/mojo/public/java/system/README.md) 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.
+define some set of interfaces which are used to generate code that resembles
+an 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.
+Interfaces are defined using the
+[**Mojom IDL**](/mojo/public/tools/bindings/README.md), which can be fed to the
+[**bindings generator**](/mojo/public/tools/bindings/README.md) 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,
+[**C++ Bindings API**](/mojo/public/cpp/bindings/README.md) 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. :)
+The [**JavaScript Bindings API**](/mojo/public/js/README.md) provides helper
+classes for working with JavaScript code emitted by the bindings generator.
### Java Bindings
-The [**Java Bindings API**](/mojo/public/java/bindings) provides helper classes
-for working with Java code emitted by the bindings generator.
+The [**Java Bindings API**](/mojo/public/java/bindings/README.md) provides
+helper classes for working with Java code emitted by the bindings generator.
## FAQ
@@ -121,6 +156,10 @@ 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.)
+### What are the performance characteristics of Mojo?
+Compared to the old IPC in Chrome, making a Mojo call is about 1/3 faster and uses
+1/3 fewer context switches. The full data is [available here](https://docs.google.com/document/d/1n7qYjQ5iy8xAkQVMYGqjIy_AXu2_JJtMoAcOOupO_jQ/edit).
+
### 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.
diff --git a/mojo/android/BUILD.gn b/mojo/android/BUILD.gn
deleted file mode 100644
index 1a8cdbdb3c..0000000000
--- a/mojo/android/BUILD.gn
+++ /dev/null
@@ -1,155 +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.
-
-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
deleted file mode 100644
index c80012b562..0000000000
--- a/mojo/android/DEPS
+++ /dev/null
@@ -1,3 +0,0 @@
-include_rules = [
- "+jni",
-]
diff --git a/mojo/android/javatests/AndroidManifest.xml b/mojo/android/javatests/AndroidManifest.xml
deleted file mode 100644
index 32a3927662..0000000000
--- a/mojo/android/javatests/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
- <!-- 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 name must be unique so suffix with "tests" so package loader
- doesn't ignore this. -->
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="org.chromium.mojo.tests">
-
- <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
-
- <uses-permission android:name="android.permission.INJECT_EVENTS"
- tools:ignore="ProtectedPermissions"/>
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.RUN_INSTRUMENTATION" />
-
- <!-- We add an application tag here just so that we can indicate that this
- package needs to link against the android.test library, which is
- needed when building test cases. -->
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="org.chromium.mojo.tests"
- android:label="Tests for org.chromium.mojo"/>
-
-</manifest>
diff --git a/mojo/android/javatests/DEPS b/mojo/android/javatests/DEPS
deleted file mode 100644
index 78cf465ca0..0000000000
--- a/mojo/android/javatests/DEPS
+++ /dev/null
@@ -1,4 +0,0 @@
-include_rules = [
- # out should be allowed by default, but bots are failing on this.
- "+out",
-]
diff --git a/mojo/android/javatests/init_library.cc b/mojo/android/javatests/init_library.cc
deleted file mode 100644
index 9e1a593011..0000000000
--- a/mojo/android/javatests/init_library.cc
+++ /dev/null
@@ -1,50 +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/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
deleted file mode 100644
index fc59009bd4..0000000000
--- a/mojo/android/javatests/mojo_test_case.cc
+++ /dev/null
@@ -1,70 +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 "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<jobject>& jcaller) {
- base::InitAndroidTestMessageLoop();
-}
-
-static jlong SetupTestEnvironment(JNIEnv* env,
- const JavaParamRef<jobject>& jcaller) {
- return reinterpret_cast<intptr_t>(new TestEnvironment());
-}
-
-static void TearDownTestEnvironment(JNIEnv* env,
- const JavaParamRef<jobject>& jcaller,
- jlong test_environment) {
- delete reinterpret_cast<TestEnvironment*>(test_environment);
-}
-
-static void RunLoop(JNIEnv* env,
- const JavaParamRef<jobject>& 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
deleted file mode 100644
index 2ce342848e..0000000000
--- a/mojo/android/javatests/mojo_test_case.h
+++ /dev/null
@@ -1,20 +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 MOJO_ANDROID_JAVATESTS_CORE_TEST_H_
-#define MOJO_ANDROID_JAVATESTS_CORE_TEST_H_
-
-#include <jni.h>
-
-#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
deleted file mode 100644
index 1f8de94500..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java
+++ /dev/null
@@ -1,226 +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.
-
-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<Integer> readData(ByteBuffer elements, DataPipe.ReadFlags flags) {
- // Do nothing.
- return new ResultAnd<Integer>(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<Integer> writeData(ByteBuffer elements, DataPipe.WriteFlags flags) {
- // Do nothing.
- return new ResultAnd<Integer>(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<? extends Handle> handles,
- WriteFlags flags) {
- // Do nothing.
- }
-
- /**
- * @see MessagePipeHandle#readMessage(java.nio.ByteBuffer, int, MessagePipeHandle.ReadFlags)
- */
- @Override
- public ResultAnd<ReadMessageResult> readMessage(
- ByteBuffer bytes, int maxNumberOfHandles, ReadFlags flags) {
- // Do nothing.
- return new ResultAnd<ReadMessageResult>(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
deleted file mode 100644
index f4d7ab7236..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/MojoTestCase.java
+++ /dev/null
@@ -1,66 +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.
-
-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
deleted file mode 100644
index d10d0d7558..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/TestUtils.java
+++ /dev/null
@@ -1,32 +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.
-
-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
deleted file mode 100644
index 38bd3482e4..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java
+++ /dev/null
@@ -1,55 +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.
-
-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
deleted file mode 100644
index d280c774c6..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTest.java
+++ /dev/null
@@ -1,241 +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.
-
-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 <T> void checkConstantField(
- Field field, Class<T> 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 <T> void checkField(Field field, Class<T> 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<Rect, Integer> 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
deleted file mode 100644
index 5554f805a7..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java
+++ /dev/null
@@ -1,108 +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.
-
-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<Message> messages = new ArrayList<Message>();
-
- /**
- * @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<Pair<Message, MessageReceiver>> messagesWithReceivers =
- new ArrayList<Pair<Message, MessageReceiver>>();
-
- /**
- * @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<Handle>());
- }
-
- public static <I extends Interface, P extends Interface.Proxy> P newProxyOverPipe(
- Interface.Manager<I, P> manager, I impl, List<Closeable> toClose) {
- Pair<MessagePipeHandle, MessagePipeHandle> 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
deleted file mode 100644
index eea92ab7b8..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java
+++ /dev/null
@@ -1,211 +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.
-
-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
deleted file mode 100644
index 497be65af2..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java
+++ /dev/null
@@ -1,59 +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.
-
-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<Integer> parameters = new ArrayList<Integer>();
- new Callback1<Integer>() {
- @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<Integer> parameters = new ArrayList<Integer>();
- new Callback7<Integer, Integer, Integer, Integer, Integer, Integer, Integer>() {
- @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
deleted file mode 100644
index 15f9f1fa33..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java
+++ /dev/null
@@ -1,108 +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.
-
-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<MessagePipeHandle, MessagePipeHandle> 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<MessagePipeHandle.ReadMessageResult> 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<Handle>(),
- 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
deleted file mode 100644
index cabe2306b8..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java
+++ /dev/null
@@ -1,104 +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.
-
-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<Thread> mThreadContainer;
-
- /**
- * @see MojoTestCase#setUp()
- */
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mExecutor = ExecutorFactory.getExecutorForCurrentThread(CoreImpl.getInstance());
- mThreadContainer = new ArrayList<Thread>();
- }
-
- /**
- * 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
deleted file mode 100644
index 8cdd4abf29..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java
+++ /dev/null
@@ -1,129 +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.
-
-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<Closeable> mCloseablesToClose = new ArrayList<Closeable>();
-
- /**
- * 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<Integer>() {
- @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
deleted file mode 100644
index d8bd3e85ad..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java
+++ /dev/null
@@ -1,284 +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.
-
-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<Closeable> mCloseablesToClose = new ArrayList<Closeable>();
-
- /**
- * 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<NamedObject> obj) {
- NamedObject.MANAGER.bind(new MockNamedObjectImpl(), obj);
- }
-
- @Override
- public void requestImportedInterface(InterfaceRequest<ImportedInterface> 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<NamedObject.Proxy, InterfaceRequest<NamedObject>> 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<MessagePipeHandle, MessagePipeHandle> 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
deleted file mode 100644
index b2e6ac8521..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java
+++ /dev/null
@@ -1,69 +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.
-
-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
deleted file mode 100644
index 51dbd22800..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java
+++ /dev/null
@@ -1,109 +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.
-
-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<MessagePipeHandle, MessagePipeHandle> mHandles;
- private List<Handle> mHandlesToSend = new ArrayList<Handle>();
- private List<Handle> mHandlesToClose = new ArrayList<Handle>();
- 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.ProducerHandle, DataPipe.ConsumerHandle> 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
deleted file mode 100644
index 6aa1726b4c..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java
+++ /dev/null
@@ -1,231 +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.
-
-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<MessagePipeHandle, MessagePipeHandle> 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<MessagePipeHandle.ReadMessageResult> result =
- mHandle.readMessage(receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE);
-
- assertEquals(MojoResult.OK, result.getMojoResult());
- MessageHeader receivedHeader = new Message(
- receiveBuffer, new ArrayList<Handle>()).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<Handle>(),
- 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<Handle>(),
- MessagePipeHandle.WriteFlags.NONE);
- runLoopUntilIdle();
-
- assertEquals(messageIndex + 1, mReceiver.messagesWithReceivers.size());
- Pair<Message, MessageReceiver> 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<Message, MessageReceiver> 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<MessagePipeHandle.ReadMessageResult> 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.
- * <p>
- * 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<Message, MessageReceiver> 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
deleted file mode 100644
index 2c17e3a194..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/bindings/SerializationTest.java
+++ /dev/null
@@ -1,175 +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.
-
-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
deleted file mode 100644
index 84246188d3..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTest.java
+++ /dev/null
@@ -1,237 +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.
-
-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.
- * <p>
- * 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<TestData> getTestData(String prefix)
- throws FileNotFoundException {
- List<TestData> results = new ArrayList<TestData>();
-
- // 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> testData = getTestData(prefix);
- for (TestData test : testData) {
- assertNull("Unable to read: " + test.dataFile.getName()
- + ": " + test.inputData.getErrorMessage(),
- test.inputData.getErrorMessage());
- List<Handle> handles = new ArrayList<Handle>();
- 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
deleted file mode 100644
index 91b993c3b6..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java
+++ /dev/null
@@ -1,68 +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.
-
-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
deleted file mode 100644
index 623abc3ed2..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java
+++ /dev/null
@@ -1,151 +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.
-
-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
deleted file mode 100644
index 8fb79d7edf..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java
+++ /dev/null
@@ -1,31 +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.
-
-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
deleted file mode 100644
index 5120198feb..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java
+++ /dev/null
@@ -1,545 +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.
-
-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<Handle> mHandlesToClose = new ArrayList<Handle>();
-
- /**
- * @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<? extends Handle, ? extends Handle> 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<MessagePipeHandle.ReadMessageResult> 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<Integer> result = in.writeData(buffer, DataPipe.WriteFlags.NONE);
- assertEquals(MojoResult.OK, result.getMojoResult());
- assertEquals(bytes.length, result.getValue().intValue());
-
- // Query number of bytes available.
- ResultAnd<Integer> 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<? extends Handle, ? extends Handle> 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<MessagePipeHandle, MessagePipeHandle> 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<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
- addHandlePairToClose(handles);
-
- // Testing read on an empty pipe.
- ResultAnd<MessagePipeHandle.ReadMessageResult> 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<MessagePipeHandle, MessagePipeHandle> 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<MessagePipeHandle, MessagePipeHandle> 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<MessagePipeHandle.ReadMessageResult> 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<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
- Pair<MessagePipeHandle, MessagePipeHandle> handlesToShare = core.createMessagePipe(null);
- addHandlePairToClose(handles);
- addHandlePairToClose(handlesToShare);
-
- handles.first.writeMessage(null, Collections.<Handle>singletonList(handlesToShare.second),
- MessagePipeHandle.WriteFlags.NONE);
- assertFalse(handlesToShare.second.isValid());
- ResultAnd<MessagePipeHandle.ReadMessageResult> 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<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> 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<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> 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<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> 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<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> 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<Integer> 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<Integer> 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<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
- addHandlePairToClose(handles);
- try {
- handles.first.writeMessage(null, Collections.<Handle>singletonList(handle),
- MessagePipeHandle.WriteFlags.NONE);
- ResultAnd<MessagePipeHandle.ReadMessageResult> 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<MessagePipeHandle, MessagePipeHandle> 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<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> 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<MessagePipeHandle, MessagePipeHandle> 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
deleted file mode 100644
index e14adb1160..0000000000
--- a/mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java
+++ /dev/null
@@ -1,255 +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.
-
-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<Handle> mHandlesToClose = new ArrayList<Handle>();
- 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<? extends Handle, ? extends Handle> 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<MessagePipeHandle, MessagePipeHandle> 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<MessagePipeHandle, MessagePipeHandle> 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<MessagePipeHandle, MessagePipeHandle> 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<MessagePipeHandle, MessagePipeHandle> 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<MessagePipeHandle, MessagePipeHandle> 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<MessagePipeHandle, MessagePipeHandle> 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
deleted file mode 100644
index 75f79b370e..0000000000
--- a/mojo/android/javatests/validation_test_util.cc
+++ /dev/null
@@ -1,54 +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 "mojo/android/javatests/validation_test_util.h"
-
-#include <stddef.h>
-#include <stdint.h>
-
-#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<jobject> ParseData(
- JNIEnv* env,
- const JavaParamRef<jclass>& jcaller,
- const JavaParamRef<jstring>& data_as_string) {
- std::string input =
- base::android::ConvertJavaStringToUTF8(env, data_as_string);
- std::vector<uint8_t> data;
- size_t num_handles;
- std::string error_message;
- if (!test::ParseValidationTestInput(
- input, &data, &num_handles, &error_message)) {
- ScopedJavaLocalRef<jstring> 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
deleted file mode 100644
index f58dc07885..0000000000
--- a/mojo/android/javatests/validation_test_util.h
+++ /dev/null
@@ -1,20 +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 MOJO_ANDROID_JAVATESTS_VALIDATION_TEST_UTIL_H_
-#define MOJO_ANDROID_JAVATESTS_VALIDATION_TEST_UTIL_H_
-
-#include <jni.h>
-
-#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
deleted file mode 100644
index 7993ba86a3..0000000000
--- a/mojo/android/system/base_run_loop.cc
+++ /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.
-
-#include "mojo/android/system/base_run_loop.h"
-
-#include <jni.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/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<jobject>& jcaller) {
- base::MessageLoop* message_loop = new base::MessageLoop;
- return reinterpret_cast<uintptr_t>(message_loop);
-}
-
-static void Run(JNIEnv* env,
- const JavaParamRef<jobject>& jcaller) {
- base::RunLoop().Run();
-}
-
-static void RunUntilIdle(JNIEnv* env,
- const JavaParamRef<jobject>& jcaller) {
- base::RunLoop().RunUntilIdle();
-}
-
-static void Quit(JNIEnv* env,
- const JavaParamRef<jobject>& jcaller,
- jlong runLoopID) {
- reinterpret_cast<base::MessageLoop*>(runLoopID)->QuitWhenIdle();
-}
-
-static void RunJavaRunnable(
- const base::android::ScopedJavaGlobalRef<jobject>& runnable_ref) {
- Java_BaseRunLoop_runRunnable(base::android::AttachCurrentThread(),
- runnable_ref);
-}
-
-static void PostDelayedTask(JNIEnv* env,
- const JavaParamRef<jobject>& jcaller,
- jlong runLoopID,
- const JavaParamRef<jobject>& runnable,
- jlong delay) {
- base::android::ScopedJavaGlobalRef<jobject> 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<base::MessageLoop*>(runLoopID)
- ->task_runner()
- ->PostDelayedTask(FROM_HERE, base::Bind(&RunJavaRunnable, runnable_ref),
- base::TimeDelta::FromMicroseconds(delay));
-}
-
-static void DeleteMessageLoop(JNIEnv* env,
- const JavaParamRef<jobject>& jcaller,
- jlong runLoopID) {
- base::MessageLoop* message_loop =
- reinterpret_cast<base::MessageLoop*>(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
deleted file mode 100644
index f225c65375..0000000000
--- a/mojo/android/system/base_run_loop.h
+++ /dev/null
@@ -1,20 +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 MOJO_ANDROID_SYSTEM_BASE_RUN_LOOP_H_
-#define MOJO_ANDROID_SYSTEM_BASE_RUN_LOOP_H_
-
-#include <jni.h>
-
-#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
deleted file mode 100644
index 7d5a40220d..0000000000
--- a/mojo/android/system/core_impl.cc
+++ /dev/null
@@ -1,310 +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 "mojo/android/system/core_impl.h"
-
-#include <stddef.h>
-#include <stdint.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/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<jobject>& jcaller) {
- return MojoGetTimeTicksNow();
-}
-
-static ScopedJavaLocalRef<jobject> CreateMessagePipe(
- JNIEnv* env,
- const JavaParamRef<jobject>& jcaller,
- const JavaParamRef<jobject>& options_buffer) {
- const MojoCreateMessagePipeOptions* options = NULL;
- if (options_buffer) {
- const void* buffer_start = env->GetDirectBufferAddress(options_buffer);
- DCHECK(buffer_start);
- DCHECK_EQ(reinterpret_cast<const uintptr_t>(buffer_start) % 8, 0u);
- const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer);
- DCHECK_EQ(buffer_size, sizeof(MojoCreateMessagePipeOptions));
- options = static_cast<const MojoCreateMessagePipeOptions*>(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<jobject> CreateDataPipe(
- JNIEnv* env,
- const JavaParamRef<jobject>& jcaller,
- const JavaParamRef<jobject>& options_buffer) {
- const MojoCreateDataPipeOptions* options = NULL;
- if (options_buffer) {
- const void* buffer_start = env->GetDirectBufferAddress(options_buffer);
- DCHECK(buffer_start);
- DCHECK_EQ(reinterpret_cast<const uintptr_t>(buffer_start) % 8, 0u);
- const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer);
- DCHECK_EQ(buffer_size, sizeof(MojoCreateDataPipeOptions));
- options = static_cast<const MojoCreateDataPipeOptions*>(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<jobject> CreateSharedBuffer(
- JNIEnv* env,
- const JavaParamRef<jobject>& jcaller,
- const JavaParamRef<jobject>& 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<const uintptr_t>(buffer_start) % 8, 0u);
- const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer);
- DCHECK_EQ(buffer_size, sizeof(MojoCreateSharedBufferOptions));
- options = static_cast<const MojoCreateSharedBufferOptions*>(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<jobject>& jcaller,
- jint mojo_handle) {
- return MojoClose(mojo_handle);
-}
-
-static jint QueryHandleSignalsState(JNIEnv* env,
- const JavaParamRef<jobject>& jcaller,
- jint mojo_handle,
- const JavaParamRef<jobject>& buffer) {
- MojoHandleSignalsState* signals_state =
- static_cast<MojoHandleSignalsState*>(env->GetDirectBufferAddress(buffer));
- DCHECK(signals_state);
- DCHECK_EQ(sizeof(MojoHandleSignalsState),
- static_cast<size_t>(env->GetDirectBufferCapacity(buffer)));
- return MojoQueryHandleSignalsState(mojo_handle, signals_state);
-}
-
-static jint WriteMessage(JNIEnv* env,
- const JavaParamRef<jobject>& jcaller,
- jint mojo_handle,
- const JavaParamRef<jobject>& bytes,
- jint num_bytes,
- const JavaParamRef<jobject>& 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<MojoHandle*>(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<jobject> ReadMessage(
- JNIEnv* env,
- const JavaParamRef<jobject>& jcaller,
- jint mojo_handle,
- const JavaParamRef<jobject>& bytes,
- const JavaParamRef<jobject>& 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<MojoHandle*>(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<jobject> ReadData(
- JNIEnv* env,
- const JavaParamRef<jobject>& jcaller,
- jint mojo_handle,
- const JavaParamRef<jobject>& 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<jobject> BeginReadData(
- JNIEnv* env,
- const JavaParamRef<jobject>& 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<void*>(buffer), buffer_size);
- }
- return Java_CoreImpl_newResultAndBuffer(env, result, byte_buffer);
-}
-
-static jint EndReadData(JNIEnv* env,
- const JavaParamRef<jobject>& jcaller,
- jint mojo_handle,
- jint num_bytes_read) {
- return MojoEndReadData(mojo_handle, num_bytes_read);
-}
-
-static ScopedJavaLocalRef<jobject> WriteData(
- JNIEnv* env,
- const JavaParamRef<jobject>& jcaller,
- jint mojo_handle,
- const JavaParamRef<jobject>& 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<jobject> BeginWriteData(
- JNIEnv* env,
- const JavaParamRef<jobject>& 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<jobject>& jcaller,
- jint mojo_handle,
- jint num_bytes_written) {
- return MojoEndWriteData(mojo_handle, num_bytes_written);
-}
-
-static ScopedJavaLocalRef<jobject> Duplicate(
- JNIEnv* env,
- const JavaParamRef<jobject>& jcaller,
- jint mojo_handle,
- const JavaParamRef<jobject>& 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<const MojoDuplicateBufferHandleOptions*>(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<jobject> Map(JNIEnv* env,
- const JavaParamRef<jobject>& 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<jobject>& jcaller,
- const JavaParamRef<jobject>& buffer) {
- void* buffer_start = env->GetDirectBufferAddress(buffer);
- DCHECK(buffer_start);
- return MojoUnmapBuffer(buffer_start);
-}
-
-static jint GetNativeBufferOffset(JNIEnv* env,
- const JavaParamRef<jobject>& jcaller,
- const JavaParamRef<jobject>& buffer,
- jint alignment) {
- jint offset =
- reinterpret_cast<uintptr_t>(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
deleted file mode 100644
index c6249994e5..0000000000
--- a/mojo/android/system/core_impl.h
+++ /dev/null
@@ -1,20 +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 MOJO_ANDROID_SYSTEM_CORE_IMPL_H_
-#define MOJO_ANDROID_SYSTEM_CORE_IMPL_H_
-
-#include <jni.h>
-
-#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
deleted file mode 100644
index 3db6670d71..0000000000
--- a/mojo/android/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java
+++ /dev/null
@@ -1,74 +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.
-
-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
deleted file mode 100644
index 173f80180f..0000000000
--- a/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java
+++ /dev/null
@@ -1,522 +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.
-
-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<BaseRunLoop> mCurrentRunLoop = new ThreadLocal<BaseRunLoop>();
-
- /**
- * 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<MessagePipeHandle, MessagePipeHandle> createMessagePipe(
- MessagePipeHandle.CreateOptions options) {
- ByteBuffer optionsBuffer = null;
- if (options != null) {
- optionsBuffer = allocateDirectBuffer(8);
- optionsBuffer.putInt(0, 8);
- optionsBuffer.putInt(4, options.getFlags().getFlags());
- }
- ResultAnd<IntegerPair> result = nativeCreateMessagePipe(optionsBuffer);
- if (result.getMojoResult() != MojoResult.OK) {
- throw new MojoException(result.getMojoResult());
- }
- return Pair.<MessagePipeHandle, MessagePipeHandle>create(
- new MessagePipeHandleImpl(this, result.getValue().first),
- new MessagePipeHandleImpl(this, result.getValue().second));
- }
-
- /**
- * @see Core#createDataPipe(DataPipe.CreateOptions)
- */
- @Override
- public Pair<ProducerHandle, ConsumerHandle> 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<IntegerPair> result = nativeCreateDataPipe(optionsBuffer);
- if (result.getMojoResult() != MojoResult.OK) {
- throw new MojoException(result.getMojoResult());
- }
- return Pair.<ProducerHandle, ConsumerHandle>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<Integer> 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<? extends Handle> 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<MessagePipeHandle.ReadMessageResult> readMessage(MessagePipeHandleImpl handle,
- ByteBuffer bytes, int maxNumberOfHandles, MessagePipeHandle.ReadFlags flags) {
- ByteBuffer handlesBuffer = null;
- if (maxNumberOfHandles > 0) {
- handlesBuffer = allocateDirectBuffer(maxNumberOfHandles * HANDLE_SIZE);
- }
- ResultAnd<MessagePipeHandle.ReadMessageResult> 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<UntypedHandle> handles =
- new ArrayList<UntypedHandle>(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<Integer> 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<Integer> readData(
- DataPipeConsumerHandleImpl handle, ByteBuffer elements, DataPipe.ReadFlags flags) {
- ResultAnd<Integer> 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<ByteBuffer> 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<Integer> 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<ByteBuffer> 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<Integer> 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<ByteBuffer> 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<ByteBuffer> newResultAndBuffer(int mojoResult, ByteBuffer buffer) {
- return new ResultAnd<>(mojoResult, buffer);
- }
-
- /**
- * Trivial alias for Pair<Integer, Integer>. This is needed because our jni generator is unable
- * to handle class that contains space.
- */
- private static final class IntegerPair extends Pair<Integer, Integer> {
- public IntegerPair(Integer first, Integer second) {
- super(first, second);
- }
- }
-
- @CalledByNative
- private static ResultAnd<MessagePipeHandle.ReadMessageResult> 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<Integer> newResultAndInteger(int mojoResult, int numBytesRead) {
- return new ResultAnd<>(mojoResult, numBytesRead);
- }
-
- @CalledByNative
- private static ResultAnd<IntegerPair> newNativeCreationResult(
- int mojoResult, int mojoHandle1, int mojoHandle2) {
- return new ResultAnd<>(mojoResult, new IntegerPair(mojoHandle1, mojoHandle2));
- }
-
- private native long nativeGetTimeTicksNow();
-
- private native ResultAnd<IntegerPair> nativeCreateMessagePipe(ByteBuffer optionsBuffer);
-
- private native ResultAnd<IntegerPair> nativeCreateDataPipe(ByteBuffer optionsBuffer);
-
- private native ResultAnd<Integer> 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<MessagePipeHandle.ReadMessageResult> nativeReadMessage(
- int mojoHandle, ByteBuffer bytes, ByteBuffer handlesBuffer, int flags);
-
- private native ResultAnd<Integer> nativeReadData(
- int mojoHandle, ByteBuffer elements, int elementsSize, int flags);
-
- private native ResultAnd<ByteBuffer> nativeBeginReadData(
- int mojoHandle, int numBytes, int flags);
-
- private native int nativeEndReadData(int mojoHandle, int numBytesRead);
-
- private native ResultAnd<Integer> nativeWriteData(
- int mojoHandle, ByteBuffer elements, int limit, int flags);
-
- private native ResultAnd<ByteBuffer> nativeBeginWriteData(
- int mojoHandle, int numBytes, int flags);
-
- private native int nativeEndWriteData(int mojoHandle, int numBytesWritten);
-
- private native ResultAnd<Integer> nativeDuplicate(int mojoHandle, ByteBuffer optionsBuffer);
-
- private native ResultAnd<ByteBuffer> 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
deleted file mode 100644
index 83097d7eb4..0000000000
--- a/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java
+++ /dev/null
@@ -1,72 +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.
-
-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<Integer> 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
deleted file mode 100644
index 901f26c029..0000000000
--- a/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java
+++ /dev/null
@@ -1,64 +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.
-
-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<Integer> 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
deleted file mode 100644
index 4d149a48d7..0000000000
--- a/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java
+++ /dev/null
@@ -1,140 +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.
-
-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
deleted file mode 100644
index b3df0aed5b..0000000000
--- a/mojo/android/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java
+++ /dev/null
@@ -1,58 +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.
-
-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<? extends Handle> handles, WriteFlags flags) {
- mCore.writeMessage(this, bytes, handles, flags);
- }
-
- /**
- * @see MessagePipeHandle#readMessage(ByteBuffer, int, ReadFlags)
- */
- @Override
- public ResultAnd<ReadMessageResult> 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
deleted file mode 100644
index 76ef73945f..0000000000
--- a/mojo/android/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java
+++ /dev/null
@@ -1,62 +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.
-
-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
deleted file mode 100644
index 4774ab86e9..0000000000
--- a/mojo/android/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java
+++ /dev/null
@@ -1,72 +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.
-
-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/watcher_impl.cc b/mojo/android/system/watcher_impl.cc
deleted file mode 100644
index 3344447f41..0000000000
--- a/mojo/android/system/watcher_impl.cc
+++ /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.
-
-#include "mojo/android/system/watcher_impl.h"
-
-#include <stddef.h>
-#include <stdint.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/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<jobject>& 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<MojoHandle>(mojo_handle)),
- static_cast<MojoHandleSignals>(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<jobject> 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<jobject> java_watcher_;
-
- DISALLOW_COPY_AND_ASSIGN(WatcherImpl);
-};
-
-} // namespace
-
-static jlong CreateWatcher(JNIEnv* env, const JavaParamRef<jobject>& jcaller) {
- return reinterpret_cast<jlong>(new WatcherImpl);
-}
-
-static jint Start(JNIEnv* env,
- const JavaParamRef<jobject>& jcaller,
- jlong watcher_ptr,
- jint mojo_handle,
- jint signals) {
- auto* watcher = reinterpret_cast<WatcherImpl*>(watcher_ptr);
- return watcher->Start(env, jcaller, mojo_handle, signals);
-}
-
-static void Cancel(JNIEnv* env,
- const JavaParamRef<jobject>& jcaller,
- jlong watcher_ptr) {
- reinterpret_cast<WatcherImpl*>(watcher_ptr)->Cancel();
-}
-
-static void Delete(JNIEnv* env,
- const JavaParamRef<jobject>& jcaller,
- jlong watcher_ptr) {
- delete reinterpret_cast<WatcherImpl*>(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
deleted file mode 100644
index 784f007e25..0000000000
--- a/mojo/android/system/watcher_impl.h
+++ /dev/null
@@ -1,20 +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 MOJO_ANDROID_SYSTEM_WATCHER_IMPL_H_
-#define MOJO_ANDROID_SYSTEM_WATCHER_IMPL_H_
-
-#include <jni.h>
-
-#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
deleted file mode 100644
index 9e74e582d3..0000000000
--- a/mojo/common/BUILD.gn
+++ /dev/null
@@ -1,93 +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.
-
-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
deleted file mode 100644
index e8ac42887d..0000000000
--- a/mojo/common/DEPS
+++ /dev/null
@@ -1,6 +0,0 @@
-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
deleted file mode 100644
index 62895048ad..0000000000
--- a/mojo/common/common_custom_types_struct_traits.cc
+++ /dev/null
@@ -1,108 +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 "mojo/common/common_custom_types_struct_traits.h"
-
-#include "mojo/public/cpp/system/platform_handle.h"
-
-namespace mojo {
-
-// static
-bool StructTraits<common::mojom::String16DataView, base::string16>::Read(
- common::mojom::String16DataView data,
- base::string16* out) {
- ArrayDataView<uint16_t> view;
- data.GetDataDataView(&view);
- out->assign(reinterpret_cast<const base::char16*>(view.data()), view.size());
- return true;
-}
-
-// static
-const std::vector<uint32_t>&
-StructTraits<common::mojom::VersionDataView, base::Version>::components(
- const base::Version& version) {
- return version.components();
-}
-
-// static
-bool StructTraits<common::mojom::VersionDataView, base::Version>::Read(
- common::mojom::VersionDataView data,
- base::Version* out) {
- std::vector<uint32_t> 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<common::mojom::FileDataView, base::File>::fd(
- base::File& file) {
- DCHECK(file.IsValid());
- return mojo::WrapPlatformFile(file.TakePlatformFile());
-}
-
-bool StructTraits<common::mojom::FileDataView, base::File>::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<common::mojom::TextDirection, base::i18n::TextDirection>::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<common::mojom::TextDirection, base::i18n::TextDirection>::
- 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
deleted file mode 100644
index 85815ffd62..0000000000
--- a/mojo/common/common_custom_types_struct_traits.h
+++ /dev/null
@@ -1,85 +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 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<common::mojom::String16DataView, base::string16> {
- static ConstCArray<uint16_t> data(const base::string16& str) {
- return ConstCArray<uint16_t>(str.size(),
- reinterpret_cast<const uint16_t*>(str.data()));
- }
-
- static bool Read(common::mojom::String16DataView data, base::string16* out);
-};
-
-template <>
-struct StructTraits<common::mojom::VersionDataView, base::Version> {
- 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<uint32_t>& 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<common::mojom::UnguessableTokenDataView,
- base::UnguessableToken> {
- 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<common::mojom::FileDataView, base::File> {
- 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<common::mojom::TextDirection, base::i18n::TextDirection> {
- 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
deleted file mode 100644
index e3571d9d86..0000000000
--- a/mojo/common/common_custom_types_unittest.cc
+++ /dev/null
@@ -1,433 +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/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 <typename T>
-struct BounceTestTraits {
- static void ExpectEquality(const T& a, const T& b) {
- EXPECT_EQ(a, b);
- }
-};
-
-template <typename T>
-struct PassTraits {
- using Type = const T&;
-};
-
-template <>
-struct PassTraits<base::Time> {
- using Type = base::Time;
-};
-
-template <>
-struct PassTraits<base::TimeDelta> {
- using Type = base::TimeDelta;
-};
-
-template <>
-struct PassTraits<base::TimeTicks> {
- using Type = base::TimeTicks;
-};
-
-template <typename T>
-void DoExpectResponse(T* expected_value,
- const base::Closure& closure,
- typename PassTraits<T>::Type value) {
- BounceTestTraits<T>::ExpectEquality(*expected_value, value);
- closure.Run();
-}
-
-template <typename T>
-base::Callback<void(typename PassTraits<T>::Type)> ExpectResponse(
- T* expected_value,
- const base::Closure& closure) {
- return base::Bind(&DoExpectResponse<T>, 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<TestFilePath> 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<TestUnguessableToken> 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<TestTime> binding_;
-};
-
-class TestValueImpl : public TestValue {
- public:
- explicit TestValueImpl(TestValueRequest request)
- : binding_(this, std::move(request)) {}
-
- // TestValue implementation:
- void BounceDictionaryValue(
- std::unique_ptr<base::DictionaryValue> in,
- const BounceDictionaryValueCallback& callback) override {
- callback.Run(std::move(in));
- }
-
- void BounceListValue(std::unique_ptr<base::ListValue> in,
- const BounceListValueCallback& callback) override {
- callback.Run(std::move(in));
- }
-
- void BounceValue(std::unique_ptr<base::Value> in,
- const BounceValueCallback& callback) override {
- callback.Run(std::move(in));
- }
-
- private:
- mojo::Binding<TestValue> 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<TestString16> 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<TestFile> 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<TestTextDirection> 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<base::Value> output;
-
- ASSERT_TRUE(ptr->BounceValue(nullptr, &output));
- EXPECT_FALSE(output);
-
- std::unique_ptr<base::Value> input = base::Value::CreateNullValue();
- ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output));
- EXPECT_TRUE(base::Value::Equals(input.get(), output.get()));
-
- input = base::MakeUnique<base::Value>(123);
- ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output));
- EXPECT_TRUE(base::Value::Equals(input.get(), output.get()));
-
- input = base::MakeUnique<base::Value>(1.23);
- ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output));
- EXPECT_TRUE(base::Value::Equals(input.get(), output.get()));
-
- input = base::MakeUnique<base::Value>(false);
- ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output));
- EXPECT_TRUE(base::Value::Equals(input.get(), output.get()));
-
- input = base::MakeUnique<base::Value>("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<base::DictionaryValue>();
- 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<base::ListValue> dict_list(new base::ListValue());
- dict_list->AppendString("string");
- dict_list->AppendBoolean(true);
- dict->Set("list", std::move(dict_list));
- }
-
- std::unique_ptr<base::DictionaryValue> 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<base::ListValue>();
- 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<base::DictionaryValue> list_dict(
- new base::DictionaryValue());
- list_dict->SetString("string", "str");
- list->Append(std::move(list_dict));
- }
- std::unique_ptr<base::ListValue> 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<int>(test_content.size()).ValueOrDie());
-
- base::File file_out;
- ASSERT_TRUE(ptr->BounceFile(std::move(file), &file_out));
- std::vector<char> content(test_content.size());
- ASSERT_TRUE(file_out.IsValid());
- ASSERT_EQ(static_cast<int>(test_content.size()),
- file_out.Read(
- 0, content.data(),
- base::CheckedNumeric<int>(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
deleted file mode 100644
index e705c8d387..0000000000
--- a/mojo/common/data_pipe_drainer.cc
+++ /dev/null
@@ -1,50 +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 "mojo/common/data_pipe_drainer.h"
-
-#include <stdint.h>
-
-#include <utility>
-
-#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
deleted file mode 100644
index 5cff8203e0..0000000000
--- a/mojo/common/data_pipe_drainer.h
+++ /dev/null
@@ -1,49 +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 MOJO_COMMON_DATA_PIPE_DRAINER_H_
-#define MOJO_COMMON_DATA_PIPE_DRAINER_H_
-
-#include <stddef.h>
-
-#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<DataPipeDrainer> 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
deleted file mode 100644
index 9b069b80c5..0000000000
--- a/mojo/common/data_pipe_utils.cc
+++ /dev/null
@@ -1,96 +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 "mojo/common/data_pipe_utils.h"
-
-#include <utility>
-
-#include "base/bind.h"
-#include "mojo/public/cpp/system/wait.h"
-
-namespace mojo {
-namespace common {
-namespace {
-
-bool BlockingCopyHelper(ScopedDataPipeConsumerHandle source,
- const base::Callback<size_t(const void*, uint32_t)>& 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<const char*>(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<char*>(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
deleted file mode 100644
index a3f7c093e4..0000000000
--- a/mojo/common/data_pipe_utils.h
+++ /dev/null
@@ -1,32 +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 MOJO_COMMON_DATA_PIPE_UTILS_H_
-#define MOJO_COMMON_DATA_PIPE_UTILS_H_
-
-#include <stdint.h>
-
-#include <string>
-
-#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
deleted file mode 100644
index fe224734d9..0000000000
--- a/mojo/common/file.mojom
+++ /dev/null
@@ -1,10 +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.
-
-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
deleted file mode 100644
index 26d494139e..0000000000
--- a/mojo/common/file.typemap
+++ /dev/null
@@ -1,13 +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.
-
-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
deleted file mode 100644
index 10ebe058be..0000000000
--- a/mojo/common/file_path.mojom
+++ /dev/null
@@ -1,8 +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.
-
-module mojo.common.mojom;
-
-[Native]
-struct FilePath;
diff --git a/mojo/common/file_path.typemap b/mojo/common/file_path.typemap
deleted file mode 100644
index 66d8c54da8..0000000000
--- a/mojo/common/file_path.typemap
+++ /dev/null
@@ -1,12 +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.
-
-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
deleted file mode 100644
index 48d21d0d3d..0000000000
--- a/mojo/common/mojo_common_export.h
+++ /dev/null
@@ -1,32 +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 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
deleted file mode 100644
index 173c8670cd..0000000000
--- a/mojo/common/string16.mojom
+++ /dev/null
@@ -1,12 +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.
-
-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<uint16> data;
-};
diff --git a/mojo/common/string16.typemap b/mojo/common/string16.typemap
deleted file mode 100644
index 223de29c0d..0000000000
--- a/mojo/common/string16.typemap
+++ /dev/null
@@ -1,12 +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.
-
-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
deleted file mode 100644
index 5ac4bc9c80..0000000000
--- a/mojo/common/struct_traits_unittest.cc
+++ /dev/null
@@ -1,57 +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/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<base::Version>& m,
- const EchoVersionCallback& callback) override {
- callback.Run(m);
- }
-
- base::MessageLoop loop_;
- mojo::BindingSet<TraitsTestService> 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<base::Version> 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<base::Version> 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
deleted file mode 100644
index 0f13680f68..0000000000
--- a/mojo/common/test_common_custom_types.mojom
+++ /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.
-
-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
deleted file mode 100644
index 7d651245ed..0000000000
--- a/mojo/common/text_direction.mojom
+++ /dev/null
@@ -1,12 +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.
-
-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
deleted file mode 100644
index 1f5be8edba..0000000000
--- a/mojo/common/text_direction.typemap
+++ /dev/null
@@ -1,12 +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.
-
-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
deleted file mode 100644
index b403bcac3a..0000000000
--- a/mojo/common/time.mojom
+++ /dev/null
@@ -1,21 +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.
-
-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
deleted file mode 100644
index 0eeb292c01..0000000000
--- a/mojo/common/time.typemap
+++ /dev/null
@@ -1,20 +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.
-
-mojom = "//mojo/common/time.mojom"
-public_headers = [ "//base/time/time.h" ]
-traits_headers = [
- "//ipc/ipc_message_utils.h",
- "//mojo/common/time_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
deleted file mode 100644
index b480edb648..0000000000
--- a/mojo/common/time_struct_traits.h
+++ /dev/null
@@ -1,55 +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 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<common::mojom::TimeDataView, base::Time> {
- 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<common::mojom::TimeDeltaDataView, base::TimeDelta> {
- 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<common::mojom::TimeTicksDataView, base::TimeTicks> {
- 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
deleted file mode 100644
index 7659eea8ab..0000000000
--- a/mojo/common/traits_test_service.mojom
+++ /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.
-
-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
deleted file mode 100644
index ae360310f1..0000000000
--- a/mojo/common/typemaps.gni
+++ /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.
-
-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
deleted file mode 100644
index 32797171ca..0000000000
--- a/mojo/common/unguessable_token.mojom
+++ /dev/null
@@ -1,11 +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.
-
-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
deleted file mode 100644
index ec7b1942b3..0000000000
--- a/mojo/common/unguessable_token.typemap
+++ /dev/null
@@ -1,12 +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.
-
-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
deleted file mode 100644
index 722198c56a..0000000000
--- a/mojo/common/values.mojom
+++ /dev/null
@@ -1,32 +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.
-
-module mojo.common.mojom;
-
-union Value {
- NullValue? null_value;
- bool bool_value;
- int32 int_value;
- double double_value;
- string string_value;
- array<uint8> binary_value;
- DictionaryValue dictionary_value;
- ListValue list_value;
-};
-
-struct ListValue {
- array<Value> values;
-};
-
-struct DictionaryValue {
- map<string, Value> 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
deleted file mode 100644
index f1f3fc2772..0000000000
--- a/mojo/common/values.typemap
+++ /dev/null
@@ -1,25 +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.
-
-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<base::DictionaryValue>[move_only,nullable_is_same_type]",
- "mojo.common.mojom.LegacyListValue=base::ListValue[non_copyable_non_movable]",
- "mojo.common.mojom.ListValue=std::unique_ptr<base::ListValue>[move_only,nullable_is_same_type]",
- "mojo.common.mojom.Value=std::unique_ptr<base::Value>[move_only,nullable_is_same_type]",
-]
diff --git a/mojo/common/values_struct_traits.cc b/mojo/common/values_struct_traits.cc
deleted file mode 100644
index 6af7a39533..0000000000
--- a/mojo/common/values_struct_traits.cc
+++ /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.
-
-#include "base/memory/ptr_util.h"
-#include "mojo/common/values_struct_traits.h"
-
-namespace mojo {
-
-bool StructTraits<common::mojom::ListValueDataView,
- std::unique_ptr<base::ListValue>>::
- Read(common::mojom::ListValueDataView data,
- std::unique_ptr<base::ListValue>* value_out) {
- mojo::ArrayDataView<common::mojom::ValueDataView> view;
- data.GetValuesDataView(&view);
-
- auto list_value = base::MakeUnique<base::ListValue>();
- for (size_t i = 0; i < view.size(); ++i) {
- std::unique_ptr<base::Value> value;
- if (!view.Read(i, &value))
- return false;
-
- list_value->Append(std::move(value));
- }
- *value_out = std::move(list_value);
- return true;
-}
-
-bool StructTraits<common::mojom::DictionaryValueDataView,
- std::unique_ptr<base::DictionaryValue>>::
- Read(common::mojom::DictionaryValueDataView data,
- std::unique_ptr<base::DictionaryValue>* value_out) {
- mojo::MapDataView<mojo::StringDataView, common::mojom::ValueDataView> view;
- data.GetValuesDataView(&view);
- auto dictionary_value = base::MakeUnique<base::DictionaryValue>();
- for (size_t i = 0; i < view.size(); ++i) {
- base::StringPiece key;
- std::unique_ptr<base::Value> 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<base::DictionaryValue>
-CloneTraits<std::unique_ptr<base::DictionaryValue>, false>::Clone(
- const std::unique_ptr<base::DictionaryValue>& input) {
- auto result = base::MakeUnique<base::DictionaryValue>();
- result->MergeDictionary(input.get());
- return result;
-}
-
-bool UnionTraits<common::mojom::ValueDataView, std::unique_ptr<base::Value>>::
- Read(common::mojom::ValueDataView data,
- std::unique_ptr<base::Value>* 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<base::Value>(data.bool_value());
- return true;
- }
- case common::mojom::ValueDataView::Tag::INT_VALUE: {
- *value_out = base::MakeUnique<base::Value>(data.int_value());
- return true;
- }
- case common::mojom::ValueDataView::Tag::DOUBLE_VALUE: {
- *value_out = base::MakeUnique<base::Value>(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<base::Value>(string_value);
- return true;
- }
- case common::mojom::ValueDataView::Tag::BINARY_VALUE: {
- mojo::ArrayDataView<uint8_t> binary_data;
- data.GetBinaryValueDataView(&binary_data);
- *value_out = base::BinaryValue::CreateWithCopiedBuffer(
- reinterpret_cast<const char*>(binary_data.data()),
- binary_data.size());
- return true;
- }
- case common::mojom::ValueDataView::Tag::DICTIONARY_VALUE: {
- std::unique_ptr<base::DictionaryValue> 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<base::ListValue> 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
deleted file mode 100644
index befcf3a6ea..0000000000
--- a/mojo/common/values_struct_traits.h
+++ /dev/null
@@ -1,257 +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 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<base::ListValue> {
- using Element = std::unique_ptr<base::Value>;
- 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<common::mojom::ListValueDataView, base::ListValue> {
- static const base::ListValue& values(const base::ListValue& value) {
- return value;
- }
-};
-
-template <>
-struct StructTraits<common::mojom::ListValueDataView,
- std::unique_ptr<base::ListValue>> {
- static bool IsNull(const std::unique_ptr<base::ListValue>& value) {
- return !value;
- }
-
- static void SetToNull(std::unique_ptr<base::ListValue>* value) {
- value->reset();
- }
-
- static const base::ListValue& values(
- const std::unique_ptr<base::ListValue>& value) {
- return *value;
- }
-
- static bool Read(common::mojom::ListValueDataView data,
- std::unique_ptr<base::ListValue>* value);
-};
-
-template <>
-struct MapTraits<base::DictionaryValue> {
- 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<common::mojom::DictionaryValueDataView,
- base::DictionaryValue> {
- static const base::DictionaryValue& values(
- const base::DictionaryValue& value) {
- return value;
- }
-};
-
-template <>
-struct StructTraits<common::mojom::DictionaryValueDataView,
- std::unique_ptr<base::DictionaryValue>> {
- static bool IsNull(const std::unique_ptr<base::DictionaryValue>& value) {
- return !value;
- }
-
- static void SetToNull(std::unique_ptr<base::DictionaryValue>* value) {
- value->reset();
- }
-
- static const base::DictionaryValue& values(
- const std::unique_ptr<base::DictionaryValue>& value) {
- return *value;
- }
- static bool Read(common::mojom::DictionaryValueDataView data,
- std::unique_ptr<base::DictionaryValue>* value);
-};
-
-template <>
-struct CloneTraits<std::unique_ptr<base::DictionaryValue>, false> {
- static std::unique_ptr<base::DictionaryValue> Clone(
- const std::unique_ptr<base::DictionaryValue>& input);
-};
-
-template <>
-struct UnionTraits<common::mojom::ValueDataView, base::Value> {
- 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<uint8_t> binary_value(const base::Value& value) {
- const base::BinaryValue* binary_value = nullptr;
- if (!value.GetAsBinary(&binary_value))
- NOTREACHED();
- return mojo::ConstCArray<uint8_t>(
- binary_value->GetSize(),
- reinterpret_cast<const uint8_t*>(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<common::mojom::ValueDataView, std::unique_ptr<base::Value>> {
- static bool IsNull(const std::unique_ptr<base::Value>& value) {
- return !value;
- }
-
- static void SetToNull(std::unique_ptr<base::Value>* value) { value->reset(); }
-
- static common::mojom::ValueDataView::Tag GetTag(
- const std::unique_ptr<base::Value>& value) {
- return UnionTraits<common::mojom::ValueDataView, base::Value>::GetTag(
- *value);
- }
-
- static common::mojom::NullValuePtr null_value(
- const std::unique_ptr<base::Value>& value) {
- return UnionTraits<common::mojom::ValueDataView, base::Value>::null_value(
- *value);
- }
- static bool bool_value(const std::unique_ptr<base::Value>& value) {
- return UnionTraits<common::mojom::ValueDataView, base::Value>::bool_value(
- *value);
- }
- static int32_t int_value(const std::unique_ptr<base::Value>& value) {
- return UnionTraits<common::mojom::ValueDataView, base::Value>::int_value(
- *value);
- }
- static double double_value(const std::unique_ptr<base::Value>& value) {
- return UnionTraits<common::mojom::ValueDataView, base::Value>::double_value(
- *value);
- }
- static base::StringPiece string_value(
- const std::unique_ptr<base::Value>& value) {
- return UnionTraits<common::mojom::ValueDataView, base::Value>::string_value(
- *value);
- }
- static mojo::ConstCArray<uint8_t> binary_value(
- const std::unique_ptr<base::Value>& value) {
- return UnionTraits<common::mojom::ValueDataView, base::Value>::binary_value(
- *value);
- }
- static const base::ListValue& list_value(
- const std::unique_ptr<base::Value>& value) {
- return UnionTraits<common::mojom::ValueDataView, base::Value>::list_value(
- *value);
- }
- static const base::DictionaryValue& dictionary_value(
- const std::unique_ptr<base::Value>& value) {
- return UnionTraits<common::mojom::ValueDataView,
- base::Value>::dictionary_value(*value);
- }
-
- static bool Read(common::mojom::ValueDataView data,
- std::unique_ptr<base::Value>* value);
-};
-
-} // namespace mojo
-
-#endif // MOJO_COMMON_VALUES_STRUCT_TRAITS_H_
diff --git a/mojo/common/version.mojom b/mojo/common/version.mojom
deleted file mode 100644
index 6ddf6e6b8c..0000000000
--- a/mojo/common/version.mojom
+++ /dev/null
@@ -1,10 +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.
-
-module mojo.common.mojom;
-
-// Corresponds to |base::Version| in base/version.h
-struct Version {
- array<uint32> components;
-};
diff --git a/mojo/common/version.typemap b/mojo/common/version.typemap
deleted file mode 100644
index fa7fed9acf..0000000000
--- a/mojo/common/version.typemap
+++ /dev/null
@@ -1,12 +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.
-
-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/core/BUILD.gn b/mojo/core/BUILD.gn
new file mode 100644
index 0000000000..49a537bcb7
--- /dev/null
+++ b/mojo/core/BUILD.gn
@@ -0,0 +1,325 @@
+# 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.
+
+import("//build/config/nacl/config.gni")
+import("//testing/test.gni")
+
+component("embedder_internal") {
+ output_name = "mojo_core_embedder_internal"
+ public_deps = [
+ ":impl_for_embedder",
+ ]
+ visibility = [
+ ":test_sources",
+ "//mojo:*",
+ "//mojo/core/embedder",
+ ]
+}
+
+# Bits of the EDK library which do not depend on public API linkage. It is
+# not allowed for this target or any of its transitive dependencies to depend
+# on anything under //mojo/public beyond strict C type definitions.
+#
+# This is templated because it's consumed by both the ":embedder_internal"
+# component library as well as the ":mojo_core" shared library. In the former
+# case we want to export symbols, but in the latter case we don't. The template
+# stamps out two nearly identical targets which differ only in what symbols they
+# export.
+template("core_impl_source_set") {
+ source_set(target_name) {
+ if (invoker.for_shared_library) {
+ visibility = [ ":shared_library" ]
+ } else {
+ visibility = [ ":embedder_internal" ]
+ }
+
+ public = [
+ "channel.h",
+ "configuration.h",
+ "connection_params.h",
+ "core.h",
+ "data_pipe_consumer_dispatcher.h",
+ "data_pipe_control_message.h",
+ "data_pipe_producer_dispatcher.h",
+ "dispatcher.h",
+ "embedder/configuration.h",
+ "embedder/process_error_callback.h",
+ "entrypoints.h",
+ "handle_signals_state.h",
+ "handle_table.h",
+ "invitation_dispatcher.h",
+ "message_pipe_dispatcher.h",
+ "node_controller.h",
+ "options_validation.h",
+ "platform_handle_dispatcher.h",
+ "platform_handle_utils.h",
+ "platform_shared_memory_mapping.h",
+ "request_context.h",
+ "scoped_process_handle.h",
+ "shared_buffer_dispatcher.h",
+ "user_message_impl.h",
+ ]
+
+ sources = [
+ "atomic_flag.h",
+ "broker.h",
+ "broker_win.cc",
+ "channel.cc",
+ "channel_win.cc",
+ "configuration.cc",
+ "connection_params.cc",
+ "core.cc",
+ "data_pipe_consumer_dispatcher.cc",
+ "data_pipe_control_message.cc",
+ "data_pipe_producer_dispatcher.cc",
+ "dispatcher.cc",
+ "entrypoints.cc",
+ "handle_table.cc",
+ "invitation_dispatcher.cc",
+ "message_pipe_dispatcher.cc",
+ "node_channel.cc",
+ "node_channel.h",
+ "node_controller.cc",
+ "platform_handle_dispatcher.cc",
+ "platform_handle_in_transit.cc",
+ "platform_handle_in_transit.h",
+ "platform_handle_utils.cc",
+ "platform_shared_memory_mapping.cc",
+ "request_context.cc",
+ "scoped_process_handle.cc",
+ "shared_buffer_dispatcher.cc",
+ "user_message_impl.cc",
+ "watch.cc",
+ "watch.h",
+ "watcher_dispatcher.cc",
+ "watcher_dispatcher.h",
+ "watcher_set.cc",
+ "watcher_set.h",
+ ]
+
+ public_deps = [
+ "//base",
+ "//mojo/core/ports",
+ "//mojo/public/c/system:headers",
+ "//mojo/public/cpp/platform",
+ ]
+
+ if (is_fuchsia) {
+ sources += [ "channel_fuchsia.cc" ]
+
+ public_deps += [ "//third_party/fuchsia-sdk:fdio" ]
+ }
+
+ if (is_posix) {
+ if (!is_nacl || is_nacl_nonsfi) {
+ sources += [
+ "broker_posix.cc",
+ "channel_posix.cc",
+ ]
+ }
+ }
+
+ 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_host.h",
+ ]
+ }
+
+ defines = []
+ if (invoker.for_shared_library) {
+ defines += [ "MOJO_CORE_SHARED_LIBRARY" ]
+ } else {
+ defines += [ "MOJO_SYSTEM_IMPL_IMPLEMENTATION" ]
+ }
+
+ deps = []
+ if (is_android) {
+ deps += [ "//third_party/ashmem" ]
+ }
+ if (!is_nacl) {
+ deps += [ "//crypto" ]
+ }
+
+ if (is_win) {
+ cflags = [ "/wd4324" ] # Structure was padded due to __declspec(align()),
+ # which is uninteresting.
+ }
+
+ # 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_CORE_LEGACY_PROTOCOL" ]
+ }
+ }
+}
+
+core_impl_source_set("impl_for_embedder") {
+ for_shared_library = false
+}
+
+if (is_chromeos || is_linux || is_android || is_win) {
+ core_impl_source_set("impl_for_shared_library") {
+ for_shared_library = true
+ }
+
+ shared_library("shared_library") {
+ output_name = "mojo_core"
+ sources = [
+ "mojo_core.cc",
+ ]
+ defines = [ "MOJO_CORE_SHARED_LIBRARY" ]
+ deps = [
+ ":impl_for_shared_library",
+ "//mojo/public/c/system:headers",
+ ]
+ if (is_win) {
+ inputs = [
+ "mojo_core.def",
+ ]
+ ldflags = [ "/DEF:" + rebase_path("mojo_core.def", root_build_dir) ]
+ } else {
+ configs += [ ":export_only_thunks_api" ]
+ }
+ }
+
+ if (is_chromeos) {
+ if (target_cpu == "arm" || target_cpu == "arm64") {
+ android32_toolchain = "android_clang_arm"
+ android64_toolchain = "android_clang_arm64"
+ } else {
+ android32_toolchain = "android_clang_x86"
+ android64_toolchain = "android_clang_x64"
+ }
+
+ group("shared_libraries_for_arc") {
+ deps = [
+ ":shared_library_arc32",
+ ":shared_library_arc64",
+ ]
+ }
+
+ copy("shared_library_arc32") {
+ sources = [
+ "${root_out_dir}/${android32_toolchain}/libmojo_core.so",
+ ]
+ outputs = [
+ "${root_out_dir}/libmojo_core_arc32.so",
+ ]
+ deps = [
+ ":shared_library(//build/toolchain/android:${android32_toolchain})",
+ ]
+ }
+
+ copy("shared_library_arc64") {
+ sources = [
+ "${root_out_dir}/${android64_toolchain}/libmojo_core.so",
+ ]
+ outputs = [
+ "${root_out_dir}/libmojo_core_arc64.so",
+ ]
+ deps = [
+ ":shared_library(//build/toolchain/android:${android64_toolchain})",
+ ]
+ }
+ }
+
+ config("export_only_thunks_api") {
+ ldflags = [ "-Wl,--version-script=" +
+ rebase_path("//mojo/core/export_only_thunks_api.lst",
+ root_build_dir) ]
+ }
+
+ if (is_chromeos || is_linux || is_win) {
+ test("mojo_core_unittests") {
+ sources = [
+ "mojo_core_unittest.cc",
+ "run_all_core_unittests.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//base/test:test_support",
+ "//mojo/public/c/system",
+ "//testing/gtest",
+ ]
+
+ data_deps = [
+ ":shared_library",
+ ]
+ }
+ }
+}
+
+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/core/test:test_support",
+ "//testing/gtest:gtest",
+ ]
+}
+
+source_set("test_sources") {
+ testonly = true
+ sources = [
+ "channel_unittest.cc",
+ "core_test_base.cc",
+ "core_test_base.h",
+ "core_unittest.cc",
+ "embedder_unittest.cc",
+ "handle_table_unittest.cc",
+ "message_pipe_unittest.cc",
+ "message_unittest.cc",
+ "options_validation_unittest.cc",
+ "platform_handle_dispatcher_unittest.cc",
+ "quota_unittest.cc",
+ "shared_buffer_dispatcher_unittest.cc",
+ "shared_buffer_unittest.cc",
+ "signals_unittest.cc",
+ "trap_unittest.cc",
+ ]
+
+ if (!is_ios) {
+ sources += [
+ "data_pipe_unittest.cc",
+ "invitation_unittest.cc",
+ "multiprocess_message_pipe_unittest.cc",
+ "platform_wrapper_unittest.cc",
+ ]
+ }
+
+ deps = [
+ ":test_utils",
+ "//base",
+ "//base/test:test_support",
+ "//mojo/core:embedder_internal",
+ "//mojo/core/embedder",
+ "//mojo/core/ports:tests",
+ "//mojo/core/test:run_all_unittests",
+ "//mojo/core/test:test_support",
+ "//mojo/public/cpp/system",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
diff --git a/mojo/core/atomic_flag.h b/mojo/core/atomic_flag.h
new file mode 100644
index 0000000000..075b837ab1
--- /dev/null
+++ b/mojo/core/atomic_flag.h
@@ -0,0 +1,53 @@
+// 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_CORE_ATOMIC_FLAG_H_
+#define MOJO_CORE_ATOMIC_FLAG_H_
+
+#include "base/atomicops.h"
+#include "base/macros.h"
+
+namespace mojo {
+namespace core {
+
+// 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 core
+} // namespace mojo
+
+#endif // MOJO_CORE_ATOMIC_FLAG_H_
diff --git a/mojo/core/broker.h b/mojo/core/broker.h
new file mode 100644
index 0000000000..41e0b89664
--- /dev/null
+++ b/mojo/core/broker.h
@@ -0,0 +1,55 @@
+// 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_CORE_BROKER_H_
+#define MOJO_CORE_BROKER_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/writable_shared_memory_region.h"
+#include "base/synchronization/lock.h"
+#include "mojo/public/cpp/platform/platform_channel_endpoint.h"
+#include "mojo/public/cpp/platform/platform_handle.h"
+
+namespace mojo {
+namespace core {
+
+// The Broker is a channel to the broker process, which allows synchronous IPCs
+// to fulfill shared memory allocation requests on some platforms.
+class Broker {
+ public:
+ // Note: This is blocking, and will wait for the first message over
+ // the endpoint handle in |handle|.
+ explicit Broker(PlatformHandle handle);
+ ~Broker();
+
+ // Returns the platform handle that should be used to establish a NodeChannel
+ // to the process which is inviting us to join its network. This is the first
+ // handle read off the Broker channel upon construction.
+ PlatformChannelEndpoint GetInviterEndpoint();
+
+ // Request a shared buffer from the broker process. Blocks the current thread.
+ base::WritableSharedMemoryRegion GetWritableSharedMemoryRegion(
+ size_t num_bytes);
+
+ private:
+ // Handle to the broker process, used for synchronous IPCs.
+ PlatformHandle sync_channel_;
+
+ // Channel endpoint connected to the inviter process. Recieved in the first
+ // first message over |sync_channel_|.
+ PlatformChannelEndpoint inviter_endpoint_;
+
+ // 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 core
+} // namespace mojo
+
+#endif // MOJO_CORE_BROKER_H_
diff --git a/mojo/core/broker_host.cc b/mojo/core/broker_host.cc
new file mode 100644
index 0000000000..875cc2fe49
--- /dev/null
+++ b/mojo/core/broker_host.cc
@@ -0,0 +1,180 @@
+// 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/core/broker_host.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/memory/platform_shared_memory_region.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "mojo/core/broker_messages.h"
+#include "mojo/core/platform_handle_utils.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace mojo {
+namespace core {
+
+BrokerHost::BrokerHost(base::ProcessHandle client_process,
+ ConnectionParams connection_params,
+ const ProcessErrorCallback& process_error_callback)
+ : process_error_callback_(process_error_callback)
+#if defined(OS_WIN)
+ ,
+ client_process_(ScopedProcessHandle::CloneFrom(client_process))
+#endif
+{
+ CHECK(connection_params.endpoint().is_valid() ||
+ connection_params.server_endpoint().is_valid());
+
+ base::MessageLoopCurrent::Get()->AddDestructionObserver(this);
+
+ channel_ = Channel::Create(this, std::move(connection_params),
+ base::ThreadTaskRunnerHandle::Get());
+ channel_->Start();
+}
+
+BrokerHost::~BrokerHost() {
+ // We're always destroyed on the creation thread, which is the IO thread.
+ base::MessageLoopCurrent::Get()->RemoveDestructionObserver(this);
+
+ if (channel_)
+ channel_->ShutDown();
+}
+
+bool BrokerHost::PrepareHandlesForClient(
+ std::vector<PlatformHandleInTransit>* handles) {
+#if defined(OS_WIN)
+ bool handles_ok = true;
+ for (auto& handle : *handles) {
+ if (!handle.TransferToProcess(client_process_.Clone()))
+ handles_ok = false;
+ }
+ return handles_ok;
+#else
+ return true;
+#endif
+}
+
+bool BrokerHost::SendChannel(PlatformHandle 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
+ std::vector<PlatformHandleInTransit> handles(1);
+ handles[0] = PlatformHandleInTransit(std::move(handle));
+
+ // This may legitimately fail on Windows if the client process is in another
+ // session, e.g., is an elevated process.
+ if (!PrepareHandlesForClient(&handles))
+ 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<void**>(&name_data));
+ data->pipe_name_length = static_cast<uint32_t>(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) {
+ base::subtle::PlatformSharedMemoryRegion region =
+ base::subtle::PlatformSharedMemoryRegion::CreateWritable(num_bytes);
+
+ std::vector<PlatformHandleInTransit> handles(2);
+ if (region.IsValid()) {
+ PlatformHandle h[2];
+ ExtractPlatformHandlesFromSharedMemoryRegionHandle(
+ region.PassPlatformHandle(), &h[0], &h[1]);
+ handles[0] = PlatformHandleInTransit(std::move(h[0]));
+ handles[1] = PlatformHandleInTransit(std::move(h[1]));
+#if !defined(OS_POSIX) || defined(OS_ANDROID) || defined(OS_FUCHSIA) || \
+ (defined(OS_MACOSX) && !defined(OS_IOS))
+ // Non-POSIX systems, as well as Android, Fuchsia, and non-iOS Mac, only use
+ // a single handle to represent a writable region.
+ DCHECK(!handles[1].handle().is_valid());
+ handles.resize(1);
+#else
+ DCHECK(handles[1].handle().is_valid());
+#endif
+ }
+
+ BufferResponseData* response;
+ Channel::MessagePtr message = CreateBrokerMessage(
+ BrokerMessageType::BUFFER_RESPONSE, handles.size(), 0, &response);
+ if (!handles.empty()) {
+ base::UnguessableToken guid = region.GetGUID();
+ response->guid_high = guid.GetHighForSerialization();
+ response->guid_low = guid.GetLowForSerialization();
+ PrepareHandlesForClient(&handles);
+ message->SetHandles(std::move(handles));
+ }
+
+ channel_->Write(std::move(message));
+}
+
+void BrokerHost::OnChannelMessage(const void* payload,
+ size_t payload_size,
+ std::vector<PlatformHandle> handles) {
+ if (payload_size < sizeof(BrokerMessageHeader))
+ return;
+
+ const BrokerMessageHeader* header =
+ static_cast<const BrokerMessageHeader*>(payload);
+ switch (header->type) {
+ case BrokerMessageType::BUFFER_REQUEST:
+ if (payload_size ==
+ sizeof(BrokerMessageHeader) + sizeof(BufferRequestData)) {
+ const BufferRequestData* request =
+ reinterpret_cast<const BufferRequestData*>(header + 1);
+ OnBufferRequest(request->size);
+ }
+ break;
+
+ default:
+ DLOG(ERROR) << "Unexpected broker message type: " << header->type;
+ break;
+ }
+}
+
+void BrokerHost::OnChannelError(Channel::Error error) {
+ if (process_error_callback_ &&
+ error == Channel::Error::kReceivedMalformedData) {
+ process_error_callback_.Run("Broker host received malformed message");
+ }
+
+ delete this;
+}
+
+void BrokerHost::WillDestroyCurrentMessageLoop() {
+ delete this;
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/broker_host.h b/mojo/core/broker_host.h
new file mode 100644
index 0000000000..3ff36eaa9f
--- /dev/null
+++ b/mojo/core/broker_host.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_CORE_BROKER_HOST_H_
+#define MOJO_CORE_BROKER_HOST_H_
+
+#include <stdint.h>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/process/process_handle.h"
+#include "base/strings/string_piece.h"
+#include "build/build_config.h"
+#include "mojo/core/channel.h"
+#include "mojo/core/connection_params.h"
+#include "mojo/core/embedder/process_error_callback.h"
+#include "mojo/core/platform_handle_in_transit.h"
+#include "mojo/core/scoped_process_handle.h"
+#include "mojo/public/cpp/platform/platform_handle.h"
+
+namespace mojo {
+namespace core {
+
+// The BrokerHost is a channel to a broker client process, servicing synchronous
+// IPCs issued by the client.
+class BrokerHost : public Channel::Delegate,
+ public base::MessageLoopCurrent::DestructionObserver {
+ public:
+ BrokerHost(base::ProcessHandle client_process,
+ ConnectionParams connection_params,
+ const ProcessErrorCallback& process_error_callback);
+
+ // Send |handle| to the client, to be used to establish a NodeChannel to us.
+ bool SendChannel(PlatformHandle handle);
+
+#if defined(OS_WIN)
+ // Sends a named channel to the client. Like above, but for named pipes.
+ void SendNamedChannel(const base::StringPiece16& pipe_name);
+#endif
+
+ private:
+ ~BrokerHost() override;
+
+ bool PrepareHandlesForClient(std::vector<PlatformHandleInTransit>* handles);
+
+ // Channel::Delegate:
+ void OnChannelMessage(const void* payload,
+ size_t payload_size,
+ std::vector<PlatformHandle> handles) override;
+ void OnChannelError(Channel::Error error) override;
+
+ // base::MessageLoopCurrent::DestructionObserver:
+ void WillDestroyCurrentMessageLoop() override;
+
+ void OnBufferRequest(uint32_t num_bytes);
+
+ const ProcessErrorCallback process_error_callback_;
+
+#if defined(OS_WIN)
+ ScopedProcessHandle client_process_;
+#endif
+
+ scoped_refptr<Channel> channel_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrokerHost);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_BROKER_HOST_H_
diff --git a/mojo/core/broker_messages.h b/mojo/core/broker_messages.h
new file mode 100644
index 0000000000..3bad6f5d9d
--- /dev/null
+++ b/mojo/core/broker_messages.h
@@ -0,0 +1,97 @@
+// 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_CORE_BROKER_MESSAGES_H_
+#define MOJO_CORE_BROKER_MESSAGES_H_
+
+#include "build/build_config.h"
+#include "mojo/core/channel.h"
+
+namespace mojo {
+namespace core {
+
+#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;
+};
+
+struct BufferResponseData {
+ uint64_t guid_high;
+ uint64_t guid_low;
+};
+
+#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 <typename T>
+inline bool GetBrokerMessageData(Channel::Message* message, T** out_data) {
+ const size_t required_size = sizeof(BrokerMessageHeader) + sizeof(T);
+ if (message->payload_size() < required_size)
+ return false;
+
+ auto* header = static_cast<BrokerMessageHeader*>(message->mutable_payload());
+ *out_data = reinterpret_cast<T*>(header + 1);
+ return true;
+}
+
+template <typename T>
+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<BrokerMessageHeader*>(message->mutable_payload());
+ header->type = type;
+ header->padding = 0;
+ *out_message_data = reinterpret_cast<T*>(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<BrokerMessageHeader*>(message->mutable_payload());
+ header->type = type;
+ header->padding = 0;
+ return message;
+}
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_BROKER_MESSAGES_H_
diff --git a/mojo/core/broker_posix.cc b/mojo/core/broker_posix.cc
new file mode 100644
index 0000000000..a2501af5f5
--- /dev/null
+++ b/mojo/core/broker_posix.cc
@@ -0,0 +1,148 @@
+// 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/core/broker.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/memory/platform_shared_memory_region.h"
+#include "build/build_config.h"
+#include "mojo/core/broker_messages.h"
+#include "mojo/core/channel.h"
+#include "mojo/core/platform_handle_utils.h"
+#include "mojo/public/cpp/platform/socket_utils_posix.h"
+
+namespace mojo {
+namespace core {
+
+namespace {
+
+Channel::MessagePtr WaitForBrokerMessage(
+ int socket_fd,
+ BrokerMessageType expected_type,
+ size_t expected_num_handles,
+ size_t expected_data_size,
+ std::vector<PlatformHandle>* incoming_handles) {
+ Channel::MessagePtr message(new Channel::Message(
+ sizeof(BrokerMessageHeader) + expected_data_size, expected_num_handles));
+ std::vector<base::ScopedFD> incoming_fds;
+ ssize_t read_result =
+ SocketRecvmsg(socket_fd, const_cast<void*>(message->data()),
+ message->data_num_bytes(), &incoming_fds, true /* block */);
+ bool error = false;
+ if (read_result < 0) {
+ PLOG(ERROR) << "Recvmsg error";
+ error = true;
+ } else if (static_cast<size_t>(read_result) != message->data_num_bytes()) {
+ LOG(ERROR) << "Invalid node channel message";
+ error = true;
+ } else if (incoming_fds.size() != expected_num_handles) {
+ LOG(ERROR) << "Received unexpected number of handles";
+ error = true;
+ }
+
+ if (error)
+ return nullptr;
+
+ const BrokerMessageHeader* header =
+ reinterpret_cast<const BrokerMessageHeader*>(message->payload());
+ if (header->type != expected_type) {
+ LOG(ERROR) << "Unexpected message";
+ return nullptr;
+ }
+
+ incoming_handles->reserve(incoming_fds.size());
+ for (size_t i = 0; i < incoming_fds.size(); ++i)
+ incoming_handles->emplace_back(std::move(incoming_fds[i]));
+
+ return message;
+}
+
+} // namespace
+
+Broker::Broker(PlatformHandle handle) : sync_channel_(std::move(handle)) {
+ CHECK(sync_channel_.is_valid());
+
+ int fd = sync_channel_.GetFD().get();
+ // Mark the channel as blocking.
+ int flags = fcntl(fd, F_GETFL);
+ PCHECK(flags != -1);
+ flags = fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
+ PCHECK(flags != -1);
+
+ // Wait for the first message, which should contain a handle.
+ std::vector<PlatformHandle> incoming_platform_handles;
+ if (WaitForBrokerMessage(fd, BrokerMessageType::INIT, 1, 0,
+ &incoming_platform_handles)) {
+ inviter_endpoint_ =
+ PlatformChannelEndpoint(std::move(incoming_platform_handles[0]));
+ }
+}
+
+Broker::~Broker() = default;
+
+PlatformChannelEndpoint Broker::GetInviterEndpoint() {
+ return std::move(inviter_endpoint_);
+}
+
+base::WritableSharedMemoryRegion Broker::GetWritableSharedMemoryRegion(
+ 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 =
+ SocketWrite(sync_channel_.GetFD().get(), out_message->data(),
+ out_message->data_num_bytes());
+ if (write_result < 0) {
+ PLOG(ERROR) << "Error sending sync broker message";
+ return base::WritableSharedMemoryRegion();
+ } else if (static_cast<size_t>(write_result) !=
+ out_message->data_num_bytes()) {
+ LOG(ERROR) << "Error sending complete broker message";
+ return base::WritableSharedMemoryRegion();
+ }
+
+#if !defined(OS_POSIX) || defined(OS_ANDROID) || defined(OS_FUCHSIA) || \
+ (defined(OS_MACOSX) && !defined(OS_IOS))
+ // Non-POSIX systems, as well as Android, Fuchsia, and non-iOS Mac, only use
+ // a single handle to represent a writable region.
+ constexpr size_t kNumExpectedHandles = 1;
+#else
+ constexpr size_t kNumExpectedHandles = 2;
+#endif
+
+ std::vector<PlatformHandle> handles;
+ Channel::MessagePtr message = WaitForBrokerMessage(
+ sync_channel_.GetFD().get(), BrokerMessageType::BUFFER_RESPONSE,
+ kNumExpectedHandles, sizeof(BufferResponseData), &handles);
+ if (message) {
+ const BufferResponseData* data;
+ if (!GetBrokerMessageData(message.get(), &data))
+ return base::WritableSharedMemoryRegion();
+
+ if (handles.size() == 1)
+ handles.emplace_back();
+ return base::WritableSharedMemoryRegion::Deserialize(
+ base::subtle::PlatformSharedMemoryRegion::Take(
+ CreateSharedMemoryRegionHandleFromPlatformHandles(
+ std::move(handles[0]), std::move(handles[1])),
+ base::subtle::PlatformSharedMemoryRegion::Mode::kWritable,
+ num_bytes,
+ base::UnguessableToken::Deserialize(data->guid_high,
+ data->guid_low)));
+ }
+
+ return base::WritableSharedMemoryRegion();
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/broker_win.cc b/mojo/core/broker_win.cc
new file mode 100644
index 0000000000..3ebc8839ce
--- /dev/null
+++ b/mojo/core/broker_win.cc
@@ -0,0 +1,162 @@
+// 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 <windows.h>
+
+#include <limits>
+#include <utility>
+
+#include "base/debug/alias.h"
+#include "base/memory/platform_shared_memory_region.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/string_piece.h"
+#include "mojo/core/broker.h"
+#include "mojo/core/broker_messages.h"
+#include "mojo/core/channel.h"
+#include "mojo/core/platform_handle_utils.h"
+#include "mojo/public/cpp/platform/named_platform_channel.h"
+
+namespace mojo {
+namespace core {
+
+namespace {
+
+// 256 bytes should be enough for anyone!
+const size_t kMaxBrokerMessageSize = 256;
+
+bool TakeHandlesFromBrokerMessage(Channel::Message* message,
+ size_t num_handles,
+ PlatformHandle* out_handles) {
+ if (message->num_handles() != num_handles) {
+ DLOG(ERROR) << "Received unexpected number of handles in broker message";
+ return false;
+ }
+
+ std::vector<PlatformHandleInTransit> handles = message->TakeHandles();
+ DCHECK_EQ(handles.size(), num_handles);
+ DCHECK(out_handles);
+
+ for (size_t i = 0; i < num_handles; ++i)
+ out_handles[i] = handles[i].TakeHandle();
+ return true;
+}
+
+Channel::MessagePtr WaitForBrokerMessage(HANDLE pipe_handle,
+ BrokerMessageType expected_type) {
+ char buffer[kMaxBrokerMessageSize];
+ DWORD bytes_read = 0;
+ BOOL result = ::ReadFile(pipe_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<size_t>(bytes_read));
+ if (!message || message->payload_size() < sizeof(BrokerMessageHeader)) {
+ LOG(ERROR) << "Invalid broker message";
+
+ base::debug::Alias(&buffer[0]);
+ base::debug::Alias(&bytes_read);
+ CHECK(false);
+ return nullptr;
+ }
+
+ const BrokerMessageHeader* header =
+ reinterpret_cast<const BrokerMessageHeader*>(message->payload());
+ if (header->type != expected_type) {
+ LOG(ERROR) << "Unexpected broker message type";
+
+ base::debug::Alias(&buffer[0]);
+ base::debug::Alias(&bytes_read);
+ CHECK(false);
+ return nullptr;
+ }
+
+ return message;
+}
+
+} // namespace
+
+Broker::Broker(PlatformHandle handle) : sync_channel_(std::move(handle)) {
+ CHECK(sync_channel_.is_valid());
+ Channel::MessagePtr message = WaitForBrokerMessage(
+ sync_channel_.GetHandle().Get(), BrokerMessageType::INIT);
+
+ // If we fail to read a message (broken pipe), just return early. The inviter
+ // handle will be null and callers must handle this gracefully.
+ if (!message)
+ return;
+
+ PlatformHandle endpoint_handle;
+ if (TakeHandlesFromBrokerMessage(message.get(), 1, &endpoint_handle)) {
+ inviter_endpoint_ = PlatformChannelEndpoint(std::move(endpoint_handle));
+ } else {
+ // If the message has no handles, we expect it to carry pipe name instead.
+ const BrokerMessageHeader* header =
+ static_cast<const BrokerMessageHeader*>(message->payload());
+ CHECK_GE(message->payload_size(),
+ sizeof(BrokerMessageHeader) + sizeof(InitData));
+ const InitData* data = reinterpret_cast<const InitData*>(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<const base::char16*>(data + 1);
+ CHECK(data->pipe_name_length);
+ inviter_endpoint_ = NamedPlatformChannel::ConnectToServer(
+ base::StringPiece16(name_data, data->pipe_name_length).as_string());
+ }
+}
+
+Broker::~Broker() {}
+
+PlatformChannelEndpoint Broker::GetInviterEndpoint() {
+ return std::move(inviter_endpoint_);
+}
+
+base::WritableSharedMemoryRegion Broker::GetWritableSharedMemoryRegion(
+ 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<uint32_t>(num_bytes);
+ DWORD bytes_written = 0;
+ BOOL result =
+ ::WriteFile(sync_channel_.GetHandle().Get(), out_message->data(),
+ static_cast<DWORD>(out_message->data_num_bytes()),
+ &bytes_written, nullptr);
+ if (!result ||
+ static_cast<size_t>(bytes_written) != out_message->data_num_bytes()) {
+ PLOG(ERROR) << "Error sending sync broker message";
+ return base::WritableSharedMemoryRegion();
+ }
+
+ PlatformHandle handle;
+ Channel::MessagePtr response = WaitForBrokerMessage(
+ sync_channel_.GetHandle().Get(), BrokerMessageType::BUFFER_RESPONSE);
+ if (response && TakeHandlesFromBrokerMessage(response.get(), 1, &handle)) {
+ BufferResponseData* data;
+ if (!GetBrokerMessageData(response.get(), &data))
+ return base::WritableSharedMemoryRegion();
+ return base::WritableSharedMemoryRegion::Deserialize(
+ base::subtle::PlatformSharedMemoryRegion::Take(
+ CreateSharedMemoryRegionHandleFromPlatformHandles(std::move(handle),
+ PlatformHandle()),
+ base::subtle::PlatformSharedMemoryRegion::Mode::kWritable,
+ num_bytes,
+ base::UnguessableToken::Deserialize(data->guid_high,
+ data->guid_low)));
+ }
+
+ return base::WritableSharedMemoryRegion();
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/channel.cc b/mojo/core/channel.cc
new file mode 100644
index 0000000000..85d2a629be
--- /dev/null
+++ b/mojo/core/channel.cc
@@ -0,0 +1,738 @@
+// 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/core/channel.h"
+
+#include <stddef.h>
+#include <string.h>
+
+#include <algorithm>
+#include <limits>
+#include <utility>
+
+#include "base/macros.h"
+#include "base/memory/aligned_memory.h"
+#include "base/numerics/safe_math.h"
+#include "base/process/process_handle.h"
+#include "build/build_config.h"
+#include "mojo/core/configuration.h"
+#include "mojo/core/core.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 core {
+
+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;
+
+// TODO(rockot): Increase this if/when Channel implementations support more.
+// Linux: The platform imposes a limit of 253 handles per sendmsg().
+// Fuchsia: The zx_channel_write() API supports up to 64 handles.
+const size_t kMaxAttachedHandles = 64;
+
+Channel::Message::Message(size_t payload_size, size_t max_handles)
+ : Message(payload_size, payload_size, max_handles) {}
+
+Channel::Message::Message(size_t payload_size,
+ size_t max_handles,
+ MessageType message_type)
+ : Message(payload_size, payload_size, max_handles, message_type) {}
+
+Channel::Message::Message(size_t capacity,
+ size_t payload_size,
+ size_t max_handles)
+#if defined(MOJO_CORE_LEGACY_PROTOCOL)
+ : Message(capacity, payload_size, max_handles, MessageType::NORMAL_LEGACY) {
+}
+#else
+ : Message(capacity, payload_size, max_handles, MessageType::NORMAL) {
+}
+#endif
+
+Channel::Message::Message(size_t capacity,
+ size_t payload_size,
+ size_t max_handles,
+ MessageType message_type)
+ : max_handles_(max_handles) {
+ DCHECK_GE(capacity, payload_size);
+ 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_FUCHSIA)
+ // On Fuchsia we serialize handle types into the extra header space.
+ extra_header_size = max_handles_ * sizeof(HandleInfoEntry);
+#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 <index, port> 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);
+
+ capacity_ = header_size + extra_header_size + capacity;
+ size_ = header_size + extra_header_size + payload_size;
+ data_ = static_cast<char*>(
+ base::AlignedAlloc(capacity_, 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(base::IsValueInRangeForNumericType<uint32_t>(size_));
+ legacy_header()->num_bytes = static_cast<uint32_t>(size_);
+
+ DCHECK(base::IsValueInRangeForNumericType<uint16_t>(header_size +
+ extra_header_size));
+ legacy_header()->message_type = message_type;
+
+ if (is_legacy_message) {
+ legacy_header()->num_handles = static_cast<uint16_t>(max_handles);
+ } else {
+ header()->num_header_bytes =
+ static_cast<uint16_t>(header_size + extra_header_size);
+ }
+
+ if (max_handles_ > 0) {
+#if defined(OS_WIN)
+ handles_ = reinterpret_cast<HandleEntry*>(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<MachPortsExtraHeader*>(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<uint32_t>(MACH_PORT_NULL)};
+ }
+#endif
+ }
+}
+
+Channel::Message::~Message() {
+ base::AlignedFree(data_);
+}
+
+// static
+Channel::MessagePtr Channel::Message::Deserialize(
+ const void* data,
+ size_t data_num_bytes,
+ base::ProcessHandle from_process) {
+ if (data_num_bytes < sizeof(LegacyHeader))
+ return nullptr;
+
+ const LegacyHeader* legacy_header =
+ reinterpret_cast<const LegacyHeader*>(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<const Header*>(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<const char*>(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<const char*>(data) + header->num_header_bytes;
+ }
+
+#if defined(OS_WIN)
+ uint32_t max_handles = extra_header_size / sizeof(HandleEntry);
+#elif defined(OS_FUCHSIA)
+ uint32_t max_handles = extra_header_size / sizeof(HandleInfoEntry);
+#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<const char*>(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)
+ std::vector<PlatformHandleInTransit> handles(num_handles);
+ for (size_t i = 0; i < num_handles; i++) {
+ HANDLE handle = base::win::Uint32ToHandle(message->handles_[i].handle);
+ if (from_process == base::kNullProcessHandle) {
+ handles[i] = PlatformHandleInTransit(
+ PlatformHandle(base::win::ScopedHandle(handle)));
+ } else {
+ handles[i] = PlatformHandleInTransit(
+ PlatformHandleInTransit::TakeIncomingRemoteHandle(handle,
+ from_process));
+ }
+ }
+ message->SetHandles(std::move(handles));
+#endif
+
+ return message;
+}
+
+size_t Channel::Message::capacity() const {
+ if (is_legacy_message())
+ return capacity_ - sizeof(LegacyHeader);
+ return capacity_ - header()->num_header_bytes;
+}
+
+void Channel::Message::ExtendPayload(size_t new_payload_size) {
+ size_t capacity_without_header = capacity();
+ size_t header_size = capacity_ - capacity_without_header;
+ if (new_payload_size > capacity_without_header) {
+ size_t new_capacity =
+ std::max(capacity_without_header * 2, new_payload_size) + header_size;
+ void* new_data = base::AlignedAlloc(new_capacity, kChannelMessageAlignment);
+ memcpy(new_data, data_, capacity_);
+ base::AlignedFree(data_);
+ data_ = static_cast<char*>(new_data);
+ capacity_ = new_capacity;
+
+ if (max_handles_ > 0) {
+// We also need to update the cached extra header addresses in case the
+// payload buffer has been relocated.
+#if defined(OS_WIN)
+ handles_ = reinterpret_cast<HandleEntry*>(mutable_extra_header());
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ mach_ports_header_ =
+ reinterpret_cast<MachPortsExtraHeader*>(mutable_extra_header());
+#endif
+ }
+ }
+ size_ = header_size + new_payload_size;
+ DCHECK(base::IsValueInRangeForNumericType<uint32_t>(size_));
+ legacy_header()->num_bytes = static_cast<uint32_t>(size_);
+}
+
+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<void*>(legacy_header() + 1);
+ return data_ + header()->num_header_bytes;
+}
+
+const void* Channel::Message::payload() const {
+ if (is_legacy_message())
+ return static_cast<const void*>(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.is_mach_port_name() || handle.handle().is_mach_port())
+ 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<LegacyHeader*>(data_);
+}
+
+Channel::Message::Header* Channel::Message::header() const {
+ DCHECK(!is_legacy_message());
+ return reinterpret_cast<Header*>(data_);
+}
+
+void Channel::Message::SetHandles(std::vector<PlatformHandle> new_handles) {
+ std::vector<PlatformHandleInTransit> handles;
+ handles.reserve(new_handles.size());
+ for (auto& h : new_handles) {
+ handles.emplace_back(PlatformHandleInTransit(std::move(h)));
+ }
+ SetHandles(std::move(handles));
+}
+
+void Channel::Message::SetHandles(
+ std::vector<PlatformHandleInTransit> new_handles) {
+ if (is_legacy_message()) {
+ // Old semantics for ChromeOS and Android
+ if (legacy_header()->num_handles == 0) {
+ CHECK(new_handles.empty());
+ return;
+ }
+ CHECK_EQ(new_handles.size(), legacy_header()->num_handles);
+ std::swap(handle_vector_, new_handles);
+ return;
+ }
+
+ if (max_handles_ == 0) {
+ CHECK(new_handles.empty());
+ return;
+ }
+
+ CHECK_LE(new_handles.size(), max_handles_);
+ header()->num_handles = static_cast<uint16_t>(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++) {
+ HANDLE handle = handle_vector_[i].remote_handle();
+ if (handle == INVALID_HANDLE_VALUE)
+ handle = handle_vector_[i].handle().GetHandle().Get();
+ handles_[i].handle = base::win::HandleToUint32(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<uint32_t>(MACH_PORT_NULL)};
+ }
+ for (size_t i = 0; i < handle_vector_.size(); i++) {
+ if (!handle_vector_[i].is_mach_port_name() &&
+ !handle_vector_[i].handle().is_mach_port()) {
+ DCHECK(handle_vector_[i].handle().is_valid_fd());
+ continue;
+ }
+
+ mach_port_t port = handle_vector_[i].is_mach_port_name()
+ ? handle_vector_[i].mach_port_name()
+ : handle_vector_[i].handle().GetMachPort().get();
+ 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<uint16_t>(mach_port_index);
+ }
+#endif
+}
+
+std::vector<PlatformHandleInTransit> 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<uint32_t>(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_);
+}
+
+std::vector<PlatformHandleInTransit>
+Channel::Message::TakeHandlesForTransport() {
+#if defined(OS_WIN)
+ // Not necessary on Windows.
+ NOTREACHED();
+ return std::vector<PlatformHandleInTransit>();
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ std::vector<PlatformHandleInTransit> non_mach_handles;
+ for (auto& handle : handle_vector_) {
+ if (handle.is_mach_port_name() || handle.handle().is_mach_port()) {
+ // Ownership is effectively transferred to the receiving process
+ // out-of-band via MachPortRelay.
+ handle.CompleteTransit();
+ } else {
+ non_mach_handles.emplace_back(std::move(handle));
+ }
+ }
+ handle_vector_.clear();
+ return non_mach_handles;
+#else
+ return std::move(handle_vector_);
+#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<char*>(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<char*>(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<char*>(
+ 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<char*>(
+ 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() {
+ ShutDownImpl();
+ delegate_ = nullptr;
+}
+
+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_consume_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<uintptr_t>(read_buffer_->occupied_bytes()))) {
+ read_buffer_->Realign();
+ }
+
+ // We have at least enough data available for a LegacyHeader.
+ const Message::LegacyHeader* legacy_header =
+ reinterpret_cast<const Message::LegacyHeader*>(
+ read_buffer_->occupied_bytes());
+
+ const size_t kMaxMessageSize = GetConfiguration().max_message_num_bytes;
+ if (legacy_header->num_bytes < sizeof(Message::LegacyHeader) ||
+ legacy_header->num_bytes > kMaxMessageSize) {
+ 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<const Message::Header*>(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<Message::Header*>(
+ const_cast<char*>(read_buffer_->occupied_bytes()) +
+ header->num_header_bytes)
+ : nullptr;
+ } else {
+ payload_size = legacy_header->num_bytes - sizeof(Message::LegacyHeader);
+ payload = payload_size
+ ? const_cast<Message::LegacyHeader*>(&legacy_header[1])
+ : nullptr;
+ }
+
+ const uint16_t num_handles =
+ header ? header->num_handles : legacy_header->num_handles;
+ std::vector<PlatformHandle> handles;
+ bool deferred = false;
+ if (num_handles > 0) {
+ if (!GetReadPlatformHandles(payload, payload_size, num_handles,
+ extra_header, extra_header_size, &handles,
+ &deferred)) {
+ return false;
+ }
+
+ if (handles.empty()) {
+ // 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) {
+ DCHECK(!deferred);
+ if (!OnControlMessage(legacy_header->message_type, payload, payload_size,
+ std::move(handles))) {
+ return false;
+ }
+ did_consume_message = true;
+ } else if (deferred) {
+ did_consume_message = true;
+ } else if (delegate_) {
+ delegate_->OnChannelMessage(payload, payload_size, std::move(handles));
+ did_consume_message = true;
+ }
+
+ read_buffer_->Discard(legacy_header->num_bytes);
+ }
+
+ *next_read_size_hint = did_consume_message ? 0 : kReadBufferSize;
+ return true;
+}
+
+void Channel::OnError(Error error) {
+ if (delegate_)
+ delegate_->OnChannelError(error);
+}
+
+bool Channel::OnControlMessage(Message::MessageType message_type,
+ const void* payload,
+ size_t payload_size,
+ std::vector<PlatformHandle> handles) {
+ return false;
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/channel.h b/mojo/core/channel.h
new file mode 100644
index 0000000000..17108e9f02
--- /dev/null
+++ b/mojo/core/channel.h
@@ -0,0 +1,370 @@
+// 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_CORE_CHANNEL_H_
+#define MOJO_CORE_CHANNEL_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/process/process_handle.h"
+#include "base/task_runner.h"
+#include "build/build_config.h"
+#include "mojo/core/connection_params.h"
+#include "mojo/core/platform_handle_in_transit.h"
+#include "mojo/core/scoped_process_handle.h"
+#include "mojo/public/cpp/platform/platform_handle.h"
+
+namespace mojo {
+namespace core {
+
+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<Channel> {
+ public:
+ struct Message;
+
+ using MessagePtr = std::unique_ptr<Message>;
+
+ // 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(https://crbug.com/695645): remove legacy support when Arc++ has
+ // updated to Mojo with normal versioned messages.
+ 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 PlatformHandleInTransits.
+ 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_FUCHSIA)
+ struct HandleInfoEntry {
+ // The FDIO type associated with one or more handles, or zero for handles
+ // that do not belong to FDIO.
+ uint8_t type;
+ // Zero for non-FDIO handles, otherwise the number of handles to consume
+ // to generate an FDIO file-descriptor wrapper.
+ uint8_t count;
+ };
+#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(size_t capacity, size_t payload_size, size_t max_handles);
+ Message(size_t capacity,
+ size_t max_handles,
+ size_t payload_size,
+ MessageType message_type);
+ ~Message();
+
+ // Constructs a Message from serialized message data, optionally coming from
+ // a known remote process.
+ static MessagePtr Deserialize(
+ const void* data,
+ size_t data_num_bytes,
+ base::ProcessHandle from_process = base::kNullProcessHandle);
+
+ const void* data() const { return data_; }
+ size_t data_num_bytes() const { return size_; }
+
+ // The current capacity of the message buffer, not counting internal header
+ // data.
+ size_t capacity() const;
+
+ // Extends the portion of the total message capacity which contains
+ // meaningful payload data. Storage capacity which falls outside of this
+ // range is not transmitted when the message is sent.
+ //
+ // If the message's current capacity is not large enough to accommodate the
+ // new payload size, it will be reallocated accordingly.
+ void ExtendPayload(size_t new_payload_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(std::vector<PlatformHandle> new_handles);
+ void SetHandles(std::vector<PlatformHandleInTransit> new_handles);
+ std::vector<PlatformHandleInTransit> 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.
+ std::vector<PlatformHandleInTransit> TakeHandlesForTransport();
+
+ void SetVersionForTest(uint16_t version_number);
+
+ private:
+ // The message data buffer.
+ char* data_ = nullptr;
+
+ // The capacity of the buffer at |data_|.
+ size_t capacity_ = 0;
+
+ // The size of the message. This is the portion of |data_| that should
+ // be transmitted if the message is written to a channel. Includes all
+ // headers and user payload.
+ size_t size_ = 0;
+
+ // Maximum number of handles which may be attached to this message.
+ size_t max_handles_ = 0;
+
+ std::vector<PlatformHandleInTransit> 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);
+ };
+
+ // Error types which may be reported by a Channel instance to its delegate.
+ enum class Error {
+ // The remote end of the channel has been closed, either explicitly or
+ // because the process which hosted it is gone.
+ kDisconnected,
+
+ // For connection-oriented channels (e.g. named pipes), an unexpected error
+ // occurred during channel connection.
+ kConnectionFailed,
+
+ // Some incoming data failed validation, implying either a buggy or
+ // compromised sender.
+ kReceivedMalformedData,
+ };
+
+ // 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,
+ std::vector<PlatformHandle> handles) = 0;
+
+ // Notify that an error has occured and the Channel will cease operation.
+ virtual void OnChannelError(Error error) = 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<Channel> Create(
+ Delegate* delegate,
+ ConnectionParams connection_params,
+ scoped_refptr<base::TaskRunner> 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();
+
+ // Sets the process handle of the remote endpoint to which this Channel is
+ // connected. If called at all, must be called only once, and before Start().
+ void set_remote_process(ScopedProcessHandle remote_process) {
+ DCHECK(!remote_process_.is_valid());
+ remote_process_ = std::move(remote_process);
+ }
+ const ScopedProcessHandle& remote_process() const { return remote_process_; }
+
+ // 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();
+
+ Delegate* delegate() const { return delegate_; }
+
+ // 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(Error error);
+
+ // 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.
+ //
+ // If the implementation sets |*deferred| to |true|, it assumes responsibility
+ // for dispatching the message eventually. It must copy |payload| to retain
+ // it for later transmission.
+ virtual bool GetReadPlatformHandles(const void* payload,
+ size_t payload_size,
+ size_t num_handles,
+ const void* extra_header,
+ size_t extra_header_size,
+ std::vector<PlatformHandle>* handles,
+ bool* deferred) = 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,
+ std::vector<PlatformHandle> handles);
+
+ private:
+ friend class base::RefCountedThreadSafe<Channel>;
+
+ class ReadBuffer;
+
+ Delegate* delegate_;
+ const std::unique_ptr<ReadBuffer> read_buffer_;
+
+ // Handle to the process on the other end of this Channel, iff known.
+ ScopedProcessHandle remote_process_;
+
+ DISALLOW_COPY_AND_ASSIGN(Channel);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_CHANNEL_H_
diff --git a/mojo/core/channel_fuchsia.cc b/mojo/core/channel_fuchsia.cc
new file mode 100644
index 0000000000..4386b200b8
--- /dev/null
+++ b/mojo/core/channel_fuchsia.cc
@@ -0,0 +1,466 @@
+// 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/core/channel.h"
+
+#include <lib/fdio/limits.h>
+#include <lib/fdio/util.h>
+#include <lib/zx/channel.h>
+#include <lib/zx/handle.h>
+#include <zircon/processargs.h>
+#include <zircon/status.h>
+#include <zircon/syscalls.h>
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/containers/circular_deque.h"
+#include "base/files/scoped_file.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/message_loop/message_pump_for_io.h"
+#include "base/stl_util.h"
+#include "base/synchronization/lock.h"
+#include "base/task_runner.h"
+#include "mojo/core/platform_handle_in_transit.h"
+
+namespace mojo {
+namespace core {
+
+namespace {
+
+const size_t kMaxBatchReadCapacity = 256 * 1024;
+
+bool UnwrapPlatformHandle(PlatformHandleInTransit handle,
+ Channel::Message::HandleInfoEntry* info_out,
+ std::vector<PlatformHandleInTransit>* handles_out) {
+ DCHECK(handle.handle().is_valid());
+
+ if (!handle.handle().is_valid_fd()) {
+ *info_out = {0u, 0u};
+ handles_out->emplace_back(std::move(handle));
+ return true;
+ }
+
+ // Each FDIO file descriptor is implemented using one or more native resources
+ // and can be un-wrapped into a set of |handle| and |info| pairs, with |info|
+ // consisting of an FDIO-defined type & arguments (see zircon/processargs.h).
+ //
+ // We try to transfer the FD, but if that fails (for example if the file has
+ // already been dup()d into another FD) we may need to clone.
+ zx_handle_t handles[FDIO_MAX_HANDLES] = {};
+ uint32_t info[FDIO_MAX_HANDLES] = {};
+ zx_status_t result =
+ fdio_transfer_fd(handle.handle().GetFD().get(), 0, handles, info);
+ if (result > 0) {
+ // On success, the fd in |handle| has been transferred and is no longer
+ // valid. Release from the PlatformHandle to avoid close()ing an invalid
+ // an invalid handle.
+ handle.CompleteTransit();
+ } else if (result == ZX_ERR_UNAVAILABLE) {
+ // No luck, try cloning instead.
+ result = fdio_clone_fd(handle.handle().GetFD().get(), 0, handles, info);
+ }
+
+ if (result <= 0) {
+ ZX_DLOG(ERROR, result) << "fdio_transfer_fd("
+ << handle.handle().GetFD().get() << ")";
+ return false;
+ }
+ DCHECK_LE(result, FDIO_MAX_HANDLES);
+
+ // We assume here that only the |PA_HND_TYPE| of the |info| really matters,
+ // and that that is the same for all the underlying handles.
+ *info_out = {PA_HND_TYPE(info[0]), result};
+ for (int i = 0; i < result; ++i) {
+ DCHECK_EQ(PA_HND_TYPE(info[0]), PA_HND_TYPE(info[i]));
+ DCHECK_EQ(0u, PA_HND_SUBTYPE(info[i]));
+ handles_out->emplace_back(
+ PlatformHandleInTransit(PlatformHandle(zx::handle(handles[i]))));
+ }
+
+ return true;
+}
+
+PlatformHandle WrapPlatformHandles(Channel::Message::HandleInfoEntry info,
+ base::circular_deque<zx::handle>* handles) {
+ PlatformHandle out_handle;
+ if (!info.type) {
+ out_handle = PlatformHandle(std::move(handles->front()));
+ handles->pop_front();
+ } else {
+ if (info.count > FDIO_MAX_HANDLES)
+ return PlatformHandle();
+
+ // Fetch the required number of handles from |handles| and set up type info.
+ zx_handle_t fd_handles[FDIO_MAX_HANDLES] = {};
+ uint32_t fd_infos[FDIO_MAX_HANDLES] = {};
+ for (int i = 0; i < info.count; ++i) {
+ fd_handles[i] = (*handles)[i].get();
+ fd_infos[i] = PA_HND(info.type, 0);
+ }
+
+ // Try to wrap the handles into an FDIO file descriptor.
+ base::ScopedFD out_fd;
+ zx_status_t result =
+ fdio_create_fd(fd_handles, fd_infos, info.count, out_fd.receive());
+ if (result != ZX_OK) {
+ ZX_DLOG(ERROR, result) << "fdio_create_fd";
+ return PlatformHandle();
+ }
+
+ // The handles are owned by FDIO now, so |release()| them before removing
+ // the entries from |handles|.
+ for (int i = 0; i < info.count; ++i) {
+ ignore_result(handles->front().release());
+ handles->pop_front();
+ }
+
+ out_handle = PlatformHandle(std::move(out_fd));
+ }
+ return out_handle;
+}
+
+// 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<const char*>(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;
+ }
+
+ std::vector<PlatformHandleInTransit> TakeHandles() {
+ if (handles_.empty())
+ return std::vector<PlatformHandleInTransit>();
+
+ // We can only pass Fuchsia handles via IPC, so unwrap any FDIO file-
+ // descriptors in |handles_| into the underlying handles, and serialize the
+ // metadata, if any, into the extra header.
+ auto* handles_info = reinterpret_cast<Channel::Message::HandleInfoEntry*>(
+ message_->mutable_extra_header());
+ memset(handles_info, 0, message_->extra_header_size());
+
+ std::vector<PlatformHandleInTransit> in_handles = std::move(handles_);
+ handles_.reserve(in_handles.size());
+ for (size_t i = 0; i < in_handles.size(); i++) {
+ if (!UnwrapPlatformHandle(std::move(in_handles[i]), &handles_info[i],
+ &handles_))
+ return std::vector<PlatformHandleInTransit>();
+ }
+ return std::move(handles_);
+ }
+
+ private:
+ Channel::MessagePtr message_;
+ size_t offset_;
+ std::vector<PlatformHandleInTransit> handles_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageView);
+};
+
+class ChannelFuchsia : public Channel,
+ public base::MessageLoopCurrent::DestructionObserver,
+ public base::MessagePumpForIO::ZxHandleWatcher {
+ public:
+ ChannelFuchsia(Delegate* delegate,
+ ConnectionParams connection_params,
+ scoped_refptr<base::TaskRunner> io_task_runner)
+ : Channel(delegate),
+ self_(this),
+ handle_(
+ connection_params.TakeEndpoint().TakePlatformHandle().TakeHandle()),
+ io_task_runner_(io_task_runner) {
+ CHECK(handle_.is_valid());
+ }
+
+ void Start() override {
+ if (io_task_runner_->RunsTasksInCurrentSequence()) {
+ StartOnIOThread();
+ } else {
+ io_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&ChannelFuchsia::StartOnIOThread, this));
+ }
+ }
+
+ void ShutDownImpl() override {
+ // Always shut down asynchronously when called through the public interface.
+ io_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&ChannelFuchsia::ShutDownOnIOThread, this));
+ }
+
+ void Write(MessagePtr message) override {
+ bool write_error = false;
+ {
+ base::AutoLock lock(write_lock_);
+ if (reject_writes_)
+ return;
+ if (!WriteNoLock(MessageView(std::move(message), 0)))
+ reject_writes_ = write_error = true;
+ }
+ if (write_error) {
+ // Do not synchronously invoke OnWriteError(). Write() may have been
+ // called by the delegate and we don't want to re-enter it.
+ io_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&ChannelFuchsia::OnWriteError, this,
+ Error::kDisconnected));
+ }
+ }
+
+ void LeakHandle() override {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ leak_handle_ = true;
+ }
+
+ bool GetReadPlatformHandles(const void* payload,
+ size_t payload_size,
+ size_t num_handles,
+ const void* extra_header,
+ size_t extra_header_size,
+ std::vector<PlatformHandle>* handles,
+ bool* deferred) override {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ if (num_handles > std::numeric_limits<uint16_t>::max())
+ return false;
+
+ // Locate the handle info and verify there is enough of it.
+ if (!extra_header)
+ return false;
+ const auto* handles_info =
+ reinterpret_cast<const Channel::Message::HandleInfoEntry*>(
+ extra_header);
+ size_t handles_info_size = sizeof(handles_info[0]) * num_handles;
+ if (handles_info_size > extra_header_size)
+ return false;
+
+ // Some caller-supplied handles may be FDIO file-descriptors, which were
+ // un-wrapped to more than one native platform resource handle for transfer.
+ // We may therefore need to expect more than |num_handles| handles to have
+ // been accumulated in |incoming_handles_|, based on the handle info.
+ size_t num_raw_handles = 0u;
+ for (size_t i = 0; i < num_handles; ++i)
+ num_raw_handles += handles_info[i].type ? handles_info[i].count : 1;
+
+ // If there are too few handles then we're not ready yet, so return true
+ // indicating things are OK, but leave |handles| empty.
+ if (incoming_handles_.size() < num_raw_handles)
+ return true;
+
+ handles->reserve(num_handles);
+ for (size_t i = 0; i < num_handles; ++i) {
+ handles->emplace_back(
+ WrapPlatformHandles(handles_info[i], &incoming_handles_));
+ }
+ return true;
+ }
+
+ private:
+ ~ChannelFuchsia() override { DCHECK(!read_watch_); }
+
+ void StartOnIOThread() {
+ DCHECK(!read_watch_);
+
+ base::MessageLoopCurrent::Get()->AddDestructionObserver(this);
+
+ read_watch_.reset(
+ new base::MessagePumpForIO::ZxHandleWatchController(FROM_HERE));
+ base::MessageLoopCurrentForIO::Get()->WatchZxHandle(
+ handle_.get(), true /* persistent */,
+ ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, read_watch_.get(), this);
+ }
+
+ void ShutDownOnIOThread() {
+ base::MessageLoopCurrent::Get()->RemoveDestructionObserver(this);
+
+ read_watch_.reset();
+ if (leak_handle_)
+ ignore_result(handle_.release());
+ handle_.reset();
+
+ // May destroy the |this| if it was the last reference.
+ self_ = nullptr;
+ }
+
+ // base::MessageLoopCurrent::DestructionObserver:
+ void WillDestroyCurrentMessageLoop() override {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ if (self_)
+ ShutDownOnIOThread();
+ }
+
+ // base::MessagePumpForIO::ZxHandleWatcher:
+ void OnZxHandleSignalled(zx_handle_t handle, zx_signals_t signals) override {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ CHECK_EQ(handle, handle_.get());
+ DCHECK((ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED) & signals);
+
+ // We always try to read message(s), even if ZX_CHANNEL_PEER_CLOSED, since
+ // the peer may have closed while messages were still unread, in the pipe.
+
+ bool validation_error = false;
+ bool read_error = false;
+ size_t next_read_size = 0;
+ size_t buffer_capacity = 0;
+ size_t total_bytes_read = 0;
+ do {
+ buffer_capacity = next_read_size;
+ char* buffer = GetReadBuffer(&buffer_capacity);
+ DCHECK_GT(buffer_capacity, 0u);
+
+ uint32_t bytes_read = 0;
+ uint32_t handles_read = 0;
+ zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES] = {};
+
+ zx_status_t read_result =
+ handle_.read(0, buffer, buffer_capacity, &bytes_read, handles,
+ base::size(handles), &handles_read);
+ if (read_result == ZX_OK) {
+ for (size_t i = 0; i < handles_read; ++i) {
+ incoming_handles_.emplace_back(handles[i]);
+ }
+ total_bytes_read += bytes_read;
+ if (!OnReadComplete(bytes_read, &next_read_size)) {
+ read_error = true;
+ validation_error = true;
+ break;
+ }
+ } else if (read_result == ZX_ERR_BUFFER_TOO_SMALL) {
+ DCHECK_LE(handles_read, base::size(handles));
+ next_read_size = bytes_read;
+ } else if (read_result == ZX_ERR_SHOULD_WAIT) {
+ break;
+ } else {
+ ZX_DLOG_IF(ERROR, read_result != ZX_ERR_PEER_CLOSED, read_result)
+ << "zx_channel_read";
+ read_error = true;
+ break;
+ }
+ } while (total_bytes_read < kMaxBatchReadCapacity && next_read_size > 0);
+ if (read_error) {
+ // Stop receiving read notifications.
+ read_watch_.reset();
+ if (validation_error)
+ OnError(Error::kReceivedMalformedData);
+ else
+ OnError(Error::kDisconnected);
+ }
+ }
+
+ // 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) {
+ uint32_t write_bytes = 0;
+ do {
+ message_view.advance_data_offset(write_bytes);
+
+ std::vector<PlatformHandleInTransit> outgoing_handles =
+ message_view.TakeHandles();
+ zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES] = {};
+ size_t handles_count = outgoing_handles.size();
+
+ DCHECK_LE(handles_count, base::size(handles));
+ for (size_t i = 0; i < handles_count; ++i) {
+ DCHECK(outgoing_handles[i].handle().is_valid());
+ handles[i] = outgoing_handles[i].handle().GetHandle().get();
+ }
+
+ write_bytes = std::min(message_view.data_num_bytes(),
+ static_cast<size_t>(ZX_CHANNEL_MAX_MSG_BYTES));
+ zx_status_t result = handle_.write(0, message_view.data(), write_bytes,
+ handles, handles_count);
+ // zx_channel_write() consumes |handles| whether or not it succeeds, so
+ // release() our copies now, to avoid them being double-closed.
+ for (auto& outgoing_handle : outgoing_handles)
+ outgoing_handle.CompleteTransit();
+
+ if (result != ZX_OK) {
+ // TODO(fuchsia): Handle ZX_ERR_SHOULD_WAIT flow-control errors, once
+ // the platform starts generating them. See https://crbug.com/754084.
+ ZX_DLOG_IF(ERROR, result != ZX_ERR_PEER_CLOSED, result)
+ << "WriteNoLock(zx_channel_write)";
+ return false;
+ }
+
+ } while (write_bytes < message_view.data_num_bytes());
+
+ return true;
+ }
+
+ void OnWriteError(Error error) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(reject_writes_);
+
+ if (error == Error::kDisconnected) {
+ // If we can't write because the pipe is disconnected then continue
+ // reading to fetch any in-flight messages, relying on end-of-stream to
+ // signal the actual disconnection.
+ if (read_watch_) {
+ // TODO: When we add flow-control for writes, we also need to reset the
+ // write-watcher here.
+ return;
+ }
+ }
+
+ OnError(error);
+ }
+
+ // Keeps the Channel alive at least until explicit shutdown on the IO thread.
+ scoped_refptr<Channel> self_;
+
+ zx::channel handle_;
+ scoped_refptr<base::TaskRunner> io_task_runner_;
+
+ // These members are only used on the IO thread.
+ std::unique_ptr<base::MessagePumpForIO::ZxHandleWatchController> read_watch_;
+ base::circular_deque<zx::handle> incoming_handles_;
+ bool leak_handle_ = false;
+
+ base::Lock write_lock_;
+ bool reject_writes_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(ChannelFuchsia);
+};
+
+} // namespace
+
+// static
+scoped_refptr<Channel> Channel::Create(
+ Delegate* delegate,
+ ConnectionParams connection_params,
+ scoped_refptr<base::TaskRunner> io_task_runner) {
+ return new ChannelFuchsia(delegate, std::move(connection_params),
+ std::move(io_task_runner));
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/channel_posix.cc b/mojo/core/channel_posix.cc
new file mode 100644
index 0000000000..dc7f3a22aa
--- /dev/null
+++ b/mojo/core/channel_posix.cc
@@ -0,0 +1,768 @@
+// 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/core/channel.h"
+
+#include <errno.h>
+#include <sys/socket.h>
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+
+#include "base/bind.h"
+#include "base/containers/queue.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/message_loop/message_pump_for_io.h"
+#include "base/synchronization/lock.h"
+#include "base/task_runner.h"
+#include "build/build_config.h"
+#include "mojo/core/core.h"
+#include "mojo/public/cpp/platform/socket_utils_posix.h"
+
+#if !defined(OS_NACL)
+#include <sys/uio.h>
+#endif
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include "mojo/core/mach_port_relay.h"
+#endif
+
+namespace mojo {
+namespace core {
+
+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<const char*>(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;
+ }
+
+ std::vector<PlatformHandleInTransit> TakeHandles() {
+ return std::move(handles_);
+ }
+ Channel::MessagePtr TakeMessage() { return std::move(message_); }
+
+ void SetHandles(std::vector<PlatformHandleInTransit> handles) {
+ handles_ = std::move(handles);
+ }
+
+ private:
+ Channel::MessagePtr message_;
+ size_t offset_;
+ std::vector<PlatformHandleInTransit> handles_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageView);
+};
+
+class ChannelPosix : public Channel,
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ public MachPortRelay::Observer,
+#endif
+ public base::MessageLoopCurrent::DestructionObserver,
+ public base::MessagePumpForIO::FdWatcher {
+ public:
+ ChannelPosix(Delegate* delegate,
+ ConnectionParams connection_params,
+ scoped_refptr<base::TaskRunner> io_task_runner)
+ : Channel(delegate), self_(this), io_task_runner_(io_task_runner) {
+ if (connection_params.server_endpoint().is_valid())
+ server_ = connection_params.TakeServerEndpoint();
+ else
+ socket_ = connection_params.TakeEndpoint().TakePlatformHandle().TakeFD();
+
+ CHECK(server_.is_valid() || socket_.is_valid());
+ }
+
+ void Start() override {
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ auto* relay = Core::Get()->GetMachPortRelay();
+ if (relay) {
+ // We should only have a relay if we know the remote process handle,
+ // because that means we're in the broker process.
+ relay->AddObserver(this);
+ }
+#endif
+
+ if (io_task_runner_->RunsTasksInCurrentSequence()) {
+ StartOnIOThread();
+ } else {
+ io_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&ChannelPosix::StartOnIOThread, this));
+ }
+ }
+
+ void ShutDownImpl() override {
+ // Always shut down asynchronously when called through the public interface.
+ io_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&ChannelPosix::ShutDownOnIOThread, this));
+ }
+
+ void Write(MessagePtr message) override {
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // If this message has Mach ports and we have a MachPortRelay, use the relay
+ // to rewrite the ports as receive rights from which the send right can be
+ // read. See |MachPortRelay::SendPortsToProcess()|.
+ //
+ // Note that if we don't have a relay, the receiving process must, and they
+ // must also have the ability to extract a send right from the ports that
+ // are already attached.
+ MachPortRelay* relay = Core::Get()->GetMachPortRelay();
+ if (relay && remote_process().is_valid() && message->has_mach_ports()) {
+ if (relay->port_provider()->TaskForPid(remote_process().get()) ==
+ MACH_PORT_NULL) {
+ // We also need to have a task port for the remote process before we can
+ // send it any other ports. If we don't have one yet, queue the message
+ // until OnProcessReady() is invoked.
+ base::AutoLock lock(task_port_wait_lock_);
+ pending_outgoing_with_mach_ports_.emplace_back(std::move(message));
+ return;
+ }
+
+ relay->SendPortsToProcess(message.get(), remote_process().get());
+ }
+#endif
+
+ 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) {
+ // Invoke OnWriteError() asynchronously on the IO thread, in case Write()
+ // was called by the delegate, in which case we should not re-enter it.
+ io_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&ChannelPosix::OnWriteError, this,
+ Error::kDisconnected));
+ }
+ }
+
+ void LeakHandle() override {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ leak_handle_ = true;
+ }
+
+ bool GetReadPlatformHandles(const void* payload,
+ size_t payload_size,
+ size_t num_handles,
+ const void* extra_header,
+ size_t extra_header_size,
+ std::vector<PlatformHandle>* handles,
+ bool* deferred) override {
+ if (num_handles > std::numeric_limits<uint16_t>::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;
+ if (extra_header_size <
+ sizeof(MachPortsExtraHeader) + num_handles * sizeof(MachPortsEntry)) {
+ return false;
+ }
+ const MachPortsExtraHeader* mach_ports_header =
+ reinterpret_cast<const MachPortsExtraHeader*>(extra_header);
+ size_t num_mach_ports = mach_ports_header->num_ports;
+ if (num_mach_ports > num_handles)
+ return false;
+ if (incoming_fds_.size() + num_mach_ports < num_handles)
+ return true;
+
+ std::vector<PlatformHandleInTransit> handles_in_transit(num_handles);
+ const MachPortsEntry* mach_ports = mach_ports_header->entries;
+
+ // If we know the remote process handle, we assume all incoming Mach ports
+ // are send right references owned by the remote process. Otherwise they're
+ // receive ports we can use to read a send right.
+ const bool extract_send_rights = remote_process().is_valid();
+ 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) {
+ mach_port_t port_name =
+ static_cast<mach_port_t>(mach_ports[mach_port_index].mach_port);
+ if (extract_send_rights) {
+ handles_in_transit[i] =
+ PlatformHandleInTransit::CreateForMachPortName(port_name);
+ } else {
+ handles_in_transit[i] = PlatformHandleInTransit(
+ PlatformHandle(MachPortRelay::ReceiveSendRight(
+ base::mac::ScopedMachReceiveRight(port_name))));
+ }
+ mach_port_index++;
+ } else {
+ if (incoming_fds_.empty())
+ return false;
+ handles_in_transit[i] = PlatformHandleInTransit(
+ PlatformHandle(std::move(incoming_fds_.front())));
+ incoming_fds_.pop_front();
+ }
+ }
+ if (extract_send_rights && num_mach_ports) {
+ MachPortRelay* relay = Core::Get()->GetMachPortRelay();
+ DCHECK(relay);
+ // Extracting send rights requires that we have a task port for the
+ // remote process, which we may not yet have.
+ if (relay->port_provider()->TaskForPid(remote_process().get()) !=
+ MACH_PORT_NULL) {
+ // We do have a task port, so extract the send rights immediately.
+ for (auto& handle : handles_in_transit) {
+ if (handle.is_mach_port_name()) {
+ handle = PlatformHandleInTransit(PlatformHandle(relay->ExtractPort(
+ handle.mach_port_name(), remote_process().get())));
+ }
+ }
+ } else {
+ // No task port, we have to defer this message.
+ *deferred = true;
+ base::AutoLock lock(task_port_wait_lock_);
+ std::vector<uint8_t> data(payload_size);
+ memcpy(data.data(), payload, payload_size);
+ pending_incoming_with_mach_ports_.emplace_back(
+ std::move(data), std::move(handles_in_transit));
+ return true;
+ }
+ }
+
+ handles->resize(handles_in_transit.size());
+ for (size_t i = 0; i < handles->size(); ++i)
+ handles->at(i) = handles_in_transit[i].TakeHandle();
+#else
+ if (incoming_fds_.size() < num_handles)
+ return true;
+
+ handles->resize(num_handles);
+ for (size_t i = 0; i < num_handles; ++i) {
+ handles->at(i) = PlatformHandle(std::move(incoming_fds_.front()));
+ incoming_fds_.pop_front();
+ }
+#endif
+
+ return true;
+ }
+
+ private:
+ ~ChannelPosix() override {
+ DCHECK(!read_watcher_);
+ DCHECK(!write_watcher_);
+ }
+
+ void StartOnIOThread() {
+ DCHECK(!read_watcher_);
+ DCHECK(!write_watcher_);
+ read_watcher_.reset(
+ new base::MessagePumpForIO::FdWatchController(FROM_HERE));
+ base::MessageLoopCurrent::Get()->AddDestructionObserver(this);
+ if (server_.is_valid()) {
+ base::MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
+ server_.platform_handle().GetFD().get(), false /* persistent */,
+ base::MessagePumpForIO::WATCH_READ, read_watcher_.get(), this);
+ } else {
+ write_watcher_.reset(
+ new base::MessagePumpForIO::FdWatchController(FROM_HERE));
+ base::MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
+ socket_.get(), true /* persistent */,
+ base::MessagePumpForIO::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_->RunsTasksInCurrentSequence()) {
+ pending_write_ = true;
+ base::MessageLoopCurrentForIO::Get()->WatchFileDescriptor(
+ socket_.get(), false /* persistent */,
+ base::MessagePumpForIO::WATCH_WRITE, write_watcher_.get(), this);
+ } else {
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&ChannelPosix::WaitForWriteOnIOThread, this));
+ }
+ }
+
+ void ShutDownOnIOThread() {
+ base::MessageLoopCurrent::Get()->RemoveDestructionObserver(this);
+
+ read_watcher_.reset();
+ write_watcher_.reset();
+ if (leak_handle_) {
+ ignore_result(socket_.release());
+ server_.TakePlatformHandle().release();
+ } else {
+ socket_.reset();
+ ignore_result(server_.TakePlatformHandle());
+ }
+#if defined(OS_MACOSX)
+ fds_to_close_.clear();
+#endif
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ auto* relay = Core::Get()->GetMachPortRelay();
+ if (relay)
+ relay->RemoveObserver(this);
+#endif
+
+ // May destroy the |this| if it was the last reference.
+ self_ = nullptr;
+ }
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // MachPortRelay::Observer:
+ void OnProcessReady(base::ProcessHandle process) override {
+ if (process != remote_process().get())
+ return;
+
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &ChannelPosix::FlushPendingMessagesWithMachPortsOnIOThread, this));
+ }
+
+ void FlushPendingMessagesWithMachPortsOnIOThread() {
+ // We have a task port for the remote process. Now we can send or accept
+ // any pending messages with Mach ports.
+ std::vector<RawIncomingMessage> incoming;
+ std::vector<MessagePtr> outgoing;
+ {
+ base::AutoLock lock(task_port_wait_lock_);
+ if (reject_incoming_messages_with_mach_ports_)
+ return;
+ std::swap(pending_incoming_with_mach_ports_, incoming);
+ std::swap(pending_outgoing_with_mach_ports_, outgoing);
+ }
+
+ DCHECK(remote_process().is_valid());
+ base::ProcessHandle process = remote_process().get();
+ MachPortRelay* relay = Core::Get()->GetMachPortRelay();
+ DCHECK(relay);
+ for (auto& message : incoming) {
+ Channel::Delegate* d = delegate();
+ if (!d)
+ break;
+ std::vector<PlatformHandle> handles(message.handles.size());
+ for (size_t i = 0; i < message.handles.size(); ++i) {
+ if (message.handles[i].is_mach_port_name()) {
+ handles[i] = PlatformHandle(
+ relay->ExtractPort(message.handles[i].mach_port_name(), process));
+ } else {
+ DCHECK(!message.handles[i].owning_process().is_valid());
+ handles[i] = message.handles[i].TakeHandle();
+ }
+ }
+ d->OnChannelMessage(message.data.data(), message.data.size(),
+ std::move(handles));
+ }
+
+ for (auto& message : outgoing) {
+ relay->SendPortsToProcess(message.get(), process);
+ Write(std::move(message));
+ }
+ }
+#endif
+
+ // base::MessageLoopCurrent::DestructionObserver:
+ void WillDestroyCurrentMessageLoop() override {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ if (self_)
+ ShutDownOnIOThread();
+ }
+
+ // base::MessagePumpForIO::FdWatcher:
+ void OnFileCanReadWithoutBlocking(int fd) override {
+ if (server_.is_valid()) {
+ CHECK_EQ(fd, server_.platform_handle().GetFD().get());
+#if !defined(OS_NACL)
+ read_watcher_.reset();
+ base::MessageLoopCurrent::Get()->RemoveDestructionObserver(this);
+
+ AcceptSocketConnection(server_.platform_handle().GetFD().get(), &socket_);
+ ignore_result(server_.TakePlatformHandle());
+ if (!socket_.is_valid()) {
+ OnError(Error::kConnectionFailed);
+ return;
+ }
+ StartOnIOThread();
+#else
+ NOTREACHED();
+#endif
+ return;
+ }
+ CHECK_EQ(fd, socket_.get());
+
+ bool validation_error = false;
+ 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);
+
+ std::vector<base::ScopedFD> incoming_fds;
+ ssize_t read_result =
+ SocketRecvmsg(socket_.get(), buffer, buffer_capacity, &incoming_fds);
+ for (auto& fd : incoming_fds)
+ incoming_fds_.emplace_back(std::move(fd));
+
+ if (read_result > 0) {
+ bytes_read = static_cast<size_t>(read_result);
+ total_bytes_read += bytes_read;
+ if (!OnReadComplete(bytes_read, &next_read_size)) {
+ read_error = true;
+ validation_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();
+ if (validation_error)
+ OnError(Error::kReceivedMalformedData);
+ else
+ OnError(Error::kDisconnected);
+ }
+ }
+
+ 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)
+ OnWriteError(Error::kDisconnected);
+ }
+
+ // 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 (server_.is_valid()) {
+ 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;
+ std::vector<PlatformHandleInTransit> handles = message_view.TakeHandles();
+ if (!handles.empty()) {
+ iovec iov = {const_cast<void*>(message_view.data()),
+ message_view.data_num_bytes()};
+ std::vector<base::ScopedFD> fds(handles.size());
+ for (size_t i = 0; i < handles.size(); ++i)
+ fds[i] = handles[i].TakeHandle().TakeFD();
+ // TODO: Handle lots of handles.
+ result = SendmsgWithHandles(socket_.get(), &iov, 1, fds);
+ 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
+ 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);
+ {
+ base::AutoLock l(fds_to_close_lock_);
+ for (auto& fd : fds)
+ fds_to_close_.emplace_back(std::move(fd));
+ }
+#endif // defined(OS_MACOSX)
+ } else {
+ // Message transmission failed, so pull the FDs back into |handles|
+ // so they can be held by the Message again.
+ for (size_t i = 0; i < fds.size(); ++i) {
+ handles[i] =
+ PlatformHandleInTransit(PlatformHandle(std::move(fds[i])));
+ }
+ }
+ } else {
+ result = SocketWrite(socket_.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<size_t>(result);
+ } while (bytes_written < message_view.data_num_bytes());
+
+ return FlushOutgoingMessagesNoLock();
+ }
+
+ bool FlushOutgoingMessagesNoLock() {
+ base::circular_deque<MessageView> 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,
+ std::vector<PlatformHandle> 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<const int*>(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 |fds_to_close_|.
+ bool CloseHandles(const int* fds, size_t num_fds) {
+ base::AutoLock l(fds_to_close_lock_);
+ if (!num_fds)
+ return false;
+
+ auto start = std::find_if(
+ fds_to_close_.begin(), fds_to_close_.end(),
+ [&fds](const base::ScopedFD& fd) { return fd.get() == fds[0]; });
+ if (start == fds_to_close_.end())
+ return false;
+
+ auto it = start;
+ size_t i = 0;
+ // The FDs in the message should match a sequence of handles in
+ // |fds_to_close_|.
+ // TODO(wez): Consider making |fds_to_close_| a circular_deque<>
+ // for greater efficiency? Or assign a unique Id to each FD-containing
+ // message, and map that to a vector of FDs to close, to avoid the
+ // need for this traversal? Id could even be the first FD in the message.
+ for (; i < num_fds && it != fds_to_close_.end(); i++, ++it) {
+ if (it->get() != fds[i])
+ return false;
+ }
+ if (i != num_fds)
+ return false;
+
+ // Close the FDs by erase()ing their ScopedFDs.
+ fds_to_close_.erase(start, it);
+ return true;
+ }
+#endif // defined(OS_MACOSX)
+
+ void OnWriteError(Error error) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(reject_writes_);
+
+ if (error == Error::kDisconnected) {
+ // If we can't write because the pipe is disconnected then continue
+ // reading to fetch any in-flight messages, relying on end-of-stream to
+ // signal the actual disconnection.
+ if (read_watcher_) {
+ write_watcher_.reset();
+ return;
+ }
+ }
+
+ OnError(error);
+ }
+
+ // Keeps the Channel alive at least until explicit shutdown on the IO thread.
+ scoped_refptr<Channel> self_;
+
+ // We may be initialized with a server socket, in which case this will be
+ // valid until it accepts an incoming connection.
+ PlatformChannelServerEndpoint server_;
+
+ // The socket over which to communicate. May be passed in at construction time
+ // or accepted over |server_|.
+ base::ScopedFD socket_;
+
+ scoped_refptr<base::TaskRunner> io_task_runner_;
+
+ // These watchers must only be accessed on the IO thread.
+ std::unique_ptr<base::MessagePumpForIO::FdWatchController> read_watcher_;
+ std::unique_ptr<base::MessagePumpForIO::FdWatchController> write_watcher_;
+
+ base::circular_deque<base::ScopedFD> incoming_fds_;
+
+ // Protects |pending_write_| and |outgoing_messages_|.
+ base::Lock write_lock_;
+ bool pending_write_ = false;
+ bool reject_writes_ = false;
+ base::circular_deque<MessageView> outgoing_messages_;
+
+ bool leak_handle_ = false;
+
+#if defined(OS_MACOSX)
+ base::Lock fds_to_close_lock_;
+ std::vector<base::ScopedFD> fds_to_close_;
+#if !defined(OS_IOS)
+ // Guards access to the send/receive queues below. These are messages that
+ // can't be fully accepted from or dispatched to the Channel user yet because
+ // we're still waiting on a task port for the remote process.
+ struct RawIncomingMessage {
+ RawIncomingMessage(std::vector<uint8_t> data,
+ std::vector<PlatformHandleInTransit> handles)
+ : data(std::move(data)), handles(std::move(handles)) {}
+ RawIncomingMessage(RawIncomingMessage&&) = default;
+ ~RawIncomingMessage() = default;
+
+ std::vector<uint8_t> data;
+ std::vector<PlatformHandleInTransit> handles;
+ };
+
+ base::Lock task_port_wait_lock_;
+ bool reject_incoming_messages_with_mach_ports_ = false;
+ std::vector<MessagePtr> pending_outgoing_with_mach_ports_;
+ std::vector<RawIncomingMessage> pending_incoming_with_mach_ports_;
+#endif // !defined(OS_IOS)
+#endif // defined(OS_MACOSX)
+
+ DISALLOW_COPY_AND_ASSIGN(ChannelPosix);
+};
+
+} // namespace
+
+// static
+scoped_refptr<Channel> Channel::Create(
+ Delegate* delegate,
+ ConnectionParams connection_params,
+ scoped_refptr<base::TaskRunner> io_task_runner) {
+ return new ChannelPosix(delegate, std::move(connection_params),
+ io_task_runner);
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/channel_unittest.cc b/mojo/core/channel_unittest.cc
new file mode 100644
index 0000000000..e53cc0ffbc
--- /dev/null
+++ b/mojo/core/channel_unittest.cc
@@ -0,0 +1,278 @@
+// 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/core/channel.h"
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/thread.h"
+#include "mojo/core/platform_handle_utils.h"
+#include "mojo/public/cpp/platform/platform_channel.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace core {
+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_METHOD7(GetReadPlatformHandles,
+ bool(const void* payload,
+ size_t payload_size,
+ size_t num_handles,
+ const void* extra_header,
+ size_t extra_header_size,
+ std::vector<PlatformHandle>* handles,
+ bool* deferred));
+ MOCK_METHOD0(Start, void());
+ MOCK_METHOD0(ShutDownImpl, void());
+ MOCK_METHOD0(LeakHandle, void());
+
+ void Write(MessagePtr message) override {}
+
+ 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,
+ std::vector<PlatformHandle> 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(Channel::Error error) override {}
+
+ private:
+ size_t payload_size_ = 0;
+ std::unique_ptr<char[]> payload_;
+};
+
+Channel::MessagePtr CreateDefaultMessage(bool legacy_message) {
+ const size_t payload_size = 100;
+ Channel::MessagePtr message = std::make_unique<Channel::Message>(
+ payload_size, 0,
+ legacy_message ? Channel::Message::MessageType::NORMAL_LEGACY
+ : Channel::Message::MessageType::NORMAL);
+ char* payload = static_cast<char*>(message->mutable_payload());
+ for (size_t i = 0; i < payload_size; i++) {
+ payload[i] = static_cast<char>(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<const unsigned char*>(data1);
+ const unsigned char* data2_char = static_cast<const unsigned char*>(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<TestChannel> 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<TestChannel> 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());
+}
+
+class ChannelTestShutdownAndWriteDelegate : public Channel::Delegate {
+ public:
+ ChannelTestShutdownAndWriteDelegate(
+ PlatformChannelEndpoint endpoint,
+ scoped_refptr<base::TaskRunner> task_runner,
+ scoped_refptr<Channel> client_channel,
+ std::unique_ptr<base::Thread> client_thread,
+ base::RepeatingClosure quit_closure)
+ : quit_closure_(std::move(quit_closure)),
+ client_channel_(std::move(client_channel)),
+ client_thread_(std::move(client_thread)) {
+ channel_ = Channel::Create(this, ConnectionParams(std::move(endpoint)),
+ std::move(task_runner));
+ channel_->Start();
+ }
+ ~ChannelTestShutdownAndWriteDelegate() override { channel_->ShutDown(); }
+
+ // Channel::Delegate implementation
+ void OnChannelMessage(const void* payload,
+ size_t payload_size,
+ std::vector<PlatformHandle> handles) override {
+ ++message_count_;
+
+ // If |client_channel_| exists then close it and its thread.
+ if (client_channel_) {
+ // Write a fresh message, making our channel readable again.
+ Channel::MessagePtr message = CreateDefaultMessage(false);
+ client_thread_->task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&Channel::Write, client_channel_,
+ base::Passed(&message)));
+
+ // Close the channel and wait for it to shutdown.
+ client_channel_->ShutDown();
+ client_channel_ = nullptr;
+
+ client_thread_->Stop();
+ client_thread_ = nullptr;
+ }
+
+ // Write a message to the channel, to verify whether this triggers an
+ // OnChannelError callback before all messages were read.
+ Channel::MessagePtr message = CreateDefaultMessage(false);
+ channel_->Write(std::move(message));
+ }
+
+ void OnChannelError(Channel::Error error) override {
+ EXPECT_EQ(2, message_count_);
+ quit_closure_.Run();
+ }
+
+ base::RepeatingClosure quit_closure_;
+ int message_count_ = 0;
+ scoped_refptr<Channel> channel_;
+
+ scoped_refptr<Channel> client_channel_;
+ std::unique_ptr<base::Thread> client_thread_;
+};
+
+TEST(ChannelTest, PeerShutdownDuringRead) {
+ base::MessageLoop message_loop(base::MessageLoop::TYPE_IO);
+ PlatformChannel channel;
+
+ // Create a "client" Channel with one end of the pipe, and Start() it.
+ std::unique_ptr<base::Thread> client_thread =
+ std::make_unique<base::Thread>("clientio_thread");
+ client_thread->StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
+
+ scoped_refptr<Channel> client_channel =
+ Channel::Create(nullptr, ConnectionParams(channel.TakeRemoteEndpoint()),
+ client_thread->task_runner());
+ client_channel->Start();
+
+ // On the "client" IO thread, create and write a message.
+ Channel::MessagePtr message = CreateDefaultMessage(false);
+ client_thread->task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&Channel::Write, client_channel, base::Passed(&message)));
+
+ // Create a "server" Channel with the other end of the pipe, and process the
+ // messages from it. The |server_delegate| will ShutDown the client end of
+ // the pipe after the first message, and quit the RunLoop when OnChannelError
+ // is received.
+ base::RunLoop run_loop;
+ ChannelTestShutdownAndWriteDelegate server_delegate(
+ channel.TakeLocalEndpoint(), message_loop.task_runner(),
+ std::move(client_channel), std::move(client_thread),
+ run_loop.QuitClosure());
+
+ run_loop.Run();
+}
+
+} // namespace
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/channel_win.cc b/mojo/core/channel_win.cc
new file mode 100644
index 0000000000..30a14867be
--- /dev/null
+++ b/mojo/core/channel_win.cc
@@ -0,0 +1,377 @@
+// 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/core/channel.h"
+
+#include <stdint.h>
+#include <windows.h>
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+
+#include "base/bind.h"
+#include "base/containers/queue.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/message_loop/message_pump_for_io.h"
+#include "base/process/process_handle.h"
+#include "base/synchronization/lock.h"
+#include "base/task_runner.h"
+#include "base/win/scoped_handle.h"
+#include "base/win/win_util.h"
+
+namespace mojo {
+namespace core {
+
+namespace {
+
+class ChannelWin : public Channel,
+ public base::MessageLoopCurrent::DestructionObserver,
+ public base::MessagePumpForIO::IOHandler {
+ public:
+ ChannelWin(Delegate* delegate,
+ ConnectionParams connection_params,
+ scoped_refptr<base::TaskRunner> io_task_runner)
+ : Channel(delegate), self_(this), io_task_runner_(io_task_runner) {
+ if (connection_params.server_endpoint().is_valid()) {
+ handle_ = connection_params.TakeServerEndpoint()
+ .TakePlatformHandle()
+ .TakeHandle();
+ needs_connection_ = true;
+ } else {
+ handle_ =
+ connection_params.TakeEndpoint().TakePlatformHandle().TakeHandle();
+ }
+
+ CHECK(handle_.IsValid());
+ }
+
+ void Start() override {
+ io_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&ChannelWin::StartOnIOThread, this));
+ }
+
+ void ShutDownImpl() override {
+ // Always shut down asynchronously when called through the public interface.
+ io_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&ChannelWin::ShutDownOnIOThread, this));
+ }
+
+ void Write(MessagePtr message) override {
+ if (remote_process().is_valid()) {
+ // If we know the remote process handle, we transfer all outgoing handles
+ // to the process now rewriting them in the message.
+ std::vector<PlatformHandleInTransit> handles = message->TakeHandles();
+ for (auto& handle : handles) {
+ if (handle.handle().is_valid())
+ handle.TransferToProcess(remote_process().Clone());
+ }
+ message->SetHandles(std::move(handles));
+ }
+
+ 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));
+ if (write_now && !WriteNoLock(outgoing_messages_.front()))
+ reject_writes_ = write_error = true;
+ }
+ if (write_error) {
+ // Do not synchronously invoke OnWriteError(). Write() may have been
+ // called by the delegate and we don't want to re-enter it.
+ io_task_runner_->PostTask(FROM_HERE,
+ base::BindOnce(&ChannelWin::OnWriteError, this,
+ Error::kDisconnected));
+ }
+ }
+
+ void LeakHandle() override {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ leak_handle_ = true;
+ }
+
+ bool GetReadPlatformHandles(const void* payload,
+ size_t payload_size,
+ size_t num_handles,
+ const void* extra_header,
+ size_t extra_header_size,
+ std::vector<PlatformHandle>* handles,
+ bool* deferred) override {
+ DCHECK(extra_header);
+ if (num_handles > std::numeric_limits<uint16_t>::max())
+ return false;
+ using HandleEntry = Channel::Message::HandleEntry;
+ size_t handles_size = sizeof(HandleEntry) * num_handles;
+ if (handles_size > extra_header_size)
+ return false;
+ handles->reserve(num_handles);
+ const HandleEntry* extra_header_handles =
+ reinterpret_cast<const HandleEntry*>(extra_header);
+ for (size_t i = 0; i < num_handles; i++) {
+ HANDLE handle_value =
+ base::win::Uint32ToHandle(extra_header_handles[i].handle);
+ if (remote_process().is_valid()) {
+ // If we know the remote process's handle, we assume it doesn't know
+ // ours; that means any handle values still belong to that process, and
+ // we need to transfer them to this process.
+ handle_value = PlatformHandleInTransit::TakeIncomingRemoteHandle(
+ handle_value, remote_process().get())
+ .ReleaseHandle();
+ }
+ handles->emplace_back(base::win::ScopedHandle(std::move(handle_value)));
+ }
+ return true;
+ }
+
+ private:
+ // May run on any thread.
+ ~ChannelWin() override {}
+
+ void StartOnIOThread() {
+ base::MessageLoopCurrent::Get()->AddDestructionObserver(this);
+ base::MessageLoopCurrentForIO::Get()->RegisterIOHandler(handle_.Get(),
+ this);
+
+ if (needs_connection_) {
+ BOOL ok = ::ConnectNamedPipe(handle_.Get(), &connect_context_.overlapped);
+ if (ok) {
+ PLOG(ERROR) << "Unexpected success while waiting for pipe connection";
+ OnError(Error::kConnectionFailed);
+ return;
+ }
+
+ const DWORD err = GetLastError();
+ switch (err) {
+ case ERROR_PIPE_CONNECTED:
+ break;
+ case ERROR_IO_PENDING:
+ is_connect_pending_ = true;
+ AddRef();
+ return;
+ case ERROR_NO_DATA:
+ default:
+ OnError(Error::kConnectionFailed);
+ 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, via OnError(),
+ // as a result of a ReadFile() failure on the channel.
+ scoped_refptr<ChannelWin> keep_alive(this);
+ ReadMore(0);
+ }
+
+ void ShutDownOnIOThread() {
+ base::MessageLoopCurrent::Get()->RemoveDestructionObserver(this);
+
+ // TODO(https://crbug.com/583525): This function is expected to be called
+ // once, and |handle_| should be valid at this point.
+ CHECK(handle_.IsValid());
+ CancelIo(handle_.Get());
+ if (leak_handle_)
+ ignore_result(handle_.Take());
+ else
+ handle_.Close();
+
+ // Allow |this| to be destroyed as soon as no IO is pending.
+ self_ = nullptr;
+ }
+
+ // base::MessageLoopCurrent::DestructionObserver:
+ void WillDestroyCurrentMessageLoop() override {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ if (self_)
+ ShutDownOnIOThread();
+ }
+
+ // base::MessageLoop::IOHandler:
+ void OnIOCompleted(base::MessagePumpForIO::IOContext* context,
+ DWORD bytes_transfered,
+ DWORD error) override {
+ if (error != ERROR_SUCCESS) {
+ if (context == &write_context_) {
+ {
+ base::AutoLock lock(write_lock_);
+ reject_writes_ = true;
+ }
+ OnWriteError(Error::kDisconnected);
+ } else {
+ OnError(Error::kDisconnected);
+ }
+ } else if (context == &connect_context_) {
+ DCHECK(is_connect_pending_);
+ is_connect_pending_ = false;
+ ReadMore(0);
+
+ base::AutoLock lock(write_lock_);
+ if (delay_writes_) {
+ delay_writes_ = false;
+ WriteNextNoLock();
+ }
+ } else if (context == &read_context_) {
+ OnReadDone(static_cast<size_t>(bytes_transfered));
+ } else {
+ CHECK(context == &write_context_);
+ OnWriteDone(static_cast<size_t>(bytes_transfered));
+ }
+ Release();
+ }
+
+ void OnReadDone(size_t bytes_read) {
+ DCHECK(is_read_pending_);
+ is_read_pending_ = false;
+
+ if (bytes_read > 0) {
+ size_t next_read_size = 0;
+ if (OnReadComplete(bytes_read, &next_read_size)) {
+ ReadMore(next_read_size);
+ } else {
+ OnError(Error::kReceivedMalformedData);
+ }
+ } else if (bytes_read == 0) {
+ OnError(Error::kDisconnected);
+ }
+ }
+
+ void OnWriteDone(size_t bytes_written) {
+ if (bytes_written == 0)
+ return;
+
+ bool write_error = false;
+ {
+ base::AutoLock lock(write_lock_);
+
+ DCHECK(is_write_pending_);
+ is_write_pending_ = false;
+ DCHECK(!outgoing_messages_.empty());
+
+ Channel::MessagePtr message = std::move(outgoing_messages_.front());
+ outgoing_messages_.pop_front();
+
+ // Invalidate all the scoped handles so we don't attempt to close them.
+ std::vector<PlatformHandleInTransit> handles = message->TakeHandles();
+ for (auto& handle : handles)
+ handle.CompleteTransit();
+
+ // Overlapped WriteFile() to a pipe should always fully complete.
+ if (message->data_num_bytes() != bytes_written)
+ reject_writes_ = write_error = true;
+ else if (!WriteNextNoLock())
+ reject_writes_ = write_error = true;
+ }
+ if (write_error)
+ OnWriteError(Error::kDisconnected);
+ }
+
+ void ReadMore(size_t next_read_size_hint) {
+ DCHECK(!is_read_pending_);
+
+ size_t buffer_capacity = next_read_size_hint;
+ char* buffer = GetReadBuffer(&buffer_capacity);
+ DCHECK_GT(buffer_capacity, 0u);
+
+ BOOL ok =
+ ::ReadFile(handle_.Get(), buffer, static_cast<DWORD>(buffer_capacity),
+ NULL, &read_context_.overlapped);
+ if (ok || GetLastError() == ERROR_IO_PENDING) {
+ is_read_pending_ = true;
+ AddRef();
+ } else {
+ OnError(Error::kDisconnected);
+ }
+ }
+
+ // 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 Channel::MessagePtr& message) {
+ BOOL ok = WriteFile(handle_.Get(), message->data(),
+ static_cast<DWORD>(message->data_num_bytes()), NULL,
+ &write_context_.overlapped);
+ if (ok || GetLastError() == ERROR_IO_PENDING) {
+ is_write_pending_ = true;
+ AddRef();
+ return true;
+ }
+ return false;
+ }
+
+ bool WriteNextNoLock() {
+ if (outgoing_messages_.empty())
+ return true;
+ return WriteNoLock(outgoing_messages_.front());
+ }
+
+ void OnWriteError(Error error) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(reject_writes_);
+
+ if (error == Error::kDisconnected) {
+ // If we can't write because the pipe is disconnected then continue
+ // reading to fetch any in-flight messages, relying on end-of-stream to
+ // signal the actual disconnection.
+ if (is_read_pending_ || is_connect_pending_)
+ return;
+ }
+
+ OnError(error);
+ }
+
+ // Keeps the Channel alive at least until explicit shutdown on the IO thread.
+ scoped_refptr<Channel> self_;
+
+ // The pipe handle this Channel uses for communication.
+ base::win::ScopedHandle handle_;
+
+ // Indicates whether |handle_| must wait for a connection.
+ bool needs_connection_ = false;
+
+ const scoped_refptr<base::TaskRunner> io_task_runner_;
+
+ base::MessagePumpForIO::IOContext connect_context_;
+ base::MessagePumpForIO::IOContext read_context_;
+ bool is_connect_pending_ = false;
+ bool is_read_pending_ = false;
+
+ // Protects all fields potentially accessed on multiple threads via Write().
+ base::Lock write_lock_;
+ base::MessagePumpForIO::IOContext write_context_;
+ base::circular_deque<Channel::MessagePtr> outgoing_messages_;
+ bool delay_writes_ = true;
+ bool reject_writes_ = false;
+ bool is_write_pending_ = false;
+
+ bool leak_handle_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(ChannelWin);
+};
+
+} // namespace
+
+// static
+scoped_refptr<Channel> Channel::Create(
+ Delegate* delegate,
+ ConnectionParams connection_params,
+ scoped_refptr<base::TaskRunner> io_task_runner) {
+ return new ChannelWin(delegate, std::move(connection_params), io_task_runner);
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/configuration.cc b/mojo/core/configuration.cc
new file mode 100644
index 0000000000..b65612cea4
--- /dev/null
+++ b/mojo/core/configuration.cc
@@ -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.
+
+#include "mojo/core/configuration.h"
+
+namespace mojo {
+namespace core {
+namespace internal {
+
+Configuration g_configuration;
+
+} // namespace internal
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/configuration.h b/mojo/core/configuration.h
new file mode 100644
index 0000000000..4ba3eeb3cb
--- /dev/null
+++ b/mojo/core/configuration.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_CORE_CONFIGURATION_H_
+#define MOJO_CORE_CONFIGURATION_H_
+
+#include "mojo/core/embedder/configuration.h"
+#include "mojo/core/system_impl_export.h"
+
+namespace mojo {
+namespace core {
+
+namespace internal {
+MOJO_SYSTEM_IMPL_EXPORT extern Configuration g_configuration;
+} // namespace internal
+
+MOJO_SYSTEM_IMPL_EXPORT inline const Configuration& GetConfiguration() {
+ return internal::g_configuration;
+}
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_CONFIGURATION_H_
diff --git a/mojo/core/connection_params.cc b/mojo/core/connection_params.cc
new file mode 100644
index 0000000000..444c1a1f3b
--- /dev/null
+++ b/mojo/core/connection_params.cc
@@ -0,0 +1,31 @@
+// 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/core/connection_params.h"
+
+#include <utility>
+
+#include "base/logging.h"
+
+namespace mojo {
+namespace core {
+
+ConnectionParams::ConnectionParams() = default;
+
+ConnectionParams::ConnectionParams(PlatformChannelEndpoint endpoint)
+ : endpoint_(std::move(endpoint)) {}
+
+ConnectionParams::ConnectionParams(
+ PlatformChannelServerEndpoint server_endpoint)
+ : server_endpoint_(std::move(server_endpoint)) {}
+
+ConnectionParams::ConnectionParams(ConnectionParams&&) = default;
+
+ConnectionParams::~ConnectionParams() = default;
+
+ConnectionParams& ConnectionParams::operator=(ConnectionParams&& params) =
+ default;
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/connection_params.h b/mojo/core/connection_params.h
new file mode 100644
index 0000000000..77b4190563
--- /dev/null
+++ b/mojo/core/connection_params.h
@@ -0,0 +1,49 @@
+// 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_CORE_CONNECTION_PARAMS_H_
+#define MOJO_CORE_CONNECTION_PARAMS_H_
+
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "mojo/core/system_impl_export.h"
+#include "mojo/public/cpp/platform/platform_channel_endpoint.h"
+#include "mojo/public/cpp/platform/platform_channel_server_endpoint.h"
+
+namespace mojo {
+namespace core {
+
+// A set of parameters used when establishing a connection to another process.
+class MOJO_SYSTEM_IMPL_EXPORT ConnectionParams {
+ public:
+ ConnectionParams();
+ explicit ConnectionParams(PlatformChannelEndpoint endpoint);
+ explicit ConnectionParams(PlatformChannelServerEndpoint server_endpoint);
+ ConnectionParams(ConnectionParams&&);
+ ~ConnectionParams();
+
+ ConnectionParams& operator=(ConnectionParams&&);
+
+ const PlatformChannelEndpoint& endpoint() const { return endpoint_; }
+ const PlatformChannelServerEndpoint& server_endpoint() const {
+ return server_endpoint_;
+ }
+
+ PlatformChannelEndpoint TakeEndpoint() { return std::move(endpoint_); }
+
+ PlatformChannelServerEndpoint TakeServerEndpoint() {
+ return std::move(server_endpoint_);
+ }
+
+ private:
+ PlatformChannelEndpoint endpoint_;
+ PlatformChannelServerEndpoint server_endpoint_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConnectionParams);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_CONNECTION_PARAMS_H_
diff --git a/mojo/core/core.cc b/mojo/core/core.cc
new file mode 100644
index 0000000000..3ffa640ed3
--- /dev/null
+++ b/mojo/core/core.cc
@@ -0,0 +1,1513 @@
+// 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/core/core.h"
+
+#include <string.h>
+
+#include <algorithm>
+#include <utility>
+
+#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/memory/writable_shared_memory_region.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+// #include "base/trace_event/memory_dump_manager.h"
+#include "build/build_config.h"
+#include "mojo/core/channel.h"
+#include "mojo/core/configuration.h"
+#include "mojo/core/data_pipe_consumer_dispatcher.h"
+#include "mojo/core/data_pipe_producer_dispatcher.h"
+#include "mojo/core/embedder/process_error_callback.h"
+#include "mojo/core/handle_signals_state.h"
+#include "mojo/core/invitation_dispatcher.h"
+#include "mojo/core/message_pipe_dispatcher.h"
+#include "mojo/core/platform_handle_dispatcher.h"
+#include "mojo/core/platform_handle_utils.h"
+#include "mojo/core/platform_shared_memory_mapping.h"
+#include "mojo/core/ports/event.h"
+#include "mojo/core/ports/name.h"
+#include "mojo/core/ports/node.h"
+#include "mojo/core/request_context.h"
+#include "mojo/core/shared_buffer_dispatcher.h"
+#include "mojo/core/user_message_impl.h"
+#include "mojo/core/watcher_dispatcher.h"
+
+namespace mojo {
+namespace core {
+
+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;
+
+// The pipe name which must be used for the sole pipe attachment on any isolated
+// invitation.
+constexpr base::StringPiece kIsolatedInvitationPipeName = {"\0\0\0\0", 4};
+
+void InvokeProcessErrorCallbackOnTaskRunner(
+ scoped_refptr<base::TaskRunner> task_runner,
+ MojoProcessErrorHandler handler,
+ uintptr_t context,
+ const std::string& error,
+ MojoProcessErrorFlags flags) {
+ // We always run the handler asynchronously to ensure no Mojo core reentrancy.
+ task_runner->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ [](MojoProcessErrorHandler handler, uintptr_t context,
+ const std::string& error, MojoProcessErrorFlags flags) {
+ MojoProcessErrorDetails details;
+ details.struct_size = sizeof(details);
+ DCHECK(base::IsValueInRangeForNumericType<uint32_t>(error.size()));
+ details.error_message_length = static_cast<uint32_t>(error.size());
+ if (!error.empty())
+ details.error_message = error.data();
+ else
+ details.error_message = nullptr;
+ details.flags = flags;
+ handler(context, &details);
+ },
+ handler, context, error, flags));
+}
+
+// Helper class which is bound to the lifetime of a
+// ProcessErrorCallback generated by the |MojoSendInvitation()|
+// API. When the last reference to the error callback is lost within the EDK,
+// which will happen shortly after a connection to the process is lost, that
+// obviously guarantees that no more invocations of the callback will occur. At
+// that point, the corresponding instance of this object (owned by the callback
+// -- see Core::SendInvitation) will be destroyed.
+class ProcessDisconnectHandler {
+ public:
+ ProcessDisconnectHandler(scoped_refptr<base::TaskRunner> task_runner,
+ MojoProcessErrorHandler handler,
+ uintptr_t context)
+ : task_runner_(std::move(task_runner)),
+ handler_(handler),
+ context_(context) {}
+
+ ~ProcessDisconnectHandler() {
+ InvokeProcessErrorCallbackOnTaskRunner(
+ task_runner_, handler_, context_, std::string(),
+ MOJO_PROCESS_ERROR_FLAG_DISCONNECTED);
+ }
+
+ private:
+ const scoped_refptr<base::TaskRunner> task_runner_;
+ const MojoProcessErrorHandler handler_;
+ const uintptr_t context_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProcessDisconnectHandler);
+};
+
+void RunMojoProcessErrorHandler(ProcessDisconnectHandler* disconnect_handler,
+ scoped_refptr<base::TaskRunner> task_runner,
+ MojoProcessErrorHandler handler,
+ uintptr_t context,
+ const std::string& error) {
+ InvokeProcessErrorCallbackOnTaskRunner(task_runner, handler, context, error,
+ MOJO_PROCESS_ERROR_FLAG_NONE);
+}
+
+} // namespace
+
+Core::Core() {
+ handles_.reset(new HandleTable);
+ // base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
+ // handles_.get(), "MojoHandleTable", nullptr);
+}
+
+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<base::TaskRunner> io_task_runner =
+ node_controller_->io_task_runner();
+ io_task_runner->PostTask(FROM_HERE,
+ base::BindOnce(&Core::PassNodeControllerToIOThread,
+ base::Passed(&node_controller_)));
+ }
+ // base::trace_event::MemoryDumpManager::GetInstance()
+ // ->UnregisterAndDeleteDumpProviderSoon(std::move(handles_));
+}
+
+void Core::SetIOTaskRunner(scoped_refptr<base::TaskRunner> 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<Dispatcher> Core::GetDispatcher(MojoHandle handle) {
+ base::AutoLock lock(handles_->GetLock());
+ return handles_->GetDispatcher(handle);
+}
+
+scoped_refptr<Dispatcher> Core::GetAndRemoveDispatcher(MojoHandle handle) {
+ scoped_refptr<Dispatcher> dispatcher;
+ base::AutoLock lock(handles_->GetLock());
+ handles_->GetAndRemoveDispatcher(handle, &dispatcher);
+ return dispatcher;
+}
+
+void Core::SetDefaultProcessErrorCallback(
+ const ProcessErrorCallback& callback) {
+ default_process_error_callback_ = callback;
+}
+
+MojoHandle Core::CreatePartialMessagePipe(ports::PortRef* peer) {
+ RequestContext request_context;
+ ports::PortRef local_port;
+ GetNodeController()->node()->CreatePortPair(&local_port, peer);
+ return AddDispatcher(new MessagePipeDispatcher(
+ GetNodeController(), local_port, kUnknownPipeIdForDebug, 0));
+}
+
+MojoHandle Core::CreatePartialMessagePipe(const ports::PortRef& port) {
+ RequestContext request_context;
+ return AddDispatcher(new MessagePipeDispatcher(GetNodeController(), port,
+ kUnknownPipeIdForDebug, 1));
+}
+
+void Core::SendBrokerClientInvitation(
+ base::ProcessHandle target_process,
+ ConnectionParams connection_params,
+ const std::vector<std::pair<std::string, ports::PortRef>>& attached_ports,
+ const ProcessErrorCallback& process_error_callback) {
+ RequestContext request_context;
+ GetNodeController()->SendBrokerClientInvitation(
+ target_process, std::move(connection_params), attached_ports,
+ process_error_callback);
+}
+
+void Core::AcceptBrokerClientInvitation(ConnectionParams connection_params) {
+ RequestContext request_context;
+ GetNodeController()->AcceptBrokerClientInvitation(
+ std::move(connection_params));
+}
+
+void Core::ConnectIsolated(ConnectionParams connection_params,
+ const ports::PortRef& port,
+ base::StringPiece connection_name) {
+ RequestContext request_context;
+ GetNodeController()->ConnectIsolated(std::move(connection_params), port,
+ connection_name);
+}
+
+void Core::SetMachPortProvider(base::PortProvider* port_provider) {
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ GetNodeController()->CreateMachPortRelay(port_provider);
+#endif
+}
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+MachPortRelay* Core::GetMachPortRelay() {
+ return GetNodeController()->GetMachPortRelay();
+}
+#endif
+
+MojoHandle Core::AddDispatcher(scoped_refptr<Dispatcher> dispatcher) {
+ base::AutoLock lock(handles_->GetLock());
+ return handles_->AddDispatcher(dispatcher);
+}
+
+bool Core::AddDispatchersFromTransit(
+ const std::vector<Dispatcher::DispatcherInTransit>& dispatchers,
+ MojoHandle* handles) {
+ bool failed = false;
+ {
+ base::AutoLock lock(handles_->GetLock());
+ if (!handles_->AddDispatchersFromTransit(dispatchers, handles))
+ failed = true;
+ }
+ if (failed) {
+ for (auto d : dispatchers) {
+ if (d.dispatcher)
+ d.dispatcher->Close();
+ }
+ return false;
+ }
+ return true;
+}
+
+MojoResult Core::AcquireDispatchersForTransit(
+ const MojoHandle* handles,
+ size_t num_handles,
+ std::vector<Dispatcher::DispatcherInTransit>* dispatchers) {
+ base::AutoLock lock(handles_->GetLock());
+ MojoResult rv = handles_->BeginTransit(handles, num_handles, dispatchers);
+ if (rv != MOJO_RESULT_OK)
+ handles_->CancelTransit(*dispatchers);
+ return rv;
+}
+
+void Core::ReleaseDispatchersForTransit(
+ const std::vector<Dispatcher::DispatcherInTransit>& dispatchers,
+ bool in_transit) {
+ base::AutoLock lock(handles_->GetLock());
+ if (in_transit)
+ handles_->CompleteTransitAndClose(dispatchers);
+ else
+ handles_->CancelTransit(dispatchers);
+}
+
+void Core::RequestShutdown(const base::Closure& callback) {
+ GetNodeController()->RequestShutdown(callback);
+}
+
+MojoHandle Core::ExtractMessagePipeFromInvitation(const std::string& name) {
+ RequestContext request_context;
+ ports::PortRef port0, port1;
+ GetNodeController()->node()->CreatePortPair(&port0, &port1);
+ MojoHandle handle = AddDispatcher(new MessagePipeDispatcher(
+ GetNodeController(), port0, kUnknownPipeIdForDebug, 1));
+ GetNodeController()->MergePortIntoInviter(name, port1);
+ return handle;
+}
+
+MojoTimeTicks Core::GetTimeTicksNow() {
+ return base::TimeTicks::Now().ToInternalValue();
+}
+
+MojoResult Core::Close(MojoHandle handle) {
+ RequestContext request_context;
+ scoped_refptr<Dispatcher> dispatcher;
+ {
+ base::AutoLock lock(handles_->GetLock());
+ 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> dispatcher = GetDispatcher(handle);
+ if (!dispatcher || !signals_state)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ *signals_state = dispatcher->GetHandleSignalsState();
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::CreateTrap(MojoTrapEventHandler handler,
+ const MojoCreateTrapOptions* options,
+ MojoHandle* trap_handle) {
+ if (options && options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ RequestContext request_context;
+ if (!trap_handle)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ *trap_handle = AddDispatcher(new WatcherDispatcher(handler));
+ if (*trap_handle == MOJO_HANDLE_INVALID)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::AddTrigger(MojoHandle trap_handle,
+ MojoHandle handle,
+ MojoHandleSignals signals,
+ MojoTriggerCondition condition,
+ uintptr_t context,
+ const MojoAddTriggerOptions* options) {
+ if (options && options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ RequestContext request_context;
+ scoped_refptr<Dispatcher> watcher = GetDispatcher(trap_handle);
+ if (!watcher || watcher->GetType() != Dispatcher::Type::WATCHER)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ scoped_refptr<Dispatcher> dispatcher = GetDispatcher(handle);
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return watcher->WatchDispatcher(std::move(dispatcher), signals, condition,
+ context);
+}
+
+MojoResult Core::RemoveTrigger(MojoHandle trap_handle,
+ uintptr_t context,
+ const MojoRemoveTriggerOptions* options) {
+ if (options && options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ RequestContext request_context;
+ scoped_refptr<Dispatcher> watcher = GetDispatcher(trap_handle);
+ if (!watcher || watcher->GetType() != Dispatcher::Type::WATCHER)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ return watcher->CancelWatch(context);
+}
+
+MojoResult Core::ArmTrap(MojoHandle trap_handle,
+ const MojoArmTrapOptions* options,
+ uint32_t* num_blocking_events,
+ MojoTrapEvent* blocking_events) {
+ if (options && options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ RequestContext request_context;
+ scoped_refptr<Dispatcher> watcher = GetDispatcher(trap_handle);
+ if (!watcher || watcher->GetType() != Dispatcher::Type::WATCHER)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ return watcher->Arm(num_blocking_events, blocking_events);
+}
+
+MojoResult Core::CreateMessage(const MojoCreateMessageOptions* options,
+ MojoMessageHandle* message_handle) {
+ if (!message_handle)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (options && options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ *message_handle = reinterpret_cast<MojoMessageHandle>(
+ UserMessageImpl::CreateEventForNewMessage().release());
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::DestroyMessage(MojoMessageHandle message_handle) {
+ if (!message_handle)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ RequestContext request_context;
+ delete reinterpret_cast<ports::UserMessageEvent*>(message_handle);
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::SerializeMessage(MojoMessageHandle message_handle,
+ const MojoSerializeMessageOptions* options) {
+ if (!message_handle)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (options && options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ RequestContext request_context;
+ return reinterpret_cast<ports::UserMessageEvent*>(message_handle)
+ ->GetMessage<UserMessageImpl>()
+ ->SerializeIfNecessary();
+}
+
+MojoResult Core::AppendMessageData(MojoMessageHandle message_handle,
+ uint32_t additional_payload_size,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ const MojoAppendMessageDataOptions* options,
+ void** buffer,
+ uint32_t* buffer_size) {
+ if (!message_handle || (num_handles && !handles))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (options && options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ RequestContext request_context;
+ auto* message = reinterpret_cast<ports::UserMessageEvent*>(message_handle)
+ ->GetMessage<UserMessageImpl>();
+ MojoResult rv =
+ message->AppendData(additional_payload_size, handles, num_handles);
+ if (rv != MOJO_RESULT_OK)
+ return rv;
+
+ if (options && (options->flags & MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE)) {
+ RequestContext request_context;
+ message->CommitSize();
+ }
+
+ if (buffer)
+ *buffer = message->user_payload();
+ if (buffer_size) {
+ *buffer_size =
+ base::checked_cast<uint32_t>(message->user_payload_capacity());
+ }
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::GetMessageData(MojoMessageHandle message_handle,
+ const MojoGetMessageDataOptions* options,
+ void** buffer,
+ uint32_t* num_bytes,
+ MojoHandle* handles,
+ uint32_t* num_handles) {
+ if (!message_handle || (num_handles && *num_handles && !handles))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (options && options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ auto* message = reinterpret_cast<ports::UserMessageEvent*>(message_handle)
+ ->GetMessage<UserMessageImpl>();
+ if (!message->IsSerialized() || !message->IsTransmittable())
+ return MOJO_RESULT_FAILED_PRECONDITION;
+
+ if (num_bytes) {
+ base::CheckedNumeric<uint32_t> payload_size = message->user_payload_size();
+ *num_bytes = payload_size.ValueOrDie();
+ }
+
+ if (message->user_payload_size() > 0) {
+ if (!num_bytes || !buffer)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ *buffer = message->user_payload();
+ } else if (buffer) {
+ *buffer = nullptr;
+ }
+
+ if (options && (options->flags & MOJO_GET_MESSAGE_DATA_FLAG_IGNORE_HANDLES))
+ return MOJO_RESULT_OK;
+
+ uint32_t max_num_handles = 0;
+ if (num_handles) {
+ max_num_handles = *num_handles;
+ *num_handles = static_cast<uint32_t>(message->num_handles());
+ }
+
+ if (message->num_handles() > max_num_handles ||
+ message->num_handles() > kMaxHandlesPerMessage) {
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ }
+
+ RequestContext request_context;
+ return message->ExtractSerializedHandles(
+ UserMessageImpl::ExtractBadHandlePolicy::kAbort, handles);
+}
+
+MojoResult Core::SetMessageContext(
+ MojoMessageHandle message_handle,
+ uintptr_t context,
+ MojoMessageContextSerializer serializer,
+ MojoMessageContextDestructor destructor,
+ const MojoSetMessageContextOptions* options) {
+ if (!message_handle)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (options && options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ auto* message = reinterpret_cast<ports::UserMessageEvent*>(message_handle)
+ ->GetMessage<UserMessageImpl>();
+ return message->SetContext(context, serializer, destructor);
+}
+
+MojoResult Core::GetMessageContext(MojoMessageHandle message_handle,
+ const MojoGetMessageContextOptions* options,
+ uintptr_t* context) {
+ if (!message_handle)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (options && options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ auto* message = reinterpret_cast<ports::UserMessageEvent*>(message_handle)
+ ->GetMessage<UserMessageImpl>();
+ if (!message->HasContext())
+ return MOJO_RESULT_NOT_FOUND;
+
+ *context = message->context();
+ return MOJO_RESULT_OK;
+}
+
+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);
+
+ DCHECK(message_pipe_handle0);
+ DCHECK(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<Dispatcher> dispatcher0;
+ {
+ base::AutoLock lock(handles_->GetLock());
+ handles_->GetAndRemoveDispatcher(*message_pipe_handle0, &dispatcher0);
+ }
+ dispatcher0->Close();
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ }
+
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::WriteMessage(MojoHandle message_pipe_handle,
+ MojoMessageHandle message_handle,
+ const MojoWriteMessageOptions* options) {
+ RequestContext request_context;
+ if (!message_handle)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ auto message_event = base::WrapUnique(
+ reinterpret_cast<ports::UserMessageEvent*>(message_handle));
+ auto* message = message_event->GetMessage<UserMessageImpl>();
+ if (!message || !message->IsTransmittable())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ auto dispatcher = GetDispatcher(message_pipe_handle);
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ return dispatcher->WriteMessage(std::move(message_event));
+}
+
+MojoResult Core::ReadMessage(MojoHandle message_pipe_handle,
+ const MojoReadMessageOptions* options,
+ MojoMessageHandle* message_handle) {
+ RequestContext request_context;
+ auto dispatcher = GetDispatcher(message_pipe_handle);
+ if (!dispatcher || !message_handle)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ std::unique_ptr<ports::UserMessageEvent> message_event;
+ MojoResult rv = dispatcher->ReadMessage(&message_event);
+ if (rv != MOJO_RESULT_OK)
+ return rv;
+
+ *message_handle =
+ reinterpret_cast<MojoMessageHandle>(message_event.release());
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::FuseMessagePipes(MojoHandle handle0,
+ MojoHandle handle1,
+ const MojoFuseMessagePipesOptions* options) {
+ RequestContext request_context;
+ scoped_refptr<Dispatcher> dispatcher0;
+ scoped_refptr<Dispatcher> dispatcher1;
+
+ bool valid_handles = true;
+ {
+ base::AutoLock lock(handles_->GetLock());
+ 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<MessagePipeDispatcher*>(dispatcher0.get());
+ MessagePipeDispatcher* mpd1 =
+ static_cast<MessagePipeDispatcher*>(dispatcher1.get());
+
+ if (!mpd0->Fuse(mpd1))
+ return MOJO_RESULT_FAILED_PRECONDITION;
+
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::NotifyBadMessage(MojoMessageHandle message_handle,
+ const char* error,
+ size_t error_num_bytes,
+ const MojoNotifyBadMessageOptions* options) {
+ if (!message_handle)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ auto* message_event =
+ reinterpret_cast<ports::UserMessageEvent*>(message_handle);
+ auto* message = message_event->GetMessage<UserMessageImpl>();
+ if (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(
+ 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;
+ if (!create_options.element_num_bytes || !create_options.capacity_num_bytes ||
+ create_options.capacity_num_bytes < create_options.element_num_bytes) {
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ base::subtle::PlatformSharedMemoryRegion ring_buffer_region =
+ base::WritableSharedMemoryRegion::TakeHandleForSerialization(
+ GetNodeController()->CreateSharedBuffer(
+ create_options.capacity_num_bytes));
+
+ // NOTE: We demote the writable region to an unsafe region so that the
+ // producer handle can be transferred freely. There is no compelling reason
+ // to restrict access rights of consumers since they are the exclusive
+ // consumer of this pipe, and it would be impossible to support such access
+ // control on Android anyway.
+ auto writable_region_handle = ring_buffer_region.PassPlatformHandle();
+#if defined(OS_POSIX) && !defined(OS_ANDROID) && \
+ (!defined(OS_MACOSX) || defined(OS_IOS))
+ // This isn't strictly necessary, but it does make the handle configuration
+ // consistent with regular UnsafeSharedMemoryRegions.
+ writable_region_handle.readonly_fd.reset();
+#endif
+ base::UnsafeSharedMemoryRegion producer_region =
+ base::UnsafeSharedMemoryRegion::Deserialize(
+ base::subtle::PlatformSharedMemoryRegion::Take(
+ std::move(writable_region_handle),
+ base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
+ create_options.capacity_num_bytes, ring_buffer_region.GetGUID()));
+ if (!producer_region.IsValid())
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ ports::PortRef port0, port1;
+ GetNodeController()->node()->CreatePortPair(&port0, &port1);
+
+ DCHECK(data_pipe_producer_handle);
+ DCHECK(data_pipe_consumer_handle);
+
+ base::UnsafeSharedMemoryRegion consumer_region = producer_region.Duplicate();
+ uint64_t pipe_id = base::RandUint64();
+ scoped_refptr<Dispatcher> producer = DataPipeProducerDispatcher::Create(
+ GetNodeController(), port0, std::move(producer_region), create_options,
+ pipe_id);
+ if (!producer)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ scoped_refptr<Dispatcher> consumer = DataPipeConsumerDispatcher::Create(
+ GetNodeController(), port1, std::move(consumer_region), create_options,
+ pipe_id);
+ if (!consumer) {
+ producer->Close();
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ }
+
+ *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<Dispatcher> unused;
+ base::AutoLock lock(handles_->GetLock());
+ 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,
+ const MojoWriteDataOptions* options) {
+ RequestContext request_context;
+ scoped_refptr<Dispatcher> dispatcher(
+ GetDispatcher(data_pipe_producer_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ MojoWriteDataOptions validated_options;
+ if (options) {
+ if (options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ constexpr MojoWriteDataFlags kSupportedFlags =
+ MOJO_WRITE_DATA_FLAG_NONE | MOJO_WRITE_DATA_FLAG_ALL_OR_NONE;
+ if (options->flags & ~kSupportedFlags)
+ return MOJO_RESULT_UNIMPLEMENTED;
+ validated_options.flags = options->flags;
+ } else {
+ validated_options.flags = MOJO_WRITE_DATA_FLAG_NONE;
+ }
+ return dispatcher->WriteData(elements, num_bytes, validated_options);
+}
+
+MojoResult Core::BeginWriteData(MojoHandle data_pipe_producer_handle,
+ const MojoBeginWriteDataOptions* options,
+ void** buffer,
+ uint32_t* buffer_num_bytes) {
+ RequestContext request_context;
+ scoped_refptr<Dispatcher> dispatcher(
+ GetDispatcher(data_pipe_producer_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (options) {
+ if (options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (options->flags != MOJO_BEGIN_WRITE_DATA_FLAG_NONE)
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+ return dispatcher->BeginWriteData(buffer, buffer_num_bytes);
+}
+
+MojoResult Core::EndWriteData(MojoHandle data_pipe_producer_handle,
+ uint32_t num_bytes_written,
+ const MojoEndWriteDataOptions* options) {
+ RequestContext request_context;
+ scoped_refptr<Dispatcher> dispatcher(
+ GetDispatcher(data_pipe_producer_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (options) {
+ if (options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (options->flags != MOJO_END_WRITE_DATA_FLAG_NONE)
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+ return dispatcher->EndWriteData(num_bytes_written);
+}
+
+MojoResult Core::ReadData(MojoHandle data_pipe_consumer_handle,
+ const MojoReadDataOptions* options,
+ void* elements,
+ uint32_t* num_bytes) {
+ RequestContext request_context;
+ scoped_refptr<Dispatcher> dispatcher(
+ GetDispatcher(data_pipe_consumer_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ MojoReadDataOptions validated_options;
+ if (options) {
+ if (options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ constexpr MojoReadDataFlags kSupportedFlags =
+ MOJO_READ_DATA_FLAG_NONE | MOJO_READ_DATA_FLAG_ALL_OR_NONE |
+ MOJO_READ_DATA_FLAG_DISCARD | MOJO_READ_DATA_FLAG_QUERY |
+ MOJO_READ_DATA_FLAG_PEEK;
+ if (options->flags & ~kSupportedFlags)
+ return MOJO_RESULT_UNIMPLEMENTED;
+ validated_options.flags = options->flags;
+ } else {
+ validated_options.flags = MOJO_WRITE_DATA_FLAG_NONE;
+ }
+ return dispatcher->ReadData(validated_options, elements, num_bytes);
+}
+
+MojoResult Core::BeginReadData(MojoHandle data_pipe_consumer_handle,
+ const MojoBeginReadDataOptions* options,
+ const void** buffer,
+ uint32_t* buffer_num_bytes) {
+ RequestContext request_context;
+ scoped_refptr<Dispatcher> dispatcher(
+ GetDispatcher(data_pipe_consumer_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if (options) {
+ if (options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (options->flags != MOJO_BEGIN_READ_DATA_FLAG_NONE)
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+ return dispatcher->BeginReadData(buffer, buffer_num_bytes);
+}
+
+MojoResult Core::EndReadData(MojoHandle data_pipe_consumer_handle,
+ uint32_t num_bytes_read,
+ const MojoEndReadDataOptions* options) {
+ RequestContext request_context;
+ scoped_refptr<Dispatcher> dispatcher(
+ GetDispatcher(data_pipe_consumer_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (options) {
+ if (options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (options->flags != MOJO_END_READ_DATA_FLAG_NONE)
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+ return dispatcher->EndReadData(num_bytes_read);
+}
+
+MojoResult Core::CreateSharedBuffer(
+ uint64_t num_bytes,
+ const MojoCreateSharedBufferOptions* options,
+ 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<SharedBufferDispatcher> 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> dispatcher(GetDispatcher(buffer_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ // Don't verify |options| here; that's the dispatcher's job.
+ scoped_refptr<Dispatcher> 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";
+ new_dispatcher->Close();
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ }
+
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::MapBuffer(MojoHandle buffer_handle,
+ uint64_t offset,
+ uint64_t num_bytes,
+ const MojoMapBufferOptions* options,
+ void** buffer) {
+ scoped_refptr<Dispatcher> dispatcher(GetDispatcher(buffer_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (options) {
+ if (options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (options->flags != MOJO_MAP_BUFFER_FLAG_NONE)
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+
+ std::unique_ptr<PlatformSharedMemoryMapping> mapping;
+ MojoResult result = dispatcher->MapBuffer(offset, num_bytes, &mapping);
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ DCHECK(mapping);
+ void* address = mapping->GetBase();
+ {
+ base::AutoLock locker(mapping_table_lock_);
+ if (mapping_table_.size() >= GetConfiguration().max_mapping_table_size)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ auto emplace_result = mapping_table_.emplace(address, std::move(mapping));
+ DCHECK(emplace_result.second);
+ }
+
+ *buffer = address;
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::UnmapBuffer(void* buffer) {
+ std::unique_ptr<PlatformSharedMemoryMapping> mapping;
+ // Destroy |mapping| while not holding the lock.
+ {
+ base::AutoLock lock(mapping_table_lock_);
+ auto iter = mapping_table_.find(buffer);
+ if (iter == mapping_table_.end())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ // Grab a reference so that it gets unmapped outside of this lock.
+ mapping = std::move(iter->second);
+ mapping_table_.erase(iter);
+ }
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::GetBufferInfo(MojoHandle buffer_handle,
+ const MojoGetBufferInfoOptions* options,
+ MojoSharedBufferInfo* info) {
+ if (options) {
+ if (options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (options->flags != MOJO_GET_BUFFER_INFO_FLAG_NONE)
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+ if (!info || info->struct_size < sizeof(MojoSharedBufferInfo))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ scoped_refptr<Dispatcher> dispatcher(GetDispatcher(buffer_handle));
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return dispatcher->GetBufferInfo(info);
+}
+
+MojoResult Core::WrapPlatformHandle(
+ const MojoPlatformHandle* platform_handle,
+ const MojoWrapPlatformHandleOptions* options,
+ MojoHandle* mojo_handle) {
+ if (!platform_handle ||
+ platform_handle->struct_size < sizeof(*platform_handle)) {
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ auto handle = PlatformHandle::FromMojoPlatformHandle(platform_handle);
+ MojoHandle h =
+ AddDispatcher(PlatformHandleDispatcher::Create(std::move(handle)));
+ if (h == MOJO_HANDLE_INVALID)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ *mojo_handle = h;
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::UnwrapPlatformHandle(
+ MojoHandle mojo_handle,
+ const MojoUnwrapPlatformHandleOptions* options,
+ MojoPlatformHandle* platform_handle) {
+ if (!platform_handle ||
+ platform_handle->struct_size < sizeof(*platform_handle)) {
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ scoped_refptr<Dispatcher> dispatcher;
+ {
+ base::AutoLock lock(handles_->GetLock());
+ dispatcher = handles_->GetDispatcher(mojo_handle);
+ if (dispatcher->GetType() != Dispatcher::Type::PLATFORM_HANDLE)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ MojoResult result =
+ handles_->GetAndRemoveDispatcher(mojo_handle, &dispatcher);
+ if (result != MOJO_RESULT_OK)
+ return result;
+ }
+
+ PlatformHandleDispatcher* phd =
+ static_cast<PlatformHandleDispatcher*>(dispatcher.get());
+ PlatformHandle handle = phd->TakePlatformHandle();
+ phd->Close();
+
+ PlatformHandle::ToMojoPlatformHandle(std::move(handle), platform_handle);
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::WrapPlatformSharedMemoryRegion(
+ const MojoPlatformHandle* platform_handles,
+ uint32_t num_platform_handles,
+ uint64_t size,
+ const MojoSharedBufferGuid* guid,
+ MojoPlatformSharedMemoryRegionAccessMode access_mode,
+ const MojoWrapPlatformSharedMemoryRegionOptions* options,
+ MojoHandle* mojo_handle) {
+ DCHECK(size);
+
+#if defined(OS_POSIX) && !defined(OS_ANDROID) && \
+ (!defined(OS_MACOSX) || defined(OS_IOS))
+ if (access_mode == MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_WRITABLE) {
+ if (num_platform_handles != 2)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+#else
+ if (num_platform_handles != 1)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+#endif
+
+ PlatformHandle handles[2];
+ bool handles_ok = true;
+ for (size_t i = 0; i < num_platform_handles; ++i) {
+ handles[i] = PlatformHandle::FromMojoPlatformHandle(&platform_handles[i]);
+ if (!handles[i].is_valid())
+ handles_ok = false;
+ }
+ if (!handles_ok)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ base::UnguessableToken token =
+ base::UnguessableToken::Deserialize(guid->high, guid->low);
+
+ base::subtle::PlatformSharedMemoryRegion::Mode mode;
+ switch (access_mode) {
+ case MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_READ_ONLY:
+ mode = base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly;
+ break;
+ case MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_WRITABLE:
+ mode = base::subtle::PlatformSharedMemoryRegion::Mode::kWritable;
+ break;
+ case MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_UNSAFE:
+ mode = base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe;
+ break;
+ default:
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ base::subtle::PlatformSharedMemoryRegion region =
+ base::subtle::PlatformSharedMemoryRegion::Take(
+ CreateSharedMemoryRegionHandleFromPlatformHandles(
+ std::move(handles[0]), std::move(handles[1])),
+ mode, size, token);
+ if (!region.IsValid())
+ return MOJO_RESULT_UNKNOWN;
+
+ scoped_refptr<SharedBufferDispatcher> dispatcher;
+ MojoResult result =
+ SharedBufferDispatcher::CreateFromPlatformSharedMemoryRegion(
+ std::move(region), &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::UnwrapPlatformSharedMemoryRegion(
+ MojoHandle mojo_handle,
+ const MojoUnwrapPlatformSharedMemoryRegionOptions* options,
+ MojoPlatformHandle* platform_handles,
+ uint32_t* num_platform_handles,
+ uint64_t* size,
+ MojoSharedBufferGuid* guid,
+ MojoPlatformSharedMemoryRegionAccessMode* access_mode) {
+ scoped_refptr<Dispatcher> dispatcher;
+ MojoResult result = MOJO_RESULT_OK;
+ {
+ base::AutoLock lock(handles_->GetLock());
+ 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<SharedBufferDispatcher*>(dispatcher.get());
+ base::subtle::PlatformSharedMemoryRegion region =
+ shm_dispatcher->PassPlatformSharedMemoryRegion();
+ DCHECK(region.IsValid());
+ DCHECK(size);
+ *size = region.GetSize();
+
+ base::UnguessableToken token = region.GetGUID();
+ guid->high = token.GetHighForSerialization();
+ guid->low = token.GetLowForSerialization();
+
+ DCHECK(access_mode);
+ switch (region.GetMode()) {
+ case base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly:
+ *access_mode = MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_READ_ONLY;
+ break;
+ case base::subtle::PlatformSharedMemoryRegion::Mode::kWritable:
+ *access_mode = MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_WRITABLE;
+ break;
+ case base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe:
+ *access_mode = MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_UNSAFE;
+ break;
+ default:
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ PlatformHandle handle;
+ PlatformHandle read_only_handle;
+ ExtractPlatformHandlesFromSharedMemoryRegionHandle(
+ region.PassPlatformHandle(), &handle, &read_only_handle);
+
+ const uint32_t available_handle_storage_slots = *num_platform_handles;
+ if (available_handle_storage_slots < 1)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ *num_platform_handles = 1;
+#if defined(OS_POSIX) && !defined(OS_ANDROID) && \
+ (!defined(OS_MACOSX) || defined(OS_IOS))
+ if (region.GetMode() ==
+ base::subtle::PlatformSharedMemoryRegion::Mode::kWritable) {
+ if (available_handle_storage_slots < 2)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ PlatformHandle::ToMojoPlatformHandle(std::move(read_only_handle),
+ &platform_handles[1]);
+ if (platform_handles[1].type == MOJO_PLATFORM_HANDLE_TYPE_INVALID)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ *num_platform_handles = 2;
+ }
+#endif
+
+ PlatformHandle::ToMojoPlatformHandle(std::move(handle), &platform_handles[0]);
+ if (platform_handles[0].type == MOJO_PLATFORM_HANDLE_TYPE_INVALID)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::CreateInvitation(const MojoCreateInvitationOptions* options,
+ MojoHandle* invitation_handle) {
+ if (options && options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (!invitation_handle)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ *invitation_handle = AddDispatcher(new InvitationDispatcher);
+ if (*invitation_handle == MOJO_HANDLE_INVALID)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::AttachMessagePipeToInvitation(
+ MojoHandle invitation_handle,
+ const void* name,
+ uint32_t name_num_bytes,
+ const MojoAttachMessagePipeToInvitationOptions* options,
+ MojoHandle* message_pipe_handle) {
+ if (options && options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (!message_pipe_handle)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (name_num_bytes == 0)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ scoped_refptr<Dispatcher> dispatcher = GetDispatcher(invitation_handle);
+ if (!dispatcher || dispatcher->GetType() != Dispatcher::Type::INVITATION)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ auto* invitation_dispatcher =
+ static_cast<InvitationDispatcher*>(dispatcher.get());
+
+ RequestContext request_context;
+
+ ports::PortRef remote_peer_port;
+ MojoHandle local_handle = CreatePartialMessagePipe(&remote_peer_port);
+ if (local_handle == MOJO_HANDLE_INVALID)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ MojoResult result = invitation_dispatcher->AttachMessagePipe(
+ base::StringPiece(static_cast<const char*>(name), name_num_bytes),
+ std::move(remote_peer_port));
+ if (result != MOJO_RESULT_OK) {
+ Close(local_handle);
+ return result;
+ }
+
+ *message_pipe_handle = local_handle;
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::ExtractMessagePipeFromInvitation(
+ MojoHandle invitation_handle,
+ const void* name,
+ uint32_t name_num_bytes,
+ const MojoExtractMessagePipeFromInvitationOptions* options,
+ MojoHandle* message_pipe_handle) {
+ if (options && options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (!message_pipe_handle)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (name_num_bytes == 0)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ RequestContext request_context;
+
+ base::StringPiece name_string(static_cast<const char*>(name), name_num_bytes);
+ scoped_refptr<Dispatcher> dispatcher = GetDispatcher(invitation_handle);
+ if (!dispatcher || dispatcher->GetType() != Dispatcher::Type::INVITATION)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ auto* invitation_dispatcher =
+ static_cast<InvitationDispatcher*>(dispatcher.get());
+ // First attempt to extract from the invitation object itself. This is for
+ // cases where this invitation was created in-process or is an accepted
+ // isolated invitation.
+ MojoResult extract_result = invitation_dispatcher->ExtractMessagePipe(
+ name_string, message_pipe_handle);
+ if (extract_result == MOJO_RESULT_OK ||
+ extract_result == MOJO_RESULT_RESOURCE_EXHAUSTED) {
+ return extract_result;
+ }
+
+ *message_pipe_handle =
+ ExtractMessagePipeFromInvitation(name_string.as_string());
+ if (*message_pipe_handle == MOJO_HANDLE_INVALID)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::SendInvitation(
+ MojoHandle invitation_handle,
+ const MojoPlatformProcessHandle* process_handle,
+ const MojoInvitationTransportEndpoint* transport_endpoint,
+ MojoProcessErrorHandler error_handler,
+ uintptr_t error_handler_context,
+ const MojoSendInvitationOptions* options) {
+ if (options && options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ base::ProcessHandle target_process = base::kNullProcessHandle;
+ if (process_handle) {
+ if (process_handle->struct_size < sizeof(*process_handle))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+#if defined(OS_WIN)
+ target_process = reinterpret_cast<base::ProcessHandle>(
+ static_cast<uintptr_t>(process_handle->value));
+#else
+ target_process = static_cast<base::ProcessHandle>(process_handle->value);
+#endif
+ }
+
+ ProcessErrorCallback process_error_callback;
+ if (error_handler) {
+ auto error_handler_task_runner = GetNodeController()->io_task_runner();
+ process_error_callback = base::BindRepeating(
+ &RunMojoProcessErrorHandler,
+ base::Owned(new ProcessDisconnectHandler(
+ error_handler_task_runner, error_handler, error_handler_context)),
+ error_handler_task_runner, error_handler, error_handler_context);
+ } else if (default_process_error_callback_) {
+ process_error_callback = default_process_error_callback_;
+ }
+
+ if (!transport_endpoint)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (transport_endpoint->struct_size < sizeof(*transport_endpoint))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (transport_endpoint->num_platform_handles == 0)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (!transport_endpoint->platform_handles)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (transport_endpoint->type != MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL &&
+ transport_endpoint->type !=
+ MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL_SERVER) {
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+
+ scoped_refptr<Dispatcher> dispatcher = GetDispatcher(invitation_handle);
+ if (!dispatcher || dispatcher->GetType() != Dispatcher::Type::INVITATION)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ auto* invitation_dispatcher =
+ static_cast<InvitationDispatcher*>(dispatcher.get());
+
+ auto endpoint = PlatformHandle::FromMojoPlatformHandle(
+ &transport_endpoint->platform_handles[0]);
+ if (!endpoint.is_valid())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ ConnectionParams connection_params;
+#if defined(OS_WIN) || defined(OS_POSIX)
+ if (transport_endpoint->type ==
+ MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL_SERVER) {
+ connection_params =
+ ConnectionParams(PlatformChannelServerEndpoint(std::move(endpoint)));
+ }
+#endif
+ if (!connection_params.server_endpoint().is_valid()) {
+ connection_params =
+ ConnectionParams(PlatformChannelEndpoint(std::move(endpoint)));
+ }
+
+ // At this point everything else has been validated, so we can take ownership
+ // of the dispatcher.
+ {
+ base::AutoLock lock(handles_->GetLock());
+ scoped_refptr<Dispatcher> removed_dispatcher;
+ MojoResult result = handles_->GetAndRemoveDispatcher(invitation_handle,
+ &removed_dispatcher);
+ if (result != MOJO_RESULT_OK) {
+ // Release ownership of the endpoint platform handle, per the API
+ // contract. The caller retains ownership on failure.
+ connection_params.TakeEndpoint().TakePlatformHandle().release();
+ connection_params.TakeServerEndpoint().TakePlatformHandle().release();
+ return result;
+ }
+ DCHECK_EQ(removed_dispatcher.get(), invitation_dispatcher);
+ }
+
+ std::vector<std::pair<std::string, ports::PortRef>> attached_ports;
+ InvitationDispatcher::PortMapping attached_port_map =
+ invitation_dispatcher->TakeAttachedPorts();
+ invitation_dispatcher->Close();
+ for (auto& entry : attached_port_map)
+ attached_ports.emplace_back(entry.first, std::move(entry.second));
+
+ bool is_isolated =
+ options && (options->flags & MOJO_SEND_INVITATION_FLAG_ISOLATED);
+ RequestContext request_context;
+ if (is_isolated) {
+ DCHECK_EQ(attached_ports.size(), 1u);
+ DCHECK_EQ(attached_ports[0].first, kIsolatedInvitationPipeName);
+ base::StringPiece connection_name(options->isolated_connection_name,
+ options->isolated_connection_name_length);
+ GetNodeController()->ConnectIsolated(std::move(connection_params),
+ attached_ports[0].second,
+ connection_name);
+ } else {
+ GetNodeController()->SendBrokerClientInvitation(
+ target_process, std::move(connection_params), attached_ports,
+ process_error_callback);
+ }
+
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::AcceptInvitation(
+ const MojoInvitationTransportEndpoint* transport_endpoint,
+ const MojoAcceptInvitationOptions* options,
+ MojoHandle* invitation_handle) {
+ if (options && options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if (!transport_endpoint)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (transport_endpoint->struct_size < sizeof(*transport_endpoint))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (transport_endpoint->num_platform_handles == 0)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (!transport_endpoint->platform_handles)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (transport_endpoint->type != MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL &&
+ transport_endpoint->type !=
+ MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL_SERVER) {
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+
+ if (!invitation_handle)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ auto dispatcher = base::MakeRefCounted<InvitationDispatcher>();
+ *invitation_handle = AddDispatcher(dispatcher);
+ if (*invitation_handle == MOJO_HANDLE_INVALID)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ auto endpoint = PlatformHandle::FromMojoPlatformHandle(
+ &transport_endpoint->platform_handles[0]);
+ if (!endpoint.is_valid()) {
+ Close(*invitation_handle);
+ *invitation_handle = MOJO_HANDLE_INVALID;
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ ConnectionParams connection_params;
+#if defined(OS_WIN) || defined(OS_POSIX)
+ if (transport_endpoint->type ==
+ MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL_SERVER) {
+ connection_params =
+ ConnectionParams(PlatformChannelServerEndpoint(std::move(endpoint)));
+ }
+#endif
+ if (!connection_params.server_endpoint().is_valid()) {
+ connection_params =
+ ConnectionParams(PlatformChannelEndpoint(std::move(endpoint)));
+ }
+
+ bool is_isolated =
+ options && (options->flags & MOJO_ACCEPT_INVITATION_FLAG_ISOLATED);
+ NodeController* const node_controller = GetNodeController();
+ RequestContext request_context;
+ if (is_isolated) {
+ // For an isolated invitation, we simply mint a new port pair here and send
+ // one name to the remote endpoint while stashing the other in the accepted
+ // invitation object for later extraction.
+ ports::PortRef local_port;
+ ports::PortRef remote_port;
+ node_controller->node()->CreatePortPair(&local_port, &remote_port);
+ node_controller->ConnectIsolated(std::move(connection_params), remote_port,
+ base::StringPiece());
+ MojoResult result =
+ dispatcher->AttachMessagePipe(kIsolatedInvitationPipeName, local_port);
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+ } else {
+ node_controller->AcceptBrokerClientInvitation(std::move(connection_params));
+ }
+
+ return MOJO_RESULT_OK;
+}
+
+MojoResult Core::SetQuota(MojoHandle handle,
+ MojoQuotaType type,
+ uint64_t limit,
+ const MojoSetQuotaOptions* options) {
+ RequestContext request_context;
+ if (options && options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ auto dispatcher = GetDispatcher(handle);
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ return dispatcher->SetQuota(type, limit);
+}
+
+MojoResult Core::QueryQuota(MojoHandle handle,
+ MojoQuotaType type,
+ const MojoQueryQuotaOptions* options,
+ uint64_t* limit,
+ uint64_t* usage) {
+ RequestContext request_context;
+ if (options && options->struct_size < sizeof(*options))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ auto dispatcher = GetDispatcher(handle);
+ if (!dispatcher)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ return dispatcher->QueryQuota(type, limit, usage);
+}
+
+void Core::GetActiveHandlesForTest(std::vector<MojoHandle>* handles) {
+ base::AutoLock lock(handles_->GetLock());
+ handles_->GetActiveHandlesForTest(handles);
+}
+
+// static
+void Core::PassNodeControllerToIOThread(
+ std::unique_ptr<NodeController> 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 core
+} // namespace mojo
diff --git a/mojo/core/core.h b/mojo/core/core.h
new file mode 100644
index 0000000000..2840bc5e68
--- /dev/null
+++ b/mojo/core/core.h
@@ -0,0 +1,385 @@
+// 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_CORE_CORE_H_
+#define MOJO_CORE_CORE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#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 "build/build_config.h"
+#include "mojo/core/dispatcher.h"
+#include "mojo/core/handle_signals_state.h"
+#include "mojo/core/handle_table.h"
+#include "mojo/core/node_controller.h"
+#include "mojo/core/system_impl_export.h"
+#include "mojo/public/c/system/buffer.h"
+#include "mojo/public/c/system/data_pipe.h"
+#include "mojo/public/c/system/invitation.h"
+#include "mojo/public/c/system/message_pipe.h"
+#include "mojo/public/c/system/platform_handle.h"
+#include "mojo/public/c/system/quota.h"
+#include "mojo/public/c/system/trap.h"
+#include "mojo/public/c/system/types.h"
+
+namespace base {
+class PortProvider;
+}
+
+namespace mojo {
+namespace core {
+
+class MachPortRelay;
+class PlatformSharedMemoryMapping;
+
+// |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();
+
+ static Core* Get();
+
+ // Called exactly once, shortly after construction, and before any other
+ // methods are called on this object.
+ void SetIOTaskRunner(scoped_refptr<base::TaskRunner> io_task_runner);
+
+ // Retrieves the NodeController for the current process.
+ NodeController* GetNodeController();
+
+ scoped_refptr<Dispatcher> GetDispatcher(MojoHandle handle);
+ scoped_refptr<Dispatcher> GetAndRemoveDispatcher(MojoHandle handle);
+
+ void SetDefaultProcessErrorCallback(const ProcessErrorCallback& callback);
+
+ // Creates a message pipe endpoint with an unbound peer port returned in
+ // |*peer|. Useful for setting up cross-process bootstrap message pipes. The
+ // returned message pipe handle is usable immediately by the caller.
+ //
+ // The value returned in |*peer| may be passed along with a broker client
+ // invitation. See SendBrokerClientInvitation() below.
+ MojoHandle CreatePartialMessagePipe(ports::PortRef* peer);
+
+ // Like above but exchanges an existing ports::PortRef for a message pipe
+ // handle which wraps it.
+ MojoHandle CreatePartialMessagePipe(const ports::PortRef& port);
+
+ // Sends a broker client invitation to |target_process| over the connection
+ // medium in |connection_params|. The other end of the connection medium in
+ // |connection_params| can be used within the target process to call
+ // AcceptBrokerClientInvitation() and complete the process's admission into
+ // this process graph.
+ //
+ // |attached_ports| is a list of named port references to be attached to the
+ // invitation. An attached port can be claimed (as a message pipe handle) by
+ // the invitee.
+ void SendBrokerClientInvitation(
+ base::ProcessHandle target_process,
+ ConnectionParams connection_params,
+ const std::vector<std::pair<std::string, ports::PortRef>>& attached_ports,
+ const ProcessErrorCallback& process_error_callback);
+
+ // Accepts an invitation via |connection_params|. The other end of the
+ // connection medium in |connection_params| must have been used by some other
+ // process to send an invitation.
+ void AcceptBrokerClientInvitation(ConnectionParams connection_params);
+
+ // Extracts a named message pipe endpoint from the broker client invitation
+ // accepted by this process. Must only be called after
+ // AcceptBrokerClientInvitation.
+ MojoHandle ExtractMessagePipeFromInvitation(const std::string& name);
+
+ // 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. |port| is a port to be merged with the remote peer's port, which
+ // it will provide via the same API.
+ //
+ // |connection_name| if non-empty guarantees that no other isolated
+ // connections exist in the calling process using the same name. This is
+ // useful for invitation endpoints that use a named server accepting multiple
+ // connections.
+ void ConnectIsolated(ConnectionParams connection_params,
+ const ports::PortRef& port,
+ base::StringPiece connection_name);
+
+ // Sets the mach port provider for this process.
+ void SetMachPortProvider(base::PortProvider* port_provider);
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ MachPortRelay* GetMachPortRelay();
+#endif
+
+ MojoHandle AddDispatcher(scoped_refptr<Dispatcher> 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<Dispatcher::DispatcherInTransit>& dispatchers,
+ MojoHandle* handles);
+
+ // Marks a set of handles as busy and acquires references to each of their
+ // dispatchers. The caller MUST eventually call ReleaseDispatchersForTransit()
+ // on the resulting |*dispatchers|. Note that |*dispatchers| contents are
+ // extended, not replaced, by this call.
+ MojoResult AcquireDispatchersForTransit(
+ const MojoHandle* handles,
+ size_t num_handles,
+ std::vector<Dispatcher::DispatcherInTransit>* dispatchers);
+
+ // Releases dispatchers previously acquired by
+ // |AcquireDispatchersForTransit()|. |in_transit| should be |true| if the
+ // caller has fully serialized every dispatcher in |dispatchers|, in which
+ // case this will close and remove their handles from the handle table.
+ //
+ // If |in_transit| is false, this simply unmarks the dispatchers as busy,
+ // making them available for general use once again.
+ void ReleaseDispatchersForTransit(
+ const std::vector<Dispatcher::DispatcherInTransit>& dispatchers,
+ bool in_transit);
+
+ // 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);
+
+ // ---------------------------------------------------------------------------
+
+ // The following methods are essentially implementations of the Mojo Core
+ // functions of the Mojo API, with the C interface translated to C++ by
+ // "mojo/core/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 CreateTrap(MojoTrapEventHandler handler,
+ const MojoCreateTrapOptions* options,
+ MojoHandle* trap_handle);
+ MojoResult AddTrigger(MojoHandle trap_handle,
+ MojoHandle handle,
+ MojoHandleSignals signals,
+ MojoTriggerCondition condition,
+ uintptr_t context,
+ const MojoAddTriggerOptions* options);
+ MojoResult RemoveTrigger(MojoHandle trap_handle,
+ uintptr_t context,
+ const MojoRemoveTriggerOptions* options);
+ MojoResult ArmTrap(MojoHandle trap_handle,
+ const MojoArmTrapOptions* options,
+ uint32_t* num_blocking_events,
+ MojoTrapEvent* blocking_events);
+ MojoResult CreateMessage(const MojoCreateMessageOptions* options,
+ MojoMessageHandle* message_handle);
+ MojoResult DestroyMessage(MojoMessageHandle message_handle);
+ MojoResult SerializeMessage(MojoMessageHandle message_handle,
+ const MojoSerializeMessageOptions* options);
+ MojoResult AppendMessageData(MojoMessageHandle message_handle,
+ uint32_t additional_payload_size,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ const MojoAppendMessageDataOptions* options,
+ void** buffer,
+ uint32_t* buffer_size);
+ MojoResult GetMessageData(MojoMessageHandle message_handle,
+ const MojoGetMessageDataOptions* options,
+ void** buffer,
+ uint32_t* num_bytes,
+ MojoHandle* handles,
+ uint32_t* num_handles);
+ MojoResult SetMessageContext(MojoMessageHandle message_handle,
+ uintptr_t context,
+ MojoMessageContextSerializer serializer,
+ MojoMessageContextDestructor destructor,
+ const MojoSetMessageContextOptions* options);
+ MojoResult GetMessageContext(MojoMessageHandle message_handle,
+ const MojoGetMessageContextOptions* options,
+ uintptr_t* context);
+
+ // 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,
+ MojoMessageHandle message_handle,
+ const MojoWriteMessageOptions* options);
+ MojoResult ReadMessage(MojoHandle message_pipe_handle,
+ const MojoReadMessageOptions* options,
+ MojoMessageHandle* message_handle);
+ MojoResult FuseMessagePipes(MojoHandle handle0,
+ MojoHandle handle1,
+ const MojoFuseMessagePipesOptions* options);
+ MojoResult NotifyBadMessage(MojoMessageHandle message_handle,
+ const char* error,
+ size_t error_num_bytes,
+ const MojoNotifyBadMessageOptions* options);
+
+ // 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,
+ const MojoWriteDataOptions* options);
+ MojoResult BeginWriteData(MojoHandle data_pipe_producer_handle,
+ const MojoBeginWriteDataOptions* options,
+ void** buffer,
+ uint32_t* buffer_num_bytes);
+ MojoResult EndWriteData(MojoHandle data_pipe_producer_handle,
+ uint32_t num_bytes_written,
+ const MojoEndWriteDataOptions* options);
+ MojoResult ReadData(MojoHandle data_pipe_consumer_handle,
+ const MojoReadDataOptions* options,
+ void* elements,
+ uint32_t* num_bytes);
+ MojoResult BeginReadData(MojoHandle data_pipe_consumer_handle,
+ const MojoBeginReadDataOptions* options,
+ const void** buffer,
+ uint32_t* buffer_num_bytes);
+ MojoResult EndReadData(MojoHandle data_pipe_consumer_handle,
+ uint32_t num_bytes_read,
+ const MojoEndReadDataOptions* options);
+
+ // These methods correspond to the API functions defined in
+ // "mojo/public/c/system/buffer.h":
+ MojoResult CreateSharedBuffer(uint64_t num_bytes,
+ const MojoCreateSharedBufferOptions* options,
+ 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,
+ const MojoMapBufferOptions* options,
+ void** buffer);
+ MojoResult UnmapBuffer(void* buffer);
+ MojoResult GetBufferInfo(MojoHandle buffer_handle,
+ const MojoGetBufferInfoOptions* options,
+ MojoSharedBufferInfo* info);
+
+ // These methods correspond to the API functions defined in
+ // "mojo/public/c/system/platform_handle.h".
+ MojoResult WrapPlatformHandle(const MojoPlatformHandle* platform_handle,
+ const MojoWrapPlatformHandleOptions* options,
+ MojoHandle* mojo_handle);
+ MojoResult UnwrapPlatformHandle(
+ MojoHandle mojo_handle,
+ const MojoUnwrapPlatformHandleOptions* options,
+ MojoPlatformHandle* platform_handle);
+ MojoResult WrapPlatformSharedMemoryRegion(
+ const MojoPlatformHandle* platform_handles,
+ uint32_t num_platform_handles,
+ uint64_t size,
+ const MojoSharedBufferGuid* guid,
+ MojoPlatformSharedMemoryRegionAccessMode access_mode,
+ const MojoWrapPlatformSharedMemoryRegionOptions* options,
+ MojoHandle* mojo_handle);
+ MojoResult UnwrapPlatformSharedMemoryRegion(
+ MojoHandle mojo_handle,
+ const MojoUnwrapPlatformSharedMemoryRegionOptions* options,
+ MojoPlatformHandle* platform_handles,
+ uint32_t* num_platform_handles,
+ uint64_t* size,
+ MojoSharedBufferGuid* guid,
+ MojoPlatformSharedMemoryRegionAccessMode* access_mode);
+
+ // Invitation API.
+ MojoResult CreateInvitation(const MojoCreateInvitationOptions* options,
+ MojoHandle* invitation_handle);
+ MojoResult AttachMessagePipeToInvitation(
+ MojoHandle invitation_handle,
+ const void* name,
+ uint32_t name_num_bytes,
+ const MojoAttachMessagePipeToInvitationOptions* options,
+ MojoHandle* message_pipe_handle);
+ MojoResult ExtractMessagePipeFromInvitation(
+ MojoHandle invitation_handle,
+ const void* name,
+ uint32_t name_num_bytes,
+ const MojoExtractMessagePipeFromInvitationOptions* options,
+ MojoHandle* message_pipe_handle);
+ MojoResult SendInvitation(
+ MojoHandle invitation_handle,
+ const MojoPlatformProcessHandle* process_handle,
+ const MojoInvitationTransportEndpoint* transport_endpoint,
+ MojoProcessErrorHandler error_handler,
+ uintptr_t error_handler_context,
+ const MojoSendInvitationOptions* options);
+ MojoResult AcceptInvitation(
+ const MojoInvitationTransportEndpoint* transport_endpoint,
+ const MojoAcceptInvitationOptions* options,
+ MojoHandle* invitation_handle);
+
+ // Quota API.
+ MojoResult SetQuota(MojoHandle handle,
+ MojoQuotaType type,
+ uint64_t limit,
+ const MojoSetQuotaOptions* options);
+ MojoResult QueryQuota(MojoHandle handle,
+ MojoQuotaType type,
+ const MojoQueryQuotaOptions* options,
+ uint64_t* limit,
+ uint64_t* usage);
+
+ void GetActiveHandlesForTest(std::vector<MojoHandle>* 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<NodeController> 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<NodeController> 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_;
+
+ std::unique_ptr<HandleTable> handles_;
+
+ base::Lock mapping_table_lock_; // Protects |mapping_table_|.
+
+ using MappingTable =
+ std::unordered_map<void*, std::unique_ptr<PlatformSharedMemoryMapping>>;
+ MappingTable mapping_table_;
+
+ DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_CORE_H_
diff --git a/mojo/core/core_test_base.cc b/mojo/core/core_test_base.cc
new file mode 100644
index 0000000000..3be60c5b73
--- /dev/null
+++ b/mojo/core/core_test_base.cc
@@ -0,0 +1,250 @@
+// 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/core/core_test_base.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/core/configuration.h"
+#include "mojo/core/core.h"
+#include "mojo/core/dispatcher.h"
+#include "mojo/core/user_message_impl.h"
+
+namespace mojo {
+namespace core {
+namespace test {
+
+namespace {
+
+// MockDispatcher --------------------------------------------------------------
+
+class MockDispatcher : public Dispatcher {
+ public:
+ static scoped_refptr<MockDispatcher> Create(
+ CoreTestBase::MockHandleInfo* info) {
+ return base::WrapRefCounted(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<ports::UserMessageEvent> message_event) override {
+ info_->IncrementWriteMessageCallCount();
+ return MOJO_RESULT_OK;
+ }
+
+ MojoResult ReadMessage(
+ std::unique_ptr<ports::UserMessageEvent>* message_event) override {
+ info_->IncrementReadMessageCallCount();
+ return MOJO_RESULT_OK;
+ }
+
+ MojoResult WriteData(const void* elements,
+ uint32_t* num_bytes,
+ const MojoWriteDataOptions& options) override {
+ info_->IncrementWriteDataCallCount();
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+
+ MojoResult BeginWriteData(void** buffer,
+ uint32_t* buffer_num_bytes) override {
+ info_->IncrementBeginWriteDataCallCount();
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+
+ MojoResult EndWriteData(uint32_t num_bytes_written) override {
+ info_->IncrementEndWriteDataCallCount();
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+
+ MojoResult ReadData(const MojoReadDataOptions& options,
+ void* elements,
+ uint32_t* num_bytes) override {
+ info_->IncrementReadDataCallCount();
+ return MOJO_RESULT_UNIMPLEMENTED;
+ }
+
+ MojoResult BeginReadData(const void** buffer,
+ uint32_t* buffer_num_bytes) 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<MockDispatcher> dispatcher = MockDispatcher::Create(info);
+ return core()->AddDispatcher(dispatcher);
+}
+
+Core* CoreTestBase::core() {
+ return Core::Get();
+}
+
+// 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 core
+} // namespace mojo
diff --git a/mojo/core/core_test_base.h b/mojo/core/core_test_base.h
new file mode 100644
index 0000000000..5d37d252c2
--- /dev/null
+++ b/mojo/core/core_test_base.h
@@ -0,0 +1,93 @@
+// 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_CORE_CORE_TEST_BASE_H_
+#define MOJO_CORE_CORE_TEST_BASE_H_
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "base/synchronization/lock.h"
+#include "mojo/core/test_utils.h"
+#include "mojo/public/c/system/types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace core {
+
+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 core
+} // namespace mojo
+
+#endif // MOJO_CORE_CORE_TEST_BASE_H_
diff --git a/mojo/core/core_unittest.cc b/mojo/core/core_unittest.cc
new file mode 100644
index 0000000000..8846051ad0
--- /dev/null
+++ b/mojo/core/core_unittest.cc
@@ -0,0 +1,459 @@
+// 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/core/core.h"
+
+#include <stdint.h>
+
+#include <limits>
+
+#include "base/bind.h"
+#include "build/build_config.h"
+#include "mojo/core/core_test_base.h"
+#include "mojo/core/test_utils.h"
+#include "mojo/public/cpp/system/wait.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+namespace mojo {
+namespace core {
+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 | MOJO_HANDLE_SIGNAL_PEER_REMOTE |
+ MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED;
+
+using CoreTest = test::CoreTestBase;
+
+TEST_F(CoreTest, GetTimeTicksNow) {
+ const MojoTimeTicks start = core()->GetTimeTicksNow();
+ ASSERT_NE(static_cast<MojoTimeTicks>(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<MojoTimeTicks>(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());
+ MojoMessageHandle message;
+ ASSERT_EQ(MOJO_RESULT_OK, core()->CreateMessage(nullptr, &message));
+ ASSERT_EQ(MOJO_RESULT_OK, core()->WriteMessage(h, message, nullptr));
+ ASSERT_EQ(1u, info.GetWriteMessageCallCount());
+
+ ASSERT_EQ(0u, info.GetReadMessageCallCount());
+ ASSERT_EQ(MOJO_RESULT_OK, core()->ReadMessage(h, nullptr, &message));
+ ASSERT_EQ(1u, info.GetReadMessageCallCount());
+ ASSERT_EQ(MOJO_RESULT_OK, core()->ReadMessage(h, nullptr, &message));
+ ASSERT_EQ(2u, info.GetReadMessageCallCount());
+
+ ASSERT_EQ(0u, info.GetWriteDataCallCount());
+ ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED,
+ core()->WriteData(h, nullptr, nullptr, nullptr));
+ ASSERT_EQ(1u, info.GetWriteDataCallCount());
+
+ ASSERT_EQ(0u, info.GetBeginWriteDataCallCount());
+ ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED,
+ core()->BeginWriteData(h, nullptr, nullptr, nullptr));
+ ASSERT_EQ(1u, info.GetBeginWriteDataCallCount());
+
+ ASSERT_EQ(0u, info.GetEndWriteDataCallCount());
+ ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED, core()->EndWriteData(h, 0, nullptr));
+ ASSERT_EQ(1u, info.GetEndWriteDataCallCount());
+
+ ASSERT_EQ(0u, info.GetReadDataCallCount());
+ ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED,
+ core()->ReadData(h, nullptr, nullptr, nullptr));
+ ASSERT_EQ(1u, info.GetReadDataCallCount());
+
+ ASSERT_EQ(0u, info.GetBeginReadDataCallCount());
+ ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED,
+ core()->BeginReadData(h, nullptr, nullptr, nullptr));
+ ASSERT_EQ(1u, info.GetBeginReadDataCallCount());
+
+ ASSERT_EQ(0u, info.GetEndReadDataCallCount());
+ ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED, core()->EndReadData(h, 0, nullptr));
+ 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).
+
+ // |WriteMessageNew()|:
+ // Only check arguments checked by |Core|, namely |handle|, |handles|, and
+ // |num_handles|.
+ ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->WriteMessage(MOJO_HANDLE_INVALID, 0, nullptr));
+
+ // |ReadMessageNew()|:
+ // 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));
+
+ MockHandleInfo info;
+ MojoHandle h = CreateMockHandle(&info);
+ ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->ReadMessage(h, nullptr, nullptr));
+ // Checked by |Core|, shouldn't go through to the dispatcher.
+ ASSERT_EQ(0u, info.GetReadMessageCallCount());
+ 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.
+ MojoMessageHandle message;
+ ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT,
+ core()->ReadMessage(h[0], nullptr, &message));
+
+ // Write to |h[1]|.
+ const uintptr_t kTestMessageContext = 123;
+ ASSERT_EQ(MOJO_RESULT_OK, core()->CreateMessage(nullptr, &message));
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->SetMessageContext(message, kTestMessageContext, nullptr,
+ nullptr, nullptr));
+ ASSERT_EQ(MOJO_RESULT_OK, core()->WriteMessage(h[1], message, nullptr));
+
+ // 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]|.
+ ASSERT_EQ(MOJO_RESULT_OK, core()->ReadMessage(h[0], nullptr, &message));
+ uintptr_t context;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->GetMessageContext(message, nullptr, &context));
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->SetMessageContext(message, 0, nullptr, nullptr, nullptr));
+ ASSERT_EQ(kTestMessageContext, context);
+ ASSERT_EQ(MOJO_RESULT_OK, core()->DestroyMessage(message));
+
+ // |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]|.
+ ASSERT_EQ(MOJO_RESULT_OK, core()->CreateMessage(nullptr, &message));
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->SetMessageContext(message, kTestMessageContext, nullptr,
+ nullptr, nullptr));
+ ASSERT_EQ(MOJO_RESULT_OK, core()->WriteMessage(h[0], message, nullptr));
+
+ // 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_FALSE(hss[1].satisfiable_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+
+ // 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_TRUE(hss[1].satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE);
+
+ // Discard a message from |h[1]|.
+ ASSERT_EQ(MOJO_RESULT_OK, core()->ReadMessage(h[1], nullptr, &message));
+ ASSERT_EQ(MOJO_RESULT_OK, core()->DestroyMessage(message));
+
+ // |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_FALSE(hss[1].satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE);
+
+ // Try writing to |h[1]|.
+ ASSERT_EQ(MOJO_RESULT_OK, core()->CreateMessage(nullptr, &message));
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->SetMessageContext(message, kTestMessageContext, nullptr,
+ nullptr, nullptr));
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ core()->WriteMessage(h[1], message, nullptr));
+
+ ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h[1]));
+}
+
+// Tests passing a message pipe handle.
+TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing1) {
+ MojoHandleSignalsState hss;
+ 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.
+ const uintptr_t kTestMessageContext = 42;
+ MojoMessageHandle message;
+ ASSERT_EQ(MOJO_RESULT_OK, core()->CreateMessage(nullptr, &message));
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->SetMessageContext(message, kTestMessageContext, nullptr,
+ nullptr, nullptr));
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->WriteMessage(h_passing[0], message, nullptr));
+ 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);
+ MojoMessageHandle message_handle;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->ReadMessage(h_passing[1], nullptr, &message_handle));
+ uintptr_t context;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->GetMessageContext(message_handle, nullptr, &context));
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->SetMessageContext(message, 0, nullptr, nullptr, nullptr));
+ ASSERT_EQ(kTestMessageContext, context);
+ ASSERT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message_handle));
+
+ ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h_passing[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h_passing[1]));
+}
+
+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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ 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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ 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, nullptr));
+ 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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ hss.satisfiable_signals);
+
+ // Peek one character.
+ elements[0] = -1;
+ elements[1] = -1;
+ num_bytes = 1u;
+ MojoReadDataOptions read_options;
+ read_options.struct_size = sizeof(read_options);
+ read_options.flags = MOJO_READ_DATA_FLAG_NONE | MOJO_READ_DATA_FLAG_PEEK;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->ReadData(ch, &read_options, elements, &num_bytes));
+ ASSERT_EQ('A', elements[0]);
+ ASSERT_EQ(-1, elements[1]);
+
+ // Read one character.
+ elements[0] = -1;
+ elements[1] = -1;
+ num_bytes = 1u;
+ read_options.flags = MOJO_READ_DATA_FLAG_NONE;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->ReadData(ch, &read_options, elements, &num_bytes));
+ 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, nullptr, &write_ptr, &num_bytes));
+ // 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, nullptr));
+
+ // Actually write the data, and complete it now.
+ static_cast<char*>(write_ptr)[0] = 'C';
+ static_cast<char*>(write_ptr)[1] = 'D';
+ static_cast<char*>(write_ptr)[2] = 'E';
+ ASSERT_EQ(MOJO_RESULT_OK, core()->EndWriteData(ph, 3u, nullptr));
+
+ // 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;
+ read_options.flags = MOJO_READ_DATA_FLAG_QUERY;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->ReadData(ch, &read_options, nullptr, &num_bytes));
+ ASSERT_GE(num_bytes, 1u);
+
+ // Try to query with peek. Should fail.
+ num_bytes = 0;
+ read_options.flags = MOJO_READ_DATA_FLAG_QUERY | MOJO_READ_DATA_FLAG_PEEK;
+ ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->ReadData(ch, &read_options, nullptr, &num_bytes));
+ ASSERT_EQ(0u, num_bytes);
+
+ // Try to discard ten characters, in all-or-none mode. Should fail.
+ num_bytes = 10;
+ read_options.flags =
+ MOJO_READ_DATA_FLAG_DISCARD | MOJO_READ_DATA_FLAG_ALL_OR_NONE;
+ ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE,
+ core()->ReadData(ch, &read_options, nullptr, &num_bytes));
+
+ // Try to discard two characters, in peek mode. Should fail.
+ num_bytes = 2;
+ read_options.flags = MOJO_READ_DATA_FLAG_DISCARD | MOJO_READ_DATA_FLAG_PEEK;
+ ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ core()->ReadData(ch, &read_options, nullptr, &num_bytes));
+
+ // Discard a character.
+ num_bytes = 1;
+ read_options.flags =
+ MOJO_READ_DATA_FLAG_DISCARD | MOJO_READ_DATA_FLAG_ALL_OR_NONE;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->ReadData(ch, &read_options, nullptr, &num_bytes));
+
+ // Ensure the 3 bytes were read.
+ ASSERT_EQ(MOJO_RESULT_OK,
+ mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_READABLE, &hss));
+
+ // Read the remaining three characters, in two-phase mode.
+ const void* read_ptr = nullptr;
+ num_bytes = 3;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ core()->BeginReadData(ch, nullptr, &read_ptr, &num_bytes));
+ // 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;
+ read_options.flags = MOJO_READ_DATA_FLAG_DISCARD;
+ ASSERT_EQ(MOJO_RESULT_BUSY,
+ core()->ReadData(ch, &read_options, nullptr, &num_bytes));
+
+ // Actually check our data and end the two-phase read.
+ ASSERT_EQ('C', static_cast<const char*>(read_ptr)[0]);
+ ASSERT_EQ('D', static_cast<const char*>(read_ptr)[1]);
+ ASSERT_EQ('E', static_cast<const char*>(read_ptr)[2]);
+ ASSERT_EQ(MOJO_RESULT_OK, core()->EndReadData(ch, 3u, nullptr));
+
+ // 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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ 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));
+}
+
+} // namespace
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/data_pipe_consumer_dispatcher.cc b/mojo/core/data_pipe_consumer_dispatcher.cc
new file mode 100644
index 0000000000..68e2f7e24d
--- /dev/null
+++ b/mojo/core/data_pipe_consumer_dispatcher.cc
@@ -0,0 +1,594 @@
+// 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/core/data_pipe_consumer_dispatcher.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <limits>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/core/core.h"
+#include "mojo/core/data_pipe_control_message.h"
+#include "mojo/core/node_controller.h"
+#include "mojo/core/platform_handle_utils.h"
+#include "mojo/core/request_context.h"
+#include "mojo/core/user_message_impl.h"
+#include "mojo/public/c/system/data_pipe.h"
+
+namespace mojo {
+namespace core {
+
+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;
+ uint64_t buffer_guid_high;
+ uint64_t buffer_guid_low;
+ 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<DataPipeConsumerDispatcher> dispatcher)
+ : dispatcher_(dispatcher) {}
+
+ private:
+ ~PortObserverThunk() override {}
+
+ // NodeController::PortObserver:
+ void OnPortStatusChanged() override { dispatcher_->OnPortStatusChanged(); }
+
+ scoped_refptr<DataPipeConsumerDispatcher> dispatcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(PortObserverThunk);
+};
+
+// static
+scoped_refptr<DataPipeConsumerDispatcher> DataPipeConsumerDispatcher::Create(
+ NodeController* node_controller,
+ const ports::PortRef& control_port,
+ base::UnsafeSharedMemoryRegion shared_ring_buffer,
+ const MojoCreateDataPipeOptions& options,
+ uint64_t pipe_id) {
+ scoped_refptr<DataPipeConsumerDispatcher> consumer =
+ new DataPipeConsumerDispatcher(node_controller, control_port,
+ std::move(shared_ring_buffer), options,
+ pipe_id);
+ base::AutoLock lock(consumer->lock_);
+ if (!consumer->InitializeNoLock())
+ return nullptr;
+ return consumer;
+}
+
+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(
+ const MojoReadDataOptions& options,
+ void* elements,
+ uint32_t* num_bytes) {
+ base::AutoLock lock(lock_);
+
+ if (!shared_ring_buffer_.IsValid() || 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 ((options.flags & MOJO_READ_DATA_FLAG_QUERY)) {
+ if ((options.flags & MOJO_READ_DATA_FLAG_PEEK) ||
+ (options.flags & MOJO_READ_DATA_FLAG_DISCARD))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ DCHECK(!(options.flags & MOJO_READ_DATA_FLAG_DISCARD)); // Handled above.
+ DVLOG_IF(2, elements) << "Query mode: ignoring non-null |elements|";
+ *num_bytes = static_cast<uint32_t>(bytes_available_);
+
+ if (had_new_data)
+ watchers_.NotifyState(GetHandleSignalsStateNoLock());
+ return MOJO_RESULT_OK;
+ }
+
+ bool discard = false;
+ if ((options.flags & MOJO_READ_DATA_FLAG_DISCARD)) {
+ // These flags are mutally exclusive.
+ if (options.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 = options.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) {
+ const uint8_t* data =
+ static_cast<const uint8_t*>(ring_buffer_mapping_.memory());
+ CHECK(data);
+
+ uint8_t* destination = static_cast<uint8_t*>(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 = !!(options.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) {
+ base::AutoLock lock(lock_);
+ if (!shared_ring_buffer_.IsValid() || 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 (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_.IsValid());
+ uint8_t* data = static_cast<uint8_t*>(ring_buffer_mapping_.memory());
+ 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_.IsValid());
+
+ 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<WatcherDispatcher>& 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<uint32_t>(sizeof(SerializedState));
+ *num_ports = 1;
+ *num_handles = 1;
+}
+
+bool DataPipeConsumerDispatcher::EndSerialize(
+ void* destination,
+ ports::PortName* ports,
+ PlatformHandle* platform_handles) {
+ SerializedState* state = static_cast<SerializedState*>(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;
+
+ auto region_handle =
+ base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
+ std::move(shared_ring_buffer_));
+ const base::UnguessableToken& guid = region_handle.GetGUID();
+ state->buffer_guid_high = guid.GetHighForSerialization();
+ state->buffer_guid_low = guid.GetLowForSerialization();
+
+ ports[0] = control_port_.name();
+
+ PlatformHandle handle;
+ PlatformHandle ignored_handle;
+ ExtractPlatformHandlesFromSharedMemoryRegionHandle(
+ region_handle.PassPlatformHandle(), &handle, &ignored_handle);
+ if (!handle.is_valid() || ignored_handle.is_valid())
+ return false;
+
+ platform_handles[0] = std::move(handle);
+ 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;
+ CloseNoLock();
+}
+
+void DataPipeConsumerDispatcher::CancelTransit() {
+ base::AutoLock lock(lock_);
+ DCHECK(in_transit_);
+ in_transit_ = false;
+ UpdateSignalsStateNoLock();
+}
+
+// static
+scoped_refptr<DataPipeConsumerDispatcher>
+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<const SerializedState*>(data);
+ if (!state->options.capacity_num_bytes || !state->options.element_num_bytes ||
+ state->options.capacity_num_bytes < state->options.element_num_bytes) {
+ return nullptr;
+ }
+
+ NodeController* node_controller = Core::Get()->GetNodeController();
+ ports::PortRef port;
+ if (node_controller->node()->GetPort(ports[0], &port) != ports::OK)
+ return nullptr;
+
+ auto region_handle = CreateSharedMemoryRegionHandleFromPlatformHandles(
+ std::move(handles[0]), PlatformHandle());
+ auto region = base::subtle::PlatformSharedMemoryRegion::Take(
+ std::move(region_handle),
+ base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
+ state->options.capacity_num_bytes,
+ base::UnguessableToken::Deserialize(state->buffer_guid_high,
+ state->buffer_guid_low));
+ auto ring_buffer =
+ base::UnsafeSharedMemoryRegion::Deserialize(std::move(region));
+ if (!ring_buffer.IsValid()) {
+ DLOG(ERROR) << "Failed to deserialize shared buffer handle.";
+ return nullptr;
+ }
+
+ scoped_refptr<DataPipeConsumerDispatcher> dispatcher =
+ new DataPipeConsumerDispatcher(node_controller, port,
+ std::move(ring_buffer), state->options,
+ 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;
+ if (!dispatcher->InitializeNoLock())
+ return nullptr;
+ dispatcher->UpdateSignalsStateNoLock();
+ }
+
+ return dispatcher;
+}
+
+DataPipeConsumerDispatcher::DataPipeConsumerDispatcher(
+ NodeController* node_controller,
+ const ports::PortRef& control_port,
+ base::UnsafeSharedMemoryRegion shared_ring_buffer,
+ const MojoCreateDataPipeOptions& options,
+ uint64_t pipe_id)
+ : options_(options),
+ node_controller_(node_controller),
+ control_port_(control_port),
+ pipe_id_(pipe_id),
+ watchers_(this),
+ shared_ring_buffer_(std::move(shared_ring_buffer)) {}
+
+DataPipeConsumerDispatcher::~DataPipeConsumerDispatcher() {
+ DCHECK(is_closed_ && !shared_ring_buffer_.IsValid() &&
+ !ring_buffer_mapping_.IsValid() && !in_transit_);
+}
+
+bool DataPipeConsumerDispatcher::InitializeNoLock() {
+ lock_.AssertAcquired();
+ if (!shared_ring_buffer_.IsValid())
+ return false;
+
+ DCHECK(!ring_buffer_mapping_.IsValid());
+ ring_buffer_mapping_ = shared_ring_buffer_.Map();
+ if (!ring_buffer_mapping_.IsValid()) {
+ DLOG(ERROR) << "Failed to map shared buffer.";
+ shared_ring_buffer_ = base::UnsafeSharedMemoryRegion();
+ return false;
+ }
+
+ base::AutoUnlock unlock(lock_);
+ node_controller_->SetPortObserver(
+ control_port_, base::MakeRefCounted<PortObserverThunk>(this));
+
+ return true;
+}
+
+MojoResult DataPipeConsumerDispatcher::CloseNoLock() {
+ lock_.AssertAcquired();
+ if (is_closed_ || in_transit_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ is_closed_ = true;
+ ring_buffer_mapping_ = base::WritableSharedMemoryMapping();
+ shared_ring_buffer_ = base::UnsafeSharedMemoryRegion();
+
+ 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_.IsValid() && 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_.IsValid()) {
+ rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE;
+ }
+
+ if (shared_ring_buffer_.IsValid()) {
+ 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;
+ } else {
+ rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_PEER_REMOTE;
+ if (peer_remote_)
+ rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_PEER_REMOTE;
+ }
+ 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();
+
+ const bool was_peer_closed = peer_closed_;
+ const bool was_peer_remote = peer_remote_;
+ size_t previous_bytes_available = bytes_available_;
+
+ ports::PortStatus port_status;
+ int rv = node_controller_->node()->GetStatus(control_port_, &port_status);
+ peer_remote_ = rv == ports::OK && port_status.peer_remote;
+ 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_) {
+ std::unique_ptr<ports::UserMessageEvent> message_event;
+ do {
+ int rv = node_controller_->node()->GetMessage(control_port_,
+ &message_event, nullptr);
+ if (rv != ports::OK)
+ peer_closed_ = true;
+ if (message_event) {
+ auto* message = message_event->GetMessage<UserMessageImpl>();
+ if (message->user_payload_size() < sizeof(DataPipeControlMessage)) {
+ peer_closed_ = true;
+ break;
+ }
+
+ const DataPipeControlMessage* m =
+ static_cast<const DataPipeControlMessage*>(message->user_payload());
+
+ if (m->command != DataPipeCommand::DATA_WAS_WRITTEN) {
+ DLOG(ERROR) << "Unexpected control message from producer.";
+ peer_closed_ = true;
+ break;
+ }
+
+ if (static_cast<size_t>(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_event);
+ }
+
+ 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 ||
+ peer_remote_ != was_peer_remote) {
+ watchers_.NotifyState(GetHandleSignalsStateNoLock());
+ }
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/data_pipe_consumer_dispatcher.h b/mojo/core/data_pipe_consumer_dispatcher.h
new file mode 100644
index 0000000000..982d3f055a
--- /dev/null
+++ b/mojo/core/data_pipe_consumer_dispatcher.h
@@ -0,0 +1,128 @@
+// 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_CORE_DATA_PIPE_CONSUMER_DISPATCHER_H_
+#define MOJO_CORE_DATA_PIPE_CONSUMER_DISPATCHER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "base/synchronization/lock.h"
+#include "mojo/core/dispatcher.h"
+#include "mojo/core/ports/port_ref.h"
+#include "mojo/core/system_impl_export.h"
+#include "mojo/core/watcher_set.h"
+
+namespace mojo {
+namespace core {
+
+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:
+ static scoped_refptr<DataPipeConsumerDispatcher> Create(
+ NodeController* node_controller,
+ const ports::PortRef& control_port,
+ base::UnsafeSharedMemoryRegion shared_ring_buffer,
+ const MojoCreateDataPipeOptions& options,
+ uint64_t pipe_id);
+
+ // Dispatcher:
+ Type GetType() const override;
+ MojoResult Close() override;
+ MojoResult ReadData(const MojoReadDataOptions& validated_options,
+ void* elements,
+ uint32_t* num_bytes) override;
+ MojoResult BeginReadData(const void** buffer,
+ uint32_t* buffer_num_bytes) override;
+ MojoResult EndReadData(uint32_t num_bytes_read) override;
+ HandleSignalsState GetHandleSignalsState() const override;
+ MojoResult AddWatcherRef(const scoped_refptr<WatcherDispatcher>& 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<DataPipeConsumerDispatcher> 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(NodeController* node_controller,
+ const ports::PortRef& control_port,
+ base::UnsafeSharedMemoryRegion shared_ring_buffer,
+ const MojoCreateDataPipeOptions& options,
+ uint64_t pipe_id);
+ ~DataPipeConsumerDispatcher() override;
+
+ bool 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_;
+
+ base::UnsafeSharedMemoryRegion shared_ring_buffer_;
+
+ // We don't really write to it, and it's safe because we're the only consumer
+ // of this buffer.
+ base::WritableSharedMemoryMapping ring_buffer_mapping_;
+
+ 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 peer_remote_ = 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 core
+} // namespace mojo
+
+#endif // MOJO_CORE_DATA_PIPE_CONSUMER_DISPATCHER_H_
diff --git a/mojo/core/data_pipe_control_message.cc b/mojo/core/data_pipe_control_message.cc
new file mode 100644
index 0000000000..cd782e5bc1
--- /dev/null
+++ b/mojo/core/data_pipe_control_message.cc
@@ -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.
+
+#include "mojo/core/data_pipe_control_message.h"
+
+#include "mojo/core/node_controller.h"
+#include "mojo/core/ports/event.h"
+#include "mojo/core/user_message_impl.h"
+
+namespace mojo {
+namespace core {
+
+void SendDataPipeControlMessage(NodeController* node_controller,
+ const ports::PortRef& port,
+ DataPipeCommand command,
+ uint32_t num_bytes) {
+ std::unique_ptr<ports::UserMessageEvent> event;
+ MojoResult result = UserMessageImpl::CreateEventForNewSerializedMessage(
+ sizeof(DataPipeControlMessage), nullptr, 0, &event);
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+ DCHECK(event);
+
+ DataPipeControlMessage* data = static_cast<DataPipeControlMessage*>(
+ event->GetMessage<UserMessageImpl>()->user_payload());
+ data->command = command;
+ data->num_bytes = num_bytes;
+
+ int rv = node_controller->SendUserMessage(port, std::move(event));
+ if (rv != ports::OK && rv != ports::ERROR_PORT_PEER_CLOSED) {
+ DLOG(ERROR) << "Unexpected failure sending data pipe control message: "
+ << rv;
+ }
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/data_pipe_control_message.h b/mojo/core/data_pipe_control_message.h
new file mode 100644
index 0000000000..3493e2b8b7
--- /dev/null
+++ b/mojo/core/data_pipe_control_message.h
@@ -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.
+
+#ifndef MOJO_CORE_DATA_PIPE_CONTROL_MESSAGE_H_
+#define MOJO_CORE_DATA_PIPE_CONTROL_MESSAGE_H_
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "mojo/core/ports/port_ref.h"
+#include "mojo/public/c/system/macros.h"
+
+namespace mojo {
+namespace core {
+
+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 core
+} // namespace mojo
+
+#endif // MOJO_CORE_DATA_PIPE_CONTROL_MESSAGE_H_
diff --git a/mojo/core/data_pipe_producer_dispatcher.cc b/mojo/core/data_pipe_producer_dispatcher.cc
new file mode 100644
index 0000000000..e6256f8f0d
--- /dev/null
+++ b/mojo/core/data_pipe_producer_dispatcher.cc
@@ -0,0 +1,538 @@
+// 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/core/data_pipe_producer_dispatcher.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/core/configuration.h"
+#include "mojo/core/core.h"
+#include "mojo/core/data_pipe_control_message.h"
+#include "mojo/core/node_controller.h"
+#include "mojo/core/platform_handle_utils.h"
+#include "mojo/core/request_context.h"
+#include "mojo/core/user_message_impl.h"
+#include "mojo/public/c/system/data_pipe.h"
+
+namespace mojo {
+namespace core {
+
+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;
+ uint64_t buffer_guid_high;
+ uint64_t buffer_guid_low;
+ 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<DataPipeProducerDispatcher> dispatcher)
+ : dispatcher_(dispatcher) {}
+
+ private:
+ ~PortObserverThunk() override {}
+
+ // NodeController::PortObserver:
+ void OnPortStatusChanged() override { dispatcher_->OnPortStatusChanged(); }
+
+ scoped_refptr<DataPipeProducerDispatcher> dispatcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(PortObserverThunk);
+};
+
+// static
+scoped_refptr<DataPipeProducerDispatcher> DataPipeProducerDispatcher::Create(
+ NodeController* node_controller,
+ const ports::PortRef& control_port,
+ base::UnsafeSharedMemoryRegion shared_ring_buffer,
+ const MojoCreateDataPipeOptions& options,
+ uint64_t pipe_id) {
+ scoped_refptr<DataPipeProducerDispatcher> producer =
+ new DataPipeProducerDispatcher(node_controller, control_port,
+ std::move(shared_ring_buffer), options,
+ pipe_id);
+ base::AutoLock lock(producer->lock_);
+ if (!producer->InitializeNoLock())
+ return nullptr;
+ return producer;
+}
+
+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,
+ const MojoWriteDataOptions& options) {
+ base::AutoLock lock(lock_);
+ if (!shared_ring_buffer_.IsValid() || 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 ((options.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_.IsValid());
+ uint8_t* data = static_cast<uint8_t*>(ring_buffer_mapping_.memory());
+ CHECK(data);
+
+ const uint8_t* source = static_cast<const uint8_t*>(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) {
+ base::AutoLock lock(lock_);
+ if (!shared_ring_buffer_.IsValid() || 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 (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_.IsValid());
+ uint8_t* data = static_cast<uint8_t*>(ring_buffer_mapping_.memory());
+ *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;
+
+ // 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<WatcherDispatcher>& 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<SerializedState*>(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;
+
+ auto region_handle =
+ base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
+ std::move(shared_ring_buffer_));
+ const base::UnguessableToken& guid = region_handle.GetGUID();
+ state->buffer_guid_high = guid.GetHighForSerialization();
+ state->buffer_guid_low = guid.GetLowForSerialization();
+
+ ports[0] = control_port_.name();
+
+ PlatformHandle handle;
+ PlatformHandle ignored_handle;
+ ExtractPlatformHandlesFromSharedMemoryRegionHandle(
+ region_handle.PassPlatformHandle(), &handle, &ignored_handle);
+ if (!handle.is_valid() || ignored_handle.is_valid())
+ return false;
+
+ platform_handles[0] = std::move(handle);
+ 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;
+ CloseNoLock();
+}
+
+void DataPipeProducerDispatcher::CancelTransit() {
+ base::AutoLock lock(lock_);
+ DCHECK(in_transit_);
+ in_transit_ = false;
+
+ HandleSignalsState state = GetHandleSignalsStateNoLock();
+ watchers_.NotifyState(state);
+}
+
+// static
+scoped_refptr<DataPipeProducerDispatcher>
+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<const SerializedState*>(data);
+ if (!state->options.capacity_num_bytes || !state->options.element_num_bytes ||
+ state->options.capacity_num_bytes < state->options.element_num_bytes) {
+ return nullptr;
+ }
+
+ NodeController* node_controller = Core::Get()->GetNodeController();
+ ports::PortRef port;
+ if (node_controller->node()->GetPort(ports[0], &port) != ports::OK)
+ return nullptr;
+
+ auto region_handle = CreateSharedMemoryRegionHandleFromPlatformHandles(
+ std::move(handles[0]), PlatformHandle());
+ auto region = base::subtle::PlatformSharedMemoryRegion::Take(
+ std::move(region_handle),
+ base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
+ state->options.capacity_num_bytes,
+ base::UnguessableToken::Deserialize(state->buffer_guid_high,
+ state->buffer_guid_low));
+ auto ring_buffer =
+ base::UnsafeSharedMemoryRegion::Deserialize(std::move(region));
+ if (!ring_buffer.IsValid()) {
+ DLOG(ERROR) << "Failed to deserialize shared buffer handle.";
+ return nullptr;
+ }
+
+ scoped_refptr<DataPipeProducerDispatcher> dispatcher =
+ new DataPipeProducerDispatcher(node_controller, port,
+ std::move(ring_buffer), state->options,
+ 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;
+ if (!dispatcher->InitializeNoLock())
+ return nullptr;
+ dispatcher->UpdateSignalsStateNoLock();
+ }
+
+ return dispatcher;
+}
+
+DataPipeProducerDispatcher::DataPipeProducerDispatcher(
+ NodeController* node_controller,
+ const ports::PortRef& control_port,
+ base::UnsafeSharedMemoryRegion shared_ring_buffer,
+ const MojoCreateDataPipeOptions& options,
+ uint64_t pipe_id)
+ : options_(options),
+ node_controller_(node_controller),
+ control_port_(control_port),
+ pipe_id_(pipe_id),
+ watchers_(this),
+ shared_ring_buffer_(std::move(shared_ring_buffer)),
+ available_capacity_(options_.capacity_num_bytes) {}
+
+DataPipeProducerDispatcher::~DataPipeProducerDispatcher() {
+ DCHECK(is_closed_ && !in_transit_ && !shared_ring_buffer_.IsValid() &&
+ !ring_buffer_mapping_.IsValid());
+}
+
+bool DataPipeProducerDispatcher::InitializeNoLock() {
+ lock_.AssertAcquired();
+ if (!shared_ring_buffer_.IsValid())
+ return false;
+
+ DCHECK(!ring_buffer_mapping_.IsValid());
+ ring_buffer_mapping_ = shared_ring_buffer_.Map();
+ if (!ring_buffer_mapping_.IsValid()) {
+ DLOG(ERROR) << "Failed to map shared buffer.";
+ shared_ring_buffer_ = base::UnsafeSharedMemoryRegion();
+ return false;
+ }
+
+ base::AutoUnlock unlock(lock_);
+ node_controller_->SetPortObserver(
+ control_port_, base::MakeRefCounted<PortObserverThunk>(this));
+
+ return true;
+}
+
+MojoResult DataPipeProducerDispatcher::CloseNoLock() {
+ lock_.AssertAcquired();
+ if (is_closed_ || in_transit_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ is_closed_ = true;
+ ring_buffer_mapping_ = base::WritableSharedMemoryMapping();
+ shared_ring_buffer_ = base::UnsafeSharedMemoryRegion();
+
+ 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_.IsValid() &&
+ available_capacity_ > 0)
+ rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_WRITABLE;
+ if (peer_remote_)
+ rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_PEER_REMOTE;
+ rv.satisfiable_signals |=
+ MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_REMOTE;
+ } 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();
+
+ const bool was_peer_closed = peer_closed_;
+ const bool was_peer_remote = peer_remote_;
+ size_t previous_capacity = available_capacity_;
+
+ ports::PortStatus port_status;
+ int rv = node_controller_->node()->GetStatus(control_port_, &port_status);
+ peer_remote_ = rv == ports::OK && port_status.peer_remote;
+ 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_) {
+ std::unique_ptr<ports::UserMessageEvent> message_event;
+ do {
+ int rv = node_controller_->node()->GetMessage(control_port_,
+ &message_event, nullptr);
+ if (rv != ports::OK)
+ peer_closed_ = true;
+ if (message_event) {
+ auto* message = message_event->GetMessage<UserMessageImpl>();
+ if (message->user_payload_size() < sizeof(DataPipeControlMessage)) {
+ peer_closed_ = true;
+ break;
+ }
+
+ const DataPipeControlMessage* m =
+ static_cast<const DataPipeControlMessage*>(message->user_payload());
+
+ if (m->command != DataPipeCommand::DATA_WAS_READ) {
+ DLOG(ERROR) << "Unexpected message from consumer.";
+ peer_closed_ = true;
+ break;
+ }
+
+ if (static_cast<size_t>(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_event);
+ }
+
+ if (peer_closed_ != was_peer_closed ||
+ available_capacity_ != previous_capacity ||
+ was_peer_remote != peer_remote_) {
+ watchers_.NotifyState(GetHandleSignalsStateNoLock());
+ }
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/data_pipe_producer_dispatcher.h b/mojo/core/data_pipe_producer_dispatcher.h
new file mode 100644
index 0000000000..15cd1c9b0d
--- /dev/null
+++ b/mojo/core/data_pipe_producer_dispatcher.h
@@ -0,0 +1,119 @@
+// 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_CORE_DATA_PIPE_PRODUCER_DISPATCHER_H_
+#define MOJO_CORE_DATA_PIPE_PRODUCER_DISPATCHER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "base/synchronization/lock.h"
+#include "mojo/core/dispatcher.h"
+#include "mojo/core/ports/port_ref.h"
+#include "mojo/core/system_impl_export.h"
+#include "mojo/core/watcher_set.h"
+
+namespace mojo {
+namespace core {
+
+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:
+ static scoped_refptr<DataPipeProducerDispatcher> Create(
+ NodeController* node_controller,
+ const ports::PortRef& control_port,
+ base::UnsafeSharedMemoryRegion shared_ring_buffer,
+ const MojoCreateDataPipeOptions& options,
+ uint64_t pipe_id);
+
+ // Dispatcher:
+ Type GetType() const override;
+ MojoResult Close() override;
+ MojoResult WriteData(const void* elements,
+ uint32_t* num_bytes,
+ const MojoWriteDataOptions& options) override;
+ MojoResult BeginWriteData(void** buffer, uint32_t* buffer_num_bytes) override;
+ MojoResult EndWriteData(uint32_t num_bytes_written) override;
+ HandleSignalsState GetHandleSignalsState() const override;
+ MojoResult AddWatcherRef(const scoped_refptr<WatcherDispatcher>& 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<DataPipeProducerDispatcher> 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(NodeController* node_controller,
+ const ports::PortRef& port,
+ base::UnsafeSharedMemoryRegion shared_ring_buffer,
+ const MojoCreateDataPipeOptions& options,
+ uint64_t pipe_id);
+ ~DataPipeProducerDispatcher() override;
+
+ bool InitializeNoLock();
+ MojoResult CloseNoLock();
+ HandleSignalsState GetHandleSignalsStateNoLock() const;
+ void NotifyWrite(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_;
+
+ base::UnsafeSharedMemoryRegion shared_ring_buffer_;
+ base::WritableSharedMemoryMapping ring_buffer_mapping_;
+
+ bool in_transit_ = false;
+ bool is_closed_ = false;
+ bool peer_closed_ = false;
+ bool peer_remote_ = 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 core
+} // namespace mojo
+
+#endif // MOJO_CORE_DATA_PIPE_PRODUCER_DISPATCHER_H_
diff --git a/mojo/core/data_pipe_unittest.cc b/mojo/core/data_pipe_unittest.cc
new file mode 100644
index 0000000000..b2f32a4c29
--- /dev/null
+++ b/mojo/core/data_pipe_unittest.cc
@@ -0,0 +1,2047 @@
+// 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 <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+
+#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 "build/build_config.h"
+#include "mojo/core/embedder/embedder.h"
+#include "mojo/core/test/mojo_test_base.h"
+#include "mojo/core/test_utils.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/message_pipe.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace core {
+namespace {
+
+const uint32_t kSizeOfOptions =
+ static_cast<uint32_t>(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 ReadEmptyMessageWithHandles(MojoHandle pipe,
+ MojoHandle* out_handles,
+ uint32_t num_handles) {
+ std::vector<uint8_t> bytes;
+ std::vector<ScopedHandle> handles;
+ MojoResult rv = ReadMessageRaw(MessagePipeHandle(pipe), &bytes, &handles,
+ MOJO_READ_MESSAGE_FLAG_NONE);
+ if (rv == MOJO_RESULT_OK) {
+ CHECK_EQ(0u, bytes.size());
+ CHECK_EQ(num_handles, handles.size());
+ for (size_t i = 0; i < num_handles; ++i)
+ out_handles[i] = handles[i].release().value();
+ }
+ return rv;
+ }
+
+ MojoResult Create(const MojoCreateDataPipeOptions* options) {
+ return MojoCreateDataPipe(options, &producer_, &consumer_);
+ }
+
+ MojoResult WriteData(const void* elements,
+ uint32_t* num_bytes,
+ bool all_or_none = false) {
+ MojoWriteDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = all_or_none ? MOJO_WRITE_DATA_FLAG_ALL_OR_NONE
+ : MOJO_WRITE_DATA_FLAG_NONE;
+ return MojoWriteData(producer_, elements, num_bytes, &options);
+ }
+
+ 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;
+
+ MojoReadDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = flags;
+ return MojoReadData(consumer_, &options, elements, num_bytes);
+ }
+
+ MojoResult QueryData(uint32_t* num_bytes) {
+ MojoReadDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_READ_DATA_FLAG_QUERY;
+ return MojoReadData(consumer_, &options, nullptr, num_bytes);
+ }
+
+ 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;
+ MojoReadDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = flags;
+ return MojoReadData(consumer_, &options, nullptr, num_bytes);
+ }
+
+ MojoResult BeginReadData(const void** elements, uint32_t* num_bytes) {
+ return MojoBeginReadData(consumer_, nullptr, elements, num_bytes);
+ }
+
+ MojoResult EndReadData(uint32_t num_bytes_read) {
+ return MojoEndReadData(consumer_, num_bytes_read, nullptr);
+ }
+
+ MojoResult BeginWriteData(void** elements, uint32_t* num_bytes) {
+ return MojoBeginWriteData(producer_, nullptr, elements, num_bytes);
+ }
+
+ MojoResult EndWriteData(uint32_t num_bytes_written) {
+ return MojoEndWriteData(producer_, num_bytes_written, nullptr);
+ }
+
+ 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_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(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<uint32_t>(arraysize(elements) * sizeof(elements[0]));
+
+ elements[0] = 123;
+ elements[1] = 456;
+ num_bytes = static_cast<uint32_t>(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<uint32_t>(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_FLAG_NONE, // |flags|.
+ 1, // |element_num_bytes|.
+ 1000}, // |capacity_num_bytes|.
+ // Nontrivial element size, non-default capacity.
+ {kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_FLAG_NONE, // |flags|.
+ 4, // |element_num_bytes|.
+ 4000}, // |capacity_num_bytes|.
+ // Nontrivial element size, default capacity.
+ {kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_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_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ 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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ hss.satisfiable_signals);
+
+ // Write two elements.
+ int32_t elements[2] = {123, 456};
+ uint32_t num_bytes = static_cast<uint32_t>(2u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true));
+ ASSERT_EQ(static_cast<uint32_t>(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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ hss.satisfiable_signals);
+
+ // Peek one element.
+ elements[0] = -1;
+ elements[1] = -1;
+ num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true, true));
+ ASSERT_EQ(static_cast<uint32_t>(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<uint32_t>(1u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true, false));
+ ASSERT_EQ(static_cast<uint32_t>(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<uint32_t>(3u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&buffer, &num_bytes));
+ EXPECT_TRUE(buffer);
+ ASSERT_GE(num_bytes, static_cast<uint32_t>(1u * sizeof(elements[0])));
+
+ static_cast<int32_t*>(buffer)[0] = 789;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ EndWriteData(static_cast<uint32_t>(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));
+ EXPECT_TRUE(read_buffer);
+ // The two-phase read should be able to read at least one element.
+ ASSERT_GE(num_bytes, static_cast<uint32_t>(1u * sizeof(elements[0])));
+ ASSERT_EQ(456, static_cast<const int32_t*>(read_buffer)[0]);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ EndReadData(static_cast<uint32_t>(1u * sizeof(elements[0]))));
+
+ // Write one element.
+ elements[0] = 123;
+ num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes));
+ ASSERT_EQ(static_cast<uint32_t>(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_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(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_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(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_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ hss.satisfiable_signals);
+
+ // Write two elements.
+ int32_t elements[2] = {123, 456};
+ uint32_t num_bytes = static_cast<uint32_t>(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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ hss.satisfiable_signals);
+
+ // Discard one element.
+ num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, DiscardData(&num_bytes, true));
+ ASSERT_EQ(static_cast<uint32_t>(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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ hss.satisfiable_signals);
+
+ // Peek one element.
+ elements[0] = -1;
+ elements[1] = -1;
+ num_bytes = static_cast<uint32_t>(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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ hss.satisfiable_signals);
+
+ // Read one element.
+ elements[0] = -1;
+ elements[1] = -1;
+ num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true));
+ ASSERT_EQ(static_cast<uint32_t>(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<uint32_t>(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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ 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<uint32_t>(1u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true));
+ ASSERT_EQ(static_cast<uint32_t>(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 create_options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(sizeof(int32_t)), // |element_num_bytes|.
+ 1000 * sizeof(int32_t) // |capacity_num_bytes|.
+ };
+ EXPECT_EQ(MOJO_RESULT_OK, Create(&create_options));
+
+ int32_t elements[2] = {123, 456};
+ uint32_t num_bytes = static_cast<uint32_t>(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);
+ MojoReadDataOptions read_options;
+ read_options.struct_size = sizeof(read_options);
+ read_options.flags = MOJO_READ_DATA_FLAG_ALL_OR_NONE;
+ EXPECT_EQ(
+ MOJO_RESULT_OUT_OF_RANGE,
+ MojoReadData(consumer_, &read_options, read_elements, &num_read_bytes));
+
+ // 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_options,
+ read_elements, &num_read_bytes));
+
+ // 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_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(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<uint32_t>(3u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&buffer, &num_bytes));
+ EXPECT_TRUE(buffer);
+ EXPECT_GE(num_bytes, static_cast<uint32_t>(3u * sizeof(elements[0])));
+ elements = static_cast<int32_t*>(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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ hss.satisfiable_signals);
+
+ // Read one element.
+ // Two should be available, but only read one.
+ const void* read_buffer = nullptr;
+ ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer, &num_bytes));
+ EXPECT_TRUE(read_buffer);
+ ASSERT_EQ(static_cast<uint32_t>(2u * sizeof(elements[0])), num_bytes);
+ const int32_t* read_elements = static_cast<const int32_t*>(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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ hss.satisfiable_signals);
+
+ // Read one element.
+ // Request three, but not in all-or-none mode.
+ read_buffer = nullptr;
+ num_bytes = static_cast<uint32_t>(3u * sizeof(elements[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer, &num_bytes));
+ EXPECT_TRUE(read_buffer);
+ ASSERT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes);
+ read_elements = static_cast<const int32_t*>(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_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ hss.satisfiable_signals);
+
+ uint32_t num_bytes = static_cast<uint32_t>(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<uint32_t>(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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ 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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ hss.satisfiable_signals);
+
+ static_cast<int32_t*>(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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ 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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ hss.satisfiable_signals);
+
+ // Start another two-phase write and check that it's readable even in the
+ // middle of it.
+ num_bytes = static_cast<uint32_t>(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<uint32_t>(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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ 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<uint32_t>(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<uint32_t>(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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ 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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ 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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ 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<int32_t>(i);
+}
+
+TEST_F(DataPipeTest, AllOrNone) {
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(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 | MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ 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<unsigned char>(i);
+
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ 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));
+ 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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+
+ // 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));
+ 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<uint32_t>(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<uint32_t>(sizeof(kTestData));
+
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_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));
+ 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<uint32_t>(sizeof(kTestData));
+
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ 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_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<uint32_t>(sizeof(kTestData));
+
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(sizeof(kTestData));
+
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ 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<uint32_t>(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_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(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<uint32_t>(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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ 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<uint32_t>(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<const int32_t*>(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<uint32_t>(sizeof(kTestData));
+
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ hss.satisfiable_signals);
+
+ // Check the data.
+ const void* read_buffer = nullptr;
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer, &num_bytes));
+ 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,
+ WriteMessageRaw(MessagePipeHandle(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));
+ ASSERT_EQ(MOJO_RESULT_OK, ReadEmptyMessageWithHandles(pipe1, &producer_, 1));
+
+ // Write more data.
+ const char kExtraData[] = "bye world";
+ const uint32_t kExtraDataSize = static_cast<uint32_t>(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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ hss.satisfiable_signals);
+
+ // Check the second write.
+ num_bytes = 0u;
+ ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer, &num_bytes));
+ 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_FLAG_NONE, // |flags|.
+ static_cast<uint32_t>(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,
+ WriteMessageRaw(MessagePipeHandle(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));
+ ASSERT_EQ(MOJO_RESULT_OK, ReadEmptyMessageWithHandles(pipe1, &consumer_, 1));
+
+ 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, nullptr);
+ if (result == MOJO_RESULT_OK) {
+ num_bytes -= write_bytes;
+ elements = static_cast<const uint8_t*>(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_TRUE(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+ EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ 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, nullptr, elements, &read_bytes);
+ if (result == MOJO_RESULT_OK) {
+ num_bytes -= read_bytes;
+ elements = static_cast<uint8_t*>(elements) + read_bytes;
+ if (num_bytes == 0) {
+ if (expect_empty) {
+ // Expect no more data.
+ test::Sleep(test::TinyDeadline());
+ MojoReadDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_READ_DATA_FLAG_QUERY;
+ MojoReadData(consumer, &options, nullptr, &num_bytes);
+ 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<uint32_t>(sizeof(kMultiprocessTestData));
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_FLAG_NONE, // |flags|.
+ 1, // |element_num_bytes|.
+ kMultiprocessCapacity // |capacity_num_bytes|.
+ };
+ ASSERT_EQ(MOJO_RESULT_OK, Create(&options));
+
+ RunTestClient("MultiprocessClient", [&](MojoHandle 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,
+ WriteMessageRaw(MessagePipeHandle(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,
+ WriteMessageRaw(MessagePipeHandle(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));
+ ASSERT_EQ(MOJO_RESULT_OK,
+ ReadEmptyMessageWithHandles(server_mp, &consumer_, 1));
+
+ // 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.
+ });
+}
+
+DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MultiprocessClient, DataPipeTest, client_mp) {
+ const uint32_t kTestDataSize =
+ static_cast<uint32_t>(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));
+ ASSERT_EQ(MOJO_RESULT_OK,
+ ReadEmptyMessageWithHandles(client_mp, &consumer, 1));
+
+ // 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,
+ WriteMessageRaw(MessagePipeHandle(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));
+ ASSERT_EQ(MOJO_RESULT_OK,
+ ReadEmptyMessageWithHandles(client_mp, &producer, 1));
+
+ // 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<uint32_t>(message.size());
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoWriteData(p, message.data(), &num_bytes, nullptr));
+ EXPECT_EQ(num_bytes, static_cast<uint32_t>(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<uint32_t>(expected_message.size());
+ std::vector<char> bytes(expected_message.size());
+ EXPECT_EQ(MOJO_RESULT_OK, MojoReadData(c, nullptr, bytes.data(), &num_bytes));
+ EXPECT_EQ(num_bytes, static_cast<uint32_t>(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));
+
+ RunTestClient("WriteAndCloseProducer", [&](MojoHandle producer_client) {
+ RunTestClient("ReadAndCloseConsumer", [&](MojoHandle consumer_client) {
+ const std::string kMessage = "Hello, world!";
+ WriteMessageWithHandles(producer_client, kMessage, &p, 1);
+ WriteMessageWithHandles(consumer_client, kMessage, &c, 1);
+
+ WriteMessage(consumer_client, "quit");
+ });
+
+ WriteMessage(producer_client, "quit");
+ });
+}
+
+DEFINE_TEST_CLIENT_TEST_WITH_PIPE(CreateAndWrite, DataPipeTest, h) {
+ const MojoCreateDataPipeOptions options = {
+ kSizeOfOptions, // |struct_size|.
+ MOJO_CREATE_DATA_PIPE_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<uint32_t>(kMessage.size());
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoWriteData(p, kMessage.data(), &num_bytes, nullptr));
+ EXPECT_EQ(num_bytes, static_cast<uint32_t>(kMessage.size()));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(p));
+
+ // Wait for a quit message.
+ EXPECT_EQ("quit", ReadMessage(h));
+}
+
+TEST_F(DataPipeTest, CreateInChild) {
+ RunTestClient("CreateAndWrite", [&](MojoHandle 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<uint32_t>(expected_message.size());
+ std::vector<char> bytes(expected_message.size());
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadData(c, nullptr, bytes.data(), &num_bytes));
+ EXPECT_EQ(num_bytes, static_cast<uint32_t>(bytes.size()));
+
+ std::string message(bytes.data(), bytes.size());
+ EXPECT_EQ(expected_message, message);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c));
+ WriteMessage(child, "quit");
+ });
+}
+
+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,
+ base::SequencedTaskRunnerHandle::Get());
+ SimpleWatcher consumer_watcher(FROM_HERE,
+ SimpleWatcher::ArmingPolicy::AUTOMATIC,
+ base::SequencedTaskRunnerHandle::Get());
+ 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, nullptr);
+ } 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], nullptr, &byte, &num_bytes);
+ } 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);
+
+ RunTestClient("DataPipeStatusChangeInTransitClient", [&](MojoHandle 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]);
+ });
+}
+
+#endif // !defined(OS_IOS)
+
+} // namespace
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/dispatcher.cc b/mojo/core/dispatcher.cc
new file mode 100644
index 0000000000..a110dbdc8e
--- /dev/null
+++ b/mojo/core/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/core/dispatcher.h"
+
+#include "base/logging.h"
+#include "mojo/core/configuration.h"
+#include "mojo/core/data_pipe_consumer_dispatcher.h"
+#include "mojo/core/data_pipe_producer_dispatcher.h"
+#include "mojo/core/message_pipe_dispatcher.h"
+#include "mojo/core/platform_handle_dispatcher.h"
+#include "mojo/core/ports/event.h"
+#include "mojo/core/shared_buffer_dispatcher.h"
+
+namespace mojo {
+namespace core {
+
+Dispatcher::DispatcherInTransit::DispatcherInTransit() {}
+
+Dispatcher::DispatcherInTransit::DispatcherInTransit(
+ const DispatcherInTransit& other) = default;
+
+Dispatcher::DispatcherInTransit::~DispatcherInTransit() {}
+
+MojoResult Dispatcher::WatchDispatcher(scoped_refptr<Dispatcher> dispatcher,
+ MojoHandleSignals signals,
+ MojoTriggerCondition condition,
+ 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_blocking_events,
+ MojoTrapEvent* blocking_events) {
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::WriteMessage(
+ std::unique_ptr<ports::UserMessageEvent> message) {
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::ReadMessage(
+ std::unique_ptr<ports::UserMessageEvent>* message) {
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::DuplicateBufferHandle(
+ const MojoDuplicateBufferHandleOptions* options,
+ scoped_refptr<Dispatcher>* new_dispatcher) {
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::MapBuffer(
+ uint64_t offset,
+ uint64_t num_bytes,
+ std::unique_ptr<PlatformSharedMemoryMapping>* mapping) {
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::GetBufferInfo(MojoSharedBufferInfo* info) {
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::ReadData(const MojoReadDataOptions& options,
+ void* elements,
+ uint32_t* num_bytes) {
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::BeginReadData(const void** buffer,
+ uint32_t* buffer_num_bytes) {
+ 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,
+ const MojoWriteDataOptions& options) {
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::BeginWriteData(void** buffer,
+ uint32_t* buffer_num_bytes) {
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::EndWriteData(uint32_t num_bytes_written) {
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::AttachMessagePipe(base::StringPiece name,
+ ports::PortRef remote_peer_port) {
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::ExtractMessagePipe(base::StringPiece name,
+ MojoHandle* message_pipe_handle) {
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::SetQuota(MojoQuotaType type, uint64_t limit) {
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+MojoResult Dispatcher::QueryQuota(MojoQuotaType type,
+ uint64_t* limit,
+ uint64_t* usage) {
+ return MOJO_RESULT_INVALID_ARGUMENT;
+}
+
+HandleSignalsState Dispatcher::GetHandleSignalsState() const {
+ return HandleSignalsState();
+}
+
+MojoResult Dispatcher::AddWatcherRef(
+ const scoped_refptr<WatcherDispatcher>& 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> 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 core
+} // namespace mojo
diff --git a/mojo/core/dispatcher.h b/mojo/core/dispatcher.h
new file mode 100644
index 0000000000..58cd7a7a39
--- /dev/null
+++ b/mojo/core/dispatcher.h
@@ -0,0 +1,233 @@
+// 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_CORE_DISPATCHER_H_
+#define MOJO_CORE_DISPATCHER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <ostream>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_piece.h"
+#include "base/synchronization/lock.h"
+#include "mojo/core/handle_signals_state.h"
+#include "mojo/core/ports/name.h"
+#include "mojo/core/ports/port_ref.h"
+#include "mojo/core/system_impl_export.h"
+#include "mojo/core/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/quota.h"
+#include "mojo/public/c/system/trap.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/platform/platform_handle.h"
+
+namespace mojo {
+namespace core {
+
+namespace ports {
+class UserMessageEvent;
+}
+
+class Dispatcher;
+class PlatformSharedMemoryMapping;
+
+using DispatcherVector = std::vector<scoped_refptr<Dispatcher>>;
+
+// A |Dispatcher| implements Mojo EDK calls that are associated with a
+// particular MojoHandle.
+class MOJO_SYSTEM_IMPL_EXPORT Dispatcher
+ : public base::RefCountedThreadSafe<Dispatcher> {
+ public:
+ struct DispatcherInTransit {
+ DispatcherInTransit();
+ DispatcherInTransit(const DispatcherInTransit& other);
+ ~DispatcherInTransit();
+
+ scoped_refptr<Dispatcher> dispatcher;
+ MojoHandle local_handle;
+ };
+
+ enum class Type {
+ UNKNOWN = 0,
+ MESSAGE_PIPE,
+ DATA_PIPE_PRODUCER,
+ DATA_PIPE_CONSUMER,
+ SHARED_BUFFER,
+ WATCHER,
+ INVITATION,
+
+ // "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> dispatcher,
+ MojoHandleSignals signals,
+ MojoTriggerCondition condition,
+ uintptr_t context);
+ virtual MojoResult CancelWatch(uintptr_t context);
+ virtual MojoResult Arm(uint32_t* num_blocking_events,
+ MojoTrapEvent* blocking_events);
+
+ ///////////// Message pipe API /////////////
+
+ virtual MojoResult WriteMessage(
+ std::unique_ptr<ports::UserMessageEvent> message);
+
+ virtual MojoResult ReadMessage(
+ std::unique_ptr<ports::UserMessageEvent>* message);
+
+ ///////////// 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<Dispatcher>* new_dispatcher);
+
+ virtual MojoResult MapBuffer(
+ uint64_t offset,
+ uint64_t num_bytes,
+ std::unique_ptr<PlatformSharedMemoryMapping>* mapping);
+
+ virtual MojoResult GetBufferInfo(MojoSharedBufferInfo* info);
+
+ ///////////// Data pipe consumer API /////////////
+
+ virtual MojoResult ReadData(const MojoReadDataOptions& options,
+ void* elements,
+ uint32_t* num_bytes);
+
+ virtual MojoResult BeginReadData(const void** buffer,
+ uint32_t* buffer_num_bytes);
+
+ virtual MojoResult EndReadData(uint32_t num_bytes_read);
+
+ ///////////// Data pipe producer API /////////////
+
+ virtual MojoResult WriteData(const void* elements,
+ uint32_t* num_bytes,
+ const MojoWriteDataOptions& options);
+
+ virtual MojoResult BeginWriteData(void** buffer, uint32_t* buffer_num_bytes);
+
+ virtual MojoResult EndWriteData(uint32_t num_bytes_written);
+
+ // Invitation API.
+ virtual MojoResult AttachMessagePipe(base::StringPiece name,
+ ports::PortRef remote_peer_port);
+ virtual MojoResult ExtractMessagePipe(base::StringPiece name,
+ MojoHandle* message_pipe_handle);
+
+ // Quota API.
+ virtual MojoResult SetQuota(MojoQuotaType type, uint64_t limit);
+ virtual MojoResult QueryQuota(MojoQuotaType type,
+ uint64_t* limit,
+ uint64_t* usage);
+
+ ///////////// 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<WatcherDispatcher>& 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<Dispatcher> Deserialize(Type type,
+ const void* bytes,
+ size_t num_bytes,
+ const ports::PortName* ports,
+ size_t num_ports,
+ PlatformHandle* platform_handles,
+ size_t platform_handle_count);
+
+ protected:
+ friend class base::RefCountedThreadSafe<Dispatcher>;
+
+ 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<int>(type);
+}
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_DISPATCHER_H_
diff --git a/mojo/core/embedder/BUILD.gn b/mojo/core/embedder/BUILD.gn
new file mode 100644
index 0000000000..47f1c39016
--- /dev/null
+++ b/mojo/core/embedder/BUILD.gn
@@ -0,0 +1,29 @@
+# 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.
+
+component("embedder") {
+ output_name = "mojo_core_embedder"
+
+ public = [
+ "configuration.h",
+ "embedder.h",
+ "scoped_ipc_support.h",
+ ]
+
+ sources = [
+ "embedder.cc",
+ "scoped_ipc_support.cc",
+ ]
+
+ defines = [ "IS_MOJO_CORE_EMBEDDER_IMPL" ]
+
+ public_deps = [
+ "//base",
+ ]
+
+ deps = [
+ "//mojo/core:embedder_internal",
+ "//mojo/public/c/system",
+ ]
+}
diff --git a/mojo/core/embedder/README.md b/mojo/core/embedder/README.md
new file mode 100644
index 0000000000..7183a6374d
--- /dev/null
+++ b/mojo/core/embedder/README.md
@@ -0,0 +1,86 @@
+# Mojo Core Embedder API
+This document is a subset of the [Mojo documentation](/mojo/README.md).
+
+[TOC]
+
+## Overview
+
+The Mojo Core Embedder API enables process to initialize and use Mojo for IPC,
+using an implementation of Mojo Core that is statically linked into the
+application. See the note about dynamic linking
+[here](/mojo/README.md#Mojo-Core) for more information about an alternative
+approach to Mojo Core initialization.
+
+**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 Embedder API. Most processes defined in the
+Chrome repo today already fully initialize Mojo Core so that all other public
+Mojo APIs just work out of the box.
+
+## Basic Initialization
+
+As an embedder, initializing Mojo Core requires a single call to
+`mojo::core::Init`:
+
+```
+#include "mojo/core/embedder/embedder.h"
+
+int main(int argc, char** argv) {
+ mojo::core::Init();
+
+ // Now you can create message pipes, write messages, etc
+
+ return 0;
+}
+```
+
+This enables local API calls to work, so message pipes *etc* can be created and
+used. In some cases (particuarly many unit testing scenarios) this is
+sufficient, but to support any actual multiprocess communication (e.g. sending
+or accepting Mojo invitations), a second IPC initialization step is required.
+
+## IPC Initialization
+
+Internal Mojo IPC implementation requires a background `TaskRunner` on which it
+can watch for inbound I/O from other processes. This is configured using a
+`ScopedIPCSupport` object, which keeps IPC support alive through the extent of
+its lifetime.
+
+Typically an application will create a dedicated background thread and give its
+`TaskRunner` to Mojo. Note that in Chromium, we use the existing "IO thread" in
+the browser process and content child processes. In general, any thread used
+for Mojo IPC support must be running a `base::MessageLoop::TYPE_IO` loop.
+
+```
+#include "base/threading/thread.h"
+#include "mojo/core/embedder/embedder.h"
+#include "mojo/core/embedder/scoped_ipc_support.h"
+
+int main(int argc, char** argv) {
+ mojo::core::Init();
+
+ base::Thread ipc_thread("ipc!");
+ ipc_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
+
+ // As long as this object is alive, all Mojo API surface relevant to IPC
+ // connections is usable, and message pipes which span a process boundary will
+ // continue to function.
+ mojo::core::ScopedIPCSupport ipc_support(
+ ipc_thread.task_runner(),
+ mojo::core::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
+
+Once IPC is initialized, you can bootstrap connections to other processes by
+using the public
+[Invitations API](/mojo/public/cpp/system/README.md#Invitations).
diff --git a/mojo/core/embedder/configuration.h b/mojo/core/embedder/configuration.h
new file mode 100644
index 0000000000..f4621dcd17
--- /dev/null
+++ b/mojo/core/embedder/configuration.h
@@ -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.
+
+#ifndef MOJO_CORE_EMBEDDER_CONFIGURATION_H_
+#define MOJO_CORE_EMBEDDER_CONFIGURATION_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+namespace mojo {
+namespace core {
+
+// A set of configuration parameters that the Mojo system uses internally. The
+// configuration used can be overridden from the default by passing a
+// Configuration into |mojo::core::Init()|. See embedder.h.
+//
+// NOTE: Please ensure that this type remains a simple aggregate of POD fields.
+struct Configuration {
+ // Indicates whether this process should act as the sole broker process within
+ // its graph of interconnected Mojo-embedder processes. This setting is only
+ // relevant in multiprocess environments.
+ bool is_broker_process = false;
+
+ // If |true|, this process will always attempt to allocate shared memory
+ // directly rather than synchronously delegating to a broker process where
+ // applicable.
+ //
+ // This is useful to set in processes which are not acting as the broker but
+ // which are otherwise sufficiently privileged to allocate named shared memory
+ // objects.
+ bool force_direct_shared_memory_allocation = false;
+
+ // Maximum number of active memory mappings.
+ size_t max_mapping_table_size = 1000000;
+
+ // Maximum data size of messages sent over message pipes, in bytes.
+ size_t max_message_num_bytes = 256 * 1024 * 1024;
+
+ // Maximum size of a single shared memory segment, in bytes.
+ size_t max_shared_memory_num_bytes = 1024 * 1024 * 1024;
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_EMBEDDER_CONFIGURATION_H_
diff --git a/mojo/core/embedder/embedder.cc b/mojo/core/embedder/embedder.cc
new file mode 100644
index 0000000000..4044de083e
--- /dev/null
+++ b/mojo/core/embedder/embedder.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/core/embedder/embedder.h"
+
+#include <stdint.h>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/task_runner.h"
+#include "build/build_config.h"
+#include "mojo/core/configuration.h"
+#include "mojo/core/core.h"
+#include "mojo/core/entrypoints.h"
+#include "mojo/core/node_controller.h"
+#include "mojo/public/c/system/thunks.h"
+
+namespace mojo {
+namespace core {
+
+void Init(const Configuration& configuration) {
+ internal::g_configuration = configuration;
+ InitializeCore();
+ MojoEmbedderSetSystemThunks(&GetSystemThunks());
+}
+
+void Init() {
+ Init(Configuration());
+}
+
+void SetDefaultProcessErrorCallback(const ProcessErrorCallback& callback) {
+ Core::Get()->SetDefaultProcessErrorCallback(callback);
+}
+
+scoped_refptr<base::TaskRunner> GetIOTaskRunner() {
+ return Core::Get()->GetNodeController()->io_task_runner();
+}
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+void SetMachPortProvider(base::PortProvider* port_provider) {
+ DCHECK(port_provider);
+ Core::Get()->SetMachPortProvider(port_provider);
+}
+#endif
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/embedder/embedder.h b/mojo/core/embedder/embedder.h
new file mode 100644
index 0000000000..feb7ca1be4
--- /dev/null
+++ b/mojo/core/embedder/embedder.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_CORE_EMBEDDER_EMBEDDER_H_
+#define MOJO_CORE_EMBEDDER_EMBEDDER_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/component_export.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 "build/build_config.h"
+#include "mojo/core/embedder/configuration.h"
+
+namespace base {
+class PortProvider;
+}
+
+namespace mojo {
+namespace core {
+
+using ProcessErrorCallback = base::Callback<void(const std::string& error)>;
+
+// Basic configuration/initialization ------------------------------------------
+
+// Must be called first, or just after setting configuration parameters, to
+// initialize the (global, singleton) system state. There is no corresponding
+// shutdown operation: once the embedder is initialized, public Mojo C API calls
+// remain available for the remainder of the process's lifetime.
+COMPONENT_EXPORT(MOJO_CORE_EMBEDDER)
+void Init(const Configuration& configuration);
+
+// Like above but uses a default Configuration.
+COMPONENT_EXPORT(MOJO_CORE_EMBEDDER) void Init();
+
+// Sets a default callback to invoke when an internal error is reported but
+// cannot be associated with a specific child process. Calling this is optional.
+COMPONENT_EXPORT(MOJO_CORE_EMBEDDER)
+void SetDefaultProcessErrorCallback(const ProcessErrorCallback& callback);
+
+// Initialialization/shutdown for interprocess communication (IPC) -------------
+
+// Retrieves the TaskRunner used for IPC I/O, as set by ScopedIPCSupport.
+COMPONENT_EXPORT(MOJO_CORE_EMBEDDER)
+scoped_refptr<base::TaskRunner> GetIOTaskRunner();
+
+#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.
+//
+// If called at all, this must be called while a ScopedIPCSupport exists.
+COMPONENT_EXPORT(MOJO_CORE_EMBEDDER)
+void SetMachPortProvider(base::PortProvider* port_provider);
+#endif
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_EMBEDDER_EMBEDDER_H_
diff --git a/mojo/core/embedder/process_error_callback.h b/mojo/core/embedder/process_error_callback.h
new file mode 100644
index 0000000000..f60ce89990
--- /dev/null
+++ b/mojo/core/embedder/process_error_callback.h
@@ -0,0 +1,21 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_CORE_EMBEDDER_PROCESS_ERROR_CALLBACK_H_
+#define MOJO_CORE_EMBEDDER_PROCESS_ERROR_CALLBACK_H_
+
+#include <string>
+
+#include "base/callback.h"
+
+namespace mojo {
+namespace core {
+
+using ProcessErrorCallback =
+ base::RepeatingCallback<void(const std::string& error)>;
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_EMBEDDER_PROCESS_ERROR_CALLBACK_H_
diff --git a/mojo/core/embedder/scoped_ipc_support.cc b/mojo/core/embedder/scoped_ipc_support.cc
new file mode 100644
index 0000000000..71174f535e
--- /dev/null
+++ b/mojo/core/embedder/scoped_ipc_support.cc
@@ -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.
+
+#include "mojo/core/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/core/core.h"
+
+namespace mojo {
+namespace core {
+
+namespace {
+
+void ShutdownIPCSupport(const base::Closure& callback) {
+ Core::Get()->RequestShutdown(callback);
+}
+
+} // namespace
+
+ScopedIPCSupport::ScopedIPCSupport(
+ scoped_refptr<base::TaskRunner> io_thread_task_runner,
+ ShutdownPolicy shutdown_policy)
+ : shutdown_policy_(shutdown_policy) {
+ Core::Get()->SetIOTaskRunner(io_thread_task_runner);
+}
+
+ScopedIPCSupport::~ScopedIPCSupport() {
+ if (shutdown_policy_ == ShutdownPolicy::FAST) {
+ ShutdownIPCSupport(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::ScopedAllowBaseSyncPrimitives allow_io;
+ shutdown_event.Wait();
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/embedder/scoped_ipc_support.h b/mojo/core/embedder/scoped_ipc_support.h
new file mode 100644
index 0000000000..8afcd00d54
--- /dev/null
+++ b/mojo/core/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_CORE_EMBEDDER_SCOPED_IPC_SUPPORT_H_
+#define MOJO_CORE_EMBEDDER_SCOPED_IPC_SUPPORT_H_
+
+#include "base/component_export.h"
+#include "base/memory/ref_counted.h"
+
+namespace base {
+class TaskRunner;
+}
+
+namespace mojo {
+namespace core {
+
+// A simple class that initialized Mojo IPC support on construction and shuts
+// down IPC support on destruction, optionally blocking the destructor on clean
+// IPC shutdown completion.
+class COMPONENT_EXPORT(MOJO_CORE_EMBEDDER) 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/core/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<base::TaskRunner> io_thread_task_runner,
+ ShutdownPolicy shutdown_policy);
+ ~ScopedIPCSupport();
+
+ private:
+ const ShutdownPolicy shutdown_policy_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedIPCSupport);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_EMBEDDER_SCOPED_IPC_SUPPORT_H_
diff --git a/mojo/core/embedder_unittest.cc b/mojo/core/embedder_unittest.cc
new file mode 100644
index 0000000000..f336f4c8f7
--- /dev/null
+++ b/mojo/core/embedder_unittest.cc
@@ -0,0 +1,429 @@
+// 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/core/embedder/embedder.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <utility>
+
+#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/read_only_shared_memory_region.h"
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "base/memory/writable_shared_memory_region.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "base/rand_util.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/test_timeouts.h"
+#include "build/build_config.h"
+#include "mojo/core/core.h"
+#include "mojo/core/shared_buffer_dispatcher.h"
+#include "mojo/core/test/mojo_test_base.h"
+#include "mojo/core/test_utils.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/platform_handle.h"
+#include "mojo/public/cpp/system/wait.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace core {
+namespace {
+
+template <typename T>
+MojoResult CreateSharedBufferFromRegion(T&& region, MojoHandle* handle) {
+ scoped_refptr<SharedBufferDispatcher> buffer;
+ MojoResult result =
+ SharedBufferDispatcher::CreateFromPlatformSharedMemoryRegion(
+ T::TakeHandleForSerialization(std::move(region)), &buffer);
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ *handle = Core::Get()->AddDispatcher(std::move(buffer));
+ return MOJO_RESULT_OK;
+}
+
+template <typename T>
+MojoResult ExtractRegionFromSharedBuffer(MojoHandle handle, T* region) {
+ scoped_refptr<Dispatcher> dispatcher =
+ Core::Get()->GetAndRemoveDispatcher(handle);
+ if (!dispatcher || dispatcher->GetType() != Dispatcher::Type::SHARED_BUFFER)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ auto* buffer = static_cast<SharedBufferDispatcher*>(dispatcher.get());
+ *region = T::Deserialize(buffer->PassPlatformSharedMemoryRegion());
+ return MOJO_RESULT_OK;
+}
+
+// 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));
+}
+
+// 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) {
+ RunTestClient("MultiprocessChannelsClient", [&](MojoHandle 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_FALSE(state.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_FALSE(state.satisfiable_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+
+ ASSERT_EQ(MOJO_RESULT_OK, MojoClose(mp2));
+ });
+}
+
+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_FALSE(state.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_FALSE(state.satisfiable_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+ ASSERT_EQ(MOJO_RESULT_OK, MojoClose(mp1));
+}
+
+TEST_F(EmbedderTest, MultiprocessBaseSharedMemory) {
+ RunTestClient("MultiprocessSharedMemoryClient", [&](MojoHandle server_mp) {
+ // 1. Create a shared memory region and wrap it as a Mojo object.
+ auto shared_memory = base::UnsafeSharedMemoryRegion::Create(123);
+ ASSERT_TRUE(shared_memory.IsValid());
+ MojoHandle sb1;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ CreateSharedBufferFromRegion(shared_memory.Duplicate(), &sb1));
+
+ // 2. Map |sb1| and write something into it.
+ char* buffer = nullptr;
+ ASSERT_EQ(MOJO_RESULT_OK, MojoMapBuffer(sb1, 0, 123, nullptr,
+ reinterpret_cast<void**>(&buffer)));
+ 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, nullptr, &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.
+ auto mapping = shared_memory.Map();
+ ASSERT_TRUE(mapping.IsValid());
+ EXPECT_EQ(kByeWorld, std::string(static_cast<char*>(mapping.memory())));
+
+ ASSERT_EQ(MOJO_RESULT_OK, MojoClose(sb1));
+ });
+}
+
+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, nullptr,
+ reinterpret_cast<void**>(&buffer)));
+ 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::UnsafeSharedMemoryRegion shared_memory;
+ ASSERT_EQ(MOJO_RESULT_OK, ExtractRegionFromSharedBuffer(sb1, &shared_memory));
+ auto mapping = shared_memory.Map();
+ ASSERT_TRUE(mapping.IsValid());
+ EXPECT_NE(buffer, mapping.memory());
+ EXPECT_EQ(kByeWorld, std::string(static_cast<char*>(mapping.memory())));
+
+ // 6. Close |sb1|. Should fail because |ExtractRegionFromSharedBuffer()|
+ // should have closed the handle.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(sb1));
+}
+
+#if defined(OS_MACOSX)
+
+enum class HandleType {
+ POSIX,
+ MACH,
+};
+
+const HandleType kTestHandleTypes[] = {
+ HandleType::MACH, 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;
+ RunTestClient("MultiprocessMixMachAndFdsClient", [&](MojoHandle server_mp) {
+ // 1. Create fds or Mach objects and mojo handles from them.
+ MojoHandle platform_handles[base::size(kTestHandleTypes)];
+ for (size_t i = 0; i < base::size(kTestHandleTypes); i++) {
+ const auto type = kTestHandleTypes[i];
+ PlatformHandle 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 = PlatformHandle(base::ScopedFD(file.TakePlatformFile()));
+ ASSERT_TRUE(scoped_handle.is_valid_fd());
+ } else {
+ auto shared_memory = base::UnsafeSharedMemoryRegion::Create(kShmSize);
+ ASSERT_TRUE(shared_memory.IsValid());
+ auto shm_handle =
+ base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
+ std::move(shared_memory))
+ .PassPlatformHandle();
+ scoped_handle = PlatformHandle(std::move(shm_handle));
+ ASSERT_TRUE(scoped_handle.is_valid_mach_port());
+ }
+ platform_handles[i] =
+ WrapPlatformHandle(std::move(scoped_handle)).release().value();
+ }
+
+ // 2. Send all the handles to the child.
+ WriteMessageWithHandles(server_mp, "hello", platform_handles,
+ base::size(kTestHandleTypes));
+
+ // 3. Read a message from |server_mp|.
+ EXPECT_EQ("bye", ReadMessage(server_mp));
+ });
+}
+
+DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MultiprocessMixMachAndFdsClient,
+ EmbedderTest,
+ client_mp) {
+ const int kNumHandles = base::size(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];
+ PlatformHandle scoped_handle =
+ UnwrapPlatformHandle(ScopedHandle(Handle(platform_handles[i])));
+ if (type == HandleType::POSIX) {
+ EXPECT_TRUE(scoped_handle.is_valid_fd());
+ } else {
+ EXPECT_TRUE(scoped_handle.is_valid_mach_port());
+ }
+ }
+
+ // 3. Say bye!
+ WriteMessage(client_mp, "bye");
+}
+
+#endif // defined(OS_MACOSX)
+
+#endif // !defined(OS_IOS)
+
+} // namespace
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/entrypoints.cc b/mojo/core/entrypoints.cc
new file mode 100644
index 0000000000..42e7417192
--- /dev/null
+++ b/mojo/core/entrypoints.cc
@@ -0,0 +1,414 @@
+// 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/core/entrypoints.h"
+
+#include <stdint.h>
+
+#include "mojo/core/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"
+#include "mojo/public/c/system/quota.h"
+
+namespace {
+
+mojo::core::Core* g_core;
+
+extern "C" {
+
+MojoResult MojoInitializeImpl(const struct MojoInitializeOptions* options) {
+ NOTREACHED() << "Do not call MojoInitialize() as an EDK embedder!";
+ return MOJO_RESULT_OK;
+}
+
+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 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,
+ MojoMessageHandle message,
+ const MojoWriteMessageOptions* options) {
+ return g_core->WriteMessage(message_pipe_handle, message, options);
+}
+
+MojoResult MojoReadMessageImpl(MojoHandle message_pipe_handle,
+ const MojoReadMessageOptions* options,
+ MojoMessageHandle* message) {
+ return g_core->ReadMessage(message_pipe_handle, options, message);
+}
+
+MojoResult MojoFuseMessagePipesImpl(
+ MojoHandle handle0,
+ MojoHandle handle1,
+ const MojoFuseMessagePipesOptions* options) {
+ return g_core->FuseMessagePipes(handle0, handle1, options);
+}
+
+MojoResult MojoCreateMessageImpl(const MojoCreateMessageOptions* options,
+ MojoMessageHandle* message) {
+ return g_core->CreateMessage(options, message);
+}
+
+MojoResult MojoDestroyMessageImpl(MojoMessageHandle message) {
+ return g_core->DestroyMessage(message);
+}
+
+MojoResult MojoSerializeMessageImpl(
+ MojoMessageHandle message,
+ const MojoSerializeMessageOptions* options) {
+ return g_core->SerializeMessage(message, options);
+}
+
+MojoResult MojoAppendMessageDataImpl(
+ MojoMessageHandle message,
+ uint32_t additional_payload_size,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ const MojoAppendMessageDataOptions* options,
+ void** buffer,
+ uint32_t* buffer_size) {
+ return g_core->AppendMessageData(message, additional_payload_size, handles,
+ num_handles, options, buffer, buffer_size);
+}
+
+MojoResult MojoGetMessageDataImpl(MojoMessageHandle message,
+ const MojoGetMessageDataOptions* options,
+ void** buffer,
+ uint32_t* num_bytes,
+ MojoHandle* handles,
+ uint32_t* num_handles) {
+ return g_core->GetMessageData(message, options, buffer, num_bytes, handles,
+ num_handles);
+}
+
+MojoResult MojoSetMessageContextImpl(
+ MojoMessageHandle message,
+ uintptr_t context,
+ MojoMessageContextSerializer serializer,
+ MojoMessageContextDestructor destructor,
+ const MojoSetMessageContextOptions* options) {
+ return g_core->SetMessageContext(message, context, serializer, destructor,
+ options);
+}
+
+MojoResult MojoGetMessageContextImpl(
+ MojoMessageHandle message,
+ const MojoGetMessageContextOptions* options,
+ uintptr_t* context) {
+ return g_core->GetMessageContext(message, options, context);
+}
+
+MojoResult MojoNotifyBadMessageImpl(
+ MojoMessageHandle message,
+ const char* error,
+ uint32_t error_num_bytes,
+ const MojoNotifyBadMessageOptions* options) {
+ return g_core->NotifyBadMessage(message, error, error_num_bytes, options);
+}
+
+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,
+ const MojoWriteDataOptions* options) {
+ return g_core->WriteData(data_pipe_producer_handle, elements, num_elements,
+ options);
+}
+
+MojoResult MojoBeginWriteDataImpl(MojoHandle data_pipe_producer_handle,
+ const MojoBeginWriteDataOptions* options,
+ void** buffer,
+ uint32_t* buffer_num_elements) {
+ return g_core->BeginWriteData(data_pipe_producer_handle, options, buffer,
+ buffer_num_elements);
+}
+
+MojoResult MojoEndWriteDataImpl(MojoHandle data_pipe_producer_handle,
+ uint32_t num_elements_written,
+ const MojoEndWriteDataOptions* options) {
+ return g_core->EndWriteData(data_pipe_producer_handle, num_elements_written,
+ options);
+}
+
+MojoResult MojoReadDataImpl(MojoHandle data_pipe_consumer_handle,
+ const MojoReadDataOptions* options,
+ void* elements,
+ uint32_t* num_elements) {
+ return g_core->ReadData(data_pipe_consumer_handle, options, elements,
+ num_elements);
+}
+
+MojoResult MojoBeginReadDataImpl(MojoHandle data_pipe_consumer_handle,
+ const MojoBeginReadDataOptions* options,
+ const void** buffer,
+ uint32_t* buffer_num_elements) {
+ return g_core->BeginReadData(data_pipe_consumer_handle, options, buffer,
+ buffer_num_elements);
+}
+
+MojoResult MojoEndReadDataImpl(MojoHandle data_pipe_consumer_handle,
+ uint32_t num_elements_read,
+ const MojoEndReadDataOptions* options) {
+ return g_core->EndReadData(data_pipe_consumer_handle, num_elements_read,
+ options);
+}
+
+MojoResult MojoCreateSharedBufferImpl(
+ uint64_t num_bytes,
+ const MojoCreateSharedBufferOptions* options,
+ MojoHandle* shared_buffer_handle) {
+ return g_core->CreateSharedBuffer(num_bytes, options, shared_buffer_handle);
+}
+
+MojoResult MojoDuplicateBufferHandleImpl(
+ MojoHandle buffer_handle,
+ const 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,
+ const MojoMapBufferOptions* options,
+ void** buffer) {
+ return g_core->MapBuffer(buffer_handle, offset, num_bytes, options, buffer);
+}
+
+MojoResult MojoUnmapBufferImpl(void* buffer) {
+ return g_core->UnmapBuffer(buffer);
+}
+
+MojoResult MojoGetBufferInfoImpl(MojoHandle buffer_handle,
+ const MojoGetBufferInfoOptions* options,
+ MojoSharedBufferInfo* info) {
+ return g_core->GetBufferInfo(buffer_handle, options, info);
+}
+
+MojoResult MojoCreateTrapImpl(MojoTrapEventHandler handler,
+ const MojoCreateTrapOptions* options,
+ MojoHandle* trap_handle) {
+ return g_core->CreateTrap(handler, options, trap_handle);
+}
+
+MojoResult MojoAddTriggerImpl(MojoHandle trap_handle,
+ MojoHandle handle,
+ MojoHandleSignals signals,
+ MojoTriggerCondition condition,
+ uintptr_t context,
+ const MojoAddTriggerOptions* options) {
+ return g_core->AddTrigger(trap_handle, handle, signals, condition, context,
+ options);
+}
+
+MojoResult MojoRemoveTriggerImpl(MojoHandle trap_handle,
+ uintptr_t context,
+ const MojoRemoveTriggerOptions* options) {
+ return g_core->RemoveTrigger(trap_handle, context, options);
+}
+
+MojoResult MojoArmTrapImpl(MojoHandle trap_handle,
+ const MojoArmTrapOptions* options,
+ uint32_t* num_blocking_events,
+ MojoTrapEvent* blocking_events) {
+ return g_core->ArmTrap(trap_handle, options, num_blocking_events,
+ blocking_events);
+}
+
+MojoResult MojoWrapPlatformHandleImpl(
+ const MojoPlatformHandle* platform_handle,
+ const MojoWrapPlatformHandleOptions* options,
+ MojoHandle* mojo_handle) {
+ return g_core->WrapPlatformHandle(platform_handle, options, mojo_handle);
+}
+
+MojoResult MojoUnwrapPlatformHandleImpl(
+ MojoHandle mojo_handle,
+ const MojoUnwrapPlatformHandleOptions* options,
+ MojoPlatformHandle* platform_handle) {
+ return g_core->UnwrapPlatformHandle(mojo_handle, options, platform_handle);
+}
+
+MojoResult MojoWrapPlatformSharedMemoryRegionImpl(
+ const MojoPlatformHandle* platform_handles,
+ uint32_t num_platform_handles,
+ uint64_t num_bytes,
+ const MojoSharedBufferGuid* guid,
+ MojoPlatformSharedMemoryRegionAccessMode access_mode,
+ const MojoWrapPlatformSharedMemoryRegionOptions* options,
+ MojoHandle* mojo_handle) {
+ return g_core->WrapPlatformSharedMemoryRegion(
+ platform_handles, num_platform_handles, num_bytes, guid, access_mode,
+ options, mojo_handle);
+}
+
+MojoResult MojoUnwrapPlatformSharedMemoryRegionImpl(
+ MojoHandle mojo_handle,
+ const MojoUnwrapPlatformSharedMemoryRegionOptions* options,
+ MojoPlatformHandle* platform_handles,
+ uint32_t* num_platform_handles,
+ uint64_t* num_bytes,
+ MojoSharedBufferGuid* guid,
+ MojoPlatformSharedMemoryRegionAccessMode* access_mode) {
+ return g_core->UnwrapPlatformSharedMemoryRegion(
+ mojo_handle, options, platform_handles, num_platform_handles, num_bytes,
+ guid, access_mode);
+}
+
+MojoResult MojoCreateInvitationImpl(const MojoCreateInvitationOptions* options,
+ MojoHandle* invitation_handle) {
+ return g_core->CreateInvitation(options, invitation_handle);
+}
+
+MojoResult MojoAttachMessagePipeToInvitationImpl(
+ MojoHandle invitation_handle,
+ const void* name,
+ uint32_t name_num_bytes,
+ const MojoAttachMessagePipeToInvitationOptions* options,
+ MojoHandle* message_pipe_handle) {
+ return g_core->AttachMessagePipeToInvitation(
+ invitation_handle, name, name_num_bytes, options, message_pipe_handle);
+}
+
+MojoResult MojoExtractMessagePipeFromInvitationImpl(
+ MojoHandle invitation_handle,
+ const void* name,
+ uint32_t name_num_bytes,
+ const MojoExtractMessagePipeFromInvitationOptions* options,
+ MojoHandle* message_pipe_handle) {
+ return g_core->ExtractMessagePipeFromInvitation(
+ invitation_handle, name, name_num_bytes, options, message_pipe_handle);
+}
+
+MojoResult MojoSendInvitationImpl(
+ MojoHandle invitation_handle,
+ const MojoPlatformProcessHandle* process_handle,
+ const MojoInvitationTransportEndpoint* transport_endpoint,
+ MojoProcessErrorHandler error_handler,
+ uintptr_t error_handler_context,
+ const MojoSendInvitationOptions* options) {
+ return g_core->SendInvitation(invitation_handle, process_handle,
+ transport_endpoint, error_handler,
+ error_handler_context, options);
+}
+
+MojoResult MojoAcceptInvitationImpl(
+ const MojoInvitationTransportEndpoint* transport_endpoint,
+ const MojoAcceptInvitationOptions* options,
+ MojoHandle* invitation_handle) {
+ return g_core->AcceptInvitation(transport_endpoint, options,
+ invitation_handle);
+}
+
+MojoResult MojoSetQuotaImpl(MojoHandle handle,
+ MojoQuotaType type,
+ uint64_t limit,
+ const MojoSetQuotaOptions* options) {
+ return g_core->SetQuota(handle, type, limit, options);
+}
+
+MojoResult MojoQueryQuotaImpl(MojoHandle handle,
+ MojoQuotaType type,
+ const MojoQueryQuotaOptions* options,
+ uint64_t* current_limit,
+ uint64_t* current_usage) {
+ return g_core->QueryQuota(handle, type, options, current_limit,
+ current_usage);
+}
+
+} // extern "C"
+
+MojoSystemThunks g_thunks = {sizeof(MojoSystemThunks),
+ MojoInitializeImpl,
+ MojoGetTimeTicksNowImpl,
+ MojoCloseImpl,
+ MojoQueryHandleSignalsStateImpl,
+ MojoCreateMessagePipeImpl,
+ MojoWriteMessageImpl,
+ MojoReadMessageImpl,
+ MojoFuseMessagePipesImpl,
+ MojoCreateMessageImpl,
+ MojoDestroyMessageImpl,
+ MojoSerializeMessageImpl,
+ MojoAppendMessageDataImpl,
+ MojoGetMessageDataImpl,
+ MojoSetMessageContextImpl,
+ MojoGetMessageContextImpl,
+ MojoNotifyBadMessageImpl,
+ MojoCreateDataPipeImpl,
+ MojoWriteDataImpl,
+ MojoBeginWriteDataImpl,
+ MojoEndWriteDataImpl,
+ MojoReadDataImpl,
+ MojoBeginReadDataImpl,
+ MojoEndReadDataImpl,
+ MojoCreateSharedBufferImpl,
+ MojoDuplicateBufferHandleImpl,
+ MojoMapBufferImpl,
+ MojoUnmapBufferImpl,
+ MojoGetBufferInfoImpl,
+ MojoCreateTrapImpl,
+ MojoAddTriggerImpl,
+ MojoRemoveTriggerImpl,
+ MojoArmTrapImpl,
+ MojoWrapPlatformHandleImpl,
+ MojoUnwrapPlatformHandleImpl,
+ MojoWrapPlatformSharedMemoryRegionImpl,
+ MojoUnwrapPlatformSharedMemoryRegionImpl,
+ MojoCreateInvitationImpl,
+ MojoAttachMessagePipeToInvitationImpl,
+ MojoExtractMessagePipeFromInvitationImpl,
+ MojoSendInvitationImpl,
+ MojoAcceptInvitationImpl,
+ MojoSetQuotaImpl,
+ MojoQueryQuotaImpl};
+
+} // namespace
+
+namespace mojo {
+namespace core {
+
+// static
+Core* Core::Get() {
+ return g_core;
+}
+
+void InitializeCore() {
+ g_core = new Core;
+}
+
+const MojoSystemThunks& GetSystemThunks() {
+ return g_thunks;
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/entrypoints.h b/mojo/core/entrypoints.h
new file mode 100644
index 0000000000..e18366b74d
--- /dev/null
+++ b/mojo/core/entrypoints.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_CORE_ENTRYPOINTS_H_
+#define MOJO_CORE_ENTRYPOINTS_H_
+
+#include "mojo/core/system_impl_export.h"
+#include "mojo/public/c/system/thunks.h"
+
+namespace mojo {
+namespace core {
+
+// Initializes the global Core object.
+MOJO_SYSTEM_IMPL_EXPORT void InitializeCore();
+
+// Returns 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 const MojoSystemThunks& GetSystemThunks();
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_ENTRYPOINTS_H_
diff --git a/mojo/core/export_only_thunks_api.lst b/mojo/core/export_only_thunks_api.lst
new file mode 100644
index 0000000000..11e436bd3a
--- /dev/null
+++ b/mojo/core/export_only_thunks_api.lst
@@ -0,0 +1,12 @@
+# 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.
+
+# Linker script that ensures only MojoGetSystemThunks is exposed from the
+# mojo_core library.
+{
+ global:
+ MojoGetSystemThunks;
+ local:
+ *;
+};
diff --git a/mojo/core/handle_signals_state.h b/mojo/core/handle_signals_state.h
new file mode 100644
index 0000000000..7cd7d8db69
--- /dev/null
+++ b/mojo/core/handle_signals_state.h
@@ -0,0 +1,111 @@
+// 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_CORE_HANDLE_SIGNALS_STATE_H_
+#define MOJO_CORE_HANDLE_SIGNALS_STATE_H_
+
+#include "mojo/core/system_impl_export.h"
+#include "mojo/public/c/system/types.h"
+
+namespace mojo {
+namespace core {
+
+// A convenience wrapper around the MojoHandleSignalsState struct.
+//
+// NOTE: This is duplicated in the public C++ SDK to avoid circular
+// dependencies between the EDK and the public SDK.
+struct MOJO_SYSTEM_IMPL_EXPORT HandleSignalsState final
+ : public MojoHandleSignalsState {
+ HandleSignalsState() {
+ satisfied_signals = MOJO_HANDLE_SIGNAL_NONE;
+ satisfiable_signals = MOJO_HANDLE_SIGNAL_NONE;
+ }
+
+ HandleSignalsState(MojoHandleSignals satisfied,
+ MojoHandleSignals satisfiable) {
+ satisfied_signals = satisfied;
+ satisfiable_signals = satisfiable;
+ }
+
+ bool operator==(const HandleSignalsState& other) const {
+ return satisfied_signals == other.satisfied_signals &&
+ satisfiable_signals == other.satisfiable_signals;
+ }
+
+ // TODO(rockot): Remove this in favor of operator==.
+ bool equals(const HandleSignalsState& other) const {
+ return satisfied_signals == other.satisfied_signals &&
+ satisfiable_signals == other.satisfiable_signals;
+ }
+
+ bool satisfies_any(MojoHandleSignals signals) const {
+ return !!(satisfied_signals & signals);
+ }
+
+ bool satisfies_all(MojoHandleSignals signals) const {
+ return (satisfied_signals & signals) == signals;
+ }
+
+ bool can_satisfy_any(MojoHandleSignals signals) const {
+ return !!(satisfiable_signals & signals);
+ }
+
+ // The handle is currently readable. May apply to a message pipe handle or
+ // data pipe consumer handle.
+ bool readable() const { return satisfies_any(MOJO_HANDLE_SIGNAL_READABLE); }
+
+ // The handle is currently writable. May apply to a message pipe handle or
+ // data pipe producer handle.
+ bool writable() const { return satisfies_any(MOJO_HANDLE_SIGNAL_WRITABLE); }
+
+ // The handle's peer is closed. May apply to any message pipe or data pipe
+ // handle.
+ bool peer_closed() const {
+ return satisfies_any(MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+ }
+
+ // The handle's peer exists in a remote execution context (e.g. in another
+ // process.)
+ bool peer_remote() const {
+ return satisfies_any(MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+ }
+
+ // Indicates whether the handle has exceeded some quota limit.
+ bool quota_exceeded() const {
+ return satisfies_any(MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);
+ }
+
+ // The handle will never be |readable()| again.
+ bool never_readable() const {
+ return !can_satisfy_any(MOJO_HANDLE_SIGNAL_READABLE);
+ }
+
+ // The handle will never be |writable()| again.
+ bool never_writable() const {
+ return !can_satisfy_any(MOJO_HANDLE_SIGNAL_WRITABLE);
+ }
+
+ // The handle can never indicate |peer_closed()|. Never true for message pipe
+ // or data pipe handles (they can always signal peer closure), but always true
+ // for other types of handles (they have no peer.)
+ bool never_peer_closed() const {
+ return !can_satisfy_any(MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+ }
+
+ // The handle will never indicate |peer_remote()| again. True iff the peer is
+ // known to be closed.
+ bool never_peer_remote() const {
+ return !can_satisfy_any(MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+ }
+
+ // (Copy and assignment allowed.)
+};
+
+static_assert(sizeof(HandleSignalsState) == sizeof(MojoHandleSignalsState),
+ "HandleSignalsState should add no overhead");
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_HANDLE_SIGNALS_STATE_H_
diff --git a/mojo/core/handle_table.cc b/mojo/core/handle_table.cc
new file mode 100644
index 0000000000..e039c7108d
--- /dev/null
+++ b/mojo/core/handle_table.cc
@@ -0,0 +1,204 @@
+// 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/core/handle_table.h"
+
+#include <stdint.h>
+
+#include <limits>
+
+// #include "base/trace_event/memory_dump_manager.h"
+
+namespace mojo {
+namespace core {
+
+namespace {
+
+// const char* GetNameForDispatcherType(Dispatcher::Type type) {
+// switch (type) {
+// case Dispatcher::Type::UNKNOWN:
+// return "unknown";
+// case Dispatcher::Type::MESSAGE_PIPE:
+// return "message_pipe";
+// case Dispatcher::Type::DATA_PIPE_PRODUCER:
+// return "data_pipe_producer";
+// case Dispatcher::Type::DATA_PIPE_CONSUMER:
+// return "data_pipe_consumer";
+// case Dispatcher::Type::SHARED_BUFFER:
+// return "shared_buffer";
+// case Dispatcher::Type::WATCHER:
+// return "watcher";
+// case Dispatcher::Type::PLATFORM_HANDLE:
+// return "platform_handle";
+// case Dispatcher::Type::INVITATION:
+// return "invitation";
+// }
+// NOTREACHED();
+// return "unknown";
+// }
+
+} // namespace
+
+HandleTable::HandleTable() {}
+
+HandleTable::~HandleTable() {}
+
+base::Lock& HandleTable::GetLock() {
+ return lock_;
+}
+
+MojoHandle HandleTable::AddDispatcher(scoped_refptr<Dispatcher> 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<Dispatcher::DispatcherInTransit>& 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<uint32_t>::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 = MOJO_HANDLE_INVALID;
+ if (dispatchers[i].dispatcher) {
+ 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<Dispatcher> 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>* 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,
+ size_t num_handles,
+ std::vector<Dispatcher::DispatcherInTransit>* dispatchers) {
+ dispatchers->reserve(dispatchers->size() + 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<Dispatcher::DispatcherInTransit>& 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<Dispatcher::DispatcherInTransit>& 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<MojoHandle>* handles) {
+ handles->clear();
+ for (const auto& entry : handles_)
+ handles->push_back(entry.first);
+}
+
+// MemoryDumpProvider implementation.
+// bool HandleTable::OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
+// base::trace_event::ProcessMemoryDump* pmd) {
+// // Create entries for all relevant dispatcher types to ensure they are present
+// // in the final dump.
+// std::map<Dispatcher::Type, int> handle_count;
+// handle_count[Dispatcher::Type::MESSAGE_PIPE];
+// handle_count[Dispatcher::Type::DATA_PIPE_PRODUCER];
+// handle_count[Dispatcher::Type::DATA_PIPE_CONSUMER];
+// handle_count[Dispatcher::Type::SHARED_BUFFER];
+// handle_count[Dispatcher::Type::WATCHER];
+// handle_count[Dispatcher::Type::PLATFORM_HANDLE];
+// handle_count[Dispatcher::Type::INVITATION];
+
+// // Count the number of each dispatcher type.
+// {
+// base::AutoLock lock(GetLock());
+// for (const auto& entry : handles_) {
+// ++handle_count[entry.second.dispatcher->GetType()];
+// }
+// }
+
+// for (const auto& entry : handle_count) {
+// base::trace_event::MemoryAllocatorDump* inner_dump =
+// pmd->CreateAllocatorDump(std::string("mojo/") +
+// GetNameForDispatcherType(entry.first));
+// inner_dump->AddScalar(
+// base::trace_event::MemoryAllocatorDump::kNameObjectCount,
+// base::trace_event::MemoryAllocatorDump::kUnitsObjects, entry.second);
+// }
+
+// return true;
+// }
+
+HandleTable::Entry::Entry() {}
+
+HandleTable::Entry::Entry(scoped_refptr<Dispatcher> dispatcher)
+ : dispatcher(std::move(dispatcher)) {}
+
+HandleTable::Entry::Entry(const Entry& other) = default;
+
+HandleTable::Entry::~Entry() {}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/handle_table.h b/mojo/core/handle_table.h
new file mode 100644
index 0000000000..2e0edf72e3
--- /dev/null
+++ b/mojo/core/handle_table.h
@@ -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.
+
+#ifndef MOJO_CORE_HANDLE_TABLE_H_
+#define MOJO_CORE_HANDLE_TABLE_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/synchronization/lock.h"
+// #include "base/trace_event/memory_dump_provider.h"
+#include "mojo/core/dispatcher.h"
+#include "mojo/core/system_impl_export.h"
+#include "mojo/public/c/system/types.h"
+
+namespace mojo {
+namespace core {
+
+class MOJO_SYSTEM_IMPL_EXPORT HandleTable {
+ public:
+ HandleTable();
+ ~HandleTable();
+
+ // HandleTable is thread-hostile. All access should be gated by GetLock().
+ base::Lock& GetLock();
+
+ MojoHandle AddDispatcher(scoped_refptr<Dispatcher> dispatcher);
+
+ // Inserts multiple dispatchers received from message transit, populating
+ // |handles| with their newly allocated handles. Returns |true| on success.
+ bool AddDispatchersFromTransit(
+ const std::vector<Dispatcher::DispatcherInTransit>& dispatchers,
+ MojoHandle* handles);
+
+ scoped_refptr<Dispatcher> GetDispatcher(MojoHandle handle) const;
+ MojoResult GetAndRemoveDispatcher(MojoHandle,
+ scoped_refptr<Dispatcher>* 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,
+ size_t num_handles,
+ std::vector<Dispatcher::DispatcherInTransit>* dispatchers);
+
+ void CompleteTransitAndClose(
+ const std::vector<Dispatcher::DispatcherInTransit>& dispatchers);
+ void CancelTransit(
+ const std::vector<Dispatcher::DispatcherInTransit>& dispatchers);
+
+ void GetActiveHandlesForTest(std::vector<MojoHandle>* handles);
+
+ private:
+ // FRIEND_TEST_ALL_PREFIXES(HandleTableTest, OnMemoryDump);
+
+ // MemoryDumpProvider implementation.
+ // bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
+ // base::trace_event::ProcessMemoryDump* pmd) override;
+
+ struct Entry {
+ Entry();
+ explicit Entry(scoped_refptr<Dispatcher> dispatcher);
+ Entry(const Entry& other);
+ ~Entry();
+
+ scoped_refptr<Dispatcher> dispatcher;
+ bool busy = false;
+ };
+
+ using HandleMap = base::hash_map<MojoHandle, Entry>;
+
+ HandleMap handles_;
+ base::Lock lock_;
+
+ uint32_t next_available_handle_ = 1;
+
+ DISALLOW_COPY_AND_ASSIGN(HandleTable);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_HANDLE_TABLE_H_
diff --git a/mojo/core/handle_table_unittest.cc b/mojo/core/handle_table_unittest.cc
new file mode 100644
index 0000000000..493f71e021
--- /dev/null
+++ b/mojo/core/handle_table_unittest.cc
@@ -0,0 +1,73 @@
+// 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/core/handle_table.h"
+
+#include <memory>
+
+#include "base/synchronization/lock.h"
+#include "base/trace_event/memory_allocator_dump.h"
+#include "base/trace_event/memory_dump_request_args.h"
+#include "base/trace_event/process_memory_dump.h"
+#include "base/trace_event/trace_event_argument.h"
+#include "mojo/core/dispatcher.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::trace_event::MemoryAllocatorDump;
+using testing::Contains;
+using testing::Eq;
+using testing::Contains;
+using testing::ByRef;
+
+namespace mojo {
+namespace core {
+namespace {
+
+class FakeMessagePipeDispatcher : public Dispatcher {
+ public:
+ FakeMessagePipeDispatcher() {}
+
+ Type GetType() const override { return Type::MESSAGE_PIPE; }
+
+ MojoResult Close() override { return MOJO_RESULT_OK; }
+
+ private:
+ ~FakeMessagePipeDispatcher() override {}
+ DISALLOW_COPY_AND_ASSIGN(FakeMessagePipeDispatcher);
+};
+
+void CheckNameAndValue(base::trace_event::ProcessMemoryDump* pmd,
+ const std::string& name,
+ uint64_t value) {
+ base::trace_event::MemoryAllocatorDump* mad = pmd->GetAllocatorDump(name);
+ ASSERT_TRUE(mad);
+
+ MemoryAllocatorDump::Entry expected(
+ "object_count", MemoryAllocatorDump::kUnitsObjects, value);
+ EXPECT_THAT(mad->entries(), Contains(Eq(ByRef(expected))));
+}
+
+} // namespace
+
+TEST(HandleTableTest, OnMemoryDump) {
+ HandleTable ht;
+
+ {
+ base::AutoLock auto_lock(ht.GetLock());
+ scoped_refptr<Dispatcher> dispatcher(new FakeMessagePipeDispatcher);
+ ht.AddDispatcher(dispatcher);
+ }
+
+ base::trace_event::MemoryDumpArgs args = {
+ base::trace_event::MemoryDumpLevelOfDetail::DETAILED};
+ base::trace_event::ProcessMemoryDump pmd(args);
+ ht.OnMemoryDump(args, &pmd);
+
+ CheckNameAndValue(&pmd, "mojo/message_pipe", 1);
+ CheckNameAndValue(&pmd, "mojo/data_pipe_consumer", 0);
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/invitation_dispatcher.cc b/mojo/core/invitation_dispatcher.cc
new file mode 100644
index 0000000000..82d3387102
--- /dev/null
+++ b/mojo/core/invitation_dispatcher.cc
@@ -0,0 +1,78 @@
+// 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.
+
+#include "mojo/core/invitation_dispatcher.h"
+
+#include "mojo/core/core.h"
+
+namespace mojo {
+namespace core {
+
+InvitationDispatcher::InvitationDispatcher() = default;
+
+Dispatcher::Type InvitationDispatcher::GetType() const {
+ return Type::INVITATION;
+}
+
+MojoResult InvitationDispatcher::Close() {
+ PortMapping attached_ports;
+ {
+ base::AutoLock lock(lock_);
+ if (is_closed_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ is_closed_ = true;
+ std::swap(attached_ports, attached_ports_);
+ }
+ for (auto& entry : attached_ports)
+ Core::Get()->GetNodeController()->ClosePort(entry.second);
+ return MOJO_RESULT_OK;
+}
+
+MojoResult InvitationDispatcher::AttachMessagePipe(
+ base::StringPiece name,
+ ports::PortRef remote_peer_port) {
+ base::AutoLock lock(lock_);
+ auto result = attached_ports_.emplace(name.as_string(), remote_peer_port);
+ if (!result.second) {
+ Core::Get()->GetNodeController()->ClosePort(remote_peer_port);
+ return MOJO_RESULT_ALREADY_EXISTS;
+ }
+ return MOJO_RESULT_OK;
+}
+
+MojoResult InvitationDispatcher::ExtractMessagePipe(
+ base::StringPiece name,
+ MojoHandle* message_pipe_handle) {
+ ports::PortRef remote_peer_port;
+ {
+ base::AutoLock lock(lock_);
+ auto it = attached_ports_.find(name.as_string());
+ if (it == attached_ports_.end())
+ return MOJO_RESULT_NOT_FOUND;
+ remote_peer_port = std::move(it->second);
+ attached_ports_.erase(it);
+ }
+
+ *message_pipe_handle =
+ Core::Get()->CreatePartialMessagePipe(remote_peer_port);
+ if (*message_pipe_handle == MOJO_HANDLE_INVALID)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ return MOJO_RESULT_OK;
+}
+
+InvitationDispatcher::PortMapping InvitationDispatcher::TakeAttachedPorts() {
+ PortMapping attached_ports;
+ {
+ base::AutoLock lock(lock_);
+ std::swap(attached_ports, attached_ports_);
+ }
+ return attached_ports;
+}
+
+InvitationDispatcher::~InvitationDispatcher() {
+ DCHECK(is_closed_);
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/invitation_dispatcher.h b/mojo/core/invitation_dispatcher.h
new file mode 100644
index 0000000000..c856e1f7da
--- /dev/null
+++ b/mojo/core/invitation_dispatcher.h
@@ -0,0 +1,49 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_CORE_INVITATION_DISPATCHER_H_
+#define MOJO_CORE_INVITATION_DISPATCHER_H_
+
+#include <stdint.h>
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "mojo/core/dispatcher.h"
+#include "mojo/core/ports/port_ref.h"
+#include "mojo/core/system_impl_export.h"
+
+namespace mojo {
+namespace core {
+
+class MOJO_SYSTEM_IMPL_EXPORT InvitationDispatcher : public Dispatcher {
+ public:
+ InvitationDispatcher();
+
+ // Dispatcher:
+ Type GetType() const override;
+ MojoResult Close() override;
+ MojoResult AttachMessagePipe(base::StringPiece name,
+ ports::PortRef remote_peer_port) override;
+ MojoResult ExtractMessagePipe(base::StringPiece name,
+ MojoHandle* message_pipe_handle) override;
+
+ using PortMapping = base::flat_map<std::string, ports::PortRef>;
+ PortMapping TakeAttachedPorts();
+
+ private:
+ ~InvitationDispatcher() override;
+
+ base::Lock lock_;
+ bool is_closed_ = false;
+ PortMapping attached_ports_;
+
+ DISALLOW_COPY_AND_ASSIGN(InvitationDispatcher);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_INVITATION_DISPATCHER_H
diff --git a/mojo/core/invitation_unittest.cc b/mojo/core/invitation_unittest.cc
new file mode 100644
index 0000000000..791bb08553
--- /dev/null
+++ b/mojo/core/invitation_unittest.cc
@@ -0,0 +1,874 @@
+// 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.
+
+#include <cstdint>
+#include <string>
+
+#include "base/base_paths.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/synchronization/lock.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "build/build_config.h"
+#include "mojo/core/test/mojo_test_base.h"
+#include "mojo/public/c/system/invitation.h"
+#include "mojo/public/cpp/platform/named_platform_channel.h"
+#include "mojo/public/cpp/platform/platform_channel.h"
+#include "mojo/public/cpp/system/platform_handle.h"
+
+namespace mojo {
+namespace core {
+namespace {
+
+enum class TransportType {
+ kChannel,
+ kChannelServer,
+};
+
+const char kSecondaryChannelHandleSwitch[] = "test-secondary-channel-handle";
+
+class InvitationTest : public test::MojoTestBase {
+ public:
+ InvitationTest() = default;
+ ~InvitationTest() override = default;
+
+ protected:
+ static base::Process LaunchChildTestClient(
+ const std::string& test_client_name,
+ MojoHandle* primordial_pipes,
+ size_t num_primordial_pipes,
+ TransportType transport_type,
+ MojoSendInvitationFlags send_flags,
+ MojoProcessErrorHandler error_handler = nullptr,
+ uintptr_t error_handler_context = 0,
+ base::CommandLine* custom_command_line = nullptr,
+ base::LaunchOptions* custom_launch_options = nullptr);
+
+ static void SendInvitationToClient(
+ PlatformHandle endpoint_handle,
+ base::ProcessHandle process,
+ MojoHandle* primordial_pipes,
+ size_t num_primordial_pipes,
+ TransportType transport_type,
+ MojoSendInvitationFlags flags,
+ MojoProcessErrorHandler error_handler,
+ uintptr_t error_handler_context,
+ base::StringPiece isolated_invitation_name);
+
+ private:
+ base::test::ScopedTaskEnvironment task_environment_;
+
+ DISALLOW_COPY_AND_ASSIGN(InvitationTest);
+};
+
+void PrepareToPassRemoteEndpoint(PlatformChannel* channel,
+ base::LaunchOptions* options,
+ base::CommandLine* command_line,
+ base::StringPiece switch_name = {}) {
+ std::string value;
+#if defined(OS_FUCHSIA)
+ channel->PrepareToPassRemoteEndpoint(&options->handles_to_transfer, &value);
+#elif defined(OS_POSIX)
+ channel->PrepareToPassRemoteEndpoint(&options->fds_to_remap, &value);
+#elif defined(OS_WIN)
+ channel->PrepareToPassRemoteEndpoint(&options->handles_to_inherit, &value);
+#else
+#error "Platform not yet supported."
+#endif
+
+ if (switch_name.empty())
+ switch_name = PlatformChannel::kHandleSwitch;
+ command_line->AppendSwitchASCII(switch_name.as_string(), value);
+}
+
+TEST_F(InvitationTest, Create) {
+ MojoHandle invitation;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateInvitation(nullptr, &invitation));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(invitation));
+
+ MojoCreateInvitationOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_CREATE_INVITATION_FLAG_NONE;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateInvitation(&options, &invitation));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(invitation));
+}
+
+TEST_F(InvitationTest, InvalidArguments) {
+ MojoHandle invitation;
+ MojoCreateInvitationOptions invalid_create_options;
+ invalid_create_options.struct_size = 0;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoCreateInvitation(&invalid_create_options, &invitation));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoCreateInvitation(nullptr, nullptr));
+
+ // We need a valid invitation handle to exercise some of the other invalid
+ // argument cases below.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateInvitation(nullptr, &invitation));
+
+ MojoHandle pipe;
+ MojoAttachMessagePipeToInvitationOptions invalid_attach_options;
+ invalid_attach_options.struct_size = 0;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoAttachMessagePipeToInvitation(MOJO_HANDLE_INVALID, "x", 1,
+ nullptr, &pipe));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoAttachMessagePipeToInvitation(invitation, "x", 1,
+ &invalid_attach_options, &pipe));
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ MojoAttachMessagePipeToInvitation(invitation, "x", 1, nullptr, nullptr));
+
+ MojoExtractMessagePipeFromInvitationOptions invalid_extract_options;
+ invalid_extract_options.struct_size = 0;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoExtractMessagePipeFromInvitation(MOJO_HANDLE_INVALID, "x", 1,
+ nullptr, &pipe));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoExtractMessagePipeFromInvitation(
+ invitation, "x", 1, &invalid_extract_options, &pipe));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoExtractMessagePipeFromInvitation(invitation, "x", 1, nullptr,
+ nullptr));
+
+ PlatformChannel channel;
+ MojoPlatformHandle endpoint_handle;
+ endpoint_handle.struct_size = sizeof(endpoint_handle);
+ PlatformHandle::ToMojoPlatformHandle(
+ channel.TakeLocalEndpoint().TakePlatformHandle(), &endpoint_handle);
+ ASSERT_NE(endpoint_handle.type, MOJO_PLATFORM_HANDLE_TYPE_INVALID);
+
+ MojoInvitationTransportEndpoint valid_endpoint;
+ valid_endpoint.struct_size = sizeof(valid_endpoint);
+ valid_endpoint.type = MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL;
+ valid_endpoint.num_platform_handles = 1;
+ valid_endpoint.platform_handles = &endpoint_handle;
+
+ MojoSendInvitationOptions invalid_send_options;
+ invalid_send_options.struct_size = 0;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoSendInvitation(MOJO_HANDLE_INVALID, nullptr, &valid_endpoint,
+ nullptr, 0, nullptr));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoSendInvitation(invitation, nullptr, &valid_endpoint, nullptr, 0,
+ &invalid_send_options));
+
+ MojoInvitationTransportEndpoint invalid_endpoint;
+ invalid_endpoint.struct_size = 0;
+ invalid_endpoint.type = MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL;
+ invalid_endpoint.num_platform_handles = 1;
+ invalid_endpoint.platform_handles = &endpoint_handle;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoSendInvitation(invitation, nullptr, &invalid_endpoint, nullptr,
+ 0, nullptr));
+
+ invalid_endpoint.struct_size = sizeof(invalid_endpoint);
+ invalid_endpoint.num_platform_handles = 0;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoSendInvitation(invitation, nullptr, &invalid_endpoint, nullptr,
+ 0, nullptr));
+
+ MojoPlatformHandle invalid_platform_handle;
+ invalid_platform_handle.struct_size = 0;
+ invalid_endpoint.num_platform_handles = 1;
+ invalid_endpoint.platform_handles = &invalid_platform_handle;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoSendInvitation(invitation, nullptr, &invalid_endpoint, nullptr,
+ 0, nullptr));
+ invalid_platform_handle.struct_size = sizeof(invalid_platform_handle);
+ invalid_platform_handle.type = MOJO_PLATFORM_HANDLE_TYPE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoSendInvitation(invitation, nullptr, &invalid_endpoint, nullptr,
+ 0, nullptr));
+
+ invalid_endpoint.num_platform_handles = 1;
+ invalid_endpoint.platform_handles = nullptr;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoSendInvitation(invitation, nullptr, &invalid_endpoint, nullptr,
+ 0, nullptr));
+
+ MojoHandle accepted_invitation;
+ MojoAcceptInvitationOptions invalid_accept_options;
+ invalid_accept_options.struct_size = 0;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoAcceptInvitation(nullptr, nullptr, &accepted_invitation));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoAcceptInvitation(&valid_endpoint, &invalid_accept_options,
+ &accepted_invitation));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoAcceptInvitation(&valid_endpoint, nullptr, nullptr));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(invitation));
+}
+
+TEST_F(InvitationTest, AttachAndExtractLocally) {
+ MojoHandle invitation;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateInvitation(nullptr, &invitation));
+
+ MojoHandle pipe0 = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoAttachMessagePipeToInvitation(
+ invitation, "x", 1, nullptr, &pipe0));
+ EXPECT_NE(MOJO_HANDLE_INVALID, pipe0);
+
+ MojoHandle pipe1 = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoExtractMessagePipeFromInvitation(
+ invitation, "x", 1, nullptr, &pipe1));
+ EXPECT_NE(MOJO_HANDLE_INVALID, pipe1);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(invitation));
+
+ // Should be able to communicate over the pipe.
+ const std::string kMessage = "RSVP LOL";
+ WriteMessage(pipe0, kMessage);
+ EXPECT_EQ(kMessage, ReadMessage(pipe1));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(pipe0));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(pipe1));
+}
+
+TEST_F(InvitationTest, ClosedInvitationClosesAttachments) {
+ MojoHandle invitation;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateInvitation(nullptr, &invitation));
+
+ MojoHandle pipe = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoAttachMessagePipeToInvitation(
+ invitation, "x", 1, nullptr, &pipe));
+ EXPECT_NE(MOJO_HANDLE_INVALID, pipe);
+
+ // Closing the invitation should close |pipe|'s peer.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(invitation));
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(pipe));
+}
+
+TEST_F(InvitationTest, AttachNameInUse) {
+ MojoHandle invitation;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateInvitation(nullptr, &invitation));
+
+ MojoHandle pipe0 = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoAttachMessagePipeToInvitation(
+ invitation, "x", 1, nullptr, &pipe0));
+ EXPECT_NE(MOJO_HANDLE_INVALID, pipe0);
+
+ MojoHandle pipe1 = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ MojoAttachMessagePipeToInvitation(invitation, "x", 1, nullptr, &pipe1));
+ EXPECT_EQ(MOJO_HANDLE_INVALID, pipe1);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoAttachMessagePipeToInvitation(
+ invitation, "y", 1, nullptr, &pipe1));
+ EXPECT_NE(MOJO_HANDLE_INVALID, pipe1);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(invitation));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(pipe0));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(pipe1));
+}
+
+// static
+base::Process InvitationTest::LaunchChildTestClient(
+ const std::string& test_client_name,
+ MojoHandle* primordial_pipes,
+ size_t num_primordial_pipes,
+ TransportType transport_type,
+ MojoSendInvitationFlags send_flags,
+ MojoProcessErrorHandler error_handler,
+ uintptr_t error_handler_context,
+ base::CommandLine* custom_command_line,
+ base::LaunchOptions* custom_launch_options) {
+ base::CommandLine default_command_line =
+ base::GetMultiProcessTestChildBaseCommandLine();
+ base::CommandLine& command_line =
+ custom_command_line ? *custom_command_line : default_command_line;
+
+ base::LaunchOptions default_launch_options;
+ base::LaunchOptions& launch_options =
+ custom_launch_options ? *custom_launch_options : default_launch_options;
+#if defined(OS_WIN)
+ launch_options.start_hidden = true;
+#endif
+
+ base::Optional<PlatformChannel> channel;
+ base::Optional<NamedPlatformChannel> named_channel;
+ PlatformHandle local_endpoint_handle;
+ if (transport_type == TransportType::kChannel) {
+ channel.emplace();
+ PrepareToPassRemoteEndpoint(&channel.value(), &launch_options,
+ &command_line);
+ local_endpoint_handle = channel->TakeLocalEndpoint().TakePlatformHandle();
+ } else {
+#if defined(OS_FUCHSIA)
+ NOTREACHED() << "Named pipe support does not exist for Mojo on Fuchsia.";
+#else
+ NamedPlatformChannel::Options named_channel_options;
+#if !defined(OS_WIN)
+ CHECK(base::PathService::Get(base::DIR_TEMP,
+ &named_channel_options.socket_dir));
+#endif
+ named_channel.emplace(named_channel_options);
+ named_channel->PassServerNameOnCommandLine(&command_line);
+ local_endpoint_handle =
+ named_channel->TakeServerEndpoint().TakePlatformHandle();
+#endif
+ }
+
+ base::Process child_process = base::SpawnMultiProcessTestChild(
+ test_client_name, command_line, launch_options);
+ if (channel)
+ channel->RemoteProcessLaunchAttempted();
+
+ SendInvitationToClient(std::move(local_endpoint_handle),
+ child_process.Handle(), primordial_pipes,
+ num_primordial_pipes, transport_type, send_flags,
+ error_handler, error_handler_context, "");
+
+ return child_process;
+}
+
+// static
+void InvitationTest::SendInvitationToClient(
+ PlatformHandle endpoint_handle,
+ base::ProcessHandle process,
+ MojoHandle* primordial_pipes,
+ size_t num_primordial_pipes,
+ TransportType transport_type,
+ MojoSendInvitationFlags flags,
+ MojoProcessErrorHandler error_handler,
+ uintptr_t error_handler_context,
+ base::StringPiece isolated_invitation_name) {
+ MojoPlatformHandle handle;
+ PlatformHandle::ToMojoPlatformHandle(std::move(endpoint_handle), &handle);
+ CHECK_NE(handle.type, MOJO_PLATFORM_HANDLE_TYPE_INVALID);
+
+ MojoHandle invitation;
+ CHECK_EQ(MOJO_RESULT_OK, MojoCreateInvitation(nullptr, &invitation));
+ for (uint32_t name = 0; name < num_primordial_pipes; ++name) {
+ CHECK_EQ(MOJO_RESULT_OK,
+ MojoAttachMessagePipeToInvitation(invitation, &name, 4, nullptr,
+ &primordial_pipes[name]));
+ }
+
+ MojoPlatformProcessHandle process_handle;
+ process_handle.struct_size = sizeof(process_handle);
+#if defined(OS_WIN)
+ process_handle.value =
+ static_cast<uint64_t>(reinterpret_cast<uintptr_t>(process));
+#else
+ process_handle.value = static_cast<uint64_t>(process);
+#endif
+
+ MojoInvitationTransportEndpoint transport_endpoint;
+ transport_endpoint.struct_size = sizeof(transport_endpoint);
+ if (transport_type == TransportType::kChannel)
+ transport_endpoint.type = MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL;
+ else
+ transport_endpoint.type = MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL_SERVER;
+ transport_endpoint.num_platform_handles = 1;
+ transport_endpoint.platform_handles = &handle;
+
+ MojoSendInvitationOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = flags;
+ if (flags & MOJO_SEND_INVITATION_FLAG_ISOLATED) {
+ options.isolated_connection_name = isolated_invitation_name.data();
+ options.isolated_connection_name_length =
+ static_cast<uint32_t>(isolated_invitation_name.size());
+ }
+ CHECK_EQ(MOJO_RESULT_OK,
+ MojoSendInvitation(invitation, &process_handle, &transport_endpoint,
+ error_handler, error_handler_context, &options));
+}
+
+class TestClientBase : public InvitationTest {
+ public:
+ static MojoHandle AcceptInvitation(MojoAcceptInvitationFlags flags,
+ base::StringPiece switch_name = {}) {
+ const auto& command_line = *base::CommandLine::ForCurrentProcess();
+ PlatformChannelEndpoint channel_endpoint =
+ NamedPlatformChannel::ConnectToServer(command_line);
+ if (!channel_endpoint.is_valid()) {
+ if (switch_name.empty()) {
+ channel_endpoint =
+ PlatformChannel::RecoverPassedEndpointFromCommandLine(command_line);
+ } else {
+ channel_endpoint = PlatformChannel::RecoverPassedEndpointFromString(
+ command_line.GetSwitchValueASCII(switch_name));
+ }
+ }
+ MojoPlatformHandle endpoint_handle;
+ PlatformHandle::ToMojoPlatformHandle(channel_endpoint.TakePlatformHandle(),
+ &endpoint_handle);
+ CHECK_NE(endpoint_handle.type, MOJO_PLATFORM_HANDLE_TYPE_INVALID);
+
+ MojoInvitationTransportEndpoint transport_endpoint;
+ transport_endpoint.struct_size = sizeof(transport_endpoint);
+ transport_endpoint.type = MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL;
+ transport_endpoint.num_platform_handles = 1;
+ transport_endpoint.platform_handles = &endpoint_handle;
+
+ MojoAcceptInvitationOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = flags;
+ MojoHandle invitation;
+ CHECK_EQ(MOJO_RESULT_OK,
+ MojoAcceptInvitation(&transport_endpoint, &options, &invitation));
+ return invitation;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestClientBase);
+};
+
+#define DEFINE_TEST_CLIENT(name) \
+ class name##Impl : public TestClientBase { \
+ public: \
+ static void Run(); \
+ }; \
+ MULTIPROCESS_TEST_MAIN(name) { \
+ name##Impl::Run(); \
+ return 0; \
+ } \
+ void name##Impl::Run()
+
+const std::string kTestMessage1 = "i am the pusher robot";
+const std::string kTestMessage2 = "i push the messages down the pipe";
+const std::string kTestMessage3 = "i am the shover robot";
+const std::string kTestMessage4 = "i shove the messages down the pipe";
+
+TEST_F(InvitationTest, SendInvitation) {
+ MojoHandle primordial_pipe;
+ base::Process child_process = LaunchChildTestClient(
+ "SendInvitationClient", &primordial_pipe, 1, TransportType::kChannel,
+ MOJO_SEND_INVITATION_FLAG_NONE);
+
+ WriteMessage(primordial_pipe, kTestMessage1);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_EQ(kTestMessage3, ReadMessage(primordial_pipe));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe));
+
+ int wait_result = -1;
+ base::WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &wait_result);
+ child_process.Close();
+ EXPECT_EQ(0, wait_result);
+}
+
+DEFINE_TEST_CLIENT(SendInvitationClient) {
+ MojoHandle primordial_pipe;
+ MojoHandle invitation = AcceptInvitation(MOJO_ACCEPT_INVITATION_FLAG_NONE);
+ const uint32_t pipe_name = 0;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoExtractMessagePipeFromInvitation(invitation, &pipe_name, 4,
+ nullptr, &primordial_pipe));
+ ASSERT_EQ(MOJO_RESULT_OK, MojoClose(invitation));
+
+ WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_EQ(kTestMessage1, ReadMessage(primordial_pipe));
+ WriteMessage(primordial_pipe, kTestMessage3);
+ WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+
+ ASSERT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe));
+}
+
+TEST_F(InvitationTest, SendInvitationMultiplePipes) {
+ MojoHandle pipes[2];
+ base::Process child_process = LaunchChildTestClient(
+ "SendInvitationMultiplePipesClient", pipes, 2, TransportType::kChannel,
+ MOJO_SEND_INVITATION_FLAG_NONE);
+
+ WriteMessage(pipes[0], kTestMessage1);
+ WriteMessage(pipes[1], kTestMessage2);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(pipes[0], MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(pipes[1], MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_EQ(kTestMessage3, ReadMessage(pipes[0]));
+ EXPECT_EQ(kTestMessage4, ReadMessage(pipes[1]));
+
+ ASSERT_EQ(MOJO_RESULT_OK, MojoClose(pipes[0]));
+ ASSERT_EQ(MOJO_RESULT_OK, MojoClose(pipes[1]));
+
+ int wait_result = -1;
+ base::WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &wait_result);
+ child_process.Close();
+ EXPECT_EQ(0, wait_result);
+}
+
+DEFINE_TEST_CLIENT(SendInvitationMultiplePipesClient) {
+ MojoHandle pipes[2];
+ MojoHandle invitation = AcceptInvitation(MOJO_ACCEPT_INVITATION_FLAG_NONE);
+ const uint32_t pipe_names[] = {0, 1};
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoExtractMessagePipeFromInvitation(invitation, &pipe_names[0], 4,
+ nullptr, &pipes[0]));
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoExtractMessagePipeFromInvitation(invitation, &pipe_names[1], 4,
+ nullptr, &pipes[1]));
+
+ WaitForSignals(pipes[0], MOJO_HANDLE_SIGNAL_READABLE);
+ WaitForSignals(pipes[1], MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_EQ(kTestMessage1, ReadMessage(pipes[0]));
+ ASSERT_EQ(kTestMessage2, ReadMessage(pipes[1]));
+ WriteMessage(pipes[0], kTestMessage3);
+ WriteMessage(pipes[1], kTestMessage4);
+ WaitForSignals(pipes[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+ WaitForSignals(pipes[1], MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+}
+
+#if !defined(OS_FUCHSIA)
+TEST_F(InvitationTest, SendInvitationWithServer) {
+ MojoHandle primordial_pipe;
+ base::Process child_process = LaunchChildTestClient(
+ "SendInvitationWithServerClient", &primordial_pipe, 1,
+ TransportType::kChannelServer, MOJO_SEND_INVITATION_FLAG_NONE);
+
+ WriteMessage(primordial_pipe, kTestMessage1);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_EQ(kTestMessage3, ReadMessage(primordial_pipe));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe));
+
+ int wait_result = -1;
+ base::WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &wait_result);
+ child_process.Close();
+ EXPECT_EQ(0, wait_result);
+}
+
+DEFINE_TEST_CLIENT(SendInvitationWithServerClient) {
+ MojoHandle primordial_pipe;
+ MojoHandle invitation = AcceptInvitation(MOJO_ACCEPT_INVITATION_FLAG_NONE);
+ const uint32_t pipe_name = 0;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoExtractMessagePipeFromInvitation(invitation, &pipe_name, 4,
+ nullptr, &primordial_pipe));
+ ASSERT_EQ(MOJO_RESULT_OK, MojoClose(invitation));
+
+ WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_EQ(kTestMessage1, ReadMessage(primordial_pipe));
+ WriteMessage(primordial_pipe, kTestMessage3);
+ WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+
+ ASSERT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe));
+}
+#endif // !defined(OS_FUCHSIA)
+
+const char kErrorMessage[] = "ur bad :(";
+const char kDisconnectMessage[] = "go away plz";
+
+class RemoteProcessState {
+ public:
+ RemoteProcessState()
+ : callback_task_runner_(base::SequencedTaskRunnerHandle::Get()) {}
+ ~RemoteProcessState() = default;
+
+ bool disconnected() {
+ base::AutoLock lock(lock_);
+ return disconnected_;
+ }
+
+ void set_error_callback(base::RepeatingClosure callback) {
+ error_callback_ = std::move(callback);
+ }
+
+ void set_expected_error_message(const std::string& expected) {
+ expected_error_message_ = expected;
+ }
+
+ void NotifyError(const std::string& error_message, bool disconnected) {
+ base::AutoLock lock(lock_);
+ CHECK(!disconnected_);
+ EXPECT_NE(error_message.find(expected_error_message_), std::string::npos);
+ disconnected_ = disconnected;
+ ++call_count_;
+ if (error_callback_)
+ callback_task_runner_->PostTask(FROM_HERE, error_callback_);
+ }
+
+ private:
+ const scoped_refptr<base::SequencedTaskRunner> callback_task_runner_;
+
+ base::Lock lock_;
+ int call_count_ = 0;
+ bool disconnected_ = false;
+ std::string expected_error_message_;
+ base::RepeatingClosure error_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(RemoteProcessState);
+};
+
+void TestProcessErrorHandler(uintptr_t context,
+ const MojoProcessErrorDetails* details) {
+ auto* state = reinterpret_cast<RemoteProcessState*>(context);
+ std::string error_message;
+ if (details->error_message) {
+ error_message =
+ std::string(details->error_message, details->error_message_length - 1);
+ }
+ state->NotifyError(error_message,
+ details->flags & MOJO_PROCESS_ERROR_FLAG_DISCONNECTED);
+}
+
+TEST_F(InvitationTest, ProcessErrors) {
+ RemoteProcessState process_state;
+ MojoHandle pipe;
+ base::Process child_process = LaunchChildTestClient(
+ "ProcessErrorsClient", &pipe, 1, TransportType::kChannel,
+ MOJO_SEND_INVITATION_FLAG_NONE, &TestProcessErrorHandler,
+ reinterpret_cast<uintptr_t>(&process_state));
+
+ MojoMessageHandle message;
+ WaitForSignals(pipe, MOJO_HANDLE_SIGNAL_READABLE);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoReadMessage(pipe, nullptr, &message));
+
+ base::RunLoop error_loop;
+ process_state.set_error_callback(error_loop.QuitClosure());
+
+ // Report this message as "bad". This should cause the error handler to be
+ // invoked and the RunLoop to be quit.
+ process_state.set_expected_error_message(kErrorMessage);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoNotifyBadMessage(message, kErrorMessage, sizeof(kErrorMessage),
+ nullptr));
+ error_loop.Run();
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message));
+
+ // Now tell the child it can exit, and wait for it to disconnect.
+ base::RunLoop disconnect_loop;
+ process_state.set_error_callback(disconnect_loop.QuitClosure());
+ process_state.set_expected_error_message(std::string());
+ WriteMessage(pipe, kDisconnectMessage);
+ disconnect_loop.Run();
+
+ EXPECT_TRUE(process_state.disconnected());
+
+ int wait_result = -1;
+ base::WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &wait_result);
+ child_process.Close();
+ EXPECT_EQ(0, wait_result);
+}
+
+DEFINE_TEST_CLIENT(ProcessErrorsClient) {
+ MojoHandle pipe;
+ MojoHandle invitation = AcceptInvitation(MOJO_ACCEPT_INVITATION_FLAG_NONE);
+ const uint32_t pipe_name = 0;
+ ASSERT_EQ(MOJO_RESULT_OK, MojoExtractMessagePipeFromInvitation(
+ invitation, &pipe_name, 4, nullptr, &pipe));
+ ASSERT_EQ(MOJO_RESULT_OK, MojoClose(invitation));
+
+ // Send a message. Contents are irrelevant, the test process is just going to
+ // flag it as a bad.
+ WriteMessage(pipe, "doesn't matter");
+
+ // Wait for our goodbye before exiting.
+ WaitForSignals(pipe, MOJO_HANDLE_SIGNAL_READABLE);
+ EXPECT_EQ(kDisconnectMessage, ReadMessage(pipe));
+}
+
+TEST_F(InvitationTest, SendIsolatedInvitation) {
+ MojoHandle primordial_pipe;
+ base::Process child_process = LaunchChildTestClient(
+ "SendIsolatedInvitationClient", &primordial_pipe, 1,
+ TransportType::kChannel, MOJO_SEND_INVITATION_FLAG_ISOLATED);
+
+ WriteMessage(primordial_pipe, kTestMessage1);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_EQ(kTestMessage3, ReadMessage(primordial_pipe));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe));
+
+ int wait_result = -1;
+ base::WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &wait_result);
+ child_process.Close();
+ EXPECT_EQ(0, wait_result);
+}
+
+DEFINE_TEST_CLIENT(SendIsolatedInvitationClient) {
+ MojoHandle primordial_pipe;
+ MojoHandle invitation =
+ AcceptInvitation(MOJO_ACCEPT_INVITATION_FLAG_ISOLATED);
+ const uint32_t pipe_name = 0;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoExtractMessagePipeFromInvitation(invitation, &pipe_name, 4,
+ nullptr, &primordial_pipe));
+ ASSERT_EQ(MOJO_RESULT_OK, MojoClose(invitation));
+
+ WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_EQ(kTestMessage1, ReadMessage(primordial_pipe));
+ WriteMessage(primordial_pipe, kTestMessage3);
+ WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+
+ ASSERT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe));
+}
+
+TEST_F(InvitationTest, SendMultipleIsolatedInvitations) {
+ // We send a secondary transport to the client process so we can send a second
+ // isolated invitation.
+ base::CommandLine command_line =
+ base::GetMultiProcessTestChildBaseCommandLine();
+ PlatformChannel secondary_transport;
+ base::LaunchOptions options;
+ PrepareToPassRemoteEndpoint(&secondary_transport, &options, &command_line,
+ kSecondaryChannelHandleSwitch);
+
+ MojoHandle primordial_pipe;
+ base::Process child_process = LaunchChildTestClient(
+ "SendMultipleIsolatedInvitationsClient", &primordial_pipe, 1,
+ TransportType::kChannel, MOJO_SEND_INVITATION_FLAG_ISOLATED, nullptr, 0,
+ &command_line, &options);
+ secondary_transport.RemoteProcessLaunchAttempted();
+
+ WriteMessage(primordial_pipe, kTestMessage1);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_EQ(kTestMessage3, ReadMessage(primordial_pipe));
+
+ // Send another invitation over our seconary pipe. This should trample the
+ // original connection, breaking the first pipe.
+ MojoHandle new_pipe;
+ SendInvitationToClient(
+ secondary_transport.TakeLocalEndpoint().TakePlatformHandle(),
+ child_process.Handle(), &new_pipe, 1, TransportType::kChannel,
+ MOJO_SEND_INVITATION_FLAG_ISOLATED, nullptr, 0, "");
+ WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe));
+
+ // And the new pipe should be working.
+ WriteMessage(new_pipe, kTestMessage1);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(new_pipe, MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_EQ(kTestMessage3, ReadMessage(new_pipe));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(new_pipe));
+
+ int wait_result = -1;
+ base::WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &wait_result);
+ child_process.Close();
+ EXPECT_EQ(0, wait_result);
+}
+
+DEFINE_TEST_CLIENT(SendMultipleIsolatedInvitationsClient) {
+ MojoHandle primordial_pipe;
+ MojoHandle invitation =
+ AcceptInvitation(MOJO_ACCEPT_INVITATION_FLAG_ISOLATED);
+ const uint32_t pipe_name = 0;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoExtractMessagePipeFromInvitation(invitation, &pipe_name, 4,
+ nullptr, &primordial_pipe));
+ ASSERT_EQ(MOJO_RESULT_OK, MojoClose(invitation));
+
+ WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_EQ(kTestMessage1, ReadMessage(primordial_pipe));
+ WriteMessage(primordial_pipe, kTestMessage3);
+
+ // The above pipe should get closed once we accept a new invitation.
+ invitation = AcceptInvitation(MOJO_ACCEPT_INVITATION_FLAG_ISOLATED,
+ kSecondaryChannelHandleSwitch);
+ WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+ primordial_pipe = MOJO_HANDLE_INVALID;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoExtractMessagePipeFromInvitation(invitation, &pipe_name, 4,
+ nullptr, &primordial_pipe));
+ ASSERT_EQ(MOJO_RESULT_OK, MojoClose(invitation));
+ WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_EQ(kTestMessage1, ReadMessage(primordial_pipe));
+ WriteMessage(primordial_pipe, kTestMessage3);
+ WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+
+ ASSERT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe));
+}
+
+TEST_F(InvitationTest, SendIsolatedInvitationWithDuplicateName) {
+ PlatformChannel channel1;
+ PlatformChannel channel2;
+ MojoHandle pipe0, pipe1;
+ const char kConnectionName[] = "there can be only one!";
+ SendInvitationToClient(
+ channel1.TakeLocalEndpoint().TakePlatformHandle(),
+ base::kNullProcessHandle, &pipe0, 1, TransportType::kChannel,
+ MOJO_SEND_INVITATION_FLAG_ISOLATED, nullptr, 0, kConnectionName);
+
+ // Send another invitation with the same connection name. |pipe0| should be
+ // disconnected as the first invitation's connection is torn down.
+ SendInvitationToClient(
+ channel2.TakeLocalEndpoint().TakePlatformHandle(),
+ base::kNullProcessHandle, &pipe1, 1, TransportType::kChannel,
+ MOJO_SEND_INVITATION_FLAG_ISOLATED, nullptr, 0, kConnectionName);
+
+ WaitForSignals(pipe0, MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+}
+
+TEST_F(InvitationTest, SendIsolatedInvitationToSelf) {
+ PlatformChannel channel;
+ MojoHandle pipe0, pipe1;
+ SendInvitationToClient(channel.TakeLocalEndpoint().TakePlatformHandle(),
+ base::kNullProcessHandle, &pipe0, 1,
+ TransportType::kChannel,
+ MOJO_SEND_INVITATION_FLAG_ISOLATED, nullptr, 0, "");
+ SendInvitationToClient(channel.TakeRemoteEndpoint().TakePlatformHandle(),
+ base::kNullProcessHandle, &pipe1, 1,
+ TransportType::kChannel,
+ MOJO_SEND_INVITATION_FLAG_ISOLATED, nullptr, 0, "");
+
+ WriteMessage(pipe0, kTestMessage1);
+ EXPECT_EQ(kTestMessage1, ReadMessage(pipe1));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(pipe0));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(pipe1));
+}
+
+TEST_F(InvitationTest, BrokenInvitationTransportBreaksAttachedPipe) {
+ MojoHandle primordial_pipe;
+ base::Process child_process = LaunchChildTestClient(
+ "BrokenTransportClient", &primordial_pipe, 1, TransportType::kChannel,
+ MOJO_SEND_INVITATION_FLAG_NONE);
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe));
+
+ int wait_result = -1;
+ base::WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &wait_result);
+ child_process.Close();
+ EXPECT_EQ(0, wait_result);
+}
+
+TEST_F(InvitationTest, BrokenIsolatedInvitationTransportBreaksAttachedPipe) {
+ MojoHandle primordial_pipe;
+ base::Process child_process = LaunchChildTestClient(
+ "BrokenTransportClient", &primordial_pipe, 1, TransportType::kChannel,
+ MOJO_SEND_INVITATION_FLAG_ISOLATED);
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(primordial_pipe, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(primordial_pipe));
+
+ int wait_result = -1;
+ base::WaitForMultiprocessTestChildExit(
+ child_process, TestTimeouts::action_timeout(), &wait_result);
+ child_process.Close();
+ EXPECT_EQ(0, wait_result);
+}
+
+DEFINE_TEST_CLIENT(BrokenTransportClient) {
+ // No-op. Exit immediately without accepting any invitation.
+}
+
+} // namespace
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/mach_port_relay.cc b/mojo/core/mach_port_relay.cc
new file mode 100644
index 0000000000..7d7a42510e
--- /dev/null
+++ b/mojo/core/mach_port_relay.cc
@@ -0,0 +1,200 @@
+// 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/core/mach_port_relay.h"
+
+#include <mach/mach.h>
+
+#include <utility>
+
+#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"
+
+namespace mojo {
+namespace core {
+
+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<int>(error),
+ static_cast<int>(BrokerUMAError::ERROR_MAX));
+}
+
+void ReportChildError(ChildUMAError error) {
+ UMA_HISTOGRAM_ENUMERATION("Mojo.MachPortRelay.ChildError",
+ static_cast<int>(error),
+ static_cast<int>(ChildUMAError::ERROR_MAX));
+}
+
+} // namespace
+
+// static
+base::mac::ScopedMachSendRight MachPortRelay::ReceiveSendRight(
+ base::mac::ScopedMachReceiveRight port) {
+ // MACH_PORT_NULL doesn't need translation.
+ if (!port.is_valid())
+ return base::mac::ScopedMachSendRight();
+
+ // Take ownership of the receive right. We only need it to read this single
+ // send right, then it can be closed.
+ base::mac::ScopedMachSendRight received_port(
+ base::ReceiveMachPort(port.get()));
+ if (!received_port.is_valid()) {
+ ReportChildError(ChildUMAError::ERROR_RECEIVE_MACH_MESSAGE);
+ DLOG(ERROR) << "Error receiving mach port";
+ return base::mac::ScopedMachSendRight();
+ }
+
+ ReportChildError(ChildUMAError::SUCCESS);
+ return received_port;
+}
+
+MachPortRelay::MachPortRelay(base::PortProvider* port_provider)
+ : port_provider_(port_provider) {
+ DCHECK(port_provider);
+ port_provider_->AddObserver(this);
+}
+
+MachPortRelay::~MachPortRelay() {
+ port_provider_->RemoveObserver(this);
+}
+
+void MachPortRelay::SendPortsToProcess(Channel::Message* message,
+ base::ProcessHandle process) {
+ DCHECK(message);
+ mach_port_t task_port = port_provider_->TaskForPid(process);
+
+ std::vector<PlatformHandleInTransit> handles = message->TakeHandles();
+ // Message should have handles, otherwise there's no point in calling this
+ // function.
+ DCHECK(!handles.empty());
+ for (auto& handle : handles) {
+ if (!handle.handle().is_valid_mach_port())
+ continue;
+
+ 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);
+ handle = PlatformHandleInTransit(
+ PlatformHandle(base::mac::ScopedMachSendRight()));
+ continue;
+ }
+
+ mach_port_name_t intermediate_port;
+ base::MachCreateError error_code;
+ intermediate_port = base::CreateIntermediateMachPort(
+ task_port, handle.TakeHandle().TakeMachPort(), &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 = PlatformHandleInTransit(
+ PlatformHandle(base::mac::ScopedMachSendRight()));
+ continue;
+ }
+
+ handle = PlatformHandleInTransit::CreateForMachPortName(intermediate_port);
+ ReportBrokerError(BrokerUMAError::SUCCESS);
+ }
+ message->SetHandles(std::move(handles));
+}
+
+base::mac::ScopedMachSendRight MachPortRelay::ExtractPort(
+ mach_port_t port_name,
+ base::ProcessHandle process) {
+ // No extraction necessary for MACH_PORT_NULL.
+ if (port_name == MACH_PORT_NULL)
+ return base::mac::ScopedMachSendRight();
+
+ mach_port_t task_port = port_provider_->TaskForPid(process);
+ if (task_port == MACH_PORT_NULL) {
+ ReportBrokerError(BrokerUMAError::ERROR_TASK_FOR_PID);
+ return base::mac::ScopedMachSendRight();
+ }
+
+ 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, port_name, MACH_MSG_TYPE_MOVE_SEND,
+ &extracted_right, &extracted_right_type);
+ if (kr != KERN_SUCCESS) {
+ ReportBrokerError(BrokerUMAError::ERROR_EXTRACT_SOURCE_RIGHT);
+ return base::mac::ScopedMachSendRight();
+ }
+
+ ReportBrokerError(BrokerUMAError::SUCCESS);
+ DCHECK_EQ(static_cast<mach_msg_type_name_t>(MACH_MSG_TYPE_PORT_SEND),
+ extracted_right_type);
+ return base::mac::ScopedMachSendRight(extracted_right);
+}
+
+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 core
+} // namespace mojo
diff --git a/mojo/core/mach_port_relay.h b/mojo/core/mach_port_relay.h
new file mode 100644
index 0000000000..4cb6de4bd6
--- /dev/null
+++ b/mojo/core/mach_port_relay.h
@@ -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.
+
+#ifndef MOJO_CORE_MACH_PORT_RELAY_H_
+#define MOJO_CORE_MACH_PORT_RELAY_H_
+
+#include <set>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/process/port_provider_mac.h"
+#include "base/synchronization/lock.h"
+#include "mojo/core/channel.h"
+
+namespace mojo {
+namespace core {
+
+// 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. The Mach port in |port| 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.
+ //
+ // On failure, returns a null send right.
+ //
+ // See SendPortsToProcess() for the definition of intermediate and final Mach
+ // ports.
+ static base::mac::ScopedMachSendRight ReceiveSendRight(
+ base::mac::ScopedMachReceiveRight port);
+
+ 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.
+ //
+ // All ports in |message|'s set of handles are reset by this call, and all
+ // port names in the message's header are replaced with the new receive right
+ // ports.
+ void SendPortsToProcess(Channel::Message* message,
+ base::ProcessHandle process);
+
+ // Given the name of a Mach send right within |process|, extracts an owned
+ // send right ref and returns it. May return a null port on failure.
+ base::mac::ScopedMachSendRight ExtractPort(mach_port_t port_name,
+ 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<Observer*> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(MachPortRelay);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_MACH_PORT_RELAY_H_
diff --git a/mojo/core/message_pipe_dispatcher.cc b/mojo/core/message_pipe_dispatcher.cc
new file mode 100644
index 0000000000..00fc12e2aa
--- /dev/null
+++ b/mojo/core/message_pipe_dispatcher.cc
@@ -0,0 +1,426 @@
+// 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/core/message_pipe_dispatcher.h"
+
+#include <limits>
+#include <memory>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/core/core.h"
+#include "mojo/core/node_controller.h"
+#include "mojo/core/ports/event.h"
+#include "mojo/core/ports/message_filter.h"
+#include "mojo/core/request_context.h"
+#include "mojo/core/user_message_impl.h"
+
+namespace mojo {
+namespace core {
+
+namespace {
+
+#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<MessagePipeDispatcher> dispatcher)
+ : dispatcher_(dispatcher) {}
+
+ private:
+ ~PortObserverThunk() override {}
+
+ // NodeController::PortObserver:
+ void OnPortStatusChanged() override { dispatcher_->OnPortStatusChanged(); }
+
+ scoped_refptr<MessagePipeDispatcher> dispatcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(PortObserverThunk);
+};
+
+#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::UserMessageEvent& message_event) override {
+ const auto* message = message_event.GetMessage<UserMessageImpl>();
+ if (message->IsSerialized())
+ message_size_ = message->user_payload_size();
+ 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_, base::MakeRefCounted<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<ports::UserMessageEvent> message) {
+ if (port_closed_ || in_transit_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ int rv = node_controller_->SendUserMessage(port_, std::move(message));
+
+ DVLOG(4) << "Sent message on pipe " << pipe_id_ << " endpoint " << endpoint_
+ << " [port=" << port_.name() << "; rv=" << rv << "]";
+
+ 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<ports::UserMessageEvent>* message) {
+ // We can't read from a port that's closed or in transit!
+ if (port_closed_ || in_transit_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ int rv = node_controller_->node()->GetMessage(port_, message, nullptr);
+ 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;
+ }
+
+ if (!*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;
+ }
+
+ // We may need to update anyone watching our signals in case we just read the
+ // last available message.
+ base::AutoLock lock(signal_lock_);
+ watchers_.NotifyState(GetHandleSignalsStateNoLock());
+ return MOJO_RESULT_OK;
+}
+
+MojoResult MessagePipeDispatcher::SetQuota(MojoQuotaType type, uint64_t limit) {
+ switch (type) {
+ case MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH:
+ if (limit == MOJO_QUOTA_LIMIT_NONE)
+ receive_queue_length_limit_.reset();
+ else
+ receive_queue_length_limit_ = limit;
+ break;
+
+ case MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE:
+ if (limit == MOJO_QUOTA_LIMIT_NONE)
+ receive_queue_memory_size_limit_.reset();
+ else
+ receive_queue_memory_size_limit_ = limit;
+ break;
+
+ default:
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ return MOJO_RESULT_OK;
+}
+
+MojoResult MessagePipeDispatcher::QueryQuota(MojoQuotaType type,
+ uint64_t* limit,
+ uint64_t* usage) {
+ ports::PortStatus port_status;
+ if (node_controller_->node()->GetStatus(port_, &port_status) != ports::OK) {
+ CHECK(in_transit_ || port_transferred_ || port_closed_);
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ switch (type) {
+ case MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH:
+ *limit = receive_queue_length_limit_.value_or(MOJO_QUOTA_LIMIT_NONE);
+ *usage = port_status.queued_message_count;
+ break;
+
+ case MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE:
+ *limit = receive_queue_memory_size_limit_.value_or(MOJO_QUOTA_LIMIT_NONE);
+ *usage = port_status.queued_num_bytes;
+ break;
+
+ default:
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ return MOJO_RESULT_OK;
+}
+
+HandleSignalsState MessagePipeDispatcher::GetHandleSignalsState() const {
+ base::AutoLock lock(signal_lock_);
+ return GetHandleSignalsStateNoLock();
+}
+
+MojoResult MessagePipeDispatcher::AddWatcherRef(
+ const scoped_refptr<WatcherDispatcher>& 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<uint32_t>(sizeof(SerializedState));
+ *num_ports = 1;
+ *num_handles = 0;
+}
+
+bool MessagePipeDispatcher::EndSerialize(void* destination,
+ ports::PortName* ports,
+ PlatformHandle* handles) {
+ SerializedState* state = static_cast<SerializedState*>(destination);
+ state->pipe_id = pipe_id_;
+ state->endpoint = static_cast<int8_t>(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<Dispatcher> 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<const SerializedState*>(data);
+
+ ports::Node* node = Core::Get()->GetNodeController()->node();
+ ports::PortRef port;
+ if (node->GetPort(ports[0], &port) != ports::OK)
+ return nullptr;
+
+ ports::PortStatus status;
+ if (node->GetStatus(port, &status) != ports::OK)
+ return nullptr;
+
+ return new MessagePipeDispatcher(Core::Get()->GetNodeController(), port,
+ state->pipe_id, state->endpoint);
+}
+
+MessagePipeDispatcher::~MessagePipeDispatcher() = default;
+
+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;
+ rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_PEER_REMOTE;
+ if (port_status.peer_remote)
+ rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_PEER_REMOTE;
+ } else {
+ rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED;
+ }
+ if (receive_queue_length_limit_ &&
+ port_status.queued_message_count > *receive_queue_length_limit_) {
+ rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED;
+ } else if (receive_queue_memory_size_limit_ &&
+ port_status.queued_num_bytes > *receive_queue_memory_size_limit_) {
+ rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED;
+ }
+ rv.satisfiable_signals |=
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED;
+ 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) {
+ std::unique_ptr<ports::UserMessageEvent> 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 core
+} // namespace mojo
diff --git a/mojo/core/message_pipe_dispatcher.h b/mojo/core/message_pipe_dispatcher.h
new file mode 100644
index 0000000000..4fef708099
--- /dev/null
+++ b/mojo/core/message_pipe_dispatcher.h
@@ -0,0 +1,116 @@
+// 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_CORE_MESSAGE_PIPE_DISPATCHER_H_
+#define MOJO_CORE_MESSAGE_PIPE_DISPATCHER_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <queue>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "mojo/core/atomic_flag.h"
+#include "mojo/core/dispatcher.h"
+#include "mojo/core/ports/port_ref.h"
+#include "mojo/core/watcher_set.h"
+
+namespace mojo {
+namespace core {
+
+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<ports::UserMessageEvent> message) override;
+ MojoResult ReadMessage(
+ std::unique_ptr<ports::UserMessageEvent>* message) override;
+ MojoResult SetQuota(MojoQuotaType type, uint64_t limit) override;
+ MojoResult QueryQuota(MojoQuotaType type,
+ uint64_t* limit,
+ uint64_t* usage) override;
+ HandleSignalsState GetHandleSignalsState() const override;
+ MojoResult AddWatcherRef(const scoped_refptr<WatcherDispatcher>& 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<Dispatcher> 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_;
+ base::Optional<uint64_t> receive_queue_length_limit_;
+ base::Optional<uint64_t> receive_queue_memory_size_limit_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessagePipeDispatcher);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_MESSAGE_PIPE_DISPATCHER_H_
diff --git a/mojo/core/message_pipe_perftest.cc b/mojo/core/message_pipe_perftest.cc
new file mode 100644
index 0000000000..93c833a559
--- /dev/null
+++ b/mojo/core/message_pipe_perftest.cc
@@ -0,0 +1,161 @@
+// 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 <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <utility>
+
+#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/core/embedder/embedder.h"
+#include "mojo/core/handle_signals_state.h"
+#include "mojo/core/test/mojo_test_base.h"
+#include "mojo/core/test/test_utils.h"
+#include "mojo/core/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 core {
+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(
+ WriteMessageRaw(MessagePipeHandle(mp), payload_.data(), 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);
+ CHECK_EQ(ReadMessageRaw(MessagePipeHandle(mp), &read_buffer_, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ CHECK_EQ(read_buffer_.size(), payload_.size());
+ }
+
+ void SendQuitMessage(MojoHandle mp) {
+ CHECK_EQ(WriteMessageRaw(MessagePipeHandle(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<unsigned>(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::vector<uint8_t> buffer;
+ 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;
+ }
+
+ CHECK_EQ(ReadMessageRaw(MessagePipeHandle(mp), &buffer, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+
+ // Empty message indicates quit.
+ if (buffer.empty())
+ break;
+
+ CHECK_EQ(
+ WriteMessageRaw(MessagePipeHandle(mp), buffer.data(), buffer.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::vector<uint8_t> read_buffer_;
+ std::unique_ptr<base::PerfTimeLogger> 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) {
+ RunTestClient("PingPongClient", [&](MojoHandle h) { RunPingPongServer(h); });
+}
+
+} // namespace
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/message_pipe_unittest.cc b/mojo/core/message_pipe_unittest.cc
new file mode 100644
index 0000000000..20c71ff454
--- /dev/null
+++ b/mojo/core/message_pipe_unittest.cc
@@ -0,0 +1,556 @@
+// 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 <stdint.h>
+#include <string.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "build/build_config.h"
+#include "mojo/core/test/mojo_test_base.h"
+#include "mojo/core/test_utils.h"
+#include "mojo/public/c/system/core.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace mojo {
+namespace core {
+namespace {
+
+const MojoHandleSignals kAllSignals =
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_PEER_REMOTE |
+ MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED;
+
+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 mojo::WriteMessageRaw(MessagePipeHandle(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) {
+ MojoMessageHandle message_handle;
+ MojoResult rv =
+ MojoReadMessage(message_pipe_handle, nullptr, &message_handle);
+ if (rv != MOJO_RESULT_OK)
+ return rv;
+
+ const uint32_t expected_num_bytes = *num_bytes;
+ void* buffer;
+ rv = MojoGetMessageData(message_handle, nullptr, &buffer, num_bytes,
+ nullptr, nullptr);
+
+ if (rv == MOJO_RESULT_RESOURCE_EXHAUSTED) {
+ CHECK(may_discard);
+ } else if (*num_bytes) {
+ CHECK_EQ(MOJO_RESULT_OK, rv);
+ CHECK_GE(expected_num_bytes, *num_bytes);
+ CHECK(bytes);
+ memcpy(bytes, buffer, *num_bytes);
+ }
+ CHECK_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message_handle));
+ return rv;
+ }
+
+ 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<uint32_t>(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<uint32_t>(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.
+ buffer[0] = 123;
+ buffer[1] = 456;
+ buffer_size = kBufferSize;
+ ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe1_, buffer, &buffer_size));
+ ASSERT_EQ(static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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 = kBufferSize;
+ ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe0_, buffer, &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, BasicWaiting) {
+ MojoHandleSignalsState hss;
+
+ int32_t buffer[1];
+ const uint32_t kBufferSize = static_cast<uint32_t>(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_TRUE(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+ ASSERT_TRUE(hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+
+ // Port 1 should not be writable now or ever again.
+ hss = MojoHandleSignalsState();
+
+ ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_WRITABLE, &hss));
+ ASSERT_FALSE(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+ ASSERT_FALSE(hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+
+ // But it should still be readable.
+ hss = MojoHandleSignalsState();
+ ASSERT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &hss));
+ ASSERT_TRUE(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_TRUE(hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE);
+
+ // 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_FALSE(hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_FALSE(hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+}
+
+#if !defined(OS_IOS)
+
+const size_t kPingPongHandlesPerIteration = 30;
+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);
+ }
+
+ RunTestClient("HandlePingPong", [&](MojoHandle h) {
+ for (size_t i = 0; i < kPingPongIterations; i++) {
+ WriteMessageWithHandles(h, "", c, kPingPongHandlesPerIteration);
+ ReadMessageWithHandles(h, c, kPingPongHandlesPerIteration);
+ }
+ WriteMessage(h, "quit", 4);
+ });
+ 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);
+ }
+
+ RunTestClient("HandlePingPong", [&](MojoHandle h) {
+ for (size_t i = 0; i < kPingPongIterations; i++) {
+ WriteMessageWithHandles(h, "", p, kPingPongHandlesPerIteration);
+ ReadMessageWithHandles(h, p, kPingPongHandlesPerIteration);
+ }
+ WriteMessage(h, "quit", 4);
+ });
+ for (size_t i = 0; i < kPingPongHandlesPerIteration; ++i)
+ MojoClose(p[i]);
+}
+
+TEST_F(MessagePipeTest, SharedBufferHandlePingPong) {
+ MojoHandle buffers[kPingPongHandlesPerIteration];
+ for (size_t i = 0; i < kPingPongHandlesPerIteration; ++i)
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateSharedBuffer(1, nullptr, &buffers[i]));
+
+ RunTestClient("HandlePingPong", [&](MojoHandle h) {
+ for (size_t i = 0; i < kPingPongIterations; i++) {
+ WriteMessageWithHandles(h, "", buffers, kPingPongHandlesPerIteration);
+ ReadMessageWithHandles(h, buffers, kPingPongHandlesPerIteration);
+ }
+ WriteMessage(h, "quit", 4);
+ });
+ for (size_t i = 0; i < kPingPongHandlesPerIteration; ++i)
+ MojoClose(buffers[i]);
+}
+
+#endif // !defined(OS_IOS)
+
+TEST_F(FuseMessagePipeTest, Basic) {
+ // Test that we can fuse pipes and they still work.
+
+ MojoHandle a, b, c, d;
+ CreateMessagePipe(&a, &b);
+ CreateMessagePipe(&c, &d);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoFuseMessagePipes(b, c, nullptr));
+
+ // Handles b and c should be closed.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(b));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(c));
+
+ const std::string kTestMessage1 = "Hello, world!";
+ const std::string kTestMessage2 = "Goodbye, world!";
+
+ WriteMessage(a, kTestMessage1);
+ EXPECT_EQ(kTestMessage1, ReadMessage(d));
+
+ WriteMessage(d, kTestMessage2);
+ EXPECT_EQ(kTestMessage2, ReadMessage(a));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
+}
+
+TEST_F(FuseMessagePipeTest, FuseAfterPeerWrite) {
+ // Test that messages written before fusion are eventually delivered.
+
+ MojoHandle a, b, c, d;
+ CreateMessagePipe(&a, &b);
+ CreateMessagePipe(&c, &d);
+
+ const std::string kTestMessage1 = "Hello, world!";
+ const std::string kTestMessage2 = "Goodbye, world!";
+ WriteMessage(a, kTestMessage1);
+ WriteMessage(d, kTestMessage2);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoFuseMessagePipes(b, c, nullptr));
+
+ // Handles b and c should be closed.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(b));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(c));
+
+ EXPECT_EQ(kTestMessage1, ReadMessage(d));
+ EXPECT_EQ(kTestMessage2, ReadMessage(a));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
+}
+
+TEST_F(FuseMessagePipeTest, NoFuseAfterWrite) {
+ // Test that a pipe endpoint which has been written to cannot be fused.
+
+ MojoHandle a, b, c, d;
+ CreateMessagePipe(&a, &b);
+ CreateMessagePipe(&c, &d);
+
+ WriteMessage(b, "shouldn't have done that!");
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoFuseMessagePipes(b, c, nullptr));
+
+ // Handles b and c should be closed.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(b));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(c));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
+}
+
+TEST_F(FuseMessagePipeTest, NoFuseSelf) {
+ // Test that a pipe's own endpoints can't be fused together.
+
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoFuseMessagePipes(a, b, nullptr));
+
+ // Handles a and b should be closed.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(a));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(b));
+}
+
+TEST_F(FuseMessagePipeTest, FuseInvalidArguments) {
+ MojoHandle a, b, c, d;
+ CreateMessagePipe(&a, &b);
+ CreateMessagePipe(&c, &d);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+
+ // Can't fuse an invalid handle.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoFuseMessagePipes(b, c, nullptr));
+
+ // Handle c should be closed.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(c));
+
+ // Can't fuse a non-message pipe handle.
+ MojoHandle e, f;
+ CreateDataPipe(&e, &f, 16);
+
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoFuseMessagePipes(e, d, nullptr));
+
+ // Handles d and e should be closed.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(d));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(e));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(f));
+}
+
+TEST_F(FuseMessagePipeTest, FuseAfterPeerClosure) {
+ // Test that peer closure prior to fusion can still be detected after fusion.
+
+ MojoHandle a, b, c, d;
+ CreateMessagePipe(&a, &b);
+ CreateMessagePipe(&c, &d);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoFuseMessagePipes(b, c, nullptr));
+
+ // Handles b and c should be closed.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(b));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(c));
+
+ EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(d, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
+}
+
+TEST_F(FuseMessagePipeTest, FuseAfterPeerWriteAndClosure) {
+ // Test that peer write and closure prior to fusion still results in the
+ // both message arrival and awareness of peer closure.
+
+ MojoHandle a, b, c, d;
+ CreateMessagePipe(&a, &b);
+ CreateMessagePipe(&c, &d);
+
+ const std::string kTestMessage = "ayyy lmao";
+ WriteMessage(a, kTestMessage);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoFuseMessagePipes(b, c, nullptr));
+
+ // Handles b and c should be closed.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(b));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(c));
+
+ EXPECT_EQ(kTestMessage, ReadMessage(d));
+ EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(d, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
+}
+
+TEST_F(MessagePipeTest, ClosePipesStressTest) {
+ // Stress test to exercise https://crbug.com/665869.
+ const size_t kNumPipes = 100000;
+ for (size_t i = 0; i < kNumPipes; ++i) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+ MojoClose(a);
+ MojoClose(b);
+ }
+}
+
+} // namespace
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/message_unittest.cc b/mojo/core/message_unittest.cc
new file mode 100644
index 0000000000..ce44f22144
--- /dev/null
+++ b/mojo/core/message_unittest.cc
@@ -0,0 +1,956 @@
+// 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 <algorithm>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "base/numerics/safe_math.h"
+#include "base/rand_util.h"
+#include "build/build_config.h"
+#include "mojo/core/test/mojo_test_base.h"
+#include "mojo/core/user_message_impl.h"
+#include "mojo/public/cpp/platform/platform_channel.h"
+#include "mojo/public/cpp/system/buffer.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "mojo/public/cpp/system/platform_handle.h"
+
+namespace mojo {
+namespace core {
+namespace {
+
+using MessageTest = test::MojoTestBase;
+
+// Helper class which provides a base implementation for an unserialized user
+// message context and helpers to go between these objects and opaque message
+// handles.
+class TestMessageBase {
+ public:
+ virtual ~TestMessageBase() {}
+
+ static MojoMessageHandle MakeMessageHandle(
+ std::unique_ptr<TestMessageBase> message) {
+ MojoMessageHandle handle;
+ MojoResult rv = MojoCreateMessage(nullptr, &handle);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+
+ rv = MojoSetMessageContext(
+ handle, reinterpret_cast<uintptr_t>(message.release()),
+ &TestMessageBase::SerializeMessageContext,
+ &TestMessageBase::DestroyMessageContext, nullptr);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+
+ return handle;
+ }
+
+ template <typename T>
+ static std::unique_ptr<T> UnwrapMessageHandle(
+ MojoMessageHandle* message_handle) {
+ MojoMessageHandle handle = MOJO_HANDLE_INVALID;
+ std::swap(handle, *message_handle);
+ uintptr_t context;
+ MojoResult rv = MojoGetMessageContext(handle, nullptr, &context);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+ rv = MojoSetMessageContext(handle, 0, nullptr, nullptr, nullptr);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+ MojoDestroyMessage(handle);
+ return base::WrapUnique(reinterpret_cast<T*>(context));
+ }
+
+ protected:
+ virtual void GetSerializedSize(size_t* num_bytes, size_t* num_handles) = 0;
+ virtual void SerializeHandles(MojoHandle* handles) = 0;
+ virtual void SerializePayload(void* buffer) = 0;
+
+ private:
+ static void SerializeMessageContext(MojoMessageHandle message_handle,
+ uintptr_t context) {
+ auto* message = reinterpret_cast<TestMessageBase*>(context);
+ size_t num_bytes = 0;
+ size_t num_handles = 0;
+ message->GetSerializedSize(&num_bytes, &num_handles);
+ std::vector<MojoHandle> handles(num_handles);
+ if (num_handles)
+ message->SerializeHandles(handles.data());
+
+ MojoAppendMessageDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE;
+ void* buffer;
+ uint32_t buffer_size;
+ MojoResult rv = MojoAppendMessageData(
+ message_handle, base::checked_cast<uint32_t>(num_bytes), handles.data(),
+ base::checked_cast<uint32_t>(num_handles), &options, &buffer,
+ &buffer_size);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+ DCHECK_GE(buffer_size, base::checked_cast<uint32_t>(num_bytes));
+ if (num_bytes)
+ message->SerializePayload(buffer);
+ }
+
+ static void DestroyMessageContext(uintptr_t context) {
+ delete reinterpret_cast<TestMessageBase*>(context);
+ }
+};
+
+class NeverSerializedMessage : public TestMessageBase {
+ public:
+ NeverSerializedMessage(
+ const base::Closure& destruction_callback = base::Closure())
+ : destruction_callback_(destruction_callback) {}
+ ~NeverSerializedMessage() override {
+ if (destruction_callback_)
+ destruction_callback_.Run();
+ }
+
+ private:
+ // TestMessageBase:
+ void GetSerializedSize(size_t* num_bytes, size_t* num_handles) override {
+ NOTREACHED();
+ }
+ void SerializeHandles(MojoHandle* handles) override { NOTREACHED(); }
+ void SerializePayload(void* buffer) override { NOTREACHED(); }
+
+ const base::Closure destruction_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(NeverSerializedMessage);
+};
+
+class SimpleMessage : public TestMessageBase {
+ public:
+ SimpleMessage(const std::string& contents,
+ const base::Closure& destruction_callback = base::Closure())
+ : contents_(contents), destruction_callback_(destruction_callback) {}
+
+ ~SimpleMessage() override {
+ if (destruction_callback_)
+ destruction_callback_.Run();
+ }
+
+ void AddMessagePipe(mojo::ScopedMessagePipeHandle handle) {
+ handles_.emplace_back(std::move(handle));
+ }
+
+ std::vector<mojo::ScopedMessagePipeHandle>& handles() { return handles_; }
+
+ private:
+ // TestMessageBase:
+ void GetSerializedSize(size_t* num_bytes, size_t* num_handles) override {
+ *num_bytes = contents_.size();
+ *num_handles = handles_.size();
+ }
+
+ void SerializeHandles(MojoHandle* handles) override {
+ ASSERT_TRUE(!handles_.empty());
+ for (size_t i = 0; i < handles_.size(); ++i)
+ handles[i] = handles_[i].release().value();
+ handles_.clear();
+ }
+
+ void SerializePayload(void* buffer) override {
+ std::copy(contents_.begin(), contents_.end(), static_cast<char*>(buffer));
+ }
+
+ const std::string contents_;
+ const base::Closure destruction_callback_;
+ std::vector<mojo::ScopedMessagePipeHandle> handles_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleMessage);
+};
+
+TEST_F(MessageTest, InvalidMessageObjects) {
+ ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoDestroyMessage(MOJO_MESSAGE_HANDLE_INVALID));
+
+ ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoAppendMessageData(MOJO_MESSAGE_HANDLE_INVALID, 0, nullptr, 0,
+ nullptr, nullptr, nullptr));
+
+ ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoGetMessageData(MOJO_MESSAGE_HANDLE_INVALID, nullptr, nullptr,
+ nullptr, nullptr, nullptr));
+
+ ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoSerializeMessage(MOJO_MESSAGE_HANDLE_INVALID, nullptr));
+
+ MojoMessageHandle message_handle;
+ ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoCreateMessage(nullptr, nullptr));
+ ASSERT_EQ(MOJO_RESULT_OK, MojoCreateMessage(nullptr, &message_handle));
+ ASSERT_EQ(MOJO_RESULT_OK, MojoSetMessageContext(message_handle, 0, nullptr,
+ nullptr, nullptr));
+ ASSERT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message_handle));
+}
+
+TEST_F(MessageTest, SendLocalMessageWithContext) {
+ // Simple write+read of a message with context. Verifies that such messages
+ // are passed through a local pipe without serialization.
+ auto message = std::make_unique<NeverSerializedMessage>();
+ auto* original_message = message.get();
+
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ MojoWriteMessage(
+ a, TestMessageBase::MakeMessageHandle(std::move(message)), nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(b, MOJO_HANDLE_SIGNAL_READABLE));
+
+ MojoMessageHandle read_message_handle;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoReadMessage(b, nullptr, &read_message_handle));
+ message = TestMessageBase::UnwrapMessageHandle<NeverSerializedMessage>(
+ &read_message_handle);
+ EXPECT_EQ(original_message, message.get());
+
+ MojoClose(a);
+ MojoClose(b);
+}
+
+TEST_F(MessageTest, DestroyMessageWithContext) {
+ // Tests that |MojoDestroyMessage()| destroys any attached context.
+ bool was_deleted = false;
+ auto message = std::make_unique<NeverSerializedMessage>(
+ base::Bind([](bool* was_deleted) { *was_deleted = true; }, &was_deleted));
+ MojoMessageHandle handle =
+ TestMessageBase::MakeMessageHandle(std::move(message));
+ EXPECT_FALSE(was_deleted);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(handle));
+ EXPECT_TRUE(was_deleted);
+}
+
+const char kTestMessageWithContext1[] = "hello laziness";
+
+#if !defined(OS_IOS)
+
+const char kTestMessageWithContext2[] = "my old friend";
+const char kTestMessageWithContext3[] = "something something";
+const char kTestMessageWithContext4[] = "do moar ipc";
+
+DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReceiveMessageNoHandles, MessageTest, h) {
+ MojoTestBase::WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE);
+ auto m = MojoTestBase::ReadMessage(h);
+ EXPECT_EQ(kTestMessageWithContext1, m);
+}
+
+TEST_F(MessageTest, SerializeSimpleMessageNoHandlesWithContext) {
+ RunTestClient("ReceiveMessageNoHandles", [&](MojoHandle h) {
+ auto message = std::make_unique<SimpleMessage>(kTestMessageWithContext1);
+ MojoWriteMessage(h, TestMessageBase::MakeMessageHandle(std::move(message)),
+ nullptr);
+ });
+}
+
+TEST_F(MessageTest, SerializeDynamicallySizedMessage) {
+ RunTestClient("ReceiveMessageNoHandles", [&](MojoHandle h) {
+ MojoMessageHandle message;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessage(nullptr, &message));
+
+ void* buffer;
+ uint32_t buffer_size;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAppendMessageData(message, 0, nullptr, 0, nullptr, &buffer,
+ &buffer_size));
+
+ MojoAppendMessageDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoAppendMessageData(
+ message, sizeof(kTestMessageWithContext1) - 1,
+ nullptr, 0, &options, &buffer, &buffer_size));
+
+ memcpy(buffer, kTestMessageWithContext1,
+ sizeof(kTestMessageWithContext1) - 1);
+ MojoWriteMessage(h, message, nullptr);
+ });
+}
+
+DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReceiveMessageOneHandle, MessageTest, h) {
+ MojoTestBase::WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE);
+ MojoHandle h1;
+ auto m = MojoTestBase::ReadMessageWithHandles(h, &h1, 1);
+ EXPECT_EQ(kTestMessageWithContext1, m);
+ MojoTestBase::WriteMessage(h1, kTestMessageWithContext2);
+}
+
+TEST_F(MessageTest, SerializeSimpleMessageOneHandleWithContext) {
+ RunTestClient("ReceiveMessageOneHandle", [&](MojoHandle h) {
+ auto message = std::make_unique<SimpleMessage>(kTestMessageWithContext1);
+ mojo::MessagePipe pipe;
+ message->AddMessagePipe(std::move(pipe.handle0));
+ MojoWriteMessage(h, TestMessageBase::MakeMessageHandle(std::move(message)),
+ nullptr);
+ EXPECT_EQ(kTestMessageWithContext2,
+ MojoTestBase::ReadMessage(pipe.handle1.get().value()));
+ });
+}
+
+DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReceiveMessageWithHandles, MessageTest, h) {
+ MojoTestBase::WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE);
+ MojoHandle handles[4];
+ auto m = MojoTestBase::ReadMessageWithHandles(h, handles, 4);
+ EXPECT_EQ(kTestMessageWithContext1, m);
+ MojoTestBase::WriteMessage(handles[0], kTestMessageWithContext1);
+ MojoTestBase::WriteMessage(handles[1], kTestMessageWithContext2);
+ MojoTestBase::WriteMessage(handles[2], kTestMessageWithContext3);
+ MojoTestBase::WriteMessage(handles[3], kTestMessageWithContext4);
+}
+
+TEST_F(MessageTest, SerializeSimpleMessageWithHandlesWithContext) {
+ RunTestClient("ReceiveMessageWithHandles", [&](MojoHandle h) {
+ auto message = std::make_unique<SimpleMessage>(kTestMessageWithContext1);
+ mojo::MessagePipe pipes[4];
+ message->AddMessagePipe(std::move(pipes[0].handle0));
+ message->AddMessagePipe(std::move(pipes[1].handle0));
+ message->AddMessagePipe(std::move(pipes[2].handle0));
+ message->AddMessagePipe(std::move(pipes[3].handle0));
+ MojoWriteMessage(h, TestMessageBase::MakeMessageHandle(std::move(message)),
+ nullptr);
+ EXPECT_EQ(kTestMessageWithContext1,
+ MojoTestBase::ReadMessage(pipes[0].handle1.get().value()));
+ EXPECT_EQ(kTestMessageWithContext2,
+ MojoTestBase::ReadMessage(pipes[1].handle1.get().value()));
+ EXPECT_EQ(kTestMessageWithContext3,
+ MojoTestBase::ReadMessage(pipes[2].handle1.get().value()));
+ EXPECT_EQ(kTestMessageWithContext4,
+ MojoTestBase::ReadMessage(pipes[3].handle1.get().value()));
+ });
+}
+
+#endif // !defined(OS_IOS)
+
+TEST_F(MessageTest, SendLocalSimpleMessageWithHandlesWithContext) {
+ auto message = std::make_unique<SimpleMessage>(kTestMessageWithContext1);
+ auto* original_message = message.get();
+ mojo::MessagePipe pipes[4];
+ MojoHandle original_handles[4] = {
+ pipes[0].handle0.get().value(), pipes[1].handle0.get().value(),
+ pipes[2].handle0.get().value(), pipes[3].handle0.get().value(),
+ };
+ message->AddMessagePipe(std::move(pipes[0].handle0));
+ message->AddMessagePipe(std::move(pipes[1].handle0));
+ message->AddMessagePipe(std::move(pipes[2].handle0));
+ message->AddMessagePipe(std::move(pipes[3].handle0));
+
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ MojoWriteMessage(
+ a, TestMessageBase::MakeMessageHandle(std::move(message)), nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(b, MOJO_HANDLE_SIGNAL_READABLE));
+
+ MojoMessageHandle read_message_handle;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoReadMessage(b, nullptr, &read_message_handle));
+ message =
+ TestMessageBase::UnwrapMessageHandle<SimpleMessage>(&read_message_handle);
+ EXPECT_EQ(original_message, message.get());
+ ASSERT_EQ(4u, message->handles().size());
+ EXPECT_EQ(original_handles[0], message->handles()[0].get().value());
+ EXPECT_EQ(original_handles[1], message->handles()[1].get().value());
+ EXPECT_EQ(original_handles[2], message->handles()[2].get().value());
+ EXPECT_EQ(original_handles[3], message->handles()[3].get().value());
+
+ MojoClose(a);
+ MojoClose(b);
+}
+
+TEST_F(MessageTest, DropUnreadLocalMessageWithContext) {
+ // Verifies that if a message is sent with context over a pipe and the
+ // receiver closes without reading the message, the context is properly
+ // cleaned up.
+ bool message_was_destroyed = false;
+ auto message = std::make_unique<SimpleMessage>(
+ kTestMessageWithContext1,
+ base::Bind([](bool* was_destroyed) { *was_destroyed = true; },
+ &message_was_destroyed));
+
+ mojo::MessagePipe pipe;
+ message->AddMessagePipe(std::move(pipe.handle0));
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ MojoWriteMessage(
+ a, TestMessageBase::MakeMessageHandle(std::move(message)), nullptr));
+ MojoClose(a);
+ MojoClose(b);
+
+ EXPECT_TRUE(message_was_destroyed);
+ EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(pipe.handle1.get().value(),
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED));
+}
+
+TEST_F(MessageTest, GetMessageDataWithHandles) {
+ MojoHandle h[2];
+ CreateMessagePipe(&h[0], &h[1]);
+
+ MojoMessageHandle message_handle;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessage(nullptr, &message_handle));
+
+ MojoAppendMessageDataOptions append_data_options;
+ append_data_options.struct_size = sizeof(append_data_options);
+ append_data_options.flags = MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE;
+ const std::string kTestMessage = "hello";
+ void* buffer;
+ uint32_t buffer_size;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoAppendMessageData(
+ message_handle, static_cast<uint32_t>(kTestMessage.size()), h,
+ 2, &append_data_options, &buffer, &buffer_size));
+ memcpy(buffer, kTestMessage.data(), kTestMessage.size());
+
+ // Ignore handles the first time around. This should mean a subsequent call is
+ // allowed to grab the handles.
+ MojoGetMessageDataOptions get_data_options;
+ get_data_options.struct_size = sizeof(get_data_options);
+ get_data_options.flags = MOJO_GET_MESSAGE_DATA_FLAG_IGNORE_HANDLES;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoGetMessageData(message_handle, &get_data_options, &buffer,
+ &buffer_size, nullptr, nullptr));
+
+ // Now grab the handles.
+ uint32_t num_handles = 2;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoGetMessageData(message_handle, nullptr, &buffer,
+ &buffer_size, h, &num_handles));
+ EXPECT_EQ(2u, num_handles);
+
+ // Should still be callable as long as we ignore handles.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoGetMessageData(message_handle, &get_data_options, &buffer,
+ &buffer_size, nullptr, nullptr));
+
+ // But not if we don't.
+ EXPECT_EQ(MOJO_RESULT_NOT_FOUND,
+ MojoGetMessageData(message_handle, nullptr, &buffer, &buffer_size,
+ h, &num_handles));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message_handle));
+}
+
+TEST_F(MessageTest, ReadMessageWithContextAsSerializedMessage) {
+ bool message_was_destroyed = false;
+ std::unique_ptr<TestMessageBase> message =
+ std::make_unique<NeverSerializedMessage>(
+ base::Bind([](bool* was_destroyed) { *was_destroyed = true; },
+ &message_was_destroyed));
+
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ MojoWriteMessage(
+ a, TestMessageBase::MakeMessageHandle(std::move(message)), nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(b, MOJO_HANDLE_SIGNAL_READABLE));
+
+ MojoMessageHandle message_handle;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoReadMessage(b, nullptr, &message_handle));
+ EXPECT_FALSE(message_was_destroyed);
+
+ // Not a serialized message, so we can't get serialized contents.
+ uint32_t num_bytes = 0;
+ void* buffer;
+ uint32_t num_handles = 0;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoGetMessageData(message_handle, nullptr, &buffer, &num_bytes,
+ nullptr, &num_handles));
+ EXPECT_FALSE(message_was_destroyed);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message_handle));
+ EXPECT_TRUE(message_was_destroyed);
+
+ MojoClose(a);
+ MojoClose(b);
+}
+
+TEST_F(MessageTest, ReadSerializedMessageAsMessageWithContext) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+ MojoTestBase::WriteMessage(a, "hello there");
+ EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(b, MOJO_HANDLE_SIGNAL_READABLE));
+
+ MojoMessageHandle message_handle;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoReadMessage(b, nullptr, &message_handle));
+ uintptr_t context;
+ EXPECT_EQ(MOJO_RESULT_NOT_FOUND,
+ MojoGetMessageContext(message_handle, nullptr, &context));
+ MojoClose(a);
+ MojoClose(b);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message_handle));
+}
+
+TEST_F(MessageTest, ForceSerializeMessageWithContext) {
+ // Basic test - we can serialize a simple message.
+ bool message_was_destroyed = false;
+ auto message = std::make_unique<SimpleMessage>(
+ kTestMessageWithContext1,
+ base::Bind([](bool* was_destroyed) { *was_destroyed = true; },
+ &message_was_destroyed));
+ auto message_handle = TestMessageBase::MakeMessageHandle(std::move(message));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoSerializeMessage(message_handle, nullptr));
+ EXPECT_TRUE(message_was_destroyed);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message_handle));
+
+ // Serialize a message with a single handle. Freeing the message should close
+ // the handle.
+ message_was_destroyed = false;
+ message = std::make_unique<SimpleMessage>(
+ kTestMessageWithContext1,
+ base::Bind([](bool* was_destroyed) { *was_destroyed = true; },
+ &message_was_destroyed));
+ MessagePipe pipe1;
+ message->AddMessagePipe(std::move(pipe1.handle0));
+ message_handle = TestMessageBase::MakeMessageHandle(std::move(message));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoSerializeMessage(message_handle, nullptr));
+ EXPECT_TRUE(message_was_destroyed);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message_handle));
+ EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(pipe1.handle1.get().value(),
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED));
+
+ // Serialize a message with a handle and extract its serialized contents.
+ message_was_destroyed = false;
+ message = std::make_unique<SimpleMessage>(
+ kTestMessageWithContext1,
+ base::Bind([](bool* was_destroyed) { *was_destroyed = true; },
+ &message_was_destroyed));
+ MessagePipe pipe2;
+ message->AddMessagePipe(std::move(pipe2.handle0));
+ message_handle = TestMessageBase::MakeMessageHandle(std::move(message));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoSerializeMessage(message_handle, nullptr));
+ EXPECT_TRUE(message_was_destroyed);
+ uint32_t num_bytes = 0;
+ void* buffer = nullptr;
+ uint32_t num_handles = 0;
+ MojoHandle extracted_handle;
+ EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED,
+ MojoGetMessageData(message_handle, nullptr, &buffer, &num_bytes,
+ nullptr, &num_handles));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoGetMessageData(message_handle, nullptr, &buffer, &num_bytes,
+ &extracted_handle, &num_handles));
+ EXPECT_EQ(std::string(kTestMessageWithContext1).size(), num_bytes);
+ EXPECT_EQ(std::string(kTestMessageWithContext1),
+ base::StringPiece(static_cast<char*>(buffer), num_bytes));
+
+ // Confirm that the handle we extracted from the serialized message is still
+ // connected to the same peer, despite the fact that its handle value may have
+ // changed.
+ const char kTestMessage[] = "hey you";
+ MojoTestBase::WriteMessage(pipe2.handle1.get().value(), kTestMessage);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(extracted_handle, MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_EQ(kTestMessage, MojoTestBase::ReadMessage(extracted_handle));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message_handle));
+}
+
+TEST_F(MessageTest, DoubleSerialize) {
+ bool message_was_destroyed = false;
+ auto message = std::make_unique<SimpleMessage>(
+ kTestMessageWithContext1,
+ base::Bind([](bool* was_destroyed) { *was_destroyed = true; },
+ &message_was_destroyed));
+ auto message_handle = TestMessageBase::MakeMessageHandle(std::move(message));
+
+ // Ensure we can safely call |MojoSerializeMessage()| twice on the same
+ // message handle.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoSerializeMessage(message_handle, nullptr));
+ EXPECT_TRUE(message_was_destroyed);
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoSerializeMessage(message_handle, nullptr));
+
+ // And also check that we can call it again after we've written and read the
+ // message object from a pipe.
+ MessagePipe pipe;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(pipe.handle0->value(), message_handle, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(pipe.handle1->value(), MOJO_HANDLE_SIGNAL_READABLE));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadMessage(pipe.handle1->value(), nullptr, &message_handle));
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoSerializeMessage(message_handle, nullptr));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message_handle));
+}
+
+TEST_F(MessageTest, ExtendMessagePayload) {
+ MojoMessageHandle message;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessage(nullptr, &message));
+
+ const std::string kTestMessagePart1("hello i am message.");
+ void* buffer;
+ uint32_t buffer_size;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAppendMessageData(
+ message, static_cast<uint32_t>(kTestMessagePart1.size()),
+ nullptr, 0, nullptr, &buffer, &buffer_size));
+ ASSERT_GE(buffer_size, static_cast<uint32_t>(kTestMessagePart1.size()));
+ memcpy(buffer, kTestMessagePart1.data(), kTestMessagePart1.size());
+
+ const std::string kTestMessagePart2 = " in ur computer.";
+ const std::string kTestMessageCombined1 =
+ kTestMessagePart1 + kTestMessagePart2;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAppendMessageData(
+ message, static_cast<uint32_t>(kTestMessagePart2.size()),
+ nullptr, 0, nullptr, &buffer, &buffer_size));
+ memcpy(static_cast<uint8_t*>(buffer) + kTestMessagePart1.size(),
+ kTestMessagePart2.data(), kTestMessagePart2.size());
+
+ const std::string kTestMessagePart3 = kTestMessagePart2 + " carry ur bits.";
+ const std::string kTestMessageCombined2 =
+ kTestMessageCombined1 + kTestMessagePart3;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAppendMessageData(
+ message, static_cast<uint32_t>(kTestMessagePart3.size()),
+ nullptr, 0, nullptr, &buffer, &buffer_size));
+ memcpy(static_cast<uint8_t*>(buffer) + kTestMessageCombined1.size(),
+ kTestMessagePart3.data(), kTestMessagePart3.size());
+
+ void* payload;
+ uint32_t payload_size;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoGetMessageData(message, nullptr, &payload, &payload_size,
+ nullptr, nullptr));
+
+ MojoAppendMessageDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoAppendMessageData(message, 0, nullptr, 0,
+ &options, nullptr, nullptr));
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoGetMessageData(message, nullptr, &payload, &payload_size,
+ nullptr, nullptr));
+ EXPECT_EQ(kTestMessageCombined2.size(), payload_size);
+ EXPECT_EQ(0, memcmp(payload, kTestMessageCombined2.data(),
+ kTestMessageCombined2.size()));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message));
+}
+
+TEST_F(MessageTest, ExtendMessageWithHandlesPayload) {
+ MojoMessageHandle message;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessage(nullptr, &message));
+
+ MojoHandle handles[2];
+ CreateMessagePipe(&handles[0], &handles[1]);
+
+ const std::string kTestMessagePart1("hello i am message.");
+ void* buffer;
+ uint32_t buffer_size;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAppendMessageData(
+ message, static_cast<uint32_t>(kTestMessagePart1.size()),
+ handles, 2, nullptr, &buffer, &buffer_size));
+ ASSERT_GE(buffer_size, static_cast<uint32_t>(kTestMessagePart1.size()));
+ memcpy(buffer, kTestMessagePart1.data(), kTestMessagePart1.size());
+
+ const std::string kTestMessagePart2 = " in ur computer.";
+ const std::string kTestMessageCombined1 =
+ kTestMessagePart1 + kTestMessagePart2;
+ MojoAppendMessageDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAppendMessageData(
+ message, static_cast<uint32_t>(kTestMessagePart2.size()),
+ nullptr, 0, &options, &buffer, &buffer_size));
+ memcpy(static_cast<uint8_t*>(buffer) + kTestMessagePart1.size(),
+ kTestMessagePart2.data(), kTestMessagePart2.size());
+
+ void* payload;
+ uint32_t payload_size;
+ uint32_t num_handles = 2;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoGetMessageData(message, nullptr, &payload, &payload_size,
+ handles, &num_handles));
+ EXPECT_EQ(2u, num_handles);
+ EXPECT_EQ(kTestMessageCombined1.size(), payload_size);
+ EXPECT_EQ(0, memcmp(payload, kTestMessageCombined1.data(),
+ kTestMessageCombined1.size()));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handles[0]));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handles[1]));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message));
+}
+
+TEST_F(MessageTest, ExtendMessagePayloadLarge) {
+ // We progressively extend a message payload from small to large using various
+ // chunk sizes to test potentially interesting boundary conditions.
+ constexpr size_t kTestChunkSizes[] = {1, 2, 3, 64, 509, 4096, 16384, 65535};
+ for (const size_t kChunkSize : kTestChunkSizes) {
+ MojoMessageHandle message;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessage(nullptr, &message));
+
+ MojoHandle handles[2];
+ CreateMessagePipe(&handles[0], &handles[1]);
+
+ const std::string kTestMessageHeader("hey pretend i'm a header");
+ void* buffer;
+ uint32_t buffer_size;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAppendMessageData(
+ message, static_cast<uint32_t>(kTestMessageHeader.size()),
+ handles, 2, nullptr, &buffer, &buffer_size));
+ ASSERT_GE(buffer_size, static_cast<uint32_t>(kTestMessageHeader.size()));
+ memcpy(buffer, kTestMessageHeader.data(), kTestMessageHeader.size());
+
+ // 512 kB should be well beyond any reasonable default buffer size for the
+ // system implementation to choose, meaning that this test should guarantee
+ // several reallocations of the serialized message buffer as we
+ // progressively extend the payload to this size.
+ constexpr size_t kTestMessagePayloadSize = 512 * 1024;
+ std::vector<uint8_t> test_payload(kTestMessagePayloadSize);
+ base::RandBytes(test_payload.data(), kTestMessagePayloadSize);
+
+ size_t current_payload_size = 0;
+ while (current_payload_size < kTestMessagePayloadSize) {
+ const size_t previous_payload_size = current_payload_size;
+ current_payload_size =
+ std::min(current_payload_size + kChunkSize, kTestMessagePayloadSize);
+ const size_t current_chunk_size =
+ current_payload_size - previous_payload_size;
+ const size_t previous_total_size =
+ kTestMessageHeader.size() + previous_payload_size;
+ const size_t current_total_size =
+ kTestMessageHeader.size() + current_payload_size;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAppendMessageData(
+ message, static_cast<uint32_t>(current_chunk_size), nullptr,
+ 0, nullptr, &buffer, &buffer_size));
+ EXPECT_GE(buffer_size, static_cast<uint32_t>(current_total_size));
+ memcpy(static_cast<uint8_t*>(buffer) + previous_total_size,
+ &test_payload[previous_payload_size], current_chunk_size);
+ }
+
+ MojoAppendMessageDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAppendMessageData(message, 0, nullptr, 0, &options, nullptr,
+ nullptr));
+
+ void* payload;
+ uint32_t payload_size;
+ uint32_t num_handles = 2;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoGetMessageData(message, nullptr, &payload, &payload_size,
+ handles, &num_handles));
+ EXPECT_EQ(static_cast<uint32_t>(kTestMessageHeader.size() +
+ kTestMessagePayloadSize),
+ payload_size);
+ EXPECT_EQ(0, memcmp(payload, kTestMessageHeader.data(),
+ kTestMessageHeader.size()));
+ EXPECT_EQ(0,
+ memcmp(static_cast<uint8_t*>(payload) + kTestMessageHeader.size(),
+ test_payload.data(), kTestMessagePayloadSize));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handles[0]));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handles[1]));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message));
+ }
+}
+
+TEST_F(MessageTest, CorrectPayloadBufferBoundaries) {
+ // Exercises writes to the full extent of a message's payload under various
+ // circumstances in an effort to catch any potential bugs in internal
+ // allocations or reported size from Mojo APIs.
+
+ MojoMessageHandle message;
+ void* buffer = nullptr;
+ uint32_t buffer_size = 0;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessage(nullptr, &message));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAppendMessageData(message, 0, nullptr, 0, nullptr, &buffer,
+ &buffer_size));
+ // Fill the buffer end-to-end.
+ memset(buffer, 'x', buffer_size);
+
+ // Continuously grow and fill the message buffer several more times. Should
+ // not crash.
+ constexpr uint32_t kChunkSize = 4096;
+ constexpr size_t kNumIterations = 1000;
+ for (size_t i = 0; i < kNumIterations; ++i) {
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAppendMessageData(message, kChunkSize, nullptr, 0, nullptr,
+ &buffer, &buffer_size));
+ memset(buffer, 'x', buffer_size);
+ }
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message));
+}
+
+TEST_F(MessageTest, CommitInvalidMessageContents) {
+ // Regression test for https://crbug.com/755127. Ensures that we don't crash
+ // if we attempt to commit the contents of an unserialized message.
+ MojoMessageHandle message;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessage(nullptr, &message));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoAppendMessageData(message, 0, nullptr, 0,
+ nullptr, nullptr, nullptr));
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoAppendMessageData(message, 0, &a, 1, nullptr,
+ nullptr, nullptr));
+
+ UserMessageImpl::FailHandleSerializationForTesting(true);
+ MojoAppendMessageDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoAppendMessageData(message, 0, nullptr, 0,
+ nullptr, nullptr, nullptr));
+ UserMessageImpl::FailHandleSerializationForTesting(false);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message));
+}
+
+#if !defined(OS_IOS)
+
+TEST_F(MessageTest, ExtendPayloadWithHandlesAttached) {
+ // Regression test for https://crbug.com/748996. Verifies that internal
+ // message objects do not retain invalid payload pointers across buffer
+ // relocations.
+
+ MojoHandle handles[5];
+ CreateMessagePipe(&handles[0], &handles[1]);
+ PlatformChannel channel;
+ handles[2] =
+ WrapPlatformHandle(channel.TakeLocalEndpoint().TakePlatformHandle())
+ .release()
+ .value();
+ handles[3] =
+ WrapPlatformHandle(channel.TakeRemoteEndpoint().TakePlatformHandle())
+ .release()
+ .value();
+ handles[4] = SharedBufferHandle::Create(64).release().value();
+
+ MojoMessageHandle message;
+ void* buffer = nullptr;
+ uint32_t buffer_size = 0;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessage(nullptr, &message));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAppendMessageData(message, 0, handles, 5, nullptr, &buffer,
+ &buffer_size));
+
+ // Force buffer reallocation by extending the payload beyond the original
+ // buffer size. This should typically result in a relocation of the buffer as
+ // well -- at least often enough that breakage will be caught by automated
+ // tests.
+ MojoAppendMessageDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE;
+ uint32_t payload_size = buffer_size * 64;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAppendMessageData(message, payload_size, nullptr, 0, &options,
+ &buffer, &buffer_size));
+ ASSERT_GE(buffer_size, payload_size);
+ memset(buffer, 'x', payload_size);
+
+ RunTestClient("ReadAndIgnoreMessage", [&](MojoHandle h) {
+ // Send the message out of process to exercise the regression path where
+ // internally cached, stale payload pointers may be dereferenced and written
+ // into.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWriteMessage(h, message, nullptr));
+ });
+}
+
+DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReadAndIgnoreMessage, MessageTest, h) {
+ MojoTestBase::WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE);
+
+ MojoHandle handles[5];
+ MojoTestBase::ReadMessageWithHandles(h, handles, 5);
+ for (size_t i = 0; i < 5; ++i)
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handles[i]));
+}
+
+TEST_F(MessageTest, ExtendPayloadWithHandlesAttachedViaExtension) {
+ MojoHandle handles[5];
+ CreateMessagePipe(&handles[0], &handles[4]);
+ PlatformChannel channel;
+ handles[1] =
+ WrapPlatformHandle(channel.TakeLocalEndpoint().TakePlatformHandle())
+ .release()
+ .value();
+ handles[2] =
+ WrapPlatformHandle(channel.TakeRemoteEndpoint().TakePlatformHandle())
+ .release()
+ .value();
+ handles[3] = SharedBufferHandle::Create(64).release().value();
+
+ MojoMessageHandle message;
+ void* buffer = nullptr;
+ uint32_t buffer_size = 0;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessage(nullptr, &message));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAppendMessageData(message, 0, handles, 1, nullptr, &buffer,
+ &buffer_size));
+ uint32_t payload_size = buffer_size * 64;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAppendMessageData(message, payload_size, nullptr, 0, nullptr,
+ &buffer, nullptr));
+
+ // Add more handles.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoAppendMessageData(message, 0, handles + 1, 1,
+ nullptr, &buffer, nullptr));
+ MojoAppendMessageDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoAppendMessageData(message, 0, handles + 2, 3,
+ &options, &buffer, nullptr));
+ memset(buffer, 'x', payload_size);
+
+ RunTestClient("ReadMessageAndCheckPipe", [&](MojoHandle h) {
+ // Send the message out of process to exercise the regression path where
+ // internally cached, stale payload pointers may be dereferenced and written
+ // into.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWriteMessage(h, message, nullptr));
+ });
+}
+
+DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReadMessageAndCheckPipe, MessageTest, h) {
+ MojoTestBase::WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE);
+
+ const std::string kTestMessage("hey pipe");
+ MojoHandle handles[5];
+ MojoTestBase::ReadMessageWithHandles(h, handles, 5);
+ MojoTestBase::WriteMessage(handles[0], kTestMessage);
+ MojoTestBase::WaitForSignals(handles[4], MOJO_HANDLE_SIGNAL_READABLE);
+ EXPECT_EQ(kTestMessage, MojoTestBase::ReadMessage(handles[4]));
+ for (size_t i = 0; i < 5; ++i)
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handles[i]));
+}
+
+#endif // !defined(OS_IOS)
+
+TEST_F(MessageTest, PartiallySerializedMessagesDontLeakHandles) {
+ MojoMessageHandle message;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessage(nullptr, &message));
+
+ MojoHandle handles[2];
+ CreateMessagePipe(&handles[0], &handles[1]);
+
+ const std::string kTestMessagePart1("hello i am message.");
+ void* buffer;
+ uint32_t buffer_size;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAppendMessageData(
+ message, static_cast<uint32_t>(kTestMessagePart1.size()),
+ nullptr, 0, nullptr, &buffer, &buffer_size));
+ ASSERT_GE(buffer_size, static_cast<uint32_t>(kTestMessagePart1.size()));
+ memcpy(buffer, kTestMessagePart1.data(), kTestMessagePart1.size());
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAppendMessageData(message, 0, handles, 1, nullptr, &buffer,
+ &buffer_size));
+
+ // This must close |handles[0]|, which we can detect by observing the
+ // signal state of |handles[1].
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(handles[1], MOJO_HANDLE_SIGNAL_PEER_CLOSED));
+}
+
+} // namespace
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/mojo_core.cc b/mojo/core/mojo_core.cc
new file mode 100644
index 0000000000..9823b20170
--- /dev/null
+++ b/mojo/core/mojo_core.cc
@@ -0,0 +1,42 @@
+// 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.
+
+#include "base/time/time.h"
+#include "mojo/core/core.h"
+#include "mojo/core/entrypoints.h"
+#include "mojo/public/c/system/core.h"
+#include "mojo/public/c/system/thunks.h"
+
+extern "C" {
+
+namespace {
+
+MojoResult InitializeImpl(const struct MojoInitializeOptions* options) {
+ mojo::core::InitializeCore();
+ return MOJO_RESULT_OK;
+}
+
+MojoSystemThunks g_thunks = {0};
+
+} // namespace
+
+#if defined(WIN32)
+#define EXPORT_FROM_MOJO_CORE __declspec(dllexport)
+#else
+#define EXPORT_FROM_MOJO_CORE __attribute__((visibility("default")))
+#endif
+
+EXPORT_FROM_MOJO_CORE void MojoGetSystemThunks(MojoSystemThunks* thunks) {
+ if (!g_thunks.size) {
+ g_thunks = mojo::core::GetSystemThunks();
+ g_thunks.Initialize = InitializeImpl;
+ }
+
+ // Since this is the first version of the library, no valid system API
+ // implementation can request fewer thunks than we have available.
+ CHECK_GE(thunks->size, g_thunks.size);
+ memcpy(thunks, &g_thunks, g_thunks.size);
+}
+
+} // extern "C"
diff --git a/mojo/core/mojo_core.def b/mojo/core/mojo_core.def
new file mode 100644
index 0000000000..59c7351599
--- /dev/null
+++ b/mojo/core/mojo_core.def
@@ -0,0 +1,10 @@
+; 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.
+
+; Ensure that we export only MojoGetSystemThunks from the mojo_core library.
+
+LIBRARY "mojo_core.dll"
+
+EXPORTS
+ MojoGetSystemThunks
diff --git a/mojo/core/mojo_core_unittest.cc b/mojo/core/mojo_core_unittest.cc
new file mode 100644
index 0000000000..8428e09859
--- /dev/null
+++ b/mojo/core/mojo_core_unittest.cc
@@ -0,0 +1,39 @@
+// 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.
+
+#include "mojo/public/c/system/core.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+TEST(MojoCoreTest, SanityCheck) {
+ // Exercises some APIs against the mojo_core library and expects them to work
+ // as intended.
+
+ MojoHandle a, b;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(nullptr, &a, &b));
+
+ MojoMessageHandle m;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessage(nullptr, &m));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoSetMessageContext(m, 42, nullptr, nullptr, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWriteMessage(a, m, nullptr));
+ m = MOJO_MESSAGE_HANDLE_INVALID;
+
+ MojoHandleSignalsState state;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state));
+ EXPECT_TRUE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoReadMessage(b, nullptr, &m));
+
+ uintptr_t context = 0;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoGetMessageContext(m, nullptr, &context));
+ EXPECT_EQ(42u, context);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(m));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+}
+
+} // namespace
diff --git a/mojo/core/multiprocess_message_pipe_unittest.cc b/mojo/core/multiprocess_message_pipe_unittest.cc
new file mode 100644
index 0000000000..b69af36353
--- /dev/null
+++ b/mojo/core/multiprocess_message_pipe_unittest.cc
@@ -0,0 +1,1339 @@
+// 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 <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#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/core/handle_signals_state.h"
+#include "mojo/core/test/mojo_test_base.h"
+#include "mojo/core/test/test_utils.h"
+#include "mojo/core/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/message_pipe.h"
+#include "mojo/public/cpp/system/platform_handle.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 core {
+namespace {
+
+// Temporary helpers to avoid tons of churn as old APIs are removed. These
+// support only enough of a subset of the old APIs to satisfy the usage in these
+// tests.
+//
+// TODO(rockot): Remove these.
+MojoResult MojoReadMessage(MojoHandle pipe,
+ void* out_bytes,
+ uint32_t* num_bytes,
+ MojoHandle* out_handles,
+ uint32_t* num_handles,
+ MojoReadMessageFlags flags) {
+ std::vector<uint8_t> bytes;
+ std::vector<ScopedHandle> handles;
+ MojoResult rv =
+ ReadMessageRaw(MessagePipeHandle(pipe), &bytes, &handles, flags);
+ if (rv != MOJO_RESULT_OK)
+ return rv;
+
+ if (num_bytes)
+ *num_bytes = static_cast<uint32_t>(bytes.size());
+ if (!bytes.empty()) {
+ CHECK(out_bytes && num_bytes && *num_bytes >= bytes.size());
+ memcpy(out_bytes, bytes.data(), bytes.size());
+ }
+
+ if (num_handles)
+ *num_handles = static_cast<uint32_t>(handles.size());
+ if (!handles.empty()) {
+ CHECK(out_handles && num_handles && *num_handles >= handles.size());
+ for (size_t i = 0; i < handles.size(); ++i)
+ out_handles[i] = handles[i].release().value();
+ }
+ return MOJO_RESULT_OK;
+}
+
+MojoResult MojoWriteMessage(MojoHandle pipe,
+ const void* bytes,
+ uint32_t num_bytes,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ MojoWriteMessageFlags flags) {
+ return WriteMessageRaw(MessagePipeHandle(pipe), bytes, num_bytes, handles,
+ num_handles, flags);
+}
+
+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<test::MojoTestBase::LaunchType> {
+ 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<uint32_t>(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<uint32_t>(write_buffer.size()),
+ nullptr, 0u, MOJO_WRITE_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ }
+
+ return rv;
+}
+
+TEST_P(MultiprocessMessagePipeTestWithPeerSupport, Basic) {
+ int exit_code = RunTestClientAndGetExitCode("EchoEcho", [&](MojoHandle h) {
+ std::string hello("hello");
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ MojoWriteMessage(h, hello.data(), static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(quitquitquit.size()),
+ nullptr, 0u, MOJO_WRITE_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ });
+ EXPECT_EQ(1, exit_code);
+}
+
+TEST_P(MultiprocessMessagePipeTestWithPeerSupport, QueueMessages) {
+ static const size_t kNumMessages = 1001;
+ int exit_code = RunTestClientAndGetExitCode("EchoEcho", [&](MojoHandle 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<uint32_t>(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<uint32_t>(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<uint32_t>(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_FALSE(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_FALSE(hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ });
+ EXPECT_EQ(static_cast<int>(kNumMessages % 100), exit_code);
+}
+
+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(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ CHECK(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+
+ // It should have a shared buffer.
+ std::string read_buffer(100, '\0');
+ uint32_t num_bytes = static_cast<uint32_t>(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, nullptr, &buffer), 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<uint32_t>(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(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ CHECK(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+
+ read_buffer = std::string(100, '\0');
+ num_bytes = static_cast<uint32_t>(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) {
+ RunTestClient("CheckSharedBuffer", [&](MojoHandle h) {
+ // Make a shared buffer.
+ MojoCreateSharedBufferOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_CREATE_SHARED_BUFFER_FLAG_NONE;
+
+ MojoHandle shared_buffer;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoCreateSharedBuffer(100, &options, &shared_buffer));
+ MojoSharedBufferInfo buffer_info;
+ buffer_info.struct_size = sizeof(buffer_info);
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoGetBufferInfo(shared_buffer, nullptr, &buffer_info));
+ EXPECT_GE(buffer_info.size, 100U);
+
+ // 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));
+ buffer_info.size = 0;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoGetBufferInfo(shared_buffer, nullptr, &buffer_info));
+ EXPECT_GE(buffer_info.size, 100U);
+ MojoHandle handles[1];
+ handles[0] = duplicated_shared_buffer;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWriteMessage(h, &go1[0], static_cast<uint32_t>(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<uint32_t>(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, nullptr, &buffer),
+ 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<uint32_t>(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_FALSE(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_FALSE(hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ });
+}
+
+DEFINE_TEST_CLIENT_WITH_PIPE(CheckPlatformHandleFile,
+ MultiprocessMessagePipeTest,
+ h) {
+ HandleSignalsState hss;
+ CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss),
+ MOJO_RESULT_OK);
+ CHECK(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ CHECK(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+
+ std::string read_buffer(100, '\0');
+ uint32_t num_bytes = static_cast<uint32_t>(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) {
+ PlatformHandle h = UnwrapPlatformHandle(ScopedHandle(Handle(handles[i])));
+ CHECK(h.is_valid());
+
+ 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<size_t> {};
+
+TEST_P(MultiprocessMessagePipeTestWithPipeCount, PlatformHandlePassing) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ RunTestClient("CheckPlatformHandleFile", [&](MojoHandle h) {
+ std::vector<MojoHandle> 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());
+ ScopedHandle handle =
+ WrapPlatformHandle(test::PlatformHandleFromFILE(std::move(fp)));
+ ASSERT_TRUE(handle.is_valid());
+ handles.push_back(handle.release().value());
+ }
+
+ char message[128];
+ snprintf(message, sizeof(message), "hello %d",
+ static_cast<int>(pipe_count));
+ ASSERT_EQ(
+ MOJO_RESULT_OK,
+ MojoWriteMessage(h, message, static_cast<uint32_t>(strlen(message)),
+ &handles[0], static_cast<uint32_t>(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_FALSE(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ ASSERT_FALSE(hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ });
+}
+
+// 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): Enable the 128 and 250 pipe cases when
+ // ChannelPosix and ChannelFuchsia have support for
+ // sending larger numbers of handles per-message. See
+ // kMaxAttachedHandles in channel.cc for details.
+ testing::Values(1u, 64u /*, 128u, 250u*/));
+#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(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ CHECK(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+
+ // 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(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ CHECK(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+
+ std::string read_buffer(100, '\0');
+ uint32_t read_buffer_size = static_cast<uint32_t>(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<uint32_t>(write_buffer.size()), nullptr,
+ 0u, MOJO_WRITE_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ MojoClose(handles[0]);
+ return 0;
+}
+
+TEST_P(MultiprocessMessagePipeTestWithPeerSupport, MessagePipePassing) {
+ RunTestClient("CheckMessagePipe", [&](MojoHandle h) {
+ MojoCreateSharedBufferOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_CREATE_SHARED_BUFFER_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<uint32_t>(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<uint32_t>(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);
+ });
+}
+
+TEST_P(MultiprocessMessagePipeTestWithPeerSupport, MessagePipeTwoPassing) {
+ RunTestClient("CheckMessagePipe", [&](MojoHandle 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<uint32_t>(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<uint32_t>(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"));
+ });
+}
+
+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(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ CHECK(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+ CHECK_EQ(hss.satisfiable_signals,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_PEER_REMOTE |
+ MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);
+
+ // 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(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
+ CHECK(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+ CHECK_EQ(hss.satisfiable_signals,
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_PEER_REMOTE |
+ MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);
+
+ std::string read_buffer(100, '\0');
+ uint32_t read_buffer_size = static_cast<uint32_t>(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<uint32_t>(write_buffer.size()), nullptr,
+ 0u, MOJO_WRITE_MESSAGE_FLAG_NONE),
+ MOJO_RESULT_OK);
+ MojoClose(handles[0]);
+ return 0;
+}
+
+TEST_F(MultiprocessMessagePipeTest, DataPipeConsumer) {
+ RunTestClient("DataPipeConsumer", [&](MojoHandle h) {
+ MojoCreateSharedBufferOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_CREATE_SHARED_BUFFER_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<uint32_t>(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<uint32_t>(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);
+ });
+}
+
+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) {
+ RunTestClient("ChannelEchoClient", [&](MojoHandle 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");
+ });
+}
+
+// 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);
+ RunTestClient("EchoServiceClient", [&](MojoHandle 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");
+ });
+ 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<Handle> handles(2);
+ handles[0] = Handle(h);
+ handles[1] = Handle(p);
+ std::vector<MojoHandleSignals> 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);
+
+ RunTestClient("EchoServiceFactoryClient", [&](MojoHandle 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");
+ });
+
+ CloseHandle(echo_factory_proxy);
+ CloseHandle(echo_proxy_a);
+ CloseHandle(echo_proxy_b);
+ CloseHandle(echo_proxy_c);
+}
+
+TEST_P(MultiprocessMessagePipeTestWithPeerSupport,
+ ChannelPipesWithMultipleChildren) {
+ RunTestClient("ChannelEchoClient", [&](MojoHandle a) {
+ RunTestClient("ChannelEchoClient", [&](MojoHandle b) {
+ VerifyEcho(a, "hello child 0");
+ VerifyEcho(b, "hello child 1");
+
+ WriteMessage(a, "exit");
+ WriteMessage(b, "exit");
+ });
+ });
+}
+
+// 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);
+
+ RunTestClient("PingPongPipeClient", [&](MojoHandle 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");
+ });
+
+ 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<std::string, MojoHandle> 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) {
+ RunTestClient("CommandDrivenClient", [&](MojoHandle h0) {
+ RunTestClient("CommandDrivenClient", [&](MojoHandle 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();
+ });
+ });
+}
+
+TEST_F(MultiprocessMessagePipeTest, MoreChildToChildPipes) {
+ RunTestClient("CommandDrivenClient", [&](MojoHandle h0) {
+ RunTestClient("CommandDrivenClient", [&](MojoHandle h1) {
+ RunTestClient("CommandDrivenClient", [&](MojoHandle h2) {
+ RunTestClient("CommandDrivenClient", [&](MojoHandle 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();
+ });
+ });
+ });
+ });
+}
+
+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) {
+ RunTestClient("ReceivePipeWithClosedPeer", [&](MojoHandle 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);
+ });
+}
+
+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) {
+ RunTestClient("SendOtherChildPipeWithClosedPeer", [&](MojoHandle kid_a) {
+ RunTestClient(
+ "ReceivePipeWithClosedPeerFromOtherChild", [&](MojoHandle 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);
+ });
+
+ WriteMessage(kid_a, "quit");
+ });
+}
+
+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]);
+
+ RunTestClient("WriteCloseSendPeerClient", [&](MojoHandle 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");
+ });
+}
+
+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,
+ base::SequencedTaskRunnerHandle::Get());
+ 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]);
+
+ RunTestClient("MessagePipeStatusChangeInTransitClient",
+ [&](MojoHandle 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]);
+ });
+}
+
+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));
+}
+
+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 core
+} // namespace mojo
diff --git a/mojo/core/node_channel.cc b/mojo/core/node_channel.cc
new file mode 100644
index 0000000000..ebcb8812e1
--- /dev/null
+++ b/mojo/core/node_channel.cc
@@ -0,0 +1,731 @@
+// 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/core/node_channel.h"
+
+#include <cstring>
+#include <limits>
+#include <sstream>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "mojo/core/channel.h"
+#include "mojo/core/configuration.h"
+#include "mojo/core/core.h"
+#include "mojo/core/request_context.h"
+
+namespace mojo {
+namespace core {
+
+namespace {
+
+// NOTE: Please ONLY append messages to the end of this enum.
+enum class MessageType : uint32_t {
+ ACCEPT_INVITEE,
+ ACCEPT_INVITATION,
+ ADD_BROKER_CLIENT,
+ BROKER_CLIENT_ADDED,
+ ACCEPT_BROKER_CLIENT,
+ EVENT_MESSAGE,
+ REQUEST_PORT_MERGE,
+ REQUEST_INTRODUCTION,
+ INTRODUCE,
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
+ RELAY_EVENT_MESSAGE,
+#endif
+ BROADCAST_EVENT,
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
+ EVENT_MESSAGE_FROM_RELAY,
+#endif
+ ACCEPT_PEER,
+};
+
+struct Header {
+ MessageType type;
+ uint32_t padding;
+};
+
+static_assert(IsAlignedForChannelMessage(sizeof(Header)),
+ "Invalid header size.");
+
+struct AcceptInviteeData {
+ ports::NodeName inviter_name;
+ ports::NodeName token;
+};
+
+struct AcceptInvitationData {
+ ports::NodeName token;
+ ports::NodeName invitee_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 inviter 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 RelayEventMessageData {
+ ports::NodeName destination;
+};
+
+// This struct is followed by the full payload of a relayed message.
+struct EventMessageFromRelayData {
+ ports::NodeName source;
+};
+#endif
+
+template <typename DataType>
+Channel::MessagePtr CreateMessage(MessageType type,
+ size_t payload_size,
+ size_t num_handles,
+ DataType** out_data,
+ size_t capacity = 0) {
+ const size_t total_size = payload_size + sizeof(Header);
+ if (capacity == 0)
+ capacity = total_size;
+ else
+ capacity = std::max(total_size, capacity);
+ auto message =
+ std::make_unique<Channel::Message>(capacity, total_size, num_handles);
+ Header* header = reinterpret_cast<Header*>(message->mutable_payload());
+ header->type = type;
+ header->padding = 0;
+ *out_data = reinterpret_cast<DataType*>(&header[1]);
+ return message;
+}
+
+template <typename DataType>
+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<const DataType*>(
+ static_cast<const char*>(bytes) + sizeof(Header));
+ return true;
+}
+
+} // namespace
+
+// static
+scoped_refptr<NodeChannel> NodeChannel::Create(
+ Delegate* delegate,
+ ConnectionParams connection_params,
+ scoped_refptr<base::TaskRunner> 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::CreateEventMessage(size_t capacity,
+ size_t payload_size,
+ void** payload,
+ size_t num_handles) {
+ return CreateMessage(MessageType::EVENT_MESSAGE, payload_size, num_handles,
+ payload, capacity);
+}
+
+// static
+void NodeChannel::GetEventMessageData(Channel::Message* message,
+ void** data,
+ size_t* num_data_bytes) {
+ // NOTE: OnChannelMessage guarantees that we never accept a Channel::Message
+ // with a payload of fewer than |sizeof(Header)| bytes.
+ *data = reinterpret_cast<Header*>(message->mutable_payload()) + 1;
+ *num_data_bytes = message->payload_size() - sizeof(Header);
+}
+
+void NodeChannel::Start() {
+ base::AutoLock lock(channel_lock_);
+ // ShutDown() may have already been called, in which case |channel_| is null.
+ if (channel_)
+ channel_->Start();
+}
+
+void NodeChannel::ShutDown() {
+ 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(ScopedProcessHandle process_handle) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ {
+ base::AutoLock lock(channel_lock_);
+ if (channel_)
+ channel_->set_remote_process(process_handle.Clone());
+ }
+ base::AutoLock lock(remote_process_handle_lock_);
+ DCHECK(!remote_process_handle_.is_valid());
+ CHECK_NE(remote_process_handle_.get(), base::GetCurrentProcessHandle());
+ remote_process_handle_ = std::move(process_handle);
+}
+
+bool NodeChannel::HasRemoteProcessHandle() {
+ base::AutoLock lock(remote_process_handle_lock_);
+ return remote_process_handle_.is_valid();
+}
+
+ScopedProcessHandle NodeChannel::CloneRemoteProcessHandle() {
+ base::AutoLock lock(remote_process_handle_lock_);
+ if (!remote_process_handle_.is_valid())
+ return ScopedProcessHandle();
+ return remote_process_handle_.Clone();
+}
+
+void NodeChannel::SetRemoteNodeName(const ports::NodeName& name) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ remote_node_name_ = name;
+}
+
+void NodeChannel::AcceptInvitee(const ports::NodeName& inviter_name,
+ const ports::NodeName& token) {
+ AcceptInviteeData* data;
+ Channel::MessagePtr message = CreateMessage(
+ MessageType::ACCEPT_INVITEE, sizeof(AcceptInviteeData), 0, &data);
+ data->inviter_name = inviter_name;
+ data->token = token;
+ WriteChannelMessage(std::move(message));
+}
+
+void NodeChannel::AcceptInvitation(const ports::NodeName& token,
+ const ports::NodeName& invitee_name) {
+ AcceptInvitationData* data;
+ Channel::MessagePtr message = CreateMessage(
+ MessageType::ACCEPT_INVITATION, sizeof(AcceptInvitationData), 0, &data);
+ data->token = token;
+ data->invitee_name = invitee_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,
+ ScopedProcessHandle process_handle) {
+ AddBrokerClientData* data;
+ std::vector<PlatformHandle> handles;
+#if defined(OS_WIN)
+ handles.emplace_back(base::win::ScopedHandle(process_handle.release()));
+#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.get();
+ data->padding = 0;
+#endif
+ WriteChannelMessage(std::move(message));
+}
+
+void NodeChannel::BrokerClientAdded(const ports::NodeName& client_name,
+ PlatformHandle broker_channel) {
+ BrokerClientAddedData* data;
+ std::vector<PlatformHandle> handles;
+ if (broker_channel.is_valid())
+ handles.emplace_back(std::move(broker_channel));
+ 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,
+ PlatformHandle broker_channel) {
+ AcceptBrokerClientData* data;
+ std::vector<PlatformHandle> handles;
+ if (broker_channel.is_valid())
+ handles.emplace_back(std::move(broker_channel));
+ 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::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,
+ PlatformHandle channel_handle) {
+ IntroductionData* data;
+ std::vector<PlatformHandle> handles;
+ if (channel_handle.is_valid())
+ handles.emplace_back(std::move(channel_handle));
+ 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::SendChannelMessage(Channel::MessagePtr message) {
+ WriteChannelMessage(std::move(message));
+}
+
+void NodeChannel::Broadcast(Channel::MessagePtr message) {
+ DCHECK(!message->has_handles());
+ void* data;
+ Channel::MessagePtr broadcast_message = CreateMessage(
+ MessageType::BROADCAST_EVENT, 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::RelayEventMessage(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 broker) will duplicate handles as needed.
+ size_t num_bytes = sizeof(RelayEventMessageData) + message->data_num_bytes();
+ RelayEventMessageData* data;
+ Channel::MessagePtr relay_message =
+ CreateMessage(MessageType::RELAY_EVENT_MESSAGE, num_bytes, 0, &data);
+ data->destination = destination;
+ memcpy(data + 1, message->data(), message->data_num_bytes());
+
+ // When the handles are duplicated in the broker, the source handles will
+ // be closed. If the broker 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.
+ //
+ // TODO(https://crbug.com/813112): We would like to be able to violate the
+ // above stated assumption. We should not leak handles in cases where we
+ // outlive the broker, as we may continue existing and eventually accept a new
+ // broker invitation.
+ std::vector<PlatformHandleInTransit> handles = message->TakeHandles();
+ for (auto& handle : handles)
+ handle.TakeHandle().release();
+
+#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.
+ std::vector<PlatformHandleInTransit> handles = message->TakeHandles();
+ size_t num_bytes = sizeof(RelayEventMessageData) + message->data_num_bytes();
+ RelayEventMessageData* data;
+ Channel::MessagePtr relay_message = CreateMessage(
+ MessageType::RELAY_EVENT_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::EventMessageFromRelay(const ports::NodeName& source,
+ Channel::MessagePtr message) {
+ size_t num_bytes =
+ sizeof(EventMessageFromRelayData) + message->payload_size();
+ EventMessageFromRelayData* data;
+ Channel::MessagePtr relayed_message =
+ CreateMessage(MessageType::EVENT_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<base::TaskRunner> 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,
+ std::vector<PlatformHandle> handles) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ 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<NodeChannel> keepalive = this;
+
+ if (payload_size <= sizeof(Header)) {
+ delegate_->OnChannelError(remote_node_name_, this);
+ return;
+ }
+
+ const Header* header = static_cast<const Header*>(payload);
+ switch (header->type) {
+ case MessageType::ACCEPT_INVITEE: {
+ const AcceptInviteeData* data;
+ if (GetMessagePayload(payload, payload_size, &data)) {
+ delegate_->OnAcceptInvitee(remote_node_name_, data->inviter_name,
+ data->token);
+ return;
+ }
+ break;
+ }
+
+ case MessageType::ACCEPT_INVITATION: {
+ const AcceptInvitationData* data;
+ if (GetMessagePayload(payload, payload_size, &data)) {
+ delegate_->OnAcceptInvitation(remote_node_name_, data->token,
+ data->invitee_name);
+ return;
+ }
+ break;
+ }
+
+ case MessageType::ADD_BROKER_CLIENT: {
+ const AddBrokerClientData* data;
+ if (GetMessagePayload(payload, payload_size, &data)) {
+#if defined(OS_WIN)
+ if (handles.size() != 1) {
+ DLOG(ERROR) << "Dropping invalid AddBrokerClient message.";
+ break;
+ }
+ delegate_->OnAddBrokerClient(remote_node_name_, data->client_name,
+ handles[0].ReleaseHandle());
+#else
+ if (!handles.empty()) {
+ 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)) {
+ if (handles.size() != 1) {
+ DLOG(ERROR) << "Dropping invalid BrokerClientAdded message.";
+ break;
+ }
+ delegate_->OnBrokerClientAdded(remote_node_name_, data->client_name,
+ std::move(handles[0]));
+ return;
+ }
+ break;
+ }
+
+ case MessageType::ACCEPT_BROKER_CLIENT: {
+ const AcceptBrokerClientData* data;
+ if (GetMessagePayload(payload, payload_size, &data)) {
+ PlatformHandle broker_channel;
+ if (handles.size() > 1) {
+ DLOG(ERROR) << "Dropping invalid AcceptBrokerClient message.";
+ break;
+ }
+ if (handles.size() == 1)
+ broker_channel = std::move(handles[0]);
+
+ delegate_->OnAcceptBrokerClient(remote_node_name_, data->broker_name,
+ std::move(broker_channel));
+ return;
+ }
+ break;
+ }
+
+ case MessageType::EVENT_MESSAGE: {
+ Channel::MessagePtr message(
+ new Channel::Message(payload_size, handles.size()));
+ message->SetHandles(std::move(handles));
+ memcpy(message->mutable_payload(), payload, payload_size);
+ delegate_->OnEventMessage(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<const char*>(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.size() > 1) {
+ DLOG(ERROR) << "Dropping invalid introduction message.";
+ break;
+ }
+ PlatformHandle channel_handle;
+ if (handles.size() == 1)
+ channel_handle = std::move(handles[0]);
+
+ 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_EVENT_MESSAGE: {
+ base::ProcessHandle from_process;
+ {
+ base::AutoLock lock(remote_process_handle_lock_);
+ // NOTE: It's safe to retain a weak reference to this process handle
+ // through the extent of this call because |this| is kept alive and
+ // |remote_process_handle_| is never reset once set.
+ from_process = remote_process_handle_.get();
+ }
+ const RelayEventMessageData* data;
+ if (GetMessagePayload(payload, payload_size, &data)) {
+ // Don't try to relay an empty message.
+ if (payload_size <= sizeof(Header) + sizeof(RelayEventMessageData))
+ break;
+
+ const void* message_start = data + 1;
+ Channel::MessagePtr message = Channel::Message::Deserialize(
+ message_start, payload_size - sizeof(Header) - sizeof(*data),
+ from_process);
+ if (!message) {
+ DLOG(ERROR) << "Dropping invalid relay message.";
+ break;
+ }
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ message->SetHandles(std::move(handles));
+#endif
+ delegate_->OnRelayEventMessage(remote_node_name_, from_process,
+ data->destination, std::move(message));
+ return;
+ }
+ break;
+ }
+#endif
+
+ case MessageType::BROADCAST_EVENT: {
+ if (payload_size <= sizeof(Header))
+ break;
+ const void* data = static_cast<const void*>(
+ reinterpret_cast<const Header*>(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::EVENT_MESSAGE_FROM_RELAY:
+ const EventMessageFromRelayData* 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);
+
+ Channel::MessagePtr message(
+ new Channel::Message(num_bytes, handles.size()));
+ message->SetHandles(std::move(handles));
+ if (num_bytes)
+ memcpy(message->mutable_payload(), data + 1, num_bytes);
+ delegate_->OnEventMessageFromRelay(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:
+ // Ignore unrecognized message types, allowing for future extensibility.
+ return;
+ }
+
+ DLOG(ERROR) << "Received invalid message. Closing channel.";
+ if (process_error_callback_)
+ process_error_callback_.Run("NodeChannel received a malformed message");
+ delegate_->OnChannelError(remote_node_name_, this);
+}
+
+void NodeChannel::OnChannelError(Channel::Error error) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ RequestContext request_context(RequestContext::Source::SYSTEM);
+
+ ShutDown();
+
+ if (process_error_callback_ &&
+ error == Channel::Error::kReceivedMalformedData) {
+ process_error_callback_.Run("Channel received a malformed message");
+ }
+
+ // |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);
+}
+
+void NodeChannel::WriteChannelMessage(Channel::MessagePtr message) {
+ // Force a crash if this process attempts to send a message larger than the
+ // maximum allowed size. This is more useful than killing a Channel when we
+ // *receive* an oversized message, as we should consider oversized message
+ // transmission to be a bug and this helps easily identify offending code.
+ CHECK(message->data_num_bytes() < GetConfiguration().max_message_num_bytes);
+
+ base::AutoLock lock(channel_lock_);
+ if (!channel_)
+ DLOG(ERROR) << "Dropping message on closed channel.";
+ else
+ channel_->Write(std::move(message));
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/node_channel.h b/mojo/core/node_channel.h
new file mode 100644
index 0000000000..5573305013
--- /dev/null
+++ b/mojo/core/node_channel.h
@@ -0,0 +1,188 @@
+// 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_CORE_NODE_CHANNEL_H_
+#define MOJO_CORE_NODE_CHANNEL_H_
+
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/queue.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/core/channel.h"
+#include "mojo/core/connection_params.h"
+#include "mojo/core/embedder/process_error_callback.h"
+#include "mojo/core/ports/name.h"
+#include "mojo/core/scoped_process_handle.h"
+
+namespace mojo {
+namespace core {
+
+// Wraps a Channel to send and receive Node control messages.
+class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>,
+ public Channel::Delegate {
+ public:
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+ virtual void OnAcceptInvitee(const ports::NodeName& from_node,
+ const ports::NodeName& inviter_name,
+ const ports::NodeName& token) = 0;
+ virtual void OnAcceptInvitation(const ports::NodeName& from_node,
+ const ports::NodeName& token,
+ const ports::NodeName& invitee_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,
+ PlatformHandle broker_channel) = 0;
+ virtual void OnAcceptBrokerClient(const ports::NodeName& from_node,
+ const ports::NodeName& broker_name,
+ PlatformHandle broker_channel) = 0;
+ virtual void OnEventMessage(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,
+ PlatformHandle 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 OnRelayEventMessage(const ports::NodeName& from_node,
+ base::ProcessHandle from_process,
+ const ports::NodeName& destination,
+ Channel::MessagePtr message) = 0;
+ virtual void OnEventMessageFromRelay(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;
+ };
+
+ static scoped_refptr<NodeChannel> Create(
+ Delegate* delegate,
+ ConnectionParams connection_params,
+ scoped_refptr<base::TaskRunner> io_task_runner,
+ const ProcessErrorCallback& process_error_callback);
+
+ static Channel::MessagePtr CreateEventMessage(size_t capacity,
+ size_t payload_size,
+ void** payload,
+ size_t num_handles);
+
+ static void GetEventMessageData(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);
+
+ void SetRemoteProcessHandle(ScopedProcessHandle process_handle);
+ bool HasRemoteProcessHandle();
+ ScopedProcessHandle CloneRemoteProcessHandle();
+
+ // Used for context in Delegate calls (via |from_node| arguments.)
+ void SetRemoteNodeName(const ports::NodeName& name);
+
+ void AcceptInvitee(const ports::NodeName& inviter_name,
+ const ports::NodeName& token);
+ void AcceptInvitation(const ports::NodeName& token,
+ const ports::NodeName& invitee_name);
+ void AcceptPeer(const ports::NodeName& sender_name,
+ const ports::NodeName& token,
+ const ports::PortName& port_name);
+ void AddBrokerClient(const ports::NodeName& client_name,
+ ScopedProcessHandle process_handle);
+ void BrokerClientAdded(const ports::NodeName& client_name,
+ PlatformHandle broker_channel);
+ void AcceptBrokerClient(const ports::NodeName& broker_name,
+ PlatformHandle broker_channel);
+ void RequestPortMerge(const ports::PortName& connector_port_name,
+ const std::string& token);
+ void RequestIntroduction(const ports::NodeName& name);
+ void Introduce(const ports::NodeName& name, PlatformHandle channel_handle);
+ void SendChannelMessage(Channel::MessagePtr message);
+ 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 RelayEventMessage(const ports::NodeName& destination,
+ Channel::MessagePtr message);
+
+ // Sends a message to its destination from a relay. This is interpreted by the
+ // receiver similarly to EventMessage, but the original source node is
+ // provided as additional message metadata from the (trusted) relay node.
+ void EventMessageFromRelay(const ports::NodeName& source,
+ Channel::MessagePtr message);
+#endif
+
+ private:
+ friend class base::RefCountedThreadSafe<NodeChannel>;
+
+ using PendingMessageQueue = base::queue<Channel::MessagePtr>;
+ using PendingRelayMessageQueue =
+ base::queue<std::pair<ports::NodeName, Channel::MessagePtr>>;
+
+ NodeChannel(Delegate* delegate,
+ ConnectionParams connection_params,
+ scoped_refptr<base::TaskRunner> io_task_runner,
+ const ProcessErrorCallback& process_error_callback);
+ ~NodeChannel() override;
+
+ // Channel::Delegate:
+ void OnChannelMessage(const void* payload,
+ size_t payload_size,
+ std::vector<PlatformHandle> handles) override;
+ void OnChannelError(Channel::Error error) override;
+
+ void WriteChannelMessage(Channel::MessagePtr message);
+
+ Delegate* const delegate_;
+ const scoped_refptr<base::TaskRunner> io_task_runner_;
+ const ProcessErrorCallback process_error_callback_;
+
+ base::Lock channel_lock_;
+ scoped_refptr<Channel> channel_;
+
+ // Must only be accessed from |io_task_runner_|'s thread.
+ ports::NodeName remote_node_name_;
+
+ base::Lock remote_process_handle_lock_;
+ ScopedProcessHandle remote_process_handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(NodeChannel);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_NODE_CHANNEL_H_
diff --git a/mojo/core/node_controller.cc b/mojo/core/node_controller.cc
new file mode 100644
index 0000000000..fc65c4d5b6
--- /dev/null
+++ b/mojo/core/node_controller.cc
@@ -0,0 +1,1272 @@
+// 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/core/node_controller.h"
+
+#include <algorithm>
+#include <limits>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/containers/queue.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop_current.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/core/broker.h"
+#include "mojo/core/broker_host.h"
+#include "mojo/core/configuration.h"
+#include "mojo/core/core.h"
+#include "mojo/core/request_context.h"
+#include "mojo/core/user_message_impl.h"
+#include "mojo/public/cpp/platform/named_platform_channel.h"
+#include "mojo/public/cpp/platform/platform_channel.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include "mojo/core/mach_port_relay.h"
+#endif
+
+#if !defined(OS_NACL)
+#include "crypto/random.h"
+#endif
+
+namespace mojo {
+namespace core {
+
+namespace {
+
+#if defined(OS_NACL)
+template <typename T>
+void GenerateRandomName(T* out) {
+ base::RandBytes(out, sizeof(T));
+}
+#else
+template <typename T>
+void GenerateRandomName(T* out) {
+ crypto::RandBytes(out, sizeof(T));
+}
+#endif
+
+ports::NodeName GetRandomNodeName() {
+ ports::NodeName name;
+ GenerateRandomName(&name);
+ return name;
+}
+
+Channel::MessagePtr SerializeEventMessage(ports::ScopedEvent event) {
+ if (event->type() == ports::Event::Type::kUserMessage) {
+ // User message events must already be partially serialized.
+ return UserMessageImpl::FinalizeEventMessage(
+ ports::Event::Cast<ports::UserMessageEvent>(&event));
+ }
+
+ void* data;
+ size_t size = event->GetSerializedSize();
+ auto message = NodeChannel::CreateEventMessage(size, size, &data, 0);
+ event->Serialize(data);
+ return message;
+}
+
+ports::ScopedEvent DeserializeEventMessage(
+ const ports::NodeName& from_node,
+ Channel::MessagePtr channel_message) {
+ void* data;
+ size_t size;
+ NodeChannel::GetEventMessageData(channel_message.get(), &data, &size);
+ auto event = ports::Event::Deserialize(data, size);
+ if (!event)
+ return nullptr;
+
+ if (event->type() != ports::Event::Type::kUserMessage)
+ return event;
+
+ // User messages require extra parsing.
+ const size_t event_size = event->GetSerializedSize();
+
+ // Note that if this weren't true, the event couldn't have been deserialized
+ // in the first place.
+ DCHECK_LE(event_size, size);
+
+ auto message_event = ports::Event::Cast<ports::UserMessageEvent>(&event);
+ auto message = UserMessageImpl::CreateFromChannelMessage(
+ message_event.get(), std::move(channel_message),
+ static_cast<uint8_t*>(data) + event_size, size - event_size);
+ message->set_source_node(from_node);
+
+ message_event->AttachMessage(std::move(message));
+ return std::move(message_event);
+}
+
+// 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::MessageLoopCurrent::DestructionObserver {
+ public:
+ static void Create(scoped_refptr<base::TaskRunner> task_runner,
+ const base::Closure& callback) {
+ if (task_runner->RunsTasksInCurrentSequence()) {
+ // 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::MessageLoopCurrent::Get()->AddDestructionObserver(this);
+ }
+
+ ~ThreadDestructionObserver() override {
+ base::MessageLoopCurrent::Get()->RemoveDestructionObserver(this);
+ }
+
+ // base::MessageLoopCurrent::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<base::TaskRunner> task_runner) {
+ io_task_runner_ = task_runner;
+ ThreadDestructionObserver::Create(
+ io_task_runner_,
+ base::Bind(&NodeController::DropAllPeers, base::Unretained(this)));
+}
+
+void NodeController::SendBrokerClientInvitation(
+ base::ProcessHandle target_process,
+ ConnectionParams connection_params,
+ const std::vector<std::pair<std::string, ports::PortRef>>& attached_ports,
+ const ProcessErrorCallback& process_error_callback) {
+ // Generate the temporary remote node name here so that it can be associated
+ // with the ports "attached" to this invitation.
+ ports::NodeName temporary_node_name;
+ GenerateRandomName(&temporary_node_name);
+
+ {
+ base::AutoLock lock(reserved_ports_lock_);
+ PortMap& port_map = reserved_ports_[temporary_node_name];
+ for (auto& entry : attached_ports) {
+ auto result = port_map.emplace(entry.first, entry.second);
+ DCHECK(result.second) << "Duplicate attachment: " << entry.first;
+ }
+ }
+
+ ScopedProcessHandle scoped_target_process =
+ ScopedProcessHandle::CloneFrom(target_process);
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&NodeController::SendBrokerClientInvitationOnIOThread,
+ base::Unretained(this), std::move(scoped_target_process),
+ std::move(connection_params), temporary_node_name,
+ process_error_callback));
+}
+
+void NodeController::AcceptBrokerClientInvitation(
+ ConnectionParams connection_params) {
+ DCHECK(!GetConfiguration().is_broker_process);
+#if !defined(OS_MACOSX) && !defined(OS_NACL_SFI) && !defined(OS_FUCHSIA)
+ // Use the bootstrap channel for the broker and receive the node's channel
+ // synchronously as the first message from the broker.
+ DCHECK(connection_params.endpoint().is_valid());
+ base::ElapsedTimer timer;
+ broker_ = std::make_unique<Broker>(
+ connection_params.TakeEndpoint().TakePlatformHandle());
+ PlatformChannelEndpoint endpoint = broker_->GetInviterEndpoint();
+
+ if (!endpoint.is_valid()) {
+ // Most likely the inviter's side of the channel has already been closed and
+ // the broker was unable to negotiate a NodeChannel pipe. In this case we
+ // can cancel our connection to our inviter.
+ DVLOG(1) << "Cannot connect to invalid inviter channel.";
+ CancelPendingPortMerges();
+ return;
+ }
+ connection_params = ConnectionParams(std::move(endpoint));
+#endif
+
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&NodeController::AcceptBrokerClientInvitationOnIOThread,
+ base::Unretained(this), std::move(connection_params)));
+}
+
+void NodeController::ConnectIsolated(ConnectionParams connection_params,
+ const ports::PortRef& port,
+ base::StringPiece connection_name) {
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&NodeController::ConnectIsolatedOnIOThread,
+ base::Unretained(this), base::Passed(&connection_params),
+ port, connection_name.as_string()));
+}
+
+void NodeController::SetPortObserver(const ports::PortRef& port,
+ scoped_refptr<PortObserver> 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();
+}
+
+int NodeController::SendUserMessage(
+ const ports::PortRef& port,
+ std::unique_ptr<ports::UserMessageEvent> message) {
+ return node_->SendUserMessage(port, std::move(message));
+}
+
+void NodeController::MergePortIntoInviter(const std::string& name,
+ const ports::PortRef& port) {
+ scoped_refptr<NodeChannel> inviter;
+ bool reject_merge = false;
+ {
+ // Hold |pending_port_merges_lock_| while getting |inviter|. Otherwise,
+ // there is a race where the inviter can be set, and |pending_port_merges_|
+ // be processed between retrieving |inviter| and adding the merge to
+ // |pending_port_merges_|.
+ base::AutoLock lock(pending_port_merges_lock_);
+ inviter = GetInviterChannel();
+ if (reject_pending_merges_) {
+ reject_merge = true;
+ } else if (!inviter) {
+ pending_port_merges_.push_back(std::make_pair(name, port));
+ return;
+ }
+ }
+ if (reject_merge) {
+ node_->ClosePort(port);
+ DVLOG(2) << "Rejecting port merge for name " << name
+ << " due to closed inviter channel.";
+ return;
+ }
+
+ inviter->RequestPortMerge(port.name(), name);
+}
+
+int NodeController::MergeLocalPorts(const ports::PortRef& port0,
+ const ports::PortRef& port1) {
+ return node_->MergeLocalPorts(port0, port1);
+}
+
+base::WritableSharedMemoryRegion NodeController::CreateSharedBuffer(
+ size_t num_bytes) {
+#if !defined(OS_MACOSX) && !defined(OS_NACL_SFI) && !defined(OS_FUCHSIA)
+ // Shared buffer creation failure is fatal, so always use the broker when we
+ // have one; unless of course the embedder forces us not to.
+ if (!GetConfiguration().force_direct_shared_memory_allocation && broker_)
+ return broker_->GetWritableSharedMemoryRegion(num_bytes);
+#endif
+ return base::WritableSharedMemoryRegion::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<NodeChannel> peer = GetPeerChannel(source_node);
+ if (peer)
+ peer->NotifyBadMessage(error);
+}
+
+void NodeController::SendBrokerClientInvitationOnIOThread(
+ ScopedProcessHandle target_process,
+ ConnectionParams connection_params,
+ ports::NodeName temporary_node_name,
+ const ProcessErrorCallback& process_error_callback) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+#if !defined(OS_MACOSX) && !defined(OS_NACL) && !defined(OS_FUCHSIA)
+ PlatformChannel node_channel;
+ ConnectionParams node_connection_params(node_channel.TakeLocalEndpoint());
+ // BrokerHost owns itself.
+ BrokerHost* broker_host =
+ new BrokerHost(target_process.get(), std::move(connection_params),
+ process_error_callback);
+ bool channel_ok = broker_host->SendChannel(
+ node_channel.TakeRemoteEndpoint().TakePlatformHandle());
+
+#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.
+ NamedPlatformChannel::Options options;
+ NamedPlatformChannel named_channel(options);
+ node_connection_params =
+ ConnectionParams(named_channel.TakeServerEndpoint());
+ broker_host->SendNamedChannel(named_channel.GetServerName());
+ }
+#else
+ CHECK(channel_ok);
+#endif // defined(OS_WIN)
+
+ scoped_refptr<NodeChannel> channel =
+ NodeChannel::Create(this, std::move(node_connection_params),
+ io_task_runner_, process_error_callback);
+
+#else // !defined(OS_MACOSX) && !defined(OS_NACL)
+ scoped_refptr<NodeChannel> 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 invitee channel with a temporary name so it can be identified
+ // as a pending invitee 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_invitations_.insert(std::make_pair(temporary_node_name, channel));
+
+ channel->SetRemoteNodeName(temporary_node_name);
+ channel->SetRemoteProcessHandle(std::move(target_process));
+ channel->Start();
+
+ channel->AcceptInvitee(name_, temporary_node_name);
+}
+
+void NodeController::AcceptBrokerClientInvitationOnIOThread(
+ ConnectionParams connection_params) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ {
+ base::AutoLock lock(inviter_lock_);
+ DCHECK(inviter_name_ == ports::kInvalidNodeName);
+
+ // At this point we don't know the inviter's name, so we can't yet insert it
+ // into our |peers_| map. That will happen as soon as we receive an
+ // AcceptInvitee message from them.
+ bootstrap_inviter_channel_ =
+ NodeChannel::Create(this, std::move(connection_params), io_task_runner_,
+ ProcessErrorCallback());
+ // Prevent the inviter pipe handle from being closed on shutdown. Pipe
+ // closure may be used by the inviter to detect the invitee process has
+ // exited.
+ bootstrap_inviter_channel_->LeakHandleOnShutdown();
+ }
+ bootstrap_inviter_channel_->Start();
+}
+
+void NodeController::ConnectIsolatedOnIOThread(
+ ConnectionParams connection_params,
+ ports::PortRef port,
+ const std::string& connection_name) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ scoped_refptr<NodeChannel> channel = NodeChannel::Create(
+ this, std::move(connection_params), io_task_runner_, {});
+
+ RequestContext request_context;
+ ports::NodeName token;
+ GenerateRandomName(&token);
+ pending_isolated_connections_.emplace(
+ token, IsolatedConnection{channel, port, connection_name});
+ if (!connection_name.empty()) {
+ // If a connection already exists with this name, drop it.
+ auto it = named_isolated_connections_.find(connection_name);
+ if (it != named_isolated_connections_.end()) {
+ ports::NodeName connection_node = it->second;
+ if (connection_node != name_) {
+ DropPeer(connection_node, nullptr);
+ } else {
+ auto pending_it = pending_isolated_connections_.find(connection_node);
+ if (pending_it != pending_isolated_connections_.end()) {
+ node_->ClosePort(pending_it->second.local_port);
+ pending_isolated_connections_.erase(pending_it);
+ }
+ named_isolated_connections_.erase(it);
+ }
+ }
+ named_isolated_connections_.emplace(connection_name, token);
+ }
+
+ channel->SetRemoteNodeName(token);
+ channel->Start();
+
+ channel->AcceptPeer(name_, token, port.name());
+}
+
+scoped_refptr<NodeChannel> 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<NodeChannel> NodeController::GetInviterChannel() {
+ ports::NodeName inviter_name;
+ {
+ base::AutoLock lock(inviter_lock_);
+ inviter_name = inviter_name_;
+ }
+ return GetPeerChannel(inviter_name);
+}
+
+scoped_refptr<NodeChannel> NodeController::GetBrokerChannel() {
+ if (GetConfiguration().is_broker_process)
+ return nullptr;
+
+ 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<NodeChannel> channel,
+ bool start_channel) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ 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_;
+
+ 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->SendChannelMessage(std::move(pending_messages.front()));
+ pending_messages.pop();
+ }
+}
+
+void NodeController::DropPeer(const ports::NodeName& name,
+ NodeChannel* channel) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ {
+ 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_invitations_.erase(name);
+ }
+
+ std::vector<ports::PortRef> ports_to_close;
+ {
+ // Clean up any reserved ports.
+ base::AutoLock lock(reserved_ports_lock_);
+ auto it = reserved_ports_.find(name);
+ if (it != reserved_ports_.end()) {
+ for (auto& entry : it->second)
+ ports_to_close.emplace_back(entry.second);
+ reserved_ports_.erase(it);
+ }
+ }
+
+ bool is_inviter;
+ {
+ base::AutoLock lock(inviter_lock_);
+ is_inviter = (name == inviter_name_ ||
+ (channel && channel == bootstrap_inviter_channel_));
+ }
+
+ // If the error comes from the inviter channel, we also need to cancel any
+ // port merge requests, so that errors can be propagated to the message
+ // pipes.
+ if (is_inviter)
+ CancelPendingPortMerges();
+
+ auto connection_it = pending_isolated_connections_.find(name);
+ if (connection_it != pending_isolated_connections_.end()) {
+ IsolatedConnection& connection = connection_it->second;
+ ports_to_close.push_back(connection.local_port);
+ if (!connection.name.empty())
+ named_isolated_connections_.erase(connection.name);
+ pending_isolated_connections_.erase(connection_it);
+ }
+
+ for (const auto& port : ports_to_close)
+ node_->ClosePort(port);
+
+ node_->LostConnectionToNode(name);
+ AttemptShutdownIfRequested();
+}
+
+void NodeController::SendPeerEvent(const ports::NodeName& name,
+ ports::ScopedEvent event) {
+ Channel::MessagePtr event_message = SerializeEventMessage(std::move(event));
+ if (!event_message)
+ return;
+ scoped_refptr<NodeChannel> peer = GetPeerChannel(name);
+#if defined(OS_WIN)
+ if (event_message->has_handles()) {
+ // If we're sending a message with handles we aren't the destination
+ // node's inviter or broker (i.e. we don't know its process handle), ask
+ // the broker to relay for us.
+ scoped_refptr<NodeChannel> broker = GetBrokerChannel();
+ if (!peer || !peer->HasRemoteProcessHandle()) {
+ if (!GetConfiguration().is_broker_process && broker) {
+ broker->RelayEventMessage(name, std::move(event_message));
+ } else {
+ base::AutoLock lock(broker_lock_);
+ pending_relay_messages_[name].emplace(std::move(event_message));
+ }
+ return;
+ }
+ }
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ if (event_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;
+ if (!GetConfiguration().is_broker_process) {
+ base::AutoLock lock(inviter_lock_);
+ use_broker = (bootstrap_inviter_channel_ ||
+ inviter_name_ != ports::kInvalidNodeName);
+ }
+
+ if (use_broker) {
+ scoped_refptr<NodeChannel> broker = GetBrokerChannel();
+ if (broker) {
+ broker->RelayEventMessage(name, std::move(event_message));
+ } else {
+ base::AutoLock lock(broker_lock_);
+ pending_relay_messages_[name].emplace(std::move(event_message));
+ }
+ return;
+ }
+ }
+#endif // defined(OS_WIN)
+
+ if (peer) {
+ peer->SendChannelMessage(std::move(event_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<NodeChannel> 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_);
+ // We may have been introduced on another thread by the time we get here.
+ // Double-check to be safe.
+ auto it = peers_.find(name);
+ if (it == peers_.end()) {
+ auto& queue = pending_peer_messages_[name];
+ needs_introduction = queue.empty();
+ queue.emplace(std::move(event_message));
+ } else {
+ peer = it->second;
+ }
+ }
+ if (needs_introduction)
+ broker->RequestIntroduction(name);
+ else if (peer)
+ peer->SendChannelMessage(std::move(event_message));
+}
+
+void NodeController::DropAllPeers() {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ std::vector<scoped_refptr<NodeChannel>> all_peers;
+ {
+ base::AutoLock lock(inviter_lock_);
+ if (bootstrap_inviter_channel_) {
+ // |bootstrap_inviter_channel_| isn't null'd here becuase we rely on its
+ // existence to determine whether or not this is the root node. Once
+ // bootstrap_inviter_channel_->ShutDown() has been called,
+ // |bootstrap_inviter_channel_| is essentially a dead object and it
+ // doesn't matter if it's deleted now or when |this| is deleted. Note:
+ // |bootstrap_inviter_channel_| is only modified on the IO thread.
+ all_peers.push_back(bootstrap_inviter_channel_);
+ }
+ }
+
+ {
+ base::AutoLock lock(peers_lock_);
+ for (const auto& peer : peers_)
+ all_peers.push_back(peer.second);
+ for (const auto& peer : pending_invitations_)
+ all_peers.push_back(peer.second);
+ peers_.clear();
+ pending_invitations_.clear();
+ pending_peer_messages_.clear();
+ pending_isolated_connections_.clear();
+ named_isolated_connections_.clear();
+ }
+
+ for (const auto& peer : all_peers)
+ peer->ShutDown();
+
+ if (destroy_on_io_thread_shutdown_)
+ delete this;
+}
+
+void NodeController::ForwardEvent(const ports::NodeName& node,
+ ports::ScopedEvent event) {
+ DCHECK(event);
+ if (node == name_)
+ node_->AcceptEvent(std::move(event));
+ else
+ SendPeerEvent(node, std::move(event));
+
+ AttemptShutdownIfRequested();
+}
+
+void NodeController::BroadcastEvent(ports::ScopedEvent event) {
+ Channel::MessagePtr channel_message = SerializeEventMessage(std::move(event));
+ DCHECK(channel_message && !channel_message->has_handles());
+
+ scoped_refptr<NodeChannel> 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<ports::UserData> user_data;
+ node_->GetUserData(port, &user_data);
+
+ PortObserver* observer = static_cast<PortObserver*>(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::OnAcceptInvitee(const ports::NodeName& from_node,
+ const ports::NodeName& inviter_name,
+ const ports::NodeName& token) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ scoped_refptr<NodeChannel> inviter;
+ {
+ base::AutoLock lock(inviter_lock_);
+ if (bootstrap_inviter_channel_ &&
+ inviter_name_ == ports::kInvalidNodeName) {
+ inviter_name_ = inviter_name;
+ inviter = bootstrap_inviter_channel_;
+ }
+ }
+
+ if (!inviter) {
+ DLOG(ERROR) << "Unexpected AcceptInvitee message from " << from_node;
+ DropPeer(from_node, nullptr);
+ return;
+ }
+
+ inviter->SetRemoteNodeName(inviter_name);
+ inviter->AcceptInvitation(token, name_);
+
+ // NOTE: The invitee does not actually add its inviter as a peer until
+ // receiving an AcceptBrokerClient message from the broker. The inviter will
+ // request that said message be sent upon receiving AcceptInvitation.
+
+ DVLOG(1) << "Broker client " << name_ << " accepting invitation from "
+ << inviter_name;
+}
+
+void NodeController::OnAcceptInvitation(const ports::NodeName& from_node,
+ const ports::NodeName& token,
+ const ports::NodeName& invitee_name) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ auto it = pending_invitations_.find(from_node);
+ if (it == pending_invitations_.end() || token != from_node) {
+ DLOG(ERROR) << "Received unexpected AcceptInvitation message from "
+ << from_node;
+ DropPeer(from_node, nullptr);
+ return;
+ }
+
+ {
+ base::AutoLock lock(reserved_ports_lock_);
+ auto it = reserved_ports_.find(from_node);
+ if (it != reserved_ports_.end()) {
+ // Swap the temporary node name's reserved ports into an entry keyed by
+ // the real node name.
+ auto result =
+ reserved_ports_.emplace(invitee_name, std::move(it->second));
+ DCHECK(result.second);
+ reserved_ports_.erase(it);
+ }
+ }
+
+ scoped_refptr<NodeChannel> channel = it->second;
+ pending_invitations_.erase(it);
+
+ DCHECK(channel);
+
+ DVLOG(1) << "Node " << name_ << " accepted invitee " << invitee_name;
+
+ AddPeer(invitee_name, channel, false /* start_channel */);
+
+ // TODO(rockot): We could simplify invitee 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<NodeChannel> broker = GetBrokerChannel();
+ if (broker) {
+ // Inform the broker of this new client.
+ broker->AddBrokerClient(invitee_name, channel->CloneRemoteProcessHandle());
+ } else {
+ // If we have no broker, either we need to wait for one, or we *are* the
+ // broker.
+ scoped_refptr<NodeChannel> inviter = GetInviterChannel();
+ if (!inviter) {
+ base::AutoLock lock(inviter_lock_);
+ inviter = bootstrap_inviter_channel_;
+ }
+
+ if (!inviter) {
+ // Yes, we're the broker. We can initialize the client directly.
+ channel->AcceptBrokerClient(name_, PlatformHandle());
+ } else {
+ // We aren't the broker, so wait for a broker connection.
+ base::AutoLock lock(broker_lock_);
+ pending_broker_clients_.push(invitee_name);
+ }
+ }
+}
+
+void NodeController::OnAddBrokerClient(const ports::NodeName& from_node,
+ const ports::NodeName& client_name,
+ base::ProcessHandle process_handle) {
+ ScopedProcessHandle scoped_process_handle(process_handle);
+
+ scoped_refptr<NodeChannel> 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;
+ }
+
+ PlatformChannel broker_channel;
+ ConnectionParams connection_params(broker_channel.TakeLocalEndpoint());
+ scoped_refptr<NodeChannel> 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;
+ }
+#endif
+ client->SetRemoteProcessHandle(std::move(scoped_process_handle));
+
+ 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.TakeRemoteEndpoint().TakePlatformHandle());
+}
+
+void NodeController::OnBrokerClientAdded(const ports::NodeName& from_node,
+ const ports::NodeName& client_name,
+ PlatformHandle broker_channel) {
+ scoped_refptr<NodeChannel> client = GetPeerChannel(client_name);
+ if (!client) {
+ DLOG(ERROR) << "BrokerClientAdded for unknown client " << 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) << "Client " << 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,
+ PlatformHandle broker_channel) {
+ DCHECK(!GetConfiguration().is_broker_process);
+
+ // This node should already have an inviter in bootstrap mode.
+ ports::NodeName inviter_name;
+ scoped_refptr<NodeChannel> inviter;
+ {
+ base::AutoLock lock(inviter_lock_);
+ inviter_name = inviter_name_;
+ inviter = bootstrap_inviter_channel_;
+ bootstrap_inviter_channel_ = nullptr;
+ }
+ DCHECK(inviter_name == from_node);
+ DCHECK(inviter);
+
+ base::queue<ports::NodeName> pending_broker_clients;
+ std::unordered_map<ports::NodeName, OutgoingMessageQueue>
+ 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 inviter as peers.
+ // Note that the broker and inviter may be the same node.
+ scoped_refptr<NodeChannel> broker;
+ if (broker_name == inviter_name) {
+ DCHECK(!broker_channel.is_valid());
+ broker = inviter;
+ } else {
+ DCHECK(broker_channel.is_valid());
+ broker = NodeChannel::Create(
+ this,
+ ConnectionParams(PlatformChannelEndpoint(std::move(broker_channel))),
+ io_task_runner_, ProcessErrorCallback());
+ AddPeer(broker_name, broker, true /* start_channel */);
+ }
+
+ AddPeer(inviter_name, inviter, false /* start_channel */);
+
+ {
+ // Complete any port merge requests we have waiting for the inviter.
+ base::AutoLock lock(pending_port_merges_lock_);
+ for (const auto& request : pending_port_merges_)
+ inviter->RequestPortMerge(request.second.name(), request.first);
+ pending_port_merges_.clear();
+ }
+
+ // Feed the broker any pending invitees of our own.
+ while (!pending_broker_clients.empty()) {
+ const ports::NodeName& invitee_name = pending_broker_clients.front();
+ auto it = pending_invitations_.find(invitee_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_invitations_.end()) {
+ broker->AddBrokerClient(invitee_name,
+ it->second->CloneRemoteProcessHandle());
+ }
+ 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->RelayEventMessage(destination, std::move(message_queue.front()));
+ message_queue.pop();
+ }
+ }
+#endif
+
+ DVLOG(1) << "Client " << name_ << " accepted by broker " << broker_name;
+}
+
+void NodeController::OnEventMessage(const ports::NodeName& from_node,
+ Channel::MessagePtr channel_message) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ auto event = DeserializeEventMessage(from_node, std::move(channel_message));
+ if (!event) {
+ // We silently ignore unparseable events, as they may come from a process
+ // running a newer version of Mojo.
+ DVLOG(1) << "Ignoring invalid or unknown event from " << from_node;
+ return;
+ }
+
+ node_->AcceptEvent(std::move(event));
+
+ AttemptShutdownIfRequested();
+}
+
+void NodeController::OnRequestPortMerge(
+ const ports::NodeName& from_node,
+ const ports::PortName& connector_port_name,
+ const std::string& name) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ DVLOG(2) << "Node " << name_ << " received RequestPortMerge for name " << name
+ << " and port " << connector_port_name << "@" << from_node;
+
+ ports::PortRef local_port;
+ {
+ base::AutoLock lock(reserved_ports_lock_);
+ auto it = reserved_ports_.find(from_node);
+ // TODO(https://crbug.com/822034): We should send a notification back to the
+ // requestor so they can clean up their dangling port in this failure case.
+ // This requires changes to the internal protocol, which can't be made yet.
+ // Until this is done, pipes from |MojoExtractMessagePipeFromInvitation()|
+ // will never break if the given name was invalid.
+ if (it == reserved_ports_.end()) {
+ DVLOG(1) << "Ignoring port merge request from node " << from_node << ". "
+ << "No ports reserved for that node.";
+ return;
+ }
+
+ PortMap& port_map = it->second;
+ auto port_it = port_map.find(name);
+ if (port_it == port_map.end()) {
+ DVLOG(1) << "Ignoring request to connect to port for unknown name "
+ << name << " from node " << from_node;
+ return;
+ }
+ local_port = port_it->second;
+ port_map.erase(port_it);
+ if (port_map.empty())
+ reserved_ports_.erase(it);
+ }
+
+ int rv = node_->MergePorts(local_port, from_node, connector_port_name);
+ if (rv != ports::OK)
+ DLOG(ERROR) << "MergePorts failed: " << rv;
+}
+
+void NodeController::OnRequestIntroduction(const ports::NodeName& from_node,
+ const ports::NodeName& name) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ scoped_refptr<NodeChannel> 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<NodeChannel> new_friend = GetPeerChannel(name);
+ if (!new_friend) {
+ // We don't know who they're talking about!
+ requestor->Introduce(name, PlatformHandle());
+ } else {
+ PlatformChannel new_channel;
+ requestor->Introduce(name,
+ new_channel.TakeLocalEndpoint().TakePlatformHandle());
+ new_friend->Introduce(
+ from_node, new_channel.TakeRemoteEndpoint().TakePlatformHandle());
+ }
+}
+
+void NodeController::OnIntroduce(const ports::NodeName& from_node,
+ const ports::NodeName& name,
+ PlatformHandle channel_handle) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ 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<NodeChannel> channel = NodeChannel::Create(
+ this,
+ ConnectionParams(PlatformChannelEndpoint(std::move(channel_handle))),
+ io_task_runner_, ProcessErrorCallback());
+
+ DVLOG(1) << "Adding new peer " << name << " via broker introduction.";
+ AddPeer(name, channel, true /* start_channel */);
+}
+
+void NodeController::OnBroadcast(const ports::NodeName& from_node,
+ Channel::MessagePtr message) {
+ DCHECK(!message->has_handles());
+
+ auto event = DeserializeEventMessage(from_node, std::move(message));
+ if (!event) {
+ // We silently ignore unparseable events, as they may come from a process
+ // running a newer version of Mojo.
+ DVLOG(1) << "Ignoring request to broadcast invalid or unknown event from "
+ << from_node;
+ return;
+ }
+
+ base::AutoLock lock(peers_lock_);
+ for (auto& iter : peers_) {
+ // Clone and send the event to each known peer. Events which cannot be
+ // cloned cannot be broadcast.
+ ports::ScopedEvent clone = event->Clone();
+ if (!clone) {
+ DVLOG(1) << "Ignoring request to broadcast invalid event from "
+ << from_node << " [type=" << static_cast<uint32_t>(event->type())
+ << "]";
+ return;
+ }
+
+ iter.second->SendChannelMessage(SerializeEventMessage(std::move(clone)));
+ }
+}
+
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
+void NodeController::OnRelayEventMessage(const ports::NodeName& from_node,
+ base::ProcessHandle from_process,
+ const ports::NodeName& destination,
+ Channel::MessagePtr message) {
+ // The broker should always know which process this came from.
+ DCHECK(from_process != base::kNullProcessHandle);
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ 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;
+ }
+
+ if (destination == name_) {
+ // Great, we can deliver this message locally.
+ OnEventMessage(from_node, std::move(message));
+ return;
+ }
+
+ scoped_refptr<NodeChannel> peer = GetPeerChannel(destination);
+ if (peer)
+ peer->EventMessageFromRelay(from_node, std::move(message));
+ else
+ DLOG(ERROR) << "Dropping relay message for unknown node " << destination;
+}
+
+void NodeController::OnEventMessageFromRelay(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;
+ }
+
+ OnEventMessage(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_->RunsTasksInCurrentSequence());
+
+ auto it = pending_isolated_connections_.find(from_node);
+ if (it == pending_isolated_connections_.end()) {
+ DLOG(ERROR) << "Received unexpected AcceptPeer message from " << from_node;
+ DropPeer(from_node, nullptr);
+ return;
+ }
+
+ IsolatedConnection& connection = it->second;
+ scoped_refptr<NodeChannel> channel = std::move(connection.channel);
+ ports::PortRef local_port = connection.local_port;
+ if (!connection.name.empty())
+ named_isolated_connections_[connection.name] = peer_name;
+ pending_isolated_connections_.erase(it);
+ DCHECK(channel);
+
+ if (name_ != peer_name) {
+ // It's possible (e.g. in tests) that we may "connect" to ourself, in which
+ // case we skip this |AddPeer()| call and go straight to merging ports.
+ // Note that we explicitly drop any prior connection to the same peer so
+ // that new isolated connections can replace old ones.
+ DropPeer(peer_name, nullptr);
+ AddPeer(peer_name, channel, false /* start_channel */);
+ DVLOG(1) << "Node " << name_ << " accepted peer " << peer_name;
+ }
+
+ // 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_->RunsTasksInCurrentSequence()) {
+ RequestContext request_context(RequestContext::Source::SYSTEM);
+ DropPeer(from_node, channel);
+ } else {
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&NodeController::OnChannelError, base::Unretained(this),
+ from_node, base::RetainedRef(channel)));
+ }
+}
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+MachPortRelay* NodeController::GetMachPortRelay() {
+ {
+ base::AutoLock lock(inviter_lock_);
+ // Return null if we're not the root.
+ if (bootstrap_inviter_channel_ || inviter_name_ != ports::kInvalidNodeName)
+ return nullptr;
+ }
+
+ base::AutoLock lock(mach_port_relay_lock_);
+ return mach_port_relay_.get();
+}
+#endif
+
+void NodeController::CancelPendingPortMerges() {
+ std::vector<ports::PortRef> 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::IsolatedConnection::IsolatedConnection() = default;
+
+NodeController::IsolatedConnection::IsolatedConnection(
+ const IsolatedConnection& other) = default;
+
+NodeController::IsolatedConnection::IsolatedConnection(
+ IsolatedConnection&& other) = default;
+
+NodeController::IsolatedConnection::IsolatedConnection(
+ scoped_refptr<NodeChannel> channel,
+ const ports::PortRef& local_port,
+ base::StringPiece name)
+ : channel(std::move(channel)), local_port(local_port), name(name) {}
+
+NodeController::IsolatedConnection::~IsolatedConnection() = default;
+
+NodeController::IsolatedConnection& NodeController::IsolatedConnection::
+operator=(const IsolatedConnection& other) = default;
+
+NodeController::IsolatedConnection& NodeController::IsolatedConnection::
+operator=(IsolatedConnection&& other) = default;
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/node_controller.h b/mojo/core/node_controller.h
new file mode 100644
index 0000000000..fac39df9e4
--- /dev/null
+++ b/mojo/core/node_controller.h
@@ -0,0 +1,346 @@
+// 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_CORE_NODE_CONTROLLER_H_
+#define MOJO_CORE_NODE_CONTROLLER_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/hash_tables.h"
+#include "base/containers/queue.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/writable_shared_memory_region.h"
+#include "base/task_runner.h"
+#include "build/build_config.h"
+#include "mojo/core/atomic_flag.h"
+#include "mojo/core/node_channel.h"
+#include "mojo/core/ports/event.h"
+#include "mojo/core/ports/name.h"
+#include "mojo/core/ports/node.h"
+#include "mojo/core/ports/node_delegate.h"
+#include "mojo/core/scoped_process_handle.h"
+#include "mojo/core/system_impl_export.h"
+#include "mojo/public/cpp/platform/platform_handle.h"
+
+namespace base {
+class PortProvider;
+}
+
+namespace mojo {
+namespace core {
+
+class Broker;
+class Core;
+class MachPortRelay;
+
+// The owner of ports::Node which facilitates core EDK implementation. All
+// public interface methods are safe to call from any thread.
+class MOJO_SYSTEM_IMPL_EXPORT 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<base::TaskRunner> 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<base::TaskRunner> io_task_runner);
+
+ // Sends an invitation to a remote process (via |connection_params|) to join
+ // this process's graph of connected processes as a broker client.
+ void SendBrokerClientInvitation(
+ base::ProcessHandle target_process,
+ ConnectionParams connection_params,
+ const std::vector<std::pair<std::string, ports::PortRef>>& attached_ports,
+ const ProcessErrorCallback& process_error_callback);
+
+ // Connects this node to the process which invited it to be a broker client.
+ void AcceptBrokerClientInvitation(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 ConnectIsolated(ConnectionParams connection_params,
+ const ports::PortRef& port,
+ base::StringPiece connection_name);
+
+ // Sets a port's observer. If |observer| is null the port's current observer
+ // is removed.
+ void SetPortObserver(const ports::PortRef& port,
+ scoped_refptr<PortObserver> 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 SendUserMessage(const ports::PortRef& port_ref,
+ std::unique_ptr<ports::UserMessageEvent> message);
+
+ // Merges a local port |port| into a port reserved by |name| in the node which
+ // invited this node.
+ void MergePortIntoInviter(const std::string& name,
+ 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.
+ base::WritableSharedMemoryRegion 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<ports::NodeName, scoped_refptr<NodeChannel>>;
+ using OutgoingMessageQueue = base::queue<Channel::MessagePtr>;
+ using PortMap = std::map<std::string, ports::PortRef>;
+
+ struct IsolatedConnection {
+ IsolatedConnection();
+ IsolatedConnection(const IsolatedConnection& other);
+ IsolatedConnection(IsolatedConnection&& other);
+ IsolatedConnection(scoped_refptr<NodeChannel> channel,
+ const ports::PortRef& local_port,
+ base::StringPiece name);
+ ~IsolatedConnection();
+
+ IsolatedConnection& operator=(const IsolatedConnection& other);
+ IsolatedConnection& operator=(IsolatedConnection&& other);
+
+ // NOTE: |channel| is null once the connection is fully established.
+ scoped_refptr<NodeChannel> channel;
+ ports::PortRef local_port;
+ std::string name;
+ };
+
+ void SendBrokerClientInvitationOnIOThread(
+ ScopedProcessHandle target_process,
+ ConnectionParams connection_params,
+ ports::NodeName token,
+ const ProcessErrorCallback& process_error_callback);
+ void AcceptBrokerClientInvitationOnIOThread(
+ ConnectionParams connection_params);
+
+ void ConnectIsolatedOnIOThread(ConnectionParams connection_params,
+ ports::PortRef port,
+ const std::string& connection_name);
+
+ scoped_refptr<NodeChannel> GetPeerChannel(const ports::NodeName& name);
+ scoped_refptr<NodeChannel> GetInviterChannel();
+ scoped_refptr<NodeChannel> GetBrokerChannel();
+
+ void AddPeer(const ports::NodeName& name,
+ scoped_refptr<NodeChannel> channel,
+ bool start_channel);
+ void DropPeer(const ports::NodeName& name, NodeChannel* channel);
+ void SendPeerEvent(const ports::NodeName& name, ports::ScopedEvent event);
+ void DropAllPeers();
+
+ // ports::NodeDelegate:
+ void ForwardEvent(const ports::NodeName& node,
+ ports::ScopedEvent event) override;
+ void BroadcastEvent(ports::ScopedEvent event) override;
+ void PortStatusChanged(const ports::PortRef& port) override;
+
+ // NodeChannel::Delegate:
+ void OnAcceptInvitee(const ports::NodeName& from_node,
+ const ports::NodeName& inviter_name,
+ const ports::NodeName& token) override;
+ void OnAcceptInvitation(const ports::NodeName& from_node,
+ const ports::NodeName& token,
+ const ports::NodeName& invitee_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,
+ PlatformHandle broker_channel) override;
+ void OnAcceptBrokerClient(const ports::NodeName& from_node,
+ const ports::NodeName& broker_name,
+ PlatformHandle broker_channel) override;
+ void OnEventMessage(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,
+ PlatformHandle 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 OnRelayEventMessage(const ports::NodeName& from_node,
+ base::ProcessHandle from_process,
+ const ports::NodeName& destination,
+ Channel::MessagePtr message) override;
+ void OnEventMessageFromRelay(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();
+#endif
+
+ // Cancels all pending port merges. These are merges which are supposed to
+ // be requested from the inviter ASAP, and they may be cancelled if the
+ // connection to the inviter 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<ports::Node> node_;
+ scoped_refptr<base::TaskRunner> io_task_runner_;
+
+ // Guards |peers_| and |pending_peer_messages_|.
+ base::Lock peers_lock_;
+
+ // Channels to known peers, including inviter and invitees, if any.
+ NodeMap peers_;
+
+ // Outgoing message queues for peers we've heard of but can't yet talk to.
+ std::unordered_map<ports::NodeName, OutgoingMessageQueue>
+ pending_peer_messages_;
+
+ // Guards |reserved_ports_|.
+ base::Lock reserved_ports_lock_;
+
+ // Ports reserved by name, per peer.
+ std::map<ports::NodeName, PortMap> reserved_ports_;
+
+ // Guards |pending_port_merges_| and |reject_pending_merges_|.
+ base::Lock pending_port_merges_lock_;
+
+ // A set of port merge requests awaiting inviter connection.
+ std::vector<std::pair<std::string, ports::PortRef>> pending_port_merges_;
+
+ // Indicates that new merge requests should be rejected because the inviter
+ // has disconnected.
+ bool reject_pending_merges_ = false;
+
+ // Guards |inviter_name_| and |bootstrap_inviter_channel_|.
+ base::Lock inviter_lock_;
+
+ // The name of the node which invited us to join its network, if any.
+ ports::NodeName inviter_name_;
+
+ // A temporary reference to the inviter channel before we know their name.
+ scoped_refptr<NodeChannel> bootstrap_inviter_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 remote broker clients waiting to be connected to the broker.
+ base::queue<ports::NodeName> pending_broker_clients_;
+
+ // Messages waiting to be relayed by the broker once it's known.
+ std::unordered_map<ports::NodeName, OutgoingMessageQueue>
+ pending_relay_messages_;
+
+ // 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 invitees during handshake.
+ NodeMap pending_invitations_;
+
+ std::map<ports::NodeName, IsolatedConnection> pending_isolated_connections_;
+ std::map<std::string, ports::NodeName> named_isolated_connections_;
+
+ // 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) && !defined(OS_FUCHSIA)
+ // Broker for sync shared buffer creation on behalf of broker clients.
+ std::unique_ptr<Broker> broker_;
+#endif
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ base::Lock mach_port_relay_lock_;
+ // Relay for transferring mach ports to/from broker clients.
+ std::unique_ptr<MachPortRelay> mach_port_relay_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(NodeController);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_NODE_CONTROLLER_H_
diff --git a/mojo/core/options_validation.h b/mojo/core/options_validation.h
new file mode 100644
index 0000000000..ae4120800a
--- /dev/null
+++ b/mojo/core/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_CORE_OPTIONS_VALIDATION_H_
+#define MOJO_CORE_OPTIONS_VALIDATION_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "mojo/core/system_impl_export.h"
+#include "mojo/public/c/system/types.h"
+
+namespace mojo {
+namespace core {
+
+template <class Options>
+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<MOJO_ALIGNOF(Options)>(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<const uint32_t*>(options);
+ if (struct_size < sizeof(uint32_t))
+ return 0;
+
+ return std::min(static_cast<size_t>(struct_size), sizeof(Options));
+ }
+
+ template <size_t alignment>
+ static bool IsAligned(const void* pointer) {
+ return reinterpret_cast<uintptr_t>(pointer) % alignment == 0;
+ }
+
+ const Options* options_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserOptionsReader);
+};
+
+// Macro to invoke |UserOptionsReader<Options>::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 core
+} // namespace mojo
+
+#endif // MOJO_CORE_OPTIONS_VALIDATION_H_
diff --git a/mojo/core/options_validation_unittest.cc b/mojo/core/options_validation_unittest.cc
new file mode 100644
index 0000000000..52e0032a89
--- /dev/null
+++ b/mojo/core/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/core/options_validation.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "mojo/public/c/system/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace core {
+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<uint32_t>(sizeof(TestOptions));
+
+TEST(OptionsValidationTest, Valid) {
+ {
+ const TestOptions kOptions = {kSizeOfTestOptions};
+ UserOptionsReader<TestOptions> 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<uint32_t>(
+ offsetof(TestOptions, struct_size) + sizeof(uint32_t))};
+ UserOptionsReader<TestOptions> 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<uint32_t>(offsetof(TestOptions, flags) + sizeof(uint32_t))};
+ UserOptionsReader<TestOptions> 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<TestOptions*>(buf);
+ options->struct_size = kSizeOfTestOptions + 1;
+ UserOptionsReader<TestOptions> 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<TestOptions*>(buf);
+ options->struct_size = kSizeOfTestOptions + 4;
+ UserOptionsReader<TestOptions> 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<uint32_t>(i)};
+ UserOptionsReader<TestOptions> 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<TestOptions> reader((nullptr)); },
+ kMemoryCheckFailedRegex);
+
+ // Unaligned:
+ EXPECT_DEATH_IF_SUPPORTED(
+ {
+ UserOptionsReader<TestOptions> reader(
+ reinterpret_cast<const TestOptions*>(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<uintptr_t>(buffer) % 8 == 0)
+ ? reinterpret_cast<TestOptions*>(&buffer[1])
+ : reinterpret_cast<TestOptions*>(&buffer[0]);
+ options->struct_size = static_cast<uint32_t>(sizeof(TestOptions));
+ UserOptionsReader<TestOptions> reader(options);
+ },
+ kMemoryCheckFailedRegex);
+}
+
+} // namespace
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/platform_handle_dispatcher.cc b/mojo/core/platform_handle_dispatcher.cc
new file mode 100644
index 0000000000..7029b96536
--- /dev/null
+++ b/mojo/core/platform_handle_dispatcher.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/core/platform_handle_dispatcher.h"
+
+#include "base/synchronization/lock.h"
+
+namespace mojo {
+namespace core {
+
+// static
+scoped_refptr<PlatformHandleDispatcher> PlatformHandleDispatcher::Create(
+ PlatformHandle platform_handle) {
+ return new PlatformHandleDispatcher(std::move(platform_handle));
+}
+
+PlatformHandle PlatformHandleDispatcher::TakePlatformHandle() {
+ 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] = std::move(platform_handle_);
+ 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;
+}
+
+void PlatformHandleDispatcher::CancelTransit() {
+ base::AutoLock lock(lock_);
+ in_transit_ = false;
+}
+
+// static
+scoped_refptr<PlatformHandleDispatcher> 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;
+
+ return PlatformHandleDispatcher::Create(std::move(handles[0]));
+}
+
+PlatformHandleDispatcher::PlatformHandleDispatcher(
+ PlatformHandle platform_handle)
+ : platform_handle_(std::move(platform_handle)) {}
+
+PlatformHandleDispatcher::~PlatformHandleDispatcher() {
+ DCHECK(is_closed_ && !in_transit_);
+ DCHECK(!platform_handle_.is_valid());
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/platform_handle_dispatcher.h b/mojo/core/platform_handle_dispatcher.h
new file mode 100644
index 0000000000..8d9627c393
--- /dev/null
+++ b/mojo/core/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_CORE_PLATFORM_HANDLE_DISPATCHER_H_
+#define MOJO_CORE_PLATFORM_HANDLE_DISPATCHER_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "mojo/core/dispatcher.h"
+#include "mojo/core/system_impl_export.h"
+#include "mojo/public/cpp/platform/platform_handle.h"
+
+namespace mojo {
+namespace core {
+
+class MOJO_SYSTEM_IMPL_EXPORT PlatformHandleDispatcher : public Dispatcher {
+ public:
+ static scoped_refptr<PlatformHandleDispatcher> Create(
+ PlatformHandle platform_handle);
+
+ PlatformHandle TakePlatformHandle();
+
+ // 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<PlatformHandleDispatcher> Deserialize(
+ const void* bytes,
+ size_t num_bytes,
+ const ports::PortName* ports,
+ size_t num_ports,
+ PlatformHandle* handles,
+ size_t num_handles);
+
+ private:
+ PlatformHandleDispatcher(PlatformHandle platform_handle);
+ ~PlatformHandleDispatcher() override;
+
+ base::Lock lock_;
+ bool in_transit_ = false;
+ bool is_closed_ = false;
+ PlatformHandle platform_handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformHandleDispatcher);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_PLATFORM_HANDLE_DISPATCHER_H_
diff --git a/mojo/core/platform_handle_dispatcher_unittest.cc b/mojo/core/platform_handle_dispatcher_unittest.cc
new file mode 100644
index 0000000000..8e3ad81f3d
--- /dev/null
+++ b/mojo/core/platform_handle_dispatcher_unittest.cc
@@ -0,0 +1,125 @@
+// 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/core/platform_handle_dispatcher.h"
+
+#include <stdio.h>
+#include <utility>
+
+#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/core/test/test_utils.h"
+#include "mojo/public/cpp/system/platform_handle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace core {
+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()));
+
+ PlatformHandle h = test::PlatformHandleFromFILE(std::move(fp));
+ EXPECT_FALSE(fp);
+ ASSERT_TRUE(h.is_valid());
+
+ scoped_refptr<PlatformHandleDispatcher> dispatcher =
+ PlatformHandleDispatcher::Create(std::move(h));
+ EXPECT_FALSE(h.is_valid());
+ EXPECT_EQ(Dispatcher::Type::PLATFORM_HANDLE, dispatcher->GetType());
+
+ h = dispatcher->TakePlatformHandle();
+ 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.)
+ auto internal_handle = dispatcher->TakePlatformHandle();
+ EXPECT_FALSE(internal_handle.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<PlatformHandleDispatcher> 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);
+
+ PlatformHandle received_handle;
+ EXPECT_TRUE(dispatcher->EndSerialize(nullptr, nullptr, &received_handle));
+
+ dispatcher->CompleteTransitAndClose();
+
+ EXPECT_TRUE(received_handle.is_valid());
+
+ PlatformHandle handle = dispatcher->TakePlatformHandle();
+ EXPECT_FALSE(handle.is_valid());
+
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, dispatcher->Close());
+
+ dispatcher = static_cast<PlatformHandleDispatcher*>(
+ Dispatcher::Deserialize(Dispatcher::Type::PLATFORM_HANDLE, nullptr,
+ num_bytes, nullptr, num_ports, &received_handle,
+ 1u)
+ .get());
+
+ EXPECT_FALSE(received_handle.is_valid());
+ EXPECT_TRUE(dispatcher->GetType() == Dispatcher::Type::PLATFORM_HANDLE);
+
+ fp = test::FILEFromPlatformHandle(dispatcher->TakePlatformHandle(), "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 core
+} // namespace mojo
diff --git a/mojo/core/platform_handle_in_transit.cc b/mojo/core/platform_handle_in_transit.cc
new file mode 100644
index 0000000000..7b82f27c34
--- /dev/null
+++ b/mojo/core/platform_handle_in_transit.cc
@@ -0,0 +1,156 @@
+// 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.
+
+#include "mojo/core/platform_handle_in_transit.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/process/process_handle.h"
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+
+#include "base/win/scoped_handle.h"
+#endif
+
+namespace mojo {
+namespace core {
+
+namespace {
+
+#if defined(OS_WIN)
+HANDLE TransferHandle(HANDLE handle,
+ base::ProcessHandle from_process,
+ base::ProcessHandle to_process) {
+ BOOL result =
+ ::DuplicateHandle(from_process, handle, to_process, &handle, 0, FALSE,
+ DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
+ if (result) {
+ return handle;
+ } else {
+ DPLOG(ERROR) << "DuplicateHandle failed";
+ return INVALID_HANDLE_VALUE;
+ }
+}
+
+void CloseHandleInProcess(HANDLE handle, const ScopedProcessHandle& process) {
+ DCHECK_NE(handle, INVALID_HANDLE_VALUE);
+ DCHECK(process.is_valid());
+
+ // The handle lives in |process|, so we close it there using a special
+ // incantation of |DuplicateHandle()|.
+ //
+ // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724251 for
+ // this usage of |DuplicateHandle()|, particularly where it says "to close a
+ // handle from the source process...". Note that although the documentation
+ // says that the target *handle* address must be NULL, it seems that the
+ // target process handle being NULL is what really matters here.
+ BOOL result = ::DuplicateHandle(process.get(), handle, NULL, &handle, 0,
+ FALSE, DUPLICATE_CLOSE_SOURCE);
+ if (!result) {
+ DPLOG(ERROR) << "DuplicateHandle failed";
+ }
+}
+#endif
+
+} // namespace
+
+PlatformHandleInTransit::PlatformHandleInTransit() = default;
+
+PlatformHandleInTransit::PlatformHandleInTransit(PlatformHandle handle)
+ : handle_(std::move(handle)) {}
+
+PlatformHandleInTransit::PlatformHandleInTransit(
+ PlatformHandleInTransit&& other) {
+ *this = std::move(other);
+}
+
+PlatformHandleInTransit::~PlatformHandleInTransit() {
+#if defined(OS_WIN)
+ if (!owning_process_.is_valid()) {
+ DCHECK_EQ(remote_handle_, INVALID_HANDLE_VALUE);
+ return;
+ }
+
+ CloseHandleInProcess(remote_handle_, owning_process_);
+#endif
+}
+
+PlatformHandleInTransit& PlatformHandleInTransit::operator=(
+ PlatformHandleInTransit&& other) {
+#if defined(OS_WIN)
+ if (owning_process_.is_valid()) {
+ DCHECK_NE(remote_handle_, INVALID_HANDLE_VALUE);
+ CloseHandleInProcess(remote_handle_, owning_process_);
+ }
+
+ remote_handle_ = INVALID_HANDLE_VALUE;
+ std::swap(remote_handle_, other.remote_handle_);
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ mach_port_name_ = MACH_PORT_NULL;
+ std::swap(mach_port_name_, other.mach_port_name_);
+#endif
+ handle_ = std::move(other.handle_);
+ owning_process_ = std::move(other.owning_process_);
+ return *this;
+}
+
+PlatformHandle PlatformHandleInTransit::TakeHandle() {
+ DCHECK(!owning_process_.is_valid());
+ return std::move(handle_);
+}
+
+void PlatformHandleInTransit::CompleteTransit() {
+#if defined(OS_WIN)
+ remote_handle_ = INVALID_HANDLE_VALUE;
+#endif
+ handle_.release();
+ owning_process_ = ScopedProcessHandle();
+}
+
+bool PlatformHandleInTransit::TransferToProcess(
+ ScopedProcessHandle target_process) {
+ DCHECK(target_process.is_valid());
+ DCHECK(!owning_process_.is_valid());
+ DCHECK(handle_.is_valid());
+#if defined(OS_WIN)
+ remote_handle_ =
+ TransferHandle(handle_.ReleaseHandle(), base::GetCurrentProcessHandle(),
+ target_process.get());
+ if (remote_handle_ == INVALID_HANDLE_VALUE)
+ return false;
+#endif
+ owning_process_ = std::move(target_process);
+ return true;
+}
+
+#if defined(OS_WIN)
+// static
+PlatformHandle PlatformHandleInTransit::TakeIncomingRemoteHandle(
+ HANDLE handle,
+ base::ProcessHandle owning_process) {
+ return PlatformHandle(base::win::ScopedHandle(
+ TransferHandle(handle, owning_process, base::GetCurrentProcessHandle())));
+}
+#endif
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+// static
+PlatformHandleInTransit PlatformHandleInTransit::CreateForMachPortName(
+ mach_port_t name) {
+ if (name == MACH_PORT_NULL) {
+ return PlatformHandleInTransit(
+ PlatformHandle(base::mac::ScopedMachSendRight()));
+ }
+
+ PlatformHandleInTransit handle;
+ handle.mach_port_name_ = name;
+ return handle;
+}
+#endif
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/platform_handle_in_transit.h b/mojo/core/platform_handle_in_transit.h
new file mode 100644
index 0000000000..b75a01fbb0
--- /dev/null
+++ b/mojo/core/platform_handle_in_transit.h
@@ -0,0 +1,107 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_CORE_PLATFORM_HANDLE_IN_TRANSIT_H_
+#define MOJO_CORE_PLATFORM_HANDLE_IN_TRANSIT_H_
+
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "mojo/core/scoped_process_handle.h"
+#include "mojo/public/cpp/platform/platform_handle.h"
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include <mach/mach.h>
+#endif
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace mojo {
+namespace core {
+
+// Owns a PlatformHandle which may actually belong to another process. On
+// Windows and (sometimes) Mac, handles in a message object may take on values
+// which only have meaning in the context of a remote process.
+//
+// This class provides a safe way of scoping the lifetime of such handles so
+// that they don't leak when transmission can't be completed.
+class PlatformHandleInTransit {
+ public:
+ PlatformHandleInTransit();
+ explicit PlatformHandleInTransit(PlatformHandle handle);
+ PlatformHandleInTransit(PlatformHandleInTransit&&);
+ ~PlatformHandleInTransit();
+
+ PlatformHandleInTransit& operator=(PlatformHandleInTransit&&);
+
+ // Accessor for the owned handle. Must be owned by the calling process.
+ const PlatformHandle& handle() const {
+ DCHECK(!owning_process_.is_valid());
+ return handle_;
+ }
+
+ // Returns the process which owns this handle. If this is invalid, the handle
+ // is owned by the current process.
+ const ScopedProcessHandle& owning_process() const { return owning_process_; }
+
+ // Takes ownership of the held handle as-is. The handle must belong to the
+ // current process.
+ PlatformHandle TakeHandle();
+
+ // Discards the handle owned by this object. The implication is that its
+ // value has been successfully communicated to the owning process and the
+ // calling process is no longer responsible for managing the handle's
+ // lifetime.
+ void CompleteTransit();
+
+ // Transfers ownership of this (local) handle to |target_process|.
+ bool TransferToProcess(ScopedProcessHandle target_process);
+
+#if defined(OS_WIN)
+ HANDLE remote_handle() const { return remote_handle_; }
+
+ // Returns a new local handle, with ownership of |handle| being transferred
+ // from |owning_process| to the caller.
+ static PlatformHandle TakeIncomingRemoteHandle(
+ HANDLE handle,
+ base::ProcessHandle owning_process);
+#endif
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // Creates a special wrapper holding an unowned Mach port name. This may refer
+ // to a send or receive right in a remote task (process), and is used for
+ // cases where message must retain such an object as one of its attached
+ // handles. We're OK for now with leaking in any scenario where a lack of
+ // strict ownership could cause leakage. See https://crbug.com/855930 for more
+ // details.
+ static PlatformHandleInTransit CreateForMachPortName(mach_port_t name);
+
+ bool is_mach_port_name() const { return mach_port_name_ != MACH_PORT_NULL; }
+ mach_port_t mach_port_name() const { return mach_port_name_; }
+#endif
+
+ private:
+#if defined(OS_WIN)
+ // We don't use a ScopedHandle (or, by extension, PlatformHandle) here because
+ // the handle verifier expects all handle values to be owned by this process.
+ // On Windows we use |handle_| for locally owned handles and |remote_handle_|
+ // otherwise. On all other platforms we use |handle_| regardless of ownership.
+ HANDLE remote_handle_ = INVALID_HANDLE_VALUE;
+#endif
+
+ PlatformHandle handle_;
+ ScopedProcessHandle owning_process_;
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ mach_port_t mach_port_name_ = MACH_PORT_NULL;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformHandleInTransit);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_PLATFORM_HANDLE_IN_TRANSIT_H_
diff --git a/mojo/core/platform_handle_utils.cc b/mojo/core/platform_handle_utils.cc
new file mode 100644
index 0000000000..edd0cff063
--- /dev/null
+++ b/mojo/core/platform_handle_utils.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "mojo/core/platform_handle_utils.h"
+
+#include "build/build_config.h"
+
+#if defined(OS_FUCHSIA)
+#include <lib/zx/vmo.h>
+#elif defined(OS_POSIX)
+#include "base/files/scoped_file.h"
+#elif defined(OS_WIN)
+#include "base/win/scoped_handle.h"
+#endif
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include "base/mac/scoped_mach_port.h"
+#endif
+
+namespace mojo {
+namespace core {
+
+void ExtractPlatformHandlesFromSharedMemoryRegionHandle(
+ base::subtle::PlatformSharedMemoryRegion::ScopedPlatformHandle handle,
+ PlatformHandle* extracted_handle,
+ PlatformHandle* extracted_readonly_handle) {
+#if defined(OS_WIN)
+ *extracted_handle = PlatformHandle(base::win::ScopedHandle(handle.Take()));
+#elif defined(OS_FUCHSIA)
+ *extracted_handle = PlatformHandle(std::move(handle));
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ // This is a Mach port. Same code as above and below, but separated for
+ // clarity.
+ *extracted_handle = PlatformHandle(std::move(handle));
+#elif defined(OS_ANDROID)
+ // This is a file descriptor. Same code as above, but separated for clarity.
+ *extracted_handle = PlatformHandle(std::move(handle));
+#else
+ *extracted_handle = PlatformHandle(std::move(handle.fd));
+ *extracted_readonly_handle = PlatformHandle(std::move(handle.readonly_fd));
+#endif
+}
+
+base::subtle::PlatformSharedMemoryRegion::ScopedPlatformHandle
+CreateSharedMemoryRegionHandleFromPlatformHandles(
+ PlatformHandle handle,
+ PlatformHandle readonly_handle) {
+#if defined(OS_WIN)
+ DCHECK(!readonly_handle.is_valid());
+ return handle.TakeHandle();
+#elif defined(OS_FUCHSIA)
+ DCHECK(!readonly_handle.is_valid());
+ return zx::vmo(handle.TakeHandle());
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ DCHECK(!readonly_handle.is_valid());
+ return handle.TakeMachPort();
+#elif defined(OS_ANDROID)
+ DCHECK(!readonly_handle.is_valid());
+ return handle.TakeFD();
+#else
+ return base::subtle::ScopedFDPair(handle.TakeFD(), readonly_handle.TakeFD());
+#endif
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/platform_handle_utils.h b/mojo/core/platform_handle_utils.h
new file mode 100644
index 0000000000..b18275b5a4
--- /dev/null
+++ b/mojo/core/platform_handle_utils.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_CORE_PLATFORM_HANDLE_UTILS_H_
+#define MOJO_CORE_PLATFORM_HANDLE_UTILS_H_
+
+#include "base/memory/platform_shared_memory_region.h"
+#include "mojo/core/system_impl_export.h"
+#include "mojo/public/c/system/platform_handle.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/platform/platform_handle.h"
+
+namespace mojo {
+namespace core {
+
+// Converts a base shared memory platform handle into one (maybe two on POSIX)
+// PlatformHandle(s).
+MOJO_SYSTEM_IMPL_EXPORT void ExtractPlatformHandlesFromSharedMemoryRegionHandle(
+ base::subtle::PlatformSharedMemoryRegion::ScopedPlatformHandle handle,
+ PlatformHandle* extracted_handle,
+ PlatformHandle* extracted_readonly_handle);
+
+// Converts one (maybe two on POSIX) PlatformHandle(s) to a base shared memory
+// platform handle.
+MOJO_SYSTEM_IMPL_EXPORT
+base::subtle::PlatformSharedMemoryRegion::ScopedPlatformHandle
+CreateSharedMemoryRegionHandleFromPlatformHandles(
+ PlatformHandle handle,
+ PlatformHandle readonly_handle);
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_PLATFORM_HANDLE_UTILS_H_
diff --git a/mojo/core/platform_shared_memory_mapping.cc b/mojo/core/platform_shared_memory_mapping.cc
new file mode 100644
index 0000000000..6b8d67201f
--- /dev/null
+++ b/mojo/core/platform_shared_memory_mapping.cc
@@ -0,0 +1,104 @@
+// 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.
+
+#include "mojo/core/platform_shared_memory_mapping.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "base/memory/writable_shared_memory_region.h"
+#include "base/sys_info.h"
+#include "build/build_config.h"
+
+#if defined(OS_NACL)
+// For getpagesize() on NaCl.
+#include <unistd.h>
+#endif
+
+namespace mojo {
+namespace core {
+
+namespace {
+
+size_t GetPageSize() {
+#if defined(OS_NACL)
+ // base::SysInfo isn't available under NaCl.
+ return getpagesize();
+#else
+ return base::SysInfo::VMAllocationGranularity();
+#endif
+}
+
+} // namespace
+
+PlatformSharedMemoryMapping::PlatformSharedMemoryMapping(
+ base::subtle::PlatformSharedMemoryRegion* region,
+ size_t offset,
+ size_t length)
+ : type_(region->GetMode() ==
+ base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly
+ ? Type::kReadOnly
+ : Type::kWritable),
+ offset_(offset),
+ length_(length) {
+ // Mojo shared buffers can be mapped at any offset, but //base shared memory
+ // regions must be mapped at a page boundary. We calculate the nearest whole
+ // page offset and map from there.
+ size_t offset_rounding = offset_ % GetPageSize();
+ off_t real_offset = static_cast<off_t>(offset_ - offset_rounding);
+ size_t real_length = length_ + offset_rounding;
+ void* mapped_memory = nullptr;
+ if (type_ == Type::kReadOnly) {
+ auto read_only_region =
+ base::ReadOnlySharedMemoryRegion::Deserialize(std::move(*region));
+ auto read_only_mapping = read_only_region.MapAt(real_offset, real_length);
+ mapped_memory = const_cast<void*>(read_only_mapping.memory());
+ mapping_ = std::make_unique<base::ReadOnlySharedMemoryMapping>(
+ std::move(read_only_mapping));
+ *region = base::ReadOnlySharedMemoryRegion::TakeHandleForSerialization(
+ std::move(read_only_region));
+ } else if (region->GetMode() ==
+ base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe) {
+ auto unsafe_region =
+ base::UnsafeSharedMemoryRegion::Deserialize(std::move(*region));
+ auto writable_mapping = unsafe_region.MapAt(real_offset, real_length);
+ mapped_memory = writable_mapping.memory();
+ mapping_ = std::make_unique<base::WritableSharedMemoryMapping>(
+ std::move(writable_mapping));
+ *region = base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
+ std::move(unsafe_region));
+ } else {
+ DCHECK_EQ(region->GetMode(),
+ base::subtle::PlatformSharedMemoryRegion::Mode::kWritable);
+ auto writable_region =
+ base::WritableSharedMemoryRegion::Deserialize(std::move(*region));
+ auto writable_mapping = writable_region.MapAt(real_offset, real_length);
+ mapped_memory = writable_mapping.memory();
+ mapping_ = std::make_unique<base::WritableSharedMemoryMapping>(
+ std::move(writable_mapping));
+ *region = base::WritableSharedMemoryRegion::TakeHandleForSerialization(
+ std::move(writable_region));
+ }
+
+ base_ = static_cast<char*>(mapped_memory) + offset_rounding;
+}
+
+PlatformSharedMemoryMapping::~PlatformSharedMemoryMapping() = default;
+
+bool PlatformSharedMemoryMapping::IsValid() const {
+ return mapping_ && mapping_->IsValid();
+}
+
+void* PlatformSharedMemoryMapping::GetBase() const {
+ return base_;
+}
+
+size_t PlatformSharedMemoryMapping::GetLength() const {
+ return length_;
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/platform_shared_memory_mapping.h b/mojo/core/platform_shared_memory_mapping.h
new file mode 100644
index 0000000000..b7c43ace9e
--- /dev/null
+++ b/mojo/core/platform_shared_memory_mapping.h
@@ -0,0 +1,60 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_CORE_PLATFORM_SHARED_MEMORY_MAPPING_H_
+#define MOJO_CORE_PLATFORM_SHARED_MEMORY_MAPPING_H_
+
+#include <stddef.h>
+
+#include <memory>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/platform_shared_memory_region.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "mojo/core/system_impl_export.h"
+
+namespace mojo {
+namespace core {
+
+// A mapping of a |base::subtle::PlatformSharedMemoryRegion|, created
+// exclusively by |SharedBufferDispatcher::MapBuffer()|. Automatically maps
+// upon construction and unmaps upon destruction.
+//
+// Mappings are NOT thread-safe.
+//
+// This may represent either a |base::ReadOnlySharedMemoryMapping| OR a
+// |base::WritableSharedMemoryMapping|, and it supports non-page-aligned base
+// offsets for convenience.
+class MOJO_SYSTEM_IMPL_EXPORT PlatformSharedMemoryMapping {
+ public:
+ enum class Type {
+ kReadOnly,
+ kWritable,
+ };
+
+ PlatformSharedMemoryMapping(base::subtle::PlatformSharedMemoryRegion* region,
+ size_t offset,
+ size_t length);
+ ~PlatformSharedMemoryMapping();
+
+ bool IsValid() const;
+
+ void* GetBase() const;
+ size_t GetLength() const;
+
+ private:
+ const Type type_;
+ const size_t offset_;
+ const size_t length_;
+ void* base_ = nullptr;
+ std::unique_ptr<base::SharedMemoryMapping> mapping_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformSharedMemoryMapping);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_PLATFORM_SHARED_MEMORY_MAPPING_H_
diff --git a/mojo/core/platform_wrapper_unittest.cc b/mojo/core/platform_wrapper_unittest.cc
new file mode 100644
index 0000000000..e795b94920
--- /dev/null
+++ b/mojo/core/platform_wrapper_unittest.cc
@@ -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.
+
+#include <stdint.h>
+#include <string.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/memory/shared_memory.h"
+#include "base/process/process_handle.h"
+#include "build/build_config.h"
+#include "mojo/core/test/mojo_test_base.h"
+#include "mojo/public/c/system/platform_handle.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+#if defined(OS_WIN)
+#define SIMPLE_PLATFORM_HANDLE_TYPE MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#define SIMPLE_PLATFORM_HANDLE_TYPE MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR
+#endif
+
+#if defined(OS_FUCHSIA)
+#define SHARED_BUFFER_PLATFORM_HANDLE_TYPE \
+ MOJO_PLATFORM_HANDLE_TYPE_FUCHSIA_HANDLE
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+#define SHARED_BUFFER_PLATFORM_HANDLE_TYPE MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT
+#elif defined(OS_WIN) || defined(OS_POSIX)
+#define SHARED_BUFFER_PLATFORM_HANDLE_TYPE SIMPLE_PLATFORM_HANDLE_TYPE
+#endif
+
+uint64_t PlatformHandleValueFromPlatformFile(base::PlatformFile file) {
+#if defined(OS_WIN)
+ return reinterpret_cast<uint64_t>(file);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ return static_cast<uint64_t>(file);
+#endif
+}
+
+base::PlatformFile PlatformFileFromPlatformHandleValue(uint64_t value) {
+#if defined(OS_WIN)
+ return reinterpret_cast<base::PlatformFile>(value);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ return static_cast<base::PlatformFile>(value);
+#endif
+}
+
+namespace mojo {
+namespace core {
+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<int>(kMessage.size())),
+ static_cast<int>(kMessage.size()));
+
+ RunTestClient("ReadPlatformFile", [&](MojoHandle 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, nullptr, &wrapped_handle));
+
+ WriteMessageWithHandles(h, kMessage, &wrapped_handle, 1);
+ });
+
+ 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, nullptr,
+ &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<char> data(message.size());
+ EXPECT_EQ(file.ReadAtCurrentPos(data.data(), static_cast<int>(data.size())),
+ static_cast<int>(data.size()));
+ EXPECT_TRUE(std::equal(message.begin(), message.end(), data.begin()));
+}
+
+TEST_F(PlatformWrapperTest, WrapPlatformSharedMemoryRegion) {
+ // 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());
+
+ RunTestClient("ReadPlatformSharedBuffer", [&](MojoHandle 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_WIN)
+ os_buffer.value = reinterpret_cast<uint64_t>(memory_handle.GetHandle());
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ os_buffer.value = static_cast<uint64_t>(memory_handle.GetMemoryObject());
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ os_buffer.value = static_cast<uint64_t>(memory_handle.GetHandle());
+#else
+#error Unsupported platform
+#endif
+
+ MojoSharedBufferGuid mojo_guid;
+ base::UnguessableToken guid = memory_handle.GetGUID();
+ mojo_guid.high = guid.GetHighForSerialization();
+ mojo_guid.low = guid.GetLowForSerialization();
+
+ MojoHandle wrapped_handle;
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoWrapPlatformSharedMemoryRegion(
+ &os_buffer, 1, kMessage.size(), &mojo_guid,
+ MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_UNSAFE,
+ nullptr, &wrapped_handle));
+ WriteMessageWithHandles(h, kMessage, &wrapped_handle, 1);
+
+ // As a sanity check, send the GUID explicitly in a second message. We'll
+ // verify that the deserialized buffer handle holds the same GUID on the
+ // receiving end.
+ WriteMessageRaw(MessagePipeHandle(h), &mojo_guid, sizeof(mojo_guid),
+ nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE);
+ });
+}
+
+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);
+ uint32_t num_handles = 1;
+ uint64_t size;
+ MojoSharedBufferGuid mojo_guid;
+ MojoPlatformSharedMemoryRegionAccessMode access_mode;
+ ASSERT_EQ(MOJO_RESULT_OK, MojoUnwrapPlatformSharedMemoryRegion(
+ wrapped_handle, nullptr, &os_buffer,
+ &num_handles, &size, &mojo_guid, &access_mode));
+ EXPECT_EQ(MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_UNSAFE, access_mode);
+
+ base::UnguessableToken guid =
+ base::UnguessableToken::Deserialize(mojo_guid.high, mojo_guid.low);
+#if defined(OS_WIN)
+ ASSERT_EQ(MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE, os_buffer.type);
+ base::SharedMemoryHandle memory_handle(
+ reinterpret_cast<HANDLE>(os_buffer.value), size, guid);
+#elif defined(OS_FUCHSIA)
+ ASSERT_EQ(MOJO_PLATFORM_HANDLE_TYPE_FUCHSIA_HANDLE, os_buffer.type);
+ base::SharedMemoryHandle memory_handle(
+ static_cast<zx_handle_t>(os_buffer.value), size, guid);
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ ASSERT_EQ(MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT, os_buffer.type);
+ base::SharedMemoryHandle memory_handle(
+ static_cast<mach_port_t>(os_buffer.value), size, guid);
+#elif defined(OS_POSIX)
+ ASSERT_EQ(MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR, os_buffer.type);
+ base::SharedMemoryHandle memory_handle(
+ base::FileDescriptor(static_cast<int>(os_buffer.value), false), size,
+ guid);
+#endif
+
+ base::SharedMemory memory(memory_handle, false);
+ memory.Map(message.size());
+ ASSERT_TRUE(memory.memory());
+
+ EXPECT_TRUE(std::equal(message.begin(), message.end(),
+ static_cast<const char*>(memory.memory())));
+
+ // Verify that the received buffer's internal GUID was preserved in transit.
+ EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE));
+ std::vector<uint8_t> guid_bytes;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ ReadMessageRaw(MessagePipeHandle(h), &guid_bytes, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(sizeof(MojoSharedBufferGuid), guid_bytes.size());
+ auto* expected_guid =
+ reinterpret_cast<MojoSharedBufferGuid*>(guid_bytes.data());
+ EXPECT_EQ(expected_guid->high, mojo_guid.high);
+ EXPECT_EQ(expected_guid->low, mojo_guid.low);
+}
+
+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, nullptr, &wrapped_handle));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoUnwrapPlatformHandle(wrapped_handle, nullptr, &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, nullptr, &wrapped_handle));
+}
+
+} // namespace
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/ports/BUILD.gn b/mojo/core/ports/BUILD.gn
new file mode 100644
index 0000000000..68dce3f8eb
--- /dev/null
+++ b/mojo/core/ports/BUILD.gn
@@ -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.
+
+import("//testing/test.gni")
+
+component("ports") {
+ output_name = "mojo_core_ports"
+
+ sources = [
+ "event.cc",
+ "event.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_locker.cc",
+ "port_locker.h",
+ "port_ref.cc",
+ "port_ref.h",
+ "user_data.h",
+ "user_message.cc",
+ "user_message.h",
+ ]
+
+ defines = [ "IS_MOJO_CORE_PORTS_IMPL" ]
+
+ public_deps = [
+ "//base",
+ ]
+
+ if (!is_nacl) {
+ deps = [
+ "//crypto",
+ ]
+ }
+}
+
+source_set("tests") {
+ testonly = true
+
+ sources = [
+ "name_unittest.cc",
+ "ports_unittest.cc",
+ ]
+
+ deps = [
+ ":ports",
+ "//base",
+ "//base/test:test_support",
+ "//testing/gtest",
+ ]
+}
diff --git a/mojo/core/ports/event.cc b/mojo/core/ports/event.cc
new file mode 100644
index 0000000000..f3cf74edbd
--- /dev/null
+++ b/mojo/core/ports/event.cc
@@ -0,0 +1,383 @@
+// 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/core/ports/event.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include "base/numerics/safe_math.h"
+#include "mojo/core/ports/user_message.h"
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+namespace {
+
+const size_t kPortsMessageAlignment = 8;
+
+#pragma pack(push, 1)
+
+struct SerializedHeader {
+ Event::Type type;
+ uint32_t padding;
+ PortName port_name;
+};
+
+struct UserMessageEventData {
+ uint64_t sequence_num;
+ uint32_t num_ports;
+ uint32_t padding;
+};
+
+struct ObserveProxyEventData {
+ NodeName proxy_node_name;
+ PortName proxy_port_name;
+ NodeName proxy_target_node_name;
+ PortName proxy_target_port_name;
+};
+
+struct ObserveProxyAckEventData {
+ uint64_t last_sequence_num;
+};
+
+struct ObserveClosureEventData {
+ uint64_t last_sequence_num;
+};
+
+struct MergePortEventData {
+ PortName new_port_name;
+ Event::PortDescriptor new_port_descriptor;
+};
+
+#pragma pack(pop)
+
+static_assert(sizeof(Event::PortDescriptor) % kPortsMessageAlignment == 0,
+ "Invalid PortDescriptor size.");
+
+static_assert(sizeof(SerializedHeader) % kPortsMessageAlignment == 0,
+ "Invalid SerializedHeader size.");
+
+static_assert(sizeof(UserMessageEventData) % 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
+
+Event::PortDescriptor::PortDescriptor() {
+ memset(padding, 0, sizeof(padding));
+}
+
+Event::~Event() = default;
+
+// static
+ScopedEvent Event::Deserialize(const void* buffer, size_t num_bytes) {
+ if (num_bytes < sizeof(SerializedHeader))
+ return nullptr;
+
+ const auto* header = static_cast<const SerializedHeader*>(buffer);
+ const PortName& port_name = header->port_name;
+ const size_t data_size = num_bytes - sizeof(*header);
+ switch (header->type) {
+ case Type::kUserMessage:
+ return UserMessageEvent::Deserialize(port_name, header + 1, data_size);
+ case Type::kPortAccepted:
+ return PortAcceptedEvent::Deserialize(port_name, header + 1, data_size);
+ case Type::kObserveProxy:
+ return ObserveProxyEvent::Deserialize(port_name, header + 1, data_size);
+ case Type::kObserveProxyAck:
+ return ObserveProxyAckEvent::Deserialize(port_name, header + 1,
+ data_size);
+ case Type::kObserveClosure:
+ return ObserveClosureEvent::Deserialize(port_name, header + 1, data_size);
+ case Type::kMergePort:
+ return MergePortEvent::Deserialize(port_name, header + 1, data_size);
+ default:
+ DVLOG(2) << "Ingoring unknown port event type: "
+ << static_cast<uint32_t>(header->type);
+ return nullptr;
+ }
+}
+
+Event::Event(Type type, const PortName& port_name)
+ : type_(type), port_name_(port_name) {}
+
+size_t Event::GetSerializedSize() const {
+ return sizeof(SerializedHeader) + GetSerializedDataSize();
+}
+
+void Event::Serialize(void* buffer) const {
+ auto* header = static_cast<SerializedHeader*>(buffer);
+ header->type = type_;
+ header->padding = 0;
+ header->port_name = port_name_;
+ SerializeData(header + 1);
+}
+
+ScopedEvent Event::Clone() const {
+ return nullptr;
+}
+
+UserMessageEvent::~UserMessageEvent() = default;
+
+UserMessageEvent::UserMessageEvent(size_t num_ports)
+ : Event(Type::kUserMessage, kInvalidPortName) {
+ ReservePorts(num_ports);
+}
+
+void UserMessageEvent::AttachMessage(std::unique_ptr<UserMessage> message) {
+ DCHECK(!message_);
+ message_ = std::move(message);
+}
+
+void UserMessageEvent::ReservePorts(size_t num_ports) {
+ port_descriptors_.resize(num_ports);
+ ports_.resize(num_ports);
+}
+
+bool UserMessageEvent::NotifyWillBeRoutedExternally() {
+ DCHECK(message_);
+ return message_->WillBeRoutedExternally();
+}
+
+// static
+ScopedEvent UserMessageEvent::Deserialize(const PortName& port_name,
+ const void* buffer,
+ size_t num_bytes) {
+ if (num_bytes < sizeof(UserMessageEventData))
+ return nullptr;
+
+ const auto* data = static_cast<const UserMessageEventData*>(buffer);
+ base::CheckedNumeric<size_t> port_data_size = data->num_ports;
+ port_data_size *= sizeof(PortDescriptor) + sizeof(PortName);
+ if (!port_data_size.IsValid())
+ return nullptr;
+
+ base::CheckedNumeric<size_t> total_size = port_data_size.ValueOrDie();
+ total_size += sizeof(UserMessageEventData);
+ if (!total_size.IsValid() || num_bytes < total_size.ValueOrDie())
+ return nullptr;
+
+ auto event =
+ base::WrapUnique(new UserMessageEvent(port_name, data->sequence_num));
+ event->ReservePorts(data->num_ports);
+ const auto* in_descriptors =
+ reinterpret_cast<const PortDescriptor*>(data + 1);
+ std::copy(in_descriptors, in_descriptors + data->num_ports,
+ event->port_descriptors());
+
+ const auto* in_names =
+ reinterpret_cast<const PortName*>(in_descriptors + data->num_ports);
+ std::copy(in_names, in_names + data->num_ports, event->ports());
+ return std::move(event);
+}
+
+UserMessageEvent::UserMessageEvent(const PortName& port_name,
+ uint64_t sequence_num)
+ : Event(Type::kUserMessage, port_name), sequence_num_(sequence_num) {}
+
+size_t UserMessageEvent::GetSizeIfSerialized() const {
+ if (!message_)
+ return 0;
+ return message_->GetSizeIfSerialized();
+}
+
+size_t UserMessageEvent::GetSerializedDataSize() const {
+ DCHECK_EQ(ports_.size(), port_descriptors_.size());
+ base::CheckedNumeric<size_t> size = sizeof(UserMessageEventData);
+ base::CheckedNumeric<size_t> ports_size =
+ sizeof(PortDescriptor) + sizeof(PortName);
+ ports_size *= ports_.size();
+ return (size + ports_size.ValueOrDie()).ValueOrDie();
+}
+
+void UserMessageEvent::SerializeData(void* buffer) const {
+ DCHECK_EQ(ports_.size(), port_descriptors_.size());
+ auto* data = static_cast<UserMessageEventData*>(buffer);
+ data->sequence_num = sequence_num_;
+ DCHECK(base::IsValueInRangeForNumericType<uint32_t>(ports_.size()));
+ data->num_ports = static_cast<uint32_t>(ports_.size());
+ data->padding = 0;
+
+ auto* ports_data = reinterpret_cast<PortDescriptor*>(data + 1);
+ std::copy(port_descriptors_.begin(), port_descriptors_.end(), ports_data);
+
+ auto* port_names_data =
+ reinterpret_cast<PortName*>(ports_data + ports_.size());
+ std::copy(ports_.begin(), ports_.end(), port_names_data);
+}
+
+PortAcceptedEvent::PortAcceptedEvent(const PortName& port_name)
+ : Event(Type::kPortAccepted, port_name) {}
+
+PortAcceptedEvent::~PortAcceptedEvent() = default;
+
+// static
+ScopedEvent PortAcceptedEvent::Deserialize(const PortName& port_name,
+ const void* buffer,
+ size_t num_bytes) {
+ return std::make_unique<PortAcceptedEvent>(port_name);
+}
+
+size_t PortAcceptedEvent::GetSerializedDataSize() const {
+ return 0;
+}
+
+void PortAcceptedEvent::SerializeData(void* buffer) const {}
+
+ObserveProxyEvent::ObserveProxyEvent(const PortName& port_name,
+ const NodeName& proxy_node_name,
+ const PortName& proxy_port_name,
+ const NodeName& proxy_target_node_name,
+ const PortName& proxy_target_port_name)
+ : Event(Type::kObserveProxy, port_name),
+ proxy_node_name_(proxy_node_name),
+ proxy_port_name_(proxy_port_name),
+ proxy_target_node_name_(proxy_target_node_name),
+ proxy_target_port_name_(proxy_target_port_name) {}
+
+ObserveProxyEvent::~ObserveProxyEvent() = default;
+
+// static
+ScopedEvent ObserveProxyEvent::Deserialize(const PortName& port_name,
+ const void* buffer,
+ size_t num_bytes) {
+ if (num_bytes < sizeof(ObserveProxyEventData))
+ return nullptr;
+
+ const auto* data = static_cast<const ObserveProxyEventData*>(buffer);
+ return std::make_unique<ObserveProxyEvent>(
+ port_name, data->proxy_node_name, data->proxy_port_name,
+ data->proxy_target_node_name, data->proxy_target_port_name);
+}
+
+size_t ObserveProxyEvent::GetSerializedDataSize() const {
+ return sizeof(ObserveProxyEventData);
+}
+
+void ObserveProxyEvent::SerializeData(void* buffer) const {
+ auto* data = static_cast<ObserveProxyEventData*>(buffer);
+ data->proxy_node_name = proxy_node_name_;
+ data->proxy_port_name = proxy_port_name_;
+ data->proxy_target_node_name = proxy_target_node_name_;
+ data->proxy_target_port_name = proxy_target_port_name_;
+}
+
+ScopedEvent ObserveProxyEvent::Clone() const {
+ return std::make_unique<ObserveProxyEvent>(
+ port_name(), proxy_node_name_, proxy_port_name_, proxy_target_node_name_,
+ proxy_target_port_name_);
+}
+
+ObserveProxyAckEvent::ObserveProxyAckEvent(const PortName& port_name,
+ uint64_t last_sequence_num)
+ : Event(Type::kObserveProxyAck, port_name),
+ last_sequence_num_(last_sequence_num) {}
+
+ObserveProxyAckEvent::~ObserveProxyAckEvent() = default;
+
+// static
+ScopedEvent ObserveProxyAckEvent::Deserialize(const PortName& port_name,
+ const void* buffer,
+ size_t num_bytes) {
+ if (num_bytes < sizeof(ObserveProxyAckEventData))
+ return nullptr;
+
+ const auto* data = static_cast<const ObserveProxyAckEventData*>(buffer);
+ return std::make_unique<ObserveProxyAckEvent>(port_name,
+ data->last_sequence_num);
+}
+
+size_t ObserveProxyAckEvent::GetSerializedDataSize() const {
+ return sizeof(ObserveProxyAckEventData);
+}
+
+void ObserveProxyAckEvent::SerializeData(void* buffer) const {
+ auto* data = static_cast<ObserveProxyAckEventData*>(buffer);
+ data->last_sequence_num = last_sequence_num_;
+}
+
+ScopedEvent ObserveProxyAckEvent::Clone() const {
+ return std::make_unique<ObserveProxyAckEvent>(port_name(),
+ last_sequence_num_);
+}
+
+ObserveClosureEvent::ObserveClosureEvent(const PortName& port_name,
+ uint64_t last_sequence_num)
+ : Event(Type::kObserveClosure, port_name),
+ last_sequence_num_(last_sequence_num) {}
+
+ObserveClosureEvent::~ObserveClosureEvent() = default;
+
+// static
+ScopedEvent ObserveClosureEvent::Deserialize(const PortName& port_name,
+ const void* buffer,
+ size_t num_bytes) {
+ if (num_bytes < sizeof(ObserveClosureEventData))
+ return nullptr;
+
+ const auto* data = static_cast<const ObserveClosureEventData*>(buffer);
+ return std::make_unique<ObserveClosureEvent>(port_name,
+ data->last_sequence_num);
+}
+
+size_t ObserveClosureEvent::GetSerializedDataSize() const {
+ return sizeof(ObserveClosureEventData);
+}
+
+void ObserveClosureEvent::SerializeData(void* buffer) const {
+ auto* data = static_cast<ObserveClosureEventData*>(buffer);
+ data->last_sequence_num = last_sequence_num_;
+}
+
+ScopedEvent ObserveClosureEvent::Clone() const {
+ return std::make_unique<ObserveClosureEvent>(port_name(), last_sequence_num_);
+}
+
+MergePortEvent::MergePortEvent(const PortName& port_name,
+ const PortName& new_port_name,
+ const PortDescriptor& new_port_descriptor)
+ : Event(Type::kMergePort, port_name),
+ new_port_name_(new_port_name),
+ new_port_descriptor_(new_port_descriptor) {}
+
+MergePortEvent::~MergePortEvent() = default;
+
+// static
+ScopedEvent MergePortEvent::Deserialize(const PortName& port_name,
+ const void* buffer,
+ size_t num_bytes) {
+ if (num_bytes < sizeof(MergePortEventData))
+ return nullptr;
+
+ const auto* data = static_cast<const MergePortEventData*>(buffer);
+ return std::make_unique<MergePortEvent>(port_name, data->new_port_name,
+ data->new_port_descriptor);
+}
+
+size_t MergePortEvent::GetSerializedDataSize() const {
+ return sizeof(MergePortEventData);
+}
+
+void MergePortEvent::SerializeData(void* buffer) const {
+ auto* data = static_cast<MergePortEventData*>(buffer);
+ data->new_port_name = new_port_name_;
+ data->new_port_descriptor = new_port_descriptor_;
+}
+
+} // namespace ports
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/ports/event.h b/mojo/core/ports/event.h
new file mode 100644
index 0000000000..e278896b04
--- /dev/null
+++ b/mojo/core/ports/event.h
@@ -0,0 +1,284 @@
+// 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_CORE_PORTS_EVENT_H_
+#define MOJO_CORE_PORTS_EVENT_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "mojo/core/ports/name.h"
+#include "mojo/core/ports/user_message.h"
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+class Event;
+
+using ScopedEvent = std::unique_ptr<Event>;
+
+// A Event is the fundamental unit of operation and communication within and
+// between Nodes.
+class COMPONENT_EXPORT(MOJO_CORE_PORTS) Event {
+ public:
+ enum Type : uint32_t {
+ // A user message event contains arbitrary user-specified payload data
+ // which may include any number of ports and/or system handles (e.g. FDs).
+ kUserMessage,
+
+ // When a Node receives a user message with one or more ports attached, it
+ // sends back an instance of this event for every attached port to indicate
+ // that the port has been accepted by its destination node.
+ kPortAccepted,
+
+ // This event begins circulation any time a port enters a proxying state. It
+ // may be re-circulated in certain edge cases, but the ultimate purpose of
+ // the event is to ensure that every port along a route is (if necessary)
+ // aware that the proxying port is indeed proxying (and to where) so that it
+ // can begin to be bypassed along the route.
+ kObserveProxy,
+
+ // An event used to acknowledge to a proxy that all concerned nodes and
+ // ports are aware of its proxying state and that no more user messages will
+ // be routed to it beyond a given final sequence number.
+ kObserveProxyAck,
+
+ // Indicates that a port has been closed. This event fully circulates a
+ // route to ensure that all ports are aware of closure.
+ kObserveClosure,
+
+ // Used to request the merging of two routes via two sacrificial receiving
+ // ports, one from each route.
+ kMergePort,
+ };
+
+#pragma pack(push, 1)
+ 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];
+ };
+#pragma pack(pop)
+ virtual ~Event();
+
+ static ScopedEvent Deserialize(const void* buffer, size_t num_bytes);
+
+ template <typename T>
+ static std::unique_ptr<T> Cast(ScopedEvent* event) {
+ return base::WrapUnique(static_cast<T*>(event->release()));
+ }
+
+ Type type() const { return type_; }
+ const PortName& port_name() const { return port_name_; }
+ void set_port_name(const PortName& port_name) { port_name_ = port_name; }
+
+ size_t GetSerializedSize() const;
+ void Serialize(void* buffer) const;
+ virtual ScopedEvent Clone() const;
+
+ protected:
+ Event(Type type, const PortName& port_name);
+
+ virtual size_t GetSerializedDataSize() const = 0;
+ virtual void SerializeData(void* buffer) const = 0;
+
+ private:
+ const Type type_;
+ PortName port_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(Event);
+};
+
+class COMPONENT_EXPORT(MOJO_CORE_PORTS) UserMessageEvent : public Event {
+ public:
+ explicit UserMessageEvent(size_t num_ports);
+ ~UserMessageEvent() override;
+
+ bool HasMessage() const { return !!message_; }
+ void AttachMessage(std::unique_ptr<UserMessage> message);
+
+ template <typename T>
+ T* GetMessage() {
+ DCHECK(HasMessage());
+ DCHECK_EQ(&T::kUserMessageTypeInfo, message_->type_info());
+ return static_cast<T*>(message_.get());
+ }
+
+ template <typename T>
+ const T* GetMessage() const {
+ DCHECK(HasMessage());
+ DCHECK_EQ(&T::kUserMessageTypeInfo, message_->type_info());
+ return static_cast<const T*>(message_.get());
+ }
+
+ void ReservePorts(size_t num_ports);
+ bool NotifyWillBeRoutedExternally();
+
+ uint32_t sequence_num() const { return sequence_num_; }
+ void set_sequence_num(uint32_t sequence_num) { sequence_num_ = sequence_num; }
+
+ size_t num_ports() const { return ports_.size(); }
+ PortDescriptor* port_descriptors() { return port_descriptors_.data(); }
+ PortName* ports() { return ports_.data(); }
+
+ static ScopedEvent Deserialize(const PortName& port_name,
+ const void* buffer,
+ size_t num_bytes);
+
+ size_t GetSizeIfSerialized() const;
+
+ private:
+ UserMessageEvent(const PortName& port_name, uint64_t sequence_num);
+
+ size_t GetSerializedDataSize() const override;
+ void SerializeData(void* buffer) const override;
+
+ uint64_t sequence_num_ = 0;
+ std::vector<PortDescriptor> port_descriptors_;
+ std::vector<PortName> ports_;
+ std::unique_ptr<UserMessage> message_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserMessageEvent);
+};
+
+class COMPONENT_EXPORT(MOJO_CORE_PORTS) PortAcceptedEvent : public Event {
+ public:
+ explicit PortAcceptedEvent(const PortName& port_name);
+ ~PortAcceptedEvent() override;
+
+ static ScopedEvent Deserialize(const PortName& port_name,
+ const void* buffer,
+ size_t num_bytes);
+
+ private:
+ size_t GetSerializedDataSize() const override;
+ void SerializeData(void* buffer) const override;
+
+ DISALLOW_COPY_AND_ASSIGN(PortAcceptedEvent);
+};
+
+class COMPONENT_EXPORT(MOJO_CORE_PORTS) ObserveProxyEvent : public Event {
+ public:
+ ObserveProxyEvent(const PortName& port_name,
+ const NodeName& proxy_node_name,
+ const PortName& proxy_port_name,
+ const NodeName& proxy_target_node_name,
+ const PortName& proxy_target_port_name);
+ ~ObserveProxyEvent() override;
+
+ const NodeName& proxy_node_name() const { return proxy_node_name_; }
+ const PortName& proxy_port_name() const { return proxy_port_name_; }
+ const NodeName& proxy_target_node_name() const {
+ return proxy_target_node_name_;
+ }
+ const PortName& proxy_target_port_name() const {
+ return proxy_target_port_name_;
+ }
+
+ static ScopedEvent Deserialize(const PortName& port_name,
+ const void* buffer,
+ size_t num_bytes);
+
+ private:
+ size_t GetSerializedDataSize() const override;
+ void SerializeData(void* buffer) const override;
+ ScopedEvent Clone() const override;
+
+ const NodeName proxy_node_name_;
+ const PortName proxy_port_name_;
+ const NodeName proxy_target_node_name_;
+ const PortName proxy_target_port_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(ObserveProxyEvent);
+};
+
+class COMPONENT_EXPORT(MOJO_CORE_PORTS) ObserveProxyAckEvent : public Event {
+ public:
+ ObserveProxyAckEvent(const PortName& port_name, uint64_t last_sequence_num);
+ ~ObserveProxyAckEvent() override;
+
+ uint64_t last_sequence_num() const { return last_sequence_num_; }
+
+ static ScopedEvent Deserialize(const PortName& port_name,
+ const void* buffer,
+ size_t num_bytes);
+
+ private:
+ size_t GetSerializedDataSize() const override;
+ void SerializeData(void* buffer) const override;
+ ScopedEvent Clone() const override;
+
+ const uint64_t last_sequence_num_;
+
+ DISALLOW_COPY_AND_ASSIGN(ObserveProxyAckEvent);
+};
+
+class COMPONENT_EXPORT(MOJO_CORE_PORTS) ObserveClosureEvent : public Event {
+ public:
+ ObserveClosureEvent(const PortName& port_name, uint64_t last_sequence_num);
+ ~ObserveClosureEvent() override;
+
+ uint64_t last_sequence_num() const { return last_sequence_num_; }
+ void set_last_sequence_num(uint64_t last_sequence_num) {
+ last_sequence_num_ = last_sequence_num;
+ }
+
+ static ScopedEvent Deserialize(const PortName& port_name,
+ const void* buffer,
+ size_t num_bytes);
+
+ private:
+ size_t GetSerializedDataSize() const override;
+ void SerializeData(void* buffer) const override;
+ ScopedEvent Clone() const override;
+
+ uint64_t last_sequence_num_;
+
+ DISALLOW_COPY_AND_ASSIGN(ObserveClosureEvent);
+};
+
+class COMPONENT_EXPORT(MOJO_CORE_PORTS) MergePortEvent : public Event {
+ public:
+ MergePortEvent(const PortName& port_name,
+ const PortName& new_port_name,
+ const PortDescriptor& new_port_descriptor);
+ ~MergePortEvent() override;
+
+ const PortName& new_port_name() const { return new_port_name_; }
+ const PortDescriptor& new_port_descriptor() const {
+ return new_port_descriptor_;
+ }
+
+ static ScopedEvent Deserialize(const PortName& port_name,
+ const void* buffer,
+ size_t num_bytes);
+
+ private:
+ size_t GetSerializedDataSize() const override;
+ void SerializeData(void* buffer) const override;
+
+ const PortName new_port_name_;
+ const PortDescriptor new_port_descriptor_;
+
+ DISALLOW_COPY_AND_ASSIGN(MergePortEvent);
+};
+
+} // namespace ports
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_PORTS_EVENT_H_
diff --git a/mojo/core/ports/message_filter.h b/mojo/core/ports/message_filter.h
new file mode 100644
index 0000000000..f09903ffca
--- /dev/null
+++ b/mojo/core/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_CORE_PORTS_MESSAGE_FILTER_H_
+#define MOJO_CORE_PORTS_MESSAGE_FILTER_H_
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+class UserMessageEvent;
+
+// An interface which can be implemented to user message events according to
+// arbitrary policy.
+class MessageFilter {
+ public:
+ virtual ~MessageFilter() {}
+
+ // Returns true if |message| should be accepted by whomever is applying this
+ // filter. See MessageQueue::GetNextMessage(), for example.
+ virtual bool Match(const UserMessageEvent& message) = 0;
+};
+
+} // namespace ports
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_PORTS_MESSAGE_FILTER_H_
diff --git a/mojo/core/ports/message_queue.cc b/mojo/core/ports/message_queue.cc
new file mode 100644
index 0000000000..074b35a46d
--- /dev/null
+++ b/mojo/core/ports/message_queue.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/core/ports/message_queue.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "mojo/core/ports/message_filter.h"
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+// Used by std::{push,pop}_heap functions
+inline bool operator<(const std::unique_ptr<UserMessageEvent>& a,
+ const std::unique_ptr<UserMessageEvent>& b) {
+ return a->sequence_num() > b->sequence_num();
+}
+
+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() && heap_[0]->sequence_num() == next_sequence_num_;
+}
+
+void MessageQueue::GetNextMessage(std::unique_ptr<UserMessageEvent>* message,
+ MessageFilter* filter) {
+ if (!HasNextMessage() || (filter && !filter->Match(*heap_[0]))) {
+ message->reset();
+ return;
+ }
+
+ std::pop_heap(heap_.begin(), heap_.end());
+ *message = std::move(heap_.back());
+ total_queued_bytes_ -= (*message)->GetSizeIfSerialized();
+ heap_.pop_back();
+
+ next_sequence_num_++;
+}
+
+void MessageQueue::AcceptMessage(std::unique_ptr<UserMessageEvent> message,
+ bool* has_next_message) {
+ // TODO: Handle sequence number roll-over.
+
+ total_queued_bytes_ += message->GetSizeIfSerialized();
+ heap_.emplace_back(std::move(message));
+ std::push_heap(heap_.begin(), heap_.end());
+
+ if (!signalable_) {
+ *has_next_message = false;
+ } else {
+ *has_next_message = (heap_[0]->sequence_num() == next_sequence_num_);
+ }
+}
+
+void MessageQueue::TakeAllMessages(
+ std::vector<std::unique_ptr<UserMessageEvent>>* messages) {
+ *messages = std::move(heap_);
+ total_queued_bytes_ = 0;
+}
+
+} // namespace ports
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/ports/message_queue.h b/mojo/core/ports/message_queue.h
new file mode 100644
index 0000000000..1d34222c5e
--- /dev/null
+++ b/mojo/core/ports/message_queue.h
@@ -0,0 +1,86 @@
+// 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_CORE_PORTS_MESSAGE_QUEUE_H_
+#define MOJO_CORE_PORTS_MESSAGE_QUEUE_H_
+
+#include <stdint.h>
+
+#include <limits>
+#include <memory>
+#include <vector>
+
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "mojo/core/ports/event.h"
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+constexpr uint64_t kInitialSequenceNum = 1;
+constexpr uint64_t kInvalidSequenceNum = std::numeric_limits<uint64_t>::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 COMPONENT_EXPORT(MOJO_CORE_PORTS) 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(std::unique_ptr<UserMessageEvent>* 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(std::unique_ptr<UserMessageEvent> message,
+ bool* has_next_message);
+
+ // Takes all messages from this queue. Used to safely destroy queued messages
+ // without holding any Port lock.
+ void TakeAllMessages(
+ std::vector<std::unique_ptr<UserMessageEvent>>* messages);
+
+ // The number of messages queued here, regardless of whether the next expected
+ // message has arrived yet.
+ size_t queued_message_count() const { return heap_.size(); }
+
+ // The aggregate memory size in bytes of all messages queued here, regardless
+ // of whether the next expected message has arrived yet.
+ size_t queued_num_bytes() const { return total_queued_bytes_; }
+
+ private:
+ std::vector<std::unique_ptr<UserMessageEvent>> heap_;
+ uint64_t next_sequence_num_;
+ bool signalable_ = true;
+ size_t total_queued_bytes_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageQueue);
+};
+
+} // namespace ports
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_PORTS_MESSAGE_QUEUE_H_
diff --git a/mojo/core/ports/name.cc b/mojo/core/ports/name.cc
new file mode 100644
index 0000000000..332a0a0826
--- /dev/null
+++ b/mojo/core/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/core/ports/name.h"
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+const PortName kInvalidPortName = {0, 0};
+
+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 core
+} // namespace mojo
diff --git a/mojo/core/ports/name.h b/mojo/core/ports/name.h
new file mode 100644
index 0000000000..415ba65a95
--- /dev/null
+++ b/mojo/core/ports/name.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_CORE_PORTS_NAME_H_
+#define MOJO_CORE_PORTS_NAME_H_
+
+#include <stdint.h>
+
+#include <ostream>
+#include <tuple>
+
+#include "base/component_export.h"
+#include "base/hash.h"
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+struct COMPONENT_EXPORT(MOJO_CORE_PORTS) 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);
+}
+
+COMPONENT_EXPORT(MOJO_CORE_PORTS)
+std::ostream& operator<<(std::ostream& stream, const Name& name);
+
+struct COMPONENT_EXPORT(MOJO_CORE_PORTS) PortName : Name {
+ PortName() : Name(0, 0) {}
+ PortName(uint64_t v1, uint64_t v2) : Name(v1, v2) {}
+};
+
+extern COMPONENT_EXPORT(MOJO_CORE_PORTS) const PortName kInvalidPortName;
+
+struct COMPONENT_EXPORT(MOJO_CORE_PORTS) NodeName : Name {
+ NodeName() : Name(0, 0) {}
+ NodeName(uint64_t v1, uint64_t v2) : Name(v1, v2) {}
+};
+
+extern COMPONENT_EXPORT(MOJO_CORE_PORTS) const NodeName kInvalidNodeName;
+
+} // namespace ports
+} // namespace core
+} // namespace mojo
+
+namespace std {
+
+template <>
+struct COMPONENT_EXPORT(MOJO_CORE_PORTS) hash<mojo::core::ports::PortName> {
+ std::size_t operator()(const mojo::core::ports::PortName& name) const {
+ return base::HashInts64(name.v1, name.v2);
+ }
+};
+
+template <>
+struct COMPONENT_EXPORT(MOJO_CORE_PORTS) hash<mojo::core::ports::NodeName> {
+ std::size_t operator()(const mojo::core::ports::NodeName& name) const {
+ return base::HashInts64(name.v1, name.v2);
+ }
+};
+
+} // namespace std
+
+#endif // MOJO_CORE_PORTS_NAME_H_
diff --git a/mojo/core/ports/name_unittest.cc b/mojo/core/ports/name_unittest.cc
new file mode 100644
index 0000000000..520295c580
--- /dev/null
+++ b/mojo/core/ports/name_unittest.cc
@@ -0,0 +1,75 @@
+// 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/core/ports/name.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace core {
+namespace ports {
+namespace test {
+
+TEST(NameTest, Defaults) {
+ PortName default_port_name;
+ EXPECT_EQ(kInvalidPortName, default_port_name);
+
+ NodeName default_node_name;
+ EXPECT_EQ(kInvalidNodeName, default_node_name);
+}
+
+TEST(NameTest, PortNameChecks) {
+ PortName port_name_a(50, 100);
+ PortName port_name_b(50, 100);
+ PortName port_name_c(100, 50);
+
+ EXPECT_EQ(port_name_a, port_name_b);
+ EXPECT_NE(port_name_a, port_name_c);
+ EXPECT_NE(port_name_b, port_name_c);
+
+ EXPECT_LT(port_name_a, port_name_c);
+ EXPECT_LT(port_name_b, port_name_c);
+ EXPECT_FALSE(port_name_a < port_name_b);
+ EXPECT_FALSE(port_name_b < port_name_a);
+
+ std::hash<PortName> port_hash_fn;
+
+ size_t hash_a = port_hash_fn(port_name_a);
+ size_t hash_b = port_hash_fn(port_name_b);
+ size_t hash_c = port_hash_fn(port_name_c);
+
+ EXPECT_EQ(hash_a, hash_b);
+ EXPECT_NE(hash_a, hash_c);
+ EXPECT_NE(hash_b, hash_c);
+}
+
+TEST(NameTest, NodeNameChecks) {
+ NodeName node_name_a(50, 100);
+ NodeName node_name_b(50, 100);
+ NodeName node_name_c(100, 50);
+
+ EXPECT_EQ(node_name_a, node_name_b);
+ EXPECT_NE(node_name_a, node_name_c);
+ EXPECT_NE(node_name_b, node_name_c);
+
+ EXPECT_LT(node_name_a, node_name_c);
+ EXPECT_LT(node_name_b, node_name_c);
+ EXPECT_FALSE(node_name_a < node_name_b);
+ EXPECT_FALSE(node_name_b < node_name_a);
+
+ std::hash<NodeName> node_hash_fn;
+
+ size_t hash_a = node_hash_fn(node_name_a);
+ size_t hash_b = node_hash_fn(node_name_b);
+ size_t hash_c = node_hash_fn(node_name_c);
+
+ EXPECT_EQ(hash_a, hash_b);
+ EXPECT_NE(hash_a, hash_c);
+ EXPECT_NE(hash_b, hash_c);
+}
+
+} // namespace test
+} // namespace ports
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/ports/node.cc b/mojo/core/ports/node.cc
new file mode 100644
index 0000000000..7ed191c788
--- /dev/null
+++ b/mojo/core/ports/node.cc
@@ -0,0 +1,1388 @@
+// 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/core/ports/node.h"
+
+#include <string.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "base/atomicops.h"
+#include "base/containers/stack_container.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_local.h"
+#include "build/build_config.h"
+#include "mojo/core/ports/event.h"
+#include "mojo/core/ports/node_delegate.h"
+#include "mojo/core/ports/port_locker.h"
+
+#if !defined(OS_NACL)
+#include "crypto/random.h"
+#else
+#include "base/rand_util.h"
+#endif
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+namespace {
+
+constexpr size_t kRandomNameCacheSize = 256;
+
+// Random port name generator which maintains a cache of random bytes to draw
+// from. This amortizes the cost of random name generation on platforms where
+// RandBytes may have significant per-call overhead.
+//
+// Note that the use of this cache means one has to be careful about fork()ing
+// a process once any port names have been generated, as that behavior can lead
+// to collisions between independently generated names in different processes.
+class RandomNameGenerator {
+ public:
+ RandomNameGenerator() = default;
+ ~RandomNameGenerator() = default;
+
+ PortName GenerateRandomPortName() {
+ base::AutoLock lock(lock_);
+ if (cache_index_ == kRandomNameCacheSize) {
+#if defined(OS_NACL)
+ base::RandBytes(cache_, sizeof(PortName) * kRandomNameCacheSize);
+#else
+ crypto::RandBytes(cache_, sizeof(PortName) * kRandomNameCacheSize);
+#endif
+ cache_index_ = 0;
+ }
+ return cache_[cache_index_++];
+ }
+
+ private:
+ base::Lock lock_;
+ PortName cache_[kRandomNameCacheSize];
+ size_t cache_index_ = kRandomNameCacheSize;
+
+ DISALLOW_COPY_AND_ASSIGN(RandomNameGenerator);
+};
+
+base::LazyInstance<RandomNameGenerator>::Leaky g_name_generator =
+ LAZY_INSTANCE_INITIALIZER;
+
+int DebugError(const char* message, int error_code) {
+ NOTREACHED() << "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;
+}
+
+void GenerateRandomPortName(PortName* name) {
+ *name = g_name_generator.Get().GenerateRandomPortName();
+}
+
+} // namespace
+
+Node::Node(const NodeName& name, NodeDelegate* delegate)
+ : name_(name), delegate_(this, delegate) {}
+
+Node::~Node() {
+ if (!ports_.empty())
+ DLOG(WARNING) << "Unclean shutdown for node " << name_;
+}
+
+bool Node::CanShutdownCleanly(ShutdownPolicy policy) {
+ PortLocker::AssertNoPortsLockedOnCurrentThread();
+ 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_) {
+ PortRef port_ref(entry.first, entry.second);
+ SinglePortLocker locker(&port_ref);
+ auto* port = locker.port();
+ if (port->peer_node_name != name_ && port->state != Port::kReceiving) {
+ can_shutdown = false;
+#if DCHECK_IS_ON()
+ DVLOG(2) << "Port " << entry.first << " referencing node "
+ << port->peer_node_name << " is blocking shutdown of "
+ << "node " << name_ << " (state=" << port->state << ")";
+#else
+ // Exit early when not debugging.
+ break;
+#endif
+ }
+ }
+
+ return can_shutdown;
+}
+
+int Node::GetPort(const PortName& port_name, PortRef* port_ref) {
+ PortLocker::AssertNoPortsLockedOnCurrentThread();
+ base::AutoLock lock(ports_lock_);
+ auto iter = ports_.find(port_name);
+ if (iter == ports_.end())
+ return ERROR_PORT_UNKNOWN;
+
+#if defined(OS_ANDROID) && defined(ARCH_CPU_ARM64)
+ // Workaround for https://crbug.com/665869.
+ base::subtle::MemoryBarrier();
+#endif
+
+ *port_ref = PortRef(port_name, iter->second);
+ return OK;
+}
+
+int Node::CreateUninitializedPort(PortRef* port_ref) {
+ PortName port_name;
+ GenerateRandomPortName(&port_name);
+
+ scoped_refptr<Port> 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) {
+ {
+ SinglePortLocker locker(&port_ref);
+ auto* port = locker.port();
+ 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<UserData> user_data) {
+ SinglePortLocker locker(&port_ref);
+ auto* port = locker.port();
+ 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<UserData>* user_data) {
+ SinglePortLocker locker(&port_ref);
+ auto* port = locker.port();
+ 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::vector<std::unique_ptr<UserMessageEvent>> undelivered_messages;
+ NodeName peer_node_name;
+ PortName peer_port_name;
+ uint64_t last_sequence_num = 0;
+ bool was_initialized = false;
+ {
+ SinglePortLocker locker(&port_ref);
+ auto* port = locker.port();
+ switch (port->state) {
+ case Port::kUninitialized:
+ break;
+
+ case Port::kReceiving:
+ was_initialized = true;
+ 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.
+ 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.TakeAllMessages(&undelivered_messages);
+ break;
+
+ default:
+ return ERROR_PORT_STATE_UNEXPECTED;
+ }
+ }
+
+ ErasePort(port_ref.name());
+
+ if (was_initialized) {
+ DVLOG(2) << "Sending ObserveClosure from " << port_ref.name() << "@"
+ << name_ << " to " << peer_port_name << "@" << peer_node_name;
+ delegate_->ForwardEvent(peer_node_name,
+ std::make_unique<ObserveClosureEvent>(
+ peer_port_name, last_sequence_num));
+ for (const auto& message : undelivered_messages) {
+ for (size_t i = 0; i < message->num_ports(); ++i) {
+ PortRef ref;
+ if (GetPort(message->ports()[i], &ref) == OK)
+ ClosePort(ref);
+ }
+ }
+ }
+ return OK;
+}
+
+int Node::GetStatus(const PortRef& port_ref, PortStatus* port_status) {
+ SinglePortLocker locker(&port_ref);
+ auto* port = locker.port();
+ 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;
+ port_status->peer_remote = port->peer_node_name != name_;
+ port_status->queued_message_count =
+ port->message_queue.queued_message_count();
+ port_status->queued_num_bytes = port->message_queue.queued_num_bytes();
+ return OK;
+}
+
+int Node::GetMessage(const PortRef& port_ref,
+ std::unique_ptr<UserMessageEvent>* message,
+ MessageFilter* filter) {
+ *message = nullptr;
+
+ DVLOG(4) << "GetMessage for " << port_ref.name() << "@" << name_;
+
+ {
+ SinglePortLocker locker(&port_ref);
+ auto* port = locker.port();
+
+ // 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) {
+ PortRef new_port_ref;
+ int rv = GetPort((*message)->ports()[i], &new_port_ref);
+
+ DCHECK_EQ(OK, rv) << "Port " << new_port_ref.name() << "@" << name_
+ << " does not exist!";
+
+ SinglePortLocker locker(&new_port_ref);
+ DCHECK(locker.port()->state == Port::kReceiving);
+ locker.port()->message_queue.set_signalable(true);
+ }
+
+ // The user may retransmit this message from another port. We reset the
+ // sequence number so that the message will get a new one if that happens.
+ (*message)->set_sequence_num(0);
+ }
+
+ return OK;
+}
+
+int Node::SendUserMessage(const PortRef& port_ref,
+ std::unique_ptr<UserMessageEvent> message) {
+ int rv = SendUserMessageInternal(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::AcceptEvent(ScopedEvent event) {
+ switch (event->type()) {
+ case Event::Type::kUserMessage:
+ return OnUserMessage(Event::Cast<UserMessageEvent>(&event));
+ case Event::Type::kPortAccepted:
+ return OnPortAccepted(Event::Cast<PortAcceptedEvent>(&event));
+ case Event::Type::kObserveProxy:
+ return OnObserveProxy(Event::Cast<ObserveProxyEvent>(&event));
+ case Event::Type::kObserveProxyAck:
+ return OnObserveProxyAck(Event::Cast<ObserveProxyAckEvent>(&event));
+ case Event::Type::kObserveClosure:
+ return OnObserveClosure(Event::Cast<ObserveClosureEvent>(&event));
+ case Event::Type::kMergePort:
+ return OnMergePort(Event::Cast<MergePortEvent>(&event));
+ }
+ return OOPS(ERROR_NOT_IMPLEMENTED);
+}
+
+int Node::MergePorts(const PortRef& port_ref,
+ const NodeName& destination_node_name,
+ const PortName& destination_port_name) {
+ PortName new_port_name;
+ Event::PortDescriptor new_port_descriptor;
+ {
+ SinglePortLocker locker(&port_ref);
+
+ 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.
+ new_port_name = port_ref.name();
+ ConvertToProxy(locker.port(), destination_node_name, &new_port_name,
+ &new_port_descriptor);
+ }
+
+ if (new_port_descriptor.peer_node_name == name_ &&
+ destination_node_name != name_) {
+ // Ensure that the locally retained peer of the new proxy gets a status
+ // update so it notices that its peer is now remote.
+ PortRef local_peer;
+ if (GetPort(new_port_descriptor.peer_port_name, &local_peer) == OK)
+ delegate_->PortStatusChanged(local_peer);
+ }
+
+ delegate_->ForwardEvent(
+ destination_node_name,
+ std::make_unique<MergePortEvent>(destination_port_name, new_port_name,
+ new_port_descriptor));
+ return OK;
+}
+
+int Node::MergeLocalPorts(const PortRef& port0_ref, const PortRef& port1_ref) {
+ DVLOG(1) << "Merging local ports " << port0_ref.name() << "@" << name_
+ << " and " << port1_ref.name() << "@" << name_;
+ return MergePortsInternal(port0_ref, port1_ref,
+ true /* allow_close_on_bad_state */);
+}
+
+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(std::unique_ptr<UserMessageEvent> message) {
+ PortName port_name = message->port_name();
+
+#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) << "OnUserMessage " << message->sequence_num()
+ << " [ports=" << ports_buf.str() << "] at " << port_name << "@"
+ << name_;
+#endif
+
+ // 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) {
+ Event::PortDescriptor& descriptor = message->port_descriptors()[i];
+ if (descriptor.referring_node_name == kInvalidNodeName) {
+ // If the referring node name is invalid, this descriptor can be ignored
+ // and the port should already exist locally.
+ PortRef port_ref;
+ if (GetPort(message->ports()[i], &port_ref) != OK)
+ return ERROR_PORT_UNKNOWN;
+ } else {
+ int rv = AcceptPort(message->ports()[i], descriptor);
+ if (rv != OK)
+ return rv;
+
+ // Ensure that the referring node is wiped out of this descriptor. This
+ // allows the event to be forwarded across multiple local hops without
+ // attempting to accept the port more than once.
+ descriptor.referring_node_name = kInvalidNodeName;
+ }
+ }
+
+ PortRef port_ref;
+ GetPort(port_name, &port_ref);
+ bool has_next_message = false;
+ bool message_accepted = false;
+ bool should_forward_messages = false;
+ if (port_ref.is_valid()) {
+ SinglePortLocker locker(&port_ref);
+ auto* port = locker.port();
+
+ // Reject spurious messages if we've already received the last expected
+ // message.
+ if (CanAcceptMoreMessages(port)) {
+ 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;
+ should_forward_messages = true;
+ }
+ }
+ }
+
+ if (should_forward_messages) {
+ int rv = ForwardUserMessagesFromProxy(port_ref);
+ if (rv != OK)
+ return rv;
+ TryRemoveProxy(port_ref);
+ }
+
+ 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 attached_port_ref;
+ if (GetPort(message->ports()[i], &attached_port_ref) == OK) {
+ ClosePort(attached_port_ref);
+ } else {
+ DLOG(WARNING) << "Cannot close non-existent port!\n";
+ }
+ }
+ } else if (has_next_message) {
+ delegate_->PortStatusChanged(port_ref);
+ }
+
+ return OK;
+}
+
+int Node::OnPortAccepted(std::unique_ptr<PortAcceptedEvent> event) {
+ PortRef port_ref;
+ if (GetPort(event->port_name(), &port_ref) != OK)
+ return ERROR_PORT_UNKNOWN;
+
+#if DCHECK_IS_ON()
+ {
+ SinglePortLocker locker(&port_ref);
+ DVLOG(2) << "PortAccepted at " << port_ref.name() << "@" << name_
+ << " pointing to " << locker.port()->peer_port_name << "@"
+ << locker.port()->peer_node_name;
+ }
+#endif
+
+ return BeginProxying(port_ref);
+}
+
+int Node::OnObserveProxy(std::unique_ptr<ObserveProxyEvent> event) {
+ if (event->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.
+ DCHECK_EQ(event->proxy_target_node_name(), kInvalidNodeName);
+ DCHECK_EQ(event->proxy_target_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.
+ PortRef port_ref;
+ if (GetPort(event->port_name(), &port_ref) != OK) {
+ DVLOG(1) << "ObserveProxy: " << event->port_name() << "@" << name_
+ << " not found";
+ return OK;
+ }
+
+ DVLOG(2) << "ObserveProxy at " << port_ref.name() << "@" << name_
+ << ", proxy at " << event->proxy_port_name() << "@"
+ << event->proxy_node_name() << " pointing to "
+ << event->proxy_target_port_name() << "@"
+ << event->proxy_target_node_name();
+
+ bool update_status = false;
+ ScopedEvent event_to_forward;
+ NodeName event_target_node;
+ {
+ SinglePortLocker locker(&port_ref);
+ auto* port = locker.port();
+
+ 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_target_node_name();
+ port->peer_port_name = event->proxy_target_port_name();
+ event_target_node = event->proxy_node_name();
+ event_to_forward = std::make_unique<ObserveProxyAckEvent>(
+ event->proxy_port_name(), port->next_sequence_num_to_send - 1);
+ update_status = true;
+ DVLOG(2) << "Forwarding ObserveProxyAck from " << event->port_name()
+ << "@" << name_ << " to " << event->proxy_port_name() << "@"
+ << event_target_node;
+ } 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();
+
+ port->send_on_proxy_removal.reset(new std::pair<NodeName, ScopedEvent>(
+ event->proxy_node_name(),
+ std::make_unique<ObserveProxyAckEvent>(event->proxy_port_name(),
+ kInvalidSequenceNum)));
+ }
+ } else {
+ // Forward this event along to our peer. Eventually, it should find the
+ // port referring to the proxy.
+ event_target_node = port->peer_node_name;
+ event->set_port_name(port->peer_port_name);
+ event_to_forward = std::move(event);
+ }
+ }
+
+ if (event_to_forward)
+ delegate_->ForwardEvent(event_target_node, std::move(event_to_forward));
+
+ if (update_status)
+ delegate_->PortStatusChanged(port_ref);
+
+ return OK;
+}
+
+int Node::OnObserveProxyAck(std::unique_ptr<ObserveProxyAckEvent> event) {
+ DVLOG(2) << "ObserveProxyAck at " << event->port_name() << "@" << name_
+ << " (last_sequence_num=" << event->last_sequence_num() << ")";
+
+ PortRef port_ref;
+ if (GetPort(event->port_name(), &port_ref) != OK)
+ return ERROR_PORT_UNKNOWN; // The port may have observed closure first.
+
+ bool try_remove_proxy_immediately;
+ {
+ SinglePortLocker locker(&port_ref);
+ auto* port = locker.port();
+ if (port->state != Port::kProxying)
+ return OOPS(ERROR_PORT_STATE_UNEXPECTED);
+
+ // If the last sequence number is invalid, this is a signal that we need to
+ // retransmit the ObserveProxy event for this port rather than flagging the
+ // the proxy for removal ASAP.
+ try_remove_proxy_immediately =
+ event->last_sequence_num() != kInvalidSequenceNum;
+ if (try_remove_proxy_immediately) {
+ // 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 = event->last_sequence_num();
+ }
+ }
+
+ if (try_remove_proxy_immediately)
+ TryRemoveProxy(port_ref);
+ else
+ InitiateProxyRemoval(port_ref);
+
+ return OK;
+}
+
+int Node::OnObserveClosure(std::unique_ptr<ObserveClosureEvent> event) {
+ // OK if the port doesn't exist, as it may have been closed already.
+ PortRef port_ref;
+ if (GetPort(event->port_name(), &port_ref) != OK)
+ 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;
+ NodeName peer_node_name;
+ PortName peer_port_name;
+ bool try_remove_proxy = false;
+ {
+ SinglePortLocker locker(&port_ref);
+ auto* port = locker.port();
+
+ port->peer_closed = true;
+ port->last_sequence_num_to_receive = event->last_sequence_num();
+
+ DVLOG(2) << "ObserveClosure at " << port_ref.name() << "@" << name_
+ << " (state=" << port->state << ") pointing to "
+ << port->peer_port_name << "@" << port->peer_node_name
+ << " (last_sequence_num=" << event->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.
+ event->set_last_sequence_num(port->next_sequence_num_to_send - 1);
+ } else {
+ // We haven't yet reached the receiving peer of the closed port, so we'll
+ // forward the message along as-is.
+ // 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_ref.name() << "@"
+ << name_ << " to peer " << port->peer_port_name << "@"
+ << port->peer_node_name
+ << " (last_sequence_num=" << event->last_sequence_num() << ")";
+
+ peer_node_name = port->peer_node_name;
+ peer_port_name = port->peer_port_name;
+ }
+
+ if (try_remove_proxy)
+ TryRemoveProxy(port_ref);
+
+ event->set_port_name(peer_port_name);
+ delegate_->ForwardEvent(peer_node_name, std::move(event));
+
+ if (notify_delegate)
+ delegate_->PortStatusChanged(port_ref);
+
+ return OK;
+}
+
+int Node::OnMergePort(std::unique_ptr<MergePortEvent> event) {
+ PortRef port_ref;
+ GetPort(event->port_name(), &port_ref);
+
+ DVLOG(1) << "MergePort at " << port_ref.name() << "@" << name_
+ << " 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;
+
+ // Accept the new port. This is now the receiving end of the other port cycle
+ // to be merged with ours. Note that we always attempt to accept the new port
+ // first as otherwise its peer receiving port could be left stranded
+ // indefinitely.
+ if (AcceptPort(event->new_port_name(), event->new_port_descriptor()) != OK) {
+ if (port_ref.is_valid())
+ ClosePort(port_ref);
+ return ERROR_PORT_STATE_UNEXPECTED;
+ }
+
+ PortRef new_port_ref;
+ GetPort(event->new_port_name(), &new_port_ref);
+ if (!port_ref.is_valid() && new_port_ref.is_valid()) {
+ ClosePort(new_port_ref);
+ return ERROR_PORT_UNKNOWN;
+ } else if (port_ref.is_valid() && !new_port_ref.is_valid()) {
+ ClosePort(port_ref);
+ return ERROR_PORT_UNKNOWN;
+ }
+
+ return MergePortsInternal(port_ref, new_port_ref,
+ false /* allow_close_on_bad_state */);
+}
+
+int Node::AddPortWithName(const PortName& port_name, scoped_refptr<Port> port) {
+ PortLocker::AssertNoPortsLockedOnCurrentThread();
+ base::AutoLock lock(ports_lock_);
+ if (!ports_.emplace(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) {
+ PortLocker::AssertNoPortsLockedOnCurrentThread();
+ scoped_refptr<Port> port;
+ {
+ base::AutoLock lock(ports_lock_);
+ auto it = ports_.find(port_name);
+ if (it == ports_.end())
+ return;
+ port = std::move(it->second);
+ ports_.erase(it);
+ }
+ // NOTE: We are careful not to release the port's messages while holding any
+ // locks, since they may run arbitrary user code upon destruction.
+ std::vector<std::unique_ptr<UserMessageEvent>> messages;
+ {
+ PortRef port_ref(port_name, std::move(port));
+ SinglePortLocker locker(&port_ref);
+ locker.port()->message_queue.TakeAllMessages(&messages);
+ }
+ DVLOG(2) << "Deleted port " << port_name << "@" << name_;
+}
+
+int Node::SendUserMessageInternal(const PortRef& port_ref,
+ std::unique_ptr<UserMessageEvent>* message) {
+ std::unique_ptr<UserMessageEvent>& 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;
+ }
+
+ NodeName target_node;
+ int rv = PrepareToForwardUserMessage(port_ref, Port::kReceiving,
+ false /* ignore_closed_peer */, m.get(),
+ &target_node);
+ 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.
+
+ DCHECK_NE(kInvalidNodeName, target_node);
+ if (target_node != name_) {
+ delegate_->ForwardEvent(target_node, std::move(m));
+ return OK;
+ }
+
+ int accept_result = AcceptEvent(std::move(m));
+ if (accept_result != OK) {
+ // See comment above for why we don't return an error in this case.
+ DVLOG(2) << "AcceptEvent failed: " << accept_result;
+ }
+
+ return OK;
+}
+
+int Node::MergePortsInternal(const PortRef& port0_ref,
+ const PortRef& port1_ref,
+ bool allow_close_on_bad_state) {
+ const PortRef* port_refs[2] = {&port0_ref, &port1_ref};
+ {
+ base::Optional<PortLocker> locker(base::in_place, port_refs, 2);
+ auto* port0 = locker->GetPort(port0_ref);
+ auto* port1 = locker->GetPort(port1_ref);
+
+ // There are several conditions which must be met before we'll consider
+ // merging two ports:
+ //
+ // - They must both be in the kReceiving state
+ // - They must not be each other's peer
+ // - They must have never sent a user message
+ //
+ // If any of these criteria are not met, we fail early.
+ if (port0->state != Port::kReceiving || port1->state != Port::kReceiving ||
+ (port0->peer_node_name == name_ &&
+ port0->peer_port_name == port1_ref.name()) ||
+ (port1->peer_node_name == name_ &&
+ port1->peer_port_name == port0_ref.name()) ||
+ port0->next_sequence_num_to_send != kInitialSequenceNum ||
+ port1->next_sequence_num_to_send != kInitialSequenceNum) {
+ // On failure, we only close a port if it was at least properly in the
+ // |kReceiving| state. This avoids getting the system in an inconsistent
+ // state by e.g. closing a proxy abruptly.
+ //
+ // Note that we must release the port locks before closing ports.
+ const bool close_port0 =
+ port0->state == Port::kReceiving || allow_close_on_bad_state;
+ const bool close_port1 =
+ port1->state == Port::kReceiving || allow_close_on_bad_state;
+ locker.reset();
+ if (close_port0)
+ ClosePort(port0_ref);
+ if (close_port1)
+ ClosePort(port1_ref);
+ return ERROR_PORT_STATE_UNEXPECTED;
+ }
+
+ // Swap the ports' peer information and switch them both to 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::kProxying;
+ port1->state = Port::kProxying;
+ if (port0->peer_closed)
+ port0->remove_proxy_on_last_message = true;
+ if (port1->peer_closed)
+ port1->remove_proxy_on_last_message = true;
+ }
+
+ // Flush any queued messages from the new proxies and, if successful, complete
+ // the merge by initiating proxy removals.
+ if (ForwardUserMessagesFromProxy(port0_ref) == OK &&
+ ForwardUserMessagesFromProxy(port1_ref) == OK) {
+ for (size_t i = 0; i < 2; ++i) {
+ bool try_remove_proxy_immediately = false;
+ ScopedEvent closure_event;
+ NodeName closure_event_target_node;
+ {
+ SinglePortLocker locker(port_refs[i]);
+ auto* port = locker.port();
+ DCHECK(port->state == Port::kProxying);
+ try_remove_proxy_immediately = port->remove_proxy_on_last_message;
+ if (try_remove_proxy_immediately || port->peer_closed) {
+ // If either end of the port cycle is closed, we propagate an
+ // ObserveClosure event.
+ closure_event_target_node = port->peer_node_name;
+ closure_event = std::make_unique<ObserveClosureEvent>(
+ port->peer_port_name, port->last_sequence_num_to_receive);
+ }
+ }
+ if (try_remove_proxy_immediately)
+ TryRemoveProxy(*port_refs[i]);
+ else
+ InitiateProxyRemoval(*port_refs[i]);
+
+ if (closure_event) {
+ delegate_->ForwardEvent(closure_event_target_node,
+ std::move(closure_event));
+ }
+ }
+
+ return OK;
+ }
+
+ // If we failed to forward proxied messages, we keep the system in a
+ // consistent state by undoing the peer swap and closing the ports.
+ {
+ PortLocker locker(port_refs, 2);
+ auto* port0 = locker.GetPort(port0_ref);
+ auto* port1 = locker.GetPort(port1_ref);
+ 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;
+ DCHECK_EQ(Port::kProxying, port0->state);
+ DCHECK_EQ(Port::kProxying, port1->state);
+ port0->state = Port::kReceiving;
+ port1->state = Port::kReceiving;
+ }
+
+ ClosePort(port0_ref);
+ ClosePort(port1_ref);
+ return ERROR_PORT_STATE_UNEXPECTED;
+}
+
+void Node::ConvertToProxy(Port* port,
+ const NodeName& to_node_name,
+ PortName* port_name,
+ Event::PortDescriptor* port_descriptor) {
+ port->AssertLockAcquired();
+ PortName local_port_name = *port_name;
+
+ PortName new_port_name;
+ 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 Event::PortDescriptor& port_descriptor) {
+ scoped_refptr<Port> port =
+ base::MakeRefCounted<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_->ForwardEvent(
+ port_descriptor.referring_node_name,
+ std::make_unique<PortAcceptedEvent>(port_descriptor.referring_port_name));
+ return OK;
+}
+
+int Node::PrepareToForwardUserMessage(const PortRef& forwarding_port_ref,
+ Port::State expected_port_state,
+ bool ignore_closed_peer,
+ UserMessageEvent* message,
+ NodeName* forward_to_node) {
+ bool target_is_remote = false;
+ for (;;) {
+ NodeName target_node_name;
+ {
+ SinglePortLocker locker(&forwarding_port_ref);
+ target_node_name = locker.port()->peer_node_name;
+ }
+
+ // NOTE: This may call out to arbitrary user code, so it's important to call
+ // it only while no port locks are held on the calling thread.
+ if (target_node_name != name_) {
+ if (!message->NotifyWillBeRoutedExternally()) {
+ LOG(ERROR) << "NotifyWillBeRoutedExternally failed unexpectedly.";
+ return ERROR_PORT_STATE_UNEXPECTED;
+ }
+ }
+
+ // Simultaneously lock the forwarding port as well as all attached ports.
+ base::StackVector<PortRef, 4> attached_port_refs;
+ base::StackVector<const PortRef*, 5> ports_to_lock;
+ attached_port_refs.container().resize(message->num_ports());
+ ports_to_lock.container().resize(message->num_ports() + 1);
+ ports_to_lock[0] = &forwarding_port_ref;
+ for (size_t i = 0; i < message->num_ports(); ++i) {
+ GetPort(message->ports()[i], &attached_port_refs[i]);
+ DCHECK(attached_port_refs[i].is_valid());
+ ports_to_lock[i + 1] = &attached_port_refs[i];
+ }
+ PortLocker locker(ports_to_lock.container().data(),
+ ports_to_lock.container().size());
+ auto* forwarding_port = locker.GetPort(forwarding_port_ref);
+
+ if (forwarding_port->peer_node_name != target_node_name) {
+ // The target node has already changed since we last held the lock.
+ if (target_node_name == name_) {
+ // If the target node was previously this local node, we need to restart
+ // the loop, since that means we may now route the message externally.
+ continue;
+ }
+
+ target_node_name = forwarding_port->peer_node_name;
+ }
+ target_is_remote = target_node_name != name_;
+
+ if (forwarding_port->state != expected_port_state)
+ return ERROR_PORT_STATE_UNEXPECTED;
+ if (forwarding_port->peer_closed && !ignore_closed_peer)
+ return ERROR_PORT_PEER_CLOSED;
+
+ // Messages may already have a sequence number if they're being forwarded by
+ // a proxy. Otherwise, use the next outgoing sequence number.
+ if (message->sequence_num() == 0)
+ message->set_sequence_num(forwarding_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) {
+ // Sanity check to make sure we can actually send all the attached ports.
+ // They must all be in the |kReceiving| state and must not be the sender's
+ // own peer.
+ DCHECK_EQ(message->num_ports(), attached_port_refs.container().size());
+ for (size_t i = 0; i < message->num_ports(); ++i) {
+ auto* attached_port = locker.GetPort(attached_port_refs[i]);
+ int error = OK;
+ if (attached_port->state != Port::kReceiving) {
+ error = ERROR_PORT_STATE_UNEXPECTED;
+ } else if (attached_port_refs[i].name() ==
+ forwarding_port->peer_port_name) {
+ error = ERROR_PORT_CANNOT_SEND_PEER;
+ }
+
+ if (error != OK) {
+ // Not going to send. Backpedal on the sequence number.
+ forwarding_port->next_sequence_num_to_send--;
+ return error;
+ }
+ }
+
+ if (target_is_remote) {
+ // We only bother to proxy and rewrite ports in the event if it's
+ // going to be routed to an external node. This substantially reduces
+ // the amount of port churn in the system, as many port-carrying
+ // events are routed at least 1 or 2 intra-node hops before (if ever)
+ // being routed externally.
+ Event::PortDescriptor* port_descriptors = message->port_descriptors();
+ for (size_t i = 0; i < message->num_ports(); ++i) {
+ ConvertToProxy(locker.GetPort(attached_port_refs[i]),
+ target_node_name, message->ports() + i,
+ port_descriptors + i);
+ }
+ }
+ }
+
+#if DCHECK_IS_ON()
+ DVLOG(4) << "Sending message " << message->sequence_num()
+ << " [ports=" << ports_buf.str() << "]"
+ << " from " << forwarding_port_ref.name() << "@" << name_ << " to "
+ << forwarding_port->peer_port_name << "@" << target_node_name;
+#endif
+
+ *forward_to_node = target_node_name;
+ message->set_port_name(forwarding_port->peer_port_name);
+ break;
+ }
+
+ if (target_is_remote) {
+ for (size_t i = 0; i < message->num_ports(); ++i) {
+ // For any ports that were converted to proxies above, make sure their
+ // prior local peer (if applicable) receives a status update so it can be
+ // made aware of its peer's location.
+ const Event::PortDescriptor& descriptor = message->port_descriptors()[i];
+ if (descriptor.peer_node_name == name_) {
+ PortRef local_peer;
+ if (GetPort(descriptor.peer_port_name, &local_peer) == OK)
+ delegate_->PortStatusChanged(local_peer);
+ }
+ }
+ }
+
+ return OK;
+}
+
+int Node::BeginProxying(const PortRef& port_ref) {
+ {
+ SinglePortLocker locker(&port_ref);
+ auto* port = locker.port();
+ if (port->state != Port::kBuffering)
+ return OOPS(ERROR_PORT_STATE_UNEXPECTED);
+ port->state = Port::kProxying;
+ }
+
+ int rv = ForwardUserMessagesFromProxy(port_ref);
+ if (rv != OK)
+ return rv;
+
+ bool try_remove_proxy_immediately;
+ ScopedEvent closure_event;
+ NodeName closure_target_node;
+ {
+ SinglePortLocker locker(&port_ref);
+ auto* port = locker.port();
+ if (port->state != Port::kProxying)
+ return OOPS(ERROR_PORT_STATE_UNEXPECTED);
+
+ try_remove_proxy_immediately = port->remove_proxy_on_last_message;
+ if (try_remove_proxy_immediately) {
+ // Make sure we propagate closure to our current peer.
+ closure_target_node = port->peer_node_name;
+ closure_event = std::make_unique<ObserveClosureEvent>(
+ port->peer_port_name, port->last_sequence_num_to_receive);
+ }
+ }
+
+ if (try_remove_proxy_immediately) {
+ TryRemoveProxy(port_ref);
+ delegate_->ForwardEvent(closure_target_node, std::move(closure_event));
+ } else {
+ InitiateProxyRemoval(port_ref);
+ }
+
+ return OK;
+}
+
+int Node::ForwardUserMessagesFromProxy(const PortRef& port_ref) {
+ for (;;) {
+ // NOTE: 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.
+ std::unique_ptr<UserMessageEvent> message;
+ {
+ SinglePortLocker locker(&port_ref);
+ locker.port()->message_queue.GetNextMessage(&message, nullptr);
+ if (!message)
+ break;
+ }
+
+ NodeName target_node;
+ int rv = PrepareToForwardUserMessage(port_ref, Port::kProxying,
+ true /* ignore_closed_peer */,
+ message.get(), &target_node);
+ if (rv != OK)
+ return rv;
+
+ delegate_->ForwardEvent(target_node, std::move(message));
+ }
+ return OK;
+}
+
+void Node::InitiateProxyRemoval(const PortRef& port_ref) {
+ NodeName peer_node_name;
+ PortName peer_port_name;
+ {
+ SinglePortLocker locker(&port_ref);
+ auto* port = locker.port();
+ peer_node_name = port->peer_node_name;
+ peer_port_name = port->peer_port_name;
+ }
+
+ // 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).
+ delegate_->ForwardEvent(peer_node_name,
+ std::make_unique<ObserveProxyEvent>(
+ peer_port_name, name_, port_ref.name(),
+ peer_node_name, peer_port_name));
+}
+
+void Node::TryRemoveProxy(const PortRef& port_ref) {
+ bool should_erase = false;
+ NodeName removal_target_node;
+ ScopedEvent removal_event;
+
+ {
+ SinglePortLocker locker(&port_ref);
+ auto* port = locker.port();
+ 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)) {
+ should_erase = true;
+ if (port->send_on_proxy_removal) {
+ removal_target_node = port->send_on_proxy_removal->first;
+ removal_event = std::move(port->send_on_proxy_removal->second);
+ }
+ } else {
+ DVLOG(2) << "Cannot remove port " << port_ref.name() << "@" << name_
+ << " now; waiting for more messages";
+ }
+ }
+
+ if (should_erase)
+ ErasePort(port_ref.name());
+
+ if (removal_event)
+ delegate_->ForwardEvent(removal_target_node, std::move(removal_event));
+}
+
+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<PortRef> ports_to_notify;
+ std::vector<PortName> dead_proxies_to_broadcast;
+ std::vector<std::unique_ptr<UserMessageEvent>> undelivered_messages;
+
+ {
+ PortLocker::AssertNoPortsLockedOnCurrentThread();
+ base::AutoLock ports_lock(ports_lock_);
+
+ for (auto iter = ports_.begin(); iter != ports_.end(); ++iter) {
+ PortRef port_ref(iter->first, iter->second);
+ {
+ SinglePortLocker locker(&port_ref);
+ auto* port = locker.port();
+
+ 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);
+ std::vector<std::unique_ptr<UserMessageEvent>> messages;
+ iter->second->message_queue.TakeAllMessages(&messages);
+ for (auto& message : messages)
+ undelivered_messages.emplace_back(std::move(message));
+ }
+ }
+ }
+ }
+ }
+
+ for (const auto& proxy_name : dead_proxies_to_broadcast) {
+ ErasePort(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.
+ delegate_->BroadcastEvent(std::make_unique<ObserveProxyEvent>(
+ kInvalidPortName, name_, proxy_name, kInvalidNodeName,
+ kInvalidPortName));
+
+ // 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 undelivered messages.
+ for (const auto& message : undelivered_messages) {
+ for (size_t i = 0; i < message->num_ports(); ++i) {
+ PortRef ref;
+ if (GetPort(message->ports()[i], &ref) == OK)
+ ClosePort(ref);
+ }
+ }
+}
+
+Node::DelegateHolder::DelegateHolder(Node* node, NodeDelegate* delegate)
+ : node_(node), delegate_(delegate) {
+ DCHECK(node_);
+}
+
+Node::DelegateHolder::~DelegateHolder() {}
+
+#if DCHECK_IS_ON()
+void Node::DelegateHolder::EnsureSafeDelegateAccess() const {
+ PortLocker::AssertNoPortsLockedOnCurrentThread();
+ base::AutoLock lock(node_->ports_lock_);
+}
+#endif
+
+} // namespace ports
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/ports/node.h b/mojo/core/ports/node.h
new file mode 100644
index 0000000000..a9576bf266
--- /dev/null
+++ b/mojo/core/ports/node.h
@@ -0,0 +1,250 @@
+// 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_CORE_PORTS_NODE_H_
+#define MOJO_CORE_PORTS_NODE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <queue>
+#include <unordered_map>
+
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "mojo/core/ports/event.h"
+#include "mojo/core/ports/name.h"
+#include "mojo/core/ports/port.h"
+#include "mojo/core/ports/port_ref.h"
+#include "mojo/core/ports/user_data.h"
+
+namespace mojo {
+namespace core {
+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;
+ bool peer_remote;
+ size_t queued_message_count;
+ size_t queued_num_bytes;
+};
+
+class MessageFilter;
+class NodeDelegate;
+
+// A Node maintains a collection of Ports (see port.h) indexed by unique 128-bit
+// addresses (names), performing routing and processing of events among the
+// Ports within the Node and to or from other Nodes in the system. Typically
+// (and practically, in all uses today) there is a single Node per system
+// process. Thus a Node boundary effectively models a process boundary.
+//
+// New Ports can be created uninitialized using CreateUninitializedPort (and
+// later initialized using InitializePort), or created in a fully initialized
+// state using CreatePortPair(). Initialized ports have exactly one conjugate
+// port which is the ultimate receiver of any user messages sent by that port.
+// See SendUserMessage().
+//
+// In addition to routing user message events, various control events are used
+// by Nodes to coordinate Port behavior and lifetime within and across Nodes.
+// See Event documentation for description of different types of events used by
+// a Node to coordinate behavior.
+class COMPONENT_EXPORT(MOJO_CORE_PORTS) 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<UserData> user_data);
+ int GetUserData(const PortRef& port_ref, scoped_refptr<UserData>* 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,
+ std::unique_ptr<UserMessageEvent>* 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 SendUserMessage(const PortRef& port_ref,
+ std::unique_ptr<UserMessageEvent> message);
+
+ // Corresponding to NodeDelegate::ForwardEvent.
+ int AcceptEvent(ScopedEvent event);
+
+ // 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:
+ // Helper to ensure that a Node always calls into its delegate safely, i.e.
+ // without holding any internal locks.
+ class DelegateHolder {
+ public:
+ DelegateHolder(Node* node, NodeDelegate* delegate);
+ ~DelegateHolder();
+
+ NodeDelegate* operator->() const {
+ EnsureSafeDelegateAccess();
+ return delegate_;
+ }
+
+ private:
+#if DCHECK_IS_ON()
+ void EnsureSafeDelegateAccess() const;
+#else
+ void EnsureSafeDelegateAccess() const {}
+#endif
+
+ Node* const node_;
+ NodeDelegate* const delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(DelegateHolder);
+ };
+
+ int OnUserMessage(std::unique_ptr<UserMessageEvent> message);
+ int OnPortAccepted(std::unique_ptr<PortAcceptedEvent> event);
+ int OnObserveProxy(std::unique_ptr<ObserveProxyEvent> event);
+ int OnObserveProxyAck(std::unique_ptr<ObserveProxyAckEvent> event);
+ int OnObserveClosure(std::unique_ptr<ObserveClosureEvent> event);
+ int OnMergePort(std::unique_ptr<MergePortEvent> event);
+
+ int AddPortWithName(const PortName& port_name, scoped_refptr<Port> port);
+ void ErasePort(const PortName& port_name);
+
+ int SendUserMessageInternal(const PortRef& port_ref,
+ std::unique_ptr<UserMessageEvent>* message);
+ int MergePortsInternal(const PortRef& port0_ref,
+ const PortRef& port1_ref,
+ bool allow_close_on_bad_state);
+ void ConvertToProxy(Port* port,
+ const NodeName& to_node_name,
+ PortName* port_name,
+ Event::PortDescriptor* port_descriptor);
+ int AcceptPort(const PortName& port_name,
+ const Event::PortDescriptor& port_descriptor);
+
+ int PrepareToForwardUserMessage(const PortRef& forwarding_port_ref,
+ Port::State expected_port_state,
+ bool ignore_closed_peer,
+ UserMessageEvent* message,
+ NodeName* forward_to_node);
+ int BeginProxying(const PortRef& port_ref);
+ int ForwardUserMessagesFromProxy(const PortRef& port_ref);
+ void InitiateProxyRemoval(const PortRef& port_ref);
+ void TryRemoveProxy(const PortRef& port_ref);
+ void DestroyAllPortsWithPeer(const NodeName& node_name,
+ const PortName& port_name);
+
+ const NodeName name_;
+ const DelegateHolder delegate_;
+
+ // Guards |ports_|. This must never be acquired while an individual port's
+ // lock is held on the same thread. Conversely, individual port locks may be
+ // acquired while this one is held.
+ //
+ // Because UserMessage events may execute arbitrary user code during
+ // destruction, it is also important to ensure that such events are never
+ // destroyed while this (or any individual Port) lock is held.
+ base::Lock ports_lock_;
+ std::unordered_map<PortName, scoped_refptr<Port>> ports_;
+
+ DISALLOW_COPY_AND_ASSIGN(Node);
+};
+
+} // namespace ports
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_PORTS_NODE_H_
diff --git a/mojo/core/ports/node_delegate.h b/mojo/core/ports/node_delegate.h
new file mode 100644
index 0000000000..afe1c4cd97
--- /dev/null
+++ b/mojo/core/ports/node_delegate.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_CORE_PORTS_NODE_DELEGATE_H_
+#define MOJO_CORE_PORTS_NODE_DELEGATE_H_
+
+#include <stddef.h>
+
+#include "mojo/core/ports/event.h"
+#include "mojo/core/ports/name.h"
+#include "mojo/core/ports/port_ref.h"
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+class NodeDelegate {
+ public:
+ virtual ~NodeDelegate() {}
+
+ // Forward an event (possibly asynchronously) to the specified node.
+ virtual void ForwardEvent(const NodeName& node, ScopedEvent event) = 0;
+
+ // Broadcast an event to all nodes.
+ virtual void BroadcastEvent(ScopedEvent event) = 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 core
+} // namespace mojo
+
+#endif // MOJO_CORE_PORTS_NODE_DELEGATE_H_
diff --git a/mojo/core/ports/port.cc b/mojo/core/ports/port.cc
new file mode 100644
index 0000000000..7186979021
--- /dev/null
+++ b/mojo/core/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/core/ports/port.h"
+
+namespace mojo {
+namespace core {
+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 core
+} // namespace mojo
diff --git a/mojo/core/ports/port.h b/mojo/core/ports/port.h
new file mode 100644
index 0000000000..d1a825e2c6
--- /dev/null
+++ b/mojo/core/ports/port.h
@@ -0,0 +1,175 @@
+// 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_CORE_PORTS_PORT_H_
+#define MOJO_CORE_PORTS_PORT_H_
+
+#include <memory>
+#include <queue>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "mojo/core/ports/event.h"
+#include "mojo/core/ports/message_queue.h"
+#include "mojo/core/ports/user_data.h"
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+class PortLocker;
+
+// A Port is essentially a node in a circular list of addresses. For the sake of
+// this documentation such a list will henceforth be referred to as a "route."
+// Routes are the fundamental medium upon which all Node event circulation takes
+// place and are thus the backbone of all Mojo message passing.
+//
+// Each Port is identified by a 128-bit address within a Node (see node.h). A
+// Port doesn't really *do* anything per se: it's a named collection of state,
+// and its owning Node manages all event production, transmission, routing, and
+// processing logic. See Node for more details on how Ports may be used to
+// transmit arbitrary user messages as well as other Ports.
+//
+// Ports may be in any of a handful of states (see State below) which dictate
+// how they react to system events targeting them. In the simplest and most
+// common case, Ports are initially created as an entangled pair (i.e. a simple
+// cycle consisting of two Ports) both in the |kReceiving| State. Consider Ports
+// we'll label |A| and |B| here, which may be created using
+// Node::CreatePortPair():
+//
+// +-----+ +-----+
+// | |--------->| |
+// | A | | B |
+// | |<---------| |
+// +-----+ +-----+
+//
+// |A| references |B| via |peer_node_name| and |peer_port_name|, while |B| in
+// turn references |A|. Note that a Node is NEVER aware of who is sending events
+// to a given Port; it is only aware of where it must route events FROM a given
+// Port.
+//
+// For the sake of documentation, we refer to one receiving port in a route as
+// the "conjugate" of the other. A receiving port's conjugate is also its peer
+// upon initial creation, but because of proxying this may not be the case at a
+// later time.
+//
+// ALL access to this data structure must be guarded by |lock_| acquisition,
+// which is only possible using a PortLocker. PortLocker ensures that
+// overlapping Port lock acquisitions on a single thread are always acquired in
+// a globally consistent order.
+class Port : public base::RefCountedThreadSafe<Port> {
+ public:
+ // The state of a given Port. A Port may only exist in one of these states at
+ // any given time.
+ enum State {
+ // The Port is not yet paired with a peer and is therefore unusable. See
+ // Node::CreateUninitializedPort and Node::InitializePort for motivation.
+ kUninitialized,
+
+ // The Port is publicly visible outside of its Node and may be used to send
+ // and receive user messages. There are always AT MOST two |kReceiving|
+ // Ports along any given route. A user message event sent from a receiving
+ // port is always circulated along the Port's route until it reaches either
+ // a dead-end -- in which case the route is broken -- or it reaches the
+ // other receiving Port in the route -- in which case it lands in that
+ // Port's incoming message queue which can by read by user code.
+ kReceiving,
+
+ // The Port has been taken out of the |kReceiving| state in preparation for
+ // proxying to a new destination. A Port enters this state immediately when
+ // it's attached to a user message and may only leave this state when
+ // transitioning to |kProxying|. See Node for more details.
+ kBuffering,
+
+ // The Port is forwarding all user messages (and most other events) to its
+ // peer without discretion. Ports in the |kProxying| state may never leave
+ // this state and only exist temporarily until their owning Node has
+ // established that no more events will target them. See Node for more
+ // details.
+ kProxying,
+
+ // The Port has been closed and is now permanently unusable. Only
+ // |kReceiving| ports can be closed.
+ kClosed
+ };
+
+ // The current State of the Port.
+ State state;
+
+ // The Node and Port address to which events should be routed FROM this Port.
+ // Note that this is NOT necessarily the address of the Port currently sending
+ // events TO this Port.
+ NodeName peer_node_name;
+ PortName peer_port_name;
+
+ // The next available sequence number to use for outgoing user message events
+ // originating from this port.
+ uint64_t next_sequence_num_to_send;
+
+ // The sequence number of the last message this Port should ever expect to
+ // receive in its lifetime. May be used to determine that a proxying port is
+ // ready to be destroyed or that a receiving port's conjugate has been closed
+ // and we know the sequence number of the last message it sent.
+ uint64_t last_sequence_num_to_receive;
+
+ // The queue of incoming user messages received by this Port. Only non-empty
+ // for buffering or receiving Ports. When a buffering port enters the proxying
+ // state, it flushes its queue and the proxy then bypasses the queue
+ // indefinitely.
+ //
+ // A receiving port's queue only has elements removed by user code reading
+ // messages from the port.
+ //
+ // Note that this is a priority queue which only exposes messages to consumers
+ // in strict sequential order.
+ MessageQueue message_queue;
+
+ // In some edge cases, a Node may need to remember to route a single special
+ // event upon destruction of this (proxying) Port. That event is stashed here
+ // in the interim.
+ std::unique_ptr<std::pair<NodeName, ScopedEvent>> send_on_proxy_removal;
+
+ // Arbitrary user data attached to the Port. In practice, Mojo uses this to
+ // stash an observer interface which can be notified about various Port state
+ // changes.
+ scoped_refptr<UserData> user_data;
+
+ // Indicates that this (proxying) Port has received acknowledgement that no
+ // new user messages will be routed to it. If |true|, the proxy will be
+ // removed once it has received and forwarded all sequenced messages up to and
+ // including the one numbered |last_sequence_num_to_receive|.
+ bool remove_proxy_on_last_message;
+
+ // Indicates that this Port is aware that its nearest (in terms of forward,
+ // non-zero cyclic routing distance) receiving Port has been closed.
+ bool peer_closed;
+
+ Port(uint64_t next_sequence_num_to_send,
+ uint64_t next_sequence_num_to_receive);
+
+ void AssertLockAcquired() {
+#if DCHECK_IS_ON()
+ lock_.AssertAcquired();
+#endif
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<Port>;
+ friend class PortLocker;
+
+ ~Port();
+
+ base::Lock lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(Port);
+};
+
+} // namespace ports
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_PORTS_PORT_H_
diff --git a/mojo/core/ports/port_locker.cc b/mojo/core/ports/port_locker.cc
new file mode 100644
index 0000000000..880492332d
--- /dev/null
+++ b/mojo/core/ports/port_locker.cc
@@ -0,0 +1,74 @@
+// 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/core/ports/port_locker.h"
+
+#include <algorithm>
+
+#include "mojo/core/ports/port.h"
+
+#if DCHECK_IS_ON()
+#include "base/threading/thread_local.h"
+#endif
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+namespace {
+
+#if DCHECK_IS_ON()
+void UpdateTLS(PortLocker* old_locker, PortLocker* new_locker) {
+ // Sanity check when DCHECK is on to make sure there is only ever one
+ // PortLocker extant on the current thread.
+ static auto* tls = new base::ThreadLocalPointer<PortLocker>();
+ DCHECK_EQ(old_locker, tls->Get());
+ tls->Set(new_locker);
+}
+#endif
+
+} // namespace
+
+PortLocker::PortLocker(const PortRef** port_refs, size_t num_ports)
+ : port_refs_(port_refs), num_ports_(num_ports) {
+#if DCHECK_IS_ON()
+ UpdateTLS(nullptr, this);
+#endif
+
+ // Sort the ports by address to lock them in a globally consistent order.
+ std::sort(
+ port_refs_, port_refs_ + num_ports_,
+ [](const PortRef* a, const PortRef* b) { return a->port() < b->port(); });
+ for (size_t i = 0; i < num_ports_; ++i) {
+ // TODO(crbug.com/725605): Remove this CHECK.
+ CHECK(port_refs_[i]->port());
+ port_refs_[i]->port()->lock_.Acquire();
+ }
+}
+
+PortLocker::~PortLocker() {
+ for (size_t i = 0; i < num_ports_; ++i)
+ port_refs_[i]->port()->lock_.Release();
+
+#if DCHECK_IS_ON()
+ UpdateTLS(this, nullptr);
+#endif
+}
+
+#if DCHECK_IS_ON()
+// static
+void PortLocker::AssertNoPortsLockedOnCurrentThread() {
+ // Forces a DCHECK if the TLS PortLocker is anything other than null.
+ UpdateTLS(nullptr, nullptr);
+}
+#endif
+
+SinglePortLocker::SinglePortLocker(const PortRef* port_ref)
+ : port_ref_(port_ref), locker_(&port_ref_, 1) {}
+
+SinglePortLocker::~SinglePortLocker() = default;
+
+} // namespace ports
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/ports/port_locker.h b/mojo/core/ports/port_locker.h
new file mode 100644
index 0000000000..0da2654563
--- /dev/null
+++ b/mojo/core/ports/port_locker.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_CORE_PORTS_PORT_LOCKER_H_
+#define MOJO_CORE_PORTS_PORT_LOCKER_H_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "mojo/core/ports/port_ref.h"
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+class Port;
+class PortRef;
+
+// A helper which must be used to acquire individual Port locks. Any given
+// thread may have at most one of these alive at any time. This ensures that
+// when multiple ports are locked, they're locked in globally consistent order.
+//
+// Port locks are acquired upon construction of this object and released upon
+// destruction.
+class PortLocker {
+ public:
+ // Constructs a PortLocker over a sequence of |num_ports| contiguous
+ // |PortRef*|s. The sequence may be reordered by this constructor, and upon
+ // return, all referenced ports' locks are held.
+ PortLocker(const PortRef** port_refs, size_t num_ports);
+ ~PortLocker();
+
+ // Provides safe access to a PortRef's Port. Note that in release builds this
+ // doesn't do anything other than pass through to the private accessor on
+ // |port_ref|, but it does force callers to go through a PortLocker to get to
+ // the state, thus minimizing the likelihood that they'll go and do something
+ // stupid.
+ Port* GetPort(const PortRef& port_ref) const {
+#if DCHECK_IS_ON()
+ // Sanity check when DCHECK is on to ensure this is actually a port whose
+ // lock is held by this PortLocker.
+ bool is_port_locked = false;
+ for (size_t i = 0; i < num_ports_ && !is_port_locked; ++i)
+ if (port_refs_[i]->port() == port_ref.port())
+ is_port_locked = true;
+ DCHECK(is_port_locked);
+#endif
+ return port_ref.port();
+ }
+
+// A helper which can be used to verify that no Port locks are held on the
+// current thread. In non-DCHECK builds this is a no-op.
+#if DCHECK_IS_ON()
+ static void AssertNoPortsLockedOnCurrentThread();
+#else
+ static void AssertNoPortsLockedOnCurrentThread() {}
+#endif
+
+ private:
+ const PortRef** const port_refs_;
+ const size_t num_ports_;
+
+ DISALLOW_COPY_AND_ASSIGN(PortLocker);
+};
+
+// Convenience wrapper for a PortLocker that locks a single port.
+class SinglePortLocker {
+ public:
+ explicit SinglePortLocker(const PortRef* port_ref);
+ ~SinglePortLocker();
+
+ Port* port() const { return locker_.GetPort(*port_ref_); }
+
+ private:
+ const PortRef* port_ref_;
+ PortLocker locker_;
+
+ DISALLOW_COPY_AND_ASSIGN(SinglePortLocker);
+};
+
+} // namespace ports
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_PORTS_PORT_LOCKER_H_
diff --git a/mojo/core/ports/port_ref.cc b/mojo/core/ports/port_ref.cc
new file mode 100644
index 0000000000..a3d312bc39
--- /dev/null
+++ b/mojo/core/ports/port_ref.cc
@@ -0,0 +1,30 @@
+// 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/core/ports/port_ref.h"
+
+#include "mojo/core/ports/port.h"
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+PortRef::~PortRef() {}
+
+PortRef::PortRef() {}
+
+PortRef::PortRef(const PortName& name, scoped_refptr<Port> port)
+ : name_(name), port_(std::move(port)) {}
+
+PortRef::PortRef(const PortRef& other) = default;
+
+PortRef::PortRef(PortRef&& other) = default;
+
+PortRef& PortRef::operator=(const PortRef& other) = default;
+
+PortRef& PortRef::operator=(PortRef&& other) = default;
+
+} // namespace ports
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/ports/port_ref.h b/mojo/core/ports/port_ref.h
new file mode 100644
index 0000000000..b63d6cfcd0
--- /dev/null
+++ b/mojo/core/ports/port_ref.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 MOJO_CORE_PORTS_PORT_REF_H_
+#define MOJO_CORE_PORTS_PORT_REF_H_
+
+#include "base/component_export.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "mojo/core/ports/name.h"
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+class Port;
+class PortLocker;
+
+class COMPONENT_EXPORT(MOJO_CORE_PORTS) PortRef {
+ public:
+ ~PortRef();
+ PortRef();
+ PortRef(const PortName& name, scoped_refptr<Port> port);
+
+ PortRef(const PortRef& other);
+ PortRef(PortRef&& other);
+
+ PortRef& operator=(const PortRef& other);
+ PortRef& operator=(PortRef&& other);
+
+ const PortName& name() const { return name_; }
+
+ bool is_valid() const { return !!port_; }
+
+ private:
+ friend class PortLocker;
+
+ Port* port() const { return port_.get(); }
+
+ PortName name_;
+ scoped_refptr<Port> port_;
+};
+
+} // namespace ports
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_PORTS_PORT_REF_H_
diff --git a/mojo/core/ports/ports_unittest.cc b/mojo/core/ports/ports_unittest.cc
new file mode 100644
index 0000000000..3d18bb680f
--- /dev/null
+++ b/mojo/core/ports/ports_unittest.cc
@@ -0,0 +1,1644 @@
+// 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 <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <map>
+#include <sstream>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/containers/queue.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.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/test/scoped_task_environment.h"
+#include "base/threading/thread.h"
+#include "mojo/core/ports/event.h"
+#include "mojo/core/ports/node.h"
+#include "mojo/core/ports/node_delegate.h"
+#include "mojo/core/ports/user_message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace core {
+namespace ports {
+namespace test {
+
+namespace {
+
+// TODO(rockot): Remove this unnecessary alias.
+using ScopedMessage = std::unique_ptr<UserMessageEvent>;
+
+class TestMessage : public UserMessage {
+ public:
+ static const TypeInfo kUserMessageTypeInfo;
+
+ TestMessage(const base::StringPiece& payload)
+ : UserMessage(&kUserMessageTypeInfo), payload_(payload) {}
+ ~TestMessage() override {}
+
+ const std::string& payload() const { return payload_; }
+
+ private:
+ std::string payload_;
+};
+
+const UserMessage::TypeInfo TestMessage::kUserMessageTypeInfo = {};
+
+ScopedMessage NewUserMessageEvent(const base::StringPiece& payload,
+ size_t num_ports) {
+ auto event = std::make_unique<UserMessageEvent>(num_ports);
+ event->AttachMessage(std::make_unique<TestMessage>(payload));
+ return event;
+}
+
+bool MessageEquals(const ScopedMessage& message, const base::StringPiece& s) {
+ return message->GetMessage<TestMessage>()->payload() == s;
+}
+
+class TestNode;
+
+class MessageRouter {
+ public:
+ virtual ~MessageRouter() {}
+
+ virtual void ForwardEvent(TestNode* from_node,
+ const NodeName& node_name,
+ ScopedEvent event) = 0;
+ virtual void BroadcastEvent(TestNode* from_node, ScopedEvent event) = 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)),
+ events_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_events_.empty() || (block_on_event_ && blocked_));
+ }
+
+ void BlockOnEvent(Event::Type type) {
+ base::AutoLock lock(lock_);
+ blocked_event_type_ = type;
+ block_on_event_ = true;
+ }
+
+ void Unblock() {
+ base::AutoLock lock(lock_);
+ block_on_event_ = false;
+ events_available_event_.Signal();
+ }
+
+ void Start(MessageRouter* router) {
+ router_ = router;
+ node_thread_.Start();
+ node_thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&TestNode::ProcessEvents, base::Unretained(this)));
+ }
+
+ void StopWhenIdle() {
+ base::AutoLock lock(lock_);
+ should_quit_ = true;
+ events_available_event_.Signal();
+ }
+
+ void WakeUp() { events_available_event_.Signal(); }
+
+ int SendStringMessage(const PortRef& port, const std::string& s) {
+ return node_.SendUserMessage(port, NewUserMessageEvent(s, 0));
+ }
+
+ int SendStringMessageWithPort(const PortRef& port,
+ const std::string& s,
+ const PortName& sent_port_name) {
+ auto event = NewUserMessageEvent(s, 1);
+ event->ports()[0] = sent_port_name;
+ return node_.SendUserMessage(port, std::move(event));
+ }
+
+ 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 EnqueueEvent(ScopedEvent event) {
+ idle_event_.Reset();
+
+ // NOTE: This may be called from ForwardMessage and thus must not reenter
+ // |node_|.
+ base::AutoLock lock(lock_);
+ incoming_events_.emplace(std::move(event));
+ events_available_event_.Signal();
+ }
+
+ void ForwardEvent(const NodeName& node_name, ScopedEvent event) override {
+ {
+ base::AutoLock lock(lock_);
+ if (drop_messages_) {
+ DVLOG(1) << "Dropping ForwardMessage from node " << node_name_ << " to "
+ << node_name;
+
+ base::AutoUnlock unlock(lock_);
+ ClosePortsInEvent(event.get());
+ return;
+ }
+ }
+
+ DCHECK(router_);
+ DVLOG(1) << "ForwardEvent from node " << node_name_ << " to " << node_name;
+ router_->ForwardEvent(this, node_name, std::move(event));
+ }
+
+ void BroadcastEvent(ScopedEvent event) override {
+ router_->BroadcastEvent(this, std::move(event));
+ }
+
+ 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 ClosePortsInEvent(Event* event) {
+ if (event->type() != Event::Type::kUserMessage)
+ return;
+
+ UserMessageEvent* message_event = static_cast<UserMessageEvent*>(event);
+ for (size_t i = 0; i < message_event->num_ports(); ++i) {
+ PortRef port;
+ ASSERT_EQ(OK, node_.GetPort(message_event->ports()[i], &port));
+ EXPECT_EQ(OK, node_.ClosePort(port));
+ }
+ }
+
+ private:
+ void ProcessEvents() {
+ for (;;) {
+ events_available_event_.Wait();
+ base::AutoLock lock(lock_);
+
+ if (should_quit_)
+ return;
+
+ dispatching_ = true;
+ while (!incoming_events_.empty()) {
+ if (block_on_event_ &&
+ incoming_events_.front()->type() == blocked_event_type_) {
+ blocked_ = true;
+ // Go idle if we hit a blocked event type.
+ break;
+ } else {
+ blocked_ = false;
+ }
+ ScopedEvent event = std::move(incoming_events_.front());
+ incoming_events_.pop();
+
+ // NOTE: AcceptMessage() can re-enter this object to call any of the
+ // NodeDelegate interface methods.
+ base::AutoUnlock unlock(lock_);
+ node_.AcceptEvent(std::move(event));
+ }
+
+ dispatching_ = false;
+ started_ = true;
+ idle_event_.Signal();
+ };
+ }
+
+ const NodeName node_name_;
+ Node node_;
+ MessageRouter* router_ = nullptr;
+
+ base::Thread node_thread_;
+ base::WaitableEvent events_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;
+ Event::Type blocked_event_type_;
+ base::queue<ScopedEvent> incoming_events_;
+ base::queue<ScopedMessage> 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<base::WaitableEvent*> 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 ForwardEvent(TestNode* from_node,
+ const NodeName& node_name,
+ ScopedEvent event) 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->ClosePortsInEvent(event.get());
+ return;
+ }
+
+ auto it = nodes_.find(node_name);
+ if (it == nodes_.end()) {
+ DVLOG(1) << "Node not found: " << node_name;
+ return;
+ }
+
+ it->second->EnqueueEvent(std::move(event));
+ }
+
+ void BroadcastEvent(TestNode* from_node, ScopedEvent event) 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;
+ node->EnqueueEvent(event->Clone());
+ }
+ }
+
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+ // Acquired before any operation which makes a Node busy, and before testing
+ // if all nodes are idle.
+ base::Lock global_lock_;
+
+ base::Lock lock_;
+ std::map<NodeName, TestNode*> 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.ClosePortsInEvent(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(Event::Type::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(Event::Type::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.ClosePortsInEvent(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, node0.node().CreateUninitializedPort(&X));
+ EXPECT_EQ(OK, node1.node().CreateUninitializedPort(&Y));
+ EXPECT_EQ(OK, node0.node().InitializePort(X, node1.name(), Y.name()));
+ EXPECT_EQ(OK, node1.node().InitializePort(Y, node0.name(), X.name()));
+
+ // 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(Event::Type::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 time the merge is processed.
+ // As a result, B should be closed.
+ EXPECT_EQ(OK, node1.SendStringMessageWithPort(Y, "foo", C));
+
+ node1.Unblock();
+
+ WaitForIdle();
+
+ ASSERT_TRUE(node0.ReadMessage(X, &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, 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, node0.node().ClosePort(E));
+
+ WaitForIdle();
+
+ // Expect everything to have gone away.
+ EXPECT_TRUE(node0.node().CanShutdownCleanly());
+ EXPECT_TRUE(node1.node().CanShutdownCleanly());
+}
+
+TEST_F(PortsTest, RemotePeerStatus) {
+ TestNode node0(0);
+ AddNode(&node0);
+
+ TestNode node1(1);
+ AddNode(&node1);
+
+ // Create a local port pair. Neither port should appear to have a remote peer.
+ PortRef a, b;
+ PortStatus status;
+ node0.node().CreatePortPair(&a, &b);
+ ASSERT_EQ(OK, node0.node().GetStatus(a, &status));
+ EXPECT_FALSE(status.peer_remote);
+ ASSERT_EQ(OK, node0.node().GetStatus(b, &status));
+ EXPECT_FALSE(status.peer_remote);
+
+ // Create a port pair spanning the two nodes. Both spanning ports should
+ // immediately appear to have a remote peer.
+ PortRef x0, x1;
+ CreatePortPair(&node0, &x0, &node1, &x1);
+
+ ASSERT_EQ(OK, node0.node().GetStatus(x0, &status));
+ EXPECT_TRUE(status.peer_remote);
+ ASSERT_EQ(OK, node1.node().GetStatus(x1, &status));
+ EXPECT_TRUE(status.peer_remote);
+
+ PortRef x2, x3;
+ CreatePortPair(&node0, &x2, &node1, &x3);
+
+ // Transfer |b| to |node1| and |x1| to |node0|. i.e., make the local peers
+ // remote and the remote peers local.
+ EXPECT_EQ(OK, node0.SendStringMessageWithPort(x2, "foo", b));
+ EXPECT_EQ(OK, node1.SendStringMessageWithPort(x3, "bar", x1));
+ WaitForIdle();
+
+ ScopedMessage message;
+ ASSERT_TRUE(node0.ReadMessage(x2, &message));
+ ASSERT_EQ(1u, message->num_ports());
+ ASSERT_EQ(OK, node0.node().GetPort(message->ports()[0], &x1));
+
+ ASSERT_TRUE(node1.ReadMessage(x3, &message));
+ ASSERT_EQ(1u, message->num_ports());
+ ASSERT_EQ(OK, node1.node().GetPort(message->ports()[0], &b));
+
+ // Now x0-x1 should be local to node0 and a-b should span the nodes.
+ ASSERT_EQ(OK, node0.node().GetStatus(x0, &status));
+ EXPECT_FALSE(status.peer_remote);
+ ASSERT_EQ(OK, node0.node().GetStatus(x1, &status));
+ EXPECT_FALSE(status.peer_remote);
+ ASSERT_EQ(OK, node0.node().GetStatus(a, &status));
+ EXPECT_TRUE(status.peer_remote);
+ ASSERT_EQ(OK, node1.node().GetStatus(b, &status));
+ EXPECT_TRUE(status.peer_remote);
+
+ // And swap them back one more time.
+ EXPECT_EQ(OK, node0.SendStringMessageWithPort(x2, "foo", x1));
+ EXPECT_EQ(OK, node1.SendStringMessageWithPort(x3, "bar", b));
+ WaitForIdle();
+
+ ASSERT_TRUE(node0.ReadMessage(x2, &message));
+ ASSERT_EQ(1u, message->num_ports());
+ ASSERT_EQ(OK, node0.node().GetPort(message->ports()[0], &b));
+
+ ASSERT_TRUE(node1.ReadMessage(x3, &message));
+ ASSERT_EQ(1u, message->num_ports());
+ ASSERT_EQ(OK, node1.node().GetPort(message->ports()[0], &x1));
+
+ ASSERT_EQ(OK, node0.node().GetStatus(x0, &status));
+ EXPECT_TRUE(status.peer_remote);
+ ASSERT_EQ(OK, node1.node().GetStatus(x1, &status));
+ EXPECT_TRUE(status.peer_remote);
+ ASSERT_EQ(OK, node0.node().GetStatus(a, &status));
+ EXPECT_FALSE(status.peer_remote);
+ ASSERT_EQ(OK, node0.node().GetStatus(b, &status));
+ EXPECT_FALSE(status.peer_remote);
+
+ EXPECT_EQ(OK, node0.node().ClosePort(x0));
+ EXPECT_EQ(OK, node1.node().ClosePort(x1));
+ EXPECT_EQ(OK, node0.node().ClosePort(x2));
+ EXPECT_EQ(OK, node1.node().ClosePort(x3));
+ EXPECT_EQ(OK, node0.node().ClosePort(a));
+ EXPECT_EQ(OK, node0.node().ClosePort(b));
+
+ EXPECT_TRUE(node0.node().CanShutdownCleanly());
+ EXPECT_TRUE(node1.node().CanShutdownCleanly());
+}
+
+TEST_F(PortsTest, RemotePeerStatusAfterLocalPortMerge) {
+ TestNode node0(0);
+ AddNode(&node0);
+
+ TestNode node1(1);
+ AddNode(&node1);
+
+ // Set up a-b on node0 and c-d spanning node0-node1.
+ PortRef a, b, c, d;
+ node0.node().CreatePortPair(&a, &b);
+ CreatePortPair(&node0, &c, &node1, &d);
+
+ PortStatus status;
+ ASSERT_EQ(OK, node0.node().GetStatus(a, &status));
+ EXPECT_FALSE(status.peer_remote);
+ ASSERT_EQ(OK, node0.node().GetStatus(b, &status));
+ EXPECT_FALSE(status.peer_remote);
+ ASSERT_EQ(OK, node0.node().GetStatus(c, &status));
+ EXPECT_TRUE(status.peer_remote);
+ ASSERT_EQ(OK, node1.node().GetStatus(d, &status));
+ EXPECT_TRUE(status.peer_remote);
+
+ EXPECT_EQ(OK, node0.node().MergeLocalPorts(b, c));
+ WaitForIdle();
+
+ ASSERT_EQ(OK, node0.node().GetStatus(a, &status));
+ EXPECT_TRUE(status.peer_remote);
+ ASSERT_EQ(OK, node1.node().GetStatus(d, &status));
+ EXPECT_TRUE(status.peer_remote);
+
+ EXPECT_EQ(OK, node0.node().ClosePort(a));
+ EXPECT_EQ(OK, node1.node().ClosePort(d));
+ EXPECT_TRUE(node0.node().CanShutdownCleanly());
+ EXPECT_TRUE(node1.node().CanShutdownCleanly());
+}
+
+TEST_F(PortsTest, RemotePeerStatusAfterRemotePortMerge) {
+ TestNode node0(0);
+ AddNode(&node0);
+
+ TestNode node1(1);
+ AddNode(&node1);
+
+ // Set up a-b on node0 and c-d on node1.
+ PortRef a, b, c, d;
+ node0.node().CreatePortPair(&a, &b);
+ node1.node().CreatePortPair(&c, &d);
+
+ PortStatus status;
+ ASSERT_EQ(OK, node0.node().GetStatus(a, &status));
+ EXPECT_FALSE(status.peer_remote);
+ ASSERT_EQ(OK, node0.node().GetStatus(b, &status));
+ EXPECT_FALSE(status.peer_remote);
+ ASSERT_EQ(OK, node1.node().GetStatus(c, &status));
+ EXPECT_FALSE(status.peer_remote);
+ ASSERT_EQ(OK, node1.node().GetStatus(d, &status));
+ EXPECT_FALSE(status.peer_remote);
+
+ EXPECT_EQ(OK, node0.node().MergePorts(b, node1.name(), c.name()));
+ WaitForIdle();
+
+ ASSERT_EQ(OK, node0.node().GetStatus(a, &status));
+ EXPECT_TRUE(status.peer_remote);
+ ASSERT_EQ(OK, node1.node().GetStatus(d, &status));
+ EXPECT_TRUE(status.peer_remote);
+
+ EXPECT_EQ(OK, node0.node().ClosePort(a));
+ EXPECT_EQ(OK, node1.node().ClosePort(d));
+ EXPECT_TRUE(node0.node().CanShutdownCleanly());
+ EXPECT_TRUE(node1.node().CanShutdownCleanly());
+}
+
+TEST_F(PortsTest, RetransmitUserMessageEvents) {
+ // Ensures that user message events can be retransmitted properly.
+ TestNode node0(0);
+ AddNode(&node0);
+
+ PortRef a, b;
+ node0.node().CreatePortPair(&a, &b);
+
+ // Ping.
+ const char* kMessage = "hey";
+ ScopedMessage message;
+ EXPECT_EQ(OK, node0.SendStringMessage(a, kMessage));
+ ASSERT_TRUE(node0.ReadMessage(b, &message));
+ EXPECT_TRUE(MessageEquals(message, kMessage));
+
+ // Pong.
+ EXPECT_EQ(OK, node0.node().SendUserMessage(b, std::move(message)));
+ EXPECT_FALSE(message);
+ ASSERT_TRUE(node0.ReadMessage(a, &message));
+ EXPECT_TRUE(MessageEquals(message, kMessage));
+
+ // Ping again.
+ EXPECT_EQ(OK, node0.node().SendUserMessage(a, std::move(message)));
+ EXPECT_FALSE(message);
+ ASSERT_TRUE(node0.ReadMessage(b, &message));
+ EXPECT_TRUE(MessageEquals(message, kMessage));
+
+ // Pong again!
+ EXPECT_EQ(OK, node0.node().SendUserMessage(b, std::move(message)));
+ EXPECT_FALSE(message);
+ ASSERT_TRUE(node0.ReadMessage(a, &message));
+ EXPECT_TRUE(MessageEquals(message, kMessage));
+
+ EXPECT_EQ(OK, node0.node().ClosePort(a));
+ EXPECT_EQ(OK, node0.node().ClosePort(b));
+}
+
+} // namespace test
+} // namespace ports
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/ports/user_data.h b/mojo/core/ports/user_data.h
new file mode 100644
index 0000000000..e18d9b79d5
--- /dev/null
+++ b/mojo/core/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_CORE_PORTS_USER_DATA_H_
+#define MOJO_CORE_PORTS_USER_DATA_H_
+
+#include "base/memory/ref_counted.h"
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+class UserData : public base::RefCountedThreadSafe<UserData> {
+ protected:
+ friend class base::RefCountedThreadSafe<UserData>;
+
+ virtual ~UserData() {}
+};
+
+} // namespace ports
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_PORTS_USER_DATA_H_
diff --git a/mojo/core/ports/user_message.cc b/mojo/core/ports/user_message.cc
new file mode 100644
index 0000000000..aa16376c9c
--- /dev/null
+++ b/mojo/core/ports/user_message.cc
@@ -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.
+
+#include "mojo/core/ports/user_message.h"
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+UserMessage::UserMessage(const TypeInfo* type_info) : type_info_(type_info) {}
+
+UserMessage::~UserMessage() = default;
+
+bool UserMessage::WillBeRoutedExternally() {
+ return true;
+}
+
+size_t UserMessage::GetSizeIfSerialized() const {
+ return 0;
+}
+
+} // namespace ports
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/ports/user_message.h b/mojo/core/ports/user_message.h
new file mode 100644
index 0000000000..34d2688720
--- /dev/null
+++ b/mojo/core/ports/user_message.h
@@ -0,0 +1,56 @@
+// 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_CORE_PORTS_USER_MESSAGE_H_
+#define MOJO_CORE_PORTS_USER_MESSAGE_H_
+
+#include "base/component_export.h"
+#include "base/macros.h"
+
+namespace mojo {
+namespace core {
+namespace ports {
+
+// Base type to use for any embedder-defined user message implementation. This
+// class is intentionally empty.
+//
+// Provides a bit of type-safety help to subclasses since by design downcasting
+// from this type is a common operation in embedders.
+//
+// Each subclass should define a static const instance of TypeInfo named
+// |kUserMessageTypeInfo| and pass its address down to the UserMessage
+// constructor. The type of a UserMessage can then be dynamically inspected by
+// comparing |type_info()| to any subclass's |&kUserMessageTypeInfo|.
+class COMPONENT_EXPORT(MOJO_CORE_PORTS) UserMessage {
+ public:
+ struct TypeInfo {};
+
+ explicit UserMessage(const TypeInfo* type_info);
+ virtual ~UserMessage();
+
+ const TypeInfo* type_info() const { return type_info_; }
+
+ // Invoked immediately before the system asks the embedder to forward this
+ // message to an external node.
+ //
+ // Returns |true| if the message is OK to route externally, or |false|
+ // otherwise. Returning |false| implies an unrecoverable condition, and the
+ // message event will be destroyed without further routing.
+ virtual bool WillBeRoutedExternally();
+
+ // Returns the size in bytes of this message iff it's serialized. Zero
+ // otherwise.
+ virtual size_t GetSizeIfSerialized() const;
+
+ private:
+ const TypeInfo* const type_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserMessage);
+};
+
+} // namespace ports
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_PORTS_USER_MESSAGE_H_
diff --git a/mojo/core/quota_unittest.cc b/mojo/core/quota_unittest.cc
new file mode 100644
index 0000000000..bc26baf151
--- /dev/null
+++ b/mojo/core/quota_unittest.cc
@@ -0,0 +1,314 @@
+// 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.
+
+#include <string>
+
+#include "mojo/core/test/mojo_test_base.h"
+#include "mojo/public/c/system/quota.h"
+
+namespace mojo {
+namespace core {
+namespace {
+
+using QuotaTest = test::MojoTestBase;
+
+void QuotaExceededEventHandler(const MojoTrapEvent* event) {
+ // Always treat trigger context as the address of a bool to set to |true|.
+ if (event->result == MOJO_RESULT_OK)
+ *reinterpret_cast<bool*>(event->trigger_context) = true;
+}
+
+TEST_F(QuotaTest, InvalidArguments) {
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoSetQuota(MOJO_HANDLE_INVALID,
+ MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, 2, nullptr));
+
+ const MojoQuotaType kInvalidQuotaType = 0xfffffffful;
+ MojoHandle message_pipe0, message_pipe1;
+ CreateMessagePipe(&message_pipe0, &message_pipe1);
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoSetQuota(message_pipe0, kInvalidQuotaType, 0, nullptr));
+
+ const MojoSetQuotaOptions kInvalidSetQuotaOptions = {0};
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoSetQuota(message_pipe0, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, 0,
+ &kInvalidSetQuotaOptions));
+
+ uint64_t limit = 0;
+ uint64_t usage = 0;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoQueryQuota(message_pipe0, kInvalidQuotaType, nullptr, &limit,
+ &usage));
+
+ const MojoQueryQuotaOptions kInvalidQueryQuotaOptions = {0};
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoQueryQuota(message_pipe0, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH,
+ &kInvalidQueryQuotaOptions, &limit, &usage));
+
+ MojoClose(message_pipe0);
+ MojoClose(message_pipe1);
+
+ MojoHandle producer, consumer;
+ CreateDataPipe(&producer, &consumer, 1);
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ MojoSetQuota(producer, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, 0, nullptr));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoSetQuota(producer, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE, 0,
+ nullptr));
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ MojoSetQuota(consumer, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, 0, nullptr));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoSetQuota(consumer, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE, 0,
+ nullptr));
+ MojoClose(producer);
+ MojoClose(consumer);
+}
+
+TEST_F(QuotaTest, BasicReceiveQueueLength) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ uint64_t limit = 0;
+ uint64_t usage = 0;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoQueryQuota(a, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, nullptr,
+ &limit, &usage));
+ EXPECT_EQ(MOJO_QUOTA_LIMIT_NONE, limit);
+ EXPECT_EQ(0u, usage);
+
+ const uint64_t kTestLimit = 42;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoSetQuota(a, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, kTestLimit,
+ nullptr));
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoQueryQuota(a, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, nullptr,
+ &limit, &usage));
+ EXPECT_EQ(kTestLimit, limit);
+ EXPECT_EQ(0u, usage);
+
+ const std::string kTestMessage = "doot";
+ WriteMessage(b, kTestMessage);
+ WaitForSignals(a, MOJO_HANDLE_SIGNAL_READABLE);
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoQueryQuota(a, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, nullptr,
+ &limit, &usage));
+ EXPECT_EQ(kTestLimit, limit);
+ EXPECT_EQ(1u, usage);
+}
+
+TEST_F(QuotaTest, BasicReceiveQueueMemorySize) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ uint64_t limit = 0;
+ uint64_t usage = 0;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoQueryQuota(a, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE,
+ nullptr, &limit, &usage));
+ EXPECT_EQ(MOJO_QUOTA_LIMIT_NONE, limit);
+ EXPECT_EQ(0u, usage);
+
+ const uint64_t kTestLimit = 42;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoSetQuota(a, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE,
+ kTestLimit, nullptr));
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoQueryQuota(a, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE,
+ nullptr, &limit, &usage));
+ EXPECT_EQ(kTestLimit, limit);
+ EXPECT_EQ(0u, usage);
+
+ const std::string kTestMessage = "doot";
+ WriteMessage(b, kTestMessage);
+ WaitForSignals(a, MOJO_HANDLE_SIGNAL_READABLE);
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoQueryQuota(a, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE,
+ nullptr, &limit, &usage));
+ EXPECT_EQ(kTestLimit, limit);
+ EXPECT_EQ(usage, kTestMessage.size());
+
+ MojoClose(a);
+ MojoClose(b);
+}
+
+TEST_F(QuotaTest, ReceiveQueueLengthLimitExceeded) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ const uint64_t kMaxMessages = 1;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoSetQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, kMaxMessages,
+ nullptr));
+
+ MojoHandleSignalsState signals;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
+ EXPECT_FALSE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);
+
+ const std::string kTestMessage = "this message is lit, fam";
+ WriteMessage(a, kTestMessage);
+ WaitForSignals(b, MOJO_HANDLE_SIGNAL_READABLE);
+
+ uint64_t limit = 0;
+ uint64_t usage = 0;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoQueryQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, nullptr,
+ &limit, &usage));
+ EXPECT_EQ(kMaxMessages, limit);
+ EXPECT_EQ(1u, usage);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
+ EXPECT_FALSE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);
+
+ // Push the endpoint over quota and ensure that it signals accordingly.
+ WriteMessage(a, kTestMessage);
+ WaitForSignals(b, MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
+ EXPECT_TRUE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoQueryQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, nullptr,
+ &limit, &usage));
+ EXPECT_EQ(kMaxMessages, limit);
+ EXPECT_EQ(2u, usage);
+
+ // Read a message and wait for QUOTA_EXCEEDED to go back low.
+ EXPECT_EQ(kTestMessage, ReadMessage(b));
+ WaitForSignals(b, MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED,
+ MOJO_TRIGGER_CONDITION_SIGNALS_UNSATISFIED);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
+ EXPECT_FALSE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoQueryQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, nullptr,
+ &limit, &usage));
+ EXPECT_EQ(kMaxMessages, limit);
+ EXPECT_EQ(1u, usage);
+
+ MojoClose(a);
+ MojoClose(b);
+}
+
+TEST_F(QuotaTest, ReceiveQueueMemorySizeLimitExceeded) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ const uint64_t kMaxMessageBytes = 6;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoSetQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE,
+ kMaxMessageBytes, nullptr));
+
+ MojoHandleSignalsState signals;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
+ EXPECT_FALSE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);
+
+ const std::string kTestMessage = "four";
+ WriteMessage(a, kTestMessage);
+ WaitForSignals(b, MOJO_HANDLE_SIGNAL_READABLE);
+
+ uint64_t limit = 0;
+ uint64_t usage = 0;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoQueryQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE,
+ nullptr, &limit, &usage));
+ EXPECT_EQ(kMaxMessageBytes, limit);
+ EXPECT_EQ(kTestMessage.size(), usage);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
+ EXPECT_FALSE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);
+
+ // Push the endpoint over quota and ensure that it signals accordingly.
+ WriteMessage(a, kTestMessage);
+ WaitForSignals(b, MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
+ EXPECT_TRUE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoQueryQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE,
+ nullptr, &limit, &usage));
+ EXPECT_EQ(kMaxMessageBytes, limit);
+ EXPECT_EQ(kTestMessage.size() * 2, usage);
+
+ // Read a message and wait for QUOTA_EXCEEDED to go back low.
+ EXPECT_EQ(kTestMessage, ReadMessage(b));
+ WaitForSignals(b, MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED,
+ MOJO_TRIGGER_CONDITION_SIGNALS_UNSATISFIED);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
+ EXPECT_FALSE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoQueryQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE,
+ nullptr, &limit, &usage));
+ EXPECT_EQ(kMaxMessageBytes, limit);
+ EXPECT_EQ(kTestMessage.size(), usage);
+
+ MojoClose(a);
+ MojoClose(b);
+}
+
+TEST_F(QuotaTest, TrapQuotaExceeded) {
+ // Simple sanity check to verify that QUOTA_EXCEEDED signals can be trapped
+ // like any other signals.
+
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ const uint64_t kMaxMessages = 42;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoSetQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, kMaxMessages,
+ nullptr));
+
+ bool signal_event_fired = false;
+ MojoHandle quota_trap;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoCreateTrap(&QuotaExceededEventHandler, nullptr, &quota_trap));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(quota_trap, b, MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ reinterpret_cast<uintptr_t>(&signal_event_fired),
+ nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(quota_trap, nullptr, nullptr, nullptr));
+
+ const std::string kTestMessage("sup");
+ for (uint64_t i = 0; i < kMaxMessages; ++i)
+ WriteMessage(a, kTestMessage);
+
+ // We're at quota but not yet over.
+ MojoHandleSignalsState signals;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
+ EXPECT_FALSE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);
+ EXPECT_FALSE(signal_event_fired);
+
+ // Push over quota. The event handler should be invoked before this returns.
+ WriteMessage(a, kTestMessage);
+ EXPECT_TRUE(signal_event_fired);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &signals));
+ EXPECT_TRUE(signals.satisfied_signals & MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);
+
+ uint64_t limit = 0;
+ uint64_t usage = 0;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoQueryQuota(b, MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH, nullptr,
+ &limit, &usage));
+ EXPECT_EQ(kMaxMessages, limit);
+ EXPECT_EQ(kMaxMessages + 1, usage);
+
+ MojoClose(quota_trap);
+ MojoClose(a);
+ MojoClose(b);
+}
+
+} // namespace
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/request_context.cc b/mojo/core/request_context.cc
new file mode 100644
index 0000000000..0a481af729
--- /dev/null
+++ b/mojo/core/request_context.cc
@@ -0,0 +1,116 @@
+// 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/core/request_context.h"
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/threading/thread_local.h"
+
+namespace mojo {
+namespace core {
+
+namespace {
+
+base::LazyInstance<base::ThreadLocalPointer<RequestContext>>::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);
+
+ MojoTrapEventFlags flags = MOJO_TRAP_EVENT_FLAG_NONE;
+ if (source_ == Source::LOCAL_API_CALL)
+ flags |= MOJO_TRAP_EVENT_FLAG_WITHIN_API_CALL;
+
+ // 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 :
+ 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. Note that while it would be safe
+ // to inherit |source_| from the perspective of Mojo core re-entrancy,
+ // upper application layers may use the flag as a signal to allow
+ // synchronous event dispatch and in turn shoot themselves in the foot
+ // with e.g. mutually recursive event handlers. We avoid that by
+ // treating all nested trap events as if they originated from a local API
+ // call even if this is a system RequestContext.
+ RequestContext inner_context(Source::LOCAL_API_CALL);
+ 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> 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> 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> 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 core
+} // namespace mojo
diff --git a/mojo/core/request_context.h b/mojo/core/request_context.h
new file mode 100644
index 0000000000..89988f27d3
--- /dev/null
+++ b/mojo/core/request_context.h
@@ -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.
+
+#ifndef MOJO_CORE_REQUEST_CONTEXT_H_
+#define MOJO_CORE_REQUEST_CONTEXT_H_
+
+#include "base/containers/stack_container.h"
+#include "base/macros.h"
+#include "mojo/core/handle_signals_state.h"
+#include "mojo/core/system_impl_export.h"
+#include "mojo/core/watch.h"
+
+namespace base {
+template <typename T>
+class ThreadLocalPointer;
+}
+
+namespace mojo {
+namespace core {
+
+// 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> 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> watch);
+
+ private:
+ // Is this request context the current one?
+ bool IsCurrent() const;
+
+ struct WatchNotifyFinalizer {
+ WatchNotifyFinalizer(scoped_refptr<Watch> watch,
+ MojoResult result,
+ const HandleSignalsState& state);
+ WatchNotifyFinalizer(const WatchNotifyFinalizer& other);
+ ~WatchNotifyFinalizer();
+
+ scoped_refptr<Watch> 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<WatchNotifyFinalizer, kStaticWatchFinalizersCapacity>;
+ using WatchCancelFinalizerList =
+ base::StackVector<scoped_refptr<Watch>, 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<RequestContext>* tls_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(RequestContext);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_REQUEST_CONTEXT_H_
diff --git a/mojo/core/run_all_core_unittests.cc b/mojo/core/run_all_core_unittests.cc
new file mode 100644
index 0000000000..92dcafc961
--- /dev/null
+++ b/mojo/core/run_all_core_unittests.cc
@@ -0,0 +1,18 @@
+// 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.
+
+#include "base/bind.h"
+#include "base/test/launcher/unit_test_launcher.h"
+#include "base/test/test_suite.h"
+#include "build/build_config.h"
+#include "mojo/public/c/system/core.h"
+
+int main(int argc, char** argv) {
+ base::TestSuite test_suite(argc, argv);
+
+ CHECK_EQ(MOJO_RESULT_OK, MojoInitialize(nullptr));
+ return base::LaunchUnitTests(
+ argc, argv,
+ base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite)));
+}
diff --git a/mojo/core/scoped_process_handle.cc b/mojo/core/scoped_process_handle.cc
new file mode 100644
index 0000000000..65dfcf78aa
--- /dev/null
+++ b/mojo/core/scoped_process_handle.cc
@@ -0,0 +1,90 @@
+// 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.
+
+#include "mojo/core/scoped_process_handle.h"
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace mojo {
+namespace core {
+
+namespace {
+
+base::ProcessHandle GetCurrentProcessHandle() {
+#if defined(OS_NACL_NONSFI)
+ // Doesn't really matter, it's not going to be used for anything interesting
+ // under NaCl.
+ return 1;
+#else
+ return base::GetCurrentProcessHandle();
+#endif
+}
+
+} // namespace
+
+ScopedProcessHandle::ScopedProcessHandle() = default;
+
+ScopedProcessHandle::ScopedProcessHandle(base::ProcessHandle handle)
+ : handle_(handle) {
+ DCHECK_NE(handle, GetCurrentProcessHandle());
+}
+
+ScopedProcessHandle::ScopedProcessHandle(ScopedProcessHandle&&) = default;
+
+ScopedProcessHandle::~ScopedProcessHandle() = default;
+
+// static
+ScopedProcessHandle ScopedProcessHandle::CloneFrom(base::ProcessHandle handle) {
+ DCHECK_NE(handle, GetCurrentProcessHandle());
+ if (handle == base::kNullProcessHandle)
+ return ScopedProcessHandle();
+
+#if defined(OS_WIN)
+ BOOL ok = ::DuplicateHandle(GetCurrentProcessHandle(), handle,
+ GetCurrentProcessHandle(), &handle, 0, FALSE,
+ DUPLICATE_SAME_ACCESS);
+ DCHECK(ok);
+#endif
+ return ScopedProcessHandle(handle);
+}
+
+ScopedProcessHandle& ScopedProcessHandle::operator=(ScopedProcessHandle&&) =
+ default;
+
+bool ScopedProcessHandle::is_valid() const {
+#if defined(OS_WIN)
+ return handle_.IsValid();
+#else
+ return handle_ != base::kNullProcessHandle;
+#endif
+}
+
+base::ProcessHandle ScopedProcessHandle::get() const {
+#if defined(OS_WIN)
+ return handle_.Get();
+#else
+ return handle_;
+#endif
+}
+
+base::ProcessHandle ScopedProcessHandle::release() {
+#if defined(OS_WIN)
+ return handle_.Take();
+#else
+ return handle_;
+#endif
+}
+
+ScopedProcessHandle ScopedProcessHandle::Clone() const {
+ if (is_valid())
+ return CloneFrom(get());
+ return ScopedProcessHandle();
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/scoped_process_handle.h b/mojo/core/scoped_process_handle.h
new file mode 100644
index 0000000000..4677145d9f
--- /dev/null
+++ b/mojo/core/scoped_process_handle.h
@@ -0,0 +1,65 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_CORE_SCOPED_PROCESS_HANDLE_H_
+#define MOJO_CORE_SCOPED_PROCESS_HANDLE_H_
+
+#include "base/macros.h"
+#include "base/process/process_handle.h"
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include "base/win/scoped_handle.h"
+#endif
+
+namespace mojo {
+namespace core {
+
+// Wraps a |base::ProcessHandle| with additional scoped lifetime semantics on
+// applicable platforms. For platforms where process handles aren't ownable
+// references, this is just a wrapper around |base::ProcessHandle|.
+//
+// This essentially exists to support passing around process handles internally
+// in a generic way while also supporting Windows process handle ownership
+// semantics.
+//
+// A ScopedProcessHandle will never refer to the current process, and
+// constructing a ScopedProcessHandle over the current process's handle is
+// considered an error.
+class ScopedProcessHandle {
+ public:
+ ScopedProcessHandle();
+
+ // Assumes ownership of |handle|.
+ explicit ScopedProcessHandle(base::ProcessHandle handle);
+
+ ScopedProcessHandle(ScopedProcessHandle&&);
+
+ ~ScopedProcessHandle();
+
+ // Creates a new ScopedProcessHandle from a clone of |handle|.
+ static ScopedProcessHandle CloneFrom(base::ProcessHandle handle);
+
+ ScopedProcessHandle& operator=(ScopedProcessHandle&&);
+
+ bool is_valid() const;
+ base::ProcessHandle get() const;
+ base::ProcessHandle release();
+
+ ScopedProcessHandle Clone() const;
+
+ private:
+#if defined(OS_WIN)
+ base::win::ScopedHandle handle_;
+#else
+ base::ProcessHandle handle_ = base::kNullProcessHandle;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedProcessHandle);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_SCOPED_PROCESS_HANDLE_H_
diff --git a/mojo/core/shared_buffer_dispatcher.cc b/mojo/core/shared_buffer_dispatcher.cc
new file mode 100644
index 0000000000..8a0026ab0e
--- /dev/null
+++ b/mojo/core/shared_buffer_dispatcher.cc
@@ -0,0 +1,445 @@
+// 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/core/shared_buffer_dispatcher.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+#include <memory>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "build/build_config.h"
+#include "mojo/core/configuration.h"
+#include "mojo/core/node_controller.h"
+#include "mojo/core/options_validation.h"
+#include "mojo/core/platform_handle_utils.h"
+#include "mojo/core/platform_shared_memory_mapping.h"
+#include "mojo/public/c/system/platform_handle.h"
+
+namespace mojo {
+namespace core {
+
+namespace {
+
+#pragma pack(push, 1)
+
+struct SerializedState {
+ uint64_t num_bytes;
+ uint32_t access_mode;
+ uint64_t guid_high;
+ uint64_t guid_low;
+ uint32_t padding;
+};
+
+#pragma pack(pop)
+
+static_assert(sizeof(SerializedState) % 8 == 0,
+ "Invalid SerializedState size.");
+
+} // namespace
+
+// static
+const MojoCreateSharedBufferOptions
+ SharedBufferDispatcher::kDefaultCreateOptions = {
+ static_cast<uint32_t>(sizeof(MojoCreateSharedBufferOptions)),
+ MOJO_CREATE_SHARED_BUFFER_FLAG_NONE};
+
+// static
+MojoResult SharedBufferDispatcher::ValidateCreateOptions(
+ const MojoCreateSharedBufferOptions* in_options,
+ MojoCreateSharedBufferOptions* out_options) {
+ const MojoCreateSharedBufferFlags kKnownFlags =
+ MOJO_CREATE_SHARED_BUFFER_FLAG_NONE;
+
+ *out_options = kDefaultCreateOptions;
+ if (!in_options)
+ return MOJO_RESULT_OK;
+
+ UserOptionsReader<MojoCreateSharedBufferOptions> 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<SharedBufferDispatcher>* result) {
+ if (!num_bytes)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (num_bytes > GetConfiguration().max_shared_memory_num_bytes)
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ base::WritableSharedMemoryRegion writable_region;
+ if (node_controller) {
+ writable_region =
+ node_controller->CreateSharedBuffer(static_cast<size_t>(num_bytes));
+ } else {
+ writable_region = base::WritableSharedMemoryRegion::Create(
+ static_cast<size_t>(num_bytes));
+ }
+ if (!writable_region.IsValid())
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+
+ *result = CreateInternal(
+ base::WritableSharedMemoryRegion::TakeHandleForSerialization(
+ std::move(writable_region)));
+ return MOJO_RESULT_OK;
+}
+
+// static
+MojoResult SharedBufferDispatcher::CreateFromPlatformSharedMemoryRegion(
+ base::subtle::PlatformSharedMemoryRegion region,
+ scoped_refptr<SharedBufferDispatcher>* result) {
+ if (!region.IsValid())
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ *result = CreateInternal(std::move(region));
+ return MOJO_RESULT_OK;
+}
+
+// static
+scoped_refptr<SharedBufferDispatcher> 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* serialized_state =
+ static_cast<const SerializedState*>(bytes);
+ if (!serialized_state->num_bytes) {
+ LOG(ERROR)
+ << "Invalid serialized shared buffer dispatcher (invalid num_bytes)";
+ return nullptr;
+ }
+
+ if (num_ports)
+ return nullptr;
+
+ PlatformHandle handles[2];
+#if defined(OS_POSIX) && !defined(OS_ANDROID) && \
+ (!defined(OS_MACOSX) || defined(OS_IOS))
+ if (serialized_state->access_mode ==
+ MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_WRITABLE) {
+ if (num_platform_handles != 2)
+ return nullptr;
+ handles[1] = std::move(platform_handles[1]);
+ } else {
+ if (num_platform_handles != 1)
+ return nullptr;
+ }
+#else
+ if (num_platform_handles != 1)
+ return nullptr;
+#endif
+ handles[0] = std::move(platform_handles[0]);
+
+ base::UnguessableToken guid = base::UnguessableToken::Deserialize(
+ serialized_state->guid_high, serialized_state->guid_low);
+
+ base::subtle::PlatformSharedMemoryRegion::Mode mode;
+ switch (serialized_state->access_mode) {
+ case MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_READ_ONLY:
+ mode = base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly;
+ break;
+ case MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_WRITABLE:
+ mode = base::subtle::PlatformSharedMemoryRegion::Mode::kWritable;
+ break;
+ case MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_UNSAFE:
+ mode = base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe;
+ break;
+ default:
+ LOG(ERROR) << "Invalid serialized shared buffer access mode.";
+ return nullptr;
+ }
+
+ auto region = base::subtle::PlatformSharedMemoryRegion::Take(
+ CreateSharedMemoryRegionHandleFromPlatformHandles(std::move(handles[0]),
+ std::move(handles[1])),
+ mode, static_cast<size_t>(serialized_state->num_bytes), guid);
+ if (!region.IsValid()) {
+ LOG(ERROR)
+ << "Invalid serialized shared buffer dispatcher (invalid num_bytes?)";
+ return nullptr;
+ }
+
+ return CreateInternal(std::move(region));
+}
+
+base::subtle::PlatformSharedMemoryRegion
+SharedBufferDispatcher::PassPlatformSharedMemoryRegion() {
+ base::AutoLock lock(lock_);
+ if (!region_.IsValid() || in_transit_)
+ return base::subtle::PlatformSharedMemoryRegion();
+
+ return std::move(region_);
+}
+
+Dispatcher::Type SharedBufferDispatcher::GetType() const {
+ return Type::SHARED_BUFFER;
+}
+
+MojoResult SharedBufferDispatcher::Close() {
+ base::AutoLock lock(lock_);
+ if (in_transit_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ region_ = base::subtle::PlatformSharedMemoryRegion();
+ return MOJO_RESULT_OK;
+}
+
+MojoResult SharedBufferDispatcher::DuplicateBufferHandle(
+ const MojoDuplicateBufferHandleOptions* options,
+ scoped_refptr<Dispatcher>* new_dispatcher) {
+ MojoDuplicateBufferHandleOptions validated_options;
+ MojoResult result = ValidateDuplicateOptions(options, &validated_options);
+ if (result != MOJO_RESULT_OK)
+ return result;
+
+ base::AutoLock lock(lock_);
+ if (in_transit_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if ((validated_options.flags & MOJO_DUPLICATE_BUFFER_HANDLE_FLAG_READ_ONLY)) {
+ // If a read-only duplicate is requested and this handle is not already
+ // read-only, we need to make it read-only before duplicating. If it's
+ // unsafe it can't be made read-only, and we must fail instead.
+ if (region_.GetMode() ==
+ base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe) {
+ return MOJO_RESULT_FAILED_PRECONDITION;
+ } else if (region_.GetMode() ==
+ base::subtle::PlatformSharedMemoryRegion::Mode::kWritable) {
+ region_ = base::ReadOnlySharedMemoryRegion::TakeHandleForSerialization(
+ base::WritableSharedMemoryRegion::ConvertToReadOnly(
+ base::WritableSharedMemoryRegion::Deserialize(
+ std::move(region_))));
+ }
+
+ DCHECK_EQ(region_.GetMode(),
+ base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly);
+ } else {
+ // A writable duplicate was requested. If this is already a read-only handle
+ // we have to reject. Otherwise we have to convert to unsafe to ensure that
+ // no future read-only duplication requests can succeed.
+ if (region_.GetMode() ==
+ base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly) {
+ return MOJO_RESULT_FAILED_PRECONDITION;
+ } else if (region_.GetMode() ==
+ base::subtle::PlatformSharedMemoryRegion::Mode::kWritable) {
+ auto handle = region_.PassPlatformHandle();
+#if defined(OS_POSIX) && !defined(OS_ANDROID) && \
+ (!defined(OS_MACOSX) || defined(OS_IOS))
+ // On POSIX systems excluding Android, Fuchsia, and OSX, we explicitly
+ // wipe out the secondary (read-only) FD from the platform handle to
+ // repurpose it for exclusive unsafe usage.
+ handle.readonly_fd.reset();
+#endif
+ region_ = base::subtle::PlatformSharedMemoryRegion::Take(
+ std::move(handle),
+ base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
+ region_.GetSize(), region_.GetGUID());
+ }
+ }
+
+ *new_dispatcher = CreateInternal(region_.Duplicate());
+ return MOJO_RESULT_OK;
+}
+
+MojoResult SharedBufferDispatcher::MapBuffer(
+ uint64_t offset,
+ uint64_t num_bytes,
+ std::unique_ptr<PlatformSharedMemoryMapping>* mapping) {
+ if (offset > static_cast<uint64_t>(std::numeric_limits<size_t>::max()))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (num_bytes > static_cast<uint64_t>(std::numeric_limits<size_t>::max()))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ base::AutoLock lock(lock_);
+ DCHECK(region_.IsValid());
+ if (in_transit_ || num_bytes == 0 ||
+ static_cast<size_t>(offset + num_bytes) > region_.GetSize()) {
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ DCHECK(mapping);
+ *mapping = std::make_unique<PlatformSharedMemoryMapping>(
+ &region_, static_cast<size_t>(offset), static_cast<size_t>(num_bytes));
+ if (!(*mapping)->IsValid()) {
+ LOG(ERROR) << "Failed to map shared memory region.";
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ }
+
+ return MOJO_RESULT_OK;
+}
+
+MojoResult SharedBufferDispatcher::GetBufferInfo(MojoSharedBufferInfo* info) {
+ if (!info)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ base::AutoLock lock(lock_);
+ info->struct_size = sizeof(*info);
+ info->size = region_.GetSize();
+ 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;
+#if defined(OS_POSIX) && !defined(OS_ANDROID) && \
+ (!defined(OS_MACOSX) || defined(OS_IOS))
+ if (region_.GetMode() ==
+ base::subtle::PlatformSharedMemoryRegion::Mode::kWritable) {
+ *num_platform_handles = 2;
+ }
+#endif
+}
+
+bool SharedBufferDispatcher::EndSerialize(void* destination,
+ ports::PortName* ports,
+ PlatformHandle* handles) {
+ SerializedState* serialized_state =
+ static_cast<SerializedState*>(destination);
+ base::AutoLock lock(lock_);
+ serialized_state->num_bytes = region_.GetSize();
+ switch (region_.GetMode()) {
+ case base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly:
+ serialized_state->access_mode =
+ MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_READ_ONLY;
+ break;
+ case base::subtle::PlatformSharedMemoryRegion::Mode::kWritable:
+ serialized_state->access_mode =
+ MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_WRITABLE;
+ break;
+ case base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe:
+ serialized_state->access_mode =
+ MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_UNSAFE;
+ break;
+ default:
+ NOTREACHED();
+ return false;
+ }
+
+ const base::UnguessableToken& guid = region_.GetGUID();
+ serialized_state->guid_high = guid.GetHighForSerialization();
+ serialized_state->guid_low = guid.GetLowForSerialization();
+ serialized_state->padding = 0;
+
+ auto region = std::move(region_);
+#if defined(OS_POSIX) && !defined(OS_ANDROID) && \
+ (!defined(OS_MACOSX) || defined(OS_IOS))
+ if (region.GetMode() ==
+ base::subtle::PlatformSharedMemoryRegion::Mode::kWritable) {
+ PlatformHandle platform_handles[2];
+ ExtractPlatformHandlesFromSharedMemoryRegionHandle(
+ region.PassPlatformHandle(), &platform_handles[0],
+ &platform_handles[1]);
+ handles[0] = std::move(platform_handles[0]);
+ handles[1] = std::move(platform_handles[1]);
+ return true;
+ }
+#endif
+
+ PlatformHandle platform_handle;
+ PlatformHandle ignored_handle;
+ ExtractPlatformHandlesFromSharedMemoryRegionHandle(
+ region.PassPlatformHandle(), &platform_handle, &ignored_handle);
+ handles[0] = std::move(platform_handle);
+ return true;
+}
+
+bool SharedBufferDispatcher::BeginTransit() {
+ base::AutoLock lock(lock_);
+ if (in_transit_)
+ return false;
+ in_transit_ = region_.IsValid();
+ return in_transit_;
+}
+
+void SharedBufferDispatcher::CompleteTransitAndClose() {
+ base::AutoLock lock(lock_);
+ in_transit_ = false;
+ region_ = base::subtle::PlatformSharedMemoryRegion();
+}
+
+void SharedBufferDispatcher::CancelTransit() {
+ base::AutoLock lock(lock_);
+ in_transit_ = false;
+}
+
+SharedBufferDispatcher::SharedBufferDispatcher(
+ base::subtle::PlatformSharedMemoryRegion region)
+ : region_(std::move(region)) {
+ DCHECK(region_.IsValid());
+}
+
+SharedBufferDispatcher::~SharedBufferDispatcher() {
+ DCHECK(!region_.IsValid() && !in_transit_);
+}
+
+// static
+scoped_refptr<SharedBufferDispatcher> SharedBufferDispatcher::CreateInternal(
+ base::subtle::PlatformSharedMemoryRegion region) {
+ return base::WrapRefCounted(new SharedBufferDispatcher(std::move(region)));
+}
+
+// static
+MojoResult SharedBufferDispatcher::ValidateDuplicateOptions(
+ const MojoDuplicateBufferHandleOptions* in_options,
+ MojoDuplicateBufferHandleOptions* out_options) {
+ const MojoDuplicateBufferHandleFlags kKnownFlags =
+ MOJO_DUPLICATE_BUFFER_HANDLE_FLAG_READ_ONLY;
+ static const MojoDuplicateBufferHandleOptions kDefaultOptions = {
+ static_cast<uint32_t>(sizeof(MojoDuplicateBufferHandleOptions)),
+ MOJO_DUPLICATE_BUFFER_HANDLE_FLAG_NONE};
+
+ *out_options = kDefaultOptions;
+ if (!in_options)
+ return MOJO_RESULT_OK;
+
+ UserOptionsReader<MojoDuplicateBufferHandleOptions> 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 core
+} // namespace mojo
diff --git a/mojo/core/shared_buffer_dispatcher.h b/mojo/core/shared_buffer_dispatcher.h
new file mode 100644
index 0000000000..cbdb3533f9
--- /dev/null
+++ b/mojo/core/shared_buffer_dispatcher.h
@@ -0,0 +1,124 @@
+// 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_CORE_SHARED_BUFFER_DISPATCHER_H_
+#define MOJO_CORE_SHARED_BUFFER_DISPATCHER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/macros.h"
+#include "base/memory/platform_shared_memory_region.h"
+#include "mojo/core/dispatcher.h"
+#include "mojo/core/system_impl_export.h"
+
+namespace mojo {
+
+namespace core {
+
+class NodeController;
+class PlatformSharedMemoryMapping;
+
+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<SharedBufferDispatcher>* result);
+
+ // Create a |SharedBufferDispatcher| from |shared_buffer|.
+ static MojoResult CreateFromPlatformSharedMemoryRegion(
+ base::subtle::PlatformSharedMemoryRegion region,
+ scoped_refptr<SharedBufferDispatcher>* result);
+
+ // The "opposite" of SerializeAndClose(). Called by Dispatcher::Deserialize().
+ 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_handles);
+
+ // Passes the underlying PlatformSharedMemoryRegion. This dispatcher must be
+ // closed after calling this function.
+ base::subtle::PlatformSharedMemoryRegion PassPlatformSharedMemoryRegion();
+
+ // NOTE: This is not thread-safe. Definitely never use it outside of tests.
+ base::subtle::PlatformSharedMemoryRegion& GetRegionForTesting() {
+ return region_;
+ }
+
+ // Dispatcher:
+ Type GetType() const override;
+ MojoResult Close() override;
+ MojoResult DuplicateBufferHandle(
+ const MojoDuplicateBufferHandleOptions* options,
+ scoped_refptr<Dispatcher>* new_dispatcher) override;
+ MojoResult MapBuffer(
+ uint64_t offset,
+ uint64_t num_bytes,
+ std::unique_ptr<PlatformSharedMemoryMapping>* mapping) override;
+ MojoResult GetBufferInfo(MojoSharedBufferInfo* info) 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:
+ explicit SharedBufferDispatcher(
+ base::subtle::PlatformSharedMemoryRegion region);
+ ~SharedBufferDispatcher() override;
+
+ static scoped_refptr<SharedBufferDispatcher> CreateInternal(
+ base::subtle::PlatformSharedMemoryRegion region);
+
+ // 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 the fields below.
+ base::Lock lock_;
+
+ bool in_transit_ = false;
+ base::subtle::PlatformSharedMemoryRegion region_;
+
+ DISALLOW_COPY_AND_ASSIGN(SharedBufferDispatcher);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_SHARED_BUFFER_DISPATCHER_H_
diff --git a/mojo/core/shared_buffer_dispatcher_unittest.cc b/mojo/core/shared_buffer_dispatcher_unittest.cc
new file mode 100644
index 0000000000..22cbb44a4a
--- /dev/null
+++ b/mojo/core/shared_buffer_dispatcher_unittest.cc
@@ -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.
+
+#include "mojo/core/shared_buffer_dispatcher.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <limits>
+
+#include "base/macros.h"
+#include "base/memory/platform_shared_memory_region.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/writable_shared_memory_region.h"
+#include "mojo/core/dispatcher.h"
+#include "mojo/core/platform_shared_memory_mapping.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace core {
+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.
+ MojoCreateSharedBufferFlags flags_values[] = {
+ MOJO_CREATE_SHARED_BUFFER_FLAG_NONE};
+ for (size_t i = 0; i < arraysize(flags_values); i++) {
+ const MojoCreateSharedBufferFlags 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_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<SharedBufferDispatcher> 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<PlatformSharedMemoryMapping> mapping1;
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher->MapBuffer(0, 100, &mapping1));
+ ASSERT_TRUE(mapping1);
+ ASSERT_TRUE(mapping1->GetBase());
+ EXPECT_EQ(100u, mapping1->GetLength());
+ // Write something.
+ static_cast<char*>(mapping1->GetBase())[50] = 'x';
+
+ std::unique_ptr<PlatformSharedMemoryMapping> mapping2;
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher->MapBuffer(50, 50, &mapping2));
+ ASSERT_TRUE(mapping2);
+ ASSERT_TRUE(mapping2->GetBase());
+ EXPECT_EQ(50u, mapping2->GetLength());
+ EXPECT_EQ('x', static_cast<char*>(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<char*>(mapping2->GetBase())[1] = 'y';
+ EXPECT_EQ('y', static_cast<char*>(mapping1->GetBase())[51]);
+}
+
+TEST_F(SharedBufferDispatcherTest, CreateAndMapBufferFromPlatformBuffer) {
+ base::WritableSharedMemoryRegion region =
+ base::WritableSharedMemoryRegion::Create(100);
+ ASSERT_TRUE(region.IsValid());
+ scoped_refptr<SharedBufferDispatcher> dispatcher;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ SharedBufferDispatcher::CreateFromPlatformSharedMemoryRegion(
+ base::WritableSharedMemoryRegion::TakeHandleForSerialization(
+ std::move(region)),
+ &dispatcher));
+ ASSERT_TRUE(dispatcher);
+ EXPECT_EQ(Dispatcher::Type::SHARED_BUFFER, dispatcher->GetType());
+
+ // Make a couple of mappings.
+ std::unique_ptr<PlatformSharedMemoryMapping> mapping1;
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher->MapBuffer(0, 100, &mapping1));
+ ASSERT_TRUE(mapping1);
+ ASSERT_TRUE(mapping1->GetBase());
+ EXPECT_EQ(100u, mapping1->GetLength());
+ // Write something.
+ static_cast<char*>(mapping1->GetBase())[50] = 'x';
+
+ std::unique_ptr<PlatformSharedMemoryMapping> mapping2;
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher->MapBuffer(50, 50, &mapping2));
+ ASSERT_TRUE(mapping2);
+ ASSERT_TRUE(mapping2->GetBase());
+ EXPECT_EQ(50u, mapping2->GetLength());
+ EXPECT_EQ('x', static_cast<char*>(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<char*>(mapping2->GetBase())[1] = 'y';
+ EXPECT_EQ('y', static_cast<char*>(mapping1->GetBase())[51]);
+}
+
+TEST_F(SharedBufferDispatcherTest, DuplicateBufferHandle) {
+ scoped_refptr<SharedBufferDispatcher> dispatcher1;
+ EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create(
+ SharedBufferDispatcher::kDefaultCreateOptions,
+ nullptr, 100, &dispatcher1));
+
+ // Map and write something.
+ std::unique_ptr<PlatformSharedMemoryMapping> mapping;
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->MapBuffer(0, 100, &mapping));
+ static_cast<char*>(mapping->GetBase())[0] = 'x';
+ mapping.reset();
+
+ // Duplicate |dispatcher1| and then close it.
+ scoped_refptr<Dispatcher> 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, &mapping));
+ EXPECT_EQ('x', static_cast<char*>(mapping->GetBase())[0]);
+
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher2->Close());
+}
+
+TEST_F(SharedBufferDispatcherTest, DuplicateBufferHandleOptionsValid) {
+ scoped_refptr<SharedBufferDispatcher> dispatcher1;
+ EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create(
+ SharedBufferDispatcher::kDefaultCreateOptions,
+ nullptr, 100, &dispatcher1));
+
+ scoped_refptr<SharedBufferDispatcher> dispatcher2;
+ EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create(
+ SharedBufferDispatcher::kDefaultCreateOptions,
+ nullptr, 100, &dispatcher2));
+
+ MojoDuplicateBufferHandleOptions kReadOnlyOptions = {
+ sizeof(MojoCreateSharedBufferOptions),
+ MOJO_DUPLICATE_BUFFER_HANDLE_FLAG_READ_ONLY};
+
+ // NOTE: We forbid handles from being duplicated read-only after they've been
+ // duplicated non-read-only; conversely we also forbid handles from being
+ // duplicated non-read-only after they've been duplicated read-only.
+ scoped_refptr<Dispatcher> writable_duped_dispatcher1;
+ scoped_refptr<Dispatcher> read_only_duped_dispatcher1;
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->DuplicateBufferHandle(
+ nullptr, &writable_duped_dispatcher1));
+ EXPECT_TRUE(writable_duped_dispatcher1);
+ EXPECT_EQ(Dispatcher::Type::SHARED_BUFFER,
+ writable_duped_dispatcher1->GetType());
+ {
+ std::unique_ptr<PlatformSharedMemoryMapping> mapping;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ writable_duped_dispatcher1->MapBuffer(0, 100, &mapping));
+ }
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ dispatcher1->DuplicateBufferHandle(&kReadOnlyOptions,
+ &read_only_duped_dispatcher1));
+ EXPECT_FALSE(read_only_duped_dispatcher1);
+
+ scoped_refptr<Dispatcher> read_only_duped_dispatcher2;
+ scoped_refptr<Dispatcher> writable_duped_dispatcher2;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ dispatcher2->DuplicateBufferHandle(&kReadOnlyOptions,
+ &read_only_duped_dispatcher2));
+ EXPECT_TRUE(read_only_duped_dispatcher2);
+ EXPECT_EQ(Dispatcher::Type::SHARED_BUFFER,
+ read_only_duped_dispatcher2->GetType());
+ {
+ std::unique_ptr<PlatformSharedMemoryMapping> mapping;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ read_only_duped_dispatcher2->MapBuffer(0, 100, &mapping));
+ }
+ EXPECT_EQ(
+ MOJO_RESULT_FAILED_PRECONDITION,
+ dispatcher2->DuplicateBufferHandle(nullptr, &writable_duped_dispatcher2));
+ EXPECT_FALSE(writable_duped_dispatcher2);
+
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->Close());
+ EXPECT_EQ(MOJO_RESULT_OK, writable_duped_dispatcher1->Close());
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher2->Close());
+ EXPECT_EQ(MOJO_RESULT_OK, read_only_duped_dispatcher2->Close());
+}
+
+TEST_F(SharedBufferDispatcherTest, DuplicateBufferHandleOptionsInvalid) {
+ scoped_refptr<SharedBufferDispatcher> dispatcher1;
+ EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create(
+ SharedBufferDispatcher::kDefaultCreateOptions,
+ nullptr, 100, &dispatcher1));
+
+ // Invalid |struct_size|.
+ {
+ MojoDuplicateBufferHandleOptions options = {
+ 1u, MOJO_DUPLICATE_BUFFER_HANDLE_FLAG_NONE};
+ scoped_refptr<Dispatcher> dispatcher2;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher1->DuplicateBufferHandle(&options, &dispatcher2));
+ EXPECT_FALSE(dispatcher2);
+ }
+
+ // Unknown |flags|.
+ {
+ MojoDuplicateBufferHandleOptions options = {
+ sizeof(MojoDuplicateBufferHandleOptions), ~0u};
+ scoped_refptr<Dispatcher> 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<SharedBufferDispatcher> dispatcher;
+ EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED,
+ SharedBufferDispatcher::Create(
+ SharedBufferDispatcher::kDefaultCreateOptions, nullptr,
+ std::numeric_limits<uint64_t>::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<SharedBufferDispatcher> dispatcher;
+ EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create(
+ SharedBufferDispatcher::kDefaultCreateOptions,
+ nullptr, 100, &dispatcher));
+
+ MojoSharedBufferInfo info = {sizeof(info), 0u};
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher->GetBufferInfo(&info));
+
+ std::unique_ptr<PlatformSharedMemoryMapping> mapping;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher->MapBuffer(0, info.size + 1, &mapping));
+ EXPECT_FALSE(mapping);
+
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher->MapBuffer(1, info.size, &mapping));
+ EXPECT_FALSE(mapping);
+
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ dispatcher->MapBuffer(0, 0, &mapping));
+ EXPECT_FALSE(mapping);
+
+ EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close());
+}
+
+} // namespace
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/shared_buffer_unittest.cc b/mojo/core/shared_buffer_unittest.cc
new file mode 100644
index 0000000000..58a0b88376
--- /dev/null
+++ b/mojo/core/shared_buffer_unittest.cc
@@ -0,0 +1,323 @@
+// 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 <string.h>
+
+#include <string>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/memory/shared_memory.h"
+#include "base/strings/string_piece.h"
+#include "build/build_config.h"
+#include "mojo/core/core.h"
+#include "mojo/core/shared_buffer_dispatcher.h"
+#include "mojo/core/test/mojo_test_base.h"
+#include "mojo/public/c/system/types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace core {
+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());
+
+ RunTestClient("CopyToBufferClient", [&](MojoHandle h) {
+ MojoHandle dupe = DuplicateBuffer(b, false);
+ WriteMessageWithHandles(h, message, &dupe, 1);
+ WriteMessage(h, "quit");
+ });
+
+ 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;
+ RunTestClient("CreateBufferClient", [&](MojoHandle h) {
+ WriteMessage(h, message);
+ ReadMessageWithHandles(h, &b, 1);
+ WriteMessage(h, "quit");
+ });
+
+ 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;
+ RunTestClient("CreateAndPassBuffer", [&](MojoHandle h0) {
+ RunTestClient("ReceiveAndEditBuffer", [&](MojoHandle 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");
+ });
+ WriteMessage(h0, "quit");
+ });
+
+ // The second child should have written this message.
+ ExpectBufferContents(b, 0, message);
+}
+
+DEFINE_TEST_CLIENT_TEST_WITH_PIPE(CreateAndPassBufferParent,
+ SharedBufferTest,
+ parent) {
+ RunTestClient("CreateAndPassBuffer", [&](MojoHandle 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");
+ });
+}
+
+DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReceiveAndEditBufferParent,
+ SharedBufferTest,
+ parent) {
+ RunTestClient("ReceiveAndEditBuffer", [&](MojoHandle 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");
+ });
+}
+
+#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;
+ RunTestClient("CreateAndPassBufferParent", [&](MojoHandle child1) {
+ RunTestClient("ReceiveAndEditBufferParent", [&](MojoHandle 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");
+ });
+ WriteMessage(child1, "quit");
+ });
+
+ // 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 verify that it is read-only.
+ auto* dispatcher =
+ static_cast<SharedBufferDispatcher*>(Core::Get()->GetDispatcher(b).get());
+ base::subtle::PlatformSharedMemoryRegion& region =
+ dispatcher->GetRegionForTesting();
+ EXPECT_EQ(region.GetMode(),
+ base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly);
+
+ 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) {
+ RunTestClient("ReadAndMapWriteSharedBuffer", [&](MojoHandle 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));
+ });
+}
+
+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) {
+ RunTestClient("CreateAndPassReadOnlyBuffer", [&](MojoHandle h) {
+ MojoHandle b;
+ EXPECT_EQ("", ReadMessageWithHandles(h, &b, 1));
+ ExpectBufferContents(b, 0, "hello");
+
+ // Extract the shared memory handle and verify that it is read-only.
+ auto* dispatcher = static_cast<SharedBufferDispatcher*>(
+ Core::Get()->GetDispatcher(b).get());
+ base::subtle::PlatformSharedMemoryRegion& region =
+ dispatcher->GetRegionForTesting();
+ EXPECT_EQ(region.GetMode(),
+ base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly);
+
+ WriteMessage(h, "quit");
+ EXPECT_EQ("ok", ReadMessage(h));
+ });
+}
+
+#endif // !defined(OS_IOS)
+
+} // namespace
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/signals_unittest.cc b/mojo/core/signals_unittest.cc
new file mode 100644
index 0000000000..2519763250
--- /dev/null
+++ b/mojo/core/signals_unittest.cc
@@ -0,0 +1,222 @@
+// 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 "build/build_config.h"
+#include "mojo/core/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/trap.h"
+#include "mojo/public/c/system/types.h"
+
+namespace mojo {
+namespace core {
+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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE |
+ MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED,
+ 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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE |
+ MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED,
+ 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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE |
+ MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED,
+ 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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE |
+ MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED,
+ 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 | MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED,
+ state.satisfiable_signals);
+}
+
+TEST_F(SignalsTest, LocalPeers) {
+ MojoHandleSignalsState state = {0, 0};
+ MojoHandle a, b, c, d;
+ CreateMessagePipe(&a, &b);
+ CreateMessagePipe(&c, &d);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(a, &state));
+ EXPECT_TRUE(state.satisfiable_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+ EXPECT_FALSE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state));
+ EXPECT_TRUE(state.satisfiable_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+ EXPECT_FALSE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(c, &state));
+ EXPECT_TRUE(state.satisfiable_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+ EXPECT_FALSE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(d, &state));
+ EXPECT_TRUE(state.satisfiable_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+ EXPECT_FALSE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+
+ // Verify that sending a local pipe over a local pipe doesn't change the
+ // perceived locality of the peer.
+ const char kMessage[] = "ayyy";
+ WriteMessageWithHandles(a, kMessage, &c, 1);
+ EXPECT_EQ(kMessage, ReadMessageWithHandles(b, &c, 1));
+
+ WriteMessage(c, kMessage);
+ EXPECT_EQ(kMessage, ReadMessage(d));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(c, &state));
+ EXPECT_TRUE(state.satisfiable_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+ EXPECT_FALSE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(d, &state));
+ EXPECT_TRUE(state.satisfiable_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+ EXPECT_FALSE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+
+ // Sanity check: a closed peer can never signal remoteness.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(d, &state));
+ EXPECT_FALSE(state.satisfiable_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+}
+
+#if !defined(OS_IOS)
+
+TEST_F(SignalsTest, RemotePeers) {
+ MojoHandleSignalsState state = {0, 0};
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(a, &state));
+ EXPECT_TRUE(state.satisfiable_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+ EXPECT_FALSE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(a, MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_UNSATISFIED));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state));
+ EXPECT_TRUE(state.satisfiable_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+ EXPECT_FALSE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(b, MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_UNSATISFIED));
+
+ RunTestClient("RemotePeersClient", [&](MojoHandle h) {
+ // The bootstrap pipe should eventually signal remoteness.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(h, MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED));
+
+ // And so should |a| after we send its peer.
+ WriteMessageWithHandles(h, ":)", &b, 1);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(a, MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(a, &state));
+ EXPECT_TRUE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+
+ // And so should |c| after we fuse |d| to |a|.
+ MojoHandle c, d;
+ CreateMessagePipe(&c, &d);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoFuseMessagePipes(d, a, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(c, MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(c, &state));
+ EXPECT_TRUE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+
+ // We fused c-d to a-b, so we'll just sort of "rename" |c| back to |a| so
+ // the system resembles the state it was in before we did that.
+ a = c;
+
+ WriteMessage(h, "OK!");
+
+ // Read |b| back before joining the client.
+ EXPECT_EQ("O_O", ReadMessageWithHandles(h, &b, 1));
+
+ // Wait for |a| to see its peer as local again.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(a, MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_UNSATISFIED));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(a, &state));
+ EXPECT_FALSE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+ });
+}
+
+DEFINE_TEST_CLIENT_TEST_WITH_PIPE(RemotePeersClient, SignalsTest, h) {
+ // The bootstrap pipe should eventually signal remoteness.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(h, MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED));
+
+ MojoHandle b;
+ EXPECT_EQ(":)", ReadMessageWithHandles(h, &b, 1));
+
+ // And so should |b|.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ WaitForSignals(b, MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED));
+
+ // Wait for the test to signal that it's ready to read |b| back.
+ EXPECT_EQ("OK!", ReadMessage(h));
+
+ // Now send |b| back home.
+ WriteMessageWithHandles(h, "O_O", &b, 1);
+}
+
+#endif // !defined(OS_IOS)
+
+} // namespace
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/system_impl_export.h b/mojo/core/system_impl_export.h
new file mode 100644
index 0000000000..b55acbc26c
--- /dev/null
+++ b/mojo/core/system_impl_export.h
@@ -0,0 +1,33 @@
+// 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_CORE_SYSTEM_IMPL_EXPORT_H_
+#define MOJO_CORE_SYSTEM_IMPL_EXPORT_H_
+
+#if defined(MOJO_CORE_SHARED_LIBRARY)
+#define MOJO_SYSTEM_IMPL_EXPORT
+#else
+#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
+
+#endif // MOJO_CORE_SYSTEM_IMPL_EXPORT_H_
diff --git a/mojo/core/test/BUILD.gn b/mojo/core/test/BUILD.gn
new file mode 100644
index 0000000000..6fad6fec71
--- /dev/null
+++ b/mojo/core/test/BUILD.gn
@@ -0,0 +1,88 @@
+# 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") {
+ testonly = true
+ sources = [
+ "mojo_test_base.cc",
+ "mojo_test_base.h",
+ "test_utils.h",
+ "test_utils_win.cc",
+ ]
+
+ if (!is_ios) {
+ sources += [
+ "multiprocess_test_helper.cc",
+ "multiprocess_test_helper.h",
+ ]
+ }
+
+ if (is_fuchsia || is_posix) {
+ sources += [ "test_utils.cc" ]
+ }
+
+ public_deps = [
+ "//base",
+ "//base/test:test_support",
+ "//mojo/core/embedder",
+ "//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/core/embedder",
+ "//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/core/embedder",
+ "//mojo/core/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",
+ ]
+}
diff --git a/mojo/core/test/mojo_test_base.cc b/mojo/core/test/mojo_test_base.cc
new file mode 100644
index 0000000000..53f71014fc
--- /dev/null
+++ b/mojo/core/test/mojo_test_base.cc
@@ -0,0 +1,308 @@
+// 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/core/test/mojo_test_base.h"
+
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/run_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "build/build_config.h"
+#include "mojo/core/embedder/embedder.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/trap.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"
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include "base/mac/mach_port_broker.h"
+#endif
+
+namespace mojo {
+namespace core {
+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(
+ std::make_unique<ClientController>(client_name, this, launch_type_));
+ return *clients_.back();
+}
+
+MojoTestBase::ClientController::ClientController(const std::string& client_name,
+ MojoTestBase* test,
+ 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
+ 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().";
+}
+
+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(WriteMessageRaw(MessagePipeHandle(mp), message.data(),
+ static_cast<uint32_t>(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* out_handles,
+ uint32_t expected_num_handles) {
+ CHECK_EQ(WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE), MOJO_RESULT_OK);
+
+ std::vector<uint8_t> bytes;
+ std::vector<ScopedHandle> handles;
+ CHECK_EQ(MOJO_RESULT_OK,
+ ReadMessageRaw(MessagePipeHandle(mp), &bytes, &handles,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ CHECK_EQ(expected_num_handles, handles.size());
+ for (size_t i = 0; i < handles.size(); ++i)
+ out_handles[i] = handles[i].release().value();
+
+ return std::string(bytes.begin(), bytes.end());
+}
+
+// static
+std::string MojoTestBase::ReadMessageWithOptionalHandle(MojoHandle mp,
+ MojoHandle* handle) {
+ CHECK_EQ(WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE), MOJO_RESULT_OK);
+
+ std::vector<uint8_t> bytes;
+ std::vector<ScopedHandle> handles;
+ CHECK_EQ(MOJO_RESULT_OK,
+ ReadMessageRaw(MessagePipeHandle(mp), &bytes, &handles,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ CHECK(handles.size() == 0 || handles.size() == 1);
+ CHECK(handle);
+
+ if (handles.size() == 1)
+ *handle = handles[0].release().value();
+ else
+ *handle = MOJO_HANDLE_INVALID;
+
+ return std::string(bytes.begin(), bytes.end());
+}
+
+// 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);
+
+ std::vector<uint8_t> bytes;
+ std::vector<ScopedHandle> handles;
+ CHECK_EQ(MOJO_RESULT_OK,
+ ReadMessageRaw(MessagePipeHandle(mp), &bytes, &handles,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ CHECK_EQ(0u, handles.size());
+ CHECK_EQ(num_bytes, bytes.size());
+ memcpy(data, bytes.data(), bytes.size());
+}
+
+// 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(size, nullptr, &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_FLAG_NONE};
+ if (read_only)
+ options.flags |= MOJO_DUPLICATE_BUFFER_HANDLE_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(), nullptr,
+ reinterpret_cast<void**>(&data)));
+ memcpy(data, s.data(), s.size());
+ EXPECT_EQ(MOJO_RESULT_OK, MojoUnmapBuffer(static_cast<void*>(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(), nullptr,
+ reinterpret_cast<void**>(&data)));
+ EXPECT_EQ(s, base::StringPiece(data, s.size()));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoUnmapBuffer(static_cast<void*>(data)));
+}
+
+// static
+void MojoTestBase::CreateDataPipe(MojoHandle* p0,
+ MojoHandle* p1,
+ size_t capacity) {
+ MojoCreateDataPipeOptions options;
+ options.struct_size = static_cast<uint32_t>(sizeof(options));
+ options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
+ options.element_num_bytes = 1;
+ options.capacity_num_bytes = static_cast<uint32_t>(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<uint32_t>(data.size());
+ MojoWriteDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_WRITE_DATA_FLAG_ALL_OR_NONE;
+ CHECK_EQ(MojoWriteData(producer, data.data(), &num_bytes, &options),
+ MOJO_RESULT_OK);
+ CHECK_EQ(num_bytes, static_cast<uint32_t>(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<char> buffer(size);
+ uint32_t num_bytes = static_cast<uint32_t>(size);
+ MojoReadDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_READ_DATA_FLAG_ALL_OR_NONE;
+ CHECK_EQ(MojoReadData(consumer, &options, buffer.data(), &num_bytes),
+ MOJO_RESULT_OK);
+ CHECK_EQ(num_bytes, static_cast<uint32_t>(size));
+
+ return std::string(buffer.begin(), buffer.end());
+}
+
+// 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,
+ MojoTriggerCondition condition,
+ MojoHandleSignalsState* state) {
+ return Wait(Handle(handle), signals, condition, state);
+}
+
+// static
+MojoResult MojoTestBase::WaitForSignals(MojoHandle handle,
+ MojoHandleSignals signals,
+ MojoHandleSignalsState* state) {
+ return Wait(Handle(handle), signals, MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ state);
+}
+
+} // namespace test
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/test/mojo_test_base.h b/mojo/core/test/mojo_test_base.h
new file mode 100644
index 0000000000..8110cfc513
--- /dev/null
+++ b/mojo/core/test/mojo_test_base.h
@@ -0,0 +1,232 @@
+// 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_CORE_TEST_MOJO_TEST_BASE_H_
+#define MOJO_CORE_TEST_MOJO_TEST_BASE_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "build/build_config.h"
+#include "mojo/core/test/multiprocess_test_helper.h"
+#include "mojo/public/c/system/trap.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 core {
+namespace test {
+
+class MojoTestBase : public testing::Test {
+ public:
+ MojoTestBase();
+ ~MojoTestBase() override;
+
+ using LaunchType = MultiprocessTestHelper::LaunchType;
+ using HandlerCallback = base::Callback<void(ScopedMessagePipeHandle)>;
+
+ class ClientController {
+ public:
+ ClientController(const std::string& client_name,
+ MojoTestBase* test,
+ LaunchType launch_type);
+ ~ClientController();
+
+ MojoHandle pipe() const { return pipe_.get().value(); }
+
+ int WaitForShutdown();
+
+ private:
+ friend class MojoTestBase;
+
+#if !defined(OS_IOS)
+ MultiprocessTestHelper helper_;
+#endif
+ ScopedMessagePipeHandle pipe_;
+ bool was_shutdown_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(ClientController);
+ };
+
+ ClientController& StartClient(const std::string& client_name);
+
+ template <typename HandlerFunc>
+ void RunTestClient(const std::string& client_name, HandlerFunc handler) {
+ EXPECT_EQ(0, RunTestClientAndGetExitCode(client_name, handler));
+ }
+
+ template <typename HandlerFunc>
+ int RunTestClientAndGetExitCode(const std::string& client_name,
+ HandlerFunc handler) {
+ ClientController& c = StartClient(client_name);
+ handler(c.pipe());
+ return 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 go high or low.
+ static MojoResult WaitForSignals(MojoHandle handle,
+ MojoHandleSignals signals,
+ MojoTriggerCondition condition,
+ MojoHandleSignalsState* state = nullptr);
+
+ // Like above but only waits for signals to go high.
+ 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<std::unique_ptr<ClientController>> clients_;
+
+ LaunchType launch_type_ = LaunchType::CHILD;
+
+ DISALLOW_COPY_AND_ASSIGN(MojoTestBase);
+};
+
+// 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 test process (see RunTestClient* 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::core::test::MultiprocessTestHelper::ChildSetup) { \
+ client_name##_MainFixture test; \
+ return ::mojo::core::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::core::test::MultiprocessTestHelper::ChildSetup) { \
+ client_name##_MainFixture test; \
+ return ::mojo::core::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 core
+} // namespace mojo
+
+#endif // MOJO_CORE_TEST_MOJO_TEST_BASE_H_
diff --git a/mojo/core/test/multiprocess_test_helper.cc b/mojo/core/test/multiprocess_test_helper.cc
new file mode 100644
index 0000000000..7181520e3e
--- /dev/null
+++ b/mojo/core/test/multiprocess_test_helper.cc
@@ -0,0 +1,276 @@
+// 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/core/test/multiprocess_test_helper.h"
+
+#include <functional>
+#include <set>
+#include <utility>
+
+#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/lazy_instance.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/rand_util.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.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/public/cpp/platform/named_platform_channel.h"
+#include "mojo/public/cpp/platform/platform_channel.h"
+#include "mojo/public/cpp/platform/platform_channel_endpoint.h"
+#include "mojo/public/cpp/platform/platform_channel_server_endpoint.h"
+#include "mojo/public/cpp/system/invitation.h"
+#include "mojo/public/cpp/system/isolated_connection.h"
+#include "mojo/public/cpp/system/platform_handle.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 core {
+namespace test {
+
+namespace {
+
+const char kNamedPipeName[] = "named-pipe-name";
+const char kRunAsBrokerClient[] = "run-as-broker-client";
+
+const char kTestChildMessagePipeName[] = "test_pipe";
+
+// For use (and only valid) in a test child process:
+base::LazyInstance<IsolatedConnection>::Leaky g_child_isolated_connection;
+
+template <typename Func>
+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_.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_.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<std::string> 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);
+ }
+
+ mojo::PlatformChannel channel;
+ mojo::NamedPlatformChannel::ServerName server_name;
+ base::LaunchOptions options;
+ if (launch_type == LaunchType::CHILD || launch_type == LaunchType::PEER) {
+ channel.PrepareToPassRemoteEndpoint(&options, &command_line);
+ } else if (launch_type == LaunchType::NAMED_CHILD ||
+ launch_type == LaunchType::NAMED_PEER) {
+#if defined(OS_FUCHSIA)
+ // TODO(fuchsia): Implement named channels. See crbug.com/754038.
+ NOTREACHED();
+#elif defined(OS_POSIX)
+ base::FilePath temp_dir;
+ CHECK(base::PathService::Get(base::DIR_TEMP, &temp_dir));
+ server_name =
+ temp_dir.AppendASCII(base::NumberToString(base::RandUint64())).value();
+#elif defined(OS_WIN)
+ server_name = base::NumberToString16(base::RandUint64());
+#else
+#error "Platform not yet supported."
+#endif
+ command_line.AppendSwitchNative(kNamedPipeName, server_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);
+ }
+
+#if defined(OS_WIN)
+ options.start_hidden = true;
+#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.
+ PlatformChannelEndpoint local_channel_endpoint;
+ PlatformChannelServerEndpoint server_endpoint;
+ if (launch_type == LaunchType::CHILD || launch_type == LaunchType::PEER) {
+ local_channel_endpoint = channel.TakeLocalEndpoint();
+ } else if (launch_type == LaunchType::NAMED_CHILD ||
+ launch_type == LaunchType::NAMED_PEER) {
+ NamedPlatformChannel::Options options;
+ options.server_name = server_name;
+ NamedPlatformChannel named_channel(options);
+ server_endpoint = named_channel.TakeServerEndpoint();
+ }
+
+ OutgoingInvitation child_invitation;
+ ScopedMessagePipeHandle pipe;
+ if (launch_type == LaunchType::CHILD ||
+ launch_type == LaunchType::NAMED_CHILD) {
+ pipe = child_invitation.AttachMessagePipe(kTestChildMessagePipeName);
+ command_line.AppendSwitch(kRunAsBrokerClient);
+ } else if (launch_type == LaunchType::PEER ||
+ launch_type == LaunchType::NAMED_PEER) {
+ isolated_connection_ = std::make_unique<IsolatedConnection>();
+ if (local_channel_endpoint.is_valid()) {
+ pipe = isolated_connection_->Connect(std::move(local_channel_endpoint));
+ } else {
+#if defined(OS_POSIX) || defined(OS_WIN)
+ DCHECK(server_endpoint.is_valid());
+ pipe = isolated_connection_->Connect(std::move(server_endpoint));
+#else
+ NOTREACHED();
+#endif
+ }
+ }
+
+ test_child_ =
+ base::SpawnMultiProcessTestChild(test_child_main, command_line, options);
+ if (launch_type == LaunchType::CHILD || launch_type == LaunchType::PEER)
+ channel.RemoteProcessLaunchAttempted();
+
+ if (launch_type == LaunchType::CHILD) {
+ DCHECK(local_channel_endpoint.is_valid());
+ OutgoingInvitation::Send(std::move(child_invitation), test_child_.Handle(),
+ std::move(local_channel_endpoint),
+ mojo::ProcessErrorCallback());
+ } else if (launch_type == LaunchType::NAMED_CHILD) {
+ DCHECK(server_endpoint.is_valid());
+ OutgoingInvitation::Send(std::move(child_invitation), test_child_.Handle(),
+ std::move(server_endpoint),
+ mojo::ProcessErrorCallback());
+ }
+
+ CHECK(test_child_.IsValid());
+ return pipe;
+}
+
+int MultiprocessTestHelper::WaitForChildShutdown() {
+ CHECK(test_child_.IsValid());
+
+ int rv = -1;
+ WaitForMultiprocessTestChildExit(test_child_, TestTimeouts::action_timeout(),
+ &rv);
+ test_child_.Close();
+ return rv;
+}
+
+bool MultiprocessTestHelper::WaitForChildTestShutdown() {
+ return WaitForChildShutdown() == 0;
+}
+
+// static
+void MultiprocessTestHelper::ChildSetup() {
+ CHECK(base::CommandLine::InitializedForCurrentProcess());
+
+ auto& command_line = *base::CommandLine::ForCurrentProcess();
+ NamedPlatformChannel::ServerName named_pipe(
+ command_line.GetSwitchValueNative(kNamedPipeName));
+ if (command_line.HasSwitch(kRunAsBrokerClient)) {
+ mojo::IncomingInvitation invitation;
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ CHECK(base::MachPortBroker::ChildSendTaskPortToParent("mojo_test"));
+#endif
+ if (!named_pipe.empty()) {
+ invitation = mojo::IncomingInvitation::Accept(
+ mojo::NamedPlatformChannel::ConnectToServer(named_pipe));
+ } else {
+ auto endpoint =
+ mojo::PlatformChannel::RecoverPassedEndpointFromCommandLine(
+ command_line);
+ invitation = IncomingInvitation::Accept(std::move(endpoint));
+ }
+ primordial_pipe = invitation.ExtractMessagePipe(kTestChildMessagePipeName);
+ } else {
+ if (!named_pipe.empty()) {
+ primordial_pipe = g_child_isolated_connection.Get().Connect(
+ NamedPlatformChannel::ConnectToServer(named_pipe));
+ } else {
+ primordial_pipe = g_child_isolated_connection.Get().Connect(
+ PlatformChannel::RecoverPassedEndpointFromCommandLine(command_line));
+ }
+ }
+}
+
+// static
+int MultiprocessTestHelper::RunClientMain(
+ const base::Callback<int(MojoHandle)>& 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<void(MojoHandle)>& main) {
+ return RunClientFunction(
+ [main](MojoHandle handle) {
+ main.Run(handle);
+ return (::testing::Test::HasFatalFailure() ||
+ ::testing::Test::HasNonfatalFailure())
+ ? 1
+ : 0;
+ },
+ true /* pass_pipe_ownership_to_main */);
+}
+
+// static
+mojo::ScopedMessagePipeHandle MultiprocessTestHelper::primordial_pipe;
+
+} // namespace test
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/test/multiprocess_test_helper.h b/mojo/core/test/multiprocess_test_helper.h
new file mode 100644
index 0000000000..e7be8355c3
--- /dev/null
+++ b/mojo/core/test/multiprocess_test_helper.h
@@ -0,0 +1,102 @@
+// 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_CORE_TEST_MULTIPROCESS_TEST_HELPER_H_
+#define MOJO_CORE_TEST_MULTIPROCESS_TEST_HELPER_H_
+
+#include <string>
+
+#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/public/cpp/system/message_pipe.h"
+#include "testing/multiprocess_func_list.h"
+
+namespace mojo {
+
+class IsolatedConnection;
+
+namespace core {
+namespace test {
+
+class MultiprocessTestHelper {
+ public:
+ using HandlerCallback = base::Callback<void(ScopedMessagePipeHandle)>;
+
+ 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);
+
+ // 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_; }
+
+ // Used by macros in mojo/core/test/mojo_test_base.h to support multiprocess
+ // test client initialization.
+ static void ChildSetup();
+ static int RunClientMain(const base::Callback<int(MojoHandle)>& main,
+ bool pass_pipe_ownership_to_main = false);
+ static int RunClientTestMain(const base::Callback<void(MojoHandle)>& main);
+
+ // For use (and only valid) in the child process:
+ static mojo::ScopedMessagePipeHandle primordial_pipe;
+
+ private:
+ // Valid after |StartChild()| and before |WaitForChildShutdown()|.
+ base::Process test_child_;
+
+ std::unique_ptr<IsolatedConnection> isolated_connection_;
+
+ DISALLOW_COPY_AND_ASSIGN(MultiprocessTestHelper);
+};
+
+} // namespace test
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_TEST_MULTIPROCESS_TEST_HELPER_H_
diff --git a/mojo/core/test/run_all_perftests.cc b/mojo/core/test/run_all_perftests.cc
new file mode 100644
index 0000000000..2f86cc07a3
--- /dev/null
+++ b/mojo/core/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/core/embedder/embedder.h"
+#include "mojo/core/embedder/scoped_ipc_support.h"
+#include "mojo/core/test/multiprocess_test_helper.h"
+#include "mojo/core/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::core::Init();
+ base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart);
+ mojo::core::ScopedIPCSupport ipc_support(
+ test_io_thread.task_runner(),
+ mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);
+ mojo::test::TestSupport::Init(new mojo::core::test::TestSupportImpl());
+
+ return test.Run();
+}
diff --git a/mojo/core/test/run_all_unittests.cc b/mojo/core/test/run_all_unittests.cc
new file mode 100644
index 0000000000..974dfdefbd
--- /dev/null
+++ b/mojo/core/test/run_all_unittests.cc
@@ -0,0 +1,50 @@
+// 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 <signal.h>
+
+#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 "build/build_config.h"
+#include "mojo/core/embedder/embedder.h"
+#include "mojo/core/embedder/scoped_ipc_support.h"
+#include "mojo/core/test/multiprocess_test_helper.h"
+#include "mojo/core/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::core::Init();
+
+ mojo::test::TestSupport::Init(new mojo::core::test::TestSupportImpl());
+ base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart);
+
+ mojo::core::ScopedIPCSupport ipc_support(
+ test_io_thread.task_runner(),
+ mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);
+ return base::LaunchUnitTests(
+ argc, argv,
+ base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite)));
+}
diff --git a/mojo/core/test/test_support_impl.cc b/mojo/core/test/test_support_impl.cc
new file mode 100644
index 0000000000..f2fd5c579b
--- /dev/null
+++ b/mojo/core/test/test_support_impl.cc
@@ -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.
+
+#include "mojo/core/test/test_support_impl.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <string>
+
+#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 core {
+namespace test {
+namespace {
+
+base::FilePath ResolveSourceRootRelativePath(const char* relative_path) {
+ base::FilePath path;
+ if (!base::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<std::string> 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<char**>(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 core
+} // namespace mojo
diff --git a/mojo/core/test/test_support_impl.h b/mojo/core/test/test_support_impl.h
new file mode 100644
index 0000000000..1047e91245
--- /dev/null
+++ b/mojo/core/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_CORE_TEST_TEST_SUPPORT_IMPL_H_
+#define MOJO_CORE_TEST_TEST_SUPPORT_IMPL_H_
+
+#include <stdio.h>
+
+#include "base/macros.h"
+#include "mojo/public/tests/test_support_private.h"
+
+namespace mojo {
+namespace core {
+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 core
+} // namespace mojo
+
+#endif // MOJO_CORE_TEST_TEST_SUPPORT_IMPL_H_
diff --git a/mojo/core/test/test_utils.cc b/mojo/core/test/test_utils.cc
new file mode 100644
index 0000000000..28aee0e557
--- /dev/null
+++ b/mojo/core/test/test_utils.cc
@@ -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.
+
+#include "mojo/core/test/test_utils.h"
+
+#include <fcntl.h>
+#include <stddef.h>
+#include <unistd.h>
+
+#include "base/posix/eintr_wrapper.h"
+
+namespace mojo {
+namespace core {
+namespace test {
+
+PlatformHandle PlatformHandleFromFILE(base::ScopedFILE fp) {
+ CHECK(fp);
+ int rv = HANDLE_EINTR(dup(fileno(fp.get())));
+ PCHECK(rv != -1) << "dup";
+ return PlatformHandle(base::ScopedFD(rv));
+}
+
+base::ScopedFILE FILEFromPlatformHandle(PlatformHandle h, const char* mode) {
+ CHECK(h.is_valid());
+ base::ScopedFILE rv(fdopen(h.ReleaseFD(), mode));
+ PCHECK(rv) << "fdopen";
+ return rv;
+}
+
+} // namespace test
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/test/test_utils.h b/mojo/core/test/test_utils.h
new file mode 100644
index 0000000000..8e4385d1c2
--- /dev/null
+++ b/mojo/core/test/test_utils.h
@@ -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.
+
+#ifndef MOJO_CORE_TEST_TEST_UTILS_H_
+#define MOJO_CORE_TEST_TEST_UTILS_H_
+
+#include <stddef.h>
+#include <stdio.h>
+
+#include <string>
+
+#include "base/files/scoped_file.h"
+#include "mojo/public/cpp/platform/platform_handle.h"
+
+namespace mojo {
+namespace core {
+namespace test {
+
+// Gets a (scoped) |PlatformHandle| from the given (scoped) |FILE|.
+PlatformHandle PlatformHandleFromFILE(base::ScopedFILE fp);
+
+// Gets a (scoped) |FILE| from a (scoped) |PlatformHandle|.
+base::ScopedFILE FILEFromPlatformHandle(PlatformHandle h, const char* mode);
+
+} // namespace test
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_TEST_TEST_UTILS_H_
diff --git a/mojo/core/test/test_utils_win.cc b/mojo/core/test/test_utils_win.cc
new file mode 100644
index 0000000000..9adfb7aaa5
--- /dev/null
+++ b/mojo/core/test/test_utils_win.cc
@@ -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.
+
+#include "mojo/core/test/test_utils.h"
+
+#include <fcntl.h>
+#include <io.h>
+#include <stddef.h>
+#include <string.h>
+#include <windows.h>
+
+namespace mojo {
+namespace core {
+namespace test {
+
+PlatformHandle PlatformHandleFromFILE(base::ScopedFILE fp) {
+ CHECK(fp);
+
+ HANDLE rv = INVALID_HANDLE_VALUE;
+ PCHECK(DuplicateHandle(
+ GetCurrentProcess(),
+ reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(fp.get()))),
+ GetCurrentProcess(), &rv, 0, TRUE, DUPLICATE_SAME_ACCESS))
+ << "DuplicateHandle";
+ return PlatformHandle(base::win::ScopedHandle(rv));
+}
+
+base::ScopedFILE FILEFromPlatformHandle(PlatformHandle 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<intptr_t>(h.ReleaseHandle()), flags),
+ mode));
+ PCHECK(rv) << "_fdopen";
+ return rv;
+}
+
+} // namespace test
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/test_utils.cc b/mojo/core/test_utils.cc
new file mode 100644
index 0000000000..33ce9ea015
--- /dev/null
+++ b/mojo/core/test_utils.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/core/test_utils.h"
+
+#include <stdint.h>
+
+#include <limits>
+
+#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 core {
+namespace test {
+
+MojoDeadline DeadlineFromMilliseconds(unsigned milliseconds) {
+ return static_cast<MojoDeadline>(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<MojoDeadline>(
+ TestTimeouts::tiny_timeout().InMicroseconds());
+}
+
+MojoDeadline ActionDeadline() {
+ return static_cast<MojoDeadline>(
+ TestTimeouts::action_timeout().InMicroseconds());
+}
+
+void Sleep(MojoDeadline deadline) {
+ CHECK_LE(deadline,
+ static_cast<MojoDeadline>(std::numeric_limits<int64_t>::max()));
+ base::PlatformThread::Sleep(
+ base::TimeDelta::FromMicroseconds(static_cast<int64_t>(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<MojoDeadline>(result);
+}
+
+} // namespace test
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/test_utils.h b/mojo/core/test_utils.h
new file mode 100644
index 0000000000..98ac51e182
--- /dev/null
+++ b/mojo/core/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_CORE_TEST_UTILS_H_
+#define MOJO_CORE_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 core {
+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 core
+} // namespace mojo
+
+#endif // MOJO_CORE_TEST_UTILS_H_
diff --git a/mojo/core/trap_unittest.cc b/mojo/core/trap_unittest.cc
new file mode 100644
index 0000000000..555726f4da
--- /dev/null
+++ b/mojo/core/trap_unittest.cc
@@ -0,0 +1,1763 @@
+// 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 <stdint.h>
+
+#include <map>
+#include <memory>
+#include <set>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/rand_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/bind_test_util.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/simple_thread.h"
+#include "base/time/time.h"
+#include "mojo/core/test/mojo_test_base.h"
+#include "mojo/public/c/system/data_pipe.h"
+#include "mojo/public/c/system/trap.h"
+#include "mojo/public/c/system/types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace core {
+namespace {
+
+using TrapTest = test::MojoTestBase;
+
+class TriggerHelper {
+ public:
+ using ContextCallback = base::RepeatingCallback<void(const MojoTrapEvent&)>;
+
+ TriggerHelper() {}
+ ~TriggerHelper() {}
+
+ MojoResult CreateTrap(MojoHandle* handle) {
+ return MojoCreateTrap(&Notify, nullptr, handle);
+ }
+
+ template <typename Handler>
+ uintptr_t CreateContext(Handler handler) {
+ return CreateContextWithCancel(handler, [] {});
+ }
+
+ template <typename Handler, typename CancelHandler>
+ uintptr_t CreateContextWithCancel(Handler handler,
+ CancelHandler cancel_handler) {
+ auto* context =
+ new NotificationContext(base::BindLambdaForTesting(handler));
+ context->SetCancelCallback(
+ base::BindOnce(base::BindLambdaForTesting([cancel_handler, context] {
+ cancel_handler();
+ delete context;
+ })));
+ return reinterpret_cast<uintptr_t>(context);
+ }
+
+ private:
+ class NotificationContext {
+ public:
+ explicit NotificationContext(const ContextCallback& callback)
+ : callback_(callback) {}
+
+ ~NotificationContext() {}
+
+ void SetCancelCallback(base::OnceClosure cancel_callback) {
+ cancel_callback_ = std::move(cancel_callback);
+ }
+
+ void Notify(const MojoTrapEvent& event) {
+ if (event.result == MOJO_RESULT_CANCELLED && cancel_callback_)
+ std::move(cancel_callback_).Run();
+ else
+ callback_.Run(event);
+ }
+
+ private:
+ const ContextCallback callback_;
+ base::OnceClosure cancel_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(NotificationContext);
+ };
+
+ static void Notify(const MojoTrapEvent* event) {
+ reinterpret_cast<NotificationContext*>(event->trigger_context)
+ ->Notify(*event);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(TriggerHelper);
+};
+
+class ThreadedRunner : public base::SimpleThread {
+ public:
+ explicit ThreadedRunner(base::OnceClosure callback)
+ : SimpleThread("ThreadedRunner"), callback_(std::move(callback)) {}
+ ~ThreadedRunner() override {}
+
+ void Run() override { std::move(callback_).Run(); }
+
+ private:
+ base::OnceClosure callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadedRunner);
+};
+
+void ExpectNoNotification(const MojoTrapEvent* event) {
+ NOTREACHED();
+}
+
+void ExpectOnlyCancel(const MojoTrapEvent* event) {
+ EXPECT_EQ(event->result, MOJO_RESULT_CANCELLED);
+}
+
+TEST_F(TrapTest, InvalidArguments) {
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoCreateTrap(&ExpectNoNotification, nullptr, nullptr));
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectNoNotification, nullptr, &t));
+
+ // Try to add triggers for handles which don't raise trappable signals.
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ MojoAddTrigger(t, t, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, 0, nullptr));
+ MojoHandle buffer_handle = CreateBuffer(42);
+ EXPECT_EQ(
+ MOJO_RESULT_INVALID_ARGUMENT,
+ MojoAddTrigger(t, buffer_handle, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, 0, nullptr));
+
+ // Try to remove a trigger on a non-trap handle.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoRemoveTrigger(buffer_handle, 0, nullptr));
+
+ // Try to arm an invalid or non-trap handle.
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoArmTrap(MOJO_HANDLE_INVALID, nullptr, nullptr, nullptr));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoArmTrap(buffer_handle, nullptr, nullptr, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(buffer_handle));
+
+ // Try to arm with a non-null count but a null output buffer.
+ uint32_t num_blocking_events = 1;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoArmTrap(t, nullptr, &num_blocking_events, nullptr));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+}
+
+TEST_F(TrapTest, TrapMessagePipeReadable) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ TriggerHelper helper;
+ int num_expected_notifications = 1;
+ const uintptr_t readable_a_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_GT(num_expected_notifications, 0);
+ num_expected_notifications -= 1;
+
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+ wait.Signal();
+ });
+
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_a_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, 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);
+ wait.Wait();
+
+ // This also shouldn't fire a notification; the trap is still disarmed.
+ WriteMessage(b, kMessage3);
+
+ // Arming should fail with relevant information.
+ constexpr size_t kMaxBlockingEvents = 3;
+ uint32_t num_blocking_events = kMaxBlockingEvents;
+ MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])}};
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
+ EXPECT_EQ(1u, num_blocking_events);
+ EXPECT_EQ(readable_a_context, blocking_events[0].trigger_context);
+ EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);
+
+ // 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 trap.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+}
+
+TEST_F(TrapTest, CloseWatchedMessagePipeHandle) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ TriggerHelper helper;
+ const uintptr_t readable_a_context = helper.CreateContextWithCancel(
+ [](const MojoTrapEvent&) {}, [&] { wait.Signal(); });
+
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_a_context, nullptr));
+
+ // Test that closing a watched handle fires an appropriate notification, even
+ // when the trap is unarmed.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+ wait.Wait();
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+}
+
+TEST_F(TrapTest, CloseWatchedMessagePipeHandlePeer) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ TriggerHelper helper;
+ const uintptr_t readable_a_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, event.result);
+ wait.Signal();
+ });
+
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_a_context, nullptr));
+
+ // Test that closing a watched handle's peer with an armed trap fires an
+ // appropriate notification.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+ wait.Wait();
+
+ // And now arming should fail with correct information about |a|'s state.
+ constexpr size_t kMaxBlockingEvents = 3;
+ uint32_t num_blocking_events = kMaxBlockingEvents;
+ MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])}};
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
+ EXPECT_EQ(1u, num_blocking_events);
+ EXPECT_EQ(readable_a_context, blocking_events[0].trigger_context);
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, blocking_events[0].result);
+ EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals &
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+ EXPECT_FALSE(blocking_events[0].signals_state.satisfiable_signals &
+ MOJO_HANDLE_SIGNAL_READABLE);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+}
+
+TEST_F(TrapTest, TrapDataPipeConsumerReadable) {
+ constexpr size_t kTestPipeCapacity = 64;
+ MojoHandle producer, consumer;
+ CreateDataPipe(&producer, &consumer, kTestPipeCapacity);
+
+ base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ TriggerHelper helper;
+ int num_expected_notifications = 1;
+ const uintptr_t readable_consumer_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_GT(num_expected_notifications, 0);
+ num_expected_notifications -= 1;
+
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+ wait.Signal();
+ });
+
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, consumer, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_consumer_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, 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);
+ wait.Wait();
+
+ // This also shouldn't fire a notification; the trap is still disarmed.
+ WriteData(producer, kMessage3);
+
+ // Arming should fail with relevant information.
+ constexpr size_t kMaxBlockingEvents = 3;
+ uint32_t num_blocking_events = kMaxBlockingEvents;
+ MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])}};
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
+ EXPECT_EQ(1u, num_blocking_events);
+ EXPECT_EQ(readable_consumer_context, blocking_events[0].trigger_context);
+ EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);
+
+ // 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 trap.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
+}
+
+TEST_F(TrapTest, TrapDataPipeConsumerNewDataReadable) {
+ constexpr size_t kTestPipeCapacity = 64;
+ MojoHandle producer, consumer;
+ CreateDataPipe(&producer, &consumer, kTestPipeCapacity);
+
+ base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ TriggerHelper helper;
+ int num_new_data_notifications = 0;
+ const uintptr_t new_data_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ num_new_data_notifications += 1;
+
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+ wait.Signal();
+ });
+
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, consumer, MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ new_data_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, 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);
+ wait.Wait();
+
+ // This also shouldn't fire a notification; the trap is still disarmed.
+ WriteData(producer, kMessage3);
+
+ // Arming should fail with relevant information.
+ constexpr size_t kMaxBlockingEvents = 3;
+ uint32_t num_blocking_events = kMaxBlockingEvents;
+ MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])}};
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
+ EXPECT_EQ(1u, num_blocking_events);
+ EXPECT_EQ(new_data_context, blocking_events[0].trigger_context);
+ EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);
+
+ // 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;
+ MojoReadDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_READ_DATA_FLAG_ALL_OR_NONE;
+ EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE,
+ MojoReadData(consumer, &options, large_buffer, &large_read_size));
+
+ // Attempt to arm again. Should succeed.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ // Write more data. Should notify.
+ wait.Reset();
+ WriteData(producer, kMessage1);
+ wait.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, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ EXPECT_EQ(2, num_new_data_notifications);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
+}
+
+TEST_F(TrapTest, TrapDataPipeProducerWritable) {
+ 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 wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ TriggerHelper helper;
+ int num_expected_notifications = 1;
+ const uintptr_t writable_producer_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_GT(num_expected_notifications, 0);
+ num_expected_notifications -= 1;
+
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+ wait.Signal();
+ });
+
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, producer, MOJO_HANDLE_SIGNAL_WRITABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ writable_producer_context, nullptr));
+
+ // The producer is already writable, so arming should fail with relevant
+ // information.
+ constexpr size_t kMaxBlockingEvents = 3;
+ uint32_t num_blocking_events = kMaxBlockingEvents;
+ MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])}};
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
+ EXPECT_EQ(1u, num_blocking_events);
+ EXPECT_EQ(writable_producer_context, blocking_events[0].trigger_context);
+ EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);
+ EXPECT_TRUE(blocking_events[0].signals_state.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,
+ MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
+ EXPECT_EQ(1u, num_blocking_events);
+ EXPECT_EQ(writable_producer_context, blocking_events[0].trigger_context);
+ EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);
+ EXPECT_TRUE(blocking_events[0].signals_state.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, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ // Now read from the pipe, making the producer writable again. Should notify.
+ EXPECT_EQ(kTestData, ReadData(consumer, sizeof(kTestData) - 1));
+ wait.Wait();
+
+ // Arming should fail again.
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
+ EXPECT_EQ(1u, num_blocking_events);
+ EXPECT_EQ(writable_producer_context, blocking_events[0].trigger_context);
+ EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);
+ EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals &
+ MOJO_HANDLE_SIGNAL_WRITABLE);
+
+ // Fill the pipe once more and arm the trap. Should succeed.
+ WriteData(producer, kTestData);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
+};
+
+TEST_F(TrapTest, CloseWatchedDataPipeConsumerHandle) {
+ constexpr size_t kTestPipeCapacity = 8;
+ MojoHandle producer, consumer;
+ CreateDataPipe(&producer, &consumer, kTestPipeCapacity);
+
+ base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ TriggerHelper helper;
+ const uintptr_t readable_consumer_context = helper.CreateContextWithCancel(
+ [](const MojoTrapEvent&) {}, [&] { wait.Signal(); });
+
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, consumer, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_consumer_context, nullptr));
+
+ // Closing the consumer should fire a cancellation notification.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
+ wait.Wait();
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+}
+
+TEST_F(TrapTest, CloseWatchedDataPipeConsumerHandlePeer) {
+ constexpr size_t kTestPipeCapacity = 8;
+ MojoHandle producer, consumer;
+ CreateDataPipe(&producer, &consumer, kTestPipeCapacity);
+
+ base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ TriggerHelper helper;
+ const uintptr_t readable_consumer_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, event.result);
+ wait.Signal();
+ });
+
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, consumer, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_consumer_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ // Closing the producer should fire a notification for an unsatisfiable
+ // condition.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
+ wait.Wait();
+
+ // Now attempt to rearm and expect appropriate error feedback.
+ constexpr size_t kMaxBlockingEvents = 3;
+ uint32_t num_blocking_events = kMaxBlockingEvents;
+ MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])}};
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
+ EXPECT_EQ(1u, num_blocking_events);
+ EXPECT_EQ(readable_consumer_context, blocking_events[0].trigger_context);
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, blocking_events[0].result);
+ EXPECT_FALSE(blocking_events[0].signals_state.satisfiable_signals &
+ MOJO_HANDLE_SIGNAL_READABLE);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
+}
+
+TEST_F(TrapTest, CloseWatchedDataPipeProducerHandle) {
+ constexpr size_t kTestPipeCapacity = 8;
+ MojoHandle producer, consumer;
+ CreateDataPipe(&producer, &consumer, kTestPipeCapacity);
+
+ base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ TriggerHelper helper;
+ const uintptr_t writable_producer_context = helper.CreateContextWithCancel(
+ [](const MojoTrapEvent&) {}, [&] { wait.Signal(); });
+
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, producer, MOJO_HANDLE_SIGNAL_WRITABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ writable_producer_context, nullptr));
+
+ // Closing the consumer should fire a cancellation notification.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
+ wait.Wait();
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+}
+
+TEST_F(TrapTest, 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 wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ TriggerHelper helper;
+ const uintptr_t writable_producer_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, event.result);
+ wait.Signal();
+ });
+
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, producer, MOJO_HANDLE_SIGNAL_WRITABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ writable_producer_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ // Closing the consumer should fire a notification for an unsatisfiable
+ // condition, 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));
+ wait.Wait();
+
+ // Now attempt to rearm and expect appropriate error feedback.
+ constexpr size_t kMaxBlockingEvents = 3;
+ uint32_t num_blocking_events = kMaxBlockingEvents;
+ MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])}};
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
+ EXPECT_EQ(1u, num_blocking_events);
+ EXPECT_EQ(writable_producer_context, blocking_events[0].trigger_context);
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, blocking_events[0].result);
+ EXPECT_FALSE(blocking_events[0].signals_state.satisfiable_signals &
+ MOJO_HANDLE_SIGNAL_WRITABLE);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer));
+}
+
+TEST_F(TrapTest, ArmWithNoTriggers) {
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectNoNotification, nullptr, &t));
+ EXPECT_EQ(MOJO_RESULT_NOT_FOUND, MojoArmTrap(t, nullptr, nullptr, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+}
+
+TEST_F(TrapTest, DuplicateTriggerContext) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectOnlyCancel, nullptr, &t));
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, 0, nullptr));
+ EXPECT_EQ(
+ MOJO_RESULT_ALREADY_EXISTS,
+ MojoAddTrigger(t, b, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, 0, nullptr));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+}
+
+TEST_F(TrapTest, RemoveUnknownTrigger) {
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectNoNotification, nullptr, &t));
+ EXPECT_EQ(MOJO_RESULT_NOT_FOUND, MojoRemoveTrigger(t, 1234, nullptr));
+}
+
+TEST_F(TrapTest, ArmWithTriggerConditionAlreadySatisfied) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectOnlyCancel, nullptr, &t));
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_WRITABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, 0, nullptr));
+
+ // |a| is always writable, so we can never arm this trap.
+ constexpr size_t kMaxBlockingEvents = 3;
+ uint32_t num_blocking_events = kMaxBlockingEvents;
+ MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])}};
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
+ EXPECT_EQ(1u, num_blocking_events);
+ EXPECT_EQ(0u, blocking_events[0].trigger_context);
+ EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);
+ EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals &
+ MOJO_HANDLE_SIGNAL_WRITABLE);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+}
+
+TEST_F(TrapTest, ArmWithTriggerConditionAlreadyUnsatisfiable) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectOnlyCancel, nullptr, &t));
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, 0, nullptr));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+
+ // |b| is closed and never wrote any messages, so |a| won't be readable again.
+ // MojoArmTrap() should fail, incidcating as much.
+ constexpr size_t kMaxBlockingEvents = 3;
+ uint32_t num_blocking_events = kMaxBlockingEvents;
+ MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])}};
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
+ EXPECT_EQ(1u, num_blocking_events);
+ EXPECT_EQ(0u, blocking_events[0].trigger_context);
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, blocking_events[0].result);
+ EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals &
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+ EXPECT_FALSE(blocking_events[0].signals_state.satisfiable_signals &
+ MOJO_HANDLE_SIGNAL_READABLE);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+}
+
+TEST_F(TrapTest, MultipleTriggers) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ base::WaitableEvent a_wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ base::WaitableEvent b_wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ TriggerHelper helper;
+ int num_a_notifications = 0;
+ int num_b_notifications = 0;
+ uintptr_t readable_a_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ num_a_notifications += 1;
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+ a_wait.Signal();
+ });
+ uintptr_t readable_b_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ num_b_notifications += 1;
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+ b_wait.Signal();
+ });
+
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+
+ // Add two independent triggers to trap |a| or |b| readability.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_a_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, b, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_b_context, nullptr));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, 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_wait.Wait();
+ a_wait.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
+ // trap 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 kMaxBlockingEvents = 3;
+ uint32_t num_blocking_events = 1;
+ MojoTrapEvent blocking_events[kMaxBlockingEvents] = {
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])},
+ {sizeof(blocking_events[0])}};
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
+ EXPECT_EQ(1u, num_blocking_events);
+ EXPECT_TRUE(blocking_events[0].trigger_context == readable_a_context ||
+ blocking_events[0].trigger_context == readable_b_context);
+ EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);
+ EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals &
+ MOJO_HANDLE_SIGNAL_WRITABLE);
+
+ // Now try arming again, verifying that both contexts are returned.
+ num_blocking_events = kMaxBlockingEvents;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
+ EXPECT_EQ(2u, num_blocking_events);
+ EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);
+ EXPECT_EQ(MOJO_RESULT_OK, blocking_events[1].result);
+ EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals &
+ MOJO_HANDLE_SIGNAL_WRITABLE);
+ EXPECT_TRUE(blocking_events[1].signals_state.satisfied_signals &
+ MOJO_HANDLE_SIGNAL_WRITABLE);
+ EXPECT_TRUE((blocking_events[0].trigger_context == readable_a_context &&
+ blocking_events[1].trigger_context == readable_b_context) ||
+ (blocking_events[0].trigger_context == readable_b_context &&
+ blocking_events[1].trigger_context == 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 trigger whose condition 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([](const MojoTrapEvent&) { NOTREACHED(); });
+ MojoHandle c, d;
+ CreateMessagePipe(&c, &d);
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, c, MOJO_HANDLE_SIGNAL_WRITABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ writable_c_context, nullptr));
+ num_blocking_events = kMaxBlockingEvents;
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_events[0]));
+ EXPECT_EQ(1u, num_blocking_events);
+ EXPECT_EQ(writable_c_context, blocking_events[0].trigger_context);
+ EXPECT_EQ(MOJO_RESULT_OK, blocking_events[0].result);
+ EXPECT_TRUE(blocking_events[0].signals_state.satisfied_signals &
+ MOJO_HANDLE_SIGNAL_WRITABLE);
+
+ // Remove the new trigger and arming should succeed once again.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoRemoveTrigger(t, writable_c_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+ 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(TrapTest, ActivateOtherTriggerFromEventHandler) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ static const char kTestMessageToA[] = "hello a";
+ static const char kTestMessageToB[] = "hello b";
+
+ base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+ TriggerHelper helper;
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+
+ uintptr_t readable_a_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+ EXPECT_EQ("hello a", ReadMessage(a));
+
+ // Re-arm the trap and signal |b|.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+ WriteMessage(a, kTestMessageToB);
+ });
+
+ uintptr_t readable_b_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+ EXPECT_EQ(kTestMessageToB, ReadMessage(b));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+ wait.Signal();
+ });
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_a_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, b, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_b_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ // Send a message to |a|. The relevant trigger should be notified and the
+ // event handler should send a message to |b|, in turn notifying the other
+ // trigger. The second event handler will signal |wait|.
+ WriteMessage(b, kTestMessageToA);
+ wait.Wait();
+}
+
+TEST_F(TrapTest, ActivateSameTriggerFromEventHandler) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ static const char kTestMessageToA[] = "hello a";
+
+ base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+ TriggerHelper helper;
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+
+ int expected_notifications = 10;
+ uintptr_t readable_a_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+ EXPECT_EQ("hello a", ReadMessage(a));
+
+ EXPECT_GT(expected_notifications, 0);
+ expected_notifications -= 1;
+ if (expected_notifications == 0) {
+ wait.Signal();
+ return;
+ } else {
+ // Re-arm the trap and signal |a| again.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+ WriteMessage(b, kTestMessageToA);
+ }
+ });
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_a_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ // Send a message to |a|. When the trigger above is activated, the event
+ // handler will rearm the trap and send another message to |a|. This will
+ // happen until |expected_notifications| reaches 0.
+ WriteMessage(b, kTestMessageToA);
+ wait.Wait();
+}
+
+TEST_F(TrapTest, ImplicitRemoveOtherTriggerWithinEventHandler) {
+ 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 wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+ TriggerHelper helper;
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+
+ uintptr_t readable_a_context = helper.CreateContextWithCancel(
+ [](const MojoTrapEvent&) { NOTREACHED(); }, [&] { wait.Signal(); });
+
+ uintptr_t readable_c_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+ EXPECT_EQ(kTestMessageToC, ReadMessage(c));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ // Must result in exactly ONE notification from the above trigger, 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 trigger should already be
+ // implicitly removed (even though the notification will not have
+ // been invoked yet.)
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ // Nothing interesting should happen as a result of this.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+ });
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_a_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, c, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_c_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ WriteMessage(d, kTestMessageToC);
+ wait.Wait();
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
+}
+
+TEST_F(TrapTest, ExplicitRemoveOtherTriggerWithinEventHandler) {
+ 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 wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+ TriggerHelper helper;
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+
+ uintptr_t readable_a_context =
+ helper.CreateContext([](const MojoTrapEvent&) { NOTREACHED(); });
+
+ uintptr_t readable_c_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+ EXPECT_EQ(kTestMessageToC, ReadMessage(c));
+
+ // Now rearm the trap.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ // Should result in no notifications from the above trigger, because the
+ // trigger will have been removed by the time the event handler can
+ // execute.
+ WriteMessage(b, kTestMessageToA);
+ WriteMessage(b, kTestMessageToA);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoRemoveTrigger(t, readable_a_context, nullptr));
+
+ // Rearming should be fine now.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, 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));
+
+ wait.Signal();
+ });
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_a_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, c, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_c_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ WriteMessage(d, kTestMessageToC);
+ wait.Wait();
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
+}
+
+TEST_F(TrapTest, 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 trigger on |b| using one trap and
+ // triggers on |c| and |d| using another trap.
+ //
+ // A message is written to |d| to activate |c|'s trigger, and the resuling
+ // event handler invocation does the folllowing:
+ // 1. Writes to |a| to eventually activate |b|'s trigger.
+ // 2. Rearms |c|'s trap.
+ // 3. Writes to |d| to eventually activate |c|'s trigger again.
+ //
+ // Meanwhile, |b|'s event handler removes |c|'s trigger altogether before
+ // writing to |c| to activate |d|'s trigger.
+ //
+ // The net result should be that |c|'s trigger only gets activated once (from
+ // the first write to |d| above) and everyone else gets notified as expected.
+
+ MojoHandle b_trap;
+ MojoHandle cd_trap;
+ TriggerHelper helper;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&b_trap));
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&cd_trap));
+
+ base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ uintptr_t readable_d_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+ EXPECT_EQ(kTestMessageToD, ReadMessage(d));
+ wait.Signal();
+ });
+
+ int num_expected_c_notifications = 1;
+ uintptr_t readable_c_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_OK, event.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,
+ MojoArmTrap(cd_trap, nullptr, nullptr, nullptr));
+
+ // Trigger another eventual |readable_c_context| notification.
+ WriteMessage(d, kTestMessageToC);
+ });
+
+ uintptr_t readable_b_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoRemoveTrigger(cd_trap, readable_c_context, nullptr));
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoArmTrap(cd_trap, nullptr, nullptr, nullptr));
+
+ WriteMessage(c, kTestMessageToD);
+ });
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(b_trap, b, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_b_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(cd_trap, c, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_c_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(cd_trap, d, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_d_context, nullptr));
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(b_trap, nullptr, nullptr, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(cd_trap, nullptr, nullptr, nullptr));
+
+ WriteMessage(d, kTestMessageToC);
+ wait.Wait();
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(cd_trap));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b_trap));
+ 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(TrapTest, RemoveSelfWithinEventHandler) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ static const char kTestMessageToA[] = "hey a";
+
+ MojoHandle t;
+ TriggerHelper helper;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+
+ base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+ static uintptr_t readable_a_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+
+ // There should be no problem removing this trigger from its own
+ // notification invocation.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoRemoveTrigger(t, readable_a_context, nullptr));
+ EXPECT_EQ(kTestMessageToA, ReadMessage(a));
+
+ // Arming should fail because there are no longer any registered
+ // triggers on the trap.
+ EXPECT_EQ(MOJO_RESULT_NOT_FOUND,
+ MojoArmTrap(t, 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));
+
+ wait.Signal();
+ });
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_a_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ WriteMessage(b, kTestMessageToA);
+ wait.Wait();
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+}
+
+TEST_F(TrapTest, CloseTrapWithinEventHandler) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ static const char kTestMessageToA1[] = "hey a";
+ static const char kTestMessageToA2[] = "hey a again";
+
+ MojoHandle t;
+ TriggerHelper helper;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+
+ base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+ uintptr_t readable_a_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+ EXPECT_EQ(kTestMessageToA1, ReadMessage(a));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ // There should be no problem closing this trap from its own
+ // notification callback.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+
+ // And these should not trigger more notifications, because |t| has been
+ // closed already.
+ WriteMessage(b, kTestMessageToA2);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+
+ wait.Signal();
+ });
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_a_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ WriteMessage(b, kTestMessageToA1);
+ wait.Wait();
+}
+
+TEST_F(TrapTest, CloseTrapAfterImplicitTriggerRemoval) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ static const char kTestMessageToA[] = "hey a";
+
+ MojoHandle t;
+ TriggerHelper helper;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+
+ base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+ uintptr_t readable_a_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+ EXPECT_EQ(kTestMessageToA, ReadMessage(a));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, 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 trap here.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+
+ wait.Signal();
+ });
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_a_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ WriteMessage(b, kTestMessageToA);
+ wait.Wait();
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+}
+
+TEST_F(TrapTest, OtherThreadRemovesTriggerDuringEventHandler) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ static const char kTestMessageToA[] = "hey a";
+
+ MojoHandle t;
+ TriggerHelper helper;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+
+ 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(
+ [&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_OK, event.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;
+ },
+ [&] {
+ EXPECT_TRUE(callback_done);
+ wait_for_cancellation.Signal();
+ });
+
+ ThreadedRunner runner(base::BindOnce(base::BindLambdaForTesting([&] {
+ wait_for_notification.Wait();
+
+ // Cancel the watch while the notification is still running.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoRemoveTrigger(t, readable_a_context, nullptr));
+
+ wait_for_cancellation.Wait();
+
+ EXPECT_TRUE(callback_done);
+ })));
+ runner.Start();
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_a_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ WriteMessage(b, kTestMessageToA);
+ runner.Join();
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+}
+
+TEST_F(TrapTest, TriggersRemoveEachOtherWithinEventHandlers) {
+ 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_trap;
+ MojoHandle b_trap;
+ TriggerHelper helper;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&a_trap));
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&b_trap));
+
+ // We set up two traps, one triggered on |a| readability and one triggered on
+ // |b| readability. Each removes the other's trigger from within its own event
+ // handler. This should be safe, i.e., it should not deadlock in spite of the
+ // fact that we also guarantee mutually exclusive event handler invocation
+ // (including cancellations) on any given trap.
+ bool a_cancelled = false;
+ bool b_cancelled = false;
+ static uintptr_t readable_b_context;
+ uintptr_t readable_a_context = helper.CreateContextWithCancel(
+ [&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+ EXPECT_EQ(kTestMessageToA, ReadMessage(a));
+ wait_for_a_to_notify.Signal();
+ wait_for_b_to_notify.Wait();
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoRemoveTrigger(b_trap, readable_b_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b_trap));
+ },
+ [&] {
+ a_cancelled = true;
+ wait_for_a_to_cancel.Signal();
+ wait_for_b_to_cancel.Wait();
+ });
+
+ readable_b_context = helper.CreateContextWithCancel(
+ [&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+ EXPECT_EQ(kTestMessageToB, ReadMessage(b));
+ wait_for_b_to_notify.Signal();
+ wait_for_a_to_notify.Wait();
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoRemoveTrigger(a_trap, readable_a_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a_trap));
+ },
+ [&] {
+ b_cancelled = true;
+ wait_for_b_to_cancel.Signal();
+ wait_for_a_to_cancel.Wait();
+ });
+
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(a_trap, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_a_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(a_trap, nullptr, nullptr, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(b_trap, b, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_b_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(b_trap, nullptr, nullptr, nullptr));
+
+ ThreadedRunner runner(base::BindOnce(
+ [](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(TrapTest, AlwaysCancel) {
+ // Basic sanity check to ensure that all possible ways to remove a trigger
+ // result in a final MOJO_RESULT_CANCELLED notification.
+
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ MojoHandle t;
+ TriggerHelper helper;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+
+ base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ auto ignore_event = [](const MojoTrapEvent&) {};
+ auto signal_wait = [&] { wait.Signal(); };
+
+ // Cancel via |MojoRemoveTrigger()|.
+ uintptr_t context = helper.CreateContextWithCancel(ignore_event, signal_wait);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, context,
+ nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoRemoveTrigger(t, context, nullptr));
+ wait.Wait();
+ wait.Reset();
+
+ // Cancel by closing the trigger's watched handle.
+ context = helper.CreateContextWithCancel(ignore_event, signal_wait);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, context,
+ nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+ wait.Wait();
+ wait.Reset();
+
+ // Cancel by closing the trap handle.
+ context = helper.CreateContextWithCancel(ignore_event, signal_wait);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, b, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, context,
+ nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+ wait.Wait();
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+}
+
+TEST_F(TrapTest, ArmFailureCirculation) {
+ // Sanity check to ensure that all ready trigger events will eventually be
+ // returned over a finite number of calls to MojoArmTrap().
+
+ 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 trap and watch all of them for readability.
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateTrap(&ExpectOnlyCancel, nullptr, &t));
+ for (size_t i = 0; i < kNumTestHandles; ++i) {
+ EXPECT_EQ(
+ MOJO_RESULT_OK,
+ MojoAddTrigger(t, handles[i], MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, i, nullptr));
+ }
+
+ // Keep trying to arm |t| until every trigger gets an entry in
+ // |ready_contexts|. If MojoArmTrap() is well-behaved, this should terminate
+ // eventually.
+ std::set<uintptr_t> ready_contexts;
+ while (ready_contexts.size() < kNumTestHandles) {
+ uint32_t num_blocking_events = 1;
+ MojoTrapEvent blocking_event = {sizeof(blocking_event)};
+ EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
+ MojoArmTrap(t, nullptr, &num_blocking_events, &blocking_event));
+ EXPECT_EQ(1u, num_blocking_events);
+ EXPECT_EQ(MOJO_RESULT_OK, blocking_event.result);
+ ready_contexts.insert(blocking_event.trigger_context);
+ }
+
+ for (size_t i = 0; i < kNumTestHandles; ++i)
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handles[i]));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+}
+
+TEST_F(TrapTest, TriggerOnUnsatisfiedSignals) {
+ MojoHandle a, b;
+ CreateMessagePipe(&a, &b);
+
+ base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ TriggerHelper helper;
+ const uintptr_t readable_a_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+ wait.Signal();
+ });
+
+ MojoHandle t;
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ readable_a_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ const char kMessage[] = "this is not a message";
+
+ WriteMessage(b, kMessage);
+ wait.Wait();
+
+ // Now we know |a| is readable. Remove the trigger and add a new one to watch
+ // for a not-readable state.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+ const uintptr_t not_readable_a_context =
+ helper.CreateContext([&](const MojoTrapEvent& event) {
+ EXPECT_EQ(MOJO_RESULT_OK, event.result);
+ wait.Signal();
+ });
+ EXPECT_EQ(MOJO_RESULT_OK, helper.CreateTrap(&t));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_UNSATISFIED,
+ not_readable_a_context, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoArmTrap(t, nullptr, nullptr, nullptr));
+
+ // This should not block, because the event should be signaled by
+ // |not_readable_a_context| when we read the only available message off of
+ // |a|.
+ wait.Reset();
+ EXPECT_EQ(kMessage, ReadMessage(a));
+ wait.Wait();
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(t));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
+}
+
+base::Closure g_do_random_thing_callback;
+
+void ReadAllMessages(const MojoTrapEvent* event) {
+ if (event->result == MOJO_RESULT_OK) {
+ MojoHandle handle = static_cast<MojoHandle>(event->trigger_context);
+ MojoMessageHandle message;
+ while (MojoReadMessage(handle, nullptr, &message) == MOJO_RESULT_OK)
+ MojoDestroyMessage(message);
+ }
+
+ constexpr size_t kNumRandomThingsToDoOnNotify = 5;
+ for (size_t i = 0; i < kNumRandomThingsToDoOnNotify; ++i)
+ g_do_random_thing_callback.Run();
+}
+
+MojoHandle RandomHandle(MojoHandle* handles, size_t size) {
+ return handles[base::RandInt(0, static_cast<int>(size) - 1)];
+}
+
+void DoRandomThing(MojoHandle* traps,
+ size_t num_traps,
+ MojoHandle* watched_handles,
+ size_t num_watched_handles) {
+ switch (base::RandInt(0, 10)) {
+ case 0:
+ MojoClose(RandomHandle(traps, num_traps));
+ break;
+ case 1:
+ MojoClose(RandomHandle(watched_handles, num_watched_handles));
+ break;
+ case 2:
+ case 3:
+ case 4: {
+ MojoMessageHandle message;
+ ASSERT_EQ(MOJO_RESULT_OK, MojoCreateMessage(nullptr, &message));
+ ASSERT_EQ(MOJO_RESULT_OK,
+ MojoSetMessageContext(message, 1, nullptr, nullptr, nullptr));
+ MojoWriteMessage(RandomHandle(watched_handles, num_watched_handles),
+ message, nullptr);
+ break;
+ }
+ case 5:
+ case 6: {
+ MojoHandle t = RandomHandle(traps, num_traps);
+ MojoHandle h = RandomHandle(watched_handles, num_watched_handles);
+ MojoAddTrigger(t, h, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ static_cast<uintptr_t>(h), nullptr);
+ break;
+ }
+ case 7:
+ case 8: {
+ uint32_t num_blocking_events = 1;
+ MojoTrapEvent blocking_event = {sizeof(blocking_event)};
+ if (MojoArmTrap(RandomHandle(traps, num_traps), nullptr,
+ &num_blocking_events,
+ &blocking_event) == MOJO_RESULT_FAILED_PRECONDITION &&
+ blocking_event.result == MOJO_RESULT_OK) {
+ ReadAllMessages(&blocking_event);
+ }
+ break;
+ }
+ case 9:
+ case 10: {
+ MojoHandle t = RandomHandle(traps, num_traps);
+ MojoHandle h = RandomHandle(watched_handles, num_watched_handles);
+ MojoRemoveTrigger(t, static_cast<uintptr_t>(h), nullptr);
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+TEST_F(TrapTest, ConcurrencyStressTest) {
+ // Regression test for https://crbug.com/740044. Exercises racy usage of the
+ // trap API to weed out potential crashes.
+
+ constexpr size_t kNumTraps = 50;
+ constexpr size_t kNumWatchedHandles = 50;
+ static_assert(kNumWatchedHandles % 2 == 0, "Invalid number of test handles.");
+
+ constexpr size_t kNumThreads = 10;
+ static constexpr size_t kNumOperationsPerThread = 400;
+
+ MojoHandle traps[kNumTraps];
+ MojoHandle watched_handles[kNumWatchedHandles];
+ g_do_random_thing_callback = base::BindRepeating(
+ &DoRandomThing, traps, kNumTraps, watched_handles, kNumWatchedHandles);
+
+ for (size_t i = 0; i < kNumTraps; ++i)
+ MojoCreateTrap(&ReadAllMessages, nullptr, &traps[i]);
+ for (size_t i = 0; i < kNumWatchedHandles; i += 2)
+ CreateMessagePipe(&watched_handles[i], &watched_handles[i + 1]);
+
+ std::unique_ptr<ThreadedRunner> threads[kNumThreads];
+ for (size_t i = 0; i < kNumThreads; ++i) {
+ threads[i] = std::make_unique<ThreadedRunner>(base::BindOnce([] {
+ for (size_t i = 0; i < kNumOperationsPerThread; ++i)
+ g_do_random_thing_callback.Run();
+ }));
+ threads[i]->Start();
+ }
+ for (size_t i = 0; i < kNumThreads; ++i)
+ threads[i]->Join();
+ for (size_t i = 0; i < kNumTraps; ++i)
+ MojoClose(traps[i]);
+ for (size_t i = 0; i < kNumWatchedHandles; ++i)
+ MojoClose(watched_handles[i]);
+}
+
+} // namespace
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/user_message_impl.cc b/mojo/core/user_message_impl.cc
new file mode 100644
index 0000000000..9cb8284315
--- /dev/null
+++ b/mojo/core/user_message_impl.cc
@@ -0,0 +1,686 @@
+// 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/core/user_message_impl.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/atomicops.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros_local.h"
+#include "base/no_destructor.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/numerics/safe_math.h"
+// #include "base/trace_event/memory_allocator_dump.h"
+// #include "base/trace_event/memory_dump_manager.h"
+// #include "base/trace_event/memory_dump_provider.h"
+// #include "base/trace_event/trace_event.h"
+#include "mojo/core/core.h"
+#include "mojo/core/node_channel.h"
+#include "mojo/core/node_controller.h"
+#include "mojo/core/ports/event.h"
+#include "mojo/core/ports/message_filter.h"
+#include "mojo/core/ports/node.h"
+#include "mojo/public/c/system/types.h"
+
+namespace mojo {
+namespace core {
+
+namespace {
+
+// The minimum amount of memory to allocate for a new serialized message buffer.
+// This should be sufficiently large such that most seiralized messages do not
+// incur any reallocations as they're expanded to full size.
+const uint32_t kMinimumPayloadBufferSize = 128;
+
+// Indicates whether handle serialization failure should be emulated in testing.
+bool g_always_fail_handle_serialization = false;
+
+#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)
+
+static_assert(sizeof(MessageHeader) % 8 == 0, "Invalid MessageHeader size.");
+static_assert(sizeof(DispatcherHeader) % 8 == 0,
+ "Invalid DispatcherHeader size.");
+
+// Creates a new Channel message with sufficient storage for |num_bytes| user
+// message payload and all |dispatchers| given. If |original_message| is not
+// null, its contents are copied and extended by the other parameters given
+// here.
+MojoResult CreateOrExtendSerializedEventMessage(
+ ports::UserMessageEvent* event,
+ size_t payload_size,
+ size_t payload_buffer_size,
+ const Dispatcher::DispatcherInTransit* new_dispatchers,
+ size_t num_new_dispatchers,
+ Channel::MessagePtr* out_message,
+ void** out_header,
+ size_t* out_header_size,
+ void** out_user_payload) {
+ // A structure for tracking information about every Dispatcher that will be
+ // serialized into the message. This is NOT part of the message itself.
+ struct DispatcherInfo {
+ uint32_t num_bytes;
+ uint32_t num_ports;
+ uint32_t num_handles;
+ };
+
+ size_t original_header_size = sizeof(MessageHeader);
+ size_t original_num_ports = 0;
+ size_t original_num_handles = 0;
+ size_t original_payload_size = 0;
+ MessageHeader* original_header = nullptr;
+ void* original_user_payload = nullptr;
+ Channel::MessagePtr original_message;
+ if (*out_message) {
+ original_message = std::move(*out_message);
+ original_header = static_cast<MessageHeader*>(*out_header);
+ original_header_size = *out_header_size;
+ original_num_ports = event->num_ports();
+ original_num_handles = original_message->num_handles();
+ original_user_payload = *out_user_payload;
+ original_payload_size =
+ original_message->payload_size() -
+ (static_cast<char*>(original_user_payload) -
+ static_cast<char*>(original_message->mutable_payload()));
+ }
+
+ // This is only the base header size. It will grow as we accumulate the
+ // size of serialized state for each dispatcher.
+ base::CheckedNumeric<size_t> safe_header_size = num_new_dispatchers;
+ safe_header_size *= sizeof(DispatcherHeader);
+ safe_header_size += original_header_size;
+ size_t header_size = safe_header_size.ValueOrDie();
+ size_t num_new_ports = 0;
+ size_t num_new_handles = 0;
+ std::vector<DispatcherInfo> new_dispatcher_info(num_new_dispatchers);
+ for (size_t i = 0; i < num_new_dispatchers; ++i) {
+ Dispatcher* d = new_dispatchers[i].dispatcher.get();
+ d->StartSerialize(&new_dispatcher_info[i].num_bytes,
+ &new_dispatcher_info[i].num_ports,
+ &new_dispatcher_info[i].num_handles);
+ header_size += new_dispatcher_info[i].num_bytes;
+ num_new_ports += new_dispatcher_info[i].num_ports;
+ num_new_handles += new_dispatcher_info[i].num_handles;
+ }
+
+ size_t num_ports = original_num_ports + num_new_ports;
+ size_t num_handles = original_num_handles + num_new_handles;
+
+ // We now have enough information to fully allocate the message storage.
+ if (num_ports > event->num_ports())
+ event->ReservePorts(num_ports);
+ const size_t event_size = event->GetSerializedSize();
+ const size_t total_size = event_size + header_size + payload_size;
+ const size_t total_buffer_size =
+ event_size + header_size + payload_buffer_size;
+ void* data;
+ Channel::MessagePtr message = NodeChannel::CreateEventMessage(
+ total_buffer_size, total_size, &data, num_handles);
+ auto* header = reinterpret_cast<MessageHeader*>(static_cast<uint8_t*>(data) +
+ event_size);
+
+ // 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.
+ DispatcherHeader* new_dispatcher_headers;
+ char* new_dispatcher_data;
+ size_t total_num_dispatchers = num_new_dispatchers;
+ std::vector<PlatformHandle> handles;
+ if (original_message) {
+ DCHECK(original_header);
+ size_t original_dispatcher_headers_size =
+ original_header->num_dispatchers * sizeof(DispatcherHeader);
+ memcpy(header, original_header,
+ original_dispatcher_headers_size + sizeof(MessageHeader));
+ new_dispatcher_headers = reinterpret_cast<DispatcherHeader*>(
+ reinterpret_cast<uint8_t*>(header + 1) +
+ original_dispatcher_headers_size);
+ total_num_dispatchers += original_header->num_dispatchers;
+ size_t total_dispatcher_headers_size =
+ total_num_dispatchers * sizeof(DispatcherHeader);
+ char* original_dispatcher_data =
+ reinterpret_cast<char*>(original_header + 1) +
+ original_dispatcher_headers_size;
+ char* dispatcher_data =
+ reinterpret_cast<char*>(header + 1) + total_dispatcher_headers_size;
+ size_t original_dispatcher_data_size = original_header_size -
+ sizeof(MessageHeader) -
+ original_dispatcher_headers_size;
+ memcpy(dispatcher_data, original_dispatcher_data,
+ original_dispatcher_data_size);
+ new_dispatcher_data = dispatcher_data + original_dispatcher_data_size;
+ auto handles_in_transit = original_message->TakeHandles();
+ if (!handles_in_transit.empty()) {
+ handles.resize(num_handles);
+ for (size_t i = 0; i < handles_in_transit.size(); ++i)
+ handles[i] = handles_in_transit[i].TakeHandle();
+ }
+ memcpy(reinterpret_cast<char*>(header) + header_size,
+ reinterpret_cast<char*>(original_header) + original_header_size,
+ original_payload_size);
+ } else {
+ new_dispatcher_headers = reinterpret_cast<DispatcherHeader*>(header + 1);
+ // Serialized dispatcher state immediately follows the series of
+ // DispatcherHeaders.
+ new_dispatcher_data =
+ reinterpret_cast<char*>(new_dispatcher_headers + num_new_dispatchers);
+ }
+
+ if (handles.empty() && num_new_handles)
+ handles.resize(num_new_handles);
+
+ header->num_dispatchers =
+ base::CheckedNumeric<uint32_t>(total_num_dispatchers).ValueOrDie();
+
+ // |header_size| is the total number of bytes preceding the message payload,
+ // including all dispatcher headers and serialized dispatcher state.
+ if (!base::IsValueInRangeForNumericType<uint32_t>(header_size))
+ return MOJO_RESULT_OUT_OF_RANGE;
+
+ header->header_size = static_cast<uint32_t>(header_size);
+
+ if (num_new_dispatchers > 0) {
+ size_t port_index = original_num_ports;
+ size_t handle_index = original_num_handles;
+ bool fail = false;
+ for (size_t i = 0; i < num_new_dispatchers; ++i) {
+ Dispatcher* d = new_dispatchers[i].dispatcher.get();
+ DispatcherHeader* dh = &new_dispatcher_headers[i];
+ const DispatcherInfo& info = new_dispatcher_info[i];
+
+ // Fill in the header for this dispatcher.
+ dh->type = static_cast<int32_t>(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 (g_always_fail_handle_serialization ||
+ !d->EndSerialize(
+ static_cast<void*>(new_dispatcher_data),
+ event->ports() + port_index,
+ !handles.empty() ? handles.data() + handle_index : nullptr)) {
+ fail = true;
+ break;
+ }
+
+ new_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.
+ for (auto& handle : handles)
+ handle.release();
+
+ // Leave the original message in place on failure if applicable.
+ if (original_message)
+ *out_message = std::move(original_message);
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ }
+
+ // Take ownership of all the handles and move them into message storage.
+ message->SetHandles(std::move(handles));
+ }
+
+ *out_message = std::move(message);
+ *out_header = header;
+ *out_header_size = header_size;
+ *out_user_payload = reinterpret_cast<uint8_t*>(header) + header_size;
+ return MOJO_RESULT_OK;
+}
+
+base::subtle::Atomic32 g_message_count = 0;
+
+void IncrementMessageCount() {
+ base::subtle::NoBarrier_AtomicIncrement(&g_message_count, 1);
+}
+
+void DecrementMessageCount() {
+ base::subtle::NoBarrier_AtomicIncrement(&g_message_count, -1);
+}
+
+// class MessageMemoryDumpProvider : public base::trace_event::MemoryDumpProvider {
+// public:
+// MessageMemoryDumpProvider() {
+// base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
+// this, "MojoMessages", nullptr);
+// }
+
+// ~MessageMemoryDumpProvider() override {
+// base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
+// this);
+// }
+
+// private:
+// // base::trace_event::MemoryDumpProvider:
+// bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
+// base::trace_event::ProcessMemoryDump* pmd) override {
+// auto* dump = pmd->CreateAllocatorDump("mojo/messages");
+// dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameObjectCount,
+// base::trace_event::MemoryAllocatorDump::kUnitsObjects,
+// base::subtle::NoBarrier_Load(&g_message_count));
+// return true;
+// }
+
+// DISALLOW_COPY_AND_ASSIGN(MessageMemoryDumpProvider);
+// };
+
+// void EnsureMemoryDumpProviderExists() {
+// static base::NoDestructor<MessageMemoryDumpProvider> provider;
+// ALLOW_UNUSED_LOCAL(provider);
+// }
+
+} // namespace
+
+// static
+const ports::UserMessage::TypeInfo UserMessageImpl::kUserMessageTypeInfo = {};
+
+UserMessageImpl::~UserMessageImpl() {
+ if (HasContext() && context_destructor_) {
+ DCHECK(!channel_message_);
+ DCHECK(!has_serialized_handles_);
+ context_destructor_(context_);
+ } else if (IsSerialized() && has_serialized_handles_) {
+ // Ensure that any handles still serialized within this message are
+ // extracted and closed so they don't leak.
+ std::vector<MojoHandle> handles(num_handles());
+ MojoResult result =
+ ExtractSerializedHandles(ExtractBadHandlePolicy::kSkip, handles.data());
+ if (result == MOJO_RESULT_OK) {
+ for (auto handle : handles) {
+ if (handle != MOJO_HANDLE_INVALID)
+ Core::Get()->Close(handle);
+ }
+ }
+
+ if (!pending_handle_attachments_.empty()) {
+ Core::Get()->ReleaseDispatchersForTransit(pending_handle_attachments_,
+ false);
+ for (const auto& dispatcher : pending_handle_attachments_)
+ Core::Get()->Close(dispatcher.local_handle);
+ }
+ }
+
+ DecrementMessageCount();
+}
+
+// static
+std::unique_ptr<ports::UserMessageEvent>
+UserMessageImpl::CreateEventForNewMessage() {
+ auto message_event = std::make_unique<ports::UserMessageEvent>(0);
+ message_event->AttachMessage(
+ base::WrapUnique(new UserMessageImpl(message_event.get())));
+ return message_event;
+}
+
+// static
+MojoResult UserMessageImpl::CreateEventForNewSerializedMessage(
+ uint32_t num_bytes,
+ const Dispatcher::DispatcherInTransit* dispatchers,
+ uint32_t num_dispatchers,
+ std::unique_ptr<ports::UserMessageEvent>* out_event) {
+ Channel::MessagePtr channel_message;
+ void* header = nullptr;
+ void* user_payload = nullptr;
+ auto event = std::make_unique<ports::UserMessageEvent>(0);
+ size_t header_size = 0;
+ MojoResult rv = CreateOrExtendSerializedEventMessage(
+ event.get(), num_bytes, num_bytes, dispatchers, num_dispatchers,
+ &channel_message, &header, &header_size, &user_payload);
+ if (rv != MOJO_RESULT_OK)
+ return rv;
+ event->AttachMessage(base::WrapUnique(
+ new UserMessageImpl(event.get(), std::move(channel_message), header,
+ header_size, user_payload, num_bytes)));
+ *out_event = std::move(event);
+ return MOJO_RESULT_OK;
+}
+
+// static
+std::unique_ptr<UserMessageImpl> UserMessageImpl::CreateFromChannelMessage(
+ ports::UserMessageEvent* message_event,
+ Channel::MessagePtr channel_message,
+ void* payload,
+ size_t payload_size) {
+ DCHECK(channel_message);
+ if (payload_size < sizeof(MessageHeader))
+ return nullptr;
+
+ auto* header = static_cast<MessageHeader*>(payload);
+ const size_t header_size = header->header_size;
+ if (header_size > payload_size)
+ return nullptr;
+
+ void* user_payload = static_cast<uint8_t*>(payload) + header_size;
+ const size_t user_payload_size = payload_size - header_size;
+ return base::WrapUnique(
+ new UserMessageImpl(message_event, std::move(channel_message), header,
+ header_size, user_payload, user_payload_size));
+}
+
+// static
+Channel::MessagePtr UserMessageImpl::FinalizeEventMessage(
+ std::unique_ptr<ports::UserMessageEvent> message_event) {
+ auto* message = message_event->GetMessage<UserMessageImpl>();
+ DCHECK(message->IsSerialized());
+
+ if (!message->is_committed_)
+ return nullptr;
+
+ Channel::MessagePtr channel_message = std::move(message->channel_message_);
+ message->user_payload_ = nullptr;
+ message->user_payload_size_ = 0;
+
+ // Serialize the UserMessageEvent into the front of the message payload where
+ // there is already space reserved for it.
+ if (channel_message) {
+ void* data;
+ size_t size;
+ NodeChannel::GetEventMessageData(channel_message.get(), &data, &size);
+ message_event->Serialize(data);
+ }
+
+ return channel_message;
+}
+
+size_t UserMessageImpl::user_payload_capacity() const {
+ DCHECK(IsSerialized());
+ const size_t user_payload_offset =
+ static_cast<uint8_t*>(user_payload_) -
+ static_cast<const uint8_t*>(channel_message_->payload());
+ const size_t message_capacity = channel_message_->capacity();
+ DCHECK_LE(user_payload_offset, message_capacity);
+ return message_capacity - user_payload_offset;
+}
+
+size_t UserMessageImpl::num_handles() const {
+ DCHECK(IsSerialized());
+ DCHECK(header_);
+ return static_cast<const MessageHeader*>(header_)->num_dispatchers;
+}
+
+MojoResult UserMessageImpl::SetContext(
+ uintptr_t context,
+ MojoMessageContextSerializer serializer,
+ MojoMessageContextDestructor destructor) {
+ if (!context && (serializer || destructor))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (context && HasContext())
+ return MOJO_RESULT_ALREADY_EXISTS;
+ if (IsSerialized())
+ return MOJO_RESULT_FAILED_PRECONDITION;
+ context_ = context;
+ context_serializer_ = serializer;
+ context_destructor_ = destructor;
+ return MOJO_RESULT_OK;
+}
+
+MojoResult UserMessageImpl::AppendData(uint32_t additional_payload_size,
+ const MojoHandle* handles,
+ uint32_t num_handles) {
+ if (HasContext())
+ return MOJO_RESULT_FAILED_PRECONDITION;
+
+ std::vector<Dispatcher::DispatcherInTransit> dispatchers;
+ if (num_handles > 0) {
+ MojoResult acquire_result = Core::Get()->AcquireDispatchersForTransit(
+ handles, num_handles, &dispatchers);
+ if (acquire_result != MOJO_RESULT_OK)
+ return acquire_result;
+ }
+
+ if (!IsSerialized()) {
+ // First data for this message.
+ Channel::MessagePtr channel_message;
+ MojoResult rv = CreateOrExtendSerializedEventMessage(
+ message_event_, additional_payload_size,
+ std::max(additional_payload_size, kMinimumPayloadBufferSize),
+ dispatchers.data(), num_handles, &channel_message, &header_,
+ &header_size_, &user_payload_);
+ if (num_handles > 0) {
+ Core::Get()->ReleaseDispatchersForTransit(dispatchers,
+ rv == MOJO_RESULT_OK);
+ }
+ if (rv != MOJO_RESULT_OK)
+ return MOJO_RESULT_ABORTED;
+
+ user_payload_size_ = additional_payload_size;
+ channel_message_ = std::move(channel_message);
+ has_serialized_handles_ = true;
+ } else {
+ // Extend the existing message payload.
+
+ // In order to avoid rather expensive message resizing on every handle
+ // attachment operation, we merely lock and prepare the handle for transit
+ // here, deferring serialization until |CommitSize()|.
+ std::copy(dispatchers.begin(), dispatchers.end(),
+ std::back_inserter(pending_handle_attachments_));
+
+ if (additional_payload_size) {
+ size_t header_offset =
+ static_cast<uint8_t*>(header_) -
+ static_cast<const uint8_t*>(channel_message_->payload());
+ size_t user_payload_offset =
+ static_cast<uint8_t*>(user_payload_) -
+ static_cast<const uint8_t*>(channel_message_->payload());
+ channel_message_->ExtendPayload(user_payload_offset + user_payload_size_ +
+ additional_payload_size);
+ header_ = static_cast<uint8_t*>(channel_message_->mutable_payload()) +
+ header_offset;
+ user_payload_ =
+ static_cast<uint8_t*>(channel_message_->mutable_payload()) +
+ user_payload_offset;
+ user_payload_size_ += additional_payload_size;
+ }
+ }
+
+ return MOJO_RESULT_OK;
+}
+
+MojoResult UserMessageImpl::CommitSize() {
+ if (!IsSerialized())
+ return MOJO_RESULT_FAILED_PRECONDITION;
+
+ if (is_committed_)
+ return MOJO_RESULT_OK;
+
+ if (!pending_handle_attachments_.empty()) {
+ CreateOrExtendSerializedEventMessage(
+ message_event_, user_payload_size_, user_payload_size_,
+ pending_handle_attachments_.data(), pending_handle_attachments_.size(),
+ &channel_message_, &header_, &header_size_, &user_payload_);
+ Core::Get()->ReleaseDispatchersForTransit(pending_handle_attachments_,
+ true);
+ pending_handle_attachments_.clear();
+ }
+
+ is_committed_ = true;
+ return MOJO_RESULT_OK;
+}
+
+MojoResult UserMessageImpl::SerializeIfNecessary() {
+ if (IsSerialized())
+ return MOJO_RESULT_FAILED_PRECONDITION;
+
+ DCHECK(HasContext());
+ DCHECK(!has_serialized_handles_);
+ if (!context_serializer_)
+ return MOJO_RESULT_NOT_FOUND;
+
+ uintptr_t context = context_;
+ context_ = 0;
+ context_serializer_(reinterpret_cast<MojoMessageHandle>(message_event_),
+ context);
+
+ if (context_destructor_)
+ context_destructor_(context);
+
+ has_serialized_handles_ = true;
+ return MOJO_RESULT_OK;
+}
+
+MojoResult UserMessageImpl::ExtractSerializedHandles(
+ ExtractBadHandlePolicy bad_handle_policy,
+ MojoHandle* handles) {
+ if (!IsSerialized())
+ return MOJO_RESULT_FAILED_PRECONDITION;
+
+ if (!has_serialized_handles_)
+ return MOJO_RESULT_NOT_FOUND;
+
+ const MessageHeader* header = static_cast<const MessageHeader*>(header_);
+ const DispatcherHeader* dispatcher_headers =
+ reinterpret_cast<const DispatcherHeader*>(header + 1);
+
+ if (header->num_dispatchers > std::numeric_limits<uint16_t>::max())
+ return MOJO_RESULT_ABORTED;
+
+ if (header->num_dispatchers == 0)
+ return MOJO_RESULT_OK;
+
+ has_serialized_handles_ = false;
+
+ std::vector<Dispatcher::DispatcherInTransit> 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_ABORTED;
+ const char* dispatcher_data = reinterpret_cast<const char*>(
+ dispatcher_headers + header->num_dispatchers);
+ size_t port_index = 0;
+ size_t platform_handle_index = 0;
+ std::vector<PlatformHandleInTransit> handles_in_transit =
+ channel_message_->TakeHandles();
+ std::vector<PlatformHandle> msg_handles(handles_in_transit.size());
+ for (size_t i = 0; i < handles_in_transit.size(); ++i) {
+ DCHECK(!handles_in_transit[i].owning_process().is_valid());
+ msg_handles[i] = handles_in_transit[i].TakeHandle();
+ }
+ for (size_t i = 0; i < header->num_dispatchers; ++i) {
+ const DispatcherHeader& dh = dispatcher_headers[i];
+ auto type = static_cast<Dispatcher::Type>(dh.type);
+
+ base::CheckedNumeric<size_t> next_payload_index = data_payload_index;
+ next_payload_index += dh.num_bytes;
+ if (!next_payload_index.IsValid() ||
+ header->header_size < next_payload_index.ValueOrDie()) {
+ return MOJO_RESULT_ABORTED;
+ }
+
+ base::CheckedNumeric<size_t> next_port_index = port_index;
+ next_port_index += dh.num_ports;
+ if (!next_port_index.IsValid() ||
+ message_event_->num_ports() < next_port_index.ValueOrDie()) {
+ return MOJO_RESULT_ABORTED;
+ }
+
+ base::CheckedNumeric<size_t> next_platform_handle_index =
+ platform_handle_index;
+ next_platform_handle_index += dh.num_platform_handles;
+ if (!next_platform_handle_index.IsValid() ||
+ msg_handles.size() < next_platform_handle_index.ValueOrDie()) {
+ return MOJO_RESULT_ABORTED;
+ }
+
+ PlatformHandle* out_handles =
+ !msg_handles.empty() ? msg_handles.data() + platform_handle_index
+ : nullptr;
+ dispatchers[i].dispatcher = Dispatcher::Deserialize(
+ type, dispatcher_data, dh.num_bytes,
+ message_event_->ports() + port_index, dh.num_ports, out_handles,
+ dh.num_platform_handles);
+ if (!dispatchers[i].dispatcher &&
+ bad_handle_policy == ExtractBadHandlePolicy::kAbort) {
+ return MOJO_RESULT_ABORTED;
+ }
+
+ dispatcher_data += dh.num_bytes;
+ data_payload_index = next_payload_index.ValueOrDie();
+ port_index = next_port_index.ValueOrDie();
+ platform_handle_index = next_platform_handle_index.ValueOrDie();
+ }
+
+ if (!Core::Get()->AddDispatchersFromTransit(dispatchers, handles))
+ return MOJO_RESULT_ABORTED;
+
+ return MOJO_RESULT_OK;
+}
+
+// static
+void UserMessageImpl::FailHandleSerializationForTesting(bool fail) {
+ g_always_fail_handle_serialization = fail;
+}
+
+UserMessageImpl::UserMessageImpl(ports::UserMessageEvent* message_event)
+ : ports::UserMessage(&kUserMessageTypeInfo), message_event_(message_event) {
+ // EnsureMemoryDumpProviderExists();
+ IncrementMessageCount();
+}
+
+UserMessageImpl::UserMessageImpl(ports::UserMessageEvent* message_event,
+ Channel::MessagePtr channel_message,
+ void* header,
+ size_t header_size,
+ void* user_payload,
+ size_t user_payload_size)
+ : ports::UserMessage(&kUserMessageTypeInfo),
+ message_event_(message_event),
+ channel_message_(std::move(channel_message)),
+ has_serialized_handles_(true),
+ is_committed_(true),
+ header_(header),
+ header_size_(header_size),
+ user_payload_(user_payload),
+ user_payload_size_(user_payload_size) {
+ // EnsureMemoryDumpProviderExists();
+ IncrementMessageCount();
+}
+
+bool UserMessageImpl::WillBeRoutedExternally() {
+ MojoResult result = SerializeIfNecessary();
+ return result == MOJO_RESULT_OK || result == MOJO_RESULT_FAILED_PRECONDITION;
+}
+
+size_t UserMessageImpl::GetSizeIfSerialized() const {
+ if (!IsSerialized())
+ return 0;
+ return user_payload_size_;
+}
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/user_message_impl.h b/mojo/core/user_message_impl.h
new file mode 100644
index 0000000000..c6a15f98c7
--- /dev/null
+++ b/mojo/core/user_message_impl.h
@@ -0,0 +1,218 @@
+// 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_CORE_USER_MESSAGE_IMPL_H_
+#define MOJO_CORE_USER_MESSAGE_IMPL_H_
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "mojo/core/channel.h"
+#include "mojo/core/dispatcher.h"
+#include "mojo/core/ports/event.h"
+#include "mojo/core/ports/name.h"
+#include "mojo/core/ports/port_ref.h"
+#include "mojo/core/ports/user_message.h"
+#include "mojo/core/system_impl_export.h"
+#include "mojo/public/c/system/message_pipe.h"
+#include "mojo/public/c/system/types.h"
+
+namespace mojo {
+namespace core {
+
+// UserMessageImpl is the sole implementation of ports::UserMessage used to
+// attach message data to any ports::UserMessageEvent.
+//
+// A UserMessageImpl may be either serialized or unserialized. Unserialized
+// instances are serialized lazily only when necessary, i.e., if and when
+// Serialize() is called to obtain a serialized message for wire transfer.
+class MOJO_SYSTEM_IMPL_EXPORT UserMessageImpl : public ports::UserMessage {
+ public:
+ static const TypeInfo kUserMessageTypeInfo;
+
+ // Determines how ExtractSerializedHandles should behave when it encounters an
+ // unrecoverable serialized handle.
+ enum ExtractBadHandlePolicy {
+ // Continue extracting handles upon encountering a bad handle. The bad
+ // handle will be extracted with an invalid handle value.
+ kSkip,
+
+ // Abort the extraction process, leaving any valid serialized handles still
+ // in the message.
+ kAbort,
+ };
+
+ ~UserMessageImpl() override;
+
+ // Creates a new ports::UserMessageEvent with an attached UserMessageImpl.
+ static std::unique_ptr<ports::UserMessageEvent> CreateEventForNewMessage();
+
+ // Creates a new ports::UserMessageEvent with an attached serialized
+ // UserMessageImpl. May fail iff one or more |dispatchers| fails to serialize
+ // (e.g. due to it being in an invalid state.)
+ //
+ // Upon success, MOJO_RESULT_OK is returned and the new UserMessageEvent is
+ // stored in |*out_event|.
+ static MojoResult CreateEventForNewSerializedMessage(
+ uint32_t num_bytes,
+ const Dispatcher::DispatcherInTransit* dispatchers,
+ uint32_t num_dispatchers,
+ std::unique_ptr<ports::UserMessageEvent>* out_event);
+
+ // Creates a new UserMessageImpl from an existing serialized message buffer
+ // which was read from a Channel. Takes ownership of |channel_message|.
+ // |payload| and |payload_size| represent the range of bytes within
+ // |channel_message| which should be parsed by this call.
+ static std::unique_ptr<UserMessageImpl> CreateFromChannelMessage(
+ ports::UserMessageEvent* message_event,
+ Channel::MessagePtr channel_message,
+ void* payload,
+ size_t payload_size);
+
+ // Extracts the serialized Channel::Message from the UserMessageEvent in
+ // |event|. |event| must have a serialized UserMessageImpl instance attached.
+ // |message_event| is serialized into the front of the message payload before
+ // returning.
+ static Channel::MessagePtr FinalizeEventMessage(
+ std::unique_ptr<ports::UserMessageEvent> event);
+
+ bool HasContext() const { return context_ != 0; }
+
+ uintptr_t context() const { return context_; }
+
+ bool IsSerialized() const {
+ if (HasContext()) {
+ DCHECK(!channel_message_);
+ return false;
+ }
+
+ return !!channel_message_;
+ }
+
+ bool IsTransmittable() const { return !IsSerialized() || is_committed_; }
+
+ void* user_payload() {
+ DCHECK(IsSerialized());
+ return user_payload_;
+ }
+
+ const void* user_payload() const {
+ DCHECK(IsSerialized());
+ return user_payload_;
+ }
+
+ size_t user_payload_size() const {
+ DCHECK(IsSerialized());
+ return user_payload_size_;
+ }
+
+ size_t user_payload_capacity() const;
+
+ size_t num_handles() const;
+
+ void set_source_node(const ports::NodeName& name) { source_node_ = name; }
+ const ports::NodeName& source_node() const { return source_node_; }
+
+ MojoResult SetContext(uintptr_t context,
+ MojoMessageContextSerializer serializer,
+ MojoMessageContextDestructor destructor);
+ MojoResult AppendData(uint32_t additional_payload_size,
+ const MojoHandle* handles,
+ uint32_t num_handles);
+ MojoResult CommitSize();
+
+ // If this message is not already serialized, this serializes it.
+ MojoResult SerializeIfNecessary();
+
+ // Extracts handles from this (serialized) message.
+ //
+ // Returns |MOJO_RESULT_OK|
+ // if sucessful, |MOJO_RESULT_FAILED_PRECONDITION| if this isn't a serialized
+ // message, |MOJO_RESULT_NOT_FOUND| if all serialized handles have already
+ // been extracted, or |MOJO_RESULT_ABORTED| if one or more handles failed
+ // extraction.
+ //
+ // On success, |handles| is populated with |num_handles()| extracted handles,
+ // whose ownership is thereby transferred to the caller.
+ MojoResult ExtractSerializedHandles(ExtractBadHandlePolicy bad_handle_policy,
+ MojoHandle* handles);
+
+ // Forces all handle serialization to fail. Serialization can fail in
+ // production for a few different reasons (e.g. file descriptor exhaustion
+ // when duping data pipe buffer handles) which may be difficult to control in
+ // testing environments. This forces the common serialization code path to
+ // always behave as if the underlying implementation signaled failure,
+ // allowing tests to exercise those cases.
+ static void FailHandleSerializationForTesting(bool fail);
+
+ private:
+ // Creates an unserialized UserMessageImpl with an associated |context| and
+ // |thunks|. If the message is ever going to be routed to another node (see
+ // |WillBeRoutedExternally()| below), it will be serialized at that time using
+ // operations provided by |thunks|.
+ UserMessageImpl(ports::UserMessageEvent* message_event);
+
+ // Creates a serialized UserMessageImpl backed by an existing Channel::Message
+ // object. |header| and |user_payload| must be pointers into
+ // |channel_message|'s own storage, and |user_payload_size| is the number of
+ // bytes comprising the user message contents at |user_payload|.
+ UserMessageImpl(ports::UserMessageEvent* message_event,
+ Channel::MessagePtr channel_message,
+ void* header,
+ size_t header_size,
+ void* user_payload,
+ size_t user_payload_size);
+
+ // UserMessage:
+ bool WillBeRoutedExternally() override;
+ size_t GetSizeIfSerialized() const override;
+
+ // The event which owns this serialized message. Not owned.
+ ports::UserMessageEvent* const message_event_;
+
+ // Unserialized message state.
+ uintptr_t context_ = 0;
+ MojoMessageContextSerializer context_serializer_ = nullptr;
+ MojoMessageContextDestructor context_destructor_ = nullptr;
+
+ // Serialized message contents. May be null if this is not a serialized
+ // message.
+ Channel::MessagePtr channel_message_;
+
+ // Indicates whether any handles serialized within |channel_message_| have
+ // yet to be extracted.
+ bool has_serialized_handles_ = false;
+
+ // Indicates whether the serialized message's contents (if any) have been
+ // committed yet.
+ bool is_committed_ = false;
+
+ // Only valid if |channel_message_| is non-null. |header_| is the address
+ // of the UserMessageImpl's internal MessageHeader structure within the
+ // serialized message buffer. |user_payload_| is the address of the first byte
+ // after any serialized dispatchers, with the payload comprising the remaining
+ // |user_payload_size_| bytes of the message.
+ void* header_ = nullptr;
+ size_t header_size_ = 0;
+ void* user_payload_ = nullptr;
+ size_t user_payload_size_ = 0;
+
+ // Handles which have been attached to the serialized message but which have
+ // not yet been serialized.
+ std::vector<Dispatcher::DispatcherInTransit> pending_handle_attachments_;
+
+ // The node name from which this message was received, iff it came from
+ // out-of-process and the source is known.
+ ports::NodeName source_node_ = ports::kInvalidNodeName;
+
+ DISALLOW_COPY_AND_ASSIGN(UserMessageImpl);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_USER_MESSAGE_IMPL_H_
diff --git a/mojo/core/watch.cc b/mojo/core/watch.cc
new file mode 100644
index 0000000000..996f0a7ca2
--- /dev/null
+++ b/mojo/core/watch.cc
@@ -0,0 +1,90 @@
+// 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/core/watch.h"
+
+#include "mojo/core/request_context.h"
+#include "mojo/core/watcher_dispatcher.h"
+
+namespace mojo {
+namespace core {
+
+Watch::Watch(const scoped_refptr<WatcherDispatcher>& watcher,
+ const scoped_refptr<Dispatcher>& dispatcher,
+ uintptr_t context,
+ MojoHandleSignals signals,
+ MojoTriggerCondition condition)
+ : watcher_(watcher),
+ dispatcher_(dispatcher),
+ context_(context),
+ signals_(signals),
+ condition_(condition) {}
+
+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();
+ const bool notify_success =
+ (state.satisfies_any(signals_) &&
+ condition_ == MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED) ||
+ (!state.satisfies_all(signals_) &&
+ condition_ == MOJO_TRIGGER_CONDITION_SIGNALS_UNSATISFIED);
+ if (notify_success) {
+ rv = MOJO_RESULT_OK;
+ if (allowed_to_call_callback && rv != last_known_result_) {
+ request_context->AddWatchNotifyFinalizer(this, MOJO_RESULT_OK, state);
+ }
+ } else if (condition_ == MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED &&
+ !state.can_satisfy_any(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<const MojoHandleSignalsState*>(&state);
+ last_known_result_ = rv;
+ return ready();
+}
+
+void Watch::Cancel() {
+ RequestContext::current()->AddWatchCancelFinalizer(this);
+}
+
+void Watch::InvokeCallback(MojoResult result,
+ const HandleSignalsState& state,
+ MojoTrapEventFlags 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_);
+
+ // Ensure that no notifications are dispatched beyond cancellation.
+ if (is_cancelled_)
+ return;
+
+ if (result == MOJO_RESULT_CANCELLED)
+ is_cancelled_ = true;
+
+ // 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 core
+} // namespace mojo
diff --git a/mojo/core/watch.h b/mojo/core/watch.h
new file mode 100644
index 0000000000..b2a8165833
--- /dev/null
+++ b/mojo/core/watch.h
@@ -0,0 +1,127 @@
+// 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_CORE_WATCH_H_
+#define MOJO_CORE_WATCH_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "mojo/core/atomic_flag.h"
+#include "mojo/core/handle_signals_state.h"
+#include "mojo/public/c/system/trap.h"
+
+namespace mojo {
+namespace core {
+
+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<Watch> {
+ public:
+ // Constructs a Watch which represents a watch within |watcher| associated
+ // with |context|, watching |dispatcher| for |signals|.
+ Watch(const scoped_refptr<WatcherDispatcher>& watcher,
+ const scoped_refptr<Dispatcher>& dispatcher,
+ uintptr_t context,
+ MojoHandleSignals signals,
+ MojoTriggerCondition condition);
+
+ // 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,
+ MojoTrapEventFlags flags);
+
+ const scoped_refptr<Dispatcher>& 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>;
+
+ ~Watch();
+
+#if DCHECK_IS_ON()
+ void AssertWatcherLockAcquired() const;
+#else
+ void AssertWatcherLockAcquired() const {}
+#endif
+
+ const scoped_refptr<WatcherDispatcher> watcher_;
+ const scoped_refptr<Dispatcher> dispatcher_;
+ const uintptr_t context_;
+ const MojoHandleSignals signals_;
+ const MojoTriggerCondition condition_;
+
+ // 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 core
+} // namespace mojo
+
+#endif // MOJO_CORE_WATCH_H_
diff --git a/mojo/core/watcher_dispatcher.cc b/mojo/core/watcher_dispatcher.cc
new file mode 100644
index 0000000000..f2eb2e253d
--- /dev/null
+++ b/mojo/core/watcher_dispatcher.cc
@@ -0,0 +1,263 @@
+// 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/core/watcher_dispatcher.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "base/debug/alias.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "mojo/core/watch.h"
+
+namespace mojo {
+namespace core {
+
+WatcherDispatcher::WatcherDispatcher(MojoTrapEventHandler handler)
+ : handler_(handler) {}
+
+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 associated with this dispatcher,
+ // provided we're armed and 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> 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,
+ MojoTrapEventFlags flags) {
+ MojoTrapEvent event;
+ event.struct_size = sizeof(event);
+ event.trigger_context = context;
+ event.result = result;
+ event.signals_state = static_cast<MojoHandleSignalsState>(state);
+ event.flags = 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;
+ }
+
+ handler_(&event);
+}
+
+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.
+ base::flat_map<uintptr_t, scoped_refptr<Watch>> watches;
+ {
+ base::AutoLock lock(lock_);
+ if (closed_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ 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> dispatcher,
+ MojoHandleSignals signals,
+ MojoTriggerCondition condition,
+ 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 (closed_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+
+ if (watches_.count(context) || watched_handles_.count(dispatcher.get()))
+ return MOJO_RESULT_ALREADY_EXISTS;
+
+ scoped_refptr<Watch> watch =
+ new Watch(this, dispatcher, context, signals, condition);
+ 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;
+ }
+
+ bool remove_now;
+ {
+ // If we've been closed already, there's a chance our closure raced with
+ // the call to AddWatcherRef() above. In that case we want to ensure we've
+ // removed our ref from |dispatcher|. Note that this may in turn race
+ // with normal removal, but that's fine.
+ base::AutoLock lock(lock_);
+ remove_now = closed_;
+ }
+ if (remove_now)
+ dispatcher->RemoveWatcherRef(this, context);
+
+ 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> watch;
+ {
+ base::AutoLock lock(lock_);
+ if (closed_)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ 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());
+
+ // If another thread races to close this watcher handler, |watched_handles_|
+ // may have been cleared by the time we reach this section.
+ if (handle_it == watched_handles_.end())
+ return MOJO_RESULT_OK;
+
+ ready_watches_.erase(handle_it->second.get());
+ watched_handles_.erase(handle_it);
+ }
+
+ return MOJO_RESULT_OK;
+}
+
+MojoResult WatcherDispatcher::Arm(uint32_t* num_blocking_events,
+ MojoTrapEvent* blocking_events) {
+ base::AutoLock lock(lock_);
+ if (num_blocking_events && !blocking_events)
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ if (closed_)
+ 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_blocking_events) {
+ DCHECK_LE(ready_watches_.size(), std::numeric_limits<uint32_t>::max());
+ *num_blocking_events = std::min(
+ *num_blocking_events, static_cast<uint32_t>(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_blocking_events; ++i) {
+ const Watch* const watch = *next_ready_iter;
+ if (blocking_events[i].struct_size < sizeof(*blocking_events))
+ return MOJO_RESULT_INVALID_ARGUMENT;
+ blocking_events[i].flags = MOJO_TRAP_EVENT_FLAG_WITHIN_API_CALL;
+ blocking_events[i].trigger_context = watch->context();
+ blocking_events[i].result = watch->last_known_result();
+ blocking_events[i].signals_state = 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() = default;
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/watcher_dispatcher.h b/mojo/core/watcher_dispatcher.h
new file mode 100644
index 0000000000..6a8bceda9f
--- /dev/null
+++ b/mojo/core/watcher_dispatcher.h
@@ -0,0 +1,102 @@
+// 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_CORE_WATCHER_DISPATCHER_H_
+#define MOJO_CORE_WATCHER_DISPATCHER_H_
+
+#include <stdint.h>
+
+#include <set>
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "mojo/core/dispatcher.h"
+#include "mojo/core/handle_signals_state.h"
+#include "mojo/core/system_impl_export.h"
+#include "mojo/public/c/system/trap.h"
+
+namespace mojo {
+namespace core {
+
+class Watch;
+
+// The dispatcher type which backs watcher handles.
+class WatcherDispatcher : public Dispatcher {
+ public:
+ // Constructs a new WatcherDispatcher which invokes |handler| when a
+ // registered watch observes some relevant state change.
+ explicit WatcherDispatcher(MojoTrapEventHandler handler);
+
+ // 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,
+ MojoTrapEventFlags flags);
+
+ // Dispatcher:
+ Type GetType() const override;
+ MojoResult Close() override;
+ MojoResult WatchDispatcher(scoped_refptr<Dispatcher> dispatcher,
+ MojoHandleSignals signals,
+ MojoTriggerCondition condition,
+ uintptr_t context) override;
+ MojoResult CancelWatch(uintptr_t context) override;
+ MojoResult Arm(uint32_t* num_blocking_events,
+ MojoTrapEvent* blocking_events) override;
+
+ private:
+ friend class Watch;
+
+ using WatchSet = std::set<const Watch*>;
+
+ ~WatcherDispatcher() override;
+
+ const MojoTrapEventHandler handler_;
+
+ // 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.
+ base::flat_map<uintptr_t, scoped_refptr<Watch>> watches_;
+
+ // A mapping from watched dispatcher to Watch.
+ base::flat_map<Dispatcher*, scoped_refptr<Watch>> 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 core
+} // namespace mojo
+
+#endif // MOJO_CORE_WATCHER_DISPATCHER_H_
diff --git a/mojo/core/watcher_set.cc b/mojo/core/watcher_set.cc
new file mode 100644
index 0000000000..98aa1afe7c
--- /dev/null
+++ b/mojo/core/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/core/watcher_set.h"
+
+#include <utility>
+
+namespace mojo {
+namespace core {
+
+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<WatcherDispatcher>& 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<WatcherDispatcher>& dispatcher)
+ : dispatcher(dispatcher) {}
+
+WatcherSet::Entry::Entry(Entry&& other) = default;
+
+WatcherSet::Entry::~Entry() = default;
+
+WatcherSet::Entry& WatcherSet::Entry::operator=(Entry&& other) = default;
+
+} // namespace core
+} // namespace mojo
diff --git a/mojo/core/watcher_set.h b/mojo/core/watcher_set.h
new file mode 100644
index 0000000000..6abd43e099
--- /dev/null
+++ b/mojo/core/watcher_set.h
@@ -0,0 +1,70 @@
+// 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_CORE_WATCHER_SET_H_
+#define MOJO_CORE_WATCHER_SET_H_
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "mojo/core/handle_signals_state.h"
+#include "mojo/core/watcher_dispatcher.h"
+
+namespace mojo {
+namespace core {
+
+// 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<WatcherDispatcher>& watcher,
+ uintptr_t context,
+ const HandleSignalsState& current_state);
+
+ // Removes a watcher+context.
+ MojoResult Remove(WatcherDispatcher* watcher, uintptr_t context);
+
+ private:
+ using ContextSet = std::set<uintptr_t>;
+
+ struct Entry {
+ Entry(const scoped_refptr<WatcherDispatcher>& dispatcher);
+ Entry(Entry&& other);
+ ~Entry();
+
+ Entry& operator=(Entry&& other);
+
+ scoped_refptr<WatcherDispatcher> dispatcher;
+ ContextSet contexts;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Entry);
+ };
+
+ Dispatcher* const owner_;
+ base::flat_map<WatcherDispatcher*, Entry> watchers_;
+ base::Optional<HandleSignalsState> last_known_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(WatcherSet);
+};
+
+} // namespace core
+} // namespace mojo
+
+#endif // MOJO_CORE_WATCHER_SET_H_
diff --git a/mojo/edk/DEPS b/mojo/edk/DEPS
deleted file mode 100644
index 77abb21ee3..0000000000
--- a/mojo/edk/DEPS
+++ /dev/null
@@ -1,14 +0,0 @@
-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
deleted file mode 100644
index 8105bedb5f..0000000000
--- a/mojo/edk/embedder/BUILD.gn
+++ /dev/null
@@ -1,147 +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.
-
-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
deleted file mode 100644
index fc53beceea..0000000000
--- a/mojo/edk/embedder/README.md
+++ /dev/null
@@ -1,346 +0,0 @@
-# ![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<local::mojom::Foo> 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
deleted file mode 100644
index 1990fb130d..0000000000
--- a/mojo/edk/embedder/configuration.h
+++ /dev/null
@@ -1,65 +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 MOJO_EDK_EMBEDDER_CONFIGURATION_H_
-#define MOJO_EDK_EMBEDDER_CONFIGURATION_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-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
deleted file mode 100644
index 9b7ec54eb4..0000000000
--- a/mojo/edk/embedder/connection_params.cc
+++ /dev/null
@@ -1,28 +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 "mojo/edk/embedder/connection_params.h"
-
-#include <utility>
-
-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
deleted file mode 100644
index 25ffdde7ab..0000000000
--- a/mojo/edk/embedder/connection_params.h
+++ /dev/null
@@ -1,34 +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 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
deleted file mode 100644
index 0fdda5cd1c..0000000000
--- a/mojo/edk/embedder/embedder.cc
+++ /dev/null
@@ -1,158 +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 "mojo/edk/embedder/embedder.h"
-
-#include <stdint.h>
-#include <utility>
-
-#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<base::TaskRunner> io_thread_task_runner) {
- CHECK(internal::g_core);
- internal::g_core->SetIOTaskRunner(io_thread_task_runner);
-}
-
-scoped_refptr<base::TaskRunner> 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
deleted file mode 100644
index 97258e52f6..0000000000
--- a/mojo/edk/embedder/embedder.h
+++ /dev/null
@@ -1,173 +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 MOJO_EDK_EMBEDDER_EMBEDDER_H_
-#define MOJO_EDK_EMBEDDER_EMBEDDER_H_
-
-#include <stddef.h>
-
-#include <memory>
-#include <string>
-
-#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<base::TaskRunner> io_thread_task_runner);
-
-// Retrieves the TaskRunner used for IPC I/O, as set by InitIPCSupport.
-MOJO_SYSTEM_IMPL_EXPORT scoped_refptr<base::TaskRunner> 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
deleted file mode 100644
index 7deeca157d..0000000000
--- a/mojo/edk/embedder/embedder_internal.h
+++ /dev/null
@@ -1,44 +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.
-
-// 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 <stdint.h>
-
-#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
deleted file mode 100644
index 388b45c365..0000000000
--- a/mojo/edk/embedder/embedder_unittest.cc
+++ /dev/null
@@ -1,603 +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 "mojo/edk/embedder/embedder.h"
-
-#include <stddef.h>
-#include <stdint.h>
-#include <string.h>
-
-#include <utility>
-
-#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<PendingProcessConnection>();
- 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<void**>(&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<char*>(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<void**>(&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<char*>(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<void**>(&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<char*>(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_t>(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_t>(MACH_PORT_NULL),
- scoped_handle.get().port);
- EXPECT_EQ(PlatformHandle::Type::MACH, scoped_handle.get().type);
- } else {
- EXPECT_NE(static_cast<mach_port_t>(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
deleted file mode 100644
index 9081368550..0000000000
--- a/mojo/edk/embedder/entrypoints.cc
+++ /dev/null
@@ -1,283 +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 "mojo/edk/embedder/entrypoints.h"
-
-#include <stdint.h>
-
-#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
deleted file mode 100644
index 8e448c156f..0000000000
--- a/mojo/edk/embedder/entrypoints.h
+++ /dev/null
@@ -1,22 +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 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
deleted file mode 100644
index 5a83ae3b85..0000000000
--- a/mojo/edk/embedder/named_platform_channel_pair.h
+++ /dev/null
@@ -1,73 +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 MOJO_EDK_EMBEDDER_NAMED_PLATFORM_CHANNEL_PAIR_H_
-#define MOJO_EDK_EMBEDDER_NAMED_PLATFORM_CHANNEL_PAIR_H_
-
-#include <string>
-
-#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
deleted file mode 100644
index 96589ff5cf..0000000000
--- a/mojo/edk/embedder/named_platform_channel_pair_win.cc
+++ /dev/null
@@ -1,89 +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 "mojo/edk/embedder/named_platform_channel_pair.h"
-
-#include <windows.h>
-
-#include <memory>
-#include <string>
-#include <utility>
-
-#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
deleted file mode 100644
index 15ca6565d5..0000000000
--- a/mojo/edk/embedder/named_platform_handle.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 MOJO_EDK_EMBEDDER_NAMED_PLATFORM_HANDLE_H_
-#define MOJO_EDK_EMBEDDER_NAMED_PLATFORM_HANDLE_H_
-
-#include <string>
-
-#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
deleted file mode 100644
index b767ea0975..0000000000
--- a/mojo/edk/embedder/named_platform_handle_utils.h
+++ /dev/null
@@ -1,56 +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 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
deleted file mode 100644
index 056f4d64e5..0000000000
--- a/mojo/edk/embedder/named_platform_handle_utils_posix.cc
+++ /dev/null
@@ -1,141 +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 "mojo/edk/embedder/named_platform_handle_utils.h"
-
-#include <errno.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <unistd.h>
-
-#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<sockaddr*>(&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<const sockaddr*>(&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
deleted file mode 100644
index a145847fd0..0000000000
--- a/mojo/edk/embedder/named_platform_handle_utils_win.cc
+++ /dev/null
@@ -1,95 +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 "mojo/edk/embedder/named_platform_handle_utils.h"
-
-#include <sddl.h>
-#include <windows.h>
-
-#include <memory>
-
-#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<void, decltype(::LocalFree)*> 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
deleted file mode 100644
index d6be76e81f..0000000000
--- a/mojo/edk/embedder/pending_process_connection.cc
+++ /dev/null
@@ -1,50 +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 "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
deleted file mode 100644
index ca18227696..0000000000
--- a/mojo/edk/embedder/pending_process_connection.h
+++ /dev/null
@@ -1,124 +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 MOJO_EDK_EMBEDDER_PENDING_PROCESS_CONNECTION_H_
-#define MOJO_EDK_EMBEDDER_PENDING_PROCESS_CONNECTION_H_
-
-#include <string>
-
-#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<void(const std::string& error)>;
-
-// 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
deleted file mode 100644
index ee1905abee..0000000000
--- a/mojo/edk/embedder/platform_channel_pair.cc
+++ /dev/null
@@ -1,34 +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 "mojo/edk/embedder/platform_channel_pair.h"
-
-#include <utility>
-
-#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
deleted file mode 100644
index 9c93f76094..0000000000
--- a/mojo/edk/embedder/platform_channel_pair.h
+++ /dev/null
@@ -1,106 +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 MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_PAIR_H_
-#define MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_PAIR_H_
-
-#include <memory>
-
-#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
deleted file mode 100644
index fe9f8f5cc7..0000000000
--- a/mojo/edk/embedder/platform_channel_pair_posix.cc
+++ /dev/null
@@ -1,172 +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 "mojo/edk/embedder/platform_channel_pair.h"
-
-#include <fcntl.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <limits>
-
-#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 <sys/socket.h>
-#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<int, int>(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<int, int>(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
deleted file mode 100644
index a3fd275b9f..0000000000
--- a/mojo/edk/embedder/platform_channel_pair_posix_unittest.cc
+++ /dev/null
@@ -1,261 +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 "mojo/edk/embedder/platform_channel_pair.h"
-
-#include <errno.h>
-#include <poll.h>
-#include <signal.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <sys/uio.h>
-#include <unistd.h>
-#include <deque>
-#include <utility>
-
-#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<ssize_t>(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<ssize_t>(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<char*>(kHello), sizeof(kHello)},
- {const_cast<char*>(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<ssize_t>(send_string.size()),
- PlatformChannelWrite(server_handle.get(), send_string.data(),
- send_string.size()));
-
- WaitReadable(client_handle.get());
-
- char buf[10000] = {};
- std::deque<PlatformHandle> received_handles;
- ssize_t result = PlatformChannelRecvmsg(client_handle.get(), buf,
- sizeof(buf), &received_handles);
- EXPECT_EQ(static_cast<ssize_t>(send_string.size()), result);
- EXPECT_EQ(send_string, std::string(buf, static_cast<size_t>(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<char*>(kHello), sizeof(kHello)};
- // We assume that the |sendmsg()| actually sends all the data.
- EXPECT_EQ(static_cast<ssize_t>(sizeof(kHello)),
- PlatformChannelSendmsgWithHandles(server_handle.get(), &iov, 1,
- &platform_handles->at(0),
- platform_handles->size()));
-
- WaitReadable(client_handle.get());
-
- char buf[10000] = {};
- std::deque<PlatformHandle> received_handles;
- // We assume that the |recvmsg()| actually reads all the data.
- EXPECT_EQ(static_cast<ssize_t>(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<char*>(kHello), sizeof(kHello)};
- // We assume that the |sendmsg()| actually sends all the data.
- EXPECT_EQ(static_cast<ssize_t>(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<PlatformHandle> received_handles;
- received_handles.push_back(PlatformHandle());
-
- char buf[100] = {};
- // We assume that the |recvmsg()| actually reads all the data.
- EXPECT_EQ(static_cast<ssize_t>(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
deleted file mode 100644
index f523ade335..0000000000
--- a/mojo/edk/embedder/platform_channel_pair_win.cc
+++ /dev/null
@@ -1,123 +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 "mojo/edk/embedder/platform_channel_pair.h"
-
-#include <windows.h>
-
-#include <string>
-
-#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
deleted file mode 100644
index 689b6eec0d..0000000000
--- a/mojo/edk/embedder/platform_channel_utils_posix.cc
+++ /dev/null
@@ -1,282 +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 "mojo/edk/embedder/platform_channel_utils_posix.h"
-
-#include <stddef.h>
-#include <sys/socket.h>
-#include <unistd.h>
-
-#include <utility>
-
-#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 <sys/uio.h>
-#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<unsigned>(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<int>(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<int*>(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<char*>(""), 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<int*>(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<PlatformHandle>* 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<int*>(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
deleted file mode 100644
index 23cfa92a34..0000000000
--- a/mojo/edk/embedder/platform_channel_utils_posix.h
+++ /dev/null
@@ -1,87 +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 MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_UTILS_POSIX_H_
-#define MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_UTILS_POSIX_H_
-
-#include <stddef.h>
-#include <sys/types.h> // For |ssize_t|.
-
-#include <deque>
-#include <memory>
-
-#include "mojo/edk/embedder/platform_handle.h"
-#include "mojo/edk/system/system_impl_export.h"
-
-struct iovec; // Declared in <sys/uio.h>.
-
-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<PlatformHandle>* 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
deleted file mode 100644
index b6b2cd22d1..0000000000
--- a/mojo/edk/embedder/platform_handle.cc
+++ /dev/null
@@ -1,74 +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.
-
-#include "mojo/edk/embedder/platform_handle.h"
-
-#include "build/build_config.h"
-#if defined(OS_POSIX)
-#include <unistd.h>
-#elif defined(OS_WIN)
-#include <windows.h>
-#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
deleted file mode 100644
index 4866e754fd..0000000000
--- a/mojo/edk/embedder/platform_handle.h
+++ /dev/null
@@ -1,90 +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 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 <windows.h>
-
-#include "base/process/process_handle.h"
-#elif defined(OS_MACOSX) && !defined(OS_IOS)
-#include <mach/mach.h>
-#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
deleted file mode 100644
index fa683e4a60..0000000000
--- a/mojo/edk/embedder/platform_handle_utils.h
+++ /dev/null
@@ -1,33 +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 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 <typename PlatformHandleContainer>
-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
deleted file mode 100644
index 5604f96bf1..0000000000
--- a/mojo/edk/embedder/platform_handle_utils_posix.cc
+++ /dev/null
@@ -1,24 +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 "mojo/edk/embedder/platform_handle_utils.h"
-
-#include <unistd.h>
-
-#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
deleted file mode 100644
index 32ed49afc1..0000000000
--- a/mojo/edk/embedder/platform_handle_utils_win.cc
+++ /dev/null
@@ -1,28 +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 "mojo/edk/embedder/platform_handle_utils.h"
-
-#include <windows.h>
-
-#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
deleted file mode 100644
index 9892b23cac..0000000000
--- a/mojo/edk/embedder/platform_handle_vector.h
+++ /dev/null
@@ -1,35 +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 MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_VECTOR_H_
-#define MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_VECTOR_H_
-
-#include <memory>
-#include <vector>
-
-#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<PlatformHandle>;
-
-// 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<PlatformHandleVector, PlatformHandleVectorDeleter>;
-
-} // 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
deleted file mode 100644
index 58af44df8f..0000000000
--- a/mojo/edk/embedder/platform_shared_buffer.cc
+++ /dev/null
@@ -1,325 +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 "mojo/edk/embedder/platform_shared_buffer.h"
-
-#include <stddef.h>
-
-#include <utility>
-
-#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 <unistd.h>
-#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<PlatformSharedBuffer> 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<PlatformSharedBuffer> 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<PlatformSharedBuffer> 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<PlatformSharedBufferMapping> 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<PlatformSharedBufferMapping> 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<PlatformSharedBufferMapping> 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<off_t>(real_offset), real_length))
- return false;
-
- base_ = static_cast<char*>(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
deleted file mode 100644
index 45be7233c4..0000000000
--- a/mojo/edk/embedder/platform_shared_buffer.h
+++ /dev/null
@@ -1,178 +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 MOJO_EDK_EMBEDDER_PLATFORM_SHARED_BUFFER_H_
-#define MOJO_EDK_EMBEDDER_PLATFORM_SHARED_BUFFER_H_
-
-#include <stddef.h>
-
-#include <memory>
-
-#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<PlatformSharedBuffer> {
- 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<PlatformSharedBufferMapping> 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<PlatformSharedBufferMapping> 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>;
-
- 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<base::SharedMemory> shared_memory_;
-
- // A separate read-only shared memory for platforms that need it (i.e. Linux
- // with sync broker).
- std::unique_ptr<base::SharedMemory> 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
deleted file mode 100644
index f1593f06c4..0000000000
--- a/mojo/edk/embedder/platform_shared_buffer_unittest.cc
+++ /dev/null
@@ -1,227 +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 "mojo/edk/embedder/platform_shared_buffer.h"
-
-#include <stddef.h>
-
-#include <limits>
-#include <memory>
-
-#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<PlatformSharedBuffer> 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<PlatformSharedBufferMapping> mapping(
- buffer->Map(0, kNumBytes));
- ASSERT_TRUE(mapping);
- ASSERT_TRUE(mapping->GetBase());
- int* stuff = static_cast<int*>(mapping->GetBase());
- for (size_t i = 0; i < kNumInts; i++)
- stuff[i] = static_cast<int>(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<PlatformSharedBufferMapping> mapping1(
- buffer->MapNoCheck(0, kNumBytes));
- ASSERT_TRUE(mapping1);
- ASSERT_TRUE(mapping1->GetBase());
- int* stuff1 = static_cast<int*>(mapping1->GetBase());
- for (size_t i = 0; i < kNumInts; i++)
- EXPECT_EQ(static_cast<int>(i) + kFudge, stuff1[i]) << i;
-
- std::unique_ptr<PlatformSharedBufferMapping> mapping2(
- buffer->Map((kNumInts / 2) * sizeof(int), 2 * sizeof(int)));
- ASSERT_TRUE(mapping2);
- ASSERT_TRUE(mapping2->GetBase());
- int* stuff2 = static_cast<int*>(mapping2->GetBase());
- EXPECT_EQ(static_cast<int>(kNumInts / 2) + kFudge, stuff2[0]);
- EXPECT_EQ(static_cast<int>(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<PlatformSharedBufferMapping> mapping(
- buffer->Map(sizeof(int), kNumBytes - sizeof(int)));
- ASSERT_TRUE(mapping);
- ASSERT_TRUE(mapping->GetBase());
- int* stuff = static_cast<int*>(mapping->GetBase());
-
- for (size_t j = 0; j < kNumInts - 1; j++) {
- int i = static_cast<int>(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<PlatformSharedBuffer> 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<size_t>::max();
- if (base::SysInfo::AmountOfVirtualMemory() &&
- max_size > static_cast<size_t>(base::SysInfo::AmountOfVirtualMemory()))
- max_size = static_cast<size_t>(base::SysInfo::AmountOfVirtualMemory());
- scoped_refptr<PlatformSharedBuffer> 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<PlatformSharedBuffer> buffer(PlatformSharedBuffer::Create(100));
- std::unique_ptr<PlatformSharedBufferMapping> mapping1(buffer->Map(0, 100));
- std::unique_ptr<PlatformSharedBufferMapping> 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<PlatformSharedBuffer> buffer(
- PlatformSharedBuffer::Create(kSizes[i]));
- std::unique_ptr<PlatformSharedBufferMapping> 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<char*>(mapping->GetBase())[j])
- << "size " << kSizes[i] << ", offset " << j;
- }
- }
-}
-
-TEST(PlatformSharedBufferTest, MappingsOutliveBuffer) {
- std::unique_ptr<PlatformSharedBufferMapping> mapping1;
- std::unique_ptr<PlatformSharedBufferMapping> mapping2;
-
- {
- scoped_refptr<PlatformSharedBuffer> buffer(
- PlatformSharedBuffer::Create(100));
- mapping1 = buffer->Map(0, 100);
- mapping2 = buffer->Map(50, 50);
- static_cast<char*>(mapping1->GetBase())[50] = 'x';
- }
-
- EXPECT_EQ('x', static_cast<char*>(mapping2->GetBase())[0]);
-
- static_cast<char*>(mapping2->GetBase())[1] = 'y';
- EXPECT_EQ('y', static_cast<char*>(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<PlatformSharedBuffer> simple_buffer(
- PlatformSharedBuffer::CreateFromSharedMemoryHandle(
- kBufferSize, false /* read_only */, shm_handle));
- ASSERT_TRUE(simple_buffer);
-
- std::unique_ptr<PlatformSharedBufferMapping> mapping =
- simple_buffer->Map(0, kBufferSize);
- ASSERT_TRUE(mapping);
-
- const int kOffset = 123;
- char* base_memory = static_cast<char*>(shared_memory.memory());
- char* mojo_memory = static_cast<char*>(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
deleted file mode 100644
index f67210a817..0000000000
--- a/mojo/edk/embedder/scoped_ipc_support.cc
+++ /dev/null
@@ -1,39 +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 "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<base::TaskRunner> 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
deleted file mode 100644
index 22d8e50113..0000000000
--- a/mojo/edk/embedder/scoped_ipc_support.h
+++ /dev/null
@@ -1,118 +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 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<base::TaskRunner> 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
deleted file mode 100644
index 15b80ea4b2..0000000000
--- a/mojo/edk/embedder/scoped_platform_handle.h
+++ /dev/null
@@ -1,63 +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 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
deleted file mode 100644
index 9658010586..0000000000
--- a/mojo/edk/embedder/test_embedder.cc
+++ /dev/null
@@ -1,46 +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 "mojo/edk/embedder/test_embedder.h"
-
-#include <memory>
-
-#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<MojoHandle> 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
deleted file mode 100644
index c64ba179e1..0000000000
--- a/mojo/edk/embedder/test_embedder.h
+++ /dev/null
@@ -1,28 +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 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
deleted file mode 100644
index fc1e03c3a4..0000000000
--- a/mojo/edk/js/BUILD.gn
+++ /dev/null
@@ -1,35 +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.
-
-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
deleted file mode 100644
index baccc4c0d7..0000000000
--- a/mojo/edk/js/core.cc
+++ /dev/null
@@ -1,454 +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 "mojo/edk/js/core.h"
-
-#include <stddef.h>
-#include <stdint.h>
-
-#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<HandleWrapper> 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<v8::Value>());
- } 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<v8::Value> 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<gin::Handle<HandleWrapper> >& handles,
- MojoWriteMessageFlags flags) {
- std::vector<MojoHandle> 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<uint32_t>(buffer.num_bytes()),
- raw_handles.empty() ? NULL : &raw_handles[0],
- static_cast<uint32_t>(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<v8::ArrayBuffer> array_buffer =
- v8::ArrayBuffer::New(args.isolate(), num_bytes);
- std::vector<mojo::Handle> 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<MojoHandle*>(&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<v8::Value> 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<uint32_t>(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<v8::ArrayBuffer> 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<v8::Value> DoDrainData(gin::Arguments* args,
- gin::Handle<HandleWrapper> handle) {
- return (new DrainData(args->isolate(), handle->release()))->GetPromise();
-}
-
-bool IsHandle(gin::Arguments* args, v8::Handle<v8::Value> val) {
- gin::Handle<mojo::edk::js::HandleWrapper> ignore_handle;
- return gin::Converter<gin::Handle<mojo::edk::js::HandleWrapper>>::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<v8::ArrayBuffer> 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<v8::ArrayBuffer>& 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<v8::Value> Core::GetModule(v8::Isolate* isolate) {
- gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
- v8::Local<v8::ObjectTemplate> 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
deleted file mode 100644
index 97ef5dfd81..0000000000
--- a/mojo/edk/js/core.h
+++ /dev/null
@@ -1,25 +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 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<v8::Value> 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
deleted file mode 100644
index 334ced32e7..0000000000
--- a/mojo/edk/js/drain_data.cc
+++ /dev/null
@@ -1,129 +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 "mojo/edk/js/drain_data.h"
-
-#include <stddef.h>
-#include <stdint.h>
-
-#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<v8::Context> context(isolate_->GetCurrentContext());
- runner_ = gin::PerContextData::From(context)->runner()->GetWeakPtr();
-
- WaitForData();
-}
-
-v8::Handle<v8::Value> DrainData::GetPromise() {
- CHECK(resolver_.IsEmpty());
- v8::Handle<v8::Promise::Resolver> 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<const char*>(buffer);
- data_buffers_.push_back(base::MakeUnique<DataBuffer>(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<v8::ArrayBuffer> 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<char*>(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<v8::Promise::Resolver> resolver(
- v8::Local<v8::Promise::Resolver>::New(isolate_, resolver_));
-
- gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(isolate_);
- dictionary.Set("result", result);
- dictionary.Set("buffer", array_buffer);
- v8::Handle<v8::Value> 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
deleted file mode 100644
index 42da90f110..0000000000
--- a/mojo/edk/js/drain_data.h
+++ /dev/null
@@ -1,65 +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 MOJO_EDK_JS_DRAIN_DATA_H_
-#define MOJO_EDK_JS_DRAIN_DATA_H_
-
-#include <memory>
-#include <vector>
-
-#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<v8::Value> 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<char>;
-
- v8::Isolate* isolate_;
- ScopedDataPipeConsumerHandle handle_;
- SimpleWatcher handle_watcher_;
- base::WeakPtr<gin::Runner> runner_;
- v8::UniquePersistent<v8::Promise::Resolver> resolver_;
- std::vector<std::unique_ptr<DataBuffer>> 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
deleted file mode 100644
index 7da8e9fa19..0000000000
--- a/mojo/edk/js/handle.cc
+++ /dev/null
@@ -1,85 +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 "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<v8::Value> Converter<mojo::Handle>::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<mojo::Handle>::FromV8(v8::Isolate* isolate,
- v8::Handle<v8::Value> val,
- mojo::Handle* out) {
- if (val->IsNull()) {
- *out = mojo::Handle();
- return true;
- }
-
- gin::Handle<mojo::edk::js::HandleWrapper> handle;
- if (!Converter<gin::Handle<mojo::edk::js::HandleWrapper>>::FromV8(
- isolate, val, &handle))
- return false;
-
- *out = handle->get();
- return true;
-}
-
-v8::Handle<v8::Value> Converter<mojo::MessagePipeHandle>::ToV8(
- v8::Isolate* isolate, mojo::MessagePipeHandle val) {
- return Converter<mojo::Handle>::ToV8(isolate, val);
-}
-
-bool Converter<mojo::MessagePipeHandle>::FromV8(v8::Isolate* isolate,
- v8::Handle<v8::Value> val,
- mojo::MessagePipeHandle* out) {
- return Converter<mojo::Handle>::FromV8(isolate, val, out);
-}
-
-} // namespace gin
diff --git a/mojo/edk/js/handle.h b/mojo/edk/js/handle.h
deleted file mode 100644
index 60652ed8c2..0000000000
--- a/mojo/edk/js/handle.h
+++ /dev/null
@@ -1,107 +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 MOJO_EDK_JS_HANDLE_H_
-#define MOJO_EDK_JS_HANDLE_H_
-
-#include <stdint.h>
-
-#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<HandleWrapper> {
- public:
- static gin::WrapperInfo kWrapperInfo;
-
- static gin::Handle<HandleWrapper> 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<HandleCloseObserver> 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<mojo::Handle> {
- static v8::Handle<v8::Value> ToV8(v8::Isolate* isolate,
- const mojo::Handle& val);
- static bool FromV8(v8::Isolate* isolate, v8::Handle<v8::Value> val,
- mojo::Handle* out);
-};
-
-template <>
-struct MOJO_JS_EXPORT Converter<mojo::MessagePipeHandle> {
- static v8::Handle<v8::Value> ToV8(v8::Isolate* isolate,
- mojo::MessagePipeHandle val);
- static bool FromV8(v8::Isolate* isolate,
- v8::Handle<v8::Value> 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<gin::Handle<mojo::edk::js::HandleWrapper>> {
- static v8::Handle<v8::Value> ToV8(
- v8::Isolate* isolate,
- const gin::Handle<mojo::edk::js::HandleWrapper>& val) {
- return val.ToV8();
- }
-
- static bool FromV8(v8::Isolate* isolate,
- v8::Handle<v8::Value> val,
- gin::Handle<mojo::edk::js::HandleWrapper>* out) {
- if (val->IsNull()) {
- *out = mojo::edk::js::HandleWrapper::Create(isolate, MOJO_HANDLE_INVALID);
- return true;
- }
-
- mojo::edk::js::HandleWrapper* object = NULL;
- if (!Converter<mojo::edk::js::HandleWrapper*>::FromV8(isolate, val,
- &object)) {
- return false;
- }
- *out = gin::Handle<mojo::edk::js::HandleWrapper>(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
deleted file mode 100644
index c7b935ec6e..0000000000
--- a/mojo/edk/js/handle_close_observer.h
+++ /dev/null
@@ -1,24 +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 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
deleted file mode 100644
index dd2562fa63..0000000000
--- a/mojo/edk/js/handle_unittest.cc
+++ /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.
-
-#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
deleted file mode 100644
index 179113ce4f..0000000000
--- a/mojo/edk/js/js_export.h
+++ /dev/null
@@ -1,32 +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 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
deleted file mode 100644
index dda0b2c315..0000000000
--- a/mojo/edk/js/mojo_runner_delegate.cc
+++ /dev/null
@@ -1,80 +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.
-
-#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<base::FilePath> GetModuleSearchPaths() {
- std::vector<base::FilePath> 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<gin::Runner> runner,
- MojoHandle pipe,
- v8::Handle<v8::Value> module) {
- v8::Isolate* isolate = runner->GetContextHolder()->isolate();
- v8::Handle<v8::Function> start;
- CHECK(gin::ConvertFromV8(isolate, module, &start));
-
- v8::Handle<v8::Value> 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
deleted file mode 100644
index 9ab325c8df..0000000000
--- a/mojo/edk/js/mojo_runner_delegate.h
+++ /dev/null
@@ -1,36 +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 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
deleted file mode 100644
index 404cb9b786..0000000000
--- a/mojo/edk/js/support.cc
+++ /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.
-
-#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<HandleWrapper> handle,
- MojoHandleSignals signals,
- v8::Handle<v8::Function> 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<HandleWrapper> handle,
- MojoHandleSignals signals,
- v8::Handle<v8::Function> 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<v8::Value> Support::GetModule(v8::Isolate* isolate) {
- gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
- v8::Local<v8::ObjectTemplate> 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
deleted file mode 100644
index 551f5ac0a8..0000000000
--- a/mojo/edk/js/support.h
+++ /dev/null
@@ -1,25 +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 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<v8::Value> 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
deleted file mode 100644
index f56c4b95f9..0000000000
--- a/mojo/edk/js/tests/BUILD.gn
+++ /dev/null
@@ -1,68 +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.
-
-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
deleted file mode 100644
index 688b22b3de..0000000000
--- a/mojo/edk/js/tests/js_to_cpp.mojom
+++ /dev/null
@@ -1,54 +0,0 @@
-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>? string_array;
- handle<message_pipe>? message_handle;
- handle<data_pipe_consumer>? 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
deleted file mode 100644
index b6b74e31fb..0000000000
--- a/mojo/edk/js/tests/js_to_cpp_tests.cc
+++ /dev/null
@@ -1,455 +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 <stddef.h>
-#include <stdint.h>
-
-#include <string>
-#include <utility>
-
-#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<double>::infinity();
-const double kExpectedDoubleNan = std::numeric_limits<double>::quiet_NaN();
-const float kExpectedFloatVal = static_cast<float>(kExpectedDoubleVal);
-const float kExpectedFloatInf = std::numeric_limits<float>::infinity();
-const float kExpectedFloatNan = std::numeric_limits<float>::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<uint32_t>(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<std::string>& arg) {
- if (!arg)
- return;
- CheckCorruptedString(*arg);
-}
-
-void CheckCorruptedStringArray(
- const base::Optional<std::vector<std::string>>& 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<uint32_t>(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<uint32_t>(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<js_to_cpp::CppSide> 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<js_to_cpp::CppSide> 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
deleted file mode 100644
index 6b69fcacc8..0000000000
--- a/mojo/edk/js/tests/js_to_cpp_tests.js
+++ /dev/null
@@ -1,223 +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.
-
-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
deleted file mode 100644
index 13e796ba62..0000000000
--- a/mojo/edk/js/tests/run_js_unittests.cc
+++ /dev/null
@@ -1,61 +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/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
deleted file mode 100644
index db9f12d3d5..0000000000
--- a/mojo/edk/js/threading.cc
+++ /dev/null
@@ -1,49 +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.
-
-#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<v8::Value> Threading::GetModule(v8::Isolate* isolate) {
- gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
- v8::Local<v8::ObjectTemplate> 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
deleted file mode 100644
index 653d0765a6..0000000000
--- a/mojo/edk/js/threading.h
+++ /dev/null
@@ -1,28 +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 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<v8::Value> 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
deleted file mode 100644
index 6ad4bd0347..0000000000
--- a/mojo/edk/js/waiting_callback.cc
+++ /dev/null
@@ -1,95 +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 "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<v8::Private> 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> WaitingCallback::Create(
- v8::Isolate* isolate,
- v8::Handle<v8::Function> callback,
- gin::Handle<HandleWrapper> handle_wrapper,
- MojoHandleSignals signals,
- bool one_shot) {
- gin::Handle<WaitingCallback> 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<v8::Function> callback,
- bool one_shot)
- : one_shot_(one_shot),
- watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC),
- weak_factory_(this) {
- v8::Handle<v8::Context> 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<v8::Value> hidden_value =
- GetWrapper(isolate)
- ->GetPrivate(runner_->GetContextHolder()->context(),
- GetHiddenPropertyName(isolate))
- .ToLocalChecked();
- v8::Handle<v8::Function> callback;
- CHECK(gin::ConvertFromV8(isolate, hidden_value, &callback));
-
- v8::Handle<v8::Value> 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
deleted file mode 100644
index f97b389aea..0000000000
--- a/mojo/edk/js/waiting_callback.h
+++ /dev/null
@@ -1,67 +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 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<WaitingCallback> {
- 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<WaitingCallback> Create(
- v8::Isolate* isolate,
- v8::Handle<v8::Function> callback,
- gin::Handle<HandleWrapper> 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<v8::Function> 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<gin::Runner> runner_;
- SimpleWatcher watcher_;
- base::WeakPtrFactory<WaitingCallback> 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
deleted file mode 100644
index a68cd44ff1..0000000000
--- a/mojo/edk/system/BUILD.gn
+++ /dev/null
@@ -1,205 +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.
-
-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
deleted file mode 100644
index 6bdcfaaddd..0000000000
--- a/mojo/edk/system/atomic_flag.h
+++ /dev/null
@@ -1,57 +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 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
deleted file mode 100644
index 1577972a6d..0000000000
--- a/mojo/edk/system/broker.h
+++ /dev/null
@@ -1,52 +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 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<PlatformSharedBuffer> 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
deleted file mode 100644
index 6096034fa2..0000000000
--- a/mojo/edk/system/broker_host.cc
+++ /dev/null
@@ -1,153 +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 "mojo/edk/system/broker_host.h"
-
-#include <utility>
-
-#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<void**>(&name_data));
- data->pipe_name_length = static_cast<uint32_t>(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<PlatformSharedBuffer> read_only_buffer;
- scoped_refptr<PlatformSharedBuffer> 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<const BrokerMessageHeader*>(payload);
- switch (header->type) {
- case BrokerMessageType::BUFFER_REQUEST:
- if (payload_size ==
- sizeof(BrokerMessageHeader) + sizeof(BufferRequestData)) {
- const BufferRequestData* request =
- reinterpret_cast<const BufferRequestData*>(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
deleted file mode 100644
index a7995d2b0f..0000000000
--- a/mojo/edk/system/broker_host.h
+++ /dev/null
@@ -1,64 +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 MOJO_EDK_SYSTEM_BROKER_HOST_H_
-#define MOJO_EDK_SYSTEM_BROKER_HOST_H_
-
-#include <stdint.h>
-
-#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> 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
deleted file mode 100644
index 0f0dd9dc42..0000000000
--- a/mojo/edk/system/broker_messages.h
+++ /dev/null
@@ -1,80 +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 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 <typename T>
-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<BrokerMessageHeader*>(message->mutable_payload());
- header->type = type;
- header->padding = 0;
- *out_message_data = reinterpret_cast<T*>(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<BrokerMessageHeader*>(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
deleted file mode 100644
index 8742f709a7..0000000000
--- a/mojo/edk/system/broker_posix.cc
+++ /dev/null
@@ -1,125 +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 "mojo/edk/system/broker.h"
-
-#include <fcntl.h>
-#include <unistd.h>
-
-#include <utility>
-
-#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<PlatformHandle>* incoming_handles) {
- Channel::MessagePtr message(
- new Channel::Message(sizeof(BrokerMessageHeader), expected_num_handles));
- std::deque<PlatformHandle> incoming_platform_handles;
- ssize_t read_result = PlatformChannelRecvmsg(
- platform_handle, const_cast<void*>(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<size_t>(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<const BrokerMessageHeader*>(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<PlatformHandle> 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<PlatformSharedBuffer> 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<size_t>(write_result) !=
- out_message->data_num_bytes()) {
- LOG(ERROR) << "Error sending complete broker message";
- return nullptr;
- }
-
- std::deque<PlatformHandle> 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
deleted file mode 100644
index 063282c146..0000000000
--- a/mojo/edk/system/broker_win.cc
+++ /dev/null
@@ -1,155 +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 <windows.h>
-
-#include <limits>
-#include <utility>
-
-#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<size_t>(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<const BrokerMessageHeader*>(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<const BrokerMessageHeader*>(message->payload());
- CHECK_GE(message->payload_size(),
- sizeof(BrokerMessageHeader) + sizeof(InitData));
- const InitData* data = reinterpret_cast<const InitData*>(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<const base::char16*>(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<PlatformSharedBuffer> 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<uint32_t>(num_bytes);
- DWORD bytes_written = 0;
- BOOL result = ::WriteFile(sync_channel_.get().handle, out_message->data(),
- static_cast<DWORD>(out_message->data_num_bytes()),
- &bytes_written, nullptr);
- if (!result ||
- static_cast<size_t>(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
deleted file mode 100644
index 8a44d36024..0000000000
--- a/mojo/edk/system/channel.cc
+++ /dev/null
@@ -1,683 +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 "mojo/edk/system/channel.h"
-
-#include <stddef.h>
-#include <string.h>
-
-#include <algorithm>
-#include <limits>
-#include <utility>
-
-#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 <index, port> 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<char*>(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<uint32_t>::max());
- legacy_header()->num_bytes = static_cast<uint32_t>(size_);
-
- DCHECK_LE(header_size + extra_header_size,
- std::numeric_limits<uint16_t>::max());
- legacy_header()->message_type = message_type;
-
- if (is_legacy_message) {
- legacy_header()->num_handles = static_cast<uint16_t>(max_handles);
- } else {
- header()->num_header_bytes =
- static_cast<uint16_t>(header_size + extra_header_size);
- }
-
- if (max_handles_ > 0) {
-#if defined(OS_WIN)
- handles_ = reinterpret_cast<HandleEntry*>(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<MachPortsExtraHeader*>(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<uint32_t>(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<const LegacyHeader*>(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<const Header*>(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<const char*>(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<const char*>(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<const char*>(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<void*>(legacy_header() + 1);
- return data_ + header()->num_header_bytes;
-}
-
-const void* Channel::Message::payload() const {
- if (is_legacy_message())
- return static_cast<const void*>(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<LegacyHeader*>(data_);
-}
-
-Channel::Message::Header* Channel::Message::header() const {
- DCHECK(!is_legacy_message());
- return reinterpret_cast<Header*>(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<uint16_t>(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<uint32_t>(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<uint16_t>(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<uint32_t>(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<char*>(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<char*>(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<char*>(
- 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<char*>(
- 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<uintptr_t>(read_buffer_->occupied_bytes()))) {
- read_buffer_->Realign();
- }
-
- // We have at least enough data available for a LegacyHeader.
- const Message::LegacyHeader* legacy_header =
- reinterpret_cast<const Message::LegacyHeader*>(
- 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<const Message::Header*>(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<Message::Header*>(
- const_cast<char*>(read_buffer_->occupied_bytes()) +
- header->num_header_bytes)
- : nullptr;
- } else {
- payload_size = legacy_header->num_bytes - sizeof(Message::LegacyHeader);
- payload = payload_size
- ? const_cast<Message::LegacyHeader*>(&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
deleted file mode 100644
index 33a510c6f0..0000000000
--- a/mojo/edk/system/channel.h
+++ /dev/null
@@ -1,303 +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 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<Channel> {
- public:
- struct Message;
-
- using MessagePtr = std::unique_ptr<Message>;
-
- // 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<Channel> Create(
- Delegate* delegate,
- ConnectionParams connection_params,
- scoped_refptr<base::TaskRunner> 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<Channel>;
-
- class ReadBuffer;
-
- Delegate* delegate_;
- const std::unique_ptr<ReadBuffer> 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
deleted file mode 100644
index 8b4ca7fdf3..0000000000
--- a/mojo/edk/system/channel_posix.cc
+++ /dev/null
@@ -1,572 +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 "mojo/edk/system/channel.h"
-
-#include <errno.h>
-#include <sys/socket.h>
-
-#include <algorithm>
-#include <deque>
-#include <limits>
-#include <memory>
-
-#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 <sys/uio.h>
-#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<const char*>(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<base::TaskRunner> 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<uint16_t>::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<const MachPortsExtraHeader*>(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_port_t>(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<size_t>(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<void*>(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<int> 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<size_t>(result);
- } while (bytes_written < message_view.data_num_bytes());
-
- return FlushOutgoingMessagesNoLock();
- }
-
- bool FlushOutgoingMessagesNoLock() {
- std::deque<MessageView> 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<const int*>(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<Channel> self_;
-
- ScopedPlatformHandle handle_;
- scoped_refptr<base::TaskRunner> io_task_runner_;
-
- // These watchers must only be accessed on the IO thread.
- std::unique_ptr<base::MessageLoopForIO::FileDescriptorWatcher> read_watcher_;
- std::unique_ptr<base::MessageLoopForIO::FileDescriptorWatcher> write_watcher_;
-
- std::deque<PlatformHandle> incoming_platform_handles_;
-
- // Protects |pending_write_| and |outgoing_messages_|.
- base::Lock write_lock_;
- bool pending_write_ = false;
- bool reject_writes_ = false;
- std::deque<MessageView> 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> Channel::Create(
- Delegate* delegate,
- ConnectionParams connection_params,
- scoped_refptr<base::TaskRunner> 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
deleted file mode 100644
index ce2c804d55..0000000000
--- a/mojo/edk/system/channel_unittest.cc
+++ /dev/null
@@ -1,177 +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 "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<char[]> payload_;
-};
-
-Channel::MessagePtr CreateDefaultMessage(bool legacy_message) {
- const size_t payload_size = 100;
- Channel::MessagePtr message = base::MakeUnique<Channel::Message>(
- payload_size, 0,
- legacy_message ? Channel::Message::MessageType::NORMAL_LEGACY
- : Channel::Message::MessageType::NORMAL);
- char* payload = static_cast<char*>(message->mutable_payload());
- for (size_t i = 0; i < payload_size; i++) {
- payload[i] = static_cast<char>(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<const unsigned char*>(data1);
- const unsigned char* data2_char = static_cast<const unsigned char*>(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<TestChannel> 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<TestChannel> 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
deleted file mode 100644
index c15df16bb1..0000000000
--- a/mojo/edk/system/channel_win.cc
+++ /dev/null
@@ -1,360 +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 "mojo/edk/system/channel.h"
-
-#include <stdint.h>
-#include <windows.h>
-
-#include <algorithm>
-#include <deque>
-#include <limits>
-#include <memory>
-
-#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<const char*>(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<base::TaskRunner> 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<uint16_t>::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<const HandleEntry*>(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<ChannelWin> 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<size_t>(bytes_transfered));
- } else {
- CHECK(context == &write_context_);
- OnWriteDone(static_cast<size_t>(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<DWORD>(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<DWORD>(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<Channel> self_;
-
- ScopedPlatformHandle handle_;
- scoped_refptr<base::TaskRunner> 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<MessageView> outgoing_messages_;
-
- bool wait_for_connect_;
-
- bool leak_handle_ = false;
-
- DISALLOW_COPY_AND_ASSIGN(ChannelWin);
-};
-
-} // namespace
-
-// static
-scoped_refptr<Channel> Channel::Create(
- Delegate* delegate,
- ConnectionParams connection_params,
- scoped_refptr<base::TaskRunner> 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
deleted file mode 100644
index f5eb2b8f6f..0000000000
--- a/mojo/edk/system/configuration.cc
+++ /dev/null
@@ -1,25 +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 "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
deleted file mode 100644
index 038835ffdd..0000000000
--- a/mojo/edk/system/configuration.h
+++ /dev/null
@@ -1,29 +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 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
deleted file mode 100644
index 360e8c3012..0000000000
--- a/mojo/edk/system/core.cc
+++ /dev/null
@@ -1,1019 +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.
-
-#include "mojo/edk/system/core.h"
-
-#include <string.h>
-
-#include <utility>
-
-#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<int>(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<mach_port_t>(platform_handle->value);
- break;
-#endif
-
-#if defined(OS_WIN)
- case MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE:
- handle.handle = reinterpret_cast<HANDLE>(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<uint64_t>(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<uint64_t>(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<uint64_t>(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<base::TaskRunner> 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<base::TaskRunner> 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<Dispatcher> 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> dispatcher) {
- base::AutoLock lock(handles_lock_);
- return handles_.AddDispatcher(dispatcher);
-}
-
-bool Core::AddDispatchersFromTransit(
- const std::vector<Dispatcher::DispatcherInTransit>& 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<Dispatcher> 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<PlatformHandleDispatcher*>(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<PlatformSharedBuffer> platform_buffer =
- PlatformSharedBuffer::CreateFromSharedMemoryHandle(num_bytes, read_only,
- shared_memory_handle);
- if (!platform_buffer)
- return MOJO_RESULT_UNKNOWN;
-
- scoped_refptr<SharedBufferDispatcher> 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> 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<SharedBufferDispatcher*>(dispatcher.get());
- scoped_refptr<PlatformSharedBuffer> 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<const bool*>(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> 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> 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<Dispatcher> watcher = GetDispatcher(watcher_handle);
- if (!watcher || watcher->GetType() != Dispatcher::Type::WATCHER)
- return MOJO_RESULT_INVALID_ARGUMENT;
- scoped_refptr<Dispatcher> 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<Dispatcher> 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<Dispatcher> 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<MessageForTransit> msg;
- MojoResult rv = MessageForTransit::Create(&msg, num_bytes, nullptr, 0);
- if (rv != MOJO_RESULT_OK)
- return rv;
-
- *message = reinterpret_cast<MojoMessageHandle>(msg.release());
- return MOJO_RESULT_OK;
- }
-
- if (!handles)
- return MOJO_RESULT_INVALID_ARGUMENT;
-
- if (num_handles > kMaxHandlesPerMessage)
- return MOJO_RESULT_RESOURCE_EXHAUSTED;
-
- std::vector<Dispatcher::DispatcherInTransit> 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<MessageForTransit> 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<MojoMessageHandle>(msg.release());
- } else {
- handles_.CancelTransit(dispatchers);
- }
- }
-
- return rv;
-}
-
-MojoResult Core::FreeMessage(MojoMessageHandle message) {
- if (!message)
- return MOJO_RESULT_INVALID_ARGUMENT;
-
- delete reinterpret_cast<MessageForTransit*>(message);
-
- return MOJO_RESULT_OK;
-}
-
-MojoResult Core::GetMessageBuffer(MojoMessageHandle message, void** buffer) {
- if (!message)
- return MOJO_RESULT_INVALID_ARGUMENT;
-
- *buffer = reinterpret_cast<MessageForTransit*>(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<bool*>(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<Dispatcher> 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<MessageForTransit> message_for_transit(
- reinterpret_cast<MessageForTransit*>(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<MessageForTransit> 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<MessageForTransit> 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<MojoMessageHandle>(msg.release());
- return MOJO_RESULT_OK;
-}
-
-MojoResult Core::FuseMessagePipes(MojoHandle handle0, MojoHandle handle1) {
- RequestContext request_context;
- scoped_refptr<Dispatcher> dispatcher0;
- scoped_refptr<Dispatcher> 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<MessagePipeDispatcher*>(dispatcher0.get());
- MessagePipeDispatcher* mpd1 =
- static_cast<MessagePipeDispatcher*>(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<MessageForTransit*>(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<PlatformSharedBuffer> 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<Dispatcher> producer = new DataPipeProducerDispatcher(
- GetNodeController(), port0, ring_buffer, create_options,
- true /* initialized */, pipe_id);
- scoped_refptr<Dispatcher> 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<Dispatcher> 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> 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> 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> 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> 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> 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> 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<SharedBufferDispatcher> 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> dispatcher(GetDispatcher(buffer_handle));
- if (!dispatcher)
- return MOJO_RESULT_INVALID_ARGUMENT;
-
- // Don't verify |options| here; that's the dispatcher's job.
- scoped_refptr<Dispatcher> 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> dispatcher(GetDispatcher(buffer_handle));
- if (!dispatcher)
- return MOJO_RESULT_INVALID_ARGUMENT;
-
- std::unique_ptr<PlatformSharedBufferMapping> 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<PlatformSharedBuffer> platform_buffer =
- PlatformSharedBuffer::CreateFromPlatformHandle(size, read_only,
- std::move(handle));
- if (!platform_buffer)
- return MOJO_RESULT_UNKNOWN;
-
- scoped_refptr<SharedBufferDispatcher> 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> 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<SharedBufferDispatcher*>(dispatcher.get());
- scoped_refptr<PlatformSharedBuffer> 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<MojoHandle>* handles) {
- base::AutoLock lock(handles_lock_);
- handles_.GetActiveHandlesForTest(handles);
-}
-
-// static
-void Core::PassNodeControllerToIOThread(
- std::unique_ptr<NodeController> 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
deleted file mode 100644
index 1f6d865d23..0000000000
--- a/mojo/edk/system/core.h
+++ /dev/null
@@ -1,297 +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 MOJO_EDK_SYSTEM_CORE_H_
-#define MOJO_EDK_SYSTEM_CORE_H_
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#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<base::TaskRunner> io_task_runner);
-
- // Retrieves the NodeController for the current process.
- NodeController* GetNodeController();
-
- scoped_refptr<Dispatcher> 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> 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<Dispatcher::DispatcherInTransit>& 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<MojoHandle>* 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<NodeController> 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<NodeController> 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
deleted file mode 100644
index 7751612e9d..0000000000
--- a/mojo/edk/system/core_test_base.cc
+++ /dev/null
@@ -1,272 +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.
-
-#include "mojo/edk/system/core_test_base.h"
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <vector>
-
-#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<MockDispatcher> 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<MessageForTransit> 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<MessageForTransit>* 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<MockDispatcher> 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
deleted file mode 100644
index 3d156e32e2..0000000000
--- a/mojo/edk/system/core_test_base.h
+++ /dev/null
@@ -1,94 +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 MOJO_EDK_SYSTEM_CORE_TEST_BASE_H_
-#define MOJO_EDK_SYSTEM_CORE_TEST_BASE_H_
-
-#include <stddef.h>
-
-#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
deleted file mode 100644
index 0d60b48a8b..0000000000
--- a/mojo/edk/system/core_unittest.cc
+++ /dev/null
@@ -1,971 +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.
-
-#include "mojo/edk/system/core.h"
-
-#include <stdint.h>
-
-#include <limits>
-
-#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<MojoTimeTicks>(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<MojoTimeTicks>(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<uint32_t>::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<uint32_t>::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<uint32_t>(sizeof(kHello));
- const char kWorld[] = "world!!!";
- const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
- char buffer[100];
- const uint32_t kBufferSize = static_cast<uint32_t>(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<char*>(write_ptr)[0] = 'C';
- static_cast<char*>(write_ptr)[1] = 'D';
- static_cast<char*>(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<const char*>(read_ptr)[0]);
- ASSERT_EQ('D', static_cast<const char*>(read_ptr)[1]);
- ASSERT_EQ('E', static_cast<const char*>(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<uint32_t>(sizeof(kHello));
- const char kWorld[] = "world!!!";
- const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
- char buffer[100];
- const uint32_t kBufferSize = static_cast<uint32_t>(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<char*>(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<const char*>(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
deleted file mode 100644
index f3387324fc..0000000000
--- a/mojo/edk/system/data_pipe_consumer_dispatcher.cc
+++ /dev/null
@@ -1,562 +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.
-
-#include "mojo/edk/system/data_pipe_consumer_dispatcher.h"
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <algorithm>
-#include <limits>
-#include <utility>
-
-#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<DataPipeConsumerDispatcher> dispatcher)
- : dispatcher_(dispatcher) {}
-
- private:
- ~PortObserverThunk() override {}
-
- // NodeController::PortObserver:
- void OnPortStatusChanged() override { dispatcher_->OnPortStatusChanged(); }
-
- scoped_refptr<DataPipeConsumerDispatcher> dispatcher_;
-
- DISALLOW_COPY_AND_ASSIGN(PortObserverThunk);
-};
-
-DataPipeConsumerDispatcher::DataPipeConsumerDispatcher(
- NodeController* node_controller,
- const ports::PortRef& control_port,
- scoped_refptr<PlatformSharedBuffer> 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<uint32_t>(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<uint8_t*>(ring_buffer_mapping_->GetBase());
- CHECK(data);
-
- uint8_t* destination = static_cast<uint8_t*>(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<uint8_t*>(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<WatcherDispatcher>& 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<uint32_t>(sizeof(SerializedState));
- *num_ports = 1;
- *num_handles = 1;
-}
-
-bool DataPipeConsumerDispatcher::EndSerialize(
- void* destination,
- ports::PortName* ports,
- PlatformHandle* platform_handles) {
- SerializedState* state = static_cast<SerializedState*>(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>
-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<const SerializedState*>(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<PlatformSharedBuffer> 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<DataPipeConsumerDispatcher> 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<const DataPipeControlMessage*>(
- message->payload_bytes());
-
- if (m->command != DataPipeCommand::DATA_WAS_WRITTEN) {
- DLOG(ERROR) << "Unexpected control message from producer.";
- peer_closed_ = true;
- break;
- }
-
- if (static_cast<size_t>(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
deleted file mode 100644
index 120c7a387f..0000000000
--- a/mojo/edk/system/data_pipe_consumer_dispatcher.h
+++ /dev/null
@@ -1,123 +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 MOJO_EDK_SYSTEM_DATA_PIPE_CONSUMER_DISPATCHER_H_
-#define MOJO_EDK_SYSTEM_DATA_PIPE_CONSUMER_DISPATCHER_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <memory>
-
-#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<PlatformSharedBuffer> 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<WatcherDispatcher>& 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<DataPipeConsumerDispatcher>
- 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<PlatformSharedBuffer> shared_ring_buffer_;
- std::unique_ptr<PlatformSharedBufferMapping> 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
deleted file mode 100644
index 23873b8290..0000000000
--- a/mojo/edk/system/data_pipe_control_message.cc
+++ /dev/null
@@ -1,35 +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 "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<PortsMessage> message =
- PortsMessage::NewUserMessage(sizeof(DataPipeControlMessage), 0, 0);
- CHECK(message);
-
- DataPipeControlMessage* data =
- static_cast<DataPipeControlMessage*>(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
deleted file mode 100644
index ec84ea3c55..0000000000
--- a/mojo/edk/system/data_pipe_control_message.h
+++ /dev/null
@@ -1,43 +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 MOJO_EDK_SYSTEM_DATA_PIPE_CONTROL_MESSAGE_H_
-#define MOJO_EDK_SYSTEM_DATA_PIPE_CONTROL_MESSAGE_H_
-
-#include <stdint.h>
-
-#include <memory>
-
-#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
deleted file mode 100644
index b0102a6d9b..0000000000
--- a/mojo/edk/system/data_pipe_producer_dispatcher.cc
+++ /dev/null
@@ -1,507 +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.
-
-#include "mojo/edk/system/data_pipe_producer_dispatcher.h"
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <utility>
-
-#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<DataPipeProducerDispatcher> dispatcher)
- : dispatcher_(dispatcher) {}
-
- private:
- ~PortObserverThunk() override {}
-
- // NodeController::PortObserver:
- void OnPortStatusChanged() override { dispatcher_->OnPortStatusChanged(); }
-
- scoped_refptr<DataPipeProducerDispatcher> dispatcher_;
-
- DISALLOW_COPY_AND_ASSIGN(PortObserverThunk);
-};
-
-DataPipeProducerDispatcher::DataPipeProducerDispatcher(
- NodeController* node_controller,
- const ports::PortRef& control_port,
- scoped_refptr<PlatformSharedBuffer> 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<uint8_t*>(ring_buffer_mapping_->GetBase());
- CHECK(data);
-
- const uint8_t* source = static_cast<const uint8_t*>(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<uint8_t*>(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<WatcherDispatcher>& 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<SerializedState*>(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>
-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<const SerializedState*>(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<PlatformSharedBuffer> 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<DataPipeProducerDispatcher> 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<const DataPipeControlMessage*>(
- message->payload_bytes());
-
- if (m->command != DataPipeCommand::DATA_WAS_READ) {
- DLOG(ERROR) << "Unexpected message from consumer.";
- peer_closed_ = true;
- break;
- }
-
- if (static_cast<size_t>(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
deleted file mode 100644
index 1eddd5dfa8..0000000000
--- a/mojo/edk/system/data_pipe_producer_dispatcher.h
+++ /dev/null
@@ -1,123 +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 MOJO_EDK_SYSTEM_DATA_PIPE_PRODUCER_DISPATCHER_H_
-#define MOJO_EDK_SYSTEM_DATA_PIPE_PRODUCER_DISPATCHER_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <memory>
-
-#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<PlatformSharedBuffer> 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<WatcherDispatcher>& 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<DataPipeProducerDispatcher>
- 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<PlatformSharedBuffer>& 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<PlatformSharedBuffer> shared_ring_buffer_;
- std::unique_ptr<PlatformSharedBufferMapping> 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
deleted file mode 100644
index 79c1f758fb..0000000000
--- a/mojo/edk/system/data_pipe_unittest.cc
+++ /dev/null
@@ -1,2034 +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 <stddef.h>
-#include <stdint.h>
-
-#include <memory>
-
-#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<uint32_t>(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<uint32_t>(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<uint32_t>(arraysize(elements) * sizeof(elements[0]));
-
- elements[0] = 123;
- elements[1] = 456;
- num_bytes = static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(2u * sizeof(elements[0]));
- ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true));
- ASSERT_EQ(static_cast<uint32_t>(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<uint32_t>(1u * sizeof(elements[0]));
- ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true, true));
- ASSERT_EQ(static_cast<uint32_t>(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<uint32_t>(1u * sizeof(elements[0]));
- ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true, false));
- ASSERT_EQ(static_cast<uint32_t>(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<uint32_t>(3u * sizeof(elements[0]));
- ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&buffer, &num_bytes));
- EXPECT_TRUE(buffer);
- ASSERT_GE(num_bytes, static_cast<uint32_t>(1u * sizeof(elements[0])));
-
- static_cast<int32_t*>(buffer)[0] = 789;
- ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(static_cast<uint32_t>(
- 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<uint32_t>(1u * sizeof(elements[0])));
- ASSERT_EQ(456, static_cast<const int32_t*>(read_buffer)[0]);
- ASSERT_EQ(MOJO_RESULT_OK, EndReadData(static_cast<uint32_t>(
- 1u * sizeof(elements[0]))));
-
- // Write one element.
- elements[0] = 123;
- num_bytes = static_cast<uint32_t>(1u * sizeof(elements[0]));
- ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes));
- ASSERT_EQ(static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(1u * sizeof(elements[0]));
- ASSERT_EQ(MOJO_RESULT_OK, DiscardData(&num_bytes, true));
- ASSERT_EQ(static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(1u * sizeof(elements[0]));
- ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true));
- ASSERT_EQ(static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(1u * sizeof(elements[0]));
- ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true));
- ASSERT_EQ(static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(3u * sizeof(elements[0]));
- ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&buffer, &num_bytes, false));
- EXPECT_TRUE(buffer);
- EXPECT_GE(num_bytes, static_cast<uint32_t>(3u * sizeof(elements[0])));
- elements = static_cast<int32_t*>(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<uint32_t>(2u * sizeof(elements[0]));
- ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer, &num_bytes, true));
- EXPECT_TRUE(read_buffer);
- ASSERT_EQ(static_cast<uint32_t>(2u * sizeof(elements[0])), num_bytes);
- const int32_t* read_elements = static_cast<const int32_t*>(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<uint32_t>(3u * sizeof(elements[0]));
- ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer, &num_bytes));
- EXPECT_TRUE(read_buffer);
- ASSERT_EQ(static_cast<uint32_t>(1u * sizeof(elements[0])), num_bytes);
- read_elements = static_cast<const int32_t*>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<int32_t*>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<int32_t>(i);
-}
-
-TEST_F(DataPipeTest, AllOrNone) {
- const MojoCreateDataPipeOptions options = {
- kSizeOfOptions, // |struct_size|.
- MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|.
- static_cast<uint32_t>(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<unsigned char>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<const int32_t*>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<const uint8_t*>(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<uint8_t*>(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<uint32_t>(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<uint32_t>(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<uint32_t>(message.size());
- EXPECT_EQ(MOJO_RESULT_OK, MojoWriteData(p, message.data(), &num_bytes,
- MOJO_WRITE_DATA_FLAG_NONE));
- EXPECT_EQ(num_bytes, static_cast<uint32_t>(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<uint32_t>(expected_message.size());
- std::vector<char> 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<uint32_t>(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<uint32_t>(kMessage.size());
- EXPECT_EQ(MOJO_RESULT_OK, MojoWriteData(p, kMessage.data(), &num_bytes,
- MOJO_WRITE_DATA_FLAG_NONE));
- EXPECT_EQ(num_bytes, static_cast<uint32_t>(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<uint32_t>(expected_message.size());
- std::vector<char> 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<uint32_t>(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
deleted file mode 100644
index 7cdbe910d9..0000000000
--- a/mojo/edk/system/dispatcher.cc
+++ /dev/null
@@ -1,198 +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.
-
-#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> 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<MessageForTransit> message,
- MojoWriteMessageFlags flags) {
- return MOJO_RESULT_INVALID_ARGUMENT;
-}
-
-MojoResult Dispatcher::ReadMessage(std::unique_ptr<MessageForTransit>* 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<Dispatcher>* new_dispatcher) {
- return MOJO_RESULT_INVALID_ARGUMENT;
-}
-
-MojoResult Dispatcher::MapBuffer(
- uint64_t offset,
- uint64_t num_bytes,
- MojoMapBufferFlags flags,
- std::unique_ptr<PlatformSharedBufferMapping>* 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>& dispatcher,
- MojoHandleSignals signals,
- uintptr_t context) {
- return MOJO_RESULT_INVALID_ARGUMENT;
-}
-
-MojoResult Dispatcher::RemoveWaitingDispatcher(
- const scoped_refptr<Dispatcher>& 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<WatcherDispatcher>& 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> 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
deleted file mode 100644
index db1f1f18d7..0000000000
--- a/mojo/edk/system/dispatcher.h
+++ /dev/null
@@ -1,245 +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 MOJO_EDK_SYSTEM_DISPATCHER_H_
-#define MOJO_EDK_SYSTEM_DISPATCHER_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <memory>
-#include <ostream>
-#include <vector>
-
-#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<scoped_refptr<Dispatcher>>;
-
-// A |Dispatcher| implements Mojo EDK calls that are associated with a
-// particular MojoHandle.
-class MOJO_SYSTEM_IMPL_EXPORT Dispatcher
- : public base::RefCountedThreadSafe<Dispatcher> {
- public:
- struct DispatcherInTransit {
- DispatcherInTransit();
- DispatcherInTransit(const DispatcherInTransit& other);
- ~DispatcherInTransit();
-
- scoped_refptr<Dispatcher> 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> 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<MessageForTransit> message,
- MojoWriteMessageFlags flags);
-
- virtual MojoResult ReadMessage(std::unique_ptr<MessageForTransit>* 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<Dispatcher>* new_dispatcher);
-
- virtual MojoResult MapBuffer(
- uint64_t offset,
- uint64_t num_bytes,
- MojoMapBufferFlags flags,
- std::unique_ptr<PlatformSharedBufferMapping>* 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>& 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>& 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<WatcherDispatcher>& 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<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);
-
- protected:
- friend class base::RefCountedThreadSafe<Dispatcher>;
-
- 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<int>(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
deleted file mode 100644
index f2412787cb..0000000000
--- a/mojo/edk/system/handle_signals_state.h
+++ /dev/null
@@ -1,13 +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 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
deleted file mode 100644
index b570793dbe..0000000000
--- a/mojo/edk/system/handle_table.cc
+++ /dev/null
@@ -1,135 +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 "mojo/edk/system/handle_table.h"
-
-#include <stdint.h>
-
-#include <limits>
-
-namespace mojo {
-namespace edk {
-
-HandleTable::HandleTable() {}
-
-HandleTable::~HandleTable() {}
-
-MojoHandle HandleTable::AddDispatcher(scoped_refptr<Dispatcher> 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<Dispatcher::DispatcherInTransit>& 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<uint32_t>::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<Dispatcher> 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>* 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<Dispatcher::DispatcherInTransit>* 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<Dispatcher::DispatcherInTransit>& 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<Dispatcher::DispatcherInTransit>& 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<MojoHandle>* handles) {
- handles->clear();
- for (const auto& entry : handles_)
- handles->push_back(entry.first);
-}
-
-HandleTable::Entry::Entry() {}
-
-HandleTable::Entry::Entry(scoped_refptr<Dispatcher> 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
deleted file mode 100644
index 882d5405ce..0000000000
--- a/mojo/edk/system/handle_table.h
+++ /dev/null
@@ -1,75 +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 MOJO_EDK_SYSTEM_HANDLE_TABLE_H_
-#define MOJO_EDK_SYSTEM_HANDLE_TABLE_H_
-
-#include <stdint.h>
-
-#include <vector>
-
-#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> dispatcher);
-
- // Inserts multiple dispatchers received from message transit, populating
- // |handles| with their newly allocated handles. Returns |true| on success.
- bool AddDispatchersFromTransit(
- const std::vector<Dispatcher::DispatcherInTransit>& dispatchers,
- MojoHandle* handles);
-
- scoped_refptr<Dispatcher> GetDispatcher(MojoHandle handle) const;
- MojoResult GetAndRemoveDispatcher(MojoHandle,
- scoped_refptr<Dispatcher>* 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<Dispatcher::DispatcherInTransit>* dispatchers);
-
- void CompleteTransitAndClose(
- const std::vector<Dispatcher::DispatcherInTransit>& dispatchers);
- void CancelTransit(
- const std::vector<Dispatcher::DispatcherInTransit>& dispatchers);
-
- void GetActiveHandlesForTest(std::vector<MojoHandle> *handles);
-
- private:
- struct Entry {
- Entry();
- explicit Entry(scoped_refptr<Dispatcher> dispatcher);
- Entry(const Entry& other);
- ~Entry();
-
- scoped_refptr<Dispatcher> dispatcher;
- bool busy = false;
- };
-
- using HandleMap = base::hash_map<MojoHandle, Entry>;
-
- 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
deleted file mode 100644
index f05cf22a9a..0000000000
--- a/mojo/edk/system/mach_port_relay.cc
+++ /dev/null
@@ -1,248 +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 "mojo/edk/system/mach_port_relay.h"
-
-#include <mach/mach.h>
-
-#include <utility>
-
-#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<int>(error),
- static_cast<int>(BrokerUMAError::ERROR_MAX));
-}
-
-void ReportChildError(ChildUMAError error) {
- UMA_HISTOGRAM_ENUMERATION("Mojo.MachPortRelay.ChildError",
- static_cast<int>(error),
- static_cast<int>(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_name_t>(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
deleted file mode 100644
index 87bc56cf5b..0000000000
--- a/mojo/edk/system/mach_port_relay.h
+++ /dev/null
@@ -1,94 +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 MOJO_EDK_SYSTEM_MACH_PORT_RELAY_H_
-#define MOJO_EDK_SYSTEM_MACH_PORT_RELAY_H_
-
-#include <set>
-
-#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<Observer*> 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
deleted file mode 100644
index 850944306e..0000000000
--- a/mojo/edk/system/mapping_table.cc
+++ /dev/null
@@ -1,48 +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 "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<PlatformSharedBufferMapping> 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
deleted file mode 100644
index 00167e3604..0000000000
--- a/mojo/edk/system/mapping_table.h
+++ /dev/null
@@ -1,57 +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 MOJO_EDK_SYSTEM_MAPPING_TABLE_H_
-#define MOJO_EDK_SYSTEM_MAPPING_TABLE_H_
-
-#include <stdint.h>
-
-#include <memory>
-#include <vector>
-
-#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<PlatformSharedBufferMapping> mapping);
- MojoResult RemoveMapping(void* address);
-
- private:
- friend bool internal::ShutdownCheckNoLeaks(Core*);
-
- using AddressToMappingMap =
- base::hash_map<void*, PlatformSharedBufferMapping*>;
- 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
deleted file mode 100644
index 26658e161c..0000000000
--- a/mojo/edk/system/message_for_transit.cc
+++ /dev/null
@@ -1,136 +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 "mojo/edk/system/message_for_transit.h"
-
-#include <vector>
-
-#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<MessageForTransit>* 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<DispatcherInfo> 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<PortsMessage> 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<MessageHeader*>(msg->mutable_payload_bytes());
- DispatcherHeader* dispatcher_headers =
- reinterpret_cast<DispatcherHeader*>(header + 1);
-
- // Serialized dispatcher state immediately follows the series of
- // DispatcherHeaders.
- char* dispatcher_data =
- reinterpret_cast<char*>(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<uint32_t>::max());
- header->header_size = static_cast<uint32_t>(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<int32_t>(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<void*>(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<PortsMessage> 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
deleted file mode 100644
index 6103a771e1..0000000000
--- a/mojo/edk/system/message_for_transit.h
+++ /dev/null
@@ -1,115 +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 MOJO_EDK_SYSTEM_MESSAGE_FOR_TRANSIT_H_
-#define MOJO_EDK_SYSTEM_MESSAGE_FOR_TRANSIT_H_
-
-#include <stdint.h>
-
-#include <memory>
-
-#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<MessageForTransit>* message,
- uint32_t num_bytes,
- const Dispatcher::DispatcherInTransit* dispatchers,
- uint32_t num_dispatchers);
-
- // A static constructor for wrapping inbound messages.
- static std::unique_ptr<MessageForTransit> WrapPortsMessage(
- std::unique_ptr<PortsMessage> message) {
- return base::WrapUnique(new MessageForTransit(std::move(message)));
- }
-
- const void* bytes() const {
- DCHECK(message_);
- return static_cast<const void*>(
- static_cast<const char*>(message_->payload_bytes()) +
- header()->header_size);
- }
-
- void* mutable_bytes() {
- DCHECK(message_);
- return static_cast<void*>(
- static_cast<char*>(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<PortsMessage> TakePortsMessage() {
- return std::move(message_);
- }
-
- private:
- explicit MessageForTransit(std::unique_ptr<PortsMessage> message);
-
- const MessageForTransit::MessageHeader* header() const {
- DCHECK(message_);
- return static_cast<const MessageForTransit::MessageHeader*>(
- message_->payload_bytes());
- }
-
- std::unique_ptr<PortsMessage> 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
deleted file mode 100644
index 1db56c0dac..0000000000
--- a/mojo/edk/system/message_pipe_dispatcher.cc
+++ /dev/null
@@ -1,554 +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 "mojo/edk/system/message_pipe_dispatcher.h"
-
-#include <limits>
-#include <memory>
-
-#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<MessagePipeDispatcher> dispatcher)
- : dispatcher_(dispatcher) {}
-
- private:
- ~PortObserverThunk() override {}
-
- // NodeController::PortObserver:
- void OnPortStatusChanged() override { dispatcher_->OnPortStatusChanged(); }
-
- scoped_refptr<MessagePipeDispatcher> 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<const PortsMessage&>(m);
- if (message.num_payload_bytes() < sizeof(MessageHeader)) {
- *invalid_message_ = true;
- return true;
- }
-
- const MessageHeader* header =
- static_cast<const MessageHeader*>(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<uint32_t>(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<MessageForTransit> 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<MessageForTransit>* 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<PortsMessage> msg(
- static_cast<PortsMessage*>(ports_message.release()));
-
- const MessageHeader* header =
- static_cast<const MessageHeader*>(msg->payload_bytes());
- const DispatcherHeader* dispatcher_headers =
- reinterpret_cast<const DispatcherHeader*>(header + 1);
-
- if (header->num_dispatchers > std::numeric_limits<uint16_t>::max())
- return MOJO_RESULT_UNKNOWN;
-
- // Deserialize dispatchers.
- if (header->num_dispatchers > 0) {
- CHECK(handles);
- std::vector<DispatcherInTransit> 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<const char*>(
- 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<Type>(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<WatcherDispatcher>& 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<uint32_t>(sizeof(SerializedState));
- *num_ports = 1;
- *num_handles = 0;
-}
-
-bool MessagePipeDispatcher::EndSerialize(void* destination,
- ports::PortName* ports,
- PlatformHandle* handles) {
- SerializedState* state = static_cast<SerializedState*>(destination);
- state->pipe_id = pipe_id_;
- state->endpoint = static_cast<int8_t>(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<Dispatcher> 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<const SerializedState*>(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
deleted file mode 100644
index 574ad660b0..0000000000
--- a/mojo/edk/system/message_pipe_dispatcher.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 MOJO_EDK_SYSTEM_MESSAGE_PIPE_DISPATCHER_H_
-#define MOJO_EDK_SYSTEM_MESSAGE_PIPE_DISPATCHER_H_
-
-#include <stdint.h>
-
-#include <memory>
-#include <queue>
-
-#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<MessageForTransit> message,
- MojoWriteMessageFlags flags) override;
- MojoResult ReadMessage(std::unique_ptr<MessageForTransit>* 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<WatcherDispatcher>& 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<Dispatcher> 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
deleted file mode 100644
index 9866c474de..0000000000
--- a/mojo/edk/system/message_pipe_perftest.cc
+++ /dev/null
@@ -1,167 +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 <stddef.h>
-#include <stdint.h>
-
-#include <memory>
-#include <utility>
-
-#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<uint32_t>(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<uint32_t>(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<uint32_t>(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<unsigned>(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<uint32_t>(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<base::PerfTimeLogger> 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
deleted file mode 100644
index e6f1ff6437..0000000000
--- a/mojo/edk/system/message_pipe_unittest.cc
+++ /dev/null
@@ -1,699 +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.
-
-#include <stdint.h>
-#include <string.h>
-
-#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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<const char*>(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 <kPingPongHandlesPerIteration; ++i)
- EXPECT_EQ(MOJO_RESULT_OK, MojoCreateSharedBuffer(nullptr, 1, &buffers[i]));
-
- RUN_CHILD_ON_PIPE(HandlePingPong, h)
- for (size_t i = 0; i < kPingPongIterations; i++) {
- WriteMessageWithHandles(h, "", buffers, kPingPongHandlesPerIteration);
- ReadMessageWithHandles(h, buffers, kPingPongHandlesPerIteration);
- }
- WriteMessage(h, "quit", 4);
- END_CHILD()
- for (size_t i = 0; i < kPingPongHandlesPerIteration; ++i)
- MojoClose(buffers[i]);
-}
-
-#endif // !defined(OS_IOS)
-
-TEST_F(FuseMessagePipeTest, Basic) {
- // Test that we can fuse pipes and they still work.
-
- MojoHandle a, b, c, d;
- CreateMessagePipe(&a, &b);
- CreateMessagePipe(&c, &d);
-
- EXPECT_EQ(MOJO_RESULT_OK, MojoFuseMessagePipes(b, c));
-
- // Handles b and c should be closed.
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(b));
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(c));
-
- const std::string kTestMessage1 = "Hello, world!";
- const std::string kTestMessage2 = "Goodbye, world!";
-
- WriteMessage(a, kTestMessage1);
- EXPECT_EQ(kTestMessage1, ReadMessage(d));
-
- WriteMessage(d, kTestMessage2);
- EXPECT_EQ(kTestMessage2, ReadMessage(a));
-
- EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
- EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
-}
-
-TEST_F(FuseMessagePipeTest, FuseAfterPeerWrite) {
- // Test that messages written before fusion are eventually delivered.
-
- MojoHandle a, b, c, d;
- CreateMessagePipe(&a, &b);
- CreateMessagePipe(&c, &d);
-
- const std::string kTestMessage1 = "Hello, world!";
- const std::string kTestMessage2 = "Goodbye, world!";
- WriteMessage(a, kTestMessage1);
- WriteMessage(d, kTestMessage2);
-
- EXPECT_EQ(MOJO_RESULT_OK, MojoFuseMessagePipes(b, c));
-
- // Handles b and c should be closed.
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(b));
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(c));
-
- EXPECT_EQ(kTestMessage1, ReadMessage(d));
- EXPECT_EQ(kTestMessage2, ReadMessage(a));
-
- EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
- EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
-}
-
-TEST_F(FuseMessagePipeTest, NoFuseAfterWrite) {
- // Test that a pipe endpoint which has been written to cannot be fused.
-
- MojoHandle a, b, c, d;
- CreateMessagePipe(&a, &b);
- CreateMessagePipe(&c, &d);
-
- WriteMessage(b, "shouldn't have done that!");
- EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, MojoFuseMessagePipes(b, c));
-
- // Handles b and c should be closed.
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(b));
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(c));
-
- EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
- EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
-}
-
-TEST_F(FuseMessagePipeTest, NoFuseSelf) {
- // Test that a pipe's own endpoints can't be fused together.
-
- MojoHandle a, b;
- CreateMessagePipe(&a, &b);
-
- EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, MojoFuseMessagePipes(a, b));
-
- // Handles a and b should be closed.
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(a));
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(b));
-}
-
-TEST_F(FuseMessagePipeTest, FuseInvalidArguments) {
- MojoHandle a, b, c, d;
- CreateMessagePipe(&a, &b);
- CreateMessagePipe(&c, &d);
-
- EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b));
-
- // Can't fuse an invalid handle.
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoFuseMessagePipes(b, c));
-
- // Handle c should be closed.
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(c));
-
- // Can't fuse a non-message pipe handle.
- MojoHandle e, f;
- CreateDataPipe(&e, &f, 16);
-
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoFuseMessagePipes(e, d));
-
- // Handles d and e should be closed.
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(d));
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(e));
-
- EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
- EXPECT_EQ(MOJO_RESULT_OK, MojoClose(f));
-}
-
-TEST_F(FuseMessagePipeTest, FuseAfterPeerClosure) {
- // Test that peer closure prior to fusion can still be detected after fusion.
-
- MojoHandle a, b, c, d;
- CreateMessagePipe(&a, &b);
- CreateMessagePipe(&c, &d);
-
- EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
- EXPECT_EQ(MOJO_RESULT_OK, MojoFuseMessagePipes(b, c));
-
- // Handles b and c should be closed.
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(b));
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(c));
-
- EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(d, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
- EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
-}
-
-TEST_F(FuseMessagePipeTest, FuseAfterPeerWriteAndClosure) {
- // Test that peer write and closure prior to fusion still results in the
- // both message arrival and awareness of peer closure.
-
- MojoHandle a, b, c, d;
- CreateMessagePipe(&a, &b);
- CreateMessagePipe(&c, &d);
-
- const std::string kTestMessage = "ayyy lmao";
- WriteMessage(a, kTestMessage);
- EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a));
-
- EXPECT_EQ(MOJO_RESULT_OK, MojoFuseMessagePipes(b, c));
-
- // Handles b and c should be closed.
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(b));
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(c));
-
- EXPECT_EQ(kTestMessage, ReadMessage(d));
- EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(d, MOJO_HANDLE_SIGNAL_PEER_CLOSED));
- EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d));
-}
-
-TEST_F(MessagePipeTest, ClosePipesStressTest) {
- // Stress test to exercise https://crbug.com/665869.
- const size_t kNumPipes = 100000;
- for (size_t i = 0; i < kNumPipes; ++i) {
- MojoHandle a, b;
- CreateMessagePipe(&a, &b);
- MojoClose(a);
- MojoClose(b);
- }
-}
-
-} // namespace
-} // namespace edk
-} // namespace mojo
diff --git a/mojo/edk/system/multiprocess_message_pipe_unittest.cc b/mojo/edk/system/multiprocess_message_pipe_unittest.cc
deleted file mode 100644
index 37248d1438..0000000000
--- a/mojo/edk/system/multiprocess_message_pipe_unittest.cc
+++ /dev/null
@@ -1,1366 +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.
-
-#include <stddef.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-
-#include <string>
-#include <utility>
-#include <vector>
-
-#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<test::MojoTestBase::LaunchType> {
- 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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<int>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<size_t> {};
-
-TEST_P(MultiprocessMessagePipeTestWithPipeCount, PlatformHandlePassing) {
- base::ScopedTempDir temp_dir;
- ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
-
- RUN_CHILD_ON_PIPE(CheckPlatformHandleFile, h)
- std::vector<MojoHandle> 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<int>(pipe_count));
- ASSERT_EQ(MOJO_RESULT_OK,
- MojoWriteMessage(h, message,
- static_cast<uint32_t>(strlen(message)),
- &handles[0],
- static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<Handle> handles(2);
- handles[0] = Handle(h);
- handles[1] = Handle(p);
- std::vector<MojoHandleSignals> 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<std::string, MojoHandle> 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
deleted file mode 100644
index b0f770d907..0000000000
--- a/mojo/edk/system/node_channel.cc
+++ /dev/null
@@ -1,905 +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 "mojo/edk/system/node_channel.h"
-
-#include <cstring>
-#include <limits>
-#include <sstream>
-
-#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 <typename T>
-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 <typename DataType>
-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<Header*>(message->mutable_payload());
- header->type = type;
- header->padding = 0;
- *out_data = reinterpret_cast<DataType*>(&header[1]);
- return message;
-}
-
-template <typename DataType>
-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<const DataType*>(
- static_cast<const char*>(bytes) + sizeof(Header));
- return true;
-}
-
-} // namespace
-
-// static
-scoped_refptr<NodeChannel> NodeChannel::Create(
- Delegate* delegate,
- ConnectionParams connection_params,
- scoped_refptr<base::TaskRunner> 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<Header*>(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<base::TaskRunner> 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<NodeChannel> 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<const Header*>(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<const char*>(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<const void*>(
- reinterpret_cast<const Header*>(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<NodeChannel> 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
deleted file mode 100644
index 95dc3410eb..0000000000
--- a/mojo/edk/system/node_channel.h
+++ /dev/null
@@ -1,219 +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 MOJO_EDK_SYSTEM_NODE_CHANNEL_H_
-#define MOJO_EDK_SYSTEM_NODE_CHANNEL_H_
-
-#include <queue>
-#include <unordered_map>
-#include <utility>
-
-#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<NodeChannel>,
- 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<NodeChannel> Create(
- Delegate* delegate,
- ConnectionParams connection_params,
- scoped_refptr<base::TaskRunner> 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<NodeChannel>;
-
- using PendingMessageQueue = std::queue<Channel::MessagePtr>;
- using PendingRelayMessageQueue =
- std::queue<std::pair<ports::NodeName, Channel::MessagePtr>>;
-
- NodeChannel(Delegate* delegate,
- ConnectionParams connection_params,
- scoped_refptr<base::TaskRunner> 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<base::TaskRunner> io_task_runner_;
- const ProcessErrorCallback process_error_callback_;
-
- base::Lock channel_lock_;
- scoped_refptr<Channel> 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
deleted file mode 100644
index e608f0c009..0000000000
--- a/mojo/edk/system/node_controller.cc
+++ /dev/null
@@ -1,1475 +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 "mojo/edk/system/node_controller.h"
-
-#include <algorithm>
-#include <limits>
-
-#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 <typename T>
-void GenerateRandomName(T* out) { base::RandBytes(out, sizeof(T)); }
-#else
-template <typename T>
-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<size_t>(std::numeric_limits<int32_t>::max()));
-
- // 8k is the maximum number of file descriptors allowed in Chrome.
- UMA_HISTOGRAM_CUSTOM_COUNTS("Mojo.System.Node.ConnectedPeers",
- static_cast<int32_t>(count),
- 1 /* min */,
- 8000 /* max */,
- 50 /* bucket count */);
-}
-
-void RecordPendingChildCount(size_t count) {
- DCHECK_LE(count, static_cast<size_t>(std::numeric_limits<int32_t>::max()));
-
- // 8k is the maximum number of file descriptors allowed in Chrome.
- UMA_HISTOGRAM_CUSTOM_COUNTS("Mojo.System.Node.PendingChildren",
- static_cast<int32_t>(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<base::TaskRunner> 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<base::TaskRunner> 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::PortRef> ports_to_close;
- {
- std::vector<std::string> 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<PortObserver> 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<PortsMessage> 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<NodeChannel> 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<PlatformSharedBuffer> 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<NodeChannel> 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<NodeChannel> channel =
- NodeChannel::Create(this, ConnectionParams(std::move(server_handle)),
- io_task_runner_, process_error_callback);
-
-#else // !defined(OS_MACOSX) && !defined(OS_NACL)
- scoped_refptr<NodeChannel> 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<NodeChannel> 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<NodeChannel> 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<NodeChannel> NodeController::GetParentChannel() {
- ports::NodeName parent_name;
- {
- base::AutoLock lock(parent_lock_);
- parent_name = parent_name_;
- }
- return GetPeerChannel(parent_name);
-}
-
-scoped_refptr<NodeChannel> 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<NodeChannel> 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::PortRef> 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<std::string> 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<PortsMessage*>(message.get())->TakeChannelMessage();
-
- scoped_refptr<NodeChannel> 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<NodeChannel> 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<NodeChannel> 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<NodeChannel> 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<ports::ScopedMessage> 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<int32_t>(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<scoped_refptr<NodeChannel>> 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<PortsMessage*>(message.get())->TakeChannelMessage();
- CHECK(!channel_message->has_handles());
-
- scoped_refptr<NodeChannel> 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<ports::UserData> user_data;
- node_->GetUserData(port, &user_data);
-
- PortObserver* observer = static_cast<PortObserver*>(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<NodeChannel> 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<NodeChannel> 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<NodeChannel> 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<NodeChannel> 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<NodeChannel> 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<NodeChannel> 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<NodeChannel> 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<NodeChannel> 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<ports::NodeName> pending_broker_clients;
- std::unordered_map<ports::NodeName, OutgoingMessageQueue>
- 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<NodeChannel> 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<PortsMessage> 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<NodeChannel> 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<NodeChannel> 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<NodeChannel> 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<NodeChannel> 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<NodeChannel> 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::PortRef> 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<NodeChannel> 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
deleted file mode 100644
index 46a2d61208..0000000000
--- a/mojo/edk/system/node_controller.h
+++ /dev/null
@@ -1,378 +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 MOJO_EDK_SYSTEM_NODE_CONTROLLER_H_
-#define MOJO_EDK_SYSTEM_NODE_CONTROLLER_H_
-
-#include <memory>
-#include <queue>
-#include <unordered_map>
-#include <unordered_set>
-#include <utility>
-#include <vector>
-
-#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<base::TaskRunner> 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<base::TaskRunner> 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<PortObserver> 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<PortsMessage> 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<PlatformSharedBuffer> 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<ports::NodeName,
- scoped_refptr<NodeChannel>>;
- using OutgoingMessageQueue = std::queue<Channel::MessagePtr>;
-
- struct ReservedPort {
- ports::PortRef port;
- const std::string child_token;
- };
-
- struct PeerConnection {
- PeerConnection();
- PeerConnection(const PeerConnection& other);
- PeerConnection(PeerConnection&& other);
- PeerConnection(scoped_refptr<NodeChannel> channel,
- const ports::PortRef& local_port,
- const std::string& peer_token);
- ~PeerConnection();
-
- PeerConnection& operator=(const PeerConnection& other);
- PeerConnection& operator=(PeerConnection&& other);
-
-
- scoped_refptr<NodeChannel> 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<NodeChannel> GetPeerChannel(const ports::NodeName& name);
- scoped_refptr<NodeChannel> GetParentChannel();
- scoped_refptr<NodeChannel> GetBrokerChannel();
-
- void AddPeer(const ports::NodeName& name,
- scoped_refptr<NodeChannel> 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<ports::Node> node_;
- scoped_refptr<base::TaskRunner> 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<ports::NodeName, OutgoingMessageQueue>
- 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<std::string, ReservedPort> reserved_ports_;
- // TODO(amistry): This _really_ needs to be a bimap. Unfortunately, we don't
- // have one yet :(
- std::unordered_map<ports::NodeName, std::string> 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<std::pair<std::string, ports::PortRef>> 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<NodeChannel> 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<ports::NodeName> pending_broker_clients_;
-
- // Messages waiting to be relayed by the broker once it's known.
- std::unordered_map<ports::NodeName, OutgoingMessageQueue>
- pending_relay_messages_;
-
- // Guards |incoming_messages_| and |incoming_messages_task_posted_|.
- base::Lock messages_lock_;
- std::queue<ports::ScopedMessage> 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<ports::NodeName, PeerConnection>;
- PeerNodeMap peer_connections_;
-
- // Maps from peer token to node name, pending or not.
- std::unordered_map<std::string, ports::NodeName> 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> 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<MachPortRelay> 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
deleted file mode 100644
index e1b337d5f7..0000000000
--- a/mojo/edk/system/options_validation.h
+++ /dev/null
@@ -1,97 +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.
-
-// 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 <stddef.h>
-#include <stdint.h>
-
-#include <algorithm>
-
-#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 Options>
-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<MOJO_ALIGNOF(Options)>(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<const uint32_t*>(options);
- if (struct_size < sizeof(uint32_t))
- return 0;
-
- return std::min(static_cast<size_t>(struct_size), sizeof(Options));
- }
-
- template <size_t alignment>
- static bool IsAligned(const void* pointer) {
- return reinterpret_cast<uintptr_t>(pointer) % alignment == 0;
- }
-
- const Options* options_;
-
- DISALLOW_COPY_AND_ASSIGN(UserOptionsReader);
-};
-
-// Macro to invoke |UserOptionsReader<Options>::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
deleted file mode 100644
index a01a92cfb1..0000000000
--- a/mojo/edk/system/options_validation_unittest.cc
+++ /dev/null
@@ -1,134 +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 "mojo/edk/system/options_validation.h"
-
-#include <stddef.h>
-#include <stdint.h>
-
-#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<uint32_t>(sizeof(TestOptions));
-
-TEST(OptionsValidationTest, Valid) {
- {
- const TestOptions kOptions = {kSizeOfTestOptions};
- UserOptionsReader<TestOptions> 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<uint32_t>(
- offsetof(TestOptions, struct_size) + sizeof(uint32_t))};
- UserOptionsReader<TestOptions> 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<uint32_t>(offsetof(TestOptions, flags) + sizeof(uint32_t))};
- UserOptionsReader<TestOptions> 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<TestOptions*>(buf);
- options->struct_size = kSizeOfTestOptions + 1;
- UserOptionsReader<TestOptions> 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<TestOptions*>(buf);
- options->struct_size = kSizeOfTestOptions + 4;
- UserOptionsReader<TestOptions> 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<uint32_t>(i)};
- UserOptionsReader<TestOptions> 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<TestOptions> reader((nullptr)); },
- kMemoryCheckFailedRegex);
-
- // Unaligned:
- EXPECT_DEATH_IF_SUPPORTED(
- {
- UserOptionsReader<TestOptions> reader(
- reinterpret_cast<const TestOptions*>(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<uintptr_t>(buffer) % 8 == 0)
- ? reinterpret_cast<TestOptions*>(&buffer[1])
- : reinterpret_cast<TestOptions*>(&buffer[0]);
- options->struct_size = static_cast<uint32_t>(sizeof(TestOptions));
- UserOptionsReader<TestOptions> 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
deleted file mode 100644
index 3e708c2517..0000000000
--- a/mojo/edk/system/platform_handle_dispatcher.cc
+++ /dev/null
@@ -1,104 +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 "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> 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> 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
deleted file mode 100644
index a36c7a0e22..0000000000
--- a/mojo/edk/system/platform_handle_dispatcher.h
+++ /dev/null
@@ -1,61 +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 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<PlatformHandleDispatcher> 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<PlatformHandleDispatcher> 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
deleted file mode 100644
index 7a942622b0..0000000000
--- a/mojo/edk/system/platform_handle_dispatcher_unittest.cc
+++ /dev/null
@@ -1,123 +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 "mojo/edk/system/platform_handle_dispatcher.h"
-
-#include <stdio.h>
-#include <utility>
-
-#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<PlatformHandleDispatcher> 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<PlatformHandleDispatcher> 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<PlatformHandleDispatcher*>(
- 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
deleted file mode 100644
index f29d62b04f..0000000000
--- a/mojo/edk/system/platform_wrapper_unittest.cc
+++ /dev/null
@@ -1,212 +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 <stdint.h>
-#include <string.h>
-
-#include <algorithm>
-#include <string>
-#include <vector>
-
-#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 <windows.h>
-#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<uint64_t>(file);
-#else
- return static_cast<uint64_t>(file);
-#endif
-}
-
-base::PlatformFile PlatformFileFromPlatformHandleValue(uint64_t value) {
-#if defined(OS_WIN)
- return reinterpret_cast<base::PlatformFile>(value);
-#else
- return static_cast<base::PlatformFile>(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<int>(kMessage.size())),
- static_cast<int>(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<char> data(message.size());
- EXPECT_EQ(file.ReadAtCurrentPos(data.data(), static_cast<int>(data.size())),
- static_cast<int>(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<uint64_t>(memory_handle.GetMemoryObject());
-#elif defined(OS_POSIX)
- os_buffer.value = static_cast<uint64_t>(memory_handle.fd);
-#elif defined(OS_WIN)
- os_buffer.value = reinterpret_cast<uint64_t>(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<mach_port_t>(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<int>(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<HANDLE>(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<const char*>(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
deleted file mode 100644
index 5c82761982..0000000000
--- a/mojo/edk/system/ports/BUILD.gn
+++ /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.
-
-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
deleted file mode 100644
index 2e2208641f..0000000000
--- a/mojo/edk/system/ports/event.cc
+++ /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.
-
-#include "mojo/edk/system/ports/event.h"
-
-#include <string.h>
-
-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
deleted file mode 100644
index a66dfc1186..0000000000
--- a/mojo/edk/system/ports/event.h
+++ /dev/null
@@ -1,111 +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 MOJO_EDK_SYSTEM_PORTS_EVENT_H_
-#define MOJO_EDK_SYSTEM_PORTS_EVENT_H_
-
-#include <stdint.h>
-
-#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<const EventHeader*>(message.header_bytes());
-}
-
-inline EventHeader* GetMutableEventHeader(Message* message) {
- return static_cast<EventHeader*>(message->mutable_header_bytes());
-}
-
-template <typename EventData>
-inline const EventData* GetEventData(const Message& message) {
- return reinterpret_cast<const EventData*>(
- reinterpret_cast<const char*>(GetEventHeader(message) + 1));
-}
-
-template <typename EventData>
-inline EventData* GetMutableEventData(Message* message) {
- return reinterpret_cast<EventData*>(
- reinterpret_cast<char*>(GetMutableEventHeader(message) + 1));
-}
-
-inline const PortDescriptor* GetPortDescriptors(const UserEventData* event) {
- return reinterpret_cast<const PortDescriptor*>(
- reinterpret_cast<const char*>(event + 1));
-}
-
-inline PortDescriptor* GetMutablePortDescriptors(UserEventData* event) {
- return reinterpret_cast<PortDescriptor*>(reinterpret_cast<char*>(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
deleted file mode 100644
index 5d3c000a3a..0000000000
--- a/mojo/edk/system/ports/message.cc
+++ /dev/null
@@ -1,100 +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 <stdlib.h>
-
-#include <limits>
-
-#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<const EventHeader*>(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<const UserEventData*>(
- reinterpret_cast<const char*>(header + 1));
- if (event_data->num_ports > std::numeric_limits<uint16_t>::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<char*>(start);
- memset(start_, 0, num_header_bytes_);
- GetMutableEventHeader(this)->type = EventType::kUser;
- GetMutableEventData<UserEventData>(this)->num_ports =
- static_cast<uint32_t>(num_ports_);
-}
-
-} // namespace ports
-} // namespace edk
-} // namespace mojo
diff --git a/mojo/edk/system/ports/message.h b/mojo/edk/system/ports/message.h
deleted file mode 100644
index 95fa04676c..0000000000
--- a/mojo/edk/system/ports/message.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 MOJO_EDK_SYSTEM_PORTS_MESSAGE_H_
-#define MOJO_EDK_SYSTEM_PORTS_MESSAGE_H_
-
-#include <stddef.h>
-
-#include <memory>
-
-#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<Message*>(this)->mutable_payload_bytes();
- }
- size_t num_payload_bytes() const { return num_payload_bytes_; }
-
- PortName* mutable_ports() {
- return reinterpret_cast<PortName*>(start_ + num_header_bytes_);
- }
- const PortName* ports() const {
- return const_cast<Message*>(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<Message>;
-
-} // 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
deleted file mode 100644
index bf8fa21966..0000000000
--- a/mojo/edk/system/ports/message_filter.h
+++ /dev/null
@@ -1,29 +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 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
deleted file mode 100644
index defb1b6c75..0000000000
--- a/mojo/edk/system/ports/message_queue.cc
+++ /dev/null
@@ -1,87 +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 "mojo/edk/system/ports/message_queue.h"
-
-#include <algorithm>
-
-#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<UserEventData>(*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<PortName>* 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
deleted file mode 100644
index d9a47ed0a1..0000000000
--- a/mojo/edk/system/ports/message_queue.h
+++ /dev/null
@@ -1,73 +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 MOJO_EDK_SYSTEM_PORTS_MESSAGE_QUEUE_H_
-#define MOJO_EDK_SYSTEM_PORTS_MESSAGE_QUEUE_H_
-
-#include <stdint.h>
-
-#include <deque>
-#include <functional>
-#include <limits>
-#include <vector>
-
-#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<uint64_t>::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<PortName>* ports);
-
- private:
- std::vector<ScopedMessage> 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
deleted file mode 100644
index ea17698f97..0000000000
--- a/mojo/edk/system/ports/name.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 "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
deleted file mode 100644
index 72e41b92ab..0000000000
--- a/mojo/edk/system/ports/name.h
+++ /dev/null
@@ -1,74 +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 MOJO_EDK_SYSTEM_PORTS_NAME_H_
-#define MOJO_EDK_SYSTEM_PORTS_NAME_H_
-
-#include <stdint.h>
-
-#include <ostream>
-#include <tuple>
-
-#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<mojo::edk::ports::PortName> {
- std::size_t operator()(const mojo::edk::ports::PortName& name) const {
- return base::HashInts64(name.v1, name.v2);
- }
-};
-
-template <>
-struct hash<mojo::edk::ports::NodeName> {
- 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
deleted file mode 100644
index f9a3feb805..0000000000
--- a/mojo/edk/system/ports/node.cc
+++ /dev/null
@@ -1,1385 +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 "mojo/edk/system/ports/node.h"
-
-#include <string.h>
-
-#include <utility>
-
-#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> 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> 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<UserData> 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<UserData>* 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<PortName> 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<Port> 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<ObserveProxyEventData>(*message));
- case EventType::kObserveProxyAck:
- return OnObserveProxyAck(
- header->port_name,
- GetEventData<ObserveProxyAckEventData>(*message)->last_sequence_num);
- case EventType::kObserveClosure:
- return OnObserveClosure(
- header->port_name,
- GetEventData<ObserveClosureEventData>(*message)->last_sequence_num);
- case EventType::kMergePort:
- return OnMergePort(header->port_name,
- *GetEventData<MergePortEventData>(*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<UserEventData>(*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> 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> 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> 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<NodeName, ScopedMessage>(
- 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> 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> 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> 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<Port> 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> 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<Port> Node::GetPort(const PortName& port_name) {
- base::AutoLock lock(ports_lock_);
- return GetPort_Locked(port_name);
-}
-
-scoped_refptr<Port> 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> 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<UserEventData>(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<scoped_refptr<Port>> 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<UserEventData>(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<UserEventData>(*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<PortRef> ports_to_notify;
- std::vector<PortName> dead_proxies_to_broadcast;
- std::deque<PortName> 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
deleted file mode 100644
index 55b8d27547..0000000000
--- a/mojo/edk/system/ports/node.h
+++ /dev/null
@@ -1,228 +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 MOJO_EDK_SYSTEM_PORTS_NODE_H_
-#define MOJO_EDK_SYSTEM_PORTS_NODE_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <queue>
-#include <unordered_map>
-
-#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<UserData> user_data);
- int GetUserData(const PortRef& port_ref,
- scoped_refptr<UserData>* 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> port);
- void ErasePort(const PortName& port_name);
- void ErasePort_Locked(const PortName& port_name);
- scoped_refptr<Port> GetPort(const PortName& port_name);
- scoped_refptr<Port> 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 <typename EventData>
- 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<PortName, scoped_refptr<Port>> 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
deleted file mode 100644
index 8547302a5e..0000000000
--- a/mojo/edk/system/ports/node_delegate.h
+++ /dev/null
@@ -1,48 +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 MOJO_EDK_SYSTEM_PORTS_NODE_DELEGATE_H_
-#define MOJO_EDK_SYSTEM_PORTS_NODE_DELEGATE_H_
-
-#include <stddef.h>
-
-#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
deleted file mode 100644
index e4403aed78..0000000000
--- a/mojo/edk/system/ports/port.cc
+++ /dev/null
@@ -1,24 +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 "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
deleted file mode 100644
index ea53d43b5f..0000000000
--- a/mojo/edk/system/ports/port.h
+++ /dev/null
@@ -1,60 +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 MOJO_EDK_SYSTEM_PORTS_PORT_H_
-#define MOJO_EDK_SYSTEM_PORTS_PORT_H_
-
-#include <memory>
-#include <queue>
-#include <utility>
-#include <vector>
-
-#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<Port> {
- 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<std::pair<NodeName, ScopedMessage>> send_on_proxy_removal;
- scoped_refptr<UserData> 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>;
-
- ~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
deleted file mode 100644
index 675754d488..0000000000
--- a/mojo/edk/system/ports/port_ref.cc
+++ /dev/null
@@ -1,36 +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 "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> 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
deleted file mode 100644
index 59036c3869..0000000000
--- a/mojo/edk/system/ports/port_ref.h
+++ /dev/null
@@ -1,41 +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 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> 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> 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
deleted file mode 100644
index cb48b3eb2f..0000000000
--- a/mojo/edk/system/ports/ports_unittest.cc
+++ /dev/null
@@ -1,1478 +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 <inttypes.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <map>
-#include <queue>
-#include <sstream>
-#include <utility>
-
-#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<const char*>(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<ScopedMessage> incoming_messages_;
- std::queue<ScopedMessage> 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<base::WaitableEvent*> 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<NodeName, TestNode*> 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
deleted file mode 100644
index 73e7d17b32..0000000000
--- a/mojo/edk/system/ports/user_data.h
+++ /dev/null
@@ -1,25 +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 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<UserData> {
- protected:
- friend class base::RefCountedThreadSafe<UserData>;
-
- 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
deleted file mode 100644
index 5f3e8c0125..0000000000
--- a/mojo/edk/system/ports_message.cc
+++ /dev/null
@@ -1,62 +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 "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> 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<char*>(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<char*>(ptr);
- }
-}
-
-} // namespace edk
-} // namespace mojo
diff --git a/mojo/edk/system/ports_message.h b/mojo/edk/system/ports_message.h
deleted file mode 100644
index 542b981700..0000000000
--- a/mojo/edk/system/ports_message.h
+++ /dev/null
@@ -1,69 +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 MOJO_EDK_SYSTEM_PORTS_MESSAGE_H__
-#define MOJO_EDK_SYSTEM_PORTS_MESSAGE_H__
-
-#include <memory>
-#include <utility>
-
-#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<PortsMessage> 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
deleted file mode 100644
index 5de65d7b64..0000000000
--- a/mojo/edk/system/request_context.cc
+++ /dev/null
@@ -1,110 +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 "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<base::ThreadLocalPointer<RequestContext>>::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 :
- 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> 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> 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> 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
deleted file mode 100644
index d1f43bdfbd..0000000000
--- a/mojo/edk/system/request_context.h
+++ /dev/null
@@ -1,107 +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 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<typename T> 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> 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> watch);
-
- private:
- // Is this request context the current one?
- bool IsCurrent() const;
-
- struct WatchNotifyFinalizer {
- WatchNotifyFinalizer(scoped_refptr<Watch> watch,
- MojoResult result,
- const HandleSignalsState& state);
- WatchNotifyFinalizer(const WatchNotifyFinalizer& other);
- ~WatchNotifyFinalizer();
-
- scoped_refptr<Watch> 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<WatchNotifyFinalizer, kStaticWatchFinalizersCapacity>;
- using WatchCancelFinalizerList =
- base::StackVector<scoped_refptr<Watch>, 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<RequestContext>* 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
deleted file mode 100644
index df391050a2..0000000000
--- a/mojo/edk/system/shared_buffer_dispatcher.cc
+++ /dev/null
@@ -1,339 +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 "mojo/edk/system/shared_buffer_dispatcher.h"
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <limits>
-#include <memory>
-#include <utility>
-
-#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<uint32_t>(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<MojoCreateSharedBufferOptions> 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<SharedBufferDispatcher>* 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<PlatformSharedBuffer> shared_buffer;
- if (node_controller) {
- shared_buffer =
- node_controller->CreateSharedBuffer(static_cast<size_t>(num_bytes));
- } else {
- shared_buffer =
- PlatformSharedBuffer::Create(static_cast<size_t>(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<PlatformSharedBuffer>& shared_buffer,
- scoped_refptr<SharedBufferDispatcher>* result) {
- if (!shared_buffer)
- return MOJO_RESULT_INVALID_ARGUMENT;
-
- *result = CreateInternal(shared_buffer);
- return MOJO_RESULT_OK;
-}
-
-// static
-scoped_refptr<SharedBufferDispatcher> 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<const SerializedState*>(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<PlatformSharedBuffer> shared_buffer(
- PlatformSharedBuffer::CreateFromPlatformHandle(
- static_cast<size_t>(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<PlatformSharedBuffer>
-SharedBufferDispatcher::PassPlatformSharedBuffer() {
- base::AutoLock lock(lock_);
- if (!shared_buffer_ || in_transit_)
- return nullptr;
-
- scoped_refptr<PlatformSharedBuffer> 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<Dispatcher>* 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<PlatformSharedBuffer> 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<PlatformSharedBufferMapping>* mapping) {
- if (offset > static_cast<uint64_t>(std::numeric_limits<size_t>::max()))
- return MOJO_RESULT_INVALID_ARGUMENT;
- if (num_bytes > static_cast<uint64_t>(std::numeric_limits<size_t>::max()))
- return MOJO_RESULT_INVALID_ARGUMENT;
-
- base::AutoLock lock(lock_);
- DCHECK(shared_buffer_);
- if (in_transit_ ||
- !shared_buffer_->IsValidMap(static_cast<size_t>(offset),
- static_cast<size_t>(num_bytes))) {
- return MOJO_RESULT_INVALID_ARGUMENT;
- }
-
- DCHECK(mapping);
- *mapping = shared_buffer_->MapNoCheck(static_cast<size_t>(offset),
- static_cast<size_t>(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<SerializedState*>(destination);
- base::AutoLock lock(lock_);
- serialization->num_bytes =
- static_cast<uint64_t>(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<bool>(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<PlatformSharedBuffer> 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<uint32_t>(sizeof(MojoDuplicateBufferHandleOptions)),
- MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE};
-
- *out_options = kDefaultOptions;
- if (!in_options)
- return MOJO_RESULT_OK;
-
- UserOptionsReader<MojoDuplicateBufferHandleOptions> 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
deleted file mode 100644
index 6015595317..0000000000
--- a/mojo/edk/system/shared_buffer_dispatcher.h
+++ /dev/null
@@ -1,127 +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 MOJO_EDK_SYSTEM_SHARED_BUFFER_DISPATCHER_H_
-#define MOJO_EDK_SYSTEM_SHARED_BUFFER_DISPATCHER_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <utility>
-
-#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<SharedBufferDispatcher>* result);
-
- // Create a |SharedBufferDispatcher| from |shared_buffer|.
- static MojoResult CreateFromPlatformSharedBuffer(
- const scoped_refptr<PlatformSharedBuffer>& shared_buffer,
- scoped_refptr<SharedBufferDispatcher>* result);
-
- // The "opposite" of SerializeAndClose(). Called by Dispatcher::Deserialize().
- 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);
-
- // Passes the underlying platform shared buffer. This dispatcher must be
- // closed after calling this function.
- scoped_refptr<PlatformSharedBuffer> PassPlatformSharedBuffer();
-
- // Dispatcher:
- Type GetType() const override;
- MojoResult Close() override;
- MojoResult DuplicateBufferHandle(
- const MojoDuplicateBufferHandleOptions* options,
- scoped_refptr<Dispatcher>* new_dispatcher) override;
- MojoResult MapBuffer(
- uint64_t offset,
- uint64_t num_bytes,
- MojoMapBufferFlags flags,
- std::unique_ptr<PlatformSharedBufferMapping>* 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<SharedBufferDispatcher> CreateInternal(
- scoped_refptr<PlatformSharedBuffer> shared_buffer) {
- return make_scoped_refptr(
- new SharedBufferDispatcher(std::move(shared_buffer)));
- }
-
- explicit SharedBufferDispatcher(
- scoped_refptr<PlatformSharedBuffer> 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<PlatformSharedBuffer> 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
deleted file mode 100644
index c95bdc3b70..0000000000
--- a/mojo/edk/system/shared_buffer_dispatcher_unittest.cc
+++ /dev/null
@@ -1,312 +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 "mojo/edk/system/shared_buffer_dispatcher.h"
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <limits>
-
-#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<SharedBufferDispatcher> 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<PlatformSharedBufferMapping> 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<char*>(mapping1->GetBase())[50] = 'x';
-
- std::unique_ptr<PlatformSharedBufferMapping> 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<char*>(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<char*>(mapping2->GetBase())[1] = 'y';
- EXPECT_EQ('y', static_cast<char*>(mapping1->GetBase())[51]);
-}
-
-TEST_F(SharedBufferDispatcherTest, CreateAndMapBufferFromPlatformBuffer) {
- scoped_refptr<PlatformSharedBuffer> platform_shared_buffer =
- PlatformSharedBuffer::Create(100);
- ASSERT_TRUE(platform_shared_buffer);
- scoped_refptr<SharedBufferDispatcher> 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<PlatformSharedBufferMapping> 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<char*>(mapping1->GetBase())[50] = 'x';
-
- std::unique_ptr<PlatformSharedBufferMapping> 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<char*>(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<char*>(mapping2->GetBase())[1] = 'y';
- EXPECT_EQ('y', static_cast<char*>(mapping1->GetBase())[51]);
-}
-
-TEST_F(SharedBufferDispatcherTest, DuplicateBufferHandle) {
- scoped_refptr<SharedBufferDispatcher> dispatcher1;
- EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create(
- SharedBufferDispatcher::kDefaultCreateOptions,
- nullptr, 100, &dispatcher1));
-
- // Map and write something.
- std::unique_ptr<PlatformSharedBufferMapping> mapping;
- EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->MapBuffer(
- 0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping));
- static_cast<char*>(mapping->GetBase())[0] = 'x';
- mapping.reset();
-
- // Duplicate |dispatcher1| and then close it.
- scoped_refptr<Dispatcher> 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<char*>(mapping->GetBase())[0]);
-
- EXPECT_EQ(MOJO_RESULT_OK, dispatcher2->Close());
-}
-
-TEST_F(SharedBufferDispatcherTest, DuplicateBufferHandleOptionsValid) {
- scoped_refptr<SharedBufferDispatcher> 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<Dispatcher> 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<PlatformSharedBufferMapping> 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<SharedBufferDispatcher> 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<Dispatcher> dispatcher2;
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
- dispatcher1->DuplicateBufferHandle(&options, &dispatcher2));
- EXPECT_FALSE(dispatcher2);
- }
-
- // Unknown |flags|.
- {
- MojoDuplicateBufferHandleOptions options = {
- sizeof(MojoDuplicateBufferHandleOptions), ~0u};
- scoped_refptr<Dispatcher> 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<SharedBufferDispatcher> dispatcher;
- EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED,
- SharedBufferDispatcher::Create(
- SharedBufferDispatcher::kDefaultCreateOptions, nullptr,
- std::numeric_limits<uint64_t>::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<SharedBufferDispatcher> dispatcher;
- EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create(
- SharedBufferDispatcher::kDefaultCreateOptions,
- nullptr, 100, &dispatcher));
-
- std::unique_ptr<PlatformSharedBufferMapping> 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
deleted file mode 100644
index 3a728728a5..0000000000
--- a/mojo/edk/system/shared_buffer_unittest.cc
+++ /dev/null
@@ -1,318 +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 <string.h>
-
-#include <string>
-#include <utility>
-
-#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
deleted file mode 100644
index e8b0cd1914..0000000000
--- a/mojo/edk/system/signals_unittest.cc
+++ /dev/null
@@ -1,76 +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 "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
deleted file mode 100644
index 5bbf0057b0..0000000000
--- a/mojo/edk/system/system_impl_export.h
+++ /dev/null
@@ -1,29 +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 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
deleted file mode 100644
index 4a39cf73da..0000000000
--- a/mojo/edk/system/test_utils.cc
+++ /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.
-
-#include "mojo/edk/system/test_utils.h"
-
-#include <stdint.h>
-
-#include <limits>
-
-#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<MojoDeadline>(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<MojoDeadline>(
- TestTimeouts::tiny_timeout().InMicroseconds());
-}
-
-MojoDeadline ActionDeadline() {
- return static_cast<MojoDeadline>(
- TestTimeouts::action_timeout().InMicroseconds());
-}
-
-void Sleep(MojoDeadline deadline) {
- CHECK_LE(deadline,
- static_cast<MojoDeadline>(std::numeric_limits<int64_t>::max()));
- base::PlatformThread::Sleep(
- base::TimeDelta::FromMicroseconds(static_cast<int64_t>(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<MojoDeadline>(result);
-}
-
-} // namespace test
-} // namespace edk
-} // namespace mojo
diff --git a/mojo/edk/system/test_utils.h b/mojo/edk/system/test_utils.h
deleted file mode 100644
index 1c90dc1717..0000000000
--- a/mojo/edk/system/test_utils.h
+++ /dev/null
@@ -1,59 +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 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
deleted file mode 100644
index cf08ac37ee..0000000000
--- a/mojo/edk/system/watch.cc
+++ /dev/null
@@ -1,83 +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 "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<WatcherDispatcher>& watcher,
- const scoped_refptr<Dispatcher>& 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<const MojoHandleSignalsState*>(&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
deleted file mode 100644
index f277de9917..0000000000
--- a/mojo/edk/system/watch.h
+++ /dev/null
@@ -1,124 +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 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<Watch> {
- public:
- // Constructs a Watch which represents a watch within |watcher| associated
- // with |context|, watching |dispatcher| for |signals|.
- Watch(const scoped_refptr<WatcherDispatcher>& watcher,
- const scoped_refptr<Dispatcher>& 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>& 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>;
-
- ~Watch();
-
-#if DCHECK_IS_ON()
- void AssertWatcherLockAcquired() const;
-#else
- void AssertWatcherLockAcquired() const {}
-#endif
-
- const scoped_refptr<WatcherDispatcher> watcher_;
- const scoped_refptr<Dispatcher> 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
deleted file mode 100644
index 409dd2a922..0000000000
--- a/mojo/edk/system/watcher_dispatcher.cc
+++ /dev/null
@@ -1,232 +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 "mojo/edk/system/watcher_dispatcher.h"
-
-#include <algorithm>
-#include <limits>
-#include <map>
-
-#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> 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<MojoHandleSignalsState>(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<uintptr_t, scoped_refptr<Watch>> 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> 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> 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> 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<uint32_t>::max());
- *num_ready_contexts = std::min(
- *num_ready_contexts, static_cast<uint32_t>(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
deleted file mode 100644
index 605a3150cc..0000000000
--- a/mojo/edk/system/watcher_dispatcher.h
+++ /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.
-
-#ifndef MOJO_EDK_SYSTEM_WATCHER_DISPATCHER_H_
-#define MOJO_EDK_SYSTEM_WATCHER_DISPATCHER_H_
-
-#include <map>
-#include <set>
-
-#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> 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<const Watch*>;
-
- ~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<uintptr_t, scoped_refptr<Watch>> watches_;
-
- // A mapping from watched dispatcher to Watch.
- std::map<Dispatcher*, scoped_refptr<Watch>> 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
deleted file mode 100644
index 0355b58795..0000000000
--- a/mojo/edk/system/watcher_set.cc
+++ /dev/null
@@ -1,82 +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 "mojo/edk/system/watcher_set.h"
-
-#include <utility>
-
-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<WatcherDispatcher>& 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<WatcherDispatcher>& 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
deleted file mode 100644
index 2b7ef2c5ac..0000000000
--- a/mojo/edk/system/watcher_set.h
+++ /dev/null
@@ -1,71 +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 MOJO_EDK_SYSTEM_WATCHER_SET_H_
-#define MOJO_EDK_SYSTEM_WATCHER_SET_H_
-
-#include <map>
-
-#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<WatcherDispatcher>& watcher,
- uintptr_t context,
- const HandleSignalsState& current_state);
-
- // Removes a watcher+context.
- MojoResult Remove(WatcherDispatcher* watcher, uintptr_t context);
-
- private:
- using ContextSet = std::set<uintptr_t>;
-
- struct Entry {
- Entry(const scoped_refptr<WatcherDispatcher>& dispatcher);
- Entry(Entry&& other);
- ~Entry();
-
- Entry& operator=(Entry&& other);
-
- scoped_refptr<WatcherDispatcher> dispatcher;
- ContextSet contexts;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(Entry);
- };
-
- Dispatcher* const owner_;
- std::map<WatcherDispatcher*, Entry> watchers_;
- base::Optional<HandleSignalsState> 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
deleted file mode 100644
index dd396cd905..0000000000
--- a/mojo/edk/system/watcher_unittest.cc
+++ /dev/null
@@ -1,1637 +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 <stdint.h>
-
-#include <map>
-#include <memory>
-#include <set>
-
-#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<void(MojoResult, MojoHandleSignalsState)>;
-
- 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<NotificationContext>(callback);
- NotificationContext* raw_context = context.get();
- raw_context->SetCancelCallback(base::Bind(
- [](std::unique_ptr<NotificationContext> context,
- const base::Closure& cancel_callback) {
- if (cancel_callback)
- cancel_callback.Run();
- },
- base::Passed(&context), cancel_callback));
- return reinterpret_cast<uintptr_t>(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<NotificationContext*>(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<uintptr_t> 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
deleted file mode 100644
index a15456a32f..0000000000
--- a/mojo/edk/test/BUILD.gn
+++ /dev/null
@@ -1,131 +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.
-
-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
deleted file mode 100644
index 71a5e3b9a5..0000000000
--- a/mojo/edk/test/mojo_test_base.cc
+++ /dev/null
@@ -1,327 +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 "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<ClientController>(
- 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<uint32_t>(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<void**>(&data),
- MOJO_MAP_BUFFER_FLAG_NONE));
- memcpy(data, s.data(), s.size());
- EXPECT_EQ(MOJO_RESULT_OK, MojoUnmapBuffer(static_cast<void*>(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<void**>(&data),
- MOJO_MAP_BUFFER_FLAG_NONE));
- EXPECT_EQ(s, base::StringPiece(data, s.size()));
- EXPECT_EQ(MOJO_RESULT_OK, MojoUnmapBuffer(static_cast<void*>(data)));
-}
-
-// static
-void MojoTestBase::CreateDataPipe(MojoHandle *p0,
- MojoHandle* p1,
- size_t capacity) {
- MojoCreateDataPipeOptions options;
- options.struct_size = static_cast<uint32_t>(sizeof(options));
- options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
- options.element_num_bytes = 1;
- options.capacity_num_bytes = static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(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<char> buffer(size);
- uint32_t num_bytes = static_cast<uint32_t>(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<uint32_t>(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
deleted file mode 100644
index 35e2c2b12f..0000000000
--- a/mojo/edk/test/mojo_test_base.h
+++ /dev/null
@@ -1,249 +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 MOJO_EDK_TEST_MOJO_TEST_BASE_H_
-#define MOJO_EDK_TEST_MOJO_TEST_BASE_H_
-
-#include <memory>
-#include <string>
-#include <utility>
-
-#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<void(ScopedMessagePipeHandle)>;
-
- 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 <typename HandlerFunc>
- 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<std::unique_ptr<ClientController>> 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
deleted file mode 100644
index cf377827f5..0000000000
--- a/mojo/edk/test/multiprocess_test_helper.cc
+++ /dev/null
@@ -1,263 +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.
-
-#include "mojo/edk/test/multiprocess_test_helper.h"
-
-#include <functional>
-#include <set>
-#include <utility>
-
-#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 <typename Func>
-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<std::string> 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<int(MojoHandle)>& 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<void(MojoHandle)>& 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
deleted file mode 100644
index dc1c9bcc18..0000000000
--- a/mojo/edk/test/multiprocess_test_helper.h
+++ /dev/null
@@ -1,110 +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 MOJO_EDK_TEST_MULTIPROCESS_TEST_HELPER_H_
-#define MOJO_EDK_TEST_MULTIPROCESS_TEST_HELPER_H_
-
-#include <string>
-
-#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<void(ScopedMessagePipeHandle)>;
-
- 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<int(MojoHandle)>& main,
- bool pass_pipe_ownership_to_main = false);
- static int RunClientTestMain(const base::Callback<void(MojoHandle)>& 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
deleted file mode 100644
index f7e9e832f4..0000000000
--- a/mojo/edk/test/multiprocess_test_helper_unittest.cc
+++ /dev/null
@@ -1,165 +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.
-
-#include "mojo/edk/test/multiprocess_test_helper.h"
-
-#include <stddef.h>
-
-#include <utility>
-
-#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 <fcntl.h>
-#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<int>(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
deleted file mode 100644
index 3ce3b47099..0000000000
--- a/mojo/edk/test/run_all_perftests.cc
+++ /dev/null
@@ -1,26 +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.
-
-#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
deleted file mode 100644
index a057825d8f..0000000000
--- a/mojo/edk/test/run_all_unittests.cc
+++ /dev/null
@@ -1,49 +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.
-
-#include <signal.h>
-
-#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
deleted file mode 100644
index 25684e4805..0000000000
--- a/mojo/edk/test/test_support_impl.cc
+++ /dev/null
@@ -1,84 +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 "mojo/edk/test/test_support_impl.h"
-
-#include <stddef.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <string>
-
-#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<std::string> 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<char**>(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
deleted file mode 100644
index ebb5ce658a..0000000000
--- a/mojo/edk/test/test_support_impl.h
+++ /dev/null
@@ -1,38 +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 MOJO_EDK_TEST_TEST_SUPPORT_IMPL_H_
-#define MOJO_EDK_TEST_TEST_SUPPORT_IMPL_H_
-
-#include <stdio.h>
-
-#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
deleted file mode 100644
index 939171b26f..0000000000
--- a/mojo/edk/test/test_utils.h
+++ /dev/null
@@ -1,55 +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 MOJO_EDK_TEST_TEST_UTILS_H_
-#define MOJO_EDK_TEST_TEST_UTILS_H_
-
-#include <stddef.h>
-#include <stdio.h>
-
-#include <string>
-
-#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
deleted file mode 100644
index 60d5db59db..0000000000
--- a/mojo/edk/test/test_utils_posix.cc
+++ /dev/null
@@ -1,94 +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 "mojo/edk/test/test_utils.h"
-
-#include <fcntl.h>
-#include <stddef.h>
-#include <unistd.h>
-
-#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
deleted file mode 100644
index 17bf5bbfa3..0000000000
--- a/mojo/edk/test/test_utils_win.cc
+++ /dev/null
@@ -1,115 +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 "mojo/edk/test/test_utils.h"
-
-#include <windows.h>
-#include <fcntl.h>
-#include <io.h>
-#include <stddef.h>
-#include <string.h>
-
-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<DWORD>(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<DWORD>(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<DWORD>(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<HANDLE>(_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<intptr_t>(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
index 3baf667064..bd094c8c32 100644
--- a/mojo/public/BUILD.gn
+++ b/mojo/public/BUILD.gn
@@ -23,6 +23,11 @@ group("sdk") {
deps = [
"c/system",
"cpp/bindings",
- "js",
+ ]
+}
+
+group("fuzzers") {
+ deps = [
+ "tools/fuzzers",
]
}
diff --git a/mojo/public/DEPS b/mojo/public/DEPS
deleted file mode 100644
index 2e49995741..0000000000
--- a/mojo/public/DEPS
+++ /dev/null
@@ -1,11 +0,0 @@
-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/c/system/BUILD.gn b/mojo/public/c/system/BUILD.gn
index 08185c7514..6cc2b02a7a 100644
--- a/mojo/public/c/system/BUILD.gn
+++ b/mojo/public/c/system/BUILD.gn
@@ -6,32 +6,33 @@ component("system") {
output_name = "mojo_public_system"
sources = [
+ "thunks.cc",
+ ]
+
+ defines = [ "MOJO_SYSTEM_IMPLEMENTATION" ]
+
+ public_deps = [
+ ":headers",
+ ]
+
+ deps = [
+ "//base",
+ ]
+}
+
+source_set("headers") {
+ public = [
"buffer.h",
"core.h",
"data_pipe.h",
"functions.h",
+ "invitation.h",
"macros.h",
"message_pipe.h",
"platform_handle.h",
"system_export.h",
- "thunks.cc",
"thunks.h",
+ "trap.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
index 2abe80ffc7..ec88e3e667 100644
--- a/mojo/public/c/system/README.md
+++ b/mojo/public/c/system/README.md
@@ -1,18 +1,19 @@
-# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo C System API
-This document is a subset of the [Mojo documentation](/mojo).
+# Mojo C System API
+This document is a subset of the [Mojo documentation](/mojo/README.md).
[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.
+The Mojo C System API is a lightweight API (with an stable, forward-compatible
+ABI) upon which all higher-level public Mojo APIs 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.
+handles for various types of state transitions. Finally, there are also APIs to
+bootstrap Mojo IPC between two processes.
This document provides a brief guide to API usage with example code snippets.
For a detailed API references please consult the headers in
@@ -42,9 +43,9 @@ 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.
+The higher level [system](/mojo/README.md#High-Level-System-APIs) and
+[bindings](/mojo/README.md#High-Level-Bindings-APIs) APIs provide helpers to
+simplify Mojo usage in this regard, at the expense of some flexibility.
## Result Codes
@@ -64,11 +65,14 @@ 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.)
+handle such as a POSIX file descriptor or a Windows system handle, a trap object
+(see [Signals & Traps](#Signals-Traps) below), or a process invitation (see
+[Invitations](#Invitations) below).
-All types of handles except for watchers (which are an inherently local concept)
-can be attached to messages and sent over message pipes.
+Message pipes, data pipes, shared buffers, and platform handles can all be
+attached to messages and sent over message pipes. Traps are an inherently
+process-local concept, and invitations are transmitted using special dedicated
+APIs.
Any `MojoHandle` may be closed by calling `MojoClose`:
@@ -92,12 +96,11 @@ 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...)
+[Invitations](#Invitations) provide the means to bootstrap one or more
+primordial cross-process message pipes between two processes. 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.
@@ -124,48 +127,92 @@ written to `b` are eventually readable from `a`. If `a` is closed at any point,
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.
+described in the [Signals & Traps](#Signals-Traps) section below.
-### Allocating Messages
+### Creating Messages
-In order to avoid redundant internal buffer copies, Mojo would like to allocate
-your message storage buffers for you. This is easy:
+Message pipes carry message objects which may or may not be serialized. You can
+create a new message object as follows:
``` c
MojoMessageHandle message;
-MojoResult result = MojoAllocMessage(6, NULL, 0, MOJO_ALLOC_MESSAGE_FLAG_NONE,
- &message);
+MojoResult result = MojoCreateMessage(nullptr, &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.
+Messages may be serialized with attached data or unserialized with an
+opaque context value. Unserialized messages support lazy serialization, allowing
+custom serialization logic to be invoked only if and when serialization is
+required, e.g. when the message needs to cross a process or language boundary.
-If we change our mind and decide not to send this message, we can delete it:
+To make a serialized message, you might write something like:
``` c
-MojoResult result = MojoFreeMessage(message);
+void* buffer;
+uint32_t buffer_size;
+MojoResult result = MojoAppendMessageData(message, nullptr, 6, nullptr, 0,
+ &buffer, &buffer_size);
+memcpy(buffer, "hello", 6);
```
-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:
+This attaches a data buffer to `message` with at least `6` bytes of storage
+capacity. The outputs returned in `buffer` and `buffer_size` can be used by the
+caller to fill in the message contents.
+
+Multiple calls to `MojoAppendMessageData` may be made on a single message
+object, and each call appends to any payload and handles accumulated so far.
+Before you can transmit a message carrying data you must commit to never calling
+`MojoAppendMessageData` again. You do this by passing the
+`MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE` flag:
``` c
-void* buffer = NULL;
-MojoResult result = MojoGetMessageBuffer(message, &buffer);
-memcpy(buffer, "hello", 6);
+MojoAppendMessageDataOptions options;
+options.struct_size = sizeof(options);
+options.flags = MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE;
+MojoResult result = MojoAppendMessageData(message, &options, 0, nullptr, 0,
+ &buffer, &buffer_size);
+```
+
+Creating lazily-serialized messages is also straightforward:
+
+``` c
+struct MyMessage {
+ // some interesting data...
+};
+
+void SerializeMessage(MojoMessageHandle message, uintptr_t context) {
+ struct MyMessage* my_message = (struct MyMessage*)context;
+
+ MojoResult result = MojoAppendMessageData(message, ...);
+ // Serialize however you like.
+}
+
+void DestroyMessage(uintptr_t context) {
+ free((void*)context);
+}
+
+MyMessage* data = malloc(sizeof(MyMessage));
+// initialize *data...
+
+MojoResult result = MojoSetMessageContext(
+ message, (uintptr_t)data, &SerializeMessage, &DestroyMessage, nullptr);
```
-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.
+If we change our mind and decide not to send the message, we can destroy it:
+
+``` c
+MojoResult result = MojoDestroyMessage(message);
+```
+
+Note that attempting to write a message will transfer ownership of the message
+object (and any attached handles) into the message pipe, and there is therefore
+no need to subsequently call `MojoDestroyMessage` on that message.
### Writing Messages
``` c
-result = MojoWriteMessageNew(a, message, MOJO_WRITE_MESSAGE_FLAG_NONE);
+result = MojoWriteMessage(a, message, nullptr);
```
`MojoWriteMessage` is a *non-blocking* call: it always returns
@@ -177,13 +224,14 @@ 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.
+[Signals & Traps](#Signals-Traps) 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.
+**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
@@ -192,16 +240,16 @@ 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);
+MojoResult result = MojoReadMessage(b, nullptr, &message);
```
-and map its buffer to retrieve the contents:
+and extract its data:
``` c
void* buffer = NULL;
-MojoResult result = MojoGetMessageBuffer(message, &buffer);
+uint32_t num_bytes;
+MojoResult result = MojoGetMessageData(message, nullptr, &buffer, &num_bytes,
+ nullptr, nullptr);
printf("Pipe says: %s", (const char*)buffer);
```
@@ -212,13 +260,22 @@ 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);
+MojoResult result = MojoReadMessage(b, nullptr, &message);
```
We'll get a `result` of `MOJO_RESULT_SHOULD_WAIT`, indicating that the pipe is
not yet readable.
+Note that message also may not have been serialized if it came from within the
+same process, in which case it may have no attached data and
+`MojoGetMessageData` will return `MOJO_RESULT_FAILED_PRECONDITION`. The
+message's unserialized context can instead be retrieved using
+`MojoGetMessageContext`.
+
+Messages read from a message pipe are owned by the caller and must be
+subsequently destroyed using `MojoDestroyMessage` (or, in theory, written to
+another pipe using `MojoWriteMessage`.)
+
### Messages With Handles
Probably the most useful feature of Mojo IPC is that message pipes can carry
@@ -233,52 +290,54 @@ interesting:
``` c
MojoHandle a, b;
MojoHandle c, d;
-MojoMessage message;
+MojoCreateMessagePipe(NULL, &a, &b);
+MojoCreateMessagePipe(NULL, &c, &d);
// 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);
+MojoMessageHandle message;
+void* buffer;
+uint32_t buffer_size;
+MojoCreateMessage(nullptr, &message);
-result = MojoWriteMessageNew(a, message, MOJO_WRITE_MESSAGE_FLAG_NONE);
+MojoAppendMessageDataOptions options;
+options.struct_size = sizeof(options);
+options.flags = MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE;
+MojoAppendMessageData(message, &options, 2, &c, 1, &buffer, &buffer_size);
+memcpy(buffer, "hi", 2);
+MojoWriteMessage(a, message, nullptr);
// 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);
+MojoReadMessage(b, nullptr, &message);
+MojoGetMessageData(message, nullptr, &buffer, &buffer_size, &e, &num_handles);
```
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
+`MojoGetMessageData`. 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
+often the case -- we can use `MojoGetMessageData` to query for this information
first:
``` c
MojoMessageHandle message;
+void* buffer;
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);
+MojoResult result = MojoGetMessageData(message, nullptr, &buffer, &num_bytes,
+ nullptr, &num_handles);
```
-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`.
+If `message` has some non-zero number of handles, `result` will be
+`MOJO_RESULT_RESOURCE_EXHAUSTED`, and both `num_bytes` and `num_handles` will be
+updated to reflect the payload size and number of attached handles in the
+message.
## Data Pipes
@@ -322,7 +381,7 @@ 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);
+ nullptr);
```
The above snippet will attempt to write 12 bytes into the data pipe, which
@@ -337,8 +396,7 @@ 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);
+MojoResult result = MojoReadData(consumer, nullptr, buffer, &num_bytes);
```
This will attempt to read up to 64 bytes, returning the actual number of bytes
@@ -361,8 +419,7 @@ 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);
+MojoResult result = MojoBeginWriteData(producer, nullptr, &buffer, &num_bytes);
```
This requests write access to a region of up to 1024 bytes of the data pipe's
@@ -374,7 +431,7 @@ ASAP, indicating the number of bytes actually written:
``` c
memcpy(buffer, "hello", 6);
-MojoResult result = MojoEndWriteData(producer, 6);
+MojoResult result = MojoEndWriteData(producer, 6, nullptr);
```
Two-phase reads look similar:
@@ -382,19 +439,18 @@ 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);
+MojoResult result = MojoBeginReadData(consumer, nullptr, &buffer, &num_bytes);
// 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.
+// Say we only consumed one byte.
+result = MojoEndReadData(consumer, 1, nullptr);
num_bytes = 1024;
-result = MojoBeginReadData(consumer, &buffer, &num_bytes,
- MOJO_READ_DATA_FLAG_NONE);
+result = MojoBeginReadData(consumer, nullptr, &buffer, &num_bytes);
printf("Pipe says: %s", (const char*)buffer); // Should say "ello".
-result = MojoEndReadData(consumer, 5);
+result = MojoEndReadData(consumer, 5, nullptr);
```
## Shared Buffers
@@ -413,7 +469,7 @@ 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);
+MojoResult result = MojoCreateSharedBuffer(4096, NULL, &buffer);
```
You can also duplicate an existing shared buffer handle:
@@ -435,8 +491,7 @@ memory access to its contents:
``` c
void* data;
-MojoResult result = MojoMapBuffer(buffer, 0, 64, &data,
- MOJO_MAP_BUFFER_FLAG_NONE);
+MojoResult result = MojoMapBuffer(buffer, 0, 64, nullptr, &data);
*(int*)data = 42;
result = MojoUnmapBuffer(data);
@@ -454,14 +509,13 @@ that the newly duplicated handle can only be mapped to read-only memory:
MojoHandle read_only_buffer;
MojoDuplicateBufferHandleOptions options;
options.struct_size = sizeof(options);
-options.flags = MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY;
+options.flags = MOJO_DUPLICATE_BUFFER_HANDLE_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);
+result = MojoMapBuffer(read_only_buffer, 0, 64, nullptr, &data);
*(int*)data = 42; // CRASH
```
@@ -477,7 +531,7 @@ over a message pipe first.
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.
+file descriptors, Windows handles, Mach ports, and Fuchsia zx_handles.
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.
@@ -492,7 +546,7 @@ 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);
+MojoResult result = MojoWrapPlatformHandle(&platform_handle, nullptr, &handle);
```
Note that at this point `handle` effectively owns the file descriptor
@@ -503,7 +557,7 @@ 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);
+MojoResult result = MojoUnwrapPlatformHandle(handle, nullptr, &platform_handle);
int fd = (int)platform_handle.value;
```
@@ -528,7 +582,7 @@ 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
+## Signals & Traps
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
@@ -598,81 +652,72 @@ Finally if we read the last message from `a` its signaling state becomes:
and we know definitively that `a` can never be read from again.
-### Watching Signals
+### Trapping 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.
+sufficient to support robust and efficient pipe usage. Mojo traps empower users
+with the ability to **trap** changes in a handle's signaling state and
+automatically invoke a notification handler in response.
-When a watcher is created it must be bound to a function pointer matching
+When a trap 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):
+[//mojo/public/c/system/trap.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/trap.h):
``` c
-typedef void (*MojoWatcherNotificationCallback)(
- uintptr_t context,
- MojoResult result,
- MojoHandleSignalsState signals_state,
- MojoWatcherNotificationFlags flags);
+typedef void (*MojoTrapEventHandler)(const struct MojoTrapEvent* event);
```
-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**.
+The `event` parameter conveys details about why the event handler is being
+invoked. The handler may be called **at any time** and **from any thread**, so
+it is critical that handler implementations account for this.
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
+a trap, and that trap 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.
+before the stack unwinds beyond the outermost System API call on the current
+thread.
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.
+local endpoint, the arrival of that message may trigger a state change on the
+receiving handle and may therefore invoke one or more traps' 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.
+The `MOJO_TRAP_EVENT_FLAG_WITHIN_API_CALL` flag on the `flags` field of `event`
+is used to indicate whether the handler was invoked due to such an internal
+system IPC event (if the flag is unset), or if it was invoked synchronously due
+to some local API call (if the flag is set.) This distinction can be useful to
+make in certain cases to *e.g.* avoid accidental reentrancy in user code.
-### Creating a Watcher
+### Creating a Trap
-Creating a watcher is simple:
+Creating a trap is simple:
``` c
-void OnNotification(uintptr_t context,
- MojoResult result,
- MojoHandleSignalsState signals_state,
- MojoWatcherNotificationFlags flags) {
+void OnNotification(const struct MojoTrapEvent* event) {
// ...
}
-MojoHandle w;
-MojoResult result = MojoCreateWatcher(&OnNotification, &w);
+MojoHandle t;
+MojoResult result = MojoCreateTrap(&OnNotification, NULL, &t);
```
-Like all other `MojoHandle` types, watchers may be destroyed by closing them
-with `MojoClose`. Unlike other `MojoHandle` types, watcher handles are **not**
+Like all other `MojoHandle` types, traps may be destroyed by closing them with
+`MojoClose`. Unlike most other `MojoHandle` types, trap handles are **not**
transferrable across message pipes.
-In order for a watcher to be useful, it has to watch at least one handle.
+In order for a trap to be useful, it has have at least one **trigger** attached
+to it.
-### Adding a Handle to a Watcher
+### Adding a Trigger to a Trap
-Any given watcher can watch any given (message or data pipe) handle for some set
+Any given trap 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.
+traps, and a single trap can watch multiple different handles simultaneously.
``` c
MojoHandle a, b;
@@ -680,85 +725,91 @@ MojoCreateMessagePipe(NULL, &a, &b);
// Watch handle |a| for readability.
const uintptr_t context = 1234;
-MojoResult result = MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, context);
+MojoResult result = MojoAddTrigger(t, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ context, NULL);
```
-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.
+We've successfully instructed trap `t` to begin watching pipe handle `a` for
+readability. However, our recently created trap is still in a **disarmed**
+state, meaning that it will never fire a notification pertaining to this
+trigger. It must be **armed** before that can happen.
-### Arming a Watcher
+### Arming a Trap
-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 order for a trap to invoke its notification handler in response to a relevant
+signaling state change on a watched handle, it must first be armed. A trap may
+only be armed if none of its attached triggers 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);
+MojoResult result = MojoArmTrap(t, NULL, NULL, NULL);
```
Now we can write to `b` to make `a` readable:
``` c
-MojoWriteMessage(b, NULL, 0, NULL, 0, MOJO_WRITE_MESSAGE_NONE);
+MojoMessageHandle m;
+MojoCreateMessage(nullptr, &m);
+MojoWriteMessage(b, m, nullptr);
```
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.
+with the `context` value (*i.e.* 1234) that was given when the trigger was added
+to the trap.
-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`.
+The `result` field of the event will be `MOJO_RESULT_OK` to indicate that the
+trigger's condition has been met. If the handle's state had instead changed in
+such a way that the trigger's condition could never be met again (*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.
+**NOTE:** Immediately before a trigger decides to invoke its event handler, it
+automatically disarms itself to prevent another state change from eliciting
+another notification. Therefore a trap must be repeatedly rearmed in order to
+continue dispatching events.
-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:
+As noted above, arming a watcher may fail if any of its triggers would be
+activated immediately. In that case, the caller may provide buffers to
+`MojoArmTrap` to receive information about a subset of the triggers 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);
+// Provide some storage for information about triggers that would have been
+// activated immediately.
+uint32_t num_blocking_events = 2;
+MojoTrapEvent blocking_events[2] = {{sizeof(MojoTrapEvent)},
+ {sizeof(MojoTrapEvent)}};
+MojoResult result = MojoArmTrap(t, NULL, &num_blocking_events,
+ &blocking_events);
```
-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:
+Because `a` is still readable this operation will now fail with
+`MOJO_RESULT_FAILED_PRECONDITION`. The input value of `num_blocking_events`
+informs `MojoArmTrap` that it may store information regarding up to 2 triggers
+which have prevented arming. In this case of course there is only one active
+trigger, 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`.
+* `num_blocking_events` is `1`.
+* `blocking_events[0].trigger_context` is `1234`.
+* `blocking_events[0].result` is `MOJO_RESULT_OK`
+* `blocking_events[0].signals_state` 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.
+In other words the stored information mirrors what would have been the resulting
+event structure if the trap were allowed to arm and then notify immediately.
-### Cancelling a Watch
+### Removing a Trigger
-There are three ways a watch can be cancelled:
+There are three ways a trigger can be removed:
-* 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`.
+* The handle being watched by the trigger is closed
+* The trap handle is closed, in which case all of its attached triggers are
+ implicitly removed.
+* `MojoRemoveTrigger` is called for a given `context`.
In the above example this means any of the following operations will cancel the
watch on `a`:
@@ -767,25 +818,24 @@ watch on `a`:
// Close the watched handle...
MojoClose(a);
-// OR close the watcher handle...
-MojoClose(w);
+// OR close the trap handle...
+MojoClose(t);
-// OR explicitly cancel.
-MojoResult result = MojoCancelWatch(w, 1234);
+// OR explicitly remove it.
+MojoResult result = MojoRemoveTrigger(t, 1234, NULL);
```
-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.
+In every case the trap's event handler is invoked for the cancelled trigger(es)
+regardless of whether or not the trap was armed at the time. The event handler
+receives a `result` of `MOJO_RESULT_CANCELLED` for each of these invocations,
+and this is guaranteed to be the final event for any given trigger context.
-### Practical Watch Context Usage
+### Practical Trigger Context Usage
-It is common and probably wise to treat a watch's `context` value as an opaque
+It is common and probably wise to treat a trigger'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.
+watched. Here's a small example which uses a single trap 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
@@ -793,45 +843,43 @@ of a message pipe and accumulate a count of messages received at each end.
// no external process connections, this is fine.
struct WatchedHandleState {
- MojoHandle watcher;
+ MojoHandle trap;
MojoHandle handle;
int message_count;
};
-void OnNotification(uintptr_t context,
- MojoResult result,
- MojoHandleSignalsState signals_state,
- MojoWatcherNotificationFlags flags) {
- struct WatchedHandleState* state = (struct WatchedHandleState*)(context);
+void OnNotification(const struct MojoTrapEvent* event) {
+ struct WatchedHandleState* state =
+ (struct WatchedHandleState*)(event->trigger_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.
+ if (event->result == MOJO_RESULT_CANCELLED) {
+ // Cancellation is always the last event and is guaranteed to 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);
+ // cancel. Note that we could also just call MojoClose(state->trap) here
+ // since we know there's only one attached trigger.
+ MojoRemoveTrigger(state->trap, event->trigger_context, NULL);
return;
}
- // This is the only handle watched by the watcher, so as long as we can't arm
+ // This is the only handle watched by the trap, 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) ==
+ while (MojoArmTrap(state->trap, 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);
+ MojoRemoveTrigger(state->trap, event->trigger_context, NULL);
return;
}
}
@@ -840,25 +888,29 @@ void OnNotification(uintptr_t context,
MojoHandle a, b;
MojoCreateMessagePipe(NULL, &a, &b);
-MojoHandle a_watcher, b_watcher;
-MojoCreateWatcher(&OnNotification, &a_watcher);
-MojoCreateWatcher(&OnNotification, &b_watcher)
+MojoHandle a_trap, b_trap;
+MojoCreateTrap(&OnNotification, NULL, &a_trap);
+MojoCreateTrap(&OnNotification, NULL, &b_trap)
struct WatchedHandleState* a_state = malloc(sizeof(struct WatchedHandleState));
-a_state->watcher = a_watcher;
+a_state->trap = a_trap;
a_state->handle = a;
a_state->message_count = 0;
struct WatchedHandleState* b_state = malloc(sizeof(struct WatchedHandleState));
-b_state->watcher = b_watcher;
+b_state->trap = b_trap;
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);
+MojoAddTrigger(a_trap, a, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, (uintptr_t)a_state,
+ NULL);
+MojoAddTrigger(b_trap, b, MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, (uintptr_t)b_state,
+ NULL);
-MojoArmWatcher(a_watcher, NULL, NULL, NULL, NULL);
-MojoArmWatcher(b_watcher, NULL, NULL, NULL, NULL);
+MojoArmTrap(a_trap, NULL, NULL, NULL);
+MojoArmTrap(b_trap, NULL, NULL, NULL);
```
Now any writes to `a` will increment `message_count` in `b_state`, and any
@@ -867,3 +919,12 @@ 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.
+
+## Invitations
+
+TODO.
+
+For now see the
+[C header](https://cs.chromium.org/src/mojo/public/c/system/invitation.h) and
+the documentation for the equivalent
+[C++ API](/mojo/public/cpp/system/README.md#Invitations).
diff --git a/mojo/public/c/system/buffer.h b/mojo/public/c/system/buffer.h
index 285e0d7b03..2cc54270ad 100644
--- a/mojo/public/c/system/buffer.h
+++ b/mojo/public/c/system/buffer.h
@@ -15,91 +15,108 @@
#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;
+// Flags passed to |MojoCreateSharedBuffer()| via
+// |MojoCreateSharedBufferOptions|. See values defined below.
+typedef uint32_t MojoCreateSharedBufferFlags;
-#ifdef __cplusplus
-const MojoCreateSharedBufferOptionsFlags
- MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE = 0;
-#else
-#define MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE \
- ((MojoCreateSharedBufferOptionsFlags)0)
-#endif
+// No flags. Default behavior.
+#define MOJO_CREATE_SHARED_BUFFER_FLAG_NONE ((uint32_t)0)
-MOJO_STATIC_ASSERT(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment");
+// Options passed to |MojoCreateSharedBuffer()|.
struct MOJO_ALIGNAS(8) MojoCreateSharedBufferOptions {
+ // The size of this structure, used for versioning.
uint32_t struct_size;
- MojoCreateSharedBufferOptionsFlags flags;
+
+ // See |MojoCreateSharedBufferFlags|.
+ MojoCreateSharedBufferFlags flags;
};
+MOJO_STATIC_ASSERT(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment");
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.
+// Flags passed to |MojoGetBufferInfo()| via |MojoGetBufferInfoOptions|. See
+// values defined below.
+typedef uint32_t MojoGetBufferInfoFlags;
-typedef uint32_t MojoDuplicateBufferHandleOptionsFlags;
+// No flags. Default behavior.
+#define MOJO_GET_BUFFER_INFO_FLAG_NONE ((uint32_t)0)
-#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
+// Options passed to |MojoGetBufferInfo()|.
+struct MOJO_ALIGNAS(8) MojoGetBufferInfoOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoGetBufferInfoFlags|.
+ MojoGetBufferInfoFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoGetBufferInfoOptions) == 8,
+ "MojoSharedBufferOptions has wrong size");
+
+// Structure used to receive information about a shared buffer via
+// |MojoGetBufferInfo()|.
+struct MOJO_ALIGNAS(8) MojoSharedBufferInfo {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+ // The size of the shared buffer.
+ uint64_t size;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoSharedBufferInfo) == 16,
+ "MojoSharedBufferInfo has wrong size");
+
+// Flags passed to |MojoDuplicateBufferHandle()| via
+// |MojoDuplicateBufferHandleOptions|. See values defined below.
+typedef uint32_t MojoDuplicateBufferHandleFlags;
+
+// No options. Default behavior. Note that if a shared buffer handle is ever
+// duplicated without |MOJO_DUPLICATE_BUFFER_HANDLE_READ_ONLY| (see below),
+// neither it nor any of its duplicates can ever be duplicated *with*
+// |MOJO_DUPLICATE_BUFFER_HANDLE_READ_ONLY| in the future. That is, once a
+// writable handle has been duplicated as another writable handle, it is no
+// longer possible to create read-only handles to the underlying buffer object.
+#define MOJO_DUPLICATE_BUFFER_HANDLE_FLAG_NONE ((uint32_t)0)
+
+// Duplicates the handle as read-only. If successful, the resulting new handle
+// will always map to a read-only memory region. Successful use of this flag
+// also imposes the limitation that the handle or any of its subsequent
+// duplicates may never be duplicated *without* this flag in the future. That
+// is, once a read-only handle is produced for a buffer object, all future
+// handles to that object must also be read-only.
+#define MOJO_DUPLICATE_BUFFER_HANDLE_FLAG_READ_ONLY ((uint32_t)1 << 0)
+
+// Options passed to |MojoDuplicateBufferHandle()|.
struct MojoDuplicateBufferHandleOptions {
+ // The size of this structure, used for versioning.
uint32_t struct_size;
- MojoDuplicateBufferHandleOptionsFlags flags;
+
+ // See |MojoDuplicateBufferHandleFlags|.
+ MojoDuplicateBufferHandleFlags 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.
-
+// Flags passed to |MojoMapBuffer()| via |MojoMapBufferOptions|. See values
+// defined below.
typedef uint32_t MojoMapBufferFlags;
-#ifdef __cplusplus
-const MojoMapBufferFlags MOJO_MAP_BUFFER_FLAG_NONE = 0;
-#else
-#define MOJO_MAP_BUFFER_FLAG_NONE ((MojoMapBufferFlags)0)
-#endif
+// No flags. Default behavior.
+#define MOJO_MAP_BUFFER_FLAG_NONE ((uint32_t)0)
+
+// Options passed to |MojoMapBuffer()|.
+struct MojoMapBufferOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoMapBufferFlags|.
+ MojoMapBufferFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoMapBufferOptions) == 8,
+ "MojoMapBufferOptions has wrong size");
#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()|.
@@ -119,10 +136,10 @@ extern "C" {
// 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.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoCreateSharedBuffer(uint64_t num_bytes,
+ const struct MojoCreateSharedBufferOptions* options,
+ MojoHandle* shared_buffer_handle);
// 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
@@ -132,42 +149,56 @@ MOJO_SYSTEM_EXPORT MojoResult MojoCreateSharedBuffer(
// |options| may be set to null to duplicate the buffer handle with the default
// options.
//
+// Access rights to mapped memory from the duplicated handle may be controlled
+// by flags in |*options|, with some limitations. See notes on
+// |MOJO_DUPLICATE_BUFFER_HANDLE_FLAG_NONE| and
+// |MOJO_DUPLICATE_BUFFER_HANDLE_FLAG_READ_ONLY| regarding restrictions on
+// duplication with respect to these flags.
+//
// 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_RESULT_FAILED_PRECONDITION| if
+// |MOJO_DUPLICATE_BUFFER_HANDLE_FLAG_READ_ONLY| was set but the handle
+// was already previously duplicated without that flag; or if
+// |MOJO_DUPLICATE_BUFFER_HANDLE_FLAG_READ_ONLY| was not set but the
+// handle was already previously duplicated with that flag.
MOJO_SYSTEM_EXPORT MojoResult MojoDuplicateBufferHandle(
MojoHandle buffer_handle,
- const struct MojoDuplicateBufferHandleOptions* options, // Optional.
- MojoHandle* new_buffer_handle); // Out.
+ const struct MojoDuplicateBufferHandleOptions* options,
+ MojoHandle* new_buffer_handle);
// 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
+// by |buffer_handle| into memory, with options specified by |options|.
+// |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
+// A single buffer handle may have multiple active mappings. The permissions
+// (e.g., writable or executable) of the returned memory depend on the
// 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.
//
+// |options| may be null to map the buffer with default behavior.
+//
// 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).
+// |buffer_handle| is not a valid buffer handle, the range specified by
+// |offset| and |num_bytes| is not valid, or |*options| is invalid).
// |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);
+MOJO_SYSTEM_EXPORT MojoResult
+MojoMapBuffer(MojoHandle buffer_handle,
+ uint64_t offset,
+ uint64_t num_bytes,
+ const struct MojoMapBufferOptions* options,
+ void** buffer);
// Unmaps a buffer pointer that was mapped by |MojoMapBuffer()|. |buffer| must
// have been the result of |MojoMapBuffer()| (not some other pointer inside
@@ -179,7 +210,26 @@ MOJO_SYSTEM_EXPORT MojoResult MojoMapBuffer(MojoHandle buffer_handle,
// |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.
+MOJO_SYSTEM_EXPORT MojoResult MojoUnmapBuffer(void* buffer);
+
+// Retrieve information about |buffer_handle| into |info|.
+//
+// Callers must initialize |info->struct_size| to |sizeof(MojoSharedBufferInfo)|
+// before calling this function.
+//
+// |options| may be null for default options.
+//
+// Returns:
+// |MOJO_RESULT_OK| on success.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |buffer_handle| is invalid, |info| is
+// null, or |*options| is invalid.
+//
+// On success, |info->size| will be set to the size of the buffer. On failure it
+// is not modified.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoGetBufferInfo(MojoHandle buffer_handle,
+ const struct MojoGetBufferInfoOptions* options,
+ struct MojoSharedBufferInfo* info);
#ifdef __cplusplus
} // extern "C"
diff --git a/mojo/public/c/system/core.h b/mojo/public/c/system/core.h
index 03c0652a57..2e8fd39d3f 100644
--- a/mojo/public/c/system/core.h
+++ b/mojo/public/c/system/core.h
@@ -12,11 +12,13 @@
#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/invitation.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/quota.h"
#include "mojo/public/c/system/system_export.h"
+#include "mojo/public/c/system/trap.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
index f51e36cb2e..3702cdb624 100644
--- a/mojo/public/c/system/data_pipe.h
+++ b/mojo/public/c/system/data_pipe.h
@@ -15,104 +15,173 @@
#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;
+// Flags passed to |MojoCreateDataPipe()| via |MojoCreateDataPipeOptions|. See
+// values defined below.
+typedef uint32_t MojoCreateDataPipeFlags;
-#ifdef __cplusplus
-const MojoCreateDataPipeOptionsFlags MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE =
- 0;
-#else
-#define MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE \
- ((MojoCreateDataPipeOptionsFlags)0)
-#endif
+// No flags. Default behavior.
+#define MOJO_CREATE_DATA_PIPE_FLAG_NONE ((uint32_t)0)
-MOJO_STATIC_ASSERT(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment");
+// Options passed to |MojoCreateDataPipe()|.
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;
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoCreateDataPipeFlags|.
+ MojoCreateDataPipeFlags flags;
+
+ // The size of an element in bytes. All transactions and buffer sizes must
+ // consist of an integral number of elements. Must be non-zero.
+ uint32_t element_num_bytes;
+
+ // The capacity of the data pipe in bytes. Must be a multiple of
+ // |element_num_bytes|. If successfully created, the pipe will always be able
+ // to queue at least this much data. If zero, the pipe buffer will be of a
+ // system-dependent capacity of at least one element in size.
+ uint32_t capacity_num_bytes;
};
+MOJO_STATIC_ASSERT(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment");
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.
-
+// Flags passed to |MojoWriteData()| via |MojoWriteDataOptions|. See values
+// defined below.
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
+// No flags. Default behavior.
+#define MOJO_WRITE_DATA_FLAG_NONE ((uint32_t)0)
-// |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|.
+// Requires that all provided data must fit into the pipe's available capacity
+// in order for the write to succeed. Otherwise the write fails and no data is
+// written into the pipe.
+#define MOJO_WRITE_DATA_FLAG_ALL_OR_NONE ((uint32_t)1 << 0)
+
+// Options passed to |MojoWriteData()|.
+struct MOJO_ALIGNAS(8) MojoWriteDataOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoWriteDataFlags|.
+ MojoWriteDataFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoWriteDataOptions) == 8,
+ "MojoWriteDataOptions has wrong size");
+
+// Flags passed to |MojoBeginWriteData()| via |MojoBeginWriteDataOptions|. See
+// values defined below.
+typedef uint32_t MojoBeginWriteDataFlags;
+
+// No flags. Default behavior.
+#define MOJO_BEGIN_WRITE_DATA_FLAG_NONE ((uint32_t)0)
+
+// Options passed to |MojoBeginWriteData()|.
+struct MOJO_ALIGNAS(8) MojoBeginWriteDataOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoBeginWriteDataFlags|.
+ MojoBeginWriteDataFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoBeginWriteDataOptions) == 8,
+ "MojoBeginWriteDataOptions has wrong size");
+
+// Flags passed to |MojoEndWriteData()| via |MojoEndWriteDataOptions|. See
+// values defined below.
+typedef uint32_t MojoEndWriteDataFlags;
+
+// No flags. Default behavior.
+#define MOJO_END_WRITE_DATA_FLAG_NONE ((uint32_t)0)
+
+// Options passed to |MojoEndWriteData()|.
+struct MOJO_ALIGNAS(8) MojoEndWriteDataOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoEndWriteDataFlags|.
+ MojoEndWriteDataFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoEndWriteDataOptions) == 8,
+ "MojoEndWriteDataOptions has wrong size");
+// Flags passed to |MojoReadData()| via |MojoReadDataOptions|.
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
+// No flags. Default behavior.
+#define MOJO_READ_DATA_FLAG_NONE ((uint32_t)0)
+
+// Requires that all request bytes can be read from the data pipe in order for
+// the read to succeed. If that many bytes are not available for reading, the
+// read will fail and no bytes will be read. Ignored of
+// |MOJO_READ_DATA_FLAG_QUERY| is also set.
+#define MOJO_READ_DATA_FLAG_ALL_OR_NONE ((uint32_t)1 << 0)
+
+// Discards the data read rather than copying it into the caller's provided
+// buffer. May not be combined with |MOJO_READ_DATA_FLAG_PEEK| or
+// |MOJO_READ_DATA_FLAG_QUERY|.
+#define MOJO_READ_DATA_FLAG_DISCARD ((uint32_t)1 << 1)
+
+// Queries the number of bytes available for reading without actually reading
+// the data. May not be combined with |MOJO_READ_DATA_FLAG_DISCARD| or
+// |MOJO_READ_DATA_FLAG_PEEK|. |MOJO_READ_DATA_FLAG_ALL_OR_NONE| is ignored if
+// this is set.
+#define MOJO_READ_DATA_FLAG_QUERY ((uint32_t)1 << 2)
+
+// Reads data from the pipe and copies it to the caller's provided buffer
+// without actually removing the data from the pipe. May not be combined with
+// |MOJO_READ_DATA_FLAG_DISCARD| or |MOJO_READ_DATA_FLAG_QUERY|.
+#define MOJO_READ_DATA_FLAG_PEEK ((uint32_t)1 << 3)
+
+// Options passed to |MojoReadData()|.
+struct MOJO_ALIGNAS(8) MojoReadDataOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoReadDataFlags|.
+ MojoReadDataFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoReadDataOptions) == 8,
+ "MojoReadDataOptions has wrong size");
+
+// Flags passed to |MojoBeginReadData()| via |MojoBeginReadDataOptions|. See
+// values defined below.
+typedef uint32_t MojoBeginReadDataFlags;
+
+// No flags. Default behavior.
+#define MOJO_BEGIN_READ_DATA_FLAG_NONE ((uint32_t)0)
+
+// Options passed to |MojoBeginReadData()|.
+struct MOJO_ALIGNAS(8) MojoBeginReadDataOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoBeginReadDataFlags|.
+ MojoBeginReadDataFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoBeginReadDataOptions) == 8,
+ "MojoBeginReadDataOptions has wrong size");
+
+// Flags passed to |MojoEndReadData()| via |MojoEndReadDataOptions|. See
+// values defined below.
+typedef uint32_t MojoEndReadDataFlags;
+
+// No flags. Default behavior.
+#define MOJO_END_READ_DATA_FLAG_NONE ((uint32_t)0)
+
+// Options passed to |MojoEndReadData()|.
+struct MOJO_ALIGNAS(8) MojoEndReadDataOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoEndReadDataFlags|.
+ MojoEndReadDataFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoEndReadDataOptions) == 8,
+ "MojoEndReadDataOptions has wrong size");
#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|.
@@ -130,27 +199,29 @@ extern "C" {
//
// Returns:
// |MOJO_RESULT_OK| on success.
-// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
-// |*options| is invalid).
+// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid, e.g.,
+// |*options| is invalid, specified capacity or element size is zero, or
+// the specified element size exceeds the specified capacity.
// |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.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoCreateDataPipe(const struct MojoCreateDataPipeOptions* options,
+ MojoHandle* data_pipe_producer_handle,
+ MojoHandle* data_pipe_consumer_handle);
// 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.
+// multiple of the data pipe's element size.
//
// On success |*num_bytes| is set to the amount of data that was actually
// written. On failure it is unmodified.
//
+// |options| may be null for default options. See |MojoWriteDataOptions| for
+// the effect of various options on the behavior of this function.
+//
// Returns:
// |MOJO_RESULT_OK| on success.
// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
@@ -159,20 +230,20 @@ MOJO_SYSTEM_EXPORT MojoResult MojoCreateDataPipe(
// size.)
// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe consumer handle has been
// closed.
-// |MOJO_RESULT_OUT_OF_RANGE| if |flags| has
+// |MOJO_RESULT_OUT_OF_RANGE| if |options->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
+// consumer is still open) and |options->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);
+MojoWriteData(MojoHandle data_pipe_producer_handle,
+ const void* elements,
+ uint32_t* num_bytes,
+ const struct MojoWriteDataOptions* options);
// 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
@@ -188,11 +259,13 @@ MOJO_SYSTEM_EXPORT MojoResult
// write operation. |MojoEndWriteData()| need not be called when
// |MojoBeginWriteData()| fails.
//
+// |options| may be null for default options.
+//
// 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.
+// |*options| is invalid.
// |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
@@ -201,10 +274,10 @@ MOJO_SYSTEM_EXPORT MojoResult
// |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);
+MojoBeginWriteData(MojoHandle data_pipe_producer_handle,
+ const struct MojoBeginWriteDataOptions* options,
+ void** buffer,
+ uint32_t* buffer_num_bytes);
// Ends a two-phase write that was previously initiated by
// |MojoBeginWriteData()| for the same |data_pipe_producer_handle|.
@@ -218,6 +291,8 @@ MOJO_SYSTEM_EXPORT MojoResult
// writable again if there's space available) but no data written to |*buffer|
// is "put into" the data pipe.
//
+// |options| may be null for default options.
+//
// Returns:
// |MOJO_RESULT_OK| on success.
// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
@@ -228,13 +303,14 @@ MOJO_SYSTEM_EXPORT MojoResult
// 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);
+MojoEndWriteData(MojoHandle data_pipe_producer_handle,
+ uint32_t num_bytes_written,
+ const struct MojoEndWriteDataOptions* options);
// 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
+// If |options->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
@@ -257,28 +333,32 @@ MOJO_SYSTEM_EXPORT MojoResult
// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| is ignored, as are |elements| and the input
// value of |*num_bytes|.
//
+// |options| may be null for default options.
+//
// 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.).
+// |options->flags| is invalid, or |*options| itself is invalid).
// |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_OUT_OF_RANGE| if |options->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
+// the producer is still open) and |options->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);
+MOJO_SYSTEM_EXPORT MojoResult
+MojoReadData(MojoHandle data_pipe_consumer_handle,
+ const struct MojoReadDataOptions* options,
+ void* elements,
+ uint32_t* num_bytes);
// Begins a two-phase read from the data pipe consumer given by
// |data_pipe_consumer_handle|. On success, |*buffer| will be a pointer from
@@ -292,15 +372,13 @@ MOJO_SYSTEM_EXPORT MojoResult MojoReadData(MojoHandle data_pipe_consumer_handle,
// 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.
+// |options| may be null for default options.
//
// 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.)
+// or |*options| is invalid.)
// |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
@@ -309,10 +387,10 @@ MOJO_SYSTEM_EXPORT MojoResult MojoReadData(MojoHandle data_pipe_consumer_handle,
// |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);
+MojoBeginReadData(MojoHandle data_pipe_consumer_handle,
+ const struct MojoBeginReadDataOptions* options,
+ const void** buffer,
+ uint32_t* buffer_num_bytes);
// Ends a two-phase read from the data pipe consumer given by
// |data_pipe_consumer_handle| that was begun by a call to |MojoBeginReadData()|
@@ -324,6 +402,8 @@ MOJO_SYSTEM_EXPORT MojoResult
// 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.
//
+// |options| may be null for default options.
+//
// Returns:
// |MOJO_RESULT_OK| on success.
// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g.,
@@ -334,8 +414,9 @@ MOJO_SYSTEM_EXPORT MojoResult
// 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);
+MojoEndReadData(MojoHandle data_pipe_consumer_handle,
+ uint32_t num_bytes_read,
+ const struct MojoEndReadDataOptions* options);
#ifdef __cplusplus
} // extern "C"
diff --git a/mojo/public/c/system/functions.h b/mojo/public/c/system/functions.h
index d0656c67fc..9f90c38962 100644
--- a/mojo/public/c/system/functions.h
+++ b/mojo/public/c/system/functions.h
@@ -19,12 +19,21 @@
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.)
+// Initializes Mojo in the calling application.
+//
+// With the exception of Mojo Core embedders, applications using Mojo APIs must
+// call this function before any others.
+//
+// |options| may be null.
+//
+// Returns:
+// |MOJO_RESULT_OK| if Mojo intiailization was successful.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |options| was null or invalid.
+// |MOJO_RESULT_FAILED_PRECONDITION| if |MojoInitialize()| was already called
+// once or if the application already explicitly initialized a Mojo Core
+// environment as an embedder.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoInitialize(const struct MojoInitializeOptions* options);
// Returns the time, in microseconds, since some undefined point in the past.
// The values are only meaningful relative to other values that were obtained
@@ -42,7 +51,7 @@ MOJO_SYSTEM_EXPORT MojoTimeTicks MojoGetTimeTicksNow(void);
//
// 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
+// they properly overlap (this is likely the case with traps), or fail with
// |MOJO_RESULT_INVALID_ARGUMENT| if they happen after.
MOJO_SYSTEM_EXPORT MojoResult MojoClose(MojoHandle handle);
@@ -61,16 +70,6 @@ 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
diff --git a/mojo/public/c/system/invitation.h b/mojo/public/c/system/invitation.h
new file mode 100644
index 0000000000..eb1a4a80e8
--- /dev/null
+++ b/mojo/public/c/system/invitation.h
@@ -0,0 +1,456 @@
+// 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.
+
+// This file contains type and function definitions relevant to Mojo invitation
+// APIs.
+//
+// Note: This header should be compilable as C.
+
+#ifndef MOJO_PUBLIC_C_SYSTEM_INVITATION_H_
+#define MOJO_PUBLIC_C_SYSTEM_INVITATION_H_
+
+#include <stdint.h>
+
+#include "mojo/public/c/system/macros.h"
+#include "mojo/public/c/system/platform_handle.h"
+#include "mojo/public/c/system/system_export.h"
+#include "mojo/public/c/system/types.h"
+
+// Flags included in |MojoProcessErrorDetails| indicating additional status
+// information.
+typedef uint32_t MojoProcessErrorFlags;
+
+// No flags.
+#define MOJO_PROCESS_ERROR_FLAG_NONE ((MojoProcessErrorFlags)0)
+
+// If set, the process has been disconnected. No further
+// |MojoProcessErrorHandler| invocations occur for it, and any IPC primitives
+// (message pipes, data pipes) which were connected to it have been or will
+// imminently be disconnected.
+#define MOJO_PROCESS_ERROR_FLAG_DISCONNECTED ((MojoProcessErrorFlags)1)
+
+// Details regarding why an invited process has had its connection to this
+// process terminated by the system. See |MojoProcessErrorHandler| and
+// |MojoSendInvitation()|.
+struct MOJO_ALIGNAS(8) MojoProcessErrorDetails {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // The length of the string pointed to by |error_message| below.
+ uint32_t error_message_length;
+
+ // An error message corresponding to the reason why the connection was
+ // terminated. This is an information message which may be useful to
+ // developers.
+ MOJO_POINTER_FIELD(const char*, error_message);
+
+ // See |MojoProcessErrorFlags|.
+ MojoProcessErrorFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoProcessErrorDetails) == 24,
+ "MojoProcessErrorDetails has wrong size.");
+
+// An opaque process handle value which must be provided when sending an
+// invitation to another process via a platform transport. See
+// |MojoSendInvitation()|.
+struct MOJO_ALIGNAS(8) MojoPlatformProcessHandle {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // The process handle value. For Windows this is a valid process HANDLE value.
+ // For Fuchsia it must be |zx_handle_t| process handle, and for all other
+ // POSIX systems, it's a PID.
+ uint64_t value;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoPlatformProcessHandle) == 16,
+ "MojoPlatformProcesHandle has wrong size.");
+
+// Enumeration indicating the type of transport over which an invitation will be
+// sent or received.
+typedef uint32_t MojoInvitationTransportType;
+
+// The channel transport type embodies common platform-specific OS primitives
+// for FIFO message passing:
+// - For Windows, this is a named pipe.
+// - For Fuchsia, it's a channel.
+// - For all other POSIX systems, it's a Unix domain socket pair.
+//
+// See |MojoInvitationTransportHandle| for details.
+#define MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL ((MojoInvitationTransportType)0)
+
+// Similar to CHANNEL transport, but used for an endpoint which requires an
+// additional step to accept an inbound connection. This corresponds to a
+// bound listening socket on POSIX, or named pipe server handle on Windows.
+//
+// The remote endpoint should establish a working connection to the server side
+// and wrap the handle to that connection using a CHANNEL transport.
+#define MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL_SERVER \
+ ((MojoInvitationTransportType)1)
+
+// A transport endpoint over which an invitation may be sent or received via
+// |MojoSendInvitation()| or |MojoAcceptInvitation()| respectively.
+struct MOJO_ALIGNAS(8) MojoInvitationTransportEndpoint {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // The type of this transport endpoint. See |MojoInvitationTransportType|.
+ MojoInvitationTransportType type;
+
+ // The number of platform handles in |platform_handles| below.
+ uint32_t num_platform_handles;
+
+ // Platform handle(s) corresponding to the system object(s) backing this
+ // endpoint. The concrete type of the handle(s) depends on |type|.
+ //
+ // For |MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL| endpoints:
+ // - On Windows, this is a single named pipe HANDLE
+ // - On Fuchsua, this is a single channel Fuchsia handle
+ // - On other POSIX systems, this is a single Unix domain socket file
+ // descriptor.
+ MOJO_POINTER_FIELD(const struct MojoPlatformHandle*, platform_handles);
+};
+MOJO_STATIC_ASSERT(sizeof(MojoInvitationTransportEndpoint) == 24,
+ "MojoInvitationTransportEndpoint has wrong size.");
+
+// Flags passed to |MojoCreateInvitation()| via |MojoCreateInvitationOptions|.
+typedef uint32_t MojoCreateInvitationFlags;
+
+// No flags. Default behavior.
+#define MOJO_CREATE_INVITATION_FLAG_NONE ((MojoCreateInvitationFlags)0)
+
+// Options passed to |MojoCreateInvitation()|.
+struct MOJO_ALIGNAS(8) MojoCreateInvitationOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoCreateInvitationFlags|.
+ MojoCreateInvitationFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoCreateInvitationOptions) == 8,
+ "MojoCreateInvitationOptions has wrong size");
+
+// Flags passed to |MojoAttachMessagePipeToInvitation()| via
+// |MojoAttachMessagePipeToInvitationOptions|.
+typedef uint32_t MojoAttachMessagePipeToInvitationFlags;
+
+// No flags. Default behavior.
+#define MOJO_ATTACH_MESSAGE_PIPE_TO_INVITATION_FLAG_NONE \
+ ((MojoAttachMessagePipeToInvitationFlags)0)
+
+// Options passed to |MojoAttachMessagePipeToInvitation()|.
+struct MOJO_ALIGNAS(8) MojoAttachMessagePipeToInvitationOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoAttachMessagePipeToInvitationFlags|.
+ MojoAttachMessagePipeToInvitationFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoAttachMessagePipeToInvitationOptions) == 8,
+ "MojoAttachMessagePipeToInvitationOptions has wrong size");
+
+// Flags passed to |MojoExtractMessagePipeFromInvitation()| via
+// |MojoExtractMessagePipeFromInvitationOptions|.
+typedef uint32_t MojoExtractMessagePipeFromInvitationFlags;
+
+// No flags. Default behavior.
+#define MOJO_EXTRACT_MESSAGE_PIPE_FROM_INVITATION_FLAG_NONE \
+ ((MojoExtractMessagePipeFromInvitationFlags)0)
+
+// Options passed to |MojoExtractMessagePipeFromInvitation()|.
+struct MOJO_ALIGNAS(8) MojoExtractMessagePipeFromInvitationOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoExtractMessagePipeFromInvitationFlags|.
+ MojoExtractMessagePipeFromInvitationFlags flags;
+};
+MOJO_STATIC_ASSERT(
+ sizeof(MojoExtractMessagePipeFromInvitationOptions) == 8,
+ "MojoExtractMessagePipeFromInvitationOptions has wrong size");
+
+// Flags passed to |MojoSendInvitation()| via |MojoSendInvitationOptions|.
+typedef uint32_t MojoSendInvitationFlags;
+
+// No flags. Default behavior.
+#define MOJO_SEND_INVITATION_FLAG_NONE ((MojoSendInvitationFlags)0)
+
+// Send an isolated invitation to the receiver. Isolated invitations only
+// establish communication between the sender and the receiver. Accepting an
+// isolated invitation does not make IPC possible between the sender and any
+// other members of the receiver's process graph, nor does it make IPC possible
+// between the receiver and any other members of the sender's process graph.
+//
+// Invitations sent with this flag set must be accepted with the corresponding
+// |MOJO_ACCEPT_INVITATION_FLAG_ISOLATED| flag set, and may only have a single
+// message pipe attached with a name of exactly four zero-bytes ("\0\0\0\0").
+#define MOJO_SEND_INVITATION_FLAG_ISOLATED ((MojoSendInvitationFlags)1)
+
+// Options passed to |MojoSendInvitation()|.
+struct MOJO_ALIGNAS(8) MojoSendInvitationOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoSendInvitationFlags|.
+ MojoSendInvitationFlags flags;
+
+ // If |flags| includes |MOJO_SEND_INVITATION_FLAG_ISOLATED| then these fields
+ // specify a name identifying the established isolated connection. There are
+ // no restrictions on the value given. If |isolated_connection_name_length| is
+ // non-zero, the system ensures that only one isolated process connection can
+ // exist for the given name at any time.
+ MOJO_POINTER_FIELD(const char*, isolated_connection_name);
+ uint32_t isolated_connection_name_length;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoSendInvitationOptions) == 24,
+ "MojoSendInvitationOptions has wrong size");
+
+// Flags passed to |MojoAcceptInvitation()| via |MojoAcceptInvitationOptions|.
+typedef uint32_t MojoAcceptInvitationFlags;
+
+// No flags. Default behavior.
+#define MOJO_ACCEPT_INVITATION_FLAG_NONE ((MojoAcceptInvitationFlags)0)
+
+// Accept an isoalted invitation from the sender. See
+// |MOJO_SEND_INVITATION_FLAG_ISOLATED| for details.
+#define MOJO_ACCEPT_INVITATION_FLAG_ISOLATED ((MojoAcceptInvitationFlags)1)
+
+// Options passed to |MojoAcceptInvitation()|.
+struct MOJO_ALIGNAS(8) MojoAcceptInvitationOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoAcceptInvitationFlags|.
+ MojoAcceptInvitationFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoAcceptInvitationOptions) == 8,
+ "MojoAcceptInvitationOptions has wrong size");
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// A callback which may be invoked by the system when a connection to an invited
+// process is terminated due to a communication error (i.e. the invited process
+// has sent a message which fails some validation check in the system). See
+// |MojoSendInvitation()|.
+//
+// |context| is the value of |context| given to |MojoSendInvitation()| when
+// inviting the process for whom this callback is being invoked.
+typedef void (*MojoProcessErrorHandler)(
+ uintptr_t context,
+ const struct MojoProcessErrorDetails* details);
+
+// Creates a new invitation to be sent to another process.
+//
+// An invitation is used to invite another process to join this process's
+// IPC network. The caller must already be a member of a Mojo network, either
+// either by itself having been previously invited, or by being the Mojo broker
+// process initialized via the Mojo Core Embedder API.
+//
+// Invitations can have message pipes attached to them, and these message pipes
+// are used to bootstrap Mojo IPC between the inviter and the invitee. See
+// |MojoAttachMessagePipeToInvitation()| for attaching message pipes, and
+// |MojoSendInvitation()| for sending an invitation.
+//
+// |options| controls behavior. May be null for default behavior.
+// |invitation_handle| must be the address of storage for a MojoHandle value
+// to be output upon success.
+//
+// NOTE: If discarding an invitation instead of sending it with
+// |MojoSendInvitation()|, you must close its handle (i.e. |MojoClose()|) to
+// avoid leaking associated system resources.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the invitation was created successfully. The new
+// invitation's handle is stored in |*invitation_handle| before returning.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |options| was non-null but malformed.
+// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a handle could not be allocated for the
+// new invitation.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoCreateInvitation(const struct MojoCreateInvitationOptions* options,
+ MojoHandle* invitation_handle);
+
+// Attaches a message pipe endpoint to an invitation.
+//
+// This creates a new message pipe which will span the boundary between the
+// calling process and the invitation's eventual target process. One end of the
+// new pipe is attached to the invitation while the other end is returned to the
+// caller. Every attached message pipe has an arbitrary |name| value which
+// identifies it within the invitation.
+//
+// Message pipes can be extracted by the recipient by calling
+// |MojoExtractMessagePipeFromInvitation()|. It is up to applications to
+// communicate out-of-band or establish a convention for how attached pipes
+// are named.
+//
+// |invitation_handle| is the invitation to which a pipe should be attached.
+// |name| is an arbitrary name to give this pipe, required to extract the pipe
+// on the receiving end of the invitation. Note that the name is scoped to
+// this invitation only, so e.g. multiple invitations may attach pipes with
+// the name "foo", but any given invitation may only have a single pipe
+// attached with that name.
+// |name_num_bytes| is the number of bytes from |name| to use as the name.
+// |options| controls behavior. May be null for default behavior.
+// |message_pipe_handle| is the address of storage for a MojoHandle value.
+// Upon success, the handle of the local endpoint of the new message pipe
+// will be stored here.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the pipe was created and attached successfully. The
+// local endpoint of the pipe has its handle stored in
+// |*message_pipe_handle| before returning. The other endpoint is attached
+// to the invitation.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |invitation_handle| was not an invitation
+// handle, |options| was non-null but malformed, or |message_pipe_handle|
+// was null.
+// |MOJO_RESULT_ALREADY_EXISTS| if |name| was already in use for this
+// invitation.
+// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a handle could not be allocated for the
+// new local message pipe endpoint.
+MOJO_SYSTEM_EXPORT MojoResult MojoAttachMessagePipeToInvitation(
+ MojoHandle invitation_handle,
+ const void* name,
+ uint32_t name_num_bytes,
+ const struct MojoAttachMessagePipeToInvitationOptions* options,
+ MojoHandle* message_pipe_handle);
+
+// Extracts a message pipe endpoint from an invitation.
+//
+// |invitation_handle| is the invitation from which to extract the endpoint.
+// |name| is the name of the endpoint within the invitation. This corresponds
+// to the name that was given to |MojoAttachMessagePipeToInvitation()| when
+// the endpoint was attached.
+// |name_num_bytes| is the number of bytes from |name| to use as the name.
+// |options| controls behavior. May be null for default behavior.
+// |message_pipe_handle| is the address of storage for a MojoHandle value.
+// Upon success, the handle of the extracted message pipe endpoint will be
+// stored here.
+//
+// Note that it is possible to extract an endpoint from an invitation even
+// before the invitation has been sent to a remote process. If this is done and
+// then the invitation is sent, the receiver will not see this endpoint as it
+// will no longer be attached.
+//
+// Returns:
+// |MOJO_RESULT_OK| if a new local message pipe endpoint was successfully
+// created and returned in |*message_pipe_handle|. Note that the
+// association of this endpoint with an invitation attachment is
+// necessarily an asynchronous operation, and it is not known at return
+// whether an attachment named |name| actually exists on the invitation.
+// As such, the operation may still fail eventually, resuling in a broken
+// pipe, i.e. the extracted pipe will signal peer closure.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |invitation_handle| was not an invitation
+// handle, |options| was non-null but malformed, or |message_pipe_handle|
+// was null.
+// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a handle could not be allocated for the
+// new local message pipe endpoint.
+// |MOJO_RESULT_NOT_FOUND| if it is known at call time that there is no pipe
+// named |name| attached to the invitation. This is possible if the
+// invtation was created within the calling process by
+// |MojoCreateInvitation()|.
+MOJO_SYSTEM_EXPORT MojoResult MojoExtractMessagePipeFromInvitation(
+ MojoHandle invitation_handle,
+ const void* name,
+ uint32_t name_num_bytes,
+ const struct MojoExtractMessagePipeFromInvitationOptions* options,
+ MojoHandle* message_pipe_handle);
+
+// Sends an invitation on a transport endpoint to bootstrap IPC between the
+// calling process and another process.
+//
+// |invitation_handle| is the handle of the invitation to send.
+// |process_handle| is an opaque, platform-specific handle to the remote
+// process. See |MojoPlatformProcessHandle|. This is not necessarily
+// required to be a valid process handle, but on some platforms (namely
+// Windows and Mac) it's important if the invitation target will need to
+// send or receive any kind of platform handles (including shared memory)
+// over Mojo message pipes.
+// |transport_endpoint| is one endpoint of a platform transport primitive, the
+// other endpoint of which should be established within the process
+// corresponding to |*process_handle|. See |MojoInvitationTransportEndpoint|
+// for details.
+// |error_handler| is a function to invoke if the connection to the invitee
+// encounters any kind of error condition, e.g. a message validation failure
+// reported by |MojoNotifyBadMessage()|, or permanent disconnection. See
+// |MojoProcessErrorDetails| for more information.
+// |error_handler_context| is an arbitrary value to be associated with this
+// process invitation. This value is passed as the |context| argument to
+// |error_handler| when invoked regarding this invitee.
+// |options| controls behavior. May be null for default behavior.
+//
+// This assumes ownership of any platform handles in |transport_endpoint| if
+// and only if returning |MOJO_RESULT_OK|. In that case, |invitation_handle| is
+// also invalidated.
+//
+// NOTE: It's pointless to send an invitation without at least one message pipe
+// attached, so it is considered an error to attempt to do so.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the invitation was successfully sent over the
+// transport. |invitation_handle| is implicitly closed. Note that this
+// does not guarantee that the invitation has been received by the target
+// yet, or that it ever will be (e.g. the target process may terminate
+// first or simply fail to accept the invitation).
+// |MOJO_RESULT_INVALID_ARGUMENT| if |invitation_handle| was not an invitation
+// handle, |transport_endpoint| was null or malformed, or |options| was
+// non-null but malformed.
+// |MOJO_RESULT_ABORTED| if the system failed to issue any necessary
+// communication via |transport_endpoint|, possibly due to a configuration
+// issue with the endpoint. The caller may attempt to correct this
+// situation and call again.
+// |MOJO_RESULT_FAILED_PRECONDITION| if there were no message pipes attached
+// to the invitation. The caller may correct this situation and call
+// again.
+// |MOJO_RESULT_UNIMPLEMENTED| if the transport endpoint type is not supported
+// by the system's version of Mojo.
+MOJO_SYSTEM_EXPORT MojoResult MojoSendInvitation(
+ MojoHandle invitation_handle,
+ const struct MojoPlatformProcessHandle* process_handle,
+ const struct MojoInvitationTransportEndpoint* transport_endpoint,
+ MojoProcessErrorHandler error_handler,
+ uintptr_t error_handler_context,
+ const struct MojoSendInvitationOptions* options);
+
+// Accepts an invitation from a transport endpoint to complete IPC bootstrapping
+// between the calling process and whoever sent the invitation from the other
+// end of the transport.
+//
+// |transport_endpoint| is one endpoint of a platform transport primitive, the
+// other endpoint of which should be established within a process
+// who has sent or will send an invitation via that endpoint. See
+// |MojoInvitationTransportEndpoint| for details.
+// |options| controls behavior. May be null for default behavior.
+// |invitation_handle| is the address of storage for a MojoHandle value. Upon
+// success, the handle of the accepted invitation will be stored here.
+//
+// Once an invitation is accepted, message pipes endpoints may be extracted from
+// it by calling |MojoExtractMessagePipeFromInvitation()|.
+//
+// Note that it is necessary to eventually close (i.e. |MojoClose()|) any
+// accepted invitation handle in order to clean up any associated system
+// resources. If an accepted invitation is closed while it still has message
+// pipes attached (i.e. not exracted as above), those pipe endpoints are also
+// closed.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the invitation was successfully accepted. The handle
+// to the invitation is stored in |*invitation_handle| before returning.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |transport_endpoint| was null or
+// malfored, |options| was non-null but malformed, or |invitation_handle|
+// was null.
+// |MOJO_RESULT_ABORTED| if the system failed to receive any communication via
+// |transport_endpoint|, possibly due to some configuration error. The
+// caller may attempt to correct this situation and call again.
+// |MOJO_RESULT_UNIMPLEMENTED| if the transport endpoint type is not supported
+// by the system's version of Mojo.
+MOJO_SYSTEM_EXPORT MojoResult MojoAcceptInvitation(
+ const struct MojoInvitationTransportEndpoint* transport_endpoint,
+ const struct MojoAcceptInvitationOptions* options,
+ MojoHandle* invitation_handle);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // MOJO_PUBLIC_C_SYSTEM_INVITATION_H_
diff --git a/mojo/public/c/system/macros.h b/mojo/public/c/system/macros.h
index 917c69cc15..6de4a913de 100644
--- a/mojo/public/c/system/macros.h
+++ b/mojo/public/c/system/macros.h
@@ -17,6 +17,15 @@
#define MOJO_STATIC_ASSERT(expr, msg)
#endif
+// Defines a pointer-sized struct field of the given type. This ensures that the
+// field has an 8-byte footprint on both 32-bit and 64-bit systems, using an
+// anonymous bitfield of either 32 or 0 bits, depending on pointer size. Weird
+// formatting here courtesy of clang-format.
+#define MOJO_POINTER_FIELD(type, name) \
+ type name; \
+ uint32_t: \
+ (sizeof(void*) == 4 ? 32 : 0)
+
// Like the C++11 |alignof| operator.
#if __cplusplus >= 201103L
#define MOJO_ALIGNOF(type) alignof(type)
diff --git a/mojo/public/c/system/message_pipe.h b/mojo/public/c/system/message_pipe.h
index b759bc73db..9f222f9aa8 100644
--- a/mojo/public/c/system/message_pipe.h
+++ b/mojo/public/c/system/message_pipe.h
@@ -15,90 +15,234 @@
#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()|.
-
+// Used to refer to message objects created by |MojoCreateMessage()|.
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;
+// Flags passed to |MojoCreateMessagePipe()| via |MojoCreateMessagePipeOptions|.
+// See values defined below.
+typedef uint32_t MojoCreateMessagePipeFlags;
-#ifdef __cplusplus
-const MojoCreateMessagePipeOptionsFlags
- MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE = 0;
-#else
-#define MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE \
- ((MojoCreateMessagePipeOptionsFlags)0)
-#endif
+// No flags. Default behavior.
+#define MOJO_CREATE_MESSAGE_PIPE_FLAG_NONE ((uint32_t)0)
-MOJO_STATIC_ASSERT(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment");
+// Options passed to |MojoCreateMessagePipe()|.
struct MOJO_ALIGNAS(8) MojoCreateMessagePipeOptions {
+ // The size of this structure, used for versioning.
uint32_t struct_size;
- MojoCreateMessagePipeOptionsFlags flags;
+
+ // See |MojoCreateMessagePipeFlags|.
+ MojoCreateMessagePipeFlags flags;
};
+MOJO_STATIC_ASSERT(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment");
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.
-
+// Flags passed to |MojoWriteMessage()| via |MojoWriteMessageOptions|. See
+// values defined below.
typedef uint32_t MojoWriteMessageFlags;
-#ifdef __cplusplus
-const MojoWriteMessageFlags MOJO_WRITE_MESSAGE_FLAG_NONE = 0;
-#else
-#define MOJO_WRITE_MESSAGE_FLAG_NONE ((MojoWriteMessageFlags)0)
-#endif
+// No flags. Default behavior.
+#define MOJO_WRITE_MESSAGE_FLAG_NONE ((uint32_t)0)
-// |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).
+// Options passed to |MojoWriteMessage()|.
+struct MOJO_ALIGNAS(8) MojoWriteMessageOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoWriteMessageFlags|.
+ MojoWriteMessageFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoWriteMessageOptions) == 8,
+ "MojoWriteMessageOptions has wrong size");
+// Flags passed to |MojoReadMessage()| via |MojoReadMessageOptions|. See values
+// defined below.
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
+// No flags. Default behavior.
+#define MOJO_READ_MESSAGE_FLAG_NONE ((uint32_t)0)
+
+// Options passed to |MojoReadMessage()|.
+struct MOJO_ALIGNAS(8) MojoReadMessageOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
-// |MojoAllocMessageFlags|: Used to specify different options for
-// |MojoAllocMessage()|.
-// |MOJO_ALLOC_MESSAGE_FLAG_NONE| - No flags; default mode.
+ // See |MojoReadMessageFlags|.
+ MojoReadMessageFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoReadMessageOptions) == 8,
+ "MojoReadMessageOptions has wrong size");
-typedef uint32_t MojoAllocMessageFlags;
+// Flags passed to |MojoFuseMessagePipes()| via |MojoFuseMessagePipeOptions|.
+// See values defined below.
+typedef uint32_t MojoFuseMessagePipesFlags;
-#ifdef __cplusplus
-const MojoAllocMessageFlags MOJO_ALLOC_MESSAGE_FLAG_NONE = 0;
-#else
-#define MOJO_ALLOC_MESSAGE_FLAG_NONE ((MojoAllocMessageFlags)0)
-#endif
+// No flags. Default behavior.
+#define MOJO_FUSE_MESSAGE_PIPES_FLAG_NONE ((uint32_t)0)
+
+// Options passed to |MojoFuseMessagePipes()|.
+struct MOJO_ALIGNAS(8) MojoFuseMessagePipesOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoFuseMessagePipesFlags|.
+ MojoFuseMessagePipesFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoFuseMessagePipesOptions) == 8,
+ "MojoFuseMessagePipesOptions has wrong size");
+
+// Flags passed to |MojoCreateMessage()| via |MojoCreateMessageOptions|.
+typedef uint32_t MojoCreateMessageFlags;
+
+// No flags. Default behavior.
+#define MOJO_CREATE_MESSAGE_FLAG_NONE ((uint32_t)0)
+
+// Options passed to |MojoCreateMessage()|.
+struct MOJO_ALIGNAS(8) MojoCreateMessageOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoCreateMessageFlags|.
+ MojoCreateMessageFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoCreateMessageOptions) == 8,
+ "MojoCreateMessageOptions has wrong size");
+
+// Flags passed to |MojoSerializeMessage()| via |MojoSerializeMessageOptions|.
+typedef uint32_t MojoSerializeMessageFlags;
+
+// No flags. Default behavior.
+#define MOJO_SERIALIZE_MESSAGE_FLAG_NONE ((uint32_t)0)
+
+// Options passed to |MojoSerializeMessage()|.
+struct MOJO_ALIGNAS(8) MojoSerializeMessageOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoSerializeMessageFlags|.
+ MojoSerializeMessageFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoSerializeMessageOptions) == 8,
+ "MojoSerializeMessageOptions has wrong size");
+
+// Flags passed to |MojoAppendMessageData()| via |MojoAppendMessageDataOptions|.
+typedef uint32_t MojoAppendMessageDataFlags;
+
+// No flags. Default behavior.
+#define MOJO_APPEND_MESSAGE_DATA_FLAG_NONE ((uint32_t)0)
+
+// If set, this comments the resulting (post-append) message size as the final
+// size of the message payload, in terms of both bytes and attached handles.
+#define MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE \
+ ((MojoAppendMessageDataFlags)1)
+
+// Options passed to |MojoAppendMessageData()|.
+struct MOJO_ALIGNAS(8) MojoAppendMessageDataOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoAppendMessageDataFlags|.
+ MojoAppendMessageDataFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoAppendMessageDataOptions) == 8,
+ "MojoAppendMessageDataOptions has wrong size");
+
+// Flags passed to |MojoGetMessageData()| via |MojoGetMessageDataOptions|.
+typedef uint32_t MojoGetMessageDataFlags;
+
+// No flags. Default behavior.
+#define MOJO_GET_MESSAGE_DATA_FLAG_NONE ((uint32_t)0)
+
+// Ignores attached handles when retrieving message data. This leaves any
+// attached handles intact and owned by the message object.
+#define MOJO_GET_MESSAGE_DATA_FLAG_IGNORE_HANDLES ((uint32_t)1)
+
+// Options passed to |MojoGetMessageData()|.
+struct MOJO_ALIGNAS(8) MojoGetMessageDataOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoGetMessageDataFlags|.
+ MojoGetMessageDataFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoGetMessageDataOptions) == 8,
+ "MojoGetMessageDataOptions has wrong size");
+
+// Flags passed to |MojoSetMessageContext()| via |MojoSetMessageContextOptions|.
+typedef uint32_t MojoSetMessageContextFlags;
+
+// No flags. Default behavior.
+#define MOJO_SET_MESSAGE_CONTEXT_FLAG_NONE ((uint32_t)0)
+
+// Options passed to |MojoSetMessageContext()|.
+struct MOJO_ALIGNAS(8) MojoSetMessageContextOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoSetMessageContextFlags|.
+ MojoSetMessageContextFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoSetMessageContextOptions) == 8,
+ "MojoSetMessageContextOptions has wrong size");
+
+// Flags passed to |MojoGetMessageContext()| via |MojoGetMessageContextOptions|.
+typedef uint32_t MojoGetMessageContextFlags;
+
+// No flags. Default behavior.
+#define MOJO_GET_MESSAGE_CONTEXT_FLAG_NONE ((uint32_t)0)
+
+// Options passed to |MojoGetMessageContext()|.
+struct MOJO_ALIGNAS(8) MojoGetMessageContextOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoGetMessageContextFlags|.
+ MojoGetMessageContextFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoGetMessageContextOptions) == 8,
+ "MojoGetMessageContextOptions has wrong size");
+
+// Flags passed to |MojoNotifyBadMessage()| via |MojoNotifyBadMessageOptions|.
+typedef uint32_t MojoNotifyBadMessageFlags;
+
+// No flags. Default behavior.
+#define MOJO_NOTIFY_BAD_MESSAGE_FLAG_NONE ((uint32_t)0)
+
+// Options passed to |MojoNotifyBadMessage()|.
+struct MOJO_ALIGNAS(8) MojoNotifyBadMessageOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoNotifyBadMessageFlags|.
+ MojoNotifyBadMessageFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoNotifyBadMessageOptions) == 8,
+ "MojoNotifyBadMessageOptions has wrong size");
#ifdef __cplusplus
extern "C" {
#endif
+// A callback which can serialize a message given some context. Passed to
+// |MojoSetMessageContext()| along with a context it knows how to serialize.
+// See |MojoSetMessageContext()| for more details.
+//
+// |message| is a message object which had |context| attached.
+// |context| the context which was attached to |message|.
+//
+// Note that the context is always detached from the message immediately before
+// this callback is invoked, and that the associated destructor (if any) is also
+// invoked on |context| immediately after the serializer returns.
+typedef void (*MojoMessageContextSerializer)(MojoMessageHandle message,
+ uintptr_t context);
+
+// A callback which can be used to destroy a message context after serialization
+// or in the event that the message to which it's attached is destroyed without
+// ever being serialized.
+typedef void (*MojoMessageContextDestructor)(uintptr_t context);
+
// Note: See the comment in functions.h about the meaning of the "optional"
// label for pointer parameters.
@@ -122,115 +266,48 @@ MOJO_SYSTEM_EXPORT MojoResult MojoCreateMessagePipe(
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.
+// Writes a message to the message pipe endpoint given by |message_pipe_handle|.
//
-// 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).
+// Note that regardless of success or failure, |message| is destroyed by this
+// call and therefore invalidated.
+//
+// |options| may be null.
//
// 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_INVALID_ARGUMENT| if |message_pipe_handle| or |message| is
+// invalid.
// |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_RESULT_NOT_FOUND| if |message| has neither a context nor serialized
+// buffer attached and therefore has nothing to be written.
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);
+MojoWriteMessage(MojoHandle message_pipe_handle,
+ MojoMessageHandle message,
+ const struct MojoWriteMessageOptions* options);
-// Writes a message to the message pipe endpoint given by |message_pipe_handle|.
+// Reads the next message from a message pipe and returns a message as an opaque
+// message handle. The returned message must eventually be destroyed using
+// |MojoDestroyMessage()|.
//
-// |message|: A message object allocated by |MojoAllocMessage()|. Ownership of
-// the message is passed into Mojo.
+// Message payload and handles can be accessed using |MojoGetMessageData()|. For
+// Unserialized messages, context may be accessed using
+// |MojoGetMessageContext()|.
//
-// 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.
+// |options| may be null. |message| must be non-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_FAILED_PRECONDITION| if the other endpoint has been closed
+// and there are no more messages to read.
// |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);
+MojoReadMessage(MojoHandle message_pipe_handle,
+ const struct MojoReadMessageOptions* options,
+ MojoMessageHandle* message);
// Fuses two message pipe endpoints together. Given two pipes:
//
@@ -247,6 +324,8 @@ MOJO_SYSTEM_EXPORT MojoResult
// NOTE: A handle may only be fused if it is an open message pipe handle which
// has not been written to.
//
+// |options| may be null.
+//
// Returns:
// |MOJO_RESULT_OK| on success.
// |MOJO_RESULT_FAILED_PRECONDITION| if both handles were valid message pipe
@@ -254,64 +333,243 @@ MOJO_SYSTEM_EXPORT MojoResult
// |MOJO_INVALID_ARGUMENT| if either handle is not a fusable message pipe
// handle.
MOJO_SYSTEM_EXPORT MojoResult
- MojoFuseMessagePipes(MojoHandle handle0, MojoHandle handle1);
+MojoFuseMessagePipes(MojoHandle handle0,
+ MojoHandle handle1,
+ const struct MojoFuseMessagePipesOptions* options);
-// Allocates a new message whose ownership may be passed to
-// |MojoWriteMessageNew()|. Use |MojoGetMessageBuffer()| to retrieve the address
-// of the mutable message payload.
+// Creates a new message object which may be sent over a message pipe via
+// |MojoWriteMessage()|. Returns a handle to the new message object in
+// |*message|.
//
-// |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.
+// In its initial state the message object cannot be successfully written to a
+// message pipe, but must first have either an opaque context or some serialized
+// data attached (see |MojoSetMessageContext()| and
+// |MojoAppendMessageData()|).
+//
+// NOTE: Unlike other types of Mojo API objects, messages are NOT thread-safe
+// and thus callers of message-related APIs must be careful to restrict usage of
+// any given |MojoMessageHandle| to a single thread at a time.
//
// 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_RESULT_OK| if a new message was created. |*message| contains a handle
+// to the new message object upon return.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |message| is null.
MOJO_SYSTEM_EXPORT MojoResult
-MojoAllocMessage(uint32_t num_bytes,
- const MojoHandle* handles,
- uint32_t num_handles,
- MojoAllocMessageFlags flags,
- MojoMessageHandle* message); // Out
+MojoCreateMessage(const struct MojoCreateMessageOptions* options,
+ MojoMessageHandle* message);
-// Frees a message allocated by |MojoAllocMessage()| or |MojoReadMessageNew()|.
+// Destroys a message object created by |MojoCreateMessage()| or
+// |MojoReadMessage()|.
//
-// |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.
+// |message|: The message to destroy. Note that if a message has been written
+// to a message pipe using |MojoWriteMessage()|, it is neither necessary nor
+// valid to attempt to destroy it.
//
// 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);
+MOJO_SYSTEM_EXPORT MojoResult MojoDestroyMessage(MojoMessageHandle message);
-// Retrieves the address of mutable message bytes for a message allocated by
-// either |MojoAllocMessage()| or |MojoReadMessageNew()|.
+// Forces a message to be serialized in-place if not already serialized.
//
// 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.
+// |MOJO_RESULT_OK| if |message| was not serialized and is now serialized.
+// In this case its thunks were invoked to perform serialization and
+// ultimately destroy its associated context. The message may still be
+// written to a pipe or decomposed by |MojoGetMessageData()|.
+// |MOJO_RESULT_FAILED_PRECONDITION| if |message| was already serialized.
+// |MOJO_RESULT_NOT_FOUND| if |message| cannot be serialized (i.e. it was
+// created with null |MojoMessageContextSerializer|.)
+// |MOJO_RESULT_BUSY| if one or more handles provided by the user context
+// reported itself as busy during the serialization attempt. In this case
+// all serialized handles are closed automatically.
+// |MOJO_RESULT_ABORTED| if some other unspecified error occurred during
+// handle serialization. In this case all serialized handles are closed
+// automatically.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |message| is not a valid message handle.
//
-// NOTE: A returned buffer address is always guaranteed to be 8-byte aligned.
-MOJO_SYSTEM_EXPORT MojoResult MojoGetMessageBuffer(MojoMessageHandle message,
- void** buffer); // Out
+// Note that unserialized messages may be successfully transferred from one
+// message pipe endpoint to another without ever being serialized. This function
+// allows callers to coerce eager serialization.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoSerializeMessage(MojoMessageHandle message,
+ const struct MojoSerializeMessageOptions* options);
+
+// Appends data to a message object.
+//
+// |message|: The message.
+// |additional_payload_size|: The number of bytes by which to extend the payload
+// of the message.
+// |handles|: Handles to be appended to the message. May be null iff
+// |num_handles| is 0.
+// |num_handles|: The number of handles to be appended to the message.
+//
+// |options| may be null.
+//
+// If this call succeeds, |*buffer| will contain the address of the data's
+// storage if |buffer| was non-null, and if |buffer_size| was non-null then
+// |*buffer_size| will contain the storage capacity. The caller may write
+// message contents here.
+//
+// Note that while the size of the returned buffer may exceed the total
+// requested size accumulated over one or more calls to
+// |MojoAppendMessageData()|, only the extent of caller's requested capacity is
+// considered to be part of the message.
+//
+// A message with attached data must have its capacity finalized before it can
+// be transmitted by calling this function with
+// |MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE| set in |options->flags|. Note
+// that even after this happens, the returned |*buffer| remains valid and
+// writable until the message is passed to either |MojoWriteMessage()| or
+// |MojoDestroyMessage()|.
+//
+// Ownership of all handles in |handles| is transferred to the message object if
+// and ONLY if this operation succeeds and returns |MOJO_RESULT_OK|. Otherwise
+// the caller retains ownership.
+//
+// Returns:
+// |MOJO_RESULT_OK| upon success. The message's data buffer and size are
+// stored to |*buffer| and |*buffer_size| respectively. Any previously
+// appended data remains intact but may be moved in the event that
+// |*buffer| itself is moved. If
+// |MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE| was set in
+// |options->flags|, the message is ready for transmission.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |message| is not a valid message object;
+// if |num_handles| is non-zero but |handles| is null; or if any handle in
+// |handles| is invalid.
+// |MOJO_RESULT_RESOURCE_EXHAUSTED| if |additional_payload_size| or
+// |num_handles| exceeds some implementation- or embedder-defined maximum.
+// |MOJO_RESULT_FAILED_PRECONDITION| if |message| has a context attached.
+// |MOJO_RESULT_BUSY| if one or more handles in |handles| is currently busy
+// and unable to be serialized.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoAppendMessageData(MojoMessageHandle message,
+ uint32_t payload_size,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ const struct MojoAppendMessageDataOptions* options,
+ void** buffer,
+ uint32_t* buffer_size);
+
+// Retrieves data attached to a message object.
+//
+// |message|: The message.
+// |num_bytes|: An output parameter which will receive the total size in bytes
+// of the message's payload.
+// |buffer|: An output parameter which will receive the address of a buffer
+// containing exactly |*num_bytes| bytes of payload data. This buffer
+// address is not owned by the caller and is only valid as long as the
+// message handle in |message| is valid.
+// |num_handles|: An input/output parameter. On input, if not null, this points
+// to value specifying the available capacity (in number of handles) of
+// |handles|. On output, if not null, this will point to a value specifying
+// the actual number of handles available in the serialized message.
+// |handles|: A buffer to contain up to (input) |*num_handles| handles. May be
+// null if |num_handles| is null or |*num_handles| is 0.
+//
+// |options| may be null.
+//
+// Returns:
+// |MOJO_RESULT_OK| if |message| is a message with data attached and the
+// provided handle storage is sufficient to contain all handles attached
+// to the message. If |MOJO_GET_MESSAGE_DATA_FLAG_IGNORE_HANDLES| was set
+// in |options->flags|, any attached handles in the message are left
+// intact and both |handles| and |num_handles| are ignored. Otherwise
+// ownership of any attached handles is transferred to the caller, and
+// |MojoGetMessageData()| may no longer be called on |message| unless
+// |MOJO_GET_MESSAGE_DATA_FLAG_IGNORE_HANDLES| is used.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |num_handles| is non-null and
+// |*num_handles| is non-zero, but |handles| is null; or if |message| is
+// not a valid message handle.
+// |MOJO_RESULT_FAILED_PRECONDITION| if |message| is not a fully serialized
+// message. The caller may use |MojoSerializeMessage()| and try again,
+// or |MojoAppendMessageData()| with the
+// |MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE| flag set to complete a
+// partially serialized |message|.
+// |MOJO_RESULT_NOT_FOUND| if the message's handles (if any) contents have
+// already been extracted (or have failed to be extracted) by a previous
+// call to |MojoGetMessageData()|.
+// |MOJO_RESULT_RESOURCE_EXHAUSTED| if |num_handles| is null and there are
+// handles attached to the message, or if |*num_handles| on input is less
+// than the number of handles attached to the message. Also may be
+// returned if |num_bytes| or |buffer| is null and the message has a non-
+// empty payload. Note that if |MOJO_GET_MESSAGE_DATA_FLAG_IGNORE_HANDLES|
+// is set in |options->flags| any current or previously attached handles
+// are ignored.
+// |MOJO_RESULT_ARBORTED| if the message is in an invalid state and its data
+// and/or handles are unrecoverable. The message is left in this state but
+// future calls to this API will yield the same result.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoGetMessageData(MojoMessageHandle message,
+ const struct MojoGetMessageDataOptions* options,
+ void** buffer,
+ uint32_t* num_bytes,
+ MojoHandle* handles,
+ uint32_t* num_handles);
+
+// Sets an opaque context value on a message object. The presence of an opaque
+// context is mutually exclusive to the presence of message data.
+//
+// |context| is the context value to associate with this message, and
+// |serializer| is a function which may be called at some later time to convert
+// |message| to a serialized message object using |context| as input. See
+// |MojoMessageContextSerializer| for more details. |destructor| is a function
+// which may be called to allow the user to cleanup state associated with
+// |context| after serialization or in the event that the message is destroyed
+// without ever being serialized.
+//
+// Typically a caller will use |context| as an opaque pointer to some heap
+// object which is effectively owned by the message once this returns. In this
+// way, messages can be sent over a message pipe to a peer endpoint in the same
+// process as the sender without performing a serialization step.
+//
+// If the message does need to cross a process boundary or is otherwise
+// forced to serialize (see |MojoSerializeMessage()| below), it will be
+// serialized by invoking |serializer|.
+//
+// If |serializer| is null, the created message cannot be serialized. Subsequent
+// calls to |MojoSerializeMessage()| on the created message, or any attempt to
+// transmit the message across a process boundary, will fail.
+//
+// If |destructor| is null, it is assumed that no cleanup is required after
+// serializing or destroying a message with |context| attached.
+//
+// A |context| value of zero is invalid, and setting this on a message
+// effectively removes its context. This is necessary if the caller wishes to
+// attach data to the message or if the caller wants to destroy the message
+// object without triggering |destructor|.
+//
+// |options| may be null.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the opaque context value was successfully set.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |message| is not a valid message object;
+// |options| is non-null and |*options| contains one or more malformed
+// fields; or |context| is zero but either |serializer| or |destructor| is
+// non-null.
+// |MOJO_RESULT_ALREADY_EXISTS| if |message| already has a non-zero context.
+// |MOJO_RESULT_FAILED_PRECONDITION| if |message| has data attached.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoSetMessageContext(MojoMessageHandle message,
+ uintptr_t context,
+ MojoMessageContextSerializer serializer,
+ MojoMessageContextDestructor destructor,
+ const struct MojoSetMessageContextOptions* options);
+
+// Retrieves a message object's opaque context value.
+//
+// |options| may be null.
+//
+// Returns:
+// |MOJO_RESULT_OK| if |message| is a valid message object. |*context|
+// contains its opaque context value, which may be zero.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |message| is not a valid message object
+// or |options| is non-null and |*options| contains one or more malformed
+// fields.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoGetMessageContext(MojoMessageHandle message,
+ const struct MojoGetMessageContextOptions* options,
+ uintptr_t* context);
// Notifies the system that a bad message was received on a message pipe,
// according to whatever criteria the caller chooses. This ultimately tries to
@@ -320,19 +578,21 @@ MOJO_SYSTEM_EXPORT MojoResult MojoGetMessageBuffer(MojoMessageHandle message,
// 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()|.
+// |message|: The message to report as bad.
// |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.
//
+// |options| may be null.
+//
// 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);
+ uint32_t error_num_bytes,
+ const struct MojoNotifyBadMessageOptions* options);
#ifdef __cplusplus
} // extern "C"
diff --git a/mojo/public/c/system/platform_handle.h b/mojo/public/c/system/platform_handle.h
index 7449c2e794..17a1fbdee6 100644
--- a/mojo/public/c/system/platform_handle.h
+++ b/mojo/public/c/system/platform_handle.h
@@ -19,31 +19,28 @@
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.
-
+// The type of handle value contained in a |MojoPlatformHandle| structure.
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
+// An invalid handle value. Other contents of the |MojoPlatformHandle| are
+// ignored.
#define MOJO_PLATFORM_HANDLE_TYPE_INVALID ((MojoPlatformHandleType)0)
+
+// The |MojoPlatformHandle| value represents a POSIX file descriptor. Only
+// usable on POSIX host systems (e.g. Android, Linux, Chrome OS, Mac).
#define MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR ((MojoPlatformHandleType)1)
+
+// The |MojoPlatformHandle| value represents a Mach port right (e.g. a value
+// opaquely of type |mach_port_t|). Only usable on Mac OS X hosts.
#define MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT ((MojoPlatformHandleType)2)
+
+// The |MojoPlatformHandle| value represents a Windows HANDLE value. Only usable
+// on Windows hosts.
#define MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE ((MojoPlatformHandleType)3)
-#endif
+
+// The |MojoPlatformHandle| value represents a Fuchsia system handle. Only
+// usable on Fuchsia hosts.
+#define MOJO_PLATFORM_HANDLE_TYPE_FUCHSIA_HANDLE ((MojoPlatformHandleType)4)
// |MojoPlatformHandle|: A handle to a native platform object.
//
@@ -57,43 +54,149 @@ const MojoPlatformHandleType MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE = 3;
// value depends on the value of |type|.
//
+// Represents a native platform handle value for coersion to or from a wrapping
+// Mojo handle.
struct MOJO_ALIGNAS(8) MojoPlatformHandle {
+ // The size of this structure, used for versioning.
uint32_t struct_size;
+
+ // The type of platform handle represented by |value|.
MojoPlatformHandleType type;
+
+ // An opaque representation of the native platform handle. Interpretation and
+ // treatment of this value by Mojo depends on the value of |type|.
uint64_t value;
};
MOJO_STATIC_ASSERT(sizeof(MojoPlatformHandle) == 16,
"MojoPlatformHandle has wrong size");
-// |MojoPlatformSharedBufferHandleFlags|: Flags relevant to wrapped platform
-// shared buffers.
+// Flags passed to |MojoWrapPlatformHandle()| via
+// |MojoWrapPlatformHandleOptions|.
+typedef uint32_t MojoWrapPlatformHandleFlags;
+
+// No flags. Default behavior.
+#define MOJO_WRAP_PLATFORM_HANDLE_FLAG_NONE ((MojoWrapPlatformHandleFlags)0)
+
+// Options passed to |MojoWrapPlatformHandle()|.
+struct MOJO_ALIGNAS(8) MojoWrapPlatformHandleOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoWrapPlatformHandleFlags|.
+ MojoWrapPlatformHandleFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoWrapPlatformHandleOptions) == 8,
+ "MojoWrapPlatformHandleOptions has wrong size");
+
+// Flags passed to |MojoUnwrapPlatformHandle()| via
+// |MojoUnwrapPlatformHandleOptions|.
+typedef uint32_t MojoUnwrapPlatformHandleFlags;
+
+// No flags. Default behavior.
+#define MOJO_UNWRAP_PLATFORM_HANDLE_FLAG_NONE ((MojoUnwrapPlatformHandleFlags)0)
+
+// Options passed to |MojoUnwrapPlatformHandle()|.
+struct MOJO_ALIGNAS(8) MojoUnwrapPlatformHandleOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoUnwrapPlatformHandleFlags|.
+ MojoUnwrapPlatformHandleFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoUnwrapPlatformHandleOptions) == 8,
+ "MojoUnwrapPlatformHandleOptions has wrong size");
+
+// A GUID value used to identify the shared memory region backing a Mojo shared
+// buffer handle.
+struct MOJO_ALIGNAS(8) MojoSharedBufferGuid {
+ uint64_t high;
+ uint64_t low;
+};
+
+// The access type of shared memory region wrapped by a Mojo shared buffer
+// handle. See values defined below.
+typedef uint32_t MojoPlatformSharedMemoryRegionAccessMode;
+
+// The region is read-only, meaning there is at most one writable mapped handle
+// to the region somewhere, and there are any number of handles (including this
+// one) which can only be mapped read-only.
//
-// |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.
+// WARNING: See notes in |MojoWrapPlatformSharedMemoryRegion()| about the
+// meaning and usage of different access modes. This CANNOT be used to change
+// a buffer's access mode; it is merely an informational value to allow Mojo
+// to retain consistency between wrapping and unwrapping of buffer handles.
+#define MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_READ_ONLY \
+ ((MojoPlatformSharedMemoryRegionAccessMode)0)
-typedef uint32_t MojoPlatformSharedBufferHandleFlags;
+// The region is writable, meaning there is exactly one handle to the region and
+// it is mappable read/writable.
+//
+// WARNING: See notes in |MojoWrapPlatformSharedMemoryRegion()| about the
+// meaning and usage of different access modes. This CANNOT be used to change
+// a buffer's access mode; it is merely an informational value to allow Mojo
+// to retain consistency between wrapping and unwrapping of buffer handles.
+#define MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_WRITABLE \
+ ((MojoPlatformSharedMemoryRegionAccessMode)1)
-#ifdef __cplusplus
-const MojoPlatformSharedBufferHandleFlags
-MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE = 0;
+// The region is unsafe, meaning any number of read/writable handles may refer
+// to it.
+//
+// WARNING: See notes in |MojoWrapPlatformSharedMemoryRegion()| about the
+// meaning and usage of different access modes. This CANNOT be used to change
+// a buffer's access mode; it is merely an informational value to allow Mojo
+// to retain consistency between wrapping and unwrapping of buffer handles.
+#define MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_UNSAFE \
+ ((MojoPlatformSharedMemoryRegionAccessMode)2)
-const MojoPlatformSharedBufferHandleFlags
-MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY = 1 << 0;
-#else
-#define MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE \
- ((MojoPlatformSharedBufferHandleFlags)0)
+// Flags passed to |MojoWrapPlatformSharedMemoryRegion()| via
+// |MojoWrapPlatformSharedMemoryRegionOptions|.
+typedef uint32_t MojoWrapPlatformSharedMemoryRegionFlags;
-#define MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY \
- ((MojoPlatformSharedBufferHandleFlags)1 << 0)
-#endif
+// No flags. Default behavior.
+#define MOJO_WRAP_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE \
+ ((MojoWrapPlatformSharedMemoryRegionFlags)0)
+
+// Options passed to |MojoWrapPlatformSharedMemoryRegion()|.
+struct MOJO_ALIGNAS(8) MojoWrapPlatformSharedMemoryRegionOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoWrapPlatformSharedMemoryRegionFlags|.
+ MojoWrapPlatformSharedMemoryRegionFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoWrapPlatformSharedMemoryRegionOptions) == 8,
+ "MojoWrapPlatformSharedMemoryRegionOptions has wrong size");
+
+// Flags passed to |MojoUnwrapPlatformSharedMemoryRegion()| via
+// |MojoUnwrapPlatformSharedMemoryRegionOptions|.
+typedef uint32_t MojoUnwrapPlatformSharedMemoryRegionFlags;
+
+// No flags. Default behavior.
+#define MOJO_UNWRAP_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE \
+ ((MojoUnwrapPlatformSharedMemoryRegionFlags)0)
+
+// Options passed to |MojoUnwrapPlatformSharedMemoryRegion()|.
+struct MOJO_ALIGNAS(8) MojoUnwrapPlatformSharedMemoryRegionOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoUnwrapPlatformSharedMemoryRegionFlags|.
+ MojoUnwrapPlatformSharedMemoryRegionFlags flags;
+};
+MOJO_STATIC_ASSERT(
+ sizeof(MojoUnwrapPlatformSharedMemoryRegionOptions) == 8,
+ "MojoUnwrapPlatformSharedMemoryRegionOptions has wrong size");
// 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.
+// object. i.e. if you wrap a POSIX file descriptor or Windows HANDLE and then
+// call |MojoClose()| on the resulting MojoHandle, the underlying file
+// descriptor or HANDLE will be closed.
//
// |platform_handle|: The platform handle to wrap.
//
+// |options| may be null.
+//
// Returns:
// |MOJO_RESULT_OK| if the handle was successfully wrapped. In this case
// |*mojo_handle| contains the Mojo handle of the wrapped object.
@@ -106,7 +209,8 @@ MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY = 1 << 0;
// |platform_handle->value| does not represent a valid platform object.
MOJO_SYSTEM_EXPORT MojoResult
MojoWrapPlatformHandle(const struct MojoPlatformHandle* platform_handle,
- MojoHandle* mojo_handle); // Out
+ const struct MojoWrapPlatformHandleOptions* options,
+ MojoHandle* mojo_handle);
// 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
@@ -115,6 +219,8 @@ MojoWrapPlatformHandle(const struct MojoPlatformHandle* platform_handle,
// |mojo_handle|: The Mojo handle from which to unwrap the native platform
// handle.
//
+// |options| may be null.
+//
// Returns:
// |MOJO_RESULT_OK| if the handle was successfully unwrapped. In this case
// |*platform_handle| contains the unwrapped platform handle.
@@ -122,40 +228,49 @@ MojoWrapPlatformHandle(const struct MojoPlatformHandle* platform_handle,
// handle wrapping a platform handle.
MOJO_SYSTEM_EXPORT MojoResult
MojoUnwrapPlatformHandle(MojoHandle mojo_handle,
- struct MojoPlatformHandle* platform_handle); // Out
+ const struct MojoUnwrapPlatformHandleOptions* options,
+ struct MojoPlatformHandle* platform_handle);
-// Wraps a native platform shared buffer handle as a Mojo shared buffer handle
+// Wraps a native platform shared memory region with 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.
+// Takes ownership of the native platform shared buffer handle(s).
//
-// |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.
+// |platform_handles|: The platform handle(s) to wrap. Must be one or more
+// native handles representing a shared memory region. On POSIX systems
+// with |access_mode| set to
+// |MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_WRITABLE| this must have
+// two handles, with the second one being a handle opened for read-only
+// mapping. For all other platforms and all other access modes, there should
+// be only one handle.
+// |num_platform_handles|: The number of platform handles given in
+// |platform_handles|. See note above.
+// |num_bytes|: The size of the shared memory region in bytes.
+// |access_mode|: The current access mode of the shared memory region.
+// |options|: Options to control behavior. May be null.
//
-// 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.
+// !!WARNING!!: |access_mode| DOES NOT CONTROL ACCESS TO THE REGION. It is an
+// informational field used by Mojo to ensure end-to-end consistency when
+// wrapping and unwrapping region handles. The caller is responsible for
+// ensuring that wrapped handles are already subject to the access constraints
+// conveyed by |access_mode|.
//
// 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
+MOJO_SYSTEM_EXPORT MojoResult MojoWrapPlatformSharedMemoryRegion(
+ const struct MojoPlatformHandle* platform_handles,
+ uint32_t num_platform_handles,
+ uint64_t num_bytes,
+ const struct MojoSharedBufferGuid* guid,
+ MojoPlatformSharedMemoryRegionAccessMode access_mode,
+ const struct MojoWrapPlatformSharedMemoryRegionOptions* options,
+ MojoHandle* mojo_handle);
-// Unwraps a native platform shared buffer handle from a Mojo shared buffer
+// Unwraps a native platform shared memory region from a Mojo shared buffer
// handle. If this call succeeds, ownership of the underlying shared buffer
// object is assumed by the caller.
//
@@ -163,26 +278,42 @@ MojoWrapPlatformSharedBufferHandle(
//
// |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.
+// On input, |*num_platform_handles| must be non-zero, and |platform_handles|
+// should point to enough memory to hold at least that many |MojoPlatformHandle|
+// values. Each element in |platform_handles| must have also initialized
+// |struct_size| to the caller's known |sizeof(MojoPlatformHandle)|.
+//
+// |platform_handles|, |num_platform_handles|, |num_bytes| and |access_mode| are
+// all used to receive output values and MUST always be non-null.
+//
+// |options| may be null.
+//
+// NOTE: On POSIX systems when unwrapping regions with the
+// |MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_WRITABLE| access mode,
+// this will always unwrap two platform handles, with the first one being a
+// POSIX file descriptor which can be mapped to writable memory, and the second
+// one being a POSIX file descriptor which can only be mapped read-only. For all
+// other access modes and all other platforms, this always unwraps to a single
+// platform handle.
//
// Returns:
// |MOJO_RESULT_OK| if the handle was successfully unwrapped. In this case
-// |*platform_handle| contains a platform shared buffer handle,
+// |*platform_handles| contains one or more platform handles to represent
+// the unwrapped region, |*num_platform_handles| contains the number of
+// platform handles actually stored in |platform_handles| on output,
// |*num_bytes| contains the size of the shared buffer object, and
-// |*flags| indicates flags relevant to the wrapped buffer (see below).
+// |*access_mode| indicates the access mode of the region.
// |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(
+// shared buffer handle or |*num_platform_handles| is not large enough
+// to hold all the handles that would have been unwrapped on success.
+MOJO_SYSTEM_EXPORT MojoResult MojoUnwrapPlatformSharedMemoryRegion(
MojoHandle mojo_handle,
- struct MojoPlatformHandle* platform_handle,
- size_t* num_bytes,
- MojoPlatformSharedBufferHandleFlags* flags);
+ const struct MojoUnwrapPlatformSharedMemoryRegionOptions* options,
+ struct MojoPlatformHandle* platform_handles,
+ uint32_t* num_platform_handles,
+ uint64_t* num_bytes,
+ struct MojoSharedBufferGuid* guid,
+ MojoPlatformSharedMemoryRegionAccessMode* access_mode);
#ifdef __cplusplus
} // extern "C"
diff --git a/mojo/public/c/system/quota.h b/mojo/public/c/system/quota.h
new file mode 100644
index 0000000000..0e4ad3cb72
--- /dev/null
+++ b/mojo/public/c/system/quota.h
@@ -0,0 +1,126 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_C_SYSTEM_QUOTA_H_
+#define MOJO_PUBLIC_C_SYSTEM_QUOTA_H_
+
+#include <stdint.h>
+
+#include "mojo/public/c/system/macros.h"
+#include "mojo/public/c/system/system_export.h"
+#include "mojo/public/c/system/types.h"
+
+// Flags passed to |MojoSetQuota| via |MojoSetQuotaOptions|.
+typedef uint32_t MojoSetQuotaFlags;
+
+// No flags.
+#define MOJO_SET_QUOTA_FLAG_NONE ((MojoSetQuotaFlags)0)
+
+// Options passed to |MojoSetQuota()|.
+struct MOJO_ALIGNAS(8) MojoSetQuotaOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoSetQuotaFlags| above.
+ MojoSetQuotaFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoSetQuotaOptions) == 8,
+ "MojoSetQuotaOptions has wrong size.");
+
+// Flags passed to |MojoQueryQuota| via |MojoQueryQuotaOptions|.
+typedef uint32_t MojoQueryQuotaFlags;
+
+// No flags.
+#define MOJO_QUERY_QUOTA_FLAG_NONE ((MojoQueryQuotaFlags)0)
+
+// Options passed to |MojoQueryQuota()|.
+struct MOJO_ALIGNAS(8) MojoQueryQuotaOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoQueryQuotaFlags| above.
+ MojoQueryQuotaFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoQueryQuotaOptions) == 8,
+ "MojoQueryQuotaOptions has wrong size.");
+
+// The maximum value any quota can be set to. Effectively means "no quota".
+#define MOJO_QUOTA_LIMIT_NONE ((uint64_t)0xffffffffffffffff)
+
+// An enumeration of different types of quotas that can be set on a handle.
+typedef uint32_t MojoQuotaType;
+
+// Limits the number of unread messages which can be queued on a message pipe
+// endpoint before raising a |MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED| signal on that
+// endpoint. May only be set on message pipe handles.
+#define MOJO_QUOTA_TYPE_RECEIVE_QUEUE_LENGTH ((MojoQuotaType)0)
+
+// Limits the total size (in bytes) of unread messages which can be queued on a
+// message pipe endpoint before raising a |MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED|
+// signal on that endpoint. May only be set on message pipe handles.
+#define MOJO_QUOTA_TYPE_RECEIVE_QUEUE_MEMORY_SIZE ((MojoQuotaType)1)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Sets a quota on a given handle which will cause that handle to raise the
+// |MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED| signal if the quota is exceeded. Signals
+// can be trapped using |MojoCreateTrap()| and related APIs (see trap.h).
+//
+// All quota limits on a handle default to |MOJO_QUOTA_LIMIT_NONE|, meaning that
+// the resource is unlimited.
+//
+// NOTE: A handle's quota is only enforced as long as the handle remains within
+// the process which set the quota.
+//
+// Parameters:
+// |handle|: The handle on which a quota should be set.
+// |type|: The type of quota to set. Certain types of quotas may only be set
+// on certain types of handles. See notes on individual quota type
+// definitions above for meaning and restrictions.
+// |limit|: The limiting value of the quota. The meaning of this is determined
+// by |type|. See notes on individual quota type definitions above.
+// |options|: Additional options; may be null.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the quota was successfully set.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle value,
+// |type| is not a known quota type, |options| is non-null but
+// |*options| is malformed, or the quota |type| cannot be set on |handle|
+// because the quota does not apply to that type of handle.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoSetQuota(MojoHandle handle,
+ MojoQuotaType type,
+ uint64_t limit,
+ const struct MojoSetQuotaOptions* options);
+
+// Queries a handle for information about a specific quota.
+//
+// Parameters:
+// |handle|: The handle to query.
+// |type|: The type of quota to query.
+// |limit|: Receives the quota's currently set limit if non-null.
+// |usage|: Receives the quota's current usage if non-null.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the quota was successfully queried on |handle|. Upon
+// return, |*limit| contains the quota's current limit if |limit| is
+// non-null, and |*usage| contains the quota's current usage if |usage| is
+// non-null.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle value or
+// quota |type| does not apply to the type of object referenced by
+// |handle|.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoQueryQuota(MojoHandle handle,
+ MojoQuotaType type,
+ const struct MojoQueryQuotaOptions* options,
+ uint64_t* limit,
+ uint64_t* usage);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // MOJO_PUBLIC_C_SYSTEM_QUOTA_H_
diff --git a/mojo/public/c/system/set_thunks_for_app.cc b/mojo/public/c/system/set_thunks_for_app.cc
deleted file mode 100644
index 335cc02b79..0000000000
--- a/mojo/public/c/system/set_thunks_for_app.cc
+++ /dev/null
@@ -1,20 +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 "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/tests/BUILD.gn b/mojo/public/c/system/tests/BUILD.gn
index bace63c4aa..f116cc6c91 100644
--- a/mojo/public/c/system/tests/BUILD.gn
+++ b/mojo/public/c/system/tests/BUILD.gn
@@ -6,12 +6,12 @@ source_set("tests") {
testonly = true
visibility = [
- "//mojo/public/cpp/system/tests:mojo_public_system_unittests",
+ "//mojo/public/cpp/system/tests:mojo_unittests",
"//mojo/public/cpp/system/tests:tests",
]
sources = [
- "core_unittest.cc",
+ "core_api_unittest.cc",
"core_unittest_pure_c.c",
"macros_unittest.cc",
]
diff --git a/mojo/public/c/system/tests/core_api_unittest.cc b/mojo/public/c/system/tests/core_api_unittest.cc
new file mode 100644
index 0000000000..dd30eadb97
--- /dev/null
+++ b/mojo/public/c/system/tests/core_api_unittest.cc
@@ -0,0 +1,327 @@
+// 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 <stdint.h>
+#include <string.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 {
+
+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 | MOJO_HANDLE_SIGNAL_PEER_REMOTE |
+ MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED;
+
+TEST(CoreAPITest, GetTimeTicksNow) {
+ const MojoTimeTicks start = MojoGetTimeTicksNow();
+ EXPECT_NE(static_cast<MojoTimeTicks>(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(CoreAPITest, 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, MOJO_MESSAGE_HANDLE_INVALID, nullptr));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoReadMessage(h0, nullptr, nullptr));
+
+ // Data pipe:
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoWriteData(h0, buffer, &buffer_size, nullptr));
+ write_pointer = nullptr;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoBeginWriteData(h0, nullptr, &write_pointer, &buffer_size));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoEndWriteData(h0, 1, nullptr));
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoReadData(h0, nullptr, buffer, &buffer_size));
+ read_pointer = nullptr;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoBeginReadData(h0, nullptr, &read_pointer, &buffer_size));
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoEndReadData(h0, 1, nullptr));
+
+ // 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, nullptr, &write_pointer));
+}
+
+TEST(CoreAPITest, BasicMessagePipe) {
+ MojoHandle h0, h1;
+ MojoHandleSignals sig;
+
+ 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.
+ MojoMessageHandle message;
+ EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, MojoReadMessage(h0, nullptr, &message));
+
+ // Write to |h1|.
+ const uintptr_t kTestMessageContext = 1234;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessage(nullptr, &message));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoSetMessageContext(message, kTestMessageContext,
+ nullptr, nullptr, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWriteMessage(h1, message, nullptr));
+
+ // |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|.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoReadMessage(h0, nullptr, &message));
+ uintptr_t context;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoGetMessageContext(message, nullptr, &context));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoSetMessageContext(message, 0, nullptr, nullptr, nullptr));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message));
+ EXPECT_EQ(kTestMessageContext, context);
+
+ // |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_FALSE(state.satisfiable_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+ EXPECT_FALSE(state.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE);
+
+ EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1));
+}
+
+TEST(CoreAPITest, 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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ 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 |
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ state.satisfiable_signals);
+
+ // Try to read from |hc|.
+ buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT,
+ MojoReadData(hc, nullptr, buffer, &buffer_size));
+
+ // Try to begin a two-phase read from |hc|.
+ read_pointer = nullptr;
+ EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT,
+ MojoBeginReadData(hc, nullptr, &read_pointer, &buffer_size));
+
+ // Write to |hp|.
+ static const char kHello[] = "hello ";
+ // Don't include terminating null.
+ buffer_size = static_cast<uint32_t>(strlen(kHello));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWriteData(hp, kHello, &buffer_size, nullptr));
+
+ // |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_NEW_DATA_READABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_PEER_REMOTE,
+ states[0].satisfiable_signals);
+
+ // Do a two-phase write to |hp|.
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoBeginWriteData(hp, nullptr, &write_pointer, &buffer_size));
+ 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<uint32_t>(sizeof(kWorld)), nullptr));
+
+ // Read one character from |hc|.
+ memset(buffer, 0, sizeof(buffer));
+ buffer_size = 1;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoReadData(hc, nullptr, buffer, &buffer_size));
+
+ // 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, nullptr, &read_pointer, &buffer_size));
+ ASSERT_LE(buffer_size, sizeof(buffer) - 1);
+ memcpy(&buffer[1], read_pointer, buffer_size);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoEndReadData(hc, buffer_size, nullptr));
+ 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(CoreAPITest, BasicSharedBuffer) {
+ MojoSharedBufferInfo buffer_info;
+ buffer_info.struct_size = sizeof(buffer_info);
+ MojoHandle h0 = MOJO_HANDLE_INVALID;
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoGetBufferInfo(h0, nullptr, &buffer_info));
+
+ // Create a shared buffer (|h0|).
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateSharedBuffer(100, nullptr, &h0));
+ EXPECT_NE(h0, MOJO_HANDLE_INVALID);
+
+ EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
+ MojoGetBufferInfo(h0, nullptr, nullptr));
+ ASSERT_EQ(MOJO_RESULT_OK, MojoGetBufferInfo(h0, nullptr, &buffer_info));
+ EXPECT_GE(buffer_info.size, 100U);
+
+ // Map everything.
+ void* pointer = nullptr;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoMapBuffer(h0, 0, 100, nullptr, &pointer));
+ ASSERT_TRUE(pointer);
+ static_cast<char*>(pointer)[50] = 'x';
+
+ // Duplicate |h0| to |h1|.
+ MojoHandle 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<char*>(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, nullptr, &pointer));
+ ASSERT_TRUE(pointer);
+
+ // It should have what we wrote.
+ EXPECT_EQ('x', static_cast<char*>(pointer)[0]);
+ EXPECT_EQ('y', static_cast<char*>(pointer)[1]);
+
+ // Unmap it.
+ EXPECT_EQ(MOJO_RESULT_OK, MojoUnmapBuffer(pointer));
+
+ buffer_info.size = 0;
+ ASSERT_EQ(MOJO_RESULT_OK, MojoGetBufferInfo(h1, nullptr, &buffer_info));
+ EXPECT_GE(buffer_info.size, 100U);
+
+ 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(CoreAPITest, 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_perftest.cc b/mojo/public/c/system/tests/core_perftest.cc
index cab465b43d..947c32b7d7 100644
--- a/mojo/public/c/system/tests/core_perftest.cc
+++ b/mojo/public/c/system/tests/core_perftest.cc
@@ -12,6 +12,7 @@
#include "base/macros.h"
#include "base/threading/simple_thread.h"
+#include "mojo/public/cpp/system/message_pipe.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"
@@ -37,10 +38,10 @@ class MessagePipeWriterThread : public base::SimpleThread {
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);
+ MojoResult result = mojo::WriteMessageRaw(
+ mojo::MessagePipeHandle(handle_), buffer, num_bytes_, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE);
if (result == MOJO_RESULT_OK) {
num_writes_++;
continue;
@@ -74,12 +75,11 @@ class MessagePipeReaderThread : public base::SimpleThread {
~MessagePipeReaderThread() override {}
void Run() override {
- char buffer[10000];
-
for (;;) {
- uint32_t num_bytes = static_cast<uint32_t>(sizeof(buffer));
- MojoResult result = MojoReadMessage(handle_, buffer, &num_bytes, nullptr,
- nullptr, MOJO_READ_MESSAGE_FLAG_NONE);
+ std::vector<uint8_t> bytes;
+ MojoResult result =
+ mojo::ReadMessageRaw(mojo::MessagePipeHandle(handle_), &bytes,
+ nullptr, MOJO_READ_MESSAGE_FLAG_NONE);
if (result == MOJO_RESULT_OK) {
num_reads_++;
continue;
@@ -114,7 +114,7 @@ class MessagePipeReaderThread : public base::SimpleThread {
class CorePerftest : public testing::Test {
public:
- CorePerftest() : buffer_(nullptr), num_bytes_(0) {}
+ CorePerftest() {}
~CorePerftest() override {}
static void NoOp(void* /*closure*/) {}
@@ -132,22 +132,21 @@ class CorePerftest : public testing::Test {
static void MessagePipe_WriteAndRead(void* closure) {
CorePerftest* self = static_cast<CorePerftest*>(closure);
- MojoResult result =
- MojoWriteMessage(self->h0_, self->buffer_, self->num_bytes_, nullptr, 0,
- MOJO_WRITE_MESSAGE_FLAG_NONE);
+ MojoResult result = mojo::WriteMessageRaw(
+ mojo::MessagePipeHandle(self->h0_), self->buffer_.data(),
+ self->buffer_.size(), 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,
+ result =
+ mojo::ReadMessageRaw(mojo::MessagePipeHandle(self->h1_), &self->buffer_,
nullptr, MOJO_READ_MESSAGE_FLAG_NONE);
assert(result == MOJO_RESULT_OK);
}
static void MessagePipe_EmptyRead(void* closure) {
CorePerftest* self = static_cast<CorePerftest*>(closure);
- MojoResult result =
- MojoReadMessage(self->h0_, nullptr, nullptr, nullptr, nullptr,
- MOJO_READ_MESSAGE_FLAG_MAY_DISCARD);
+ MojoMessageHandle message;
+ MojoResult result = MojoReadMessage(self->h0_, nullptr, &message);
ALLOW_UNUSED_LOCAL(result);
assert(result == MOJO_RESULT_SHOULD_WAIT);
}
@@ -233,8 +232,7 @@ class CorePerftest : public testing::Test {
MojoHandle h0_;
MojoHandle h1_;
- void* buffer_;
- uint32_t num_bytes_;
+ std::vector<uint8_t> buffer_;
private:
#if !defined(WIN32)
@@ -268,21 +266,19 @@ 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;
+ buffer_.resize(10);
mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead", "10bytes",
&CorePerftest::MessagePipe_WriteAndRead,
this);
- num_bytes_ = 100u;
+ buffer_.resize(100);
mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead", "100bytes",
&CorePerftest::MessagePipe_WriteAndRead,
this);
- num_bytes_ = 1000u;
+ buffer_.resize(1000);
mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead", "1000bytes",
&CorePerftest::MessagePipe_WriteAndRead,
this);
- num_bytes_ = 10000u;
+ buffer_.resize(10000);
mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead", "10000bytes",
&CorePerftest::MessagePipe_WriteAndRead,
this);
diff --git a/mojo/public/c/system/tests/core_unittest.cc b/mojo/public/c/system/tests/core_unittest.cc
deleted file mode 100644
index a9da255717..0000000000
--- a/mojo/public/c/system/tests/core_unittest.cc
+++ /dev/null
@@ -1,322 +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.
-
-// This file tests the C API.
-
-#include "mojo/public/c/system/core.h"
-
-#include <stdint.h>
-#include <string.h>
-
-#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<MojoTimeTicks>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(sizeof(buffer));
- EXPECT_EQ(MOJO_RESULT_OK,
- MojoReadMessage(h0, buffer, &buffer_size, nullptr, nullptr,
- MOJO_READ_MESSAGE_FLAG_NONE));
- EXPECT_EQ(static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<char*>(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<char*>(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<char*>(pointer)[0]);
- EXPECT_EQ('y', static_cast<char*>(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
index 3164649cbd..48b3c1a3c8 100644
--- a/mojo/public/c/system/tests/core_unittest_pure_c.c
+++ b/mojo/public/c/system/tests/core_unittest_pure_c.c
@@ -42,9 +42,6 @@ const char* MinimalCTest(void) {
// 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);
@@ -58,20 +55,22 @@ const char* MinimalCTest(void) {
handle1 = MOJO_HANDLE_INVALID;
EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(NULL, &handle0, &handle1));
+ MojoMessageHandle message;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessage(NULL, &message));
EXPECT_EQ(MOJO_RESULT_OK,
- MojoWriteMessage(handle0, kHello, (uint32_t)sizeof(kHello), NULL,
- 0u, MOJO_WRITE_DATA_FLAG_NONE));
+ MojoSetMessageContext(message, 42, NULL, NULL, NULL));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoWriteMessage(handle0, message, NULL));
+ EXPECT_EQ(MOJO_RESULT_OK, MojoReadMessage(handle1, NULL, &message));
- 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)));
+ uintptr_t context;
+ EXPECT_EQ(MOJO_RESULT_OK, MojoGetMessageContext(message, NULL, &context));
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoSetMessageContext(message, 0, NULL, NULL, NULL));
+ EXPECT_EQ(42, context);
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message));
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/thunks.cc b/mojo/public/c/system/thunks.cc
index 67c568f7d7..cffef2cc50 100644
--- a/mojo/public/c/system/thunks.cc
+++ b/mojo/public/c/system/thunks.cc
@@ -4,270 +4,483 @@
#include "mojo/public/c/system/thunks.h"
-#include <assert.h>
-#include <stddef.h>
-#include <stdint.h>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/protected_memory.h"
+#include "base/memory/protected_memory_cfi.h"
+#include "base/no_destructor.h"
+#include "build/build_config.h"
+#include "mojo/public/c/system/core.h"
+
+#if defined(OS_CHROMEOS) || defined(OS_LINUX) || defined(OS_WIN)
+#include "base/environment.h"
+#include "base/files/file_path.h"
+#include "base/optional.h"
+#include "base/scoped_native_library.h"
+#include "base/threading/thread_restrictions.h"
+#endif
+
+namespace {
+
+typedef void (*MojoGetSystemThunksFunction)(MojoSystemThunks* thunks);
+
+#if defined(OS_CHROMEOS) || defined(OS_LINUX) || defined(OS_WIN)
+PROTECTED_MEMORY_SECTION
+base::ProtectedMemory<MojoGetSystemThunksFunction> g_get_thunks;
+#endif
+
+PROTECTED_MEMORY_SECTION base::ProtectedMemory<MojoSystemThunks> g_thunks;
+
+MojoResult NotImplemented(const char* name) {
+ DLOG(ERROR) << "Function 'Mojo" << name
+ << "()' not supported in this version of Mojo Core.";
+ return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+} // namespace
+
+// Macro to verify that the thunk symbol |name| is actually present in the
+// runtime version of Mojo Core that is currently in use.
+#define FUNCTION_IS_IMPLEMENTED(name) \
+ (reinterpret_cast<uintptr_t>(static_cast<const void*>(&g_thunks->name)) - \
+ reinterpret_cast<uintptr_t>(static_cast<const void*>(&g_thunks)) < \
+ g_thunks->size)
+
+#define INVOKE_THUNK(name, ...) \
+ FUNCTION_IS_IMPLEMENTED(name) \
+ ? base::UnsanitizedCfiCall(g_thunks, &MojoSystemThunks::name)(__VA_ARGS__) \
+ : NotImplemented(#name)
+
+namespace mojo {
+
+// NOTE: This is defined within the global mojo namespace so that it can be
+// referenced as a friend to base::ScopedAllowBlocking when library support is
+// enabled.
+class CoreLibraryInitializer {
+ public:
+ CoreLibraryInitializer(const MojoInitializeOptions* options) {
+#if defined(OS_CHROMEOS) || defined(OS_LINUX) || defined(OS_WIN)
+ bool application_provided_path = false;
+ base::Optional<base::FilePath> library_path;
+ if (options && options->struct_size >= sizeof(*options) &&
+ options->mojo_core_path) {
+ base::StringPiece utf8_path(options->mojo_core_path,
+ options->mojo_core_path_length);
+ library_path.emplace(base::FilePath::FromUTF8Unsafe(utf8_path));
+ application_provided_path = true;
+ } else {
+ auto environment = base::Environment::Create();
+ std::string library_path_value;
+ const char kLibraryPathEnvironmentVar[] = "MOJO_CORE_LIBRARY_PATH";
+ if (environment->GetVar(kLibraryPathEnvironmentVar, &library_path_value))
+ library_path = base::FilePath::FromUTF8Unsafe(library_path_value);
+ }
+
+ if (!library_path) {
+ // Default to looking for the library in the current working directory.
+#if defined(OS_CHROMEOS) || defined(OS_LINUX)
+ const base::FilePath::CharType kDefaultLibraryPathValue[] =
+ FILE_PATH_LITERAL("./libmojo_core.so");
+#elif defined(OS_WIN)
+ const base::FilePath::CharType kDefaultLibraryPathValue[] =
+ FILE_PATH_LITERAL("mojo_core.dll");
+#endif
+ library_path.emplace(kDefaultLibraryPathValue);
+ }
+
+ base::ScopedAllowBlocking allow_blocking;
+ library_.emplace(*library_path);
+ if (!application_provided_path) {
+ CHECK(library_->is_valid())
+ << "Unable to load the mojo_core library. Make sure the library is "
+ << "in the working directory or is correctly pointed to by the "
+ << "MOJO_CORE_LIBRARY_PATH environment variable.";
+ } else {
+ CHECK(library_->is_valid())
+ << "Unable to locate mojo_core library. This application expects to "
+ << "find it at " << library_path->value();
+ }
+
+ const char kGetThunksFunctionName[] = "MojoGetSystemThunks";
+ {
+ auto writer = base::AutoWritableMemory::Create(g_get_thunks);
+ *g_get_thunks = reinterpret_cast<MojoGetSystemThunksFunction>(
+ library_->GetFunctionPointer(kGetThunksFunctionName));
+ }
+ CHECK(*g_get_thunks) << "Invalid mojo_core library: "
+ << library_path->value();
+
+ DCHECK_EQ(g_thunks->size, 0u);
+ {
+ auto writer = base::AutoWritableMemory::Create(g_thunks);
+ g_thunks->size = sizeof(*g_thunks);
+ base::UnsanitizedCfiCall(g_get_thunks)(&*g_thunks);
+ }
+
+ CHECK_GT(g_thunks->size, 0u)
+ << "Invalid mojo_core library: " << library_path->value();
+#else // defined(OS_CHROMEOS) || defined(OS_LINUX)
+ NOTREACHED()
+ << "Dynamic mojo_core loading is not supported on this platform.";
+#endif // defined(OS_CHROMEOS) || defined(OS_LINUX)
+ }
+
+ ~CoreLibraryInitializer() = default;
+
+ private:
+#if defined(OS_CHROMEOS) || defined(OS_LINUX) || defined(OS_WIN)
+ base::Optional<base::ScopedNativeLibrary> library_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(CoreLibraryInitializer);
+};
+
+} // namespace mojo
extern "C" {
-static MojoSystemThunks g_thunks = {0};
+MojoResult MojoInitialize(const struct MojoInitializeOptions* options) {
+ static base::NoDestructor<mojo::CoreLibraryInitializer> initializer(options);
+ ALLOW_UNUSED_LOCAL(initializer);
+ DCHECK(g_thunks->Initialize);
+
+ return INVOKE_THUNK(Initialize, options);
+}
MojoTimeTicks MojoGetTimeTicksNow() {
- assert(g_thunks.GetTimeTicksNow);
- return g_thunks.GetTimeTicksNow();
+ return INVOKE_THUNK(GetTimeTicksNow);
}
MojoResult MojoClose(MojoHandle handle) {
- assert(g_thunks.Close);
- return g_thunks.Close(handle);
+ return INVOKE_THUNK(Close, handle);
}
MojoResult MojoQueryHandleSignalsState(
MojoHandle handle,
struct MojoHandleSignalsState* signals_state) {
- assert(g_thunks.QueryHandleSignalsState);
- return g_thunks.QueryHandleSignalsState(handle, signals_state);
+ return INVOKE_THUNK(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);
+ return INVOKE_THUNK(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);
+ MojoMessageHandle message_handle,
+ const MojoWriteMessageOptions* options) {
+ return INVOKE_THUNK(WriteMessage, message_pipe_handle, message_handle,
+ options);
}
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);
+ const MojoReadMessageOptions* options,
+ MojoMessageHandle* message_handle) {
+ return INVOKE_THUNK(ReadMessage, message_pipe_handle, options,
+ message_handle);
+}
+
+MojoResult MojoFuseMessagePipes(MojoHandle handle0,
+ MojoHandle handle1,
+ const MojoFuseMessagePipesOptions* options) {
+ return INVOKE_THUNK(FuseMessagePipes, handle0, handle1, options);
}
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);
+ return INVOKE_THUNK(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);
+ const MojoWriteDataOptions* options) {
+ return INVOKE_THUNK(WriteData, data_pipe_producer_handle, elements,
+ num_elements, options);
}
MojoResult MojoBeginWriteData(MojoHandle data_pipe_producer_handle,
+ const MojoBeginWriteDataOptions* options,
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);
+ uint32_t* buffer_num_elements) {
+ return INVOKE_THUNK(BeginWriteData, data_pipe_producer_handle, options,
+ buffer, buffer_num_elements);
}
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);
+ uint32_t num_elements_written,
+ const MojoEndWriteDataOptions* options) {
+ return INVOKE_THUNK(EndWriteData, data_pipe_producer_handle,
+ num_elements_written, options);
}
MojoResult MojoReadData(MojoHandle data_pipe_consumer_handle,
+ const MojoReadDataOptions* options,
void* elements,
- uint32_t* num_elements,
- MojoReadDataFlags flags) {
- assert(g_thunks.ReadData);
- return g_thunks.ReadData(data_pipe_consumer_handle, elements, num_elements,
- flags);
+ uint32_t* num_elements) {
+ return INVOKE_THUNK(ReadData, data_pipe_consumer_handle, options, elements,
+ num_elements);
}
MojoResult MojoBeginReadData(MojoHandle data_pipe_consumer_handle,
+ const MojoBeginReadDataOptions* options,
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);
+ uint32_t* buffer_num_elements) {
+ return INVOKE_THUNK(BeginReadData, data_pipe_consumer_handle, options, buffer,
+ buffer_num_elements);
}
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);
+ uint32_t num_elements_read,
+ const MojoEndReadDataOptions* options) {
+ return INVOKE_THUNK(EndReadData, data_pipe_consumer_handle, num_elements_read,
+ options);
}
-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 MojoCreateSharedBuffer(uint64_t num_bytes,
+ const MojoCreateSharedBufferOptions* options,
+ MojoHandle* shared_buffer_handle) {
+ return INVOKE_THUNK(CreateSharedBuffer, num_bytes, options,
+ shared_buffer_handle);
}
MojoResult MojoDuplicateBufferHandle(
MojoHandle buffer_handle,
- const struct MojoDuplicateBufferHandleOptions* options,
+ const MojoDuplicateBufferHandleOptions* options,
MojoHandle* new_buffer_handle) {
- assert(g_thunks.DuplicateBufferHandle);
- return g_thunks.DuplicateBufferHandle(buffer_handle, options,
- new_buffer_handle);
+ return INVOKE_THUNK(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);
+ const MojoMapBufferOptions* options,
+ void** buffer) {
+ return INVOKE_THUNK(MapBuffer, buffer_handle, offset, num_bytes, options,
+ buffer);
}
MojoResult MojoUnmapBuffer(void* buffer) {
- assert(g_thunks.UnmapBuffer);
- return g_thunks.UnmapBuffer(buffer);
+ return INVOKE_THUNK(UnmapBuffer, buffer);
+}
+
+MojoResult MojoGetBufferInfo(MojoHandle buffer_handle,
+ const MojoGetBufferInfoOptions* options,
+ MojoSharedBufferInfo* info) {
+ return INVOKE_THUNK(GetBufferInfo, buffer_handle, options, info);
+}
+
+MojoResult MojoCreateTrap(MojoTrapEventHandler handler,
+ const MojoCreateTrapOptions* options,
+ MojoHandle* trap_handle) {
+ return INVOKE_THUNK(CreateTrap, handler, options, trap_handle);
+}
+
+MojoResult MojoAddTrigger(MojoHandle trap_handle,
+ MojoHandle handle,
+ MojoHandleSignals signals,
+ MojoTriggerCondition condition,
+ uintptr_t context,
+ const MojoAddTriggerOptions* options) {
+ return INVOKE_THUNK(AddTrigger, trap_handle, handle, signals, condition,
+ context, options);
}
-MojoResult MojoCreateWatcher(MojoWatcherCallback callback,
- MojoHandle* watcher_handle) {
- assert(g_thunks.CreateWatcher);
- return g_thunks.CreateWatcher(callback, watcher_handle);
+MojoResult MojoRemoveTrigger(MojoHandle trap_handle,
+ uintptr_t context,
+ const MojoRemoveTriggerOptions* options) {
+ return INVOKE_THUNK(RemoveTrigger, trap_handle, context, options);
}
-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 MojoArmTrap(MojoHandle trap_handle,
+ const MojoArmTrapOptions* options,
+ uint32_t* num_blocking_events,
+ MojoTrapEvent* blocking_events) {
+ return INVOKE_THUNK(ArmTrap, trap_handle, options, num_blocking_events,
+ blocking_events);
}
-MojoResult MojoCancelWatch(MojoHandle watcher_handle, uintptr_t context) {
- assert(g_thunks.CancelWatch);
- return g_thunks.CancelWatch(watcher_handle, context);
+MojoResult MojoCreateMessage(const MojoCreateMessageOptions* options,
+ MojoMessageHandle* message) {
+ return INVOKE_THUNK(CreateMessage, options, message);
}
-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 MojoDestroyMessage(MojoMessageHandle message) {
+ return INVOKE_THUNK(DestroyMessage, message);
}
-MojoResult MojoFuseMessagePipes(MojoHandle handle0, MojoHandle handle1) {
- assert(g_thunks.FuseMessagePipes);
- return g_thunks.FuseMessagePipes(handle0, handle1);
+MojoResult MojoSerializeMessage(MojoMessageHandle message,
+ const MojoSerializeMessageOptions* options) {
+ return INVOKE_THUNK(SerializeMessage, message, options);
}
-MojoResult MojoWriteMessageNew(MojoHandle message_pipe_handle,
- MojoMessageHandle message,
- MojoWriteMessageFlags flags) {
- assert(g_thunks.WriteMessageNew);
- return g_thunks.WriteMessageNew(message_pipe_handle, message, flags);
+MojoResult MojoAppendMessageData(MojoMessageHandle message,
+ uint32_t payload_size,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ const MojoAppendMessageDataOptions* options,
+ void** buffer,
+ uint32_t* buffer_size) {
+ return INVOKE_THUNK(AppendMessageData, message, payload_size, handles,
+ num_handles, options, buffer, buffer_size);
}
-MojoResult MojoReadMessageNew(MojoHandle message_pipe_handle,
- MojoMessageHandle* message,
+MojoResult MojoGetMessageData(MojoMessageHandle message,
+ const MojoGetMessageDataOptions* options,
+ void** buffer,
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);
+ uint32_t* num_handles) {
+ return INVOKE_THUNK(GetMessageData, message, options, buffer, num_bytes,
+ handles, num_handles);
}
-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 MojoSetMessageContext(MojoMessageHandle message,
+ uintptr_t context,
+ MojoMessageContextSerializer serializer,
+ MojoMessageContextDestructor destructor,
+ const MojoSetMessageContextOptions* options) {
+ return INVOKE_THUNK(SetMessageContext, message, context, serializer,
+ destructor, options);
}
-MojoResult MojoFreeMessage(MojoMessageHandle message) {
- assert(g_thunks.FreeMessage);
- return g_thunks.FreeMessage(message);
+MojoResult MojoGetMessageContext(MojoMessageHandle message,
+ const MojoGetMessageContextOptions* options,
+ uintptr_t* context) {
+ return INVOKE_THUNK(GetMessageContext, message, options, context);
}
-MojoResult MojoGetMessageBuffer(MojoMessageHandle message, void** buffer) {
- assert(g_thunks.GetMessageBuffer);
- return g_thunks.GetMessageBuffer(message, buffer);
+MojoResult MojoNotifyBadMessage(MojoMessageHandle message,
+ const char* error,
+ uint32_t error_num_bytes,
+ const MojoNotifyBadMessageOptions* options) {
+ return INVOKE_THUNK(NotifyBadMessage, message, error, error_num_bytes,
+ options);
}
-MojoResult MojoWrapPlatformHandle(
- const struct MojoPlatformHandle* platform_handle,
- MojoHandle* mojo_handle) {
- assert(g_thunks.WrapPlatformHandle);
- return g_thunks.WrapPlatformHandle(platform_handle, mojo_handle);
+MojoResult MojoWrapPlatformHandle(const MojoPlatformHandle* platform_handle,
+ const MojoWrapPlatformHandleOptions* options,
+ MojoHandle* mojo_handle) {
+ return INVOKE_THUNK(WrapPlatformHandle, platform_handle, options,
+ mojo_handle);
}
MojoResult MojoUnwrapPlatformHandle(
MojoHandle mojo_handle,
- struct MojoPlatformHandle* platform_handle) {
- assert(g_thunks.UnwrapPlatformHandle);
- return g_thunks.UnwrapPlatformHandle(mojo_handle, platform_handle);
+ const MojoUnwrapPlatformHandleOptions* options,
+ MojoPlatformHandle* platform_handle) {
+ return INVOKE_THUNK(UnwrapPlatformHandle, mojo_handle, options,
+ platform_handle);
}
-MojoResult MojoWrapPlatformSharedBufferHandle(
- const struct MojoPlatformHandle* platform_handle,
- size_t num_bytes,
- MojoPlatformSharedBufferHandleFlags flags,
+MojoResult MojoWrapPlatformSharedMemoryRegion(
+ const struct MojoPlatformHandle* platform_handles,
+ uint32_t num_platform_handles,
+ uint64_t num_bytes,
+ const MojoSharedBufferGuid* guid,
+ MojoPlatformSharedMemoryRegionAccessMode access_mode,
+ const MojoWrapPlatformSharedMemoryRegionOptions* options,
MojoHandle* mojo_handle) {
- assert(g_thunks.WrapPlatformSharedBufferHandle);
- return g_thunks.WrapPlatformSharedBufferHandle(platform_handle, num_bytes,
- flags, mojo_handle);
+ return INVOKE_THUNK(WrapPlatformSharedMemoryRegion, platform_handles,
+ num_platform_handles, num_bytes, guid, access_mode,
+ options, mojo_handle);
}
-MojoResult MojoUnwrapPlatformSharedBufferHandle(
+MojoResult MojoUnwrapPlatformSharedMemoryRegion(
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);
+ const MojoUnwrapPlatformSharedMemoryRegionOptions* options,
+ struct MojoPlatformHandle* platform_handles,
+ uint32_t* num_platform_handles,
+ uint64_t* num_bytes,
+ struct MojoSharedBufferGuid* guid,
+ MojoPlatformSharedMemoryRegionAccessMode* access_mode) {
+ return INVOKE_THUNK(UnwrapPlatformSharedMemoryRegion, mojo_handle, options,
+ platform_handles, num_platform_handles, num_bytes, guid,
+ access_mode);
+}
+
+MojoResult MojoCreateInvitation(const MojoCreateInvitationOptions* options,
+ MojoHandle* invitation_handle) {
+ return INVOKE_THUNK(CreateInvitation, options, invitation_handle);
+}
+
+MojoResult MojoAttachMessagePipeToInvitation(
+ MojoHandle invitation_handle,
+ const void* name,
+ uint32_t name_num_bytes,
+ const MojoAttachMessagePipeToInvitationOptions* options,
+ MojoHandle* message_pipe_handle) {
+ return INVOKE_THUNK(AttachMessagePipeToInvitation, invitation_handle, name,
+ name_num_bytes, options, message_pipe_handle);
+}
+
+MojoResult MojoExtractMessagePipeFromInvitation(
+ MojoHandle invitation_handle,
+ const void* name,
+ uint32_t name_num_bytes,
+ const MojoExtractMessagePipeFromInvitationOptions* options,
+ MojoHandle* message_pipe_handle) {
+ return INVOKE_THUNK(ExtractMessagePipeFromInvitation, invitation_handle, name,
+ name_num_bytes, options, message_pipe_handle);
+}
+
+MojoResult MojoSendInvitation(
+ MojoHandle invitation_handle,
+ const MojoPlatformProcessHandle* process_handle,
+ const MojoInvitationTransportEndpoint* transport_endpoint,
+ MojoProcessErrorHandler error_handler,
+ uintptr_t error_handler_context,
+ const MojoSendInvitationOptions* options) {
+ return INVOKE_THUNK(SendInvitation, invitation_handle, process_handle,
+ transport_endpoint, error_handler, error_handler_context,
+ options);
+}
+
+MojoResult MojoAcceptInvitation(
+ const MojoInvitationTransportEndpoint* transport_endpoint,
+ const MojoAcceptInvitationOptions* options,
+ MojoHandle* invitation_handle) {
+ return INVOKE_THUNK(AcceptInvitation, transport_endpoint, options,
+ invitation_handle);
+}
+
+MojoResult MojoSetQuota(MojoHandle handle,
+ MojoQuotaType type,
+ uint64_t limit,
+ const MojoSetQuotaOptions* options) {
+ return INVOKE_THUNK(SetQuota, handle, type, limit, options);
+}
+
+MojoResult MojoQueryQuota(MojoHandle handle,
+ MojoQuotaType type,
+ const MojoQueryQuotaOptions* options,
+ uint64_t* limit,
+ uint64_t* usage) {
+ return INVOKE_THUNK(QueryQuota, handle, type, options, limit, usage);
}
-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);
-}
+} // extern "C"
-MojoResult MojoGetProperty(MojoPropertyType type, void* value) {
- assert(g_thunks.GetProperty);
- return g_thunks.GetProperty(type, value);
-}
+void MojoEmbedderSetSystemThunks(const MojoSystemThunks* thunks) {
+ // Assume embedders will always use matching versions of the Mojo Core and
+ // public APIs.
+ DCHECK_EQ(thunks->size, sizeof(*g_thunks));
-} // extern "C"
+ // This should only have to check that the |g_thunks->size| is zero, but we
+ // have multiple Mojo Core initializations in some test suites still. For now
+ // we allow double calls as long as they're the same thunks as before.
+ DCHECK(g_thunks->size == 0 || !memcmp(&*g_thunks, thunks, sizeof(*g_thunks)))
+ << "Cannot set embedder thunks after Mojo API calls have been made.";
-size_t MojoEmbedderSetSystemThunks(const MojoSystemThunks* system_thunks) {
- if (system_thunks->size >= sizeof(g_thunks))
- g_thunks = *system_thunks;
- return sizeof(g_thunks);
+ auto writer = base::AutoWritableMemory::Create(g_thunks);
+ *g_thunks = *thunks;
}
diff --git a/mojo/public/c/system/thunks.h b/mojo/public/c/system/thunks.h
index e61bb46a46..1e5e2493c7 100644
--- a/mojo/public/c/system/thunks.h
+++ b/mojo/public/c/system/thunks.h
@@ -13,60 +13,119 @@
#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.
+// This defines the *stable*, foward-compatible ABI for the Mojo Core C library.
+// As such, the following types of changes are DISALLOWED:
+//
+// - DO NOT delete or re-order any of the fields in this structure
+// - DO NOT modify any function signatures defined here
+// - DO NOT alter the alignment of the stucture
+//
+// Some changes are of course permissible:
+//
+// - DO feel free to rename existing fields if there's a good reason to do so,
+// e.g. deprecation of a function for all future applications.
+// - DO add new functions to the end of this structure, but ensure that they
+// have a signature which lends itself to reasonably extensible behavior
+// (e.g. an optional "Options" structure as many functions here have).
+//
#pragma pack(push, 8)
struct MojoSystemThunks {
- size_t size; // Should be set to sizeof(MojoSystemThunks).
+ uint32_t size; // Should be set to sizeof(MojoSystemThunks).
+
+ MojoResult (*Initialize)(const struct MojoInitializeOptions* options);
+
MojoTimeTicks (*GetTimeTicksNow)();
+
+ // Generic handle API.
MojoResult (*Close)(MojoHandle handle);
MojoResult (*QueryHandleSignalsState)(
MojoHandle handle,
struct MojoHandleSignalsState* signals_state);
+
+ // Message pipe API.
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);
+ MojoMessageHandle message_handle,
+ const struct MojoWriteMessageOptions* options);
MojoResult (*ReadMessage)(MojoHandle message_pipe_handle,
- void* bytes,
- uint32_t* num_bytes,
- MojoHandle* handles,
- uint32_t* num_handles,
- MojoReadMessageFlags flags);
+ const struct MojoReadMessageOptions* options,
+ MojoMessageHandle* message_handle);
+ MojoResult (*FuseMessagePipes)(
+ MojoHandle handle0,
+ MojoHandle handle1,
+ const struct MojoFuseMessagePipesOptions* options);
+
+ // Message object API.
+ MojoResult (*CreateMessage)(const struct MojoCreateMessageOptions* options,
+ MojoMessageHandle* message);
+ MojoResult (*DestroyMessage)(MojoMessageHandle message);
+ MojoResult (*SerializeMessage)(
+ MojoMessageHandle message,
+ const struct MojoSerializeMessageOptions* options);
+ MojoResult (*AppendMessageData)(
+ MojoMessageHandle message,
+ uint32_t additional_payload_size,
+ const MojoHandle* handles,
+ uint32_t num_handles,
+ const struct MojoAppendMessageDataOptions* options,
+ void** buffer,
+ uint32_t* buffer_size);
+ MojoResult (*GetMessageData)(MojoMessageHandle message,
+ const struct MojoGetMessageDataOptions* options,
+ void** buffer,
+ uint32_t* num_bytes,
+ MojoHandle* handles,
+ uint32_t* num_handles);
+ MojoResult (*SetMessageContext)(
+ MojoMessageHandle message,
+ uintptr_t context,
+ MojoMessageContextSerializer serializer,
+ MojoMessageContextDestructor destructor,
+ const struct MojoSetMessageContextOptions* options);
+ MojoResult (*GetMessageContext)(
+ MojoMessageHandle message,
+ const struct MojoGetMessageContextOptions* options,
+ uintptr_t* context);
+ MojoResult (*NotifyBadMessage)(
+ MojoMessageHandle message,
+ const char* error,
+ uint32_t error_num_bytes,
+ const struct MojoNotifyBadMessageOptions* options);
+
+ // Data pipe API.
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);
+ const struct MojoWriteDataOptions* options);
MojoResult (*BeginWriteData)(MojoHandle data_pipe_producer_handle,
+ const struct MojoBeginWriteDataOptions* options,
void** buffer,
- uint32_t* buffer_num_elements,
- MojoWriteDataFlags flags);
+ uint32_t* buffer_num_elements);
MojoResult (*EndWriteData)(MojoHandle data_pipe_producer_handle,
- uint32_t num_elements_written);
+ uint32_t num_elements_written,
+ const struct MojoEndWriteDataOptions* options);
MojoResult (*ReadData)(MojoHandle data_pipe_consumer_handle,
+ const struct MojoReadDataOptions* options,
void* elements,
- uint32_t* num_elements,
- MojoReadDataFlags flags);
+ uint32_t* num_elements);
MojoResult (*BeginReadData)(MojoHandle data_pipe_consumer_handle,
+ const struct MojoBeginReadDataOptions* options,
const void** buffer,
- uint32_t* buffer_num_elements,
- MojoReadDataFlags flags);
+ uint32_t* buffer_num_elements);
MojoResult (*EndReadData)(MojoHandle data_pipe_consumer_handle,
- uint32_t num_elements_read);
+ uint32_t num_elements_read,
+ const struct MojoEndReadDataOptions* options);
+
+ // Shared buffer API.
MojoResult (*CreateSharedBuffer)(
- const struct MojoCreateSharedBufferOptions* options,
uint64_t num_bytes,
+ const struct MojoCreateSharedBufferOptions* options,
MojoHandle* shared_buffer_handle);
MojoResult (*DuplicateBufferHandle)(
MojoHandle buffer_handle,
@@ -75,74 +134,99 @@ struct MojoSystemThunks {
MojoResult (*MapBuffer)(MojoHandle buffer_handle,
uint64_t offset,
uint64_t num_bytes,
- void** buffer,
- MojoMapBufferFlags flags);
+ const struct MojoMapBufferOptions* options,
+ void** buffer);
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 (*GetBufferInfo)(MojoHandle buffer_handle,
+ const struct MojoGetBufferInfoOptions* options,
+ struct MojoSharedBufferInfo* info);
+
+ // Traps API.
+ MojoResult (*CreateTrap)(MojoTrapEventHandler handler,
+ const struct MojoCreateTrapOptions* options,
+ MojoHandle* trap_handle);
+ MojoResult (*AddTrigger)(MojoHandle trap_handle,
+ MojoHandle handle,
+ MojoHandleSignals signals,
+ MojoTriggerCondition condition,
+ uintptr_t context,
+ const struct MojoAddTriggerOptions* options);
+ MojoResult (*RemoveTrigger)(MojoHandle trap_handle,
+ uintptr_t context,
+ const struct MojoRemoveTriggerOptions* options);
+ MojoResult (*ArmTrap)(MojoHandle trap_handle,
+ const struct MojoArmTrapOptions* options,
+ uint32_t* num_blocking_events,
+ struct MojoTrapEvent* blocking_events);
+
+ // Platform handle API.
MojoResult (*WrapPlatformHandle)(
const struct MojoPlatformHandle* platform_handle,
+ const struct MojoWrapPlatformHandleOptions* options,
MojoHandle* mojo_handle);
MojoResult (*UnwrapPlatformHandle)(
MojoHandle mojo_handle,
+ const MojoUnwrapPlatformHandleOptions* options,
struct MojoPlatformHandle* platform_handle);
- MojoResult (*WrapPlatformSharedBufferHandle)(
- const struct MojoPlatformHandle* platform_handle,
- size_t num_bytes,
- MojoPlatformSharedBufferHandleFlags flags,
+ MojoResult (*WrapPlatformSharedMemoryRegion)(
+ const struct MojoPlatformHandle* platform_handles,
+ uint32_t num_platform_handles,
+ uint64_t num_bytes,
+ const struct MojoSharedBufferGuid* guid,
+ MojoPlatformSharedMemoryRegionAccessMode access_mode,
+ const struct MojoWrapPlatformSharedMemoryRegionOptions* options,
MojoHandle* mojo_handle);
- MojoResult (*UnwrapPlatformSharedBufferHandle)(
+ MojoResult (*UnwrapPlatformSharedMemoryRegion)(
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);
+ const struct MojoUnwrapPlatformSharedMemoryRegionOptions* options,
+ struct MojoPlatformHandle* platform_handles,
+ uint32_t* num_platform_handles,
+ uint64_t* num_bytes,
+ struct MojoSharedBufferGuid* guid,
+ MojoPlatformSharedMemoryRegionAccessMode* access_mode);
+
+ // Invitation API.
+ MojoResult (*CreateInvitation)(
+ const struct MojoCreateInvitationOptions* options,
+ MojoHandle* invitation_handle);
+ MojoResult (*AttachMessagePipeToInvitation)(
+ MojoHandle invitation_handle,
+ const void* name,
+ uint32_t name_num_bytes,
+ const MojoAttachMessagePipeToInvitationOptions* options,
+ MojoHandle* message_pipe_handle);
+ MojoResult (*ExtractMessagePipeFromInvitation)(
+ MojoHandle invitation_handle,
+ const void* name,
+ uint32_t name_num_bytes,
+ const MojoExtractMessagePipeFromInvitationOptions* options,
+ MojoHandle* message_pipe_handle);
+ MojoResult (*SendInvitation)(
+ MojoHandle invitation_handle,
+ const struct MojoPlatformProcessHandle* process_handle,
+ const struct MojoInvitationTransportEndpoint* transport_endpoint,
+ MojoProcessErrorHandler error_handler,
+ uintptr_t error_handler_context,
+ const struct MojoSendInvitationOptions* options);
+ MojoResult (*AcceptInvitation)(
+ const struct MojoInvitationTransportEndpoint* transport_endpoint,
+ const struct MojoAcceptInvitationOptions* options,
+ MojoHandle* invitation_handle);
+ MojoResult (*SetQuota)(MojoHandle handle,
+ MojoQuotaType type,
+ uint64_t limit,
+ const struct MojoSetQuotaOptions* options);
+ MojoResult (*QueryQuota)(MojoHandle handle,
+ MojoQuotaType type,
+ const struct MojoQueryQuotaOptions* options,
+ uint64_t* limit,
+ uint64_t* usage);
};
#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<MojoSetSystemThunksFn>(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(
+MOJO_SYSTEM_EXPORT void MojoEmbedderSetSystemThunks(
const struct MojoSystemThunks* system_thunks);
#endif // MOJO_PUBLIC_C_SYSTEM_THUNKS_H_
diff --git a/mojo/public/c/system/trap.h b/mojo/public/c/system/trap.h
new file mode 100644
index 0000000000..c5e718ec13
--- /dev/null
+++ b/mojo/public/c/system/trap.h
@@ -0,0 +1,322 @@
+// 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_TRAP_H_
+#define MOJO_PUBLIC_C_SYSTEM_TRAP_H_
+
+#include <stdint.h>
+
+#include "mojo/public/c/system/macros.h"
+#include "mojo/public/c/system/system_export.h"
+#include "mojo/public/c/system/types.h"
+
+// Flags passed to trap event handlers within |MojoTrapEvent|.
+typedef uint32_t MojoTrapEventFlags;
+
+#ifdef __cplusplus
+const MojoTrapEventFlags MOJO_TRAP_EVENT_FLAG_NONE = 0;
+const MojoTrapEventFlags MOJO_TRAP_EVENT_FLAG_WITHIN_API_CALL = 1 << 0;
+#else
+#define MOJO_TRAP_EVENT_FLAG_NONE ((MojoTrapEventFlags)0)
+#define MOJO_TRAP_EVENT_FLAG_WITHIN_API_CALL ((MojoTrapEventFlags)1 << 0)
+#endif
+
+// Structure passed to trap event handlers when invoked by a tripped trap.
+struct MOJO_ALIGNAS(8) MojoTrapEvent {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // May take on some combination of the following values:
+ //
+ // |MOJO_TRAP_EVENT_FLAG_NONE|: No flags.
+ //
+ // |MOJO_TRAP_EVENT_FLAG_WITHIN_API_CALL|: The trap was tripped within the
+ // extent of a user call to some Mojo API. This means that the event
+ // handler itself is re-entering user code. May happen, for example, if
+ // user code writes to an intra-process pipe and the receiving end trips
+ // a trap as a result. In that case the event handler executes within
+ // the extent of the |MojoWriteMessage()| call.
+ MojoTrapEventFlags flags;
+
+ // The context for the trigger which tripped the trap.
+ MOJO_POINTER_FIELD(uintptr_t, trigger_context);
+
+ // A result code indicating the cause of the event. May take on any of the
+ // following values:
+ //
+ // |MOJO_RESULT_OK|: The trigger's conditions were met.
+ // |MOJO_RESULT_FAILED_PRECONDITION|: The trigger's observed handle has
+ // changed state in such a way that the trigger's conditions can never
+ // be met again.
+ // |MOJO_RESULT_CANCELLED|: The trigger has been removed and will never
+ // cause another event to fire. This is always the last event fired by
+ // a trigger and it will fire when: the trigger is explicitly removed
+ // with |MojoRemoteTrigger()|, the trigger's owning trap handle is
+ // closed, or the handle observed by the trigger is closed.
+ //
+ // Unlike the other result types above |MOJO_RESULT_CANCELLED| can
+ // fire even when the trap is disarmed.
+ MojoResult result;
+
+ // The last known signalling state of the trigger's observed handle at the
+ // time the trap was tripped.
+ struct MojoHandleSignalsState signals_state;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoTrapEvent) == 32,
+ "MojoTrapEvent has wrong size.");
+
+// Value given to |MojoAddTrigger| to configure what condition should cause it
+// to trip its trap. May be one of the following values:
+//
+// |MOJO_TRIGGER_CONDITION_SIGNALS_UNSATISFIED| - A trigger added with this
+// condition will trip its trap when any of its observed signals
+// transition from being satisfied to being unsatisfied.
+// |MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED| - A triger added with this
+// condition will trip its trap when any of its observed signals
+// transition from being unsatisfied to being satisfied, or when none of
+// the observed signals can ever be satisfied again.
+typedef uint32_t MojoTriggerCondition;
+
+#ifdef __cplusplus
+const MojoTriggerCondition MOJO_TRIGGER_CONDITION_SIGNALS_UNSATISFIED = 0;
+const MojoTriggerCondition MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED = 1;
+#else
+#define MOJO_TRIGGER_CONDITION_SIGNALS_UNSATISFIED ((MojoTriggerCondition)0)
+#define MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED ((MojoTriggerCondition)1)
+#endif
+
+// Flags passed to |MojoCreateTrap()| via |MojoCreateTrapOptions|.
+typedef uint32_t MojoCreateTrapFlags;
+
+#ifdef __cplusplus
+const MojoCreateTrapFlags MOJO_CREATE_TRAP_FLAG_NONE = 0;
+#else
+#define MOJO_CREATE_TRAP_FLAG_NONE ((MojoCreateTrapFlags)0)
+#endif
+
+// Options passed to |MojoCreateTrap()|.
+struct MOJO_ALIGNAS(8) MojoCreateTrapOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // Flags. Currently unused.
+ MojoCreateTrapFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoCreateTrapOptions) == 8,
+ "MojoCreateTrapOptions has wrong size.");
+
+// Flags passed to |MojoAddTrigger()| via |MojoAddTriggerOptions|.
+typedef uint32_t MojoAddTriggerFlags;
+
+#ifdef __cplusplus
+const MojoAddTriggerFlags MOJO_ADD_TRIGGER_FLAG_NONE = 0;
+#else
+#define MOJO_ADD_TRIGGER_FLAG_NONE ((MojoAddTriggerFlags)0)
+#endif
+
+// Options passed to |MojoAddTrigger()|.
+struct MOJO_ALIGNAS(8) MojoAddTriggerOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // Flags. Currently unused.
+ MojoAddTriggerFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoAddTriggerOptions) == 8,
+ "MojoAddTriggerOptions has wrong size.");
+
+// Flags passed to |MojoRemoveTrigger()| via |MojoRemoveTriggerOptions|.
+typedef uint32_t MojoRemoveTriggerFlags;
+
+#ifdef __cplusplus
+const MojoRemoveTriggerFlags MOJO_REMOVE_TRIGGER_FLAG_NONE = 0;
+#else
+#define MOJO_REMOVE_TRIGGER_FLAG_NONE ((MojoRemoveTriggerFlags)0)
+#endif
+
+// Options passed to |MojoRemoveTrigger()|.
+struct MOJO_ALIGNAS(8) MojoRemoveTriggerOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // Flags. Currently unused.
+ MojoRemoveTriggerFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoRemoveTriggerOptions) == 8,
+ "MojoRemoveTriggerOptions has wrong size.");
+
+// Flags passed to |MojoArmTrap()| via |MojoArmTrapOptions|.
+typedef uint32_t MojoArmTrapFlags;
+
+#ifdef __cplusplus
+const MojoArmTrapFlags MOJO_ARM_TRAP_FLAG_NONE = 0;
+#else
+#define MOJO_ARM_TRAP_FLAG_NONE ((MojoArmTrapFlags)0)
+#endif
+
+// Options passed to |MojoArmTrap()|.
+struct MOJO_ALIGNAS(8) MojoArmTrapOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // Flags. Currently unused.
+ MojoArmTrapFlags flags;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoArmTrapOptions) == 8,
+ "MojoArmTrapOptions has wrong size.");
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// A user-provided callback to handle trap events. Passed to |MojoCreateTrap()|.
+typedef void (*MojoTrapEventHandler)(const struct MojoTrapEvent* event);
+
+// Creates a new trap which can be used to detect signal changes on a handle.
+// Traps execute arbitrary user code when tripped.
+//
+// Traps can trip only while armed**, and new traps are created in a disarmed
+// state. Traps may be armed using |MojoArmTrap()|.
+//
+// Arming a trap is only possible when the trap has one or more triggers
+// attached to it. Triggers can be added or removed using |MojoAddTrigger()| and
+// |MojoRemoveTrigger()|.
+//
+// If a trap is tripped by any of its triggers, it is disarmed immediately and
+// the traps |MojoTrapEventHandler| is invoked once for every relevant trigger.
+//
+// |options| may be null.
+//
+// ** An unarmed trap will still fire an event when a trigger is removed. This
+// event will always convey the result |MOJO_RESULT_CANCELLED|.
+//
+// Parameters:
+// |handler|: The |MojoTrapEventHandler| to invoke any time this trap is
+// tripped. Note that this may be called from any arbitrary thread.
+// |trap_handle|: The address at which to store the MojoHandle corresponding
+// to the new trap if successfully created.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the trap has been successfully created.
+// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a handle could not be allocated for
+// this trap.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoCreateTrap(MojoTrapEventHandler handler,
+ const struct MojoCreateTrapOptions* options,
+ MojoHandle* trap_handle);
+
+// Adds a trigger to a trap. This configures the trap to invoke its event
+// handler if the specified conditions are met (or can no longer be met) while
+// the trap is armed.
+//
+// Note that event handler invocations for a given trigger are mutually
+// exclusive in execution: the handler will never be entered for a trigger while
+// another thread is executing it for the same trigger. Similarly, event
+// handlers are never re-entered. If an event handler changes the state of the
+// system such that another event would fire, that event is deferred until the
+// first handler returns.
+//
+// Parameters:
+// |trap_handle|: The trap to which this trigger is to be added.
+// |handle|: The handle whose signals this trigger will observe. Must be a
+// message pipe or data pipe handle.
+// |signals|: The specific signal(s) this trigger will observe on |handle|.
+// |condition|: The signaling condition this trigger will observe. i.e.
+// whether to trip the trap when |signals| become satisfied or when they
+// become unsatisfied.
+// |context|: An arbitrary context value to be passed to the trap's event
+// handler when this trigger was responsible for tripping the trap. See
+// the |trigger_context| field in |MojoTrapEvent|. This value must be
+// unique among all triggers on the trap.
+//
+// |options| may be null.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the handle is now being observed by the trigger.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |trap_handle| is not a trap handle,
+// |handle| is not a valid message pipe or data pipe handle, or |signals|
+// or |condition| are an invalid value.
+// |MOJO_RESULT_ALREADY_EXISTS| if the trap already has a trigger associated
+// with |context| or |handle|.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoAddTrigger(MojoHandle trap_handle,
+ MojoHandle handle,
+ MojoHandleSignals signals,
+ MojoTriggerCondition condition,
+ uintptr_t context,
+ const struct MojoAddTriggerOptions* options);
+
+// Removes a trigger from a trap.
+//
+// This ensures that the trigger is removed as soon as possible. Removal may
+// block an arbitrarily long time if the trap is already executing its handler.
+//
+// When removal is complete, the trap's handler is invoked one final time for
+// time for |context|, with the result |MOJO_RESULT_CANCELLED|.
+//
+// The same behavior can be elicted by either closing the watched handle
+// associated with this trigger, or by closing |trap_handle| itself.
+//
+// Parameters:
+// |trap_handle|: The handle of the trap from which to remove a trigger.
+// |context|: The context of the trigger to be removed.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the trigger has been removed.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |trap_handle| is not a trap handle.
+// |MOJO_RESULT_NOT_FOUND| if there is no trigger registered on this trap for
+// the given value of |context|.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoRemoveTrigger(MojoHandle trap_handle,
+ uintptr_t context,
+ const struct MojoRemoveTriggerOptions* options);
+
+// Arms a trap, allowing it to invoke its event handler the next time any of its
+// triggers' conditions are met.
+//
+// Parameters:
+// |trap_handle|: The handle of the trap to be armed.
+// |num_blocking_events|: An address pointing to the number of elements
+// available for storage at the address given by |blocking_events|.
+// Optional and only used when |MOJO_RESULT_FAILED_PRECONDITION| is
+// returned. See below.
+// |blocking_events|: An output buffer for |MojoTrapEvent| structures to be
+// filled in if one or more triggers would have tripped the trap
+// immediately if it were armed. Optional and used only when
+// |MOJO_RESULT_FAILED_PRECONDITION| is returned. See below.
+//
+// Returns:
+// |MOJO_RESULT_OK| if the trap has been successfully armed.
+// |num_blocking_events| and |blocking_events| are ignored.
+// |MOJO_RESULT_NOT_FOUND| if the trap does not have any triggers.
+// |num_blocking_events| and |blocking_events| are ignored.
+// |MOJO_RESULT_INVALID_ARGUMENT| if |trap_handle| is not a valid trap handle,
+// or if |num_blocking_events| is non-null but |blocking_events| is
+// not.
+// |MOJO_RESULT_FAILED_PRECONDITION| if one or more triggers would have
+// tripped the trap immediately upon arming. If |num_blocking_events| is
+// non-null, this assumes there is enough space for |*num_blocking_events|
+// entries at the non-null address in |blocking_events|.
+//
+// At most |*num_blocking_events| entries are populated there, with each
+// entry corresponding to one of the triggers which would have tripped the
+// trap. The actual number of entries populated is written to
+// |*num_blocking_events| before returning.
+//
+// If there are more ready triggers than available provided storage, the
+// subset presented to the caller is arbitrary. The runtime makes an
+// effort to circulate triggers returned by consecutive failed
+// |MojoArmTrap()| calls so that callers may avoid handle starvation when
+// observing a large number of active handles with a single trap.
+MOJO_SYSTEM_EXPORT MojoResult
+MojoArmTrap(MojoHandle trap_handle,
+ const struct MojoArmTrapOptions* options,
+ uint32_t* num_blocking_events,
+ struct MojoTrapEvent* blocking_events);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // MOJO_PUBLIC_C_SYSTEM_TRAP_H_
diff --git a/mojo/public/c/system/types.h b/mojo/public/c/system/types.h
index 15813b6b73..4cf566a569 100644
--- a/mojo/public/c/system/types.h
+++ b/mojo/public/c/system/types.h
@@ -77,7 +77,7 @@ const MojoHandle MOJO_HANDLE_INVALID = 0;
// 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.
+// wait for it to be feasible using a trap.
//
// The codes from |MOJO_RESULT_OK| to |MOJO_RESULT_DATA_LOSS| come from
// Google3's canonical error codes.
@@ -133,9 +133,33 @@ typedef uint64_t MojoDeadline;
#ifdef __cplusplus
const MojoDeadline MOJO_DEADLINE_INDEFINITE = static_cast<MojoDeadline>(-1);
#else
-#define MOJO_DEADLINE_INDEFINITE ((MojoDeadline) - 1)
+#define MOJO_DEADLINE_INDEFINITE ((MojoDeadline)-1)
#endif
+// Flags passed to |MojoInitialize()| via |MojoInitializeOptions|.
+typedef uint32_t MojoInitializeFlags;
+
+// No flags.
+#define MOJO_INITIALIZE_FLAG_NONE ((MojoInitializeFlags)0)
+
+// Options passed to |MojoInitialize()|.
+struct MOJO_ALIGNAS(8) MojoInitializeOptions {
+ // The size of this structure, used for versioning.
+ uint32_t struct_size;
+
+ // See |MojoInitializeFlags|.
+ MojoInitializeFlags flags;
+
+ // Address and length of the UTF8-encoded path of a mojo_core shared library
+ // to load. If the |mojo_core_path| is null then |mojo_core_path_length| is
+ // ignored and Mojo will fall back first onto the |MOJO_CORE_LIBRARY_PATH|
+ // environment variable, and then onto the current working directory.
+ MOJO_POINTER_FIELD(const char*, mojo_core_path);
+ uint32_t mojo_core_path_length;
+};
+MOJO_STATIC_ASSERT(sizeof(MojoInitializeOptions) == 24,
+ "MojoInitializeOptions has wrong size");
+
// |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.
@@ -149,6 +173,12 @@ const MojoDeadline MOJO_DEADLINE_INDEFINITE = static_cast<MojoDeadline>(-1);
// AND there is some nonzero quantity of new data available on the pipe
// since the last |MojoReadData()| or |MojoBeginReadData()| call on the
// handle.
+// |MOJO_HANDLE_SIGNAL_PEER_REMOTE| - The peer handle exists in a remote
+// execution context (e.g. in another process.) Note that this signal is
+// maintained with best effort but may at any time be slightly out of sync
+// with the actual location of the peer handle.
+// |MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED| - One or more quotas set on the handle
+// is currently exceeded.
typedef uint32_t MojoHandleSignals;
@@ -158,12 +188,16 @@ 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;
+const MojoHandleSignals MOJO_HANDLE_SIGNAL_PEER_REMOTE = 1 << 4;
+const MojoHandleSignals MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED = 1 << 5;
#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);
+#define MOJO_HANDLE_SIGNAL_PEER_REMOTE ((MojoHandleSignals)1 << 4);
+#define MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED ((MojoHandleSignals)1 << 5);
#endif
// |MojoHandleSignalsState|: Returned by watch notification callbacks and
@@ -185,39 +219,9 @@ struct MOJO_ALIGNAS(4) MojoHandleSignalsState {
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
+// TODO(https://crbug.com/819046): Remove these aliases.
+#define MOJO_WATCH_CONDITION_SATISFIED MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED
+#define MOJO_WATCH_CONDITION_NOT_SATISFIED \
+ MOJO_TRIGGER_CONDITION_SIGNALS_UNSATISFIED
#endif // MOJO_PUBLIC_C_SYSTEM_TYPES_H_
diff --git a/mojo/public/c/system/watcher.h b/mojo/public/c/system/watcher.h
deleted file mode 100644
index e32856b6d9..0000000000
--- a/mojo/public/c/system/watcher.h
+++ /dev/null
@@ -1,184 +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 MOJO_PUBLIC_C_SYSTEM_WATCHER_H_
-#define MOJO_PUBLIC_C_SYSTEM_WATCHER_H_
-
-#include <stdint.h>
-
-#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/cpp/base/BUILD.gn b/mojo/public/cpp/base/BUILD.gn
new file mode 100644
index 0000000000..c57ac0bf1d
--- /dev/null
+++ b/mojo/public/cpp/base/BUILD.gn
@@ -0,0 +1,85 @@
+# 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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+component("base") {
+ output_name = "mojo_base_lib"
+
+ sources = [
+ "big_buffer.cc",
+ "big_buffer.h",
+ ]
+
+ defines = [ "IS_MOJO_BASE_IMPL" ]
+
+ public_deps = [
+ "//base",
+ "//mojo/public/cpp/bindings",
+ "//mojo/public/cpp/system",
+ ]
+}
+
+# Normally typemap traits sources should be build directly into mojom targets
+# via the typemap file. This target is for typemapped mojo_base types whose
+# traits are shared between chromium and blink variants.
+component("shared_typemap_traits") {
+ output_name = "mojo_base_shared_typemap_traits"
+
+ sources = [
+ "big_buffer_mojom_traits.cc",
+ "big_buffer_mojom_traits.h",
+ "file_info_mojom_traits.cc",
+ "file_info_mojom_traits.h",
+ "file_path_mojom_traits.cc",
+ "file_path_mojom_traits.h",
+ "shared_memory_mojom_traits.cc",
+ "shared_memory_mojom_traits.h",
+ "time_mojom_traits.cc",
+ "time_mojom_traits.h",
+ "values_mojom_traits.cc",
+ "values_mojom_traits.h",
+ ]
+
+ defines = [ "IS_MOJO_BASE_SHARED_TRAITS_IMPL" ]
+
+ public_deps = [
+ ":base",
+ "//base:i18n",
+ "//mojo/public/mojom/base:base_shared",
+ ]
+}
+
+source_set("tests") {
+ testonly = true
+
+ sources = [
+ "big_buffer_unittest.cc",
+ "big_string_unittest.cc",
+ "file_path_unittest.cc",
+ "file_unittest.cc",
+ "memory_allocator_dump_cross_process_uid_unittest.cc",
+ "process_id_unittest.cc",
+ "read_only_buffer_unittest.cc",
+ "ref_counted_memory_unittest.cc",
+ "shared_memory_unittest.cc",
+ "string16_unittest.cc",
+ "text_direction_unittest.cc",
+ "thread_priority_unittest.cc",
+ "time_unittest.cc",
+ "unguessable_token_unittest.cc",
+ "values_unittest.cc",
+ ]
+
+ public_deps = [
+ ":base",
+ ":shared_typemap_traits",
+ "//base",
+ "//base/test:test_support",
+ "//mojo/public/cpp/test_support:test_utils",
+ "//mojo/public/mojom/base",
+ "//mojo/public/mojom/base:read_only_buffer",
+ "//testing/gtest",
+ ]
+}
diff --git a/mojo/public/cpp/base/README.md b/mojo/public/cpp/base/README.md
new file mode 100644
index 0000000000..0f6198dfe2
--- /dev/null
+++ b/mojo/public/cpp/base/README.md
@@ -0,0 +1,5 @@
+# Mojo Public Base Library
+
+This is a library of common helper types for use in conjunction with Mojo
+bindings and //base types. These things do not quite fit under the System or
+Bindings libraries.
diff --git a/mojo/public/cpp/base/big_buffer.cc b/mojo/public/cpp/base/big_buffer.cc
new file mode 100644
index 0000000000..5f18359d2b
--- /dev/null
+++ b/mojo/public/cpp/base/big_buffer.cc
@@ -0,0 +1,155 @@
+// 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.
+
+#include "mojo/public/cpp/base/big_buffer.h"
+
+#include "base/logging.h"
+
+namespace mojo_base {
+
+namespace internal {
+
+BigBufferSharedMemoryRegion::BigBufferSharedMemoryRegion() = default;
+
+BigBufferSharedMemoryRegion::BigBufferSharedMemoryRegion(
+ mojo::ScopedSharedBufferHandle buffer_handle,
+ size_t size)
+ : size_(size),
+ buffer_handle_(std::move(buffer_handle)),
+ buffer_mapping_(buffer_handle_->Map(size)) {}
+
+BigBufferSharedMemoryRegion::BigBufferSharedMemoryRegion(
+ BigBufferSharedMemoryRegion&& other) = default;
+
+BigBufferSharedMemoryRegion::~BigBufferSharedMemoryRegion() = default;
+
+BigBufferSharedMemoryRegion& BigBufferSharedMemoryRegion::operator=(
+ BigBufferSharedMemoryRegion&& other) = default;
+
+mojo::ScopedSharedBufferHandle BigBufferSharedMemoryRegion::TakeBufferHandle() {
+ DCHECK(buffer_handle_.is_valid());
+ buffer_mapping_.reset();
+ return std::move(buffer_handle_);
+}
+
+} // namespace internal
+
+// static
+constexpr size_t BigBuffer::kMaxInlineBytes;
+
+BigBuffer::BigBuffer() : storage_type_(StorageType::kBytes) {}
+
+BigBuffer::BigBuffer(BigBuffer&& other) = default;
+
+BigBuffer::BigBuffer(base::span<const uint8_t> data) {
+ *this = BigBufferView::ToBigBuffer(BigBufferView(data));
+}
+
+BigBuffer::BigBuffer(const std::vector<uint8_t>& data)
+ : BigBuffer(base::make_span(data)) {}
+
+BigBuffer::BigBuffer(internal::BigBufferSharedMemoryRegion shared_memory)
+ : storage_type_(StorageType::kSharedMemory),
+ shared_memory_(std::move(shared_memory)) {}
+
+BigBuffer::~BigBuffer() = default;
+
+BigBuffer& BigBuffer::operator=(BigBuffer&& other) = default;
+
+uint8_t* BigBuffer::data() {
+ return const_cast<uint8_t*>(const_cast<const BigBuffer*>(this)->data());
+}
+
+const uint8_t* BigBuffer::data() const {
+ switch (storage_type_) {
+ case StorageType::kBytes:
+ return bytes_.data();
+ case StorageType::kSharedMemory:
+ DCHECK(shared_memory_->buffer_mapping_);
+ return static_cast<const uint8_t*>(
+ const_cast<const void*>(shared_memory_->buffer_mapping_.get()));
+ default:
+ NOTREACHED();
+ return nullptr;
+ }
+}
+
+size_t BigBuffer::size() const {
+ switch (storage_type_) {
+ case StorageType::kBytes:
+ return bytes_.size();
+ case StorageType::kSharedMemory:
+ return shared_memory_->size();
+ default:
+ NOTREACHED();
+ return 0;
+ }
+}
+
+BigBufferView::BigBufferView() = default;
+
+BigBufferView::BigBufferView(BigBufferView&& other) = default;
+
+BigBufferView::BigBufferView(base::span<const uint8_t> bytes) {
+ if (bytes.size() > BigBuffer::kMaxInlineBytes) {
+ auto buffer = mojo::SharedBufferHandle::Create(bytes.size());
+ if (buffer.is_valid()) {
+ storage_type_ = BigBuffer::StorageType::kSharedMemory;
+ shared_memory_.emplace(std::move(buffer), bytes.size());
+ std::copy(bytes.begin(), bytes.end(),
+ static_cast<uint8_t*>(shared_memory_->buffer_mapping_.get()));
+ return;
+ }
+ }
+
+ // Either the data is small enough or shared memory allocation failed. Either
+ // way we fall back to directly referencing the input bytes.
+ storage_type_ = BigBuffer::StorageType::kBytes;
+ bytes_ = bytes;
+}
+
+BigBufferView::~BigBufferView() = default;
+
+BigBufferView& BigBufferView::operator=(BigBufferView&& other) = default;
+
+void BigBufferView::SetBytes(base::span<const uint8_t> bytes) {
+ DCHECK(bytes_.empty());
+ DCHECK(!shared_memory_);
+ storage_type_ = BigBuffer::StorageType::kBytes;
+ bytes_ = bytes;
+}
+
+void BigBufferView::SetSharedMemory(
+ internal::BigBufferSharedMemoryRegion shared_memory) {
+ DCHECK(bytes_.empty());
+ DCHECK(!shared_memory_);
+ storage_type_ = BigBuffer::StorageType::kSharedMemory;
+ shared_memory_ = std::move(shared_memory);
+}
+
+base::span<const uint8_t> BigBufferView::data() const {
+ if (storage_type_ == BigBuffer::StorageType::kBytes) {
+ return bytes_;
+ } else {
+ DCHECK(shared_memory_.has_value());
+ return base::make_span(static_cast<const uint8_t*>(const_cast<const void*>(
+ shared_memory_->memory())),
+ shared_memory_->size());
+ }
+}
+
+// static
+BigBuffer BigBufferView::ToBigBuffer(BigBufferView view) {
+ BigBuffer buffer;
+ buffer.storage_type_ = view.storage_type_;
+ if (view.storage_type_ == BigBuffer::StorageType::kBytes) {
+ std::copy(view.bytes_.begin(), view.bytes_.end(),
+ std::back_inserter(buffer.bytes_));
+ } else {
+ buffer.shared_memory_ = std::move(*view.shared_memory_);
+ }
+ return buffer;
+}
+
+} // namespace mojo_base
diff --git a/mojo/public/cpp/base/big_buffer.h b/mojo/public/cpp/base/big_buffer.h
new file mode 100644
index 0000000000..eca2ba7e92
--- /dev/null
+++ b/mojo/public/cpp/base/big_buffer.h
@@ -0,0 +1,179 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BASE_BIG_BUFFER_H_
+#define MOJO_PUBLIC_CPP_BASE_BIG_BUFFER_H_
+
+#include <cstdint>
+#include <vector>
+
+#include "base/component_export.h"
+#include "base/containers/span.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "mojo/public/cpp/system/buffer.h"
+
+namespace mojo_base {
+
+class BigBuffer;
+class BigBufferView;
+
+namespace internal {
+
+// Internal helper used by BigBuffer when backed by shared memory.
+class COMPONENT_EXPORT(MOJO_BASE) BigBufferSharedMemoryRegion {
+ public:
+ BigBufferSharedMemoryRegion();
+ BigBufferSharedMemoryRegion(mojo::ScopedSharedBufferHandle buffer_handle,
+ size_t size);
+ BigBufferSharedMemoryRegion(BigBufferSharedMemoryRegion&& other);
+ ~BigBufferSharedMemoryRegion();
+
+ BigBufferSharedMemoryRegion& operator=(BigBufferSharedMemoryRegion&& other);
+
+ void* memory() const { return buffer_mapping_.get(); }
+
+ size_t size() const { return size_; }
+ mojo::ScopedSharedBufferHandle TakeBufferHandle();
+
+ private:
+ friend class mojo_base::BigBuffer;
+ friend class mojo_base::BigBufferView;
+
+ size_t size_;
+ mojo::ScopedSharedBufferHandle buffer_handle_;
+ mojo::ScopedSharedBufferMapping buffer_mapping_;
+
+ DISALLOW_COPY_AND_ASSIGN(BigBufferSharedMemoryRegion);
+};
+
+} // namespace internal
+
+// BigBuffer represents a potentially large sequence of bytes. When passed over
+// mojom (as a mojo_base::mojom::BigBuffer union), it may serialize as either an
+// inlined array of bytes or as a shared buffer handle with the payload copied
+// into the corresponding shared memory region. This makes it easier to send
+// payloads of varying and unbounded size over IPC without fear of hitting
+// message size limits.
+//
+// A BigBuffer may be (implicitly) constructed over any span of bytes, and it
+// exposes simple |data()| and |size()| accessors akin to what common container
+// types provide. Users do not need to be concerned with the actual backing
+// storage used to implement this interface.
+class COMPONENT_EXPORT(MOJO_BASE) BigBuffer {
+ public:
+ static constexpr size_t kMaxInlineBytes = 64 * 1024;
+
+ enum class StorageType {
+ kBytes,
+ kSharedMemory,
+ };
+
+ // Defaults to empty vector storage.
+ BigBuffer();
+ BigBuffer(BigBuffer&& other);
+
+ // Constructs a BigBuffer over an existing span of bytes. Intentionally
+ // implicit for convenience. Always copies the contents of |data| into some
+ // internal storage.
+ BigBuffer(base::span<const uint8_t> data);
+
+ // Helper for implicit conversion from byte vectors.
+ BigBuffer(const std::vector<uint8_t>& data);
+
+ // Constructs a BigBuffer from an existing shared memory region. Not intended
+ // for general-purpose use.
+ explicit BigBuffer(internal::BigBufferSharedMemoryRegion shared_memory);
+
+ ~BigBuffer();
+
+ BigBuffer& operator=(BigBuffer&& other);
+
+ // Returns a pointer to the data stored by this BigBuffer, regardless of
+ // backing storage type.
+ uint8_t* data();
+ const uint8_t* data() const;
+
+ // Returns the size of the data stored by this BigBuffer, regardless of
+ // backing storage type.
+ size_t size() const;
+
+ StorageType storage_type() const { return storage_type_; }
+
+ const std::vector<uint8_t>& bytes() const {
+ DCHECK_EQ(storage_type_, StorageType::kBytes);
+ return bytes_;
+ }
+
+ internal::BigBufferSharedMemoryRegion& shared_memory() {
+ DCHECK_EQ(storage_type_, StorageType::kSharedMemory);
+ return shared_memory_.value();
+ }
+
+ private:
+ friend class BigBufferView;
+
+ StorageType storage_type_;
+ std::vector<uint8_t> bytes_;
+ base::Optional<internal::BigBufferSharedMemoryRegion> shared_memory_;
+
+ DISALLOW_COPY_AND_ASSIGN(BigBuffer);
+};
+
+// Similar to BigBuffer, but doesn't *necessarily* own the buffer storage.
+// Namely, if constructed over a small enough span of memory, it will simply
+// retain a reference to that memory. This is generally only safe to use for
+// serialization and deserialization.
+class COMPONENT_EXPORT(MOJO_BASE) BigBufferView {
+ public:
+ BigBufferView();
+ BigBufferView(BigBufferView&& other);
+
+ // Constructs a BigBufferView over |bytes|. If |bytes| is large enough, this
+ // will allocate shared memory and copy the contents there. Otherwise this
+ // will retain an unsafe reference to |bytes| and must therefore not outlive
+ // |bytes|.
+ explicit BigBufferView(base::span<const uint8_t> bytes);
+ ~BigBufferView();
+
+ BigBufferView& operator=(BigBufferView&& other);
+
+ base::span<const uint8_t> data() const;
+
+ // Explicitly retains a reference to |bytes| as the backing storage for this
+ // view. Does not copy and therefore |bytes| must remain valid throughout the
+ // view's lifetime. Used for deserialization.
+ void SetBytes(base::span<const uint8_t> bytes);
+
+ // Explictly adopts |shared_memory| as the backing storage for this view. Used
+ // for deserialization.
+ void SetSharedMemory(internal::BigBufferSharedMemoryRegion shared_memory);
+
+ // Converts to a BigBuffer which owns the viewed data. May have to copy data.
+ static BigBuffer ToBigBuffer(BigBufferView view) WARN_UNUSED_RESULT;
+
+ BigBuffer::StorageType storage_type() const { return storage_type_; }
+
+ base::span<const uint8_t> bytes() const {
+ DCHECK_EQ(storage_type_, BigBuffer::StorageType::kBytes);
+ return bytes_;
+ }
+
+ internal::BigBufferSharedMemoryRegion& shared_memory() {
+ DCHECK_EQ(storage_type_, BigBuffer::StorageType::kSharedMemory);
+ return shared_memory_.value();
+ }
+
+ private:
+ BigBuffer::StorageType storage_type_;
+ base::span<const uint8_t> bytes_;
+ base::Optional<internal::BigBufferSharedMemoryRegion> shared_memory_;
+
+ DISALLOW_COPY_AND_ASSIGN(BigBufferView);
+};
+
+} // namespace mojo_base
+
+#endif // MOJO_PUBLIC_CPP_BASE_BIG_BUFFER_H_
diff --git a/mojo/public/cpp/base/big_buffer.typemap b/mojo/public/cpp/base/big_buffer.typemap
new file mode 100644
index 0000000000..32726d5a2c
--- /dev/null
+++ b/mojo/public/cpp/base/big_buffer.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/public/mojom/base/big_buffer.mojom"
+public_headers = [ "//mojo/public/cpp/base/big_buffer.h" ]
+traits_headers = [ "//mojo/public/cpp/base/big_buffer_mojom_traits.h" ]
+public_deps = [
+ "//mojo/public/cpp/base",
+ "//mojo/public/cpp/base:shared_typemap_traits",
+]
+type_mappings = [ "mojo_base.mojom.BigBuffer=mojo_base::BigBuffer[move_only]" ]
diff --git a/mojo/public/cpp/base/big_buffer_mojom_traits.cc b/mojo/public/cpp/base/big_buffer_mojom_traits.cc
new file mode 100644
index 0000000000..1e1b8571ae
--- /dev/null
+++ b/mojo/public/cpp/base/big_buffer_mojom_traits.cc
@@ -0,0 +1,146 @@
+// 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.
+
+#include "mojo/public/cpp/base/big_buffer_mojom_traits.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "mojo/public/cpp/bindings/array_data_view.h"
+
+namespace mojo {
+
+// static
+uint32_t StructTraits<mojo_base::mojom::BigBufferSharedMemoryRegionDataView,
+ mojo_base::internal::BigBufferSharedMemoryRegion>::
+ size(const mojo_base::internal::BigBufferSharedMemoryRegion& region) {
+ return base::checked_cast<uint32_t>(region.size());
+}
+
+// static
+ScopedSharedBufferHandle
+StructTraits<mojo_base::mojom::BigBufferSharedMemoryRegionDataView,
+ mojo_base::internal::BigBufferSharedMemoryRegion>::
+ buffer_handle(mojo_base::internal::BigBufferSharedMemoryRegion& region) {
+ return region.TakeBufferHandle();
+}
+
+// static
+bool StructTraits<mojo_base::mojom::BigBufferSharedMemoryRegionDataView,
+ mojo_base::internal::BigBufferSharedMemoryRegion>::
+ Read(mojo_base::mojom::BigBufferSharedMemoryRegionDataView data,
+ mojo_base::internal::BigBufferSharedMemoryRegion* out) {
+ *out = mojo_base::internal::BigBufferSharedMemoryRegion(
+ data.TakeBufferHandle(), data.size());
+ return out->memory() != nullptr;
+}
+
+// static
+mojo_base::mojom::BigBufferDataView::Tag
+UnionTraits<mojo_base::mojom::BigBufferDataView, mojo_base::BigBuffer>::GetTag(
+ const mojo_base::BigBuffer& buffer) {
+ switch (buffer.storage_type()) {
+ case mojo_base::BigBuffer::StorageType::kBytes:
+ return mojo_base::mojom::BigBufferDataView::Tag::BYTES;
+ case mojo_base::BigBuffer::StorageType::kSharedMemory:
+ return mojo_base::mojom::BigBufferDataView::Tag::SHARED_MEMORY;
+ }
+
+ NOTREACHED();
+ return mojo_base::mojom::BigBufferDataView::Tag::BYTES;
+}
+
+// static
+const std::vector<uint8_t>&
+UnionTraits<mojo_base::mojom::BigBufferDataView, mojo_base::BigBuffer>::bytes(
+ const mojo_base::BigBuffer& buffer) {
+ return buffer.bytes();
+}
+
+// static
+mojo_base::internal::BigBufferSharedMemoryRegion&
+UnionTraits<mojo_base::mojom::BigBufferDataView,
+ mojo_base::BigBuffer>::shared_memory(mojo_base::BigBuffer& buffer) {
+ return buffer.shared_memory();
+}
+
+// static
+bool UnionTraits<mojo_base::mojom::BigBufferDataView, mojo_base::BigBuffer>::
+ Read(mojo_base::mojom::BigBufferDataView data, mojo_base::BigBuffer* out) {
+ switch (data.tag()) {
+ case mojo_base::mojom::BigBufferDataView::Tag::BYTES: {
+ mojo::ArrayDataView<uint8_t> bytes_view;
+ data.GetBytesDataView(&bytes_view);
+ *out = mojo_base::BigBuffer(bytes_view);
+ return true;
+ }
+
+ case mojo_base::mojom::BigBufferDataView::Tag::SHARED_MEMORY: {
+ mojo_base::internal::BigBufferSharedMemoryRegion shared_memory;
+ if (!data.ReadSharedMemory(&shared_memory))
+ return false;
+ *out = mojo_base::BigBuffer(std::move(shared_memory));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// static
+mojo_base::mojom::BigBufferDataView::Tag UnionTraits<
+ mojo_base::mojom::BigBufferDataView,
+ mojo_base::BigBufferView>::GetTag(const mojo_base::BigBufferView& view) {
+ switch (view.storage_type()) {
+ case mojo_base::BigBuffer::StorageType::kBytes:
+ return mojo_base::mojom::BigBufferDataView::Tag::BYTES;
+ case mojo_base::BigBuffer::StorageType::kSharedMemory:
+ return mojo_base::mojom::BigBufferDataView::Tag::SHARED_MEMORY;
+ }
+
+ NOTREACHED();
+ return mojo_base::mojom::BigBufferDataView::Tag::BYTES;
+}
+
+// static
+base::span<const uint8_t> UnionTraits<
+ mojo_base::mojom::BigBufferDataView,
+ mojo_base::BigBufferView>::bytes(const mojo_base::BigBufferView& view) {
+ return view.bytes();
+}
+
+// static
+mojo_base::internal::BigBufferSharedMemoryRegion& UnionTraits<
+ mojo_base::mojom::BigBufferDataView,
+ mojo_base::BigBufferView>::shared_memory(mojo_base::BigBufferView& view) {
+ return view.shared_memory();
+}
+
+// static
+bool UnionTraits<
+ mojo_base::mojom::BigBufferDataView,
+ mojo_base::BigBufferView>::Read(mojo_base::mojom::BigBufferDataView data,
+ mojo_base::BigBufferView* out) {
+ switch (data.tag()) {
+ case mojo_base::mojom::BigBufferDataView::Tag::BYTES: {
+ mojo::ArrayDataView<uint8_t> bytes_view;
+ data.GetBytesDataView(&bytes_view);
+ out->SetBytes(bytes_view);
+ return true;
+ }
+
+ case mojo_base::mojom::BigBufferDataView::Tag::SHARED_MEMORY: {
+ mojo_base::internal::BigBufferSharedMemoryRegion shared_memory;
+ if (!data.ReadSharedMemory(&shared_memory))
+ return false;
+ out->SetSharedMemory(std::move(shared_memory));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/base/big_buffer_mojom_traits.h b/mojo/public/cpp/base/big_buffer_mojom_traits.h
new file mode 100644
index 0000000000..b2ca954992
--- /dev/null
+++ b/mojo/public/cpp/base/big_buffer_mojom_traits.h
@@ -0,0 +1,64 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BASE_BIG_BUFFER_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_BIG_BUFFER_MOJOM_TRAITS_H_
+
+#include <cstdint>
+#include <vector>
+
+#include "base/component_export.h"
+#include "base/containers/span.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/base/big_buffer.h"
+#include "mojo/public/cpp/bindings/union_traits.h"
+#include "mojo/public/cpp/system/buffer.h"
+#include "mojo/public/mojom/base/big_buffer.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_SHARED_TRAITS)
+ StructTraits<mojo_base::mojom::BigBufferSharedMemoryRegionDataView,
+ mojo_base::internal::BigBufferSharedMemoryRegion> {
+ static uint32_t size(
+ const mojo_base::internal::BigBufferSharedMemoryRegion& region);
+ static mojo::ScopedSharedBufferHandle buffer_handle(
+ mojo_base::internal::BigBufferSharedMemoryRegion& region);
+
+ static bool Read(mojo_base::mojom::BigBufferSharedMemoryRegionDataView data,
+ mojo_base::internal::BigBufferSharedMemoryRegion* out);
+};
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_SHARED_TRAITS)
+ UnionTraits<mojo_base::mojom::BigBufferDataView, mojo_base::BigBuffer> {
+ static mojo_base::mojom::BigBufferDataView::Tag GetTag(
+ const mojo_base::BigBuffer& buffer);
+
+ static const std::vector<uint8_t>& bytes(const mojo_base::BigBuffer& buffer);
+ static mojo_base::internal::BigBufferSharedMemoryRegion& shared_memory(
+ mojo_base::BigBuffer& buffer);
+
+ static bool Read(mojo_base::mojom::BigBufferDataView data,
+ mojo_base::BigBuffer* out);
+};
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_SHARED_TRAITS)
+ UnionTraits<mojo_base::mojom::BigBufferDataView, mojo_base::BigBufferView> {
+ static mojo_base::mojom::BigBufferDataView::Tag GetTag(
+ const mojo_base::BigBufferView& view);
+
+ static base::span<const uint8_t> bytes(const mojo_base::BigBufferView& view);
+ static mojo_base::internal::BigBufferSharedMemoryRegion& shared_memory(
+ mojo_base::BigBufferView& view);
+
+ static bool Read(mojo_base::mojom::BigBufferDataView data,
+ mojo_base::BigBufferView* out);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BASE_BIG_BUFFER_MOJOM_TRAITS_H_
diff --git a/mojo/public/cpp/base/big_buffer_unittest.cc b/mojo/public/cpp/base/big_buffer_unittest.cc
new file mode 100644
index 0000000000..9afba4b296
--- /dev/null
+++ b/mojo/public/cpp/base/big_buffer_unittest.cc
@@ -0,0 +1,69 @@
+// 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.
+
+#include <algorithm>
+#include <vector>
+
+#include "base/rand_util.h"
+#include "mojo/public/cpp/base/big_buffer.h"
+#include "mojo/public/cpp/base/big_buffer_mojom_traits.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/mojom/base/big_buffer.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo_base {
+namespace big_buffer_unittest {
+
+namespace {
+
+bool BufferEquals(const BigBuffer& a, const BigBuffer& b) {
+ return a.size() == b.size() && std::equal(a.data(), a.data() + a.size(),
+ b.data(), b.data() + b.size());
+}
+
+} // namespace
+
+TEST(BigBufferTest, EmptyBuffer) {
+ BigBuffer in;
+ BigBuffer out;
+ EXPECT_EQ(BigBuffer::StorageType::kBytes, in.storage_type());
+ EXPECT_EQ(0u, in.size());
+
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::BigBuffer>(&in, &out));
+
+ EXPECT_EQ(BigBuffer::StorageType::kBytes, out.storage_type());
+ EXPECT_TRUE(BufferEquals(in, out));
+}
+
+TEST(BigBufferTest, SmallDataSize) {
+ BigBuffer in(std::vector<uint8_t>{1, 2, 3});
+ EXPECT_EQ(BigBuffer::StorageType::kBytes, in.storage_type());
+
+ BigBuffer out;
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::BigBuffer>(&in, &out));
+
+ EXPECT_EQ(BigBuffer::StorageType::kBytes, out.storage_type());
+ EXPECT_TRUE(BufferEquals(in, out));
+}
+
+TEST(BigBufferTest, LargeDataSize) {
+ constexpr size_t kLargeDataSize = BigBuffer::kMaxInlineBytes * 2;
+ std::vector<uint8_t> data(kLargeDataSize);
+ base::RandBytes(data.data(), kLargeDataSize);
+
+ BigBuffer in(data);
+ EXPECT_EQ(BigBuffer::StorageType::kSharedMemory, in.storage_type());
+
+ BigBuffer out;
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::BigBuffer>(&in, &out));
+
+ EXPECT_EQ(BigBuffer::StorageType::kSharedMemory, out.storage_type());
+
+ // NOTE: It's not safe to compare to |in| here since serialization will have
+ // taken ownership of its internal shared buffer handle.
+ EXPECT_TRUE(BufferEquals(data, out));
+}
+
+} // namespace big_buffer_unittest
+} // namespace mojo_base
diff --git a/mojo/public/cpp/base/big_string.typemap b/mojo/public/cpp/base/big_string.typemap
new file mode 100644
index 0000000000..d67d69b24c
--- /dev/null
+++ b/mojo/public/cpp/base/big_string.typemap
@@ -0,0 +1,17 @@
+# 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.
+
+mojom = "//mojo/public/mojom/base/big_string.mojom"
+public_headers = []
+traits_headers = [ "//mojo/public/cpp/base/big_string_mojom_traits.h" ]
+sources = [
+ "//mojo/public/cpp/base/big_string_mojom_traits.cc",
+ "//mojo/public/cpp/base/big_string_mojom_traits.h",
+]
+public_deps = [
+ "//mojo/public/cpp/base",
+ "//mojo/public/cpp/base:shared_typemap_traits",
+]
+
+type_mappings = [ "mojo_base.mojom.BigString=std::string" ]
diff --git a/mojo/public/cpp/base/big_string_mojom_traits.cc b/mojo/public/cpp/base/big_string_mojom_traits.cc
new file mode 100644
index 0000000000..4454253830
--- /dev/null
+++ b/mojo/public/cpp/base/big_string_mojom_traits.cc
@@ -0,0 +1,33 @@
+// 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.
+
+#include "mojo/public/cpp/base/big_string_mojom_traits.h"
+
+#include "mojo/public/cpp/base/big_buffer_mojom_traits.h"
+
+namespace mojo {
+
+// static
+mojo_base::BigBuffer StructTraits<mojo_base::mojom::BigStringDataView,
+ std::string>::data(const std::string& str) {
+ const auto* bytes = reinterpret_cast<const uint8_t*>(str.data());
+ return mojo_base::BigBuffer(
+ base::make_span(bytes, str.size() * sizeof(char)));
+}
+
+// static
+bool StructTraits<mojo_base::mojom::BigStringDataView, std::string>::Read(
+ mojo_base::mojom::BigStringDataView data,
+ std::string* out) {
+ mojo_base::BigBuffer buffer;
+ if (!data.ReadData(&buffer))
+ return false;
+ if (buffer.size() % sizeof(char))
+ return false;
+ *out = std::string(reinterpret_cast<const char*>(buffer.data()),
+ buffer.size() / sizeof(char));
+ return true;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/base/big_string_mojom_traits.h b/mojo/public/cpp/base/big_string_mojom_traits.h
new file mode 100644
index 0000000000..8f490e25db
--- /dev/null
+++ b/mojo/public/cpp/base/big_string_mojom_traits.h
@@ -0,0 +1,27 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BASE_BIG_STRING_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_BIG_STRING_MOJOM_TRAITS_H_
+
+#include <string>
+
+#include "base/component_export.h"
+#include "mojo/public/cpp/base/big_buffer.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "mojo/public/mojom/base/big_string.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_MOJOM)
+ StructTraits<mojo_base::mojom::BigStringDataView, std::string> {
+ static mojo_base::BigBuffer data(const std::string& str);
+
+ static bool Read(mojo_base::mojom::BigStringDataView data, std::string* out);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BASE_BIG_STRING_MOJOM_TRAITS_H_
diff --git a/mojo/public/cpp/base/big_string_unittest.cc b/mojo/public/cpp/base/big_string_unittest.cc
new file mode 100644
index 0000000000..31193be560
--- /dev/null
+++ b/mojo/public/cpp/base/big_string_unittest.cc
@@ -0,0 +1,43 @@
+// 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.
+
+#include <string>
+
+#include "base/rand_util.h"
+#include "mojo/public/cpp/base/big_buffer_mojom_traits.h"
+#include "mojo/public/cpp/base/big_string_mojom_traits.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/mojom/base/big_string.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo_base {
+namespace big_string_unittest {
+
+TEST(BigStringTest, Empty) {
+ std::string in;
+ std::string out;
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::BigString>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+TEST(BigStringTest, Short) {
+ std::string in("hello world");
+ std::string out;
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::BigString>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+TEST(BigStringTest, Long) {
+ constexpr size_t kLargeStringSize = 1024 * 1024;
+
+ std::string in(kLargeStringSize, 0);
+ base::RandBytes(&in[0], kLargeStringSize);
+
+ std::string out;
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::BigString>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+} // namespace big_string_unittest
+} // namespace mojo_base
diff --git a/mojo/public/cpp/base/file.typemap b/mojo/public/cpp/base/file.typemap
new file mode 100644
index 0000000000..85ef6eb2e7
--- /dev/null
+++ b/mojo/public/cpp/base/file.typemap
@@ -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.
+
+mojom = "//mojo/public/mojom/base/file.mojom"
+public_headers = [ "//base/files/file.h" ]
+traits_headers = [ "//mojo/public/cpp/base/file_mojom_traits.h" ]
+sources = [
+ "//mojo/public/cpp/base/file_mojom_traits.cc",
+ "//mojo/public/cpp/base/file_mojom_traits.h",
+]
+
+type_mappings =
+ [ "mojo_base.mojom.File=base::File[move_only,nullable_is_same_type]" ]
diff --git a/mojo/public/cpp/base/file_error.typemap b/mojo/public/cpp/base/file_error.typemap
new file mode 100644
index 0000000000..738a9a7c48
--- /dev/null
+++ b/mojo/public/cpp/base/file_error.typemap
@@ -0,0 +1,12 @@
+# 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.
+
+mojom = "//mojo/public/mojom/base/file_error.mojom"
+public_headers = [ "//base/files/file.h" ]
+traits_headers = [ "//mojo/public/cpp/base/file_error_mojom_traits.h" ]
+public_deps = [
+ "//base",
+ "//mojo/public/cpp/bindings",
+]
+type_mappings = [ "mojo_base.mojom.FileError=::base::File::Error" ]
diff --git a/mojo/public/cpp/base/file_error_mojom_traits.h b/mojo/public/cpp/base/file_error_mojom_traits.h
new file mode 100644
index 0000000000..941608e31f
--- /dev/null
+++ b/mojo/public/cpp/base/file_error_mojom_traits.h
@@ -0,0 +1,120 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BASE_FILE_ERROR_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_FILE_ERROR_MOJOM_TRAITS_H_
+
+#include "base/files/file.h"
+#include "mojo/public/mojom/base/file_error.mojom.h"
+
+namespace mojo {
+
+template <>
+struct EnumTraits<mojo_base::mojom::FileError, base::File::Error> {
+ static mojo_base::mojom::FileError ToMojom(base::File::Error error) {
+ switch (error) {
+ case base::File::FILE_OK:
+ return mojo_base::mojom::FileError::OK;
+ case base::File::FILE_ERROR_FAILED:
+ return mojo_base::mojom::FileError::FAILED;
+ case base::File::FILE_ERROR_IN_USE:
+ return mojo_base::mojom::FileError::IN_USE;
+ case base::File::FILE_ERROR_EXISTS:
+ return mojo_base::mojom::FileError::EXISTS;
+ case base::File::FILE_ERROR_NOT_FOUND:
+ return mojo_base::mojom::FileError::NOT_FOUND;
+ case base::File::FILE_ERROR_ACCESS_DENIED:
+ return mojo_base::mojom::FileError::ACCESS_DENIED;
+ case base::File::FILE_ERROR_TOO_MANY_OPENED:
+ return mojo_base::mojom::FileError::TOO_MANY_OPENED;
+ case base::File::FILE_ERROR_NO_MEMORY:
+ return mojo_base::mojom::FileError::NO_MEMORY;
+ case base::File::FILE_ERROR_NO_SPACE:
+ return mojo_base::mojom::FileError::NO_SPACE;
+ case base::File::FILE_ERROR_NOT_A_DIRECTORY:
+ return mojo_base::mojom::FileError::NOT_A_DIRECTORY;
+ case base::File::FILE_ERROR_INVALID_OPERATION:
+ return mojo_base::mojom::FileError::INVALID_OPERATION;
+ case base::File::FILE_ERROR_SECURITY:
+ return mojo_base::mojom::FileError::SECURITY;
+ case base::File::FILE_ERROR_ABORT:
+ return mojo_base::mojom::FileError::ABORT;
+ case base::File::FILE_ERROR_NOT_A_FILE:
+ return mojo_base::mojom::FileError::NOT_A_FILE;
+ case base::File::FILE_ERROR_NOT_EMPTY:
+ return mojo_base::mojom::FileError::NOT_EMPTY;
+ case base::File::FILE_ERROR_INVALID_URL:
+ return mojo_base::mojom::FileError::INVALID_URL;
+ case base::File::FILE_ERROR_IO:
+ return mojo_base::mojom::FileError::IO;
+ case base::File::FILE_ERROR_MAX:
+ return mojo_base::mojom::FileError::FAILED;
+ }
+ NOTREACHED();
+ return mojo_base::mojom::FileError::FAILED;
+ }
+
+ static bool FromMojom(mojo_base::mojom::FileError in,
+ base::File::Error* out) {
+ switch (in) {
+ case mojo_base::mojom::FileError::OK:
+ *out = base::File::FILE_OK;
+ return true;
+ case mojo_base::mojom::FileError::FAILED:
+ *out = base::File::FILE_ERROR_FAILED;
+ return true;
+ case mojo_base::mojom::FileError::IN_USE:
+ *out = base::File::FILE_ERROR_IN_USE;
+ return true;
+ case mojo_base::mojom::FileError::EXISTS:
+ *out = base::File::FILE_ERROR_EXISTS;
+ return true;
+ case mojo_base::mojom::FileError::NOT_FOUND:
+ *out = base::File::FILE_ERROR_NOT_FOUND;
+ return true;
+ case mojo_base::mojom::FileError::ACCESS_DENIED:
+ *out = base::File::FILE_ERROR_ACCESS_DENIED;
+ return true;
+ case mojo_base::mojom::FileError::TOO_MANY_OPENED:
+ *out = base::File::FILE_ERROR_TOO_MANY_OPENED;
+ return true;
+ case mojo_base::mojom::FileError::NO_MEMORY:
+ *out = base::File::FILE_ERROR_NO_MEMORY;
+ return true;
+ case mojo_base::mojom::FileError::NO_SPACE:
+ *out = base::File::FILE_ERROR_NO_SPACE;
+ return true;
+ case mojo_base::mojom::FileError::NOT_A_DIRECTORY:
+ *out = base::File::FILE_ERROR_NOT_A_DIRECTORY;
+ return true;
+ case mojo_base::mojom::FileError::INVALID_OPERATION:
+ *out = base::File::FILE_ERROR_INVALID_OPERATION;
+ return true;
+ case mojo_base::mojom::FileError::SECURITY:
+ *out = base::File::FILE_ERROR_SECURITY;
+ return true;
+ case mojo_base::mojom::FileError::ABORT:
+ *out = base::File::FILE_ERROR_ABORT;
+ return true;
+ case mojo_base::mojom::FileError::NOT_A_FILE:
+ *out = base::File::FILE_ERROR_NOT_A_FILE;
+ return true;
+ case mojo_base::mojom::FileError::NOT_EMPTY:
+ *out = base::File::FILE_ERROR_NOT_EMPTY;
+ return true;
+ case mojo_base::mojom::FileError::INVALID_URL:
+ *out = base::File::FILE_ERROR_INVALID_URL;
+ return true;
+ case mojo_base::mojom::FileError::IO:
+ *out = base::File::FILE_ERROR_IO;
+ return true;
+ }
+ NOTREACHED();
+ return false;
+ }
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BASE_FILE_ERROR_MOJOM_TRAITS_H_
diff --git a/mojo/public/cpp/base/file_info.typemap b/mojo/public/cpp/base/file_info.typemap
new file mode 100644
index 0000000000..4faac8f1c4
--- /dev/null
+++ b/mojo/public/cpp/base/file_info.typemap
@@ -0,0 +1,13 @@
+# 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/public/mojom/base/file_info.mojom"
+public_headers = [ "//base/files/file.h" ]
+traits_headers = [ "//mojo/public/cpp/base/file_info_mojom_traits.h" ]
+public_deps = [
+ "//base",
+ "//mojo/public/cpp/base:shared_typemap_traits",
+]
+
+type_mappings = [ "mojo_base.mojom.FileInfo=base::File::Info" ]
diff --git a/mojo/public/cpp/base/file_info_mojom_traits.cc b/mojo/public/cpp/base/file_info_mojom_traits.cc
new file mode 100644
index 0000000000..64a0802957
--- /dev/null
+++ b/mojo/public/cpp/base/file_info_mojom_traits.cc
@@ -0,0 +1,28 @@
+// 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.
+
+#include "mojo/public/cpp/base/file_info_mojom_traits.h"
+
+#include "base/logging.h"
+#include "mojo/public/cpp/base/time_mojom_traits.h"
+
+namespace mojo {
+
+// static
+bool StructTraits<mojo_base::mojom::FileInfoDataView, base::File::Info>::Read(
+ mojo_base::mojom::FileInfoDataView data,
+ base::File::Info* out) {
+ if (!data.ReadLastModified(&out->last_modified))
+ return false;
+ if (!data.ReadLastAccessed(&out->last_accessed))
+ return false;
+ if (!data.ReadCreationTime(&out->creation_time))
+ return false;
+ out->size = data.size();
+ out->is_directory = data.is_directory();
+ out->is_symbolic_link = data.is_symbolic_link();
+ return true;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/base/file_info_mojom_traits.h b/mojo/public/cpp/base/file_info_mojom_traits.h
new file mode 100644
index 0000000000..4049e038d7
--- /dev/null
+++ b/mojo/public/cpp/base/file_info_mojom_traits.h
@@ -0,0 +1,49 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BASE_FILE_INFO_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_FILE_INFO_MOJOM_TRAITS_H_
+
+#include <cstdint>
+
+#include "base/component_export.h"
+#include "base/files/file.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "mojo/public/mojom/base/file_info.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_SHARED_TRAITS)
+ StructTraits<mojo_base::mojom::FileInfoDataView, base::File::Info> {
+ static int64_t size(const base::File::Info& info) { return info.size; }
+
+ static bool is_directory(const base::File::Info& info) {
+ return info.is_directory;
+ }
+
+ static bool is_symbolic_link(const base::File::Info& info) {
+ return info.is_symbolic_link;
+ }
+
+ static base::Time last_modified(const base::File::Info& info) {
+ return info.last_modified;
+ }
+
+ static base::Time last_accessed(const base::File::Info& info) {
+ return info.last_accessed;
+ }
+
+ static base::Time creation_time(const base::File::Info& info) {
+ return info.creation_time;
+ }
+
+ static bool Read(mojo_base::mojom::FileInfoDataView data,
+ base::File::Info* out);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BASE_FILE_INFO_MOJOM_TRAITS_H_
diff --git a/mojo/public/cpp/base/file_mojom_traits.cc b/mojo/public/cpp/base/file_mojom_traits.cc
new file mode 100644
index 0000000000..5799b79dd1
--- /dev/null
+++ b/mojo/public/cpp/base/file_mojom_traits.cc
@@ -0,0 +1,30 @@
+// 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.
+
+#include "mojo/public/cpp/base/file_mojom_traits.h"
+#include "base/files/file.h"
+#include "mojo/public/cpp/system/platform_handle.h"
+
+namespace mojo {
+
+mojo::ScopedHandle StructTraits<mojo_base::mojom::FileDataView, base::File>::fd(
+ base::File& file) {
+ DCHECK(file.IsValid());
+
+ return mojo::WrapPlatformFile(file.TakePlatformFile());
+}
+
+bool StructTraits<mojo_base::mojom::FileDataView, base::File>::Read(
+ mojo_base::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, data.async());
+ return true;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/base/file_mojom_traits.h b/mojo/public/cpp/base/file_mojom_traits.h
new file mode 100644
index 0000000000..e4dd2f9875
--- /dev/null
+++ b/mojo/public/cpp/base/file_mojom_traits.h
@@ -0,0 +1,28 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BASE_FILE_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_FILE_MOJOM_TRAITS_H_
+
+#include "base/component_export.h"
+#include "base/files/file.h"
+#include "mojo/public/mojom/base/file.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_MOJOM)
+ StructTraits<mojo_base::mojom::FileDataView, base::File> {
+ 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 async(base::File& file) { return file.async(); }
+ static bool Read(mojo_base::mojom::FileDataView data, base::File* file);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BASE_FILE_MOJOM_TRAITS_H_
diff --git a/mojo/public/cpp/base/file_path.typemap b/mojo/public/cpp/base/file_path.typemap
new file mode 100644
index 0000000000..aa30b2d89d
--- /dev/null
+++ b/mojo/public/cpp/base/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/public/mojom/base/file_path.mojom"
+public_headers = [ "//base/files/file_path.h" ]
+traits_headers = [ "//mojo/public/cpp/base/file_path_mojom_traits.h" ]
+public_deps = [
+ "//mojo/public/cpp/base:shared_typemap_traits",
+]
+
+type_mappings = [ "mojo_base.mojom.FilePath=base::FilePath" ]
diff --git a/mojo/public/cpp/base/file_path_mojom_traits.cc b/mojo/public/cpp/base/file_path_mojom_traits.cc
new file mode 100644
index 0000000000..f7af33d8b7
--- /dev/null
+++ b/mojo/public/cpp/base/file_path_mojom_traits.cc
@@ -0,0 +1,28 @@
+// 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.
+
+#include "mojo/public/cpp/base/file_path_mojom_traits.h"
+
+namespace mojo {
+
+// static
+bool StructTraits<mojo_base::mojom::FilePathDataView, base::FilePath>::Read(
+ mojo_base::mojom::FilePathDataView data,
+ base::FilePath* out) {
+ base::FilePath::StringPieceType path_view;
+#if defined(OS_WIN)
+ ArrayDataView<uint16_t> view;
+ data.GetPathDataView(&view);
+ path_view.set(reinterpret_cast<const base::char16*>(view.data()),
+ view.size());
+#else
+ if (!data.ReadPath(&path_view)) {
+ return false;
+ }
+#endif
+ *out = base::FilePath(path_view);
+ return true;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/base/file_path_mojom_traits.h b/mojo/public/cpp/base/file_path_mojom_traits.h
new file mode 100644
index 0000000000..f974f1c25f
--- /dev/null
+++ b/mojo/public/cpp/base/file_path_mojom_traits.h
@@ -0,0 +1,38 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BASE_FILE_PATH_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_FILE_PATH_MOJOM_TRAITS_H_
+
+#include "base/component_export.h"
+#include "base/containers/span.h"
+#include "base/files/file_path.h"
+#include "build/build_config.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "mojo/public/mojom/base/file_path.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_SHARED_TRAITS)
+ StructTraits<mojo_base::mojom::FilePathDataView, base::FilePath> {
+#if defined(OS_WIN)
+ static base::span<const uint16_t> path(const base::FilePath& path) {
+ return base::make_span(
+ reinterpret_cast<const uint16_t*>(path.value().data()),
+ path.value().size());
+ }
+#else
+ static const base::FilePath::StringType& path(const base::FilePath& path) {
+ return path.value();
+ }
+#endif
+
+ static bool Read(mojo_base::mojom::FilePathDataView data,
+ base::FilePath* out);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BASE_FILE_PATH_MOJOM_TRAITS_H_
diff --git a/mojo/public/cpp/base/file_path_unittest.cc b/mojo/public/cpp/base/file_path_unittest.cc
new file mode 100644
index 0000000000..a5b027a9bf
--- /dev/null
+++ b/mojo/public/cpp/base/file_path_unittest.cc
@@ -0,0 +1,24 @@
+// 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.
+
+#include "mojo/public/cpp/base/file_path_mojom_traits.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/mojom/base/file_path.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo_base {
+namespace file_path_unittest {
+
+TEST(FilePathTest, File) {
+ base::FilePath dir(FILE_PATH_LITERAL("hello"));
+ base::FilePath file = dir.Append(FILE_PATH_LITERAL("world"));
+ base::FilePath file_out;
+
+ ASSERT_TRUE(
+ mojo::test::SerializeAndDeserialize<mojom::FilePath>(&file, &file_out));
+ ASSERT_EQ(file, file_out);
+}
+
+} // namespace file_path_unittest
+} // namespace mojo_base
diff --git a/mojo/public/cpp/base/file_unittest.cc b/mojo/public/cpp/base/file_unittest.cc
new file mode 100644
index 0000000000..e1db729c75
--- /dev/null
+++ b/mojo/public/cpp/base/file_unittest.cc
@@ -0,0 +1,73 @@
+// 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.
+
+#include "base/files/scoped_temp_dir.h"
+#include "mojo/public/cpp/base/file_mojom_traits.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/mojom/base/file.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo_base {
+namespace file_unittest {
+
+TEST(FileTest, File) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ 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::checked_cast<int>(test_content.size()));
+
+ base::File file_out;
+ ASSERT_TRUE(
+ mojo::test::SerializeAndDeserialize<mojom::File>(&file, &file_out));
+ std::vector<char> content(test_content.size());
+ ASSERT_TRUE(file_out.IsValid());
+ ASSERT_FALSE(file_out.async());
+ ASSERT_EQ(static_cast<int>(test_content.size()),
+ file_out.Read(0, content.data(),
+ base::checked_cast<int>(test_content.size())));
+ EXPECT_EQ(test_content,
+ base::StringPiece(content.data(), test_content.size()));
+}
+
+TEST(FileTest, AsyncFile) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath path = temp_dir.GetPath().AppendASCII("async_test_file.txt");
+
+ base::File write_file(path, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+ const base::StringPiece test_content = "test string";
+ write_file.WriteAtCurrentPos(test_content.data(),
+ base::checked_cast<int>(test_content.size()));
+ write_file.Close();
+
+ base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ |
+ base::File::FLAG_ASYNC);
+ base::File file_out;
+ ASSERT_TRUE(
+ mojo::test::SerializeAndDeserialize<mojom::File>(&file, &file_out));
+ ASSERT_TRUE(file_out.async());
+}
+
+TEST(FileTest, InvalidFile) {
+ 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);
+
+ base::File file = base::File();
+ ASSERT_TRUE(
+ mojo::test::SerializeAndDeserialize<mojom::File>(&file, &file_out));
+ EXPECT_FALSE(file_out.IsValid());
+}
+
+} // namespace file_unittest
+} // namespace mojo_base
diff --git a/mojo/public/cpp/base/logfont_win.typemap b/mojo/public/cpp/base/logfont_win.typemap
new file mode 100644
index 0000000000..daaf6fcca6
--- /dev/null
+++ b/mojo/public/cpp/base/logfont_win.typemap
@@ -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.
+
+mojom = "//mojo/public/mojom/base/logfont_win.mojom"
+public_headers = [
+ "//base/win/windows_full.h",
+ "//base/win/windows_types.h",
+]
+traits_headers = [ "//mojo/public/cpp/base/logfont_win_mojom_traits.h" ]
+public_deps = [
+ "//base",
+]
+sources = [
+ "//mojo/public/cpp/base/logfont_win_mojom_traits.cc",
+ "//mojo/public/cpp/base/logfont_win_mojom_traits.h",
+]
+type_mappings = [ "mojo_base.mojom.LOGFONT=::LOGFONT" ]
diff --git a/mojo/public/cpp/base/logfont_win_mojom_traits.cc b/mojo/public/cpp/base/logfont_win_mojom_traits.cc
new file mode 100644
index 0000000000..3c73701ce9
--- /dev/null
+++ b/mojo/public/cpp/base/logfont_win_mojom_traits.cc
@@ -0,0 +1,39 @@
+// 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.
+
+#include "mojo/public/cpp/base/logfont_win_mojom_traits.h"
+
+#include <tchar.h>
+
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+
+namespace mojo {
+
+// static
+base::span<const uint8_t>
+StructTraits<mojo_base::mojom::LOGFONTDataView, ::LOGFONT>::bytes(
+ const ::LOGFONT& input) {
+ return base::make_span(reinterpret_cast<const uint8_t*>(&input),
+ sizeof(::LOGFONT));
+}
+
+// static
+bool StructTraits<mojo_base::mojom::LOGFONTDataView, ::LOGFONT>::Read(
+ mojo_base::mojom::LOGFONTDataView data,
+ ::LOGFONT* out) {
+ ArrayDataView<uint8_t> bytes_view;
+ data.GetBytesDataView(&bytes_view);
+ if (bytes_view.size() != sizeof(::LOGFONT))
+ return false;
+
+ const ::LOGFONT* font = reinterpret_cast<const ::LOGFONT*>(bytes_view.data());
+ if (_tcsnlen(font->lfFaceName, LF_FACESIZE) >= LF_FACESIZE)
+ return false;
+
+ memcpy(out, font, sizeof(::LOGFONT));
+ return true;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/base/logfont_win_mojom_traits.h b/mojo/public/cpp/base/logfont_win_mojom_traits.h
new file mode 100644
index 0000000000..09a9fbbeee
--- /dev/null
+++ b/mojo/public/cpp/base/logfont_win_mojom_traits.h
@@ -0,0 +1,30 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BASE_LOGFONT_WIN_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_LOGFONT_WIN_MOJOM_TRAITS_H_
+
+#include <windows.h>
+
+#include <cstdint>
+
+#include "base/component_export.h"
+#include "base/containers/span.h"
+#include "base/macros.h"
+#include "base/win/windows_types.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "mojo/public/mojom/base/logfont_win.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_MOJOM)
+ StructTraits<mojo_base::mojom::LOGFONTDataView, ::LOGFONT> {
+ static base::span<const uint8_t> bytes(const ::LOGFONT& input);
+ static bool Read(mojo_base::mojom::LOGFONTDataView data, ::LOGFONT* out);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BASE_LOGFONT_WIN_MOJOM_TRAITS_H_
diff --git a/mojo/public/cpp/base/memory_allocator_dump_cross_process_uid.typemap b/mojo/public/cpp/base/memory_allocator_dump_cross_process_uid.typemap
new file mode 100644
index 0000000000..f839a95161
--- /dev/null
+++ b/mojo/public/cpp/base/memory_allocator_dump_cross_process_uid.typemap
@@ -0,0 +1,13 @@
+# 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/public/mojom/base/memory_allocator_dump_cross_process_uid.mojom"
+public_headers = [ "//base/trace_event/memory_allocator_dump_guid.h" ]
+traits_headers = [ "//mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_mojom_traits.h" ]
+sources = [
+ "//mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_mojom_traits.cc",
+ "//mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_mojom_traits.h",
+]
+
+type_mappings = [ "mojo_base.mojom.MemoryAllocatorDumpCrossProcessUid=base::trace_event::MemoryAllocatorDumpGuid" ]
diff --git a/mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_mojom_traits.cc b/mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_mojom_traits.cc
new file mode 100644
index 0000000000..4a5ed8e119
--- /dev/null
+++ b/mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_mojom_traits.cc
@@ -0,0 +1,22 @@
+// 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.
+
+#include "mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_mojom_traits.h"
+
+namespace mojo {
+
+// static
+bool StructTraits<mojo_base::mojom::MemoryAllocatorDumpCrossProcessUidDataView,
+ base::trace_event::MemoryAllocatorDumpGuid>::
+ Read(mojo_base::mojom::MemoryAllocatorDumpCrossProcessUidDataView data,
+ base::trace_event::MemoryAllocatorDumpGuid* out) {
+ // Receiving a zeroed MemoryAllocatorDumpCrossProcessUid is a bug.
+ if (data.value() == 0)
+ return false;
+
+ *out = base::trace_event::MemoryAllocatorDumpGuid(data.value());
+ return true;
+}
+
+} // namespace mojo \ No newline at end of file
diff --git a/mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_mojom_traits.h b/mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_mojom_traits.h
new file mode 100644
index 0000000000..b99a9c537a
--- /dev/null
+++ b/mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_mojom_traits.h
@@ -0,0 +1,29 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BASE_MEMORY_ALLOCATOR_DUMP_CROSS_PROCESS_UID_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_MEMORY_ALLOCATOR_DUMP_CROSS_PROCESS_UID_MOJOM_TRAITS_H_
+
+#include "base/component_export.h"
+#include "base/trace_event/memory_allocator_dump_guid.h"
+#include "mojo/public/mojom/base/memory_allocator_dump_cross_process_uid.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_MOJOM)
+ StructTraits<mojo_base::mojom::MemoryAllocatorDumpCrossProcessUidDataView,
+ base::trace_event::MemoryAllocatorDumpGuid> {
+ static uint64_t value(const base::trace_event::MemoryAllocatorDumpGuid& id) {
+ return id.ToUint64();
+ }
+
+ static bool Read(
+ mojo_base::mojom::MemoryAllocatorDumpCrossProcessUidDataView data,
+ base::trace_event::MemoryAllocatorDumpGuid* out);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BASE_MEMORY_ALLOCATOR_DUMP_CROSS_PROCESS_UID_MOJOM_TRAITS_H_
diff --git a/mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_unittest.cc b/mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_unittest.cc
new file mode 100644
index 0000000000..b89781cbd8
--- /dev/null
+++ b/mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_unittest.cc
@@ -0,0 +1,42 @@
+// 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.
+
+#include "mojo/public/cpp/base/memory_allocator_dump_cross_process_uid_mojom_traits.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/mojom/base/memory_allocator_dump_cross_process_uid.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo_base {
+namespace memory_allocator_dump_cross_process_uid_unittest {
+
+TEST(MemoryAllocatorDumpCrossProcessUidTest, SerializeFailsOnZeroValue) {
+ base::trace_event::MemoryAllocatorDumpGuid in(0);
+ base::trace_event::MemoryAllocatorDumpGuid out;
+
+ ASSERT_FALSE(mojo::test::SerializeAndDeserialize<
+ mojom::MemoryAllocatorDumpCrossProcessUid>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+TEST(MemoryAllocatorDumpCrossProcessUidTest, SerializeSucceedsOnValidIntValue) {
+ base::trace_event::MemoryAllocatorDumpGuid in(12345);
+ base::trace_event::MemoryAllocatorDumpGuid out;
+
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<
+ mojom::MemoryAllocatorDumpCrossProcessUid>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+TEST(MemoryAllocatorDumpCrossProcessUidTest,
+ SerializeSucceedsOnValidStringValue) {
+ base::trace_event::MemoryAllocatorDumpGuid in("12345");
+ base::trace_event::MemoryAllocatorDumpGuid out;
+
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<
+ mojom::MemoryAllocatorDumpCrossProcessUid>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+} // namespace memory_allocator_dump_cross_process_uid_unittest
+} // namespace mojo_base \ No newline at end of file
diff --git a/mojo/public/cpp/base/process_id.typemap b/mojo/public/cpp/base/process_id.typemap
new file mode 100644
index 0000000000..7ba31ffc81
--- /dev/null
+++ b/mojo/public/cpp/base/process_id.typemap
@@ -0,0 +1,14 @@
+# 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/public/mojom/base/process_id.mojom"
+public_headers = [ "//base/process/process_handle.h" ]
+traits_headers = [ "//mojo/public/cpp/base/process_id_mojom_traits.h" ]
+sources = [
+ "//mojo/public/cpp/base/process_id_mojom_traits.cc",
+ "//mojo/public/cpp/base/process_id_mojom_traits.h",
+]
+
+type_mappings =
+ [ "mojo_base.mojom.ProcessId=base::ProcessId[copyable_pass_by_value]" ]
diff --git a/mojo/public/cpp/base/process_id_mojom_traits.cc b/mojo/public/cpp/base/process_id_mojom_traits.cc
new file mode 100644
index 0000000000..fd49327c1a
--- /dev/null
+++ b/mojo/public/cpp/base/process_id_mojom_traits.cc
@@ -0,0 +1,16 @@
+// 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.
+
+#include "mojo/public/cpp/base/process_id_mojom_traits.h"
+
+namespace mojo {
+
+bool StructTraits<mojo_base::mojom::ProcessIdDataView, base::ProcessId>::Read(
+ mojo_base::mojom::ProcessIdDataView data,
+ base::ProcessId* process_id) {
+ *process_id = static_cast<base::ProcessId>(data.pid());
+ return true;
+}
+
+} // namespace mojo \ No newline at end of file
diff --git a/mojo/public/cpp/base/process_id_mojom_traits.h b/mojo/public/cpp/base/process_id_mojom_traits.h
new file mode 100644
index 0000000000..ba8a80db47
--- /dev/null
+++ b/mojo/public/cpp/base/process_id_mojom_traits.h
@@ -0,0 +1,27 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BASE_PROCESS_ID_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_PROCESS_ID_MOJOM_TRAITS_H_
+
+#include "base/component_export.h"
+#include "base/process/process_handle.h"
+#include "mojo/public/mojom/base/process_id.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_MOJOM)
+ StructTraits<mojo_base::mojom::ProcessIdDataView, base::ProcessId> {
+ static uint32_t pid(const base::ProcessId& process_id) {
+ return static_cast<uint32_t>(process_id);
+ }
+
+ static bool Read(mojo_base::mojom::ProcessIdDataView data,
+ base::ProcessId* process_id);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BASE_PROCESS_ID_MOJOM_TRAITS_H_ \ No newline at end of file
diff --git a/mojo/public/cpp/base/process_id_unittest.cc b/mojo/public/cpp/base/process_id_unittest.cc
new file mode 100644
index 0000000000..d0025320e7
--- /dev/null
+++ b/mojo/public/cpp/base/process_id_unittest.cc
@@ -0,0 +1,23 @@
+// 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.
+
+#include "mojo/public/cpp/base/process_id_mojom_traits.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/mojom/base/process_id.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo_base {
+namespace process_id_unittest {
+
+TEST(ProcessIdTest, ProcessId) {
+ base::ProcessId pid = base::GetCurrentProcId();
+ base::ProcessId out_pid = base::kNullProcessId;
+ ASSERT_NE(pid, out_pid);
+ EXPECT_TRUE(mojom::ProcessId::Deserialize(mojom::ProcessId::Serialize(&pid),
+ &out_pid));
+ EXPECT_EQ(pid, out_pid);
+}
+
+} // namespace process_id_unittest
+} // namespace mojo_base \ No newline at end of file
diff --git a/mojo/public/cpp/base/read_only_buffer.typemap b/mojo/public/cpp/base/read_only_buffer.typemap
new file mode 100644
index 0000000000..b772b729b6
--- /dev/null
+++ b/mojo/public/cpp/base/read_only_buffer.typemap
@@ -0,0 +1,13 @@
+# 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/public/mojom/base/read_only_buffer.mojom"
+public_headers = [ "//base/containers/span.h" ]
+traits_headers = [ "//mojo/public/cpp/base/read_only_buffer_mojom_traits.h" ]
+sources = [
+ "//mojo/public/cpp/base/read_only_buffer_mojom_traits.cc",
+ "//mojo/public/cpp/base/read_only_buffer_mojom_traits.h",
+]
+
+type_mappings = [ "mojo_base.mojom.ReadOnlyBuffer=base::span<const uint8_t>[copyable_pass_by_value,force_serialize]" ]
diff --git a/mojo/public/cpp/base/read_only_buffer_mojom_traits.cc b/mojo/public/cpp/base/read_only_buffer_mojom_traits.cc
new file mode 100644
index 0000000000..bd5240e48e
--- /dev/null
+++ b/mojo/public/cpp/base/read_only_buffer_mojom_traits.cc
@@ -0,0 +1,23 @@
+// 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.
+
+#include "mojo/public/cpp/base/read_only_buffer_mojom_traits.h"
+
+namespace mojo {
+
+bool StructTraits<mojo_base::mojom::ReadOnlyBufferDataView,
+ base::span<const uint8_t>>::
+ Read(mojo_base::mojom::ReadOnlyBufferDataView input,
+ base::span<const uint8_t>* out) {
+ ArrayDataView<uint8_t> data_view;
+ input.GetBufferDataView(&data_view);
+
+ // NOTE: This output directly refers to memory owned by the message.
+ // Therefore, the message must stay valid while the output is passed to the
+ // user code.
+ *out = base::span<const uint8_t>(data_view.data(), data_view.size());
+ return true;
+}
+
+} // namespace mojo \ No newline at end of file
diff --git a/mojo/public/cpp/base/read_only_buffer_mojom_traits.h b/mojo/public/cpp/base/read_only_buffer_mojom_traits.h
new file mode 100644
index 0000000000..ad96e91e07
--- /dev/null
+++ b/mojo/public/cpp/base/read_only_buffer_mojom_traits.h
@@ -0,0 +1,26 @@
+// 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_BASE_READ_ONLY_BUFFER_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_READ_ONLY_BUFFER_MOJOM_TRAITS_H_
+
+#include "base/containers/span.h"
+#include "mojo/public/mojom/base/read_only_buffer.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct StructTraits<mojo_base::mojom::ReadOnlyBufferDataView,
+ base::span<const uint8_t>> {
+ static base::span<const uint8_t> buffer(base::span<const uint8_t> input) {
+ return input;
+ }
+
+ static bool Read(mojo_base::mojom::ReadOnlyBufferDataView input,
+ base::span<const uint8_t>* out);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BASE_READ_ONLY_BUFFER_MOJOM_TRAITS_H_
diff --git a/mojo/public/cpp/base/read_only_buffer_unittest.cc b/mojo/public/cpp/base/read_only_buffer_unittest.cc
new file mode 100644
index 0000000000..990cb36a0f
--- /dev/null
+++ b/mojo/public/cpp/base/read_only_buffer_unittest.cc
@@ -0,0 +1,37 @@
+// 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.
+
+#include "mojo/public/cpp/base/read_only_buffer_mojom_traits.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/mojom/base/read_only_buffer.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo_base {
+namespace read_only_buffer_unittest {
+
+TEST(ReadOnlyBufferTest, ReadOnlyBufferEmptySpan) {
+ base::span<const uint8_t> in;
+ base::span<const uint8_t> out;
+
+ ASSERT_TRUE(
+ mojo::test::SerializeAndDeserialize<mojom::ReadOnlyBuffer>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+TEST(ReadOnlyBufferTest, ReadOnlyBufferNonEmptySpan) {
+ std::vector<uint8_t> v{'1', '2', '3'};
+ base::span<const uint8_t> in(v);
+ base::span<const uint8_t> out;
+
+ // SerializeAndDeserialize doesn't work here: the traits for ReadOnlyBuffer
+ // returns a span that points into the raw bytes in the mojo::Message;however,
+ // the stack object will be freed before SerializeAndSerialize returns.
+ std::vector<uint8_t> data = mojom::ReadOnlyBuffer::Serialize(&in);
+
+ EXPECT_TRUE(mojom::ReadOnlyBuffer::Deserialize(std::move(data), &out));
+ EXPECT_EQ(in, out);
+}
+
+} // namespace read_only_buffer_unittest
+} // namespace mojo_base
diff --git a/mojo/public/cpp/base/ref_counted_memory.typemap b/mojo/public/cpp/base/ref_counted_memory.typemap
new file mode 100644
index 0000000000..407fc955d1
--- /dev/null
+++ b/mojo/public/cpp/base/ref_counted_memory.typemap
@@ -0,0 +1,17 @@
+# 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.
+
+mojom = "//mojo/public/mojom/base/ref_counted_memory.mojom"
+public_headers = [ "//base/memory/ref_counted_memory.h" ]
+traits_headers = [ "//mojo/public/cpp/base/ref_counted_memory_mojom_traits.h" ]
+sources = [
+ "//mojo/public/cpp/base/ref_counted_memory_mojom_traits.cc",
+ "//mojo/public/cpp/base/ref_counted_memory_mojom_traits.h",
+]
+public_deps = [
+ "//base",
+ "//mojo/public/cpp/base",
+ "//mojo/public/cpp/base:shared_typemap_traits",
+]
+type_mappings = [ "mojo.common.mojom.RefCountedMemory=scoped_refptr<base::RefCountedMemory>[copyable_pass_by_value,nullable_is_same_type]" ]
diff --git a/mojo/public/cpp/base/ref_counted_memory_mojom_traits.cc b/mojo/public/cpp/base/ref_counted_memory_mojom_traits.cc
new file mode 100644
index 0000000000..72835cc7e5
--- /dev/null
+++ b/mojo/public/cpp/base/ref_counted_memory_mojom_traits.cc
@@ -0,0 +1,46 @@
+// 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.
+
+#include "mojo/public/cpp/base/ref_counted_memory_mojom_traits.h"
+
+#include "mojo/public/cpp/base/big_buffer_mojom_traits.h"
+
+namespace mojo {
+
+// static
+mojo_base::BigBuffer StructTraits<mojo_base::mojom::RefCountedMemoryDataView,
+ scoped_refptr<base::RefCountedMemory>>::
+ data(const scoped_refptr<base::RefCountedMemory>& in) {
+ return mojo_base::BigBuffer(base::make_span(in->front(), in->size()));
+}
+
+// static
+bool StructTraits<mojo_base::mojom::RefCountedMemoryDataView,
+ scoped_refptr<base::RefCountedMemory>>::
+ IsNull(const scoped_refptr<base::RefCountedMemory>& input) {
+ return !input.get();
+}
+
+// static
+void StructTraits<mojo_base::mojom::RefCountedMemoryDataView,
+ scoped_refptr<base::RefCountedMemory>>::
+ SetToNull(scoped_refptr<base::RefCountedMemory>* out) {
+ *out = scoped_refptr<base::RefCountedMemory>();
+}
+
+// static
+bool StructTraits<mojo_base::mojom::RefCountedMemoryDataView,
+ scoped_refptr<base::RefCountedMemory>>::
+ Read(mojo_base::mojom::RefCountedMemoryDataView data,
+ scoped_refptr<base::RefCountedMemory>* out) {
+ mojo_base::BigBuffer buffer;
+ if (!data.ReadData(&buffer))
+ return false;
+
+ *out =
+ base::MakeRefCounted<base::RefCountedBytes>(buffer.data(), buffer.size());
+ return true;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/base/ref_counted_memory_mojom_traits.h b/mojo/public/cpp/base/ref_counted_memory_mojom_traits.h
new file mode 100644
index 0000000000..ee8131f9d3
--- /dev/null
+++ b/mojo/public/cpp/base/ref_counted_memory_mojom_traits.h
@@ -0,0 +1,34 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BASE_REF_COUNTED_MEMORY_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_REF_COUNTED_MEMORY_MOJOM_TRAITS_H_
+
+#include <string>
+
+#include "base/component_export.h"
+#include "base/memory/ref_counted_memory.h"
+#include "mojo/public/cpp/base/big_buffer.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "mojo/public/mojom/base/ref_counted_memory.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_MOJOM)
+ StructTraits<mojo_base::mojom::RefCountedMemoryDataView,
+ scoped_refptr<base::RefCountedMemory>> {
+ static mojo_base::BigBuffer data(
+ const scoped_refptr<base::RefCountedMemory>& in);
+
+ static bool IsNull(const scoped_refptr<base::RefCountedMemory>& input);
+ static void SetToNull(scoped_refptr<base::RefCountedMemory>* out);
+
+ static bool Read(mojo_base::mojom::RefCountedMemoryDataView data,
+ scoped_refptr<base::RefCountedMemory>* out);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BASE_REF_COUNTED_MEMORY_MOJOM_TRAITS_H_
diff --git a/mojo/public/cpp/base/ref_counted_memory_unittest.cc b/mojo/public/cpp/base/ref_counted_memory_unittest.cc
new file mode 100644
index 0000000000..a3c757a0ee
--- /dev/null
+++ b/mojo/public/cpp/base/ref_counted_memory_unittest.cc
@@ -0,0 +1,40 @@
+// 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.
+
+#include "mojo/public/cpp/base/big_buffer_mojom_traits.h"
+#include "mojo/public/cpp/base/ref_counted_memory_mojom_traits.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/mojom/base/ref_counted_memory.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo_base {
+
+TEST(RefCountedMemoryTest, Data) {
+ uint8_t data[] = {'a', 'b', 'c', 'd', 'e'};
+
+ scoped_refptr<base::RefCountedMemory> in =
+ new base::RefCountedStaticMemory(&data, arraysize(data));
+
+ scoped_refptr<base::RefCountedMemory> out;
+ ASSERT_TRUE(
+ mojo::test::SerializeAndDeserialize<mojom::RefCountedMemory>(&in, &out));
+ ASSERT_EQ(out->size(), in->size());
+ for (size_t i = 0; i < out->size(); ++i)
+ EXPECT_EQ(in->front()[i], out->front()[i]);
+}
+
+TEST(RefCountedMemoryTest, Null) {
+ // Stuff real data in out to ensure it gets overwritten with a null.
+ uint8_t data[] = {'a', 'b', 'c', 'd', 'e'};
+ scoped_refptr<base::RefCountedMemory> out =
+ new base::RefCountedStaticMemory(&data, arraysize(data));
+
+ scoped_refptr<base::RefCountedMemory> in;
+ ASSERT_TRUE(
+ mojo::test::SerializeAndDeserialize<mojom::RefCountedMemory>(&in, &out));
+
+ ASSERT_FALSE(out.get());
+}
+
+} // namespace mojo_base
diff --git a/mojo/public/cpp/base/shared_memory.typemap b/mojo/public/cpp/base/shared_memory.typemap
new file mode 100644
index 0000000000..9454f0d1a8
--- /dev/null
+++ b/mojo/public/cpp/base/shared_memory.typemap
@@ -0,0 +1,24 @@
+# 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.
+
+mojom = "//mojo/public/mojom/base/shared_memory.mojom"
+public_headers = [
+ "//base/memory/platform_shared_memory_region.h",
+ "//base/memory/read_only_shared_memory_region.h",
+ "//base/memory/unsafe_shared_memory_region.h",
+ "//base/memory/writable_shared_memory_region.h",
+]
+traits_headers = [ "//mojo/public/cpp/base/shared_memory_mojom_traits.h" ]
+public_deps = [
+ "//base",
+ "//mojo/public/cpp/base:shared_typemap_traits",
+]
+type_mappings = [
+ "mojo_base.mojom.PlatformSharedMemoryHandle=base::subtle::PlatformSharedMemoryRegion::ScopedPlatformHandle[move_only]",
+ "mojo_base.mojom.PlatformSharedMemoryRegion=base::subtle::PlatformSharedMemoryRegion[move_only]",
+ "mojo_base.mojom.PlatformSharedMemoryRegion.Mode=base::subtle::PlatformSharedMemoryRegion::Mode",
+ "mojo_base.mojom.ReadOnlySharedMemoryRegion=base::ReadOnlySharedMemoryRegion[move_only,nullable_is_same_type]",
+ "mojo_base.mojom.UnsafeSharedMemoryRegion=base::UnsafeSharedMemoryRegion[move_only,nullable_is_same_type]",
+ "mojo_base.mojom.WritableSharedMemoryRegion=base::WritableSharedMemoryRegion[move_only,nullable_is_same_type]",
+]
diff --git a/mojo/public/cpp/base/shared_memory_mojom_traits.cc b/mojo/public/cpp/base/shared_memory_mojom_traits.cc
new file mode 100644
index 0000000000..8c28b0872c
--- /dev/null
+++ b/mojo/public/cpp/base/shared_memory_mojom_traits.cc
@@ -0,0 +1,105 @@
+// 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.
+
+#include "mojo/public/cpp/base/shared_memory_mojom_traits.h"
+
+#include "base/logging.h"
+#include "mojo/public/cpp/system/platform_handle.h"
+
+namespace mojo {
+
+// static
+bool StructTraits<mojo_base::mojom::ReadOnlySharedMemoryRegionDataView,
+ base::ReadOnlySharedMemoryRegion>::
+ IsNull(const base::ReadOnlySharedMemoryRegion& region) {
+ return !region.IsValid();
+}
+
+// static
+void StructTraits<mojo_base::mojom::ReadOnlySharedMemoryRegionDataView,
+ base::ReadOnlySharedMemoryRegion>::
+ SetToNull(base::ReadOnlySharedMemoryRegion* region) {
+ *region = base::ReadOnlySharedMemoryRegion();
+}
+
+// static
+mojo::ScopedSharedBufferHandle StructTraits<
+ mojo_base::mojom::ReadOnlySharedMemoryRegionDataView,
+ base::ReadOnlySharedMemoryRegion>::buffer(base::ReadOnlySharedMemoryRegion&
+ in_region) {
+ return mojo::WrapReadOnlySharedMemoryRegion(std::move(in_region));
+}
+
+// static
+bool StructTraits<mojo_base::mojom::ReadOnlySharedMemoryRegionDataView,
+ base::ReadOnlySharedMemoryRegion>::
+ Read(mojo_base::mojom::ReadOnlySharedMemoryRegionDataView data,
+ base::ReadOnlySharedMemoryRegion* out) {
+ *out = mojo::UnwrapReadOnlySharedMemoryRegion(data.TakeBuffer());
+ return out->IsValid();
+}
+
+// static
+bool StructTraits<mojo_base::mojom::UnsafeSharedMemoryRegionDataView,
+ base::UnsafeSharedMemoryRegion>::
+ IsNull(const base::UnsafeSharedMemoryRegion& region) {
+ return !region.IsValid();
+}
+
+// static
+void StructTraits<mojo_base::mojom::UnsafeSharedMemoryRegionDataView,
+ base::UnsafeSharedMemoryRegion>::
+ SetToNull(base::UnsafeSharedMemoryRegion* region) {
+ *region = base::UnsafeSharedMemoryRegion();
+}
+
+// static
+mojo::ScopedSharedBufferHandle StructTraits<
+ mojo_base::mojom::UnsafeSharedMemoryRegionDataView,
+ base::UnsafeSharedMemoryRegion>::buffer(base::UnsafeSharedMemoryRegion&
+ in_region) {
+ return mojo::WrapUnsafeSharedMemoryRegion(std::move(in_region));
+}
+
+// static
+bool StructTraits<mojo_base::mojom::UnsafeSharedMemoryRegionDataView,
+ base::UnsafeSharedMemoryRegion>::
+ Read(mojo_base::mojom::UnsafeSharedMemoryRegionDataView data,
+ base::UnsafeSharedMemoryRegion* out) {
+ *out = mojo::UnwrapUnsafeSharedMemoryRegion(data.TakeBuffer());
+ return out->IsValid();
+}
+
+// static
+bool StructTraits<mojo_base::mojom::WritableSharedMemoryRegionDataView,
+ base::WritableSharedMemoryRegion>::
+ IsNull(const base::WritableSharedMemoryRegion& region) {
+ return !region.IsValid();
+}
+
+// static
+void StructTraits<mojo_base::mojom::WritableSharedMemoryRegionDataView,
+ base::WritableSharedMemoryRegion>::
+ SetToNull(base::WritableSharedMemoryRegion* region) {
+ *region = base::WritableSharedMemoryRegion();
+}
+
+// static
+mojo::ScopedSharedBufferHandle StructTraits<
+ mojo_base::mojom::WritableSharedMemoryRegionDataView,
+ base::WritableSharedMemoryRegion>::buffer(base::WritableSharedMemoryRegion&
+ in_region) {
+ return mojo::WrapWritableSharedMemoryRegion(std::move(in_region));
+}
+
+// static
+bool StructTraits<mojo_base::mojom::WritableSharedMemoryRegionDataView,
+ base::WritableSharedMemoryRegion>::
+ Read(mojo_base::mojom::WritableSharedMemoryRegionDataView data,
+ base::WritableSharedMemoryRegion* out) {
+ *out = mojo::UnwrapWritableSharedMemoryRegion(data.TakeBuffer());
+ return out->IsValid();
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/base/shared_memory_mojom_traits.h b/mojo/public/cpp/base/shared_memory_mojom_traits.h
new file mode 100644
index 0000000000..96ab50651a
--- /dev/null
+++ b/mojo/public/cpp/base/shared_memory_mojom_traits.h
@@ -0,0 +1,56 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BASE_SHARED_MEMORY_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_SHARED_MEMORY_MOJOM_TRAITS_H_
+
+#include "base/component_export.h"
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "base/memory/writable_shared_memory_region.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/mojom/base/shared_memory.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_SHARED_TRAITS)
+ StructTraits<mojo_base::mojom::ReadOnlySharedMemoryRegionDataView,
+ base::ReadOnlySharedMemoryRegion> {
+ static bool IsNull(const base::ReadOnlySharedMemoryRegion& region);
+ static void SetToNull(base::ReadOnlySharedMemoryRegion* region);
+ static mojo::ScopedSharedBufferHandle buffer(
+ base::ReadOnlySharedMemoryRegion& in_region);
+ static bool Read(mojo_base::mojom::ReadOnlySharedMemoryRegionDataView data,
+ base::ReadOnlySharedMemoryRegion* out);
+};
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_SHARED_TRAITS)
+ StructTraits<mojo_base::mojom::UnsafeSharedMemoryRegionDataView,
+ base::UnsafeSharedMemoryRegion> {
+ static bool IsNull(const base::UnsafeSharedMemoryRegion& region);
+ static void SetToNull(base::UnsafeSharedMemoryRegion* region);
+ static mojo::ScopedSharedBufferHandle buffer(
+ base::UnsafeSharedMemoryRegion& in_region);
+ static bool Read(mojo_base::mojom::UnsafeSharedMemoryRegionDataView data,
+ base::UnsafeSharedMemoryRegion* out);
+};
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_SHARED_TRAITS)
+ StructTraits<mojo_base::mojom::WritableSharedMemoryRegionDataView,
+ base::WritableSharedMemoryRegion> {
+ static bool IsNull(const base::WritableSharedMemoryRegion& region);
+ static void SetToNull(base::WritableSharedMemoryRegion* region);
+ static mojo::ScopedSharedBufferHandle buffer(
+ base::WritableSharedMemoryRegion& in_region);
+ static bool Read(mojo_base::mojom::WritableSharedMemoryRegionDataView data,
+ base::WritableSharedMemoryRegion* out);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BASE_SHARED_MEMORY_MOJOM_TRAITS_H_
diff --git a/mojo/public/cpp/base/shared_memory_unittest.cc b/mojo/public/cpp/base/shared_memory_unittest.cc
new file mode 100644
index 0000000000..8f7a98675c
--- /dev/null
+++ b/mojo/public/cpp/base/shared_memory_unittest.cc
@@ -0,0 +1,53 @@
+// 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.
+
+#include <string>
+
+#include "mojo/public/cpp/base/shared_memory_mojom_traits.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/mojom/base/shared_memory.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(SharedMemoryMojomTest, ReadOnly) {
+ auto region = base::ReadOnlySharedMemoryRegion::Create(64);
+ const std::string kTestData = "Hello, world!";
+ memcpy(region.mapping.memory(), kTestData.data(), kTestData.size());
+
+ base::ReadOnlySharedMemoryRegion read_only_out;
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<
+ mojo_base::mojom::ReadOnlySharedMemoryRegion>(&region.region,
+ &read_only_out));
+ base::ReadOnlySharedMemoryMapping mapping = read_only_out.Map();
+ EXPECT_EQ(0, memcmp(mapping.memory(), kTestData.data(), kTestData.size()));
+}
+
+TEST(SharedMemoryMojomTest, Writable) {
+ auto region = base::WritableSharedMemoryRegion::Create(64);
+ auto mapping = region.Map();
+ const std::string kTestData = "Hello, world!";
+ memcpy(mapping.memory(), kTestData.data(), kTestData.size());
+
+ base::WritableSharedMemoryRegion writable_out;
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<
+ mojo_base::mojom::WritableSharedMemoryRegion>(&region,
+ &writable_out));
+
+ mapping = writable_out.Map();
+ EXPECT_EQ(0, memcmp(mapping.memory(), kTestData.data(), kTestData.size()));
+}
+
+TEST(SharedMemoryMojomTest, Unsafe) {
+ auto region = base::UnsafeSharedMemoryRegion::Create(64);
+ auto mapping = region.Map();
+ const std::string kTestData = "Hello, world!";
+ memcpy(mapping.memory(), kTestData.data(), kTestData.size());
+
+ base::UnsafeSharedMemoryRegion unsafe_out;
+ ASSERT_TRUE(
+ mojo::test::SerializeAndDeserialize<
+ mojo_base::mojom::UnsafeSharedMemoryRegion>(&region, &unsafe_out));
+
+ mapping = unsafe_out.Map();
+ EXPECT_EQ(0, memcmp(mapping.memory(), kTestData.data(), kTestData.size()));
+}
diff --git a/mojo/public/cpp/base/string16.typemap b/mojo/public/cpp/base/string16.typemap
new file mode 100644
index 0000000000..ce429afd7a
--- /dev/null
+++ b/mojo/public/cpp/base/string16.typemap
@@ -0,0 +1,19 @@
+# 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/public/mojom/base/string16.mojom"
+public_headers = [ "//base/strings/string16.h" ]
+traits_headers = [ "//mojo/public/cpp/base/string16_mojom_traits.h" ]
+sources = [
+ "//mojo/public/cpp/base/string16_mojom_traits.cc",
+ "//mojo/public/cpp/base/string16_mojom_traits.h",
+]
+public_deps = [
+ "//mojo/public/cpp/base:shared_typemap_traits",
+]
+
+type_mappings = [
+ "mojo_base.mojom.BigString16=base::string16",
+ "mojo_base.mojom.String16=base::string16",
+]
diff --git a/mojo/public/cpp/base/string16_mojom_traits.cc b/mojo/public/cpp/base/string16_mojom_traits.cc
new file mode 100644
index 0000000000..d19dd441ed
--- /dev/null
+++ b/mojo/public/cpp/base/string16_mojom_traits.cc
@@ -0,0 +1,44 @@
+// 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.
+
+#include "mojo/public/cpp/base/string16_mojom_traits.h"
+
+#include "mojo/public/cpp/base/big_buffer_mojom_traits.h"
+
+namespace mojo {
+
+// static
+bool StructTraits<mojo_base::mojom::String16DataView, base::string16>::Read(
+ mojo_base::mojom::String16DataView data,
+ base::string16* out) {
+ ArrayDataView<uint16_t> view;
+ data.GetDataDataView(&view);
+ out->assign(reinterpret_cast<const base::char16*>(view.data()), view.size());
+ return true;
+}
+
+// static
+mojo_base::BigBuffer
+StructTraits<mojo_base::mojom::BigString16DataView, base::string16>::data(
+ const base::string16& str) {
+ const auto* bytes = reinterpret_cast<const uint8_t*>(str.data());
+ return mojo_base::BigBuffer(
+ base::make_span(bytes, str.size() * sizeof(base::char16)));
+}
+
+// static
+bool StructTraits<mojo_base::mojom::BigString16DataView, base::string16>::Read(
+ mojo_base::mojom::BigString16DataView data,
+ base::string16* out) {
+ mojo_base::BigBuffer buffer;
+ if (!data.ReadData(&buffer))
+ return false;
+ if (buffer.size() % sizeof(base::char16))
+ return false;
+ *out = base::string16(reinterpret_cast<const base::char16*>(buffer.data()),
+ buffer.size() / sizeof(base::char16));
+ return true;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/base/string16_mojom_traits.h b/mojo/public/cpp/base/string16_mojom_traits.h
new file mode 100644
index 0000000000..93ef714414
--- /dev/null
+++ b/mojo/public/cpp/base/string16_mojom_traits.h
@@ -0,0 +1,51 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BASE_STRING16_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_STRING16_MOJOM_TRAITS_H_
+
+#include <cstdint>
+
+#include "base/component_export.h"
+#include "base/containers/span.h"
+#include "base/strings/string_piece.h"
+#include "mojo/public/cpp/base/big_buffer.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "mojo/public/mojom/base/string16.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_MOJOM)
+ StructTraits<mojo_base::mojom::String16DataView, base::StringPiece16> {
+ static base::span<const uint16_t> data(base::StringPiece16 str) {
+ return base::make_span(reinterpret_cast<const uint16_t*>(str.data()),
+ str.size());
+ }
+};
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_MOJOM)
+ StructTraits<mojo_base::mojom::String16DataView, base::string16> {
+ static base::span<const uint16_t> data(const base::string16& str) {
+ return StructTraits<mojo_base::mojom::String16DataView,
+ base::StringPiece16>::data(str);
+ }
+
+ static bool Read(mojo_base::mojom::String16DataView data,
+ base::string16* out);
+};
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_MOJOM)
+ StructTraits<mojo_base::mojom::BigString16DataView, base::string16> {
+ static mojo_base::BigBuffer data(const base::string16& str);
+
+ static bool Read(mojo_base::mojom::BigString16DataView data,
+ base::string16* out);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BASE_STRING16_MOJOM_TRAITS_H_
diff --git a/mojo/public/cpp/base/string16_unittest.cc b/mojo/public/cpp/base/string16_unittest.cc
new file mode 100644
index 0000000000..6a6d8a252b
--- /dev/null
+++ b/mojo/public/cpp/base/string16_unittest.cc
@@ -0,0 +1,60 @@
+// 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.
+
+#include "base/strings/string16.h"
+#include "base/rand_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "mojo/public/cpp/base/big_buffer_mojom_traits.h"
+#include "mojo/public/cpp/base/string16_mojom_traits.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/mojom/base/string16.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo_base {
+namespace string16_unittest {
+
+TEST(String16Test, Empty) {
+ base::string16 in;
+ base::string16 out;
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::String16>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+TEST(String16Test, NonEmpty) {
+ base::string16 in = base::ASCIIToUTF16("hello world");
+ base::string16 out;
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::String16>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+TEST(BigString16Test, Empty) {
+ base::string16 in;
+ base::string16 out;
+ ASSERT_TRUE(
+ mojo::test::SerializeAndDeserialize<mojom::BigString16>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+TEST(BigString16Test, Short) {
+ base::string16 in = base::ASCIIToUTF16("hello world");
+ base::string16 out;
+ ASSERT_TRUE(
+ mojo::test::SerializeAndDeserialize<mojom::BigString16>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+TEST(BigString16Test, Long) {
+ constexpr size_t kLargeStringSize = 1024 * 1024;
+
+ base::string16 in(kLargeStringSize, 0);
+ base::RandBytes(&in[0], kLargeStringSize);
+
+ base::string16 out;
+ ASSERT_TRUE(
+ mojo::test::SerializeAndDeserialize<mojom::BigString16>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+} // namespace string16_unittest
+} // namespace mojo_base
diff --git a/mojo/public/cpp/base/text_direction.typemap b/mojo/public/cpp/base/text_direction.typemap
new file mode 100644
index 0000000000..33a3d1159d
--- /dev/null
+++ b/mojo/public/cpp/base/text_direction.typemap
@@ -0,0 +1,15 @@
+# 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/public/mojom/base/text_direction.mojom"
+public_headers = [ "//base/i18n/rtl.h" ]
+traits_headers = [ "//mojo/public/cpp/base/text_direction_mojom_traits.h" ]
+sources = [
+ "//mojo/public/cpp/base/text_direction_mojom_traits.cc",
+ "//mojo/public/cpp/base/text_direction_mojom_traits.h",
+]
+public_deps = [
+ "//base:i18n",
+]
+type_mappings = [ "mojo_base.mojom.TextDirection=base::i18n::TextDirection" ]
diff --git a/mojo/public/cpp/base/text_direction_mojom_traits.cc b/mojo/public/cpp/base/text_direction_mojom_traits.cc
new file mode 100644
index 0000000000..5e1f6eb854
--- /dev/null
+++ b/mojo/public/cpp/base/text_direction_mojom_traits.cc
@@ -0,0 +1,43 @@
+// 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.
+
+#include "mojo/public/cpp/base/text_direction_mojom_traits.h"
+
+namespace mojo {
+
+// static
+mojo_base::mojom::TextDirection
+EnumTraits<mojo_base::mojom::TextDirection, base::i18n::TextDirection>::ToMojom(
+ base::i18n::TextDirection text_direction) {
+ switch (text_direction) {
+ case base::i18n::UNKNOWN_DIRECTION:
+ return mojo_base::mojom::TextDirection::UNKNOWN_DIRECTION;
+ case base::i18n::RIGHT_TO_LEFT:
+ return mojo_base::mojom::TextDirection::RIGHT_TO_LEFT;
+ case base::i18n::LEFT_TO_RIGHT:
+ return mojo_base::mojom::TextDirection::LEFT_TO_RIGHT;
+ }
+ NOTREACHED();
+ return mojo_base::mojom::TextDirection::UNKNOWN_DIRECTION;
+}
+
+// static
+bool EnumTraits<mojo_base::mojom::TextDirection, base::i18n::TextDirection>::
+ FromMojom(mojo_base::mojom::TextDirection input,
+ base::i18n::TextDirection* out) {
+ switch (input) {
+ case mojo_base::mojom::TextDirection::UNKNOWN_DIRECTION:
+ *out = base::i18n::UNKNOWN_DIRECTION;
+ return true;
+ case mojo_base::mojom::TextDirection::RIGHT_TO_LEFT:
+ *out = base::i18n::RIGHT_TO_LEFT;
+ return true;
+ case mojo_base::mojom::TextDirection::LEFT_TO_RIGHT:
+ *out = base::i18n::LEFT_TO_RIGHT;
+ return true;
+ }
+ return false;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/base/text_direction_mojom_traits.h b/mojo/public/cpp/base/text_direction_mojom_traits.h
new file mode 100644
index 0000000000..f59d45ca4c
--- /dev/null
+++ b/mojo/public/cpp/base/text_direction_mojom_traits.h
@@ -0,0 +1,25 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BASE_TEXT_DIRECTION_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_TEXT_DIRECTION_MOJOM_TRAITS_H_
+
+#include "base/component_export.h"
+#include "base/i18n/rtl.h"
+#include "mojo/public/mojom/base/text_direction.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_MOJOM)
+ EnumTraits<mojo_base::mojom::TextDirection, base::i18n::TextDirection> {
+ static mojo_base::mojom::TextDirection ToMojom(
+ base::i18n::TextDirection text_direction);
+ static bool FromMojom(mojo_base::mojom::TextDirection input,
+ base::i18n::TextDirection* out);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BASE_TEXT_DIRECTION_MOJOM_TRAITS_H_
diff --git a/mojo/public/cpp/base/text_direction_unittest.cc b/mojo/public/cpp/base/text_direction_unittest.cc
new file mode 100644
index 0000000000..02085fcfdc
--- /dev/null
+++ b/mojo/public/cpp/base/text_direction_unittest.cc
@@ -0,0 +1,31 @@
+// 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.
+
+#include "mojo/public/cpp/base/text_direction_mojom_traits.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo_base {
+namespace text_direction_unittest {
+
+TEST(TextDirectionTest, TextDirection) {
+ static constexpr base::i18n::TextDirection kTestDirections[] = {
+ base::i18n::LEFT_TO_RIGHT, base::i18n::RIGHT_TO_LEFT,
+ base::i18n::UNKNOWN_DIRECTION};
+
+ for (auto direction_in : kTestDirections) {
+ base::i18n::TextDirection direction_out;
+
+ mojo_base::mojom::TextDirection serialized_direction =
+ mojo::EnumTraits<mojo_base::mojom::TextDirection,
+ base::i18n::TextDirection>::ToMojom(direction_in);
+ ASSERT_TRUE((mojo::EnumTraits<
+ mojo_base::mojom::TextDirection,
+ base::i18n::TextDirection>::FromMojom(serialized_direction,
+ &direction_out)));
+ EXPECT_EQ(direction_in, direction_out);
+ }
+}
+
+} // namespace text_direction_unittest
+} // namespace mojo_base \ No newline at end of file
diff --git a/mojo/public/cpp/base/thread_priority.typemap b/mojo/public/cpp/base/thread_priority.typemap
new file mode 100644
index 0000000000..319818f4ca
--- /dev/null
+++ b/mojo/public/cpp/base/thread_priority.typemap
@@ -0,0 +1,13 @@
+# 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/public/mojom/base/thread_priority.mojom"
+public_headers = [ "//base/threading/platform_thread.h" ]
+traits_headers = [ "//mojo/public/cpp/base/thread_priority_mojom_traits.h" ]
+sources = [
+ "//mojo/public/cpp/base/thread_priority_mojom_traits.cc",
+ "//mojo/public/cpp/base/thread_priority_mojom_traits.h",
+]
+
+type_mappings = [ "mojo_base.mojom.ThreadPriority=base::ThreadPriority" ]
diff --git a/mojo/public/cpp/base/thread_priority_mojom_traits.cc b/mojo/public/cpp/base/thread_priority_mojom_traits.cc
new file mode 100644
index 0000000000..f8b88cd809
--- /dev/null
+++ b/mojo/public/cpp/base/thread_priority_mojom_traits.cc
@@ -0,0 +1,48 @@
+// 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.
+
+#include "mojo/public/cpp/base/thread_priority_mojom_traits.h"
+
+namespace mojo {
+
+// static
+mojo_base::mojom::ThreadPriority
+EnumTraits<mojo_base::mojom::ThreadPriority, base::ThreadPriority>::ToMojom(
+ base::ThreadPriority thread_priority) {
+ switch (thread_priority) {
+ case base::ThreadPriority::BACKGROUND:
+ return mojo_base::mojom::ThreadPriority::BACKGROUND;
+ case base::ThreadPriority::NORMAL:
+ return mojo_base::mojom::ThreadPriority::NORMAL;
+ case base::ThreadPriority::DISPLAY:
+ return mojo_base::mojom::ThreadPriority::DISPLAY;
+ case base::ThreadPriority::REALTIME_AUDIO:
+ return mojo_base::mojom::ThreadPriority::REALTIME_AUDIO;
+ }
+ NOTREACHED();
+ return mojo_base::mojom::ThreadPriority::BACKGROUND;
+}
+
+// static
+bool EnumTraits<mojo_base::mojom::ThreadPriority, base::ThreadPriority>::
+ FromMojom(mojo_base::mojom::ThreadPriority input,
+ base::ThreadPriority* out) {
+ switch (input) {
+ case mojo_base::mojom::ThreadPriority::BACKGROUND:
+ *out = base::ThreadPriority::BACKGROUND;
+ return true;
+ case mojo_base::mojom::ThreadPriority::NORMAL:
+ *out = base::ThreadPriority::NORMAL;
+ return true;
+ case mojo_base::mojom::ThreadPriority::DISPLAY:
+ *out = base::ThreadPriority::DISPLAY;
+ return true;
+ case mojo_base::mojom::ThreadPriority::REALTIME_AUDIO:
+ *out = base::ThreadPriority::REALTIME_AUDIO;
+ return true;
+ }
+ return false;
+}
+
+} // namespace mojo \ No newline at end of file
diff --git a/mojo/public/cpp/base/thread_priority_mojom_traits.h b/mojo/public/cpp/base/thread_priority_mojom_traits.h
new file mode 100644
index 0000000000..7f58e03f28
--- /dev/null
+++ b/mojo/public/cpp/base/thread_priority_mojom_traits.h
@@ -0,0 +1,24 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BASE_THREAD_PRIORITY_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_THREAD_PRIORITY_MOJOM_TRAITS_H_
+
+#include "base/component_export.h"
+#include "mojo/public/mojom/base/thread_priority.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_MOJOM)
+ EnumTraits<mojo_base::mojom::ThreadPriority, base::ThreadPriority> {
+ static mojo_base::mojom::ThreadPriority ToMojom(
+ base::ThreadPriority thread_priority);
+ static bool FromMojom(mojo_base::mojom::ThreadPriority input,
+ base::ThreadPriority* out);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BASE_THREAD_PRIORITY_MOJOM_TRAITS_H_ \ No newline at end of file
diff --git a/mojo/public/cpp/base/thread_priority_unittest.cc b/mojo/public/cpp/base/thread_priority_unittest.cc
new file mode 100644
index 0000000000..115d757ad6
--- /dev/null
+++ b/mojo/public/cpp/base/thread_priority_unittest.cc
@@ -0,0 +1,31 @@
+// 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.
+
+#include "mojo/public/cpp/base/thread_priority_mojom_traits.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo_base {
+namespace thread_priority_unittest {
+
+TEST(ThreadPriorityTest, ThreadPriority) {
+ static constexpr base::ThreadPriority kTestPriorities[] = {
+ base::ThreadPriority::BACKGROUND, base::ThreadPriority::NORMAL,
+ base::ThreadPriority::DISPLAY, base::ThreadPriority::REALTIME_AUDIO};
+
+ for (auto priority_in : kTestPriorities) {
+ base::ThreadPriority priority_out;
+
+ mojo_base::mojom::ThreadPriority serialized_priority =
+ mojo::EnumTraits<mojo_base::mojom::ThreadPriority,
+ base::ThreadPriority>::ToMojom(priority_in);
+ ASSERT_TRUE(
+ (mojo::EnumTraits<mojo_base::mojom::ThreadPriority,
+ base::ThreadPriority>::FromMojom(serialized_priority,
+ &priority_out)));
+ EXPECT_EQ(priority_in, priority_out);
+ }
+}
+
+} // namespace thread_priority_unittest
+} // namespace mojo_base \ No newline at end of file
diff --git a/mojo/public/cpp/base/time.typemap b/mojo/public/cpp/base/time.typemap
new file mode 100644
index 0000000000..ecd6a27fc7
--- /dev/null
+++ b/mojo/public/cpp/base/time.typemap
@@ -0,0 +1,17 @@
+# 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/public/mojom/base/time.mojom"
+public_headers = [ "//base/time/time.h" ]
+traits_headers = [ "//mojo/public/cpp/base/time_mojom_traits.h" ]
+public_deps = [
+ "//base",
+ "//mojo/public/cpp/base:shared_typemap_traits",
+]
+
+type_mappings = [
+ "mojo_base.mojom.Time=base::Time[copyable_pass_by_value]",
+ "mojo_base.mojom.TimeDelta=base::TimeDelta[copyable_pass_by_value]",
+ "mojo_base.mojom.TimeTicks=base::TimeTicks[copyable_pass_by_value]",
+]
diff --git a/mojo/public/cpp/base/time_mojom_traits.cc b/mojo/public/cpp/base/time_mojom_traits.cc
new file mode 100644
index 0000000000..1fc25d8144
--- /dev/null
+++ b/mojo/public/cpp/base/time_mojom_traits.cc
@@ -0,0 +1,49 @@
+// 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.
+
+#include "mojo/public/cpp/base/time_mojom_traits.h"
+
+namespace mojo {
+
+int64_t StructTraits<mojo_base::mojom::TimeDataView,
+ base::Time>::internal_value(const base::Time& time) {
+ return time.since_origin().InMicroseconds();
+}
+
+bool StructTraits<mojo_base::mojom::TimeDataView, base::Time>::Read(
+ mojo_base::mojom::TimeDataView data,
+ base::Time* time) {
+ *time =
+ base::Time() + base::TimeDelta::FromMicroseconds(data.internal_value());
+ return true;
+}
+
+int64_t
+StructTraits<mojo_base::mojom::TimeDeltaDataView,
+ base::TimeDelta>::microseconds(const base::TimeDelta& delta) {
+ return delta.InMicroseconds();
+}
+
+bool StructTraits<mojo_base::mojom::TimeDeltaDataView, base::TimeDelta>::Read(
+ mojo_base::mojom::TimeDeltaDataView data,
+ base::TimeDelta* delta) {
+ *delta = base::TimeDelta::FromMicroseconds(data.microseconds());
+ return true;
+}
+
+int64_t
+StructTraits<mojo_base::mojom::TimeTicksDataView,
+ base::TimeTicks>::internal_value(const base::TimeTicks& time) {
+ return time.since_origin().InMicroseconds();
+}
+
+bool StructTraits<mojo_base::mojom::TimeTicksDataView, base::TimeTicks>::Read(
+ mojo_base::mojom::TimeTicksDataView data,
+ base::TimeTicks* time) {
+ *time = base::TimeTicks() +
+ base::TimeDelta::FromMicroseconds(data.internal_value());
+ return true;
+}
+
+} // namespace mojo \ No newline at end of file
diff --git a/mojo/public/cpp/base/time_mojom_traits.h b/mojo/public/cpp/base/time_mojom_traits.h
new file mode 100644
index 0000000000..213c6e2169
--- /dev/null
+++ b/mojo/public/cpp/base/time_mojom_traits.h
@@ -0,0 +1,42 @@
+// 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_BASE_TIME_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_TIME_MOJOM_TRAITS_H_
+
+#include "base/component_export.h"
+#include "base/time/time.h"
+#include "mojo/public/mojom/base/time.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_SHARED_TRAITS)
+ StructTraits<mojo_base::mojom::TimeDataView, base::Time> {
+ static int64_t internal_value(const base::Time& time);
+
+ static bool Read(mojo_base::mojom::TimeDataView data, base::Time* time);
+};
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_SHARED_TRAITS)
+ StructTraits<mojo_base::mojom::TimeDeltaDataView, base::TimeDelta> {
+ static int64_t microseconds(const base::TimeDelta& delta);
+
+ static bool Read(mojo_base::mojom::TimeDeltaDataView data,
+ base::TimeDelta* delta);
+};
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_SHARED_TRAITS)
+ StructTraits<mojo_base::mojom::TimeTicksDataView, base::TimeTicks> {
+ static int64_t internal_value(const base::TimeTicks& time);
+
+ static bool Read(mojo_base::mojom::TimeTicksDataView data,
+ base::TimeTicks* time);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BASE_TIME_MOJOM_TRAITS_H_
diff --git a/mojo/public/cpp/base/time_unittest.cc b/mojo/public/cpp/base/time_unittest.cc
new file mode 100644
index 0000000000..9ab3910723
--- /dev/null
+++ b/mojo/public/cpp/base/time_unittest.cc
@@ -0,0 +1,38 @@
+// 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.
+
+#include "mojo/public/cpp/base/time_mojom_traits.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/mojom/base/time.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo_base {
+namespace time_unittest {
+
+TEST(TimeTest, Time) {
+ base::Time in = base::Time::Now();
+ base::Time out;
+
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Time>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+TEST(TimeTest, TimeDelta) {
+ base::TimeDelta in = base::TimeDelta::FromDays(123);
+ base::TimeDelta out;
+
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::TimeDelta>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+TEST(TimeTest, TimeTicks) {
+ base::TimeTicks in = base::TimeTicks::Now();
+ base::TimeTicks out;
+
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::TimeTicks>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+} // namespace time_unittest
+} // namespace mojo_base \ No newline at end of file
diff --git a/mojo/public/cpp/base/typemaps.gni b/mojo/public/cpp/base/typemaps.gni
new file mode 100644
index 0000000000..668dc8edb9
--- /dev/null
+++ b/mojo/public/cpp/base/typemaps.gni
@@ -0,0 +1,24 @@
+# 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.
+
+typemaps = [
+ "//mojo/public/cpp/base/big_buffer.typemap",
+ "//mojo/public/cpp/base/big_string.typemap",
+ "//mojo/public/cpp/base/file_error.typemap",
+ "//mojo/public/cpp/base/file_info.typemap",
+ "//mojo/public/cpp/base/file_path.typemap",
+ "//mojo/public/cpp/base/file.typemap",
+ "//mojo/public/cpp/base/read_only_buffer.typemap",
+ "//mojo/public/cpp/base/memory_allocator_dump_cross_process_uid.typemap",
+ "//mojo/public/cpp/base/process_id.typemap",
+ "//mojo/public/cpp/base/ref_counted_memory.typemap",
+ "//mojo/public/cpp/base/shared_memory.typemap",
+ "//mojo/public/cpp/base/string16.typemap",
+ "//mojo/public/cpp/base/logfont_win.typemap",
+ "//mojo/public/cpp/base/text_direction.typemap",
+ "//mojo/public/cpp/base/thread_priority.typemap",
+ "//mojo/public/cpp/base/time.typemap",
+ "//mojo/public/cpp/base/unguessable_token.typemap",
+ "//mojo/public/cpp/base/values.typemap",
+]
diff --git a/mojo/public/cpp/base/unguessable_token.typemap b/mojo/public/cpp/base/unguessable_token.typemap
new file mode 100644
index 0000000000..056f14612c
--- /dev/null
+++ b/mojo/public/cpp/base/unguessable_token.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/public/mojom/base/unguessable_token.mojom"
+public_headers = [ "//base/unguessable_token.h" ]
+traits_headers = [ "//mojo/public/cpp/base/unguessable_token_mojom_traits.h" ]
+sources = [
+ "//mojo/public/cpp/base/unguessable_token_mojom_traits.cc",
+ "//mojo/public/cpp/base/unguessable_token_mojom_traits.h",
+]
+
+type_mappings = [ "mojo_base.mojom.UnguessableToken=base::UnguessableToken" ]
diff --git a/mojo/public/cpp/base/unguessable_token_mojom_traits.cc b/mojo/public/cpp/base/unguessable_token_mojom_traits.cc
new file mode 100644
index 0000000000..153d0a46b1
--- /dev/null
+++ b/mojo/public/cpp/base/unguessable_token_mojom_traits.cc
@@ -0,0 +1,25 @@
+// 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.
+
+#include "mojo/public/cpp/base/unguessable_token_mojom_traits.h"
+
+namespace mojo {
+
+// static
+bool StructTraits<mojo_base::mojom::UnguessableTokenDataView,
+ base::UnguessableToken>::
+ Read(mojo_base::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;
+}
+
+} // namespace mojo \ No newline at end of file
diff --git a/mojo/public/cpp/base/unguessable_token_mojom_traits.h b/mojo/public/cpp/base/unguessable_token_mojom_traits.h
new file mode 100644
index 0000000000..b23bd70cc0
--- /dev/null
+++ b/mojo/public/cpp/base/unguessable_token_mojom_traits.h
@@ -0,0 +1,37 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BASE_UNGUESSABLE_TOKEN_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_UNGUESSABLE_TOKEN_MOJOM_TRAITS_H_
+
+#include "base/component_export.h"
+#include "base/unguessable_token.h"
+#include "mojo/public/mojom/base/unguessable_token.mojom-shared.h"
+
+namespace mojo {
+
+// 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 COMPONENT_EXPORT(MOJO_BASE_MOJOM)
+ StructTraits<mojo_base::mojom::UnguessableTokenDataView,
+ base::UnguessableToken> {
+ 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(mojo_base::mojom::UnguessableTokenDataView data,
+ base::UnguessableToken* out);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BASE_UNGUESSABLE_TOKEN_MOJOM_TRAITS_H_ \ No newline at end of file
diff --git a/mojo/public/cpp/base/unguessable_token_unittest.cc b/mojo/public/cpp/base/unguessable_token_unittest.cc
new file mode 100644
index 0000000000..c752178c2b
--- /dev/null
+++ b/mojo/public/cpp/base/unguessable_token_unittest.cc
@@ -0,0 +1,23 @@
+// 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.
+
+#include "mojo/public/cpp/base/unguessable_token_mojom_traits.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/mojom/base/unguessable_token.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo_base {
+namespace unguessable_token_unittest {
+
+TEST(UnguessableTokenTest, UnguessableToken) {
+ base::UnguessableToken in = base::UnguessableToken::Create();
+ base::UnguessableToken out;
+
+ ASSERT_TRUE(
+ mojo::test::SerializeAndDeserialize<mojom::UnguessableToken>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+} // namespace unguessable_token_unittest
+} // namespace mojo_base \ No newline at end of file
diff --git a/mojo/public/cpp/base/values.typemap b/mojo/public/cpp/base/values.typemap
new file mode 100644
index 0000000000..8dfaa67538
--- /dev/null
+++ b/mojo/public/cpp/base/values.typemap
@@ -0,0 +1,16 @@
+# 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.
+
+mojom = "//mojo/public/mojom/base/values.mojom"
+public_headers = [ "//base/values.h" ]
+traits_headers = [ "//mojo/public/cpp/base/values_mojom_traits.h" ]
+public_deps = [
+ "//base",
+ "//mojo/public/cpp/base:shared_typemap_traits",
+]
+type_mappings = [
+ "mojo_base.mojom.Value=base::Value[move_only]",
+ "mojo_base.mojom.DictionaryValue=base::Value[move_only]",
+ "mojo_base.mojom.ListValue=base::Value[move_only]",
+]
diff --git a/mojo/public/cpp/base/values_mojom_traits.cc b/mojo/public/cpp/base/values_mojom_traits.cc
new file mode 100644
index 0000000000..2593066855
--- /dev/null
+++ b/mojo/public/cpp/base/values_mojom_traits.cc
@@ -0,0 +1,94 @@
+// 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.
+
+#include "mojo/public/cpp/base/values_mojom_traits.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/strings/string_piece.h"
+
+namespace mojo {
+
+bool StructTraits<mojo_base::mojom::DictionaryValueDataView, base::Value>::Read(
+ mojo_base::mojom::DictionaryValueDataView data,
+ base::Value* value_out) {
+ mojo::MapDataView<mojo::StringDataView, mojo_base::mojom::ValueDataView> view;
+ data.GetStorageDataView(&view);
+ std::vector<base::Value::DictStorage::value_type> dict_storage;
+ dict_storage.reserve(view.size());
+ for (size_t i = 0; i < view.size(); ++i) {
+ base::StringPiece key;
+ auto value = std::make_unique<base::Value>();
+ if (!view.keys().Read(i, &key) || !view.values().Read(i, value.get()))
+ return false;
+ dict_storage.emplace_back(key.as_string(), std::move(value));
+ }
+ *value_out = base::Value(base::Value::DictStorage(std::move(dict_storage),
+ base::KEEP_LAST_OF_DUPES));
+ return true;
+}
+
+bool StructTraits<mojo_base::mojom::ListValueDataView, base::Value>::Read(
+ mojo_base::mojom::ListValueDataView data,
+ base::Value* value_out) {
+ mojo::ArrayDataView<mojo_base::mojom::ValueDataView> view;
+ data.GetStorageDataView(&view);
+ base::Value::ListStorage list_storage(view.size());
+ for (size_t i = 0; i < view.size(); ++i) {
+ if (!view.Read(i, &list_storage[i]))
+ return false;
+ }
+ *value_out = base::Value(std::move(list_storage));
+ return true;
+}
+
+bool UnionTraits<mojo_base::mojom::ValueDataView, base::Value>::Read(
+ mojo_base::mojom::ValueDataView data,
+ base::Value* value_out) {
+ switch (data.tag()) {
+ case mojo_base::mojom::ValueDataView::Tag::NULL_VALUE: {
+ *value_out = base::Value();
+ return true;
+ }
+ case mojo_base::mojom::ValueDataView::Tag::BOOL_VALUE: {
+ *value_out = base::Value(data.bool_value());
+ return true;
+ }
+ case mojo_base::mojom::ValueDataView::Tag::INT_VALUE: {
+ *value_out = base::Value(data.int_value());
+ return true;
+ }
+ case mojo_base::mojom::ValueDataView::Tag::DOUBLE_VALUE: {
+ *value_out = base::Value(data.double_value());
+ return true;
+ }
+ case mojo_base::mojom::ValueDataView::Tag::STRING_VALUE: {
+ base::StringPiece string_piece;
+ if (!data.ReadStringValue(&string_piece))
+ return false;
+ *value_out = base::Value(string_piece);
+ return true;
+ }
+ case mojo_base::mojom::ValueDataView::Tag::BINARY_VALUE: {
+ mojo::ArrayDataView<uint8_t> binary_data_view;
+ data.GetBinaryValueDataView(&binary_data_view);
+ const char* data_pointer =
+ reinterpret_cast<const char*>(binary_data_view.data());
+ base::Value::BlobStorage blob_storage(
+ data_pointer, data_pointer + binary_data_view.size());
+ *value_out = base::Value(std::move(blob_storage));
+ return true;
+ }
+ case mojo_base::mojom::ValueDataView::Tag::DICTIONARY_VALUE: {
+ return data.ReadDictionaryValue(value_out);
+ }
+ case mojo_base::mojom::ValueDataView::Tag::LIST_VALUE: {
+ return data.ReadListValue(value_out);
+ }
+ }
+ return false;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/base/values_mojom_traits.h b/mojo/public/cpp/base/values_mojom_traits.h
new file mode 100644
index 0000000000..ee1dbee510
--- /dev/null
+++ b/mojo/public/cpp/base/values_mojom_traits.h
@@ -0,0 +1,130 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BASE_VALUES_MOJOM_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BASE_VALUES_MOJOM_TRAITS_H_
+
+#include <vector>
+
+#include "base/component_export.h"
+#include "base/values.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"
+#include "mojo/public/mojom/base/values.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct MapTraits<base::Value> {
+ using Key = std::string;
+ using Value = base::Value;
+ using Iterator = base::Value::const_dict_iterator_proxy::const_iterator;
+
+ static size_t GetSize(const base::Value& input) {
+ DCHECK(input.is_dict());
+ return static_cast<const base::DictionaryValue&>(input).size();
+ }
+
+ static Iterator GetBegin(const base::Value& input) {
+ DCHECK(input.is_dict());
+ return input.DictItems().cbegin();
+ }
+
+ static void AdvanceIterator(Iterator& iterator) { ++iterator; }
+
+ static const Key& GetKey(const Iterator& iterator) { return iterator->first; }
+
+ static const Value& GetValue(const Iterator& iterator) {
+ return iterator->second;
+ }
+};
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_SHARED_TRAITS)
+ StructTraits<mojo_base::mojom::DictionaryValueDataView, base::Value> {
+ static const base::Value& storage(const base::Value& value) {
+ DCHECK(value.is_dict());
+ return value;
+ }
+
+ static bool Read(mojo_base::mojom::DictionaryValueDataView data,
+ base::Value* value);
+};
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_SHARED_TRAITS)
+ StructTraits<mojo_base::mojom::ListValueDataView, base::Value> {
+ static const base::Value::ListStorage& storage(const base::Value& value) {
+ DCHECK(value.is_list());
+ return value.GetList();
+ }
+
+ static bool Read(mojo_base::mojom::ListValueDataView data,
+ base::Value* value);
+};
+
+template <>
+struct COMPONENT_EXPORT(MOJO_BASE_SHARED_TRAITS)
+ UnionTraits<mojo_base::mojom::ValueDataView, base::Value> {
+ static mojo_base::mojom::ValueDataView::Tag GetTag(const base::Value& data) {
+ switch (data.type()) {
+ case base::Value::Type::NONE:
+ return mojo_base::mojom::ValueDataView::Tag::NULL_VALUE;
+ case base::Value::Type::BOOLEAN:
+ return mojo_base::mojom::ValueDataView::Tag::BOOL_VALUE;
+ case base::Value::Type::INTEGER:
+ return mojo_base::mojom::ValueDataView::Tag::INT_VALUE;
+ case base::Value::Type::DOUBLE:
+ return mojo_base::mojom::ValueDataView::Tag::DOUBLE_VALUE;
+ case base::Value::Type::STRING:
+ return mojo_base::mojom::ValueDataView::Tag::STRING_VALUE;
+ case base::Value::Type::BINARY:
+ return mojo_base::mojom::ValueDataView::Tag::BINARY_VALUE;
+ case base::Value::Type::DICTIONARY:
+ return mojo_base::mojom::ValueDataView::Tag::DICTIONARY_VALUE;
+ case base::Value::Type::LIST:
+ return mojo_base::mojom::ValueDataView::Tag::LIST_VALUE;
+ }
+ NOTREACHED();
+ return mojo_base::mojom::ValueDataView::Tag::NULL_VALUE;
+ }
+
+ static uint8_t null_value(const base::Value& value) { return 0; }
+
+ static bool bool_value(const base::Value& value) { return value.GetBool(); }
+
+ static int32_t int_value(const base::Value& value) { return value.GetInt(); }
+
+ static double double_value(const base::Value& value) {
+ return value.GetDouble();
+ }
+
+ static base::StringPiece string_value(const base::Value& value) {
+ return value.GetString();
+ }
+
+ static base::span<const uint8_t> binary_value(const base::Value& value) {
+ // TODO(dcheng): Change base::Value::BlobStorage to uint8_t.
+ return base::make_span(
+ reinterpret_cast<const uint8_t*>(value.GetBlob().data()),
+ value.GetBlob().size());
+ }
+
+ static const base::Value& list_value(const base::Value& value) {
+ DCHECK(value.is_list());
+ return value;
+ }
+ static const base::Value& dictionary_value(const base::Value& value) {
+ DCHECK(value.is_dict());
+ return value;
+ }
+
+ static bool Read(mojo_base::mojom::ValueDataView view,
+ base::Value* value_out);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BASE_VALUES_MOJOM_TRAITS_H_
diff --git a/mojo/public/cpp/base/values_unittest.cc b/mojo/public/cpp/base/values_unittest.cc
new file mode 100644
index 0000000000..169572bb34
--- /dev/null
+++ b/mojo/public/cpp/base/values_unittest.cc
@@ -0,0 +1,160 @@
+// 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.
+
+#include <string>
+#include <utility>
+
+#include "base/test/gtest_util.h"
+#include "base/values.h"
+#include "mojo/public/cpp/base/values_mojom_traits.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "mojo/public/mojom/base/values.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo_base {
+
+TEST(ValuesStructTraitsTest, NullValue) {
+ base::Value in;
+ base::Value out;
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Value>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+TEST(ValuesStructTraitsTest, BoolValue) {
+ static constexpr bool kTestCases[] = {true, false};
+ for (auto& test_case : kTestCases) {
+ base::Value in(test_case);
+ base::Value out;
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Value>(&in, &out));
+ EXPECT_EQ(in, out);
+ }
+}
+
+TEST(ValuesStructTraitsTest, IntValue) {
+ static constexpr int kTestCases[] = {0, -1, 1,
+ std::numeric_limits<int>::min(),
+ std::numeric_limits<int>::max()};
+ for (auto& test_case : kTestCases) {
+ base::Value in(test_case);
+ base::Value out;
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Value>(&in, &out));
+ EXPECT_EQ(in, out);
+ }
+}
+
+TEST(ValuesStructTraitsTest, DoubleValue) {
+ static constexpr double kTestCases[] = {-0.0,
+ +0.0,
+ -1.0,
+ +1.0,
+ std::numeric_limits<double>::min(),
+ std::numeric_limits<double>::max()};
+ for (auto& test_case : kTestCases) {
+ base::Value in(test_case);
+ base::Value out;
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Value>(&in, &out));
+ EXPECT_EQ(in, out);
+ }
+}
+
+TEST(ValuesStructTraitsTest, StringValue) {
+ static constexpr const char* kTestCases[] = {
+ "", "ascii",
+ // 🎆: Unicode FIREWORKS
+ "\xf0\x9f\x8e\x86",
+ };
+ for (auto* test_case : kTestCases) {
+ base::Value in(test_case);
+ base::Value out;
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Value>(&in, &out));
+ EXPECT_EQ(in, out);
+ }
+}
+
+TEST(ValuesStructTraitsTest, BinaryValue) {
+ std::vector<char> kBinaryData = {'\x00', '\x80', '\xff', '\x7f', '\x01'};
+ base::Value in(std::move(kBinaryData));
+ base::Value out;
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Value>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+TEST(ValuesStructTraitsTest, DictionaryValue) {
+ // Note: here and below, it would be nice to use an initializer list, but
+ // move-only types and initializer lists don't mix. Initializer lists can't be
+ // modified: thus it's not possible to move.
+ std::vector<base::Value::DictStorage::value_type> storage;
+ storage.emplace_back("null", std::make_unique<base::Value>());
+ storage.emplace_back("bool", std::make_unique<base::Value>(false));
+ storage.emplace_back("int", std::make_unique<base::Value>(0));
+ storage.emplace_back("double", std::make_unique<base::Value>(0.0));
+ storage.emplace_back("string", std::make_unique<base::Value>("0"));
+ storage.emplace_back(
+ "binary", std::make_unique<base::Value>(base::Value::BlobStorage({0})));
+ storage.emplace_back(
+ "dictionary", std::make_unique<base::Value>(base::Value::DictStorage()));
+ storage.emplace_back(
+ "list", std::make_unique<base::Value>(base::Value::ListStorage()));
+
+ base::Value in(
+ base::Value::DictStorage(std::move(storage), base::KEEP_LAST_OF_DUPES));
+ base::Value out;
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Value>(&in, &out));
+ EXPECT_EQ(in, out);
+
+ ASSERT_TRUE(
+ mojo::test::SerializeAndDeserialize<mojom::DictionaryValue>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+TEST(ValuesStructTraitsTest, SerializeInvalidDictionaryValue) {
+ base::Value in;
+ ASSERT_FALSE(in.is_dict());
+
+ base::Value out;
+ EXPECT_DCHECK_DEATH(
+ mojo::test::SerializeAndDeserialize<mojom::DictionaryValue>(&in, &out));
+}
+
+TEST(ValuesStructTraitsTest, ListValue) {
+ base::Value::ListStorage storage;
+ storage.emplace_back();
+ storage.emplace_back(false);
+ storage.emplace_back(0);
+ storage.emplace_back(0.0);
+ storage.emplace_back("0");
+ storage.emplace_back(base::Value::BlobStorage({0}));
+ storage.emplace_back(base::Value::DictStorage());
+ storage.emplace_back(base::Value::ListStorage());
+ base::Value in(std::move(storage));
+ base::Value out;
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Value>(&in, &out));
+ EXPECT_EQ(in, out);
+
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::ListValue>(&in, &out));
+ EXPECT_EQ(in, out);
+}
+
+TEST(ValuesStructTraitsTest, SerializeInvalidListValue) {
+ base::Value in;
+ ASSERT_FALSE(in.is_dict());
+
+ base::Value out;
+ EXPECT_DCHECK_DEATH(
+ mojo::test::SerializeAndDeserialize<mojom::ListValue>(&in, &out));
+}
+
+// A deeply nested base::Value should trigger a deserialization error.
+TEST(ValuesStructTraitsTest, DeeplyNestedValue) {
+ base::Value in;
+ for (int i = 0; i < 100; ++i) {
+ base::Value::ListStorage storage;
+ storage.emplace_back(std::move(in));
+ in = base::Value(std::move(storage));
+ }
+ base::Value out;
+ ASSERT_FALSE(mojo::test::SerializeAndDeserialize<mojom::Value>(&in, &out));
+}
+
+} // namespace mojo_base
diff --git a/mojo/public/cpp/bindings/BUILD.gn b/mojo/public/cpp/bindings/BUILD.gn
index bd87965fc8..27152835ac 100644
--- a/mojo/public/cpp/bindings/BUILD.gn
+++ b/mojo/public/cpp/bindings/BUILD.gn
@@ -2,112 +2,66 @@
# 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"
+import("//build/buildflag_header.gni")
+import("//build/config/nacl/config.gni")
+import("//tools/ipc_fuzzer/ipc_fuzzer.gni")
-component("bindings") {
+declare_args() {
+ enable_mojo_tracing = false
+}
+
+buildflag_header("mojo_buildflags") {
+ header = "mojo_buildflags.h"
+
+ flags = [ "MOJO_TRACE_ENABLED=$enable_mojo_tracing" ]
+}
+
+# Headers and sources which generated bindings can depend upon. No need for
+# other targets to depend on this directly: just use the "bindings" target.
+component("bindings_base") {
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_span.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",
+ "enum_traits.h",
+ "equals_traits.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.cc",
"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/handle_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.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.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/unserialized_message_context.cc",
+ "lib/unserialized_message_context.h",
"lib/validate_params.h",
"lib/validation_context.cc",
"lib/validation_context.h",
@@ -118,47 +72,121 @@ component("bindings") {
"map.h",
"map_data_view.h",
"map_traits.h",
+ "map_traits_flat_map.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",
+ "struct_ptr.h",
+ "struct_traits.h",
+ "type_converter.h",
+ "union_traits.h",
+ ]
+
+ defines = [ "IS_MOJO_CPP_BINDINGS_BASE_IMPL" ]
+
+ public_deps = [
+ ":mojo_buildflags",
+ "//base",
+ "//mojo/public/cpp/system",
+ ]
+
+ if (enable_ipc_fuzzer) {
+ all_dependent_configs = [ "//tools/ipc_fuzzer:ipc_fuzzer_config" ]
+ }
+}
+
+component("bindings") {
+ sources = [
+ "associated_binding.h",
+ "associated_binding_set.h",
+ "associated_interface_ptr.h",
+ "associated_interface_ptr_info.h",
+ "associated_interface_request.h",
+ "binding.h",
+ "binding_set.h",
+ "bindings_export.h",
+ "callback_helpers.h",
+ "connection_error_callback.h",
+ "connector.h",
+ "filter_chain.h",
+ "interface_endpoint_client.h",
+ "interface_endpoint_controller.h",
+ "interface_ptr.h",
+ "interface_ptr_info.h",
+ "interface_ptr_set.h",
+ "interface_request.h",
+ "lib/associated_binding.cc",
+ "lib/associated_interface_ptr.cc",
+ "lib/associated_interface_ptr_state.cc",
+ "lib/associated_interface_ptr_state.h",
+ "lib/binding_state.cc",
+ "lib/binding_state.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/filter_chain.cc",
+ "lib/interface_endpoint_client.cc",
+ "lib/interface_ptr_state.cc",
+ "lib/interface_ptr_state.h",
+ "lib/interface_serialization.h",
+ "lib/multiplex_router.cc",
+ "lib/multiplex_router.h",
+ "lib/native_enum_data.h",
+ "lib/native_enum_serialization.h",
+ "lib/native_struct_serialization.cc",
+ "lib/native_struct_serialization.h",
+ "lib/pipe_control_message_handler.cc",
+ "lib/pipe_control_message_proxy.cc",
+ "lib/sequence_local_sync_event_watcher.cc",
+ "lib/sync_call_restrictions.cc",
+ "lib/sync_event_watcher.cc",
+ "lib/sync_handle_registry.cc",
+ "lib/sync_handle_watcher.cc",
+ "lib/task_runner_helper.cc",
+ "lib/task_runner_helper.h",
+ "native_enum.h",
+ "pipe_control_message_handler.h",
+ "pipe_control_message_handler_delegate.h",
+ "pipe_control_message_proxy.h",
+ "raw_ptr_impl_ref_traits.h",
+ "sequence_local_sync_event_watcher.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",
]
+ if (enable_ipc_fuzzer && !is_nacl_nonsfi) {
+ sources += [
+ "lib/message_dumper.cc",
+ "message_dumper.h",
+ ]
+ }
+
public_deps = [
+ ":bindings_base",
":struct_traits",
"//base",
+ "//ipc:message_support",
"//ipc:param_traits",
"//mojo/public/cpp/system",
+ "//mojo/public/interfaces/bindings",
]
deps = [
- "//base",
- "//mojo/public/interfaces/bindings:bindings__generator",
- "//mojo/public/interfaces/bindings:bindings_shared__generator",
+ "//ipc:native_handle_type_converters",
]
defines = [ "MOJO_CPP_BINDINGS_IMPLEMENTATION" ]
@@ -166,8 +194,13 @@ component("bindings") {
source_set("struct_traits") {
sources = [
+ "array_traits.h",
"enum_traits.h",
+ "lib/template_util.h",
+ "map_traits.h",
+ "string_traits.h",
"struct_traits.h",
+ "union_traits.h",
]
}
@@ -186,9 +219,10 @@ if (!is_ios) {
public_deps = [
":bindings",
- "//third_party/WebKit/Source/wtf",
+ "//third_party/blink/renderer/platform:platform_export",
+ "//third_party/blink/renderer/platform/wtf",
]
- public_configs = [ "//third_party/WebKit/Source:config" ]
+ public_configs = [ "//third_party/blink/renderer:config" ]
}
}
diff --git a/mojo/public/cpp/bindings/DEPS b/mojo/public/cpp/bindings/DEPS
deleted file mode 100644
index 36eba448e8..0000000000
--- a/mojo/public/cpp/bindings/DEPS
+++ /dev/null
@@ -1,3 +0,0 @@
-include_rules = [
- "+third_party/WebKit/Source/wtf",
-]
diff --git a/mojo/public/cpp/bindings/README.md b/mojo/public/cpp/bindings/README.md
index b37267a338..89e5d95b15 100644
--- a/mojo/public/cpp/bindings/README.md
+++ b/mojo/public/cpp/bindings/README.md
@@ -1,19 +1,23 @@
-# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo C++ Bindings API
-This document is a subset of the [Mojo documentation](/mojo).
+# Mojo C++ Bindings API
+This document is a subset of the [Mojo documentation](/mojo/README.md).
[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.
+[C++ System API](/mojo/public/cpp/system/README.md) 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/README.md), 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/).
+[//mojo/public/cpp/bindings](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/README.md).
+
+For a simplified guide targeted at Chromium developers, see [this
+link](/docs/mojo_guide.md).
## Getting Started
@@ -47,6 +51,12 @@ mojom("interfaces") {
}
```
+Ensure that any target that needs this interface depends on it, e.g. with a line like:
+
+```
+ deps += [ '//services/db/public/interfaces' ]
+```
+
If we then build this target:
```
@@ -57,8 +67,8 @@ 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
+out/gen/services/db/public/interfaces/db.mojom.cc
+out/gen/services/db/public/interfaces/db.mojom.h
```
You can include the above generated header in your sources in order to use the
@@ -143,58 +153,49 @@ routed to some implementation which will **bind** it. The `InterfaceRequest<T>`
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)
+![Diagram illustrating InterfacePtr and InterfaceRequest on either end of a message pipe](https://docs.google.com/drawings/d/1_Ocprq7EGgTKcSE_WlOn_RBfXcr5C3FJyIbWhwzwNX8/pub?w=608&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:
+One way to do this is by manually creating a pipe and wrapping each end with a
+strongly-typed object:
``` 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));
+sample::mojom::LoggerPtr logger(
+ sample::mojom::LoggerPtrInfo(std::move(pipe.handle0), 0));
+sample::mojom::LoggerRequest request(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)
+That's pretty verbose, but the C++ Bindings library provides a more convenient
+way 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);
+auto request = mojo::MakeRequest(&logger);
```
-and the `InterfaceRequest<T>` constructor can also take an explicit
-`InterfacePtr<T>*` output argument:
-
-``` cpp
-sample::mojom::LoggerPtr logger;
-sample::mojom::LoggerRequest request(&logger);
-```
-
-Both of these last two snippets are equivalent to the first one.
+This second snippet is 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<Logger>`. This is
similar to an `InterfaceRequest<T>` in that it merely holds onto a pipe handle
and cannot actually read or write messages on the pipe. Both this type and
-`InterfaceRequest<T>` are safe to move freely from thread to thread, whereas a
-bound `InterfacePtr<T>` is bound to a single thread.
+`InterfaceRequest<T>` are safe to move freely from sequence to sequence, whereas
+a bound `InterfacePtr<T>` is bound to a single sequence.
An `InterfacePtr<T>` may be unbound by calling its `PassInterface()` method,
which returns a new `InterfacePtrInfo<T>`. Conversely, an `InterfacePtr<T>` may
bind (and thus take ownership of) an `InterfacePtrInfo<T>` so that interface
calls can be made on the pipe.
-The thread-bound nature of `InterfacePtr<T>` is necessary to support safe
+The sequence-bound nature of `InterfacePtr<T>` is necessary to support safe
dispatch of its [message responses](#Receiving-Responses) and
[connection error notifications](#Connection-Errors).
***
@@ -210,7 +211,7 @@ 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)
+![Diagram illustrating a message traveling on a pipe from LoggerPtr to LoggerRequest](https://docs.google.com/drawings/d/11vnOpNP3UBLlWg4KplQuIU3r_e1XqwDFETD-O_bV-2w/pub?w=635&h=112)
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
@@ -260,7 +261,7 @@ class LoggerImpl : public sample::mojom::Logger {
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:
+sequence:
``` cpp
LoggerImpl impl(std::move(request));
@@ -277,7 +278,7 @@ motion by the above line of code:
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)
+![Diagram illustrating the progression of binding a request, reading a pending message, and dispatching it](https://docs.google.com/drawings/d/1F2VvfoOINGuNibomqeEU8KekYCtxYVFC00146CFGGQY/pub?w=550&h=500)
As a result, our implementation will eventually log the client's `"Hello!"`
message via `LOG(ERROR)`.
@@ -314,9 +315,9 @@ class Logger {
virtual void Log(const std::string& message) = 0;
- using GetTailCallback = base::Callback<void(const std::string& message)>;
+ using GetTailCallback = base::OnceCallback<void(const std::string& message)>;
- virtual void GetTail(const GetTailCallback& callback) = 0;
+ virtual void GetTail(GetTailCallback callback) = 0;
}
} // namespace mojom
@@ -344,8 +345,8 @@ class LoggerImpl : public sample::mojom::Logger {
lines_.push_back(message);
}
- void GetTail(const GetTailCallback& callback) override {
- callback.Run(lines_.back());
+ void GetTail(GetTailCallback callback) override {
+ std::move(callback).Run(lines_.back());
}
private:
@@ -363,7 +364,7 @@ void OnGetTail(const std::string& message) {
LOG(ERROR) << "Tail was: " << message;
}
-logger->GetTail(base::Bind(&OnGetTail));
+logger->GetTail(base::BindOnce(&OnGetTail));
```
Behind the scenes, the implementation-side callback is actually serializing the
@@ -375,10 +376,18 @@ 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.
+If a pipe is disconnected, both endpoints will be able to observe the connection
+error (unless the disconnection is caused by closing/destroying an endpoint, in
+which case that endpoint won't get such a notification). If there are remaining
+incoming messages for an endpoint on disconnection, the connection error won't
+be triggered until the messages are drained.
+
+Pipe disconnecition may be caused by:
+* Mojo system-level causes: process terminated, resource exhausted, etc.
+* The bindings close the pipe due to a validation error when processing a
+ received message.
+* The peer endpoint is closed. For example, the remote side is a bound
+ `mojo::InterfacePtr<T>` and it is destroyed.
Regardless of the underlying cause, when a connection error is encountered on
a binding endpoint, that endpoint's **connection error handler** (if set) is
@@ -398,7 +407,7 @@ invocation:
``` cpp
sample::mojom::LoggerPtr logger;
LoggerImpl impl(mojo::MakeRequest(&logger));
-impl.set_connection_error_handler(base::Bind([] { LOG(ERROR) << "Bye."; }));
+impl.set_connection_error_handler(base::BindOnce([] { LOG(ERROR) << "Bye."; }));
logger->Log("OK cool");
logger.reset(); // Closes the client end.
```
@@ -415,7 +424,7 @@ handler within its constructor:
LoggerImpl::LoggerImpl(sample::mojom::LoggerRequest request)
: binding_(this, std::move(request)) {
binding_.set_connection_error_handler(
- base::Bind(&LoggerImpl::OnError, base::Unretained(this)));
+ base::BindOnce(&LoggerImpl::OnError, base::Unretained(this)));
}
void LoggerImpl::OnError() {
@@ -427,6 +436,39 @@ void LoggerImpl::OnError() {
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 Endpoint Lifetime and Callbacks
+Once a `mojo::InterfacePtr<T>` is destroyed, it is guaranteed that pending
+callbacks as well as the connection error handler (if registered) won't be
+called.
+
+Once a `mojo::Binding<T>` is destroyed, it is guaranteed that no more method
+calls are dispatched to the implementation and the connection error handler (if
+registered) won't be called.
+
+### Best practices for dealing with process crashes and callbacks
+A common situation when calling mojo interface methods that take a callback is
+that the caller wants to know if the other endpoint is torn down (e.g. because
+of a crash). In that case, the consumer usually wants to know if the response
+callback won't be run. There are different solutions for this problem, depending
+on how the `InterfacePtr<T>` is held:
+1. The consumer owns the `InterfacePtr<T>`: `set_connection_error_handler`
+ should be used.
+2. The consumer doesn't own the `InterfacePtr<T>`: there are two helpers
+ depending on the behavior that the caller wants. If the caller wants to
+ ensure that an error handler is run, then
+ [**`mojo::WrapCallbackWithDropHandler`**](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/callback_helpers.h?l=46)
+ should be used. If the caller wants the callback to always be run, then
+ [**`mojo::WrapCallbackWithDefaultInvokeIfNotRun`**](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/callback_helpers.h?l=40)
+ helper should be used. With both of these helpers, usual callback care should
+ be followed to ensure that the callbacks don't run after the consumer is
+ destructed (e.g. because the owner of the `InterfacePtr<T>` outlives the
+ consumer). This includes using
+ [**`base::WeakPtr`**](https://cs.chromium.org/chromium/src/base/memory/weak_ptr.h?l=5)
+ or
+ [**`base::RefCounted`**](https://cs.chromium.org/chromium/src/base/memory/ref_counted.h?l=246).
+ It should also be noted that with these helpers, the callbacks could be run
+ synchronously while the InterfacePtr<T> is reset or destroyed.
+
### A Note About Ordering
As mentioned in the previous section, closing one end of a pipe will eventually
@@ -457,13 +499,228 @@ 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.
+## Types
+
+### Enums
+
+[Mojom enums](/mojo/public/tools/bindings/README.md#Enumeration-Types) translate
+directly to equivalent strongly-typed C++11 enum classes with `int32_t` as the
+underlying type. The typename and value names are identical between Mojom and
+C++. Mojo also always defines a special enumerator `kMaxValue` that shares the
+value of the highest enumerator: this makes it easy to record Mojo enums in
+histograms and interoperate with legacy IPC.
+
+For example, consider the following Mojom definition:
+
+```cpp
+module business.mojom;
+
+enum Department {
+ kEngineering,
+ kMarketing,
+ kSales,
+};
+```
+
+This translates to the following C++ definition:
+
+```cpp
+namespace business {
+namespace mojom {
+
+enum class Department : int32_t {
+ kEngineering,
+ kMarketing,
+ kSales,
+ kMaxValue = kSales,
+};
+
+} // namespace mojom
+} // namespace business
+```
+
+### Structs
+
+[Mojom structs](mojo/public/tools/bindings/README.md#Structs) can be used to
+define logical groupings of fields into a new composite type. Every Mojom struct
+elicits the generation of an identically named, representative C++ class, with
+identically named public fields of corresponding C++ types, and several helpful
+public methods.
+
+For example, consider the following Mojom struct:
+
+```cpp
+module business.mojom;
+
+struct Employee {
+ int64 id;
+ string username;
+ Department department;
+};
+```
+
+This would generate a C++ class like so:
+
+```cpp
+namespace business {
+namespace mojom {
+
+class Employee;
+
+using EmployeePtr = mojo::StructPtr<Employee>;
+
+class Employee {
+ public:
+ // Default constructor - applies default values, potentially ones specified
+ // explicitly within the Mojom.
+ Employee();
+
+ // Value constructor - an explicit argument for every field in the struct, in
+ // lexical Mojom definition order.
+ Employee(int64_t id, const std::string& username, Department department);
+
+ // Creates a new copy of this struct value
+ EmployeePtr Clone();
+
+ // Tests for equality with another struct value of the same type.
+ bool Equals(const Employee& other);
+
+ // Equivalent public fields with names identical to the Mojom.
+ int64_t id;
+ std::string username;
+ Department department;
+};
+
+} // namespace mojom
+} // namespace business
+```
+
+Note when used as a message parameter or as a field within another Mojom struct,
+a `struct` type is wrapped by the move-only `mojo::StructPtr` helper, which is
+roughly equivalent to a `std::unique_ptr` with some additional utility methods.
+This allows struct values to be nullable and struct types to be potentially
+self-referential.
+
+Every genereated struct class has a static `New()` method which returns a new
+`mojo::StructPtr<T>` wrapping a new instance of the class constructed by
+forwarding the arguments from `New`. For example:
+
+```cpp
+mojom::EmployeePtr e1 = mojom::Employee::New();
+e1->id = 42;
+e1->username = "mojo";
+e1->department = mojom::Department::kEngineering;
+```
+
+is equivalent to
+
+```cpp
+auto e1 = mojom::Employee::New(42, "mojo", mojom::Department::kEngineering);
+```
+
+Now if we define an interface like:
+
+```cpp
+interface EmployeeManager {
+ AddEmployee(Employee e);
+};
+```
+
+We'll get this C++ interface to implement:
+
+```cpp
+class EmployeeManager {
+ public:
+ virtual ~EmployeManager() {}
+
+ virtual void AddEmployee(EmployeePtr e) = 0;
+};
+```
+
+And we can send this message from C++ code as follows:
+
+```cpp
+mojom::EmployeManagerPtr manager = ...;
+manager->AddEmployee(
+ Employee::New(42, "mojo", mojom::Department::kEngineering));
+
+// or
+auto e = Employee::New(42, "mojo", mojom::Department::kEngineering);
+manager->AddEmployee(std::move(e));
+```
+
+### Unions
+
+Similarly to [structs](#Structs), tagged unions generate an identically named,
+representative C++ class which is typically wrapped in a `mojo::StructPtr<T>`.
+
+Unlike structs, all generated union fields are private and must be retrieved and
+manipulated using accessors. A field `foo` is accessible by `foo()` and
+settable by `set_foo()`. There is also a boolean `is_foo()` for each field which
+indicates whether the union is currently taking on the value of field `foo` in
+exclusion to all other union fields.
+
+Finally, every generated union class also has a nested `Tag` enum class which
+enumerates all of the named union fields. A Mojom union value's current type can
+be determined by calling the `which()` method which returns a `Tag`.
+
+For example, consider the following Mojom definitions:
+
+```cpp
+union Value {
+ int64 int_value;
+ float32 float_value;
+ string string_value;
+};
+
+interface Dictionary {
+ AddValue(string key, Value value);
+};
+```
+
+This generates a the following C++ interface:
+
+```cpp
+class Value {
+ public:
+ virtual ~Value() {}
+
+ virtual void AddValue(const std::string& key, ValuePtr value) = 0;
+};
+```
+
+And we can use it like so:
+
+```cpp
+ValuePtr value = Value::New();
+value->set_int_value(42);
+CHECK(value->is_int_value());
+CHECK_EQ(value->which(), Value::Tag::INT_VALUE);
+
+value->set_float_value(42);
+CHECK(value->is_float_value());
+CHECK_EQ(value->which(), Value::Tag::FLOAT_VALUE);
+
+value->set_string_value("bananas");
+CHECK(value->is_string_value());
+CHECK_EQ(value->which(), Value::Tag::STRING_VALUE);
+```
+
+Finally, note that if a union value is not currently occupied by a given field,
+attempts to access that field will DCHECK:
+
+```cpp
+ValuePtr value = Value::New();
+value->set_int_value(42);
+LOG(INFO) << "Value is " << value->string_value(); // DCHECK!
+```
+
### 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.
+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
@@ -482,7 +739,7 @@ interface Database {
```
As noted in the
-[Mojom IDL documentation](/mojo/public/tools/bindings#Primitive-Types),
+[Mojom IDL documentation](/mojo/public/tools/bindings/README.md#Primitive-Types),
the `Table&` syntax denotes a `Table` interface request. This corresponds
precisely to the `InterfaceRequest<T>` type discussed in the sections above, and
in fact the generated code for these interfaces is approximately:
@@ -545,7 +802,7 @@ class DatabaseImpl : public db::mojom::Database {
// db::mojom::Database:
void AddTable(db::mojom::TableRequest table) {
- tables_.emplace_back(base::MakeUnique<TableImpl>(std::move(table)));
+ tables_.emplace_back(std::make_unique<TableImpl>(std::move(table)));
}
private:
@@ -620,7 +877,7 @@ pipes.
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)
+[**`MakeStrongBinding`**](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/strong_binding.h)
function is used to create such a binding.
.
@@ -640,14 +897,14 @@ class LoggerImpl : public sample::mojom::Logger {
};
db::mojom::LoggerPtr logger;
-mojo::MakeStrongBinding(base::MakeUnique<DatabaseImpl>(),
+mojo::MakeStrongBinding(std::make_unique<LoggerImpl>(),
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.
+`LoggerImpl` on the other end will remain alive.
### Binding Sets
@@ -744,9 +1001,182 @@ class TableImpl : public db::mojom::Table {
## Associated Interfaces
-See [this document](https://www.chromium.org/developers/design-documents/mojo/associated-interfaces).
+Associated interfaces are interfaces which:
-TODO: Move the above doc into the repository markdown docs.
+* enable running multiple interfaces over a single message pipe while
+ preserving message ordering.
+* make it possible for the bindings to access a single message pipe from
+ multiple sequences.
+
+### Mojom
+
+A new keyword `associated` is introduced for interface pointer/request
+fields. For example:
+
+``` cpp
+interface Bar {};
+
+struct Qux {
+ associated Bar bar3;
+};
+
+interface Foo {
+ // Uses associated interface pointer.
+ SetBar(associated Bar bar1);
+ // Uses associated interface request.
+ GetBar(associated Bar& bar2);
+ // Passes a struct with associated interface pointer.
+ PassQux(Qux qux);
+ // Uses associated interface pointer in callback.
+ AsyncGetBar() => (associated Bar bar4);
+};
+```
+
+It means the interface impl/client will communicate using the same
+message pipe over which the associated interface pointer/request is
+passed.
+
+### Using associated interfaces in C++
+
+When generating C++ bindings, the associated interface pointer of `Bar` is
+mapped to `BarAssociatedPtrInfo` (which is an alias of
+`mojo::AssociatedInterfacePtrInfo<Bar>`); associated interface request to
+`BarAssociatedRequest` (which is an alias of
+`mojo::AssociatedInterfaceRequest<Bar>`).
+
+``` cpp
+// In mojom:
+interface Foo {
+ ...
+ SetBar(associated Bar bar1);
+ GetBar(associated Bar& bar2);
+ ...
+};
+
+// In C++:
+class Foo {
+ ...
+ virtual void SetBar(BarAssociatedPtrInfo bar1) = 0;
+ virtual void GetBar(BarAssociatedRequest bar2) = 0;
+ ...
+};
+```
+
+#### Passing associated interface requests
+
+Assume you have already got an `InterfacePtr<Foo> foo_ptr`, and you would like
+to call `GetBar()` on it. You can do:
+
+``` cpp
+BarAssociatedPtrInfo bar_ptr_info;
+BarAssociatedRequest bar_request = MakeRequest(&bar_ptr_info);
+foo_ptr->GetBar(std::move(bar_request));
+
+// BarAssociatedPtr is an alias of AssociatedInterfacePtr<Bar>.
+BarAssociatedPtr bar_ptr;
+bar_ptr.Bind(std::move(bar_ptr_info));
+bar_ptr->DoSomething();
+```
+
+First, the code creates an associated interface of type `Bar`. It looks very
+similar to what you would do to setup a non-associated interface. An
+important difference is that one of the two associated endpoints (either
+`bar_request` or `bar_ptr_info`) must be sent over another interface. That is
+how the interface is associated with an existing message pipe.
+
+It should be noted that you cannot call `bar_ptr->DoSomething()` before passing
+`bar_request`. This is required by the FIFO-ness guarantee: at the receiver
+side, when the message of `DoSomething` call arrives, we want to dispatch it to
+the corresponding `AssociatedBinding<Bar>` before processing any subsequent
+messages. If `bar_request` is in a subsequent message, message dispatching gets
+into a deadlock. On the other hand, as soon as `bar_request` is sent, `bar_ptr`
+is usable. There is no need to wait until `bar_request` is bound to an
+implementation at the remote side.
+
+A `MakeRequest` overload which takes an `AssociatedInterfacePtr` pointer
+(instead of an `AssociatedInterfacePtrInfo` pointer) is provided to make the
+code a little shorter. The following code achieves the same purpose:
+
+``` cpp
+BarAssociatedPtr bar_ptr;
+foo_ptr->GetBar(MakeRequest(&bar_ptr));
+bar_ptr->DoSomething();
+```
+
+The implementation of `Foo` looks like this:
+
+``` cpp
+class FooImpl : public Foo {
+ ...
+ void GetBar(BarAssociatedRequest bar2) override {
+ bar_binding_.Bind(std::move(bar2));
+ ...
+ }
+ ...
+
+ Binding<Foo> foo_binding_;
+ AssociatedBinding<Bar> bar_binding_;
+};
+```
+
+In this example, `bar_binding_`'s lifespan is tied to that of `FooImpl`. But you
+don't have to do that. You can, for example, pass `bar2` to another sequence to
+bind to an `AssociatedBinding<Bar>` there.
+
+When the underlying message pipe is disconnected (e.g., `foo_ptr` or
+`foo_binding_` is destroyed), all associated interface endpoints (e.g.,
+`bar_ptr` and `bar_binding_`) will receive a connection error.
+
+#### Passing associated interface pointers
+
+Similarly, assume you have already got an `InterfacePtr<Foo> foo_ptr`, and you
+would like to call `SetBar()` on it. You can do:
+
+``` cpp
+AssociatedBind<Bar> bar_binding(some_bar_impl);
+BarAssociatedPtrInfo bar_ptr_info;
+BarAssociatedRequest bar_request = MakeRequest(&bar_ptr_info);
+foo_ptr->SetBar(std::move(bar_ptr_info));
+bar_binding.Bind(std::move(bar_request));
+```
+
+The following code achieves the same purpose:
+
+``` cpp
+AssociatedBind<Bar> bar_binding(some_bar_impl);
+BarAssociatedPtrInfo bar_ptr_info;
+bar_binding.Bind(&bar_ptr_info);
+foo_ptr->SetBar(std::move(bar_ptr_info));
+```
+
+### Performance considerations
+
+When using associated interfaces on different sequences than the master sequence
+(where the master interface lives):
+
+* Sending messages: send happens directly on the calling sequence. So there
+ isn't sequence hopping.
+* Receiving messages: associated interfaces bound on a different sequence from
+ the master interface incur an extra sequence hop during dispatch.
+
+Therefore, performance-wise associated interfaces are better suited for
+scenarios where message receiving happens on the master sequence.
+
+### Testing
+
+Associated interfaces need to be associated with a master interface before
+they can be used. This means one end of the associated interface must be sent
+over one end of the master interface, or over one end of another associated
+interface which itself already has a master interface.
+
+If you want to test an associated interface endpoint without first
+associating it, you can use `mojo::MakeIsolatedRequest()`. This will create
+working associated interface endpoints which are not actually associated with
+anything else.
+
+### Read more
+
+* [Design: Mojo Associated Interfaces](https://docs.google.com/document/d/1nq3J_HbS-gvVfIoEhcVyxm1uY-9G_7lhD-4Kyxb1WIY/edit)
## Synchronous Calls
@@ -808,9 +1238,9 @@ 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)
+[chromium_bindings_configuration.gni](https://cs.chromium.org/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
+[blink_bindings_configuration.gni](https://cs.chromium.org/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`
@@ -910,7 +1340,10 @@ 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" ]
+sources = [
+ "//ui/gfx/geometry/mojo/geometry_struct_traits.cc",
+ "//ui/gfx/geometry/mojo/geometry_struct_traits.h",
+]
public_deps = [ "//ui/gfx/geometry" ]
type_mappings = [
"gfx.mojom.Rect=gfx::Rect",
@@ -928,8 +1361,9 @@ Let's look at each of the variables above:
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.
+* `sources`: Any implementation sources and headers needed for the
+ `StructTraits` definition. These sources are compiled directly into the
+ generated C++ bindings target for a `mojom` file applying this typemap.
* `public_deps`: Target dependencies exposed by the `public_headers` and
`traits_headers`.
* `deps`: Target dependencies exposed by `sources` but not already covered by
@@ -952,6 +1386,10 @@ Let's look at each of the variables above:
`StructTraits` definition for this type mapping must define additional
`IsNull` and `SetToNull` methods. See
[Specializing Nullability](#Specializing-Nullability) below.
+ * `force_serialize`: The typemap is incompatible with lazy serialization
+ (e.g. consider a typemap to a `base::StringPiece`, where retaining a
+ copy is unsafe). Any messages carrying the type will be forced down the
+ eager serailization path.
Now that we have the typemap file we need to add it to a local list of typemaps
@@ -966,7 +1404,7 @@ typemaps = [
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):
+[chromium_bindings_configuration.gni](https://cs.chromium.org/chromium/src/mojo/public/tools/bindings/chromium_bindings_configuration.gni):
```
_typemap_imports = [
@@ -1103,6 +1541,7 @@ class StructTraits
Generated `ReadFoo` methods always convert `multi_word_field_name` fields to
`ReadMultiWordFieldName` methods.
+<a name="Blink-Type-Mapping"></a>
### Variants
By now you may have noticed that additional C++ sources are generated when a
@@ -1152,8 +1591,8 @@ 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.
+implicitly includes the shared header, but may 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
@@ -1175,7 +1614,7 @@ depend on `"//sample:interfaces_blink"`.
## Versioning Considerations
For general documentation of versioning in the Mojom IDL see
-[Versioning](/mojo/public/tools/bindings#Versioning).
+[Versioning](/mojo/public/tools/bindings/README.md#Versiwoning).
This section briefly discusses some C++-specific considerations relevant to
versioned Mojom types.
@@ -1224,6 +1663,10 @@ generates the function in the same namespace as the generated C++ enum type:
inline bool IsKnownEnumValue(Department value);
```
+### Using Mojo Bindings in Chrome
+
+See [Converting Legacy Chrome IPC To Mojo](/ipc/README.md).
+
### Additional Documentation
[Calling Mojo From Blink](https://www.chromium.org/developers/design-documents/mojo/calling-mojo-from-blink)
diff --git a/mojo/public/cpp/bindings/array_traits.h b/mojo/public/cpp/bindings/array_traits.h
index 594b2e0789..3bf232875c 100644
--- a/mojo/public/cpp/bindings/array_traits.h
+++ b/mojo/public/cpp/bindings/array_traits.h
@@ -5,6 +5,8 @@
#ifndef MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_H_
#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_H_
+#include "mojo/public/cpp/bindings/lib/template_util.h"
+
namespace mojo {
// This must be specialized for any type |T| to be serialized/deserialized as
@@ -24,6 +26,8 @@ namespace mojo {
// // using ConstIterator = T::const_iterator;
//
// // These two methods are optional. Please see comments in struct_traits.h
+// // Note that unlike with StructTraits, IsNull() is called *twice* during
+// // serialization for ArrayTraits.
// static bool IsNull(const Container<T>& input);
// static void SetToNull(Container<T>* output);
//
@@ -64,7 +68,11 @@ namespace mojo {
// };
//
template <typename T>
-struct ArrayTraits;
+struct ArrayTraits {
+ static_assert(internal::AlwaysFalse<T>::value,
+ "Cannot find the mojo::ArrayTraits specialization. Did you "
+ "forget to include the corresponding header file?");
+};
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/array_traits_carray.h b/mojo/public/cpp/bindings/array_traits_carray.h
deleted file mode 100644
index 3ff694b882..0000000000
--- a/mojo/public/cpp/bindings/array_traits_carray.h
+++ /dev/null
@@ -1,76 +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 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 <typename T>
-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 <typename T>
-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 <typename T>
-struct ArrayTraits<CArray<T>> {
- using Element = T;
-
- static bool IsNull(const CArray<T>& input) { return !input.data; }
-
- static void SetToNull(CArray<T>* output) { output->data = nullptr; }
-
- static size_t GetSize(const CArray<T>& input) { return input.size; }
-
- static T* GetData(CArray<T>& input) { return input.data; }
-
- static const T* GetData(const CArray<T>& input) { return input.data; }
-
- static T& GetAt(CArray<T>& input, size_t index) { return input.data[index]; }
-
- static const T& GetAt(const CArray<T>& input, size_t index) {
- return input.data[index];
- }
-
- static bool Resize(CArray<T>& input, size_t size) {
- if (size > input.max_size)
- return false;
-
- input.size = size;
- return true;
- }
-};
-
-template <typename T>
-struct ArrayTraits<ConstCArray<T>> {
- using Element = T;
-
- static bool IsNull(const ConstCArray<T>& input) { return !input.data; }
-
- static size_t GetSize(const ConstCArray<T>& input) { return input.size; }
-
- static const T* GetData(const ConstCArray<T>& input) { return input.data; }
-
- static const T& GetAt(const ConstCArray<T>& 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_span.h b/mojo/public/cpp/bindings/array_traits_span.h
new file mode 100644
index 0000000000..d8364030f3
--- /dev/null
+++ b/mojo/public/cpp/bindings/array_traits_span.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_CARRAY_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_CARRAY_H_
+
+#include <cstddef>
+
+#include "base/containers/span.h"
+#include "mojo/public/cpp/bindings/array_traits.h"
+
+namespace mojo {
+
+template <typename T>
+struct ArrayTraits<base::span<T>> {
+ using Element = T;
+
+ // There is no concept of a null span, as it is indistinguishable from the
+ // empty span.
+ static bool IsNull(const base::span<T>& input) { return false; }
+
+ static size_t GetSize(const base::span<T>& input) { return input.size(); }
+
+ static T* GetData(base::span<T>& input) { return input.data(); }
+
+ static const T* GetData(const base::span<T>& input) { return input.data(); }
+
+ static T& GetAt(base::span<T>& input, size_t index) {
+ return input.data()[index];
+ }
+
+ static const T& GetAt(const base::span<T>& input, size_t index) {
+ return input.data()[index];
+ }
+
+ static bool Resize(base::span<T>& input, size_t size) {
+ if (size > input.size())
+ return false;
+ input = input.subspan(0, size);
+ return true;
+ }
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_CARRAY_H_
diff --git a/mojo/public/cpp/bindings/array_traits_wtf_vector.h b/mojo/public/cpp/bindings/array_traits_wtf_vector.h
index 6e207351fd..83d2ba3f51 100644
--- a/mojo/public/cpp/bindings/array_traits_wtf_vector.h
+++ b/mojo/public/cpp/bindings/array_traits_wtf_vector.h
@@ -6,37 +6,46 @@
#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"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace mojo {
-template <typename U>
-struct ArrayTraits<WTF::Vector<U>> {
+template <typename U, size_t InlineCapacity>
+struct ArrayTraits<WTF::Vector<U, InlineCapacity>> {
using Element = U;
- static bool IsNull(const WTF::Vector<U>& input) {
+ static bool IsNull(const WTF::Vector<U, InlineCapacity>& input) {
// WTF::Vector<> is always converted to non-null mojom array.
return false;
}
- static void SetToNull(WTF::Vector<U>* output) {
+ static void SetToNull(WTF::Vector<U, InlineCapacity>* output) {
// WTF::Vector<> doesn't support null state. Set it to empty instead.
output->clear();
}
- static size_t GetSize(const WTF::Vector<U>& input) { return input.size(); }
+ static size_t GetSize(const WTF::Vector<U, InlineCapacity>& input) {
+ return input.size();
+ }
- static U* GetData(WTF::Vector<U>& input) { return input.data(); }
+ static U* GetData(WTF::Vector<U, InlineCapacity>& input) {
+ return input.data();
+ }
- static const U* GetData(const WTF::Vector<U>& input) { return input.data(); }
+ static const U* GetData(const WTF::Vector<U, InlineCapacity>& input) {
+ return input.data();
+ }
- static U& GetAt(WTF::Vector<U>& input, size_t index) { return input[index]; }
+ static U& GetAt(WTF::Vector<U, InlineCapacity>& input, size_t index) {
+ return input[index];
+ }
- static const U& GetAt(const WTF::Vector<U>& input, size_t index) {
+ static const U& GetAt(const WTF::Vector<U, InlineCapacity>& input,
+ size_t index) {
return input[index];
}
- static bool Resize(WTF::Vector<U>& input, size_t size) {
+ static bool Resize(WTF::Vector<U, InlineCapacity>& input, size_t size) {
input.resize(size);
return true;
}
diff --git a/mojo/public/cpp/bindings/associated_binding.h b/mojo/public/cpp/bindings/associated_binding.h
index 59411666f5..e8e0cb1e25 100644
--- a/mojo/public/cpp/bindings/associated_binding.h
+++ b/mojo/public/cpp/bindings/associated_binding.h
@@ -16,7 +16,6 @@
#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"
@@ -53,14 +52,16 @@ class MOJO_CPP_BINDINGS_EXPORT AssociatedBindingBase {
// 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_handler(base::OnceClosure error_handler);
void set_connection_error_with_reason_handler(
- const ConnectionErrorWithReasonCallback& error_handler);
+ ConnectionErrorWithReasonCallback error_handler);
// Indicates whether the associated binding has been completed.
bool is_bound() const { return !!endpoint_client_; }
+ explicit operator bool() 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
@@ -85,9 +86,9 @@ class MOJO_CPP_BINDINGS_EXPORT AssociatedBindingBase {
// 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.
+// single thread for the purposes of task scheduling. Please note that
+// incoming synchronous method calls may not be run from this task runner, when
+// they reenter outgoing synchronous calls on the same thread.
template <typename Interface,
typename ImplRefTraits = RawPtrImplRefTraits<Interface>>
class AssociatedBinding : public AssociatedBindingBase {
@@ -96,47 +97,26 @@ class AssociatedBinding : public AssociatedBindingBase {
// 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<Interface>* ptr_info,
- scoped_refptr<base::SingleThreadTaskRunner> runner =
- base::ThreadTaskRunnerHandle::Get())
- : AssociatedBinding(std::move(impl)) {
- Bind(ptr_info, std::move(runner));
+ // |Bind| method.
+ explicit AssociatedBinding(ImplPointerType impl) {
+ stub_.set_sink(std::move(impl));
}
// Constructs a completed associated binding of |impl|. |impl| must outlive
// the binding.
- AssociatedBinding(ImplPointerType impl,
- AssociatedInterfaceRequest<Interface> request,
- scoped_refptr<base::SingleThreadTaskRunner> runner =
- base::ThreadTaskRunnerHandle::Get())
+ AssociatedBinding(
+ ImplPointerType impl,
+ AssociatedInterfaceRequest<Interface> request,
+ scoped_refptr<base::SingleThreadTaskRunner> runner = nullptr)
: 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<Interface>* ptr_info,
- scoped_refptr<base::SingleThreadTaskRunner> 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<Interface> request,
- scoped_refptr<base::SingleThreadTaskRunner> runner =
- base::ThreadTaskRunnerHandle::Get()) {
+ scoped_refptr<base::SingleThreadTaskRunner> runner = nullptr) {
BindImpl(request.PassHandle(), &stub_,
base::WrapUnique(new typename Interface::RequestValidator_()),
Interface::HasSyncMethods_, std::move(runner),
@@ -144,22 +124,26 @@ class AssociatedBinding : public AssociatedBindingBase {
}
// Unbinds and returns the associated interface request so it can be
- // used in another context, such as on another thread or with a different
+ // used in another context, such as on another sequence or with a different
// implementation. Puts this object into a state where it can be rebound.
AssociatedInterfaceRequest<Interface> Unbind() {
DCHECK(endpoint_client_);
-
- AssociatedInterfaceRequest<Interface> request;
- request.Bind(endpoint_client_->PassHandle());
-
+ AssociatedInterfaceRequest<Interface> request(
+ endpoint_client_->PassHandle());
endpoint_client_.reset();
-
return request;
}
// Returns the interface implementation that was previously specified.
Interface* impl() { return ImplRefTraits::GetRawPointer(&stub_.sink()); }
+ // Allows test code to swap the interface implementation.
+ ImplPointerType SwapImplForTesting(ImplPointerType new_impl) {
+ Interface* old_impl = impl();
+ stub_.set_sink(std::move(new_impl));
+ return old_impl;
+ }
+
private:
typename Interface::template Stub_<ImplRefTraits> stub_;
diff --git a/mojo/public/cpp/bindings/associated_group.h b/mojo/public/cpp/bindings/associated_group.h
index 14e78ec3f9..1a31f904ea 100644
--- a/mojo/public/cpp/bindings/associated_group.h
+++ b/mojo/public/cpp/bindings/associated_group.h
@@ -6,8 +6,8 @@
#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_H_
#include "base/callback.h"
+#include "base/component_export.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 {
@@ -17,7 +17,7 @@ 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 {
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) AssociatedGroup {
public:
AssociatedGroup();
diff --git a/mojo/public/cpp/bindings/associated_group_controller.h b/mojo/public/cpp/bindings/associated_group_controller.h
index d33c2776d5..386ebdf860 100644
--- a/mojo/public/cpp/bindings/associated_group_controller.h
+++ b/mojo/public/cpp/bindings/associated_group_controller.h
@@ -7,11 +7,11 @@
#include <memory>
+#include "base/component_export.h"
#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 "base/sequenced_task_runner.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"
@@ -23,7 +23,7 @@ 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
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) AssociatedGroupController
: public base::RefCountedThreadSafe<AssociatedGroupController> {
public:
// Associates an interface with this AssociatedGroupController's message pipe.
@@ -53,15 +53,15 @@ class MOJO_CPP_BINDINGS_EXPORT AssociatedGroupController
// 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
+ // on the same sequence as this call, and only before the client is detached
// using DetachEndpointClient().
virtual InterfaceEndpointController* AttachEndpointClient(
const ScopedInterfaceEndpointHandle& handle,
InterfaceEndpointClient* endpoint_client,
- scoped_refptr<base::SingleThreadTaskRunner> runner) = 0;
+ scoped_refptr<base::SequencedTaskRunner> runner) = 0;
// Detaches the client attached to the specified endpoint. It must be called
- // on the same thread as the corresponding AttachEndpointClient() call.
+ // on the same sequence as the corresponding AttachEndpointClient() call.
virtual void DetachEndpointClient(
const ScopedInterfaceEndpointHandle& handle) = 0;
@@ -69,6 +69,10 @@ class MOJO_CPP_BINDINGS_EXPORT AssociatedGroupController
// and notifies all interfaces running on this pipe.
virtual void RaiseError() = 0;
+ // Indicates whether or this endpoint prefers to accept outgoing messages in
+ // serializaed form only.
+ virtual bool PrefersSerializedMessages() = 0;
+
protected:
friend class base::RefCountedThreadSafe<AssociatedGroupController>;
diff --git a/mojo/public/cpp/bindings/associated_interface_ptr.h b/mojo/public/cpp/bindings/associated_interface_ptr.h
index 8806a3e090..3d08001230 100644
--- a/mojo/public/cpp/bindings/associated_interface_ptr.h
+++ b/mojo/public/cpp/bindings/associated_interface_ptr.h
@@ -14,8 +14,7 @@
#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 "base/sequenced_task_runner.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"
@@ -33,6 +32,7 @@ class AssociatedInterfacePtr {
public:
using InterfaceType = Interface;
using PtrInfoType = AssociatedInterfacePtrInfo<Interface>;
+ using Proxy = typename Interface::Proxy_;
// Constructs an unbound AssociatedInterfacePtr.
AssociatedInterfacePtr() {}
@@ -42,6 +42,8 @@ class AssociatedInterfacePtr {
internal_state_.Swap(&other.internal_state_);
}
+ explicit AssociatedInterfacePtr(PtrInfoType&& info) { Bind(std::move(info)); }
+
AssociatedInterfacePtr& operator=(AssociatedInterfacePtr&& other) {
reset();
internal_state_.Swap(&other.internal_state_);
@@ -61,18 +63,17 @@ class AssociatedInterfacePtr {
// 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.
+ // Optionally, |runner| is a SequencedTaskRunner bound to the current sequence
+ // on which all callbacks and connection error notifications will be
+ // dispatched. It is only useful to specify this to use a different
+ // SequencedTaskRunner than SequencedTaskRunnerHandle::Get().
//
// NOTE: The corresponding AssociatedInterfaceRequest must be sent over
// another interface before using this object to make calls. Please see the
// comments of MakeRequest(AssociatedInterfacePtr<Interface>*) for more
// details.
void Bind(AssociatedInterfacePtrInfo<Interface> info,
- scoped_refptr<base::SingleThreadTaskRunner> runner =
- base::ThreadTaskRunnerHandle::Get()) {
+ scoped_refptr<base::SequencedTaskRunner> runner = nullptr) {
reset();
if (info.is_valid())
@@ -81,11 +82,11 @@ class AssociatedInterfacePtr {
bool is_bound() const { return internal_state_.is_bound(); }
- Interface* get() const { return internal_state_.instance(); }
+ Proxy* 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(); }
+ Proxy* operator->() const { return get(); }
+ Proxy& operator*() const { return *get(); }
// Returns the version number of the interface that the remote side supports.
uint32_t version() const { return internal_state_.version(); }
@@ -136,18 +137,19 @@ class AssociatedInterfacePtr {
//
// 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_handler(base::OnceClosure error_handler) {
+ internal_state_.set_connection_error_handler(std::move(error_handler));
}
void set_connection_error_with_reason_handler(
- const ConnectionErrorWithReasonCallback& error_handler) {
- internal_state_.set_connection_error_with_reason_handler(error_handler);
+ ConnectionErrorWithReasonCallback error_handler) {
+ internal_state_.set_connection_error_with_reason_handler(
+ std::move(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.
+ // used to move the proxy to a different sequence.
//
// 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
@@ -165,27 +167,10 @@ class AssociatedInterfacePtr {
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<Interface>
- AssociatedInterfacePtr::*Testable;
-
- public:
- operator Testable() const {
- return internal_state_.is_bound() ? &AssociatedInterfacePtr::internal_state_
- : nullptr;
- }
+ // Allow AssociatedInterfacePtr<> to be used in boolean expressions.
+ explicit operator bool() const { return internal_state_.is_bound(); }
private:
- // Forbid the == and != operators explicitly, otherwise AssociatedInterfacePtr
- // will be converted to Testable to do == or != comparison.
- template <typename T>
- bool operator==(const AssociatedInterfacePtr<T>& other) const = delete;
- template <typename T>
- bool operator!=(const AssociatedInterfacePtr<T>& other) const = delete;
-
typedef internal::AssociatedInterfacePtrState<Interface> State;
mutable State internal_state_;
@@ -202,8 +187,7 @@ class AssociatedInterfacePtr {
template <typename Interface>
AssociatedInterfaceRequest<Interface> MakeRequest(
AssociatedInterfacePtr<Interface>* ptr,
- scoped_refptr<base::SingleThreadTaskRunner> runner =
- base::ThreadTaskRunnerHandle::Get()) {
+ scoped_refptr<base::SequencedTaskRunner> runner = nullptr) {
AssociatedInterfacePtrInfo<Interface> ptr_info;
auto request = MakeRequest(&ptr_info);
ptr->Bind(std::move(ptr_info), std::move(runner));
@@ -228,9 +212,7 @@ AssociatedInterfaceRequest<Interface> MakeRequest(
ptr_info->set_handle(std::move(handle0));
ptr_info->set_version(0);
- AssociatedInterfaceRequest<Interface> request;
- request.Bind(std::move(handle1));
- return request;
+ return AssociatedInterfaceRequest<Interface>(std::move(handle1));
}
// Like MakeRequest() above, but it creates a dedicated message pipe. The
@@ -245,17 +227,17 @@ AssociatedInterfaceRequest<Interface> MakeRequest(
// * When discarding messages sent on an interface, which can be done by
// discarding the returned request.
template <typename Interface>
-AssociatedInterfaceRequest<Interface> MakeIsolatedRequest(
+AssociatedInterfaceRequest<Interface> MakeRequestAssociatedWithDedicatedPipe(
AssociatedInterfacePtr<Interface>* ptr) {
MessagePipe pipe;
scoped_refptr<internal::MultiplexRouter> router0 =
- new internal::MultiplexRouter(std::move(pipe.handle0),
- internal::MultiplexRouter::MULTI_INTERFACE,
- false, base::ThreadTaskRunnerHandle::Get());
+ new internal::MultiplexRouter(
+ std::move(pipe.handle0), internal::MultiplexRouter::MULTI_INTERFACE,
+ false, base::SequencedTaskRunnerHandle::Get());
scoped_refptr<internal::MultiplexRouter> router1 =
- new internal::MultiplexRouter(std::move(pipe.handle1),
- internal::MultiplexRouter::MULTI_INTERFACE,
- true, base::ThreadTaskRunnerHandle::Get());
+ new internal::MultiplexRouter(
+ std::move(pipe.handle1), internal::MultiplexRouter::MULTI_INTERFACE,
+ true, base::SequencedTaskRunnerHandle::Get());
ScopedInterfaceEndpointHandle endpoint0, endpoint1;
ScopedInterfaceEndpointHandle::CreatePairPendingAssociation(&endpoint0,
@@ -265,17 +247,14 @@ AssociatedInterfaceRequest<Interface> MakeIsolatedRequest(
ptr->Bind(AssociatedInterfacePtrInfo<Interface>(std::move(endpoint0),
Interface::Version_));
-
- AssociatedInterfaceRequest<Interface> request;
- request.Bind(std::move(endpoint1));
- return request;
+ return AssociatedInterfaceRequest<Interface>(std::move(endpoint1));
}
// |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(
+MOJO_CPP_BINDINGS_EXPORT void AssociateWithDisconnectedPipe(
ScopedInterfaceEndpointHandle handle);
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/associated_interface_ptr_info.h b/mojo/public/cpp/bindings/associated_interface_ptr_info.h
index 3c6ca54603..cc3f627167 100644
--- a/mojo/public/cpp/bindings/associated_interface_ptr_info.h
+++ b/mojo/public/cpp/bindings/associated_interface_ptr_info.h
@@ -45,6 +45,8 @@ class AssociatedInterfacePtrInfo {
bool is_valid() const { return handle_.is_valid(); }
+ explicit operator bool() const { return handle_.is_valid(); }
+
ScopedInterfaceEndpointHandle PassHandle() {
return std::move(handle_);
}
diff --git a/mojo/public/cpp/bindings/associated_interface_request.h b/mojo/public/cpp/bindings/associated_interface_request.h
index c37636c9f3..0926f3df92 100644
--- a/mojo/public/cpp/bindings/associated_interface_request.h
+++ b/mojo/public/cpp/bindings/associated_interface_request.h
@@ -23,11 +23,15 @@ class AssociatedInterfaceRequest {
AssociatedInterfaceRequest() {}
AssociatedInterfaceRequest(decltype(nullptr)) {}
+ explicit AssociatedInterfaceRequest(ScopedInterfaceEndpointHandle handle)
+ : handle_(std::move(handle)) {}
+
// 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_);
@@ -46,13 +50,9 @@ class AssociatedInterfaceRequest {
// handle.
bool is_pending() const { return handle_.is_valid(); }
- void Bind(ScopedInterfaceEndpointHandle handle) {
- handle_ = std::move(handle);
- }
+ explicit operator bool() const { return handle_.is_valid(); }
- ScopedInterfaceEndpointHandle PassHandle() {
- return std::move(handle_);
- }
+ ScopedInterfaceEndpointHandle PassHandle() { return std::move(handle_); }
const ScopedInterfaceEndpointHandle& handle() const { return handle_; }
@@ -75,16 +75,6 @@ class AssociatedInterfaceRequest {
DISALLOW_COPY_AND_ASSIGN(AssociatedInterfaceRequest);
};
-// Makes an AssociatedInterfaceRequest bound to the specified associated
-// endpoint.
-template <typename Interface>
-AssociatedInterfaceRequest<Interface> MakeAssociatedRequest(
- ScopedInterfaceEndpointHandle handle) {
- AssociatedInterfaceRequest<Interface> 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
index 88d2f4ba3e..5b119b4130 100644
--- a/mojo/public/cpp/bindings/binding.h
+++ b/mojo/public/cpp/bindings/binding.h
@@ -12,7 +12,6 @@
#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"
@@ -28,7 +27,10 @@ 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.
+// interface implementation in an unbound state. Once the |Binding| object is
+// destroyed, it is guaranteed that no more method calls are dispatched to the
+// implementation and the connection error handler (if registered) won't be
+// called.
//
// Example:
//
@@ -55,17 +57,17 @@ class MessageReceiver;
// };
//
// 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.
+// class must be from the sequence that bound it. The interface implementation's
+// methods will be called from the sequence that bound this. If a Binding is not
+// bound to a message pipe, it may be bound or destroyed on any sequence.
//
// 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.
+// 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 <typename Interface,
typename ImplRefTraits = RawPtrImplRefTraits<Interface>>
class Binding {
@@ -77,85 +79,26 @@ class Binding {
// 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<base::SingleThreadTaskRunner> 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<Interface>* ptr,
- scoped_refptr<base::SingleThreadTaskRunner> 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<Interface> request,
- scoped_refptr<base::SingleThreadTaskRunner> runner =
- base::ThreadTaskRunnerHandle::Get())
+ scoped_refptr<base::SingleThreadTaskRunner> runner = nullptr)
: Binding(std::move(impl)) {
- Bind(request.PassMessagePipe(), std::move(runner));
+ Bind(std::move(request), 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<Interface> CreateInterfacePtrAndBind(
- scoped_refptr<base::SingleThreadTaskRunner> runner =
- base::ThreadTaskRunnerHandle::Get()) {
- InterfacePtr<Interface> 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<base::SingleThreadTaskRunner> 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<Interface>* ptr,
- scoped_refptr<base::SingleThreadTaskRunner> runner =
- base::ThreadTaskRunnerHandle::Get()) {
- MessagePipe pipe;
- ptr->Bind(InterfacePtrInfo<Interface>(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<Interface> request,
- scoped_refptr<base::SingleThreadTaskRunner> runner =
- base::ThreadTaskRunnerHandle::Get()) {
- Bind(request.PassMessagePipe(), std::move(runner));
+ scoped_refptr<base::SingleThreadTaskRunner> runner = nullptr) {
+ internal_state_.Bind(request.PassMessagePipe(), std::move(runner));
}
// Adds a message filter to be notified of each incoming message before
@@ -187,7 +130,7 @@ class Binding {
internal_state_.ResumeIncomingMethodCallProcessing();
}
- // Blocks the calling thread until either a call arrives on the previously
+ // Blocks the calling sequence 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.
//
@@ -209,7 +152,7 @@ class Binding {
}
// 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
+ // used in another context, such as on another sequence or with a different
// implementation. Put this object into a state where it can be rebound to a
// new pipe.
//
@@ -231,15 +174,16 @@ class Binding {
// 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) {
+ void set_connection_error_handler(base::OnceClosure error_handler) {
DCHECK(is_bound());
- internal_state_.set_connection_error_handler(error_handler);
+ internal_state_.set_connection_error_handler(std::move(error_handler));
}
void set_connection_error_with_reason_handler(
- const ConnectionErrorWithReasonCallback& error_handler) {
+ ConnectionErrorWithReasonCallback error_handler) {
DCHECK(is_bound());
- internal_state_.set_connection_error_with_reason_handler(error_handler);
+ internal_state_.set_connection_error_with_reason_handler(
+ std::move(error_handler));
}
// Returns the interface implementation that was previously specified. Caller
@@ -250,12 +194,33 @@ class Binding {
// pipe has been bound to the implementation).
bool is_bound() const { return internal_state_.is_bound(); }
+ explicit operator bool() 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(); }
+ // Reports the currently dispatching Message as bad and closes this binding.
+ // Note that this is only legal to call from directly within the stack frame
+ // of a message dispatch. If you need to do asynchronous work before you can
+ // determine the legitimacy of a message, use GetBadMessageCallback() and
+ // retain its result until you're ready to invoke or discard it.
+ void ReportBadMessage(const std::string& error) {
+ GetBadMessageCallback().Run(error);
+ }
+
+ // Acquires a callback which may be run to report the currently dispatching
+ // Message as bad and close this binding. Note that this is only legal to call
+ // from directly within the stack frame of a message dispatch, but the
+ // returned callback may be called exactly once any time thereafter to report
+ // the message as bad. This may only be called once per message. The returned
+ // callback must be called on the Binding's own sequence.
+ ReportBadMessageCallback GetBadMessageCallback() {
+ return internal_state_.GetBadMessageCallback();
+ }
+
// 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
@@ -265,6 +230,20 @@ class Binding {
// Exposed for testing, should not generally be used.
void EnableTestingMode() { internal_state_.EnableTestingMode(); }
+ scoped_refptr<internal::MultiplexRouter> RouterForTesting() {
+ return internal_state_.RouterForTesting();
+ }
+
+ // Allows test code to swap the interface implementation.
+ ImplPointerType SwapImplForTesting(ImplPointerType new_impl) {
+ return internal_state_.SwapImplForTesting(new_impl);
+ }
+
+ // DO NOT USE. Exposed only for internal use and for testing.
+ internal::BindingState<Interface, ImplRefTraits>* internal_state() {
+ return &internal_state_;
+ }
+
private:
internal::BindingState<Interface, ImplRefTraits> internal_state_;
diff --git a/mojo/public/cpp/bindings/binding_set.h b/mojo/public/cpp/bindings/binding_set.h
index 919f9c09ad..414583bbd7 100644
--- a/mojo/public/cpp/bindings/binding_set.h
+++ b/mojo/public/cpp/bindings/binding_set.h
@@ -71,16 +71,16 @@ class BindingSetBase {
using RequestType = typename Traits::RequestType;
using ImplPointerType = typename Traits::ImplPointerType;
- BindingSetBase() {}
+ BindingSetBase() : weak_ptr_factory_(this) {}
- void set_connection_error_handler(const base::Closure& error_handler) {
- error_handler_ = error_handler;
+ void set_connection_error_handler(base::RepeatingClosure error_handler) {
+ error_handler_ = std::move(error_handler);
error_with_reason_handler_.Reset();
}
void set_connection_error_with_reason_handler(
- const ConnectionErrorWithReasonCallback& error_handler) {
- error_with_reason_handler_ = error_handler;
+ RepeatingConnectionErrorWithReasonCallback error_handler) {
+ error_with_reason_handler_ = std::move(error_handler);
error_handler_.Reset();
}
@@ -123,22 +123,24 @@ class BindingSetBase {
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;
+ // Swaps the interface implementation with a different one, to allow tests
+ // to modify behavior.
+ //
+ // Returns the existing interface implementation to the caller.
+ ImplPointerType SwapImplForTesting(BindingId id, ImplPointerType new_impl) {
+ auto it = bindings_.find(id);
+ if (it == bindings_.end())
+ return nullptr;
+
+ return it->second->SwapImplForTesting(new_impl);
}
void CloseAllBindings() { bindings_.clear(); }
bool empty() const { return bindings_.empty(); }
+ size_t size() const { return bindings_.size(); }
+
// 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
@@ -150,9 +152,59 @@ class BindingSetBase {
return *dispatch_context_;
}
+ // Implementations may call this when processing a dispatched message or
+ // error. During the extent of message or error dispatch, this will return the
+ // BindingId of the specific binding which received the message or error.
+ BindingId dispatch_binding() const {
+ DCHECK(dispatch_context_);
+ return dispatch_binding_;
+ }
+
+ // Reports the currently dispatching Message as bad and closes the binding the
+ // message was received from. Note that this is only legal to call from
+ // directly within the stack frame of a message dispatch. If you need to do
+ // asynchronous work before you can determine the legitimacy of a message, use
+ // GetBadMessageCallback() and retain its result until you're ready to invoke
+ // or discard it.
+ void ReportBadMessage(const std::string& error) {
+ GetBadMessageCallback().Run(error);
+ }
+
+ // Acquires a callback which may be run to report the currently dispatching
+ // Message as bad and close the binding the message was received from. Note
+ // that this is only legal to call from directly within the stack frame of a
+ // message dispatch, but the returned callback may be called exactly once any
+ // time thereafter as long as the binding set itself hasn't been destroyed yet
+ // to report the message as bad. This may only be called once per message.
+ // The returned callback must be called on the BindingSet's own sequence.
+ ReportBadMessageCallback GetBadMessageCallback() {
+ DCHECK(dispatch_context_);
+ return base::BindOnce(
+ [](ReportBadMessageCallback error_callback,
+ base::WeakPtr<BindingSetBase> binding_set, BindingId binding_id,
+ const std::string& error) {
+ std::move(error_callback).Run(error);
+ if (binding_set)
+ binding_set->RemoveBinding(binding_id);
+ },
+ mojo::GetBadMessageCallback(), weak_ptr_factory_.GetWeakPtr(),
+ dispatch_binding());
+ }
+
void FlushForTesting() {
+ DCHECK(!is_flushing_);
+ is_flushing_ = true;
for (auto& binding : bindings_)
- binding.second->FlushForTesting();
+ if (binding.second)
+ binding.second->FlushForTesting();
+ is_flushing_ = false;
+ // Clean up any bindings that were destroyed.
+ for (auto it = bindings_.begin(); it != bindings_.end();) {
+ if (!it->second)
+ it = bindings_.erase(it);
+ else
+ ++it;
+ }
}
private:
@@ -169,14 +221,17 @@ class BindingSetBase {
binding_set_(binding_set),
binding_id_(binding_id),
context_(std::move(context)) {
- if (ContextTraits::SupportsContext())
- binding_.AddFilter(base::MakeUnique<DispatchFilter>(this));
+ binding_.AddFilter(std::make_unique<DispatchFilter>(this));
binding_.set_connection_error_with_reason_handler(
- base::Bind(&Entry::OnConnectionError, base::Unretained(this)));
+ base::BindOnce(&Entry::OnConnectionError, base::Unretained(this)));
}
void FlushForTesting() { binding_.FlushForTesting(); }
+ ImplPointerType SwapImplForTesting(ImplPointerType new_impl) {
+ return binding_.SwapImplForTesting(new_impl);
+ }
+
private:
class DispatchFilter : public MessageReceiver {
public:
@@ -196,14 +251,12 @@ class BindingSetBase {
};
void WillDispatch() {
- DCHECK(ContextTraits::SupportsContext());
- binding_set_->SetDispatchContext(&context_);
+ binding_set_->SetDispatchContext(&context_, binding_id_);
}
void OnConnectionError(uint32_t custom_reason,
const std::string& description) {
- if (ContextTraits::SupportsContext())
- WillDispatch();
+ WillDispatch();
binding_set_->OnConnectionError(binding_id_, custom_reason, description);
}
@@ -215,9 +268,9 @@ class BindingSetBase {
DISALLOW_COPY_AND_ASSIGN(Entry);
};
- void SetDispatchContext(const Context* context) {
- DCHECK(ContextTraits::SupportsContext());
+ void SetDispatchContext(const Context* context, BindingId binding_id) {
dispatch_context_ = context;
+ dispatch_binding_ = binding_id;
if (!pre_dispatch_handler_.is_null())
pre_dispatch_handler_.Run(*context);
}
@@ -227,7 +280,7 @@ class BindingSetBase {
Context context) {
BindingId id = next_binding_id_++;
DCHECK_GE(next_binding_id_, 0u);
- auto entry = base::MakeUnique<Entry>(std::move(impl), std::move(request),
+ auto entry = std::make_unique<Entry>(std::move(impl), std::move(request),
this, id, std::move(context));
bindings_.insert(std::make_pair(id, std::move(entry)));
return id;
@@ -241,20 +294,25 @@ class BindingSetBase {
// We keep the Entry alive throughout error dispatch.
std::unique_ptr<Entry> entry = std::move(it->second);
- bindings_.erase(it);
+ if (!is_flushing_)
+ bindings_.erase(it);
- if (!error_handler_.is_null())
+ if (error_handler_) {
error_handler_.Run();
- else if (!error_with_reason_handler_.is_null())
+ } else if (error_with_reason_handler_) {
error_with_reason_handler_.Run(custom_reason, description);
+ }
}
- base::Closure error_handler_;
- ConnectionErrorWithReasonCallback error_with_reason_handler_;
+ base::RepeatingClosure error_handler_;
+ RepeatingConnectionErrorWithReasonCallback error_with_reason_handler_;
PreDispatchCallback pre_dispatch_handler_;
BindingId next_binding_id_ = 0;
std::map<BindingId, std::unique_ptr<Entry>> bindings_;
+ bool is_flushing_ = false;
const Context* dispatch_context_ = nullptr;
+ BindingId dispatch_binding_;
+ base::WeakPtrFactory<BindingSetBase> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(BindingSetBase);
};
diff --git a/mojo/public/cpp/bindings/callback_helpers.h b/mojo/public/cpp/bindings/callback_helpers.h
new file mode 100644
index 0000000000..be4d97bb18
--- /dev/null
+++ b/mojo/public/cpp/bindings/callback_helpers.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_PUBLIC_CPP_BINDINGS_CALLBACK_HELPERS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_CALLBACK_HELPERS_H_
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+
+// This is a helper utility to wrap a base::OnceCallback such that if the
+// callback is destructed before it has a chance to run (e.g. the callback is
+// bound into a task and the task is dropped), it will be run the with
+// default arguments passed into WrapCallbackWithDefaultInvokeIfNotRun.
+// Alternatively, it will run the delete closure passed to
+// WrapCallbackWithDropHandler.
+//
+// These helpers are intended for use on the client side of a mojo interface,
+// where users want to know if their individual callback was dropped (e.g.
+// due to connection error). This can save the burden of tracking pending
+// mojo callbacks in a map so they can be cleaned up in the interface's
+// connection error callback.
+//
+// Caveats:
+// 1) The default form of the callback, called when the original was dropped
+// before running, may not run on the thread you expected. If this is a problem
+// for your code, DO NOT USE these helpers.
+// 2) There is no type information that indicates the wrapped object has special
+// destructor behavior. It is therefore not recommended to pass these wrapped
+// callbacks into deep call graphs where code readers could be confused whether
+// or not the Run() mehtod should be invoked.
+//
+// Example:
+// foo->DoWorkAndReturnResult(
+// WrapCallbackWithDefaultInvokeIfNotRun(
+// base::BindOnce(&Foo::OnResult, this), false));
+//
+// If the callback is destructed without running, it'll be run with "false".
+//
+// foo->DoWorkAndReturnResult(
+// WrapCallbackWithDropHandler(base::BindOnce(&Foo::OnResult, this),
+// base::BindOnce(&Foo::LogError, this, WAS_DROPPED)));
+
+namespace mojo {
+namespace internal {
+
+// First, tell the compiler CallbackWithDeleteHelper is a class template with
+// one type parameter. Then define specializations where the type is a function
+// returning void and taking zero or more arguments.
+template <typename Signature>
+class CallbackWithDeleteHelper;
+
+// Only support callbacks that return void because otherwise it is odd to call
+// the callback in the destructor and drop the return value immediately.
+template <typename... Args>
+class CallbackWithDeleteHelper<void(Args...)> {
+ public:
+ using CallbackType = base::OnceCallback<void(Args...)>;
+
+ // Bound arguments may be different to the callback signature when wrappers
+ // are used, e.g. in base::Owned and base::Unretained case, they are
+ // OwnedWrapper and UnretainedWrapper. Use BoundArgs to help handle this.
+ template <typename... BoundArgs>
+ explicit CallbackWithDeleteHelper(CallbackType callback, BoundArgs&&... args)
+ : callback_(std::move(callback)) {
+ delete_callback_ =
+ base::BindOnce(&CallbackWithDeleteHelper::Run, base::Unretained(this),
+ std::forward<BoundArgs>(args)...);
+ }
+
+ // The first int param acts to disambiguate this constructor from the template
+ // constructor above. The precendent is C++'s own operator++(int) vs
+ // operator++() to distinguish post-increment and pre-increment.
+ CallbackWithDeleteHelper(int ignored,
+ CallbackType callback,
+ base::OnceClosure delete_callback)
+ : callback_(std::move(callback)),
+ delete_callback_(std::move(delete_callback)) {}
+
+ ~CallbackWithDeleteHelper() {
+ if (delete_callback_)
+ std::move(delete_callback_).Run();
+ }
+
+ void Run(Args... args) {
+ delete_callback_.Reset();
+ std::move(callback_).Run(std::forward<Args>(args)...);
+ }
+
+ private:
+ CallbackType callback_;
+ base::OnceClosure delete_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallbackWithDeleteHelper);
+};
+
+} // namespace internal
+
+template <typename T, typename... Args>
+inline base::OnceCallback<T> WrapCallbackWithDropHandler(
+ base::OnceCallback<T> cb,
+ base::OnceClosure delete_cb) {
+ return base::BindOnce(&internal::CallbackWithDeleteHelper<T>::Run,
+ std::make_unique<internal::CallbackWithDeleteHelper<T>>(
+ 0, std::move(cb), std::move(delete_cb)));
+}
+
+template <typename T, typename... Args>
+inline base::OnceCallback<T> WrapCallbackWithDefaultInvokeIfNotRun(
+ base::OnceCallback<T> cb,
+ Args&&... args) {
+ return base::BindOnce(&internal::CallbackWithDeleteHelper<T>::Run,
+ std::make_unique<internal::CallbackWithDeleteHelper<T>>(
+ std::move(cb), std::forward<Args>(args)...));
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_CALLBACK_HELPERS_H_
diff --git a/mojo/public/cpp/bindings/clone_traits.h b/mojo/public/cpp/bindings/clone_traits.h
index 203ab34189..e7e0a14cf0 100644
--- a/mojo/public/cpp/bindings/clone_traits.h
+++ b/mojo/public/cpp/bindings/clone_traits.h
@@ -6,9 +6,9 @@
#define MOJO_PUBLIC_CPP_BINDINGS_CLONE_TRAITS_H_
#include <type_traits>
-#include <unordered_map>
#include <vector>
+#include "base/containers/flat_map.h"
#include "base/optional.h"
#include "mojo/public/cpp/bindings/lib/template_util.h"
@@ -65,9 +65,9 @@ struct CloneTraits<std::vector<T>, false> {
};
template <typename K, typename V>
-struct CloneTraits<std::unordered_map<K, V>, false> {
- static std::unordered_map<K, V> Clone(const std::unordered_map<K, V>& input) {
- std::unordered_map<K, V> result;
+struct CloneTraits<base::flat_map<K, V>, false> {
+ static base::flat_map<K, V> Clone(const base::flat_map<K, V>& input) {
+ base::flat_map<K, V> result;
for (const auto& element : input) {
result.insert(std::make_pair(mojo::Clone(element.first),
mojo::Clone(element.second)));
diff --git a/mojo/public/cpp/bindings/connection_error_callback.h b/mojo/public/cpp/bindings/connection_error_callback.h
index 306e99e45b..0b9759e6d9 100644
--- a/mojo/public/cpp/bindings/connection_error_callback.h
+++ b/mojo/public/cpp/bindings/connection_error_callback.h
@@ -9,12 +9,15 @@
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
+// These callback types accept 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<void(uint32_t /* custom_reason */,
- const std::string& /* description */)>;
+ base::OnceCallback<void(uint32_t /* custom_reason */,
+ const std::string& /* description */)>;
+using RepeatingConnectionErrorWithReasonCallback =
+ base::RepeatingCallback<void(uint32_t /* custom_reason */,
+ const std::string& /* description */)>;
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/connector.h b/mojo/public/cpp/bindings/connector.h
index cb065c174d..fa71604b1b 100644
--- a/mojo/public/cpp/bindings/connector.h
+++ b/mojo/public/cpp/bindings/connector.h
@@ -5,19 +5,22 @@
#ifndef MOJO_PUBLIC_CPP_BINDINGS_CONNECTOR_H_
#define MOJO_PUBLIC_CPP_BINDINGS_CONNECTOR_H_
+#include <atomic>
#include <memory>
+#include <utility>
#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 "base/sequence_checker.h"
+#include "base/sequenced_task_runner.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/handle_signal_tracker.h"
#include "mojo/public/cpp/system/simple_watcher.h"
namespace base {
@@ -35,28 +38,60 @@ namespace mojo {
// - 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) {
+// on the creating sequence.
+class MOJO_CPP_BINDINGS_EXPORT Connector : public MessageReceiver {
public:
enum ConnectorConfig {
- // Connector::Accept() is only called from a single thread.
+ // Connector::Accept() is only called from a single sequence.
SINGLE_THREADED_SEND,
- // Connector::Accept() is allowed to be called from multiple threads.
+ // Connector::Accept() is allowed to be called from multiple sequences.
MULTI_THREADED_SEND
};
+ // Determines how this Connector should behave with respect to serialization
+ // of outgoing messages.
+ enum class OutgoingSerializationMode {
+ // Lazy serialization. The Connector prefers to transmit serialized messages
+ // only when it knows its peer endpoint is remote. This ensures outgoing
+ // requests are unserialized by default (when possible, i.e. when generated
+ // bindings support it) and serialized only if and when necessary.
+ kLazy,
+
+ // Eager serialization. The Connector always prefers serialized messages,
+ // ensuring that interface calls will be serialized immediately before
+ // sending on the Connector.
+ kEager,
+ };
+
+ // Determines how this Connector should behave with respect to serialization
+ // of incoming messages.
+ enum class IncomingSerializationMode {
+ // Accepts and dispatches either serialized or unserialized messages. This
+ // is the only mode that should be used in production.
+ kDispatchAsIs,
+
+ // Accepts either serialized or unserialized messages, but always forces
+ // serialization (if applicable) before dispatch. Should be used only in
+ // test environments to coerce the lazy serialization of a message after
+ // transmission.
+ kSerializeBeforeDispatchForTesting,
+ };
+
// The Connector takes ownership of |message_pipe|.
Connector(ScopedMessagePipeHandle message_pipe,
ConnectorConfig config,
- scoped_refptr<base::SingleThreadTaskRunner> runner);
+ scoped_refptr<base::SequencedTaskRunner> runner);
~Connector() override;
+ // Sets outgoing serialization mode.
+ void SetOutgoingSerializationMode(OutgoingSerializationMode mode);
+ void SetIncomingSerializationMode(IncomingSerializationMode mode);
+
// 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());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
incoming_receiver_ = receiver;
}
@@ -64,21 +99,21 @@ class MOJO_CPP_BINDINGS_EXPORT Connector
// 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());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
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;
+ void set_connection_error_handler(base::OnceClosure error_handler) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ connection_error_handler_ = std::move(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());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return error_;
}
@@ -106,7 +141,7 @@ class MOJO_CPP_BINDINGS_EXPORT Connector
// Is the connector bound to a MessagePipe handle?
bool is_valid() const {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return message_pipe_.is_valid();
}
@@ -120,15 +155,16 @@ class MOJO_CPP_BINDINGS_EXPORT Connector
void ResumeIncomingMethodCallProcessing();
// MessageReceiver implementation:
+ bool PrefersSerializedMessages() override;
bool Accept(Message* message) override;
MessagePipeHandle handle() const {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return message_pipe_.get();
}
// Allows |message_pipe_| to be watched while others perform sync handle
- // watching on the same thread. Please see comments of
+ // watching on the same sequence. Please see comments of
// SyncHandleWatcher::AllowWokenUpBySyncWatchOnSameThread().
void AllowWokenUpBySyncWatchOnSameThread();
@@ -147,17 +183,22 @@ class MOJO_CPP_BINDINGS_EXPORT Connector
return sync_handle_watcher_callback_count_ > 0;
}
- base::SingleThreadTaskRunner* task_runner() const {
- return task_runner_.get();
- }
+ base::SequencedTaskRunner* 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);
+ // Allows testing environments to override the default serialization behavior
+ // of newly constructed Connector instances. Must be called before any
+ // Connector instances are constructed.
+ static void OverrideDefaultSerializationBehaviorForTesting(
+ OutgoingSerializationMode outgoing_mode,
+ IncomingSerializationMode incoming_mode);
+
private:
class ActiveDispatchTracker;
- class MessageLoopNestingObserver;
+ class RunLoopNestingObserver;
// Callback of mojo::SimpleWatcher.
void OnWatcherHandleReady(MojoResult result);
@@ -185,21 +226,25 @@ class MOJO_CPP_BINDINGS_EXPORT Connector
void EnsureSyncWatcherExists();
- base::Closure connection_error_handler_;
+ base::OnceClosure connection_error_handler_;
ScopedMessagePipeHandle message_pipe_;
MessageReceiver* incoming_receiver_ = nullptr;
- scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
std::unique_ptr<SimpleWatcher> handle_watcher_;
+ base::Optional<HandleSignalTracker> peer_remoteness_tracker_;
- bool error_ = false;
+ std::atomic<bool> error_;
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
+ OutgoingSerializationMode outgoing_serialization_mode_;
+ IncomingSerializationMode incoming_serialization_mode_;
+
+ // If sending messages is allowed from multiple sequences, |lock_| is used to
// protect modifications to |message_pipe_| and |drop_writes_|.
base::Optional<base::Lock> lock_;
@@ -209,23 +254,27 @@ class MOJO_CPP_BINDINGS_EXPORT Connector
// callback.
size_t sync_handle_watcher_callback_count_ = 0;
- base::ThreadChecker thread_checker_;
+ SEQUENCE_CHECKER(sequence_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;
+ const char* heap_profiler_tag_ = "unknown interface";
- // A cached pointer to the MessageLoopNestingObserver for the MessageLoop on
- // which this Connector was created.
- MessageLoopNestingObserver* const nesting_observer_;
+ // A cached pointer to the RunLoopNestingObserver for the thread on which this
+ // Connector was created.
+ RunLoopNestingObserver* const nesting_observer_;
// |true| iff the Connector is currently dispatching a message. Used to detect
// nested dispatch operations.
bool is_dispatching_ = false;
+#if defined(ENABLE_IPC_FUZZER)
+ std::unique_ptr<MessageReceiver> message_dumper_;
+#endif
+
// 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
diff --git a/mojo/public/cpp/bindings/enum_traits.h b/mojo/public/cpp/bindings/enum_traits.h
index 2c528f3226..f4ba5a241b 100644
--- a/mojo/public/cpp/bindings/enum_traits.h
+++ b/mojo/public/cpp/bindings/enum_traits.h
@@ -5,6 +5,8 @@
#ifndef MOJO_PUBLIC_CPP_BINDINGS_ENUM_TRAITS_H_
#define MOJO_PUBLIC_CPP_BINDINGS_ENUM_TRAITS_H_
+#include "mojo/public/cpp/bindings/lib/template_util.h"
+
namespace mojo {
// This must be specialized for any type |T| to be serialized/deserialized as a
@@ -20,7 +22,11 @@ namespace mojo {
// };
//
template <typename MojomType, typename T>
-struct EnumTraits;
+struct EnumTraits {
+ static_assert(internal::AlwaysFalse<T>::value,
+ "Cannot find the mojo::EnumTraits specialization. Did you "
+ "forget to include the corresponding header file?");
+};
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/equals_traits.h b/mojo/public/cpp/bindings/equals_traits.h
new file mode 100644
index 0000000000..d0bf7c1f3c
--- /dev/null
+++ b/mojo/public/cpp/bindings/equals_traits.h
@@ -0,0 +1,98 @@
+// 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_EQUALS_TRAITS_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_EQUALS_TRAITS_H_
+
+#include <type_traits>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/optional.h"
+#include "mojo/public/cpp/bindings/lib/template_util.h"
+
+namespace mojo {
+
+// EqualsTraits<> allows you to specify comparison functions for mapped mojo
+// objects. By default objects can be compared if they implement operator==()
+// or have a method named Equals().
+
+template <typename T>
+struct HasEqualsMethod {
+ template <typename U>
+ static char Test(decltype(&U::Equals));
+ template <typename U>
+ static int Test(...);
+ static const bool value = sizeof(Test<T>(0)) == sizeof(char);
+
+ private:
+ internal::EnsureTypeIsComplete<T> check_t_;
+};
+
+template <typename T, bool has_equals_method = HasEqualsMethod<T>::value>
+struct EqualsTraits;
+
+template <typename T>
+bool Equals(const T& a, const T& b);
+
+template <typename T>
+struct EqualsTraits<T, true> {
+ static bool Equals(const T& a, const T& b) { return a.Equals(b); }
+};
+
+template <typename T>
+struct EqualsTraits<T, false> {
+ static bool Equals(const T& a, const T& b) { return a == b; }
+};
+
+template <typename T>
+struct EqualsTraits<base::Optional<T>, false> {
+ static bool Equals(const base::Optional<T>& a, const base::Optional<T>& b) {
+ if (!a && !b)
+ return true;
+ if (!a || !b)
+ return false;
+
+ // NOTE: Not just Equals() because that's EqualsTraits<>::Equals() and we
+ // want mojo::Equals() for things like base::Optional<std::vector<T>>.
+ return mojo::Equals(*a, *b);
+ }
+};
+
+template <typename T>
+struct EqualsTraits<std::vector<T>, false> {
+ static bool Equals(const std::vector<T>& a, const std::vector<T>& b) {
+ if (a.size() != b.size())
+ return false;
+ for (size_t i = 0; i < a.size(); ++i) {
+ if (!mojo::Equals(a[i], b[i]))
+ return false;
+ }
+ return true;
+ }
+};
+
+template <typename K, typename V>
+struct EqualsTraits<base::flat_map<K, V>, false> {
+ static bool Equals(const base::flat_map<K, V>& a,
+ const base::flat_map<K, V>& b) {
+ if (a.size() != b.size())
+ return false;
+ for (const auto& element : a) {
+ auto iter = b.find(element.first);
+ if (iter == b.end() || !mojo::Equals(element.second, iter->second))
+ return false;
+ }
+ return true;
+ }
+};
+
+template <typename T>
+bool Equals(const T& a, const T& b) {
+ return EqualsTraits<T>::Equals(a, b);
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_EQUALS_TRAITS_H_
diff --git a/mojo/public/cpp/bindings/filter_chain.h b/mojo/public/cpp/bindings/filter_chain.h
index 1262f39b80..9d3f2c08c7 100644
--- a/mojo/public/cpp/bindings/filter_chain.h
+++ b/mojo/public/cpp/bindings/filter_chain.h
@@ -16,8 +16,7 @@
namespace mojo {
-class MOJO_CPP_BINDINGS_EXPORT FilterChain
- : NON_EXPORTED_BASE(public MessageReceiver) {
+class MOJO_CPP_BINDINGS_EXPORT FilterChain : public MessageReceiver {
public:
// Doesn't take ownership of |sink|. Therefore |sink| has to stay alive while
// this object is alive.
@@ -49,7 +48,7 @@ class MOJO_CPP_BINDINGS_EXPORT FilterChain
template <typename FilterType, typename... Args>
inline void FilterChain::Append(Args&&... args) {
- Append(base::MakeUnique<FilterType>(std::forward<Args>(args)...));
+ Append(std::make_unique<FilterType>(std::forward<Args>(args)...));
}
template <>
diff --git a/mojo/public/cpp/bindings/interface_endpoint_client.h b/mojo/public/cpp/bindings/interface_endpoint_client.h
index b519fe92bb..6842c7c322 100644
--- a/mojo/public/cpp/bindings/interface_endpoint_client.h
+++ b/mojo/public/cpp/bindings/interface_endpoint_client.h
@@ -9,6 +9,7 @@
#include <map>
#include <memory>
+#include <utility>
#include "base/callback.h"
#include "base/compiler_specific.h"
@@ -17,8 +18,8 @@
#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 "base/sequence_checker.h"
+#include "base/sequenced_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/disconnect_reason.h"
@@ -35,9 +36,9 @@ 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.
+// It should only be accessed and destructed on the creating sequence.
class MOJO_CPP_BINDINGS_EXPORT InterfaceEndpointClient
- : NON_EXPORTED_BASE(public MessageReceiverWithResponder) {
+ : public MessageReceiverWithResponder {
public:
// |receiver| is okay to be null. If it is not null, it must outlive this
// object.
@@ -45,34 +46,34 @@ class MOJO_CPP_BINDINGS_EXPORT InterfaceEndpointClient
MessageReceiverWithResponderStatus* receiver,
std::unique_ptr<MessageReceiver> payload_validator,
bool expect_sync_requests,
- scoped_refptr<base::SingleThreadTaskRunner> runner,
+ scoped_refptr<base::SequencedTaskRunner> 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;
+ void set_connection_error_handler(base::OnceClosure error_handler) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ error_handler_ = std::move(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;
+ ConnectionErrorWithReasonCallback error_handler) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ error_with_reason_handler_ = std::move(error_handler);
error_handler_.Reset();
}
// Returns true if an error was encountered.
bool encountered_error() const {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return encountered_error_;
}
// Returns true if this endpoint has any pending callbacks.
bool has_pending_responders() const {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return !async_responders_.empty() || !sync_responses_.empty();
}
@@ -94,6 +95,7 @@ class MOJO_CPP_BINDINGS_EXPORT InterfaceEndpointClient
// MessageReceiverWithResponder implementation:
// They must only be called when the handle is not in pending association
// state.
+ bool PrefersSerializedMessages() override;
bool Accept(Message* message) override;
bool AcceptWithResponder(Message* message,
std::unique_ptr<MessageReceiver> responder) override;
@@ -172,16 +174,16 @@ class MOJO_CPP_BINDINGS_EXPORT InterfaceEndpointClient
uint64_t next_request_id_ = 1;
- base::Closure error_handler_;
+ base::OnceClosure error_handler_;
ConnectionErrorWithReasonCallback error_with_reason_handler_;
bool encountered_error_ = false;
- scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
internal::ControlMessageProxy control_message_proxy_;
internal::ControlMessageHandler control_message_handler_;
- base::ThreadChecker thread_checker_;
+ SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<InterfaceEndpointClient> weak_ptr_factory_;
diff --git a/mojo/public/cpp/bindings/interface_endpoint_controller.h b/mojo/public/cpp/bindings/interface_endpoint_controller.h
index 8d99d4a45f..dabc3d8800 100644
--- a/mojo/public/cpp/bindings/interface_endpoint_controller.h
+++ b/mojo/public/cpp/bindings/interface_endpoint_controller.h
@@ -18,8 +18,8 @@ class 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().
+ // others perform sync handle watching on the same sequence. Please see
+ // comments of SyncHandleWatcher::AllowWokenUpBySyncWatchOnSameThread().
virtual void AllowWokenUpBySyncWatchOnSameThread() = 0;
// Watches the interface endpoint for incoming sync messages. (It also watches
diff --git a/mojo/public/cpp/bindings/interface_id.h b/mojo/public/cpp/bindings/interface_id.h
index 53475d6f78..d6128537d2 100644
--- a/mojo/public/cpp/bindings/interface_id.h
+++ b/mojo/public/cpp/bindings/interface_id.h
@@ -30,6 +30,10 @@ inline bool IsValidInterfaceId(InterfaceId id) {
return id != kInvalidInterfaceId;
}
+inline bool HasInterfaceIdNamespaceBitSet(InterfaceId id) {
+ return (id & kInterfaceIdNamespaceMask) != 0;
+}
+
} // 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
index e88be7436f..3ec522d472 100644
--- a/mojo/public/cpp/bindings/interface_ptr.h
+++ b/mojo/public/cpp/bindings/interface_ptr.h
@@ -14,8 +14,7 @@
#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 "base/sequenced_task_runner.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"
@@ -25,34 +24,39 @@ 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.
+// bound to a message pipe before the interface methods can be called. Once a
+// pointer is destroyed, it is guaranteed that pending callbacks as well as the
+// connection error handler (if registered) won't 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.
+// same sequence that bound it. If you need to move the proxy to a different
+// sequence, extract the InterfacePtrInfo (containing just the message pipe and
+// any version information) using PassInterface() on the original sequence, pass
+// it to a different sequence, and create and bind a new InterfacePtr from that
+// sequence. If an InterfacePtr is not bound to a message pipe, it may be bound
+// or destroyed on any sequence.
template <typename Interface>
class InterfacePtr {
public:
using InterfaceType = Interface;
using PtrInfoType = InterfacePtrInfo<Interface>;
+ using Proxy = typename Interface::Proxy_;
// Constructs an unbound InterfacePtr.
InterfacePtr() {}
InterfacePtr(decltype(nullptr)) {}
// Takes over the binding of another InterfacePtr.
- InterfacePtr(InterfacePtr&& other) {
+ InterfacePtr(InterfacePtr&& other) noexcept {
internal_state_.Swap(&other.internal_state_);
}
+ explicit InterfacePtr(PtrInfoType&& info) noexcept { Bind(std::move(info)); }
+
// Takes over the binding of another InterfacePtr, and closes any message pipe
// already bound to this pointer.
- InterfacePtr& operator=(InterfacePtr&& other) {
+ InterfacePtr& operator=(InterfacePtr&& other) noexcept {
reset();
internal_state_.Swap(&other.internal_state_);
return *this;
@@ -74,13 +78,12 @@ class InterfacePtr {
// 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.
+ // Optionally, |runner| is a SequencedTaskRunner bound to the current sequence
+ // on which all callbacks and connection error notifications will be
+ // dispatched. It is only useful to specify this to use a different
+ // SequencedTaskRunner than SequencedTaskRunnerHandle::Get().
void Bind(InterfacePtrInfo<Interface> info,
- scoped_refptr<base::SingleThreadTaskRunner> runner =
- base::ThreadTaskRunnerHandle::Get()) {
+ scoped_refptr<base::SequencedTaskRunner> runner = nullptr) {
reset();
if (info.is_valid())
internal_state_.Bind(std::move(info), std::move(runner));
@@ -91,11 +94,11 @@ class InterfacePtr {
// 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(); }
+ Proxy* 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(); }
+ Proxy* operator->() const { return get(); }
+ Proxy& operator*() const { return *get(); }
// Returns the version number of the interface that the remote side supports.
uint32_t version() const { return internal_state_.version(); }
@@ -124,8 +127,7 @@ class InterfacePtr {
// stimulus.
void FlushForTesting() { internal_state_.FlushForTesting(); }
- // Closes the bound message pipe (if any) and returns the pointer to the
- // unbound state.
+ // Closes the bound message pipe, if any.
void reset() {
State doomed;
internal_state_.Swap(&doomed);
@@ -143,28 +145,32 @@ class InterfacePtr {
return internal_state_.HasAssociatedInterfaces();
}
+ // Returns true if bound and awaiting a response to a message.
+ bool IsExpectingResponse() { return internal_state_.has_pending_callbacks(); }
+
// 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.
+ // called from the sequence 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_handler(base::OnceClosure error_handler) {
+ internal_state_.set_connection_error_handler(std::move(error_handler));
}
void set_connection_error_with_reason_handler(
- const ConnectionErrorWithReasonCallback& error_handler) {
- internal_state_.set_connection_error_with_reason_handler(error_handler);
+ ConnectionErrorWithReasonCallback error_handler) {
+ internal_state_.set_connection_error_with_reason_handler(
+ std::move(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).
+ // to a different sequence (see class comments for details).
//
// It is an error to call PassInterface() while:
// - there are pending responses; or
@@ -198,26 +204,10 @@ class InterfacePtr {
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<Interface> InterfacePtr::*Testable;
-
- public:
- operator Testable() const {
- return internal_state_.is_bound() ? &InterfacePtr::internal_state_
- : nullptr;
- }
+ // Allow InterfacePtr<> to be used in boolean expressions.
+ explicit operator bool() const { return internal_state_.is_bound(); }
private:
- // Forbid the == and != operators explicitly, otherwise InterfacePtr will be
- // converted to Testable to do == or != comparison.
- template <typename T>
- bool operator==(const InterfacePtr<T>& other) const = delete;
- template <typename T>
- bool operator!=(const InterfacePtr<T>& other) const = delete;
-
typedef internal::InterfacePtrState<Interface> State;
mutable State internal_state_;
@@ -229,8 +219,7 @@ class InterfacePtr {
template <typename Interface>
InterfacePtr<Interface> MakeProxy(
InterfacePtrInfo<Interface> info,
- scoped_refptr<base::SingleThreadTaskRunner> runner =
- base::ThreadTaskRunnerHandle::Get()) {
+ scoped_refptr<base::SequencedTaskRunner> runner = nullptr) {
InterfacePtr<Interface> ptr;
if (info.is_valid())
ptr.Bind(std::move(info), std::move(runner));
diff --git a/mojo/public/cpp/bindings/interface_ptr_info.h b/mojo/public/cpp/bindings/interface_ptr_info.h
index 0b2d8089c4..7003043417 100644
--- a/mojo/public/cpp/bindings/interface_ptr_info.h
+++ b/mojo/public/cpp/bindings/interface_ptr_info.h
@@ -5,7 +5,8 @@
#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_INFO_H_
#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_INFO_H_
-#include <stdint.h>
+#include <cstddef>
+#include <cstdint>
#include <utility>
#include "base/macros.h"
@@ -19,6 +20,7 @@ template <typename Interface>
class InterfacePtrInfo {
public:
InterfacePtrInfo() : version_(0u) {}
+ InterfacePtrInfo(std::nullptr_t) : InterfacePtrInfo() {}
InterfacePtrInfo(ScopedMessagePipeHandle handle, uint32_t version)
: handle_(std::move(handle)), version_(version) {}
@@ -51,6 +53,9 @@ class InterfacePtrInfo {
uint32_t version() const { return version_; }
void set_version(uint32_t version) { version_ = version; }
+ // Allow InterfacePtrInfo<> to be used in boolean expressions.
+ explicit operator bool() const { return handle_.is_valid(); }
+
private:
ScopedMessagePipeHandle handle_;
uint32_t version_;
diff --git a/mojo/public/cpp/bindings/interface_ptr_set.h b/mojo/public/cpp/bindings/interface_ptr_set.h
index 09a268229d..17f90b1e7f 100644
--- a/mojo/public/cpp/bindings/interface_ptr_set.h
+++ b/mojo/public/cpp/bindings/interface_ptr_set.h
@@ -5,15 +5,19 @@
#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_SET_H_
#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_SET_H_
+#include <map>
#include <utility>
-#include <vector>
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
+#include "base/stl_util.h"
#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
#include "mojo/public/cpp/bindings/interface_ptr.h"
namespace mojo {
+
+using InterfacePtrSetElementId = size_t;
+
namespace internal {
// TODO(blundell): This class should be rewritten to be structured
@@ -26,29 +30,61 @@ class PtrSet {
PtrSet() {}
~PtrSet() { CloseAll(); }
- void AddPtr(Ptr<Interface> ptr) {
+ InterfacePtrSetElementId AddPtr(Ptr<Interface> ptr) {
+ InterfacePtrSetElementId id = next_ptr_id_++;
auto weak_interface_ptr = new Element(std::move(ptr));
- ptrs_.push_back(weak_interface_ptr->GetWeakPtr());
+ ptrs_.emplace(std::piecewise_construct, std::forward_as_tuple(id),
+ std::forward_as_tuple(weak_interface_ptr->GetWeakPtr()));
ClearNullPtrs();
+ return id;
}
template <typename FunctionType>
void ForAllPtrs(FunctionType function) {
for (const auto& it : ptrs_) {
- if (it)
- function(it->get());
+ if (it.second)
+ function(it.second->get());
}
ClearNullPtrs();
}
void CloseAll() {
for (const auto& it : ptrs_) {
- if (it)
- it->Close();
+ if (it.second)
+ it.second->Close();
}
ptrs_.clear();
}
+ bool empty() const { return ptrs_.empty(); }
+
+ // Calls FlushForTesting on all Ptrs sequentially. Since each call is a
+ // blocking operation, may be very slow as the number of pointers increases.
+ void FlushForTesting() {
+ for (const auto& it : ptrs_) {
+ if (it.second)
+ it.second->FlushForTesting();
+ }
+ ClearNullPtrs();
+ }
+
+ bool HasPtr(InterfacePtrSetElementId id) {
+ return ptrs_.find(id) != ptrs_.end();
+ }
+
+ Ptr<Interface> RemovePtr(InterfacePtrSetElementId id) {
+ auto it = ptrs_.find(id);
+ if (it == ptrs_.end())
+ return Ptr<Interface>();
+ Ptr<Interface> ptr;
+ if (it->second) {
+ ptr = it->second->Take();
+ delete it->second.get();
+ }
+ ptrs_.erase(it);
+ return ptr;
+ }
+
private:
class Element {
public:
@@ -69,10 +105,14 @@ class PtrSet {
Interface* get() { return ptr_.get(); }
+ Ptr<Interface> Take() { return std::move(ptr_); }
+
base::WeakPtr<Element> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
+ void FlushForTesting() { ptr_.FlushForTesting(); }
+
private:
static void DeleteElement(Element* element) { delete element; }
@@ -83,14 +123,11 @@ class PtrSet {
};
void ClearNullPtrs() {
- ptrs_.erase(std::remove_if(ptrs_.begin(), ptrs_.end(),
- [](const base::WeakPtr<Element>& p) {
- return p.get() == nullptr;
- }),
- ptrs_.end());
+ base::EraseIf(ptrs_, [](const auto& pair) { return !(pair.second); });
}
- std::vector<base::WeakPtr<Element>> ptrs_;
+ InterfacePtrSetElementId next_ptr_id_ = 0;
+ std::map<InterfacePtrSetElementId, base::WeakPtr<Element>> ptrs_;
};
} // namespace internal
diff --git a/mojo/public/cpp/bindings/interface_request.h b/mojo/public/cpp/bindings/interface_request.h
index 29d883615e..ccfdb3716e 100644
--- a/mojo/public/cpp/bindings/interface_request.h
+++ b/mojo/public/cpp/bindings/interface_request.h
@@ -11,7 +11,6 @@
#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"
@@ -33,18 +32,8 @@ class InterfaceRequest {
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<Interface>* ptr,
- scoped_refptr<base::SingleThreadTaskRunner> runner =
- base::ThreadTaskRunnerHandle::Get()) {
- MessagePipe pipe;
- ptr->Bind(InterfacePtrInfo<Interface>(std::move(pipe.handle0), 0u),
- std::move(runner));
- Bind(std::move(pipe.handle1));
- }
+ explicit InterfaceRequest(ScopedMessagePipeHandle handle)
+ : handle_(std::move(handle)) {}
// Takes the message pipe from another InterfaceRequest.
InterfaceRequest(InterfaceRequest&& other) {
@@ -62,14 +51,11 @@ class InterfaceRequest {
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(); }
+ explicit operator bool() const { return handle_.is_valid(); }
+
// Removes the message pipe from the request and returns it.
ScopedMessagePipeHandle PassMessagePipe() { return std::move(handle_); }
@@ -102,16 +88,6 @@ class InterfaceRequest {
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 <typename Interface>
-InterfaceRequest<Interface> MakeRequest(ScopedMessagePipeHandle handle) {
- InterfaceRequest<Interface> 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
@@ -158,9 +134,21 @@ InterfaceRequest<Interface> MakeRequest(ScopedMessagePipeHandle handle) {
template <typename Interface>
InterfaceRequest<Interface> MakeRequest(
InterfacePtr<Interface>* ptr,
- scoped_refptr<base::SingleThreadTaskRunner> runner =
- base::ThreadTaskRunnerHandle::Get()) {
- return InterfaceRequest<Interface>(ptr, runner);
+ scoped_refptr<base::SingleThreadTaskRunner> runner = nullptr) {
+ MessagePipe pipe;
+ ptr->Bind(InterfacePtrInfo<Interface>(std::move(pipe.handle0), 0u),
+ std::move(runner));
+ return InterfaceRequest<Interface>(std::move(pipe.handle1));
+}
+
+// Similar to the constructor above, but binds one end of the message pipe to
+// an InterfacePtrInfo instance.
+template <typename Interface>
+InterfaceRequest<Interface> MakeRequest(InterfacePtrInfo<Interface>* ptr_info) {
+ MessagePipe pipe;
+ ptr_info->set_handle(std::move(pipe.handle0));
+ ptr_info->set_version(0u);
+ return InterfaceRequest<Interface>(std::move(pipe.handle1));
}
// Fuses an InterfaceRequest<T> endpoint with an InterfacePtrInfo<T> endpoint.
diff --git a/mojo/public/cpp/bindings/lib/array_internal.h b/mojo/public/cpp/bindings/lib/array_internal.h
index eecfcfbc28..574be9b6f5 100644
--- a/mojo/public/cpp/bindings/lib/array_internal.h
+++ b/mojo/public/cpp/bindings/lib/array_internal.h
@@ -11,9 +11,10 @@
#include <limits>
#include <new>
+#include "base/component_export.h"
#include "base/logging.h"
+#include "base/macros.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"
@@ -29,13 +30,15 @@ namespace internal {
template <typename K, typename V>
class Map_Data;
-MOJO_CPP_BINDINGS_EXPORT std::string
-MakeMessageWithArrayIndex(const char* message, size_t size, size_t index);
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+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);
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+std::string MakeMessageWithExpectedArraySize(const char* message,
+ size_t size,
+ size_t expected_size);
template <typename T>
struct ArrayDataTraits {
@@ -68,7 +71,7 @@ template <>
struct ArrayDataTraits<bool> {
// Helper class to emulate a reference to a bool, used for direct element
// access.
- class MOJO_CPP_BINDINGS_EXPORT BitRef {
+ class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) BitRef {
public:
~BitRef();
BitRef& operator=(bool value);
@@ -268,17 +271,35 @@ class Array_Data {
std::is_same<T, Handle_Data>::value>;
using Element = T;
- // Returns null if |num_elements| or the corresponding storage size cannot be
- // stored in uint32_t.
- static Array_Data<T>* New(size_t num_elements, Buffer* buf) {
- if (num_elements > Traits::kMaxNumElements)
- return nullptr;
+ class BufferWriter {
+ public:
+ BufferWriter() = default;
+
+ void Allocate(size_t num_elements, Buffer* buffer) {
+ if (num_elements > Traits::kMaxNumElements)
+ return;
+
+ uint32_t num_bytes =
+ Traits::GetStorageSize(static_cast<uint32_t>(num_elements));
+ buffer_ = buffer;
+ index_ = buffer_->Allocate(num_bytes);
+ new (data())
+ Array_Data<T>(num_bytes, static_cast<uint32_t>(num_elements));
+ }
- uint32_t num_bytes =
- Traits::GetStorageSize(static_cast<uint32_t>(num_elements));
- return new (buf->Allocate(num_bytes))
- Array_Data<T>(num_bytes, static_cast<uint32_t>(num_elements));
- }
+ bool is_null() const { return !buffer_; }
+ Array_Data<T>* data() {
+ DCHECK(!is_null());
+ return buffer_->Get<Array_Data<T>>(index_);
+ }
+ Array_Data<T>* operator->() { return data(); }
+
+ private:
+ Buffer* buffer_ = nullptr;
+ size_t index_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferWriter);
+ };
static bool Validate(const void* data,
ValidationContext* validation_context,
diff --git a/mojo/public/cpp/bindings/lib/array_serialization.h b/mojo/public/cpp/bindings/lib/array_serialization.h
index d2f8ecfd72..8323b5f9a1 100644
--- a/mojo/public/cpp/bindings/lib/array_serialization.h
+++ b/mojo/public/cpp/bindings/lib/array_serialization.h
@@ -112,20 +112,19 @@ struct ArraySerializer<
using DataElement = typename Data::Element;
using Element = typename MojomType::Element;
using Traits = ArrayTraits<UserType>;
+ using BufferWriter = typename Data::BufferWriter;
static_assert(std::is_same<Element, DataElement>::value,
"Incorrect array serializer");
- static_assert(std::is_same<Element, typename Traits::Element>::value,
- "Incorrect array serializer");
-
- static size_t GetSerializedSize(UserTypeIterator* input,
- SerializationContext* context) {
- return sizeof(Data) + Align(input->GetSize() * sizeof(DataElement));
- }
+ static_assert(
+ std::is_same<
+ Element,
+ typename std::remove_const<typename Traits::Element>::type>::value,
+ "Incorrect array serializer");
static void SerializeElements(UserTypeIterator* input,
Buffer* buf,
- Data* output,
+ BufferWriter* writer,
const ContainerValidateParams* validate_params,
SerializationContext* context) {
DCHECK(!validate_params->element_is_nullable)
@@ -138,6 +137,7 @@ struct ArraySerializer<
return;
auto data = input->GetDataIfExists();
+ Data* output = writer->data();
if (data) {
memcpy(output->storage(), data, size * sizeof(DataElement));
} else {
@@ -180,18 +180,14 @@ struct ArraySerializer<
using DataElement = typename Data::Element;
using Element = typename MojomType::Element;
using Traits = ArrayTraits<UserType>;
+ using BufferWriter = typename Data::BufferWriter;
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,
+ BufferWriter* writer,
const ContainerValidateParams* validate_params,
SerializationContext* context) {
DCHECK(!validate_params->element_is_nullable)
@@ -199,6 +195,7 @@ struct ArraySerializer<
DCHECK(!validate_params->element_validate_params)
<< "Primitive type should not have array validate params";
+ Data* output = writer->data();
size_t size = input->GetSize();
for (size_t i = 0; i < size; ++i)
Serialize<Element>(input->GetNext(), output->storage() + i);
@@ -231,18 +228,14 @@ struct ArraySerializer<MojomType,
using UserType = typename std::remove_const<MaybeConstUserType>::type;
using Traits = ArrayTraits<UserType>;
using Data = typename MojomTypeTraits<MojomType>::Data;
+ using BufferWriter = typename Data::BufferWriter;
static_assert(std::is_same<bool, typename Traits::Element>::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,
+ BufferWriter* writer,
const ContainerValidateParams* validate_params,
SerializationContext* context) {
DCHECK(!validate_params->element_is_nullable)
@@ -250,6 +243,7 @@ struct ArraySerializer<MojomType,
DCHECK(!validate_params->element_validate_params)
<< "Primitive type should not have array validate params";
+ Data* output = writer->data();
size_t size = input->GetSize();
for (size_t i = 0; i < size; ++i)
output->at(i) = input->GetNext();
@@ -278,37 +272,23 @@ struct ArraySerializer<
BelongsTo<typename MojomType::Element,
MojomTypeCategory::ASSOCIATED_INTERFACE |
MojomTypeCategory::ASSOCIATED_INTERFACE_REQUEST |
- MojomTypeCategory::HANDLE |
- MojomTypeCategory::INTERFACE |
+ MojomTypeCategory::HANDLE | MojomTypeCategory::INTERFACE |
MojomTypeCategory::INTERFACE_REQUEST>::value>::type> {
using UserType = typename std::remove_const<MaybeConstUserType>::type;
using Data = typename MojomTypeTraits<MojomType>::Data;
using Element = typename MojomType::Element;
using Traits = ArrayTraits<UserType>;
-
- static size_t GetSerializedSize(UserTypeIterator* input,
- SerializationContext* context) {
- size_t element_count = input->GetSize();
- if (BelongsTo<Element,
- MojomTypeCategory::ASSOCIATED_INTERFACE |
- MojomTypeCategory::ASSOCIATED_INTERFACE_REQUEST>::value) {
- for (size_t i = 0; i < element_count; ++i) {
- typename UserTypeIterator::GetNextResult next = input->GetNext();
- size_t size = PrepareToSerialize<Element>(next, context);
- DCHECK_EQ(size, 0u);
- }
- }
- return sizeof(Data) + Align(element_count * sizeof(typename Data::Element));
- }
+ using BufferWriter = typename Data::BufferWriter;
static void SerializeElements(UserTypeIterator* input,
Buffer* buf,
- Data* output,
+ BufferWriter* writer,
const ContainerValidateParams* validate_params,
SerializationContext* context) {
DCHECK(!validate_params->element_validate_params)
<< "Handle or interface type should not have array validate params";
+ Data* output = writer->data();
size_t size = input->GetSize();
for (size_t i = 0; i < size; ++i) {
typename UserTypeIterator::GetNextResult next = input->GetNext();
@@ -360,35 +340,27 @@ struct ArraySerializer<MojomType,
using UserType = typename std::remove_const<MaybeConstUserType>::type;
using Data = typename MojomTypeTraits<MojomType>::Data;
using Element = typename MojomType::Element;
- using DataElementPtr = typename MojomTypeTraits<Element>::Data*;
+ using DataElementWriter =
+ typename MojomTypeTraits<Element>::Data::BufferWriter;
using Traits = ArrayTraits<UserType>;
-
- 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<Element>(next, context);
- }
- return size;
- }
+ using BufferWriter = typename Data::BufferWriter;
static void SerializeElements(UserTypeIterator* input,
Buffer* buf,
- Data* output,
+ BufferWriter* writer,
const ContainerValidateParams* validate_params,
SerializationContext* context) {
size_t size = input->GetSize();
for (size_t i = 0; i < size; ++i) {
- DataElementPtr data_ptr;
+ DataElementWriter data_writer;
typename UserTypeIterator::GetNextResult next = input->GetNext();
- SerializeCaller<Element>::Run(next, buf, &data_ptr,
+ SerializeCaller<Element>::Run(next, buf, &data_writer,
validate_params->element_validate_params,
context);
- output->at(i).Set(data_ptr);
+ writer->data()->at(i).Set(data_writer.is_null() ? nullptr
+ : data_writer.data());
MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
- !validate_params->element_is_nullable && !data_ptr,
+ !validate_params->element_is_nullable && data_writer.is_null(),
VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
MakeMessageWithArrayIndex("null in array expecting valid pointers",
size, i));
@@ -417,10 +389,10 @@ struct ArraySerializer<MojomType,
template <typename InputElementType>
static void Run(InputElementType&& input,
Buffer* buf,
- DataElementPtr* output,
+ DataElementWriter* writer,
const ContainerValidateParams* validate_params,
SerializationContext* context) {
- Serialize<T>(std::forward<InputElementType>(input), buf, output, context);
+ Serialize<T>(std::forward<InputElementType>(input), buf, writer, context);
}
};
@@ -429,10 +401,10 @@ struct ArraySerializer<MojomType,
template <typename InputElementType>
static void Run(InputElementType&& input,
Buffer* buf,
- DataElementPtr* output,
+ DataElementWriter* writer,
const ContainerValidateParams* validate_params,
SerializationContext* context) {
- Serialize<T>(std::forward<InputElementType>(input), buf, output,
+ Serialize<T>(std::forward<InputElementType>(input), buf, writer,
validate_params, context);
}
};
@@ -451,33 +423,24 @@ struct ArraySerializer<
using UserType = typename std::remove_const<MaybeConstUserType>::type;
using Data = typename MojomTypeTraits<MojomType>::Data;
using Element = typename MojomType::Element;
+ using ElementWriter = typename Data::Element::BufferWriter;
using Traits = ArrayTraits<UserType>;
-
- 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<Element>(next, false, context);
- }
- return size;
- }
+ using BufferWriter = typename Data::BufferWriter;
static void SerializeElements(UserTypeIterator* input,
Buffer* buf,
- Data* output,
+ BufferWriter* writer,
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;
+ ElementWriter result;
+ result.AllocateInline(buf, writer->data()->storage() + i);
typename UserTypeIterator::GetNextResult next = input->GetNext();
Serialize<Element>(next, buf, &result, true, context);
MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
- !validate_params->element_is_nullable && output->at(i).is_null(),
+ !validate_params->element_is_nullable &&
+ writer->data()->at(i).is_null(),
VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
MakeMessageWithArrayIndex("null in array expecting valid unions",
size, i));
@@ -506,38 +469,27 @@ struct Serializer<ArrayDataView<Element>, MaybeConstUserType> {
MaybeConstUserType,
ArrayIterator<Traits, MaybeConstUserType>>;
using Data = typename MojomTypeTraits<ArrayDataView<Element>>::Data;
-
- static size_t PrepareToSerialize(MaybeConstUserType& input,
- SerializationContext* context) {
- if (CallIsNullIfExists<Traits>(input))
- return 0;
- ArrayIterator<Traits, MaybeConstUserType> iterator(input);
- return Impl::GetSerializedSize(&iterator, context);
- }
+ using BufferWriter = typename Data::BufferWriter;
static void Serialize(MaybeConstUserType& input,
Buffer* buf,
- Data** output,
+ BufferWriter* writer,
const ContainerValidateParams* validate_params,
SerializationContext* context) {
- if (!CallIsNullIfExists<Traits>(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<Traits, MaybeConstUserType> iterator(input);
- Impl::SerializeElements(&iterator, buf, result, validate_params,
- context);
- }
- *output = result;
- } else {
- *output = nullptr;
- }
+ if (CallIsNullIfExists<Traits>(input))
+ return;
+
+ const size_t size = Traits::GetSize(input);
+ MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
+ validate_params->expected_num_elements != 0 &&
+ size != validate_params->expected_num_elements,
+ internal::VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER,
+ internal::MakeMessageWithExpectedArraySize(
+ "fixed-size array has wrong number of elements", size,
+ validate_params->expected_num_elements));
+ writer->Allocate(size, buf);
+ ArrayIterator<Traits, MaybeConstUserType> iterator(input);
+ Impl::SerializeElements(&iterator, buf, writer, validate_params, context);
}
static bool Deserialize(Data* input,
diff --git a/mojo/public/cpp/bindings/lib/associated_binding.cc b/mojo/public/cpp/bindings/lib/associated_binding.cc
index 6788e68e07..c7eddc2372 100644
--- a/mojo/public/cpp/bindings/lib/associated_binding.cc
+++ b/mojo/public/cpp/bindings/lib/associated_binding.cc
@@ -4,6 +4,9 @@
#include "mojo/public/cpp/bindings/associated_binding.h"
+#include "base/single_thread_task_runner.h"
+#include "mojo/public/cpp/bindings/lib/task_runner_helper.h"
+
namespace mojo {
AssociatedBindingBase::AssociatedBindingBase() {}
@@ -27,15 +30,16 @@ void AssociatedBindingBase::CloseWithReason(uint32_t custom_reason,
}
void AssociatedBindingBase::set_connection_error_handler(
- const base::Closure& error_handler) {
+ base::OnceClosure error_handler) {
DCHECK(is_bound());
- endpoint_client_->set_connection_error_handler(error_handler);
+ endpoint_client_->set_connection_error_handler(std::move(error_handler));
}
void AssociatedBindingBase::set_connection_error_with_reason_handler(
- const ConnectionErrorWithReasonCallback& error_handler) {
+ ConnectionErrorWithReasonCallback error_handler) {
DCHECK(is_bound());
- endpoint_client_->set_connection_error_with_reason_handler(error_handler);
+ endpoint_client_->set_connection_error_with_reason_handler(
+ std::move(error_handler));
}
void AssociatedBindingBase::FlushForTesting() {
@@ -56,7 +60,9 @@ void AssociatedBindingBase::BindImpl(
endpoint_client_.reset(new InterfaceEndpointClient(
std::move(handle), receiver, std::move(payload_validator),
- expect_sync_requests, std::move(runner), interface_version));
+ expect_sync_requests,
+ internal::GetTaskRunnerToUseFromUserProvidedTaskRunner(std::move(runner)),
+ interface_version));
}
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc b/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc
index 78281eda9a..453e47a995 100644
--- a/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc
+++ b/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc
@@ -6,12 +6,12 @@
namespace mojo {
-void GetIsolatedInterface(ScopedInterfaceEndpointHandle handle) {
+void AssociateWithDisconnectedPipe(ScopedInterfaceEndpointHandle handle) {
MessagePipe pipe;
scoped_refptr<internal::MultiplexRouter> router =
- new internal::MultiplexRouter(std::move(pipe.handle0),
- internal::MultiplexRouter::MULTI_INTERFACE,
- false, base::ThreadTaskRunnerHandle::Get());
+ new internal::MultiplexRouter(
+ std::move(pipe.handle0), internal::MultiplexRouter::MULTI_INTERFACE,
+ false, base::SequencedTaskRunnerHandle::Get());
router->AssociateInterface(std::move(handle));
}
diff --git a/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.cc b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.cc
new file mode 100644
index 0000000000..dd3a2510f1
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.cc
@@ -0,0 +1,81 @@
+// 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/lib/associated_interface_ptr_state.h"
+
+#include "mojo/public/cpp/bindings/lib/task_runner_helper.h"
+
+namespace mojo {
+namespace internal {
+
+AssociatedInterfacePtrStateBase::AssociatedInterfacePtrStateBase() = default;
+
+AssociatedInterfacePtrStateBase::~AssociatedInterfacePtrStateBase() = default;
+
+void AssociatedInterfacePtrStateBase::QueryVersion(
+ const base::Callback<void(uint32_t)>& callback) {
+ // It is safe to capture |this| because the callback won't be run after this
+ // object goes away.
+ endpoint_client_->QueryVersion(
+ base::Bind(&AssociatedInterfacePtrStateBase::OnQueryVersion,
+ base::Unretained(this), callback));
+}
+
+void AssociatedInterfacePtrStateBase::RequireVersion(uint32_t version) {
+ if (version <= version_)
+ return;
+
+ version_ = version;
+ endpoint_client_->RequireVersion(version);
+}
+
+void AssociatedInterfacePtrStateBase::OnQueryVersion(
+ const base::Callback<void(uint32_t)>& callback,
+ uint32_t version) {
+ version_ = version;
+ callback.Run(version);
+}
+
+void AssociatedInterfacePtrStateBase::FlushForTesting() {
+ endpoint_client_->FlushForTesting();
+}
+
+void AssociatedInterfacePtrStateBase::CloseWithReason(
+ uint32_t custom_reason,
+ const std::string& description) {
+ endpoint_client_->CloseWithReason(custom_reason, description);
+}
+
+void AssociatedInterfacePtrStateBase::Swap(
+ AssociatedInterfacePtrStateBase* other) {
+ using std::swap;
+ swap(other->endpoint_client_, endpoint_client_);
+ swap(other->version_, version_);
+}
+
+void AssociatedInterfacePtrStateBase::Bind(
+ ScopedInterfaceEndpointHandle handle,
+ uint32_t version,
+ std::unique_ptr<MessageReceiver> validator,
+ scoped_refptr<base::SequencedTaskRunner> runner) {
+ DCHECK(!endpoint_client_);
+ DCHECK_EQ(0u, version_);
+ DCHECK(handle.is_valid());
+
+ version_ = version;
+ // The version is only queried from the client so the value passed here
+ // will not be used.
+ endpoint_client_ = std::make_unique<InterfaceEndpointClient>(
+ std::move(handle), nullptr, std::move(validator), false,
+ GetTaskRunnerToUseFromUserProvidedTaskRunner(std::move(runner)), 0u);
+}
+
+ScopedInterfaceEndpointHandle AssociatedInterfacePtrStateBase::PassHandle() {
+ auto handle = endpoint_client_->PassHandle();
+ endpoint_client_.reset();
+ return handle;
+}
+
+} // namespace internal
+} // 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
index a4b51882d2..79ec2bec93 100644
--- a/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h
+++ b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h
@@ -17,9 +17,10 @@
#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/sequenced_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/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/interface_id.h"
@@ -29,77 +30,17 @@
namespace mojo {
namespace internal {
-template <typename Interface>
-class AssociatedInterfacePtrState {
+class MOJO_CPP_BINDINGS_EXPORT AssociatedInterfacePtrStateBase {
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();
- }
+ AssociatedInterfacePtrStateBase();
+ ~AssociatedInterfacePtrStateBase();
uint32_t version() const { return version_; }
- void QueryVersion(const base::Callback<void(uint32_t)>& 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<Interface> info,
- scoped_refptr<base::SingleThreadTaskRunner> 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<Interface> PassInterface() {
- ScopedInterfaceEndpointHandle handle = endpoint_client_->PassHandle();
- endpoint_client_.reset();
- proxy_.reset();
- return AssociatedInterfacePtrInfo<Interface>(std::move(handle), version_);
- }
+ void QueryVersion(const base::Callback<void(uint32_t)>& callback);
+ void RequireVersion(uint32_t version);
+ void FlushForTesting();
+ void CloseWithReason(uint32_t custom_reason, const std::string& description);
bool is_bound() const { return !!endpoint_client_; }
@@ -107,15 +48,16 @@ class AssociatedInterfacePtrState {
return endpoint_client_ ? endpoint_client_->encountered_error() : false;
}
- void set_connection_error_handler(const base::Closure& error_handler) {
+ void set_connection_error_handler(base::OnceClosure error_handler) {
DCHECK(endpoint_client_);
- endpoint_client_->set_connection_error_handler(error_handler);
+ endpoint_client_->set_connection_error_handler(std::move(error_handler));
}
void set_connection_error_with_reason_handler(
- const ConnectionErrorWithReasonCallback& error_handler) {
+ ConnectionErrorWithReasonCallback error_handler) {
DCHECK(endpoint_client_);
- endpoint_client_->set_connection_error_with_reason_handler(error_handler);
+ endpoint_client_->set_connection_error_with_reason_handler(
+ std::move(error_handler));
}
// Returns true if bound and awaiting a response to a message.
@@ -134,19 +76,62 @@ class AssociatedInterfacePtrState {
endpoint_client_->AcceptWithResponder(&message, std::move(responder));
}
+ protected:
+ void Swap(AssociatedInterfacePtrStateBase* other);
+ void Bind(ScopedInterfaceEndpointHandle handle,
+ uint32_t version,
+ std::unique_ptr<MessageReceiver> validator,
+ scoped_refptr<base::SequencedTaskRunner> runner);
+ ScopedInterfaceEndpointHandle PassHandle();
+
+ InterfaceEndpointClient* endpoint_client() { return endpoint_client_.get(); }
+
private:
+ void OnQueryVersion(const base::Callback<void(uint32_t)>& callback,
+ uint32_t version);
+
+ std::unique_ptr<InterfaceEndpointClient> endpoint_client_;
+ uint32_t version_ = 0;
+};
+
+template <typename Interface>
+class AssociatedInterfacePtrState : public AssociatedInterfacePtrStateBase {
+ public:
using Proxy = typename Interface::Proxy_;
- void OnQueryVersion(const base::Callback<void(uint32_t)>& callback,
- uint32_t version) {
- version_ = version;
- callback.Run(version);
+ AssociatedInterfacePtrState() {}
+ ~AssociatedInterfacePtrState() = default;
+
+ Proxy* instance() {
+ // This will be null if the object is not bound.
+ return proxy_.get();
}
- std::unique_ptr<InterfaceEndpointClient> endpoint_client_;
- std::unique_ptr<Proxy> proxy_;
+ void Swap(AssociatedInterfacePtrState* other) {
+ AssociatedInterfacePtrStateBase::Swap(other);
+ std::swap(other->proxy_, proxy_);
+ }
+
+ void Bind(AssociatedInterfacePtrInfo<Interface> info,
+ scoped_refptr<base::SequencedTaskRunner> runner) {
+ DCHECK(!proxy_);
+ AssociatedInterfacePtrStateBase::Bind(
+ info.PassHandle(), info.version(),
+ std::make_unique<typename Interface::ResponseValidator_>(),
+ std::move(runner));
+ proxy_.reset(new Proxy(endpoint_client()));
+ }
+
+ // After this method is called, the object is in an invalid state and
+ // shouldn't be reused.
+ AssociatedInterfacePtrInfo<Interface> PassInterface() {
+ AssociatedInterfacePtrInfo<Interface> info(PassHandle(), version());
+ proxy_.reset();
+ return info;
+ }
- uint32_t version_;
+ private:
+ std::unique_ptr<Proxy> proxy_;
DISALLOW_COPY_AND_ASSIGN(AssociatedInterfacePtrState);
};
diff --git a/mojo/public/cpp/bindings/lib/binding_state.cc b/mojo/public/cpp/bindings/lib/binding_state.cc
index b34cb47e28..bb4a20f39b 100644
--- a/mojo/public/cpp/bindings/lib/binding_state.cc
+++ b/mojo/public/cpp/bindings/lib/binding_state.cc
@@ -4,10 +4,12 @@
#include "mojo/public/cpp/bindings/lib/binding_state.h"
+#include "mojo/public/cpp/bindings/lib/task_runner_helper.h"
+
namespace mojo {
namespace internal {
-BindingStateBase::BindingStateBase() = default;
+BindingStateBase::BindingStateBase() : weak_ptr_factory_(this) {}
BindingStateBase::~BindingStateBase() = default;
@@ -24,6 +26,7 @@ void BindingStateBase::PauseIncomingMethodCallProcessing() {
DCHECK(router_);
router_->PauseIncomingMethodCallProcessing();
}
+
void BindingStateBase::ResumeIncomingMethodCallProcessing() {
DCHECK(router_);
router_->ResumeIncomingMethodCallProcessing();
@@ -51,6 +54,17 @@ void BindingStateBase::CloseWithReason(uint32_t custom_reason,
Close();
}
+ReportBadMessageCallback BindingStateBase::GetBadMessageCallback() {
+ return base::BindOnce(
+ [](ReportBadMessageCallback inner_callback,
+ base::WeakPtr<BindingStateBase> binding, const std::string& error) {
+ std::move(inner_callback).Run(error);
+ if (binding)
+ binding->Close();
+ },
+ mojo::GetBadMessageCallback(), weak_ptr_factory_.GetWeakPtr());
+}
+
void BindingStateBase::FlushForTesting() {
endpoint_client_->FlushForTesting();
}
@@ -60,6 +74,10 @@ void BindingStateBase::EnableTestingMode() {
router_->EnableTestingMode();
}
+scoped_refptr<internal::MultiplexRouter> BindingStateBase::RouterForTesting() {
+ return router_;
+}
+
void BindingStateBase::BindInternal(
ScopedMessagePipeHandle handle,
scoped_refptr<base::SingleThreadTaskRunner> runner,
@@ -69,21 +87,25 @@ void BindingStateBase::BindInternal(
bool has_sync_methods,
MessageReceiverWithResponderStatus* stub,
uint32_t interface_version) {
- DCHECK(!router_);
+ DCHECK(!is_bound()) << "Attempting to bind interface that is already bound: "
+ << interface_name;
+ auto sequenced_runner =
+ GetTaskRunnerToUseFromUserProvidedTaskRunner(std::move(runner));
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_ =
+ new MultiplexRouter(std::move(handle), config, false, sequenced_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));
+ std::move(request_validator), has_sync_methods,
+ std::move(sequenced_runner), interface_version));
}
} // namesapce internal
diff --git a/mojo/public/cpp/bindings/lib/binding_state.h b/mojo/public/cpp/bindings/lib/binding_state.h
index 0b0dbee002..d1c561c748 100644
--- a/mojo/public/cpp/bindings/lib/binding_state.h
+++ b/mojo/public/cpp/bindings/lib/binding_state.h
@@ -15,6 +15,7 @@
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner.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"
@@ -50,15 +51,18 @@ class MOJO_CPP_BINDINGS_EXPORT BindingStateBase {
void Close();
void CloseWithReason(uint32_t custom_reason, const std::string& description);
- void set_connection_error_handler(const base::Closure& error_handler) {
+ void RaiseError() { endpoint_client_->RaiseError(); }
+
+ void set_connection_error_handler(base::OnceClosure error_handler) {
DCHECK(is_bound());
- endpoint_client_->set_connection_error_handler(error_handler);
+ endpoint_client_->set_connection_error_handler(std::move(error_handler));
}
void set_connection_error_with_reason_handler(
- const ConnectionErrorWithReasonCallback& error_handler) {
+ ConnectionErrorWithReasonCallback error_handler) {
DCHECK(is_bound());
- endpoint_client_->set_connection_error_with_reason_handler(error_handler);
+ endpoint_client_->set_connection_error_with_reason_handler(
+ std::move(error_handler));
}
bool is_bound() const { return !!router_; }
@@ -68,10 +72,14 @@ class MOJO_CPP_BINDINGS_EXPORT BindingStateBase {
return router_->handle();
}
+ ReportBadMessageCallback GetBadMessageCallback();
+
void FlushForTesting();
void EnableTestingMode();
+ scoped_refptr<internal::MultiplexRouter> RouterForTesting();
+
protected:
void BindInternal(ScopedMessagePipeHandle handle,
scoped_refptr<base::SingleThreadTaskRunner> runner,
@@ -84,6 +92,8 @@ class MOJO_CPP_BINDINGS_EXPORT BindingStateBase {
scoped_refptr<internal::MultiplexRouter> router_;
std::unique_ptr<InterfaceEndpointClient> endpoint_client_;
+
+ base::WeakPtrFactory<BindingStateBase> weak_ptr_factory_;
};
template <typename Interface, typename ImplRefTraits>
@@ -101,20 +111,24 @@ class BindingState : public BindingStateBase {
scoped_refptr<base::SingleThreadTaskRunner> runner) {
BindingStateBase::BindInternal(
std::move(handle), runner, Interface::Name_,
- base::MakeUnique<typename Interface::RequestValidator_>(),
+ std::make_unique<typename Interface::RequestValidator_>(),
Interface::PassesAssociatedKinds_, Interface::HasSyncMethods_, &stub_,
Interface::Version_);
}
InterfaceRequest<Interface> Unbind() {
endpoint_client_.reset();
- InterfaceRequest<Interface> request =
- MakeRequest<Interface>(router_->PassMessagePipe());
+ InterfaceRequest<Interface> request(router_->PassMessagePipe());
router_ = nullptr;
return request;
}
Interface* impl() { return ImplRefTraits::GetRawPointer(&stub_.sink()); }
+ ImplPointerType SwapImplForTesting(ImplPointerType new_impl) {
+ Interface* old_impl = impl();
+ stub_.set_sink(std::move(new_impl));
+ return old_impl;
+ }
private:
typename Interface::template Stub_<ImplRefTraits> stub_;
diff --git a/mojo/public/cpp/bindings/lib/bindings_internal.h b/mojo/public/cpp/bindings/lib/bindings_internal.h
index 631daec392..8bdb9c7b77 100644
--- a/mojo/public/cpp/bindings/lib/bindings_internal.h
+++ b/mojo/public/cpp/bindings/lib/bindings_internal.h
@@ -8,8 +8,9 @@
#include <stdint.h>
#include <functional>
+#include <type_traits>
-#include "base/template_util.h"
+#include "mojo/public/cpp/bindings/enum_traits.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"
@@ -34,8 +35,6 @@ class InterfaceRequestDataView;
template <typename K, typename V>
class MapDataView;
-class NativeStructDataView;
-
class StringDataView;
namespace internal {
@@ -54,8 +53,6 @@ class Array_Data;
template <typename K, typename V>
class Map_Data;
-class NativeStruct_Data;
-
using String_Data = Array_Data<char>;
inline size_t Align(size_t size) {
@@ -299,14 +296,6 @@ struct MojomTypeTraits<MapDataView<K, V>, false> {
};
template <>
-struct MojomTypeTraits<NativeStructDataView, false> {
- using Data = internal::NativeStruct_Data;
- using DataAsArrayElement = Pointer<Data>;
-
- static const MojomTypeCategory category = MojomTypeCategory::STRUCT;
-};
-
-template <>
struct MojomTypeTraits<StringDataView, false> {
using Data = String_Data;
using DataAsArrayElement = Pointer<Data>;
@@ -325,11 +314,19 @@ struct EnumHashImpl {
static_assert(std::is_enum<T>::value, "Incorrect hash function.");
size_t operator()(T input) const {
- using UnderlyingType = typename base::underlying_type<T>::type;
+ using UnderlyingType = typename std::underlying_type<T>::type;
return std::hash<UnderlyingType>()(static_cast<UnderlyingType>(input));
}
};
+template <typename MojomType, typename T>
+T ConvertEnumValue(MojomType input) {
+ T output;
+ bool result = EnumTraits<MojomType, T>::FromMojom(input, &output);
+ DCHECK(result);
+ return output;
+}
+
} // namespace internal
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/buffer.cc b/mojo/public/cpp/bindings/lib/buffer.cc
new file mode 100644
index 0000000000..2444cf4e54
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/buffer.cc
@@ -0,0 +1,136 @@
+// 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/lib/buffer.h"
+
+#include "base/logging.h"
+#include "base/numerics/safe_math.h"
+#include "mojo/public/c/system/message_pipe.h"
+#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+
+namespace mojo {
+namespace internal {
+
+Buffer::Buffer() = default;
+
+Buffer::Buffer(void* data, size_t size, size_t cursor)
+ : data_(data), size_(size), cursor_(cursor) {
+ DCHECK(IsAligned(data_));
+}
+
+Buffer::Buffer(MessageHandle message,
+ size_t message_payload_size,
+ void* data,
+ size_t size)
+ : message_(message),
+ message_payload_size_(message_payload_size),
+ data_(data),
+ size_(size),
+ cursor_(0) {
+ DCHECK(IsAligned(data_));
+}
+
+Buffer::Buffer(Buffer&& other) {
+ *this = std::move(other);
+}
+
+Buffer::~Buffer() = default;
+
+Buffer& Buffer::operator=(Buffer&& other) {
+ message_ = other.message_;
+ message_payload_size_ = other.message_payload_size_;
+ data_ = other.data_;
+ size_ = other.size_;
+ cursor_ = other.cursor_;
+ other.Reset();
+ return *this;
+}
+
+size_t Buffer::Allocate(size_t num_bytes) {
+ const size_t aligned_num_bytes = Align(num_bytes);
+ const size_t new_cursor = cursor_ + aligned_num_bytes;
+ if (new_cursor < cursor_ || (new_cursor > size_ && !message_.is_valid())) {
+ // Either we've overflowed or exceeded a fixed capacity.
+ NOTREACHED();
+ return 0;
+ }
+
+ if (new_cursor > size_) {
+ // If we have an underlying message object we can extend its payload to
+ // obtain more storage capacity.
+ DCHECK_LE(message_payload_size_, new_cursor);
+ size_t additional_bytes = new_cursor - message_payload_size_;
+ DCHECK(base::IsValueInRangeForNumericType<uint32_t>(additional_bytes));
+ uint32_t new_size;
+ MojoResult rv = MojoAppendMessageData(
+ message_.value(), static_cast<uint32_t>(additional_bytes), nullptr, 0,
+ nullptr, &data_, &new_size);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+ message_payload_size_ = new_cursor;
+ size_ = new_size;
+ }
+
+ DCHECK_LE(new_cursor, size_);
+ size_t block_start = cursor_;
+ cursor_ = new_cursor;
+
+ // Ensure that all the allocated space is zeroed to avoid uninitialized bits
+ // leaking into messages.
+ //
+ // TODO(rockot): We should consider only clearing the alignment padding. This
+ // means being careful about generated bindings zeroing padding explicitly,
+ // which itself gets particularly messy with e.g. packed bool bitfields.
+ memset(static_cast<uint8_t*>(data_) + block_start, 0, aligned_num_bytes);
+
+ return block_start;
+}
+
+void Buffer::AttachHandles(std::vector<ScopedHandle>* handles) {
+ DCHECK(message_.is_valid());
+
+ uint32_t new_size = 0;
+ MojoResult rv = MojoAppendMessageData(
+ message_.value(), 0, reinterpret_cast<MojoHandle*>(handles->data()),
+ static_cast<uint32_t>(handles->size()), nullptr, &data_, &new_size);
+ if (rv != MOJO_RESULT_OK)
+ return;
+
+ size_ = new_size;
+ for (auto& handle : *handles)
+ ignore_result(handle.release());
+}
+
+void Buffer::Seal() {
+ if (!message_.is_valid())
+ return;
+
+ // Ensure that the backing message has the final accumulated payload size.
+ DCHECK_LE(message_payload_size_, cursor_);
+ size_t additional_bytes = cursor_ - message_payload_size_;
+ DCHECK(base::IsValueInRangeForNumericType<uint32_t>(additional_bytes));
+
+ MojoAppendMessageDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE;
+ void* data;
+ uint32_t size;
+ MojoResult rv = MojoAppendMessageData(message_.value(),
+ static_cast<uint32_t>(additional_bytes),
+ nullptr, 0, &options, &data, &size);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+ message_ = MessageHandle();
+ message_payload_size_ = cursor_;
+ data_ = data;
+ size_ = size;
+}
+
+void Buffer::Reset() {
+ message_ = MessageHandle();
+ data_ = nullptr;
+ size_ = 0;
+ cursor_ = 0;
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/buffer.h b/mojo/public/cpp/bindings/lib/buffer.h
index 213a44590f..9f2a768490 100644
--- a/mojo/public/cpp/bindings/lib/buffer.h
+++ b/mojo/public/cpp/bindings/lib/buffer.h
@@ -6,60 +6,121 @@
#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BUFFER_H_
#include <stddef.h>
+#include <stdint.h>
-#include "base/logging.h"
+#include <vector>
+
+#include "base/component_export.h"
#include "base/macros.h"
-#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/cpp/system/message.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 {
+// aligned. It doesn't own the underlying memory. Users must ensure that the
+// memory stays valid while using the allocated blocks from Buffer.
+//
+// A Buffer may be moved around. A moved-from Buffer is reset and may no longer
+// be used to Allocate memory unless re-Initialized.
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) Buffer {
public:
- Buffer() {}
+ // Constructs an invalid Buffer. May not call Allocate().
+ Buffer();
+
+ // Constructs a Buffer which can Allocate() blocks from a buffer of fixed size
+ // |size| at |data|. Allocations start at |cursor|, so if |cursor| == |size|
+ // then no allocations are allowed.
+ //
+ // |data| is not owned.
+ Buffer(void* data, size_t size, size_t cursor);
+
+ // Like above, but gives the Buffer an underlying message object which can
+ // have its payload extended to acquire more storage capacity on Allocate().
+ //
+ // |data| and |size| must correspond to |message|'s data buffer at the time of
+ // construction.
+ //
+ // |payload_size| is the length of the payload as known by |message|, and it
+ // must be less than or equal to |size|.
+ //
+ // |message| is NOT owned and must outlive this Buffer.
+ Buffer(MessageHandle message,
+ size_t message_payload_size,
+ void* data,
+ size_t size);
+
+ Buffer(Buffer&& other);
+ ~Buffer();
+
+ Buffer& operator=(Buffer&& other);
- // The memory must have been zero-initialized. |data| must be 8-byte
- // aligned.
- void Initialize(void* data, size_t size) {
- DCHECK(IsAligned(data));
+ void* data() const { return data_; }
+ size_t size() const { return size_; }
+ size_t cursor() const { return cursor_; }
- data_ = data;
- size_ = size;
- cursor_ = reinterpret_cast<uintptr_t>(data);
- data_end_ = cursor_ + size;
+ bool is_valid() const {
+ return data_ != nullptr || (size_ == 0 && !message_.is_valid());
}
- size_t size() const { return size_; }
+ // Allocates |num_bytes| from the buffer and returns an index to the start of
+ // the allocated block. The resulting index is 8-byte aligned and can be
+ // resolved to an address using Get<T>() below.
+ size_t Allocate(size_t num_bytes);
+
+ // Returns a typed address within the Buffer corresponding to |index|. Note
+ // that this address is NOT stable across calls to |Allocate()| and thus must
+ // not be cached accordingly.
+ template <typename T>
+ T* Get(size_t index) {
+ DCHECK_LT(index, cursor_);
+ return reinterpret_cast<T*>(static_cast<uint8_t*>(data_) + index);
+ }
- void* data() const { return data_; }
+ // A template helper combining Allocate() and Get<T>() above to allocate and
+ // return a block of size |sizeof(T)|.
+ template <typename T>
+ T* AllocateAndGet() {
+ return Get<T>(Allocate(sizeof(T)));
+ }
- // 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<void*>(result);
+ // A helper which combines Allocate() and Get<void>() for a specified number
+ // of bytes.
+ void* AllocateAndGet(size_t num_bytes) {
+ return Get<void>(Allocate(num_bytes));
}
+ // Serializes |handles| into the buffer object. Only valid to call when this
+ // Buffer is backed by a message object.
+ void AttachHandles(std::vector<ScopedHandle>* handles);
+
+ // Seals this Buffer so it can no longer be used for allocation, and ensures
+ // the backing message object has a complete accounting of the size of the
+ // meaningful payload bytes.
+ void Seal();
+
+ // Resets the buffer to an invalid state. Can no longer be used to Allocate().
+ void Reset();
+
private:
+ MessageHandle message_;
+
+ // The payload size from the message's internal perspective. This differs from
+ // |size_| as Mojo may intentionally over-allocate space to account for future
+ // growth. It differs from |cursor_| because we don't push payload size
+ // updates to the message object as frequently as we update |cursor_|, for
+ // performance.
+ size_t message_payload_size_ = 0;
+
+ // The storage location and capacity currently backing |message_|. Owned by
+ // the message object internally, not by this Buffer.
void* data_ = nullptr;
size_t size_ = 0;
- uintptr_t cursor_ = 0;
- uintptr_t data_end_ = 0;
+ // The current write offset into |data_| if this Buffer is being used for
+ // message creation.
+ size_t cursor_ = 0;
DISALLOW_COPY_AND_ASSIGN(Buffer);
};
diff --git a/mojo/public/cpp/bindings/lib/connector.cc b/mojo/public/cpp/bindings/lib/connector.cc
index d93e45ed93..352c51815f 100644
--- a/mojo/public/cpp/bindings/lib/connector.cc
+++ b/mojo/public/cpp/bindings/lib/connector.cc
@@ -5,7 +5,6 @@
#include "mojo/public/cpp/bindings/connector.h"
#include <stdint.h>
-#include <utility>
#include "base/bind.h"
#include "base/lazy_instance.h"
@@ -13,23 +12,37 @@
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
-#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/run_loop.h"
#include "base/synchronization/lock.h"
#include "base/threading/thread_local.h"
+#include "base/trace_event/trace_event.h"
#include "mojo/public/cpp/bindings/lib/may_auto_lock.h"
+#include "mojo/public/cpp/bindings/mojo_buildflags.h"
#include "mojo/public/cpp/bindings/sync_handle_watcher.h"
#include "mojo/public/cpp/system/wait.h"
+#if defined(ENABLE_IPC_FUZZER)
+#include "mojo/public/cpp/bindings/message_dumper.h"
+#endif
+
namespace mojo {
namespace {
// The NestingObserver for each thread. Note that this is always a
-// Connector::MessageLoopNestingObserver; we use the base type here because that
+// Connector::RunLoopNestingObserver; we use the base type here because that
// subclass is private to Connector.
-base::LazyInstance<
- base::ThreadLocalPointer<base::MessageLoop::NestingObserver>>::Leaky
- g_tls_nesting_observer = LAZY_INSTANCE_INITIALIZER;
+base::LazyInstance<base::ThreadLocalPointer<base::RunLoop::NestingObserver>>::
+ Leaky g_tls_nesting_observer = LAZY_INSTANCE_INITIALIZER;
+
+// The default outgoing serialization mode for new Connectors.
+Connector::OutgoingSerializationMode g_default_outgoing_serialization_mode =
+ Connector::OutgoingSerializationMode::kLazy;
+
+// The default incoming serialization mode for new Connectors.
+Connector::IncomingSerializationMode g_default_incoming_serialization_mode =
+ Connector::IncomingSerializationMode::kDispatchAsIs;
} // namespace
@@ -44,7 +57,7 @@ class Connector::ActiveDispatchTracker {
private:
const base::WeakPtr<Connector> connector_;
- MessageLoopNestingObserver* const nesting_observer_;
+ RunLoopNestingObserver* const nesting_observer_;
ActiveDispatchTracker* outer_tracker_ = nullptr;
ActiveDispatchTracker* inner_tracker_ = nullptr;
@@ -52,41 +65,40 @@ class Connector::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 {
+// ActiveDispatchTrackers when a nested run loop is started.
+class Connector::RunLoopNestingObserver
+ : public base::RunLoop::NestingObserver,
+ public base::MessageLoopCurrent::DestructionObserver {
public:
- MessageLoopNestingObserver() {
- base::MessageLoop::current()->AddNestingObserver(this);
- base::MessageLoop::current()->AddDestructionObserver(this);
+ RunLoopNestingObserver() {
+ base::RunLoop::AddNestingObserverOnCurrentThread(this);
+ base::MessageLoopCurrent::Get()->AddDestructionObserver(this);
}
- ~MessageLoopNestingObserver() override {}
+ ~RunLoopNestingObserver() override {}
- // base::MessageLoop::NestingObserver:
- void OnBeginNestedMessageLoop() override {
+ // base::RunLoop::NestingObserver:
+ void OnBeginNestedRunLoop() override {
if (top_tracker_)
top_tracker_->NotifyBeginNesting();
}
- // base::MessageLoop::DestructionObserver:
+ // base::MessageLoopCurrent::DestructionObserver:
void WillDestroyCurrentMessageLoop() override {
- base::MessageLoop::current()->RemoveNestingObserver(this);
- base::MessageLoop::current()->RemoveDestructionObserver(this);
+ base::RunLoop::RemoveNestingObserverOnCurrentThread(this);
+ base::MessageLoopCurrent::Get()->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())
+ static RunLoopNestingObserver* GetForThread() {
+ if (!base::MessageLoopCurrent::Get())
return nullptr;
- auto* observer = static_cast<MessageLoopNestingObserver*>(
+ auto* observer = static_cast<RunLoopNestingObserver*>(
g_tls_nesting_observer.Get().Get());
if (!observer) {
- observer = new MessageLoopNestingObserver;
+ observer = new RunLoopNestingObserver;
g_tls_nesting_observer.Get().Set(observer);
}
return observer;
@@ -97,7 +109,7 @@ class Connector::MessageLoopNestingObserver
ActiveDispatchTracker* top_tracker_ = nullptr;
- DISALLOW_COPY_AND_ASSIGN(MessageLoopNestingObserver);
+ DISALLOW_COPY_AND_ASSIGN(RunLoopNestingObserver);
};
Connector::ActiveDispatchTracker::ActiveDispatchTracker(
@@ -129,14 +141,22 @@ void Connector::ActiveDispatchTracker::NotifyBeginNesting() {
Connector::Connector(ScopedMessagePipeHandle message_pipe,
ConnectorConfig config,
- scoped_refptr<base::SingleThreadTaskRunner> runner)
+ scoped_refptr<base::SequencedTaskRunner> runner)
: message_pipe_(std::move(message_pipe)),
task_runner_(std::move(runner)),
- nesting_observer_(MessageLoopNestingObserver::GetForThread()),
+ error_(false),
+ outgoing_serialization_mode_(g_default_outgoing_serialization_mode),
+ incoming_serialization_mode_(g_default_incoming_serialization_mode),
+ nesting_observer_(RunLoopNestingObserver::GetForThread()),
weak_factory_(this) {
if (config == MULTI_THREADED_SEND)
lock_.emplace();
+#if defined(ENABLE_IPC_FUZZER)
+ if (!MessageDumper::GetMessageDumpDirectory().empty())
+ message_dumper_ = std::make_unique<MessageDumper>();
+#endif
+
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.
@@ -145,23 +165,34 @@ Connector::Connector(ScopedMessagePipeHandle message_pipe,
Connector::~Connector() {
{
- // Allow for quick destruction on any thread if the pipe is already closed.
+ // Allow for quick destruction on any sequence if the pipe is already
+ // closed.
base::AutoLock lock(connected_lock_);
if (!connected_)
return;
}
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CancelWait();
}
+void Connector::SetOutgoingSerializationMode(OutgoingSerializationMode mode) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ outgoing_serialization_mode_ = mode;
+}
+
+void Connector::SetIncomingSerializationMode(IncomingSerializationMode mode) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ incoming_serialization_mode_ = mode;
+}
+
void Connector::CloseMessagePipe() {
// Throw away the returned message pipe.
PassMessagePipe();
}
ScopedMessagePipeHandle Connector::PassMessagePipe() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CancelWait();
internal::MayAutoLock locker(&lock_);
@@ -175,13 +206,13 @@ ScopedMessagePipeHandle Connector::PassMessagePipe() {
}
void Connector::RaiseError() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
HandleError(true, true);
}
bool Connector::WaitForIncomingMessage(MojoDeadline deadline) {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (error_)
return false;
@@ -211,7 +242,7 @@ bool Connector::WaitForIncomingMessage(MojoDeadline deadline) {
}
void Connector::PauseIncomingMethodCallProcessing() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (paused_)
return;
@@ -221,7 +252,7 @@ void Connector::PauseIncomingMethodCallProcessing() {
}
void Connector::ResumeIncomingMethodCallProcessing() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!paused_)
return;
@@ -230,12 +261,18 @@ void Connector::ResumeIncomingMethodCallProcessing() {
WaitToReadMore();
}
+bool Connector::PrefersSerializedMessages() {
+ if (outgoing_serialization_mode_ == OutgoingSerializationMode::kEager)
+ return true;
+ DCHECK_EQ(OutgoingSerializationMode::kLazy, outgoing_serialization_mode_);
+ return peer_remoteness_tracker_ &&
+ peer_remoteness_tracker_->last_known_state().peer_remote();
+}
+
bool Connector::Accept(Message* message) {
- DCHECK(lock_ || thread_checker_.CalledOnValidThread());
+ if (!lock_)
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- // 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;
@@ -244,6 +281,13 @@ bool Connector::Accept(Message* message) {
if (!message_pipe_.is_valid() || drop_writes_)
return true;
+#if defined(ENABLE_IPC_FUZZER)
+ if (message_dumper_ && message->is_serialized()) {
+ bool dump_result = message_dumper_->Accept(message);
+ DCHECK(dump_result);
+ }
+#endif
+
MojoResult rv =
WriteMessageNew(message_pipe_.get(), message->TakeMojoMessage(),
MOJO_WRITE_MESSAGE_FLAG_NONE);
@@ -261,10 +305,10 @@ bool Connector::Accept(Message* message) {
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
+ // - simultaneously being used on another sequence; 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
+ // regardless of which sequence 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
@@ -280,7 +324,7 @@ bool Connector::Accept(Message* message) {
}
void Connector::AllowWokenUpBySyncWatchOnSameThread() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
allow_woken_up_by_others_ = true;
@@ -289,7 +333,7 @@ void Connector::AllowWokenUpBySyncWatchOnSameThread() {
}
bool Connector::SyncWatch(const bool* should_stop) {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (error_)
return false;
@@ -301,12 +345,21 @@ bool Connector::SyncWatch(const bool* should_stop) {
}
void Connector::SetWatcherHeapProfilerTag(const char* tag) {
- heap_profiler_tag_ = tag;
- if (handle_watcher_) {
- handle_watcher_->set_heap_profiler_tag(tag);
+ if (tag) {
+ heap_profiler_tag_ = tag;
+ if (handle_watcher_)
+ handle_watcher_->set_heap_profiler_tag(tag);
}
}
+// static
+void Connector::OverrideDefaultSerializationBehaviorForTesting(
+ OutgoingSerializationMode outgoing_mode,
+ IncomingSerializationMode incoming_mode) {
+ g_default_outgoing_serialization_mode = outgoing_mode;
+ g_default_incoming_serialization_mode = incoming_mode;
+}
+
void Connector::OnWatcherHandleReady(MojoResult result) {
OnHandleReadyInternal(result);
}
@@ -324,7 +377,7 @@ void Connector::OnSyncHandleWatcherHandleReady(MojoResult result) {
}
void Connector::OnHandleReadyInternal(MojoResult result) {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (result != MOJO_RESULT_OK) {
HandleError(result != MOJO_RESULT_FAILED_PRECONDITION, false);
@@ -341,12 +394,16 @@ void Connector::WaitToReadMore() {
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_);
+ 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 (message_pipe_.is_valid()) {
+ peer_remoteness_tracker_.emplace(message_pipe_.get(),
+ MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+ }
+
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.
@@ -383,6 +440,19 @@ bool Connector::ReadSingleMessage(MojoResult* read_result) {
dispatch_tracker.emplace(weak_self);
}
+ if (incoming_serialization_mode_ ==
+ IncomingSerializationMode::kSerializeBeforeDispatchForTesting) {
+ message.SerializeIfNecessary();
+ } else {
+ DCHECK_EQ(IncomingSerializationMode::kDispatchAsIs,
+ incoming_serialization_mode_);
+ }
+
+#if !BUILDFLAG(MOJO_TRACE_ENABLED)
+ // This emits just full class name, and is inferior to mojo tracing.
+ TRACE_EVENT0("mojom", heap_profiler_tag_);
+#endif
+
receiver_result =
incoming_receiver_ && incoming_receiver_->Accept(&message);
@@ -443,6 +513,7 @@ void Connector::ReadAllAvailableMessages() {
}
void Connector::CancelWait() {
+ peer_remoteness_tracker_.reset();
handle_watcher_.reset();
sync_watcher_.reset();
}
@@ -476,8 +547,8 @@ void Connector::HandleError(bool force_pipe_reset, bool force_async_handler) {
WaitToReadMore();
} else {
error_ = true;
- if (!connection_error_handler_.is_null())
- connection_error_handler_.Run();
+ if (connection_error_handler_)
+ std::move(connection_error_handler_).Run();
}
}
diff --git a/mojo/public/cpp/bindings/lib/control_message_handler.cc b/mojo/public/cpp/bindings/lib/control_message_handler.cc
index 1b7bb78e5f..b87c11c874 100644
--- a/mojo/public/cpp/bindings/lib/control_message_handler.cc
+++ b/mojo/public/cpp/bindings/lib/control_message_handler.cc
@@ -10,9 +10,9 @@
#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/cpp/bindings/message.h"
#include "mojo/public/interfaces/bindings/interface_control_messages.mojom.h"
namespace mojo {
@@ -115,19 +115,15 @@ bool ControlMessageHandler::Run(
auto response_params_ptr = interface_control::RunResponseMessageParams::New();
response_params_ptr->output = std::move(output);
- size_t size =
- PrepareToSerialize<interface_control::RunResponseMessageParamsDataView>(
- 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;
+ Message response_message(interface_control::kRunMessageId,
+ Message::kFlagIsResponse, 0, 0, nullptr);
+ response_message.set_request_id(message->request_id());
+ interface_control::internal::RunResponseMessageParams_Data::BufferWriter
+ response_params;
Serialize<interface_control::RunResponseMessageParamsDataView>(
- response_params_ptr, builder.buffer(), &response_params, &context_);
- ignore_result(responder->Accept(builder.message()));
-
+ response_params_ptr, response_message.payload_buffer(), &response_params,
+ &context_);
+ ignore_result(responder->Accept(&response_message));
return true;
}
diff --git a/mojo/public/cpp/bindings/lib/control_message_handler.h b/mojo/public/cpp/bindings/lib/control_message_handler.h
index 5d1f716ea8..daa884bb52 100644
--- a/mojo/public/cpp/bindings/lib/control_message_handler.h
+++ b/mojo/public/cpp/bindings/lib/control_message_handler.h
@@ -18,7 +18,7 @@ namespace internal {
// Handlers for request messages defined in interface_control_messages.mojom.
class MOJO_CPP_BINDINGS_EXPORT ControlMessageHandler
- : NON_EXPORTED_BASE(public MessageReceiverWithResponderStatus) {
+ : public MessageReceiverWithResponderStatus {
public:
static bool IsControlMessage(const Message* message);
diff --git a/mojo/public/cpp/bindings/lib/control_message_proxy.cc b/mojo/public/cpp/bindings/lib/control_message_proxy.cc
index d082b49fb3..9fd7bf4173 100644
--- a/mojo/public/cpp/bindings/lib/control_message_proxy.cc
+++ b/mojo/public/cpp/bindings/lib/control_message_proxy.cc
@@ -12,7 +12,6 @@
#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"
@@ -73,49 +72,37 @@ bool RunResponseForwardToCallback::Accept(Message* message) {
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<interface_control::RunMessageParamsDataView>(
- params_ptr, &context);
- MessageBuilder builder(interface_control::kRunMessageId,
- Message::kFlagExpectsResponse, size, 0);
-
- interface_control::internal::RunMessageParams_Data* params = nullptr;
+ Message message(interface_control::kRunMessageId,
+ Message::kFlagExpectsResponse, 0, 0, nullptr);
+ SerializationContext context;
+ interface_control::internal::RunMessageParams_Data::BufferWriter params;
Serialize<interface_control::RunMessageParamsDataView>(
- params_ptr, builder.buffer(), &params, &context);
+ params_ptr, message.payload_buffer(), &params, &context);
std::unique_ptr<MessageReceiver> responder =
- base::MakeUnique<RunResponseForwardToCallback>(callback);
- ignore_result(
- receiver->AcceptWithResponder(builder.message(), std::move(responder)));
+ std::make_unique<RunResponseForwardToCallback>(callback);
+ ignore_result(receiver->AcceptWithResponder(&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;
+ Message message(interface_control::kRunOrClosePipeMessageId, 0, 0, 0,
+ nullptr);
+ SerializationContext context;
+ interface_control::internal::RunOrClosePipeMessageParams_Data::BufferWriter
+ params;
Serialize<interface_control::RunOrClosePipeMessageParamsDataView>(
- params_ptr, builder.buffer(), &params, &context);
- return std::move(*builder.message());
+ params_ptr, message.payload_buffer(), &params, &context);
+ return message;
}
void SendRunOrClosePipeMessage(
MessageReceiverWithResponder* receiver,
interface_control::RunOrClosePipeInputPtr input_ptr) {
Message message(ConstructRunOrClosePipeMessage(std::move(input_ptr)));
-
ignore_result(receiver->Accept(&message));
}
@@ -163,7 +150,7 @@ void ControlMessageProxy::FlushForTesting() {
auto input_ptr = interface_control::RunInput::New();
input_ptr->set_flush_for_testing(interface_control::FlushForTesting::New());
- base::RunLoop run_loop;
+ base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
run_loop_quit_closure_ = run_loop.QuitClosure();
SendRunMessage(
receiver_, std::move(input_ptr),
diff --git a/mojo/public/cpp/bindings/lib/equals_traits.h b/mojo/public/cpp/bindings/lib/equals_traits.h
deleted file mode 100644
index 53c7dce693..0000000000
--- a/mojo/public/cpp/bindings/lib/equals_traits.h
+++ /dev/null
@@ -1,94 +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 MOJO_PUBLIC_CPP_BINDINGS_LIB_EQUALS_TRAITS_H_
-#define MOJO_PUBLIC_CPP_BINDINGS_LIB_EQUALS_TRAITS_H_
-
-#include <type_traits>
-#include <unordered_map>
-#include <vector>
-
-#include "base/optional.h"
-#include "mojo/public/cpp/bindings/lib/template_util.h"
-
-namespace mojo {
-namespace internal {
-
-template <typename T>
-struct HasEqualsMethod {
- template <typename U>
- static char Test(decltype(&U::Equals));
- template <typename U>
- static int Test(...);
- static const bool value = sizeof(Test<T>(0)) == sizeof(char);
-
- private:
- EnsureTypeIsComplete<T> check_t_;
-};
-
-template <typename T, bool has_equals_method = HasEqualsMethod<T>::value>
-struct EqualsTraits;
-
-template <typename T>
-bool Equals(const T& a, const T& b);
-
-template <typename T>
-struct EqualsTraits<T, true> {
- static bool Equals(const T& a, const T& b) { return a.Equals(b); }
-};
-
-template <typename T>
-struct EqualsTraits<T, false> {
- static bool Equals(const T& a, const T& b) { return a == b; }
-};
-
-template <typename T>
-struct EqualsTraits<base::Optional<T>, false> {
- static bool Equals(const base::Optional<T>& a, const base::Optional<T>& b) {
- if (!a && !b)
- return true;
- if (!a || !b)
- return false;
-
- return internal::Equals(*a, *b);
- }
-};
-
-template <typename T>
-struct EqualsTraits<std::vector<T>, false> {
- static bool Equals(const std::vector<T>& a, const std::vector<T>& 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 <typename K, typename V>
-struct EqualsTraits<std::unordered_map<K, V>, false> {
- static bool Equals(const std::unordered_map<K, V>& a,
- const std::unordered_map<K, V>& 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 <typename T>
-bool Equals(const T& a, const T& b) {
- return EqualsTraits<T>::Equals(a, b);
-}
-
-} // namespace internal
-} // namespace mojo
-
-#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_EQUALS_TRAITS_H_
diff --git a/mojo/public/cpp/bindings/lib/fixed_buffer.cc b/mojo/public/cpp/bindings/lib/fixed_buffer.cc
index 725a193cd7..3d595cc063 100644
--- a/mojo/public/cpp/bindings/lib/fixed_buffer.cc
+++ b/mojo/public/cpp/bindings/lib/fixed_buffer.cc
@@ -6,25 +6,17 @@
#include <stdlib.h>
+#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
+
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(size_t size)
+ : Buffer(calloc(Align(size), 1), Align(size), 0) {}
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
index 070b0c8cef..147ce7b115 100644
--- a/mojo/public/cpp/bindings/lib/fixed_buffer.h
+++ b/mojo/public/cpp/bindings/lib/fixed_buffer.h
@@ -5,11 +5,10 @@
#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_FIXED_BUFFER_H_
#define MOJO_PUBLIC_CPP_BINDINGS_LIB_FIXED_BUFFER_H_
-#include <stddef.h>
+#include <cstddef>
-#include "base/compiler_specific.h"
+#include "base/component_export.h"
#include "base/macros.h"
-#include "mojo/public/cpp/bindings/bindings_export.h"
#include "mojo/public/cpp/bindings/lib/buffer.h"
namespace mojo {
@@ -17,18 +16,12 @@ 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) {
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) FixedBufferForTesting
+ : 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);
};
diff --git a/mojo/public/cpp/bindings/lib/handle_interface_serialization.h b/mojo/public/cpp/bindings/lib/handle_interface_serialization.h
deleted file mode 100644
index 14ed21f0ac..0000000000
--- a/mojo/public/cpp/bindings/lib/handle_interface_serialization.h
+++ /dev/null
@@ -1,181 +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 MOJO_PUBLIC_CPP_BINDINGS_LIB_HANDLE_INTERFACE_SERIALIZATION_H_
-#define MOJO_PUBLIC_CPP_BINDINGS_LIB_HANDLE_INTERFACE_SERIALIZATION_H_
-
-#include <type_traits>
-
-#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 <typename Base, typename T>
-struct Serializer<AssociatedInterfacePtrInfoDataView<Base>,
- AssociatedInterfacePtrInfo<T>> {
- static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch.");
-
- static size_t PrepareToSerialize(const AssociatedInterfacePtrInfo<T>& input,
- SerializationContext* context) {
- if (input.handle().is_valid())
- context->associated_endpoint_count++;
- return 0;
- }
-
- static void Serialize(AssociatedInterfacePtrInfo<T>& 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<uint32_t>(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<T>* 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 <typename Base, typename T>
-struct Serializer<AssociatedInterfaceRequestDataView<Base>,
- AssociatedInterfaceRequest<T>> {
- static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch.");
-
- static size_t PrepareToSerialize(const AssociatedInterfaceRequest<T>& input,
- SerializationContext* context) {
- if (input.handle().is_valid())
- context->associated_endpoint_count++;
- return 0;
- }
-
- static void Serialize(AssociatedInterfaceRequest<T>& 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<uint32_t>(context->associated_endpoint_handles.size());
- context->associated_endpoint_handles.push_back(input.PassHandle());
- } else {
- output->value = kEncodedInvalidHandleValue;
- }
- }
-
- static bool Deserialize(AssociatedEndpointHandle_Data* input,
- AssociatedInterfaceRequest<T>* 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 <typename Base, typename T>
-struct Serializer<InterfacePtrDataView<Base>, InterfacePtr<T>> {
- static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch.");
-
- static size_t PrepareToSerialize(const InterfacePtr<T>& input,
- SerializationContext* context) {
- return 0;
- }
-
- static void Serialize(InterfacePtr<T>& input,
- Interface_Data* output,
- SerializationContext* context) {
- InterfacePtrInfo<T> info = input.PassInterface();
- output->handle = context->handles.AddHandle(info.PassHandle().release());
- output->version = info.version();
- }
-
- static bool Deserialize(Interface_Data* input,
- InterfacePtr<T>* output,
- SerializationContext* context) {
- output->Bind(InterfacePtrInfo<T>(
- context->handles.TakeHandleAs<mojo::MessagePipeHandle>(input->handle),
- input->version));
- return true;
- }
-};
-
-template <typename Base, typename T>
-struct Serializer<InterfaceRequestDataView<Base>, InterfaceRequest<T>> {
- static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch.");
-
- static size_t PrepareToSerialize(const InterfaceRequest<T>& input,
- SerializationContext* context) {
- return 0;
- }
-
- static void Serialize(InterfaceRequest<T>& input,
- Handle_Data* output,
- SerializationContext* context) {
- *output = context->handles.AddHandle(input.PassMessagePipe().release());
- }
-
- static bool Deserialize(Handle_Data* input,
- InterfaceRequest<T>* output,
- SerializationContext* context) {
- output->Bind(context->handles.TakeHandleAs<MessagePipeHandle>(*input));
- return true;
- }
-};
-
-template <typename T>
-struct Serializer<ScopedHandleBase<T>, ScopedHandleBase<T>> {
- static size_t PrepareToSerialize(const ScopedHandleBase<T>& input,
- SerializationContext* context) {
- return 0;
- }
-
- static void Serialize(ScopedHandleBase<T>& input,
- Handle_Data* output,
- SerializationContext* context) {
- *output = context->handles.AddHandle(input.release());
- }
-
- static bool Deserialize(Handle_Data* input,
- ScopedHandleBase<T>* output,
- SerializationContext* context) {
- *output = context->handles.TakeHandleAs<T>(*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/handle_serialization.h b/mojo/public/cpp/bindings/lib/handle_serialization.h
new file mode 100644
index 0000000000..6e1294e0a2
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/handle_serialization.h
@@ -0,0 +1,35 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_HANDLE_SERIALIZATION_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_HANDLE_SERIALIZATION_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 <typename T>
+struct Serializer<ScopedHandleBase<T>, ScopedHandleBase<T>> {
+ static void Serialize(ScopedHandleBase<T>& input,
+ Handle_Data* output,
+ SerializationContext* context) {
+ context->AddHandle(ScopedHandle::From(std::move(input)), output);
+ }
+
+ static bool Deserialize(Handle_Data* input,
+ ScopedHandleBase<T>* output,
+ SerializationContext* context) {
+ *output = context->TakeHandleAs<T>(*input);
+ return true;
+ }
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_HANDLE_SERIALIZATION_H_
diff --git a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
index 4682e72fad..6f119e4c1d 100644
--- a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
+++ b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
@@ -6,18 +6,17 @@
#include <stdint.h>
-#include <utility>
-
#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/sequenced_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/task_runner_helper.h"
#include "mojo/public/cpp/bindings/lib/validation_util.h"
#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
@@ -27,10 +26,10 @@ namespace mojo {
namespace {
-void DCheckIfInvalid(const base::WeakPtr<InterfaceEndpointClient>& client,
- const std::string& message) {
- bool is_valid = client && !client->encountered_error();
- DCHECK(!is_valid) << message;
+void DetermineIfEndpointIsConnected(
+ const base::WeakPtr<InterfaceEndpointClient>& client,
+ base::OnceCallback<void(bool)> callback) {
+ std::move(callback).Run(client && !client->encountered_error());
}
// When receiving an incoming message which expects a repsonse,
@@ -41,7 +40,7 @@ class ResponderThunk : public MessageReceiverWithStatus {
public:
explicit ResponderThunk(
const base::WeakPtr<InterfaceEndpointClient>& endpoint_client,
- scoped_refptr<base::SingleThreadTaskRunner> runner)
+ scoped_refptr<base::SequencedTaskRunner> runner)
: endpoint_client_(endpoint_client),
accept_was_invoked_(false),
task_runner_(std::move(runner)) {}
@@ -52,7 +51,7 @@ class ResponderThunk : public MessageReceiverWithStatus {
// 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()) {
+ if (task_runner_->RunsTasksInCurrentSequence()) {
// 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
@@ -69,8 +68,12 @@ class ResponderThunk : public MessageReceiverWithStatus {
}
// MessageReceiver implementation:
+ bool PrefersSerializedMessages() override {
+ return endpoint_client_ && endpoint_client_->PrefersSerializedMessages();
+ }
+
bool Accept(Message* message) override {
- DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
accept_was_invoked_ = true;
DCHECK(message->has_flag(Message::kFlagIsResponse));
@@ -83,24 +86,25 @@ class ResponderThunk : public MessageReceiverWithStatus {
}
// MessageReceiverWithStatus implementation:
- bool IsValid() override {
- DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ bool IsConnected() override {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
return endpoint_client_ && !endpoint_client_->encountered_error();
}
- void DCheckInvalid(const std::string& message) override {
- if (task_runner_->RunsTasksOnCurrentThread()) {
- DCheckIfInvalid(endpoint_client_, message);
+ void IsConnectedAsync(base::OnceCallback<void(bool)> callback) override {
+ if (task_runner_->RunsTasksInCurrentSequence()) {
+ DetermineIfEndpointIsConnected(endpoint_client_, std::move(callback));
} else {
task_runner_->PostTask(
- FROM_HERE, base::Bind(&DCheckIfInvalid, endpoint_client_, message));
+ FROM_HERE, base::BindOnce(&DetermineIfEndpointIsConnected,
+ endpoint_client_, std::move(callback)));
}
- }
+ }
private:
base::WeakPtr<InterfaceEndpointClient> endpoint_client_;
bool accept_was_invoked_;
- scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
DISALLOW_COPY_AND_ASSIGN(ResponderThunk);
};
@@ -136,7 +140,7 @@ InterfaceEndpointClient::InterfaceEndpointClient(
MessageReceiverWithResponderStatus* receiver,
std::unique_ptr<MessageReceiver> payload_validator,
bool expect_sync_requests,
- scoped_refptr<base::SingleThreadTaskRunner> runner,
+ scoped_refptr<base::SequencedTaskRunner> runner,
uint32_t interface_version)
: expect_sync_requests_(expect_sync_requests),
handle_(std::move(handle)),
@@ -163,20 +167,19 @@ InterfaceEndpointClient::InterfaceEndpointClient(
}
InterfaceEndpointClient::~InterfaceEndpointClient() {
- DCHECK(thread_checker_.CalledOnValidThread());
-
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (controller_)
handle_.group_controller()->DetachEndpointClient(handle_);
}
AssociatedGroup* InterfaceEndpointClient::associated_group() {
if (!associated_group_)
- associated_group_ = base::MakeUnique<AssociatedGroup>(handle_);
+ associated_group_ = std::make_unique<AssociatedGroup>(handle_);
return associated_group_.get();
}
ScopedInterfaceEndpointHandle InterfaceEndpointClient::PassHandle() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!has_pending_responders());
if (!handle_.is_valid())
@@ -199,7 +202,7 @@ void InterfaceEndpointClient::AddFilter(
}
void InterfaceEndpointClient::RaiseError() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!handle_.pending_association())
handle_.group_controller()->RaiseError();
@@ -207,14 +210,19 @@ void InterfaceEndpointClient::RaiseError() {
void InterfaceEndpointClient::CloseWithReason(uint32_t custom_reason,
const std::string& description) {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto handle = PassHandle();
handle.ResetWithReason(custom_reason, description);
}
+bool InterfaceEndpointClient::PrefersSerializedMessages() {
+ auto* controller = handle_.group_controller();
+ return controller && controller->PrefersSerializedMessages();
+}
+
bool InterfaceEndpointClient::Accept(Message* message) {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!message->has_flag(Message::kFlagExpectsResponse));
DCHECK(!handle_.pending_association());
@@ -237,7 +245,7 @@ bool InterfaceEndpointClient::Accept(Message* message) {
bool InterfaceEndpointClient::AcceptWithResponder(
Message* message,
std::unique_ptr<MessageReceiver> responder) {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(message->has_flag(Message::kFlagExpectsResponse));
DCHECK(!handle_.pending_association());
@@ -270,7 +278,7 @@ bool InterfaceEndpointClient::AcceptWithResponder(
bool response_received = false;
sync_responses_.insert(std::make_pair(
- request_id, base::MakeUnique<SyncResponseInfo>(&response_received)));
+ request_id, std::make_unique<SyncResponseInfo>(&response_received)));
base::WeakPtr<InterfaceEndpointClient> weak_self =
weak_ptr_factory_.GetWeakPtr();
@@ -280,8 +288,13 @@ bool InterfaceEndpointClient::AcceptWithResponder(
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)
+ if (response_received) {
ignore_result(responder->Accept(&iter->second->response));
+ } else {
+ DVLOG(1) << "Mojo sync call returns without receiving a response. "
+ << "Typcially it is because the interface has been "
+ << "disconnected.";
+ }
sync_responses_.erase(iter);
}
@@ -289,13 +302,13 @@ bool InterfaceEndpointClient::AcceptWithResponder(
}
bool InterfaceEndpointClient::HandleIncomingMessage(Message* message) {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return filters_.Accept(message);
}
void InterfaceEndpointClient::NotifyError(
const base::Optional<DisconnectReason>& reason) {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (encountered_error_)
return;
@@ -309,16 +322,14 @@ void InterfaceEndpointClient::NotifyError(
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 (error_handler_) {
+ std::move(error_handler_).Run();
+ } else if (error_with_reason_handler_) {
if (reason) {
- error_with_reason_handler.Run(reason->custom_reason, reason->description);
+ std::move(error_with_reason_handler_)
+ .Run(reason->custom_reason, reason->description);
} else {
- error_with_reason_handler.Run(0, std::string());
+ std::move(error_with_reason_handler_).Run(0, std::string());
}
}
}
@@ -374,7 +385,7 @@ bool InterfaceEndpointClient::HandleValidatedMessage(Message* message) {
if (message->has_flag(Message::kFlagExpectsResponse)) {
std::unique_ptr<MessageReceiverWithStatus> responder =
- base::MakeUnique<ResponderThunk>(weak_ptr_factory_.GetWeakPtr(),
+ std::make_unique<ResponderThunk>(weak_ptr_factory_.GetWeakPtr(),
task_runner_);
if (mojo::internal::ControlMessageHandler::IsControlMessage(message)) {
return control_message_handler_.AcceptWithResponder(message,
diff --git a/mojo/public/cpp/bindings/lib/interface_ptr_state.cc b/mojo/public/cpp/bindings/lib/interface_ptr_state.cc
new file mode 100644
index 0000000000..8cd23ea067
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/interface_ptr_state.cc
@@ -0,0 +1,94 @@
+// 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/lib/interface_ptr_state.h"
+
+#include "mojo/public/cpp/bindings/lib/task_runner_helper.h"
+
+namespace mojo {
+namespace internal {
+
+InterfacePtrStateBase::InterfacePtrStateBase() = default;
+
+InterfacePtrStateBase::~InterfacePtrStateBase() {
+ endpoint_client_.reset();
+ if (router_)
+ router_->CloseMessagePipe();
+}
+
+void InterfacePtrStateBase::QueryVersion(
+ const base::Callback<void(uint32_t)>& callback) {
+ // It is safe to capture |this| because the callback won't be run after this
+ // object goes away.
+ endpoint_client_->QueryVersion(
+ base::Bind(&InterfacePtrStateBase::OnQueryVersion, base::Unretained(this),
+ callback));
+}
+
+void InterfacePtrStateBase::RequireVersion(uint32_t version) {
+ if (version <= version_)
+ return;
+
+ version_ = version;
+ endpoint_client_->RequireVersion(version);
+}
+
+void InterfacePtrStateBase::Swap(InterfacePtrStateBase* other) {
+ using std::swap;
+ swap(other->router_, router_);
+ swap(other->endpoint_client_, endpoint_client_);
+ handle_.swap(other->handle_);
+ runner_.swap(other->runner_);
+ swap(other->version_, version_);
+}
+
+void InterfacePtrStateBase::Bind(
+ ScopedMessagePipeHandle handle,
+ uint32_t version,
+ scoped_refptr<base::SequencedTaskRunner> task_runner) {
+ DCHECK(!router_);
+ DCHECK(!endpoint_client_);
+ DCHECK(!handle_.is_valid());
+ DCHECK_EQ(0u, version_);
+ DCHECK(handle.is_valid());
+
+ handle_ = std::move(handle);
+ version_ = version;
+ runner_ =
+ GetTaskRunnerToUseFromUserProvidedTaskRunner(std::move(task_runner));
+}
+
+void InterfacePtrStateBase::OnQueryVersion(
+ const base::Callback<void(uint32_t)>& callback,
+ uint32_t version) {
+ version_ = version;
+ callback.Run(version);
+}
+
+bool InterfacePtrStateBase::InitializeEndpointClient(
+ bool passes_associated_kinds,
+ bool has_sync_methods,
+ std::unique_ptr<MessageReceiver> payload_validator) {
+ // The object hasn't been bound.
+ if (!handle_.is_valid())
+ return false;
+
+ 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, true, runner_);
+ endpoint_client_.reset(new InterfaceEndpointClient(
+ router_->CreateLocalEndpointHandle(kMasterInterfaceId), nullptr,
+ std::move(payload_validator), false, std::move(runner_),
+ // The version is only queried from the client so the value passed here
+ // will not be used.
+ 0u));
+ return true;
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/interface_ptr_state.h b/mojo/public/cpp/bindings/lib/interface_ptr_state.h
index fa54979795..2e73564a80 100644
--- a/mojo/public/cpp/bindings/lib/interface_ptr_state.h
+++ b/mojo/public/cpp/bindings/lib/interface_ptr_state.h
@@ -18,8 +18,9 @@
#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/sequenced_task_runner.h"
#include "mojo/public/cpp/bindings/associated_group.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"
@@ -32,191 +33,190 @@
namespace mojo {
namespace internal {
-template <typename Interface>
-class InterfacePtrState {
+class MOJO_CPP_BINDINGS_EXPORT InterfacePtrStateBase {
public:
- InterfacePtrState() : version_(0u) {}
+ InterfacePtrStateBase();
+ ~InterfacePtrStateBase();
+
+ MessagePipeHandle handle() const {
+ return router_ ? router_->handle() : handle_.get();
+ }
+
+ uint32_t version() const { return version_; }
+
+ bool is_bound() const { return handle_.is_valid() || endpoint_client_; }
+
+ bool encountered_error() const {
+ return endpoint_client_ ? endpoint_client_->encountered_error() : false;
+ }
+
+ bool HasAssociatedInterfaces() const {
+ return router_ ? router_->HasAssociatedEndpoints() : false;
+ }
+
+ // Returns true if bound and awaiting a response to a message.
+ bool has_pending_callbacks() const {
+ return endpoint_client_ && endpoint_client_->has_pending_responders();
+ }
+
+ protected:
+ InterfaceEndpointClient* endpoint_client() const {
+ return endpoint_client_.get();
+ }
+ MultiplexRouter* router() const { return router_.get(); }
- ~InterfacePtrState() {
+ void QueryVersion(const base::Callback<void(uint32_t)>& callback);
+ void RequireVersion(uint32_t version);
+ void Swap(InterfacePtrStateBase* other);
+ void Bind(ScopedMessagePipeHandle handle,
+ uint32_t version,
+ scoped_refptr<base::SequencedTaskRunner> task_runner);
+
+ ScopedMessagePipeHandle PassMessagePipe() {
endpoint_client_.reset();
- proxy_.reset();
- if (router_)
- router_->CloseMessagePipe();
+ return router_ ? router_->PassMessagePipe() : std::move(handle_);
}
- Interface* instance() {
+ bool InitializeEndpointClient(
+ bool passes_associated_kinds,
+ bool has_sync_methods,
+ std::unique_ptr<MessageReceiver> payload_validator);
+
+ private:
+ void OnQueryVersion(const base::Callback<void(uint32_t)>& callback,
+ uint32_t version);
+
+ scoped_refptr<MultiplexRouter> router_;
+
+ std::unique_ptr<InterfaceEndpointClient> endpoint_client_;
+
+ // |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<base::SequencedTaskRunner> runner_;
+
+ uint32_t version_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(InterfacePtrStateBase);
+};
+
+template <typename Interface>
+class InterfacePtrState : public InterfacePtrStateBase {
+ public:
+ using Proxy = typename Interface::Proxy_;
+
+ InterfacePtrState() = default;
+ ~InterfacePtrState() = default;
+
+ Proxy* 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<void(uint32_t)>& 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));
+ InterfacePtrStateBase::QueryVersion(callback);
}
void RequireVersion(uint32_t version) {
ConfigureProxyIfNecessary();
-
- if (version <= version_)
- return;
-
- version_ = version;
- endpoint_client_->RequireVersion(version);
+ InterfacePtrStateBase::RequireVersion(version);
}
void FlushForTesting() {
ConfigureProxyIfNecessary();
- endpoint_client_->FlushForTesting();
+ endpoint_client()->FlushForTesting();
}
void CloseWithReason(uint32_t custom_reason, const std::string& description) {
ConfigureProxyIfNecessary();
- endpoint_client_->CloseWithReason(custom_reason, description);
+ 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_);
+ InterfacePtrStateBase::Swap(other);
}
void Bind(InterfacePtrInfo<Interface> info,
- scoped_refptr<base::SingleThreadTaskRunner> runner) {
- DCHECK(!router_);
- DCHECK(!endpoint_client_);
+ scoped_refptr<base::SequencedTaskRunner> runner) {
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;
+ InterfacePtrStateBase::Bind(info.PassHandle(), info.version(),
+ std::move(runner));
}
// After this method is called, the object is in an invalid state and
// shouldn't be reused.
InterfacePtrInfo<Interface> PassInterface() {
- endpoint_client_.reset();
proxy_.reset();
- return InterfacePtrInfo<Interface>(
- router_ ? router_->PassMessagePipe() : std::move(handle_), version_);
+ return InterfacePtrInfo<Interface>(PassMessagePipe(), 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) {
+ void set_connection_error_handler(base::OnceClosure error_handler) {
ConfigureProxyIfNecessary();
- DCHECK(endpoint_client_);
- endpoint_client_->set_connection_error_handler(error_handler);
+ DCHECK(endpoint_client());
+ endpoint_client()->set_connection_error_handler(std::move(error_handler));
}
void set_connection_error_with_reason_handler(
- const ConnectionErrorWithReasonCallback& error_handler) {
+ 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();
+ DCHECK(endpoint_client());
+ endpoint_client()->set_connection_error_with_reason_handler(
+ std::move(error_handler));
}
AssociatedGroup* associated_group() {
ConfigureProxyIfNecessary();
- return endpoint_client_->associated_group();
+ return endpoint_client()->associated_group();
}
void EnableTestingMode() {
ConfigureProxyIfNecessary();
- router_->EnableTestingMode();
+ router()->EnableTestingMode();
}
void ForwardMessage(Message message) {
ConfigureProxyIfNecessary();
- endpoint_client_->Accept(&message);
+ endpoint_client()->Accept(&message);
}
void ForwardMessageWithResponder(Message message,
std::unique_ptr<MessageReceiver> responder) {
ConfigureProxyIfNecessary();
- endpoint_client_->AcceptWithResponder(&message, std::move(responder));
+ endpoint_client()->AcceptWithResponder(&message, std::move(responder));
}
- private:
- using Proxy = typename Interface::Proxy_;
+ void RaiseError() {
+ ConfigureProxyIfNecessary();
+ endpoint_client()->RaiseError();
+ }
+ private:
void ConfigureProxyIfNecessary() {
// The proxy has been configured.
if (proxy_) {
- DCHECK(router_);
- DCHECK(endpoint_client_);
+ 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<void(uint32_t)>& callback,
- uint32_t version) {
- version_ = version;
- callback.Run(version);
+ if (InitializeEndpointClient(
+ Interface::PassesAssociatedKinds_, Interface::HasSyncMethods_,
+ std::make_unique<typename Interface::ResponseValidator_>())) {
+ router()->SetMasterInterfaceName(Interface::Name_);
+ proxy_ = std::make_unique<Proxy>(endpoint_client());
+ }
}
- scoped_refptr<MultiplexRouter> router_;
-
- std::unique_ptr<InterfaceEndpointClient> endpoint_client_;
std::unique_ptr<Proxy> 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<base::SingleThreadTaskRunner> runner_;
-
- uint32_t version_;
-
DISALLOW_COPY_AND_ASSIGN(InterfacePtrState);
};
diff --git a/mojo/public/cpp/bindings/lib/interface_serialization.h b/mojo/public/cpp/bindings/lib/interface_serialization.h
new file mode 100644
index 0000000000..00954de261
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/interface_serialization.h
@@ -0,0 +1,139 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_SERIALIZATION_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_SERIALIZATION_H_
+
+#include <type_traits>
+
+#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"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace mojo {
+namespace internal {
+
+template <typename Base, typename T>
+struct Serializer<AssociatedInterfacePtrInfoDataView<Base>,
+ AssociatedInterfacePtrInfo<T>> {
+ static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch.");
+
+ static void Serialize(AssociatedInterfacePtrInfo<T>& input,
+ AssociatedInterface_Data* output,
+ SerializationContext* context) {
+ DCHECK(!input.handle().is_valid() || input.handle().pending_association());
+ context->AddAssociatedInterfaceInfo(input.PassHandle(), input.version(),
+ output);
+ }
+
+ static bool Deserialize(AssociatedInterface_Data* input,
+ AssociatedInterfacePtrInfo<T>* output,
+ SerializationContext* context) {
+ auto handle = context->TakeAssociatedEndpointHandle(input->handle);
+ if (!handle.is_valid()) {
+ *output = AssociatedInterfacePtrInfo<T>();
+ } else {
+ output->set_handle(std::move(handle));
+ output->set_version(input->version);
+ }
+ return true;
+ }
+};
+
+template <typename Base, typename T>
+struct Serializer<AssociatedInterfaceRequestDataView<Base>,
+ AssociatedInterfaceRequest<T>> {
+ static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch.");
+
+ static void Serialize(AssociatedInterfaceRequest<T>& input,
+ AssociatedEndpointHandle_Data* output,
+ SerializationContext* context) {
+ DCHECK(!input.handle().is_valid() || input.handle().pending_association());
+ context->AddAssociatedEndpoint(input.PassHandle(), output);
+ }
+
+ static bool Deserialize(AssociatedEndpointHandle_Data* input,
+ AssociatedInterfaceRequest<T>* output,
+ SerializationContext* context) {
+ auto handle = context->TakeAssociatedEndpointHandle(*input);
+ if (!handle.is_valid())
+ *output = AssociatedInterfaceRequest<T>();
+ else
+ *output = AssociatedInterfaceRequest<T>(std::move(handle));
+ return true;
+ }
+};
+
+template <typename Base, typename T>
+struct Serializer<InterfacePtrDataView<Base>, InterfacePtr<T>> {
+ static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch.");
+
+ static void Serialize(InterfacePtr<T>& input,
+ Interface_Data* output,
+ SerializationContext* context) {
+ InterfacePtrInfo<T> info = input.PassInterface();
+ context->AddInterfaceInfo(info.PassHandle(), info.version(), output);
+ }
+
+ static bool Deserialize(Interface_Data* input,
+ InterfacePtr<T>* output,
+ SerializationContext* context) {
+ output->Bind(InterfacePtrInfo<T>(
+ context->TakeHandleAs<mojo::MessagePipeHandle>(input->handle),
+ input->version));
+ return true;
+ }
+};
+
+template <typename Base, typename T>
+struct Serializer<InterfacePtrDataView<Base>, InterfacePtrInfo<T>> {
+ static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch.");
+
+ static void Serialize(InterfacePtrInfo<T>& input,
+ Interface_Data* output,
+ SerializationContext* context) {
+ context->AddInterfaceInfo(input.PassHandle(), input.version(), output);
+ }
+
+ static bool Deserialize(Interface_Data* input,
+ InterfacePtrInfo<T>* output,
+ SerializationContext* context) {
+ *output = InterfacePtrInfo<T>(
+ context->TakeHandleAs<mojo::MessagePipeHandle>(input->handle),
+ input->version);
+ return true;
+ }
+};
+
+template <typename Base, typename T>
+struct Serializer<InterfaceRequestDataView<Base>, InterfaceRequest<T>> {
+ static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch.");
+
+ static void Serialize(InterfaceRequest<T>& input,
+ Handle_Data* output,
+ SerializationContext* context) {
+ context->AddHandle(ScopedHandle::From(input.PassMessagePipe()), output);
+ }
+
+ static bool Deserialize(Handle_Data* input,
+ InterfaceRequest<T>* output,
+ SerializationContext* context) {
+ *output =
+ InterfaceRequest<T>(context->TakeHandleAs<MessagePipeHandle>(*input));
+ return true;
+ }
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_SERIALIZATION_H_
diff --git a/mojo/public/cpp/bindings/lib/map_data_internal.h b/mojo/public/cpp/bindings/lib/map_data_internal.h
index f8e3d2918f..217904fd43 100644
--- a/mojo/public/cpp/bindings/lib/map_data_internal.h
+++ b/mojo/public/cpp/bindings/lib/map_data_internal.h
@@ -5,6 +5,7 @@
#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_DATA_INTERNAL_H_
#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_DATA_INTERNAL_H_
+#include "base/macros.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"
@@ -18,9 +19,29 @@ namespace internal {
template <typename Key, typename Value>
class Map_Data {
public:
- static Map_Data* New(Buffer* buf) {
- return new (buf->Allocate(sizeof(Map_Data))) Map_Data();
- }
+ class BufferWriter {
+ public:
+ BufferWriter() = default;
+
+ void Allocate(Buffer* buffer) {
+ buffer_ = buffer;
+ index_ = buffer_->Allocate(sizeof(Map_Data));
+ new (data()) Map_Data();
+ }
+
+ bool is_null() const { return !buffer_; }
+ Map_Data* data() {
+ DCHECK(!is_null());
+ return buffer_->Get<Map_Data>(index_);
+ }
+ Map_Data* operator->() { return data(); }
+
+ private:
+ Buffer* buffer_ = nullptr;
+ size_t index_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferWriter);
+ };
// |validate_params| must have non-null |key_validate_params| and
// |element_validate_params| members.
@@ -41,16 +62,13 @@ class Map_Data {
return false;
}
- if (!ValidatePointerNonNullable(
- object->keys, "null key array in map struct", validation_context) ||
+ if (!ValidatePointerNonNullable(object->keys, 0, 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) ||
+ if (!ValidatePointerNonNullable(object->values, 1, validation_context) ||
!ValidateContainer(object->values, validation_context,
validate_params->element_validate_params)) {
return false;
diff --git a/mojo/public/cpp/bindings/lib/map_serialization.h b/mojo/public/cpp/bindings/lib/map_serialization.h
index 718a76307d..b114f4995c 100644
--- a/mojo/public/cpp/bindings/lib/map_serialization.h
+++ b/mojo/public/cpp/bindings/lib/map_serialization.h
@@ -95,57 +95,34 @@ struct Serializer<MapDataView<Key, Value>, MaybeConstUserType> {
std::vector<UserValue>,
MapValueReader<MaybeConstUserType>>;
- static size_t PrepareToSerialize(MaybeConstUserType& input,
- SerializationContext* context) {
- if (CallIsNullIfExists<Traits>(input))
- return 0;
-
- size_t struct_overhead = sizeof(Data);
- MapKeyReader<MaybeConstUserType> key_reader(input);
- size_t keys_size =
- KeyArraySerializer::GetSerializedSize(&key_reader, context);
- MapValueReader<MaybeConstUserType> 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,
+ typename Data::BufferWriter* writer,
const ContainerValidateParams* validate_params,
SerializationContext* context) {
DCHECK(validate_params->key_validate_params);
DCHECK(validate_params->element_validate_params);
- if (CallIsNullIfExists<Traits>(input)) {
- *output = nullptr;
+ if (CallIsNullIfExists<Traits>(input))
return;
- }
- auto result = Data::New(buf);
- if (result) {
- auto keys_ptr = MojomTypeTraits<ArrayDataView<Key>>::Data::New(
- Traits::GetSize(input), buf);
- if (keys_ptr) {
- MapKeyReader<MaybeConstUserType> 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<ArrayDataView<Value>>::Data::New(
- Traits::GetSize(input), buf);
- if (values_ptr) {
- MapValueReader<MaybeConstUserType> value_reader(input);
- ValueArraySerializer::SerializeElements(
- &value_reader, buf, values_ptr,
- validate_params->element_validate_params, context);
- result->values.Set(values_ptr);
- }
- }
- *output = result;
+ writer->Allocate(buf);
+ typename MojomTypeTraits<ArrayDataView<Key>>::Data::BufferWriter
+ keys_writer;
+ keys_writer.Allocate(Traits::GetSize(input), buf);
+ MapKeyReader<MaybeConstUserType> key_reader(input);
+ KeyArraySerializer::SerializeElements(&key_reader, buf, &keys_writer,
+ validate_params->key_validate_params,
+ context);
+ (*writer)->keys.Set(keys_writer.data());
+
+ typename MojomTypeTraits<ArrayDataView<Value>>::Data::BufferWriter
+ values_writer;
+ values_writer.Allocate(Traits::GetSize(input), buf);
+ MapValueReader<MaybeConstUserType> value_reader(input);
+ ValueArraySerializer::SerializeElements(
+ &value_reader, buf, &values_writer,
+ validate_params->element_validate_params, context);
+ (*writer)->values.Set(values_writer.data());
}
static bool Deserialize(Data* input,
diff --git a/mojo/public/cpp/bindings/lib/may_auto_lock.h b/mojo/public/cpp/bindings/lib/may_auto_lock.h
index 06091fee90..78cb89fa77 100644
--- a/mojo/public/cpp/bindings/lib/may_auto_lock.h
+++ b/mojo/public/cpp/bindings/lib/may_auto_lock.h
@@ -5,6 +5,7 @@
#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MAY_AUTO_LOCK_H_
#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MAY_AUTO_LOCK_H_
+#include "base/component_export.h"
#include "base/macros.h"
#include "base/optional.h"
#include "base/synchronization/lock.h"
@@ -14,7 +15,7 @@ namespace internal {
// Similar to base::AutoLock, except that it does nothing if |lock| passed into
// the constructor is null.
-class MayAutoLock {
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) MayAutoLock {
public:
explicit MayAutoLock(base::Optional<base::Lock>* lock)
: lock_(lock->has_value() ? &lock->value() : nullptr) {
@@ -36,7 +37,7 @@ class MayAutoLock {
// Similar to base::AutoUnlock, except that it does nothing if |lock| passed
// into the constructor is null.
-class MayAutoUnlock {
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) MayAutoUnlock {
public:
explicit MayAutoUnlock(base::Optional<base::Lock>* lock)
: lock_(lock->has_value() ? &lock->value() : nullptr) {
diff --git a/mojo/public/cpp/bindings/lib/message.cc b/mojo/public/cpp/bindings/lib/message.cc
index e5f3808117..8972d9efd1 100644
--- a/mojo/public/cpp/bindings/lib/message.cc
+++ b/mojo/public/cpp/bindings/lib/message.cc
@@ -14,72 +14,279 @@
#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
+#include "base/numerics/safe_math.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"
+#include "mojo/public/cpp/bindings/lib/unserialized_message_context.h"
namespace mojo {
namespace {
base::LazyInstance<base::ThreadLocalPointer<internal::MessageDispatchContext>>::
- DestructorAtExit g_tls_message_dispatch_context = LAZY_INSTANCE_INITIALIZER;
+ Leaky g_tls_message_dispatch_context = LAZY_INSTANCE_INITIALIZER;
-base::LazyInstance<base::ThreadLocalPointer<SyncMessageResponseContext>>::
- DestructorAtExit g_tls_sync_response_context = LAZY_INSTANCE_INITIALIZER;
+base::LazyInstance<base::ThreadLocalPointer<SyncMessageResponseContext>>::Leaky
+ g_tls_sync_response_context = LAZY_INSTANCE_INITIALIZER;
void DoNotifyBadMessage(Message message, const std::string& error) {
message.NotifyBadMessage(error);
}
-} // namespace
+template <typename HeaderType>
+void AllocateHeaderFromBuffer(internal::Buffer* buffer, HeaderType** header) {
+ *header = buffer->AllocateAndGet<HeaderType>();
+ (*header)->num_bytes = sizeof(HeaderType);
+}
+
+void WriteMessageHeader(uint32_t name,
+ uint32_t flags,
+ size_t payload_interface_id_count,
+ internal::Buffer* payload_buffer) {
+ if (payload_interface_id_count > 0) {
+ // Version 2
+ internal::MessageHeaderV2* header;
+ AllocateHeaderFromBuffer(payload_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
+ internal::MessageHeaderV1* header;
+ AllocateHeaderFromBuffer(payload_buffer, &header);
+ header->version = 1;
+ header->name = name;
+ header->flags = flags;
+ } else {
+ internal::MessageHeader* header;
+ AllocateHeaderFromBuffer(payload_buffer, &header);
+ header->version = 0;
+ header->name = name;
+ header->flags = flags;
+ }
+}
+
+void CreateSerializedMessageObject(uint32_t name,
+ uint32_t flags,
+ size_t payload_size,
+ size_t payload_interface_id_count,
+ std::vector<ScopedHandle>* handles,
+ ScopedMessageHandle* out_handle,
+ internal::Buffer* out_buffer) {
+ ScopedMessageHandle handle;
+ MojoResult rv = mojo::CreateMessage(&handle);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+ DCHECK(handle.is_valid());
+
+ void* buffer;
+ uint32_t buffer_size;
+ size_t total_size = internal::ComputeSerializedMessageSize(
+ flags, payload_size, payload_interface_id_count);
+ DCHECK(base::IsValueInRangeForNumericType<uint32_t>(total_size));
+ DCHECK(!handles ||
+ base::IsValueInRangeForNumericType<uint32_t>(handles->size()));
+ rv = MojoAppendMessageData(
+ handle->value(), static_cast<uint32_t>(total_size),
+ handles ? reinterpret_cast<MojoHandle*>(handles->data()) : nullptr,
+ handles ? static_cast<uint32_t>(handles->size()) : 0, nullptr, &buffer,
+ &buffer_size);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+ if (handles) {
+ // Handle ownership has been taken by MojoAppendMessageData.
+ for (size_t i = 0; i < handles->size(); ++i)
+ ignore_result(handles->at(i).release());
+ }
+
+ internal::Buffer payload_buffer(handle.get(), total_size, buffer,
+ buffer_size);
+
+ // Make sure we zero the memory first!
+ memset(payload_buffer.data(), 0, total_size);
+ WriteMessageHeader(name, flags, payload_interface_id_count, &payload_buffer);
+
+ *out_handle = std::move(handle);
+ *out_buffer = std::move(payload_buffer);
+}
+
+void SerializeUnserializedContext(MojoMessageHandle message,
+ uintptr_t context_value) {
+ auto* context =
+ reinterpret_cast<internal::UnserializedMessageContext*>(context_value);
+ void* buffer;
+ uint32_t buffer_size;
+ MojoResult attach_result = MojoAppendMessageData(
+ message, 0, nullptr, 0, nullptr, &buffer, &buffer_size);
+ if (attach_result != MOJO_RESULT_OK)
+ return;
+
+ internal::Buffer payload_buffer(MessageHandle(message), 0, buffer,
+ buffer_size);
+ WriteMessageHeader(context->message_name(), context->message_flags(),
+ 0 /* payload_interface_id_count */, &payload_buffer);
+
+ // We need to copy additional header data which may have been set after
+ // message construction, as this codepath may be reached at some arbitrary
+ // time between message send and message dispatch.
+ static_cast<internal::MessageHeader*>(buffer)->interface_id =
+ context->header()->interface_id;
+ if (context->header()->flags &
+ (Message::kFlagExpectsResponse | Message::kFlagIsResponse)) {
+ DCHECK_GE(context->header()->version, 1u);
+ static_cast<internal::MessageHeaderV1*>(buffer)->request_id =
+ context->header()->request_id;
+ }
+
+ internal::SerializationContext serialization_context;
+ context->Serialize(&serialization_context, &payload_buffer);
+
+ // TODO(crbug.com/753433): Support lazy serialization of associated endpoint
+ // handles. See corresponding TODO in the bindings generator for proof that
+ // this DCHECK is indeed valid.
+ DCHECK(serialization_context.associated_endpoint_handles()->empty());
+ if (!serialization_context.handles()->empty())
+ payload_buffer.AttachHandles(serialization_context.mutable_handles());
+ payload_buffer.Seal();
+}
+
+void DestroyUnserializedContext(uintptr_t context) {
+ delete reinterpret_cast<internal::UnserializedMessageContext*>(context);
+}
-Message::Message() {
+ScopedMessageHandle CreateUnserializedMessageObject(
+ std::unique_ptr<internal::UnserializedMessageContext> context) {
+ ScopedMessageHandle handle;
+ MojoResult rv = mojo::CreateMessage(&handle);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+ DCHECK(handle.is_valid());
+
+ rv = MojoSetMessageContext(
+ handle->value(), reinterpret_cast<uintptr_t>(context.release()),
+ &SerializeUnserializedContext, &DestroyUnserializedContext, nullptr);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+ return handle;
}
+} // namespace
+
+Message::Message() = default;
+
Message::Message(Message&& other)
- : buffer_(std::move(other.buffer_)),
+ : handle_(std::move(other.handle_)),
+ payload_buffer_(std::move(other.payload_buffer_)),
handles_(std::move(other.handles_)),
associated_endpoint_handles_(
- std::move(other.associated_endpoint_handles_)) {}
+ std::move(other.associated_endpoint_handles_)),
+ transferable_(other.transferable_),
+ serialized_(other.serialized_) {
+ other.transferable_ = false;
+ other.serialized_ = false;
+#if defined(ENABLE_IPC_FUZZER)
+ interface_name_ = other.interface_name_;
+ method_name_ = other.method_name_;
+#endif
+}
+
+Message::Message(std::unique_ptr<internal::UnserializedMessageContext> context)
+ : Message(CreateUnserializedMessageObject(std::move(context))) {}
+
+Message::Message(uint32_t name,
+ uint32_t flags,
+ size_t payload_size,
+ size_t payload_interface_id_count,
+ std::vector<ScopedHandle>* handles) {
+ CreateSerializedMessageObject(name, flags, payload_size,
+ payload_interface_id_count, handles, &handle_,
+ &payload_buffer_);
+ transferable_ = true;
+ serialized_ = true;
+}
+
+Message::Message(ScopedMessageHandle handle) {
+ DCHECK(handle.is_valid());
+
+ uintptr_t context_value = 0;
+ MojoResult get_context_result =
+ MojoGetMessageContext(handle->value(), nullptr, &context_value);
+ if (get_context_result == MOJO_RESULT_NOT_FOUND) {
+ // It's a serialized message. Extract handles if possible.
+ uint32_t num_bytes;
+ void* buffer;
+ uint32_t num_handles = 0;
+ MojoResult rv = MojoGetMessageData(handle->value(), nullptr, &buffer,
+ &num_bytes, nullptr, &num_handles);
+ if (rv == MOJO_RESULT_RESOURCE_EXHAUSTED) {
+ handles_.resize(num_handles);
+ rv = MojoGetMessageData(handle->value(), nullptr, &buffer, &num_bytes,
+ reinterpret_cast<MojoHandle*>(handles_.data()),
+ &num_handles);
+ } else {
+ // No handles, so it's safe to retransmit this message if the caller
+ // really wants to.
+ transferable_ = true;
+ }
-Message::~Message() {
- CloseHandles();
+ if (rv != MOJO_RESULT_OK) {
+ // Failed to deserialize handles. Leave the Message uninitialized.
+ return;
+ }
+
+ payload_buffer_ = internal::Buffer(buffer, num_bytes, num_bytes);
+ serialized_ = true;
+ } else {
+ DCHECK_EQ(MOJO_RESULT_OK, get_context_result);
+ auto* context =
+ reinterpret_cast<internal::UnserializedMessageContext*>(context_value);
+ // Dummy data address so common header accessors still behave properly. The
+ // choice is V1 reflects unserialized message capabilities: we may or may
+ // not need to support request IDs (which require at least V1), but we never
+ // (for now, anyway) need to support associated interface handles (V2).
+ payload_buffer_ =
+ internal::Buffer(context->header(), sizeof(internal::MessageHeaderV1),
+ sizeof(internal::MessageHeaderV1));
+ transferable_ = true;
+ serialized_ = false;
+ }
+
+ handle_ = std::move(handle);
}
+Message::~Message() = default;
+
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_);
+ handle_ = std::move(other.handle_);
+ payload_buffer_ = std::move(other.payload_buffer_);
+ handles_ = std::move(other.handles_);
+ associated_endpoint_handles_ = std::move(other.associated_endpoint_handles_);
+ transferable_ = other.transferable_;
+ other.transferable_ = false;
+ serialized_ = other.serialized_;
+ other.serialized_ = false;
+#if defined(ENABLE_IPC_FUZZER)
+ interface_name_ = other.interface_name_;
+ method_name_ = other.method_name_;
+#endif
return *this;
}
void Message::Reset() {
- CloseHandles();
+ handle_.reset();
+ payload_buffer_.Reset();
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<Handle>* handles) {
- DCHECK(!buffer_);
- buffer_.reset(new internal::MessageBuffer(std::move(message), num_bytes));
- handles_.swap(*handles);
+ transferable_ = false;
+ serialized_ = false;
}
const uint8_t* Message::payload() const {
if (version() < 2)
return data() + header()->num_bytes;
+ DCHECK(!header_v2()->payload.is_null());
return static_cast<const uint8_t*>(header_v2()->payload.Get());
}
@@ -89,19 +296,16 @@ uint32_t Message::payload_num_bytes() const {
if (version() < 2) {
num_bytes = data_num_bytes() - header()->num_bytes;
} else {
- auto payload = reinterpret_cast<uintptr_t>(header_v2()->payload.Get());
- if (!payload) {
- num_bytes = 0;
- } else {
- auto payload_end =
- reinterpret_cast<uintptr_t>(header_v2()->payload_interface_ids.Get());
- if (!payload_end)
- payload_end = reinterpret_cast<uintptr_t>(data() + data_num_bytes());
- DCHECK_GE(payload_end, payload);
- num_bytes = payload_end - payload;
- }
+ auto payload_begin =
+ reinterpret_cast<uintptr_t>(header_v2()->payload.Get());
+ auto payload_end =
+ reinterpret_cast<uintptr_t>(header_v2()->payload_interface_ids.Get());
+ if (!payload_end)
+ payload_end = reinterpret_cast<uintptr_t>(data() + data_num_bytes());
+ DCHECK_GE(payload_end, payload_begin);
+ num_bytes = payload_end - payload_begin;
}
- DCHECK_LE(num_bytes, std::numeric_limits<uint32_t>::max());
+ DCHECK(base::IsValueInRangeForNumericType<uint32_t>(num_bytes));
return static_cast<uint32_t>(num_bytes);
}
@@ -117,52 +321,52 @@ const uint32_t* Message::payload_interface_ids() const {
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());
+void Message::AttachHandlesFromSerializationContext(
+ internal::SerializationContext* context) {
+ if (context->handles()->empty() &&
+ context->associated_endpoint_handles()->empty()) {
+ // No handles attached, so no extra serialization work.
+ return;
+ }
- if (handles_.empty()) // Fast path for the common case: No handles.
- return buffer_->TakeMessage();
+ if (context->associated_endpoint_handles()->empty()) {
+ // Attaching only non-associated handles is easier since we don't have to
+ // modify the message header. Faster path for that.
+ payload_buffer_.AttachHandles(context->mutable_handles());
+ return;
+ }
- // Allocate a new message with space for the handles, then copy the buffer
- // contents into it.
+ // Allocate a new message with enough space to hold all attached handles. Copy
+ // this message's contents into the new one and use it to replace ourself.
//
- // 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<const MojoHandle*>(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;
+ // TODO(rockot): We could avoid the extra full message allocation by instead
+ // growing the buffer and carefully moving its contents around. This errs on
+ // the side of less complexity with probably only marginal performance cost.
+ uint32_t payload_size = payload_num_bytes();
+ mojo::Message new_message(name(), header()->flags, payload_size,
+ context->associated_endpoint_handles()->size(),
+ context->mutable_handles());
+ std::swap(*context->mutable_associated_endpoint_handles(),
+ new_message.associated_endpoint_handles_);
+ memcpy(new_message.payload_buffer()->AllocateAndGet(payload_size), payload(),
+ payload_size);
+ *this = std::move(new_message);
}
-void Message::NotifyBadMessage(const std::string& error) {
- DCHECK(buffer_);
- buffer_->NotifyBadMessage(error);
+ScopedMessageHandle Message::TakeMojoMessage() {
+ // If there are associated endpoints transferred,
+ // SerializeAssociatedEndpointHandles() must be called before this method.
+ DCHECK(associated_endpoint_handles_.empty());
+ DCHECK(transferable_);
+ payload_buffer_.Seal();
+ auto handle = std::move(handle_);
+ Reset();
+ return handle;
}
-void Message::CloseHandles() {
- for (std::vector<Handle>::iterator it = handles_.begin();
- it != handles_.end(); ++it) {
- if (it->is_valid())
- CloseRaw(*it);
- }
+void Message::NotifyBadMessage(const std::string& error) {
+ DCHECK(handle_.is_valid());
+ mojo::NotifyBadMessage(handle_.get(), error);
}
void Message::SerializeAssociatedEndpointHandles(
@@ -172,16 +376,20 @@ void Message::SerializeAssociatedEndpointHandles(
DCHECK_GE(version(), 2u);
DCHECK(header_v2()->payload_interface_ids.is_null());
+ DCHECK(payload_buffer_.is_valid());
+ DCHECK(handle_.is_valid());
size_t size = associated_endpoint_handles_.size();
- auto* data = internal::Array_Data<uint32_t>::New(size, buffer());
- header_v2()->payload_interface_ids.Set(data);
+
+ internal::Array_Data<uint32_t>::BufferWriter handle_writer;
+ handle_writer.Allocate(size, &payload_buffer_);
+ header_v2()->payload_interface_ids.Set(handle_writer.data());
for (size_t i = 0; i < size; ++i) {
ScopedInterfaceEndpointHandle& handle = associated_endpoint_handles_[i];
DCHECK(handle.pending_association());
- data->storage()[i] =
+ handle_writer->storage()[i] =
group_controller->AssociateInterface(std::move(handle));
}
associated_endpoint_handles_.clear();
@@ -189,6 +397,9 @@ void Message::SerializeAssociatedEndpointHandles(
bool Message::DeserializeAssociatedEndpointHandles(
AssociatedGroupController* group_controller) {
+ if (!serialized_)
+ return true;
+
associated_endpoint_handles_.clear();
uint32_t num_ids = payload_num_interface_ids();
@@ -213,11 +424,48 @@ bool Message::DeserializeAssociatedEndpointHandles(
return result;
}
+void Message::SerializeIfNecessary() {
+ MojoResult rv = MojoSerializeMessage(handle_->value(), nullptr);
+ if (rv == MOJO_RESULT_FAILED_PRECONDITION)
+ return;
+
+ // Reconstruct this Message instance from the serialized message's handle.
+ *this = Message(std::move(handle_));
+}
+
+std::unique_ptr<internal::UnserializedMessageContext>
+Message::TakeUnserializedContext(
+ const internal::UnserializedMessageContext::Tag* tag) {
+ DCHECK(handle_.is_valid());
+ uintptr_t context_value = 0;
+ MojoResult rv =
+ MojoGetMessageContext(handle_->value(), nullptr, &context_value);
+ if (rv == MOJO_RESULT_NOT_FOUND)
+ return nullptr;
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+
+ auto* context =
+ reinterpret_cast<internal::UnserializedMessageContext*>(context_value);
+ if (context->tag() != tag)
+ return nullptr;
+
+ // Detach the context from the message.
+ rv = MojoSetMessageContext(handle_->value(), 0, nullptr, nullptr, nullptr);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+ return base::WrapUnique(context);
+}
+
+bool MessageReceiver::PrefersSerializedMessages() {
+ return false;
+}
+
PassThroughFilter::PassThroughFilter() {}
PassThroughFilter::~PassThroughFilter() {}
-bool PassThroughFilter::Accept(Message* message) { return true; }
+bool PassThroughFilter::Accept(Message* message) {
+ return true;
+}
SyncMessageResponseContext::SyncMessageResponseContext()
: outer_context_(current()) {
@@ -238,43 +486,19 @@ 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_;
+ReportBadMessageCallback SyncMessageResponseContext::GetBadMessageCallback() {
+ DCHECK(!response_.IsNull());
+ return base::BindOnce(&DoNotifyBadMessage, std::move(response_));
}
MojoResult ReadMessage(MessagePipeHandle handle, Message* message) {
- MojoResult rv;
-
- std::vector<Handle> 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<MojoHandle*>(handles.data()),
- &num_handles,
- MOJO_READ_MESSAGE_FLAG_NONE);
- }
-
+ ScopedMessageHandle message_handle;
+ MojoResult rv =
+ ReadMessageNew(handle, &message_handle, MOJO_READ_MESSAGE_FLAG_NONE);
if (rv != MOJO_RESULT_OK)
return rv;
- message->InitializeFromMojoMessage(
- std::move(mojo_message), num_bytes, &handles);
+ *message = Message(std::move(message_handle));
return MOJO_RESULT_OK;
}
@@ -311,13 +535,9 @@ 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_;
+ReportBadMessageCallback MessageDispatchContext::GetBadMessageCallback() {
+ DCHECK(!message_->IsNull());
+ return base::BindOnce(&DoNotifyBadMessage, std::move(*message_));
}
// static
diff --git a/mojo/public/cpp/bindings/lib/message_buffer.cc b/mojo/public/cpp/bindings/lib/message_buffer.cc
deleted file mode 100644
index cc12ef6e31..0000000000
--- a/mojo/public/cpp/bindings/lib/message_buffer.cc
+++ /dev/null
@@ -1,52 +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 "mojo/public/cpp/bindings/lib/message_buffer.h"
-
-#include <limits>
-
-#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<uint32_t>::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
deleted file mode 100644
index 96d5140f77..0000000000
--- a/mojo/public/cpp/bindings/lib/message_buffer.h
+++ /dev/null
@@ -1,43 +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 MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_LIB_MESSAGE_BUFFER_H_
-#define MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_LIB_MESSAGE_BUFFER_H_
-
-#include <stdint.h>
-
-#include <utility>
-
-#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
deleted file mode 100644
index 6806a73213..0000000000
--- a/mojo/public/cpp/bindings/lib/message_builder.cc
+++ /dev/null
@@ -1,69 +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.
-
-#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 <typename Header>
-void Allocate(Buffer* buf, Header** header) {
- *header = static_cast<Header*>(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<uint32_t>::GetStorageSize(
- static_cast<uint32_t>(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<uint32_t>(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
deleted file mode 100644
index 8a4d5c4690..0000000000
--- a/mojo/public/cpp/bindings/lib/message_builder.h
+++ /dev/null
@@ -1,45 +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 MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_BUILDER_H_
-#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_BUILDER_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#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_dumper.cc b/mojo/public/cpp/bindings/lib/message_dumper.cc
new file mode 100644
index 0000000000..35696bbcbf
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/message_dumper.cc
@@ -0,0 +1,96 @@
+// 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.
+
+#include "mojo/public/cpp/bindings/message_dumper.h"
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/no_destructor.h"
+#include "base/process/process.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task_scheduler/post_task.h"
+#include "mojo/public/cpp/bindings/message.h"
+
+namespace {
+
+base::FilePath& DumpDirectory() {
+ static base::NoDestructor<base::FilePath> dump_directory;
+ return *dump_directory;
+}
+
+// void WriteMessage(uint32_t identifier,
+// const mojo::MessageDumper::MessageEntry& entry) {
+// static uint64_t num = 0;
+
+// if (!entry.interface_name)
+// return;
+
+// base::FilePath message_directory =
+// DumpDirectory()
+// .AppendASCII(entry.interface_name)
+// .AppendASCII(base::NumberToString(identifier));
+
+// if (!base::DirectoryExists(message_directory) &&
+// !base::CreateDirectory(message_directory)) {
+// LOG(ERROR) << "Failed to create" << message_directory.value();
+// return;
+// }
+
+// std::string filename =
+// base::NumberToString(num++) + "." + entry.method_name + ".mojomsg";
+// base::FilePath path = message_directory.AppendASCII(filename);
+// base::File file(path,
+// base::File::FLAG_WRITE | base::File::FLAG_CREATE_ALWAYS);
+
+// file.WriteAtCurrentPos(reinterpret_cast<const char*>(entry.data_bytes.data()),
+// static_cast<int>(entry.data_bytes.size()));
+// }
+
+} // namespace
+
+namespace mojo {
+
+MessageDumper::MessageEntry::MessageEntry(const uint8_t* data,
+ uint32_t data_size,
+ const char* interface_name,
+ const char* method_name)
+ : interface_name(interface_name),
+ method_name(method_name),
+ data_bytes(data, data + data_size) {}
+
+MessageDumper::MessageEntry::MessageEntry(const MessageEntry& entry) = default;
+
+MessageDumper::MessageEntry::~MessageEntry() {}
+
+MessageDumper::MessageDumper() : identifier_(base::RandUint64()) {}
+
+MessageDumper::~MessageDumper() {}
+
+bool MessageDumper::Accept(mojo::Message* message) {
+ // MessageEntry entry(message->data(), message->data_num_bytes(),
+ // "unknown interface", "unknown name");
+
+ // static base::NoDestructor<scoped_refptr<base::TaskRunner>> task_runner(
+ // base::CreateSequencedTaskRunnerWithTraits(
+ // {base::MayBlock(), base::TaskPriority::USER_BLOCKING,
+ // base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}));
+
+ // (*task_runner)
+ // ->PostTask(FROM_HERE,
+ // base::BindOnce(&WriteMessage, identifier_, std::move(entry)));
+ return true;
+}
+
+void MessageDumper::SetMessageDumpDirectory(const base::FilePath& directory) {
+ DumpDirectory() = directory;
+}
+
+const base::FilePath& MessageDumper::GetMessageDumpDirectory() {
+ return DumpDirectory();
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/message_header_validator.cc b/mojo/public/cpp/bindings/lib/message_header_validator.cc
index 9f8c6278c0..46bc5ed6e3 100644
--- a/mojo/public/cpp/bindings/lib/message_header_validator.cc
+++ b/mojo/public/cpp/bindings/lib/message_header_validator.cc
@@ -73,9 +73,10 @@ bool IsValidMessageHeader(const internal::MessageHeader* header,
// 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))) {
+ if (!internal::ValidatePointerNonNullable(header_v2->payload, 5,
+ validation_context) ||
+ !internal::ValidatePointer(header_v2->payload, validation_context) ||
+ !validation_context->ClaimMemory(header_v2->payload.Get(), 1)) {
return false;
}
@@ -115,6 +116,10 @@ void MessageHeaderValidator::SetDescription(const std::string& description) {
}
bool MessageHeaderValidator::Accept(Message* message) {
+ // Don't bother validating unserialized message headers.
+ if (!message->is_serialized())
+ return true;
+
// 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(
diff --git a/mojo/public/cpp/bindings/lib/message_internal.cc b/mojo/public/cpp/bindings/lib/message_internal.cc
new file mode 100644
index 0000000000..445eb4d891
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/message_internal.cc
@@ -0,0 +1,45 @@
+// 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/lib/message_internal.h"
+
+#include "mojo/public/cpp/bindings/lib/array_internal.h"
+#include "mojo/public/cpp/bindings/message.h"
+
+namespace mojo {
+namespace internal {
+
+namespace {
+
+size_t ComputeHeaderSize(uint32_t flags, size_t payload_interface_id_count) {
+ if (payload_interface_id_count > 0) {
+ // Version 2
+ return sizeof(MessageHeaderV2);
+ } else if (flags &
+ (Message::kFlagExpectsResponse | Message::kFlagIsResponse)) {
+ // Version 1
+ return sizeof(MessageHeaderV1);
+ } else {
+ // Version 0
+ return sizeof(MessageHeader);
+ }
+}
+
+} // namespace
+
+size_t ComputeSerializedMessageSize(uint32_t flags,
+ size_t payload_size,
+ size_t payload_interface_id_count) {
+ const size_t header_size =
+ ComputeHeaderSize(flags, payload_interface_id_count);
+ if (payload_interface_id_count > 0) {
+ return Align(header_size + Align(payload_size) +
+ ArrayDataTraits<uint32_t>::GetStorageSize(
+ static_cast<uint32_t>(payload_interface_id_count)));
+ }
+ return internal::Align(header_size + payload_size);
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/message_internal.h b/mojo/public/cpp/bindings/lib/message_internal.h
index 6693198f81..40539e27aa 100644
--- a/mojo/public/cpp/bindings/lib/message_internal.h
+++ b/mojo/public/cpp/bindings/lib/message_internal.h
@@ -10,8 +10,8 @@
#include <string>
#include "base/callback.h"
+#include "base/component_export.h"
#include "base/macros.h"
-#include "mojo/public/cpp/bindings/bindings_export.h"
#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
namespace mojo {
@@ -54,28 +54,32 @@ static_assert(sizeof(MessageHeaderV2) == 48, "Bad sizeof(MessageHeaderV2)");
#pragma pack(pop)
-class MOJO_CPP_BINDINGS_EXPORT MessageDispatchContext {
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) MessageDispatchContext {
public:
explicit MessageDispatchContext(Message* message);
~MessageDispatchContext();
static MessageDispatchContext* current();
- const base::Callback<void(const std::string&)>& GetBadMessageCallback();
+ base::OnceCallback<void(const std::string&)> GetBadMessageCallback();
private:
MessageDispatchContext* outer_context_;
Message* message_;
- base::Callback<void(const std::string&)> bad_message_callback_;
DISALLOW_COPY_AND_ASSIGN(MessageDispatchContext);
};
-class MOJO_CPP_BINDINGS_EXPORT SyncMessageResponseSetup {
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) SyncMessageResponseSetup {
public:
static void SetCurrentSyncResponseMessage(Message* message);
};
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+size_t ComputeSerializedMessageSize(uint32_t flags,
+ size_t payload_size,
+ size_t payload_interface_id_count);
+
} // namespace internal
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/multiplex_router.cc b/mojo/public/cpp/bindings/lib/multiplex_router.cc
index ff7c678289..61833097ef 100644
--- a/mojo/public/cpp/bindings/lib/multiplex_router.cc
+++ b/mojo/public/cpp/bindings/lib/multiplex_router.cc
@@ -12,14 +12,13 @@
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
-#include "base/single_thread_task_runner.h"
+#include "base/sequenced_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"
+#include "mojo/public/cpp/bindings/sequence_local_sync_event_watcher.h"
namespace mojo {
namespace internal {
@@ -41,7 +40,7 @@ class MultiplexRouter::InterfaceEndpoint
client_(nullptr) {}
// ---------------------------------------------------------------------------
- // The following public methods are safe to call from any threads without
+ // The following public methods are safe to call from any sequence without
// locking.
InterfaceId id() const { return id_; }
@@ -76,29 +75,27 @@ class MultiplexRouter::InterfaceEndpoint
disconnect_reason_ = disconnect_reason;
}
- base::SingleThreadTaskRunner* task_runner() const {
- return task_runner_.get();
- }
+ base::SequencedTaskRunner* task_runner() const { return task_runner_.get(); }
InterfaceEndpointClient* client() const { return client_; }
void AttachClient(InterfaceEndpointClient* client,
- scoped_refptr<base::SingleThreadTaskRunner> runner) {
+ scoped_refptr<base::SequencedTaskRunner> runner) {
router_->AssertLockAcquired();
DCHECK(!client_);
DCHECK(!closed_);
- DCHECK(runner->BelongsToCurrentThread());
+ DCHECK(runner->RunsTasksInCurrentSequence());
task_runner_ = std::move(runner);
client_ = client;
}
- // This method must be called on the same thread as the corresponding
+ // This method must be called on the same sequence as the corresponding
// AttachClient() call.
void DetachClient() {
router_->AssertLockAcquired();
DCHECK(client_);
- DCHECK(task_runner_->BelongsToCurrentThread());
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
DCHECK(!closed_);
task_runner_ = nullptr;
@@ -111,8 +108,8 @@ class MultiplexRouter::InterfaceEndpoint
if (sync_message_event_signaled_)
return;
sync_message_event_signaled_ = true;
- if (sync_message_event_)
- sync_message_event_->Signal();
+ if (sync_watcher_)
+ sync_watcher_->SignalEvent();
}
void ResetSyncMessageSignal() {
@@ -120,30 +117,30 @@ class MultiplexRouter::InterfaceEndpoint
if (!sync_message_event_signaled_)
return;
sync_message_event_signaled_ = false;
- if (sync_message_event_)
- sync_message_event_->Reset();
+ if (sync_watcher_)
+ sync_watcher_->ResetEvent();
}
// ---------------------------------------------------------------------------
// The following public methods (i.e., InterfaceEndpointController
- // implementation) are called by the client on the same thread as the
+ // implementation) are called by the client on the same sequence as the
// AttachClient() call. They are called outside of the router's lock.
bool SendMessage(Message* message) override {
- DCHECK(task_runner_->BelongsToCurrentThread());
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
message->set_interface_id(id_);
return router_->connector_.Accept(message);
}
void AllowWokenUpBySyncWatchOnSameThread() override {
- DCHECK(task_runner_->BelongsToCurrentThread());
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
EnsureSyncWatcherExists();
- sync_watcher_->AllowWokenUpBySyncWatchOnSameThread();
+ sync_watcher_->AllowWokenUpBySyncWatchOnSameSequence();
}
bool SyncWatch(const bool* should_stop) override {
- DCHECK(task_runner_->BelongsToCurrentThread());
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
EnsureSyncWatcherExists();
return sync_watcher_->SyncWatch(should_stop);
@@ -156,13 +153,10 @@ class MultiplexRouter::InterfaceEndpoint
router_->AssertLockAcquired();
DCHECK(!client_);
- DCHECK(closed_);
- DCHECK(peer_closed_);
- DCHECK(!sync_watcher_);
}
void OnSyncEventSignaled() {
- DCHECK(task_runner_->BelongsToCurrentThread());
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
scoped_refptr<MultiplexRouter> router_protector(router_);
MayAutoLock locker(&router_->lock_);
@@ -184,28 +178,20 @@ class MultiplexRouter::InterfaceEndpoint
}
void EnsureSyncWatcherExists() {
- DCHECK(task_runner_->BelongsToCurrentThread());
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
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))));
+ MayAutoLock locker(&router_->lock_);
+ sync_watcher_ =
+ std::make_unique<SequenceLocalSyncEventWatcher>(base::BindRepeating(
+ &InterfaceEndpoint::OnSyncEventSignaled, base::Unretained(this)));
+ if (sync_message_event_signaled_)
+ sync_watcher_->SignalEvent();
}
// ---------------------------------------------------------------------------
- // The following members are safe to access from any threads.
+ // The following members are safe to access from any sequence.
MultiplexRouter* const router_;
const InterfaceId id_;
@@ -225,30 +211,22 @@ class MultiplexRouter::InterfaceEndpoint
base::Optional<DisconnectReason> disconnect_reason_;
// The task runner on which |client_|'s methods can be called.
- scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ scoped_refptr<base::SequencedTaskRunner> 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<base::WaitableEvent> sync_message_event_;
+ // Indicates whether the sync watcher should be signaled for this endpoint.
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<SyncEventWatcher> sync_watcher_;
+ // Guarded by the router's lock. Used to synchronously wait on replies.
+ std::unique_ptr<SequenceLocalSyncEventWatcher> 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.
+// destruction, if the message it wrappers contains interface IDs, the wrapper
+// closes the corresponding endpoints.
class MultiplexRouter::MessageWrapper {
public:
MessageWrapper() = default;
@@ -260,14 +238,14 @@ class MultiplexRouter::MessageWrapper {
: router_(other.router_), value_(std::move(other.value_)) {}
~MessageWrapper() {
- if (value_.associated_endpoint_handles()->empty())
+ if (!router_ || value_.IsNull())
return;
router_->AssertLockAcquired();
- {
- MayAutoUnlock unlocker(&router_->lock_);
- value_.mutable_associated_endpoint_handles()->clear();
- }
+ // Don't try to close the endpoints if at this point the router is already
+ // half-destructed.
+ if (!router_->being_destructed_)
+ router_->CloseEndpointsForMessage(value_);
}
MessageWrapper& operator=(MessageWrapper&& other) {
@@ -276,7 +254,21 @@ class MultiplexRouter::MessageWrapper {
return *this;
}
- Message& value() { return value_; }
+ const Message& value() const { return value_; }
+
+ // Must be called outside of the router's lock.
+ // Returns a null message if it fails to deseralize the associated endpoint
+ // handles.
+ Message DeserializeEndpointHandlesAndTake() {
+ if (!value_.DeserializeAssociatedEndpointHandles(router_)) {
+ // The previous call may have deserialized part of the associated
+ // interface endpoint handles. They must be destroyed outside of the
+ // router's lock, so we cannot wait until destruction of MessageWrapper.
+ value_.Reset();
+ return Message();
+ }
+ return std::move(value_);
+ }
private:
MultiplexRouter* router_ = nullptr;
@@ -322,23 +314,17 @@ MultiplexRouter::MultiplexRouter(
ScopedMessagePipeHandle message_pipe,
Config config,
bool set_interface_id_namesapce_bit,
- scoped_refptr<base::SingleThreadTaskRunner> runner)
+ scoped_refptr<base::SequencedTaskRunner> 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());
+ control_message_proxy_(&connector_) {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (config == MULTI_INTERFACE)
lock_.emplace();
@@ -348,16 +334,15 @@ MultiplexRouter::MultiplexRouter(
// 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.
+ // on a different sequence.
connector_.AllowWokenUpBySyncWatchOnSameThread();
}
connector_.set_incoming_receiver(&filters_);
- connector_.set_connection_error_handler(
- base::Bind(&MultiplexRouter::OnPipeConnectionError,
- base::Unretained(this)));
+ connector_.set_connection_error_handler(base::Bind(
+ &MultiplexRouter::OnPipeConnectionError, base::Unretained(this)));
std::unique_ptr<MessageHeaderValidator> header_validator =
- base::MakeUnique<MessageHeaderValidator>();
+ std::make_unique<MessageHeaderValidator>();
header_validator_ = header_validator.get();
filters_.Append(std::move(header_validator));
}
@@ -365,33 +350,22 @@ MultiplexRouter::MultiplexRouter(
MultiplexRouter::~MultiplexRouter() {
MayAutoLock locker(&lock_);
+ being_destructed_ = true;
+
sync_message_tasks_.clear();
tasks_.clear();
+ endpoints_.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::AddIncomingMessageFilter(
+ std::unique_ptr<MessageReceiver> filter) {
+ filters_.Append(std::move(filter));
}
void MultiplexRouter::SetMasterInterfaceName(const char* name) {
- DCHECK(thread_checker_.CalledOnValidThread());
- header_validator_->SetDescription(
- std::string(name) + " [master] MessageHeaderValidator");
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ header_validator_->SetDescription(std::string(name) +
+ " [master] MessageHeaderValidator");
control_message_handler_.SetDescription(
std::string(name) + " [master] PipeControlMessageHandler");
connector_.SetWatcherHeapProfilerTag(name);
@@ -445,17 +419,10 @@ ScopedInterfaceEndpointHandle MultiplexRouter::CreateLocalEndpointHandle(
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())
+ if (endpoint->handle_created() || endpoint->closed())
return ScopedInterfaceEndpointHandle();
}
@@ -487,7 +454,7 @@ void MultiplexRouter::CloseEndpointHandle(
InterfaceEndpointController* MultiplexRouter::AttachEndpointClient(
const ScopedInterfaceEndpointHandle& handle,
InterfaceEndpointClient* client,
- scoped_refptr<base::SingleThreadTaskRunner> runner) {
+ scoped_refptr<base::SequencedTaskRunner> runner) {
const InterfaceId id = handle.id();
DCHECK(IsValidInterfaceId(id));
@@ -520,7 +487,7 @@ void MultiplexRouter::DetachEndpointClient(
}
void MultiplexRouter::RaiseError() {
- if (task_runner_->BelongsToCurrentThread()) {
+ if (task_runner_->RunsTasksInCurrentSequence()) {
connector_.RaiseError();
} else {
task_runner_->PostTask(FROM_HERE,
@@ -528,8 +495,13 @@ void MultiplexRouter::RaiseError() {
}
}
+bool MultiplexRouter::PrefersSerializedMessages() {
+ MayAutoLock locker(&lock_);
+ return connector_.PrefersSerializedMessages();
+}
+
void MultiplexRouter::CloseMessagePipe() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
connector_.CloseMessagePipe();
// CloseMessagePipe() above won't trigger connection error handler.
// Explicitly call OnPipeConnectionError() so that associated endpoints will
@@ -538,7 +510,7 @@ void MultiplexRouter::CloseMessagePipe() {
}
void MultiplexRouter::PauseIncomingMethodCallProcessing() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
connector_.PauseIncomingMethodCallProcessing();
MayAutoLock locker(&lock_);
@@ -549,7 +521,7 @@ void MultiplexRouter::PauseIncomingMethodCallProcessing() {
}
void MultiplexRouter::ResumeIncomingMethodCallProcessing() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
connector_.ResumeIncomingMethodCallProcessing();
MayAutoLock locker(&lock_);
@@ -568,7 +540,7 @@ void MultiplexRouter::ResumeIncomingMethodCallProcessing() {
}
bool MultiplexRouter::HasAssociatedEndpoints() const {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
MayAutoLock locker(&lock_);
if (endpoints_.size() > 1)
@@ -580,7 +552,7 @@ bool MultiplexRouter::HasAssociatedEndpoints() const {
}
void MultiplexRouter::EnableTestingMode() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
MayAutoLock locker(&lock_);
testing_mode_ = true;
@@ -588,9 +560,19 @@ void MultiplexRouter::EnableTestingMode() {
}
bool MultiplexRouter::Accept(Message* message) {
- DCHECK(thread_checker_.CalledOnValidThread());
-
- if (!message->DeserializeAssociatedEndpointHandles(this))
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Insert endpoints for the payload interface IDs as soon as the message
+ // arrives, instead of waiting till the message is dispatched. Consider the
+ // following sequence:
+ // 1) Async message msg1 arrives, containing interface ID x. Msg1 is not
+ // dispatched because a sync call is blocking the thread.
+ // 2) Sync message msg2 arrives targeting interface ID x.
+ //
+ // If we don't insert endpoint for interface ID x, when trying to dispatch
+ // msg2 we don't know whether it is an unexpected message or it is just
+ // because the message containing x hasn't been dispatched.
+ if (!InsertEndpointsForMessage(*message))
return false;
scoped_refptr<MultiplexRouter> protector(this);
@@ -603,15 +585,15 @@ bool MultiplexRouter::Accept(Message* message) {
? ALLOW_DIRECT_CLIENT_CALLS_FOR_SYNC_MESSAGES
: ALLOW_DIRECT_CLIENT_CALLS;
- bool processed =
- tasks_.empty() && ProcessIncomingMessage(message, client_call_behavior,
- connector_.task_runner());
+ MessageWrapper message_wrapper(this, std::move(*message));
+ bool processed = tasks_.empty() && ProcessIncomingMessage(
+ &message_wrapper, 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))));
+ tasks_.push_back(Task::CreateMessageTask(std::move(message_wrapper)));
Task* task = tasks_.back().get();
if (task->message_wrapper.value().has_flag(Message::kFlagIsSync)) {
@@ -636,8 +618,6 @@ bool MultiplexRouter::Accept(Message* message) {
bool MultiplexRouter::OnPeerAssociatedEndpointClosed(
InterfaceId id,
const base::Optional<DisconnectReason>& reason) {
- DCHECK(!IsMasterInterfaceId(id) || reason);
-
MayAutoLock locker(&lock_);
InterfaceEndpoint* endpoint = FindOrInsertEndpoint(id, nullptr);
@@ -662,23 +642,26 @@ bool MultiplexRouter::OnPeerAssociatedEndpointClosed(
}
void MultiplexRouter::OnPipeConnectionError() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
scoped_refptr<MultiplexRouter> 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;
+ // Calling UpdateEndpointStateMayRemove() may remove the corresponding value
+ // from |endpoints_| and invalidate any iterator of |endpoints_|. Therefore,
+ // copy the endpoint pointers to a vector and iterate over it instead.
+ std::vector<scoped_refptr<InterfaceEndpoint>> endpoint_vector;
+ endpoint_vector.reserve(endpoints_.size());
+ for (const auto& pair : endpoints_)
+ endpoint_vector.push_back(pair.second);
+ for (const auto& endpoint : endpoint_vector) {
if (endpoint->client())
- tasks_.push_back(Task::CreateNotifyErrorTask(endpoint));
+ tasks_.push_back(Task::CreateNotifyErrorTask(endpoint.get()));
- UpdateEndpointStateMayRemove(endpoint, PEER_ENDPOINT_CLOSED);
+ UpdateEndpointStateMayRemove(endpoint.get(), PEER_ENDPOINT_CLOSED);
}
ProcessTasks(connector_.during_sync_handle_watcher_callback()
@@ -689,7 +672,7 @@ void MultiplexRouter::OnPipeConnectionError() {
void MultiplexRouter::ProcessTasks(
ClientCallBehavior client_call_behavior,
- base::SingleThreadTaskRunner* current_task_runner) {
+ base::SequencedTaskRunner* current_task_runner) {
AssertLockAcquired();
if (posted_to_process_tasks_)
@@ -714,7 +697,7 @@ void MultiplexRouter::ProcessTasks(
task->IsNotifyErrorTask()
? ProcessNotifyErrorTask(task.get(), client_call_behavior,
current_task_runner)
- : ProcessIncomingMessage(&task->message_wrapper.value(),
+ : ProcessIncomingMessage(&task->message_wrapper,
client_call_behavior, current_task_runner);
if (!processed) {
@@ -752,8 +735,7 @@ bool MultiplexRouter::ProcessFirstSyncMessageForEndpoint(InterfaceId id) {
// Note: after this call, |task| and |iter| may be invalidated.
bool processed = ProcessIncomingMessage(
- &message_wrapper.value(), ALLOW_DIRECT_CLIENT_CALLS_FOR_SYNC_MESSAGES,
- nullptr);
+ &message_wrapper, ALLOW_DIRECT_CLIENT_CALLS_FOR_SYNC_MESSAGES, nullptr);
DCHECK(processed);
iter = sync_message_tasks_.find(id);
@@ -771,8 +753,9 @@ bool MultiplexRouter::ProcessFirstSyncMessageForEndpoint(InterfaceId id) {
bool MultiplexRouter::ProcessNotifyErrorTask(
Task* task,
ClientCallBehavior client_call_behavior,
- base::SingleThreadTaskRunner* current_task_runner) {
- DCHECK(!current_task_runner || current_task_runner->BelongsToCurrentThread());
+ base::SequencedTaskRunner* current_task_runner) {
+ DCHECK(!current_task_runner ||
+ current_task_runner->RunsTasksInCurrentSequence());
DCHECK(!paused_);
AssertLockAcquired();
@@ -786,7 +769,7 @@ bool MultiplexRouter::ProcessNotifyErrorTask(
return false;
}
- DCHECK(endpoint->task_runner()->BelongsToCurrentThread());
+ DCHECK(endpoint->task_runner()->RunsTasksInCurrentSequence());
InterfaceEndpointClient* client = endpoint->client();
base::Optional<DisconnectReason> disconnect_reason(
@@ -797,7 +780,7 @@ bool MultiplexRouter::ProcessNotifyErrorTask(
// 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().
+ // always accessed on the same sequence, including DetachEndpointClient().
MayAutoUnlock unlocker(&lock_);
client->NotifyError(disconnect_reason);
}
@@ -805,14 +788,16 @@ bool MultiplexRouter::ProcessNotifyErrorTask(
}
bool MultiplexRouter::ProcessIncomingMessage(
- Message* message,
+ MessageWrapper* message_wrapper,
ClientCallBehavior client_call_behavior,
- base::SingleThreadTaskRunner* current_task_runner) {
- DCHECK(!current_task_runner || current_task_runner->BelongsToCurrentThread());
+ base::SequencedTaskRunner* current_task_runner) {
+ DCHECK(!current_task_runner ||
+ current_task_runner->RunsTasksInCurrentSequence());
DCHECK(!paused_);
- DCHECK(message);
+ DCHECK(message_wrapper);
AssertLockAcquired();
+ const Message* message = &message_wrapper->value();
if (message->IsNull()) {
// This is a sync message and has been processed during sync handle
// watching.
@@ -824,7 +809,10 @@ bool MultiplexRouter::ProcessIncomingMessage(
{
MayAutoUnlock unlocker(&lock_);
- result = control_message_handler_.Accept(message);
+ Message tmp_message =
+ message_wrapper->DeserializeEndpointHandlesAndTake();
+ result = !tmp_message.IsNull() &&
+ control_message_handler_.Accept(&tmp_message);
}
if (!result)
@@ -849,7 +837,7 @@ bool MultiplexRouter::ProcessIncomingMessage(
bool can_direct_call;
if (message->has_flag(Message::kFlagIsSync)) {
can_direct_call = client_call_behavior != NO_DIRECT_CLIENT_CALLS &&
- endpoint->task_runner()->BelongsToCurrentThread();
+ endpoint->task_runner()->RunsTasksInCurrentSequence();
} else {
can_direct_call = client_call_behavior == ALLOW_DIRECT_CLIENT_CALLS &&
endpoint->task_runner() == current_task_runner;
@@ -860,7 +848,7 @@ bool MultiplexRouter::ProcessIncomingMessage(
return false;
}
- DCHECK(endpoint->task_runner()->BelongsToCurrentThread());
+ DCHECK(endpoint->task_runner()->RunsTasksInCurrentSequence());
InterfaceEndpointClient* client = endpoint->client();
bool result = false;
@@ -870,9 +858,11 @@ bool MultiplexRouter::ProcessIncomingMessage(
// deadlock.
//
// It is safe to call into |client| without the lock. Because |client| is
- // always accessed on the same thread, including DetachEndpointClient().
+ // always accessed on the same sequence, including DetachEndpointClient().
MayAutoUnlock unlocker(&lock_);
- result = client->HandleIncomingMessage(message);
+ Message tmp_message = message_wrapper->DeserializeEndpointHandlesAndTake();
+ result =
+ !tmp_message.IsNull() && client->HandleIncomingMessage(&tmp_message);
}
if (!result)
RaiseErrorInNonTestingMode();
@@ -881,7 +871,7 @@ bool MultiplexRouter::ProcessIncomingMessage(
}
void MultiplexRouter::MaybePostToProcessTasks(
- base::SingleThreadTaskRunner* task_runner) {
+ base::SequencedTaskRunner* task_runner) {
AssertLockAcquired();
if (posted_to_process_tasks_)
return;
@@ -897,7 +887,7 @@ void MultiplexRouter::LockAndCallProcessTasks() {
// always called using base::Bind(), which holds a ref.
MayAutoLock locker(&lock_);
posted_to_process_tasks_ = false;
- scoped_refptr<base::SingleThreadTaskRunner> runner(
+ scoped_refptr<base::SequencedTaskRunner> runner(
std::move(posted_to_task_runner_));
ProcessTasks(ALLOW_DIRECT_CLIENT_CALLS, runner.get());
}
@@ -956,5 +946,67 @@ void MultiplexRouter::AssertLockAcquired() {
#endif
}
+bool MultiplexRouter::InsertEndpointsForMessage(const Message& message) {
+ if (!message.is_serialized())
+ return true;
+
+ uint32_t num_ids = message.payload_num_interface_ids();
+ if (num_ids == 0)
+ return true;
+
+ const uint32_t* ids = message.payload_interface_ids();
+
+ MayAutoLock locker(&lock_);
+ for (uint32_t i = 0; i < num_ids; ++i) {
+ // Message header validation already ensures that the IDs are valid and not
+ // the master ID.
+ // The IDs are from the remote side and therefore their namespace bit is
+ // supposed to be different than the value that this router would use.
+ if (set_interface_id_namespace_bit_ ==
+ HasInterfaceIdNamespaceBitSet(ids[i])) {
+ return false;
+ }
+
+ // It is possible that the endpoint already exists even when the remote side
+ // is well-behaved: it might have notified us that the peer endpoint has
+ // closed.
+ bool inserted = false;
+ InterfaceEndpoint* endpoint = FindOrInsertEndpoint(ids[i], &inserted);
+ if (endpoint->closed() || endpoint->handle_created())
+ return false;
+ }
+
+ return true;
+}
+
+void MultiplexRouter::CloseEndpointsForMessage(const Message& message) {
+ AssertLockAcquired();
+
+ if (!message.is_serialized())
+ return;
+
+ uint32_t num_ids = message.payload_num_interface_ids();
+ if (num_ids == 0)
+ return;
+
+ const uint32_t* ids = message.payload_interface_ids();
+ for (uint32_t i = 0; i < num_ids; ++i) {
+ InterfaceEndpoint* endpoint = FindEndpoint(ids[i]);
+ // If the remote side maliciously sends the same interface ID in another
+ // message which has been dispatched, we could get here with no endpoint
+ // for the ID, a closed endpoint, or an endpoint with handle created.
+ if (!endpoint || endpoint->closed() || endpoint->handle_created()) {
+ RaiseErrorInNonTestingMode();
+ continue;
+ }
+
+ UpdateEndpointStateMayRemove(endpoint, ENDPOINT_CLOSED);
+ MayAutoUnlock unlocker(&lock_);
+ control_message_proxy_.NotifyPeerEndpointClosed(ids[i], base::nullopt);
+ }
+
+ ProcessTasks(NO_DIRECT_CLIENT_CALLS, nullptr);
+}
+
} // namespace internal
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/multiplex_router.h b/mojo/public/cpp/bindings/lib/multiplex_router.h
index cac138bcb7..8c2e7c8b0f 100644
--- a/mojo/public/cpp/bindings/lib/multiplex_router.h
+++ b/mojo/public/cpp/bindings/lib/multiplex_router.h
@@ -7,20 +7,21 @@
#include <stdint.h>
-#include <deque>
#include <map>
#include <memory>
#include <string>
#include "base/compiler_specific.h"
+#include "base/containers/queue.h"
+#include "base/containers/small_map.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/sequence_checker.h"
+#include "base/sequenced_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"
@@ -33,7 +34,7 @@
#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
namespace base {
-class SingleThreadTaskRunner;
+class SequencedTaskRunner;
}
namespace mojo {
@@ -43,19 +44,19 @@ 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
+// It is created on the sequence 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
+// same sequence.
+// Some public methods are only allowed to be called on the creating sequence;
+// while the others are safe to call from any sequence. Please see the method
// comments for more details.
//
// NOTE: CloseMessagePipe() or PassMessagePipe() MUST be called on |runner|'s
-// thread before this object is destroyed.
+// sequence before this object is destroyed.
class MOJO_CPP_BINDINGS_EXPORT MultiplexRouter
- : NON_EXPORTED_BASE(public MessageReceiver),
+ : public MessageReceiver,
public AssociatedGroupController,
- NON_EXPORTED_BASE(public PipeControlMessageHandlerDelegate) {
+ public PipeControlMessageHandlerDelegate {
public:
enum Config {
// There is only the master interface running on this router. Please note
@@ -76,7 +77,11 @@ class MOJO_CPP_BINDINGS_EXPORT MultiplexRouter
MultiplexRouter(ScopedMessagePipeHandle message_pipe,
Config config,
bool set_interface_id_namespace_bit,
- scoped_refptr<base::SingleThreadTaskRunner> runner);
+ scoped_refptr<base::SequencedTaskRunner> runner);
+
+ // Adds a MessageReceiver which can filter a message after validation but
+ // before dispatch.
+ void AddIncomingMessageFilter(std::unique_ptr<MessageReceiver> filter);
// Sets the master interface name for this router. Only used when reporting
// message header or control message validation errors.
@@ -84,7 +89,7 @@ class MOJO_CPP_BINDINGS_EXPORT MultiplexRouter
void SetMasterInterfaceName(const char* name);
// ---------------------------------------------------------------------------
- // The following public methods are safe to call from any threads.
+ // The following public methods are safe to call from any sequence.
// AssociatedGroupController implementation:
InterfaceId AssociateInterface(
@@ -97,13 +102,14 @@ class MOJO_CPP_BINDINGS_EXPORT MultiplexRouter
InterfaceEndpointController* AttachEndpointClient(
const ScopedInterfaceEndpointHandle& handle,
InterfaceEndpointClient* endpoint_client,
- scoped_refptr<base::SingleThreadTaskRunner> runner) override;
+ scoped_refptr<base::SequencedTaskRunner> runner) override;
void DetachEndpointClient(
const ScopedInterfaceEndpointHandle& handle) override;
void RaiseError() override;
+ bool PrefersSerializedMessages() override;
// ---------------------------------------------------------------------------
- // The following public methods are called on the creating thread.
+ // The following public methods are called on the creating sequence.
// 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
@@ -112,14 +118,15 @@ class MOJO_CPP_BINDINGS_EXPORT MultiplexRouter
// Extracts the underlying message pipe.
ScopedMessagePipeHandle PassMessagePipe() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!HasAssociatedEndpoints());
return connector_.PassMessagePipe();
}
- // Blocks the current thread until the first incoming message, or |deadline|.
+ // Blocks the current sequence until the first incoming message, or
+ // |deadline|.
bool WaitForIncomingMessage(MojoDeadline deadline) {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return connector_.WaitForIncomingMessage(deadline);
}
@@ -137,13 +144,13 @@ class MOJO_CPP_BINDINGS_EXPORT MultiplexRouter
// Is the router bound to a message pipe handle?
bool is_valid() const {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return connector_.is_valid();
}
// TODO(yzshen): consider removing this getter.
MessagePipeHandle handle() const {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return connector_.handle();
}
@@ -169,7 +176,7 @@ class MOJO_CPP_BINDINGS_EXPORT MultiplexRouter
void OnPipeConnectionError();
// Specifies whether we are allowed to directly call into
- // InterfaceEndpointClient (given that we are already on the same thread as
+ // InterfaceEndpointClient (given that we are already on the same sequence as
// the client).
enum ClientCallBehavior {
// Don't call any InterfaceEndpointClient methods directly.
@@ -191,7 +198,7 @@ class MOJO_CPP_BINDINGS_EXPORT MultiplexRouter
// 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);
+ base::SequencedTaskRunner* 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
@@ -202,16 +209,14 @@ class MOJO_CPP_BINDINGS_EXPORT MultiplexRouter
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);
+ bool ProcessNotifyErrorTask(Task* task,
+ ClientCallBehavior client_call_behavior,
+ base::SequencedTaskRunner* current_task_runner);
+ bool ProcessIncomingMessage(MessageWrapper* message_wrapper,
+ ClientCallBehavior client_call_behavior,
+ base::SequencedTaskRunner* current_task_runner);
+
+ void MaybePostToProcessTasks(base::SequencedTaskRunner* task_runner);
void LockAndCallProcessTasks();
// Updates the state of |endpoint|. If both the endpoint and its peer have
@@ -226,21 +231,25 @@ class MOJO_CPP_BINDINGS_EXPORT MultiplexRouter
InterfaceEndpoint* FindOrInsertEndpoint(InterfaceId id, bool* inserted);
InterfaceEndpoint* FindEndpoint(InterfaceId id);
+ // Returns false if some interface IDs are invalid or have been used.
+ bool InsertEndpointsForMessage(const Message& message);
+ void CloseEndpointsForMessage(const Message& message);
+
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<base::SingleThreadTaskRunner> task_runner_;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
// Owned by |filters_| below.
- MessageHeaderValidator* header_validator_;
+ MessageHeaderValidator* header_validator_ = nullptr;
FilterChain filters_;
Connector connector_;
- base::ThreadChecker thread_checker_;
+ SEQUENCE_CHECKER(sequence_checker_);
// Protects the following members.
// Not set in Config::SINGLE_INTERFACE* mode.
@@ -250,21 +259,24 @@ class MOJO_CPP_BINDINGS_EXPORT MultiplexRouter
// NOTE: It is unsafe to call into this object while holding |lock_|.
PipeControlMessageProxy control_message_proxy_;
- std::map<InterfaceId, scoped_refptr<InterfaceEndpoint>> endpoints_;
- uint32_t next_interface_id_value_;
+ base::small_map<std::map<InterfaceId, scoped_refptr<InterfaceEndpoint>>, 1>
+ endpoints_;
+ uint32_t next_interface_id_value_ = 1;
- std::deque<std::unique_ptr<Task>> tasks_;
+ base::circular_deque<std::unique_ptr<Task>> tasks_;
// It refers to tasks in |tasks_| and doesn't own any of them.
- std::map<InterfaceId, std::deque<Task*>> sync_message_tasks_;
+ std::map<InterfaceId, base::circular_deque<Task*>> sync_message_tasks_;
+
+ bool posted_to_process_tasks_ = false;
+ scoped_refptr<base::SequencedTaskRunner> posted_to_task_runner_;
- bool posted_to_process_tasks_;
- scoped_refptr<base::SingleThreadTaskRunner> posted_to_task_runner_;
+ bool encountered_error_ = false;
- bool encountered_error_;
+ bool paused_ = false;
- bool paused_;
+ bool testing_mode_ = false;
- bool testing_mode_;
+ bool being_destructed_ = false;
DISALLOW_COPY_AND_ASSIGN(MultiplexRouter);
};
diff --git a/mojo/public/cpp/bindings/lib/native_struct.cc b/mojo/public/cpp/bindings/lib/native_struct.cc
deleted file mode 100644
index 7b1a1a6c59..0000000000
--- a/mojo/public/cpp/bindings/lib/native_struct.cc
+++ /dev/null
@@ -1,34 +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 "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
deleted file mode 100644
index 0e5d245692..0000000000
--- a/mojo/public/cpp/bindings/lib/native_struct_data.cc
+++ /dev/null
@@ -1,22 +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 "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<uint8_t>::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
deleted file mode 100644
index 1c7cd81c77..0000000000
--- a/mojo/public/cpp/bindings/lib/native_struct_data.h
+++ /dev/null
@@ -1,38 +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 MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_DATA_H_
-#define MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_DATA_H_
-
-#include <vector>
-
-#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<uint8_t> data;
-
- private:
- NativeStruct_Data() = delete;
- ~NativeStruct_Data() = delete;
-};
-
-static_assert(sizeof(Array_Data<uint8_t>) == sizeof(NativeStruct_Data),
- "Mismatched NativeStruct_Data and Array_Data<uint8_t> 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
index fa0dbf3803..283080089f 100644
--- a/mojo/public/cpp/bindings/lib/native_struct_serialization.cc
+++ b/mojo/public/cpp/bindings/lib/native_struct_serialization.cc
@@ -4,56 +4,120 @@
#include "mojo/public/cpp/bindings/lib/native_struct_serialization.h"
+#include "ipc/ipc_message_attachment.h"
+#include "ipc/ipc_message_attachment_set.h"
+#include "ipc/native_handle_type_converters.h"
#include "mojo/public/cpp/bindings/lib/serialization.h"
+#include "mojo/public/cpp/bindings/lib/serialization_forward.h"
namespace mojo {
namespace internal {
// static
-size_t UnmappedNativeStructSerializerImpl::PrepareToSerialize(
- const NativeStructPtr& input,
+void UnmappedNativeStructSerializerImpl::Serialize(
+ const native::NativeStructPtr& input,
+ Buffer* buffer,
+ native::internal::NativeStruct_Data::BufferWriter* writer,
SerializationContext* context) {
if (!input)
- return 0;
- return internal::PrepareToSerialize<ArrayDataView<uint8_t>>(input->data,
- context);
+ return;
+
+ writer->Allocate(buffer);
+
+ Array_Data<uint8_t>::BufferWriter data_writer;
+ const mojo::internal::ContainerValidateParams data_validate_params(0, false,
+ nullptr);
+ mojo::internal::Serialize<ArrayDataView<uint8_t>>(
+ input->data, buffer, &data_writer, &data_validate_params, context);
+ writer->data()->data.Set(data_writer.data());
+
+ mojo::internal::Array_Data<mojo::internal::Pointer<
+ native::internal::SerializedHandle_Data>>::BufferWriter handles_writer;
+ const mojo::internal::ContainerValidateParams handles_validate_params(
+ 0, false, nullptr);
+ mojo::internal::Serialize<
+ mojo::ArrayDataView<::mojo::native::SerializedHandleDataView>>(
+ input->handles, buffer, &handles_writer, &handles_validate_params,
+ context);
+ writer->data()->handles.Set(handles_writer.is_null() ? nullptr
+ : handles_writer.data());
}
// static
-void UnmappedNativeStructSerializerImpl::Serialize(
- const NativeStructPtr& input,
- Buffer* buffer,
- NativeStruct_Data** output,
+bool UnmappedNativeStructSerializerImpl::Deserialize(
+ native::internal::NativeStruct_Data* input,
+ native::NativeStructPtr* output,
SerializationContext* context) {
if (!input) {
- *output = nullptr;
- return;
+ output->reset();
+ return true;
}
- Array_Data<uint8_t>* data = nullptr;
- const ContainerValidateParams params(0, false, nullptr);
- internal::Serialize<ArrayDataView<uint8_t>>(input->data, buffer, &data,
- &params, context);
- *output = reinterpret_cast<NativeStruct_Data*>(data);
+ native::NativeStructDataView data_view(input, context);
+ return StructTraits<::mojo::native::NativeStructDataView,
+ native::NativeStructPtr>::Read(data_view, output);
}
// static
-bool UnmappedNativeStructSerializerImpl::Deserialize(
- NativeStruct_Data* input,
- NativeStructPtr* output,
+void UnmappedNativeStructSerializerImpl::SerializeMessageContents(
+ IPC::Message* message,
+ Buffer* buffer,
+ native::internal::NativeStruct_Data::BufferWriter* writer,
SerializationContext* context) {
- Array_Data<uint8_t>* data = reinterpret_cast<Array_Data<uint8_t>*>(input);
+ writer->Allocate(buffer);
+
+ // Allocate a uint8 array, initialize its header, and copy the Pickle in.
+ Array_Data<uint8_t>::BufferWriter data_writer;
+ data_writer.Allocate(message->payload_size(), buffer);
+ memcpy(data_writer->storage(), message->payload(), message->payload_size());
+ writer->data()->data.Set(data_writer.data());
+
+ if (message->attachment_set()->empty()) {
+ writer->data()->handles.Set(nullptr);
+ return;
+ }
+
+ mojo::internal::Array_Data<mojo::internal::Pointer<
+ native::internal::SerializedHandle_Data>>::BufferWriter handles_writer;
+ auto* attachments = message->attachment_set();
+ handles_writer.Allocate(attachments->size(), buffer);
+ for (unsigned i = 0; i < attachments->size(); ++i) {
+ native::internal::SerializedHandle_Data::BufferWriter handle_writer;
+ handle_writer.Allocate(buffer);
+
+ auto attachment = attachments->GetAttachmentAt(i);
+ ScopedHandle handle = attachment->TakeMojoHandle();
+ internal::Serializer<ScopedHandle, ScopedHandle>::Serialize(
+ handle, &handle_writer->the_handle, context);
+ handle_writer->type = static_cast<int32_t>(
+ mojo::ConvertTo<native::SerializedHandle::Type>(attachment->GetType()));
+ handles_writer.data()->at(i).Set(handle_writer.data());
+ }
+ writer->data()->handles.Set(handles_writer.data());
+}
+
+// static
+bool UnmappedNativeStructSerializerImpl::DeserializeMessageAttachments(
+ native::internal::NativeStruct_Data* data,
+ SerializationContext* context,
+ IPC::Message* message) {
+ if (data->handles.is_null())
+ return true;
- NativeStructPtr result(NativeStruct::New());
- if (!internal::Deserialize<ArrayDataView<uint8_t>>(data, &result->data,
- context)) {
- output = nullptr;
- return false;
+ auto* handles_data = data->handles.Get();
+ for (size_t i = 0; i < handles_data->size(); ++i) {
+ auto* handle_data = handles_data->at(i).Get();
+ if (!handle_data)
+ return false;
+ ScopedHandle handle;
+ internal::Serializer<ScopedHandle, ScopedHandle>::Deserialize(
+ &handle_data->the_handle, &handle, context);
+ auto attachment = IPC::MessageAttachment::CreateFromMojoHandle(
+ std::move(handle),
+ mojo::ConvertTo<IPC::MessageAttachment::Type>(
+ static_cast<native::SerializedHandle::Type>(handle_data->type)));
+ message->attachment_set()->AddAttachment(std::move(attachment));
}
- if (!result->data)
- *output = nullptr;
- else
- result.Swap(output);
return true;
}
diff --git a/mojo/public/cpp/bindings/lib/native_struct_serialization.h b/mojo/public/cpp/bindings/lib/native_struct_serialization.h
index 457435b955..6aa4c3a4a8 100644
--- a/mojo/public/cpp/bindings/lib/native_struct_serialization.h
+++ b/mojo/public/cpp/bindings/lib/native_struct_serialization.h
@@ -12,59 +12,63 @@
#include "base/logging.h"
#include "base/pickle.h"
+#include "ipc/ipc_message.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"
+#include "mojo/public/interfaces/bindings/native_struct.mojom.h"
namespace mojo {
namespace internal {
+// Base class for the templated native struct serialization interface below,
+// used to consolidated some shared logic and provide a basic
+// Serialize/Deserialize for [Native] mojom structs which do not have a
+// registered typemap in the current configuration (i.e. structs that are
+// represented by a raw native::NativeStruct mojom struct in C++ bindings.)
+struct MOJO_CPP_BINDINGS_EXPORT UnmappedNativeStructSerializerImpl {
+ static void Serialize(
+ const native::NativeStructPtr& input,
+ Buffer* buffer,
+ native::internal::NativeStruct_Data::BufferWriter* writer,
+ SerializationContext* context);
+
+ static bool Deserialize(native::internal::NativeStruct_Data* input,
+ native::NativeStructPtr* output,
+ SerializationContext* context);
+
+ static void SerializeMessageContents(
+ IPC::Message* message,
+ Buffer* buffer,
+ native::internal::NativeStruct_Data::BufferWriter* writer,
+ SerializationContext* context);
+
+ static bool DeserializeMessageAttachments(
+ native::internal::NativeStruct_Data* data,
+ SerializationContext* context,
+ IPC::Message* message);
+};
+
template <typename MaybeConstUserType>
struct NativeStructSerializerImpl {
using UserType = typename std::remove_const<MaybeConstUserType>::type;
using Traits = IPC::ParamTraits<UserType>;
- 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<uint32_t>::max());
-
- // Allocate a uint8 array, initialize its header, and copy the Pickle in.
- ArrayHeader* header =
- reinterpret_cast<ArrayHeader*>(buffer->Allocate(total_size));
- header->num_bytes = static_cast<uint32_t>(total_size);
- header->num_elements = static_cast<uint32_t>(pickle.payload_size());
- memcpy(reinterpret_cast<char*>(header) + sizeof(ArrayHeader),
- pickle.payload(), pickle.payload_size());
-
- *out = reinterpret_cast<NativeStruct_Data*>(header);
+ static void Serialize(
+ MaybeConstUserType& value,
+ Buffer* buffer,
+ native::internal::NativeStruct_Data::BufferWriter* writer,
+ SerializationContext* context) {
+ IPC::Message message;
+ Traits::Write(&message, value);
+ UnmappedNativeStructSerializerImpl::SerializeMessageContents(
+ &message, buffer, writer, context);
}
- static bool Deserialize(NativeStruct_Data* data,
+ static bool Deserialize(native::internal::NativeStruct_Data* data,
UserType* out,
SerializationContext* context) {
if (!data)
@@ -82,7 +86,7 @@ struct NativeStructSerializerImpl {
// 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<ArrayHeader*>(data);
+ ArrayHeader* header = reinterpret_cast<ArrayHeader*>(data->data.Get());
DCHECK_GE(header->num_bytes, sizeof(ArrayHeader));
header->num_bytes -= sizeof(ArrayHeader);
@@ -90,10 +94,15 @@ struct NativeStructSerializerImpl {
// 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<const char*>(header),
- header->num_bytes + sizeof(ArrayHeader));
- base::PickleIterator iter(pickle_view);
- if (!Traits::Read(&pickle_view, &iter, out))
+ IPC::Message message_view(reinterpret_cast<const char*>(header),
+ header->num_bytes + sizeof(ArrayHeader));
+ base::PickleIterator iter(message_view);
+ if (!UnmappedNativeStructSerializerImpl::DeserializeMessageAttachments(
+ data, context, &message_view)) {
+ return false;
+ }
+
+ if (!Traits::Read(&message_view, &iter, out))
return false;
}
@@ -104,28 +113,16 @@ struct NativeStructSerializerImpl {
}
};
-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<NativeStructPtr>
+struct NativeStructSerializerImpl<native::NativeStructPtr>
: public UnmappedNativeStructSerializerImpl {};
template <>
-struct NativeStructSerializerImpl<const NativeStructPtr>
+struct NativeStructSerializerImpl<const native::NativeStructPtr>
: public UnmappedNativeStructSerializerImpl {};
template <typename MaybeConstUserType>
-struct Serializer<NativeStructDataView, MaybeConstUserType>
+struct Serializer<native::NativeStructDataView, MaybeConstUserType>
: public NativeStructSerializerImpl<MaybeConstUserType> {};
} // namespace internal
diff --git a/mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc b/mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc
index d451c05a5f..d39b991e20 100644
--- a/mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc
+++ b/mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc
@@ -6,7 +6,6 @@
#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"
diff --git a/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc b/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc
index 1029c2c491..f218892db5 100644
--- a/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc
+++ b/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc
@@ -9,8 +9,8 @@
#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/message.h"
#include "mojo/public/interfaces/bindings/pipe_control_messages.mojom.h"
namespace mojo {
@@ -18,21 +18,16 @@ 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;
+ Message message(pipe_control::kRunOrClosePipeMessageId, 0, 0, 0, nullptr);
+ internal::SerializationContext context;
+ pipe_control::internal::RunOrClosePipeMessageParams_Data::BufferWriter params;
internal::Serialize<pipe_control::RunOrClosePipeMessageParamsDataView>(
- params_ptr, builder.buffer(), &params, &context);
- builder.message()->set_interface_id(kInvalidInterfaceId);
- return std::move(*builder.message());
+ params_ptr, message.payload_buffer(), &params, &context);
+ message.set_interface_id(kInvalidInterfaceId);
+ return message;
}
} // namespace
diff --git a/mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc b/mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc
index c1345079a5..2e5559ce0d 100644
--- a/mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc
+++ b/mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc
@@ -7,6 +7,7 @@
#include "base/bind.h"
#include "base/logging.h"
#include "base/synchronization/lock.h"
+#include "base/threading/sequenced_task_runner_handle.h"
#include "mojo/public/cpp/bindings/associated_group_controller.h"
#include "mojo/public/cpp/bindings/lib/may_auto_lock.h"
@@ -14,7 +15,7 @@ namespace mojo {
// ScopedInterfaceEndpointHandle::State ----------------------------------------
-// State could be called from multiple threads.
+// State could be called from multiple sequences.
class ScopedInterfaceEndpointHandle::State
: public base::RefCountedThreadSafe<State> {
public:
@@ -51,7 +52,7 @@ class ScopedInterfaceEndpointHandle::State
// 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
+ // potentially from another sequence. We would like it to continue
// returning the same group controller.
//
// Imagine there is a ThreadSafeForwarder A:
@@ -103,7 +104,7 @@ class ScopedInterfaceEndpointHandle::State
return;
}
- runner_ = base::ThreadTaskRunnerHandle::Get();
+ runner_ = base::SequencedTaskRunnerHandle::Get();
if (!pending_association_) {
runner_->PostTask(
FROM_HERE,
@@ -171,7 +172,7 @@ class ScopedInterfaceEndpointHandle::State
DCHECK(!IsValidInterfaceId(id_));
}
- // Called by the peer, maybe from a different thread.
+ // Called by the peer, maybe from a different sequence.
void OnAssociated(InterfaceId id,
scoped_refptr<AssociatedGroupController> group_controller) {
AssociationEventCallback handler;
@@ -179,7 +180,7 @@ class ScopedInterfaceEndpointHandle::State
internal::MayAutoLock locker(&lock_);
// There may be race between Close() of endpoint A and
- // NotifyPeerAssociation() of endpoint A_peer on different threads.
+ // NotifyPeerAssociation() of endpoint A_peer on different sequences.
// Therefore, it is possible that endpoint A has been closed but it
// still gets OnAssociated() call from its peer.
if (!pending_association_)
@@ -191,7 +192,7 @@ class ScopedInterfaceEndpointHandle::State
group_controller_ = std::move(group_controller);
if (!association_event_handler_.is_null()) {
- if (runner_->BelongsToCurrentThread()) {
+ if (runner_->RunsTasksInCurrentSequence()) {
handler = std::move(association_event_handler_);
runner_ = nullptr;
} else {
@@ -207,7 +208,7 @@ class ScopedInterfaceEndpointHandle::State
std::move(handler).Run(ASSOCIATED);
}
- // Called by the peer, maybe from a different thread.
+ // Called by the peer, maybe from a different sequence.
void OnPeerClosedBeforeAssociation(
const base::Optional<DisconnectReason>& reason) {
AssociationEventCallback handler;
@@ -215,7 +216,7 @@ class ScopedInterfaceEndpointHandle::State
internal::MayAutoLock locker(&lock_);
// There may be race between Close()/NotifyPeerAssociation() of endpoint
- // A and Close() of endpoint A_peer on different threads.
+ // A and Close() of endpoint A_peer on different sequences.
// Therefore, it is possible that endpoint A is not in pending association
// state but still gets OnPeerClosedBeforeAssociation() call from its
// peer.
@@ -227,7 +228,7 @@ class ScopedInterfaceEndpointHandle::State
peer_state_ = nullptr;
if (!association_event_handler_.is_null()) {
- if (runner_->BelongsToCurrentThread()) {
+ if (runner_->RunsTasksInCurrentSequence()) {
handler = std::move(association_event_handler_);
runner_ = nullptr;
} else {
@@ -245,7 +246,7 @@ class ScopedInterfaceEndpointHandle::State
}
void RunAssociationEventHandler(
- scoped_refptr<base::SingleThreadTaskRunner> posted_to_runner,
+ scoped_refptr<base::SequencedTaskRunner> posted_to_runner,
AssociationEvent event) {
AssociationEventCallback handler;
@@ -271,7 +272,7 @@ class ScopedInterfaceEndpointHandle::State
scoped_refptr<State> peer_state_;
AssociationEventCallback association_event_handler_;
- scoped_refptr<base::SingleThreadTaskRunner> runner_;
+ scoped_refptr<base::SequencedTaskRunner> runner_;
InterfaceId id_ = kInvalidInterfaceId;
scoped_refptr<AssociatedGroupController> group_controller_;
@@ -373,7 +374,7 @@ void ScopedInterfaceEndpointHandle::ResetInternal(
base::Callback<AssociatedGroupController*()>
ScopedInterfaceEndpointHandle::CreateGroupControllerGetter() const {
- // We allow this callback to be run on any thread. If this handle is created
+ // We allow this callback to be run on any sequence. 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_);
diff --git a/mojo/public/cpp/bindings/lib/sequence_local_sync_event_watcher.cc b/mojo/public/cpp/bindings/lib/sequence_local_sync_event_watcher.cc
new file mode 100644
index 0000000000..f4618ffbe8
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/sequence_local_sync_event_watcher.cc
@@ -0,0 +1,286 @@
+// 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.
+
+#include "mojo/public/cpp/bindings/sequence_local_sync_event_watcher.h"
+
+#include <map>
+#include <memory>
+#include <set>
+
+#include "base/bind.h"
+#include "base/containers/flat_set.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/no_destructor.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/sequence_local_storage_slot.h"
+#include "mojo/public/cpp/bindings/sync_event_watcher.h"
+
+namespace mojo {
+
+namespace {
+
+struct WatcherState;
+
+using WatcherStateMap =
+ std::map<const SequenceLocalSyncEventWatcher*, scoped_refptr<WatcherState>>;
+
+// Ref-counted watcher state which may outlive the watcher to which it pertains.
+// This is necessary to store outside of the SequenceLocalSyncEventWatcher
+// itself in order to support nested sync operations where an inner operation
+// may destroy the watcher.
+struct WatcherState : public base::RefCounted<WatcherState> {
+ WatcherState() = default;
+
+ bool watcher_was_destroyed = false;
+
+ private:
+ friend class base::RefCounted<WatcherState>;
+
+ ~WatcherState() = default;
+
+ DISALLOW_COPY_AND_ASSIGN(WatcherState);
+};
+
+} // namespace
+
+// Owns the WaitableEvent and SyncEventWatcher shared by all
+// SequenceLocalSyncEventWatchers on a single sequence, and coordinates the
+// multiplexing of those shared objects to support an arbitrary number of
+// SequenceLocalSyncEventWatchers waiting and signaling potentially while
+// nested.
+class SequenceLocalSyncEventWatcher::SequenceLocalState {
+ public:
+ SequenceLocalState()
+ : event_(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED),
+ event_watcher_(&event_,
+ base::BindRepeating(&SequenceLocalState::OnEventSignaled,
+ base::Unretained(this))),
+ weak_ptr_factory_(this) {
+ // We always allow this event handler to be awoken during any sync event on
+ // the sequence. Individual watchers still must opt into having such
+ // wake-ups propagated to them.
+ event_watcher_.AllowWokenUpBySyncWatchOnSameThread();
+ }
+
+ ~SequenceLocalState() {}
+
+ // Initializes a SequenceLocalState instance in sequence-local storage if
+ // not already initialized. Returns a WeakPtr to the stored state object.
+ static base::WeakPtr<SequenceLocalState> GetOrCreate() {
+ auto& state_ptr = GetStorageSlot().Get();
+ if (!state_ptr)
+ state_ptr = std::make_unique<SequenceLocalState>();
+ return state_ptr->weak_ptr_factory_.GetWeakPtr();
+ }
+
+ // Registers a new watcher and returns an iterator into the WatcherStateMap to
+ // be used for fast access with other methods.
+ WatcherStateMap::iterator RegisterWatcher(
+ const SequenceLocalSyncEventWatcher* watcher) {
+ auto result = registered_watchers_.emplace(
+ watcher, base::MakeRefCounted<WatcherState>());
+ DCHECK(result.second);
+ return result.first;
+ }
+
+ void UnregisterWatcher(WatcherStateMap::iterator iter) {
+ if (top_watcher_ == iter->first) {
+ // If the watcher being unregistered is currently blocking in a
+ // |SyncWatch()| operation, we need to unblock it. Setting this flag does
+ // that.
+ top_watcher_state_->watcher_was_destroyed = true;
+ top_watcher_state_ = nullptr;
+ top_watcher_ = nullptr;
+ }
+
+ {
+ base::AutoLock lock(ready_watchers_lock_);
+ ready_watchers_.erase(iter->first);
+ }
+
+ registered_watchers_.erase(iter);
+ if (registered_watchers_.empty()) {
+ // If no more watchers are registered, clear our sequence-local storage.
+ // Deletes |this|.
+ GetStorageSlot().Get().reset();
+ }
+ }
+
+ void SignalForWatcher(const SequenceLocalSyncEventWatcher* watcher) {
+ bool must_signal = false;
+ {
+ base::AutoLock lock(ready_watchers_lock_);
+ must_signal = ready_watchers_.empty();
+ ready_watchers_.insert(watcher);
+ }
+
+ // If we didn't have any ready watchers before, the event may not have
+ // been signaled. Signal it to ensure that |OnEventSignaled()| is run.
+ if (must_signal)
+ event_.Signal();
+ }
+
+ void ResetForWatcher(const SequenceLocalSyncEventWatcher* watcher) {
+ base::AutoLock lock(ready_watchers_lock_);
+ ready_watchers_.erase(watcher);
+
+ // No more watchers are ready, so we can reset the event. The next watcher
+ // to call |SignalForWatcher()| will re-signal the event.
+ if (ready_watchers_.empty())
+ event_.Reset();
+ }
+
+ bool SyncWatch(const SequenceLocalSyncEventWatcher* watcher,
+ WatcherState* watcher_state,
+ const bool* should_stop) {
+ // |SyncWatch()| calls may nest arbitrarily deep on the same sequence. We
+ // preserve the outer watcher state on the stack and restore it once the
+ // innermost watch is complete.
+ const SequenceLocalSyncEventWatcher* outer_watcher = top_watcher_;
+ WatcherState* outer_watcher_state = top_watcher_state_;
+
+ // Keep a ref on the stack so the state stays alive even if the watcher is
+ // destroyed.
+ scoped_refptr<WatcherState> top_watcher_state(watcher_state);
+ top_watcher_state_ = watcher_state;
+ top_watcher_ = watcher;
+
+ // In addition to the caller's own stop condition, we need to interrupt the
+ // SyncEventWatcher if |watcher| is destroyed while we're waiting.
+ const bool* stop_flags[] = {should_stop,
+ &top_watcher_state_->watcher_was_destroyed};
+
+ // |SyncWatch()| may delete |this|.
+ auto weak_self = weak_ptr_factory_.GetWeakPtr();
+ bool result = event_watcher_.SyncWatch(stop_flags, 2);
+ if (!weak_self)
+ return false;
+
+ top_watcher_state_ = outer_watcher_state;
+ top_watcher_ = outer_watcher;
+ return result;
+ }
+
+ private:
+ using StorageSlotType =
+ base::SequenceLocalStorageSlot<std::unique_ptr<SequenceLocalState>>;
+ static StorageSlotType& GetStorageSlot() {
+ static base::NoDestructor<StorageSlotType> storage;
+ return *storage;
+ }
+
+ void OnEventSignaled();
+
+ // The shared event and watcher used for this sequence.
+ base::WaitableEvent event_;
+ mojo::SyncEventWatcher event_watcher_;
+
+ // All SequenceLocalSyncEventWatchers on the current sequence have some state
+ // registered here.
+ WatcherStateMap registered_watchers_;
+
+ // Tracks state of the top-most |SyncWatch()| invocation on the stack.
+ const SequenceLocalSyncEventWatcher* top_watcher_ = nullptr;
+ WatcherState* top_watcher_state_ = nullptr;
+
+ // Set of all SequenceLocalSyncEventWatchers in a signaled state, guarded by
+ // a lock for sequence-safe signaling.
+ base::Lock ready_watchers_lock_;
+ base::flat_set<const SequenceLocalSyncEventWatcher*> ready_watchers_;
+
+ base::WeakPtrFactory<SequenceLocalState> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SequenceLocalState);
+};
+
+void SequenceLocalSyncEventWatcher::SequenceLocalState::OnEventSignaled() {
+ for (;;) {
+ base::flat_set<const SequenceLocalSyncEventWatcher*> ready_watchers;
+ {
+ base::AutoLock lock(ready_watchers_lock_);
+ std::swap(ready_watchers_, ready_watchers);
+ }
+ if (ready_watchers.empty())
+ return;
+
+ auto weak_self = weak_ptr_factory_.GetWeakPtr();
+ for (auto* watcher : ready_watchers) {
+ if (top_watcher_ == watcher || watcher->can_wake_up_during_any_watch_) {
+ watcher->callback_.Run();
+
+ // The callback may have deleted |this|.
+ if (!weak_self)
+ return;
+ }
+ }
+ }
+}
+
+// Manages a watcher's reference to the sequence-local state. This hides
+// implementation details from the SequenceLocalSyncEventWatcher interface.
+class SequenceLocalSyncEventWatcher::Registration {
+ public:
+ explicit Registration(const SequenceLocalSyncEventWatcher* watcher)
+ : weak_shared_state_(SequenceLocalState::GetOrCreate()),
+ shared_state_(weak_shared_state_.get()),
+ watcher_state_iterator_(shared_state_->RegisterWatcher(watcher)),
+ watcher_state_(watcher_state_iterator_->second) {}
+
+ ~Registration() {
+ if (weak_shared_state_) {
+ // Because |this| may itself be owned by sequence- or thread-local storage
+ // (e.g. if an interface binding lives there) we have no guarantee that
+ // our SequenceLocalState's storage slot will still be alive during our
+ // own destruction; so we have to guard against any access to it. Note
+ // that this uncertainty only exists within the destructor and does not
+ // apply to other methods on SequenceLocalSyncEventWatcher.
+ //
+ // May delete |shared_state_|.
+ shared_state_->UnregisterWatcher(watcher_state_iterator_);
+ }
+ }
+
+ SequenceLocalState* shared_state() const { return shared_state_; }
+ WatcherState* watcher_state() { return watcher_state_.get(); }
+
+ private:
+ const base::WeakPtr<SequenceLocalState> weak_shared_state_;
+ SequenceLocalState* const shared_state_;
+ WatcherStateMap::iterator watcher_state_iterator_;
+ const scoped_refptr<WatcherState> watcher_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(Registration);
+};
+
+SequenceLocalSyncEventWatcher::SequenceLocalSyncEventWatcher(
+ const base::RepeatingClosure& callback)
+ : registration_(std::make_unique<Registration>(this)),
+ callback_(callback) {}
+
+SequenceLocalSyncEventWatcher::~SequenceLocalSyncEventWatcher() = default;
+
+void SequenceLocalSyncEventWatcher::SignalEvent() {
+ registration_->shared_state()->SignalForWatcher(this);
+}
+
+void SequenceLocalSyncEventWatcher::ResetEvent() {
+ registration_->shared_state()->ResetForWatcher(this);
+}
+
+void SequenceLocalSyncEventWatcher::AllowWokenUpBySyncWatchOnSameSequence() {
+ can_wake_up_during_any_watch_ = true;
+}
+
+bool SequenceLocalSyncEventWatcher::SyncWatch(const bool* should_stop) {
+ // NOTE: |SyncWatch()| may delete |this|.
+ return registration_->shared_state()->SyncWatch(
+ this, registration_->watcher_state(), should_stop);
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/serialization.h b/mojo/public/cpp/bindings/lib/serialization.h
index 2a7d288d55..8ced91ea53 100644
--- a/mojo/public/cpp/bindings/lib/serialization.h
+++ b/mojo/public/cpp/bindings/lib/serialization.h
@@ -7,96 +7,122 @@
#include <string.h>
-#include "mojo/public/cpp/bindings/array_traits_carray.h"
+#include <type_traits>
+
+#include "base/numerics/safe_math.h"
+#include "mojo/public/cpp/bindings/array_traits_span.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/bindings_internal.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/handle_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_flat_map.h"
#include "mojo/public/cpp/bindings/map_traits_stl.h"
+#include "mojo/public/cpp/bindings/message.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 <typename MojomType, typename DataArrayType, typename UserType>
-DataArrayType StructSerializeImpl(UserType* input) {
- static_assert(BelongsTo<MojomType, MojomTypeCategory::STRUCT>::value,
- "Unexpected type.");
+template <typename MojomType, typename EnableType = void>
+struct MojomSerializationImplTraits;
+
+template <typename MojomType>
+struct MojomSerializationImplTraits<
+ MojomType,
+ typename std::enable_if<
+ BelongsTo<MojomType, MojomTypeCategory::STRUCT>::value>::type> {
+ template <typename MaybeConstUserType, typename WriterType>
+ static void Serialize(MaybeConstUserType& input,
+ Buffer* buffer,
+ WriterType* writer,
+ SerializationContext* context) {
+ mojo::internal::Serialize<MojomType>(input, buffer, writer, context);
+ }
+};
+
+template <typename MojomType>
+struct MojomSerializationImplTraits<
+ MojomType,
+ typename std::enable_if<
+ BelongsTo<MojomType, MojomTypeCategory::UNION>::value>::type> {
+ template <typename MaybeConstUserType, typename WriterType>
+ static void Serialize(MaybeConstUserType& input,
+ Buffer* buffer,
+ WriterType* writer,
+ SerializationContext* context) {
+ mojo::internal::Serialize<MojomType>(input, buffer, writer,
+ false /* inline */, context);
+ }
+};
+template <typename MojomType, typename UserType>
+mojo::Message SerializeAsMessageImpl(UserType* input) {
SerializationContext context;
- size_t size = PrepareToSerialize<MojomType>(*input, &context);
- DCHECK_EQ(size, Align(size));
+ mojo::Message message(0, 0, 0, 0, nullptr);
+ typename MojomTypeTraits<MojomType>::Data::BufferWriter writer;
+ MojomSerializationImplTraits<MojomType>::Serialize(
+ *input, message.payload_buffer(), &writer, &context);
+ message.AttachHandlesFromSerializationContext(&context);
+ return message;
+}
+template <typename MojomType, typename DataArrayType, typename UserType>
+DataArrayType SerializeImpl(UserType* input) {
+ static_assert(BelongsTo<MojomType, MojomTypeCategory::STRUCT>::value ||
+ BelongsTo<MojomType, MojomTypeCategory::UNION>::value,
+ "Unexpected type.");
+ Message message = SerializeAsMessageImpl<MojomType>(input);
+ uint32_t size = message.payload_num_bytes();
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<MojomType>::Data* data = nullptr;
- Serialize<MojomType>(*input, &buffer, &data, &context);
-
- if (need_copy) {
- memcpy(&result.front(), result_buffer, size);
- free(result_buffer);
- }
-
+ if (size)
+ memcpy(&result.front(), message.payload(), size);
return result;
}
-template <typename MojomType, typename DataArrayType, typename UserType>
-bool StructDeserializeImpl(const DataArrayType& input,
- UserType* output,
- bool (*validate_func)(const void*,
- ValidationContext*)) {
- static_assert(BelongsTo<MojomType, MojomTypeCategory::STRUCT>::value,
+template <typename MojomType, typename UserType>
+bool DeserializeImpl(const void* data,
+ size_t data_num_bytes,
+ std::vector<mojo::ScopedHandle> handles,
+ UserType* output,
+ bool (*validate_func)(const void*, ValidationContext*)) {
+ static_assert(BelongsTo<MojomType, MojomTypeCategory::STRUCT>::value ||
+ BelongsTo<MojomType, MojomTypeCategory::UNION>::value,
"Unexpected type.");
using DataType = typename MojomTypeTraits<MojomType>::Data;
- // TODO(sammc): Use DataArrayType::empty() once WTF::Vector::empty() exists.
- void* input_buffer =
- input.size() == 0
- ? nullptr
- : const_cast<void*>(reinterpret_cast<const void*>(&input.front()));
+ const void* input_buffer = data_num_bytes == 0 ? nullptr : data;
+ void* aligned_input_buffer = nullptr;
- // Please see comments in StructSerializeImpl.
+ // Validation code will insist that the input buffer is aligned, so we ensure
+ // that here. If the input data is not aligned, we (sadly) copy into an
+ // aligned buffer. In practice this should happen only rarely if ever.
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());
+ aligned_input_buffer = malloc(data_num_bytes);
+ DCHECK(IsAligned(aligned_input_buffer));
+ memcpy(aligned_input_buffer, data, data_num_bytes);
+ input_buffer = aligned_input_buffer;
}
- ValidationContext validation_context(input_buffer, input.size(), 0, 0);
+ DCHECK(base::IsValueInRangeForNumericType<uint32_t>(data_num_bytes));
+ ValidationContext validation_context(
+ input_buffer, static_cast<uint32_t>(data_num_bytes), handles.size(), 0);
bool result = false;
if (validate_func(input_buffer, &validation_context)) {
- auto data = reinterpret_cast<DataType*>(input_buffer);
SerializationContext context;
- result = Deserialize<MojomType>(data, output, &context);
+ *context.mutable_handles() = std::move(handles);
+ result = Deserialize<MojomType>(
+ reinterpret_cast<DataType*>(const_cast<void*>(input_buffer)), output,
+ &context);
}
- if (need_copy)
- free(input_buffer);
+ if (aligned_input_buffer)
+ free(aligned_input_buffer);
return result;
}
diff --git a/mojo/public/cpp/bindings/lib/serialization_context.cc b/mojo/public/cpp/bindings/lib/serialization_context.cc
index e2fd5c6e18..267b54154b 100644
--- a/mojo/public/cpp/bindings/lib/serialization_context.cc
+++ b/mojo/public/cpp/bindings/lib/serialization_context.cc
@@ -7,50 +7,78 @@
#include <limits>
#include "base/logging.h"
+#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/cpp/system/core.h"
namespace mojo {
namespace internal {
-SerializedHandleVector::SerializedHandleVector() {}
+SerializationContext::SerializationContext() = default;
-SerializedHandleVector::~SerializedHandleVector() {
- for (auto handle : handles_) {
- if (handle.is_valid()) {
- MojoResult rv = MojoClose(handle.value());
- DCHECK_EQ(rv, MOJO_RESULT_OK);
- }
+SerializationContext::~SerializationContext() = default;
+
+void SerializationContext::AddHandle(mojo::ScopedHandle handle,
+ Handle_Data* out_data) {
+ if (!handle.is_valid()) {
+ out_data->value = kEncodedInvalidHandleValue;
+ } else {
+ DCHECK_LT(handles_.size(), std::numeric_limits<uint32_t>::max());
+ out_data->value = static_cast<uint32_t>(handles_.size());
+ handles_.emplace_back(std::move(handle));
}
}
-Handle_Data SerializedHandleVector::AddHandle(mojo::Handle handle) {
- Handle_Data data;
+void SerializationContext::AddInterfaceInfo(
+ mojo::ScopedMessagePipeHandle handle,
+ uint32_t version,
+ Interface_Data* out_data) {
+ AddHandle(ScopedHandle::From(std::move(handle)), &out_data->handle);
+ out_data->version = version;
+}
+
+void SerializationContext::AddAssociatedEndpoint(
+ ScopedInterfaceEndpointHandle handle,
+ AssociatedEndpointHandle_Data* out_data) {
if (!handle.is_valid()) {
- data.value = kEncodedInvalidHandleValue;
+ out_data->value = kEncodedInvalidHandleValue;
} else {
- DCHECK_LT(handles_.size(), std::numeric_limits<uint32_t>::max());
- data.value = static_cast<uint32_t>(handles_.size());
- handles_.push_back(handle);
+ DCHECK_LT(associated_endpoint_handles_.size(),
+ std::numeric_limits<uint32_t>::max());
+ out_data->value =
+ static_cast<uint32_t>(associated_endpoint_handles_.size());
+ associated_endpoint_handles_.emplace_back(std::move(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 SerializationContext::AddAssociatedInterfaceInfo(
+ ScopedInterfaceEndpointHandle handle,
+ uint32_t version,
+ AssociatedInterface_Data* out_data) {
+ AddAssociatedEndpoint(std::move(handle), &out_data->handle);
+ out_data->version = version;
}
-void SerializedHandleVector::Swap(std::vector<mojo::Handle>* other) {
- handles_.swap(*other);
+void SerializationContext::TakeHandlesFromMessage(Message* message) {
+ handles_.swap(*message->mutable_handles());
+ associated_endpoint_handles_.swap(
+ *message->mutable_associated_endpoint_handles());
}
-SerializationContext::SerializationContext() {}
+mojo::ScopedHandle SerializationContext::TakeHandle(
+ const Handle_Data& encoded_handle) {
+ if (!encoded_handle.is_valid())
+ return mojo::ScopedHandle();
+ DCHECK_LT(encoded_handle.value, handles_.size());
+ return std::move(handles_[encoded_handle.value]);
+}
-SerializationContext::~SerializationContext() {
- DCHECK(!custom_contexts || custom_contexts->empty());
+mojo::ScopedInterfaceEndpointHandle
+SerializationContext::TakeAssociatedEndpointHandle(
+ const AssociatedEndpointHandle_Data& encoded_handle) {
+ if (!encoded_handle.is_valid())
+ return mojo::ScopedInterfaceEndpointHandle();
+ DCHECK_LT(encoded_handle.value, associated_endpoint_handles_.size());
+ return std::move(associated_endpoint_handles_[encoded_handle.value]);
}
} // namespace internal
diff --git a/mojo/public/cpp/bindings/lib/serialization_context.h b/mojo/public/cpp/bindings/lib/serialization_context.h
index a34fe3d4ed..0e3c0788dc 100644
--- a/mojo/public/cpp/bindings/lib/serialization_context.h
+++ b/mojo/public/cpp/bindings/lib/serialization_context.h
@@ -8,67 +8,88 @@
#include <stddef.h>
#include <memory>
-#include <queue>
#include <vector>
+#include "base/component_export.h"
+#include "base/containers/stack_container.h"
#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 {
+
+class Message;
+
namespace internal {
-// A container for handles during serialization/deserialization.
-class MOJO_CPP_BINDINGS_EXPORT SerializedHandleVector {
+// Context information for serialization/deserialization routines.
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) SerializationContext {
public:
- SerializedHandleVector();
- ~SerializedHandleVector();
+ SerializationContext();
+ ~SerializationContext();
- size_t size() const { return handles_.size(); }
+ // Adds a handle to the handle list and outputs its serialized form in
+ // |*out_data|.
+ void AddHandle(mojo::ScopedHandle handle, Handle_Data* out_data);
+
+ // Adds an interface info to the handle list and outputs its serialized form
+ // in |*out_data|.
+ void AddInterfaceInfo(mojo::ScopedMessagePipeHandle handle,
+ uint32_t version,
+ Interface_Data* out_data);
+
+ // Adds an associated interface endpoint (for e.g. an
+ // AssociatedInterfaceRequest) to this context and outputs its serialized form
+ // in |*out_data|.
+ void AddAssociatedEndpoint(ScopedInterfaceEndpointHandle handle,
+ AssociatedEndpointHandle_Data* out_data);
+
+ // Adds an associated interface info to associated endpoint handle and version
+ // data lists and outputs its serialized form in |*out_data|.
+ void AddAssociatedInterfaceInfo(ScopedInterfaceEndpointHandle handle,
+ uint32_t version,
+ AssociatedInterface_Data* out_data);
+
+ const std::vector<mojo::ScopedHandle>* handles() { return &handles_; }
+ std::vector<mojo::ScopedHandle>* mutable_handles() { return &handles_; }
+
+ const std::vector<ScopedInterfaceEndpointHandle>*
+ associated_endpoint_handles() const {
+ return &associated_endpoint_handles_;
+ }
+ std::vector<ScopedInterfaceEndpointHandle>*
+ mutable_associated_endpoint_handles() {
+ return &associated_endpoint_handles_;
+ }
- // Adds a handle to the handle list and returns its index for encoding.
- Handle_Data AddHandle(mojo::Handle handle);
+ // Takes handles from a received Message object and assumes ownership of them.
+ // Individual handles can be extracted using Take* methods below.
+ void TakeHandlesFromMessage(Message* message);
// Takes a handle from the list of serialized handle data.
- mojo::Handle TakeHandle(const Handle_Data& encoded_handle);
+ mojo::ScopedHandle 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 <typename T>
ScopedHandleBase<T> TakeHandleAs(const Handle_Data& encoded_handle) {
- return MakeScopedHandle(T(TakeHandle(encoded_handle).value()));
+ return ScopedHandleBase<T>::From(TakeHandle(encoded_handle));
}
- // Swaps all owned handles out with another Handle vector.
- void Swap(std::vector<mojo::Handle>* other);
+ mojo::ScopedInterfaceEndpointHandle TakeAssociatedEndpointHandle(
+ const AssociatedEndpointHandle_Data& encoded_handle);
private:
- // Handles are owned by this object.
- std::vector<mojo::Handle> 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<std::queue<void*>> 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;
+ // Handles owned by this object. Used during serialization to hold onto
+ // handles accumulated during pre-serialization, and used during
+ // deserialization to hold onto handles extracted from a message.
+ std::vector<mojo::ScopedHandle> handles_;
// Stashes ScopedInterfaceEndpointHandles encoded in a message by index.
- std::vector<ScopedInterfaceEndpointHandle> associated_endpoint_handles;
+ std::vector<ScopedInterfaceEndpointHandle> associated_endpoint_handles_;
+
+ DISALLOW_COPY_AND_ASSIGN(SerializationContext);
};
} // namespace internal
diff --git a/mojo/public/cpp/bindings/lib/serialization_forward.h b/mojo/public/cpp/bindings/lib/serialization_forward.h
index 55c9982ccc..562951ee4a 100644
--- a/mojo/public/cpp/bindings/lib/serialization_forward.h
+++ b/mojo/public/cpp/bindings/lib/serialization_forward.h
@@ -33,22 +33,6 @@ struct IsOptionalWrapper {
typename std::remove_reference<T>::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 <typename MojomType,
- typename InputUserType,
- typename... Args,
- typename std::enable_if<
- !IsOptionalWrapper<InputUserType>::value>::type* = nullptr>
-size_t PrepareToSerialize(InputUserType&& input, Args&&... args) {
- return Serializer<MojomType,
- typename std::remove_reference<InputUserType>::type>::
- PrepareToSerialize(std::forward<InputUserType>(input),
- std::forward<Args>(args)...);
-}
-
template <typename MojomType,
typename InputUserType,
typename... Args,
@@ -71,33 +55,19 @@ bool Deserialize(DataType&& input, InputUserType* output, Args&&... args) {
std::forward<DataType>(input), output, std::forward<Args>(args)...);
}
-// Specialization that unwraps base::Optional<>.
template <typename MojomType,
typename InputUserType,
- typename... Args,
- typename std::enable_if<
- IsOptionalWrapper<InputUserType>::value>::type* = nullptr>
-size_t PrepareToSerialize(InputUserType&& input, Args&&... args) {
- if (!input)
- return 0;
- return PrepareToSerialize<MojomType>(*input, std::forward<Args>(args)...);
-}
-
-template <typename MojomType,
- typename InputUserType,
- typename DataType,
+ typename BufferWriterType,
typename... Args,
typename std::enable_if<
IsOptionalWrapper<InputUserType>::value>::type* = nullptr>
void Serialize(InputUserType&& input,
Buffer* buffer,
- DataType** output,
+ BufferWriterType* writer,
Args&&... args) {
- if (!input) {
- *output = nullptr;
+ if (!input)
return;
- }
- Serialize<MojomType>(*input, buffer, output, std::forward<Args>(args)...);
+ Serialize<MojomType>(*input, buffer, writer, std::forward<Args>(args)...);
}
template <typename MojomType,
diff --git a/mojo/public/cpp/bindings/lib/serialization_util.h b/mojo/public/cpp/bindings/lib/serialization_util.h
index 4820a014ec..a7a99b3bb7 100644
--- a/mojo/public/cpp/bindings/lib/serialization_util.h
+++ b/mojo/public/cpp/bindings/lib/serialization_util.h
@@ -21,7 +21,7 @@ namespace internal {
template <typename T>
struct HasIsNullMethod {
template <typename U>
- static char Test(decltype(U::IsNull)*);
+ static char Test(decltype(U::IsNull) *);
template <typename U>
static int Test(...);
static const bool value = sizeof(Test<T>(0)) == sizeof(char);
@@ -48,7 +48,7 @@ bool CallIsNullIfExists(const UserType& input) {
template <typename T>
struct HasSetToNullMethod {
template <typename U>
- static char Test(decltype(U::SetToNull)*);
+ static char Test(decltype(U::SetToNull) *);
template <typename U>
static int Test(...);
static const bool value = sizeof(Test<T>(0)) == sizeof(char);
@@ -80,7 +80,7 @@ bool CallSetToNullIfExists(UserType* output) {
template <typename T>
struct HasSetUpContextMethod {
template <typename U>
- static char Test(decltype(U::SetUpContext)*);
+ static char Test(decltype(U::SetUpContext) *);
template <typename U>
static int Test(...);
static const bool value = sizeof(Test<T>(0)) == sizeof(char);
@@ -97,17 +97,7 @@ template <typename Traits>
struct CustomContextHelper<Traits, true> {
template <typename MaybeConstUserType>
static void* SetUp(MaybeConstUserType& input, SerializationContext* context) {
- void* custom_context = Traits::SetUpContext(input);
- if (!context->custom_contexts)
- context->custom_contexts.reset(new std::queue<void*>());
- 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;
+ return Traits::SetUpContext(input);
}
template <typename MaybeConstUserType>
@@ -123,8 +113,6 @@ struct CustomContextHelper<Traits, false> {
return nullptr;
}
- static void* GetNext(SerializationContext* context) { return nullptr; }
-
template <typename MaybeConstUserType>
static void TearDown(MaybeConstUserType& input, void* custom_context) {
DCHECK(!custom_context);
@@ -148,7 +136,8 @@ ReturnType CallWithContext(ReturnType (*f)(ParamType),
template <typename T, typename MaybeConstUserType>
struct HasGetBeginMethod {
template <typename U>
- static char Test(decltype(U::GetBegin(std::declval<MaybeConstUserType&>()))*);
+ static char Test(
+ decltype(U::GetBegin(std::declval<MaybeConstUserType&>())) *);
template <typename U>
static int Test(...);
static const bool value = sizeof(Test<T>(0)) == sizeof(char);
@@ -179,7 +168,7 @@ size_t CallGetBeginIfExists(MaybeConstUserType& input) {
template <typename T, typename MaybeConstUserType>
struct HasGetDataMethod {
template <typename U>
- static char Test(decltype(U::GetData(std::declval<MaybeConstUserType&>()))*);
+ static char Test(decltype(U::GetData(std::declval<MaybeConstUserType&>())) *);
template <typename U>
static int Test(...);
static const bool value = sizeof(Test<T>(0)) == sizeof(char);
diff --git a/mojo/public/cpp/bindings/lib/string_serialization.h b/mojo/public/cpp/bindings/lib/string_serialization.h
index 6e0c758576..1fe6b87af7 100644
--- a/mojo/public/cpp/bindings/lib/string_serialization.h
+++ b/mojo/public/cpp/bindings/lib/string_serialization.h
@@ -22,36 +22,18 @@ struct Serializer<StringDataView, MaybeConstUserType> {
using UserType = typename std::remove_const<MaybeConstUserType>::type;
using Traits = StringTraits<UserType>;
- static size_t PrepareToSerialize(MaybeConstUserType& input,
- SerializationContext* context) {
- if (CallIsNullIfExists<Traits>(input))
- return 0;
-
- void* custom_context = CustomContextHelper<Traits>::SetUp(input, context);
- return Align(sizeof(String_Data) +
- CallWithContext(Traits::GetSize, input, custom_context));
- }
-
static void Serialize(MaybeConstUserType& input,
Buffer* buffer,
- String_Data** output,
+ String_Data::BufferWriter* writer,
SerializationContext* context) {
- if (CallIsNullIfExists<Traits>(input)) {
- *output = nullptr;
+ if (CallIsNullIfExists<Traits>(input))
return;
- }
-
- void* custom_context = CustomContextHelper<Traits>::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;
+ void* custom_context = CustomContextHelper<Traits>::SetUp(input, context);
+ const size_t size = CallWithContext(Traits::GetSize, input, custom_context);
+ writer->Allocate(size, buffer);
+ memcpy((*writer)->storage(),
+ CallWithContext(Traits::GetData, input, custom_context), size);
CustomContextHelper<Traits>::TearDown(input, custom_context);
}
diff --git a/mojo/public/cpp/bindings/lib/string_traits_string16.cc b/mojo/public/cpp/bindings/lib/string_traits_string16.cc
deleted file mode 100644
index 95ff6ccf25..0000000000
--- a/mojo/public/cpp/bindings/lib/string_traits_string16.cc
+++ /dev/null
@@ -1,42 +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 "mojo/public/cpp/bindings/string_traits_string16.h"
-
-#include <string>
-
-#include "base/strings/utf_string_conversions.h"
-
-namespace mojo {
-
-// static
-void* StringTraits<base::string16>::SetUpContext(const base::string16& input) {
- return new std::string(base::UTF16ToUTF8(input));
-}
-
-// static
-void StringTraits<base::string16>::TearDownContext(const base::string16& input,
- void* context) {
- delete static_cast<std::string*>(context);
-}
-
-// static
-size_t StringTraits<base::string16>::GetSize(const base::string16& input,
- void* context) {
- return static_cast<std::string*>(context)->size();
-}
-
-// static
-const char* StringTraits<base::string16>::GetData(const base::string16& input,
- void* context) {
- return static_cast<std::string*>(context)->data();
-}
-
-// static
-bool StringTraits<base::string16>::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
index 203f6f5903..71b758c49c 100644
--- a/mojo/public/cpp/bindings/lib/string_traits_wtf.cc
+++ b/mojo/public/cpp/bindings/lib/string_traits_wtf.cc
@@ -8,7 +8,8 @@
#include "base/logging.h"
#include "mojo/public/cpp/bindings/lib/array_internal.h"
-#include "third_party/WebKit/Source/wtf/text/StringUTF8Adaptor.h"
+#include "mojo/public/cpp/bindings/string_data_view.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
namespace mojo {
namespace {
@@ -16,7 +17,7 @@ namespace {
struct UTF8AdaptorInfo {
explicit UTF8AdaptorInfo(const WTF::String& input) : utf8_adaptor(input) {
#if DCHECK_IS_ON()
- original_size_in_bytes = input.charactersSizeInBytes();
+ original_size_in_bytes = input.CharactersSizeInBytes();
#endif
}
@@ -34,7 +35,7 @@ UTF8AdaptorInfo* ToAdaptor(const WTF::String& input, void* context) {
UTF8AdaptorInfo* adaptor = static_cast<UTF8AdaptorInfo*>(context);
#if DCHECK_IS_ON()
- DCHECK_EQ(adaptor->original_size_in_bytes, input.charactersSizeInBytes());
+ DCHECK_EQ(adaptor->original_size_in_bytes, input.CharactersSizeInBytes());
#endif
return adaptor;
}
@@ -43,7 +44,7 @@ UTF8AdaptorInfo* ToAdaptor(const WTF::String& input, void* context) {
// static
void StringTraits<WTF::String>::SetToNull(WTF::String* output) {
- if (output->isNull())
+ if (output->IsNull())
return;
WTF::String result;
@@ -70,13 +71,13 @@ size_t StringTraits<WTF::String>::GetSize(const WTF::String& input,
// static
const char* StringTraits<WTF::String>::GetData(const WTF::String& input,
void* context) {
- return ToAdaptor(input, context)->utf8_adaptor.data();
+ return ToAdaptor(input, context)->utf8_adaptor.Data();
}
// static
bool StringTraits<WTF::String>::Read(StringDataView input,
WTF::String* output) {
- WTF::String result = WTF::String::fromUTF8(input.storage(), input.size());
+ WTF::String result = WTF::String::FromUTF8(input.storage(), input.size());
output->swap(result);
return true;
}
diff --git a/mojo/public/cpp/bindings/lib/sync_call_restrictions.cc b/mojo/public/cpp/bindings/lib/sync_call_restrictions.cc
index 585a8f094c..2b359861d7 100644
--- a/mojo/public/cpp/bindings/lib/sync_call_restrictions.cc
+++ b/mojo/public/cpp/bindings/lib/sync_call_restrictions.cc
@@ -7,85 +7,79 @@
#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 "base/macros.h"
+#include "base/no_destructor.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/sequence_local_storage_slot.h"
#include "mojo/public/c/system/core.h"
namespace mojo {
namespace {
-class SyncCallSettings {
+class GlobalSyncCallSettings {
public:
- static SyncCallSettings* current();
+ GlobalSyncCallSettings() = default;
+ ~GlobalSyncCallSettings() = default;
- bool allowed() const {
- return scoped_allow_count_ > 0 || system_defined_value_;
+ bool sync_call_allowed_by_default() const {
+ base::AutoLock lock(lock_);
+ return sync_call_allowed_by_default_;
}
- void IncreaseScopedAllowCount() { scoped_allow_count_++; }
- void DecreaseScopedAllowCount() {
- DCHECK_LT(0u, scoped_allow_count_);
- scoped_allow_count_--;
+ void DisallowSyncCallByDefault() {
+ base::AutoLock lock(lock_);
+ sync_call_allowed_by_default_ = false;
}
private:
- SyncCallSettings();
- ~SyncCallSettings();
+ mutable base::Lock lock_;
+ bool sync_call_allowed_by_default_ = true;
- bool system_defined_value_ = true;
- size_t scoped_allow_count_ = 0;
+ DISALLOW_COPY_AND_ASSIGN(GlobalSyncCallSettings);
};
-base::LazyInstance<base::ThreadLocalPointer<SyncCallSettings>>::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);
+GlobalSyncCallSettings& GetGlobalSettings() {
+ static base::NoDestructor<GlobalSyncCallSettings> global_settings;
+ return *global_settings;
}
-SyncCallSettings::~SyncCallSettings() {
- g_sync_call_settings.Pointer()->Set(nullptr);
+size_t& GetSequenceLocalScopedAllowCount() {
+ static base::NoDestructor<base::SequenceLocalStorageSlot<size_t>> count;
+ return count->Get();
}
} // 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.";
- }
+ if (GetGlobalSettings().sync_call_allowed_by_default())
+ return;
+ if (GetSequenceLocalScopedAllowCount() > 0)
+ return;
+
+ 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::DisallowSyncCall() {
+ GetGlobalSettings().DisallowSyncCallByDefault();
}
// static
void SyncCallRestrictions::IncreaseScopedAllowCount() {
- SyncCallSettings::current()->IncreaseScopedAllowCount();
+ ++GetSequenceLocalScopedAllowCount();
}
// static
void SyncCallRestrictions::DecreaseScopedAllowCount() {
- SyncCallSettings::current()->DecreaseScopedAllowCount();
+ DCHECK_GT(GetSequenceLocalScopedAllowCount(), 0u);
+ --GetSequenceLocalScopedAllowCount();
}
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/sync_event_watcher.cc b/mojo/public/cpp/bindings/lib/sync_event_watcher.cc
index b1c97e3691..17165912fc 100644
--- a/mojo/public/cpp/bindings/lib/sync_event_watcher.cc
+++ b/mojo/public/cpp/bindings/lib/sync_event_watcher.cc
@@ -4,6 +4,9 @@
#include "mojo/public/cpp/bindings/sync_event_watcher.h"
+#include <algorithm>
+
+#include "base/containers/stack_container.h"
#include "base/logging.h"
namespace mojo {
@@ -16,19 +19,20 @@ SyncEventWatcher::SyncEventWatcher(base::WaitableEvent* event,
destroyed_(new base::RefCountedData<bool>(false)) {}
SyncEventWatcher::~SyncEventWatcher() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (registered_)
- registry_->UnregisterEvent(event_);
+ registry_->UnregisterEvent(event_, callback_);
destroyed_->data = true;
}
void SyncEventWatcher::AllowWokenUpBySyncWatchOnSameThread() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
IncrementRegisterCount();
}
-bool SyncEventWatcher::SyncWatch(const bool* should_stop) {
- DCHECK(thread_checker_.CalledOnValidThread());
+bool SyncEventWatcher::SyncWatch(const bool** stop_flags,
+ size_t num_stop_flags) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
IncrementRegisterCount();
if (!registered_) {
DecrementRegisterCount();
@@ -38,8 +42,14 @@ bool SyncEventWatcher::SyncWatch(const bool* should_stop) {
// 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);
+
+ constexpr size_t kFlagStackCapacity = 4;
+ base::StackVector<const bool*, kFlagStackCapacity> should_stop_array;
+ should_stop_array.container().push_back(&destroyed->data);
+ std::copy(stop_flags, stop_flags + num_stop_flags,
+ std::back_inserter(should_stop_array.container()));
+ bool result = registry_->Wait(should_stop_array.container().data(),
+ should_stop_array.container().size());
// This object has been destroyed.
if (destroyed->data)
@@ -51,15 +61,17 @@ bool SyncEventWatcher::SyncWatch(const bool* should_stop) {
void SyncEventWatcher::IncrementRegisterCount() {
register_request_count_++;
- if (!registered_)
- registered_ = registry_->RegisterEvent(event_, callback_);
+ if (!registered_) {
+ registry_->RegisterEvent(event_, callback_);
+ registered_ = true;
+ }
}
void SyncEventWatcher::DecrementRegisterCount() {
DCHECK_GT(register_request_count_, 0u);
register_request_count_--;
if (register_request_count_ == 0 && registered_) {
- registry_->UnregisterEvent(event_);
+ registry_->UnregisterEvent(event_, callback_);
registered_ = false;
}
}
diff --git a/mojo/public/cpp/bindings/lib/sync_handle_registry.cc b/mojo/public/cpp/bindings/lib/sync_handle_registry.cc
index fd3df396ec..2ac4833445 100644
--- a/mojo/public/cpp/bindings/lib/sync_handle_registry.cc
+++ b/mojo/public/cpp/bindings/lib/sync_handle_registry.cc
@@ -4,27 +4,36 @@
#include "mojo/public/cpp/bindings/sync_handle_registry.h"
+#include <algorithm>
+
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/stl_util.h"
-#include "base/threading/thread_local.h"
+#include "base/threading/sequence_local_storage_slot.h"
+#include "base/threading/sequenced_task_runner_handle.h"
#include "mojo/public/c/system/core.h"
namespace mojo {
namespace {
-base::LazyInstance<base::ThreadLocalPointer<SyncHandleRegistry>>::Leaky
+base::LazyInstance<
+ base::SequenceLocalStorageSlot<scoped_refptr<SyncHandleRegistry>>>::Leaky
g_current_sync_handle_watcher = LAZY_INSTANCE_INITIALIZER;
} // namespace
// static
scoped_refptr<SyncHandleRegistry> SyncHandleRegistry::current() {
- scoped_refptr<SyncHandleRegistry> result(
- g_current_sync_handle_watcher.Pointer()->Get());
+ // SyncMessageFilter can be used on threads without sequence-local storage
+ // being available. Those receive a unique, standalone SyncHandleRegistry.
+ if (!base::SequencedTaskRunnerHandle::IsSet())
+ return new SyncHandleRegistry();
+
+ scoped_refptr<SyncHandleRegistry> result =
+ g_current_sync_handle_watcher.Get().Get();
if (!result) {
result = new SyncHandleRegistry();
- DCHECK_EQ(result.get(), g_current_sync_handle_watcher.Pointer()->Get());
+ g_current_sync_handle_watcher.Get().Set(result);
}
return result;
}
@@ -32,7 +41,7 @@ scoped_refptr<SyncHandleRegistry> SyncHandleRegistry::current() {
bool SyncHandleRegistry::RegisterHandle(const Handle& handle,
MojoHandleSignals handle_signals,
const HandleCallback& callback) {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (base::ContainsKey(handles_, handle))
return false;
@@ -46,7 +55,7 @@ bool SyncHandleRegistry::RegisterHandle(const Handle& handle,
}
void SyncHandleRegistry::UnregisterHandle(const Handle& handle) {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!base::ContainsKey(handles_, handle))
return;
@@ -55,27 +64,63 @@ void SyncHandleRegistry::UnregisterHandle(const Handle& handle) {
handles_.erase(handle);
}
-bool SyncHandleRegistry::RegisterEvent(base::WaitableEvent* event,
+void 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;
+ auto it = events_.find(event);
+ if (it == events_.end()) {
+ auto result = events_.emplace(event, EventCallbackList{});
+ it = result.first;
+ }
+
+ // The event may already be in the WaitSet, but we don't care. This will be a
+ // no-op in that case, which is more efficient than scanning the list of
+ // callbacks to see if any are valid.
+ wait_set_.AddEvent(event);
+
+ it->second.container().push_back(callback);
}
-void SyncHandleRegistry::UnregisterEvent(base::WaitableEvent* event) {
+void SyncHandleRegistry::UnregisterEvent(base::WaitableEvent* event,
+ const base::Closure& callback) {
auto it = events_.find(event);
- DCHECK(it != events_.end());
- events_.erase(it);
- MojoResult rv = wait_set_.RemoveEvent(event);
- DCHECK_EQ(MOJO_RESULT_OK, rv);
+ if (it == events_.end())
+ return;
+
+ bool has_valid_callbacks = false;
+ auto& callbacks = it->second.container();
+ if (is_dispatching_event_callbacks_) {
+ // Not safe to remove any elements from |callbacks| here since an outer
+ // stack frame is currently iterating over it in Wait().
+ for (auto& cb : callbacks) {
+ if (cb.Equals(callback))
+ cb.Reset();
+ else if (cb)
+ has_valid_callbacks = true;
+ }
+ remove_invalid_event_callbacks_after_dispatch_ = true;
+ } else {
+ callbacks.erase(std::remove_if(callbacks.begin(), callbacks.end(),
+ [&callback](const base::Closure& cb) {
+ return cb.Equals(callback);
+ }),
+ callbacks.end());
+ if (callbacks.empty())
+ events_.erase(it);
+ else
+ has_valid_callbacks = true;
+ }
+
+ if (!has_valid_callbacks) {
+ // Regardless of whether or not we're nested within a Wait(), we need to
+ // ensure that |event| is removed from the WaitSet before returning if this
+ // was the last callback registered for 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());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
size_t num_ready_handles;
Handle ready_handle;
@@ -83,9 +128,10 @@ bool SyncHandleRegistry::Wait(const bool* should_stop[], size_t count) {
scoped_refptr<SyncHandleRegistry> preserver(this);
while (true) {
- for (size_t i = 0; i < count; ++i)
+ 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.
@@ -102,34 +148,51 @@ bool SyncHandleRegistry::Wait(const bool* should_stop[], size_t count) {
if (ready_event) {
const auto iter = events_.find(ready_event);
DCHECK(iter != events_.end());
- iter->second.Run();
+ bool was_dispatching_event_callbacks = is_dispatching_event_callbacks_;
+ is_dispatching_event_callbacks_ = true;
+
+ // NOTE: It's possible for the container to be extended by any of these
+ // callbacks if they call RegisterEvent, so we are careful to iterate by
+ // index. Also note that conversely, elements cannot be *removed* from the
+ // container, by any of these callbacks, so it is safe to assume the size
+ // only stays the same or increases, with no elements changing position.
+ auto& callbacks = iter->second.container();
+ for (size_t i = 0; i < callbacks.size(); ++i) {
+ auto& callback = callbacks[i];
+ if (callback)
+ callback.Run();
+ }
+
+ is_dispatching_event_callbacks_ = was_dispatching_event_callbacks;
+ if (!was_dispatching_event_callbacks &&
+ remove_invalid_event_callbacks_after_dispatch_) {
+ // If we've had events unregistered within any callback dispatch, now is
+ // a good time to prune them from the map.
+ RemoveInvalidEventCallbacks();
+ remove_invalid_event_callbacks_after_dispatch_ = false;
+ }
}
};
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);
+SyncHandleRegistry::SyncHandleRegistry() = default;
+
+SyncHandleRegistry::~SyncHandleRegistry() = default;
+
+void SyncHandleRegistry::RemoveInvalidEventCallbacks() {
+ for (auto it = events_.begin(); it != events_.end();) {
+ auto& callbacks = it->second.container();
+ callbacks.erase(
+ std::remove_if(callbacks.begin(), callbacks.end(),
+ [](const base::Closure& callback) { return !callback; }),
+ callbacks.end());
+ if (callbacks.empty())
+ events_.erase(it++);
+ else
+ ++it;
+ }
}
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc b/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc
index f20af56b20..294b8a1a4b 100644
--- a/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc
+++ b/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc
@@ -21,7 +21,7 @@ SyncHandleWatcher::SyncHandleWatcher(
destroyed_(new base::RefCountedData<bool>(false)) {}
SyncHandleWatcher::~SyncHandleWatcher() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (registered_)
registry_->UnregisterHandle(handle_);
@@ -29,12 +29,12 @@ SyncHandleWatcher::~SyncHandleWatcher() {
}
void SyncHandleWatcher::AllowWokenUpBySyncWatchOnSameThread() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
IncrementRegisterCount();
}
bool SyncHandleWatcher::SyncWatch(const bool* should_stop) {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
IncrementRegisterCount();
if (!registered_) {
DecrementRegisterCount();
diff --git a/mojo/public/cpp/bindings/lib/task_runner_helper.cc b/mojo/public/cpp/bindings/lib/task_runner_helper.cc
new file mode 100644
index 0000000000..6104a9740e
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/task_runner_helper.cc
@@ -0,0 +1,24 @@
+// 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/lib/task_runner_helper.h"
+
+#include "base/sequenced_task_runner.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+
+namespace mojo {
+namespace internal {
+
+scoped_refptr<base::SequencedTaskRunner>
+GetTaskRunnerToUseFromUserProvidedTaskRunner(
+ scoped_refptr<base::SequencedTaskRunner> runner) {
+ if (runner) {
+ DCHECK(runner->RunsTasksInCurrentSequence());
+ return runner;
+ }
+ return base::SequencedTaskRunnerHandle::Get();
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/task_runner_helper.h b/mojo/public/cpp/bindings/lib/task_runner_helper.h
new file mode 100644
index 0000000000..d34d179675
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/task_runner_helper.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_TASK_RUNNER_HELPER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_TASK_RUNNER_HELPER_H_
+
+#include "base/memory/ref_counted.h"
+
+namespace base {
+class SequencedTaskRunner;
+} // namespace base
+
+namespace mojo {
+namespace internal {
+
+// Returns the SequencedTaskRunner to use from the optional user-provided
+// SequencedTaskRunner. If |runner| is provided non-null, it is returned.
+// Otherwise, SequencedTaskRunnerHandle::Get() is returned. If |runner| is non-
+// null, it must run tasks on the current sequence.
+scoped_refptr<base::SequencedTaskRunner>
+GetTaskRunnerToUseFromUserProvidedTaskRunner(
+ scoped_refptr<base::SequencedTaskRunner> runner);
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_TASK_RUNNER_HELPER_H_
diff --git a/mojo/public/cpp/bindings/lib/template_util.h b/mojo/public/cpp/bindings/lib/template_util.h
index 5151123ac0..383eb91593 100644
--- a/mojo/public/cpp/bindings/lib/template_util.h
+++ b/mojo/public/cpp/bindings/lib/template_util.h
@@ -114,6 +114,11 @@ struct Conditional<false, T, F> {
typedef F type;
};
+template <typename T>
+struct AlwaysFalse {
+ static const bool value = false;
+};
+
} // namespace internal
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/union_accessor.h b/mojo/public/cpp/bindings/lib/union_accessor.h
deleted file mode 100644
index 821aede595..0000000000
--- a/mojo/public/cpp/bindings/lib/union_accessor.h
+++ /dev/null
@@ -1,33 +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 MOJO_PUBLIC_CPP_BINDINGS_LIB_UNION_ACCESSOR_H_
-#define MOJO_PUBLIC_CPP_BINDINGS_LIB_UNION_ACCESSOR_H_
-
-namespace mojo {
-namespace internal {
-
-// When serializing and deserializing Unions, it is necessary to access
-// the private fields and methods of the Union. This allows us to do that
-// without leaking those same fields and methods in the Union interface.
-// All Union wrappers are friends of this class allowing such access.
-template <typename U>
-class UnionAccessor {
- public:
- explicit UnionAccessor(U* u) : u_(u) {}
-
- typename U::Union_* data() { return &(u_->data_); }
-
- typename U::Tag* tag() { return &(u_->tag_); }
-
- void SwitchActive(typename U::Tag new_tag) { u_->SwitchActive(new_tag); }
-
- private:
- U* u_;
-};
-
-} // namespace internal
-} // namespace mojo
-
-#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_UNION_ACCESSOR_H_
diff --git a/mojo/public/cpp/bindings/lib/unserialized_message_context.cc b/mojo/public/cpp/bindings/lib/unserialized_message_context.cc
new file mode 100644
index 0000000000..b029f4ef00
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/unserialized_message_context.cc
@@ -0,0 +1,24 @@
+// 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/lib/unserialized_message_context.h"
+
+namespace mojo {
+namespace internal {
+
+UnserializedMessageContext::UnserializedMessageContext(const Tag* tag,
+ uint32_t message_name,
+ uint32_t message_flags)
+ : tag_(tag) {
+ header_.interface_id = 0;
+ header_.version = 1;
+ header_.name = message_name;
+ header_.flags = message_flags;
+ header_.num_bytes = sizeof(header_);
+}
+
+UnserializedMessageContext::~UnserializedMessageContext() = default;
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/unserialized_message_context.h b/mojo/public/cpp/bindings/lib/unserialized_message_context.h
new file mode 100644
index 0000000000..4886a981dc
--- /dev/null
+++ b/mojo/public/cpp/bindings/lib/unserialized_message_context.h
@@ -0,0 +1,63 @@
+// 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_LIB_UNSERIALIZED_MESSAGE_CONTEXT_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_LIB_UNSERIALIZED_MESSAGE_CONTEXT_H_
+
+#include <stdint.h>
+
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/bindings/lib/buffer.h"
+#include "mojo/public/cpp/bindings/lib/message_internal.h"
+#include "mojo/public/cpp/bindings/lib/serialization_context.h"
+
+namespace mojo {
+namespace internal {
+
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) UnserializedMessageContext {
+ public:
+ struct Tag {};
+
+ UnserializedMessageContext(const Tag* tag,
+ uint32_t message_name,
+ uint32_t message_flags);
+ virtual ~UnserializedMessageContext();
+
+ template <typename MessageType>
+ MessageType* SafeCast() {
+ if (&MessageType::kMessageTag != tag_)
+ return nullptr;
+ return static_cast<MessageType*>(this);
+ }
+
+ const Tag* tag() const { return tag_; }
+ uint32_t message_name() const { return header_.name; }
+ uint32_t message_flags() const { return header_.flags; }
+
+ MessageHeaderV1* header() { return &header_; }
+
+ virtual void Serialize(SerializationContext* serialization_context,
+ Buffer* buffer) = 0;
+
+ private:
+ // The |tag_| is used for run-time type identification of specific
+ // unserialized message types, e.g. messages generated by mojom bindings. This
+ // allows opaque message objects to be safely downcast once pulled off a pipe.
+ const Tag* const tag_;
+
+ // We store message metadata in a serialized header structure to simplify
+ // Message implementation which needs to query such metadata for both
+ // serialized and unserialized message objects.
+ MessageHeaderV1 header_;
+
+ DISALLOW_COPY_AND_ASSIGN(UnserializedMessageContext);
+};
+
+} // namespace internal
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_UNSERIALIZED_MESSAGE_CONTEXT_H_
diff --git a/mojo/public/cpp/bindings/lib/validation_context.h b/mojo/public/cpp/bindings/lib/validation_context.h
index ed6c6542e7..7c4de47327 100644
--- a/mojo/public/cpp/bindings/lib/validation_context.h
+++ b/mojo/public/cpp/bindings/lib/validation_context.h
@@ -9,9 +9,9 @@
#include <stdint.h>
#include "base/compiler_specific.h"
+#include "base/component_export.h"
#include "base/macros.h"
#include "base/strings/string_piece.h"
-#include "mojo/public/cpp/bindings/bindings_export.h"
#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
static const int kMaxRecursionDepth = 100;
@@ -24,7 +24,7 @@ namespace internal {
// ValidationContext is used when validating object sizes, pointers and handle
// indices in the payload of incoming messages.
-class MOJO_CPP_BINDINGS_EXPORT ValidationContext {
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) ValidationContext {
public:
// [data, data + data_num_bytes) specifies the initial valid memory range.
// [0, num_handles) specifies the initial valid range of handle indices.
diff --git a/mojo/public/cpp/bindings/lib/validation_errors.h b/mojo/public/cpp/bindings/lib/validation_errors.h
index 122418d9e3..e48e37c6b6 100644
--- a/mojo/public/cpp/bindings/lib/validation_errors.h
+++ b/mojo/public/cpp/bindings/lib/validation_errors.h
@@ -6,9 +6,9 @@
#define MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_ERRORS_H_
#include "base/callback.h"
+#include "base/component_export.h"
#include "base/logging.h"
#include "base/macros.h"
-#include "mojo/public/cpp/bindings/bindings_export.h"
#include "mojo/public/cpp/bindings/lib/validation_context.h"
namespace mojo {
@@ -76,23 +76,24 @@ enum ValidationError {
VALIDATION_ERROR_MAX_RECURSION_DEPTH,
};
-MOJO_CPP_BINDINGS_EXPORT const char* ValidationErrorToString(
- ValidationError error);
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+const char* ValidationErrorToString(ValidationError error);
-MOJO_CPP_BINDINGS_EXPORT void ReportValidationError(
- ValidationContext* context,
- ValidationError error,
- const char* description = nullptr);
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+void ReportValidationError(ValidationContext* context,
+ ValidationError error,
+ const char* description = nullptr);
-MOJO_CPP_BINDINGS_EXPORT void ReportValidationErrorForMessage(
- mojo::Message* message,
- ValidationError error,
- const char* description = nullptr);
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+void ReportValidationErrorForMessage(mojo::Message* message,
+ ValidationError error,
+ const char* description = nullptr);
// This class may be used by tests to suppress validation error logging. This is
// not thread-safe and must only be instantiated on the main thread with no
// other threads using Mojo bindings at the time of construction or destruction.
-class MOJO_CPP_BINDINGS_EXPORT ScopedSuppressValidationErrorLoggingForTests {
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+ ScopedSuppressValidationErrorLoggingForTests {
public:
ScopedSuppressValidationErrorLoggingForTests();
~ScopedSuppressValidationErrorLoggingForTests();
@@ -105,7 +106,8 @@ class MOJO_CPP_BINDINGS_EXPORT ScopedSuppressValidationErrorLoggingForTests {
// Only used by validation tests and when there is only one thread doing message
// validation.
-class MOJO_CPP_BINDINGS_EXPORT ValidationErrorObserverForTesting {
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+ ValidationErrorObserverForTesting {
public:
explicit ValidationErrorObserverForTesting(const base::Closure& callback);
~ValidationErrorObserverForTesting();
@@ -127,11 +129,13 @@ class MOJO_CPP_BINDINGS_EXPORT ValidationErrorObserverForTesting {
//
// The function returns true if the error is recorded (by a
// SerializationWarningObserverForTesting object), false otherwise.
-MOJO_CPP_BINDINGS_EXPORT bool ReportSerializationWarning(ValidationError error);
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+bool ReportSerializationWarning(ValidationError error);
// Only used by serialization tests and when there is only one thread doing
// message serialization.
-class MOJO_CPP_BINDINGS_EXPORT SerializationWarningObserverForTesting {
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+ SerializationWarningObserverForTesting {
public:
SerializationWarningObserverForTesting();
~SerializationWarningObserverForTesting();
diff --git a/mojo/public/cpp/bindings/lib/validation_util.cc b/mojo/public/cpp/bindings/lib/validation_util.cc
index 7614df5cbc..4b414c4e3b 100644
--- a/mojo/public/cpp/bindings/lib/validation_util.cc
+++ b/mojo/public/cpp/bindings/lib/validation_util.cc
@@ -8,14 +8,25 @@
#include <limits>
+#include "base/strings/stringprintf.h"
#include "mojo/public/cpp/bindings/lib/message_internal.h"
#include "mojo/public/cpp/bindings/lib/serialization_util.h"
#include "mojo/public/cpp/bindings/lib/validation_errors.h"
-#include "mojo/public/interfaces/bindings/interface_control_messages.mojom.h"
namespace mojo {
namespace internal {
+void ReportNonNullableValidationError(ValidationContext* validation_context,
+ ValidationError error,
+ int field_index) {
+ const char* null_or_invalid =
+ error == VALIDATION_ERROR_UNEXPECTED_NULL_POINTER ? "null" : "invalid";
+
+ std::string error_message =
+ base::StringPrintf("%s field %d", null_or_invalid, field_index);
+ ReportValidationError(validation_context, error, error_message.c_str());
+}
+
bool ValidateStructHeaderAndClaimMemory(const void* data,
ValidationContext* validation_context) {
if (!IsAligned(data)) {
@@ -118,53 +129,53 @@ bool IsHandleOrInterfaceValid(const Handle_Data& input) {
bool ValidateHandleOrInterfaceNonNullable(
const AssociatedInterface_Data& input,
- const char* error_message,
+ int field_index,
ValidationContext* validation_context) {
if (IsHandleOrInterfaceValid(input))
return true;
- ReportValidationError(validation_context,
- VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID,
- error_message);
+ ReportNonNullableValidationError(
+ validation_context, VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID,
+ field_index);
return false;
}
bool ValidateHandleOrInterfaceNonNullable(
const AssociatedEndpointHandle_Data& input,
- const char* error_message,
+ int field_index,
ValidationContext* validation_context) {
if (IsHandleOrInterfaceValid(input))
return true;
- ReportValidationError(validation_context,
- VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID,
- error_message);
+ ReportNonNullableValidationError(
+ validation_context, VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID,
+ field_index);
return false;
}
bool ValidateHandleOrInterfaceNonNullable(
const Interface_Data& input,
- const char* error_message,
+ int field_index,
ValidationContext* validation_context) {
if (IsHandleOrInterfaceValid(input))
return true;
- ReportValidationError(validation_context,
- VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE,
- error_message);
+ ReportNonNullableValidationError(validation_context,
+ VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE,
+ field_index);
return false;
}
bool ValidateHandleOrInterfaceNonNullable(
const Handle_Data& input,
- const char* error_message,
+ int field_index,
ValidationContext* validation_context) {
if (IsHandleOrInterfaceValid(input))
return true;
- ReportValidationError(validation_context,
- VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE,
- error_message);
+ ReportNonNullableValidationError(validation_context,
+ VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE,
+ field_index);
return false;
}
diff --git a/mojo/public/cpp/bindings/lib/validation_util.h b/mojo/public/cpp/bindings/lib/validation_util.h
index ea5a991668..3b88956f7a 100644
--- a/mojo/public/cpp/bindings/lib/validation_util.h
+++ b/mojo/public/cpp/bindings/lib/validation_util.h
@@ -7,7 +7,7 @@
#include <stdint.h>
-#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "base/component_export.h"
#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
#include "mojo/public/cpp/bindings/lib/serialization_util.h"
#include "mojo/public/cpp/bindings/lib/validate_params.h"
@@ -18,6 +18,12 @@
namespace mojo {
namespace internal {
+// Calls ReportValidationError() with a constructed error string.
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+void ReportNonNullableValidationError(ValidationContext* validation_context,
+ ValidationError error,
+ int field_index);
+
// Checks whether decoding the pointer will overflow and produce a pointer
// smaller than |offset|.
inline bool ValidateEncodedPointer(const uint64_t* offset) {
@@ -47,32 +53,35 @@ bool ValidatePointer(const Pointer<T>& input,
// |validation_context|. On success, the memory range is marked as occupied.
// Note: Does not verify |version| or that |num_bytes| is correct for the
// claimed version.
-MOJO_CPP_BINDINGS_EXPORT bool ValidateStructHeaderAndClaimMemory(
- const void* data,
- ValidationContext* validation_context);
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+bool ValidateStructHeaderAndClaimMemory(const void* data,
+ ValidationContext* validation_context);
// Validates that |data| contains a valid union header, in terms of alignment
// and size. It checks that the memory range [data, data + kUnionDataSize) is
// not marked as occupied by other objects in |validation_context|. On success,
// the memory range is marked as occupied.
-MOJO_CPP_BINDINGS_EXPORT bool ValidateNonInlinedUnionHeaderAndClaimMemory(
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+bool ValidateNonInlinedUnionHeaderAndClaimMemory(
const void* data,
ValidationContext* validation_context);
// Validates that the message is a request which doesn't expect a response.
-MOJO_CPP_BINDINGS_EXPORT bool ValidateMessageIsRequestWithoutResponse(
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+bool ValidateMessageIsRequestWithoutResponse(
const Message* message,
ValidationContext* validation_context);
// Validates that the message is a request expecting a response.
-MOJO_CPP_BINDINGS_EXPORT bool ValidateMessageIsRequestExpectingResponse(
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+bool ValidateMessageIsRequestExpectingResponse(
const Message* message,
ValidationContext* validation_context);
// Validates that the message is a response.
-MOJO_CPP_BINDINGS_EXPORT bool ValidateMessageIsResponse(
- const Message* message,
- ValidationContext* validation_context);
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+bool ValidateMessageIsResponse(const Message* message,
+ ValidationContext* validation_context);
// Validates that the message payload is a valid struct of type ParamsType.
template <typename ParamsType>
@@ -85,54 +94,56 @@ bool ValidateMessagePayload(const Message* message,
// |input| is not null/invalid.
template <typename T>
bool ValidatePointerNonNullable(const T& input,
- const char* error_message,
+ int field_index,
ValidationContext* validation_context) {
if (input.offset)
return true;
-
- ReportValidationError(validation_context,
- VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
- error_message);
+ ReportNonNullableValidationError(validation_context,
+ VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ field_index);
return false;
}
template <typename T>
bool ValidateInlinedUnionNonNullable(const T& input,
- const char* error_message,
+ int field_index,
ValidationContext* validation_context) {
if (!input.is_null())
return true;
-
- ReportValidationError(validation_context,
- VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
- error_message);
+ ReportNonNullableValidationError(validation_context,
+ VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ field_index);
return false;
}
-MOJO_CPP_BINDINGS_EXPORT bool IsHandleOrInterfaceValid(
- const AssociatedInterface_Data& input);
-MOJO_CPP_BINDINGS_EXPORT bool IsHandleOrInterfaceValid(
- const AssociatedEndpointHandle_Data& input);
-MOJO_CPP_BINDINGS_EXPORT bool IsHandleOrInterfaceValid(
- const Interface_Data& input);
-MOJO_CPP_BINDINGS_EXPORT bool IsHandleOrInterfaceValid(
- const Handle_Data& input);
-
-MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterfaceNonNullable(
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+bool IsHandleOrInterfaceValid(const AssociatedInterface_Data& input);
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+bool IsHandleOrInterfaceValid(const AssociatedEndpointHandle_Data& input);
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+bool IsHandleOrInterfaceValid(const Interface_Data& input);
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+bool IsHandleOrInterfaceValid(const Handle_Data& input);
+
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+bool ValidateHandleOrInterfaceNonNullable(
const AssociatedInterface_Data& input,
- const char* error_message,
+ int field_index,
ValidationContext* validation_context);
-MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterfaceNonNullable(
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+bool ValidateHandleOrInterfaceNonNullable(
const AssociatedEndpointHandle_Data& input,
- const char* error_message,
+ int field_index,
ValidationContext* validation_context);
-MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterfaceNonNullable(
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+bool ValidateHandleOrInterfaceNonNullable(
const Interface_Data& input,
- const char* error_message,
+ int field_index,
ValidationContext* validation_context);
-MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterfaceNonNullable(
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+bool ValidateHandleOrInterfaceNonNullable(
const Handle_Data& input,
- const char* error_message,
+ int field_index,
ValidationContext* validation_context);
template <typename T>
@@ -187,18 +198,18 @@ bool ValidateNonInlinedUnion(const Pointer<T>& input,
T::Validate(input.Get(), validation_context, false);
}
-MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterface(
- const AssociatedInterface_Data& input,
- ValidationContext* validation_context);
-MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterface(
- const AssociatedEndpointHandle_Data& input,
- ValidationContext* validation_context);
-MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterface(
- const Interface_Data& input,
- ValidationContext* validation_context);
-MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterface(
- const Handle_Data& input,
- ValidationContext* validation_context);
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+bool ValidateHandleOrInterface(const AssociatedInterface_Data& input,
+ ValidationContext* validation_context);
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+bool ValidateHandleOrInterface(const AssociatedEndpointHandle_Data& input,
+ ValidationContext* validation_context);
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+bool ValidateHandleOrInterface(const Interface_Data& input,
+ ValidationContext* validation_context);
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
+bool ValidateHandleOrInterface(const Handle_Data& input,
+ ValidationContext* validation_context);
} // namespace internal
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/lib/wtf_clone_equals_util.h b/mojo/public/cpp/bindings/lib/wtf_clone_equals_util.h
index cb24bc46ee..bb0ee531f5 100644
--- a/mojo/public/cpp/bindings/lib/wtf_clone_equals_util.h
+++ b/mojo/public/cpp/bindings/lib/wtf_clone_equals_util.h
@@ -8,11 +8,10 @@
#include <type_traits>
#include "mojo/public/cpp/bindings/clone_traits.h"
-#include "mojo/public/cpp/bindings/lib/equals_traits.h"
-#include "third_party/WebKit/Source/wtf/HashMap.h"
-#include "third_party/WebKit/Source/wtf/Optional.h"
-#include "third_party/WebKit/Source/wtf/Vector.h"
-#include "third_party/WebKit/Source/wtf/text/WTFString.h"
+#include "mojo/public/cpp/bindings/equals_traits.h"
+#include "third_party/blink/renderer/platform/wtf/hash_map.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace mojo {
@@ -20,7 +19,7 @@ template <typename T>
struct CloneTraits<WTF::Vector<T>, false> {
static WTF::Vector<T> Clone(const WTF::Vector<T>& input) {
WTF::Vector<T> result;
- result.reserveCapacity(input.size());
+ result.ReserveCapacity(input.size());
for (const auto& element : input)
result.push_back(mojo::Clone(element));
@@ -32,22 +31,20 @@ template <typename K, typename V>
struct CloneTraits<WTF::HashMap<K, V>, false> {
static WTF::HashMap<K, V> Clone(const WTF::HashMap<K, V>& input) {
WTF::HashMap<K, V> result;
- auto input_end = input.end();
- for (auto it = input.begin(); it != input_end; ++it)
- result.add(mojo::Clone(it->key), mojo::Clone(it->value));
+ for (const auto& element : input)
+ result.insert(mojo::Clone(element.key), mojo::Clone(element.value));
+
return result;
}
};
-namespace internal {
-
template <typename T>
struct EqualsTraits<WTF::Vector<T>, false> {
static bool Equals(const WTF::Vector<T>& a, const WTF::Vector<T>& b) {
if (a.size() != b.size())
return false;
for (size_t i = 0; i < a.size(); ++i) {
- if (!internal::Equals(a[i], b[i]))
+ if (!mojo::Equals(a[i], b[i]))
return false;
}
return true;
@@ -65,14 +62,13 @@ struct EqualsTraits<WTF::HashMap<K, V>, false> {
for (auto iter = a.begin(); iter != a_end; ++iter) {
auto b_iter = b.find(iter->key);
- if (b_iter == b_end || !internal::Equals(iter->value, b_iter->value))
+ if (b_iter == b_end || !mojo::Equals(iter->value, b_iter->value))
return false;
}
return true;
}
};
-} // namespace internal
} // namespace mojo
#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_CLONE_EQUALS_UTIL_H_
diff --git a/mojo/public/cpp/bindings/lib/wtf_hash_util.h b/mojo/public/cpp/bindings/lib/wtf_hash_util.h
index cc590da67a..fa02262e8e 100644
--- a/mojo/public/cpp/bindings/lib/wtf_hash_util.h
+++ b/mojo/public/cpp/bindings/lib/wtf_hash_util.h
@@ -9,9 +9,9 @@
#include "mojo/public/cpp/bindings/lib/hash_util.h"
#include "mojo/public/cpp/bindings/struct_ptr.h"
-#include "third_party/WebKit/Source/wtf/HashFunctions.h"
-#include "third_party/WebKit/Source/wtf/text/StringHash.h"
-#include "third_party/WebKit/Source/wtf/text/WTFString.h"
+#include "third_party/blink/renderer/platform/wtf/hash_functions.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace mojo {
namespace internal {
@@ -48,7 +48,7 @@ struct WTFHashTraits<T, false> {
template <>
struct WTFHashTraits<WTF::String, false> {
static size_t Hash(size_t seed, const WTF::String& value) {
- return HashCombine(seed, WTF::StringHash::hash(value));
+ return HashCombine(seed, WTF::StringHash::GetHash(value));
}
};
@@ -59,25 +59,25 @@ size_t WTFHash(size_t seed, const T& value) {
template <typename T>
struct StructPtrHashFn {
- static unsigned hash(const StructPtr<T>& value) {
+ static unsigned GetHash(const StructPtr<T>& value) {
return value.Hash(kHashSeed);
}
- static bool equal(const StructPtr<T>& left, const StructPtr<T>& right) {
+ static bool Equal(const StructPtr<T>& left, const StructPtr<T>& right) {
return left.Equals(right);
}
- static const bool safeToCompareToEmptyOrDeleted = false;
+ static const bool safe_to_compare_to_empty_or_deleted = false;
};
template <typename T>
struct InlinedStructPtrHashFn {
- static unsigned hash(const InlinedStructPtr<T>& value) {
+ static unsigned GetHash(const InlinedStructPtr<T>& value) {
return value.Hash(kHashSeed);
}
- static bool equal(const InlinedStructPtr<T>& left,
+ static bool Equal(const InlinedStructPtr<T>& left,
const InlinedStructPtr<T>& right) {
return left.Equals(right);
}
- static const bool safeToCompareToEmptyOrDeleted = false;
+ static const bool safe_to_compare_to_empty_or_deleted = false;
};
} // namespace internal
@@ -93,14 +93,14 @@ struct DefaultHash<mojo::StructPtr<T>> {
template <typename T>
struct HashTraits<mojo::StructPtr<T>>
: public GenericHashTraits<mojo::StructPtr<T>> {
- static const bool hasIsEmptyValueFunction = true;
- static bool isEmptyValue(const mojo::StructPtr<T>& value) {
+ static const bool kHasIsEmptyValueFunction = true;
+ static bool IsEmptyValue(const mojo::StructPtr<T>& value) {
return value.is_null();
}
- static void constructDeletedValue(mojo::StructPtr<T>& slot, bool) {
+ static void ConstructDeletedValue(mojo::StructPtr<T>& slot, bool) {
mojo::internal::StructPtrWTFHelper<T>::ConstructDeletedValue(slot);
}
- static bool isDeletedValue(const mojo::StructPtr<T>& value) {
+ static bool IsDeletedValue(const mojo::StructPtr<T>& value) {
return mojo::internal::StructPtrWTFHelper<T>::IsHashTableDeletedValue(
value);
}
@@ -114,14 +114,14 @@ struct DefaultHash<mojo::InlinedStructPtr<T>> {
template <typename T>
struct HashTraits<mojo::InlinedStructPtr<T>>
: public GenericHashTraits<mojo::InlinedStructPtr<T>> {
- static const bool hasIsEmptyValueFunction = true;
- static bool isEmptyValue(const mojo::InlinedStructPtr<T>& value) {
+ static const bool kHasIsEmptyValueFunction = true;
+ static bool IsEmptyValue(const mojo::InlinedStructPtr<T>& value) {
return value.is_null();
}
- static void constructDeletedValue(mojo::InlinedStructPtr<T>& slot, bool) {
+ static void ConstructDeletedValue(mojo::InlinedStructPtr<T>& slot, bool) {
mojo::internal::InlinedStructPtrWTFHelper<T>::ConstructDeletedValue(slot);
}
- static bool isDeletedValue(const mojo::InlinedStructPtr<T>& value) {
+ static bool IsDeletedValue(const mojo::InlinedStructPtr<T>& value) {
return mojo::internal::InlinedStructPtrWTFHelper<
T>::IsHashTableDeletedValue(value);
}
diff --git a/mojo/public/cpp/bindings/map.h b/mojo/public/cpp/bindings/map.h
index c1ba0756a3..350bfad76a 100644
--- a/mojo/public/cpp/bindings/map.h
+++ b/mojo/public/cpp/bindings/map.h
@@ -6,33 +6,32 @@
#define MOJO_PUBLIC_CPP_BINDINGS_MAP_H_
#include <map>
-#include <unordered_map>
#include <utility>
+#include "base/containers/flat_map.h"
+
namespace mojo {
// TODO(yzshen): These conversion functions should be removed and callsites
// should be revisited and changed to use the same map type.
template <typename Key, typename Value>
-std::unordered_map<Key, Value> MapToUnorderedMap(
- const std::map<Key, Value>& input) {
- return std::unordered_map<Key, Value>(input.begin(), input.end());
+base::flat_map<Key, Value> MapToFlatMap(const std::map<Key, Value>& input) {
+ return base::flat_map<Key, Value>(input.begin(), input.end());
}
template <typename Key, typename Value>
-std::unordered_map<Key, Value> MapToUnorderedMap(std::map<Key, Value>&& input) {
- return std::unordered_map<Key, Value>(std::make_move_iterator(input.begin()),
- std::make_move_iterator(input.end()));
+base::flat_map<Key, Value> MapToFlatMap(std::map<Key, Value>&& input) {
+ return base::flat_map<Key, Value>(std::make_move_iterator(input.begin()),
+ std::make_move_iterator(input.end()));
}
template <typename Key, typename Value>
-std::map<Key, Value> UnorderedMapToMap(
- const std::unordered_map<Key, Value>& input) {
+std::map<Key, Value> FlatMapToMap(const base::flat_map<Key, Value>& input) {
return std::map<Key, Value>(input.begin(), input.end());
}
template <typename Key, typename Value>
-std::map<Key, Value> UnorderedMapToMap(std::unordered_map<Key, Value>&& input) {
+std::map<Key, Value> FlatMapToMap(base::flat_map<Key, Value>&& input) {
return std::map<Key, Value>(std::make_move_iterator(input.begin()),
std::make_move_iterator(input.end()));
}
diff --git a/mojo/public/cpp/bindings/map_traits.h b/mojo/public/cpp/bindings/map_traits.h
index 5c0d8b2846..60bcb59255 100644
--- a/mojo/public/cpp/bindings/map_traits.h
+++ b/mojo/public/cpp/bindings/map_traits.h
@@ -5,6 +5,8 @@
#ifndef MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_H_
#define MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_H_
+#include "mojo/public/cpp/bindings/lib/template_util.h"
+
namespace mojo {
// This must be specialized for any type |T| to be serialized/deserialized as
@@ -19,6 +21,8 @@ namespace mojo {
// using Value = V;
//
// // These two methods are optional. Please see comments in struct_traits.h
+// // Note that unlike with StructTraits, IsNull() is called *twice* during
+// // serialization for MapTraits.
// static bool IsNull(const CustomMap<K, V>& input);
// static void SetToNull(CustomMap<K, V>* output);
//
@@ -49,7 +53,11 @@ namespace mojo {
// };
//
template <typename T>
-struct MapTraits;
+struct MapTraits {
+ static_assert(internal::AlwaysFalse<T>::value,
+ "Cannot find the mojo::MapTraits specialization. Did you "
+ "forget to include the corresponding header file?");
+};
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/map_traits_flat_map.h b/mojo/public/cpp/bindings/map_traits_flat_map.h
new file mode 100644
index 0000000000..9efbabea14
--- /dev/null
+++ b/mojo/public/cpp/bindings/map_traits_flat_map.h
@@ -0,0 +1,56 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_FLAT_MAP_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_FLAT_MAP_H_
+
+#include "base/containers/flat_map.h"
+#include "mojo/public/cpp/bindings/map_traits.h"
+
+namespace mojo {
+
+template <typename K, typename V, typename Compare>
+struct MapTraits<base::flat_map<K, V, Compare>> {
+ using Key = K;
+ using Value = V;
+ using Iterator = typename base::flat_map<K, V, Compare>::iterator;
+ using ConstIterator = typename base::flat_map<K, V, Compare>::const_iterator;
+
+ static size_t GetSize(const base::flat_map<K, V, Compare>& input) {
+ return input.size();
+ }
+
+ static ConstIterator GetBegin(const base::flat_map<K, V, Compare>& input) {
+ return input.begin();
+ }
+ static Iterator GetBegin(base::flat_map<K, V, Compare>& input) {
+ return input.begin();
+ }
+
+ static void AdvanceIterator(ConstIterator& iterator) { iterator++; }
+ static void AdvanceIterator(Iterator& iterator) { iterator++; }
+
+ static const K& GetKey(Iterator& iterator) { return iterator->first; }
+ static const K& GetKey(ConstIterator& iterator) { return iterator->first; }
+
+ static V& GetValue(Iterator& iterator) { return iterator->second; }
+ static const V& GetValue(ConstIterator& iterator) { return iterator->second; }
+
+ template <typename MaybeConstKeyType, typename MaybeConstValueType>
+ static bool Insert(base::flat_map<K, V, Compare>& input,
+ MaybeConstKeyType&& key,
+ MaybeConstValueType&& value) {
+ input.emplace(std::forward<MaybeConstKeyType>(key),
+ std::forward<MaybeConstValueType>(value));
+ return true;
+ }
+
+ static void SetToEmpty(base::flat_map<K, V, Compare>* output) {
+ output->clear();
+ }
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_FLAT_MAP_H_
diff --git a/mojo/public/cpp/bindings/map_traits_stl.h b/mojo/public/cpp/bindings/map_traits_stl.h
index 83a4399ce0..1c4a13e554 100644
--- a/mojo/public/cpp/bindings/map_traits_stl.h
+++ b/mojo/public/cpp/bindings/map_traits_stl.h
@@ -12,29 +12,33 @@
namespace mojo {
-template <typename K, typename V>
-struct MapTraits<std::map<K, V>> {
+template <typename K, typename V, typename Compare>
+struct MapTraits<std::map<K, V, Compare>> {
using Key = K;
using Value = V;
- using Iterator = typename std::map<K, V>::iterator;
- using ConstIterator = typename std::map<K, V>::const_iterator;
+ using Iterator = typename std::map<K, V, Compare>::iterator;
+ using ConstIterator = typename std::map<K, V, Compare>::const_iterator;
- static bool IsNull(const std::map<K, V>& input) {
+ static bool IsNull(const std::map<K, V, Compare>& input) {
// std::map<> is always converted to non-null mojom map.
return false;
}
- static void SetToNull(std::map<K, V>* output) {
+ static void SetToNull(std::map<K, V, Compare>* output) {
// std::map<> doesn't support null state. Set it to empty instead.
output->clear();
}
- static size_t GetSize(const std::map<K, V>& input) { return input.size(); }
+ static size_t GetSize(const std::map<K, V, Compare>& input) {
+ return input.size();
+ }
- static ConstIterator GetBegin(const std::map<K, V>& input) {
+ static ConstIterator GetBegin(const std::map<K, V, Compare>& input) {
+ return input.begin();
+ }
+ static Iterator GetBegin(std::map<K, V, Compare>& input) {
return input.begin();
}
- static Iterator GetBegin(std::map<K, V>& input) { return input.begin(); }
static void AdvanceIterator(ConstIterator& iterator) { iterator++; }
static void AdvanceIterator(Iterator& iterator) { iterator++; }
@@ -45,16 +49,18 @@ struct MapTraits<std::map<K, V>> {
static V& GetValue(Iterator& iterator) { return iterator->second; }
static const V& GetValue(ConstIterator& iterator) { return iterator->second; }
- static bool Insert(std::map<K, V>& input, const K& key, V&& value) {
+ static bool Insert(std::map<K, V, Compare>& input, const K& key, V&& value) {
input.insert(std::make_pair(key, std::forward<V>(value)));
return true;
}
- static bool Insert(std::map<K, V>& input, const K& key, const V& value) {
+ static bool Insert(std::map<K, V, Compare>& input,
+ const K& key,
+ const V& value) {
input.insert(std::make_pair(key, value));
return true;
}
- static void SetToEmpty(std::map<K, V>* output) { output->clear(); }
+ static void SetToEmpty(std::map<K, V, Compare>* output) { output->clear(); }
};
template <typename K, typename V>
diff --git a/mojo/public/cpp/bindings/map_traits_wtf_hash_map.h b/mojo/public/cpp/bindings/map_traits_wtf_hash_map.h
index dd68b3686a..32deab7aae 100644
--- a/mojo/public/cpp/bindings/map_traits_wtf_hash_map.h
+++ b/mojo/public/cpp/bindings/map_traits_wtf_hash_map.h
@@ -7,7 +7,7 @@
#include "base/logging.h"
#include "mojo/public/cpp/bindings/map_traits.h"
-#include "third_party/WebKit/Source/wtf/HashMap.h"
+#include "third_party/blink/renderer/platform/wtf/hash_map.h"
namespace mojo {
@@ -48,7 +48,7 @@ struct MapTraits<WTF::HashMap<K, V>> {
template <typename IK, typename IV>
static bool Insert(WTF::HashMap<K, V>& input, IK&& key, IV&& value) {
- if (!WTF::HashMap<K, V>::isValidKey(key)) {
+ if (!WTF::HashMap<K, V>::IsValidKey(key)) {
LOG(ERROR) << "The key value is disallowed by WTF::HashMap";
return false;
}
diff --git a/mojo/public/cpp/bindings/message.h b/mojo/public/cpp/bindings/message.h
index 48e6900306..7f6e3ea436 100644
--- a/mojo/public/cpp/bindings/message.h
+++ b/mojo/public/cpp/bindings/message.h
@@ -15,10 +15,12 @@
#include "base/callback.h"
#include "base/compiler_specific.h"
+#include "base/component_export.h"
#include "base/logging.h"
-#include "mojo/public/cpp/bindings/bindings_export.h"
-#include "mojo/public/cpp/bindings/lib/message_buffer.h"
+#include "base/memory/ptr_util.h"
+#include "mojo/public/cpp/bindings/lib/buffer.h"
#include "mojo/public/cpp/bindings/lib/message_internal.h"
+#include "mojo/public/cpp/bindings/lib/unserialized_message_context.h"
#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
#include "mojo/public/cpp/system/message.h"
@@ -26,23 +28,58 @@ namespace mojo {
class AssociatedGroupController;
-using ReportBadMessageCallback = base::Callback<void(const std::string& error)>;
+using ReportBadMessageCallback =
+ base::OnceCallback<void(const std::string& error)>;
// Message is a holder for the data and handles to be sent over a MessagePipe.
// Message owns its data and handles, but a consumer of Message is free to
// mutate the data and handles. The message's data is comprised of a header
// followed by payload.
-class MOJO_CPP_BINDINGS_EXPORT Message {
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) Message {
public:
static const uint32_t kFlagExpectsResponse = 1 << 0;
static const uint32_t kFlagIsResponse = 1 << 1;
static const uint32_t kFlagIsSync = 1 << 2;
+ // Constructs an uninitialized Message object.
Message();
+
+ // See the move-assignment operator below.
Message(Message&& other);
+ // Constructs a new message with an unserialized context attached. This
+ // message may be serialized later if necessary.
+ explicit Message(
+ std::unique_ptr<internal::UnserializedMessageContext> context);
+
+ // Constructs a new serialized Message object with optional handles attached.
+ // This message is fully functional and may be exchanged for a
+ // ScopedMessageHandle for transit over a message pipe. See TakeMojoMessage().
+ //
+ // If |handles| is non-null, any handles in |*handles| are attached to the
+ // newly constructed message.
+ //
+ // Note that |payload_size| is only the initially known size of the message
+ // payload, if any. The payload can be expanded after construction using the
+ // interface returned by |payload_buffer()|.
+ Message(uint32_t name,
+ uint32_t flags,
+ size_t payload_size,
+ size_t payload_interface_id_count,
+ std::vector<ScopedHandle>* handles);
+
+ // Constructs a new serialized Message object from an existing
+ // ScopedMessageHandle; e.g., one read from a message pipe.
+ //
+ // If the message had any handles attached, they will be extracted and
+ // retrievable via |handles()|. Such messages may NOT be sent back over
+ // another message pipe, but are otherwise safe to inspect and pass around.
+ Message(ScopedMessageHandle handle);
+
~Message();
+ // Moves |other| into a new Message object. The moved-from Message becomes
+ // invalid and is effectively in a default-constructed state after this call.
Message& operator=(Message&& other);
// Resets the Message to an uninitialized state. Upon reset, the Message
@@ -51,51 +88,47 @@ class MOJO_CPP_BINDINGS_EXPORT Message {
void Reset();
// Indicates whether this Message is uninitialized.
- bool IsNull() const { return !buffer_; }
-
- // Initializes a Message with enough space for |capacity| bytes.
- void Initialize(size_t capacity, bool zero_initialized);
+ bool IsNull() const { return !handle_.is_valid(); }
- // Initializes a Message from an existing Mojo MessageHandle.
- void InitializeFromMojoMessage(ScopedMessageHandle message,
- uint32_t num_bytes,
- std::vector<Handle>* handles);
-
- uint32_t data_num_bytes() const {
- return static_cast<uint32_t>(buffer_->size());
- }
+ // Indicates whether this Message is serialized.
+ bool is_serialized() const { return serialized_; }
// Access the raw bytes of the message.
const uint8_t* data() const {
- return static_cast<const uint8_t*>(buffer_->data());
+ DCHECK(payload_buffer_.is_valid());
+ return static_cast<const uint8_t*>(payload_buffer_.data());
}
+ uint8_t* mutable_data() { return const_cast<uint8_t*>(data()); }
- uint8_t* mutable_data() { return static_cast<uint8_t*>(buffer_->data()); }
+ size_t data_num_bytes() const {
+ DCHECK(payload_buffer_.is_valid());
+ return payload_buffer_.cursor();
+ }
// Access the header.
const internal::MessageHeader* header() const {
- return static_cast<const internal::MessageHeader*>(buffer_->data());
+ return reinterpret_cast<const internal::MessageHeader*>(data());
}
internal::MessageHeader* header() {
- return static_cast<internal::MessageHeader*>(buffer_->data());
+ return reinterpret_cast<internal::MessageHeader*>(mutable_data());
}
const internal::MessageHeaderV1* header_v1() const {
DCHECK_GE(version(), 1u);
- return static_cast<const internal::MessageHeaderV1*>(buffer_->data());
+ return reinterpret_cast<const internal::MessageHeaderV1*>(data());
}
internal::MessageHeaderV1* header_v1() {
DCHECK_GE(version(), 1u);
- return static_cast<internal::MessageHeaderV1*>(buffer_->data());
+ return reinterpret_cast<internal::MessageHeaderV1*>(mutable_data());
}
const internal::MessageHeaderV2* header_v2() const {
DCHECK_GE(version(), 2u);
- return static_cast<const internal::MessageHeaderV2*>(buffer_->data());
+ return reinterpret_cast<const internal::MessageHeaderV2*>(data());
}
internal::MessageHeaderV2* header_v2() {
DCHECK_GE(version(), 2u);
- return static_cast<internal::MessageHeaderV2*>(buffer_->data());
+ return reinterpret_cast<internal::MessageHeaderV2*>(mutable_data());
}
uint32_t version() const { return header()->version; }
@@ -120,9 +153,12 @@ class MOJO_CPP_BINDINGS_EXPORT Message {
uint32_t payload_num_interface_ids() const;
const uint32_t* payload_interface_ids() const;
- // Access the handles.
- const std::vector<Handle>* handles() const { return &handles_; }
- std::vector<Handle>* mutable_handles() { return &handles_; }
+ internal::Buffer* payload_buffer() { return &payload_buffer_; }
+
+ // Access the handles of a received message. Note that these are unused on
+ // outgoing messages.
+ const std::vector<ScopedHandle>* handles() const { return &handles_; }
+ std::vector<ScopedHandle>* mutable_handles() { return &handles_; }
const std::vector<ScopedInterfaceEndpointHandle>*
associated_endpoint_handles() const {
@@ -133,8 +169,10 @@ class MOJO_CPP_BINDINGS_EXPORT Message {
return &associated_endpoint_handles_;
}
- // Access the underlying Buffer interface.
- internal::Buffer* buffer() { return buffer_.get(); }
+ // Takes ownership of any handles within |*context| and attaches them to this
+ // Message.
+ void AttachHandlesFromSerializationContext(
+ internal::SerializationContext* context);
// Takes a scoped MessageHandle which may be passed to |WriteMessageNew()| for
// transmission. Note that this invalidates this Message object, taking
@@ -155,20 +193,68 @@ class MOJO_CPP_BINDINGS_EXPORT Message {
bool DeserializeAssociatedEndpointHandles(
AssociatedGroupController* group_controller);
+ // If this Message has an unserialized message context attached, force it to
+ // be serialized immediately. Otherwise this does nothing.
+ void SerializeIfNecessary();
+
+ // Takes the unserialized message context from this Message if its tag matches
+ // |tag|.
+ std::unique_ptr<internal::UnserializedMessageContext> TakeUnserializedContext(
+ const internal::UnserializedMessageContext::Tag* tag);
+
+ template <typename MessageType>
+ std::unique_ptr<MessageType> TakeUnserializedContext() {
+ auto generic_context = TakeUnserializedContext(&MessageType::kMessageTag);
+ if (!generic_context)
+ return nullptr;
+ return base::WrapUnique(
+ generic_context.release()->template SafeCast<MessageType>());
+ }
+
+#if defined(ENABLE_IPC_FUZZER)
+ const char* interface_name() const { return interface_name_; }
+ void set_interface_name(const char* interface_name) {
+ interface_name_ = interface_name;
+ }
+
+ const char* method_name() const { return method_name_; }
+ void set_method_name(const char* method_name) { method_name_ = method_name; }
+#endif
+
private:
- void CloseHandles();
+ ScopedMessageHandle handle_;
+
+ // A Buffer which may be used to allocate blocks of data within the message
+ // payload for reading or writing.
+ internal::Buffer payload_buffer_;
- std::unique_ptr<internal::MessageBuffer> buffer_;
- std::vector<Handle> handles_;
+ std::vector<ScopedHandle> handles_;
std::vector<ScopedInterfaceEndpointHandle> associated_endpoint_handles_;
+ // Indicates whether this Message object is transferable, i.e. can be sent
+ // elsewhere. In general this is true unless |handle_| is invalid or
+ // serialized handles have been extracted from the serialized message object
+ // identified by |handle_|.
+ bool transferable_ = false;
+
+ // Indicates whether this Message object is serialized.
+ bool serialized_ = false;
+
+#if defined(ENABLE_IPC_FUZZER)
+ const char* interface_name_ = nullptr;
+ const char* method_name_ = nullptr;
+#endif
+
DISALLOW_COPY_AND_ASSIGN(Message);
};
-class MessageReceiver {
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) MessageReceiver {
public:
virtual ~MessageReceiver() {}
+ // Indicates whether the receiver prefers to receive serialized messages.
+ virtual bool PrefersSerializedMessages();
+
// The receiver may mutate the given message. Returns true if the message
// was accepted and false otherwise, indicating that the message was invalid
// or malformed.
@@ -197,12 +283,13 @@ class MessageReceiverWithStatus : public MessageReceiver {
// Returns |true| if this MessageReceiver is currently bound to a MessagePipe,
// the pipe has not been closed, and the pipe has not encountered an error.
- virtual bool IsValid() = 0;
+ virtual bool IsConnected() = 0;
- // DCHECKs if this MessageReceiver is currently bound to a MessagePipe, the
- // pipe has not been closed, and the pipe has not encountered an error.
- // This function may be called on any thread.
- virtual void DCheckInvalid(const std::string& message) = 0;
+ // Determines if this MessageReceiver is still bound to a message pipe and has
+ // not encountered any errors. This is asynchronous but may be called from any
+ // sequence. |callback| is eventually invoked from an arbitrary sequence with
+ // the result of the query.
+ virtual void IsConnectedAsync(base::OnceCallback<void(bool)> callback) = 0;
};
// An alternative to MessageReceiverWithResponder for cases in which it
@@ -221,8 +308,8 @@ class MessageReceiverWithResponderStatus : public MessageReceiver {
responder) WARN_UNUSED_RESULT = 0;
};
-class MOJO_CPP_BINDINGS_EXPORT PassThroughFilter
- : NON_EXPORTED_BASE(public MessageReceiver) {
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) PassThroughFilter
+ : public MessageReceiver {
public:
PassThroughFilter();
~PassThroughFilter() override;
@@ -251,7 +338,7 @@ class SyncMessageResponseSetup;
// if (response_value.IsBad())
// response_context.ReportBadMessage("Bad response_value!");
//
-class MOJO_CPP_BINDINGS_EXPORT SyncMessageResponseContext {
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) SyncMessageResponseContext {
public:
SyncMessageResponseContext();
~SyncMessageResponseContext();
@@ -260,14 +347,13 @@ class MOJO_CPP_BINDINGS_EXPORT SyncMessageResponseContext {
void ReportBadMessage(const std::string& error);
- const ReportBadMessageCallback& GetBadMessageCallback();
+ ReportBadMessageCallback GetBadMessageCallback();
private:
friend class internal::SyncMessageResponseSetup;
SyncMessageResponseContext* outer_context_;
Message response_;
- ReportBadMessageCallback bad_message_callback_;
DISALLOW_COPY_AND_ASSIGN(SyncMessageResponseContext);
};
@@ -279,14 +365,15 @@ class MOJO_CPP_BINDINGS_EXPORT SyncMessageResponseContext {
// dispatched, otherwise returns an error code if something went wrong.
//
// NOTE: The message hasn't been validated and may be malformed!
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
MojoResult ReadMessage(MessagePipeHandle handle, Message* message);
// Reports the currently dispatching Message as bad. Note that this is only
// legal to call from directly within the stack frame of a message dispatch. If
// you need to do asynchronous work before you can determine the legitimacy of
-// a message, use TakeBadMessageCallback() and retain its result until you're
+// a message, use GetBadMessageCallback() and retain its result until you're
// ready to invoke or discard it.
-MOJO_CPP_BINDINGS_EXPORT
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
void ReportBadMessage(const std::string& error);
// Acquires a callback which may be run to report the currently dispatching
@@ -294,7 +381,7 @@ void ReportBadMessage(const std::string& error);
// stack frame of a message dispatch, but the returned callback may be called
// exactly once any time thereafter to report the message as bad. This may only
// be called once per message.
-MOJO_CPP_BINDINGS_EXPORT
+COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
ReportBadMessageCallback GetBadMessageCallback();
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/message_dumper.h b/mojo/public/cpp/bindings/message_dumper.h
new file mode 100644
index 0000000000..44cf384ab0
--- /dev/null
+++ b/mojo/public/cpp/bindings/message_dumper.h
@@ -0,0 +1,43 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_DUMPER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_DUMPER_H_
+
+#include "base/files/file_path.h"
+#include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/cpp/bindings/message_dumper.h"
+
+namespace mojo {
+
+class MessageDumper : public mojo::MessageReceiver {
+ public:
+ MessageDumper();
+ ~MessageDumper() override;
+
+ bool Accept(mojo::Message* message) override;
+
+ struct MessageEntry {
+ MessageEntry(const uint8_t* data,
+ uint32_t data_size,
+ const char* interface_name,
+ const char* method_name);
+ MessageEntry(const MessageEntry& entry);
+ ~MessageEntry();
+
+ const char* interface_name;
+ const char* method_name;
+ std::vector<uint8_t> data_bytes;
+ };
+
+ static void SetMessageDumpDirectory(const base::FilePath& directory);
+ static const base::FilePath& GetMessageDumpDirectory();
+
+ private:
+ uint32_t identifier_;
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_DUMPER_H_
diff --git a/mojo/public/cpp/bindings/message_header_validator.h b/mojo/public/cpp/bindings/message_header_validator.h
index 50c19dbe04..621d14fdec 100644
--- a/mojo/public/cpp/bindings/message_header_validator.h
+++ b/mojo/public/cpp/bindings/message_header_validator.h
@@ -6,13 +6,13 @@
#define MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_HEADER_VALIDATOR_H_
#include "base/compiler_specific.h"
-#include "mojo/public/cpp/bindings/bindings_export.h"
+#include "base/component_export.h"
#include "mojo/public/cpp/bindings/message.h"
namespace mojo {
-class MOJO_CPP_BINDINGS_EXPORT MessageHeaderValidator
- : NON_EXPORTED_BASE(public MessageReceiver) {
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) MessageHeaderValidator
+ : public MessageReceiver {
public:
MessageHeaderValidator();
explicit MessageHeaderValidator(const std::string& description);
diff --git a/mojo/public/cpp/bindings/mojo_buildflags.h b/mojo/public/cpp/bindings/mojo_buildflags.h
new file mode 100644
index 0000000000..fb646fc92e
--- /dev/null
+++ b/mojo/public/cpp/bindings/mojo_buildflags.h
@@ -0,0 +1,6 @@
+#ifndef CPP_MOJO_BUILD_FLAGS_H_
+#define CPP_MOJO_BUILD_FLAGS_H_
+
+#include <build/buildflag.h>
+#define BUILDFLAG_INTERNAL_MOJO_TRACE_ENABLED() (0)
+#endif // CPP_MOJO_BUILD_FLAGS_H_
diff --git a/mojo/public/cpp/bindings/native_struct.h b/mojo/public/cpp/bindings/native_struct.h
deleted file mode 100644
index ac27250bcc..0000000000
--- a/mojo/public/cpp/bindings/native_struct.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 MOJO_PUBLIC_CPP_BINDINGS_NATIVE_STRUCT_H_
-#define MOJO_PUBLIC_CPP_BINDINGS_NATIVE_STRUCT_H_
-
-#include <vector>
-
-#include "base/optional.h"
-#include "mojo/public/cpp/bindings/bindings_export.h"
-#include "mojo/public/cpp/bindings/lib/native_struct_data.h"
-#include "mojo/public/cpp/bindings/struct_ptr.h"
-#include "mojo/public/cpp/bindings/type_converter.h"
-
-namespace mojo {
-
-class NativeStruct;
-using NativeStructPtr = StructPtr<NativeStruct>;
-
-// Native-only structs correspond to "[Native] struct Foo;" definitions in
-// mojom.
-class MOJO_CPP_BINDINGS_EXPORT NativeStruct {
- public:
- using Data_ = internal::NativeStruct_Data;
-
- static NativeStructPtr New();
-
- template <typename U>
- static NativeStructPtr From(const U& u) {
- return TypeConverter<NativeStructPtr, U>::Convert(u);
- }
-
- template <typename U>
- U To() const {
- return TypeConverter<U, NativeStruct>::Convert(*this);
- }
-
- NativeStruct();
- ~NativeStruct();
-
- NativeStructPtr Clone() const;
- bool Equals(const NativeStruct& other) const;
- size_t Hash(size_t seed) const;
-
- base::Optional<std::vector<uint8_t>> data;
-};
-
-} // namespace mojo
-
-#endif // MOJO_PUBLIC_CPP_BINDINGS_NATIVE_STRUCT_H_
diff --git a/mojo/public/cpp/bindings/native_struct_data_view.h b/mojo/public/cpp/bindings/native_struct_data_view.h
deleted file mode 100644
index 613bd7a0b0..0000000000
--- a/mojo/public/cpp/bindings/native_struct_data_view.h
+++ /dev/null
@@ -1,36 +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 MOJO_PUBLIC_CPP_BINDINGS_NATIVE_STRUCT_DATA_VIEW_H_
-#define MOJO_PUBLIC_CPP_BINDINGS_NATIVE_STRUCT_DATA_VIEW_H_
-
-#include "mojo/public/cpp/bindings/lib/native_struct_data.h"
-#include "mojo/public/cpp/bindings/lib/serialization_context.h"
-
-namespace mojo {
-
-class NativeStructDataView {
- public:
- using Data_ = internal::NativeStruct_Data;
-
- NativeStructDataView() {}
-
- NativeStructDataView(Data_* data, internal::SerializationContext* context)
- : data_(data) {}
-
- bool is_null() const { return !data_; }
-
- size_t size() const { return data_->data.size(); }
-
- uint8_t operator[](size_t index) const { return data_->data.at(index); }
-
- const uint8_t* data() const { return data_->data.storage(); }
-
- private:
- Data_* data_ = nullptr;
-};
-
-} // namespace mojo
-
-#endif // MOJO_PUBLIC_CPP_BINDINGS_NATIVE_STRUCT_DATA_VIEW_H_
diff --git a/mojo/public/cpp/bindings/pipe_control_message_handler.h b/mojo/public/cpp/bindings/pipe_control_message_handler.h
index a5c04da627..071d9fe46d 100644
--- a/mojo/public/cpp/bindings/pipe_control_message_handler.h
+++ b/mojo/public/cpp/bindings/pipe_control_message_handler.h
@@ -18,7 +18,7 @@ class PipeControlMessageHandlerDelegate;
// Handler for messages defined in pipe_control_messages.mojom.
class MOJO_CPP_BINDINGS_EXPORT PipeControlMessageHandler
- : NON_EXPORTED_BASE(public MessageReceiver) {
+ : public MessageReceiver {
public:
explicit PipeControlMessageHandler(
PipeControlMessageHandlerDelegate* delegate);
diff --git a/mojo/public/cpp/bindings/pipe_control_message_proxy.h b/mojo/public/cpp/bindings/pipe_control_message_proxy.h
index 52c408f827..f57f039a4e 100644
--- a/mojo/public/cpp/bindings/pipe_control_message_proxy.h
+++ b/mojo/public/cpp/bindings/pipe_control_message_proxy.h
@@ -19,11 +19,11 @@ class MessageReceiver;
// Proxy for request messages defined in pipe_control_messages.mojom.
//
-// NOTE: This object may be used from multiple threads.
+// NOTE: This object may be used from multiple sequences.
class MOJO_CPP_BINDINGS_EXPORT PipeControlMessageProxy {
public:
// Doesn't take ownership of |receiver|. If This PipeControlMessageProxy will
- // be used from multiple threads, |receiver| must be thread-safe.
+ // be used from multiple sequences, |receiver| must be thread-safe.
explicit PipeControlMessageProxy(MessageReceiver* receiver);
void NotifyPeerEndpointClosed(InterfaceId id,
diff --git a/mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h b/mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h
index 16527cf747..0637d49755 100644
--- a/mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h
+++ b/mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h
@@ -8,12 +8,12 @@
#include <string>
#include "base/callback.h"
+#include "base/component_export.h"
#include "base/macros.h"
#include "base/memory/ref_counted.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/bindings_export.h"
#include "mojo/public/cpp/bindings/disconnect_reason.h"
#include "mojo/public/cpp/bindings/interface_id.h"
@@ -24,8 +24,8 @@ class AssociatedGroupController;
// ScopedInterfaceEndpointHandle refers to one end of an interface, either the
// implementation side or the client side.
// Threading: At any given time, a ScopedInterfaceEndpointHandle should only
-// be accessed from a single thread.
-class MOJO_CPP_BINDINGS_EXPORT ScopedInterfaceEndpointHandle {
+// be accessed from a single sequence.
+class COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE) ScopedInterfaceEndpointHandle {
public:
// Creates a pair of handles representing the two endpoints of an interface,
// which are not yet associated with a message pipe.
@@ -69,7 +69,7 @@ class MOJO_CPP_BINDINGS_EXPORT ScopedInterfaceEndpointHandle {
using AssociationEventCallback = base::OnceCallback<void(AssociationEvent)>;
// Note:
// - |handler| won't run if the handle is invalid. Otherwise, |handler| is run
- // on the calling thread asynchronously, even if the interface has already
+ // on the calling sequence asynchronously, even if the interface has already
// been associated or the peer has been closed before association.
// - |handler| won't be called after this object is destroyed or reset.
// - A null |handler| can be used to cancel the previous callback.
@@ -98,8 +98,8 @@ class MOJO_CPP_BINDINGS_EXPORT ScopedInterfaceEndpointHandle {
void ResetInternal(const base::Optional<DisconnectReason>& reason);
// Used by AssociatedGroup.
- // It is safe to run the returned callback on any thread, or after this handle
- // is destroyed.
+ // It is safe to run the returned callback on any sequence, or after this
+ // handle is destroyed.
// The return value of the getter:
// - If the getter is retrieved when the handle is invalid, the return value
// of the getter will always be null.
diff --git a/mojo/public/cpp/bindings/sequence_local_sync_event_watcher.h b/mojo/public/cpp/bindings/sequence_local_sync_event_watcher.h
new file mode 100644
index 0000000000..ad50bde436
--- /dev/null
+++ b/mojo/public/cpp/bindings/sequence_local_sync_event_watcher.h
@@ -0,0 +1,69 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_BINDINGS_SEQUENCE_LOCAL_SYNC_EVENT_WATCHER_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_SEQUENCE_LOCAL_SYNC_EVENT_WATCHER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/public/cpp/bindings/bindings_export.h"
+
+namespace mojo {
+
+// This encapsulates a SyncEventWatcher watching an event shared by all
+// |SequenceLocalSyncEventWatcher| on the same sequence. This class is NOT
+// sequence-safe in general, but |SignalEvent()| is safe to call from any
+// sequence.
+//
+// Interfaces which support sync messages use a WaitableEvent to block and
+// be signaled when messages are available, but having a WaitableEvent for every
+// such interface endpoint would cause the number of WaitableEvents to grow
+// arbitrarily large.
+//
+// Some platform constraints may limit the number of WaitableEvents the bindings
+// layer can wait upon concurrently, so this type is used to keep the number
+// of such events fixed at a small constant value per sequence regardless of the
+// number of active interface endpoints supporting sync messages on that
+// sequence.
+class MOJO_CPP_BINDINGS_EXPORT SequenceLocalSyncEventWatcher {
+ public:
+ explicit SequenceLocalSyncEventWatcher(
+ const base::RepeatingClosure& callback);
+ ~SequenceLocalSyncEventWatcher();
+
+ // Signals the shared event on behalf of this specific watcher. Safe to call
+ // from any sequence.
+ void SignalEvent();
+
+ // Resets the shared event on behalf of this specific watcher.
+ void ResetEvent();
+
+ // Allows this watcher to be notified during sync wait operations invoked by
+ // other watchers (for example, other SequenceLocalSyncEventWatchers calling
+ // |SyncWatch()|) on the same sequence.
+ void AllowWokenUpBySyncWatchOnSameSequence();
+
+ // Blocks the calling sequence until the shared event is signaled on behalf of
+ // this specific watcher (i.e. until someone calls |SignalEvent()| on |this|).
+ // Behaves similarly to SyncEventWatcher and SyncHandleWatcher, returning
+ // |true| when |*should_stop| is set to |true|, or |false| if some other
+ // (e.g. error) event interrupts the wait.
+ bool SyncWatch(const bool* should_stop);
+
+ private:
+ class Registration;
+ class SequenceLocalState;
+ friend class SequenceLocalState;
+
+ const std::unique_ptr<Registration> registration_;
+ const base::RepeatingClosure callback_;
+ bool can_wake_up_during_any_watch_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(SequenceLocalSyncEventWatcher);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_SEQUENCE_LOCAL_SYNC_EVENT_WATCHER_H_
diff --git a/mojo/public/cpp/bindings/string_traits.h b/mojo/public/cpp/bindings/string_traits.h
index 7d3075a579..165c9fa8cd 100644
--- a/mojo/public/cpp/bindings/string_traits.h
+++ b/mojo/public/cpp/bindings/string_traits.h
@@ -5,7 +5,7 @@
#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_H_
#define MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_H_
-#include "mojo/public/cpp/bindings/string_data_view.h"
+#include "mojo/public/cpp/bindings/lib/template_util.h"
namespace mojo {
@@ -47,7 +47,11 @@ namespace mojo {
// so that you can do any necessary cleanup.
//
template <typename T>
-struct StringTraits;
+struct StringTraits {
+ static_assert(internal::AlwaysFalse<T>::value,
+ "Cannot find the mojo::StringTraits specialization. Did you "
+ "forget to include the corresponding header file?");
+};
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/string_traits_string16.h b/mojo/public/cpp/bindings/string_traits_string16.h
deleted file mode 100644
index f96973ad91..0000000000
--- a/mojo/public/cpp/bindings/string_traits_string16.h
+++ /dev/null
@@ -1,37 +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 MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STRING16_H_
-#define MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STRING16_H_
-
-#include "base/strings/string16.h"
-#include "mojo/public/cpp/bindings/bindings_export.h"
-#include "mojo/public/cpp/bindings/string_traits.h"
-
-namespace mojo {
-
-template <>
-struct MOJO_CPP_BINDINGS_EXPORT StringTraits<base::string16> {
- static bool IsNull(const base::string16& input) {
- // base::string16 is always converted to non-null mojom string.
- return false;
- }
-
- static void SetToNull(base::string16* output) {
- // Convert null to an "empty" base::string16.
- output->clear();
- }
-
- static void* SetUpContext(const base::string16& input);
- static void TearDownContext(const base::string16& input, void* context);
-
- static size_t GetSize(const base::string16& input, void* context);
- static const char* GetData(const base::string16& input, void* context);
-
- static bool Read(StringDataView input, base::string16* output);
-};
-
-} // namespace mojo
-
-#endif // MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STRING16_H_
diff --git a/mojo/public/cpp/bindings/string_traits_wtf.h b/mojo/public/cpp/bindings/string_traits_wtf.h
index 238c2eb119..51601d1ab8 100644
--- a/mojo/public/cpp/bindings/string_traits_wtf.h
+++ b/mojo/public/cpp/bindings/string_traits_wtf.h
@@ -7,13 +7,13 @@
#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
#include "mojo/public/cpp/bindings/string_traits.h"
-#include "third_party/WebKit/Source/wtf/text/WTFString.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace mojo {
template <>
struct StringTraits<WTF::String> {
- static bool IsNull(const WTF::String& input) { return input.isNull(); }
+ static bool IsNull(const WTF::String& input) { return input.IsNull(); }
static void SetToNull(WTF::String* output);
static void* SetUpContext(const WTF::String& input);
diff --git a/mojo/public/cpp/bindings/strong_associated_binding.h b/mojo/public/cpp/bindings/strong_associated_binding.h
index a1e299bb2d..fffdeb4d62 100644
--- a/mojo/public/cpp/bindings/strong_associated_binding.h
+++ b/mojo/public/cpp/bindings/strong_associated_binding.h
@@ -37,12 +37,15 @@ using StrongAssociatedBindingPtr =
// To use, call StrongAssociatedBinding<T>::Create() (see below) or the helper
// MakeStrongAssociatedBinding function:
//
-// mojo::MakeStrongAssociatedBinding(base::MakeUnique<FooImpl>(),
+// mojo::MakeStrongAssociatedBinding(std::make_unique<FooImpl>(),
// std::move(foo_request));
//
template <typename Interface>
class StrongAssociatedBinding {
public:
+ using ImplPointerType =
+ typename AssociatedBinding<Interface>::ImplPointerType;
+
// Create a new StrongAssociatedBinding instance. The instance owns itself,
// cleaning up only in the event of a pipe connection error. Returns a WeakPtr
// to the new StrongAssociatedBinding instance.
@@ -58,16 +61,16 @@ class StrongAssociatedBinding {
//
// This method may only be called after this StrongAssociatedBinding has been
// bound to a message pipe.
- void set_connection_error_handler(const base::Closure& error_handler) {
+ void set_connection_error_handler(base::OnceClosure error_handler) {
DCHECK(binding_.is_bound());
- connection_error_handler_ = error_handler;
+ connection_error_handler_ = std::move(error_handler);
connection_error_with_reason_handler_.Reset();
}
void set_connection_error_with_reason_handler(
- const ConnectionErrorWithReasonCallback& error_handler) {
+ ConnectionErrorWithReasonCallback error_handler) {
DCHECK(binding_.is_bound());
- connection_error_with_reason_handler_ = error_handler;
+ connection_error_with_reason_handler_ = std::move(error_handler);
connection_error_handler_.Reset();
}
@@ -82,6 +85,11 @@ class StrongAssociatedBinding {
// stimulus.
void FlushForTesting() { binding_.FlushForTesting(); }
+ // Allows test code to swap the interface implementation.
+ ImplPointerType SwapImplForTesting(ImplPointerType new_impl) {
+ return binding_.SwapImplForTesting(new_impl);
+ }
+
private:
StrongAssociatedBinding(std::unique_ptr<Interface> impl,
AssociatedInterfaceRequest<Interface> request)
@@ -96,15 +104,17 @@ class StrongAssociatedBinding {
void OnConnectionError(uint32_t custom_reason,
const std::string& description) {
- if (!connection_error_handler_.is_null())
- connection_error_handler_.Run();
- else if (!connection_error_with_reason_handler_.is_null())
- connection_error_with_reason_handler_.Run(custom_reason, description);
+ if (connection_error_handler_) {
+ std::move(connection_error_handler_).Run();
+ } else if (connection_error_with_reason_handler_) {
+ std::move(connection_error_with_reason_handler_)
+ .Run(custom_reason, description);
+ }
Close();
}
std::unique_ptr<Interface> impl_;
- base::Closure connection_error_handler_;
+ base::OnceClosure connection_error_handler_;
ConnectionErrorWithReasonCallback connection_error_with_reason_handler_;
AssociatedBinding<Interface> binding_;
base::WeakPtrFactory<StrongAssociatedBinding> weak_factory_;
diff --git a/mojo/public/cpp/bindings/strong_associated_binding_set.h b/mojo/public/cpp/bindings/strong_associated_binding_set.h
new file mode 100644
index 0000000000..8c769698ba
--- /dev/null
+++ b/mojo/public/cpp/bindings/strong_associated_binding_set.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_STRONG_ASSOCIATED_BINDING_SET_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_STRONG_ASSOCIATED_BINDING_SET_H_
+
+#include "mojo/public/cpp/bindings/associated_binding.h"
+#include "mojo/public/cpp/bindings/associated_binding_set.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"
+#include "mojo/public/cpp/bindings/unique_ptr_impl_ref_traits.h"
+
+namespace mojo {
+
+template <typename Interface, typename ContextType = void>
+using StrongAssociatedBindingSet = BindingSetBase<
+ Interface,
+ AssociatedBinding<Interface, UniquePtrImplRefTraits<Interface>>,
+ ContextType>;
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_STRONG_ASSOCIATED_BINDING_SET_H_
diff --git a/mojo/public/cpp/bindings/strong_binding.h b/mojo/public/cpp/bindings/strong_binding.h
index f4b4a061cd..40c67e7834 100644
--- a/mojo/public/cpp/bindings/strong_binding.h
+++ b/mojo/public/cpp/bindings/strong_binding.h
@@ -38,7 +38,7 @@ using StrongBindingPtr = base::WeakPtr<StrongBinding<Interface>>;
// To use, call StrongBinding<T>::Create() (see below) or the helper
// MakeStrongBinding function:
//
-// mojo::MakeStrongBinding(base::MakeUnique<FooImpl>(),
+// mojo::MakeStrongBinding(std::make_unique<FooImpl>(),
// std::move(foo_request));
//
template <typename Interface>
@@ -59,19 +59,34 @@ class StrongBinding {
//
// This method may only be called after this StrongBinding has been bound to a
// message pipe.
- void set_connection_error_handler(const base::Closure& error_handler) {
+ void set_connection_error_handler(base::OnceClosure error_handler) {
DCHECK(binding_.is_bound());
- connection_error_handler_ = error_handler;
+ connection_error_handler_ = std::move(error_handler);
connection_error_with_reason_handler_.Reset();
}
void set_connection_error_with_reason_handler(
- const ConnectionErrorWithReasonCallback& error_handler) {
+ ConnectionErrorWithReasonCallback error_handler) {
DCHECK(binding_.is_bound());
- connection_error_with_reason_handler_ = error_handler;
+ connection_error_with_reason_handler_ = std::move(error_handler);
connection_error_handler_.Reset();
}
+ // Stops processing incoming messages until
+ // ResumeIncomingMethodCallProcessing().
+ // 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() {
+ binding_.PauseIncomingMethodCallProcessing();
+ }
+ void ResumeIncomingMethodCallProcessing() {
+ binding_.ResumeIncomingMethodCallProcessing();
+ }
+
// Forces the binding to close. This destroys the StrongBinding instance.
void Close() { delete this; }
@@ -97,15 +112,17 @@ class StrongBinding {
void OnConnectionError(uint32_t custom_reason,
const std::string& description) {
- if (!connection_error_handler_.is_null())
- connection_error_handler_.Run();
- else if (!connection_error_with_reason_handler_.is_null())
- connection_error_with_reason_handler_.Run(custom_reason, description);
+ if (connection_error_handler_) {
+ std::move(connection_error_handler_).Run();
+ } else if (connection_error_with_reason_handler_) {
+ std::move(connection_error_with_reason_handler_)
+ .Run(custom_reason, description);
+ }
Close();
}
std::unique_ptr<Interface> impl_;
- base::Closure connection_error_handler_;
+ base::OnceClosure connection_error_handler_;
ConnectionErrorWithReasonCallback connection_error_with_reason_handler_;
Binding<Interface> binding_;
base::WeakPtrFactory<StrongBinding> weak_factory_;
diff --git a/mojo/public/cpp/bindings/strong_binding_set.h b/mojo/public/cpp/bindings/strong_binding_set.h
index f6bcd5259c..3f855f428e 100644
--- a/mojo/public/cpp/bindings/strong_binding_set.h
+++ b/mojo/public/cpp/bindings/strong_binding_set.h
@@ -15,11 +15,13 @@ namespace mojo {
// set, and the interface implementation is deleted. When the StrongBindingSet
// is destructed, all outstanding bindings in the set are destroyed and all the
// bound interface implementations are automatically deleted.
-template <typename Interface, typename ContextType = void>
-using StrongBindingSet =
- BindingSetBase<Interface,
- Binding<Interface, UniquePtrImplRefTraits<Interface>>,
- ContextType>;
+template <typename Interface,
+ typename ContextType = void,
+ typename Deleter = std::default_delete<Interface>>
+using StrongBindingSet = BindingSetBase<
+ Interface,
+ Binding<Interface, UniquePtrImplRefTraits<Interface, Deleter>>,
+ ContextType>;
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/struct_ptr.h b/mojo/public/cpp/bindings/struct_ptr.h
index b135312e39..5c88f5a39e 100644
--- a/mojo/public/cpp/bindings/struct_ptr.h
+++ b/mojo/public/cpp/bindings/struct_ptr.h
@@ -81,7 +81,8 @@ class StructPtr {
StructPtr Clone() const { return is_null() ? StructPtr() : ptr_->Clone(); }
// Compares the pointees (which might both be null).
- // TODO(tibell): Get rid of Equals in favor of the operator. Same for Hash.
+ // TODO(crbug.com/735302): Get rid of Equals in favor of the operator. Same
+ // for Hash.
bool Equals(const StructPtr& other) const {
if (is_null() || other.is_null())
return is_null() && other.is_null();
@@ -97,6 +98,10 @@ class StructPtr {
explicit operator bool() const { return !is_null(); }
+ bool operator<(const StructPtr& other) const {
+ return Hash(internal::kHashSeed) < other.Hash(internal::kHashSeed);
+ }
+
private:
friend class internal::StructPtrWTFHelper<Struct>;
void Take(StructPtr* other) {
@@ -192,6 +197,10 @@ class InlinedStructPtr {
explicit operator bool() const { return !is_null(); }
+ bool operator<(const InlinedStructPtr& other) const {
+ return Hash(internal::kHashSeed) < other.Hash(internal::kHashSeed);
+ }
+
private:
friend class internal::InlinedStructPtrWTFHelper<Struct>;
void Take(InlinedStructPtr* other) {
diff --git a/mojo/public/cpp/bindings/struct_traits.h b/mojo/public/cpp/bindings/struct_traits.h
index 6cc070fc48..ff86f4219d 100644
--- a/mojo/public/cpp/bindings/struct_traits.h
+++ b/mojo/public/cpp/bindings/struct_traits.h
@@ -5,6 +5,8 @@
#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRUCT_TRAITS_H_
#define MOJO_PUBLIC_CPP_BINDINGS_STRUCT_TRAITS_H_
+#include "mojo/public/cpp/bindings/lib/template_util.h"
+
namespace mojo {
// This must be specialized for any type |T| to be serialized/deserialized as
@@ -37,7 +39,7 @@ namespace mojo {
//
// - map:
// Value or reference of any type that has a MapTraits defined.
-// Supported by default: std::map, std::unordered_map,
+// Supported by default: std::map, std::unordered_map, base::flat_map,
// WTF::HashMap (in blink).
//
// - struct:
@@ -47,16 +49,13 @@ namespace mojo {
// Value of any type that has an EnumTraits defined.
//
// For any nullable string/struct/array/map/union field you could also
-// return value or reference of base::Optional<T>/WTF::Optional<T>, if T
-// has the right *Traits defined.
-//
-// During serialization, getters for string/struct/array/map/union fields
-// are called twice (one for size calculation and one for actual
-// serialization). If you want to return a value (as opposed to a
-// reference) from these getters, you have to be sure that constructing and
-// copying the returned object is really cheap.
+// return value or reference of base::Optional<T>, if T has the right
+// *Traits defined.
//
-// Getters for fields of other types are called once.
+// During serialization, getters for all fields are called exactly once. It
+// is therefore reasonably effecient for a getter to construct and return
+// temporary value in the event that it cannot return a readily
+// serializable reference to some existing object.
//
// 2. A static Read() method to set the contents of a |T| instance from a
// DataViewType.
@@ -76,9 +75,10 @@ namespace mojo {
//
// static bool IsNull(const T& input);
//
-// If this method returns true, it is guaranteed that none of the getters
-// (described in section 1) will be called for the same |input|. So you
-// don't have to check whether |input| is null in those getters.
+// This method is called exactly once during serialization, and if it
+// returns |true|, it is guaranteed that none of the getters (described in
+// section 1) will be called for the same |input|. So you don't have to
+// check whether |input| is null in those getters.
//
// If it is not defined, |T| instances are always considered non-null.
//
@@ -158,7 +158,11 @@ namespace mojo {
// };
//
template <typename DataViewType, typename T>
-struct StructTraits;
+struct StructTraits {
+ static_assert(internal::AlwaysFalse<T>::value,
+ "Cannot find the mojo::StructTraits specialization. Did you "
+ "forget to include the corresponding header file?");
+};
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/sync_call_restrictions.h b/mojo/public/cpp/bindings/sync_call_restrictions.h
index 5529042784..e72cb1d3cb 100644
--- a/mojo/public/cpp/bindings/sync_call_restrictions.h
+++ b/mojo/public/cpp/bindings/sync_call_restrictions.h
@@ -15,6 +15,20 @@
#define ENABLE_SYNC_CALL_RESTRICTIONS 0
#endif
+class ChromeSelectFileDialogFactory;
+
+namespace sync_preferences {
+class PrefServiceSyncable;
+}
+
+namespace content {
+class BlinkTestController;
+}
+
+namespace display {
+class ForwardingDisplayDelegate;
+}
+
namespace leveldb {
class LevelDBMojoProxy;
}
@@ -24,14 +38,16 @@ class PersistentPrefStoreClient;
}
namespace ui {
-class Gpu;
-}
+class ClipboardClient;
+class HostContextFactoryPrivate;
+} // namespace ui
-namespace views {
-class ClipboardMus;
+namespace viz {
+class HostFrameSinkManager;
}
namespace mojo {
+class ScopedAllowSyncCallForTesting;
// In some processes, sync calls are disallowed. For example, in the browser
// process we don't want any sync calls to child processes for performance,
@@ -40,38 +56,62 @@ namespace mojo {
//
// Before processing a sync call, the bindings call
// SyncCallRestrictions::AssertSyncCallAllowed() to check whether sync calls are
-// allowed. By default, it is determined by the mojo system property
-// MOJO_PROPERTY_SYNC_CALL_ALLOWED. If the default setting says no but you have
-// a very compelling reason to disregard that (which should be very very rare),
-// you can override it by constructing a ScopedAllowSyncCall object, which
-// allows making sync calls on the current thread during its lifetime.
+// allowed. By default sync calls are allowed but they may be globally
+// disallowed within a process by calling DisallowSyncCall().
+//
+// If globally disallowed but you but you have a very compelling reason to
+// disregard that (which should be very very rare), you can override it by
+// constructing a ScopedAllowSyncCall object which allows making sync calls on
+// the current sequence during its lifetime.
class MOJO_CPP_BINDINGS_EXPORT SyncCallRestrictions {
public:
#if ENABLE_SYNC_CALL_RESTRICTIONS
- // Checks whether the current thread is allowed to make sync calls, and causes
- // a DCHECK if not.
+ // Checks whether the current sequence is allowed to make sync calls, and
+ // causes a DCHECK if not.
static void AssertSyncCallAllowed();
+
+ // Disables sync calls within the calling process. Any caller who wishes to
+ // make sync calls once this has been invoked must do so within the extent of
+ // a ScopedAllowSyncCall or ScopedAllowSyncCallForTesting.
+ static void DisallowSyncCall();
+
#else
// Inline the empty definitions of functions so that they can be compiled out.
static void AssertSyncCallAllowed() {}
+ static void DisallowSyncCall() {}
#endif
private:
// DO NOT ADD ANY OTHER FRIEND STATEMENTS, talk to mojo/OWNERS first.
// BEGIN ALLOWED USAGE.
- friend class ui::Gpu; // http://crbug.com/620058
+ // SynchronousCompositorHost is used for Android webview.
+ friend class content::SynchronousCompositorHost;
// LevelDBMojoProxy makes same-process sync calls from the DB thread.
friend class leveldb::LevelDBMojoProxy;
// Pref service connection is sync at startup.
friend class prefs::PersistentPrefStoreClient;
-
+ // Incognito pref service instances are created synchronously.
+ friend class sync_preferences::PrefServiceSyncable;
+ friend class mojo::ScopedAllowSyncCallForTesting;
+ // For file open and save dialogs created synchronously.
+ friend class ::ChromeSelectFileDialogFactory;
+ // For synchronous system clipboard access.
+ friend class ui::ClipboardClient;
+ // For destroying the GL context/surface that draw to a platform window before
+ // the platform window is destroyed.
+ friend class viz::HostFrameSinkManager;
+ // Allow for layout test pixel dumps.
+ friend class content::BlinkTestController;
+ // For preventing frame swaps of wrong size during resize on Windows.
+ // (https://crbug.com/811945)
+ friend class ui::HostContextFactoryPrivate;
// END ALLOWED USAGE.
// BEGIN USAGE THAT NEEDS TO BE FIXED.
- // In the non-mus case, we called blocking OS functions in the ui::Clipboard
- // implementation which weren't caught by sync call restrictions. Our blocking
- // calls to mus, however, are.
- friend class views::ClipboardMus;
+ // In ash::Shell::Init() it assumes that NativeDisplayDelegate will be
+ // synchronous at first. In mushrome ForwardingDisplayDelegate uses a
+ // synchronous call to get the display snapshots as a workaround.
+ friend class display::ForwardingDisplayDelegate;
// END USAGE THAT NEEDS TO BE FIXED.
#if ENABLE_SYNC_CALL_RESTRICTIONS
@@ -84,7 +124,7 @@ class MOJO_CPP_BINDINGS_EXPORT SyncCallRestrictions {
// If a process is configured to disallow sync calls in general, constructing
// a ScopedAllowSyncCall object temporarily allows making sync calls on the
- // current thread. Doing this is almost always incorrect, which is why we
+ // current sequence. Doing this is almost always incorrect, which is why we
// limit who can use this through friend. If you find yourself needing to use
// this, talk to mojo/OWNERS.
class ScopedAllowSyncCall {
@@ -103,6 +143,17 @@ class MOJO_CPP_BINDINGS_EXPORT SyncCallRestrictions {
DISALLOW_IMPLICIT_CONSTRUCTORS(SyncCallRestrictions);
};
+class ScopedAllowSyncCallForTesting {
+ public:
+ ScopedAllowSyncCallForTesting() {}
+ ~ScopedAllowSyncCallForTesting() {}
+
+ private:
+ SyncCallRestrictions::ScopedAllowSyncCall scoped_allow_sync_call_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedAllowSyncCallForTesting);
+};
+
} // namespace mojo
#endif // MOJO_PUBLIC_CPP_BINDINGS_SYNC_CALL_RESTRICTIONS_H_
diff --git a/mojo/public/cpp/bindings/sync_event_watcher.h b/mojo/public/cpp/bindings/sync_event_watcher.h
index 6e254844e9..9bc9ada594 100644
--- a/mojo/public/cpp/bindings/sync_event_watcher.h
+++ b/mojo/public/cpp/bindings/sync_event_watcher.h
@@ -10,8 +10,8 @@
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
+#include "base/sequence_checker.h"
#include "base/synchronization/waitable_event.h"
-#include "base/threading/thread_checker.h"
#include "mojo/public/cpp/bindings/bindings_export.h"
#include "mojo/public/cpp/bindings/sync_handle_registry.h"
@@ -19,7 +19,7 @@ namespace mojo {
// SyncEventWatcher supports waiting on a base::WaitableEvent to signal while
// also allowing other SyncEventWatchers and SyncHandleWatchers on the same
-// thread to wake up as needed.
+// sequence to wake up as needed.
//
// This class is not thread safe.
class MOJO_CPP_BINDINGS_EXPORT SyncEventWatcher {
@@ -29,17 +29,22 @@ class MOJO_CPP_BINDINGS_EXPORT SyncEventWatcher {
~SyncEventWatcher();
// Registers |event_| with SyncHandleRegistry, so that when others perform
- // sync watching on the same thread, |event_| will be watched along with them.
+ // sync watching on the same sequence, |event_| will be watched along with
+ // them.
void AllowWokenUpBySyncWatchOnSameThread();
// Waits on |event_| plus all other events and handles registered with this
- // thread's SyncHandleRegistry, running callbacks synchronously for any ready
- // events and handles.
+ // sequence's SyncHandleRegistry, running callbacks synchronously for any
+ // ready events and handles.
+ //
+ // |stop_flags| is treated as an array of |const bool*| with |num_stop_flags|
+ // entries.
+ //
// This method:
- // - returns true when |should_stop| is set to true;
+ // - returns true when any flag in |stop_flags| is set to |true|.
// - return false when any error occurs, including this object being
// destroyed during a callback.
- bool SyncWatch(const bool* should_stop);
+ bool SyncWatch(const bool** stop_flags, size_t num_stop_flags);
private:
void IncrementRegisterCount();
@@ -58,7 +63,7 @@ class MOJO_CPP_BINDINGS_EXPORT SyncEventWatcher {
scoped_refptr<base::RefCountedData<bool>> destroyed_;
- base::ThreadChecker thread_checker_;
+ SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(SyncEventWatcher);
};
diff --git a/mojo/public/cpp/bindings/sync_handle_registry.h b/mojo/public/cpp/bindings/sync_handle_registry.h
index afb3b56bf4..cd535aa69d 100644
--- a/mojo/public/cpp/bindings/sync_handle_registry.h
+++ b/mojo/public/cpp/bindings/sync_handle_registry.h
@@ -6,30 +6,34 @@
#define MOJO_PUBLIC_CPP_BINDINGS_SYNC_HANDLE_REGISTRY_H_
#include <map>
-#include <unordered_map>
#include "base/callback.h"
+#include "base/containers/stack_container.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
+#include "base/sequence_checker.h"
#include "base/synchronization/waitable_event.h"
-#include "base/threading/thread_checker.h"
#include "mojo/public/cpp/bindings/bindings_export.h"
#include "mojo/public/cpp/system/core.h"
#include "mojo/public/cpp/system/wait_set.h"
namespace mojo {
-// SyncHandleRegistry is a thread-local storage to register handles that want to
-// be watched together.
+// SyncHandleRegistry is a sequence-local storage to register handles that want
+// to be watched together.
//
-// This class is not thread safe.
+// This class is thread unsafe.
class MOJO_CPP_BINDINGS_EXPORT SyncHandleRegistry
: public base::RefCounted<SyncHandleRegistry> {
public:
- // Returns a thread-local object.
+ // Returns a sequence-local object.
static scoped_refptr<SyncHandleRegistry> current();
using HandleCallback = base::Callback<void(MojoResult)>;
+
+ // Registers a |Handle| to be watched for |handle_signals|. If any such
+ // signals are satisfied during a Wait(), the Wait() is woken up and
+ // |callback| is run.
bool RegisterHandle(const Handle& handle,
MojoHandleSignals handle_signals,
const HandleCallback& callback);
@@ -38,11 +42,13 @@ class MOJO_CPP_BINDINGS_EXPORT SyncHandleRegistry
// Registers a |base::WaitableEvent| which can be used to wake up
// Wait() before any handle signals. |event| is not owned, and if it signals
- // during Wait(), |callback| is invoked. Returns |true| if registered
- // successfully or |false| if |event| was already registered.
- bool RegisterEvent(base::WaitableEvent* event, const base::Closure& callback);
+ // during Wait(), |callback| is invoked. Note that |event| may be registered
+ // multiple times with different callbacks.
+ void RegisterEvent(base::WaitableEvent* event, const base::Closure& callback);
- void UnregisterEvent(base::WaitableEvent* event);
+ // Unregisters a specific |event|+|callback| pair.
+ void UnregisterEvent(base::WaitableEvent* event,
+ const base::Closure& callback);
// Waits on all the registered handles and events and runs callbacks
// synchronously for any that become ready.
@@ -54,14 +60,28 @@ class MOJO_CPP_BINDINGS_EXPORT SyncHandleRegistry
private:
friend class base::RefCounted<SyncHandleRegistry>;
+ using EventCallbackList = base::StackVector<base::Closure, 1>;
+ using EventMap = std::map<base::WaitableEvent*, EventCallbackList>;
+
SyncHandleRegistry();
~SyncHandleRegistry();
+ void RemoveInvalidEventCallbacks();
+
WaitSet wait_set_;
std::map<Handle, HandleCallback> handles_;
- std::map<base::WaitableEvent*, base::Closure> events_;
+ EventMap events_;
+
+ // |true| iff this registry is currently dispatching event callbacks in
+ // Wait(). Used to allow for safe event registration/unregistration from event
+ // callbacks.
+ bool is_dispatching_event_callbacks_ = false;
+
+ // Indicates if one or more event callbacks was unregistered during the most
+ // recent event callback dispatch.
+ bool remove_invalid_event_callbacks_after_dispatch_ = false;
- base::ThreadChecker thread_checker_;
+ SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(SyncHandleRegistry);
};
diff --git a/mojo/public/cpp/bindings/sync_handle_watcher.h b/mojo/public/cpp/bindings/sync_handle_watcher.h
index eff73dd66e..e680d74aa6 100644
--- a/mojo/public/cpp/bindings/sync_handle_watcher.h
+++ b/mojo/public/cpp/bindings/sync_handle_watcher.h
@@ -7,7 +7,7 @@
#include "base/macros.h"
#include "base/memory/ref_counted.h"
-#include "base/threading/thread_checker.h"
+#include "base/sequence_checker.h"
#include "mojo/public/cpp/bindings/bindings_export.h"
#include "mojo/public/cpp/bindings/sync_handle_registry.h"
#include "mojo/public/cpp/system/core.h"
@@ -15,15 +15,15 @@
namespace mojo {
// SyncHandleWatcher supports watching a handle synchronously. It also supports
-// registering the handle with a thread-local storage (SyncHandleRegistry), so
-// that when other SyncHandleWatcher instances on the same thread perform sync
+// registering the handle with a sequence-local storage (SyncHandleRegistry), so
+// that when other SyncHandleWatcher instances on the same sequence perform sync
// handle watching, this handle will be watched together.
//
// SyncHandleWatcher is used for sync methods. While a sync call is waiting for
-// response, we would like to block the thread. On the other hand, we need
-// incoming sync method requests on the same thread to be able to reenter. We
+// response, we would like to block the sequence. On the other hand, we need
+// incoming sync method requests on the same sequence to be able to reenter. We
// also need master interface endpoints to continue dispatching messages for
-// associated endpoints on different threads.
+// associated endpoints on different sequence.
//
// This class is not thread safe.
class MOJO_CPP_BINDINGS_EXPORT SyncHandleWatcher {
@@ -36,7 +36,7 @@ class MOJO_CPP_BINDINGS_EXPORT SyncHandleWatcher {
~SyncHandleWatcher();
// Registers |handle_| with SyncHandleRegistry, so that when others perform
- // sync handle watching on the same thread, |handle_| will be watched
+ // sync handle watching on the same sequence, |handle_| will be watched
// together.
void AllowWokenUpBySyncWatchOnSameThread();
@@ -65,7 +65,7 @@ class MOJO_CPP_BINDINGS_EXPORT SyncHandleWatcher {
scoped_refptr<base::RefCountedData<bool>> destroyed_;
- base::ThreadChecker thread_checker_;
+ SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(SyncHandleWatcher);
};
diff --git a/mojo/public/cpp/bindings/tests/BUILD.gn b/mojo/public/cpp/bindings/tests/BUILD.gn
index 668ca6da90..82038a186d 100644
--- a/mojo/public/cpp/bindings/tests/BUILD.gn
+++ b/mojo/public/cpp/bindings/tests/BUILD.gn
@@ -11,7 +11,10 @@ source_set("tests") {
"binding_callback_unittest.cc",
"binding_set_unittest.cc",
"binding_unittest.cc",
+ "bindings_test_base.cc",
+ "bindings_test_base.h",
"buffer_unittest.cc",
+ "callback_helpers_unittest.cc",
"connector_unittest.cc",
"constant_unittest.cc",
"container_test_util.cc",
@@ -21,10 +24,12 @@ source_set("tests") {
"handle_passing_unittest.cc",
"hash_unittest.cc",
"interface_ptr_unittest.cc",
+ "lazy_serialization_unittest.cc",
"map_unittest.cc",
"message_queue.cc",
"message_queue.h",
"multiplex_router_unittest.cc",
+ "native_struct_unittest.cc",
"report_bad_message_unittest.cc",
"request_response_unittest.cc",
"router_test_util.cc",
@@ -32,7 +37,9 @@ source_set("tests") {
"sample_service_unittest.cc",
"serialization_warning_unittest.cc",
"struct_unittest.cc",
+ "sync_handle_registry_unittest.cc",
"sync_method_unittest.cc",
+ "test_helpers_unittest.cc",
"type_conversion_unittest.cc",
"union_unittest.cc",
"validation_context_unittest.cc",
@@ -43,7 +50,7 @@ source_set("tests") {
deps = [
":mojo_public_bindings_test_utils",
"//base/test:test_support",
- "//mojo/edk/system",
+ "//mojo/core/embedder",
"//mojo/public/cpp/bindings",
"//mojo/public/cpp/system",
"//mojo/public/cpp/test_support:test_utils",
@@ -68,7 +75,10 @@ source_set("tests") {
"struct_traits_unittest.cc",
]
- deps += [ "//mojo/public/interfaces/bindings/tests:test_interfaces_blink" ]
+ deps += [
+ "//mojo/public/cpp/bindings/tests:struct_with_traits_impl",
+ "//mojo/public/interfaces/bindings/tests:test_interfaces_blink",
+ ]
}
}
@@ -124,8 +134,8 @@ source_set("perftests") {
deps = [
"//base/test:test_support",
- "//mojo/edk/system",
- "//mojo/edk/test:test_support",
+ "//mojo/core/embedder",
+ "//mojo/core/test:test_support",
"//mojo/public/cpp/bindings",
"//mojo/public/cpp/system",
"//mojo/public/cpp/test_support:test_utils",
diff --git a/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc b/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc
index be225e4761..85cec97ae9 100644
--- a/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc
@@ -11,10 +11,11 @@
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/memory/ptr_util.h"
-#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/waitable_event.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/test/scoped_task_environment.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.h"
@@ -95,7 +96,8 @@ class IntegerSenderConnectionImpl : public IntegerSenderConnection {
class AssociatedInterfaceTest : public testing::Test {
public:
- AssociatedInterfaceTest() {}
+ AssociatedInterfaceTest()
+ : main_runner_(base::ThreadTaskRunnerHandle::Get()) {}
~AssociatedInterfaceTest() override { base::RunLoop().RunUntilIdle(); }
void PumpMessages() { base::RunLoop().RunUntilIdle(); }
@@ -117,10 +119,10 @@ class AssociatedInterfaceTest : public testing::Test {
MessagePipe pipe;
*router0 = new MultiplexRouter(std::move(pipe.handle0),
MultiplexRouter::MULTI_INTERFACE, true,
- base::ThreadTaskRunnerHandle::Get());
+ main_runner_);
*router1 = new MultiplexRouter(std::move(pipe.handle1),
MultiplexRouter::MULTI_INTERFACE, false,
- base::ThreadTaskRunnerHandle::Get());
+ main_runner_);
}
void CreateIntegerSenderWithExistingRouters(
@@ -143,10 +145,10 @@ class AssociatedInterfaceTest : public testing::Test {
// Okay to call from any thread.
void QuitRunLoop(base::RunLoop* run_loop) {
- if (loop_.task_runner()->BelongsToCurrentThread()) {
+ if (main_runner_->RunsTasksInCurrentSequence()) {
run_loop->Quit();
} else {
- loop_.task_runner()->PostTask(
+ main_runner_->PostTask(
FROM_HERE,
base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
base::Unretained(this), base::Unretained(run_loop)));
@@ -154,7 +156,8 @@ class AssociatedInterfaceTest : public testing::Test {
}
private:
- base::MessageLoop loop_;
+ base::test::ScopedTaskEnvironment task_environment;
+ scoped_refptr<base::SequencedTaskRunner> main_runner_;
};
void DoSetFlagAndRunClosure(bool* flag, const base::Closure& closure) {
@@ -244,17 +247,15 @@ TEST_F(AssociatedInterfaceTest, InterfacesAtBothEnds) {
class TestSender {
public:
TestSender()
- : sender_thread_("TestSender"),
+ : task_runner_(base::CreateSequencedTaskRunnerWithTraits({})),
next_sender_(nullptr),
- max_value_to_send_(-1) {
- sender_thread_.Start();
- }
+ max_value_to_send_(-1) {}
// The following three methods are called on the corresponding sender thread.
void SetUp(IntegerSenderAssociatedPtrInfo ptr_info,
TestSender* next_sender,
int32_t max_value_to_send) {
- CHECK(sender_thread_.task_runner()->BelongsToCurrentThread());
+ CHECK(task_runner()->RunsTasksInCurrentSequence());
ptr_.Bind(std::move(ptr_info));
next_sender_ = next_sender ? next_sender : this;
@@ -262,28 +263,28 @@ class TestSender {
}
void Send(int32_t value) {
- CHECK(sender_thread_.task_runner()->BelongsToCurrentThread());
+ CHECK(task_runner()->RunsTasksInCurrentSequence());
if (value > max_value_to_send_)
return;
ptr_->Send(value);
- next_sender_->sender_thread()->task_runner()->PostTask(
+ next_sender_->task_runner()->PostTask(
FROM_HERE,
base::Bind(&TestSender::Send, base::Unretained(next_sender_), ++value));
}
void TearDown() {
- CHECK(sender_thread_.task_runner()->BelongsToCurrentThread());
+ CHECK(task_runner()->RunsTasksInCurrentSequence());
ptr_.reset();
}
- base::Thread* sender_thread() { return &sender_thread_; }
+ base::SequencedTaskRunner* task_runner() { return task_runner_.get(); }
private:
- base::Thread sender_thread_;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
TestSender* next_sender_;
int32_t max_value_to_send_;
@@ -292,15 +293,15 @@ class TestSender {
class TestReceiver {
public:
- TestReceiver() : receiver_thread_("TestReceiver"), expected_calls_(0) {
- receiver_thread_.Start();
- }
+ TestReceiver()
+ : task_runner_(base::CreateSequencedTaskRunnerWithTraits({})),
+ expected_calls_(0) {}
void SetUp(AssociatedInterfaceRequest<IntegerSender> request0,
AssociatedInterfaceRequest<IntegerSender> request1,
size_t expected_calls,
const base::Closure& notify_finish) {
- CHECK(receiver_thread_.task_runner()->BelongsToCurrentThread());
+ CHECK(task_runner()->RunsTasksInCurrentSequence());
impl0_.reset(new IntegerSenderImpl(std::move(request0)));
impl0_->set_notify_send_method_called(
@@ -314,13 +315,13 @@ class TestReceiver {
}
void TearDown() {
- CHECK(receiver_thread_.task_runner()->BelongsToCurrentThread());
+ CHECK(task_runner()->RunsTasksInCurrentSequence());
impl0_.reset();
impl1_.reset();
}
- base::Thread* receiver_thread() { return &receiver_thread_; }
+ base::SequencedTaskRunner* task_runner() { return task_runner_.get(); }
const std::vector<int32_t>& values() const { return values_; }
private:
@@ -331,7 +332,7 @@ class TestReceiver {
notify_finish_.Run();
}
- base::Thread receiver_thread_;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
size_t expected_calls_;
std::unique_ptr<IntegerSenderImpl> impl0_;
@@ -392,7 +393,7 @@ TEST_F(AssociatedInterfaceTest, MultiThreadAccess) {
TestSender senders[4];
for (size_t i = 0; i < 4; ++i) {
- senders[i].sender_thread()->task_runner()->PostTask(
+ senders[i].task_runner()->PostTask(
FROM_HERE, base::Bind(&TestSender::SetUp, base::Unretained(&senders[i]),
base::Passed(&ptr_infos[i]), nullptr,
kMaxValue * (i + 1) / 4));
@@ -404,7 +405,7 @@ TEST_F(AssociatedInterfaceTest, MultiThreadAccess) {
2, base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
base::Unretained(this), base::Unretained(&run_loop)));
for (size_t i = 0; i < 2; ++i) {
- receivers[i].receiver_thread()->task_runner()->PostTask(
+ receivers[i].task_runner()->PostTask(
FROM_HERE,
base::Bind(&TestReceiver::SetUp, base::Unretained(&receivers[i]),
base::Passed(&requests[2 * i]),
@@ -415,7 +416,7 @@ TEST_F(AssociatedInterfaceTest, MultiThreadAccess) {
}
for (size_t i = 0; i < 4; ++i) {
- senders[i].sender_thread()->task_runner()->PostTask(
+ senders[i].task_runner()->PostTask(
FROM_HERE, base::Bind(&TestSender::Send, base::Unretained(&senders[i]),
kMaxValue * i / 4 + 1));
}
@@ -424,7 +425,7 @@ TEST_F(AssociatedInterfaceTest, MultiThreadAccess) {
for (size_t i = 0; i < 4; ++i) {
base::RunLoop run_loop;
- senders[i].sender_thread()->task_runner()->PostTaskAndReply(
+ senders[i].task_runner()->PostTaskAndReply(
FROM_HERE,
base::Bind(&TestSender::TearDown, base::Unretained(&senders[i])),
base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
@@ -434,7 +435,7 @@ TEST_F(AssociatedInterfaceTest, MultiThreadAccess) {
for (size_t i = 0; i < 2; ++i) {
base::RunLoop run_loop;
- receivers[i].receiver_thread()->task_runner()->PostTaskAndReply(
+ receivers[i].task_runner()->PostTaskAndReply(
FROM_HERE,
base::Bind(&TestReceiver::TearDown, base::Unretained(&receivers[i])),
base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
@@ -477,7 +478,7 @@ TEST_F(AssociatedInterfaceTest, FIFO) {
TestSender senders[4];
for (size_t i = 0; i < 4; ++i) {
- senders[i].sender_thread()->task_runner()->PostTask(
+ senders[i].task_runner()->PostTask(
FROM_HERE,
base::Bind(&TestSender::SetUp, base::Unretained(&senders[i]),
base::Passed(&ptr_infos[i]),
@@ -490,7 +491,7 @@ TEST_F(AssociatedInterfaceTest, FIFO) {
2, base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
base::Unretained(this), base::Unretained(&run_loop)));
for (size_t i = 0; i < 2; ++i) {
- receivers[i].receiver_thread()->task_runner()->PostTask(
+ receivers[i].task_runner()->PostTask(
FROM_HERE,
base::Bind(&TestReceiver::SetUp, base::Unretained(&receivers[i]),
base::Passed(&requests[2 * i]),
@@ -500,7 +501,7 @@ TEST_F(AssociatedInterfaceTest, FIFO) {
base::Unretained(&counter))));
}
- senders[0].sender_thread()->task_runner()->PostTask(
+ senders[0].task_runner()->PostTask(
FROM_HERE,
base::Bind(&TestSender::Send, base::Unretained(&senders[0]), 1));
@@ -508,7 +509,7 @@ TEST_F(AssociatedInterfaceTest, FIFO) {
for (size_t i = 0; i < 4; ++i) {
base::RunLoop run_loop;
- senders[i].sender_thread()->task_runner()->PostTaskAndReply(
+ senders[i].task_runner()->PostTaskAndReply(
FROM_HERE,
base::Bind(&TestSender::TearDown, base::Unretained(&senders[i])),
base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
@@ -518,7 +519,7 @@ TEST_F(AssociatedInterfaceTest, FIFO) {
for (size_t i = 0; i < 2; ++i) {
base::RunLoop run_loop;
- receivers[i].receiver_thread()->task_runner()->PostTaskAndReply(
+ receivers[i].task_runner()->PostTaskAndReply(
FROM_HERE,
base::Bind(&TestReceiver::TearDown, base::Unretained(&receivers[i])),
base::Bind(&AssociatedInterfaceTest::QuitRunLoop,
@@ -669,7 +670,7 @@ class CallbackFilter : public MessageReceiver {
~CallbackFilter() override {}
static std::unique_ptr<CallbackFilter> Wrap(const base::Closure& callback) {
- return base::MakeUnique<CallbackFilter>(callback);
+ return std::make_unique<CallbackFilter>(callback);
}
// MessageReceiver:
@@ -760,8 +761,8 @@ TEST_F(AssociatedInterfaceTest, AssociatedPtrFlushForTesting) {
ptr0.set_connection_error_handler(base::Bind(&Fail));
bool ptr0_callback_run = false;
- ptr0->Echo(123, ExpectValueSetFlagAndRunClosure(
- 123, &ptr0_callback_run, base::Bind(&base::DoNothing)));
+ ptr0->Echo(123, ExpectValueSetFlagAndRunClosure(123, &ptr0_callback_run,
+ base::DoNothing()));
ptr0.FlushForTesting();
EXPECT_TRUE(ptr0_callback_run);
}
@@ -802,8 +803,8 @@ TEST_F(AssociatedInterfaceTest, AssociatedBindingFlushForTesting) {
ptr0.Bind(std::move(ptr_info));
bool ptr0_callback_run = false;
- ptr0->Echo(123, ExpectValueSetFlagAndRunClosure(
- 123, &ptr0_callback_run, base::Bind(&base::DoNothing)));
+ ptr0->Echo(123, ExpectValueSetFlagAndRunClosure(123, &ptr0_callback_run,
+ base::DoNothing()));
// Because the flush is sent from the binding, it only guarantees that the
// request has been received, not the response. The second flush waits for the
// response to be received.
@@ -864,7 +865,7 @@ TEST_F(AssociatedInterfaceTest, BindingFlushForTestingWithClosedPeer) {
TEST_F(AssociatedInterfaceTest, StrongBindingFlushForTesting) {
IntegerSenderConnectionPtr ptr;
auto binding =
- MakeStrongBinding(base::MakeUnique<IntegerSenderConnectionImpl>(
+ MakeStrongBinding(std::make_unique<IntegerSenderConnectionImpl>(
IntegerSenderConnectionRequest{}),
MakeRequest(&ptr));
bool called = false;
@@ -886,7 +887,7 @@ TEST_F(AssociatedInterfaceTest, StrongBindingFlushForTestingWithClosedPeer) {
IntegerSenderConnectionPtr ptr;
bool called = false;
auto binding =
- MakeStrongBinding(base::MakeUnique<IntegerSenderConnectionImpl>(
+ MakeStrongBinding(std::make_unique<IntegerSenderConnectionImpl>(
IntegerSenderConnectionRequest{}),
MakeRequest(&ptr));
binding->set_connection_error_handler(base::Bind(&SetBool, &called));
@@ -1060,8 +1061,6 @@ TEST_F(AssociatedInterfaceTest, ThreadSafeAssociatedInterfacePtr) {
// Test the thread safe pointer can be used from another thread.
base::RunLoop run_loop;
- base::Thread other_thread("service test thread");
- other_thread.Start();
auto run_method = base::Bind(
[](const scoped_refptr<base::TaskRunner>& main_task_runner,
@@ -1071,21 +1070,24 @@ TEST_F(AssociatedInterfaceTest, ThreadSafeAssociatedInterfacePtr) {
auto done_callback = base::Bind(
[](const scoped_refptr<base::TaskRunner>& main_task_runner,
const base::Closure& quit_closure,
- base::PlatformThreadId thread_id, int32_t result) {
+ scoped_refptr<base::SequencedTaskRunner> sender_sequence_runner,
+ int32_t result) {
EXPECT_EQ(123, result);
- // Validate the callback is invoked on the calling thread.
- EXPECT_EQ(thread_id, base::PlatformThread::CurrentId());
+ // Validate the callback is invoked on the calling sequence.
+ EXPECT_TRUE(sender_sequence_runner->RunsTasksInCurrentSequence());
// Notify the run_loop to quit.
main_task_runner->PostTask(FROM_HERE, quit_closure);
});
+ scoped_refptr<base::SequencedTaskRunner> current_sequence_runner =
+ base::SequencedTaskRunnerHandle::Get();
(*thread_safe_sender)
- ->Echo(123,
- base::Bind(done_callback, main_task_runner, quit_closure,
- base::PlatformThread::CurrentId()));
+ ->Echo(123, base::Bind(done_callback, main_task_runner,
+ quit_closure, current_sequence_runner));
},
base::SequencedTaskRunnerHandle::Get(), run_loop.QuitClosure(),
thread_safe_sender);
- other_thread.message_loop()->task_runner()->PostTask(FROM_HERE, run_method);
+ base::CreateSequencedTaskRunnerWithTraits({})->PostTask(FROM_HERE,
+ run_method);
// Block until the method callback is called on the background thread.
run_loop.Run();
@@ -1099,11 +1101,8 @@ struct ForwarderTestContext {
TEST_F(AssociatedInterfaceTest,
ThreadSafeAssociatedInterfacePtrWithTaskRunner) {
- // Start the thread from where we'll bind the interface pointer.
- base::Thread other_thread("service test thread");
- other_thread.Start();
- const scoped_refptr<base::SingleThreadTaskRunner>& other_thread_task_runner =
- other_thread.message_loop()->task_runner();
+ const scoped_refptr<base::SequencedTaskRunner> other_thread_task_runner =
+ base::CreateSequencedTaskRunnerWithTraits({});
ForwarderTestContext* context = new ForwarderTestContext();
IntegerSenderAssociatedPtrInfo sender_info;
@@ -1113,7 +1112,7 @@ TEST_F(AssociatedInterfaceTest,
auto setup = [](base::WaitableEvent* sender_info_bound_event,
IntegerSenderAssociatedPtrInfo* sender_info,
ForwarderTestContext* context) {
- context->interface_impl = base::MakeUnique<IntegerSenderConnectionImpl>(
+ context->interface_impl = std::make_unique<IntegerSenderConnectionImpl>(
MakeRequest(&context->connection_ptr));
auto sender_request = MakeRequest(sender_info);
@@ -1168,7 +1167,8 @@ TEST_F(AssociatedInterfaceTest, CloseWithoutBindingAssociatedRequest) {
DiscardingAssociatedPingProviderProvider ping_provider_provider;
mojo::Binding<AssociatedPingProviderProvider> binding(
&ping_provider_provider);
- auto provider_provider = binding.CreateInterfacePtrAndBind();
+ AssociatedPingProviderProviderPtr provider_provider;
+ binding.Bind(mojo::MakeRequest(&provider_provider));
AssociatedPingProviderAssociatedPtr provider;
provider_provider->GetPingProvider(mojo::MakeRequest(&provider));
PingServiceAssociatedPtr ping;
@@ -1178,9 +1178,9 @@ TEST_F(AssociatedInterfaceTest, CloseWithoutBindingAssociatedRequest) {
run_loop.Run();
}
-TEST_F(AssociatedInterfaceTest, GetIsolatedInterface) {
+TEST_F(AssociatedInterfaceTest, AssociateWithDisconnectedPipe) {
IntegerSenderAssociatedPtr sender;
- GetIsolatedInterface(MakeRequest(&sender).PassHandle());
+ AssociateWithDisconnectedPipe(MakeRequest(&sender).PassHandle());
sender->Send(42);
}
diff --git a/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc b/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc
index 569eb518c6..17f4685af2 100644
--- a/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc
@@ -6,6 +6,7 @@
#include "base/bind.h"
#include "base/callback.h"
+#include "base/containers/queue.h"
#include "base/message_loop/message_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/lock.h"
@@ -31,14 +32,14 @@ class TestTaskRunner : public base::SingleThreadTaskRunner {
task_ready_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED) {}
- bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here,
+ bool PostNonNestableDelayedTask(const base::Location& from_here,
base::OnceClosure task,
base::TimeDelta delay) override {
NOTREACHED();
return false;
}
- bool PostDelayedTask(const tracked_objects::Location& from_here,
+ bool PostDelayedTask(const base::Location& from_here,
base::OnceClosure task,
base::TimeDelta delay) override {
{
@@ -48,13 +49,13 @@ class TestTaskRunner : public base::SingleThreadTaskRunner {
task_ready_.Signal();
return true;
}
- bool RunsTasksOnCurrentThread() const override {
+ bool RunsTasksInCurrentSequence() const override {
return base::PlatformThread::CurrentRef() == thread_id_;
}
// Only quits when Quit() is called.
void Run() {
- DCHECK(RunsTasksOnCurrentThread());
+ DCHECK(RunsTasksInCurrentSequence());
quit_called_ = false;
while (true) {
@@ -77,13 +78,13 @@ class TestTaskRunner : public base::SingleThreadTaskRunner {
}
void Quit() {
- DCHECK(RunsTasksOnCurrentThread());
+ DCHECK(RunsTasksInCurrentSequence());
quit_called_ = true;
}
// Waits until one task is ready and runs it.
void RunOneTask() {
- DCHECK(RunsTasksOnCurrentThread());
+ DCHECK(RunsTasksInCurrentSequence());
while (true) {
{
@@ -112,7 +113,7 @@ class TestTaskRunner : public base::SingleThreadTaskRunner {
// Protect |tasks_|.
base::Lock lock_;
- std::queue<base::OnceClosure> tasks_;
+ base::queue<base::OnceClosure> tasks_;
DISALLOW_COPY_AND_ASSIGN(TestTaskRunner);
};
diff --git a/mojo/public/cpp/bindings/tests/binding_set_unittest.cc b/mojo/public/cpp/bindings/tests/binding_set_unittest.cc
index 07acfbebe0..67b6fb1819 100644
--- a/mojo/public/cpp/bindings/tests/binding_set_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/binding_set_unittest.cc
@@ -5,11 +5,12 @@
#include <memory>
#include <utility>
-#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
+#include "mojo/core/embedder/embedder.h"
#include "mojo/public/cpp/bindings/associated_binding_set.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "mojo/public/cpp/bindings/strong_binding_set.h"
+#include "mojo/public/cpp/bindings/tests/bindings_test_base.h"
#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h"
#include "mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -18,18 +19,7 @@ namespace mojo {
namespace test {
namespace {
-class BindingSetTest : public testing::Test {
- public:
- BindingSetTest() {}
- ~BindingSetTest() override {}
-
- base::MessageLoop& loop() { return loop_; }
-
- private:
- base::MessageLoop loop_;
-
- DISALLOW_COPY_AND_ASSIGN(BindingSetTest);
-};
+using BindingSetTest = BindingsTestBase;
template <typename BindingSetType, typename ContextType>
void ExpectContextHelper(BindingSetType* binding_set,
@@ -45,6 +35,45 @@ base::Closure ExpectContext(BindingSetType* binding_set,
expected_context);
}
+template <typename BindingSetType>
+void ExpectBindingIdHelper(BindingSetType* binding_set,
+ BindingId expected_binding_id) {
+ EXPECT_EQ(expected_binding_id, binding_set->dispatch_binding());
+}
+
+template <typename BindingSetType>
+base::Closure ExpectBindingId(BindingSetType* binding_set,
+ BindingId expected_binding_id) {
+ return base::Bind(&ExpectBindingIdHelper<BindingSetType>, binding_set,
+ expected_binding_id);
+}
+
+template <typename BindingSetType>
+void ReportBadMessageHelper(BindingSetType* binding_set,
+ const std::string& error) {
+ binding_set->ReportBadMessage(error);
+}
+
+template <typename BindingSetType>
+base::Closure ReportBadMessage(BindingSetType* binding_set,
+ const std::string& error) {
+ return base::Bind(&ReportBadMessageHelper<BindingSetType>, binding_set,
+ error);
+}
+
+template <typename BindingSetType>
+void SaveBadMessageCallbackHelper(BindingSetType* binding_set,
+ ReportBadMessageCallback* callback) {
+ *callback = binding_set->GetBadMessageCallback();
+}
+
+template <typename BindingSetType>
+base::Closure SaveBadMessageCallback(BindingSetType* binding_set,
+ ReportBadMessageCallback* callback) {
+ return base::Bind(&SaveBadMessageCallbackHelper<BindingSetType>, binding_set,
+ callback);
+}
+
base::Closure Sequence(const base::Closure& first,
const base::Closure& second) {
return base::Bind(
@@ -74,7 +103,7 @@ class PingImpl : public PingService {
base::Closure ping_handler_;
};
-TEST_F(BindingSetTest, BindingSetContext) {
+TEST_P(BindingSetTest, BindingSetContext) {
PingImpl impl;
BindingSet<PingService, int> bindings;
@@ -115,7 +144,48 @@ TEST_F(BindingSetTest, BindingSetContext) {
EXPECT_TRUE(bindings.empty());
}
-TEST_F(BindingSetTest, BindingSetConnectionErrorWithReason) {
+TEST_P(BindingSetTest, BindingSetDispatchBinding) {
+ PingImpl impl;
+
+ BindingSet<PingService, int> bindings;
+ PingServicePtr ping_a, ping_b;
+ BindingId id_a = bindings.AddBinding(&impl, MakeRequest(&ping_a), 1);
+ BindingId id_b = bindings.AddBinding(&impl, MakeRequest(&ping_b), 2);
+
+ {
+ impl.set_ping_handler(ExpectBindingId(&bindings, id_a));
+ base::RunLoop loop;
+ ping_a->Ping(loop.QuitClosure());
+ loop.Run();
+ }
+
+ {
+ impl.set_ping_handler(ExpectBindingId(&bindings, id_b));
+ base::RunLoop loop;
+ ping_b->Ping(loop.QuitClosure());
+ loop.Run();
+ }
+
+ {
+ base::RunLoop loop;
+ bindings.set_connection_error_handler(
+ Sequence(ExpectBindingId(&bindings, id_a), loop.QuitClosure()));
+ ping_a.reset();
+ loop.Run();
+ }
+
+ {
+ base::RunLoop loop;
+ bindings.set_connection_error_handler(
+ Sequence(ExpectBindingId(&bindings, id_b), loop.QuitClosure()));
+ ping_b.reset();
+ loop.Run();
+ }
+
+ EXPECT_TRUE(bindings.empty());
+}
+
+TEST_P(BindingSetTest, BindingSetConnectionErrorWithReason) {
PingImpl impl;
PingServicePtr ptr;
BindingSet<PingService> bindings;
@@ -134,6 +204,121 @@ TEST_F(BindingSetTest, BindingSetConnectionErrorWithReason) {
ptr.ResetWithReason(1024u, "bye");
}
+TEST_P(BindingSetTest, BindingSetReportBadMessage) {
+ PingImpl impl;
+
+ std::string last_received_error;
+ core::SetDefaultProcessErrorCallback(
+ base::Bind([](std::string* out_error,
+ const std::string& error) { *out_error = error; },
+ &last_received_error));
+
+ BindingSet<PingService, int> bindings;
+ PingServicePtr ping_a, ping_b;
+ bindings.AddBinding(&impl, MakeRequest(&ping_a), 1);
+ bindings.AddBinding(&impl, MakeRequest(&ping_b), 2);
+
+ {
+ impl.set_ping_handler(ReportBadMessage(&bindings, "message 1"));
+ base::RunLoop loop;
+ ping_a.set_connection_error_handler(loop.QuitClosure());
+ ping_a->Ping(base::Bind([] {}));
+ loop.Run();
+ EXPECT_EQ("message 1", last_received_error);
+ }
+
+ {
+ impl.set_ping_handler(ReportBadMessage(&bindings, "message 2"));
+ base::RunLoop loop;
+ ping_b.set_connection_error_handler(loop.QuitClosure());
+ ping_b->Ping(base::Bind([] {}));
+ loop.Run();
+ EXPECT_EQ("message 2", last_received_error);
+ }
+
+ EXPECT_TRUE(bindings.empty());
+
+ core::SetDefaultProcessErrorCallback(mojo::core::ProcessErrorCallback());
+}
+
+TEST_P(BindingSetTest, BindingSetGetBadMessageCallback) {
+ PingImpl impl;
+
+ std::string last_received_error;
+ core::SetDefaultProcessErrorCallback(
+ base::Bind([](std::string* out_error,
+ const std::string& error) { *out_error = error; },
+ &last_received_error));
+
+ BindingSet<PingService, int> bindings;
+ PingServicePtr ping_a, ping_b;
+ bindings.AddBinding(&impl, MakeRequest(&ping_a), 1);
+ bindings.AddBinding(&impl, MakeRequest(&ping_b), 2);
+
+ ReportBadMessageCallback bad_message_callback_a;
+ ReportBadMessageCallback bad_message_callback_b;
+
+ {
+ impl.set_ping_handler(
+ SaveBadMessageCallback(&bindings, &bad_message_callback_a));
+ base::RunLoop loop;
+ ping_a->Ping(loop.QuitClosure());
+ loop.Run();
+ ping_a.reset();
+ }
+
+ {
+ impl.set_ping_handler(
+ SaveBadMessageCallback(&bindings, &bad_message_callback_b));
+ base::RunLoop loop;
+ ping_b->Ping(loop.QuitClosure());
+ loop.Run();
+ }
+
+ std::move(bad_message_callback_a).Run("message 1");
+ EXPECT_EQ("message 1", last_received_error);
+
+ {
+ base::RunLoop loop;
+ ping_b.set_connection_error_handler(loop.QuitClosure());
+ std::move(bad_message_callback_b).Run("message 2");
+ EXPECT_EQ("message 2", last_received_error);
+ loop.Run();
+ }
+
+ EXPECT_TRUE(bindings.empty());
+
+ core::SetDefaultProcessErrorCallback(mojo::core::ProcessErrorCallback());
+}
+
+TEST_P(BindingSetTest, BindingSetGetBadMessageCallbackOutlivesBindingSet) {
+ PingImpl impl;
+
+ std::string last_received_error;
+ core::SetDefaultProcessErrorCallback(
+ base::Bind([](std::string* out_error,
+ const std::string& error) { *out_error = error; },
+ &last_received_error));
+
+ ReportBadMessageCallback bad_message_callback;
+ {
+ BindingSet<PingService, int> bindings;
+ PingServicePtr ping_a;
+ bindings.AddBinding(&impl, MakeRequest(&ping_a), 1);
+
+ impl.set_ping_handler(
+ SaveBadMessageCallback(&bindings, &bad_message_callback));
+ base::RunLoop loop;
+ ping_a->Ping(loop.QuitClosure());
+ loop.Run();
+ }
+
+ std::move(bad_message_callback).Run("message 1");
+ EXPECT_EQ("message 1", last_received_error);
+
+ core::SetDefaultProcessErrorCallback(mojo::core::ProcessErrorCallback());
+}
+
class PingProviderImpl : public AssociatedPingProvider, public PingService {
public:
PingProviderImpl() {}
@@ -174,7 +359,7 @@ class PingProviderImpl : public AssociatedPingProvider, public PingService {
base::Closure new_ping_handler_;
};
-TEST_F(BindingSetTest, AssociatedBindingSetContext) {
+TEST_P(BindingSetTest, AssociatedBindingSetContext) {
AssociatedPingProviderPtr provider;
PingProviderImpl impl;
Binding<AssociatedPingProvider> binding(&impl, MakeRequest(&provider));
@@ -230,7 +415,7 @@ TEST_F(BindingSetTest, AssociatedBindingSetContext) {
EXPECT_TRUE(impl.ping_bindings().empty());
}
-TEST_F(BindingSetTest, MasterInterfaceBindingSetContext) {
+TEST_P(BindingSetTest, MasterInterfaceBindingSetContext) {
AssociatedPingProviderPtr provider_a, provider_b;
PingProviderImpl impl;
BindingSet<AssociatedPingProvider, int> bindings;
@@ -275,7 +460,52 @@ TEST_F(BindingSetTest, MasterInterfaceBindingSetContext) {
EXPECT_TRUE(bindings.empty());
}
-TEST_F(BindingSetTest, PreDispatchHandler) {
+TEST_P(BindingSetTest, MasterInterfaceBindingSetDispatchBinding) {
+ AssociatedPingProviderPtr provider_a, provider_b;
+ PingProviderImpl impl;
+ BindingSet<AssociatedPingProvider, int> bindings;
+
+ BindingId id_a = bindings.AddBinding(&impl, MakeRequest(&provider_a), 1);
+ BindingId id_b = bindings.AddBinding(&impl, MakeRequest(&provider_b), 2);
+
+ {
+ PingServiceAssociatedPtr ping;
+ base::RunLoop loop;
+ impl.set_new_ping_handler(
+ Sequence(ExpectBindingId(&bindings, id_a), loop.QuitClosure()));
+ provider_a->GetPing(MakeRequest(&ping));
+ loop.Run();
+ }
+
+ {
+ PingServiceAssociatedPtr ping;
+ base::RunLoop loop;
+ impl.set_new_ping_handler(
+ Sequence(ExpectBindingId(&bindings, id_b), loop.QuitClosure()));
+ provider_b->GetPing(MakeRequest(&ping));
+ loop.Run();
+ }
+
+ {
+ base::RunLoop loop;
+ bindings.set_connection_error_handler(
+ Sequence(ExpectBindingId(&bindings, id_a), loop.QuitClosure()));
+ provider_a.reset();
+ loop.Run();
+ }
+
+ {
+ base::RunLoop loop;
+ bindings.set_connection_error_handler(
+ Sequence(ExpectBindingId(&bindings, id_b), loop.QuitClosure()));
+ provider_b.reset();
+ loop.Run();
+ }
+
+ EXPECT_TRUE(bindings.empty());
+}
+
+TEST_P(BindingSetTest, PreDispatchHandler) {
PingImpl impl;
BindingSet<PingService, int> bindings;
@@ -326,10 +556,11 @@ TEST_F(BindingSetTest, PreDispatchHandler) {
EXPECT_TRUE(bindings.empty());
}
-TEST_F(BindingSetTest, AssociatedBindingSetConnectionErrorWithReason) {
+TEST_P(BindingSetTest, AssociatedBindingSetConnectionErrorWithReason) {
AssociatedPingProviderPtr master_ptr;
PingProviderImpl master_impl;
- Binding<AssociatedPingProvider> master_binding(&master_impl, &master_ptr);
+ Binding<AssociatedPingProvider> master_binding(&master_impl,
+ MakeRequest(&master_ptr));
base::RunLoop run_loop;
master_impl.ping_bindings().set_connection_error_with_reason_handler(
@@ -361,15 +592,15 @@ class PingInstanceCounter : public PingService {
};
int PingInstanceCounter::instance_count = 0;
-TEST_F(BindingSetTest, StrongBinding_Destructor) {
+TEST_P(BindingSetTest, StrongBinding_Destructor) {
PingServicePtr ping_a, ping_b;
- auto bindings = base::MakeUnique<StrongBindingSet<PingService>>();
+ auto bindings = std::make_unique<StrongBindingSet<PingService>>();
- bindings->AddBinding(base::MakeUnique<PingInstanceCounter>(),
+ bindings->AddBinding(std::make_unique<PingInstanceCounter>(),
mojo::MakeRequest(&ping_a));
EXPECT_EQ(1, PingInstanceCounter::instance_count);
- bindings->AddBinding(base::MakeUnique<PingInstanceCounter>(),
+ bindings->AddBinding(std::make_unique<PingInstanceCounter>(),
mojo::MakeRequest(&ping_b));
EXPECT_EQ(2, PingInstanceCounter::instance_count);
@@ -377,12 +608,12 @@ TEST_F(BindingSetTest, StrongBinding_Destructor) {
EXPECT_EQ(0, PingInstanceCounter::instance_count);
}
-TEST_F(BindingSetTest, StrongBinding_ConnectionError) {
+TEST_P(BindingSetTest, StrongBinding_ConnectionError) {
PingServicePtr ping_a, ping_b;
StrongBindingSet<PingService> bindings;
- bindings.AddBinding(base::MakeUnique<PingInstanceCounter>(),
+ bindings.AddBinding(std::make_unique<PingInstanceCounter>(),
mojo::MakeRequest(&ping_a));
- bindings.AddBinding(base::MakeUnique<PingInstanceCounter>(),
+ bindings.AddBinding(std::make_unique<PingInstanceCounter>(),
mojo::MakeRequest(&ping_b));
EXPECT_EQ(2, PingInstanceCounter::instance_count);
@@ -395,13 +626,13 @@ TEST_F(BindingSetTest, StrongBinding_ConnectionError) {
EXPECT_EQ(0, PingInstanceCounter::instance_count);
}
-TEST_F(BindingSetTest, StrongBinding_RemoveBinding) {
+TEST_P(BindingSetTest, StrongBinding_RemoveBinding) {
PingServicePtr ping_a, ping_b;
StrongBindingSet<PingService> bindings;
BindingId binding_id_a = bindings.AddBinding(
- base::MakeUnique<PingInstanceCounter>(), mojo::MakeRequest(&ping_a));
+ std::make_unique<PingInstanceCounter>(), mojo::MakeRequest(&ping_a));
BindingId binding_id_b = bindings.AddBinding(
- base::MakeUnique<PingInstanceCounter>(), mojo::MakeRequest(&ping_b));
+ std::make_unique<PingInstanceCounter>(), mojo::MakeRequest(&ping_b));
EXPECT_EQ(2, PingInstanceCounter::instance_count);
EXPECT_TRUE(bindings.RemoveBinding(binding_id_a));
@@ -411,6 +642,8 @@ TEST_F(BindingSetTest, StrongBinding_RemoveBinding) {
EXPECT_EQ(0, PingInstanceCounter::instance_count);
}
+INSTANTIATE_MOJO_BINDINGS_TEST_CASE_P(BindingSetTest);
+
} // namespace
} // namespace test
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/binding_unittest.cc b/mojo/public/cpp/bindings/tests/binding_unittest.cc
index e76993bb68..33b6a5aa81 100644
--- a/mojo/public/cpp/bindings/tests/binding_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/binding_unittest.cc
@@ -13,9 +13,10 @@
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
-#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
+#include "mojo/core/embedder/embedder.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/cpp/bindings/tests/bindings_test_base.h"
#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h"
#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h"
#include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h"
@@ -24,19 +25,6 @@
namespace mojo {
namespace {
-class BindingTestBase : public testing::Test {
- public:
- BindingTestBase() {}
- ~BindingTestBase() override {}
-
- base::MessageLoop& loop() { return loop_; }
-
- private:
- base::MessageLoop loop_;
-
- DISALLOW_COPY_AND_ASSIGN(BindingTestBase);
-};
-
class ServiceImpl : public sample::Service {
public:
explicit ServiceImpl(bool* was_deleted = nullptr)
@@ -79,9 +67,9 @@ base::Callback<void(Args...)> SetFlagAndRunClosure(
// BindingTest -----------------------------------------------------------------
-using BindingTest = BindingTestBase;
+using BindingTest = BindingsTestBase;
-TEST_F(BindingTest, Close) {
+TEST_P(BindingTest, Close) {
bool called = false;
sample::ServicePtr ptr;
auto request = MakeRequest(&ptr);
@@ -98,7 +86,7 @@ TEST_F(BindingTest, Close) {
}
// Tests that destroying a mojo::Binding closes the bound message pipe handle.
-TEST_F(BindingTest, DestroyClosesMessagePipe) {
+TEST_P(BindingTest, DestroyClosesMessagePipe) {
bool encountered_error = false;
ServiceImpl impl;
sample::ServicePtr ptr;
@@ -133,7 +121,7 @@ TEST_F(BindingTest, DestroyClosesMessagePipe) {
// Tests that the binding's connection error handler gets called when the other
// end is closed.
-TEST_F(BindingTest, ConnectionError) {
+TEST_P(BindingTest, ConnectionError) {
bool called = false;
{
ServiceImpl impl;
@@ -154,7 +142,7 @@ TEST_F(BindingTest, ConnectionError) {
// Tests that calling Close doesn't result in the connection error handler being
// called.
-TEST_F(BindingTest, CloseDoesntCallConnectionErrorHandler) {
+TEST_P(BindingTest, CloseDoesntCallConnectionErrorHandler) {
ServiceImpl impl;
sample::ServicePtr ptr;
Binding<sample::Service> binding(&impl, MakeRequest(&ptr));
@@ -198,7 +186,7 @@ class ServiceImplWithBinding : public ServiceImpl {
};
// Tests that the binding may be deleted in the connection error handler.
-TEST_F(BindingTest, SelfDeleteOnConnectionError) {
+TEST_P(BindingTest, SelfDeleteOnConnectionError) {
bool was_deleted = false;
sample::ServicePtr ptr;
// This should delete itself on connection error.
@@ -212,7 +200,7 @@ TEST_F(BindingTest, SelfDeleteOnConnectionError) {
}
// Tests that explicitly calling Unbind followed by rebinding works.
-TEST_F(BindingTest, Unbind) {
+TEST_P(BindingTest, Unbind) {
ServiceImpl impl;
sample::ServicePtr ptr;
Binding<sample::Service> binding(&impl, MakeRequest(&ptr));
@@ -262,14 +250,7 @@ class IntegerAccessorImpl : public sample::IntegerAccessor {
DISALLOW_COPY_AND_ASSIGN(IntegerAccessorImpl);
};
-TEST_F(BindingTest, SetInterfacePtrVersion) {
- IntegerAccessorImpl impl;
- sample::IntegerAccessorPtr ptr;
- Binding<sample::IntegerAccessor> binding(&impl, &ptr);
- EXPECT_EQ(3u, ptr.version());
-}
-
-TEST_F(BindingTest, PauseResume) {
+TEST_P(BindingTest, PauseResume) {
bool called = false;
base::RunLoop run_loop;
sample::ServicePtr ptr;
@@ -292,7 +273,7 @@ TEST_F(BindingTest, PauseResume) {
}
// Verifies the connection error handler is not run while a binding is paused.
-TEST_F(BindingTest, ErrorHandleNotRunWhilePaused) {
+TEST_P(BindingTest, ErrorHandleNotRunWhilePaused) {
bool called = false;
base::RunLoop run_loop;
sample::ServicePtr ptr;
@@ -343,7 +324,7 @@ class CallbackFilter : public MessageReceiver {
~CallbackFilter() override {}
static std::unique_ptr<CallbackFilter> Wrap(const base::Closure& callback) {
- return base::MakeUnique<CallbackFilter>(callback);
+ return std::make_unique<CallbackFilter>(callback);
}
// MessageReceiver:
@@ -358,7 +339,7 @@ class CallbackFilter : public MessageReceiver {
// Verifies that message filters are notified in the order they were added and
// are always notified before a message is dispatched.
-TEST_F(BindingTest, MessageFilter) {
+TEST_P(BindingTest, MessageFilter) {
test::PingServicePtr ptr;
PingServiceImpl impl;
mojo::Binding<test::PingService> binding(&impl, MakeRequest(&ptr));
@@ -389,7 +370,7 @@ void Fail() {
FAIL() << "Unexpected connection error";
}
-TEST_F(BindingTest, FlushForTesting) {
+TEST_P(BindingTest, FlushForTesting) {
bool called = false;
sample::ServicePtr ptr;
auto request = MakeRequest(&ptr);
@@ -408,7 +389,7 @@ TEST_F(BindingTest, FlushForTesting) {
EXPECT_TRUE(called);
}
-TEST_F(BindingTest, FlushForTestingWithClosedPeer) {
+TEST_P(BindingTest, FlushForTestingWithClosedPeer) {
bool called = false;
sample::ServicePtr ptr;
auto request = MakeRequest(&ptr);
@@ -423,7 +404,7 @@ TEST_F(BindingTest, FlushForTestingWithClosedPeer) {
binding.FlushForTesting();
}
-TEST_F(BindingTest, ConnectionErrorWithReason) {
+TEST_P(BindingTest, ConnectionErrorWithReason) {
sample::ServicePtr ptr;
auto request = MakeRequest(&ptr);
ServiceImpl impl;
@@ -455,7 +436,7 @@ struct WeakPtrImplRefTraits {
template <typename T>
using WeakBinding = Binding<T, WeakPtrImplRefTraits<T>>;
-TEST_F(BindingTest, CustomImplPointerType) {
+TEST_P(BindingTest, CustomImplPointerType) {
PingServiceImpl impl;
base::WeakPtrFactory<test::PingService> weak_factory(&impl);
@@ -485,13 +466,76 @@ TEST_F(BindingTest, CustomImplPointerType) {
}
}
+TEST_P(BindingTest, ReportBadMessage) {
+ bool called = false;
+ test::PingServicePtr ptr;
+ auto request = MakeRequest(&ptr);
+ base::RunLoop run_loop;
+ ptr.set_connection_error_handler(
+ SetFlagAndRunClosure(&called, run_loop.QuitClosure()));
+ PingServiceImpl impl;
+ Binding<test::PingService> binding(&impl, std::move(request));
+ impl.set_ping_handler(base::Bind(
+ [](Binding<test::PingService>* binding) {
+ binding->ReportBadMessage("received bad message");
+ },
+ &binding));
+
+ std::string received_error;
+ core::SetDefaultProcessErrorCallback(
+ base::Bind([](std::string* out_error,
+ const std::string& error) { *out_error = error; },
+ &received_error));
+
+ ptr->Ping(base::Bind([] {}));
+ EXPECT_FALSE(called);
+ run_loop.Run();
+ EXPECT_TRUE(called);
+ EXPECT_EQ("received bad message", received_error);
+
+ core::SetDefaultProcessErrorCallback(mojo::core::ProcessErrorCallback());
+}
+
+TEST_P(BindingTest, GetBadMessageCallback) {
+ test::PingServicePtr ptr;
+ auto request = MakeRequest(&ptr);
+ base::RunLoop run_loop;
+ PingServiceImpl impl;
+ ReportBadMessageCallback bad_message_callback;
+
+ std::string received_error;
+ core::SetDefaultProcessErrorCallback(
+ base::Bind([](std::string* out_error,
+ const std::string& error) { *out_error = error; },
+ &received_error));
+
+ {
+ Binding<test::PingService> binding(&impl, std::move(request));
+ impl.set_ping_handler(base::Bind(
+ [](Binding<test::PingService>* binding,
+ ReportBadMessageCallback* out_callback) {
+ *out_callback = binding->GetBadMessageCallback();
+ },
+ &binding, &bad_message_callback));
+ ptr->Ping(run_loop.QuitClosure());
+ run_loop.Run();
+ EXPECT_TRUE(received_error.empty());
+ EXPECT_TRUE(bad_message_callback);
+ }
+
+ std::move(bad_message_callback).Run("delayed bad message");
+ EXPECT_EQ("delayed bad message", received_error);
+
+ core::SetDefaultProcessErrorCallback(mojo::core::ProcessErrorCallback());
+}
+
// StrongBindingTest -----------------------------------------------------------
-using StrongBindingTest = BindingTestBase;
+using StrongBindingTest = BindingsTestBase;
// Tests that destroying a mojo::StrongBinding closes the bound message pipe
// handle but does *not* destroy the implementation object.
-TEST_F(StrongBindingTest, DestroyClosesMessagePipe) {
+TEST_P(StrongBindingTest, DestroyClosesMessagePipe) {
base::RunLoop run_loop;
bool encountered_error = false;
bool was_deleted = false;
@@ -502,7 +546,7 @@ TEST_F(StrongBindingTest, DestroyClosesMessagePipe) {
bool called = false;
base::RunLoop run_loop2;
- auto binding = MakeStrongBinding(base::MakeUnique<ServiceImpl>(&was_deleted),
+ auto binding = MakeStrongBinding(std::make_unique<ServiceImpl>(&was_deleted),
std::move(request));
ptr->Frobinate(
nullptr, sample::Service::BazOptions::REGULAR, nullptr,
@@ -523,7 +567,7 @@ TEST_F(StrongBindingTest, DestroyClosesMessagePipe) {
// Tests the typical case, where the implementation object owns the
// StrongBinding (and should be destroyed on connection error).
-TEST_F(StrongBindingTest, ConnectionErrorDestroysImpl) {
+TEST_P(StrongBindingTest, ConnectionErrorDestroysImpl) {
sample::ServicePtr ptr;
bool was_deleted = false;
// Will delete itself.
@@ -540,12 +584,12 @@ TEST_F(StrongBindingTest, ConnectionErrorDestroysImpl) {
EXPECT_TRUE(was_deleted);
}
-TEST_F(StrongBindingTest, FlushForTesting) {
+TEST_P(StrongBindingTest, FlushForTesting) {
bool called = false;
bool was_deleted = false;
sample::ServicePtr ptr;
auto request = MakeRequest(&ptr);
- auto binding = MakeStrongBinding(base::MakeUnique<ServiceImpl>(&was_deleted),
+ auto binding = MakeStrongBinding(std::make_unique<ServiceImpl>(&was_deleted),
std::move(request));
binding->set_connection_error_handler(base::Bind(&Fail));
@@ -568,12 +612,12 @@ TEST_F(StrongBindingTest, FlushForTesting) {
EXPECT_TRUE(was_deleted);
}
-TEST_F(StrongBindingTest, FlushForTestingWithClosedPeer) {
+TEST_P(StrongBindingTest, FlushForTestingWithClosedPeer) {
bool called = false;
bool was_deleted = false;
sample::ServicePtr ptr;
auto request = MakeRequest(&ptr);
- auto binding = MakeStrongBinding(base::MakeUnique<ServiceImpl>(&was_deleted),
+ auto binding = MakeStrongBinding(std::make_unique<ServiceImpl>(&was_deleted),
std::move(request));
binding->set_connection_error_handler(SetFlagAndRunClosure(&called));
ptr.reset();
@@ -587,11 +631,11 @@ TEST_F(StrongBindingTest, FlushForTestingWithClosedPeer) {
ASSERT_FALSE(binding);
}
-TEST_F(StrongBindingTest, ConnectionErrorWithReason) {
+TEST_P(StrongBindingTest, ConnectionErrorWithReason) {
sample::ServicePtr ptr;
auto request = MakeRequest(&ptr);
auto binding =
- MakeStrongBinding(base::MakeUnique<ServiceImpl>(), std::move(request));
+ MakeStrongBinding(std::make_unique<ServiceImpl>(), std::move(request));
base::RunLoop run_loop;
binding->set_connection_error_with_reason_handler(base::Bind(
[](const base::Closure& quit_closure, uint32_t custom_reason,
@@ -607,5 +651,8 @@ TEST_F(StrongBindingTest, ConnectionErrorWithReason) {
run_loop.Run();
}
+INSTANTIATE_MOJO_BINDINGS_TEST_CASE_P(BindingTest);
+INSTANTIATE_MOJO_BINDINGS_TEST_CASE_P(StrongBindingTest);
+
} // namespace
} // mojo
diff --git a/mojo/public/cpp/bindings/tests/bindings_perftest.cc b/mojo/public/cpp/bindings/tests/bindings_perftest.cc
index 65b3c8c1d4..c54d7ca6a9 100644
--- a/mojo/public/cpp/bindings/tests/bindings_perftest.cc
+++ b/mojo/public/cpp/bindings/tests/bindings_perftest.cc
@@ -12,7 +12,6 @@
#include "base/time/time.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/interface_endpoint_client.h"
-#include "mojo/public/cpp/bindings/lib/message_builder.h"
#include "mojo/public/cpp/bindings/lib/multiplex_router.h"
#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/cpp/test_support/test_support.h"
@@ -154,8 +153,8 @@ class PingPongPaddle : public MessageReceiverWithResponderStatus {
}
}
- internal::MessageBuilder builder(count, 0, 8, 0);
- bool result = sender_->Accept(builder.message());
+ Message reply(count, 0, 0, 0, nullptr);
+ bool result = sender_->Accept(&reply);
DCHECK(result);
return true;
}
@@ -174,8 +173,8 @@ class PingPongPaddle : public MessageReceiverWithResponderStatus {
quit_closure_ = run_loop.QuitClosure();
start_time_ = base::TimeTicks::Now();
- internal::MessageBuilder builder(0, 0, 8, 0);
- bool result = sender_->Accept(builder.message());
+ Message message(0, 0, 0, 0, nullptr);
+ bool result = sender_->Accept(&message);
DCHECK(result);
run_loop.Run();
@@ -264,9 +263,8 @@ TEST_F(MojoBindingsPerftest, MultiplexRouterDispatchCost) {
receiver.Reset();
base::TimeTicks start_time = base::TimeTicks::Now();
for (size_t j = 0; j < kIterations[i]; ++j) {
- internal::MessageBuilder builder(0, 0, 8, 0);
- bool result =
- router->SimulateReceivingMessageForTesting(builder.message());
+ Message message(0, 0, 8, 0, nullptr);
+ bool result = router->SimulateReceivingMessageForTesting(&message);
DCHECK(result);
}
diff --git a/mojo/public/cpp/bindings/tests/bindings_test_base.cc b/mojo/public/cpp/bindings/tests/bindings_test_base.cc
new file mode 100644
index 0000000000..02430475f6
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/bindings_test_base.cc
@@ -0,0 +1,40 @@
+// 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/tests/bindings_test_base.h"
+
+#include "mojo/public/cpp/bindings/connector.h"
+
+namespace mojo {
+
+BindingsTestBase::BindingsTestBase() {
+ SetupSerializationBehavior(GetParam());
+}
+
+BindingsTestBase::~BindingsTestBase() = default;
+
+// static
+void BindingsTestBase::SetupSerializationBehavior(
+ BindingsTestSerializationMode mode) {
+ switch (mode) {
+ case BindingsTestSerializationMode::kSerializeBeforeSend:
+ Connector::OverrideDefaultSerializationBehaviorForTesting(
+ Connector::OutgoingSerializationMode::kEager,
+ Connector::IncomingSerializationMode::kDispatchAsIs);
+ break;
+ case BindingsTestSerializationMode::kSerializeBeforeDispatch:
+ Connector::OverrideDefaultSerializationBehaviorForTesting(
+ Connector::OutgoingSerializationMode::kLazy,
+ Connector::IncomingSerializationMode ::
+ kSerializeBeforeDispatchForTesting);
+ break;
+ case BindingsTestSerializationMode::kNeverSerialize:
+ Connector::OverrideDefaultSerializationBehaviorForTesting(
+ Connector::OutgoingSerializationMode::kLazy,
+ Connector::IncomingSerializationMode::kDispatchAsIs);
+ break;
+ }
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/bindings_test_base.h b/mojo/public/cpp/bindings/tests/bindings_test_base.h
new file mode 100644
index 0000000000..5f5c779579
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/bindings_test_base.h
@@ -0,0 +1,51 @@
+// 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_TESTS_BINDINGS_TEST_BASE_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_BINDINGS_TEST_BASE_H_
+
+#include "base/test/scoped_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+
+// Used to parameterize tests which inherit from BindingsTestBase to exercise
+// various message serialization-related code paths for intra-process bindings
+// usage.
+enum class BindingsTestSerializationMode {
+ // Messages should be serialized immediately before sending.
+ kSerializeBeforeSend,
+
+ // Messages should be serialized immediately before dispatching.
+ kSerializeBeforeDispatch,
+
+ // Messages should never be serialized.
+ kNeverSerialize,
+};
+
+class BindingsTestBase
+ : public testing::Test,
+ public testing::WithParamInterface<BindingsTestSerializationMode> {
+ public:
+ BindingsTestBase();
+ ~BindingsTestBase();
+
+ // Helper which other test fixtures can use.
+ static void SetupSerializationBehavior(BindingsTestSerializationMode mode);
+
+ private:
+ base::test::ScopedTaskEnvironment task_environment_;
+};
+
+} // namespace mojo
+
+#define INSTANTIATE_MOJO_BINDINGS_TEST_CASE_P(fixture) \
+ INSTANTIATE_TEST_CASE_P( \
+ , fixture, \
+ testing::Values( \
+ mojo::BindingsTestSerializationMode::kSerializeBeforeSend, \
+ mojo::BindingsTestSerializationMode::kSerializeBeforeDispatch, \
+ mojo::BindingsTestSerializationMode::kNeverSerialize))
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_BINDINGS_TEST_BASE_H_
diff --git a/mojo/public/cpp/bindings/tests/buffer_unittest.cc b/mojo/public/cpp/bindings/tests/buffer_unittest.cc
index d75bdd0785..2370454bff 100644
--- a/mojo/public/cpp/bindings/tests/buffer_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/buffer_unittest.cc
@@ -14,80 +14,20 @@ namespace mojo {
namespace test {
namespace {
-bool IsZero(void* p_buf, size_t size) {
- char* buf = reinterpret_cast<char*>(p_buf);
- for (size_t i = 0; i < size; ++i) {
- if (buf[i] != 0)
- return false;
- }
- return true;
-}
-
// Tests that FixedBuffer allocates memory aligned to 8 byte boundaries.
TEST(FixedBufferTest, Alignment) {
internal::FixedBufferForTesting buf(internal::Align(10) * 2);
ASSERT_EQ(buf.size(), 16u * 2);
- void* a = buf.Allocate(10);
- ASSERT_TRUE(a);
- EXPECT_TRUE(IsZero(a, 10));
- EXPECT_EQ(0, reinterpret_cast<ptrdiff_t>(a) % 8);
+ size_t a = buf.Allocate(10);
+ EXPECT_EQ(0u, a);
- void* b = buf.Allocate(10);
- ASSERT_TRUE(b);
- EXPECT_TRUE(IsZero(b, 10));
- EXPECT_EQ(0, reinterpret_cast<ptrdiff_t>(b) % 8);
+ size_t b = buf.Allocate(10);
+ ASSERT_EQ(16u, b);
// Any more allocations would result in an assert, but we can't test that.
}
-// Tests that FixedBufferForTesting::Leak passes ownership to the caller.
-TEST(FixedBufferTest, Leak) {
- void* ptr = nullptr;
- void* buf_ptr = nullptr;
- {
- internal::FixedBufferForTesting buf(8);
- ASSERT_EQ(8u, buf.size());
-
- ptr = buf.Allocate(8);
- ASSERT_TRUE(ptr);
- buf_ptr = buf.Leak();
-
- // The buffer should point to the first element allocated.
- // TODO(mpcomplete): Is this a reasonable expectation?
- EXPECT_EQ(ptr, buf_ptr);
-
- // The FixedBufferForTesting should be empty now.
- EXPECT_EQ(0u, buf.size());
- EXPECT_FALSE(buf.Leak());
- }
-
- // Since we called Leak, ptr is still writable after FixedBufferForTesting
- // went out of scope.
- memset(ptr, 1, 8);
- free(buf_ptr);
-}
-
-#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON)
-TEST(FixedBufferTest, TooBig) {
- internal::FixedBufferForTesting buf(24);
-
- // A little bit too large.
- EXPECT_EQ(reinterpret_cast<void*>(0), buf.Allocate(32));
-
- // Move the cursor forward.
- EXPECT_NE(reinterpret_cast<void*>(0), buf.Allocate(16));
-
- // A lot too large.
- EXPECT_EQ(reinterpret_cast<void*>(0),
- buf.Allocate(std::numeric_limits<size_t>::max() - 1024u));
-
- // A lot too large, leading to possible integer overflow.
- EXPECT_EQ(reinterpret_cast<void*>(0),
- buf.Allocate(std::numeric_limits<size_t>::max() - 8u));
-}
-#endif
-
} // namespace
} // namespace test
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/callback_helpers_unittest.cc b/mojo/public/cpp/bindings/tests/callback_helpers_unittest.cc
new file mode 100644
index 0000000000..fbb0dc7e1b
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/callback_helpers_unittest.cc
@@ -0,0 +1,202 @@
+// 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/callback_helpers.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/memory/ptr_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+
+namespace {
+
+void SetBool(bool* var, bool val) {
+ *var = val;
+}
+
+void SetBoolFromRawPtr(bool* var, bool* val) {
+ *var = *val;
+}
+
+void SetIntegers(int* a_var, int* b_var, int a_val, int b_val) {
+ *a_var = a_val;
+ *b_var = b_val;
+}
+
+void SetIntegerFromUniquePtr(int* var, std::unique_ptr<int> val) {
+ *var = *val;
+}
+
+void SetString(std::string* var, const std::string val) {
+ *var = val;
+}
+
+void CallClosure(base::OnceClosure cl) {
+ std::move(cl).Run();
+}
+
+} // namespace
+
+TEST(CallbackWithDeleteTest, SetIntegers_Run) {
+ int a = 0;
+ int b = 0;
+ auto cb =
+ WrapCallbackWithDropHandler(base::BindOnce(&SetIntegers, &a, &b),
+ base::BindOnce(&SetIntegers, &a, &b, 3, 4));
+ std::move(cb).Run(1, 2);
+ EXPECT_EQ(a, 1);
+ EXPECT_EQ(b, 2);
+}
+
+TEST(CallbackWithDeleteTest, SetIntegers_Destruction) {
+ int a = 0;
+ int b = 0;
+ {
+ auto cb =
+ WrapCallbackWithDropHandler(base::BindOnce(&SetIntegers, &a, &b),
+ base::BindOnce(&SetIntegers, &a, &b, 3, 4));
+ }
+ EXPECT_EQ(a, 3);
+ EXPECT_EQ(b, 4);
+}
+
+TEST(CallbackWithDefaultTest, CallClosure_Run) {
+ int a = 0;
+ int b = 0;
+ auto cb = WrapCallbackWithDefaultInvokeIfNotRun(
+ base::BindOnce(&CallClosure), base::BindOnce(&SetIntegers, &a, &b, 3, 4));
+ std::move(cb).Run(base::BindOnce(&SetIntegers, &a, &b, 1, 2));
+ EXPECT_EQ(a, 1);
+ EXPECT_EQ(b, 2);
+}
+
+TEST(CallbackWithDefaultTest, CallClosure_Destruction) {
+ int a = 0;
+ int b = 0;
+ {
+ auto cb = WrapCallbackWithDefaultInvokeIfNotRun(
+ base::BindOnce(&CallClosure),
+ base::BindOnce(&SetIntegers, &a, &b, 3, 4));
+ }
+ EXPECT_EQ(a, 3);
+ EXPECT_EQ(b, 4);
+}
+
+TEST(CallbackWithDefaultTest, Closure_Run) {
+ bool a = false;
+ auto cb =
+ WrapCallbackWithDefaultInvokeIfNotRun(base::BindOnce(&SetBool, &a, true));
+ std::move(cb).Run();
+ EXPECT_TRUE(a);
+}
+
+TEST(CallbackWithDefaultTest, Closure_Destruction) {
+ bool a = false;
+ {
+ auto cb = WrapCallbackWithDefaultInvokeIfNotRun(
+ base::BindOnce(&SetBool, &a, true));
+ }
+ EXPECT_TRUE(a);
+}
+
+TEST(CallbackWithDefaultTest, SetBool_Run) {
+ bool a = false;
+ auto cb =
+ WrapCallbackWithDefaultInvokeIfNotRun(base::BindOnce(&SetBool, &a), true);
+ std::move(cb).Run(true);
+ EXPECT_TRUE(a);
+}
+
+TEST(CallbackWithDefaultTest, SetBoolFromRawPtr_Run) {
+ bool a = false;
+ bool* b = new bool(false);
+ bool c = true;
+ auto cb = WrapCallbackWithDefaultInvokeIfNotRun(
+ base::BindOnce(&SetBoolFromRawPtr, &a), base::Owned(b));
+ std::move(cb).Run(&c);
+ EXPECT_TRUE(a);
+}
+
+TEST(CallbackWithDefaultTest, SetBoolFromRawPtr_Destruction) {
+ bool a = false;
+ bool* b = new bool(true);
+ {
+ auto cb = WrapCallbackWithDefaultInvokeIfNotRun(
+ base::BindOnce(&SetBoolFromRawPtr, &a), base::Owned(b));
+ }
+ EXPECT_TRUE(a);
+}
+
+TEST(CallbackWithDefaultTest, SetBool_Destruction) {
+ bool a = false;
+ {
+ auto cb = WrapCallbackWithDefaultInvokeIfNotRun(
+ base::BindOnce(&SetBool, &a), true);
+ }
+ EXPECT_TRUE(a);
+}
+
+TEST(CallbackWithDefaultTest, SetIntegers_Run) {
+ int a = 0;
+ int b = 0;
+ auto cb = WrapCallbackWithDefaultInvokeIfNotRun(
+ base::BindOnce(&SetIntegers, &a, &b), 3, 4);
+ std::move(cb).Run(1, 2);
+ EXPECT_EQ(a, 1);
+ EXPECT_EQ(b, 2);
+}
+
+TEST(CallbackWithDefaultTest, SetIntegers_Destruction) {
+ int a = 0;
+ int b = 0;
+ {
+ auto cb = WrapCallbackWithDefaultInvokeIfNotRun(
+ base::BindOnce(&SetIntegers, &a, &b), 3, 4);
+ }
+ EXPECT_EQ(a, 3);
+ EXPECT_EQ(b, 4);
+}
+
+TEST(CallbackWithDefaultTest, SetIntegerFromUniquePtr_Run) {
+ int a = 0;
+ auto cb = WrapCallbackWithDefaultInvokeIfNotRun(
+ base::BindOnce(&SetIntegerFromUniquePtr, &a), std::make_unique<int>(1));
+ std::move(cb).Run(std::make_unique<int>(2));
+ EXPECT_EQ(a, 2);
+}
+
+TEST(CallbackWithDefaultTest, SetIntegerFromUniquePtr_Destruction) {
+ int a = 0;
+ {
+ auto cb = WrapCallbackWithDefaultInvokeIfNotRun(
+ base::BindOnce(&SetIntegerFromUniquePtr, &a), std::make_unique<int>(1));
+ }
+ EXPECT_EQ(a, 1);
+}
+
+TEST(CallbackWithDefaultTest, SetString_Run) {
+ std::string a;
+ auto cb = WrapCallbackWithDefaultInvokeIfNotRun(
+ base::BindOnce(&SetString, &a), "hello");
+ std::move(cb).Run("world");
+ EXPECT_EQ(a, "world");
+}
+
+TEST(CallbackWithDefaultTest, SetString_Destruction) {
+ std::string a;
+ {
+ auto cb = WrapCallbackWithDefaultInvokeIfNotRun(
+ base::BindOnce(&SetString, &a), "hello");
+ }
+ EXPECT_EQ(a, "hello");
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/connector_unittest.cc b/mojo/public/cpp/bindings/tests/connector_unittest.cc
index 74ecb7a9ee..68f51fc5bd 100644
--- a/mojo/public/cpp/bindings/tests/connector_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/connector_unittest.cc
@@ -16,7 +16,7 @@
#include "base/run_loop.h"
#include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.h"
-#include "mojo/public/cpp/bindings/lib/message_builder.h"
+#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/cpp/bindings/tests/message_queue.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -91,18 +91,17 @@ class ConnectorTest : public testing::Test {
public:
ConnectorTest() {}
- void SetUp() override {
- CreateMessagePipe(nullptr, &handle0_, &handle1_);
- }
+ void SetUp() override { CreateMessagePipe(nullptr, &handle0_, &handle1_); }
void TearDown() override {}
- void AllocMessage(const char* text, Message* message) {
- size_t payload_size = strlen(text) + 1; // Plus null terminator.
- internal::MessageBuilder builder(1, 0, payload_size, 0);
- memcpy(builder.buffer()->Allocate(payload_size), text, payload_size);
-
- *message = std::move(*builder.message());
+ Message CreateMessage(
+ const char* text,
+ std::vector<ScopedHandle> handles = std::vector<ScopedHandle>()) {
+ const size_t size = strlen(text) + 1; // Plus null terminator.
+ Message message(1, 0, size, 0, &handles);
+ memcpy(message.payload_buffer()->AllocateAndGet(size), text, size);
+ return message;
}
protected:
@@ -120,10 +119,7 @@ TEST_F(ConnectorTest, Basic) {
base::ThreadTaskRunnerHandle::Get());
const char kText[] = "hello world";
-
- Message message;
- AllocMessage(kText, &message);
-
+ Message message = CreateMessage(kText);
connector0.Accept(&message);
base::RunLoop run_loop;
@@ -149,10 +145,7 @@ TEST_F(ConnectorTest, Basic_Synchronous) {
base::ThreadTaskRunnerHandle::Get());
const char kText[] = "hello world";
-
- Message message;
- AllocMessage(kText, &message);
-
+ Message message = CreateMessage(kText);
connector0.Accept(&message);
MessageAccumulator accumulator;
@@ -181,10 +174,7 @@ TEST_F(ConnectorTest, Basic_EarlyIncomingReceiver) {
connector1.set_incoming_receiver(&accumulator);
const char kText[] = "hello world";
-
- Message message;
- AllocMessage(kText, &message);
-
+ Message message = CreateMessage(kText);
connector0.Accept(&message);
run_loop.Run();
@@ -206,11 +196,8 @@ TEST_F(ConnectorTest, Basic_TwoMessages) {
base::ThreadTaskRunnerHandle::Get());
const char* kText[] = {"hello", "world"};
-
for (size_t i = 0; i < arraysize(kText); ++i) {
- Message message;
- AllocMessage(kText[i], &message);
-
+ Message message = CreateMessage(kText[i]);
connector0.Accept(&message);
}
@@ -241,11 +228,8 @@ TEST_F(ConnectorTest, Basic_TwoMessages_Synchronous) {
base::ThreadTaskRunnerHandle::Get());
const char* kText[] = {"hello", "world"};
-
for (size_t i = 0; i < arraysize(kText); ++i) {
- Message message;
- AllocMessage(kText[i], &message);
-
+ Message message = CreateMessage(kText[i]);
connector0.Accept(&message);
}
@@ -271,9 +255,7 @@ TEST_F(ConnectorTest, WriteToClosedPipe) {
base::ThreadTaskRunnerHandle::Get());
const char kText[] = "hello world";
-
- Message message;
- AllocMessage(kText, &message);
+ Message message = CreateMessage(kText);
// Close the other end of the pipe.
handle1_.reset();
@@ -304,17 +286,13 @@ TEST_F(ConnectorTest, MessageWithHandles) {
const char kText[] = "hello world";
- Message message1;
- AllocMessage(kText, &message1);
-
MessagePipe pipe;
- message1.mutable_handles()->push_back(pipe.handle0.release());
+ std::vector<ScopedHandle> handles;
+ handles.emplace_back(ScopedHandle::From(std::move(pipe.handle0)));
+ Message message1 = CreateMessage(kText, std::move(handles));
connector0.Accept(&message1);
- // The message should have been transferred, releasing the handles.
- EXPECT_TRUE(message1.handles()->empty());
-
base::RunLoop run_loop;
MessageAccumulator accumulator(run_loop.QuitClosure());
connector1.set_incoming_receiver(&accumulator);
@@ -333,21 +311,16 @@ TEST_F(ConnectorTest, MessageWithHandles) {
// Now send a message to the transferred handle and confirm it's sent through
// to the orginal pipe.
- // TODO(vtl): Do we need a better way of "downcasting" the handle types?
- ScopedMessagePipeHandle smph;
- smph.reset(MessagePipeHandle(message_received.handles()->front().value()));
- message_received.mutable_handles()->front() = Handle();
- // |smph| now owns this handle.
-
- Connector connector_received(std::move(smph), Connector::SINGLE_THREADED_SEND,
+ auto pipe_handle = ScopedMessagePipeHandle::From(
+ std::move(message_received.mutable_handles()->front()));
+ Connector connector_received(std::move(pipe_handle),
+ Connector::SINGLE_THREADED_SEND,
base::ThreadTaskRunnerHandle::Get());
Connector connector_original(std::move(pipe.handle1),
Connector::SINGLE_THREADED_SEND,
base::ThreadTaskRunnerHandle::Get());
- Message message2;
- AllocMessage(kText, &message2);
-
+ Message message2 = CreateMessage(kText);
connector_received.Accept(&message2);
base::RunLoop run_loop2;
MessageAccumulator accumulator2(run_loop2.QuitClosure());
@@ -379,10 +352,7 @@ TEST_F(ConnectorTest, WaitForIncomingMessageWithDeletion) {
base::ThreadTaskRunnerHandle::Get());
const char kText[] = "hello world";
-
- Message message;
- AllocMessage(kText, &message);
-
+ Message message = CreateMessage(kText);
connector0.Accept(&message);
ConnectorDeletingMessageAccumulator accumulator(&connector1);
@@ -408,11 +378,8 @@ TEST_F(ConnectorTest, WaitForIncomingMessageWithReentrancy) {
base::ThreadTaskRunnerHandle::Get());
const char* kText[] = {"hello", "world"};
-
for (size_t i = 0; i < arraysize(kText); ++i) {
- Message message;
- AllocMessage(kText[i], &message);
-
+ Message message = CreateMessage(kText[i]);
connector0.Accept(&message);
}
@@ -448,22 +415,17 @@ TEST_F(ConnectorTest, RaiseError) {
Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND,
base::ThreadTaskRunnerHandle::Get());
bool error_handler_called0 = false;
- connector0.set_connection_error_handler(
- base::Bind(&ForwardErrorHandler, &error_handler_called0,
- run_loop.QuitClosure()));
+ connector0.set_connection_error_handler(base::Bind(
+ &ForwardErrorHandler, &error_handler_called0, run_loop.QuitClosure()));
Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND,
base::ThreadTaskRunnerHandle::Get());
bool error_handler_called1 = false;
- connector1.set_connection_error_handler(
- base::Bind(&ForwardErrorHandler, &error_handler_called1,
- run_loop2.QuitClosure()));
+ connector1.set_connection_error_handler(base::Bind(
+ &ForwardErrorHandler, &error_handler_called1, run_loop2.QuitClosure()));
const char kText[] = "hello world";
-
- Message message;
- AllocMessage(kText, &message);
-
+ Message message = CreateMessage(kText);
connector0.Accept(&message);
connector0.RaiseError();
@@ -514,18 +476,16 @@ TEST_F(ConnectorTest, PauseWithQueuedMessages) {
const char kText[] = "hello world";
// Queue up two messages.
- Message message;
- AllocMessage(kText, &message);
+ Message message = CreateMessage(kText);
connector0.Accept(&message);
- AllocMessage(kText, &message);
+ message = CreateMessage(kText);
connector0.Accept(&message);
base::RunLoop run_loop;
// Configure the accumulator such that it pauses after the first message is
// received.
- MessageAccumulator accumulator(
- base::Bind(&PauseConnectorAndRunClosure, &connector1,
- run_loop.QuitClosure()));
+ MessageAccumulator accumulator(base::Bind(
+ &PauseConnectorAndRunClosure, &connector1, run_loop.QuitClosure()));
connector1.set_incoming_receiver(&accumulator);
run_loop.Run();
@@ -537,9 +497,7 @@ TEST_F(ConnectorTest, PauseWithQueuedMessages) {
void AccumulateWithNestedLoop(MessageAccumulator* accumulator,
const base::Closure& closure) {
- base::RunLoop nested_run_loop;
- base::MessageLoop::ScopedNestableTaskAllower allow(
- base::MessageLoop::current());
+ base::RunLoop nested_run_loop(base::RunLoop::Type::kNestableTasksAllowed);
accumulator->set_closure(nested_run_loop.QuitClosure());
nested_run_loop.Run();
closure.Run();
@@ -554,10 +512,9 @@ TEST_F(ConnectorTest, ProcessWhenNested) {
const char kText[] = "hello world";
// Queue up two messages.
- Message message;
- AllocMessage(kText, &message);
+ Message message = CreateMessage(kText);
connector0.Accept(&message);
- AllocMessage(kText, &message);
+ message = CreateMessage(kText);
connector0.Accept(&message);
base::RunLoop run_loop;
diff --git a/mojo/public/cpp/bindings/tests/constant_unittest.cc b/mojo/public/cpp/bindings/tests/constant_unittest.cc
index caa6464cf4..12a8615e41 100644
--- a/mojo/public/cpp/bindings/tests/constant_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/constant_unittest.cc
@@ -53,7 +53,7 @@ TEST(ConstantTest, InterfaceConstants) {
EXPECT_EQ(base::StringPiece(InterfaceWithConstants::kStringValue),
"interface test string contents");
EXPECT_EQ(base::StringPiece(InterfaceWithConstants::Name_),
- "mojo::test::InterfaceWithConstants");
+ "mojo.test.InterfaceWithConstants");
}
} // namespace test
diff --git a/mojo/public/cpp/bindings/tests/data_view_unittest.cc b/mojo/public/cpp/bindings/tests/data_view_unittest.cc
index 0ebfda5d12..552bc6c86f 100644
--- a/mojo/public/cpp/bindings/tests/data_view_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/data_view_unittest.cc
@@ -26,22 +26,18 @@ class DataViewTest : public testing::Test {
struct DataViewHolder {
std::unique_ptr<TestStructDataView> data_view;
- std::unique_ptr<mojo::internal::FixedBufferForTesting> buf;
+ mojo::Message message;
mojo::internal::SerializationContext context;
};
std::unique_ptr<DataViewHolder> SerializeTestStruct(TestStructPtr input) {
- std::unique_ptr<DataViewHolder> result(new DataViewHolder);
-
- size_t size = mojo::internal::PrepareToSerialize<TestStructDataView>(
- input, &result->context);
-
- result->buf.reset(new mojo::internal::FixedBufferForTesting(size));
- internal::TestStruct_Data* data = nullptr;
- mojo::internal::Serialize<TestStructDataView>(input, result->buf.get(), &data,
- &result->context);
-
- result->data_view.reset(new TestStructDataView(data, &result->context));
+ auto result = std::make_unique<DataViewHolder>();
+ result->message = Message(0, 0, 0, 0, nullptr);
+ internal::TestStruct_Data::BufferWriter writer;
+ mojo::internal::Serialize<TestStructDataView>(
+ input, result->message.payload_buffer(), &writer, &result->context);
+ result->data_view =
+ std::make_unique<TestStructDataView>(writer.data(), &result->context);
return result;
}
@@ -94,21 +90,24 @@ TEST_F(DataViewTest, NestedStruct) {
TEST_F(DataViewTest, NativeStruct) {
TestStructPtr obj(TestStruct::New());
- obj->f_native_struct = NativeStruct::New();
+ obj->f_native_struct = native::NativeStruct::New();
obj->f_native_struct->data = std::vector<uint8_t>({3, 2, 1});
auto data_view_holder = SerializeTestStruct(std::move(obj));
auto& data_view = *data_view_holder->data_view;
- NativeStructDataView struct_data_view;
+ native::NativeStructDataView struct_data_view;
data_view.GetFNativeStructDataView(&struct_data_view);
- ASSERT_FALSE(struct_data_view.is_null());
- ASSERT_EQ(3u, struct_data_view.size());
- EXPECT_EQ(3, struct_data_view[0]);
- EXPECT_EQ(2, struct_data_view[1]);
- EXPECT_EQ(1, struct_data_view[2]);
- EXPECT_EQ(3, *struct_data_view.data());
+ ArrayDataView<uint8_t> data_data_view;
+ struct_data_view.GetDataDataView(&data_data_view);
+
+ ASSERT_FALSE(data_data_view.is_null());
+ ASSERT_EQ(3u, data_data_view.size());
+ EXPECT_EQ(3, data_data_view[0]);
+ EXPECT_EQ(2, data_data_view[1]);
+ EXPECT_EQ(1, data_data_view[2]);
+ EXPECT_EQ(3, *data_data_view.data());
}
TEST_F(DataViewTest, BoolArray) {
@@ -166,11 +165,11 @@ TEST_F(DataViewTest, EnumArray) {
}
TEST_F(DataViewTest, InterfaceArray) {
- TestInterfacePtr ptr;
- TestInterfaceImpl impl(MakeRequest(&ptr));
+ TestInterfacePtrInfo ptr_info;
+ TestInterfaceImpl impl(MakeRequest(&ptr_info));
TestStructPtr obj(TestStruct::New());
- obj->f_interface_array.push_back(std::move(ptr));
+ obj->f_interface_array.push_back(std::move(ptr_info));
auto data_view_holder = SerializeTestStruct(std::move(obj));
auto& data_view = *data_view_holder->data_view;
diff --git a/mojo/public/cpp/bindings/tests/e2e_perftest.cc b/mojo/public/cpp/bindings/tests/e2e_perftest.cc
index bc69e0f727..037b3d5910 100644
--- a/mojo/public/cpp/bindings/tests/e2e_perftest.cc
+++ b/mojo/public/cpp/bindings/tests/e2e_perftest.cc
@@ -9,12 +9,13 @@
#include "base/callback.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_current.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/perf_time_logger.h"
#include "base/threading/thread_task_runner_handle.h"
-#include "mojo/edk/embedder/embedder.h"
-#include "mojo/edk/test/mojo_test_base.h"
+#include "mojo/core/embedder/embedder.h"
+#include "mojo/core/test/mojo_test_base.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -82,7 +83,7 @@ void PingPongTest::RunTest(int iterations, int batch_size, int message_size) {
current_iterations_ = 0;
calls_outstanding_ = 0;
- base::MessageLoop::current()->SetNestableTasksAllowed(true);
+ base::MessageLoopCurrent::Get()->SetNestableTasksAllowed(true);
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
base::ThreadTaskRunnerHandle::Get()->PostTask(
@@ -112,7 +113,7 @@ void PingPongTest::OnPingDone(const std::string& reply) {
DoPing();
}
-class MojoE2EPerftest : public edk::test::MojoTestBase {
+class MojoE2EPerftest : public core::test::MojoTestBase {
public:
void RunTestOnTaskRunner(base::TaskRunner* runner,
MojoHandle client_mp,
@@ -122,8 +123,9 @@ class MojoE2EPerftest : public edk::test::MojoTestBase {
} else {
base::RunLoop run_loop;
runner->PostTaskAndReply(
- FROM_HERE, base::Bind(&MojoE2EPerftest::RunTests,
- base::Unretained(this), client_mp, test_name),
+ FROM_HERE,
+ base::Bind(&MojoE2EPerftest::RunTests, base::Unretained(this),
+ client_mp, test_name),
run_loop.QuitClosure());
run_loop.Run();
}
@@ -161,17 +163,17 @@ class MojoE2EPerftest : public edk::test::MojoTestBase {
void CreateAndRunService(InterfaceRequest<test::EchoService> request,
const base::Closure& cb) {
- MakeStrongBinding(base::MakeUnique<EchoServiceImpl>(cb), std::move(request));
+ MakeStrongBinding(std::make_unique<EchoServiceImpl>(cb), std::move(request));
}
DEFINE_TEST_CLIENT_TEST_WITH_PIPE(PingService, MojoE2EPerftest, mp) {
MojoHandle service_mp;
EXPECT_EQ("hello", ReadMessageWithHandles(mp, &service_mp, 1));
- InterfaceRequest<test::EchoService> request;
- request.Bind(ScopedMessagePipeHandle(MessagePipeHandle(service_mp)));
+ auto request = InterfaceRequest<test::EchoService>(
+ ScopedMessagePipeHandle(MessagePipeHandle(service_mp)));
base::RunLoop run_loop;
- edk::GetIOTaskRunner()->PostTask(
+ core::GetIOTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&CreateAndRunService, base::Passed(&request),
base::Bind(base::IgnoreResult(&base::TaskRunner::PostTask),
@@ -181,23 +183,23 @@ DEFINE_TEST_CLIENT_TEST_WITH_PIPE(PingService, MojoE2EPerftest, mp) {
}
TEST_F(MojoE2EPerftest, MultiProcessEchoMainThread) {
- RUN_CHILD_ON_PIPE(PingService, mp)
+ RunTestClient("PingService", [&](MojoHandle mp) {
MojoHandle client_mp, service_mp;
CreateMessagePipe(&client_mp, &service_mp);
WriteMessageWithHandles(mp, "hello", &service_mp, 1);
RunTestOnTaskRunner(message_loop_.task_runner().get(), client_mp,
"MultiProcessEchoMainThread");
- END_CHILD()
+ });
}
TEST_F(MojoE2EPerftest, MultiProcessEchoIoThread) {
- RUN_CHILD_ON_PIPE(PingService, mp)
+ RunTestClient("PingService", [&](MojoHandle mp) {
MojoHandle client_mp, service_mp;
CreateMessagePipe(&client_mp, &service_mp);
WriteMessageWithHandles(mp, "hello", &service_mp, 1);
- RunTestOnTaskRunner(edk::GetIOTaskRunner().get(), client_mp,
+ RunTestOnTaskRunner(core::GetIOTaskRunner().get(), client_mp,
"MultiProcessEchoIoThread");
- END_CHILD()
+ });
}
} // namespace
diff --git a/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc b/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc
index ef977af935..509b0feb71 100644
--- a/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc
@@ -6,10 +6,10 @@
#include <utility>
#include "base/memory/ptr_util.h"
-#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/cpp/bindings/tests/bindings_test_base.h"
#include "mojo/public/cpp/system/wait.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "mojo/public/interfaces/bindings/tests/sample_factory.mojom.h"
@@ -99,8 +99,10 @@ class SampleFactoryImpl : public sample::Factory {
sample::ResponsePtr response(sample::Response::New(2, std::move(pipe0)));
callback.Run(std::move(response), text1);
- if (request->obj)
- request->obj->DoSomething();
+ if (request->obj) {
+ imported::ImportedInterfacePtr proxy(std::move(request->obj));
+ proxy->DoSomething();
+ }
}
void DoStuff2(ScopedDataPipeConsumerHandle pipe,
@@ -115,15 +117,12 @@ class SampleFactoryImpl : public sample::Factory {
mojo::Wait(pipe.get(), MOJO_HANDLE_SIGNAL_READABLE, &state));
ASSERT_TRUE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE);
ASSERT_EQ(MOJO_RESULT_OK,
- ReadDataRaw(
- pipe.get(), nullptr, &data_size, MOJO_READ_DATA_FLAG_QUERY));
+ pipe->ReadData(nullptr, &data_size, MOJO_READ_DATA_FLAG_QUERY));
ASSERT_NE(0, static_cast<int>(data_size));
char data[64];
ASSERT_LT(static_cast<int>(data_size), 64);
- ASSERT_EQ(
- MOJO_RESULT_OK,
- ReadDataRaw(
- pipe.get(), data, &data_size, MOJO_READ_DATA_FLAG_ALL_OR_NONE));
+ ASSERT_EQ(MOJO_RESULT_OK, pipe->ReadData(data, &data_size,
+ MOJO_READ_DATA_FLAG_ALL_OR_NONE));
callback.Run(data);
}
@@ -131,7 +130,7 @@ class SampleFactoryImpl : public sample::Factory {
void CreateNamedObject(
InterfaceRequest<sample::NamedObject> object_request) override {
EXPECT_TRUE(object_request.is_pending());
- MakeStrongBinding(base::MakeUnique<SampleNamedObjectImpl>(),
+ MakeStrongBinding(std::make_unique<SampleNamedObjectImpl>(),
std::move(object_request));
}
@@ -150,7 +149,7 @@ class SampleFactoryImpl : public sample::Factory {
Binding<sample::Factory> binding_;
};
-class HandlePassingTest : public testing::Test {
+class HandlePassingTest : public BindingsTestBase {
public:
HandlePassingTest() {}
@@ -158,8 +157,6 @@ class HandlePassingTest : public testing::Test {
void PumpMessages() { base::RunLoop().RunUntilIdle(); }
- private:
- base::MessageLoop loop_;
};
void DoStuff(bool* got_response,
@@ -197,7 +194,7 @@ void DoStuff2(bool* got_response,
closure.Run();
}
-TEST_F(HandlePassingTest, Basic) {
+TEST_P(HandlePassingTest, Basic) {
sample::FactoryPtr factory;
SampleFactoryImpl factory_impl(MakeRequest(&factory));
@@ -207,7 +204,7 @@ TEST_F(HandlePassingTest, Basic) {
MessagePipe pipe1;
EXPECT_TRUE(WriteTextMessage(pipe1.handle1.get(), kText2));
- imported::ImportedInterfacePtr imported;
+ imported::ImportedInterfacePtrInfo imported;
base::RunLoop run_loop;
ImportedInterfaceImpl imported_impl(MakeRequest(&imported),
run_loop.QuitClosure());
@@ -232,13 +229,13 @@ TEST_F(HandlePassingTest, Basic) {
EXPECT_EQ(1, ImportedInterfaceImpl::do_something_count() - count_before);
}
-TEST_F(HandlePassingTest, PassInvalid) {
+TEST_P(HandlePassingTest, PassInvalid) {
sample::FactoryPtr factory;
SampleFactoryImpl factory_impl(MakeRequest(&factory));
- sample::RequestPtr request(
- sample::Request::New(1, ScopedMessagePipeHandle(), base::nullopt,
- imported::ImportedInterfacePtr()));
+ sample::RequestPtr request(sample::Request::New(1, ScopedMessagePipeHandle(),
+ base::nullopt, nullptr));
+
bool got_response = false;
std::string got_text_reply;
base::RunLoop run_loop;
@@ -254,7 +251,7 @@ TEST_F(HandlePassingTest, PassInvalid) {
}
// Verifies DataPipeConsumer can be passed and read from.
-TEST_F(HandlePassingTest, DataPipe) {
+TEST_P(HandlePassingTest, DataPipe) {
sample::FactoryPtr factory;
SampleFactoryImpl factory_impl(MakeRequest(&factory));
@@ -263,8 +260,7 @@ TEST_F(HandlePassingTest, DataPipe) {
ScopedDataPipeProducerHandle producer_handle;
ScopedDataPipeConsumerHandle consumer_handle;
MojoCreateDataPipeOptions options = {sizeof(MojoCreateDataPipeOptions),
- MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE,
- 1,
+ MOJO_CREATE_DATA_PIPE_FLAG_NONE, 1,
1024};
ASSERT_EQ(MOJO_RESULT_OK,
CreateDataPipe(&options, &producer_handle, &consumer_handle));
@@ -272,10 +268,8 @@ TEST_F(HandlePassingTest, DataPipe) {
// +1 for \0.
uint32_t data_size = static_cast<uint32_t>(expected_text_reply.size() + 1);
ASSERT_EQ(MOJO_RESULT_OK,
- WriteDataRaw(producer_handle.get(),
- expected_text_reply.c_str(),
- &data_size,
- MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
+ producer_handle->WriteData(expected_text_reply.c_str(), &data_size,
+ MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
bool got_response = false;
std::string got_text_reply;
@@ -292,40 +286,14 @@ TEST_F(HandlePassingTest, DataPipe) {
EXPECT_EQ(expected_text_reply, got_text_reply);
}
-TEST_F(HandlePassingTest, PipesAreClosed) {
- sample::FactoryPtr factory;
- SampleFactoryImpl factory_impl(MakeRequest(&factory));
-
- MessagePipe extra_pipe;
-
- MojoHandle handle0_value = extra_pipe.handle0.get().value();
- MojoHandle handle1_value = extra_pipe.handle1.get().value();
-
- {
- std::vector<ScopedMessagePipeHandle> pipes(2);
- pipes[0] = std::move(extra_pipe.handle0);
- pipes[1] = std::move(extra_pipe.handle1);
-
- sample::RequestPtr request(sample::Request::New());
- request->more_pipes = std::move(pipes);
-
- factory->DoStuff(std::move(request), ScopedMessagePipeHandle(),
- sample::Factory::DoStuffCallback());
- }
-
- // We expect the pipes to have been closed.
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handle0_value));
- EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handle1_value));
-}
-
-TEST_F(HandlePassingTest, CreateNamedObject) {
+TEST_P(HandlePassingTest, CreateNamedObject) {
sample::FactoryPtr factory;
SampleFactoryImpl factory_impl(MakeRequest(&factory));
sample::NamedObjectPtr object1;
EXPECT_FALSE(object1);
- InterfaceRequest<sample::NamedObject> object1_request(&object1);
+ auto object1_request = mojo::MakeRequest(&object1);
EXPECT_TRUE(object1_request.is_pending());
factory->CreateNamedObject(std::move(object1_request));
EXPECT_FALSE(object1_request.is_pending()); // We've passed the request.
@@ -351,6 +319,8 @@ TEST_F(HandlePassingTest, CreateNamedObject) {
EXPECT_EQ(std::string("object2"), name2);
}
+INSTANTIATE_MOJO_BINDINGS_TEST_CASE_P(HandlePassingTest);
+
} // namespace
} // namespace test
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/hash_unittest.cc b/mojo/public/cpp/bindings/tests/hash_unittest.cc
index 9ce1f5bc7b..3a5bf4791c 100644
--- a/mojo/public/cpp/bindings/tests/hash_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/hash_unittest.cc
@@ -22,14 +22,6 @@ TEST_F(HashTest, NestedStruct) {
SimpleNestedStruct::New(ContainsOther::New(1))));
}
-TEST_F(HashTest, UnmappedNativeStruct) {
- // Just check that this template instantiation compiles.
- ASSERT_EQ(::mojo::internal::Hash(::mojo::internal::kHashSeed,
- UnmappedNativeStruct::New()),
- ::mojo::internal::Hash(::mojo::internal::kHashSeed,
- UnmappedNativeStruct::New()));
-}
-
} // namespace
} // namespace test
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc b/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc
index 431a844250..550a5aff56 100644
--- a/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc
@@ -1,3 +1,4 @@
+
// 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.
@@ -6,14 +7,21 @@
#include <utility>
#include "base/bind.h"
+#include "base/bind_helpers.h"
#include "base/callback.h"
+#include "base/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
+#include "base/sequenced_task_runner.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/test/scoped_task_environment.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/cpp/bindings/tests/bindings_test_base.h"
#include "mojo/public/cpp/bindings/thread_safe_interface_ptr.h"
#include "mojo/public/interfaces/bindings/tests/math_calculator.mojom.h"
#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h"
@@ -192,15 +200,12 @@ class IntegerAccessorImpl : public sample::IntegerAccessor {
base::Closure closure_;
};
-class InterfacePtrTest : public testing::Test {
+class InterfacePtrTest : public BindingsTestBase {
public:
InterfacePtrTest() {}
~InterfacePtrTest() override { base::RunLoop().RunUntilIdle(); }
void PumpMessages() { base::RunLoop().RunUntilIdle(); }
-
- private:
- base::MessageLoop loop_;
};
void SetFlagAndRunClosure(bool* flag, const base::Closure& closure) {
@@ -219,54 +224,62 @@ void ExpectValueAndRunClosure(uint32_t expected_value,
closure.Run();
}
-TEST_F(InterfacePtrTest, IsBound) {
+TEST_P(InterfacePtrTest, IsBound) {
math::CalculatorPtr calc;
EXPECT_FALSE(calc.is_bound());
MathCalculatorImpl calc_impl(MakeRequest(&calc));
EXPECT_TRUE(calc.is_bound());
}
-TEST_F(InterfacePtrTest, EndToEnd) {
- math::CalculatorPtr calc;
- MathCalculatorImpl calc_impl(MakeRequest(&calc));
-
- // Suppose this is instantiated in a process that has pipe1_.
- MathCalculatorUI calculator_ui(std::move(calc));
-
- base::RunLoop run_loop, run_loop2;
- calculator_ui.Add(2.0, run_loop.QuitClosure());
- calculator_ui.Multiply(5.0, run_loop2.QuitClosure());
- run_loop.Run();
- run_loop2.Run();
+class EndToEndInterfacePtrTest : public InterfacePtrTest {
+ public:
+ void RunTest(const scoped_refptr<base::SequencedTaskRunner> runner) {
+ base::RunLoop run_loop;
+ done_closure_ = run_loop.QuitClosure();
+ done_runner_ = base::ThreadTaskRunnerHandle::Get();
+ runner->PostTask(FROM_HERE,
+ base::Bind(&EndToEndInterfacePtrTest::RunTestImpl,
+ base::Unretained(this)));
+ run_loop.Run();
+ }
- EXPECT_EQ(10.0, calculator_ui.GetOutput());
-}
+ private:
+ void RunTestImpl() {
+ math::CalculatorPtr calc;
+ calc_impl_ = std::make_unique<MathCalculatorImpl>(MakeRequest(&calc));
+ calculator_ui_ = std::make_unique<MathCalculatorUI>(std::move(calc));
+ calculator_ui_->Add(2.0, base::Bind(&EndToEndInterfacePtrTest::AddDone,
+ base::Unretained(this)));
+ calculator_ui_->Multiply(5.0,
+ base::Bind(&EndToEndInterfacePtrTest::MultiplyDone,
+ base::Unretained(this)));
+ EXPECT_EQ(0.0, calculator_ui_->GetOutput());
+ }
-TEST_F(InterfacePtrTest, EndToEnd_Synchronous) {
- math::CalculatorPtr calc;
- MathCalculatorImpl calc_impl(MakeRequest(&calc));
+ void AddDone() { EXPECT_EQ(2.0, calculator_ui_->GetOutput()); }
- // Suppose this is instantiated in a process that has pipe1_.
- MathCalculatorUI calculator_ui(std::move(calc));
+ void MultiplyDone() {
+ EXPECT_EQ(10.0, calculator_ui_->GetOutput());
+ calculator_ui_.reset();
+ calc_impl_.reset();
+ done_runner_->PostTask(FROM_HERE, base::ResetAndReturn(&done_closure_));
+ }
- EXPECT_EQ(0.0, calculator_ui.GetOutput());
+ base::Closure done_closure_;
+ scoped_refptr<base::SingleThreadTaskRunner> done_runner_;
+ std::unique_ptr<MathCalculatorUI> calculator_ui_;
+ std::unique_ptr<MathCalculatorImpl> calc_impl_;
+};
- base::RunLoop run_loop;
- calculator_ui.Add(2.0, run_loop.QuitClosure());
- EXPECT_EQ(0.0, calculator_ui.GetOutput());
- calc_impl.binding()->WaitForIncomingMethodCall();
- run_loop.Run();
- EXPECT_EQ(2.0, calculator_ui.GetOutput());
+TEST_P(EndToEndInterfacePtrTest, EndToEnd) {
+ RunTest(base::ThreadTaskRunnerHandle::Get());
+}
- base::RunLoop run_loop2;
- calculator_ui.Multiply(5.0, run_loop2.QuitClosure());
- EXPECT_EQ(2.0, calculator_ui.GetOutput());
- calc_impl.binding()->WaitForIncomingMethodCall();
- run_loop2.Run();
- EXPECT_EQ(10.0, calculator_ui.GetOutput());
+TEST_P(EndToEndInterfacePtrTest, EndToEndOnSequence) {
+ RunTest(base::CreateSequencedTaskRunnerWithTraits({}));
}
-TEST_F(InterfacePtrTest, Movable) {
+TEST_P(InterfacePtrTest, Movable) {
math::CalculatorPtr a;
math::CalculatorPtr b;
MathCalculatorImpl calc_impl(MakeRequest(&b));
@@ -280,7 +293,7 @@ TEST_F(InterfacePtrTest, Movable) {
EXPECT_TRUE(!b);
}
-TEST_F(InterfacePtrTest, Resettable) {
+TEST_P(InterfacePtrTest, Resettable) {
math::CalculatorPtr a;
EXPECT_TRUE(!a);
@@ -304,7 +317,7 @@ TEST_F(InterfacePtrTest, Resettable) {
EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, CloseRaw(handle));
}
-TEST_F(InterfacePtrTest, BindInvalidHandle) {
+TEST_P(InterfacePtrTest, BindInvalidHandle) {
math::CalculatorPtr ptr;
EXPECT_FALSE(ptr.get());
EXPECT_FALSE(ptr);
@@ -314,7 +327,7 @@ TEST_F(InterfacePtrTest, BindInvalidHandle) {
EXPECT_FALSE(ptr);
}
-TEST_F(InterfacePtrTest, EncounteredError) {
+TEST_P(InterfacePtrTest, EncounteredError) {
math::CalculatorPtr proxy;
MathCalculatorImpl calc_impl(MakeRequest(&proxy));
@@ -343,7 +356,7 @@ TEST_F(InterfacePtrTest, EncounteredError) {
EXPECT_TRUE(calculator_ui.encountered_error());
}
-TEST_F(InterfacePtrTest, EncounteredErrorCallback) {
+TEST_P(InterfacePtrTest, EncounteredErrorCallback) {
math::CalculatorPtr proxy;
MathCalculatorImpl calc_impl(MakeRequest(&proxy));
@@ -380,7 +393,7 @@ TEST_F(InterfacePtrTest, EncounteredErrorCallback) {
EXPECT_TRUE(encountered_error);
}
-TEST_F(InterfacePtrTest, DestroyInterfacePtrOnMethodResponse) {
+TEST_P(InterfacePtrTest, DestroyInterfacePtrOnMethodResponse) {
math::CalculatorPtr proxy;
MathCalculatorImpl calc_impl(MakeRequest(&proxy));
@@ -395,7 +408,7 @@ TEST_F(InterfacePtrTest, DestroyInterfacePtrOnMethodResponse) {
EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances());
}
-TEST_F(InterfacePtrTest, NestedDestroyInterfacePtrOnMethodResponse) {
+TEST_P(InterfacePtrTest, NestedDestroyInterfacePtrOnMethodResponse) {
math::CalculatorPtr proxy;
MathCalculatorImpl calc_impl(MakeRequest(&proxy));
@@ -410,7 +423,7 @@ TEST_F(InterfacePtrTest, NestedDestroyInterfacePtrOnMethodResponse) {
EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances());
}
-TEST_F(InterfacePtrTest, ReentrantWaitForIncomingMethodCall) {
+TEST_P(InterfacePtrTest, ReentrantWaitForIncomingMethodCall) {
sample::ServicePtr proxy;
ReentrantServiceImpl impl(MakeRequest(&proxy));
@@ -428,7 +441,7 @@ TEST_F(InterfacePtrTest, ReentrantWaitForIncomingMethodCall) {
EXPECT_EQ(2, impl.max_call_depth());
}
-TEST_F(InterfacePtrTest, QueryVersion) {
+TEST_P(InterfacePtrTest, QueryVersion) {
IntegerAccessorImpl impl;
sample::IntegerAccessorPtr ptr;
Binding<sample::IntegerAccessor> binding(&impl, MakeRequest(&ptr));
@@ -443,7 +456,7 @@ TEST_F(InterfacePtrTest, QueryVersion) {
EXPECT_EQ(3u, ptr.version());
}
-TEST_F(InterfacePtrTest, RequireVersion) {
+TEST_P(InterfacePtrTest, RequireVersion) {
IntegerAccessorImpl impl;
sample::IntegerAccessorPtr ptr;
Binding<sample::IntegerAccessor> binding(&impl, MakeRequest(&ptr));
@@ -513,7 +526,7 @@ TEST(StrongConnectorTest, Math) {
base::RunLoop run_loop;
auto binding =
- MakeStrongBinding(base::MakeUnique<StrongMathCalculatorImpl>(&destroyed),
+ MakeStrongBinding(std::make_unique<StrongMathCalculatorImpl>(&destroyed),
MakeRequest(&calc));
binding->set_connection_error_handler(base::Bind(
&SetFlagAndRunClosure, &error_received, run_loop.QuitClosure()));
@@ -544,14 +557,14 @@ TEST(StrongConnectorTest, Math) {
class WeakMathCalculatorImpl : public math::Calculator {
public:
- WeakMathCalculatorImpl(ScopedMessagePipeHandle handle,
+ WeakMathCalculatorImpl(math::CalculatorRequest request,
bool* error_received,
bool* destroyed,
const base::Closure& closure)
: error_received_(error_received),
destroyed_(destroyed),
closure_(closure),
- binding_(this, std::move(handle)) {
+ binding_(this, std::move(request)) {
binding_.set_connection_error_handler(
base::Bind(&SetFlagAndRunClosure, error_received_, closure_));
}
@@ -585,8 +598,9 @@ TEST(WeakConnectorTest, Math) {
bool destroyed = false;
MessagePipe pipe;
base::RunLoop run_loop;
- WeakMathCalculatorImpl impl(std::move(pipe.handle0), &error_received,
- &destroyed, run_loop.QuitClosure());
+ WeakMathCalculatorImpl impl(math::CalculatorRequest(std::move(pipe.handle0)),
+ &error_received, &destroyed,
+ run_loop.QuitClosure());
math::CalculatorPtr calc;
calc.Bind(InterfacePtrInfo<math::Calculator>(std::move(pipe.handle1), 0u));
@@ -639,7 +653,7 @@ class BImpl : public B {
private:
void GetC(InterfaceRequest<C> c) override {
- MakeStrongBinding(base::MakeUnique<CImpl>(d_called_, closure_),
+ MakeStrongBinding(std::make_unique<CImpl>(d_called_, closure_),
std::move(c));
}
@@ -658,7 +672,7 @@ class AImpl : public A {
private:
void GetB(InterfaceRequest<B> b) override {
- MakeStrongBinding(base::MakeUnique<BImpl>(&d_called_, closure_),
+ MakeStrongBinding(std::make_unique<BImpl>(&d_called_, closure_),
std::move(b));
}
@@ -667,7 +681,7 @@ class AImpl : public A {
base::Closure closure_;
};
-TEST_F(InterfacePtrTest, Scoping) {
+TEST_P(InterfacePtrTest, Scoping) {
APtr a;
base::RunLoop run_loop;
AImpl a_impl(MakeRequest(&a), run_loop.QuitClosure());
@@ -703,16 +717,13 @@ class PingTestImpl : public sample::PingTest {
};
// Tests that FuseProxy does what it's supposed to do.
-TEST_F(InterfacePtrTest, Fusion) {
- sample::PingTestPtr proxy;
- PingTestImpl impl(MakeRequest(&proxy));
+TEST_P(InterfacePtrTest, Fusion) {
+ sample::PingTestPtrInfo proxy_info;
+ PingTestImpl impl(MakeRequest(&proxy_info));
- // Create another PingTest pipe.
+ // Create another PingTest pipe and fuse it to the one hanging off |impl|.
sample::PingTestPtr ptr;
- sample::PingTestRequest request(&ptr);
-
- // Fuse the new pipe to the one hanging off |impl|.
- EXPECT_TRUE(FuseInterface(std::move(request), proxy.PassInterface()));
+ EXPECT_TRUE(FuseInterface(mojo::MakeRequest(&ptr), std::move(proxy_info)));
// Ping!
bool called = false;
@@ -726,18 +737,18 @@ void Fail() {
FAIL() << "Unexpected connection error";
}
-TEST_F(InterfacePtrTest, FlushForTesting) {
+TEST_P(InterfacePtrTest, FlushForTesting) {
math::CalculatorPtr calc;
MathCalculatorImpl calc_impl(MakeRequest(&calc));
calc.set_connection_error_handler(base::Bind(&Fail));
MathCalculatorUI calculator_ui(std::move(calc));
- calculator_ui.Add(2.0, base::Bind(&base::DoNothing));
+ calculator_ui.Add(2.0, base::DoNothing());
calculator_ui.GetInterfacePtr().FlushForTesting();
EXPECT_EQ(2.0, calculator_ui.GetOutput());
- calculator_ui.Multiply(5.0, base::Bind(&base::DoNothing));
+ calculator_ui.Multiply(5.0, base::DoNothing());
calculator_ui.GetInterfacePtr().FlushForTesting();
EXPECT_EQ(10.0, calculator_ui.GetOutput());
@@ -747,7 +758,7 @@ void SetBool(bool* value) {
*value = true;
}
-TEST_F(InterfacePtrTest, FlushForTestingWithClosedPeer) {
+TEST_P(InterfacePtrTest, FlushForTestingWithClosedPeer) {
math::CalculatorPtr calc;
MakeRequest(&calc);
bool called = false;
@@ -757,7 +768,7 @@ TEST_F(InterfacePtrTest, FlushForTestingWithClosedPeer) {
calc.FlushForTesting();
}
-TEST_F(InterfacePtrTest, ConnectionErrorWithReason) {
+TEST_P(InterfacePtrTest, ConnectionErrorWithReason) {
math::CalculatorPtr calc;
MathCalculatorImpl calc_impl(MakeRequest(&calc));
@@ -776,7 +787,7 @@ TEST_F(InterfacePtrTest, ConnectionErrorWithReason) {
run_loop.Run();
}
-TEST_F(InterfacePtrTest, InterfaceRequestResetWithReason) {
+TEST_P(InterfacePtrTest, InterfaceRequestResetWithReason) {
math::CalculatorPtr calc;
auto request = MakeRequest(&calc);
@@ -795,17 +806,17 @@ TEST_F(InterfacePtrTest, InterfaceRequestResetWithReason) {
run_loop.Run();
}
-TEST_F(InterfacePtrTest, CallbackIsPassedInterfacePtr) {
+TEST_P(InterfacePtrTest, CallbackIsPassedInterfacePtr) {
sample::PingTestPtr ptr;
- sample::PingTestRequest request(&ptr);
+ auto request = mojo::MakeRequest(&ptr);
base::RunLoop run_loop;
// Make a call with the proxy's lifetime bound to the response callback.
sample::PingTest* raw_proxy = ptr.get();
ptr.set_connection_error_handler(run_loop.QuitClosure());
- raw_proxy->Ping(
- base::Bind([](sample::PingTestPtr ptr) {}, base::Passed(&ptr)));
+ raw_proxy->Ping(base::Bind(base::DoNothing::Repeatedly<sample::PingTestPtr>(),
+ base::Passed(&ptr)));
// Trigger an error on |ptr|. This will ultimately lead to the proxy's
// response callbacks being destroyed, which will in turn lead to the proxy
@@ -814,9 +825,9 @@ TEST_F(InterfacePtrTest, CallbackIsPassedInterfacePtr) {
run_loop.Run();
}
-TEST_F(InterfacePtrTest, ConnectionErrorHandlerOwnsInterfacePtr) {
+TEST_P(InterfacePtrTest, ConnectionErrorHandlerOwnsInterfacePtr) {
sample::PingTestPtr* ptr = new sample::PingTestPtr;
- sample::PingTestRequest request(ptr);
+ auto request = mojo::MakeRequest(ptr);
base::RunLoop run_loop;
@@ -836,7 +847,7 @@ TEST_F(InterfacePtrTest, ConnectionErrorHandlerOwnsInterfacePtr) {
run_loop.Run();
}
-TEST_F(InterfacePtrTest, ThreadSafeInterfacePointer) {
+TEST_P(InterfacePtrTest, ThreadSafeInterfacePointer) {
math::CalculatorPtr ptr;
MathCalculatorImpl calc_impl(MakeRequest(&ptr));
scoped_refptr<math::ThreadSafeCalculatorPtr> thread_safe_ptr =
@@ -844,10 +855,6 @@ TEST_F(InterfacePtrTest, ThreadSafeInterfacePointer) {
base::RunLoop run_loop;
- // Create and start the thread from where we'll call the interface pointer.
- base::Thread other_thread("service test thread");
- other_thread.Start();
-
auto run_method = base::Bind(
[](const scoped_refptr<base::TaskRunner>& main_task_runner,
const base::Closure& quit_closure,
@@ -855,35 +862,35 @@ TEST_F(InterfacePtrTest, ThreadSafeInterfacePointer) {
auto calc_callback = base::Bind(
[](const scoped_refptr<base::TaskRunner>& main_task_runner,
const base::Closure& quit_closure,
- base::PlatformThreadId thread_id,
+ scoped_refptr<base::SequencedTaskRunner> sender_sequence_runner,
double result) {
EXPECT_EQ(123, result);
- // Validate the callback is invoked on the calling thread.
- EXPECT_EQ(thread_id, base::PlatformThread::CurrentId());
+ // Validate the callback is invoked on the calling sequence.
+ EXPECT_TRUE(sender_sequence_runner->RunsTasksInCurrentSequence());
// Notify the run_loop to quit.
main_task_runner->PostTask(FROM_HERE, quit_closure);
});
- (*thread_safe_ptr)->Add(
- 123, base::Bind(calc_callback, main_task_runner, quit_closure,
- base::PlatformThread::CurrentId()));
+ scoped_refptr<base::SequencedTaskRunner> current_sequence_runner =
+ base::SequencedTaskRunnerHandle::Get();
+ (*thread_safe_ptr)
+ ->Add(123, base::Bind(calc_callback, main_task_runner, quit_closure,
+ current_sequence_runner));
},
base::SequencedTaskRunnerHandle::Get(), run_loop.QuitClosure(),
thread_safe_ptr);
- other_thread.message_loop()->task_runner()->PostTask(FROM_HERE, run_method);
+ base::CreateSequencedTaskRunnerWithTraits({})->PostTask(FROM_HERE,
+ run_method);
// Block until the method callback is called on the background thread.
run_loop.Run();
}
-TEST_F(InterfacePtrTest, ThreadSafeInterfacePointerWithTaskRunner) {
- // Create and start the thread from where we'll bind the interface pointer.
- base::Thread other_thread("service test thread");
- other_thread.Start();
- const scoped_refptr<base::SingleThreadTaskRunner>& other_thread_task_runner =
- other_thread.message_loop()->task_runner();
+TEST_P(InterfacePtrTest, ThreadSafeInterfacePointerWithTaskRunner) {
+ const scoped_refptr<base::SequencedTaskRunner> other_thread_task_runner =
+ base::CreateSequencedTaskRunnerWithTraits({});
math::CalculatorPtr ptr;
- math::CalculatorRequest request(&ptr);
+ auto request = mojo::MakeRequest(&ptr);
// Create a ThreadSafeInterfacePtr that we'll bind from a different thread.
scoped_refptr<math::ThreadSafeCalculatorPtr> thread_safe_ptr =
@@ -907,7 +914,7 @@ TEST_F(InterfacePtrTest, ThreadSafeInterfacePointerWithTaskRunner) {
},
base::SequencedTaskRunnerHandle::Get(), run_loop.QuitClosure(),
thread_safe_ptr, base::Passed(&request), &math_calc_impl);
- other_thread.message_loop()->task_runner()->PostTask(FROM_HERE, run_method);
+ other_thread_task_runner->PostTask(FROM_HERE, run_method);
run_loop.Run();
}
@@ -932,6 +939,8 @@ TEST_F(InterfacePtrTest, ThreadSafeInterfacePointerWithTaskRunner) {
thread_safe_ptr = nullptr;
}
+INSTANTIATE_MOJO_BINDINGS_TEST_CASE_P(InterfacePtrTest);
+
} // namespace
} // namespace test
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/lazy_serialization_unittest.cc b/mojo/public/cpp/bindings/tests/lazy_serialization_unittest.cc
new file mode 100644
index 0000000000..81f8419fab
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/lazy_serialization_unittest.cc
@@ -0,0 +1,166 @@
+// 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/macros.h"
+#include "base/run_loop.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/scoped_task_environment.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/tests/bindings_test_base.h"
+#include "mojo/public/interfaces/bindings/tests/struct_with_traits.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+class LazySerializationTest : public testing::Test {
+ public:
+ LazySerializationTest() {}
+ ~LazySerializationTest() override {}
+
+ private:
+ base::test::ScopedTaskEnvironment task_environment_;
+
+ DISALLOW_COPY_AND_ASSIGN(LazySerializationTest);
+};
+
+class TestUnserializedStructImpl : public test::TestUnserializedStruct {
+ public:
+ explicit TestUnserializedStructImpl(
+ test::TestUnserializedStructRequest request)
+ : binding_(this, std::move(request)) {}
+ ~TestUnserializedStructImpl() override {}
+
+ // test::TestUnserializedStruct:
+ void PassUnserializedStruct(
+ const test::StructWithUnreachableTraitsImpl& s,
+ const PassUnserializedStructCallback& callback) override {
+ callback.Run(s);
+ }
+
+ private:
+ mojo::Binding<test::TestUnserializedStruct> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestUnserializedStructImpl);
+};
+
+class ForceSerializeTesterImpl : public test::ForceSerializeTester {
+ public:
+ ForceSerializeTesterImpl(test::ForceSerializeTesterRequest request)
+ : binding_(this, std::move(request)) {}
+ ~ForceSerializeTesterImpl() override = default;
+
+ // test::ForceSerializeTester:
+ void SendForceSerializedStruct(
+ const test::StructForceSerializeImpl& s,
+ const SendForceSerializedStructCallback& callback) override {
+ callback.Run(s);
+ }
+
+ void SendNestedForceSerializedStruct(
+ const test::StructNestedForceSerializeImpl& s,
+ const SendNestedForceSerializedStructCallback& callback) override {
+ callback.Run(s);
+ }
+
+ private:
+ Binding<test::ForceSerializeTester> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(ForceSerializeTesterImpl);
+};
+
+TEST_F(LazySerializationTest, NeverSerialize) {
+ // Basic sanity check to ensure that no messages are serialized by default in
+ // environments where lazy serialization is supported, on an interface which
+ // supports lazy serialization, and where both ends of the interface are in
+ // the same process.
+
+ test::TestUnserializedStructPtr ptr;
+ TestUnserializedStructImpl impl(MakeRequest(&ptr));
+
+ const int32_t kTestMagicNumber = 42;
+
+ test::StructWithUnreachableTraitsImpl data;
+ EXPECT_EQ(0, data.magic_number);
+ data.magic_number = kTestMagicNumber;
+
+ // Send our data over the pipe and wait for it to come back. The value should
+ // be preserved. We know the data was never serialized because the
+ // StructTraits for this type will DCHECK if executed in any capacity.
+ int received_number = 0;
+ base::RunLoop loop;
+ ptr->PassUnserializedStruct(
+ data, base::Bind(
+ [](base::RunLoop* loop, int* received_number,
+ const test::StructWithUnreachableTraitsImpl& passed) {
+ *received_number = passed.magic_number;
+ loop->Quit();
+ },
+ &loop, &received_number));
+ loop.Run();
+ EXPECT_EQ(kTestMagicNumber, received_number);
+}
+
+TEST_F(LazySerializationTest, ForceSerialize) {
+ // Verifies that the [force_serialize] attribute works as intended: i.e., even
+ // with lazy serialization enabled, messages which carry a force-serialized
+ // type will always serialize at call time.
+
+ test::ForceSerializeTesterPtr tester;
+ ForceSerializeTesterImpl impl(mojo::MakeRequest(&tester));
+
+ constexpr int32_t kTestValue = 42;
+
+ base::RunLoop loop;
+ test::StructForceSerializeImpl in;
+ in.set_value(kTestValue);
+ EXPECT_FALSE(in.was_serialized());
+ EXPECT_FALSE(in.was_deserialized());
+ tester->SendForceSerializedStruct(
+ in, base::BindLambdaForTesting(
+ [&](const test::StructForceSerializeImpl& passed) {
+ EXPECT_EQ(kTestValue, passed.value());
+ EXPECT_TRUE(passed.was_deserialized());
+ EXPECT_FALSE(passed.was_serialized());
+ loop.Quit();
+ }));
+ EXPECT_TRUE(in.was_serialized());
+ EXPECT_FALSE(in.was_deserialized());
+ loop.Run();
+ EXPECT_TRUE(in.was_serialized());
+ EXPECT_FALSE(in.was_deserialized());
+}
+
+TEST_F(LazySerializationTest, ForceSerializeNested) {
+ // Verifies that the [force_serialize] attribute works as intended in a nested
+ // context, i.e. when a force-serialized type is contained within a
+ // non-force-serialized type,
+
+ test::ForceSerializeTesterPtr tester;
+ ForceSerializeTesterImpl impl(mojo::MakeRequest(&tester));
+
+ constexpr int32_t kTestValue = 42;
+
+ base::RunLoop loop;
+ test::StructNestedForceSerializeImpl in;
+ in.force().set_value(kTestValue);
+ EXPECT_FALSE(in.was_serialized());
+ EXPECT_FALSE(in.was_deserialized());
+ tester->SendNestedForceSerializedStruct(
+ in, base::BindLambdaForTesting(
+ [&](const test::StructNestedForceSerializeImpl& passed) {
+ EXPECT_EQ(kTestValue, passed.force().value());
+ EXPECT_TRUE(passed.was_deserialized());
+ EXPECT_FALSE(passed.was_serialized());
+ loop.Quit();
+ }));
+ EXPECT_TRUE(in.was_serialized());
+ EXPECT_FALSE(in.was_deserialized());
+ loop.Run();
+ EXPECT_TRUE(in.was_serialized());
+ EXPECT_FALSE(in.was_deserialized());
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/map_unittest.cc b/mojo/public/cpp/bindings/tests/map_unittest.cc
index 8d630a5862..104f3fd37c 100644
--- a/mojo/public/cpp/bindings/tests/map_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/map_unittest.cc
@@ -4,9 +4,9 @@
#include <stddef.h>
#include <stdint.h>
-#include <unordered_map>
#include <utility>
+#include "base/containers/flat_map.h"
#include "mojo/public/cpp/bindings/tests/rect_chromium.h"
#include "mojo/public/interfaces/bindings/tests/rect.mojom.h"
#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h"
@@ -17,7 +17,7 @@ namespace test {
namespace {
TEST(MapTest, StructKey) {
- std::unordered_map<RectPtr, int32_t> map;
+ base::flat_map<RectPtr, int32_t> map;
map.insert(std::make_pair(Rect::New(1, 2, 3, 4), 123));
RectPtr key = Rect::New(1, 2, 3, 4);
@@ -29,7 +29,7 @@ TEST(MapTest, StructKey) {
}
TEST(MapTest, TypemappedStructKey) {
- std::unordered_map<ContainsHashablePtr, int32_t> map;
+ base::flat_map<ContainsHashablePtr, int32_t> map;
map.insert(
std::make_pair(ContainsHashable::New(RectChromium(1, 2, 3, 4)), 123));
diff --git a/mojo/public/cpp/bindings/tests/message_queue.h b/mojo/public/cpp/bindings/tests/message_queue.h
index 8f13f7ab6d..836c60d3a4 100644
--- a/mojo/public/cpp/bindings/tests/message_queue.h
+++ b/mojo/public/cpp/bindings/tests/message_queue.h
@@ -5,8 +5,7 @@
#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_MESSAGE_QUEUE_H_
#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_MESSAGE_QUEUE_H_
-#include <queue>
-
+#include "base/containers/queue.h"
#include "base/macros.h"
#include "mojo/public/cpp/bindings/message.h"
@@ -33,7 +32,7 @@ class MessageQueue {
private:
void Pop();
- std::queue<Message> queue_;
+ base::queue<Message> queue_;
DISALLOW_COPY_AND_ASSIGN(MessageQueue);
};
diff --git a/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc b/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc
index 89509283c4..ff1c02db35 100644
--- a/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc
@@ -43,7 +43,12 @@ class MultiplexRouterTest : public testing::Test {
endpoint1_ = router1_->CreateLocalEndpointHandle(id);
}
- void TearDown() override {}
+ void TearDown() override {
+ endpoint1_.reset();
+ endpoint0_.reset();
+ router1_ = nullptr;
+ router0_ = nullptr;
+ }
void PumpMessages() { base::RunLoop().RunUntilIdle(); }
@@ -59,11 +64,11 @@ class MultiplexRouterTest : public testing::Test {
TEST_F(MultiplexRouterTest, BasicRequestResponse) {
InterfaceEndpointClient client0(std::move(endpoint0_), nullptr,
- base::MakeUnique<PassThroughFilter>(), false,
+ std::make_unique<PassThroughFilter>(), false,
base::ThreadTaskRunnerHandle::Get(), 0u);
ResponseGenerator generator;
InterfaceEndpointClient client1(std::move(endpoint1_), &generator,
- base::MakeUnique<PassThroughFilter>(), false,
+ std::make_unique<PassThroughFilter>(), false,
base::ThreadTaskRunnerHandle::Get(), 0u);
Message request;
@@ -72,7 +77,7 @@ TEST_F(MultiplexRouterTest, BasicRequestResponse) {
MessageQueue message_queue;
base::RunLoop run_loop;
client0.AcceptWithResponder(
- &request, base::MakeUnique<MessageAccumulator>(&message_queue,
+ &request, std::make_unique<MessageAccumulator>(&message_queue,
run_loop.QuitClosure()));
run_loop.Run();
@@ -91,7 +96,7 @@ TEST_F(MultiplexRouterTest, BasicRequestResponse) {
base::RunLoop run_loop2;
client0.AcceptWithResponder(
- &request2, base::MakeUnique<MessageAccumulator>(&message_queue,
+ &request2, std::make_unique<MessageAccumulator>(&message_queue,
run_loop2.QuitClosure()));
run_loop2.Run();
@@ -106,11 +111,11 @@ TEST_F(MultiplexRouterTest, BasicRequestResponse) {
TEST_F(MultiplexRouterTest, BasicRequestResponse_Synchronous) {
InterfaceEndpointClient client0(std::move(endpoint0_), nullptr,
- base::MakeUnique<PassThroughFilter>(), false,
+ std::make_unique<PassThroughFilter>(), false,
base::ThreadTaskRunnerHandle::Get(), 0u);
ResponseGenerator generator;
InterfaceEndpointClient client1(std::move(endpoint1_), &generator,
- base::MakeUnique<PassThroughFilter>(), false,
+ std::make_unique<PassThroughFilter>(), false,
base::ThreadTaskRunnerHandle::Get(), 0u);
Message request;
@@ -118,7 +123,7 @@ TEST_F(MultiplexRouterTest, BasicRequestResponse_Synchronous) {
MessageQueue message_queue;
client0.AcceptWithResponder(
- &request, base::MakeUnique<MessageAccumulator>(&message_queue));
+ &request, std::make_unique<MessageAccumulator>(&message_queue));
router1_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE);
router0_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE);
@@ -136,7 +141,7 @@ TEST_F(MultiplexRouterTest, BasicRequestResponse_Synchronous) {
AllocRequestMessage(1, "hello again", &request2);
client0.AcceptWithResponder(
- &request2, base::MakeUnique<MessageAccumulator>(&message_queue));
+ &request2, std::make_unique<MessageAccumulator>(&message_queue));
router1_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE);
router0_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE);
@@ -168,7 +173,7 @@ TEST_F(MultiplexRouterTest, LazyResponses) {
MessageQueue message_queue;
base::RunLoop run_loop2;
client0.AcceptWithResponder(
- &request, base::MakeUnique<MessageAccumulator>(&message_queue,
+ &request, std::make_unique<MessageAccumulator>(&message_queue,
run_loop2.QuitClosure()));
run_loop.Run();
@@ -195,7 +200,7 @@ TEST_F(MultiplexRouterTest, LazyResponses) {
base::RunLoop run_loop4;
client0.AcceptWithResponder(
- &request2, base::MakeUnique<MessageAccumulator>(&message_queue,
+ &request2, std::make_unique<MessageAccumulator>(&message_queue,
run_loop4.QuitClosure()));
run_loop3.Run();
@@ -248,7 +253,7 @@ TEST_F(MultiplexRouterTest, MissingResponses) {
MessageQueue message_queue;
client0.AcceptWithResponder(
- &request, base::MakeUnique<MessageAccumulator>(&message_queue));
+ &request, std::make_unique<MessageAccumulator>(&message_queue));
run_loop3.Run();
// The request has been received but no response has been sent.
@@ -284,10 +289,10 @@ TEST_F(MultiplexRouterTest, LateResponse) {
LazyResponseGenerator generator(run_loop.QuitClosure());
{
InterfaceEndpointClient client0(
- std::move(endpoint0_), nullptr, base::MakeUnique<PassThroughFilter>(),
+ std::move(endpoint0_), nullptr, std::make_unique<PassThroughFilter>(),
false, base::ThreadTaskRunnerHandle::Get(), 0u);
InterfaceEndpointClient client1(std::move(endpoint1_), &generator,
- base::MakeUnique<PassThroughFilter>(),
+ std::make_unique<PassThroughFilter>(),
false, base::ThreadTaskRunnerHandle::Get(),
0u);
@@ -296,7 +301,7 @@ TEST_F(MultiplexRouterTest, LateResponse) {
MessageQueue message_queue;
client0.AcceptWithResponder(
- &request, base::MakeUnique<MessageAccumulator>(&message_queue));
+ &request, std::make_unique<MessageAccumulator>(&message_queue));
run_loop.Run();
diff --git a/mojo/public/cpp/bindings/tests/native_struct_unittest.cc b/mojo/public/cpp/bindings/tests/native_struct_unittest.cc
new file mode 100644
index 0000000000..6e3cbcbe36
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/native_struct_unittest.cc
@@ -0,0 +1,98 @@
+// 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 <stdint.h>
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "ipc/ipc_param_traits.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/tests/bindings_test_base.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "mojo/public/cpp/system/wait.h"
+#include "mojo/public/interfaces/bindings/tests/test_native_types.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+
+class NativeStructTest : public BindingsTestBase,
+ public test::NativeTypeTester {
+ public:
+ NativeStructTest() : binding_(this, mojo::MakeRequest(&proxy_)) {}
+ ~NativeStructTest() override = default;
+
+ test::NativeTypeTester* proxy() { return proxy_.get(); }
+
+ private:
+ // test::NativeTypeTester:
+ void PassNativeStruct(const test::TestNativeStruct& s,
+ const PassNativeStructCallback& callback) override {
+ callback.Run(s);
+ }
+
+ void PassNativeStructWithAttachments(
+ test::TestNativeStructWithAttachments s,
+ const PassNativeStructWithAttachmentsCallback& callback) override {
+ callback.Run(std::move(s));
+ }
+
+ test::NativeTypeTesterPtr proxy_;
+ Binding<test::NativeTypeTester> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeStructTest);
+};
+
+TEST_P(NativeStructTest, NativeStruct) {
+ test::TestNativeStruct s("hello world", 5, 42);
+ base::RunLoop loop;
+ proxy()->PassNativeStruct(
+ s, base::Bind(
+ [](test::TestNativeStruct* expected_struct, base::RunLoop* loop,
+ const test::TestNativeStruct& passed) {
+ EXPECT_EQ(expected_struct->message(), passed.message());
+ EXPECT_EQ(expected_struct->x(), passed.x());
+ EXPECT_EQ(expected_struct->y(), passed.y());
+ loop->Quit();
+ },
+ &s, &loop));
+ loop.Run();
+}
+
+TEST_P(NativeStructTest, NativeStructWithAttachments) {
+ mojo::MessagePipe pipe;
+ const std::string kTestMessage = "hey hi";
+ test::TestNativeStructWithAttachments s(kTestMessage,
+ std::move(pipe.handle0));
+ base::RunLoop loop;
+ proxy()->PassNativeStructWithAttachments(
+ std::move(s),
+ base::Bind(
+ [](const std::string& expected_message,
+ mojo::ScopedMessagePipeHandle peer_pipe, base::RunLoop* loop,
+ test::TestNativeStructWithAttachments passed) {
+ // To ensure that the received pipe handle is functioning, we write
+ // to its peer and wait for the message to be received.
+ WriteMessageRaw(peer_pipe.get(), "ping", 4, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE);
+ auto pipe = passed.PassPipe();
+ EXPECT_EQ(MOJO_RESULT_OK,
+ Wait(pipe.get(), MOJO_HANDLE_SIGNAL_READABLE));
+ std::vector<uint8_t> bytes;
+ EXPECT_EQ(MOJO_RESULT_OK,
+ ReadMessageRaw(pipe.get(), &bytes, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ("ping", std::string(bytes.begin(), bytes.end()));
+ loop->Quit();
+ },
+ kTestMessage, base::Passed(&pipe.handle1), &loop));
+ loop.Run();
+}
+
+INSTANTIATE_MOJO_BINDINGS_TEST_CASE_P(NativeStructTest);
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/pickle_unittest.cc b/mojo/public/cpp/bindings/tests/pickle_unittest.cc
index a5947ce9ed..fd97c574e3 100644
--- a/mojo/public/cpp/bindings/tests/pickle_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/pickle_unittest.cc
@@ -157,23 +157,40 @@ class PickleTest : public testing::Test {
template <typename ProxyType = PicklePasser>
InterfacePtr<ProxyType> ConnectToChromiumService() {
InterfacePtr<ProxyType> proxy;
- InterfaceRequest<ProxyType> request(&proxy);
chromium_bindings_.AddBinding(
&chromium_service_,
- ConvertInterfaceRequest<PicklePasser>(std::move(request)));
+ ConvertInterfaceRequest<PicklePasser>(mojo::MakeRequest(&proxy)));
return proxy;
}
template <typename ProxyType = blink::PicklePasser>
InterfacePtr<ProxyType> ConnectToBlinkService() {
InterfacePtr<ProxyType> proxy;
- InterfaceRequest<ProxyType> request(&proxy);
- blink_bindings_.AddBinding(
- &blink_service_,
- ConvertInterfaceRequest<blink::PicklePasser>(std::move(request)));
+ blink_bindings_.AddBinding(&blink_service_,
+ ConvertInterfaceRequest<blink::PicklePasser>(
+ mojo::MakeRequest(&proxy)));
return proxy;
}
+ protected:
+ static void ForceMessageSerialization(bool forced) {
+ // Force messages to be serialized in this test since it intentionally
+ // exercises StructTraits logic.
+ Connector::OverrideDefaultSerializationBehaviorForTesting(
+ forced ? Connector::OutgoingSerializationMode::kEager
+ : Connector::OutgoingSerializationMode::kLazy,
+ Connector::IncomingSerializationMode::kDispatchAsIs);
+ }
+
+ class ScopedForceMessageSerialization {
+ public:
+ ScopedForceMessageSerialization() { ForceMessageSerialization(true); }
+ ~ScopedForceMessageSerialization() { ForceMessageSerialization(false); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedForceMessageSerialization);
+ };
+
private:
base::MessageLoop loop_;
ChromiumPicklePasserImpl chromium_service_;
@@ -297,6 +314,7 @@ TEST_F(PickleTest, BlinkProxyToChromiumService) {
}
TEST_F(PickleTest, PickleArray) {
+ ScopedForceMessageSerialization force_serialization;
auto proxy = ConnectToChromiumService();
auto pickles = std::vector<PickledStructChromium>(2);
pickles[0].set_foo(1);
@@ -328,6 +346,7 @@ TEST_F(PickleTest, PickleArray) {
}
TEST_F(PickleTest, PickleArrayArray) {
+ ScopedForceMessageSerialization force_serialization;
auto proxy = ConnectToChromiumService();
auto pickle_arrays = std::vector<std::vector<PickledStructChromium>>(2);
for (size_t i = 0; i < 2; ++i)
@@ -374,6 +393,7 @@ TEST_F(PickleTest, PickleArrayArray) {
}
TEST_F(PickleTest, PickleContainer) {
+ ScopedForceMessageSerialization force_serialization;
auto proxy = ConnectToChromiumService();
PickleContainerPtr pickle_container = PickleContainer::New();
pickle_container->f_struct.set_foo(42);
diff --git a/mojo/public/cpp/bindings/tests/pickled_types_blink.cc b/mojo/public/cpp/bindings/tests/pickled_types_blink.cc
index 7e556507bb..9e56bea90c 100644
--- a/mojo/public/cpp/bindings/tests/pickled_types_blink.cc
+++ b/mojo/public/cpp/bindings/tests/pickled_types_blink.cc
@@ -25,13 +25,6 @@ PickledStructBlink::~PickledStructBlink() {}
namespace IPC {
-void ParamTraits<mojo::test::PickledStructBlink>::GetSize(
- base::PickleSizer* sizer,
- const param_type& p) {
- sizer->AddInt();
- sizer->AddInt();
-}
-
void ParamTraits<mojo::test::PickledStructBlink>::Write(base::Pickle* m,
const param_type& p) {
m->WriteInt(p.foo());
@@ -51,9 +44,6 @@ bool ParamTraits<mojo::test::PickledStructBlink>::Read(
return true;
}
-#include "ipc/param_traits_size_macros.h"
-IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumBlink,
- mojo::test::PickledEnumBlink::VALUE_1)
#include "ipc/param_traits_write_macros.h"
IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumBlink,
mojo::test::PickledEnumBlink::VALUE_1)
diff --git a/mojo/public/cpp/bindings/tests/pickled_types_blink.h b/mojo/public/cpp/bindings/tests/pickled_types_blink.h
index 37e9e70578..fc6bd4e677 100644
--- a/mojo/public/cpp/bindings/tests/pickled_types_blink.h
+++ b/mojo/public/cpp/bindings/tests/pickled_types_blink.h
@@ -17,7 +17,6 @@
namespace base {
class Pickle;
class PickleIterator;
-class PickleSizer;
}
namespace mojo {
@@ -72,7 +71,6 @@ template <>
struct ParamTraits<mojo::test::PickledStructBlink> {
using param_type = mojo::test::PickledStructBlink;
- 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,
diff --git a/mojo/public/cpp/bindings/tests/pickled_types_chromium.cc b/mojo/public/cpp/bindings/tests/pickled_types_chromium.cc
index 9957c9a4d0..aeb0be4555 100644
--- a/mojo/public/cpp/bindings/tests/pickled_types_chromium.cc
+++ b/mojo/public/cpp/bindings/tests/pickled_types_chromium.cc
@@ -26,13 +26,6 @@ bool operator==(const PickledStructChromium& a,
namespace IPC {
-void ParamTraits<mojo::test::PickledStructChromium>::GetSize(
- base::PickleSizer* sizer,
- const param_type& p) {
- sizer->AddInt();
- sizer->AddInt();
-}
-
void ParamTraits<mojo::test::PickledStructChromium>::Write(
base::Pickle* m,
const param_type& p) {
@@ -53,9 +46,6 @@ bool ParamTraits<mojo::test::PickledStructChromium>::Read(
return true;
}
-#include "ipc/param_traits_size_macros.h"
-IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumChromium,
- mojo::test::PickledEnumChromium::VALUE_2)
#include "ipc/param_traits_write_macros.h"
IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumChromium,
mojo::test::PickledEnumChromium::VALUE_2)
diff --git a/mojo/public/cpp/bindings/tests/pickled_types_chromium.h b/mojo/public/cpp/bindings/tests/pickled_types_chromium.h
index d9287b62e7..51649c278e 100644
--- a/mojo/public/cpp/bindings/tests/pickled_types_chromium.h
+++ b/mojo/public/cpp/bindings/tests/pickled_types_chromium.h
@@ -16,7 +16,6 @@
namespace base {
class Pickle;
class PickleIterator;
-class PickleSizer;
}
namespace mojo {
@@ -65,7 +64,6 @@ template <>
struct ParamTraits<mojo::test::PickledStructChromium> {
using param_type = mojo::test::PickledStructChromium;
- 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,
diff --git a/mojo/public/cpp/bindings/tests/report_bad_message_unittest.cc b/mojo/public/cpp/bindings/tests/report_bad_message_unittest.cc
index 1bf3f7a4b7..cdd9799d0e 100644
--- a/mojo/public/cpp/bindings/tests/report_bad_message_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/report_bad_message_unittest.cc
@@ -5,11 +5,11 @@
#include "base/bind.h"
#include "base/callback.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/core/embedder/embedder.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/cpp/bindings/tests/bindings_test_base.h"
#include "mojo/public/interfaces/bindings/tests/test_bad_messages.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -26,7 +26,7 @@ class TestBadMessagesImpl : public TestBadMessages {
binding_.Bind(std::move(request));
}
- const ReportBadMessageCallback& bad_message_callback() {
+ ReportBadMessageCallback& bad_message_callback() {
return bad_message_callback_;
}
@@ -57,21 +57,20 @@ class TestBadMessagesImpl : public TestBadMessages {
DISALLOW_COPY_AND_ASSIGN(TestBadMessagesImpl);
};
-class ReportBadMessageTest : public testing::Test {
+class ReportBadMessageTest : public BindingsTestBase {
public:
ReportBadMessageTest() {}
void SetUp() override {
- mojo::edk::SetDefaultProcessErrorCallback(
- base::Bind(&ReportBadMessageTest::OnProcessError,
- base::Unretained(this)));
+ mojo::core::SetDefaultProcessErrorCallback(base::Bind(
+ &ReportBadMessageTest::OnProcessError, base::Unretained(this)));
impl_.BindImpl(MakeRequest(&proxy_));
}
void TearDown() override {
- mojo::edk::SetDefaultProcessErrorCallback(
- mojo::edk::ProcessErrorCallback());
+ mojo::core::SetDefaultProcessErrorCallback(
+ mojo::core::ProcessErrorCallback());
}
TestBadMessages* proxy() { return proxy_.get(); }
@@ -91,10 +90,9 @@ class ReportBadMessageTest : public testing::Test {
TestBadMessagesPtr proxy_;
TestBadMessagesImpl impl_;
base::Closure error_handler_;
- base::MessageLoop message_loop;
};
-TEST_F(ReportBadMessageTest, Request) {
+TEST_P(ReportBadMessageTest, Request) {
// Verify that basic immediate error reporting works.
bool error = false;
SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error));
@@ -102,7 +100,7 @@ TEST_F(ReportBadMessageTest, Request) {
EXPECT_TRUE(error);
}
-TEST_F(ReportBadMessageTest, RequestAsync) {
+TEST_P(ReportBadMessageTest, RequestAsync) {
bool error = false;
SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error));
@@ -115,11 +113,11 @@ TEST_F(ReportBadMessageTest, RequestAsync) {
// Now we can run the callback and it should trigger a bad message report.
DCHECK(!impl()->bad_message_callback().is_null());
- impl()->bad_message_callback().Run("bad!");
+ std::move(impl()->bad_message_callback()).Run("bad!");
EXPECT_TRUE(error);
}
-TEST_F(ReportBadMessageTest, Response) {
+TEST_P(ReportBadMessageTest, Response) {
bool error = false;
SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error));
@@ -137,7 +135,7 @@ TEST_F(ReportBadMessageTest, Response) {
EXPECT_TRUE(error);
}
-TEST_F(ReportBadMessageTest, ResponseAsync) {
+TEST_P(ReportBadMessageTest, ResponseAsync) {
bool error = false;
SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error));
@@ -157,11 +155,12 @@ TEST_F(ReportBadMessageTest, ResponseAsync) {
// Invoking this callback should report a bad message and trigger the error
// handler immediately.
- bad_message_callback.Run("this message is bad and should feel bad");
+ std::move(bad_message_callback)
+ .Run("this message is bad and should feel bad");
EXPECT_TRUE(error);
}
-TEST_F(ReportBadMessageTest, ResponseSync) {
+TEST_P(ReportBadMessageTest, ResponseSync) {
bool error = false;
SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error));
@@ -173,7 +172,7 @@ TEST_F(ReportBadMessageTest, ResponseSync) {
EXPECT_TRUE(error);
}
-TEST_F(ReportBadMessageTest, ResponseSyncDeferred) {
+TEST_P(ReportBadMessageTest, ResponseSyncDeferred) {
bool error = false;
SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error));
@@ -185,10 +184,12 @@ TEST_F(ReportBadMessageTest, ResponseSyncDeferred) {
}
EXPECT_FALSE(error);
- bad_message_callback.Run("nope nope nope");
+ std::move(bad_message_callback).Run("nope nope nope");
EXPECT_TRUE(error);
}
+INSTANTIATE_MOJO_BINDINGS_TEST_CASE_P(ReportBadMessageTest);
+
} // namespace
} // namespace test
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/request_response_unittest.cc b/mojo/public/cpp/bindings/tests/request_response_unittest.cc
index 43b8f0dc90..7293a5c4da 100644
--- a/mojo/public/cpp/bindings/tests/request_response_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/request_response_unittest.cc
@@ -5,9 +5,9 @@
#include <stdint.h>
#include <utility>
-#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/tests/bindings_test_base.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "mojo/public/interfaces/bindings/tests/sample_import.mojom.h"
#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h"
@@ -82,18 +82,15 @@ void RecordEnum(sample::Enum* storage,
closure.Run();
}
-class RequestResponseTest : public testing::Test {
+class RequestResponseTest : public BindingsTestBase {
public:
RequestResponseTest() {}
~RequestResponseTest() override { base::RunLoop().RunUntilIdle(); }
void PumpMessages() { base::RunLoop().RunUntilIdle(); }
-
- private:
- base::MessageLoop loop_;
};
-TEST_F(RequestResponseTest, EchoString) {
+TEST_P(RequestResponseTest, EchoString) {
sample::ProviderPtr provider;
ProviderImpl provider_impl(MakeRequest(&provider));
@@ -107,7 +104,7 @@ TEST_F(RequestResponseTest, EchoString) {
EXPECT_EQ(std::string("hello"), buf);
}
-TEST_F(RequestResponseTest, EchoStrings) {
+TEST_P(RequestResponseTest, EchoStrings) {
sample::ProviderPtr provider;
ProviderImpl provider_impl(MakeRequest(&provider));
@@ -121,7 +118,7 @@ TEST_F(RequestResponseTest, EchoStrings) {
EXPECT_EQ(std::string("hello world"), buf);
}
-TEST_F(RequestResponseTest, EchoMessagePipeHandle) {
+TEST_P(RequestResponseTest, EchoMessagePipeHandle) {
sample::ProviderPtr provider;
ProviderImpl provider_impl(MakeRequest(&provider));
@@ -139,7 +136,7 @@ TEST_F(RequestResponseTest, EchoMessagePipeHandle) {
EXPECT_EQ(std::string("hello"), value);
}
-TEST_F(RequestResponseTest, EchoEnum) {
+TEST_P(RequestResponseTest, EchoEnum) {
sample::ProviderPtr provider;
ProviderImpl provider_impl(MakeRequest(&provider));
@@ -152,6 +149,8 @@ TEST_F(RequestResponseTest, EchoEnum) {
EXPECT_EQ(sample::Enum::VALUE, value);
}
+INSTANTIATE_MOJO_BINDINGS_TEST_CASE_P(RequestResponseTest);
+
} // namespace
} // namespace test
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/router_test_util.cc b/mojo/public/cpp/bindings/tests/router_test_util.cc
index 9bab1cb360..b36de55c41 100644
--- a/mojo/public/cpp/bindings/tests/router_test_util.cc
+++ b/mojo/public/cpp/bindings/tests/router_test_util.cc
@@ -8,7 +8,7 @@
#include <stdint.h>
#include <string.h>
-#include "mojo/public/cpp/bindings/lib/message_builder.h"
+#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/cpp/bindings/tests/message_queue.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -17,10 +17,10 @@ namespace test {
void AllocRequestMessage(uint32_t name, const char* text, Message* message) {
size_t payload_size = strlen(text) + 1; // Plus null terminator.
- internal::MessageBuilder builder(name, Message::kFlagExpectsResponse,
- payload_size, 0);
- memcpy(builder.buffer()->Allocate(payload_size), text, payload_size);
- *message = std::move(*builder.message());
+ *message =
+ Message(name, Message::kFlagExpectsResponse, payload_size, 0, nullptr);
+ memcpy(message->payload_buffer()->AllocateAndGet(payload_size), text,
+ payload_size);
}
void AllocResponseMessage(uint32_t name,
@@ -28,11 +28,10 @@ void AllocResponseMessage(uint32_t name,
uint64_t request_id,
Message* message) {
size_t payload_size = strlen(text) + 1; // Plus null terminator.
- internal::MessageBuilder builder(name, Message::kFlagIsResponse, payload_size,
- 0);
- builder.message()->set_request_id(request_id);
- memcpy(builder.buffer()->Allocate(payload_size), text, payload_size);
- *message = std::move(*builder.message());
+ *message = Message(name, Message::kFlagIsResponse, payload_size, 0, nullptr);
+ message->set_request_id(request_id);
+ memcpy(message->payload_buffer()->AllocateAndGet(payload_size), text,
+ payload_size);
}
MessageAccumulator::MessageAccumulator(MessageQueue* queue,
@@ -64,7 +63,7 @@ bool ResponseGenerator::AcceptWithResponder(
bool result = SendResponse(message->name(), message->request_id(),
reinterpret_cast<const char*>(message->payload()),
responder.get());
- EXPECT_TRUE(responder->IsValid());
+ EXPECT_TRUE(responder->IsConnected());
return result;
}
diff --git a/mojo/public/cpp/bindings/tests/router_test_util.h b/mojo/public/cpp/bindings/tests/router_test_util.h
index dd6aff63da..6d18a032b1 100644
--- a/mojo/public/cpp/bindings/tests/router_test_util.h
+++ b/mojo/public/cpp/bindings/tests/router_test_util.h
@@ -64,7 +64,7 @@ class LazyResponseGenerator : public ResponseGenerator {
bool has_responder() const { return !!responder_; }
- bool responder_is_valid() const { return responder_->IsValid(); }
+ bool responder_is_valid() const { return responder_->IsConnected(); }
void set_closure(const base::Closure& closure) { closure_ = closure; }
diff --git a/mojo/public/cpp/bindings/tests/sample_service_unittest.cc b/mojo/public/cpp/bindings/tests/sample_service_unittest.cc
index 1f95a27a5e..9762a8bf4b 100644
--- a/mojo/public/cpp/bindings/tests/sample_service_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/sample_service_unittest.cc
@@ -9,6 +9,7 @@
#include <string>
#include <utility>
+#include "mojo/public/cpp/bindings/tests/bindings_test_base.h"
#include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -56,7 +57,7 @@ FooPtr MakeFoo() {
for (size_t i = 0; i < input_streams.size(); ++i) {
MojoCreateDataPipeOptions options;
options.struct_size = sizeof(MojoCreateDataPipeOptions);
- options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
+ options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
options.element_num_bytes = 1;
options.capacity_num_bytes = 1024;
mojo::ScopedDataPipeProducerHandle producer;
@@ -225,8 +226,8 @@ void Print(int depth, const char* name, const FooPtr& foo) {
}
}
-void DumpHex(const uint8_t* bytes, uint32_t num_bytes) {
- for (uint32_t i = 0; i < num_bytes; ++i) {
+void DumpHex(const uint8_t* bytes, size_t num_bytes) {
+ for (size_t i = 0; i < num_bytes; ++i) {
std::cout << std::setw(2) << std::setfill('0') << std::hex
<< uint32_t(bytes[i]);
@@ -278,6 +279,8 @@ class ServiceProxyImpl : public ServiceProxy {
class SimpleMessageReceiver : public mojo::MessageReceiverWithResponder {
public:
+ bool PrefersSerializedMessages() override { return true; }
+
bool Accept(mojo::Message* message) override {
// Imagine some IPC happened here.
@@ -302,9 +305,9 @@ class SimpleMessageReceiver : public mojo::MessageReceiverWithResponder {
}
};
-using BindingsSampleTest = testing::Test;
+using BindingsSampleTest = mojo::BindingsTestBase;
-TEST_F(BindingsSampleTest, Basic) {
+TEST_P(BindingsSampleTest, Basic) {
SimpleMessageReceiver receiver;
// User has a proxy to a Service somehow.
@@ -326,7 +329,7 @@ TEST_F(BindingsSampleTest, Basic) {
delete service;
}
-TEST_F(BindingsSampleTest, DefaultValues) {
+TEST_P(BindingsSampleTest, DefaultValues) {
DefaultsTestPtr defaults(DefaultsTest::New());
EXPECT_EQ(-12, defaults->a0);
EXPECT_EQ(kTwelve, defaults->a1);
@@ -358,5 +361,7 @@ TEST_F(BindingsSampleTest, DefaultValues) {
EXPECT_EQ(-0x123456789, defaults->a25);
}
+INSTANTIATE_MOJO_BINDINGS_TEST_CASE_P(BindingsSampleTest);
+
} // namespace
} // namespace sample
diff --git a/mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc b/mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc
index 275f10f9e7..37aaff32d9 100644
--- a/mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc
@@ -51,11 +51,11 @@ class SerializationWarningTest : public testing::Test {
warning_observer_.set_last_warning(mojo::internal::VALIDATION_ERROR_NONE);
mojo::internal::SerializationContext context;
- mojo::internal::FixedBufferForTesting buf(
- mojo::internal::PrepareToSerialize<MojomType>(obj, &context));
- typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
- mojo::internal::Serialize<MojomType>(obj, &buf, &data, &context);
-
+ mojo::Message message(0, 0, 0, 0, nullptr);
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data::BufferWriter
+ writer;
+ mojo::internal::Serialize<MojomType>(obj, message.payload_buffer(), &writer,
+ &context);
EXPECT_EQ(expected_warning, warning_observer_.last_warning());
}
@@ -66,12 +66,11 @@ class SerializationWarningTest : public testing::Test {
warning_observer_.set_last_warning(mojo::internal::VALIDATION_ERROR_NONE);
mojo::internal::SerializationContext context;
- mojo::internal::FixedBufferForTesting buf(
- mojo::internal::PrepareToSerialize<MojomType>(obj, &context));
- typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
- mojo::internal::Serialize<MojomType>(obj, &buf, &data, validate_params,
- &context);
-
+ mojo::Message message(0, 0, 0, 0, nullptr);
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data::BufferWriter
+ writer;
+ mojo::internal::Serialize<MojomType>(obj, message.payload_buffer(), &writer,
+ validate_params, &context);
EXPECT_EQ(expected_warning, warning_observer_.last_warning());
}
@@ -83,10 +82,11 @@ class SerializationWarningTest : public testing::Test {
warning_observer_.set_last_warning(mojo::internal::VALIDATION_ERROR_NONE);
mojo::internal::SerializationContext context;
- mojo::internal::FixedBufferForTesting buf(
- mojo::internal::PrepareToSerialize<MojomType>(obj, false, &context));
- typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
- mojo::internal::Serialize<MojomType>(obj, &buf, &data, false, &context);
+ mojo::Message message(0, 0, 0, 0, nullptr);
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data::BufferWriter
+ writer;
+ mojo::internal::Serialize<MojomType>(obj, message.payload_buffer(), &writer,
+ false, &context);
EXPECT_EQ(expected_warning, warning_observer_.last_warning());
}
diff --git a/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc b/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc
index 77b448a215..f74aa5acff 100644
--- a/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc
@@ -135,7 +135,9 @@ class StructTraitsTest : public testing::Test,
}
TraitsTestServicePtr GetTraitsTestProxy() {
- return traits_test_bindings_.CreateInterfacePtrAndBind(this);
+ TraitsTestServicePtr proxy;
+ traits_test_bindings_.AddBinding(this, mojo::MakeRequest(&proxy));
+ return proxy;
}
private:
@@ -394,13 +396,12 @@ TEST_F(StructTraitsTest, EchoMoveOnlyStructWithTraits) {
EXPECT_EQ(MOJO_RESULT_OK, Wait(received.get(), MOJO_HANDLE_SIGNAL_READABLE));
- char buffer[10] = {0};
- uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
- EXPECT_EQ(MOJO_RESULT_OK,
- ReadMessageRaw(received.get(), buffer, &buffer_size, nullptr,
- nullptr, MOJO_READ_MESSAGE_FLAG_NONE));
- EXPECT_EQ(kHelloSize, buffer_size);
- EXPECT_STREQ(kHello, buffer);
+ std::vector<uint8_t> bytes;
+ std::vector<ScopedHandle> handles;
+ EXPECT_EQ(MOJO_RESULT_OK, ReadMessageRaw(received.get(), &bytes, &handles,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kHelloSize, bytes.size());
+ EXPECT_STREQ(kHello, reinterpret_cast<char*>(bytes.data()));
}
void CaptureNullableMoveOnlyStructWithTraitsImpl(
@@ -490,8 +491,8 @@ TEST_F(StructTraitsTest, TypemapUniquePtr) {
{
base::RunLoop loop;
proxy->EchoStructWithTraitsForUniquePtr(
- base::MakeUnique<int>(12345),
- base::Bind(&ExpectUniquePtr, base::Passed(base::MakeUnique<int>(12345)),
+ std::make_unique<int>(12345),
+ base::Bind(&ExpectUniquePtr, base::Passed(std::make_unique<int>(12345)),
loop.QuitClosure()));
loop.Run();
}
@@ -549,5 +550,10 @@ TEST_F(StructTraitsTest, EchoUnionWithTraits) {
}
}
+TEST_F(StructTraitsTest, DefaultValueOfEnumWithTraits) {
+ auto container = EnumWithTraitsContainer::New();
+ EXPECT_EQ(EnumWithTraitsImpl::CUSTOM_VALUE_1, container->f_field);
+}
+
} // namespace test
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/struct_unittest.cc b/mojo/public/cpp/bindings/tests/struct_unittest.cc
index a687052706..c6bd169f06 100644
--- a/mojo/public/cpp/bindings/tests/struct_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/struct_unittest.cc
@@ -28,6 +28,33 @@ void CheckRect(const Rect& rect, int32_t factor = 1) {
EXPECT_EQ(20 * factor, rect.height);
}
+template <typename StructType>
+struct SerializeStructHelperTraits {
+ using DataView = typename StructType::DataView;
+};
+
+template <>
+struct SerializeStructHelperTraits<native::NativeStruct> {
+ using DataView = native::NativeStructDataView;
+};
+
+template <typename InputType, typename DataType>
+size_t SerializeStruct(InputType& input,
+ mojo::Message* message,
+ mojo::internal::SerializationContext* context,
+ DataType** out_data) {
+ using StructType = typename InputType::Struct;
+ using DataViewType =
+ typename SerializeStructHelperTraits<StructType>::DataView;
+ *message = mojo::Message(0, 0, 0, 0, nullptr);
+ const size_t payload_start = message->payload_buffer()->cursor();
+ typename DataType::BufferWriter writer;
+ mojo::internal::Serialize<DataViewType>(input, message->payload_buffer(),
+ &writer, context);
+ *out_data = writer.is_null() ? nullptr : writer.data();
+ return message->payload_buffer()->cursor() - payload_start;
+}
+
MultiVersionStructPtr MakeMultiVersionStruct() {
MessagePipe pipe;
return MultiVersionStruct::New(123, MakeRect(5), std::string("hello"),
@@ -45,19 +72,18 @@ U SerializeAndDeserialize(T input) {
using OutputDataType =
typename mojo::internal::MojomTypeTraits<OutputMojomType>::Data*;
+ mojo::Message message;
mojo::internal::SerializationContext context;
- size_t size =
- mojo::internal::PrepareToSerialize<InputMojomType>(input, &context);
- mojo::internal::FixedBufferForTesting buf(size + 32);
InputDataType data;
- mojo::internal::Serialize<InputMojomType>(input, &buf, &data, &context);
+ SerializeStruct(input, &message, &context, &data);
// Set the subsequent area to a special value, so that we can find out if we
// mistakenly access the area.
- void* subsequent_area = buf.Allocate(32);
+ void* subsequent_area = message.payload_buffer()->AllocateAndGet(32);
memset(subsequent_area, 0xAA, 32);
- OutputDataType output_data = reinterpret_cast<OutputDataType>(data);
+ OutputDataType output_data =
+ reinterpret_cast<OutputDataType>(message.mutable_payload());
U output;
mojo::internal::Deserialize<OutputMojomType>(output_data, &output, &context);
@@ -124,15 +150,13 @@ TEST_F(StructTest, Clone) {
TEST_F(StructTest, Serialization_Basic) {
RectPtr rect(MakeRect());
- size_t size = mojo::internal::PrepareToSerialize<RectDataView>(rect, nullptr);
- EXPECT_EQ(8U + 16U, size);
-
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::Rect_Data* data;
- mojo::internal::Serialize<RectDataView>(rect, &buf, &data, nullptr);
+ EXPECT_EQ(8U + 16U, SerializeStruct(rect, &message, &context, &data));
RectPtr rect2;
- mojo::internal::Deserialize<RectDataView>(data, &rect2, nullptr);
+ mojo::internal::Deserialize<RectDataView>(data, &rect2, &context);
CheckRect(*rect2);
}
@@ -155,16 +179,14 @@ TEST_F(StructTest, Construction_StructPointers) {
TEST_F(StructTest, Serialization_StructPointers) {
RectPairPtr pair(RectPair::New(MakeRect(), MakeRect()));
- size_t size =
- mojo::internal::PrepareToSerialize<RectPairDataView>(pair, nullptr);
- EXPECT_EQ(8U + 16U + 2 * (8U + 16U), size);
-
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::RectPair_Data* data;
- mojo::internal::Serialize<RectPairDataView>(pair, &buf, &data, nullptr);
+ EXPECT_EQ(8U + 16U + 2 * (8U + 16U),
+ SerializeStruct(pair, &message, &context, &data));
RectPairPtr pair2;
- mojo::internal::Deserialize<RectPairDataView>(data, &pair2, nullptr);
+ mojo::internal::Deserialize<RectPairDataView>(data, &pair2, &context);
CheckRect(*pair2->first);
CheckRect(*pair2->second);
@@ -179,8 +201,9 @@ TEST_F(StructTest, Serialization_ArrayPointers) {
NamedRegionPtr region(
NamedRegion::New(std::string("region"), std::move(rects)));
- size_t size =
- mojo::internal::PrepareToSerialize<NamedRegionDataView>(region, nullptr);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
+ internal::NamedRegion_Data* data;
EXPECT_EQ(8U + // header
8U + // name pointer
8U + // rects pointer
@@ -190,14 +213,10 @@ TEST_F(StructTest, Serialization_ArrayPointers) {
4 * 8U + // rects payload (four pointers)
4 * (8U + // rect header
16U), // rect payload (four ints)
- size);
-
- mojo::internal::FixedBufferForTesting buf(size);
- internal::NamedRegion_Data* data;
- mojo::internal::Serialize<NamedRegionDataView>(region, &buf, &data, nullptr);
+ SerializeStruct(region, &message, &context, &data));
NamedRegionPtr region2;
- mojo::internal::Deserialize<NamedRegionDataView>(data, &region2, nullptr);
+ mojo::internal::Deserialize<NamedRegionDataView>(data, &region2, &context);
EXPECT_EQ("region", *region2->name);
@@ -212,19 +231,16 @@ TEST_F(StructTest, Serialization_NullArrayPointers) {
EXPECT_FALSE(region->name);
EXPECT_FALSE(region->rects);
- size_t size =
- mojo::internal::PrepareToSerialize<NamedRegionDataView>(region, nullptr);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
+ internal::NamedRegion_Data* data;
EXPECT_EQ(8U + // header
8U + // name pointer
8U, // rects pointer
- size);
-
- mojo::internal::FixedBufferForTesting buf(size);
- internal::NamedRegion_Data* data;
- mojo::internal::Serialize<NamedRegionDataView>(region, &buf, &data, nullptr);
+ SerializeStruct(region, &message, &context, &data));
NamedRegionPtr region2;
- mojo::internal::Deserialize<NamedRegionDataView>(data, &region2, nullptr);
+ mojo::internal::Deserialize<NamedRegionDataView>(data, &region2, &context);
EXPECT_FALSE(region2->name);
EXPECT_FALSE(region2->rects);
@@ -360,70 +376,57 @@ TEST_F(StructTest, Versioning_NewToOld) {
// Serialization test for native struct.
TEST_F(StructTest, Serialization_NativeStruct) {
- using Data = mojo::internal::NativeStruct_Data;
+ using Data = native::internal::NativeStruct_Data;
{
// Serialization of a null native struct.
- NativeStructPtr native;
- size_t size = mojo::internal::PrepareToSerialize<NativeStructDataView>(
- native, nullptr);
- EXPECT_EQ(0u, size);
- mojo::internal::FixedBufferForTesting buf(size);
+ native::NativeStructPtr native;
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
Data* data = nullptr;
- mojo::internal::Serialize<NativeStructDataView>(std::move(native), &buf,
- &data, nullptr);
-
+ EXPECT_EQ(0u, SerializeStruct(native, &message, &context, &data));
EXPECT_EQ(nullptr, data);
- NativeStructPtr output_native;
- mojo::internal::Deserialize<NativeStructDataView>(data, &output_native,
- nullptr);
+ native::NativeStructPtr output_native;
+ mojo::internal::Deserialize<native::NativeStructDataView>(
+ data, &output_native, &context);
EXPECT_TRUE(output_native.is_null());
}
{
// Serialization of a native struct with null data.
- NativeStructPtr native(NativeStruct::New());
- size_t size = mojo::internal::PrepareToSerialize<NativeStructDataView>(
- native, nullptr);
- EXPECT_EQ(0u, size);
- mojo::internal::FixedBufferForTesting buf(size);
+ native::NativeStructPtr native(native::NativeStruct::New());
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
Data* data = nullptr;
- mojo::internal::Serialize<NativeStructDataView>(std::move(native), &buf,
- &data, nullptr);
+ EXPECT_EQ(32u, SerializeStruct(native, &message, &context, &data));
+ EXPECT_EQ(0u, data->data.Get()->size());
- EXPECT_EQ(nullptr, data);
-
- NativeStructPtr output_native;
- mojo::internal::Deserialize<NativeStructDataView>(data, &output_native,
- nullptr);
- EXPECT_TRUE(output_native.is_null());
+ native::NativeStructPtr output_native;
+ mojo::internal::Deserialize<native::NativeStructDataView>(
+ data, &output_native, &context);
+ EXPECT_TRUE(output_native->data.empty());
}
{
- NativeStructPtr native(NativeStruct::New());
+ native::NativeStructPtr native(native::NativeStruct::New());
native->data = std::vector<uint8_t>{'X', 'Y'};
- size_t size = mojo::internal::PrepareToSerialize<NativeStructDataView>(
- native, nullptr);
- EXPECT_EQ(16u, size);
- mojo::internal::FixedBufferForTesting buf(size);
-
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
Data* data = nullptr;
- mojo::internal::Serialize<NativeStructDataView>(std::move(native), &buf,
- &data, nullptr);
-
- EXPECT_NE(nullptr, data);
+ EXPECT_EQ(40u, SerializeStruct(native, &message, &context, &data));
+ EXPECT_EQ(2u, data->data.Get()->size());
- NativeStructPtr output_native;
- mojo::internal::Deserialize<NativeStructDataView>(data, &output_native,
- nullptr);
+ native::NativeStructPtr output_native;
+ mojo::internal::Deserialize<native::NativeStructDataView>(
+ data, &output_native, &context);
ASSERT_TRUE(output_native);
- ASSERT_FALSE(output_native->data->empty());
- EXPECT_EQ(2u, output_native->data->size());
- EXPECT_EQ('X', (*output_native->data)[0]);
- EXPECT_EQ('Y', (*output_native->data)[1]);
+ ASSERT_FALSE(output_native->data.empty());
+ EXPECT_EQ(2u, output_native->data.size());
+ EXPECT_EQ('X', output_native->data[0]);
+ EXPECT_EQ('Y', output_native->data[1]);
}
}
diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits.typemap b/mojo/public/cpp/bindings/tests/struct_with_traits.typemap
index 752ce44b58..fccaf2b486 100644
--- a/mojo/public/cpp/bindings/tests/struct_with_traits.typemap
+++ b/mojo/public/cpp/bindings/tests/struct_with_traits.typemap
@@ -18,9 +18,12 @@ deps = [
type_mappings = [
"mojo.test.EnumWithTraits=mojo::test::EnumWithTraitsImpl",
"mojo.test.StructWithTraits=mojo::test::StructWithTraitsImpl",
+ "mojo.test.StructWithUnreachableTraits=mojo::test::StructWithUnreachableTraitsImpl",
"mojo.test.NestedStructWithTraits=mojo::test::NestedStructWithTraitsImpl",
"mojo.test.TrivialStructWithTraits=mojo::test::TrivialStructWithTraitsImpl[copyable_pass_by_value]",
"mojo.test.MoveOnlyStructWithTraits=mojo::test::MoveOnlyStructWithTraitsImpl[move_only]",
"mojo.test.StructWithTraitsForUniquePtr=std::unique_ptr<int>[move_only,nullable_is_same_type]",
"mojo.test.UnionWithTraits=std::unique_ptr<mojo::test::UnionWithTraitsBase>[move_only,nullable_is_same_type]",
+ "mojo.test.StructForceSerialize=mojo::test::StructForceSerializeImpl[force_serialize]",
+ "mojo.test.StructNestedForceSerialize=mojo::test::StructNestedForceSerializeImpl",
]
diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits_impl.cc b/mojo/public/cpp/bindings/tests/struct_with_traits_impl.cc
index cbdd4bfde7..e537830a77 100644
--- a/mojo/public/cpp/bindings/tests/struct_with_traits_impl.cc
+++ b/mojo/public/cpp/bindings/tests/struct_with_traits_impl.cc
@@ -7,30 +7,38 @@
namespace mojo {
namespace test {
-NestedStructWithTraitsImpl::NestedStructWithTraitsImpl() {}
+NestedStructWithTraitsImpl::NestedStructWithTraitsImpl() = default;
NestedStructWithTraitsImpl::NestedStructWithTraitsImpl(int32_t in_value)
: value(in_value) {}
-StructWithTraitsImpl::StructWithTraitsImpl() {}
+StructWithTraitsImpl::StructWithTraitsImpl() = default;
-StructWithTraitsImpl::~StructWithTraitsImpl() {}
+StructWithTraitsImpl::~StructWithTraitsImpl() = default;
StructWithTraitsImpl::StructWithTraitsImpl(const StructWithTraitsImpl& other) =
default;
-MoveOnlyStructWithTraitsImpl::MoveOnlyStructWithTraitsImpl() {}
+MoveOnlyStructWithTraitsImpl::MoveOnlyStructWithTraitsImpl() = default;
MoveOnlyStructWithTraitsImpl::MoveOnlyStructWithTraitsImpl(
MoveOnlyStructWithTraitsImpl&& other) = default;
-MoveOnlyStructWithTraitsImpl::~MoveOnlyStructWithTraitsImpl() {}
+MoveOnlyStructWithTraitsImpl::~MoveOnlyStructWithTraitsImpl() = default;
MoveOnlyStructWithTraitsImpl& MoveOnlyStructWithTraitsImpl::operator=(
MoveOnlyStructWithTraitsImpl&& other) = default;
-UnionWithTraitsInt32::~UnionWithTraitsInt32() {}
+UnionWithTraitsInt32::~UnionWithTraitsInt32() = default;
-UnionWithTraitsStruct::~UnionWithTraitsStruct() {}
+UnionWithTraitsStruct::~UnionWithTraitsStruct() = default;
+
+StructForceSerializeImpl::StructForceSerializeImpl() = default;
+
+StructForceSerializeImpl::~StructForceSerializeImpl() = default;
+
+StructNestedForceSerializeImpl::StructNestedForceSerializeImpl() = default;
+
+StructNestedForceSerializeImpl::~StructNestedForceSerializeImpl() = default;
} // namespace test
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits_impl.h b/mojo/public/cpp/bindings/tests/struct_with_traits_impl.h
index 7b007cc083..e8d96120a2 100644
--- a/mojo/public/cpp/bindings/tests/struct_with_traits_impl.h
+++ b/mojo/public/cpp/bindings/tests/struct_with_traits_impl.h
@@ -98,6 +98,14 @@ class StructWithTraitsImpl {
std::map<std::string, NestedStructWithTraitsImpl> struct_map_;
};
+// A type which corresponds nominally to the
+// mojo::test::StructWithUnreachableTraits mojom type. Used to test that said
+// type is never serialized, i.e. objects of this type are simply copied into
+// a message as-is when written to an intra-process interface.
+struct StructWithUnreachableTraitsImpl {
+ int32_t magic_number = 0;
+};
+
// A type which knows how to look like a mojo::test::TrivialStructWithTraits
// mojom type by way of mojo::StructTraits.
struct TrivialStructWithTraitsImpl {
@@ -162,6 +170,46 @@ class UnionWithTraitsStruct : public UnionWithTraitsBase {
NestedStructWithTraitsImpl struct_;
};
+class StructForceSerializeImpl {
+ public:
+ StructForceSerializeImpl();
+ ~StructForceSerializeImpl();
+
+ void set_value(int32_t value) { value_ = value; }
+ int32_t value() const { return value_; }
+
+ void set_was_serialized() const { was_serialized_ = true; }
+ bool was_serialized() const { return was_serialized_; }
+
+ void set_was_deserialized() { was_deserialized_ = true; }
+ bool was_deserialized() const { return was_deserialized_; }
+
+ private:
+ int32_t value_ = 0;
+ mutable bool was_serialized_ = false;
+ bool was_deserialized_ = false;
+};
+
+class StructNestedForceSerializeImpl {
+ public:
+ StructNestedForceSerializeImpl();
+ ~StructNestedForceSerializeImpl();
+
+ StructForceSerializeImpl& force() { return force_; }
+ const StructForceSerializeImpl& force() const { return force_; }
+
+ void set_was_serialized() const { was_serialized_ = true; }
+ bool was_serialized() const { return was_serialized_; }
+
+ void set_was_deserialized() { was_deserialized_ = true; }
+ bool was_deserialized() const { return was_deserialized_; }
+
+ private:
+ StructForceSerializeImpl force_;
+ mutable bool was_serialized_ = false;
+ bool was_deserialized_ = false;
+};
+
} // namespace test
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc b/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc
index 6b770b1a49..2586d8d0d4 100644
--- a/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc
+++ b/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc
@@ -5,39 +5,11 @@
#include "mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h"
namespace mojo {
-namespace {
-
-struct Context {
- int32_t value;
-};
-
-} // namespace
-
-// static
-void* StructTraits<test::NestedStructWithTraitsDataView,
- test::NestedStructWithTraitsImpl>::
- SetUpContext(const test::NestedStructWithTraitsImpl& input) {
- Context* context = new Context;
- context->value = input.value;
- return context;
-}
-
-// static
-void StructTraits<test::NestedStructWithTraitsDataView,
- test::NestedStructWithTraitsImpl>::
- TearDownContext(const test::NestedStructWithTraitsImpl& input,
- void* context) {
- Context* context_obj = static_cast<Context*>(context);
- CHECK_EQ(context_obj->value, input.value);
- delete context_obj;
-}
// static
int32_t StructTraits<test::NestedStructWithTraitsDataView,
test::NestedStructWithTraitsImpl>::
- value(const test::NestedStructWithTraitsImpl& input, void* context) {
- Context* context_obj = static_cast<Context*>(context);
- CHECK_EQ(context_obj->value, input.value);
+ value(const test::NestedStructWithTraitsImpl& input) {
return input.value;
}
diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h b/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h
index adcad8aa9e..69d344f7df 100644
--- a/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h
+++ b/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h
@@ -20,12 +20,7 @@ namespace mojo {
template <>
struct StructTraits<test::NestedStructWithTraitsDataView,
test::NestedStructWithTraitsImpl> {
- static void* SetUpContext(const test::NestedStructWithTraitsImpl& input);
- static void TearDownContext(const test::NestedStructWithTraitsImpl& input,
- void* context);
-
- static int32_t value(const test::NestedStructWithTraitsImpl& input,
- void* context);
+ static int32_t value(const test::NestedStructWithTraitsImpl& input);
static bool Read(test::NestedStructWithTraitsDataView data,
test::NestedStructWithTraitsImpl* output);
@@ -99,6 +94,22 @@ struct StructTraits<test::StructWithTraitsDataView,
};
template <>
+struct StructTraits<test::StructWithUnreachableTraitsDataView,
+ test::StructWithUnreachableTraitsImpl> {
+ public:
+ static bool ignore_me(const test::StructWithUnreachableTraitsImpl& input) {
+ NOTREACHED();
+ return false;
+ }
+
+ static bool Read(test::StructWithUnreachableTraitsDataView data,
+ test::StructWithUnreachableTraitsImpl* out) {
+ NOTREACHED();
+ return false;
+ }
+};
+
+template <>
struct StructTraits<test::TrivialStructWithTraitsDataView,
test::TrivialStructWithTraitsImpl> {
// Deserialization to test::TrivialStructTraitsImpl.
@@ -191,6 +202,40 @@ struct UnionTraits<test::UnionWithTraitsDataView,
}
};
+template <>
+struct StructTraits<test::StructForceSerializeDataView,
+ test::StructForceSerializeImpl> {
+ static int32_t value(const test::StructForceSerializeImpl& impl) {
+ impl.set_was_serialized();
+ return impl.value();
+ }
+
+ static bool Read(test::StructForceSerializeDataView data,
+ test::StructForceSerializeImpl* out) {
+ out->set_value(data.value());
+ out->set_was_deserialized();
+ return true;
+ }
+};
+
+template <>
+struct StructTraits<test::StructNestedForceSerializeDataView,
+ test::StructNestedForceSerializeImpl> {
+ static const test::StructForceSerializeImpl& force(
+ const test::StructNestedForceSerializeImpl& impl) {
+ impl.set_was_serialized();
+ return impl.force();
+ }
+
+ static bool Read(test::StructNestedForceSerializeDataView data,
+ test::StructNestedForceSerializeImpl* out) {
+ if (!data.ReadForce(&out->force()))
+ return false;
+ out->set_was_deserialized();
+ return true;
+ }
+};
+
} // namespace mojo
#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_TRAITS_H_
diff --git a/mojo/public/cpp/bindings/tests/sync_handle_registry_unittest.cc b/mojo/public/cpp/bindings/tests/sync_handle_registry_unittest.cc
new file mode 100644
index 0000000000..2f17fc7fbf
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/sync_handle_registry_unittest.cc
@@ -0,0 +1,258 @@
+// 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 <memory>
+
+#include "mojo/public/cpp/bindings/sync_handle_registry.h"
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/waitable_event.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+
+class SyncHandleRegistryTest : public testing::Test {
+ public:
+ SyncHandleRegistryTest() : registry_(SyncHandleRegistry::current()) {}
+
+ const scoped_refptr<SyncHandleRegistry>& registry() { return registry_; }
+
+ private:
+ scoped_refptr<SyncHandleRegistry> registry_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncHandleRegistryTest);
+};
+
+TEST_F(SyncHandleRegistryTest, DuplicateEventRegistration) {
+ bool called1 = false;
+ bool called2 = false;
+ auto callback = [](bool* called) { *called = true; };
+ auto callback1 = base::Bind(callback, &called1);
+ auto callback2 = base::Bind(callback, &called2);
+
+ base::WaitableEvent e(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::SIGNALED);
+ registry()->RegisterEvent(&e, callback1);
+ registry()->RegisterEvent(&e, callback2);
+
+ const bool* stop_flags[] = {&called1, &called2};
+ registry()->Wait(stop_flags, 2);
+
+ EXPECT_TRUE(called1);
+ EXPECT_TRUE(called2);
+ registry()->UnregisterEvent(&e, callback1);
+
+ called1 = false;
+ called2 = false;
+
+ registry()->Wait(stop_flags, 2);
+
+ EXPECT_FALSE(called1);
+ EXPECT_TRUE(called2);
+
+ registry()->UnregisterEvent(&e, callback2);
+}
+
+TEST_F(SyncHandleRegistryTest, UnregisterDuplicateEventInNestedWait) {
+ base::WaitableEvent e(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::SIGNALED);
+ bool called1 = false;
+ bool called2 = false;
+ bool called3 = false;
+ auto callback1 = base::Bind([](bool* called) { *called = true; }, &called1);
+ auto callback2 = base::Bind(
+ [](base::WaitableEvent* e, const base::Closure& other_callback,
+ scoped_refptr<SyncHandleRegistry> registry, bool* called) {
+ registry->UnregisterEvent(e, other_callback);
+ *called = true;
+ },
+ &e, callback1, registry(), &called2);
+ auto callback3 = base::Bind([](bool* called) { *called = true; }, &called3);
+
+ registry()->RegisterEvent(&e, callback1);
+ registry()->RegisterEvent(&e, callback2);
+ registry()->RegisterEvent(&e, callback3);
+
+ const bool* stop_flags[] = {&called1, &called2, &called3};
+ registry()->Wait(stop_flags, 3);
+
+ // We don't make any assumptions about the order in which callbacks run, so
+ // we can't check |called1| - it may or may not get set depending on internal
+ // details. All we know is |called2| should be set, and a subsequent wait
+ // should definitely NOT set |called1|.
+ EXPECT_TRUE(called2);
+ EXPECT_TRUE(called3);
+
+ called1 = false;
+ called2 = false;
+ called3 = false;
+
+ registry()->UnregisterEvent(&e, callback2);
+ registry()->Wait(stop_flags, 3);
+
+ EXPECT_FALSE(called1);
+ EXPECT_FALSE(called2);
+ EXPECT_TRUE(called3);
+}
+
+TEST_F(SyncHandleRegistryTest, UnregisterAndRegisterForNewEventInCallback) {
+ auto e = std::make_unique<base::WaitableEvent>(
+ base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::SIGNALED);
+ bool called = false;
+ base::Closure callback_holder;
+ auto callback = base::Bind(
+ [](std::unique_ptr<base::WaitableEvent>* e,
+ base::Closure* callback_holder,
+ scoped_refptr<SyncHandleRegistry> registry, bool* called) {
+ EXPECT_FALSE(*called);
+
+ registry->UnregisterEvent(e->get(), *callback_holder);
+ e->reset();
+ *called = true;
+
+ base::WaitableEvent nested_event(
+ base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::SIGNALED);
+ bool nested_called = false;
+ auto nested_callback =
+ base::Bind([](bool* called) { *called = true; }, &nested_called);
+ registry->RegisterEvent(&nested_event, nested_callback);
+ const bool* stop_flag = &nested_called;
+ registry->Wait(&stop_flag, 1);
+ registry->UnregisterEvent(&nested_event, nested_callback);
+ },
+ &e, &callback_holder, registry(), &called);
+ callback_holder = callback;
+
+ registry()->RegisterEvent(e.get(), callback);
+
+ const bool* stop_flag = &called;
+ registry()->Wait(&stop_flag, 1);
+ EXPECT_TRUE(called);
+}
+
+TEST_F(SyncHandleRegistryTest, UnregisterAndRegisterForSameEventInCallback) {
+ base::WaitableEvent e(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::SIGNALED);
+ bool called = false;
+ base::Closure callback_holder;
+ auto callback = base::Bind(
+ [](base::WaitableEvent* e, base::Closure* callback_holder,
+ scoped_refptr<SyncHandleRegistry> registry, bool* called) {
+ EXPECT_FALSE(*called);
+
+ registry->UnregisterEvent(e, *callback_holder);
+ *called = true;
+
+ bool nested_called = false;
+ auto nested_callback =
+ base::Bind([](bool* called) { *called = true; }, &nested_called);
+ registry->RegisterEvent(e, nested_callback);
+ const bool* stop_flag = &nested_called;
+ registry->Wait(&stop_flag, 1);
+ registry->UnregisterEvent(e, nested_callback);
+
+ EXPECT_TRUE(nested_called);
+ },
+ &e, &callback_holder, registry(), &called);
+ callback_holder = callback;
+
+ registry()->RegisterEvent(&e, callback);
+
+ const bool* stop_flag = &called;
+ registry()->Wait(&stop_flag, 1);
+ EXPECT_TRUE(called);
+}
+
+TEST_F(SyncHandleRegistryTest, RegisterDuplicateEventFromWithinCallback) {
+ base::WaitableEvent e(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::SIGNALED);
+ bool called = false;
+ int call_count = 0;
+ auto callback = base::Bind(
+ [](base::WaitableEvent* e, scoped_refptr<SyncHandleRegistry> registry,
+ bool* called, int* call_count) {
+ // Don't re-enter.
+ ++(*call_count);
+ if (*called)
+ return;
+
+ *called = true;
+
+ bool called2 = false;
+ auto callback2 =
+ base::Bind([](bool* called) { *called = true; }, &called2);
+ registry->RegisterEvent(e, callback2);
+
+ const bool* stop_flag = &called2;
+ registry->Wait(&stop_flag, 1);
+
+ registry->UnregisterEvent(e, callback2);
+ },
+ &e, registry(), &called, &call_count);
+
+ registry()->RegisterEvent(&e, callback);
+
+ const bool* stop_flag = &called;
+ registry()->Wait(&stop_flag, 1);
+
+ EXPECT_TRUE(called);
+ EXPECT_EQ(2, call_count);
+
+ registry()->UnregisterEvent(&e, callback);
+}
+
+TEST_F(SyncHandleRegistryTest, UnregisterUniqueEventInNestedWait) {
+ auto e1 = std::make_unique<base::WaitableEvent>(
+ base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ base::WaitableEvent e2(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::SIGNALED);
+ bool called1 = false;
+ bool called2 = false;
+ auto callback1 = base::Bind([](bool* called) { *called = true; }, &called1);
+ auto callback2 = base::Bind(
+ [](std::unique_ptr<base::WaitableEvent>* e1,
+ const base::Closure& other_callback,
+ scoped_refptr<SyncHandleRegistry> registry, bool* called) {
+ // Prevent re-entrancy.
+ if (*called)
+ return;
+
+ registry->UnregisterEvent(e1->get(), other_callback);
+ *called = true;
+ e1->reset();
+
+ // Nest another wait.
+ bool called3 = false;
+ auto callback3 =
+ base::Bind([](bool* called) { *called = true; }, &called3);
+ base::WaitableEvent e3(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::SIGNALED);
+ registry->RegisterEvent(&e3, callback3);
+
+ // This nested Wait() must not attempt to wait on |e1| since it has
+ // been unregistered. This would crash otherwise, since |e1| has been
+ // deleted. See http://crbug.com/761097.
+ const bool* stop_flags[] = {&called3};
+ registry->Wait(stop_flags, 1);
+
+ EXPECT_TRUE(called3);
+ registry->UnregisterEvent(&e3, callback3);
+ },
+ &e1, callback1, registry(), &called2);
+
+ registry()->RegisterEvent(e1.get(), callback1);
+ registry()->RegisterEvent(&e2, callback2);
+
+ const bool* stop_flags[] = {&called1, &called2};
+ registry()->Wait(stop_flags, 2);
+
+ EXPECT_TRUE(called2);
+
+ registry()->UnregisterEvent(&e2, callback2);
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/sync_method_unittest.cc b/mojo/public/cpp/bindings/tests/sync_method_unittest.cc
index 084e080ad3..f09e732fda 100644
--- a/mojo/public/cpp/bindings/tests/sync_method_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/sync_method_unittest.cc
@@ -7,11 +7,14 @@
#include "base/bind.h"
#include "base/logging.h"
#include "base/macros.h"
-#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
+#include "base/sequence_token.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/test/scoped_task_environment.h"
#include "base/threading/thread.h"
#include "mojo/public/cpp/bindings/associated_binding.h"
#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/tests/bindings_test_base.h"
#include "mojo/public/interfaces/bindings/tests/test_sync_methods.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -238,9 +241,12 @@ class PtrWrapper {
DISALLOW_COPY_AND_ASSIGN(PtrWrapper);
};
-// The type parameter for SyncMethodCommonTests for varying the Interface and
-// whether to use InterfacePtr or ThreadSafeInterfacePtr.
-template <typename InterfaceT, bool use_thread_safe_ptr>
+// The type parameter for SyncMethodCommonTests and
+// SyncMethodOnSequenceCommonTests for varying the Interface and whether to use
+// InterfacePtr or ThreadSafeInterfacePtr.
+template <typename InterfaceT,
+ bool use_thread_safe_ptr,
+ BindingsTestSerializationMode serialization_mode>
struct TestParams {
using Interface = InterfaceT;
static const bool kIsThreadSafeInterfacePtrTest = use_thread_safe_ptr;
@@ -253,18 +259,20 @@ struct TestParams {
return PtrWrapper<Interface>(std::move(ptr));
}
}
+
+ static const BindingsTestSerializationMode kSerializationMode =
+ serialization_mode;
};
template <typename Interface>
-class TestSyncServiceThread {
+class TestSyncServiceSequence {
public:
- TestSyncServiceThread()
- : thread_("TestSyncServiceThread"), ping_called_(false) {
- thread_.Start();
- }
+ TestSyncServiceSequence()
+ : task_runner_(base::CreateSequencedTaskRunnerWithTraits({})),
+ ping_called_(false) {}
void SetUp(InterfaceRequest<Interface> request) {
- CHECK(thread_.task_runner()->BelongsToCurrentThread());
+ CHECK(task_runner()->RunsTasksInCurrentSequence());
impl_.reset(new ImplTypeFor<Interface>(std::move(request)));
impl_->set_ping_handler(
[this](const typename Interface::PingCallback& callback) {
@@ -277,25 +285,25 @@ class TestSyncServiceThread {
}
void TearDown() {
- CHECK(thread_.task_runner()->BelongsToCurrentThread());
+ CHECK(task_runner()->RunsTasksInCurrentSequence());
impl_.reset();
}
- base::Thread* thread() { return &thread_; }
+ base::SequencedTaskRunner* task_runner() { return task_runner_.get(); }
bool ping_called() const {
base::AutoLock locker(lock_);
return ping_called_;
}
private:
- base::Thread thread_;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
std::unique_ptr<ImplTypeFor<Interface>> impl_;
mutable base::Lock lock_;
bool ping_called_;
- DISALLOW_COPY_AND_ASSIGN(TestSyncServiceThread);
+ DISALLOW_COPY_AND_ASSIGN(TestSyncServiceSequence);
};
class SyncMethodTest : public testing::Test {
@@ -304,14 +312,18 @@ class SyncMethodTest : public testing::Test {
~SyncMethodTest() override { base::RunLoop().RunUntilIdle(); }
protected:
- base::MessageLoop loop_;
+ base::test::ScopedTaskEnvironment task_environment;
};
-template <typename T>
+template <typename TypeParam>
class SyncMethodCommonTest : public SyncMethodTest {
public:
SyncMethodCommonTest() {}
~SyncMethodCommonTest() override {}
+
+ void SetUp() override {
+ BindingsTestBase::SetupSerializationBehavior(TypeParam::kSerializationMode);
+ }
};
class SyncMethodAssociatedTest : public SyncMethodTest {
@@ -386,16 +398,125 @@ TestSync::AsyncEchoCallback BindAsyncEchoCallback(Func func) {
return base::Bind(&CallAsyncEchoCallback<Func>, func);
}
+class SequencedTaskRunnerTestBase;
+
+void RunTestOnSequencedTaskRunner(
+ std::unique_ptr<SequencedTaskRunnerTestBase> test);
+
+class SequencedTaskRunnerTestBase {
+ public:
+ virtual ~SequencedTaskRunnerTestBase() = default;
+
+ void RunTest() {
+ SetUp();
+ Run();
+ }
+
+ virtual void Run() = 0;
+
+ virtual void SetUp() {}
+ virtual void TearDown() {}
+
+ protected:
+ void Done() {
+ TearDown();
+ task_runner_->PostTask(FROM_HERE, quit_closure_);
+ delete this;
+ }
+
+ base::Closure DoneClosure() {
+ return base::Bind(&SequencedTaskRunnerTestBase::Done,
+ base::Unretained(this));
+ }
+
+ private:
+ friend void RunTestOnSequencedTaskRunner(
+ std::unique_ptr<SequencedTaskRunnerTestBase> test);
+
+ void Init(const base::Closure& quit_closure) {
+ task_runner_ = base::SequencedTaskRunnerHandle::Get();
+ quit_closure_ = quit_closure;
+ }
+
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+ base::Closure quit_closure_;
+};
+
+// A helper class to launch tests on a SequencedTaskRunner. This is necessary
+// so gtest can instantiate copies for each |TypeParam|.
+template <typename TypeParam>
+class SequencedTaskRunnerTestLauncher : public testing::Test {
+ base::test::ScopedTaskEnvironment task_environment;
+};
+
+// Similar to SyncMethodCommonTest, but the test body runs on a
+// SequencedTaskRunner.
+template <typename TypeParam>
+class SyncMethodOnSequenceCommonTest : public SequencedTaskRunnerTestBase {
+ public:
+ void SetUp() override {
+ BindingsTestBase::SetupSerializationBehavior(TypeParam::kSerializationMode);
+ impl_ = std::make_unique<ImplTypeFor<typename TypeParam::Interface>>(
+ MakeRequest(&ptr_));
+ }
+
+ protected:
+ InterfacePtr<typename TypeParam::Interface> ptr_;
+ std::unique_ptr<ImplTypeFor<typename TypeParam::Interface>> impl_;
+};
+
+void RunTestOnSequencedTaskRunner(
+ std::unique_ptr<SequencedTaskRunnerTestBase> test) {
+ base::RunLoop run_loop;
+ test->Init(run_loop.QuitClosure());
+ base::CreateSequencedTaskRunnerWithTraits({base::WithBaseSyncPrimitives()})
+ ->PostTask(FROM_HERE, base::Bind(&SequencedTaskRunnerTestBase::RunTest,
+ base::Unretained(test.release())));
+ run_loop.Run();
+}
+
// TestSync (without associated interfaces) and TestSyncMaster (with associated
// interfaces) exercise MultiplexRouter with different configurations.
// Each test is run once with an InterfacePtr and once with a
// ThreadSafeInterfacePtr to ensure that they behave the same with respect to
-// sync calls.
-using InterfaceTypes = testing::Types<TestParams<TestSync, true>,
- TestParams<TestSync, false>,
- TestParams<TestSyncMaster, true>,
- TestParams<TestSyncMaster, false>>;
+// sync calls. Finally, all such combinations are tested in different message
+// serialization modes.
+using InterfaceTypes = testing::Types<
+ TestParams<TestSync,
+ true,
+ BindingsTestSerializationMode::kSerializeBeforeSend>,
+ TestParams<TestSync,
+ false,
+ BindingsTestSerializationMode::kSerializeBeforeSend>,
+ TestParams<TestSyncMaster,
+ true,
+ BindingsTestSerializationMode::kSerializeBeforeSend>,
+ TestParams<TestSyncMaster,
+ false,
+ BindingsTestSerializationMode::kSerializeBeforeSend>,
+ TestParams<TestSync,
+ true,
+ BindingsTestSerializationMode::kSerializeBeforeDispatch>,
+ TestParams<TestSync,
+ false,
+ BindingsTestSerializationMode::kSerializeBeforeDispatch>,
+ TestParams<TestSyncMaster,
+ true,
+ BindingsTestSerializationMode::kSerializeBeforeDispatch>,
+ TestParams<TestSyncMaster,
+ false,
+ BindingsTestSerializationMode::kSerializeBeforeDispatch>,
+ TestParams<TestSync, true, BindingsTestSerializationMode::kNeverSerialize>,
+ TestParams<TestSync, false, BindingsTestSerializationMode::kNeverSerialize>,
+ TestParams<TestSyncMaster,
+ true,
+ BindingsTestSerializationMode::kNeverSerialize>,
+ TestParams<TestSyncMaster,
+ false,
+ BindingsTestSerializationMode::kNeverSerialize>>;
+
TYPED_TEST_CASE(SyncMethodCommonTest, InterfaceTypes);
+TYPED_TEST_CASE(SequencedTaskRunnerTestLauncher, InterfaceTypes);
TYPED_TEST(SyncMethodCommonTest, CallSyncMethodAsynchronously) {
using Interface = typename TypeParam::Interface;
@@ -409,29 +530,65 @@ TYPED_TEST(SyncMethodCommonTest, CallSyncMethodAsynchronously) {
run_loop.Run();
}
+#define SEQUENCED_TASK_RUNNER_TYPED_TEST_NAME(fixture_name, name) \
+ fixture_name##name##_SequencedTaskRunnerTestSuffix
+
+#define SEQUENCED_TASK_RUNNER_TYPED_TEST(fixture_name, name) \
+ template <typename TypeParam> \
+ class SEQUENCED_TASK_RUNNER_TYPED_TEST_NAME(fixture_name, name) \
+ : public fixture_name<TypeParam> { \
+ void Run() override; \
+ }; \
+ TYPED_TEST(SequencedTaskRunnerTestLauncher, name) { \
+ RunTestOnSequencedTaskRunner( \
+ std::make_unique<SEQUENCED_TASK_RUNNER_TYPED_TEST_NAME( \
+ fixture_name, name) < TypeParam>> ()); \
+ } \
+ template <typename TypeParam> \
+ void SEQUENCED_TASK_RUNNER_TYPED_TEST_NAME(fixture_name, \
+ name)<TypeParam>::Run()
+
+#define SEQUENCED_TASK_RUNNER_TYPED_TEST_F(fixture_name, name) \
+ template <typename TypeParam> \
+ class SEQUENCED_TASK_RUNNER_TYPED_TEST_NAME(fixture_name, name); \
+ TYPED_TEST(SequencedTaskRunnerTestLauncher, name) { \
+ RunTestOnSequencedTaskRunner( \
+ std::make_unique<SEQUENCED_TASK_RUNNER_TYPED_TEST_NAME( \
+ fixture_name, name) < TypeParam>> ()); \
+ } \
+ template <typename TypeParam> \
+ class SEQUENCED_TASK_RUNNER_TYPED_TEST_NAME(fixture_name, name) \
+ : public fixture_name<TypeParam>
+
+SEQUENCED_TASK_RUNNER_TYPED_TEST(SyncMethodOnSequenceCommonTest,
+ CallSyncMethodAsynchronously) {
+ this->ptr_->Echo(
+ 123, base::Bind(&ExpectValueAndRunClosure, 123, this->DoneClosure()));
+}
+
TYPED_TEST(SyncMethodCommonTest, BasicSyncCalls) {
using Interface = typename TypeParam::Interface;
InterfacePtr<Interface> interface_ptr;
InterfaceRequest<Interface> request = MakeRequest(&interface_ptr);
auto ptr = TypeParam::Wrap(std::move(interface_ptr));
- TestSyncServiceThread<Interface> service_thread;
- service_thread.thread()->task_runner()->PostTask(
+ TestSyncServiceSequence<Interface> service_sequence;
+ service_sequence.task_runner()->PostTask(
FROM_HERE,
- base::Bind(&TestSyncServiceThread<Interface>::SetUp,
- base::Unretained(&service_thread), base::Passed(&request)));
+ base::Bind(&TestSyncServiceSequence<Interface>::SetUp,
+ base::Unretained(&service_sequence), base::Passed(&request)));
ASSERT_TRUE(ptr->Ping());
- ASSERT_TRUE(service_thread.ping_called());
+ ASSERT_TRUE(service_sequence.ping_called());
int32_t output_value = -1;
ASSERT_TRUE(ptr->Echo(42, &output_value));
ASSERT_EQ(42, output_value);
base::RunLoop run_loop;
- service_thread.thread()->task_runner()->PostTaskAndReply(
+ service_sequence.task_runner()->PostTaskAndReply(
FROM_HERE,
- base::Bind(&TestSyncServiceThread<Interface>::TearDown,
- base::Unretained(&service_thread)),
+ base::Bind(&TestSyncServiceSequence<Interface>::TearDown,
+ base::Unretained(&service_sequence)),
run_loop.QuitClosure());
run_loop.Run();
}
@@ -450,6 +607,17 @@ TYPED_TEST(SyncMethodCommonTest, ReenteredBySyncMethodBinding) {
EXPECT_EQ(42, output_value);
}
+SEQUENCED_TASK_RUNNER_TYPED_TEST(SyncMethodOnSequenceCommonTest,
+ ReenteredBySyncMethodBinding) {
+ // Test that an interface pointer waiting for a sync call response can be
+ // reentered by a binding serving sync methods on the same thread.
+
+ int32_t output_value = -1;
+ ASSERT_TRUE(this->ptr_->Echo(42, &output_value));
+ EXPECT_EQ(42, output_value);
+ this->Done();
+}
+
TYPED_TEST(SyncMethodCommonTest, InterfacePtrDestroyedDuringSyncCall) {
// Test that it won't result in crash or hang if an interface pointer is
// destroyed while it is waiting for a sync call response.
@@ -465,6 +633,20 @@ TYPED_TEST(SyncMethodCommonTest, InterfacePtrDestroyedDuringSyncCall) {
ASSERT_FALSE(ptr->Ping());
}
+SEQUENCED_TASK_RUNNER_TYPED_TEST(SyncMethodOnSequenceCommonTest,
+ InterfacePtrDestroyedDuringSyncCall) {
+ // Test that it won't result in crash or hang if an interface pointer is
+ // destroyed while it is waiting for a sync call response.
+
+ auto* ptr = &this->ptr_;
+ this->impl_->set_ping_handler([ptr](const TestSync::PingCallback& callback) {
+ ptr->reset();
+ callback.Run();
+ });
+ ASSERT_FALSE(this->ptr_->Ping());
+ this->Done();
+}
+
TYPED_TEST(SyncMethodCommonTest, BindingDestroyedDuringSyncCall) {
// Test that it won't result in crash or hang if a binding is
// closed (and therefore the message pipe handle is closed) while the
@@ -481,6 +663,22 @@ TYPED_TEST(SyncMethodCommonTest, BindingDestroyedDuringSyncCall) {
ASSERT_FALSE(ptr->Ping());
}
+SEQUENCED_TASK_RUNNER_TYPED_TEST(SyncMethodOnSequenceCommonTest,
+ BindingDestroyedDuringSyncCall) {
+ // Test that it won't result in crash or hang if a binding is
+ // closed (and therefore the message pipe handle is closed) while the
+ // corresponding interface pointer is waiting for a sync call response.
+
+ auto& impl = *this->impl_;
+ this->impl_->set_ping_handler(
+ [&impl](const TestSync::PingCallback& callback) {
+ impl.binding()->Close();
+ callback.Run();
+ });
+ ASSERT_FALSE(this->ptr_->Ping());
+ this->Done();
+}
+
TYPED_TEST(SyncMethodCommonTest, NestedSyncCallsWithInOrderResponses) {
// Test that we can call a sync method on an interface ptr, while there is
// already a sync call ongoing. The responses arrive in order.
@@ -509,6 +707,34 @@ TYPED_TEST(SyncMethodCommonTest, NestedSyncCallsWithInOrderResponses) {
EXPECT_EQ(123, result_value);
}
+SEQUENCED_TASK_RUNNER_TYPED_TEST(SyncMethodOnSequenceCommonTest,
+ NestedSyncCallsWithInOrderResponses) {
+ // Test that we can call a sync method on an interface ptr, while there is
+ // already a sync call ongoing. The responses arrive in order.
+
+ // The same variable is used to store the output of the two sync calls, in
+ // order to test that responses are handled in the correct order.
+ int32_t result_value = -1;
+
+ bool first_call = true;
+ auto& ptr = this->ptr_;
+ auto& impl = *this->impl_;
+ impl.set_echo_handler(
+ [&first_call, &ptr, &result_value](
+ int32_t value, const TestSync::EchoCallback& callback) {
+ if (first_call) {
+ first_call = false;
+ ASSERT_TRUE(ptr->Echo(456, &result_value));
+ EXPECT_EQ(456, result_value);
+ }
+ callback.Run(value);
+ });
+
+ ASSERT_TRUE(ptr->Echo(123, &result_value));
+ EXPECT_EQ(123, result_value);
+ this->Done();
+}
+
TYPED_TEST(SyncMethodCommonTest, NestedSyncCallsWithOutOfOrderResponses) {
// Test that we can call a sync method on an interface ptr, while there is
// already a sync call ongoing. The responses arrive out of order.
@@ -537,6 +763,34 @@ TYPED_TEST(SyncMethodCommonTest, NestedSyncCallsWithOutOfOrderResponses) {
EXPECT_EQ(123, result_value);
}
+SEQUENCED_TASK_RUNNER_TYPED_TEST(SyncMethodOnSequenceCommonTest,
+ NestedSyncCallsWithOutOfOrderResponses) {
+ // Test that we can call a sync method on an interface ptr, while there is
+ // already a sync call ongoing. The responses arrive out of order.
+
+ // The same variable is used to store the output of the two sync calls, in
+ // order to test that responses are handled in the correct order.
+ int32_t result_value = -1;
+
+ bool first_call = true;
+ auto& ptr = this->ptr_;
+ auto& impl = *this->impl_;
+ impl.set_echo_handler(
+ [&first_call, &ptr, &result_value](
+ int32_t value, const TestSync::EchoCallback& callback) {
+ callback.Run(value);
+ if (first_call) {
+ first_call = false;
+ ASSERT_TRUE(ptr->Echo(456, &result_value));
+ EXPECT_EQ(456, result_value);
+ }
+ });
+
+ ASSERT_TRUE(ptr->Echo(123, &result_value));
+ EXPECT_EQ(123, result_value);
+ this->Done();
+}
+
TYPED_TEST(SyncMethodCommonTest, AsyncResponseQueuedDuringSyncCall) {
// Test that while an interface pointer is waiting for the response to a sync
// call, async responses are queued until the sync call completes.
@@ -594,6 +848,52 @@ TYPED_TEST(SyncMethodCommonTest, AsyncResponseQueuedDuringSyncCall) {
EXPECT_TRUE(async_echo_response_dispatched);
}
+SEQUENCED_TASK_RUNNER_TYPED_TEST_F(SyncMethodOnSequenceCommonTest,
+ AsyncResponseQueuedDuringSyncCall) {
+ // Test that while an interface pointer is waiting for the response to a sync
+ // call, async responses are queued until the sync call completes.
+
+ void Run() override {
+ this->impl_->set_async_echo_handler(
+ [this](int32_t value, const TestSync::AsyncEchoCallback& callback) {
+ async_echo_request_value_ = value;
+ async_echo_request_callback_ = callback;
+ OnAsyncEchoReceived();
+ });
+
+ this->ptr_->AsyncEcho(123, BindAsyncEchoCallback([this](int32_t result) {
+ async_echo_response_dispatched_ = true;
+ EXPECT_EQ(123, result);
+ EXPECT_TRUE(async_echo_response_dispatched_);
+ this->Done();
+ }));
+ }
+
+ // Called when the AsyncEcho request reaches the service side.
+ void OnAsyncEchoReceived() {
+ this->impl_->set_echo_handler(
+ [this](int32_t value, const TestSync::EchoCallback& callback) {
+ // Send back the async response first.
+ EXPECT_FALSE(async_echo_request_callback_.is_null());
+ async_echo_request_callback_.Run(async_echo_request_value_);
+
+ callback.Run(value);
+ });
+
+ int32_t result_value = -1;
+ ASSERT_TRUE(this->ptr_->Echo(456, &result_value));
+ EXPECT_EQ(456, result_value);
+
+ // Although the AsyncEcho response arrives before the Echo response, it
+ // should be queued and not yet dispatched.
+ EXPECT_FALSE(async_echo_response_dispatched_);
+ }
+
+ int32_t async_echo_request_value_ = -1;
+ TestSync::AsyncEchoCallback async_echo_request_callback_;
+ bool async_echo_response_dispatched_ = false;
+};
+
TYPED_TEST(SyncMethodCommonTest, AsyncRequestQueuedDuringSyncCall) {
// Test that while an interface pointer is waiting for the response to a sync
// call, async requests for a binding running on the same thread are queued
@@ -645,6 +945,44 @@ TYPED_TEST(SyncMethodCommonTest, AsyncRequestQueuedDuringSyncCall) {
EXPECT_TRUE(async_echo_response_dispatched);
}
+SEQUENCED_TASK_RUNNER_TYPED_TEST_F(SyncMethodOnSequenceCommonTest,
+ AsyncRequestQueuedDuringSyncCall) {
+ // Test that while an interface pointer is waiting for the response to a sync
+ // call, async requests for a binding running on the same thread are queued
+ // until the sync call completes.
+ void Run() override {
+ this->impl_->set_async_echo_handler(
+ [this](int32_t value, const TestSync::AsyncEchoCallback& callback) {
+ async_echo_request_dispatched_ = true;
+ callback.Run(value);
+ });
+
+ this->ptr_->AsyncEcho(123, BindAsyncEchoCallback([this](int32_t result) {
+ EXPECT_EQ(123, result);
+ this->Done();
+ }));
+
+ this->impl_->set_echo_handler(
+ [this](int32_t value, const TestSync::EchoCallback& callback) {
+ // Although the AsyncEcho request is sent before the Echo request, it
+ // shouldn't be dispatched yet at this point, because there is an
+ // ongoing
+ // sync call on the same thread.
+ EXPECT_FALSE(async_echo_request_dispatched_);
+ callback.Run(value);
+ });
+
+ int32_t result_value = -1;
+ ASSERT_TRUE(this->ptr_->Echo(456, &result_value));
+ EXPECT_EQ(456, result_value);
+
+ // Although the AsyncEcho request is sent before the Echo request, it
+ // shouldn't be dispatched yet.
+ EXPECT_FALSE(async_echo_request_dispatched_);
+ }
+ bool async_echo_request_dispatched_ = false;
+};
+
TYPED_TEST(SyncMethodCommonTest,
QueuedMessagesProcessedBeforeErrorNotification) {
// Test that while an interface pointer is waiting for the response to a sync
@@ -675,19 +1013,17 @@ TYPED_TEST(SyncMethodCommonTest,
bool async_echo_response_dispatched = false;
bool connection_error_dispatched = false;
base::RunLoop run_loop2;
- ptr->AsyncEcho(
- 123,
- BindAsyncEchoCallback(
- [&async_echo_response_dispatched, &connection_error_dispatched, &ptr,
- &run_loop2](int32_t result) {
- async_echo_response_dispatched = true;
- // At this point, error notification should not be dispatched
- // yet.
- EXPECT_FALSE(connection_error_dispatched);
- EXPECT_FALSE(ptr.encountered_error());
- EXPECT_EQ(123, result);
- run_loop2.Quit();
- }));
+ ptr->AsyncEcho(123, BindAsyncEchoCallback([&async_echo_response_dispatched,
+ &connection_error_dispatched, &ptr,
+ &run_loop2](int32_t result) {
+ async_echo_response_dispatched = true;
+ // At this point, error notification should not be dispatched
+ // yet.
+ EXPECT_FALSE(connection_error_dispatched);
+ EXPECT_FALSE(ptr.encountered_error());
+ EXPECT_EQ(123, result);
+ run_loop2.Quit();
+ }));
// Run until the AsyncEcho request reaches the service side.
run_loop1.Run();
@@ -702,9 +1038,9 @@ TYPED_TEST(SyncMethodCommonTest,
});
base::RunLoop run_loop3;
- ptr.set_connection_error_handler(
- base::Bind(&SetFlagAndRunClosure, &connection_error_dispatched,
- run_loop3.QuitClosure()));
+ ptr.set_connection_error_handler(base::Bind(&SetFlagAndRunClosure,
+ &connection_error_dispatched,
+ run_loop3.QuitClosure()));
int32_t result_value = -1;
ASSERT_FALSE(ptr->Echo(456, &result_value));
@@ -728,6 +1064,74 @@ TYPED_TEST(SyncMethodCommonTest,
EXPECT_TRUE(ptr.encountered_error());
}
+SEQUENCED_TASK_RUNNER_TYPED_TEST_F(
+ SyncMethodOnSequenceCommonTest,
+ QueuedMessagesProcessedBeforeErrorNotification) {
+ // Test that while an interface pointer is waiting for the response to a sync
+ // call, async responses are queued. If the message pipe is disconnected
+ // before the queued messages are processed, the connection error
+ // notification is delayed until all the queued messages are processed.
+
+ void Run() override {
+ this->impl_->set_async_echo_handler(
+ [this](int32_t value, const TestSync::AsyncEchoCallback& callback) {
+ OnAsyncEchoReachedService(value, callback);
+ });
+
+ this->ptr_->AsyncEcho(123, BindAsyncEchoCallback([this](int32_t result) {
+ async_echo_response_dispatched_ = true;
+ // At this point, error notification should not be
+ // dispatched
+ // yet.
+ EXPECT_FALSE(connection_error_dispatched_);
+ EXPECT_FALSE(this->ptr_.encountered_error());
+ EXPECT_EQ(123, result);
+ EXPECT_TRUE(async_echo_response_dispatched_);
+ }));
+ }
+
+ void OnAsyncEchoReachedService(int32_t value,
+ const TestSync::AsyncEchoCallback& callback) {
+ async_echo_request_value_ = value;
+ async_echo_request_callback_ = callback;
+ this->impl_->set_echo_handler(
+ [this](int32_t value, const TestSync::EchoCallback& callback) {
+ // Send back the async response first.
+ EXPECT_FALSE(async_echo_request_callback_.is_null());
+ async_echo_request_callback_.Run(async_echo_request_value_);
+
+ this->impl_->binding()->Close();
+ });
+
+ this->ptr_.set_connection_error_handler(
+ base::Bind(&SetFlagAndRunClosure, &connection_error_dispatched_,
+ LambdaBinder<>::BindLambda(
+ [this]() { OnErrorNotificationDispatched(); })));
+
+ int32_t result_value = -1;
+ ASSERT_FALSE(this->ptr_->Echo(456, &result_value));
+ EXPECT_EQ(-1, result_value);
+ ASSERT_FALSE(connection_error_dispatched_);
+ EXPECT_FALSE(this->ptr_.encountered_error());
+
+ // Although the AsyncEcho response arrives before the Echo response, it
+ // should
+ // be queued and not yet dispatched.
+ EXPECT_FALSE(async_echo_response_dispatched_);
+ }
+
+ void OnErrorNotificationDispatched() {
+ ASSERT_TRUE(connection_error_dispatched_);
+ EXPECT_TRUE(this->ptr_.encountered_error());
+ this->Done();
+ }
+
+ int32_t async_echo_request_value_ = -1;
+ TestSync::AsyncEchoCallback async_echo_request_callback_;
+ bool async_echo_response_dispatched_ = false;
+ bool connection_error_dispatched_ = false;
+};
+
TYPED_TEST(SyncMethodCommonTest, InvalidMessageDuringSyncCall) {
// Test that while an interface pointer is waiting for the response to a sync
// call, an invalid incoming message will disconnect the message pipe, cause
@@ -742,7 +1146,8 @@ TYPED_TEST(SyncMethodCommonTest, InvalidMessageDuringSyncCall) {
auto ptr = TypeParam::Wrap(std::move(interface_ptr));
MessagePipeHandle raw_binding_handle = pipe.handle1.get();
- ImplTypeFor<Interface> impl(MakeRequest<Interface>(std::move(pipe.handle1)));
+ ImplTypeFor<Interface> impl(
+ InterfaceRequest<Interface>(std::move(pipe.handle1)));
impl.set_echo_handler([&raw_binding_handle](
int32_t value, const TestSync::EchoCallback& callback) {
@@ -775,6 +1180,50 @@ TYPED_TEST(SyncMethodCommonTest, InvalidMessageDuringSyncCall) {
}
}
+SEQUENCED_TASK_RUNNER_TYPED_TEST_F(SyncMethodOnSequenceCommonTest,
+ InvalidMessageDuringSyncCall) {
+ // Test that while an interface pointer is waiting for the response to a sync
+ // call, an invalid incoming message will disconnect the message pipe, cause
+ // the sync call to return false, and run the connection error handler
+ // asynchronously.
+
+ void Run() override {
+ MessagePipe pipe;
+
+ using InterfaceType = typename TypeParam::Interface;
+ this->ptr_.Bind(
+ InterfacePtrInfo<InterfaceType>(std::move(pipe.handle0), 0u));
+
+ MessagePipeHandle raw_binding_handle = pipe.handle1.get();
+ this->impl_ = std::make_unique<ImplTypeFor<InterfaceType>>(
+ InterfaceRequest<InterfaceType>(std::move(pipe.handle1)));
+
+ this->impl_->set_echo_handler(
+ [raw_binding_handle](int32_t value,
+ const TestSync::EchoCallback& callback) {
+ // Write a 1-byte message, which is considered invalid.
+ char invalid_message = 0;
+ MojoResult result =
+ WriteMessageRaw(raw_binding_handle, &invalid_message, 1u, nullptr,
+ 0u, MOJO_WRITE_MESSAGE_FLAG_NONE);
+ ASSERT_EQ(MOJO_RESULT_OK, result);
+ callback.Run(value);
+ });
+
+ this->ptr_.set_connection_error_handler(
+ LambdaBinder<>::BindLambda([this]() {
+ connection_error_dispatched_ = true;
+ this->Done();
+ }));
+
+ int32_t result_value = -1;
+ ASSERT_FALSE(this->ptr_->Echo(456, &result_value));
+ EXPECT_EQ(-1, result_value);
+ ASSERT_FALSE(connection_error_dispatched_);
+ }
+ bool connection_error_dispatched_ = false;
+};
+
TEST_F(SyncMethodAssociatedTest, ReenteredBySyncMethodAssoBindingOfSameRouter) {
// Test that an interface pointer waiting for a sync call response can be
// reentered by an associated binding serving sync methods on the same thread.
diff --git a/mojo/public/cpp/bindings/tests/test_helpers_unittest.cc b/mojo/public/cpp/bindings/tests/test_helpers_unittest.cc
new file mode 100644
index 0000000000..4595dea4ed
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/test_helpers_unittest.cc
@@ -0,0 +1,128 @@
+// 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/macros.h"
+#include "base/test/scoped_task_environment.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "mojo/public/cpp/system/wait.h"
+#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+class TestHelperTest : public testing::Test {
+ public:
+ TestHelperTest() = default;
+ ~TestHelperTest() override = default;
+
+ private:
+ base::test::ScopedTaskEnvironment task_environment_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestHelperTest);
+};
+
+class PingImpl : public test::PingService {
+ public:
+ explicit PingImpl(test::PingServiceRequest request)
+ : binding_(this, std::move(request)) {}
+ ~PingImpl() override = default;
+
+ bool pinged() const { return pinged_; }
+
+ // test::PingService:
+ void Ping(const PingCallback& callback) override {
+ pinged_ = true;
+ callback.Run();
+ }
+
+ private:
+ bool pinged_ = false;
+ Binding<test::PingService> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(PingImpl);
+};
+
+class EchoImpl : public test::EchoService {
+ public:
+ explicit EchoImpl(test::EchoServiceRequest request)
+ : binding_(this, std::move(request)) {}
+ ~EchoImpl() override = default;
+
+ // test::EchoService:
+ void Echo(const std::string& message, const EchoCallback& callback) override {
+ callback.Run(message);
+ }
+
+ private:
+ Binding<test::EchoService> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(EchoImpl);
+};
+
+class TrampolineImpl : public test::HandleTrampoline {
+ public:
+ explicit TrampolineImpl(test::HandleTrampolineRequest request)
+ : binding_(this, std::move(request)) {}
+ ~TrampolineImpl() override = default;
+
+ // test::HandleTrampoline:
+ void BounceOne(ScopedMessagePipeHandle one,
+ const BounceOneCallback& callback) override {
+ callback.Run(std::move(one));
+ }
+
+ void BounceTwo(ScopedMessagePipeHandle one,
+ ScopedMessagePipeHandle two,
+ const BounceTwoCallback& callback) override {
+ callback.Run(std::move(one), std::move(two));
+ }
+
+ private:
+ Binding<test::HandleTrampoline> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrampolineImpl);
+};
+
+TEST_F(TestHelperTest, AsyncWaiter) {
+ test::PingServicePtr ping;
+ PingImpl ping_impl(MakeRequest(&ping));
+
+ test::PingServiceAsyncWaiter wait_for_ping(ping.get());
+ EXPECT_FALSE(ping_impl.pinged());
+ wait_for_ping.Ping();
+ EXPECT_TRUE(ping_impl.pinged());
+
+ test::EchoServicePtr echo;
+ EchoImpl echo_impl(MakeRequest(&echo));
+
+ test::EchoServiceAsyncWaiter wait_for_echo(echo.get());
+ const std::string kTestString = "a machine that goes 'ping'";
+ std::string response;
+ wait_for_echo.Echo(kTestString, &response);
+ EXPECT_EQ(kTestString, response);
+
+ test::HandleTrampolinePtr trampoline;
+ TrampolineImpl trampoline_impl(MakeRequest(&trampoline));
+
+ test::HandleTrampolineAsyncWaiter wait_for_trampoline(trampoline.get());
+ MessagePipe pipe;
+ ScopedMessagePipeHandle handle0, handle1;
+ WriteMessageRaw(pipe.handle0.get(), kTestString.data(), kTestString.size(),
+ nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE);
+ wait_for_trampoline.BounceOne(std::move(pipe.handle0), &handle0);
+ wait_for_trampoline.BounceTwo(std::move(handle0), std::move(pipe.handle1),
+ &handle0, &handle1);
+
+ // Verify that our pipe handles are the same as the original pipe.
+ Wait(handle1.get(), MOJO_HANDLE_SIGNAL_READABLE);
+ std::vector<uint8_t> payload;
+ ReadMessageRaw(handle1.get(), &payload, nullptr, MOJO_READ_MESSAGE_FLAG_NONE);
+ std::string original_message(payload.begin(), payload.end());
+ EXPECT_EQ(kTestString, original_message);
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/bindings/tests/test_native_types.cc b/mojo/public/cpp/bindings/tests/test_native_types.cc
new file mode 100644
index 0000000000..b11cc23172
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/test_native_types.cc
@@ -0,0 +1,99 @@
+// 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/tests/test_native_types.h"
+
+#include "base/macros.h"
+#include "ipc/ipc_mojo_message_helper.h"
+
+namespace mojo {
+namespace test {
+
+TestNativeStruct::TestNativeStruct() = default;
+
+TestNativeStruct::TestNativeStruct(const std::string& message, int x, int y)
+ : message_(message), x_(x), y_(y) {}
+
+TestNativeStruct::~TestNativeStruct() = default;
+
+TestNativeStructWithAttachments::TestNativeStructWithAttachments() = default;
+
+TestNativeStructWithAttachments::TestNativeStructWithAttachments(
+ TestNativeStructWithAttachments&& other) = default;
+
+TestNativeStructWithAttachments::TestNativeStructWithAttachments(
+ const std::string& message,
+ mojo::ScopedMessagePipeHandle pipe)
+ : message_(message), pipe_(std::move(pipe)) {}
+
+TestNativeStructWithAttachments::~TestNativeStructWithAttachments() = default;
+
+TestNativeStructWithAttachments& TestNativeStructWithAttachments::operator=(
+ TestNativeStructWithAttachments&& other) = default;
+
+} // namespace test
+} // namespace mojo
+
+namespace IPC {
+
+// static
+void ParamTraits<mojo::test::TestNativeStruct>::Write(base::Pickle* m,
+ const param_type& p) {
+ m->WriteString(p.message());
+ m->WriteInt(p.x());
+ m->WriteInt(p.y());
+}
+
+// static
+bool ParamTraits<mojo::test::TestNativeStruct>::Read(const base::Pickle* m,
+ base::PickleIterator* iter,
+ param_type* r) {
+ std::string message;
+ if (!iter->ReadString(&message))
+ return false;
+ int x, y;
+ if (!iter->ReadInt(&x) || !iter->ReadInt(&y))
+ return false;
+ r->set_message(message);
+ r->set_x(x);
+ r->set_y(y);
+ return true;
+}
+
+// static
+void ParamTraits<mojo::test::TestNativeStruct>::Log(const param_type& p,
+ std::string* l) {}
+
+// static
+void ParamTraits<mojo::test::TestNativeStructWithAttachments>::Write(
+ Message* m,
+ const param_type& p) {
+ m->WriteString(p.message());
+ IPC::MojoMessageHelper::WriteMessagePipeTo(m, p.PassPipe());
+}
+
+// static
+bool ParamTraits<mojo::test::TestNativeStructWithAttachments>::Read(
+ const Message* m,
+ base::PickleIterator* iter,
+ param_type* r) {
+ std::string message;
+ if (!iter->ReadString(&message))
+ return false;
+ r->set_message(message);
+
+ mojo::ScopedMessagePipeHandle pipe;
+ if (!IPC::MojoMessageHelper::ReadMessagePipeFrom(m, iter, &pipe))
+ return false;
+
+ r->set_pipe(std::move(pipe));
+ return true;
+}
+
+// static
+void ParamTraits<mojo::test::TestNativeStructWithAttachments>::Log(
+ const param_type& p,
+ std::string* l) {}
+
+} // namespace IPC
diff --git a/mojo/public/cpp/bindings/tests/test_native_types.h b/mojo/public/cpp/bindings/tests/test_native_types.h
new file mode 100644
index 0000000000..9ef2f902b5
--- /dev/null
+++ b/mojo/public/cpp/bindings/tests/test_native_types.h
@@ -0,0 +1,89 @@
+// 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_TESTS_BINDINGS_TEST_NATIVE_TYPES_H_
+#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_BINDINGS_TEST_NATIVE_TYPES_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_param_traits.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace mojo {
+namespace test {
+
+class TestNativeStruct {
+ public:
+ TestNativeStruct();
+ TestNativeStruct(const std::string& message, int x, int y);
+ ~TestNativeStruct();
+
+ const std::string& message() const { return message_; }
+ void set_message(const std::string& message) { message_ = message; }
+
+ int x() const { return x_; }
+ void set_x(int x) { x_ = x; }
+
+ int y() const { return y_; }
+ void set_y(int y) { y_ = y; }
+
+ private:
+ std::string message_;
+ int x_, y_;
+};
+
+class TestNativeStructWithAttachments {
+ public:
+ TestNativeStructWithAttachments();
+ TestNativeStructWithAttachments(TestNativeStructWithAttachments&& other);
+ TestNativeStructWithAttachments(const std::string& message,
+ ScopedMessagePipeHandle pipe);
+ ~TestNativeStructWithAttachments();
+
+ TestNativeStructWithAttachments& operator=(
+ TestNativeStructWithAttachments&& other);
+
+ const std::string& message() const { return message_; }
+ void set_message(const std::string& message) { message_ = message; }
+
+ void set_pipe(mojo::ScopedMessagePipeHandle pipe) { pipe_ = std::move(pipe); }
+ mojo::ScopedMessagePipeHandle PassPipe() const { return std::move(pipe_); }
+
+ private:
+ std::string message_;
+ mutable mojo::ScopedMessagePipeHandle pipe_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestNativeStructWithAttachments);
+};
+
+} // namespace test
+} // namespace mojo
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mojo::test::TestNativeStruct> {
+ using param_type = mojo::test::TestNativeStruct;
+
+ 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<mojo::test::TestNativeStructWithAttachments> {
+ using param_type = mojo::test::TestNativeStructWithAttachments;
+
+ static void Write(Message* m, const param_type& p);
+ static bool Read(const Message* m, base::PickleIterator* iter, param_type* r);
+ static void Log(const param_type& p, std::string* l);
+};
+
+} // namespace IPC
+
+#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_BINDINGS_TEST_NATIVE_TYPES_H_
diff --git a/mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap b/mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap
index 50e8076a50..da99a1a8d7 100644
--- a/mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap
+++ b/mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap
@@ -3,9 +3,13 @@
# found in the LICENSE file.
mojom = "//mojo/public/interfaces/bindings/tests/test_native_types.mojom"
-public_headers = [ "//mojo/public/cpp/bindings/tests/pickled_types_chromium.h" ]
+public_headers = [
+ "//mojo/public/cpp/bindings/tests/pickled_types_chromium.h",
+ "//mojo/public/cpp/bindings/tests/test_native_types.h",
+]
sources = [
"//mojo/public/cpp/bindings/tests/pickled_types_chromium.cc",
+ "//mojo/public/cpp/bindings/tests/test_native_types.cc",
]
deps = [
"//ipc",
@@ -14,4 +18,6 @@ deps = [
type_mappings = [
"mojo.test.PickledEnum=mojo::test::PickledEnumChromium",
"mojo.test.PickledStruct=mojo::test::PickledStructChromium[move_only]",
+ "mojo.test.TestNativeStructMojom=mojo::test::TestNativeStruct",
+ "mojo.test.TestNativeStructWithAttachmentsMojom=mojo::test::TestNativeStructWithAttachments[move_only]",
]
diff --git a/mojo/public/cpp/bindings/tests/union_unittest.cc b/mojo/public/cpp/bindings/tests/union_unittest.cc
index bdf27dfff3..a45d17d7c5 100644
--- a/mojo/public/cpp/bindings/tests/union_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/union_unittest.cc
@@ -7,6 +7,7 @@
#include <utility>
#include <vector>
+#include "base/containers/flat_map.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "mojo/public/cpp/bindings/binding.h"
@@ -15,6 +16,7 @@
#include "mojo/public/cpp/bindings/lib/serialization.h"
#include "mojo/public/cpp/bindings/lib/validation_context.h"
#include "mojo/public/cpp/bindings/lib/validation_errors.h"
+#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h"
#include "mojo/public/interfaces/bindings/tests/test_unions.mojom.h"
@@ -23,6 +25,55 @@
namespace mojo {
namespace test {
+template <typename InputType, typename DataType>
+size_t SerializeStruct(InputType& input,
+ mojo::Message* message,
+ mojo::internal::SerializationContext* context,
+ DataType** out_data) {
+ using StructType = typename InputType::Struct;
+ using DataViewType = typename StructType::DataView;
+ *message = mojo::Message(0, 0, 0, 0, nullptr);
+ const size_t payload_start = message->payload_buffer()->cursor();
+ typename DataType::BufferWriter writer;
+ mojo::internal::Serialize<DataViewType>(input, message->payload_buffer(),
+ &writer, context);
+ *out_data = writer.is_null() ? nullptr : writer.data();
+ return message->payload_buffer()->cursor() - payload_start;
+}
+
+template <typename InputType, typename DataType>
+size_t SerializeUnion(InputType& input,
+ mojo::Message* message,
+ mojo::internal::SerializationContext* context,
+ DataType** out_data = nullptr) {
+ using StructType = typename InputType::Struct;
+ using DataViewType = typename StructType::DataView;
+ *message = mojo::Message(0, 0, 0, 0, nullptr);
+ const size_t payload_start = message->payload_buffer()->cursor();
+ typename DataType::BufferWriter writer;
+ mojo::internal::Serialize<DataViewType>(input, message->payload_buffer(),
+ &writer, false, context);
+ *out_data = writer.is_null() ? nullptr : writer.data();
+ return message->payload_buffer()->cursor() - payload_start;
+}
+
+template <typename DataViewType, typename InputType>
+size_t SerializeArray(InputType& input,
+ bool nullable_elements,
+ mojo::Message* message,
+ mojo::internal::SerializationContext* context,
+ typename DataViewType::Data_** out_data) {
+ *message = mojo::Message(0, 0, 0, 0, nullptr);
+ const size_t payload_start = message->payload_buffer()->cursor();
+ typename DataViewType::Data_::BufferWriter writer;
+ mojo::internal::ContainerValidateParams validate_params(0, nullable_elements,
+ nullptr);
+ mojo::internal::Serialize<DataViewType>(input, message->payload_buffer(),
+ &writer, &validate_params, context);
+ *out_data = writer.is_null() ? nullptr : writer.data();
+ return message->payload_buffer()->cursor() - payload_start;
+}
+
TEST(UnionTest, PlainOldDataGetterSetter) {
PodUnionPtr pod(PodUnion::New());
@@ -53,8 +104,8 @@ TEST(UnionTest, PlainOldDataGetterSetter) {
EXPECT_TRUE(pod->is_f_int32());
EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT32);
- pod->set_f_uint32(static_cast<uint32_t>(15));
- EXPECT_EQ(static_cast<uint32_t>(15), pod->get_f_uint32());
+ pod->set_f_uint32(uint32_t{15});
+ EXPECT_EQ(uint32_t{15}, pod->get_f_uint32());
EXPECT_TRUE(pod->is_f_uint32());
EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT32);
@@ -63,8 +114,8 @@ TEST(UnionTest, PlainOldDataGetterSetter) {
EXPECT_TRUE(pod->is_f_int64());
EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT64);
- pod->set_f_uint64(static_cast<uint64_t>(17));
- EXPECT_EQ(static_cast<uint64_t>(17), pod->get_f_uint64());
+ pod->set_f_uint64(uint64_t{17});
+ EXPECT_EQ(uint64_t{17}, pod->get_f_uint64());
EXPECT_TRUE(pod->is_f_uint64());
EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT64);
@@ -91,6 +142,70 @@ TEST(UnionTest, PlainOldDataGetterSetter) {
EXPECT_EQ(pod->which(), PodUnion::Tag::F_ENUM);
}
+TEST(UnionTest, PlainOldDataFactoryFunction) {
+ PodUnionPtr pod = PodUnion::NewFInt8(11);
+ EXPECT_EQ(11, pod->get_f_int8());
+ EXPECT_TRUE(pod->is_f_int8());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT8);
+
+ pod = PodUnion::NewFInt16(12);
+ EXPECT_EQ(12, pod->get_f_int16());
+ EXPECT_TRUE(pod->is_f_int16());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT16);
+
+ pod = PodUnion::NewFUint16(13);
+ EXPECT_EQ(13, pod->get_f_uint16());
+ EXPECT_TRUE(pod->is_f_uint16());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT16);
+
+ pod = PodUnion::NewFInt32(14);
+ EXPECT_EQ(14, pod->get_f_int32());
+ EXPECT_TRUE(pod->is_f_int32());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT32);
+
+ pod = PodUnion::NewFUint32(15);
+ EXPECT_EQ(uint32_t{15}, pod->get_f_uint32());
+ EXPECT_TRUE(pod->is_f_uint32());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT32);
+
+ pod = PodUnion::NewFInt64(16);
+ EXPECT_EQ(16, pod->get_f_int64());
+ EXPECT_TRUE(pod->is_f_int64());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT64);
+
+ pod = PodUnion::NewFUint64(17);
+ EXPECT_EQ(uint64_t{17}, pod->get_f_uint64());
+ EXPECT_TRUE(pod->is_f_uint64());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT64);
+
+ pod = PodUnion::NewFFloat(1.5);
+ EXPECT_EQ(1.5, pod->get_f_float());
+ EXPECT_TRUE(pod->is_f_float());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_FLOAT);
+
+ pod = PodUnion::NewFDouble(1.9);
+ EXPECT_EQ(1.9, pod->get_f_double());
+ EXPECT_TRUE(pod->is_f_double());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_DOUBLE);
+
+ pod = PodUnion::NewFBool(true);
+ EXPECT_TRUE(pod->get_f_bool());
+ pod = PodUnion::NewFBool(false);
+ EXPECT_FALSE(pod->get_f_bool());
+ EXPECT_TRUE(pod->is_f_bool());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_BOOL);
+
+ pod = PodUnion::NewFEnum(AnEnum::SECOND);
+ EXPECT_EQ(AnEnum::SECOND, pod->get_f_enum());
+ EXPECT_TRUE(pod->is_f_enum());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_ENUM);
+
+ pod = PodUnion::NewFEnum(AnEnum::FIRST);
+ EXPECT_EQ(AnEnum::FIRST, pod->get_f_enum());
+ EXPECT_TRUE(pod->is_f_enum());
+ EXPECT_EQ(pod->which(), PodUnion::Tag::F_ENUM);
+}
+
TEST(UnionTest, PodEquals) {
PodUnionPtr pod1(PodUnion::New());
PodUnionPtr pod2(PodUnion::New());
@@ -120,15 +235,10 @@ TEST(UnionTest, PodSerialization) {
PodUnionPtr pod1(PodUnion::New());
pod1->set_f_int8(10);
+ mojo::Message message;
mojo::internal::SerializationContext context;
- size_t size = mojo::internal::PrepareToSerialize<PodUnionDataView>(
- pod1, false, &context);
- EXPECT_EQ(16U, size);
-
- mojo::internal::FixedBufferForTesting buf(size);
internal::PodUnion_Data* data = nullptr;
- mojo::internal::Serialize<PodUnionDataView>(pod1, &buf, &data, false,
- &context);
+ EXPECT_EQ(16U, SerializeUnion(pod1, &message, &context, &data));
PodUnionPtr pod2;
mojo::internal::Deserialize<PodUnionDataView>(data, &pod2, &context);
@@ -139,17 +249,12 @@ TEST(UnionTest, PodSerialization) {
}
TEST(UnionTest, EnumSerialization) {
- PodUnionPtr pod1(PodUnion::New());
- pod1->set_f_enum(AnEnum::SECOND);
+ PodUnionPtr pod1(PodUnion::NewFEnum(AnEnum::SECOND));
- size_t size = mojo::internal::PrepareToSerialize<PodUnionDataView>(
- pod1, false, nullptr);
- EXPECT_EQ(16U, size);
-
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::PodUnion_Data* data = nullptr;
- mojo::internal::Serialize<PodUnionDataView>(pod1, &buf, &data, false,
- nullptr);
+ EXPECT_EQ(16U, SerializeUnion(pod1, &message, &context, &data));
PodUnionPtr pod2;
mojo::internal::Deserialize<PodUnionDataView>(data, &pod2, nullptr);
@@ -163,62 +268,52 @@ TEST(UnionTest, PodValidation) {
PodUnionPtr pod(PodUnion::New());
pod->set_f_int8(10);
- size_t size =
- mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr);
- EXPECT_EQ(16U, size);
-
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::PodUnion_Data* data = nullptr;
- mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr);
+ const size_t size = SerializeUnion(pod, &message, &context, &data);
+ EXPECT_EQ(16U, size);
- void* raw_buf = buf.Leak();
mojo::internal::ValidationContext validation_context(
data, static_cast<uint32_t>(size), 0, 0);
EXPECT_TRUE(
- internal::PodUnion_Data::Validate(raw_buf, &validation_context, false));
- free(raw_buf);
+ internal::PodUnion_Data::Validate(data, &validation_context, false));
}
TEST(UnionTest, SerializeNotNull) {
PodUnionPtr pod(PodUnion::New());
pod->set_f_int8(0);
- size_t size =
- mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr);
- mojo::internal::FixedBufferForTesting buf(size);
+
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::PodUnion_Data* data = nullptr;
- mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr);
+ SerializeUnion(pod, &message, &context, &data);
EXPECT_FALSE(data->is_null());
}
TEST(UnionTest, SerializeIsNullInlined) {
PodUnionPtr pod;
- size_t size =
- mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr);
- EXPECT_EQ(16U, size);
- mojo::internal::FixedBufferForTesting buf(size);
- internal::PodUnion_Data* data = internal::PodUnion_Data::New(&buf);
-
- // Check that dirty output buffers are handled correctly by serialization.
- data->size = 16U;
- data->tag = PodUnion::Tag::F_UINT16;
- data->data.f_f_int16 = 20;
- mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, true, nullptr);
- EXPECT_TRUE(data->is_null());
+ mojo::internal::FixedBufferForTesting buffer(16);
+ internal::PodUnion_Data::BufferWriter writer;
+ writer.Allocate(&buffer);
+ mojo::internal::SerializationContext context;
+ mojo::internal::Serialize<PodUnionDataView>(pod, &buffer, &writer, true,
+ &context);
+ EXPECT_TRUE(writer.data()->is_null());
+ EXPECT_EQ(16U, buffer.cursor());
PodUnionPtr pod2;
- mojo::internal::Deserialize<PodUnionDataView>(data, &pod2, nullptr);
+ mojo::internal::Deserialize<PodUnionDataView>(writer.data(), &pod2, nullptr);
EXPECT_TRUE(pod2.is_null());
}
TEST(UnionTest, SerializeIsNullNotInlined) {
PodUnionPtr pod;
- size_t size =
- mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr);
- EXPECT_EQ(16U, size);
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::PodUnion_Data* data = nullptr;
- mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr);
+ EXPECT_EQ(0u, SerializeUnion(pod, &message, &context, &data));
EXPECT_EQ(nullptr, data);
}
@@ -229,85 +324,58 @@ TEST(UnionTest, NullValidation) {
buf, &validation_context, false));
}
-TEST(UnionTest, OutOfAlignmentValidation) {
- size_t size = sizeof(internal::PodUnion_Data);
- // Get an aligned object and shift the alignment.
- mojo::internal::FixedBufferForTesting aligned_buf(size + 1);
- void* raw_buf = aligned_buf.Leak();
- char* buf = reinterpret_cast<char*>(raw_buf) + 1;
-
- internal::PodUnion_Data* data =
- reinterpret_cast<internal::PodUnion_Data*>(buf);
- mojo::internal::ValidationContext validation_context(
- data, static_cast<uint32_t>(size), 0, 0);
- EXPECT_FALSE(internal::PodUnion_Data::Validate(
- buf, &validation_context, false));
- free(raw_buf);
-}
-
TEST(UnionTest, OOBValidation) {
- size_t size = sizeof(internal::PodUnion_Data) - 1;
- mojo::internal::FixedBufferForTesting buf(size);
- internal::PodUnion_Data* data = internal::PodUnion_Data::New(&buf);
+ constexpr size_t size = sizeof(internal::PodUnion_Data) - 1;
+ mojo::Message message(0, 0, size, 0, nullptr);
+ internal::PodUnion_Data::BufferWriter writer;
+ writer.Allocate(message.payload_buffer());
mojo::internal::ValidationContext validation_context(
- data, static_cast<uint32_t>(size), 0, 0);
- void* raw_buf = buf.Leak();
- EXPECT_FALSE(
- internal::PodUnion_Data::Validate(raw_buf, &validation_context, false));
- free(raw_buf);
+ writer.data(), static_cast<uint32_t>(size), 0, 0);
+ EXPECT_FALSE(internal::PodUnion_Data::Validate(writer.data(),
+ &validation_context, false));
}
TEST(UnionTest, UnknownTagValidation) {
- size_t size = sizeof(internal::PodUnion_Data);
- mojo::internal::FixedBufferForTesting buf(size);
- internal::PodUnion_Data* data = internal::PodUnion_Data::New(&buf);
- data->tag = static_cast<internal::PodUnion_Data::PodUnion_Tag>(0xFFFFFF);
+ constexpr size_t size = sizeof(internal::PodUnion_Data);
+ mojo::Message message(0, 0, size, 0, nullptr);
+ internal::PodUnion_Data::BufferWriter writer;
+ writer.Allocate(message.payload_buffer());
+ writer->tag = static_cast<internal::PodUnion_Data::PodUnion_Tag>(0xFFFFFF);
mojo::internal::ValidationContext validation_context(
- data, static_cast<uint32_t>(size), 0, 0);
- void* raw_buf = buf.Leak();
- EXPECT_FALSE(
- internal::PodUnion_Data::Validate(raw_buf, &validation_context, false));
- free(raw_buf);
+ writer.data(), static_cast<uint32_t>(size), 0, 0);
+ EXPECT_FALSE(internal::PodUnion_Data::Validate(writer.data(),
+ &validation_context, false));
}
TEST(UnionTest, UnknownEnumValueValidation) {
- PodUnionPtr pod(PodUnion::New());
- pod->set_f_enum(static_cast<AnEnum>(0xFFFF));
-
- size_t size =
- mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr);
- EXPECT_EQ(16U, size);
+ PodUnionPtr pod(PodUnion::NewFEnum(static_cast<AnEnum>(0xFFFF)));
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::PodUnion_Data* data = nullptr;
- mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr);
+ const size_t size = SerializeUnion(pod, &message, &context, &data);
+ EXPECT_EQ(16U, size);
- void* raw_buf = buf.Leak();
mojo::internal::ValidationContext validation_context(
data, static_cast<uint32_t>(size), 0, 0);
EXPECT_FALSE(
- internal::PodUnion_Data::Validate(raw_buf, &validation_context, false));
- free(raw_buf);
+ internal::PodUnion_Data::Validate(data, &validation_context, false));
}
TEST(UnionTest, UnknownExtensibleEnumValueValidation) {
- PodUnionPtr pod(PodUnion::New());
- pod->set_f_extensible_enum(static_cast<AnExtensibleEnum>(0xFFFF));
+ PodUnionPtr pod(
+ PodUnion::NewFExtensibleEnum(static_cast<AnExtensibleEnum>(0xFFFF)));
- size_t size =
- mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr);
- EXPECT_EQ(16U, size);
-
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::PodUnion_Data* data = nullptr;
- mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr);
+ const size_t size = SerializeUnion(pod, &message, &context, &data);
+ EXPECT_EQ(16U, size);
- void* raw_buf = buf.Leak();
mojo::internal::ValidationContext validation_context(
data, static_cast<uint32_t>(size), 0, 0);
EXPECT_TRUE(
- internal::PodUnion_Data::Validate(raw_buf, &validation_context, false));
- free(raw_buf);
+ internal::PodUnion_Data::Validate(data, &validation_context, false));
}
TEST(UnionTest, StringGetterSetter) {
@@ -320,12 +388,19 @@ TEST(UnionTest, StringGetterSetter) {
EXPECT_EQ(pod->which(), ObjectUnion::Tag::F_STRING);
}
+TEST(UnionTest, StringFactoryFunction) {
+ std::string hello("hello world");
+ ObjectUnionPtr pod(ObjectUnion::NewFString(hello));
+
+ EXPECT_EQ(hello, pod->get_f_string());
+ EXPECT_TRUE(pod->is_f_string());
+ EXPECT_EQ(pod->which(), ObjectUnion::Tag::F_STRING);
+}
+
TEST(UnionTest, StringEquals) {
- ObjectUnionPtr pod1(ObjectUnion::New());
- ObjectUnionPtr pod2(ObjectUnion::New());
+ ObjectUnionPtr pod1(ObjectUnion::NewFString("hello world"));
+ ObjectUnionPtr pod2(ObjectUnion::NewFString("hello world"));
- pod1->set_f_string("hello world");
- pod2->set_f_string("hello world");
EXPECT_TRUE(pod1.Equals(pod2));
pod2->set_f_string("hello universe");
@@ -333,10 +408,9 @@ TEST(UnionTest, StringEquals) {
}
TEST(UnionTest, StringClone) {
- ObjectUnionPtr pod(ObjectUnion::New());
-
std::string hello("hello world");
- pod->set_f_string(hello);
+ ObjectUnionPtr pod(ObjectUnion::NewFString(hello));
+
ObjectUnionPtr pod_clone = pod.Clone();
EXPECT_EQ(hello, pod_clone->get_f_string());
EXPECT_TRUE(pod_clone->is_f_string());
@@ -344,17 +418,13 @@ TEST(UnionTest, StringClone) {
}
TEST(UnionTest, StringSerialization) {
- ObjectUnionPtr pod1(ObjectUnion::New());
-
std::string hello("hello world");
- pod1->set_f_string(hello);
+ ObjectUnionPtr pod1(ObjectUnion::NewFString(hello));
- size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
- pod1, false, nullptr);
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::ObjectUnion_Data* data = nullptr;
- mojo::internal::Serialize<ObjectUnionDataView>(pod1, &buf, &data, false,
- nullptr);
+ SerializeUnion(pod1, &message, &context, &data);
ObjectUnionPtr pod2;
mojo::internal::Deserialize<ObjectUnionDataView>(data, &pod2, nullptr);
@@ -364,50 +434,47 @@ TEST(UnionTest, StringSerialization) {
}
TEST(UnionTest, NullStringValidation) {
- size_t size = sizeof(internal::ObjectUnion_Data);
- mojo::internal::FixedBufferForTesting buf(size);
- internal::ObjectUnion_Data* data = internal::ObjectUnion_Data::New(&buf);
- data->tag = internal::ObjectUnion_Data::ObjectUnion_Tag::F_STRING;
- data->data.unknown = 0x0;
+ constexpr size_t size = sizeof(internal::ObjectUnion_Data);
+ mojo::internal::FixedBufferForTesting buffer(size);
+ internal::ObjectUnion_Data::BufferWriter writer;
+ writer.Allocate(&buffer);
+ writer->tag = internal::ObjectUnion_Data::ObjectUnion_Tag::F_STRING;
+ writer->data.unknown = 0x0;
mojo::internal::ValidationContext validation_context(
- data, static_cast<uint32_t>(size), 0, 0);
- void* raw_buf = buf.Leak();
+ writer.data(), static_cast<uint32_t>(size), 0, 0);
EXPECT_FALSE(internal::ObjectUnion_Data::Validate(
- raw_buf, &validation_context, false));
- free(raw_buf);
+ buffer.data(), &validation_context, false));
}
TEST(UnionTest, StringPointerOverflowValidation) {
- size_t size = sizeof(internal::ObjectUnion_Data);
- mojo::internal::FixedBufferForTesting buf(size);
- internal::ObjectUnion_Data* data = internal::ObjectUnion_Data::New(&buf);
- data->tag = internal::ObjectUnion_Data::ObjectUnion_Tag::F_STRING;
- data->data.unknown = 0xFFFFFFFFFFFFFFFF;
+ constexpr size_t size = sizeof(internal::ObjectUnion_Data);
+ mojo::internal::FixedBufferForTesting buffer(size);
+ internal::ObjectUnion_Data::BufferWriter writer;
+ writer.Allocate(&buffer);
+ writer->tag = internal::ObjectUnion_Data::ObjectUnion_Tag::F_STRING;
+ writer->data.unknown = 0xFFFFFFFFFFFFFFFF;
mojo::internal::ValidationContext validation_context(
- data, static_cast<uint32_t>(size), 0, 0);
- void* raw_buf = buf.Leak();
+ writer.data(), static_cast<uint32_t>(size), 0, 0);
EXPECT_FALSE(internal::ObjectUnion_Data::Validate(
- raw_buf, &validation_context, false));
- free(raw_buf);
+ buffer.data(), &validation_context, false));
}
TEST(UnionTest, StringValidateOOB) {
- size_t size = 32;
- mojo::internal::FixedBufferForTesting buf(size);
- internal::ObjectUnion_Data* data = internal::ObjectUnion_Data::New(&buf);
- data->tag = internal::ObjectUnion_Data::ObjectUnion_Tag::F_STRING;
-
- data->data.f_f_string.offset = 8;
- char* ptr = reinterpret_cast<char*>(&data->data.f_f_string);
+ constexpr size_t size = 32;
+ mojo::internal::FixedBufferForTesting buffer(size);
+ internal::ObjectUnion_Data::BufferWriter writer;
+ writer.Allocate(&buffer);
+ writer->tag = internal::ObjectUnion_Data::ObjectUnion_Tag::F_STRING;
+
+ writer->data.f_f_string.offset = 8;
+ char* ptr = reinterpret_cast<char*>(&writer->data.f_f_string);
mojo::internal::ArrayHeader* array_header =
reinterpret_cast<mojo::internal::ArrayHeader*>(ptr + *ptr);
array_header->num_bytes = 20; // This should go out of bounds.
array_header->num_elements = 20;
- mojo::internal::ValidationContext validation_context(data, 32, 0, 0);
- void* raw_buf = buf.Leak();
+ mojo::internal::ValidationContext validation_context(writer.data(), 32, 0, 0);
EXPECT_FALSE(internal::ObjectUnion_Data::Validate(
- raw_buf, &validation_context, false));
- free(raw_buf);
+ buffer.data(), &validation_context, false));
}
// TODO(azani): Move back in array_unittest.cc when possible.
@@ -434,23 +501,16 @@ TEST(UnionTest, PodUnionInArraySerialization) {
array[1]->set_f_int16(12);
EXPECT_EQ(2U, array.size());
- size_t size =
- mojo::internal::PrepareToSerialize<ArrayDataView<PodUnionDataView>>(
- array, nullptr);
- EXPECT_EQ(40U, size);
-
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
mojo::internal::Array_Data<internal::PodUnion_Data>* data;
- mojo::internal::ContainerValidateParams validate_params(0, false, nullptr);
- mojo::internal::Serialize<ArrayDataView<PodUnionDataView>>(
- array, &buf, &data, &validate_params, nullptr);
+ EXPECT_EQ(40U, SerializeArray<ArrayDataView<PodUnionDataView>>(
+ array, false, &message, &context, &data));
std::vector<PodUnionPtr> array2;
mojo::internal::Deserialize<ArrayDataView<PodUnionDataView>>(data, &array2,
nullptr);
-
EXPECT_EQ(2U, array2.size());
-
EXPECT_EQ(10, array2[0]->get_f_int8());
EXPECT_EQ(12, array2[1]->get_f_int16());
}
@@ -462,23 +522,16 @@ TEST(UnionTest, PodUnionInArraySerializationWithNull) {
array[0]->set_f_int8(10);
EXPECT_EQ(2U, array.size());
- size_t size =
- mojo::internal::PrepareToSerialize<ArrayDataView<PodUnionDataView>>(
- array, nullptr);
- EXPECT_EQ(40U, size);
-
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
mojo::internal::Array_Data<internal::PodUnion_Data>* data;
- mojo::internal::ContainerValidateParams validate_params(0, true, nullptr);
- mojo::internal::Serialize<ArrayDataView<PodUnionDataView>>(
- array, &buf, &data, &validate_params, nullptr);
+ EXPECT_EQ(40U, SerializeArray<ArrayDataView<PodUnionDataView>>(
+ array, true, &message, &context, &data));
std::vector<PodUnionPtr> array2;
mojo::internal::Deserialize<ArrayDataView<PodUnionDataView>>(data, &array2,
nullptr);
-
EXPECT_EQ(2U, array2.size());
-
EXPECT_EQ(10, array2[0]->get_f_int8());
EXPECT_TRUE(array2[1].is_null());
}
@@ -492,30 +545,23 @@ TEST(UnionTest, ObjectUnionInArraySerialization) {
array[1]->set_f_string("world");
EXPECT_EQ(2U, array.size());
- size_t size =
- mojo::internal::PrepareToSerialize<ArrayDataView<ObjectUnionDataView>>(
- array, nullptr);
- EXPECT_EQ(72U, size);
-
- mojo::internal::FixedBufferForTesting buf(size);
-
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
mojo::internal::Array_Data<internal::ObjectUnion_Data>* data;
- mojo::internal::ContainerValidateParams validate_params(0, false, nullptr);
- mojo::internal::Serialize<ArrayDataView<ObjectUnionDataView>>(
- array, &buf, &data, &validate_params, nullptr);
+ const size_t size = SerializeArray<ArrayDataView<ObjectUnionDataView>>(
+ array, false, &message, &context, &data);
+ EXPECT_EQ(72U, size);
std::vector<char> new_buf;
new_buf.resize(size);
-
- void* raw_buf = buf.Leak();
- memcpy(new_buf.data(), raw_buf, size);
- free(raw_buf);
+ memcpy(new_buf.data(), data, size);
data =
reinterpret_cast<mojo::internal::Array_Data<internal::ObjectUnion_Data>*>(
new_buf.data());
mojo::internal::ValidationContext validation_context(
data, static_cast<uint32_t>(size), 0, 0);
+ mojo::internal::ContainerValidateParams validate_params(0, false, nullptr);
ASSERT_TRUE(mojo::internal::Array_Data<internal::ObjectUnion_Data>::Validate(
data, &validation_context, &validate_params));
@@ -546,14 +592,10 @@ TEST(UnionTest, Serialization_UnionOfPods) {
small_struct->pod_union = PodUnion::New();
small_struct->pod_union->set_f_int32(10);
+ mojo::Message message;
mojo::internal::SerializationContext context;
- size_t size = mojo::internal::PrepareToSerialize<SmallStructDataView>(
- small_struct, &context);
-
- mojo::internal::FixedBufferForTesting buf(size);
internal::SmallStruct_Data* data = nullptr;
- mojo::internal::Serialize<SmallStructDataView>(small_struct, &buf, &data,
- &context);
+ SerializeStruct(small_struct, &message, &context, &data);
SmallStructPtr deserialized;
mojo::internal::Deserialize<SmallStructDataView>(data, &deserialized,
@@ -569,13 +611,10 @@ TEST(UnionTest, Serialization_UnionOfObjects) {
std::string hello("hello world");
obj_struct->obj_union->set_f_string(hello);
- size_t size = mojo::internal::PrepareToSerialize<SmallObjStructDataView>(
- obj_struct, nullptr);
-
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::SmallObjStruct_Data* data = nullptr;
- mojo::internal::Serialize<SmallObjStructDataView>(obj_struct, &buf, &data,
- nullptr);
+ SerializeStruct(obj_struct, &message, &context, &data);
SmallObjStructPtr deserialized;
mojo::internal::Deserialize<SmallObjStructDataView>(data, &deserialized,
@@ -590,21 +629,14 @@ TEST(UnionTest, Validation_UnionsInStruct) {
small_struct->pod_union = PodUnion::New();
small_struct->pod_union->set_f_int32(10);
+ mojo::Message message;
mojo::internal::SerializationContext context;
- size_t size = mojo::internal::PrepareToSerialize<SmallStructDataView>(
- small_struct, &context);
-
- mojo::internal::FixedBufferForTesting buf(size);
internal::SmallStruct_Data* data = nullptr;
- mojo::internal::Serialize<SmallStructDataView>(small_struct, &buf, &data,
- &context);
+ const size_t size = SerializeStruct(small_struct, &message, &context, &data);
- void* raw_buf = buf.Leak();
mojo::internal::ValidationContext validation_context(
data, static_cast<uint32_t>(size), 0, 0);
- EXPECT_TRUE(internal::SmallStruct_Data::Validate(
- raw_buf, &validation_context));
- free(raw_buf);
+ EXPECT_TRUE(internal::SmallStruct_Data::Validate(data, &validation_context));
}
// Validation test of a struct union fails due to unknown union tag.
@@ -613,22 +645,15 @@ TEST(UnionTest, Validation_PodUnionInStruct_Failure) {
small_struct->pod_union = PodUnion::New();
small_struct->pod_union->set_f_int32(10);
+ mojo::Message message;
mojo::internal::SerializationContext context;
- size_t size = mojo::internal::PrepareToSerialize<SmallStructDataView>(
- small_struct, &context);
-
- mojo::internal::FixedBufferForTesting buf(size);
internal::SmallStruct_Data* data = nullptr;
- mojo::internal::Serialize<SmallStructDataView>(small_struct, &buf, &data,
- &context);
+ const size_t size = SerializeStruct(small_struct, &message, &context, &data);
data->pod_union.tag = static_cast<internal::PodUnion_Data::PodUnion_Tag>(100);
- void* raw_buf = buf.Leak();
mojo::internal::ValidationContext validation_context(
data, static_cast<uint32_t>(size), 0, 0);
- EXPECT_FALSE(internal::SmallStruct_Data::Validate(
- raw_buf, &validation_context));
- free(raw_buf);
+ EXPECT_FALSE(internal::SmallStruct_Data::Validate(data, &validation_context));
}
// Validation fails due to non-nullable null union in struct.
@@ -636,41 +661,29 @@ TEST(UnionTest, Validation_NullUnion_Failure) {
SmallStructNonNullableUnionPtr small_struct(
SmallStructNonNullableUnion::New());
- size_t size =
- mojo::internal::PrepareToSerialize<SmallStructNonNullableUnionDataView>(
- small_struct, nullptr);
-
- mojo::internal::FixedBufferForTesting buf(size);
- internal::SmallStructNonNullableUnion_Data* data =
- internal::SmallStructNonNullableUnion_Data::New(&buf);
-
- void* raw_buf = buf.Leak();
+ constexpr size_t size = sizeof(internal::SmallStructNonNullableUnion_Data);
+ mojo::internal::FixedBufferForTesting buffer(size);
+ mojo::Message message;
+ internal::SmallStructNonNullableUnion_Data::BufferWriter writer;
+ writer.Allocate(&buffer);
mojo::internal::ValidationContext validation_context(
- data, static_cast<uint32_t>(size), 0, 0);
+ writer.data(), static_cast<uint32_t>(size), 0, 0);
EXPECT_FALSE(internal::SmallStructNonNullableUnion_Data::Validate(
- raw_buf, &validation_context));
- free(raw_buf);
+ writer.data(), &validation_context));
}
// Validation passes with nullable null union.
TEST(UnionTest, Validation_NullableUnion) {
SmallStructPtr small_struct(SmallStruct::New());
+ mojo::Message message;
mojo::internal::SerializationContext context;
- size_t size = mojo::internal::PrepareToSerialize<SmallStructDataView>(
- small_struct, &context);
-
- mojo::internal::FixedBufferForTesting buf(size);
internal::SmallStruct_Data* data = nullptr;
- mojo::internal::Serialize<SmallStructDataView>(small_struct, &buf, &data,
- &context);
+ const size_t size = SerializeStruct(small_struct, &message, &context, &data);
- void* raw_buf = buf.Leak();
mojo::internal::ValidationContext validation_context(
data, static_cast<uint32_t>(size), 0, 0);
- EXPECT_TRUE(internal::SmallStruct_Data::Validate(
- raw_buf, &validation_context));
- free(raw_buf);
+ EXPECT_TRUE(internal::SmallStruct_Data::Validate(data, &validation_context));
}
// TODO(azani): Move back in map_unittest.cc when possible.
@@ -691,28 +704,28 @@ TEST(UnionTest, PodUnionInMap) {
TEST(UnionTest, PodUnionInMapSerialization) {
using MojomType = MapDataView<StringDataView, PodUnionDataView>;
- std::unordered_map<std::string, PodUnionPtr> map;
+ base::flat_map<std::string, PodUnionPtr> map;
map.insert(std::make_pair("one", PodUnion::New()));
map.insert(std::make_pair("two", PodUnion::New()));
map["one"]->set_f_int8(8);
map["two"]->set_f_int16(16);
+ mojo::Message message(0, 0, 0, 0, nullptr);
mojo::internal::SerializationContext context;
- size_t size = mojo::internal::PrepareToSerialize<MojomType>(map, &context);
- EXPECT_EQ(120U, size);
-
- mojo::internal::FixedBufferForTesting buf(size);
+ const size_t payload_start = message.payload_buffer()->cursor();
- typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data::BufferWriter
+ writer;
mojo::internal::ContainerValidateParams validate_params(
new mojo::internal::ContainerValidateParams(0, false, nullptr),
new mojo::internal::ContainerValidateParams(0, false, nullptr));
- mojo::internal::Serialize<MojomType>(map, &buf, &data, &validate_params,
- &context);
+ mojo::internal::Serialize<MojomType>(map, message.payload_buffer(), &writer,
+ &validate_params, &context);
+ EXPECT_EQ(120U, message.payload_buffer()->cursor() - payload_start);
- std::unordered_map<std::string, PodUnionPtr> map2;
- mojo::internal::Deserialize<MojomType>(data, &map2, &context);
+ base::flat_map<std::string, PodUnionPtr> map2;
+ mojo::internal::Deserialize<MojomType>(writer.data(), &map2, &context);
EXPECT_EQ(8, map2["one"]->get_f_int8());
EXPECT_EQ(16, map2["two"]->get_f_int16());
@@ -721,26 +734,27 @@ TEST(UnionTest, PodUnionInMapSerialization) {
TEST(UnionTest, PodUnionInMapSerializationWithNull) {
using MojomType = MapDataView<StringDataView, PodUnionDataView>;
- std::unordered_map<std::string, PodUnionPtr> map;
+ base::flat_map<std::string, PodUnionPtr> map;
map.insert(std::make_pair("one", PodUnion::New()));
map.insert(std::make_pair("two", nullptr));
map["one"]->set_f_int8(8);
+ mojo::Message message(0, 0, 0, 0, nullptr);
mojo::internal::SerializationContext context;
- size_t size = mojo::internal::PrepareToSerialize<MojomType>(map, &context);
- EXPECT_EQ(120U, size);
+ const size_t payload_start = message.payload_buffer()->cursor();
- mojo::internal::FixedBufferForTesting buf(size);
- typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data::BufferWriter
+ writer;
mojo::internal::ContainerValidateParams validate_params(
new mojo::internal::ContainerValidateParams(0, false, nullptr),
new mojo::internal::ContainerValidateParams(0, true, nullptr));
- mojo::internal::Serialize<MojomType>(map, &buf, &data, &validate_params,
- &context);
+ mojo::internal::Serialize<MojomType>(map, message.payload_buffer(), &writer,
+ &validate_params, &context);
+ EXPECT_EQ(120U, message.payload_buffer()->cursor() - payload_start);
- std::unordered_map<std::string, PodUnionPtr> map2;
- mojo::internal::Deserialize<MojomType>(data, &map2, &context);
+ base::flat_map<std::string, PodUnionPtr> map2;
+ mojo::internal::Deserialize<MojomType>(writer.data(), &map2, &context);
EXPECT_EQ(8, map2["one"]->get_f_int8());
EXPECT_TRUE(map2["two"].is_null());
@@ -763,14 +777,10 @@ TEST(UnionTest, StructInUnionSerialization) {
ObjectUnionPtr obj(ObjectUnion::New());
obj->set_f_dummy(std::move(dummy));
- size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
- obj, false, nullptr);
- EXPECT_EQ(32U, size);
-
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::ObjectUnion_Data* data = nullptr;
- mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
- nullptr);
+ EXPECT_EQ(32U, SerializeUnion(obj, &message, &context, &data));
ObjectUnionPtr obj2;
mojo::internal::Deserialize<ObjectUnionDataView>(data, &obj2, nullptr);
@@ -784,20 +794,15 @@ TEST(UnionTest, StructInUnionValidation) {
ObjectUnionPtr obj(ObjectUnion::New());
obj->set_f_dummy(std::move(dummy));
- size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
- obj, false, nullptr);
-
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::ObjectUnion_Data* data = nullptr;
- mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
- nullptr);
+ const size_t size = SerializeUnion(obj, &message, &context, &data);
- void* raw_buf = buf.Leak();
mojo::internal::ValidationContext validation_context(
data, static_cast<uint32_t>(size), 0, 0);
- EXPECT_TRUE(internal::ObjectUnion_Data::Validate(
- raw_buf, &validation_context, false));
- free(raw_buf);
+ EXPECT_TRUE(
+ internal::ObjectUnion_Data::Validate(data, &validation_context, false));
}
TEST(UnionTest, StructInUnionValidationNonNullable) {
@@ -808,20 +813,15 @@ TEST(UnionTest, StructInUnionValidationNonNullable) {
ObjectUnionPtr obj(ObjectUnion::New());
obj->set_f_dummy(std::move(dummy));
- size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
- obj, false, nullptr);
-
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::ObjectUnion_Data* data = nullptr;
- mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
- nullptr);
+ const size_t size = SerializeUnion(obj, &message, &context, &data);
- void* raw_buf = buf.Leak();
mojo::internal::ValidationContext validation_context(
data, static_cast<uint32_t>(size), 0, 0);
- EXPECT_FALSE(internal::ObjectUnion_Data::Validate(
- raw_buf, &validation_context, false));
- free(raw_buf);
+ EXPECT_FALSE(
+ internal::ObjectUnion_Data::Validate(data, &validation_context, false));
}
TEST(UnionTest, StructInUnionValidationNullable) {
@@ -830,20 +830,15 @@ TEST(UnionTest, StructInUnionValidationNullable) {
ObjectUnionPtr obj(ObjectUnion::New());
obj->set_f_nullable(std::move(dummy));
- size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
- obj, false, nullptr);
-
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::ObjectUnion_Data* data = nullptr;
- mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
- nullptr);
+ const size_t size = SerializeUnion(obj, &message, &context, &data);
- void* raw_buf = buf.Leak();
mojo::internal::ValidationContext validation_context(
data, static_cast<uint32_t>(size), 0, 0);
- EXPECT_TRUE(internal::ObjectUnion_Data::Validate(
- raw_buf, &validation_context, false));
- free(raw_buf);
+ EXPECT_TRUE(
+ internal::ObjectUnion_Data::Validate(data, &validation_context, false));
}
TEST(UnionTest, ArrayInUnionGetterSetter) {
@@ -866,14 +861,11 @@ TEST(UnionTest, ArrayInUnionSerialization) {
ObjectUnionPtr obj(ObjectUnion::New());
obj->set_f_array_int8(std::move(array));
- size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
- obj, false, nullptr);
- EXPECT_EQ(32U, size);
-
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::ObjectUnion_Data* data = nullptr;
- mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
- nullptr);
+ const size_t size = SerializeUnion(obj, &message, &context, &data);
+ EXPECT_EQ(32U, size);
ObjectUnionPtr obj2;
mojo::internal::Deserialize<ObjectUnionDataView>(data, &obj2, nullptr);
@@ -890,24 +882,19 @@ TEST(UnionTest, ArrayInUnionValidation) {
ObjectUnionPtr obj(ObjectUnion::New());
obj->set_f_array_int8(std::move(array));
- size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
- obj, false, nullptr);
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::ObjectUnion_Data* data = nullptr;
- mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
- nullptr);
+ const size_t size = SerializeUnion(obj, &message, &context, &data);
- void* raw_buf = buf.Leak();
mojo::internal::ValidationContext validation_context(
data, static_cast<uint32_t>(size), 0, 0);
-
- EXPECT_TRUE(internal::ObjectUnion_Data::Validate(
- raw_buf, &validation_context, false));
- free(raw_buf);
+ EXPECT_TRUE(
+ internal::ObjectUnion_Data::Validate(data, &validation_context, false));
}
TEST(UnionTest, MapInUnionGetterSetter) {
- std::unordered_map<std::string, int8_t> map;
+ base::flat_map<std::string, int8_t> map;
map.insert({"one", 1});
map.insert({"two", 2});
@@ -919,22 +906,18 @@ TEST(UnionTest, MapInUnionGetterSetter) {
}
TEST(UnionTest, MapInUnionSerialization) {
- std::unordered_map<std::string, int8_t> map;
+ base::flat_map<std::string, int8_t> map;
map.insert({"one", 1});
map.insert({"two", 2});
ObjectUnionPtr obj(ObjectUnion::New());
obj->set_f_map_int8(std::move(map));
+ mojo::Message message;
mojo::internal::SerializationContext context;
- size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
- obj, false, &context);
- EXPECT_EQ(112U, size);
-
- mojo::internal::FixedBufferForTesting buf(size);
internal::ObjectUnion_Data* data = nullptr;
- mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
- &context);
+ const size_t size = SerializeUnion(obj, &message, &context, &data);
+ EXPECT_EQ(112U, size);
ObjectUnionPtr obj2;
mojo::internal::Deserialize<ObjectUnionDataView>(data, &obj2, &context);
@@ -944,30 +927,23 @@ TEST(UnionTest, MapInUnionSerialization) {
}
TEST(UnionTest, MapInUnionValidation) {
- std::unordered_map<std::string, int8_t> map;
+ base::flat_map<std::string, int8_t> map;
map.insert({"one", 1});
map.insert({"two", 2});
ObjectUnionPtr obj(ObjectUnion::New());
obj->set_f_map_int8(std::move(map));
+ mojo::Message message;
mojo::internal::SerializationContext context;
- size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
- obj, false, &context);
- EXPECT_EQ(112U, size);
-
- mojo::internal::FixedBufferForTesting buf(size);
internal::ObjectUnion_Data* data = nullptr;
- mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
- &context);
+ const size_t size = SerializeUnion(obj, &message, &context, &data);
+ EXPECT_EQ(112U, size);
- void* raw_buf = buf.Leak();
mojo::internal::ValidationContext validation_context(
data, static_cast<uint32_t>(size), 0, 0);
-
- EXPECT_TRUE(internal::ObjectUnion_Data::Validate(
- raw_buf, &validation_context, false));
- free(raw_buf);
+ EXPECT_TRUE(
+ internal::ObjectUnion_Data::Validate(data, &validation_context, false));
}
TEST(UnionTest, UnionInUnionGetterSetter) {
@@ -980,6 +956,15 @@ TEST(UnionTest, UnionInUnionGetterSetter) {
EXPECT_EQ(10, obj->get_f_pod_union()->get_f_int8());
}
+TEST(UnionTest, UnionInUnionFactoryFunction) {
+ PodUnionPtr pod(PodUnion::New());
+ pod->set_f_int8(10);
+
+ ObjectUnionPtr obj(ObjectUnion::NewFPodUnion(std::move(pod)));
+
+ EXPECT_EQ(10, obj->get_f_pod_union()->get_f_int8());
+}
+
TEST(UnionTest, UnionInUnionSerialization) {
PodUnionPtr pod(PodUnion::New());
pod->set_f_int8(10);
@@ -987,14 +972,11 @@ TEST(UnionTest, UnionInUnionSerialization) {
ObjectUnionPtr obj(ObjectUnion::New());
obj->set_f_pod_union(std::move(pod));
- size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
- obj, false, nullptr);
- EXPECT_EQ(32U, size);
-
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::ObjectUnion_Data* data = nullptr;
- mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
- nullptr);
+ const size_t size = SerializeUnion(obj, &message, &context, &data);
+ EXPECT_EQ(32U, size);
ObjectUnionPtr obj2;
mojo::internal::Deserialize<ObjectUnionDataView>(data, &obj2, nullptr);
@@ -1008,21 +990,16 @@ TEST(UnionTest, UnionInUnionValidation) {
ObjectUnionPtr obj(ObjectUnion::New());
obj->set_f_pod_union(std::move(pod));
- size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
- obj, false, nullptr);
- EXPECT_EQ(32U, size);
-
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::ObjectUnion_Data* data = nullptr;
- mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
- nullptr);
+ const size_t size = SerializeUnion(obj, &message, &context, &data);
+ EXPECT_EQ(32U, size);
- void* raw_buf = buf.Leak();
mojo::internal::ValidationContext validation_context(
data, static_cast<uint32_t>(size), 0, 0);
- EXPECT_TRUE(internal::ObjectUnion_Data::Validate(
- raw_buf, &validation_context, false));
- free(raw_buf);
+ EXPECT_TRUE(
+ internal::ObjectUnion_Data::Validate(data, &validation_context, false));
}
TEST(UnionTest, UnionInUnionValidationNonNullable) {
@@ -1033,20 +1010,15 @@ TEST(UnionTest, UnionInUnionValidationNonNullable) {
ObjectUnionPtr obj(ObjectUnion::New());
obj->set_f_pod_union(std::move(pod));
- size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>(
- obj, false, nullptr);
-
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::ObjectUnion_Data* data = nullptr;
- mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false,
- nullptr);
+ const size_t size = SerializeUnion(obj, &message, &context, &data);
- void* raw_buf = buf.Leak();
mojo::internal::ValidationContext validation_context(
data, static_cast<uint32_t>(size), 0, 0);
- EXPECT_FALSE(internal::ObjectUnion_Data::Validate(
- raw_buf, &validation_context, false));
- free(raw_buf);
+ EXPECT_FALSE(
+ internal::ObjectUnion_Data::Validate(data, &validation_context, false));
}
TEST(UnionTest, HandleInUnionGetterSetter) {
@@ -1067,6 +1039,23 @@ TEST(UnionTest, HandleInUnionGetterSetter) {
EXPECT_EQ(golden, actual);
}
+TEST(UnionTest, HandleInUnionGetterFactoryFunction) {
+ ScopedMessagePipeHandle pipe0;
+ ScopedMessagePipeHandle pipe1;
+
+ CreateMessagePipe(nullptr, &pipe0, &pipe1);
+
+ HandleUnionPtr handle(HandleUnion::NewFMessagePipe(std::move(pipe1)));
+
+ std::string golden("hello world");
+ WriteTextMessage(pipe0.get(), golden);
+
+ std::string actual;
+ ReadTextMessage(handle->get_f_message_pipe().get(), &actual);
+
+ EXPECT_EQ(golden, actual);
+}
+
TEST(UnionTest, HandleInUnionSerialization) {
ScopedMessagePipeHandle pipe0;
ScopedMessagePipeHandle pipe1;
@@ -1076,16 +1065,12 @@ TEST(UnionTest, HandleInUnionSerialization) {
HandleUnionPtr handle(HandleUnion::New());
handle->set_f_message_pipe(std::move(pipe1));
+ mojo::Message message;
mojo::internal::SerializationContext context;
- size_t size = mojo::internal::PrepareToSerialize<HandleUnionDataView>(
- handle, false, &context);
- EXPECT_EQ(16U, size);
-
- mojo::internal::FixedBufferForTesting buf(size);
internal::HandleUnion_Data* data = nullptr;
- mojo::internal::Serialize<HandleUnionDataView>(handle, &buf, &data, false,
- &context);
- EXPECT_EQ(1U, context.handles.size());
+ const size_t size = SerializeUnion(handle, &message, &context, &data);
+ EXPECT_EQ(16U, size);
+ EXPECT_EQ(1U, context.handles()->size());
HandleUnionPtr handle2(HandleUnion::New());
mojo::internal::Deserialize<HandleUnionDataView>(data, &handle2, &context);
@@ -1108,22 +1093,16 @@ TEST(UnionTest, HandleInUnionValidation) {
HandleUnionPtr handle(HandleUnion::New());
handle->set_f_message_pipe(std::move(pipe1));
+ mojo::Message message;
mojo::internal::SerializationContext context;
- size_t size = mojo::internal::PrepareToSerialize<HandleUnionDataView>(
- handle, false, &context);
- EXPECT_EQ(16U, size);
-
- mojo::internal::FixedBufferForTesting buf(size);
internal::HandleUnion_Data* data = nullptr;
- mojo::internal::Serialize<HandleUnionDataView>(handle, &buf, &data, false,
- &context);
+ const size_t size = SerializeUnion(handle, &message, &context, &data);
+ EXPECT_EQ(16U, size);
- void* raw_buf = buf.Leak();
mojo::internal::ValidationContext validation_context(
data, static_cast<uint32_t>(size), 1, 0);
- EXPECT_TRUE(internal::HandleUnion_Data::Validate(
- raw_buf, &validation_context, false));
- free(raw_buf);
+ EXPECT_TRUE(
+ internal::HandleUnion_Data::Validate(data, &validation_context, false));
}
TEST(UnionTest, HandleInUnionValidationNull) {
@@ -1133,22 +1112,16 @@ TEST(UnionTest, HandleInUnionValidationNull) {
HandleUnionPtr handle(HandleUnion::New());
handle->set_f_message_pipe(std::move(pipe));
+ mojo::Message message;
mojo::internal::SerializationContext context;
- size_t size = mojo::internal::PrepareToSerialize<HandleUnionDataView>(
- handle, false, &context);
- EXPECT_EQ(16U, size);
-
- mojo::internal::FixedBufferForTesting buf(size);
internal::HandleUnion_Data* data = nullptr;
- mojo::internal::Serialize<HandleUnionDataView>(handle, &buf, &data, false,
- &context);
+ const size_t size = SerializeUnion(handle, &message, &context, &data);
+ EXPECT_EQ(16U, size);
- void* raw_buf = buf.Leak();
mojo::internal::ValidationContext validation_context(
data, static_cast<uint32_t>(size), 1, 0);
- EXPECT_FALSE(internal::HandleUnion_Data::Validate(
- raw_buf, &validation_context, false));
- free(raw_buf);
+ EXPECT_FALSE(
+ internal::HandleUnion_Data::Validate(data, &validation_context, false));
}
class SmallCacheImpl : public SmallCache {
@@ -1179,9 +1152,24 @@ TEST(UnionTest, InterfaceInUnion) {
Binding<SmallCache> bindings(&impl, MakeRequest(&ptr));
HandleUnionPtr handle(HandleUnion::New());
- handle->set_f_small_cache(std::move(ptr));
+ handle->set_f_small_cache(ptr.PassInterface());
- handle->get_f_small_cache()->SetIntValue(10);
+ ptr.Bind(std::move(handle->get_f_small_cache()));
+ ptr->SetIntValue(10);
+ run_loop.Run();
+ EXPECT_EQ(10, impl.int_value());
+}
+
+TEST(UnionTest, InterfaceInUnionFactoryFunction) {
+ base::MessageLoop message_loop;
+ base::RunLoop run_loop;
+ SmallCacheImpl impl(run_loop.QuitClosure());
+ SmallCachePtr ptr;
+ Binding<SmallCache> bindings(&impl, MakeRequest(&ptr));
+
+ HandleUnionPtr handle = HandleUnion::NewFSmallCache(ptr.PassInterface());
+ ptr.Bind(std::move(handle->get_f_small_cache()));
+ ptr->SetIntValue(10);
run_loop.Run();
EXPECT_EQ(10, impl.int_value());
}
@@ -1193,23 +1181,21 @@ TEST(UnionTest, InterfaceInUnionSerialization) {
SmallCachePtr ptr;
Binding<SmallCache> bindings(&impl, MakeRequest(&ptr));
- mojo::internal::SerializationContext context;
HandleUnionPtr handle(HandleUnion::New());
- handle->set_f_small_cache(std::move(ptr));
- size_t size = mojo::internal::PrepareToSerialize<HandleUnionDataView>(
- handle, false, &context);
- EXPECT_EQ(16U, size);
+ handle->set_f_small_cache(ptr.PassInterface());
- mojo::internal::FixedBufferForTesting buf(size);
+ mojo::Message message;
+ mojo::internal::SerializationContext context;
internal::HandleUnion_Data* data = nullptr;
- mojo::internal::Serialize<HandleUnionDataView>(handle, &buf, &data, false,
- &context);
- EXPECT_EQ(1U, context.handles.size());
+ const size_t size = SerializeUnion(handle, &message, &context, &data);
+ EXPECT_EQ(16U, size);
+ EXPECT_EQ(1U, context.handles()->size());
HandleUnionPtr handle2(HandleUnion::New());
mojo::internal::Deserialize<HandleUnionDataView>(data, &handle2, &context);
- handle2->get_f_small_cache()->SetIntValue(10);
+ ptr.Bind(std::move(handle2->get_f_small_cache()));
+ ptr->SetIntValue(10);
run_loop.Run();
EXPECT_EQ(10, impl.int_value());
}
diff --git a/mojo/public/cpp/bindings/tests/validation_unittest.cc b/mojo/public/cpp/bindings/tests/validation_unittest.cc
index 7af7396d4e..2db4d9b598 100644
--- a/mojo/public/cpp/bindings/tests/validation_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/validation_unittest.cc
@@ -10,7 +10,9 @@
#include <utility>
#include <vector>
+#include "base/macros.h"
#include "base/message_loop/message_loop.h"
+#include "base/numerics/safe_math.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "mojo/public/c/system/macros.h"
@@ -23,6 +25,7 @@
#include "mojo/public/cpp/bindings/message_header_validator.h"
#include "mojo/public/cpp/bindings/tests/validation_test_input_parser.h"
#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/system/message.h"
#include "mojo/public/cpp/test_support/test_support.h"
#include "mojo/public/interfaces/bindings/tests/validation_test_associated_interfaces.mojom.h"
#include "mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom.h"
@@ -32,6 +35,25 @@ namespace mojo {
namespace test {
namespace {
+Message CreateRawMessage(size_t size) {
+ ScopedMessageHandle handle;
+ MojoResult rv = CreateMessage(&handle);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+ DCHECK(handle.is_valid());
+
+ DCHECK(base::IsValueInRangeForNumericType<uint32_t>(size));
+ MojoAppendMessageDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE;
+ void* buffer;
+ uint32_t buffer_size;
+ rv = MojoAppendMessageData(handle->value(), static_cast<uint32_t>(size),
+ nullptr, 0, &options, &buffer, &buffer_size);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+
+ return Message(std::move(handle));
+}
+
template <typename T>
void Append(std::vector<uint8_t>* data_vector, T data) {
size_t pos = data_vector->size();
@@ -150,8 +172,7 @@ bool ReadTestCase(const std::string& test,
return false;
}
- message->Initialize(static_cast<uint32_t>(data.size()),
- false /* zero_initialized */);
+ *message = CreateRawMessage(data.size());
if (!data.empty())
memcpy(message->mutable_data(), &data[0], data.size());
message->mutable_handles()->resize(num_handles);
@@ -443,8 +464,7 @@ TEST_F(ValidationIntegrationTest, InterfacePtr) {
TEST_F(ValidationIntegrationTest, Binding) {
IntegrationTestInterfaceImpl interface_impl;
Binding<IntegrationTestInterface> binding(
- &interface_impl,
- MakeRequest<IntegrationTestInterface>(testee_endpoint()));
+ &interface_impl, IntegrationTestInterfaceRequest(testee_endpoint()));
binding.EnableTestingMode();
RunValidationTests("integration_intf_rqst", test_message_receiver());
diff --git a/mojo/public/cpp/bindings/tests/variant_test_util.h b/mojo/public/cpp/bindings/tests/variant_test_util.h
index 3f6b1f1b5f..f1e75b3298 100644
--- a/mojo/public/cpp/bindings/tests/variant_test_util.h
+++ b/mojo/public/cpp/bindings/tests/variant_test_util.h
@@ -21,9 +21,7 @@ template <typename Interface0, typename Interface1>
InterfaceRequest<Interface0> ConvertInterfaceRequest(
InterfaceRequest<Interface1> request) {
DCHECK_EQ(0, strcmp(Interface0::Name_, Interface1::Name_));
- InterfaceRequest<Interface0> result;
- result.Bind(request.PassMessagePipe());
- return result;
+ return InterfaceRequest<Interface0>(request.PassMessagePipe());
}
} // namespace test
diff --git a/mojo/public/cpp/bindings/tests/versioning_test_service.cc b/mojo/public/cpp/bindings/tests/versioning_test_service.cc
index 313a6249e2..174147883f 100644
--- a/mojo/public/cpp/bindings/tests/versioning_test_service.cc
+++ b/mojo/public/cpp/bindings/tests/versioning_test_service.cc
@@ -11,7 +11,7 @@
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "mojo/public/interfaces/bindings/tests/versioning_test_service.mojom.h"
#include "services/service_manager/public/c/main.h"
-#include "services/service_manager/public/cpp/interface_factory.h"
+#include "services/service_manager/public/cpp/binder_registry.h"
#include "services/service_manager/public/cpp/service.h"
#include "services/service_manager/public/cpp/service_runner.h"
@@ -94,25 +94,28 @@ class HumanResourceDatabaseImpl : public HumanResourceDatabase {
StrongBinding<HumanResourceDatabase> strong_binding_;
};
-class HumanResourceSystemServer
- : public service_manager::Service,
- public InterfaceFactory<HumanResourceDatabase> {
+class HumanResourceSystemServer : public service_manager::Service {
public:
- HumanResourceSystemServer() {}
+ HumanResourceSystemServer() {
+ registry_.AddInterface<HumanResourceDatabase>(
+ base::Bind(&HumanResourceSystemServer::Create, base::Unretained(this)));
+ }
// service_manager::Service implementation.
- bool OnConnect(Connection* connection) override {
- connection->AddInterface<HumanResourceDatabase>(this);
- return true;
+ void OnBindInterface(const service_manager::BindSourceInfo& source_info,
+ const std::string& interface_name,
+ mojo::ScopedMessagePipeHandle interface_pipe) override {
+ registry_.BindInterface(interface_name, std::move(interface_pipe));
}
- // InterfaceFactory<HumanResourceDatabase> implementation.
- void Create(Connection* connection,
- InterfaceRequest<HumanResourceDatabase> request) override {
+ void Create(HumanResourceDatabaseRequest request) {
// It will be deleted automatically when the underlying pipe encounters a
// connection error.
new HumanResourceDatabaseImpl(std::move(request));
}
+
+ private:
+ service_manager::BinderRegistry registry_;
};
} // namespace versioning
diff --git a/mojo/public/cpp/bindings/tests/wtf_hash_unittest.cc b/mojo/public/cpp/bindings/tests/wtf_hash_unittest.cc
index 959d25b368..458df2c9f1 100644
--- a/mojo/public/cpp/bindings/tests/wtf_hash_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/wtf_hash_unittest.cc
@@ -7,7 +7,7 @@
#include "mojo/public/interfaces/bindings/tests/test_structs.mojom-blink.h"
#include "mojo/public/interfaces/bindings/tests/test_wtf_types.mojom-blink.h"
#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/WebKit/Source/wtf/HashFunctions.h"
+#include "third_party/blink/renderer/platform/wtf/hash_functions.h"
namespace mojo {
namespace test {
@@ -25,33 +25,25 @@ TEST_F(WTFHashTest, NestedStruct) {
blink::SimpleNestedStruct::New(blink::ContainsOther::New(1))));
}
-TEST_F(WTFHashTest, UnmappedNativeStruct) {
- // Just check that this template instantiation compiles.
- ASSERT_EQ(::mojo::internal::Hash(::mojo::internal::kHashSeed,
- blink::UnmappedNativeStruct::New()),
- ::mojo::internal::Hash(::mojo::internal::kHashSeed,
- blink::UnmappedNativeStruct::New()));
-}
-
TEST_F(WTFHashTest, Enum) {
// Just check that this template instantiation compiles.
// Top-level.
- ASSERT_EQ(WTF::DefaultHash<blink::TopLevelEnum>::Hash().hash(
+ ASSERT_EQ(WTF::DefaultHash<blink::TopLevelEnum>::Hash().GetHash(
blink::TopLevelEnum::E0),
- WTF::DefaultHash<blink::TopLevelEnum>::Hash().hash(
+ WTF::DefaultHash<blink::TopLevelEnum>::Hash().GetHash(
blink::TopLevelEnum::E0));
// Nested in struct.
- ASSERT_EQ(WTF::DefaultHash<blink::TestWTFStruct::NestedEnum>::Hash().hash(
+ ASSERT_EQ(WTF::DefaultHash<blink::TestWTFStruct::NestedEnum>::Hash().GetHash(
blink::TestWTFStruct::NestedEnum::E0),
- WTF::DefaultHash<blink::TestWTFStruct::NestedEnum>::Hash().hash(
+ WTF::DefaultHash<blink::TestWTFStruct::NestedEnum>::Hash().GetHash(
blink::TestWTFStruct::NestedEnum::E0));
// Nested in interface.
- ASSERT_EQ(WTF::DefaultHash<blink::TestWTF::NestedEnum>::Hash().hash(
+ ASSERT_EQ(WTF::DefaultHash<blink::TestWTF::NestedEnum>::Hash().GetHash(
blink::TestWTF::NestedEnum::E0),
- WTF::DefaultHash<blink::TestWTF::NestedEnum>::Hash().hash(
+ WTF::DefaultHash<blink::TestWTF::NestedEnum>::Hash().GetHash(
blink::TestWTF::NestedEnum::E0));
}
diff --git a/mojo/public/cpp/bindings/tests/wtf_types_unittest.cc b/mojo/public/cpp/bindings/tests/wtf_types_unittest.cc
index 363ef7cdab..1064e41b8b 100644
--- a/mojo/public/cpp/bindings/tests/wtf_types_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/wtf_types_unittest.cc
@@ -14,7 +14,7 @@
#include "mojo/public/interfaces/bindings/tests/test_wtf_types.mojom-blink.h"
#include "mojo/public/interfaces/bindings/tests/test_wtf_types.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/WebKit/Source/wtf/text/StringHash.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
namespace mojo {
namespace test {
@@ -44,8 +44,7 @@ class TestWTFImpl : public TestWTF {
void EchoStringMap(
const base::Optional<
- std::unordered_map<std::string, base::Optional<std::string>>>&
- str_map,
+ base::flat_map<std::string, base::Optional<std::string>>>& str_map,
const EchoStringMapCallback& callback) override {
callback.Run(std::move(str_map));
}
@@ -68,7 +67,7 @@ WTF::Vector<WTF::String> ConstructStringArray() {
// strs[1] is empty.
strs[1] = "";
strs[2] = kHelloWorld;
- strs[3] = WTF::String::fromUTF8(kUTF8HelloWorld);
+ strs[3] = WTF::String::FromUTF8(kUTF8HelloWorld);
return strs;
}
@@ -78,7 +77,7 @@ WTF::HashMap<WTF::String, WTF::String> ConstructStringMap() {
// A null string as value.
str_map.insert("0", WTF::String());
str_map.insert("1", kHelloWorld);
- str_map.insert("2", WTF::String::fromUTF8(kUTF8HelloWorld));
+ str_map.insert("2", WTF::String::FromUTF8(kUTF8HelloWorld));
return str_map;
}
@@ -90,17 +89,17 @@ void ExpectString(const WTF::String& expected_string,
closure.Run();
}
-void ExpectStringArray(WTF::Optional<WTF::Vector<WTF::String>>* expected_arr,
+void ExpectStringArray(base::Optional<WTF::Vector<WTF::String>>* expected_arr,
const base::Closure& closure,
- const WTF::Optional<WTF::Vector<WTF::String>>& arr) {
+ const base::Optional<WTF::Vector<WTF::String>>& arr) {
EXPECT_EQ(*expected_arr, arr);
closure.Run();
}
void ExpectStringMap(
- WTF::Optional<WTF::HashMap<WTF::String, WTF::String>>* expected_map,
+ base::Optional<WTF::HashMap<WTF::String, WTF::String>>* expected_map,
const base::Closure& closure,
- const WTF::Optional<WTF::HashMap<WTF::String, WTF::String>>& map) {
+ const base::Optional<WTF::HashMap<WTF::String, WTF::String>>& map) {
EXPECT_EQ(*expected_map, map);
closure.Run();
}
@@ -113,19 +112,43 @@ TEST_F(WTFTypesTest, Serialization_WTFVectorToWTFVector) {
WTF::Vector<WTF::String> strs = ConstructStringArray();
auto cloned_strs = strs;
+ mojo::Message message(0, 0, 0, 0, nullptr);
mojo::internal::SerializationContext context;
- size_t size =
- mojo::internal::PrepareToSerialize<MojomType>(cloned_strs, &context);
-
- mojo::internal::FixedBufferForTesting buf(size);
- typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data::BufferWriter
+ writer;
mojo::internal::ContainerValidateParams validate_params(
0, true, new mojo::internal::ContainerValidateParams(0, false, nullptr));
- mojo::internal::Serialize<MojomType>(cloned_strs, &buf, &data,
- &validate_params, &context);
+ mojo::internal::Serialize<MojomType>(cloned_strs, message.payload_buffer(),
+ &writer, &validate_params, &context);
WTF::Vector<WTF::String> strs2;
- mojo::internal::Deserialize<MojomType>(data, &strs2, &context);
+ mojo::internal::Deserialize<MojomType>(writer.data(), &strs2, &context);
+
+ EXPECT_EQ(strs, strs2);
+}
+
+TEST_F(WTFTypesTest, Serialization_WTFVectorInlineCapacity) {
+ using MojomType = ArrayDataView<StringDataView>;
+
+ WTF::Vector<WTF::String, 1> strs(4);
+ // strs[0] is null.
+ // strs[1] is empty.
+ strs[1] = "";
+ strs[2] = kHelloWorld;
+ strs[3] = WTF::String::FromUTF8(kUTF8HelloWorld);
+ auto cloned_strs = strs;
+
+ mojo::Message message(0, 0, 0, 0, nullptr);
+ mojo::internal::SerializationContext context;
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data::BufferWriter
+ writer;
+ mojo::internal::ContainerValidateParams validate_params(
+ 0, true, new mojo::internal::ContainerValidateParams(0, false, nullptr));
+ mojo::internal::Serialize<MojomType>(cloned_strs, message.payload_buffer(),
+ &writer, &validate_params, &context);
+
+ WTF::Vector<WTF::String, 1> strs2;
+ mojo::internal::Deserialize<MojomType>(writer.data(), &strs2, &context);
EXPECT_EQ(strs, strs2);
}
@@ -136,19 +159,17 @@ TEST_F(WTFTypesTest, Serialization_WTFVectorToStlVector) {
WTF::Vector<WTF::String> strs = ConstructStringArray();
auto cloned_strs = strs;
+ mojo::Message message(0, 0, 0, 0, nullptr);
mojo::internal::SerializationContext context;
- size_t size =
- mojo::internal::PrepareToSerialize<MojomType>(cloned_strs, &context);
-
- mojo::internal::FixedBufferForTesting buf(size);
- typename mojo::internal::MojomTypeTraits<MojomType>::Data* data;
+ typename mojo::internal::MojomTypeTraits<MojomType>::Data::BufferWriter
+ writer;
mojo::internal::ContainerValidateParams validate_params(
0, true, new mojo::internal::ContainerValidateParams(0, false, nullptr));
- mojo::internal::Serialize<MojomType>(cloned_strs, &buf, &data,
- &validate_params, &context);
+ mojo::internal::Serialize<MojomType>(cloned_strs, message.payload_buffer(),
+ &writer, &validate_params, &context);
std::vector<base::Optional<std::string>> strs2;
- mojo::internal::Deserialize<MojomType>(data, &strs2, &context);
+ mojo::internal::Deserialize<MojomType>(writer.data(), &strs2, &context);
ASSERT_EQ(4u, strs2.size());
EXPECT_FALSE(strs2[0]);
@@ -192,7 +213,7 @@ TEST_F(WTFTypesTest, SendStringArray) {
blink::TestWTFPtr ptr;
TestWTFImpl impl(ConvertInterfaceRequest<TestWTF>(MakeRequest(&ptr)));
- WTF::Optional<WTF::Vector<WTF::String>> arrs[3];
+ base::Optional<WTF::Vector<WTF::String>> arrs[3];
// arrs[0] is empty.
arrs[0].emplace();
// arrs[1] is null.
@@ -200,13 +221,13 @@ TEST_F(WTFTypesTest, SendStringArray) {
for (size_t i = 0; i < arraysize(arrs); ++i) {
base::RunLoop loop;
- // Test that a WTF::Optional<WTF::Vector<WTF::String>> is unchanged after
+ // Test that a base::Optional<WTF::Vector<WTF::String>> is unchanged after
// the following conversion:
// - serialized;
// - deserialized as
// base::Optional<std::vector<base::Optional<std::string>>>;
// - serialized;
- // - deserialized as WTF::Optional<WTF::Vector<WTF::String>>.
+ // - deserialized as base::Optional<WTF::Vector<WTF::String>>.
ptr->EchoStringArray(
arrs[i], base::Bind(&ExpectStringArray, base::Unretained(&arrs[i]),
loop.QuitClosure()));
@@ -218,7 +239,7 @@ TEST_F(WTFTypesTest, SendStringMap) {
blink::TestWTFPtr ptr;
TestWTFImpl impl(ConvertInterfaceRequest<TestWTF>(MakeRequest(&ptr)));
- WTF::Optional<WTF::HashMap<WTF::String, WTF::String>> maps[3];
+ base::Optional<WTF::HashMap<WTF::String, WTF::String>> maps[3];
// maps[0] is empty.
maps[0].emplace();
// maps[1] is null.
@@ -226,13 +247,13 @@ TEST_F(WTFTypesTest, SendStringMap) {
for (size_t i = 0; i < arraysize(maps); ++i) {
base::RunLoop loop;
- // Test that a WTF::Optional<WTF::HashMap<WTF::String, WTF::String>> is
+ // Test that a base::Optional<WTF::HashMap<WTF::String, WTF::String>> is
// unchanged after the following conversion:
// - serialized;
// - deserialized as base::Optional<
- // std::unordered_map<std::string, base::Optional<std::string>>>;
+ // base::flat_map<std::string, base::Optional<std::string>>>;
// - serialized;
- // - deserialized as WTF::Optional<WTF::HashMap<WTF::String,
+ // - deserialized as base::Optional<WTF::HashMap<WTF::String,
// WTF::String>>.
ptr->EchoStringMap(maps[i],
base::Bind(&ExpectStringMap, base::Unretained(&maps[i]),
@@ -241,5 +262,20 @@ TEST_F(WTFTypesTest, SendStringMap) {
}
}
+TEST_F(WTFTypesTest, NestedStruct_CloneAndEquals) {
+ auto a = blink::TestWTFStructWrapper::New();
+ a->nested_struct = blink::TestWTFStruct::New("foo", 1);
+ a->array_struct.push_back(blink::TestWTFStruct::New("bar", 2));
+ a->array_struct.push_back(blink::TestWTFStruct::New("bar", 3));
+ a->map_struct.insert(blink::TestWTFStruct::New("baz", 4),
+ blink::TestWTFStruct::New("baz", 5));
+ auto b = a.Clone();
+ EXPECT_EQ(a, b);
+ EXPECT_EQ(2u, b->array_struct.size());
+ EXPECT_EQ(1u, b->map_struct.size());
+ EXPECT_NE(blink::TestWTFStructWrapper::New(), a);
+ EXPECT_NE(blink::TestWTFStructWrapper::New(), b);
+}
+
} // namespace test
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/thread_safe_interface_ptr.h b/mojo/public/cpp/bindings/thread_safe_interface_ptr.h
index 740687f379..0a5c7f6f43 100644
--- a/mojo/public/cpp/bindings/thread_safe_interface_ptr.h
+++ b/mojo/public/cpp/bindings/thread_safe_interface_ptr.h
@@ -13,7 +13,7 @@
#include "base/stl_util.h"
#include "base/synchronization/waitable_event.h"
#include "base/task_runner.h"
-#include "base/threading/thread_task_runner_handle.h"
+#include "base/threading/sequenced_task_runner_handle.h"
#include "mojo/public/cpp/bindings/associated_group.h"
#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
#include "mojo/public/cpp/bindings/interface_ptr.h"
@@ -22,22 +22,22 @@
#include "mojo/public/cpp/bindings/sync_event_watcher.h"
// ThreadSafeInterfacePtr wraps a non-thread-safe InterfacePtr and proxies
-// messages to it. Async calls are posted to the thread that the InteracePtr is
-// bound to, and the responses are posted back. Sync calls are dispatched
-// directly if the call is made on the thread that the wrapped InterfacePtr is
+// messages to it. Async calls are posted to the sequence that the InteracePtr
+// is bound to, and the responses are posted back. Sync calls are dispatched
+// directly if the call is made on the sequence that the wrapped InterfacePtr is
// bound to, or posted otherwise. It's important to be aware that sync calls
-// block both the calling thread and the InterfacePtr thread. That means that
-// you cannot make sync calls through a ThreadSafeInterfacePtr if the
-// underlying InterfacePtr is bound to a thread that cannot block, like the IO
+// block both the calling sequence and the InterfacePtr sequence. That means
+// that you cannot make sync calls through a ThreadSafeInterfacePtr if the
+// underlying InterfacePtr is bound to a sequence that cannot block, like the IO
// thread.
namespace mojo {
-// Instances of this class may be used from any thread to serialize |Interface|
-// messages and forward them elsewhere. In general you should use one of the
-// ThreadSafeInterfacePtrBase helper aliases defined below, but this type may be
-// useful if you need/want to manually manage the lifetime of the underlying
-// proxy object which will be used to ultimately send messages.
+// Instances of this class may be used from any sequence to serialize
+// |Interface| messages and forward them elsewhere. In general you should use
+// one of the ThreadSafeInterfacePtrBase helper aliases defined below, but this
+// type may be useful if you need/want to manually manage the lifetime of the
+// underlying proxy object which will be used to ultimately send messages.
template <typename Interface>
class ThreadSafeForwarder : public MessageReceiverWithResponder {
public:
@@ -50,9 +50,10 @@ class ThreadSafeForwarder : public MessageReceiverWithResponder {
// |forward| or |forward_with_responder| by posting to |task_runner|.
//
// Any message sent through this forwarding interface will dispatch its reply,
- // if any, back to the thread which called the corresponding interface method.
+ // if any, back to the sequence which called the corresponding interface
+ // method.
ThreadSafeForwarder(
- const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner,
const ForwardMessageCallback& forward,
const ForwardMessageWithResponderCallback& forward_with_responder,
const AssociatedGroup& associated_group)
@@ -74,6 +75,13 @@ class ThreadSafeForwarder : public MessageReceiverWithResponder {
private:
// MessageReceiverWithResponder implementation:
+ bool PrefersSerializedMessages() override {
+ // TSIP is primarily used because it emulates legacy IPC threading behavior.
+ // In practice this means it's only for cross-process messaging and we can
+ // just always assume messages should be serialized.
+ return true;
+ }
+
bool Accept(Message* message) override {
if (!message->associated_endpoint_handles()->empty()) {
// If this DCHECK fails, it is likely because:
@@ -104,10 +112,10 @@ class ThreadSafeForwarder : public MessageReceiverWithResponder {
}
// Async messages are always posted (even if |task_runner_| runs tasks on
- // this thread) to guarantee that two async calls can't be reordered.
+ // this sequence) to guarantee that two async calls can't be reordered.
if (!message->has_flag(Message::kFlagIsSync)) {
auto reply_forwarder =
- base::MakeUnique<ForwardToCallingThread>(std::move(responder));
+ std::make_unique<ForwardToCallingThread>(std::move(responder));
task_runner_->PostTask(
FROM_HERE, base::Bind(forward_with_responder_, base::Passed(message),
base::Passed(&reply_forwarder)));
@@ -116,17 +124,17 @@ class ThreadSafeForwarder : public MessageReceiverWithResponder {
SyncCallRestrictions::AssertSyncCallAllowed();
- // If the InterfacePtr is bound to this thread, dispatch it directly.
- if (task_runner_->RunsTasksOnCurrentThread()) {
+ // If the InterfacePtr is bound to this sequence, dispatch it directly.
+ if (task_runner_->RunsTasksInCurrentSequence()) {
forward_with_responder_.Run(std::move(*message), std::move(responder));
return true;
}
- // If the InterfacePtr is bound on another thread, post the call.
- // TODO(yzshen, watk): We block both this thread and the InterfacePtr
- // thread. Ideally only this thread would block.
- auto response = make_scoped_refptr(new SyncResponseInfo());
- auto response_signaler = base::MakeUnique<SyncResponseSignaler>(response);
+ // If the InterfacePtr is bound on another sequence, post the call.
+ // TODO(yzshen, watk): We block both this sequence and the InterfacePtr
+ // sequence. Ideally only this sequence would block.
+ auto response = base::MakeRefCounted<SyncResponseInfo>();
+ auto response_signaler = std::make_unique<SyncResponseSignaler>(response);
task_runner_->PostTask(
FROM_HERE, base::Bind(forward_with_responder_, base::Passed(message),
base::Passed(&response_signaler)));
@@ -144,7 +152,8 @@ class ThreadSafeForwarder : public MessageReceiverWithResponder {
bool event_signaled = false;
SyncEventWatcher watcher(&response->event,
base::Bind(assign_true, &event_signaled));
- watcher.SyncWatch(&event_signaled);
+ const bool* stop_flags[] = {&event_signaled};
+ watcher.SyncWatch(stop_flags, 1);
{
base::AutoLock l(sync_calls->lock);
@@ -157,7 +166,7 @@ class ThreadSafeForwarder : public MessageReceiverWithResponder {
return true;
}
- // Data that we need to share between the threads involved in a sync call.
+ // Data that we need to share between the sequences involved in a sync call.
struct SyncResponseInfo
: public base::RefCountedThreadSafe<SyncResponseInfo> {
Message message;
@@ -183,7 +192,7 @@ class ThreadSafeForwarder : public MessageReceiverWithResponder {
response_->event.Signal();
}
- bool Accept(Message* message) {
+ bool Accept(Message* message) override {
response_->message = std::move(*message);
response_->received = true;
response_->event.Signal();
@@ -208,10 +217,13 @@ class ThreadSafeForwarder : public MessageReceiverWithResponder {
public:
explicit ForwardToCallingThread(std::unique_ptr<MessageReceiver> responder)
: responder_(std::move(responder)),
- caller_task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
+ caller_task_runner_(base::SequencedTaskRunnerHandle::Get()) {}
+ ~ForwardToCallingThread() override {
+ caller_task_runner_->DeleteSoon(FROM_HERE, std::move(responder_));
+ }
private:
- bool Accept(Message* message) {
+ bool Accept(Message* message) override {
// The current instance will be deleted when this method returns, so we
// have to relinquish the responder's ownership so it does not get
// deleted.
@@ -230,11 +242,11 @@ class ThreadSafeForwarder : public MessageReceiverWithResponder {
}
std::unique_ptr<MessageReceiver> responder_;
- scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_;
+ scoped_refptr<base::SequencedTaskRunner> caller_task_runner_;
};
ProxyType proxy_;
- const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ const scoped_refptr<base::SequencedTaskRunner> task_runner_;
const ForwardMessageCallback forward_;
const ForwardMessageWithResponderCallback forward_with_responder_;
AssociatedGroup associated_group_;
@@ -256,9 +268,9 @@ class ThreadSafeInterfacePtrBase
: forwarder_(std::move(forwarder)) {}
// Creates a ThreadSafeInterfacePtrBase wrapping an underlying non-thread-safe
- // InterfacePtrType which is bound to the calling thread. All messages sent
+ // InterfacePtrType which is bound to the calling sequence. All messages sent
// via this thread-safe proxy will internally be sent by first posting to this
- // (the calling) thread's TaskRunner.
+ // (the calling) sequence's TaskRunner.
static scoped_refptr<ThreadSafeInterfacePtrBase> Create(
InterfacePtrType interface_ptr) {
scoped_refptr<PtrWrapper> wrapper =
@@ -272,7 +284,7 @@ class ThreadSafeInterfacePtrBase
// that TaskRunner.
static scoped_refptr<ThreadSafeInterfacePtrBase> Create(
PtrInfoType ptr_info,
- const scoped_refptr<base::SingleThreadTaskRunner>& bind_task_runner) {
+ const scoped_refptr<base::SequencedTaskRunner>& bind_task_runner) {
scoped_refptr<PtrWrapper> wrapper = new PtrWrapper(bind_task_runner);
wrapper->BindOnTaskRunner(std::move(ptr_info));
return new ThreadSafeInterfacePtrBase(wrapper->CreateForwarder());
@@ -289,19 +301,19 @@ class ThreadSafeInterfacePtrBase
struct PtrWrapperDeleter;
// Helper class which owns an |InterfacePtrType| instance on an appropriate
- // thread. This is kept alive as long its bound within some
+ // sequence. This is kept alive as long its bound within some
// ThreadSafeForwarder's callbacks.
class PtrWrapper
: public base::RefCountedThreadSafe<PtrWrapper, PtrWrapperDeleter> {
public:
explicit PtrWrapper(InterfacePtrType ptr)
- : PtrWrapper(base::ThreadTaskRunnerHandle::Get()) {
+ : PtrWrapper(base::SequencedTaskRunnerHandle::Get()) {
ptr_ = std::move(ptr);
associated_group_ = *ptr_.internal_state()->associated_group();
}
explicit PtrWrapper(
- const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
+ const scoped_refptr<base::SequencedTaskRunner>& task_runner)
: task_runner_(task_runner) {}
void BindOnTaskRunner(AssociatedInterfacePtrInfo<InterfaceType> ptr_info) {
@@ -316,14 +328,14 @@ class ThreadSafeInterfacePtrBase
// endpoints on this interface (at least not immediately). In order to fix
// this, we need to create a MultiplexRouter immediately and bind it to
// the interface pointer on the |task_runner_|. Therefore, MultiplexRouter
- // should be able to be created on a thread different than the one that it
- // is supposed to listen on. crbug.com/682334
+ // should be able to be created on a sequence different than the one that
+ // it is supposed to listen on. crbug.com/682334
task_runner_->PostTask(FROM_HERE, base::Bind(&PtrWrapper::Bind, this,
base::Passed(&ptr_info)));
}
std::unique_ptr<ThreadSafeForwarder<InterfaceType>> CreateForwarder() {
- return base::MakeUnique<ThreadSafeForwarder<InterfaceType>>(
+ return std::make_unique<ThreadSafeForwarder<InterfaceType>>(
task_runner_, base::Bind(&PtrWrapper::Accept, this),
base::Bind(&PtrWrapper::AcceptWithResponder, this),
associated_group_);
@@ -335,7 +347,7 @@ class ThreadSafeInterfacePtrBase
~PtrWrapper() {}
void Bind(PtrInfoType ptr_info) {
- DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
ptr_.Bind(std::move(ptr_info));
}
@@ -350,7 +362,7 @@ class ThreadSafeInterfacePtrBase
}
void DeleteOnCorrectThread() const {
- if (!task_runner_->RunsTasksOnCurrentThread()) {
+ if (!task_runner_->RunsTasksInCurrentSequence()) {
// NOTE: This is only called when there are no more references to
// |this|, so binding it unretained is both safe and necessary.
task_runner_->PostTask(FROM_HERE,
@@ -362,7 +374,7 @@ class ThreadSafeInterfacePtrBase
}
InterfacePtrType ptr_;
- const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ const scoped_refptr<base::SequencedTaskRunner> task_runner_;
AssociatedGroup associated_group_;
DISALLOW_COPY_AND_ASSIGN(PtrWrapper);
diff --git a/mojo/public/cpp/bindings/union_traits.h b/mojo/public/cpp/bindings/union_traits.h
index 292ee58f27..243addd7ed 100644
--- a/mojo/public/cpp/bindings/union_traits.h
+++ b/mojo/public/cpp/bindings/union_traits.h
@@ -5,6 +5,8 @@
#ifndef MOJO_PUBLIC_CPP_BINDINGS_UNION_TRAITS_H_
#define MOJO_PUBLIC_CPP_BINDINGS_UNION_TRAITS_H_
+#include "mojo/public/cpp/bindings/lib/template_util.h"
+
namespace mojo {
// This must be specialized for any type |T| to be serialized/deserialized as
@@ -32,7 +34,11 @@ namespace mojo {
// will be called.
//
template <typename DataViewType, typename T>
-struct UnionTraits;
+struct UnionTraits {
+ static_assert(internal::AlwaysFalse<T>::value,
+ "Cannot find the mojo::UnionTraits specialization. Did you "
+ "forget to include the corresponding header file?");
+};
} // namespace mojo
diff --git a/mojo/public/cpp/bindings/unique_ptr_impl_ref_traits.h b/mojo/public/cpp/bindings/unique_ptr_impl_ref_traits.h
index f1ac097396..ca7fe930c6 100644
--- a/mojo/public/cpp/bindings/unique_ptr_impl_ref_traits.h
+++ b/mojo/public/cpp/bindings/unique_ptr_impl_ref_traits.h
@@ -9,9 +9,9 @@ namespace mojo {
// Traits for a binding's implementation reference type.
// This corresponds to a unique_ptr reference type.
-template <typename Interface>
+template <typename Interface, typename Deleter = std::default_delete<Interface>>
struct UniquePtrImplRefTraits {
- using PointerType = std::unique_ptr<Interface>;
+ using PointerType = std::unique_ptr<Interface, Deleter>;
static bool IsNull(const PointerType& ptr) { return !ptr; }
static Interface* GetRawPointer(PointerType* ptr) { return ptr->get(); }
diff --git a/mojo/public/cpp/platform/BUILD.gn b/mojo/public/cpp/platform/BUILD.gn
new file mode 100644
index 0000000000..b0aa90ef37
--- /dev/null
+++ b/mojo/public/cpp/platform/BUILD.gn
@@ -0,0 +1,50 @@
+# 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.
+
+import("//build/config/nacl/config.gni")
+
+component("platform") {
+ output_name = "mojo_cpp_platform"
+
+ public = [
+ "named_platform_channel.h",
+ "platform_channel.h",
+ "platform_channel_endpoint.h",
+ "platform_channel_server_endpoint.h",
+ "platform_handle.h",
+ ]
+
+ sources = [
+ "named_platform_channel.cc",
+ "named_platform_channel_win.cc",
+ "platform_channel.cc",
+ "platform_channel_endpoint.cc",
+ "platform_channel_server_endpoint.cc",
+ "platform_handle.cc",
+ ]
+
+ if (is_posix && (!is_nacl || is_nacl_nonsfi)) {
+ public += [ "socket_utils_posix.h" ]
+ sources += [ "socket_utils_posix.cc" ]
+ }
+
+ public_deps = [
+ "//base",
+ "//mojo/public/c/system:headers",
+ ]
+
+ if (is_posix && (!is_nacl && !is_fuchsia)) {
+ sources += [ "named_platform_channel_posix.cc" ]
+ }
+
+ if (is_fuchsia) {
+ sources += [ "named_platform_channel_fuchsia.cc" ]
+ public_deps += [
+ "//third_party/fuchsia-sdk:fdio",
+ "//third_party/fuchsia-sdk:zx",
+ ]
+ }
+
+ defines = [ "IS_MOJO_CPP_PLATFORM_IMPL" ]
+}
diff --git a/mojo/public/cpp/platform/README.md b/mojo/public/cpp/platform/README.md
new file mode 100644
index 0000000000..c91e85fa7f
--- /dev/null
+++ b/mojo/public/cpp/platform/README.md
@@ -0,0 +1,75 @@
+# Mojo C++ Platform API
+This document is a subset of the [Mojo documentation](/mojo/README.md).
+
+[TOC]
+
+## Overview
+The Mojo C++ Platform API provides a lightweight set of abstractions around
+stable platform primitive APIs like UNIX domain sockets and Windows named pipes.
+This API is primarily useful in conjunction with Mojo
+[Invitations](/mojo/public/cpp/system/README.md#Invitations) to bootstrap Mojo
+IPC between two processes.
+
+## Platform Handles
+The `PlatformHandle` type provides a move-only wrapper around an owned,
+platform-specific primitive handle types. The type of primitive it holds can be
+any of the following:
+
+ * Windows HANDLE (Windows only)
+ * Fuchsia zx_handle_t (Fuchsia only)
+ * Mach send right (OSX only)
+ * POSIX file descriptor (POSIX systems only)
+
+See the
+[header](https://cs.chromium.org/src/mojo/public/cpp/platform/platform_handle.h)
+for more details.
+
+## Platform Channels
+The `PlatformChannel` type abstracts a platform-specific IPC FIFO primitive
+primarily for use with the Mojo
+[Invitations](/mojo/public/cpp/system/README.md#Invitations) API. Constructing
+a `PlatformChannel` instance creates the underlying system primitive with two
+transferrable `PlatformHandle` instances, each thinly wrapped as a
+`PlatformChannelEndpoint` for additional type-safety. One endpoint is designated
+as **local** and the other **remote**, the intention being that the remote
+endpoint will be transferred to another process in the system.
+
+See the
+[header](https://cs.chromium.org/src/mojo/public/cpp/platform/platform_channel.h)
+for more details. See the
+[Invitations](/mojo/public/cpp/system/README.md#Invitations) documentation for
+an example of using `PlatformChannel` with an invitation to bootstrap IPC
+between a process and one of its newly launched child processes.
+
+## Named Platform Channels
+For cases where it is not feasible to transfer a `PlatformHandle` from one
+running process to another, the Platform API also provides
+`NamedPlatformChannel`, which abstracts a named system resource that can
+facilitate communication similarly to `PlatformChannel`.
+
+A `NamedPlatformChannel` upon construction will begin listening on
+platform-specific primitive (a named pipe server on Windows, a domain socket
+server on POSIX, *etc.*). The globally reachable name of the server (*e.g.* the
+socket path) can be specified at construction time via
+`NamedPlatformChannel::Options::server_name`, but if no name is given, a
+suitably random one is generated and used.
+
+``` cpp
+// In one process
+mojo::NamedPlatformChannel::Options options;
+mojo::NamedPlatformChannel named_channel(options);
+OutgoingInvitation::Send(std::move(invitation),
+ named_channel.TakeServerEndpoint());
+SendServerNameToRemoteProcessSomehow(named_channel.GetServerName());
+
+// In the other process
+void OnGotServerName(const mojo::NamedPlatformChannel::ServerName& name) {
+ // Connect to the server.
+ mojo::PlatformChannelEndpoint endpoint =
+ mojo::NamedPlatformChannel::ConnectToServer(name);
+
+ // Proceed normally with invitation acceptance.
+ auto invitation = mojo::IncomingInvitation::Accept(std::move(endpoint));
+ // ...
+}
+```
diff --git a/mojo/public/cpp/platform/named_platform_channel.cc b/mojo/public/cpp/platform/named_platform_channel.cc
new file mode 100644
index 0000000000..4439710df9
--- /dev/null
+++ b/mojo/public/cpp/platform/named_platform_channel.cc
@@ -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.
+
+#include "mojo/public/cpp/platform/named_platform_channel.h"
+
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace mojo {
+
+const char NamedPlatformChannel::kNamedHandleSwitch[] =
+ "mojo-named-platform-channel-pipe";
+
+NamedPlatformChannel::NamedPlatformChannel(const Options& options) {
+ server_endpoint_ = PlatformChannelServerEndpoint(
+ CreateServerEndpoint(options, &server_name_));
+}
+
+NamedPlatformChannel::NamedPlatformChannel(NamedPlatformChannel&& other) =
+ default;
+
+NamedPlatformChannel::~NamedPlatformChannel() = default;
+
+NamedPlatformChannel& NamedPlatformChannel::operator=(
+ NamedPlatformChannel&& other) = default;
+
+// static
+NamedPlatformChannel::ServerName NamedPlatformChannel::ServerNameFromUTF8(
+ base::StringPiece name) {
+#if defined(OS_WIN)
+ return base::UTF8ToUTF16(name);
+#else
+ return name.as_string();
+#endif
+}
+
+void NamedPlatformChannel::PassServerNameOnCommandLine(
+ base::CommandLine* command_line) {
+ command_line->AppendSwitchNative(kNamedHandleSwitch, server_name_);
+}
+
+// static
+PlatformChannelEndpoint NamedPlatformChannel::ConnectToServer(
+ const ServerName& server_name) {
+ DCHECK(!server_name.empty());
+ return CreateClientEndpoint(server_name);
+}
+
+// static
+PlatformChannelEndpoint NamedPlatformChannel::ConnectToServer(
+ const base::CommandLine& command_line) {
+ ServerName name = command_line.GetSwitchValueNative(kNamedHandleSwitch);
+ if (name.empty())
+ return PlatformChannelEndpoint();
+ return ConnectToServer(name);
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/platform/named_platform_channel.h b/mojo/public/cpp/platform/named_platform_channel.h
new file mode 100644
index 0000000000..177136e4e5
--- /dev/null
+++ b/mojo/public/cpp/platform/named_platform_channel.h
@@ -0,0 +1,122 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_PLATFORM_NAMED_PLATFORM_CHANNEL_H_
+#define MOJO_PUBLIC_CPP_PLATFORM_NAMED_PLATFORM_CHANNEL_H_
+
+#include "base/command_line.h"
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "build/build_config.h"
+#include "mojo/public/cpp/platform/platform_channel_endpoint.h"
+#include "mojo/public/cpp/platform/platform_channel_server_endpoint.h"
+
+#if defined(OS_WIN)
+#include "base/strings/string16.h"
+#elif defined(OS_POSIX)
+#include "base/files/file_path.h"
+#endif
+
+namespace mojo {
+
+// NamedPlatformChannel encapsulates a Mojo invitation transport channel which
+// can listen for inbound connections established by clients connecting to
+// a named system resource (i.e. a named pipe server on Windows, a named
+// Unix domain socket on POSIX; other platforms not supported).
+//
+// This can be especially useful when the local process has no way to transfer
+// handles to the remote process, e.g. it does not control process launch or
+// have any pre-existing communication channel to the process.
+class COMPONENT_EXPORT(MOJO_CPP_PLATFORM) NamedPlatformChannel {
+ public:
+ static const char kNamedHandleSwitch[];
+
+#if defined(OS_WIN)
+ using ServerName = base::string16;
+#else
+ using ServerName = std::string;
+#endif
+
+ struct COMPONENT_EXPORT(MOJO_CPP_PLATFORM) Options {
+ // Specifies the name to use for the server. If empty, a random name is
+ // generated.
+ ServerName server_name;
+
+#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|.
+ base::string16 security_descriptor;
+
+ // If |true|, only a server endpoint will be allowed with the given name and
+ // only one client will be able to connect. Otherwise many
+ // NamedPlatformChannel instances can be created with the same name and
+ // a different client can connect to each one.
+ bool enforce_uniqueness = true;
+#elif defined(OS_POSIX)
+ // On POSIX, every new unnamed NamedPlatformChannel creates a server socket
+ // with a random name. This controls the directory where that happens.
+ // Ignored if |server_name| was set explicitly.
+ base::FilePath socket_dir;
+#endif
+ };
+
+ NamedPlatformChannel(const Options& options);
+ NamedPlatformChannel(NamedPlatformChannel&& other);
+ ~NamedPlatformChannel();
+
+ NamedPlatformChannel& operator=(NamedPlatformChannel&& other);
+
+ const PlatformChannelServerEndpoint& server_endpoint() const {
+ return server_endpoint_;
+ }
+
+ // Helper to create a ServerName from a UTF8 string regardless of platform.
+ static ServerName ServerNameFromUTF8(base::StringPiece name);
+
+ // Passes the local server endpoint for the channel. On Windows, this is a
+ // named pipe server; on POSIX it's a bound, listening domain socket. In each
+ // case it should accept a single new connection.
+ //
+ // Use the handle to send or receive an invitation, with the endpoint type as
+ // |MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL_SERVER|.
+ PlatformChannelServerEndpoint TakeServerEndpoint() WARN_UNUSED_RESULT {
+ return std::move(server_endpoint_);
+ }
+
+ // Returns a name that can be used a remote process to connect to the server
+ // endpoint.
+ const ServerName& GetServerName() const { return server_name_; }
+
+ // Passes the server name on |*command_line| using the common
+ // |kNamedHandleSwitch| flag.
+ void PassServerNameOnCommandLine(base::CommandLine* command_line);
+
+ // Recovers a functioning client endpoint handle by creating a new endpoint
+ // and connecting it to |server_name| if possible.
+ static PlatformChannelEndpoint ConnectToServer(const ServerName& server_name)
+ WARN_UNUSED_RESULT;
+
+ // Like above, but extracts the server name from |command_line| using the
+ // common |kNamedHandleSwitch| flag.
+ static PlatformChannelEndpoint ConnectToServer(
+ const base::CommandLine& command_line) WARN_UNUSED_RESULT;
+
+ private:
+ static PlatformChannelServerEndpoint CreateServerEndpoint(
+ const Options& options,
+ ServerName* server_name);
+ static PlatformChannelEndpoint CreateClientEndpoint(
+ const ServerName& server_name);
+
+ ServerName server_name_;
+ PlatformChannelServerEndpoint server_endpoint_;
+
+ DISALLOW_COPY_AND_ASSIGN(NamedPlatformChannel);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_PLATFORM_NAMED_PLATFORM_CHANNEL_H_
diff --git a/mojo/public/cpp/platform/named_platform_channel_fuchsia.cc b/mojo/public/cpp/platform/named_platform_channel_fuchsia.cc
new file mode 100644
index 0000000000..44ae4af368
--- /dev/null
+++ b/mojo/public/cpp/platform/named_platform_channel_fuchsia.cc
@@ -0,0 +1,26 @@
+// 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.
+
+#include "mojo/public/cpp/platform/named_platform_channel.h"
+
+namespace mojo {
+
+// static
+PlatformChannelServerEndpoint NamedPlatformChannel::CreateServerEndpoint(
+ const Options& options,
+ ServerName* server_name) {
+ // TODO(https://crbug.com/754038): Implement, or remove dependencies.
+ NOTREACHED();
+ return {};
+}
+
+// static
+PlatformChannelEndpoint NamedPlatformChannel::CreateClientEndpoint(
+ const ServerName& server_name) {
+ // TODO(https://crbug.com/754038): Implement, or remove dependencies.
+ NOTREACHED();
+ return {};
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/platform/named_platform_channel_posix.cc b/mojo/public/cpp/platform/named_platform_channel_posix.cc
new file mode 100644
index 0000000000..9082ac4da5
--- /dev/null
+++ b/mojo/public/cpp/platform/named_platform_channel_posix.cc
@@ -0,0 +1,151 @@
+// 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.
+
+#include "mojo/public/cpp/platform/named_platform_channel.h"
+
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+
+namespace mojo {
+
+namespace {
+
+NamedPlatformChannel::ServerName GenerateRandomServerName(
+ const NamedPlatformChannel::Options& options) {
+ return options.socket_dir
+ .AppendASCII(base::NumberToString(base::RandUint64()))
+ .value();
+}
+
+// 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 |server_name|
+// violated the naming rules).
+bool MakeUnixAddr(const NamedPlatformChannel::ServerName& server_name,
+ struct sockaddr_un* unix_addr,
+ size_t* unix_addr_len) {
+ DCHECK(unix_addr);
+ DCHECK(unix_addr_len);
+ DCHECK(!server_name.empty());
+
+ constexpr size_t kMaxSocketNameLength = 104;
+
+ // We reject server_name.length() == kMaxSocketNameLength to make room for the
+ // NUL terminator at the end of the string.
+ if (server_name.length() >= kMaxSocketNameLength) {
+ LOG(ERROR) << "Socket name too long: " << server_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, server_name.c_str(), kMaxSocketNameLength);
+ *unix_addr_len =
+ offsetof(struct sockaddr_un, sun_path) + server_name.length();
+ return true;
+}
+
+// This function creates a unix domain socket, and set it as non-blocking.
+// If successful, this returns a PlatformHandle containing the socket.
+// Otherwise, this returns an invalid PlatformHandle.
+PlatformHandle CreateUnixDomainSocket() {
+ // Create the unix domain socket.
+ PlatformHandle handle(base::ScopedFD(socket(AF_UNIX, SOCK_STREAM, 0)));
+ if (!handle.is_valid()) {
+ PLOG(ERROR) << "Failed to create AF_UNIX socket.";
+ return PlatformHandle();
+ }
+
+ // Now set it as non-blocking.
+ if (!base::SetNonBlocking(handle.GetFD().get())) {
+ PLOG(ERROR) << "base::SetNonBlocking() failed " << handle.GetFD().get();
+ return PlatformHandle();
+ }
+ return handle;
+}
+
+} // namespace
+
+// static
+PlatformChannelServerEndpoint NamedPlatformChannel::CreateServerEndpoint(
+ const Options& options,
+ ServerName* server_name) {
+ ServerName name = options.server_name;
+ if (name.empty())
+ name = GenerateRandomServerName(options);
+
+ // Make sure the path we need exists.
+ base::FilePath socket_dir = base::FilePath(name).DirName();
+ if (!base::CreateDirectory(socket_dir)) {
+ LOG(ERROR) << "Couldn't create directory: " << socket_dir.value();
+ return PlatformChannelServerEndpoint();
+ }
+
+ // Delete any old FS instances.
+ if (unlink(name.c_str()) < 0 && errno != ENOENT) {
+ PLOG(ERROR) << "unlink " << name;
+ return PlatformChannelServerEndpoint();
+ }
+
+ struct sockaddr_un unix_addr;
+ size_t unix_addr_len;
+ if (!MakeUnixAddr(name, &unix_addr, &unix_addr_len))
+ return PlatformChannelServerEndpoint();
+
+ PlatformHandle handle = CreateUnixDomainSocket();
+ if (!handle.is_valid())
+ return PlatformChannelServerEndpoint();
+
+ // Bind the socket.
+ if (bind(handle.GetFD().get(), reinterpret_cast<const sockaddr*>(&unix_addr),
+ unix_addr_len) < 0) {
+ PLOG(ERROR) << "bind " << name;
+ return PlatformChannelServerEndpoint();
+ }
+
+ // Start listening on the socket.
+ if (listen(handle.GetFD().get(), SOMAXCONN) < 0) {
+ PLOG(ERROR) << "listen " << name;
+ unlink(name.c_str());
+ return PlatformChannelServerEndpoint();
+ }
+
+ *server_name = name;
+ return PlatformChannelServerEndpoint(std::move(handle));
+}
+
+// static
+PlatformChannelEndpoint NamedPlatformChannel::CreateClientEndpoint(
+ const ServerName& server_name) {
+ DCHECK(!server_name.empty());
+
+ struct sockaddr_un unix_addr;
+ size_t unix_addr_len;
+ if (!MakeUnixAddr(server_name, &unix_addr, &unix_addr_len))
+ return PlatformChannelEndpoint();
+
+ PlatformHandle handle = CreateUnixDomainSocket();
+ if (!handle.is_valid())
+ return PlatformChannelEndpoint();
+
+ if (HANDLE_EINTR(connect(handle.GetFD().get(),
+ reinterpret_cast<sockaddr*>(&unix_addr),
+ unix_addr_len)) < 0) {
+ PLOG(ERROR) << "connect " << server_name;
+ return PlatformChannelEndpoint();
+ }
+ return PlatformChannelEndpoint(std::move(handle));
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/platform/named_platform_channel_win.cc b/mojo/public/cpp/platform/named_platform_channel_win.cc
new file mode 100644
index 0000000000..9c329bd6d8
--- /dev/null
+++ b/mojo/public/cpp/platform/named_platform_channel_win.cc
@@ -0,0 +1,110 @@
+// 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.
+
+#include "mojo/public/cpp/platform/named_platform_channel.h"
+
+#include <windows.h>
+#include <memory>
+
+// NOTE: This needs to be included *after* windows.h.
+#include <sddl.h>
+
+#include "base/rand_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/scoped_handle.h"
+#include "base/win/windows_version.h"
+
+namespace mojo {
+
+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)";
+
+NamedPlatformChannel::ServerName GenerateRandomServerName() {
+ return base::UTF8ToUTF16(
+ base::StringPrintf("%lu.%lu.%I64u", ::GetCurrentProcessId(),
+ ::GetCurrentThreadId(), base::RandUint64()));
+}
+
+base::string16 GetPipeNameFromServerName(
+ const NamedPlatformChannel::ServerName& server_name) {
+ return L"\\\\.\\pipe\\mojo." + server_name;
+}
+
+} // namespace
+
+// static
+PlatformChannelServerEndpoint NamedPlatformChannel::CreateServerEndpoint(
+ const Options& options,
+ ServerName* server_name) {
+ ServerName name = options.server_name;
+ if (name.empty())
+ name = GenerateRandomServerName();
+
+ 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<void, decltype(::LocalFree)*> 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;
+
+ base::string16 pipe_name = GetPipeNameFromServerName(name);
+ PlatformHandle handle(base::win::ScopedHandle(::CreateNamedPipeW(
+ 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)));
+
+ *server_name = name;
+ return PlatformChannelServerEndpoint(std::move(handle));
+}
+
+// static
+PlatformChannelEndpoint NamedPlatformChannel::CreateClientEndpoint(
+ const ServerName& server_name) {
+ base::string16 pipe_name = GetPipeNameFromServerName(server_name);
+
+ // Note: This may block.
+ if (!::WaitNamedPipeW(pipe_name.c_str(), NMPWAIT_USE_DEFAULT_WAIT))
+ return PlatformChannelEndpoint();
+
+ 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;
+ PlatformHandle handle(base::win::ScopedHandle(
+ ::CreateFileW(pipe_name.c_str(), kDesiredAccess, 0, nullptr,
+ OPEN_EXISTING, kFlags, nullptr)));
+
+ // 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 " << pipe_name
+ << " could not be opened after WaitNamedPipe succeeded";
+ return PlatformChannelEndpoint(std::move(handle));
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/platform/platform_channel.cc b/mojo/public/cpp/platform/platform_channel.cc
new file mode 100644
index 0000000000..a82910d06a
--- /dev/null
+++ b/mojo/public/cpp/platform/platform_channel.cc
@@ -0,0 +1,282 @@
+// 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.
+
+#include "mojo/public/cpp/platform/platform_channel.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+
+#include "base/win/scoped_handle.h"
+#elif defined(OS_FUCHSIA)
+#include <lib/zx/channel.h>
+#include <zircon/process.h>
+#include <zircon/processargs.h>
+
+#include "base/fuchsia/fuchsia_logging.h"
+#elif defined(OS_POSIX)
+#include <fcntl.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/files/scoped_file.h"
+#include "base/posix/global_descriptors.h"
+#endif
+
+#if defined(OS_POSIX) && !defined(OS_NACL_SFI)
+#include <sys/socket.h>
+#elif defined(OS_NACL_SFI)
+#include "native_client/src/public/imc_syscalls.h"
+#endif
+
+namespace mojo {
+
+namespace {
+
+#if defined(OS_WIN)
+void CreateChannel(PlatformHandle* local_endpoint,
+ PlatformHandle* remote_endpoint) {
+ base::string16 pipe_name = base::UTF8ToUTF16(base::StringPrintf(
+ "\\\\.\\pipe\\mojo.%lu.%lu.%I64u", ::GetCurrentProcessId(),
+ ::GetCurrentThreadId(), base::RandUint64()));
+ DWORD kOpenMode =
+ PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE;
+ const DWORD kPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE;
+ *local_endpoint = PlatformHandle(base::win::ScopedHandle(
+ ::CreateNamedPipeW(pipe_name.c_str(), kOpenMode, kPipeMode,
+ 1, // Max instances.
+ 4096, // Output buffer size.
+ 4096, // Input buffer size.
+ 5000, // Timeout in ms.
+ nullptr))); // Default security descriptor.
+ PCHECK(local_endpoint->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 | FILE_FLAG_OVERLAPPED;
+ // Allow the handle to be inherited by child processes.
+ SECURITY_ATTRIBUTES security_attributes = {sizeof(SECURITY_ATTRIBUTES),
+ nullptr, TRUE};
+ *remote_endpoint = PlatformHandle(base::win::ScopedHandle(
+ ::CreateFileW(pipe_name.c_str(), kDesiredAccess, 0, &security_attributes,
+ OPEN_EXISTING, kFlags, nullptr)));
+ PCHECK(remote_endpoint->is_valid());
+
+ // Since a client has connected, ConnectNamedPipe() should return zero and
+ // GetLastError() should return ERROR_PIPE_CONNECTED.
+ CHECK(!::ConnectNamedPipe(local_endpoint->GetHandle().Get(), nullptr));
+ PCHECK(::GetLastError() == ERROR_PIPE_CONNECTED);
+}
+#elif defined(OS_FUCHSIA)
+void CreateChannel(PlatformHandle* local_endpoint,
+ PlatformHandle* remote_endpoint) {
+ zx::channel handles[2];
+ zx_status_t result = zx::channel::create(0, &handles[0], &handles[1]);
+ ZX_CHECK(result == ZX_OK, result);
+
+ *local_endpoint = PlatformHandle(std::move(handles[0]));
+ *remote_endpoint = PlatformHandle(std::move(handles[1]));
+ DCHECK(local_endpoint->is_valid());
+ DCHECK(remote_endpoint->is_valid());
+}
+#elif defined(OS_POSIX)
+
+#if defined(OS_ANDROID)
+// Leave room for any other descriptors defined in content for example.
+// TODO(https://crbug.com/676442): Consider changing base::GlobalDescriptors to
+// generate a key when setting the file descriptor.
+constexpr int kAndroidClientHandleDescriptor =
+ base::GlobalDescriptors::kBaseDescriptor + 10000;
+#else
+bool IsTargetDescriptorUsed(const base::FileHandleMappingVector& mapping,
+ int target_fd) {
+ for (size_t i = 0; i < mapping.size(); ++i) {
+ if (mapping[i].second == target_fd)
+ return true;
+ }
+ return false;
+}
+#endif
+
+void CreateChannel(PlatformHandle* local_endpoint,
+ PlatformHandle* remote_endpoint) {
+ int fds[2];
+#if defined(OS_NACL_SFI)
+ PCHECK(imc_socketpair(fds) == 0);
+#else
+ PCHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0);
+
+ // Set non-blocking on both ends.
+ PCHECK(fcntl(fds[0], F_SETFL, O_NONBLOCK) == 0);
+ PCHECK(fcntl(fds[1], F_SETFL, O_NONBLOCK) == 0);
+
+#if defined(OS_MACOSX)
+ // This turns off |SIGPIPE| when writing to a closed socket, causing the call
+ // to fail with |EPIPE| instead. On Linux we have to use |send...()| with
+ // |MSG_NOSIGNAL| instead, which is not supported on Mac.
+ 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)
+
+ *local_endpoint = PlatformHandle(base::ScopedFD(fds[0]));
+ *remote_endpoint = PlatformHandle(base::ScopedFD(fds[1]));
+ DCHECK(local_endpoint->is_valid());
+ DCHECK(remote_endpoint->is_valid());
+}
+#else
+#error "Unsupported platform."
+#endif
+
+} // namespace
+
+const char PlatformChannel::kHandleSwitch[] = "mojo-platform-channel-handle";
+
+PlatformChannel::PlatformChannel() {
+ PlatformHandle local_handle;
+ PlatformHandle remote_handle;
+ CreateChannel(&local_handle, &remote_handle);
+ local_endpoint_ = PlatformChannelEndpoint(std::move(local_handle));
+ remote_endpoint_ = PlatformChannelEndpoint(std::move(remote_handle));
+}
+
+PlatformChannel::PlatformChannel(PlatformChannel&& other) = default;
+
+PlatformChannel::~PlatformChannel() = default;
+
+PlatformChannel& PlatformChannel::operator=(PlatformChannel&& other) = default;
+
+void PlatformChannel::PrepareToPassRemoteEndpoint(HandlePassingInfo* info,
+ std::string* value) {
+ DCHECK(info);
+ DCHECK(value);
+ DCHECK(remote_endpoint_.is_valid());
+
+#if defined(OS_WIN)
+ info->push_back(remote_endpoint_.platform_handle().GetHandle().Get());
+ *value = base::NumberToString(
+ HandleToLong(remote_endpoint_.platform_handle().GetHandle().Get()));
+#elif defined(OS_FUCHSIA)
+ const uint32_t id = PA_HND(PA_USER0, info->size());
+ info->push_back({id, remote_endpoint_.platform_handle().GetHandle().get()});
+ *value = base::NumberToString(id);
+#elif defined(OS_ANDROID)
+ int fd = remote_endpoint_.platform_handle().GetFD().get();
+ int mapped_fd = kAndroidClientHandleDescriptor + info->size();
+ info->emplace_back(fd, mapped_fd);
+ *value = base::NumberToString(mapped_fd);
+#elif defined(OS_POSIX)
+ // Arbitrary sanity check to ensure the loop below terminates reasonably
+ // quickly.
+ CHECK_LT(info->size(), 1000u);
+
+ // Find a suitable FD to map the remote endpoint handle to in the child
+ // process. This has quadratic time complexity in the size of |*info|, but
+ // |*info| should be very small and is usually empty.
+ int target_fd = base::GlobalDescriptors::kBaseDescriptor;
+ while (IsTargetDescriptorUsed(*info, target_fd))
+ ++target_fd;
+ info->emplace_back(remote_endpoint_.platform_handle().GetFD().get(),
+ target_fd);
+ *value = base::NumberToString(target_fd);
+#endif
+}
+
+void PlatformChannel::PrepareToPassRemoteEndpoint(
+ HandlePassingInfo* info,
+ base::CommandLine* command_line) {
+ std::string value;
+ PrepareToPassRemoteEndpoint(info, &value);
+ if (!value.empty())
+ command_line->AppendSwitchASCII(kHandleSwitch, value);
+}
+
+void PlatformChannel::PrepareToPassRemoteEndpoint(
+ base::LaunchOptions* options,
+ base::CommandLine* command_line) {
+#if defined(OS_WIN)
+ PrepareToPassRemoteEndpoint(&options->handles_to_inherit, command_line);
+#elif defined(OS_FUCHSIA)
+ PrepareToPassRemoteEndpoint(&options->handles_to_transfer, command_line);
+#elif defined(OS_POSIX)
+ PrepareToPassRemoteEndpoint(&options->fds_to_remap, command_line);
+#else
+#error "Platform not supported."
+#endif
+}
+
+void PlatformChannel::RemoteProcessLaunchAttempted() {
+#if defined(OS_FUCHSIA)
+ // Unlike other platforms, Fuchsia transfers handle ownership to the new
+ // process, rather than duplicating it. For consistency the process-launch
+ // call will have consumed the handle regardless of whether launch succeeded.
+ DCHECK(remote_endpoint_.platform_handle().is_valid_handle());
+ ignore_result(remote_endpoint_.TakePlatformHandle().ReleaseHandle());
+#else
+ remote_endpoint_.reset();
+#endif
+}
+
+// static
+PlatformChannelEndpoint PlatformChannel::RecoverPassedEndpointFromString(
+ base::StringPiece value) {
+#if defined(OS_WIN)
+ int handle_value = 0;
+ if (value.empty() || !base::StringToInt(value, &handle_value)) {
+ DLOG(ERROR) << "Invalid PlatformChannel endpoint string.";
+ return PlatformChannelEndpoint();
+ }
+ return PlatformChannelEndpoint(
+ PlatformHandle(base::win::ScopedHandle(LongToHandle(handle_value))));
+#elif defined(OS_FUCHSIA)
+ unsigned int handle_value = 0;
+ if (value.empty() || !base::StringToUint(value, &handle_value)) {
+ DLOG(ERROR) << "Invalid PlatformChannel endpoint string.";
+ return PlatformChannelEndpoint();
+ }
+ return PlatformChannelEndpoint(PlatformHandle(zx::handle(
+ zx_take_startup_handle(base::checked_cast<uint32_t>(handle_value)))));
+#elif defined(OS_ANDROID)
+ base::GlobalDescriptors::Key key = -1;
+ if (value.empty() || !base::StringToUint(value, &key)) {
+ DLOG(ERROR) << "Invalid PlatformChannel endpoint string.";
+ return PlatformChannelEndpoint();
+ }
+ return PlatformChannelEndpoint(PlatformHandle(
+ base::ScopedFD(base::GlobalDescriptors::GetInstance()->Get(key))));
+#elif defined(OS_POSIX)
+ int fd = -1;
+ if (value.empty() || !base::StringToInt(value, &fd) ||
+ fd < base::GlobalDescriptors::kBaseDescriptor) {
+ DLOG(ERROR) << "Invalid PlatformChannel endpoint string.";
+ return PlatformChannelEndpoint();
+ }
+ return PlatformChannelEndpoint(PlatformHandle(base::ScopedFD(fd)));
+#endif
+}
+
+// static
+PlatformChannelEndpoint PlatformChannel::RecoverPassedEndpointFromCommandLine(
+ const base::CommandLine& command_line) {
+ return RecoverPassedEndpointFromString(
+ command_line.GetSwitchValueASCII(kHandleSwitch));
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/platform/platform_channel.h b/mojo/public/cpp/platform/platform_channel.h
new file mode 100644
index 0000000000..534c6bbfdc
--- /dev/null
+++ b/mojo/public/cpp/platform/platform_channel.h
@@ -0,0 +1,117 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_PLATFORM_PLATFORM_CHANNEL_H_
+#define MOJO_PUBLIC_CPP_PLATFORM_PLATFORM_CHANNEL_H_
+
+#include "base/command_line.h"
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "base/process/launch.h"
+#include "build/build_config.h"
+#include "mojo/public/cpp/platform/platform_channel_endpoint.h"
+
+namespace mojo {
+
+// PlatformChannel encapsulates construction and ownership of two entangled
+// endpoints of a platform-specific communication primitive, e.g. a Windows pipe
+// or a Unix domain socket. One endpoint is designated as the "local" endpoint
+// and should be retained by the creating process; the other endpoint is
+// designated as the "remote" endpoint and should be passed to an external
+// process.
+//
+// PlatformChannels can be used to bootstrap Mojo IPC between one process and
+// another. Typically the other process is a child of this process, and there
+// are helper methods for passing the endpoint to a child as such; but this
+// arrangement is not strictly necessary on all platforms.
+//
+// For a channel which allows clients to connect by name (i.e. a named pipe
+// or socket server, supported only on Windows and POSIX systems) see
+// NamedPlatformChannel.
+class COMPONENT_EXPORT(MOJO_CPP_PLATFORM) PlatformChannel {
+ public:
+ // A common helper constant that is used to pass handle values on the
+ // command line when the relevant methods are used on this class.
+ static const char kHandleSwitch[];
+
+// Unfortunately base process support code has no unified handle-passing
+// data pipe, so we have this.
+#if defined(OS_WIN)
+ using HandlePassingInfo = base::HandlesToInheritVector;
+#elif defined(OS_FUCHSIA)
+ using HandlePassingInfo = base::HandlesToTransferVector;
+#elif defined(OS_POSIX)
+ using HandlePassingInfo = base::FileHandleMappingVector;
+#else
+#error "Unsupported platform."
+#endif
+
+ PlatformChannel();
+ PlatformChannel(PlatformChannel&& other);
+ ~PlatformChannel();
+
+ PlatformChannel& operator=(PlatformChannel&& other);
+
+ const PlatformChannelEndpoint& local_endpoint() const {
+ return local_endpoint_;
+ }
+ const PlatformChannelEndpoint& remote_endpoint() const {
+ return remote_endpoint_;
+ }
+
+ PlatformChannelEndpoint TakeLocalEndpoint() WARN_UNUSED_RESULT {
+ return std::move(local_endpoint_);
+ }
+
+ PlatformChannelEndpoint TakeRemoteEndpoint() WARN_UNUSED_RESULT {
+ return std::move(remote_endpoint_);
+ }
+
+ // Prepares to pass the remote endpoint handle to a process that will soon be
+ // launched. Returns a string that can be used in the remote process with
+ // |RecoverPassedEndpointFromString()| (see below). The string can e.g. be
+ // passed on the new process's command line.
+ //
+ // **NOTE**: If this method is called it is important to also call
+ // |RemoteProcessLaunchAttempted()| on this PlatformChannel *after* attempting
+ // to launch the new process, regardless of whether the attempt succeeded.
+ // Failing to do so can result in leaked handles.
+ void PrepareToPassRemoteEndpoint(HandlePassingInfo* info, std::string* value);
+
+ // Like above but modifies |*command_line| to include the endpoint string
+ // via the |kHandleSwitch| flag.
+ void PrepareToPassRemoteEndpoint(HandlePassingInfo* info,
+ base::CommandLine* command_line);
+
+ // Like above but adds handle-passing information directly to
+ // |*launch_options|, eliminating the potential need for callers to write
+ // platform-specific code to do the same.
+ void PrepareToPassRemoteEndpoint(base::LaunchOptions* options,
+ base::CommandLine* command_line);
+
+ // Must be called after the corresponding process launch attempt if
+ // |PrepareToPassRemoteEndpoint()| was used.
+ void RemoteProcessLaunchAttempted();
+
+ // Recovers an endpoint handle which was passed to the calling process by
+ // its creator. |value| is a string returned by
+ // |PrepareToPassRemoteEndpoint()| in the creator's process.
+ static PlatformChannelEndpoint RecoverPassedEndpointFromString(
+ base::StringPiece value) WARN_UNUSED_RESULT;
+
+ // Like above but extracts the input string from |command_line| via the
+ // |kHandleSwitch| flag.
+ static PlatformChannelEndpoint RecoverPassedEndpointFromCommandLine(
+ const base::CommandLine& command_line) WARN_UNUSED_RESULT;
+
+ private:
+ PlatformChannelEndpoint local_endpoint_;
+ PlatformChannelEndpoint remote_endpoint_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformChannel);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_PLATFORM_PLATFORM_CHANNEL_H_
diff --git a/mojo/public/cpp/platform/platform_channel_endpoint.cc b/mojo/public/cpp/platform/platform_channel_endpoint.cc
new file mode 100644
index 0000000000..0ebc133fff
--- /dev/null
+++ b/mojo/public/cpp/platform/platform_channel_endpoint.cc
@@ -0,0 +1,30 @@
+// 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.
+
+#include "mojo/public/cpp/platform/platform_channel_endpoint.h"
+
+namespace mojo {
+
+PlatformChannelEndpoint::PlatformChannelEndpoint() = default;
+
+PlatformChannelEndpoint::PlatformChannelEndpoint(
+ PlatformChannelEndpoint&& other) = default;
+
+PlatformChannelEndpoint::PlatformChannelEndpoint(PlatformHandle handle)
+ : handle_(std::move(handle)) {}
+
+PlatformChannelEndpoint::~PlatformChannelEndpoint() = default;
+
+PlatformChannelEndpoint& PlatformChannelEndpoint::operator=(
+ PlatformChannelEndpoint&& other) = default;
+
+void PlatformChannelEndpoint::reset() {
+ handle_.reset();
+}
+
+PlatformChannelEndpoint PlatformChannelEndpoint::Clone() const {
+ return PlatformChannelEndpoint(handle_.Clone());
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/platform/platform_channel_endpoint.h b/mojo/public/cpp/platform/platform_channel_endpoint.h
new file mode 100644
index 0000000000..36812be7ff
--- /dev/null
+++ b/mojo/public/cpp/platform/platform_channel_endpoint.h
@@ -0,0 +1,45 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_PLATFORM_PLATFORM_CHANNEL_ENDPOINT_H_
+#define MOJO_PUBLIC_CPP_PLATFORM_PLATFORM_CHANNEL_ENDPOINT_H_
+
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/platform/platform_handle.h"
+
+namespace mojo {
+
+// A PlatformHandle with a little extra type information to convey that it's
+// a channel endpoint, i.e. a handle that can be used to send or receive
+// invitations as |MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL| to a remote
+// PlatformChannelEndpoint.
+class COMPONENT_EXPORT(MOJO_CPP_PLATFORM) PlatformChannelEndpoint {
+ public:
+ PlatformChannelEndpoint();
+ PlatformChannelEndpoint(PlatformChannelEndpoint&& other);
+ explicit PlatformChannelEndpoint(PlatformHandle handle);
+ ~PlatformChannelEndpoint();
+
+ PlatformChannelEndpoint& operator=(PlatformChannelEndpoint&& other);
+
+ bool is_valid() const { return handle_.is_valid(); }
+ void reset();
+ PlatformChannelEndpoint Clone() const;
+
+ const PlatformHandle& platform_handle() const { return handle_; }
+
+ PlatformHandle TakePlatformHandle() WARN_UNUSED_RESULT {
+ return std::move(handle_);
+ }
+
+ private:
+ PlatformHandle handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformChannelEndpoint);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_PLATFORM_PLATFORM_CHANNEL_ENDPOINT_H_
diff --git a/mojo/public/cpp/platform/platform_channel_server_endpoint.cc b/mojo/public/cpp/platform/platform_channel_server_endpoint.cc
new file mode 100644
index 0000000000..72ff2d22f5
--- /dev/null
+++ b/mojo/public/cpp/platform/platform_channel_server_endpoint.cc
@@ -0,0 +1,31 @@
+// 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.
+
+#include "mojo/public/cpp/platform/platform_channel_server_endpoint.h"
+
+namespace mojo {
+
+PlatformChannelServerEndpoint::PlatformChannelServerEndpoint() = default;
+
+PlatformChannelServerEndpoint::PlatformChannelServerEndpoint(
+ PlatformChannelServerEndpoint&& other) = default;
+
+PlatformChannelServerEndpoint::PlatformChannelServerEndpoint(
+ PlatformHandle handle)
+ : handle_(std::move(handle)) {}
+
+PlatformChannelServerEndpoint::~PlatformChannelServerEndpoint() = default;
+
+PlatformChannelServerEndpoint& PlatformChannelServerEndpoint::operator=(
+ PlatformChannelServerEndpoint&& other) = default;
+
+void PlatformChannelServerEndpoint::reset() {
+ handle_.reset();
+}
+
+PlatformChannelServerEndpoint PlatformChannelServerEndpoint::Clone() const {
+ return PlatformChannelServerEndpoint(handle_.Clone());
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/platform/platform_channel_server_endpoint.h b/mojo/public/cpp/platform/platform_channel_server_endpoint.h
new file mode 100644
index 0000000000..f80d9e3b68
--- /dev/null
+++ b/mojo/public/cpp/platform/platform_channel_server_endpoint.h
@@ -0,0 +1,46 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_PLATFORM_PLATFORM_CHANNEL_SERVER_ENDPOINT_H_
+#define MOJO_PUBLIC_CPP_PLATFORM_PLATFORM_CHANNEL_SERVER_ENDPOINT_H_
+
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/platform/platform_handle.h"
+
+namespace mojo {
+
+// A PlatformHandle with a little extra type information to convey that it's
+// a channel server endpoint, i.e. a handle that can be used to send invitations
+// as |MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL_SERVER| to a remote
+// PlatformChannelEndpoint.
+class COMPONENT_EXPORT(MOJO_CPP_PLATFORM) PlatformChannelServerEndpoint {
+ public:
+ PlatformChannelServerEndpoint();
+ PlatformChannelServerEndpoint(PlatformChannelServerEndpoint&& other);
+ explicit PlatformChannelServerEndpoint(PlatformHandle handle);
+ ~PlatformChannelServerEndpoint();
+
+ PlatformChannelServerEndpoint& operator=(
+ PlatformChannelServerEndpoint&& other);
+
+ bool is_valid() const { return handle_.is_valid(); }
+ void reset();
+ PlatformChannelServerEndpoint Clone() const;
+
+ const PlatformHandle& platform_handle() const { return handle_; }
+
+ PlatformHandle TakePlatformHandle() WARN_UNUSED_RESULT {
+ return std::move(handle_);
+ }
+
+ private:
+ PlatformHandle handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformChannelServerEndpoint);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_PLATFORM_PLATFORM_CHANNEL_SERVER_ENDPOINT_H_
diff --git a/mojo/public/cpp/platform/platform_handle.cc b/mojo/public/cpp/platform/platform_handle.cc
new file mode 100644
index 0000000000..106fe48e4c
--- /dev/null
+++ b/mojo/public/cpp/platform/platform_handle.cc
@@ -0,0 +1,252 @@
+// 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.
+
+#include "mojo/public/cpp/platform/platform_handle.h"
+
+#include "base/logging.h"
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+
+#include "base/win/scoped_handle.h"
+#elif defined(OS_FUCHSIA)
+#include <lib/fdio/limits.h>
+#include <unistd.h>
+#include <zircon/status.h>
+
+#include "base/fuchsia/fuchsia_logging.h"
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+#include <mach/mach_vm.h>
+
+#include "base/mac/mach_logging.h"
+#include "base/mac/scoped_mach_port.h"
+#endif
+
+#if defined(OS_POSIX)
+#include <unistd.h>
+
+#include "base/files/scoped_file.h"
+#endif
+
+namespace mojo {
+
+namespace {
+
+#if defined(OS_WIN)
+base::win::ScopedHandle CloneHandle(const base::win::ScopedHandle& handle) {
+ DCHECK(handle.IsValid());
+
+ HANDLE dupe;
+ BOOL result = ::DuplicateHandle(::GetCurrentProcess(), handle.Get(),
+ ::GetCurrentProcess(), &dupe, 0, FALSE,
+ DUPLICATE_SAME_ACCESS);
+ if (!result)
+ return base::win::ScopedHandle();
+ DCHECK_NE(dupe, INVALID_HANDLE_VALUE);
+ return base::win::ScopedHandle(dupe);
+}
+#elif defined(OS_FUCHSIA)
+zx::handle CloneHandle(const zx::handle& handle) {
+ DCHECK(handle.is_valid());
+
+ zx::handle dupe;
+ zx_status_t result = handle.duplicate(ZX_RIGHT_SAME_RIGHTS, &dupe);
+ if (result != ZX_OK)
+ ZX_DLOG(ERROR, result) << "zx_duplicate_handle";
+ return std::move(dupe);
+}
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+base::mac::ScopedMachSendRight CloneMachPort(
+ const base::mac::ScopedMachSendRight& mach_port) {
+ DCHECK(mach_port.is_valid());
+
+ kern_return_t kr = mach_port_mod_refs(mach_task_self(), mach_port.get(),
+ MACH_PORT_RIGHT_SEND, 1);
+ if (kr != KERN_SUCCESS) {
+ MACH_DLOG(ERROR, kr) << "mach_port_mod_refs";
+ return base::mac::ScopedMachSendRight();
+ }
+ return base::mac::ScopedMachSendRight(mach_port.get());
+}
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+base::ScopedFD CloneFD(const base::ScopedFD& fd) {
+ DCHECK(fd.is_valid());
+ return base::ScopedFD(dup(fd.get()));
+}
+#endif
+
+} // namespace
+
+PlatformHandle::PlatformHandle() = default;
+
+PlatformHandle::PlatformHandle(PlatformHandle&& other) {
+ *this = std::move(other);
+}
+
+#if defined(OS_WIN)
+PlatformHandle::PlatformHandle(base::win::ScopedHandle handle)
+ : type_(Type::kHandle), handle_(std::move(handle)) {}
+#elif defined(OS_FUCHSIA)
+PlatformHandle::PlatformHandle(zx::handle handle)
+ : type_(Type::kHandle), handle_(std::move(handle)) {}
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+PlatformHandle::PlatformHandle(base::mac::ScopedMachSendRight mach_port)
+ : type_(Type::kMachPort), mach_port_(std::move(mach_port)) {}
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+PlatformHandle::PlatformHandle(base::ScopedFD fd)
+ : type_(Type::kFd), fd_(std::move(fd)) {
+#if defined(OS_FUCHSIA)
+ DCHECK_LT(fd_.get(), FDIO_MAX_FD);
+#endif
+}
+#endif
+
+PlatformHandle::~PlatformHandle() = default;
+
+PlatformHandle& PlatformHandle::operator=(PlatformHandle&& other) {
+ type_ = other.type_;
+ other.type_ = Type::kNone;
+
+#if defined(OS_WIN)
+ handle_ = std::move(other.handle_);
+#elif defined(OS_FUCHSIA)
+ handle_ = std::move(other.handle_);
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ mach_port_ = std::move(other.mach_port_);
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ fd_ = std::move(other.fd_);
+#endif
+
+ return *this;
+}
+
+// static
+void PlatformHandle::ToMojoPlatformHandle(PlatformHandle handle,
+ MojoPlatformHandle* out_handle) {
+ DCHECK(out_handle);
+ out_handle->struct_size = sizeof(MojoPlatformHandle);
+ if (handle.type_ == Type::kNone) {
+ out_handle->type = MOJO_PLATFORM_HANDLE_TYPE_INVALID;
+ out_handle->value = 0;
+ return;
+ }
+
+ do {
+#if defined(OS_WIN)
+ out_handle->type = MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE;
+ out_handle->value =
+ static_cast<uint64_t>(HandleToLong(handle.TakeHandle().Take()));
+ break;
+#elif defined(OS_FUCHSIA)
+ if (handle.is_handle()) {
+ out_handle->type = MOJO_PLATFORM_HANDLE_TYPE_FUCHSIA_HANDLE;
+ out_handle->value = handle.TakeHandle().release();
+ break;
+ }
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ if (handle.is_mach_port()) {
+ out_handle->type = MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT;
+ out_handle->value =
+ static_cast<uint64_t>(handle.TakeMachPort().release());
+ break;
+ }
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ DCHECK(handle.is_fd());
+ out_handle->type = MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR;
+ out_handle->value = static_cast<uint64_t>(handle.TakeFD().release());
+#endif
+ } while (false);
+
+ // One of the above cases must take ownership of |handle|.
+ DCHECK(!handle.is_valid());
+}
+
+// static
+PlatformHandle PlatformHandle::FromMojoPlatformHandle(
+ const MojoPlatformHandle* handle) {
+ if (handle->struct_size < sizeof(*handle) ||
+ handle->type == MOJO_PLATFORM_HANDLE_TYPE_INVALID) {
+ return PlatformHandle();
+ }
+
+#if defined(OS_WIN)
+ if (handle->type != MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE)
+ return PlatformHandle();
+ return PlatformHandle(
+ base::win::ScopedHandle(LongToHandle(static_cast<long>(handle->value))));
+#elif defined(OS_FUCHSIA)
+ if (handle->type == MOJO_PLATFORM_HANDLE_TYPE_FUCHSIA_HANDLE)
+ return PlatformHandle(zx::handle(handle->value));
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ if (handle->type == MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT) {
+ return PlatformHandle(base::mac::ScopedMachSendRight(
+ static_cast<mach_port_t>(handle->value)));
+ }
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ if (handle->type != MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR)
+ return PlatformHandle();
+ return PlatformHandle(base::ScopedFD(static_cast<int>(handle->value)));
+#endif
+}
+
+void PlatformHandle::reset() {
+ type_ = Type::kNone;
+
+#if defined(OS_WIN)
+ handle_.Close();
+#elif defined(OS_FUCHSIA)
+ handle_.reset();
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ mach_port_.reset();
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ fd_.reset();
+#endif
+}
+
+void PlatformHandle::release() {
+ type_ = Type::kNone;
+
+#if defined(OS_WIN)
+ ignore_result(handle_.Take());
+#elif defined(OS_FUCHSIA)
+ ignore_result(handle_.release());
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ ignore_result(mach_port_.release());
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ ignore_result(fd_.release());
+#endif
+}
+
+PlatformHandle PlatformHandle::Clone() const {
+#if defined(OS_WIN)
+ return PlatformHandle(CloneHandle(handle_));
+#elif defined(OS_FUCHSIA)
+ if (is_valid_handle())
+ return PlatformHandle(CloneHandle(handle_));
+ return PlatformHandle(CloneFD(fd_));
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ if (is_valid_mach_port())
+ return PlatformHandle(CloneMachPort(mach_port_));
+ return PlatformHandle(CloneFD(fd_));
+#elif defined(OS_POSIX)
+ return PlatformHandle(CloneFD(fd_));
+#endif
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/platform/platform_handle.h b/mojo/public/cpp/platform/platform_handle.h
new file mode 100644
index 0000000000..22ea774f56
--- /dev/null
+++ b/mojo/public/cpp/platform/platform_handle.h
@@ -0,0 +1,189 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_PLATFORM_PLATFORM_HANDLE_H_
+#define MOJO_PUBLIC_CPP_PLATFORM_PLATFORM_HANDLE_H_
+
+#include "base/component_export.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "mojo/public/c/system/platform_handle.h"
+
+#if defined(OS_WIN)
+#include "base/win/scoped_handle.h"
+#elif defined(OS_FUCHSIA)
+#include <lib/zx/handle.h>
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+#include "base/mac/scoped_mach_port.h"
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include "base/files/scoped_file.h"
+#endif
+
+namespace mojo {
+
+// A PlatformHandle is a generic wrapper around a platform-specific system
+// handle type, e.g. a POSIX file descriptor or Windows HANDLE. This can wrap
+// any of various such types depending on the host platform for which it's
+// compiled.
+//
+// This is useful primarily for two reasons:
+//
+// - Interacting with the Mojo invitation API, which use OS primitives to
+// bootstrap Mojo IPC connections.
+// - Interacting with Mojo platform handle wrapping and unwrapping API, which
+// allows handles to OS primitives to be transmitted over Mojo IPC with a
+// stable wire representation via Mojo handles.
+//
+// NOTE: This assumes ownership if the handle it represents.
+class COMPONENT_EXPORT(MOJO_CPP_PLATFORM) PlatformHandle {
+ public:
+ enum class Type {
+ kNone,
+#if defined(OS_WIN) || defined(OS_FUCHSIA)
+ kHandle,
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ kMachPort,
+#endif
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ kFd,
+#endif
+ };
+
+ PlatformHandle();
+ PlatformHandle(PlatformHandle&& other);
+
+#if defined(OS_WIN)
+ explicit PlatformHandle(base::win::ScopedHandle handle);
+#elif defined(OS_FUCHSIA)
+ explicit PlatformHandle(zx::handle handle);
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ explicit PlatformHandle(base::mac::ScopedMachSendRight mach_port);
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ explicit PlatformHandle(base::ScopedFD fd);
+#endif
+
+ ~PlatformHandle();
+
+ PlatformHandle& operator=(PlatformHandle&& other);
+
+ // Takes ownership of |handle|'s underlying platform handle and fills in
+ // |mojo_handle| with a representation of it. The caller assumes ownership of
+ // the platform handle.
+ static void ToMojoPlatformHandle(PlatformHandle handle,
+ MojoPlatformHandle* mojo_handle);
+
+ // Closes the underlying platform handle.
+ // Assumes ownership of the platform handle described by |handle|, and returns
+ // it as a new PlatformHandle.
+ static PlatformHandle FromMojoPlatformHandle(
+ const MojoPlatformHandle* handle);
+
+ Type type() const { return type_; }
+
+ void reset();
+
+ // Relinquishes ownership of the underlying handle, regardless of type, and
+ // discards its value. To release and obtain the underlying handle value, use
+ // one of the specific |Release*()| methods below.
+ void release();
+
+ // Duplicates the underlying platform handle, returning a new PlatformHandle
+ // which owns it.
+ PlatformHandle Clone() const;
+
+#if defined(OS_WIN)
+ bool is_valid() const { return is_valid_handle(); }
+ bool is_valid_handle() const { return handle_.IsValid(); }
+ bool is_handle() const { return type_ == Type::kHandle; }
+ const base::win::ScopedHandle& GetHandle() const { return handle_; }
+ base::win::ScopedHandle TakeHandle() {
+ DCHECK_EQ(type_, Type::kHandle);
+ type_ = Type::kNone;
+ return std::move(handle_);
+ }
+ HANDLE ReleaseHandle() WARN_UNUSED_RESULT {
+ DCHECK_EQ(type_, Type::kHandle);
+ type_ = Type::kNone;
+ return handle_.Take();
+ }
+#elif defined(OS_FUCHSIA)
+ bool is_valid() const { return is_valid_fd() || is_valid_handle(); }
+ bool is_valid_handle() const { return handle_.is_valid(); }
+ bool is_handle() const { return type_ == Type::kHandle; }
+ const zx::handle& GetHandle() const { return handle_; }
+ zx::handle TakeHandle() {
+ if (type_ == Type::kHandle)
+ type_ = Type::kNone;
+ return std::move(handle_);
+ }
+ zx_handle_t ReleaseHandle() WARN_UNUSED_RESULT {
+ if (type_ == Type::kHandle)
+ type_ = Type::kNone;
+ return handle_.release();
+ }
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ bool is_valid() const { return is_valid_fd() || is_valid_mach_port(); }
+ bool is_valid_mach_port() const { return mach_port_.is_valid(); }
+ bool is_mach_port() const { return type_ == Type::kMachPort; }
+ const base::mac::ScopedMachSendRight& GetMachPort() const {
+ return mach_port_;
+ }
+ base::mac::ScopedMachSendRight TakeMachPort() {
+ if (type_ == Type::kMachPort)
+ type_ = Type::kNone;
+ return std::move(mach_port_);
+ }
+ mach_port_t ReleaseMachPort() WARN_UNUSED_RESULT {
+ if (type_ == Type::kMachPort)
+ type_ = Type::kNone;
+ return mach_port_.release();
+ }
+#elif defined(OS_POSIX)
+ bool is_valid() const { return is_valid_fd(); }
+#else
+#error "Unsupported platform."
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ bool is_valid_fd() const { return fd_.is_valid(); }
+ bool is_fd() const { return type_ == Type::kFd; }
+ const base::ScopedFD& GetFD() const { return fd_; }
+ base::ScopedFD TakeFD() {
+ if (type_ == Type::kFd)
+ type_ = Type::kNone;
+ return std::move(fd_);
+ }
+ int ReleaseFD() WARN_UNUSED_RESULT {
+ if (type_ == Type::kFd)
+ type_ = Type::kNone;
+ return fd_.release();
+ }
+#endif
+
+ private:
+ Type type_ = Type::kNone;
+
+#if defined(OS_WIN)
+ base::win::ScopedHandle handle_;
+#elif defined(OS_FUCHSIA)
+ zx::handle handle_;
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ base::mac::ScopedMachSendRight mach_port_;
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ base::ScopedFD fd_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformHandle);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_PLATFORM_PLATFORM_HANDLE_H_
diff --git a/mojo/public/cpp/platform/socket_utils_posix.cc b/mojo/public/cpp/platform/socket_utils_posix.cc
new file mode 100644
index 0000000000..4bbdcb754b
--- /dev/null
+++ b/mojo/public/cpp/platform/socket_utils_posix.cc
@@ -0,0 +1,194 @@
+// 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.
+
+#include "mojo/public/cpp/platform/socket_utils_posix.h"
+
+#include <stddef.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#if !defined(OS_NACL)
+#include <sys/uio.h>
+#endif
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "build/build_config.h"
+
+namespace mojo {
+
+namespace {
+
+#if !defined(OS_NACL)
+bool IsRecoverableError() {
+ return errno == ECONNABORTED || errno == EMFILE || errno == ENFILE ||
+ errno == ENOMEM || errno == ENOBUFS;
+}
+
+bool GetPeerEuid(base::PlatformFile fd, uid_t* peer_euid) {
+#if defined(OS_MACOSX) || defined(OS_OPENBSD) || defined(OS_FREEBSD)
+ uid_t socket_euid;
+ gid_t socket_gid;
+ if (getpeereid(fd, &socket_euid, &socket_gid) < 0) {
+ PLOG(ERROR) << "getpeereid " << fd;
+ return false;
+ }
+ *peer_euid = socket_euid;
+ return true;
+#else
+ struct ucred cred;
+ socklen_t cred_len = sizeof(cred);
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &cred_len) < 0) {
+ PLOG(ERROR) << "getsockopt " << fd;
+ return false;
+ }
+ if (static_cast<unsigned>(cred_len) < sizeof(cred)) {
+ NOTREACHED() << "Truncated ucred from SO_PEERCRED?";
+ return false;
+ }
+ *peer_euid = cred.uid;
+ return true;
+#endif
+}
+
+bool IsPeerAuthorized(base::PlatformFile fd) {
+ uid_t peer_euid;
+ if (!GetPeerEuid(fd, &peer_euid))
+ return false;
+ if (peer_euid != geteuid()) {
+ DLOG(ERROR) << "Client euid is not authorized";
+ return false;
+ }
+ return true;
+}
+#endif // !defined(OS_NACL)
+
+// NOTE: On Linux |SIGPIPE| is suppressed by passing |MSG_NOSIGNAL| to
+// |sendmsg()|. On Mac we instead set |SO_NOSIGPIPE| on the socket itself.
+#if defined(OS_MACOSX)
+constexpr int kSendmsgFlags = 0;
+#else
+constexpr int kSendmsgFlags = MSG_NOSIGNAL;
+#endif
+
+constexpr size_t kMaxSendmsgHandles = 128;
+
+} // namespace
+
+ssize_t SocketWrite(base::PlatformFile socket,
+ const void* bytes,
+ size_t num_bytes) {
+#if defined(OS_MACOSX) || defined(OS_NACL_NONSFI)
+ return HANDLE_EINTR(write(socket, bytes, num_bytes));
+#else
+ return send(socket, bytes, num_bytes, kSendmsgFlags);
+#endif
+}
+
+ssize_t SocketWritev(base::PlatformFile socket,
+ struct iovec* iov,
+ size_t num_iov) {
+#if defined(OS_MACOSX)
+ return HANDLE_EINTR(writev(socket, iov, static_cast<int>(num_iov)));
+#else
+ struct msghdr msg = {};
+ msg.msg_iov = iov;
+ msg.msg_iovlen = num_iov;
+ return HANDLE_EINTR(sendmsg(socket, &msg, kSendmsgFlags));
+#endif
+}
+
+ssize_t SendmsgWithHandles(base::PlatformFile socket,
+ struct iovec* iov,
+ size_t num_iov,
+ const std::vector<base::ScopedFD>& descriptors) {
+ DCHECK(iov);
+ DCHECK_GT(num_iov, 0u);
+ DCHECK(!descriptors.empty());
+ DCHECK_LE(descriptors.size(), kMaxSendmsgHandles);
+
+ char cmsg_buf[CMSG_SPACE(kMaxSendmsgHandles * sizeof(int))];
+ struct msghdr msg = {};
+ msg.msg_iov = iov;
+ msg.msg_iovlen = num_iov;
+ msg.msg_control = cmsg_buf;
+ msg.msg_controllen = CMSG_LEN(descriptors.size() * sizeof(int));
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(descriptors.size() * sizeof(int));
+ for (size_t i = 0; i < descriptors.size(); ++i) {
+ DCHECK_GE(descriptors[i].get(), 0);
+ reinterpret_cast<int*>(CMSG_DATA(cmsg))[i] = descriptors[i].get();
+ }
+ return HANDLE_EINTR(sendmsg(socket, &msg, kSendmsgFlags));
+}
+
+ssize_t SocketRecvmsg(base::PlatformFile socket,
+ void* buf,
+ size_t num_bytes,
+ std::vector<base::ScopedFD>* descriptors,
+ bool block) {
+ struct iovec iov = {buf, num_bytes};
+ char cmsg_buf[CMSG_SPACE(kMaxSendmsgHandles * 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(socket, &msg, block ? 0 : MSG_DONTWAIT));
+ if (result < 0)
+ return result;
+
+ if (msg.msg_controllen == 0)
+ return result;
+
+ DCHECK(!(msg.msg_flags & MSG_CTRUNC));
+
+ descriptors->clear();
+ 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<int*>(CMSG_DATA(cmsg));
+ for (size_t i = 0; i < num_fds; ++i) {
+ base::ScopedFD fd(fds[i]);
+ DCHECK(fd.is_valid());
+ descriptors->emplace_back(std::move(fd));
+ }
+ }
+ }
+
+ return result;
+}
+
+bool AcceptSocketConnection(base::PlatformFile server_fd,
+ base::ScopedFD* connection_fd,
+ bool check_peer_user) {
+ DCHECK_GE(server_fd, 0);
+ connection_fd->reset();
+#if defined(OS_NACL)
+ NOTREACHED();
+ return false;
+#else
+ base::ScopedFD accepted_handle(HANDLE_EINTR(accept(server_fd, nullptr, 0)));
+ if (!accepted_handle.is_valid())
+ return IsRecoverableError();
+ if (check_peer_user && !IsPeerAuthorized(accepted_handle.get()))
+ return true;
+ if (!base::SetNonBlocking(accepted_handle.get())) {
+ PLOG(ERROR) << "base::SetNonBlocking() failed " << accepted_handle.get();
+ return true;
+ }
+
+ *connection_fd = std::move(accepted_handle);
+ return true;
+#endif // defined(OS_NACL)
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/platform/socket_utils_posix.h b/mojo/public/cpp/platform/socket_utils_posix.h
new file mode 100644
index 0000000000..e512f1bc80
--- /dev/null
+++ b/mojo/public/cpp/platform/socket_utils_posix.h
@@ -0,0 +1,80 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_PLATFORM_SOCKET_UTILS_POSIX_H_
+#define MOJO_PUBLIC_CPP_PLATFORM_SOCKET_UTILS_POSIX_H_
+
+#include <stddef.h>
+#include <sys/types.h>
+
+#include "base/component_export.h"
+#include "base/files/platform_file.h"
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/macros.h"
+
+struct iovec; // Declared in <sys/uio.h>
+
+namespace mojo {
+
+// NOTE: Functions declared here really don't belong in Mojo, but they exist to
+// support code which used to rely on internal parts of the Mojo implementation
+// and there wasn't a much better home for them. Consider moving them to
+// src/base or something.
+
+// Like |write()| but handles |EINTR| and never raises |SIGPIPE|.
+COMPONENT_EXPORT(MOJO_CPP_PLATFORM)
+ssize_t SocketWrite(base::PlatformFile socket,
+ const void* bytes,
+ size_t num_bytes);
+
+// Like |writev()| but handles |EINTR| and never raises |SIGPIPE|.
+COMPONENT_EXPORT(MOJO_CPP_PLATFORM)
+ssize_t SocketWritev(base::PlatformFile socket,
+ struct iovec* iov,
+ size_t num_iov);
+
+// Wrapper around |sendmsg()| which makes it convenient to send attached file
+// descriptors. All entries in |descriptors| must be valid and |descriptors|
+// must be non-empty.
+//
+// Returns the same value as |sendmsg()|, i.e. -1 on error and otherwise the
+// number of bytes sent. Note that the number of bytes sent may be smaller
+// than the total data in |iov|.
+//
+// Note that regardless of success or failure, descriptors in |descriptors| are
+// not closed.
+COMPONENT_EXPORT(MOJO_CPP_PLATFORM)
+ssize_t SendmsgWithHandles(base::PlatformFile socket,
+ struct iovec* iov,
+ size_t num_iov,
+ const std::vector<base::ScopedFD>& descriptors);
+
+// Like |recvmsg()|, but handles |EINTR|.
+COMPONENT_EXPORT(MOJO_CPP_PLATFORM)
+ssize_t SocketRecvmsg(base::PlatformFile socket,
+ void* buf,
+ size_t num_bytes,
+ std::vector<base::ScopedFD>* descriptors,
+ bool block = false);
+
+// Treats |server_fd| as a socket listening for new connections. Returns |false|
+// if it encounters an unrecoverable error.
+//
+// If a connection wasn't established but the server is still OK, this returns
+// |true| and leaves |*connection_fd| unchanged.
+//
+// If a connection was accepted, this returns |true| and |*connection_fd| is
+// updated with a file descriptor for the new connection.
+//
+// Iff |check_peer_user| is |true|, connecting clients running as a different
+// user from the server (i.e. the calling process) will be rejected.
+COMPONENT_EXPORT(MOJO_CPP_PLATFORM)
+bool AcceptSocketConnection(base::PlatformFile server_fd,
+ base::ScopedFD* connection_fd,
+ bool check_peer_user = true);
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_PLATFORM_SOCKET_UTILS_POSIX_H_
diff --git a/mojo/public/cpp/platform/tests/BUILD.gn b/mojo/public/cpp/platform/tests/BUILD.gn
new file mode 100644
index 0000000000..d90e9f6155
--- /dev/null
+++ b/mojo/public/cpp/platform/tests/BUILD.gn
@@ -0,0 +1,19 @@
+# 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.
+
+source_set("tests") {
+ testonly = true
+
+ sources = [
+ "platform_handle_unittest.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//mojo/public/c/system",
+ "//mojo/public/cpp/platform",
+ "//mojo/public/cpp/system",
+ "//testing/gtest",
+ ]
+}
diff --git a/mojo/public/cpp/platform/tests/platform_handle_unittest.cc b/mojo/public/cpp/platform/tests/platform_handle_unittest.cc
new file mode 100644
index 0000000000..d68733a75d
--- /dev/null
+++ b/mojo/public/cpp/platform/tests/platform_handle_unittest.cc
@@ -0,0 +1,262 @@
+// 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.
+
+#include "mojo/public/cpp/platform/platform_handle.h"
+#include "base/files/file.h"
+#include "base/files/platform_file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "base/rand_util.h"
+#include "base/unguessable_token.h"
+#include "build/build_config.h"
+#include "mojo/public/c/system/platform_handle.h"
+#include "mojo/public/cpp/system/platform_handle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include <mach/mach_vm.h>
+#endif
+
+#if defined(OS_WIN)
+#include "base/win/scoped_handle.h"
+#else
+#include "base/files/scoped_file.h"
+#endif
+
+namespace mojo {
+namespace {
+
+// Different types of platform handles are supported on different platforms.
+// We run all PlatformHandle once for each type of handle available on the
+// target platform.
+enum class HandleType {
+#if defined(OS_WIN) || defined(OS_FUCHSIA)
+ kHandle,
+#endif
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ kFileDescriptor,
+#endif
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ kMachPort,
+#endif
+};
+
+// Different types of test modes we support in order to exercise the available
+// handles types. Fuchsia zx_handle tests and Mac Mach port tests use shared
+// memory for setup and validation. Everything else uses platform files.
+// See |SetUp()| below.
+enum class TestType {
+ kFile,
+ kSharedMemory,
+};
+
+const std::string kTestData = "some data to validate";
+
+class PlatformHandleTest : public testing::Test,
+ public testing::WithParamInterface<HandleType> {
+ public:
+ PlatformHandleTest() = default;
+
+ void SetUp() override {
+ test_type_ = TestType::kFile;
+
+#if defined(OS_FUCHSIA)
+ if (GetParam() == HandleType::kHandle)
+ test_type_ = TestType::kSharedMemory;
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ if (GetParam() == HandleType::kMachPort)
+ test_type_ = TestType::kSharedMemory;
+#endif
+
+ if (test_type_ == TestType::kFile)
+ test_handle_ = SetUpFile();
+#if defined(OS_FUCHSIA) || (defined(OS_MACOSX) && !defined(OS_IOS))
+ else
+ test_handle_ = SetUpSharedMemory();
+#endif
+ }
+
+ // Extracts the contents of a file or shared memory object, given a generic
+ // PlatformHandle wrapping it. Used to verify that a |handle| refers to some
+ // expected platform object.
+ std::string GetObjectContents(PlatformHandle& handle) {
+ if (test_type_ == TestType::kFile)
+ return GetFileContents(handle);
+#if defined(OS_FUCHSIA) || (defined(OS_MACOSX) && !defined(OS_IOS))
+ else
+ return GetSharedMemoryContents(handle);
+#endif
+ NOTREACHED();
+ return std::string();
+ }
+
+ protected:
+ PlatformHandle& test_handle() { return test_handle_; }
+
+ private:
+ // Creates a platform file with some test data in it. Leaves the file open
+ // with cursor positioned at the beginning, and returns it as a generic
+ // PlatformHandle.
+ PlatformHandle SetUpFile() {
+ CHECK(temp_dir_.CreateUniqueTempDir());
+ base::File test_file(temp_dir_.GetPath().AppendASCII("test"),
+ base::File::FLAG_CREATE | base::File::FLAG_WRITE |
+ base::File::FLAG_READ);
+ test_file.WriteAtCurrentPos(kTestData.data(), kTestData.size());
+
+#if defined(OS_WIN)
+ return PlatformHandle(
+ base::win::ScopedHandle(test_file.TakePlatformFile()));
+#else
+ return PlatformHandle(base::ScopedFD(test_file.TakePlatformFile()));
+#endif
+ }
+
+ // Returns the contents of a platform file referenced by |handle|. Used to
+ // verify that |handle| is in fact the platform file handle it's expected to
+ // be. See |GetObjectContents()|.
+ std::string GetFileContents(PlatformHandle& handle) {
+#if defined(OS_WIN)
+ // We must temporarily release ownership of the handle due to how File
+ // interacts with ScopedHandle.
+ base::File file(handle.TakeHandle().Take());
+#else
+ base::File file(handle.GetFD().get());
+#endif
+ std::vector<char> buffer(kTestData.size());
+ file.Read(0, buffer.data(), buffer.size());
+ std::string contents(buffer.begin(), buffer.end());
+
+// Let |handle| retain ownership.
+#if defined(OS_WIN)
+ handle = PlatformHandle(base::win::ScopedHandle(file.TakePlatformFile()));
+#else
+ ignore_result(file.TakePlatformFile());
+#endif
+
+ return contents;
+ }
+
+#if defined(OS_FUCHSIA) || (defined(OS_MACOSX) && !defined(OS_IOS))
+ // Creates a shared memory region with some test data in it. Leaves the
+ // handle open and returns it as a generic PlatformHandle.
+ PlatformHandle SetUpSharedMemory() {
+ auto region = base::UnsafeSharedMemoryRegion::Create(kTestData.size());
+ auto mapping = region.Map();
+ memcpy(mapping.memory(), kTestData.data(), kTestData.size());
+ auto generic_region =
+ base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
+ std::move(region));
+ shm_guid_ = generic_region.GetGUID();
+ return PlatformHandle(generic_region.PassPlatformHandle());
+ }
+
+ // Extracts data stored in a shared memory object referenced by |handle|. Used
+ // to verify that |handle| does in fact reference a shared memory object when
+ // expected. See |GetObjectContents()|.
+ std::string GetSharedMemoryContents(const PlatformHandle& handle) {
+ base::subtle::PlatformSharedMemoryRegion::ScopedPlatformHandle
+ region_handle(
+#if defined(OS_FUCHSIA)
+ handle.GetHandle().get()
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ handle.GetMachPort().get()
+#endif
+ );
+ auto generic_region = base::subtle::PlatformSharedMemoryRegion::Take(
+ std::move(region_handle),
+ base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
+ kTestData.size(), shm_guid_);
+ auto region =
+ base::UnsafeSharedMemoryRegion::Deserialize(std::move(generic_region));
+ auto mapping = region.Map();
+ std::string contents(static_cast<char*>(mapping.memory()),
+ kTestData.size());
+
+ // Let |handle| retain ownership.
+ generic_region = base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
+ std::move(region));
+ region_handle = generic_region.PassPlatformHandle();
+ ignore_result(region_handle.release());
+
+ return contents;
+ }
+#endif // defined(OS_FUCHSIA) || (defined(OS_MACOSX) && !defined(OS_IOS))
+
+ base::ScopedTempDir temp_dir_;
+ TestType test_type_;
+ PlatformHandle test_handle_;
+
+ // Needed to reconstitute a base::PlatformSharedMemoryRegion from an unwrapped
+ // PlatformHandle.
+ base::UnguessableToken shm_guid_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformHandleTest);
+};
+
+TEST_P(PlatformHandleTest, BasicConstruction) {
+ EXPECT_EQ(kTestData, GetObjectContents(test_handle()));
+}
+
+TEST_P(PlatformHandleTest, Move) {
+ EXPECT_EQ(kTestData, GetObjectContents(test_handle()));
+
+ auto new_handle = std::move(test_handle());
+ EXPECT_FALSE(test_handle().is_valid());
+ EXPECT_TRUE(new_handle.is_valid());
+
+ EXPECT_EQ(kTestData, GetObjectContents(new_handle));
+}
+
+TEST_P(PlatformHandleTest, Reset) {
+ auto handle = std::move(test_handle());
+ EXPECT_TRUE(handle.is_valid());
+ handle.reset();
+ EXPECT_FALSE(handle.is_valid());
+}
+
+TEST_P(PlatformHandleTest, Clone) {
+ EXPECT_EQ(kTestData, GetObjectContents(test_handle()));
+
+ auto clone = test_handle().Clone();
+ EXPECT_TRUE(clone.is_valid());
+ EXPECT_EQ(kTestData, GetObjectContents(clone));
+ clone.reset();
+ EXPECT_FALSE(clone.is_valid());
+
+ EXPECT_TRUE(test_handle().is_valid());
+ EXPECT_EQ(kTestData, GetObjectContents(test_handle()));
+}
+
+// This is really testing system library stuff, but we conveniently have all
+// this handle type parameterization already in place here.
+TEST_P(PlatformHandleTest, CStructConversion) {
+ EXPECT_EQ(kTestData, GetObjectContents(test_handle()));
+
+ MojoPlatformHandle c_handle;
+ PlatformHandle::ToMojoPlatformHandle(std::move(test_handle()), &c_handle);
+
+ PlatformHandle handle = PlatformHandle::FromMojoPlatformHandle(&c_handle);
+ EXPECT_EQ(kTestData, GetObjectContents(handle));
+}
+
+INSTANTIATE_TEST_CASE_P(,
+ PlatformHandleTest,
+#if defined(OS_WIN)
+ testing::Values(HandleType::kHandle)
+#elif defined(OS_FUCHSIA)
+ testing::Values(HandleType::kHandle,
+ HandleType::kFileDescriptor)
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ testing::Values(HandleType::kFileDescriptor,
+ HandleType::kMachPort)
+#elif defined(OS_POSIX)
+ testing::Values(HandleType::kFileDescriptor)
+#endif
+ );
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/system/BUILD.gn b/mojo/public/cpp/system/BUILD.gn
index 35087ef6f1..155ac14ab3 100644
--- a/mojo/public/cpp/system/BUILD.gn
+++ b/mojo/public/cpp/system/BUILD.gn
@@ -27,27 +27,45 @@ component("system") {
"buffer.h",
"core.h",
"data_pipe.h",
+ "data_pipe_drainer.cc",
+ "data_pipe_drainer.h",
+ "data_pipe_utils.cc",
+ "data_pipe_utils.h",
+ "file_data_pipe_producer.cc",
+ "file_data_pipe_producer.h",
"functions.h",
"handle.h",
+ "handle_signal_tracker.cc",
+ "handle_signal_tracker.h",
"handle_signals_state.h",
+ "invitation.cc",
+ "invitation.h",
+ "isolated_connection.cc",
+ "isolated_connection.h",
"message.h",
+ "message_pipe.cc",
"message_pipe.h",
"platform_handle.cc",
"platform_handle.h",
+ "scope_to_message_pipe.cc",
+ "scope_to_message_pipe.h",
"simple_watcher.cc",
"simple_watcher.h",
+ "string_data_pipe_producer.cc",
+ "string_data_pipe_producer.h",
"system_export.h",
+ "trap.cc",
+ "trap.h",
"wait.cc",
"wait.h",
"wait_set.cc",
"wait_set.h",
- "watcher.cc",
- "watcher.h",
]
public_deps = [
"//base",
"//mojo/public/c/system",
+ "//mojo/public/cpp/platform",
]
deps = [
":clean_up_old_dylib",
diff --git a/mojo/public/cpp/system/README.md b/mojo/public/cpp/system/README.md
index 782744f0b1..0e6d72df97 100644
--- a/mojo/public/cpp/system/README.md
+++ b/mojo/public/cpp/system/README.md
@@ -1,19 +1,19 @@
-# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo C++ System API
-This document is a subset of the [Mojo documentation](/mojo).
+# Mojo C++ System API
+This document is a subset of the [Mojo documentation](/mojo/README.md).
[TOC]
## Overview
The Mojo C++ System API provides a convenient set of helper classes and
functions for working with Mojo primitives. Unlike the low-level
-[C API](/mojo/public/c/system) (upon which this is built) this library takes
-advantage of C++ language features and common STL and `//base` types to provide
-a slightly more idiomatic interface to the Mojo system layer, making it
+[C API](/mojo/public/c/system/README.md) (upon which this is built) this library
+takes advantage of C++ language features and common STL and `//base` types to
+provide a slightly more idiomatic interface to the Mojo system layer, making it
generally easier to use.
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/cpp/system](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/).
+[//mojo/public/cpp/system](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/README.md).
Note that all API symbols referenced in this document are implicitly in the
top-level `mojo` namespace.
@@ -23,7 +23,8 @@ top-level `mojo` namespace.
All types of Mojo handles in the C API are simply opaque, integral `MojoHandle`
values. The C++ API has more strongly typed wrappers defined for different
handle types: `MessagePipeHandle`, `SharedBufferHandle`,
-`DataPipeConsumerHandle`, `DataPipeProducerHandle`, and `WatcherHandle`.
+`DataPipeConsumerHandle`, `DataPipeProducerHandle`, `TrapHandle`, and
+`InvitationHandle`.
Each of these also has a corresponding, move-only, scoped type for safer usage:
`ScopedMessagePipeHandle`, `ScopedSharedBufferHandle`, and so on. When a scoped
@@ -103,29 +104,19 @@ mojo::ScopedDataPipeConsumerHandle consumer;
mojo::CreateDataPipe(null, &producer, &consumer);
```
-// Reads from a data pipe. See |MojoReadData()| for complete documentation.
-inline MojoResult ReadDataRaw(DataPipeConsumerHandle data_pipe_consumer,
- void* elements,
- uint32_t* num_bytes,
- MojoReadDataFlags flags) {
- return MojoReadData(data_pipe_consumer.value(), elements, num_bytes, flags);
-}
-
-// Begins a two-phase read
C++ helpers which correspond directly to the
-[Data Pipe C API](/mojo/public/c/system#Data-Pipes) for immediate and two-phase
-I/O are provided as well. For example:
+[Data Pipe C API](/mojo/public/c/system/README.md#Data-Pipes) for immediate and
+two-phase I/O are provided as well. For example:
``` cpp
uint32_t num_bytes = 7;
-mojo::WriteDataRaw(producer.get(), "hihihi",
- &num_bytes, MOJO_WRITE_DATA_FLAG_NONE);
+producer.WriteData("hihihi", &num_bytes, MOJO_WRITE_DATA_FLAG_NONE);
// Some time later...
char buffer[64];
uint32_t num_bytes = 64;
-mojo::ReadDataRaw(consumer.get(), buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
+consumer.ReadData(buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
```
See [data_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/data_pipe.h)
@@ -137,7 +128,7 @@ A new shared buffers can be allocated like so:
``` cpp
mojo::ScopedSharedBufferHandle buffer =
- mojo::ScopedSharedBufferHandle::Create(4096);
+ mojo::SharedBufferHandle::Create(4096);
```
This new handle can be cloned arbitrarily many times by using the underlying
@@ -170,14 +161,17 @@ for detailed C++ shared buffer API documentation.
The C++ library provides several helpers for wrapping system handle types.
These are specifically useful when working with a few `//base` types, namely
-`base::PlatformFile` and `base::SharedMemoryHandle`. See
+`base::PlatformFile`, `base::SharedMemoryHandle` (deprecated), and various
+strongly-typed shared memory region types like
+`base::ReadOnlySharedMemoryRegion`. See
[platform_handle.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/platform_handle.h)
for detailed C++ platform handle API documentation.
-## Signals & Watchers
+## Signals & Traps
-For an introduction to the concepts of handle signals and watchers, check out
-the C API's documentation on [Signals & Watchers](/mojo/public/c/system#Signals-Watchers).
+For an introduction to the concepts of handle signals and traps, check out
+the C API's documentation on
+[Signals & Traps](/mojo/public/c/system/README.md#Signals-Traps).
### Querying Signals
@@ -214,10 +208,11 @@ if (message_pipe.handle0->QuerySignalsState().readable()) {
### Watching Handles
The [`mojo::SimpleWatcher`](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/simple_watcher.h)
-class serves as a convenient helper for using the [low-level watcher API](/mojo/public/c/system#Signals-Watchers)
+class serves as a convenient helper for using the
+[low-level traps API](/mojo/public/c/system/README.md#Signals-Traps)
to watch a handle for signaling state changes. A `SimpleWatcher` is bound to a
-single thread and always dispatches its notifications on a
-`base::SingleThreadTaskRunner`.
+single sequence and always dispatches its notifications on a
+`base::SequencedTaskRunner`.
`SimpleWatcher` has two possible modes of operation, selected at construction
time by the `mojo::SimpleWatcher::ArmingPolicy` enum:
@@ -225,8 +220,8 @@ time by the `mojo::SimpleWatcher::ArmingPolicy` enum:
* `MANUAL` mode requires the user to manually call `Arm` and/or `ArmOrNotify`
before any notifications will fire regarding the state of the watched handle.
Every time the notification callback is run, the `SimpleWatcher` must be
- rearmed again before the next one can fire. See
- [Arming a Watcher](/mojo/public/c/system#Arming-a-Watcher) and the
+ rearmed again before the next one can fire. See
+ [Arming a Trap](/mojo/public/c/system/README.md#Arming-a-Trap) and the
documentation in `SimpleWatcher`'s header.
* `AUTOMATIC` mode ensures that the `SimpleWatcher` always either is armed or
@@ -279,22 +274,24 @@ WriteABunchOfStuff(pipe.handle1.get());
## Synchronous Waiting
-The C++ System API defines some utilities to block a calling thread while
+The C++ System API defines some utilities to block a calling sequence while
waiting for one or more handles to change signaling state in an interesting way.
-These threads combine usage of the [low-level Watcher API](/mojo/public/c/system#Signals-Watchers)
+These threads combine usage of the
+[low-level traps API](/mojo/public/c/system/README.md#Signals-Traps)
with common synchronization primitives (namely `base::WaitableEvent`.)
While these API features should be used sparingly, they are sometimes necessary.
-See the documentation in
+See the documentation in
[wait.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait.h)
and [wait_set.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait_set.h)
for a more detailed API reference.
### Waiting On a Single Handle
-The `mojo::Wait` function simply blocks the calling thread until a given signal
-mask is either partially satisfied or fully unsatisfiable on a given handle.
+The `mojo::Wait` function simply blocks the calling sequence until a given
+signal mask is either partially satisfied or fully unsatisfiable on a given
+handle.
``` cpp
mojo::MessagePipe pipe;
@@ -335,7 +332,7 @@ if (ready_index == 0) {
```
Similar to `mojo::Wait`, `mojo::WaitMany` is primarily useful in testing. When
-waiting on multiple handles in production code, you should almost always instead
+waiting on multiple handles in production code, you should almost always instead
use a more efficient and more flexible `mojo::WaitSet` as described in the next
section.
@@ -352,9 +349,9 @@ set of (not-owned) Mojo handles and `base::WaitableEvent`s, which may be
explicitly added to or removed from the set at any time.
The `WaitSet` may be waited upon repeatedly, each time blocking the calling
-thread until either one of the handles attains an interesting signaling state or
-one of the events is signaled. For example let's suppose we want to wait up to 5
-seconds for either one of two handles to become readable:
+sequence until either one of the handles attains an interesting signaling state
+or one of the events is signaled. For example let's suppose we want to wait up
+to 5 seconds for either one of two handles to become readable:
``` cpp
base::WaitableEvent timeout_event(
@@ -394,3 +391,150 @@ if (num_ready_handles > 0) {
// signaling races with timeout, both things might be true.
}
```
+
+## Invitations
+Invitations are the means by which two processes can have Mojo IPC bootstrapped
+between them. An invitation must be transmitted over some platform-specific IPC
+primitive (*e.g.* a Windows named pipe or UNIX domain socket), and the public
+[platform support library](/mojo/public/cpp/platform/README.md) provides some
+lightweight, cross-platform abstractions for those primitives.
+
+For any two processes looking to be connected, one must send an
+`OutgoingInvitation` and the other must accept an `IncomingInvitation`. The
+sender can attach named message pipe handles to the `OutgoingInvitation`, and
+the receiver can extract them from its `IncomingInvitation`.
+
+Basic usage might look something like this in the case where one process is
+responsible for launching the other.
+
+``` cpp
+#include "base/command_line.h"
+#include "base/process/launch.h"
+#include "mojo/public/cpp/platform/platform_channel.h"
+#include "mojo/public/cpp/system/invitation.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+mojo::ScopedMessagePipeHandle LaunchAndConnectSomething() {
+ // Under the hood, this is essentially always an OS pipe (domain socket pair,
+ // Windows named pipe, Fuchsia channel, etc).
+ mojo::PlatformChannel channel;
+
+ mojo::OutgoingInvitation invitation;
+
+ // Attach a message pipe to be extracted by the receiver. The other end of the
+ // pipe is returned for us to use locally.
+ mojo::ScopedMessagePipeHandle pipe =
+ invitation->AttachMessagePipe("arbitrary pipe name");
+
+ base::LaunchOptions options;
+ base::CommandLine command_line("some_executable")
+ channel.PrepareToPassRemoteEndpoint(&options, &command_line);
+ base::Process child_process = base::LaunchProcess(command_line, options);
+ channel.RemoteProcessLaunchAttempted();
+
+ OutgoingInvitation::Send(std::move(invitation), child_process.Handle(),
+ channel.TakeLocalEndpoint());
+ return pipe;
+}
+```
+
+The launched process can in turn accept an `IncomingInvitation`:
+
+``` cpp
+#include "base/command_line.h"
+#include "base/threading/thread.h"
+#include "mojo/core/embedder/embedder.h"
+#include "mojo/core/embedder/scoped_ipc_support.h"
+#include "mojo/public/cpp/platform/platform_channel.h"
+#include "mojo/public/cpp/system/invitation.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+int main(int argc, char** argv) {
+ // Basic Mojo initialization for a new process.
+ mojo::core::Init();
+ base::Thread ipc_thread("ipc!");
+ ipc_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
+ mojo::core::ScopedIPCSupport ipc_support(
+ ipc_thread.task_runner(),
+ mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);
+
+ // Accept an invitation.
+ mojo::IncomingInvitation invitation = mojo::IncomingInvitation::Accept(
+ mojo::PlatformChannel::RecoverPassedEndpointFromCommandLine(
+ *base::CommandLine::ForCurrentProcess()));
+ mojo::ScopedMessagePipeHandle pipe =
+ invitation->ExtractMessagePipe("arbitrary pipe name");
+
+ // etc...
+ return GoListenForMessagesAndRunForever(std::move(pipe));
+}
+```
+
+Now we have IPC initialized between the two processes.
+
+Also keep in mind that bindings interfaces are just message pipes with some
+semantic and syntactic sugar wrapping them, so you can use these primordial
+message pipe handles as mojom interfaces. For example:
+
+``` cpp
+// Process A
+mojo::OutgoingInvitation invitation;
+auto pipe = invitation->AttachMessagePipe("x");
+mojo::Binding<foo::mojom::Bar> binding(
+ &bar_impl,
+ foo::mojom::BarRequest(std::move(pipe)));
+
+// Process B
+auto invitation = mojo::IncomingInvitation::Accept(...);
+auto pipe = invitation->ExtractMessagePipe("x");
+foo::mojom::BarPtr bar(foo::mojom::BarPtrInfo(std::move(pipe), 0));
+
+// Will asynchronously invoke bar_impl.DoSomething() in process A.
+bar->DoSomething();
+```
+
+And just to be sure, the usage here could be reversed: the invitation sender
+could just as well treat its pipe endpoint as a `BarPtr` while the receiver
+treats theirs as a `BarRequest` to be bound.
+
+### Process Networks
+Accepting an invitation admits the accepting process into the sender's connected
+network of processes. Once this is done, it's possible for the newly admitted
+process to establish communication with any other process in that network via
+normal message pipe passing.
+
+This does not mean that the invited process can proactively locate and connect
+to other processes without assistance; rather it means that Mojo handles created
+by the process can safely be transferred to any other process in the network
+over established message pipes, and similarly that Mojo handles created by any
+other process in the network can be safely passed to the newly admitted process.
+
+### Invitation Restrictions
+A process may only belong to a single network at a time.
+
+Additionally, once a process has joined a network, it cannot join another for
+the remainder of its lifetime even if it has lost the connection to its original
+network. This restriction will soon be lifted, but for now developers must be
+mindful of it when authoring any long-running daemon process that will accept an
+incoming invitation.
+
+### Isolated Invitations
+It is possible to have two independent networks of Mojo-connected processes; for
+example, a long-running system daemon which uses Mojo to talk to child processes
+of its own, as well as the Chrome browser process running with no common
+ancestor, talking to its own child processes.
+
+In this scenario it may be desirable to have a process in one network talk to a
+process in the other network. Normal invitations cannot be used here since both
+processes already belong to a network. In this case, an **isolated** invitation
+can be used. These work just like regular invitations, except the sender must
+call `OutgoingInvitation::SendIsolated` and the receiver must call
+`IncomingInvitation::AcceptIsolated`.
+
+Once a connection is established via isolated invitation, Mojo IPC can be used
+normally, with the exception that transitive process connections are not
+supported; that is, if process A sends a message pipe handle to process B via
+an isolated connection, process B cannot reliably send that pipe handle onward
+to another process in its own network. Isolated invitations therefore may only
+be used to facilitate direct 1:1 communication between two processes.
diff --git a/mojo/public/cpp/system/buffer.cc b/mojo/public/cpp/system/buffer.cc
index 49f45d8498..81f6abfa6d 100644
--- a/mojo/public/cpp/system/buffer.cc
+++ b/mojo/public/cpp/system/buffer.cc
@@ -8,10 +8,10 @@ namespace mojo {
// static
ScopedSharedBufferHandle SharedBufferHandle::Create(uint64_t num_bytes) {
- MojoCreateSharedBufferOptions options = {
- sizeof(options), MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE};
+ MojoCreateSharedBufferOptions options = {sizeof(options),
+ MOJO_CREATE_SHARED_BUFFER_FLAG_NONE};
SharedBufferHandle handle;
- MojoCreateSharedBuffer(&options, num_bytes, handle.mutable_value());
+ MojoCreateSharedBuffer(num_bytes, &options, handle.mutable_value());
return MakeScopedHandle(handle);
}
@@ -22,9 +22,9 @@ ScopedSharedBufferHandle SharedBufferHandle::Clone(
return result;
MojoDuplicateBufferHandleOptions options = {
- sizeof(options), MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE};
+ sizeof(options), MOJO_DUPLICATE_BUFFER_HANDLE_FLAG_NONE};
if (access_mode == AccessMode::READ_ONLY)
- options.flags |= MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY;
+ options.flags |= MOJO_DUPLICATE_BUFFER_HANDLE_FLAG_READ_ONLY;
SharedBufferHandle result_handle;
MojoDuplicateBufferHandle(value(), &options, result_handle.mutable_value());
result.reset(result_handle);
@@ -39,8 +39,16 @@ ScopedSharedBufferMapping SharedBufferHandle::MapAtOffset(
uint64_t size,
uint64_t offset) const {
void* buffer = nullptr;
- MojoMapBuffer(value(), offset, size, &buffer, MOJO_MAP_BUFFER_FLAG_NONE);
+ MojoMapBuffer(value(), offset, size, nullptr, &buffer);
return ScopedSharedBufferMapping(buffer);
}
+uint64_t SharedBufferHandle::GetSize() const {
+ MojoSharedBufferInfo buffer_info;
+ buffer_info.struct_size = sizeof(buffer_info);
+ return MojoGetBufferInfo(value(), nullptr, &buffer_info) == MOJO_RESULT_OK
+ ? buffer_info.size
+ : 0;
+}
+
} // namespace mojo
diff --git a/mojo/public/cpp/system/buffer.h b/mojo/public/cpp/system/buffer.h
index 1ae923cb75..d72d197650 100644
--- a/mojo/public/cpp/system/buffer.h
+++ b/mojo/public/cpp/system/buffer.h
@@ -42,8 +42,7 @@ typedef ScopedHandleBase<SharedBufferHandle> ScopedSharedBufferHandle;
// A strongly-typed representation of a |MojoHandle| referring to a shared
// buffer.
-class MOJO_CPP_SYSTEM_EXPORT SharedBufferHandle
- : NON_EXPORTED_BASE(public Handle) {
+class MOJO_CPP_SYSTEM_EXPORT SharedBufferHandle : public Handle {
public:
enum class AccessMode {
READ_WRITE,
@@ -61,7 +60,7 @@ class MOJO_CPP_SYSTEM_EXPORT SharedBufferHandle
// Clones this shared buffer handle. If |access_mode| is READ_ONLY or this is
// a read-only handle, the new handle will be read-only. On failure, this will
// return an empty result.
- ScopedSharedBufferHandle Clone(AccessMode = AccessMode::READ_WRITE) const;
+ ScopedSharedBufferHandle Clone(AccessMode access_mode) const;
// Maps |size| bytes of this shared buffer. On failure, this will return a
// null mapping.
@@ -70,6 +69,9 @@ class MOJO_CPP_SYSTEM_EXPORT SharedBufferHandle
// Maps |size| bytes of this shared buffer, starting |offset| bytes into the
// buffer. On failure, this will return a null mapping.
ScopedSharedBufferMapping MapAtOffset(uint64_t size, uint64_t offset) const;
+
+ // Get the size of this shared buffer.
+ uint64_t GetSize() const;
};
static_assert(sizeof(SharedBufferHandle) == sizeof(Handle),
diff --git a/mojo/public/cpp/system/data_pipe.h b/mojo/public/cpp/system/data_pipe.h
index 0dbc3c74e5..30c7546461 100644
--- a/mojo/public/cpp/system/data_pipe.h
+++ b/mojo/public/cpp/system/data_pipe.h
@@ -28,6 +28,33 @@ class DataPipeProducerHandle : public Handle {
DataPipeProducerHandle() {}
explicit DataPipeProducerHandle(MojoHandle value) : Handle(value) {}
+ // Writes to a data pipe. See |MojoWriteData| for complete documentation.
+ MojoResult WriteData(const void* elements,
+ uint32_t* num_bytes,
+ MojoWriteDataFlags flags) const {
+ MojoWriteDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = flags;
+ return MojoWriteData(value(), elements, num_bytes, &options);
+ }
+
+ // Begins a two-phase write to a data pipe. See |MojoBeginWriteData()| for
+ // complete documentation.
+ MojoResult BeginWriteData(void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoBeginWriteDataFlags flags) const {
+ MojoBeginWriteDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = flags;
+ return MojoBeginWriteData(value(), &options, buffer, buffer_num_bytes);
+ }
+
+ // Completes a two-phase write to a data pipe. See |MojoEndWriteData()| for
+ // complete documentation.
+ MojoResult EndWriteData(uint32_t num_bytes_written) const {
+ return MojoEndWriteData(value(), num_bytes_written, nullptr);
+ }
+
// Copying and assignment allowed.
};
@@ -46,6 +73,33 @@ class DataPipeConsumerHandle : public Handle {
DataPipeConsumerHandle() {}
explicit DataPipeConsumerHandle(MojoHandle value) : Handle(value) {}
+ // Reads from a data pipe. See |MojoReadData()| for complete documentation.
+ MojoResult ReadData(void* elements,
+ uint32_t* num_bytes,
+ MojoReadDataFlags flags) const {
+ MojoReadDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = flags;
+ return MojoReadData(value(), &options, elements, num_bytes);
+ }
+
+ // Begins a two-phase read from a data pipe. See |MojoBeginReadData()| for
+ // complete documentation.
+ MojoResult BeginReadData(const void** buffer,
+ uint32_t* buffer_num_bytes,
+ MojoBeginReadDataFlags flags) const {
+ MojoBeginReadDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = flags;
+ return MojoBeginReadData(value(), &options, buffer, buffer_num_bytes);
+ }
+
+ // Completes a two-phase read from a data pipe. See |MojoEndReadData()| for
+ // complete documentation.
+ MojoResult EndReadData(uint32_t num_bytes_read) const {
+ return MojoEndReadData(value(), num_bytes_read, nullptr);
+ }
+
// Copying and assignment allowed.
};
@@ -77,56 +131,6 @@ inline MojoResult CreateDataPipe(
return rv;
}
-// Writes to a data pipe. See |MojoWriteData| for complete documentation.
-inline MojoResult WriteDataRaw(DataPipeProducerHandle data_pipe_producer,
- const void* elements,
- uint32_t* num_bytes,
- MojoWriteDataFlags flags) {
- return MojoWriteData(data_pipe_producer.value(), elements, num_bytes, flags);
-}
-
-// Begins a two-phase write to a data pipe. See |MojoBeginWriteData()| for
-// complete documentation.
-inline MojoResult BeginWriteDataRaw(DataPipeProducerHandle data_pipe_producer,
- void** buffer,
- uint32_t* buffer_num_bytes,
- MojoWriteDataFlags flags) {
- return MojoBeginWriteData(
- data_pipe_producer.value(), buffer, buffer_num_bytes, flags);
-}
-
-// Completes a two-phase write to a data pipe. See |MojoEndWriteData()| for
-// complete documentation.
-inline MojoResult EndWriteDataRaw(DataPipeProducerHandle data_pipe_producer,
- uint32_t num_bytes_written) {
- return MojoEndWriteData(data_pipe_producer.value(), num_bytes_written);
-}
-
-// Reads from a data pipe. See |MojoReadData()| for complete documentation.
-inline MojoResult ReadDataRaw(DataPipeConsumerHandle data_pipe_consumer,
- void* elements,
- uint32_t* num_bytes,
- MojoReadDataFlags flags) {
- return MojoReadData(data_pipe_consumer.value(), elements, num_bytes, flags);
-}
-
-// Begins a two-phase read from a data pipe. See |MojoBeginReadData()| for
-// complete documentation.
-inline MojoResult BeginReadDataRaw(DataPipeConsumerHandle data_pipe_consumer,
- const void** buffer,
- uint32_t* buffer_num_bytes,
- MojoReadDataFlags flags) {
- return MojoBeginReadData(
- data_pipe_consumer.value(), buffer, buffer_num_bytes, flags);
-}
-
-// Completes a two-phase read from a data pipe. See |MojoEndReadData()| for
-// complete documentation.
-inline MojoResult EndReadDataRaw(DataPipeConsumerHandle data_pipe_consumer,
- uint32_t num_bytes_read) {
- return MojoEndReadData(data_pipe_consumer.value(), num_bytes_read);
-}
-
// A wrapper class that automatically creates a data pipe and owns both handles.
// TODO(vtl): Make an even more friendly version? (Maybe templatized for a
// particular type instead of some "element"? Maybe functions that take
@@ -134,6 +138,7 @@ inline MojoResult EndReadDataRaw(DataPipeConsumerHandle data_pipe_consumer,
class DataPipe {
public:
DataPipe();
+ explicit DataPipe(uint32_t capacity_num_bytes);
explicit DataPipe(const MojoCreateDataPipeOptions& options);
~DataPipe();
@@ -148,6 +153,19 @@ inline DataPipe::DataPipe() {
DCHECK_EQ(MOJO_RESULT_OK, result);
}
+inline DataPipe::DataPipe(uint32_t capacity_num_bytes) {
+ MojoCreateDataPipeOptions options;
+ options.struct_size = sizeof(MojoCreateDataPipeOptions);
+ options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
+ options.element_num_bytes = 1;
+ options.capacity_num_bytes = capacity_num_bytes;
+ mojo::DataPipe data_pipe(options);
+ MojoResult result =
+ CreateDataPipe(&options, &producer_handle, &consumer_handle);
+ ALLOW_UNUSED_LOCAL(result);
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+}
+
inline DataPipe::DataPipe(const MojoCreateDataPipeOptions& options) {
MojoResult result =
CreateDataPipe(&options, &producer_handle, &consumer_handle);
diff --git a/mojo/public/cpp/system/data_pipe_drainer.cc b/mojo/public/cpp/system/data_pipe_drainer.cc
new file mode 100644
index 0000000000..27b995a2a7
--- /dev/null
+++ b/mojo/public/cpp/system/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/public/cpp/system/data_pipe_drainer.h"
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "base/bind.h"
+
+namespace mojo {
+
+DataPipeDrainer::DataPipeDrainer(Client* client,
+ mojo::ScopedDataPipeConsumerHandle source)
+ : client_(client),
+ source_(std::move(source)),
+ handle_watcher_(FROM_HERE,
+ SimpleWatcher::ArmingPolicy::AUTOMATIC,
+ base::SequencedTaskRunnerHandle::Get()),
+ 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 =
+ source_->BeginReadData(&buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
+ if (rv == MOJO_RESULT_OK) {
+ client_->OnDataAvailable(buffer, num_bytes);
+ source_->EndReadData(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 mojo
diff --git a/mojo/public/cpp/system/data_pipe_drainer.h b/mojo/public/cpp/system/data_pipe_drainer.h
new file mode 100644
index 0000000000..209d93d705
--- /dev/null
+++ b/mojo/public/cpp/system/data_pipe_drainer.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_DRAINER_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_DRAINER_H_
+
+#include <stddef.h>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+#include "mojo/public/cpp/system/system_export.h"
+
+namespace mojo {
+
+class MOJO_CPP_SYSTEM_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<DataPipeDrainer> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DataPipeDrainer);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_DRAINER_H_
diff --git a/mojo/public/cpp/system/data_pipe_utils.cc b/mojo/public/cpp/system/data_pipe_utils.cc
new file mode 100644
index 0000000000..77f79d31bb
--- /dev/null
+++ b/mojo/public/cpp/system/data_pipe_utils.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 <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "mojo/public/cpp/system/data_pipe_utils.h"
+#include "mojo/public/cpp/system/wait.h"
+
+namespace mojo {
+namespace {
+
+bool BlockingCopyHelper(
+ ScopedDataPipeConsumerHandle source,
+ const base::Callback<size_t(const void*, uint32_t)>& write_bytes) {
+ for (;;) {
+ const void* buffer;
+ uint32_t num_bytes;
+ MojoResult result =
+ source->BeginReadData(&buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
+ if (result == MOJO_RESULT_OK) {
+ size_t bytes_written = write_bytes.Run(buffer, num_bytes);
+ result = source->EndReadData(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<const char*>(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_CPP_SYSTEM_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 = destination->BeginWriteData(&buffer, &buffer_num_bytes,
+ MOJO_WRITE_DATA_FLAG_NONE);
+ if (result == MOJO_RESULT_OK) {
+ char* char_buffer = static_cast<char*>(buffer);
+ uint32_t byte_index = 0;
+ while (it != source.end() && byte_index < buffer_num_bytes) {
+ char_buffer[byte_index++] = *it++;
+ }
+ destination->EndWriteData(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 mojo
diff --git a/mojo/public/cpp/system/data_pipe_utils.h b/mojo/public/cpp/system/data_pipe_utils.h
new file mode 100644
index 0000000000..d0d6f521e3
--- /dev/null
+++ b/mojo/public/cpp/system/data_pipe_utils.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_UTILS_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_UTILS_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "mojo/public/cpp/system/system_export.h"
+
+namespace mojo {
+
+// 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_CPP_SYSTEM_EXPORT
+BlockingCopyToString(ScopedDataPipeConsumerHandle source,
+ std::string* contents);
+
+bool MOJO_CPP_SYSTEM_EXPORT
+BlockingCopyFromString(const std::string& source,
+ const ScopedDataPipeProducerHandle& destination);
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_UTILS_H_
diff --git a/mojo/public/cpp/system/file_data_pipe_producer.cc b/mojo/public/cpp/system/file_data_pipe_producer.cc
new file mode 100644
index 0000000000..6038bbe16b
--- /dev/null
+++ b/mojo/public/cpp/system/file_data_pipe_producer.cc
@@ -0,0 +1,289 @@
+// 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/system/file_data_pipe_producer.h"
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/memory/ref_counted_delete_on_sequence.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/sequenced_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+
+namespace mojo {
+
+namespace {
+
+// No good reason not to attempt very large pipe transactions in case the data
+// pipe in use has a very large capacity available, so we default to trying
+// 64 MB chunks whenever a producer is writable.
+constexpr uint32_t kDefaultMaxReadSize = 64 * 1024 * 1024;
+
+MojoResult FileErrorToMojoResult(base::File::Error error) {
+ switch (error) {
+ case base::File::FILE_OK:
+ return MOJO_RESULT_OK;
+ case base::File::FILE_ERROR_NOT_FOUND:
+ return MOJO_RESULT_NOT_FOUND;
+ case base::File::FILE_ERROR_SECURITY:
+ case base::File::FILE_ERROR_ACCESS_DENIED:
+ return MOJO_RESULT_PERMISSION_DENIED;
+ case base::File::FILE_ERROR_TOO_MANY_OPENED:
+ case base::File::FILE_ERROR_NO_MEMORY:
+ return MOJO_RESULT_RESOURCE_EXHAUSTED;
+ case base::File::FILE_ERROR_ABORT:
+ return MOJO_RESULT_ABORTED;
+ default:
+ return MOJO_RESULT_UNKNOWN;
+ }
+}
+
+} // namespace
+
+class FileDataPipeProducer::FileSequenceState
+ : public base::RefCountedDeleteOnSequence<FileSequenceState> {
+ public:
+ using CompletionCallback =
+ base::OnceCallback<void(ScopedDataPipeProducerHandle producer,
+ MojoResult result)>;
+
+ FileSequenceState(
+ ScopedDataPipeProducerHandle producer_handle,
+ scoped_refptr<base::SequencedTaskRunner> file_task_runner,
+ CompletionCallback callback,
+ scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
+ std::unique_ptr<Observer> observer)
+ : base::RefCountedDeleteOnSequence<FileSequenceState>(
+ std::move(file_task_runner)),
+ callback_task_runner_(std::move(callback_task_runner)),
+ producer_handle_(std::move(producer_handle)),
+ callback_(std::move(callback)),
+ observer_(std::move(observer)) {}
+
+ void Cancel() {
+ base::AutoLock lock(lock_);
+ is_cancelled_ = true;
+ }
+
+ void StartFromFile(base::File file, size_t max_bytes) {
+ owning_task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&FileSequenceState::StartFromFileOnFileSequence, this,
+ std::move(file), max_bytes));
+ }
+
+ void StartFromPath(const base::FilePath& path) {
+ owning_task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&FileSequenceState::StartFromPathOnFileSequence, this,
+ path));
+ }
+
+ private:
+ friend class base::DeleteHelper<FileSequenceState>;
+ friend class base::RefCountedDeleteOnSequence<FileSequenceState>;
+
+ ~FileSequenceState() = default;
+
+ void StartFromFileOnFileSequence(base::File file, size_t max_bytes) {
+ if (file.error_details() != base::File::FILE_OK) {
+ Finish(FileErrorToMojoResult(file.error_details()));
+ return;
+ }
+ file_ = std::move(file);
+ max_bytes_ = max_bytes;
+ TransferSomeBytes();
+ if (producer_handle_.is_valid()) {
+ // If we didn't nail it all on the first transaction attempt, setup a
+ // watcher and complete the read asynchronously.
+ watcher_ = std::make_unique<SimpleWatcher>(
+ FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC,
+ base::SequencedTaskRunnerHandle::Get());
+ watcher_->Watch(producer_handle_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
+ MOJO_WATCH_CONDITION_SATISFIED,
+ base::Bind(&FileSequenceState::OnHandleReady, this));
+ }
+ }
+
+ void StartFromPathOnFileSequence(const base::FilePath& path) {
+ StartFromFileOnFileSequence(
+ base::File(path, base::File::FLAG_OPEN | base::File::FLAG_READ),
+ std::numeric_limits<size_t>::max());
+ }
+
+ void OnHandleReady(MojoResult result, const HandleSignalsState& state) {
+ {
+ // Stop ourselves from doing redundant work if we've been cancelled from
+ // another thread. Note that we do not rely on this for any kind of thread
+ // safety concerns.
+ base::AutoLock lock(lock_);
+ if (is_cancelled_)
+ return;
+ }
+
+ if (result != MOJO_RESULT_OK) {
+ // Either the consumer pipe has been closed or something terrible
+ // happened. In any case, we'll never be able to write more data.
+ Finish(result);
+ return;
+ }
+
+ TransferSomeBytes();
+ }
+
+ void TransferSomeBytes() {
+ while (true) {
+ // Lock as much of the pipe as we can.
+ void* pipe_buffer;
+ uint32_t size = kDefaultMaxReadSize;
+ MojoResult result = producer_handle_->BeginWriteData(
+ &pipe_buffer, &size, MOJO_WRITE_DATA_FLAG_NONE);
+ if (result == MOJO_RESULT_SHOULD_WAIT)
+ return;
+ if (result != MOJO_RESULT_OK) {
+ Finish(result);
+ return;
+ }
+
+ // Attempt to read that many bytes from the file, directly into the data
+ // pipe. Note that while |max_bytes_remaining| may be very large, the
+ // length we attempt read is bounded by the much smaller
+ // |kDefaultMaxReadSize| via |size|.
+ DCHECK(base::IsValueInRangeForNumericType<int>(size));
+ const size_t max_bytes_remaining = max_bytes_ - bytes_transferred_;
+ int attempted_read_size = static_cast<int>(
+ std::min(static_cast<size_t>(size), max_bytes_remaining));
+ int read_size = file_.ReadAtCurrentPos(static_cast<char*>(pipe_buffer),
+ attempted_read_size);
+ base::File::Error read_error;
+ if (read_size < 0) {
+ read_error = base::File::GetLastFileError();
+ DCHECK_NE(base::File::FILE_OK, read_error);
+ if (observer_)
+ observer_->OnBytesRead(pipe_buffer, 0u, read_error);
+ } else {
+ read_error = base::File::FILE_OK;
+ if (observer_) {
+ observer_->OnBytesRead(pipe_buffer, static_cast<size_t>(read_size),
+ base::File::FILE_OK);
+ }
+ }
+ producer_handle_->EndWriteData(
+ read_size >= 0 ? static_cast<uint32_t>(read_size) : 0);
+
+ if (read_size < 0) {
+ Finish(FileErrorToMojoResult(read_error));
+ return;
+ }
+
+ bytes_transferred_ += read_size;
+ DCHECK_LE(bytes_transferred_, max_bytes_);
+
+ if (read_size < attempted_read_size) {
+ // ReadAtCurrentPos makes a best effort to read all requested bytes. We
+ // reasonably assume if it fails to read what we ask for, we've hit EOF.
+ Finish(MOJO_RESULT_OK);
+ return;
+ }
+
+ if (bytes_transferred_ == max_bytes_) {
+ // We've read as much as we were asked to read.
+ Finish(MOJO_RESULT_OK);
+ return;
+ }
+ }
+ }
+
+ void Finish(MojoResult result) {
+ if (observer_) {
+ observer_->OnDoneReading();
+ observer_ = nullptr;
+ }
+ watcher_.reset();
+ callback_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback_),
+ std::move(producer_handle_), result));
+ }
+
+ const scoped_refptr<base::SequencedTaskRunner> callback_task_runner_;
+
+ // State which is effectively owned and used only on the file sequence.
+ ScopedDataPipeProducerHandle producer_handle_;
+ base::File file_;
+ size_t max_bytes_ = 0;
+ size_t bytes_transferred_ = 0;
+ CompletionCallback callback_;
+ std::unique_ptr<SimpleWatcher> watcher_;
+
+ // Guards |is_cancelled_|.
+ base::Lock lock_;
+ bool is_cancelled_ = false;
+ std::unique_ptr<Observer> observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSequenceState);
+};
+
+FileDataPipeProducer::FileDataPipeProducer(
+ ScopedDataPipeProducerHandle producer,
+ std::unique_ptr<Observer> observer)
+ : producer_(std::move(producer)),
+ observer_(std::move(observer)),
+ weak_factory_(this) {}
+
+FileDataPipeProducer::~FileDataPipeProducer() {
+ if (file_sequence_state_)
+ file_sequence_state_->Cancel();
+}
+
+void FileDataPipeProducer::WriteFromFile(base::File file,
+ CompletionCallback callback) {
+ WriteFromFile(std::move(file), std::numeric_limits<size_t>::max(),
+ std::move(callback));
+}
+
+void FileDataPipeProducer::WriteFromFile(base::File file,
+ size_t max_bytes,
+ CompletionCallback callback) {
+ InitializeNewRequest(std::move(callback));
+ file_sequence_state_->StartFromFile(std::move(file), max_bytes);
+}
+
+void FileDataPipeProducer::WriteFromPath(const base::FilePath& path,
+ CompletionCallback callback) {
+ InitializeNewRequest(std::move(callback));
+ file_sequence_state_->StartFromPath(path);
+}
+
+void FileDataPipeProducer::InitializeNewRequest(CompletionCallback callback) {
+ DCHECK(!file_sequence_state_);
+
+ LOG(FATAL) << "unsupported in libchrome";
+ // auto file_task_runner = base::CreateSequencedTaskRunnerWithTraits(
+ // {base::MayBlock(), base::TaskPriority::BACKGROUND});
+ // file_sequence_state_ = new FileSequenceState(
+ // std::move(producer_), file_task_runner,
+ // base::BindOnce(&FileDataPipeProducer::OnWriteComplete,
+ // weak_factory_.GetWeakPtr(), std::move(callback)),
+ // base::SequencedTaskRunnerHandle::Get(), std::move(observer_));
+}
+
+void FileDataPipeProducer::OnWriteComplete(
+ CompletionCallback callback,
+ ScopedDataPipeProducerHandle producer,
+ MojoResult ready_result) {
+ producer_ = std::move(producer);
+ file_sequence_state_ = nullptr;
+ std::move(callback).Run(ready_result);
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/system/file_data_pipe_producer.h b/mojo/public/cpp/system/file_data_pipe_producer.h
new file mode 100644
index 0000000000..c3479f75bf
--- /dev/null
+++ b/mojo/public/cpp/system/file_data_pipe_producer.h
@@ -0,0 +1,114 @@
+// 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_SYSTEM_FILE_DATA_PIPE_PRODUCER_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_FILE_DATA_PIPE_PRODUCER_H_
+
+#include <memory>
+
+#include "base/callback_forward.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "mojo/public/cpp/system/system_export.h"
+
+namespace mojo {
+
+// Helper class which takes ownership of a ScopedDataPipeProducerHandle and
+// assumes responsibility for feeding it the contents of a given file. This
+// takes care of waiting for pipe capacity as needed, and can notify callers
+// asynchronously when the operation is complete.
+//
+// Note that the FileDataPipeProducer must be kept alive until notified of
+// completion to ensure that all of the intended contents are written to the
+// pipe. Premature destruction may result in partial or total truncation of data
+// made available to the consumer.
+class MOJO_CPP_SYSTEM_EXPORT FileDataPipeProducer {
+ public:
+ using CompletionCallback = base::OnceCallback<void(MojoResult result)>;
+
+ // Interface definition of an optional object that may be supplied to the
+ // FileDataPipeProducer so that the data being read from the consumer can be
+ // observed.
+ class Observer {
+ public:
+ virtual ~Observer() {}
+
+ // Called once per read attempt. |data| contains the read data (if any).
+ // |num_bytes_read| is the number of read bytes, 0 indicates EOF. Both
+ // parameters may only be used when |read_result| is base::File::FILE_OK.
+ // Can be called on any sequence.
+ virtual void OnBytesRead(const void* data,
+ size_t num_bytes_read,
+ base::File::Error read_result) = 0;
+
+ // Called when the FileDataPipeProducer has finished reading all data. Will
+ // be called even if there was an error opening the file or reading the
+ // data. Can be called on any sequence.
+ virtual void OnDoneReading() = 0;
+ };
+
+ // Constructs a new FileDataPipeProducer which will write data to |producer|.
+ // Caller may supply an optional |observer| if observation of the read file
+ // data is desired.
+ FileDataPipeProducer(ScopedDataPipeProducerHandle producer,
+ std::unique_ptr<Observer> observer);
+ ~FileDataPipeProducer();
+
+ // Attempts to eventually write all of |file|'s contents to the pipe. Invokes
+ // |callback| asynchronously when done. Note that |callback| IS allowed to
+ // delete this FileDataPipeProducer.
+ //
+ // If the write is successful |result| will be |MOJO_RESULT_OK|. Otherwise
+ // (e.g. if the producer detects the consumer is closed and the pipe has no
+ // remaining capacity, or if file open/reads fail for any reason) |result|
+ // will be one of the following:
+ //
+ // |MOJO_RESULT_ABORTED|
+ // |MOJO_RESULT_NOT_FOUND|
+ // |MOJO_RESULT_PERMISSION_DENIED|
+ // |MOJO_RESULT_RESOURCE_EXHAUSTED|
+ // |MOJO_RESULT_UNKNOWN|
+ //
+ // Note that if the FileDataPipeProducer is destroyed before |callback| can be
+ // invoked, |callback| is *never* invoked, and the write will be permanently
+ // interrupted (and the producer handle closed) after making potentially only
+ // partial progress.
+ //
+ // Multiple writes may be performed in sequence (each one after the last
+ // completes), but Write() must not be called before the |callback| for the
+ // previous call to Write() (if any) has returned.
+ void WriteFromFile(base::File file, CompletionCallback callback);
+
+ // Like above, but writes at most |max_bytes| bytes from the file to the pipe.
+ void WriteFromFile(base::File file,
+ size_t max_bytes,
+ CompletionCallback callback);
+
+ // Same as above but takes a FilePath instead of an opened File. Opens the
+ // file on an appropriate sequence and then proceeds as WriteFromFile() would.
+ void WriteFromPath(const base::FilePath& path, CompletionCallback callback);
+
+ private:
+ class FileSequenceState;
+
+ void InitializeNewRequest(CompletionCallback callback);
+ void OnWriteComplete(CompletionCallback callback,
+ ScopedDataPipeProducerHandle producer,
+ MojoResult result);
+
+ ScopedDataPipeProducerHandle producer_;
+ scoped_refptr<FileSequenceState> file_sequence_state_;
+ std::unique_ptr<Observer> observer_;
+ base::WeakPtrFactory<FileDataPipeProducer> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileDataPipeProducer);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_FILE_DATA_PIPE_PRODUCER_H_
diff --git a/mojo/public/cpp/system/handle.h b/mojo/public/cpp/system/handle.h
index 781944eb76..2cc10148c4 100644
--- a/mojo/public/cpp/system/handle.h
+++ b/mojo/public/cpp/system/handle.h
@@ -39,9 +39,9 @@ namespace mojo {
// |ScopedHandleBase<HandleType>| is a templated scoped wrapper, for the handle
// types above (in the same sense that a C++11 |unique_ptr<T>| is a scoped
// wrapper for a |T*|). It provides lifetime management, closing its owned
-// handle on destruction. It also provides (emulated) move semantics, again
-// along the lines of C++11's |unique_ptr| (and exactly like Chromium's
-// |scoped_ptr|).
+// handle on destruction. It also provides move semantics, again along the lines
+// of C++11's |unique_ptr|. A moved-from |ScopedHandleBase<HandleType>| sets its
+// handle value to MOJO_HANDLE_INVALID.
//
// |ScopedHandle| is just (a typedef of) a |ScopedHandleBase<Handle>|.
// Similarly, |ScopedMessagePipeHandle| is just a
@@ -84,8 +84,9 @@ class ScopedHandleBase {
: handle_(other.release()) {}
// Move-only constructor and operator=.
- ScopedHandleBase(ScopedHandleBase&& other) : handle_(other.release()) {}
- ScopedHandleBase& operator=(ScopedHandleBase&& other) {
+ ScopedHandleBase(ScopedHandleBase&& other) noexcept
+ : handle_(other.release()) {}
+ ScopedHandleBase& operator=(ScopedHandleBase&& other) noexcept {
if (&other != this) {
CloseIfNecessary();
handle_ = other.release();
@@ -94,7 +95,10 @@ class ScopedHandleBase {
}
const HandleType& get() const { return handle_; }
- const HandleType* operator->() const { return &handle_; }
+ const HandleType* operator->() const {
+ DCHECK(handle_.is_valid());
+ return &handle_;
+ }
template <typename PassedHandleType>
static ScopedHandleBase<HandleType> From(
@@ -121,6 +125,8 @@ class ScopedHandleBase {
bool is_valid() const { return handle_.is_valid(); }
+ explicit operator bool() const { return handle_; }
+
bool operator==(const ScopedHandleBase& other) const {
return handle_.value() == other.get().value();
}
@@ -160,6 +166,8 @@ class Handle {
bool is_valid() const { return value_ != kInvalidHandleValue; }
+ explicit operator bool() const { return value_ != kInvalidHandleValue; }
+
const MojoHandle& value() const { return value_; }
MojoHandle* mutable_value() { return &value_; }
void set_value(MojoHandle value) { value_ = value; }
diff --git a/mojo/public/cpp/system/handle_signal_tracker.cc b/mojo/public/cpp/system/handle_signal_tracker.cc
new file mode 100644
index 0000000000..a6e9d97a01
--- /dev/null
+++ b/mojo/public/cpp/system/handle_signal_tracker.cc
@@ -0,0 +1,70 @@
+// 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/system/handle_signal_tracker.h"
+
+#include "base/synchronization/lock.h"
+#include "mojo/public/cpp/system/handle_signals_state.h"
+
+namespace mojo {
+
+HandleSignalTracker::HandleSignalTracker(Handle handle,
+ MojoHandleSignals signals)
+ : high_watcher_(FROM_HERE,
+ SimpleWatcher::ArmingPolicy::MANUAL,
+ base::SequencedTaskRunnerHandle::Get()),
+ low_watcher_(FROM_HERE,
+ SimpleWatcher::ArmingPolicy::MANUAL,
+ base::SequencedTaskRunnerHandle::Get()) {
+ MojoResult rv = high_watcher_.Watch(
+ handle, signals, MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ base::Bind(&HandleSignalTracker::OnNotify, base::Unretained(this)));
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+
+ rv = low_watcher_.Watch(
+ handle, signals, MOJO_TRIGGER_CONDITION_SIGNALS_UNSATISFIED,
+ base::Bind(&HandleSignalTracker::OnNotify, base::Unretained(this)));
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+
+ last_known_state_ = handle.QuerySignalsState();
+
+ Arm();
+}
+
+HandleSignalTracker::~HandleSignalTracker() = default;
+
+void HandleSignalTracker::Arm() {
+ // Arm either the low watcher or high watcher. We cycle until one of them
+ // succeeds, which should almost always happen within two iterations.
+ bool arm_low_watcher = true;
+ for (;;) {
+ MojoResult ready_result;
+ SimpleWatcher& watcher = arm_low_watcher ? low_watcher_ : high_watcher_;
+ MojoResult result = watcher.Arm(&ready_result, &last_known_state_);
+ if (result == MOJO_RESULT_OK) {
+ // Successfully armed one of the watchers, so we can go back to waiting
+ // for a notification.
+ return;
+ }
+
+ DCHECK_EQ(MOJO_RESULT_FAILED_PRECONDITION, result);
+ if (ready_result == MOJO_RESULT_FAILED_PRECONDITION && !arm_low_watcher) {
+ // The high watcher failed to arm because the watched signal will never
+ // be satisfied again. We can also return in this case, and
+ // |last_known_state_| will remain with its current value indefinitely.
+ return;
+ }
+ arm_low_watcher = !arm_low_watcher;
+ }
+}
+
+void HandleSignalTracker::OnNotify(MojoResult result,
+ const HandleSignalsState& state) {
+ last_known_state_ = state;
+ Arm();
+ if (notification_callback_)
+ notification_callback_.Run(state);
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/system/handle_signal_tracker.h b/mojo/public/cpp/system/handle_signal_tracker.h
new file mode 100644
index 0000000000..7df6b7fe99
--- /dev/null
+++ b/mojo/public/cpp/system/handle_signal_tracker.h
@@ -0,0 +1,69 @@
+// 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_SYSTEM_HANDLE_SIGNAL_TRACKER_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_HANDLE_SIGNAL_TRACKER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+#include "mojo/public/cpp/system/system_export.h"
+
+namespace mojo {
+
+// This class helps track the state of specific signal on a handle so that
+// the user doesn't have to manually query the signal state every time they
+// want to know the handle's state.
+//
+// Usage of this class is specifically targeting cases where the signal state
+// changes infrequently but must be queried frequently. If either condition does
+// not hold, consider using Handle::QuerySignalsState (or
+// MojoQueryHandleSignalsState) directly instead.
+class MOJO_CPP_SYSTEM_EXPORT HandleSignalTracker {
+ public:
+ using NotificationCallback =
+ base::Callback<void(const HandleSignalsState& signals_state)>;
+
+ // Constructs a tracker which tracks |signals| on |handle|. |signals| may
+ // be any single signal flag or any combination of signal flags.
+ HandleSignalTracker(Handle handle, MojoHandleSignals signals);
+ ~HandleSignalTracker();
+
+ const HandleSignalsState& last_known_state() const {
+ return last_known_state_;
+ }
+
+ // Sets an optional callback to be invoked any time the tracker is notified of
+ // a relevant state change.
+ void set_notification_callback(const NotificationCallback& callback) {
+ notification_callback_ = callback;
+ }
+
+ private:
+ class State;
+
+ void Arm();
+ void OnNotify(MojoResult result, const HandleSignalsState& state);
+
+ NotificationCallback notification_callback_;
+
+ // The last known signaliing state of the handle.
+ HandleSignalsState last_known_state_ = {0, 0};
+
+ // Watches for the signal(s) to be signaled. May only be armed when
+ // |low_watcher_| is not.
+ SimpleWatcher high_watcher_;
+
+ // Watches for the signal(s) to be cleared. May only be armed when
+ // |high_watcher_| is not.
+ SimpleWatcher low_watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(HandleSignalTracker);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_HANDLE_SIGNAL_TRACKER_H_
diff --git a/mojo/public/cpp/system/handle_signals_state.h b/mojo/public/cpp/system/handle_signals_state.h
index 9e2430f15a..3eb295becc 100644
--- a/mojo/public/cpp/system/handle_signals_state.h
+++ b/mojo/public/cpp/system/handle_signals_state.h
@@ -35,41 +35,64 @@ struct MOJO_CPP_SYSTEM_EXPORT HandleSignalsState final
satisfiable_signals == other.satisfiable_signals;
}
- bool satisfies(MojoHandleSignals signals) const {
+ bool satisfies_any(MojoHandleSignals signals) const {
return !!(satisfied_signals & signals);
}
- bool can_satisfy(MojoHandleSignals signals) const {
+ bool satisfies_all(MojoHandleSignals signals) const {
+ return (satisfied_signals & signals) == signals;
+ }
+
+ bool can_satisfy_any(MojoHandleSignals signals) const {
return !!(satisfiable_signals & signals);
}
// The handle is currently readable. May apply to a message pipe handle or
// data pipe consumer handle.
- bool readable() const { return satisfies(MOJO_HANDLE_SIGNAL_READABLE); }
+ bool readable() const { return satisfies_any(MOJO_HANDLE_SIGNAL_READABLE); }
// The handle is currently writable. May apply to a message pipe handle or
// data pipe producer handle.
- bool writable() const { return satisfies(MOJO_HANDLE_SIGNAL_WRITABLE); }
+ bool writable() const { return satisfies_any(MOJO_HANDLE_SIGNAL_WRITABLE); }
// The handle's peer is closed. May apply to any message pipe or data pipe
// handle.
- bool peer_closed() const { return satisfies(MOJO_HANDLE_SIGNAL_PEER_CLOSED); }
+ bool peer_closed() const {
+ return satisfies_any(MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+ }
+
+ // The handle's peer exists in a remote execution context (e.g. in another
+ // process.)
+ bool peer_remote() const {
+ return satisfies_any(MOJO_HANDLE_SIGNAL_PEER_REMOTE);
+ }
+
+ // Indicates whether the handle has exceeded some quota limit.
+ bool quota_exceeded() const {
+ return satisfies_any(MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED);
+ }
// The handle will never be |readable()| again.
bool never_readable() const {
- return !can_satisfy(MOJO_HANDLE_SIGNAL_READABLE);
+ return !can_satisfy_any(MOJO_HANDLE_SIGNAL_READABLE);
}
// The handle will never be |writable()| again.
bool never_writable() const {
- return !can_satisfy(MOJO_HANDLE_SIGNAL_WRITABLE);
+ return !can_satisfy_any(MOJO_HANDLE_SIGNAL_WRITABLE);
}
// The handle can never indicate |peer_closed()|. Never true for message pipe
// or data pipe handles (they can always signal peer closure), but always true
// for other types of handles (they have no peer.)
bool never_peer_closed() const {
- return !can_satisfy(MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+ return !can_satisfy_any(MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+ }
+
+ // THe handle will never indicate |peer_remote()| again. True iff the peer is
+ // known to be closed.
+ bool never_peer_remote() const {
+ return !can_satisfy_any(MOJO_HANDLE_SIGNAL_PEER_REMOTE);
}
// (Copy and assignment allowed.)
diff --git a/mojo/public/cpp/system/invitation.cc b/mojo/public/cpp/system/invitation.cc
new file mode 100644
index 0000000000..f70c435e95
--- /dev/null
+++ b/mojo/public/cpp/system/invitation.cc
@@ -0,0 +1,286 @@
+// 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.
+
+#include "mojo/public/cpp/system/invitation.h"
+
+#include "base/numerics/safe_conversions.h"
+#include "build/build_config.h"
+#include "mojo/public/c/system/invitation.h"
+#include "mojo/public/c/system/platform_handle.h"
+#include "mojo/public/cpp/system/platform_handle.h"
+
+namespace mojo {
+
+namespace {
+
+static constexpr base::StringPiece kIsolatedPipeName = {"\0\0\0\0", 4};
+
+void ProcessHandleToMojoProcessHandle(base::ProcessHandle target_process,
+ MojoPlatformProcessHandle* handle) {
+ handle->struct_size = sizeof(*handle);
+#if defined(OS_WIN)
+ handle->value =
+ static_cast<uint64_t>(reinterpret_cast<uintptr_t>(target_process));
+#else
+ handle->value = static_cast<uint64_t>(target_process);
+#endif
+}
+
+void PlatformHandleToTransportEndpoint(
+ PlatformHandle platform_handle,
+ MojoPlatformHandle* endpoint_handle,
+ MojoInvitationTransportEndpoint* endpoint) {
+ PlatformHandle::ToMojoPlatformHandle(std::move(platform_handle),
+ endpoint_handle);
+ CHECK_NE(endpoint_handle->type, MOJO_PLATFORM_HANDLE_TYPE_INVALID);
+
+ endpoint->struct_size = sizeof(*endpoint);
+ endpoint->num_platform_handles = 1;
+ endpoint->platform_handles = endpoint_handle;
+}
+
+void RunErrorCallback(uintptr_t context,
+ const MojoProcessErrorDetails* details) {
+ auto* callback = reinterpret_cast<ProcessErrorCallback*>(context);
+ std::string error_message;
+ if (details->error_message) {
+ error_message =
+ std::string(details->error_message, details->error_message_length - 1);
+ callback->Run(error_message);
+ } else if (details->flags & MOJO_PROCESS_ERROR_FLAG_DISCONNECTED) {
+ delete callback;
+ }
+}
+
+void SendInvitation(ScopedInvitationHandle invitation,
+ base::ProcessHandle target_process,
+ PlatformHandle endpoint_handle,
+ MojoInvitationTransportType transport_type,
+ MojoSendInvitationFlags flags,
+ const ProcessErrorCallback& error_callback,
+ base::StringPiece isolated_connection_name) {
+ MojoPlatformProcessHandle process_handle;
+ ProcessHandleToMojoProcessHandle(target_process, &process_handle);
+
+ MojoPlatformHandle platform_handle;
+ MojoInvitationTransportEndpoint endpoint;
+ PlatformHandleToTransportEndpoint(std::move(endpoint_handle),
+ &platform_handle, &endpoint);
+ endpoint.type = transport_type;
+
+ MojoProcessErrorHandler error_handler = nullptr;
+ uintptr_t error_handler_context = 0;
+ if (error_callback) {
+ error_handler = &RunErrorCallback;
+
+ // NOTE: The allocated callback is effectively owned by the error handler,
+ // which will delete it on the final invocation for this context (i.e.
+ // process disconnection).
+ error_handler_context =
+ reinterpret_cast<uintptr_t>(new ProcessErrorCallback(error_callback));
+ }
+
+ MojoSendInvitationOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = flags;
+ if (flags & MOJO_SEND_INVITATION_FLAG_ISOLATED) {
+ options.isolated_connection_name = isolated_connection_name.data();
+ options.isolated_connection_name_length =
+ static_cast<uint32_t>(isolated_connection_name.size());
+ }
+ MojoResult result =
+ MojoSendInvitation(invitation.get().value(), &process_handle, &endpoint,
+ error_handler, error_handler_context, &options);
+ // If successful, the invitation handle is already closed for us.
+ if (result == MOJO_RESULT_OK)
+ ignore_result(invitation.release());
+}
+
+} // namespace
+
+OutgoingInvitation::OutgoingInvitation() {
+ MojoHandle invitation_handle;
+ MojoResult result = MojoCreateInvitation(nullptr, &invitation_handle);
+ DCHECK_EQ(result, MOJO_RESULT_OK);
+
+ handle_.reset(InvitationHandle(invitation_handle));
+}
+
+OutgoingInvitation::OutgoingInvitation(OutgoingInvitation&& other) = default;
+
+OutgoingInvitation::~OutgoingInvitation() = default;
+
+OutgoingInvitation& OutgoingInvitation::operator=(OutgoingInvitation&& other) =
+ default;
+
+ScopedMessagePipeHandle OutgoingInvitation::AttachMessagePipe(
+ base::StringPiece name) {
+ DCHECK(!name.empty());
+ DCHECK(base::IsValueInRangeForNumericType<uint32_t>(name.size()));
+ MojoHandle message_pipe_handle;
+ MojoResult result = MojoAttachMessagePipeToInvitation(
+ handle_.get().value(), name.data(), static_cast<uint32_t>(name.size()),
+ nullptr, &message_pipe_handle);
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+ return ScopedMessagePipeHandle(MessagePipeHandle(message_pipe_handle));
+}
+
+ScopedMessagePipeHandle OutgoingInvitation::AttachMessagePipe(uint64_t name) {
+ return AttachMessagePipe(
+ base::StringPiece(reinterpret_cast<const char*>(&name), sizeof(name)));
+}
+
+ScopedMessagePipeHandle OutgoingInvitation::ExtractMessagePipe(
+ base::StringPiece name) {
+ DCHECK(!name.empty());
+ DCHECK(base::IsValueInRangeForNumericType<uint32_t>(name.size()));
+ MojoHandle message_pipe_handle;
+ MojoResult result = MojoExtractMessagePipeFromInvitation(
+ handle_.get().value(), name.data(), static_cast<uint32_t>(name.size()),
+ nullptr, &message_pipe_handle);
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+ return ScopedMessagePipeHandle(MessagePipeHandle(message_pipe_handle));
+}
+
+ScopedMessagePipeHandle OutgoingInvitation::ExtractMessagePipe(uint64_t name) {
+ return ExtractMessagePipe(
+ base::StringPiece(reinterpret_cast<const char*>(&name), sizeof(name)));
+}
+
+// static
+void OutgoingInvitation::Send(OutgoingInvitation invitation,
+ base::ProcessHandle target_process,
+ PlatformChannelEndpoint channel_endpoint,
+ const ProcessErrorCallback& error_callback) {
+ SendInvitation(std::move(invitation.handle_), target_process,
+ channel_endpoint.TakePlatformHandle(),
+ MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL,
+ MOJO_SEND_INVITATION_FLAG_NONE, error_callback, "");
+}
+
+// static
+void OutgoingInvitation::Send(OutgoingInvitation invitation,
+ base::ProcessHandle target_process,
+ PlatformChannelServerEndpoint server_endpoint,
+ const ProcessErrorCallback& error_callback) {
+ SendInvitation(std::move(invitation.handle_), target_process,
+ server_endpoint.TakePlatformHandle(),
+ MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL_SERVER,
+ MOJO_SEND_INVITATION_FLAG_NONE, error_callback, "");
+}
+
+// static
+ScopedMessagePipeHandle OutgoingInvitation::SendIsolated(
+ PlatformChannelEndpoint channel_endpoint,
+ base::StringPiece connection_name) {
+ mojo::OutgoingInvitation invitation;
+ ScopedMessagePipeHandle pipe =
+ invitation.AttachMessagePipe(kIsolatedPipeName);
+ SendInvitation(std::move(invitation.handle_), base::kNullProcessHandle,
+ channel_endpoint.TakePlatformHandle(),
+ MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL,
+ MOJO_SEND_INVITATION_FLAG_ISOLATED, ProcessErrorCallback(),
+ connection_name);
+ return pipe;
+}
+
+// static
+ScopedMessagePipeHandle OutgoingInvitation::SendIsolated(
+ PlatformChannelServerEndpoint server_endpoint,
+ base::StringPiece connection_name) {
+ mojo::OutgoingInvitation invitation;
+ ScopedMessagePipeHandle pipe =
+ invitation.AttachMessagePipe(kIsolatedPipeName);
+ SendInvitation(std::move(invitation.handle_), base::kNullProcessHandle,
+ server_endpoint.TakePlatformHandle(),
+ MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL_SERVER,
+ MOJO_SEND_INVITATION_FLAG_ISOLATED, ProcessErrorCallback(),
+ connection_name);
+ return pipe;
+}
+
+IncomingInvitation::IncomingInvitation() = default;
+
+IncomingInvitation::IncomingInvitation(IncomingInvitation&& other) = default;
+
+IncomingInvitation::IncomingInvitation(ScopedInvitationHandle handle)
+ : handle_(std::move(handle)) {}
+
+IncomingInvitation::~IncomingInvitation() = default;
+
+IncomingInvitation& IncomingInvitation::operator=(IncomingInvitation&& other) =
+ default;
+
+// static
+IncomingInvitation IncomingInvitation::Accept(
+ PlatformChannelEndpoint channel_endpoint) {
+ MojoPlatformHandle endpoint_handle;
+ PlatformHandle::ToMojoPlatformHandle(channel_endpoint.TakePlatformHandle(),
+ &endpoint_handle);
+ CHECK_NE(endpoint_handle.type, MOJO_PLATFORM_HANDLE_TYPE_INVALID);
+
+ MojoInvitationTransportEndpoint transport_endpoint;
+ transport_endpoint.struct_size = sizeof(transport_endpoint);
+ transport_endpoint.type = MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL;
+ transport_endpoint.num_platform_handles = 1;
+ transport_endpoint.platform_handles = &endpoint_handle;
+
+ MojoHandle invitation_handle;
+ MojoResult result =
+ MojoAcceptInvitation(&transport_endpoint, nullptr, &invitation_handle);
+ if (result != MOJO_RESULT_OK)
+ return IncomingInvitation();
+
+ return IncomingInvitation(
+ ScopedInvitationHandle(InvitationHandle(invitation_handle)));
+}
+
+// static
+ScopedMessagePipeHandle IncomingInvitation::AcceptIsolated(
+ PlatformChannelEndpoint channel_endpoint) {
+ MojoPlatformHandle endpoint_handle;
+ PlatformHandle::ToMojoPlatformHandle(channel_endpoint.TakePlatformHandle(),
+ &endpoint_handle);
+ CHECK_NE(endpoint_handle.type, MOJO_PLATFORM_HANDLE_TYPE_INVALID);
+
+ MojoInvitationTransportEndpoint transport_endpoint;
+ transport_endpoint.struct_size = sizeof(transport_endpoint);
+ transport_endpoint.type = MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL;
+ transport_endpoint.num_platform_handles = 1;
+ transport_endpoint.platform_handles = &endpoint_handle;
+
+ MojoAcceptInvitationOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_ACCEPT_INVITATION_FLAG_ISOLATED;
+
+ MojoHandle invitation_handle;
+ MojoResult result =
+ MojoAcceptInvitation(&transport_endpoint, &options, &invitation_handle);
+ if (result != MOJO_RESULT_OK)
+ return ScopedMessagePipeHandle();
+
+ IncomingInvitation invitation{
+ ScopedInvitationHandle(InvitationHandle(invitation_handle))};
+ return invitation.ExtractMessagePipe(kIsolatedPipeName);
+}
+
+ScopedMessagePipeHandle IncomingInvitation::ExtractMessagePipe(
+ base::StringPiece name) {
+ DCHECK(!name.empty());
+ DCHECK(base::IsValueInRangeForNumericType<uint32_t>(name.size()));
+ DCHECK(handle_.is_valid());
+ MojoHandle message_pipe_handle;
+ MojoResult result = MojoExtractMessagePipeFromInvitation(
+ handle_.get().value(), name.data(), static_cast<uint32_t>(name.size()),
+ nullptr, &message_pipe_handle);
+ DCHECK_EQ(MOJO_RESULT_OK, result);
+ return ScopedMessagePipeHandle(MessagePipeHandle(message_pipe_handle));
+}
+
+ScopedMessagePipeHandle IncomingInvitation::ExtractMessagePipe(uint64_t name) {
+ return ExtractMessagePipe(
+ base::StringPiece(reinterpret_cast<const char*>(&name), sizeof(name)));
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/system/invitation.h b/mojo/public/cpp/system/invitation.h
new file mode 100644
index 0000000000..475a673582
--- /dev/null
+++ b/mojo/public/cpp/system/invitation.h
@@ -0,0 +1,186 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_INVITATION_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_INVITATION_H_
+
+#include <cstdint>
+#include <string>
+
+#include "base/callback.h"
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "base/process/process_handle.h"
+#include "base/strings/string_piece.h"
+#include "mojo/public/cpp/platform/platform_channel_endpoint.h"
+#include "mojo/public/cpp/platform/platform_channel_server_endpoint.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "mojo/public/cpp/system/system_export.h"
+
+namespace mojo {
+
+// A callback which may be provided when sending an invitation to another
+// process. In the event of any validation errors regarding messages from that
+// process (reported via MojoNotifyBadMessage etc and related helpers), the
+// callback will be invoked.
+using ProcessErrorCallback = base::RepeatingCallback<void(const std::string&)>;
+
+// A strongly-typed representation of a |MojoHandle| for an invitation.
+class InvitationHandle : public Handle {
+ public:
+ InvitationHandle() {}
+ explicit InvitationHandle(MojoHandle value) : Handle(value) {}
+
+ // Copying and assignment allowed.
+};
+
+static_assert(sizeof(InvitationHandle) == sizeof(Handle),
+ "Bad size for C++ InvitationHandle");
+
+using ScopedInvitationHandle = ScopedHandleBase<InvitationHandle>;
+static_assert(sizeof(ScopedInvitationHandle) == sizeof(InvitationHandle),
+ "Bad size for C++ ScopedInvitationHandle");
+
+// An OutgoingInvitation is used to invite another process to join the calling
+// process's IPC network.
+//
+// Typical use involves constructing a |PlatformChannel| and using one end to
+// send the invitation (see |Send()| below) while passing the other to a child
+// process.
+//
+// This may also be used with the server endpoint of a |NamedPlatformChannel|.
+class MOJO_CPP_SYSTEM_EXPORT OutgoingInvitation {
+ public:
+ OutgoingInvitation();
+ OutgoingInvitation(OutgoingInvitation&& other);
+ ~OutgoingInvitation();
+
+ OutgoingInvitation& operator=(OutgoingInvitation&& other);
+
+ // Creates a new message pipe, attaching one end to this invitation and
+ // returning the other end to the caller. The invitee can extract the
+ // attached endpoint (see |IncomingInvitation|) thus establishing end-to-end
+ // Mojo communication.
+ //
+ // |name| is an arbitrary value that must be used by the invitee to extract
+ // the corresponding attached endpoint.
+ ScopedMessagePipeHandle AttachMessagePipe(base::StringPiece name);
+
+ // Same as above but allows use of an integer name for convenience.
+ ScopedMessagePipeHandle AttachMessagePipe(uint64_t name);
+
+ // Extracts an attached pipe. Note that this is not typically useful, but it
+ // is potentially necessary in cases where a caller wants to, e.g., abort
+ // launching another process and recover a pipe endpoint they had previously
+ // attached.
+ ScopedMessagePipeHandle ExtractMessagePipe(base::StringPiece name);
+
+ // Same as above but allows use of an integer name for convenience.
+ ScopedMessagePipeHandle ExtractMessagePipe(uint64_t name);
+
+ // Sends |invitation| to another process via |channel_endpoint|, which should
+ // correspond to the local endpoint taken from a |PlatformChannel|.
+ //
+ // |process_handle| is a handle to the destination process if known. If not
+ // provided, IPC may be limited on some platforms (namely Mac and Windows) due
+ // to an inability to transfer system handles across the boundary.
+ static void Send(OutgoingInvitation invitation,
+ base::ProcessHandle target_process,
+ PlatformChannelEndpoint channel_endpoint,
+ const ProcessErrorCallback& error_callback = {});
+
+ // Similar to above, but sends |invitation| via |server_endpoint|, which
+ // should correspond to a |PlatformChannelServerEndpoint| taken from a
+ // |NamedPlatformChannel|.
+ static void Send(OutgoingInvitation invitation,
+ base::ProcessHandle target_process,
+ PlatformChannelServerEndpoint server_endpoint,
+ const ProcessErrorCallback& error_callback = {});
+
+ // Sends an isolated invitation over |endpoint|. The process at the other
+ // endpoint must use |IncomingInvitation::AcceptIsolated()| to accept the
+ // invitation.
+ //
+ // Isolated invitations must be used in lieu of regular invitations in cases
+ // where both of the processes being connected already belong to independent
+ // multiprocess graphs.
+ //
+ // Such connections are limited in functionality:
+ //
+ // * Platform handles may not be transferrable between the processes
+ //
+ // * Pipes sent between the processes may not be subsequently transferred to
+ // other processes in each others' process graph.
+ //
+ // Only one concurrent isolated connection is supported between any two
+ // processes.
+ //
+ // Unlike |Send()| above, isolated invitations automatically have a single
+ // message pipe attached and this is the only attachment allowed. The local
+ // end of the attached pipe is returned here.
+ //
+ // If |connection_name| is non-empty, any previously established isolated
+ // connection using the same name will be disconnected.
+ static ScopedMessagePipeHandle SendIsolated(
+ PlatformChannelEndpoint channel_endpoint,
+ base::StringPiece connection_name = {});
+
+ // Similar to above but sends |invitation| via |server_endpoint|, which should
+ // correspond to a |PlatformChannelServerEndpoint| taken from a
+ // |NamedPlatformChannel|.
+ //
+ // If |connection_name| is non-empty, any previously established isolated
+ // connection using the same name will be disconnected.
+ static ScopedMessagePipeHandle SendIsolated(
+ PlatformChannelServerEndpoint server_endpoint,
+ base::StringPiece connection_name = {});
+
+ private:
+ ScopedInvitationHandle handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(OutgoingInvitation);
+};
+
+// An IncomingInvitation can be accepted by an invited process by calling
+// |IncomingInvitation::Accept()|. Once accepted, the invitation can be used
+// to extract attached message pipes by name.
+class MOJO_CPP_SYSTEM_EXPORT IncomingInvitation {
+ public:
+ IncomingInvitation();
+ IncomingInvitation(IncomingInvitation&& other);
+ explicit IncomingInvitation(ScopedInvitationHandle handle);
+ ~IncomingInvitation();
+
+ IncomingInvitation& operator=(IncomingInvitation&& other);
+
+ // Accepts an incoming invitation from |channel_endpoint|. If the invitation
+ // was sent using one end of a |PlatformChannel|, |channel_endpoint| should be
+ // the other end of that channel. If the invitation was sent using a
+ // |PlatformChannelServerEndpoint|, then |channel_endpoint| should be created
+ // by |NamedPlatformChannel::ConnectToServer|.
+ static IncomingInvitation Accept(PlatformChannelEndpoint channel_endpoint);
+
+ // Accepts an incoming isolated invitation from |channel_endpoint|. See
+ // notes on |OutgoingInvitation::SendIsolated()|.
+ static ScopedMessagePipeHandle AcceptIsolated(
+ PlatformChannelEndpoint channel_endpoint);
+
+ // Extracts an attached message pipe from this invitation. This may succeed
+ // even if no such pipe was attached, though the extracted pipe will
+ // eventually observe peer closure.
+ ScopedMessagePipeHandle ExtractMessagePipe(base::StringPiece name);
+
+ // Same as above but allows use of an integer name for convenience.
+ ScopedMessagePipeHandle ExtractMessagePipe(uint64_t name);
+
+ private:
+ ScopedInvitationHandle handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(IncomingInvitation);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_INVITATION_H_
diff --git a/mojo/public/cpp/system/isolated_connection.cc b/mojo/public/cpp/system/isolated_connection.cc
new file mode 100644
index 0000000000..f85be4e3d4
--- /dev/null
+++ b/mojo/public/cpp/system/isolated_connection.cc
@@ -0,0 +1,41 @@
+// 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.
+
+#include "mojo/public/cpp/system/isolated_connection.h"
+
+#include "mojo/public/cpp/platform/platform_channel.h"
+#include "mojo/public/cpp/system/invitation.h"
+
+namespace mojo {
+
+IsolatedConnection::IsolatedConnection()
+ : token_(base::UnguessableToken::Create()) {}
+
+IsolatedConnection::~IsolatedConnection() {
+ // We send a dummy invitation over a temporary channel, re-using |token_| as
+ // the name. This ensures that the connection set up by Connect(), if any,
+ // will be replaced with a short-lived, self-terminating connection.
+ //
+ // This is a bit of a hack since Mojo does not provide any API for explicitly
+ // terminating isolated connections, but this is a decision made to minimize
+ // the API surface dedicated to isolated connections in anticipation of the
+ // concept being deprecated eventually.
+ PlatformChannel channel;
+ OutgoingInvitation::SendIsolated(channel.TakeLocalEndpoint(),
+ token_.ToString());
+}
+
+ScopedMessagePipeHandle IsolatedConnection::Connect(
+ PlatformChannelEndpoint endpoint) {
+ return OutgoingInvitation::SendIsolated(std::move(endpoint),
+ token_.ToString());
+}
+
+ScopedMessagePipeHandle IsolatedConnection::Connect(
+ PlatformChannelServerEndpoint endpoint) {
+ return OutgoingInvitation::SendIsolated(std::move(endpoint),
+ token_.ToString());
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/system/isolated_connection.h b/mojo/public/cpp/system/isolated_connection.h
new file mode 100644
index 0000000000..42c90532ad
--- /dev/null
+++ b/mojo/public/cpp/system/isolated_connection.h
@@ -0,0 +1,60 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_ISOLATED_CONNECTION_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_ISOLATED_CONNECTION_H_
+
+#include "base/macros.h"
+#include "base/unguessable_token.h"
+#include "mojo/public/cpp/platform/platform_channel_endpoint.h"
+#include "mojo/public/cpp/platform/platform_channel_server_endpoint.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "mojo/public/cpp/system/system_export.h"
+
+namespace mojo {
+
+// IsolatedConnection establishes a one-off Mojo IPC connection between two
+// processes. Unlike more common connections established by invitation
+// (see OutgoingInvitation and IncomingInvitation), isolated connections
+// do not result in the two processes becoming part of the same connected
+// graph of processes. As such, any message pipe established over this
+// connection can only be used for direct IPC between the two processes in
+// question.
+//
+// This means that if one of the processes sends a Mojo handle (e.g. another
+// message pipe endpoint) to the other process, the receiving process cannot
+// pass that handle to yet another process in its own graph. This limitation is
+// subtle and can be difficult to work around, so use of IsolatedConnection
+// should be rare.
+//
+// This is primarily useful when you already have two established Mojo process
+// graphs isolated form each other, and you want to do some IPC between two
+// processes, one in each graph.
+//
+// A connection established via |Connect()|, and any opened message pipes
+// spanning that connection, will remain valid and connected as long as this
+// object remains alive.
+class MOJO_CPP_SYSTEM_EXPORT IsolatedConnection {
+ public:
+ IsolatedConnection();
+ ~IsolatedConnection();
+
+ // Connects to a process at the other end of the channel. Returns a primordial
+ // message pipe that can be used for Mojo IPC. The connection
+ // will be connected to a corresponding peer pipe in the remote process.
+ ScopedMessagePipeHandle Connect(PlatformChannelEndpoint endpoint);
+
+ // Same as above but works with a server endpoint. The corresponding client
+ // could use the above signature with NamedPlatformChannel::ConnectToServer.
+ ScopedMessagePipeHandle Connect(PlatformChannelServerEndpoint endpoint);
+
+ private:
+ const base::UnguessableToken token_;
+
+ DISALLOW_COPY_AND_ASSIGN(IsolatedConnection);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_ISOLATED_CONNECTION_H_
diff --git a/mojo/public/cpp/system/message.cc b/mojo/public/cpp/system/message.cc
deleted file mode 100644
index 09d8d46e6d..0000000000
--- a/mojo/public/cpp/system/message.cc
+++ /dev/null
@@ -1,13 +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 "mojo/public/cpp/system/message.h"
-
-namespace mojo {
-
-ScopedMessageHandle::~ScopedMessageHandle() {
-
-}
-
-} // namespace mojo
diff --git a/mojo/public/cpp/system/message.h b/mojo/public/cpp/system/message.h
index d4406ee808..127ff18c7f 100644
--- a/mojo/public/cpp/system/message.h
+++ b/mojo/public/cpp/system/message.h
@@ -6,8 +6,10 @@
#define MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_H_
#include <limits>
+#include <vector>
#include "base/macros.h"
+#include "base/numerics/safe_conversions.h"
#include "base/strings/string_piece.h"
#include "mojo/public/c/system/message_pipe.h"
#include "mojo/public/cpp/system/handle.h"
@@ -38,7 +40,7 @@ class MessageHandle {
void Close() {
DCHECK(is_valid());
- MojoResult result = MojoFreeMessage(value_);
+ MojoResult result = MojoDestroyMessage(value_);
ALLOW_UNUSED_LOCAL(result);
DCHECK_EQ(MOJO_RESULT_OK, result);
}
@@ -49,17 +51,9 @@ class MessageHandle {
using ScopedMessageHandle = ScopedHandleBase<MessageHandle>;
-inline MojoResult AllocMessage(size_t num_bytes,
- const MojoHandle* handles,
- size_t num_handles,
- MojoAllocMessageFlags flags,
- ScopedMessageHandle* handle) {
- DCHECK_LE(num_bytes, std::numeric_limits<uint32_t>::max());
- DCHECK_LE(num_handles, std::numeric_limits<uint32_t>::max());
+inline MojoResult CreateMessage(ScopedMessageHandle* handle) {
MojoMessageHandle raw_handle;
- MojoResult rv = MojoAllocMessage(static_cast<uint32_t>(num_bytes), handles,
- static_cast<uint32_t>(num_handles), flags,
- &raw_handle);
+ MojoResult rv = MojoCreateMessage(nullptr, &raw_handle);
if (rv != MOJO_RESULT_OK)
return rv;
@@ -67,15 +61,39 @@ inline MojoResult AllocMessage(size_t num_bytes,
return MOJO_RESULT_OK;
}
-inline MojoResult GetMessageBuffer(MessageHandle message, void** buffer) {
+inline MojoResult GetMessageData(MessageHandle message,
+ void** buffer,
+ uint32_t* num_bytes,
+ std::vector<ScopedHandle>* handles,
+ MojoGetMessageDataFlags flags) {
DCHECK(message.is_valid());
- return MojoGetMessageBuffer(message.value(), buffer);
+ DCHECK(num_bytes);
+ DCHECK(buffer);
+ uint32_t num_handles = 0;
+
+ MojoGetMessageDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = flags;
+ MojoResult rv = MojoGetMessageData(message.value(), &options, buffer,
+ num_bytes, nullptr, &num_handles);
+ if (rv != MOJO_RESULT_RESOURCE_EXHAUSTED) {
+ if (handles)
+ handles->clear();
+ return rv;
+ }
+
+ handles->resize(num_handles);
+ return MojoGetMessageData(message.value(), &options, buffer, num_bytes,
+ reinterpret_cast<MojoHandle*>(handles->data()),
+ &num_handles);
}
inline MojoResult NotifyBadMessage(MessageHandle message,
const base::StringPiece& error) {
DCHECK(message.is_valid());
- return MojoNotifyBadMessage(message.value(), error.data(), error.size());
+ DCHECK(base::IsValueInRangeForNumericType<uint32_t>(error.size()));
+ return MojoNotifyBadMessage(message.value(), error.data(),
+ static_cast<uint32_t>(error.size()), nullptr);
}
} // namespace mojo
diff --git a/mojo/public/cpp/system/message_pipe.cc b/mojo/public/cpp/system/message_pipe.cc
new file mode 100644
index 0000000000..4059b6edc4
--- /dev/null
+++ b/mojo/public/cpp/system/message_pipe.cc
@@ -0,0 +1,88 @@
+// 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/system/message_pipe.h"
+
+#include "base/numerics/safe_math.h"
+
+namespace mojo {
+
+MojoResult WriteMessageRaw(MessagePipeHandle message_pipe,
+ const void* bytes,
+ size_t num_bytes,
+ const MojoHandle* handles,
+ size_t num_handles,
+ MojoWriteMessageFlags flags) {
+ ScopedMessageHandle message_handle;
+ MojoResult rv = CreateMessage(&message_handle);
+ DCHECK_EQ(MOJO_RESULT_OK, rv);
+
+ MojoAppendMessageDataOptions append_options;
+ append_options.struct_size = sizeof(append_options);
+ append_options.flags = MOJO_APPEND_MESSAGE_DATA_FLAG_COMMIT_SIZE;
+ void* buffer;
+ uint32_t buffer_size;
+ rv = MojoAppendMessageData(message_handle->value(),
+ base::checked_cast<uint32_t>(num_bytes), handles,
+ base::checked_cast<uint32_t>(num_handles),
+ &append_options, &buffer, &buffer_size);
+ if (rv != MOJO_RESULT_OK)
+ return MOJO_RESULT_ABORTED;
+
+ DCHECK(buffer);
+ DCHECK_GE(buffer_size, base::checked_cast<uint32_t>(num_bytes));
+ memcpy(buffer, bytes, num_bytes);
+
+ MojoWriteMessageOptions write_options;
+ write_options.struct_size = sizeof(write_options);
+ write_options.flags = flags;
+ return MojoWriteMessage(message_pipe.value(),
+ message_handle.release().value(), &write_options);
+}
+
+MojoResult ReadMessageRaw(MessagePipeHandle message_pipe,
+ std::vector<uint8_t>* payload,
+ std::vector<ScopedHandle>* handles,
+ MojoReadMessageFlags flags) {
+ ScopedMessageHandle message_handle;
+ MojoResult rv = ReadMessageNew(message_pipe, &message_handle, flags);
+ if (rv != MOJO_RESULT_OK)
+ return rv;
+
+ rv = MojoSerializeMessage(message_handle->value(), nullptr);
+ if (rv != MOJO_RESULT_OK && rv != MOJO_RESULT_FAILED_PRECONDITION)
+ return MOJO_RESULT_ABORTED;
+
+ void* buffer = nullptr;
+ uint32_t num_bytes = 0;
+ uint32_t num_handles = 0;
+ rv = MojoGetMessageData(message_handle->value(), nullptr, &buffer, &num_bytes,
+ nullptr, &num_handles);
+ if (rv == MOJO_RESULT_RESOURCE_EXHAUSTED) {
+ DCHECK(handles);
+ handles->resize(num_handles);
+ rv = MojoGetMessageData(
+ message_handle->value(), nullptr, &buffer, &num_bytes,
+ reinterpret_cast<MojoHandle*>(handles->data()), &num_handles);
+ }
+
+ if (num_bytes) {
+ DCHECK(buffer);
+ uint8_t* payload_data = reinterpret_cast<uint8_t*>(buffer);
+ payload->resize(num_bytes);
+ std::copy(payload_data, payload_data + num_bytes, payload->begin());
+ } else if (payload) {
+ payload->clear();
+ }
+
+ if (handles && !num_handles)
+ handles->clear();
+
+ if (rv != MOJO_RESULT_OK)
+ return MOJO_RESULT_ABORTED;
+
+ return MOJO_RESULT_OK;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/system/message_pipe.h b/mojo/public/cpp/system/message_pipe.h
index 7fbe43f7f4..3803a20dee 100644
--- a/mojo/public/cpp/system/message_pipe.h
+++ b/mojo/public/cpp/system/message_pipe.h
@@ -14,11 +14,14 @@
#include <stdint.h>
+#include <vector>
+
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "mojo/public/c/system/message_pipe.h"
#include "mojo/public/cpp/system/handle.h"
#include "mojo/public/cpp/system/message.h"
+#include "mojo/public/cpp/system/system_export.h"
namespace mojo {
@@ -57,58 +60,54 @@ inline MojoResult CreateMessagePipe(const MojoCreateMessagePipeOptions* options,
return rv;
}
-// The following "...Raw" versions fully expose the underlying API, and don't
-// help with ownership of handles (especially when writing messages). It is
-// expected that in most cases these methods will be called through generated
-// bindings anyway.
-// TODO(vtl): Write friendlier versions of these functions (using scoped
-// handles and/or vectors) if there is a demonstrated need for them.
-
-// Writes to a message pipe. If handles are attached, on success the handles
-// will no longer be valid (the receiver will receive equivalent, but logically
-// different, handles). See |MojoWriteMessage()| for complete documentation.
-inline MojoResult WriteMessageRaw(MessagePipeHandle message_pipe,
- const void* bytes,
- uint32_t num_bytes,
- const MojoHandle* handles,
- uint32_t num_handles,
- MojoWriteMessageFlags flags) {
- return MojoWriteMessage(
- message_pipe.value(), bytes, num_bytes, handles, num_handles, flags);
-}
-
-// Reads from a message pipe. See |MojoReadMessage()| for complete
-// documentation.
-inline MojoResult ReadMessageRaw(MessagePipeHandle message_pipe,
- void* bytes,
- uint32_t* num_bytes,
- MojoHandle* handles,
- uint32_t* num_handles,
- MojoReadMessageFlags flags) {
- return MojoReadMessage(
- message_pipe.value(), bytes, num_bytes, handles, num_handles, flags);
-}
+// A helper for writing a serialized message to a message pipe. Use this for
+// convenience in lieu of the lower-level MojoWriteMessage API, but beware that
+// it does incur an extra copy of the message payload.
+//
+// See documentation for MojoWriteMessage for return code details.
+MOJO_CPP_SYSTEM_EXPORT MojoResult
+WriteMessageRaw(MessagePipeHandle message_pipe,
+ const void* bytes,
+ size_t num_bytes,
+ const MojoHandle* handles,
+ size_t num_handles,
+ MojoWriteMessageFlags flags);
+
+// A helper for reading serialized messages from a pipe. Use this for
+// convenience in lieu of the lower-level MojoReadMessage API, but beware that
+// it does incur an extra copy of the message payload.
+//
+// See documentation for MojoReadMessage for return code details. In addition to
+// those return codes, this may return |MOJO_RESULT_ABORTED| if the message was
+// unable to be serialized into the provided containers.
+MOJO_CPP_SYSTEM_EXPORT MojoResult
+ReadMessageRaw(MessagePipeHandle message_pipe,
+ std::vector<uint8_t>* payload,
+ std::vector<ScopedHandle>* handles,
+ MojoReadMessageFlags flags);
// Writes to a message pipe. Takes ownership of |message| and any attached
// handles.
inline MojoResult WriteMessageNew(MessagePipeHandle message_pipe,
ScopedMessageHandle message,
MojoWriteMessageFlags flags) {
- return MojoWriteMessageNew(
- message_pipe.value(), message.release().value(), flags);
+ MojoWriteMessageOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = flags;
+ return MojoWriteMessage(message_pipe.value(), message.release().value(),
+ &options);
}
-// Reads from a message pipe. See |MojoReadMessageNew()| for complete
+// Reads from a message pipe. See |MojoReadMessage()| for complete
// documentation.
inline MojoResult ReadMessageNew(MessagePipeHandle message_pipe,
ScopedMessageHandle* message,
- uint32_t* num_bytes,
- MojoHandle* handles,
- uint32_t* num_handles,
MojoReadMessageFlags flags) {
+ MojoReadMessageOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = flags;
MojoMessageHandle raw_message;
- MojoResult rv = MojoReadMessageNew(message_pipe.value(), &raw_message,
- num_bytes, handles, num_handles, flags);
+ MojoResult rv = MojoReadMessage(message_pipe.value(), &options, &raw_message);
if (rv != MOJO_RESULT_OK)
return rv;
@@ -121,7 +120,7 @@ inline MojoResult ReadMessageNew(MessagePipeHandle message_pipe,
inline MojoResult FuseMessagePipes(ScopedMessagePipeHandle message_pipe0,
ScopedMessagePipeHandle message_pipe1) {
return MojoFuseMessagePipes(message_pipe0.release().value(),
- message_pipe1.release().value());
+ message_pipe1.release().value(), nullptr);
}
// A wrapper class that automatically creates a message pipe and owns both
diff --git a/mojo/public/cpp/system/platform_handle.cc b/mojo/public/cpp/system/platform_handle.cc
index 42e4abac83..9e55f550b2 100644
--- a/mojo/public/cpp/system/platform_handle.cc
+++ b/mojo/public/cpp/system/platform_handle.cc
@@ -4,6 +4,10 @@
#include "mojo/public/cpp/system/platform_handle.h"
+#include "base/memory/platform_shared_memory_region.h"
+#include "base/numerics/safe_conversions.h"
+#include "build/build_config.h"
+
#if defined(OS_MACOSX) && !defined(OS_IOS)
#include <mach/mach.h>
#include "base/mac/mach_logging.h"
@@ -29,8 +33,175 @@ base::PlatformFile PlatformFileFromPlatformHandleValue(uint64_t value) {
#endif
}
+ScopedSharedBufferHandle WrapPlatformSharedMemoryRegion(
+ base::subtle::PlatformSharedMemoryRegion region) {
+ if (!region.IsValid())
+ return ScopedSharedBufferHandle();
+
+ MojoPlatformSharedMemoryRegionAccessMode access_mode;
+ switch (region.GetMode()) {
+ case base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly:
+ access_mode = MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_READ_ONLY;
+ break;
+ case base::subtle::PlatformSharedMemoryRegion::Mode::kWritable:
+ access_mode = MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_WRITABLE;
+ break;
+ case base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe:
+ access_mode = MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_UNSAFE;
+ break;
+ default:
+ NOTREACHED();
+ return ScopedSharedBufferHandle();
+ }
+
+ base::subtle::PlatformSharedMemoryRegion::ScopedPlatformHandle handle =
+ region.PassPlatformHandle();
+ MojoPlatformHandle platform_handles[2];
+ uint32_t num_platform_handles = 1;
+ platform_handles[0].struct_size = sizeof(platform_handles[0]);
+#if defined(OS_WIN)
+ platform_handles[0].type = MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE;
+ platform_handles[0].value = reinterpret_cast<uint64_t>(handle.Take());
+#elif defined(OS_FUCHSIA)
+ platform_handles[0].type = MOJO_PLATFORM_HANDLE_TYPE_FUCHSIA_HANDLE;
+ platform_handles[0].value = static_cast<uint64_t>(handle.release());
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ platform_handles[0].type = MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT;
+ platform_handles[0].value = static_cast<uint64_t>(handle.release());
+#elif defined(OS_ANDROID)
+ platform_handles[0].type = MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR;
+ platform_handles[0].value = static_cast<uint64_t>(handle.release());
+#else
+ platform_handles[0].type = MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR;
+ platform_handles[0].value = static_cast<uint64_t>(handle.fd.release());
+
+ if (region.GetMode() ==
+ base::subtle::PlatformSharedMemoryRegion::Mode::kWritable) {
+ num_platform_handles = 2;
+ platform_handles[1].struct_size = sizeof(platform_handles[1]);
+ platform_handles[1].type = MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR;
+ platform_handles[1].value =
+ static_cast<uint64_t>(handle.readonly_fd.release());
+ }
+#endif
+ const auto& guid = region.GetGUID();
+ MojoSharedBufferGuid mojo_guid = {guid.GetHighForSerialization(),
+ guid.GetLowForSerialization()};
+ MojoHandle mojo_handle;
+ MojoResult result = MojoWrapPlatformSharedMemoryRegion(
+ platform_handles, num_platform_handles, region.GetSize(), &mojo_guid,
+ access_mode, nullptr, &mojo_handle);
+ if (result != MOJO_RESULT_OK)
+ return ScopedSharedBufferHandle();
+ return ScopedSharedBufferHandle(SharedBufferHandle(mojo_handle));
+}
+
+base::subtle::PlatformSharedMemoryRegion UnwrapPlatformSharedMemoryRegion(
+ ScopedSharedBufferHandle mojo_handle) {
+ if (!mojo_handle.is_valid())
+ return base::subtle::PlatformSharedMemoryRegion();
+
+ MojoPlatformHandle platform_handles[2];
+ platform_handles[0].struct_size = sizeof(platform_handles[0]);
+ platform_handles[1].struct_size = sizeof(platform_handles[1]);
+ uint32_t num_platform_handles = 2;
+ uint64_t size;
+ MojoSharedBufferGuid mojo_guid;
+ MojoPlatformSharedMemoryRegionAccessMode access_mode;
+ MojoResult result = MojoUnwrapPlatformSharedMemoryRegion(
+ mojo_handle.release().value(), nullptr, platform_handles,
+ &num_platform_handles, &size, &mojo_guid, &access_mode);
+ if (result != MOJO_RESULT_OK)
+ return base::subtle::PlatformSharedMemoryRegion();
+
+ base::subtle::PlatformSharedMemoryRegion::ScopedPlatformHandle region_handle;
+#if defined(OS_WIN)
+ if (num_platform_handles != 1)
+ return base::subtle::PlatformSharedMemoryRegion();
+ if (platform_handles[0].type != MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE)
+ return base::subtle::PlatformSharedMemoryRegion();
+ region_handle.Set(reinterpret_cast<HANDLE>(platform_handles[0].value));
+#elif defined(OS_FUCHSIA)
+ if (num_platform_handles != 1)
+ return base::subtle::PlatformSharedMemoryRegion();
+ if (platform_handles[0].type != MOJO_PLATFORM_HANDLE_TYPE_FUCHSIA_HANDLE)
+ return base::subtle::PlatformSharedMemoryRegion();
+ region_handle.reset(static_cast<zx_handle_t>(platform_handles[0].value));
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ if (num_platform_handles != 1)
+ return base::subtle::PlatformSharedMemoryRegion();
+ if (platform_handles[0].type != MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT)
+ return base::subtle::PlatformSharedMemoryRegion();
+ region_handle.reset(static_cast<mach_port_t>(platform_handles[0].value));
+#elif defined(OS_ANDROID)
+ if (num_platform_handles != 1)
+ return base::subtle::PlatformSharedMemoryRegion();
+ if (platform_handles[0].type != MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR)
+ return base::subtle::PlatformSharedMemoryRegion();
+ region_handle.reset(static_cast<int>(platform_handles[0].value));
+#else
+ if (access_mode == MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_WRITABLE) {
+ if (num_platform_handles != 2)
+ return base::subtle::PlatformSharedMemoryRegion();
+ } else if (num_platform_handles != 1) {
+ return base::subtle::PlatformSharedMemoryRegion();
+ }
+ if (platform_handles[0].type != MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR)
+ return base::subtle::PlatformSharedMemoryRegion();
+ region_handle.fd.reset(static_cast<int>(platform_handles[0].value));
+ if (num_platform_handles == 2) {
+ if (platform_handles[1].type != MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR)
+ return base::subtle::PlatformSharedMemoryRegion();
+ region_handle.readonly_fd.reset(
+ static_cast<int>(platform_handles[1].value));
+ }
+#endif
+
+ base::subtle::PlatformSharedMemoryRegion::Mode mode;
+ switch (access_mode) {
+ case MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_READ_ONLY:
+ mode = base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly;
+ break;
+ case MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_WRITABLE:
+ mode = base::subtle::PlatformSharedMemoryRegion::Mode::kWritable;
+ break;
+ case MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_UNSAFE:
+ mode = base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe;
+ break;
+ default:
+ return base::subtle::PlatformSharedMemoryRegion();
+ }
+
+ return base::subtle::PlatformSharedMemoryRegion::Take(
+ std::move(region_handle), mode, size,
+ base::UnguessableToken::Deserialize(mojo_guid.high, mojo_guid.low));
+}
+
} // namespace
+ScopedHandle WrapPlatformHandle(PlatformHandle handle) {
+ MojoPlatformHandle platform_handle;
+ PlatformHandle::ToMojoPlatformHandle(std::move(handle), &platform_handle);
+
+ MojoHandle wrapped_handle;
+ MojoResult result =
+ MojoWrapPlatformHandle(&platform_handle, nullptr, &wrapped_handle);
+ if (result != MOJO_RESULT_OK)
+ return ScopedHandle();
+ return ScopedHandle(Handle(wrapped_handle));
+}
+
+PlatformHandle UnwrapPlatformHandle(ScopedHandle handle) {
+ MojoPlatformHandle platform_handle;
+ platform_handle.struct_size = sizeof(platform_handle);
+ MojoResult result = MojoUnwrapPlatformHandle(handle.release().value(),
+ nullptr, &platform_handle);
+ if (result != MOJO_RESULT_OK)
+ return PlatformHandle();
+ return PlatformHandle::FromMojoPlatformHandle(&platform_handle);
+}
+
+// Wraps a PlatformFile as a Mojo handle. Takes ownership of the file object.
ScopedHandle WrapPlatformFile(base::PlatformFile platform_file) {
MojoPlatformHandle platform_handle;
platform_handle.struct_size = sizeof(MojoPlatformHandle);
@@ -38,7 +209,8 @@ ScopedHandle WrapPlatformFile(base::PlatformFile platform_file) {
platform_handle.value = PlatformHandleValueFromPlatformFile(platform_file);
MojoHandle mojo_handle;
- MojoResult result = MojoWrapPlatformHandle(&platform_handle, &mojo_handle);
+ MojoResult result =
+ MojoWrapPlatformHandle(&platform_handle, nullptr, &mojo_handle);
CHECK_EQ(result, MOJO_RESULT_OK);
return ScopedHandle(Handle(mojo_handle));
@@ -48,7 +220,7 @@ MojoResult UnwrapPlatformFile(ScopedHandle handle, base::PlatformFile* file) {
MojoPlatformHandle platform_handle;
platform_handle.struct_size = sizeof(MojoPlatformHandle);
MojoResult result = MojoUnwrapPlatformHandle(handle.release().value(),
- &platform_handle);
+ nullptr, &platform_handle);
if (result != MOJO_RESULT_OK)
return result;
@@ -65,81 +237,164 @@ MojoResult UnwrapPlatformFile(ScopedHandle handle, base::PlatformFile* file) {
ScopedSharedBufferHandle WrapSharedMemoryHandle(
const base::SharedMemoryHandle& memory_handle,
size_t size,
- bool read_only) {
-#if defined(OS_POSIX) && !(defined(OS_MACOSX) && !defined(OS_IOS))
- if (memory_handle.fd == base::kInvalidPlatformFile)
- return ScopedSharedBufferHandle();
-#else
+ UnwrappedSharedMemoryHandleProtection protection) {
if (!memory_handle.IsValid())
return ScopedSharedBufferHandle();
-#endif
MojoPlatformHandle platform_handle;
platform_handle.struct_size = sizeof(MojoPlatformHandle);
platform_handle.type = kPlatformSharedBufferHandleType;
#if defined(OS_MACOSX) && !defined(OS_IOS)
platform_handle.value =
static_cast<uint64_t>(memory_handle.GetMemoryObject());
-#elif defined(OS_POSIX)
- platform_handle.value = PlatformHandleValueFromPlatformFile(memory_handle.fd);
-#elif defined(OS_WIN)
+#else
platform_handle.value =
PlatformHandleValueFromPlatformFile(memory_handle.GetHandle());
#endif
- MojoPlatformSharedBufferHandleFlags flags =
- MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE;
- if (read_only)
- flags |= MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY;
+ MojoPlatformSharedMemoryRegionAccessMode access_mode =
+ MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_UNSAFE;
+ if (protection == UnwrappedSharedMemoryHandleProtection::kReadOnly) {
+ access_mode = MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_READ_ONLY;
+
+#if defined(OS_ANDROID)
+ // Many callers assume that base::SharedMemory::GetReadOnlyHandle() gives
+ // them a handle which is actually read-only. This assumption is invalid on
+ // Android. As a precursor to migrating all base::SharedMemory usage --
+ // including Mojo internals -- to the new base shared memory API, we ensure
+ // that regions are set to read-only if any of their handles are wrapped
+ // read-only. This relies on existing usages not attempting to map the
+ // region writable any time after this call.
+ if (!memory_handle.IsRegionReadOnly())
+ memory_handle.SetRegionReadOnly();
+#endif
+ }
+ MojoSharedBufferGuid guid;
+ guid.high = memory_handle.GetGUID().GetHighForSerialization();
+ guid.low = memory_handle.GetGUID().GetLowForSerialization();
MojoHandle mojo_handle;
- MojoResult result = MojoWrapPlatformSharedBufferHandle(
- &platform_handle, size, flags, &mojo_handle);
+ MojoResult result = MojoWrapPlatformSharedMemoryRegion(
+ &platform_handle, 1, size, &guid, access_mode, nullptr, &mojo_handle);
CHECK_EQ(result, MOJO_RESULT_OK);
return ScopedSharedBufferHandle(SharedBufferHandle(mojo_handle));
}
-MojoResult UnwrapSharedMemoryHandle(ScopedSharedBufferHandle handle,
- base::SharedMemoryHandle* memory_handle,
- size_t* size,
- bool* read_only) {
+MojoResult UnwrapSharedMemoryHandle(
+ ScopedSharedBufferHandle handle,
+ base::SharedMemoryHandle* memory_handle,
+ size_t* size,
+ UnwrappedSharedMemoryHandleProtection* protection) {
if (!handle.is_valid())
return MOJO_RESULT_INVALID_ARGUMENT;
- MojoPlatformHandle platform_handle;
- platform_handle.struct_size = sizeof(MojoPlatformHandle);
+ MojoPlatformHandle platform_handles[2];
+ platform_handles[0].struct_size = sizeof(platform_handles[0]);
+ platform_handles[1].struct_size = sizeof(platform_handles[1]);
- MojoPlatformSharedBufferHandleFlags flags;
- size_t num_bytes;
- MojoResult result = MojoUnwrapPlatformSharedBufferHandle(
- handle.release().value(), &platform_handle, &num_bytes, &flags);
+ uint32_t num_platform_handles = 2;
+ uint64_t num_bytes;
+ MojoSharedBufferGuid mojo_guid;
+ MojoPlatformSharedMemoryRegionAccessMode access_mode;
+ MojoResult result = MojoUnwrapPlatformSharedMemoryRegion(
+ handle.release().value(), nullptr, platform_handles,
+ &num_platform_handles, &num_bytes, &mojo_guid, &access_mode);
if (result != MOJO_RESULT_OK)
return result;
- if (size)
- *size = num_bytes;
+ if (size) {
+ DCHECK(base::IsValueInRangeForNumericType<size_t>(num_bytes));
+ *size = static_cast<size_t>(num_bytes);
+ }
- if (read_only)
- *read_only = flags & MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY;
+ if (protection) {
+ *protection =
+ access_mode == MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_READ_ONLY
+ ? UnwrappedSharedMemoryHandleProtection::kReadOnly
+ : UnwrappedSharedMemoryHandleProtection::kReadWrite;
+ }
+ base::UnguessableToken guid =
+ base::UnguessableToken::Deserialize(mojo_guid.high, mojo_guid.low);
#if defined(OS_MACOSX) && !defined(OS_IOS)
- CHECK_EQ(platform_handle.type, MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT);
+ DCHECK_EQ(platform_handles[0].type, MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT);
+ DCHECK_EQ(num_platform_handles, 1u);
*memory_handle = base::SharedMemoryHandle(
- static_cast<mach_port_t>(platform_handle.value), num_bytes,
- base::GetCurrentProcId());
+ static_cast<mach_port_t>(platform_handles[0].value), num_bytes, guid);
+#elif defined(OS_FUCHSIA)
+ DCHECK_EQ(platform_handles[0].type, MOJO_PLATFORM_HANDLE_TYPE_FUCHSIA_HANDLE);
+ DCHECK_EQ(num_platform_handles, 1u);
+ *memory_handle = base::SharedMemoryHandle(
+ static_cast<zx_handle_t>(platform_handles[0].value), num_bytes, guid);
#elif defined(OS_POSIX)
- CHECK_EQ(platform_handle.type, MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR);
+ DCHECK_EQ(platform_handles[0].type,
+ MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR);
*memory_handle = base::SharedMemoryHandle(
- static_cast<int>(platform_handle.value), false);
+ base::FileDescriptor(static_cast<int>(platform_handles[0].value), false),
+ num_bytes, guid);
+#if !defined(OS_ANDROID)
+ if (access_mode == MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_WRITABLE) {
+ DCHECK_EQ(num_platform_handles, 2u);
+ // When unwrapping as a base::SharedMemoryHandle, make sure to discard the
+ // extra file descriptor if the region is writable. base::SharedMemoryHandle
+ // effectively only supports read-only and unsafe usage modes when wrapping
+ // or unwrapping to and from Mojo handles.
+ base::ScopedFD discarded_readonly_fd(
+ static_cast<int>(platform_handles[1].value));
+ } else {
+ DCHECK_EQ(num_platform_handles, 1u);
+ }
+#else // !defined(OS_ANDROID)
+ DCHECK_EQ(num_platform_handles, 1u);
+#endif // !defined(OS_ANDROID)
#elif defined(OS_WIN)
- CHECK_EQ(platform_handle.type, MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE);
+ DCHECK_EQ(platform_handles[0].type, MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE);
+ DCHECK_EQ(num_platform_handles, 1u);
*memory_handle = base::SharedMemoryHandle(
- reinterpret_cast<HANDLE>(platform_handle.value),
- base::GetCurrentProcId());
+ reinterpret_cast<HANDLE>(platform_handles[0].value), num_bytes, guid);
#endif
return MOJO_RESULT_OK;
}
+ScopedSharedBufferHandle WrapReadOnlySharedMemoryRegion(
+ base::ReadOnlySharedMemoryRegion region) {
+ return WrapPlatformSharedMemoryRegion(
+ base::ReadOnlySharedMemoryRegion::TakeHandleForSerialization(
+ std::move(region)));
+}
+
+ScopedSharedBufferHandle WrapUnsafeSharedMemoryRegion(
+ base::UnsafeSharedMemoryRegion region) {
+ return WrapPlatformSharedMemoryRegion(
+ base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
+ std::move(region)));
+}
+
+ScopedSharedBufferHandle WrapWritableSharedMemoryRegion(
+ base::WritableSharedMemoryRegion region) {
+ return WrapPlatformSharedMemoryRegion(
+ base::WritableSharedMemoryRegion::TakeHandleForSerialization(
+ std::move(region)));
+}
+
+base::ReadOnlySharedMemoryRegion UnwrapReadOnlySharedMemoryRegion(
+ ScopedSharedBufferHandle handle) {
+ return base::ReadOnlySharedMemoryRegion::Deserialize(
+ UnwrapPlatformSharedMemoryRegion(std::move(handle)));
+}
+
+base::UnsafeSharedMemoryRegion UnwrapUnsafeSharedMemoryRegion(
+ ScopedSharedBufferHandle handle) {
+ return base::UnsafeSharedMemoryRegion::Deserialize(
+ UnwrapPlatformSharedMemoryRegion(std::move(handle)));
+}
+
+base::WritableSharedMemoryRegion UnwrapWritableSharedMemoryRegion(
+ ScopedSharedBufferHandle handle) {
+ return base::WritableSharedMemoryRegion::Deserialize(
+ UnwrapPlatformSharedMemoryRegion(std::move(handle)));
+}
+
#if defined(OS_MACOSX) && !defined(OS_IOS)
ScopedHandle WrapMachPort(mach_port_t port) {
kern_return_t kr =
@@ -155,7 +410,8 @@ ScopedHandle WrapMachPort(mach_port_t port) {
platform_handle.value = static_cast<uint64_t>(port);
MojoHandle mojo_handle;
- MojoResult result = MojoWrapPlatformHandle(&platform_handle, &mojo_handle);
+ MojoResult result =
+ MojoWrapPlatformHandle(&platform_handle, nullptr, &mojo_handle);
CHECK_EQ(result, MOJO_RESULT_OK);
return ScopedHandle(Handle(mojo_handle));
@@ -164,12 +420,13 @@ ScopedHandle WrapMachPort(mach_port_t port) {
MojoResult UnwrapMachPort(ScopedHandle handle, mach_port_t* port) {
MojoPlatformHandle platform_handle;
platform_handle.struct_size = sizeof(MojoPlatformHandle);
- MojoResult result =
- MojoUnwrapPlatformHandle(handle.release().value(), &platform_handle);
+ MojoResult result = MojoUnwrapPlatformHandle(handle.release().value(),
+ nullptr, &platform_handle);
if (result != MOJO_RESULT_OK)
return result;
- CHECK_EQ(platform_handle.type, MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT);
+ CHECK(platform_handle.type == MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT ||
+ platform_handle.type == MOJO_PLATFORM_HANDLE_TYPE_INVALID);
*port = static_cast<mach_port_t>(platform_handle.value);
return MOJO_RESULT_OK;
}
diff --git a/mojo/public/cpp/system/platform_handle.h b/mojo/public/cpp/system/platform_handle.h
index 801264efce..b18ceafacd 100644
--- a/mojo/public/cpp/system/platform_handle.h
+++ b/mojo/public/cpp/system/platform_handle.h
@@ -17,9 +17,14 @@
#include "base/files/file.h"
#include "base/logging.h"
#include "base/macros.h"
+#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/shared_memory_handle.h"
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "base/memory/writable_shared_memory_region.h"
#include "base/process/process_handle.h"
+#include "build/build_config.h"
#include "mojo/public/c/system/platform_handle.h"
+#include "mojo/public/cpp/platform/platform_handle.h"
#include "mojo/public/cpp/system/buffer.h"
#include "mojo/public/cpp/system/handle.h"
#include "mojo/public/cpp/system/system_export.h"
@@ -30,7 +35,20 @@
namespace mojo {
-#if defined(OS_POSIX)
+#if defined(OS_WIN)
+const MojoPlatformHandleType kPlatformFileHandleType =
+ MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE;
+
+const MojoPlatformHandleType kPlatformSharedBufferHandleType =
+ MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE;
+
+#elif defined(OS_FUCHSIA)
+const MojoPlatformHandleType kPlatformFileHandleType =
+ MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR;
+const MojoPlatformHandleType kPlatformSharedBufferHandleType =
+ MOJO_PLATFORM_HANDLE_TYPE_FUCHSIA_HANDLE;
+
+#elif defined(OS_POSIX)
const MojoPlatformHandleType kPlatformFileHandleType =
MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR;
@@ -42,15 +60,32 @@ const MojoPlatformHandleType kPlatformSharedBufferHandleType =
MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR;
#endif // defined(OS_MACOSX) && !defined(OS_IOS)
-#elif defined(OS_WIN)
-const MojoPlatformHandleType kPlatformFileHandleType =
- MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE;
+#endif // defined(OS_WIN)
-const MojoPlatformHandleType kPlatformSharedBufferHandleType =
- MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE;
-#endif // defined(OS_POSIX)
+// Used to specify the protection status of a base::SharedMemoryHandle memory
+// handle wrapped or unwrapped by mojo::WrapSharedMemoryHandle or
+// mojo::UnwrapSharedMemoryHandle below. See those functions for additional
+// details.
+enum class UnwrappedSharedMemoryHandleProtection {
+ // Indicates that the base::SharedMemoryHandle supports being mapped to
+ // writable memory regions.
+ kReadWrite,
+
+ // Indicates that the base::SharedMemoryHandle supports being mapped only to
+ // read-only memory regions.
+ kReadOnly,
+};
+
+// Wraps a PlatformHandle from the C++ platform support library as a Mojo
+// handle.
+MOJO_CPP_SYSTEM_EXPORT ScopedHandle WrapPlatformHandle(PlatformHandle handle);
+
+// Unwraps a Mojo handle to a PlatformHandle object from the C++ platform
+// support library.
+MOJO_CPP_SYSTEM_EXPORT PlatformHandle UnwrapPlatformHandle(ScopedHandle handle);
// Wraps a PlatformFile as a Mojo handle. Takes ownership of the file object.
+// If |platform_file| is valid, this will return a valid handle.
MOJO_CPP_SYSTEM_EXPORT
ScopedHandle WrapPlatformFile(base::PlatformFile platform_file);
@@ -58,23 +93,82 @@ ScopedHandle WrapPlatformFile(base::PlatformFile platform_file);
MOJO_CPP_SYSTEM_EXPORT
MojoResult UnwrapPlatformFile(ScopedHandle handle, base::PlatformFile* file);
+// DEPRECATED: Don't introduce new uses of base::SharedMemoryHandle, and please
+// attempt to avoid using this function. Use the new base shared memory APIs
+// (base::ReadOnlySharedMemoryRegion et al) and the corresponding wrap/unwrap
+// calls defined below instead.
+//
// Wraps a base::SharedMemoryHandle as a Mojo handle. Takes ownership of the
-// SharedMemoryHandle. Note that |read_only| is only an indicator of whether
-// |memory_handle| only supports read-only mapping. It does NOT have any
-// influence on the access control of the shared buffer object.
+// SharedMemoryHandle. |size| indicates the size of the underlying
+// base::SharedMemory object, and |current_protection| indicates whether or
+// not |memory_handle| supports being mapped to writable memory segments.
+//
+// ***** IMPORTANT. PLEASE READ BELOW CAREFULLY. *****
+//
+// THIS CALL DOES NOT IN ANY WAY AFFECT THE MEMORY PROTECTION STATUS OF THE
+// WRAPPED HANDLE.
+//
+// The |current_protection| argument is only an indication of the current memory
+// protection status of |memory_handle| as known by the caller.
+//
+// DO NOT wrap a writable |memory_handle| with |current_protection| set to
+// |UnwrappedSharedMemoryHandleProtection::kReadOnly|, as this will mislead
+// corresponding callers to |UnwrapSharedMemoryHandle()|: the subsequently
+// unwrapped SharedMemoryHandle will appear to be read-only on the surface, but
+// will still be mappable to a writable memory segment.
+//
+// Use base::SharedMemory::GetReadOnlyHandle() to acquire a read-only handle to
+// a shared memory object if you intend to wrap the handle with
+// |UnwrappedSharedMemoryHandleProtection::kReadOnly|.
MOJO_CPP_SYSTEM_EXPORT
ScopedSharedBufferHandle WrapSharedMemoryHandle(
const base::SharedMemoryHandle& memory_handle,
size_t size,
- bool read_only);
+ UnwrappedSharedMemoryHandleProtection current_protection);
+// DEPRECATED: Don't introduce new uses of base::SharedMemoryHandle, and please
+// attempt to avoid using this function. Use the new base shared memory APIs
+// (base::ReadOnlySharedMemoryRegion et al) and the corresponding wrap/unwrap
+// calls defined below instead.
+//
// Unwraps a base::SharedMemoryHandle from a Mojo handle. The caller assumes
-// responsibility for the lifetime of the SharedMemoryHandle.
+// responsibility for the lifetime of the SharedMemoryHandle. On success,
+// |*memory_handle| is set to a valid handle, |*size| is is set to the size of
+// that handle's underlying base::SharedMemory object, and
+// |*protection| indicates whether or not the handle may only be mapped
+// to a read-only memory segment.
+//
+// Note that if |*protection| is
+// |UnwrappedSharedMemoryHandleProtection::kReadOnly| upon return, writable
+// mapping of |*memory_handle| should not be attempted, and (unless there
+// is buggy code misusing WrapSharedMemoryHandle above) will always fail.
MOJO_CPP_SYSTEM_EXPORT MojoResult
UnwrapSharedMemoryHandle(ScopedSharedBufferHandle handle,
base::SharedMemoryHandle* memory_handle,
size_t* size,
- bool* read_only);
+ UnwrappedSharedMemoryHandleProtection* protection);
+
+// Helpers for wrapping and unwrapping new base shared memory API primitives.
+// If the input |region| is valid for the Wrap* functions, they will always
+// succeed and return a valid Mojo shared buffer handle.
+
+MOJO_CPP_SYSTEM_EXPORT ScopedSharedBufferHandle
+WrapReadOnlySharedMemoryRegion(base::ReadOnlySharedMemoryRegion region);
+
+MOJO_CPP_SYSTEM_EXPORT ScopedSharedBufferHandle
+WrapUnsafeSharedMemoryRegion(base::UnsafeSharedMemoryRegion region);
+
+MOJO_CPP_SYSTEM_EXPORT ScopedSharedBufferHandle
+WrapWritableSharedMemoryRegion(base::WritableSharedMemoryRegion region);
+
+MOJO_CPP_SYSTEM_EXPORT base::ReadOnlySharedMemoryRegion
+UnwrapReadOnlySharedMemoryRegion(ScopedSharedBufferHandle handle);
+
+MOJO_CPP_SYSTEM_EXPORT base::UnsafeSharedMemoryRegion
+UnwrapUnsafeSharedMemoryRegion(ScopedSharedBufferHandle handle);
+
+MOJO_CPP_SYSTEM_EXPORT base::WritableSharedMemoryRegion
+UnwrapWritableSharedMemoryRegion(ScopedSharedBufferHandle handle);
#if defined(OS_MACOSX) && !defined(OS_IOS)
// Wraps a mach_port_t as a Mojo handle. This takes a reference to the
diff --git a/mojo/public/cpp/system/scope_to_message_pipe.cc b/mojo/public/cpp/system/scope_to_message_pipe.cc
new file mode 100644
index 0000000000..3c6ff6713f
--- /dev/null
+++ b/mojo/public/cpp/system/scope_to_message_pipe.cc
@@ -0,0 +1,46 @@
+// 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.
+
+#include "mojo/public/cpp/system/scope_to_message_pipe.h"
+
+namespace mojo {
+namespace internal {
+
+namespace {
+
+void OnWatcherSignaled(std::unique_ptr<MessagePipeScoperBase> scoper,
+ MojoResult result,
+ const HandleSignalsState& state) {
+ DCHECK(scoper);
+ DCHECK_EQ(result, MOJO_RESULT_OK);
+ DCHECK(state.peer_closed());
+
+ // No work to do except for letting |scoper| go out of scope and be destroyed.
+}
+
+} // namespace
+
+MessagePipeScoperBase::MessagePipeScoperBase(ScopedMessagePipeHandle pipe)
+ : pipe_(std::move(pipe)),
+ pipe_watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC) {}
+
+MessagePipeScoperBase::~MessagePipeScoperBase() = default;
+
+// static
+void MessagePipeScoperBase::StartWatchingPipe(
+ std::unique_ptr<MessagePipeScoperBase> scoper) {
+ auto* unowned_scoper = scoper.get();
+
+ // NOTE: We intentionally use base::Passed here with the owned |scoper|. The
+ // way we use it here, there is no way for the watcher callback to be invoked
+ // more than once. If this expectation is ever violated,
+ // base::RepeatingCallback will CHECK-fail.
+ unowned_scoper->pipe_watcher_.Watch(
+ unowned_scoper->pipe_.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ base::BindRepeating(&OnWatcherSignaled, base::Passed(&scoper)));
+}
+
+} // namespace internal
+} // namespace mojo
diff --git a/mojo/public/cpp/system/scope_to_message_pipe.h b/mojo/public/cpp/system/scope_to_message_pipe.h
new file mode 100644
index 0000000000..73282deb11
--- /dev/null
+++ b/mojo/public/cpp/system/scope_to_message_pipe.h
@@ -0,0 +1,65 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_SCOPE_TO_MESSAGE_PIPE_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_SCOPE_TO_MESSAGE_PIPE_H_
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+#include "mojo/public/cpp/system/system_export.h"
+
+namespace mojo {
+
+namespace internal {
+
+// Owns the state and details to implement ScopeToMessagePipe (see below).
+class MOJO_CPP_SYSTEM_EXPORT MessagePipeScoperBase {
+ public:
+ explicit MessagePipeScoperBase(ScopedMessagePipeHandle pipe);
+ virtual ~MessagePipeScoperBase();
+
+ static void StartWatchingPipe(std::unique_ptr<MessagePipeScoperBase> scoper);
+
+ private:
+ ScopedMessagePipeHandle pipe_;
+ SimpleWatcher pipe_watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessagePipeScoperBase);
+};
+
+template <typename T>
+class MessagePipeScoper : public MessagePipeScoperBase {
+ public:
+ explicit MessagePipeScoper(T scoped_object, ScopedMessagePipeHandle pipe)
+ : MessagePipeScoperBase(std::move(pipe)),
+ scoped_object_(std::move(scoped_object)) {}
+ ~MessagePipeScoper() override = default;
+
+ private:
+ T scoped_object_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessagePipeScoper);
+};
+
+} // namespace internal
+
+// Binds the lifetime of |object| to that of |pipe|'s connection. When |pipe|'s
+// peer is closed, |pipe| will be closed and |object| will be destroyed. This
+// can be useful as a simple mechanism to track object lifetime across process
+// boundaries.
+template <typename T>
+void ScopeToMessagePipe(T object, ScopedMessagePipeHandle pipe) {
+ internal::MessagePipeScoperBase::StartWatchingPipe(
+ std::make_unique<internal::MessagePipeScoper<T>>(std::move(object),
+ std::move(pipe)));
+}
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_SCOPE_TO_MESSAGE_PIPE_H_
diff --git a/mojo/public/cpp/system/simple_watcher.cc b/mojo/public/cpp/system/simple_watcher.cc
index ae96faa395..6384c1abed 100644
--- a/mojo/public/cpp/system/simple_watcher.cc
+++ b/mojo/public/cpp/system/simple_watcher.cc
@@ -7,39 +7,42 @@
#include "base/bind.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
+#include "base/sequenced_task_runner.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/lock.h"
+#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/heap_profiler.h"
-#include "mojo/public/c/system/watcher.h"
+#include "mojo/public/c/system/trap.h"
namespace mojo {
-// Thread-safe Context object used to dispatch watch notifications from a
-// arbitrary threads.
+// Thread-safe Context object used to schedule trap events from arbitrary
+// threads.
class SimpleWatcher::Context : public base::RefCountedThreadSafe<Context> {
public:
- // Creates a |Context| instance for a new watch on |watcher|, to watch
- // |handle| for |signals|.
+ // Creates a |Context| instance for a new watch on |watcher|, to observe
+ // |signals| on |handle|.
static scoped_refptr<Context> Create(
base::WeakPtr<SimpleWatcher> watcher,
- scoped_refptr<base::SingleThreadTaskRunner> task_runner,
- WatcherHandle watcher_handle,
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ TrapHandle trap_handle,
Handle handle,
MojoHandleSignals signals,
+ MojoTriggerCondition condition,
int watch_id,
- MojoResult* watch_result) {
+ MojoResult* result) {
scoped_refptr<Context> context =
new Context(watcher, task_runner, watch_id);
- // If MojoWatch succeeds, it assumes ownership of a reference to |context|.
- // In that case, this reference is balanced in CallNotify() when |result| is
- // |MOJO_RESULT_CANCELLED|.
+ // If MojoAddTrigger succeeds, it effectively assumes ownership of a
+ // reference to |context|. In that case, this reference is balanced in
+ // CallNotify() when |result| is |MOJO_RESULT_CANCELLED|.
context->AddRef();
- *watch_result = MojoWatch(watcher_handle.value(), handle.value(), signals,
- context->value());
- if (*watch_result != MOJO_RESULT_OK) {
- // Balanced by the AddRef() above since watching failed.
+ *result = MojoAddTrigger(trap_handle.value(), handle.value(), signals,
+ condition, context->value(), nullptr);
+ if (*result != MOJO_RESULT_OK) {
+ // Balanced by the AddRef() above since MojoAddTrigger failed.
context->Release();
return nullptr;
}
@@ -47,16 +50,13 @@ class SimpleWatcher::Context : public base::RefCountedThreadSafe<Context> {
return context;
}
- static void CallNotify(uintptr_t context_value,
- MojoResult result,
- MojoHandleSignalsState signals_state,
- MojoWatcherNotificationFlags flags) {
- auto* context = reinterpret_cast<Context*>(context_value);
- context->Notify(result, signals_state, flags);
+ static void CallNotify(const MojoTrapEvent* event) {
+ auto* context = reinterpret_cast<Context*>(event->trigger_context);
+ context->Notify(event->result, event->signals_state, event->flags);
- // That was the last notification for the context. We can release the ref
- // owned by the watch, which may in turn delete the Context.
- if (result == MOJO_RESULT_CANCELLED)
+ // The trigger was removed. We can release the ref it owned, which in turn
+ // may delete the Context.
+ if (event->result == MOJO_RESULT_CANCELLED)
context->Release();
}
@@ -71,7 +71,7 @@ class SimpleWatcher::Context : public base::RefCountedThreadSafe<Context> {
friend class base::RefCountedThreadSafe<Context>;
Context(base::WeakPtr<SimpleWatcher> weak_watcher,
- scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
int watch_id)
: weak_watcher_(weak_watcher),
task_runner_(task_runner),
@@ -80,9 +80,9 @@ class SimpleWatcher::Context : public base::RefCountedThreadSafe<Context> {
void Notify(MojoResult result,
MojoHandleSignalsState signals_state,
- MojoWatcherNotificationFlags flags) {
+ MojoTrapEventFlags flags) {
if (result == MOJO_RESULT_CANCELLED) {
- // The SimpleWatcher may have explicitly cancelled this watch, so we don't
+ // The SimpleWatcher may have explicitly removed this trigger, so we don't
// bother dispatching the notification - it would be ignored anyway.
//
// TODO(rockot): This shouldn't really be necessary, but there are already
@@ -94,22 +94,24 @@ class SimpleWatcher::Context : public base::RefCountedThreadSafe<Context> {
return;
}
- if ((flags & MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM) &&
- task_runner_->RunsTasksOnCurrentThread() && weak_watcher_ &&
+ HandleSignalsState state(signals_state.satisfied_signals,
+ signals_state.satisfiable_signals);
+ if (!(flags & MOJO_TRAP_EVENT_FLAG_WITHIN_API_CALL) &&
+ task_runner_->RunsTasksInCurrentSequence() && weak_watcher_ &&
weak_watcher_->is_default_task_runner_) {
// System notifications will trigger from the task runner passed to
- // mojo::edk::InitIPCSupport(). In Chrome this happens to always be the
- // default task runner for the IO thread.
- weak_watcher_->OnHandleReady(watch_id_, result);
+ // mojo::core::ScopedIPCSupport. In Chrome this happens to always be
+ // the default task runner for the IO thread.
+ weak_watcher_->OnHandleReady(watch_id_, result, state);
} else {
task_runner_->PostTask(
FROM_HERE, base::Bind(&SimpleWatcher::OnHandleReady, weak_watcher_,
- watch_id_, result));
+ watch_id_, result, state));
}
}
const base::WeakPtr<SimpleWatcher> weak_watcher_;
- const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ const scoped_refptr<base::SequencedTaskRunner> task_runner_;
const int watch_id_;
base::Lock lock_;
@@ -118,18 +120,19 @@ class SimpleWatcher::Context : public base::RefCountedThreadSafe<Context> {
DISALLOW_COPY_AND_ASSIGN(Context);
};
-SimpleWatcher::SimpleWatcher(const tracked_objects::Location& from_here,
+SimpleWatcher::SimpleWatcher(const base::Location& from_here,
ArmingPolicy arming_policy,
- scoped_refptr<base::SingleThreadTaskRunner> runner)
+ scoped_refptr<base::SequencedTaskRunner> runner)
: arming_policy_(arming_policy),
task_runner_(std::move(runner)),
- is_default_task_runner_(task_runner_ ==
- base::ThreadTaskRunnerHandle::Get()),
+ is_default_task_runner_(base::ThreadTaskRunnerHandle::IsSet() &&
+ task_runner_ ==
+ base::ThreadTaskRunnerHandle::Get()),
heap_profiler_tag_(from_here.file_name()),
weak_factory_(this) {
- MojoResult rv = CreateWatcher(&Context::CallNotify, &watcher_handle_);
+ MojoResult rv = CreateTrap(&Context::CallNotify, &trap_handle_);
DCHECK_EQ(MOJO_RESULT_OK, rv);
- DCHECK(task_runner_->BelongsToCurrentThread());
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
}
SimpleWatcher::~SimpleWatcher() {
@@ -138,14 +141,15 @@ SimpleWatcher::~SimpleWatcher() {
}
bool SimpleWatcher::IsWatching() const {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return context_ != nullptr;
}
MojoResult SimpleWatcher::Watch(Handle handle,
MojoHandleSignals signals,
- const ReadyCallback& callback) {
- DCHECK(thread_checker_.CalledOnValidThread());
+ MojoTriggerCondition condition,
+ const ReadyCallbackWithState& callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!IsWatching());
DCHECK(!callback.is_null());
@@ -153,15 +157,15 @@ MojoResult SimpleWatcher::Watch(Handle handle,
handle_ = handle;
watch_id_ += 1;
- MojoResult watch_result = MOJO_RESULT_UNKNOWN;
+ MojoResult result = MOJO_RESULT_UNKNOWN;
context_ = Context::Create(weak_factory_.GetWeakPtr(), task_runner_,
- watcher_handle_.get(), handle_, signals, watch_id_,
- &watch_result);
+ trap_handle_.get(), handle_, signals, condition,
+ watch_id_, &result);
if (!context_) {
handle_.set_value(kInvalidHandleValue);
callback_.Reset();
- DCHECK_EQ(MOJO_RESULT_INVALID_ARGUMENT, watch_result);
- return watch_result;
+ DCHECK_EQ(MOJO_RESULT_INVALID_ARGUMENT, result);
+ return result;
}
if (arming_policy_ == ArmingPolicy::AUTOMATIC)
@@ -171,7 +175,7 @@ MojoResult SimpleWatcher::Watch(Handle handle,
}
void SimpleWatcher::Cancel() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// The watcher may have already been cancelled if the handle was closed.
if (!context_)
@@ -185,13 +189,13 @@ void SimpleWatcher::Cancel() {
handle_.set_value(kInvalidHandleValue);
callback_.Reset();
- // Ensure |context_| is unset by the time we call MojoCancelWatch, as may
+ // Ensure |context_| is unset by the time we call MojoRemoveTrigger, as it may
// re-enter the notification callback and we want to ensure |context_| is
// unset by then.
scoped_refptr<Context> context;
std::swap(context, context_);
MojoResult rv =
- MojoCancelWatch(watcher_handle_.get().value(), context->value());
+ MojoRemoveTrigger(trap_handle_.get().value(), context->value(), nullptr);
// It's possible this cancellation could race with a handle closure
// notification, in which case the watch may have already been implicitly
@@ -199,53 +203,60 @@ void SimpleWatcher::Cancel() {
DCHECK(rv == MOJO_RESULT_OK || rv == MOJO_RESULT_NOT_FOUND);
}
-MojoResult SimpleWatcher::Arm(MojoResult* ready_result) {
- DCHECK(thread_checker_.CalledOnValidThread());
- uint32_t num_ready_contexts = 1;
- uintptr_t ready_context;
- MojoResult local_ready_result;
- MojoHandleSignalsState ready_state;
- MojoResult rv =
- MojoArmWatcher(watcher_handle_.get().value(), &num_ready_contexts,
- &ready_context, &local_ready_result, &ready_state);
+MojoResult SimpleWatcher::Arm(MojoResult* ready_result,
+ HandleSignalsState* ready_state) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ uint32_t num_blocking_events = 1;
+ MojoTrapEvent blocking_event = {sizeof(blocking_event)};
+ MojoResult rv = MojoArmTrap(trap_handle_.get().value(), nullptr,
+ &num_blocking_events, &blocking_event);
if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
DCHECK(context_);
- DCHECK_EQ(1u, num_ready_contexts);
- DCHECK_EQ(context_->value(), ready_context);
+ DCHECK_EQ(1u, num_blocking_events);
+ DCHECK_EQ(context_->value(), blocking_event.trigger_context);
if (ready_result)
- *ready_result = local_ready_result;
+ *ready_result = blocking_event.result;
+ if (ready_state) {
+ *ready_state =
+ HandleSignalsState(blocking_event.signals_state.satisfied_signals,
+ blocking_event.signals_state.satisfiable_signals);
+ }
}
return rv;
}
void SimpleWatcher::ArmOrNotify() {
- DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Already cancelled, nothing to do.
if (!IsWatching())
return;
MojoResult ready_result;
- MojoResult rv = Arm(&ready_result);
+ HandleSignalsState ready_state;
+ MojoResult rv = Arm(&ready_result, &ready_state);
if (rv == MOJO_RESULT_OK)
return;
DCHECK_EQ(MOJO_RESULT_FAILED_PRECONDITION, rv);
- task_runner_->PostTask(FROM_HERE, base::Bind(&SimpleWatcher::OnHandleReady,
- weak_factory_.GetWeakPtr(),
- watch_id_, ready_result));
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&SimpleWatcher::OnHandleReady, weak_factory_.GetWeakPtr(),
+ watch_id_, ready_result, ready_state));
}
-void SimpleWatcher::OnHandleReady(int watch_id, MojoResult result) {
- DCHECK(thread_checker_.CalledOnValidThread());
+void SimpleWatcher::OnHandleReady(int watch_id,
+ MojoResult result,
+ const HandleSignalsState& state) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// This notification may be for a previously watched context, in which case
// we just ignore it.
if (watch_id != watch_id_)
return;
- ReadyCallback callback = callback_;
+ ReadyCallbackWithState callback = callback_;
if (result == MOJO_RESULT_CANCELLED) {
// Implicit cancellation due to someone closing the watched handle. We clear
// the SimppleWatcher's state before dispatching this.
@@ -259,17 +270,14 @@ void SimpleWatcher::OnHandleReady(int watch_id, MojoResult result) {
TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION event(heap_profiler_tag_);
base::WeakPtr<SimpleWatcher> weak_self = weak_factory_.GetWeakPtr();
- callback.Run(result);
+ callback.Run(result, state);
if (!weak_self)
return;
- if (unsatisfiable_)
- return;
-
// Prevent |MOJO_RESULT_FAILED_PRECONDITION| task spam by only notifying
// at most once in AUTOMATIC arming mode.
if (result == MOJO_RESULT_FAILED_PRECONDITION)
- unsatisfiable_ = true;
+ return;
if (arming_policy_ == ArmingPolicy::AUTOMATIC && IsWatching())
ArmOrNotify();
diff --git a/mojo/public/cpp/system/simple_watcher.h b/mojo/public/cpp/system/simple_watcher.h
index 9001884c97..cc916c8801 100644
--- a/mojo/public/cpp/system/simple_watcher.h
+++ b/mojo/public/cpp/system/simple_watcher.h
@@ -5,26 +5,28 @@
#ifndef MOJO_PUBLIC_CPP_SYSTEM_SIMPLE_WATCHER_H_
#define MOJO_PUBLIC_CPP_SYSTEM_SIMPLE_WATCHER_H_
+#include "base/bind.h"
#include "base/callback.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
-#include "base/threading/thread_checker.h"
-#include "base/threading/thread_task_runner_handle.h"
+#include "base/sequence_checker.h"
+#include "base/threading/sequenced_task_runner_handle.h"
#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/handle_signals_state.h"
#include "mojo/public/cpp/system/system_export.h"
-#include "mojo/public/cpp/system/watcher.h"
+#include "mojo/public/cpp/system/trap.h"
namespace base {
-class SingleThreadTaskRunner;
+class SequencedTaskRunner;
}
namespace mojo {
-// This provides a convenient thread-bound watcher implementation to safely
+// This provides a convenient sequence-bound watcher implementation to safely
// watch a single handle, dispatching state change notifications to an arbitrary
-// SingleThreadTaskRunner running on the same thread as the SimpleWatcher.
+// SequencedTaskRunner running on the same sequence as the SimpleWatcher.
//
// SimpleWatcher exposes the concept of "arming" from the low-level Watcher API.
// In general, a SimpleWatcher must be "armed" in order to dispatch a single
@@ -48,7 +50,13 @@ class MOJO_CPP_SYSTEM_EXPORT SimpleWatcher {
//
// Note that unlike the first two conditions, this callback may be invoked
// with |MOJO_RESULT_CANCELLED| even while the SimpleWatcher is disarmed.
- using ReadyCallback = base::Callback<void(MojoResult result)>;
+ using ReadyCallback = base::RepeatingCallback<void(MojoResult result)>;
+
+ // Like above but also receives the last known handle signal state at the time
+ // of the notification.
+ using ReadyCallbackWithState =
+ base::RepeatingCallback<void(MojoResult result,
+ const HandleSignalsState& state)>;
// Selects how this SimpleWatcher is to be armed.
enum class ArmingPolicy {
@@ -78,10 +86,10 @@ class MOJO_CPP_SYSTEM_EXPORT SimpleWatcher {
MANUAL,
};
- SimpleWatcher(const tracked_objects::Location& from_here,
+ SimpleWatcher(const base::Location& from_here,
ArmingPolicy arming_policy,
- scoped_refptr<base::SingleThreadTaskRunner> runner =
- base::ThreadTaskRunnerHandle::Get());
+ scoped_refptr<base::SequencedTaskRunner> runner =
+ base::SequencedTaskRunnerHandle::Get());
~SimpleWatcher();
// Indicates if the SimpleWatcher is currently watching a handle.
@@ -100,7 +108,7 @@ class MOJO_CPP_SYSTEM_EXPORT SimpleWatcher {
// explicitly called.
//
// Once the watch is started, |callback| may be called at any time on the
- // current thread until |Cancel()| is called or the handle is closed. Note
+ // current sequence until |Cancel()| is called or the handle is closed. Note
// that |callback| can be called for results other than
// |MOJO_RESULT_CANCELLED| only if the SimpleWatcher is currently armed. Use
// ArmingPolicy to configure how a SimpleWatcher is armed.
@@ -111,7 +119,20 @@ class MOJO_CPP_SYSTEM_EXPORT SimpleWatcher {
// Destroying the SimpleWatcher implicitly calls |Cancel()|.
MojoResult Watch(Handle handle,
MojoHandleSignals signals,
- const ReadyCallback& callback);
+ MojoTriggerCondition condition,
+ const ReadyCallbackWithState& callback);
+
+ // DEPRECATED: Please use the above signature instead.
+ //
+ // This watches a handle for |signals| to be satisfied, provided with a
+ // callback which takes only a MojoResult value corresponding to the result of
+ // a notification.
+ MojoResult Watch(Handle handle,
+ MojoHandleSignals signals,
+ const ReadyCallback& callback) {
+ return Watch(handle, signals, MOJO_WATCH_CONDITION_SATISFIED,
+ base::Bind(&DiscardReadyState, callback));
+ }
// Cancels the current watch. Once this returns, the ReadyCallback previously
// passed to |Watch()| will never be called again for this SimpleWatcher.
@@ -135,11 +156,14 @@ class MOJO_CPP_SYSTEM_EXPORT SimpleWatcher {
// is NOT armed, and this call fails with a return value of
// |MOJO_RESULT_FAILED_PRECONDITION|. In that case, what would have been the
// result code for that immediate notification is instead placed in
- // |*ready_result| if |ready_result| is non-null.
+ // |*ready_result| if |ready_result| is non-null, and the last known signaling
+ // state of the handle is placed in |*ready_state| if |ready_state| is
+ // non-null.
//
- // If the watcher is successfully armed, this returns |MOJO_RESULT_OK| and
- // |ready_result| is ignored.
- MojoResult Arm(MojoResult* ready_result = nullptr);
+ // If the watcher is successfully armed (or was already armed), this returns
+ // |MOJO_RESULT_OK| and |ready_result| and |ready_state| are ignored.
+ MojoResult Arm(MojoResult* ready_result = nullptr,
+ HandleSignalsState* ready_state = nullptr);
// Manually arms the SimpleWatcher OR posts a task to invoke the ReadyCallback
// with the ready result of the failed arming attempt.
@@ -153,7 +177,7 @@ class MOJO_CPP_SYSTEM_EXPORT SimpleWatcher {
void ArmOrNotify();
Handle handle() const { return handle_; }
- ReadyCallback ready_callback() const { return callback_; }
+ ReadyCallbackWithState ready_callback() const { return callback_; }
// Sets the tag used by the heap profiler.
// |tag| must be a const string literal.
@@ -164,28 +188,36 @@ class MOJO_CPP_SYSTEM_EXPORT SimpleWatcher {
private:
class Context;
- void OnHandleReady(int watch_id, MojoResult result);
+ static void DiscardReadyState(const ReadyCallback& callback,
+ MojoResult result,
+ const HandleSignalsState& state) {
+ callback.Run(result);
+ }
+
+ void OnHandleReady(int watch_id,
+ MojoResult result,
+ const HandleSignalsState& state);
- base::ThreadChecker thread_checker_;
+ SEQUENCE_CHECKER(sequence_checker_);
// The policy used to determine how this SimpleWatcher is armed.
const ArmingPolicy arming_policy_;
- // The TaskRunner of this SimpleWatcher's owning thread. This field is safe to
- // access from any thread.
- const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ // The TaskRunner of this SimpleWatcher's owning sequence. This field is safe
+ // to access from any sequence.
+ const scoped_refptr<base::SequencedTaskRunner> task_runner_;
- // Whether |task_runner_| is the same as base::ThreadTaskRunnerHandle::Get()
- // for the thread.
+ // Whether |task_runner_| is the same as
+ // base::SequencedTaskRunnerHandle::Get() for the thread.
const bool is_default_task_runner_;
- ScopedWatcherHandle watcher_handle_;
+ ScopedTrapHandle trap_handle_;
// A thread-safe context object corresponding to the currently active watch,
// if any.
scoped_refptr<Context> context_;
- // Fields below must only be accessed on the SimpleWatcher's owning thread.
+ // Fields below must only be accessed on the SimpleWatcher's owning sequence.
// The handle currently under watch. Not owned.
Handle handle_;
@@ -195,11 +227,7 @@ class MOJO_CPP_SYSTEM_EXPORT SimpleWatcher {
int watch_id_ = 0;
// The callback to call when the handle is signaled.
- ReadyCallback callback_;
-
- // Tracks if the SimpleWatcher has already notified of unsatisfiability. This
- // is used to prevent redundant notifications in AUTOMATIC mode.
- bool unsatisfiable_ = false;
+ ReadyCallbackWithState callback_;
// Tag used to ID memory allocations that originated from notifications in
// this watcher.
diff --git a/mojo/public/cpp/system/string_data_pipe_producer.cc b/mojo/public/cpp/system/string_data_pipe_producer.cc
new file mode 100644
index 0000000000..6b36b8ec06
--- /dev/null
+++ b/mojo/public/cpp/system/string_data_pipe_producer.cc
@@ -0,0 +1,138 @@
+// 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/system/string_data_pipe_producer.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/task_scheduler/post_task.h"
+
+namespace mojo {
+
+namespace {
+
+// Attempts to write data to a producer handle. Outputs the actual number of
+// bytes written in |*size|, and returns a result indicating the status of the
+// last attempted write operation.
+MojoResult WriteDataToProducerHandle(DataPipeProducerHandle producer,
+ const char* data,
+ size_t* size) {
+ void* dest;
+ uint32_t bytes_left = static_cast<uint32_t>(*size);
+
+ // We loop here since the pipe's available capacity may be larger than its
+ // *contiguous* capacity, and hence two independent consecutive two-phase
+ // writes may succeed. The goal here is to write as much of |data| as possible
+ // until we either run out of data or run out of capacity.
+ MojoResult result;
+ do {
+ uint32_t capacity = bytes_left;
+ result =
+ producer.BeginWriteData(&dest, &capacity, MOJO_WRITE_DATA_FLAG_NONE);
+ if (result == MOJO_RESULT_SHOULD_WAIT) {
+ result = MOJO_RESULT_OK;
+ break;
+ } else if (result != MOJO_RESULT_OK) {
+ break;
+ }
+
+ capacity = std::min(capacity, bytes_left);
+ memcpy(dest, data, capacity);
+ MojoResult end_result = producer.EndWriteData(capacity);
+ DCHECK_EQ(MOJO_RESULT_OK, end_result);
+
+ data += capacity;
+ bytes_left -= capacity;
+ } while (bytes_left);
+
+ *size -= bytes_left;
+ return result;
+}
+
+} // namespace
+
+StringDataPipeProducer::StringDataPipeProducer(
+ ScopedDataPipeProducerHandle producer)
+ : producer_(std::move(producer)),
+ watcher_(FROM_HERE,
+ SimpleWatcher::ArmingPolicy::AUTOMATIC,
+ base::SequencedTaskRunnerHandle::Get()),
+ weak_factory_(this) {}
+
+StringDataPipeProducer::~StringDataPipeProducer() = default;
+
+void StringDataPipeProducer::Write(const base::StringPiece& data,
+ AsyncWritingMode mode,
+ CompletionCallback callback) {
+ DCHECK(!callback_);
+ callback_ = std::move(callback);
+
+ // Immediately attempt to write data without making an extra copy. If we can
+ // get it all in one shot, we're done aleady.
+ size_t size = data.size();
+ MojoResult result =
+ WriteDataToProducerHandle(producer_.get(), data.data(), &size);
+ if (result == MOJO_RESULT_OK && size == data.size()) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&StringDataPipeProducer::InvokeCallback,
+ weak_factory_.GetWeakPtr(), MOJO_RESULT_OK));
+ } else {
+ // Copy whatever data didn't make the cut and try again when the pipe has
+ // some more capacity.
+ if (mode == AsyncWritingMode::STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION) {
+ data_ = std::string(data.data() + size, data.size() - size);
+ data_view_ = data_;
+ } else {
+ data_view_ = base::StringPiece(data.data() + size, data.size() - size);
+ }
+ watcher_.Watch(producer_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
+ MOJO_WATCH_CONDITION_SATISFIED,
+ base::Bind(&StringDataPipeProducer::OnProducerHandleReady,
+ base::Unretained(this)));
+ }
+}
+
+void StringDataPipeProducer::InvokeCallback(MojoResult result) {
+ // May delete |this|.
+ std::move(callback_).Run(result);
+}
+
+void StringDataPipeProducer::OnProducerHandleReady(
+ MojoResult ready_result,
+ const HandleSignalsState& state) {
+ bool failed = false;
+ size_t size = data_view_.size();
+ if (ready_result == MOJO_RESULT_OK) {
+ MojoResult write_result =
+ WriteDataToProducerHandle(producer_.get(), data_view_.data(), &size);
+ if (write_result != MOJO_RESULT_OK)
+ failed = true;
+ } else {
+ failed = true;
+ }
+
+ if (failed) {
+ watcher_.Cancel();
+
+ // May delete |this|.
+ std::move(callback_).Run(MOJO_RESULT_ABORTED);
+ return;
+ }
+
+ if (size == data_view_.size()) {
+ watcher_.Cancel();
+
+ // May delete |this|.
+ std::move(callback_).Run(MOJO_RESULT_OK);
+ return;
+ }
+
+ data_view_ =
+ base::StringPiece(data_view_.data() + size, data_view_.size() - size);
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/system/string_data_pipe_producer.h b/mojo/public/cpp/system/string_data_pipe_producer.h
new file mode 100644
index 0000000000..0e66f2bcfa
--- /dev/null
+++ b/mojo/public/cpp/system/string_data_pipe_producer.h
@@ -0,0 +1,90 @@
+// 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_SYSTEM_STRING_DATA_PIPE_PRODUCER_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_STRING_DATA_PIPE_PRODUCER_H_
+
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string_piece.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+#include "mojo/public/cpp/system/system_export.h"
+
+namespace mojo {
+
+// Helper class which takes ownership of a ScopedDataPipeProducerHandle and
+// assumes responsibility for feeding it the contents of a given string. This
+// takes care of waiting for pipe capacity as needed, and can notify callers
+// asynchronously when the operation is complete.
+//
+// Note that the StringDataPipeProducer must be kept alive until notified of
+// completion to ensure that all of the string's data is written to the pipe.
+// Premature destruction may result in partial or total truncation of data made
+// available to the consumer.
+class MOJO_CPP_SYSTEM_EXPORT StringDataPipeProducer {
+ public:
+ using CompletionCallback = base::OnceCallback<void(MojoResult result)>;
+
+ // Constructs a new StringDataPipeProducer which will write data to
+ // |producer|.
+ explicit StringDataPipeProducer(ScopedDataPipeProducerHandle producer);
+ ~StringDataPipeProducer();
+
+ // Describes what happens to the data when an async writing situation occurs
+ // (where the pipe cannot immediately accept all of the data).
+ enum class AsyncWritingMode {
+ // The |data| given to Write() may be invalidated before completion
+ // |callback| is called. The pending |data| is copied and owned by this
+ // class until all bytes are written.
+ STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION,
+ // The |data| given to Write() stays valid until the completion |callback|
+ // is called.
+ STRING_STAYS_VALID_UNTIL_COMPLETION
+ };
+
+ // Attempts to eventually write all of |data|. Invokes |callback|
+ // asynchronously when done. Note that |callback| IS allowed to delete this
+ // StringDataPipeProducer.
+ //
+ // If the data cannot be entirely written synchronously, then the |mode|
+ // determines how this class holds the pending data.
+ //
+ // If the write is successful |result| will be |MOJO_RESULT_OK|. Otherwise
+ // (e.g. if the producer detects the consumer is closed and the pipe has no
+ // remaining capacity) |result| will be |MOJO_RESULT_ABORTED|.
+ //
+ // Note that if the StringDataPipeProducer is destroyed before |callback| can
+ // be invoked, |callback| is *never* invoked, and the write will be
+ // permanently interrupted (and the producer handle closed) after making
+ // potentially only partial progress.
+ //
+ // Multiple writes may be performed in sequence (each one after the last
+ // completes), but Write() must not be called before the |callback| for the
+ // previous call to Write() (if any) has returned.
+ void Write(const base::StringPiece& data,
+ AsyncWritingMode mode,
+ CompletionCallback callback);
+
+ private:
+ void InvokeCallback(MojoResult result);
+ void OnProducerHandleReady(MojoResult ready_result,
+ const HandleSignalsState& state);
+
+ ScopedDataPipeProducerHandle producer_;
+ std::string data_;
+ base::StringPiece data_view_;
+ CompletionCallback callback_;
+ SimpleWatcher watcher_;
+ base::WeakPtrFactory<StringDataPipeProducer> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(StringDataPipeProducer);
+};
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_STRING_DATA_PIPE_PRODUCER_H_
diff --git a/mojo/public/cpp/system/tests/BUILD.gn b/mojo/public/cpp/system/tests/BUILD.gn
index 705d009c9c..c08f3c1099 100644
--- a/mojo/public/cpp/system/tests/BUILD.gn
+++ b/mojo/public/cpp/system/tests/BUILD.gn
@@ -7,15 +7,27 @@ source_set("tests") {
sources = [
"core_unittest.cc",
+ "data_pipe_drainer_unittest.cc",
+ "file_data_pipe_producer_unittest.cc",
+ "handle_signal_tracker_unittest.cc",
"handle_signals_state_unittest.cc",
+ "scope_to_message_pipe_unittest.cc",
"simple_watcher_unittest.cc",
+ "string_data_pipe_producer_unittest.cc",
"wait_set_unittest.cc",
"wait_unittest.cc",
]
+ if (!is_ios) {
+ sources += [ "invitation_unittest.cc" ]
+ }
+
deps = [
"//base",
+ "//base/test:test_support",
+ "//mojo/core/test:test_support",
"//mojo/public/c/system/tests",
+ "//mojo/public/cpp/platform",
"//mojo/public/cpp/system",
"//mojo/public/cpp/test_support:test_utils",
"//testing/gtest",
diff --git a/mojo/public/cpp/system/tests/core_unittest.cc b/mojo/public/cpp/system/tests/core_unittest.cc
index 40a94f008f..d40b18667f 100644
--- a/mojo/public/cpp/system/tests/core_unittest.cc
+++ b/mojo/public/cpp/system/tests/core_unittest.cc
@@ -22,9 +22,10 @@ namespace {
const MojoHandleSignals kSignalReadableWritable =
MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE;
-const MojoHandleSignals kSignalAll = MOJO_HANDLE_SIGNAL_READABLE |
- MOJO_HANDLE_SIGNAL_WRITABLE |
- MOJO_HANDLE_SIGNAL_PEER_CLOSED;
+const MojoHandleSignals kSignalAll =
+ MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_PEER_REMOTE |
+ MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED;
TEST(CoreCppTest, GetTimeTicksNow) {
const MojoTimeTicks start = GetTimeTicksNow();
@@ -133,32 +134,15 @@ TEST(CoreCppTest, Basic) {
{
MessagePipeHandle h_invalid;
EXPECT_FALSE(h_invalid.is_valid());
- EXPECT_EQ(
- MOJO_RESULT_INVALID_ARGUMENT,
- WriteMessageRaw(
- h_invalid, nullptr, 0, nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
- char buffer[10] = {0};
EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
- WriteMessageRaw(h_invalid,
- buffer,
- sizeof(buffer),
- nullptr,
- 0,
+ WriteMessageRaw(h_invalid, nullptr, 0, nullptr, 0,
MOJO_WRITE_MESSAGE_FLAG_NONE));
+ char buffer[10] = {0};
EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
- ReadMessageRaw(h_invalid,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- MOJO_READ_MESSAGE_FLAG_NONE));
- uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
+ WriteMessageRaw(h_invalid, buffer, sizeof(buffer), nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE));
EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
- ReadMessageRaw(h_invalid,
- buffer,
- &buffer_size,
- nullptr,
- nullptr,
+ ReadMessageRaw(h_invalid, nullptr, nullptr,
MOJO_READ_MESSAGE_FLAG_NONE));
// Basic tests of waiting and closing.
@@ -212,7 +196,8 @@ TEST(CoreCppTest, Basic) {
Wait(h0.get(), 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_FALSE(state.satisfiable_signals & MOJO_HANDLE_SIGNAL_WRITABLE);
+ EXPECT_FALSE(state.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE);
}
// |hv0| should have been closed when |h0| went out of scope, so this close
// should fail.
@@ -227,11 +212,7 @@ TEST(CoreCppTest, Basic) {
const char kHello[] = "hello";
const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello));
EXPECT_EQ(MOJO_RESULT_OK,
- WriteMessageRaw(h0.get(),
- kHello,
- kHelloSize,
- nullptr,
- 0,
+ WriteMessageRaw(h0.get(), kHello, kHelloSize - 1, nullptr, 0,
MOJO_WRITE_MESSAGE_FLAG_NONE));
MojoHandleSignalsState state;
@@ -240,17 +221,10 @@ TEST(CoreCppTest, Basic) {
EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals);
EXPECT_EQ(kSignalAll, state.satisfiable_signals);
- char buffer[10] = {0};
- uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
- EXPECT_EQ(MOJO_RESULT_OK,
- ReadMessageRaw(h1.get(),
- buffer,
- &buffer_size,
- nullptr,
- nullptr,
- MOJO_READ_MESSAGE_FLAG_NONE));
- EXPECT_EQ(kHelloSize, buffer_size);
- EXPECT_STREQ(kHello, buffer);
+ std::vector<uint8_t> bytes;
+ EXPECT_EQ(MOJO_RESULT_OK, ReadMessageRaw(h1.get(), &bytes, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kHello, std::string(bytes.begin(), bytes.end()));
// Send a handle over the previously-establish message pipe. Use the
// |MessagePipe| wrapper (to test it), which automatically creates a
@@ -261,12 +235,8 @@ TEST(CoreCppTest, Basic) {
const char kWorld[] = "world!";
const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
EXPECT_EQ(MOJO_RESULT_OK,
- WriteMessageRaw(mp.handle0.get(),
- kWorld,
- kWorldSize,
- nullptr,
- 0,
- MOJO_WRITE_MESSAGE_FLAG_NONE));
+ WriteMessageRaw(mp.handle0.get(), kWorld, kWorldSize - 1,
+ nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
// Send |mp.handle1| over |h1| to |h0|.
MojoHandle handles[5];
@@ -275,12 +245,8 @@ TEST(CoreCppTest, Basic) {
EXPECT_FALSE(mp.handle1.get().is_valid());
uint32_t handles_count = 1;
EXPECT_EQ(MOJO_RESULT_OK,
- WriteMessageRaw(h1.get(),
- kHello,
- kHelloSize,
- handles,
- handles_count,
- MOJO_WRITE_MESSAGE_FLAG_NONE));
+ WriteMessageRaw(h1.get(), kHello, kHelloSize - 1, handles,
+ handles_count, MOJO_WRITE_MESSAGE_FLAG_NONE));
// |handles[0]| should actually be invalid now.
EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handles[0]));
@@ -290,54 +256,32 @@ TEST(CoreCppTest, Basic) {
EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals);
EXPECT_EQ(kSignalAll, state.satisfiable_signals);
- memset(buffer, 0, sizeof(buffer));
- buffer_size = static_cast<uint32_t>(sizeof(buffer));
- for (size_t i = 0; i < arraysize(handles); i++)
- handles[i] = kInvalidHandleValue;
- handles_count = static_cast<uint32_t>(arraysize(handles));
- EXPECT_EQ(MOJO_RESULT_OK,
- ReadMessageRaw(h0.get(),
- buffer,
- &buffer_size,
- handles,
- &handles_count,
- MOJO_READ_MESSAGE_FLAG_NONE));
- EXPECT_EQ(kHelloSize, buffer_size);
- EXPECT_STREQ(kHello, buffer);
- EXPECT_EQ(1u, handles_count);
- EXPECT_NE(kInvalidHandleValue, handles[0]);
+ std::vector<ScopedHandle> read_handles;
+ EXPECT_EQ(MOJO_RESULT_OK, ReadMessageRaw(h0.get(), &bytes, &read_handles,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ EXPECT_EQ(kHello, std::string(bytes.begin(), bytes.end()));
+ EXPECT_EQ(1u, read_handles.size());
+ EXPECT_NE(kInvalidHandleValue, read_handles[0]->value());
// Read from the sent/received handle.
- mp.handle1.reset(MessagePipeHandle(handles[0]));
+ mp.handle1.reset(MessagePipeHandle(read_handles[0]->value()));
// Save |handles[0]| to check that it gets properly closed.
- hv0 = handles[0];
+ hv0 = read_handles[0].release().value();
EXPECT_EQ(MOJO_RESULT_OK,
Wait(mp.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &state));
EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals);
EXPECT_EQ(kSignalAll, state.satisfiable_signals);
- memset(buffer, 0, sizeof(buffer));
- buffer_size = static_cast<uint32_t>(sizeof(buffer));
- for (size_t i = 0; i < arraysize(handles); i++)
- handles[i] = kInvalidHandleValue;
- handles_count = static_cast<uint32_t>(arraysize(handles));
+ read_handles.clear();
EXPECT_EQ(MOJO_RESULT_OK,
- ReadMessageRaw(mp.handle1.get(),
- buffer,
- &buffer_size,
- handles,
- &handles_count,
+ ReadMessageRaw(mp.handle1.get(), &bytes, &read_handles,
MOJO_READ_MESSAGE_FLAG_NONE));
- EXPECT_EQ(kWorldSize, buffer_size);
- EXPECT_STREQ(kWorld, buffer);
- EXPECT_EQ(0u, handles_count);
+ EXPECT_EQ(kWorld, std::string(bytes.begin(), bytes.end()));
+ EXPECT_TRUE(read_handles.empty());
}
EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(hv0));
}
-
- // TODO(vtl): Test |CloseRaw()|.
- // TODO(vtl): Test |reset()| more thoroughly?
}
TEST(CoreCppTest, TearDownWithMessagesEnqueued) {
@@ -358,19 +302,11 @@ TEST(CoreCppTest, TearDownWithMessagesEnqueued) {
const char kWorld[] = "world!";
const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
EXPECT_EQ(MOJO_RESULT_OK,
- WriteMessageRaw(h2.get(),
- kWorld,
- kWorldSize,
- nullptr,
- 0,
+ WriteMessageRaw(h2.get(), kWorld, kWorldSize, nullptr, 0,
MOJO_WRITE_MESSAGE_FLAG_NONE));
// And also a message to |h3|.
EXPECT_EQ(MOJO_RESULT_OK,
- WriteMessageRaw(h3.get(),
- kWorld,
- kWorldSize,
- nullptr,
- 0,
+ WriteMessageRaw(h3.get(), kWorld, kWorldSize, nullptr, 0,
MOJO_WRITE_MESSAGE_FLAG_NONE));
// Send |h3| over |h1| to |h0|.
@@ -381,11 +317,7 @@ TEST(CoreCppTest, TearDownWithMessagesEnqueued) {
EXPECT_NE(kInvalidHandleValue, h3_value);
EXPECT_FALSE(h3.get().is_valid());
EXPECT_EQ(MOJO_RESULT_OK,
- WriteMessageRaw(h1.get(),
- kHello,
- kHelloSize,
- &h3_value,
- 1,
+ WriteMessageRaw(h1.get(), kHello, kHelloSize, &h3_value, 1,
MOJO_WRITE_MESSAGE_FLAG_NONE));
// |h3_value| should actually be invalid now.
EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(h3_value));
@@ -412,19 +344,11 @@ TEST(CoreCppTest, TearDownWithMessagesEnqueued) {
const char kWorld[] = "world!";
const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
EXPECT_EQ(MOJO_RESULT_OK,
- WriteMessageRaw(h2.get(),
- kWorld,
- kWorldSize,
- nullptr,
- 0,
+ WriteMessageRaw(h2.get(), kWorld, kWorldSize, nullptr, 0,
MOJO_WRITE_MESSAGE_FLAG_NONE));
// And also a message to |h3|.
EXPECT_EQ(MOJO_RESULT_OK,
- WriteMessageRaw(h3.get(),
- kWorld,
- kWorldSize,
- nullptr,
- 0,
+ WriteMessageRaw(h3.get(), kWorld, kWorldSize, nullptr, 0,
MOJO_WRITE_MESSAGE_FLAG_NONE));
// Send |h3| over |h1| to |h0|.
@@ -435,11 +359,7 @@ TEST(CoreCppTest, TearDownWithMessagesEnqueued) {
EXPECT_NE(kInvalidHandleValue, h3_value);
EXPECT_FALSE(h3.get().is_valid());
EXPECT_EQ(MOJO_RESULT_OK,
- WriteMessageRaw(h1.get(),
- kHello,
- kHelloSize,
- &h3_value,
- 1,
+ WriteMessageRaw(h1.get(), kHello, kHelloSize, &h3_value, 1,
MOJO_WRITE_MESSAGE_FLAG_NONE));
// |h3_value| should actually be invalid now.
EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(h3_value));
@@ -468,6 +388,7 @@ TEST(CoreCppTest, ScopedHandleMoveCtor) {
TEST(CoreCppTest, BasicSharedBuffer) {
ScopedSharedBufferHandle h0 = SharedBufferHandle::Create(100);
ASSERT_TRUE(h0.is_valid());
+ EXPECT_GE(h0->GetSize(), 100U);
// Map everything.
ScopedSharedBufferMapping mapping = h0->Map(100);
diff --git a/mojo/public/cpp/system/tests/data_pipe_drainer_unittest.cc b/mojo/public/cpp/system/tests/data_pipe_drainer_unittest.cc
new file mode 100644
index 0000000000..6de9de418d
--- /dev/null
+++ b/mojo/public/cpp/system/tests/data_pipe_drainer_unittest.cc
@@ -0,0 +1,62 @@
+// 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.
+
+#include "mojo/public/cpp/system/data_pipe_drainer.h"
+#include "base/callback.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace test {
+
+template <typename Functor>
+base::RepeatingClosure BindLambda(Functor callable) {
+ return base::BindRepeating([](Functor callable) { callable(); }, callable);
+}
+
+class DataPipeDrainerTest : public testing::Test,
+ public DataPipeDrainer::Client {
+ protected:
+ DataPipeDrainerTest() {
+ DataPipe pipe;
+ drainer_ = std::make_unique<DataPipeDrainer>(
+ this, std::move(pipe.consumer_handle));
+ producer_handle_ = std::move(pipe.producer_handle);
+ }
+
+ ScopedDataPipeProducerHandle producer_handle_;
+ base::RepeatingClosure completion_callback_;
+
+ void OnDataAvailable(const void* data, size_t num_bytes) override {
+ data_.append(static_cast<const char*>(data), num_bytes);
+ }
+
+ void OnDataComplete() override { completion_callback_.Run(); }
+
+ base::MessageLoop message_loop_;
+ std::string data_;
+ std::unique_ptr<DataPipeDrainer> drainer_;
+
+ DISALLOW_COPY_AND_ASSIGN(DataPipeDrainerTest);
+};
+
+TEST_F(DataPipeDrainerTest, TestCompleteIsCalledOnce) {
+ bool had_data_complete = false;
+
+ completion_callback_ = BindLambda([&had_data_complete]() {
+ EXPECT_FALSE(had_data_complete);
+ had_data_complete = true;
+ });
+ uint32_t size = 5;
+ EXPECT_EQ(MOJO_RESULT_OK, producer_handle_->WriteData(
+ "hello", &size, MOJO_WRITE_DATA_FLAG_NONE));
+ base::RunLoop().RunUntilIdle();
+ producer_handle_.reset();
+ base::RunLoop().RunUntilIdle();
+}
+
+} // namespace test
+} // namespace mojo
diff --git a/mojo/public/cpp/system/tests/file_data_pipe_producer_unittest.cc b/mojo/public/cpp/system/tests/file_data_pipe_producer_unittest.cc
new file mode 100644
index 0000000000..7c9b6e5e43
--- /dev/null
+++ b/mojo/public/cpp/system/tests/file_data_pipe_producer_unittest.cc
@@ -0,0 +1,338 @@
+// 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 <algorithm>
+#include <limits>
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/scoped_task_environment.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "mojo/public/cpp/system/file_data_pipe_producer.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+// Test helper. Reads a consumer handle, accumulating data into a string. Reads
+// until encountering an error (e.g. peer closure), at which point it invokes an
+// async callback.
+class DataPipeReader {
+ public:
+ explicit DataPipeReader(ScopedDataPipeConsumerHandle consumer_handle,
+ size_t read_size,
+ base::OnceClosure on_read_done)
+ : consumer_handle_(std::move(consumer_handle)),
+ read_size_(read_size),
+ on_read_done_(std::move(on_read_done)),
+ watcher_(FROM_HERE,
+ SimpleWatcher::ArmingPolicy::AUTOMATIC,
+ base::SequencedTaskRunnerHandle::Get()) {
+ watcher_.Watch(
+ consumer_handle_.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_WATCH_CONDITION_SATISFIED,
+ base::Bind(&DataPipeReader::OnDataAvailable, base::Unretained(this)));
+ }
+ ~DataPipeReader() = default;
+
+ const std::string& data() const { return data_; }
+
+ private:
+ void OnDataAvailable(MojoResult result, const HandleSignalsState& state) {
+ if (result == MOJO_RESULT_OK) {
+ uint32_t size = static_cast<uint32_t>(read_size_);
+ std::vector<char> buffer(size, 0);
+ MojoResult read_result;
+ do {
+ read_result = consumer_handle_->ReadData(buffer.data(), &size,
+ MOJO_READ_DATA_FLAG_NONE);
+ if (read_result == MOJO_RESULT_OK) {
+ std::copy(buffer.begin(), buffer.begin() + size,
+ std::back_inserter(data_));
+ }
+ } while (read_result == MOJO_RESULT_OK);
+
+ if (read_result == MOJO_RESULT_SHOULD_WAIT)
+ return;
+ }
+
+ if (result != MOJO_RESULT_CANCELLED)
+ watcher_.Cancel();
+
+ std::move(on_read_done_).Run();
+ }
+
+ ScopedDataPipeConsumerHandle consumer_handle_;
+ const size_t read_size_;
+ base::OnceClosure on_read_done_;
+ SimpleWatcher watcher_;
+ std::string data_;
+
+ DISALLOW_COPY_AND_ASSIGN(DataPipeReader);
+};
+
+class FileDataPipeProducerTest : public testing::Test {
+ public:
+ FileDataPipeProducerTest() { CHECK(temp_dir_.CreateUniqueTempDir()); }
+
+ ~FileDataPipeProducerTest() override = default;
+
+ protected:
+ base::FilePath CreateTempFileWithContents(const std::string& contents) {
+ base::FilePath temp_file_path = temp_dir_.GetPath().AppendASCII(
+ base::StringPrintf("tmp%d", tmp_file_id_++));
+ base::File temp_file(temp_file_path,
+ base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+ int bytes_written = temp_file.WriteAtCurrentPos(
+ contents.data(), static_cast<int>(contents.size()));
+ CHECK_EQ(static_cast<int>(contents.size()), bytes_written);
+ return temp_file_path;
+ }
+
+ static void WriteFromFileThenCloseWriter(
+ std::unique_ptr<FileDataPipeProducer> producer,
+ base::File file) {
+ FileDataPipeProducer* raw_producer = producer.get();
+ raw_producer->WriteFromFile(
+ std::move(file),
+ base::BindOnce([](std::unique_ptr<FileDataPipeProducer> producer,
+ MojoResult result) {},
+ std::move(producer)));
+ }
+
+ static void WriteFromFileThenCloseWriter(
+ std::unique_ptr<FileDataPipeProducer> producer,
+ base::File file,
+ size_t max_bytes) {
+ FileDataPipeProducer* raw_producer = producer.get();
+ raw_producer->WriteFromFile(
+ std::move(file), max_bytes,
+ base::BindOnce([](std::unique_ptr<FileDataPipeProducer> producer,
+ MojoResult result) {},
+ std::move(producer)));
+ }
+
+ static void WriteFromPathThenCloseWriter(
+ std::unique_ptr<FileDataPipeProducer> producer,
+ const base::FilePath& path) {
+ FileDataPipeProducer* raw_producer = producer.get();
+ raw_producer->WriteFromPath(
+ path, base::BindOnce([](std::unique_ptr<FileDataPipeProducer> producer,
+ MojoResult result) {},
+ std::move(producer)));
+ }
+
+ private:
+ base::test::ScopedTaskEnvironment task_environment_;
+ base::ScopedTempDir temp_dir_;
+ int tmp_file_id_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(FileDataPipeProducerTest);
+};
+
+struct DataPipeObserverData {
+ int num_read_errors = 0;
+ size_t bytes_read = 0;
+ int done_called = 0;
+};
+
+class TestObserver : public FileDataPipeProducer::Observer {
+ public:
+ explicit TestObserver(DataPipeObserverData* observer_data)
+ : observer_data_(observer_data) {}
+
+ void OnBytesRead(const void* data,
+ size_t num_bytes_read,
+ base::File::Error read_result) override {
+ base::AutoLock auto_lock(lock_);
+ if (read_result == base::File::FILE_OK)
+ observer_data_->bytes_read += num_bytes_read;
+ else
+ observer_data_->num_read_errors++;
+ }
+
+ void OnDoneReading() override {
+ base::AutoLock auto_lock(lock_);
+ observer_data_->done_called++;
+ }
+
+ private:
+ DataPipeObserverData* observer_data_;
+ // Observer may be called on any sequence.
+ base::Lock lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestObserver);
+};
+
+TEST_F(FileDataPipeProducerTest, WriteFromFile) {
+ const std::string kTestStringFragment = "Hello, world!";
+ constexpr size_t kNumRepetitions = 1000;
+ std::string test_string;
+ for (size_t i = 0; i < kNumRepetitions; ++i)
+ test_string += kTestStringFragment;
+
+ base::FilePath path = CreateTempFileWithContents(test_string);
+
+ base::RunLoop loop;
+ DataPipe pipe(16);
+ DataPipeReader reader(std::move(pipe.consumer_handle), 16,
+ loop.QuitClosure());
+
+ base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+ DataPipeObserverData observer_data;
+ auto observer = std::make_unique<TestObserver>(&observer_data);
+ WriteFromFileThenCloseWriter(
+ std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle),
+ std::move(observer)),
+ std::move(file));
+ loop.Run();
+
+ EXPECT_EQ(test_string, reader.data());
+ EXPECT_EQ(0, observer_data.num_read_errors);
+ EXPECT_EQ(test_string.size(), observer_data.bytes_read);
+ EXPECT_EQ(1, observer_data.done_called);
+}
+
+TEST_F(FileDataPipeProducerTest, WriteFromFilePartial) {
+ const std::string kTestString = "abcdefghijklmnopqrstuvwxyz";
+ base::FilePath path = CreateTempFileWithContents(kTestString);
+ constexpr size_t kBytesToWrite = 7;
+
+ base::RunLoop loop;
+ DataPipe pipe(static_cast<uint32_t>(kTestString.size()));
+ DataPipeReader reader(std::move(pipe.consumer_handle), kTestString.size(),
+ loop.QuitClosure());
+
+ base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+ DataPipeObserverData observer_data;
+ auto observer = std::make_unique<TestObserver>(&observer_data);
+ WriteFromFileThenCloseWriter(
+ std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle),
+ std::move(observer)),
+ std::move(file), kBytesToWrite);
+ loop.Run();
+
+ EXPECT_EQ(kTestString.substr(0, kBytesToWrite), reader.data());
+ EXPECT_EQ(0, observer_data.num_read_errors);
+ EXPECT_EQ(kBytesToWrite, observer_data.bytes_read);
+ EXPECT_EQ(1, observer_data.done_called);
+}
+
+TEST_F(FileDataPipeProducerTest, WriteFromInvalidFile) {
+ base::FilePath path(FILE_PATH_LITERAL("<nonexistent-file>"));
+ constexpr size_t kBytesToWrite = 7;
+
+ base::RunLoop loop;
+ DataPipe pipe(kBytesToWrite);
+ DataPipeObserverData observer_data;
+ auto observer = std::make_unique<TestObserver>(&observer_data);
+ DataPipeReader reader(std::move(pipe.consumer_handle), kBytesToWrite,
+ loop.QuitClosure());
+
+ base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+ WriteFromFileThenCloseWriter(
+ std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle),
+ std::move(observer)),
+ std::move(file), kBytesToWrite);
+ loop.Run();
+
+ EXPECT_EQ(0UL, reader.data().size());
+ EXPECT_EQ(0, observer_data.num_read_errors);
+ EXPECT_EQ(0UL, observer_data.bytes_read);
+ EXPECT_EQ(1, observer_data.done_called);
+}
+
+TEST_F(FileDataPipeProducerTest, WriteFromPath) {
+ const std::string kTestStringFragment = "Hello, world!";
+ constexpr size_t kNumRepetitions = 1000;
+ std::string test_string;
+ for (size_t i = 0; i < kNumRepetitions; ++i)
+ test_string += kTestStringFragment;
+
+ base::FilePath path = CreateTempFileWithContents(test_string);
+
+ base::RunLoop loop;
+ DataPipe pipe(16);
+ DataPipeReader reader(std::move(pipe.consumer_handle), 16,
+ loop.QuitClosure());
+
+ DataPipeObserverData observer_data;
+ auto observer = std::make_unique<TestObserver>(&observer_data);
+ WriteFromPathThenCloseWriter(
+ std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle),
+ std::move(observer)),
+ path);
+ loop.Run();
+
+ EXPECT_EQ(test_string, reader.data());
+ EXPECT_EQ(0, observer_data.num_read_errors);
+ EXPECT_EQ(test_string.size(), observer_data.bytes_read);
+ EXPECT_EQ(1, observer_data.done_called);
+}
+
+TEST_F(FileDataPipeProducerTest, TinyFile) {
+ const std::string kTestString = ".";
+ base::FilePath path = CreateTempFileWithContents(kTestString);
+ base::RunLoop loop;
+ DataPipe pipe(16);
+ DataPipeReader reader(std::move(pipe.consumer_handle), 16,
+ loop.QuitClosure());
+ DataPipeObserverData observer_data;
+ auto observer = std::make_unique<TestObserver>(&observer_data);
+ WriteFromPathThenCloseWriter(
+ std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle),
+ std::move(observer)),
+ path);
+ loop.Run();
+
+ EXPECT_EQ(kTestString, reader.data());
+ EXPECT_EQ(0, observer_data.num_read_errors);
+ EXPECT_EQ(kTestString.size(), observer_data.bytes_read);
+ EXPECT_EQ(1, observer_data.done_called);
+}
+
+TEST_F(FileDataPipeProducerTest, HugeFile) {
+ // We want a file size that is many times larger than the data pipe size.
+ // 63MB is large enough, while being small enough to fit in a typical tmpfs.
+ constexpr size_t kHugeFileSize = 63 * 1024 * 1024;
+ constexpr uint32_t kDataPipeSize = 512 * 1024;
+
+ std::string test_string(kHugeFileSize, 'a');
+ for (size_t i = 0; i + 3 < test_string.size(); i += 4) {
+ test_string[i + 1] = 'b';
+ test_string[i + 2] = 'c';
+ test_string[i + 3] = 'd';
+ }
+ base::FilePath path = CreateTempFileWithContents(test_string);
+
+ base::RunLoop loop;
+ DataPipe pipe(kDataPipeSize);
+ DataPipeReader reader(std::move(pipe.consumer_handle), kDataPipeSize,
+ loop.QuitClosure());
+
+ DataPipeObserverData observer_data;
+ auto observer = std::make_unique<TestObserver>(&observer_data);
+ WriteFromPathThenCloseWriter(
+ std::make_unique<FileDataPipeProducer>(std::move(pipe.producer_handle),
+ std::move(observer)),
+ path);
+ loop.Run();
+
+ EXPECT_EQ(test_string, reader.data());
+ EXPECT_EQ(0, observer_data.num_read_errors);
+ EXPECT_EQ(kHugeFileSize, observer_data.bytes_read);
+ EXPECT_EQ(1, observer_data.done_called);
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/system/tests/handle_signal_tracker_unittest.cc b/mojo/public/cpp/system/tests/handle_signal_tracker_unittest.cc
new file mode 100644
index 0000000000..b9922acb2b
--- /dev/null
+++ b/mojo/public/cpp/system/tests/handle_signal_tracker_unittest.cc
@@ -0,0 +1,116 @@
+// 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/system/handle_signal_tracker.h"
+
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.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 {
+
+class HandleSignalTrackerTest : public testing::Test {
+ public:
+ HandleSignalTrackerTest() {}
+ ~HandleSignalTrackerTest() override {}
+
+ void WaitForNextNotification(HandleSignalTracker* tracker) {
+ base::RunLoop loop;
+ tracker->set_notification_callback(base::Bind(
+ [](base::RunLoop* loop, const HandleSignalsState& signals_state) {
+ loop->Quit();
+ },
+ &loop));
+ loop.Run();
+ tracker->set_notification_callback(
+ HandleSignalTracker::NotificationCallback());
+ }
+
+ private:
+ base::test::ScopedTaskEnvironment task_environment_;
+
+ DISALLOW_COPY_AND_ASSIGN(HandleSignalTrackerTest);
+};
+
+TEST_F(HandleSignalTrackerTest, StartsWithCorrectState) {
+ MessagePipe pipe;
+ {
+ HandleSignalTracker tracker(pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE);
+ EXPECT_FALSE(tracker.last_known_state().readable());
+ }
+
+ WriteMessageRaw(pipe.handle1.get(), "hi", 2, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE);
+ Wait(pipe.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_WATCH_CONDITION_SATISFIED);
+
+ {
+ HandleSignalTracker tracker(pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE);
+ EXPECT_TRUE(tracker.last_known_state().readable());
+ }
+}
+
+TEST_F(HandleSignalTrackerTest, BasicTracking) {
+ MessagePipe pipe;
+ HandleSignalTracker tracker(pipe.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE);
+ EXPECT_FALSE(tracker.last_known_state().readable());
+
+ WriteMessageRaw(pipe.handle1.get(), "hi", 2, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE);
+ WaitForNextNotification(&tracker);
+ EXPECT_TRUE(tracker.last_known_state().readable());
+
+ std::vector<uint8_t> bytes;
+ ReadMessageRaw(pipe.handle0.get(), &bytes, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE);
+ WaitForNextNotification(&tracker);
+ EXPECT_FALSE(tracker.last_known_state().readable());
+}
+
+TEST_F(HandleSignalTrackerTest, DoesntUpdateOnIrrelevantChanges) {
+ MessagePipe pipe;
+ HandleSignalTracker readable_tracker(pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_READABLE);
+ HandleSignalTracker peer_closed_tracker(pipe.handle0.get(),
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+ EXPECT_FALSE(readable_tracker.last_known_state().readable());
+ EXPECT_FALSE(peer_closed_tracker.last_known_state().peer_closed());
+
+ WriteMessageRaw(pipe.handle1.get(), "hi", 2, nullptr, 0,
+ MOJO_WRITE_MESSAGE_FLAG_NONE);
+ WaitForNextNotification(&readable_tracker);
+ EXPECT_TRUE(readable_tracker.last_known_state().readable());
+ EXPECT_FALSE(readable_tracker.last_known_state().peer_closed());
+
+ // Closing the peer won't change the |readable_tracker|'s state since there's
+ // still an unread message. Therefore the tracker's last known state should
+ // continue to reflect the state prior to peer closure even after the handle's
+ // signals state has updated.
+ pipe.handle1.reset();
+ WaitForNextNotification(&peer_closed_tracker);
+ EXPECT_TRUE(pipe.handle0->QuerySignalsState().peer_closed());
+ EXPECT_TRUE(peer_closed_tracker.last_known_state().peer_closed());
+ EXPECT_FALSE(readable_tracker.last_known_state().peer_closed());
+
+ // Now read the message, which will ultimately trigger the pipe becoming
+ // unreadable.
+ std::vector<uint8_t> bytes;
+ ReadMessageRaw(pipe.handle0.get(), &bytes, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE);
+ WaitForNextNotification(&readable_tracker);
+ EXPECT_FALSE(readable_tracker.last_known_state().readable());
+
+ // And note that the |peer_closed_tracker| should not have seen the readable
+ // state change above since it's not relevant to its tracked signal.
+ EXPECT_TRUE(peer_closed_tracker.last_known_state().readable());
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/system/tests/handle_signals_state_unittest.cc b/mojo/public/cpp/system/tests/handle_signals_state_unittest.cc
index 82f538e17a..08e87cea20 100644
--- a/mojo/public/cpp/system/tests/handle_signals_state_unittest.cc
+++ b/mojo/public/cpp/system/tests/handle_signals_state_unittest.cc
@@ -21,21 +21,25 @@ TEST_F(HandleSignalsStateTest, SanityCheck) {
EXPECT_FALSE(empty_signals.readable());
EXPECT_FALSE(empty_signals.writable());
EXPECT_FALSE(empty_signals.peer_closed());
+ EXPECT_FALSE(empty_signals.peer_remote());
EXPECT_TRUE(empty_signals.never_readable());
EXPECT_TRUE(empty_signals.never_writable());
EXPECT_TRUE(empty_signals.never_peer_closed());
+ EXPECT_TRUE(empty_signals.never_peer_remote());
HandleSignalsState all_signals(
MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
- MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_PEER_REMOTE,
MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
- MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+ MOJO_HANDLE_SIGNAL_PEER_CLOSED | MOJO_HANDLE_SIGNAL_PEER_REMOTE);
EXPECT_TRUE(all_signals.readable());
EXPECT_TRUE(all_signals.writable());
EXPECT_TRUE(all_signals.peer_closed());
+ EXPECT_TRUE(all_signals.peer_remote());
EXPECT_FALSE(all_signals.never_readable());
EXPECT_FALSE(all_signals.never_writable());
EXPECT_FALSE(all_signals.never_peer_closed());
+ EXPECT_FALSE(all_signals.never_peer_remote());
}
} // namespace
diff --git a/mojo/public/cpp/system/tests/invitation_unittest.cc b/mojo/public/cpp/system/tests/invitation_unittest.cc
new file mode 100644
index 0000000000..d97c0d0123
--- /dev/null
+++ b/mojo/public/cpp/system/tests/invitation_unittest.cc
@@ -0,0 +1,318 @@
+// 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.
+
+#include <utility>
+
+#include "mojo/public/cpp/system/invitation.h"
+#include "base/base_paths.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/strings/string_piece.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/test/test_timeouts.h"
+#include "build/build_config.h"
+#include "mojo/public/cpp/platform/named_platform_channel.h"
+#include "mojo/public/cpp/platform/platform_channel.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "mojo/public/cpp/system/platform_handle.h"
+#include "mojo/public/cpp/system/wait.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/multiprocess_func_list.h"
+
+namespace mojo {
+namespace {
+
+enum class InvitationType {
+ kNormal,
+ kIsolated,
+};
+
+enum class TransportType {
+ kChannel,
+ kChannelServer,
+};
+
+// Switches and values to tell clients of parameterized test runs what mode they
+// should be testing against.
+const char kTransportTypeSwitch[] = "test-transport-type";
+const char kTransportTypeChannel[] = "channel";
+const char kTransportTypeChannelServer[] = "channel-server";
+
+class InvitationCppTest : public testing::Test,
+ public testing::WithParamInterface<TransportType> {
+ public:
+ InvitationCppTest() = default;
+ ~InvitationCppTest() override = default;
+
+ protected:
+ void LaunchChildTestClient(const std::string& test_client_name,
+ ScopedMessagePipeHandle* primordial_pipes,
+ size_t num_primordial_pipes,
+ InvitationType invitation_type,
+ TransportType transport_type,
+ const ProcessErrorCallback& error_callback = {}) {
+ base::CommandLine command_line(
+ base::GetMultiProcessTestChildBaseCommandLine());
+
+ base::LaunchOptions launch_options;
+ base::Optional<PlatformChannel> channel;
+ PlatformChannelEndpoint channel_endpoint;
+ PlatformChannelServerEndpoint server_endpoint;
+ if (transport_type == TransportType::kChannel) {
+ command_line.AppendSwitchASCII(kTransportTypeSwitch,
+ kTransportTypeChannel);
+ channel.emplace();
+ channel->PrepareToPassRemoteEndpoint(&launch_options, &command_line);
+#if defined(OS_WIN)
+ launch_options.start_hidden = true;
+#endif
+ channel_endpoint = channel->TakeLocalEndpoint();
+ } else if (transport_type == TransportType::kChannelServer) {
+ command_line.AppendSwitchASCII(kTransportTypeSwitch,
+ kTransportTypeChannelServer);
+#if defined(OS_FUCHSIA)
+ NOTREACHED() << "Named pipe support does not exist for Mojo on Fuchsia.";
+#else
+ NamedPlatformChannel::Options named_channel_options;
+#if !defined(OS_WIN)
+ CHECK(base::PathService::Get(base::DIR_TEMP,
+ &named_channel_options.socket_dir));
+#endif
+ NamedPlatformChannel named_channel(named_channel_options);
+ named_channel.PassServerNameOnCommandLine(&command_line);
+ server_endpoint = named_channel.TakeServerEndpoint();
+#endif
+ }
+
+ child_process_ = base::SpawnMultiProcessTestChild(
+ test_client_name, command_line, launch_options);
+ if (channel)
+ channel->RemoteProcessLaunchAttempted();
+
+ OutgoingInvitation invitation;
+ if (invitation_type == InvitationType::kNormal) {
+ for (uint64_t name = 0; name < num_primordial_pipes; ++name)
+ primordial_pipes[name] = invitation.AttachMessagePipe(name);
+ }
+
+ if (transport_type == TransportType::kChannel) {
+ DCHECK(channel_endpoint.is_valid());
+ if (invitation_type == InvitationType::kNormal) {
+ OutgoingInvitation::Send(std::move(invitation), child_process_.Handle(),
+ std::move(channel_endpoint), error_callback);
+ } else {
+ DCHECK(primordial_pipes);
+ DCHECK_EQ(num_primordial_pipes, 1u);
+ primordial_pipes[0] =
+ OutgoingInvitation::SendIsolated(std::move(channel_endpoint));
+ }
+ } else if (transport_type == TransportType::kChannelServer) {
+ DCHECK(server_endpoint.is_valid());
+ if (invitation_type == InvitationType::kNormal) {
+ OutgoingInvitation::Send(std::move(invitation), child_process_.Handle(),
+ std::move(server_endpoint), error_callback);
+ } else {
+ DCHECK(primordial_pipes);
+ DCHECK_EQ(num_primordial_pipes, 1u);
+ primordial_pipes[0] =
+ OutgoingInvitation::SendIsolated(std::move(server_endpoint));
+ }
+ }
+ }
+
+ void WaitForChildExit() {
+ int wait_result = -1;
+ base::WaitForMultiprocessTestChildExit(
+ child_process_, TestTimeouts::action_timeout(), &wait_result);
+ child_process_.Close();
+ EXPECT_EQ(0, wait_result);
+ }
+
+ static void WriteMessage(const ScopedMessagePipeHandle& pipe,
+ base::StringPiece message) {
+ CHECK_EQ(MOJO_RESULT_OK,
+ WriteMessageRaw(pipe.get(), message.data(), message.size(),
+ nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
+ }
+
+ static std::string ReadMessage(const ScopedMessagePipeHandle& pipe) {
+ CHECK_EQ(MOJO_RESULT_OK, Wait(pipe.get(), MOJO_HANDLE_SIGNAL_READABLE));
+
+ std::vector<uint8_t> payload;
+ std::vector<ScopedHandle> handles;
+ CHECK_EQ(MOJO_RESULT_OK, ReadMessageRaw(pipe.get(), &payload, &handles,
+ MOJO_READ_MESSAGE_FLAG_NONE));
+ return std::string(payload.begin(), payload.end());
+ }
+
+ private:
+ base::test::ScopedTaskEnvironment task_environment_;
+ base::Process child_process_;
+
+ DISALLOW_COPY_AND_ASSIGN(InvitationCppTest);
+};
+
+class TestClientBase : public InvitationCppTest {
+ public:
+ static PlatformChannelEndpoint RecoverEndpointFromCommandLine() {
+ const auto& command_line = *base::CommandLine::ForCurrentProcess();
+ std::string transport_type_string =
+ command_line.GetSwitchValueASCII(kTransportTypeSwitch);
+ CHECK(!transport_type_string.empty());
+ if (transport_type_string == kTransportTypeChannel) {
+ return PlatformChannel::RecoverPassedEndpointFromCommandLine(
+ command_line);
+ } else {
+ return NamedPlatformChannel::ConnectToServer(command_line);
+ }
+ }
+
+ static IncomingInvitation AcceptInvitation() {
+ return IncomingInvitation::Accept(RecoverEndpointFromCommandLine());
+ }
+
+ static ScopedMessagePipeHandle AcceptIsolatedInvitation() {
+ return IncomingInvitation::AcceptIsolated(RecoverEndpointFromCommandLine());
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestClientBase);
+};
+
+#define DEFINE_TEST_CLIENT(name) \
+ class name##Impl : public TestClientBase { \
+ public: \
+ static void Run(); \
+ }; \
+ MULTIPROCESS_TEST_MAIN(name) { \
+ name##Impl::Run(); \
+ return 0; \
+ } \
+ void name##Impl::Run()
+
+const char kTestMessage1[] = "hello";
+const char kTestMessage2[] = "hello";
+
+TEST_P(InvitationCppTest, Send) {
+ ScopedMessagePipeHandle pipe;
+ LaunchChildTestClient("CppSendClient", &pipe, 1, InvitationType::kNormal,
+ GetParam());
+ WriteMessage(pipe, kTestMessage1);
+ WaitForChildExit();
+}
+
+DEFINE_TEST_CLIENT(CppSendClient) {
+ auto invitation = AcceptInvitation();
+ auto pipe = invitation.ExtractMessagePipe(0);
+ CHECK_EQ(kTestMessage1, ReadMessage(pipe));
+}
+
+TEST_P(InvitationCppTest, SendIsolated) {
+ ScopedMessagePipeHandle pipe;
+ LaunchChildTestClient("CppSendIsolatedClient", &pipe, 1,
+ InvitationType::kIsolated, GetParam());
+ WriteMessage(pipe, kTestMessage1);
+ WaitForChildExit();
+}
+
+DEFINE_TEST_CLIENT(CppSendIsolatedClient) {
+ auto pipe = AcceptIsolatedInvitation();
+ CHECK_EQ(kTestMessage1, ReadMessage(pipe));
+}
+
+TEST_P(InvitationCppTest, SendWithMultiplePipes) {
+ ScopedMessagePipeHandle pipes[2];
+ LaunchChildTestClient("CppSendWithMultiplePipesClient", pipes, 2,
+ InvitationType::kNormal, GetParam());
+ WriteMessage(pipes[0], kTestMessage1);
+ WriteMessage(pipes[1], kTestMessage2);
+ WaitForChildExit();
+}
+
+DEFINE_TEST_CLIENT(CppSendWithMultiplePipesClient) {
+ auto invitation = AcceptInvitation();
+ auto pipe0 = invitation.ExtractMessagePipe(0);
+ auto pipe1 = invitation.ExtractMessagePipe(1);
+ CHECK_EQ(kTestMessage1, ReadMessage(pipe0));
+ CHECK_EQ(kTestMessage2, ReadMessage(pipe1));
+}
+
+TEST(InvitationCppTest_NoParam, SendIsolatedInvitationWithDuplicateName) {
+ base::test::ScopedTaskEnvironment task_environment;
+ PlatformChannel channel1;
+ PlatformChannel channel2;
+ const char kConnectionName[] = "foo";
+ ScopedMessagePipeHandle pipe0 = OutgoingInvitation::SendIsolated(
+ channel1.TakeLocalEndpoint(), kConnectionName);
+ ScopedMessagePipeHandle pipe1 = OutgoingInvitation::SendIsolated(
+ channel2.TakeLocalEndpoint(), kConnectionName);
+ Wait(pipe0.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED);
+}
+
+const char kErrorMessage[] = "ur bad :{{";
+const char kDisconnectMessage[] = "go away plz";
+
+TEST_P(InvitationCppTest, ProcessErrors) {
+ ProcessErrorCallback actual_error_callback;
+
+ ScopedMessagePipeHandle pipe;
+ LaunchChildTestClient(
+ "CppProcessErrorsClient", &pipe, 1, InvitationType::kNormal, GetParam(),
+ base::BindLambdaForTesting([&](const std::string& error_message) {
+ ASSERT_TRUE(actual_error_callback);
+ actual_error_callback.Run(error_message);
+ }));
+
+ MojoMessageHandle message;
+ Wait(pipe.get(), MOJO_HANDLE_SIGNAL_READABLE);
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoReadMessage(pipe.get().value(), nullptr, &message));
+
+ // Report the message as bad and expect to be notified through the process
+ // error callback.
+ base::RunLoop error_loop;
+ actual_error_callback =
+ base::BindLambdaForTesting([&](const std::string& error_message) {
+ EXPECT_NE(error_message.find(kErrorMessage), std::string::npos);
+ error_loop.Quit();
+ });
+ EXPECT_EQ(MOJO_RESULT_OK,
+ MojoNotifyBadMessage(message, kErrorMessage, sizeof(kErrorMessage),
+ nullptr));
+ error_loop.Run();
+ EXPECT_EQ(MOJO_RESULT_OK, MojoDestroyMessage(message));
+
+ // TODO(https://crbug.com/846833): Once we can rework the C++ invitation API
+ // to also notify on disconnect, this test should cover that too. For now we
+ // just tell the process to exit and wait for it to do.
+ WriteMessage(pipe, kDisconnectMessage);
+ WaitForChildExit();
+}
+
+DEFINE_TEST_CLIENT(CppProcessErrorsClient) {
+ auto invitation = AcceptInvitation();
+ auto pipe = invitation.ExtractMessagePipe(0);
+ WriteMessage(pipe, "ignored");
+ EXPECT_EQ(kDisconnectMessage, ReadMessage(pipe));
+}
+
+INSTANTIATE_TEST_CASE_P(,
+ InvitationCppTest,
+ testing::Values(TransportType::kChannel
+#if !defined(OS_FUCHSIA)
+ ,
+ TransportType::kChannelServer
+#endif
+ ));
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/system/tests/scope_to_message_pipe_unittest.cc b/mojo/public/cpp/system/tests/scope_to_message_pipe_unittest.cc
new file mode 100644
index 0000000000..562ef221da
--- /dev/null
+++ b/mojo/public/cpp/system/tests/scope_to_message_pipe_unittest.cc
@@ -0,0 +1,69 @@
+// 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.
+
+#include "mojo/public/cpp/system/scope_to_message_pipe.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/scoped_task_environment.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+class RunCallbackOnDestruction {
+ public:
+ explicit RunCallbackOnDestruction(base::OnceClosure destruction_callback)
+ : destruction_callback_(std::move(destruction_callback)) {}
+ ~RunCallbackOnDestruction() { std::move(destruction_callback_).Run(); }
+
+ private:
+ base::OnceClosure destruction_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(RunCallbackOnDestruction);
+};
+
+class ScopeToMessagePipeTest : public testing::Test {
+ public:
+ ScopeToMessagePipeTest() = default;
+ ~ScopeToMessagePipeTest() override = default;
+
+ private:
+ base::test::ScopedTaskEnvironment task_environment_;
+ DISALLOW_COPY_AND_ASSIGN(ScopeToMessagePipeTest);
+};
+
+TEST_F(ScopeToMessagePipeTest, ObjectDestroyedOnPeerClosure) {
+ base::RunLoop wait_for_destruction;
+ MessagePipe pipe;
+ ScopeToMessagePipe(std::make_unique<RunCallbackOnDestruction>(
+ wait_for_destruction.QuitClosure()),
+ std::move(pipe.handle0));
+ pipe.handle1.reset();
+ wait_for_destruction.Run();
+}
+
+TEST_F(ScopeToMessagePipeTest, PipeClosedOnPeerClosure) {
+ base::RunLoop wait_for_pipe_closure;
+ MessagePipe pipe;
+ SimpleWatcher watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+ watcher.Watch(pipe.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ base::BindLambdaForTesting(
+ [&](MojoResult result, const HandleSignalsState& state) {
+ EXPECT_EQ(result, MOJO_RESULT_CANCELLED);
+ wait_for_pipe_closure.Quit();
+ }));
+
+ ScopeToMessagePipe(42, std::move(pipe.handle0));
+ pipe.handle1.reset();
+ wait_for_pipe_closure.Run();
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/system/tests/simple_watcher_unittest.cc b/mojo/public/cpp/system/tests/simple_watcher_unittest.cc
index 795f262c4e..a94608437f 100644
--- a/mojo/public/cpp/system/tests/simple_watcher_unittest.cc
+++ b/mojo/public/cpp/system/tests/simple_watcher_unittest.cc
@@ -13,6 +13,7 @@
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/data_pipe.h"
#include "mojo/public/cpp/system/message_pipe.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -50,7 +51,8 @@ TEST_F(SimpleWatcherTest, WatchBasic) {
bool notified = false;
base::RunLoop run_loop;
- SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+ SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC,
+ base::SequencedTaskRunnerHandle::Get());
EXPECT_EQ(MOJO_RESULT_OK,
b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
OnReady([&](MojoResult result) {
@@ -73,20 +75,55 @@ TEST_F(SimpleWatcherTest, WatchUnsatisfiable) {
CreateMessagePipe(nullptr, &a, &b);
a.reset();
- SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL);
+ SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL,
+ base::SequencedTaskRunnerHandle::Get());
EXPECT_EQ(
MOJO_RESULT_OK,
b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached()));
EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, b_watcher.Arm());
}
+TEST_F(SimpleWatcherTest, WatchFailedPreconditionNoSpam) {
+ DataPipe pipe;
+ bool had_failed_precondition = false;
+
+ SimpleWatcher watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+ MojoResult rc =
+ watcher.Watch(pipe.consumer_handle.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ OnReady([&](MojoResult result) {
+ EXPECT_FALSE(had_failed_precondition);
+ switch (result) {
+ case MOJO_RESULT_OK:
+ const void* begin;
+ uint32_t num_bytes;
+ pipe.consumer_handle->BeginReadData(
+ &begin, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
+ pipe.consumer_handle->EndReadData(num_bytes);
+ break;
+ case MOJO_RESULT_FAILED_PRECONDITION:
+ had_failed_precondition = true;
+ break;
+ }
+ }));
+ EXPECT_EQ(MOJO_RESULT_OK, rc);
+
+ uint32_t size = 5;
+ EXPECT_EQ(MOJO_RESULT_OK, pipe.producer_handle->WriteData(
+ "hello", &size, MOJO_WRITE_DATA_FLAG_NONE));
+ base::RunLoop().RunUntilIdle();
+ pipe.producer_handle.reset();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(had_failed_precondition);
+}
+
TEST_F(SimpleWatcherTest, WatchInvalidHandle) {
ScopedMessagePipeHandle a, b;
CreateMessagePipe(nullptr, &a, &b);
a.reset();
b.reset();
- SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+ SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC,
+ base::SequencedTaskRunnerHandle::Get());
EXPECT_EQ(
MOJO_RESULT_INVALID_ARGUMENT,
b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached()));
@@ -98,7 +135,8 @@ TEST_F(SimpleWatcherTest, Cancel) {
CreateMessagePipe(nullptr, &a, &b);
base::RunLoop run_loop;
- SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+ SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC,
+ base::SequencedTaskRunnerHandle::Get());
EXPECT_EQ(
MOJO_RESULT_OK,
b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached()));
@@ -120,7 +158,8 @@ TEST_F(SimpleWatcherTest, CancelOnClose) {
CreateMessagePipe(nullptr, &a, &b);
base::RunLoop run_loop;
- SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+ SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC,
+ base::SequencedTaskRunnerHandle::Get());
EXPECT_EQ(MOJO_RESULT_OK,
b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
OnReady([&](MojoResult result) {
@@ -142,7 +181,8 @@ TEST_F(SimpleWatcherTest, CancelOnDestruction) {
CreateMessagePipe(nullptr, &a, &b);
base::RunLoop run_loop;
{
- SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+ SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC,
+ base::SequencedTaskRunnerHandle::Get());
EXPECT_EQ(
MOJO_RESULT_OK,
b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached()));
@@ -163,7 +203,8 @@ TEST_F(SimpleWatcherTest, CloseAndCancel) {
ScopedMessagePipeHandle a, b;
CreateMessagePipe(nullptr, &a, &b);
- SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC);
+ SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC,
+ base::SequencedTaskRunnerHandle::Get());
EXPECT_EQ(MOJO_RESULT_OK,
b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
OnReady([](MojoResult result) { FAIL(); })));
@@ -183,7 +224,8 @@ TEST_F(SimpleWatcherTest, UnarmedCancel) {
ScopedMessagePipeHandle a, b;
CreateMessagePipe(nullptr, &a, &b);
- SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL);
+ SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL,
+ base::SequencedTaskRunnerHandle::Get());
base::RunLoop loop;
EXPECT_EQ(MOJO_RESULT_OK,
b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
@@ -206,7 +248,8 @@ TEST_F(SimpleWatcherTest, ManualArming) {
ScopedMessagePipeHandle a, b;
CreateMessagePipe(nullptr, &a, &b);
- SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL);
+ SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL,
+ base::SequencedTaskRunnerHandle::Get());
base::RunLoop loop;
EXPECT_EQ(MOJO_RESULT_OK,
b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE,
diff --git a/mojo/public/cpp/system/tests/string_data_pipe_producer_unittest.cc b/mojo/public/cpp/system/tests/string_data_pipe_producer_unittest.cc
new file mode 100644
index 0000000000..9bb5d63ced
--- /dev/null
+++ b/mojo/public/cpp/system/tests/string_data_pipe_producer_unittest.cc
@@ -0,0 +1,208 @@
+// 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 <algorithm>
+#include <list>
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/strings/string_piece.h"
+#include "base/test/scoped_task_environment.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+#include "mojo/public/cpp/system/string_data_pipe_producer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace {
+
+// Test helper. Reads a consumer handle, accumulating data into a string. Reads
+// until encountering an error (e.g. peer closure), at which point it invokes an
+// async callback.
+class DataPipeReader {
+ public:
+ explicit DataPipeReader(ScopedDataPipeConsumerHandle consumer_handle,
+ base::OnceClosure on_read_done)
+ : consumer_handle_(std::move(consumer_handle)),
+ on_read_done_(std::move(on_read_done)),
+ watcher_(FROM_HERE,
+ SimpleWatcher::ArmingPolicy::AUTOMATIC,
+ base::SequencedTaskRunnerHandle::Get()) {
+ watcher_.Watch(
+ consumer_handle_.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_WATCH_CONDITION_SATISFIED,
+ base::Bind(&DataPipeReader::OnDataAvailable, base::Unretained(this)));
+ }
+ ~DataPipeReader() = default;
+
+ const std::string& data() const { return data_; }
+
+ private:
+ void OnDataAvailable(MojoResult result, const HandleSignalsState& state) {
+ if (result == MOJO_RESULT_OK) {
+ uint32_t size = 64;
+ std::vector<char> buffer(size, 0);
+ MojoResult read_result;
+ do {
+ read_result = consumer_handle_->ReadData(buffer.data(), &size,
+ MOJO_READ_DATA_FLAG_NONE);
+ if (read_result == MOJO_RESULT_OK) {
+ std::copy(buffer.begin(), buffer.begin() + size,
+ std::back_inserter(data_));
+ }
+ } while (read_result == MOJO_RESULT_OK);
+
+ if (read_result == MOJO_RESULT_SHOULD_WAIT)
+ return;
+ }
+
+ if (result != MOJO_RESULT_CANCELLED)
+ watcher_.Cancel();
+
+ std::move(on_read_done_).Run();
+ }
+
+ ScopedDataPipeConsumerHandle consumer_handle_;
+ base::OnceClosure on_read_done_;
+ SimpleWatcher watcher_;
+ std::string data_;
+
+ DISALLOW_COPY_AND_ASSIGN(DataPipeReader);
+};
+
+class StringDataPipeProducerTest : public testing::Test {
+ public:
+ StringDataPipeProducerTest() = default;
+ ~StringDataPipeProducerTest() override = default;
+
+ protected:
+ static void WriteStringThenCloseProducer(
+ std::unique_ptr<StringDataPipeProducer> producer,
+ const base::StringPiece& str,
+ StringDataPipeProducer::AsyncWritingMode mode) {
+ StringDataPipeProducer* raw_producer = producer.get();
+ raw_producer->Write(
+ str, mode,
+ base::BindOnce([](std::unique_ptr<StringDataPipeProducer> producer,
+ MojoResult result) {},
+ std::move(producer)));
+ }
+
+ static void WriteStringsThenCloseProducer(
+ std::unique_ptr<StringDataPipeProducer> producer,
+ std::list<base::StringPiece> strings,
+ StringDataPipeProducer::AsyncWritingMode mode) {
+ StringDataPipeProducer* raw_producer = producer.get();
+ base::StringPiece str = strings.front();
+ strings.pop_front();
+ raw_producer->Write(str, mode,
+ base::BindOnce(
+ [](std::unique_ptr<StringDataPipeProducer> producer,
+ std::list<base::StringPiece> strings,
+ StringDataPipeProducer::AsyncWritingMode mode,
+ MojoResult result) {
+ if (!strings.empty())
+ WriteStringsThenCloseProducer(
+ std::move(producer), std::move(strings),
+ mode);
+ },
+ std::move(producer), std::move(strings), mode));
+ }
+
+ private:
+ base::test::ScopedTaskEnvironment task_environment_;
+
+ DISALLOW_COPY_AND_ASSIGN(StringDataPipeProducerTest);
+};
+
+TEST_F(StringDataPipeProducerTest, EqualCapacity) {
+ const std::string kTestString = "Hello, world!";
+
+ base::RunLoop loop;
+ mojo::DataPipe pipe(static_cast<uint32_t>(kTestString.size()));
+ DataPipeReader reader(std::move(pipe.consumer_handle), loop.QuitClosure());
+ WriteStringThenCloseProducer(
+ std::make_unique<StringDataPipeProducer>(std::move(pipe.producer_handle)),
+ kTestString,
+ StringDataPipeProducer::AsyncWritingMode::
+ STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION);
+ loop.Run();
+
+ EXPECT_EQ(kTestString, reader.data());
+}
+
+TEST_F(StringDataPipeProducerTest, UnderCapacity) {
+ const std::string kTestString = "Hello, world!";
+
+ base::RunLoop loop;
+ mojo::DataPipe pipe(static_cast<uint32_t>(kTestString.size() * 2));
+ DataPipeReader reader(std::move(pipe.consumer_handle), loop.QuitClosure());
+ WriteStringThenCloseProducer(
+ std::make_unique<StringDataPipeProducer>(std::move(pipe.producer_handle)),
+ kTestString,
+ StringDataPipeProducer::AsyncWritingMode::
+ STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION);
+ loop.Run();
+
+ EXPECT_EQ(kTestString, reader.data());
+}
+
+TEST_F(StringDataPipeProducerTest, OverCapacity) {
+ const std::string kTestString = "Hello, world!";
+
+ base::RunLoop loop;
+ mojo::DataPipe pipe(static_cast<uint32_t>(kTestString.size() / 2));
+ DataPipeReader reader(std::move(pipe.consumer_handle), loop.QuitClosure());
+ WriteStringThenCloseProducer(
+ std::make_unique<StringDataPipeProducer>(std::move(pipe.producer_handle)),
+ kTestString,
+ StringDataPipeProducer::AsyncWritingMode::
+ STRING_STAYS_VALID_UNTIL_COMPLETION);
+ loop.Run();
+
+ EXPECT_EQ(kTestString, reader.data());
+}
+
+TEST_F(StringDataPipeProducerTest, TinyPipe) {
+ const std::string kTestString = "Hello, world!";
+
+ base::RunLoop loop;
+ mojo::DataPipe pipe(1);
+ DataPipeReader reader(std::move(pipe.consumer_handle), loop.QuitClosure());
+ WriteStringThenCloseProducer(
+ std::make_unique<StringDataPipeProducer>(std::move(pipe.producer_handle)),
+ kTestString,
+ StringDataPipeProducer::AsyncWritingMode::
+ STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION);
+ loop.Run();
+
+ EXPECT_EQ(kTestString, reader.data());
+}
+
+TEST_F(StringDataPipeProducerTest, MultipleWrites) {
+ const std::string kTestString1 = "Hello, world!";
+ const std::string kTestString2 = "There is a lot of data coming your way!";
+ const std::string kTestString3 = "So many strings!";
+ const std::string kTestString4 = "Your cup runneth over!";
+
+ base::RunLoop loop;
+ mojo::DataPipe pipe(4);
+ DataPipeReader reader(std::move(pipe.consumer_handle), loop.QuitClosure());
+ WriteStringsThenCloseProducer(
+ std::make_unique<StringDataPipeProducer>(std::move(pipe.producer_handle)),
+ {kTestString1, kTestString2, kTestString3, kTestString4},
+ StringDataPipeProducer::AsyncWritingMode::
+ STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION);
+ loop.Run();
+
+ EXPECT_EQ(kTestString1 + kTestString2 + kTestString3 + kTestString4,
+ reader.data());
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/public/cpp/system/tests/wait_set_unittest.cc b/mojo/public/cpp/system/tests/wait_set_unittest.cc
index d60cb45924..9ec5819eeb 100644
--- a/mojo/public/cpp/system/tests/wait_set_unittest.cc
+++ b/mojo/public/cpp/system/tests/wait_set_unittest.cc
@@ -31,18 +31,11 @@ void WriteMessage(const ScopedMessagePipeHandle& handle,
}
std::string ReadMessage(const ScopedMessagePipeHandle& handle) {
- uint32_t num_bytes = 0;
- uint32_t num_handles = 0;
- MojoResult rv = ReadMessageRaw(handle.get(), nullptr, &num_bytes, nullptr,
- &num_handles, MOJO_READ_MESSAGE_FLAG_NONE);
- CHECK_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, rv);
- CHECK_EQ(0u, num_handles);
-
- std::vector<char> buffer(num_bytes);
- rv = ReadMessageRaw(handle.get(), buffer.data(), &num_bytes, nullptr,
- &num_handles, MOJO_READ_MESSAGE_FLAG_NONE);
+ std::vector<uint8_t> bytes;
+ MojoResult rv = ReadMessageRaw(handle.get(), &bytes, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE);
CHECK_EQ(MOJO_RESULT_OK, rv);
- return std::string(buffer.data(), buffer.size());
+ return std::string(bytes.begin(), bytes.end());
}
class ThreadedRunner : public base::SimpleThread {
@@ -340,7 +333,7 @@ TEST_F(WaitSetTest, NoStarvation) {
std::vector<std::unique_ptr<base::WaitableEvent>> events(kNumTestEvents);
for (auto& event_ptr : events) {
- event_ptr = base::MakeUnique<base::WaitableEvent>(
+ event_ptr = std::make_unique<base::WaitableEvent>(
base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
event_ptr->Signal();
diff --git a/mojo/public/cpp/system/tests/wait_unittest.cc b/mojo/public/cpp/system/tests/wait_unittest.cc
index 1d9d3c69bc..96efc8888b 100644
--- a/mojo/public/cpp/system/tests/wait_unittest.cc
+++ b/mojo/public/cpp/system/tests/wait_unittest.cc
@@ -33,18 +33,11 @@ void WriteMessage(const ScopedMessagePipeHandle& handle,
}
std::string ReadMessage(const ScopedMessagePipeHandle& handle) {
- uint32_t num_bytes = 0;
- uint32_t num_handles = 0;
- MojoResult rv = ReadMessageRaw(handle.get(), nullptr, &num_bytes, nullptr,
- &num_handles, MOJO_READ_MESSAGE_FLAG_NONE);
- CHECK_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, rv);
- CHECK_EQ(0u, num_handles);
-
- std::vector<char> buffer(num_bytes);
- rv = ReadMessageRaw(handle.get(), buffer.data(), &num_bytes, nullptr,
- &num_handles, MOJO_READ_MESSAGE_FLAG_NONE);
+ std::vector<uint8_t> bytes;
+ MojoResult rv = ReadMessageRaw(handle.get(), &bytes, nullptr,
+ MOJO_READ_MESSAGE_FLAG_NONE);
CHECK_EQ(MOJO_RESULT_OK, rv);
- return std::string(buffer.data(), buffer.size());
+ return std::string(bytes.begin(), bytes.end());
}
class ThreadedRunner : public base::SimpleThread {
@@ -97,9 +90,6 @@ TEST_F(WaitTest, Basic) {
Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &c_hss));
EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
c_hss.satisfied_signals);
- EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
- MOJO_HANDLE_SIGNAL_PEER_CLOSED,
- c_hss.satisfiable_signals);
HandleSignalsState hss;
EXPECT_EQ(MOJO_RESULT_OK,
@@ -206,14 +196,8 @@ TEST_F(WaitManyTest, Basic) {
WaitMany(handles, signals, 2, &result_index, c_hss));
EXPECT_EQ(1u, result_index);
EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, c_hss[0].satisfied_signals);
- EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
- MOJO_HANDLE_SIGNAL_PEER_CLOSED,
- c_hss[0].satisfiable_signals);
EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
c_hss[1].satisfied_signals);
- EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
- MOJO_HANDLE_SIGNAL_PEER_CLOSED,
- c_hss[1].satisfiable_signals);
EXPECT_EQ(MOJO_RESULT_OK, WaitMany(handles, signals, 2, &result_index, hss));
EXPECT_EQ(1u, result_index);
diff --git a/mojo/public/cpp/system/trap.cc b/mojo/public/cpp/system/trap.cc
new file mode 100644
index 0000000000..65154c12d0
--- /dev/null
+++ b/mojo/public/cpp/system/trap.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/cpp/system/trap.h"
+
+#include "mojo/public/c/system/functions.h"
+
+namespace mojo {
+
+MojoResult CreateTrap(MojoTrapEventHandler handler,
+ ScopedTrapHandle* trap_handle) {
+ MojoHandle handle;
+ MojoResult rv = MojoCreateTrap(handler, nullptr, &handle);
+ if (rv == MOJO_RESULT_OK)
+ trap_handle->reset(TrapHandle(handle));
+ return rv;
+}
+
+} // namespace mojo
diff --git a/mojo/public/cpp/system/trap.h b/mojo/public/cpp/system/trap.h
new file mode 100644
index 0000000000..5414aaf285
--- /dev/null
+++ b/mojo/public/cpp/system/trap.h
@@ -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.
+
+#ifndef MOJO_PUBLIC_CPP_SYSTEM_TRAP_H_
+#define MOJO_PUBLIC_CPP_SYSTEM_TRAP_H_
+
+#include "mojo/public/c/system/trap.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/handle.h"
+#include "mojo/public/cpp/system/system_export.h"
+
+namespace mojo {
+
+// A strongly-typed representation of a |MojoHandle| for a trap.
+class TrapHandle : public Handle {
+ public:
+ TrapHandle() = default;
+ explicit TrapHandle(MojoHandle value) : Handle(value) {}
+
+ // Copying and assignment allowed.
+};
+
+static_assert(sizeof(TrapHandle) == sizeof(Handle),
+ "Bad size for C++ TrapHandle");
+
+typedef ScopedHandleBase<TrapHandle> ScopedTrapHandle;
+static_assert(sizeof(ScopedTrapHandle) == sizeof(TrapHandle),
+ "Bad size for C++ ScopedTrapHandle");
+
+MOJO_CPP_SYSTEM_EXPORT MojoResult CreateTrap(MojoTrapEventHandler handler,
+ ScopedTrapHandle* trap_handle);
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_CPP_SYSTEM_TRAP_H_
diff --git a/mojo/public/cpp/system/wait.cc b/mojo/public/cpp/system/wait.cc
index e4e124f25c..8defafe4d8 100644
--- a/mojo/public/cpp/system/wait.cc
+++ b/mojo/public/cpp/system/wait.cc
@@ -10,15 +10,15 @@
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/synchronization/waitable_event.h"
-#include "mojo/public/c/system/watcher.h"
-#include "mojo/public/cpp/system/watcher.h"
+#include "mojo/public/c/system/trap.h"
+#include "mojo/public/cpp/system/trap.h"
namespace mojo {
namespace {
-class WatchContext : public base::RefCountedThreadSafe<WatchContext> {
+class TriggerContext : public base::RefCountedThreadSafe<TriggerContext> {
public:
- WatchContext()
+ TriggerContext()
: event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED) {}
@@ -27,22 +27,19 @@ class WatchContext : public base::RefCountedThreadSafe<WatchContext> {
MojoHandleSignalsState wait_state() const { return wait_state_; }
uintptr_t context_value() const { return reinterpret_cast<uintptr_t>(this); }
- static void OnNotification(uintptr_t context_value,
- MojoResult result,
- MojoHandleSignalsState state,
- MojoWatcherNotificationFlags flags) {
- auto* context = reinterpret_cast<WatchContext*>(context_value);
- context->Notify(result, state);
- if (result == MOJO_RESULT_CANCELLED) {
+ static void OnNotification(const MojoTrapEvent* event) {
+ auto* context = reinterpret_cast<TriggerContext*>(event->trigger_context);
+ context->Notify(event->result, event->signals_state);
+ if (event->result == MOJO_RESULT_CANCELLED) {
// Balanced in Wait() or WaitMany().
context->Release();
}
}
private:
- friend class base::RefCountedThreadSafe<WatchContext>;
+ friend class base::RefCountedThreadSafe<TriggerContext>;
- ~WatchContext() {}
+ ~TriggerContext() {}
void Notify(MojoResult result, MojoHandleSignalsState state) {
if (wait_result_ == MOJO_RESULT_UNKNOWN) {
@@ -55,33 +52,34 @@ class WatchContext : public base::RefCountedThreadSafe<WatchContext> {
base::WaitableEvent event_;
// NOTE: Although these are modified in Notify() which may be called from any
- // thread, Notify() is guaranteed to never run concurrently with itself.
+ // sequence, Notify() is guaranteed to never run concurrently with itself.
// Furthermore, they are only modified once, before |event_| signals; so there
- // is no need for a WatchContext user to synchronize access to these fields
+ // is no need for a TriggerContext user to synchronize access to these fields
// apart from waiting on |event()|.
MojoResult wait_result_ = MOJO_RESULT_UNKNOWN;
MojoHandleSignalsState wait_state_ = {0, 0};
- DISALLOW_COPY_AND_ASSIGN(WatchContext);
+ DISALLOW_COPY_AND_ASSIGN(TriggerContext);
};
} // namespace
MojoResult Wait(Handle handle,
MojoHandleSignals signals,
+ MojoTriggerCondition condition,
MojoHandleSignalsState* signals_state) {
- ScopedWatcherHandle watcher;
- MojoResult rv = CreateWatcher(&WatchContext::OnNotification, &watcher);
+ ScopedTrapHandle trap;
+ MojoResult rv = CreateTrap(&TriggerContext::OnNotification, &trap);
DCHECK_EQ(MOJO_RESULT_OK, rv);
- scoped_refptr<WatchContext> context = new WatchContext;
+ scoped_refptr<TriggerContext> context = new TriggerContext;
- // Balanced in WatchContext::OnNotification if MojoWatch() is successful.
- // Otherwise balanced immediately below.
+ // Balanced in TriggerContext::OnNotification if MojoAddTrigger() is
+ // successful. Otherwise balanced immediately below.
context->AddRef();
- rv = MojoWatch(watcher.get().value(), handle.value(), signals,
- context->context_value());
+ rv = MojoAddTrigger(trap.get().value(), handle.value(), signals, condition,
+ context->context_value(), nullptr);
if (rv == MOJO_RESULT_INVALID_ARGUMENT) {
// Balanced above.
context->Release();
@@ -89,23 +87,21 @@ MojoResult Wait(Handle handle,
}
DCHECK_EQ(MOJO_RESULT_OK, rv);
- uint32_t num_ready_contexts = 1;
- uintptr_t ready_context;
- MojoResult ready_result;
- MojoHandleSignalsState ready_state;
- rv = MojoArmWatcher(watcher.get().value(), &num_ready_contexts,
- &ready_context, &ready_result, &ready_state);
+ uint32_t num_blocking_events = 1;
+ MojoTrapEvent blocking_event = {sizeof(blocking_event)};
+ rv = MojoArmTrap(trap.get().value(), nullptr, &num_blocking_events,
+ &blocking_event);
if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
- DCHECK_EQ(1u, num_ready_contexts);
+ DCHECK_EQ(1u, num_blocking_events);
if (signals_state)
- *signals_state = ready_state;
- return ready_result;
+ *signals_state = blocking_event.signals_state;
+ return blocking_event.result;
}
// Wait for the first notification only.
context->event().Wait();
- ready_result = context->wait_result();
+ MojoResult ready_result = context->wait_result();
DCHECK_NE(MOJO_RESULT_UNKNOWN, ready_result);
if (signals_state)
@@ -122,21 +118,23 @@ MojoResult WaitMany(const Handle* handles,
if (!handles || !signals)
return MOJO_RESULT_INVALID_ARGUMENT;
- ScopedWatcherHandle watcher;
- MojoResult rv = CreateWatcher(&WatchContext::OnNotification, &watcher);
+ ScopedTrapHandle trap;
+ MojoResult rv = CreateTrap(&TriggerContext::OnNotification, &trap);
DCHECK_EQ(MOJO_RESULT_OK, rv);
- std::vector<scoped_refptr<WatchContext>> contexts(num_handles);
+ std::vector<scoped_refptr<TriggerContext>> contexts(num_handles);
std::vector<base::WaitableEvent*> events(num_handles);
for (size_t i = 0; i < num_handles; ++i) {
- contexts[i] = new WatchContext();
+ contexts[i] = new TriggerContext();
- // Balanced in WatchContext::OnNotification if MojoWatch() is successful.
- // Otherwise balanced immediately below.
+ // Balanced in TriggerContext::OnNotification if MojoAddTrigger() is
+ // successful. Otherwise balanced immediately below.
contexts[i]->AddRef();
- MojoResult rv = MojoWatch(watcher.get().value(), handles[i].value(),
- signals[i], contexts[i]->context_value());
+ MojoResult rv =
+ MojoAddTrigger(trap.get().value(), handles[i].value(), signals[i],
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ contexts[i]->context_value(), nullptr);
if (rv == MOJO_RESULT_INVALID_ARGUMENT) {
if (result_index)
*result_index = i;
@@ -150,22 +148,24 @@ MojoResult WaitMany(const Handle* handles,
events[i] = &contexts[i]->event();
}
- uint32_t num_ready_contexts = 1;
- uintptr_t ready_context = 0;
- MojoResult ready_result = MOJO_RESULT_UNKNOWN;
- MojoHandleSignalsState ready_state{0, 0};
- rv = MojoArmWatcher(watcher.get().value(), &num_ready_contexts,
- &ready_context, &ready_result, &ready_state);
+ uint32_t num_blocking_events = 1;
+ MojoTrapEvent blocking_event = {sizeof(blocking_event)};
+ rv = MojoArmTrap(trap.get().value(), nullptr, &num_blocking_events,
+ &blocking_event);
size_t index = num_handles;
+ MojoResult ready_result = MOJO_RESULT_UNKNOWN;
+ MojoHandleSignalsState ready_state = {};
if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
- DCHECK_EQ(1u, num_ready_contexts);
+ DCHECK_EQ(1u, num_blocking_events);
// Most commonly we only watch a small number of handles. Just scan for
// the right index.
for (size_t i = 0; i < num_handles; ++i) {
- if (contexts[i]->context_value() == ready_context) {
+ if (contexts[i]->context_value() == blocking_event.trigger_context) {
index = i;
+ ready_result = blocking_event.result;
+ ready_state = blocking_event.signals_state;
break;
}
}
diff --git a/mojo/public/cpp/system/wait.h b/mojo/public/cpp/system/wait.h
index 808e44fc25..651a37f169 100644
--- a/mojo/public/cpp/system/wait.h
+++ b/mojo/public/cpp/system/wait.h
@@ -7,15 +7,16 @@
#include <stddef.h>
+#include "mojo/public/c/system/trap.h"
#include "mojo/public/c/system/types.h"
#include "mojo/public/cpp/system/handle.h"
#include "mojo/public/cpp/system/system_export.h"
namespace mojo {
-// Blocks the calling thread, waiting for one or more signals in |signals| to be
-// become satisfied -- or for all of them to become unsatisfiable -- on the
-// given Handle.
+// Blocks the calling sequence, waiting for one or more signals in |signals| to
+// be become satisfied, not-satisfied, or permanently unsatisfiable on the
+// handle, depending on the |condition| selected.
//
// If |signals_state| is non-null, |handle| is valid, the wait is not cancelled
// (see return values below), the last known signaling state of |handle| is
@@ -23,17 +24,30 @@ namespace mojo {
//
// Return values:
// |MOJO_RESULT_OK| if one or more signals in |signals| has been raised on
-// |handle| .
+// |handle| with |condition| set to |MOJO_WATCH_CONDITION_SATISFIED|, or
+// one or more signals in |signals| has been lowered on |handle| with
+// |condition| set to |MOJO_WATCH_CONDITION_NOT_SATISFIED|.
// |MOJO_RESULT_FAILED_PRECONDITION| if the state of |handle| changes such
-// that no signals in |signals| can ever be raised again.
+// that no signals in |signals| can ever be raised again and |condition|
+// is |MOJO_WATCH_CONDITION_SATISFIED|.
// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle.
// |MOJO_RESULT_CANCELLED| if the wait was cancelled because |handle| was
-// closed by some other thread while waiting.
+// closed by some other sequence while waiting.
MOJO_CPP_SYSTEM_EXPORT MojoResult
Wait(Handle handle,
MojoHandleSignals signals,
+ MojoTriggerCondition condition,
MojoHandleSignalsState* signals_state = nullptr);
+// A pseudonym for the above Wait() which always waits on
+// |MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED|.
+inline MojoResult Wait(Handle handle,
+ MojoHandleSignals signals,
+ MojoHandleSignalsState* signals_state = nullptr) {
+ return Wait(handle, signals, MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ signals_state);
+}
+
// Waits on |handles[0]|, ..., |handles[num_handles-1]| until:
// - At least one handle satisfies a signal indicated in its respective
// |signals[0]|, ..., |signals[num_handles-1]|.
@@ -60,7 +74,7 @@ Wait(Handle handle,
// |MOJO_RESULT_INVALID_ARGUMENT| if any Handle in |handles| is invalid,
// or if either |handles| or |signals| is null.
// |MOJO_RESULT_CANCELLED| if the wait was cancelled because a handle in
-// |handles| was closed by some other thread while waiting.
+// |handles| was closed by some other sequence while waiting.
// |*result_index| contains the index of the closed Handle if
// |result_index| is non-null.
MOJO_CPP_SYSTEM_EXPORT MojoResult
diff --git a/mojo/public/cpp/system/wait_set.cc b/mojo/public/cpp/system/wait_set.cc
index 1728f81b95..cd8b859dc9 100644
--- a/mojo/public/cpp/system/wait_set.cc
+++ b/mojo/public/cpp/system/wait_set.cc
@@ -16,7 +16,7 @@
#include "base/memory/ptr_util.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
-#include "mojo/public/cpp/system/watcher.h"
+#include "mojo/public/cpp/system/trap.h"
namespace mojo {
@@ -25,13 +25,15 @@ class WaitSet::State : public base::RefCountedThreadSafe<State> {
State()
: handle_event_(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED) {
- MojoResult rv = CreateWatcher(&Context::OnNotification, &watcher_handle_);
+ MojoResult rv = CreateTrap(&Context::OnNotification, &trap_handle_);
DCHECK_EQ(MOJO_RESULT_OK, rv);
}
void ShutDown() {
// NOTE: This may immediately invoke Notify for every context.
- watcher_handle_.reset();
+ trap_handle_.reset();
+
+ cancelled_contexts_.clear();
}
MojoResult AddEvent(base::WaitableEvent* event) {
@@ -50,7 +52,7 @@ class WaitSet::State : public base::RefCountedThreadSafe<State> {
}
MojoResult AddHandle(Handle handle, MojoHandleSignals signals) {
- DCHECK(watcher_handle_.is_valid());
+ DCHECK(trap_handle_.is_valid());
scoped_refptr<Context> context = new Context(this, handle);
@@ -66,13 +68,15 @@ class WaitSet::State : public base::RefCountedThreadSafe<State> {
}
// Balanced in State::Notify() with MOJO_RESULT_CANCELLED if
- // MojoWatch() succeeds. Otherwise balanced immediately below.
+ // MojoAddTrigger() succeeds. Otherwise balanced immediately below.
context->AddRef();
// This can notify immediately if the watcher is already armed. Don't hold
// |lock_| while calling it.
- MojoResult rv = MojoWatch(watcher_handle_.get().value(), handle.value(),
- signals, context->context_value());
+ MojoResult rv =
+ MojoAddTrigger(trap_handle_.get().value(), handle.value(), signals,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ context->context_value(), nullptr);
if (rv == MOJO_RESULT_INVALID_ARGUMENT) {
base::AutoLock lock(lock_);
handle_to_context_.erase(handle);
@@ -88,11 +92,16 @@ class WaitSet::State : public base::RefCountedThreadSafe<State> {
}
MojoResult RemoveHandle(Handle handle) {
- DCHECK(watcher_handle_.is_valid());
+ DCHECK(trap_handle_.is_valid());
scoped_refptr<Context> context;
{
base::AutoLock lock(lock_);
+
+ // Always clear |cancelled_contexts_| in case it's accumulated any more
+ // entries since the last time we ran.
+ cancelled_contexts_.clear();
+
auto it = handle_to_context_.find(handle);
if (it == handle_to_context_.end())
return MOJO_RESULT_NOT_FOUND;
@@ -108,21 +117,14 @@ class WaitSet::State : public base::RefCountedThreadSafe<State> {
// NOTE: This may enter the notification callback immediately, so don't hold
// |lock_| while calling it.
- MojoResult rv = MojoCancelWatch(watcher_handle_.get().value(),
- context->context_value());
+ MojoResult rv = MojoRemoveTrigger(trap_handle_.get().value(),
+ context->context_value(), nullptr);
// We don't really care whether or not this succeeds. In either case, the
// context was or will imminently be cancelled and moved from |contexts_|
// to |cancelled_contexts_|.
DCHECK(rv == MOJO_RESULT_OK || rv == MOJO_RESULT_NOT_FOUND);
- {
- // Always clear |cancelled_contexts_| in case it's accumulated any more
- // entries since the last time we ran.
- base::AutoLock lock(lock_);
- cancelled_contexts_.clear();
- }
-
return rv;
}
@@ -131,7 +133,7 @@ class WaitSet::State : public base::RefCountedThreadSafe<State> {
Handle* ready_handles,
MojoResult* ready_results,
MojoHandleSignalsState* signals_states) {
- DCHECK(watcher_handle_.is_valid());
+ DCHECK(trap_handle_.is_valid());
DCHECK(num_ready_handles);
DCHECK(ready_handles);
DCHECK(ready_results);
@@ -143,22 +145,18 @@ class WaitSet::State : public base::RefCountedThreadSafe<State> {
handle_event_.Reset();
DCHECK_LE(*num_ready_handles, std::numeric_limits<uint32_t>::max());
- uint32_t num_ready_contexts = static_cast<uint32_t>(*num_ready_handles);
-
- base::StackVector<uintptr_t, 4> ready_contexts;
- ready_contexts.container().resize(num_ready_contexts);
- base::StackVector<MojoHandleSignalsState, 4> ready_states;
- MojoHandleSignalsState* out_states = signals_states;
- if (!out_states) {
- // If the caller didn't provide a buffer for signal states, we provide
- // our own locally. MojoArmWatcher() requires one if we want to handle
- // arming failure properly.
- ready_states.container().resize(num_ready_contexts);
- out_states = ready_states.container().data();
+ uint32_t num_blocking_events =
+ static_cast<uint32_t>(*num_ready_handles);
+
+ base::StackVector<MojoTrapEvent, 4> blocking_events;
+ blocking_events.container().resize(num_blocking_events);
+ for (size_t i = 0; i < num_blocking_events; ++i) {
+ blocking_events.container()[i].struct_size =
+ sizeof(blocking_events.container()[i]);
}
- MojoResult rv = MojoArmWatcher(
- watcher_handle_.get().value(), &num_ready_contexts,
- ready_contexts.container().data(), ready_results, out_states);
+ MojoResult rv = MojoArmTrap(trap_handle_.get().value(), nullptr,
+ &num_blocking_events,
+ blocking_events.container().data());
if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
// Simulate the handles becoming ready. We do this in lieu of
@@ -166,11 +164,12 @@ class WaitSet::State : public base::RefCountedThreadSafe<State> {
// starving user events. i.e., we always want to call WaitMany()
// below.
handle_event_.Signal();
- for (size_t i = 0; i < num_ready_contexts; ++i) {
- auto it = contexts_.find(ready_contexts.container()[i]);
+ for (size_t i = 0; i < num_blocking_events; ++i) {
+ const auto& event = blocking_events.container()[i];
+ auto it = contexts_.find(event.trigger_context);
DCHECK(it != contexts_.end());
- ready_handles_[it->second->handle()] = {ready_results[i],
- out_states[i]};
+ ready_handles_[it->second->handle()] = {event.result,
+ event.signals_state};
}
} else if (rv == MOJO_RESULT_NOT_FOUND) {
// Nothing to watch. If there are no user events, always signal to
@@ -241,11 +240,9 @@ class WaitSet::State : public base::RefCountedThreadSafe<State> {
return reinterpret_cast<uintptr_t>(this);
}
- static void OnNotification(uintptr_t context,
- MojoResult result,
- MojoHandleSignalsState signals_state,
- MojoWatcherNotificationFlags flags) {
- reinterpret_cast<Context*>(context)->Notify(result, signals_state);
+ static void OnNotification(const MojoTrapEvent* event) {
+ reinterpret_cast<Context*>(event->trigger_context)
+ ->Notify(event->result, event->signals_state);
}
private:
@@ -271,12 +268,9 @@ class WaitSet::State : public base::RefCountedThreadSafe<State> {
Context* context) {
base::AutoLock lock(lock_);
- // This could be a cancellation notification following an explicit
- // RemoveHandle(), in which case we really don't care and don't want to
- // add it to the ready set. Only update and signal if that's not the case.
- if (!handle_to_context_.count(handle)) {
- DCHECK_EQ(MOJO_RESULT_CANCELLED, result);
- } else {
+ // This notification may have raced with RemoveHandle() from another
+ // sequence. We only signal the WaitSet if that's not the case.
+ if (handle_to_context_.count(handle)) {
ready_handles_[handle] = {result, signals_state};
handle_event_.Signal();
}
@@ -290,15 +284,15 @@ class WaitSet::State : public base::RefCountedThreadSafe<State> {
// NOTE: We retain a context ref in |cancelled_contexts_| to ensure that
// this Context's heap address is not reused too soon. For example, it
// would otherwise be possible for the user to call AddHandle() from the
- // WaitSet's thread immediately after this notification has fired on
- // another thread, potentially reusing the same heap address for the newly
- // added Context; and then they may call RemoveHandle() for this handle
- // (not knowing its context has just been implicitly cancelled) and
+ // WaitSet's sequence immediately after this notification has fired on
+ // another sequence, potentially reusing the same heap address for the
+ // newly added Context; and then they may call RemoveHandle() for this
+ // handle (not knowing its context has just been implicitly cancelled) and
// cause the new Context to be incorrectly removed from |contexts_|.
//
- // This vector is cleared on the WaitSet's own thread every time
+ // This vector is cleared on the WaitSet's own sequence every time
// RemoveHandle is called.
- cancelled_contexts_.emplace_back(make_scoped_refptr(context));
+ cancelled_contexts_.emplace_back(base::WrapRefCounted(context));
// Balanced in State::AddHandle().
context->Release();
@@ -316,8 +310,8 @@ class WaitSet::State : public base::RefCountedThreadSafe<State> {
};
// Not guarded by lock. Must only be accessed from the WaitSet's owning
- // thread.
- ScopedWatcherHandle watcher_handle_;
+ // sequence.
+ ScopedTrapHandle trap_handle_;
base::Lock lock_;
std::map<uintptr_t, scoped_refptr<Context>> contexts_;
diff --git a/mojo/public/cpp/system/wait_set.h b/mojo/public/cpp/system/wait_set.h
index 5047a86a48..efb54dea39 100644
--- a/mojo/public/cpp/system/wait_set.h
+++ b/mojo/public/cpp/system/wait_set.h
@@ -10,6 +10,7 @@
#include "base/macros.h"
#include "base/memory/ref_counted.h"
+#include "mojo/public/c/system/trap.h"
#include "mojo/public/c/system/types.h"
#include "mojo/public/cpp/system/handle.h"
#include "mojo/public/cpp/system/system_export.h"
@@ -20,7 +21,7 @@ class WaitableEvent;
namespace mojo {
-// WaitSet provides an efficient means of blocking a thread on any number of
+// WaitSet provides an efficient means of blocking a sequence on any number of
// events and Mojo handle state changes.
//
// Unlike WaitMany(), which incurs some extra setup cost for every call, a
@@ -98,7 +99,7 @@ class MOJO_CPP_SYSTEM_EXPORT WaitSet {
// |MOJO_RESULT_FAILED_PRECONDITION| all of the signals for the handle have
// become permanently unsatisfiable.
// |MOJO_RESULT_CANCELLED| if the handle has been closed from another
- // thread. NOTE: It is important to recognize that this means the
+ // sequence. NOTE: It is important to recognize that this means the
// corresponding value in |ready_handles| is either invalid, or valid
// but referring to a different handle (i.e. has already been reused) by
// the time Wait() returns. The handle in question is automatically
diff --git a/mojo/public/cpp/system/watcher.cc b/mojo/public/cpp/system/watcher.cc
deleted file mode 100644
index 0c62ba8e20..0000000000
--- a/mojo/public/cpp/system/watcher.cc
+++ /dev/null
@@ -1,20 +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 "mojo/public/cpp/system/watcher.h"
-
-#include "mojo/public/c/system/functions.h"
-
-namespace mojo {
-
-MojoResult CreateWatcher(MojoWatcherCallback callback,
- ScopedWatcherHandle* watcher_handle) {
- MojoHandle handle;
- MojoResult rv = MojoCreateWatcher(callback, &handle);
- if (rv == MOJO_RESULT_OK)
- watcher_handle->reset(WatcherHandle(handle));
- return rv;
-}
-
-} // namespace mojo
diff --git a/mojo/public/cpp/system/watcher.h b/mojo/public/cpp/system/watcher.h
deleted file mode 100644
index d0a257814d..0000000000
--- a/mojo/public/cpp/system/watcher.h
+++ /dev/null
@@ -1,37 +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 MOJO_PUBLIC_CPP_SYSTEM_WATCHER_H_
-#define MOJO_PUBLIC_CPP_SYSTEM_WATCHER_H_
-
-#include "mojo/public/c/system/types.h"
-#include "mojo/public/c/system/watcher.h"
-#include "mojo/public/cpp/system/handle.h"
-#include "mojo/public/cpp/system/system_export.h"
-
-namespace mojo {
-
-// A strongly-typed representation of a |MojoHandle| for a watcher.
-class WatcherHandle : public Handle {
- public:
- WatcherHandle() = default;
- explicit WatcherHandle(MojoHandle value) : Handle(value) {}
-
- // Copying and assignment allowed.
-};
-
-static_assert(sizeof(WatcherHandle) == sizeof(Handle),
- "Bad size for C++ WatcherHandle");
-
-typedef ScopedHandleBase<WatcherHandle> ScopedWatcherHandle;
-static_assert(sizeof(ScopedWatcherHandle) == sizeof(WatcherHandle),
- "Bad size for C++ ScopedWatcherHandle");
-
-MOJO_CPP_SYSTEM_EXPORT MojoResult
-CreateWatcher(MojoWatcherCallback callback,
- ScopedWatcherHandle* watcher_handle);
-
-} // namespace mojo
-
-#endif // MOJO_PUBLIC_CPP_SYSTEM_WATCHER_H_
diff --git a/mojo/public/cpp/test_support/BUILD.gn b/mojo/public/cpp/test_support/BUILD.gn
index efa1712fff..3312a371ba 100644
--- a/mojo/public/cpp/test_support/BUILD.gn
+++ b/mojo/public/cpp/test_support/BUILD.gn
@@ -13,6 +13,7 @@ static_library("test_utils") {
deps = [
"//mojo/public/c/test_support",
+ "//mojo/public/cpp/bindings",
"//mojo/public/cpp/system",
"//testing/gtest",
]
diff --git a/mojo/public/cpp/test_support/lib/test_utils.cc b/mojo/public/cpp/test_support/lib/test_utils.cc
index 7fe6f02788..c876f423ec 100644
--- a/mojo/public/cpp/test_support/lib/test_utils.cc
+++ b/mojo/public/cpp/test_support/lib/test_utils.cc
@@ -7,6 +7,8 @@
#include <stddef.h>
#include <stdint.h>
+#include <vector>
+
#include "mojo/public/cpp/system/core.h"
#include "mojo/public/cpp/system/wait.h"
#include "mojo/public/cpp/test_support/test_support.h"
@@ -26,50 +28,29 @@ bool WriteTextMessage(const MessagePipeHandle& handle,
}
bool ReadTextMessage(const MessagePipeHandle& handle, std::string* text) {
- MojoResult rv;
- bool did_wait = false;
+ if (Wait(handle, MOJO_HANDLE_SIGNAL_READABLE) != MOJO_RESULT_OK)
+ return false;
- uint32_t num_bytes = 0, num_handles = 0;
- for (;;) {
- rv = ReadMessageRaw(handle,
- nullptr,
- &num_bytes,
- nullptr,
- &num_handles,
- MOJO_READ_MESSAGE_FLAG_NONE);
- if (rv == MOJO_RESULT_SHOULD_WAIT) {
- if (did_wait) {
- assert(false); // Looping endlessly!?
- return false;
- }
- rv = Wait(handle, MOJO_HANDLE_SIGNAL_READABLE);
- if (rv != MOJO_RESULT_OK)
- return false;
- did_wait = true;
- } else {
- assert(!num_handles);
- break;
- }
+ std::vector<uint8_t> bytes;
+ std::vector<ScopedHandle> handles;
+ if (ReadMessageRaw(handle, &bytes, &handles, MOJO_READ_MESSAGE_FLAG_NONE) !=
+ MOJO_RESULT_OK) {
+ return false;
}
- text->resize(num_bytes);
- rv = ReadMessageRaw(handle,
- &text->at(0),
- &num_bytes,
- nullptr,
- &num_handles,
- MOJO_READ_MESSAGE_FLAG_NONE);
- return rv == MOJO_RESULT_OK;
+ assert(handles.empty());
+ text->resize(bytes.size());
+ std::copy(bytes.begin(), bytes.end(), text->begin());
+ return true;
}
bool DiscardMessage(const MessagePipeHandle& handle) {
- MojoResult rv = ReadMessageRaw(handle,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- MOJO_READ_MESSAGE_FLAG_MAY_DISCARD);
- return rv == MOJO_RESULT_OK;
+ MojoMessageHandle message;
+ int rv = MojoReadMessage(handle.value(), nullptr, &message);
+ if (rv != MOJO_RESULT_OK)
+ return false;
+ MojoDestroyMessage(message);
+ return true;
}
void IterateAndReportPerf(const char* test_name,
diff --git a/mojo/public/cpp/test_support/test_utils.h b/mojo/public/cpp/test_support/test_utils.h
index 6fd5a9ea25..d56ca3fe33 100644
--- a/mojo/public/cpp/test_support/test_utils.h
+++ b/mojo/public/cpp/test_support/test_utils.h
@@ -9,9 +9,23 @@
#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/bindings/message.h"
+
namespace mojo {
namespace test {
+template <typename MojomType, typename UserType>
+bool SerializeAndDeserialize(UserType* input, UserType* output) {
+ mojo::Message message = MojomType::SerializeAsMessage(input);
+
+ // This accurately simulates full serialization to ensure that all attached
+ // handles are serialized as well. Necessary for DeserializeFromMessage to
+ // work properly.
+ message = mojo::Message(message.TakeMojoMessage());
+
+ return MojomType::DeserializeFromMessage(std::move(message), output);
+}
+
// Writes a message to |handle| with message data |text|. Returns true on
// success.
bool WriteTextMessage(const MessagePipeHandle& handle, const std::string& text);
diff --git a/mojo/public/interfaces/bindings/BUILD.gn b/mojo/public/interfaces/bindings/BUILD.gn
index c2cadcd736..eca88c6c9e 100644
--- a/mojo/public/interfaces/bindings/BUILD.gn
+++ b/mojo/public/interfaces/bindings/BUILD.gn
@@ -4,26 +4,21 @@
import("../../tools/bindings/mojom.gni")
-mojom("bindings") {
- visibility = []
- sources = [
- "interface_control_messages.mojom",
- "pipe_control_messages.mojom",
- ]
+mojom_component("bindings") {
+ output_prefix = "mojo_mojom_bindings"
+ macro_prefix = "MOJO_MOJOM_BINDINGS"
- export_class_attribute = "MOJO_CPP_BINDINGS_EXPORT"
- export_define = "MOJO_CPP_BINDINGS_IMPLEMENTATION"
- export_header = "mojo/public/cpp/bindings/bindings_export.h"
-}
+ visibility = [
+ "//mojo/public/cpp/bindings",
+ "//ipc:mojom",
+ ]
-# TODO(yzshen): Remove this target and use the one above once
-# |use_new_js_bindings| becomes true by default.
-mojom("new_bindings") {
- visibility = []
sources = [
- "new_bindings/interface_control_messages.mojom",
- "new_bindings/pipe_control_messages.mojom",
+ "interface_control_messages.mojom",
+ "native_struct.mojom",
+ "pipe_control_messages.mojom",
]
- use_new_js_bindings = true
+ disallow_native_types = true
+ disallow_interfaces = true
}
diff --git a/mojo/public/interfaces/bindings/native_struct.mojom b/mojo/public/interfaces/bindings/native_struct.mojom
new file mode 100644
index 0000000000..f2e869cf62
--- /dev/null
+++ b/mojo/public/interfaces/bindings/native_struct.mojom
@@ -0,0 +1,26 @@
+// 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.
+
+[JavaPackage="org.chromium.mojo.native_types"]
+module mojo.native;
+
+struct SerializedHandle {
+ handle the_handle;
+
+ enum Type {
+ MOJO_HANDLE,
+ PLATFORM_FILE,
+ WIN_HANDLE,
+ MACH_PORT,
+ FUCHSIA_HANDLE,
+ };
+
+ Type type;
+};
+
+[CustomSerializer]
+struct NativeStruct {
+ array<uint8> data;
+ array<SerializedHandle>? handles;
+};
diff --git a/mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom b/mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom
deleted file mode 100644
index e03ffd6589..0000000000
--- a/mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom
+++ /dev/null
@@ -1,67 +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.
-
-[JavaPackage="org.chromium.mojo.bindings.interfacecontrol"]
-module mojo.interface_control2;
-
-// For each user-defined interface, some control functions are provided by the
-// interface endpoints at both sides.
-
-////////////////////////////////////////////////////////////////////////////////
-// Run@0xFFFFFFFF(RunInput input) => (RunOutput? output);
-//
-// This control function runs the input command. If the command is not
-// supported, |output| is set to null; otherwise |output| stores the result,
-// whose type depends on the input.
-
-const uint32 kRunMessageId = 0xFFFFFFFF;
-
-struct RunMessageParams {
- RunInput input;
-};
-union RunInput {
- QueryVersion query_version;
- FlushForTesting flush_for_testing;
-};
-
-struct RunResponseMessageParams {
- RunOutput? output;
-};
-union RunOutput {
- QueryVersionResult query_version_result;
-};
-
-// Queries the max supported version of the user-defined interface.
-// Sent by the interface client side.
-struct QueryVersion {
-};
-struct QueryVersionResult {
- uint32 version;
-};
-
-// Sent by either side of the interface.
-struct FlushForTesting {
-};
-
-////////////////////////////////////////////////////////////////////////////////
-// RunOrClosePipe@0xFFFFFFFE(RunOrClosePipeInput input);
-//
-// This control function runs the input command. If the operation fails or the
-// command is not supported, the message pipe is closed.
-
-const uint32 kRunOrClosePipeMessageId = 0xFFFFFFFE;
-
-struct RunOrClosePipeMessageParams {
- RunOrClosePipeInput input;
-};
-union RunOrClosePipeInput {
- RequireVersion require_version;
-};
-
-// If the specified version of the user-defined interface is not supported, the
-// function fails and the pipe is closed.
-// Sent by the interface client side.
-struct RequireVersion {
- uint32 version;
-};
diff --git a/mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom b/mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom
deleted file mode 100644
index 69975fc1c0..0000000000
--- a/mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom
+++ /dev/null
@@ -1,46 +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.
-
-[JavaPackage="org.chromium.mojo.bindings.pipecontrol"]
-module mojo.pipe_control2;
-
-// For each message pipe running user-defined interfaces, some control
-// functions are provided and used by the routers at both ends of the pipe, so
-// that they can coordinate to manage interface endpoints.
-// All these control messages will have the interface ID field in the message
-// header set to invalid.
-
-////////////////////////////////////////////////////////////////////////////////
-// RunOrClosePipe@0xFFFFFFFE(RunOrClosePipeInput input);
-//
-// This control function runs the input command. If the operation fails or the
-// command is not supported, the message pipe is closed.
-
-const uint32 kRunOrClosePipeMessageId = 0xFFFFFFFE;
-
-struct RunOrClosePipeMessageParams {
- RunOrClosePipeInput input;
-};
-
-union RunOrClosePipeInput {
- PeerAssociatedEndpointClosedEvent peer_associated_endpoint_closed_event;
-};
-
-// A user-defined reason about why the interface is disconnected.
-struct DisconnectReason {
- uint32 custom_reason;
- string description;
-};
-
-// An event to notify that an interface endpoint set up at the message sender
-// side has been closed.
-//
-// This event is omitted if the endpoint belongs to the master interface and
-// there is no disconnect reason specified.
-struct PeerAssociatedEndpointClosedEvent {
- // The interface ID.
- uint32 id;
- DisconnectReason? disconnect_reason;
-};
-
diff --git a/mojo/public/interfaces/bindings/tests/BUILD.gn b/mojo/public/interfaces/bindings/tests/BUILD.gn
index e496eb656c..939c837c0f 100644
--- a/mojo/public/interfaces/bindings/tests/BUILD.gn
+++ b/mojo/public/interfaces/bindings/tests/BUILD.gn
@@ -4,6 +4,44 @@
import("../../../tools/bindings/mojom.gni")
+group("test_data_deps") {
+ testonly = true
+ data_deps = [
+ ":test_interfaces_js_data_deps",
+ ":test_associated_interfaces_js_data_deps",
+ ":validation_test_data",
+ ":validation_test_data_list",
+ ]
+}
+
+copy("validation_test_data") {
+ testonly = true
+ sources = [
+ "data/validation",
+ ]
+ outputs = [
+ "$root_gen_dir/layout_test_data/{{source_root_relative_dir}}/{{source_file_part}}",
+ ]
+}
+
+action_foreach("validation_test_data_list") {
+ testonly = true
+ script = "//mojo/public/tools/bindings/gen_data_files_list.py"
+ inputs = mojom_generator_sources
+ sources = [
+ "data/validation",
+ ]
+ outputs = [
+ "$root_gen_dir/layout_test_data/{{source_root_relative_dir}}/{{source_file_part}}_index.txt",
+ ]
+ args = [
+ "-d",
+ rebase_path(sources[0], root_build_dir),
+ "-o",
+ rebase_path(outputs[0], root_build_dir),
+ ]
+}
+
mojom("test_interfaces") {
testonly = true
sources = [
@@ -20,6 +58,7 @@ mojom("test_interfaces") {
"test_bad_messages.mojom",
"test_constants.mojom",
"test_data_view.mojom",
+ "test_name_generator.mojom",
"test_native_types.mojom",
"test_structs.mojom",
"test_sync_methods.mojom",
@@ -31,6 +70,15 @@ mojom("test_interfaces") {
":test_mojom_import",
":test_mojom_import2",
]
+
+ # TODO(crbug.com/714018): Convert the implementation to use OnceCallback.
+ use_once_callback = false
+
+ support_lazy_serialization = true
+
+ # Validation tests require precise message content matching, so we avoid
+ # scrambling message IDs for test interfaces.
+ scramble_message_ids = false
}
component("test_export_component") {
@@ -148,6 +196,11 @@ mojom("test_struct_traits_interfaces") {
sources = [
"struct_with_traits.mojom",
]
+
+ # TODO(crbug.com/714018): Convert the implementation to use OnceCallback.
+ use_once_callback = false
+
+ support_lazy_serialization = true
}
mojom("test_associated_interfaces") {
@@ -162,6 +215,13 @@ mojom("test_associated_interfaces") {
public_deps = [
":test_interfaces",
]
+
+ # TODO(crbug.com/714018): Convert the implementation to use OnceCallback.
+ use_once_callback = false
+
+ # Validation tests require precise message content matching, so we avoid
+ # scrambling message IDs for test interfaces.
+ scramble_message_ids = false
}
mojom("versioning_test_service_interfaces") {
@@ -184,6 +244,9 @@ mojom("test_wtf_types") {
sources = [
"test_wtf_types.mojom",
]
+
+ # TODO(crbug.com/714018): Convert the implementation to use OnceCallback.
+ use_once_callback = false
}
mojom("test_no_sources") {
@@ -198,7 +261,6 @@ mojom("echo") {
testonly = true
sources = [
"echo.mojom",
- "echo_import.mojom",
+ "echo_import/echo_import.mojom",
]
- use_new_js_bindings = true
}
diff --git a/mojo/public/interfaces/bindings/tests/echo.mojom b/mojo/public/interfaces/bindings/tests/echo.mojom
index 56c6063010..1418e3d272 100644
--- a/mojo/public/interfaces/bindings/tests/echo.mojom
+++ b/mojo/public/interfaces/bindings/tests/echo.mojom
@@ -4,7 +4,7 @@
module test.echo.mojom;
-import "echo_import.mojom";
+import "mojo/public/interfaces/bindings/tests/echo_import/echo_import.mojom";
interface Echo {
EchoPoint(test.echo_import.mojom.Point point)
diff --git a/mojo/public/interfaces/bindings/tests/echo_import.mojom b/mojo/public/interfaces/bindings/tests/echo_import/echo_import.mojom
index a024ce2ff1..a024ce2ff1 100644
--- a/mojo/public/interfaces/bindings/tests/echo_import.mojom
+++ b/mojo/public/interfaces/bindings/tests/echo_import/echo_import.mojom
diff --git a/mojo/public/interfaces/bindings/tests/ping_service.mojom b/mojo/public/interfaces/bindings/tests/ping_service.mojom
index ba6ad3d66a..e5f8d947cb 100644
--- a/mojo/public/interfaces/bindings/tests/ping_service.mojom
+++ b/mojo/public/interfaces/bindings/tests/ping_service.mojom
@@ -6,9 +6,16 @@
module mojo.test;
interface PingService {
+ [Sync]
Ping() => ();
};
interface EchoService {
Echo(string test_data) => (string echo_data);
};
+
+interface HandleTrampoline {
+ BounceOne(handle<message_pipe> one) => (handle<message_pipe> one);
+ BounceTwo(handle<message_pipe> one, handle<message_pipe> two)
+ => (handle<message_pipe> one, handle<message_pipe> two);
+};
diff --git a/mojo/public/interfaces/bindings/tests/sample_factory.mojom b/mojo/public/interfaces/bindings/tests/sample_factory.mojom
index ade3bf37d6..cb718afd76 100644
--- a/mojo/public/interfaces/bindings/tests/sample_factory.mojom
+++ b/mojo/public/interfaces/bindings/tests/sample_factory.mojom
@@ -5,7 +5,7 @@
[JavaPackage="org.chromium.mojo.bindings.test.mojom.sample"]
module sample;
-import "sample_import.mojom";
+import "mojo/public/interfaces/bindings/tests/sample_import.mojom";
// This sample shows how handles to MessagePipes can be sent as both parameters
// to methods as well as fields on structs.
diff --git a/mojo/public/interfaces/bindings/tests/sample_import.mojom b/mojo/public/interfaces/bindings/tests/sample_import.mojom
index d28cb7b4db..c9570515f8 100644
--- a/mojo/public/interfaces/bindings/tests/sample_import.mojom
+++ b/mojo/public/interfaces/bindings/tests/sample_import.mojom
@@ -36,3 +36,8 @@ struct Point {
interface ImportedInterface {
DoSomething();
};
+
+union PointOrShape {
+ Point point;
+ Shape shape;
+};
diff --git a/mojo/public/interfaces/bindings/tests/sample_import2.mojom b/mojo/public/interfaces/bindings/tests/sample_import2.mojom
index ca4e81c0bb..a8c909a3c2 100644
--- a/mojo/public/interfaces/bindings/tests/sample_import2.mojom
+++ b/mojo/public/interfaces/bindings/tests/sample_import2.mojom
@@ -5,7 +5,7 @@
[JavaPackage="org.chromium.mojo.bindings.test.mojom.imported"]
module imported;
-import "sample_import.mojom";
+import "mojo/public/interfaces/bindings/tests/sample_import.mojom";
// This sample adds more types and constants to the "imported" namespace,
// to test a bug with importing multiple modules with the same namespace.
diff --git a/mojo/public/interfaces/bindings/tests/sample_service.mojom b/mojo/public/interfaces/bindings/tests/sample_service.mojom
index 761cb91a9b..62ff1a8b7e 100644
--- a/mojo/public/interfaces/bindings/tests/sample_service.mojom
+++ b/mojo/public/interfaces/bindings/tests/sample_service.mojom
@@ -6,8 +6,8 @@
[JavaPackage="org.chromium.mojo.bindings.test.mojom.sample"]
module sample;
-import "sample_import.mojom";
-import "sample_import2.mojom";
+import "mojo/public/interfaces/bindings/tests/sample_import.mojom";
+import "mojo/public/interfaces/bindings/tests/sample_import2.mojom";
const uint8 kTwelve = 12;
diff --git a/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom b/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom
index b50409ee88..6c67b04900 100644
--- a/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom
+++ b/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom
@@ -29,6 +29,10 @@ struct StructWithTraits {
map<string, NestedStructWithTraits> f_struct_map;
};
+struct StructWithUnreachableTraits {
+ bool ignore_me;
+};
+
// Test that this container can be cloned.
struct StructWithTraitsContainer {
StructWithTraits f_struct;
@@ -81,3 +85,28 @@ interface TraitsTestService {
EchoUnionWithTraits(UnionWithTraits u) => (UnionWithTraits passed);
};
+
+interface TestUnserializedStruct {
+ PassUnserializedStruct(StructWithUnreachableTraits s)
+ => (StructWithUnreachableTraits passed);
+};
+
+// Test that specifying default value for a typemapped enum field works.
+struct EnumWithTraitsContainer {
+ EnumWithTraits f_field = EnumWithTraits.VALUE_1;
+};
+
+struct StructForceSerialize {
+ int32 value;
+};
+
+struct StructNestedForceSerialize {
+ StructForceSerialize force;
+};
+
+interface ForceSerializeTester {
+ SendForceSerializedStruct(StructForceSerialize s)
+ => (StructForceSerialize passed);
+ SendNestedForceSerializedStruct(StructNestedForceSerialize s)
+ => (StructNestedForceSerialize passed);
+};
diff --git a/mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom b/mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom
index adc4e7e809..6df1e8be3e 100644
--- a/mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom
+++ b/mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom
@@ -40,11 +40,26 @@ interface IntegerSender {
Send(int32 value);
};
+interface StringSender {
+ Echo(string value) => (string value);
+ Send(string value);
+};
+
interface IntegerSenderConnection {
GetSender(associated IntegerSender& sender);
AsyncGetSender() => (associated IntegerSender sender);
};
+interface IntegerSenderConnectionAtBothEnds {
+ GetSender(associated IntegerSender& sender);
+ SetSender(associated IntegerSender sender) => (int32 value);
+};
+
+interface SenderConnection {
+ GetIntegerSender(associated IntegerSender& sender);
+ GetStringSender(associated StringSender& sender);
+};
+
interface AssociatedPingProvider {
GetPing(associated PingService& request);
};
diff --git a/mojo/public/interfaces/bindings/tests/test_name_generator.mojom b/mojo/public/interfaces/bindings/tests/test_name_generator.mojom
new file mode 100644
index 0000000000..f2bc673fb8
--- /dev/null
+++ b/mojo/public/interfaces/bindings/tests/test_name_generator.mojom
@@ -0,0 +1,27 @@
+// 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.
+
+[JavaPackage="org.chromium.mojo.bindings.test.mojom.sample",
+ JavaConstantsClassName="NameGeneratorConstants"]
+module sample;
+
+enum SupportedCases {
+ lowerCamelCase,
+ UpperCamelCase,
+ snake_case,
+ MACRO_CASE,
+ kHungarianNotation,
+ upperACRONYMCase
+};
+
+const uint64 PAD_RSA_PKCS1_1_5_SIGN = 1;
+const uint64 kDigestSha1 = 1;
+const uint64 kE2eIntegration = 1;
+const uint64 M3Test = 1;
+const uint64 URLLoaderFactory = 1;
+const uint64 Ipv6Address = 1;
+const uint64 Numb3r5InTH3Middl3 = 1;
+const uint64 Name_WITHUnderscore = 1;
+const uint64 SINGLETON = 1;
+
diff --git a/mojo/public/interfaces/bindings/tests/test_native_types.mojom b/mojo/public/interfaces/bindings/tests/test_native_types.mojom
index 3df43182a3..caa6a71c56 100644
--- a/mojo/public/interfaces/bindings/tests/test_native_types.mojom
+++ b/mojo/public/interfaces/bindings/tests/test_native_types.mojom
@@ -36,3 +36,15 @@ interface RectService {
GetLargestRect() => (TypemappedRect largest);
PassSharedRect(SharedTypemappedRect r) => (SharedTypemappedRect passed);
};
+
+[Native]
+struct TestNativeStructMojom;
+
+[Native]
+struct TestNativeStructWithAttachmentsMojom;
+
+interface NativeTypeTester {
+ PassNativeStruct(TestNativeStructMojom s) => (TestNativeStructMojom passed);
+ PassNativeStructWithAttachments(TestNativeStructWithAttachmentsMojom s)
+ => (TestNativeStructWithAttachmentsMojom s);
+};
diff --git a/mojo/public/interfaces/bindings/tests/test_structs.mojom b/mojo/public/interfaces/bindings/tests/test_structs.mojom
index 03a0a20581..c7dc10e95d 100644
--- a/mojo/public/interfaces/bindings/tests/test_structs.mojom
+++ b/mojo/public/interfaces/bindings/tests/test_structs.mojom
@@ -126,8 +126,8 @@ struct MapKeyTypes {
map<float, float> f9;
map<double, double> f10;
map<string, string> f11;
- // TODO(tibell): JS/Java don't support struct as key.
- // map<Rect, Rect> f12;
+ // TODO(crbug.com/628104): JS doesn't support struct as key. map<Rect, Rect>
+ // f12;
};
// Used to verify that various map value types can be encoded and decoded
diff --git a/mojo/public/interfaces/bindings/tests/test_unions.mojom b/mojo/public/interfaces/bindings/tests/test_unions.mojom
index 41e1ed6ace..4343bc7341 100644
--- a/mojo/public/interfaces/bindings/tests/test_unions.mojom
+++ b/mojo/public/interfaces/bindings/tests/test_unions.mojom
@@ -4,6 +4,8 @@
module mojo.test;
+import "mojo/public/interfaces/bindings/tests/sample_import.mojom";
+
enum AnEnum {
FIRST, SECOND
};
@@ -103,3 +105,11 @@ union NewUnion {
int8 f_int8;
int16 f_int16;
};
+
+struct ImportedUnionStruct {
+ imported.PointOrShape point_or_shape;
+};
+
+union ImportedUnionUnion {
+ imported.PointOrShape point_or_shape;
+};
diff --git a/mojo/public/interfaces/bindings/tests/test_wtf_types.mojom b/mojo/public/interfaces/bindings/tests/test_wtf_types.mojom
index 183f184ef3..22d6a67ebb 100644
--- a/mojo/public/interfaces/bindings/tests/test_wtf_types.mojom
+++ b/mojo/public/interfaces/bindings/tests/test_wtf_types.mojom
@@ -33,6 +33,12 @@ struct TestWTFStruct {
int32 integer;
};
+struct TestWTFStructWrapper {
+ TestWTFStruct nested_struct;
+ array<TestWTFStruct> array_struct;
+ map<TestWTFStruct, TestWTFStruct> map_struct;
+};
+
interface TestWTF {
enum NestedEnum {
E0,
diff --git a/mojo/public/java/BUILD.gn b/mojo/public/java/BUILD.gn
index 850785aa2c..c6159ab2e8 100644
--- a/mojo/public/java/BUILD.gn
+++ b/mojo/public/java/BUILD.gn
@@ -21,6 +21,10 @@ android_library("system_java") {
"system/src/org/chromium/mojo/system/RunLoop.java",
"system/src/org/chromium/mojo/system/Watcher.java",
]
+
+ deps = [
+ "//base:base_java",
+ ]
}
android_library("bindings_java") {
@@ -63,3 +67,13 @@ android_library("bindings_java") {
srcjar_deps = [ "../interfaces/bindings:bindings_java_sources" ]
}
+
+android_library("base_java") {
+ java_files = [ "base/src/org/chromium/mojo_base/BigBufferUtil.java" ]
+
+ deps = [
+ ":system_java",
+ "//mojo/public/java/system:system_impl_java",
+ "//mojo/public/mojom/base:base_java",
+ ]
+}
diff --git a/mojo/public/java/base/src/org/chromium/mojo_base/BigBufferUtil.java b/mojo/public/java/base/src/org/chromium/mojo_base/BigBufferUtil.java
new file mode 100644
index 0000000000..aefc257e0b
--- /dev/null
+++ b/mojo/public/java/base/src/org/chromium/mojo_base/BigBufferUtil.java
@@ -0,0 +1,55 @@
+// 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_base;
+
+import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.SharedBufferHandle;
+import org.chromium.mojo.system.impl.CoreImpl;
+import org.chromium.mojo_base.mojom.BigBuffer;
+import org.chromium.mojo_base.mojom.BigBufferSharedMemoryRegion;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Static helper methods for working with the mojom BigBuffer type.
+ */
+public final class BigBufferUtil {
+ public static final int MAX_INLINE_ARRAY_SIZE = 64 * 1024;
+
+ // Retrives a copy of the buffer's contents regardless of what type was backing it (i.e. array
+ // or shared memory).
+ public static byte[] getBytesFromBigBuffer(BigBuffer buffer) {
+ if (buffer.which() == BigBuffer.Tag.Bytes) {
+ return buffer.getBytes();
+ } else {
+ BigBufferSharedMemoryRegion region = buffer.getSharedMemory();
+ ByteBuffer byteBuffer =
+ region.bufferHandle.map(0, region.size, SharedBufferHandle.MapFlags.NONE);
+ byte[] bytes = new byte[region.size];
+ byteBuffer.get(bytes);
+ return bytes;
+ }
+ }
+
+ // Creates a new mojom.BigBuffer for IPC from a set of bytes. If the byte array is larger than
+ // MAX_INLINE_ARRAY_SIZE, shared memory will be used instead of an inline array.
+ public static BigBuffer createBigBufferFromBytes(byte[] bytes) {
+ BigBuffer buffer = new BigBuffer();
+ if (bytes.length <= MAX_INLINE_ARRAY_SIZE) {
+ buffer.setBytes(bytes);
+ return buffer;
+ }
+ Core core = CoreImpl.getInstance();
+ BigBufferSharedMemoryRegion region = new BigBufferSharedMemoryRegion();
+ region.bufferHandle =
+ core.createSharedBuffer(new SharedBufferHandle.CreateOptions(), bytes.length);
+ region.size = bytes.length;
+ ByteBuffer mappedRegion =
+ region.bufferHandle.map(0, bytes.length, SharedBufferHandle.MapFlags.NONE);
+ mappedRegion.put(bytes);
+ buffer.setSharedMemory(region);
+ return buffer;
+ }
+}
diff --git a/mojo/public/java/bindings/README.md b/mojo/public/java/bindings/README.md
index 821a230ed9..e2e1701a15 100644
--- a/mojo/public/java/bindings/README.md
+++ b/mojo/public/java/bindings/README.md
@@ -1,5 +1,5 @@
-# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo Java Bindings API
-This document is a subset of the [Mojo documentation](/mojo).
+# Mojo Java Bindings API
+This document is a subset of the [Mojo documentation](/mojo/README.md).
[TOC]
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java
index 8a83be928e..1e39976824 100644
--- a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java
@@ -13,7 +13,6 @@ import java.util.concurrent.Executor;
* Wrapper around {@link Router} that will close the connection when not referenced anymore.
*/
class AutoCloseableRouter implements Router {
-
/**
* The underlying router.
*/
@@ -25,6 +24,12 @@ class AutoCloseableRouter implements Router {
private final Executor mExecutor;
/**
+ * Exception used to track AutoCloseableRouter's allocation location for debugging puproses when
+ * leaked.
+ */
+ private final Exception mAllocationException;
+
+ /**
* Flags to keep track if this router has been correctly closed.
*/
private boolean mClosed;
@@ -35,6 +40,7 @@ class AutoCloseableRouter implements Router {
public AutoCloseableRouter(Core core, Router router) {
mRouter = router;
mExecutor = ExecutorFactory.getExecutorForCurrentThread(core);
+ mAllocationException = new Exception("AutocloseableRouter allocated at:");
}
/**
@@ -108,8 +114,9 @@ class AutoCloseableRouter implements Router {
close();
}
});
- throw new IllegalStateException("Warning: Router objects should be explicitly closed " +
- "when no longer required otherwise you may leak handles.");
+ throw new IllegalStateException("Warning: Router objects should be explicitly closed "
+ + "when no longer required otherwise you may leak handles.",
+ mAllocationException);
}
super.finalize();
}
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 d58602ab29..45f1fc7462 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
@@ -169,9 +169,7 @@ public class Connector implements MessageReceiver, HandleOwner<MessagePipeHandle
ResultAnd<Boolean> result;
do {
try {
- result = readAndDispatchMessage(
- mMessagePipeHandle, mIncomingMessageReceiver,
- ExceptionHandler.DefaultExceptionHandler.getInstance());
+ result = readAndDispatchMessage(mMessagePipeHandle, mIncomingMessageReceiver);
} catch (MojoException e) {
onError(e);
return;
@@ -193,28 +191,25 @@ public class Connector implements MessageReceiver, HandleOwner<MessagePipeHandle
*
* @param receiver The {@link MessageReceiver} that will receive the read {@link Message}. Can
* be <code>null</code>, in which case the message is discarded.
- * @param exceptionHandler The {@link ExceptionHandler} that can decide whether any uncaught
- * exception will close the connection or not.
*/
static ResultAnd<Boolean> readAndDispatchMessage(
- MessagePipeHandle handle, MessageReceiver receiver, ExceptionHandler exceptionHandler) {
- // TODO(qsr) Allow usage of a pool of pre-allocated buffer for performance.
- ResultAnd<ReadMessageResult> result =
- handle.readMessage(null, 0, MessagePipeHandle.ReadFlags.NONE);
- if (result.getMojoResult() != MojoResult.RESOURCE_EXHAUSTED) {
+ MessagePipeHandle handle, MessageReceiver receiver) {
+ ResultAnd<ReadMessageResult> result = handle.readMessage(MessagePipeHandle.ReadFlags.NONE);
+ if (result.getMojoResult() != MojoResult.OK) {
return new ResultAnd<Boolean>(result.getMojoResult(), false);
}
ReadMessageResult readResult = result.getValue();
assert readResult != null;
- ByteBuffer buffer = ByteBuffer.allocateDirect(readResult.getMessageSize());
- result = handle.readMessage(
- buffer, readResult.getHandlesCount(), MessagePipeHandle.ReadFlags.NONE);
- if (receiver != null && result.getMojoResult() == MojoResult.OK) {
+ if (receiver != null) {
boolean accepted;
try {
- accepted = receiver.accept(new Message(buffer, result.getValue().getHandles()));
+ accepted = receiver.accept(
+ new Message(ByteBuffer.wrap(readResult.mData), readResult.mHandles));
} catch (RuntimeException e) {
- accepted = exceptionHandler.handleException(e);
+ // The DefaultExceptionHandler will decide whether any uncaught exception will
+ // close the connection or not.
+ accepted =
+ ExceptionHandler.DefaultExceptionHandler.getInstance().handleException(e);
}
return new ResultAnd<Boolean>(result.getMojoResult(), accepted);
}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java
index bb49cbc4bd..8eb96aa10b 100644
--- a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java
@@ -109,7 +109,7 @@ class ExecutorFactory {
private boolean readNotifyBufferMessage() {
try {
ResultAnd<ReadMessageResult> readMessageResult =
- mReadHandle.readMessage(NOTIFY_BUFFER, 0, MessagePipeHandle.ReadFlags.NONE);
+ mReadHandle.readMessage(MessagePipeHandle.ReadFlags.NONE);
if (readMessageResult.getMojoResult() == MojoResult.OK) {
return true;
}
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java
index 996c457338..de484c4b41 100644
--- a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java
@@ -24,7 +24,7 @@ public class Message {
/**
* The handles of the message.
*/
- private final List<? extends Handle> mHandle;
+ private final List<? extends Handle> mHandles;
/**
* This message interpreted as a message for a mojo service with an appropriate header.
@@ -38,9 +38,8 @@ public class Message {
* @param handles The list of handles to send.
*/
public Message(ByteBuffer buffer, List<? extends Handle> handles) {
- assert buffer.isDirect();
mBuffer = buffer;
- mHandle = handles;
+ mHandles = handles;
}
/**
@@ -54,7 +53,7 @@ public class Message {
* The handles of the message.
*/
public List<? extends Handle> getHandles() {
- return mHandle;
+ return mHandles;
}
/**
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java
index aebc9e21c8..a278cc5ea8 100644
--- 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,23 +171,20 @@ public class RouterImpl implements Router {
assert messageWithHeader.getHeader().hasFlag(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG);
// Compute a request id for being able to route the response.
- // 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);
+ 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);
return true;
}
@@ -230,15 +227,11 @@ public class RouterImpl implements Router {
return false;
} else if (header.hasFlag(MessageHeader.MESSAGE_IS_RESPONSE_FLAG)) {
long requestId = header.getRequestId();
- 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);
+ MessageReceiver responder = mResponders.get(requestId);
+ if (responder == null) {
+ return false;
}
+ mResponders.remove(requestId);
return responder.accept(message);
} else {
if (mIncomingMessageReceiver != null) {
diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java
index 90b40ea57b..8712e19846 100644
--- a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java
+++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java
@@ -10,6 +10,19 @@ import org.chromium.mojo.system.Core;
* Base class for all mojo unions.
*/
public abstract class Union {
+ /** They type of object that has been set. */
+ protected int mTag;
+
+ /** Returns the type of object being held by this union. */
+ public int which() {
+ return mTag;
+ }
+
+ /** Returns whether the type of object in this union is known. */
+ public boolean isUnknown() {
+ return mTag == -1;
+ }
+
/**
* Returns the serialization of the union. This method can close Handles.
*
diff --git a/mojo/public/java/system/BUILD.gn b/mojo/public/java/system/BUILD.gn
new file mode 100644
index 0000000000..0025f5588d
--- /dev/null
+++ b/mojo/public/java/system/BUILD.gn
@@ -0,0 +1,177 @@
+# 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("system") {
+ testonly = true
+ deps = [
+ ":mojo_javatests",
+ ":mojo_test_apk",
+ ":system_impl_java",
+ ]
+}
+
+generate_jni("jni_headers") {
+ sources = [
+ "javatests/src/org/chromium/mojo/MojoTestRule.java",
+ "javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java",
+ ]
+ public_deps = [
+ ":system_impl_java_jni_headers",
+ ]
+
+ jni_package = "mojo"
+}
+
+generate_jni("system_impl_java_jni_headers") {
+ sources = [
+ "src/org/chromium/mojo/system/impl/BaseRunLoop.java",
+ "src/org/chromium/mojo/system/impl/CoreImpl.java",
+ "src/org/chromium/mojo/system/impl/WatcherImpl.java",
+ ]
+
+ jni_package = "mojo"
+}
+
+source_set("native_support") {
+ sources = [
+ "base_run_loop.cc",
+ "core_impl.cc",
+ "watcher_impl.cc",
+ ]
+
+ deps = [
+ ":system_impl_java_jni_headers",
+ "//base",
+ "//mojo/public/c/system",
+ "//mojo/public/cpp/system",
+ ]
+}
+
+android_library("system_impl_java") {
+ java_files = [
+ "src/org/chromium/mojo/system/impl/BaseRunLoop.java",
+ "src/org/chromium/mojo/system/impl/CoreImpl.java",
+ "src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java",
+ "src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java",
+ "src/org/chromium/mojo/system/impl/HandleBase.java",
+ "src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java",
+ "src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java",
+ "src/org/chromium/mojo/system/impl/UntypedHandleImpl.java",
+ "src/org/chromium/mojo/system/impl/WatcherImpl.java",
+ ]
+
+ deps = [
+ "//base:base_java",
+ "//mojo/public/java:system_java",
+ ]
+}
+
+# Targets should also depend on :test_support for the native side.
+android_library("test_support_java") {
+ testonly = true
+ java_files = [ "javatests/src/org/chromium/mojo/MojoTestRule.java" ]
+ deps = [
+ "//base:base_java",
+ "//third_party/junit",
+ ]
+}
+
+source_set("test_support") {
+ testonly = true
+ sources = [
+ "javatests/mojo_test_rule.cc",
+ ]
+ deps = [
+ ":jni_headers",
+ "//base",
+ "//base/test:test_support",
+ "//mojo/core/embedder",
+ ]
+ defines = [ "UNIT_TEST" ]
+}
+
+android_library("mojo_javatests") {
+ testonly = true
+ java_files = [
+ "javatests/src/org/chromium/mojo/HandleMock.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/NameGeneratorTest.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_impl_java",
+ ":test_support_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",
+ "//third_party/junit",
+ ]
+
+ data = [
+ "//mojo/public/interfaces/bindings/tests/data/validation/",
+ ]
+}
+
+shared_library("mojo_java_unittests") {
+ testonly = true
+
+ sources = [
+ "javatests/init_library.cc",
+ "javatests/validation_test_util.cc",
+ ]
+
+ deps = [
+ ":jni_headers",
+ ":native_support",
+ ":system_impl_java_jni_headers",
+ ":test_support",
+ "//base",
+ "//base/test:test_support",
+ "//mojo/core/embedder",
+ "//mojo/public/cpp/bindings/tests:mojo_public_bindings_test_utils",
+ "//mojo/public/cpp/test_support:test_utils",
+ ]
+ defines = [ "UNIT_TEST" ]
+ configs -= [ "//build/config/android:hide_all_but_jni_onload" ]
+ configs += [ "//build/config/android:hide_all_but_jni" ]
+}
+
+instrumentation_test_apk("mojo_test_apk") {
+ deps = [
+ ":mojo_javatests",
+ ":system_impl_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/public/java/system/README.md b/mojo/public/java/system/README.md
index 3213e4c790..b3b3a4c99f 100644
--- a/mojo/public/java/system/README.md
+++ b/mojo/public/java/system/README.md
@@ -1,5 +1,5 @@
-# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo Java System API
-This document is a subset of the [Mojo documentation](/mojo).
+# Mojo Java System API
+This document is a subset of the [Mojo documentation](/mojo/README.md).
[TOC]
diff --git a/mojo/public/java/system/base_run_loop.cc b/mojo/public/java/system/base_run_loop.cc
new file mode 100644
index 0000000000..5b14852b13
--- /dev/null
+++ b/mojo/public/java/system/base_run_loop.cc
@@ -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.
+
+#include <jni.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.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 JNI_BaseRunLoop_CreateBaseRunLoop(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller) {
+ base::MessageLoop* message_loop = new base::MessageLoop;
+ return reinterpret_cast<uintptr_t>(message_loop);
+}
+
+static void JNI_BaseRunLoop_Run(JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller) {
+ base::RunLoop().Run();
+}
+
+static void JNI_BaseRunLoop_RunUntilIdle(JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller) {
+ base::RunLoop().RunUntilIdle();
+}
+
+static void JNI_BaseRunLoop_Quit(JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller) {
+ base::RunLoop::QuitCurrentWhenIdleDeprecated();
+}
+
+static void RunJavaRunnable(
+ const base::android::ScopedJavaGlobalRef<jobject>& runnable_ref) {
+ Java_BaseRunLoop_runRunnable(base::android::AttachCurrentThread(),
+ runnable_ref);
+}
+
+static void JNI_BaseRunLoop_PostDelayedTask(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jlong runLoopID,
+ const JavaParamRef<jobject>& runnable,
+ jlong delay) {
+ base::android::ScopedJavaGlobalRef<jobject> 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<base::MessageLoop*>(runLoopID)
+ ->task_runner()
+ ->PostDelayedTask(FROM_HERE,
+ base::BindOnce(&RunJavaRunnable, runnable_ref),
+ base::TimeDelta::FromMicroseconds(delay));
+}
+
+static void JNI_BaseRunLoop_DeleteMessageLoop(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jlong runLoopID) {
+ base::MessageLoop* message_loop =
+ reinterpret_cast<base::MessageLoop*>(runLoopID);
+ delete message_loop;
+}
+
+} // namespace android
+} // namespace mojo
diff --git a/mojo/public/java/system/core_impl.cc b/mojo/public/java/system/core_impl.cc
new file mode 100644
index 0000000000..33f0576a4c
--- /dev/null
+++ b/mojo/public/java/system/core_impl.cc
@@ -0,0 +1,343 @@
+// 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 <stddef.h>
+#include <stdint.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/scoped_java_ref.h"
+#include "jni/CoreImpl_jni.h"
+#include "mojo/public/c/system/core.h"
+#include "mojo/public/cpp/system/message_pipe.h"
+
+namespace mojo {
+namespace android {
+
+using base::android::JavaParamRef;
+using base::android::ScopedJavaLocalRef;
+
+static jlong JNI_CoreImpl_GetTimeTicksNow(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller) {
+ return MojoGetTimeTicksNow();
+}
+
+static ScopedJavaLocalRef<jobject> JNI_CoreImpl_CreateMessagePipe(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ const JavaParamRef<jobject>& options_buffer) {
+ const MojoCreateMessagePipeOptions* options = NULL;
+ if (options_buffer) {
+ const void* buffer_start = env->GetDirectBufferAddress(options_buffer);
+ DCHECK(buffer_start);
+ DCHECK_EQ(reinterpret_cast<const uintptr_t>(buffer_start) % 8, 0u);
+ const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer);
+ DCHECK_EQ(buffer_size, sizeof(MojoCreateMessagePipeOptions));
+ options = static_cast<const MojoCreateMessagePipeOptions*>(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<jobject> JNI_CoreImpl_CreateDataPipe(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ const JavaParamRef<jobject>& options_buffer) {
+ const MojoCreateDataPipeOptions* options = NULL;
+ if (options_buffer) {
+ const void* buffer_start = env->GetDirectBufferAddress(options_buffer);
+ DCHECK(buffer_start);
+ DCHECK_EQ(reinterpret_cast<const uintptr_t>(buffer_start) % 8, 0u);
+ const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer);
+ DCHECK_EQ(buffer_size, sizeof(MojoCreateDataPipeOptions));
+ options = static_cast<const MojoCreateDataPipeOptions*>(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<jobject> JNI_CoreImpl_CreateSharedBuffer(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ const JavaParamRef<jobject>& 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<const uintptr_t>(buffer_start) % 8, 0u);
+ const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer);
+ DCHECK_EQ(buffer_size, sizeof(MojoCreateSharedBufferOptions));
+ options = static_cast<const MojoCreateSharedBufferOptions*>(buffer_start);
+ DCHECK_EQ(options->struct_size, buffer_size);
+ }
+ MojoHandle handle;
+ MojoResult result = MojoCreateSharedBuffer(num_bytes, options, &handle);
+ return Java_CoreImpl_newResultAndInteger(env, result, handle);
+}
+
+static jint JNI_CoreImpl_Close(JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jint mojo_handle) {
+ return MojoClose(mojo_handle);
+}
+
+static jint JNI_CoreImpl_QueryHandleSignalsState(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jint mojo_handle,
+ const JavaParamRef<jobject>& buffer) {
+ MojoHandleSignalsState* signals_state =
+ static_cast<MojoHandleSignalsState*>(env->GetDirectBufferAddress(buffer));
+ DCHECK(signals_state);
+ DCHECK_EQ(sizeof(MojoHandleSignalsState),
+ static_cast<size_t>(env->GetDirectBufferCapacity(buffer)));
+ return MojoQueryHandleSignalsState(mojo_handle, signals_state);
+}
+
+static jint JNI_CoreImpl_WriteMessage(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jint mojo_handle,
+ const JavaParamRef<jobject>& bytes,
+ jint num_bytes,
+ const JavaParamRef<jobject>& 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<MojoHandle*>(env->GetDirectBufferAddress(handles_buffer));
+ num_handles = env->GetDirectBufferCapacity(handles_buffer) / 4;
+ }
+ // Java code will handle invalidating handles if the write succeeded.
+ return WriteMessageRaw(
+ MessagePipeHandle(static_cast<MojoHandle>(mojo_handle)), buffer_start,
+ buffer_size, handles, num_handles, flags);
+}
+
+static ScopedJavaLocalRef<jobject> JNI_CoreImpl_ReadMessage(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jint mojo_handle,
+ jint flags) {
+ ScopedMessageHandle message;
+ MojoResult result =
+ ReadMessageNew(MessagePipeHandle(mojo_handle), &message, flags);
+ if (result != MOJO_RESULT_OK)
+ return Java_CoreImpl_newReadMessageResult(env, result, nullptr, nullptr);
+ DCHECK(message.is_valid());
+
+ result = MojoSerializeMessage(message->value(), nullptr);
+ if (result != MOJO_RESULT_OK && result != MOJO_RESULT_FAILED_PRECONDITION) {
+ return Java_CoreImpl_newReadMessageResult(env, MOJO_RESULT_ABORTED, nullptr,
+ nullptr);
+ }
+
+ uint32_t num_bytes;
+ void* buffer;
+ uint32_t num_handles = 0;
+ std::vector<MojoHandle> handles;
+ result = MojoGetMessageData(message->value(), nullptr, &buffer, &num_bytes,
+ nullptr, &num_handles);
+ if (result == MOJO_RESULT_RESOURCE_EXHAUSTED) {
+ handles.resize(num_handles);
+ result = MojoGetMessageData(message->value(), nullptr, &buffer, &num_bytes,
+ handles.data(), &num_handles);
+ }
+
+ if (result != MOJO_RESULT_OK)
+ return Java_CoreImpl_newReadMessageResult(env, result, nullptr, nullptr);
+
+ return Java_CoreImpl_newReadMessageResult(
+ env, result,
+ base::android::ToJavaByteArray(env, static_cast<uint8_t*>(buffer),
+ num_bytes),
+ base::android::ToJavaIntArray(
+ env, reinterpret_cast<jint*>(handles.data()), num_handles));
+}
+
+static ScopedJavaLocalRef<jobject> JNI_CoreImpl_ReadData(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jint mojo_handle,
+ const JavaParamRef<jobject>& 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));
+ }
+ MojoReadDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = flags;
+ MojoResult result =
+ MojoReadData(mojo_handle, &options, buffer_start, &buffer_size);
+ return Java_CoreImpl_newResultAndInteger(
+ env, result, (result == MOJO_RESULT_OK) ? buffer_size : 0);
+}
+
+static ScopedJavaLocalRef<jobject> JNI_CoreImpl_BeginReadData(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jint mojo_handle,
+ jint num_bytes,
+ jint flags) {
+ void const* buffer = 0;
+ uint32_t buffer_size = num_bytes;
+
+ MojoBeginReadDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = flags;
+ MojoResult result =
+ MojoBeginReadData(mojo_handle, &options, &buffer, &buffer_size);
+ if (result == MOJO_RESULT_OK) {
+ ScopedJavaLocalRef<jobject> byte_buffer(
+ env, env->NewDirectByteBuffer(const_cast<void*>(buffer), buffer_size));
+ return Java_CoreImpl_newResultAndBuffer(env, result, byte_buffer);
+ } else {
+ return Java_CoreImpl_newResultAndBuffer(env, result, nullptr);
+ }
+}
+
+static jint JNI_CoreImpl_EndReadData(JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jint mojo_handle,
+ jint num_bytes_read) {
+ return MojoEndReadData(mojo_handle, num_bytes_read, nullptr);
+}
+
+static ScopedJavaLocalRef<jobject> JNI_CoreImpl_WriteData(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jint mojo_handle,
+ const JavaParamRef<jobject>& elements,
+ jint limit,
+ jint flags) {
+ void* buffer_start = env->GetDirectBufferAddress(elements);
+ DCHECK(buffer_start);
+ DCHECK(limit <= env->GetDirectBufferCapacity(elements));
+ uint32_t buffer_size = limit;
+
+ MojoWriteDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = flags;
+ MojoResult result =
+ MojoWriteData(mojo_handle, buffer_start, &buffer_size, &options);
+ return Java_CoreImpl_newResultAndInteger(
+ env, result, (result == MOJO_RESULT_OK) ? buffer_size : 0);
+}
+
+static ScopedJavaLocalRef<jobject> JNI_CoreImpl_BeginWriteData(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jint mojo_handle,
+ jint num_bytes,
+ jint flags) {
+ void* buffer = 0;
+ uint32_t buffer_size = num_bytes;
+ MojoBeginWriteDataOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = flags;
+ MojoResult result =
+ MojoBeginWriteData(mojo_handle, &options, &buffer, &buffer_size);
+ if (result == MOJO_RESULT_OK) {
+ ScopedJavaLocalRef<jobject> byte_buffer(
+ env, env->NewDirectByteBuffer(buffer, buffer_size));
+ return Java_CoreImpl_newResultAndBuffer(env, result, byte_buffer);
+ } else {
+ return Java_CoreImpl_newResultAndBuffer(env, result, nullptr);
+ }
+}
+
+static jint JNI_CoreImpl_EndWriteData(JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jint mojo_handle,
+ jint num_bytes_written) {
+ return MojoEndWriteData(mojo_handle, num_bytes_written, nullptr);
+}
+
+static ScopedJavaLocalRef<jobject> JNI_CoreImpl_Duplicate(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jint mojo_handle,
+ const JavaParamRef<jobject>& 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<const MojoDuplicateBufferHandleOptions*>(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<jobject> JNI_CoreImpl_Map(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jint mojo_handle,
+ jlong offset,
+ jlong num_bytes,
+ jint flags) {
+ void* buffer = 0;
+ MojoMapBufferOptions options;
+ options.struct_size = sizeof(options);
+ options.flags = flags;
+ MojoResult result =
+ MojoMapBuffer(mojo_handle, offset, num_bytes, &options, &buffer);
+ if (result == MOJO_RESULT_OK) {
+ ScopedJavaLocalRef<jobject> byte_buffer(
+ env, env->NewDirectByteBuffer(buffer, num_bytes));
+ return Java_CoreImpl_newResultAndBuffer(env, result, byte_buffer);
+ } else {
+ return Java_CoreImpl_newResultAndBuffer(env, result, nullptr);
+ }
+}
+
+static int JNI_CoreImpl_Unmap(JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ const JavaParamRef<jobject>& buffer) {
+ void* buffer_start = env->GetDirectBufferAddress(buffer);
+ DCHECK(buffer_start);
+ return MojoUnmapBuffer(buffer_start);
+}
+
+static jint JNI_CoreImpl_GetNativeBufferOffset(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ const JavaParamRef<jobject>& buffer,
+ jint alignment) {
+ jint offset =
+ reinterpret_cast<uintptr_t>(env->GetDirectBufferAddress(buffer)) %
+ alignment;
+ if (offset == 0)
+ return 0;
+ return alignment - offset;
+}
+
+} // namespace android
+} // namespace mojo
diff --git a/mojo/public/java/system/javatests/AndroidManifest.xml b/mojo/public/java/system/javatests/AndroidManifest.xml
new file mode 100644
index 0000000000..37fc407e07
--- /dev/null
+++ b/mojo/public/java/system/javatests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <!-- 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 name must be unique so suffix with "tests" so package loader
+ doesn't ignore this. -->
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="org.chromium.mojo.tests">
+
+ <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
+
+ <uses-permission android:name="android.permission.INJECT_EVENTS"
+ tools:ignore="ProtectedPermissions"/>
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.RUN_INSTRUMENTATION" />
+
+ <!-- We add an application tag here just so that we can indicate that this
+ package needs to link against the android.test library, which is
+ needed when building test cases. -->
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="org.chromium.mojo.tests"
+ android:label="Tests for org.chromium.mojo"
+ chromium-junit4="true"/>
+
+</manifest>
diff --git a/mojo/android/javatests/apk/.empty b/mojo/public/java/system/javatests/apk/.empty
index e69de29bb2..e69de29bb2 100644
--- a/mojo/android/javatests/apk/.empty
+++ b/mojo/public/java/system/javatests/apk/.empty
diff --git a/mojo/public/java/system/javatests/init_library.cc b/mojo/public/java/system/javatests/init_library.cc
new file mode 100644
index 0000000000..2826d29b9d
--- /dev/null
+++ b/mojo/public/java/system/javatests/init_library.cc
@@ -0,0 +1,17 @@
+// 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/jni_android.h"
+#include "mojo/core/embedder/embedder.h"
+
+JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ base::android::InitVM(vm);
+
+ if (!base::android::OnJNIOnLoadInit())
+ return -1;
+
+ mojo::core::Init();
+ return JNI_VERSION_1_4;
+}
diff --git a/mojo/public/java/system/javatests/mojo_test_rule.cc b/mojo/public/java/system/javatests/mojo_test_rule.cc
new file mode 100644
index 0000000000..67576bbfac
--- /dev/null
+++ b/mojo/public/java/system/javatests/mojo_test_rule.cc
@@ -0,0 +1,73 @@
+// 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/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/MojoTestRule_jni.h"
+#include "mojo/core/embedder/embedder.h"
+
+using base::android::JavaParamRef;
+
+namespace {
+
+struct TestEnvironment {
+ TestEnvironment() {}
+
+ base::ShadowingAtExitManager at_exit;
+ base::MessageLoop message_loop;
+};
+
+} // namespace
+
+namespace mojo {
+namespace android {
+
+static void JNI_MojoTestRule_InitCore(JNIEnv* env,
+ const JavaParamRef<jclass>& clazz) {
+ mojo::core::Init();
+}
+
+static void JNI_MojoTestRule_Init(JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller) {
+ base::InitAndroidTestMessageLoop();
+}
+
+static jlong JNI_MojoTestRule_SetupTestEnvironment(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller) {
+ return reinterpret_cast<intptr_t>(new TestEnvironment());
+}
+
+static void JNI_MojoTestRule_TearDownTestEnvironment(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jlong test_environment) {
+ delete reinterpret_cast<TestEnvironment*>(test_environment);
+}
+
+static void JNI_MojoTestRule_RunLoop(JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jlong timeout_ms) {
+ base::RunLoop run_loop;
+ if (timeout_ms) {
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitWhenIdleClosure(),
+ base::TimeDelta::FromMilliseconds(timeout_ms));
+ run_loop.Run();
+ } else {
+ run_loop.RunUntilIdle();
+ }
+}
+
+} // namespace android
+} // namespace mojo
diff --git a/mojo/public/java/system/javatests/src/org/chromium/mojo/HandleMock.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/HandleMock.java
new file mode 100644
index 0000000000..715e1e54b4
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/HandleMock.java
@@ -0,0 +1,220 @@
+// 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<Integer> readData(ByteBuffer elements, DataPipe.ReadFlags flags) {
+ // Do nothing.
+ return new ResultAnd<Integer>(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<Integer> writeData(ByteBuffer elements, DataPipe.WriteFlags flags) {
+ // Do nothing.
+ return new ResultAnd<Integer>(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<? extends Handle> handles, WriteFlags flags) {
+ // Do nothing.
+ }
+
+ /**
+ * @see MessagePipeHandle#readMessage(MessagePipeHandle.ReadFlags)
+ */
+ @Override
+ public ResultAnd<ReadMessageResult> readMessage(ReadFlags flags) {
+ // Do nothing.
+ return new ResultAnd<ReadMessageResult>(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/public/java/system/javatests/src/org/chromium/mojo/MojoTestRule.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/MojoTestRule.java
new file mode 100644
index 0000000000..7d40e133f7
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/MojoTestRule.java
@@ -0,0 +1,81 @@
+// 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.support.annotation.IntDef;
+
+import org.junit.rules.ExternalResource;
+
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.library_loader.LibraryLoader;
+import org.chromium.base.library_loader.LibraryProcessType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Base class to test mojo. Setup the environment.
+ */
+@JNINamespace("mojo::android")
+public class MojoTestRule extends ExternalResource {
+ @IntDef({MojoCore.SKIP_INITIALIZATION, MojoCore.INITIALIZE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MojoCore {
+ int SKIP_INITIALIZATION = 0;
+ int INITIALIZE = 1;
+ }
+
+ private static boolean sIsCoreInitialized = false;
+ private final boolean mShouldInitCore;
+ private long mTestEnvironmentPointer;
+
+ public MojoTestRule() {
+ this(MojoCore.SKIP_INITIALIZATION);
+ }
+
+ public MojoTestRule(@MojoCore int shouldInitMojoCore) {
+ mShouldInitCore = shouldInitMojoCore == MojoCore.INITIALIZE;
+ }
+
+ @Override
+ protected void before() throws Throwable {
+ LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER);
+ if (mShouldInitCore && !sIsCoreInitialized) {
+ nativeInitCore();
+ sIsCoreInitialized = true;
+ }
+ nativeInit();
+ mTestEnvironmentPointer = nativeSetupTestEnvironment();
+ }
+
+ @Override
+ protected void after() {
+ nativeTearDownTestEnvironment(mTestEnvironmentPointer);
+ }
+
+ /**
+ * Runs the run loop for the given time.
+ */
+ public void runLoop(long timeoutMS) {
+ nativeRunLoop(timeoutMS);
+ }
+
+ /**
+ * Runs the run loop until no handle or task are immediately available.
+ */
+ public void runLoopUntilIdle() {
+ nativeRunLoop(0);
+ }
+
+ private static native void nativeInitCore();
+
+ private native void nativeInit();
+
+ private native long nativeSetupTestEnvironment();
+
+ private native void nativeTearDownTestEnvironment(long testEnvironment);
+
+ private native void nativeRunLoop(long timeoutMS);
+}
diff --git a/mojo/public/java/system/javatests/src/org/chromium/mojo/TestUtils.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/TestUtils.java
new file mode 100644
index 0000000000..3eaf4fa0b7
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/TestUtils.java
@@ -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.
+
+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/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java
new file mode 100644
index 0000000000..871d1e07b6
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java
@@ -0,0 +1,60 @@
+// 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.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+
+import java.nio.charset.Charset;
+
+/**
+ * Testing {@link BindingsHelper}.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class BindingsHelperTest {
+ /**
+ * Testing {@link BindingsHelper#utf8StringSizeInBytes(String)}.
+ */
+ @Test
+ @SmallTest
+ public void testUTF8StringLength() {
+ String[] stringsToTest = {
+ "", "a", "hello world", "éléphant", "𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕", "你午饭想吃什么",
+ "你午饭想吃什么\0éléphant",
+ };
+ for (String s : stringsToTest) {
+ Assert.assertEquals(s.getBytes(Charset.forName("utf8")).length,
+ BindingsHelper.utf8StringSizeInBytes(s));
+ }
+ Assert.assertEquals(1, BindingsHelper.utf8StringSizeInBytes("\0"));
+ String s = new StringBuilder()
+ .appendCodePoint(0x0)
+ .appendCodePoint(0x80)
+ .appendCodePoint(0x800)
+ .appendCodePoint(0x10000)
+ .toString();
+ Assert.assertEquals(10, BindingsHelper.utf8StringSizeInBytes(s));
+ Assert.assertEquals(10, s.getBytes(Charset.forName("utf8")).length);
+ }
+
+ /**
+ * Testing {@link BindingsHelper#align(int)}.
+ */
+ @Test
+ @SmallTest
+ public void testAlign() {
+ for (int i = 0; i < 3 * BindingsHelper.ALIGNMENT; ++i) {
+ int j = BindingsHelper.align(i);
+ Assert.assertTrue(j >= i);
+ Assert.assertTrue(j % BindingsHelper.ALIGNMENT == 0);
+ Assert.assertTrue(j - i < BindingsHelper.ALIGNMENT);
+ }
+ }
+}
diff --git a/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsTest.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsTest.java
new file mode 100644
index 0000000000..9c9d96c67c
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsTest.java
@@ -0,0 +1,228 @@
+// 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.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+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;
+
+/**
+ * Testing generated classes and associated features.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class BindingsTest {
+ /**
+ * 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 <T> void checkConstantField(Field field, Class<T> expectedClass, T value)
+ throws IllegalAccessException {
+ Assert.assertEquals(expectedClass, field.getType());
+ Assert.assertEquals(Modifier.FINAL, field.getModifiers() & Modifier.FINAL);
+ Assert.assertEquals(Modifier.STATIC, field.getModifiers() & Modifier.STATIC);
+ Assert.assertEquals(value, field.get(null));
+ }
+
+ private static <T> void checkField(Field field, Class<T> expectedClass, Object object, T value)
+ throws IllegalArgumentException, IllegalAccessException {
+ Assert.assertEquals(expectedClass, field.getType());
+ Assert.assertEquals(0, field.getModifiers() & Modifier.FINAL);
+ Assert.assertEquals(0, field.getModifiers() & Modifier.STATIC);
+ Assert.assertEquals(value, field.get(object));
+ }
+
+ /**
+ * Testing constants are correctly generated.
+ */
+ @Test
+ @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.
+ */
+ @Test
+ @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
+ */
+ @Test
+ @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);
+
+ Assert.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
+ */
+ @Test
+ @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);
+
+ Assert.assertNotNull(foo.source);
+ Assert.assertFalse(foo.source.isValid());
+ checkField(Foo.class.getField("source"), MessagePipeHandle.class, foo, foo.source);
+ }
+
+ /**
+ * Testing serialization of the Foo class.
+ */
+ @Test
+ @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);
+ Assert.assertTrue(BindingsTestUtils.structsEqual(typicalFoo, deserializedFoo));
+ }
+
+ /**
+ * Testing serialization of the EmptyStruct class.
+ */
+ @Test
+ @SmallTest
+ public void testEmptyStructSerialization() {
+ // Checking serialization and deserialization of a EmptyStruct object.
+ Message serializedStruct = new EmptyStruct().serialize(null);
+ EmptyStruct emptyStruct = EmptyStruct.deserialize(serializedStruct);
+ Assert.assertNotNull(emptyStruct);
+ }
+}
diff --git a/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java
new file mode 100644
index 0000000000..ac5cfb594f
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.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 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<Message> messages = new ArrayList<Message>();
+
+ /**
+ * @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<Pair<Message, MessageReceiver>> messagesWithReceivers =
+ new ArrayList<Pair<Message, MessageReceiver>>();
+
+ /**
+ * @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<Handle>());
+ }
+
+ public static <I extends Interface, P extends Interface.Proxy> P newProxyOverPipe(
+ Interface.Manager<I, P> manager, I impl, List<Closeable> toClose) {
+ Pair<MessagePipeHandle, MessagePipeHandle> handles =
+ CoreImpl.getInstance().createMessagePipe(null);
+ P proxy = manager.attachProxy(handles.first, 0);
+ toClose.add(proxy);
+ manager.bind(impl, handles.second);
+ return proxy;
+ }
+
+ public static boolean structsEqual(Struct s1, Struct s2) {
+ if (s1 == s2) return true;
+ if (s1 == null || s2 == null) return false;
+ return s1.serialize(null).getData().equals(s2.serialize(null).getData());
+ }
+}
diff --git a/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java
new file mode 100644
index 0000000000..734f9a00f4
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java
@@ -0,0 +1,223 @@
+// 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.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.mojo.MojoTestRule;
+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
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class BindingsVersioningTest {
+ @Rule
+ public MojoTestRule mTestRule = new MojoTestRule();
+
+ 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.
+ */
+ @Test
+ @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));
+ Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output));
+ Assert.assertEquals(0, v0.getVersion());
+ Assert.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));
+ Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output));
+ Assert.assertEquals(1, v1.getVersion());
+ Assert.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));
+ Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output));
+ Assert.assertEquals(3, v3.getVersion());
+ Assert.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));
+ Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output));
+ Assert.assertEquals(5, v5.getVersion());
+ Assert.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.
+ Assert.assertEquals(expectedHandle, output.fMessagePipe.releaseNativeHandle());
+ output.fMessagePipe = expected.fMessagePipe;
+
+ Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output));
+ Assert.assertEquals(7, v7.getVersion());
+ Assert.assertEquals(7, output.getVersion());
+ }
+ }
+
+ /**
+ * Testing serializing new struct version to older one.
+ */
+ @Test
+ @SmallTest
+ public void testNewToOld() {
+ MultiVersionStruct struct = newStruct();
+ {
+ MultiVersionStructV0 expected = new MultiVersionStructV0();
+ expected.fInt32 = 123;
+
+ MultiVersionStructV0 output = MultiVersionStructV0.deserialize(struct.serialize(null));
+ Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output));
+ Assert.assertEquals(9, output.getVersion());
+ }
+
+ {
+ MultiVersionStructV1 expected = new MultiVersionStructV1();
+ expected.fInt32 = 123;
+ expected.fRect = newRect(5);
+
+ MultiVersionStructV1 output = MultiVersionStructV1.deserialize(struct.serialize(null));
+ Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output));
+ Assert.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));
+ Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output));
+ Assert.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));
+ Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output));
+ Assert.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));
+
+ Assert.assertEquals(expectedHandle, output.fMessagePipe.releaseNativeHandle());
+ output.fMessagePipe = expected.fMessagePipe;
+
+ Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output));
+ Assert.assertEquals(9, output.getVersion());
+ }
+ }
+}
diff --git a/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java
new file mode 100644
index 0000000000..8379826b9e
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/CallbacksTest.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.bindings;
+
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+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
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class CallbacksTest {
+ /**
+ * Testing {@link Callback1}.
+ */
+ @Test
+ @SmallTest
+ public void testCallback1() {
+ final List<Integer> parameters = new ArrayList<Integer>();
+ new Callback1<Integer>() {
+ @Override
+ public void call(Integer i1) {
+ parameters.add(i1);
+ }
+ }
+ .call(1);
+ Assert.assertEquals(Arrays.asList(1), parameters);
+ }
+
+ /**
+ * Testing {@link Callback7}.
+ */
+ @Test
+ @SmallTest
+ public void testCallback7() {
+ final List<Integer> parameters = new ArrayList<Integer>();
+ new Callback7<Integer, Integer, Integer, Integer, Integer, Integer, Integer>() {
+ @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);
+ Assert.assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7), parameters);
+ }
+}
diff --git a/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java
new file mode 100644
index 0000000000..ad38d7c0c8
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java
@@ -0,0 +1,119 @@
+// 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.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.mojo.MojoTestRule;
+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.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class ConnectorTest {
+ @Rule
+ public MojoTestRule mTestRule = new MojoTestRule();
+
+ 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()
+ */
+ @Before
+ public void setUp() throws Exception {
+ Core core = CoreImpl.getInstance();
+ Pair<MessagePipeHandle, MessagePipeHandle> 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);
+ Assert.assertNull(mErrorHandler.getLastMojoException());
+ Assert.assertEquals(0, mReceiver.messages.size());
+ }
+
+ /**
+ * @see MojoTestCase#tearDown()
+ */
+ @After
+ public void tearDown() throws Exception {
+ mConnector.close();
+ mHandle.close();
+ }
+
+ /**
+ * Test sending a message through a {@link Connector}.
+ */
+ @Test
+ @SmallTest
+ public void testSendingMessage() {
+ mConnector.accept(mTestMessage);
+ Assert.assertNull(mErrorHandler.getLastMojoException());
+ ResultAnd<MessagePipeHandle.ReadMessageResult> result =
+ mHandle.readMessage(MessagePipeHandle.ReadFlags.NONE);
+ Assert.assertEquals(MojoResult.OK, result.getMojoResult());
+ Assert.assertEquals(DATA_LENGTH, result.getValue().mData.length);
+ Assert.assertEquals(mTestMessage.getData(), ByteBuffer.wrap(result.getValue().mData));
+ }
+
+ /**
+ * Test receiving a message through a {@link Connector}
+ */
+ @Test
+ @SmallTest
+ public void testReceivingMessage() {
+ mHandle.writeMessage(
+ mTestMessage.getData(), new ArrayList<Handle>(), MessagePipeHandle.WriteFlags.NONE);
+ mTestRule.runLoopUntilIdle();
+ Assert.assertNull(mErrorHandler.getLastMojoException());
+ Assert.assertEquals(1, mReceiver.messages.size());
+ Message received = mReceiver.messages.get(0);
+ Assert.assertEquals(0, received.getHandles().size());
+ Assert.assertEquals(mTestMessage.getData(), received.getData());
+ }
+
+ /**
+ * Test receiving an error through a {@link Connector}.
+ */
+ @Test
+ @SmallTest
+ public void testErrors() {
+ mHandle.close();
+ mTestRule.runLoopUntilIdle();
+ Assert.assertNotNull(mErrorHandler.getLastMojoException());
+ Assert.assertEquals(MojoResult.FAILED_PRECONDITION,
+ mErrorHandler.getLastMojoException().getMojoResult());
+ }
+}
diff --git a/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java
new file mode 100644
index 0000000000..b92370fb3d
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.mojo.MojoTestRule;
+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.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class ExecutorFactoryTest {
+ @Rule
+ public MojoTestRule mTestRule = new MojoTestRule();
+
+ 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<Thread> mThreadContainer;
+
+ /**
+ * @see MojoTestCase#setUp()
+ */
+ @Before
+ public void setUp() throws Exception {
+ mExecutor = ExecutorFactory.getExecutorForCurrentThread(CoreImpl.getInstance());
+ mThreadContainer = new ArrayList<Thread>();
+ }
+
+ /**
+ * Testing the {@link Executor} when called from the executor thread.
+ */
+ @Test
+ @SmallTest
+ public void testExecutorOnCurrentThread() {
+ Runnable action = new Runnable() {
+ @Override
+ public void run() {
+ mThreadContainer.add(Thread.currentThread());
+ }
+ };
+ mExecutor.execute(action);
+ mExecutor.execute(action);
+ Assert.assertEquals(0, mThreadContainer.size());
+ mTestRule.runLoop(RUN_LOOP_TIMEOUT_MS);
+ Assert.assertEquals(2, mThreadContainer.size());
+ for (Thread thread : mThreadContainer) {
+ Assert.assertEquals(Thread.currentThread(), thread);
+ }
+ }
+
+ /**
+ * Testing the {@link Executor} when called from another thread.
+ */
+ @Test
+ @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) {
+ Assert.fail("Unexpected exception: " + e.getMessage());
+ } catch (BrokenBarrierException e) {
+ Assert.fail("Unexpected exception: " + e.getMessage());
+ }
+ }
+ });
+ }
+ try {
+ barrier.await();
+ } catch (InterruptedException e) {
+ Assert.fail("Unexpected exception: " + e.getMessage());
+ } catch (BrokenBarrierException e) {
+ Assert.fail("Unexpected exception: " + e.getMessage());
+ }
+ Assert.assertEquals(0, mThreadContainer.size());
+ mTestRule.runLoop(RUN_LOOP_TIMEOUT_MS);
+ Assert.assertEquals(CONCURRENCY_LEVEL, mThreadContainer.size());
+ for (Thread thread : mThreadContainer) {
+ Assert.assertEquals(Thread.currentThread(), thread);
+ }
+ }
+}
diff --git a/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java
new file mode 100644
index 0000000000..fcd612137a
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java
@@ -0,0 +1,140 @@
+// 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.junit.After;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.mojo.MojoTestRule;
+import org.chromium.mojo.bindings.Callbacks.Callback1;
+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.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class InterfaceControlMessageTest {
+ @Rule
+ public MojoTestRule mTestRule = new MojoTestRule();
+
+ private final List<Closeable> mCloseablesToClose = new ArrayList<Closeable>();
+
+ /**
+ * 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()
+ */
+ @After
+ public 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();
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testQueryVersion() {
+ IntegerAccessor.Proxy p = BindingsTestUtils.newProxyOverPipe(
+ IntegerAccessor.MANAGER, new IntegerAccessorImpl(), mCloseablesToClose);
+ Assert.assertEquals(0, p.getProxyHandler().getVersion());
+ p.getProxyHandler().queryVersion(new Callback1<Integer>() {
+ @Override
+ public void call(Integer version) {
+ Assert.assertEquals(3, version.intValue());
+ }
+ });
+ mTestRule.runLoopUntilIdle();
+ Assert.assertEquals(3, p.getProxyHandler().getVersion());
+ }
+
+ @Test
+ @SmallTest
+ public void testRequireVersion() {
+ IntegerAccessorImpl impl = new IntegerAccessorImpl();
+ IntegerAccessor.Proxy p = BindingsTestUtils.newProxyOverPipe(
+ IntegerAccessor.MANAGER, impl, mCloseablesToClose);
+
+ Assert.assertEquals(0, p.getProxyHandler().getVersion());
+
+ p.getProxyHandler().requireVersion(1);
+ Assert.assertEquals(1, p.getProxyHandler().getVersion());
+ p.setInteger(123, Enum.VALUE);
+ mTestRule.runLoopUntilIdle();
+ Assert.assertFalse(impl.encounteredError());
+ Assert.assertEquals(123, impl.getValue());
+
+ p.getProxyHandler().requireVersion(3);
+ Assert.assertEquals(3, p.getProxyHandler().getVersion());
+ p.setInteger(456, Enum.VALUE);
+ mTestRule.runLoopUntilIdle();
+ Assert.assertFalse(impl.encounteredError());
+ Assert.assertEquals(456, impl.getValue());
+
+ // Require a version that is not supported by the implementation side.
+ p.getProxyHandler().requireVersion(4);
+ // getVersion() is updated synchronously.
+ Assert.assertEquals(4, p.getProxyHandler().getVersion());
+ p.setInteger(789, Enum.VALUE);
+ mTestRule.runLoopUntilIdle();
+ Assert.assertTrue(impl.encounteredError());
+ // The call to setInteger() after requireVersion() is ignored.
+ Assert.assertEquals(456, impl.getValue());
+ }
+}
diff --git a/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java
new file mode 100644
index 0000000000..43387392a7
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java
@@ -0,0 +1,296 @@
+// 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.junit.After;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.mojo.MojoTestRule;
+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.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class InterfacesTest {
+ @Rule
+ public MojoTestRule mTestRule = new MojoTestRule();
+
+ private static final String OBJECT_NAME = "hello world";
+
+ private final List<Closeable> mCloseablesToClose = new ArrayList<Closeable>();
+
+ /**
+ * 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<NamedObject> obj) {
+ NamedObject.MANAGER.bind(new MockNamedObjectImpl(), obj);
+ }
+
+ @Override
+ public void requestImportedInterface(InterfaceRequest<ImportedInterface> 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()
+ */
+ @After
+ public 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();
+ }
+ }
+
+ /**
+ * 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) {
+ Assert.assertNull(impl.getLastMojoException());
+ Assert.assertEquals("", impl.getNameSynchronously());
+ }
+
+ proxy.getName(callback);
+ mTestRule.runLoopUntilIdle();
+
+ Assert.assertNull(errorHandler.getLastMojoException());
+ Assert.assertTrue(callback.wasCalled());
+ Assert.assertEquals("", callback.getName());
+
+ callback.reset();
+ proxy.setName(OBJECT_NAME);
+ mTestRule.runLoopUntilIdle();
+
+ Assert.assertNull(errorHandler.getLastMojoException());
+ if (impl != null) {
+ Assert.assertNull(impl.getLastMojoException());
+ Assert.assertEquals(OBJECT_NAME, impl.getNameSynchronously());
+ }
+
+ proxy.getName(callback);
+ mTestRule.runLoopUntilIdle();
+
+ Assert.assertNull(errorHandler.getLastMojoException());
+ Assert.assertTrue(callback.wasCalled());
+ Assert.assertEquals(OBJECT_NAME, callback.getName());
+ }
+
+ @Test
+ @SmallTest
+ public void testName() {
+ Assert.assertEquals("sample.NamedObject", NamedObject.MANAGER.getName());
+ }
+
+ @Test
+ @SmallTest
+ public void testProxyAndStub() {
+ MockNamedObjectImpl impl = new MockNamedObjectImpl();
+ NamedObject.Proxy proxy =
+ NamedObject.MANAGER.buildProxy(null, NamedObject.MANAGER.buildStub(null, impl));
+
+ checkProxy(proxy, impl);
+ }
+
+ @Test
+ @SmallTest
+ public void testProxyAndStubOverPipe() {
+ MockNamedObjectImpl impl = new MockNamedObjectImpl();
+ NamedObject.Proxy proxy =
+ BindingsTestUtils.newProxyOverPipe(NamedObject.MANAGER, impl, mCloseablesToClose);
+
+ checkProxy(proxy, impl);
+ }
+
+ @Test
+ @SmallTest
+ public void testFactoryOverPipe() {
+ Factory.Proxy proxy = BindingsTestUtils.newProxyOverPipe(
+ Factory.MANAGER, new MockFactoryImpl(), mCloseablesToClose);
+ Pair<NamedObject.Proxy, InterfaceRequest<NamedObject>> request =
+ NamedObject.MANAGER.getInterfaceRequest(CoreImpl.getInstance());
+ mCloseablesToClose.add(request.first);
+ proxy.createNamedObject(request.second);
+
+ checkProxy(request.first, null);
+ }
+
+ @Test
+ @SmallTest
+ public void testInterfaceClosing() {
+ MockFactoryImpl impl = new MockFactoryImpl();
+ Factory.Proxy proxy =
+ BindingsTestUtils.newProxyOverPipe(Factory.MANAGER, impl, mCloseablesToClose);
+
+ Assert.assertFalse(impl.isClosed());
+
+ proxy.close();
+ mTestRule.runLoopUntilIdle();
+
+ Assert.assertTrue(impl.isClosed());
+ }
+
+ @Test
+ @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<MessagePipeHandle, MessagePipeHandle> handles =
+ CoreImpl.getInstance().createMessagePipe(null);
+ DoStuffResponseImpl response = new DoStuffResponseImpl();
+ proxy.doStuff(request, handles.first, response);
+
+ Assert.assertFalse(response.wasResponseCalled());
+
+ mTestRule.runLoopUntilIdle();
+
+ Assert.assertTrue(response.wasResponseCalled());
+ }
+}
diff --git a/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java
new file mode 100644
index 0000000000..e2d24ed4c7
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java
@@ -0,0 +1,74 @@
+// 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.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.mojo.bindings.test.mojom.imported.Point;
+
+/**
+ * Testing internal classes of interfaces.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class MessageHeaderTest {
+ /**
+ * Testing that headers are identical after being serialized/deserialized.
+ */
+ @Test
+ @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();
+ Assert.assertTrue(header.validateHeader(type, 0));
+ Assert.assertEquals(type, header.getType());
+ Assert.assertEquals(0, header.getFlags());
+
+ Point p2 = Point.deserialize(message.getPayload());
+ Assert.assertNotNull(p2);
+ Assert.assertEquals(p.x, p2.x);
+ Assert.assertEquals(p.y, p2.y);
+ }
+
+ /**
+ * Testing that headers are identical after being serialized/deserialized.
+ */
+ @Test
+ @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();
+ Assert.assertTrue(header.validateHeader(type, MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG));
+ Assert.assertEquals(type, header.getType());
+ Assert.assertEquals(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, header.getFlags());
+ Assert.assertEquals(requestId, header.getRequestId());
+
+ Point p2 = Point.deserialize(message.getPayload());
+ Assert.assertNotNull(p2);
+ Assert.assertEquals(p.x, p2.x);
+ Assert.assertEquals(p.y, p2.y);
+ }
+}
diff --git a/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/NameGeneratorTest.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/NameGeneratorTest.java
new file mode 100644
index 0000000000..2a5a4b2785
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/NameGeneratorTest.java
@@ -0,0 +1,80 @@
+// 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.
+
+package org.chromium.mojo.bindings;
+
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.mojo.bindings.test.mojom.sample.NameGeneratorConstants;
+import org.chromium.mojo.bindings.test.mojom.sample.SupportedCases;
+
+/**
+ * Test mojom constant names generated for java.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class NameGeneratorTest {
+ @Test
+ @SmallTest
+ public void testLowerCamelCase() {
+ Assert.assertTrue(classHasField(SupportedCases.class, "LOWER_CAMEL_CASE"));
+ }
+
+ @Test
+ @SmallTest
+ public void testUpperCamelCase() {
+ Assert.assertTrue(classHasField(SupportedCases.class, "UPPER_CAMEL_CASE"));
+ }
+
+ @Test
+ @SmallTest
+ public void testSnakeCase() {
+ Assert.assertTrue(classHasField(SupportedCases.class, "SNAKE_CASE"));
+ }
+
+ @Test
+ @SmallTest
+ public void testMacroCase() {
+ Assert.assertTrue(classHasField(SupportedCases.class, "MACRO_CASE"));
+ }
+
+ @Test
+ @SmallTest
+ public void testHungarianNotation() {
+ Assert.assertTrue(classHasField(SupportedCases.class, "HUNGARIAN_NOTATION"));
+ }
+
+ @Test
+ @SmallTest
+ public void testUpperAcronym() {
+ Assert.assertTrue(classHasField(SupportedCases.class, "UPPER_ACRONYM_CASE"));
+ }
+
+ @Test
+ @SmallTest
+ public void testNames() {
+ Assert.assertTrue(classHasField(NameGeneratorConstants.class, "PAD_RSA_PKCS1_1_5_SIGN"));
+ Assert.assertTrue(classHasField(NameGeneratorConstants.class, "DIGEST_SHA1"));
+ Assert.assertTrue(classHasField(NameGeneratorConstants.class, "E2E_INTEGRATION"));
+ Assert.assertTrue(classHasField(NameGeneratorConstants.class, "M3_TEST"));
+ Assert.assertTrue(classHasField(NameGeneratorConstants.class, "URL_LOADER_FACTORY"));
+ Assert.assertTrue(classHasField(NameGeneratorConstants.class, "IPV6_ADDRESS"));
+ Assert.assertTrue(classHasField(NameGeneratorConstants.class, "NUMB3R5_IN_TH3_MIDDL3"));
+ Assert.assertTrue(classHasField(NameGeneratorConstants.class, "NAME_WITH_UNDERSCORE"));
+ Assert.assertTrue(classHasField(NameGeneratorConstants.class, "SINGLETON"));
+ }
+
+ private static <T> boolean classHasField(Class<T> clazz, String fieldName) {
+ try {
+ clazz.getField(fieldName);
+ return true;
+ } catch (NoSuchFieldException e) {
+ return false;
+ }
+ }
+}
diff --git a/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java
new file mode 100644
index 0000000000..b6283b8781
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java
@@ -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.
+
+package org.chromium.mojo.bindings;
+
+import android.support.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.mojo.MojoTestRule;
+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}.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class ReadAndDispatchMessageTest {
+ @Rule
+ public MojoTestRule mTestRule = new MojoTestRule();
+
+ private static final int DATA_SIZE = 1024;
+
+ private ByteBuffer mData;
+ private Pair<MessagePipeHandle, MessagePipeHandle> mHandles;
+ private List<Handle> mHandlesToSend = new ArrayList<Handle>();
+ private List<Handle> mHandlesToClose = new ArrayList<Handle>();
+ private RecordingMessageReceiver mMessageReceiver;
+
+ /**
+ * @see org.chromium.mojo.MojoTestCase#setUp()
+ */
+ @Before
+ public void setUp() throws Exception {
+ Core core = CoreImpl.getInstance();
+ mData = BindingsTestUtils.newRandomMessage(DATA_SIZE).getData();
+ mMessageReceiver = new RecordingMessageReceiver();
+ mHandles = core.createMessagePipe(new MessagePipeHandle.CreateOptions());
+ Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> 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()
+ */
+ @After
+ public void tearDown() throws Exception {
+ for (Handle handle : mHandlesToClose) {
+ handle.close();
+ }
+ }
+
+ /**
+ * Testing {@link Connector#readAndDispatchMessage(MessagePipeHandle, MessageReceiver)}
+ */
+ @Test
+ @SmallTest
+ public void testReadAndDispatchMessage() {
+ mHandles.first.writeMessage(mData, mHandlesToSend, MessagePipeHandle.WriteFlags.NONE);
+ Assert.assertEquals(MojoResult.OK,
+ Connector.readAndDispatchMessage(mHandles.second, mMessageReceiver)
+ .getMojoResult());
+ Assert.assertEquals(1, mMessageReceiver.messages.size());
+ Message message = mMessageReceiver.messages.get(0);
+ mHandlesToClose.addAll(message.getHandles());
+ Assert.assertEquals(mData, message.getData());
+ Assert.assertEquals(2, message.getHandles().size());
+ for (Handle handle : message.getHandles()) {
+ Assert.assertTrue(handle.isValid());
+ }
+ }
+
+ /**
+ * Testing {@link Connector#readAndDispatchMessage(MessagePipeHandle, MessageReceiver)}
+ * with no message available.
+ */
+ @Test
+ @SmallTest
+ public void testReadAndDispatchMessageOnEmptyHandle() {
+ Assert.assertEquals(MojoResult.SHOULD_WAIT,
+ Connector.readAndDispatchMessage(mHandles.second, mMessageReceiver)
+ .getMojoResult());
+ Assert.assertEquals(0, mMessageReceiver.messages.size());
+ }
+
+ /**
+ * Testing {@link Connector#readAndDispatchMessage(MessagePipeHandle, MessageReceiver)}
+ * on closed handle.
+ */
+ @Test
+ @SmallTest
+ public void testReadAndDispatchMessageOnClosedHandle() {
+ mHandles.first.close();
+ try {
+ Connector.readAndDispatchMessage(mHandles.second, mMessageReceiver);
+ Assert.fail("MojoException should have been thrown");
+ } catch (MojoException expected) {
+ Assert.assertEquals(MojoResult.FAILED_PRECONDITION, expected.getMojoResult());
+ }
+ Assert.assertEquals(0, mMessageReceiver.messages.size());
+ }
+}
diff --git a/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/RouterTest.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/RouterTest.java
new file mode 100644
index 0000000000..c2680ae269
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/RouterTest.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 org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.mojo.MojoTestRule;
+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}
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class RouterTest {
+ @Rule
+ public MojoTestRule mTestRule = new MojoTestRule();
+
+ private MessagePipeHandle mHandle;
+ private Router mRouter;
+ private RecordingMessageReceiverWithResponder mReceiver;
+ private CapturingErrorHandler mErrorHandler;
+
+ /**
+ * @see MojoTestCase#setUp()
+ */
+ @Before
+ public void setUp() throws Exception {
+ Core core = CoreImpl.getInstance();
+ Pair<MessagePipeHandle, MessagePipeHandle> 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.
+ */
+ @Test
+ @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);
+ ResultAnd<MessagePipeHandle.ReadMessageResult> result =
+ mHandle.readMessage(MessagePipeHandle.ReadFlags.NONE);
+
+ Assert.assertEquals(MojoResult.OK, result.getMojoResult());
+ MessageHeader receivedHeader =
+ new Message(ByteBuffer.wrap(result.getValue().mData), new ArrayList<Handle>())
+ .asServiceMessage()
+ .getHeader();
+
+ Assert.assertEquals(header.getType(), receivedHeader.getType());
+ Assert.assertEquals(header.getFlags(), receivedHeader.getFlags());
+ Assert.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<Handle>(),
+ MessagePipeHandle.WriteFlags.NONE);
+ mTestRule.runLoopUntilIdle();
+
+ Assert.assertEquals(1, mReceiver.messages.size());
+ ServiceMessage receivedResponseMessage = mReceiver.messages.get(0).asServiceMessage();
+ Assert.assertEquals(MessageHeader.MESSAGE_IS_RESPONSE_FLAG,
+ receivedResponseMessage.getHeader().getFlags());
+ Assert.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<Handle>(),
+ MessagePipeHandle.WriteFlags.NONE);
+ mTestRule.runLoopUntilIdle();
+
+ Assert.assertEquals(messageIndex + 1, mReceiver.messagesWithReceivers.size());
+ Pair<Message, MessageReceiver> receivedMessage =
+ mReceiver.messagesWithReceivers.get(messageIndex);
+ Assert.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<Message, MessageReceiver> 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);
+
+ ResultAnd<MessagePipeHandle.ReadMessageResult> result =
+ mHandle.readMessage(MessagePipeHandle.ReadFlags.NONE);
+
+ Assert.assertEquals(MojoResult.OK, result.getMojoResult());
+ Assert.assertEquals(message.getData(), ByteBuffer.wrap(result.getValue().mData));
+ }
+
+ /**
+ * Clears {@code mReceiver.messagesWithReceivers} allowing all message receivers to be
+ * finalized.
+ * <p>
+ * 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
+ public void run() {
+ for (Pair<Message, MessageReceiver> 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.
+ */
+ @Test
+ @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.
+ */
+ @Test
+ @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.
+ mTestRule.runLoopUntilIdle();
+
+ // Confirm that the pipe was closed on the Router side.
+ HandleSignals closedFlag = HandleSignals.none().setPeerClosed(true);
+ Assert.assertEquals(closedFlag, mHandle.querySignalsState().getSatisfiedSignals());
+ }
+}
diff --git a/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/SerializationTest.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/SerializationTest.java
new file mode 100644
index 0000000000..5ee6743b46
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/SerializationTest.java
@@ -0,0 +1,186 @@
+// 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.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+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 .
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class SerializationTest {
+ private static void assertThrowsSerializationException(Struct struct) {
+ try {
+ struct.serialize(null);
+ Assert.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.
+ */
+ @Test
+ @SmallTest
+ public void testHandle() {
+ Struct2 struct = new Struct2();
+ Assert.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.
+ */
+ @Test
+ @SmallTest
+ public void testStructPointer() {
+ Struct3 struct = new Struct3();
+ Assert.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.
+ */
+ @Test
+ @SmallTest
+ public void testStructArray() {
+ Struct4 struct = new Struct4();
+ Assert.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.
+ */
+ @Test
+ @SmallTest
+ public void testFixedSizeArray() {
+ Struct5 struct = new Struct5();
+ Assert.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.
+ */
+ @Test
+ @SmallTest
+ public void testString() {
+ Struct6 struct = new Struct6();
+ Assert.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.
+ */
+ @Test
+ @SmallTest
+ public void testNullableFields() {
+ StructOfNullables struct = new StructOfNullables();
+ Assert.assertFalse(struct.hdl.isValid());
+ Assert.assertNull(struct.struct1);
+ Assert.assertNull(struct.str);
+ struct.serialize(null);
+ }
+
+ /**
+ * Verifies that a struct can be serialized to and deserialized from a ByteBuffer.
+ */
+ @Test
+ @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);
+ Assert.assertEquals(expected_buf, buf);
+
+ Struct1 output = Struct1.deserialize(buf);
+ Assert.assertEquals(0x7F, output.i);
+ }
+
+ /**
+ * Verifies that a struct with handles cannot be serialized to a ByteBuffer.
+ */
+ @Test
+ @SmallTest
+ public void testByteBufferSerializationWithHandles() {
+ StructOfNullables struct = new StructOfNullables();
+ Assert.assertFalse(struct.hdl.isValid());
+ Assert.assertNull(struct.struct1);
+ Assert.assertNull(struct.str);
+
+ // It is okay to serialize invalid handles.
+ struct.serialize();
+
+ struct.hdl = new HandleMock();
+
+ try {
+ struct.serialize();
+ Assert.fail("Serializing a struct with handles to a ByteBuffer should have thrown an "
+ + "exception.");
+ } catch (UnsupportedOperationException ex) {
+ // Expected.
+ }
+ }
+}
diff --git a/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTest.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTest.java
new file mode 100644
index 0000000000..101f166fd0
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTest.java
@@ -0,0 +1,247 @@
+// 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.annotation.SuppressLint;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.base.test.util.UrlUtils;
+import org.chromium.mojo.HandleMock;
+import org.chromium.mojo.MojoTestRule;
+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.
+ * <p>
+ * 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.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class ValidationTest {
+ @Rule
+ public MojoTestRule mTestRule = new MojoTestRule();
+
+ /**
+ * 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");
+ }
+ }
+
+ @SuppressLint("NewApi")
+ private static String getStringContent(File f) throws FileNotFoundException {
+ // TODO(crbug.com/635567): Fix this properly.
+ 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<TestData> getTestData(String prefix) throws FileNotFoundException {
+ List<TestData> results = new ArrayList<TestData>();
+
+ // Fail if the test data is not present.
+ if (!VALIDATION_TEST_DATA_PATH.isDirectory()) {
+ Assert.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> testData = getTestData(prefix);
+ for (TestData test : testData) {
+ Assert.assertNull("Unable to read: " + test.dataFile.getName() + ": "
+ + test.inputData.getErrorMessage(),
+ test.inputData.getErrorMessage());
+ List<Handle> handles = new ArrayList<Handle>();
+ 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")) {
+ Assert.fail("Input: " + test.dataFile.getName()
+ + ": The message should have been refused. Expected error: "
+ + test.expectedResult);
+ }
+ if (!passed && test.expectedResult.equals("PASS")) {
+ Assert.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.
+ */
+ @Test
+ @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.
+ */
+ @Test
+ @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.
+ */
+ @Test
+ @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.
+ */
+ @Test
+ @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/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java
new file mode 100644
index 0000000000..3b59ffdae3
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java
@@ -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.
+
+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/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java
new file mode 100644
index 0000000000..314a29fc65
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java
@@ -0,0 +1,150 @@
+// 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.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.mojo.MojoTestRule;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Testing {@link ValidationTestUtil}.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class ValidationTestUtilTest {
+ @Rule
+ public MojoTestRule mTestRule = new MojoTestRule();
+
+ /**
+ * 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) {
+ Assert.assertNull(data.getErrorMessage());
+ Assert.assertEquals(expectedData, data.getData());
+ Assert.assertEquals(expectedHandlesCount, data.getHandlesCount());
+ } else {
+ Assert.assertNotNull(data.getErrorMessage());
+ Assert.assertNull(data.getData());
+ }
+ }
+
+ /**
+ * Testing {@link ValidationTestUtil#parseData(String)}.
+ */
+ @Test
+ @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/public/java/system/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java
new file mode 100644
index 0000000000..8a431905b8
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java
@@ -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.
+
+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/public/java/system/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java
new file mode 100644
index 0000000000..697086017e
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java
@@ -0,0 +1,529 @@
+// 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.junit.After;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.mojo.MojoTestRule;
+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.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class CoreImplTest {
+ @Rule
+ public MojoTestRule mTestRule = new MojoTestRule();
+
+ 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<Handle> mHandlesToClose = new ArrayList<Handle>();
+
+ /**
+ * @see MojoTestCase#tearDown()
+ */
+ @After
+ public 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;
+ }
+ }
+
+ private void addHandleToClose(Handle handle) {
+ mHandlesToClose.add(handle);
+ }
+
+ private void addHandlePairToClose(Pair<? extends Handle, ? extends Handle> 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);
+
+ // Read the message back.
+ ResultAnd<MessagePipeHandle.ReadMessageResult> result =
+ out.readMessage(MessagePipeHandle.ReadFlags.NONE);
+ Assert.assertEquals(MojoResult.OK, result.getMojoResult());
+ Assert.assertTrue(Arrays.equals(bytes, result.getValue().mData));
+ Assert.assertEquals(0, result.getValue().mHandles.size());
+ }
+
+ 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<Integer> result = in.writeData(buffer, DataPipe.WriteFlags.NONE);
+ Assert.assertEquals(MojoResult.OK, result.getMojoResult());
+ Assert.assertEquals(bytes.length, result.getValue().intValue());
+
+ // Query number of bytes available.
+ ResultAnd<Integer> readResult = out.readData(null, DataPipe.ReadFlags.none().query(true));
+ Assert.assertEquals(MojoResult.OK, readResult.getMojoResult());
+ Assert.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));
+ Assert.assertEquals(MojoResult.OK, readResult.getMojoResult());
+ Assert.assertEquals(bytes.length, readResult.getValue().intValue());
+ Assert.assertEquals(bytes.length, peekBuffer.limit());
+ byte[] peekBytes = new byte[bytes.length];
+ peekBuffer.get(peekBytes);
+ Assert.assertTrue(Arrays.equals(bytes, peekBytes));
+
+ // Read into a buffer.
+ ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length);
+ readResult = out.readData(receiveBuffer, DataPipe.ReadFlags.NONE);
+ Assert.assertEquals(MojoResult.OK, readResult.getMojoResult());
+ Assert.assertEquals(bytes.length, readResult.getValue().intValue());
+ Assert.assertEquals(0, receiveBuffer.position());
+ Assert.assertEquals(bytes.length, receiveBuffer.limit());
+ byte[] receivedBytes = new byte[bytes.length];
+ receiveBuffer.get(receivedBytes);
+ Assert.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);
+ Assert.assertEquals(8, buffer1.capacity());
+ ByteBuffer buffer2 = out.map(0, 8, SharedBufferHandle.MapFlags.NONE);
+ Assert.assertEquals(8, buffer2.capacity());
+
+ byte[] bytes = new byte[8];
+ random.nextBytes(bytes);
+ buffer1.put(bytes);
+
+ byte[] receivedBytes = new byte[bytes.length];
+ buffer2.get(receivedBytes);
+
+ Assert.assertTrue(Arrays.equals(bytes, receivedBytes));
+
+ in.unmap(buffer1);
+ out.unmap(buffer2);
+ }
+
+ /**
+ * Testing that Core can be retrieved from a handle.
+ */
+ @Test
+ @SmallTest
+ public void testGetCore() {
+ Core core = CoreImpl.getInstance();
+
+ Pair<? extends Handle, ? extends Handle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+ Assert.assertEquals(core, handles.first.getCore());
+ Assert.assertEquals(core, handles.second.getCore());
+
+ handles = core.createDataPipe(null);
+ addHandlePairToClose(handles);
+ Assert.assertEquals(core, handles.first.getCore());
+ Assert.assertEquals(core, handles.second.getCore());
+
+ SharedBufferHandle handle = core.createSharedBuffer(null, 100);
+ SharedBufferHandle handle2 = handle.duplicate(null);
+ addHandleToClose(handle);
+ addHandleToClose(handle2);
+ Assert.assertEquals(core, handle.getCore());
+ Assert.assertEquals(core, handle2.getCore());
+ }
+
+ private static void createAndCloseMessagePipe(MessagePipeHandle.CreateOptions options) {
+ Core core = CoreImpl.getInstance();
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(options);
+ handles.first.close();
+ handles.second.close();
+ }
+
+ /**
+ * Testing {@link MessagePipeHandle} creation.
+ */
+ @Test
+ @SmallTest
+ public void testMessagePipeCreation() {
+ // Test creation with null options.
+ createAndCloseMessagePipe(null);
+ // Test creation with default options.
+ createAndCloseMessagePipe(new MessagePipeHandle.CreateOptions());
+ }
+
+ /**
+ * Testing {@link MessagePipeHandle}.
+ */
+ @Test
+ @SmallTest
+ public void testMessagePipeEmpty() {
+ Core core = CoreImpl.getInstance();
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ // Testing read on an empty pipe.
+ ResultAnd<MessagePipeHandle.ReadMessageResult> readResult =
+ handles.first.readMessage(MessagePipeHandle.ReadFlags.NONE);
+ Assert.assertEquals(MojoResult.SHOULD_WAIT, readResult.getMojoResult());
+
+ handles.first.close();
+ handles.second.close();
+ }
+
+ /**
+ * Testing {@link MessagePipeHandle}.
+ */
+ @Test
+ @SmallTest
+ public void testMessagePipeSend() {
+ Core core = CoreImpl.getInstance();
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ checkSendingMessage(handles.first, handles.second);
+ checkSendingMessage(handles.second, handles.first);
+ }
+
+ /**
+ * Testing {@link MessagePipeHandle}.
+ */
+ @Test
+ @SmallTest
+ public void testMessagePipeSendHandles() {
+ Core core = CoreImpl.getInstance();
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ Pair<MessagePipeHandle, MessagePipeHandle> handlesToShare = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+ addHandlePairToClose(handlesToShare);
+
+ handles.first.writeMessage(null, Collections.<Handle>singletonList(handlesToShare.second),
+ MessagePipeHandle.WriteFlags.NONE);
+ Assert.assertFalse(handlesToShare.second.isValid());
+ ResultAnd<MessagePipeHandle.ReadMessageResult> readMessageResult =
+ handles.second.readMessage(MessagePipeHandle.ReadFlags.NONE);
+ Assert.assertEquals(1, readMessageResult.getValue().mHandles.size());
+ MessagePipeHandle newHandle =
+ readMessageResult.getValue().mHandles.get(0).toMessagePipeHandle();
+ addHandleToClose(newHandle);
+ Assert.assertTrue(newHandle.isValid());
+ checkSendingMessage(handlesToShare.first, newHandle);
+ checkSendingMessage(newHandle, handlesToShare.first);
+ }
+
+ private static void createAndCloseDataPipe(DataPipe.CreateOptions options) {
+ Core core = CoreImpl.getInstance();
+ Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles =
+ core.createDataPipe(options);
+ handles.first.close();
+ handles.second.close();
+ }
+
+ /**
+ * Testing {@link DataPipe}.
+ */
+ @Test
+ @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}.
+ */
+ @Test
+ @SmallTest
+ public void testDataPipeSend() {
+ Core core = CoreImpl.getInstance();
+
+ Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(null);
+ addHandlePairToClose(handles);
+
+ checkSendingData(handles.first, handles.second);
+ }
+
+ /**
+ * Testing {@link DataPipe}.
+ */
+ @Test
+ @SmallTest
+ public void testDataPipeTwoPhaseSend() {
+ Random random = new Random();
+ Core core = CoreImpl.getInstance();
+ Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> 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);
+ Assert.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);
+ Assert.assertEquals(0, receiveBuffer.position());
+ Assert.assertEquals(bytes.length, receiveBuffer.limit());
+ byte[] receivedBytes = new byte[bytes.length];
+ receiveBuffer.get(receivedBytes);
+ Assert.assertTrue(Arrays.equals(bytes, receivedBytes));
+ handles.second.endReadData(bytes.length);
+ }
+
+ /**
+ * Testing {@link DataPipe}.
+ */
+ @Test
+ @SmallTest
+ public void testDataPipeDiscard() {
+ Random random = new Random();
+ Core core = CoreImpl.getInstance();
+ Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> 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<Integer> result = handles.first.writeData(buffer, DataPipe.WriteFlags.NONE);
+ Assert.assertEquals(MojoResult.OK, result.getMojoResult());
+ Assert.assertEquals(bytes.length, result.getValue().intValue());
+
+ // Discard bytes.
+ final int nbBytesToDiscard = 4;
+ Assert.assertEquals(nbBytesToDiscard,
+ handles.second.discardData(nbBytesToDiscard, DataPipe.ReadFlags.NONE));
+
+ // Read into a buffer.
+ ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length - nbBytesToDiscard);
+ ResultAnd<Integer> readResult =
+ handles.second.readData(receiveBuffer, DataPipe.ReadFlags.NONE);
+ Assert.assertEquals(MojoResult.OK, readResult.getMojoResult());
+ Assert.assertEquals(bytes.length - nbBytesToDiscard, readResult.getValue().intValue());
+ Assert.assertEquals(0, receiveBuffer.position());
+ Assert.assertEquals(bytes.length - nbBytesToDiscard, receiveBuffer.limit());
+ byte[] receivedBytes = new byte[bytes.length - nbBytesToDiscard];
+ receiveBuffer.get(receivedBytes);
+ Assert.assertTrue(Arrays.equals(
+ Arrays.copyOfRange(bytes, nbBytesToDiscard, bytes.length), receivedBytes));
+ }
+
+ /**
+ * Testing {@link SharedBufferHandle}.
+ */
+ @Test
+ @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}.
+ */
+ @Test
+ @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}.
+ */
+ @Test
+ @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.
+ */
+ @Test
+ @SmallTest
+ public void testInvalidHandle() {
+ Core core = CoreImpl.getInstance();
+ Handle handle = InvalidHandle.INSTANCE;
+
+ // Checking sending an invalid handle. Should result in an ABORTED
+ // exception.
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+ try {
+ handles.first.writeMessage(null, Collections.<Handle>singletonList(handle),
+ MessagePipeHandle.WriteFlags.NONE);
+ Assert.fail();
+ } catch (MojoException e) {
+ Assert.assertEquals(MojoResult.ABORTED, e.getMojoResult());
+ }
+ }
+
+ /**
+ * Testing the pass method on message pipes.
+ */
+ @Test
+ @SmallTest
+ public void testMessagePipeHandlePass() {
+ Core core = CoreImpl.getInstance();
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ Assert.assertTrue(handles.first.isValid());
+ MessagePipeHandle handleClone = handles.first.pass();
+
+ addHandleToClose(handleClone);
+
+ Assert.assertFalse(handles.first.isValid());
+ Assert.assertTrue(handleClone.isValid());
+ checkSendingMessage(handleClone, handles.second);
+ checkSendingMessage(handles.second, handleClone);
+ }
+
+ /**
+ * Testing the pass method on data pipes.
+ */
+ @Test
+ @SmallTest
+ public void testDataPipeHandlePass() {
+ Core core = CoreImpl.getInstance();
+ Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(null);
+ addHandlePairToClose(handles);
+
+ DataPipe.ProducerHandle producerClone = handles.first.pass();
+ DataPipe.ConsumerHandle consumerClone = handles.second.pass();
+
+ addHandleToClose(producerClone);
+ addHandleToClose(consumerClone);
+
+ Assert.assertFalse(handles.first.isValid());
+ Assert.assertFalse(handles.second.isValid());
+ Assert.assertTrue(producerClone.isValid());
+ Assert.assertTrue(consumerClone.isValid());
+ checkSendingData(producerClone, consumerClone);
+ }
+
+ /**
+ * Testing the pass method on shared buffers.
+ */
+ @Test
+ @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);
+
+ Assert.assertFalse(handle.isValid());
+ Assert.assertTrue(handleClone.isValid());
+ checkSharing(handleClone, newHandleClone);
+ checkSharing(newHandleClone, handleClone);
+ }
+
+ /**
+ * esting handle conversion to native and back.
+ */
+ @Test
+ @SmallTest
+ public void testHandleConversion() {
+ Core core = CoreImpl.getInstance();
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ MessagePipeHandle converted =
+ core.acquireNativeHandle(handles.first.releaseNativeHandle()).toMessagePipeHandle();
+ addHandleToClose(converted);
+
+ Assert.assertFalse(handles.first.isValid());
+
+ checkSendingMessage(converted, handles.second);
+ checkSendingMessage(handles.second, converted);
+ }
+}
diff --git a/mojo/public/java/system/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java b/mojo/public/java/system/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java
new file mode 100644
index 0000000000..11f83e8464
--- /dev/null
+++ b/mojo/public/java/system/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java
@@ -0,0 +1,271 @@
+// 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.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.mojo.MojoTestRule;
+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.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class WatcherImplTest {
+ @Rule
+ public MojoTestRule mTestRule = new MojoTestRule();
+
+ private List<Handle> mHandlesToClose = new ArrayList<Handle>();
+ private Watcher mWatcher;
+ private Core mCore;
+
+ /**
+ * @see MojoTestCase#setUp()
+ */
+ @Before
+ public void setUp() throws Exception {
+ mWatcher = new WatcherImpl();
+ mCore = CoreImpl.getInstance();
+ }
+
+ /**
+ * @see MojoTestCase#tearDown()
+ */
+ @After
+ public 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;
+ }
+ }
+
+ private void addHandlePairToClose(Pair<? extends Handle, ? extends Handle> 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(MessagePipeHandle.ReadFlags.NONE);
+ }
+ }
+
+ /**
+ * @return the result
+ */
+ public int getResult() {
+ return mResult;
+ }
+ }
+
+ /**
+ * Testing {@link Watcher} implementation.
+ */
+ @Test
+ @SmallTest
+ public void testCorrectResult() {
+ // Checking a correct result.
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null);
+ addHandlePairToClose(handles);
+ final WatcherResult watcherResult = new WatcherResult(handles.first);
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+
+ mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult);
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+
+ handles.second.writeMessage(
+ ByteBuffer.allocateDirect(1), null, MessagePipeHandle.WriteFlags.NONE);
+ mTestRule.runLoopUntilIdle();
+ Assert.assertEquals(MojoResult.OK, watcherResult.getResult());
+ }
+
+ /**
+ * Testing {@link Watcher} implementation.
+ */
+ @Test
+ @SmallTest
+ public void testClosingPeerHandle() {
+ // Closing the peer handle.
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ final WatcherResult watcherResult = new WatcherResult();
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+
+ mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult);
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+
+ mTestRule.runLoopUntilIdle();
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+
+ handles.second.close();
+ mTestRule.runLoopUntilIdle();
+ Assert.assertEquals(MojoResult.FAILED_PRECONDITION, watcherResult.getResult());
+ }
+
+ /**
+ * Testing {@link Watcher} implementation.
+ */
+ @Test
+ @SmallTest
+ public void testClosingWatchedHandle() {
+ // Closing the peer handle.
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ final WatcherResult watcherResult = new WatcherResult();
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+
+ mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult);
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+
+ mTestRule.runLoopUntilIdle();
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+
+ handles.first.close();
+ mTestRule.runLoopUntilIdle();
+ Assert.assertEquals(MojoResult.CANCELLED, watcherResult.getResult());
+ }
+
+ /**
+ * Testing {@link Watcher} implementation.
+ */
+ @Test
+ @SmallTest
+ public void testInvalidHandle() {
+ // Closing the peer handle.
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ final WatcherResult watcherResult = new WatcherResult();
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+
+ handles.first.close();
+ Assert.assertEquals(MojoResult.INVALID_ARGUMENT,
+ mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult));
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+
+ mTestRule.runLoopUntilIdle();
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+ }
+
+ /**
+ * Testing {@link Watcher} implementation.
+ */
+ @Test
+ @SmallTest
+ public void testDefaultInvalidHandle() {
+ final WatcherResult watcherResult = new WatcherResult();
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+
+ Assert.assertEquals(MojoResult.INVALID_ARGUMENT,
+ mWatcher.start(InvalidHandle.INSTANCE, Core.HandleSignals.READABLE, watcherResult));
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+
+ mTestRule.runLoopUntilIdle();
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+ }
+
+ /**
+ * Testing {@link Watcher} implementation.
+ */
+ @Test
+ @SmallTest
+ public void testCancel() {
+ // Closing the peer handle.
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ final WatcherResult watcherResult = new WatcherResult();
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+
+ mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult);
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+
+ mTestRule.runLoopUntilIdle();
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+
+ mWatcher.cancel();
+ mTestRule.runLoopUntilIdle();
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+
+ handles.second.writeMessage(
+ ByteBuffer.allocateDirect(1), null, MessagePipeHandle.WriteFlags.NONE);
+ mTestRule.runLoopUntilIdle();
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+ }
+
+ /**
+ * Testing {@link Watcher} implementation.
+ */
+ @Test
+ @SmallTest
+ public void testImmediateCancelOnInvalidHandle() {
+ // Closing the peer handle.
+ Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null);
+ addHandlePairToClose(handles);
+
+ final WatcherResult watcherResult = new WatcherResult();
+ handles.first.close();
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+
+ mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult);
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+ mWatcher.cancel();
+
+ mTestRule.runLoopUntilIdle();
+ Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult());
+ }
+}
diff --git a/mojo/public/java/system/javatests/validation_test_util.cc b/mojo/public/java/system/javatests/validation_test_util.cc
new file mode 100644
index 0000000000..277a2d5b63
--- /dev/null
+++ b/mojo/public/java/system/javatests/validation_test_util.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 <stddef.h>
+#include <stdint.h>
+
+#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 {
+
+ScopedJavaLocalRef<jobject> JNI_ValidationTestUtil_ParseData(
+ JNIEnv* env,
+ const JavaParamRef<jclass>& jcaller,
+ const JavaParamRef<jstring>& data_as_string) {
+ std::string input =
+ base::android::ConvertJavaStringToUTF8(env, data_as_string);
+ std::vector<uint8_t> data;
+ size_t num_handles;
+ std::string error_message;
+ if (!test::ParseValidationTestInput(input, &data, &num_handles,
+ &error_message)) {
+ ScopedJavaLocalRef<jstring> j_error_message =
+ base::android::ConvertUTF8ToJavaString(env, error_message);
+ return Java_ValidationTestUtil_buildData(env, nullptr, 0, j_error_message);
+ }
+ void* data_ptr = data.data();
+ if (!data_ptr) {
+ DCHECK(!data.size());
+ data_ptr = &data;
+ }
+ ScopedJavaLocalRef<jobject> byte_buffer(
+ env, env->NewDirectByteBuffer(data_ptr, data.size()));
+ return Java_ValidationTestUtil_buildData(env, byte_buffer, num_handles,
+ nullptr);
+}
+
+} // namespace android
+} // namespace mojo
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java
index f8b99c6d66..5292605588 100644
--- a/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java
@@ -208,11 +208,10 @@ public class InvalidHandle implements UntypedHandle, MessagePipeHandle, Consumer
}
/**
- * @see MessagePipeHandle#readMessage(java.nio.ByteBuffer, int, MessagePipeHandle.ReadFlags)
+ * @see MessagePipeHandle#readMessage(MessagePipeHandle.ReadFlags)
*/
@Override
- public ResultAnd<ReadMessageResult> readMessage(
- ByteBuffer bytes, int maxNumberOfHandles, ReadFlags flags) {
+ public ResultAnd<ReadMessageResult> readMessage(ReadFlags flags) {
throw new MojoException(MojoResult.INVALID_ARGUMENT);
}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java
index deb6ac0f01..baa5a9549e 100644
--- a/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java
@@ -90,7 +90,6 @@ public interface MessagePipeHandle extends Handle {
*/
public static class ReadFlags extends Flags<ReadFlags> {
private static final int FLAG_NONE = 0;
- private static final int FLAG_MAY_DISCARD = 1 << 0;
/**
* Immutable flag with no bit set.
@@ -107,18 +106,6 @@ public interface MessagePipeHandle extends Handle {
}
/**
- * Change the may-discard bit of this flag. If set, 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).
- *
- * @param mayDiscard the new value of the may-discard bit.
- * @return this.
- */
- public ReadFlags setMayDiscard(boolean mayDiscard) {
- return setFlag(FLAG_MAY_DISCARD, mayDiscard);
- }
-
- /**
* @return a flag with no bit set.
*/
public static ReadFlags none() {
@@ -132,61 +119,17 @@ public interface MessagePipeHandle extends Handle {
*/
public static class ReadMessageResult {
/**
- * If a message was read, the size in bytes of the message, otherwise the size in bytes of
- * the next message.
+ * If a message was read, this contains the bytes of its data.
*/
- private int mMessageSize;
+ public byte[] mData;
/**
- * If a message was read, the number of handles contained in the message, otherwise the
- * number of handles contained in the next message.
+ * If a message was read, this contains the raw handle values.
*/
- private int mHandlesCount;
+ public int[] mRawHandles;
/**
* If a message was read, the handles contained in the message, undefined otherwise.
*/
- private List<UntypedHandle> mHandles;
-
- /**
- * @return the messageSize
- */
- public int getMessageSize() {
- return mMessageSize;
- }
-
- /**
- * @param messageSize the messageSize to set
- */
- public void setMessageSize(int messageSize) {
- mMessageSize = messageSize;
- }
-
- /**
- * @return the handlesCount
- */
- public int getHandlesCount() {
- return mHandlesCount;
- }
-
- /**
- * @param handlesCount the handlesCount to set
- */
- public void setHandlesCount(int handlesCount) {
- mHandlesCount = handlesCount;
- }
-
- /**
- * @return the handles
- */
- public List<UntypedHandle> getHandles() {
- return mHandles;
- }
-
- /**
- * @param handles the handles to set
- */
- public void setHandles(List<UntypedHandle> handles) {
- mHandles = handles;
- }
+ public List<UntypedHandle> mHandles;
}
/**
@@ -219,6 +162,5 @@ public interface MessagePipeHandle extends Handle {
* will be true, or the read is NOT done and |wasMessageRead| will be false (if |mayDiscard| was
* set, the message is also discarded in this case).
*/
- ResultAnd<ReadMessageResult> readMessage(
- ByteBuffer bytes, int maxNumberOfHandles, ReadFlags flags);
+ ResultAnd<ReadMessageResult> readMessage(ReadFlags flags);
}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java b/mojo/public/java/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java
new file mode 100644
index 0000000000..8295906f3a
--- /dev/null
+++ b/mojo/public/java/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();
+ }
+
+ @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();
+ private native void nativePostDelayedTask(long runLoopID, Runnable runnable, long delay);
+ private native void nativeDeleteMessageLoop(long runLoopID);
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/impl/CoreImpl.java b/mojo/public/java/system/src/org/chromium/mojo/system/impl/CoreImpl.java
new file mode 100644
index 0000000000..6ddac5b99b
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/impl/CoreImpl.java
@@ -0,0 +1,515 @@
+// 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<BaseRunLoop> mCurrentRunLoop = new ThreadLocal<BaseRunLoop>();
+
+ /**
+ * 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<MessagePipeHandle, MessagePipeHandle> createMessagePipe(
+ MessagePipeHandle.CreateOptions options) {
+ ByteBuffer optionsBuffer = null;
+ if (options != null) {
+ optionsBuffer = allocateDirectBuffer(8);
+ optionsBuffer.putInt(0, 8);
+ optionsBuffer.putInt(4, options.getFlags().getFlags());
+ }
+ ResultAnd<IntegerPair> result = nativeCreateMessagePipe(optionsBuffer);
+ if (result.getMojoResult() != MojoResult.OK) {
+ throw new MojoException(result.getMojoResult());
+ }
+ return Pair.<MessagePipeHandle, MessagePipeHandle>create(
+ new MessagePipeHandleImpl(this, result.getValue().first),
+ new MessagePipeHandleImpl(this, result.getValue().second));
+ }
+
+ /**
+ * @see Core#createDataPipe(DataPipe.CreateOptions)
+ */
+ @Override
+ public Pair<ProducerHandle, ConsumerHandle> 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<IntegerPair> result = nativeCreateDataPipe(optionsBuffer);
+ if (result.getMojoResult() != MojoResult.OK) {
+ throw new MojoException(result.getMojoResult());
+ }
+ return Pair.<ProducerHandle, ConsumerHandle>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<Integer> 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<? extends Handle> 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(MessagePipeHandle.ReadFlags)
+ */
+ ResultAnd<MessagePipeHandle.ReadMessageResult> readMessage(
+ MessagePipeHandleImpl handle, MessagePipeHandle.ReadFlags flags) {
+ ResultAnd<MessagePipeHandle.ReadMessageResult> result =
+ nativeReadMessage(handle.getMojoHandle(), flags.getFlags());
+ if (result.getMojoResult() != MojoResult.OK
+ && result.getMojoResult() != MojoResult.SHOULD_WAIT) {
+ throw new MojoException(result.getMojoResult());
+ }
+
+ MessagePipeHandle.ReadMessageResult readResult = result.getValue();
+ int[] rawHandles = readResult.mRawHandles;
+ if (rawHandles != null && rawHandles.length != 0) {
+ readResult.mHandles = new ArrayList<UntypedHandle>(rawHandles.length);
+ for (int rawHandle : rawHandles) {
+ readResult.mHandles.add(new UntypedHandleImpl(this, rawHandle));
+ }
+ } else {
+ readResult.mHandles = new ArrayList<UntypedHandle>(0);
+ }
+
+ return result;
+ }
+
+ /**
+ * @see ConsumerHandle#discardData(int, DataPipe.ReadFlags)
+ */
+ int discardData(DataPipeConsumerHandleImpl handle, int numBytes, DataPipe.ReadFlags flags) {
+ ResultAnd<Integer> 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<Integer> readData(
+ DataPipeConsumerHandleImpl handle, ByteBuffer elements, DataPipe.ReadFlags flags) {
+ ResultAnd<Integer> 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<ByteBuffer> 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<Integer> 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<ByteBuffer> 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<Integer> 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<ByteBuffer> 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<ByteBuffer> newResultAndBuffer(int mojoResult, ByteBuffer buffer) {
+ return new ResultAnd<>(mojoResult, buffer);
+ }
+
+ /**
+ * Trivial alias for Pair<Integer, Integer>. This is needed because our jni generator is unable
+ * to handle class that contains space.
+ */
+ private static final class IntegerPair extends Pair<Integer, Integer> {
+ public IntegerPair(Integer first, Integer second) {
+ super(first, second);
+ }
+ }
+
+ @CalledByNative
+ private static ResultAnd<MessagePipeHandle.ReadMessageResult> newReadMessageResult(
+ int mojoResult, byte[] data, int[] rawHandles) {
+ MessagePipeHandle.ReadMessageResult result = new MessagePipeHandle.ReadMessageResult();
+ if (mojoResult == MojoResult.OK) {
+ result.mData = data;
+ result.mRawHandles = rawHandles;
+ }
+ return new ResultAnd<>(mojoResult, result);
+ }
+
+ @CalledByNative
+ private static ResultAnd<Integer> newResultAndInteger(int mojoResult, int numBytesRead) {
+ return new ResultAnd<>(mojoResult, numBytesRead);
+ }
+
+ @CalledByNative
+ private static ResultAnd<IntegerPair> newNativeCreationResult(
+ int mojoResult, int mojoHandle1, int mojoHandle2) {
+ return new ResultAnd<>(mojoResult, new IntegerPair(mojoHandle1, mojoHandle2));
+ }
+
+ private native long nativeGetTimeTicksNow();
+
+ private native ResultAnd<IntegerPair> nativeCreateMessagePipe(ByteBuffer optionsBuffer);
+
+ private native ResultAnd<IntegerPair> nativeCreateDataPipe(ByteBuffer optionsBuffer);
+
+ private native ResultAnd<Integer> 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<MessagePipeHandle.ReadMessageResult> nativeReadMessage(
+ int mojoHandle, int flags);
+
+ private native ResultAnd<Integer> nativeReadData(
+ int mojoHandle, ByteBuffer elements, int elementsSize, int flags);
+
+ private native ResultAnd<ByteBuffer> nativeBeginReadData(
+ int mojoHandle, int numBytes, int flags);
+
+ private native int nativeEndReadData(int mojoHandle, int numBytesRead);
+
+ private native ResultAnd<Integer> nativeWriteData(
+ int mojoHandle, ByteBuffer elements, int limit, int flags);
+
+ private native ResultAnd<ByteBuffer> nativeBeginWriteData(
+ int mojoHandle, int numBytes, int flags);
+
+ private native int nativeEndWriteData(int mojoHandle, int numBytesWritten);
+
+ private native ResultAnd<Integer> nativeDuplicate(int mojoHandle, ByteBuffer optionsBuffer);
+
+ private native ResultAnd<ByteBuffer> 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/public/java/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java b/mojo/public/java/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java
new file mode 100644
index 0000000000..820a6f94f8
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java
@@ -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.
+
+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<Integer> 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/public/java/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java b/mojo/public/java/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java
new file mode 100644
index 0000000000..cf4eebe056
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.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.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<Integer> 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/public/java/system/src/org/chromium/mojo/system/impl/HandleBase.java b/mojo/public/java/system/src/org/chromium/mojo/system/impl/HandleBase.java
new file mode 100644
index 0000000000..9b7f5de960
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/impl/HandleBase.java
@@ -0,0 +1,137 @@
+// 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.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/public/java/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java b/mojo/public/java/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java
new file mode 100644
index 0000000000..a2dd75d012
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.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.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<? extends Handle> handles, WriteFlags flags) {
+ mCore.writeMessage(this, bytes, handles, flags);
+ }
+
+ /**
+ * @see MessagePipeHandle#readMessage(ReadFlags)
+ */
+ @Override
+ public ResultAnd<ReadMessageResult> readMessage(ReadFlags flags) {
+ return mCore.readMessage(this, flags);
+ }
+}
diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java b/mojo/public/java/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java
new file mode 100644
index 0000000000..cd28a48174
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java
@@ -0,0 +1,60 @@
+// 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/public/java/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java b/mojo/public/java/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java
new file mode 100644
index 0000000000..e365abf31d
--- /dev/null
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java
@@ -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.
+
+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/public/java/system/src/org/chromium/mojo/system/impl/WatcherImpl.java
index 094ad90265..094ad90265 100644
--- a/mojo/android/system/src/org/chromium/mojo/system/impl/WatcherImpl.java
+++ b/mojo/public/java/system/src/org/chromium/mojo/system/impl/WatcherImpl.java
diff --git a/mojo/public/java/system/watcher_impl.cc b/mojo/public/java/system/watcher_impl.cc
new file mode 100644
index 0000000000..36196487eb
--- /dev/null
+++ b/mojo/public/java/system/watcher_impl.cc
@@ -0,0 +1,104 @@
+// 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 <stddef.h>
+#include <stdint.h>
+
+#include "base/android/jni_android.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,
+ base::SequencedTaskRunnerHandle::Get()) {}
+
+ ~WatcherImpl() = default;
+
+ jint Start(JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jint mojo_handle,
+ jint signals) {
+ java_watcher_.Reset(env, jcaller);
+
+ auto ready_callback = base::BindRepeating(&WatcherImpl::OnHandleReady,
+ base::Unretained(this));
+
+ MojoResult result =
+ watcher_.Watch(mojo::Handle(static_cast<MojoHandle>(mojo_handle)),
+ static_cast<MojoHandleSignals>(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<jobject> 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<jobject> java_watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(WatcherImpl);
+};
+
+} // namespace
+
+static jlong JNI_WatcherImpl_CreateWatcher(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller) {
+ return reinterpret_cast<jlong>(new WatcherImpl);
+}
+
+static jint JNI_WatcherImpl_Start(JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jlong watcher_ptr,
+ jint mojo_handle,
+ jint signals) {
+ auto* watcher = reinterpret_cast<WatcherImpl*>(watcher_ptr);
+ return watcher->Start(env, jcaller, mojo_handle, signals);
+}
+
+static void JNI_WatcherImpl_Cancel(JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jlong watcher_ptr) {
+ reinterpret_cast<WatcherImpl*>(watcher_ptr)->Cancel();
+}
+
+static void JNI_WatcherImpl_Delete(JNIEnv* env,
+ const JavaParamRef<jobject>& jcaller,
+ jlong watcher_ptr) {
+ delete reinterpret_cast<WatcherImpl*>(watcher_ptr);
+}
+
+} // namespace android
+} // namespace mojo
diff --git a/mojo/public/js/BUILD.gn b/mojo/public/js/BUILD.gn
index 5ed57a1328..d6b97bda6b 100644
--- a/mojo/public/js/BUILD.gn
+++ b/mojo/public/js/BUILD.gn
@@ -2,61 +2,34 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import("//tools/grit/grit_rule.gni")
+
interfaces_bindings_gen_dir = "$root_gen_dir/mojo/public/interfaces/bindings"
-source_set("js") {
- sources = [
- "constants.cc",
- "constants.h",
- ]
-}
+action("bindings") {
+ bindings_js_files = [
+ # This must be the first file in the list, because it initializes global
+ # variable |mojo| that the others need to refer to.
+ "base.js",
-group("bindings") {
- data = [
- "$interfaces_bindings_gen_dir/interface_control_messages.mojom.js",
- "$interfaces_bindings_gen_dir/pipe_control_messages.mojom.js",
"bindings.js",
- "buffer.js",
- "codec.js",
- "connector.js",
- "core.js",
"interface_types.js",
+ "lib/buffer.js",
+ "lib/codec.js",
+ "lib/connector.js",
"lib/control_message_handler.js",
"lib/control_message_proxy.js",
"lib/interface_endpoint_client.js",
"lib/interface_endpoint_handle.js",
"lib/pipe_control_message_handler.js",
"lib/pipe_control_message_proxy.js",
- "router.js",
- "support.js",
- "threading.js",
- "unicode.js",
- "validator.js",
- ]
-
- deps = [
- ":new_bindings",
- "//mojo/public/interfaces/bindings:bindings__generator",
- ]
-}
-
-action("new_bindings") {
- new_bindings_js_files = [
- # This must be the first file in the list, because it initializes global
- # variable |mojoBindings| that the others need to refer to.
- "new_bindings/base.js",
+ "lib/router.js",
+ "lib/unicode.js",
+ "lib/validator.js",
- "$interfaces_bindings_gen_dir/new_bindings/interface_control_messages.mojom.js",
- "new_bindings/bindings.js",
- "new_bindings/buffer.js",
- "new_bindings/codec.js",
- "new_bindings/connector.js",
- "new_bindings/interface_types.js",
- "new_bindings/lib/control_message_handler.js",
- "new_bindings/lib/control_message_proxy.js",
- "new_bindings/router.js",
- "new_bindings/unicode.js",
- "new_bindings/validator.js",
+ # These two needs to refer to codec.js.
+ "$interfaces_bindings_gen_dir/interface_control_messages.mojom.js",
+ "$interfaces_bindings_gen_dir/pipe_control_messages.mojom.js",
]
compiled_file = "$target_gen_dir/mojo_bindings.js"
@@ -64,30 +37,36 @@ action("new_bindings") {
# bindings instead of simply concatenating the files.
script = "//v8/tools/concatenate-files.py"
- sources = new_bindings_js_files
+ sources = bindings_js_files
outputs = [
compiled_file,
]
- args = rebase_path(new_bindings_js_files)
- args += [ rebase_path(compiled_file) ]
+ args = rebase_path(bindings_js_files, root_build_dir)
+ args += [ rebase_path(compiled_file, root_build_dir) ]
deps = [
- "//mojo/public/interfaces/bindings:new_bindings__generator",
+ "//mojo/public/interfaces/bindings:bindings_js__generator",
]
}
-group("tests") {
- testonly = true
+grit("resources") {
+ source = "mojo_bindings_resources.grd"
- data = [
- "//mojo/public/interfaces/bindings/tests/data/validation/",
- "tests/core_unittest.js",
- "tests/validation_test_input_parser.js",
- "tests/validation_unittest.js",
- ]
+ # The .grd contains references to generated files.
+ source_is_generated = true
- public_deps = [
+ outputs = [
+ "grit/mojo_bindings_resources.h",
+ "grit/mojo_bindings_resources_map.cc",
+ "grit/mojo_bindings_resources_map.h",
+ "mojo_bindings_resources.pak",
+ ]
+ grit_flags = [
+ "-E",
+ "root_gen_dir=" + rebase_path(root_gen_dir, root_build_dir),
+ ]
+ deps = [
":bindings",
]
}
diff --git a/mojo/public/js/README.md b/mojo/public/js/README.md
index b6eafe9c82..1e426eaed1 100644
--- a/mojo/public/js/README.md
+++ b/mojo/public/js/README.md
@@ -1,7 +1,282 @@
-# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo JavaScript System and Bindings APIs
-This document is a subset of the [Mojo documentation](/mojo).
+# Mojo JavaScript Bindings API
+This document is a subset of the [Mojo documentation](/mojo/README.md).
-**NOTE:** The JavaScript APIs are currently in flux and will stabilize soon.
-More info forthcoming ASAP!
+[TOC]
-TODO: Make the contents of this document less non-existent.
+## Getting Started
+The bindings API is defined in the `mojo` namespace and implemented in
+`mojo_bindings.js`, which could be generated by the GN target
+`//mojo/public/js:bindings`.
+
+When a Mojom IDL file is processed by the bindings generator, JavaScript code is
+emitted in a `.js` file with the name based on the input `.mojom` file. Suppose
+we create the following Mojom file at
+`//services/echo/public/interfaces/echo.mojom`:
+
+```
+module test.echo.mojom;
+
+interface Echo {
+ EchoInteger(int32 value) => (int32 result);
+};
+```
+
+And a GN target to generate the bindings in
+`//services/echo/public/interfaces/BUILD.gn`:
+
+```
+import("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("interfaces") {
+ sources = [
+ "echo.mojom",
+ ]
+}
+```
+
+Bindings are generated by building one of these implicitly generated targets
+(where "foo" is the target name):
+* `foo_js` JavaScript bindings; used as compile-time dependency.
+* `foo_js_data_deps` JavaScript bindings; used as run-time dependency.
+
+If we then build this target:
+```
+ninja -C out/r services/echo/public/interfaces:interfaces_js
+```
+
+This will produce several generated source files. The one relevant to JavaScript
+bindings is:
+```
+out/gen/services/echo/public/interfaces/echo.mojom.js
+```
+
+In order to use the definitions in `echo.mojom`, you will need to include two
+files in your html page using `<script>` tags:
+* `mojo_bindings.js`
+ __Note: This file must be included before any `.mojom.js` files.__
+* `echo.mojom.js`
+
+``` html
+<!DOCTYPE html>
+<script src="URL/to/mojo_bindings.js"></script>
+<script src="URL/to/echo.mojom.js"></script>
+<script>
+
+var echoPtr = new test.echo.mojom.EchoPtr();
+var echoRequest = mojo.makeRequest(echoPtr);
+// ...
+
+</script>
+```
+
+## Interfaces
+Similar to the C++ bindings API, we have:
+* `mojo.InterfacePtrInfo` and `mojo.InterfaceRequest` encapsulate two ends of a
+ message pipe. They represent the client end and service end of an interface
+ connection, respectively.
+* For each Mojom interface `Foo`, there is a generated `FooPtr` class. It owns
+ an `InterfacePtrInfo`; provides methods to send interface calls using the
+ message pipe handle from the `InterfacePtrInfo`.
+* `mojo.Binding` owns an `InterfaceRequest`. It listens on the message pipe
+ handle and dispatches incoming messages to a user-defined interface
+ implementation.
+
+Let's consider the `echo.mojom` example above. The following shows how to create
+an `Echo` interface connection and use it to make a call.
+
+``` html
+<!DOCTYPE html>
+<script src="URL/to/mojo_bindings.js"></script>
+<script src="URL/to/echo.mojom.js"></script>
+<script>
+
+function EchoImpl() {}
+EchoImpl.prototype.echoInteger = function(value) {
+ return Promise.resolve({result: value});
+};
+
+var echoServicePtr = new test.echo.mojom.EchoPtr();
+var echoServiceRequest = mojo.makeRequest(echoServicePtr);
+var echoServiceBinding = new mojo.Binding(test.echo.mojom.Echo,
+ new EchoImpl(),
+ echoServiceRequest);
+echoServicePtr.echoInteger({value: 123}).then(function(response) {
+ console.log('The result is ' + response.value);
+});
+
+</script>
+```
+
+### Interface Pointers and Requests
+In the example above, `test.echo.mojom.EchoPtr` is an interface pointer class.
+`EchoPtr` represents the client end of an interface connection. For method
+`EchoInteger` in the `Echo` Mojom interface, there is a corresponding
+`echoInteger` method defined in `EchoPtr`. (Please note that the format of the
+generated method name is `camelCaseWithLowerInitial`.)
+
+There are some control methods shared by all interface pointer classes. For
+example, binding/extracting `InterfacePtrInfo`, setting connection error
+handler, querying version information, etc. In order to avoid name collision,
+they are defined in `mojo.InterfacePtrController` and exposed as the `ptr` field
+of every interface pointer class.
+
+In the example above, `echoServiceRequest` is an `InterfaceRequest` instance. It
+represents the service end of an interface connection.
+
+`mojo.makeRequest` creates a message pipe; populates the output argument (which
+could be an `InterfacePtrInfo` or an interface pointer) with one end of the
+pipe; returns the other end wrapped in an `InterfaceRequest` instance.
+
+### Binding an InterfaceRequest
+A `mojo.Binding` bridges an implementation of an interface and a message pipe
+endpoint, dispatching incoming messages to the implementation.
+
+In the example above, `echoServiceBinding` listens for incoming `EchoInteger`
+method calls on the messsage pipe, and dispatches those calls to the `EchoImpl`
+instance.
+
+### Receiving Responses
+Some Mojom interface methods expect a response, such as `EchoInteger`. The
+corresponding JavaScript method returns a Promise. This Promise is resolved when
+the service side sends back a response. It is rejected if the interface is
+disconnected.
+
+### Connection Errors
+If a pipe is disconnected, both endpoints will be able to observe the connection
+error (unless the disconnection is caused by closing/destroying an endpoint, in
+which case that endpoint won't get such a notification). If there are remaining
+incoming messages for an endpoint on disconnection, the connection error won't
+be triggered until the messages are drained.
+
+Pipe disconnecition may be caused by:
+* Mojo system-level causes: process terminated, resource exhausted, etc.
+* The bindings close the pipe due to a validation error when processing a
+ received message.
+* The peer endpoint is closed. For example, the remote side is a bound interface
+ pointer and it is destroyed.
+
+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 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 recovery.
+
+``` js
+// Assume echoServicePtr is already bound.
+echoServicePtr.ptr.setConnectionErrorHandler(function() {
+ DoImportantCleanUp();
+});
+
+// Assume echoServiceBinding is already bound:
+echoServiceBinding.setConnectionErrorHandler(function() {
+ DoImportantCleanUpToo();
+});
+```
+
+**Note:** Closing one end of a pipe will eventually trigger a connection error
+on the other end. However it's ordered with respect to any other event (*e.g.*
+writing a message) on the pipe. Therefore, it is safe to make an `echoInteger`
+call on `echoServicePtr` and reset it immediately (which results in
+disconnection), `echoServiceBinding` will receive the `echoInteger` call before
+it observes the connection error.
+
+## Associated Interfaces
+An associated interface connection doesn't have its own underlying message pipe.
+It is associated with an existing message pipe (i.e., interface connection).
+
+Similar to the non-associated interface case, we have:
+* `mojo.AssociatedInterfacePtrInfo` and `mojo.AssociatedInterfaceRequest`
+ encapsulate a *route ID*, representing a logical connection over a message
+ pipe.
+* For each Mojom interface `Foo`, there is a generated `FooAssociatedPtr` class.
+ It owns an `AssociatedInterfacePtrInfo`. It is the client side of an
+ interface.
+* `mojo.AssociatedBinding` owns an `AssociatedInterfaceRequest`. It listens on
+ the connection and dispatches incoming messages to a user-defined interface
+ implementation.
+
+See [this document](https://www.chromium.org/developers/design-documents/mojo/associated-interfaces)
+for more details.
+
+## Automatic and Manual Dependency Loading
+By default, generated `.mojom.js` files automatically load Mojom dependencies.
+For example, if `foo.mojom` imports `bar.mojom`, loading `foo.mojom.js` will
+insert a `<script>` tag to load `bar.mojom.js`, if it hasn't been loaded.
+
+The URL of `bar.mojom.js` is determined by:
+* the path of `bar.mojom` relative to the position of `foo.mojom` at build time;
+* the URL of `foo.mojom.js`.
+
+For exmple, if at build time the two Mojom files are located at:
+```
+a/b/c/foo.mojom
+a/b/d/bar.mojom
+```
+
+The URL of `foo.mojom.js` is:
+```
+http://example.org/scripts/b/c/foo.mojom.js
+```
+
+Then the URL of `bar.mojom.js` is supposed to be:
+```
+http://example.org/scripts/b/d/bar.mojom.js
+```
+
+If you would like `bar.mojom.js` to live at a different location, you need to
+set `mojo.config.autoLoadMojomDeps` to `false` before loading `foo.mojom.js`,
+and manually load `bar.mojom.js` yourself. Similarly, you need to turn off this
+option if you merge `bar.mojom.js` and `foo.mojom.js` into a single file.
+
+``` html
+<!-- Automatic dependency loading -->
+<script src="http://example.org/scripts/mojo_bindings.js"></script>
+<script src="http://example.org/scripts/b/c/foo.mojom.js"></script>
+
+
+<!-- Manual dependency loading -->
+<script src="http://example.org/scripts/mojo_bindings.js"></script>
+<script>
+ mojo.config.autoLoadMojomDeps = false;
+</script>
+<script src="http://example.org/scripts/b/d/bar.mojom.js"></script>
+<script src="http://example.org/scripts/b/c/foo.mojom.js"></script>
+```
+
+### Performance Tip: Avoid Loading the Same .mojom.js File Multiple Times
+If `mojo.config.autoLoadMojomDeps` is set to `true` (which is the default
+value), you might accidentally load the same `.mojom.js` file multiple times if
+you are not careful. Although it doesn't cause fatal errors, it hurts
+performance and therefore should be avoided.
+
+``` html
+<!-- Assume that mojo.config.autoLoadMojomDeps is set to true: -->
+
+<!-- No duplicate loading; recommended. -->
+<script src="http://example.org/scripts/b/c/foo.mojom.js"></script>
+
+<!-- No duplicate loading, although unnecessary. -->
+<script src="http://example.org/scripts/b/d/bar.mojom.js"></script>
+<script src="http://example.org/scripts/b/c/foo.mojom.js"></script>
+
+<!-- Load bar.mojom.js twice; should be avoided. -->
+<!-- when foo.mojom.js is loaded, it sees that bar.mojom.js is not yet loaded,
+ so it inserts another <script> tag for bar.mojom.js. -->
+<script src="http://example.org/scripts/b/c/foo.mojom.js"></script>
+<script src="http://example.org/scripts/b/d/bar.mojom.js"></script>
+```
+
+If a `.mojom.js` file is loaded for a second time, a warnings will be showed
+using `console.warn()` to bring it to developers' attention.
+
+## Name Formatting
+As a general rule, Mojom definitions follow the C++ formatting style. To make
+the generated JavaScript bindings conforms to our JavaScript style guide, the
+code generator does the following conversions:
+
+| In Mojom | In generated .mojom.js |
+|----------------------|------------------------|
+| MethodLikeThis | methodLikeThis
+| parameter_like_this | parameterLikeThis
+| field_like_this | fieldLikeThis
+| name_space.like_this | nameSpace.likeThis
diff --git a/mojo/public/js/base.js b/mojo/public/js/base.js
new file mode 100644
index 0000000000..45740341a1
--- /dev/null
+++ b/mojo/public/js/base.js
@@ -0,0 +1,134 @@
+// 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.
+
+'use strict';
+
+if ((typeof mojo !== 'undefined') && mojo.bindingsLibraryInitialized) {
+ throw new Error('The Mojo bindings library has been initialized.');
+}
+
+var mojo = mojo || {};
+mojo.bindingsLibraryInitialized = true;
+
+mojo.internal = mojo.internal || {};
+
+mojo.config = mojo.config || {};
+if (typeof mojo.config.global === 'undefined') {
+ mojo.config.global = this;
+}
+
+if (typeof mojo.config.autoLoadMojomDeps === 'undefined') {
+ // Whether to automatically load mojom dependencies.
+ // For example, if foo.mojom imports bar.mojom, |autoLoadMojomDeps| set to
+ // true means that loading foo.mojom.js will insert a <script> tag to load
+ // bar.mojom.js, if it hasn't been loaded.
+ //
+ // The URL of bar.mojom.js is determined by the relative path of bar.mojom
+ // (relative to the position of foo.mojom at build time) and the URL of
+ // foo.mojom.js. For exmple, if at build time the two mojom files are
+ // located at:
+ // a/b/c/foo.mojom
+ // a/b/d/bar.mojom
+ // and the URL of foo.mojom.js is:
+ // http://example.org/scripts/b/c/foo.mojom.js
+ // then the URL of bar.mojom.js will be:
+ // http://example.org/scripts/b/d/bar.mojom.js
+ //
+ // If you would like bar.mojom.js to live at a different location, you need
+ // to turn off |autoLoadMojomDeps| before loading foo.mojom.js, and manually
+ // load bar.mojom.js yourself. Similarly, you need to turn off the option if
+ // you merge bar.mojom.js and foo.mojom.js into a single file.
+ //
+ // Performance tip: Avoid loading the same mojom.js file multiple times.
+ // Assume that |autoLoadMojomDeps| is set to true,
+ //
+ // <!--
+ // (This comment tag is necessary on IOS to avoid interpreting the closing
+ // script tags in the example.)
+ //
+ // No duplicate loading; recommended:
+ // <script src="http://example.org/scripts/b/c/foo.mojom.js"></script>
+ //
+ // No duplicate loading, although unnecessary:
+ // <script src="http://example.org/scripts/b/d/bar.mojom.js"></script>
+ // <script src="http://example.org/scripts/b/c/foo.mojom.js"></script>
+ //
+ // Load bar.mojom.js twice; should be avoided:
+ // <script src="http://example.org/scripts/b/c/foo.mojom.js"></script>
+ // <script src="http://example.org/scripts/b/d/bar.mojom.js"></script>
+ //
+ // -->
+ mojo.config.autoLoadMojomDeps = true;
+}
+
+(function() {
+ var internal = mojo.internal;
+ var config = mojo.config;
+
+ var LoadState = {
+ PENDING_LOAD: 1,
+ LOADED: 2
+ };
+
+ var mojomRegistry = new Map();
+
+ function exposeNamespace(namespace) {
+ var current = config.global;
+ var parts = namespace.split('.');
+
+ for (var part; parts.length && (part = parts.shift());) {
+ if (!current[part]) {
+ current[part] = {};
+ }
+ current = current[part];
+ }
+
+ return current;
+ }
+
+ function isMojomPendingLoad(id) {
+ return mojomRegistry.get(id) === LoadState.PENDING_LOAD;
+ }
+
+ function isMojomLoaded(id) {
+ return mojomRegistry.get(id) === LoadState.LOADED;
+ }
+
+ function markMojomPendingLoad(id) {
+ if (isMojomLoaded(id)) {
+ throw new Error('The following mojom file has been loaded: ' + id);
+ }
+
+ mojomRegistry.set(id, LoadState.PENDING_LOAD);
+ }
+
+ function markMojomLoaded(id) {
+ mojomRegistry.set(id, LoadState.LOADED);
+ }
+
+ function loadMojomIfNecessary(id, relativePath) {
+ if (mojomRegistry.has(id)) {
+ return;
+ }
+
+ if (config.global.document === undefined) {
+ throw new Error(
+ 'Mojom dependency autoloading is not implemented in workers. ' +
+ 'Please see config variable mojo.config.autoLoadMojomDeps for more ' +
+ 'details.');
+ }
+
+ markMojomPendingLoad(id);
+ var url = new URL(relativePath, document.currentScript.src).href;
+ config.global.document.write('<script type="text/javascript" src="' +
+ url + '"><' + '/script>');
+ }
+
+ internal.exposeNamespace = exposeNamespace;
+ internal.isMojomPendingLoad = isMojomPendingLoad;
+ internal.isMojomLoaded = isMojomLoaded;
+ internal.markMojomPendingLoad = markMojomPendingLoad;
+ internal.markMojomLoaded = markMojomLoaded;
+ internal.loadMojomIfNecessary = loadMojomIfNecessary;
+})();
diff --git a/mojo/public/js/bindings.js b/mojo/public/js/bindings.js
index a944e2f7aa..a82f43ddda 100644
--- a/mojo/public/js/bindings.js
+++ b/mojo/public/js/bindings.js
@@ -1,22 +1,34 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
+// 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.
-define("mojo/public/js/bindings", [
- "mojo/public/js/core",
- "mojo/public/js/interface_types",
- "mojo/public/js/lib/interface_endpoint_client",
- "mojo/public/js/router",
-], function(core, types, interfaceEndpointClient, router) {
-
- var InterfaceEndpointClient = interfaceEndpointClient.InterfaceEndpointClient;
+(function() {
+ var internal = mojo.internal;
// ---------------------------------------------------------------------------
- function makeRequest(interfacePtr) {
- var pipe = core.createMessagePipe();
- interfacePtr.ptr.bind(new types.InterfacePtrInfo(pipe.handle0, 0));
- return new types.InterfaceRequest(pipe.handle1);
+ // |output| could be an interface pointer, InterfacePtrInfo or
+ // AssociatedInterfacePtrInfo.
+ function makeRequest(output) {
+ if (output instanceof mojo.AssociatedInterfacePtrInfo) {
+ var {handle0, handle1} = internal.createPairPendingAssociation();
+ output.interfaceEndpointHandle = handle0;
+ output.version = 0;
+
+ return new mojo.AssociatedInterfaceRequest(handle1);
+ }
+
+ if (output instanceof mojo.InterfacePtrInfo) {
+ var pipe = Mojo.createMessagePipe();
+ output.handle = pipe.handle0;
+ output.version = 0;
+
+ return new mojo.InterfaceRequest(pipe.handle1);
+ }
+
+ var pipe = Mojo.createMessagePipe();
+ output.ptr.bind(new mojo.InterfacePtrInfo(pipe.handle0, 0));
+ return new mojo.InterfaceRequest(pipe.handle1);
}
// ---------------------------------------------------------------------------
@@ -44,7 +56,7 @@ define("mojo/public/js/bindings", [
InterfacePtrController.prototype.bind = function(ptrInfoOrHandle) {
this.reset();
- if (ptrInfoOrHandle instanceof types.InterfacePtrInfo) {
+ if (ptrInfoOrHandle instanceof mojo.InterfacePtrInfo) {
this.version = ptrInfoOrHandle.version;
this.handle_ = ptrInfoOrHandle.handle;
} else {
@@ -53,7 +65,7 @@ define("mojo/public/js/bindings", [
};
InterfacePtrController.prototype.isBound = function() {
- return this.router_ !== null || this.handle_ !== null;
+ return this.interfaceEndpointClient_ !== null || this.handle_ !== null;
};
// Although users could just discard the object, reset() closes the pipe
@@ -71,15 +83,17 @@ define("mojo/public/js/bindings", [
this.proxy_ = null;
}
if (this.handle_) {
- core.close(this.handle_);
+ this.handle_.close();
this.handle_ = null;
}
};
InterfacePtrController.prototype.resetWithReason = function(reason) {
- this.configureProxyIfNecessary_();
- this.interfaceEndpointClient_.close(reason);
- this.interfaceEndpointClient_ = null;
+ if (this.isBound()) {
+ this.configureProxyIfNecessary_();
+ this.interfaceEndpointClient_.close(reason);
+ this.interfaceEndpointClient_ = null;
+ }
this.reset();
};
@@ -96,12 +110,12 @@ define("mojo/public/js/bindings", [
var result;
if (this.router_) {
// TODO(yzshen): Fix Router interface to support extracting handle.
- result = new types.InterfacePtrInfo(
+ result = new mojo.InterfacePtrInfo(
this.router_.connector_.handle_, this.version);
this.router_.connector_.handle_ = null;
} else {
// This also handles the case when this object is not bound.
- result = new types.InterfacePtrInfo(this.handle_, this.version);
+ result = new mojo.InterfacePtrInfo(this.handle_, this.version);
this.handle_ = null;
}
@@ -114,21 +128,15 @@ define("mojo/public/js/bindings", [
return this.proxy_;
};
- InterfacePtrController.prototype.waitForNextMessageForTesting = function() {
- this.configureProxyIfNecessary_();
- this.router_.waitForNextMessageForTesting();
- };
-
InterfacePtrController.prototype.configureProxyIfNecessary_ = function() {
if (!this.handle_)
return;
- this.router_ = new router.Router(this.handle_);
+ this.router_ = new internal.Router(this.handle_, true);
this.handle_ = null;
- this.interfaceEndpointClient_ = new InterfaceEndpointClient(
- this.router_.createLocalEndpointHandle(types.kMasterInterfaceId),
- this.router_);
+ this.interfaceEndpointClient_ = new internal.InterfaceEndpointClient(
+ this.router_.createLocalEndpointHandle(internal.kMasterInterfaceId));
this.interfaceEndpointClient_ .setPayloadValidators([
this.interfaceType_.validateResponse]);
@@ -197,18 +205,18 @@ define("mojo/public/js/bindings", [
Binding.prototype.bind = function(requestOrHandle) {
this.close();
- var handle = requestOrHandle instanceof types.InterfaceRequest ?
+ var handle = requestOrHandle instanceof mojo.InterfaceRequest ?
requestOrHandle.handle : requestOrHandle;
- if (!core.isHandle(handle))
+ if (!(handle instanceof MojoHandle))
return;
- this.router_ = new router.Router(handle);
+ this.router_ = new internal.Router(handle);
this.stub_ = new this.interfaceType_.stubClass(this.impl_);
- this.interfaceEndpointClient_ = new InterfaceEndpointClient(
- this.router_.createLocalEndpointHandle(types.kMasterInterfaceId),
- this.router_, this.interfaceType_.kVersion);
- this.interfaceEndpointClient_.setIncomingReceiver(this.stub_);
+ this.interfaceEndpointClient_ = new internal.InterfaceEndpointClient(
+ this.router_.createLocalEndpointHandle(internal.kMasterInterfaceId),
+ this.stub_, this.interfaceType_.kVersion);
+
this.interfaceEndpointClient_ .setPayloadValidators([
this.interfaceType_.validateRequest]);
};
@@ -235,8 +243,7 @@ define("mojo/public/js/bindings", [
this.close();
};
- Binding.prototype.setConnectionErrorHandler
- = function(callback) {
+ Binding.prototype.setConnectionErrorHandler = function(callback) {
if (!this.isBound()) {
throw new Error("Cannot set connection error handler if not bound.");
}
@@ -245,28 +252,25 @@ define("mojo/public/js/bindings", [
Binding.prototype.unbind = function() {
if (!this.isBound())
- return new types.InterfaceRequest(null);
+ return new mojo.InterfaceRequest(null);
- var result = new types.InterfaceRequest(this.router_.connector_.handle_);
+ var result = new mojo.InterfaceRequest(this.router_.connector_.handle_);
this.router_.connector_.handle_ = null;
this.close();
return result;
};
- Binding.prototype.waitForNextMessageForTesting = function() {
- this.router_.waitForNextMessageForTesting();
- };
-
// ---------------------------------------------------------------------------
- function BindingSetEntry(bindingSet, interfaceType, impl, requestOrHandle,
- bindingId) {
+ function BindingSetEntry(bindingSet, interfaceType, bindingType, impl,
+ requestOrHandle, bindingId) {
this.bindingSet_ = bindingSet;
this.bindingId_ = bindingId;
- this.binding_ = new Binding(interfaceType, impl, requestOrHandle);
+ this.binding_ = new bindingType(interfaceType, impl,
+ requestOrHandle);
- this.binding_.setConnectionErrorHandler(function() {
- this.bindingSet_.onConnectionError(bindingId);
+ this.binding_.setConnectionErrorHandler(function(reason) {
+ this.bindingSet_.onConnectionError(bindingId, reason);
}.bind(this));
}
@@ -279,6 +283,7 @@ define("mojo/public/js/bindings", [
this.nextBindingId_ = 0;
this.bindings_ = new Map();
this.errorHandler_ = null;
+ this.bindingType_ = Binding;
}
BindingSet.prototype.isEmpty = function() {
@@ -288,8 +293,8 @@ define("mojo/public/js/bindings", [
BindingSet.prototype.addBinding = function(impl, requestOrHandle) {
this.bindings_.set(
this.nextBindingId_,
- new BindingSetEntry(this, this.interfaceType_, impl, requestOrHandle,
- this.nextBindingId_));
+ new BindingSetEntry(this, this.interfaceType_, this.bindingType_, impl,
+ requestOrHandle, this.nextBindingId_));
++this.nextBindingId_;
};
@@ -303,20 +308,241 @@ define("mojo/public/js/bindings", [
this.errorHandler_ = callback;
};
- BindingSet.prototype.onConnectionError = function(bindingId) {
+ BindingSet.prototype.onConnectionError = function(bindingId, reason) {
this.bindings_.delete(bindingId);
if (this.errorHandler_)
- this.errorHandler_();
+ this.errorHandler_(reason);
+ };
+
+ // ---------------------------------------------------------------------------
+
+ // Operations used to setup/configure an associated interface pointer.
+ // Exposed as |ptr| field of generated associated interface pointer classes.
+ // |associatedPtrInfo| could be omitted and passed into bind() later.
+ //
+ // Example:
+ // // IntegerSenderImpl implements mojom.IntegerSender
+ // function IntegerSenderImpl() { ... }
+ // IntegerSenderImpl.prototype.echo = function() { ... }
+ //
+ // // IntegerSenderConnectionImpl implements mojom.IntegerSenderConnection
+ // function IntegerSenderConnectionImpl() {
+ // this.senderBinding_ = null;
+ // }
+ // IntegerSenderConnectionImpl.prototype.getSender = function(
+ // associatedRequest) {
+ // this.senderBinding_ = new AssociatedBinding(mojom.IntegerSender,
+ // new IntegerSenderImpl(),
+ // associatedRequest);
+ // }
+ //
+ // var integerSenderConnection = new mojom.IntegerSenderConnectionPtr();
+ // var integerSenderConnectionBinding = new Binding(
+ // mojom.IntegerSenderConnection,
+ // new IntegerSenderConnectionImpl(),
+ // mojo.makeRequest(integerSenderConnection));
+ //
+ // // A locally-created associated interface pointer can only be used to
+ // // make calls when the corresponding associated request is sent over
+ // // another interface (either the master interface or another
+ // // associated interface).
+ // var associatedInterfacePtrInfo = new AssociatedInterfacePtrInfo();
+ // var associatedRequest = makeRequest(interfacePtrInfo);
+ //
+ // integerSenderConnection.getSender(associatedRequest);
+ //
+ // // Create an associated interface and bind the associated handle.
+ // var integerSender = new mojom.AssociatedIntegerSenderPtr();
+ // integerSender.ptr.bind(associatedInterfacePtrInfo);
+ // integerSender.echo();
+
+ function AssociatedInterfacePtrController(interfaceType, associatedPtrInfo) {
+ this.version = 0;
+
+ this.interfaceType_ = interfaceType;
+ this.interfaceEndpointClient_ = null;
+ this.proxy_ = null;
+
+ if (associatedPtrInfo) {
+ this.bind(associatedPtrInfo);
+ }
+ }
+
+ AssociatedInterfacePtrController.prototype.bind = function(
+ associatedPtrInfo) {
+ this.reset();
+ this.version = associatedPtrInfo.version;
+
+ this.interfaceEndpointClient_ = new internal.InterfaceEndpointClient(
+ associatedPtrInfo.interfaceEndpointHandle);
+
+ this.interfaceEndpointClient_ .setPayloadValidators([
+ this.interfaceType_.validateResponse]);
+ this.proxy_ = new this.interfaceType_.proxyClass(
+ this.interfaceEndpointClient_);
+ };
+
+ AssociatedInterfacePtrController.prototype.isBound = function() {
+ return this.interfaceEndpointClient_ !== null;
+ };
+
+ AssociatedInterfacePtrController.prototype.reset = function() {
+ this.version = 0;
+ if (this.interfaceEndpointClient_) {
+ this.interfaceEndpointClient_.close();
+ this.interfaceEndpointClient_ = null;
+ }
+ if (this.proxy_) {
+ this.proxy_ = null;
+ }
+ };
+
+ AssociatedInterfacePtrController.prototype.resetWithReason = function(
+ reason) {
+ if (this.isBound()) {
+ this.interfaceEndpointClient_.close(reason);
+ this.interfaceEndpointClient_ = null;
+ }
+ this.reset();
+ };
+
+ // Indicates whether an error has been encountered. If true, method calls
+ // on this interface will be dropped (and may already have been dropped).
+ AssociatedInterfacePtrController.prototype.getEncounteredError = function() {
+ return this.interfaceEndpointClient_ ?
+ this.interfaceEndpointClient_.getEncounteredError() : false;
+ };
+
+ AssociatedInterfacePtrController.prototype.setConnectionErrorHandler =
+ function(callback) {
+ if (!this.isBound()) {
+ throw new Error("Cannot set connection error handler if not bound.");
+ }
+
+ this.interfaceEndpointClient_.setConnectionErrorHandler(callback);
+ };
+
+ AssociatedInterfacePtrController.prototype.passInterface = function() {
+ if (!this.isBound()) {
+ return new mojo.AssociatedInterfacePtrInfo(null);
+ }
+
+ var result = new mojo.AssociatedInterfacePtrInfo(
+ this.interfaceEndpointClient_.passHandle(), this.version);
+ this.reset();
+ return result;
+ };
+
+ AssociatedInterfacePtrController.prototype.getProxy = function() {
+ return this.proxy_;
+ };
+
+ AssociatedInterfacePtrController.prototype.queryVersion = function() {
+ function onQueryVersion(version) {
+ this.version = version;
+ return version;
+ }
+
+ return this.interfaceEndpointClient_.queryVersion().then(
+ onQueryVersion.bind(this));
};
- var exports = {};
- exports.InterfacePtrInfo = types.InterfacePtrInfo;
- exports.InterfaceRequest = types.InterfaceRequest;
- exports.makeRequest = makeRequest;
- exports.InterfacePtrController = InterfacePtrController;
- exports.Binding = Binding;
- exports.BindingSet = BindingSet;
+ AssociatedInterfacePtrController.prototype.requireVersion = function(
+ version) {
+ if (this.version >= version) {
+ return;
+ }
+ this.version = version;
+ this.interfaceEndpointClient_.requireVersion(version);
+ };
+
+ // ---------------------------------------------------------------------------
+
+ // |associatedInterfaceRequest| could be omitted and passed into bind()
+ // later.
+ function AssociatedBinding(interfaceType, impl, associatedInterfaceRequest) {
+ this.interfaceType_ = interfaceType;
+ this.impl_ = impl;
+ this.interfaceEndpointClient_ = null;
+ this.stub_ = null;
+
+ if (associatedInterfaceRequest) {
+ this.bind(associatedInterfaceRequest);
+ }
+ }
+
+ AssociatedBinding.prototype.isBound = function() {
+ return this.interfaceEndpointClient_ !== null;
+ };
+
+ AssociatedBinding.prototype.bind = function(associatedInterfaceRequest) {
+ this.close();
+
+ this.stub_ = new this.interfaceType_.stubClass(this.impl_);
+ this.interfaceEndpointClient_ = new internal.InterfaceEndpointClient(
+ associatedInterfaceRequest.interfaceEndpointHandle, this.stub_,
+ this.interfaceType_.kVersion);
+
+ this.interfaceEndpointClient_ .setPayloadValidators([
+ this.interfaceType_.validateRequest]);
+ };
+
+
+ AssociatedBinding.prototype.close = function() {
+ if (!this.isBound()) {
+ return;
+ }
+
+ if (this.interfaceEndpointClient_) {
+ this.interfaceEndpointClient_.close();
+ this.interfaceEndpointClient_ = null;
+ }
+
+ this.stub_ = null;
+ };
+
+ AssociatedBinding.prototype.closeWithReason = function(reason) {
+ if (this.interfaceEndpointClient_) {
+ this.interfaceEndpointClient_.close(reason);
+ this.interfaceEndpointClient_ = null;
+ }
+ this.close();
+ };
+
+ AssociatedBinding.prototype.setConnectionErrorHandler = function(callback) {
+ if (!this.isBound()) {
+ throw new Error("Cannot set connection error handler if not bound.");
+ }
+ this.interfaceEndpointClient_.setConnectionErrorHandler(callback);
+ };
+
+ AssociatedBinding.prototype.unbind = function() {
+ if (!this.isBound()) {
+ return new mojo.AssociatedInterfaceRequest(null);
+ }
+
+ var result = new mojo.AssociatedInterfaceRequest(
+ this.interfaceEndpointClient_.passHandle());
+ this.close();
+ return result;
+ };
+
+ // ---------------------------------------------------------------------------
+
+ function AssociatedBindingSet(interfaceType) {
+ mojo.BindingSet.call(this, interfaceType);
+ this.bindingType_ = AssociatedBinding;
+ }
- return exports;
-});
+ AssociatedBindingSet.prototype = Object.create(BindingSet.prototype);
+ AssociatedBindingSet.prototype.constructor = AssociatedBindingSet;
+
+ mojo.makeRequest = makeRequest;
+ mojo.AssociatedInterfacePtrController = AssociatedInterfacePtrController;
+ mojo.AssociatedBinding = AssociatedBinding;
+ mojo.AssociatedBindingSet = AssociatedBindingSet;
+ mojo.Binding = Binding;
+ mojo.BindingSet = BindingSet;
+ mojo.InterfacePtrController = InterfacePtrController;
+})();
diff --git a/mojo/public/js/buffer.js b/mojo/public/js/buffer.js
deleted file mode 100644
index e35f69513f..0000000000
--- a/mojo/public/js/buffer.js
+++ /dev/null
@@ -1,156 +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.
-
-define("mojo/public/js/buffer", function() {
-
- var kHostIsLittleEndian = (function () {
- var endianArrayBuffer = new ArrayBuffer(2);
- var endianUint8Array = new Uint8Array(endianArrayBuffer);
- var endianUint16Array = new Uint16Array(endianArrayBuffer);
- endianUint16Array[0] = 1;
- return endianUint8Array[0] == 1;
- })();
-
- var kHighWordMultiplier = 0x100000000;
-
- function Buffer(sizeOrArrayBuffer) {
- if (sizeOrArrayBuffer instanceof ArrayBuffer)
- this.arrayBuffer = sizeOrArrayBuffer;
- else
- this.arrayBuffer = new ArrayBuffer(sizeOrArrayBuffer);
-
- this.dataView = new DataView(this.arrayBuffer);
- this.next = 0;
- }
-
- Object.defineProperty(Buffer.prototype, "byteLength", {
- get: function() { return this.arrayBuffer.byteLength; }
- });
-
- Buffer.prototype.alloc = function(size) {
- var pointer = this.next;
- this.next += size;
- if (this.next > this.byteLength) {
- var newSize = (1.5 * (this.byteLength + size)) | 0;
- this.grow(newSize);
- }
- return pointer;
- };
-
- function copyArrayBuffer(dstArrayBuffer, srcArrayBuffer) {
- (new Uint8Array(dstArrayBuffer)).set(new Uint8Array(srcArrayBuffer));
- }
-
- Buffer.prototype.grow = function(size) {
- var newArrayBuffer = new ArrayBuffer(size);
- copyArrayBuffer(newArrayBuffer, this.arrayBuffer);
- this.arrayBuffer = newArrayBuffer;
- this.dataView = new DataView(this.arrayBuffer);
- };
-
- Buffer.prototype.trim = function() {
- this.arrayBuffer = this.arrayBuffer.slice(0, this.next);
- this.dataView = new DataView(this.arrayBuffer);
- };
-
- Buffer.prototype.getUint8 = function(offset) {
- return this.dataView.getUint8(offset);
- }
- Buffer.prototype.getUint16 = function(offset) {
- return this.dataView.getUint16(offset, kHostIsLittleEndian);
- }
- Buffer.prototype.getUint32 = function(offset) {
- return this.dataView.getUint32(offset, kHostIsLittleEndian);
- }
- Buffer.prototype.getUint64 = function(offset) {
- var lo, hi;
- if (kHostIsLittleEndian) {
- lo = this.dataView.getUint32(offset, kHostIsLittleEndian);
- hi = this.dataView.getUint32(offset + 4, kHostIsLittleEndian);
- } else {
- hi = this.dataView.getUint32(offset, kHostIsLittleEndian);
- lo = this.dataView.getUint32(offset + 4, kHostIsLittleEndian);
- }
- return lo + hi * kHighWordMultiplier;
- }
-
- Buffer.prototype.getInt8 = function(offset) {
- return this.dataView.getInt8(offset);
- }
- Buffer.prototype.getInt16 = function(offset) {
- return this.dataView.getInt16(offset, kHostIsLittleEndian);
- }
- Buffer.prototype.getInt32 = function(offset) {
- return this.dataView.getInt32(offset, kHostIsLittleEndian);
- }
- Buffer.prototype.getInt64 = function(offset) {
- var lo, hi;
- if (kHostIsLittleEndian) {
- lo = this.dataView.getUint32(offset, kHostIsLittleEndian);
- hi = this.dataView.getInt32(offset + 4, kHostIsLittleEndian);
- } else {
- hi = this.dataView.getInt32(offset, kHostIsLittleEndian);
- lo = this.dataView.getUint32(offset + 4, kHostIsLittleEndian);
- }
- return lo + hi * kHighWordMultiplier;
- }
-
- Buffer.prototype.getFloat32 = function(offset) {
- return this.dataView.getFloat32(offset, kHostIsLittleEndian);
- }
- Buffer.prototype.getFloat64 = function(offset) {
- return this.dataView.getFloat64(offset, kHostIsLittleEndian);
- }
-
- Buffer.prototype.setUint8 = function(offset, value) {
- this.dataView.setUint8(offset, value);
- }
- Buffer.prototype.setUint16 = function(offset, value) {
- this.dataView.setUint16(offset, value, kHostIsLittleEndian);
- }
- Buffer.prototype.setUint32 = function(offset, value) {
- this.dataView.setUint32(offset, value, kHostIsLittleEndian);
- }
- Buffer.prototype.setUint64 = function(offset, value) {
- var hi = (value / kHighWordMultiplier) | 0;
- if (kHostIsLittleEndian) {
- this.dataView.setInt32(offset, value, kHostIsLittleEndian);
- this.dataView.setInt32(offset + 4, hi, kHostIsLittleEndian);
- } else {
- this.dataView.setInt32(offset, hi, kHostIsLittleEndian);
- this.dataView.setInt32(offset + 4, value, kHostIsLittleEndian);
- }
- }
-
- Buffer.prototype.setInt8 = function(offset, value) {
- this.dataView.setInt8(offset, value);
- }
- Buffer.prototype.setInt16 = function(offset, value) {
- this.dataView.setInt16(offset, value, kHostIsLittleEndian);
- }
- Buffer.prototype.setInt32 = function(offset, value) {
- this.dataView.setInt32(offset, value, kHostIsLittleEndian);
- }
- Buffer.prototype.setInt64 = function(offset, value) {
- var hi = Math.floor(value / kHighWordMultiplier);
- if (kHostIsLittleEndian) {
- this.dataView.setInt32(offset, value, kHostIsLittleEndian);
- this.dataView.setInt32(offset + 4, hi, kHostIsLittleEndian);
- } else {
- this.dataView.setInt32(offset, hi, kHostIsLittleEndian);
- this.dataView.setInt32(offset + 4, value, kHostIsLittleEndian);
- }
- }
-
- Buffer.prototype.setFloat32 = function(offset, value) {
- this.dataView.setFloat32(offset, value, kHostIsLittleEndian);
- }
- Buffer.prototype.setFloat64 = function(offset, value) {
- this.dataView.setFloat64(offset, value, kHostIsLittleEndian);
- }
-
- var exports = {};
- exports.Buffer = Buffer;
- return exports;
-});
diff --git a/mojo/public/js/codec.js b/mojo/public/js/codec.js
deleted file mode 100644
index ce58a8cfd4..0000000000
--- a/mojo/public/js/codec.js
+++ /dev/null
@@ -1,926 +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.
-
-define("mojo/public/js/codec", [
- "mojo/public/js/buffer",
- "mojo/public/js/interface_types",
- "mojo/public/js/unicode",
-], function(buffer, types, unicode) {
-
- var kErrorUnsigned = "Passing negative value to unsigned";
- var kErrorArray = "Passing non Array for array type";
- var kErrorString = "Passing non String for string type";
- var kErrorMap = "Passing non Map for map type";
-
- // Memory -------------------------------------------------------------------
-
- var kAlignment = 8;
-
- function align(size) {
- return size + (kAlignment - (size % kAlignment)) % kAlignment;
- }
-
- function isAligned(offset) {
- return offset >= 0 && (offset % kAlignment) === 0;
- }
-
- // Constants ----------------------------------------------------------------
-
- var kArrayHeaderSize = 8;
- var kStructHeaderSize = 8;
- var kMessageHeaderSize = 24;
- var kMessageWithRequestIDHeaderSize = 32;
- var kMapStructPayloadSize = 16;
-
- var kStructHeaderNumBytesOffset = 0;
- var kStructHeaderVersionOffset = 4;
-
- var kEncodedInvalidHandleValue = 0xFFFFFFFF;
-
- // Decoder ------------------------------------------------------------------
-
- function Decoder(buffer, handles, base) {
- this.buffer = buffer;
- this.handles = handles;
- this.base = base;
- this.next = base;
- }
-
- Decoder.prototype.align = function() {
- this.next = align(this.next);
- };
-
- Decoder.prototype.skip = function(offset) {
- this.next += offset;
- };
-
- Decoder.prototype.readInt8 = function() {
- var result = this.buffer.getInt8(this.next);
- this.next += 1;
- return result;
- };
-
- Decoder.prototype.readUint8 = function() {
- var result = this.buffer.getUint8(this.next);
- this.next += 1;
- return result;
- };
-
- Decoder.prototype.readInt16 = function() {
- var result = this.buffer.getInt16(this.next);
- this.next += 2;
- return result;
- };
-
- Decoder.prototype.readUint16 = function() {
- var result = this.buffer.getUint16(this.next);
- this.next += 2;
- return result;
- };
-
- Decoder.prototype.readInt32 = function() {
- var result = this.buffer.getInt32(this.next);
- this.next += 4;
- return result;
- };
-
- Decoder.prototype.readUint32 = function() {
- var result = this.buffer.getUint32(this.next);
- this.next += 4;
- return result;
- };
-
- Decoder.prototype.readInt64 = function() {
- var result = this.buffer.getInt64(this.next);
- this.next += 8;
- return result;
- };
-
- Decoder.prototype.readUint64 = function() {
- var result = this.buffer.getUint64(this.next);
- this.next += 8;
- return result;
- };
-
- Decoder.prototype.readFloat = function() {
- var result = this.buffer.getFloat32(this.next);
- this.next += 4;
- return result;
- };
-
- Decoder.prototype.readDouble = function() {
- var result = this.buffer.getFloat64(this.next);
- this.next += 8;
- return result;
- };
-
- Decoder.prototype.decodePointer = function() {
- // TODO(abarth): To correctly decode a pointer, we need to know the real
- // base address of the array buffer.
- var offsetPointer = this.next;
- var offset = this.readUint64();
- if (!offset)
- return 0;
- return offsetPointer + offset;
- };
-
- Decoder.prototype.decodeAndCreateDecoder = function(pointer) {
- return new Decoder(this.buffer, this.handles, pointer);
- };
-
- Decoder.prototype.decodeHandle = function() {
- return this.handles[this.readUint32()] || null;
- };
-
- Decoder.prototype.decodeString = function() {
- var numberOfBytes = this.readUint32();
- var numberOfElements = this.readUint32();
- var base = this.next;
- this.next += numberOfElements;
- return unicode.decodeUtf8String(
- new Uint8Array(this.buffer.arrayBuffer, base, numberOfElements));
- };
-
- Decoder.prototype.decodeArray = function(cls) {
- var numberOfBytes = this.readUint32();
- var numberOfElements = this.readUint32();
- var val = new Array(numberOfElements);
- if (cls === PackedBool) {
- var byte;
- for (var i = 0; i < numberOfElements; ++i) {
- if (i % 8 === 0)
- byte = this.readUint8();
- val[i] = (byte & (1 << i % 8)) ? true : false;
- }
- } else {
- for (var i = 0; i < numberOfElements; ++i) {
- val[i] = cls.decode(this);
- }
- }
- return val;
- };
-
- Decoder.prototype.decodeStruct = function(cls) {
- return cls.decode(this);
- };
-
- Decoder.prototype.decodeStructPointer = function(cls) {
- var pointer = this.decodePointer();
- if (!pointer) {
- return null;
- }
- return cls.decode(this.decodeAndCreateDecoder(pointer));
- };
-
- Decoder.prototype.decodeArrayPointer = function(cls) {
- var pointer = this.decodePointer();
- if (!pointer) {
- return null;
- }
- return this.decodeAndCreateDecoder(pointer).decodeArray(cls);
- };
-
- Decoder.prototype.decodeStringPointer = function() {
- var pointer = this.decodePointer();
- if (!pointer) {
- return null;
- }
- return this.decodeAndCreateDecoder(pointer).decodeString();
- };
-
- Decoder.prototype.decodeMap = function(keyClass, valueClass) {
- this.skip(4); // numberOfBytes
- this.skip(4); // version
- var keys = this.decodeArrayPointer(keyClass);
- var values = this.decodeArrayPointer(valueClass);
- var val = new Map();
- for (var i = 0; i < keys.length; i++)
- val.set(keys[i], values[i]);
- return val;
- };
-
- Decoder.prototype.decodeMapPointer = function(keyClass, valueClass) {
- var pointer = this.decodePointer();
- if (!pointer) {
- return null;
- }
- var decoder = this.decodeAndCreateDecoder(pointer);
- return decoder.decodeMap(keyClass, valueClass);
- };
-
- // Encoder ------------------------------------------------------------------
-
- function Encoder(buffer, handles, base) {
- this.buffer = buffer;
- this.handles = handles;
- this.base = base;
- this.next = base;
- }
-
- Encoder.prototype.align = function() {
- this.next = align(this.next);
- };
-
- Encoder.prototype.skip = function(offset) {
- this.next += offset;
- };
-
- Encoder.prototype.writeInt8 = function(val) {
- this.buffer.setInt8(this.next, val);
- this.next += 1;
- };
-
- Encoder.prototype.writeUint8 = function(val) {
- if (val < 0) {
- throw new Error(kErrorUnsigned);
- }
- this.buffer.setUint8(this.next, val);
- this.next += 1;
- };
-
- Encoder.prototype.writeInt16 = function(val) {
- this.buffer.setInt16(this.next, val);
- this.next += 2;
- };
-
- Encoder.prototype.writeUint16 = function(val) {
- if (val < 0) {
- throw new Error(kErrorUnsigned);
- }
- this.buffer.setUint16(this.next, val);
- this.next += 2;
- };
-
- Encoder.prototype.writeInt32 = function(val) {
- this.buffer.setInt32(this.next, val);
- this.next += 4;
- };
-
- Encoder.prototype.writeUint32 = function(val) {
- if (val < 0) {
- throw new Error(kErrorUnsigned);
- }
- this.buffer.setUint32(this.next, val);
- this.next += 4;
- };
-
- Encoder.prototype.writeInt64 = function(val) {
- this.buffer.setInt64(this.next, val);
- this.next += 8;
- };
-
- Encoder.prototype.writeUint64 = function(val) {
- if (val < 0) {
- throw new Error(kErrorUnsigned);
- }
- this.buffer.setUint64(this.next, val);
- this.next += 8;
- };
-
- Encoder.prototype.writeFloat = function(val) {
- this.buffer.setFloat32(this.next, val);
- this.next += 4;
- };
-
- Encoder.prototype.writeDouble = function(val) {
- this.buffer.setFloat64(this.next, val);
- this.next += 8;
- };
-
- Encoder.prototype.encodePointer = function(pointer) {
- if (!pointer)
- return this.writeUint64(0);
- // TODO(abarth): To correctly encode a pointer, we need to know the real
- // base address of the array buffer.
- var offset = pointer - this.next;
- this.writeUint64(offset);
- };
-
- Encoder.prototype.createAndEncodeEncoder = function(size) {
- var pointer = this.buffer.alloc(align(size));
- this.encodePointer(pointer);
- return new Encoder(this.buffer, this.handles, pointer);
- };
-
- Encoder.prototype.encodeHandle = function(handle) {
- if (handle) {
- this.handles.push(handle);
- this.writeUint32(this.handles.length - 1);
- } else {
- this.writeUint32(kEncodedInvalidHandleValue);
- }
- };
-
- Encoder.prototype.encodeString = function(val) {
- var base = this.next + kArrayHeaderSize;
- var numberOfElements = unicode.encodeUtf8String(
- val, new Uint8Array(this.buffer.arrayBuffer, base));
- var numberOfBytes = kArrayHeaderSize + numberOfElements;
- this.writeUint32(numberOfBytes);
- this.writeUint32(numberOfElements);
- this.next += numberOfElements;
- };
-
- Encoder.prototype.encodeArray =
- function(cls, val, numberOfElements, encodedSize) {
- if (numberOfElements === undefined)
- numberOfElements = val.length;
- if (encodedSize === undefined)
- encodedSize = kArrayHeaderSize + cls.encodedSize * numberOfElements;
-
- this.writeUint32(encodedSize);
- this.writeUint32(numberOfElements);
-
- if (cls === PackedBool) {
- var byte = 0;
- for (i = 0; i < numberOfElements; ++i) {
- if (val[i])
- byte |= (1 << i % 8);
- if (i % 8 === 7 || i == numberOfElements - 1) {
- Uint8.encode(this, byte);
- byte = 0;
- }
- }
- } else {
- for (var i = 0; i < numberOfElements; ++i)
- cls.encode(this, val[i]);
- }
- };
-
- Encoder.prototype.encodeStruct = function(cls, val) {
- return cls.encode(this, val);
- };
-
- Encoder.prototype.encodeStructPointer = function(cls, val) {
- if (val == null) {
- // Also handles undefined, since undefined == null.
- this.encodePointer(val);
- return;
- }
- var encoder = this.createAndEncodeEncoder(cls.encodedSize);
- cls.encode(encoder, val);
- };
-
- Encoder.prototype.encodeArrayPointer = function(cls, val) {
- if (val == null) {
- // Also handles undefined, since undefined == null.
- this.encodePointer(val);
- return;
- }
-
- var numberOfElements = val.length;
- if (!Number.isSafeInteger(numberOfElements) || numberOfElements < 0)
- throw new Error(kErrorArray);
-
- var encodedSize = kArrayHeaderSize + ((cls === PackedBool) ?
- Math.ceil(numberOfElements / 8) : cls.encodedSize * numberOfElements);
- var encoder = this.createAndEncodeEncoder(encodedSize);
- encoder.encodeArray(cls, val, numberOfElements, encodedSize);
- };
-
- Encoder.prototype.encodeStringPointer = function(val) {
- if (val == null) {
- // Also handles undefined, since undefined == null.
- this.encodePointer(val);
- return;
- }
- // Only accepts string primivites, not String Objects like new String("foo")
- if (typeof(val) !== "string") {
- throw new Error(kErrorString);
- }
- var encodedSize = kArrayHeaderSize + unicode.utf8Length(val);
- var encoder = this.createAndEncodeEncoder(encodedSize);
- encoder.encodeString(val);
- };
-
- Encoder.prototype.encodeMap = function(keyClass, valueClass, val) {
- var keys = new Array(val.size);
- var values = new Array(val.size);
- var i = 0;
- val.forEach(function(value, key) {
- values[i] = value;
- keys[i++] = key;
- });
- this.writeUint32(kStructHeaderSize + kMapStructPayloadSize);
- this.writeUint32(0); // version
- this.encodeArrayPointer(keyClass, keys);
- this.encodeArrayPointer(valueClass, values);
- }
-
- Encoder.prototype.encodeMapPointer = function(keyClass, valueClass, val) {
- if (val == null) {
- // Also handles undefined, since undefined == null.
- this.encodePointer(val);
- return;
- }
- if (!(val instanceof Map)) {
- throw new Error(kErrorMap);
- }
- var encodedSize = kStructHeaderSize + kMapStructPayloadSize;
- var encoder = this.createAndEncodeEncoder(encodedSize);
- encoder.encodeMap(keyClass, valueClass, val);
- };
-
- // Message ------------------------------------------------------------------
-
- var kMessageInterfaceIdOffset = kStructHeaderSize;
- var kMessageNameOffset = kMessageInterfaceIdOffset + 4;
- var kMessageFlagsOffset = kMessageNameOffset + 4;
- var kMessageRequestIDOffset = kMessageFlagsOffset + 8;
-
- var kMessageExpectsResponse = 1 << 0;
- var kMessageIsResponse = 1 << 1;
-
- function Message(buffer, handles) {
- this.buffer = buffer;
- this.handles = handles;
- }
-
- Message.prototype.getHeaderNumBytes = function() {
- return this.buffer.getUint32(kStructHeaderNumBytesOffset);
- };
-
- Message.prototype.getHeaderVersion = function() {
- return this.buffer.getUint32(kStructHeaderVersionOffset);
- };
-
- Message.prototype.getName = function() {
- return this.buffer.getUint32(kMessageNameOffset);
- };
-
- Message.prototype.getFlags = function() {
- return this.buffer.getUint32(kMessageFlagsOffset);
- };
-
- Message.prototype.getInterfaceId = function() {
- return this.buffer.getUint32(kMessageInterfaceIdOffset);
- };
-
- Message.prototype.isResponse = function() {
- return (this.getFlags() & kMessageIsResponse) != 0;
- };
-
- Message.prototype.expectsResponse = function() {
- return (this.getFlags() & kMessageExpectsResponse) != 0;
- };
-
- Message.prototype.setRequestID = function(requestID) {
- // TODO(darin): Verify that space was reserved for this field!
- this.buffer.setUint64(kMessageRequestIDOffset, requestID);
- };
-
- Message.prototype.setInterfaceId = function(interfaceId) {
- this.buffer.setUint32(kMessageInterfaceIdOffset, interfaceId);
- };
-
-
- // MessageBuilder -----------------------------------------------------------
-
- function MessageBuilder(messageName, payloadSize) {
- // Currently, we don't compute the payload size correctly ahead of time.
- // Instead, we resize the buffer at the end.
- var numberOfBytes = kMessageHeaderSize + payloadSize;
- this.buffer = new buffer.Buffer(numberOfBytes);
- this.handles = [];
- var encoder = this.createEncoder(kMessageHeaderSize);
- encoder.writeUint32(kMessageHeaderSize);
- encoder.writeUint32(0); // version.
- encoder.writeUint32(0); // interface ID.
- encoder.writeUint32(messageName);
- encoder.writeUint32(0); // flags.
- encoder.writeUint32(0); // padding.
- }
-
- MessageBuilder.prototype.createEncoder = function(size) {
- var pointer = this.buffer.alloc(size);
- return new Encoder(this.buffer, this.handles, pointer);
- };
-
- MessageBuilder.prototype.encodeStruct = function(cls, val) {
- cls.encode(this.createEncoder(cls.encodedSize), val);
- };
-
- MessageBuilder.prototype.finish = function() {
- // TODO(abarth): Rather than resizing the buffer at the end, we could
- // compute the size we need ahead of time, like we do in C++.
- this.buffer.trim();
- var message = new Message(this.buffer, this.handles);
- this.buffer = null;
- this.handles = null;
- this.encoder = null;
- return message;
- };
-
- // MessageWithRequestIDBuilder -----------------------------------------------
-
- function MessageWithRequestIDBuilder(messageName, payloadSize, flags,
- requestID) {
- // Currently, we don't compute the payload size correctly ahead of time.
- // Instead, we resize the buffer at the end.
- var numberOfBytes = kMessageWithRequestIDHeaderSize + payloadSize;
- this.buffer = new buffer.Buffer(numberOfBytes);
- this.handles = [];
- var encoder = this.createEncoder(kMessageWithRequestIDHeaderSize);
- encoder.writeUint32(kMessageWithRequestIDHeaderSize);
- encoder.writeUint32(1); // version.
- encoder.writeUint32(0); // interface ID.
- encoder.writeUint32(messageName);
- encoder.writeUint32(flags);
- encoder.writeUint32(0); // padding.
- encoder.writeUint64(requestID);
- }
-
- MessageWithRequestIDBuilder.prototype =
- Object.create(MessageBuilder.prototype);
-
- MessageWithRequestIDBuilder.prototype.constructor =
- MessageWithRequestIDBuilder;
-
- // MessageReader ------------------------------------------------------------
-
- function MessageReader(message) {
- this.decoder = new Decoder(message.buffer, message.handles, 0);
- var messageHeaderSize = this.decoder.readUint32();
- this.payloadSize = message.buffer.byteLength - messageHeaderSize;
- var version = this.decoder.readUint32();
- var interface_id = this.decoder.readUint32();
- this.messageName = this.decoder.readUint32();
- this.flags = this.decoder.readUint32();
- // Skip the padding.
- this.decoder.skip(4);
- if (version >= 1)
- this.requestID = this.decoder.readUint64();
- this.decoder.skip(messageHeaderSize - this.decoder.next);
- }
-
- MessageReader.prototype.decodeStruct = function(cls) {
- return cls.decode(this.decoder);
- };
-
- // Built-in types -----------------------------------------------------------
-
- // This type is only used with ArrayOf(PackedBool).
- function PackedBool() {
- }
-
- function Int8() {
- }
-
- Int8.encodedSize = 1;
-
- Int8.decode = function(decoder) {
- return decoder.readInt8();
- };
-
- Int8.encode = function(encoder, val) {
- encoder.writeInt8(val);
- };
-
- Uint8.encode = function(encoder, val) {
- encoder.writeUint8(val);
- };
-
- function Uint8() {
- }
-
- Uint8.encodedSize = 1;
-
- Uint8.decode = function(decoder) {
- return decoder.readUint8();
- };
-
- Uint8.encode = function(encoder, val) {
- encoder.writeUint8(val);
- };
-
- function Int16() {
- }
-
- Int16.encodedSize = 2;
-
- Int16.decode = function(decoder) {
- return decoder.readInt16();
- };
-
- Int16.encode = function(encoder, val) {
- encoder.writeInt16(val);
- };
-
- function Uint16() {
- }
-
- Uint16.encodedSize = 2;
-
- Uint16.decode = function(decoder) {
- return decoder.readUint16();
- };
-
- Uint16.encode = function(encoder, val) {
- encoder.writeUint16(val);
- };
-
- function Int32() {
- }
-
- Int32.encodedSize = 4;
-
- Int32.decode = function(decoder) {
- return decoder.readInt32();
- };
-
- Int32.encode = function(encoder, val) {
- encoder.writeInt32(val);
- };
-
- function Uint32() {
- }
-
- Uint32.encodedSize = 4;
-
- Uint32.decode = function(decoder) {
- return decoder.readUint32();
- };
-
- Uint32.encode = function(encoder, val) {
- encoder.writeUint32(val);
- };
-
- function Int64() {
- }
-
- Int64.encodedSize = 8;
-
- Int64.decode = function(decoder) {
- return decoder.readInt64();
- };
-
- Int64.encode = function(encoder, val) {
- encoder.writeInt64(val);
- };
-
- function Uint64() {
- }
-
- Uint64.encodedSize = 8;
-
- Uint64.decode = function(decoder) {
- return decoder.readUint64();
- };
-
- Uint64.encode = function(encoder, val) {
- encoder.writeUint64(val);
- };
-
- function String() {
- };
-
- String.encodedSize = 8;
-
- String.decode = function(decoder) {
- return decoder.decodeStringPointer();
- };
-
- String.encode = function(encoder, val) {
- encoder.encodeStringPointer(val);
- };
-
- function NullableString() {
- }
-
- NullableString.encodedSize = String.encodedSize;
-
- NullableString.decode = String.decode;
-
- NullableString.encode = String.encode;
-
- function Float() {
- }
-
- Float.encodedSize = 4;
-
- Float.decode = function(decoder) {
- return decoder.readFloat();
- };
-
- Float.encode = function(encoder, val) {
- encoder.writeFloat(val);
- };
-
- function Double() {
- }
-
- Double.encodedSize = 8;
-
- Double.decode = function(decoder) {
- return decoder.readDouble();
- };
-
- Double.encode = function(encoder, val) {
- encoder.writeDouble(val);
- };
-
- function Enum(cls) {
- this.cls = cls;
- }
-
- Enum.prototype.encodedSize = 4;
-
- Enum.prototype.decode = function(decoder) {
- return decoder.readInt32();
- };
-
- Enum.prototype.encode = function(encoder, val) {
- encoder.writeInt32(val);
- };
-
- function PointerTo(cls) {
- this.cls = cls;
- }
-
- PointerTo.prototype.encodedSize = 8;
-
- PointerTo.prototype.decode = function(decoder) {
- var pointer = decoder.decodePointer();
- if (!pointer) {
- return null;
- }
- return this.cls.decode(decoder.decodeAndCreateDecoder(pointer));
- };
-
- PointerTo.prototype.encode = function(encoder, val) {
- if (!val) {
- encoder.encodePointer(val);
- return;
- }
- var objectEncoder = encoder.createAndEncodeEncoder(this.cls.encodedSize);
- this.cls.encode(objectEncoder, val);
- };
-
- function NullablePointerTo(cls) {
- PointerTo.call(this, cls);
- }
-
- NullablePointerTo.prototype = Object.create(PointerTo.prototype);
-
- function ArrayOf(cls, length) {
- this.cls = cls;
- this.length = length || 0;
- }
-
- ArrayOf.prototype.encodedSize = 8;
-
- ArrayOf.prototype.dimensions = function() {
- return [this.length].concat(
- (this.cls instanceof ArrayOf) ? this.cls.dimensions() : []);
- }
-
- ArrayOf.prototype.decode = function(decoder) {
- return decoder.decodeArrayPointer(this.cls);
- };
-
- ArrayOf.prototype.encode = function(encoder, val) {
- encoder.encodeArrayPointer(this.cls, val);
- };
-
- function NullableArrayOf(cls) {
- ArrayOf.call(this, cls);
- }
-
- NullableArrayOf.prototype = Object.create(ArrayOf.prototype);
-
- function Handle() {
- }
-
- Handle.encodedSize = 4;
-
- Handle.decode = function(decoder) {
- return decoder.decodeHandle();
- };
-
- Handle.encode = function(encoder, val) {
- encoder.encodeHandle(val);
- };
-
- function NullableHandle() {
- }
-
- NullableHandle.encodedSize = Handle.encodedSize;
-
- NullableHandle.decode = Handle.decode;
-
- NullableHandle.encode = Handle.encode;
-
- function Interface(cls) {
- this.cls = cls;
- }
-
- Interface.prototype.encodedSize = 8;
-
- Interface.prototype.decode = function(decoder) {
- var interfacePtrInfo = new types.InterfacePtrInfo(
- decoder.decodeHandle(), decoder.readUint32());
- var interfacePtr = new this.cls();
- interfacePtr.ptr.bind(interfacePtrInfo);
- return interfacePtr;
- };
-
- Interface.prototype.encode = function(encoder, val) {
- var interfacePtrInfo =
- val ? val.ptr.passInterface() : new types.InterfacePtrInfo(null, 0);
- encoder.encodeHandle(interfacePtrInfo.handle);
- encoder.writeUint32(interfacePtrInfo.version);
- };
-
- function NullableInterface(cls) {
- Interface.call(this, cls);
- }
-
- NullableInterface.prototype = Object.create(Interface.prototype);
-
- function InterfaceRequest() {
- }
-
- InterfaceRequest.encodedSize = 4;
-
- InterfaceRequest.decode = function(decoder) {
- return new types.InterfaceRequest(decoder.decodeHandle());
- };
-
- InterfaceRequest.encode = function(encoder, val) {
- encoder.encodeHandle(val ? val.handle : null);
- };
-
- function NullableInterfaceRequest() {
- }
-
- NullableInterfaceRequest.encodedSize = InterfaceRequest.encodedSize;
-
- NullableInterfaceRequest.decode = InterfaceRequest.decode;
-
- NullableInterfaceRequest.encode = InterfaceRequest.encode;
-
- function MapOf(keyClass, valueClass) {
- this.keyClass = keyClass;
- this.valueClass = valueClass;
- }
-
- MapOf.prototype.encodedSize = 8;
-
- MapOf.prototype.decode = function(decoder) {
- return decoder.decodeMapPointer(this.keyClass, this.valueClass);
- };
-
- MapOf.prototype.encode = function(encoder, val) {
- encoder.encodeMapPointer(this.keyClass, this.valueClass, val);
- };
-
- function NullableMapOf(keyClass, valueClass) {
- MapOf.call(this, keyClass, valueClass);
- }
-
- NullableMapOf.prototype = Object.create(MapOf.prototype);
-
- var exports = {};
- exports.align = align;
- exports.isAligned = isAligned;
- exports.Message = Message;
- exports.MessageBuilder = MessageBuilder;
- exports.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder;
- exports.MessageReader = MessageReader;
- exports.kArrayHeaderSize = kArrayHeaderSize;
- exports.kMapStructPayloadSize = kMapStructPayloadSize;
- exports.kStructHeaderSize = kStructHeaderSize;
- exports.kEncodedInvalidHandleValue = kEncodedInvalidHandleValue;
- exports.kMessageHeaderSize = kMessageHeaderSize;
- exports.kMessageWithRequestIDHeaderSize = kMessageWithRequestIDHeaderSize;
- exports.kMessageExpectsResponse = kMessageExpectsResponse;
- exports.kMessageIsResponse = kMessageIsResponse;
- exports.Int8 = Int8;
- exports.Uint8 = Uint8;
- exports.Int16 = Int16;
- exports.Uint16 = Uint16;
- exports.Int32 = Int32;
- exports.Uint32 = Uint32;
- exports.Int64 = Int64;
- exports.Uint64 = Uint64;
- exports.Float = Float;
- exports.Double = Double;
- exports.String = String;
- exports.Enum = Enum;
- exports.NullableString = NullableString;
- exports.PointerTo = PointerTo;
- exports.NullablePointerTo = NullablePointerTo;
- exports.ArrayOf = ArrayOf;
- exports.NullableArrayOf = NullableArrayOf;
- exports.PackedBool = PackedBool;
- exports.Handle = Handle;
- exports.NullableHandle = NullableHandle;
- exports.Interface = Interface;
- exports.NullableInterface = NullableInterface;
- exports.InterfaceRequest = InterfaceRequest;
- exports.NullableInterfaceRequest = NullableInterfaceRequest;
- exports.MapOf = MapOf;
- exports.NullableMapOf = NullableMapOf;
- return exports;
-});
diff --git a/mojo/public/js/connector.js b/mojo/public/js/connector.js
deleted file mode 100644
index 012e3c7c07..0000000000
--- a/mojo/public/js/connector.js
+++ /dev/null
@@ -1,113 +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.
-
-define("mojo/public/js/connector", [
- "mojo/public/js/buffer",
- "mojo/public/js/codec",
- "mojo/public/js/core",
- "mojo/public/js/support",
-], function(buffer, codec, core, support) {
-
- function Connector(handle) {
- if (!core.isHandle(handle))
- throw new Error("Connector: not a handle " + handle);
- this.handle_ = handle;
- this.dropWrites_ = false;
- this.error_ = false;
- this.incomingReceiver_ = null;
- this.readWatcher_ = null;
- this.errorHandler_ = null;
-
- if (handle) {
- this.readWatcher_ = support.watch(handle,
- core.HANDLE_SIGNAL_READABLE,
- this.readMore_.bind(this));
- }
- }
-
- Connector.prototype.close = function() {
- if (this.readWatcher_) {
- support.cancelWatch(this.readWatcher_);
- this.readWatcher_ = null;
- }
- if (this.handle_ != null) {
- core.close(this.handle_);
- this.handle_ = null;
- }
- };
-
- Connector.prototype.accept = function(message) {
- if (this.error_)
- return false;
-
- if (this.dropWrites_)
- return true;
-
- var result = core.writeMessage(this.handle_,
- new Uint8Array(message.buffer.arrayBuffer),
- message.handles,
- core.WRITE_MESSAGE_FLAG_NONE);
- switch (result) {
- case core.RESULT_OK:
- // The handles were successfully transferred, so we don't own them
- // anymore.
- message.handles = [];
- break;
- case core.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.
- this.dropWrites_ = true;
- break;
- default:
- // This particular write was rejected, presumably because of bad input.
- // The pipe is not necessarily in a bad state.
- return false;
- }
- return true;
- };
-
- Connector.prototype.setIncomingReceiver = function(receiver) {
- this.incomingReceiver_ = receiver;
- };
-
- Connector.prototype.setErrorHandler = function(handler) {
- this.errorHandler_ = handler;
- };
-
- Connector.prototype.waitForNextMessageForTesting = function() {
- var wait = core.wait(this.handle_, core.HANDLE_SIGNAL_READABLE);
- this.readMore_(wait.result);
- };
-
- Connector.prototype.readMore_ = function(result) {
- for (;;) {
- var read = core.readMessage(this.handle_,
- core.READ_MESSAGE_FLAG_NONE);
- if (this.handle_ == null) // The connector has been closed.
- return;
- if (read.result == core.RESULT_SHOULD_WAIT)
- return;
- if (read.result != core.RESULT_OK) {
- // TODO(wangjimmy): Add a handleError method to swap the handle to be
- // closed with a dummy handle in the case when
- // read.result != MOJO_RESULT_FAILED_PRECONDITION
- this.error_ = true;
- if (this.errorHandler_)
- this.errorHandler_.onError();
- return;
- }
- var messageBuffer = new buffer.Buffer(read.buffer);
- var message = new codec.Message(messageBuffer, read.handles);
- if (this.incomingReceiver_)
- this.incomingReceiver_.accept(message);
- }
- };
-
- var exports = {};
- exports.Connector = Connector;
- return exports;
-});
diff --git a/mojo/public/js/constants.cc b/mojo/public/js/constants.cc
deleted file mode 100644
index a0ce7d4d1d..0000000000
--- a/mojo/public/js/constants.cc
+++ /dev/null
@@ -1,33 +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 "mojo/public/js/constants.h"
-
-namespace mojo {
-
-const char kBindingsModuleName[] = "mojo/public/js/bindings";
-const char kBufferModuleName[] = "mojo/public/js/buffer";
-const char kCodecModuleName[] = "mojo/public/js/codec";
-const char kConnectorModuleName[] = "mojo/public/js/connector";
-const char kControlMessageHandlerModuleName[] =
- "mojo/public/js/lib/control_message_handler";
-const char kControlMessageProxyModuleName[] =
- "mojo/public/js/lib/control_message_proxy";
-const char kInterfaceControlMessagesMojom[] =
- "mojo/public/interfaces/bindings/interface_control_messages.mojom";
-const char kInterfaceEndpointClientModuleName[] =
- "mojo/public/js/lib/interface_endpoint_client";
-const char kInterfaceEndpointHandleModuleName[] =
- "mojo/public/js/lib/interface_endpoint_handle";
-const char kInterfaceTypesModuleName[] = "mojo/public/js/interface_types";
-const char kPipeControlMessageHandlerModuleName[] =
- "mojo/public/js/lib/pipe_control_message_handler";
-const char kPipeControlMessageProxyModuleName[] =
- "mojo/public/js/lib/pipe_control_message_proxy";
-const char kPipeControlMessagesMojom[] =
- "mojo/public/interfaces/bindings/pipe_control_messages.mojom";
-const char kRouterModuleName[] = "mojo/public/js/router";
-const char kUnicodeModuleName[] = "mojo/public/js/unicode";
-const char kValidatorModuleName[] = "mojo/public/js/validator";
-} // namespace mojo
diff --git a/mojo/public/js/constants.h b/mojo/public/js/constants.h
deleted file mode 100644
index f561d739b9..0000000000
--- a/mojo/public/js/constants.h
+++ /dev/null
@@ -1,30 +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 MOJO_PUBLIC_JS_BINDINGS_CONSTANTS_H_
-#define MOJO_PUBLIC_JS_BINDINGS_CONSTANTS_H_
-
-namespace mojo {
-
-// JavaScript module names:
-extern const char kBindingsModuleName[];
-extern const char kBufferModuleName[];
-extern const char kCodecModuleName[];
-extern const char kConnectorModuleName[];
-extern const char kControlMessageHandlerModuleName[];
-extern const char kControlMessageProxyModuleName[];
-extern const char kInterfaceControlMessagesMojom[];
-extern const char kInterfaceEndpointClientModuleName[];
-extern const char kInterfaceEndpointHandleModuleName[];
-extern const char kInterfaceTypesModuleName[];
-extern const char kPipeControlMessageHandlerModuleName[];
-extern const char kPipeControlMessageProxyModuleName[];
-extern const char kPipeControlMessagesMojom[];
-extern const char kRouterModuleName[];
-extern const char kUnicodeModuleName[];
-extern const char kValidatorModuleName[];
-
-} // namespace mojo
-
-#endif // MOJO_PUBLIC_JS_BINDINGS_CONSTANTS_H_
diff --git a/mojo/public/js/core.js b/mojo/public/js/core.js
deleted file mode 100644
index b2c4ee2733..0000000000
--- a/mojo/public/js/core.js
+++ /dev/null
@@ -1,304 +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.
-
-// Module "mojo/public/js/core"
-//
-// Note: This file is for documentation purposes only. The code here is not
-// actually executed. The real module is implemented natively in Mojo.
-//
-// This module provides the JavaScript bindings for mojo/public/c/system/core.h.
-// Refer to that file for more detailed documentation for equivalent methods.
-
-while (1);
-
-/**
- * MojoHandle: An opaque handles to a Mojo object (e.g. a message pipe).
- */
-var kInvalidHandle;
-
-/**
- * MojoResult {number}: Result codes for Mojo operations.
- * See core.h for more information.
- */
-var RESULT_OK;
-var RESULT_CANCELLED;
-var RESULT_UNKNOWN;
-var RESULT_INVALID_ARGUMENT;
-var RESULT_DEADLINE_EXCEEDED;
-var RESULT_NOT_FOUND;
-var RESULT_ALREADY_EXISTS;
-var RESULT_PERMISSION_DENIED;
-var RESULT_RESOURCE_EXHAUSTED;
-var RESULT_FAILED_PRECONDITION;
-var RESULT_ABORTED;
-var RESULT_OUT_OF_RANGE;
-var RESULT_UNIMPLEMENTED;
-var RESULT_INTERNAL;
-var RESULT_UNAVAILABLE;
-var RESULT_DATA_LOSS;
-var RESULT_BUSY;
-var RESULT_SHOULD_WAIT;
-
-/**
- * MojoHandleSignals: Used to specify signals that can be waited on for a handle
- *(and which can be triggered), e.g., the ability to read or write to
- * the handle.
- * See core.h for more information.
- */
-var HANDLE_SIGNAL_NONE;
-var HANDLE_SIGNAL_READABLE;
-var HANDLE_SIGNAL_WRITABLE;
-var HANDLE_SIGNAL_PEER_CLOSED;
-
-/**
- * MojoCreateDataMessageOptions: Used to specify creation parameters for a data
- * pipe to |createDataMessage()|.
- * See core.h for more information.
- */
-dictionary MojoCreateDataMessageOptions {
- MojoCreateDataMessageOptionsFlags flags; // See below.
-};
-
-// MojoCreateDataMessageOptionsFlags
-var CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE;
-
-/*
- * MojoWriteMessageFlags: Used to specify different modes to |writeMessage()|.
- * See core.h for more information.
- */
-var WRITE_MESSAGE_FLAG_NONE;
-
-/**
- * MojoReadMessageFlags: Used to specify different modes to |readMessage()|.
- * See core.h for more information.
- */
-var READ_MESSAGE_FLAG_NONE;
-var READ_MESSAGE_FLAG_MAY_DISCARD;
-
-/**
- * MojoCreateDataPipeOptions: Used to specify creation parameters for a data
- * pipe to |createDataPipe()|.
- * See core.h for more information.
- */
-dictionary MojoCreateDataPipeOptions {
- MojoCreateDataPipeOptionsFlags flags; // See below.
- int32 elementNumBytes; // The size of an element, in bytes.
- int32 capacityNumBytes; // The capacity of the data pipe, in bytes.
-};
-
-// MojoCreateDataPipeOptionsFlags
-var CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
-
-/*
- * MojoWriteDataFlags: Used to specify different modes to |writeData()|.
- * See core.h for more information.
- */
-var WRITE_DATA_FLAG_NONE;
-var WRITE_DATA_FLAG_ALL_OR_NONE;
-
-/**
- * MojoReadDataFlags: Used to specify different modes to |readData()|.
- * See core.h for more information.
- */
-var READ_DATA_FLAG_NONE;
-var READ_DATA_FLAG_ALL_OR_NONE;
-var READ_DATA_FLAG_DISCARD;
-var READ_DATA_FLAG_QUERY;
-var READ_DATA_FLAG_PEEK;
-
-/**
- * MojoCreateSharedBufferOptionsFlags: Used to specify options to
- * |createSharedBuffer()|.
- * See core.h for more information.
- */
-var CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE;
-
-/**
- * MojoDuplicateBufferHandleOptionsFlags: Used to specify options to
- * |duplicateBufferHandle()|.
- * See core.h for more information.
- */
-var DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE;
-var DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY;
-
-/**
- * MojoMapBufferFlags: Used to specify options to |mapBuffer()|.
- * See core.h for more information.
- */
-var MAP_BUFFER_FLAG_NONE;
-
-/**
- * Closes the given |handle|. See MojoClose for more info.
- * @param {MojoHandle} Handle to close.
- * @return {MojoResult} Result code.
- */
-function close(handle) { [native code] }
-
-/**
- * Queries the last known signaling state of |handle|.
- *
- * @param {MojoHandle} handle Handle to query.
- * @return {object} An object of the form {
- * result, // MOJO_RESULT_OK or MOJO_RESULT_INVALID_ARGUMENT
- * satisfiedSignals, // MojoHandleSignals (see above)
- * satisfiableSignals, // MojoHandleSignals
- * }
- */
-function queryHandleSignalsState(handle) { [native code] }
-
-/**
- * Waits on the given handle until a signal indicated by |signals| is
- * satisfied or an error occurs.
- *
- * @param {MojoHandle} handle Handle to wait on.
- * @param {MojoHandleSignals} signals Specifies the condition to wait for.
- * @return {MojoResult} Result code.
- */
-function wait(handle, signals) { [native code] }
-
-/**
- * Creates a message pipe. This function always succeeds.
- * See MojoCreateMessagePipe for more information on message pipes.
- *
- * @param {MojoCreateMessagePipeOptions} optionsDict Options to control the
- * message pipe parameters. May be null.
- * @return {MessagePipe} An object of the form {
- * handle0,
- * handle1,
- * }
- * where |handle0| and |handle1| are MojoHandles to each end of the channel.
- */
-function createMessagePipe(optionsDict) { [native code] }
-
-/**
- * Writes a message to the message pipe endpoint given by |handle|. See
- * MojoWriteMessage for more information, including return codes.
- *
- * @param {MojoHandle} handle The endpoint to write to.
- * @param {ArrayBufferView} buffer The message data. May be empty.
- * @param {Array.MojoHandle} handlesArray Any handles to attach. Handles are
- * transferred on success and will no longer be valid. May be empty.
- * @param {MojoWriteMessageFlags} flags Flags.
- * @return {MojoResult} Result code.
- */
-function writeMessage(handle, buffer, handlesArray, flags) { [native code] }
-
-/**
- * Reads a message from the message pipe endpoint given by |handle|. See
- * MojoReadMessage for more information, including return codes.
- *
- * @param {MojoHandle} handle The endpoint to read from.
- * @param {MojoReadMessageFlags} flags Flags.
- * @return {object} An object of the form {
- * result, // |RESULT_OK| on success, error code otherwise.
- * buffer, // An ArrayBufferView of the message data (only on success).
- * handles // An array of MojoHandles transferred, if any.
- * }
- */
-function readMessage(handle, flags) { [native code] }
-
-/**
- * Creates a data pipe, which is a unidirectional communication channel for
- * unframed data, with the given options. See MojoCreateDataPipe for more
- * more information, including return codes.
- *
- * @param {MojoCreateDataPipeOptions} optionsDict Options to control the data
- * pipe parameters. May be null.
- * @return {object} An object of the form {
- * result, // |RESULT_OK| on success, error code otherwise.
- * producerHandle, // MojoHandle to use with writeData (only on success).
- * consumerHandle, // MojoHandle to use with readData (only on success).
- * }
- */
-function createDataPipe(optionsDict) { [native code] }
-
-/**
- * Writes the given data to the data pipe producer given by |handle|. See
- * MojoWriteData for more information, including return codes.
- *
- * @param {MojoHandle} handle A producerHandle returned by createDataPipe.
- * @param {ArrayBufferView} buffer The data to write.
- * @param {MojoWriteDataFlags} flags Flags.
- * @return {object} An object of the form {
- * result, // |RESULT_OK| on success, error code otherwise.
- * numBytes, // The number of bytes written.
- * }
- */
-function writeData(handle, buffer, flags) { [native code] }
-
-/**
- * Reads data from the data pipe consumer given by |handle|. May also
- * be used to discard data. See MojoReadData for more information, including
- * return codes.
- *
- * @param {MojoHandle} handle A consumerHandle returned by createDataPipe.
- * @param {MojoReadDataFlags} flags Flags.
- * @return {object} An object of the form {
- * result, // |RESULT_OK| on success, error code otherwise.
- * buffer, // An ArrayBufferView of the data read (only on success).
- * }
- */
-function readData(handle, flags) { [native code] }
-
-/**
- * True if the argument is a message or data pipe handle.
- *
- * @param {value} an arbitrary JS value.
- * @return true or false
- */
-function isHandle(value) { [native code] }
-
-/**
- * Creates shared buffer of specified size |num_bytes|.
- * See MojoCreateSharedBuffer for more information including error codes.
- *
- * @param {number} num_bytes Size of the memory to be allocated for shared
- * @param {MojoCreateSharedBufferOptionsFlags} flags Flags.
- * buffer.
- * @return {object} An object of the form {
- * result, // |RESULT_OK| on success, error code otherwise.
- * handle, // An MojoHandle for shared buffer (only on success).
- * }
- */
-function createSharedBuffer(num_bytes, flags) { [native code] }
-
-/**
- * Duplicates the |buffer_handle| to a shared buffer. Duplicated handle can be
- * sent to another process over message pipe. See MojoDuplicateBufferHandle for
- * more information including error codes.
- *
- * @param {MojoHandle} buffer_handle MojoHandle.
- * @param {MojoCreateSharedBufferOptionsFlags} flags Flags.
- * @return {object} An object of the form {
- * result, // |RESULT_OK| on success, error code otherwise.
- * handle, // A duplicated MojoHandle for shared buffer (only on success).
- * }
- */
-function duplicateBufferHandle(buffer_handle, flags) { [native code] }
-
-/**
- * Maps the part (at offset |offset| of length |num_bytes|) of the buffer given
- * by |buffer_handle| into ArrayBuffer memory |buffer|, with options specified
- * by |flags|. See MojoMapBuffer for more information including error codes.
- *
- * @param {MojoHandle} buffer_handle A sharedBufferHandle returned by
- * createSharedBuffer.
- * @param {number} offset Offset.
- * @param {number} num_bytes Size of the memory to be mapped.
- * @param {MojoMapBufferFlags} flags Flags.
- * @return {object} An object of the form {
- * result, // |RESULT_OK| on success, error code otherwise.
- * buffer, // An ArrayBuffer (only on success).
- * }
- */
-function mapBuffer(buffer_handle, offset, num_bytes, flags) { [native code] }
-
-/**
- * Unmaps buffer that was mapped using mapBuffer.
- * See MojoUnmapBuffer for more information including error codes.
- *
- * @param {ArrayBuffer} buffer ArrayBuffer.
- * @return {MojoResult} Result code.
- */
-function unmapBuffer(buffer) { [native code] }
diff --git a/mojo/public/js/interface_types.js b/mojo/public/js/interface_types.js
index e8ed37ae64..b7085e5a90 100644
--- a/mojo/public/js/interface_types.js
+++ b/mojo/public/js/interface_types.js
@@ -2,9 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-define("mojo/public/js/interface_types", [
- "mojo/public/js/core",
-], function(core) {
+(function() {
+ var internal = mojo.internal;
// Constants ----------------------------------------------------------------
var kInterfaceIdNamespaceMask = 0x80000000;
@@ -19,18 +18,27 @@ define("mojo/public/js/interface_types", [
}
InterfacePtrInfo.prototype.isValid = function() {
- return core.isHandle(this.handle);
+ return this.handle instanceof MojoHandle;
};
InterfacePtrInfo.prototype.close = function() {
if (!this.isValid())
return;
- core.close(this.handle);
+ this.handle.close();
this.handle = null;
this.version = 0;
};
+ function AssociatedInterfacePtrInfo(interfaceEndpointHandle, version) {
+ this.interfaceEndpointHandle = interfaceEndpointHandle;
+ this.version = version;
+ }
+
+ AssociatedInterfacePtrInfo.prototype.isValid = function() {
+ return this.interfaceEndpointHandle.isValid();
+ };
+
// ---------------------------------------------------------------------------
function InterfaceRequest(handle) {
@@ -38,17 +46,29 @@ define("mojo/public/js/interface_types", [
}
InterfaceRequest.prototype.isValid = function() {
- return core.isHandle(this.handle);
+ return this.handle instanceof MojoHandle;
};
InterfaceRequest.prototype.close = function() {
if (!this.isValid())
return;
- core.close(this.handle);
+ this.handle.close();
this.handle = null;
};
+ function AssociatedInterfaceRequest(interfaceEndpointHandle) {
+ this.interfaceEndpointHandle = interfaceEndpointHandle;
+ }
+
+ AssociatedInterfaceRequest.prototype.isValid = function() {
+ return this.interfaceEndpointHandle.isValid();
+ };
+
+ AssociatedInterfaceRequest.prototype.resetWithReason = function(reason) {
+ this.interfaceEndpointHandle.reset(reason);
+ };
+
function isMasterInterfaceId(interfaceId) {
return interfaceId === kMasterInterfaceId;
}
@@ -57,14 +77,21 @@ define("mojo/public/js/interface_types", [
return interfaceId !== kInvalidInterfaceId;
}
- var exports = {};
- exports.InterfacePtrInfo = InterfacePtrInfo;
- exports.InterfaceRequest = InterfaceRequest;
- exports.isMasterInterfaceId = isMasterInterfaceId;
- exports.isValidInterfaceId = isValidInterfaceId;
- exports.kInvalidInterfaceId = kInvalidInterfaceId;
- exports.kMasterInterfaceId = kMasterInterfaceId;
- exports.kInterfaceIdNamespaceMask = kInterfaceIdNamespaceMask;
-
- return exports;
-});
+ function hasInterfaceIdNamespaceBitSet(interfaceId) {
+ if (interfaceId >= 2 * kInterfaceIdNamespaceMask) {
+ throw new Error("Interface ID should be a 32-bit unsigned integer.");
+ }
+ return interfaceId >= kInterfaceIdNamespaceMask;
+ }
+
+ mojo.InterfacePtrInfo = InterfacePtrInfo;
+ mojo.InterfaceRequest = InterfaceRequest;
+ mojo.AssociatedInterfacePtrInfo = AssociatedInterfacePtrInfo;
+ mojo.AssociatedInterfaceRequest = AssociatedInterfaceRequest;
+ internal.isMasterInterfaceId = isMasterInterfaceId;
+ internal.isValidInterfaceId = isValidInterfaceId;
+ internal.hasInterfaceIdNamespaceBitSet = hasInterfaceIdNamespaceBitSet;
+ internal.kInvalidInterfaceId = kInvalidInterfaceId;
+ internal.kMasterInterfaceId = kMasterInterfaceId;
+ internal.kInterfaceIdNamespaceMask = kInterfaceIdNamespaceMask;
+})();
diff --git a/mojo/public/js/new_bindings/buffer.js b/mojo/public/js/lib/buffer.js
index c44058bd0f..c44058bd0f 100644
--- a/mojo/public/js/new_bindings/buffer.js
+++ b/mojo/public/js/lib/buffer.js
diff --git a/mojo/public/js/lib/codec.js b/mojo/public/js/lib/codec.js
new file mode 100644
index 0000000000..2bb7b8ae47
--- /dev/null
+++ b/mojo/public/js/lib/codec.js
@@ -0,0 +1,1139 @@
+// 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.
+
+(function() {
+ var internal = mojo.internal;
+
+ var kErrorUnsigned = "Passing negative value to unsigned";
+ var kErrorArray = "Passing non Array for array type";
+ var kErrorString = "Passing non String for string type";
+ var kErrorMap = "Passing non Map for map type";
+
+ // Memory -------------------------------------------------------------------
+
+ var kAlignment = 8;
+
+ function align(size) {
+ return size + (kAlignment - (size % kAlignment)) % kAlignment;
+ }
+
+ function isAligned(offset) {
+ return offset >= 0 && (offset % kAlignment) === 0;
+ }
+
+ // Constants ----------------------------------------------------------------
+
+ var kArrayHeaderSize = 8;
+ var kStructHeaderSize = 8;
+ var kMessageV0HeaderSize = 24;
+ var kMessageV1HeaderSize = 32;
+ var kMessageV2HeaderSize = 48;
+ var kMapStructPayloadSize = 16;
+
+ var kStructHeaderNumBytesOffset = 0;
+ var kStructHeaderVersionOffset = 4;
+
+ var kEncodedInvalidHandleValue = 0xFFFFFFFF;
+
+ // Decoder ------------------------------------------------------------------
+
+ function Decoder(buffer, handles, associatedEndpointHandles, base) {
+ this.buffer = buffer;
+ this.handles = handles;
+ this.associatedEndpointHandles = associatedEndpointHandles;
+ this.base = base;
+ this.next = base;
+ }
+
+ Decoder.prototype.align = function() {
+ this.next = align(this.next);
+ };
+
+ Decoder.prototype.skip = function(offset) {
+ this.next += offset;
+ };
+
+ Decoder.prototype.readInt8 = function() {
+ var result = this.buffer.getInt8(this.next);
+ this.next += 1;
+ return result;
+ };
+
+ Decoder.prototype.readUint8 = function() {
+ var result = this.buffer.getUint8(this.next);
+ this.next += 1;
+ return result;
+ };
+
+ Decoder.prototype.readInt16 = function() {
+ var result = this.buffer.getInt16(this.next);
+ this.next += 2;
+ return result;
+ };
+
+ Decoder.prototype.readUint16 = function() {
+ var result = this.buffer.getUint16(this.next);
+ this.next += 2;
+ return result;
+ };
+
+ Decoder.prototype.readInt32 = function() {
+ var result = this.buffer.getInt32(this.next);
+ this.next += 4;
+ return result;
+ };
+
+ Decoder.prototype.readUint32 = function() {
+ var result = this.buffer.getUint32(this.next);
+ this.next += 4;
+ return result;
+ };
+
+ Decoder.prototype.readInt64 = function() {
+ var result = this.buffer.getInt64(this.next);
+ this.next += 8;
+ return result;
+ };
+
+ Decoder.prototype.readUint64 = function() {
+ var result = this.buffer.getUint64(this.next);
+ this.next += 8;
+ return result;
+ };
+
+ Decoder.prototype.readFloat = function() {
+ var result = this.buffer.getFloat32(this.next);
+ this.next += 4;
+ return result;
+ };
+
+ Decoder.prototype.readDouble = function() {
+ var result = this.buffer.getFloat64(this.next);
+ this.next += 8;
+ return result;
+ };
+
+ Decoder.prototype.decodePointer = function() {
+ // TODO(abarth): To correctly decode a pointer, we need to know the real
+ // base address of the array buffer.
+ var offsetPointer = this.next;
+ var offset = this.readUint64();
+ if (!offset)
+ return 0;
+ return offsetPointer + offset;
+ };
+
+ Decoder.prototype.decodeAndCreateDecoder = function(pointer) {
+ return new Decoder(this.buffer, this.handles,
+ this.associatedEndpointHandles, pointer);
+ };
+
+ Decoder.prototype.decodeHandle = function() {
+ return this.handles[this.readUint32()] || null;
+ };
+
+ Decoder.prototype.decodeAssociatedEndpointHandle = function() {
+ return this.associatedEndpointHandles[this.readUint32()] || null;
+ };
+
+ Decoder.prototype.decodeString = function() {
+ var numberOfBytes = this.readUint32();
+ var numberOfElements = this.readUint32();
+ var base = this.next;
+ this.next += numberOfElements;
+ return internal.decodeUtf8String(
+ new Uint8Array(this.buffer.arrayBuffer, base, numberOfElements));
+ };
+
+ Decoder.prototype.decodeArray = function(cls) {
+ var numberOfBytes = this.readUint32();
+ var numberOfElements = this.readUint32();
+ var val = new Array(numberOfElements);
+ if (cls === PackedBool) {
+ var byte;
+ for (var i = 0; i < numberOfElements; ++i) {
+ if (i % 8 === 0)
+ byte = this.readUint8();
+ val[i] = (byte & (1 << i % 8)) ? true : false;
+ }
+ } else {
+ for (var i = 0; i < numberOfElements; ++i) {
+ val[i] = cls.decode(this);
+ }
+ }
+ return val;
+ };
+
+ Decoder.prototype.decodeStruct = function(cls) {
+ return cls.decode(this);
+ };
+
+ Decoder.prototype.decodeStructPointer = function(cls) {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return cls.decode(this.decodeAndCreateDecoder(pointer));
+ };
+
+ Decoder.prototype.decodeArrayPointer = function(cls) {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return this.decodeAndCreateDecoder(pointer).decodeArray(cls);
+ };
+
+ Decoder.prototype.decodeStringPointer = function() {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return this.decodeAndCreateDecoder(pointer).decodeString();
+ };
+
+ Decoder.prototype.decodeMap = function(keyClass, valueClass) {
+ this.skip(4); // numberOfBytes
+ this.skip(4); // version
+ var keys = this.decodeArrayPointer(keyClass);
+ var values = this.decodeArrayPointer(valueClass);
+ var val = new Map();
+ for (var i = 0; i < keys.length; i++)
+ val.set(keys[i], values[i]);
+ return val;
+ };
+
+ Decoder.prototype.decodeMapPointer = function(keyClass, valueClass) {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ var decoder = this.decodeAndCreateDecoder(pointer);
+ return decoder.decodeMap(keyClass, valueClass);
+ };
+
+ // Encoder ------------------------------------------------------------------
+
+ function Encoder(buffer, handles, associatedEndpointHandles, base) {
+ this.buffer = buffer;
+ this.handles = handles;
+ this.associatedEndpointHandles = associatedEndpointHandles;
+ this.base = base;
+ this.next = base;
+ }
+
+ Encoder.prototype.align = function() {
+ this.next = align(this.next);
+ };
+
+ Encoder.prototype.skip = function(offset) {
+ this.next += offset;
+ };
+
+ Encoder.prototype.writeInt8 = function(val) {
+ this.buffer.setInt8(this.next, val);
+ this.next += 1;
+ };
+
+ Encoder.prototype.writeUint8 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint8(this.next, val);
+ this.next += 1;
+ };
+
+ Encoder.prototype.writeInt16 = function(val) {
+ this.buffer.setInt16(this.next, val);
+ this.next += 2;
+ };
+
+ Encoder.prototype.writeUint16 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint16(this.next, val);
+ this.next += 2;
+ };
+
+ Encoder.prototype.writeInt32 = function(val) {
+ this.buffer.setInt32(this.next, val);
+ this.next += 4;
+ };
+
+ Encoder.prototype.writeUint32 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint32(this.next, val);
+ this.next += 4;
+ };
+
+ Encoder.prototype.writeInt64 = function(val) {
+ this.buffer.setInt64(this.next, val);
+ this.next += 8;
+ };
+
+ Encoder.prototype.writeUint64 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint64(this.next, val);
+ this.next += 8;
+ };
+
+ Encoder.prototype.writeFloat = function(val) {
+ this.buffer.setFloat32(this.next, val);
+ this.next += 4;
+ };
+
+ Encoder.prototype.writeDouble = function(val) {
+ this.buffer.setFloat64(this.next, val);
+ this.next += 8;
+ };
+
+ Encoder.prototype.encodePointer = function(pointer) {
+ if (!pointer)
+ return this.writeUint64(0);
+ // TODO(abarth): To correctly encode a pointer, we need to know the real
+ // base address of the array buffer.
+ var offset = pointer - this.next;
+ this.writeUint64(offset);
+ };
+
+ Encoder.prototype.createAndEncodeEncoder = function(size) {
+ var pointer = this.buffer.alloc(align(size));
+ this.encodePointer(pointer);
+ return new Encoder(this.buffer, this.handles,
+ this.associatedEndpointHandles, pointer);
+ };
+
+ Encoder.prototype.encodeHandle = function(handle) {
+ if (handle) {
+ this.handles.push(handle);
+ this.writeUint32(this.handles.length - 1);
+ } else {
+ this.writeUint32(kEncodedInvalidHandleValue);
+ }
+ };
+
+ Encoder.prototype.encodeAssociatedEndpointHandle = function(endpointHandle) {
+ if (endpointHandle) {
+ this.associatedEndpointHandles.push(endpointHandle);
+ this.writeUint32(this.associatedEndpointHandles.length - 1);
+ } else {
+ this.writeUint32(kEncodedInvalidHandleValue);
+ }
+ };
+
+ Encoder.prototype.encodeString = function(val) {
+ var base = this.next + kArrayHeaderSize;
+ var numberOfElements = internal.encodeUtf8String(
+ val, new Uint8Array(this.buffer.arrayBuffer, base));
+ var numberOfBytes = kArrayHeaderSize + numberOfElements;
+ this.writeUint32(numberOfBytes);
+ this.writeUint32(numberOfElements);
+ this.next += numberOfElements;
+ };
+
+ Encoder.prototype.encodeArray =
+ function(cls, val, numberOfElements, encodedSize) {
+ if (numberOfElements === undefined)
+ numberOfElements = val.length;
+ if (encodedSize === undefined)
+ encodedSize = kArrayHeaderSize + cls.encodedSize * numberOfElements;
+
+ this.writeUint32(encodedSize);
+ this.writeUint32(numberOfElements);
+
+ if (cls === PackedBool) {
+ var byte = 0;
+ for (i = 0; i < numberOfElements; ++i) {
+ if (val[i])
+ byte |= (1 << i % 8);
+ if (i % 8 === 7 || i == numberOfElements - 1) {
+ Uint8.encode(this, byte);
+ byte = 0;
+ }
+ }
+ } else {
+ for (var i = 0; i < numberOfElements; ++i)
+ cls.encode(this, val[i]);
+ }
+ };
+
+ Encoder.prototype.encodeStruct = function(cls, val) {
+ return cls.encode(this, val);
+ };
+
+ Encoder.prototype.encodeStructPointer = function(cls, val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+ var encoder = this.createAndEncodeEncoder(cls.encodedSize);
+ cls.encode(encoder, val);
+ };
+
+ Encoder.prototype.encodeArrayPointer = function(cls, val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+
+ var numberOfElements = val.length;
+ if (!Number.isSafeInteger(numberOfElements) || numberOfElements < 0)
+ throw new Error(kErrorArray);
+
+ var encodedSize = kArrayHeaderSize + ((cls === PackedBool) ?
+ Math.ceil(numberOfElements / 8) : cls.encodedSize * numberOfElements);
+ var encoder = this.createAndEncodeEncoder(encodedSize);
+ encoder.encodeArray(cls, val, numberOfElements, encodedSize);
+ };
+
+ Encoder.prototype.encodeStringPointer = function(val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+ // Only accepts string primivites, not String Objects like new String("foo")
+ if (typeof(val) !== "string") {
+ throw new Error(kErrorString);
+ }
+ var encodedSize = kArrayHeaderSize + internal.utf8Length(val);
+ var encoder = this.createAndEncodeEncoder(encodedSize);
+ encoder.encodeString(val);
+ };
+
+ Encoder.prototype.encodeMap = function(keyClass, valueClass, val) {
+ var keys = new Array(val.size);
+ var values = new Array(val.size);
+ var i = 0;
+ val.forEach(function(value, key) {
+ values[i] = value;
+ keys[i++] = key;
+ });
+ this.writeUint32(kStructHeaderSize + kMapStructPayloadSize);
+ this.writeUint32(0); // version
+ this.encodeArrayPointer(keyClass, keys);
+ this.encodeArrayPointer(valueClass, values);
+ }
+
+ Encoder.prototype.encodeMapPointer = function(keyClass, valueClass, val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+ if (!(val instanceof Map)) {
+ throw new Error(kErrorMap);
+ }
+ var encodedSize = kStructHeaderSize + kMapStructPayloadSize;
+ var encoder = this.createAndEncodeEncoder(encodedSize);
+ encoder.encodeMap(keyClass, valueClass, val);
+ };
+
+ // Message ------------------------------------------------------------------
+
+ var kMessageInterfaceIdOffset = kStructHeaderSize;
+ var kMessageNameOffset = kMessageInterfaceIdOffset + 4;
+ var kMessageFlagsOffset = kMessageNameOffset + 4;
+ var kMessageRequestIDOffset = kMessageFlagsOffset + 8;
+ var kMessagePayloadInterfaceIdsPointerOffset = kMessageV2HeaderSize - 8;
+
+ var kMessageExpectsResponse = 1 << 0;
+ var kMessageIsResponse = 1 << 1;
+
+ function Message(buffer, handles, associatedEndpointHandles) {
+ if (associatedEndpointHandles === undefined) {
+ associatedEndpointHandles = [];
+ }
+
+ this.buffer = buffer;
+ this.handles = handles;
+ this.associatedEndpointHandles = associatedEndpointHandles;
+ }
+
+ Message.prototype.getHeaderNumBytes = function() {
+ return this.buffer.getUint32(kStructHeaderNumBytesOffset);
+ };
+
+ Message.prototype.getHeaderVersion = function() {
+ return this.buffer.getUint32(kStructHeaderVersionOffset);
+ };
+
+ Message.prototype.getName = function() {
+ return this.buffer.getUint32(kMessageNameOffset);
+ };
+
+ Message.prototype.getFlags = function() {
+ return this.buffer.getUint32(kMessageFlagsOffset);
+ };
+
+ Message.prototype.getInterfaceId = function() {
+ return this.buffer.getUint32(kMessageInterfaceIdOffset);
+ };
+
+ Message.prototype.getPayloadInterfaceIds = function() {
+ if (this.getHeaderVersion() < 2) {
+ return null;
+ }
+
+ var decoder = new Decoder(this.buffer, this.handles,
+ this.associatedEndpointHandles,
+ kMessagePayloadInterfaceIdsPointerOffset);
+ var payloadInterfaceIds = decoder.decodeArrayPointer(Uint32);
+ return payloadInterfaceIds;
+ };
+
+ Message.prototype.isResponse = function() {
+ return (this.getFlags() & kMessageIsResponse) != 0;
+ };
+
+ Message.prototype.expectsResponse = function() {
+ return (this.getFlags() & kMessageExpectsResponse) != 0;
+ };
+
+ Message.prototype.setRequestID = function(requestID) {
+ // TODO(darin): Verify that space was reserved for this field!
+ this.buffer.setUint64(kMessageRequestIDOffset, requestID);
+ };
+
+ Message.prototype.setInterfaceId = function(interfaceId) {
+ this.buffer.setUint32(kMessageInterfaceIdOffset, interfaceId);
+ };
+
+ Message.prototype.setPayloadInterfaceIds_ = function(payloadInterfaceIds) {
+ if (this.getHeaderVersion() < 2) {
+ throw new Error(
+ "Version of message does not support payload interface ids");
+ }
+
+ var decoder = new Decoder(this.buffer, this.handles,
+ this.associatedEndpointHandles,
+ kMessagePayloadInterfaceIdsPointerOffset);
+ var payloadInterfaceIdsOffset = decoder.decodePointer();
+ var encoder = new Encoder(this.buffer, this.handles,
+ this.associatedEndpointHandles,
+ payloadInterfaceIdsOffset);
+ encoder.encodeArray(Uint32, payloadInterfaceIds);
+ };
+
+ Message.prototype.serializeAssociatedEndpointHandles = function(
+ associatedGroupController) {
+ if (this.associatedEndpointHandles.length > 0) {
+ if (this.getHeaderVersion() < 2) {
+ throw new Error(
+ "Version of message does not support associated endpoint handles");
+ }
+
+ var data = [];
+ for (var i = 0; i < this.associatedEndpointHandles.length; i++) {
+ var handle = this.associatedEndpointHandles[i];
+ data.push(associatedGroupController.associateInterface(handle));
+ }
+ this.associatedEndpointHandles = [];
+ this.setPayloadInterfaceIds_(data);
+ }
+ };
+
+ Message.prototype.deserializeAssociatedEndpointHandles = function(
+ associatedGroupController) {
+ if (this.getHeaderVersion() < 2) {
+ return true;
+ }
+
+ this.associatedEndpointHandles = [];
+ var ids = this.getPayloadInterfaceIds();
+
+ var result = true;
+ for (var i = 0; i < ids.length; i++) {
+ var handle = associatedGroupController.createLocalEndpointHandle(ids[i]);
+ if (internal.isValidInterfaceId(ids[i]) && !handle.isValid()) {
+ // |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;
+ }
+ this.associatedEndpointHandles.push(handle);
+ ids[i] = internal.kInvalidInterfaceId;
+ }
+
+ this.setPayloadInterfaceIds_(ids);
+ return result;
+ };
+
+
+ // MessageV0Builder ---------------------------------------------------------
+
+ function MessageV0Builder(messageName, payloadSize) {
+ // Currently, we don't compute the payload size correctly ahead of time.
+ // Instead, we resize the buffer at the end.
+ var numberOfBytes = kMessageV0HeaderSize + payloadSize;
+ this.buffer = new internal.Buffer(numberOfBytes);
+ this.handles = [];
+ var encoder = this.createEncoder(kMessageV0HeaderSize);
+ encoder.writeUint32(kMessageV0HeaderSize);
+ encoder.writeUint32(0); // version.
+ encoder.writeUint32(0); // interface ID.
+ encoder.writeUint32(messageName);
+ encoder.writeUint32(0); // flags.
+ encoder.writeUint32(0); // padding.
+ }
+
+ MessageV0Builder.prototype.createEncoder = function(size) {
+ var pointer = this.buffer.alloc(size);
+ return new Encoder(this.buffer, this.handles, [], pointer);
+ };
+
+ MessageV0Builder.prototype.encodeStruct = function(cls, val) {
+ cls.encode(this.createEncoder(cls.encodedSize), val);
+ };
+
+ MessageV0Builder.prototype.finish = function() {
+ // TODO(abarth): Rather than resizing the buffer at the end, we could
+ // compute the size we need ahead of time, like we do in C++.
+ this.buffer.trim();
+ var message = new Message(this.buffer, this.handles);
+ this.buffer = null;
+ this.handles = null;
+ this.encoder = null;
+ return message;
+ };
+
+ // MessageV1Builder -----------------------------------------------
+
+ function MessageV1Builder(messageName, payloadSize, flags,
+ requestID) {
+ // Currently, we don't compute the payload size correctly ahead of time.
+ // Instead, we resize the buffer at the end.
+ var numberOfBytes = kMessageV1HeaderSize + payloadSize;
+ this.buffer = new internal.Buffer(numberOfBytes);
+ this.handles = [];
+ var encoder = this.createEncoder(kMessageV1HeaderSize);
+ encoder.writeUint32(kMessageV1HeaderSize);
+ encoder.writeUint32(1); // version.
+ encoder.writeUint32(0); // interface ID.
+ encoder.writeUint32(messageName);
+ encoder.writeUint32(flags);
+ encoder.writeUint32(0); // padding.
+ encoder.writeUint64(requestID);
+ }
+
+ MessageV1Builder.prototype =
+ Object.create(MessageV0Builder.prototype);
+
+ MessageV1Builder.prototype.constructor =
+ MessageV1Builder;
+
+ // MessageV2 -----------------------------------------------
+
+ function MessageV2Builder(messageName, payloadSize, flags, requestID) {
+ // Currently, we don't compute the payload size correctly ahead of time.
+ // Instead, we resize the buffer at the end.
+ var numberOfBytes = kMessageV2HeaderSize + payloadSize;
+ this.buffer = new internal.Buffer(numberOfBytes);
+ this.handles = [];
+
+ this.payload = null;
+ this.associatedEndpointHandles = [];
+
+ this.encoder = this.createEncoder(kMessageV2HeaderSize);
+ this.encoder.writeUint32(kMessageV2HeaderSize);
+ this.encoder.writeUint32(2); // version.
+ // Gets set to an appropriate interfaceId for the endpoint by the Router.
+ this.encoder.writeUint32(0); // interface ID.
+ this.encoder.writeUint32(messageName);
+ this.encoder.writeUint32(flags);
+ this.encoder.writeUint32(0); // padding.
+ this.encoder.writeUint64(requestID);
+ }
+
+ MessageV2Builder.prototype.createEncoder = function(size) {
+ var pointer = this.buffer.alloc(size);
+ return new Encoder(this.buffer, this.handles,
+ this.associatedEndpointHandles, pointer);
+ };
+
+ MessageV2Builder.prototype.setPayload = function(cls, val) {
+ this.payload = {cls: cls, val: val};
+ };
+
+ MessageV2Builder.prototype.finish = function() {
+ if (!this.payload) {
+ throw new Error("Payload needs to be set before calling finish");
+ }
+
+ this.encoder.encodeStructPointer(this.payload.cls, this.payload.val);
+ this.encoder.encodeArrayPointer(Uint32,
+ new Array(this.associatedEndpointHandles.length));
+
+ this.buffer.trim();
+ var message = new Message(this.buffer, this.handles,
+ this.associatedEndpointHandles);
+ this.buffer = null;
+ this.handles = null;
+ this.encoder = null;
+ this.payload = null;
+ this.associatedEndpointHandles = null;
+
+ return message;
+ };
+
+ // MessageReader ------------------------------------------------------------
+
+ function MessageReader(message) {
+ this.decoder = new Decoder(message.buffer, message.handles,
+ message.associatedEndpointHandles, 0);
+ var messageHeaderSize = this.decoder.readUint32();
+ this.payloadSize = message.buffer.byteLength - messageHeaderSize;
+ var version = this.decoder.readUint32();
+ var interface_id = this.decoder.readUint32();
+ this.messageName = this.decoder.readUint32();
+ this.flags = this.decoder.readUint32();
+ // Skip the padding.
+ this.decoder.skip(4);
+ if (version >= 1)
+ this.requestID = this.decoder.readUint64();
+ this.decoder.skip(messageHeaderSize - this.decoder.next);
+ }
+
+ MessageReader.prototype.decodeStruct = function(cls) {
+ return cls.decode(this.decoder);
+ };
+
+ // Built-in types -----------------------------------------------------------
+
+ // This type is only used with ArrayOf(PackedBool).
+ function PackedBool() {
+ }
+
+ function Int8() {
+ }
+
+ Int8.encodedSize = 1;
+
+ Int8.decode = function(decoder) {
+ return decoder.readInt8();
+ };
+
+ Int8.encode = function(encoder, val) {
+ encoder.writeInt8(val);
+ };
+
+ Uint8.encode = function(encoder, val) {
+ encoder.writeUint8(val);
+ };
+
+ function Uint8() {
+ }
+
+ Uint8.encodedSize = 1;
+
+ Uint8.decode = function(decoder) {
+ return decoder.readUint8();
+ };
+
+ Uint8.encode = function(encoder, val) {
+ encoder.writeUint8(val);
+ };
+
+ function Int16() {
+ }
+
+ Int16.encodedSize = 2;
+
+ Int16.decode = function(decoder) {
+ return decoder.readInt16();
+ };
+
+ Int16.encode = function(encoder, val) {
+ encoder.writeInt16(val);
+ };
+
+ function Uint16() {
+ }
+
+ Uint16.encodedSize = 2;
+
+ Uint16.decode = function(decoder) {
+ return decoder.readUint16();
+ };
+
+ Uint16.encode = function(encoder, val) {
+ encoder.writeUint16(val);
+ };
+
+ function Int32() {
+ }
+
+ Int32.encodedSize = 4;
+
+ Int32.decode = function(decoder) {
+ return decoder.readInt32();
+ };
+
+ Int32.encode = function(encoder, val) {
+ encoder.writeInt32(val);
+ };
+
+ function Uint32() {
+ }
+
+ Uint32.encodedSize = 4;
+
+ Uint32.decode = function(decoder) {
+ return decoder.readUint32();
+ };
+
+ Uint32.encode = function(encoder, val) {
+ encoder.writeUint32(val);
+ };
+
+ function Int64() {
+ }
+
+ Int64.encodedSize = 8;
+
+ Int64.decode = function(decoder) {
+ return decoder.readInt64();
+ };
+
+ Int64.encode = function(encoder, val) {
+ encoder.writeInt64(val);
+ };
+
+ function Uint64() {
+ }
+
+ Uint64.encodedSize = 8;
+
+ Uint64.decode = function(decoder) {
+ return decoder.readUint64();
+ };
+
+ Uint64.encode = function(encoder, val) {
+ encoder.writeUint64(val);
+ };
+
+ function String() {
+ };
+
+ String.encodedSize = 8;
+
+ String.decode = function(decoder) {
+ return decoder.decodeStringPointer();
+ };
+
+ String.encode = function(encoder, val) {
+ encoder.encodeStringPointer(val);
+ };
+
+ function NullableString() {
+ }
+
+ NullableString.encodedSize = String.encodedSize;
+
+ NullableString.decode = String.decode;
+
+ NullableString.encode = String.encode;
+
+ function Float() {
+ }
+
+ Float.encodedSize = 4;
+
+ Float.decode = function(decoder) {
+ return decoder.readFloat();
+ };
+
+ Float.encode = function(encoder, val) {
+ encoder.writeFloat(val);
+ };
+
+ function Double() {
+ }
+
+ Double.encodedSize = 8;
+
+ Double.decode = function(decoder) {
+ return decoder.readDouble();
+ };
+
+ Double.encode = function(encoder, val) {
+ encoder.writeDouble(val);
+ };
+
+ function Enum(cls) {
+ this.cls = cls;
+ }
+
+ Enum.prototype.encodedSize = 4;
+
+ Enum.prototype.decode = function(decoder) {
+ return decoder.readInt32();
+ };
+
+ Enum.prototype.encode = function(encoder, val) {
+ encoder.writeInt32(val);
+ };
+
+ function PointerTo(cls) {
+ this.cls = cls;
+ }
+
+ PointerTo.prototype.encodedSize = 8;
+
+ PointerTo.prototype.decode = function(decoder) {
+ var pointer = decoder.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return this.cls.decode(decoder.decodeAndCreateDecoder(pointer));
+ };
+
+ PointerTo.prototype.encode = function(encoder, val) {
+ if (!val) {
+ encoder.encodePointer(val);
+ return;
+ }
+ var objectEncoder = encoder.createAndEncodeEncoder(this.cls.encodedSize);
+ this.cls.encode(objectEncoder, val);
+ };
+
+ function NullablePointerTo(cls) {
+ PointerTo.call(this, cls);
+ }
+
+ NullablePointerTo.prototype = Object.create(PointerTo.prototype);
+
+ function ArrayOf(cls, length) {
+ this.cls = cls;
+ this.length = length || 0;
+ }
+
+ ArrayOf.prototype.encodedSize = 8;
+
+ ArrayOf.prototype.dimensions = function() {
+ return [this.length].concat(
+ (this.cls instanceof ArrayOf) ? this.cls.dimensions() : []);
+ }
+
+ ArrayOf.prototype.decode = function(decoder) {
+ return decoder.decodeArrayPointer(this.cls);
+ };
+
+ ArrayOf.prototype.encode = function(encoder, val) {
+ encoder.encodeArrayPointer(this.cls, val);
+ };
+
+ function NullableArrayOf(cls) {
+ ArrayOf.call(this, cls);
+ }
+
+ NullableArrayOf.prototype = Object.create(ArrayOf.prototype);
+
+ function Handle() {
+ }
+
+ Handle.encodedSize = 4;
+
+ Handle.decode = function(decoder) {
+ return decoder.decodeHandle();
+ };
+
+ Handle.encode = function(encoder, val) {
+ encoder.encodeHandle(val);
+ };
+
+ function NullableHandle() {
+ }
+
+ NullableHandle.encodedSize = Handle.encodedSize;
+
+ NullableHandle.decode = Handle.decode;
+
+ NullableHandle.encode = Handle.encode;
+
+ function Interface(cls) {
+ this.cls = cls;
+ }
+
+ Interface.prototype.encodedSize = 8;
+
+ Interface.prototype.decode = function(decoder) {
+ var interfacePtrInfo = new mojo.InterfacePtrInfo(
+ decoder.decodeHandle(), decoder.readUint32());
+ var interfacePtr = new this.cls();
+ interfacePtr.ptr.bind(interfacePtrInfo);
+ return interfacePtr;
+ };
+
+ Interface.prototype.encode = function(encoder, val) {
+ var interfacePtrInfo =
+ val ? val.ptr.passInterface() : new mojo.InterfacePtrInfo(null, 0);
+ encoder.encodeHandle(interfacePtrInfo.handle);
+ encoder.writeUint32(interfacePtrInfo.version);
+ };
+
+ function NullableInterface(cls) {
+ Interface.call(this, cls);
+ }
+
+ NullableInterface.prototype = Object.create(Interface.prototype);
+
+ function AssociatedInterfacePtrInfo() {
+ }
+
+ AssociatedInterfacePtrInfo.prototype.encodedSize = 8;
+
+ AssociatedInterfacePtrInfo.decode = function(decoder) {
+ return new mojo.AssociatedInterfacePtrInfo(
+ decoder.decodeAssociatedEndpointHandle(), decoder.readUint32());
+ };
+
+ AssociatedInterfacePtrInfo.encode = function(encoder, val) {
+ var associatedinterfacePtrInfo =
+ val ? val : new mojo.AssociatedInterfacePtrInfo(null, 0);
+ encoder.encodeAssociatedEndpointHandle(
+ associatedinterfacePtrInfo.interfaceEndpointHandle);
+ encoder.writeUint32(associatedinterfacePtrInfo.version);
+ };
+
+ function NullableAssociatedInterfacePtrInfo() {
+ }
+
+ NullableAssociatedInterfacePtrInfo.encodedSize =
+ AssociatedInterfacePtrInfo.encodedSize;
+
+ NullableAssociatedInterfacePtrInfo.decode =
+ AssociatedInterfacePtrInfo.decode;
+
+ NullableAssociatedInterfacePtrInfo.encode =
+ AssociatedInterfacePtrInfo.encode;
+
+ function InterfaceRequest() {
+ }
+
+ InterfaceRequest.encodedSize = 4;
+
+ InterfaceRequest.decode = function(decoder) {
+ return new mojo.InterfaceRequest(decoder.decodeHandle());
+ };
+
+ InterfaceRequest.encode = function(encoder, val) {
+ encoder.encodeHandle(val ? val.handle : null);
+ };
+
+ function NullableInterfaceRequest() {
+ }
+
+ NullableInterfaceRequest.encodedSize = InterfaceRequest.encodedSize;
+
+ NullableInterfaceRequest.decode = InterfaceRequest.decode;
+
+ NullableInterfaceRequest.encode = InterfaceRequest.encode;
+
+ function AssociatedInterfaceRequest() {
+ }
+
+ AssociatedInterfaceRequest.decode = function(decoder) {
+ var handle = decoder.decodeAssociatedEndpointHandle();
+ return new mojo.AssociatedInterfaceRequest(handle);
+ };
+
+ AssociatedInterfaceRequest.encode = function(encoder, val) {
+ encoder.encodeAssociatedEndpointHandle(
+ val ? val.interfaceEndpointHandle : null);
+ };
+
+ AssociatedInterfaceRequest.encodedSize = 4;
+
+ function NullableAssociatedInterfaceRequest() {
+ }
+
+ NullableAssociatedInterfaceRequest.encodedSize =
+ AssociatedInterfaceRequest.encodedSize;
+
+ NullableAssociatedInterfaceRequest.decode =
+ AssociatedInterfaceRequest.decode;
+
+ NullableAssociatedInterfaceRequest.encode =
+ AssociatedInterfaceRequest.encode;
+
+ function MapOf(keyClass, valueClass) {
+ this.keyClass = keyClass;
+ this.valueClass = valueClass;
+ }
+
+ MapOf.prototype.encodedSize = 8;
+
+ MapOf.prototype.decode = function(decoder) {
+ return decoder.decodeMapPointer(this.keyClass, this.valueClass);
+ };
+
+ MapOf.prototype.encode = function(encoder, val) {
+ encoder.encodeMapPointer(this.keyClass, this.valueClass, val);
+ };
+
+ function NullableMapOf(keyClass, valueClass) {
+ MapOf.call(this, keyClass, valueClass);
+ }
+
+ NullableMapOf.prototype = Object.create(MapOf.prototype);
+
+ internal.align = align;
+ internal.isAligned = isAligned;
+ internal.Message = Message;
+ internal.MessageV0Builder = MessageV0Builder;
+ internal.MessageV1Builder = MessageV1Builder;
+ internal.MessageV2Builder = MessageV2Builder;
+ internal.MessageReader = MessageReader;
+ internal.kArrayHeaderSize = kArrayHeaderSize;
+ internal.kMapStructPayloadSize = kMapStructPayloadSize;
+ internal.kStructHeaderSize = kStructHeaderSize;
+ internal.kEncodedInvalidHandleValue = kEncodedInvalidHandleValue;
+ internal.kMessageV0HeaderSize = kMessageV0HeaderSize;
+ internal.kMessageV1HeaderSize = kMessageV1HeaderSize;
+ internal.kMessageV2HeaderSize = kMessageV2HeaderSize;
+ internal.kMessagePayloadInterfaceIdsPointerOffset =
+ kMessagePayloadInterfaceIdsPointerOffset;
+ internal.kMessageExpectsResponse = kMessageExpectsResponse;
+ internal.kMessageIsResponse = kMessageIsResponse;
+ internal.Int8 = Int8;
+ internal.Uint8 = Uint8;
+ internal.Int16 = Int16;
+ internal.Uint16 = Uint16;
+ internal.Int32 = Int32;
+ internal.Uint32 = Uint32;
+ internal.Int64 = Int64;
+ internal.Uint64 = Uint64;
+ internal.Float = Float;
+ internal.Double = Double;
+ internal.String = String;
+ internal.Enum = Enum;
+ internal.NullableString = NullableString;
+ internal.PointerTo = PointerTo;
+ internal.NullablePointerTo = NullablePointerTo;
+ internal.ArrayOf = ArrayOf;
+ internal.NullableArrayOf = NullableArrayOf;
+ internal.PackedBool = PackedBool;
+ internal.Handle = Handle;
+ internal.NullableHandle = NullableHandle;
+ internal.Interface = Interface;
+ internal.NullableInterface = NullableInterface;
+ internal.InterfaceRequest = InterfaceRequest;
+ internal.NullableInterfaceRequest = NullableInterfaceRequest;
+ internal.AssociatedInterfacePtrInfo = AssociatedInterfacePtrInfo;
+ internal.NullableAssociatedInterfacePtrInfo =
+ NullableAssociatedInterfacePtrInfo;
+ internal.AssociatedInterfaceRequest = AssociatedInterfaceRequest;
+ internal.NullableAssociatedInterfaceRequest =
+ NullableAssociatedInterfaceRequest;
+ internal.MapOf = MapOf;
+ internal.NullableMapOf = NullableMapOf;
+})();
diff --git a/mojo/public/js/lib/connector.js b/mojo/public/js/lib/connector.js
new file mode 100644
index 0000000000..2cc2e5545e
--- /dev/null
+++ b/mojo/public/js/lib/connector.js
@@ -0,0 +1,169 @@
+// 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.
+
+(function() {
+ var internal = mojo.internal;
+
+ function Connector(handle) {
+ if (!(handle instanceof MojoHandle))
+ throw new Error("Connector: not a handle " + handle);
+ this.handle_ = handle;
+ this.dropWrites_ = false;
+ this.error_ = false;
+ this.incomingReceiver_ = null;
+ this.readWatcher_ = null;
+ this.errorHandler_ = null;
+ this.paused_ = false;
+
+ this.waitToReadMore();
+ }
+
+ Connector.prototype.close = function() {
+ this.cancelWait();
+ if (this.handle_ != null) {
+ this.handle_.close();
+ this.handle_ = null;
+ }
+ };
+
+ Connector.prototype.pauseIncomingMethodCallProcessing = function() {
+ if (this.paused_) {
+ return;
+ }
+ this.paused_= true;
+ this.cancelWait();
+ };
+
+ Connector.prototype.resumeIncomingMethodCallProcessing = function() {
+ if (!this.paused_) {
+ return;
+ }
+ this.paused_= false;
+ this.waitToReadMore();
+ };
+
+ Connector.prototype.accept = function(message) {
+ if (this.error_)
+ return false;
+
+ if (this.dropWrites_)
+ return true;
+
+ var result = this.handle_.writeMessage(
+ new Uint8Array(message.buffer.arrayBuffer), message.handles);
+ switch (result) {
+ case Mojo.RESULT_OK:
+ // The handles were successfully transferred, so we don't own them
+ // anymore.
+ message.handles = [];
+ 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.
+ this.dropWrites_ = true;
+ break;
+ default:
+ // This particular write was rejected, presumably because of bad input.
+ // The pipe is not necessarily in a bad state.
+ return false;
+ }
+ return true;
+ };
+
+ Connector.prototype.setIncomingReceiver = function(receiver) {
+ this.incomingReceiver_ = receiver;
+ };
+
+ Connector.prototype.setErrorHandler = function(handler) {
+ this.errorHandler_ = handler;
+ };
+
+ Connector.prototype.readMore_ = function(result) {
+ for (;;) {
+ if (this.paused_) {
+ return;
+ }
+
+ var read = this.handle_.readMessage();
+ if (this.handle_ == null) // The connector has been closed.
+ return;
+ if (read.result == Mojo.RESULT_SHOULD_WAIT)
+ return;
+ if (read.result != Mojo.RESULT_OK) {
+ this.handleError(read.result !== Mojo.RESULT_FAILED_PRECONDITION,
+ false);
+ return;
+ }
+ var messageBuffer = new internal.Buffer(read.buffer);
+ var message = new internal.Message(messageBuffer, read.handles);
+ var receiverResult = this.incomingReceiver_ &&
+ this.incomingReceiver_.accept(message);
+
+ // Dispatching the message may have closed the connector.
+ if (this.handle_ == null)
+ return;
+
+ // Handle invalid incoming message.
+ if (!internal.isTestingMode() && !receiverResult) {
+ // TODO(yzshen): Consider notifying the embedder.
+ this.handleError(true, false);
+ }
+ }
+ };
+
+ Connector.prototype.cancelWait = function() {
+ if (this.readWatcher_) {
+ this.readWatcher_.cancel();
+ this.readWatcher_ = null;
+ }
+ };
+
+ Connector.prototype.waitToReadMore = function() {
+ if (this.handle_) {
+ this.readWatcher_ = this.handle_.watch({readable: true},
+ this.readMore_.bind(this));
+ }
+ };
+
+ Connector.prototype.handleError = function(forcePipeReset,
+ forceAsyncHandler) {
+ if (this.error_ || this.handle_ === null) {
+ return;
+ }
+
+ if (this.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.
+ forceAsyncHandler = true;
+ }
+
+ if (!forcePipeReset && forceAsyncHandler) {
+ forcePipeReset = true;
+ }
+
+ this.cancelWait();
+ if (forcePipeReset) {
+ this.handle_.close();
+ var dummyPipe = Mojo.createMessagePipe();
+ this.handle_ = dummyPipe.handle0;
+ }
+
+ if (forceAsyncHandler) {
+ if (!this.paused_) {
+ this.waitToReadMore();
+ }
+ } else {
+ this.error_ = true;
+ if (this.errorHandler_) {
+ this.errorHandler_.onError();
+ }
+ }
+ };
+
+ internal.Connector = Connector;
+})();
diff --git a/mojo/public/js/lib/control_message_handler.js b/mojo/public/js/lib/control_message_handler.js
index 5da306e371..ad8165447e 100644
--- a/mojo/public/js/lib/control_message_handler.js
+++ b/mojo/public/js/lib/control_message_handler.js
@@ -2,110 +2,104 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-define("mojo/public/js/lib/control_message_handler", [
- "mojo/public/interfaces/bindings/interface_control_messages.mojom",
- "mojo/public/js/codec",
- "mojo/public/js/validator",
-], function(controlMessages, codec, validator) {
-
- var Validator = validator.Validator;
+(function() {
+ var internal = mojo.internal;
function validateControlRequestWithResponse(message) {
- var messageValidator = new Validator(message);
+ var messageValidator = new internal.Validator(message);
var error = messageValidator.validateMessageIsRequestExpectingResponse();
- if (error !== validator.validationError.NONE) {
+ if (error !== internal.validationError.NONE) {
throw error;
}
- if (message.getName() != controlMessages.kRunMessageId) {
+ if (message.getName() != mojo.interfaceControl.kRunMessageId) {
throw new Error("Control message name is not kRunMessageId");
}
// Validate payload.
- error = controlMessages.RunMessageParams.validate(messageValidator,
+ error = mojo.interfaceControl.RunMessageParams.validate(messageValidator,
message.getHeaderNumBytes());
- if (error != validator.validationError.NONE) {
+ if (error != internal.validationError.NONE) {
throw error;
}
}
function validateControlRequestWithoutResponse(message) {
- var messageValidator = new Validator(message);
+ var messageValidator = new internal.Validator(message);
var error = messageValidator.validateMessageIsRequestWithoutResponse();
- if (error != validator.validationError.NONE) {
+ if (error != internal.validationError.NONE) {
throw error;
}
- if (message.getName() != controlMessages.kRunOrClosePipeMessageId) {
+ if (message.getName() != mojo.interfaceControl.kRunOrClosePipeMessageId) {
throw new Error("Control message name is not kRunOrClosePipeMessageId");
}
// Validate payload.
- error = controlMessages.RunOrClosePipeMessageParams.validate(
+ error = mojo.interfaceControl.RunOrClosePipeMessageParams.validate(
messageValidator, message.getHeaderNumBytes());
- if (error != validator.validationError.NONE) {
+ if (error != internal.validationError.NONE) {
throw error;
}
}
- function runOrClosePipe(message, interface_version) {
- var reader = new codec.MessageReader(message);
+ function runOrClosePipe(message, interfaceVersion) {
+ var reader = new internal.MessageReader(message);
var runOrClosePipeMessageParams = reader.decodeStruct(
- controlMessages.RunOrClosePipeMessageParams);
- return interface_version >=
- runOrClosePipeMessageParams.input.require_version.version;
+ mojo.interfaceControl.RunOrClosePipeMessageParams);
+ return interfaceVersion >=
+ runOrClosePipeMessageParams.input.requireVersion.version;
}
- function run(message, responder, interface_version) {
- var reader = new codec.MessageReader(message);
+ function run(message, responder, interfaceVersion) {
+ var reader = new internal.MessageReader(message);
var runMessageParams =
- reader.decodeStruct(controlMessages.RunMessageParams);
+ reader.decodeStruct(mojo.interfaceControl.RunMessageParams);
var runOutput = null;
- if (runMessageParams.input.query_version) {
- runOutput = new controlMessages.RunOutput();
- runOutput.query_version_result = new
- controlMessages.QueryVersionResult({'version': interface_version});
+ if (runMessageParams.input.queryVersion) {
+ runOutput = new mojo.interfaceControl.RunOutput();
+ runOutput.queryVersionResult = new
+ mojo.interfaceControl.QueryVersionResult(
+ {'version': interfaceVersion});
}
var runResponseMessageParams = new
- controlMessages.RunResponseMessageParams();
+ mojo.interfaceControl.RunResponseMessageParams();
runResponseMessageParams.output = runOutput;
- var messageName = controlMessages.kRunMessageId;
- var payloadSize = controlMessages.RunResponseMessageParams.encodedSize;
+ var messageName = mojo.interfaceControl.kRunMessageId;
+ var payloadSize =
+ mojo.interfaceControl.RunResponseMessageParams.encodedSize;
var requestID = reader.requestID;
- var builder = new codec.MessageWithRequestIDBuilder(messageName,
- payloadSize, codec.kMessageIsResponse, requestID);
- builder.encodeStruct(controlMessages.RunResponseMessageParams,
+ var builder = new internal.MessageV1Builder(messageName,
+ payloadSize, internal.kMessageIsResponse, requestID);
+ builder.encodeStruct(mojo.interfaceControl.RunResponseMessageParams,
runResponseMessageParams);
responder.accept(builder.finish());
return true;
}
- function isControlMessage(message) {
- return message.getName() == controlMessages.kRunMessageId ||
- message.getName() == controlMessages.kRunOrClosePipeMessageId;
+ function isInterfaceControlMessage(message) {
+ return message.getName() == mojo.interfaceControl.kRunMessageId ||
+ message.getName() == mojo.interfaceControl.kRunOrClosePipeMessageId;
}
- function ControlMessageHandler(interface_version) {
- this.interface_version_ = interface_version;
+ function ControlMessageHandler(interfaceVersion) {
+ this.interfaceVersion_ = interfaceVersion;
}
ControlMessageHandler.prototype.accept = function(message) {
validateControlRequestWithoutResponse(message);
- return runOrClosePipe(message, this.interface_version_);
+ return runOrClosePipe(message, this.interfaceVersion_);
};
ControlMessageHandler.prototype.acceptWithResponder = function(message,
responder) {
validateControlRequestWithResponse(message);
- return run(message, responder, this.interface_version_);
+ return run(message, responder, this.interfaceVersion_);
};
- var exports = {};
- exports.ControlMessageHandler = ControlMessageHandler;
- exports.isControlMessage = isControlMessage;
-
- return exports;
-});
+ internal.ControlMessageHandler = ControlMessageHandler;
+ internal.isInterfaceControlMessage = isInterfaceControlMessage;
+})();
diff --git a/mojo/public/js/lib/control_message_proxy.js b/mojo/public/js/lib/control_message_proxy.js
index b6f1d3c83c..c8447cded1 100644
--- a/mojo/public/js/lib/control_message_proxy.js
+++ b/mojo/public/js/lib/control_message_proxy.js
@@ -2,43 +2,39 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-define("mojo/public/js/lib/control_message_proxy", [
- "mojo/public/interfaces/bindings/interface_control_messages.mojom",
- "mojo/public/js/codec",
- "mojo/public/js/validator",
-], function(controlMessages, codec, validator) {
-
- var Validator = validator.Validator;
+(function() {
+ var internal = mojo.internal;
function constructRunOrClosePipeMessage(runOrClosePipeInput) {
var runOrClosePipeMessageParams = new
- controlMessages.RunOrClosePipeMessageParams();
+ mojo.interfaceControl.RunOrClosePipeMessageParams();
runOrClosePipeMessageParams.input = runOrClosePipeInput;
- var messageName = controlMessages.kRunOrClosePipeMessageId;
- var payloadSize = controlMessages.RunOrClosePipeMessageParams.encodedSize;
- var builder = new codec.MessageBuilder(messageName, payloadSize);
- builder.encodeStruct(controlMessages.RunOrClosePipeMessageParams,
+ var messageName = mojo.interfaceControl.kRunOrClosePipeMessageId;
+ var payloadSize =
+ mojo.interfaceControl.RunOrClosePipeMessageParams.encodedSize;
+ var builder = new internal.MessageV0Builder(messageName, payloadSize);
+ builder.encodeStruct(mojo.interfaceControl.RunOrClosePipeMessageParams,
runOrClosePipeMessageParams);
var message = builder.finish();
return message;
}
function validateControlResponse(message) {
- var messageValidator = new Validator(message);
+ var messageValidator = new internal.Validator(message);
var error = messageValidator.validateMessageIsResponse();
- if (error != validator.validationError.NONE) {
+ if (error != internal.validationError.NONE) {
throw error;
}
- if (message.getName() != controlMessages.kRunMessageId) {
+ if (message.getName() != mojo.interfaceControl.kRunMessageId) {
throw new Error("Control message name is not kRunMessageId");
}
// Validate payload.
- error = controlMessages.RunResponseMessageParams.validate(
+ error = mojo.interfaceControl.RunResponseMessageParams.validate(
messageValidator, message.getHeaderNumBytes());
- if (error != validator.validationError.NONE) {
+ if (error != internal.validationError.NONE) {
throw error;
}
}
@@ -46,9 +42,9 @@ define("mojo/public/js/lib/control_message_proxy", [
function acceptRunResponse(message) {
validateControlResponse(message);
- var reader = new codec.MessageReader(message);
+ var reader = new internal.MessageReader(message);
var runResponseMessageParams = reader.decodeStruct(
- controlMessages.RunResponseMessageParams);
+ mojo.interfaceControl.RunResponseMessageParams);
return Promise.resolve(runResponseMessageParams);
}
@@ -63,12 +59,13 @@ define("mojo/public/js/lib/control_message_proxy", [
* @return {Promise} that resolves to a RunResponseMessageParams.
*/
function sendRunMessage(receiver, runMessageParams) {
- var messageName = controlMessages.kRunMessageId;
- var payloadSize = controlMessages.RunMessageParams.encodedSize;
+ var messageName = mojo.interfaceControl.kRunMessageId;
+ var payloadSize = mojo.interfaceControl.RunMessageParams.encodedSize;
// |requestID| is set to 0, but is later properly set by Router.
- var builder = new codec.MessageWithRequestIDBuilder(messageName,
- payloadSize, codec.kMessageExpectsResponse, 0);
- builder.encodeStruct(controlMessages.RunMessageParams, runMessageParams);
+ var builder = new internal.MessageV1Builder(messageName,
+ payloadSize, internal.kMessageExpectsResponse, 0);
+ builder.encodeStruct(mojo.interfaceControl.RunMessageParams,
+ runMessageParams);
var message = builder.finish();
return receiver.acceptAndExpectResponse(message).then(acceptRunResponse);
@@ -79,26 +76,24 @@ define("mojo/public/js/lib/control_message_proxy", [
}
ControlMessageProxy.prototype.queryVersion = function() {
- var runMessageParams = new controlMessages.RunMessageParams();
- runMessageParams.input = new controlMessages.RunInput();
- runMessageParams.input.query_version = new controlMessages.QueryVersion();
+ var runMessageParams = new mojo.interfaceControl.RunMessageParams();
+ runMessageParams.input = new mojo.interfaceControl.RunInput();
+ runMessageParams.input.queryVersion =
+ new mojo.interfaceControl.QueryVersion();
return sendRunMessage(this.receiver_, runMessageParams).then(function(
runResponseMessageParams) {
- return runResponseMessageParams.output.query_version_result.version;
+ return runResponseMessageParams.output.queryVersionResult.version;
});
};
ControlMessageProxy.prototype.requireVersion = function(version) {
- var runOrClosePipeInput = new controlMessages.RunOrClosePipeInput();
- runOrClosePipeInput.require_version = new controlMessages.RequireVersion({
- 'version': version});
+ var runOrClosePipeInput = new mojo.interfaceControl.RunOrClosePipeInput();
+ runOrClosePipeInput.requireVersion =
+ new mojo.interfaceControl.RequireVersion({'version': version});
var message = constructRunOrClosePipeMessage(runOrClosePipeInput);
this.receiver_.accept(message);
};
- var exports = {};
- exports.ControlMessageProxy = ControlMessageProxy;
-
- return exports;
-});
+ internal.ControlMessageProxy = ControlMessageProxy;
+})();
diff --git a/mojo/public/js/lib/interface_endpoint_client.js b/mojo/public/js/lib/interface_endpoint_client.js
index 631c52ec91..9b45b69b05 100644
--- a/mojo/public/js/lib/interface_endpoint_client.js
+++ b/mojo/public/js/lib/interface_endpoint_client.js
@@ -2,27 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-define("mojo/public/js/lib/interface_endpoint_client", [
- "console",
- "mojo/public/js/codec",
- "mojo/public/js/lib/control_message_handler",
- "mojo/public/js/lib/control_message_proxy",
- "mojo/public/js/lib/interface_endpoint_handle",
- "mojo/public/js/validator",
- "timer",
-], function(console,
- codec,
- controlMessageHandler,
- controlMessageProxy,
- interfaceEndpointHandle,
- validator,
- timer) {
-
- var ControlMessageHandler = controlMessageHandler.ControlMessageHandler;
- var ControlMessageProxy = controlMessageProxy.ControlMessageProxy;
- var MessageReader = codec.MessageReader;
- var Validator = validator.Validator;
- var InterfaceEndpointHandle = interfaceEndpointHandle.InterfaceEndpointHandle;
+(function() {
+ var internal = mojo.internal;
function InterfaceEndpointClient(interfaceEndpointHandle, receiver,
interfaceVersion) {
@@ -32,10 +13,10 @@ define("mojo/public/js/lib/interface_endpoint_client", [
this.incomingReceiver_ = receiver;
if (interfaceVersion !== undefined) {
- this.controlMessageHandler_ = new ControlMessageHandler(
+ this.controlMessageHandler_ = new internal.ControlMessageHandler(
interfaceVersion);
} else {
- this.controlMessageProxy_ = new ControlMessageProxy(this);
+ this.controlMessageProxy_ = new internal.ControlMessageProxy(this);
}
this.nextRequestID_ = 0;
@@ -62,20 +43,18 @@ define("mojo/public/js/lib/interface_endpoint_client", [
InterfaceEndpointClient.prototype.onAssociationEvent = function(
associationEvent) {
- if (associationEvent ===
- InterfaceEndpointHandle.AssociationEvent.ASSOCIATED) {
+ if (associationEvent === internal.AssociationEvent.ASSOCIATED) {
this.initControllerIfNecessary_();
} else if (associationEvent ===
- InterfaceEndpointHandle.AssociationEvent
- .PEER_CLOSED_BEFORE_ASSOCIATION) {
- timer.createOneShot(0, this.notifyError.bind(this,
- this.handle_.disconnectReason()));
+ internal.AssociationEvent.PEER_CLOSED_BEFORE_ASSOCIATION) {
+ setTimeout(this.notifyError.bind(this, this.handle_.disconnectReason()),
+ 0);
}
};
InterfaceEndpointClient.prototype.passHandle = function() {
if (!this.handle_.isValid()) {
- return new InterfaceEndpointHandle();
+ return new internal.InterfaceEndpointHandle();
}
// Used to clear the previously set callback.
@@ -96,6 +75,11 @@ define("mojo/public/js/lib/interface_endpoint_client", [
};
InterfaceEndpointClient.prototype.accept = function(message) {
+ if (message.associatedEndpointHandles.length > 0) {
+ message.serializeAssociatedEndpointHandles(
+ this.handle_.groupController());
+ }
+
if (this.encounteredError_) {
return false;
}
@@ -106,6 +90,11 @@ define("mojo/public/js/lib/interface_endpoint_client", [
InterfaceEndpointClient.prototype.acceptAndExpectResponse = function(
message) {
+ if (message.associatedEndpointHandles.length > 0) {
+ message.serializeAssociatedEndpointHandles(
+ this.handle_.groupController());
+ }
+
if (this.encounteredError_) {
return Promise.reject();
}
@@ -144,10 +133,9 @@ define("mojo/public/js/lib/interface_endpoint_client", [
this.connectionErrorHandler_ = handler;
};
- InterfaceEndpointClient.prototype.handleIncomingMessage_ = function(
- message) {
- var noError = validator.validationError.NONE;
- var messageValidator = new Validator(message);
+ InterfaceEndpointClient.prototype.handleIncomingMessage = function(message,
+ messageValidator) {
+ var noError = internal.validationError.NONE;
var err = noError;
for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i)
err = this.payloadValidators_[i](messageValidator);
@@ -155,14 +143,14 @@ define("mojo/public/js/lib/interface_endpoint_client", [
if (err == noError) {
return this.handleValidIncomingMessage_(message);
} else {
- validator.reportValidationError(err);
+ internal.reportValidationError(err);
return false;
}
};
InterfaceEndpointClient.prototype.handleValidIncomingMessage_ = function(
message) {
- if (validator.isTestingMode()) {
+ if (internal.isTestingMode()) {
return true;
}
@@ -173,14 +161,14 @@ define("mojo/public/js/lib/interface_endpoint_client", [
var ok = false;
if (message.expectsResponse()) {
- if (controlMessageHandler.isControlMessage(message) &&
+ if (internal.isInterfaceControlMessage(message) &&
this.controlMessageHandler_) {
ok = this.controlMessageHandler_.acceptWithResponder(message, this);
} else if (this.incomingReceiver_) {
ok = this.incomingReceiver_.acceptWithResponder(message, this);
}
} else if (message.isResponse()) {
- var reader = new MessageReader(message);
+ var reader = new internal.MessageReader(message);
var requestID = reader.requestID;
var completer = this.completers_.get(requestID);
if (completer) {
@@ -191,7 +179,7 @@ define("mojo/public/js/lib/interface_endpoint_client", [
console.log("Unexpected response with request ID: " + requestID);
}
} else {
- if (controlMessageHandler.isControlMessage(message) &&
+ if (internal.isInterfaceControlMessage(message) &&
this.controlMessageHandler_) {
ok = this.controlMessageHandler_.accept(message);
} else if (this.incomingReceiver_) {
@@ -225,8 +213,9 @@ define("mojo/public/js/lib/interface_endpoint_client", [
this.controlMessageProxy_.requireVersion(version);
};
- var exports = {};
- exports.InterfaceEndpointClient = InterfaceEndpointClient;
+ InterfaceEndpointClient.prototype.getEncounteredError = function() {
+ return this.encounteredError_;
+ };
- return exports;
-});
+ internal.InterfaceEndpointClient = InterfaceEndpointClient;
+})();
diff --git a/mojo/public/js/lib/interface_endpoint_handle.js b/mojo/public/js/lib/interface_endpoint_handle.js
index f48b89ba85..65d7baef7f 100644
--- a/mojo/public/js/lib/interface_endpoint_handle.js
+++ b/mojo/public/js/lib/interface_endpoint_handle.js
@@ -2,10 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-define("mojo/public/js/lib/interface_endpoint_handle", [
- "mojo/public/js/interface_types",
- "timer",
-], function(types, timer) {
+(function() {
+ var internal = mojo.internal;
var AssociationEvent = {
// The interface has been associated with a message pipe.
@@ -16,7 +14,7 @@ define("mojo/public/js/lib/interface_endpoint_handle", [
function State(interfaceId, associatedGroupController) {
if (interfaceId === undefined) {
- interfaceId = types.kInvalidInterfaceId;
+ interfaceId = internal.kInvalidInterfaceId;
}
this.interfaceId = interfaceId;
@@ -34,20 +32,20 @@ define("mojo/public/js/lib/interface_endpoint_handle", [
State.prototype.isValid = function() {
return this.pendingAssociation ||
- types.isValidInterfaceId(this.interfaceId);
+ internal.isValidInterfaceId(this.interfaceId);
};
State.prototype.close = function(disconnectReason) {
var cachedGroupController;
var cachedPeerState;
- var cachedId = types.kInvalidInterfaceId;
+ var cachedId = internal.kInvalidInterfaceId;
if (!this.pendingAssociation) {
- if (types.isValidInterfaceId(this.interfaceId)) {
+ if (internal.isValidInterfaceId(this.interfaceId)) {
cachedGroupController = this.associatedGroupController;
this.associatedGroupController = null;
cachedId = this.interfaceId;
- this.interfaceId = types.kInvalidInterfaceId;
+ this.interfaceId = internal.kInvalidInterfaceId;
}
} else {
this.pendingAssociation = false;
@@ -73,7 +71,7 @@ define("mojo/public/js/lib/interface_endpoint_handle", [
State.prototype.setAssociationEventHandler = function(handler) {
if (!this.pendingAssociation &&
- !types.isValidInterfaceId(this.interfaceId)) {
+ !internal.isValidInterfaceId(this.interfaceId)) {
return;
}
@@ -84,14 +82,28 @@ define("mojo/public/js/lib/interface_endpoint_handle", [
this.associationEventHandler_ = handler;
if (!this.pendingAssociation) {
- timer.createOneShot(0, this.runAssociationEventHandler.bind(this,
- AssociationEvent.ASSOCIATED));
+ setTimeout(this.runAssociationEventHandler.bind(this,
+ AssociationEvent.ASSOCIATED), 0);
} else if (!this.peerState_) {
- timer.createOneShot(0, this.runAssociationEventHandler.bind(this,
- AssociationEvent.PEER_CLOSED_BEFORE_ASSOCIATION));
+ setTimeout(this.runAssociationEventHandler.bind(this,
+ AssociationEvent.PEER_CLOSED_BEFORE_ASSOCIATION), 0);
}
};
+ State.prototype.notifyAssociation = function(interfaceId,
+ peerGroupController) {
+ var cachedPeerState = this.peerState_;
+ this.peerState_ = null;
+
+ this.pendingAssociation = false;
+
+ if (cachedPeerState) {
+ cachedPeerState.onAssociated(interfaceId, peerGroupController);
+ return true;
+ }
+ return false;
+ };
+
State.prototype.onAssociated = function(interfaceId,
associatedGroupController) {
if (!this.pendingAssociation) {
@@ -117,6 +129,14 @@ define("mojo/public/js/lib/interface_endpoint_handle", [
AssociationEvent.PEER_CLOSED_BEFORE_ASSOCIATION);
};
+ function createPairPendingAssociation() {
+ var handle0 = new InterfaceEndpointHandle();
+ var handle1 = new InterfaceEndpointHandle();
+ handle0.state_.initPendingState(handle1.state_);
+ handle1.state_.initPendingState(handle0.state_);
+ return {handle0: handle0, handle1: handle1};
+ }
+
function InterfaceEndpointHandle(interfaceId, associatedGroupController) {
this.state_ = new State(interfaceId, associatedGroupController);
}
@@ -146,13 +166,17 @@ define("mojo/public/js/lib/interface_endpoint_handle", [
this.state_.setAssociationEventHandler(handler);
};
+ InterfaceEndpointHandle.prototype.notifyAssociation = function(interfaceId,
+ peerGroupController) {
+ return this.state_.notifyAssociation(interfaceId, peerGroupController);
+ };
+
InterfaceEndpointHandle.prototype.reset = function(reason) {
this.state_.close(reason);
this.state_ = new State();
};
- var exports = {};
- exports.InterfaceEndpointHandle = InterfaceEndpointHandle;
-
- return exports;
-});
+ internal.AssociationEvent = AssociationEvent;
+ internal.InterfaceEndpointHandle = InterfaceEndpointHandle;
+ internal.createPairPendingAssociation = createPairPendingAssociation;
+})();
diff --git a/mojo/public/js/lib/pipe_control_message_handler.js b/mojo/public/js/lib/pipe_control_message_handler.js
index 2eb45d1626..f395f22b45 100644
--- a/mojo/public/js/lib/pipe_control_message_handler.js
+++ b/mojo/public/js/lib/pipe_control_message_handler.js
@@ -2,46 +2,40 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-define("mojo/public/js/lib/pipe_control_message_handler", [
- "mojo/public/interfaces/bindings/pipe_control_messages.mojom",
- "mojo/public/js/codec",
- "mojo/public/js/interface_types",
- "mojo/public/js/validator",
-], function(pipeControlMessages, codec, types, validator) {
-
- var Validator = validator.Validator;
+(function() {
+ var internal = mojo.internal;
function validateControlRequestWithoutResponse(message) {
- var messageValidator = new Validator(message);
+ var messageValidator = new internal.Validator(message);
var error = messageValidator.validateMessageIsRequestWithoutResponse();
- if (error != validator.validationError.NONE) {
+ if (error != internal.validationError.NONE) {
throw error;
}
- if (message.getName() != pipeControlMessages.kRunOrClosePipeMessageId) {
+ if (message.getName() != mojo.pipeControl.kRunOrClosePipeMessageId) {
throw new Error("Control message name is not kRunOrClosePipeMessageId");
}
// Validate payload.
- error = pipeControlMessages.RunOrClosePipeMessageParams.validate(
+ error = mojo.pipeControl.RunOrClosePipeMessageParams.validate(
messageValidator, message.getHeaderNumBytes());
- if (error != validator.validationError.NONE) {
+ if (error != internal.validationError.NONE) {
throw error;
}
}
function runOrClosePipe(message, delegate) {
- var reader = new codec.MessageReader(message);
+ var reader = new internal.MessageReader(message);
var runOrClosePipeMessageParams = reader.decodeStruct(
- pipeControlMessages.RunOrClosePipeMessageParams);
+ mojo.pipeControl.RunOrClosePipeMessageParams);
var event = runOrClosePipeMessageParams.input
- .peer_associated_endpoint_closed_event;
+ .peerAssociatedEndpointClosedEvent;
return delegate.onPeerAssociatedEndpointClosed(event.id,
- event.disconnect_reason);
+ event.disconnectReason);
}
function isPipeControlMessage(message) {
- return !types.isValidInterfaceId(message.getInterfaceId());
+ return !internal.isValidInterfaceId(message.getInterfaceId());
}
function PipeControlMessageHandler(delegate) {
@@ -53,9 +47,6 @@ define("mojo/public/js/lib/pipe_control_message_handler", [
return runOrClosePipe(message, this.delegate_);
};
- var exports = {};
- exports.PipeControlMessageHandler = PipeControlMessageHandler;
- exports.isPipeControlMessage = isPipeControlMessage;
-
- return exports;
-});
+ internal.PipeControlMessageHandler = PipeControlMessageHandler;
+ internal.isPipeControlMessage = isPipeControlMessage;
+})();
diff --git a/mojo/public/js/lib/pipe_control_message_proxy.js b/mojo/public/js/lib/pipe_control_message_proxy.js
index 4b8e7a20ea..6e3d5cace8 100644
--- a/mojo/public/js/lib/pipe_control_message_proxy.js
+++ b/mojo/public/js/lib/pipe_control_message_proxy.js
@@ -2,26 +2,23 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-define("mojo/public/js/lib/pipe_control_message_proxy", [
- "mojo/public/interfaces/bindings/pipe_control_messages.mojom",
- "mojo/public/js/codec",
- "mojo/public/js/interface_types",
-], function(pipeControlMessages, codec, types) {
+(function() {
+ var internal = mojo.internal;
function constructRunOrClosePipeMessage(runOrClosePipeInput) {
var runOrClosePipeMessageParams = new
- pipeControlMessages.RunOrClosePipeMessageParams();
+ mojo.pipeControl.RunOrClosePipeMessageParams();
runOrClosePipeMessageParams.input = runOrClosePipeInput;
- var messageName = pipeControlMessages.kRunOrClosePipeMessageId;
+ var messageName = mojo.pipeControl.kRunOrClosePipeMessageId;
var payloadSize =
- pipeControlMessages.RunOrClosePipeMessageParams.encodedSize;
+ mojo.pipeControl.RunOrClosePipeMessageParams.encodedSize;
- var builder = new codec.MessageBuilder(messageName, payloadSize);
- builder.encodeStruct(pipeControlMessages.RunOrClosePipeMessageParams,
+ var builder = new internal.MessageV0Builder(messageName, payloadSize);
+ builder.encodeStruct(mojo.pipeControl.RunOrClosePipeMessageParams,
runOrClosePipeMessageParams);
var message = builder.finish();
- message.setInterfaceId(types.kInvalidInterfaceId);
+ message.setInterfaceId(internal.kInvalidInterfaceId);
return message;
}
@@ -37,20 +34,17 @@ define("mojo/public/js/lib/pipe_control_message_proxy", [
PipeControlMessageProxy.prototype.constructPeerEndpointClosedMessage =
function(interfaceId, reason) {
- var event = new pipeControlMessages.PeerAssociatedEndpointClosedEvent();
+ var event = new mojo.pipeControl.PeerAssociatedEndpointClosedEvent();
event.id = interfaceId;
if (reason) {
- event.disconnect_reason = new pipeControlMessages.DisconnectReason({
- custom_reason: reason.custom_reason,
+ event.disconnectReason = new mojo.pipeControl.DisconnectReason({
+ customReason: reason.customReason,
description: reason.description});
}
- var runOrClosePipeInput = new pipeControlMessages.RunOrClosePipeInput();
- runOrClosePipeInput.peer_associated_endpoint_closed_event = event;
+ var runOrClosePipeInput = new mojo.pipeControl.RunOrClosePipeInput();
+ runOrClosePipeInput.peerAssociatedEndpointClosedEvent = event;
return constructRunOrClosePipeMessage(runOrClosePipeInput);
};
- var exports = {};
- exports.PipeControlMessageProxy = PipeControlMessageProxy;
-
- return exports;
-});
+ internal.PipeControlMessageProxy = PipeControlMessageProxy;
+})();
diff --git a/mojo/public/js/lib/router.js b/mojo/public/js/lib/router.js
new file mode 100644
index 0000000000..251f3a97fe
--- /dev/null
+++ b/mojo/public/js/lib/router.js
@@ -0,0 +1,317 @@
+// 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.
+
+(function() {
+ var internal = mojo.internal;
+
+ /**
+ * The state of |endpoint|. If both the endpoint and its peer have been
+ * closed, removes it from |endpoints_|.
+ * @enum {string}
+ */
+ var EndpointStateUpdateType = {
+ ENDPOINT_CLOSED: 'endpoint_closed',
+ PEER_ENDPOINT_CLOSED: 'peer_endpoint_closed'
+ };
+
+ function check(condition, output) {
+ if (!condition) {
+ // testharness.js does not rethrow errors so the error stack needs to be
+ // included as a string in the error we throw for debugging layout tests.
+ throw new Error((new Error()).stack);
+ }
+ }
+
+ function InterfaceEndpoint(router, interfaceId) {
+ this.router_ = router;
+ this.id = interfaceId;
+ this.closed = false;
+ this.peerClosed = false;
+ this.handleCreated = false;
+ this.disconnectReason = null;
+ this.client = null;
+ }
+
+ InterfaceEndpoint.prototype.sendMessage = function(message) {
+ message.setInterfaceId(this.id);
+ return this.router_.connector_.accept(message);
+ };
+
+ function Router(handle, setInterfaceIdNamespaceBit) {
+ if (!(handle instanceof MojoHandle)) {
+ throw new Error("Router constructor: Not a handle");
+ }
+ if (setInterfaceIdNamespaceBit === undefined) {
+ setInterfaceIdNamespaceBit = false;
+ }
+
+ this.connector_ = new internal.Connector(handle);
+
+ this.connector_.setIncomingReceiver({
+ accept: this.accept.bind(this),
+ });
+ this.connector_.setErrorHandler({
+ onError: this.onPipeConnectionError.bind(this),
+ });
+
+ this.setInterfaceIdNamespaceBit_ = setInterfaceIdNamespaceBit;
+ // |cachedMessageData| caches infomation about a message, so it can be
+ // processed later if a client is not yet attached to the target endpoint.
+ this.cachedMessageData = null;
+ this.controlMessageHandler_ = new internal.PipeControlMessageHandler(this);
+ this.controlMessageProxy_ =
+ new internal.PipeControlMessageProxy(this.connector_);
+ this.nextInterfaceIdValue_ = 1;
+ this.encounteredError_ = false;
+ this.endpoints_ = new Map();
+ }
+
+ Router.prototype.associateInterface = function(handleToSend) {
+ if (!handleToSend.pendingAssociation()) {
+ return internal.kInvalidInterfaceId;
+ }
+
+ var id = 0;
+ do {
+ if (this.nextInterfaceIdValue_ >= internal.kInterfaceIdNamespaceMask) {
+ this.nextInterfaceIdValue_ = 1;
+ }
+ id = this.nextInterfaceIdValue_++;
+ if (this.setInterfaceIdNamespaceBit_) {
+ id += internal.kInterfaceIdNamespaceMask;
+ }
+ } while (this.endpoints_.has(id));
+
+ var endpoint = new InterfaceEndpoint(this, id);
+ this.endpoints_.set(id, endpoint);
+ if (this.encounteredError_) {
+ this.updateEndpointStateMayRemove(endpoint,
+ EndpointStateUpdateType.PEER_ENDPOINT_CLOSED);
+ }
+ endpoint.handleCreated = true;
+
+ if (!handleToSend.notifyAssociation(id, this)) {
+ // The peer handle of |handleToSend|, which is supposed to join this
+ // associated group, has been closed.
+ this.updateEndpointStateMayRemove(endpoint,
+ EndpointStateUpdateType.ENDPOINT_CLOSED);
+
+ pipeControlMessageproxy.notifyPeerEndpointClosed(id,
+ handleToSend.disconnectReason());
+ }
+
+ return id;
+ };
+
+ Router.prototype.attachEndpointClient = function(
+ interfaceEndpointHandle, interfaceEndpointClient) {
+ check(internal.isValidInterfaceId(interfaceEndpointHandle.id()));
+ check(interfaceEndpointClient);
+
+ var endpoint = this.endpoints_.get(interfaceEndpointHandle.id());
+ check(endpoint);
+ check(!endpoint.client);
+ check(!endpoint.closed);
+ endpoint.client = interfaceEndpointClient;
+
+ if (endpoint.peerClosed) {
+ setTimeout(endpoint.client.notifyError.bind(endpoint.client), 0);
+ }
+
+ if (this.cachedMessageData && interfaceEndpointHandle.id() ===
+ this.cachedMessageData.message.getInterfaceId()) {
+ setTimeout((function() {
+ if (!this.cachedMessageData) {
+ return;
+ }
+
+ var targetEndpoint = this.endpoints_.get(
+ this.cachedMessageData.message.getInterfaceId());
+ // Check that the target endpoint's client still exists.
+ if (targetEndpoint && targetEndpoint.client) {
+ var message = this.cachedMessageData.message;
+ var messageValidator = this.cachedMessageData.messageValidator;
+ this.cachedMessageData = null;
+ this.connector_.resumeIncomingMethodCallProcessing();
+ var ok = endpoint.client.handleIncomingMessage(message,
+ messageValidator);
+
+ // Handle invalid cached incoming message.
+ if (!internal.isTestingMode() && !ok) {
+ this.connector_.handleError(true, true);
+ }
+ }
+ }).bind(this), 0);
+ }
+
+ return endpoint;
+ };
+
+ Router.prototype.detachEndpointClient = function(
+ interfaceEndpointHandle) {
+ check(internal.isValidInterfaceId(interfaceEndpointHandle.id()));
+ var endpoint = this.endpoints_.get(interfaceEndpointHandle.id());
+ check(endpoint);
+ check(endpoint.client);
+ check(!endpoint.closed);
+
+ endpoint.client = null;
+ };
+
+ Router.prototype.createLocalEndpointHandle = function(
+ interfaceId) {
+ if (!internal.isValidInterfaceId(interfaceId)) {
+ return new internal.InterfaceEndpointHandle();
+ }
+
+ // Unless it is the master ID, |interfaceId| is from the remote side and
+ // therefore its namespace bit is supposed to be different than the value
+ // that this router would use.
+ if (!internal.isMasterInterfaceId(interfaceId) &&
+ this.setInterfaceIdNamespaceBit_ ===
+ internal.hasInterfaceIdNamespaceBitSet(interfaceId)) {
+ return new internal.InterfaceEndpointHandle();
+ }
+
+ var endpoint = this.endpoints_.get(interfaceId);
+
+ if (!endpoint) {
+ endpoint = new InterfaceEndpoint(this, interfaceId);
+ this.endpoints_.set(interfaceId, endpoint);
+
+ check(!endpoint.handleCreated);
+
+ if (this.encounteredError_) {
+ this.updateEndpointStateMayRemove(endpoint,
+ EndpointStateUpdateType.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.peerClosed);
+
+ if (endpoint.handleCreated) {
+ return new internal.InterfaceEndpointHandle();
+ }
+ }
+
+ endpoint.handleCreated = true;
+ return new internal.InterfaceEndpointHandle(interfaceId, this);
+ };
+
+ Router.prototype.accept = function(message) {
+ var messageValidator = new internal.Validator(message);
+ var err = messageValidator.validateMessageHeader();
+
+ var ok = false;
+ if (err !== internal.validationError.NONE) {
+ internal.reportValidationError(err);
+ } else if (message.deserializeAssociatedEndpointHandles(this)) {
+ if (internal.isPipeControlMessage(message)) {
+ ok = this.controlMessageHandler_.accept(message);
+ } else {
+ var interfaceId = message.getInterfaceId();
+ var endpoint = this.endpoints_.get(interfaceId);
+ if (!endpoint || endpoint.closed) {
+ return true;
+ }
+
+ if (!endpoint.client) {
+ // We need to wait until a client is attached in order to dispatch
+ // further messages.
+ this.cachedMessageData = {message: message,
+ messageValidator: messageValidator};
+ this.connector_.pauseIncomingMethodCallProcessing();
+ return true;
+ }
+ ok = endpoint.client.handleIncomingMessage(message, messageValidator);
+ }
+ }
+ return ok;
+ };
+
+ Router.prototype.close = function() {
+ this.connector_.close();
+ // Closing the message pipe won't trigger connection error handler.
+ // Explicitly call onPipeConnectionError() so that associated endpoints
+ // will get notified.
+ this.onPipeConnectionError();
+ };
+
+ Router.prototype.onPeerAssociatedEndpointClosed = function(interfaceId,
+ reason) {
+ var endpoint = this.endpoints_.get(interfaceId);
+ if (!endpoint) {
+ endpoint = new InterfaceEndpoint(this, interfaceId);
+ this.endpoints_.set(interfaceId, endpoint);
+ }
+
+ if (reason) {
+ endpoint.disconnectReason = reason;
+ }
+
+ if (!endpoint.peerClosed) {
+ if (endpoint.client) {
+ setTimeout(endpoint.client.notifyError.bind(endpoint.client, reason),
+ 0);
+ }
+ this.updateEndpointStateMayRemove(endpoint,
+ EndpointStateUpdateType.PEER_ENDPOINT_CLOSED);
+ }
+ return true;
+ };
+
+ Router.prototype.onPipeConnectionError = function() {
+ this.encounteredError_ = true;
+
+ for (var endpoint of this.endpoints_.values()) {
+ if (endpoint.client) {
+ setTimeout(
+ endpoint.client.notifyError.bind(
+ endpoint.client, endpoint.disconnectReason),
+ 0);
+ }
+ this.updateEndpointStateMayRemove(endpoint,
+ EndpointStateUpdateType.PEER_ENDPOINT_CLOSED);
+ }
+ };
+
+ Router.prototype.closeEndpointHandle = function(interfaceId, reason) {
+ if (!internal.isValidInterfaceId(interfaceId)) {
+ return;
+ }
+ var endpoint = this.endpoints_.get(interfaceId);
+ check(endpoint);
+ check(!endpoint.client);
+ check(!endpoint.closed);
+
+ this.updateEndpointStateMayRemove(endpoint,
+ EndpointStateUpdateType.ENDPOINT_CLOSED);
+
+ if (!internal.isMasterInterfaceId(interfaceId) || reason) {
+ this.controlMessageProxy_.notifyPeerEndpointClosed(interfaceId, reason);
+ }
+
+ if (this.cachedMessageData && interfaceId ===
+ this.cachedMessageData.message.getInterfaceId()) {
+ this.cachedMessageData = null;
+ this.connector_.resumeIncomingMethodCallProcessing();
+ }
+ };
+
+ Router.prototype.updateEndpointStateMayRemove = function(endpoint,
+ endpointStateUpdateType) {
+ if (endpointStateUpdateType === EndpointStateUpdateType.ENDPOINT_CLOSED) {
+ endpoint.closed = true;
+ } else {
+ endpoint.peerClosed = true;
+ }
+ if (endpoint.closed && endpoint.peerClosed) {
+ this.endpoints_.delete(endpoint.id);
+ }
+ };
+
+ internal.Router = Router;
+})();
diff --git a/mojo/public/js/lib/unicode.js b/mojo/public/js/lib/unicode.js
new file mode 100644
index 0000000000..67976ae4e6
--- /dev/null
+++ b/mojo/public/js/lib/unicode.js
@@ -0,0 +1,51 @@
+// 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.
+
+/**
+ * Defines functions for translating between JavaScript strings and UTF8 strings
+ * stored in ArrayBuffers. There is much room for optimization in this code if
+ * it proves necessary.
+ */
+(function() {
+ var internal = mojo.internal;
+ var textDecoder = new TextDecoder('utf-8');
+ var textEncoder = new TextEncoder('utf-8');
+
+ /**
+ * Decodes the UTF8 string from the given buffer.
+ * @param {ArrayBufferView} buffer The buffer containing UTF8 string data.
+ * @return {string} The corresponding JavaScript string.
+ */
+ function decodeUtf8String(buffer) {
+ return textDecoder.decode(buffer);
+ }
+
+ /**
+ * Encodes the given JavaScript string into UTF8.
+ * @param {string} str The string to encode.
+ * @param {ArrayBufferView} outputBuffer The buffer to contain the result.
+ * Should be pre-allocated to hold enough space. Use |utf8Length| to determine
+ * how much space is required.
+ * @return {number} The number of bytes written to |outputBuffer|.
+ */
+ function encodeUtf8String(str, outputBuffer) {
+ const utf8Buffer = textEncoder.encode(str);
+ if (outputBuffer.length < utf8Buffer.length)
+ throw new Error("Buffer too small for encodeUtf8String");
+ outputBuffer.set(utf8Buffer);
+ return utf8Buffer.length;
+ }
+
+ /**
+ * Returns the number of bytes that a UTF8 encoding of the JavaScript string
+ * |str| would occupy.
+ */
+ function utf8Length(str) {
+ return textEncoder.encode(str).length;
+ }
+
+ internal.decodeUtf8String = decodeUtf8String;
+ internal.encodeUtf8String = encodeUtf8String;
+ internal.utf8Length = utf8Length;
+})();
diff --git a/mojo/public/js/lib/validator.js b/mojo/public/js/lib/validator.js
new file mode 100644
index 0000000000..577dcf0033
--- /dev/null
+++ b/mojo/public/js/lib/validator.js
@@ -0,0 +1,678 @@
+// 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.
+
+(function() {
+ var internal = mojo.internal;
+
+ var validationError = {
+ NONE: 'VALIDATION_ERROR_NONE',
+ MISALIGNED_OBJECT: 'VALIDATION_ERROR_MISALIGNED_OBJECT',
+ ILLEGAL_MEMORY_RANGE: 'VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE',
+ UNEXPECTED_STRUCT_HEADER: 'VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER',
+ UNEXPECTED_ARRAY_HEADER: 'VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER',
+ ILLEGAL_HANDLE: 'VALIDATION_ERROR_ILLEGAL_HANDLE',
+ UNEXPECTED_INVALID_HANDLE: 'VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE',
+ ILLEGAL_POINTER: 'VALIDATION_ERROR_ILLEGAL_POINTER',
+ UNEXPECTED_NULL_POINTER: 'VALIDATION_ERROR_UNEXPECTED_NULL_POINTER',
+ ILLEGAL_INTERFACE_ID: 'VALIDATION_ERROR_ILLEGAL_INTERFACE_ID',
+ UNEXPECTED_INVALID_INTERFACE_ID:
+ 'VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID',
+ MESSAGE_HEADER_INVALID_FLAGS:
+ 'VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS',
+ MESSAGE_HEADER_MISSING_REQUEST_ID:
+ 'VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID',
+ DIFFERENT_SIZED_ARRAYS_IN_MAP:
+ 'VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP',
+ INVALID_UNION_SIZE: 'VALIDATION_ERROR_INVALID_UNION_SIZE',
+ UNEXPECTED_NULL_UNION: 'VALIDATION_ERROR_UNEXPECTED_NULL_UNION',
+ UNKNOWN_ENUM_VALUE: 'VALIDATION_ERROR_UNKNOWN_ENUM_VALUE',
+ };
+
+ var NULL_MOJO_POINTER = "NULL_MOJO_POINTER";
+ var gValidationErrorObserver = null;
+
+ function reportValidationError(error) {
+ if (gValidationErrorObserver) {
+ gValidationErrorObserver.lastError = error;
+ } else {
+ console.warn('Invalid message: ' + error);
+ }
+ }
+
+ var ValidationErrorObserverForTesting = (function() {
+ function Observer() {
+ this.lastError = validationError.NONE;
+ }
+
+ Observer.prototype.reset = function() {
+ this.lastError = validationError.NONE;
+ };
+
+ return {
+ getInstance: function() {
+ if (!gValidationErrorObserver) {
+ gValidationErrorObserver = new Observer();
+ }
+ return gValidationErrorObserver;
+ }
+ };
+ })();
+
+ function isTestingMode() {
+ return Boolean(gValidationErrorObserver);
+ }
+
+ function clearTestingMode() {
+ gValidationErrorObserver = null;
+ }
+
+ function isEnumClass(cls) {
+ return cls instanceof internal.Enum;
+ }
+
+ function isStringClass(cls) {
+ return cls === internal.String || cls === internal.NullableString;
+ }
+
+ function isHandleClass(cls) {
+ return cls === internal.Handle || cls === internal.NullableHandle;
+ }
+
+ function isInterfaceClass(cls) {
+ return cls instanceof internal.Interface;
+ }
+
+ function isInterfaceRequestClass(cls) {
+ return cls === internal.InterfaceRequest ||
+ cls === internal.NullableInterfaceRequest;
+ }
+
+ function isAssociatedInterfaceClass(cls) {
+ return cls === internal.AssociatedInterfacePtrInfo ||
+ cls === internal.NullableAssociatedInterfacePtrInfo;
+ }
+
+ function isAssociatedInterfaceRequestClass(cls) {
+ return cls === internal.AssociatedInterfaceRequest ||
+ cls === internal.NullableAssociatedInterfaceRequest;
+ }
+
+ function isNullable(type) {
+ return type === internal.NullableString ||
+ type === internal.NullableHandle ||
+ type === internal.NullableAssociatedInterfacePtrInfo ||
+ type === internal.NullableAssociatedInterfaceRequest ||
+ type === internal.NullableInterface ||
+ type === internal.NullableInterfaceRequest ||
+ type instanceof internal.NullableArrayOf ||
+ type instanceof internal.NullablePointerTo;
+ }
+
+ function Validator(message) {
+ this.message = message;
+ this.offset = 0;
+ this.handleIndex = 0;
+ this.associatedEndpointHandleIndex = 0;
+ this.payloadInterfaceIds = null;
+ this.offsetLimit = this.message.buffer.byteLength;
+ }
+
+ Object.defineProperty(Validator.prototype, "handleIndexLimit", {
+ get: function() { return this.message.handles.length; }
+ });
+
+ Object.defineProperty(Validator.prototype, "associatedHandleIndexLimit", {
+ get: function() {
+ return this.payloadInterfaceIds ? this.payloadInterfaceIds.length : 0;
+ }
+ });
+
+ // True if we can safely allocate a block of bytes from start to
+ // to start + numBytes.
+ Validator.prototype.isValidRange = function(start, numBytes) {
+ // Only positive JavaScript integers that are less than 2^53
+ // (Number.MAX_SAFE_INTEGER) can be represented exactly.
+ if (start < this.offset || numBytes <= 0 ||
+ !Number.isSafeInteger(start) ||
+ !Number.isSafeInteger(numBytes))
+ return false;
+
+ var newOffset = start + numBytes;
+ if (!Number.isSafeInteger(newOffset) || newOffset > this.offsetLimit)
+ return false;
+
+ return true;
+ };
+
+ Validator.prototype.claimRange = function(start, numBytes) {
+ if (this.isValidRange(start, numBytes)) {
+ this.offset = start + numBytes;
+ return true;
+ }
+ return false;
+ };
+
+ Validator.prototype.claimHandle = function(index) {
+ if (index === internal.kEncodedInvalidHandleValue)
+ return true;
+
+ if (index < this.handleIndex || index >= this.handleIndexLimit)
+ return false;
+
+ // This is safe because handle indices are uint32.
+ this.handleIndex = index + 1;
+ return true;
+ };
+
+ Validator.prototype.claimAssociatedEndpointHandle = function(index) {
+ if (index === internal.kEncodedInvalidHandleValue) {
+ return true;
+ }
+
+ if (index < this.associatedEndpointHandleIndex ||
+ index >= this.associatedHandleIndexLimit) {
+ return false;
+ }
+
+ // This is safe because handle indices are uint32.
+ this.associatedEndpointHandleIndex = index + 1;
+ return true;
+ };
+
+ Validator.prototype.validateEnum = function(offset, enumClass) {
+ // Note: Assumes that enums are always 32 bits! But this matches
+ // mojom::generate::pack::PackedField::GetSizeForKind, so it should be okay.
+ var value = this.message.buffer.getInt32(offset);
+ return enumClass.validate(value);
+ }
+
+ Validator.prototype.validateHandle = function(offset, nullable) {
+ var index = this.message.buffer.getUint32(offset);
+
+ if (index === internal.kEncodedInvalidHandleValue)
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_INVALID_HANDLE;
+
+ if (!this.claimHandle(index))
+ return validationError.ILLEGAL_HANDLE;
+
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateAssociatedEndpointHandle = function(offset,
+ nullable) {
+ var index = this.message.buffer.getUint32(offset);
+
+ if (index === internal.kEncodedInvalidHandleValue) {
+ return nullable ? validationError.NONE :
+ validationError.UNEXPECTED_INVALID_INTERFACE_ID;
+ }
+
+ if (!this.claimAssociatedEndpointHandle(index)) {
+ return validationError.ILLEGAL_INTERFACE_ID;
+ }
+
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateInterface = function(offset, nullable) {
+ return this.validateHandle(offset, nullable);
+ };
+
+ Validator.prototype.validateInterfaceRequest = function(offset, nullable) {
+ return this.validateHandle(offset, nullable);
+ };
+
+ Validator.prototype.validateAssociatedInterface = function(offset,
+ nullable) {
+ return this.validateAssociatedEndpointHandle(offset, nullable);
+ };
+
+ Validator.prototype.validateAssociatedInterfaceRequest = function(
+ offset, nullable) {
+ return this.validateAssociatedEndpointHandle(offset, nullable);
+ };
+
+ Validator.prototype.validateStructHeader = function(offset, minNumBytes) {
+ if (!internal.isAligned(offset))
+ return validationError.MISALIGNED_OBJECT;
+
+ if (!this.isValidRange(offset, internal.kStructHeaderSize))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ var numBytes = this.message.buffer.getUint32(offset);
+
+ if (numBytes < minNumBytes)
+ return validationError.UNEXPECTED_STRUCT_HEADER;
+
+ if (!this.claimRange(offset, numBytes))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateStructVersion = function(offset, versionSizes) {
+ var numBytes = this.message.buffer.getUint32(offset);
+ var version = this.message.buffer.getUint32(offset + 4);
+
+ if (version <= versionSizes[versionSizes.length - 1].version) {
+ // Scan in reverse order to optimize for more recent versionSizes.
+ for (var i = versionSizes.length - 1; i >= 0; --i) {
+ if (version >= versionSizes[i].version) {
+ if (numBytes == versionSizes[i].numBytes)
+ break;
+ return validationError.UNEXPECTED_STRUCT_HEADER;
+ }
+ }
+ } else if (numBytes < versionSizes[versionSizes.length-1].numBytes) {
+ return validationError.UNEXPECTED_STRUCT_HEADER;
+ }
+
+ return validationError.NONE;
+ };
+
+ Validator.prototype.isFieldInStructVersion = function(offset, fieldVersion) {
+ var structVersion = this.message.buffer.getUint32(offset + 4);
+ return fieldVersion <= structVersion;
+ };
+
+ Validator.prototype.validateMessageHeader = function() {
+ var err = this.validateStructHeader(0, internal.kMessageV0HeaderSize);
+ if (err != validationError.NONE) {
+ return err;
+ }
+
+ var numBytes = this.message.getHeaderNumBytes();
+ var version = this.message.getHeaderVersion();
+
+ var validVersionAndNumBytes =
+ (version == 0 && numBytes == internal.kMessageV0HeaderSize) ||
+ (version == 1 && numBytes == internal.kMessageV1HeaderSize) ||
+ (version == 2 && numBytes == internal.kMessageV2HeaderSize) ||
+ (version > 2 && numBytes >= internal.kMessageV2HeaderSize);
+
+ if (!validVersionAndNumBytes) {
+ return validationError.UNEXPECTED_STRUCT_HEADER;
+ }
+
+ var expectsResponse = this.message.expectsResponse();
+ var isResponse = this.message.isResponse();
+
+ if (version == 0 && (expectsResponse || isResponse)) {
+ return validationError.MESSAGE_HEADER_MISSING_REQUEST_ID;
+ }
+
+ if (isResponse && expectsResponse) {
+ return validationError.MESSAGE_HEADER_INVALID_FLAGS;
+ }
+
+ if (version < 2) {
+ return validationError.NONE;
+ }
+
+ var err = this.validateArrayPointer(
+ internal.kMessagePayloadInterfaceIdsPointerOffset,
+ internal.Uint32.encodedSize, internal.Uint32, true, [0], 0);
+
+ if (err != validationError.NONE) {
+ return err;
+ }
+
+ this.payloadInterfaceIds = this.message.getPayloadInterfaceIds();
+ if (this.payloadInterfaceIds) {
+ for (var interfaceId of this.payloadInterfaceIds) {
+ if (!internal.isValidInterfaceId(interfaceId) ||
+ internal.isMasterInterfaceId(interfaceId)) {
+ return validationError.ILLEGAL_INTERFACE_ID;
+ }
+ }
+ }
+
+ // Set offset to the start of the payload and offsetLimit to the start of
+ // the payload interface Ids so that payload can be validated using the
+ // same messageValidator.
+ this.offset = this.message.getHeaderNumBytes();
+ this.offsetLimit = this.decodePointer(
+ internal.kMessagePayloadInterfaceIdsPointerOffset);
+
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateMessageIsRequestWithoutResponse = function() {
+ if (this.message.isResponse() || this.message.expectsResponse()) {
+ return validationError.MESSAGE_HEADER_INVALID_FLAGS;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateMessageIsRequestExpectingResponse = function() {
+ if (this.message.isResponse() || !this.message.expectsResponse()) {
+ return validationError.MESSAGE_HEADER_INVALID_FLAGS;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateMessageIsResponse = function() {
+ if (this.message.expectsResponse() || !this.message.isResponse()) {
+ return validationError.MESSAGE_HEADER_INVALID_FLAGS;
+ }
+ return validationError.NONE;
+ };
+
+ // Returns the message.buffer relative offset this pointer "points to",
+ // NULL_MOJO_POINTER if the pointer represents a null, or JS null if the
+ // pointer's value is not valid.
+ Validator.prototype.decodePointer = function(offset) {
+ var pointerValue = this.message.buffer.getUint64(offset);
+ if (pointerValue === 0)
+ return NULL_MOJO_POINTER;
+ var bufferOffset = offset + pointerValue;
+ return Number.isSafeInteger(bufferOffset) ? bufferOffset : null;
+ };
+
+ Validator.prototype.decodeUnionSize = function(offset) {
+ return this.message.buffer.getUint32(offset);
+ };
+
+ Validator.prototype.decodeUnionTag = function(offset) {
+ return this.message.buffer.getUint32(offset + 4);
+ };
+
+ Validator.prototype.validateArrayPointer = function(
+ offset, elementSize, elementType, nullable, expectedDimensionSizes,
+ currentDimension) {
+ var arrayOffset = this.decodePointer(offset);
+ if (arrayOffset === null)
+ return validationError.ILLEGAL_POINTER;
+
+ if (arrayOffset === NULL_MOJO_POINTER)
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
+
+ return this.validateArray(arrayOffset, elementSize, elementType,
+ expectedDimensionSizes, currentDimension);
+ };
+
+ Validator.prototype.validateStructPointer = function(
+ offset, structClass, nullable) {
+ var structOffset = this.decodePointer(offset);
+ if (structOffset === null)
+ return validationError.ILLEGAL_POINTER;
+
+ if (structOffset === NULL_MOJO_POINTER)
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
+
+ return structClass.validate(this, structOffset);
+ };
+
+ Validator.prototype.validateUnion = function(
+ offset, unionClass, nullable) {
+ var size = this.message.buffer.getUint32(offset);
+ if (size == 0) {
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_UNION;
+ }
+
+ return unionClass.validate(this, offset);
+ };
+
+ Validator.prototype.validateNestedUnion = function(
+ offset, unionClass, nullable) {
+ var unionOffset = this.decodePointer(offset);
+ if (unionOffset === null)
+ return validationError.ILLEGAL_POINTER;
+
+ if (unionOffset === NULL_MOJO_POINTER)
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_UNION;
+
+ return this.validateUnion(unionOffset, unionClass, nullable);
+ };
+
+ // This method assumes that the array at arrayPointerOffset has
+ // been validated.
+
+ Validator.prototype.arrayLength = function(arrayPointerOffset) {
+ var arrayOffset = this.decodePointer(arrayPointerOffset);
+ return this.message.buffer.getUint32(arrayOffset + 4);
+ };
+
+ Validator.prototype.validateMapPointer = function(
+ offset, mapIsNullable, keyClass, valueClass, valueIsNullable) {
+ // Validate the implicit map struct:
+ // struct {array<keyClass> keys; array<valueClass> values};
+ var structOffset = this.decodePointer(offset);
+ if (structOffset === null)
+ return validationError.ILLEGAL_POINTER;
+
+ if (structOffset === NULL_MOJO_POINTER)
+ return mapIsNullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
+
+ var mapEncodedSize = internal.kStructHeaderSize +
+ internal.kMapStructPayloadSize;
+ var err = this.validateStructHeader(structOffset, mapEncodedSize);
+ if (err !== validationError.NONE)
+ return err;
+
+ // Validate the keys array.
+ var keysArrayPointerOffset = structOffset + internal.kStructHeaderSize;
+ err = this.validateArrayPointer(
+ keysArrayPointerOffset, keyClass.encodedSize, keyClass, false, [0], 0);
+ if (err !== validationError.NONE)
+ return err;
+
+ // Validate the values array.
+ var valuesArrayPointerOffset = keysArrayPointerOffset + 8;
+ var valuesArrayDimensions = [0]; // Validate the actual length below.
+ if (valueClass instanceof internal.ArrayOf)
+ valuesArrayDimensions =
+ valuesArrayDimensions.concat(valueClass.dimensions());
+ var err = this.validateArrayPointer(valuesArrayPointerOffset,
+ valueClass.encodedSize,
+ valueClass,
+ valueIsNullable,
+ valuesArrayDimensions,
+ 0);
+ if (err !== validationError.NONE)
+ return err;
+
+ // Validate the lengths of the keys and values arrays.
+ var keysArrayLength = this.arrayLength(keysArrayPointerOffset);
+ var valuesArrayLength = this.arrayLength(valuesArrayPointerOffset);
+ if (keysArrayLength != valuesArrayLength)
+ return validationError.DIFFERENT_SIZED_ARRAYS_IN_MAP;
+
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateStringPointer = function(offset, nullable) {
+ return this.validateArrayPointer(
+ offset, internal.Uint8.encodedSize, internal.Uint8, nullable, [0], 0);
+ };
+
+ // Similar to Array_Data<T>::Validate()
+ // mojo/public/cpp/bindings/lib/array_internal.h
+
+ Validator.prototype.validateArray =
+ function (offset, elementSize, elementType, expectedDimensionSizes,
+ currentDimension) {
+ if (!internal.isAligned(offset))
+ return validationError.MISALIGNED_OBJECT;
+
+ if (!this.isValidRange(offset, internal.kArrayHeaderSize))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ var numBytes = this.message.buffer.getUint32(offset);
+ var numElements = this.message.buffer.getUint32(offset + 4);
+
+ // Note: this computation is "safe" because elementSize <= 8 and
+ // numElements is a uint32.
+ var elementsTotalSize = (elementType === internal.PackedBool) ?
+ Math.ceil(numElements / 8) : (elementSize * numElements);
+
+ if (numBytes < internal.kArrayHeaderSize + elementsTotalSize)
+ return validationError.UNEXPECTED_ARRAY_HEADER;
+
+ if (expectedDimensionSizes[currentDimension] != 0 &&
+ numElements != expectedDimensionSizes[currentDimension]) {
+ return validationError.UNEXPECTED_ARRAY_HEADER;
+ }
+
+ if (!this.claimRange(offset, numBytes))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ // Validate the array's elements if they are pointers or handles.
+
+ var elementsOffset = offset + internal.kArrayHeaderSize;
+ var nullable = isNullable(elementType);
+
+ if (isHandleClass(elementType))
+ return this.validateHandleElements(elementsOffset, numElements, nullable);
+ if (isInterfaceClass(elementType))
+ return this.validateInterfaceElements(
+ elementsOffset, numElements, nullable);
+ if (isInterfaceRequestClass(elementType))
+ return this.validateInterfaceRequestElements(
+ elementsOffset, numElements, nullable);
+ if (isAssociatedInterfaceClass(elementType))
+ return this.validateAssociatedInterfaceElements(
+ elementsOffset, numElements, nullable);
+ if (isAssociatedInterfaceRequestClass(elementType))
+ return this.validateAssociatedInterfaceRequestElements(
+ elementsOffset, numElements, nullable);
+ if (isStringClass(elementType))
+ return this.validateArrayElements(
+ elementsOffset, numElements, internal.Uint8, nullable, [0], 0);
+ if (elementType instanceof internal.PointerTo)
+ return this.validateStructElements(
+ elementsOffset, numElements, elementType.cls, nullable);
+ if (elementType instanceof internal.ArrayOf)
+ return this.validateArrayElements(
+ elementsOffset, numElements, elementType.cls, nullable,
+ expectedDimensionSizes, currentDimension + 1);
+ if (isEnumClass(elementType))
+ return this.validateEnumElements(elementsOffset, numElements,
+ elementType.cls);
+
+ return validationError.NONE;
+ };
+
+ // Note: the |offset + i * elementSize| computation in the validateFooElements
+ // methods below is "safe" because elementSize <= 8, offset and
+ // numElements are uint32, and 0 <= i < numElements.
+
+ Validator.prototype.validateHandleElements =
+ function(offset, numElements, nullable) {
+ var elementSize = internal.Handle.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateHandle(elementOffset, nullable);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateInterfaceElements =
+ function(offset, numElements, nullable) {
+ var elementSize = internal.Interface.prototype.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateInterface(elementOffset, nullable);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateInterfaceRequestElements =
+ function(offset, numElements, nullable) {
+ var elementSize = internal.InterfaceRequest.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateInterfaceRequest(elementOffset, nullable);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateAssociatedInterfaceElements =
+ function(offset, numElements, nullable) {
+ var elementSize = internal.AssociatedInterfacePtrInfo.prototype.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateAssociatedInterface(elementOffset, nullable);
+ if (err != validationError.NONE) {
+ return err;
+ }
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateAssociatedInterfaceRequestElements =
+ function(offset, numElements, nullable) {
+ var elementSize = internal.AssociatedInterfaceRequest.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateAssociatedInterfaceRequest(elementOffset,
+ nullable);
+ if (err != validationError.NONE) {
+ return err;
+ }
+ }
+ return validationError.NONE;
+ };
+
+ // The elementClass parameter is the element type of the element arrays.
+ Validator.prototype.validateArrayElements =
+ function(offset, numElements, elementClass, nullable,
+ expectedDimensionSizes, currentDimension) {
+ var elementSize = internal.PointerTo.prototype.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateArrayPointer(
+ elementOffset, elementClass.encodedSize, elementClass, nullable,
+ expectedDimensionSizes, currentDimension);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateStructElements =
+ function(offset, numElements, structClass, nullable) {
+ var elementSize = internal.PointerTo.prototype.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err =
+ this.validateStructPointer(elementOffset, structClass, nullable);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ };
+
+ Validator.prototype.validateEnumElements =
+ function(offset, numElements, enumClass) {
+ var elementSize = internal.Enum.prototype.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateEnum(elementOffset, enumClass);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ };
+
+ internal.validationError = validationError;
+ internal.Validator = Validator;
+ internal.ValidationErrorObserverForTesting =
+ ValidationErrorObserverForTesting;
+ internal.reportValidationError = reportValidationError;
+ internal.isTestingMode = isTestingMode;
+ internal.clearTestingMode = clearTestingMode;
+})();
diff --git a/mojo/public/js/mojo_bindings_resources.grd b/mojo/public/js/mojo_bindings_resources.grd
new file mode 100644
index 0000000000..b92f797443
--- /dev/null
+++ b/mojo/public/js/mojo_bindings_resources.grd
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1" output_all_resource_defines="false">
+ <outputs>
+ <output filename="grit/mojo_bindings_resources.h" type="rc_header">
+ <emit emit_type='prepend'></emit>
+ </output>
+ <output filename="grit/mojo_bindings_resources_map.h"
+ type="gzipped_resource_map_header" />
+ <output filename="grit/mojo_bindings_resources_map.cc"
+ type="gzipped_resource_file_map_source" />
+ <output filename="mojo_bindings_resources.pak" type="data_package" />
+ </outputs>
+ <translations />
+ <release seq="1">
+ <includes>
+ <include name="IDR_MOJO_BINDINGS_JS"
+ file="${root_gen_dir}\mojo\public\js\mojo_bindings.js"
+ type="BINDATA" use_base_dir="false" compress="gzip" />
+ </includes>
+ </release>
+</grit>
diff --git a/mojo/public/js/new_bindings/base.js b/mojo/public/js/new_bindings/base.js
deleted file mode 100644
index db72d489db..0000000000
--- a/mojo/public/js/new_bindings/base.js
+++ /dev/null
@@ -1,111 +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.
-
-'use strict';
-
-if (mojo && mojo.internal) {
- throw new Error('The Mojo bindings library has been initialized.');
-}
-
-var mojo = mojo || {};
-mojo.internal = {};
-mojo.internal.global = this;
-mojo.config = {
- // Whether to automatically load mojom dependencies.
- // For example, if foo.mojom imports bar.mojom, |autoLoadMojomDeps| set to
- // true means that loading foo.mojom.js will insert a <script> tag to load
- // bar.mojom.js, if it hasn't been loaded.
- //
- // The URL of bar.mojom.js is determined by the relative path of bar.mojom
- // (relative to the position of foo.mojom at build time) and the URL of
- // foo.mojom.js. For exmple, if at build time the two mojom files are
- // located at:
- // a/b/c/foo.mojom
- // a/b/d/bar.mojom
- // and the URL of foo.mojom.js is:
- // http://example.org/scripts/b/c/foo.mojom.js
- // then the URL of bar.mojom.js will be:
- // http://example.org/scripts/b/d/bar.mojom.js
- //
- // If you would like bar.mojom.js to live at a different location, you need
- // to turn off |autoLoadMojomDeps| before loading foo.mojom.js, and manually
- // load bar.mojom.js yourself. Similarly, you need to turn off the option if
- // you merge bar.mojom.js and foo.mojom.js into a single file.
- //
- // Performance tip: Avoid loading the same mojom.js file multiple times.
- // Assume that |autoLoadMojomDeps| is set to true:
- // <!-- No duplicate loading; recommended. -->
- // <script src="http://example.org/scripts/b/c/foo.mojom.js"></script>
- //
- // <!-- No duplicate loading, although unnecessary. -->
- // <script src="http://example.org/scripts/b/d/bar.mojom.js"></script>
- // <script src="http://example.org/scripts/b/c/foo.mojom.js"></script>
- //
- // <!-- Load bar.mojom.js twice; should be avoided. -->
- // <script src="http://example.org/scripts/b/c/foo.mojom.js"></script>
- // <script src="http://example.org/scripts/b/d/bar.mojom.js"></script>
- autoLoadMojomDeps: true
-};
-
-(function() {
- var internal = mojo.internal;
-
- var LoadState = {
- PENDING_LOAD: 1,
- LOADED: 2
- };
-
- var mojomRegistry = new Map();
-
- function exposeNamespace(namespace) {
- var current = internal.global;
- var parts = namespace.split('.');
-
- for (var part; parts.length && (part = parts.shift());) {
- if (!current[part]) {
- current[part] = {};
- }
- current = current[part];
- }
-
- return current;
- }
-
- function isMojomPendingLoad(id) {
- return mojomRegistry.get(id) === LoadState.PENDING_LOAD;
- }
-
- function isMojomLoaded(id) {
- return mojomRegistry.get(id) === LoadState.LOADED;
- }
-
- function markMojomPendingLoad(id) {
- if (isMojomLoaded(id)) {
- throw new Error('The following mojom file has been loaded: ' + id);
- }
-
- mojomRegistry.set(id, LoadState.PENDING_LOAD);
- }
-
- function markMojomLoaded(id) {
- mojomRegistry.set(id, LoadState.LOADED);
- }
-
- function loadMojomIfNecessary(id, url) {
- if (mojomRegistry.has(id)) {
- return;
- }
-
- markMojomPendingLoad(id);
- internal.global.document.write('<script type="text/javascript" src="' +
- url + '"></script>');
- }
-
- internal.exposeNamespace = exposeNamespace;
- internal.isMojomPendingLoad = isMojomPendingLoad;
- internal.isMojomLoaded = isMojomLoaded;
- internal.markMojomPendingLoad = markMojomPendingLoad;
- internal.markMojomLoaded = markMojomLoaded;
- internal.loadMojomIfNecessary = loadMojomIfNecessary;
-})();
diff --git a/mojo/public/js/new_bindings/bindings.js b/mojo/public/js/new_bindings/bindings.js
deleted file mode 100644
index 5b3b66ecea..0000000000
--- a/mojo/public/js/new_bindings/bindings.js
+++ /dev/null
@@ -1,275 +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.
-
-(function() {
- var internal = mojo.internal;
- // ---------------------------------------------------------------------------
-
- function makeRequest(interfacePtr) {
- var pipe = Mojo.createMessagePipe();
- interfacePtr.ptr.bind(new mojo.InterfacePtrInfo(pipe.handle0, 0));
- return new mojo.InterfaceRequest(pipe.handle1);
- }
-
- // ---------------------------------------------------------------------------
-
- // Operations used to setup/configure an interface pointer. Exposed as the
- // |ptr| field of generated interface pointer classes.
- // |ptrInfoOrHandle| could be omitted and passed into bind() later.
- function InterfacePtrController(interfaceType, ptrInfoOrHandle) {
- this.version = 0;
-
- this.interfaceType_ = interfaceType;
- this.router_ = null;
- this.proxy_ = null;
-
- // |router_| is lazily initialized. |handle_| is valid between bind() and
- // the initialization of |router_|.
- this.handle_ = null;
- this.controlMessageProxy_ = null;
-
- if (ptrInfoOrHandle)
- this.bind(ptrInfoOrHandle);
- }
-
- InterfacePtrController.prototype.bind = function(ptrInfoOrHandle) {
- this.reset();
-
- if (ptrInfoOrHandle instanceof mojo.InterfacePtrInfo) {
- this.version = ptrInfoOrHandle.version;
- this.handle_ = ptrInfoOrHandle.handle;
- } else {
- this.handle_ = ptrInfoOrHandle;
- }
- };
-
- InterfacePtrController.prototype.isBound = function() {
- return this.router_ !== null || this.handle_ !== null;
- };
-
- // Although users could just discard the object, reset() closes the pipe
- // immediately.
- InterfacePtrController.prototype.reset = function() {
- this.version = 0;
- if (this.router_) {
- this.router_.close();
- this.router_ = null;
-
- this.proxy_ = null;
- }
- if (this.handle_) {
- this.handle_.close();
- this.handle_ = null;
- }
- };
-
- InterfacePtrController.prototype.setConnectionErrorHandler
- = function(callback) {
- if (!this.isBound())
- throw new Error("Cannot set connection error handler if not bound.");
-
- this.configureProxyIfNecessary_();
- this.router_.setErrorHandler(callback);
- };
-
- InterfacePtrController.prototype.passInterface = function() {
- var result;
- if (this.router_) {
- // TODO(yzshen): Fix Router interface to support extracting handle.
- result = new mojo.InterfacePtrInfo(
- this.router_.connector_.handle_, this.version);
- this.router_.connector_.handle_ = null;
- } else {
- // This also handles the case when this object is not bound.
- result = new mojo.InterfacePtrInfo(this.handle_, this.version);
- this.handle_ = null;
- }
-
- this.reset();
- return result;
- };
-
- InterfacePtrController.prototype.getProxy = function() {
- this.configureProxyIfNecessary_();
- return this.proxy_;
- };
-
- InterfacePtrController.prototype.enableTestingMode = function() {
- this.configureProxyIfNecessary_();
- return this.router_.enableTestingMode();
- };
-
- InterfacePtrController.prototype.configureProxyIfNecessary_ = function() {
- if (!this.handle_)
- return;
-
- this.router_ = new internal.Router(this.handle_);
- this.handle_ = null;
- this.router_ .setPayloadValidators([this.interfaceType_.validateResponse]);
-
- this.controlMessageProxy_ = new internal.ControlMessageProxy(this.router_);
-
- this.proxy_ = new this.interfaceType_.proxyClass(this.router_);
- };
-
- InterfacePtrController.prototype.queryVersion = function() {
- function onQueryVersion(version) {
- this.version = version;
- return version;
- }
-
- this.configureProxyIfNecessary_();
- return this.controlMessageProxy_.queryVersion().then(
- onQueryVersion.bind(this));
- };
-
- InterfacePtrController.prototype.requireVersion = function(version) {
- this.configureProxyIfNecessary_();
-
- if (this.version >= version) {
- return;
- }
- this.version = version;
- this.controlMessageProxy_.requireVersion(version);
- };
-
- // ---------------------------------------------------------------------------
-
- // |request| could be omitted and passed into bind() later.
- //
- // Example:
- //
- // // FooImpl implements mojom.Foo.
- // function FooImpl() { ... }
- // FooImpl.prototype.fooMethod1 = function() { ... }
- // FooImpl.prototype.fooMethod2 = function() { ... }
- //
- // var fooPtr = new mojom.FooPtr();
- // var request = makeRequest(fooPtr);
- // var binding = new Binding(mojom.Foo, new FooImpl(), request);
- // fooPtr.fooMethod1();
- function Binding(interfaceType, impl, requestOrHandle) {
- this.interfaceType_ = interfaceType;
- this.impl_ = impl;
- this.router_ = null;
- this.stub_ = null;
-
- if (requestOrHandle)
- this.bind(requestOrHandle);
- }
-
- Binding.prototype.isBound = function() {
- return this.router_ !== null;
- };
-
- Binding.prototype.createInterfacePtrAndBind = function() {
- var ptr = new this.interfaceType_.ptrClass();
- // TODO(yzshen): Set the version of the interface pointer.
- this.bind(makeRequest(ptr));
- return ptr;
- }
-
- Binding.prototype.bind = function(requestOrHandle) {
- this.close();
-
- var handle = requestOrHandle instanceof mojo.InterfaceRequest ?
- requestOrHandle.handle : requestOrHandle;
- if (!(handle instanceof MojoHandle))
- return;
-
- this.stub_ = new this.interfaceType_.stubClass(this.impl_);
- this.router_ = new internal.Router(handle, this.interfaceType_.kVersion);
- this.router_.setIncomingReceiver(this.stub_);
- this.router_ .setPayloadValidators([this.interfaceType_.validateRequest]);
- };
-
- Binding.prototype.close = function() {
- if (!this.isBound())
- return;
-
- this.router_.close();
- this.router_ = null;
- this.stub_ = null;
- };
-
- Binding.prototype.setConnectionErrorHandler
- = function(callback) {
- if (!this.isBound())
- throw new Error("Cannot set connection error handler if not bound.");
- this.router_.setErrorHandler(callback);
- };
-
- Binding.prototype.unbind = function() {
- if (!this.isBound())
- return new mojo.InterfaceRequest(null);
-
- var result = new mojo.InterfaceRequest(this.router_.connector_.handle_);
- this.router_.connector_.handle_ = null;
- this.close();
- return result;
- };
-
- Binding.prototype.enableTestingMode = function() {
- return this.router_.enableTestingMode();
- };
-
- // ---------------------------------------------------------------------------
-
- function BindingSetEntry(bindingSet, interfaceType, impl, requestOrHandle,
- bindingId) {
- this.bindingSet_ = bindingSet;
- this.bindingId_ = bindingId;
- this.binding_ = new Binding(interfaceType, impl, requestOrHandle);
-
- this.binding_.setConnectionErrorHandler(function() {
- this.bindingSet_.onConnectionError(bindingId);
- }.bind(this));
- }
-
- BindingSetEntry.prototype.close = function() {
- this.binding_.close();
- };
-
- function BindingSet(interfaceType) {
- this.interfaceType_ = interfaceType;
- this.nextBindingId_ = 0;
- this.bindings_ = new Map();
- this.errorHandler_ = null;
- }
-
- BindingSet.prototype.isEmpty = function() {
- return this.bindings_.size == 0;
- };
-
- BindingSet.prototype.addBinding = function(impl, requestOrHandle) {
- this.bindings_.set(
- this.nextBindingId_,
- new BindingSetEntry(this, this.interfaceType_, impl, requestOrHandle,
- this.nextBindingId_));
- ++this.nextBindingId_;
- };
-
- BindingSet.prototype.closeAllBindings = function() {
- for (var entry of this.bindings_.values())
- entry.close();
- this.bindings_.clear();
- };
-
- BindingSet.prototype.setConnectionErrorHandler = function(callback) {
- this.errorHandler_ = callback;
- };
-
- BindingSet.prototype.onConnectionError = function(bindingId) {
- this.bindings_.delete(bindingId);
-
- if (this.errorHandler_)
- this.errorHandler_();
- };
-
-
- mojo.makeRequest = makeRequest;
- mojo.Binding = Binding;
- mojo.BindingSet = BindingSet;
- mojo.InterfacePtrController = InterfacePtrController;
-})();
diff --git a/mojo/public/js/new_bindings/codec.js b/mojo/public/js/new_bindings/codec.js
deleted file mode 100644
index 339fc169da..0000000000
--- a/mojo/public/js/new_bindings/codec.js
+++ /dev/null
@@ -1,917 +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.
-
-(function() {
- var internal = mojo.internal;
-
- var kErrorUnsigned = "Passing negative value to unsigned";
- var kErrorArray = "Passing non Array for array type";
- var kErrorString = "Passing non String for string type";
- var kErrorMap = "Passing non Map for map type";
-
- // Memory -------------------------------------------------------------------
-
- var kAlignment = 8;
-
- function align(size) {
- return size + (kAlignment - (size % kAlignment)) % kAlignment;
- }
-
- function isAligned(offset) {
- return offset >= 0 && (offset % kAlignment) === 0;
- }
-
- // Constants ----------------------------------------------------------------
-
- var kArrayHeaderSize = 8;
- var kStructHeaderSize = 8;
- var kMessageHeaderSize = 24;
- var kMessageWithRequestIDHeaderSize = 32;
- var kMapStructPayloadSize = 16;
-
- var kStructHeaderNumBytesOffset = 0;
- var kStructHeaderVersionOffset = 4;
-
- var kEncodedInvalidHandleValue = 0xFFFFFFFF;
-
- // Decoder ------------------------------------------------------------------
-
- function Decoder(buffer, handles, base) {
- this.buffer = buffer;
- this.handles = handles;
- this.base = base;
- this.next = base;
- }
-
- Decoder.prototype.align = function() {
- this.next = align(this.next);
- };
-
- Decoder.prototype.skip = function(offset) {
- this.next += offset;
- };
-
- Decoder.prototype.readInt8 = function() {
- var result = this.buffer.getInt8(this.next);
- this.next += 1;
- return result;
- };
-
- Decoder.prototype.readUint8 = function() {
- var result = this.buffer.getUint8(this.next);
- this.next += 1;
- return result;
- };
-
- Decoder.prototype.readInt16 = function() {
- var result = this.buffer.getInt16(this.next);
- this.next += 2;
- return result;
- };
-
- Decoder.prototype.readUint16 = function() {
- var result = this.buffer.getUint16(this.next);
- this.next += 2;
- return result;
- };
-
- Decoder.prototype.readInt32 = function() {
- var result = this.buffer.getInt32(this.next);
- this.next += 4;
- return result;
- };
-
- Decoder.prototype.readUint32 = function() {
- var result = this.buffer.getUint32(this.next);
- this.next += 4;
- return result;
- };
-
- Decoder.prototype.readInt64 = function() {
- var result = this.buffer.getInt64(this.next);
- this.next += 8;
- return result;
- };
-
- Decoder.prototype.readUint64 = function() {
- var result = this.buffer.getUint64(this.next);
- this.next += 8;
- return result;
- };
-
- Decoder.prototype.readFloat = function() {
- var result = this.buffer.getFloat32(this.next);
- this.next += 4;
- return result;
- };
-
- Decoder.prototype.readDouble = function() {
- var result = this.buffer.getFloat64(this.next);
- this.next += 8;
- return result;
- };
-
- Decoder.prototype.decodePointer = function() {
- // TODO(abarth): To correctly decode a pointer, we need to know the real
- // base address of the array buffer.
- var offsetPointer = this.next;
- var offset = this.readUint64();
- if (!offset)
- return 0;
- return offsetPointer + offset;
- };
-
- Decoder.prototype.decodeAndCreateDecoder = function(pointer) {
- return new Decoder(this.buffer, this.handles, pointer);
- };
-
- Decoder.prototype.decodeHandle = function() {
- return this.handles[this.readUint32()] || null;
- };
-
- Decoder.prototype.decodeString = function() {
- var numberOfBytes = this.readUint32();
- var numberOfElements = this.readUint32();
- var base = this.next;
- this.next += numberOfElements;
- return internal.decodeUtf8String(
- new Uint8Array(this.buffer.arrayBuffer, base, numberOfElements));
- };
-
- Decoder.prototype.decodeArray = function(cls) {
- var numberOfBytes = this.readUint32();
- var numberOfElements = this.readUint32();
- var val = new Array(numberOfElements);
- if (cls === PackedBool) {
- var byte;
- for (var i = 0; i < numberOfElements; ++i) {
- if (i % 8 === 0)
- byte = this.readUint8();
- val[i] = (byte & (1 << i % 8)) ? true : false;
- }
- } else {
- for (var i = 0; i < numberOfElements; ++i) {
- val[i] = cls.decode(this);
- }
- }
- return val;
- };
-
- Decoder.prototype.decodeStruct = function(cls) {
- return cls.decode(this);
- };
-
- Decoder.prototype.decodeStructPointer = function(cls) {
- var pointer = this.decodePointer();
- if (!pointer) {
- return null;
- }
- return cls.decode(this.decodeAndCreateDecoder(pointer));
- };
-
- Decoder.prototype.decodeArrayPointer = function(cls) {
- var pointer = this.decodePointer();
- if (!pointer) {
- return null;
- }
- return this.decodeAndCreateDecoder(pointer).decodeArray(cls);
- };
-
- Decoder.prototype.decodeStringPointer = function() {
- var pointer = this.decodePointer();
- if (!pointer) {
- return null;
- }
- return this.decodeAndCreateDecoder(pointer).decodeString();
- };
-
- Decoder.prototype.decodeMap = function(keyClass, valueClass) {
- this.skip(4); // numberOfBytes
- this.skip(4); // version
- var keys = this.decodeArrayPointer(keyClass);
- var values = this.decodeArrayPointer(valueClass);
- var val = new Map();
- for (var i = 0; i < keys.length; i++)
- val.set(keys[i], values[i]);
- return val;
- };
-
- Decoder.prototype.decodeMapPointer = function(keyClass, valueClass) {
- var pointer = this.decodePointer();
- if (!pointer) {
- return null;
- }
- var decoder = this.decodeAndCreateDecoder(pointer);
- return decoder.decodeMap(keyClass, valueClass);
- };
-
- // Encoder ------------------------------------------------------------------
-
- function Encoder(buffer, handles, base) {
- this.buffer = buffer;
- this.handles = handles;
- this.base = base;
- this.next = base;
- }
-
- Encoder.prototype.align = function() {
- this.next = align(this.next);
- };
-
- Encoder.prototype.skip = function(offset) {
- this.next += offset;
- };
-
- Encoder.prototype.writeInt8 = function(val) {
- this.buffer.setInt8(this.next, val);
- this.next += 1;
- };
-
- Encoder.prototype.writeUint8 = function(val) {
- if (val < 0) {
- throw new Error(kErrorUnsigned);
- }
- this.buffer.setUint8(this.next, val);
- this.next += 1;
- };
-
- Encoder.prototype.writeInt16 = function(val) {
- this.buffer.setInt16(this.next, val);
- this.next += 2;
- };
-
- Encoder.prototype.writeUint16 = function(val) {
- if (val < 0) {
- throw new Error(kErrorUnsigned);
- }
- this.buffer.setUint16(this.next, val);
- this.next += 2;
- };
-
- Encoder.prototype.writeInt32 = function(val) {
- this.buffer.setInt32(this.next, val);
- this.next += 4;
- };
-
- Encoder.prototype.writeUint32 = function(val) {
- if (val < 0) {
- throw new Error(kErrorUnsigned);
- }
- this.buffer.setUint32(this.next, val);
- this.next += 4;
- };
-
- Encoder.prototype.writeInt64 = function(val) {
- this.buffer.setInt64(this.next, val);
- this.next += 8;
- };
-
- Encoder.prototype.writeUint64 = function(val) {
- if (val < 0) {
- throw new Error(kErrorUnsigned);
- }
- this.buffer.setUint64(this.next, val);
- this.next += 8;
- };
-
- Encoder.prototype.writeFloat = function(val) {
- this.buffer.setFloat32(this.next, val);
- this.next += 4;
- };
-
- Encoder.prototype.writeDouble = function(val) {
- this.buffer.setFloat64(this.next, val);
- this.next += 8;
- };
-
- Encoder.prototype.encodePointer = function(pointer) {
- if (!pointer)
- return this.writeUint64(0);
- // TODO(abarth): To correctly encode a pointer, we need to know the real
- // base address of the array buffer.
- var offset = pointer - this.next;
- this.writeUint64(offset);
- };
-
- Encoder.prototype.createAndEncodeEncoder = function(size) {
- var pointer = this.buffer.alloc(align(size));
- this.encodePointer(pointer);
- return new Encoder(this.buffer, this.handles, pointer);
- };
-
- Encoder.prototype.encodeHandle = function(handle) {
- if (handle) {
- this.handles.push(handle);
- this.writeUint32(this.handles.length - 1);
- } else {
- this.writeUint32(kEncodedInvalidHandleValue);
- }
- };
-
- Encoder.prototype.encodeString = function(val) {
- var base = this.next + kArrayHeaderSize;
- var numberOfElements = internal.encodeUtf8String(
- val, new Uint8Array(this.buffer.arrayBuffer, base));
- var numberOfBytes = kArrayHeaderSize + numberOfElements;
- this.writeUint32(numberOfBytes);
- this.writeUint32(numberOfElements);
- this.next += numberOfElements;
- };
-
- Encoder.prototype.encodeArray =
- function(cls, val, numberOfElements, encodedSize) {
- if (numberOfElements === undefined)
- numberOfElements = val.length;
- if (encodedSize === undefined)
- encodedSize = kArrayHeaderSize + cls.encodedSize * numberOfElements;
-
- this.writeUint32(encodedSize);
- this.writeUint32(numberOfElements);
-
- if (cls === PackedBool) {
- var byte = 0;
- for (i = 0; i < numberOfElements; ++i) {
- if (val[i])
- byte |= (1 << i % 8);
- if (i % 8 === 7 || i == numberOfElements - 1) {
- Uint8.encode(this, byte);
- byte = 0;
- }
- }
- } else {
- for (var i = 0; i < numberOfElements; ++i)
- cls.encode(this, val[i]);
- }
- };
-
- Encoder.prototype.encodeStruct = function(cls, val) {
- return cls.encode(this, val);
- };
-
- Encoder.prototype.encodeStructPointer = function(cls, val) {
- if (val == null) {
- // Also handles undefined, since undefined == null.
- this.encodePointer(val);
- return;
- }
- var encoder = this.createAndEncodeEncoder(cls.encodedSize);
- cls.encode(encoder, val);
- };
-
- Encoder.prototype.encodeArrayPointer = function(cls, val) {
- if (val == null) {
- // Also handles undefined, since undefined == null.
- this.encodePointer(val);
- return;
- }
-
- var numberOfElements = val.length;
- if (!Number.isSafeInteger(numberOfElements) || numberOfElements < 0)
- throw new Error(kErrorArray);
-
- var encodedSize = kArrayHeaderSize + ((cls === PackedBool) ?
- Math.ceil(numberOfElements / 8) : cls.encodedSize * numberOfElements);
- var encoder = this.createAndEncodeEncoder(encodedSize);
- encoder.encodeArray(cls, val, numberOfElements, encodedSize);
- };
-
- Encoder.prototype.encodeStringPointer = function(val) {
- if (val == null) {
- // Also handles undefined, since undefined == null.
- this.encodePointer(val);
- return;
- }
- // Only accepts string primivites, not String Objects like new String("foo")
- if (typeof(val) !== "string") {
- throw new Error(kErrorString);
- }
- var encodedSize = kArrayHeaderSize + internal.utf8Length(val);
- var encoder = this.createAndEncodeEncoder(encodedSize);
- encoder.encodeString(val);
- };
-
- Encoder.prototype.encodeMap = function(keyClass, valueClass, val) {
- var keys = new Array(val.size);
- var values = new Array(val.size);
- var i = 0;
- val.forEach(function(value, key) {
- values[i] = value;
- keys[i++] = key;
- });
- this.writeUint32(kStructHeaderSize + kMapStructPayloadSize);
- this.writeUint32(0); // version
- this.encodeArrayPointer(keyClass, keys);
- this.encodeArrayPointer(valueClass, values);
- }
-
- Encoder.prototype.encodeMapPointer = function(keyClass, valueClass, val) {
- if (val == null) {
- // Also handles undefined, since undefined == null.
- this.encodePointer(val);
- return;
- }
- if (!(val instanceof Map)) {
- throw new Error(kErrorMap);
- }
- var encodedSize = kStructHeaderSize + kMapStructPayloadSize;
- var encoder = this.createAndEncodeEncoder(encodedSize);
- encoder.encodeMap(keyClass, valueClass, val);
- };
-
- // Message ------------------------------------------------------------------
-
- var kMessageInterfaceIdOffset = kStructHeaderSize;
- var kMessageNameOffset = kMessageInterfaceIdOffset + 4;
- var kMessageFlagsOffset = kMessageNameOffset + 4;
- var kMessageRequestIDOffset = kMessageFlagsOffset + 8;
-
- var kMessageExpectsResponse = 1 << 0;
- var kMessageIsResponse = 1 << 1;
-
- function Message(buffer, handles) {
- this.buffer = buffer;
- this.handles = handles;
- }
-
- Message.prototype.getHeaderNumBytes = function() {
- return this.buffer.getUint32(kStructHeaderNumBytesOffset);
- };
-
- Message.prototype.getHeaderVersion = function() {
- return this.buffer.getUint32(kStructHeaderVersionOffset);
- };
-
- Message.prototype.getName = function() {
- return this.buffer.getUint32(kMessageNameOffset);
- };
-
- Message.prototype.getFlags = function() {
- return this.buffer.getUint32(kMessageFlagsOffset);
- };
-
- Message.prototype.isResponse = function() {
- return (this.getFlags() & kMessageIsResponse) != 0;
- };
-
- Message.prototype.expectsResponse = function() {
- return (this.getFlags() & kMessageExpectsResponse) != 0;
- };
-
- Message.prototype.setRequestID = function(requestID) {
- // TODO(darin): Verify that space was reserved for this field!
- this.buffer.setUint64(kMessageRequestIDOffset, requestID);
- };
-
-
- // MessageBuilder -----------------------------------------------------------
-
- function MessageBuilder(messageName, payloadSize) {
- // Currently, we don't compute the payload size correctly ahead of time.
- // Instead, we resize the buffer at the end.
- var numberOfBytes = kMessageHeaderSize + payloadSize;
- this.buffer = new internal.Buffer(numberOfBytes);
- this.handles = [];
- var encoder = this.createEncoder(kMessageHeaderSize);
- encoder.writeUint32(kMessageHeaderSize);
- encoder.writeUint32(0); // version.
- encoder.writeUint32(0); // interface ID.
- encoder.writeUint32(messageName);
- encoder.writeUint32(0); // flags.
- encoder.writeUint32(0); // padding.
- }
-
- MessageBuilder.prototype.createEncoder = function(size) {
- var pointer = this.buffer.alloc(size);
- return new Encoder(this.buffer, this.handles, pointer);
- };
-
- MessageBuilder.prototype.encodeStruct = function(cls, val) {
- cls.encode(this.createEncoder(cls.encodedSize), val);
- };
-
- MessageBuilder.prototype.finish = function() {
- // TODO(abarth): Rather than resizing the buffer at the end, we could
- // compute the size we need ahead of time, like we do in C++.
- this.buffer.trim();
- var message = new Message(this.buffer, this.handles);
- this.buffer = null;
- this.handles = null;
- this.encoder = null;
- return message;
- };
-
- // MessageWithRequestIDBuilder -----------------------------------------------
-
- function MessageWithRequestIDBuilder(messageName, payloadSize, flags,
- requestID) {
- // Currently, we don't compute the payload size correctly ahead of time.
- // Instead, we resize the buffer at the end.
- var numberOfBytes = kMessageWithRequestIDHeaderSize + payloadSize;
- this.buffer = new internal.Buffer(numberOfBytes);
- this.handles = [];
- var encoder = this.createEncoder(kMessageWithRequestIDHeaderSize);
- encoder.writeUint32(kMessageWithRequestIDHeaderSize);
- encoder.writeUint32(1); // version.
- encoder.writeUint32(0); // interface ID.
- encoder.writeUint32(messageName);
- encoder.writeUint32(flags);
- encoder.writeUint32(0); // padding.
- encoder.writeUint64(requestID);
- }
-
- MessageWithRequestIDBuilder.prototype =
- Object.create(MessageBuilder.prototype);
-
- MessageWithRequestIDBuilder.prototype.constructor =
- MessageWithRequestIDBuilder;
-
- // MessageReader ------------------------------------------------------------
-
- function MessageReader(message) {
- this.decoder = new Decoder(message.buffer, message.handles, 0);
- var messageHeaderSize = this.decoder.readUint32();
- this.payloadSize = message.buffer.byteLength - messageHeaderSize;
- var version = this.decoder.readUint32();
- var interface_id = this.decoder.readUint32();
- if (interface_id != 0) {
- throw new Error("Receiving non-zero interface ID. Associated interfaces " +
- "are not yet supported.");
- }
- this.messageName = this.decoder.readUint32();
- this.flags = this.decoder.readUint32();
- // Skip the padding.
- this.decoder.skip(4);
- if (version >= 1)
- this.requestID = this.decoder.readUint64();
- this.decoder.skip(messageHeaderSize - this.decoder.next);
- }
-
- MessageReader.prototype.decodeStruct = function(cls) {
- return cls.decode(this.decoder);
- };
-
- // Built-in types -----------------------------------------------------------
-
- // This type is only used with ArrayOf(PackedBool).
- function PackedBool() {
- }
-
- function Int8() {
- }
-
- Int8.encodedSize = 1;
-
- Int8.decode = function(decoder) {
- return decoder.readInt8();
- };
-
- Int8.encode = function(encoder, val) {
- encoder.writeInt8(val);
- };
-
- Uint8.encode = function(encoder, val) {
- encoder.writeUint8(val);
- };
-
- function Uint8() {
- }
-
- Uint8.encodedSize = 1;
-
- Uint8.decode = function(decoder) {
- return decoder.readUint8();
- };
-
- Uint8.encode = function(encoder, val) {
- encoder.writeUint8(val);
- };
-
- function Int16() {
- }
-
- Int16.encodedSize = 2;
-
- Int16.decode = function(decoder) {
- return decoder.readInt16();
- };
-
- Int16.encode = function(encoder, val) {
- encoder.writeInt16(val);
- };
-
- function Uint16() {
- }
-
- Uint16.encodedSize = 2;
-
- Uint16.decode = function(decoder) {
- return decoder.readUint16();
- };
-
- Uint16.encode = function(encoder, val) {
- encoder.writeUint16(val);
- };
-
- function Int32() {
- }
-
- Int32.encodedSize = 4;
-
- Int32.decode = function(decoder) {
- return decoder.readInt32();
- };
-
- Int32.encode = function(encoder, val) {
- encoder.writeInt32(val);
- };
-
- function Uint32() {
- }
-
- Uint32.encodedSize = 4;
-
- Uint32.decode = function(decoder) {
- return decoder.readUint32();
- };
-
- Uint32.encode = function(encoder, val) {
- encoder.writeUint32(val);
- };
-
- function Int64() {
- }
-
- Int64.encodedSize = 8;
-
- Int64.decode = function(decoder) {
- return decoder.readInt64();
- };
-
- Int64.encode = function(encoder, val) {
- encoder.writeInt64(val);
- };
-
- function Uint64() {
- }
-
- Uint64.encodedSize = 8;
-
- Uint64.decode = function(decoder) {
- return decoder.readUint64();
- };
-
- Uint64.encode = function(encoder, val) {
- encoder.writeUint64(val);
- };
-
- function String() {
- };
-
- String.encodedSize = 8;
-
- String.decode = function(decoder) {
- return decoder.decodeStringPointer();
- };
-
- String.encode = function(encoder, val) {
- encoder.encodeStringPointer(val);
- };
-
- function NullableString() {
- }
-
- NullableString.encodedSize = String.encodedSize;
-
- NullableString.decode = String.decode;
-
- NullableString.encode = String.encode;
-
- function Float() {
- }
-
- Float.encodedSize = 4;
-
- Float.decode = function(decoder) {
- return decoder.readFloat();
- };
-
- Float.encode = function(encoder, val) {
- encoder.writeFloat(val);
- };
-
- function Double() {
- }
-
- Double.encodedSize = 8;
-
- Double.decode = function(decoder) {
- return decoder.readDouble();
- };
-
- Double.encode = function(encoder, val) {
- encoder.writeDouble(val);
- };
-
- function Enum(cls) {
- this.cls = cls;
- }
-
- Enum.prototype.encodedSize = 4;
-
- Enum.prototype.decode = function(decoder) {
- return decoder.readInt32();
- };
-
- Enum.prototype.encode = function(encoder, val) {
- encoder.writeInt32(val);
- };
-
- function PointerTo(cls) {
- this.cls = cls;
- }
-
- PointerTo.prototype.encodedSize = 8;
-
- PointerTo.prototype.decode = function(decoder) {
- var pointer = decoder.decodePointer();
- if (!pointer) {
- return null;
- }
- return this.cls.decode(decoder.decodeAndCreateDecoder(pointer));
- };
-
- PointerTo.prototype.encode = function(encoder, val) {
- if (!val) {
- encoder.encodePointer(val);
- return;
- }
- var objectEncoder = encoder.createAndEncodeEncoder(this.cls.encodedSize);
- this.cls.encode(objectEncoder, val);
- };
-
- function NullablePointerTo(cls) {
- PointerTo.call(this, cls);
- }
-
- NullablePointerTo.prototype = Object.create(PointerTo.prototype);
-
- function ArrayOf(cls, length) {
- this.cls = cls;
- this.length = length || 0;
- }
-
- ArrayOf.prototype.encodedSize = 8;
-
- ArrayOf.prototype.dimensions = function() {
- return [this.length].concat(
- (this.cls instanceof ArrayOf) ? this.cls.dimensions() : []);
- }
-
- ArrayOf.prototype.decode = function(decoder) {
- return decoder.decodeArrayPointer(this.cls);
- };
-
- ArrayOf.prototype.encode = function(encoder, val) {
- encoder.encodeArrayPointer(this.cls, val);
- };
-
- function NullableArrayOf(cls) {
- ArrayOf.call(this, cls);
- }
-
- NullableArrayOf.prototype = Object.create(ArrayOf.prototype);
-
- function Handle() {
- }
-
- Handle.encodedSize = 4;
-
- Handle.decode = function(decoder) {
- return decoder.decodeHandle();
- };
-
- Handle.encode = function(encoder, val) {
- encoder.encodeHandle(val);
- };
-
- function NullableHandle() {
- }
-
- NullableHandle.encodedSize = Handle.encodedSize;
-
- NullableHandle.decode = Handle.decode;
-
- NullableHandle.encode = Handle.encode;
-
- function Interface(cls) {
- this.cls = cls;
- }
-
- Interface.prototype.encodedSize = 8;
-
- Interface.prototype.decode = function(decoder) {
- var interfacePtrInfo = new mojo.InterfacePtrInfo(
- decoder.decodeHandle(), decoder.readUint32());
- var interfacePtr = new this.cls();
- interfacePtr.ptr.bind(interfacePtrInfo);
- return interfacePtr;
- };
-
- Interface.prototype.encode = function(encoder, val) {
- var interfacePtrInfo =
- val ? val.ptr.passInterface() : new mojo.InterfacePtrInfo(null, 0);
- encoder.encodeHandle(interfacePtrInfo.handle);
- encoder.writeUint32(interfacePtrInfo.version);
- };
-
- function NullableInterface(cls) {
- Interface.call(this, cls);
- }
-
- NullableInterface.prototype = Object.create(Interface.prototype);
-
- function InterfaceRequest() {
- }
-
- InterfaceRequest.encodedSize = 4;
-
- InterfaceRequest.decode = function(decoder) {
- return new mojo.InterfaceRequest(decoder.decodeHandle());
- };
-
- InterfaceRequest.encode = function(encoder, val) {
- encoder.encodeHandle(val ? val.handle : null);
- };
-
- function NullableInterfaceRequest() {
- }
-
- NullableInterfaceRequest.encodedSize = InterfaceRequest.encodedSize;
-
- NullableInterfaceRequest.decode = InterfaceRequest.decode;
-
- NullableInterfaceRequest.encode = InterfaceRequest.encode;
-
- function MapOf(keyClass, valueClass) {
- this.keyClass = keyClass;
- this.valueClass = valueClass;
- }
-
- MapOf.prototype.encodedSize = 8;
-
- MapOf.prototype.decode = function(decoder) {
- return decoder.decodeMapPointer(this.keyClass, this.valueClass);
- };
-
- MapOf.prototype.encode = function(encoder, val) {
- encoder.encodeMapPointer(this.keyClass, this.valueClass, val);
- };
-
- function NullableMapOf(keyClass, valueClass) {
- MapOf.call(this, keyClass, valueClass);
- }
-
- NullableMapOf.prototype = Object.create(MapOf.prototype);
-
- internal.align = align;
- internal.isAligned = isAligned;
- internal.Message = Message;
- internal.MessageBuilder = MessageBuilder;
- internal.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder;
- internal.MessageReader = MessageReader;
- internal.kArrayHeaderSize = kArrayHeaderSize;
- internal.kMapStructPayloadSize = kMapStructPayloadSize;
- internal.kStructHeaderSize = kStructHeaderSize;
- internal.kEncodedInvalidHandleValue = kEncodedInvalidHandleValue;
- internal.kMessageHeaderSize = kMessageHeaderSize;
- internal.kMessageWithRequestIDHeaderSize = kMessageWithRequestIDHeaderSize;
- internal.kMessageExpectsResponse = kMessageExpectsResponse;
- internal.kMessageIsResponse = kMessageIsResponse;
- internal.Int8 = Int8;
- internal.Uint8 = Uint8;
- internal.Int16 = Int16;
- internal.Uint16 = Uint16;
- internal.Int32 = Int32;
- internal.Uint32 = Uint32;
- internal.Int64 = Int64;
- internal.Uint64 = Uint64;
- internal.Float = Float;
- internal.Double = Double;
- internal.String = String;
- internal.Enum = Enum;
- internal.NullableString = NullableString;
- internal.PointerTo = PointerTo;
- internal.NullablePointerTo = NullablePointerTo;
- internal.ArrayOf = ArrayOf;
- internal.NullableArrayOf = NullableArrayOf;
- internal.PackedBool = PackedBool;
- internal.Handle = Handle;
- internal.NullableHandle = NullableHandle;
- internal.Interface = Interface;
- internal.NullableInterface = NullableInterface;
- internal.InterfaceRequest = InterfaceRequest;
- internal.NullableInterfaceRequest = NullableInterfaceRequest;
- internal.MapOf = MapOf;
- internal.NullableMapOf = NullableMapOf;
-})();
diff --git a/mojo/public/js/new_bindings/connector.js b/mojo/public/js/new_bindings/connector.js
deleted file mode 100644
index 7fa4822f89..0000000000
--- a/mojo/public/js/new_bindings/connector.js
+++ /dev/null
@@ -1,104 +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.
-
-(function() {
- var internal = mojo.internal;
-
- function Connector(handle) {
- if (!(handle instanceof MojoHandle))
- throw new Error("Connector: not a handle " + handle);
- this.handle_ = handle;
- this.dropWrites_ = false;
- this.error_ = false;
- this.incomingReceiver_ = null;
- this.readWatcher_ = null;
- this.errorHandler_ = null;
-
- if (handle) {
- this.readWatcher_ = handle.watch({readable: true},
- this.readMore_.bind(this));
- }
- }
-
- Connector.prototype.close = function() {
- if (this.readWatcher_) {
- this.readWatcher_.cancel();
- this.readWatcher_ = null;
- }
- if (this.handle_ != null) {
- this.handle_.close();
- this.handle_ = null;
- }
- };
-
- Connector.prototype.accept = function(message) {
- if (this.error_)
- return false;
-
- if (this.dropWrites_)
- return true;
-
- var result = this.handle_.writeMessage(
- new Uint8Array(message.buffer.arrayBuffer), message.handles);
- switch (result) {
- case Mojo.RESULT_OK:
- // The handles were successfully transferred, so we don't own them
- // anymore.
- message.handles = [];
- 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.
- this.dropWrites_ = true;
- break;
- default:
- // This particular write was rejected, presumably because of bad input.
- // The pipe is not necessarily in a bad state.
- return false;
- }
- return true;
- };
-
- Connector.prototype.setIncomingReceiver = function(receiver) {
- this.incomingReceiver_ = receiver;
- };
-
- Connector.prototype.setErrorHandler = function(handler) {
- this.errorHandler_ = handler;
- };
-
- Connector.prototype.encounteredError = function() {
- return this.error_;
- };
-
- Connector.prototype.waitForNextMessageForTesting = function() {
- // TODO(yzshen): Change the tests that use this method.
- throw new Error("Not supported!");
- };
-
- Connector.prototype.readMore_ = function(result) {
- for (;;) {
- var read = this.handle_.readMessage();
- if (this.handle_ == null) // The connector has been closed.
- return;
- if (read.result == Mojo.RESULT_SHOULD_WAIT)
- return;
- if (read.result != Mojo.RESULT_OK) {
- this.error_ = true;
- if (this.errorHandler_)
- this.errorHandler_.onError(read.result);
- return;
- }
- var messageBuffer = new internal.Buffer(read.buffer);
- var message = new internal.Message(messageBuffer, read.handles);
- if (this.incomingReceiver_)
- this.incomingReceiver_.accept(message);
- }
- };
-
- internal.Connector = Connector;
-})();
diff --git a/mojo/public/js/new_bindings/interface_types.js b/mojo/public/js/new_bindings/interface_types.js
deleted file mode 100644
index c52f6c7e55..0000000000
--- a/mojo/public/js/new_bindings/interface_types.js
+++ /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.
-
-(function() {
- // ---------------------------------------------------------------------------
-
- function InterfacePtrInfo(handle, version) {
- this.handle = handle;
- this.version = version;
- }
-
- InterfacePtrInfo.prototype.isValid = function() {
- return this.handle instanceof MojoHandle;
- };
-
- InterfacePtrInfo.prototype.close = function() {
- if (!this.isValid())
- return;
-
- this.handle.close();
- this.handle = null;
- this.version = 0;
- };
-
- // ---------------------------------------------------------------------------
-
- function InterfaceRequest(handle) {
- this.handle = handle;
- }
-
- InterfaceRequest.prototype.isValid = function() {
- return this.handle instanceof MojoHandle;
- };
-
- InterfaceRequest.prototype.close = function() {
- if (!this.isValid())
- return;
-
- this.handle.close();
- this.handle = null;
- };
-
- mojo.InterfacePtrInfo = InterfacePtrInfo;
- mojo.InterfaceRequest = InterfaceRequest;
-})();
diff --git a/mojo/public/js/new_bindings/lib/control_message_handler.js b/mojo/public/js/new_bindings/lib/control_message_handler.js
deleted file mode 100644
index 3f122fb379..0000000000
--- a/mojo/public/js/new_bindings/lib/control_message_handler.js
+++ /dev/null
@@ -1,106 +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.
-
-(function() {
- var internal = mojo.internal;
-
- function validateControlRequestWithResponse(message) {
- var messageValidator = new internal.Validator(message);
- var error = messageValidator.validateMessageIsRequestExpectingResponse();
- if (error !== internal.validationError.NONE) {
- throw error;
- }
-
- if (message.getName() != mojo.interface_control2.kRunMessageId) {
- throw new Error("Control message name is not kRunMessageId");
- }
-
- // Validate payload.
- error = mojo.interface_control2.RunMessageParams.validate(messageValidator,
- message.getHeaderNumBytes());
- if (error != internal.validationError.NONE) {
- throw error;
- }
- }
-
- function validateControlRequestWithoutResponse(message) {
- var messageValidator = new internal.Validator(message);
- var error = messageValidator.validateMessageIsRequestWithoutResponse();
- if (error != internal.validationError.NONE) {
- throw error;
- }
-
- if (message.getName() != mojo.interface_control2.kRunOrClosePipeMessageId) {
- throw new Error("Control message name is not kRunOrClosePipeMessageId");
- }
-
- // Validate payload.
- error = mojo.interface_control2.RunOrClosePipeMessageParams.validate(
- messageValidator, message.getHeaderNumBytes());
- if (error != internal.validationError.NONE) {
- throw error;
- }
- }
-
- function runOrClosePipe(message, interface_version) {
- var reader = new internal.MessageReader(message);
- var runOrClosePipeMessageParams = reader.decodeStruct(
- mojo.interface_control2.RunOrClosePipeMessageParams);
- return interface_version >=
- runOrClosePipeMessageParams.input.require_version.version;
- }
-
- function run(message, responder, interface_version) {
- var reader = new internal.MessageReader(message);
- var runMessageParams =
- reader.decodeStruct(mojo.interface_control2.RunMessageParams);
- var runOutput = null;
-
- if (runMessageParams.input.query_version) {
- runOutput = new mojo.interface_control2.RunOutput();
- runOutput.query_version_result = new
- mojo.interface_control2.QueryVersionResult(
- {'version': interface_version});
- }
-
- var runResponseMessageParams = new
- mojo.interface_control2.RunResponseMessageParams();
- runResponseMessageParams.output = runOutput;
-
- var messageName = mojo.interface_control2.kRunMessageId;
- var payloadSize =
- mojo.interface_control2.RunResponseMessageParams.encodedSize;
- var requestID = reader.requestID;
- var builder = new internal.MessageWithRequestIDBuilder(messageName,
- payloadSize, internal.kMessageIsResponse, requestID);
- builder.encodeStruct(mojo.interface_control2.RunResponseMessageParams,
- runResponseMessageParams);
- responder.accept(builder.finish());
- return true;
- }
-
- function isInterfaceControlMessage(message) {
- return message.getName() == mojo.interface_control2.kRunMessageId ||
- message.getName() ==
- mojo.interface_control2.kRunOrClosePipeMessageId;
- }
-
- function ControlMessageHandler(interface_version) {
- this.interface_version = interface_version;
- }
-
- ControlMessageHandler.prototype.accept = function(message) {
- validateControlRequestWithoutResponse(message);
- return runOrClosePipe(message, this.interface_version);
- };
-
- ControlMessageHandler.prototype.acceptWithResponder = function(message,
- responder) {
- validateControlRequestWithResponse(message);
- return run(message, responder, this.interface_version);
- };
-
- internal.ControlMessageHandler = ControlMessageHandler;
- internal.isInterfaceControlMessage = isInterfaceControlMessage;
-})();
diff --git a/mojo/public/js/new_bindings/lib/control_message_proxy.js b/mojo/public/js/new_bindings/lib/control_message_proxy.js
deleted file mode 100644
index 1d57557ae2..0000000000
--- a/mojo/public/js/new_bindings/lib/control_message_proxy.js
+++ /dev/null
@@ -1,97 +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.
-
-(function() {
- var internal = mojo.internal;
-
- function sendRunOrClosePipeMessage(receiver, runOrClosePipeMessageParams) {
- var messageName = mojo.interface_control2.kRunOrClosePipeMessageId;
- var payloadSize =
- mojo.interface_control2.RunOrClosePipeMessageParams.encodedSize;
- var builder = new internal.MessageBuilder(messageName, payloadSize);
- builder.encodeStruct(mojo.interface_control2.RunOrClosePipeMessageParams,
- runOrClosePipeMessageParams);
- var message = builder.finish();
- receiver.accept(message);
- }
-
- function validateControlResponse(message) {
- var messageValidator = new internal.Validator(message);
- var error = messageValidator.validateMessageIsResponse();
- if (error != internal.validationError.NONE) {
- throw error;
- }
-
- if (message.getName() != mojo.interface_control2.kRunMessageId) {
- throw new Error("Control message name is not kRunMessageId");
- }
-
- // Validate payload.
- error = mojo.interface_control2.RunResponseMessageParams.validate(
- messageValidator, message.getHeaderNumBytes());
- if (error != internal.validationError.NONE) {
- throw error;
- }
- }
-
- function acceptRunResponse(message) {
- validateControlResponse(message);
-
- var reader = new internal.MessageReader(message);
- var runResponseMessageParams = reader.decodeStruct(
- mojo.interface_control2.RunResponseMessageParams);
-
- return Promise.resolve(runResponseMessageParams);
- }
-
- /**
- * Sends the given run message through the receiver.
- * Accepts the response message from the receiver and decodes the message
- * struct to RunResponseMessageParams.
- *
- * @param {Router} receiver.
- * @param {RunMessageParams} runMessageParams to be sent via a message.
- * @return {Promise} that resolves to a RunResponseMessageParams.
- */
- function sendRunMessage(receiver, runMessageParams) {
- var messageName = mojo.interface_control2.kRunMessageId;
- var payloadSize = mojo.interface_control2.RunMessageParams.encodedSize;
- // |requestID| is set to 0, but is later properly set by Router.
- var builder = new internal.MessageWithRequestIDBuilder(messageName,
- payloadSize, internal.kMessageExpectsResponse, 0);
- builder.encodeStruct(mojo.interface_control2.RunMessageParams,
- runMessageParams);
- var message = builder.finish();
-
- return receiver.acceptAndExpectResponse(message).then(acceptRunResponse);
- }
-
- function ControlMessageProxy(receiver) {
- this.receiver = receiver;
- }
-
- ControlMessageProxy.prototype.queryVersion = function() {
- var runMessageParams = new mojo.interface_control2.RunMessageParams();
- runMessageParams.input = new mojo.interface_control2.RunInput();
- runMessageParams.input.query_version =
- new mojo.interface_control2.QueryVersion();
-
- return sendRunMessage(this.receiver, runMessageParams).then(function(
- runResponseMessageParams) {
- return runResponseMessageParams.output.query_version_result.version;
- });
- };
-
- ControlMessageProxy.prototype.requireVersion = function(version) {
- var runOrClosePipeMessageParams = new
- mojo.interface_control2.RunOrClosePipeMessageParams();
- runOrClosePipeMessageParams.input = new
- mojo.interface_control2.RunOrClosePipeInput();
- runOrClosePipeMessageParams.input.require_version = new
- mojo.interface_control2.RequireVersion({'version': version});
- sendRunOrClosePipeMessage(this.receiver, runOrClosePipeMessageParams);
- };
-
- internal.ControlMessageProxy = ControlMessageProxy;
-})();
diff --git a/mojo/public/js/new_bindings/router.js b/mojo/public/js/new_bindings/router.js
deleted file mode 100644
index 1272407c1e..0000000000
--- a/mojo/public/js/new_bindings/router.js
+++ /dev/null
@@ -1,190 +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.
-
-(function() {
- var internal = mojo.internal;
-
- function Router(handle, interface_version, connectorFactory) {
- if (!(handle instanceof MojoHandle))
- throw new Error("Router constructor: Not a handle");
- if (connectorFactory === undefined)
- connectorFactory = internal.Connector;
- this.connector_ = new connectorFactory(handle);
- this.incomingReceiver_ = null;
- this.errorHandler_ = null;
- this.nextRequestID_ = 0;
- this.completers_ = new Map();
- this.payloadValidators_ = [];
- this.testingController_ = null;
-
- if (interface_version !== undefined) {
- this.controlMessageHandler_ = new
- internal.ControlMessageHandler(interface_version);
- }
-
- this.connector_.setIncomingReceiver({
- accept: this.handleIncomingMessage_.bind(this),
- });
- this.connector_.setErrorHandler({
- onError: this.handleConnectionError_.bind(this),
- });
- }
-
- Router.prototype.close = function() {
- this.completers_.clear(); // Drop any responders.
- this.connector_.close();
- this.testingController_ = null;
- };
-
- Router.prototype.accept = function(message) {
- this.connector_.accept(message);
- };
-
- Router.prototype.reject = function(message) {
- // TODO(mpcomplete): no way to trasmit errors over a Connection.
- };
-
- Router.prototype.acceptAndExpectResponse = function(message) {
- // Reserve 0 in case we want it to convey special meaning in the future.
- var requestID = this.nextRequestID_++;
- if (requestID == 0)
- requestID = this.nextRequestID_++;
-
- message.setRequestID(requestID);
- var result = this.connector_.accept(message);
- if (!result)
- return Promise.reject(Error("Connection error"));
-
- var completer = {};
- this.completers_.set(requestID, completer);
- return new Promise(function(resolve, reject) {
- completer.resolve = resolve;
- completer.reject = reject;
- });
- };
-
- Router.prototype.setIncomingReceiver = function(receiver) {
- this.incomingReceiver_ = receiver;
- };
-
- Router.prototype.setPayloadValidators = function(payloadValidators) {
- this.payloadValidators_ = payloadValidators;
- };
-
- Router.prototype.setErrorHandler = function(handler) {
- this.errorHandler_ = handler;
- };
-
- Router.prototype.encounteredError = function() {
- return this.connector_.encounteredError();
- };
-
- Router.prototype.enableTestingMode = function() {
- this.testingController_ = new RouterTestingController(this.connector_);
- return this.testingController_;
- };
-
- Router.prototype.handleIncomingMessage_ = function(message) {
- var noError = internal.validationError.NONE;
- var messageValidator = new internal.Validator(message);
- var err = messageValidator.validateMessageHeader();
- for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i)
- err = this.payloadValidators_[i](messageValidator);
-
- if (err == noError)
- this.handleValidIncomingMessage_(message);
- else
- this.handleInvalidIncomingMessage_(message, err);
- };
-
- Router.prototype.handleValidIncomingMessage_ = function(message) {
- if (this.testingController_)
- return;
-
- if (message.expectsResponse()) {
- if (internal.isInterfaceControlMessage(message)) {
- if (this.controlMessageHandler_) {
- this.controlMessageHandler_.acceptWithResponder(message, this);
- } else {
- this.close();
- }
- } else if (this.incomingReceiver_) {
- this.incomingReceiver_.acceptWithResponder(message, this);
- } else {
- // If we receive a request expecting a response when the client is not
- // listening, then we have no choice but to tear down the pipe.
- this.close();
- }
- } else if (message.isResponse()) {
- var reader = new internal.MessageReader(message);
- var requestID = reader.requestID;
- var completer = this.completers_.get(requestID);
- if (completer) {
- this.completers_.delete(requestID);
- completer.resolve(message);
- } else {
- console.log("Unexpected response with request ID: " + requestID);
- }
- } else {
- if (internal.isInterfaceControlMessage(message)) {
- if (this.controlMessageHandler_) {
- var ok = this.controlMessageHandler_.accept(message);
- if (ok) return;
- }
- this.close();
- } else if (this.incomingReceiver_) {
- this.incomingReceiver_.accept(message);
- }
- }
- };
-
- Router.prototype.handleInvalidIncomingMessage_ = function(message, error) {
- if (!this.testingController_) {
- // TODO(yzshen): Consider notifying the embedder.
- // TODO(yzshen): This should also trigger connection error handler.
- // Consider making accept() return a boolean and let the connector deal
- // with this, as the C++ code does.
- console.log("Invalid message: " + internal.validationError[error]);
-
- this.close();
- return;
- }
-
- this.testingController_.onInvalidIncomingMessage(error);
- };
-
- Router.prototype.handleConnectionError_ = function(result) {
- this.completers_.forEach(function(value) {
- value.reject(result);
- });
- if (this.errorHandler_)
- this.errorHandler_();
- this.close();
- };
-
- // The RouterTestingController is used in unit tests. It defeats valid message
- // handling and delgates invalid message handling.
-
- function RouterTestingController(connector) {
- this.connector_ = connector;
- this.invalidMessageHandler_ = null;
- }
-
- RouterTestingController.prototype.waitForNextMessage = function() {
- this.connector_.waitForNextMessageForTesting();
- };
-
- RouterTestingController.prototype.setInvalidIncomingMessageHandler =
- function(callback) {
- this.invalidMessageHandler_ = callback;
- };
-
- RouterTestingController.prototype.onInvalidIncomingMessage =
- function(error) {
- if (this.invalidMessageHandler_)
- this.invalidMessageHandler_(error);
- };
-
- internal.Router = Router;
-})();
diff --git a/mojo/public/js/new_bindings/unicode.js b/mojo/public/js/new_bindings/unicode.js
deleted file mode 100644
index 6ed8839c3f..0000000000
--- a/mojo/public/js/new_bindings/unicode.js
+++ /dev/null
@@ -1,51 +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.
-
-/**
- * Defines functions for translating between JavaScript strings and UTF8 strings
- * stored in ArrayBuffers. There is much room for optimization in this code if
- * it proves necessary.
- */
-(function() {
- var internal = mojo.internal;
-
- /**
- * Decodes the UTF8 string from the given buffer.
- * @param {ArrayBufferView} buffer The buffer containing UTF8 string data.
- * @return {string} The corresponding JavaScript string.
- */
- function decodeUtf8String(buffer) {
- return decodeURIComponent(escape(String.fromCharCode.apply(null, buffer)));
- }
-
- /**
- * Encodes the given JavaScript string into UTF8.
- * @param {string} str The string to encode.
- * @param {ArrayBufferView} outputBuffer The buffer to contain the result.
- * Should be pre-allocated to hold enough space. Use |utf8Length| to determine
- * how much space is required.
- * @return {number} The number of bytes written to |outputBuffer|.
- */
- function encodeUtf8String(str, outputBuffer) {
- var utf8String = unescape(encodeURIComponent(str));
- if (outputBuffer.length < utf8String.length)
- throw new Error("Buffer too small for encodeUtf8String");
- for (var i = 0; i < outputBuffer.length && i < utf8String.length; i++)
- outputBuffer[i] = utf8String.charCodeAt(i);
- return i;
- }
-
- /**
- * Returns the number of bytes that a UTF8 encoding of the JavaScript string
- * |str| would occupy.
- */
- function utf8Length(str) {
- var utf8String = unescape(encodeURIComponent(str));
- return utf8String.length;
- }
-
- internal.decodeUtf8String = decodeUtf8String;
- internal.encodeUtf8String = encodeUtf8String;
- internal.utf8Length = utf8Length;
-})();
diff --git a/mojo/public/js/new_bindings/validator.js b/mojo/public/js/new_bindings/validator.js
deleted file mode 100644
index 610112b58e..0000000000
--- a/mojo/public/js/new_bindings/validator.js
+++ /dev/null
@@ -1,511 +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.
-
-(function() {
- var internal = mojo.internal;
-
- var validationError = {
- NONE: 'VALIDATION_ERROR_NONE',
- MISALIGNED_OBJECT: 'VALIDATION_ERROR_MISALIGNED_OBJECT',
- ILLEGAL_MEMORY_RANGE: 'VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE',
- UNEXPECTED_STRUCT_HEADER: 'VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER',
- UNEXPECTED_ARRAY_HEADER: 'VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER',
- ILLEGAL_HANDLE: 'VALIDATION_ERROR_ILLEGAL_HANDLE',
- UNEXPECTED_INVALID_HANDLE: 'VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE',
- ILLEGAL_POINTER: 'VALIDATION_ERROR_ILLEGAL_POINTER',
- UNEXPECTED_NULL_POINTER: 'VALIDATION_ERROR_UNEXPECTED_NULL_POINTER',
- MESSAGE_HEADER_INVALID_FLAGS:
- 'VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS',
- MESSAGE_HEADER_MISSING_REQUEST_ID:
- 'VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID',
- DIFFERENT_SIZED_ARRAYS_IN_MAP:
- 'VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP',
- INVALID_UNION_SIZE: 'VALIDATION_ERROR_INVALID_UNION_SIZE',
- UNEXPECTED_NULL_UNION: 'VALIDATION_ERROR_UNEXPECTED_NULL_UNION',
- UNKNOWN_ENUM_VALUE: 'VALIDATION_ERROR_UNKNOWN_ENUM_VALUE',
- };
-
- var NULL_MOJO_POINTER = "NULL_MOJO_POINTER";
-
- function isEnumClass(cls) {
- return cls instanceof internal.Enum;
- }
-
- function isStringClass(cls) {
- return cls === internal.String || cls === internal.NullableString;
- }
-
- function isHandleClass(cls) {
- return cls === internal.Handle || cls === internal.NullableHandle;
- }
-
- function isInterfaceClass(cls) {
- return cls instanceof internal.Interface;
- }
-
- function isInterfaceRequestClass(cls) {
- return cls === internal.InterfaceRequest ||
- cls === internal.NullableInterfaceRequest;
- }
-
- function isNullable(type) {
- return type === internal.NullableString ||
- type === internal.NullableHandle ||
- type === internal.NullableInterface ||
- type === internal.NullableInterfaceRequest ||
- type instanceof internal.NullableArrayOf ||
- type instanceof internal.NullablePointerTo;
- }
-
- function Validator(message) {
- this.message = message;
- this.offset = 0;
- this.handleIndex = 0;
- }
-
- Object.defineProperty(Validator.prototype, "offsetLimit", {
- get: function() { return this.message.buffer.byteLength; }
- });
-
- Object.defineProperty(Validator.prototype, "handleIndexLimit", {
- get: function() { return this.message.handles.length; }
- });
-
- // True if we can safely allocate a block of bytes from start to
- // to start + numBytes.
- Validator.prototype.isValidRange = function(start, numBytes) {
- // Only positive JavaScript integers that are less than 2^53
- // (Number.MAX_SAFE_INTEGER) can be represented exactly.
- if (start < this.offset || numBytes <= 0 ||
- !Number.isSafeInteger(start) ||
- !Number.isSafeInteger(numBytes))
- return false;
-
- var newOffset = start + numBytes;
- if (!Number.isSafeInteger(newOffset) || newOffset > this.offsetLimit)
- return false;
-
- return true;
- };
-
- Validator.prototype.claimRange = function(start, numBytes) {
- if (this.isValidRange(start, numBytes)) {
- this.offset = start + numBytes;
- return true;
- }
- return false;
- };
-
- Validator.prototype.claimHandle = function(index) {
- if (index === internal.kEncodedInvalidHandleValue)
- return true;
-
- if (index < this.handleIndex || index >= this.handleIndexLimit)
- return false;
-
- // This is safe because handle indices are uint32.
- this.handleIndex = index + 1;
- return true;
- };
-
- Validator.prototype.validateEnum = function(offset, enumClass) {
- // Note: Assumes that enums are always 32 bits! But this matches
- // mojom::generate::pack::PackedField::GetSizeForKind, so it should be okay.
- var value = this.message.buffer.getInt32(offset);
- return enumClass.validate(value);
- }
-
- Validator.prototype.validateHandle = function(offset, nullable) {
- var index = this.message.buffer.getUint32(offset);
-
- if (index === internal.kEncodedInvalidHandleValue)
- return nullable ?
- validationError.NONE : validationError.UNEXPECTED_INVALID_HANDLE;
-
- if (!this.claimHandle(index))
- return validationError.ILLEGAL_HANDLE;
-
- return validationError.NONE;
- };
-
- Validator.prototype.validateInterface = function(offset, nullable) {
- return this.validateHandle(offset, nullable);
- };
-
- Validator.prototype.validateInterfaceRequest = function(offset, nullable) {
- return this.validateHandle(offset, nullable);
- };
-
- Validator.prototype.validateStructHeader = function(offset, minNumBytes) {
- if (!internal.isAligned(offset))
- return validationError.MISALIGNED_OBJECT;
-
- if (!this.isValidRange(offset, internal.kStructHeaderSize))
- return validationError.ILLEGAL_MEMORY_RANGE;
-
- var numBytes = this.message.buffer.getUint32(offset);
-
- if (numBytes < minNumBytes)
- return validationError.UNEXPECTED_STRUCT_HEADER;
-
- if (!this.claimRange(offset, numBytes))
- return validationError.ILLEGAL_MEMORY_RANGE;
-
- return validationError.NONE;
- };
-
- Validator.prototype.validateStructVersion = function(offset, versionSizes) {
- var numBytes = this.message.buffer.getUint32(offset);
- var version = this.message.buffer.getUint32(offset + 4);
-
- if (version <= versionSizes[versionSizes.length - 1].version) {
- // Scan in reverse order to optimize for more recent versionSizes.
- for (var i = versionSizes.length - 1; i >= 0; --i) {
- if (version >= versionSizes[i].version) {
- if (numBytes == versionSizes[i].numBytes)
- break;
- return validationError.UNEXPECTED_STRUCT_HEADER;
- }
- }
- } else if (numBytes < versionSizes[versionSizes.length-1].numBytes) {
- return validationError.UNEXPECTED_STRUCT_HEADER;
- }
-
- return validationError.NONE;
- };
-
- Validator.prototype.isFieldInStructVersion = function(offset, fieldVersion) {
- var structVersion = this.message.buffer.getUint32(offset + 4);
- return fieldVersion <= structVersion;
- };
-
- Validator.prototype.validateMessageHeader = function() {
-
- var err = this.validateStructHeader(0, internal.kMessageHeaderSize);
- if (err != validationError.NONE)
- return err;
-
- var numBytes = this.message.getHeaderNumBytes();
- var version = this.message.getHeaderVersion();
-
- var validVersionAndNumBytes =
- (version == 0 && numBytes == internal.kMessageHeaderSize) ||
- (version == 1 &&
- numBytes == internal.kMessageWithRequestIDHeaderSize) ||
- (version > 1 &&
- numBytes >= internal.kMessageWithRequestIDHeaderSize);
- if (!validVersionAndNumBytes)
- return validationError.UNEXPECTED_STRUCT_HEADER;
-
- var expectsResponse = this.message.expectsResponse();
- var isResponse = this.message.isResponse();
-
- if (version == 0 && (expectsResponse || isResponse))
- return validationError.MESSAGE_HEADER_MISSING_REQUEST_ID;
-
- if (isResponse && expectsResponse)
- return validationError.MESSAGE_HEADER_INVALID_FLAGS;
-
- return validationError.NONE;
- };
-
- Validator.prototype.validateMessageIsRequestWithoutResponse = function() {
- if (this.message.isResponse() || this.message.expectsResponse()) {
- return validationError.MESSAGE_HEADER_INVALID_FLAGS;
- }
- return validationError.NONE;
- };
-
- Validator.prototype.validateMessageIsRequestExpectingResponse = function() {
- if (this.message.isResponse() || !this.message.expectsResponse()) {
- return validationError.MESSAGE_HEADER_INVALID_FLAGS;
- }
- return validationError.NONE;
- };
-
- Validator.prototype.validateMessageIsResponse = function() {
- if (this.message.expectsResponse() || !this.message.isResponse()) {
- return validationError.MESSAGE_HEADER_INVALID_FLAGS;
- }
- return validationError.NONE;
- };
-
- // Returns the message.buffer relative offset this pointer "points to",
- // NULL_MOJO_POINTER if the pointer represents a null, or JS null if the
- // pointer's value is not valid.
- Validator.prototype.decodePointer = function(offset) {
- var pointerValue = this.message.buffer.getUint64(offset);
- if (pointerValue === 0)
- return NULL_MOJO_POINTER;
- var bufferOffset = offset + pointerValue;
- return Number.isSafeInteger(bufferOffset) ? bufferOffset : null;
- };
-
- Validator.prototype.decodeUnionSize = function(offset) {
- return this.message.buffer.getUint32(offset);
- };
-
- Validator.prototype.decodeUnionTag = function(offset) {
- return this.message.buffer.getUint32(offset + 4);
- };
-
- Validator.prototype.validateArrayPointer = function(
- offset, elementSize, elementType, nullable, expectedDimensionSizes,
- currentDimension) {
- var arrayOffset = this.decodePointer(offset);
- if (arrayOffset === null)
- return validationError.ILLEGAL_POINTER;
-
- if (arrayOffset === NULL_MOJO_POINTER)
- return nullable ?
- validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
-
- return this.validateArray(arrayOffset, elementSize, elementType,
- expectedDimensionSizes, currentDimension);
- };
-
- Validator.prototype.validateStructPointer = function(
- offset, structClass, nullable) {
- var structOffset = this.decodePointer(offset);
- if (structOffset === null)
- return validationError.ILLEGAL_POINTER;
-
- if (structOffset === NULL_MOJO_POINTER)
- return nullable ?
- validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
-
- return structClass.validate(this, structOffset);
- };
-
- Validator.prototype.validateUnion = function(
- offset, unionClass, nullable) {
- var size = this.message.buffer.getUint32(offset);
- if (size == 0) {
- return nullable ?
- validationError.NONE : validationError.UNEXPECTED_NULL_UNION;
- }
-
- return unionClass.validate(this, offset);
- };
-
- Validator.prototype.validateNestedUnion = function(
- offset, unionClass, nullable) {
- var unionOffset = this.decodePointer(offset);
- if (unionOffset === null)
- return validationError.ILLEGAL_POINTER;
-
- if (unionOffset === NULL_MOJO_POINTER)
- return nullable ?
- validationError.NONE : validationError.UNEXPECTED_NULL_UNION;
-
- return this.validateUnion(unionOffset, unionClass, nullable);
- };
-
- // This method assumes that the array at arrayPointerOffset has
- // been validated.
-
- Validator.prototype.arrayLength = function(arrayPointerOffset) {
- var arrayOffset = this.decodePointer(arrayPointerOffset);
- return this.message.buffer.getUint32(arrayOffset + 4);
- };
-
- Validator.prototype.validateMapPointer = function(
- offset, mapIsNullable, keyClass, valueClass, valueIsNullable) {
- // Validate the implicit map struct:
- // struct {array<keyClass> keys; array<valueClass> values};
- var structOffset = this.decodePointer(offset);
- if (structOffset === null)
- return validationError.ILLEGAL_POINTER;
-
- if (structOffset === NULL_MOJO_POINTER)
- return mapIsNullable ?
- validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
-
- var mapEncodedSize = internal.kStructHeaderSize +
- internal.kMapStructPayloadSize;
- var err = this.validateStructHeader(structOffset, mapEncodedSize);
- if (err !== validationError.NONE)
- return err;
-
- // Validate the keys array.
- var keysArrayPointerOffset = structOffset + internal.kStructHeaderSize;
- err = this.validateArrayPointer(
- keysArrayPointerOffset, keyClass.encodedSize, keyClass, false, [0], 0);
- if (err !== validationError.NONE)
- return err;
-
- // Validate the values array.
- var valuesArrayPointerOffset = keysArrayPointerOffset + 8;
- var valuesArrayDimensions = [0]; // Validate the actual length below.
- if (valueClass instanceof internal.ArrayOf)
- valuesArrayDimensions =
- valuesArrayDimensions.concat(valueClass.dimensions());
- var err = this.validateArrayPointer(valuesArrayPointerOffset,
- valueClass.encodedSize,
- valueClass,
- valueIsNullable,
- valuesArrayDimensions,
- 0);
- if (err !== validationError.NONE)
- return err;
-
- // Validate the lengths of the keys and values arrays.
- var keysArrayLength = this.arrayLength(keysArrayPointerOffset);
- var valuesArrayLength = this.arrayLength(valuesArrayPointerOffset);
- if (keysArrayLength != valuesArrayLength)
- return validationError.DIFFERENT_SIZED_ARRAYS_IN_MAP;
-
- return validationError.NONE;
- };
-
- Validator.prototype.validateStringPointer = function(offset, nullable) {
- return this.validateArrayPointer(
- offset, internal.Uint8.encodedSize, internal.Uint8, nullable, [0], 0);
- };
-
- // Similar to Array_Data<T>::Validate()
- // mojo/public/cpp/bindings/lib/array_internal.h
-
- Validator.prototype.validateArray =
- function (offset, elementSize, elementType, expectedDimensionSizes,
- currentDimension) {
- if (!internal.isAligned(offset))
- return validationError.MISALIGNED_OBJECT;
-
- if (!this.isValidRange(offset, internal.kArrayHeaderSize))
- return validationError.ILLEGAL_MEMORY_RANGE;
-
- var numBytes = this.message.buffer.getUint32(offset);
- var numElements = this.message.buffer.getUint32(offset + 4);
-
- // Note: this computation is "safe" because elementSize <= 8 and
- // numElements is a uint32.
- var elementsTotalSize = (elementType === internal.PackedBool) ?
- Math.ceil(numElements / 8) : (elementSize * numElements);
-
- if (numBytes < internal.kArrayHeaderSize + elementsTotalSize)
- return validationError.UNEXPECTED_ARRAY_HEADER;
-
- if (expectedDimensionSizes[currentDimension] != 0 &&
- numElements != expectedDimensionSizes[currentDimension]) {
- return validationError.UNEXPECTED_ARRAY_HEADER;
- }
-
- if (!this.claimRange(offset, numBytes))
- return validationError.ILLEGAL_MEMORY_RANGE;
-
- // Validate the array's elements if they are pointers or handles.
-
- var elementsOffset = offset + internal.kArrayHeaderSize;
- var nullable = isNullable(elementType);
-
- if (isHandleClass(elementType))
- return this.validateHandleElements(elementsOffset, numElements, nullable);
- if (isInterfaceClass(elementType))
- return this.validateInterfaceElements(
- elementsOffset, numElements, nullable);
- if (isInterfaceRequestClass(elementType))
- return this.validateInterfaceRequestElements(
- elementsOffset, numElements, nullable);
- if (isStringClass(elementType))
- return this.validateArrayElements(
- elementsOffset, numElements, internal.Uint8, nullable, [0], 0);
- if (elementType instanceof internal.PointerTo)
- return this.validateStructElements(
- elementsOffset, numElements, elementType.cls, nullable);
- if (elementType instanceof internal.ArrayOf)
- return this.validateArrayElements(
- elementsOffset, numElements, elementType.cls, nullable,
- expectedDimensionSizes, currentDimension + 1);
- if (isEnumClass(elementType))
- return this.validateEnumElements(elementsOffset, numElements,
- elementType.cls);
-
- return validationError.NONE;
- };
-
- // Note: the |offset + i * elementSize| computation in the validateFooElements
- // methods below is "safe" because elementSize <= 8, offset and
- // numElements are uint32, and 0 <= i < numElements.
-
- Validator.prototype.validateHandleElements =
- function(offset, numElements, nullable) {
- var elementSize = internal.Handle.encodedSize;
- for (var i = 0; i < numElements; i++) {
- var elementOffset = offset + i * elementSize;
- var err = this.validateHandle(elementOffset, nullable);
- if (err != validationError.NONE)
- return err;
- }
- return validationError.NONE;
- };
-
- Validator.prototype.validateInterfaceElements =
- function(offset, numElements, nullable) {
- var elementSize = internal.Interface.prototype.encodedSize;
- for (var i = 0; i < numElements; i++) {
- var elementOffset = offset + i * elementSize;
- var err = this.validateInterface(elementOffset, nullable);
- if (err != validationError.NONE)
- return err;
- }
- return validationError.NONE;
- };
-
- Validator.prototype.validateInterfaceRequestElements =
- function(offset, numElements, nullable) {
- var elementSize = internal.InterfaceRequest.encodedSize;
- for (var i = 0; i < numElements; i++) {
- var elementOffset = offset + i * elementSize;
- var err = this.validateInterfaceRequest(elementOffset, nullable);
- if (err != validationError.NONE)
- return err;
- }
- return validationError.NONE;
- };
-
- // The elementClass parameter is the element type of the element arrays.
- Validator.prototype.validateArrayElements =
- function(offset, numElements, elementClass, nullable,
- expectedDimensionSizes, currentDimension) {
- var elementSize = internal.PointerTo.prototype.encodedSize;
- for (var i = 0; i < numElements; i++) {
- var elementOffset = offset + i * elementSize;
- var err = this.validateArrayPointer(
- elementOffset, elementClass.encodedSize, elementClass, nullable,
- expectedDimensionSizes, currentDimension);
- if (err != validationError.NONE)
- return err;
- }
- return validationError.NONE;
- };
-
- Validator.prototype.validateStructElements =
- function(offset, numElements, structClass, nullable) {
- var elementSize = internal.PointerTo.prototype.encodedSize;
- for (var i = 0; i < numElements; i++) {
- var elementOffset = offset + i * elementSize;
- var err =
- this.validateStructPointer(elementOffset, structClass, nullable);
- if (err != validationError.NONE)
- return err;
- }
- return validationError.NONE;
- };
-
- Validator.prototype.validateEnumElements =
- function(offset, numElements, enumClass) {
- var elementSize = internal.Enum.prototype.encodedSize;
- for (var i = 0; i < numElements; i++) {
- var elementOffset = offset + i * elementSize;
- var err = this.validateEnum(elementOffset, enumClass);
- if (err != validationError.NONE)
- return err;
- }
- return validationError.NONE;
- };
-
- internal.validationError = validationError;
- internal.Validator = Validator;
-})();
diff --git a/mojo/public/js/router.js b/mojo/public/js/router.js
deleted file mode 100644
index 89d9a2f66b..0000000000
--- a/mojo/public/js/router.js
+++ /dev/null
@@ -1,269 +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.
-
-define("mojo/public/js/router", [
- "mojo/public/js/connector",
- "mojo/public/js/core",
- "mojo/public/js/interface_types",
- "mojo/public/js/lib/interface_endpoint_handle",
- "mojo/public/js/lib/pipe_control_message_handler",
- "mojo/public/js/lib/pipe_control_message_proxy",
- "mojo/public/js/validator",
- "timer",
-], function(connector, core, types, interfaceEndpointHandle,
- controlMessageHandler, controlMessageProxy, validator, timer) {
-
- var Connector = connector.Connector;
- var PipeControlMessageHandler =
- controlMessageHandler.PipeControlMessageHandler;
- var PipeControlMessageProxy = controlMessageProxy.PipeControlMessageProxy;
- var Validator = validator.Validator;
- var InterfaceEndpointHandle = interfaceEndpointHandle.InterfaceEndpointHandle;
-
- /**
- * The state of |endpoint|. If both the endpoint and its peer have been
- * closed, removes it from |endpoints_|.
- * @enum {string}
- */
- var EndpointStateUpdateType = {
- ENDPOINT_CLOSED: 'endpoint_closed',
- PEER_ENDPOINT_CLOSED: 'peer_endpoint_closed'
- };
-
- function check(condition, output) {
- if (!condition) {
- // testharness.js does not rethrow errors so the error stack needs to be
- // included as a string in the error we throw for debugging layout tests.
- throw new Error((new Error()).stack);
- }
- }
-
- function InterfaceEndpoint(router, interfaceId) {
- this.router_ = router;
- this.id = interfaceId;
- this.closed = false;
- this.peerClosed = false;
- this.handleCreated = false;
- this.disconnectReason = null;
- this.client = null;
- }
-
- InterfaceEndpoint.prototype.sendMessage = function(message) {
- message.setInterfaceId(this.id);
- return this.router_.connector_.accept(message);
- };
-
- function Router(handle, setInterfaceIdNamespaceBit) {
- if (!core.isHandle(handle)) {
- throw new Error("Router constructor: Not a handle");
- }
- if (setInterfaceIdNamespaceBit === undefined) {
- setInterfaceIdNamespaceBit = false;
- }
-
- this.connector_ = new Connector(handle);
-
- this.connector_.setIncomingReceiver({
- accept: this.accept.bind(this),
- });
- this.connector_.setErrorHandler({
- onError: this.onPipeConnectionError.bind(this),
- });
-
- this.setInterfaceIdNamespaceBit_ = setInterfaceIdNamespaceBit;
- this.controlMessageHandler_ = new PipeControlMessageHandler(this);
- this.controlMessageProxy_ = new PipeControlMessageProxy(this.connector_);
- this.nextInterfaceIdValue = 1;
- this.encounteredError_ = false;
- this.endpoints_ = new Map();
- }
-
- Router.prototype.attachEndpointClient = function(
- interfaceEndpointHandle, interfaceEndpointClient) {
- check(types.isValidInterfaceId(interfaceEndpointHandle.id()));
- check(interfaceEndpointClient);
-
- var endpoint = this.endpoints_.get(interfaceEndpointHandle.id());
- check(endpoint);
- check(!endpoint.client);
- check(!endpoint.closed);
- endpoint.client = interfaceEndpointClient;
-
- if (endpoint.peerClosed) {
- timer.createOneShot(0,
- endpoint.client.notifyError.bind(endpoint.client));
- }
-
- return endpoint;
- };
-
- Router.prototype.detachEndpointClient = function(
- interfaceEndpointHandle) {
- check(types.isValidInterfaceId(interfaceEndpointHandle.id()));
- var endpoint = this.endpoints_.get(interfaceEndpointHandle.id());
- check(endpoint);
- check(endpoint.client);
- check(!endpoint.closed);
-
- endpoint.client = null;
- };
-
- Router.prototype.createLocalEndpointHandle = function(
- interfaceId) {
- if (!types.isValidInterfaceId(interfaceId)) {
- return new InterfaceEndpointHandle();
- }
-
- var endpoint = this.endpoints_.get(interfaceId);
-
- if (!endpoint) {
- endpoint = new InterfaceEndpoint(this, interfaceId);
- this.endpoints_.set(interfaceId, endpoint);
-
- check(!endpoint.handleCreated);
-
- if (this.encounteredError_) {
- this.updateEndpointStateMayRemove(endpoint,
- EndpointStateUpdateType.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.peerClosed);
-
- if (endpoint.handleCreated) {
- return new InterfaceEndpointHandle();
- }
- }
-
- endpoint.handleCreated = true;
- return new InterfaceEndpointHandle(interfaceId, this);
- };
-
- Router.prototype.accept = function(message) {
- var messageValidator = new Validator(message);
- var err = messageValidator.validateMessageHeader();
-
- var ok = false;
- if (err !== validator.validationError.NONE) {
- validator.reportValidationError(err);
- } else if (controlMessageHandler.isPipeControlMessage(message)) {
- ok = this.controlMessageHandler_.accept(message);
- } else {
- var interfaceId = message.getInterfaceId();
- var endpoint = this.endpoints_.get(interfaceId);
- 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;
- }
- ok = endpoint.client.handleIncomingMessage_(message);
- }
-
- if (!ok) {
- this.handleInvalidIncomingMessage_();
- }
- return ok;
- };
-
- Router.prototype.close = function() {
- this.connector_.close();
- // Closing the message pipe won't trigger connection error handler.
- // Explicitly call onPipeConnectionError() so that associated endpoints
- // will get notified.
- this.onPipeConnectionError();
- };
-
- Router.prototype.waitForNextMessageForTesting = function() {
- this.connector_.waitForNextMessageForTesting();
- };
-
- Router.prototype.handleInvalidIncomingMessage_ = function(message) {
- if (!validator.isTestingMode()) {
- // TODO(yzshen): Consider notifying the embedder.
- // TODO(yzshen): This should also trigger connection error handler.
- // Consider making accept() return a boolean and let the connector deal
- // with this, as the C++ code does.
- this.close();
- return;
- }
- };
-
- Router.prototype.onPeerAssociatedEndpointClosed = function(interfaceId,
- reason) {
- check(!types.isMasterInterfaceId(interfaceId) || reason);
-
- var endpoint = this.endpoints_.get(interfaceId);
- if (!endpoint) {
- endpoint = new InterfaceEndpoint(this, interfaceId);
- this.endpoints_.set(interfaceId, endpoint);
- }
-
- if (reason) {
- endpoint.disconnectReason = reason;
- }
-
- if (!endpoint.peerClosed) {
- if (endpoint.client) {
- timer.createOneShot(0,
- endpoint.client.notifyError.bind(endpoint.client, reason));
- }
- this.updateEndpointStateMayRemove(endpoint,
- EndpointStateUpdateType.PEER_ENDPOINT_CLOSED);
- }
- return true;
- };
-
- Router.prototype.onPipeConnectionError = function() {
- this.encounteredError_ = true;
-
- for (var endpoint of this.endpoints_.values()) {
- if (endpoint.client) {
- timer.createOneShot(0,
- endpoint.client.notifyError.bind(endpoint.client,
- endpoint.disconnectReason));
- }
- this.updateEndpointStateMayRemove(endpoint,
- EndpointStateUpdateType.PEER_ENDPOINT_CLOSED);
- }
- };
-
- Router.prototype.closeEndpointHandle = function(interfaceId, reason) {
- if (!types.isValidInterfaceId(interfaceId)) {
- return;
- }
- var endpoint = this.endpoints_.get(interfaceId);
- check(endpoint);
- check(!endpoint.client);
- check(!endpoint.closed);
-
- this.updateEndpointStateMayRemove(endpoint,
- EndpointStateUpdateType.ENDPOINT_CLOSED);
-
- if (!types.isMasterInterfaceId(interfaceId) || reason) {
- this.controlMessageProxy_.notifyPeerEndpointClosed(interfaceId, reason);
- }
- };
-
- Router.prototype.updateEndpointStateMayRemove = function(endpoint,
- endpointStateUpdateType) {
- if (endpointStateUpdateType === EndpointStateUpdateType.ENDPOINT_CLOSED) {
- endpoint.closed = true;
- } else {
- endpoint.peerClosed = true;
- }
- if (endpoint.closed && endpoint.peerClosed) {
- this.endpoints_.delete(endpoint.id);
- }
- };
-
- var exports = {};
- exports.Router = Router;
- return exports;
-});
diff --git a/mojo/public/js/support.js b/mojo/public/js/support.js
deleted file mode 100644
index 7e27504fbe..0000000000
--- a/mojo/public/js/support.js
+++ /dev/null
@@ -1,53 +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.
-
-// Module "mojo/public/js/support"
-//
-// Note: This file is for documentation purposes only. The code here is not
-// actually executed. The real module is implemented natively in Mojo.
-
-while (1);
-
-/* @deprecated Please use watch()/cancelWatch() instead of
- * asyncWait()/cancelWait().
- *
- * Waits on the given handle until the state indicated by |signals| is
- * satisfied.
- *
- * @param {MojoHandle} handle The handle to wait on.
- * @param {MojoHandleSignals} signals Specifies the condition to wait for.
- * @param {function (mojoResult)} callback Called with the result the wait is
- * complete. See MojoWait for possible result codes.
- *
- * @return {MojoWaitId} A waitId that can be passed to cancelWait to cancel the
- * wait.
- */
-function asyncWait(handle, signals, callback) { [native code] }
-
-/* @deprecated Please use watch()/cancelWatch() instead of
- * asyncWait()/cancelWait().
- *
- * Cancels the asyncWait operation specified by the given |waitId|.
- *
- * @param {MojoWaitId} waitId The waitId returned by asyncWait.
- */
-function cancelWait(waitId) { [native code] }
-
-/* Begins watching a handle for |signals| to be satisfied or unsatisfiable.
- *
- * @param {MojoHandle} handle The handle to watch.
- * @param {MojoHandleSignals} signals The signals to watch.
- * @param {function (mojoResult)} calback Called with a result any time
- * the watched signals become satisfied or unsatisfiable.
- *
- * @param {MojoWatchId} watchId An opaque identifier that identifies this
- * watch.
- */
-function watch(handle, signals, callback) { [native code] }
-
-/* Cancels a handle watch initiated by watch().
- *
- * @param {MojoWatchId} watchId The watch identifier returned by watch().
- */
-function cancelWatch(watchId) { [native code] }
diff --git a/mojo/public/js/tests/core_unittest.js b/mojo/public/js/tests/core_unittest.js
deleted file mode 100644
index 86a997f1e7..0000000000
--- a/mojo/public/js/tests/core_unittest.js
+++ /dev/null
@@ -1,223 +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.
-
-define([
- "gin/test/expect",
- "mojo/public/js/core",
- "gc",
- ], function(expect, core, gc) {
-
- var HANDLE_SIGNAL_READWRITABLE = core.HANDLE_SIGNAL_WRITABLE |
- core.HANDLE_SIGNAL_READABLE;
- var HANDLE_SIGNAL_ALL = core.HANDLE_SIGNAL_WRITABLE |
- core.HANDLE_SIGNAL_READABLE |
- core.HANDLE_SIGNAL_PEER_CLOSED;
-
- runWithMessagePipe(testNop);
- runWithMessagePipe(testReadAndWriteMessage);
- runWithMessagePipeWithOptions(testNop);
- runWithMessagePipeWithOptions(testReadAndWriteMessage);
- runWithDataPipe(testNop);
- runWithDataPipe(testReadAndWriteDataPipe);
- runWithDataPipeWithOptions(testNop);
- runWithDataPipeWithOptions(testReadAndWriteDataPipe);
- runWithMessagePipe(testIsHandleMessagePipe);
- runWithDataPipe(testIsHandleDataPipe);
- runWithSharedBuffer(testSharedBuffer);
- gc.collectGarbage(); // should not crash
- this.result = "PASS";
-
- function runWithMessagePipe(test) {
- var pipe = core.createMessagePipe();
- expect(pipe.result).toBe(core.RESULT_OK);
-
- test(pipe);
-
- expect(core.close(pipe.handle0)).toBe(core.RESULT_OK);
- expect(core.close(pipe.handle1)).toBe(core.RESULT_OK);
- }
-
- function runWithMessagePipeWithOptions(test) {
- var pipe = core.createMessagePipe({
- flags: core.CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE
- });
- expect(pipe.result).toBe(core.RESULT_OK);
-
- test(pipe);
-
- expect(core.close(pipe.handle0)).toBe(core.RESULT_OK);
- expect(core.close(pipe.handle1)).toBe(core.RESULT_OK);
- }
-
- function runWithDataPipe(test) {
- var pipe = core.createDataPipe();
- expect(pipe.result).toBe(core.RESULT_OK);
-
- test(pipe);
-
- expect(core.close(pipe.producerHandle)).toBe(core.RESULT_OK);
- expect(core.close(pipe.consumerHandle)).toBe(core.RESULT_OK);
- }
-
- function runWithDataPipeWithOptions(test) {
- var pipe = core.createDataPipe({
- flags: core.CREATE_DATA_PIPE_OPTIONS_FLAG_NONE,
- elementNumBytes: 1,
- capacityNumBytes: 64
- });
- expect(pipe.result).toBe(core.RESULT_OK);
-
- test(pipe);
-
- expect(core.close(pipe.producerHandle)).toBe(core.RESULT_OK);
- expect(core.close(pipe.consumerHandle)).toBe(core.RESULT_OK);
- }
-
- function runWithSharedBuffer(test) {
- let buffer_size = 32;
- let sharedBuffer = core.createSharedBuffer(buffer_size,
- core.CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE);
-
- expect(sharedBuffer.result).toBe(core.RESULT_OK);
- expect(core.isHandle(sharedBuffer.handle)).toBeTruthy();
-
- test(sharedBuffer, buffer_size);
- }
-
- function testNop(pipe) {
- }
-
- function testReadAndWriteMessage(pipe) {
- var state0 = core.queryHandleSignalsState(pipe.handle0);
- expect(state0.result).toBe(core.RESULT_OK);
- expect(state0.satisfiedSignals).toBe(core.HANDLE_SIGNAL_WRITABLE);
- expect(state0.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
-
- var state1 = core.queryHandleSignalsState(pipe.handle1);
- expect(state1.result).toBe(core.RESULT_OK);
- expect(state1.satisfiedSignals).toBe(core.HANDLE_SIGNAL_WRITABLE);
- expect(state1.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
-
- var senderData = new Uint8Array(42);
- for (var i = 0; i < senderData.length; ++i) {
- senderData[i] = i * i;
- }
-
- var result = core.writeMessage(
- pipe.handle0, senderData, [],
- core.WRITE_MESSAGE_FLAG_NONE);
-
- expect(result).toBe(core.RESULT_OK);
-
- state0 = core.queryHandleSignalsState(pipe.handle0);
- expect(state0.result).toBe(core.RESULT_OK);
- expect(state0.satisfiedSignals).toBe(core.HANDLE_SIGNAL_WRITABLE);
- expect(state0.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
-
- var wait = core.wait(pipe.handle1, core.HANDLE_SIGNAL_READABLE);
- expect(wait.result).toBe(core.RESULT_OK);
- expect(wait.signalsState.satisfiedSignals).toBe(HANDLE_SIGNAL_READWRITABLE);
- expect(wait.signalsState.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
-
- var read = core.readMessage(pipe.handle1, core.READ_MESSAGE_FLAG_NONE);
-
- expect(read.result).toBe(core.RESULT_OK);
- expect(read.buffer.byteLength).toBe(42);
- expect(read.handles.length).toBe(0);
-
- var memory = new Uint8Array(read.buffer);
- for (var i = 0; i < memory.length; ++i)
- expect(memory[i]).toBe((i * i) & 0xFF);
- }
-
- function testReadAndWriteDataPipe(pipe) {
- var senderData = new Uint8Array(42);
- for (var i = 0; i < senderData.length; ++i) {
- senderData[i] = i * i;
- }
-
- var write = core.writeData(
- pipe.producerHandle, senderData,
- core.WRITE_DATA_FLAG_ALL_OR_NONE);
-
- expect(write.result).toBe(core.RESULT_OK);
- expect(write.numBytes).toBe(42);
-
- var wait = core.wait(pipe.consumerHandle, core.HANDLE_SIGNAL_READABLE);
- expect(wait.result).toBe(core.RESULT_OK);
- var peeked = core.readData(
- pipe.consumerHandle,
- core.READ_DATA_FLAG_PEEK | core.READ_DATA_FLAG_ALL_OR_NONE);
- expect(peeked.result).toBe(core.RESULT_OK);
- expect(peeked.buffer.byteLength).toBe(42);
-
- var peeked_memory = new Uint8Array(peeked.buffer);
- for (var i = 0; i < peeked_memory.length; ++i)
- expect(peeked_memory[i]).toBe((i * i) & 0xFF);
-
- var read = core.readData(
- pipe.consumerHandle, core.READ_DATA_FLAG_ALL_OR_NONE);
-
- expect(read.result).toBe(core.RESULT_OK);
- expect(read.buffer.byteLength).toBe(42);
-
- var memory = new Uint8Array(read.buffer);
- for (var i = 0; i < memory.length; ++i)
- expect(memory[i]).toBe((i * i) & 0xFF);
- }
-
- function testIsHandleMessagePipe(pipe) {
- expect(core.isHandle(123).toBeFalsy);
- expect(core.isHandle("123").toBeFalsy);
- expect(core.isHandle({}).toBeFalsy);
- expect(core.isHandle([]).toBeFalsy);
- expect(core.isHandle(undefined).toBeFalsy);
- expect(core.isHandle(pipe).toBeFalsy);
- expect(core.isHandle(pipe.handle0)).toBeTruthy();
- expect(core.isHandle(pipe.handle1)).toBeTruthy();
- expect(core.isHandle(null)).toBeTruthy();
- }
-
- function testIsHandleDataPipe(pipe) {
- expect(core.isHandle(pipe.consumerHandle)).toBeTruthy();
- expect(core.isHandle(pipe.producerHandle)).toBeTruthy();
- }
-
- function testSharedBuffer(sharedBuffer, buffer_size) {
- let offset = 0;
- let mappedBuffer0 = core.mapBuffer(sharedBuffer.handle,
- offset,
- buffer_size,
- core.MAP_BUFFER_FLAG_NONE);
-
- expect(mappedBuffer0.result).toBe(core.RESULT_OK);
-
- let dupedBufferHandle = core.duplicateBufferHandle(sharedBuffer.handle,
- core.DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE);
-
- expect(dupedBufferHandle.result).toBe(core.RESULT_OK);
- expect(core.isHandle(dupedBufferHandle.handle)).toBeTruthy();
-
- let mappedBuffer1 = core.mapBuffer(dupedBufferHandle.handle,
- offset,
- buffer_size,
- core.MAP_BUFFER_FLAG_NONE);
-
- expect(mappedBuffer1.result).toBe(core.RESULT_OK);
-
- let buffer0 = new Uint8Array(mappedBuffer0.buffer);
- let buffer1 = new Uint8Array(mappedBuffer1.buffer);
- for(let i = 0; i < buffer0.length; ++i) {
- buffer0[i] = i;
- expect(buffer1[i]).toBe(i);
- }
-
- expect(core.unmapBuffer(mappedBuffer0.buffer)).toBe(core.RESULT_OK);
- expect(core.unmapBuffer(mappedBuffer1.buffer)).toBe(core.RESULT_OK);
-
- expect(core.close(dupedBufferHandle.handle)).toBe(core.RESULT_OK);
- expect(core.close(sharedBuffer.handle)).toBe(core.RESULT_OK);
- }
-
-});
diff --git a/mojo/public/js/tests/validation_test_input_parser.js b/mojo/public/js/tests/validation_test_input_parser.js
deleted file mode 100644
index f5a57f9172..0000000000
--- a/mojo/public/js/tests/validation_test_input_parser.js
+++ /dev/null
@@ -1,299 +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.
-
-// Support for parsing binary sequences encoded as readable strings
-// or ".data" files. The input format is described here:
-// mojo/public/cpp/bindings/tests/validation_test_input_parser.h
-
-define([
- "mojo/public/js/buffer"
- ], function(buffer) {
-
- // Files and Lines represent the raw text from an input string
- // or ".data" file.
-
- function InputError(message, line) {
- this.message = message;
- this.line = line;
- }
-
- InputError.prototype.toString = function() {
- var s = 'Error: ' + this.message;
- if (this.line)
- s += ', at line ' +
- (this.line.number + 1) + ': "' + this.line.contents + '"';
- return s;
- }
-
- function File(contents) {
- this.contents = contents;
- this.index = 0;
- this.lineNumber = 0;
- }
-
- File.prototype.endReached = function() {
- return this.index >= this.contents.length;
- }
-
- File.prototype.nextLine = function() {
- if (this.endReached())
- return null;
- var start = this.index;
- var end = this.contents.indexOf('\n', start);
- if (end == -1)
- end = this.contents.length;
- this.index = end + 1;
- return new Line(this.contents.substring(start, end), this.lineNumber++);
- }
-
- function Line(contents, number) {
- var i = contents.indexOf('//');
- var s = (i == -1) ? contents.trim() : contents.substring(0, i).trim();
- this.contents = contents;
- this.items = (s.length > 0) ? s.split(/\s+/) : [];
- this.index = 0;
- this.number = number;
- }
-
- Line.prototype.endReached = function() {
- return this.index >= this.items.length;
- }
-
- var ITEM_TYPE_SIZES = {
- u1: 1, u2: 2, u4: 4, u8: 8, s1: 1, s2: 2, s4: 4, s8: 8, b: 1, f: 4, d: 8,
- dist4: 4, dist8: 8, anchr: 0, handles: 0
- };
-
- function isValidItemType(type) {
- return ITEM_TYPE_SIZES[type] !== undefined;
- }
-
- Line.prototype.nextItem = function() {
- if (this.endReached())
- return null;
-
- var itemString = this.items[this.index++];
- var type = 'u1';
- var value = itemString;
-
- if (itemString.charAt(0) == '[') {
- var i = itemString.indexOf(']');
- if (i != -1 && i + 1 < itemString.length) {
- type = itemString.substring(1, i);
- value = itemString.substring(i + 1);
- } else {
- throw new InputError('invalid item', this);
- }
- }
- if (!isValidItemType(type))
- throw new InputError('invalid item type', this);
-
- return new Item(this, type, value);
- }
-
- // The text for each whitespace delimited binary data "item" is represented
- // by an Item.
-
- function Item(line, type, value) {
- this.line = line;
- this.type = type;
- this.value = value;
- this.size = ITEM_TYPE_SIZES[type];
- }
-
- Item.prototype.isFloat = function() {
- return this.type == 'f' || this.type == 'd';
- }
-
- Item.prototype.isInteger = function() {
- return ['u1', 'u2', 'u4', 'u8',
- 's1', 's2', 's4', 's8'].indexOf(this.type) != -1;
- }
-
- Item.prototype.isNumber = function() {
- return this.isFloat() || this.isInteger();
- }
-
- Item.prototype.isByte = function() {
- return this.type == 'b';
- }
-
- Item.prototype.isDistance = function() {
- return this.type == 'dist4' || this.type == 'dist8';
- }
-
- Item.prototype.isAnchor = function() {
- return this.type == 'anchr';
- }
-
- Item.prototype.isHandles = function() {
- return this.type == 'handles';
- }
-
- // A TestMessage represents the complete binary message loaded from an input
- // string or ".data" file. The parseTestMessage() function below constructs
- // a TestMessage from a File.
-
- function TestMessage(byteLength) {
- this.index = 0;
- this.buffer = new buffer.Buffer(byteLength);
- this.distances = {};
- this.handleCount = 0;
- }
-
- function checkItemNumberValue(item, n, min, max) {
- if (n < min || n > max)
- throw new InputError('invalid item value', item.line);
- }
-
- TestMessage.prototype.addNumber = function(item) {
- var n = item.isInteger() ? parseInt(item.value) : parseFloat(item.value);
- if (Number.isNaN(n))
- throw new InputError("can't parse item value", item.line);
-
- switch(item.type) {
- case 'u1':
- checkItemNumberValue(item, n, 0, 0xFF);
- this.buffer.setUint8(this.index, n);
- break;
- case 'u2':
- checkItemNumberValue(item, n, 0, 0xFFFF);
- this.buffer.setUint16(this.index, n);
- break;
- case 'u4':
- checkItemNumberValue(item, n, 0, 0xFFFFFFFF);
- this.buffer.setUint32(this.index, n);
- break;
- case 'u8':
- checkItemNumberValue(item, n, 0, Number.MAX_SAFE_INTEGER);
- this.buffer.setUint64(this.index, n);
- break;
- case 's1':
- checkItemNumberValue(item, n, -128, 127);
- this.buffer.setInt8(this.index, n);
- break;
- case 's2':
- checkItemNumberValue(item, n, -32768, 32767);
- this.buffer.setInt16(this.index, n);
- break;
- case 's4':
- checkItemNumberValue(item, n, -2147483648, 2147483647);
- this.buffer.setInt32(this.index, n);
- break;
- case 's8':
- checkItemNumberValue(item, n,
- Number.MIN_SAFE_INTEGER,
- Number.MAX_SAFE_INTEGER);
- this.buffer.setInt64(this.index, n);
- break;
- case 'f':
- this.buffer.setFloat32(this.index, n);
- break;
- case 'd':
- this.buffer.setFloat64(this.index, n);
- break;
-
- default:
- throw new InputError('unrecognized item type', item.line);
- }
- }
-
- TestMessage.prototype.addByte = function(item) {
- if (!/^[01]{8}$/.test(item.value))
- throw new InputError('invalid byte item value', item.line);
- function b(i) {
- return (item.value.charAt(7 - i) == '1') ? 1 << i : 0;
- }
- var n = b(0) | b(1) | b(2) | b(3) | b(4) | b(5) | b(6) | b(7);
- this.buffer.setUint8(this.index, n);
- }
-
- TestMessage.prototype.addDistance = function(item) {
- if (this.distances[item.value])
- throw new InputError('duplicate distance item', item.line);
- this.distances[item.value] = {index: this.index, item: item};
- }
-
- TestMessage.prototype.addAnchor = function(item) {
- var dist = this.distances[item.value];
- if (!dist)
- throw new InputError('unmatched anchor item', item.line);
- delete this.distances[item.value];
-
- var n = this.index - dist.index;
- // TODO(hansmuller): validate n
-
- if (dist.item.type == 'dist4')
- this.buffer.setUint32(dist.index, n);
- else if (dist.item.type == 'dist8')
- this.buffer.setUint64(dist.index, n);
- else
- throw new InputError('unrecognzed distance item type', dist.item.line);
- }
-
- TestMessage.prototype.addHandles = function(item) {
- this.handleCount = parseInt(item.value);
- if (Number.isNaN(this.handleCount))
- throw new InputError("can't parse handleCount", item.line);
- }
-
- TestMessage.prototype.addItem = function(item) {
- if (item.isNumber())
- this.addNumber(item);
- else if (item.isByte())
- this.addByte(item);
- else if (item.isDistance())
- this.addDistance(item);
- else if (item.isAnchor())
- this.addAnchor(item);
- else if (item.isHandles())
- this.addHandles(item);
- else
- throw new InputError('unrecognized item type', item.line);
-
- this.index += item.size;
- }
-
- TestMessage.prototype.unanchoredDistances = function() {
- var names = null;
- for (var name in this.distances) {
- if (this.distances.hasOwnProperty(name))
- names = (names === null) ? name : names + ' ' + name;
- }
- return names;
- }
-
- function parseTestMessage(text) {
- var file = new File(text);
- var items = [];
- var messageLength = 0;
- while(!file.endReached()) {
- var line = file.nextLine();
- while (!line.endReached()) {
- var item = line.nextItem();
- if (item.isHandles() && items.length > 0)
- throw new InputError('handles item is not first');
- messageLength += item.size;
- items.push(item);
- }
- }
-
- var msg = new TestMessage(messageLength);
- for (var i = 0; i < items.length; i++)
- msg.addItem(items[i]);
-
- if (messageLength != msg.index)
- throw new InputError('failed to compute message length');
- var names = msg.unanchoredDistances();
- if (names)
- throw new InputError('no anchors for ' + names, 0);
-
- return msg;
- }
-
- var exports = {};
- exports.parseTestMessage = parseTestMessage;
- exports.InputError = InputError;
- return exports;
-});
diff --git a/mojo/public/js/tests/validation_unittest.js b/mojo/public/js/tests/validation_unittest.js
deleted file mode 100644
index 2a07315436..0000000000
--- a/mojo/public/js/tests/validation_unittest.js
+++ /dev/null
@@ -1,334 +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.
-
-define([
- "console",
- "file",
- "gin/test/expect",
- "mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom",
- "mojo/public/js/bindings",
- "mojo/public/js/buffer",
- "mojo/public/js/codec",
- "mojo/public/js/core",
- "mojo/public/js/tests/validation_test_input_parser",
- "mojo/public/js/validator",
-], function(console,
- file,
- expect,
- testInterface,
- bindings,
- buffer,
- codec,
- core,
- parser,
- validator) {
-
- var noError = validator.validationError.NONE;
-
- function checkTestMessageParser() {
- function TestMessageParserFailure(message, input) {
- this.message = message;
- this.input = input;
- }
-
- TestMessageParserFailure.prototype.toString = function() {
- return 'Error: ' + this.message + ' for "' + this.input + '"';
- };
-
- function checkData(data, expectedData, input) {
- if (data.byteLength != expectedData.byteLength) {
- var s = "message length (" + data.byteLength + ") doesn't match " +
- "expected length: " + expectedData.byteLength;
- throw new TestMessageParserFailure(s, input);
- }
-
- for (var i = 0; i < data.byteLength; i++) {
- if (data.getUint8(i) != expectedData.getUint8(i)) {
- var s = 'message data mismatch at byte offset ' + i;
- throw new TestMessageParserFailure(s, input);
- }
- }
- }
-
- function testFloatItems() {
- var input = '[f]+.3e9 [d]-10.03';
- var msg = parser.parseTestMessage(input);
- var expectedData = new buffer.Buffer(12);
- expectedData.setFloat32(0, +.3e9);
- expectedData.setFloat64(4, -10.03);
- checkData(msg.buffer, expectedData, input);
- }
-
- function testUnsignedIntegerItems() {
- var input = '[u1]0x10// hello world !! \n\r \t [u2]65535 \n' +
- '[u4]65536 [u8]0xFFFFFFFFFFFFF 0 0Xff';
- var msg = parser.parseTestMessage(input);
- var expectedData = new buffer.Buffer(17);
- expectedData.setUint8(0, 0x10);
- expectedData.setUint16(1, 65535);
- expectedData.setUint32(3, 65536);
- expectedData.setUint64(7, 0xFFFFFFFFFFFFF);
- expectedData.setUint8(15, 0);
- expectedData.setUint8(16, 0xff);
- checkData(msg.buffer, expectedData, input);
- }
-
- function testSignedIntegerItems() {
- var input = '[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40';
- var msg = parser.parseTestMessage(input);
- var expectedData = new buffer.Buffer(15);
- expectedData.setInt64(0, -0x800);
- expectedData.setInt8(8, -128);
- expectedData.setInt16(9, 0);
- expectedData.setInt32(11, -40);
- checkData(msg.buffer, expectedData, input);
- }
-
- function testByteItems() {
- var input = '[b]00001011 [b]10000000 // hello world\n [b]00000000';
- var msg = parser.parseTestMessage(input);
- var expectedData = new buffer.Buffer(3);
- expectedData.setUint8(0, 11);
- expectedData.setUint8(1, 128);
- expectedData.setUint8(2, 0);
- checkData(msg.buffer, expectedData, input);
- }
-
- function testAnchors() {
- var input = '[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar';
- var msg = parser.parseTestMessage(input);
- var expectedData = new buffer.Buffer(14);
- expectedData.setUint32(0, 14);
- expectedData.setUint8(4, 0);
- expectedData.setUint64(5, 9);
- expectedData.setUint8(13, 0);
- checkData(msg.buffer, expectedData, input);
- }
-
- function testHandles() {
- var input = '// This message has handles! \n[handles]50 [u8]2';
- var msg = parser.parseTestMessage(input);
- var expectedData = new buffer.Buffer(8);
- expectedData.setUint64(0, 2);
-
- if (msg.handleCount != 50) {
- var s = 'wrong handle count (' + msg.handleCount + ')';
- throw new TestMessageParserFailure(s, input);
- }
- checkData(msg.buffer, expectedData, input);
- }
-
- function testEmptyInput() {
- var msg = parser.parseTestMessage('');
- if (msg.buffer.byteLength != 0)
- throw new TestMessageParserFailure('expected empty message', '');
- }
-
- function testBlankInput() {
- var input = ' \t // hello world \n\r \t// the answer is 42 ';
- var msg = parser.parseTestMessage(input);
- if (msg.buffer.byteLength != 0)
- throw new TestMessageParserFailure('expected empty message', input);
- }
-
- function testInvalidInput() {
- function parserShouldFail(input) {
- try {
- parser.parseTestMessage(input);
- } catch (e) {
- if (e instanceof parser.InputError)
- return;
- throw new TestMessageParserFailure(
- 'unexpected exception ' + e.toString(), input);
- }
- throw new TestMessageParserFailure("didn't detect invalid input", file);
- }
-
- ['/ 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'
- ].forEach(parserShouldFail);
- }
-
- try {
- testFloatItems();
- testUnsignedIntegerItems();
- testSignedIntegerItems();
- testByteItems();
- testInvalidInput();
- testEmptyInput();
- testBlankInput();
- testHandles();
- testAnchors();
- } catch (e) {
- return e.toString();
- }
- return null;
- }
-
- function getMessageTestFiles(prefix) {
- var sourceRoot = file.getSourceRootDirectory();
- expect(sourceRoot).not.toBeNull();
-
- var testDir = sourceRoot +
- "/mojo/public/interfaces/bindings/tests/data/validation/";
- var testFiles = file.getFilesInDirectory(testDir);
- expect(testFiles).not.toBeNull();
- expect(testFiles.length).toBeGreaterThan(0);
-
- // The matching ".data" pathnames with the extension removed.
- return testFiles.filter(function(s) {
- return s.substr(-5) == ".data" && s.indexOf(prefix) == 0;
- }).map(function(s) {
- return testDir + s.slice(0, -5);
- });
- }
-
- function readTestMessage(filename) {
- var contents = file.readFileToString(filename + ".data");
- expect(contents).not.toBeNull();
- return parser.parseTestMessage(contents);
- }
-
- function readTestExpected(filename) {
- var contents = file.readFileToString(filename + ".expected");
- expect(contents).not.toBeNull();
- return contents.trim();
- }
-
- function checkValidationResult(testFile, err) {
- var actualResult = (err === noError) ? "PASS" : err;
- var expectedResult = readTestExpected(testFile);
- if (actualResult != expectedResult)
- console.log("[Test message validation failed: " + testFile + " ]");
- expect(actualResult).toEqual(expectedResult);
- }
-
- function testMessageValidation(prefix, filters) {
- var testFiles = getMessageTestFiles(prefix);
- expect(testFiles.length).toBeGreaterThan(0);
-
- for (var i = 0; i < testFiles.length; i++) {
- // TODO(hansmuller) Temporarily skipping array pointer overflow tests
- // because JS numbers are limited to 53 bits.
- // TODO(rudominer): Temporarily skipping 'no-such-method',
- // 'invalid_request_flags', and 'invalid_response_flags' until additional
- // logic in *RequestValidator and *ResponseValidator is ported from
- // cpp to js.
- // TODO(crbug/640298): Implement max recursion depth for JS.
- // TODO(crbug/628104): Support struct map keys for JS.
- if (testFiles[i].indexOf("overflow") != -1 ||
- testFiles[i].indexOf("conformance_mthd19") != -1 ||
- testFiles[i].indexOf("conformance_mthd20") != -1 ||
- testFiles[i].indexOf("no_such_method") != -1 ||
- testFiles[i].indexOf("invalid_request_flags") != -1 ||
- testFiles[i].indexOf("invalid_response_flags") != -1) {
- console.log("[Skipping " + testFiles[i] + "]");
- continue;
- }
-
- var testMessage = readTestMessage(testFiles[i]);
- var handles = new Array(testMessage.handleCount);
- var message = new codec.Message(testMessage.buffer, handles);
- var messageValidator = new validator.Validator(message);
-
- var err = messageValidator.validateMessageHeader();
- for (var j = 0; err === noError && j < filters.length; ++j)
- err = filters[j](messageValidator);
-
- checkValidationResult(testFiles[i], err);
- }
- }
-
- function testConformanceMessageValidation() {
- testMessageValidation("conformance_", [
- testInterface.ConformanceTestInterface.validateRequest]);
- }
-
- function testBoundsCheckMessageValidation() {
- testMessageValidation("boundscheck_", [
- testInterface.BoundsCheckTestInterface.validateRequest]);
- }
-
- function testResponseConformanceMessageValidation() {
- testMessageValidation("resp_conformance_", [
- testInterface.ConformanceTestInterface.validateResponse]);
- }
-
- function testResponseBoundsCheckMessageValidation() {
- testMessageValidation("resp_boundscheck_", [
- testInterface.BoundsCheckTestInterface.validateResponse]);
- }
-
- function testIntegratedMessageValidation(testFilesPattern, endpoint) {
- var testFiles = getMessageTestFiles(testFilesPattern);
- expect(testFiles.length).toBeGreaterThan(0);
-
- var testMessagePipe = core.createMessagePipe();
- expect(testMessagePipe.result).toBe(core.RESULT_OK);
-
- endpoint.bind(testMessagePipe.handle1);
- var observer = validator.ValidationErrorObserverForTesting.getInstance();
-
- for (var i = 0; i < testFiles.length; i++) {
- var testMessage = readTestMessage(testFiles[i]);
- var handles = new Array(testMessage.handleCount);
-
- var writeMessageValue = core.writeMessage(
- testMessagePipe.handle0,
- new Uint8Array(testMessage.buffer.arrayBuffer),
- new Array(testMessage.handleCount),
- core.WRITE_MESSAGE_FLAG_NONE);
- expect(writeMessageValue).toBe(core.RESULT_OK);
-
- endpoint.waitForNextMessageForTesting();
- checkValidationResult(testFiles[i], observer.lastError);
- observer.reset();
- }
-
- expect(core.close(testMessagePipe.handle0)).toBe(core.RESULT_OK);
- }
-
- function testIntegratedMessageHeaderValidation() {
- testIntegratedMessageValidation(
- "integration_msghdr",
- new bindings.Binding(testInterface.IntegrationTestInterface, {}));
- testIntegratedMessageValidation(
- "integration_msghdr",
- new testInterface.IntegrationTestInterfacePtr().ptr);
- }
-
- function testIntegratedRequestMessageValidation() {
- testIntegratedMessageValidation(
- "integration_intf_rqst",
- new bindings.Binding(testInterface.IntegrationTestInterface, {}));
- }
-
- function testIntegratedResponseMessageValidation() {
- testIntegratedMessageValidation(
- "integration_intf_resp",
- new testInterface.IntegrationTestInterfacePtr().ptr);
- }
-
- expect(checkTestMessageParser()).toBeNull();
- testConformanceMessageValidation();
- testBoundsCheckMessageValidation();
- testResponseConformanceMessageValidation();
- testResponseBoundsCheckMessageValidation();
- testIntegratedMessageHeaderValidation();
- testIntegratedResponseMessageValidation();
- testIntegratedRequestMessageValidation();
- validator.clearTestingMode();
-
- this.result = "PASS";
-});
diff --git a/mojo/public/js/threading.js b/mojo/public/js/threading.js
deleted file mode 100644
index 49ab5c9142..0000000000
--- a/mojo/public/js/threading.js
+++ /dev/null
@@ -1,21 +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.
-
-// Module "mojo/public/js/threading"
-//
-// Note: This file is for documentation purposes only. The code here is not
-// actually executed. The real module is implemented natively in Mojo.
-//
-// This module provides a way for a Service implemented in JS
-// to exit by quitting the current message loop. This module is not
-// intended to be used by Mojo JS application started by the JS
-// content handler.
-
-while (1);
-
-/**
- * Quits the current message loop, esssentially:
- * base::MessageLoop::current()->QuitNow();
-*/
-function quit() { [native code] }
diff --git a/mojo/public/js/unicode.js b/mojo/public/js/unicode.js
deleted file mode 100644
index be2ba0e63c..0000000000
--- a/mojo/public/js/unicode.js
+++ /dev/null
@@ -1,51 +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.
-
-/**
- * Defines functions for translating between JavaScript strings and UTF8 strings
- * stored in ArrayBuffers. There is much room for optimization in this code if
- * it proves necessary.
- */
-define("mojo/public/js/unicode", function() {
- /**
- * Decodes the UTF8 string from the given buffer.
- * @param {ArrayBufferView} buffer The buffer containing UTF8 string data.
- * @return {string} The corresponding JavaScript string.
- */
- function decodeUtf8String(buffer) {
- return decodeURIComponent(escape(String.fromCharCode.apply(null, buffer)));
- }
-
- /**
- * Encodes the given JavaScript string into UTF8.
- * @param {string} str The string to encode.
- * @param {ArrayBufferView} outputBuffer The buffer to contain the result.
- * Should be pre-allocated to hold enough space. Use |utf8Length| to determine
- * how much space is required.
- * @return {number} The number of bytes written to |outputBuffer|.
- */
- function encodeUtf8String(str, outputBuffer) {
- var utf8String = unescape(encodeURIComponent(str));
- if (outputBuffer.length < utf8String.length)
- throw new Error("Buffer too small for encodeUtf8String");
- for (var i = 0; i < outputBuffer.length && i < utf8String.length; i++)
- outputBuffer[i] = utf8String.charCodeAt(i);
- return i;
- }
-
- /**
- * Returns the number of bytes that a UTF8 encoding of the JavaScript string
- * |str| would occupy.
- */
- function utf8Length(str) {
- var utf8String = unescape(encodeURIComponent(str));
- return utf8String.length;
- }
-
- var exports = {};
- exports.decodeUtf8String = decodeUtf8String;
- exports.encodeUtf8String = encodeUtf8String;
- exports.utf8Length = utf8Length;
- return exports;
-});
diff --git a/mojo/public/js/validator.js b/mojo/public/js/validator.js
deleted file mode 100644
index 283546d4f1..0000000000
--- a/mojo/public/js/validator.js
+++ /dev/null
@@ -1,560 +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.
-
-define("mojo/public/js/validator", [
- "mojo/public/js/codec",
-], function(codec) {
-
- var validationError = {
- NONE: 'VALIDATION_ERROR_NONE',
- MISALIGNED_OBJECT: 'VALIDATION_ERROR_MISALIGNED_OBJECT',
- ILLEGAL_MEMORY_RANGE: 'VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE',
- UNEXPECTED_STRUCT_HEADER: 'VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER',
- UNEXPECTED_ARRAY_HEADER: 'VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER',
- ILLEGAL_HANDLE: 'VALIDATION_ERROR_ILLEGAL_HANDLE',
- UNEXPECTED_INVALID_HANDLE: 'VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE',
- ILLEGAL_POINTER: 'VALIDATION_ERROR_ILLEGAL_POINTER',
- UNEXPECTED_NULL_POINTER: 'VALIDATION_ERROR_UNEXPECTED_NULL_POINTER',
- MESSAGE_HEADER_INVALID_FLAGS:
- 'VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS',
- MESSAGE_HEADER_MISSING_REQUEST_ID:
- 'VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID',
- DIFFERENT_SIZED_ARRAYS_IN_MAP:
- 'VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP',
- INVALID_UNION_SIZE: 'VALIDATION_ERROR_INVALID_UNION_SIZE',
- UNEXPECTED_NULL_UNION: 'VALIDATION_ERROR_UNEXPECTED_NULL_UNION',
- UNKNOWN_ENUM_VALUE: 'VALIDATION_ERROR_UNKNOWN_ENUM_VALUE',
- };
-
- var NULL_MOJO_POINTER = "NULL_MOJO_POINTER";
- var gValidationErrorObserver = null;
-
- function reportValidationError(error) {
- if (gValidationErrorObserver) {
- gValidationErrorObserver.setLastError(error);
- }
- }
-
- var ValidationErrorObserverForTesting = (function() {
- function Observer() {
- this.lastError = validationError.NONE;
- this.callback = null;
- }
-
- Observer.prototype.setLastError = function(error) {
- this.lastError = error;
- if (this.callback) {
- this.callback(error);
- }
- };
-
- Observer.prototype.reset = function(error) {
- this.lastError = validationError.NONE;
- this.callback = null;
- };
-
- return {
- getInstance: function() {
- if (!gValidationErrorObserver) {
- gValidationErrorObserver = new Observer();
- }
- return gValidationErrorObserver;
- }
- };
- })();
-
- function isTestingMode() {
- return Boolean(gValidationErrorObserver);
- }
-
- function clearTestingMode() {
- gValidationErrorObserver = null;
- }
-
- function isEnumClass(cls) {
- return cls instanceof codec.Enum;
- }
-
- function isStringClass(cls) {
- return cls === codec.String || cls === codec.NullableString;
- }
-
- function isHandleClass(cls) {
- return cls === codec.Handle || cls === codec.NullableHandle;
- }
-
- function isInterfaceClass(cls) {
- return cls instanceof codec.Interface;
- }
-
- function isInterfaceRequestClass(cls) {
- return cls === codec.InterfaceRequest ||
- cls === codec.NullableInterfaceRequest;
- }
-
- function isNullable(type) {
- return type === codec.NullableString || type === codec.NullableHandle ||
- type === codec.NullableInterface ||
- type === codec.NullableInterfaceRequest ||
- type instanceof codec.NullableArrayOf ||
- type instanceof codec.NullablePointerTo;
- }
-
- function Validator(message) {
- this.message = message;
- this.offset = 0;
- this.handleIndex = 0;
- }
-
- Object.defineProperty(Validator.prototype, "offsetLimit", {
- get: function() { return this.message.buffer.byteLength; }
- });
-
- Object.defineProperty(Validator.prototype, "handleIndexLimit", {
- get: function() { return this.message.handles.length; }
- });
-
- // True if we can safely allocate a block of bytes from start to
- // to start + numBytes.
- Validator.prototype.isValidRange = function(start, numBytes) {
- // Only positive JavaScript integers that are less than 2^53
- // (Number.MAX_SAFE_INTEGER) can be represented exactly.
- if (start < this.offset || numBytes <= 0 ||
- !Number.isSafeInteger(start) ||
- !Number.isSafeInteger(numBytes))
- return false;
-
- var newOffset = start + numBytes;
- if (!Number.isSafeInteger(newOffset) || newOffset > this.offsetLimit)
- return false;
-
- return true;
- };
-
- Validator.prototype.claimRange = function(start, numBytes) {
- if (this.isValidRange(start, numBytes)) {
- this.offset = start + numBytes;
- return true;
- }
- return false;
- };
-
- Validator.prototype.claimHandle = function(index) {
- if (index === codec.kEncodedInvalidHandleValue)
- return true;
-
- if (index < this.handleIndex || index >= this.handleIndexLimit)
- return false;
-
- // This is safe because handle indices are uint32.
- this.handleIndex = index + 1;
- return true;
- };
-
- Validator.prototype.validateEnum = function(offset, enumClass) {
- // Note: Assumes that enums are always 32 bits! But this matches
- // mojom::generate::pack::PackedField::GetSizeForKind, so it should be okay.
- var value = this.message.buffer.getInt32(offset);
- return enumClass.validate(value);
- }
-
- Validator.prototype.validateHandle = function(offset, nullable) {
- var index = this.message.buffer.getUint32(offset);
-
- if (index === codec.kEncodedInvalidHandleValue)
- return nullable ?
- validationError.NONE : validationError.UNEXPECTED_INVALID_HANDLE;
-
- if (!this.claimHandle(index))
- return validationError.ILLEGAL_HANDLE;
-
- return validationError.NONE;
- };
-
- Validator.prototype.validateInterface = function(offset, nullable) {
- return this.validateHandle(offset, nullable);
- };
-
- Validator.prototype.validateInterfaceRequest = function(offset, nullable) {
- return this.validateHandle(offset, nullable);
- };
-
- Validator.prototype.validateStructHeader = function(offset, minNumBytes) {
- if (!codec.isAligned(offset))
- return validationError.MISALIGNED_OBJECT;
-
- if (!this.isValidRange(offset, codec.kStructHeaderSize))
- return validationError.ILLEGAL_MEMORY_RANGE;
-
- var numBytes = this.message.buffer.getUint32(offset);
-
- if (numBytes < minNumBytes)
- return validationError.UNEXPECTED_STRUCT_HEADER;
-
- if (!this.claimRange(offset, numBytes))
- return validationError.ILLEGAL_MEMORY_RANGE;
-
- return validationError.NONE;
- };
-
- Validator.prototype.validateStructVersion = function(offset, versionSizes) {
- var numBytes = this.message.buffer.getUint32(offset);
- var version = this.message.buffer.getUint32(offset + 4);
-
- if (version <= versionSizes[versionSizes.length - 1].version) {
- // Scan in reverse order to optimize for more recent versionSizes.
- for (var i = versionSizes.length - 1; i >= 0; --i) {
- if (version >= versionSizes[i].version) {
- if (numBytes == versionSizes[i].numBytes)
- break;
- return validationError.UNEXPECTED_STRUCT_HEADER;
- }
- }
- } else if (numBytes < versionSizes[versionSizes.length-1].numBytes) {
- return validationError.UNEXPECTED_STRUCT_HEADER;
- }
-
- return validationError.NONE;
- };
-
- Validator.prototype.isFieldInStructVersion = function(offset, fieldVersion) {
- var structVersion = this.message.buffer.getUint32(offset + 4);
- return fieldVersion <= structVersion;
- };
-
- // TODO(wangjimmy): Add support for v2 messages.
- Validator.prototype.validateMessageHeader = function() {
-
- var err = this.validateStructHeader(0, codec.kMessageHeaderSize);
- if (err != validationError.NONE)
- return err;
-
- var numBytes = this.message.getHeaderNumBytes();
- var version = this.message.getHeaderVersion();
-
- var validVersionAndNumBytes =
- (version == 0 && numBytes == codec.kMessageHeaderSize) ||
- (version == 1 &&
- numBytes == codec.kMessageWithRequestIDHeaderSize) ||
- (version > 1 &&
- numBytes >= codec.kMessageWithRequestIDHeaderSize);
- if (!validVersionAndNumBytes)
- return validationError.UNEXPECTED_STRUCT_HEADER;
-
- var expectsResponse = this.message.expectsResponse();
- var isResponse = this.message.isResponse();
-
- if (version == 0 && (expectsResponse || isResponse))
- return validationError.MESSAGE_HEADER_MISSING_REQUEST_ID;
-
- if (isResponse && expectsResponse)
- return validationError.MESSAGE_HEADER_INVALID_FLAGS;
-
- return validationError.NONE;
- };
-
- Validator.prototype.validateMessageIsRequestWithoutResponse = function() {
- if (this.message.isResponse() || this.message.expectsResponse()) {
- return validationError.MESSAGE_HEADER_INVALID_FLAGS;
- }
- return validationError.NONE;
- };
-
- Validator.prototype.validateMessageIsRequestExpectingResponse = function() {
- if (this.message.isResponse() || !this.message.expectsResponse()) {
- return validationError.MESSAGE_HEADER_INVALID_FLAGS;
- }
- return validationError.NONE;
- };
-
- Validator.prototype.validateMessageIsResponse = function() {
- if (this.message.expectsResponse() || !this.message.isResponse()) {
- return validationError.MESSAGE_HEADER_INVALID_FLAGS;
- }
- return validationError.NONE;
- };
-
- // Returns the message.buffer relative offset this pointer "points to",
- // NULL_MOJO_POINTER if the pointer represents a null, or JS null if the
- // pointer's value is not valid.
- Validator.prototype.decodePointer = function(offset) {
- var pointerValue = this.message.buffer.getUint64(offset);
- if (pointerValue === 0)
- return NULL_MOJO_POINTER;
- var bufferOffset = offset + pointerValue;
- return Number.isSafeInteger(bufferOffset) ? bufferOffset : null;
- };
-
- Validator.prototype.decodeUnionSize = function(offset) {
- return this.message.buffer.getUint32(offset);
- };
-
- Validator.prototype.decodeUnionTag = function(offset) {
- return this.message.buffer.getUint32(offset + 4);
- };
-
- Validator.prototype.validateArrayPointer = function(
- offset, elementSize, elementType, nullable, expectedDimensionSizes,
- currentDimension) {
- var arrayOffset = this.decodePointer(offset);
- if (arrayOffset === null)
- return validationError.ILLEGAL_POINTER;
-
- if (arrayOffset === NULL_MOJO_POINTER)
- return nullable ?
- validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
-
- return this.validateArray(arrayOffset, elementSize, elementType,
- expectedDimensionSizes, currentDimension);
- };
-
- Validator.prototype.validateStructPointer = function(
- offset, structClass, nullable) {
- var structOffset = this.decodePointer(offset);
- if (structOffset === null)
- return validationError.ILLEGAL_POINTER;
-
- if (structOffset === NULL_MOJO_POINTER)
- return nullable ?
- validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
-
- return structClass.validate(this, structOffset);
- };
-
- Validator.prototype.validateUnion = function(
- offset, unionClass, nullable) {
- var size = this.message.buffer.getUint32(offset);
- if (size == 0) {
- return nullable ?
- validationError.NONE : validationError.UNEXPECTED_NULL_UNION;
- }
-
- return unionClass.validate(this, offset);
- };
-
- Validator.prototype.validateNestedUnion = function(
- offset, unionClass, nullable) {
- var unionOffset = this.decodePointer(offset);
- if (unionOffset === null)
- return validationError.ILLEGAL_POINTER;
-
- if (unionOffset === NULL_MOJO_POINTER)
- return nullable ?
- validationError.NONE : validationError.UNEXPECTED_NULL_UNION;
-
- return this.validateUnion(unionOffset, unionClass, nullable);
- };
-
- // This method assumes that the array at arrayPointerOffset has
- // been validated.
-
- Validator.prototype.arrayLength = function(arrayPointerOffset) {
- var arrayOffset = this.decodePointer(arrayPointerOffset);
- return this.message.buffer.getUint32(arrayOffset + 4);
- };
-
- Validator.prototype.validateMapPointer = function(
- offset, mapIsNullable, keyClass, valueClass, valueIsNullable) {
- // Validate the implicit map struct:
- // struct {array<keyClass> keys; array<valueClass> values};
- var structOffset = this.decodePointer(offset);
- if (structOffset === null)
- return validationError.ILLEGAL_POINTER;
-
- if (structOffset === NULL_MOJO_POINTER)
- return mapIsNullable ?
- validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
-
- var mapEncodedSize = codec.kStructHeaderSize + codec.kMapStructPayloadSize;
- var err = this.validateStructHeader(structOffset, mapEncodedSize);
- if (err !== validationError.NONE)
- return err;
-
- // Validate the keys array.
- var keysArrayPointerOffset = structOffset + codec.kStructHeaderSize;
- err = this.validateArrayPointer(
- keysArrayPointerOffset, keyClass.encodedSize, keyClass, false, [0], 0);
- if (err !== validationError.NONE)
- return err;
-
- // Validate the values array.
- var valuesArrayPointerOffset = keysArrayPointerOffset + 8;
- var valuesArrayDimensions = [0]; // Validate the actual length below.
- if (valueClass instanceof codec.ArrayOf)
- valuesArrayDimensions =
- valuesArrayDimensions.concat(valueClass.dimensions());
- var err = this.validateArrayPointer(valuesArrayPointerOffset,
- valueClass.encodedSize,
- valueClass,
- valueIsNullable,
- valuesArrayDimensions,
- 0);
- if (err !== validationError.NONE)
- return err;
-
- // Validate the lengths of the keys and values arrays.
- var keysArrayLength = this.arrayLength(keysArrayPointerOffset);
- var valuesArrayLength = this.arrayLength(valuesArrayPointerOffset);
- if (keysArrayLength != valuesArrayLength)
- return validationError.DIFFERENT_SIZED_ARRAYS_IN_MAP;
-
- return validationError.NONE;
- };
-
- Validator.prototype.validateStringPointer = function(offset, nullable) {
- return this.validateArrayPointer(
- offset, codec.Uint8.encodedSize, codec.Uint8, nullable, [0], 0);
- };
-
- // Similar to Array_Data<T>::Validate()
- // mojo/public/cpp/bindings/lib/array_internal.h
-
- Validator.prototype.validateArray =
- function (offset, elementSize, elementType, expectedDimensionSizes,
- currentDimension) {
- if (!codec.isAligned(offset))
- return validationError.MISALIGNED_OBJECT;
-
- if (!this.isValidRange(offset, codec.kArrayHeaderSize))
- return validationError.ILLEGAL_MEMORY_RANGE;
-
- var numBytes = this.message.buffer.getUint32(offset);
- var numElements = this.message.buffer.getUint32(offset + 4);
-
- // Note: this computation is "safe" because elementSize <= 8 and
- // numElements is a uint32.
- var elementsTotalSize = (elementType === codec.PackedBool) ?
- Math.ceil(numElements / 8) : (elementSize * numElements);
-
- if (numBytes < codec.kArrayHeaderSize + elementsTotalSize)
- return validationError.UNEXPECTED_ARRAY_HEADER;
-
- if (expectedDimensionSizes[currentDimension] != 0 &&
- numElements != expectedDimensionSizes[currentDimension]) {
- return validationError.UNEXPECTED_ARRAY_HEADER;
- }
-
- if (!this.claimRange(offset, numBytes))
- return validationError.ILLEGAL_MEMORY_RANGE;
-
- // Validate the array's elements if they are pointers or handles.
-
- var elementsOffset = offset + codec.kArrayHeaderSize;
- var nullable = isNullable(elementType);
-
- if (isHandleClass(elementType))
- return this.validateHandleElements(elementsOffset, numElements, nullable);
- if (isInterfaceClass(elementType))
- return this.validateInterfaceElements(
- elementsOffset, numElements, nullable);
- if (isInterfaceRequestClass(elementType))
- return this.validateInterfaceRequestElements(
- elementsOffset, numElements, nullable);
- if (isStringClass(elementType))
- return this.validateArrayElements(
- elementsOffset, numElements, codec.Uint8, nullable, [0], 0);
- if (elementType instanceof codec.PointerTo)
- return this.validateStructElements(
- elementsOffset, numElements, elementType.cls, nullable);
- if (elementType instanceof codec.ArrayOf)
- return this.validateArrayElements(
- elementsOffset, numElements, elementType.cls, nullable,
- expectedDimensionSizes, currentDimension + 1);
- if (isEnumClass(elementType))
- return this.validateEnumElements(elementsOffset, numElements,
- elementType.cls);
-
- return validationError.NONE;
- };
-
- // Note: the |offset + i * elementSize| computation in the validateFooElements
- // methods below is "safe" because elementSize <= 8, offset and
- // numElements are uint32, and 0 <= i < numElements.
-
- Validator.prototype.validateHandleElements =
- function(offset, numElements, nullable) {
- var elementSize = codec.Handle.encodedSize;
- for (var i = 0; i < numElements; i++) {
- var elementOffset = offset + i * elementSize;
- var err = this.validateHandle(elementOffset, nullable);
- if (err != validationError.NONE)
- return err;
- }
- return validationError.NONE;
- };
-
- Validator.prototype.validateInterfaceElements =
- function(offset, numElements, nullable) {
- var elementSize = codec.Interface.prototype.encodedSize;
- for (var i = 0; i < numElements; i++) {
- var elementOffset = offset + i * elementSize;
- var err = this.validateInterface(elementOffset, nullable);
- if (err != validationError.NONE)
- return err;
- }
- return validationError.NONE;
- };
-
- Validator.prototype.validateInterfaceRequestElements =
- function(offset, numElements, nullable) {
- var elementSize = codec.InterfaceRequest.encodedSize;
- for (var i = 0; i < numElements; i++) {
- var elementOffset = offset + i * elementSize;
- var err = this.validateInterfaceRequest(elementOffset, nullable);
- if (err != validationError.NONE)
- return err;
- }
- return validationError.NONE;
- };
-
- // The elementClass parameter is the element type of the element arrays.
- Validator.prototype.validateArrayElements =
- function(offset, numElements, elementClass, nullable,
- expectedDimensionSizes, currentDimension) {
- var elementSize = codec.PointerTo.prototype.encodedSize;
- for (var i = 0; i < numElements; i++) {
- var elementOffset = offset + i * elementSize;
- var err = this.validateArrayPointer(
- elementOffset, elementClass.encodedSize, elementClass, nullable,
- expectedDimensionSizes, currentDimension);
- if (err != validationError.NONE)
- return err;
- }
- return validationError.NONE;
- };
-
- Validator.prototype.validateStructElements =
- function(offset, numElements, structClass, nullable) {
- var elementSize = codec.PointerTo.prototype.encodedSize;
- for (var i = 0; i < numElements; i++) {
- var elementOffset = offset + i * elementSize;
- var err =
- this.validateStructPointer(elementOffset, structClass, nullable);
- if (err != validationError.NONE)
- return err;
- }
- return validationError.NONE;
- };
-
- Validator.prototype.validateEnumElements =
- function(offset, numElements, enumClass) {
- var elementSize = codec.Enum.prototype.encodedSize;
- for (var i = 0; i < numElements; i++) {
- var elementOffset = offset + i * elementSize;
- var err = this.validateEnum(elementOffset, enumClass);
- if (err != validationError.NONE)
- return err;
- }
- return validationError.NONE;
- };
-
- var exports = {};
- exports.validationError = validationError;
- exports.Validator = Validator;
- exports.ValidationErrorObserverForTesting = ValidationErrorObserverForTesting;
- exports.reportValidationError = reportValidationError;
- exports.isTestingMode = isTestingMode;
- exports.clearTestingMode = clearTestingMode;
- return exports;
-});
diff --git a/mojo/public/mojom/base/BUILD.gn b/mojo/public/mojom/base/BUILD.gn
new file mode 100644
index 0000000000..807d4b91be
--- /dev/null
+++ b/mojo/public/mojom/base/BUILD.gn
@@ -0,0 +1,45 @@
+# 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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+mojom_component("base") {
+ sources = [
+ "big_buffer.mojom",
+ "big_string.mojom",
+ "file.mojom",
+ "file_error.mojom",
+ "file_info.mojom",
+ "file_path.mojom",
+ "memory_allocator_dump_cross_process_uid.mojom",
+ "process_id.mojom",
+ "ref_counted_memory.mojom",
+ "shared_memory.mojom",
+ "string16.mojom",
+ "text_direction.mojom",
+ "thread_priority.mojom",
+ "time.mojom",
+ "unguessable_token.mojom",
+ "values.mojom",
+ ]
+
+ if (is_win) {
+ sources += [ "logfont_win.mojom" ]
+ }
+ enabled_features = []
+ if (is_win) {
+ enabled_features += [ "file_path_is_string16" ]
+ } else {
+ enabled_features += [ "file_path_is_string" ]
+ }
+
+ output_prefix = "mojo_base_mojom"
+ macro_prefix = "MOJO_BASE_MOJOM"
+}
+
+mojom("read_only_buffer") {
+ sources = [
+ "read_only_buffer.mojom",
+ ]
+}
diff --git a/mojo/public/mojom/base/big_buffer.mojom b/mojo/public/mojom/base/big_buffer.mojom
new file mode 100644
index 0000000000..1265b66437
--- /dev/null
+++ b/mojo/public/mojom/base/big_buffer.mojom
@@ -0,0 +1,18 @@
+// 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.
+
+module mojo_base.mojom;
+
+struct BigBufferSharedMemoryRegion {
+ handle<shared_buffer> buffer_handle;
+ uint32 size;
+};
+
+// A helper union to be used when messages want to accept arbitrarily large
+// chunks of byte data. Beyond a certain size threshold, shared memory will be
+// used in lieu of an inline byte array.
+union BigBuffer {
+ array<uint8> bytes;
+ BigBufferSharedMemoryRegion shared_memory;
+};
diff --git a/mojo/public/mojom/base/big_string.mojom b/mojo/public/mojom/base/big_string.mojom
new file mode 100644
index 0000000000..c971caf567
--- /dev/null
+++ b/mojo/public/mojom/base/big_string.mojom
@@ -0,0 +1,19 @@
+// 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.
+
+module mojo_base.mojom;
+
+import "mojo/public/mojom/base/big_buffer.mojom";
+
+// Corresponds to |std::string| in <string>.
+// Corresponds to |WTF::String| in
+// third_party/WebKit/Source/platform/wtf/text/WTFString.h.
+// This type should be used in place of string whenever the contents of the
+// string may realistically exceed 64 kB in size. This string is safe to store
+// an arbitrarily large amount of data (available memory permitting) without
+// negatively impacting IPC performance or hitting hard message size
+// boundaries.
+struct BigString {
+ mojo_base.mojom.BigBuffer data;
+};
diff --git a/mojo/public/mojom/base/file.mojom b/mojo/public/mojom/base/file.mojom
new file mode 100644
index 0000000000..9f08b26bea
--- /dev/null
+++ b/mojo/public/mojom/base/file.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_base.mojom;
+
+// Corresponds to |base::File| in base/files/file.h
+struct File {
+ handle fd;
+ bool async;
+};
diff --git a/mojo/public/mojom/base/file_error.mojom b/mojo/public/mojom/base/file_error.mojom
new file mode 100644
index 0000000000..3ca61aa91f
--- /dev/null
+++ b/mojo/public/mojom/base/file_error.mojom
@@ -0,0 +1,27 @@
+// 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.
+
+module mojo_base.mojom;
+
+// Error codes used by the file system. These error codes line up exactly with
+// those of base::File and are typemapped to base::File::Error enum.
+enum FileError {
+ OK = 0,
+ FAILED = -1,
+ IN_USE = -2,
+ EXISTS = -3,
+ NOT_FOUND = -4,
+ ACCESS_DENIED = -5,
+ TOO_MANY_OPENED = -6,
+ NO_MEMORY = -7,
+ NO_SPACE = -8,
+ NOT_A_DIRECTORY = -9,
+ INVALID_OPERATION = -10,
+ SECURITY = -11,
+ ABORT = -12,
+ NOT_A_FILE = -13,
+ NOT_EMPTY = -14,
+ INVALID_URL = -15,
+ IO = -16,
+};
diff --git a/mojo/public/mojom/base/file_info.mojom b/mojo/public/mojom/base/file_info.mojom
new file mode 100644
index 0000000000..5dff3ca38a
--- /dev/null
+++ b/mojo/public/mojom/base/file_info.mojom
@@ -0,0 +1,17 @@
+// 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_base.mojom;
+
+import "mojo/public/mojom/base/time.mojom";
+
+// Corresponds to base::File::Info.
+struct FileInfo {
+ int64 size;
+ bool is_directory;
+ bool is_symbolic_link;
+ Time last_modified;
+ Time last_accessed;
+ Time creation_time;
+};
diff --git a/mojo/public/mojom/base/file_path.mojom b/mojo/public/mojom/base/file_path.mojom
new file mode 100644
index 0000000000..097b37eeb9
--- /dev/null
+++ b/mojo/public/mojom/base/file_path.mojom
@@ -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.
+
+module mojo_base.mojom;
+
+struct FilePath {
+ // In chrome, ninja have a goal that can define file_path_is_string for a set
+ // of mojom files.
+ // In android we don't have such ability ,one would have to add
+ // "--enable_feature file_path_is_string" to all targets generating pickle
+ // files, headers and sources, and also in all project including them.
+ // Faster solution was to just remove this "EnableIf" definition in libchrome.
+ // [EnableIf=file_path_is_string]
+ string path;
+
+ // This duplicates the contents of mojo_base.mojom.String16. String16 isn't
+ // used here due to typemapping dependency problems. base::FilePath is
+ // used for the typemap for both variants, but base::string16 and WTF::String
+ // are used for mojo_base.mojom.String16 typemapping. This mismatch causes
+ // problems with dependencies.
+ [EnableIf=file_path_is_string16]
+ array<uint16> path;
+};
diff --git a/mojo/public/mojom/base/logfont_win.mojom b/mojo/public/mojom/base/logfont_win.mojom
new file mode 100644
index 0000000000..27922b8478
--- /dev/null
+++ b/mojo/public/mojom/base/logfont_win.mojom
@@ -0,0 +1,11 @@
+// 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_base.mojom;
+
+// Native Windows struct. Its typemap only exists for windows builds.
+[EnableIf=is_win]
+struct LOGFONT {
+ array<uint8> bytes;
+};
diff --git a/mojo/public/mojom/base/memory_allocator_dump_cross_process_uid.mojom b/mojo/public/mojom/base/memory_allocator_dump_cross_process_uid.mojom
new file mode 100644
index 0000000000..6091d79293
--- /dev/null
+++ b/mojo/public/mojom/base/memory_allocator_dump_cross_process_uid.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_base.mojom;
+
+// Corresponds to |base::trace_event::MemoryAllocatorDumpGuid| in base/trace_event/memory_allocator_dump_guid.h
+struct MemoryAllocatorDumpCrossProcessUid {
+ uint64 value;
+};
diff --git a/mojo/public/mojom/base/process_id.mojom b/mojo/public/mojom/base/process_id.mojom
new file mode 100644
index 0000000000..f3b17780a5
--- /dev/null
+++ b/mojo/public/mojom/base/process_id.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_base.mojom;
+
+struct ProcessId {
+ // This is the storage for the pid, on windows the pid is a DWORD and the
+ // value can be DWORD_MAX which maps gracefully to uint32 while on linux
+ // the pid is a signed int with a max value of 2^22.
+ uint32 pid;
+};
diff --git a/mojo/public/mojom/base/read_only_buffer.mojom b/mojo/public/mojom/base/read_only_buffer.mojom
new file mode 100644
index 0000000000..924f19ec82
--- /dev/null
+++ b/mojo/public/mojom/base/read_only_buffer.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_base.mojom;
+
+// This struct is typemapped to base::span<const uint8_t>.
+// Note: At the receiving end, this base::span<const uint8_t> refers to a memory
+// range within the received message.
+struct ReadOnlyBuffer {
+ array<uint8> buffer;
+};
diff --git a/mojo/public/mojom/base/ref_counted_memory.mojom b/mojo/public/mojom/base/ref_counted_memory.mojom
new file mode 100644
index 0000000000..4b245a00ec
--- /dev/null
+++ b/mojo/public/mojom/base/ref_counted_memory.mojom
@@ -0,0 +1,14 @@
+// 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.
+
+module mojo_base.mojom;
+
+import "mojo/public/mojom/base/big_buffer.mojom";
+
+// Chrome internally uses base::RefCountedMemory to pass data around. These
+// data segments can be fairly large, to the point where we don't want them
+// to be stuffed raw through the mojo ipc layer.
+struct RefCountedMemory {
+ mojo_base.mojom.BigBuffer data;
+};
diff --git a/mojo/public/mojom/base/shared_memory.mojom b/mojo/public/mojom/base/shared_memory.mojom
new file mode 100644
index 0000000000..4543ad6cb3
--- /dev/null
+++ b/mojo/public/mojom/base/shared_memory.mojom
@@ -0,0 +1,25 @@
+// 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.
+
+module mojo_base.mojom;
+
+// Wraps a shared memory handle with additional type information to convey that
+// the handle is only mappable to read-only memory.
+struct ReadOnlySharedMemoryRegion {
+ handle<shared_buffer> buffer;
+};
+
+// Wraps a shared memory handle with additional type information to convey that
+// the handle is mappable to writable memory but can also be converted to
+// a ReadOnlySharedMemoryRegion for sharing with other clients.
+struct WritableSharedMemoryRegion {
+ handle<shared_buffer> buffer;
+};
+
+// Wraps a shared memory handle with additional type information to convey that
+// the handle is always mappable to writable memory by any client which obtains
+// a handle duplicated from this one.
+struct UnsafeSharedMemoryRegion {
+ handle<shared_buffer> buffer;
+};
diff --git a/mojo/public/mojom/base/string16.mojom b/mojo/public/mojom/base/string16.mojom
new file mode 100644
index 0000000000..dd672baa16
--- /dev/null
+++ b/mojo/public/mojom/base/string16.mojom
@@ -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.
+
+module mojo_base.mojom;
+
+import "mojo/public/mojom/base/big_buffer.mojom";
+
+// Corresponds to |base::string16| in base/strings/string16.h
+// Corresponds to |WTF::String| in
+// third_party/WebKit/Source/platform/wtf/text/WTFString.h.
+// Don't make backwards-incompatible changes to this definition!
+// It's used in PageState serialization, so backwards incompatible changes
+// would cause stored PageState objects to be un-parseable. Please contact the
+// page state serialization owners before making such a change.
+struct String16 {
+ array<uint16> data;
+};
+
+// This type should be used in place of String16 whenever the contents of the
+// string may realistically exceed 64 kB in size. This string is safe to store
+// an arbitrarily large amount of data (available memory permitting) without
+// negatively impacting IPC performance or hitting hard message size
+// boundaries.
+struct BigString16 {
+ BigBuffer data;
+};
diff --git a/mojo/public/mojom/base/text_direction.mojom b/mojo/public/mojom/base/text_direction.mojom
new file mode 100644
index 0000000000..999ff3e58c
--- /dev/null
+++ b/mojo/public/mojom/base/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_base.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/public/mojom/base/thread_priority.mojom b/mojo/public/mojom/base/thread_priority.mojom
new file mode 100644
index 0000000000..21d7f34376
--- /dev/null
+++ b/mojo/public/mojom/base/thread_priority.mojom
@@ -0,0 +1,17 @@
+// 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_base.mojom;
+
+// Corresponds to |base::ThreadPriority|
+enum ThreadPriority {
+ // Suitable for threads that shouldn't disrupt high priority work.
+ BACKGROUND,
+ // Default priority level.
+ NORMAL,
+ // Suitable for threads which generate data for the display (at ~60Hz).
+ DISPLAY,
+ // Suitable for low-latency, glitch-resistant audio.
+ REALTIME_AUDIO,
+};
diff --git a/mojo/public/mojom/base/time.mojom b/mojo/public/mojom/base/time.mojom
new file mode 100644
index 0000000000..78cde1a167
--- /dev/null
+++ b/mojo/public/mojom/base/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_base.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/public/mojom/base/unguessable_token.mojom b/mojo/public/mojom/base/unguessable_token.mojom
new file mode 100644
index 0000000000..42aa7e2157
--- /dev/null
+++ b/mojo/public/mojom/base/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_base.mojom;
+
+// Corresponds to |base::UnguessableToken| in base/unguessable_token.h
+struct UnguessableToken {
+ uint64 high;
+ uint64 low;
+};
diff --git a/mojo/public/mojom/base/values.mojom b/mojo/public/mojom/base/values.mojom
new file mode 100644
index 0000000000..ea7c1fa590
--- /dev/null
+++ b/mojo/public/mojom/base/values.mojom
@@ -0,0 +1,38 @@
+// 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.
+
+module mojo_base.mojom;
+
+// Value represents a value that can be serialized to/from JSON.
+//
+// One notable caveat is that Value supports arbitrary binary data, which JSON
+// does not support natively.
+union Value {
+ // Null type placeholder. This field is never used.
+ uint8 null_value;
+ // Primitive types.
+ bool bool_value;
+ int32 int_value;
+ double double_value;
+ // Unicode string.
+ string string_value;
+ // Binary blob with arbitrary bytes. Not supported for JSON.
+ array<uint8> binary_value;
+ // Basic container support for lists and maps.
+ DictionaryValue dictionary_value;
+ ListValue list_value;
+};
+
+// Interfaces that only want to handle a value of dictionary or list type
+// should use base.mojom.DictionaryValue or base.mojom.ListValue in the method
+// declaration. Though both of these types are mapped to base::Value in C++,
+// the generated deserialization will guarantee that the method is only invoked
+// with a base::Value of the correct subtype.
+struct DictionaryValue {
+ map<string, Value> storage;
+};
+
+struct ListValue {
+ array<Value> storage;
+};
diff --git a/mojo/public/tools/bindings/BUILD.gn b/mojo/public/tools/bindings/BUILD.gn
index 153d110332..4979617303 100644
--- a/mojo/public/tools/bindings/BUILD.gn
+++ b/mojo/public/tools/bindings/BUILD.gn
@@ -3,6 +3,7 @@
# found in the LICENSE file.
import("//mojo/public/tools/bindings/mojom.gni")
+import("//third_party/jinja2/jinja2.gni")
action("precompile_templates") {
sources = mojom_generator_sources
@@ -17,6 +18,7 @@ action("precompile_templates") {
"$mojom_generator_root/generators/cpp_templates/interface_response_validator_declaration.tmpl",
"$mojom_generator_root/generators/cpp_templates/interface_stub_declaration.tmpl",
"$mojom_generator_root/generators/cpp_templates/module-shared-internal.h.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/module-shared-message-ids.h.tmpl",
"$mojom_generator_root/generators/cpp_templates/module-shared.cc.tmpl",
"$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl",
"$mojom_generator_root/generators/cpp_templates/module.cc.tmpl",
@@ -29,6 +31,7 @@ action("precompile_templates") {
"$mojom_generator_root/generators/cpp_templates/struct_serialization_declaration.tmpl",
"$mojom_generator_root/generators/cpp_templates/struct_traits_declaration.tmpl",
"$mojom_generator_root/generators/cpp_templates/struct_traits_definition.tmpl",
+ "$mojom_generator_root/generators/cpp_templates/struct_unserialized_message_context.tmpl",
"$mojom_generator_root/generators/cpp_templates/union_data_view_declaration.tmpl",
"$mojom_generator_root/generators/cpp_templates/union_data_view_definition.tmpl",
"$mojom_generator_root/generators/cpp_templates/union_declaration.tmpl",
@@ -55,6 +58,10 @@ action("precompile_templates") {
"$mojom_generator_root/generators/java_templates/struct.java.tmpl",
"$mojom_generator_root/generators/java_templates/union.java.tmpl",
"$mojom_generator_root/generators/js_templates/enum_definition.tmpl",
+ "$mojom_generator_root/generators/js_templates/externs/interface_definition.tmpl",
+ "$mojom_generator_root/generators/js_templates/externs/module.externs.tmpl",
+ "$mojom_generator_root/generators/js_templates/externs/struct_definition.tmpl",
+ "$mojom_generator_root/generators/js_templates/fuzzing.tmpl",
"$mojom_generator_root/generators/js_templates/interface_definition.tmpl",
"$mojom_generator_root/generators/js_templates/module.amd.tmpl",
"$mojom_generator_root/generators/js_templates/module_definition.tmpl",
@@ -63,6 +70,8 @@ action("precompile_templates") {
"$mojom_generator_root/generators/js_templates/validation_macros.tmpl",
]
script = mojom_generator_script
+
+ inputs = jinja2_sources
outputs = [
"$target_gen_dir/cpp_templates.zip",
"$target_gen_dir/java_templates.zip",
@@ -72,6 +81,6 @@ action("precompile_templates") {
"--use_bundled_pylibs",
"precompile",
"-o",
- rebase_path(target_gen_dir),
+ rebase_path(target_gen_dir, root_build_dir),
]
}
diff --git a/mojo/public/tools/bindings/README.md b/mojo/public/tools/bindings/README.md
index 737c7e61df..d1ffc448e9 100644
--- a/mojo/public/tools/bindings/README.md
+++ b/mojo/public/tools/bindings/README.md
@@ -1,12 +1,13 @@
-# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojom IDL and Bindings Generator
-This document is a subset of the [Mojo documentation](/mojo).
+# Mojom IDL and Bindings Generator
+This document is a subset of the [Mojo documentation](/mojo/README.md).
[TOC]
## Overview
Mojom is the IDL for Mojo bindings interfaces. Given a `.mojom` file, the
-[bindings generator](https://cs.chromium.org/chromium/src/mojo/public/tools/bindings)
+[bindings
+generator](https://cs.chromium.org/chromium/src/mojo/public/tools/bindings/)
outputs bindings for all supported languages: **C++**, **JavaScript**, and
**Java**.
@@ -248,7 +249,7 @@ struct AllTheThings {
SampleInterface&? nullable_sample_interface_request;
associated SampleInterface associated_interface_client;
associated SampleInterface& associated_interface_request;
- assocaited SampleInterface&? maybe_another_associated_request;
+ associated SampleInterface&? maybe_another_associated_request;
};
```
@@ -256,6 +257,29 @@ For details on how all of these different types translate to usable generated
code, see
[documentation for individual target languages](#Generated-Code-For-Target-Languages).
+### Unions
+
+Mojom supports tagged unions using the **union** keyword. A union is a
+collection of fields which may taken the value of any single one of those fields
+at a time. Thus they provide a way to represent a variant value type while
+minimizing storage requirements.
+
+Union fields may be of any type supported by [struct](#Structs) fields. For
+example:
+
+```cpp
+union ExampleUnion {
+ string str;
+ StringPair pair;
+ int64 id;
+ array<uint64, 2> guid;
+ SampleInterface iface;
+};
+```
+
+For details on how unions like this translate to generated bindings code, see
+[documentation for individual target languages](#Generated-Code-For-Target-Languages).
+
### Enumeration Types
Enumeration types may be defined using the **enum** keyword either directly
@@ -348,9 +372,9 @@ interesting attributes supported today.
: The `Sync` attribute may be specified for any interface method which expects
a response. This makes it so that callers of the method can wait
synchronously for a response. See
- [Synchronous Calls](/mojo/public/cpp/bindings#Synchronous-Calls) in the C++
- bindings documentation. Note that sync calls are not currently supported in
- other target languages.
+ [Synchronous Calls](/mojo/public/cpp/bindings/README.md#Synchronous-Calls)
+ in the C++ bindings documentation. Note that sync calls are not currently
+ supported in other target languages.
**`[Extensible]`**
: The `Extensible` attribute may be specified for any enum definition. This
@@ -362,15 +386,23 @@ interesting attributes supported today.
: The `Native` attribute may be specified for an empty struct declaration to
provide a nominal bridge between Mojo IPC and legacy `IPC::ParamTraits` or
`IPC_STRUCT_TRAITS*` macros.
- See [Using Legacy IPC Traits](/ipc#Using-Legacy-IPC-Traits) for more
- details. Note support for this attribute is strictly limited to C++ bindings
- generation.
+ See [Using Legacy IPC Traits](/ipc/README.md#Using-Legacy-IPC-Traits) for
+ more details. Note support for this attribute is strictly limited to C++
+ bindings generation.
**`[MinVersion=N]`**
: The `MinVersion` attribute is used to specify the version at which a given
field, enum value, interface method, or method parameter was introduced.
See [Versioning](#Versioning) for more details.
+**`[EnableIf=value]`**
+: The `EnableIf` attribute is used to conditionally enable definitions when
+ the mojom is parsed. If the `mojom` target in the GN file does not include
+ the matching `value` in the list of `enabled_features`, the definition
+ will be disabled. This is useful for mojom definitions that only make
+ sense on one platform. Note that the `EnableIf` attribute can only be set
+ once per definition.
+
## Generated Code For Target Languages
When the bindings generator successfully processes an input Mojom file, it emits
@@ -378,9 +410,9 @@ corresponding code for each supported target language. For more details on how
Mojom concepts translate to a given target langauge, please refer to the
bindings API documentation for that language:
-* [C++ Bindings](/mojo/public/cpp/bindings)
-* [JavaScript Bindings](/mojo/public/js)
-* [Java Bindings](/mojo/public/java/bindings)
+* [C++ Bindings](/mojo/public/cpp/bindings/README.md)
+* [JavaScript Bindings](/mojo/public/js/README.md)
+* [Java Bindings](/mojo/public/java/bindings/README.md)
## Message Validation
@@ -392,9 +424,11 @@ is added.
If a message fails validation, it is never dispatched. Instead a **connection
error** is raised on the binding object (see
-[C++ Connection Errors](/mojo/public/cpp/bindings#Connection-Errors),
-[Java Connection Errors](/mojo/public/java/bindings#Connection-Errors), or
-[JavaScript Connection Errors](/mojo/public/js#Connection-Errors) for details.)
+[C++ Connection Errors](/mojo/public/cpp/bindings/README.md#Connection-Errors),
+[Java Connection Errors](/mojo/public/java/bindings/README.md#Connection-Errors),
+or
+[JavaScript Connection Errors](/mojo/public/js/README.md#Connection-Errors) for
+details.)
Some baseline level of validation is done automatically for primitive Mojom
types.
@@ -443,9 +477,9 @@ manually encode their own bindings messages.
It's also possible for developers to define custom validation logic for specific
Mojom struct types by exploiting the
-[type mapping](/mojo/public/cpp/bindings#Type-Mapping) system for C++ bindings.
-Messages rejected by custom validation logic trigger the same validation failure
-behavior as the built-in type validation routines.
+[type mapping](/mojo/public/cpp/bindings/README.md#Type-Mapping) system for C++
+bindings. Messages rejected by custom validation logic trigger the same
+validation failure behavior as the built-in type validation routines.
## Associated Interfaces
@@ -461,7 +495,7 @@ more other interfaces, as they allow interfaces to share a single pipe.
Currenly associated interfaces are only supported in generated C++ bindings.
See the documentation for
-[C++ Associated Interfaces](/mojo/public/cpp/bindings#Associated-Interfaces).
+[C++ Associated Interfaces](/mojo/public/cpp/bindings/README.md#Associated-Interfaces).
## Versioning
@@ -598,8 +632,9 @@ assert the remote version from a client handle (*e.g.*, an
`InterfacePtr<T>` in C++ bindings.)
See
-[C++ Versioning Considerations](/mojo/public/cpp/bindings#Versioning-Considerations)
-and [Java Versioning Considerations](/mojo/public/java/bindings#Versioning-Considerations)
+[C++ Versioning Considerations](/mojo/public/cpp/bindings/README.md#Versioning-Considerations)
+and
+[Java Versioning Considerations](/mojo/public/java/bindings/README.md#Versioning-Considerations)
### Versioned Enums
@@ -637,7 +672,7 @@ is strictly for documentation purposes. It has no impact on the generated code.
With extensible enums, bound interface implementations may receive unknown enum
values and will need to deal with them gracefully. See
-[C++ Versioning Considerations](/mojo/public/cpp/bindings#Versioning-Considerations)
+[C++ Versioning Considerations](/mojo/public/cpp/bindings/README.md#Versioning-Considerations)
for details.
## Grammar Reference
diff --git a/mojo/public/tools/bindings/blink_bindings_configuration.gni b/mojo/public/tools/bindings/blink_bindings_configuration.gni
index bb0fc432a3..990d1337de 100644
--- a/mojo/public/tools/bindings/blink_bindings_configuration.gni
+++ b/mojo/public/tools/bindings/blink_bindings_configuration.gni
@@ -7,10 +7,10 @@ variant = "blink"
for_blink = true
_typemap_imports = [
+ "//device/gamepad/public/cpp/typemaps.gni",
"//mojo/public/cpp/bindings/tests/blink_typemaps.gni",
- "//third_party/WebKit/Source/platform/mojo/blink_typemaps.gni",
- "//third_party/WebKit/public/blink_typemaps.gni",
- "//third_party/WebKit/public/public_typemaps.gni",
+ "//third_party/blink/renderer/platform/mojo/blink_typemaps.gni",
+ "//third_party/blink/public/blink_typemaps.gni",
]
_typemaps = []
@@ -24,10 +24,13 @@ foreach(typemap_import, _typemap_imports) {
typemaps = []
foreach(typemap, _typemaps) {
- typemaps += [ {
- filename = typemap
- config = read_file(typemap, "scope")
- } ]
+ typemaps += [
+ {
+ filename = typemap
+ config = read_file(typemap, "scope")
+ },
+ ]
}
blacklist = []
+component_macro_suffix = "_BLINK"
diff --git a/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/mojo/public/tools/bindings/chromium_bindings_configuration.gni
index ca36723fb0..1893db1ac9 100644
--- a/mojo/public/tools/bindings/chromium_bindings_configuration.gni
+++ b/mojo/public/tools/bindings/chromium_bindings_configuration.gni
@@ -4,51 +4,63 @@
_typemap_imports = [
"//ash/public/interfaces/typemaps.gni",
- "//cc/ipc/typemaps.gni",
- "//chrome/browser/media/router/mojo/typemaps.gni",
"//chrome/common/extensions/typemaps.gni",
"//chrome/common/importer/typemaps.gni",
+ "//chrome/common/media_router/mojo/typemaps.gni",
"//chrome/typemaps.gni",
+ "//chromecast/common/mojom/typemaps.gni",
+ "//chromeos/typemaps.gni",
+ "//chromeos/services/device_sync/public/mojom/typemaps.gni",
+ "//chromeos/services/secure_channel/public/mojom/typemaps.gni",
"//components/arc/common/typemaps.gni",
"//components/metrics/public/cpp/typemaps.gni",
+ "//components/sync/mojo/typemaps.gni",
"//components/typemaps.gni",
"//content/common/bluetooth/typemaps.gni",
"//content/common/indexed_db/typemaps.gni",
"//content/common/presentation/typemaps.gni",
"//content/common/typemaps.gni",
"//content/public/common/typemaps.gni",
- "//device/bluetooth/public/interfaces/typemaps.gni",
- "//device/gamepad/public/interfaces/typemaps.gni",
- "//device/generic_sensor/public/interfaces/typemaps.gni",
- "//device/usb/public/interfaces/typemaps.gni",
- "//extensions/common/typemaps.gni",
+ "//device/bluetooth/public/mojom/typemaps.gni",
+ "//device/bluetooth/public/mojom/test/typemaps.gni",
+ "//device/gamepad/public/cpp/typemaps.gni",
"//gpu/ipc/common/typemaps.gni",
- "//media/capture/mojo/typemaps.gni",
+ "//ipc/typemaps.gni",
+ "//media/capture/mojom/typemaps.gni",
"//media/mojo/interfaces/typemaps.gni",
- "//mojo/common/typemaps.gni",
+ "//mojo/public/cpp/base/typemaps.gni",
"//mojo/public/cpp/bindings/tests/chromium_typemaps.gni",
"//net/interfaces/typemaps.gni",
+ "//sandbox/mac/mojom/typemaps.gni",
+ "//services/audio/public/cpp/typemaps.gni",
+ "//services/device/public/mojom/typemaps.gni",
+ "//services/identity/public/cpp/typemaps.gni",
+ "//services/network/public/cpp/typemaps.gni",
"//services/preferences/public/cpp/typemaps.gni",
+ "//services/proxy_resolver/public/cpp/typemaps.gni",
"//services/resource_coordinator/public/cpp/typemaps.gni",
"//services/service_manager/public/cpp/typemaps.gni",
- "//services/ui/gpu/interfaces/typemaps.gni",
"//services/ui/public/interfaces/cursor/typemaps.gni",
"//services/ui/public/interfaces/ime/typemaps.gni",
- "//services/video_capture/public/interfaces/typemaps.gni",
+ "//services/viz/privileged/cpp/typemaps.gni",
+ "//services/viz/privileged/interfaces/compositing/typemaps.gni",
+ "//services/viz/public/cpp/compositing/typemaps.gni",
+ "//services/viz/public/cpp/hit_test/typemaps.gni",
"//skia/public/interfaces/typemaps.gni",
- "//third_party/WebKit/public/public_typemaps.gni",
+ "//third_party/blink/common/typemaps.gni",
+ "//third_party/blink/public/public_typemaps.gni",
+ "//ui/accessibility/mojom/typemaps.gni",
"//ui/base/mojo/typemaps.gni",
"//ui/display/mojo/typemaps.gni",
"//ui/events/devices/mojo/typemaps.gni",
"//ui/events/mojo/typemaps.gni",
"//ui/gfx/typemaps.gni",
"//ui/latency/mojo/typemaps.gni",
- "//ui/message_center/mojo/typemaps.gni",
- "//url/mojo/typemaps.gni",
+ "//ui/ozone/public/interfaces/typemaps.gni",
+ "//ui/message_center/public/mojo/typemaps.gni",
+ "//url/mojom/typemaps.gni",
]
-_typemap_imports_mac = [ "//content/common/typemaps_mac.gni" ]
-
_typemaps = []
foreach(typemap_import, _typemap_imports) {
# Avoid reassignment error by assigning to empty scope first.
@@ -60,24 +72,12 @@ foreach(typemap_import, _typemap_imports) {
typemaps = []
foreach(typemap, _typemaps) {
- typemaps += [ {
- filename = typemap
- config = read_file(typemap, "scope")
- } ]
+ typemaps += [
+ {
+ filename = typemap
+ config = read_file(typemap, "scope")
+ },
+ ]
}
-_typemaps_mac = []
-foreach(typemap_import, _typemap_imports_mac) {
- _imported = {
- }
- _imported = read_file(typemap_import, "scope")
- _typemaps_mac += _imported.typemaps
-}
-
-typemaps_mac = []
-foreach(typemap, _typemaps_mac) {
- typemaps_mac += [ {
- filename = typemap
- config = read_file(typemap, "scope")
- } ]
-}
+component_macro_suffix = ""
diff --git a/mojo/public/tools/bindings/gen_data_files_list.py b/mojo/public/tools/bindings/gen_data_files_list.py
new file mode 100644
index 0000000000..8d40173fac
--- /dev/null
+++ b/mojo/public/tools/bindings/gen_data_files_list.py
@@ -0,0 +1,48 @@
+# 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.
+"""Generates a list of all files in a directory.
+
+This script takes in a directory and an output file name as input.
+It then reads the directory and creates a list of all file names
+in that directory. The list is written to the output file.
+There is also an option to pass in '-p' or '--pattern'
+which will check each file name against a regular expression
+pattern that is passed in. Only files which match the regex
+will be written to the list.
+"""
+
+import os
+import re
+import sys
+
+from cStringIO import StringIO
+from optparse import OptionParser
+
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ "pylib"))
+
+from mojom.generate.generator import WriteFile
+
+
+def main():
+ parser = OptionParser()
+ parser.add_option('-d', '--directory', help='Read files from DIRECTORY')
+ parser.add_option('-o', '--output', help='Write list to FILE')
+ parser.add_option('-p',
+ '--pattern',
+ help='Only reads files that name matches PATTERN',
+ default=".")
+ (options, _) = parser.parse_args()
+ pattern = re.compile(options.pattern)
+ files = [f for f in os.listdir(options.directory) if pattern.match(f)]
+
+ stream = StringIO()
+ for f in files:
+ print >> stream, f
+
+ WriteFile(stream.getvalue(), options.output)
+ stream.close()
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/mojo/public/tools/bindings/generate_type_mappings.py b/mojo/public/tools/bindings/generate_type_mappings.py
index 824f8045ab..ee55e99404 100755
--- a/mojo/public/tools/bindings/generate_type_mappings.py
+++ b/mojo/public/tools/bindings/generate_type_mappings.py
@@ -61,7 +61,12 @@ import argparse
import json
import os
import re
+import sys
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ "pylib"))
+
+from mojom.generate.generator import WriteFile
def ReadTypemap(path):
with open(path) as f:
@@ -106,14 +111,17 @@ def ParseTypemap(typemap):
(result[mojom_type]['typename'], native_type, mojom_type))
result[mojom_type] = {
+ 'public_headers': values['public_headers'],
+ 'traits_headers': values['traits_headers'],
'typename': native_type,
- 'non_copyable_non_movable': 'non_copyable_non_movable' in attributes,
- 'move_only': 'move_only' in attributes,
+
+ # Attributes supported for individual mappings.
'copyable_pass_by_value': 'copyable_pass_by_value' in attributes,
- 'nullable_is_same_type': 'nullable_is_same_type' in attributes,
+ 'force_serialize': 'force_serialize' in attributes,
'hashable': 'hashable' in attributes,
- 'public_headers': values['public_headers'],
- 'traits_headers': values['traits_headers'],
+ 'move_only': 'move_only' in attributes,
+ 'non_copyable_non_movable': 'non_copyable_non_movable' in attributes,
+ 'nullable_is_same_type': 'nullable_is_same_type' in attributes,
}
return result
@@ -140,8 +148,8 @@ def main():
raise IOError('Missing dependencies: %s' % ', '.join(missing))
for path in params.dependency:
typemaps.update(ReadTypemap(path))
- with open(params.output, 'w') as f:
- json.dump({'c++': typemaps}, f, indent=2)
+
+ WriteFile(json.dumps({'c++': typemaps}, indent=2), params.output)
if __name__ == '__main__':
diff --git a/mojo/public/tools/bindings/generators/__init__.py b/mojo/public/tools/bindings/generators/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/__init__.py
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl
index f0d503e5e0..d2416ab8c0 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl
@@ -12,6 +12,12 @@ enum class {{enum_name}} : int32_t {
{{field.name}},
{%- endif %}
{%- endfor %}
+{%- if enum.min_value is not none %}
+ kMinValue = {{enum.min_value}},
+{%- endif %}
+{%- if enum.max_value is not none %}
+ kMaxValue = {{enum.max_value}},
+{%- endif %}
};
inline std::ostream& operator<<(std::ostream& os, {{enum_name}} value) {
@@ -94,14 +100,14 @@ struct hash<{{enum_name}}>
{%- set deleted_value_unused = "false" if empty_value in enum|all_enum_values else "true" %}
namespace WTF {
struct {{hash_fn_name}} {
- static unsigned hash(const {{enum_name}}& value) {
- typedef base::underlying_type<{{enum_name}}>::type utype;
- return DefaultHash<utype>::Hash().hash(static_cast<utype>(value));
+ static unsigned GetHash(const {{enum_name}}& value) {
+ using utype = std::underlying_type<{{enum_name}}>::type;
+ return DefaultHash<utype>::Hash().GetHash(static_cast<utype>(value));
}
- static bool equal(const {{enum_name}}& left, const {{enum_name}}& right) {
+ static bool Equal(const {{enum_name}}& left, const {{enum_name}}& right) {
return left == right;
}
- static const bool safeToCompareToEmptyOrDeleted = true;
+ static const bool safe_to_compare_to_empty_or_deleted = true;
};
template <>
@@ -117,13 +123,13 @@ struct HashTraits<{{enum_name}}>
static_assert({{deleted_value_unused}},
"{{deleted_value}} is a reserved enum value");
static const bool hasIsEmptyValueFunction = true;
- static bool isEmptyValue(const {{enum_name}}& value) {
+ static bool IsEmptyValue(const {{enum_name}}& value) {
return value == static_cast<{{enum_name}}>({{empty_value}});
}
- static void constructDeletedValue({{enum_name}}& slot, bool) {
+ static void ConstructDeletedValue({{enum_name}}& slot, bool) {
slot = static_cast<{{enum_name}}>({{deleted_value}});
}
- static bool isDeletedValue(const {{enum_name}}& value) {
+ static bool IsDeletedValue(const {{enum_name}}& value) {
return value == static_cast<{{enum_name}}>({{deleted_value}});
}
};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl
index 7f6497475a..193d380e73 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl
@@ -63,3 +63,29 @@ class {{export_attribute}} {{interface.name}}
virtual void {{method.name}}({{interface_macros.declare_request_params("", method, use_once_callback)}}) = 0;
{%- endfor %}
};
+
+{#--- Testing interceptor #}
+class {{export_attribute}} {{interface.name}}InterceptorForTesting : public {{interface.name}} {
+ virtual {{interface.name}}* GetForwardingInterface() = 0;
+
+{%- for method in interface.methods %}
+ void {{method.name}}({{interface_macros.declare_request_params("", method, use_once_callback)}}) override;
+{%- endfor %}
+};
+
+{#--- Async wait helper for testing #}
+class {{export_attribute}} {{interface.name}}AsyncWaiter {
+ public:
+ explicit {{interface.name}}AsyncWaiter({{interface.name}}* proxy);
+ ~{{interface.name}}AsyncWaiter();
+
+{%- for method in interface.methods if method.response_parameters != None %}
+ void {{method.name}}(
+ {{interface_macros.declare_sync_method_params("", method)}});
+{%- endfor %}
+
+ private:
+ {{interface.name}}* const proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN({{interface.name}}AsyncWaiter);
+};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl
index aba18380af..72c3101c14 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl
@@ -7,12 +7,10 @@
{%- macro alloc_params(struct, params, message, description) %}
mojo::internal::SerializationContext serialization_context;
- serialization_context.handles.Swap(({{message}})->mutable_handles());
- serialization_context.associated_endpoint_handles.swap(
- *({{message}})->mutable_associated_endpoint_handles());
+ serialization_context.TakeHandlesFromMessage({{message}});
bool success = true;
{%- for param in struct.packed.packed_fields_in_ordinal_order %}
- {{param.field.kind|cpp_wrapper_type}} p_{{param.field.name}}{};
+ {{param.field.kind|cpp_wrapper_call_type}} p_{{param.field.name}}{};
{%- endfor %}
{{struct.name}}DataView input_data_view({{params}}, &serialization_context);
{{struct_macros.deserialize(struct, "input_data_view", "p_%s", "success")}}
@@ -32,24 +30,15 @@ std::move(p_{{param.name}})
{%- endfor %}
{%- endmacro %}
-{%- macro build_message(struct, input_pattern, struct_display_name,
- serialization_context) -%}
- {{struct_macros.serialize(struct, struct_display_name, input_pattern,
- "params", "builder.buffer()",
- serialization_context)}}
- ({{serialization_context}})->handles.Swap(
- builder.message()->mutable_handles());
- ({{serialization_context}})->associated_endpoint_handles.swap(
- *builder.message()->mutable_associated_endpoint_handles());
-{%- endmacro %}
-
{#--- Begin #}
-const char {{class_name}}::Name_[] = "{{namespace_as_string}}::{{class_name}}";
+const char {{class_name}}::Name_[] = "{{namespace}}.{{class_name}}";
{#--- Constants #}
{%- for constant in interface.constants %}
{%- if constant.kind|is_string_kind %}
const char {{interface.name}}::{{constant.name}}[] = {{constant|constant_value}};
+{%- else %}
+constexpr {{constant.kind|cpp_pod_type}} {{interface.name}}::{{constant.name}};
{%- endif %}
{%- endfor %}
@@ -73,7 +62,7 @@ class {{class_name}}_{{method.name}}_HandleSyncResponse
{{class_name}}_{{method.name}}_HandleSyncResponse(
bool* result
{%- for param in method.response_parameters -%}
- , {{param.kind|cpp_wrapper_type}}* out_{{param.name}}
+ , {{param.kind|cpp_wrapper_call_type}}* out_{{param.name}}
{%- endfor %})
: result_(result)
{%- for param in method.response_parameters -%}
@@ -85,27 +74,10 @@ class {{class_name}}_{{method.name}}_HandleSyncResponse
private:
bool* result_;
{%- for param in method.response_parameters %}
- {{param.kind|cpp_wrapper_type}}* out_{{param.name}}_;
+ {{param.kind|cpp_wrapper_call_type}}* out_{{param.name}}_;
{%- endfor -%}
DISALLOW_COPY_AND_ASSIGN({{class_name}}_{{method.name}}_HandleSyncResponse);
};
-bool {{class_name}}_{{method.name}}_HandleSyncResponse::Accept(
- mojo::Message* message) {
- internal::{{class_name}}_{{method.name}}_ResponseParams_Data* params =
- reinterpret_cast<internal::{{class_name}}_{{method.name}}_ResponseParams_Data*>(
- message->mutable_payload());
-
-{%- set desc = class_name~"::"~method.name~" response" %}
- {{alloc_params(method.response_param_struct, "params", "message", desc)}}
-
-{%- for param in method.response_parameters %}
- *out_{{param.name}}_ = std::move(p_{{param.name}});
-{%- endfor %}
- mojo::internal::SyncMessageResponseSetup::SetCurrentSyncResponseMessage(
- message);
- *result_ = true;
- return true;
-}
{%- endif %}
class {{class_name}}_{{method.name}}_ForwardToCallback
@@ -124,20 +96,6 @@ class {{class_name}}_{{method.name}}_ForwardToCallback
{{class_name}}::{{method.name}}Callback callback_;
DISALLOW_COPY_AND_ASSIGN({{class_name}}_{{method.name}}_ForwardToCallback);
};
-bool {{class_name}}_{{method.name}}_ForwardToCallback::Accept(
- mojo::Message* message) {
- internal::{{class_name}}_{{method.name}}_ResponseParams_Data* params =
- reinterpret_cast<internal::{{class_name}}_{{method.name}}_ResponseParams_Data*>(
- message->mutable_payload());
-
-{%- set desc = class_name~"::"~method.name~" response" %}
- {{alloc_params(method.response_param_struct, "params", "message", desc)}}
- if (!callback_.is_null()) {
- mojo::internal::MessageDispatchContext context(message);
- std::move(callback_).Run({{pass_params(method.response_parameters)}});
- }
- return true;
-}
{%- endif %}
{%- endfor %}
@@ -153,62 +111,94 @@ bool {{class_name}}_{{method.name}}_ForwardToCallback::Accept(
{%- set params_struct = method.param_struct %}
{%- set params_description =
"%s.%s request"|format(interface.name, method.name) %}
+{%- set message_typename = "%s_%s_Message"|format(proxy_name, method.name) %}
+
+{%- if method|method_supports_lazy_serialization %}
+{{interface_macros.define_message_type(
+ interface, message_typename, message_name, False, method, method.parameters,
+ params_struct, params_description, use_once_callback)}}
+{%- endif %}
+
{%- if method.sync %}
bool {{proxy_name}}::{{method.name}}(
{{interface_macros.declare_sync_method_params("param_", method)}}) {
- mojo::internal::SerializationContext serialization_context;
- {{struct_macros.get_serialized_size(params_struct, "param_%s",
- "&serialization_context")}}
-
- mojo::internal::MessageBuilder builder(
- {{message_name}},
- mojo::Message::kFlagIsSync | mojo::Message::kFlagExpectsResponse,
- size, serialization_context.associated_endpoint_count);
+#if BUILDFLAG(MOJO_TRACE_ENABLED)
+ TRACE_EVENT0("mojom", "{{namespace_as_string}}::{{class_name}}::{{method.name}}");
+#endif
+ const bool kExpectsResponse = true;
+ const bool kIsSync = true;
+{%- if method|method_supports_lazy_serialization %}
+ const bool kSerialize = receiver_->PrefersSerializedMessages();
+ auto message = {{message_typename}}::Build(
+ kSerialize, kExpectsResponse, kIsSync
+{%- for param in method.parameters -%}
+ , std::move(param_{{param.name}})
+{%- endfor %});
+{%- else %}
+ {{interface_macros.build_message_flags(False, "kIsSync", "kExpectsResponse",
+ "kFlags")}}
+ {{interface_macros.build_serialized_message(
+ message_name, "param_%s", params_struct, params_description, "kFlags",
+ "message")}}
+{%- endif %}
- {{build_message(params_struct, "param_%s", params_description,
- "&serialization_context")}}
+#if defined(ENABLE_IPC_FUZZER)
+ message.set_interface_name({{class_name}}::Name_);
+ message.set_method_name("{{method.name}}");
+#endif
bool result = false;
std::unique_ptr<mojo::MessageReceiver> responder(
new {{class_name}}_{{method.name}}_HandleSyncResponse(
&result
{%- for param in method.response_parameters -%}
- , param_{{param.name}}
+ , out_param_{{param.name}}
{%- endfor %}));
- ignore_result(receiver_->AcceptWithResponder(builder.message(),
- std::move(responder)));
+ ignore_result(receiver_->AcceptWithResponder(&message, std::move(responder)));
return result;
}
{%- endif %}
void {{proxy_name}}::{{method.name}}(
{{interface_macros.declare_request_params("in_", method, use_once_callback)}}) {
- mojo::internal::SerializationContext serialization_context;
- {{struct_macros.get_serialized_size(params_struct, "in_%s",
- "&serialization_context")}}
-
+#if BUILDFLAG(MOJO_TRACE_ENABLED)
+ TRACE_EVENT0("mojom", "{{namespace_as_string}}::{{class_name}}::{{method.name}}");
+#endif
{%- if method.response_parameters != None %}
- constexpr uint32_t kFlags = mojo::Message::kFlagExpectsResponse;
+ const bool kExpectsResponse = true;
{%- else %}
- constexpr uint32_t kFlags = 0;
+ const bool kExpectsResponse = false;
{%- endif %}
- mojo::internal::MessageBuilder builder(
- {{message_name}}, kFlags, size,
- serialization_context.associated_endpoint_count);
+ const bool kIsSync = false;
+{%- if method|method_supports_lazy_serialization %}
+ const bool kSerialize = receiver_->PrefersSerializedMessages();
+ auto message = {{message_typename}}::Build(
+ kSerialize, kExpectsResponse, kIsSync
+{%- for param in method.parameters -%}
+ , std::move(in_{{param.name}})
+{%- endfor %});
+{%- else %}
+ {{interface_macros.build_message_flags(False, "kIsSync", "kExpectsResponse",
+ "kFlags")}}
+ {{interface_macros.build_serialized_message(
+ message_name, "in_%s", params_struct, params_description, "kFlags",
+ "message")}}
+{%- endif %}
- {{build_message(params_struct, "in_%s", params_description,
- "&serialization_context")}}
+#if defined(ENABLE_IPC_FUZZER)
+ message.set_interface_name({{class_name}}::Name_);
+ message.set_method_name("{{method.name}}");
+#endif
{%- if method.response_parameters != None %}
std::unique_ptr<mojo::MessageReceiver> responder(
new {{class_name}}_{{method.name}}_ForwardToCallback(
std::move(callback)));
- ignore_result(receiver_->AcceptWithResponder(builder.message(),
- std::move(responder)));
+ ignore_result(receiver_->AcceptWithResponder(&message, std::move(responder)));
{%- else %}
// This return value may be ignored as false implies the Connector has
// encountered an error, which will be visible through other means.
- ignore_result(receiver_->Accept(builder.message()));
+ ignore_result(receiver_->Accept(&message));
{%- endif %}
}
{%- endfor %}
@@ -221,6 +211,8 @@ void {{proxy_name}}::{{method.name}}(
{%- set response_params_struct = method.response_param_struct %}
{%- set params_description =
"%s.%s response"|format(interface.name, method.name) %}
+{%- set response_message_typename =
+ "%s_%s_Response_Message"|format(interface.name, method.name) %}
class {{class_name}}_{{method.name}}_ProxyToResponder {
public:
static {{class_name}}::{{method.name}}Callback CreateCallback(
@@ -230,17 +222,23 @@ class {{class_name}}_{{method.name}}_ProxyToResponder {
std::unique_ptr<{{class_name}}_{{method.name}}_ProxyToResponder> proxy(
new {{class_name}}_{{method.name}}_ProxyToResponder(
request_id, is_sync, std::move(responder)));
+{%- if use_once_callback %}
+ return base::BindOnce(&{{class_name}}_{{method.name}}_ProxyToResponder::Run,
+ std::move(proxy));
+{%- else %}
return base::Bind(&{{class_name}}_{{method.name}}_ProxyToResponder::Run,
base::Passed(&proxy));
+{%- endif %}
}
~{{class_name}}_{{method.name}}_ProxyToResponder() {
#if DCHECK_IS_ON()
if (responder_) {
- // Is the Service destroying the callback without running it
- // and without first closing the pipe?
- responder_->DCheckInvalid("The callback passed to "
- "{{class_name}}::{{method.name}}() was never run.");
+ // If we're being destroyed without being run, we want to ensure the
+ // binding endpoint has been closed. This checks for that asynchronously.
+ // We pass a bound generated callback to handle the response so that any
+ // resulting DCHECK stack will have useful interface type information.
+ responder_->IsConnectedAsync(base::BindOnce(&OnIsConnectedComplete));
}
#endif
// If the Callback was dropped then deleting the responder will close
@@ -258,9 +256,18 @@ class {{class_name}}_{{method.name}}_ProxyToResponder {
responder_(std::move(responder)) {
}
+#if DCHECK_IS_ON()
+ static void OnIsConnectedComplete(bool connected) {
+ DCHECK(!connected)
+ << "{{class_name}}::{{method.name}}Callback was destroyed without "
+ << "first either being run or its corresponding binding being closed. "
+ << "It is an error to drop response callbacks which still correspond "
+ << "to an open interface pipe.";
+ }
+#endif
+
void Run(
- {{interface_macros.declare_responder_params(
- "in_", method.response_parameters, for_blink)}});
+ {{interface_macros.declare_params("in_", method.response_parameters)}});
uint64_t request_id_;
bool is_sync_;
@@ -269,29 +276,128 @@ class {{class_name}}_{{method.name}}_ProxyToResponder {
DISALLOW_COPY_AND_ASSIGN({{class_name}}_{{method.name}}_ProxyToResponder);
};
+{%- if method|method_supports_lazy_serialization %}
+{{interface_macros.define_message_type(
+ interface, response_message_typename, message_name, True, method,
+ method.response_parameters, response_params_struct, params_description,
+ use_once_callback)}}
+{%- endif %}
+
+bool {{class_name}}_{{method.name}}_ForwardToCallback::Accept(
+ mojo::Message* message) {
+#if BUILDFLAG(MOJO_TRACE_ENABLED)
+ TRACE_EVENT1("mojom", "{{namespace_as_string}}::{{class_name}}::{{method.name}}Callback",
+ "message", message->name());
+#endif
+ mojo::internal::MessageDispatchContext dispatch_context(message);
+{%- if method|method_supports_lazy_serialization %}
+ if (!message->is_serialized()) {
+ auto context =
+ message->TakeUnserializedContext<{{response_message_typename}}>();
+ if (!context) {
+ // The Message was not of the expected type. It may be a valid message
+ // which was build using a different variant of these bindings. Force
+ // serialization before dispatch in this case.
+ message->SerializeIfNecessary();
+ } else {
+ if (!callback_.is_null())
+ context->Dispatch(&callback_);
+ return true;
+ }
+ }
+{%- endif %}
+
+ DCHECK(message->is_serialized());
+ internal::{{class_name}}_{{method.name}}_ResponseParams_Data* params =
+ reinterpret_cast<
+ internal::{{class_name}}_{{method.name}}_ResponseParams_Data*>(
+ message->mutable_payload());
+
+{%- set desc = class_name~"::"~method.name~" response" %}
+ {{alloc_params(method.response_param_struct, "params", "message", desc)}}
+ if (!callback_.is_null())
+ std::move(callback_).Run({{pass_params(method.response_parameters)}});
+ return true;
+}
+
void {{class_name}}_{{method.name}}_ProxyToResponder::Run(
- {{interface_macros.declare_responder_params(
- "in_", method.response_parameters, for_blink)}}) {
- mojo::internal::SerializationContext serialization_context;
- {{struct_macros.get_serialized_size(response_params_struct, "in_%s",
- "&serialization_context")}}
-
- uint32_t flags = (is_sync_ ? mojo::Message::kFlagIsSync : 0) |
- mojo::Message::kFlagIsResponse;
- mojo::internal::MessageBuilder builder(
- {{message_name}}, flags, size,
- serialization_context.associated_endpoint_count);
- builder.message()->set_request_id(request_id_);
-
- {{build_message(response_params_struct, "in_%s", params_description,
- "&serialization_context")}}
- ignore_result(responder_->Accept(builder.message()));
+ {{interface_macros.declare_params("in_", method.response_parameters)}}) {
+{%- if method|method_supports_lazy_serialization %}
+ const bool kSerialize = responder_->PrefersSerializedMessages();
+ auto message = {{response_message_typename}}::Build(kSerialize, is_sync_
+{%- for param in method.response_parameters -%}
+ , std::move(in_{{param.name}})
+{%- endfor %});
+{%- else %}
+ {{interface_macros.build_message_flags(True, "is_sync_", "false", "kFlags")}}
+ {{interface_macros.build_serialized_message(
+ message_name, "in_%s", response_params_struct,
+ response_params_description, "kFlags", "message")}}
+{%- endif %}
+
+#if BUILDFLAG(MOJO_TRACE_ENABLED)
+ TRACE_EVENT1("mojom", "(Impl){{namespace_as_string}}::{{class_name}}::{{method.name}}Callback",
+ "message", message.name());
+#endif
+
+#if defined(ENABLE_IPC_FUZZER)
+ message.set_interface_name({{class_name}}::Name_);
+ message.set_method_name("{{method.name}}");
+#endif
+
+ message.set_request_id(request_id_);
+ ignore_result(responder_->Accept(&message));
// TODO(darin): Accept() returning false indicates a malformed message, and
// that may be good reason to close the connection. However, we don't have a
// way to do that from here. We should add a way.
responder_ = nullptr;
}
{%- endif -%}
+
+{%- if method.sync %}
+bool {{class_name}}_{{method.name}}_HandleSyncResponse::Accept(
+ mojo::Message* message) {
+{%- if method|method_supports_lazy_serialization %}
+ if (!message->is_serialized()) {
+ auto context =
+ message->TakeUnserializedContext<{{response_message_typename}}>();
+ if (!context) {
+ // The Message was not of the expected type. It may be a valid message
+ // which was built using a different variant of these bindings. Force
+ // serialization before dispatch in this case.
+ message->SerializeIfNecessary();
+ } else {
+ context->HandleSyncResponse(
+{%- for param in method.response_parameters %}
+ out_{{param.name}}_
+{%- if not loop.last -%}, {% endif -%}
+{%- endfor %});
+ *result_ = true;
+ mojo::internal::SyncMessageResponseSetup::SetCurrentSyncResponseMessage(
+ message);
+ return true;
+ }
+ }
+{%- endif %}
+
+ DCHECK(message->is_serialized());
+ internal::{{class_name}}_{{method.name}}_ResponseParams_Data* params =
+ reinterpret_cast<internal::{{class_name}}_{{method.name}}_ResponseParams_Data*>(
+ message->mutable_payload());
+
+{%- set desc = class_name~"::"~method.name~" response" %}
+ {{alloc_params(method.response_param_struct, "params", "message", desc)}}
+
+{%- for param in method.response_parameters %}
+ *out_{{param.name}}_ = std::move(p_{{param.name}});
+{%- endfor %}
+ mojo::internal::SyncMessageResponseSetup::SetCurrentSyncResponseMessage(
+ message);
+ *result_ = true;
+ return true;
+}
+{%- endif %}
+
{%- endfor %}
{#--- StubDispatch definition #}
@@ -304,7 +410,29 @@ bool {{class_name}}StubDispatch::Accept(
switch (message->header()->name) {
{%- for method in interface.methods %}
case internal::k{{class_name}}_{{method.name}}_Name: {
+#if BUILDFLAG(MOJO_TRACE_ENABLED)
+ TRACE_EVENT1("mojom", "(Impl){{namespace_as_string}}::{{class_name}}::{{method.name}}",
+ "message", message->name());
+#endif
{%- if method.response_parameters == None %}
+ mojo::internal::MessageDispatchContext context(message);
+{%- if method|method_supports_lazy_serialization %}
+ if (!message->is_serialized()) {
+ auto context = message->TakeUnserializedContext<
+ {{proxy_name}}_{{method.name}}_Message>();
+ if (!context) {
+ // The Message was not of the expected type. It may be a valid message
+ // which was serialized using a different variant of these bindings.
+ // Force serialization before dispatch in this case.
+ message->SerializeIfNecessary();
+ } else {
+ context->Dispatch(impl);
+ return true;
+ }
+ }
+{%- endif %}
+
+ DCHECK(message->is_serialized());
internal::{{class_name}}_{{method.name}}_Params_Data* params =
reinterpret_cast<internal::{{class_name}}_{{method.name}}_Params_Data*>(
message->mutable_payload());
@@ -314,8 +442,6 @@ bool {{class_name}}StubDispatch::Accept(
indent(4)}}
// A null |impl| means no implementation was bound.
assert(impl);
- TRACE_EVENT0("mojom", "{{class_name}}::{{method.name}}");
- mojo::internal::MessageDispatchContext context(message);
impl->{{method.name}}({{pass_params(method.parameters)}});
return true;
{%- else %}
@@ -337,10 +463,37 @@ bool {{class_name}}StubDispatch::AcceptWithResponder(
switch (message->header()->name) {
{%- for method in interface.methods %}
case internal::k{{class_name}}_{{method.name}}_Name: {
+#if BUILDFLAG(MOJO_TRACE_ENABLED)
+ TRACE_EVENT1("mojom", "(Impl){{namespace_as_string}}::{{class_name}}::{{method.name}}",
+ "message", message->name());
+#endif
{%- if method.response_parameters != None %}
+ mojo::internal::MessageDispatchContext context(message);
+{%- if method|method_supports_lazy_serialization %}
+ if (!message->is_serialized()) {
+ auto context = message->TakeUnserializedContext<
+ {{proxy_name}}_{{method.name}}_Message>();
+ if (!context) {
+ // The Message was not of the expected type. It may be a valid message
+ // which was built using a different variant of these bindings. Force
+ // serialization before dispatch in this case.
+ message->SerializeIfNecessary();
+ } else {
+ {{class_name}}::{{method.name}}Callback callback =
+ {{class_name}}_{{method.name}}_ProxyToResponder::CreateCallback(
+ message->request_id(),
+ message->has_flag(mojo::Message::kFlagIsSync),
+ std::move(responder));
+ context->Dispatch(impl, std::move(callback));
+ return true;
+ }
+ }
+{%- endif %}
+
internal::{{class_name}}_{{method.name}}_Params_Data* params =
- reinterpret_cast<internal::{{class_name}}_{{method.name}}_Params_Data*>(
- message->mutable_payload());
+ reinterpret_cast<
+ internal::{{class_name}}_{{method.name}}_Params_Data*>(
+ message->mutable_payload());
{%- set desc = class_name~"::"~method.name %}
{{alloc_params(method.param_struct, "params", "message", desc)|
@@ -352,8 +505,6 @@ bool {{class_name}}StubDispatch::AcceptWithResponder(
std::move(responder));
// A null |impl| means no implementation was bound.
assert(impl);
- TRACE_EVENT0("mojom", "{{class_name}}::{{method.name}}");
- mojo::internal::MessageDispatchContext context(message);
impl->{{method.name}}(
{%- if method.parameters -%}{{pass_params(method.parameters)}}, {% endif -%}std::move(callback));
return true;
@@ -370,8 +521,10 @@ bool {{class_name}}StubDispatch::AcceptWithResponder(
{#--- Request validator definitions #}
bool {{class_name}}RequestValidator::Accept(mojo::Message* message) {
- if (mojo::internal::ControlMessageHandler::IsControlMessage(message))
+ if (!message->is_serialized() ||
+ mojo::internal::ControlMessageHandler::IsControlMessage(message)) {
return true;
+ }
mojo::internal::ValidationContext validation_context(
message->payload(), message->payload_num_bytes(),
@@ -414,8 +567,10 @@ bool {{class_name}}RequestValidator::Accept(mojo::Message* message) {
{#--- Response validator definitions #}
{% if interface|has_callbacks %}
bool {{class_name}}ResponseValidator::Accept(mojo::Message* message) {
- if (mojo::internal::ControlMessageHandler::IsControlMessage(message))
+ if (!message->is_serialized() ||
+ mojo::internal::ControlMessageHandler::IsControlMessage(message)) {
return true;
+ }
mojo::internal::ValidationContext validation_context(
message->payload(), message->payload_num_bytes(),
@@ -446,3 +601,57 @@ bool {{class_name}}ResponseValidator::Accept(mojo::Message* message) {
return false;
}
{%- endif -%}
+
+{#--- Testing interceptor #}
+{%- for method in interface.methods %}
+void {{interface.name}}InterceptorForTesting::{{method.name}}({{interface_macros.declare_request_params("", method, use_once_callback)}}) {
+ GetForwardingInterface()->{{method.name}}(
+ {%- for param in method.parameters -%}
+ std::move({{param.name}}){%- if not loop.last %}, {% endif %}
+ {%- endfor %}
+ {%- if method.response_parameters != None -%}
+ {%- if method.parameters %}, {% endif -%}
+ std::move(callback)
+ {%- endif -%}
+ );
+}
+{%- endfor %}
+
+{#--- Async wait helper for testing #}
+{{interface.name}}AsyncWaiter::{{interface.name}}AsyncWaiter(
+ {{interface.name}}* proxy) : proxy_(proxy) {}
+
+{{interface.name}}AsyncWaiter::~{{interface.name}}AsyncWaiter() = default;
+
+{% for method in interface.methods if method.response_parameters != None -%}
+void {{interface.name}}AsyncWaiter::{{method.name}}(
+ {{interface_macros.declare_sync_method_params("", method)}}) {
+ base::RunLoop loop;
+ proxy_->{{method.name}}(
+{%- for param in method.parameters -%}
+ std::move({{param.name}}),
+{%- endfor %}
+{%- if use_once_callback %}
+ base::BindOnce(
+{%- else %}
+ base::Bind(
+{%- endif %}
+ [](base::RunLoop* loop
+{%- for param in method.response_parameters -%},
+ {{param.kind|cpp_wrapper_call_type}}* out_{{param.name}}
+{% endfor -%}
+{%- for param in method.response_parameters -%},
+ {{param.kind|cpp_wrapper_param_type}} {{param.name}}
+{%- endfor %}) {
+{%- for param in method.response_parameters -%}
+ *out_{{param.name}} = std::move({{param.name}});
+{%- endfor %}
+ loop->Quit();
+ },
+ &loop
+{%- for param in method.response_parameters -%},
+ out_{{param.name}}
+{%- endfor %}));
+ loop.Run();
+}
+{% endfor %}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl
index 8649273633..052691dea0 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl
@@ -1,11 +1,6 @@
-{%- macro declare_params(prefix, parameters) %}
-{%- for param in parameters -%}
-{{param.kind|cpp_wrapper_param_type}} {{prefix}}{{param.name}}
-{%- if not loop.last %}, {% endif %}
-{%- endfor %}
-{%- endmacro %}
+{%- import "struct_macros.tmpl" as struct_macros %}
-{%- macro declare_responder_params(prefix, parameters, for_blink) %}
+{%- macro declare_params(prefix, parameters) %}
{%- for param in parameters -%}
{{param.kind|cpp_wrapper_param_type}} {{prefix}}{{param.name}}
{%- if not loop.last %}, {% endif %}
@@ -42,8 +37,165 @@ const {{method.name}}Callback& callback
{%- if method.response_parameters %}
{%- if method.parameters %}, {% endif %}
{%- for param in method.response_parameters -%}
-{{param.kind|cpp_wrapper_type}}* {{prefix}}{{param.name}}
+{{param.kind|cpp_wrapper_call_type}}* out_{{prefix}}{{param.name}}
{%- if not loop.last %}, {% endif %}
{%- endfor %}
{%- endif -%}
{%- endmacro -%}
+
+{%- macro build_message_flags(is_response, is_sync_text, expects_response_text,
+ flags_name) %}
+{%- if is_response %}
+ const uint32_t kFlags = mojo::Message::kFlagIsResponse |
+ (({{is_sync_text}}) ? mojo::Message::kFlagIsSync : 0);
+{%- else %}
+ const uint32_t kFlags =
+ (({{expects_response_text}}) ? mojo::Message::kFlagExpectsResponse : 0) |
+ (({{is_sync_text}}) ? mojo::Message::kFlagIsSync : 0);
+{%- endif %}
+{%- endmacro %}
+
+{%- macro build_serialized_message(message_name, param_name_prefix,
+ params_struct, params_description,
+ flags_text, message_object_name) %}
+ mojo::Message {{message_object_name}}(
+ {{message_name}}, {{flags_text}}, 0, 0, nullptr);
+ auto* buffer = {{message_object_name}}.payload_buffer();
+ {{params_struct|get_qualified_name_for_kind(internal=True)}}::BufferWriter
+ params;
+ mojo::internal::SerializationContext serialization_context;
+ {{struct_macros.serialize(params_struct, params_description,
+ param_name_prefix, "params", "buffer",
+ "&serialization_context")}}
+ {{message_object_name}}.AttachHandlesFromSerializationContext(
+ &serialization_context);
+{%- endmacro %}
+
+{%- macro define_message_type(interface, message_typename, message_name,
+ is_response, method, parameters, params_struct,
+ params_description, use_once_callback) -%}
+class {{message_typename}}
+ : public mojo::internal::UnserializedMessageContext {
+ public:
+ static const mojo::internal::UnserializedMessageContext::Tag kMessageTag;
+
+ explicit {{message_typename}}(
+ uint32_t message_flags
+{%- for param in parameters %}
+ , {{param.kind|cpp_wrapper_param_type}} param_{{param.name}}
+{%- endfor %}
+ )
+ : mojo::internal::UnserializedMessageContext(
+ &kMessageTag,
+ {{message_name}},
+ message_flags)
+{%- for param in parameters -%}
+{%- if param.kind|is_interface_kind %}
+ , param_{{param.name}}_(param_{{param.name}}.PassInterface())
+{%- else %}
+ , param_{{param.name}}_(std::move(param_{{param.name}}))
+{%- endif %}
+{%- endfor -%} {}
+ ~{{message_typename}}() override = default;
+
+ static mojo::Message Build(
+ bool serialize,
+{%- if not is_response %}
+ bool expects_response,
+{%- endif %}
+ bool is_sync
+{%- if parameters -%}
+ ,
+ {{declare_params("param_", parameters)}}
+{%- endif %}) {
+
+ {{build_message_flags(is_response, "is_sync", "expects_response",
+ "kFlags")}}
+
+ if (!serialize) {
+ return mojo::Message(std::make_unique<{{message_typename}}>(
+ kFlags
+{%- for param in parameters %}
+ , std::move(param_{{param.name}})
+{%- endfor %}
+ ));
+ }
+
+ DCHECK(serialize);
+ {{build_serialized_message(message_name, "param_%s", params_struct,
+ params_description, "kFlags", "message")}}
+ return message;
+ }
+
+{% if not is_response %}
+ void Dispatch({{interface.name}}* impl
+{%- if method.response_parameters != None -%}
+{%- if use_once_callback -%}
+ , {{interface.name}}::{{method.name}}Callback callback
+{%- else -%}
+ , const {{interface.name}}::{{method.name}}Callback& callback
+{%- endif -%}
+{%- endif -%}) {
+ impl->{{method.name}}(
+{%- for param in parameters -%}
+{%- if param.kind|is_interface_kind %}
+ {{param.kind|get_name_for_kind}}Ptr(std::move(param_{{param.name}}_))
+{%- else %}
+ std::move(param_{{param.name}}_)
+{%- endif %}
+ {%- if not loop.last -%}, {%- endif %}
+{%- endfor %}
+{%- if method.response_parameters != None %}
+ {%- if parameters -%}, {% endif -%}std::move(callback)
+{%- endif -%});
+ }
+{%- else %}
+ void Dispatch({{interface.name}}::{{method.name}}Callback* callback) {
+ std::move(*callback).Run(
+{%- for param in parameters -%}
+{%- if param.kind|is_interface_kind %}
+ {{param.kind|get_name_for_kind}}Ptr(std::move(param_{{param.name}}_))
+{%- else %}
+ std::move(param_{{param.name}}_)
+{%- endif %}
+ {%- if not loop.last -%}, {% endif -%}
+{%- endfor -%});
+ }
+
+{% if method.sync %}
+ void HandleSyncResponse(
+{% for param in parameters %}
+ {{param.kind|cpp_wrapper_call_type}}* out_{{param.name}}
+ {%- if not loop.last -%}, {% endif -%}
+{%- endfor -%}) {
+{% for param in parameters -%}
+{%- if param.kind|is_interface_kind %}
+ out_{{param.name}}->Bind(std::move(param_{{param.name}}_));
+{%- else %}
+ *out_{{param.name}} = std::move(param_{{param.name}}_);
+{%- endif %}
+{% endfor %}
+ }
+{%- endif -%}
+{%- endif %}
+
+ private:
+ // mojo::internal::UnserializedMessageContext:
+ void Serialize(mojo::internal::SerializationContext* serialization_context,
+ mojo::internal::Buffer* buffer) override {
+ {{params_struct|get_qualified_name_for_kind(internal=True)}}::BufferWriter
+ params;
+ {{struct_macros.serialize(params_struct, params_description, "param_%s_",
+ "params", "buffer", "serialization_context")}}
+ }
+
+{%- for param in parameters %}
+ {{param.kind|cpp_wrapper_type}} param_{{param.name}}_;
+{%- endfor %}
+
+ DISALLOW_COPY_AND_ASSIGN({{message_typename}});
+};
+
+const mojo::internal::UnserializedMessageContext::Tag
+{{message_typename}}::kMessageTag = {};
+{%- endmacro -%}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl
index 0a158ec3e8..33baae4dd1 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl
@@ -6,9 +6,9 @@ class {{export_attribute}} {{interface.name}}Proxy
{%- for method in interface.methods %}
{%- if method.sync %}
- bool {{method.name}}({{interface_macros.declare_sync_method_params("", method)}}) override;
+ bool {{method.name}}({{interface_macros.declare_sync_method_params("", method)}}) final;
{%- endif %}
- void {{method.name}}({{interface_macros.declare_request_params("", method, use_once_callback)}}) override;
+ void {{method.name}}({{interface_macros.declare_request_params("", method, use_once_callback)}}) final;
{%- endfor %}
private:
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl
index a00d14886d..6440519b9f 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl
@@ -1,4 +1,4 @@
-class {{export_attribute}} {{interface.name}}RequestValidator : public NON_EXPORTED_BASE(mojo::MessageReceiver) {
+class {{export_attribute}} {{interface.name}}RequestValidator : public mojo::MessageReceiver {
public:
bool Accept(mojo::Message* message) override;
};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl
index e2caa02c79..759726d997 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl
@@ -1,4 +1,4 @@
-class {{export_attribute}} {{interface.name}}ResponseValidator : public NON_EXPORTED_BASE(mojo::MessageReceiver) {
+class {{export_attribute}} {{interface.name}}ResponseValidator : public mojo::MessageReceiver {
public:
bool Accept(mojo::Message* message) override;
};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl
index 79ab46f337..7253c0a5a5 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl
@@ -10,7 +10,7 @@ class {{export_attribute}} {{interface.name}}StubDispatch {
template <typename ImplRefTraits =
mojo::RawPtrImplRefTraits<{{interface.name}}>>
class {{interface.name}}Stub
- : public NON_EXPORTED_BASE(mojo::MessageReceiverWithResponderStatus) {
+ : public mojo::MessageReceiverWithResponderStatus {
public:
using ImplPointerType = typename ImplRefTraits::PointerType;
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl
index 964b25438e..e32f43bf55 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl
@@ -9,19 +9,24 @@
#ifndef {{header_guard}}
#define {{header_guard}}
-#include <stdint.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/map_data_internal.h"
-#include "mojo/public/cpp/bindings/lib/native_enum_data.h"
-#include "mojo/public/cpp/bindings/lib/native_struct_data.h"
#include "mojo/public/cpp/bindings/lib/buffer.h"
{%- for import in imports %}
-#include "{{import.module.path}}-shared-internal.h"
+#include "{{import.path}}-shared-internal.h"
{%- endfor %}
+{%- if not disallow_native_types %}
+#include "mojo/public/cpp/bindings/lib/native_enum_data.h"
+#include "mojo/public/interfaces/bindings/native_struct.mojom-shared-internal.h"
+{%- endif %}
+
+{%- if export_header %}
+#include "{{export_header}}"
+{%- endif %}
+
namespace mojo {
namespace internal {
class ValidationContext;
@@ -36,7 +41,7 @@ namespace internal {
{#--- Internal forward declarations #}
{%- for struct in structs %}
{%- if struct|is_native_only_kind %}
-using {{struct.name}}_Data = mojo::internal::NativeStruct_Data;
+using {{struct.name}}_Data = mojo::native::internal::NativeStruct_Data;
{%- else %}
class {{struct.name}}_Data;
{%- endif %}
@@ -69,14 +74,13 @@ using {{enum|get_name_for_kind(flatten_nested_kind=True)}}_Data =
{%- for struct in structs %}
{%- if not struct|is_native_only_kind %}
{% include "struct_declaration.tmpl" %}
+{% include "struct_unserialized_message_context.tmpl" %}
{%- endif %}
{%- endfor %}
{#--- Interface parameter definitions #}
{%- for interface in interfaces %}
{%- for method in interface.methods %}
-{%- set method_name = "k%s_%s_Name"|format(interface.name, method.name) %}
-constexpr uint32_t {{method_name}} = {{method.ordinal}};
{%- set struct = method.param_struct %}
{% include "struct_declaration.tmpl" %}
{%- if method.response_parameters != None %}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-shared-message-ids.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-shared-message-ids.h.tmpl
new file mode 100644
index 0000000000..0b5563c7ad
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module-shared-message-ids.h.tmpl
@@ -0,0 +1,35 @@
+// 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.
+
+{%- set header_guard = "%s_SHARED_MESSAGE_IDS_H_"|format(
+ module.path|upper|replace("/","_")|replace(".","_")|
+ replace("-", "_")) %}
+
+#ifndef {{header_guard}}
+#define {{header_guard}}
+
+#include <stdint.h>
+
+{%- for namespace in namespaces_as_array %}
+namespace {{namespace}} {
+{%- endfor %}
+
+namespace internal {
+
+{% for interface in interfaces -%}
+{%- for method in interface.methods -%}
+{%- set method_name = "k%s_%s_Name"|format(interface.name, method.name) -%}
+{%- if method.ordinal_comment %}
+// {{method.ordinal_comment}}
+{%- endif %}
+constexpr uint32_t {{method_name}} = {{method.ordinal}};
+{%- endfor %}
+{%- endfor %}
+
+} // namespace internal
+{%- for namespace in namespaces_as_array|reverse %}
+} // namespace {{namespace}}
+{%- endfor %}
+
+#endif // {{header_guard}}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl
index 645bb692b0..6004c52138 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl
@@ -42,7 +42,6 @@ namespace internal {
{#--- Interface parameter definitions #}
{%- for interface in interfaces %}
{%- for method in interface.methods %}
-{%- set method_name = "k%s_%s_Name"|format(interface.name, method.name) %}
{%- set struct = method.param_struct %}
{% include "struct_definition.tmpl" %}
{%- if method.response_parameters != None %}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl
index dd13466de1..1ff771d242 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl
@@ -43,26 +43,38 @@ namespace {{namespace}} {
#include <utility>
#include "base/compiler_specific.h"
+#include "base/containers/flat_map.h"
#include "mojo/public/cpp/bindings/array_data_view.h"
#include "mojo/public/cpp/bindings/enum_traits.h"
#include "mojo/public/cpp/bindings/interface_data_view.h"
#include "mojo/public/cpp/bindings/lib/bindings_internal.h"
#include "mojo/public/cpp/bindings/lib/serialization.h"
#include "mojo/public/cpp/bindings/map_data_view.h"
-#include "mojo/public/cpp/bindings/native_enum.h"
-#include "mojo/public/cpp/bindings/native_struct_data_view.h"
#include "mojo/public/cpp/bindings/string_data_view.h"
#include "{{module.path}}-shared-internal.h"
{%- for import in imports %}
-#include "{{import.module.path}}-shared.h"
+#include "{{import.path}}-shared.h"
{%- endfor %}
+{% if not disallow_interfaces -%}
+#include "mojo/public/cpp/bindings/lib/interface_serialization.h"
+{%- endif %}
+
+{% if not disallow_native_types %}
+#include "mojo/public/cpp/bindings/native_enum.h"
+#include "mojo/public/cpp/bindings/lib/native_struct_serialization.h"
+{%- endif %}
+
+{%- if export_header %}
+#include "{{export_header}}"
+{%- endif %}
+
{{namespace_begin()}}
{#--- Struct Forward Declarations -#}
{%- for struct in structs %}
{%- if struct|is_native_only_kind %}
-using {{struct.name}}DataView = mojo::NativeStructDataView;
+using {{struct.name}}DataView = mojo::native::NativeStructDataView;
{%- else %}
class {{struct.name}}DataView;
{%- endif %}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl
index 2c66a85f87..16f3bafa82 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl
@@ -25,14 +25,17 @@
#include <utility>
#include "base/logging.h"
-#include "base/trace_event/trace_event.h"
-#include "mojo/public/cpp/bindings/lib/message_builder.h"
+#include "base/run_loop.h"
+#include "mojo/public/cpp/bindings/lib/message_internal.h"
#include "mojo/public/cpp/bindings/lib/serialization_util.h"
+#include "mojo/public/cpp/bindings/lib/unserialized_message_context.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/interfaces/bindings/interface_control_messages.mojom.h"
+#include "{{module.path}}-shared-message-ids.h"
+
{%- if for_blink %}
#include "mojo/public/cpp/bindings/lib/wtf_serialization.h"
{%- endif %}
@@ -60,6 +63,8 @@ const char {{constant.name}}[] = {{constant|constant_value}};
{%- for constant in struct.constants %}
{%- if constant.kind|is_string_kind %}
const char {{struct.name}}::{{constant.name}}[] = {{constant|constant_value}};
+{%- else %}
+constexpr {{constant.kind|cpp_pod_type}} {{struct.name}}::{{constant.name}};
{%- endif %}
{%- endfor %}
{%- endfor %}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl
index 804a46b6f8..1537b1c828 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl
@@ -42,29 +42,23 @@ namespace {{variant}} {
#include "base/callback.h"
#include "base/macros.h"
#include "base/optional.h"
-#include "mojo/public/cpp/bindings/associated_interface_ptr.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/mojo_buildflags.h"
+#if BUILDFLAG(MOJO_TRACE_ENABLED)
+#include "base/trace_event/trace_event.h"
+#endif
#include "mojo/public/cpp/bindings/clone_traits.h"
-#include "mojo/public/cpp/bindings/interface_ptr.h"
-#include "mojo/public/cpp/bindings/interface_request.h"
-#include "mojo/public/cpp/bindings/lib/equals_traits.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/equals_traits.h"
#include "mojo/public/cpp/bindings/lib/serialization.h"
-#include "mojo/public/cpp/bindings/lib/union_accessor.h"
-#include "mojo/public/cpp/bindings/native_struct.h"
-#include "mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h"
#include "mojo/public/cpp/bindings/struct_ptr.h"
#include "mojo/public/cpp/bindings/struct_traits.h"
-#include "mojo/public/cpp/bindings/thread_safe_interface_ptr.h"
#include "mojo/public/cpp/bindings/union_traits.h"
#include "{{module.path}}-shared.h"
{%- for import in imports %}
{%- if variant %}
-#include "{{"%s-%s.h"|format(import.module.path, variant)}}"
+#include "{{"%s-%s.h"|format(import.path, variant)}}"
{%- else %}
-#include "{{import.module.path}}.h"
+#include "{{import.path}}.h"
{%- endif %}
{%- endfor %}
{%- if not for_blink %}
@@ -73,10 +67,30 @@ namespace {{variant}} {
{%- else %}
{# hash_util.h includes template specializations that should be present for
every use of {Inlined}StructPtr. #}
+#include "mojo/public/cpp/bindings/lib/wtf_clone_equals_util.h"
#include "mojo/public/cpp/bindings/lib/wtf_hash_util.h"
-#include "third_party/WebKit/Source/wtf/HashFunctions.h"
-#include "third_party/WebKit/Source/wtf/Optional.h"
-#include "third_party/WebKit/Source/wtf/text/WTFString.h"
+#include "third_party/blink/renderer/platform/mojo/revocable_interface_ptr.h"
+#include "third_party/blink/renderer/platform/wtf/hash_functions.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+{%- endif %}
+
+{% if not disallow_interfaces -%}
+#include "mojo/public/cpp/bindings/associated_interface_ptr.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_ptr.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/lib/control_message_handler.h"
+#include "mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h"
+#include "mojo/public/cpp/bindings/thread_safe_interface_ptr.h"
+{%- if for_blink %}
+#include "third_party/blink/renderer/platform/mojo/revocable_interface_ptr.h"
+{%- endif %}
+{%- endif %}
+
+{% if not disallow_native_types %}
+#include "mojo/public/cpp/bindings/lib/native_enum_serialization.h"
+#include "mojo/public/cpp/bindings/lib/native_struct_serialization.h"
{%- endif %}
{%- for header in extra_public_headers %}
@@ -115,6 +129,9 @@ using {{enum.name}} = {{enum.name}}; // Alias for definition in the parent name
{% for interface in interfaces %}
class {{interface.name}};
using {{interface.name}}Ptr = mojo::InterfacePtr<{{interface.name}}>;
+{%- if for_blink %}
+using Revocable{{interface.name}}Ptr = ::blink::RevocableInterfacePtr<{{interface.name}}>;
+{%- endif %}
using {{interface.name}}PtrInfo = mojo::InterfacePtrInfo<{{interface.name}}>;
using ThreadSafe{{interface.name}}Ptr =
mojo::ThreadSafeInterfacePtr<{{interface.name}}>;
@@ -132,8 +149,8 @@ using {{interface.name}}AssociatedRequest =
{#--- Struct Forward Declarations -#}
{% for struct in structs %}
{%- if struct|is_native_only_kind %}
-using {{struct.name}} = mojo::NativeStruct;
-using {{struct.name}}Ptr = mojo::NativeStructPtr;
+using {{struct.name}} = mojo::native::NativeStruct;
+using {{struct.name}}Ptr = mojo::native::NativeStructPtr;
{%- else %}
class {{struct.name}};
{%- if struct|should_inline %}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl
index 96e0d614d8..1d6d82c397 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl
@@ -23,10 +23,10 @@ class {{struct.name}}DataView {
template <typename UserType>
WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) {
{%- if pf.min_version != 0 %}
- auto* pointer = data_->header_.version >= {{pf.min_version}}
+ auto* pointer = data_->header_.version >= {{pf.min_version}} && !data_->{{name}}.is_null()
? &data_->{{name}} : nullptr;
{%- else %}
- auto* pointer = &data_->{{name}};
+ auto* pointer = !data_->{{name}}.is_null() ? &data_->{{name}} : nullptr;
{%- endif %}
return mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>(
pointer, output, context_);
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl
index 156f7742c4..5f90c0d74c 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl
@@ -1,10 +1,30 @@
{%- set class_name = struct.name ~ "_Data" -%}
-class {{class_name}} {
+class {{export_attribute}} {{class_name}} {
public:
- static {{class_name}}* New(mojo::internal::Buffer* buf) {
- return new (buf->Allocate(sizeof({{class_name}}))) {{class_name}}();
- }
+ class BufferWriter {
+ public:
+ BufferWriter() = default;
+
+ void Allocate(mojo::internal::Buffer* serialization_buffer) {
+ serialization_buffer_ = serialization_buffer;
+ index_ = serialization_buffer_->Allocate(sizeof({{class_name}}));
+ new (data()) {{class_name}}();
+ }
+
+ bool is_null() const { return !serialization_buffer_; }
+ {{class_name}}* data() {
+ DCHECK(!is_null());
+ return serialization_buffer_->Get<{{class_name}}>(index_);
+ }
+ {{class_name}}* operator->() { return data(); }
+
+ private:
+ mojo::internal::Buffer* serialization_buffer_ = nullptr;
+ size_t index_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferWriter);
+ };
static bool Validate(const void* data,
mojo::internal::ValidationContext* validation_context);
@@ -38,8 +58,7 @@ class {{class_name}} {
{%- endif %}
private:
- {{class_name}}() : header_({sizeof(*this), {{struct.versions[-1].version}}}) {
- }
+ {{class_name}}();
~{{class_name}}() = delete;
};
static_assert(sizeof({{class_name}}) == {{struct.versions[-1].num_bytes}},
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl
index 60dca4010e..1638962050 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl
@@ -61,10 +61,12 @@ bool {{class_name}}::Validate(
return true;
{%- endif %}
{%- set field_expr = "object->" ~ packed_field.field.name %}
-{{validation_macros.validate_field(packed_field.field, field_expr, struct.name, true)}}
+{{validation_macros.validate_field(packed_field.field, loop.index, field_expr, struct.name, true)}}
{%- endif %}
{%- endfor %}
return true;
}
+{{class_name}}::{{class_name}}()
+ : header_({sizeof(*this), {{struct.versions[-1].version}}}) {}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl
index bb5fb9c496..7c6b9cdd1f 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl
@@ -1,46 +1,5 @@
{# TODO(yzshen): Make these templates more readable. #}
-{# Computes the serialized size for the specified struct.
- |struct| is the struct definition.
- |input_field_pattern| should be a pattern that contains one string
- placeholder, for example, "input->%s", "p_%s". The placeholder will be
- substituted with struct field names to refer to the input fields.
- |context| is the name of the serialization context.
- |input_may_be_temp| indicates whether any input may be temporary obejcts.
- We need to assign temporary objects to local variables before passing it to
- Serializer, because it is illegal to pass temporary objects as non-const
- references.
- This macro is expanded to compute seriailized size for both:
- - user-defined structs: the input is an instance of the corresponding struct
- wrapper class.
- - method parameters/response parameters: the input is a list of
- arguments.
- It declares |size| of type size_t to store the resulting size. #}
-{%- macro get_serialized_size(struct, input_field_pattern, context,
- input_may_be_temp=False) -%}
- size_t size = sizeof({{struct|get_qualified_name_for_kind(internal=True)}});
-{%- for pf in struct.packed.packed_fields_in_ordinal_order
- if pf.field.kind|is_object_kind or pf.field.kind|is_associated_kind %}
-{%- set name = pf.field.name -%}
-{%- set kind = pf.field.kind -%}
-{%- set original_input_field = input_field_pattern|format(name) %}
-{%- set input_field = "in_%s"|format(name) if input_may_be_temp
- else original_input_field %}
-{%- if input_may_be_temp %}
- decltype({{original_input_field}}) in_{{name}} = {{original_input_field}};
-{%- endif %}
-
-{%- set serializer_type = kind|unmapped_type_for_serializer %}
-{%- if kind|is_union_kind %}
- size += mojo::internal::PrepareToSerialize<{{serializer_type}}>(
- {{input_field}}, true, {{context}});
-{%- else %}
- size += mojo::internal::PrepareToSerialize<{{serializer_type}}>(
- {{input_field}}, {{context}});
-{%- endif %}
-{%- endfor %}
-{%- endmacro -%}
-
{# Serializes the specified struct.
|struct| is the struct definition.
|struct_display_name| is the display name for the struct that can be showed
@@ -48,7 +7,7 @@
|input_field_pattern| should be a pattern that contains one string
placeholder, for example, "input->%s", "p_%s". The placeholder will be
substituted with struct field names to refer to the input fields.
- |output| is the name of the output struct instance.
+ |writer| is the name of the BufferWriter to use for allocation and writing.
|buffer| is the name of the Buffer instance used.
|context| is the name of the serialization context.
|input_may_be_temp|: please see the comments of get_serialized_size.
@@ -57,11 +16,9 @@
wrapper class.
- method parameters/response parameters: the input is a list of
arguments. #}
-{%- macro serialize(struct, struct_display_name, input_field_pattern, output,
+{%- macro serialize(struct, struct_display_name, input_field_pattern, writer,
buffer, context, input_may_be_temp=False) -%}
- auto {{output}} =
- {{struct|get_qualified_name_for_kind(internal=True)}}::New({{buffer}});
- ALLOW_UNUSED_LOCAL({{output}});
+ {{writer}}.Allocate({{buffer}});
{%- for pf in struct.packed.packed_fields_in_ordinal_order %}
{%- set input_field = input_field_pattern|format(pf.field.name) %}
{%- set name = pf.field.name %}
@@ -79,36 +36,41 @@
{%- if kind|is_object_kind %}
{%- if kind|is_array_kind or kind|is_map_kind %}
- typename decltype({{output}}->{{name}})::BaseType* {{name}}_ptr;
+ typename decltype({{writer}}->{{name}})::BaseType::BufferWriter
+ {{name}}_writer;
const mojo::internal::ContainerValidateParams {{name}}_validate_params(
{{kind|get_container_validate_params_ctor_args|indent(10)}});
mojo::internal::Serialize<{{serializer_type}}>(
- {{input_field}}, {{buffer}}, &{{name}}_ptr, &{{name}}_validate_params,
+ {{input_field}}, {{buffer}}, &{{name}}_writer, &{{name}}_validate_params,
{{context}});
- {{output}}->{{name}}.Set({{name}}_ptr);
+ {{writer}}->{{name}}.Set(
+ {{name}}_writer.is_null() ? nullptr : {{name}}_writer.data());
{%- elif kind|is_union_kind %}
- auto {{name}}_ptr = &{{output}}->{{name}};
+ typename decltype({{writer}}->{{name}})::BufferWriter {{name}}_writer;
+ {{name}}_writer.AllocateInline({{buffer}}, &{{writer}}->{{name}});
mojo::internal::Serialize<{{serializer_type}}>(
- {{input_field}}, {{buffer}}, &{{name}}_ptr, true, {{context}});
+ {{input_field}}, {{buffer}}, &{{name}}_writer, true, {{context}});
{%- else %}
- typename decltype({{output}}->{{name}})::BaseType* {{name}}_ptr;
+ typename decltype({{writer}}->{{name}})::BaseType::BufferWriter
+ {{name}}_writer;
mojo::internal::Serialize<{{serializer_type}}>(
- {{input_field}}, {{buffer}}, &{{name}}_ptr, {{context}});
- {{output}}->{{name}}.Set({{name}}_ptr);
+ {{input_field}}, {{buffer}}, &{{name}}_writer, {{context}});
+ {{writer}}->{{name}}.Set(
+ {{name}}_writer.is_null() ? nullptr : {{name}}_writer.data());
{%- endif %}
{%- if not kind|is_nullable_kind %}
MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
- {{output}}->{{name}}.is_null(),
+ {{writer}}->{{name}}.is_null(),
mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
"null {{name}} in {{struct_display_name}}");
{%- endif %}
{%- elif kind|is_any_handle_or_interface_kind %}
mojo::internal::Serialize<{{serializer_type}}>(
- {{input_field}}, &{{output}}->{{name}}, {{context}});
+ {{input_field}}, &{{writer}}->{{name}}, {{context}});
{%- if not kind|is_nullable_kind %}
MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
- !mojo::internal::IsHandleOrInterfaceValid({{output}}->{{name}}),
+ !mojo::internal::IsHandleOrInterfaceValid({{writer}}->{{name}}),
{%- if kind|is_associated_kind %}
mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID,
{%- else %}
@@ -119,10 +81,10 @@
{%- elif kind|is_enum_kind %}
mojo::internal::Serialize<{{serializer_type}}>(
- {{input_field}}, &{{output}}->{{name}});
+ {{input_field}}, &{{writer}}->{{name}});
{%- else %}
- {{output}}->{{name}} = {{input_field}};
+ {{writer}}->{{name}} = {{input_field}};
{%- endif %}
{%- endfor %}
{%- endmacro -%}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl
index 835178beda..5571e8449f 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl
@@ -2,6 +2,8 @@
{%- set data_view = struct|get_qualified_name_for_kind ~ "DataView" %}
{%- set data_type = struct|get_qualified_name_for_kind(internal=True) %}
+{%- if not struct|use_custom_serializer %}
+
namespace internal {
template <typename MaybeConstUserType>
@@ -9,37 +11,17 @@ struct Serializer<{{data_view}}, MaybeConstUserType> {
using UserType = typename std::remove_const<MaybeConstUserType>::type;
using Traits = StructTraits<{{data_view}}, UserType>;
- static size_t PrepareToSerialize(MaybeConstUserType& input,
- SerializationContext* context) {
- if (CallIsNullIfExists<Traits>(input))
- return 0;
-
- void* custom_context = CustomContextHelper<Traits>::SetUp(input, context);
- ALLOW_UNUSED_LOCAL(custom_context);
-
- {{struct_macros.get_serialized_size(
- struct, "CallWithContext(Traits::%s, input, custom_context)",
- "context", True)|indent(2)}}
- return size;
- }
-
static void Serialize(MaybeConstUserType& input,
Buffer* buffer,
- {{data_type}}** output,
+ {{data_type}}::BufferWriter* output,
SerializationContext* context) {
- if (CallIsNullIfExists<Traits>(input)) {
- *output = nullptr;
+ if (CallIsNullIfExists<Traits>(input))
return;
- }
-
- void* custom_context = CustomContextHelper<Traits>::GetNext(context);
-
+ void* custom_context = CustomContextHelper<Traits>::SetUp(input, context);
{{struct_macros.serialize(
struct, struct.name ~ " struct",
- "CallWithContext(Traits::%s, input, custom_context)", "result",
+ "CallWithContext(Traits::%s, input, custom_context)", "(*output)",
"buffer", "context", True)|indent(2)}}
- *output = result;
-
CustomContextHelper<Traits>::TearDown(input, custom_context);
}
@@ -55,3 +37,5 @@ struct Serializer<{{data_view}}, MaybeConstUserType> {
};
} // namespace internal
+
+{%- endif %}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_unserialized_message_context.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_unserialized_message_context.tmpl
new file mode 100644
index 0000000000..2ee085d570
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_unserialized_message_context.tmpl
@@ -0,0 +1,33 @@
+// Used by {{struct.name}}::WrapAsMessage to lazily serialize the struct.
+template <typename UserType, typename DataView>
+struct {{struct.name}}_UnserializedMessageContext
+ : public mojo::internal::UnserializedMessageContext {
+ public:
+ static const mojo::internal::UnserializedMessageContext::Tag kMessageTag;
+
+ {{struct.name}}_UnserializedMessageContext(
+ uint32_t message_name,
+ uint32_t message_flags,
+ UserType input)
+ : mojo::internal::UnserializedMessageContext(&kMessageTag, message_name, message_flags)
+ , user_data_(std::move(input)) {}
+ ~{{struct.name}}_UnserializedMessageContext() override = default;
+
+ UserType TakeData() {
+ return std::move(user_data_);
+ }
+
+ private:
+ // mojo::internal::UnserializedMessageContext:
+ void Serialize(mojo::internal::SerializationContext* context,
+ mojo::internal::Buffer* buffer) override {
+ {{struct.name}}_Data::BufferWriter writer;
+ mojo::internal::Serialize<DataView>(user_data_, buffer, &writer, context);
+ }
+
+ UserType user_data_;
+};
+
+template <typename UserType, typename DataView>
+const mojo::internal::UnserializedMessageContext::Tag
+ {{struct.name}}_UnserializedMessageContext<UserType, DataView>::kMessageTag = {};
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl
index 005ba76b61..e14169a263 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl
@@ -2,7 +2,7 @@
{%- set enum_name = union.name ~ "_Tag" -%}
{%- import "struct_macros.tmpl" as struct_macros %}
-class {{class_name}} {
+class {{export_attribute}} {{class_name}} {
public:
// Used to identify Mojom Union Data Classes.
typedef void MojomUnionDataType;
@@ -12,9 +12,38 @@ class {{class_name}} {
// non-inlined union.
~{{class_name}}() {}
- static {{class_name}}* New(mojo::internal::Buffer* buf) {
- return new (buf->Allocate(sizeof({{class_name}}))) {{class_name}}();
- }
+ class BufferWriter {
+ public:
+ BufferWriter() = default;
+
+ void Allocate(mojo::internal::Buffer* serialization_buffer) {
+ serialization_buffer_ = serialization_buffer;
+ index_ = serialization_buffer_->Allocate(sizeof({{class_name}}));
+ new (data()) {{class_name}}();
+ }
+
+ void AllocateInline(mojo::internal::Buffer* serialization_buffer,
+ void* ptr) {
+ const char* start = static_cast<const char*>(
+ serialization_buffer->data());
+ const char* slot = static_cast<const char*>(ptr);
+ DCHECK_GT(slot, start);
+ serialization_buffer_ = serialization_buffer;
+ index_ = slot - start;
+ new (data()) {{class_name}}();
+ }
+
+ bool is_null() const { return !serialization_buffer_; }
+ {{class_name}}* data() {
+ DCHECK(!is_null());
+ return serialization_buffer_->Get<{{class_name}}>(index_);
+ }
+ {{class_name}}* operator->() { return data(); }
+
+ private:
+ mojo::internal::Buffer* serialization_buffer_ = nullptr;
+ size_t index_ = 0;
+ };
static bool Validate(const void* data,
mojo::internal::ValidationContext* validation_context,
@@ -38,6 +67,8 @@ class {{class_name}} {
// "Each non-static data member is allocated as if it were the sole member of
// a struct." - Section 9.5.2 ISO/IEC 14882:2011 (The C++ Spec)
union MOJO_ALIGNAS(8) Union_ {
+ Union_() : unknown(0) {}
+
{%- for field in union.fields %}
{%- if field.kind.spec == 'b' %}
uint8_t f_{{field.name}} : 1;
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl
index af5ea9f8a8..feff2298f0 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl
@@ -32,7 +32,7 @@ bool {{class_name}}::Validate(
{% for field in union.fields %}
case {{enum_name}}::{{field.name|upper}}: {
{%- set field_expr = "object->data.f_" ~ field.name %}
-{{validation_macros.validate_field(field, field_expr, union.name, false)|indent(4)}}
+{{validation_macros.validate_field(field, loop.index, field_expr, union.name, false)|indent(4)}}
return true;
}
{%- endfor %}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl
index b589ae9147..4e39774b60 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl
@@ -8,61 +8,22 @@ struct Serializer<{{data_view}}, MaybeConstUserType> {
using UserType = typename std::remove_const<MaybeConstUserType>::type;
using Traits = UnionTraits<{{data_view}}, UserType>;
- static size_t PrepareToSerialize(MaybeConstUserType& input,
- bool inlined,
- SerializationContext* context) {
- size_t size = inlined ? 0 : sizeof({{data_type}});
-
- if (CallIsNullIfExists<Traits>(input))
- return size;
-
- void* custom_context = CustomContextHelper<Traits>::SetUp(input, context);
- ALLOW_UNUSED_LOCAL(custom_context);
-
- switch (CallWithContext(Traits::GetTag, input, custom_context)) {
-{%- for field in union.fields %}
-{%- set name = field.name %}
- case {{data_view}}::Tag::{{name|upper}}: {
-{%- if field.kind|is_object_kind or field.kind|is_associated_kind %}
-{%- set kind = field.kind %}
-{%- set serializer_type = kind|unmapped_type_for_serializer %}
- decltype(CallWithContext(Traits::{{name}}, input, custom_context))
- in_{{name}} = CallWithContext(Traits::{{name}}, input,
- custom_context);
-{%- if kind|is_union_kind %}
- size += mojo::internal::PrepareToSerialize<{{serializer_type}}>(
- in_{{name}}, false, context);
-{%- else %}
- size += mojo::internal::PrepareToSerialize<{{serializer_type}}>(
- in_{{name}}, context);
-{%- endif %}
-{%- endif %}
- break;
- }
-{%- endfor %}
- }
- return size;
- }
-
static void Serialize(MaybeConstUserType& input,
Buffer* buffer,
- {{data_type}}** output,
+ {{data_type}}::BufferWriter* writer,
bool inlined,
SerializationContext* context) {
if (CallIsNullIfExists<Traits>(input)) {
- if (inlined)
- (*output)->set_null();
- else
- *output = nullptr;
+ if (inlined)
+ writer->data()->set_null();
return;
}
-
- void* custom_context = CustomContextHelper<Traits>::GetNext(context);
+ void* custom_context = CustomContextHelper<Traits>::SetUp(input, context);
if (!inlined)
- *output = {{data_type}}::New(buffer);
+ writer->Allocate(buffer);
- {{data_type}}* result = *output;
+ {{data_type}}::BufferWriter& result = *writer;
ALLOW_UNUSED_LOCAL(result);
// TODO(azani): Handle unknown and objects.
// Set the not-null flag.
@@ -78,25 +39,29 @@ struct Serializer<{{data_view}}, MaybeConstUserType> {
in_{{name}} = CallWithContext(Traits::{{name}}, input,
custom_context);
{%- if kind|is_object_kind %}
- typename decltype(result->data.f_{{name}})::BaseType* ptr;
+ typename decltype(result->data.f_{{name}})::BaseType::BufferWriter
+ value_writer;
{%- if kind|is_union_kind %}
mojo::internal::Serialize<{{serializer_type}}>(
- in_{{name}}, buffer, &ptr, false, context);
+ in_{{name}}, buffer, &value_writer, false, context);
{%- elif kind|is_array_kind or kind|is_map_kind %}
const ContainerValidateParams {{name}}_validate_params(
{{kind|get_container_validate_params_ctor_args|indent(16)}});
mojo::internal::Serialize<{{serializer_type}}>(
- in_{{name}}, buffer, &ptr, &{{name}}_validate_params, context);
-{%- else %}
+ in_{{name}}, buffer, &value_writer, &{{name}}_validate_params,
+ context);
+ {%- else %}
mojo::internal::Serialize<{{serializer_type}}>(
- in_{{name}}, buffer, &ptr, context);
+ in_{{name}}, buffer, &value_writer, context);
{%- endif %}
- result->data.f_{{name}}.Set(ptr);
{%- if not kind|is_nullable_kind %}
MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(
- !ptr, mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
+ value_writer.is_null(),
+ mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
"null {{name}} in {{union.name}} union");
{%- endif %}
+ result->data.f_{{name}}.Set(
+ value_writer.is_null() ? nullptr : value_writer.data());
{%- elif kind|is_any_handle_or_interface_kind %}
mojo::internal::Serialize<{{serializer_type}}>(
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_traits_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_traits_definition.tmpl
index cde3f95669..f402142460 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/union_traits_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/union_traits_definition.tmpl
@@ -4,38 +4,38 @@
bool UnionTraits<{{mojom_type}}::DataView, {{mojom_type}}Ptr>::Read(
{{mojom_type}}::DataView input,
{{mojom_type}}Ptr* output) {
- *output = {{mojom_type}}::New();
- {{mojom_type}}Ptr& result = *output;
+ using UnionType = {{mojom_type}};
+ using Tag = UnionType::Tag;
- internal::UnionAccessor<{{mojom_type}}> result_acc(result.get());
switch (input.tag()) {
{%- for field in union.fields %}
- case {{mojom_type}}::Tag::{{field.name|upper}}: {
+ case Tag::{{field.name|upper}}: {
{%- set name = field.name %}
{%- set kind = field.kind %}
{%- set serializer_type = kind|unmapped_type_for_serializer %}
{%- if kind|is_object_kind %}
- result_acc.SwitchActive({{mojom_type}}::Tag::{{name|upper}});
- if (!input.Read{{name|under_to_camel}}(result_acc.data()->{{name}}))
+ {{kind|cpp_wrapper_type(True)}} result_{{name}};
+ if (!input.Read{{name|under_to_camel}}(&result_{{name}}))
return false;
+ *output = UnionType::New{{field.name|under_to_camel}}(
+ std::move(result_{{name}}));
{%- elif kind|is_any_handle_kind %}
- auto result_{{name}} = input.Take{{name|under_to_camel}}();
- result->set_{{name}}(std::move(result_{{name}}));
+ *output = UnionType::New{{field.name|under_to_camel}}(
+ input.Take{{name|under_to_camel}}());
{%- elif kind|is_any_interface_kind %}
- auto result_{{name}} =
- input.Take{{name|under_to_camel}}<typename std::remove_reference<decltype(result->get_{{name}}())>::type>();
- result->set_{{name}}(std::move(result_{{name}}));
+ *output = UnionType::New{{field.name|under_to_camel}}(
+ input.Take{{name|under_to_camel}}<{{kind|cpp_wrapper_type(True)}}>());
{%- elif kind|is_enum_kind %}
- decltype(result->get_{{name}}()) result_{{name}};
+ {{kind|cpp_wrapper_type(True)}} result_{{name}};
if (!input.Read{{name|under_to_camel}}(&result_{{name}}))
return false;
- result->set_{{name}}(result_{{name}});
+ *output = UnionType::New{{field.name|under_to_camel}}(result_{{name}});
{%- else %}
- result->set_{{name}}(input.{{name}}());
+ *output = UnionType::New{{field.name|under_to_camel}}(input.{{name}}());
{%- endif %}
break;
}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/validation_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/validation_macros.tmpl
index a50a585c09..457f2f9977 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/validation_macros.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/validation_macros.tmpl
@@ -2,20 +2,18 @@
(struct/array/string/map/union). If it is a union, |union_is_inlined|
indicates whether the union is inlined. (Nested unions are not inlined.)
This macro is expanded by the Validate() method. #}
-{%- macro validate_object(field, field_expr, object_name, union_is_inlined) %}
+{%- macro validate_object(field, field_index, field_expr, union_is_inlined) %}
{%- set name = field.name %}
{%- set kind = field.kind %}
{%- if not kind|is_nullable_kind %}
{%- if kind|is_union_kind and union_is_inlined %}
if (!mojo::internal::ValidateInlinedUnionNonNullable(
- {{field_expr}}, "null {{name}} field in {{object_name}}",
- validation_context)) {
+ {{field_expr}}, {{field_index}}, validation_context)) {
return false;
}
{%- else %}
if (!mojo::internal::ValidatePointerNonNullable(
- {{field_expr}}, "null {{name}} field in {{object_name}}",
- validation_context)) {
+ {{field_expr}}, {{field_index}}, validation_context)) {
return false;
}
{%- endif %}
@@ -47,13 +45,12 @@
{#- Validates the specified field, which is supposed to be a handle,
an interface, an associated interface or an associated interface request.
This macro is expanded by the Validate() method. #}
-{%- macro validate_handle_or_interface(field, field_expr, object_name) %}
+{%- macro validate_handle_or_interface(field, field_index, field_expr, object_name) %}
{%- set name = field.name %}
{%- set kind = field.kind %}
{%- if not kind|is_nullable_kind %}
if (!mojo::internal::ValidateHandleOrInterfaceNonNullable(
- {{field_expr}},
- "invalid {{name}} field in {{object_name}}", validation_context)) {
+ {{field_expr}}, {{field_index}}, validation_context)) {
return false;
}
{%- endif %}
@@ -71,11 +68,11 @@
return false;
{%- endmacro %}
-{%- macro validate_field(field, field_expr, object_name, union_is_inlined) %}
+{%- macro validate_field(field, field_index, field_expr, object_name, union_is_inlined) %}
{%- if field.kind|is_object_kind -%}
-{{validate_object(field, field_expr, object_name, union_is_inlined)}}
+{{validate_object(field, field_index, field_expr, union_is_inlined)}}
{%- elif field.kind|is_any_handle_or_interface_kind -%}
-{{validate_handle_or_interface(field, field_expr, object_name)}}
+{{validate_handle_or_interface(field, field_index, field_expr, object_name)}}
{%- elif field.kind|is_enum_kind %}
{{validate_enum(field, field_expr)}}
{%- endif %}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl
index 7ad9b4e1bc..e03fd0cb3e 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl
@@ -16,8 +16,7 @@ class {{export_attribute}} {{struct.name}} {
template <typename... Args>
static {{struct.name}}Ptr New(Args&&... args) {
return {{struct.name}}Ptr(
- base::in_place,
- std::forward<Args>(args)...);
+ base::in_place, std::forward<Args>(args)...);
}
template <typename U>
@@ -33,7 +32,7 @@ class {{export_attribute}} {{struct.name}} {
{% for constructor in struct|struct_constructors %}
{% if constructor.params|length == 1 %}explicit {% endif %}{{struct.name}}(
{%- for field in constructor.params %}
-{%- set type = field.kind|cpp_wrapper_param_type %}
+{%- set type = field.kind|cpp_wrapper_param_type_new %}
{%- set name = field.name %}
{{type}} {{name}}
{%- if not loop.last -%},{%- endif %}
@@ -64,16 +63,56 @@ class {{export_attribute}} {{struct.name}} {
template <typename UserType>
static {{serialization_result_type}} Serialize(UserType* input) {
- return mojo::internal::StructSerializeImpl<
+ return mojo::internal::SerializeImpl<
{{struct.name}}::DataView, {{serialization_result_type}}>(input);
}
template <typename UserType>
+ static mojo::Message SerializeAsMessage(UserType* input) {
+ return mojo::internal::SerializeAsMessageImpl<
+ {{struct.name}}::DataView>(input);
+ }
+
+ // The returned Message is serialized only if the message is moved
+ // cross-process or cross-language. Otherwise if the message is Deserialized
+ // as the same UserType |input| will just be moved to |output| in
+ // DeserializeFromMessage.
+ template <typename UserType>
+ static mojo::Message WrapAsMessage(UserType input) {
+ return mojo::Message(std::make_unique<
+ internal::{{struct.name}}_UnserializedMessageContext<
+ UserType, {{struct.name}}::DataView>>(0, 0, std::move(input)));
+ }
+
+ template <typename UserType>
+ static bool Deserialize(const void* data,
+ size_t data_num_bytes,
+ UserType* output) {
+ return mojo::internal::DeserializeImpl<{{struct.name}}::DataView>(
+ data, data_num_bytes, std::vector<mojo::ScopedHandle>(), output, Validate);
+ }
+
+ template <typename UserType>
static bool Deserialize(const {{serialization_result_type}}& input,
UserType* output) {
- return mojo::internal::StructDeserializeImpl<
- {{struct.name}}::DataView, {{serialization_result_type}}>(
- input, output, Validate);
+ return {{struct.name}}::Deserialize(
+ input.size() == 0 ? nullptr : &input.front(), input.size(), output);
+ }
+
+ template <typename UserType>
+ static bool DeserializeFromMessage(mojo::Message input,
+ UserType* output) {
+ auto context = input.TakeUnserializedContext<
+ internal::{{struct.name}}_UnserializedMessageContext<
+ UserType, {{struct.name}}::DataView>>();
+ if (context) {
+ *output = std::move(context->TakeData());
+ return true;
+ }
+ input.SerializeIfNecessary();
+ return mojo::internal::DeserializeImpl<{{struct.name}}::DataView>(
+ input.payload(), input.payload_num_bytes(),
+ std::move(*input.mutable_handles()), output, Validate);
}
{#--- Struct members #}
@@ -91,4 +130,3 @@ class {{export_attribute}} {{struct.name}} {
DISALLOW_COPY_AND_ASSIGN({{struct.name}});
{%- endif %}
};
-
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl
index ab8c22d49c..d65dd69137 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl
@@ -1,7 +1,7 @@
{% for constructor in struct|struct_constructors %}
{{struct.name}}::{{struct.name}}(
{%- for field in constructor.params %}
-{%- set type = field.kind|cpp_wrapper_param_type %}
+{%- set type = field.kind|cpp_wrapper_param_type_new %}
{%- set name = field.name %}
{{type}} {{name}}_in
{%- if not loop.last -%},{%- endif %}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_template_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_template_definition.tmpl
index feb861569f..fab27cd8d0 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_template_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_template_definition.tmpl
@@ -11,9 +11,9 @@ template <typename StructPtrType>
template <typename T,
typename std::enable_if<std::is_same<
T, {{struct.name}}>::value>::type*>
-bool {{struct.name}}::Equals(const T& other) const {
+bool {{struct.name}}::Equals(const T& other_struct) const {
{%- for field in struct.fields %}
- if (!mojo::internal::Equals(this->{{field.name}}, other.{{field.name}}))
+ if (!mojo::Equals(this->{{field.name}}, other_struct.{{field.name}}))
return false;
{%- endfor %}
return true;
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl
index 8b7cf9e6b1..a0be109b03 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl
@@ -4,7 +4,20 @@ class {{export_attribute}} {{union.name}} {
using Data_ = internal::{{union.name}}_Data;
using Tag = Data_::{{union.name}}_Tag;
- static {{union.name}}Ptr New();
+ static {{union.name}}Ptr New() {
+ return {{union.name}}Ptr(base::in_place);
+ }
+
+{%- for field in union.fields %}
+ // Construct an instance holding |{{field.name}}|.
+ static {{union.name}}Ptr
+ New{{field.name|under_to_camel}}(
+ {{field.kind|cpp_wrapper_param_type_new}} {{field.name}}) {
+ auto result = {{union.name}}Ptr(base::in_place);
+ result->set_{{field.name}}(std::move({{field.name}}));
+ return result;
+ }
+{%- endfor %}
template <typename U>
static {{union.name}}Ptr From(const U& u) {
@@ -54,11 +67,25 @@ class {{export_attribute}} {{union.name}} {
{%- endif %}
}
- void set_{{field.name}}({{field.kind|cpp_wrapper_param_type}} {{field.name}});
+ void set_{{field.name}}(
+ {{field.kind|cpp_wrapper_param_type_new}} {{field.name}});
{%- endfor %}
+ template <typename UserType>
+ static mojo::Message SerializeAsMessage(UserType* input) {
+ return mojo::internal::SerializeAsMessageImpl<
+ {{union.name}}::DataView>(input);
+ }
+
+ template <typename UserType>
+ static bool DeserializeFromMessage(mojo::Message input,
+ UserType* output) {
+ return mojo::internal::DeserializeImpl<{{union.name}}::DataView>(
+ input.payload(), input.payload_num_bytes(),
+ std::move(*input.mutable_handles()), output, Validate);
+ }
+
private:
- friend class mojo::internal::UnionAccessor<{{union.name}}>;
union Union_ {
Union_() {}
~Union_() {}
@@ -72,8 +99,10 @@ class {{export_attribute}} {{union.name}} {
{%- endif %}
{%- endfor %}
};
- void SwitchActive(Tag new_active);
- void SetActive(Tag new_active);
+
+ static bool Validate(const void* data,
+ mojo::internal::ValidationContext* validation_context);
+
void DestroyActive();
Tag tag_;
Union_ data_;
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl
index b9e416a9f4..5f9bde51d2 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl
@@ -1,12 +1,11 @@
-// static
-{{union.name}}Ptr {{union.name}}::New() {
- return {{union.name}}Ptr(base::in_place);
-}
-
-{{union.name}}::{{union.name}}() {
- // TODO(azani): Implement default values here when/if we support them.
- // TODO(azani): Set to UNKNOWN when unknown is implemented.
- SetActive(static_cast<Tag>(0));
+{%- set default_field = union.fields.0 %}
+{{union.name}}::{{union.name}}() : tag_(Tag::{{default_field.name|upper}}) {
+{%- if default_field.kind|is_object_kind or
+ default_field.kind|is_any_handle_or_interface_kind %}
+ data_.{{default_field.name}} = new {{default_field.kind|cpp_wrapper_type}};
+{%- else %}
+ data_.{{default_field.name}} = {{default_field.kind|cpp_wrapper_type}}();
+{%- endif %}
}
{{union.name}}::~{{union.name}}() {
@@ -14,43 +13,28 @@
}
{% for field in union.fields %}
-void {{union.name}}::set_{{field.name}}({{field.kind|cpp_wrapper_param_type}} {{field.name}}) {
- SwitchActive(Tag::{{field.name|upper}});
-{% if field.kind|is_string_kind %}
- *(data_.{{field.name}}) = {{field.name}};
-{% elif field.kind|is_object_kind or
+void {{union.name}}::set_{{field.name}}(
+ {{field.kind|cpp_wrapper_param_type_new}} {{field.name}}) {
+{%- if field.kind|is_object_kind or
field.kind|is_any_handle_or_interface_kind %}
- *(data_.{{field.name}}) = std::move({{field.name}});
+ if (tag_ == Tag::{{field.name|upper}}) {
+ *(data_.{{field.name}}) = std::move({{field.name}});
+ } else {
+ DestroyActive();
+ tag_ = Tag::{{field.name|upper}};
+ data_.{{field.name}} = new {{field.kind|cpp_wrapper_type}}(
+ std::move({{field.name}}));
+ }
{%- else %}
+ if (tag_ != Tag::{{field.name|upper}}) {
+ DestroyActive();
+ tag_ = Tag::{{field.name|upper}};
+ }
data_.{{field.name}} = {{field.name}};
{%- endif %}
}
{%- endfor %}
-void {{union.name}}::SwitchActive(Tag new_active) {
- if (new_active == tag_) {
- return;
- }
-
- DestroyActive();
- SetActive(new_active);
-}
-
-void {{union.name}}::SetActive(Tag new_active) {
- switch (new_active) {
-{% for field in union.fields %}
- case Tag::{{field.name|upper}}:
-{% if field.kind|is_object_kind or
- field.kind|is_any_handle_or_interface_kind %}
- data_.{{field.name}} = new {{field.kind|cpp_wrapper_type}}();
-{%- endif %}
- break;
-{%- endfor %}
- }
-
- tag_ = new_active;
-}
-
void {{union.name}}::DestroyActive() {
switch (tag_) {
{% for field in union.fields %}
@@ -81,5 +65,10 @@ size_t {{union.name}}::Hash(size_t seed) const {
return seed;
}
}
-
{%- endif %}
+
+bool {{union.name}}::Validate(
+ const void* data,
+ mojo::internal::ValidationContext* validation_context) {
+ return Data_::Validate(data, validation_context, false);
+}
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_template_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_template_definition.tmpl
index 4c4851fa83..a29a1b7f55 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_template_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_template_definition.tmpl
@@ -30,9 +30,9 @@ bool {{union.name}}::Equals(const T& other) const {
case Tag::{{field.name|upper}}:
{%- if field.kind|is_object_kind or
field.kind|is_any_handle_or_interface_kind %}
- return mojo::internal::Equals(*(data_.{{field.name}}), *(other.data_.{{field.name}}));
+ return mojo::Equals(*(data_.{{field.name}}), *(other.data_.{{field.name}}));
{%- else %}
- return mojo::internal::Equals(data_.{{field.name}}, other.data_.{{field.name}});
+ return mojo::Equals(data_.{{field.name}}, other.data_.{{field.name}});
{%- endif %}
{%- endfor %}
};
diff --git a/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl
index 4c0823cce6..7af57bd968 100644
--- a/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl
@@ -1,34 +1,6 @@
{%- from "constant_definition.tmpl" import constant_def %}
{%- from "enum_definition.tmpl" import enum_def %}
-{%- macro equality(kind, v1, v2, ne=False) -%}
-{%- if kind|is_reference_kind -%}
-{%- if kind|is_array_kind -%}
-{%- if kind.kind|is_reference_kind -%}
-{%- if ne %}!{%- endif %}java.util.Arrays.deepEquals({{v1}}, {{v2}})
-{%- else -%}
-{%- if ne %}!{%- endif %}java.util.Arrays.equals({{v1}}, {{v2}})
-{%- endif -%}
-{%- else -%}
-{%- if ne %}!{%- endif %}org.chromium.mojo.bindings.BindingsHelper.equals({{v1}}, {{v2}})
-{%- endif -%}
-{%- else -%}
-{{v1}} {%- if ne %}!={%- else %}=={%- endif %} {{v2}}
-{%- endif -%}
-{%- endmacro -%}
-
-{%- macro hash(kind, v) -%}
-{%- if kind|is_array_kind -%}
-{%- if kind.kind|is_reference_kind -%}
-java.util.Arrays.deepHashCode({{v}})
-{%- else -%}
-java.util.Arrays.hashCode({{v}})
-{%- endif -%}
-{%- else -%}
-org.chromium.mojo.bindings.BindingsHelper.hashCode({{v}})
-{%- endif -%}
-{%- endmacro -%}
-
{%- macro array_element_size(kind) -%}
{%- if kind|is_union_kind %}
org.chromium.mojo.bindings.BindingsHelper.UNION_SIZE
@@ -167,9 +139,9 @@ if ({{variable}} != null) {
super(STRUCT_SIZE, version);
{%- for field in struct.fields %}
{%- if field.default %}
- {{field|name}} = {{field|default_value}};
+ this.{{field|name}} = {{field|default_value}};
{%- elif field.kind|is_any_handle_kind %}
- {{field|name}} = org.chromium.mojo.system.InvalidHandle.INSTANCE;
+ this.{{field|name}} = org.chromium.mojo.system.InvalidHandle.INSTANCE;
{%- endif %}
{%- endfor %}
}
@@ -188,9 +160,6 @@ if ({{variable}} != null) {
* @throws org.chromium.mojo.bindings.DeserializationException on deserialization failure.
*/
public static {{struct|name}} deserialize(java.nio.ByteBuffer data) {
- if (data == null)
- return null;
-
return deserialize(new org.chromium.mojo.bindings.Message(
data, new java.util.ArrayList<org.chromium.mojo.system.Handle>()));
}
@@ -204,14 +173,30 @@ if ({{variable}} != null) {
{{struct|name}} result;
try {
org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY);
- result = new {{struct|name}}(mainDataHeader.elementsOrVersion);
+ final int elementsOrVersion = mainDataHeader.elementsOrVersion;
+ result = new {{struct|name}}(elementsOrVersion);
+
+{%- set prev_ver = [0] %}
{%- for byte in struct.bytes %}
{%- for packed_field in byte.packed_fields %}
- if (mainDataHeader.elementsOrVersion >= {{packed_field.min_version}}) {
- {{decode('result.' ~ packed_field.field|name, packed_field.field.kind, 8+packed_field.offset, packed_field.bit)|indent(16)}}
+{%- if packed_field.min_version != prev_ver[-1] %}
+{%- if prev_ver[-1] != 0 %}
}
+{%- endif %}
+{%- set _ = prev_ver.append(packed_field.min_version) %}
+{%- if prev_ver[-1] != 0 %}
+ if (elementsOrVersion >= {{packed_field.min_version}}) {
+{%- endif %}
+{%- endif %}
+ {
+ {{decode('result.' ~ packed_field.field|name, packed_field.field.kind, 8+packed_field.offset, packed_field.bit)|indent(16)}}
+ }
{%- endfor %}
{%- endfor %}
+{%- if prev_ver[-1] != 0 %}
+ }
+{%- endif %}
+
} finally {
decoder0.decreaseStackDepth();
}
@@ -228,43 +213,9 @@ if ({{variable}} != null) {
{%- endif %}
{%- for byte in struct.bytes %}
{%- for packed_field in byte.packed_fields %}
- {{encode(packed_field.field|name, packed_field.field.kind, 8+packed_field.offset, packed_field.bit)|indent(8)}}
-{%- endfor %}
-{%- endfor %}
- }
-
- /**
- * @see Object#equals(Object)
- */
- @Override
- public boolean equals(Object object) {
- if (object == this)
- return true;
- if (object == null)
- return false;
- if (getClass() != object.getClass())
- return false;
-{%- if struct.fields|length %}
- {{struct|name}} other = ({{struct|name}}) object;
-{%- for field in struct.fields %}
- if ({{equality(field.kind, 'this.'~field|name, 'other.'~field|name, True)}})
- return false;
+ {{encode('this.' ~ packed_field.field|name, packed_field.field.kind, 8+packed_field.offset, packed_field.bit)|indent(8)}}
{%- endfor %}
-{%- endif %}
- return true;
- }
-
- /**
- * @see Object#hashCode()
- */
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = prime + getClass().hashCode();
-{%- for field in struct.fields %}
- result = prime * result + {{hash(field.kind, field|name)}};
{%- endfor %}
- return result;
}
}
{%- endmacro %}
@@ -279,34 +230,20 @@ public final class {{union|name}} extends org.chromium.mojo.bindings.Union {
{%- endfor %}
};
- private int mTag_ = -1;
{%- for field in union.fields %}
private {{field.kind|java_type}} m{{field|ucc}};
{%- endfor %}
- public int which() {
- return mTag_;
- }
-
- public boolean isUnknown() {
- return mTag_ == -1;
- }
{%- for field in union.fields %}
- // TODO(rockot): Fix the findbugs error and remove this suppression.
- // See http://crbug.com/570386.
- @SuppressFBWarnings("EI_EXPOSE_REP2")
public void set{{field|ucc}}({{field.kind|java_type}} {{field|name}}) {
- mTag_ = Tag.{{field|ucc}};
- m{{field|ucc}} = {{field|name}};
+ this.mTag = Tag.{{field|ucc}};
+ this.m{{field|ucc}} = {{field|name}};
}
- // TODO(rockot): Fix the findbugs error and remove this suppression.
- // See http://crbug.com/570386.
- @SuppressFBWarnings("EI_EXPOSE_REP")
public {{field.kind|java_type}} get{{field|ucc}}() {
- assert mTag_ == Tag.{{field|ucc}};
- return m{{field|ucc}};
+ assert this.mTag == Tag.{{field|ucc}};
+ return this.m{{field|ucc}};
}
{%- endfor %}
@@ -314,18 +251,19 @@ public final class {{union|name}} extends org.chromium.mojo.bindings.Union {
@Override
protected final void encode(org.chromium.mojo.bindings.Encoder encoder0, int offset) {
encoder0.encode(org.chromium.mojo.bindings.BindingsHelper.UNION_SIZE, offset);
- encoder0.encode(mTag_, offset + 4);
- switch (mTag_) {
+ encoder0.encode(this.mTag, offset + 4);
+ switch (mTag) {
{%- for field in union.fields %}
case Tag.{{field|ucc}}: {
{%- if field.kind|is_union_kind %}
- if (m{{field|ucc}} == null) {
+ if (this.m{{field|ucc}} == null) {
encoder0.encodeNullPointer(offset + 8, {{field.kind|is_nullable_kind|java_true_false}});
} else {
- m{{field|ucc}}.encode(encoder0.encoderForUnionPointer(offset + 8), 0);
+ encoder0.encoderForUnionPointer(offset + 8).encode(
+ this.m{{field|ucc}}, 0, {{field.kind|is_nullable_kind|java_true_false}});
}
{%- else %}
- {{encode('m' ~ field|ucc, field.kind, 'offset + 8', 0)|indent(16)}}
+ {{encode('this.m' ~ field|ucc, field.kind, 'offset + 8', 0)|indent(16)}}
{%- endif %}
break;
}
@@ -352,59 +290,12 @@ public final class {{union|name}} extends org.chromium.mojo.bindings.Union {
{%- if field.kind|is_union_kind %}
org.chromium.mojo.bindings.Decoder decoder1 = decoder0.readPointer(offset + org.chromium.mojo.bindings.DataHeader.HEADER_SIZE, {{field.kind|is_nullable_kind|java_true_false}});
if (decoder1 != null) {
- result.m{{field|ucc}} = {{field.kind|name}}.decode(decoder1, 0);
+ result.m{{field|ucc}} = {{field.kind|java_type}}.decode(decoder1, 0);
}
{%- else %}
{{decode('result.m'~field|ucc, field.kind, 'offset + org.chromium.mojo.bindings.DataHeader.HEADER_SIZE', 0)|indent(16)}}
{%- endif %}
- result.mTag_ = Tag.{{field|ucc}};
- break;
- }
-{%- endfor %}
- default: {
- break;
- }
- }
- return result;
- }
-
- /**
- * @see Object#equals(Object)
- */
- @Override
- public boolean equals(Object object) {
- if (object == this)
- return true;
- if (object == null)
- return false;
- if (getClass() != object.getClass())
- return false;
- {{union|name}} other = ({{union|name}}) object;
- if (mTag_ != other.mTag_)
- return false;
- switch (mTag_) {
-{%- for field in union.fields %}
- case Tag.{{field|ucc}}:
- return {{equality(field.kind, 'm'~field|ucc, 'other.m'~field|ucc)}};
-{%- endfor %}
- default:
- break;
- }
- return false;
- }
-
- /**
- * @see Object#hashCode()
- */
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = prime + getClass().hashCode();
- result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(mTag_);
- switch (mTag_) {
-{%- for field in union.fields %}
- case Tag.{{field|ucc}}: {
- result = prime * result + {{hash(field.kind, 'm'~field|ucc)}};
+ result.mTag = Tag.{{field|ucc}};
break;
}
{%- endfor %}
diff --git a/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl
index 1d67890452..410bfce997 100644
--- a/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl
+++ b/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl
@@ -10,5 +10,4 @@
package {{package}};
-import org.chromium.base.annotations.SuppressFBWarnings;
-import org.chromium.mojo.bindings.DeserializationException; \ No newline at end of file
+import org.chromium.mojo.bindings.DeserializationException;
diff --git a/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl
index c7dcbbc7cb..27e9a3a08d 100644
--- a/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl
@@ -58,23 +58,28 @@ org.chromium.mojo.bindings.MessageHeader.MESSAGE_IS_RESPONSE_FLAG
public static final {{manager_class(interface, True)}} MANAGER =
new {{manager_class(interface, True)}}() {
+ @Override
public String getName() {
- return "{{namespace|replace(".","::")}}::{{interface.name}}";
+ return "{{namespace}}.{{interface.name}}";
}
+ @Override
public int getVersion() {
return {{interface.version}};
}
+ @Override
public Proxy buildProxy(org.chromium.mojo.system.Core core,
org.chromium.mojo.bindings.MessageReceiverWithResponder messageReceiver) {
return new Proxy(core, messageReceiver);
}
+ @Override
public Stub buildStub(org.chromium.mojo.system.Core core, {{interface|name}} impl) {
return new Stub(core, impl);
}
+ @Override
public {{interface|name}}[] buildArray(int size) {
return new {{interface|name}}[size];
}
diff --git a/mojo/public/tools/bindings/generators/js_templates/externs/interface_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/externs/interface_definition.tmpl
new file mode 100644
index 0000000000..672b5d5d89
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/js_templates/externs/interface_definition.tmpl
@@ -0,0 +1,34 @@
+{% macro generateInterfaceClass() -%}
+class {
+{%- for method in interface.methods %}
+ /**
+{%- for parameter in method.parameters %}
+ * @param { {{parameter.kind|closure_type_with_nullability}} } {{parameter.name|sanitize_identifier}}
+{%- endfor -%}
+{%- if method.response_parameters != None %}
+ * @return {Promise}
+{%- endif %}
+ */
+ {{method.name}}(
+{%- for parameter in method.parameters -%}
+{{parameter.name|sanitize_identifier}}{% if not loop.last %}, {% endif %}
+{%- endfor -%}
+) {}
+{%- endfor %}
+};
+{%- endmacro %}
+
+/**
+ * @const
+ * @type { !mojo.Interface };
+ */
+{{module.namespace}}.{{interface.name}};
+
+/** @interface */
+{{module.namespace}}.{{interface.name}}Impl = {{ generateInterfaceClass() }}
+
+/**
+ * @implements { mojo.InterfacePtr }
+ * @implements { {{module.namespace}}.{{interface.name}}Impl }
+ */
+{{module.namespace}}.{{interface.name}}Ptr = {{ generateInterfaceClass() }}
diff --git a/mojo/public/tools/bindings/generators/js_templates/externs/module.externs.tmpl b/mojo/public/tools/bindings/generators/js_templates/externs/module.externs.tmpl
new file mode 100644
index 0000000000..fd711068f2
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/js_templates/externs/module.externs.tmpl
@@ -0,0 +1,37 @@
+// 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.
+
+{% for declaration in module.namespace|namespace_declarations -%}
+/** @const */
+{%- if loop.first %}
+var {{declaration}} = {};
+{% else %}
+{{declaration}} = {};
+{% endif -%}
+{%- endfor -%}
+
+{#--- Constant definitions #}
+{%- for constant in module.constants %}
+/** @type { {{constant.kind|closure_type_with_nullability }} } */
+{{module.namespace}}.{{constant.name}};
+{%- endfor %}
+
+{#--- Enum definitions #}
+{% for enum in enums %}
+/** @enum {number} */
+{{module.namespace}}.{{enum.name}} = {};
+{%- for field in enum.fields %}
+{{module.namespace}}.{{enum.name}}.{{field.name}};
+{%- endfor %}
+{%- endfor %}
+
+{#--- Interface definitions #}
+{%- for interface in interfaces -%}
+{%- include "externs/interface_definition.tmpl" %}
+{% endfor -%}
+
+{#--- Struct definitions #}
+{%- for struct in structs -%}
+{%- include "externs/struct_definition.tmpl" %}
+{% endfor -%}
diff --git a/mojo/public/tools/bindings/generators/js_templates/externs/struct_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/externs/struct_definition.tmpl
new file mode 100644
index 0000000000..4aecb67c84
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/js_templates/externs/struct_definition.tmpl
@@ -0,0 +1,8 @@
+{{module.namespace}}.{{struct.name}} = class {
+ constructor() {
+{%- for packed_field in struct.packed.packed_fields %}
+ /** @type { {{packed_field.field.kind|closure_type_with_nullability}} } */
+ this.{{packed_field.field.name}};
+{%- endfor %}
+ }
+};
diff --git a/mojo/public/tools/bindings/generators/js_templates/fuzzing.tmpl b/mojo/public/tools/bindings/generators/js_templates/fuzzing.tmpl
new file mode 100644
index 0000000000..a91260ed9a
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/js_templates/fuzzing.tmpl
@@ -0,0 +1,125 @@
+{%- macro get_handle_deps(kind, name) -%}
+{%- if kind|is_struct_kind or kind|is_union_kind -%}
+{{name}}.getHandleDeps()
+{%- elif kind|is_array_kind -%}
+[].concat.apply([], {{name}}.map(function(val) {
+ if (val) {
+ return {{get_handle_deps(kind.kind, 'val')|indent(4)}};
+ }
+ return [];
+}))
+{%- elif kind|is_map_kind -%}
+[].concat.apply([], Array.from({{name}}.values()).map(function(val) {
+ if (val) {
+ return {{get_handle_deps(kind.value_kind, 'val')|indent(4)}};
+ }
+ return [];
+}))
+{%- elif kind|is_any_handle_or_interface_kind -%}
+ ["{{kind|fuzz_handle_name}}"]
+{%- else -%}
+ []
+{%- endif -%}
+{%- endmacro -%}
+
+{%- macro set_handles(kind, name) -%}
+{%- if kind|is_struct_kind or kind|is_union_kind -%}
+idx = {{name}}.setHandlesInternal_(handles, idx)
+{%- elif kind|is_array_kind -%}
+{{name}}.forEach(function(val) {
+ {{set_handles(kind.kind, 'val')|indent(2)}};
+})
+{%- elif kind|is_map_kind -%}
+{{name}}.forEach(function(val) {
+ {{set_handles(kind.value_kind, 'val')|indent(2)}};
+})
+{%- elif kind|is_any_handle_or_interface_kind -%}
+{{name}} = handles[idx++];
+{%- endif -%}
+{%- endmacro -%}
+
+{%- macro build_call(obj, operation, type, name) -%}
+{%- if name -%}
+{{obj}}.{{operation}}{{type}}({{((name,) + varargs)|join(', ')}})
+{%- else -%}
+{{obj}}.{{operation}}{{type}}({{varargs|join(', ')}})
+{%- endif -%}
+{%- endmacro -%}
+
+{%- macro generate_or_mutate_enum(obj, operation, kind, name) -%}
+{%- if kind.max_value is not none -%}
+{{build_call(obj, operation, 'Enum', name, '0', kind.max_value)}}
+{%- else -%}
+{{build_call(obj, operation, 'Enum', name)}}
+{%- endif %}
+{%- endmacro -%}
+
+{%- macro generate_or_mutate_array(obj, operation, kind, name) -%}
+{%- if operation == 'mutate' -%}
+{{obj}}.{{operation}}Array({{name}}, function(val) {
+ return {{generate_or_mutate(obj, operation, kind.kind, 'val')|indent(2)}};
+})
+{%- else -%}
+{{obj}}.{{operation}}Array(function() {
+ return {{generate_or_mutate(obj, operation, kind.kind)|indent(2)}};
+})
+{%- endif -%}
+{%- endmacro -%}
+
+{%- macro generate_or_mutate_map(obj, operation, kind, name) -%}
+{%- if operation == 'mutate' -%}
+{{obj}}.{{operation}}Map({{name}},
+ function(val) {
+ return {{generate_or_mutate(obj, operation, kind.key_kind, 'val')|indent(4)}};
+ },
+ function(val) {
+ return {{generate_or_mutate(obj, operation, kind.value_kind, 'val')|indent(4)}};
+ })
+{%- else -%}
+{{obj}}.{{operation}}Map(
+ function() {
+ return {{generate_or_mutate(obj, operation, kind.key_kind)|indent(4)}};
+ },
+ function() {
+ return {{generate_or_mutate(obj, operation, kind.value_kind)|indent(4)}};
+ })
+{%- endif -%}
+{%- endmacro -%}
+
+{%- macro generate_or_mutate_primitive(obj, operation, kind, name) -%}
+{%- if kind|is_reference_kind -%}
+{{build_call(obj, operation, kind|primitive_to_fuzz_type, name, kind.is_nullable|to_js_boolean)}}
+{%- else -%}
+{{build_call(obj, operation, kind|primitive_to_fuzz_type, name)}}
+{%- endif -%}
+{%- endmacro -%}
+
+{%- macro generate_or_mutate_interface(obj, operation, kind, name) -%}
+{%- if kind|is_interface_request_kind -%}
+{{build_call(obj, operation, 'InterfaceRequest', name, '"' ~ kind.kind.module.namespace ~ '.' ~ kind.kind.name ~ '"', kind.is_nullable|to_js_boolean)}}
+{%- elif kind|is_interface_kind -%}
+{{build_call(obj, operation, 'Interface', name, '"' ~ kind.module.namespace ~ '.' ~ kind.name ~ '"', kind.is_nullable|to_js_boolean)}}
+{%- elif kind|is_associated_interface_request_kind -%}
+{{build_call(obj, operation, 'AssociatedInterfaceRequest', name, '"' ~ kind.kind.module.namespace ~ '.' ~ kind.kind.name ~ '"', kind.is_nullable|to_js_boolean)}}
+{%- elif kind|is_associated_interface_kind -%}
+{{build_call(obj, operation, 'AssociatedInterface', name, '"' ~ kind.kind.module.namespace ~ '.' ~ kind.kind.name ~ '"', kind.is_nullable|to_js_boolean)}}
+{%- endif -%}
+{%- endmacro -%}
+
+{%- macro generate_or_mutate(obj, operation, kind, name='') -%}
+{%- if kind|is_primitive_kind -%}
+{{generate_or_mutate_primitive(obj, operation, kind, name)}}
+{%- elif kind|is_any_interface_kind -%}
+{{generate_or_mutate_interface(obj, operation, kind, name)}}
+{%- elif kind|is_enum_kind -%}
+{{generate_or_mutate_enum(obj, operation, kind, name)}}
+{%- elif kind|is_struct_kind -%}
+{{build_call(obj, operation, 'Struct', name, kind.module.namespace ~ '.' ~ kind.name, kind.is_nullable|to_js_boolean)}}
+{%- elif kind|is_union_kind -%}
+{{build_call(obj, operation, 'Union', name, kind.module.namespace ~ '.' ~ kind.name, kind.is_nullable|to_js_boolean)}}
+{%- elif kind|is_array_kind -%}
+{{generate_or_mutate_array(obj, operation, kind, name)}}
+{%- elif kind|is_map_kind -%}
+{{generate_or_mutate_map(obj, operation, kind, name)}}
+{%- endif -%}
+{%- endmacro -%}
diff --git a/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl
index 11e319c1f7..d6b29007ac 100644
--- a/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl
@@ -1,5 +1,7 @@
{%- for method in interface.methods %}
- var k{{interface.name}}_{{method.name}}_Name = {{method.ordinal}};
+{%- set interface_method_id =
+ interface.mojom_name ~ "_" ~ method.mojom_name %}
+ var k{{interface_method_id}}_Name = {{method.ordinal}};
{%- endfor %}
function {{interface.name}}Ptr(handleOrPtrInfo) {
@@ -7,45 +9,72 @@
handleOrPtrInfo);
}
+ function {{interface.name}}AssociatedPtr(associatedInterfacePtrInfo) {
+ this.ptr = new associatedBindings.AssociatedInterfacePtrController(
+ {{interface.name}}, associatedInterfacePtrInfo);
+ }
+
+ {{interface.name}}AssociatedPtr.prototype =
+ Object.create({{interface.name}}Ptr.prototype);
+ {{interface.name}}AssociatedPtr.prototype.constructor =
+ {{interface.name}}AssociatedPtr;
+
function {{interface.name}}Proxy(receiver) {
this.receiver_ = receiver;
}
{%- for method in interface.methods %}
- {{interface.name}}Ptr.prototype.{{method.name|stylize_method}} = function() {
- return {{interface.name}}Proxy.prototype.{{method.name|stylize_method}}
+{%- set interface_method_id =
+ interface.mojom_name ~ "_" ~ method.mojom_name %}
+ {{interface.name}}Ptr.prototype.{{method.name}} = function() {
+ return {{interface.name}}Proxy.prototype.{{method.name}}
.apply(this.ptr.getProxy(), arguments);
};
- {{interface.name}}Proxy.prototype.{{method.name|stylize_method}} = function(
+ {{interface.name}}Proxy.prototype.{{method.name}} = function(
{%- for parameter in method.parameters -%}
-{{parameter.name}}{% if not loop.last %}, {% endif %}
+{{parameter.name|sanitize_identifier}}{% if not loop.last %}, {% endif %}
{%- endfor -%}
) {
- var params = new {{interface.name}}_{{method.name}}_Params();
+ var params_ = new {{interface_method_id}}_Params();
{%- for parameter in method.parameters %}
- params.{{parameter.name}} = {{parameter.name}};
+ params_.{{parameter.name}} = {{parameter.name|sanitize_identifier}};
{%- endfor %}
{%- if method.response_parameters == None %}
- var builder = new codec.MessageBuilder(
- k{{interface.name}}_{{method.name}}_Name,
- codec.align({{interface.name}}_{{method.name}}_Params.encodedSize));
- builder.encodeStruct({{interface.name}}_{{method.name}}_Params, params);
+{%- if method|method_passes_associated_kinds %}
+ var builder = new codec.MessageV2Builder(
+ k{{interface_method_id}}_Name,
+ codec.align({{interface_method_id}}_Params.encodedSize));
+ builder.setPayload({{interface_method_id}}_Params, params_);
+{%- else %}
+ var builder = new codec.MessageV0Builder(
+ k{{interface_method_id}}_Name,
+ codec.align({{interface_method_id}}_Params.encodedSize));
+ builder.encodeStruct({{interface_method_id}}_Params, params_);
+{%- endif %}
var message = builder.finish();
this.receiver_.accept(message);
{%- else %}
return new Promise(function(resolve, reject) {
- var builder = new codec.MessageWithRequestIDBuilder(
- k{{interface.name}}_{{method.name}}_Name,
- codec.align({{interface.name}}_{{method.name}}_Params.encodedSize),
+{%- if method|method_passes_associated_kinds %}
+ var builder = new codec.MessageV2Builder(
+ k{{interface_method_id}}_Name,
+ codec.align({{interface_method_id}}_Params.encodedSize),
codec.kMessageExpectsResponse, 0);
- builder.encodeStruct({{interface.name}}_{{method.name}}_Params, params);
+ builder.setPayload({{interface_method_id}}_Params, params_);
+{%- else %}
+ var builder = new codec.MessageV1Builder(
+ k{{interface_method_id}}_Name,
+ codec.align({{interface_method_id}}_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct({{interface_method_id}}_Params, params_);
+{%- endif %}
var message = builder.finish();
this.receiver_.acceptAndExpectResponse(message).then(function(message) {
var reader = new codec.MessageReader(message);
var responseParams =
- reader.decodeStruct({{interface.name}}_{{method.name}}_ResponseParams);
+ reader.decodeStruct({{interface_method_id}}_ResponseParams);
resolve(responseParams);
}).catch(function(result) {
reject(Error("Connection error: " + result));
@@ -60,9 +89,8 @@
}
{%- for method in interface.methods %}
-{%- set js_method_name = method.name|stylize_method %}
- {{interface.name}}Stub.prototype.{{js_method_name}} = function({{method.parameters|map(attribute='name')|join(', ')}}) {
- return this.delegate_ && this.delegate_.{{js_method_name}} && this.delegate_.{{js_method_name}}({{method.parameters|map(attribute='name')|join(', ')}});
+ {{interface.name}}Stub.prototype.{{method.name}} = function({{method.parameters|map(attribute='name')|map('sanitize_identifier')|join(', ')}}) {
+ return this.delegate_ && this.delegate_.{{method.name}} && this.delegate_.{{method.name}}({{method.parameters|map(attribute='name')|map('sanitize_identifier')|join(', ')}});
}
{%- endfor %}
@@ -70,10 +98,12 @@
var reader = new codec.MessageReader(message);
switch (reader.messageName) {
{%- for method in interface.methods %}
+{%- set interface_method_id =
+ interface.mojom_name ~ "_" ~ method.mojom_name %}
{%- if method.response_parameters == None %}
- case k{{interface.name}}_{{method.name}}_Name:
- var params = reader.decodeStruct({{interface.name}}_{{method.name}}_Params);
- this.{{method.name|stylize_method}}(
+ case k{{interface_method_id}}_Name:
+ var params = reader.decodeStruct({{interface_method_id}}_Params);
+ this.{{method.name}}(
{%- for parameter in method.parameters -%}
params.{{parameter.name}}{% if not loop.last %}, {% endif %}
{%- endfor %});
@@ -90,24 +120,36 @@
var reader = new codec.MessageReader(message);
switch (reader.messageName) {
{%- for method in interface.methods %}
+{%- set interface_method_id =
+ interface.mojom_name ~ "_" ~ method.mojom_name %}
{%- if method.response_parameters != None %}
- case k{{interface.name}}_{{method.name}}_Name:
- var params = reader.decodeStruct({{interface.name}}_{{method.name}}_Params);
- this.{{method.name|stylize_method}}(
+ case k{{interface_method_id}}_Name:
+ var params = reader.decodeStruct({{interface_method_id}}_Params);
+ this.{{method.name}}(
{%- for parameter in method.parameters -%}
params.{{parameter.name}}{% if not loop.last %}, {% endif -%}
{%- endfor %}).then(function(response) {
var responseParams =
- new {{interface.name}}_{{method.name}}_ResponseParams();
+ new {{interface_method_id}}_ResponseParams();
{%- for parameter in method.response_parameters %}
responseParams.{{parameter.name}} = response.{{parameter.name}};
{%- endfor %}
- var builder = new codec.MessageWithRequestIDBuilder(
- k{{interface.name}}_{{method.name}}_Name,
- codec.align({{interface.name}}_{{method.name}}_ResponseParams.encodedSize),
+{%- if method|method_passes_associated_kinds %}
+ var builder = new codec.MessageV2Builder(
+ k{{interface_method_id}}_Name,
+ codec.align({{interface_method_id}}_ResponseParams
+ .encodedSize),
codec.kMessageIsResponse, reader.requestID);
- builder.encodeStruct({{interface.name}}_{{method.name}}_ResponseParams,
+ builder.setPayload({{interface_method_id}}_ResponseParams,
responseParams);
+{%- else %}
+ var builder = new codec.MessageV1Builder(
+ k{{interface_method_id}}_Name,
+ codec.align({{interface_method_id}}_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct({{interface_method_id}}_ResponseParams,
+ responseParams);
+{%- endif %}
var message = builder.finish();
responder.accept(message);
});
@@ -129,13 +171,15 @@ params.{{parameter.name}}{% if not loop.last %}, {% endif -%}
var paramsClass = null;
switch (message.getName()) {
{%- for method in interface.methods %}
- case k{{interface.name}}_{{method.name}}_Name:
+{%- set interface_method_id =
+ interface.mojom_name ~ "_" ~ method.mojom_name %}
+ case k{{interface_method_id}}_Name:
{%- if method.response_parameters == None %}
if (!message.expectsResponse() && !message.isResponse())
- paramsClass = {{interface.name}}_{{method.name}}_Params;
+ paramsClass = {{interface_method_id}}_Params;
{%- else %}
if (message.expectsResponse())
- paramsClass = {{interface.name}}_{{method.name}}_Params;
+ paramsClass = {{interface_method_id}}_Params;
{%- endif %}
break;
{%- endfor %}
@@ -154,10 +198,12 @@ params.{{parameter.name}}{% if not loop.last %}, {% endif -%}
var paramsClass = null;
switch (message.getName()) {
{%- for method in interface.methods %}
+{%- set interface_method_id =
+ interface.mojom_name ~ "_" ~ method.mojom_name %}
{%- if method.response_parameters != None %}
- case k{{interface.name}}_{{method.name}}_Name:
+ case k{{interface_method_id}}_Name:
if (message.isResponse())
- paramsClass = {{interface.name}}_{{method.name}}_ResponseParams;
+ paramsClass = {{interface_method_id}}_ResponseParams;
break;
{%- endif %}
{%- endfor %}
@@ -169,7 +215,7 @@ params.{{parameter.name}}{% if not loop.last %}, {% endif -%}
}
var {{interface.name}} = {
- name: '{{namespace|replace(".","::")}}::{{interface.name}}',
+ name: '{{module.mojom_namespace}}.{{interface.mojom_name}}',
kVersion: {{interface.version}},
ptrClass: {{interface.name}}Ptr,
proxyClass: {{interface.name}}Proxy,
@@ -180,6 +226,18 @@ params.{{parameter.name}}{% if not loop.last %}, {% endif -%}
{%- else %}
validateResponse: null,
{%- endif %}
+{%- if generate_fuzzing %}
+ mojomId: '{{module.path}}',
+ fuzzMethods: {
+ {%- for method in interface.methods %}
+ {%- set interface_method_id =
+ interface.mojom_name ~ "_" ~ method.mojom_name %}
+ {{ method.name }}: {
+ params: {{interface_method_id}}_Params,
+ },
+ {%- endfor %}
+ },
+{%- endif %}
};
{#--- Interface Constants #}
{%- for constant in interface.constants %}
diff --git a/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl b/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl
index 3637b196ac..6cdacb2d95 100644
--- a/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl
+++ b/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-{%- if use_new_js_bindings %}
-
'use strict';
(function() {
@@ -14,57 +12,23 @@
}
mojo.internal.markMojomLoaded(mojomId);
- // TODO(yzshen): Define these aliases to minimize the differences between the
- // old/new modes. Remove them when the old mode goes away.
+{#- TODO(crbug.com/795977): Change the media router extension to not mess with
+ the mojo namespace, so that we can refer to mojo directly. #}
var bindings = mojo;
+ var associatedBindings = mojo;
var codec = mojo.internal;
var validator = mojo.internal;
-{%- for import in imports %}
+ var exports = mojo.internal.exposeNamespace('{{module.namespace}}');
+
+{%- for import in imports %}
var {{import.unique_name}} =
- mojo.internal.exposeNamespace('{{import.module.namespace}}');
+ mojo.internal.exposeNamespace('{{import.namespace}}');
if (mojo.config.autoLoadMojomDeps) {
mojo.internal.loadMojomIfNecessary(
- '{{import.module.path}}',
- new URL(
- '{{import.module|get_relative_path(module)}}.js',
- document.currentScript.src).href);
+ '{{import.path}}', '{{import|get_relative_url(module)}}.js');
}
-{%- endfor %}
+{%- endfor %}
{% include "module_definition.tmpl" %}
})();
-
-{%- else %}
-
-define("{{module.path}}", [
-{%- if module.path !=
- "mojo/public/interfaces/bindings/interface_control_messages.mojom" and
- module.path !=
- "mojo/public/interfaces/bindings/pipe_control_messages.mojom" %}
- "mojo/public/js/bindings",
-{%- endif %}
- "mojo/public/js/codec",
- "mojo/public/js/core",
- "mojo/public/js/validator",
-{%- for import in imports %}
- "{{import.module.path}}",
-{%- endfor %}
-], function(
-{%- if module.path !=
- "mojo/public/interfaces/bindings/interface_control_messages.mojom" and
- module.path !=
- "mojo/public/interfaces/bindings/pipe_control_messages.mojom" -%}
-bindings, {% endif -%}
-codec, core, validator
-{%- for import in imports -%}
- , {{import.unique_name}}
-{%- endfor -%}
-) {
-
-{%- include "module_definition.tmpl" %}
-
- return exports;
-});
-
-{%- endif %}
diff --git a/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl
index a119ee9480..a6736b5658 100644
--- a/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl
@@ -17,7 +17,7 @@
{#--- Union definitions #}
{%- from "union_definition.tmpl" import union_def %}
{%- for union in unions %}
-{{union_def(union)|indent(2)}}
+{{union_def(union, generate_fuzzing)|indent(2)}}
{%- endfor %}
{#--- Interface definitions #}
@@ -25,12 +25,6 @@
{%- include "interface_definition.tmpl" %}
{%- endfor %}
-{%- if use_new_js_bindings %}
- var exports = mojo.internal.exposeNamespace("{{module.namespace}}");
-{%- else %}
- var exports = {};
-{%- endif %}
-
{%- for constant in module.constants %}
exports.{{constant.name}} = {{constant.name}};
{%- endfor %}
@@ -46,4 +40,5 @@
{%- for interface in interfaces %}
exports.{{interface.name}} = {{interface.name}};
exports.{{interface.name}}Ptr = {{interface.name}}Ptr;
+ exports.{{interface.name}}AssociatedPtr = {{interface.name}}AssociatedPtr;
{%- endfor %}
diff --git a/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl
index e823e46155..b2ca3eac71 100644
--- a/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl
@@ -30,6 +30,54 @@
}
};
+{#--- Fuzzing #}
+{%- if generate_fuzzing %}
+{%- from "fuzzing.tmpl" import generate_or_mutate %}
+ {{struct.name}}.generate = function(generator_) {
+ var generated = new {{struct.name}};
+{%- for field in struct.fields %}
+ generated.{{field.name}} = {{generate_or_mutate('generator_', 'generate', field.kind)|indent(4)}};
+{%- endfor %}
+ return generated;
+ };
+
+ {{struct.name}}.prototype.mutate = function(mutator_) {
+{%- for field in struct.fields %}
+ if (mutator_.chooseMutateField()) {
+ this.{{field.name}} = {{generate_or_mutate('mutator_', 'mutate', field.kind, 'this.' ~ field.name)|indent(6)}};
+ }
+{%- endfor %}
+ return this;
+ };
+
+{%- from "fuzzing.tmpl" import get_handle_deps %}
+ {{struct.name}}.prototype.getHandleDeps = function() {
+ var handles = [];
+{%- for field in struct.fields %}
+{%- if field.kind|contains_handles_or_interfaces %}
+ if (this.{{field.name}} !== null) {
+ Array.prototype.push.apply(handles, {{get_handle_deps(field.kind, 'this.' ~ field.name)|indent(6)}});
+ }
+{%- endif %}
+{%- endfor %}
+ return handles;
+ };
+
+ {{struct.name}}.prototype.setHandles = function() {
+ this.setHandlesInternal_(arguments, 0);
+ };
+
+{%- from "fuzzing.tmpl" import set_handles %}
+ {{struct.name}}.prototype.setHandlesInternal_ = function(handles, idx) {
+{%- for field in struct.fields %}
+{%- if field.kind|contains_handles_or_interfaces %}
+ {{set_handles(field.kind, 'this.' ~ field.name)|indent(4)}};
+{%- endif %}
+{%- endfor %}
+ return idx;
+ };
+{%- endif %}
+
{#--- Validation #}
{{struct.name}}.validate = function(messageValidator, offset) {
@@ -58,8 +106,9 @@
{%- set offset = packed_field|field_offset %}
{%- set field = packed_field.field %}
{%- set name = struct.name ~ '.' ~ field.name %}
-{% if field|is_object_field or field|is_any_handle_or_interface_field or
- field|is_enum_field %}
+{% if field.kind|is_object_kind or
+ field.kind|is_any_handle_or_interface_kind or
+ field.kind|is_enum_kind %}
{% if packed_field.min_version > last_checked_version %}
{% set last_checked_version = packed_field.min_version %}
// version check {{name}}
@@ -84,7 +133,7 @@
var version = decoder.readUint32();
{%- for byte in struct.bytes %}
{%- if byte.packed_fields|length >= 1 and
- byte.packed_fields[0].field|is_bool_field %}
+ byte.packed_fields[0].field.kind|is_bool_kind %}
packed = decoder.readUint8();
{%- for packed_field in byte.packed_fields %}
val.{{packed_field.field.name}} = (packed >> {{packed_field.bit}}) & 1 ? true : false;
@@ -108,7 +157,7 @@
{%- for byte in struct.bytes %}
{%- if byte.packed_fields|length >= 1 and
- byte.packed_fields[0].field|is_bool_field %}
+ byte.packed_fields[0].field.kind|is_bool_kind %}
packed = 0;
{%- for packed_field in byte.packed_fields %}
packed |= (val.{{packed_field.field.name}} & 1) << {{packed_field.bit}}
diff --git a/mojo/public/tools/bindings/generators/js_templates/union_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/union_definition.tmpl
index 4823febeca..ad857b2c25 100644
--- a/mojo/public/tools/bindings/generators/js_templates/union_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/js_templates/union_definition.tmpl
@@ -1,4 +1,4 @@
-{%- macro union_def(union) %}
+{%- macro union_def(union, generate_fuzzing=false) %}
function {{union.name}}(value) {
this.initDefault_();
this.initValue_(value);
@@ -39,6 +39,66 @@ function {{union.name}}(value) {
this[keys[0]] = value[keys[0]];
}
+{%- if generate_fuzzing %}
+{%- from "fuzzing.tmpl" import generate_or_mutate %}
+{{union.name}}.generate = function(generator_) {
+ var generated = new {{union.name}};
+ var generators = [
+{%- for field in union.fields %}
+ {
+ field: "{{field.name}}",
+
+ generator: function() { return {{generate_or_mutate('generator_', 'generate', field.kind)|indent(6)}}; },
+ },
+{%- endfor %}
+ ];
+
+ var result = generator_.generateUnionField(generators);
+ generated[result.field] = result.value;
+ return generated;
+}
+
+{{union.name}}.prototype.mutate = function(mutator_) {
+ var mutators = [
+{%- for field in union.fields %}
+ {
+ field: "{{field.name}}",
+
+ mutator: function(val) { return {{generate_or_mutate('mutator_', 'mutate', field.kind, 'val.' ~ field.name)|indent(6)}}; },
+ },
+{%- endfor %}
+ ];
+
+ var result = mutator_.mutateUnionField(this, mutators);
+ this[result.field] = result.value;
+ return this;
+}
+
+{%- from "fuzzing.tmpl" import get_handle_deps %}
+{{union.name}}.prototype.getHandleDeps = function() {
+{%- for field in union.fields %}
+{%- if field.kind|contains_handles_or_interfaces %}
+ if (this.$tag == {{union.name}}.Tags.{{field.name}}) {
+ return {{get_handle_deps(field.kind, 'this.' ~ field.name)}};
+ }
+{%- endif %}
+{%- endfor %}
+ return [];
+}
+
+{%- from "fuzzing.tmpl" import set_handles %}
+{{union.name}}.prototype.setHandles = function() {
+{%- for field in union.fields %}
+{%- if field.kind|contains_handles_or_interfaces %}
+ if (this.$tag == {{union.name}}.Tags.{{field.name}}) {
+ return {{set_handles(field.kind, 'this.' ~ field.name)}};
+ }
+{%- endif %}
+{%- endfor %}
+ return [];
+}
+{%- endif %}
+
{%- for field in union.fields %}
Object.defineProperty({{union.name}}.prototype, "{{field.name}}", {
get: function() {
@@ -89,7 +149,7 @@ Object.defineProperty({{union.name}}.prototype, "{{field.name}}", {
switch (val.$tag) {
{%- for field in union.fields %}
case {{union.name}}.Tags.{{field.name}}:
-{%- if field|is_bool_field %}
+{%- if field.kind|is_bool_kind %}
encoder.writeUint8(val.{{field.name}} ? 1 : 0);
{%- else %}
encoder.{{field.kind|union_encode_snippet}}val.{{field.name}});
@@ -115,7 +175,7 @@ Object.defineProperty({{union.name}}.prototype, "{{field.name}}", {
switch (tag) {
{%- for field in union.fields %}
case {{union.name}}.Tags.{{field.name}}:
-{%- if field|is_bool_field %}
+{%- if field.kind|is_bool_kind %}
result.{{field.name}} = decoder.readUint8() ? true : false;
{%- else %}
result.{{field.name}} = decoder.{{field.kind|union_decode_snippet}};
diff --git a/mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl b/mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl
index d4e15a7859..0e9b89315c 100644
--- a/mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl
+++ b/mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl
@@ -4,35 +4,43 @@ if (err !== validator.validationError.NONE)
{%- endmacro %}
{%- macro _validate_field(field, offset, name) %}
-{%- if field|is_string_pointer_field %}
+{%- if field.kind|is_string_kind %}
// validate {{name}}
err = messageValidator.validateStringPointer({{offset}}, {{field|validate_nullable_params}})
{{_check_err()}}
-{%- elif field|is_array_pointer_field %}
+{%- elif field.kind|is_array_kind %}
// validate {{name}}
err = messageValidator.validateArrayPointer({{offset}}, {{field|validate_array_params}});
{{_check_err()}}
-{%- elif field|is_struct_pointer_field %}
+{%- elif field.kind|is_struct_kind %}
// validate {{name}}
err = messageValidator.validateStructPointer({{offset}}, {{field|validate_struct_params}});
{{_check_err()}}
-{%- elif field|is_map_pointer_field %}
+{%- elif field.kind|is_map_kind %}
// validate {{name}}
err = messageValidator.validateMapPointer({{offset}}, {{field|validate_map_params}});
{{_check_err()}}
-{%- elif field|is_interface_field %}
+{%- elif field.kind|is_interface_kind %}
// validate {{name}}
err = messageValidator.validateInterface({{offset}}, {{field|validate_nullable_params}});
{{_check_err()}}
-{%- elif field|is_interface_request_field %}
+{%- elif field.kind|is_interface_request_kind %}
// validate {{name}}
err = messageValidator.validateInterfaceRequest({{offset}}, {{field|validate_nullable_params}})
{{_check_err()}}
-{%- elif field|is_handle_field %}
+{%- elif field.kind|is_associated_interface_kind %}
+// validate {{name}}
+err = messageValidator.validateAssociatedInterface({{offset}}, {{field|validate_nullable_params}});
+{{_check_err()}}
+{%- elif field.kind|is_associated_interface_request_kind %}
+// validate {{name}}
+err = messageValidator.validateAssociatedInterfaceRequest({{offset}}, {{field|validate_nullable_params}})
+{{_check_err()}}
+{%- elif field.kind|is_any_handle_kind %}
// validate {{name}}
err = messageValidator.validateHandle({{offset}}, {{field|validate_nullable_params}})
{{_check_err()}}
-{%- elif field|is_enum_field %}
+{%- elif field.kind|is_enum_kind %}
// validate {{name}}
err = messageValidator.validateEnum({{offset}}, {{field|validate_enum_params}});
{{_check_err()}}
@@ -40,17 +48,17 @@ err = messageValidator.validateEnum({{offset}}, {{field|validate_enum_params}});
{%- endmacro %}
{%- macro validate_struct_field(field, offset, name) %}
-{%- if field|is_union_field %}
+{%- if field.kind|is_union_kind %}
// validate {{name}}
err = messageValidator.validateUnion({{offset}}, {{field|validate_union_params}});
{{_check_err()}}
-{%- else %}
+{%- else -%}
{{_validate_field(field, offset, name)}}
{%- endif %}
{%- endmacro %}
{%- macro validate_union_field(field, offset, name) %}
-{%- if field|is_union_field %}
+{%- if field.kind|is_union_kind %}
// validate {{name}}
err = messageValidator.validateNestedUnion({{offset}}, {{field|validate_union_params}});
{{_check_err()}}
diff --git a/mojo/public/tools/bindings/generators/mojom_cpp_generator.py b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
index 38d222b136..ceded69e1f 100644
--- a/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
+++ b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py
@@ -32,15 +32,6 @@ _kind_to_cpp_literal_suffix = {
mojom.UINT64: "ULL",
}
-# TODO(rockot): Get rid of these globals. This requires some refactoring of the
-# generator library code so that filters can use the generator as context.
-_current_typemap = {}
-_for_blink = False
-# TODO(rockot, yzshen): The variant handling is kind of a hack currently. Make
-# it right.
-_variant = None
-_export_attribute = None
-
class _NameFormatter(object):
"""A formatter for the names of kinds or values."""
@@ -50,7 +41,7 @@ class _NameFormatter(object):
self._variant = variant
def Format(self, separator, prefixed=False, internal=False,
- include_variant=False, add_same_module_namespaces=False,
+ include_variant=False, omit_namespace_for_module=None,
flatten_nested_kind=False):
"""Formats the name according to the given configuration.
@@ -60,15 +51,15 @@ class _NameFormatter(object):
internal: Returns the name in the "internal" namespace.
include_variant: Whether to include variant as namespace. If |internal| is
True, then this flag is ignored and variant is not included.
- add_same_module_namespaces: Includes all namespaces even if the token is
- from the same module as the current mojom file.
+ omit_namespace_for_module: If the token is from the specified module,
+ don't add the namespaces of the module to the name.
flatten_nested_kind: It is allowed to define enums inside structs and
interfaces. If this flag is set to True, this method concatenates the
parent kind and the nested kind with '_', instead of treating the
parent kind as a scope."""
parts = []
- if self._ShouldIncludeNamespace(add_same_module_namespaces):
+ if self._ShouldIncludeNamespace(omit_namespace_for_module):
if prefixed:
parts.append("")
parts.extend(self._GetNamespace())
@@ -77,16 +68,16 @@ class _NameFormatter(object):
parts.extend(self._GetName(internal, flatten_nested_kind))
return separator.join(parts)
- def FormatForCpp(self, add_same_module_namespaces=False, internal=False,
+ def FormatForCpp(self, omit_namespace_for_module=None, internal=False,
flatten_nested_kind=False):
return self.Format(
"::", prefixed=True,
- add_same_module_namespaces=add_same_module_namespaces,
+ omit_namespace_for_module=omit_namespace_for_module,
internal=internal, include_variant=True,
flatten_nested_kind=flatten_nested_kind)
def FormatForMojom(self):
- return self.Format(".", add_same_module_namespaces=True)
+ return self.Format(".")
def _MapKindName(self, token, internal):
if not internal:
@@ -119,105 +110,32 @@ class _NameFormatter(object):
name_parts.append(self._MapKindName(self._token, internal))
return name_parts
- def _ShouldIncludeNamespace(self, add_same_module_namespaces):
- return add_same_module_namespaces or self._token.imported_from
+ def _ShouldIncludeNamespace(self, omit_namespace_for_module):
+ return self._token.module and (
+ not omit_namespace_for_module or
+ self._token.module.path != omit_namespace_for_module.path)
def _GetNamespace(self):
- if self._token.imported_from:
- return NamespaceToArray(self._token.imported_from["namespace"])
- elif hasattr(self._token, "module"):
+ if self._token.module:
return NamespaceToArray(self._token.module.namespace)
- return []
-
-
-def ConstantValue(constant):
- return ExpressionToText(constant.value, kind=constant.kind)
-# TODO(yzshen): Revisit the default value feature. It was designed prior to
-# custom type mapping.
-def DefaultValue(field):
- if field.default:
- if mojom.IsStructKind(field.kind):
- assert field.default == "default"
- if not IsTypemappedKind(field.kind):
- return "%s::New()" % GetNameForKind(field.kind)
- return ExpressionToText(field.default, kind=field.kind)
- return ""
def NamespaceToArray(namespace):
return namespace.split(".") if namespace else []
-def GetNameForKind(kind, internal=False, flatten_nested_kind=False,
- add_same_module_namespaces=False):
- return _NameFormatter(kind, _variant).FormatForCpp(
- internal=internal, flatten_nested_kind=flatten_nested_kind,
- add_same_module_namespaces=add_same_module_namespaces)
-
-def GetQualifiedNameForKind(kind, internal=False, flatten_nested_kind=False,
- include_variant=True):
- return _NameFormatter(
- kind, _variant if include_variant else None).FormatForCpp(
- internal=internal, add_same_module_namespaces=True,
- flatten_nested_kind=flatten_nested_kind)
-
def GetWtfHashFnNameForEnum(enum):
- return _NameFormatter(
- enum, None).Format("_", internal=True, add_same_module_namespaces=True,
- flatten_nested_kind=True) + "HashFn"
-
-
-def GetFullMojomNameForKind(kind):
- return _NameFormatter(kind, _variant).FormatForMojom()
+ return _NameFormatter(enum, None).Format("_", internal=True,
+ flatten_nested_kind=True) + "HashFn"
-def IsTypemappedKind(kind):
- return hasattr(kind, "name") and \
- GetFullMojomNameForKind(kind) in _current_typemap
def IsNativeOnlyKind(kind):
return (mojom.IsStructKind(kind) or mojom.IsEnumKind(kind)) and \
kind.native_only
-def IsHashableKind(kind):
- """Check if the kind can be hashed.
-
- Args:
- kind: {Kind} The kind to check.
-
- Returns:
- {bool} True if a value of this kind can be hashed.
- """
- checked = set()
- def Check(kind):
- if kind.spec in checked:
- return True
- checked.add(kind.spec)
- if mojom.IsNullableKind(kind):
- return False
- elif mojom.IsStructKind(kind):
- if (IsTypemappedKind(kind) and
- not _current_typemap[GetFullMojomNameForKind(kind)]["hashable"]):
- return False
- return all(Check(field.kind) for field in kind.fields)
- elif mojom.IsEnumKind(kind):
- return not IsTypemappedKind(kind) or _current_typemap[
- GetFullMojomNameForKind(kind)]["hashable"]
- elif mojom.IsUnionKind(kind):
- return all(Check(field.kind) for field in kind.fields)
- elif mojom.IsAnyHandleKind(kind):
- return False
- elif mojom.IsAnyInterfaceKind(kind):
- return False
- # TODO(tibell): Arrays and maps could be made hashable. We just don't have a
- # use case yet.
- elif mojom.IsArrayKind(kind):
- return False
- elif mojom.IsMapKind(kind):
- return False
- else:
- return True
- return Check(kind)
+def UseCustomSerializer(kind):
+ return mojom.IsStructKind(kind) and kind.custom_serializer
def AllEnumValues(enum):
@@ -232,252 +150,9 @@ def AllEnumValues(enum):
return set(field.numeric_value for field in enum.fields)
-def GetNativeTypeName(typemapped_kind):
- return _current_typemap[GetFullMojomNameForKind(typemapped_kind)]["typename"]
-
def GetCppPodType(kind):
return _kind_to_cpp_type[kind]
-def FormatConstantDeclaration(constant, nested=False):
- if mojom.IsStringKind(constant.kind):
- if nested:
- return "const char %s[]" % constant.name
- return "%sextern const char %s[]" % \
- ((_export_attribute + " ") if _export_attribute else "", constant.name)
- return "constexpr %s %s = %s" % (GetCppPodType(constant.kind), constant.name,
- ConstantValue(constant))
-
-def GetCppWrapperType(kind, add_same_module_namespaces=False):
- def _AddOptional(type_name):
- pattern = "WTF::Optional<%s>" if _for_blink else "base::Optional<%s>"
- return pattern % type_name
-
- if IsTypemappedKind(kind):
- type_name = GetNativeTypeName(kind)
- if (mojom.IsNullableKind(kind) and
- not _current_typemap[GetFullMojomNameForKind(kind)][
- "nullable_is_same_type"]):
- type_name = _AddOptional(type_name)
- return type_name
- if mojom.IsEnumKind(kind):
- return GetNameForKind(
- kind, add_same_module_namespaces=add_same_module_namespaces)
- if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):
- return "%sPtr" % GetNameForKind(
- kind, add_same_module_namespaces=add_same_module_namespaces)
- if mojom.IsArrayKind(kind):
- pattern = "WTF::Vector<%s>" if _for_blink else "std::vector<%s>"
- if mojom.IsNullableKind(kind):
- pattern = _AddOptional(pattern)
- return pattern % GetCppWrapperType(
- kind.kind, add_same_module_namespaces=add_same_module_namespaces)
- if mojom.IsMapKind(kind):
- pattern = ("WTF::HashMap<%s, %s>" if _for_blink else
- "std::unordered_map<%s, %s>")
- if mojom.IsNullableKind(kind):
- pattern = _AddOptional(pattern)
- return pattern % (
- GetCppWrapperType(
- kind.key_kind,
- add_same_module_namespaces=add_same_module_namespaces),
- GetCppWrapperType(
- kind.value_kind,
- add_same_module_namespaces=add_same_module_namespaces))
- if mojom.IsInterfaceKind(kind):
- return "%sPtr" % GetNameForKind(
- kind, add_same_module_namespaces=add_same_module_namespaces)
- if mojom.IsInterfaceRequestKind(kind):
- return "%sRequest" % GetNameForKind(
- kind.kind, add_same_module_namespaces=add_same_module_namespaces)
- if mojom.IsAssociatedInterfaceKind(kind):
- return "%sAssociatedPtrInfo" % GetNameForKind(
- kind.kind, add_same_module_namespaces=add_same_module_namespaces)
- if mojom.IsAssociatedInterfaceRequestKind(kind):
- return "%sAssociatedRequest" % GetNameForKind(
- kind.kind, add_same_module_namespaces=add_same_module_namespaces)
- if mojom.IsStringKind(kind):
- if _for_blink:
- return "WTF::String"
- type_name = "std::string"
- return _AddOptional(type_name) if mojom.IsNullableKind(kind) else type_name
- if mojom.IsGenericHandleKind(kind):
- return "mojo::ScopedHandle"
- if mojom.IsDataPipeConsumerKind(kind):
- return "mojo::ScopedDataPipeConsumerHandle"
- if mojom.IsDataPipeProducerKind(kind):
- return "mojo::ScopedDataPipeProducerHandle"
- if mojom.IsMessagePipeKind(kind):
- return "mojo::ScopedMessagePipeHandle"
- if mojom.IsSharedBufferKind(kind):
- return "mojo::ScopedSharedBufferHandle"
- if not kind in _kind_to_cpp_type:
- raise Exception("Unrecognized kind %s" % kind.spec)
- return _kind_to_cpp_type[kind]
-
-def IsMoveOnlyKind(kind):
- if IsTypemappedKind(kind):
- if mojom.IsEnumKind(kind):
- return False
- return _current_typemap[GetFullMojomNameForKind(kind)]["move_only"]
- if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):
- return True
- if mojom.IsArrayKind(kind):
- return IsMoveOnlyKind(kind.kind)
- if mojom.IsMapKind(kind):
- return IsMoveOnlyKind(kind.value_kind)
- if mojom.IsAnyHandleOrInterfaceKind(kind):
- return True
- return False
-
-def IsCopyablePassByValue(kind):
- if not IsTypemappedKind(kind):
- return False
- return _current_typemap[GetFullMojomNameForKind(kind)][
- "copyable_pass_by_value"]
-
-def ShouldPassParamByValue(kind):
- return ((not mojom.IsReferenceKind(kind)) or IsMoveOnlyKind(kind) or
- IsCopyablePassByValue(kind))
-
-def GetCppWrapperParamType(kind):
- cpp_wrapper_type = GetCppWrapperType(kind)
- return (cpp_wrapper_type if ShouldPassParamByValue(kind)
- else "const %s&" % cpp_wrapper_type)
-
-def GetCppFieldType(kind):
- if mojom.IsStructKind(kind):
- return ("mojo::internal::Pointer<%s>" %
- GetNameForKind(kind, internal=True))
- if mojom.IsUnionKind(kind):
- return "%s" % GetNameForKind(kind, internal=True)
- if mojom.IsArrayKind(kind):
- return ("mojo::internal::Pointer<mojo::internal::Array_Data<%s>>" %
- GetCppFieldType(kind.kind))
- if mojom.IsMapKind(kind):
- return ("mojo::internal::Pointer<mojo::internal::Map_Data<%s, %s>>" %
- (GetCppFieldType(kind.key_kind), GetCppFieldType(kind.value_kind)))
- if mojom.IsInterfaceKind(kind):
- return "mojo::internal::Interface_Data"
- if mojom.IsInterfaceRequestKind(kind):
- return "mojo::internal::Handle_Data"
- if mojom.IsAssociatedInterfaceKind(kind):
- return "mojo::internal::AssociatedInterface_Data"
- if mojom.IsAssociatedInterfaceRequestKind(kind):
- return "mojo::internal::AssociatedEndpointHandle_Data"
- if mojom.IsEnumKind(kind):
- return "int32_t"
- if mojom.IsStringKind(kind):
- return "mojo::internal::Pointer<mojo::internal::String_Data>"
- if mojom.IsAnyHandleKind(kind):
- return "mojo::internal::Handle_Data"
- return _kind_to_cpp_type[kind]
-
-def GetCppUnionFieldType(kind):
- if mojom.IsUnionKind(kind):
- return ("mojo::internal::Pointer<%s>" % GetNameForKind(kind, internal=True))
- return GetCppFieldType(kind)
-
-def GetUnionGetterReturnType(kind):
- if mojom.IsReferenceKind(kind):
- return "%s&" % GetCppWrapperType(kind)
- return GetCppWrapperType(kind)
-
-def GetUnionTraitGetterReturnType(kind):
- """Get field type used in UnionTraits template specialization.
-
- The type may be qualified as UnionTraits specializations live outside the
- namespace where e.g. structs are defined.
-
- Args:
- kind: {Kind} The type of the field.
-
- Returns:
- {str} The C++ type to use for the field.
- """
- if mojom.IsReferenceKind(kind):
- return "%s&" % GetCppWrapperType(kind, add_same_module_namespaces=True)
- return GetCppWrapperType(kind, add_same_module_namespaces=True)
-
-def GetCppDataViewType(kind, qualified=False):
- def _GetName(input_kind):
- return _NameFormatter(input_kind, None).FormatForCpp(
- add_same_module_namespaces=qualified, flatten_nested_kind=True)
-
- if mojom.IsEnumKind(kind):
- return _GetName(kind)
- if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):
- return "%sDataView" % _GetName(kind)
- if mojom.IsArrayKind(kind):
- return "mojo::ArrayDataView<%s>" % GetCppDataViewType(kind.kind, qualified)
- if mojom.IsMapKind(kind):
- return ("mojo::MapDataView<%s, %s>" % (
- GetCppDataViewType(kind.key_kind, qualified),
- GetCppDataViewType(kind.value_kind, qualified)))
- if mojom.IsStringKind(kind):
- return "mojo::StringDataView"
- if mojom.IsInterfaceKind(kind):
- return "%sPtrDataView" % _GetName(kind)
- if mojom.IsInterfaceRequestKind(kind):
- return "%sRequestDataView" % _GetName(kind.kind)
- if mojom.IsAssociatedInterfaceKind(kind):
- return "%sAssociatedPtrInfoDataView" % _GetName(kind.kind)
- if mojom.IsAssociatedInterfaceRequestKind(kind):
- return "%sAssociatedRequestDataView" % _GetName(kind.kind)
- if mojom.IsGenericHandleKind(kind):
- return "mojo::ScopedHandle"
- if mojom.IsDataPipeConsumerKind(kind):
- return "mojo::ScopedDataPipeConsumerHandle"
- if mojom.IsDataPipeProducerKind(kind):
- return "mojo::ScopedDataPipeProducerHandle"
- if mojom.IsMessagePipeKind(kind):
- return "mojo::ScopedMessagePipeHandle"
- if mojom.IsSharedBufferKind(kind):
- return "mojo::ScopedSharedBufferHandle"
- return _kind_to_cpp_type[kind]
-
-def GetUnmappedTypeForSerializer(kind):
- return GetCppDataViewType(kind, qualified=True)
-
-def TranslateConstants(token, kind):
- if isinstance(token, mojom.NamedValue):
- return GetNameForKind(token, flatten_nested_kind=True)
-
- if isinstance(token, mojom.BuiltinValue):
- if token.value == "double.INFINITY":
- return "std::numeric_limits<double>::infinity()"
- if token.value == "float.INFINITY":
- return "std::numeric_limits<float>::infinity()"
- if token.value == "double.NEGATIVE_INFINITY":
- return "-std::numeric_limits<double>::infinity()"
- if token.value == "float.NEGATIVE_INFINITY":
- return "-std::numeric_limits<float>::infinity()"
- if token.value == "double.NAN":
- return "std::numeric_limits<double>::quiet_NaN()"
- if token.value == "float.NAN":
- return "std::numeric_limits<float>::quiet_NaN()"
-
- if (kind is not None and mojom.IsFloatKind(kind)):
- return token if token.isdigit() else token + "f";
-
- # Per C++11, 2.14.2, the type of an integer literal is the first of the
- # corresponding list in Table 6 in which its value can be represented. In this
- # case, the list for decimal constants with no suffix is:
- # int, long int, long long int
- # The standard considers a program ill-formed if it contains an integer
- # literal that cannot be represented by any of the allowed types.
- #
- # As it turns out, MSVC doesn't bother trying to fall back to long long int,
- # so the integral constant -2147483648 causes it grief: it decides to
- # represent 2147483648 as an unsigned integer, and then warns that the unary
- # minus operator doesn't make sense on unsigned types. Doh!
- if kind == mojom.INT32 and token == "-2147483648":
- return "(-%d - 1) /* %s */" % (
- 2**31 - 1, "Workaround for MSVC bug; see https://crbug.com/445618")
-
- return "%s%s" % (token, _kind_to_cpp_literal_suffix.get(kind, ""))
-
-def ExpressionToText(value, kind=None):
- return TranslateConstants(value, kind)
def RequiresContextForDataView(kind):
for field in kind.fields:
@@ -485,6 +160,7 @@ def RequiresContextForDataView(kind):
return True
return False
+
def ShouldInlineStruct(struct):
# TODO(darin): Base this on the size of the wrapper class.
if len(struct.fields) > 4:
@@ -494,11 +170,6 @@ def ShouldInlineStruct(struct):
return False
return True
-def ContainsMoveOnlyMembers(struct):
- for field in struct.fields:
- if IsMoveOnlyKind(field.kind):
- return True
- return False
def ShouldInlineUnion(union):
return not any(
@@ -528,134 +199,11 @@ class StructConstructor(object):
yield (field, field in self._params)
-def GetStructConstructors(struct):
- """Returns a list of constructors for a struct.
-
- Params:
- struct: {Struct} The struct to return constructors for.
-
- Returns:
- {[StructConstructor]} A list of StructConstructors that should be generated
- for |struct|.
- """
- if not mojom.IsStructKind(struct):
- raise TypeError
- # Types that are neither copyable nor movable can't be passed to a struct
- # constructor so only generate a default constructor.
- if any(IsTypemappedKind(field.kind) and _current_typemap[
- GetFullMojomNameForKind(field.kind)]["non_copyable_non_movable"]
- for field in struct.fields):
- return [StructConstructor(struct.fields, [])]
-
- param_counts = [0]
- for version in struct.versions:
- if param_counts[-1] != version.num_fields:
- param_counts.append(version.num_fields)
-
- ordinal_fields = sorted(struct.fields, key=lambda field: field.ordinal)
- return (StructConstructor(struct.fields, ordinal_fields[:param_count])
- for param_count in param_counts)
-
-
-def GetContainerValidateParamsCtorArgs(kind):
- if mojom.IsStringKind(kind):
- expected_num_elements = 0
- element_is_nullable = False
- key_validate_params = "nullptr"
- element_validate_params = "nullptr"
- enum_validate_func = "nullptr"
- elif mojom.IsMapKind(kind):
- expected_num_elements = 0
- element_is_nullable = False
- key_validate_params = GetNewContainerValidateParams(mojom.Array(
- kind=kind.key_kind))
- element_validate_params = GetNewContainerValidateParams(mojom.Array(
- kind=kind.value_kind))
- enum_validate_func = "nullptr"
- else: # mojom.IsArrayKind(kind)
- expected_num_elements = generator.ExpectedArraySize(kind) or 0
- element_is_nullable = mojom.IsNullableKind(kind.kind)
- key_validate_params = "nullptr"
- element_validate_params = GetNewContainerValidateParams(kind.kind)
- if mojom.IsEnumKind(kind.kind):
- enum_validate_func = ("%s::Validate" %
- GetQualifiedNameForKind(kind.kind, internal=True,
- flatten_nested_kind=True))
- else:
- enum_validate_func = "nullptr"
-
- if enum_validate_func == "nullptr":
- if key_validate_params == "nullptr":
- return "%d, %s, %s" % (expected_num_elements,
- "true" if element_is_nullable else "false",
- element_validate_params)
- else:
- return "%s, %s" % (key_validate_params, element_validate_params)
- else:
- return "%d, %s" % (expected_num_elements, enum_validate_func)
-
-def GetNewContainerValidateParams(kind):
- if (not mojom.IsArrayKind(kind) and not mojom.IsMapKind(kind) and
- not mojom.IsStringKind(kind)):
- return "nullptr"
-
- return "new mojo::internal::ContainerValidateParams(%s)" % (
- GetContainerValidateParamsCtorArgs(kind))
-
class Generator(generator.Generator):
+ def __init__(self, *args, **kwargs):
+ super(Generator, self).__init__(*args, **kwargs)
- cpp_filters = {
- "all_enum_values": AllEnumValues,
- "constant_value": ConstantValue,
- "contains_handles_or_interfaces": mojom.ContainsHandlesOrInterfaces,
- "contains_move_only_members": ContainsMoveOnlyMembers,
- "cpp_wrapper_param_type": GetCppWrapperParamType,
- "cpp_data_view_type": GetCppDataViewType,
- "cpp_field_type": GetCppFieldType,
- "cpp_union_field_type": GetCppUnionFieldType,
- "cpp_pod_type": GetCppPodType,
- "cpp_union_getter_return_type": GetUnionGetterReturnType,
- "cpp_union_trait_getter_return_type": GetUnionTraitGetterReturnType,
- "cpp_wrapper_type": GetCppWrapperType,
- "default_value": DefaultValue,
- "expression_to_text": ExpressionToText,
- "format_constant_declaration": FormatConstantDeclaration,
- "get_container_validate_params_ctor_args":
- GetContainerValidateParamsCtorArgs,
- "get_name_for_kind": GetNameForKind,
- "get_pad": pack.GetPad,
- "get_qualified_name_for_kind": GetQualifiedNameForKind,
- "has_callbacks": mojom.HasCallbacks,
- "has_sync_methods": mojom.HasSyncMethods,
- "requires_context_for_data_view": RequiresContextForDataView,
- "should_inline": ShouldInlineStruct,
- "should_inline_union": ShouldInlineUnion,
- "is_array_kind": mojom.IsArrayKind,
- "is_enum_kind": mojom.IsEnumKind,
- "is_integral_kind": mojom.IsIntegralKind,
- "is_native_only_kind": IsNativeOnlyKind,
- "is_any_handle_kind": mojom.IsAnyHandleKind,
- "is_any_interface_kind": mojom.IsAnyInterfaceKind,
- "is_any_handle_or_interface_kind": mojom.IsAnyHandleOrInterfaceKind,
- "is_associated_kind": mojom.IsAssociatedKind,
- "is_hashable": IsHashableKind,
- "is_map_kind": mojom.IsMapKind,
- "is_nullable_kind": mojom.IsNullableKind,
- "is_object_kind": mojom.IsObjectKind,
- "is_reference_kind": mojom.IsReferenceKind,
- "is_string_kind": mojom.IsStringKind,
- "is_struct_kind": mojom.IsStructKind,
- "is_typemapped_kind": IsTypemappedKind,
- "is_union_kind": mojom.IsUnionKind,
- "passes_associated_kinds": mojom.PassesAssociatedKinds,
- "struct_constructors": GetStructConstructors,
- "stylize_method": generator.StudlyCapsToCamel,
- "under_to_camel": generator.UnderToCamel,
- "unmapped_type_for_serializer": GetUnmappedTypeForSerializer,
- "wtf_hash_fn_name_for_enum": GetWtfHashFnNameForEnum,
- }
-
- def GetExtraTraitsHeaders(self):
+ def _GetExtraTraitsHeaders(self):
extra_headers = set()
for typemap in self._GetAllUsedTypemaps():
extra_headers.update(typemap.get("traits_headers", []))
@@ -684,12 +232,12 @@ class Generator(generator.Generator):
AddKind(kind.key_kind)
AddKind(kind.value_kind)
else:
- name = GetFullMojomNameForKind(kind)
+ name = self._GetFullMojomNameForKind(kind)
if name in seen_types:
return
seen_types.add(name)
- typemap = _current_typemap.get(name, None)
+ typemap = self.typemap.get(name, None)
if typemap:
used_typemaps.append(typemap)
if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):
@@ -707,14 +255,14 @@ class Generator(generator.Generator):
return used_typemaps
- def GetExtraPublicHeaders(self):
+ def _GetExtraPublicHeaders(self):
all_enums = list(self.module.enums)
for struct in self.module.structs:
all_enums.extend(struct.enums)
for interface in self.module.interfaces:
all_enums.extend(interface.enums)
- types = set(GetFullMojomNameForKind(typename)
+ types = set(self._GetFullMojomNameForKind(typename)
for typename in
self.module.structs + all_enums + self.module.unions)
headers = set()
@@ -733,86 +281,600 @@ class Generator(generator.Generator):
for param in method.parameters + (method.response_parameters or []):
yield param.kind
- def GetJinjaExports(self):
- structs = self.GetStructs()
- interfaces = self.GetInterfaces()
+ def _GetJinjaExports(self):
all_enums = list(self.module.enums)
- for struct in structs:
+ for struct in self.module.structs:
all_enums.extend(struct.enums)
- for interface in interfaces:
+ for interface in self.module.interfaces:
all_enums.extend(interface.enums)
return {
+ "all_enums": all_enums,
+ "disallow_interfaces": self.disallow_interfaces,
+ "disallow_native_types": self.disallow_native_types,
+ "enums": self.module.enums,
+ "export_attribute": self.export_attribute,
+ "export_header": self.export_header,
+ "extra_public_headers": self._GetExtraPublicHeaders(),
+ "extra_traits_headers": self._GetExtraTraitsHeaders(),
+ "for_blink": self.for_blink,
+ "imports": self.module.imports,
+ "interfaces": self.module.interfaces,
+ "kinds": self.module.kinds,
"module": self.module,
"namespace": self.module.namespace,
"namespaces_as_array": NamespaceToArray(self.module.namespace),
- "imports": self.module.imports,
- "kinds": self.module.kinds,
- "enums": self.module.enums,
- "all_enums": all_enums,
- "structs": structs,
- "unions": self.GetUnions(),
- "interfaces": interfaces,
- "variant": self.variant,
- "extra_traits_headers": self.GetExtraTraitsHeaders(),
- "extra_public_headers": self.GetExtraPublicHeaders(),
- "for_blink": self.for_blink,
+ "structs": self.module.structs,
+ "support_lazy_serialization": self.support_lazy_serialization,
+ "unions": self.module.unions,
"use_once_callback": self.use_once_callback,
- "export_attribute": self.export_attribute,
- "export_header": self.export_header,
+ "variant": self.variant,
}
@staticmethod
def GetTemplatePrefix():
return "cpp_templates"
- @classmethod
- def GetFilters(cls):
- return cls.cpp_filters
+ def GetFilters(self):
+ cpp_filters = {
+ "all_enum_values": AllEnumValues,
+ "constant_value": self._ConstantValue,
+ "contains_handles_or_interfaces": mojom.ContainsHandlesOrInterfaces,
+ "contains_move_only_members": self._ContainsMoveOnlyMembers,
+ "cpp_data_view_type": self._GetCppDataViewType,
+ "cpp_field_type": self._GetCppFieldType,
+ "cpp_union_field_type": self._GetCppUnionFieldType,
+ "cpp_pod_type": GetCppPodType,
+ "cpp_union_getter_return_type": self._GetUnionGetterReturnType,
+ "cpp_union_trait_getter_return_type": self._GetUnionTraitGetterReturnType,
+ "cpp_wrapper_call_type": self._GetCppWrapperCallType,
+ "cpp_wrapper_param_type": self._GetCppWrapperParamType,
+ "cpp_wrapper_param_type_new": self._GetCppWrapperParamTypeNew,
+ "cpp_wrapper_type": self._GetCppWrapperType,
+ "default_value": self._DefaultValue,
+ "expression_to_text": self._ExpressionToText,
+ "format_constant_declaration": self._FormatConstantDeclaration,
+ "get_container_validate_params_ctor_args":
+ self._GetContainerValidateParamsCtorArgs,
+ "get_name_for_kind": self._GetNameForKind,
+ "get_pad": pack.GetPad,
+ "get_qualified_name_for_kind": self._GetQualifiedNameForKind,
+ "has_callbacks": mojom.HasCallbacks,
+ "has_sync_methods": mojom.HasSyncMethods,
+ "method_supports_lazy_serialization":
+ self._MethodSupportsLazySerialization,
+ "requires_context_for_data_view": RequiresContextForDataView,
+ "should_inline": ShouldInlineStruct,
+ "should_inline_union": ShouldInlineUnion,
+ "is_array_kind": mojom.IsArrayKind,
+ "is_enum_kind": mojom.IsEnumKind,
+ "is_integral_kind": mojom.IsIntegralKind,
+ "is_interface_kind": mojom.IsInterfaceKind,
+ "is_native_only_kind": IsNativeOnlyKind,
+ "is_any_handle_kind": mojom.IsAnyHandleKind,
+ "is_any_interface_kind": mojom.IsAnyInterfaceKind,
+ "is_any_handle_or_interface_kind": mojom.IsAnyHandleOrInterfaceKind,
+ "is_associated_kind": mojom.IsAssociatedKind,
+ "is_hashable": self._IsHashableKind,
+ "is_map_kind": mojom.IsMapKind,
+ "is_nullable_kind": mojom.IsNullableKind,
+ "is_object_kind": mojom.IsObjectKind,
+ "is_reference_kind": mojom.IsReferenceKind,
+ "is_string_kind": mojom.IsStringKind,
+ "is_struct_kind": mojom.IsStructKind,
+ "is_typemapped_kind": self._IsTypemappedKind,
+ "is_union_kind": mojom.IsUnionKind,
+ "passes_associated_kinds": mojom.PassesAssociatedKinds,
+ "struct_constructors": self._GetStructConstructors,
+ "under_to_camel": generator.ToCamel,
+ "unmapped_type_for_serializer": self._GetUnmappedTypeForSerializer,
+ "use_custom_serializer": UseCustomSerializer,
+ "wtf_hash_fn_name_for_enum": GetWtfHashFnNameForEnum,
+ }
+ return cpp_filters
@UseJinja("module.h.tmpl")
- def GenerateModuleHeader(self):
- return self.GetJinjaExports()
+ def _GenerateModuleHeader(self):
+ return self._GetJinjaExports()
@UseJinja("module.cc.tmpl")
- def GenerateModuleSource(self):
- return self.GetJinjaExports()
+ def _GenerateModuleSource(self):
+ return self._GetJinjaExports()
@UseJinja("module-shared.h.tmpl")
- def GenerateModuleSharedHeader(self):
- return self.GetJinjaExports()
+ def _GenerateModuleSharedHeader(self):
+ return self._GetJinjaExports()
@UseJinja("module-shared-internal.h.tmpl")
- def GenerateModuleSharedInternalHeader(self):
- return self.GetJinjaExports()
+ def _GenerateModuleSharedInternalHeader(self):
+ return self._GetJinjaExports()
+
+ @UseJinja("module-shared-message-ids.h.tmpl")
+ def _GenerateModuleSharedMessageIdsHeader(self):
+ return self._GetJinjaExports()
@UseJinja("module-shared.cc.tmpl")
- def GenerateModuleSharedSource(self):
- return self.GetJinjaExports()
+ def _GenerateModuleSharedSource(self):
+ return self._GetJinjaExports()
def GenerateFiles(self, args):
+ self.module.Stylize(generator.Stylizer())
+
if self.generate_non_variant_code:
- self.Write(self.GenerateModuleSharedHeader(),
- self.MatchMojomFilePath("%s-shared.h" % self.module.name))
- self.Write(
- self.GenerateModuleSharedInternalHeader(),
- self.MatchMojomFilePath("%s-shared-internal.h" % self.module.name))
- self.Write(self.GenerateModuleSharedSource(),
- self.MatchMojomFilePath("%s-shared.cc" % self.module.name))
+ if self.generate_message_ids:
+ self.Write(self._GenerateModuleSharedMessageIdsHeader(),
+ "%s-shared-message-ids.h" % self.module.path)
+ else:
+ self.Write(self._GenerateModuleSharedHeader(),
+ "%s-shared.h" % self.module.path)
+ self.Write(self._GenerateModuleSharedInternalHeader(),
+ "%s-shared-internal.h" % self.module.path)
+ self.Write(self._GenerateModuleSharedSource(),
+ "%s-shared.cc" % self.module.path)
else:
- global _current_typemap
- _current_typemap = self.typemap
- global _for_blink
- _for_blink = self.for_blink
- global _use_once_callback
- _use_once_callback = self.use_once_callback
- global _variant
- _variant = self.variant
- global _export_attribute
- _export_attribute = self.export_attribute
suffix = "-%s" % self.variant if self.variant else ""
- self.Write(self.GenerateModuleHeader(),
- self.MatchMojomFilePath("%s%s.h" % (self.module.name, suffix)))
- self.Write(
- self.GenerateModuleSource(),
- self.MatchMojomFilePath("%s%s.cc" % (self.module.name, suffix)))
+ self.Write(self._GenerateModuleHeader(),
+ "%s%s.h" % (self.module.path, suffix))
+ self.Write(self._GenerateModuleSource(),
+ "%s%s.cc" % (self.module.path, suffix))
+
+ def _ConstantValue(self, constant):
+ return self._ExpressionToText(constant.value, kind=constant.kind)
+
+ def _DefaultValue(self, field):
+ if not field.default:
+ return ""
+
+ if mojom.IsStructKind(field.kind):
+ assert field.default == "default"
+ if self._IsTypemappedKind(field.kind):
+ return ""
+ return "%s::New()" % self._GetNameForKind(field.kind)
+
+ expression = self._ExpressionToText(field.default, kind=field.kind)
+ if mojom.IsEnumKind(field.kind) and self._IsTypemappedKind(field.kind):
+ expression = "mojo::internal::ConvertEnumValue<%s, %s>(%s)" % (
+ self._GetNameForKind(field.kind), self._GetCppWrapperType(field.kind),
+ expression)
+ return expression
+
+ def _GetNameForKind(self, kind, internal=False, flatten_nested_kind=False,
+ add_same_module_namespaces=False):
+ return _NameFormatter(kind, self.variant).FormatForCpp(
+ internal=internal, flatten_nested_kind=flatten_nested_kind,
+ omit_namespace_for_module = (None if add_same_module_namespaces
+ else self.module))
+
+ def _GetQualifiedNameForKind(self, kind, internal=False,
+ flatten_nested_kind=False, include_variant=True):
+ return _NameFormatter(
+ kind, self.variant if include_variant else None).FormatForCpp(
+ internal=internal, flatten_nested_kind=flatten_nested_kind)
+
+ def _GetFullMojomNameForKind(self, kind):
+ return _NameFormatter(kind, self.variant).FormatForMojom()
+
+ def _IsTypemappedKind(self, kind):
+ return hasattr(kind, "name") and \
+ self._GetFullMojomNameForKind(kind) in self.typemap
+
+ def _IsHashableKind(self, kind):
+ """Check if the kind can be hashed.
+
+ Args:
+ kind: {Kind} The kind to check.
+
+ Returns:
+ {bool} True if a value of this kind can be hashed.
+ """
+ checked = set()
+ def Check(kind):
+ if kind.spec in checked:
+ return True
+ checked.add(kind.spec)
+ if mojom.IsNullableKind(kind):
+ return False
+ elif mojom.IsStructKind(kind):
+ if kind.native_only:
+ return False
+ if (self._IsTypemappedKind(kind) and
+ not self.typemap[self._GetFullMojomNameForKind(kind)]["hashable"]):
+ return False
+ return all(Check(field.kind) for field in kind.fields)
+ elif mojom.IsEnumKind(kind):
+ return not self._IsTypemappedKind(kind) or self.typemap[
+ self._GetFullMojomNameForKind(kind)]["hashable"]
+ elif mojom.IsUnionKind(kind):
+ return all(Check(field.kind) for field in kind.fields)
+ elif mojom.IsAnyHandleKind(kind):
+ return False
+ elif mojom.IsAnyInterfaceKind(kind):
+ return False
+ # TODO(crbug.com/735301): Arrays and maps could be made hashable. We just
+ # don't have a use case yet.
+ elif mojom.IsArrayKind(kind):
+ return False
+ elif mojom.IsMapKind(kind):
+ return False
+ else:
+ return True
+ return Check(kind)
+
+ def _GetNativeTypeName(self, typemapped_kind):
+ return self.typemap[self._GetFullMojomNameForKind(typemapped_kind)][
+ "typename"]
+
+ def _FormatConstantDeclaration(self, constant, nested=False):
+ if mojom.IsStringKind(constant.kind):
+ if nested:
+ return "const char %s[]" % constant.name
+ return "%sextern const char %s[]" % \
+ ((self.export_attribute + " ") if self.export_attribute else "",
+ constant.name)
+ return "constexpr %s %s = %s" % (
+ GetCppPodType(constant.kind), constant.name,
+ self._ConstantValue(constant))
+
+ def _GetCppWrapperType(self, kind, add_same_module_namespaces=False):
+ def _AddOptional(type_name):
+ return "base::Optional<%s>" % type_name
+
+ if self._IsTypemappedKind(kind):
+ type_name = self._GetNativeTypeName(kind)
+ if (mojom.IsNullableKind(kind) and
+ not self.typemap[self._GetFullMojomNameForKind(kind)][
+ "nullable_is_same_type"]):
+ type_name = _AddOptional(type_name)
+ return type_name
+ if mojom.IsEnumKind(kind):
+ return self._GetNameForKind(
+ kind, add_same_module_namespaces=add_same_module_namespaces)
+ if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):
+ return "%sPtr" % self._GetNameForKind(
+ kind, add_same_module_namespaces=add_same_module_namespaces)
+ if mojom.IsArrayKind(kind):
+ pattern = "WTF::Vector<%s>" if self.for_blink else "std::vector<%s>"
+ if mojom.IsNullableKind(kind):
+ pattern = _AddOptional(pattern)
+ return pattern % self._GetCppWrapperType(
+ kind.kind, add_same_module_namespaces=add_same_module_namespaces)
+ if mojom.IsMapKind(kind):
+ pattern = ("WTF::HashMap<%s, %s>" if self.for_blink else
+ "base::flat_map<%s, %s>")
+ if mojom.IsNullableKind(kind):
+ pattern = _AddOptional(pattern)
+ return pattern % (
+ self._GetCppWrapperType(
+ kind.key_kind,
+ add_same_module_namespaces=add_same_module_namespaces),
+ self._GetCppWrapperType(
+ kind.value_kind,
+ add_same_module_namespaces=add_same_module_namespaces))
+ if mojom.IsInterfaceKind(kind):
+ return "%sPtrInfo" % self._GetNameForKind(
+ kind, add_same_module_namespaces=add_same_module_namespaces)
+ if mojom.IsInterfaceRequestKind(kind):
+ return "%sRequest" % self._GetNameForKind(
+ kind.kind, add_same_module_namespaces=add_same_module_namespaces)
+ if mojom.IsAssociatedInterfaceKind(kind):
+ return "%sAssociatedPtrInfo" % self._GetNameForKind(
+ kind.kind, add_same_module_namespaces=add_same_module_namespaces)
+ if mojom.IsAssociatedInterfaceRequestKind(kind):
+ return "%sAssociatedRequest" % self._GetNameForKind(
+ kind.kind, add_same_module_namespaces=add_same_module_namespaces)
+ if mojom.IsStringKind(kind):
+ if self.for_blink:
+ return "WTF::String"
+ type_name = "std::string"
+ return (_AddOptional(type_name) if mojom.IsNullableKind(kind)
+ else type_name)
+ if mojom.IsGenericHandleKind(kind):
+ return "mojo::ScopedHandle"
+ if mojom.IsDataPipeConsumerKind(kind):
+ return "mojo::ScopedDataPipeConsumerHandle"
+ if mojom.IsDataPipeProducerKind(kind):
+ return "mojo::ScopedDataPipeProducerHandle"
+ if mojom.IsMessagePipeKind(kind):
+ return "mojo::ScopedMessagePipeHandle"
+ if mojom.IsSharedBufferKind(kind):
+ return "mojo::ScopedSharedBufferHandle"
+ if not kind in _kind_to_cpp_type:
+ raise Exception("Unrecognized kind %s" % kind.spec)
+ return _kind_to_cpp_type[kind]
+
+ def _IsMoveOnlyKind(self, kind):
+ if self._IsTypemappedKind(kind):
+ if mojom.IsEnumKind(kind):
+ return False
+ return self.typemap[self._GetFullMojomNameForKind(kind)]["move_only"]
+ if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):
+ return True
+ if mojom.IsArrayKind(kind):
+ return self._IsMoveOnlyKind(kind.kind)
+ if mojom.IsMapKind(kind):
+ return (self._IsMoveOnlyKind(kind.value_kind) or
+ self._IsMoveOnlyKind(kind.key_kind))
+ if mojom.IsAnyHandleOrInterfaceKind(kind):
+ return True
+ return False
+
+ def _IsCopyablePassByValue(self, kind):
+ if not self._IsTypemappedKind(kind):
+ return False
+ return self.typemap[self._GetFullMojomNameForKind(kind)][
+ "copyable_pass_by_value"]
+
+ def _ShouldPassParamByValue(self, kind):
+ return ((not mojom.IsReferenceKind(kind)) or self._IsMoveOnlyKind(kind) or
+ self._IsCopyablePassByValue(kind))
+
+ def _GetCppWrapperCallType(self, kind):
+ # TODO: Remove this once interfaces are always passed as PtrInfo.
+ if mojom.IsInterfaceKind(kind):
+ return "%sPtr" % self._GetNameForKind(kind)
+ return self._GetCppWrapperType(kind)
+
+ def _GetCppWrapperParamType(self, kind):
+ # TODO: Remove all usage of this method in favor of
+ # _GetCppWrapperParamTypeNew. This requires all generated code which passes
+ # interface handles to use PtrInfo instead of Ptr.
+ if mojom.IsInterfaceKind(kind):
+ return "%sPtr" % self._GetNameForKind(kind)
+ cpp_wrapper_type = self._GetCppWrapperType(kind)
+ return (cpp_wrapper_type if self._ShouldPassParamByValue(kind)
+ else "const %s&" % cpp_wrapper_type)
+
+ def _GetCppWrapperParamTypeNew(self, kind):
+ cpp_wrapper_type = self._GetCppWrapperType(kind)
+ return (cpp_wrapper_type if self._ShouldPassParamByValue(kind)
+ else "const %s&" % cpp_wrapper_type)
+
+ def _GetCppFieldType(self, kind):
+ if mojom.IsStructKind(kind):
+ return ("mojo::internal::Pointer<%s>" %
+ self._GetNameForKind(kind, internal=True))
+ if mojom.IsUnionKind(kind):
+ return "%s" % self._GetNameForKind(kind, internal=True)
+ if mojom.IsArrayKind(kind):
+ return ("mojo::internal::Pointer<mojo::internal::Array_Data<%s>>" %
+ self._GetCppFieldType(kind.kind))
+ if mojom.IsMapKind(kind):
+ return ("mojo::internal::Pointer<mojo::internal::Map_Data<%s, %s>>" %
+ (self._GetCppFieldType(kind.key_kind),
+ self._GetCppFieldType(kind.value_kind)))
+ if mojom.IsInterfaceKind(kind):
+ return "mojo::internal::Interface_Data"
+ if mojom.IsInterfaceRequestKind(kind):
+ return "mojo::internal::Handle_Data"
+ if mojom.IsAssociatedInterfaceKind(kind):
+ return "mojo::internal::AssociatedInterface_Data"
+ if mojom.IsAssociatedInterfaceRequestKind(kind):
+ return "mojo::internal::AssociatedEndpointHandle_Data"
+ if mojom.IsEnumKind(kind):
+ return "int32_t"
+ if mojom.IsStringKind(kind):
+ return "mojo::internal::Pointer<mojo::internal::String_Data>"
+ if mojom.IsAnyHandleKind(kind):
+ return "mojo::internal::Handle_Data"
+ return _kind_to_cpp_type[kind]
+
+ def _GetCppUnionFieldType(self, kind):
+ if mojom.IsUnionKind(kind):
+ return ("mojo::internal::Pointer<%s>" %
+ self._GetNameForKind(kind, internal=True))
+ return self._GetCppFieldType(kind)
+
+ def _GetUnionGetterReturnType(self, kind):
+ if mojom.IsReferenceKind(kind):
+ return "%s&" % self._GetCppWrapperType(kind)
+ return self._GetCppWrapperType(kind)
+
+ def _GetUnionTraitGetterReturnType(self, kind):
+ """Get field type used in UnionTraits template specialization.
+
+ The type may be qualified as UnionTraits specializations live outside the
+ namespace where e.g. structs are defined.
+
+ Args:
+ kind: {Kind} The type of the field.
+
+ Returns:
+ {str} The C++ type to use for the field.
+ """
+ if mojom.IsReferenceKind(kind):
+ return "%s&" % self._GetCppWrapperType(kind,
+ add_same_module_namespaces=True)
+ return self._GetCppWrapperType(kind, add_same_module_namespaces=True)
+
+ def _KindMustBeSerialized(self, kind, processed_kinds=None):
+ if not processed_kinds:
+ processed_kinds = set()
+ if kind in processed_kinds:
+ return False
+
+ if (self._IsTypemappedKind(kind) and
+ self.typemap[self._GetFullMojomNameForKind(kind)]["force_serialize"]):
+ return True
+
+ processed_kinds.add(kind)
+
+ if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):
+ return any(self._KindMustBeSerialized(field.kind,
+ processed_kinds=processed_kinds)
+ for field in kind.fields)
+
+ return False
+
+ def _MethodSupportsLazySerialization(self, method):
+ if not self.support_lazy_serialization:
+ return False
+
+ # TODO(crbug.com/753433): Support lazy serialization for methods which pass
+ # associated handles.
+ if mojom.MethodPassesAssociatedKinds(method):
+ return False
+
+ return not any(self._KindMustBeSerialized(param.kind) for param in
+ method.parameters + (method.response_parameters or []))
+
+ def _TranslateConstants(self, token, kind):
+ if isinstance(token, mojom.NamedValue):
+ return self._GetNameForKind(token, flatten_nested_kind=True)
+
+ if isinstance(token, mojom.BuiltinValue):
+ if token.value == "double.INFINITY":
+ return "std::numeric_limits<double>::infinity()"
+ if token.value == "float.INFINITY":
+ return "std::numeric_limits<float>::infinity()"
+ if token.value == "double.NEGATIVE_INFINITY":
+ return "-std::numeric_limits<double>::infinity()"
+ if token.value == "float.NEGATIVE_INFINITY":
+ return "-std::numeric_limits<float>::infinity()"
+ if token.value == "double.NAN":
+ return "std::numeric_limits<double>::quiet_NaN()"
+ if token.value == "float.NAN":
+ return "std::numeric_limits<float>::quiet_NaN()"
+
+ if (kind is not None and mojom.IsFloatKind(kind)):
+ return token if token.isdigit() else token + "f";
+
+ # Per C++11, 2.14.2, the type of an integer literal is the first of the
+ # corresponding list in Table 6 in which its value can be represented. In
+ # this case, the list for decimal constants with no suffix is:
+ # int, long int, long long int
+ # The standard considers a program ill-formed if it contains an integer
+ # literal that cannot be represented by any of the allowed types.
+ #
+ # As it turns out, MSVC doesn't bother trying to fall back to long long int,
+ # so the integral constant -2147483648 causes it grief: it decides to
+ # represent 2147483648 as an unsigned integer, and then warns that the unary
+ # minus operator doesn't make sense on unsigned types. Doh!
+ if kind == mojom.INT32 and token == "-2147483648":
+ return "(-%d - 1) /* %s */" % (
+ 2**31 - 1, "Workaround for MSVC bug; see https://crbug.com/445618")
+
+ return "%s%s" % (token, _kind_to_cpp_literal_suffix.get(kind, ""))
+
+ def _ExpressionToText(self, value, kind=None):
+ return self._TranslateConstants(value, kind)
+
+ def _ContainsMoveOnlyMembers(self, struct):
+ for field in struct.fields:
+ if self._IsMoveOnlyKind(field.kind):
+ return True
+ return False
+
+ def _GetStructConstructors(self, struct):
+ """Returns a list of constructors for a struct.
+
+ Params:
+ struct: {Struct} The struct to return constructors for.
+
+ Returns:
+ {[StructConstructor]} A list of StructConstructors that should be
+ generated for |struct|.
+ """
+ if not mojom.IsStructKind(struct):
+ raise TypeError
+ # Types that are neither copyable nor movable can't be passed to a struct
+ # constructor so only generate a default constructor.
+ if any(self._IsTypemappedKind(field.kind) and self.typemap[
+ self._GetFullMojomNameForKind(field.kind)]["non_copyable_non_movable"]
+ for field in struct.fields):
+ return [StructConstructor(struct.fields, [])]
+
+ param_counts = [0]
+ for version in struct.versions:
+ if param_counts[-1] != version.num_fields:
+ param_counts.append(version.num_fields)
+
+ ordinal_fields = sorted(struct.fields, key=lambda field: field.ordinal)
+ return (StructConstructor(struct.fields, ordinal_fields[:param_count])
+ for param_count in param_counts)
+
+ def _GetContainerValidateParamsCtorArgs(self, kind):
+ if mojom.IsStringKind(kind):
+ expected_num_elements = 0
+ element_is_nullable = False
+ key_validate_params = "nullptr"
+ element_validate_params = "nullptr"
+ enum_validate_func = "nullptr"
+ elif mojom.IsMapKind(kind):
+ expected_num_elements = 0
+ element_is_nullable = False
+ key_validate_params = self._GetNewContainerValidateParams(mojom.Array(
+ kind=kind.key_kind))
+ element_validate_params = self._GetNewContainerValidateParams(mojom.Array(
+ kind=kind.value_kind))
+ enum_validate_func = "nullptr"
+ else: # mojom.IsArrayKind(kind)
+ expected_num_elements = generator.ExpectedArraySize(kind) or 0
+ element_is_nullable = mojom.IsNullableKind(kind.kind)
+ key_validate_params = "nullptr"
+ element_validate_params = self._GetNewContainerValidateParams(kind.kind)
+ if mojom.IsEnumKind(kind.kind):
+ enum_validate_func = ("%s::Validate" %
+ self._GetQualifiedNameForKind(kind.kind, internal=True,
+ flatten_nested_kind=True))
+ else:
+ enum_validate_func = "nullptr"
+
+ if enum_validate_func == "nullptr":
+ if key_validate_params == "nullptr":
+ return "%d, %s, %s" % (expected_num_elements,
+ "true" if element_is_nullable else "false",
+ element_validate_params)
+ else:
+ return "%s, %s" % (key_validate_params, element_validate_params)
+ else:
+ return "%d, %s" % (expected_num_elements, enum_validate_func)
+
+ def _GetNewContainerValidateParams(self, kind):
+ if (not mojom.IsArrayKind(kind) and not mojom.IsMapKind(kind) and
+ not mojom.IsStringKind(kind)):
+ return "nullptr"
+
+ return "new mojo::internal::ContainerValidateParams(%s)" % (
+ self._GetContainerValidateParamsCtorArgs(kind))
+
+ def _GetCppDataViewType(self, kind, qualified=False):
+ def _GetName(input_kind):
+ return _NameFormatter(input_kind, None).FormatForCpp(
+ omit_namespace_for_module=(None if qualified else self.module),
+ flatten_nested_kind=True)
+
+ if mojom.IsEnumKind(kind):
+ return _GetName(kind)
+ if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind):
+ return "%sDataView" % _GetName(kind)
+ if mojom.IsArrayKind(kind):
+ return "mojo::ArrayDataView<%s>" % (
+ self._GetCppDataViewType(kind.kind, qualified))
+ if mojom.IsMapKind(kind):
+ return ("mojo::MapDataView<%s, %s>" % (
+ self._GetCppDataViewType(kind.key_kind, qualified),
+ self._GetCppDataViewType(kind.value_kind, qualified)))
+ if mojom.IsStringKind(kind):
+ return "mojo::StringDataView"
+ if mojom.IsInterfaceKind(kind):
+ return "%sPtrDataView" % _GetName(kind)
+ if mojom.IsInterfaceRequestKind(kind):
+ return "%sRequestDataView" % _GetName(kind.kind)
+ if mojom.IsAssociatedInterfaceKind(kind):
+ return "%sAssociatedPtrInfoDataView" % _GetName(kind.kind)
+ if mojom.IsAssociatedInterfaceRequestKind(kind):
+ return "%sAssociatedRequestDataView" % _GetName(kind.kind)
+ if mojom.IsGenericHandleKind(kind):
+ return "mojo::ScopedHandle"
+ if mojom.IsDataPipeConsumerKind(kind):
+ return "mojo::ScopedDataPipeConsumerHandle"
+ if mojom.IsDataPipeProducerKind(kind):
+ return "mojo::ScopedDataPipeProducerHandle"
+ if mojom.IsMessagePipeKind(kind):
+ return "mojo::ScopedMessagePipeHandle"
+ if mojom.IsSharedBufferKind(kind):
+ return "mojo::ScopedSharedBufferHandle"
+ return _kind_to_cpp_type[kind]
+
+ def _GetUnmappedTypeForSerializer(self, kind):
+ return self._GetCppDataViewType(kind, qualified=True)
diff --git a/mojo/public/tools/bindings/generators/mojom_java_generator.py b/mojo/public/tools/bindings/generators/mojom_java_generator.py
index c7657ff99a..0f2b618182 100644
--- a/mojo/public/tools/bindings/generators/mojom_java_generator.py
+++ b/mojo/public/tools/bindings/generators/mojom_java_generator.py
@@ -93,14 +93,22 @@ _java_primitive_to_boxed_type = {
'short': 'Short',
}
+_java_reserved_types = [
+ # These two may clash with commonly used classes on Android.
+ 'Manifest',
+ 'R'
+]
def NameToComponent(name):
- # insert '_' between anything and a Title name (e.g, HTTPEntry2FooBar ->
- # HTTP_Entry2_FooBar)
- name = re.sub('([^_])([A-Z][^A-Z_]+)', r'\1_\2', name)
- # insert '_' between non upper and start of upper blocks (e.g.,
- # HTTP_Entry2_FooBar -> HTTP_Entry2_Foo_Bar)
- name = re.sub('([^A-Z_])([A-Z])', r'\1_\2', name)
+ """ Returns a list of lowercase words corresponding to a given name. """
+ # Add underscores after uppercase letters when appropriate. An uppercase
+ # letter is considered the end of a word if it is followed by an upper and a
+ # lower. E.g. URLLoaderFactory -> URL_LoaderFactory
+ name = re.sub('([A-Z][0-9]*)(?=[A-Z][0-9]*[a-z])', r'\1_', name)
+ # Add underscores after lowercase letters when appropriate. A lowercase letter
+ # is considered the end of a word if it is followed by an upper.
+ # E.g. URLLoaderFactory -> URLLoader_Factory
+ name = re.sub('([a-z][0-9]*)(?=[A-Z])', r'\1_', name)
return [x.lower() for x in name.split('_')]
def UpperCamelCase(name):
@@ -122,7 +130,10 @@ def ConstantStyle(name):
def GetNameForElement(element):
if (mojom.IsEnumKind(element) or mojom.IsInterfaceKind(element) or
mojom.IsStructKind(element) or mojom.IsUnionKind(element)):
- return UpperCamelCase(element.name)
+ name = UpperCamelCase(element.name)
+ if name in _java_reserved_types:
+ return name + '_'
+ return name
if mojom.IsInterfaceRequestKind(element) or mojom.IsAssociatedKind(element):
return GetNameForElement(element.kind)
if isinstance(element, (mojom.Method,
@@ -403,39 +414,7 @@ def TempDir():
shutil.rmtree(dirname)
class Generator(generator.Generator):
-
- java_filters = {
- 'array_expected_length': GetArrayExpectedLength,
- 'array': GetArrayKind,
- 'constant_value': ConstantValue,
- 'decode_method': DecodeMethod,
- 'default_value': DefaultValue,
- 'encode_method': EncodeMethod,
- 'expression_to_text': ExpressionToText,
- 'has_method_without_response': HasMethodWithoutResponse,
- 'has_method_with_response': HasMethodWithResponse,
- 'interface_response_name': GetInterfaceResponseName,
- 'is_array_kind': mojom.IsArrayKind,
- 'is_any_handle_kind': mojom.IsAnyHandleKind,
- "is_enum_kind": mojom.IsEnumKind,
- 'is_interface_request_kind': mojom.IsInterfaceRequestKind,
- 'is_map_kind': mojom.IsMapKind,
- 'is_nullable_kind': mojom.IsNullableKind,
- 'is_pointer_array_kind': IsPointerArrayKind,
- 'is_reference_kind': mojom.IsReferenceKind,
- 'is_struct_kind': mojom.IsStructKind,
- 'is_union_array_kind': IsUnionArrayKind,
- 'is_union_kind': mojom.IsUnionKind,
- 'java_class_for_enum': GetJavaClassForEnum,
- 'java_true_false': GetJavaTrueFalse,
- 'java_type': GetJavaType,
- 'method_ordinal_name': GetMethodOrdinalName,
- 'name': GetNameForElement,
- 'new_array': NewArray,
- 'ucc': lambda x: UpperCamelCase(x.name),
- }
-
- def GetJinjaExports(self):
+ def _GetJinjaExports(self):
return {
'package': GetPackage(self.module),
}
@@ -444,73 +423,100 @@ class Generator(generator.Generator):
def GetTemplatePrefix():
return "java_templates"
- @classmethod
- def GetFilters(cls):
- return cls.java_filters
+ def GetFilters(self):
+ java_filters = {
+ 'array_expected_length': GetArrayExpectedLength,
+ 'array': GetArrayKind,
+ 'constant_value': ConstantValue,
+ 'decode_method': DecodeMethod,
+ 'default_value': DefaultValue,
+ 'encode_method': EncodeMethod,
+ 'expression_to_text': ExpressionToText,
+ 'has_method_without_response': HasMethodWithoutResponse,
+ 'has_method_with_response': HasMethodWithResponse,
+ 'interface_response_name': GetInterfaceResponseName,
+ 'is_array_kind': mojom.IsArrayKind,
+ 'is_any_handle_kind': mojom.IsAnyHandleKind,
+ "is_enum_kind": mojom.IsEnumKind,
+ 'is_interface_request_kind': mojom.IsInterfaceRequestKind,
+ 'is_map_kind': mojom.IsMapKind,
+ 'is_nullable_kind': mojom.IsNullableKind,
+ 'is_pointer_array_kind': IsPointerArrayKind,
+ 'is_reference_kind': mojom.IsReferenceKind,
+ 'is_struct_kind': mojom.IsStructKind,
+ 'is_union_array_kind': IsUnionArrayKind,
+ 'is_union_kind': mojom.IsUnionKind,
+ 'java_class_for_enum': GetJavaClassForEnum,
+ 'java_true_false': GetJavaTrueFalse,
+ 'java_type': GetJavaType,
+ 'method_ordinal_name': GetMethodOrdinalName,
+ 'name': GetNameForElement,
+ 'new_array': NewArray,
+ 'ucc': lambda x: UpperCamelCase(x.name),
+ }
+ return java_filters
- def GetJinjaExportsForInterface(self, interface):
- exports = self.GetJinjaExports()
+ def _GetJinjaExportsForInterface(self, interface):
+ exports = self._GetJinjaExports()
exports.update({'interface': interface})
return exports
@UseJinja('enum.java.tmpl')
- def GenerateEnumSource(self, enum):
- exports = self.GetJinjaExports()
+ def _GenerateEnumSource(self, enum):
+ exports = self._GetJinjaExports()
exports.update({'enum': enum})
return exports
@UseJinja('struct.java.tmpl')
- def GenerateStructSource(self, struct):
- exports = self.GetJinjaExports()
+ def _GenerateStructSource(self, struct):
+ exports = self._GetJinjaExports()
exports.update({'struct': struct})
return exports
@UseJinja('union.java.tmpl')
- def GenerateUnionSource(self, union):
- exports = self.GetJinjaExports()
+ def _GenerateUnionSource(self, union):
+ exports = self._GetJinjaExports()
exports.update({'union': union})
return exports
@UseJinja('interface.java.tmpl')
- def GenerateInterfaceSource(self, interface):
- return self.GetJinjaExportsForInterface(interface)
+ def _GenerateInterfaceSource(self, interface):
+ return self._GetJinjaExportsForInterface(interface)
@UseJinja('interface_internal.java.tmpl')
- def GenerateInterfaceInternalSource(self, interface):
- return self.GetJinjaExportsForInterface(interface)
+ def _GenerateInterfaceInternalSource(self, interface):
+ return self._GetJinjaExportsForInterface(interface)
@UseJinja('constants.java.tmpl')
- def GenerateConstantsSource(self, module):
- exports = self.GetJinjaExports()
+ def _GenerateConstantsSource(self, module):
+ exports = self._GetJinjaExports()
exports.update({'main_entity': GetConstantsMainEntityName(module),
'constants': module.constants})
return exports
- def DoGenerateFiles(self):
+ def _DoGenerateFiles(self):
fileutil.EnsureDirectoryExists(self.output_dir)
- # Keep this above the others as .GetStructs() changes the state of the
- # module, annotating structs with required information.
- for struct in self.GetStructs():
- self.Write(self.GenerateStructSource(struct),
+ for struct in self.module.structs:
+ self.Write(self._GenerateStructSource(struct),
'%s.java' % GetNameForElement(struct))
for union in self.module.unions:
- self.Write(self.GenerateUnionSource(union),
+ self.Write(self._GenerateUnionSource(union),
'%s.java' % GetNameForElement(union))
for enum in self.module.enums:
- self.Write(self.GenerateEnumSource(enum),
+ self.Write(self._GenerateEnumSource(enum),
'%s.java' % GetNameForElement(enum))
- for interface in self.GetInterfaces():
- self.Write(self.GenerateInterfaceSource(interface),
+ for interface in self.module.interfaces:
+ self.Write(self._GenerateInterfaceSource(interface),
'%s.java' % GetNameForElement(interface))
- self.Write(self.GenerateInterfaceInternalSource(interface),
+ self.Write(self._GenerateInterfaceInternalSource(interface),
'%s_Internal.java' % GetNameForElement(interface))
if self.module.constants:
- self.Write(self.GenerateConstantsSource(self.module),
+ self.Write(self._GenerateConstantsSource(self.module),
'%s.java' % GetConstantsMainEntityName(self.module))
def GenerateFiles(self, unparsed_args):
@@ -518,6 +524,8 @@ class Generator(generator.Generator):
if self.variant:
raise Exception("Variants not supported in Java bindings.")
+ self.module.Stylize(generator.Stylizer())
+
parser = argparse.ArgumentParser()
parser.add_argument('--java_output_directory', dest='java_output_directory')
args = parser.parse_args(unparsed_args)
@@ -525,17 +533,17 @@ class Generator(generator.Generator):
# Generate the java files in a temporary directory and place a single
# srcjar in the output directory.
- basename = self.MatchMojomFilePath("%s.srcjar" % self.module.name)
+ basename = "%s.srcjar" % self.module.path
zip_filename = os.path.join(self.output_dir, basename)
with TempDir() as temp_java_root:
self.output_dir = os.path.join(temp_java_root, package_path)
- self.DoGenerateFiles();
+ self._DoGenerateFiles();
build_utils.ZipDir(zip_filename, temp_java_root)
if args.java_output_directory:
# If requested, generate the java files directly into indicated directory.
self.output_dir = os.path.join(args.java_output_directory, package_path)
- self.DoGenerateFiles();
+ self._DoGenerateFiles();
def GetJinjaParameters(self):
return {
diff --git a/mojo/public/tools/bindings/generators/mojom_js_generator.py b/mojo/public/tools/bindings/generators/mojom_js_generator.py
index ab9635ee30..e403df6167 100644
--- a/mojo/public/tools/bindings/generators/mojom_js_generator.py
+++ b/mojo/public/tools/bindings/generators/mojom_js_generator.py
@@ -8,6 +8,7 @@ import mojom.generate.generator as generator
import mojom.generate.module as mojom
import mojom.generate.pack as pack
import os
+import urllib
from mojom.generate.template_expander import UseJinja
_kind_to_javascript_default_value = {
@@ -36,54 +37,6 @@ _kind_to_javascript_default_value = {
mojom.NULLABLE_STRING: "null"
}
-
-def JavaScriptType(kind):
- name = []
- if kind.imported_from:
- name.append(kind.imported_from["unique_name"])
- if kind.parent_kind:
- name.append(kind.parent_kind.name)
- name.append(kind.name)
- return ".".join(name)
-
-
-def JavaScriptDefaultValue(field):
- if field.default:
- if mojom.IsStructKind(field.kind):
- assert field.default == "default"
- return "new %s()" % JavaScriptType(field.kind)
- return ExpressionToText(field.default)
- if field.kind in mojom.PRIMITIVES:
- return _kind_to_javascript_default_value[field.kind]
- if mojom.IsStructKind(field.kind):
- return "null"
- if mojom.IsUnionKind(field.kind):
- return "null"
- if mojom.IsArrayKind(field.kind):
- return "null"
- if mojom.IsMapKind(field.kind):
- return "null"
- if mojom.IsInterfaceKind(field.kind):
- return "new %sPtr()" % JavaScriptType(field.kind)
- if mojom.IsInterfaceRequestKind(field.kind):
- return "new bindings.InterfaceRequest()"
- if mojom.IsAssociatedKind(field.kind):
- return "null"
- if mojom.IsEnumKind(field.kind):
- return "0"
- raise Exception("No valid default: %s" % field)
-
-
-def JavaScriptPayloadSize(packed):
- packed_fields = packed.packed_fields
- if not packed_fields:
- return 0
- last_field = packed_fields[-1]
- offset = last_field.offset + last_field.size
- pad = pack.GetPad(offset, 8)
- return offset + pad
-
-
_kind_to_codec_type = {
mojom.BOOL: "codec.Uint8",
mojom.INT8: "codec.Int8",
@@ -110,105 +63,118 @@ _kind_to_codec_type = {
mojom.NULLABLE_STRING: "codec.NullableString",
}
+_kind_to_closure_type = {
+ mojom.BOOL: "boolean",
+ mojom.INT8: "number",
+ mojom.UINT8: "number",
+ mojom.INT16: "number",
+ mojom.UINT16: "number",
+ mojom.INT32: "number",
+ mojom.UINT32: "number",
+ mojom.FLOAT: "number",
+ mojom.INT64: "number",
+ mojom.UINT64: "number",
+ mojom.DOUBLE: "number",
+ mojom.STRING: "string",
+ mojom.NULLABLE_STRING: "string",
+ mojom.HANDLE: "mojo.MojoHandle",
+ mojom.DCPIPE: "mojo.MojoHandle",
+ mojom.DPPIPE: "mojo.MojoHandle",
+ mojom.MSGPIPE: "mojo.MojoHandle",
+ mojom.SHAREDBUFFER: "mojo.MojoHandle",
+ mojom.NULLABLE_HANDLE: "mojo.MojoHandle",
+ mojom.NULLABLE_DCPIPE: "mojo.MojoHandle",
+ mojom.NULLABLE_DPPIPE: "mojo.MojoHandle",
+ mojom.NULLABLE_MSGPIPE: "mojo.MojoHandle",
+ mojom.NULLABLE_SHAREDBUFFER: "mojo.MojoHandle",
+}
-def CodecType(kind):
- if kind in mojom.PRIMITIVES:
- return _kind_to_codec_type[kind]
- if mojom.IsStructKind(kind):
- pointer_type = "NullablePointerTo" if mojom.IsNullableKind(kind) \
- else "PointerTo"
- return "new codec.%s(%s)" % (pointer_type, JavaScriptType(kind))
- if mojom.IsUnionKind(kind):
- return JavaScriptType(kind)
- if mojom.IsArrayKind(kind):
- array_type = "NullableArrayOf" if mojom.IsNullableKind(kind) else "ArrayOf"
- array_length = "" if kind.length is None else ", %d" % kind.length
- element_type = ElementCodecType(kind.kind)
- return "new codec.%s(%s%s)" % (array_type, element_type, array_length)
- if mojom.IsInterfaceKind(kind):
- return "new codec.%s(%sPtr)" % (
- "NullableInterface" if mojom.IsNullableKind(kind) else "Interface",
- JavaScriptType(kind))
- if mojom.IsInterfaceRequestKind(kind):
- return "codec.%s" % (
- "NullableInterfaceRequest" if mojom.IsNullableKind(kind)
- else "InterfaceRequest")
- if mojom.IsAssociatedInterfaceKind(kind):
- return "codec.AssociatedInterfaceNotSupported"
- if mojom.IsAssociatedInterfaceRequestKind(kind):
- return "codec.AssociatedInterfaceRequestNotSupported"
- if mojom.IsEnumKind(kind):
- return "new codec.Enum(%s)" % JavaScriptType(kind)
- if mojom.IsMapKind(kind):
- map_type = "NullableMapOf" if mojom.IsNullableKind(kind) else "MapOf"
- key_type = ElementCodecType(kind.key_kind)
- value_type = ElementCodecType(kind.value_kind)
- return "new codec.%s(%s, %s)" % (map_type, key_type, value_type)
- raise Exception("No codec type for %s" % kind)
-
-
-def ElementCodecType(kind):
- return "codec.PackedBool" if mojom.IsBoolKind(kind) else CodecType(kind)
-
-
-def JavaScriptDecodeSnippet(kind):
- if (kind in mojom.PRIMITIVES or mojom.IsUnionKind(kind) or
- mojom.IsAnyInterfaceKind(kind)):
- return "decodeStruct(%s)" % CodecType(kind)
- if mojom.IsStructKind(kind):
- return "decodeStructPointer(%s)" % JavaScriptType(kind)
- if mojom.IsMapKind(kind):
- return "decodeMapPointer(%s, %s)" % \
- (ElementCodecType(kind.key_kind), ElementCodecType(kind.value_kind))
- if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind):
- return "decodeArrayPointer(codec.PackedBool)"
- if mojom.IsArrayKind(kind):
- return "decodeArrayPointer(%s)" % CodecType(kind.kind)
- if mojom.IsUnionKind(kind):
- return "decodeUnion(%s)" % CodecType(kind)
- if mojom.IsEnumKind(kind):
- return JavaScriptDecodeSnippet(mojom.INT32)
- raise Exception("No decode snippet for %s" % kind)
-
-
-def JavaScriptEncodeSnippet(kind):
- if (kind in mojom.PRIMITIVES or mojom.IsUnionKind(kind) or
- mojom.IsAnyInterfaceKind(kind)):
- return "encodeStruct(%s, " % CodecType(kind)
- if mojom.IsUnionKind(kind):
- return "encodeStruct(%s, " % JavaScriptType(kind)
- if mojom.IsStructKind(kind):
- return "encodeStructPointer(%s, " % JavaScriptType(kind)
- if mojom.IsMapKind(kind):
- return "encodeMapPointer(%s, %s, " % \
- (ElementCodecType(kind.key_kind), ElementCodecType(kind.value_kind))
- if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind):
- return "encodeArrayPointer(codec.PackedBool, ";
- if mojom.IsArrayKind(kind):
- return "encodeArrayPointer(%s, " % CodecType(kind.kind)
- if mojom.IsEnumKind(kind):
- return JavaScriptEncodeSnippet(mojom.INT32)
- raise Exception("No encode snippet for %s" % kind)
-
-
-def JavaScriptUnionDecodeSnippet(kind):
- if mojom.IsUnionKind(kind):
- return "decodeStructPointer(%s)" % JavaScriptType(kind)
- return JavaScriptDecodeSnippet(kind)
-
-
-def JavaScriptUnionEncodeSnippet(kind):
- if mojom.IsUnionKind(kind):
- return "encodeStructPointer(%s, " % JavaScriptType(kind)
- return JavaScriptEncodeSnippet(kind)
+_js_reserved_keywords = [
+ 'arguments',
+ 'await',
+ 'break'
+ 'case',
+ 'catch',
+ 'class',
+ 'const',
+ 'continue',
+ 'debugger',
+ 'default',
+ 'delete',
+ 'do',
+ 'else',
+ 'enum',
+ 'export',
+ 'extends',
+ 'finally',
+ 'for',
+ 'function',
+ 'if',
+ 'implements',
+ 'import',
+ 'in',
+ 'instanceof',
+ 'interface',
+ 'let',
+ 'new',
+ 'package',
+ 'private',
+ 'protected',
+ 'public',
+ 'return',
+ 'static',
+ 'super',
+ 'switch',
+ 'this',
+ 'throw',
+ 'try',
+ 'typeof',
+ 'var',
+ 'void',
+ 'while',
+ 'with',
+ 'yield',
+]
+
+_primitive_kind_to_fuzz_type = {
+ mojom.BOOL: "Bool",
+ mojom.INT8: "Int8",
+ mojom.UINT8: "Uint8",
+ mojom.INT16: "Int16",
+ mojom.UINT16: "Uint16",
+ mojom.INT32: "Int32",
+ mojom.UINT32: "Uint32",
+ mojom.FLOAT: "Float",
+ mojom.INT64: "Int64",
+ mojom.UINT64: "Uint64",
+ mojom.DOUBLE: "Double",
+ mojom.STRING: "String",
+ mojom.NULLABLE_STRING: "String",
+ mojom.HANDLE: "Handle",
+ mojom.DCPIPE: "DataPipeConsumer",
+ mojom.DPPIPE: "DataPipeProducer",
+ mojom.MSGPIPE: "MessagePipe",
+ mojom.SHAREDBUFFER: "SharedBuffer",
+ mojom.NULLABLE_HANDLE: "Handle",
+ mojom.NULLABLE_DCPIPE: "DataPipeConsumer",
+ mojom.NULLABLE_DPPIPE: "DataPipeProducer",
+ mojom.NULLABLE_MSGPIPE: "MessagePipe",
+ mojom.NULLABLE_SHAREDBUFFER: "SharedBuffer",
+}
-def JavaScriptFieldOffset(packed_field):
- return "offset + codec.kStructHeaderSize + %s" % packed_field.offset
+def JavaScriptPayloadSize(packed):
+ packed_fields = packed.packed_fields
+ if not packed_fields:
+ return 0
+ last_field = packed_fields[-1]
+ offset = last_field.offset + last_field.size
+ pad = pack.GetPad(offset, 8)
+ return offset + pad
-def JavaScriptNullableParam(field):
- return "true" if mojom.IsNullableKind(field.kind) else "false"
+def JavaScriptFieldOffset(packed_field):
+ return "offset + codec.kStructHeaderSize + %s" % packed_field.offset
def GetArrayExpectedDimensionSizes(kind):
@@ -222,185 +188,134 @@ def GetArrayExpectedDimensionSizes(kind):
return expected_dimension_sizes
-def JavaScriptValidateArrayParams(field):
- nullable = JavaScriptNullableParam(field)
- element_kind = field.kind.kind
- element_size = pack.PackedField.GetSizeForKind(element_kind)
- expected_dimension_sizes = GetArrayExpectedDimensionSizes(
- field.kind)
- element_type = ElementCodecType(element_kind)
- return "%s, %s, %s, %s, 0" % \
- (element_size, element_type, nullable,
- expected_dimension_sizes)
-
-
-def JavaScriptValidateEnumParams(field):
- return JavaScriptType(field.kind)
-
-def JavaScriptValidateStructParams(field):
- nullable = JavaScriptNullableParam(field)
- struct_type = JavaScriptType(field.kind)
- return "%s, %s" % (struct_type, nullable)
-
-def JavaScriptValidateUnionParams(field):
- nullable = JavaScriptNullableParam(field)
- union_type = JavaScriptType(field.kind)
- return "%s, %s" % (union_type, nullable)
-
-def JavaScriptValidateMapParams(field):
- nullable = JavaScriptNullableParam(field)
- keys_type = ElementCodecType(field.kind.key_kind)
- values_kind = field.kind.value_kind;
- values_type = ElementCodecType(values_kind)
- values_nullable = "true" if mojom.IsNullableKind(values_kind) else "false"
- return "%s, %s, %s, %s" % \
- (nullable, keys_type, values_type, values_nullable)
-
-
-def TranslateConstants(token):
- if isinstance(token, (mojom.EnumValue, mojom.NamedValue)):
- # Both variable and enum constants are constructed like:
- # NamespaceUid.Struct[.Enum].CONSTANT_NAME
- name = []
- if token.imported_from:
- name.append(token.imported_from["unique_name"])
- if token.parent_kind:
- name.append(token.parent_kind.name)
- if isinstance(token, mojom.EnumValue):
- name.append(token.enum.name)
- name.append(token.name)
- return ".".join(name)
-
- if isinstance(token, mojom.BuiltinValue):
- if token.value == "double.INFINITY" or token.value == "float.INFINITY":
- return "Infinity";
- if token.value == "double.NEGATIVE_INFINITY" or \
- token.value == "float.NEGATIVE_INFINITY":
- return "-Infinity";
- if token.value == "double.NAN" or token.value == "float.NAN":
- return "NaN";
-
- return token
-
-
-def ExpressionToText(value):
- return TranslateConstants(value)
-
-def IsArrayPointerField(field):
- return mojom.IsArrayKind(field.kind)
-
-def IsEnumField(field):
- return mojom.IsEnumKind(field.kind)
-
-def IsStringPointerField(field):
- return mojom.IsStringKind(field.kind)
+def GetRelativeUrl(module, base_module):
+ return urllib.pathname2url(
+ os.path.relpath(module.path, os.path.dirname(base_module.path)))
-def IsStructPointerField(field):
- return mojom.IsStructKind(field.kind)
-def IsMapPointerField(field):
- return mojom.IsMapKind(field.kind)
+class JavaScriptStylizer(generator.Stylizer):
+ def StylizeConstant(self, mojom_name):
+ return mojom_name
-def IsHandleField(field):
- return mojom.IsAnyHandleKind(field.kind)
+ def StylizeField(self, mojom_name):
+ return generator.ToCamel(mojom_name, lower_initial=True)
-def IsInterfaceField(field):
- return mojom.IsInterfaceKind(field.kind)
+ def StylizeStruct(self, mojom_name):
+ return mojom_name
-def IsInterfaceRequestField(field):
- return mojom.IsInterfaceRequestKind(field.kind)
+ def StylizeUnion(self, mojom_name):
+ return mojom_name
-def IsUnionField(field):
- return mojom.IsUnionKind(field.kind)
+ def StylizeParameter(self, mojom_name):
+ return generator.ToCamel(mojom_name, lower_initial=True)
-def IsBoolField(field):
- return mojom.IsBoolKind(field.kind)
+ def StylizeMethod(self, mojom_name):
+ return generator.ToCamel(mojom_name, lower_initial=True)
-def IsObjectField(field):
- return mojom.IsObjectKind(field.kind)
+ def StylizeEnumField(self, mojom_name):
+ return mojom_name
-def IsAnyHandleOrInterfaceField(field):
- return mojom.IsAnyHandleOrInterfaceKind(field.kind)
+ def StylizeEnum(self, mojom_name):
+ return mojom_name
-def IsEnumField(field):
- return mojom.IsEnumKind(field.kind)
-
-def GetRelativePath(module, base_module):
- return os.path.relpath(module.path, os.path.dirname(base_module.path))
+ def StylizeModule(self, mojom_namespace):
+ return '.'.join(generator.ToCamel(word, lower_initial=True)
+ for word in mojom_namespace.split('.'))
class Generator(generator.Generator):
-
- js_filters = {
- "decode_snippet": JavaScriptDecodeSnippet,
- "default_value": JavaScriptDefaultValue,
- "encode_snippet": JavaScriptEncodeSnippet,
- "expression_to_text": ExpressionToText,
- "field_offset": JavaScriptFieldOffset,
- "has_callbacks": mojom.HasCallbacks,
- "is_any_handle_or_interface_field": IsAnyHandleOrInterfaceField,
- "is_array_pointer_field": IsArrayPointerField,
- "is_bool_field": IsBoolField,
- "is_enum_field": IsEnumField,
- "is_handle_field": IsHandleField,
- "is_interface_field": IsInterfaceField,
- "is_interface_request_field": IsInterfaceRequestField,
- "is_map_pointer_field": IsMapPointerField,
- "is_object_field": IsObjectField,
- "is_string_pointer_field": IsStringPointerField,
- "is_struct_pointer_field": IsStructPointerField,
- "is_union_field": IsUnionField,
- "js_type": JavaScriptType,
- "payload_size": JavaScriptPayloadSize,
- "get_relative_path": GetRelativePath,
- "stylize_method": generator.StudlyCapsToCamel,
- "union_decode_snippet": JavaScriptUnionDecodeSnippet,
- "union_encode_snippet": JavaScriptUnionEncodeSnippet,
- "validate_array_params": JavaScriptValidateArrayParams,
- "validate_enum_params": JavaScriptValidateEnumParams,
- "validate_map_params": JavaScriptValidateMapParams,
- "validate_nullable_params": JavaScriptNullableParam,
- "validate_struct_params": JavaScriptValidateStructParams,
- "validate_union_params": JavaScriptValidateUnionParams,
- }
-
- def GetParameters(self):
+ def _GetParameters(self):
return {
- "namespace": self.module.namespace,
- "imports": self.GetImports(),
- "kinds": self.module.kinds,
"enums": self.module.enums,
+ "imports": self.module.imports,
+ "interfaces": self.module.interfaces,
+ "kinds": self.module.kinds,
"module": self.module,
- "structs": self.GetStructs() + self.GetStructsFromMethods(),
- "unions": self.GetUnions(),
- "use_new_js_bindings": self.use_new_js_bindings,
- "interfaces": self.GetInterfaces(),
- "imported_interfaces": self.GetImportedInterfaces(),
+ "structs": self.module.structs + self._GetStructsFromMethods(),
+ "unions": self.module.unions,
+ "generate_fuzzing": self.generate_fuzzing,
}
@staticmethod
def GetTemplatePrefix():
return "js_templates"
- @classmethod
- def GetFilters(cls):
- return cls.js_filters
+ def GetFilters(self):
+ js_filters = {
+ "closure_type": self._ClosureType,
+ "decode_snippet": self._JavaScriptDecodeSnippet,
+ "default_value": self._JavaScriptDefaultValue,
+ "encode_snippet": self._JavaScriptEncodeSnippet,
+ "expression_to_text": self._ExpressionToText,
+ "field_offset": JavaScriptFieldOffset,
+ "get_relative_url": GetRelativeUrl,
+ "has_callbacks": mojom.HasCallbacks,
+ "is_any_handle_or_interface_kind": mojom.IsAnyHandleOrInterfaceKind,
+ "is_array_kind": mojom.IsArrayKind,
+ "is_associated_interface_kind": mojom.IsAssociatedInterfaceKind,
+ "is_associated_interface_request_kind":
+ mojom.IsAssociatedInterfaceRequestKind,
+ "is_bool_kind": mojom.IsBoolKind,
+ "is_enum_kind": mojom.IsEnumKind,
+ "is_any_handle_kind": mojom.IsAnyHandleKind,
+ "is_any_interface_kind": mojom.IsAnyInterfaceKind,
+ "is_interface_kind": mojom.IsInterfaceKind,
+ "is_interface_request_kind": mojom.IsInterfaceRequestKind,
+ "is_map_kind": mojom.IsMapKind,
+ "is_object_kind": mojom.IsObjectKind,
+ "is_reference_kind": mojom.IsReferenceKind,
+ "is_string_kind": mojom.IsStringKind,
+ "is_struct_kind": mojom.IsStructKind,
+ "is_union_kind": mojom.IsUnionKind,
+ "js_type": self._JavaScriptType,
+ "method_passes_associated_kinds": mojom.MethodPassesAssociatedKinds,
+ "namespace_declarations": self._NamespaceDeclarations,
+ "closure_type_with_nullability": self._ClosureTypeWithNullability,
+ "payload_size": JavaScriptPayloadSize,
+ "to_camel": generator.ToCamel,
+ "union_decode_snippet": self._JavaScriptUnionDecodeSnippet,
+ "union_encode_snippet": self._JavaScriptUnionEncodeSnippet,
+ "validate_array_params": self._JavaScriptValidateArrayParams,
+ "validate_enum_params": self._JavaScriptValidateEnumParams,
+ "validate_map_params": self._JavaScriptValidateMapParams,
+ "validate_nullable_params": self._JavaScriptNullableParam,
+ "validate_struct_params": self._JavaScriptValidateStructParams,
+ "validate_union_params": self._JavaScriptValidateUnionParams,
+ "sanitize_identifier": self._JavaScriptSanitizeIdentifier,
+ "contains_handles_or_interfaces": mojom.ContainsHandlesOrInterfaces,
+ "fuzz_handle_name": self._FuzzHandleName,
+ "is_primitive_kind": self._IsPrimitiveKind,
+ "primitive_to_fuzz_type": self._PrimitiveToFuzzType,
+ "to_js_boolean": self._ToJsBoolean,
+ }
+ return js_filters
@UseJinja("module.amd.tmpl")
- def GenerateAMDModule(self):
- return self.GetParameters()
+ def _GenerateAMDModule(self):
+ return self._GetParameters()
+
+ @UseJinja("externs/module.externs.tmpl")
+ def _GenerateExterns(self):
+ return self._GetParameters()
def GenerateFiles(self, args):
if self.variant:
raise Exception("Variants not supported in JavaScript bindings.")
- self.Write(self.GenerateAMDModule(),
- self.MatchMojomFilePath("%s.js" % self.module.name))
+ self.module.Stylize(JavaScriptStylizer())
- def GetImports(self):
+ # TODO(crbug.com/795977): Change the media router extension to not mess with
+ # the mojo namespace, so that namespaces such as "mojo.common.mojom" are not
+ # affected and we can remove this method.
+ self._SetUniqueNameForImports()
+
+ self.Write(self._GenerateAMDModule(), "%s.js" % self.module.path)
+ self.Write(self._GenerateExterns(), "%s.externs.js" % self.module.path)
+
+ def _SetUniqueNameForImports(self):
used_names = set()
for each_import in self.module.imports:
- simple_name = each_import["module_name"].split(".")[0]
+ simple_name = os.path.basename(each_import.path).split(".")[0]
# Since each import is assigned a variable in JS, they need to have unique
# names.
@@ -411,15 +326,287 @@ class Generator(generator.Generator):
unique_name = simple_name + str(counter)
used_names.add(unique_name)
- each_import["unique_name"] = unique_name + "$"
+ each_import.unique_name = unique_name + "$"
counter += 1
- return self.module.imports
- def GetImportedInterfaces(self):
- interface_to_import = {};
- for each_import in self.module.imports:
- for each_interface in each_import["module"].interfaces:
- name = each_interface.name
- interface_to_import[name] = each_import["unique_name"] + "." + name
- return interface_to_import;
+ def _ClosureType(self, kind):
+ if kind in mojom.PRIMITIVES:
+ return _kind_to_closure_type[kind]
+ if mojom.IsInterfaceKind(kind):
+ return kind.module.namespace + "." + kind.name + "Ptr"
+ if (mojom.IsStructKind(kind) or
+ mojom.IsEnumKind(kind)):
+ return kind.module.namespace + "." + kind.name
+ # TODO(calamity): Support unions properly.
+ if mojom.IsUnionKind(kind):
+ return "Object"
+ if mojom.IsArrayKind(kind):
+ return "Array<%s>" % self._ClosureType(kind.kind)
+ if mojom.IsMapKind(kind):
+ return "Map<%s, %s>" % (
+ self._ClosureType(kind.key_kind), self._ClosureType(kind.value_kind))
+ if mojom.IsInterfaceRequestKind(kind):
+ return "mojo.InterfaceRequest"
+ # TODO(calamity): Support associated interfaces properly.
+ if mojom.IsAssociatedInterfaceKind(kind):
+ return "mojo.AssociatedInterfacePtrInfo"
+ # TODO(calamity): Support associated interface requests properly.
+ if mojom.IsAssociatedInterfaceRequestKind(kind):
+ return "mojo.AssociatedInterfaceRequest"
+ # TODO(calamity): Support enums properly.
+
+ raise Exception("No valid closure type: %s" % kind)
+
+ def _ClosureTypeWithNullability(self, kind):
+ return ("" if mojom.IsNullableKind(kind) else "!") + self._ClosureType(kind)
+
+ def _NamespaceDeclarations(self, namespace):
+ pieces = namespace.split('.')
+ declarations = []
+ declaration = []
+ for p in pieces:
+ declaration.append(p)
+ declarations.append('.'.join(declaration))
+ return declarations
+
+ def _JavaScriptType(self, kind):
+ name = []
+ if kind.module and kind.module.path != self.module.path:
+ name.append(kind.module.unique_name)
+ if kind.parent_kind:
+ name.append(kind.parent_kind.name)
+ name.append(kind.name)
+ return ".".join(name)
+ def _JavaScriptDefaultValue(self, field):
+ if field.default:
+ if mojom.IsStructKind(field.kind):
+ assert field.default == "default"
+ return "new %s()" % self._JavaScriptType(field.kind)
+ return self._ExpressionToText(field.default)
+ if field.kind in mojom.PRIMITIVES:
+ return _kind_to_javascript_default_value[field.kind]
+ if mojom.IsStructKind(field.kind):
+ return "null"
+ if mojom.IsUnionKind(field.kind):
+ return "null"
+ if mojom.IsArrayKind(field.kind):
+ return "null"
+ if mojom.IsMapKind(field.kind):
+ return "null"
+ if mojom.IsInterfaceKind(field.kind):
+ return "new %sPtr()" % self._JavaScriptType(field.kind)
+ if mojom.IsInterfaceRequestKind(field.kind):
+ return "new bindings.InterfaceRequest()"
+ if mojom.IsAssociatedInterfaceKind(field.kind):
+ return "new associatedBindings.AssociatedInterfacePtrInfo()"
+ if mojom.IsAssociatedInterfaceRequestKind(field.kind):
+ return "new associatedBindings.AssociatedInterfaceRequest()"
+ if mojom.IsEnumKind(field.kind):
+ return "0"
+ raise Exception("No valid default: %s" % field)
+
+ def _CodecType(self, kind):
+ if kind in mojom.PRIMITIVES:
+ return _kind_to_codec_type[kind]
+ if mojom.IsStructKind(kind):
+ pointer_type = "NullablePointerTo" if mojom.IsNullableKind(kind) \
+ else "PointerTo"
+ return "new codec.%s(%s)" % (pointer_type, self._JavaScriptType(kind))
+ if mojom.IsUnionKind(kind):
+ return self._JavaScriptType(kind)
+ if mojom.IsArrayKind(kind):
+ array_type = ("NullableArrayOf" if mojom.IsNullableKind(kind)
+ else "ArrayOf")
+ array_length = "" if kind.length is None else ", %d" % kind.length
+ element_type = self._ElementCodecType(kind.kind)
+ return "new codec.%s(%s%s)" % (array_type, element_type, array_length)
+ if mojom.IsInterfaceKind(kind):
+ return "new codec.%s(%sPtr)" % (
+ "NullableInterface" if mojom.IsNullableKind(kind) else "Interface",
+ self._JavaScriptType(kind))
+ if mojom.IsInterfaceRequestKind(kind):
+ return "codec.%s" % (
+ "NullableInterfaceRequest" if mojom.IsNullableKind(kind)
+ else "InterfaceRequest")
+ if mojom.IsAssociatedInterfaceKind(kind):
+ return "codec.%s" % ("NullableAssociatedInterfacePtrInfo"
+ if mojom.IsNullableKind(kind) else "AssociatedInterfacePtrInfo")
+ if mojom.IsAssociatedInterfaceRequestKind(kind):
+ return "codec.%s" % ("NullableAssociatedInterfaceRequest"
+ if mojom.IsNullableKind(kind) else "AssociatedInterfaceRequest")
+ if mojom.IsEnumKind(kind):
+ return "new codec.Enum(%s)" % self._JavaScriptType(kind)
+ if mojom.IsMapKind(kind):
+ map_type = "NullableMapOf" if mojom.IsNullableKind(kind) else "MapOf"
+ key_type = self._ElementCodecType(kind.key_kind)
+ value_type = self._ElementCodecType(kind.value_kind)
+ return "new codec.%s(%s, %s)" % (map_type, key_type, value_type)
+ raise Exception("No codec type for %s" % kind)
+
+ def _ElementCodecType(self, kind):
+ return ("codec.PackedBool" if mojom.IsBoolKind(kind)
+ else self._CodecType(kind))
+
+ def _JavaScriptDecodeSnippet(self, kind):
+ if (kind in mojom.PRIMITIVES or mojom.IsUnionKind(kind) or
+ mojom.IsAnyInterfaceKind(kind)):
+ return "decodeStruct(%s)" % self._CodecType(kind)
+ if mojom.IsStructKind(kind):
+ return "decodeStructPointer(%s)" % self._JavaScriptType(kind)
+ if mojom.IsMapKind(kind):
+ return "decodeMapPointer(%s, %s)" % (
+ self._ElementCodecType(kind.key_kind),
+ self._ElementCodecType(kind.value_kind))
+ if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind):
+ return "decodeArrayPointer(codec.PackedBool)"
+ if mojom.IsArrayKind(kind):
+ return "decodeArrayPointer(%s)" % self._CodecType(kind.kind)
+ if mojom.IsUnionKind(kind):
+ return "decodeUnion(%s)" % self._CodecType(kind)
+ if mojom.IsEnumKind(kind):
+ return self._JavaScriptDecodeSnippet(mojom.INT32)
+ raise Exception("No decode snippet for %s" % kind)
+
+ def _JavaScriptEncodeSnippet(self, kind):
+ if (kind in mojom.PRIMITIVES or mojom.IsUnionKind(kind) or
+ mojom.IsAnyInterfaceKind(kind)):
+ return "encodeStruct(%s, " % self._CodecType(kind)
+ if mojom.IsUnionKind(kind):
+ return "encodeStruct(%s, " % self._JavaScriptType(kind)
+ if mojom.IsStructKind(kind):
+ return "encodeStructPointer(%s, " % self._JavaScriptType(kind)
+ if mojom.IsMapKind(kind):
+ return "encodeMapPointer(%s, %s, " % (
+ self._ElementCodecType(kind.key_kind),
+ self._ElementCodecType(kind.value_kind))
+ if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind):
+ return "encodeArrayPointer(codec.PackedBool, ";
+ if mojom.IsArrayKind(kind):
+ return "encodeArrayPointer(%s, " % self._CodecType(kind.kind)
+ if mojom.IsEnumKind(kind):
+ return self._JavaScriptEncodeSnippet(mojom.INT32)
+ raise Exception("No encode snippet for %s" % kind)
+
+ def _JavaScriptUnionDecodeSnippet(self, kind):
+ if mojom.IsUnionKind(kind):
+ return "decodeStructPointer(%s)" % self._JavaScriptType(kind)
+ return self._JavaScriptDecodeSnippet(kind)
+
+ def _JavaScriptUnionEncodeSnippet(self, kind):
+ if mojom.IsUnionKind(kind):
+ return "encodeStructPointer(%s, " % self._JavaScriptType(kind)
+ return self._JavaScriptEncodeSnippet(kind)
+
+ def _JavaScriptNullableParam(self, field):
+ return "true" if mojom.IsNullableKind(field.kind) else "false"
+
+ def _JavaScriptValidateArrayParams(self, field):
+ nullable = self._JavaScriptNullableParam(field)
+ element_kind = field.kind.kind
+ element_size = pack.PackedField.GetSizeForKind(element_kind)
+ expected_dimension_sizes = GetArrayExpectedDimensionSizes(
+ field.kind)
+ element_type = self._ElementCodecType(element_kind)
+ return "%s, %s, %s, %s, 0" % \
+ (element_size, element_type, nullable,
+ expected_dimension_sizes)
+
+ def _JavaScriptValidateEnumParams(self, field):
+ return self._JavaScriptType(field.kind)
+
+ def _JavaScriptValidateStructParams(self, field):
+ nullable = self._JavaScriptNullableParam(field)
+ struct_type = self._JavaScriptType(field.kind)
+ return "%s, %s" % (struct_type, nullable)
+
+ def _JavaScriptValidateUnionParams(self, field):
+ nullable = self._JavaScriptNullableParam(field)
+ union_type = self._JavaScriptType(field.kind)
+ return "%s, %s" % (union_type, nullable)
+
+ def _JavaScriptValidateMapParams(self, field):
+ nullable = self._JavaScriptNullableParam(field)
+ keys_type = self._ElementCodecType(field.kind.key_kind)
+ values_kind = field.kind.value_kind;
+ values_type = self._ElementCodecType(values_kind)
+ values_nullable = "true" if mojom.IsNullableKind(values_kind) else "false"
+ return "%s, %s, %s, %s" % \
+ (nullable, keys_type, values_type, values_nullable)
+
+ def _JavaScriptSanitizeIdentifier(self, identifier):
+ if identifier in _js_reserved_keywords:
+ return identifier + '_'
+
+ return identifier
+
+ def _TranslateConstants(self, token):
+ if isinstance(token, (mojom.EnumValue, mojom.NamedValue)):
+ # Both variable and enum constants are constructed like:
+ # NamespaceUid.Struct[.Enum].CONSTANT_NAME
+ name = []
+ if token.module and token.module.path != self.module.path:
+ name.append(token.module.unique_name)
+ if token.parent_kind:
+ name.append(token.parent_kind.name)
+ if isinstance(token, mojom.EnumValue):
+ name.append(token.enum.name)
+ name.append(token.name)
+ return ".".join(name)
+
+ if isinstance(token, mojom.BuiltinValue):
+ if token.value == "double.INFINITY" or token.value == "float.INFINITY":
+ return "Infinity";
+ if token.value == "double.NEGATIVE_INFINITY" or \
+ token.value == "float.NEGATIVE_INFINITY":
+ return "-Infinity";
+ if token.value == "double.NAN" or token.value == "float.NAN":
+ return "NaN";
+
+ return token
+
+ def _ExpressionToText(self, value):
+ return self._TranslateConstants(value)
+
+ def _GetStructsFromMethods(self):
+ result = []
+ for interface in self.module.interfaces:
+ for method in interface.methods:
+ result.append(method.param_struct)
+ if method.response_param_struct is not None:
+ result.append(method.response_param_struct)
+ return result
+
+ def _FuzzHandleName(self, kind):
+ if mojom.IsInterfaceRequestKind(kind):
+ return '{0}.{1}Request'.format(kind.kind.module.namespace,
+ kind.kind.name)
+ elif mojom.IsInterfaceKind(kind):
+ return '{0}.{1}Ptr'.format(kind.module.namespace,
+ kind.name)
+ elif mojom.IsAssociatedInterfaceRequestKind(kind):
+ return '{0}.{1}AssociatedRequest'.format(kind.kind.module.namespace,
+ kind.kind.name)
+ elif mojom.IsAssociatedInterfaceKind(kind):
+ return '{0}.{1}AssociatedPtr'.format(kind.kind.module.namespace,
+ kind.kind.name)
+ elif mojom.IsSharedBufferKind(kind):
+ return 'handle<shared_buffer>'
+ elif mojom.IsDataPipeConsumerKind(kind):
+ return 'handle<data_pipe_consumer>'
+ elif mojom.IsDataPipeProducerKind(kind):
+ return 'handle<data_pipe_producer>'
+ elif mojom.IsMessagePipeKind(kind):
+ return 'handle<message_pipe>'
+
+ def _ToJsBoolean(self, value):
+ if value:
+ return 'true'
+
+ return 'false'
+
+ def _IsPrimitiveKind(self, kind):
+ return kind in mojom.PRIMITIVES
+
+ def _PrimitiveToFuzzType(self, kind):
+ return _primitive_kind_to_fuzz_type[kind]
diff --git a/mojo/public/tools/bindings/mojom.gni b/mojo/public/tools/bindings/mojom.gni
index 4a244fb5b1..3329631470 100644
--- a/mojo/public/tools/bindings/mojom.gni
+++ b/mojo/public/tools/bindings/mojom.gni
@@ -2,6 +2,19 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import("//build/config/jumbo.gni")
+
+# TODO(rockot): Maybe we can factor these dependencies out of //mojo. They're
+# used to conditionally enable message ID scrambling in a way which is
+# consistent across toolchains and which is affected by branded vs non-branded
+# Chrome builds. Ideally we could create some generic knobs here that could be
+# flipped elsewhere though.
+import("//build/config/chrome_build.gni")
+import("//build/config/nacl/config.gni")
+import("//components/nacl/features.gni")
+import("//third_party/jinja2/jinja2.gni")
+import("//tools/ipc_fuzzer/ipc_fuzzer.gni")
+
declare_args() {
# Indicates whether typemapping should be supported in this build
# configuration. This may be disabled when building external projects which
@@ -12,8 +25,32 @@ declare_args() {
# with typemapping disabled, so it is never valid to set this to |false| in
# any Chromium build configuration.
enable_mojom_typemapping = true
+
+ # Controls message ID scrambling behavior. If |true|, message IDs are
+ # scrambled (i.e. randomized based on the contents of //chrome/VERSION) on
+ # non-Chrome OS desktop platforms. Set to |false| to disable message ID
+ # scrambling on all platforms.
+ enable_mojom_message_id_scrambling = true
}
+# NOTE: We would like to avoid scrambling message IDs where it doesn't add
+# value, so we limit the behavior to desktop builds for now. There is some
+# redundancy in the conditions here, but it is tolerated for clarity:
+# We're explicit about Mac, Windows, and Linux desktop support, but it's
+# also necessary to ensure that bindings in alternate toolchains (e.g.
+# NaCl IRT) are always consistent with the default toolchain; for that
+# reason we always enable scrambling within NaCl toolchains when possible,
+# as well as within the default toolchain when NaCl is enabled.
+#
+# Finally, because we *cannot* enable scrambling on Chrome OS (it would break
+# ARC) we have to explicitly opt out there even when NaCl is enabled (and
+# consequently also when building for NaCl toolchains.) For this reason we
+# check |target_os| explicitly, as it's consistent across all toolchains.
+enable_scrambled_message_ids =
+ enable_mojom_message_id_scrambling &&
+ (is_mac || is_win || (is_linux && !is_chromeos) ||
+ ((enable_nacl || is_nacl || is_nacl_nonsfi) && target_os != "chromeos"))
+
mojom_generator_root = "//mojo/public/tools/bindings"
mojom_generator_script = "$mojom_generator_root/mojom_bindings_generator.py"
mojom_generator_sources = [
@@ -36,6 +73,43 @@ mojom_generator_sources = [
"$mojom_generator_script",
]
+if (enable_scrambled_message_ids) {
+ declare_args() {
+ # The path to a file whose contents can be used as the basis for a message
+ # ID scrambling salt.
+ mojom_message_id_salt_path = "//chrome/VERSION"
+
+ # The path to a file whose contents will be concatenated to the contents of
+ # the file at |mojom_message_id_salt_path| to form a complete salt for
+ # message ID scrambling. May be the empty string, in which case the contents
+ # of the above file alone are used as the complete salt.
+ if (is_chrome_branded) {
+ mojom_message_id_salt_suffix_path =
+ "//mojo/internal/chrome-message-id-salt-suffix"
+ } else {
+ mojom_message_id_salt_suffix_path = ""
+ }
+ }
+
+ assert(mojom_message_id_salt_path != "")
+ message_scrambling_args = [
+ "--scrambled_message_id_salt_path",
+ rebase_path(mojom_message_id_salt_path, root_build_dir),
+ ]
+ message_scrambling_inputs = [ mojom_message_id_salt_path ]
+
+ if (mojom_message_id_salt_suffix_path != "") {
+ message_scrambling_args += [
+ "--scrambled_message_id_salt_path",
+ rebase_path(mojom_message_id_salt_suffix_path, root_build_dir),
+ ]
+ message_scrambling_inputs += [ mojom_message_id_salt_suffix_path ]
+ }
+} else {
+ message_scrambling_args = []
+ message_scrambling_inputs = []
+}
+
if (enable_mojom_typemapping) {
if (!is_ios) {
_bindings_configuration_files = [
@@ -58,23 +132,17 @@ if (enable_mojom_typemapping) {
_typemap_config = typemap.config
read_file(_typemap_config.mojom, "")
}
- if (is_mac && defined(configuration.typemaps_mac)) {
- foreach(typemap, configuration.typemaps_mac) {
- _typemap_config = {
- }
- _typemap_config = typemap.config
- read_file(_typemap_config.mojom, "")
- }
- }
}
} else {
_bindings_configuration_files = []
_bindings_configurations = [
{
typemaps = []
+ component_macro_suffix = ""
},
{
variant = "blink"
+ component_macro_suffix = "_BLINK"
for_blink = true
typemaps = []
},
@@ -89,8 +157,7 @@ if (enable_mojom_typemapping) {
# is the target name):
#
# foo
-# C++ and Javascript bindings. Other mojom targets should also depend on
-# this target.
+# C++ bindings.
#
# foo_blink
# C++ bindings using Blink standard types.
@@ -98,6 +165,12 @@ if (enable_mojom_typemapping) {
# foo_java
# Java bindings.
#
+# foo_js
+# JavaScript bindings; used as compile-time dependency.
+#
+# foo_js_data_deps
+# JavaScript bindings; used as run-time dependency.
+#
# Parameters:
#
# sources (optional if one of the deps sets listed below is present)
@@ -126,7 +199,7 @@ if (enable_mojom_typemapping) {
# use_once_callback (optional)
# If set to true, generated classes will use base::OnceCallback instead of
# base::RepeatingCallback.
-# Default value is false.
+# Default value is true.
# TODO(dcheng):
# - Convert everything to use OnceCallback.
# - Remove support for the old mode.
@@ -134,17 +207,63 @@ if (enable_mojom_typemapping) {
# cpp_only (optional)
# If set to true, only the C++ bindings targets will be generated.
#
-# use_new_js_bindings (optional)
-# If set to true, the generated JS code will use the new module loading
-# approach and the core API exposed by Web IDL.
+# support_lazy_serialization (optional)
+# If set to |true|, generated C++ bindings will effectively prefer to
+# transmit messages in an unserialized form when going between endpoints
+# in the same process. This avoids the runtime cost of serialization,
+# deserialization, and validation logic at the expensive of increased
+# code size. Defaults to |false|.
+#
+# disable_variants (optional)
+# If |true|, no variant sources will be generated for the target. Defaults
+# to |false|.
+#
+# disallow_native_types (optional)
+# If set to |true|, mojoms in this target may not apply the [Native]
+# attribute to struct or enum declarations. This avoids emitting code
+# which depends on legacy IPC serialization. Default is |false|, meaning
+# [Native] types are allowed.
#
-# TODO(yzshen): Switch all existing users to use_new_js_bindings=true and
-# remove the old mode.
+# disallow_interfaces (optional)
+# If set to |true|, mojoms in this target may not define interfaces.
+# Generates bindings with a smaller set of dependencies. Defaults to
+# |false|.
+#
+# scramble_message_ids (optional)
+# If set to |true| (the default), generated mojom interfaces will use
+# scrambled ordinal identifiers in encoded messages.
+#
+# component_output_prefix (optional)
+# The prefix to use for the output_name of any component library emitted
+# for generated C++ bindings. If this is omitted, C++ bindings targets are
+# emitted as source_sets instead. Because this controls the name of the
+# output shared library binary in the root output directory, it must be
+# unique across the entire build configuration.
+#
+# This is required if |component_macro_prefix| is specified.
+#
+# component_macro_prefix (optional)
+# This specifies a macro prefix to use for component export macros and
+# should therefore be globally unique in the project. For example if this
+# is "FOO_BAR", then the generated C++ sources will be built with
+# IS_FOO_BAR_{suffix}_IMPL defined, and the generated public headers will
+# annotate public symbol definitions with
+# COMPONENT_EXPORT(FOO_BAR_{suffix}). "suffix" in this case depends on
+# which internal subtarget is generating the code (e.g. "SHARED", or a
+# variant name like "BLINK").
+#
+# enabled_features (optional)
+# Definitions in a mojom file can be guarded by an EnableIf attribute. If
+# the value specified by the attribute does not match any items in the
+# list of enabled_features, the definition will be disabled, with no code
+# emitted for it.
#
# The following parameters are used to support the component build. They are
# needed so that bindings which are linked with a component can use the same
# export settings for classes. The first three are for the chromium variant, and
-# the last three are for the blink variant.
+# the last three are for the blink variant. These parameters are mutually
+# exclusive to |component_macro_prefix|, but |component_output_prefix| may still
+# be used to uniqueify the generated invariant (i.e. shared) output component.
# export_class_attribute (optional)
# The attribute to add to the class declaration. e.g. "CONTENT_EXPORT"
# export_define (optional)
@@ -170,16 +289,23 @@ if (enable_mojom_typemapping) {
# overridden_deps_blink (optional)
# component_deps_blink (optional)
# These two parameters are the blink variants of the previous two.
+#
+# check_includes_blink (optional)
+# Overrides the check_includes variable for the blink variant.
+# If check_includes_blink is not defined, the check_includes variable
+# retains its original value.
template("mojom") {
assert(
defined(invoker.sources) || defined(invoker.deps) ||
defined(invoker.public_deps),
"\"sources\" or \"deps\" must be defined for the $target_name template.")
+
if (defined(invoker.export_class_attribute) ||
defined(invoker.export_define) || defined(invoker.export_header)) {
assert(defined(invoker.export_class_attribute))
assert(defined(invoker.export_define))
assert(defined(invoker.export_header))
+ assert(!defined(invoker.component_macro_prefix))
}
if (defined(invoker.export_class_attribute_blink) ||
defined(invoker.export_define_blink) ||
@@ -187,6 +313,7 @@ template("mojom") {
assert(defined(invoker.export_class_attribute_blink))
assert(defined(invoker.export_define_blink))
assert(defined(invoker.export_header_blink))
+ assert(!defined(invoker.component_macro_prefix))
}
if (defined(invoker.overridden_deps) || defined(invoker.component_deps)) {
assert(defined(invoker.overridden_deps))
@@ -199,6 +326,11 @@ template("mojom") {
assert(defined(invoker.component_deps_blink))
}
+ require_full_cpp_deps =
+ !defined(invoker.disallow_native_types) ||
+ !invoker.disallow_native_types || !defined(invoker.disallow_interfaces) ||
+ !invoker.disallow_interfaces
+
all_deps = []
if (defined(invoker.deps)) {
all_deps += invoker.deps
@@ -207,6 +339,10 @@ template("mojom") {
all_deps += invoker.public_deps
}
+ if (defined(invoker.component_macro_prefix)) {
+ assert(defined(invoker.component_output_prefix))
+ }
+
group("${target_name}__is_mojom") {
}
@@ -221,22 +357,162 @@ template("mojom") {
}
}
+ target_sources_list = "$target_gen_dir/$target_name.sources_list"
+ sources_list = []
+ if (defined(invoker.sources)) {
+ sources_list = invoker.sources
+ }
+ write_file(target_sources_list, sources_list)
+
+ # a target implicitly depends on its own sources
+ deps_sources = [ rebase_path(target_sources_list, root_build_dir) ]
+ foreach(d, all_deps) {
+ dep_dir = get_label_info("$d", "target_gen_dir")
+ dep_short_name = get_label_info("$d", "name")
+ deps_sources +=
+ [ rebase_path("$dep_dir/$dep_short_name.sources_list", root_build_dir) ]
+ }
+
+ write_file("$target_gen_dir/$target_name.deps_sources_list", deps_sources)
+
+ if (defined(invoker.sources)) {
+ parser_target_name = "${target_name}__parser"
+ enabled_features = []
+ if (defined(invoker.enabled_features)) {
+ enabled_features += invoker.enabled_features
+ }
+ if (is_posix) {
+ enabled_features += [ "is_posix" ]
+ }
+ if (is_android) {
+ enabled_features += [ "is_android" ]
+ } else if (is_chromeos) {
+ enabled_features += [ "is_chromeos" ]
+ } else if (is_fuchsia) {
+ enabled_features += [ "is_fuchsia" ]
+ } else if (is_ios) {
+ enabled_features += [ "is_ios" ]
+ } else if (is_linux) {
+ enabled_features += [ "is_linux" ]
+ } else if (is_mac) {
+ enabled_features += [ "is_mac" ]
+ } else if (is_win) {
+ enabled_features += [ "is_win" ]
+ }
+
+ action(parser_target_name) {
+ script = mojom_generator_script
+ inputs = mojom_generator_sources + jinja2_sources
+ sources = invoker.sources
+ outputs = []
+ filelist = []
+ foreach(source, invoker.sources) {
+ filename = get_path_info("$source", "name")
+ dirname = get_path_info("$source", "gen_dir")
+ outputs += [ "$dirname/$filename.p" ]
+ filelist += [ rebase_path("$source", root_build_dir) ]
+ }
+
+ response_file_contents = filelist
+
+ args = [
+ "parse",
+ "--filelist={{response_file_name}}",
+ "-o",
+ rebase_path(root_gen_dir, root_build_dir),
+ "-d",
+ rebase_path("//", root_build_dir),
+ ]
+ foreach(enabled_feature, enabled_features) {
+ args += [
+ "--enable_feature",
+ enabled_feature,
+ ]
+ }
+ }
+ }
+
+ parsed_target_name = "${target_name}__parsed"
+ group(parsed_target_name) {
+ public_deps = []
+ if (defined(invoker.sources)) {
+ public_deps += [ ":$parser_target_name" ]
+ }
+ foreach(d, all_deps) {
+ # Resolve the name, so that a target //mojo/something becomes
+ # //mojo/something:something and we can append the parsed
+ # suffix to get the mojom dependency name.
+ full_name = get_label_info("$d", "label_no_toolchain")
+ public_deps += [ "${full_name}__parsed" ]
+ }
+ }
+
+ if (defined(invoker.sources)) {
+ verify_deps_target_names = []
+ if (!defined(invoker.skip_deps_check) || !invoker.skip_deps_check) {
+ verify_deps_target_name = "${target_name}__verify_deps"
+ verify_deps_target_names += [ ":$verify_deps_target_name" ]
+ source_file_name = target_name
+
+ action(verify_deps_target_name) {
+ script = mojom_generator_script
+ inputs = mojom_generator_sources + jinja2_sources
+ sources = invoker.sources
+ deps = [
+ ":$parsed_target_name",
+ ]
+ outputs = []
+ filelist = []
+ foreach(source, invoker.sources) {
+ filename = get_path_info("$source", "name")
+ dirname = get_path_info("$source", "gen_dir")
+ outputs += [ "$dirname/$filename.v" ]
+ filelist += [ rebase_path("$source", root_build_dir) ]
+ }
+
+ response_file_contents = filelist
+
+ args = [
+ "verify",
+ "--filelist={{response_file_name}}",
+ "-f",
+ rebase_path("$target_gen_dir/$source_file_name.deps_sources_list",
+ root_build_dir),
+ "--gen_dir",
+ rebase_path(root_gen_dir, root_build_dir),
+ "--depth",
+ rebase_path("//", root_build_dir),
+ ]
+ }
+ }
+ }
+
+ generator_cpp_message_ids_target_name = "${target_name}__generate_message_ids"
+
# Generate code that is shared by different variants.
if (defined(invoker.sources)) {
common_generator_args = [
"--use_bundled_pylibs",
"generate",
- "{{source}}",
"-d",
rebase_path("//", root_build_dir),
"-I",
rebase_path("//", root_build_dir),
"-o",
- rebase_path(root_gen_dir),
+ rebase_path(root_gen_dir, root_build_dir),
"--bytecode_path",
- rebase_path("$root_gen_dir/mojo/public/tools/bindings"),
+ rebase_path("$root_gen_dir/mojo/public/tools/bindings", root_build_dir),
]
+ if (defined(invoker.disallow_native_types) &&
+ invoker.disallow_native_types) {
+ common_generator_args += [ "--disallow_native_types" ]
+ }
+
+ if (defined(invoker.disallow_interfaces) && invoker.disallow_interfaces) {
+ common_generator_args += [ "--disallow_interfaces" ]
+ }
+
if (defined(invoker.import_dirs)) {
foreach(import_dir, invoker.import_dirs) {
common_generator_args += [
@@ -246,39 +522,122 @@ template("mojom") {
}
}
+ if (defined(invoker.component_macro_prefix)) {
+ shared_component_export_macro =
+ "COMPONENT_EXPORT(${invoker.component_macro_prefix}_SHARED)"
+ shared_component_impl_macro =
+ "IS_${invoker.component_macro_prefix}_SHARED_IMPL"
+ shared_component_output_name = "${invoker.component_output_prefix}_shared"
+ } else if (defined(invoker.export_class_attribute_shared) ||
+ defined(invoker.export_class_attribute)) {
+ if (defined(invoker.export_class_attribute_shared)) {
+ assert(defined(invoker.export_header_shared))
+ shared_component_export_macro = invoker.export_class_attribute_shared
+ shared_component_impl_macro = invoker.export_define_shared
+ } else {
+ assert(!defined(invoker.export_header_shared))
+
+ # If no explicit shared attribute/define was provided by the invoker,
+ # we derive some reasonable settings frorm the default variant.
+ shared_component_export_macro = "COMPONENT_EXPORT(MOJOM_SHARED_" +
+ invoker.export_class_attribute + ")"
+ shared_component_impl_macro =
+ "IS_MOJOM_SHARED_" + invoker.export_class_attribute + "_IMPL"
+ }
+
+ if (defined(invoker.component_output_prefix)) {
+ shared_component_output_name =
+ "${invoker.component_output_prefix}_shared"
+ } else {
+ shared_component_output_name = "${target_name}_shared"
+ }
+ }
+
+ action(generator_cpp_message_ids_target_name) {
+ script = mojom_generator_script
+ inputs = mojom_generator_sources + jinja2_sources
+ sources = invoker.sources
+ deps = [
+ ":$parsed_target_name",
+ "//mojo/public/tools/bindings:precompile_templates",
+ ]
+ outputs = []
+ args = common_generator_args
+ filelist = []
+ foreach(source, invoker.sources) {
+ outputs += [ "$target_gen_dir/$source-shared-message-ids.h" ]
+ filelist += [ rebase_path("$source", root_build_dir) ]
+ }
+
+ response_file_contents = filelist
+
+ args += [
+ "--filelist={{response_file_name}}",
+ "--generate_non_variant_code",
+ "--generate_message_ids",
+ "-g",
+ "c++",
+ ]
+
+ if (!defined(invoker.scramble_message_ids) ||
+ invoker.scramble_message_ids) {
+ inputs += message_scrambling_inputs
+ args += message_scrambling_args
+ }
+ }
+
generator_shared_cpp_outputs = [
"{{source_gen_dir}}/{{source_name_part}}.mojom-shared-internal.h",
"{{source_gen_dir}}/{{source_name_part}}.mojom-shared.cc",
"{{source_gen_dir}}/{{source_name_part}}.mojom-shared.h",
]
generator_shared_target_name = "${target_name}_shared__generator"
- action_foreach(generator_shared_target_name) {
+ action(generator_shared_target_name) {
script = mojom_generator_script
- inputs = mojom_generator_sources
+ inputs = mojom_generator_sources + jinja2_sources
sources = invoker.sources
deps = [
- "//mojo/public/tools/bindings:precompile_templates",
- ]
- outputs = generator_shared_cpp_outputs
+ ":$parsed_target_name",
+ "//mojo/public/tools/bindings:precompile_templates",
+ ] + verify_deps_target_names
+
+ outputs = []
args = common_generator_args
+ filelist = []
+ foreach(source, invoker.sources) {
+ filelist += [ rebase_path("$source", root_build_dir) ]
+ outputs += [
+ "$target_gen_dir/$source-shared-internal.h",
+ "$target_gen_dir/$source-shared.cc",
+ "$target_gen_dir/$source-shared.h",
+ ]
+ }
+
+ response_file_contents = filelist
+
args += [
+ "--filelist={{response_file_name}}",
"--generate_non_variant_code",
"-g",
"c++",
]
- depfile = "{{source_gen_dir}}/${generator_shared_target_name}_{{source_name_part}}.d"
- args += [
- "--depfile",
- depfile,
- "--depfile_target",
- "{{source_gen_dir}}/{{source_name_part}}.mojom-shared-internal.h",
- ]
+
+ if (defined(shared_component_export_macro)) {
+ args += [
+ "--export_attribute",
+ shared_component_export_macro,
+ "--export_header",
+ "base/component_export.h",
+ ]
+ }
+ }
+ } else {
+ group(generator_cpp_message_ids_target_name) {
}
}
- shared_cpp_sources_suffix = "shared_cpp_sources"
- shared_cpp_sources_target_name = "${target_name}_${shared_cpp_sources_suffix}"
- source_set(shared_cpp_sources_target_name) {
+ shared_cpp_sources_target_name = "${target_name}_shared_cpp_sources"
+ jumbo_source_set(shared_cpp_sources_target_name) {
if (defined(invoker.testonly)) {
testonly = invoker.testonly
}
@@ -288,18 +647,58 @@ template("mojom") {
process_file_template(invoker.sources, generator_shared_cpp_outputs)
deps += [ ":$generator_shared_target_name" ]
}
- public_deps = []
+ if (require_full_cpp_deps) {
+ public_deps = [
+ "//mojo/public/cpp/bindings",
+ ]
+ } else {
+ public_deps = [
+ "//mojo/public/cpp/bindings:bindings_base",
+ ]
+ }
foreach(d, all_deps) {
# Resolve the name, so that a target //mojo/something becomes
# //mojo/something:something and we can append shared_cpp_sources_suffix
# to get the cpp dependency name.
full_name = get_label_info("$d", "label_no_toolchain")
- public_deps += [ "${full_name}_${shared_cpp_sources_suffix}" ]
+ public_deps += [ "${full_name}_shared" ]
+ }
+ if (defined(shared_component_impl_macro)) {
+ defines = [ shared_component_impl_macro ]
+ }
+ }
+
+ shared_cpp_library_target_name = "${target_name}_shared"
+ if (defined(shared_component_output_name)) {
+ component(shared_cpp_library_target_name) {
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+ output_name = "$shared_component_output_name"
+ public_deps = [
+ ":$shared_cpp_sources_target_name",
+ ]
+ }
+ } else {
+ group(shared_cpp_library_target_name) {
+ if (defined(invoker.testonly)) {
+ testonly = invoker.testonly
+ }
+ public_deps = [
+ ":$shared_cpp_sources_target_name",
+ ]
}
}
# Generate code for variants.
- foreach(bindings_configuration, _bindings_configurations) {
+ if (!defined(invoker.disable_variants) || !invoker.disable_variants) {
+ enabled_configurations = _bindings_configurations
+ } else {
+ first_config = _bindings_configurations[0]
+ assert(!defined(first_config.variant))
+ enabled_configurations = [ first_config ]
+ }
+ foreach(bindings_configuration, enabled_configurations) {
cpp_only = false
if (defined(invoker.cpp_only)) {
cpp_only = invoker.cpp_only
@@ -317,8 +716,6 @@ template("mojom") {
enabled_sources = []
if (defined(invoker.sources)) {
generator_cpp_outputs = []
- generator_js_outputs = []
- generator_java_outputs = []
variant_dash_suffix = ""
if (defined(variant)) {
variant_dash_suffix = "-${variant}"
@@ -350,72 +747,88 @@ template("mojom") {
}
_typemap_config = typemap.config
if (get_path_info(source, "abspath") == _typemap_config.mojom) {
- active_typemaps += [ typemap ]
- }
- }
- if (is_mac && defined(bindings_configuration.typemaps_mac)) {
- foreach(typemap, bindings_configuration.typemaps_mac) {
- _typemap_config = {
+ enabled = false
+ if (!defined(_typemap_config.os_whitelist)) {
+ enabled = true
+ } else {
+ foreach(os, _typemap_config.os_whitelist) {
+ if (os == "android" && is_android) {
+ enabled = true
+ } else if (os == "chromeos" && is_chromeos) {
+ enabled = true
+ } else if (os == "fuchsia" && is_fuchsia) {
+ enabled = true
+ } else if (os == "ios" && is_ios) {
+ enabled = true
+ } else if (os == "linux" && is_linux) {
+ enabled = true
+ } else if (os == "mac" && is_mac) {
+ enabled = true
+ } else if (os == "posix" && is_posix) {
+ enabled = true
+ } else if (os == "win" && is_win) {
+ enabled = true
+ }
+ }
}
- _typemap_config = typemap.config
- if (get_path_info(source, "abspath") == _typemap_config.mojom) {
+ if (enabled) {
active_typemaps += [ typemap ]
}
}
}
}
- if (!cpp_only) {
- generator_js_outputs =
- [ "{{source_gen_dir}}/{{source_name_part}}.mojom.js" ]
- generator_java_outputs =
- [ "{{source_gen_dir}}/{{source_name_part}}.mojom.srcjar" ]
- }
generator_target_name = "${target_name}${variant_suffix}__generator"
- action_foreach(generator_target_name) {
+ action(generator_target_name) {
script = mojom_generator_script
- inputs = mojom_generator_sources
+ inputs = mojom_generator_sources + jinja2_sources
sources = invoker.sources
deps = [
- ":$type_mappings_target_name",
- "//mojo/public/tools/bindings:precompile_templates",
- ]
- outputs = generator_cpp_outputs + generator_java_outputs +
- generator_js_outputs
+ ":$parsed_target_name",
+ ":$type_mappings_target_name",
+ "//mojo/public/tools/bindings:precompile_templates",
+ ] + verify_deps_target_names
+ outputs = []
args = common_generator_args
-
- if (cpp_only) {
- args += [
- "-g",
- "c++",
- ]
- } else {
- args += [
- "-g",
- "c++,javascript,java",
+ filelist = []
+ foreach(source, invoker.sources) {
+ filelist += [ rebase_path("$source", root_build_dir) ]
+ outputs += [
+ "$target_gen_dir/${source}${variant_dash_suffix}.cc",
+ "$target_gen_dir/${source}${variant_dash_suffix}.h",
]
}
+ response_file_contents = filelist
+
+ args += [
+ "--filelist={{response_file_name}}",
+ "-g",
+ "c++",
+ ]
+
if (defined(bindings_configuration.variant)) {
args += [
"--variant",
bindings_configuration.variant,
]
}
- depfile =
- "{{source_gen_dir}}/${generator_target_name}_{{source_name_part}}.d"
- args += [
- "--depfile",
- depfile,
- "--depfile_target",
- "{{source_gen_dir}}/{{source_name_part}}.mojom${variant_dash_suffix}.cc",
- ]
args += [
"--typemap",
rebase_path(type_mappings_path, root_build_dir),
]
+ if (defined(invoker.component_macro_prefix)) {
+ args += [
+ "--export_attribute",
+ "COMPONENT_EXPORT(${invoker.component_macro_prefix}" +
+ "${bindings_configuration.component_macro_suffix})",
+ "--export_header",
+ "base/component_export.h",
+ ]
+ }
+
if (defined(bindings_configuration.for_blink) &&
bindings_configuration.for_blink) {
args += [ "--for_blink" ]
@@ -438,19 +851,20 @@ template("mojom") {
}
}
- if (defined(invoker.use_once_callback) && invoker.use_once_callback) {
+ if (!defined(invoker.use_once_callback) || invoker.use_once_callback) {
args += [ "--use_once_callback" ]
}
- if (defined(invoker.use_new_js_bindings) &&
- invoker.use_new_js_bindings) {
- args += [ "--use_new_js_bindings" ]
+ if (defined(invoker.support_lazy_serialization) &&
+ invoker.support_lazy_serialization) {
+ args += [ "--support_lazy_serialization" ]
}
}
}
action(type_mappings_target_name) {
- inputs = _bindings_configuration_files
+ inputs = _bindings_configuration_files + mojom_generator_sources +
+ jinja2_sources
outputs = [
type_mappings_path,
]
@@ -511,7 +925,16 @@ template("mojom") {
}
}
- source_set("${target_name}${variant_suffix}") {
+ if (defined(invoker.component_macro_prefix)) {
+ output_target_type = "component"
+ } else {
+ output_target_type = "source_set"
+ }
+
+ js_data_deps_target_name = target_name + "_js_data_deps"
+ not_needed([ "js_data_deps_target_name" ])
+
+ target("jumbo_" + output_target_type, "${target_name}${variant_suffix}") {
if (defined(bindings_configuration.for_blink) &&
bindings_configuration.for_blink &&
defined(invoker.visibility_blink)) {
@@ -522,13 +945,7 @@ template("mojom") {
if (defined(invoker.testonly)) {
testonly = invoker.testonly
}
- if (defined(invoker.sources) && !defined(bindings_configuration.variant)) {
- data = process_file_template(enabled_sources, generator_js_outputs)
- }
defines = []
- if (defined(invoker.testonly)) {
- testonly = invoker.testonly
- }
if (defined(invoker.export_define)) {
defines += [ invoker.export_define ]
}
@@ -539,18 +956,29 @@ template("mojom") {
sources = process_file_template(enabled_sources, generator_cpp_outputs)
}
deps = [
+ ":$generator_cpp_message_ids_target_name",
"//mojo/public/cpp/bindings:struct_traits",
"//mojo/public/interfaces/bindings:bindings__generator",
"//mojo/public/interfaces/bindings:bindings_shared__generator",
]
public_deps = [
- ":$shared_cpp_sources_target_name",
+ ":$shared_cpp_library_target_name",
"//base",
- "//mojo/public/cpp/bindings",
]
+ if (require_full_cpp_deps) {
+ public_deps += [ "//mojo/public/cpp/bindings" ]
+ } else {
+ public_deps += [ "//mojo/public/cpp/bindings:bindings_base" ]
+ }
+
if (enabled_sources != []) {
public_deps += [ ":$generator_target_name" ]
}
+ if (defined(invoker.component_macro_prefix)) {
+ output_name = "${invoker.component_output_prefix}${variant_suffix}"
+ defines += [ "IS_${invoker.component_macro_prefix}" +
+ "${bindings_configuration.component_macro_suffix}_IMPL" ]
+ }
foreach(d, all_deps) {
# Resolve the name, so that a target //mojo/something becomes
# //mojo/something:something and we can append variant_suffix to
@@ -570,7 +998,13 @@ template("mojom") {
}
public_deps += invoker.component_deps_blink
}
+ if (defined(invoker.check_includes_blink)) {
+ check_includes = invoker.check_includes_blink
+ }
} else {
+ if (defined(invoker.check_includes_blink)) {
+ not_needed(invoker, [ "check_includes_blink" ])
+ }
if (defined(invoker.overridden_deps)) {
foreach(d, invoker.overridden_deps) {
# Resolve the name, so that a target //mojo/something becomes
@@ -586,12 +1020,6 @@ template("mojom") {
_typemap_config = {
}
_typemap_config = typemap.config
- if (defined(_typemap_config.public_headers)) {
- sources += _typemap_config.public_headers
- }
- if (defined(_typemap_config.traits_headers)) {
- sources += _typemap_config.traits_headers
- }
if (defined(_typemap_config.sources)) {
sources += _typemap_config.sources
}
@@ -602,15 +1030,63 @@ template("mojom") {
deps += _typemap_config.deps
}
}
+ if (defined(invoker.export_header)) {
+ sources += [ "//" + invoker.export_header ]
+ }
if (defined(bindings_configuration.for_blink) &&
bindings_configuration.for_blink) {
public_deps += [ "//mojo/public/cpp/bindings:wtf_support" ]
}
+
+ if (enable_ipc_fuzzer) {
+ # Generate JS bindings by default if IPC fuzzer is enabled.
+ public_deps += [ ":$js_data_deps_target_name" ]
+ }
}
if (!cpp_only && is_android) {
import("//build/config/android/rules.gni")
+ java_generator_target_name = target_name + "_java__generator"
+ if (enabled_sources != []) {
+ generator_java_outputs =
+ [ "{{source_gen_dir}}/{{source_name_part}}.mojom.srcjar" ]
+ action(java_generator_target_name) {
+ script = mojom_generator_script
+ inputs = mojom_generator_sources + jinja2_sources
+ sources = enabled_sources
+ deps = [
+ ":$parsed_target_name",
+ ":$type_mappings_target_name",
+ "//mojo/public/tools/bindings:precompile_templates",
+ ] + verify_deps_target_names
+ outputs = []
+ args = common_generator_args
+ filelist = []
+ foreach(source, invoker.sources) {
+ filelist += [ rebase_path("$source", root_build_dir) ]
+ outputs += [ "$target_gen_dir/$source.srcjar" ]
+ }
+
+ response_file_contents = filelist
+
+ args += [
+ "--filelist={{response_file_name}}",
+ "-g",
+ "java",
+ ]
+
+ if (!defined(invoker.scramble_message_ids) ||
+ invoker.scramble_message_ids) {
+ inputs += message_scrambling_inputs
+ args += message_scrambling_args
+ }
+ }
+ } else {
+ group(java_generator_target_name) {
+ }
+ }
+
java_srcjar_target_name = target_name + "_java_sources"
action(java_srcjar_target_name) {
script = "//mojo/public/tools/gn/zip.py"
@@ -632,7 +1108,7 @@ template("mojom") {
deps = []
if (enabled_sources != []) {
deps = [
- ":$generator_target_name",
+ ":$java_generator_target_name",
]
}
}
@@ -645,6 +1121,9 @@ template("mojom") {
"//mojo/public/java:system_java",
]
+ # Disable warnings/checks on these generated files.
+ chromium_code = false
+
foreach(d, all_deps) {
# Resolve the name, so that a target //mojo/something becomes
# //mojo/something:something and we can append "_java" to get the java
@@ -654,8 +1133,104 @@ template("mojom") {
}
srcjar_deps = [ ":$java_srcjar_target_name" ]
- run_findbugs_override = false
}
}
}
+
+ if (enable_ipc_fuzzer || !defined(invoker.cpp_only) || !invoker.cpp_only) {
+ if (defined(invoker.sources)) {
+ generator_js_target_name = "${target_name}_js__generator"
+ generator_js_outputs = [
+ "{{source_gen_dir}}/{{source_name_part}}.mojom.js",
+ "{{source_gen_dir}}/{{source_name_part}}.mojom.externs.js",
+ ]
+ action(generator_js_target_name) {
+ script = mojom_generator_script
+ inputs = mojom_generator_sources + jinja2_sources
+ sources = []
+ if (defined(invoker.sources)) {
+ sources += invoker.sources
+ }
+ deps = [
+ ":$parsed_target_name",
+ "//mojo/public/tools/bindings:precompile_templates",
+ ] + verify_deps_target_names
+ outputs = []
+ args = common_generator_args
+ filelist = []
+ foreach(source, invoker.sources) {
+ filelist += [ rebase_path("$source", root_build_dir) ]
+ outputs += [
+ "$target_gen_dir/$source.js",
+ "$target_gen_dir/$source.externs.js",
+ ]
+ }
+
+ response_file_contents = filelist
+
+ args += [
+ "--filelist={{response_file_name}}",
+ "-g",
+ "javascript",
+ ]
+
+ if (!defined(invoker.scramble_message_ids) ||
+ invoker.scramble_message_ids) {
+ inputs += message_scrambling_inputs
+ args += message_scrambling_args
+ }
+
+ if (enable_ipc_fuzzer) {
+ args += [ "--generate_fuzzing" ]
+ }
+ }
+ }
+
+ js_target_name = target_name + "_js"
+ group(js_target_name) {
+ public_deps = []
+ if (defined(invoker.sources)) {
+ public_deps += [ ":$generator_js_target_name" ]
+ }
+
+ foreach(d, all_deps) {
+ full_name = get_label_info(d, "label_no_toolchain")
+ public_deps += [ "${full_name}_js" ]
+ }
+ }
+
+ group(js_data_deps_target_name) {
+ deps = []
+ if (defined(invoker.sources)) {
+ data = process_file_template(invoker.sources, generator_js_outputs)
+ deps += [ ":$generator_js_target_name" ]
+ }
+
+ data_deps = []
+ foreach(d, all_deps) {
+ full_name = get_label_info(d, "label_no_toolchain")
+ data_deps += [ "${full_name}_js_data_deps" ]
+ }
+ }
+ }
+}
+
+# A helper for the mojom() template above when component libraries are desired
+# for generated C++ bindings units. Supports all the same arguments as mojom()
+# except for the optional |component_output_prefix| and |component_macro_prefix|
+# arguments. These are instead shortened to |output_prefix| and |macro_prefix|
+# and are *required*.
+template("mojom_component") {
+ assert(defined(invoker.output_prefix) && defined(invoker.macro_prefix))
+
+ mojom(target_name) {
+ forward_variables_from(invoker,
+ "*",
+ [
+ "output_prefix",
+ "macro_prefix",
+ ])
+ component_output_prefix = invoker.output_prefix
+ component_macro_prefix = invoker.macro_prefix
+ }
}
diff --git a/mojo/public/tools/bindings/mojom_bindings_generator.py b/mojo/public/tools/bindings/mojom_bindings_generator.py
index a9650d7764..57a803178e 100755
--- a/mojo/public/tools/bindings/mojom_bindings_generator.py
+++ b/mojo/public/tools/bindings/mojom_bindings_generator.py
@@ -7,11 +7,14 @@
import argparse
-import imp
+import cPickle
+import hashlib
+import importlib
import json
import os
import pprint
import re
+import struct
import sys
# Disable lint check for finding modules:
@@ -37,15 +40,17 @@ sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
from mojom.error import Error
import mojom.fileutil as fileutil
-from mojom.generate import translate
from mojom.generate import template_expander
+from mojom.generate import translate
+from mojom.generate.generator import AddComputedData, WriteFile
+from mojom.parse.conditional_features import RemoveDisabledDefinitions
from mojom.parse.parser import Parse
_BUILTIN_GENERATORS = {
- "c++": "mojom_cpp_generator.py",
- "javascript": "mojom_js_generator.py",
- "java": "mojom_java_generator.py",
+ "c++": "mojom_cpp_generator",
+ "javascript": "mojom_js_generator",
+ "java": "mojom_java_generator",
}
@@ -53,18 +58,14 @@ def LoadGenerators(generators_string):
if not generators_string:
return [] # No generators.
- script_dir = os.path.dirname(os.path.abspath(__file__))
generators = {}
for generator_name in [s.strip() for s in generators_string.split(",")]:
language = generator_name.lower()
- if language in _BUILTIN_GENERATORS:
- generator_name = os.path.join(script_dir, "generators",
- _BUILTIN_GENERATORS[language])
- else:
+ if language not in _BUILTIN_GENERATORS:
print "Unknown generator name %s" % generator_name
sys.exit(1)
- generator_module = imp.load_source(os.path.basename(generator_name)[:-3],
- generator_name)
+ generator_module = importlib.import_module(
+ "generators.%s" % _BUILTIN_GENERATORS[language])
generators[language] = generator_module
return generators
@@ -100,6 +101,43 @@ def FindImportFile(rel_dir, file_name, search_rel_dirs):
rel_dir.source_root)
+def ScrambleMethodOrdinals(interfaces, salt):
+ already_generated = set()
+ for interface in interfaces:
+ i = 0
+ already_generated.clear()
+ for method in interface.methods:
+ while True:
+ i = i + 1
+ if i == 1000000:
+ raise Exception("Could not generate %d method ordinals for %s" %
+ (len(interface.methods), interface.mojom_name))
+ # Generate a scrambled method.ordinal value. The algorithm doesn't have
+ # to be very strong, cryptographically. It just needs to be non-trivial
+ # to guess the results without the secret salt, in order to make it
+ # harder for a compromised process to send fake Mojo messages.
+ sha256 = hashlib.sha256(salt)
+ sha256.update(interface.mojom_name)
+ sha256.update(str(i))
+ # Take the first 4 bytes as a little-endian uint32.
+ ordinal = struct.unpack('<L', sha256.digest()[:4])[0]
+ # Trim to 31 bits, so it always fits into a Java (signed) int.
+ ordinal = ordinal & 0x7fffffff
+ if ordinal in already_generated:
+ continue
+ already_generated.add(ordinal)
+ method.ordinal = ordinal
+ method.ordinal_comment = (
+ 'The %s value is based on sha256(salt + "%s%d").' %
+ (ordinal, interface.mojom_name, i))
+ break
+
+
+def ReadFileContents(filename):
+ with open(filename, 'rb') as f:
+ return f.read()
+
+
class MojomProcessor(object):
"""Parses mojom files and creates ASTs for them.
@@ -110,7 +148,6 @@ class MojomProcessor(object):
def __init__(self, should_generate):
self._should_generate = should_generate
self._processed_files = {}
- self._parsed_files = {}
self._typemap = {}
def LoadTypemaps(self, typemaps):
@@ -126,21 +163,20 @@ class MojomProcessor(object):
language_map.update(typemap)
self._typemap[language] = language_map
- def ProcessFile(self, args, remaining_args, generator_modules, filename):
- self._ParseFileAndImports(RelativePath(filename, args.depth),
- args.import_directories, [])
-
- return self._GenerateModule(args, remaining_args, generator_modules,
- RelativePath(filename, args.depth))
-
def _GenerateModule(self, args, remaining_args, generator_modules,
- rel_filename):
+ rel_filename, imported_filename_stack):
# Return the already-generated module.
if rel_filename.path in self._processed_files:
return self._processed_files[rel_filename.path]
- tree = self._parsed_files[rel_filename.path]
- dirname, name = os.path.split(rel_filename.path)
+ if rel_filename.path in imported_filename_stack:
+ print "%s: Error: Circular dependency" % rel_filename.path + \
+ MakeImportStackMessage(imported_filename_stack + [rel_filename.path])
+ sys.exit(1)
+
+ tree = _UnpickleAST(_FindPicklePath(rel_filename, args.gen_directories +
+ [args.output_dir]))
+ dirname = os.path.dirname(rel_filename.path)
# Process all our imports first and collect the module object for each.
# We use these to generate proper type info.
@@ -150,27 +186,37 @@ class MojomProcessor(object):
RelativePath(dirname, rel_filename.source_root),
parsed_imp.import_filename, args.import_directories)
imports[parsed_imp.import_filename] = self._GenerateModule(
- args, remaining_args, generator_modules, rel_import_file)
+ args, remaining_args, generator_modules, rel_import_file,
+ imported_filename_stack + [rel_filename.path])
- module = translate.OrderedModule(tree, name, imports)
+ # Set the module path as relative to the source root.
+ # Normalize to unix-style path here to keep the generators simpler.
+ module_path = rel_filename.relative_path().replace('\\', '/')
- # Set the path as relative to the source root.
- module.path = rel_filename.relative_path()
+ module = translate.OrderedModule(tree, module_path, imports)
- # Normalize to unix-style path here to keep the generators simpler.
- module.path = module.path.replace('\\', '/')
+ if args.scrambled_message_id_salt_paths:
+ salt = ''.join(
+ map(ReadFileContents, args.scrambled_message_id_salt_paths))
+ ScrambleMethodOrdinals(module.interfaces, salt)
if self._should_generate(rel_filename.path):
+ AddComputedData(module)
for language, generator_module in generator_modules.iteritems():
generator = generator_module.Generator(
module, args.output_dir, typemap=self._typemap.get(language, {}),
variant=args.variant, bytecode_path=args.bytecode_path,
for_blink=args.for_blink,
use_once_callback=args.use_once_callback,
- use_new_js_bindings=args.use_new_js_bindings,
+ js_bindings_mode=args.js_bindings_mode,
export_attribute=args.export_attribute,
export_header=args.export_header,
- generate_non_variant_code=args.generate_non_variant_code)
+ generate_non_variant_code=args.generate_non_variant_code,
+ support_lazy_serialization=args.support_lazy_serialization,
+ disallow_native_types=args.disallow_native_types,
+ disallow_interfaces=args.disallow_interfaces,
+ generate_message_ids=args.generate_message_ids,
+ generate_fuzzing=args.generate_fuzzing)
filtered_args = []
if hasattr(generator_module, 'GENERATOR_PREFIX'):
prefix = '--' + generator_module.GENERATOR_PREFIX + '_'
@@ -182,42 +228,6 @@ class MojomProcessor(object):
self._processed_files[rel_filename.path] = module
return module
- def _ParseFileAndImports(self, rel_filename, import_directories,
- imported_filename_stack):
- # Ignore already-parsed files.
- if rel_filename.path in self._parsed_files:
- return
-
- if rel_filename.path in imported_filename_stack:
- print "%s: Error: Circular dependency" % rel_filename.path + \
- MakeImportStackMessage(imported_filename_stack + [rel_filename.path])
- sys.exit(1)
-
- try:
- with open(rel_filename.path) as f:
- source = f.read()
- except IOError as e:
- print "%s: Error: %s" % (rel_filename.path, e.strerror) + \
- MakeImportStackMessage(imported_filename_stack + [rel_filename.path])
- sys.exit(1)
-
- try:
- tree = Parse(source, rel_filename.path)
- except Error as e:
- full_stack = imported_filename_stack + [rel_filename.path]
- print str(e) + MakeImportStackMessage(full_stack)
- sys.exit(1)
-
- dirname = os.path.split(rel_filename.path)[0]
- for imp_entry in tree.import_list:
- import_file_entry = FindImportFile(
- RelativePath(dirname, rel_filename.source_root),
- imp_entry.import_filename, import_directories)
- self._ParseFileAndImports(import_file_entry, import_directories,
- imported_filename_stack + [rel_filename.path])
-
- self._parsed_files[rel_filename.path] = tree
-
def _Generate(args, remaining_args):
if args.variant == "none":
@@ -235,15 +245,78 @@ def _Generate(args, remaining_args):
processor = MojomProcessor(lambda filename: filename in args.filename)
processor.LoadTypemaps(set(args.typemaps))
+
+ if args.filelist:
+ with open(args.filelist) as f:
+ args.filename.extend(f.read().split())
+
for filename in args.filename:
- processor.ProcessFile(args, remaining_args, generator_modules, filename)
- if args.depfile:
- assert args.depfile_target
- with open(args.depfile, 'w') as f:
- f.write('%s: %s' % (
- args.depfile_target,
- ' '.join(processor._parsed_files.keys())))
+ processor._GenerateModule(args, remaining_args, generator_modules,
+ RelativePath(filename, args.depth), [])
+
+ return 0
+
+
+def _FindPicklePath(rel_filename, search_dirs):
+ filename, _ = os.path.splitext(rel_filename.relative_path())
+ pickle_path = filename + '.p'
+ for search_dir in search_dirs:
+ path = os.path.join(search_dir, pickle_path)
+ if os.path.isfile(path):
+ return path
+ raise Exception("%s: Error: Could not find file in %r" % (pickle_path, search_dirs))
+
+
+def _GetPicklePath(rel_filename, output_dir):
+ filename, _ = os.path.splitext(rel_filename.relative_path())
+ pickle_path = filename + '.p'
+ return os.path.join(output_dir, pickle_path)
+
+
+def _PickleAST(ast, output_file):
+ full_dir = os.path.dirname(output_file)
+ fileutil.EnsureDirectoryExists(full_dir)
+
+ try:
+ WriteFile(cPickle.dumps(ast), output_file)
+ except (IOError, cPickle.PicklingError) as e:
+ print "%s: Error: %s" % (output_file, str(e))
+ sys.exit(1)
+
+def _UnpickleAST(input_file):
+ try:
+ with open(input_file, "rb") as f:
+ return cPickle.load(f)
+ except (IOError, cPickle.UnpicklingError) as e:
+ print "%s: Error: %s" % (input_file, str(e))
+ sys.exit(1)
+
+def _ParseFile(args, rel_filename):
+ try:
+ with open(rel_filename.path) as f:
+ source = f.read()
+ except IOError as e:
+ print "%s: Error: %s" % (rel_filename.path, e.strerror)
+ sys.exit(1)
+
+ try:
+ tree = Parse(source, rel_filename.path)
+ RemoveDisabledDefinitions(tree, args.enabled_features)
+ except Error as e:
+ print "%s: Error: %s" % (rel_filename.path, str(e))
+ sys.exit(1)
+ _PickleAST(tree, _GetPicklePath(rel_filename, args.output_dir))
+
+
+def _Parse(args, _):
+ fileutil.EnsureDirectoryExists(args.output_dir)
+ if args.filelist:
+ with open(args.filelist) as f:
+ args.filename.extend(f.read().split())
+
+ for filename in args.filename:
+ _ParseFile(args, RelativePath(filename, args.depth))
return 0
@@ -253,7 +326,46 @@ def _Precompile(args, _):
template_expander.PrecompileTemplates(generator_modules, args.output_dir)
return 0
+def _VerifyImportDeps(args, __):
+ fileutil.EnsureDirectoryExists(args.gen_dir)
+
+ if args.filelist:
+ with open(args.filelist) as f:
+ args.filename.extend(f.read().split())
+ for filename in args.filename:
+ rel_path = RelativePath(filename, args.depth)
+ tree = _UnpickleAST(_GetPicklePath(rel_path, args.gen_dir))
+
+ mojom_imports = set(
+ parsed_imp.import_filename for parsed_imp in tree.import_list
+ )
+
+ # read the paths from the file
+ f_deps = open(args.deps_file, 'r')
+ deps_sources = set()
+ for deps_path in f_deps:
+ deps_path = deps_path.rstrip('\n')
+ f_sources = open(deps_path, 'r')
+
+ for source_file in f_sources:
+ source_dir = deps_path.split(args.gen_dir + "/", 1)[1]
+ full_source_path = os.path.dirname(source_dir) + "/" + \
+ source_file
+ deps_sources.add(full_source_path.rstrip('\n'))
+
+ if (not deps_sources.issuperset(mojom_imports)):
+ print ">>> [%s] Missing dependencies for the following imports: %s" % ( \
+ args.filename[0], \
+ list(mojom_imports.difference(deps_sources)))
+ sys.exit(1)
+
+ source_filename, _ = os.path.splitext(rel_path.relative_path())
+ output_file = source_filename + '.v'
+ output_file_path = os.path.join(args.gen_dir, output_file)
+ WriteFile("", output_file_path)
+
+ return 0
def main():
parser = argparse.ArgumentParser(
@@ -262,10 +374,35 @@ def main():
help="use Python modules bundled in the SDK")
subparsers = parser.add_subparsers()
+
+ parse_parser = subparsers.add_parser(
+ "parse", description="Parse mojom to AST and remove disabled definitions."
+ " Pickle pruned AST into output_dir.")
+ parse_parser.add_argument("filename", nargs="*", help="mojom input file")
+ parse_parser.add_argument("--filelist", help="mojom input file list")
+ parse_parser.add_argument(
+ "-o",
+ "--output_dir",
+ dest="output_dir",
+ default=".",
+ help="output directory for generated files")
+ parse_parser.add_argument(
+ "-d", "--depth", dest="depth", default=".", help="depth from source root")
+ parse_parser.add_argument(
+ "--enable_feature",
+ dest = "enabled_features",
+ default=[],
+ action="append",
+ help="Controls which definitions guarded by an EnabledIf attribute "
+ "will be enabled. If an EnabledIf attribute does not specify a value "
+ "that matches one of the enabled features, it will be disabled.")
+ parse_parser.set_defaults(func=_Parse)
+
generate_parser = subparsers.add_parser(
"generate", description="Generate bindings from mojom files.")
- generate_parser.add_argument("filename", nargs="+",
+ generate_parser.add_argument("filename", nargs="*",
help="mojom input file")
+ generate_parser.add_argument("--filelist", help="mojom input file list")
generate_parser.add_argument("-d", "--depth", dest="depth", default=".",
help="depth from source root")
generate_parser.add_argument("-o", "--output_dir", dest="output_dir",
@@ -277,6 +414,9 @@ def main():
default="c++,javascript,java",
help="comma-separated list of generators")
generate_parser.add_argument(
+ "--gen_dir", dest="gen_directories", action="append", metavar="directory",
+ default=[], help="add a directory to be searched for the syntax trees.")
+ generate_parser.add_argument(
"-I", dest="import_directories", action="append", metavar="directory",
default=[],
help="add a directory to be searched for import files. The depth from "
@@ -288,7 +428,7 @@ def main():
generate_parser.add_argument("--variant", dest="variant", default=None,
help="output a named variant of the bindings")
generate_parser.add_argument(
- "--bytecode_path", type=str, required=True, help=(
+ "--bytecode_path", required=True, help=(
"the path from which to load template bytecode; to generate template "
"bytecode, run %s precompile BYTECODE_PATH" % os.path.basename(
sys.argv[0])))
@@ -299,26 +439,52 @@ def main():
"--use_once_callback", action="store_true",
help="Use base::OnceCallback instead of base::RepeatingCallback.")
generate_parser.add_argument(
- "--use_new_js_bindings", action="store_true",
- help="Use the new module loading approach and the core API exposed by "
- "Web IDL. This option only affects the JavaScript bindings.")
+ "--js_bindings_mode", choices=["new", "both", "old"], default="new",
+ help="This option only affects the JavaScript bindings. The value could "
+ "be: \"new\" - generate only the new-style JS bindings, which use the "
+ "new module loading approach and the core api exposed by Web IDL; "
+ "\"both\" - generate both the old- and new-style bindings; \"old\" - "
+ "generate only the old-style bindings.")
generate_parser.add_argument(
- "--export_attribute", type=str, default="",
+ "--export_attribute", default="",
help="Optional attribute to specify on class declaration to export it "
"for the component build.")
generate_parser.add_argument(
- "--export_header", type=str, default="",
+ "--export_header", default="",
help="Optional header to include in the generated headers to support the "
"component build.")
generate_parser.add_argument(
"--generate_non_variant_code", action="store_true",
help="Generate code that is shared by different variants.")
generate_parser.add_argument(
- "--depfile", type=str,
- help="A file into which the list of input files will be written.")
+ "--scrambled_message_id_salt_path",
+ dest="scrambled_message_id_salt_paths",
+ help="If non-empty, the path to a file whose contents should be used as"
+ "a salt for generating scrambled message IDs. If this switch is specified"
+ "more than once, the contents of all salt files are concatenated to form"
+ "the salt value.", default=[], action="append")
+ generate_parser.add_argument(
+ "--support_lazy_serialization",
+ help="If set, generated bindings will serialize lazily when possible.",
+ action="store_true")
generate_parser.add_argument(
- "--depfile_target", type=str,
- help="The target name to use in the depfile.")
+ "--disallow_native_types",
+ help="Disallows the [Native] attribute to be specified on structs or "
+ "enums within the mojom file.", action="store_true")
+ generate_parser.add_argument(
+ "--disallow_interfaces",
+ help="Disallows interface definitions within the mojom file. It is an "
+ "error to specify this flag when processing a mojom file which defines "
+ "any interface.", action="store_true")
+ generate_parser.add_argument(
+ "--generate_message_ids",
+ help="Generates only the message IDs header for C++ bindings. Note that "
+ "this flag only matters if --generate_non_variant_code is also "
+ "specified.", action="store_true")
+ generate_parser.add_argument(
+ "--generate_fuzzing",
+ action="store_true",
+ help="Generates additional bindings for fuzzing in JS.")
generate_parser.set_defaults(func=_Generate)
precompile_parser = subparsers.add_parser("precompile",
@@ -328,6 +494,23 @@ def main():
help="output directory for precompiled templates")
precompile_parser.set_defaults(func=_Precompile)
+ verify_parser = subparsers.add_parser("verify", description="Checks "
+ "the set of imports against the set of dependencies.")
+ verify_parser.add_argument("filename", nargs="*",
+ help="mojom input file")
+ verify_parser.add_argument("--filelist", help="mojom input file list")
+ verify_parser.add_argument("-f", "--file", dest="deps_file",
+ help="file containing paths to the sources files for "
+ "dependencies")
+ verify_parser.add_argument("-g", "--gen_dir",
+ dest="gen_dir",
+ help="directory with the syntax tree")
+ verify_parser.add_argument(
+ "-d", "--depth", dest="depth",
+ help="depth from source root")
+
+ verify_parser.set_defaults(func=_VerifyImportDeps)
+
args, remaining_args = parser.parse_known_args()
return args.func(args, remaining_args)
diff --git a/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py b/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
index de388561cb..bcffbbb116 100644
--- a/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
+++ b/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
@@ -5,6 +5,19 @@
import unittest
from mojom_bindings_generator import MakeImportStackMessage
+from mojom_bindings_generator import ScrambleMethodOrdinals
+
+
+class FakeIface(object):
+ def __init__( self ):
+ self.name = None
+ self.methods = None
+
+
+class FakeMethod(object):
+ def __init__( self ):
+ self.ordinal = None
+ self.ordinal_comment = None
class MojoBindingsGeneratorTest(unittest.TestCase):
@@ -18,6 +31,23 @@ class MojoBindingsGeneratorTest(unittest.TestCase):
self.assertEquals(MakeImportStackMessage(["x", "y", "z"]),
"\n z was imported by y\n y was imported by x")
+ def testScrambleMethodOrdinals(self):
+ """Tests ScrambleMethodOrdinals()."""
+ interface = FakeIface()
+ interface.name = 'RendererConfiguration'
+ interface.methods = [FakeMethod(), FakeMethod(), FakeMethod()]
+ ScrambleMethodOrdinals([interface], "foo")
+ # These next three values are hard-coded. If the generation algorithm
+ # changes from being based on sha256(seed + interface.name + str(i)) then
+ # these numbers will obviously need to change too.
+ #
+ # Note that hashlib.sha256('fooRendererConfiguration1').digest()[:4] is
+ # '\xa5\xbc\xf9\xca' and that hex(1257880741) = '0x4af9bca5'. The
+ # difference in 0x4a vs 0xca is because we only take 31 bits.
+ self.assertEquals(interface.methods[0].ordinal, 1257880741)
+ self.assertEquals(interface.methods[1].ordinal, 631133653)
+ self.assertEquals(interface.methods[2].ordinal, 549336076)
+
if __name__ == "__main__":
unittest.main()
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/generator.py b/mojo/public/tools/bindings/pylib/mojom/generate/generator.py
index 0e64af78a1..acf029f6a1 100644
--- a/mojo/public/tools/bindings/pylib/mojom/generate/generator.py
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/generator.py
@@ -12,142 +12,191 @@ import module as mojom
import mojom.fileutil as fileutil
import pack
+
def ExpectedArraySize(kind):
if mojom.IsArrayKind(kind):
return kind.length
return None
-def StudlyCapsToCamel(studly):
- return studly[0].lower() + studly[1:]
-def UnderToCamel(under):
- """Converts underscore_separated strings to CamelCase strings."""
- return ''.join(word.capitalize() for word in under.split('_'))
+def ToCamel(identifier, lower_initial=False, dilimiter='_'):
+ """Splits |identifier| using |dilimiter|, makes the first character of each
+ word uppercased (but makes the first character of the first word lowercased
+ if |lower_initial| is set to True), and joins the words. Please note that for
+ each word, all the characters except the first one are untouched.
+ """
+ result = ''.join(word[0].upper() + word[1:]
+ for word in identifier.split(dilimiter) if word)
+ if lower_initial and result:
+ result = result[0].lower() + result[1:]
+ return result
-def WriteFile(contents, full_path):
- # Make sure the containing directory exists.
- full_dir = os.path.dirname(full_path)
- fileutil.EnsureDirectoryExists(full_dir)
- # Dump the data to disk.
- with open(full_path, "w+") as f:
- f.write(contents)
+class Stylizer(object):
+ """Stylizers specify naming rules to map mojom names to names in generated
+ code. For example, if you would like method_name in mojom to be mapped to
+ MethodName in the generated code, you need to define a subclass of Stylizer
+ and override StylizeMethod to do the conversion."""
-class Generator(object):
- # Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all
- # files to stdout.
- def __init__(self, module, output_dir=None, typemap=None, variant=None,
- bytecode_path=None, for_blink=False, use_once_callback=False,
- use_new_js_bindings=False, export_attribute=None,
- export_header=None, generate_non_variant_code=False):
- self.module = module
- self.output_dir = output_dir
- self.typemap = typemap or {}
- self.variant = variant
- self.bytecode_path = bytecode_path
- self.for_blink = for_blink
- self.use_once_callback = use_once_callback
- self.use_new_js_bindings = use_new_js_bindings
- self.export_attribute = export_attribute
- self.export_header = export_header
- self.generate_non_variant_code = generate_non_variant_code
+ def StylizeConstant(self, mojom_name):
+ return mojom_name
- def GetStructsFromMethods(self):
- result = []
- for interface in self.module.interfaces:
- for method in interface.methods:
- result.append(self._GetStructFromMethod(method))
- if method.response_parameters != None:
- result.append(self._GetResponseStructFromMethod(method))
- return result
+ def StylizeField(self, mojom_name):
+ return mojom_name
- def GetStructs(self):
- return map(partial(self._AddStructComputedData, True), self.module.structs)
+ def StylizeStruct(self, mojom_name):
+ return mojom_name
- def GetUnions(self):
- return map(self._AddUnionComputedData, self.module.unions)
+ def StylizeUnion(self, mojom_name):
+ return mojom_name
- def GetInterfaces(self):
- return map(self._AddInterfaceComputedData, self.module.interfaces)
+ def StylizeParameter(self, mojom_name):
+ return mojom_name
- # Prepend the filename with a directory that matches the directory of the
- # original .mojom file, relative to the import root.
- def MatchMojomFilePath(self, filename):
- return os.path.join(os.path.dirname(self.module.path), filename)
+ def StylizeMethod(self, mojom_name):
+ return mojom_name
- def Write(self, contents, filename):
- if self.output_dir is None:
- print contents
- return
- full_path = os.path.join(self.output_dir, filename)
- WriteFile(contents, full_path)
+ def StylizeInterface(self, mojom_name):
+ return mojom_name
- def GenerateFiles(self, args):
- raise NotImplementedError("Subclasses must override/implement this method")
+ def StylizeEnumField(self, mojom_name):
+ return mojom_name
- def GetJinjaParameters(self):
- """Returns default constructor parameters for the jinja environment."""
- return {}
+ def StylizeEnum(self, mojom_name):
+ return mojom_name
+
+ def StylizeModule(self, mojom_namespace):
+ return mojom_namespace
+
+
+def WriteFile(contents, full_path):
+ # If |contents| is same with the file content, we skip updating.
+ if os.path.isfile(full_path):
+ with open(full_path, 'rb') as destination_file:
+ if destination_file.read() == contents:
+ return
+
+ # Make sure the containing directory exists.
+ full_dir = os.path.dirname(full_path)
+ fileutil.EnsureDirectoryExists(full_dir)
+
+ # Dump the data to disk.
+ with open(full_path, "wb") as f:
+ f.write(contents)
- def GetGlobals(self):
- """Returns global mappings for the template generation."""
- return {}
- def _AddStructComputedData(self, exported, struct):
- """Adds computed data to the given struct. The data is computed once and
- used repeatedly in the generation process."""
+def AddComputedData(module):
+ """Adds computed data to the given module. The data is computed once and
+ used repeatedly in the generation process."""
+
+ def _AddStructComputedData(exported, struct):
struct.packed = pack.PackedStruct(struct)
struct.bytes = pack.GetByteLayout(struct.packed)
struct.versions = pack.GetVersionInfo(struct.packed)
struct.exported = exported
- return struct
- def _AddUnionComputedData(self, union):
- """Adds computed data to the given union. The data is computed once and
- used repeatedly in the generation process."""
+ def _AddUnionComputedData(union):
ordinal = 0
for field in union.fields:
if field.ordinal is not None:
ordinal = field.ordinal
field.ordinal = ordinal
ordinal += 1
- return union
- def _AddInterfaceComputedData(self, interface):
- """Adds computed data to the given interface. The data is computed once and
- used repeatedly in the generation process."""
+ def _AddInterfaceComputedData(interface):
+ next_ordinal = 0
interface.version = 0
for method in interface.methods:
+ if method.ordinal is None:
+ method.ordinal = next_ordinal
+ next_ordinal = method.ordinal + 1
+
if method.min_version is not None:
interface.version = max(interface.version, method.min_version)
- method.param_struct = self._GetStructFromMethod(method)
+ method.param_struct = _GetStructFromMethod(method)
interface.version = max(interface.version,
method.param_struct.versions[-1].version)
if method.response_parameters is not None:
- method.response_param_struct = self._GetResponseStructFromMethod(method)
+ method.response_param_struct = _GetResponseStructFromMethod(method)
interface.version = max(
interface.version,
method.response_param_struct.versions[-1].version)
else:
method.response_param_struct = None
- return interface
- def _GetStructFromMethod(self, method):
+ def _GetStructFromMethod(method):
"""Converts a method's parameters into the fields of a struct."""
- params_class = "%s_%s_Params" % (method.interface.name, method.name)
+ params_class = "%s_%s_Params" % (method.interface.mojom_name,
+ method.mojom_name)
struct = mojom.Struct(params_class, module=method.interface.module)
for param in method.parameters:
- struct.AddField(param.name, param.kind, param.ordinal,
+ struct.AddField(param.mojom_name, param.kind, param.ordinal,
attributes=param.attributes)
- return self._AddStructComputedData(False, struct)
+ _AddStructComputedData(False, struct)
+ return struct
- def _GetResponseStructFromMethod(self, method):
+ def _GetResponseStructFromMethod(method):
"""Converts a method's response_parameters into the fields of a struct."""
- params_class = "%s_%s_ResponseParams" % (method.interface.name, method.name)
+ params_class = "%s_%s_ResponseParams" % (method.interface.mojom_name,
+ method.mojom_name)
struct = mojom.Struct(params_class, module=method.interface.module)
for param in method.response_parameters:
- struct.AddField(param.name, param.kind, param.ordinal,
+ struct.AddField(param.mojom_name, param.kind, param.ordinal,
attributes=param.attributes)
- return self._AddStructComputedData(False, struct)
+ _AddStructComputedData(False, struct)
+ return struct
+
+ for struct in module.structs:
+ _AddStructComputedData(True, struct)
+ for union in module.unions:
+ _AddUnionComputedData(union)
+ for interface in module.interfaces:
+ _AddInterfaceComputedData(interface)
+
+
+class Generator(object):
+ # Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all
+ # files to stdout.
+ def __init__(self, module, output_dir=None, typemap=None, variant=None,
+ bytecode_path=None, for_blink=False, use_once_callback=False,
+ js_bindings_mode="new", export_attribute=None,
+ export_header=None, generate_non_variant_code=False,
+ support_lazy_serialization=False, disallow_native_types=False,
+ disallow_interfaces=False, generate_message_ids=False,
+ generate_fuzzing=False):
+ self.module = module
+ self.output_dir = output_dir
+ self.typemap = typemap or {}
+ self.variant = variant
+ self.bytecode_path = bytecode_path
+ self.for_blink = for_blink
+ self.use_once_callback = use_once_callback
+ self.js_bindings_mode = js_bindings_mode
+ self.export_attribute = export_attribute
+ self.export_header = export_header
+ self.generate_non_variant_code = generate_non_variant_code
+ self.support_lazy_serialization = support_lazy_serialization
+ self.disallow_native_types = disallow_native_types
+ self.disallow_interfaces = disallow_interfaces
+ self.generate_message_ids = generate_message_ids
+ self.generate_fuzzing = generate_fuzzing
+
+ def Write(self, contents, filename):
+ if self.output_dir is None:
+ print contents
+ return
+ full_path = os.path.join(self.output_dir, filename)
+ WriteFile(contents, full_path)
+
+ def GenerateFiles(self, args):
+ raise NotImplementedError("Subclasses must override/implement this method")
+
+ def GetJinjaParameters(self):
+ """Returns default constructor parameters for the jinja environment."""
+ return {}
+
+ def GetGlobals(self):
+ """Returns global mappings for the template generation."""
+ return {}
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/generator_unittest.py b/mojo/public/tools/bindings/pylib/mojom/generate/generator_unittest.py
deleted file mode 100644
index 9966b0b7f8..0000000000
--- a/mojo/public/tools/bindings/pylib/mojom/generate/generator_unittest.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import unittest
-
-import module as mojom
-import generator
-
-class TestGenerator(unittest.TestCase):
-
- def testGetUnionsAddsOrdinals(self):
- module = mojom.Module()
- union = module.AddUnion('a')
- union.AddField('a', mojom.BOOL)
- union.AddField('b', mojom.BOOL)
- union.AddField('c', mojom.BOOL, ordinal=10)
- union.AddField('d', mojom.BOOL)
-
- gen = generator.Generator(module)
- union = gen.GetUnions()[0]
- ordinals = [field.ordinal for field in union.fields]
-
- self.assertEquals([0, 1, 10, 11], ordinals)
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/module.py b/mojo/public/tools/bindings/pylib/mojom/generate/module.py
index 3a5f188e75..db45e3344f 100644
--- a/mojo/public/tools/bindings/pylib/mojom/generate/module.py
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/module.py
@@ -12,7 +12,6 @@
# method = interface.AddMethod('Tat', 0)
# method.AddParameter('baz', 0, mojom.INT32)
-
# We use our own version of __repr__ when displaying the AST, as the
# AST currently doesn't capture which nodes are reference (e.g. to
# types) and which nodes are definitions. This allows us to e.g. print
@@ -82,11 +81,13 @@ class Kind(object):
Attributes:
spec: A string uniquely identifying the type. May be None.
- parent_kind: The enclosing type. For example, a struct defined
+ module: {Module} The defining module. Set to None for built-in types.
+ parent_kind: The enclosing type. For example, an enum defined
inside an interface has that interface as its parent. May be None.
"""
- def __init__(self, spec=None):
+ def __init__(self, spec=None, module=None):
self.spec = spec
+ self.module = module
self.parent_kind = None
def Repr(self, as_ref=True):
@@ -106,10 +107,9 @@ class ReferenceKind(Kind):
Attributes:
is_nullable: True if the type is nullable.
"""
-
- def __init__(self, spec=None, is_nullable=False):
+ def __init__(self, spec=None, is_nullable=False, module=None):
assert spec is None or is_nullable == spec.startswith('?')
- Kind.__init__(self, spec)
+ Kind.__init__(self, spec, module)
self.is_nullable = is_nullable
self.shared_definition = {}
@@ -138,6 +138,8 @@ class ReferenceKind(Kind):
if self.spec is not None:
nullable_kind.spec = '?' + self.spec
nullable_kind.is_nullable = True
+ nullable_kind.parent_kind = self.parent_kind
+ nullable_kind.module = self.module
return nullable_kind
@@ -222,17 +224,15 @@ ATTRIBUTE_SYNC = 'Sync'
class NamedValue(object):
- def __init__(self, module, parent_kind, name):
+ def __init__(self, module, parent_kind, mojom_name):
self.module = module
- self.namespace = module.namespace
self.parent_kind = parent_kind
- self.name = name
- self.imported_from = None
+ self.mojom_name = mojom_name
def GetSpec(self):
- return (self.namespace + '.' +
- (self.parent_kind and (self.parent_kind.name + '.') or "") +
- self.name)
+ return (self.module.mojom_namespace + '.' +
+ (self.parent_kind and (self.parent_kind.mojom_name + '.') or "") +
+ self.mojom_name)
class BuiltinValue(object):
@@ -242,35 +242,47 @@ class BuiltinValue(object):
class ConstantValue(NamedValue):
def __init__(self, module, parent_kind, constant):
- NamedValue.__init__(self, module, parent_kind, constant.name)
+ NamedValue.__init__(self, module, parent_kind, constant.mojom_name)
self.constant = constant
+ @property
+ def name(self):
+ return self.constant.name
+
class EnumValue(NamedValue):
def __init__(self, module, enum, field):
- NamedValue.__init__(self, module, enum.parent_kind, field.name)
+ NamedValue.__init__(self, module, enum.parent_kind, field.mojom_name)
+ self.field = field
self.enum = enum
def GetSpec(self):
- return (self.namespace + '.' +
- (self.parent_kind and (self.parent_kind.name + '.') or "") +
- self.enum.name + '.' + self.name)
+ return (self.module.mojom_namespace + '.' +
+ (self.parent_kind and (self.parent_kind.mojom_name + '.') or "") +
+ self.enum.mojom_name + '.' + self.mojom_name)
+
+ @property
+ def name(self):
+ return self.field.name
class Constant(object):
- def __init__(self, name=None, kind=None, value=None, parent_kind=None):
- self.name = name
+ def __init__(self, mojom_name=None, kind=None, value=None, parent_kind=None):
+ self.mojom_name = mojom_name
self.kind = kind
self.value = value
self.parent_kind = parent_kind
+ def Stylize(self, stylizer):
+ self.name = stylizer.StylizeConstant(self.mojom_name)
+
class Field(object):
- def __init__(self, name=None, kind=None, ordinal=None, default=None,
+ def __init__(self, mojom_name=None, kind=None, ordinal=None, default=None,
attributes=None):
if self.__class__.__name__ == 'Field':
raise Exception()
- self.name = name
+ self.mojom_name = mojom_name
self.kind = kind
self.ordinal = ordinal
self.default = default
@@ -279,7 +291,10 @@ class Field(object):
def Repr(self, as_ref=True):
# Fields are only referenced by objects which define them and thus
# they are always displayed as non-references.
- return GenericRepr(self, {'name': False, 'kind': True})
+ return GenericRepr(self, {'mojom_name': False, 'kind': True})
+
+ def Stylize(self, stylizer):
+ self.name = stylizer.StylizeField(self.mojom_name)
@property
def min_version(self):
@@ -297,80 +312,90 @@ class Struct(ReferenceKind):
"""A struct with typed fields.
Attributes:
- name: {str} The name of the struct type.
+ mojom_name: {str} The name of the struct type as defined in mojom.
+ name: {str} The stylized name.
native_only: {bool} Does the struct have a body (i.e. any fields) or is it
purely a native struct.
- module: {Module} The defining module.
- imported_from: {dict} Information about where this union was
- imported from.
+ custom_serializer: {bool} Should we generate a serializer for the struct or
+ will one be provided by non-generated code.
fields: {List[StructField]} The members of the struct.
+ enums: {List[Enum]} The enums defined in the struct scope.
+ constants: {List[Constant]} The constants defined in the struct scope.
attributes: {dict} Additional information about the struct, such as
if it's a native struct.
"""
+ ReferenceKind.AddSharedProperty('mojom_name')
ReferenceKind.AddSharedProperty('name')
ReferenceKind.AddSharedProperty('native_only')
- ReferenceKind.AddSharedProperty('module')
- ReferenceKind.AddSharedProperty('imported_from')
+ ReferenceKind.AddSharedProperty('custom_serializer')
ReferenceKind.AddSharedProperty('fields')
+ ReferenceKind.AddSharedProperty('enums')
+ ReferenceKind.AddSharedProperty('constants')
ReferenceKind.AddSharedProperty('attributes')
- def __init__(self, name=None, module=None, attributes=None):
- if name is not None:
- spec = 'x:' + name
+ def __init__(self, mojom_name=None, module=None, attributes=None):
+ if mojom_name is not None:
+ spec = 'x:' + mojom_name
else:
spec = None
- ReferenceKind.__init__(self, spec)
- self.name = name
+ ReferenceKind.__init__(self, spec, False, module)
+ self.mojom_name = mojom_name
self.native_only = False
- self.module = module
- self.imported_from = None
+ self.custom_serializer = False
self.fields = []
+ self.enums = []
+ self.constants = []
self.attributes = attributes
def Repr(self, as_ref=True):
if as_ref:
- return '<%s name=%r imported_from=%s>' % (
- self.__class__.__name__, self.name,
- Repr(self.imported_from, as_ref=True))
+ return '<%s mojom_name=%r module=%s>' % (
+ self.__class__.__name__, self.mojom_name,
+ Repr(self.module, as_ref=True))
else:
- return GenericRepr(self, {'name': False, 'fields': False,
- 'imported_from': True})
+ return GenericRepr(self,
+ {'mojom_name': False, 'fields': False, 'module': True})
- def AddField(self, name, kind, ordinal=None, default=None, attributes=None):
- field = StructField(name, kind, ordinal, default, attributes)
+ def AddField(self, mojom_name, kind, ordinal=None, default=None,
+ attributes=None):
+ field = StructField(mojom_name, kind, ordinal, default, attributes)
self.fields.append(field)
return field
+ def Stylize(self, stylizer):
+ self.name = stylizer.StylizeStruct(self.mojom_name)
+ for field in self.fields:
+ field.Stylize(stylizer)
+ for enum in self.enums:
+ enum.Stylize(stylizer)
+ for constant in self.constants:
+ constant.Stylize(stylizer)
+
class Union(ReferenceKind):
"""A union of several kinds.
Attributes:
- name: {str} The name of the union type.
- module: {Module} The defining module.
- imported_from: {dict} Information about where this union was
- imported from.
+ mojom_name: {str} The name of the union type as defined in mojom.
+ name: {str} The stylized name.
fields: {List[UnionField]} The members of the union.
attributes: {dict} Additional information about the union, such as
which Java class name to use to represent it in the generated
bindings.
"""
+ ReferenceKind.AddSharedProperty('mojom_name')
ReferenceKind.AddSharedProperty('name')
- ReferenceKind.AddSharedProperty('module')
- ReferenceKind.AddSharedProperty('imported_from')
ReferenceKind.AddSharedProperty('fields')
ReferenceKind.AddSharedProperty('attributes')
- def __init__(self, name=None, module=None, attributes=None):
- if name is not None:
- spec = 'x:' + name
+ def __init__(self, mojom_name=None, module=None, attributes=None):
+ if mojom_name is not None:
+ spec = 'x:' + mojom_name
else:
spec = None
- ReferenceKind.__init__(self, spec)
- self.name = name
- self.module = module
- self.imported_from = None
+ ReferenceKind.__init__(self, spec, False, module)
+ self.mojom_name = mojom_name
self.fields = []
self.attributes = attributes
@@ -382,11 +407,16 @@ class Union(ReferenceKind):
else:
return GenericRepr(self, {'fields': True, 'is_nullable': False})
- def AddField(self, name, kind, ordinal=None, attributes=None):
- field = UnionField(name, kind, ordinal, None, attributes)
+ def AddField(self, mojom_name, kind, ordinal=None, attributes=None):
+ field = UnionField(mojom_name, kind, ordinal, None, attributes)
self.fields.append(field)
return field
+ def Stylize(self, stylizer):
+ self.name = stylizer.StylizeUnion(self.mojom_name)
+ for field in self.fields:
+ field.Stylize(stylizer)
+
class Array(ReferenceKind):
"""An array.
@@ -491,17 +521,20 @@ class AssociatedInterfaceRequest(ReferenceKind):
class Parameter(object):
- def __init__(self, name=None, kind=None, ordinal=None, default=None,
+ def __init__(self, mojom_name=None, kind=None, ordinal=None, default=None,
attributes=None):
- self.name = name
+ self.mojom_name = mojom_name
self.ordinal = ordinal
self.kind = kind
self.default = default
self.attributes = attributes
def Repr(self, as_ref=True):
- return '<%s name=%r kind=%s>' % (self.__class__.__name__, self.name,
- self.kind.Repr(as_ref=True))
+ return '<%s mojom_name=%r kind=%s>' % (
+ self.__class__.__name__, self.mojom_name, self.kind.Repr(as_ref=True))
+
+ def Stylize(self, stylizer):
+ self.name = stylizer.StylizeParameter(self.mojom_name)
@property
def min_version(self):
@@ -510,35 +543,50 @@ class Parameter(object):
class Method(object):
- def __init__(self, interface, name, ordinal=None, attributes=None):
+ def __init__(self, interface, mojom_name, ordinal=None, attributes=None):
self.interface = interface
- self.name = name
+ self.mojom_name = mojom_name
self.ordinal = ordinal
self.parameters = []
+ self.param_struct = None
self.response_parameters = None
+ self.response_param_struct = None
self.attributes = attributes
def Repr(self, as_ref=True):
if as_ref:
- return '<%s name=%r>' % (self.__class__.__name__, self.name)
+ return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)
else:
- return GenericRepr(self, {'name': False, 'parameters': True,
+ return GenericRepr(self, {'mojom_name': False, 'parameters': True,
'response_parameters': True})
- def AddParameter(self, name, kind, ordinal=None, default=None,
+ def AddParameter(self, mojom_name, kind, ordinal=None, default=None,
attributes=None):
- parameter = Parameter(name, kind, ordinal, default, attributes)
+ parameter = Parameter(mojom_name, kind, ordinal, default, attributes)
self.parameters.append(parameter)
return parameter
- def AddResponseParameter(self, name, kind, ordinal=None, default=None,
+ def AddResponseParameter(self, mojom_name, kind, ordinal=None, default=None,
attributes=None):
if self.response_parameters == None:
self.response_parameters = []
- parameter = Parameter(name, kind, ordinal, default, attributes)
+ parameter = Parameter(mojom_name, kind, ordinal, default, attributes)
self.response_parameters.append(parameter)
return parameter
+ def Stylize(self, stylizer):
+ self.name = stylizer.StylizeMethod(self.mojom_name)
+ for param in self.parameters:
+ param.Stylize(stylizer)
+ if self.response_parameters is not None:
+ for param in self.response_parameters:
+ param.Stylize(stylizer)
+
+ if self.param_struct:
+ self.param_struct.Stylize(stylizer)
+ if self.response_param_struct:
+ self.response_param_struct.Stylize(stylizer)
+
@property
def min_version(self):
return self.attributes.get(ATTRIBUTE_MIN_VERSION) \
@@ -551,40 +599,45 @@ class Method(object):
class Interface(ReferenceKind):
- ReferenceKind.AddSharedProperty('module')
+ ReferenceKind.AddSharedProperty('mojom_name')
ReferenceKind.AddSharedProperty('name')
- ReferenceKind.AddSharedProperty('imported_from')
ReferenceKind.AddSharedProperty('methods')
+ ReferenceKind.AddSharedProperty('enums')
+ ReferenceKind.AddSharedProperty('constants')
ReferenceKind.AddSharedProperty('attributes')
- def __init__(self, name=None, module=None, attributes=None):
- if name is not None:
- spec = 'x:' + name
+ def __init__(self, mojom_name=None, module=None, attributes=None):
+ if mojom_name is not None:
+ spec = 'x:' + mojom_name
else:
spec = None
- ReferenceKind.__init__(self, spec)
- self.module = module
- self.name = name
- self.imported_from = None
+ ReferenceKind.__init__(self, spec, False, module)
+ self.mojom_name = mojom_name
self.methods = []
+ self.enums = []
+ self.constants = []
self.attributes = attributes
def Repr(self, as_ref=True):
if as_ref:
- return '<%s name=%r>' % (self.__class__.__name__, self.name)
+ return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)
else:
- return GenericRepr(self, {'name': False, 'attributes': False,
+ return GenericRepr(self, {'mojom_name': False, 'attributes': False,
'methods': False})
- def AddMethod(self, name, ordinal=None, attributes=None):
- method = Method(self, name, ordinal, attributes)
+ def AddMethod(self, mojom_name, ordinal=None, attributes=None):
+ method = Method(self, mojom_name, ordinal, attributes)
self.methods.append(method)
return method
- # TODO(451323): Remove when the language backends no longer rely on this.
- @property
- def client(self):
- return None
+ def Stylize(self, stylizer):
+ self.name = stylizer.StylizeInterface(self.mojom_name)
+ for method in self.methods:
+ method.Stylize(stylizer)
+ for enum in self.enums:
+ enum.Stylize(stylizer)
+ for constant in self.constants:
+ constant.Stylize(stylizer)
class AssociatedInterface(ReferenceKind):
@@ -603,13 +656,16 @@ class AssociatedInterface(ReferenceKind):
class EnumField(object):
- def __init__(self, name=None, value=None, attributes=None,
+ def __init__(self, mojom_name=None, value=None, attributes=None,
numeric_value=None):
- self.name = name
+ self.mojom_name = mojom_name
self.value = value
self.attributes = attributes
self.numeric_value = numeric_value
+ def Stylize(self, stylizer):
+ self.name = stylizer.StylizeEnumField(self.mojom_name)
+
@property
def min_version(self):
return self.attributes.get(ATTRIBUTE_MIN_VERSION) \
@@ -617,24 +673,29 @@ class EnumField(object):
class Enum(Kind):
- def __init__(self, name=None, module=None, attributes=None):
- self.module = module
- self.name = name
+ def __init__(self, mojom_name=None, module=None, attributes=None):
+ self.mojom_name = mojom_name
self.native_only = False
- self.imported_from = None
- if name is not None:
- spec = 'x:' + name
+ if mojom_name is not None:
+ spec = 'x:' + mojom_name
else:
spec = None
- Kind.__init__(self, spec)
+ Kind.__init__(self, spec, module)
self.fields = []
self.attributes = attributes
+ self.min_value = None
+ self.max_value = None
def Repr(self, as_ref=True):
if as_ref:
- return '<%s name=%r>' % (self.__class__.__name__, self.name)
+ return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)
else:
- return GenericRepr(self, {'name': False, 'fields': False})
+ return GenericRepr(self, {'mojom_name': False, 'fields': False})
+
+ def Stylize(self, stylizer):
+ self.name = stylizer.StylizeEnum(self.mojom_name)
+ for field in self.fields:
+ field.Stylize(stylizer)
@property
def extensible(self):
@@ -643,15 +704,18 @@ class Enum(Kind):
class Module(object):
- def __init__(self, name=None, namespace=None, attributes=None):
- self.name = name
- self.path = name
- self.namespace = namespace
+ def __init__(self, path=None, mojom_namespace=None,
+ attributes=None):
+ self.path = path
+ self.mojom_namespace = mojom_namespace
self.structs = []
self.unions = []
self.interfaces = []
+ self.enums = []
+ self.constants = []
self.kinds = {}
self.attributes = attributes
+ self.imports = []
def __repr__(self):
# Gives us a decent __repr__ for modules.
@@ -659,28 +723,44 @@ class Module(object):
def Repr(self, as_ref=True):
if as_ref:
- return '<%s name=%r namespace=%r>' % (
- self.__class__.__name__, self.name, self.namespace)
+ return '<%s path=%r mojom_namespace=%r>' % (
+ self.__class__.__name__, self.path, self.mojom_namespace)
else:
- return GenericRepr(self, {'name': False, 'namespace': False,
+ return GenericRepr(self, {'path': False, 'mojom_namespace': False,
'attributes': False, 'structs': False,
'interfaces': False, 'unions': False})
- def AddInterface(self, name, attributes=None):
- interface = Interface(name, self, attributes)
+ def AddInterface(self, mojom_name, attributes=None):
+ interface = Interface(mojom_name, self, attributes)
self.interfaces.append(interface)
return interface
- def AddStruct(self, name, attributes=None):
- struct = Struct(name, self, attributes)
+ def AddStruct(self, mojom_name, attributes=None):
+ struct = Struct(mojom_name, self, attributes)
self.structs.append(struct)
return struct
- def AddUnion(self, name, attributes=None):
- union = Union(name, self, attributes)
+ def AddUnion(self, mojom_name, attributes=None):
+ union = Union(mojom_name, self, attributes)
self.unions.append(union)
return union
+ def Stylize(self, stylizer):
+ self.namespace = stylizer.StylizeModule(self.mojom_namespace)
+ for struct in self.structs:
+ struct.Stylize(stylizer)
+ for union in self.unions:
+ union.Stylize(stylizer)
+ for interface in self.interfaces:
+ interface.Stylize(stylizer)
+ for enum in self.enums:
+ enum.Stylize(stylizer)
+ for constant in self.constants:
+ constant.Stylize(stylizer)
+
+ for imported_module in self.imports:
+ imported_module.Stylize(stylizer)
+
def IsBoolKind(kind):
return kind.spec == BOOL.spec
@@ -817,37 +897,57 @@ def HasCallbacks(interface):
# Finds out whether an interface passes associated interfaces and associated
# interface requests.
def PassesAssociatedKinds(interface):
- def _ContainsAssociatedKinds(kind, visited_kinds):
+ visited_kinds = set()
+ for method in interface.methods:
+ if MethodPassesAssociatedKinds(method, visited_kinds):
+ return True
+ return False
+
+
+def _AnyMethodParameterRecursive(method, predicate, visited_kinds=None):
+ def _HasProperty(kind):
if kind in visited_kinds:
# No need to examine the kind again.
return False
visited_kinds.add(kind)
- if IsAssociatedKind(kind):
+ if predicate(kind):
return True
if IsArrayKind(kind):
- return _ContainsAssociatedKinds(kind.kind, visited_kinds)
+ return _HasProperty(kind.kind)
if IsStructKind(kind) or IsUnionKind(kind):
for field in kind.fields:
- if _ContainsAssociatedKinds(field.kind, visited_kinds):
+ if _HasProperty(field.kind):
return True
if IsMapKind(kind):
- # No need to examine the key kind, only primitive kinds and non-nullable
- # string are allowed to be key kinds.
- return _ContainsAssociatedKinds(kind.value_kind, visited_kinds)
+ if _HasProperty(kind.key_kind) or _HasProperty(kind.value_kind):
+ return True
return False
- visited_kinds = set()
- for method in interface.methods:
- for param in method.parameters:
- if _ContainsAssociatedKinds(param.kind, visited_kinds):
+ if visited_kinds is None:
+ visited_kinds = set()
+
+ for param in method.parameters:
+ if _HasProperty(param.kind):
+ return True
+ if method.response_parameters != None:
+ for param in method.response_parameters:
+ if _HasProperty(param.kind):
return True
- if method.response_parameters != None:
- for param in method.response_parameters:
- if _ContainsAssociatedKinds(param.kind, visited_kinds):
- return True
return False
+# Finds out whether a method passes associated interfaces and associated
+# interface requests.
+def MethodPassesAssociatedKinds(method, visited_kinds=None):
+ return _AnyMethodParameterRecursive(method, IsAssociatedKind,
+ visited_kinds=visited_kinds)
+
+
+# Determines whether a method passes interfaces.
+def MethodPassesInterfaces(method):
+ return _AnyMethodParameterRecursive(method, IsInterfaceKind)
+
+
def HasSyncMethods(interface):
for method in interface.methods:
if method.sync:
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/pack.py b/mojo/public/tools/bindings/pylib/mojom/generate/pack.py
index 37dc8f396b..e4204a017d 100644
--- a/mojo/public/tools/bindings/pylib/mojom/generate/pack.py
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/pack.py
@@ -56,7 +56,8 @@ class PackedField(object):
# TODO(mpcomplete): what about big enums?
return cls.kind_to_size[mojom.INT32]
if not kind in cls.kind_to_size:
- raise Exception("Invalid kind: %s" % kind.spec)
+ raise Exception("Undefined type: %s. Did you forget to import the file "
+ "containing the definition?" % kind.spec)
return cls.kind_to_size[kind]
@classmethod
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py b/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py
index 66f8954012..653a2dfc74 100644
--- a/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py
@@ -4,27 +4,14 @@
# Based on third_party/WebKit/Source/build/scripts/template_expander.py.
-import imp
import os.path
import sys
-# Disable lint check for finding modules:
-# pylint: disable=F0401
-
-def _GetDirAbove(dirname):
- """Returns the directory "above" this file containing |dirname| (which must
- also be "above" this file)."""
- path = os.path.abspath(__file__)
- while True:
- path, tail = os.path.split(path)
- assert tail
- if tail == dirname:
- return path
-
-try:
- imp.find_module("jinja2")
-except ImportError:
- sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party"))
+_current_dir = os.path.dirname(os.path.realpath(__file__))
+# jinja2 is in chromium's third_party directory
+# Insert at front to override system libraries, and after path[0] == script dir
+sys.path.insert(
+ 1, os.path.join(_current_dir, *([os.pardir] * 7 + ['third_party'])))
import jinja2
diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/translate.py b/mojo/public/tools/bindings/pylib/mojom/generate/translate.py
index ffad7447a9..94fe2a6a52 100644
--- a/mojo/public/tools/bindings/pylib/mojom/generate/translate.py
+++ b/mojo/public/tools/bindings/pylib/mojom/generate/translate.py
@@ -10,20 +10,20 @@ representation of a mojom file. When called it's assumed that all imports have
already been parsed and converted to ASTs before.
"""
-import copy
+import os
import re
import module as mojom
from mojom.parse import ast
def _DuplicateName(values):
- """Returns the 'name' of the first entry in |values| whose 'name' has already
- been encountered. If there are no duplicates, returns None."""
+ """Returns the 'mojom_name' of the first entry in |values| whose 'mojom_name'
+ has already been encountered. If there are no duplicates, returns None."""
names = set()
for value in values:
- if value.name in names:
- return value.name
- names.add(value.name)
+ if value.mojom_name in names:
+ return value.mojom_name
+ names.add(value.mojom_name)
return None
def _ElemsOfType(elems, elem_type, scope):
@@ -121,36 +121,36 @@ def _LookupKind(kinds, spec, scope):
|scope| is a tuple that looks like (namespace, struct/interface), referring
to the location where the type is referenced."""
if spec.startswith('x:'):
- name = spec[2:]
+ mojom_name = spec[2:]
for i in xrange(len(scope), -1, -1):
test_spec = 'x:'
if i > 0:
test_spec += '.'.join(scope[:i]) + '.'
- test_spec += name
+ test_spec += mojom_name
kind = kinds.get(test_spec)
if kind:
return kind
return kinds.get(spec)
-def _LookupValue(values, name, scope, kind):
+def _LookupValue(values, mojom_name, scope, kind):
"""Like LookupKind, but for constant values."""
# If the type is an enum, the value can be specified as a qualified name, in
# which case the form EnumName.ENUM_VALUE must be used. We use the presence
# of a '.' in the requested name to identify this. Otherwise, we prepend the
# enum name.
- if isinstance(kind, mojom.Enum) and '.' not in name:
- name = '%s.%s' % (kind.spec.split(':', 1)[1], name)
+ if isinstance(kind, mojom.Enum) and '.' not in mojom_name:
+ mojom_name = '%s.%s' % (kind.spec.split(':', 1)[1], mojom_name)
for i in reversed(xrange(len(scope) + 1)):
test_spec = '.'.join(scope[:i])
if test_spec:
test_spec += '.'
- test_spec += name
+ test_spec += mojom_name
value = values.get(test_spec)
if value:
return value
- return values.get(name)
+ return values.get(mojom_name)
def _FixupExpression(module, value, scope, kind):
"""Translates an IDENTIFIER into a built-in value or structured NamedValue
@@ -222,41 +222,19 @@ def _Kind(kinds, spec, scope):
kinds[spec] = kind
return kind
-def _KindFromImport(original_kind, imported_from):
- """Used with 'import module' - clones the kind imported from the given
- module's namespace. Only used with Structs, Unions, Interfaces and Enums."""
- kind = copy.copy(original_kind)
- # |shared_definition| is used to store various properties (see
- # |AddSharedProperty()| in module.py), including |imported_from|. We don't
- # want the copy to share these with the original, so copy it if necessary.
- if hasattr(original_kind, 'shared_definition'):
- kind.shared_definition = copy.copy(original_kind.shared_definition)
- kind.imported_from = imported_from
- return kind
-
def _Import(module, import_module):
- import_item = {}
- import_item['module_name'] = import_module.name
- import_item['namespace'] = import_module.namespace
- import_item['module'] = import_module
-
# Copy the struct kinds from our imports into the current module.
importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface)
for kind in import_module.kinds.itervalues():
if (isinstance(kind, importable_kinds) and
- kind.imported_from is None):
- kind = _KindFromImport(kind, import_item)
+ kind.module.path == import_module.path):
module.kinds[kind.spec] = kind
# Ditto for values.
for value in import_module.values.itervalues():
- if value.imported_from is None:
- # Values don't have shared definitions (since they're not nullable), so no
- # need to do anything special.
- value = copy.copy(value)
- value.imported_from = import_item
+ if value.module.path == import_module.path:
module.values[value.GetSpec()] = value
- return import_item
+ return import_module
def _Struct(module, parsed_struct):
"""
@@ -268,9 +246,9 @@ def _Struct(module, parsed_struct):
{mojom.Struct} AST struct.
"""
struct = mojom.Struct(module=module)
- struct.name = parsed_struct.name
+ struct.mojom_name = parsed_struct.mojom_name
struct.native_only = parsed_struct.body is None
- struct.spec = 'x:' + module.namespace + '.' + struct.name
+ struct.spec = 'x:' + module.mojom_namespace + '.' + struct.mojom_name
module.kinds[struct.spec] = struct
if struct.native_only:
struct.enums = []
@@ -279,13 +257,13 @@ def _Struct(module, parsed_struct):
else:
struct.enums = map(
lambda enum: _Enum(module, enum, struct),
- _ElemsOfType(parsed_struct.body, ast.Enum, parsed_struct.name))
+ _ElemsOfType(parsed_struct.body, ast.Enum, parsed_struct.mojom_name))
struct.constants = map(
lambda constant: _Constant(module, constant, struct),
- _ElemsOfType(parsed_struct.body, ast.Const, parsed_struct.name))
+ _ElemsOfType(parsed_struct.body, ast.Const, parsed_struct.mojom_name))
# Stash fields parsed_struct here temporarily.
struct.fields_data = _ElemsOfType(
- parsed_struct.body, ast.StructField, parsed_struct.name)
+ parsed_struct.body, ast.StructField, parsed_struct.mojom_name)
struct.attributes = _AttributeListToDict(parsed_struct.attribute_list)
# Enforce that a [Native] attribute is set to make native-only struct
@@ -295,6 +273,9 @@ def _Struct(module, parsed_struct):
raise Exception("Native-only struct declarations must include a " +
"Native attribute.")
+ if struct.attributes and struct.attributes.get('CustomSerializer', False):
+ struct.custom_serializer = True
+
return struct
def _Union(module, parsed_union):
@@ -307,12 +288,12 @@ def _Union(module, parsed_union):
{mojom.Union} AST union.
"""
union = mojom.Union(module=module)
- union.name = parsed_union.name
- union.spec = 'x:' + module.namespace + '.' + union.name
+ union.mojom_name = parsed_union.mojom_name
+ union.spec = 'x:' + module.mojom_namespace + '.' + union.mojom_name
module.kinds[union.spec] = union
# Stash fields parsed_union here temporarily.
union.fields_data = _ElemsOfType(
- parsed_union.body, ast.UnionField, parsed_union.name)
+ parsed_union.body, ast.UnionField, parsed_union.mojom_name)
union.attributes = _AttributeListToDict(parsed_union.attribute_list)
return union
@@ -327,14 +308,14 @@ def _StructField(module, parsed_field, struct):
{mojom.StructField} AST struct field.
"""
field = mojom.StructField()
- field.name = parsed_field.name
+ field.mojom_name = parsed_field.mojom_name
field.kind = _Kind(
module.kinds, _MapKind(parsed_field.typename),
- (module.namespace, struct.name))
+ (module.mojom_namespace, struct.mojom_name))
field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
field.default = _FixupExpression(
- module, parsed_field.default_value, (module.namespace, struct.name),
- field.kind)
+ module, parsed_field.default_value,
+ (module.mojom_namespace, struct.mojom_name), field.kind)
field.attributes = _AttributeListToDict(parsed_field.attribute_list)
return field
@@ -349,13 +330,13 @@ def _UnionField(module, parsed_field, union):
{mojom.UnionField} AST union.
"""
field = mojom.UnionField()
- field.name = parsed_field.name
+ field.mojom_name = parsed_field.mojom_name
field.kind = _Kind(
module.kinds, _MapKind(parsed_field.typename),
- (module.namespace, union.name))
+ (module.mojom_namespace, union.mojom_name))
field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
field.default = _FixupExpression(
- module, None, (module.namespace, union.name), field.kind)
+ module, None, (module.mojom_namespace, union.mojom_name), field.kind)
field.attributes = _AttributeListToDict(parsed_field.attribute_list)
return field
@@ -370,10 +351,10 @@ def _Parameter(module, parsed_param, interface):
{mojom.Parameter} AST parameter.
"""
parameter = mojom.Parameter()
- parameter.name = parsed_param.name
+ parameter.mojom_name = parsed_param.mojom_name
parameter.kind = _Kind(
module.kinds, _MapKind(parsed_param.typename),
- (module.namespace, interface.name))
+ (module.mojom_namespace, interface.mojom_name))
parameter.ordinal = (
parsed_param.ordinal.value if parsed_param.ordinal else None)
parameter.default = None # TODO(tibell): We never have these. Remove field?
@@ -391,7 +372,7 @@ def _Method(module, parsed_method, interface):
{mojom.Method} AST method.
"""
method = mojom.Method(
- interface, parsed_method.name,
+ interface, parsed_method.mojom_name,
ordinal=parsed_method.ordinal.value if parsed_method.ordinal else None)
method.parameters = map(
lambda parameter: _Parameter(module, parameter, interface),
@@ -421,18 +402,18 @@ def _Interface(module, parsed_iface):
{mojom.Interface} AST interface.
"""
interface = mojom.Interface(module=module)
- interface.name = parsed_iface.name
- interface.spec = 'x:' + module.namespace + '.' + interface.name
+ interface.mojom_name = parsed_iface.mojom_name
+ interface.spec = 'x:' + module.mojom_namespace + '.' + interface.mojom_name
module.kinds[interface.spec] = interface
interface.enums = map(
lambda enum: _Enum(module, enum, interface),
- _ElemsOfType(parsed_iface.body, ast.Enum, parsed_iface.name))
+ _ElemsOfType(parsed_iface.body, ast.Enum, parsed_iface.mojom_name))
interface.constants = map(
lambda constant: _Constant(module, constant, interface),
- _ElemsOfType(parsed_iface.body, ast.Const, parsed_iface.name))
+ _ElemsOfType(parsed_iface.body, ast.Const, parsed_iface.mojom_name))
# Stash methods parsed_iface here temporarily.
interface.methods_data = _ElemsOfType(
- parsed_iface.body, ast.Method, parsed_iface.name)
+ parsed_iface.body, ast.Method, parsed_iface.mojom_name)
interface.attributes = _AttributeListToDict(parsed_iface.attribute_list)
return interface
@@ -448,17 +429,18 @@ def _EnumField(module, enum, parsed_field, parent_kind):
{mojom.EnumField} AST enum field.
"""
field = mojom.EnumField()
- field.name = parsed_field.name
+ field.mojom_name = parsed_field.mojom_name
# TODO(mpcomplete): FixupExpression should be done in the second pass,
# so constants and enums can refer to each other.
# TODO(mpcomplete): But then, what if constants are initialized to an enum? Or
# vice versa?
if parent_kind:
field.value = _FixupExpression(
- module, parsed_field.value, (module.namespace, parent_kind.name), enum)
+ module, parsed_field.value,
+ (module.mojom_namespace, parent_kind.mojom_name), enum)
else:
field.value = _FixupExpression(
- module, parsed_field.value, (module.namespace, ), enum)
+ module, parsed_field.value, (module.mojom_namespace, ), enum)
field.attributes = _AttributeListToDict(parsed_field.attribute_list)
value = mojom.EnumValue(module, enum, field)
module.values[value.GetSpec()] = value
@@ -468,11 +450,17 @@ def _ResolveNumericEnumValues(enum_fields):
"""
Given a reference to a list of mojom.EnumField, resolves and assigns their
values to EnumField.numeric_value.
+
+ Returns:
+ A tuple of the lowest and highest assigned enumerator value or None, None
+ if no enumerator values were assigned.
"""
- # map of <name> -> integral value
+ # map of <mojom_name> -> integral value
resolved_enum_values = {}
prev_value = -1
+ min_value = None
+ max_value = None
for field in enum_fields:
# This enum value is +1 the previous enum value (e.g: BEGIN).
if field.value is None:
@@ -484,12 +472,18 @@ def _ResolveNumericEnumValues(enum_fields):
# Reference to a previous enum value (e.g: INIT = BEGIN).
elif type(field.value) is mojom.EnumValue:
- prev_value = resolved_enum_values[field.value.name]
+ prev_value = resolved_enum_values[field.value.mojom_name]
else:
raise Exception("Unresolved enum value.")
- resolved_enum_values[field.name] = prev_value
+ resolved_enum_values[field.mojom_name] = prev_value
field.numeric_value = prev_value
+ if min_value is None or prev_value < min_value:
+ min_value = prev_value
+ if max_value is None or prev_value > max_value:
+ max_value = prev_value
+
+ return min_value, max_value
def _Enum(module, parsed_enum, parent_kind):
"""
@@ -501,21 +495,19 @@ def _Enum(module, parsed_enum, parent_kind):
{mojom.Enum} AST enum.
"""
enum = mojom.Enum(module=module)
- enum.name = parsed_enum.name
+ enum.mojom_name = parsed_enum.mojom_name
enum.native_only = parsed_enum.enum_value_list is None
- name = enum.name
+ mojom_name = enum.mojom_name
if parent_kind:
- name = parent_kind.name + '.' + name
- enum.spec = 'x:%s.%s' % (module.namespace, name)
+ mojom_name = parent_kind.mojom_name + '.' + mojom_name
+ enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name)
enum.parent_kind = parent_kind
enum.attributes = _AttributeListToDict(parsed_enum.attribute_list)
- if enum.native_only:
- enum.fields = []
- else:
+ if not enum.native_only:
enum.fields = map(
lambda field: _EnumField(module, enum, field, parent_kind),
parsed_enum.enum_value_list)
- _ResolveNumericEnumValues(enum.fields)
+ enum.min_value, enum.max_value = _ResolveNumericEnumValues(enum.fields)
module.kinds[enum.spec] = enum
@@ -538,11 +530,11 @@ def _Constant(module, parsed_const, parent_kind):
{mojom.Constant} AST constant.
"""
constant = mojom.Constant()
- constant.name = parsed_const.name
+ constant.mojom_name = parsed_const.mojom_name
if parent_kind:
- scope = (module.namespace, parent_kind.name)
+ scope = (module.mojom_namespace, parent_kind.mojom_name)
else:
- scope = (module.namespace, )
+ scope = (module.mojom_namespace, )
# TODO(mpcomplete): maybe we should only support POD kinds.
constant.kind = _Kind(module.kinds, _MapKind(parsed_const.typename), scope)
constant.parent_kind = parent_kind
@@ -552,26 +544,25 @@ def _Constant(module, parsed_const, parent_kind):
module.values[value.GetSpec()] = value
return constant
-def _Module(tree, name, imports):
+def _Module(tree, path, imports):
"""
Args:
tree: {ast.Mojom} The parse tree.
- name: {str} The mojom filename, excluding the path.
+ path: {str} The path to the mojom file.
imports: {Dict[str, mojom.Module]} Mapping from filenames, as they appear in
the import list, to already processed modules. Used to process imports.
Returns:
{mojom.Module} An AST for the mojom.
"""
- module = mojom.Module()
+ module = mojom.Module(path=path)
module.kinds = {}
for kind in mojom.PRIMITIVES:
module.kinds[kind.spec] = kind
module.values = {}
- module.name = name
- module.namespace = tree.module.name[1] if tree.module else ''
+ module.mojom_namespace = tree.module.mojom_namespace[1] if tree.module else ''
# Imports must come first, because they add to module.kinds which is used
# by by the others.
module.imports = [
@@ -583,22 +574,23 @@ def _Module(tree, name, imports):
module.attributes = dict((attribute.key, attribute.value)
for attribute in tree.module.attribute_list)
+ filename = os.path.basename(path)
# First pass collects kinds.
module.enums = map(
lambda enum: _Enum(module, enum, None),
- _ElemsOfType(tree.definition_list, ast.Enum, name))
+ _ElemsOfType(tree.definition_list, ast.Enum, filename))
module.structs = map(
lambda struct: _Struct(module, struct),
- _ElemsOfType(tree.definition_list, ast.Struct, name))
+ _ElemsOfType(tree.definition_list, ast.Struct, filename))
module.unions = map(
lambda union: _Union(module, union),
- _ElemsOfType(tree.definition_list, ast.Union, name))
+ _ElemsOfType(tree.definition_list, ast.Union, filename))
module.interfaces = map(
lambda interface: _Interface(module, interface),
- _ElemsOfType(tree.definition_list, ast.Interface, name))
+ _ElemsOfType(tree.definition_list, ast.Interface, filename))
module.constants = map(
lambda constant: _Constant(module, constant, None),
- _ElemsOfType(tree.definition_list, ast.Const, name))
+ _ElemsOfType(tree.definition_list, ast.Const, filename))
# Second pass expands fields and methods. This allows fields and parameters
# to refer to kinds defined anywhere in the mojom.
@@ -617,23 +609,17 @@ def _Module(tree, name, imports):
return module
-def OrderedModule(tree, name, imports):
+def OrderedModule(tree, path, imports):
"""Convert parse tree to AST module.
Args:
tree: {ast.Mojom} The parse tree.
- name: {str} The mojom filename, excluding the path.
+ path: {str} The path to the mojom file.
imports: {Dict[str, mojom.Module]} Mapping from filenames, as they appear in
the import list, to already processed modules. Used to process imports.
Returns:
{mojom.Module} An AST for the mojom.
"""
- module = _Module(tree, name, imports)
- for interface in module.interfaces:
- next_ordinal = 0
- for method in interface.methods:
- if method.ordinal is None:
- method.ordinal = next_ordinal
- next_ordinal = method.ordinal + 1
+ module = _Module(tree, path, imports)
return module
diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/ast.py b/mojo/public/tools/bindings/pylib/mojom/parse/ast.py
index 2c6b5fcdf8..e9d7844986 100644
--- a/mojo/public/tools/bindings/pylib/mojom/parse/ast.py
+++ b/mojo/public/tools/bindings/pylib/mojom/parse/ast.py
@@ -84,10 +84,10 @@ class Definition(NodeBase):
enum values, consts, structs, struct fields, interfaces). (This does not
include parameter definitions.) This class is meant to be subclassed."""
- def __init__(self, name, **kwargs):
- assert isinstance(name, str)
+ def __init__(self, mojom_name, **kwargs):
+ assert isinstance(mojom_name, str)
NodeBase.__init__(self, **kwargs)
- self.name = name
+ self.mojom_name = mojom_name
################################################################################
@@ -117,18 +117,21 @@ class AttributeList(NodeListBase):
class Const(Definition):
"""Represents a const definition."""
- def __init__(self, name, typename, value, **kwargs):
+ def __init__(self, mojom_name, attribute_list, typename, value, **kwargs):
+ assert attribute_list is None or isinstance(attribute_list, AttributeList)
# The typename is currently passed through as a string.
assert isinstance(typename, str)
# The value is either a literal (currently passed through as a string) or a
# "wrapped identifier".
assert isinstance(value, str) or isinstance(value, tuple)
- super(Const, self).__init__(name, **kwargs)
+ super(Const, self).__init__(mojom_name, **kwargs)
+ self.attribute_list = attribute_list
self.typename = typename
self.value = value
def __eq__(self, other):
return super(Const, self).__eq__(other) and \
+ self.attribute_list == other.attribute_list and \
self.typename == other.typename and \
self.value == other.value
@@ -136,10 +139,10 @@ class Const(Definition):
class Enum(Definition):
"""Represents an enum definition."""
- def __init__(self, name, attribute_list, enum_value_list, **kwargs):
+ def __init__(self, mojom_name, attribute_list, enum_value_list, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert enum_value_list is None or isinstance(enum_value_list, EnumValueList)
- super(Enum, self).__init__(name, **kwargs)
+ super(Enum, self).__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.enum_value_list = enum_value_list
@@ -152,12 +155,12 @@ class Enum(Definition):
class EnumValue(Definition):
"""Represents a definition of an enum value."""
- def __init__(self, name, attribute_list, value, **kwargs):
+ def __init__(self, mojom_name, attribute_list, value, **kwargs):
# The optional value is either an int (which is current a string) or a
# "wrapped identifier".
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert value is None or isinstance(value, (str, tuple))
- super(EnumValue, self).__init__(name, **kwargs)
+ super(EnumValue, self).__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.value = value
@@ -177,13 +180,16 @@ class EnumValueList(NodeListBase):
class Import(NodeBase):
"""Represents an import statement."""
- def __init__(self, import_filename, **kwargs):
+ def __init__(self, attribute_list, import_filename, **kwargs):
+ assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert isinstance(import_filename, str)
super(Import, self).__init__(**kwargs)
+ self.attribute_list = attribute_list
self.import_filename = import_filename
def __eq__(self, other):
return super(Import, self).__eq__(other) and \
+ self.attribute_list == other.attribute_list and \
self.import_filename == other.import_filename
@@ -196,10 +202,10 @@ class ImportList(NodeListBase):
class Interface(Definition):
"""Represents an interface definition."""
- def __init__(self, name, attribute_list, body, **kwargs):
+ def __init__(self, mojom_name, attribute_list, body, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert isinstance(body, InterfaceBody)
- super(Interface, self).__init__(name, **kwargs)
+ super(Interface, self).__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.body = body
@@ -212,14 +218,14 @@ class Interface(Definition):
class Method(Definition):
"""Represents a method definition."""
- def __init__(self, name, attribute_list, ordinal, parameter_list,
+ def __init__(self, mojom_name, attribute_list, ordinal, parameter_list,
response_parameter_list, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert ordinal is None or isinstance(ordinal, Ordinal)
assert isinstance(parameter_list, ParameterList)
assert response_parameter_list is None or \
isinstance(response_parameter_list, ParameterList)
- super(Method, self).__init__(name, **kwargs)
+ super(Method, self).__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.ordinal = ordinal
self.parameter_list = parameter_list
@@ -243,17 +249,17 @@ class InterfaceBody(NodeListBase):
class Module(NodeBase):
"""Represents a module statement."""
- def __init__(self, name, attribute_list, **kwargs):
- # |name| is either none or a "wrapped identifier".
- assert name is None or isinstance(name, tuple)
+ def __init__(self, mojom_namespace, attribute_list, **kwargs):
+ # |mojom_namespace| is either none or a "wrapped identifier".
+ assert mojom_namespace is None or isinstance(mojom_namespace, tuple)
assert attribute_list is None or isinstance(attribute_list, AttributeList)
super(Module, self).__init__(**kwargs)
- self.name = name
+ self.mojom_namespace = mojom_namespace
self.attribute_list = attribute_list
def __eq__(self, other):
return super(Module, self).__eq__(other) and \
- self.name == other.name and \
+ self.mojom_namespace == other.mojom_namespace and \
self.attribute_list == other.attribute_list
@@ -296,20 +302,20 @@ class Ordinal(NodeBase):
class Parameter(NodeBase):
"""Represents a method request or response parameter."""
- def __init__(self, name, attribute_list, ordinal, typename, **kwargs):
- assert isinstance(name, str)
+ def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):
+ assert isinstance(mojom_name, str)
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert ordinal is None or isinstance(ordinal, Ordinal)
assert isinstance(typename, str)
super(Parameter, self).__init__(**kwargs)
- self.name = name
+ self.mojom_name = mojom_name
self.attribute_list = attribute_list
self.ordinal = ordinal
self.typename = typename
def __eq__(self, other):
return super(Parameter, self).__eq__(other) and \
- self.name == other.name and \
+ self.mojom_name == other.mojom_name and \
self.attribute_list == other.attribute_list and \
self.ordinal == other.ordinal and \
self.typename == other.typename
@@ -324,10 +330,10 @@ class ParameterList(NodeListBase):
class Struct(Definition):
"""Represents a struct definition."""
- def __init__(self, name, attribute_list, body, **kwargs):
+ def __init__(self, mojom_name, attribute_list, body, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert isinstance(body, StructBody) or body is None
- super(Struct, self).__init__(name, **kwargs)
+ super(Struct, self).__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.body = body
@@ -340,16 +346,16 @@ class Struct(Definition):
class StructField(Definition):
"""Represents a struct field definition."""
- def __init__(self, name, attribute_list, ordinal, typename, default_value,
- **kwargs):
- assert isinstance(name, str)
+ def __init__(self, mojom_name, attribute_list, ordinal, typename,
+ default_value, **kwargs):
+ assert isinstance(mojom_name, str)
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert ordinal is None or isinstance(ordinal, Ordinal)
assert isinstance(typename, str)
# The optional default value is currently either a value as a string or a
# "wrapped identifier".
assert default_value is None or isinstance(default_value, (str, tuple))
- super(StructField, self).__init__(name, **kwargs)
+ super(StructField, self).__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.ordinal = ordinal
self.typename = typename
@@ -373,10 +379,10 @@ class StructBody(NodeListBase):
class Union(Definition):
"""Represents a union definition."""
- def __init__(self, name, attribute_list, body, **kwargs):
+ def __init__(self, mojom_name, attribute_list, body, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert isinstance(body, UnionBody)
- super(Union, self).__init__(name, **kwargs)
+ super(Union, self).__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.body = body
@@ -388,12 +394,12 @@ class Union(Definition):
class UnionField(Definition):
- def __init__(self, name, attribute_list, ordinal, typename, **kwargs):
- assert isinstance(name, str)
+ def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):
+ assert isinstance(mojom_name, str)
assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert ordinal is None or isinstance(ordinal, Ordinal)
assert isinstance(typename, str)
- super(UnionField, self).__init__(name, **kwargs)
+ super(UnionField, self).__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list
self.ordinal = ordinal
self.typename = typename
diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/conditional_features.py b/mojo/public/tools/bindings/pylib/mojom/parse/conditional_features.py
new file mode 100644
index 0000000000..c2279cfc55
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom/parse/conditional_features.py
@@ -0,0 +1,80 @@
+# 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.
+
+"""Helpers for processing conditionally enabled features in a mojom."""
+
+from . import ast
+from ..error import Error
+
+class EnableIfError(Error):
+ """ Class for errors from ."""
+
+ def __init__(self, filename, message, lineno=None):
+ Error.__init__(self, filename, message, lineno=lineno, addenda=None)
+
+def _IsEnabled(definition, enabled_features):
+ """Returns true if a definition is enabled.
+
+ A definition is enabled if it has no EnableIf attribute, or if the value of
+ the EnableIf attribute is in enabled_features.
+ """
+ if not hasattr(definition, "attribute_list"):
+ return True
+ if not definition.attribute_list:
+ return True
+
+ already_defined = False
+ for a in definition.attribute_list:
+ if a.key == 'EnableIf':
+ if already_defined:
+ raise EnableIfError(definition.filename,
+ "EnableIf attribute may only be defined once per field.",
+ definition.lineno)
+ already_defined = True
+
+ for attribute in definition.attribute_list:
+ if attribute.key == 'EnableIf' and attribute.value not in enabled_features:
+ return False
+ return True
+
+
+def _FilterDisabledFromNodeList(node_list, enabled_features):
+ if not node_list:
+ return
+ assert isinstance(node_list, ast.NodeListBase)
+ node_list.items = [
+ item for item in node_list.items if _IsEnabled(item, enabled_features)
+ ]
+ for item in node_list.items:
+ _FilterDefinition(item, enabled_features)
+
+
+def _FilterDefinition(definition, enabled_features):
+ """Filters definitions with a body."""
+ if isinstance(definition, ast.Enum):
+ _FilterDisabledFromNodeList(definition.enum_value_list, enabled_features)
+ elif isinstance(definition, ast.Interface):
+ _FilterDisabledFromNodeList(definition.body, enabled_features)
+ elif isinstance(definition, ast.Method):
+ _FilterDisabledFromNodeList(definition.parameter_list, enabled_features)
+ _FilterDisabledFromNodeList(definition.response_parameter_list,
+ enabled_features)
+ elif isinstance(definition, ast.Struct):
+ _FilterDisabledFromNodeList(definition.body, enabled_features)
+ elif isinstance(definition, ast.Union):
+ _FilterDisabledFromNodeList(definition.body, enabled_features)
+
+
+def RemoveDisabledDefinitions(mojom, enabled_features):
+ """Removes conditionally disabled definitions from a Mojom node."""
+ mojom.import_list = ast.ImportList([
+ imported_file for imported_file in mojom.import_list
+ if _IsEnabled(imported_file, enabled_features)
+ ])
+ mojom.definition_list = [
+ definition for definition in mojom.definition_list
+ if _IsEnabled(definition, enabled_features)
+ ]
+ for definition in mojom.definition_list:
+ _FilterDefinition(definition, enabled_features)
diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/parser.py b/mojo/public/tools/bindings/pylib/mojom/parse/parser.py
index 868fb45f33..b9f10dce9e 100644
--- a/mojo/public/tools/bindings/pylib/mojom/parse/parser.py
+++ b/mojo/public/tools/bindings/pylib/mojom/parse/parser.py
@@ -4,24 +4,12 @@
"""Generates a syntax tree from a Mojo IDL file."""
-import imp
import os.path
import sys
-def _GetDirAbove(dirname):
- """Returns the directory "above" this file containing |dirname| (which must
- also be "above" this file)."""
- path = os.path.abspath(__file__)
- while True:
- path, tail = os.path.split(path)
- assert tail
- if tail == dirname:
- return path
-
-try:
- imp.find_module("ply")
-except ImportError:
- sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party"))
+_current_dir = os.path.dirname(os.path.realpath(__file__))
+sys.path.insert(
+ 1, os.path.join(_current_dir, *([os.pardir] * 7 + ['third_party'])))
from ply import lex
from ply import yacc
@@ -103,10 +91,11 @@ class Parser(object):
p[0].definition_list.append(p[2])
def p_import(self, p):
- """import : IMPORT STRING_LITERAL SEMI"""
+ """import : attribute_section IMPORT STRING_LITERAL SEMI"""
# 'eval' the literal to strip the quotes.
# TODO(vtl): This eval is dubious. We should unquote/unescape ourselves.
- p[0] = ast.Import(eval(p[2]), filename=self.filename, lineno=p.lineno(2))
+ p[0] = ast.Import(p[1], eval(p[3]), filename=self.filename,
+ lineno=p.lineno(2))
def p_module(self, p):
"""module : attribute_section MODULE identifier_wrapped SEMI"""
@@ -383,8 +372,8 @@ class Parser(object):
filename=self.filename, lineno=p.lineno(2))
def p_const(self, p):
- """const : CONST typename NAME EQUALS constant SEMI"""
- p[0] = ast.Const(p[3], p[2], p[5])
+ """const : attribute_section CONST typename NAME EQUALS constant SEMI"""
+ p[0] = ast.Const(p[4], p[1], p[3], p[6])
def p_constant(self, p):
"""constant : literal
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/generate/generator_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/generate/generator_unittest.py
index a684773719..e037c963b4 100644
--- a/mojo/public/tools/bindings/pylib/mojom_tests/generate/generator_unittest.py
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/generate/generator_unittest.py
@@ -27,10 +27,14 @@ from mojom.generate import generator
class StringManipulationTest(unittest.TestCase):
"""generator contains some string utilities, this tests only those."""
- def testUnderToCamel(self):
- """Tests UnderToCamel which converts underscore_separated to CamelCase."""
- self.assertEquals("CamelCase", generator.UnderToCamel("camel_case"))
- self.assertEquals("CamelCase", generator.UnderToCamel("CAMEL_CASE"))
+ def testToCamel(self):
+ self.assertEquals("CamelCase", generator.ToCamel("camel_case"))
+ self.assertEquals("CAMELCASE", generator.ToCamel("CAMEL_CASE"))
+ self.assertEquals("camelCase", generator.ToCamel("camel_case",
+ lower_initial=True))
+ self.assertEquals("CamelCase", generator.ToCamel("camel case",
+ dilimiter=' '))
+ self.assertEquals("CaMelCaSe", generator.ToCamel("caMel_caSe"))
if __name__ == "__main__":
unittest.main()
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/conditional_features_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/conditional_features_unittest.py
new file mode 100644
index 0000000000..4d5838823f
--- /dev/null
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/conditional_features_unittest.py
@@ -0,0 +1,232 @@
+# 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.
+
+import imp
+import os
+import sys
+import unittest
+
+
+def _GetDirAbove(dirname):
+ """Returns the directory "above" this file containing |dirname| (which must
+ also be "above" this file)."""
+ path = os.path.abspath(__file__)
+ while True:
+ path, tail = os.path.split(path)
+ assert tail
+ if tail == dirname:
+ return path
+
+
+try:
+ imp.find_module('mojom')
+except ImportError:
+ sys.path.append(os.path.join(_GetDirAbove('pylib'), 'pylib'))
+import mojom.parse.ast as ast
+import mojom.parse.conditional_features as conditional_features
+import mojom.parse.parser as parser
+
+ENABLED_FEATURES = frozenset({'red', 'green', 'blue'})
+
+
+class ConditionalFeaturesTest(unittest.TestCase):
+ """Tests |mojom.parse.conditional_features|."""
+
+ def parseAndAssertEqual(self, source, expected_source):
+ definition = parser.Parse(source, "my_file.mojom")
+ conditional_features.RemoveDisabledDefinitions(definition, ENABLED_FEATURES)
+ expected = parser.Parse(expected_source, "my_file.mojom")
+ self.assertEquals(definition, expected)
+
+ def testFilterConst(self):
+ """Test that Consts are correctly filtered."""
+ const_source = """
+ [EnableIf=blue]
+ const int kMyConst1 = 1;
+ [EnableIf=orange]
+ const double kMyConst2 = 2;
+ const int kMyConst3 = 3;
+ """
+ expected_source = """
+ [EnableIf=blue]
+ const int kMyConst1 = 1;
+ const int kMyConst3 = 3;
+ """
+ self.parseAndAssertEqual(const_source, expected_source)
+
+ def testFilterEnum(self):
+ """Test that EnumValues are correctly filtered from an Enum."""
+ enum_source = """
+ enum MyEnum {
+ [EnableIf=purple]
+ VALUE1,
+ [EnableIf=blue]
+ VALUE2,
+ VALUE3,
+ };
+ """
+ expected_source = """
+ enum MyEnum {
+ [EnableIf=blue]
+ VALUE2,
+ VALUE3
+ };
+ """
+ self.parseAndAssertEqual(enum_source, expected_source)
+
+ def testFilterImport(self):
+ """Test that imports are correctly filtered from a Mojom."""
+ import_source = """
+ [EnableIf=blue]
+ import "foo.mojom";
+ import "bar.mojom";
+ [EnableIf=purple]
+ import "baz.mojom";
+ """
+ expected_source = """
+ [EnableIf=blue]
+ import "foo.mojom";
+ import "bar.mojom";
+ """
+ self.parseAndAssertEqual(import_source, expected_source)
+
+ def testFilterInterface(self):
+ """Test that definitions are correctly filtered from an Interface."""
+ interface_source = """
+ interface MyInterface {
+ [EnableIf=blue]
+ enum MyEnum {
+ [EnableIf=purple]
+ VALUE1,
+ VALUE2,
+ };
+ [EnableIf=blue]
+ const int32 kMyConst = 123;
+ [EnableIf=purple]
+ MyMethod();
+ };
+ """
+ expected_source = """
+ interface MyInterface {
+ [EnableIf=blue]
+ enum MyEnum {
+ VALUE2,
+ };
+ [EnableIf=blue]
+ const int32 kMyConst = 123;
+ };
+ """
+ self.parseAndAssertEqual(interface_source, expected_source)
+
+ def testFilterMethod(self):
+ """Test that Parameters are correctly filtered from a Method."""
+ method_source = """
+ interface MyInterface {
+ [EnableIf=blue]
+ MyMethod([EnableIf=purple] int32 a) => ([EnableIf=red] int32 b);
+ };
+ """
+ expected_source = """
+ interface MyInterface {
+ [EnableIf=blue]
+ MyMethod() => ([EnableIf=red] int32 b);
+ };
+ """
+ self.parseAndAssertEqual(method_source, expected_source)
+
+ def testFilterStruct(self):
+ """Test that definitions are correctly filtered from a Struct."""
+ struct_source = """
+ struct MyStruct {
+ [EnableIf=blue]
+ enum MyEnum {
+ VALUE1,
+ [EnableIf=purple]
+ VALUE2,
+ };
+ [EnableIf=yellow]
+ const double kMyConst = 1.23;
+ [EnableIf=green]
+ int32 a;
+ double b;
+ [EnableIf=purple]
+ int32 c;
+ [EnableIf=blue]
+ double d;
+ int32 e;
+ [EnableIf=orange]
+ double f;
+ };
+ """
+ expected_source = """
+ struct MyStruct {
+ [EnableIf=blue]
+ enum MyEnum {
+ VALUE1,
+ };
+ [EnableIf=green]
+ int32 a;
+ double b;
+ [EnableIf=blue]
+ double d;
+ int32 e;
+ };
+ """
+ self.parseAndAssertEqual(struct_source, expected_source)
+
+ def testFilterUnion(self):
+ """Test that UnionFields are correctly filtered from a Union."""
+ union_source = """
+ union MyUnion {
+ [EnableIf=yellow]
+ int32 a;
+ [EnableIf=red]
+ bool b;
+ };
+ """
+ expected_source = """
+ union MyUnion {
+ [EnableIf=red]
+ bool b;
+ };
+ """
+ self.parseAndAssertEqual(union_source, expected_source)
+
+ def testSameNameFields(self):
+ mojom_source = """
+ enum Foo {
+ [EnableIf=red]
+ VALUE1 = 5,
+ [EnableIf=yellow]
+ VALUE1 = 6,
+ };
+ [EnableIf=red]
+ const double kMyConst = 1.23;
+ [EnableIf=yellow]
+ const double kMyConst = 4.56;
+ """
+ expected_source = """
+ enum Foo {
+ [EnableIf=red]
+ VALUE1 = 5,
+ };
+ [EnableIf=red]
+ const double kMyConst = 1.23;
+ """
+ self.parseAndAssertEqual(mojom_source, expected_source)
+
+ def testMultipleEnableIfs(self):
+ source = """
+ enum Foo {
+ [EnableIf=red,EnableIf=yellow]
+ kBarValue = 5,
+ };
+ """
+ definition = parser.Parse(source, "my_file.mojom")
+ self.assertRaises(conditional_features.EnableIfError,
+ conditional_features.RemoveDisabledDefinitions,
+ definition, ENABLED_FEATURES)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py
index 6822cbc8d0..36d8c4abfe 100644
--- a/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py
@@ -17,10 +17,7 @@ def _GetDirAbove(dirname):
if tail == dirname:
return path
-try:
- imp.find_module("ply")
-except ImportError:
- sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party"))
+sys.path.insert(1, os.path.join(_GetDirAbove("mojo"), "third_party"))
from ply import lex
try:
diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py
index 3f4ca871e3..b156bb71ba 100644
--- a/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py
+++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py
@@ -192,7 +192,7 @@ class ParserTest(unittest.TestCase):
None,
ast.EnumValueList(
ast.EnumValue('VALUE', None, None))),
- ast.Const('kMyConst', 'double', '1.23'),
+ ast.Const('kMyConst', None, 'double', '1.23'),
ast.StructField('a', None, None, 'int32', None),
ast.StructField('b', None, None, 'SomeOtherStruct', None)]))])
self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
@@ -409,7 +409,7 @@ class ParserTest(unittest.TestCase):
[ast.Struct(
'MyStruct', None,
ast.StructBody(
- [ast.Const('kNumber', 'int8', '-1'),
+ [ast.Const('kNumber', None, 'int8', '-1'),
ast.StructField('number', None, ast.Ordinal(0), 'int8',
('IDENTIFIER', 'kNumber'))]))])
self.assertEquals(parser.Parse(source, "my_file.mojom"), expected)
@@ -897,7 +897,7 @@ class ParserTest(unittest.TestCase):
None,
ast.EnumValueList(
ast.EnumValue('VALUE', None, None))),
- ast.Const('kMyConst', 'int32', '123'),
+ ast.Const('kMyConst', None, 'int32', '123'),
ast.Method(
'MyMethod',
None,
@@ -990,61 +990,71 @@ class ParserTest(unittest.TestCase):
source4 = """\
[Attr0=0] module my_module;
- [Attr1=1] struct MyStruct {
- [Attr2=2] int32 a;
+ [Attr1=1] import "my_import";
+
+ [Attr2=2] struct MyStruct {
+ [Attr3=3] int32 a;
};
- [Attr3=3] union MyUnion {
- [Attr4=4] int32 a;
+ [Attr4=4] union MyUnion {
+ [Attr5=5] int32 a;
};
- [Attr5=5] enum MyEnum {
- [Attr6=6] a
+ [Attr6=6] enum MyEnum {
+ [Attr7=7] a
};
- [Attr7=7] interface MyInterface {
- [Attr8=8] MyMethod([Attr9=9] int32 a) => ([Attr10=10] bool b);
+ [Attr8=8] interface MyInterface {
+ [Attr9=9] MyMethod([Attr10=10] int32 a) => ([Attr11=11] bool b);
};
+ [Attr12=12] const double kMyConst = 1.23;
"""
expected4 = ast.Mojom(
ast.Module(('IDENTIFIER', 'my_module'),
ast.AttributeList([ast.Attribute("Attr0", 0)])),
- ast.ImportList(),
+ ast.ImportList(ast.Import(
+ ast.AttributeList([ast.Attribute("Attr1", 1)]),
+ "my_import")),
[ast.Struct(
'MyStruct',
- ast.AttributeList(ast.Attribute("Attr1", 1)),
+ ast.AttributeList(ast.Attribute("Attr2", 2)),
ast.StructBody(
ast.StructField(
- 'a', ast.AttributeList([ast.Attribute("Attr2", 2)]),
+ 'a', ast.AttributeList([ast.Attribute("Attr3", 3)]),
None, 'int32', None))),
ast.Union(
'MyUnion',
- ast.AttributeList(ast.Attribute("Attr3", 3)),
+ ast.AttributeList(ast.Attribute("Attr4", 4)),
ast.UnionBody(
ast.UnionField(
- 'a', ast.AttributeList([ast.Attribute("Attr4", 4)]), None,
+ 'a', ast.AttributeList([ast.Attribute("Attr5", 5)]), None,
'int32'))),
ast.Enum(
'MyEnum',
- ast.AttributeList(ast.Attribute("Attr5", 5)),
+ ast.AttributeList(ast.Attribute("Attr6", 6)),
ast.EnumValueList(
ast.EnumValue(
- 'VALUE', ast.AttributeList([ast.Attribute("Attr6", 6)]),
+ 'VALUE', ast.AttributeList([ast.Attribute("Attr7", 7)]),
None))),
ast.Interface(
'MyInterface',
- ast.AttributeList(ast.Attribute("Attr7", 7)),
+ ast.AttributeList(ast.Attribute("Attr8", 8)),
ast.InterfaceBody(
ast.Method(
'MyMethod',
- ast.AttributeList(ast.Attribute("Attr8", 8)),
+ ast.AttributeList(ast.Attribute("Attr9", 9)),
None,
ast.ParameterList(
ast.Parameter(
- 'a', ast.AttributeList([ast.Attribute("Attr9", 9)]),
+ 'a',
+ ast.AttributeList([ast.Attribute("Attr10", 10)]),
None, 'int32')),
ast.ParameterList(
ast.Parameter(
'b',
- ast.AttributeList([ast.Attribute("Attr10", 10)]),
- None, 'bool')))))])
+ ast.AttributeList([ast.Attribute("Attr11", 11)]),
+ None, 'bool'))))),
+ ast.Const(
+ 'kMyConst',
+ ast.AttributeList(ast.Attribute("Attr12", 12)),
+ 'double', '1.23')])
self.assertEquals(parser.Parse(source4, "my_file.mojom"), expected4)
# TODO(vtl): Boolean attributes don't work yet. (In fact, we just |eval()|
@@ -1085,7 +1095,7 @@ class ParserTest(unittest.TestCase):
source1 = "import \"somedir/my.mojom\";"
expected1 = ast.Mojom(
None,
- ast.ImportList(ast.Import("somedir/my.mojom")),
+ ast.ImportList(ast.Import(None, "somedir/my.mojom")),
[])
self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)
@@ -1096,8 +1106,8 @@ class ParserTest(unittest.TestCase):
"""
expected2 = ast.Mojom(
None,
- ast.ImportList([ast.Import("somedir/my1.mojom"),
- ast.Import("somedir/my2.mojom")]),
+ ast.ImportList([ast.Import(None, "somedir/my1.mojom"),
+ ast.Import(None, "somedir/my2.mojom")]),
[])
self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)
@@ -1109,8 +1119,8 @@ class ParserTest(unittest.TestCase):
"""
expected3 = ast.Mojom(
ast.Module(('IDENTIFIER', 'my_module'), None),
- ast.ImportList([ast.Import("somedir/my1.mojom"),
- ast.Import("somedir/my2.mojom")]),
+ ast.ImportList([ast.Import(None, "somedir/my1.mojom"),
+ ast.Import(None, "somedir/my2.mojom")]),
[])
self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3)
diff --git a/mojo/public/tools/fuzzers/BUILD.gn b/mojo/public/tools/fuzzers/BUILD.gn
new file mode 100644
index 0000000000..69531552db
--- /dev/null
+++ b/mojo/public/tools/fuzzers/BUILD.gn
@@ -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.
+
+# Mojo fuzzing tools
+
+import("//build/config/features.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
+import("//testing/libfuzzer/fuzzer_test.gni")
+import("//third_party/protobuf/proto_library.gni")
+
+# mojo/public BUILD depends on this target. Needed for package discovery
+group("fuzzers") {
+}
+
+mojom("fuzz_mojom") {
+ sources = [
+ "fuzz.mojom",
+ ]
+}
+
+fuzzer_test("mojo_parse_message_fuzzer") {
+ sources = [
+ "fuzz_impl.cc",
+ "mojo_parse_message_fuzzer.cc",
+ ]
+ deps = [
+ ":fuzz_mojom",
+ "//mojo/core/embedder",
+ ]
+ seed_corpus = "//mojo/public/tools/fuzzers/message_corpus"
+}
+
+# MessageDumper is not meant to work on Windows.
+if (!is_win) {
+ executable("mojo_fuzzer_message_dump") {
+ sources = [
+ "fuzz_impl.cc",
+ "mojo_fuzzer_message_dump.cc",
+ ]
+ deps = [
+ ":fuzz_mojom",
+ "//base",
+ "//mojo/core/embedder",
+ ]
+ }
+}
+
+fuzzer_test("mojo_parse_message_proto_fuzzer") {
+ sources = [
+ "fuzz_impl.cc",
+ "mojo_parse_message_proto_fuzzer.cc",
+ ]
+ deps = [
+ ":fuzz_mojom",
+ ":mojo_fuzzer_proto",
+ "//mojo/core/embedder",
+ "//third_party/libprotobuf-mutator",
+ ]
+ seed_corpus = "//mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus"
+}
+
+proto_library("mojo_fuzzer_proto") {
+ sources = [
+ "mojo_fuzzer.proto",
+ ]
+}
diff --git a/mojo/public/tools/fuzzers/fuzz.mojom b/mojo/public/tools/fuzzers/fuzz.mojom
new file mode 100644
index 0000000000..53c8ac6aa4
--- /dev/null
+++ b/mojo/public/tools/fuzzers/fuzz.mojom
@@ -0,0 +1,78 @@
+module fuzz.mojom;
+
+[Extensible]
+enum FuzzEnum {
+ FUZZ_VALUE0,
+ FUZZ_VALUE1,
+ FUZZ_VALUE2
+};
+
+struct FuzzDummyStruct {
+ int8 dummy;
+};
+
+union FuzzUnion {
+ bool fuzz_bool;
+ int8 fuzz_int8;
+ uint8 fuzz_uint8;
+ int16 fuzz_int16;
+ uint16 fuzz_uint16;
+ int32 fuzz_int32;
+ uint32 fuzz_uint32;
+ int64 fuzz_int64;
+ uint64 fuzz_uint64;
+ float fuzz_float;
+ double fuzz_double;
+ string fuzz_string;
+ array<int8> fuzz_primitive_array;
+ array<FuzzDummyStruct> fuzz_struct_array;
+ map<string, int8> fuzz_primitive_map;
+ map<string, array<string>> fuzz_array_map;
+ map<string, FuzzDummyStruct> fuzz_struct_map;
+ map<FuzzEnum, FuzzUnion> fuzz_union_map;
+
+ array<map<FuzzEnum, map<int8, array<FuzzUnion?>?>>>? fuzz_complex;
+};
+
+struct FuzzStruct {
+ bool fuzz_bool;
+ int8 fuzz_int8;
+ uint8 fuzz_uint8;
+ int16 fuzz_int16;
+ uint16 fuzz_uint16;
+ int32 fuzz_int32;
+ uint32 fuzz_uint32;
+ int64 fuzz_int64;
+ uint64 fuzz_uint64;
+ float fuzz_float;
+ double fuzz_double;
+ string fuzz_string;
+
+ array<int8> fuzz_primitive_array;
+ map<string, int8> fuzz_primitive_map;
+ map<string, array<string>> fuzz_array_map;
+ map<FuzzEnum, FuzzUnion> fuzz_union_map;
+ array<FuzzUnion> fuzz_union_array;
+ array<FuzzStruct> fuzz_struct_array;
+ array<int8>? fuzz_nullable_array;
+
+ array<map<FuzzEnum, map<int8, array<FuzzStruct?>?>>>? fuzz_complex;
+};
+
+interface FuzzDummyInterface {
+ Ping();
+};
+
+interface FuzzInterface {
+ FuzzBasic();
+ FuzzBasicResp() => ();
+ [Sync]
+ FuzzBasicSyncResp() => ();
+
+ FuzzArgs(FuzzStruct a, FuzzStruct? b);
+ FuzzArgsResp(FuzzStruct a, FuzzStruct? b) => ();
+ [Sync]
+ FuzzArgsSyncResp(FuzzStruct a, FuzzStruct? b) => ();
+
+ FuzzAssociated(associated FuzzDummyInterface& request);
+};
diff --git a/mojo/public/tools/fuzzers/fuzz_impl.cc b/mojo/public/tools/fuzzers/fuzz_impl.cc
new file mode 100644
index 0000000000..d3b10128bd
--- /dev/null
+++ b/mojo/public/tools/fuzzers/fuzz_impl.cc
@@ -0,0 +1,45 @@
+// 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 <utility>
+
+#include "mojo/public/tools/fuzzers/fuzz.mojom.h"
+#include "mojo/public/tools/fuzzers/fuzz_impl.h"
+
+FuzzImpl::FuzzImpl(fuzz::mojom::FuzzInterfaceRequest request)
+ : binding_(this, std::move(request)) {}
+
+FuzzImpl::~FuzzImpl() {}
+
+void FuzzImpl::FuzzBasic() {}
+
+void FuzzImpl::FuzzBasicResp(FuzzBasicRespCallback callback) {
+ std::move(callback).Run();
+}
+
+void FuzzImpl::FuzzBasicSyncResp(FuzzBasicSyncRespCallback callback) {
+ std::move(callback).Run();
+}
+
+void FuzzImpl::FuzzArgs(fuzz::mojom::FuzzStructPtr a,
+ fuzz::mojom::FuzzStructPtr b) {}
+
+void FuzzImpl::FuzzArgsResp(fuzz::mojom::FuzzStructPtr a,
+ fuzz::mojom::FuzzStructPtr b,
+ FuzzArgsRespCallback callback) {
+ std::move(callback).Run();
+}
+
+void FuzzImpl::FuzzArgsSyncResp(fuzz::mojom::FuzzStructPtr a,
+ fuzz::mojom::FuzzStructPtr b,
+ FuzzArgsSyncRespCallback callback) {
+ std::move(callback).Run();
+}
+
+void FuzzImpl::FuzzAssociated(
+ fuzz::mojom::FuzzDummyInterfaceAssociatedRequest req) {
+ associated_bindings_.AddBinding(this, std::move(req));
+}
+
+void FuzzImpl::Ping() {}
diff --git a/mojo/public/tools/fuzzers/fuzz_impl.h b/mojo/public/tools/fuzzers/fuzz_impl.h
new file mode 100644
index 0000000000..d63a7073e2
--- /dev/null
+++ b/mojo/public/tools/fuzzers/fuzz_impl.h
@@ -0,0 +1,45 @@
+// 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_TOOLS_FUZZERS_FUZZ_IMPL_H_
+#define MOJO_PUBLIC_TOOLS_FUZZERS_FUZZ_IMPL_H_
+
+#include "mojo/public/cpp/bindings/associated_binding_set.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/tools/fuzzers/fuzz.mojom.h"
+
+class FuzzImpl : public fuzz::mojom::FuzzInterface,
+ public fuzz::mojom::FuzzDummyInterface {
+ public:
+ explicit FuzzImpl(fuzz::mojom::FuzzInterfaceRequest request);
+ ~FuzzImpl() override;
+
+ // fuzz::mojom::FuzzInterface:
+ void FuzzBasic() override;
+ void FuzzBasicResp(FuzzBasicRespCallback callback) override;
+ void FuzzBasicSyncResp(FuzzBasicSyncRespCallback callback) override;
+ void FuzzArgs(fuzz::mojom::FuzzStructPtr a,
+ fuzz::mojom::FuzzStructPtr b) override;
+
+ void FuzzArgsResp(fuzz::mojom::FuzzStructPtr a,
+ fuzz::mojom::FuzzStructPtr b,
+ FuzzArgsRespCallback callback) override;
+ void FuzzArgsSyncResp(fuzz::mojom::FuzzStructPtr a,
+ fuzz::mojom::FuzzStructPtr b,
+ FuzzArgsSyncRespCallback callback) override;
+
+ void FuzzAssociated(
+ fuzz::mojom::FuzzDummyInterfaceAssociatedRequest req) override;
+
+ // fuzz::mojom::FuzzDummyInterface:
+ void Ping() override;
+
+ /* Expose the binding to the fuzz harness. */
+ mojo::Binding<FuzzInterface> binding_;
+
+ private:
+ mojo::AssociatedBindingSet<FuzzDummyInterface> associated_bindings_;
+};
+
+#endif // MOJO_PUBLIC_TOOLS_FUZZERS_FUZZ_IMPL_H_
diff --git a/mojo/public/tools/fuzzers/message_corpus/message_0.mojomsg b/mojo/public/tools/fuzzers/message_corpus/message_0.mojomsg
new file mode 100644
index 0000000000..945b63c472
--- /dev/null
+++ b/mojo/public/tools/fuzzers/message_corpus/message_0.mojomsg
Binary files differ
diff --git a/mojo/public/tools/fuzzers/message_corpus/message_1.mojomsg b/mojo/public/tools/fuzzers/message_corpus/message_1.mojomsg
new file mode 100644
index 0000000000..41715c8239
--- /dev/null
+++ b/mojo/public/tools/fuzzers/message_corpus/message_1.mojomsg
Binary files differ
diff --git a/mojo/public/tools/fuzzers/message_corpus/message_10.mojomsg b/mojo/public/tools/fuzzers/message_corpus/message_10.mojomsg
new file mode 100644
index 0000000000..c19d5c752e
--- /dev/null
+++ b/mojo/public/tools/fuzzers/message_corpus/message_10.mojomsg
Binary files differ
diff --git a/mojo/public/tools/fuzzers/message_corpus/message_11.mojomsg b/mojo/public/tools/fuzzers/message_corpus/message_11.mojomsg
new file mode 100644
index 0000000000..61ba989803
--- /dev/null
+++ b/mojo/public/tools/fuzzers/message_corpus/message_11.mojomsg
Binary files differ
diff --git a/mojo/public/tools/fuzzers/message_corpus/message_2.mojomsg b/mojo/public/tools/fuzzers/message_corpus/message_2.mojomsg
new file mode 100644
index 0000000000..7b5b302888
--- /dev/null
+++ b/mojo/public/tools/fuzzers/message_corpus/message_2.mojomsg
Binary files differ
diff --git a/mojo/public/tools/fuzzers/message_corpus/message_3.mojomsg b/mojo/public/tools/fuzzers/message_corpus/message_3.mojomsg
new file mode 100644
index 0000000000..d0503715fd
--- /dev/null
+++ b/mojo/public/tools/fuzzers/message_corpus/message_3.mojomsg
Binary files differ
diff --git a/mojo/public/tools/fuzzers/message_corpus/message_4.mojomsg b/mojo/public/tools/fuzzers/message_corpus/message_4.mojomsg
new file mode 100644
index 0000000000..380c391bbc
--- /dev/null
+++ b/mojo/public/tools/fuzzers/message_corpus/message_4.mojomsg
Binary files differ
diff --git a/mojo/public/tools/fuzzers/message_corpus/message_5.mojomsg b/mojo/public/tools/fuzzers/message_corpus/message_5.mojomsg
new file mode 100644
index 0000000000..1bf4b452b7
--- /dev/null
+++ b/mojo/public/tools/fuzzers/message_corpus/message_5.mojomsg
Binary files differ
diff --git a/mojo/public/tools/fuzzers/message_corpus/message_6.mojomsg b/mojo/public/tools/fuzzers/message_corpus/message_6.mojomsg
new file mode 100644
index 0000000000..8cffad65f7
--- /dev/null
+++ b/mojo/public/tools/fuzzers/message_corpus/message_6.mojomsg
Binary files differ
diff --git a/mojo/public/tools/fuzzers/message_corpus/message_7.mojomsg b/mojo/public/tools/fuzzers/message_corpus/message_7.mojomsg
new file mode 100644
index 0000000000..aeff3d6024
--- /dev/null
+++ b/mojo/public/tools/fuzzers/message_corpus/message_7.mojomsg
Binary files differ
diff --git a/mojo/public/tools/fuzzers/message_corpus/message_8.mojomsg b/mojo/public/tools/fuzzers/message_corpus/message_8.mojomsg
new file mode 100644
index 0000000000..019f9e656e
--- /dev/null
+++ b/mojo/public/tools/fuzzers/message_corpus/message_8.mojomsg
Binary files differ
diff --git a/mojo/public/tools/fuzzers/message_corpus/message_9.mojomsg b/mojo/public/tools/fuzzers/message_corpus/message_9.mojomsg
new file mode 100644
index 0000000000..61049cf149
--- /dev/null
+++ b/mojo/public/tools/fuzzers/message_corpus/message_9.mojomsg
Binary files differ
diff --git a/mojo/public/tools/fuzzers/mojo_fuzzer.proto b/mojo/public/tools/fuzzers/mojo_fuzzer.proto
new file mode 100644
index 0000000000..fb17871151
--- /dev/null
+++ b/mojo/public/tools/fuzzers/mojo_fuzzer.proto
@@ -0,0 +1,7 @@
+syntax = "proto2";
+
+package mojo_proto_fuzzer;
+
+message MojoFuzzerMessages {
+ repeated bytes messages = 1;
+} \ No newline at end of file
diff --git a/mojo/public/tools/fuzzers/mojo_fuzzer_message_dump.cc b/mojo/public/tools/fuzzers/mojo_fuzzer_message_dump.cc
new file mode 100644
index 0000000000..62d8d87b29
--- /dev/null
+++ b/mojo/public/tools/fuzzers/mojo_fuzzer_message_dump.cc
@@ -0,0 +1,266 @@
+// 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 <stddef.h>
+#include <stdint.h>
+
+#include "base/containers/flat_map.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task_scheduler/task_scheduler.h"
+#include "mojo/core/embedder/embedder.h"
+#include "mojo/public/tools/fuzzers/fuzz.mojom.h"
+#include "mojo/public/tools/fuzzers/fuzz_impl.h"
+
+/* Environment for the executable. Initializes the mojo EDK and sets up a
+ * TaskScheduler, because Mojo messages must be sent and processed from
+ * TaskRunners. */
+struct Environment {
+ Environment() : message_loop() {
+ base::TaskScheduler::CreateAndStartWithDefaultParams(
+ "MojoFuzzerMessageDumpProcess");
+ mojo::core::Init();
+ }
+
+ /* Message loop to send messages on. */
+ base::MessageLoop message_loop;
+
+ /* Impl to be created. Stored in environment to keep it alive after
+ * DumpMessages returns. */
+ std::unique_ptr<FuzzImpl> impl;
+};
+
+Environment* env = new Environment();
+
+/* MessageReceiver which dumps raw message bytes to disk in the provided
+ * directory. */
+class MessageDumper : public mojo::MessageReceiver {
+ public:
+ explicit MessageDumper(std::string directory)
+ : directory_(directory), count_(0) {}
+
+ bool Accept(mojo::Message* message) override {
+ base::FilePath path = directory_.Append(FILE_PATH_LITERAL("message_") +
+ base::IntToString(count_++) +
+ FILE_PATH_LITERAL(".mojomsg"));
+
+ base::File file(path,
+ base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+ if (!file.IsValid()) {
+ LOG(ERROR) << "Failed to create mojo message file: " << path.value();
+ return false;
+ }
+
+ size_t size = message->data_num_bytes();
+ const char* data = reinterpret_cast<const char*>(message->data());
+ int ret = file.WriteAtCurrentPos(data, size);
+ if (ret != static_cast<int>(size)) {
+ LOG(ERROR) << "Failed to write " << size << " bytes.";
+ return false;
+ }
+ return true;
+ }
+
+ base::FilePath directory_;
+ int count_;
+};
+
+/* Returns a FuzzUnion with fuzz_bool initialized. */
+auto GetBoolFuzzUnion() {
+ fuzz::mojom::FuzzUnionPtr union_bool = fuzz::mojom::FuzzUnion::New();
+ union_bool->set_fuzz_bool(true);
+ return union_bool;
+}
+
+/* Returns a FuzzUnion with fuzz_struct_map initialized. Takes in a
+ * FuzzDummyStructPtr to use within the fuzz_struct_map value. */
+auto GetStructMapFuzzUnion(fuzz::mojom::FuzzDummyStructPtr in) {
+ fuzz::mojom::FuzzUnionPtr union_struct_map = fuzz::mojom::FuzzUnion::New();
+ base::flat_map<std::string, fuzz::mojom::FuzzDummyStructPtr> struct_map;
+ struct_map["fuzz"] = std::move(in);
+ union_struct_map->set_fuzz_struct_map(std::move(struct_map));
+ return union_struct_map;
+}
+
+/* Returns a FuzzUnion with fuzz_complex initialized. Takes in a FuzzUnionPtr
+ * to use within the fuzz_complex value. */
+auto GetComplexFuzzUnion(fuzz::mojom::FuzzUnionPtr in) {
+ std::remove_reference<decltype(in->get_fuzz_complex())>::type complex_map;
+ std::remove_reference<decltype(complex_map.value()[0])>::type outer;
+ std::remove_reference<decltype(
+ outer[fuzz::mojom::FuzzEnum::FUZZ_VALUE0])>::type inner;
+ std::remove_reference<decltype(inner['z'])>::type center;
+
+ center.emplace();
+ center.value().push_back(std::move(in));
+ inner['z'] = std::move(center);
+ outer[fuzz::mojom::FuzzEnum::FUZZ_VALUE0] = std::move(inner);
+ complex_map.emplace();
+ complex_map.value().push_back(std::move(outer));
+
+ fuzz::mojom::FuzzUnionPtr union_complex = fuzz::mojom::FuzzUnion::New();
+ union_complex->set_fuzz_complex(std::move(complex_map));
+ return union_complex;
+}
+
+/* Returns a populated value for FuzzStruct->fuzz_primitive_array. */
+auto GetFuzzStructPrimitiveArrayValue() {
+ decltype(fuzz::mojom::FuzzStruct::fuzz_primitive_array) primitive_array;
+ primitive_array = {'f', 'u', 'z', 'z'};
+ return primitive_array;
+}
+
+/* Returns a populated value for FuzzStruct->fuzz_primitive_map. */
+auto GetFuzzStructPrimitiveMapValue() {
+ decltype(fuzz::mojom::FuzzStruct::fuzz_primitive_map) primitive_map;
+ primitive_map["fuzz"] = 'z';
+ return primitive_map;
+}
+
+/* Returns a populated value for FuzzStruct->fuzz_array_map. */
+auto GetFuzzStructArrayMapValue() {
+ decltype(fuzz::mojom::FuzzStruct::fuzz_array_map) array_map;
+ array_map["fuzz"] = {"fuzz1", "fuzz2"};
+ return array_map;
+}
+
+/* Returns a populated value for FuzzStruct->fuzz_union_map. Takes in a
+ * FuzzUnionPtr to use within the fuzz_union_map value.*/
+auto GetFuzzStructUnionMapValue(fuzz::mojom::FuzzUnionPtr in) {
+ decltype(fuzz::mojom::FuzzStruct::fuzz_union_map) union_map;
+ union_map[fuzz::mojom::FuzzEnum::FUZZ_VALUE1] = std::move(in);
+ return union_map;
+}
+
+/* Returns a populated value for FuzzStruct->fuzz_union_array. Takes in a
+ * FuzzUnionPtr to use within the fuzz_union_array value.*/
+auto GetFuzzStructUnionArrayValue(fuzz::mojom::FuzzUnionPtr in) {
+ decltype(fuzz::mojom::FuzzStruct::fuzz_union_array) union_array;
+ union_array.push_back(std::move(in));
+ return union_array;
+}
+
+/* Returns a populated value for FuzzStruct->fuzz_struct_array. Takes in a
+ * FuzzStructPtr to use within the fuzz_struct_array value. */
+auto GetFuzzStructStructArrayValue(fuzz::mojom::FuzzStructPtr in) {
+ decltype(fuzz::mojom::FuzzStruct::fuzz_struct_array) struct_array;
+ struct_array.push_back(std::move(in));
+ return struct_array;
+}
+
+/* Returns a populated value for FuzzStruct->fuzz_nullable_array. */
+auto GetFuzzStructNullableArrayValue() {
+ decltype(fuzz::mojom::FuzzStruct::fuzz_nullable_array) nullable_array;
+ return nullable_array;
+}
+
+/* Returns a populated value for FuzzStruct->fuzz_complex. */
+auto GetFuzzStructComplexValue() {
+ decltype(fuzz::mojom::FuzzStruct::fuzz_complex) complex_map;
+ std::remove_reference<decltype(complex_map.value()[0])>::type outer;
+ std::remove_reference<decltype(
+ outer[fuzz::mojom::FuzzEnum::FUZZ_VALUE0])>::type inner;
+ std::remove_reference<decltype(inner['z'])>::type center;
+
+ center.emplace();
+ center.value().push_back(fuzz::mojom::FuzzStruct::New());
+ inner['z'] = std::move(center);
+ outer[fuzz::mojom::FuzzEnum::FUZZ_VALUE0] = std::move(inner);
+ complex_map.emplace();
+ complex_map.value().push_back(std::move(outer));
+ return complex_map;
+}
+
+/* Returns a FuzzStruct with its fields populated. */
+fuzz::mojom::FuzzStructPtr GetPopulatedFuzzStruct() {
+ /* Make some populated Unions. */
+ auto union_bool = GetBoolFuzzUnion();
+ auto union_struct_map =
+ GetStructMapFuzzUnion(fuzz::mojom::FuzzDummyStruct::New());
+ auto union_complex = GetComplexFuzzUnion(std::move(union_bool));
+
+ /* Prepare the nontrivial fields for the struct. */
+ auto fuzz_primitive_array = GetFuzzStructPrimitiveArrayValue();
+ auto fuzz_primitive_map = GetFuzzStructPrimitiveMapValue();
+ auto fuzz_array_map = GetFuzzStructArrayMapValue();
+ auto fuzz_union_map = GetFuzzStructUnionMapValue(std::move(union_struct_map));
+ auto fuzz_union_array =
+ GetFuzzStructUnionArrayValue(std::move(union_complex));
+ auto fuzz_struct_array =
+ GetFuzzStructStructArrayValue(fuzz::mojom::FuzzStruct::New());
+ auto fuzz_nullable_array = GetFuzzStructNullableArrayValue();
+ auto fuzz_complex = GetFuzzStructComplexValue();
+
+ /* Make a populated struct and return it. */
+ return fuzz::mojom::FuzzStruct::New(
+ true, /* fuzz_bool */
+ -1, /* fuzz_int8 */
+ 1, /* fuzz_uint8 */
+ -(1 << 8), /* fuzz_int16 */
+ 1 << 8, /* fuzz_uint16 */
+ -(1 << 16), /* fuzz_int32 */
+ 1 << 16, /* fuzz_uint32 */
+ -((int64_t)1 << 32), /* fuzz_int64 */
+ (uint64_t)1 << 32, /* fuzz_uint64 */
+ 1.0, /* fuzz_float */
+ 1.0, /* fuzz_double */
+ "fuzz", /* fuzz_string */
+ std::move(fuzz_primitive_array), /* fuzz_primitive_array */
+ std::move(fuzz_primitive_map), /* fuzz_primitive_map */
+ std::move(fuzz_array_map), /* fuzz_array_map */
+ std::move(fuzz_union_map), /* fuzz_union_map */
+ std::move(fuzz_union_array), /* fuzz_union_array */
+ std::move(fuzz_struct_array), /* fuzz_struct_array */
+ std::move(fuzz_nullable_array), /* fuzz_nullable_array */
+ std::move(fuzz_complex)); /* fuzz_complex */
+}
+
+/* Callback used for messages with responses. Does nothing. */
+void FuzzCallback() {}
+
+/* Invokes each method in the FuzzInterface and dumps the messages to the
+ * supplied directory. */
+void DumpMessages(std::string output_directory) {
+ fuzz::mojom::FuzzInterfacePtr fuzz;
+ fuzz::mojom::FuzzDummyInterfaceAssociatedPtr dummy;
+
+ /* Create the impl and add a MessageDumper to the filter chain. */
+ env->impl = std::make_unique<FuzzImpl>(MakeRequest(&fuzz));
+ env->impl->binding_.RouterForTesting()->AddIncomingMessageFilter(
+ std::make_unique<MessageDumper>(output_directory));
+
+ /* Call methods in various ways to generate interesting messages. */
+ fuzz->FuzzBasic();
+ fuzz->FuzzBasicResp(base::Bind(FuzzCallback));
+ fuzz->FuzzBasicSyncResp();
+ fuzz->FuzzArgs(fuzz::mojom::FuzzStruct::New(),
+ fuzz::mojom::FuzzStructPtr(nullptr));
+ fuzz->FuzzArgs(fuzz::mojom::FuzzStruct::New(), GetPopulatedFuzzStruct());
+ fuzz->FuzzArgsResp(fuzz::mojom::FuzzStruct::New(), GetPopulatedFuzzStruct(),
+ base::Bind(FuzzCallback));
+ fuzz->FuzzArgsResp(fuzz::mojom::FuzzStruct::New(), GetPopulatedFuzzStruct(),
+ base::Bind(FuzzCallback));
+ fuzz->FuzzArgsSyncResp(fuzz::mojom::FuzzStruct::New(),
+ GetPopulatedFuzzStruct(), base::Bind(FuzzCallback));
+ fuzz->FuzzArgsSyncResp(fuzz::mojom::FuzzStruct::New(),
+ GetPopulatedFuzzStruct(), base::Bind(FuzzCallback));
+ fuzz->FuzzAssociated(MakeRequest(&dummy));
+ dummy->Ping();
+}
+
+int main(int argc, char** argv) {
+ if (argc < 2) {
+ printf("Usage: %s [output_directory]\n", argv[0]);
+ exit(1);
+ }
+ std::string output_directory(argv[1]);
+
+ /* Dump the messages from a MessageLoop, and wait for it to finish. */
+ env->message_loop.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&DumpMessages, output_directory));
+ base::RunLoop().RunUntilIdle();
+
+ return 0;
+}
diff --git a/mojo/public/tools/fuzzers/mojo_parse_message_fuzzer.cc b/mojo/public/tools/fuzzers/mojo_parse_message_fuzzer.cc
new file mode 100644
index 0000000000..ba0f15015f
--- /dev/null
+++ b/mojo/public/tools/fuzzers/mojo_parse_message_fuzzer.cc
@@ -0,0 +1,61 @@
+// 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/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/task_scheduler/task_scheduler.h"
+#include "mojo/core/embedder/embedder.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/tools/fuzzers/fuzz_impl.h"
+
+void FuzzMessage(const uint8_t* data, size_t size, base::RunLoop* run) {
+ fuzz::mojom::FuzzInterfacePtr fuzz;
+ auto impl = std::make_unique<FuzzImpl>(MakeRequest(&fuzz));
+ auto router = impl->binding_.RouterForTesting();
+
+ /* Create a mojo message with the appropriate payload size. */
+ mojo::Message message(0, 0, size, 0, nullptr);
+ if (message.data_num_bytes() < size) {
+ message.payload_buffer()->Allocate(size - message.data_num_bytes());
+ }
+
+ /* Set the raw message data. */
+ memcpy(message.mutable_data(), data, size);
+
+ /* Run the message through header validation, payload validation, and
+ * dispatch to the impl. */
+ router->SimulateReceivingMessageForTesting(&message);
+
+ /* Allow the harness function to return now. */
+ run->Quit();
+}
+
+/* Environment for the fuzzer. Initializes the mojo EDK and sets up a
+ * TaskScheduler, because Mojo messages must be sent and processed from
+ * TaskRunners. */
+struct Environment {
+ Environment() : message_loop(base::MessageLoop::TYPE_UI) {
+ base::TaskScheduler::CreateAndStartWithDefaultParams(
+ "MojoParseMessageFuzzerProcess");
+ mojo::core::Init();
+ }
+
+ /* Message loop to send and handle messages on. */
+ base::MessageLoop message_loop;
+
+ /* Suppress mojo validation failure logs. */
+ mojo::internal::ScopedSuppressValidationErrorLoggingForTests log_suppression;
+};
+
+// Entry point for LibFuzzer.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ static Environment* env = new Environment();
+ /* Pass the data along to run on a MessageLoop, and wait for it to finish. */
+ base::RunLoop run;
+ env->message_loop.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&FuzzMessage, data, size, &run));
+ run.Run();
+
+ return 0;
+}
diff --git a/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/07775ad8fdb79599024caefbe7889501dfee9e06 b/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/07775ad8fdb79599024caefbe7889501dfee9e06
new file mode 100644
index 0000000000..f1932a62de
--- /dev/null
+++ b/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/07775ad8fdb79599024caefbe7889501dfee9e06
@@ -0,0 +1 @@
+messages: "\060\000\000\000\002\000\000\000\000\000\000\000\006\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\014\000\000\000\001\000\000\000\001\000\000\200\000\000\000\000"
diff --git a/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/2ce2f91669a46921ebf4e47679c86dd2bf5b1496 b/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/2ce2f91669a46921ebf4e47679c86dd2bf5b1496
new file mode 100644
index 0000000000..52bae3dad4
--- /dev/null
+++ b/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/2ce2f91669a46921ebf4e47679c86dd2bf5b1496
@@ -0,0 +1 @@
+messages: "\030\000\000\000\000\000\000\000\001\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000"
diff --git a/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/32a65dcd84debde03d51f8b8ace2cdcc87461d34 b/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/32a65dcd84debde03d51f8b8ace2cdcc87461d34
new file mode 100644
index 0000000000..9cbd5621d5
--- /dev/null
+++ b/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/32a65dcd84debde03d51f8b8ace2cdcc87461d34
@@ -0,0 +1 @@
+messages: "\030\000\000\000\000\000\000\000\000\000\000\000\003\000\000\000\000\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\040\001\000\000\000\000\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\150\000\000\000\000\000\000\000\210\000\000\000\000\000\000\000\250\000\000\000\000\000\000\000\250\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\200\000\000\000\000\000\000\000\001\377\001\000\000\377\000\001\000\000\377\377\000\000\001\000\000\000\000\000\377\377\377\377\000\000\000\000\001\000\000\000\000\000\200\077\000\000\000\000\000\000\000\000\000\000\360\077\110\000\000\000\000\000\000\000\120\000\000\000\000\000\000\000\130\000\000\000\000\000\000\000\230\000\000\000\000\000\000\000\020\001\000\000\000\000\000\000\240\001\000\000\000\000\000\000\110\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\140\003\000\000\000\000\000\000\014\000\000\000\004\000\000\000\146\165\172\172\000\000\000\000\014\000\000\000\004\000\000\000\146\165\172\172\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\050\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\014\000\000\000\004\000\000\000\146\165\172\172\000\000\000\000\011\000\000\000\001\000\000\000\172\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\050\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\014\000\000\000\004\000\000\000\146\165\172\172\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\002\000\000\000\020\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\015\000\000\000\005\000\000\000\146\165\172\172\061\000\000\000\015\000\000\000\005\000\000\000\146\165\172\172\062\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\014\000\000\000\001\000\000\000\001\000\000\000\000\000\000\000\030\000\000\000\001\000\000\000\020\000\000\000\020\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\050\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\014\000\000\000\004\000\000\000\146\165\172\172\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\030\000\000\000\001\000\000\000\020\000\000\000\022\000\000\000\010\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\014\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\011\000\000\000\001\000\000\000\172\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\001\000\000\000\020\000\000\000\000\000\000\000\001\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\150\000\000\000\000\000\000\000\210\000\000\000\000\000\000\000\250\000\000\000\000\000\000\000\250\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\014\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\011\000\000\000\001\000\000\000\172\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\150\000\000\000\000\000\000\000\210\000\000\000\000\000\000\000\250\000\000\000\000\000\000\000\250\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000"
diff --git a/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/7cbf9144ec3980eb121eedc679ebc56a3ddd22a6 b/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/7cbf9144ec3980eb121eedc679ebc56a3ddd22a6
new file mode 100644
index 0000000000..fb4a52b198
--- /dev/null
+++ b/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/7cbf9144ec3980eb121eedc679ebc56a3ddd22a6
@@ -0,0 +1 @@
+messages: "\040\000\000\000\001\000\000\000\000\000\000\000\005\000\000\000\001\000\000\000\000\000\000\000\006\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\040\001\000\000\000\000\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\150\000\000\000\000\000\000\000\210\000\000\000\000\000\000\000\250\000\000\000\000\000\000\000\250\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\200\000\000\000\000\000\000\000\001\377\001\000\000\377\000\001\000\000\377\377\000\000\001\000\000\000\000\000\377\377\377\377\000\000\000\000\001\000\000\000\000\000\200\077\000\000\000\000\000\000\000\000\000\000\360\077\110\000\000\000\000\000\000\000\120\000\000\000\000\000\000\000\130\000\000\000\000\000\000\000\230\000\000\000\000\000\000\000\020\001\000\000\000\000\000\000\240\001\000\000\000\000\000\000\110\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\140\003\000\000\000\000\000\000\014\000\000\000\004\000\000\000\146\165\172\172\000\000\000\000\014\000\000\000\004\000\000\000\146\165\172\172\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\050\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\014\000\000\000\004\000\000\000\146\165\172\172\000\000\000\000\011\000\000\000\001\000\000\000\172\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\050\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\014\000\000\000\004\000\000\000\146\165\172\172\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\002\000\000\000\020\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\015\000\000\000\005\000\000\000\146\165\172\172\061\000\000\000\015\000\000\000\005\000\000\000\146\165\172\172\062\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\014\000\000\000\001\000\000\000\001\000\000\000\000\000\000\000\030\000\000\000\001\000\000\000\020\000\000\000\020\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\050\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\014\000\000\000\004\000\000\000\146\165\172\172\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\030\000\000\000\001\000\000\000\020\000\000\000\022\000\000\000\010\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\014\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\011\000\000\000\001\000\000\000\172\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\001\000\000\000\020\000\000\000\000\000\000\000\001\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\150\000\000\000\000\000\000\000\210\000\000\000\000\000\000\000\250\000\000\000\000\000\000\000\250\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\014\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\011\000\000\000\001\000\000\000\172\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\020\000\000\000\001\000\000\000\010\000\000\000\000\000\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\150\000\000\000\000\000\000\000\210\000\000\000\000\000\000\000\250\000\000\000\000\000\000\000\250\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000"
diff --git a/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/9ccc6b5c0a61672816dc252194c3d722c18107bc b/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/9ccc6b5c0a61672816dc252194c3d722c18107bc
new file mode 100644
index 0000000000..d5fa611978
--- /dev/null
+++ b/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/9ccc6b5c0a61672816dc252194c3d722c18107bc
@@ -0,0 +1 @@
+messages: "\040\000\000\000\001\000\000\000\000\000\000\000\002\000\000\000\005\000\000\000\000\000\000\000\002\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000"
diff --git a/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/9e0a62bdd4b08cb777bee9449a22b3ad6702b106 b/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/9e0a62bdd4b08cb777bee9449a22b3ad6702b106
new file mode 100644
index 0000000000..c5567f2f49
--- /dev/null
+++ b/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/9e0a62bdd4b08cb777bee9449a22b3ad6702b106
@@ -0,0 +1 @@
+messages: "\030\000\000\000\000\000\000\000\000\000\000\000\003\000\000\000\000\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\110\000\000\000\000\000\000\000\150\000\000\000\000\000\000\000\210\000\000\000\000\000\000\000\250\000\000\000\000\000\000\000\250\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000"
diff --git a/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/a74241101f97704b96c9ba11b4781651e236ad8f b/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/a74241101f97704b96c9ba11b4781651e236ad8f
new file mode 100644
index 0000000000..619a2bbe14
--- /dev/null
+++ b/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/a74241101f97704b96c9ba11b4781651e236ad8f
@@ -0,0 +1 @@
+messages: "\030\000\000\000\000\000\000\000\377\377\377\377\376\377\377\377\000\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\030\000\000\000\000\000\000\000\001\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000"
diff --git a/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/be66c5d078fbf574388b7b1d25a29ff2d16df67e b/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/be66c5d078fbf574388b7b1d25a29ff2d16df67e
new file mode 100644
index 0000000000..dcd7e6e8c1
--- /dev/null
+++ b/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/be66c5d078fbf574388b7b1d25a29ff2d16df67e
@@ -0,0 +1 @@
+messages: "\040\000\000\000\001\000\000\000\000\000\000\000\001\000\000\000\001\000\000\000\000\000\000\000\001\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000"
diff --git a/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/e4be6bde72d04c5cda7d4939a80e5890c5c01374 b/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/e4be6bde72d04c5cda7d4939a80e5890c5c01374
new file mode 100644
index 0000000000..186f82d867
--- /dev/null
+++ b/mojo/public/tools/fuzzers/mojo_parse_message_proto_corpus/e4be6bde72d04c5cda7d4939a80e5890c5c01374
@@ -0,0 +1 @@
+messages: "\030\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000"
diff --git a/mojo/public/tools/fuzzers/mojo_parse_message_proto_fuzzer.cc b/mojo/public/tools/fuzzers/mojo_parse_message_proto_fuzzer.cc
new file mode 100644
index 0000000000..69c70bea32
--- /dev/null
+++ b/mojo/public/tools/fuzzers/mojo_parse_message_proto_fuzzer.cc
@@ -0,0 +1,70 @@
+// 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.
+
+// Implementation of a proto version of mojo_parse_message_fuzzer that sends
+// multiple messages per run.
+
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/task_scheduler/task_scheduler.h"
+#include "mojo/core/embedder/embedder.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/tools/fuzzers/fuzz_impl.h"
+#include "mojo/public/tools/fuzzers/mojo_fuzzer.pb.h"
+#include "testing/libfuzzer/proto/lpm_interface.h"
+
+namespace mojo_proto_fuzzer {
+
+void FuzzMessage(const MojoFuzzerMessages& mojo_fuzzer_messages,
+ base::RunLoop* run) {
+ fuzz::mojom::FuzzInterfacePtr fuzz;
+ auto impl = std::make_unique<FuzzImpl>(MakeRequest(&fuzz));
+ auto router = impl->binding_.RouterForTesting();
+
+ for (auto& message_str : mojo_fuzzer_messages.messages()) {
+ // Create a mojo message with the appropriate payload size.
+ mojo::Message message(0, 0, message_str.size(), 0, nullptr);
+ if (message.data_num_bytes() < message_str.size()) {
+ message.payload_buffer()->Allocate(message_str.size() -
+ message.data_num_bytes());
+ }
+
+ // Set the raw message data.
+ memcpy(message.mutable_data(), message_str.data(), message_str.size());
+
+ // Run the message through header validation, payload validation, and
+ // dispatch to the impl.
+ router->SimulateReceivingMessageForTesting(&message);
+ }
+
+ // Allow the harness function to return now.
+ run->Quit();
+}
+
+// Environment for the fuzzer. Initializes the mojo EDK and sets up a
+// TaskScheduler, because Mojo messages must be sent and processed from
+// TaskRunners.
+struct Environment {
+ Environment() : message_loop(base::MessageLoop::TYPE_UI) {
+ base::TaskScheduler::CreateAndStartWithDefaultParams(
+ "MojoParseMessageFuzzerProcess");
+ mojo::core::Init();
+ }
+
+ // Message loop to send and handle messages on.
+ base::MessageLoop message_loop;
+
+ // Suppress mojo validation failure logs.
+ mojo::internal::ScopedSuppressValidationErrorLoggingForTests log_suppression;
+};
+
+DEFINE_PROTO_FUZZER(const MojoFuzzerMessages& mojo_fuzzer_messages) {
+ static Environment* env = new Environment();
+ // Pass the data along to run on a MessageLoop, and wait for it to finish.
+ base::RunLoop run;
+ env->message_loop.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&FuzzMessage, mojo_fuzzer_messages, &run));
+ run.Run();
+}
+} // namespace mojo_proto_fuzzer
diff --git a/soong/bindings_generator.go b/soong/bindings_generator.go
index 3d0e7f5da0..93ddc41d0d 100644
--- a/soong/bindings_generator.go
+++ b/soong/bindings_generator.go
@@ -85,8 +85,30 @@ func (m *mojomPickles) DepsMutator(ctx android.BottomUpMutatorContext) {
func (m *mojomPickles) GenerateAndroidBuildActions(ctx android.ModuleContext) {
m.outDir = android.PathForModuleGen(ctx, "")
- // TODO(lhchavez): Actually make this generate files once libchrome
- // roll progresses.
+
+ packagePath := android.PathForModuleSrc(ctx, "")
+
+ for _, in := range ctx.ExpandSources(m.properties.Srcs, nil) {
+ if !strings.HasSuffix(in.Rel(), ".mojom") {
+ ctx.PropertyErrorf("srcs", "Source is not a .mojom file: %s", in.Rel())
+ continue
+ }
+ relStem := strings.TrimSuffix(in.Rel(), ".mojom")
+
+ out := android.PathForModuleGen(ctx, relStem+".p")
+ m.generatedSrcs = append(m.generatedSrcs, out)
+
+ ctx.ModuleBuild(pctx, android.ModuleBuildParams{
+ Rule: generateMojomPicklesRule,
+ Input: in,
+ Output: out,
+ Args: map[string]string{
+ "package": packagePath.Rel(),
+ "outDir": m.outDir.String(),
+ "flags": fmt.Sprintf("-I=%s:%s", packagePath, packagePath),
+ },
+ })
+ }
}
func (m *mojomPickles) GeneratedHeaderDirs() android.Paths {
diff --git a/third_party/catapult/LICENSE b/third_party/catapult/LICENSE
deleted file mode 100644
index c992fe483c..0000000000
--- a/third_party/catapult/LICENSE
+++ /dev/null
@@ -1,27 +0,0 @@
-Copyright 2015 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 catapult 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 HOLDER 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/third_party/catapult/devil/PRESUBMIT.py b/third_party/catapult/devil/PRESUBMIT.py
deleted file mode 100644
index 289a5c65d0..0000000000
--- a/third_party/catapult/devil/PRESUBMIT.py
+++ /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.
-
-"""Presubmit script for devil.
-
-See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for
-details on the presubmit API built into depot_tools.
-"""
-
-
-def _RunPylint(input_api, output_api):
- return input_api.RunTests(input_api.canned_checks.RunPylint(
- input_api, output_api, pylintrc='pylintrc'))
-
-
-def _RunUnitTests(input_api, output_api):
- def J(*dirs):
- """Returns a path relative to presubmit directory."""
- return input_api.os_path.join(
- input_api.PresubmitLocalPath(), 'devil', *dirs)
-
- test_env = dict(input_api.environ)
- test_env.update({
- 'PYTHONDONTWRITEBYTECODE': '1',
- 'PYTHONPATH': ':'.join([J(), J('..')]),
- })
-
- message_type = (output_api.PresubmitError if input_api.is_committing
- else output_api.PresubmitPromptWarning)
-
- return input_api.RunTests([
- input_api.Command(
- name='devil/bin/run_py_tests',
- cmd=[
- input_api.os_path.join(
- input_api.PresubmitLocalPath(), 'bin', 'run_py_tests')],
- kwargs={'env': test_env},
- message=message_type)])
-
-
-def _EnsureNoPylibUse(input_api, output_api):
- def other_python_files(f):
- this_presubmit_file = input_api.os_path.join(
- input_api.PresubmitLocalPath(), 'PRESUBMIT.py')
- return (f.LocalPath().endswith('.py')
- and not f.AbsoluteLocalPath() == this_presubmit_file)
-
- changed_files = input_api.AffectedSourceFiles(other_python_files)
- import_error_re = input_api.re.compile(
- r'(from pylib.* import)|(import pylib)')
-
- errors = []
- for f in changed_files:
- errors.extend(
- '%s:%d' % (f.LocalPath(), line_number)
- for line_number, line_text in f.ChangedContents()
- if import_error_re.search(line_text))
-
- if errors:
- return [output_api.PresubmitError(
- 'pylib modules should not be imported from devil modules.',
- items=errors)]
- return []
-
-
-def CommonChecks(input_api, output_api):
- output = []
- output += _RunPylint(input_api, output_api)
- output += _RunUnitTests(input_api, output_api)
- output += _EnsureNoPylibUse(input_api, output_api)
- return output
-
-
-def CheckChangeOnUpload(input_api, output_api):
- return CommonChecks(input_api, output_api)
-
-
-def CheckChangeOnCommit(input_api, output_api):
- return CommonChecks(input_api, output_api)
-
diff --git a/third_party/catapult/devil/README.md b/third_party/catapult/devil/README.md
deleted file mode 100644
index 852ac3782b..0000000000
--- a/third_party/catapult/devil/README.md
+++ /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.
--->
-## devil
-
-😈
-
-devil is a library used by the Chromium developers to interact with Android
-devices. It currently supports SDK level 16 and above.
-
-## Interfaces
-
-devil provides python APIs:
- - [`devil.android.adb_wrapper`](docs/adb_wrapper.md) provides a thin wrapper
- around the adb binary. Most functions and methods have direct analogues on
- the adb command-line.
- - [`devil.android.device_utils`](docs/device_utils.md) provides higher-level
- functionality built on top of `adb_wrapper`. **This is the primary
- mechanism through which chromium's scripts interact with devices.**
-
-## Utilities
-
-devil also provides command-line utilities:
- - [`devil/utils/markdown.py`](docs/markdown.md) generated markdown
- documentation for python modules.
-
-## Constraints and Caveats
-
-devil is used with python 2.7. Its compatibility with python 3 has not been
-tested, and neither achieving nor maintaining said compatibility is currently
-a priority.
-
-## Contributing
-
-Please see the [contributor's guide](https://github.com/catapult-project/catapult/blob/master/CONTRIBUTING.md).
-
diff --git a/third_party/catapult/devil/bin/generate_md_docs b/third_party/catapult/devil/bin/generate_md_docs
deleted file mode 100755
index 634e14a54f..0000000000
--- a/third_party/catapult/devil/bin/generate_md_docs
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import os
-import sys
-
-_DEVIL_PATH = os.path.abspath(os.path.join(
- os.path.dirname(__file__), '..'))
-_DEVIL_URL = (
- 'https://github.com/catapult-project/catapult/blob/master/devil/')
-
-sys.path.append(_DEVIL_PATH)
-from devil.utils import cmd_helper
-
-_FILES_TO_DOC = {
- 'devil/android/sdk/adb_wrapper.py': 'docs/adb_wrapper.md',
- 'devil/android/device_utils.py': 'docs/device_utils.md',
- 'devil/utils/markdown.py': 'docs/markdown.md',
-}
-
-_MARKDOWN_SCRIPT = os.path.join(_DEVIL_PATH, 'devil', 'utils', 'markdown.py')
-
-def main():
- failed = False
- for k, v in _FILES_TO_DOC.iteritems():
- module_path = os.path.join(_DEVIL_PATH, k)
- module_link = _DEVIL_URL + k
- doc_path = os.path.join(_DEVIL_PATH, v)
-
- status, stdout = cmd_helper.GetCmdStatusAndOutput(
- [sys.executable, _MARKDOWN_SCRIPT, module_path,
- '--module-link', module_link])
- if status:
- logging.error('Failed to update doc for %s' % module_path)
- failed = True
- else:
- with open(doc_path, 'w') as doc_file:
- doc_file.write(stdout)
-
- return 1 if failed else 0
-
-if __name__ == '__main__':
- sys.exit(main())
diff --git a/third_party/catapult/devil/bin/run_py_devicetests b/third_party/catapult/devil/bin/run_py_devicetests
deleted file mode 100755
index 656bedf26a..0000000000
--- a/third_party/catapult/devil/bin/run_py_devicetests
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/usr/bin/env python
-# 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 os
-import sys
-
-_CATAPULT_PATH = os.path.abspath(os.path.join(
- os.path.dirname(__file__), '..', '..'))
-_DEVIL_PATH = os.path.abspath(os.path.join(
- os.path.dirname(__file__), '..'))
-_TYP_PATH = os.path.abspath(os.path.join(_CATAPULT_PATH, 'third_party', 'typ'))
-
-sys.path.append(_TYP_PATH)
-import typ
-
-sys.path.append(_DEVIL_PATH)
-from devil.android import device_test_case
-
-
-def main():
- runner = typ.Runner()
- runner.setup_fn = device_test_case.PrepareDevices
- return runner.main(
- coverage_source=[_DEVIL_PATH],
- jobs=1,
- suffixes=['*_devicetest.py'],
- top_level_dir=_DEVIL_PATH)
-
-if __name__ == '__main__':
- sys.exit(main())
diff --git a/third_party/catapult/devil/bin/run_py_tests b/third_party/catapult/devil/bin/run_py_tests
deleted file mode 100755
index 44ec61e89f..0000000000
--- a/third_party/catapult/devil/bin/run_py_tests
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env python
-# 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 os
-import sys
-
-_CATAPULT_PATH = os.path.abspath(os.path.join(
- os.path.dirname(__file__), '..', '..'))
-_DEVIL_PATH = os.path.abspath(os.path.join(
- os.path.dirname(__file__), '..'))
-
-sys.path.append(_CATAPULT_PATH)
-from catapult_build import run_with_typ
-
-
-def main():
- return run_with_typ.Run(top_level_dir=_DEVIL_PATH)
-
-if __name__ == '__main__':
- sys.exit(main())
diff --git a/third_party/catapult/devil/devil/__init__.py b/third_party/catapult/devil/devil/__init__.py
deleted file mode 100644
index 7de59c941c..0000000000
--- a/third_party/catapult/devil/devil/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import logging
-
-logging.getLogger('devil').addHandler(logging.NullHandler())
diff --git a/third_party/catapult/devil/devil/android/__init__.py b/third_party/catapult/devil/devil/android/__init__.py
deleted file mode 100644
index 50b23dff63..0000000000
--- a/third_party/catapult/devil/devil/android/__init__.py
+++ /dev/null
@@ -1,3 +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.
diff --git a/third_party/catapult/devil/devil/android/apk_helper.py b/third_party/catapult/devil/devil/android/apk_helper.py
deleted file mode 100644
index 1a9b8c5510..0000000000
--- a/third_party/catapult/devil/devil/android/apk_helper.py
+++ /dev/null
@@ -1,164 +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.
-
-"""Module containing utilities for apk packages."""
-
-import itertools
-import re
-
-from devil import base_error
-from devil.android.sdk import aapt
-
-
-_MANIFEST_ATTRIBUTE_RE = re.compile(
- r'\s*A: ([^\(\)= ]*)(?:\([^\(\)= ]*\))?='
- r'(?:"(.*)" \(Raw: .*\)|\(type.*?\)(.*))$')
-_MANIFEST_ELEMENT_RE = re.compile(r'\s*(?:E|N): (\S*) .*$')
-
-
-def GetPackageName(apk_path):
- """Returns the package name of the apk."""
- return ApkHelper(apk_path).GetPackageName()
-
-
-# TODO(jbudorick): Deprecate and remove this function once callers have been
-# converted to ApkHelper.GetInstrumentationName
-def GetInstrumentationName(apk_path):
- """Returns the name of the Instrumentation in the apk."""
- return ApkHelper(apk_path).GetInstrumentationName()
-
-
-def ToHelper(path_or_helper):
- """Creates an ApkHelper unless one is already given."""
- if isinstance(path_or_helper, basestring):
- return ApkHelper(path_or_helper)
- return path_or_helper
-
-
-def _ParseManifestFromApk(apk_path):
- aapt_output = aapt.Dump('xmltree', apk_path, 'AndroidManifest.xml')
-
- parsed_manifest = {}
- node_stack = [parsed_manifest]
- indent = ' '
-
- for line in aapt_output[1:]:
- if len(line) == 0:
- continue
-
- indent_depth = 0
- while line[(len(indent) * indent_depth):].startswith(indent):
- indent_depth += 1
-
- node_stack = node_stack[:indent_depth]
- node = node_stack[-1]
-
- m = _MANIFEST_ELEMENT_RE.match(line[len(indent) * indent_depth:])
- if m:
- manifest_key = m.group(1)
- if manifest_key in node:
- node[manifest_key] += [{}]
- else:
- node[manifest_key] = [{}]
- node_stack += [node[manifest_key][-1]]
- continue
-
- m = _MANIFEST_ATTRIBUTE_RE.match(line[len(indent) * indent_depth:])
- if m:
- manifest_key = m.group(1)
- if manifest_key in node:
- raise base_error.BaseError(
- "A single attribute should have one key and one value")
- else:
- node[manifest_key] = m.group(2) or m.group(3)
- continue
-
- return parsed_manifest
-
-
-class ApkHelper(object):
-
- def __init__(self, path):
- self._apk_path = path
- self._manifest = None
-
- @property
- def path(self):
- return self._apk_path
-
- def GetActivityName(self):
- """Returns the name of the Activity in the apk."""
- manifest_info = self._GetManifest()
- try:
- activity = (
- manifest_info['manifest'][0]['application'][0]['activity'][0]
- ['android:name'])
- except KeyError:
- return None
- if '.' not in activity:
- activity = '%s.%s' % (self.GetPackageName(), activity)
- elif activity.startswith('.'):
- activity = '%s%s' % (self.GetPackageName(), activity)
- return activity
-
- def GetInstrumentationName(
- self, default='android.test.InstrumentationTestRunner'):
- """Returns the name of the Instrumentation in the apk."""
- all_instrumentations = self.GetAllInstrumentations(default=default)
- if len(all_instrumentations) != 1:
- raise base_error.BaseError(
- 'There is more than one instrumentation. Expected one.')
- else:
- return all_instrumentations[0]['android:name']
-
- def GetAllInstrumentations(
- self, default='android.test.InstrumentationTestRunner'):
- """Returns a list of all Instrumentations in the apk."""
- try:
- return self._GetManifest()['manifest'][0]['instrumentation']
- except KeyError:
- return [{'android:name': default}]
-
- def GetPackageName(self):
- """Returns the package name of the apk."""
- manifest_info = self._GetManifest()
- try:
- return manifest_info['manifest'][0]['package']
- except KeyError:
- raise Exception('Failed to determine package name of %s' % self._apk_path)
-
- def GetPermissions(self):
- manifest_info = self._GetManifest()
- try:
- return [p['android:name'] for
- p in manifest_info['manifest'][0]['uses-permission']]
- except KeyError:
- return []
-
- def GetSplitName(self):
- """Returns the name of the split of the apk."""
- manifest_info = self._GetManifest()
- try:
- return manifest_info['manifest'][0]['split']
- except KeyError:
- return None
-
- def HasIsolatedProcesses(self):
- """Returns whether any services exist that use isolatedProcess=true."""
- manifest_info = self._GetManifest()
- try:
- applications = manifest_info['manifest'][0].get('application', [])
- services = itertools.chain(
- *(application.get('service', []) for application in applications))
- return any(
- int(s.get('android:isolatedProcess', '0'), 0)
- for s in services)
- except KeyError:
- return False
-
- def _GetManifest(self):
- if not self._manifest:
- self._manifest = _ParseManifestFromApk(self._apk_path)
- return self._manifest
-
diff --git a/third_party/catapult/devil/devil/android/apk_helper_test.py b/third_party/catapult/devil/devil/android/apk_helper_test.py
deleted file mode 100755
index f7d077dd62..0000000000
--- a/third_party/catapult/devil/devil/android/apk_helper_test.py
+++ /dev/null
@@ -1,169 +0,0 @@
-#! /usr/bin/env python
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-from devil import base_error
-from devil import devil_env
-from devil.android import apk_helper
-from devil.utils import mock_calls
-
-with devil_env.SysPath(devil_env.PYMOCK_PATH):
- import mock # pylint: disable=import-error
-
-
-_MANIFEST_DUMP = """N: android=http://schemas.android.com/apk/res/android
- E: manifest (line=1)
- A: package="org.chromium.abc" (Raw: "org.chromium.abc")
- A: split="random_split" (Raw: "random_split")
- E: uses-permission (line=2)
- A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
- E: uses-permission (line=3)
- A: android:name(0x01010003)="android.permission.READ_EXTERNAL_STORAGE" (Raw: "android.permission.READ_EXTERNAL_STORAGE")
- E: uses-permission (line=4)
- A: android:name(0x01010003)="android.permission.ACCESS_FINE_LOCATION" (Raw: "android.permission.ACCESS_FINE_LOCATION")
- E: application (line=5)
- E: activity (line=6)
- A: android:name(0x01010003)="org.chromium.ActivityName" (Raw: "org.chromium.ActivityName")
- A: android:exported(0x01010010)=(type 0x12)0xffffffff
- E: service (line=7)
- A: android:name(0x01010001)="org.chromium.RandomService" (Raw: "org.chromium.RandomService")
- A: android:isolatedProcess(0x01010888)=(type 0x12)0xffffffff
- E: instrumentation (line=8)
- A: android:label(0x01010001)="abc" (Raw: "abc")
- A: android:name(0x01010003)="org.chromium.RandomJUnit4TestRunner" (Raw: "org.chromium.RandomJUnit4TestRunner")
- A: android:targetPackage(0x01010021)="org.chromium.random_package" (Raw:"org.chromium.random_pacakge")
- A: junit4=(type 0x12)0xffffffff (Raw: "true")
- E: instrumentation (line=9)
- A: android:label(0x01010001)="abc" (Raw: "abc")
- A: android:name(0x01010003)="org.chromium.RandomTestRunner" (Raw: "org.chromium.RandomTestRunner")
- A: android:targetPackage(0x01010021)="org.chromium.random_package" (Raw:"org.chromium.random_pacakge")
-"""
-
-_NO_ISOLATED_SERVICES = """N: android=http://schemas.android.com/apk/res/android
- E: manifest (line=1)
- A: package="org.chromium.abc" (Raw: "org.chromium.abc")
- E: application (line=5)
- E: activity (line=6)
- A: android:name(0x01010003)="org.chromium.ActivityName" (Raw: "org.chromium.ActivityName")
- A: android:exported(0x01010010)=(type 0x12)0xffffffff
- E: service (line=7)
- A: android:name(0x01010001)="org.chromium.RandomService" (Raw: "org.chromium.RandomService")
-"""
-
-_NO_SERVICES = """N: android=http://schemas.android.com/apk/res/android
- E: manifest (line=1)
- A: package="org.chromium.abc" (Raw: "org.chromium.abc")
- E: application (line=5)
- E: activity (line=6)
- A: android:name(0x01010003)="org.chromium.ActivityName" (Raw: "org.chromium.ActivityName")
- A: android:exported(0x01010010)=(type 0x12)0xffffffff
-"""
-
-_NO_APPLICATION = """N: android=http://schemas.android.com/apk/res/android
- E: manifest (line=1)
- A: package="org.chromium.abc" (Raw: "org.chromium.abc")
-"""
-
-_SINGLE_INSTRUMENTATION_MANIFEST_DUMP = """N: android=http://schemas.android.com/apk/res/android
- E: manifest (line=1)
- A: package="org.chromium.xyz" (Raw: "org.chromium.xyz")
- E: instrumentation (line=8)
- A: android:label(0x01010001)="xyz" (Raw: "xyz")
- A: android:name(0x01010003)="org.chromium.RandomTestRunner" (Raw: "org.chromium.RandomTestRunner")
- A: android:targetPackage(0x01010021)="org.chromium.random_package" (Raw:"org.chromium.random_pacakge")
-"""
-
-_SINGLE_J4_INSTRUMENTATION_MANIFEST_DUMP = """N: android=http://schemas.android.com/apk/res/android
- E: manifest (line=1)
- A: package="org.chromium.xyz" (Raw: "org.chromium.xyz")
- E: instrumentation (line=8)
- A: android:label(0x01010001)="xyz" (Raw: "xyz")
- A: android:name(0x01010003)="org.chromium.RandomJ4TestRunner" (Raw: "org.chromium.RandomJ4TestRunner")
- A: android:targetPackage(0x01010021)="org.chromium.random_package" (Raw:"org.chromium.random_pacakge")
- A: junit4=(type 0x12)0xffffffff (Raw: "true")
-"""
-
-
-def _MockAaptDump(manifest_dump):
- return mock.patch(
- 'devil.android.sdk.aapt.Dump',
- mock.Mock(side_effect=None, return_value=manifest_dump.split('\n')))
-
-class ApkHelperTest(mock_calls.TestCase):
-
- def testGetInstrumentationName(self):
- with _MockAaptDump(_MANIFEST_DUMP):
- helper = apk_helper.ApkHelper("")
- with self.assertRaises(base_error.BaseError):
- helper.GetInstrumentationName()
-
- def testGetActivityName(self):
- with _MockAaptDump(_MANIFEST_DUMP):
- helper = apk_helper.ApkHelper("")
- self.assertEquals(
- helper.GetActivityName(), 'org.chromium.ActivityName')
-
- def testGetAllInstrumentations(self):
- with _MockAaptDump(_MANIFEST_DUMP):
- helper = apk_helper.ApkHelper("")
- all_instrumentations = helper.GetAllInstrumentations()
- self.assertEquals(len(all_instrumentations), 2)
- self.assertEquals(all_instrumentations[0]['android:name'],
- 'org.chromium.RandomJUnit4TestRunner')
- self.assertEquals(all_instrumentations[1]['android:name'],
- 'org.chromium.RandomTestRunner')
-
- def testGetPackageName(self):
- with _MockAaptDump(_MANIFEST_DUMP):
- helper = apk_helper.ApkHelper("")
- self.assertEquals(helper.GetPackageName(), 'org.chromium.abc')
-
- def testGetPermssions(self):
- with _MockAaptDump(_MANIFEST_DUMP):
- helper = apk_helper.ApkHelper("")
- all_permissions = helper.GetPermissions()
- self.assertEquals(len(all_permissions), 3)
- self.assertTrue('android.permission.INTERNET' in all_permissions)
- self.assertTrue(
- 'android.permission.READ_EXTERNAL_STORAGE' in all_permissions)
- self.assertTrue(
- 'android.permission.ACCESS_FINE_LOCATION' in all_permissions)
-
- def testGetSplitName(self):
- with _MockAaptDump(_MANIFEST_DUMP):
- helper = apk_helper.ApkHelper("")
- self.assertEquals(helper.GetSplitName(), 'random_split')
-
- def testHasIsolatedProcesses_noApplication(self):
- with _MockAaptDump(_NO_APPLICATION):
- helper = apk_helper.ApkHelper("")
- self.assertFalse(helper.HasIsolatedProcesses())
-
- def testHasIsolatedProcesses_noServices(self):
- with _MockAaptDump(_NO_SERVICES):
- helper = apk_helper.ApkHelper("")
- self.assertFalse(helper.HasIsolatedProcesses())
-
- def testHasIsolatedProcesses_oneNotIsolatedProcess(self):
- with _MockAaptDump(_NO_ISOLATED_SERVICES):
- helper = apk_helper.ApkHelper("")
- self.assertFalse(helper.HasIsolatedProcesses())
-
- def testHasIsolatedProcesses_oneIsolatedProcess(self):
- with _MockAaptDump(_MANIFEST_DUMP):
- helper = apk_helper.ApkHelper("")
- self.assertTrue(helper.HasIsolatedProcesses())
-
- def testGetSingleInstrumentationName(self):
- with _MockAaptDump(_SINGLE_INSTRUMENTATION_MANIFEST_DUMP):
- helper = apk_helper.ApkHelper("")
- self.assertEquals('org.chromium.RandomTestRunner',
- helper.GetInstrumentationName())
-
- def testGetSingleJUnit4InstrumentationName(self):
- with _MockAaptDump(_SINGLE_J4_INSTRUMENTATION_MANIFEST_DUMP):
- helper = apk_helper.ApkHelper("")
- self.assertEquals('org.chromium.RandomJ4TestRunner',
- helper.GetInstrumentationName())
-
diff --git a/third_party/catapult/devil/devil/android/app_ui.py b/third_party/catapult/devil/devil/android/app_ui.py
deleted file mode 100644
index 2b04e8b800..0000000000
--- a/third_party/catapult/devil/devil/android/app_ui.py
+++ /dev/null
@@ -1,243 +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.
-
-"""Provides functionality to interact with UI elements of an Android app."""
-
-import collections
-import re
-from xml.etree import ElementTree as element_tree
-
-from devil.android import decorators
-from devil.android import device_temp_file
-from devil.utils import geometry
-from devil.utils import timeout_retry
-
-_DEFAULT_SHORT_TIMEOUT = 10
-_DEFAULT_SHORT_RETRIES = 3
-_DEFAULT_LONG_TIMEOUT = 30
-_DEFAULT_LONG_RETRIES = 0
-
-# Parse rectangle bounds given as: '[left,top][right,bottom]'.
-_RE_BOUNDS = re.compile(
- r'\[(?P<left>\d+),(?P<top>\d+)\]\[(?P<right>\d+),(?P<bottom>\d+)\]')
-
-
-class _UiNode(object):
-
- def __init__(self, device, xml_node, package=None):
- """Object to interact with a UI node from an xml snapshot.
-
- Note: there is usually no need to call this constructor directly. Instead,
- use an AppUi object (below) to grab an xml screenshot from a device and
- find nodes in it.
-
- Args:
- device: A device_utils.DeviceUtils instance.
- xml_node: An ElementTree instance of the node to interact with.
- package: An optional package name for the app owning this node.
- """
- self._device = device
- self._xml_node = xml_node
- self._package = package
-
- def _GetAttribute(self, key):
- """Get the value of an attribute of this node."""
- return self._xml_node.attrib.get(key)
-
- @property
- def bounds(self):
- """Get a rectangle with the bounds of this UI node.
-
- Returns:
- A geometry.Rectangle instance.
- """
- d = _RE_BOUNDS.match(self._GetAttribute('bounds')).groupdict()
- return geometry.Rectangle.FromDict({k: int(v) for k, v in d.iteritems()})
-
- def Tap(self, point=None, dp_units=False):
- """Send a tap event to the UI node.
-
- Args:
- point: An optional geometry.Point instance indicating the location to
- tap, relative to the bounds of the UI node, i.e. (0, 0) taps the
- top-left corner. If ommited, the center of the node is tapped.
- dp_units: If True, indicates that the coordinates of the point are given
- in device-independent pixels; otherwise they are assumed to be "real"
- pixels. This option has no effect when the point is ommited.
- """
- if point is None:
- point = self.bounds.center
- else:
- if dp_units:
- point = (float(self._device.pixel_density) / 160) * point
- point += self.bounds.top_left
-
- x, y = (str(int(v)) for v in point)
- self._device.RunShellCommand(['input', 'tap', x, y], check_return=True)
-
- def Dump(self):
- """Get a brief summary of the child nodes that can be found on this node.
-
- Returns:
- A list of lines that can be logged or otherwise printed.
- """
- summary = collections.defaultdict(set)
- for node in self._xml_node.iter():
- package = node.get('package') or '(no package)'
- label = node.get('resource-id') or '(no id)'
- text = node.get('text')
- if text:
- label = '%s[%r]' % (label, text)
- summary[package].add(label)
- lines = []
- for package, labels in sorted(summary.iteritems()):
- lines.append('- %s:' % package)
- for label in sorted(labels):
- lines.append(' - %s' % label)
- return lines
-
- def __getitem__(self, key):
- """Retrieve a child of this node by its index.
-
- Args:
- key: An integer with the index of the child to retrieve.
- Returns:
- A UI node instance of the selected child.
- Raises:
- IndexError if the index is out of range.
- """
- return type(self)(self._device, self._xml_node[key], package=self._package)
-
- def _Find(self, **kwargs):
- """Find the first descendant node that matches a given criteria.
-
- Note: clients would usually call AppUi.GetUiNode or AppUi.WaitForUiNode
- instead.
-
- For example:
-
- app = app_ui.AppUi(device, package='org.my.app')
- app.GetUiNode(resource_id='some_element', text='hello')
-
- would retrieve the first matching node with both of the xml attributes:
-
- resource-id='org.my.app:id/some_element'
- text='hello'
-
- As the example shows, if given and needed, the value of the resource_id key
- is auto-completed with the package name specified in the AppUi constructor.
-
- Args:
- Arguments are specified as key-value pairs, where keys correnspond to
- attribute names in xml nodes (replacing any '-' with '_' to make them
- valid identifiers). At least one argument must be supplied, and arguments
- with a None value are ignored.
- Returns:
- A UI node instance of the first descendant node that matches ALL the
- given key-value criteria; or None if no such node is found.
- Raises:
- TypeError if no search arguments are provided.
- """
- matches_criteria = self._NodeMatcher(kwargs)
- for node in self._xml_node.iter():
- if matches_criteria(node):
- return type(self)(self._device, node, package=self._package)
- return None
-
- def _NodeMatcher(self, kwargs):
- # Auto-complete resource-id's using the package name if available.
- resource_id = kwargs.get('resource_id')
- if (resource_id is not None
- and self._package is not None
- and ':id/' not in resource_id):
- kwargs['resource_id'] = '%s:id/%s' % (self._package, resource_id)
-
- criteria = [(k.replace('_', '-'), v)
- for k, v in kwargs.iteritems()
- if v is not None]
- if not criteria:
- raise TypeError('At least one search criteria should be specified')
- return lambda node: all(node.get(k) == v for k, v in criteria)
-
-
-class AppUi(object):
- # timeout and retry arguments appear unused, but are handled by decorator.
- # pylint: disable=unused-argument
-
- def __init__(self, device, package=None):
- """Object to interact with the UI of an Android app.
-
- Args:
- device: A device_utils.DeviceUtils instance.
- package: An optional package name for the app.
- """
- self._device = device
- self._package = package
-
- @property
- def package(self):
- return self._package
-
- @decorators.WithTimeoutAndRetriesDefaults(_DEFAULT_SHORT_TIMEOUT,
- _DEFAULT_SHORT_RETRIES)
- def _GetRootUiNode(self, timeout=None, retries=None):
- """Get a node pointing to the root of the UI nodes on screen.
-
- Note: This is currently implemented via adb calls to uiatomator and it
- is *slow*, ~2 secs per call. Do not rely on low-level implementation
- details that may change in the future.
-
- TODO(crbug.com/567217): Swap to a more efficient implementation.
-
- Args:
- timeout: A number of seconds to wait for the uiautomator dump.
- retries: Number of times to retry if the adb command fails.
- Returns:
- A UI node instance pointing to the root of the xml screenshot.
- """
- with device_temp_file.DeviceTempFile(self._device.adb) as dtemp:
- self._device.RunShellCommand(['uiautomator', 'dump', dtemp.name],
- check_return=True)
- xml_node = element_tree.fromstring(
- self._device.ReadFile(dtemp.name, force_pull=True))
- return _UiNode(self._device, xml_node, package=self._package)
-
- def ScreenDump(self):
- """Get a brief summary of the nodes that can be found on the screen.
-
- Returns:
- A list of lines that can be logged or otherwise printed.
- """
- return self._GetRootUiNode().Dump()
-
- def GetUiNode(self, **kwargs):
- """Get the first node found matching a specified criteria.
-
- Args:
- See _UiNode._Find.
- Returns:
- A UI node instance of the node if found, otherwise None.
- """
- # pylint: disable=protected-access
- return self._GetRootUiNode()._Find(**kwargs)
-
- @decorators.WithTimeoutAndRetriesDefaults(_DEFAULT_LONG_TIMEOUT,
- _DEFAULT_LONG_RETRIES)
- def WaitForUiNode(self, timeout=None, retries=None, **kwargs):
- """Wait for a node matching a given criteria to appear on the screen.
-
- Args:
- timeout: A number of seconds to wait for the matching node to appear.
- retries: Number of times to retry in case of adb command errors.
- For other args, to specify the search criteria, see _UiNode._Find.
- Returns:
- The UI node instance found.
- Raises:
- device_errors.CommandTimeoutError if the node is not found before the
- timeout.
- """
- def node_found():
- return self.GetUiNode(**kwargs)
-
- return timeout_retry.WaitFor(node_found)
diff --git a/third_party/catapult/devil/devil/android/app_ui_test.py b/third_party/catapult/devil/devil/android/app_ui_test.py
deleted file mode 100644
index 3472985118..0000000000
--- a/third_party/catapult/devil/devil/android/app_ui_test.py
+++ /dev/null
@@ -1,191 +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.
-
-"""Unit tests for the app_ui module."""
-
-import unittest
-from xml.etree import ElementTree as element_tree
-
-from devil import devil_env
-from devil.android import app_ui
-from devil.android import device_errors
-from devil.utils import geometry
-
-with devil_env.SysPath(devil_env.PYMOCK_PATH):
- import mock # pylint: disable=import-error
-
-
-MOCK_XML_LOADING = '''
-<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
-<hierarchy rotation="0">
- <node bounds="[0,50][1536,178]" content-desc="Loading"
- resource-id="com.example.app:id/spinner"/>
-</hierarchy>
-'''.strip()
-
-
-MOCK_XML_LOADED = '''
-<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
-<hierarchy rotation="0">
- <node bounds="[0,50][1536,178]" content-desc=""
- resource-id="com.example.app:id/toolbar">
- <node bounds="[0,58][112,170]" content-desc="Open navigation drawer"/>
- <node bounds="[121,50][1536,178]"
- resource-id="com.example.app:id/actionbar_custom_view">
- <node bounds="[121,50][1424,178]"
- resource-id="com.example.app:id/actionbar_title" text="Primary"/>
- <node bounds="[1424,50][1536,178]" content-desc="Search"
- resource-id="com.example.app:id/actionbar_search_button"/>
- </node>
- </node>
- <node bounds="[0,178][576,1952]" resource-id="com.example.app:id/drawer">
- <node bounds="[0,178][144,1952]"
- resource-id="com.example.app:id/mini_drawer">
- <node bounds="[40,254][104,318]" resource-id="com.example.app:id/avatar"/>
- <node bounds="[16,354][128,466]" content-desc="Primary"
- resource-id="com.example.app:id/image_view"/>
- <node bounds="[16,466][128,578]" content-desc="Social"
- resource-id="com.example.app:id/image_view"/>
- <node bounds="[16,578][128,690]" content-desc="Promotions"
- resource-id="com.example.app:id/image_view"/>
- </node>
- </node>
-</hierarchy>
-'''.strip()
-
-
-class UiAppTest(unittest.TestCase):
-
- def setUp(self):
- self.device = mock.Mock()
- self.device.pixel_density = 320 # Each dp pixel is 2 real pixels.
- self.app = app_ui.AppUi(self.device, package='com.example.app')
- self._setMockXmlScreenshots([MOCK_XML_LOADED])
-
- def _setMockXmlScreenshots(self, xml_docs):
- """Mock self.app._GetRootUiNode to load nodes from some test xml_docs.
-
- Each time the method is called it will return a UI node for each string
- given in |xml_docs|, or rise a time out error when the list is exhausted.
- """
- # pylint: disable=protected-access
- def get_mock_root_ui_node(value):
- if isinstance(value, Exception):
- raise value
- return app_ui._UiNode(
- self.device, element_tree.fromstring(value), self.app.package)
-
- xml_docs.append(device_errors.CommandTimeoutError('Timed out!'))
-
- self.app._GetRootUiNode = mock.Mock(
- side_effect=(get_mock_root_ui_node(doc) for doc in xml_docs))
-
- def assertNodeHasAttribs(self, node, attr):
- # pylint: disable=protected-access
- for key, value in attr.iteritems():
- self.assertEquals(node._GetAttribute(key), value)
-
- def assertTappedOnceAt(self, x, y):
- self.device.RunShellCommand.assert_called_once_with(
- ['input', 'tap', str(x), str(y)], check_return=True)
-
- def testFind_byText(self):
- node = self.app.GetUiNode(text='Primary')
- self.assertNodeHasAttribs(node, {
- 'text': 'Primary',
- 'content-desc': None,
- 'resource-id': 'com.example.app:id/actionbar_title',
- })
- self.assertEquals(node.bounds, geometry.Rectangle([121, 50], [1424, 178]))
-
- def testFind_byContentDesc(self):
- node = self.app.GetUiNode(content_desc='Social')
- self.assertNodeHasAttribs(node, {
- 'text': None,
- 'content-desc': 'Social',
- 'resource-id': 'com.example.app:id/image_view',
- })
- self.assertEquals(node.bounds, geometry.Rectangle([16, 466], [128, 578]))
-
- def testFind_byResourceId_autocompleted(self):
- node = self.app.GetUiNode(resource_id='image_view')
- self.assertNodeHasAttribs(node, {
- 'content-desc': 'Primary',
- 'resource-id': 'com.example.app:id/image_view',
- })
-
- def testFind_byResourceId_absolute(self):
- node = self.app.GetUiNode(resource_id='com.example.app:id/image_view')
- self.assertNodeHasAttribs(node, {
- 'content-desc': 'Primary',
- 'resource-id': 'com.example.app:id/image_view',
- })
-
- def testFind_byMultiple(self):
- node = self.app.GetUiNode(resource_id='image_view',
- content_desc='Promotions')
- self.assertNodeHasAttribs(node, {
- 'content-desc': 'Promotions',
- 'resource-id': 'com.example.app:id/image_view',
- })
- self.assertEquals(node.bounds, geometry.Rectangle([16, 578], [128, 690]))
-
- def testFind_notFound(self):
- node = self.app.GetUiNode(resource_id='does_not_exist')
- self.assertIsNone(node)
-
- def testFind_noArgsGiven(self):
- # Same exception given by Python for a function call with not enough args.
- with self.assertRaises(TypeError):
- self.app.GetUiNode()
-
- def testGetChildren(self):
- node = self.app.GetUiNode(resource_id='mini_drawer')
- self.assertNodeHasAttribs(
- node[0], {'resource-id': 'com.example.app:id/avatar'})
- self.assertNodeHasAttribs(node[1], {'content-desc': 'Primary'})
- self.assertNodeHasAttribs(node[2], {'content-desc': 'Social'})
- self.assertNodeHasAttribs(node[3], {'content-desc': 'Promotions'})
- with self.assertRaises(IndexError):
- # pylint: disable=pointless-statement
- node[4]
-
- def testTap_center(self):
- node = self.app.GetUiNode(content_desc='Open navigation drawer')
- node.Tap()
- self.assertTappedOnceAt(56, 114)
-
- def testTap_topleft(self):
- node = self.app.GetUiNode(content_desc='Open navigation drawer')
- node.Tap(geometry.Point(0, 0))
- self.assertTappedOnceAt(0, 58)
-
- def testTap_withOffset(self):
- node = self.app.GetUiNode(content_desc='Open navigation drawer')
- node.Tap(geometry.Point(10, 20))
- self.assertTappedOnceAt(10, 78)
-
- def testTap_withOffsetInDp(self):
- node = self.app.GetUiNode(content_desc='Open navigation drawer')
- node.Tap(geometry.Point(10, 20), dp_units=True)
- self.assertTappedOnceAt(20, 98)
-
- def testTap_dpUnitsIgnored(self):
- node = self.app.GetUiNode(content_desc='Open navigation drawer')
- node.Tap(dp_units=True)
- self.assertTappedOnceAt(56, 114) # Still taps at center.
-
- @mock.patch('time.sleep', mock.Mock())
- def testWaitForUiNode_found(self):
- self._setMockXmlScreenshots(
- [MOCK_XML_LOADING, MOCK_XML_LOADING, MOCK_XML_LOADED])
- node = self.app.WaitForUiNode(resource_id='actionbar_title')
- self.assertNodeHasAttribs(node, {'text': 'Primary'})
-
- @mock.patch('time.sleep', mock.Mock())
- def testWaitForUiNode_notFound(self):
- self._setMockXmlScreenshots(
- [MOCK_XML_LOADING, MOCK_XML_LOADING, MOCK_XML_LOADING])
- with self.assertRaises(device_errors.CommandTimeoutError):
- self.app.WaitForUiNode(resource_id='actionbar_title')
diff --git a/third_party/catapult/devil/devil/android/battery_utils.py b/third_party/catapult/devil/devil/android/battery_utils.py
deleted file mode 100644
index 3b225aa106..0000000000
--- a/third_party/catapult/devil/devil/android/battery_utils.py
+++ /dev/null
@@ -1,699 +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.
-
-"""Provides a variety of device interactions with power.
-"""
-# pylint: disable=unused-argument
-
-import collections
-import contextlib
-import csv
-import logging
-
-from devil.android import decorators
-from devil.android import device_errors
-from devil.android import device_utils
-from devil.android.sdk import version_codes
-from devil.utils import timeout_retry
-
-logger = logging.getLogger(__name__)
-
-_DEFAULT_TIMEOUT = 30
-_DEFAULT_RETRIES = 3
-
-
-_DEVICE_PROFILES = [
- {
- 'name': ['Nexus 4'],
- 'enable_command': (
- 'echo 0 > /sys/module/pm8921_charger/parameters/disabled && '
- 'dumpsys battery reset'),
- 'disable_command': (
- 'echo 1 > /sys/module/pm8921_charger/parameters/disabled && '
- 'dumpsys battery set ac 0 && dumpsys battery set usb 0'),
- 'charge_counter': None,
- 'voltage': None,
- 'current': None,
- },
- {
- 'name': ['Nexus 5'],
- # Nexus 5
- # Setting the HIZ bit of the bq24192 causes the charger to actually ignore
- # energy coming from USB. Setting the power_supply offline just updates the
- # Android system to reflect that.
- 'enable_command': (
- 'echo 0x4A > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && '
- 'chmod 644 /sys/class/power_supply/usb/online && '
- 'echo 1 > /sys/class/power_supply/usb/online && '
- 'dumpsys battery reset'),
- 'disable_command': (
- 'echo 0xCA > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && '
- 'chmod 644 /sys/class/power_supply/usb/online && '
- 'echo 0 > /sys/class/power_supply/usb/online && '
- 'dumpsys battery set ac 0 && dumpsys battery set usb 0'),
- 'charge_counter': None,
- 'voltage': None,
- 'current': None,
- },
- {
- 'name': ['Nexus 6'],
- 'enable_command': (
- 'echo 1 > /sys/class/power_supply/battery/charging_enabled && '
- 'dumpsys battery reset'),
- 'disable_command': (
- 'echo 0 > /sys/class/power_supply/battery/charging_enabled && '
- 'dumpsys battery set ac 0 && dumpsys battery set usb 0'),
- 'charge_counter': (
- '/sys/class/power_supply/max170xx_battery/charge_counter_ext'),
- 'voltage': '/sys/class/power_supply/max170xx_battery/voltage_now',
- 'current': '/sys/class/power_supply/max170xx_battery/current_now',
- },
- {
- 'name': ['Nexus 9'],
- 'enable_command': (
- 'echo Disconnected > '
- '/sys/bus/i2c/drivers/bq2419x/0-006b/input_cable_state && '
- 'dumpsys battery reset'),
- 'disable_command': (
- 'echo Connected > '
- '/sys/bus/i2c/drivers/bq2419x/0-006b/input_cable_state && '
- 'dumpsys battery set ac 0 && dumpsys battery set usb 0'),
- 'charge_counter': '/sys/class/power_supply/battery/charge_counter_ext',
- 'voltage': '/sys/class/power_supply/battery/voltage_now',
- 'current': '/sys/class/power_supply/battery/current_now',
- },
- {
- 'name': ['Nexus 10'],
- 'enable_command': None,
- 'disable_command': None,
- 'charge_counter': None,
- 'voltage': '/sys/class/power_supply/ds2784-fuelgauge/voltage_now',
- 'current': '/sys/class/power_supply/ds2784-fuelgauge/current_now',
-
- },
- {
- 'name': ['Nexus 5X'],
- 'enable_command': (
- 'echo 1 > /sys/class/power_supply/battery/charging_enabled && '
- 'dumpsys battery reset'),
- 'disable_command': (
- 'echo 0 > /sys/class/power_supply/battery/charging_enabled && '
- 'dumpsys battery set ac 0 && dumpsys battery set usb 0'),
- 'charge_counter': None,
- 'voltage': None,
- 'current': None,
- },
- { # Galaxy s5
- 'name': ['SM-G900H'],
- 'enable_command': (
- 'chmod 644 /sys/class/power_supply/battery/test_mode && '
- 'chmod 644 /sys/class/power_supply/sec-charger/current_now && '
- 'echo 0 > /sys/class/power_supply/battery/test_mode && '
- 'echo 9999 > /sys/class/power_supply/sec-charger/current_now &&'
- 'dumpsys battery reset'),
- 'disable_command': (
- 'chmod 644 /sys/class/power_supply/battery/test_mode && '
- 'chmod 644 /sys/class/power_supply/sec-charger/current_now && '
- 'echo 1 > /sys/class/power_supply/battery/test_mode && '
- 'echo 0 > /sys/class/power_supply/sec-charger/current_now && '
- 'dumpsys battery set ac 0 && dumpsys battery set usb 0'),
- 'charge_counter': None,
- 'voltage': '/sys/class/power_supply/sec-fuelgauge/voltage_now',
- 'current': '/sys/class/power_supply/sec-charger/current_now',
- },
- { # Galaxy s6, Galaxy s6, Galaxy s6 edge
- 'name': ['SM-G920F', 'SM-G920V', 'SM-G925V'],
- 'enable_command': (
- 'chmod 644 /sys/class/power_supply/battery/test_mode && '
- 'chmod 644 /sys/class/power_supply/max77843-charger/current_now && '
- 'echo 0 > /sys/class/power_supply/battery/test_mode && '
- 'echo 9999 > /sys/class/power_supply/max77843-charger/current_now &&'
- 'dumpsys battery reset'),
- 'disable_command': (
- 'chmod 644 /sys/class/power_supply/battery/test_mode && '
- 'chmod 644 /sys/class/power_supply/max77843-charger/current_now && '
- 'echo 1 > /sys/class/power_supply/battery/test_mode && '
- 'echo 0 > /sys/class/power_supply/max77843-charger/current_now && '
- 'dumpsys battery set ac 0 && dumpsys battery set usb 0'),
- 'charge_counter': None,
- 'voltage': '/sys/class/power_supply/max77843-fuelgauge/voltage_now',
- 'current': '/sys/class/power_supply/max77843-charger/current_now',
- },
-]
-
-# The list of useful dumpsys columns.
-# Index of the column containing the format version.
-_DUMP_VERSION_INDEX = 0
-# Index of the column containing the type of the row.
-_ROW_TYPE_INDEX = 3
-# Index of the column containing the uid.
-_PACKAGE_UID_INDEX = 4
-# Index of the column containing the application package.
-_PACKAGE_NAME_INDEX = 5
-# The column containing the uid of the power data.
-_PWI_UID_INDEX = 1
-# The column containing the type of consumption. Only consumption since last
-# charge are of interest here.
-_PWI_AGGREGATION_INDEX = 2
-_PWS_AGGREGATION_INDEX = _PWI_AGGREGATION_INDEX
-# The column containing the amount of power used, in mah.
-_PWI_POWER_CONSUMPTION_INDEX = 5
-_PWS_POWER_CONSUMPTION_INDEX = _PWI_POWER_CONSUMPTION_INDEX
-
-_MAX_CHARGE_ERROR = 20
-
-
-class BatteryUtils(object):
-
- def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT,
- default_retries=_DEFAULT_RETRIES):
- """BatteryUtils constructor.
-
- Args:
- device: A DeviceUtils instance.
- default_timeout: An integer containing the default number of seconds to
- wait for an operation to complete if no explicit value
- is provided.
- default_retries: An integer containing the default number or times an
- operation should be retried on failure if no explicit
- value is provided.
- Raises:
- TypeError: If it is not passed a DeviceUtils instance.
- """
- if not isinstance(device, device_utils.DeviceUtils):
- raise TypeError('Must be initialized with DeviceUtils object.')
- self._device = device
- self._cache = device.GetClientCache(self.__class__.__name__)
- self._default_timeout = default_timeout
- self._default_retries = default_retries
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def SupportsFuelGauge(self, timeout=None, retries=None):
- """Detect if fuel gauge chip is present.
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- True if known fuel gauge files are present.
- False otherwise.
- """
- self._DiscoverDeviceProfile()
- return (self._cache['profile']['enable_command'] != None
- and self._cache['profile']['charge_counter'] != None)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def GetFuelGaugeChargeCounter(self, timeout=None, retries=None):
- """Get value of charge_counter on fuel gauge chip.
-
- Device must have charging disabled for this, not just battery updates
- disabled. The only device that this currently works with is the nexus 5.
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- value of charge_counter for fuel gauge chip in units of nAh.
-
- Raises:
- device_errors.CommandFailedError: If fuel gauge chip not found.
- """
- if self.SupportsFuelGauge():
- return int(self._device.ReadFile(
- self._cache['profile']['charge_counter']))
- raise device_errors.CommandFailedError(
- 'Unable to find fuel gauge.')
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def GetNetworkData(self, package, timeout=None, retries=None):
- """Get network data for specific package.
-
- Args:
- package: package name you want network data for.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- Tuple of (sent_data, recieved_data)
- None if no network data found
- """
- # If device_utils clears cache, cache['uids'] doesn't exist
- if 'uids' not in self._cache:
- self._cache['uids'] = {}
- if package not in self._cache['uids']:
- self.GetPowerData()
- if package not in self._cache['uids']:
- logger.warning('No UID found for %s. Can\'t get network data.',
- package)
- return None
-
- network_data_path = '/proc/uid_stat/%s/' % self._cache['uids'][package]
- try:
- send_data = int(self._device.ReadFile(network_data_path + 'tcp_snd'))
- # If ReadFile throws exception, it means no network data usage file for
- # package has been recorded. Return 0 sent and 0 received.
- except device_errors.AdbShellCommandFailedError:
- logger.warning('No sent data found for package %s', package)
- send_data = 0
- try:
- recv_data = int(self._device.ReadFile(network_data_path + 'tcp_rcv'))
- except device_errors.AdbShellCommandFailedError:
- logger.warning('No received data found for package %s', package)
- recv_data = 0
- return (send_data, recv_data)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def GetPowerData(self, timeout=None, retries=None):
- """Get power data for device.
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- Dict containing system power, and a per-package power dict keyed on
- package names.
- {
- 'system_total': 23.1,
- 'per_package' : {
- package_name: {
- 'uid': uid,
- 'data': [1,2,3]
- },
- }
- }
- """
- if 'uids' not in self._cache:
- self._cache['uids'] = {}
- dumpsys_output = self._device.RunShellCommand(
- ['dumpsys', 'batterystats', '-c'],
- check_return=True, large_output=True)
- csvreader = csv.reader(dumpsys_output)
- pwi_entries = collections.defaultdict(list)
- system_total = None
- for entry in csvreader:
- if entry[_DUMP_VERSION_INDEX] not in ['8', '9']:
- # Wrong dumpsys version.
- raise device_errors.DeviceVersionError(
- 'Dumpsys version must be 8 or 9. "%s" found.'
- % entry[_DUMP_VERSION_INDEX])
- if _ROW_TYPE_INDEX < len(entry) and entry[_ROW_TYPE_INDEX] == 'uid':
- current_package = entry[_PACKAGE_NAME_INDEX]
- if (self._cache['uids'].get(current_package)
- and self._cache['uids'].get(current_package)
- != entry[_PACKAGE_UID_INDEX]):
- raise device_errors.CommandFailedError(
- 'Package %s found multiple times with different UIDs %s and %s'
- % (current_package, self._cache['uids'][current_package],
- entry[_PACKAGE_UID_INDEX]))
- self._cache['uids'][current_package] = entry[_PACKAGE_UID_INDEX]
- elif (_PWI_POWER_CONSUMPTION_INDEX < len(entry)
- and entry[_ROW_TYPE_INDEX] == 'pwi'
- and entry[_PWI_AGGREGATION_INDEX] == 'l'):
- pwi_entries[entry[_PWI_UID_INDEX]].append(
- float(entry[_PWI_POWER_CONSUMPTION_INDEX]))
- elif (_PWS_POWER_CONSUMPTION_INDEX < len(entry)
- and entry[_ROW_TYPE_INDEX] == 'pws'
- and entry[_PWS_AGGREGATION_INDEX] == 'l'):
- # This entry should only appear once.
- assert system_total is None
- system_total = float(entry[_PWS_POWER_CONSUMPTION_INDEX])
-
- per_package = {p: {'uid': uid, 'data': pwi_entries[uid]}
- for p, uid in self._cache['uids'].iteritems()}
- return {'system_total': system_total, 'per_package': per_package}
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def GetBatteryInfo(self, timeout=None, retries=None):
- """Gets battery info for the device.
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
- Returns:
- A dict containing various battery information as reported by dumpsys
- battery.
- """
- result = {}
- # Skip the first line, which is just a header.
- for line in self._device.RunShellCommand(
- ['dumpsys', 'battery'], check_return=True)[1:]:
- # If usb charging has been disabled, an extra line of header exists.
- if 'UPDATES STOPPED' in line:
- logger.warning('Dumpsys battery not receiving updates. '
- 'Run dumpsys battery reset if this is in error.')
- elif ':' not in line:
- logger.warning('Unknown line found in dumpsys battery: "%s"', line)
- else:
- k, v = line.split(':', 1)
- result[k.strip()] = v.strip()
- return result
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def GetCharging(self, timeout=None, retries=None):
- """Gets the charging state of the device.
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
- Returns:
- True if the device is charging, false otherwise.
- """
- battery_info = self.GetBatteryInfo()
- for k in ('AC powered', 'USB powered', 'Wireless powered'):
- if (k in battery_info and
- battery_info[k].lower() in ('true', '1', 'yes')):
- return True
- return False
-
- # TODO(rnephew): Make private when all use cases can use the context manager.
- @decorators.WithTimeoutAndRetriesFromInstance()
- def DisableBatteryUpdates(self, timeout=None, retries=None):
- """Resets battery data and makes device appear like it is not
- charging so that it will collect power data since last charge.
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- device_errors.CommandFailedError: When resetting batterystats fails to
- reset power values.
- device_errors.DeviceVersionError: If device is not L or higher.
- """
- def battery_updates_disabled():
- return self.GetCharging() is False
-
- self._ClearPowerData()
- self._device.RunShellCommand(['dumpsys', 'battery', 'set', 'ac', '0'],
- check_return=True)
- self._device.RunShellCommand(['dumpsys', 'battery', 'set', 'usb', '0'],
- check_return=True)
- timeout_retry.WaitFor(battery_updates_disabled, wait_period=1)
-
- # TODO(rnephew): Make private when all use cases can use the context manager.
- @decorators.WithTimeoutAndRetriesFromInstance()
- def EnableBatteryUpdates(self, timeout=None, retries=None):
- """Restarts device charging so that dumpsys no longer collects power data.
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- device_errors.DeviceVersionError: If device is not L or higher.
- """
- def battery_updates_enabled():
- return (self.GetCharging()
- or not bool('UPDATES STOPPED' in self._device.RunShellCommand(
- ['dumpsys', 'battery'], check_return=True)))
-
- self._device.RunShellCommand(['dumpsys', 'battery', 'reset'],
- check_return=True)
- timeout_retry.WaitFor(battery_updates_enabled, wait_period=1)
-
- @contextlib.contextmanager
- def BatteryMeasurement(self, timeout=None, retries=None):
- """Context manager that enables battery data collection. It makes
- the device appear to stop charging so that dumpsys will start collecting
- power data since last charge. Once the with block is exited, charging is
- resumed and power data since last charge is no longer collected.
-
- Only for devices L and higher.
-
- Example usage:
- with BatteryMeasurement():
- browser_actions()
- get_power_data() # report usage within this block
- after_measurements() # Anything that runs after power
- # measurements are collected
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- device_errors.DeviceVersionError: If device is not L or higher.
- """
- if self._device.build_version_sdk < version_codes.LOLLIPOP:
- raise device_errors.DeviceVersionError('Device must be L or higher.')
- try:
- self.DisableBatteryUpdates(timeout=timeout, retries=retries)
- yield
- finally:
- self.EnableBatteryUpdates(timeout=timeout, retries=retries)
-
- def _DischargeDevice(self, percent, wait_period=120):
- """Disables charging and waits for device to discharge given amount
-
- Args:
- percent: level of charge to discharge.
-
- Raises:
- ValueError: If percent is not between 1 and 99.
- """
- battery_level = int(self.GetBatteryInfo().get('level'))
- if not 0 < percent < 100:
- raise ValueError('Discharge amount(%s) must be between 1 and 99'
- % percent)
- if battery_level is None:
- logger.warning('Unable to find current battery level. Cannot discharge.')
- return
- # Do not discharge if it would make battery level too low.
- if percent >= battery_level - 10:
- logger.warning('Battery is too low or discharge amount requested is too '
- 'high. Cannot discharge phone %s percent.', percent)
- return
-
- self._HardwareSetCharging(False)
-
- def device_discharged():
- self._HardwareSetCharging(True)
- current_level = int(self.GetBatteryInfo().get('level'))
- logger.info('current battery level: %s', current_level)
- if battery_level - current_level >= percent:
- return True
- self._HardwareSetCharging(False)
- return False
-
- timeout_retry.WaitFor(device_discharged, wait_period=wait_period)
-
- def ChargeDeviceToLevel(self, level, wait_period=60):
- """Enables charging and waits for device to be charged to given level.
-
- Args:
- level: level of charge to wait for.
- wait_period: time in seconds to wait between checking.
- Raises:
- device_errors.DeviceChargingError: If error while charging is detected.
- """
- self.SetCharging(True)
- charge_status = {
- 'charge_failure_count': 0,
- 'last_charge_value': 0
- }
- def device_charged():
- battery_level = self.GetBatteryInfo().get('level')
- if battery_level is None:
- logger.warning('Unable to find current battery level.')
- battery_level = 100
- else:
- logger.info('current battery level: %s', battery_level)
- battery_level = int(battery_level)
-
- # Use > so that it will not reset if charge is going down.
- if battery_level > charge_status['last_charge_value']:
- charge_status['last_charge_value'] = battery_level
- charge_status['charge_failure_count'] = 0
- else:
- charge_status['charge_failure_count'] += 1
-
- if (not battery_level >= level
- and charge_status['charge_failure_count'] >= _MAX_CHARGE_ERROR):
- raise device_errors.DeviceChargingError(
- 'Device not charging properly. Current level:%s Previous level:%s'
- % (battery_level, charge_status['last_charge_value']))
- return battery_level >= level
-
- timeout_retry.WaitFor(device_charged, wait_period=wait_period)
-
- def LetBatteryCoolToTemperature(self, target_temp, wait_period=180):
- """Lets device sit to give battery time to cool down
- Args:
- temp: maximum temperature to allow in tenths of degrees c.
- wait_period: time in seconds to wait between checking.
- """
- def cool_device():
- temp = self.GetBatteryInfo().get('temperature')
- if temp is None:
- logger.warning('Unable to find current battery temperature.')
- temp = 0
- else:
- logger.info('Current battery temperature: %s', temp)
- if int(temp) <= target_temp:
- return True
- else:
- if 'Nexus 5' in self._cache['profile']['name']:
- self._DischargeDevice(1)
- return False
-
- self._DiscoverDeviceProfile()
- self.EnableBatteryUpdates()
- logger.info('Waiting for the device to cool down to %s (0.1 C)',
- target_temp)
- timeout_retry.WaitFor(cool_device, wait_period=wait_period)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def SetCharging(self, enabled, timeout=None, retries=None):
- """Enables or disables charging on the device.
-
- Args:
- enabled: A boolean indicating whether charging should be enabled or
- disabled.
- timeout: timeout in seconds
- retries: number of retries
- """
- if self.GetCharging() == enabled:
- logger.warning('Device charging already in expected state: %s', enabled)
- return
-
- self._DiscoverDeviceProfile()
- if enabled:
- if self._cache['profile']['enable_command']:
- self._HardwareSetCharging(enabled)
- else:
- logger.info('Unable to enable charging via hardware. '
- 'Falling back to software enabling.')
- self.EnableBatteryUpdates()
- else:
- if self._cache['profile']['enable_command']:
- self._ClearPowerData()
- self._HardwareSetCharging(enabled)
- else:
- logger.info('Unable to disable charging via hardware. '
- 'Falling back to software disabling.')
- self.DisableBatteryUpdates()
-
- def _HardwareSetCharging(self, enabled, timeout=None, retries=None):
- """Enables or disables charging on the device.
-
- Args:
- enabled: A boolean indicating whether charging should be enabled or
- disabled.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- device_errors.CommandFailedError: If method of disabling charging cannot
- be determined.
- """
- self._DiscoverDeviceProfile()
- if not self._cache['profile']['enable_command']:
- raise device_errors.CommandFailedError(
- 'Unable to find charging commands.')
-
- command = (self._cache['profile']['enable_command'] if enabled
- else self._cache['profile']['disable_command'])
-
- def verify_charging():
- return self.GetCharging() == enabled
-
- self._device.RunShellCommand(
- command, check_return=True, as_root=True, large_output=True)
- timeout_retry.WaitFor(verify_charging, wait_period=1)
-
- @contextlib.contextmanager
- def PowerMeasurement(self, timeout=None, retries=None):
- """Context manager that enables battery power collection.
-
- Once the with block is exited, charging is resumed. Will attempt to disable
- charging at the hardware level, and if that fails will fall back to software
- disabling of battery updates.
-
- Only for devices L and higher.
-
- Example usage:
- with PowerMeasurement():
- browser_actions()
- get_power_data() # report usage within this block
- after_measurements() # Anything that runs after power
- # measurements are collected
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
- """
- try:
- self.SetCharging(False, timeout=timeout, retries=retries)
- yield
- finally:
- self.SetCharging(True, timeout=timeout, retries=retries)
-
- def _ClearPowerData(self):
- """Resets battery data and makes device appear like it is not
- charging so that it will collect power data since last charge.
-
- Returns:
- True if power data cleared.
- False if power data clearing is not supported (pre-L)
-
- Raises:
- device_errors.DeviceVersionError: If power clearing is supported,
- but fails.
- """
- if self._device.build_version_sdk < version_codes.LOLLIPOP:
- logger.warning('Dumpsys power data only available on 5.0 and above. '
- 'Cannot clear power data.')
- return False
-
- self._device.RunShellCommand(
- ['dumpsys', 'battery', 'set', 'usb', '1'], check_return=True)
- self._device.RunShellCommand(
- ['dumpsys', 'battery', 'set', 'ac', '1'], check_return=True)
-
- def test_if_clear():
- self._device.RunShellCommand(
- ['dumpsys', 'batterystats', '--reset'], check_return=True)
- battery_data = self._device.RunShellCommand(
- ['dumpsys', 'batterystats', '--charged', '-c'],
- check_return=True, large_output=True)
- for line in battery_data:
- l = line.split(',')
- if (len(l) > _PWI_POWER_CONSUMPTION_INDEX
- and l[_ROW_TYPE_INDEX] == 'pwi'
- and float(l[_PWI_POWER_CONSUMPTION_INDEX]) != 0.0):
- return False
- return True
-
- try:
- timeout_retry.WaitFor(test_if_clear, wait_period=1)
- return True
- finally:
- self._device.RunShellCommand(
- ['dumpsys', 'battery', 'reset'], check_return=True)
-
- def _DiscoverDeviceProfile(self):
- """Checks and caches device information.
-
- Returns:
- True if profile is found, false otherwise.
- """
-
- if 'profile' in self._cache:
- return True
- for profile in _DEVICE_PROFILES:
- if self._device.product_model in profile['name']:
- self._cache['profile'] = profile
- return True
- self._cache['profile'] = {
- 'name': [],
- 'enable_command': None,
- 'disable_command': None,
- 'charge_counter': None,
- 'voltage': None,
- 'current': None,
- }
- return False
diff --git a/third_party/catapult/devil/devil/android/battery_utils_test.py b/third_party/catapult/devil/devil/android/battery_utils_test.py
deleted file mode 100755
index beaba3b001..0000000000
--- a/third_party/catapult/devil/devil/android/battery_utils_test.py
+++ /dev/null
@@ -1,694 +0,0 @@
-#!/usr/bin/env python
-# 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.
-
-"""
-Unit tests for the contents of battery_utils.py
-"""
-
-# pylint: disable=protected-access,unused-argument
-
-import logging
-import unittest
-
-from devil import devil_env
-from devil.android import battery_utils
-from devil.android import device_errors
-from devil.android import device_utils
-from devil.android import device_utils_test
-from devil.utils import mock_calls
-
-with devil_env.SysPath(devil_env.PYMOCK_PATH):
- import mock # pylint: disable=import-error
-
-_DUMPSYS_OUTPUT = [
- '9,0,i,uid,1000,test_package1',
- '9,0,i,uid,1001,test_package2',
- '9,1000,l,pwi,uid,1',
- '9,1001,l,pwi,uid,2',
- '9,0,l,pws,1728,2000,190,207',
-]
-
-
-class BatteryUtilsTest(mock_calls.TestCase):
-
- _NEXUS_5 = {
- 'name': 'Nexus 5',
- 'witness_file': '/sys/kernel/debug/bq24192/INPUT_SRC_CONT',
- 'enable_command': (
- 'echo 0x4A > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && '
- 'echo 1 > /sys/class/power_supply/usb/online'),
- 'disable_command': (
- 'echo 0xCA > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && '
- 'chmod 644 /sys/class/power_supply/usb/online && '
- 'echo 0 > /sys/class/power_supply/usb/online'),
- 'charge_counter': None,
- 'voltage': None,
- 'current': None,
- }
-
- _NEXUS_6 = {
- 'name': 'Nexus 6',
- 'witness_file': None,
- 'enable_command': None,
- 'disable_command': None,
- 'charge_counter': (
- '/sys/class/power_supply/max170xx_battery/charge_counter_ext'),
- 'voltage': '/sys/class/power_supply/max170xx_battery/voltage_now',
- 'current': '/sys/class/power_supply/max170xx_battery/current_now',
- }
-
- _NEXUS_10 = {
- 'name': 'Nexus 10',
- 'witness_file': None,
- 'enable_command': None,
- 'disable_command': None,
- 'charge_counter': (
- '/sys/class/power_supply/ds2784-fuelgauge/charge_counter_ext'),
- 'voltage': '/sys/class/power_supply/ds2784-fuelgauge/voltage_now',
- 'current': '/sys/class/power_supply/ds2784-fuelgauge/current_now',
- }
-
- def ShellError(self, output=None, status=1):
- def action(cmd, *args, **kwargs):
- raise device_errors.AdbShellCommandFailedError(
- cmd, output, status, str(self.device))
- if output is None:
- output = 'Permission denied\n'
- return action
-
- def setUp(self):
- self.adb = device_utils_test._AdbWrapperMock('0123456789abcdef')
- self.device = device_utils.DeviceUtils(
- self.adb, default_timeout=10, default_retries=0)
- self.watchMethodCalls(self.call.adb, ignore=['GetDeviceSerial'])
- self.battery = battery_utils.BatteryUtils(
- self.device, default_timeout=10, default_retries=0)
-
-
-class BatteryUtilsInitTest(unittest.TestCase):
-
- def testInitWithDeviceUtil(self):
- serial = '0fedcba987654321'
- d = device_utils.DeviceUtils(serial)
- b = battery_utils.BatteryUtils(d)
- self.assertEqual(d, b._device)
-
- def testInitWithMissing_fails(self):
- with self.assertRaises(TypeError):
- battery_utils.BatteryUtils(None)
- with self.assertRaises(TypeError):
- battery_utils.BatteryUtils('')
-
-
-class BatteryUtilsSetChargingTest(BatteryUtilsTest):
-
- @mock.patch('time.sleep', mock.Mock())
- def testHardwareSetCharging_enabled(self):
- self.battery._cache['profile'] = self._NEXUS_5
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- mock.ANY, check_return=True, as_root=True, large_output=True), []),
- (self.call.battery.GetCharging(), False),
- (self.call.battery.GetCharging(), True)):
- self.battery._HardwareSetCharging(True)
-
- def testHardwareSetCharging_alreadyEnabled(self):
- self.battery._cache['profile'] = self._NEXUS_5
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- mock.ANY, check_return=True, as_root=True, large_output=True), []),
- (self.call.battery.GetCharging(), True)):
- self.battery._HardwareSetCharging(True)
-
- @mock.patch('time.sleep', mock.Mock())
- def testHardwareSetCharging_disabled(self):
- self.battery._cache['profile'] = self._NEXUS_5
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- mock.ANY, check_return=True, as_root=True, large_output=True), []),
- (self.call.battery.GetCharging(), True),
- (self.call.battery.GetCharging(), False)):
- self.battery._HardwareSetCharging(False)
-
-
-class BatteryUtilsSetBatteryMeasurementTest(BatteryUtilsTest):
-
- @mock.patch('time.sleep', mock.Mock())
- def testBatteryMeasurementWifi(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=22):
- with self.assertCalls(
- (self.call.battery._ClearPowerData(), True),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery', 'set', 'ac', '0'], check_return=True), []),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery', 'set', 'usb', '0'], check_return=True),
- []),
- (self.call.battery.GetCharging(), False),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery', 'reset'], check_return=True), []),
- (self.call.battery.GetCharging(), False),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery'], check_return=True), ['UPDATES STOPPED']),
- (self.call.battery.GetCharging(), False),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery'], check_return=True), [])):
- with self.battery.BatteryMeasurement():
- pass
-
- @mock.patch('time.sleep', mock.Mock())
- def testBatteryMeasurementUsb(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=22):
- with self.assertCalls(
- (self.call.battery._ClearPowerData(), True),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery', 'set', 'ac', '0'], check_return=True), []),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery', 'set', 'usb', '0'], check_return=True),
- []),
- (self.call.battery.GetCharging(), False),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery', 'reset'], check_return=True), []),
- (self.call.battery.GetCharging(), False),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery'], check_return=True), ['UPDATES STOPPED']),
- (self.call.battery.GetCharging(), True)):
- with self.battery.BatteryMeasurement():
- pass
-
-
-class BatteryUtilsGetPowerData(BatteryUtilsTest):
-
- def testGetPowerData(self):
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- ['dumpsys', 'batterystats', '-c'],
- check_return=True, large_output=True),
- _DUMPSYS_OUTPUT)):
- data = self.battery.GetPowerData()
- check = {
- 'system_total': 2000.0,
- 'per_package': {
- 'test_package1': {'uid': '1000', 'data': [1.0]},
- 'test_package2': {'uid': '1001', 'data': [2.0]}
- }
- }
- self.assertEqual(data, check)
-
- def testGetPowerData_packageCollisionSame(self):
- self.battery._cache['uids'] = {'test_package1': '1000'}
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['dumpsys', 'batterystats', '-c'],
- check_return=True, large_output=True),
- _DUMPSYS_OUTPUT):
- data = self.battery.GetPowerData()
- check = {
- 'system_total': 2000.0,
- 'per_package': {
- 'test_package1': {'uid': '1000', 'data': [1.0]},
- 'test_package2': {'uid': '1001', 'data': [2.0]}
- }
- }
- self.assertEqual(data, check)
-
- def testGetPowerData_packageCollisionDifferent(self):
- self.battery._cache['uids'] = {'test_package1': '1'}
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['dumpsys', 'batterystats', '-c'],
- check_return=True, large_output=True),
- _DUMPSYS_OUTPUT):
- with self.assertRaises(device_errors.CommandFailedError):
- self.battery.GetPowerData()
-
- def testGetPowerData_cacheCleared(self):
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- ['dumpsys', 'batterystats', '-c'],
- check_return=True, large_output=True),
- _DUMPSYS_OUTPUT)):
- self.battery._cache.clear()
- data = self.battery.GetPowerData()
- check = {
- 'system_total': 2000.0,
- 'per_package': {
- 'test_package1': {'uid': '1000', 'data': [1.0]},
- 'test_package2': {'uid': '1001', 'data': [2.0]}
- }
- }
- self.assertEqual(data, check)
-
-
-class BatteryUtilsChargeDevice(BatteryUtilsTest):
-
- @mock.patch('time.sleep', mock.Mock())
- def testChargeDeviceToLevel_pass(self):
- with self.assertCalls(
- (self.call.battery.SetCharging(True)),
- (self.call.battery.GetBatteryInfo(), {'level': '50'}),
- (self.call.battery.GetBatteryInfo(), {'level': '100'})):
- self.battery.ChargeDeviceToLevel(95)
-
- @mock.patch('time.sleep', mock.Mock())
- def testChargeDeviceToLevel_failureSame(self):
- with self.assertCalls(
- (self.call.battery.SetCharging(True)),
- (self.call.battery.GetBatteryInfo(), {'level': '50'}),
- (self.call.battery.GetBatteryInfo(), {'level': '50'}),
-
- (self.call.battery.GetBatteryInfo(), {'level': '50'})):
- with self.assertRaises(device_errors.DeviceChargingError):
- old_max = battery_utils._MAX_CHARGE_ERROR
- try:
- battery_utils._MAX_CHARGE_ERROR = 2
- self.battery.ChargeDeviceToLevel(95)
- finally:
- battery_utils._MAX_CHARGE_ERROR = old_max
-
- @mock.patch('time.sleep', mock.Mock())
- def testChargeDeviceToLevel_failureDischarge(self):
- with self.assertCalls(
- (self.call.battery.SetCharging(True)),
- (self.call.battery.GetBatteryInfo(), {'level': '50'}),
- (self.call.battery.GetBatteryInfo(), {'level': '49'}),
- (self.call.battery.GetBatteryInfo(), {'level': '48'})):
- with self.assertRaises(device_errors.DeviceChargingError):
- old_max = battery_utils._MAX_CHARGE_ERROR
- try:
- battery_utils._MAX_CHARGE_ERROR = 2
- self.battery.ChargeDeviceToLevel(95)
- finally:
- battery_utils._MAX_CHARGE_ERROR = old_max
-
-
-class BatteryUtilsDischargeDevice(BatteryUtilsTest):
-
- @mock.patch('time.sleep', mock.Mock())
- def testDischargeDevice_exact(self):
- with self.assertCalls(
- (self.call.battery.GetBatteryInfo(), {'level': '100'}),
- (self.call.battery._HardwareSetCharging(False)),
- (self.call.battery._HardwareSetCharging(True)),
- (self.call.battery.GetBatteryInfo(), {'level': '99'})):
- self.battery._DischargeDevice(1)
-
- @mock.patch('time.sleep', mock.Mock())
- def testDischargeDevice_over(self):
- with self.assertCalls(
- (self.call.battery.GetBatteryInfo(), {'level': '100'}),
- (self.call.battery._HardwareSetCharging(False)),
- (self.call.battery._HardwareSetCharging(True)),
- (self.call.battery.GetBatteryInfo(), {'level': '50'})):
- self.battery._DischargeDevice(1)
-
- @mock.patch('time.sleep', mock.Mock())
- def testDischargeDevice_takeslong(self):
- with self.assertCalls(
- (self.call.battery.GetBatteryInfo(), {'level': '100'}),
- (self.call.battery._HardwareSetCharging(False)),
- (self.call.battery._HardwareSetCharging(True)),
- (self.call.battery.GetBatteryInfo(), {'level': '100'}),
- (self.call.battery._HardwareSetCharging(False)),
- (self.call.battery._HardwareSetCharging(True)),
- (self.call.battery.GetBatteryInfo(), {'level': '99'}),
- (self.call.battery._HardwareSetCharging(False)),
- (self.call.battery._HardwareSetCharging(True)),
- (self.call.battery.GetBatteryInfo(), {'level': '98'}),
- (self.call.battery._HardwareSetCharging(False)),
- (self.call.battery._HardwareSetCharging(True)),
- (self.call.battery.GetBatteryInfo(), {'level': '97'})):
- self.battery._DischargeDevice(3)
-
- @mock.patch('time.sleep', mock.Mock())
- def testDischargeDevice_dischargeTooClose(self):
- with self.assertCalls(
- (self.call.battery.GetBatteryInfo(), {'level': '100'})):
- self.battery._DischargeDevice(99)
-
- @mock.patch('time.sleep', mock.Mock())
- def testDischargeDevice_percentageOutOfBounds(self):
- with self.assertCalls(
- (self.call.battery.GetBatteryInfo(), {'level': '100'})):
- with self.assertRaises(ValueError):
- self.battery._DischargeDevice(100)
- with self.assertCalls(
- (self.call.battery.GetBatteryInfo(), {'level': '100'})):
- with self.assertRaises(ValueError):
- self.battery._DischargeDevice(0)
-
-
-class BatteryUtilsGetBatteryInfoTest(BatteryUtilsTest):
-
- def testGetBatteryInfo_normal(self):
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery'], check_return=True),
- [
- 'Current Battery Service state:',
- ' AC powered: false',
- ' USB powered: true',
- ' level: 100',
- ' temperature: 321',
- ])):
- self.assertEquals(
- {
- 'AC powered': 'false',
- 'USB powered': 'true',
- 'level': '100',
- 'temperature': '321',
- },
- self.battery.GetBatteryInfo())
-
- def testGetBatteryInfo_nothing(self):
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery'], check_return=True), [])):
- self.assertEquals({}, self.battery.GetBatteryInfo())
-
-
-class BatteryUtilsGetChargingTest(BatteryUtilsTest):
-
- def testGetCharging_usb(self):
- with self.assertCall(
- self.call.battery.GetBatteryInfo(), {'USB powered': 'true'}):
- self.assertTrue(self.battery.GetCharging())
-
- def testGetCharging_usbFalse(self):
- with self.assertCall(
- self.call.battery.GetBatteryInfo(), {'USB powered': 'false'}):
- self.assertFalse(self.battery.GetCharging())
-
- def testGetCharging_ac(self):
- with self.assertCall(
- self.call.battery.GetBatteryInfo(), {'AC powered': 'true'}):
- self.assertTrue(self.battery.GetCharging())
-
- def testGetCharging_wireless(self):
- with self.assertCall(
- self.call.battery.GetBatteryInfo(), {'Wireless powered': 'true'}):
- self.assertTrue(self.battery.GetCharging())
-
- def testGetCharging_unknown(self):
- with self.assertCall(
- self.call.battery.GetBatteryInfo(), {'level': '42'}):
- self.assertFalse(self.battery.GetCharging())
-
-
-class BatteryUtilsGetNetworkDataTest(BatteryUtilsTest):
-
- def testGetNetworkData_noDataUsage(self):
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- ['dumpsys', 'batterystats', '-c'],
- check_return=True, large_output=True),
- _DUMPSYS_OUTPUT),
- (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_snd'),
- self.ShellError()),
- (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_rcv'),
- self.ShellError())):
- self.assertEquals(self.battery.GetNetworkData('test_package1'), (0, 0))
-
- def testGetNetworkData_badPackage(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['dumpsys', 'batterystats', '-c'],
- check_return=True, large_output=True),
- _DUMPSYS_OUTPUT):
- self.assertEqual(self.battery.GetNetworkData('asdf'), None)
-
- def testGetNetworkData_packageNotCached(self):
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- ['dumpsys', 'batterystats', '-c'],
- check_return=True, large_output=True),
- _DUMPSYS_OUTPUT),
- (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_snd'), 1),
- (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_rcv'), 2)):
- self.assertEqual(self.battery.GetNetworkData('test_package1'), (1, 2))
-
- def testGetNetworkData_packageCached(self):
- self.battery._cache['uids'] = {'test_package1': '1000'}
- with self.assertCalls(
- (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_snd'), 1),
- (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_rcv'), 2)):
- self.assertEqual(self.battery.GetNetworkData('test_package1'), (1, 2))
-
- def testGetNetworkData_clearedCache(self):
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- ['dumpsys', 'batterystats', '-c'],
- check_return=True, large_output=True),
- _DUMPSYS_OUTPUT),
- (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_snd'), 1),
- (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_rcv'), 2)):
- self.battery._cache.clear()
- self.assertEqual(self.battery.GetNetworkData('test_package1'), (1, 2))
-
-
-class BatteryUtilsLetBatteryCoolToTemperatureTest(BatteryUtilsTest):
-
- @mock.patch('time.sleep', mock.Mock())
- def testLetBatteryCoolToTemperature_startUnder(self):
- self.battery._cache['profile'] = self._NEXUS_6
- with self.assertCalls(
- (self.call.battery.EnableBatteryUpdates(), []),
- (self.call.battery.GetBatteryInfo(), {'temperature': '500'})):
- self.battery.LetBatteryCoolToTemperature(600)
-
- @mock.patch('time.sleep', mock.Mock())
- def testLetBatteryCoolToTemperature_startOver(self):
- self.battery._cache['profile'] = self._NEXUS_6
- with self.assertCalls(
- (self.call.battery.EnableBatteryUpdates(), []),
- (self.call.battery.GetBatteryInfo(), {'temperature': '500'}),
- (self.call.battery.GetBatteryInfo(), {'temperature': '400'})):
- self.battery.LetBatteryCoolToTemperature(400)
-
- @mock.patch('time.sleep', mock.Mock())
- def testLetBatteryCoolToTemperature_nexus5Hot(self):
- self.battery._cache['profile'] = self._NEXUS_5
- with self.assertCalls(
- (self.call.battery.EnableBatteryUpdates(), []),
- (self.call.battery.GetBatteryInfo(), {'temperature': '500'}),
- (self.call.battery._DischargeDevice(1), []),
- (self.call.battery.GetBatteryInfo(), {'temperature': '400'})):
- self.battery.LetBatteryCoolToTemperature(400)
-
- @mock.patch('time.sleep', mock.Mock())
- def testLetBatteryCoolToTemperature_nexus5Cool(self):
- self.battery._cache['profile'] = self._NEXUS_5
- with self.assertCalls(
- (self.call.battery.EnableBatteryUpdates(), []),
- (self.call.battery.GetBatteryInfo(), {'temperature': '400'})):
- self.battery.LetBatteryCoolToTemperature(400)
-
-
-class BatteryUtilsSupportsFuelGaugeTest(BatteryUtilsTest):
-
- def testSupportsFuelGauge_false(self):
- self.battery._cache['profile'] = self._NEXUS_5
- self.assertFalse(self.battery.SupportsFuelGauge())
-
- def testSupportsFuelGauge_trueMax(self):
- self.battery._cache['profile'] = self._NEXUS_6
- # TODO(rnephew): Change this to assertTrue when we have support for
- # disabling hardware charging on nexus 6.
- self.assertFalse(self.battery.SupportsFuelGauge())
-
- def testSupportsFuelGauge_trueDS(self):
- self.battery._cache['profile'] = self._NEXUS_10
- # TODO(rnephew): Change this to assertTrue when we have support for
- # disabling hardware charging on nexus 10.
- self.assertFalse(self.battery.SupportsFuelGauge())
-
-
-class BatteryUtilsGetFuelGaugeChargeCounterTest(BatteryUtilsTest):
-
- def testGetFuelGaugeChargeCounter_noFuelGauge(self):
- self.battery._cache['profile'] = self._NEXUS_5
- with self.assertRaises(device_errors.CommandFailedError):
- self.battery.GetFuelGaugeChargeCounter()
-
- def testGetFuelGaugeChargeCounter_fuelGaugePresent(self):
- self.battery._cache['profile'] = self._NEXUS_6
- with self.assertCalls(
- (self.call.battery.SupportsFuelGauge(), True),
- (self.call.device.ReadFile(mock.ANY), '123')):
- self.assertEqual(self.battery.GetFuelGaugeChargeCounter(), 123)
-
-
-class BatteryUtilsSetCharging(BatteryUtilsTest):
-
- @mock.patch('time.sleep', mock.Mock())
- def testSetCharging_softwareSetTrue(self):
- self.battery._cache['profile'] = self._NEXUS_6
- with self.assertCalls(
- (self.call.battery.GetCharging(), False),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery', 'reset'], check_return=True), []),
- (self.call.battery.GetCharging(), False),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery'], check_return=True), ['UPDATES STOPPED']),
- (self.call.battery.GetCharging(), True)):
- self.battery.SetCharging(True)
-
- @mock.patch('time.sleep', mock.Mock())
- def testSetCharging_softwareSetFalse(self):
- self.battery._cache['profile'] = self._NEXUS_6
- with self.assertCalls(
- (self.call.battery.GetCharging(), True),
- (self.call.battery._ClearPowerData(), True),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery', 'set', 'ac', '0'], check_return=True), []),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery', 'set', 'usb', '0'], check_return=True), []),
- (self.call.battery.GetCharging(), False)):
- self.battery.SetCharging(False)
-
- @mock.patch('time.sleep', mock.Mock())
- def testSetCharging_hardwareSetTrue(self):
- self.battery._cache['profile'] = self._NEXUS_5
- with self.assertCalls(
- (self.call.battery.GetCharging(), False),
- (self.call.battery._HardwareSetCharging(True))):
- self.battery.SetCharging(True)
-
- @mock.patch('time.sleep', mock.Mock())
- def testSetCharging_hardwareSetFalse(self):
- self.battery._cache['profile'] = self._NEXUS_5
- with self.assertCalls(
- (self.call.battery.GetCharging(), True),
- (self.call.battery._ClearPowerData(), True),
- (self.call.battery._HardwareSetCharging(False))):
- self.battery.SetCharging(False)
-
- def testSetCharging_expectedStateAlreadyTrue(self):
- with self.assertCalls((self.call.battery.GetCharging(), True)):
- self.battery.SetCharging(True)
-
- def testSetCharging_expectedStateAlreadyFalse(self):
- with self.assertCalls((self.call.battery.GetCharging(), False)):
- self.battery.SetCharging(False)
-
-
-class BatteryUtilsPowerMeasurement(BatteryUtilsTest):
-
- def testPowerMeasurement_hardware(self):
- self.battery._cache['profile'] = self._NEXUS_5
- with self.assertCalls(
- (self.call.battery.GetCharging(), True),
- (self.call.battery._ClearPowerData(), True),
- (self.call.battery._HardwareSetCharging(False)),
- (self.call.battery.GetCharging(), False),
- (self.call.battery._HardwareSetCharging(True))):
- with self.battery.PowerMeasurement():
- pass
-
- @mock.patch('time.sleep', mock.Mock())
- def testPowerMeasurement_software(self):
- self.battery._cache['profile'] = self._NEXUS_6
- with self.assertCalls(
- (self.call.battery.GetCharging(), True),
- (self.call.battery._ClearPowerData(), True),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery', 'set', 'ac', '0'], check_return=True), []),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery', 'set', 'usb', '0'], check_return=True), []),
- (self.call.battery.GetCharging(), False),
- (self.call.battery.GetCharging(), False),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery', 'reset'], check_return=True), []),
- (self.call.battery.GetCharging(), False),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery'], check_return=True), ['UPDATES STOPPED']),
- (self.call.battery.GetCharging(), True)):
- with self.battery.PowerMeasurement():
- pass
-
-
-class BatteryUtilsDiscoverDeviceProfile(BatteryUtilsTest):
-
- def testDiscoverDeviceProfile_known(self):
- with self.patch_call(self.call.device.product_model,
- return_value='Nexus 4'):
- self.battery._DiscoverDeviceProfile()
- self.assertListEqual(self.battery._cache['profile']['name'], ["Nexus 4"])
-
- def testDiscoverDeviceProfile_unknown(self):
- with self.patch_call(self.call.device.product_model,
- return_value='Other'):
- self.battery._DiscoverDeviceProfile()
- self.assertListEqual(self.battery._cache['profile']['name'], [])
-
-
-class BatteryUtilsClearPowerData(BatteryUtilsTest):
-
- def testClearPowerData_preL(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=20):
- self.assertFalse(self.battery._ClearPowerData())
-
- def testClearPowerData_clearedL(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=22):
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery', 'set', 'usb', '1'], check_return=True),
- []),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery', 'set', 'ac', '1'], check_return=True), []),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'batterystats', '--reset'], check_return=True), []),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'batterystats', '--charged', '-c'],
- check_return=True, large_output=True), []),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery', 'reset'], check_return=True), [])):
- self.assertTrue(self.battery._ClearPowerData())
-
- @mock.patch('time.sleep', mock.Mock())
- def testClearPowerData_notClearedL(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=22):
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery', 'set', 'usb', '1'], check_return=True),
- []),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery', 'set', 'ac', '1'], check_return=True), []),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'batterystats', '--reset'], check_return=True), []),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'batterystats', '--charged', '-c'],
- check_return=True, large_output=True),
- ['9,1000,l,pwi,uid,0.0327']),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'batterystats', '--reset'], check_return=True), []),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'batterystats', '--charged', '-c'],
- check_return=True, large_output=True),
- ['9,1000,l,pwi,uid,0.0327']),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'batterystats', '--reset'], check_return=True), []),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'batterystats', '--charged', '-c'],
- check_return=True, large_output=True),
- ['9,1000,l,pwi,uid,0.0327']),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'batterystats', '--reset'], check_return=True), []),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'batterystats', '--charged', '-c'],
- check_return=True, large_output=True),
- ['9,1000,l,pwi,uid,0.0']),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'battery', 'reset'], check_return=True), [])):
- self.battery._ClearPowerData()
-
-
-if __name__ == '__main__':
- logging.getLogger().setLevel(logging.DEBUG)
- unittest.main(verbosity=2)
diff --git a/third_party/catapult/devil/devil/android/constants/__init__.py b/third_party/catapult/devil/devil/android/constants/__init__.py
deleted file mode 100644
index 50b23dff63..0000000000
--- a/third_party/catapult/devil/devil/android/constants/__init__.py
+++ /dev/null
@@ -1,3 +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.
diff --git a/third_party/catapult/devil/devil/android/constants/chrome.py b/third_party/catapult/devil/devil/android/constants/chrome.py
deleted file mode 100644
index dca04bdc87..0000000000
--- a/third_party/catapult/devil/devil/android/constants/chrome.py
+++ /dev/null
@@ -1,57 +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.
-
-import collections
-
-PackageInfo = collections.namedtuple(
- 'PackageInfo',
- ['package', 'activity', 'cmdline_file', 'devtools_socket'])
-
-PACKAGE_INFO = {
- 'chrome_document': PackageInfo(
- 'com.google.android.apps.chrome.document',
- 'com.google.android.apps.chrome.document.ChromeLauncherActivity',
- 'chrome-command-line',
- 'chrome_devtools_remote'),
- 'chrome': PackageInfo(
- 'com.google.android.apps.chrome',
- 'com.google.android.apps.chrome.Main',
- 'chrome-command-line',
- 'chrome_devtools_remote'),
- 'chrome_beta': PackageInfo(
- 'com.chrome.beta',
- 'com.google.android.apps.chrome.Main',
- 'chrome-command-line',
- 'chrome_devtools_remote'),
- 'chrome_stable': PackageInfo(
- 'com.android.chrome',
- 'com.google.android.apps.chrome.Main',
- 'chrome-command-line',
- 'chrome_devtools_remote'),
- 'chrome_dev': PackageInfo(
- 'com.chrome.dev',
- 'com.google.android.apps.chrome.Main',
- 'chrome-command-line',
- 'chrome_devtools_remote'),
- 'chrome_canary': PackageInfo(
- 'com.chrome.canary',
- 'com.google.android.apps.chrome.Main',
- 'chrome-command-line',
- 'chrome_devtools_remote'),
- 'chrome_work': PackageInfo(
- 'com.chrome.work',
- 'com.google.android.apps.chrome.Main',
- 'chrome-command-line',
- 'chrome_devtools_remote'),
- 'chromium': PackageInfo(
- 'org.chromium.chrome',
- 'com.google.android.apps.chrome.Main',
- 'chrome-command-line',
- 'chrome_devtools_remote'),
- 'content_shell': PackageInfo(
- 'org.chromium.content_shell_apk',
- '.ContentShellActivity',
- 'content-shell-command-line',
- 'content_shell_devtools_remote'),
-}
diff --git a/third_party/catapult/devil/devil/android/constants/file_system.py b/third_party/catapult/devil/devil/android/constants/file_system.py
deleted file mode 100644
index bffec61442..0000000000
--- a/third_party/catapult/devil/devil/android/constants/file_system.py
+++ /dev/null
@@ -1,5 +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.
-
-TEST_EXECUTABLE_DIR = '/data/local/tmp'
diff --git a/third_party/catapult/devil/devil/android/decorators.py b/third_party/catapult/devil/devil/android/decorators.py
deleted file mode 100644
index 3844b49a1e..0000000000
--- a/third_party/catapult/devil/devil/android/decorators.py
+++ /dev/null
@@ -1,176 +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.
-
-"""
-Function/method decorators that provide timeout and retry logic.
-"""
-
-import functools
-import itertools
-import sys
-
-from devil.android import device_errors
-from devil.utils import cmd_helper
-from devil.utils import reraiser_thread
-from devil.utils import timeout_retry
-
-DEFAULT_TIMEOUT_ATTR = '_default_timeout'
-DEFAULT_RETRIES_ATTR = '_default_retries'
-
-
-def _TimeoutRetryWrapper(
- f, timeout_func, retries_func, retry_if_func=timeout_retry.AlwaysRetry,
- pass_values=False):
- """ Wraps a funcion with timeout and retry handling logic.
-
- Args:
- f: The function to wrap.
- timeout_func: A callable that returns the timeout value.
- retries_func: A callable that returns the retries value.
- pass_values: If True, passes the values returned by |timeout_func| and
- |retries_func| to the wrapped function as 'timeout' and
- 'retries' kwargs, respectively.
- Returns:
- The wrapped function.
- """
- @functools.wraps(f)
- def timeout_retry_wrapper(*args, **kwargs):
- timeout = timeout_func(*args, **kwargs)
- retries = retries_func(*args, **kwargs)
- if pass_values:
- kwargs['timeout'] = timeout
- kwargs['retries'] = retries
-
- @functools.wraps(f)
- def impl():
- return f(*args, **kwargs)
- try:
- if timeout_retry.CurrentTimeoutThreadGroup():
- # Don't wrap if there's already an outer timeout thread.
- return impl()
- else:
- desc = '%s(%s)' % (f.__name__, ', '.join(itertools.chain(
- (str(a) for a in args),
- ('%s=%s' % (k, str(v)) for k, v in kwargs.iteritems()))))
- return timeout_retry.Run(impl, timeout, retries, desc=desc,
- retry_if_func=retry_if_func)
- except reraiser_thread.TimeoutError as e:
- raise device_errors.CommandTimeoutError(str(e)), None, (
- sys.exc_info()[2])
- except cmd_helper.TimeoutError as e:
- raise device_errors.CommandTimeoutError(str(e)), None, (
- sys.exc_info()[2])
- return timeout_retry_wrapper
-
-
-def WithTimeoutAndRetries(f):
- """A decorator that handles timeouts and retries.
-
- 'timeout' and 'retries' kwargs must be passed to the function.
-
- Args:
- f: The function to decorate.
- Returns:
- The decorated function.
- """
- get_timeout = lambda *a, **kw: kw['timeout']
- get_retries = lambda *a, **kw: kw['retries']
- return _TimeoutRetryWrapper(f, get_timeout, get_retries)
-
-
-def WithTimeoutAndConditionalRetries(retry_if_func):
- """Returns a decorator that handles timeouts and, in some cases, retries.
-
- 'timeout' and 'retries' kwargs must be passed to the function.
-
- Args:
- retry_if_func: A unary callable that takes an exception and returns
- whether failures should be retried.
- Returns:
- The actual decorator.
- """
- def decorator(f):
- get_timeout = lambda *a, **kw: kw['timeout']
- get_retries = lambda *a, **kw: kw['retries']
- return _TimeoutRetryWrapper(
- f, get_timeout, get_retries, retry_if_func=retry_if_func)
- return decorator
-
-
-def WithExplicitTimeoutAndRetries(timeout, retries):
- """Returns a decorator that handles timeouts and retries.
-
- The provided |timeout| and |retries| values are always used.
-
- Args:
- timeout: The number of seconds to wait for the decorated function to
- return. Always used.
- retries: The number of times the decorated function should be retried on
- failure. Always used.
- Returns:
- The actual decorator.
- """
- def decorator(f):
- get_timeout = lambda *a, **kw: timeout
- get_retries = lambda *a, **kw: retries
- return _TimeoutRetryWrapper(f, get_timeout, get_retries)
- return decorator
-
-
-def WithTimeoutAndRetriesDefaults(default_timeout, default_retries):
- """Returns a decorator that handles timeouts and retries.
-
- The provided |default_timeout| and |default_retries| values are used only
- if timeout and retries values are not provided.
-
- Args:
- default_timeout: The number of seconds to wait for the decorated function
- to return. Only used if a 'timeout' kwarg is not passed
- to the decorated function.
- default_retries: The number of times the decorated function should be
- retried on failure. Only used if a 'retries' kwarg is not
- passed to the decorated function.
- Returns:
- The actual decorator.
- """
- def decorator(f):
- get_timeout = lambda *a, **kw: kw.get('timeout', default_timeout)
- get_retries = lambda *a, **kw: kw.get('retries', default_retries)
- return _TimeoutRetryWrapper(f, get_timeout, get_retries, pass_values=True)
- return decorator
-
-
-def WithTimeoutAndRetriesFromInstance(
- default_timeout_name=DEFAULT_TIMEOUT_ATTR,
- default_retries_name=DEFAULT_RETRIES_ATTR,
- min_default_timeout=None):
- """Returns a decorator that handles timeouts and retries.
-
- The provided |default_timeout_name| and |default_retries_name| are used to
- get the default timeout value and the default retries value from the object
- instance if timeout and retries values are not provided.
-
- Note that this should only be used to decorate methods, not functions.
-
- Args:
- default_timeout_name: The name of the default timeout attribute of the
- instance.
- default_retries_name: The name of the default retries attribute of the
- instance.
- min_timeout: Miniumum timeout to be used when using instance timeout.
- Returns:
- The actual decorator.
- """
- def decorator(f):
- def get_timeout(inst, *_args, **kwargs):
- ret = getattr(inst, default_timeout_name)
- if min_default_timeout is not None:
- ret = max(min_default_timeout, ret)
- return kwargs.get('timeout', ret)
-
- def get_retries(inst, *_args, **kwargs):
- return kwargs.get('retries', getattr(inst, default_retries_name))
- return _TimeoutRetryWrapper(f, get_timeout, get_retries, pass_values=True)
- return decorator
-
diff --git a/third_party/catapult/devil/devil/android/decorators_test.py b/third_party/catapult/devil/devil/android/decorators_test.py
deleted file mode 100644
index f60953e1f2..0000000000
--- a/third_party/catapult/devil/devil/android/decorators_test.py
+++ /dev/null
@@ -1,332 +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.
-
-"""
-Unit tests for decorators.py.
-"""
-
-# pylint: disable=W0613
-
-import time
-import traceback
-import unittest
-
-from devil.android import decorators
-from devil.android import device_errors
-from devil.utils import reraiser_thread
-
-_DEFAULT_TIMEOUT = 30
-_DEFAULT_RETRIES = 3
-
-
-class DecoratorsTest(unittest.TestCase):
- _decorated_function_called_count = 0
-
- def testFunctionDecoratorDoesTimeouts(self):
- """Tests that the base decorator handles the timeout logic."""
- DecoratorsTest._decorated_function_called_count = 0
-
- @decorators.WithTimeoutAndRetries
- def alwaysTimesOut(timeout=None, retries=None):
- DecoratorsTest._decorated_function_called_count += 1
- time.sleep(100)
-
- start_time = time.time()
- with self.assertRaises(device_errors.CommandTimeoutError):
- alwaysTimesOut(timeout=1, retries=0)
- elapsed_time = time.time() - start_time
- self.assertTrue(elapsed_time >= 1)
- self.assertEquals(1, DecoratorsTest._decorated_function_called_count)
-
- def testFunctionDecoratorDoesRetries(self):
- """Tests that the base decorator handles the retries logic."""
- DecoratorsTest._decorated_function_called_count = 0
-
- @decorators.WithTimeoutAndRetries
- def alwaysRaisesCommandFailedError(timeout=None, retries=None):
- DecoratorsTest._decorated_function_called_count += 1
- raise device_errors.CommandFailedError('testCommand failed')
-
- with self.assertRaises(device_errors.CommandFailedError):
- alwaysRaisesCommandFailedError(timeout=30, retries=10)
- self.assertEquals(11, DecoratorsTest._decorated_function_called_count)
-
- def testFunctionDecoratorRequiresParams(self):
- """Tests that the base decorator requires timeout and retries params."""
- @decorators.WithTimeoutAndRetries
- def requiresExplicitTimeoutAndRetries(timeout=None, retries=None):
- return (timeout, retries)
-
- with self.assertRaises(KeyError):
- requiresExplicitTimeoutAndRetries()
- with self.assertRaises(KeyError):
- requiresExplicitTimeoutAndRetries(timeout=10)
- with self.assertRaises(KeyError):
- requiresExplicitTimeoutAndRetries(retries=0)
- expected_timeout = 10
- expected_retries = 1
- (actual_timeout, actual_retries) = (
- requiresExplicitTimeoutAndRetries(timeout=expected_timeout,
- retries=expected_retries))
- self.assertEquals(expected_timeout, actual_timeout)
- self.assertEquals(expected_retries, actual_retries)
-
- def testFunctionDecoratorTranslatesReraiserExceptions(self):
- """Tests that the explicit decorator translates reraiser exceptions."""
- @decorators.WithTimeoutAndRetries
- def alwaysRaisesProvidedException(exception, timeout=None, retries=None):
- raise exception
-
- exception_desc = 'Reraiser thread timeout error'
- with self.assertRaises(device_errors.CommandTimeoutError) as e:
- alwaysRaisesProvidedException(
- reraiser_thread.TimeoutError(exception_desc),
- timeout=10, retries=1)
- self.assertEquals(exception_desc, str(e.exception))
-
- def testConditionalRetriesDecoratorRetries(self):
- def do_not_retry_no_adb_error(exc):
- return not isinstance(exc, device_errors.NoAdbError)
-
- actual_tries = [0]
-
- @decorators.WithTimeoutAndConditionalRetries(do_not_retry_no_adb_error)
- def alwaysRaisesCommandFailedError(timeout=None, retries=None):
- actual_tries[0] += 1
- raise device_errors.CommandFailedError('Command failed :(')
-
- with self.assertRaises(device_errors.CommandFailedError):
- alwaysRaisesCommandFailedError(timeout=10, retries=10)
- self.assertEquals(11, actual_tries[0])
-
- def testConditionalRetriesDecoratorDoesntRetry(self):
- def do_not_retry_no_adb_error(exc):
- return not isinstance(exc, device_errors.NoAdbError)
-
- actual_tries = [0]
-
- @decorators.WithTimeoutAndConditionalRetries(do_not_retry_no_adb_error)
- def alwaysRaisesNoAdbError(timeout=None, retries=None):
- actual_tries[0] += 1
- raise device_errors.NoAdbError()
-
- with self.assertRaises(device_errors.NoAdbError):
- alwaysRaisesNoAdbError(timeout=10, retries=10)
- self.assertEquals(1, actual_tries[0])
-
- def testDefaultsFunctionDecoratorDoesTimeouts(self):
- """Tests that the defaults decorator handles timeout logic."""
- DecoratorsTest._decorated_function_called_count = 0
-
- @decorators.WithTimeoutAndRetriesDefaults(1, 0)
- def alwaysTimesOut(timeout=None, retries=None):
- DecoratorsTest._decorated_function_called_count += 1
- time.sleep(100)
-
- start_time = time.time()
- with self.assertRaises(device_errors.CommandTimeoutError):
- alwaysTimesOut()
- elapsed_time = time.time() - start_time
- self.assertTrue(elapsed_time >= 1)
- self.assertEquals(1, DecoratorsTest._decorated_function_called_count)
-
- DecoratorsTest._decorated_function_called_count = 0
- with self.assertRaises(device_errors.CommandTimeoutError):
- alwaysTimesOut(timeout=2)
- elapsed_time = time.time() - start_time
- self.assertTrue(elapsed_time >= 2)
- self.assertEquals(1, DecoratorsTest._decorated_function_called_count)
-
- def testDefaultsFunctionDecoratorDoesRetries(self):
- """Tests that the defaults decorator handles retries logic."""
- DecoratorsTest._decorated_function_called_count = 0
-
- @decorators.WithTimeoutAndRetriesDefaults(30, 10)
- def alwaysRaisesCommandFailedError(timeout=None, retries=None):
- DecoratorsTest._decorated_function_called_count += 1
- raise device_errors.CommandFailedError('testCommand failed')
-
- with self.assertRaises(device_errors.CommandFailedError):
- alwaysRaisesCommandFailedError()
- self.assertEquals(11, DecoratorsTest._decorated_function_called_count)
-
- DecoratorsTest._decorated_function_called_count = 0
- with self.assertRaises(device_errors.CommandFailedError):
- alwaysRaisesCommandFailedError(retries=5)
- self.assertEquals(6, DecoratorsTest._decorated_function_called_count)
-
- def testDefaultsFunctionDecoratorPassesValues(self):
- """Tests that the defaults decorator passes timeout and retries kwargs."""
- @decorators.WithTimeoutAndRetriesDefaults(30, 10)
- def alwaysReturnsTimeouts(timeout=None, retries=None):
- return timeout
-
- self.assertEquals(30, alwaysReturnsTimeouts())
- self.assertEquals(120, alwaysReturnsTimeouts(timeout=120))
-
- @decorators.WithTimeoutAndRetriesDefaults(30, 10)
- def alwaysReturnsRetries(timeout=None, retries=None):
- return retries
-
- self.assertEquals(10, alwaysReturnsRetries())
- self.assertEquals(1, alwaysReturnsRetries(retries=1))
-
- def testDefaultsFunctionDecoratorTranslatesReraiserExceptions(self):
- """Tests that the explicit decorator translates reraiser exceptions."""
- @decorators.WithTimeoutAndRetriesDefaults(30, 10)
- def alwaysRaisesProvidedException(exception, timeout=None, retries=None):
- raise exception
-
- exception_desc = 'Reraiser thread timeout error'
- with self.assertRaises(device_errors.CommandTimeoutError) as e:
- alwaysRaisesProvidedException(
- reraiser_thread.TimeoutError(exception_desc))
- self.assertEquals(exception_desc, str(e.exception))
-
- def testExplicitFunctionDecoratorDoesTimeouts(self):
- """Tests that the explicit decorator handles timeout logic."""
- DecoratorsTest._decorated_function_called_count = 0
-
- @decorators.WithExplicitTimeoutAndRetries(1, 0)
- def alwaysTimesOut():
- DecoratorsTest._decorated_function_called_count += 1
- time.sleep(100)
-
- start_time = time.time()
- with self.assertRaises(device_errors.CommandTimeoutError):
- alwaysTimesOut()
- elapsed_time = time.time() - start_time
- self.assertTrue(elapsed_time >= 1)
- self.assertEquals(1, DecoratorsTest._decorated_function_called_count)
-
- def testExplicitFunctionDecoratorDoesRetries(self):
- """Tests that the explicit decorator handles retries logic."""
- DecoratorsTest._decorated_function_called_count = 0
-
- @decorators.WithExplicitTimeoutAndRetries(30, 10)
- def alwaysRaisesCommandFailedError():
- DecoratorsTest._decorated_function_called_count += 1
- raise device_errors.CommandFailedError('testCommand failed')
-
- with self.assertRaises(device_errors.CommandFailedError):
- alwaysRaisesCommandFailedError()
- self.assertEquals(11, DecoratorsTest._decorated_function_called_count)
-
- def testExplicitDecoratorTranslatesReraiserExceptions(self):
- """Tests that the explicit decorator translates reraiser exceptions."""
- @decorators.WithExplicitTimeoutAndRetries(30, 10)
- def alwaysRaisesProvidedException(exception):
- raise exception
-
- exception_desc = 'Reraiser thread timeout error'
- with self.assertRaises(device_errors.CommandTimeoutError) as e:
- alwaysRaisesProvidedException(
- reraiser_thread.TimeoutError(exception_desc))
- self.assertEquals(exception_desc, str(e.exception))
-
- class _MethodDecoratorTestObject(object):
- """An object suitable for testing the method decorator."""
-
- def __init__(self, test_case, default_timeout=_DEFAULT_TIMEOUT,
- default_retries=_DEFAULT_RETRIES):
- self._test_case = test_case
- self.default_timeout = default_timeout
- self.default_retries = default_retries
- self.function_call_counters = {
- 'alwaysRaisesCommandFailedError': 0,
- 'alwaysTimesOut': 0,
- 'requiresExplicitTimeoutAndRetries': 0,
- }
-
- @decorators.WithTimeoutAndRetriesFromInstance(
- 'default_timeout', 'default_retries')
- def alwaysTimesOut(self, timeout=None, retries=None):
- self.function_call_counters['alwaysTimesOut'] += 1
- time.sleep(100)
- self._test_case.assertFalse(True, msg='Failed to time out?')
-
- @decorators.WithTimeoutAndRetriesFromInstance(
- 'default_timeout', 'default_retries')
- def alwaysRaisesCommandFailedError(self, timeout=None, retries=None):
- self.function_call_counters['alwaysRaisesCommandFailedError'] += 1
- raise device_errors.CommandFailedError('testCommand failed')
-
- # pylint: disable=no-self-use
-
- @decorators.WithTimeoutAndRetriesFromInstance(
- 'default_timeout', 'default_retries')
- def alwaysReturnsTimeout(self, timeout=None, retries=None):
- return timeout
-
- @decorators.WithTimeoutAndRetriesFromInstance(
- 'default_timeout', 'default_retries', min_default_timeout=100)
- def alwaysReturnsTimeoutWithMin(self, timeout=None, retries=None):
- return timeout
-
- @decorators.WithTimeoutAndRetriesFromInstance(
- 'default_timeout', 'default_retries')
- def alwaysReturnsRetries(self, timeout=None, retries=None):
- return retries
-
- @decorators.WithTimeoutAndRetriesFromInstance(
- 'default_timeout', 'default_retries')
- def alwaysRaisesProvidedException(self, exception, timeout=None,
- retries=None):
- raise exception
-
- # pylint: enable=no-self-use
-
- def testMethodDecoratorDoesTimeout(self):
- """Tests that the method decorator handles timeout logic."""
- test_obj = self._MethodDecoratorTestObject(self)
- start_time = time.time()
- with self.assertRaises(device_errors.CommandTimeoutError):
- try:
- test_obj.alwaysTimesOut(timeout=1, retries=0)
- except:
- traceback.print_exc()
- raise
- elapsed_time = time.time() - start_time
- self.assertTrue(elapsed_time >= 1)
- self.assertEquals(1, test_obj.function_call_counters['alwaysTimesOut'])
-
- def testMethodDecoratorDoesRetries(self):
- """Tests that the method decorator handles retries logic."""
- test_obj = self._MethodDecoratorTestObject(self)
- with self.assertRaises(device_errors.CommandFailedError):
- try:
- test_obj.alwaysRaisesCommandFailedError(retries=10)
- except:
- traceback.print_exc()
- raise
- self.assertEquals(
- 11, test_obj.function_call_counters['alwaysRaisesCommandFailedError'])
-
- def testMethodDecoratorPassesValues(self):
- """Tests that the method decorator passes timeout and retries kwargs."""
- test_obj = self._MethodDecoratorTestObject(
- self, default_timeout=42, default_retries=31)
- self.assertEquals(42, test_obj.alwaysReturnsTimeout())
- self.assertEquals(41, test_obj.alwaysReturnsTimeout(timeout=41))
- self.assertEquals(31, test_obj.alwaysReturnsRetries())
- self.assertEquals(32, test_obj.alwaysReturnsRetries(retries=32))
-
- def testMethodDecoratorUsesMiniumumTimeout(self):
- test_obj = self._MethodDecoratorTestObject(
- self, default_timeout=42, default_retries=31)
- self.assertEquals(100, test_obj.alwaysReturnsTimeoutWithMin())
- self.assertEquals(41, test_obj.alwaysReturnsTimeoutWithMin(timeout=41))
-
- def testMethodDecoratorTranslatesReraiserExceptions(self):
- test_obj = self._MethodDecoratorTestObject(self)
-
- exception_desc = 'Reraiser thread timeout error'
- with self.assertRaises(device_errors.CommandTimeoutError) as e:
- test_obj.alwaysRaisesProvidedException(
- reraiser_thread.TimeoutError(exception_desc))
- self.assertEquals(exception_desc, str(e.exception))
-
-if __name__ == '__main__':
- unittest.main(verbosity=2)
-
diff --git a/third_party/catapult/devil/devil/android/device_blacklist.py b/third_party/catapult/devil/devil/android/device_blacklist.py
deleted file mode 100644
index 010e99652f..0000000000
--- a/third_party/catapult/devil/devil/android/device_blacklist.py
+++ /dev/null
@@ -1,80 +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.
-
-import json
-import logging
-import os
-import threading
-import time
-
-logger = logging.getLogger(__name__)
-
-
-class Blacklist(object):
-
- def __init__(self, path):
- self._blacklist_lock = threading.RLock()
- self._path = path
-
- def Read(self):
- """Reads the blacklist from the blacklist file.
-
- Returns:
- A dict containing bad devices.
- """
- with self._blacklist_lock:
- blacklist = dict()
- if not os.path.exists(self._path):
- return blacklist
-
- try:
- with open(self._path, 'r') as f:
- blacklist = json.load(f)
- except (IOError, ValueError) as e:
- logger.warning('Unable to read blacklist: %s', str(e))
- os.remove(self._path)
-
- if not isinstance(blacklist, dict):
- logger.warning('Ignoring %s: %s (a dict was expected instead)',
- self._path, blacklist)
- blacklist = dict()
-
- return blacklist
-
- def Write(self, blacklist):
- """Writes the provided blacklist to the blacklist file.
-
- Args:
- blacklist: list of bad devices to write to the blacklist file.
- """
- with self._blacklist_lock:
- with open(self._path, 'w') as f:
- json.dump(blacklist, f)
-
- def Extend(self, devices, reason='unknown'):
- """Adds devices to blacklist file.
-
- Args:
- devices: list of bad devices to be added to the blacklist file.
- reason: string specifying the reason for blacklist (eg: 'unauthorized')
- """
- timestamp = time.time()
- event_info = {
- 'timestamp': timestamp,
- 'reason': reason,
- }
- device_dicts = {device: event_info for device in devices}
- logger.info('Adding %s to blacklist %s for reason: %s',
- ','.join(devices), self._path, reason)
- with self._blacklist_lock:
- blacklist = self.Read()
- blacklist.update(device_dicts)
- self.Write(blacklist)
-
- def Reset(self):
- """Erases the blacklist file if it exists."""
- logger.info('Resetting blacklist %s', self._path)
- with self._blacklist_lock:
- if os.path.exists(self._path):
- os.remove(self._path)
diff --git a/third_party/catapult/devil/devil/android/device_blacklist_test.py b/third_party/catapult/devil/devil/android/device_blacklist_test.py
deleted file mode 100644
index bc44da557b..0000000000
--- a/third_party/catapult/devil/devil/android/device_blacklist_test.py
+++ /dev/null
@@ -1,38 +0,0 @@
-#! /usr/bin/env python
-# 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 os
-import tempfile
-import unittest
-
-from devil.android import device_blacklist
-
-
-class DeviceBlacklistTest(unittest.TestCase):
-
- def testBlacklistFileDoesNotExist(self):
- with tempfile.NamedTemporaryFile() as blacklist_file:
- # Allow the temporary file to be deleted.
- pass
-
- test_blacklist = device_blacklist.Blacklist(blacklist_file.name)
- self.assertEquals({}, test_blacklist.Read())
-
- def testBlacklistFileIsEmpty(self):
- try:
- with tempfile.NamedTemporaryFile(delete=False) as blacklist_file:
- # Allow the temporary file to be closed.
- pass
-
- test_blacklist = device_blacklist.Blacklist(blacklist_file.name)
- self.assertEquals({}, test_blacklist.Read())
-
- finally:
- if os.path.exists(blacklist_file.name):
- os.remove(blacklist_file.name)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/third_party/catapult/devil/devil/android/device_errors.py b/third_party/catapult/devil/devil/android/device_errors.py
deleted file mode 100644
index 568e497496..0000000000
--- a/third_party/catapult/devil/devil/android/device_errors.py
+++ /dev/null
@@ -1,180 +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.
-
-"""
-Exception classes raised by AdbWrapper and DeviceUtils.
-"""
-
-from devil import base_error
-from devil.utils import cmd_helper
-from devil.utils import parallelizer
-
-
-class CommandFailedError(base_error.BaseError):
- """Exception for command failures."""
-
- def __init__(self, message, device_serial=None):
- device_leader = '(device: %s)' % device_serial
- if device_serial is not None and not message.startswith(device_leader):
- message = '%s %s' % (device_leader, message)
- self.device_serial = device_serial
- super(CommandFailedError, self).__init__(message)
-
- def __eq__(self, other):
- return (super(CommandFailedError, self).__eq__(other)
- and self.device_serial == other.device_serial)
-
- def __ne__(self, other):
- return not self == other
-
-
-class _BaseCommandFailedError(CommandFailedError):
- """Base Exception for adb and fastboot command failures."""
-
- def __init__(self, args, output, status=None, device_serial=None,
- message=None):
- self.args = args
- self.output = output
- self.status = status
- if not message:
- adb_cmd = ' '.join(cmd_helper.SingleQuote(arg) for arg in self.args)
- message = ['adb %s: failed ' % adb_cmd]
- if status:
- message.append('with exit status %s ' % self.status)
- if output:
- message.append('and output:\n')
- message.extend('- %s\n' % line for line in output.splitlines())
- else:
- message.append('and no output.')
- message = ''.join(message)
- super(_BaseCommandFailedError, self).__init__(message, device_serial)
-
- def __eq__(self, other):
- return (super(_BaseCommandFailedError, self).__eq__(other)
- and self.args == other.args
- and self.output == other.output
- and self.status == other.status)
-
- def __ne__(self, other):
- return not self == other
-
- def __reduce__(self):
- """Support pickling."""
- result = [None, None, None, None, None]
- super_result = super(_BaseCommandFailedError, self).__reduce__()
- for i in range(len(super_result)):
- result[i] = super_result[i]
-
- # Update the args used to reconstruct this exception.
- result[1] = (
- self.args, self.output, self.status, self.device_serial, self.message)
- return tuple(result)
-
-
-class AdbCommandFailedError(_BaseCommandFailedError):
- """Exception for adb command failures."""
-
- def __init__(self, args, output, status=None, device_serial=None,
- message=None):
- super(AdbCommandFailedError, self).__init__(
- args, output, status=status, message=message,
- device_serial=device_serial)
-
-
-class FastbootCommandFailedError(_BaseCommandFailedError):
- """Exception for fastboot command failures."""
-
- def __init__(self, args, output, status=None, device_serial=None,
- message=None):
- super(FastbootCommandFailedError, self).__init__(
- args, output, status=status, message=message,
- device_serial=device_serial)
-
-
-class DeviceVersionError(CommandFailedError):
- """Exception for device version failures."""
-
- def __init__(self, message, device_serial=None):
- super(DeviceVersionError, self).__init__(message, device_serial)
-
-
-class AdbShellCommandFailedError(AdbCommandFailedError):
- """Exception for shell command failures run via adb."""
-
- def __init__(self, command, output, status, device_serial=None):
- self.command = command
- message = ['shell command run via adb failed on the device:\n',
- ' command: %s\n' % command]
- message.append(' exit status: %s\n' % status)
- if output:
- message.append(' output:\n')
- if isinstance(output, basestring):
- output_lines = output.splitlines()
- else:
- output_lines = output
- message.extend(' - %s\n' % line for line in output_lines)
- else:
- message.append(" output: ''\n")
- message = ''.join(message)
- super(AdbShellCommandFailedError, self).__init__(
- ['shell', command], output, status, device_serial, message)
-
- def __reduce__(self):
- """Support pickling."""
- result = [None, None, None, None, None]
- super_result = super(AdbShellCommandFailedError, self).__reduce__()
- for i in range(len(super_result)):
- result[i] = super_result[i]
-
- # Update the args used to reconstruct this exception.
- result[1] = (self.command, self.output, self.status, self.device_serial)
- return tuple(result)
-
-
-class CommandTimeoutError(base_error.BaseError):
- """Exception for command timeouts."""
- pass
-
-
-class DeviceUnreachableError(base_error.BaseError):
- """Exception for device unreachable failures."""
- pass
-
-
-class NoDevicesError(base_error.BaseError):
- """Exception for having no devices attached."""
-
- def __init__(self, msg=None):
- super(NoDevicesError, self).__init__(
- msg or 'No devices attached.', is_infra_error=True)
-
-
-class MultipleDevicesError(base_error.BaseError):
- """Exception for having multiple attached devices without selecting one."""
-
- def __init__(self, devices):
- parallel_devices = parallelizer.Parallelizer(devices)
- descriptions = parallel_devices.pMap(
- lambda d: d.build_description).pGet(None)
- msg = ('More than one device available. Use -d/--device to select a device '
- 'by serial.\n\nAvailable devices:\n')
- for d, desc in zip(devices, descriptions):
- msg += ' %s (%s)\n' % (d, desc)
-
- super(MultipleDevicesError, self).__init__(msg, is_infra_error=True)
-
-
-class NoAdbError(base_error.BaseError):
- """Exception for being unable to find ADB."""
-
- def __init__(self, msg=None):
- super(NoAdbError, self).__init__(
- msg or 'Unable to find adb.', is_infra_error=True)
-
-
-class DeviceChargingError(CommandFailedError):
- """Exception for device charging errors."""
-
- def __init__(self, message, device_serial=None):
- super(DeviceChargingError, self).__init__(message, device_serial)
diff --git a/third_party/catapult/devil/devil/android/device_errors_test.py b/third_party/catapult/devil/devil/android/device_errors_test.py
deleted file mode 100755
index 68a4f16770..0000000000
--- a/third_party/catapult/devil/devil/android/device_errors_test.py
+++ /dev/null
@@ -1,72 +0,0 @@
-#! /usr/bin/env python
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import pickle
-import sys
-import unittest
-
-from devil.android import device_errors
-
-
-class DeviceErrorsTest(unittest.TestCase):
-
- def assertIsPicklable(self, original):
- pickled = pickle.dumps(original)
- reconstructed = pickle.loads(pickled)
- self.assertEquals(original, reconstructed)
-
- def testPicklable_AdbCommandFailedError(self):
- original = device_errors.AdbCommandFailedError(
- ['these', 'are', 'adb', 'args'], 'adb failure output', status=':(',
- device_serial='0123456789abcdef')
- self.assertIsPicklable(original)
-
- def testPicklable_AdbShellCommandFailedError(self):
- original = device_errors.AdbShellCommandFailedError(
- 'foo', 'erroneous foo output', '1', device_serial='0123456789abcdef')
- self.assertIsPicklable(original)
-
- def testPicklable_CommandFailedError(self):
- original = device_errors.CommandFailedError(
- 'sample command failed')
- self.assertIsPicklable(original)
-
- def testPicklable_CommandTimeoutError(self):
- original = device_errors.CommandTimeoutError(
- 'My fake command timed out :(')
- self.assertIsPicklable(original)
-
- def testPicklable_DeviceChargingError(self):
- original = device_errors.DeviceChargingError(
- 'Fake device failed to charge')
- self.assertIsPicklable(original)
-
- def testPicklable_DeviceUnreachableError(self):
- original = device_errors.DeviceUnreachableError
- self.assertIsPicklable(original)
-
- def testPicklable_FastbootCommandFailedError(self):
- original = device_errors.FastbootCommandFailedError(
- ['these', 'are', 'fastboot', 'args'], 'fastboot failure output',
- status=':(', device_serial='0123456789abcdef')
- self.assertIsPicklable(original)
-
- def testPicklable_MultipleDevicesError(self):
- # TODO(jbudorick): Implement this after implementing a stable DeviceUtils
- # fake. https://github.com/catapult-project/catapult/issues/3145
- pass
-
- def testPicklable_NoAdbError(self):
- original = device_errors.NoAdbError()
- self.assertIsPicklable(original)
-
- def testPicklable_NoDevicesError(self):
- original = device_errors.NoDevicesError()
- self.assertIsPicklable(original)
-
-
-
-if __name__ == '__main__':
- sys.exit(unittest.main())
diff --git a/third_party/catapult/devil/devil/android/device_list.py b/third_party/catapult/devil/devil/android/device_list.py
deleted file mode 100644
index 0fbb0f151a..0000000000
--- a/third_party/catapult/devil/devil/android/device_list.py
+++ /dev/null
@@ -1,52 +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.
-
-"""A module to keep track of devices across builds."""
-
-import json
-import logging
-import os
-
-logger = logging.getLogger(__name__)
-
-
-def GetPersistentDeviceList(file_name):
- """Returns a list of devices.
-
- Args:
- file_name: the file name containing a list of devices.
-
- Returns: List of device serial numbers that were on the bot.
- """
- if not os.path.isfile(file_name):
- logger.warning("Device file %s doesn't exist.", file_name)
- return []
-
- try:
- with open(file_name) as f:
- devices = json.load(f)
- if not isinstance(devices, list) or not all(isinstance(d, basestring)
- for d in devices):
- logger.warning('Unrecognized device file format: %s', devices)
- return []
- return [d for d in devices if d != '(error)']
- except ValueError:
- logger.exception(
- 'Error reading device file %s. Falling back to old format.', file_name)
-
- # TODO(bpastene) Remove support for old unstructured file format.
- with open(file_name) as f:
- return [d for d in f.read().splitlines() if d != '(error)']
-
-
-def WritePersistentDeviceList(file_name, device_list):
- path = os.path.dirname(file_name)
- assert isinstance(device_list, list)
- # If there is a problem with ADB "(error)" can be added to the device list.
- # These should be removed before saving.
- device_list = [d for d in device_list if d != '(error)']
- if not os.path.exists(path):
- os.makedirs(path)
- with open(file_name, 'w') as f:
- json.dump(device_list, f)
diff --git a/third_party/catapult/devil/devil/android/device_signal.py b/third_party/catapult/devil/devil/android/device_signal.py
deleted file mode 100644
index 2cec46d7d2..0000000000
--- a/third_party/catapult/devil/devil/android/device_signal.py
+++ /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.
-
-"""Defines constants for signals that should be supported on devices.
-
-Note: Obtained by running `kill -l` on a user device.
-"""
-
-
-SIGHUP = 1 # Hangup
-SIGINT = 2 # Interrupt
-SIGQUIT = 3 # Quit
-SIGILL = 4 # Illegal instruction
-SIGTRAP = 5 # Trap
-SIGABRT = 6 # Aborted
-SIGBUS = 7 # Bus error
-SIGFPE = 8 # Floating point exception
-SIGKILL = 9 # Killed
-SIGUSR1 = 10 # User signal 1
-SIGSEGV = 11 # Segmentation fault
-SIGUSR2 = 12 # User signal 2
-SIGPIPE = 13 # Broken pipe
-SIGALRM = 14 # Alarm clock
-SIGTERM = 15 # Terminated
-SIGSTKFLT = 16 # Stack fault
-SIGCHLD = 17 # Child exited
-SIGCONT = 18 # Continue
-SIGSTOP = 19 # Stopped (signal)
-SIGTSTP = 20 # Stopped
-SIGTTIN = 21 # Stopped (tty input)
-SIGTTOU = 22 # Stopped (tty output)
-SIGURG = 23 # Urgent I/O condition
-SIGXCPU = 24 # CPU time limit exceeded
-SIGXFSZ = 25 # File size limit exceeded
-SIGVTALRM = 26 # Virtual timer expired
-SIGPROF = 27 # Profiling timer expired
-SIGWINCH = 28 # Window size changed
-SIGIO = 29 # I/O possible
-SIGPWR = 30 # Power failure
-SIGSYS = 31 # Bad system call
diff --git a/third_party/catapult/devil/devil/android/device_temp_file.py b/third_party/catapult/devil/devil/android/device_temp_file.py
deleted file mode 100644
index 4d0c7adb5c..0000000000
--- a/third_party/catapult/devil/devil/android/device_temp_file.py
+++ /dev/null
@@ -1,63 +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.
-
-"""A temp file that automatically gets pushed and deleted from a device."""
-
-# pylint: disable=W0622
-
-import posixpath
-import random
-import threading
-
-from devil.android import device_errors
-from devil.utils import cmd_helper
-
-
-class DeviceTempFile(object):
-
- def __init__(self, adb, suffix='', prefix='temp_file', dir='/data/local/tmp'):
- """Find an unused temporary file path on the device.
-
- When this object is closed, the file will be deleted on the device.
-
- Args:
- adb: An instance of AdbWrapper
- suffix: The suffix of the name of the temp file.
- prefix: The prefix of the name of the temp file.
- dir: The directory on the device where to place the temp file.
- Raises:
- ValueError if any of suffix, prefix, or dir are None.
- """
- if None in (dir, prefix, suffix):
- m = 'Provided None path component. (dir: %s, prefix: %s, suffix: %s)' % (
- dir, prefix, suffix)
- raise ValueError(m)
-
- self._adb = adb
- # Python's random module use 52-bit numbers according to its docs.
- random_hex = hex(random.randint(0, 2 ** 52))[2:]
- self.name = posixpath.join(dir, '%s-%s%s' % (prefix, random_hex, suffix))
- self.name_quoted = cmd_helper.SingleQuote(self.name)
-
- def close(self):
- """Deletes the temporary file from the device."""
- # ignore exception if the file is already gone.
- def delete_temporary_file():
- try:
- self._adb.Shell('rm -f %s' % self.name_quoted, expect_status=None)
- except device_errors.AdbCommandFailedError:
- # file does not exist on Android version without 'rm -f' support (ICS)
- pass
-
- # It shouldn't matter when the temp file gets deleted, so do so
- # asynchronously.
- threading.Thread(
- target=delete_temporary_file,
- name='delete_temporary_file(%s)' % self._adb.GetDeviceSerial()).start()
-
- def __enter__(self):
- return self
-
- def __exit__(self, type, value, traceback):
- self.close()
diff --git a/third_party/catapult/devil/devil/android/device_test_case.py b/third_party/catapult/devil/devil/android/device_test_case.py
deleted file mode 100644
index b995fa6f94..0000000000
--- a/third_party/catapult/devil/devil/android/device_test_case.py
+++ /dev/null
@@ -1,54 +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.
-
-import threading
-import unittest
-
-from devil.android import device_errors
-from devil.android import device_utils
-
-_devices_lock = threading.Lock()
-_devices_condition = threading.Condition(_devices_lock)
-_devices = set()
-
-
-def PrepareDevices(*_args):
-
- raw_devices = device_utils.DeviceUtils.HealthyDevices()
- live_devices = []
- for d in raw_devices:
- try:
- d.WaitUntilFullyBooted(timeout=5, retries=0)
- live_devices.append(str(d))
- except (device_errors.CommandFailedError,
- device_errors.CommandTimeoutError):
- pass
- with _devices_lock:
- _devices.update(set(live_devices))
-
- if not _devices:
- raise Exception('No live devices attached.')
-
-
-class DeviceTestCase(unittest.TestCase):
-
- def __init__(self, *args, **kwargs):
- super(DeviceTestCase, self).__init__(*args, **kwargs)
- self.serial = None
-
- #override
- def setUp(self):
- super(DeviceTestCase, self).setUp()
- with _devices_lock:
- while not _devices:
- _devices_condition.wait(5)
- self.serial = _devices.pop()
-
- #override
- def tearDown(self):
- super(DeviceTestCase, self).tearDown()
- with _devices_lock:
- _devices.add(self.serial)
- _devices_condition.notify()
-
diff --git a/third_party/catapult/devil/devil/android/device_utils.py b/third_party/catapult/devil/devil/android/device_utils.py
deleted file mode 100644
index 50f362c383..0000000000
--- a/third_party/catapult/devil/devil/android/device_utils.py
+++ /dev/null
@@ -1,2640 +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.
-
-"""Provides a variety of device interactions based on adb.
-
-Eventually, this will be based on adb_wrapper.
-"""
-# pylint: disable=unused-argument
-
-import calendar
-import collections
-import itertools
-import json
-import logging
-import multiprocessing
-import os
-import posixpath
-import pprint
-import re
-import shutil
-import stat
-import tempfile
-import time
-import threading
-import uuid
-import zipfile
-
-from devil import base_error
-from devil import devil_env
-from devil.utils import cmd_helper
-from devil.android import apk_helper
-from devil.android import device_signal
-from devil.android import decorators
-from devil.android import device_errors
-from devil.android import device_temp_file
-from devil.android import install_commands
-from devil.android import logcat_monitor
-from devil.android import md5sum
-from devil.android.constants import chrome
-from devil.android.sdk import adb_wrapper
-from devil.android.sdk import intent
-from devil.android.sdk import keyevent
-from devil.android.sdk import split_select
-from devil.android.sdk import version_codes
-from devil.utils import host_utils
-from devil.utils import parallelizer
-from devil.utils import reraiser_thread
-from devil.utils import timeout_retry
-from devil.utils import zip_utils
-
-logger = logging.getLogger(__name__)
-
-_DEFAULT_TIMEOUT = 30
-_DEFAULT_RETRIES = 3
-
-# A sentinel object for default values
-# TODO(jbudorick,perezju): revisit how default values are handled by
-# the timeout_retry decorators.
-DEFAULT = object()
-
-_RESTART_ADBD_SCRIPT = """
- trap '' HUP
- trap '' TERM
- trap '' PIPE
- function restart() {
- stop adbd
- start adbd
- }
- restart &
-"""
-
-# Not all permissions can be set.
-_PERMISSIONS_BLACKLIST = [
- 'android.permission.ACCESS_LOCATION_EXTRA_COMMANDS',
- 'android.permission.ACCESS_MOCK_LOCATION',
- 'android.permission.ACCESS_NETWORK_STATE',
- 'android.permission.ACCESS_NOTIFICATION_POLICY',
- 'android.permission.ACCESS_WIFI_STATE',
- 'android.permission.AUTHENTICATE_ACCOUNTS',
- 'android.permission.BLUETOOTH',
- 'android.permission.BLUETOOTH_ADMIN',
- 'android.permission.BROADCAST_STICKY',
- 'android.permission.CHANGE_NETWORK_STATE',
- 'android.permission.CHANGE_WIFI_MULTICAST_STATE',
- 'android.permission.CHANGE_WIFI_STATE',
- 'android.permission.DISABLE_KEYGUARD',
- 'android.permission.DOWNLOAD_WITHOUT_NOTIFICATION',
- 'android.permission.EXPAND_STATUS_BAR',
- 'android.permission.GET_PACKAGE_SIZE',
- 'android.permission.INSTALL_SHORTCUT',
- 'android.permission.INTERNET',
- 'android.permission.KILL_BACKGROUND_PROCESSES',
- 'android.permission.MANAGE_ACCOUNTS',
- 'android.permission.MODIFY_AUDIO_SETTINGS',
- 'android.permission.NFC',
- 'android.permission.READ_SYNC_SETTINGS',
- 'android.permission.READ_SYNC_STATS',
- 'android.permission.RECEIVE_BOOT_COMPLETED',
- 'android.permission.RECORD_VIDEO',
- 'android.permission.REORDER_TASKS',
- 'android.permission.REQUEST_INSTALL_PACKAGES',
- 'android.permission.RUN_INSTRUMENTATION',
- 'android.permission.SET_ALARM',
- 'android.permission.SET_TIME_ZONE',
- 'android.permission.SET_WALLPAPER',
- 'android.permission.SET_WALLPAPER_HINTS',
- 'android.permission.TRANSMIT_IR',
- 'android.permission.USE_CREDENTIALS',
- 'android.permission.USE_FINGERPRINT',
- 'android.permission.VIBRATE',
- 'android.permission.WAKE_LOCK',
- 'android.permission.WRITE_SYNC_SETTINGS',
- 'com.android.browser.permission.READ_HISTORY_BOOKMARKS',
- 'com.android.browser.permission.WRITE_HISTORY_BOOKMARKS',
- 'com.android.launcher.permission.INSTALL_SHORTCUT',
- 'com.chrome.permission.DEVICE_EXTRAS',
- 'com.google.android.apps.now.CURRENT_ACCOUNT_ACCESS',
- 'com.google.android.c2dm.permission.RECEIVE',
- 'com.google.android.providers.gsf.permission.READ_GSERVICES',
- 'com.sec.enterprise.knox.MDM_CONTENT_PROVIDER',
-]
-for package_info in chrome.PACKAGE_INFO.itervalues():
- _PERMISSIONS_BLACKLIST.extend([
- '%s.permission.C2D_MESSAGE' % package_info.package,
- '%s.permission.READ_WRITE_BOOKMARK_FOLDERS' % package_info.package,
- '%s.TOS_ACKED' % package_info.package])
-
-_CURRENT_FOCUS_CRASH_RE = re.compile(
- r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}')
-
-_GETPROP_RE = re.compile(r'\[(.*?)\]: \[(.*?)\]')
-
-# Regex to parse the long (-l) output of 'ls' command, c.f.
-# https://github.com/landley/toybox/blob/master/toys/posix/ls.c#L446
-_LONG_LS_OUTPUT_RE = re.compile(
- r'(?P<st_mode>[\w-]{10})\s+' # File permissions
- r'(?:(?P<st_nlink>\d+)\s+)?' # Number of links (optional)
- r'(?P<st_owner>\w+)\s+' # Name of owner
- r'(?P<st_group>\w+)\s+' # Group of owner
- r'(?:' # Either ...
- r'(?P<st_rdev_major>\d+),\s+' # Device major, and
- r'(?P<st_rdev_minor>\d+)\s+' # Device minor
- r'|' # .. or
- r'(?P<st_size>\d+)\s+' # Size in bytes
- r')?' # .. or nothing
- r'(?P<st_mtime>\d{4}-\d\d-\d\d \d\d:\d\d)\s+' # Modification date/time
- r'(?P<filename>.+?)' # File name
- r'(?: -> (?P<symbolic_link_to>.+))?' # Symbolic link (optional)
- r'$' # End of string
-)
-_LS_DATE_FORMAT = '%Y-%m-%d %H:%M'
-_FILE_MODE_RE = re.compile(r'[dbclps-](?:[r-][w-][xSs-]){2}[r-][w-][xTt-]$')
-_FILE_MODE_KIND = {
- 'd': stat.S_IFDIR, 'b': stat.S_IFBLK, 'c': stat.S_IFCHR,
- 'l': stat.S_IFLNK, 'p': stat.S_IFIFO, 's': stat.S_IFSOCK,
- '-': stat.S_IFREG}
-_FILE_MODE_PERMS = [
- stat.S_IRUSR, stat.S_IWUSR, stat.S_IXUSR,
- stat.S_IRGRP, stat.S_IWGRP, stat.S_IXGRP,
- stat.S_IROTH, stat.S_IWOTH, stat.S_IXOTH,
-]
-_FILE_MODE_SPECIAL = [
- ('s', stat.S_ISUID),
- ('s', stat.S_ISGID),
- ('t', stat.S_ISVTX),
-]
-_SELINUX_MODE = {
- 'enforcing': True,
- 'permissive': False,
- 'disabled': None
-}
-# Some devices require different logic for checking if root is necessary
-_SPECIAL_ROOT_DEVICE_LIST = [
- 'marlin',
- 'sailfish',
-]
-
-
-@decorators.WithExplicitTimeoutAndRetries(
- _DEFAULT_TIMEOUT, _DEFAULT_RETRIES)
-def GetAVDs():
- """Returns a list of Android Virtual Devices.
-
- Returns:
- A list containing the configured AVDs.
- """
- lines = cmd_helper.GetCmdOutput([
- os.path.join(devil_env.config.LocalPath('android_sdk'),
- 'tools', 'android'),
- 'list', 'avd']).splitlines()
- avds = []
- for line in lines:
- if 'Name:' not in line:
- continue
- key, value = (s.strip() for s in line.split(':', 1))
- if key == 'Name':
- avds.append(value)
- return avds
-
-
-@decorators.WithExplicitTimeoutAndRetries(
- _DEFAULT_TIMEOUT, _DEFAULT_RETRIES)
-def RestartServer():
- """Restarts the adb server.
-
- Raises:
- CommandFailedError if we fail to kill or restart the server.
- """
- def adb_killed():
- return not adb_wrapper.AdbWrapper.IsServerOnline()
-
- def adb_started():
- return adb_wrapper.AdbWrapper.IsServerOnline()
-
- adb_wrapper.AdbWrapper.KillServer()
- if not timeout_retry.WaitFor(adb_killed, wait_period=1, max_tries=5):
- # TODO(perezju): raise an exception after fixng http://crbug.com/442319
- logger.warning('Failed to kill adb server')
- adb_wrapper.AdbWrapper.StartServer()
- if not timeout_retry.WaitFor(adb_started, wait_period=1, max_tries=5):
- raise device_errors.CommandFailedError('Failed to start adb server')
-
-
-def _ParseModeString(mode_str):
- """Parse a mode string, e.g. 'drwxrwxrwx', into a st_mode value.
-
- Effectively the reverse of |mode_to_string| in, e.g.:
- https://github.com/landley/toybox/blob/master/lib/lib.c#L896
- """
- if not _FILE_MODE_RE.match(mode_str):
- raise ValueError('Unexpected file mode %r', mode_str)
- mode = _FILE_MODE_KIND[mode_str[0]]
- for c, flag in zip(mode_str[1:], _FILE_MODE_PERMS):
- if c != '-' and c.islower():
- mode |= flag
- for c, (t, flag) in zip(mode_str[3::3], _FILE_MODE_SPECIAL):
- if c.lower() == t:
- mode |= flag
- return mode
-
-
-def _GetTimeStamp():
- """Return a basic ISO 8601 time stamp with the current local time."""
- return time.strftime('%Y%m%dT%H%M%S', time.localtime())
-
-
-def _JoinLines(lines):
- # makes sure that the last line is also terminated, and is more memory
- # efficient than first appending an end-line to each line and then joining
- # all of them together.
- return ''.join(s for line in lines for s in (line, '\n'))
-
-
-def _CreateAdbWrapper(device):
- if isinstance(device, adb_wrapper.AdbWrapper):
- return device
- else:
- return adb_wrapper.AdbWrapper(device)
-
-
-def _FormatPartialOutputError(output):
- lines = output.splitlines() if isinstance(output, basestring) else output
- message = ['Partial output found:']
- if len(lines) > 11:
- message.extend('- %s' % line for line in lines[:5])
- message.extend('<snip>')
- message.extend('- %s' % line for line in lines[-5:])
- else:
- message.extend('- %s' % line for line in lines)
- return '\n'.join(message)
-
-
-class DeviceUtils(object):
-
- _MAX_ADB_COMMAND_LENGTH = 512
- _MAX_ADB_OUTPUT_LENGTH = 32768
- _LAUNCHER_FOCUSED_RE = re.compile(
- r'\s*mCurrentFocus.*(Launcher|launcher).*')
- _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$')
-
- LOCAL_PROPERTIES_PATH = posixpath.join('/', 'data', 'local.prop')
-
- # Property in /data/local.prop that controls Java assertions.
- JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions'
-
- def __init__(self, device, enable_device_files_cache=False,
- default_timeout=_DEFAULT_TIMEOUT,
- default_retries=_DEFAULT_RETRIES):
- """DeviceUtils constructor.
-
- Args:
- device: Either a device serial, an existing AdbWrapper instance, or an
- an existing AndroidCommands instance.
- enable_device_files_cache: For PushChangedFiles(), cache checksums of
- pushed files rather than recomputing them on a subsequent call.
- default_timeout: An integer containing the default number of seconds to
- wait for an operation to complete if no explicit value is provided.
- default_retries: An integer containing the default number or times an
- operation should be retried on failure if no explicit value is provided.
- """
- self.adb = None
- if isinstance(device, basestring):
- self.adb = _CreateAdbWrapper(device)
- elif isinstance(device, adb_wrapper.AdbWrapper):
- self.adb = device
- else:
- raise ValueError('Unsupported device value: %r' % device)
- self._commands_installed = None
- self._default_timeout = default_timeout
- self._default_retries = default_retries
- self._enable_device_files_cache = enable_device_files_cache
- self._cache = {}
- self._client_caches = {}
- self._cache_lock = threading.RLock()
- assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR)
- assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR)
-
- self._ClearCache()
-
- @property
- def serial(self):
- """Returns the device serial."""
- return self.adb.GetDeviceSerial()
-
- def __eq__(self, other):
- """Checks whether |other| refers to the same device as |self|.
-
- Args:
- other: The object to compare to. This can be a basestring, an instance
- of adb_wrapper.AdbWrapper, or an instance of DeviceUtils.
- Returns:
- Whether |other| refers to the same device as |self|.
- """
- return self.serial == str(other)
-
- def __lt__(self, other):
- """Compares two instances of DeviceUtils.
-
- This merely compares their serial numbers.
-
- Args:
- other: The instance of DeviceUtils to compare to.
- Returns:
- Whether |self| is less than |other|.
- """
- return self.serial < other.serial
-
- def __str__(self):
- """Returns the device serial."""
- return self.serial
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def IsOnline(self, timeout=None, retries=None):
- """Checks whether the device is online.
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- True if the device is online, False otherwise.
-
- Raises:
- CommandTimeoutError on timeout.
- """
- try:
- return self.adb.GetState() == 'device'
- except base_error.BaseError as exc:
- logger.info('Failed to get state: %s', exc)
- return False
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def HasRoot(self, timeout=None, retries=None):
- """Checks whether or not adbd has root privileges.
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- True if adbd has root privileges, False otherwise.
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- try:
- if self.product_name in _SPECIAL_ROOT_DEVICE_LIST:
- return self.GetProp('service.adb.root') == '1'
- self.RunShellCommand(['ls', '/root'], check_return=True)
- return True
- except device_errors.AdbCommandFailedError:
- return False
-
- def NeedsSU(self, timeout=DEFAULT, retries=DEFAULT):
- """Checks whether 'su' is needed to access protected resources.
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- True if 'su' is available on the device and is needed to to access
- protected resources; False otherwise if either 'su' is not available
- (e.g. because the device has a user build), or not needed (because adbd
- already has root privileges).
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- if 'needs_su' not in self._cache:
- cmd = '%s && ! ls /root' % self._Su('ls /root')
- if self.product_name in _SPECIAL_ROOT_DEVICE_LIST:
- if self.HasRoot():
- self._cache['needs_su'] = False
- return False
- cmd = 'which which && which su'
- try:
- self.RunShellCommand(cmd, shell=True, check_return=True,
- timeout=self._default_timeout if timeout is DEFAULT else timeout,
- retries=self._default_retries if retries is DEFAULT else retries)
- self._cache['needs_su'] = True
- except device_errors.AdbCommandFailedError:
- self._cache['needs_su'] = False
- return self._cache['needs_su']
-
-
- def _Su(self, command):
- if self.build_version_sdk >= version_codes.MARSHMALLOW:
- return 'su 0 %s' % command
- return 'su -c %s' % command
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def EnableRoot(self, timeout=None, retries=None):
- """Restarts adbd with root privileges.
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandFailedError if root could not be enabled.
- CommandTimeoutError on timeout.
- """
- if self.IsUserBuild():
- raise device_errors.CommandFailedError(
- 'Cannot enable root in user builds.', str(self))
- if 'needs_su' in self._cache:
- del self._cache['needs_su']
- self.adb.Root()
- self.WaitUntilFullyBooted()
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def IsUserBuild(self, timeout=None, retries=None):
- """Checks whether or not the device is running a user build.
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- True if the device is running a user build, False otherwise (i.e. if
- it's running a userdebug build).
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- return self.build_type == 'user'
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def GetExternalStoragePath(self, timeout=None, retries=None):
- """Get the device's path to its SD card.
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- The device's path to its SD card.
-
- Raises:
- CommandFailedError if the external storage path could not be determined.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- self._EnsureCacheInitialized()
- if not self._cache['external_storage']:
- raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set',
- str(self))
- return self._cache['external_storage']
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def GetApplicationPaths(self, package, timeout=None, retries=None):
- """Get the paths of the installed apks on the device for the given package.
-
- Args:
- package: Name of the package.
-
- Returns:
- List of paths to the apks on the device for the given package.
- """
- return self._GetApplicationPathsInternal(package)
-
- def _GetApplicationPathsInternal(self, package, skip_cache=False):
- cached_result = self._cache['package_apk_paths'].get(package)
- if cached_result is not None and not skip_cache:
- if package in self._cache['package_apk_paths_to_verify']:
- self._cache['package_apk_paths_to_verify'].remove(package)
- # Don't verify an app that is not thought to be installed. We are
- # concerned only with apps we think are installed having been
- # uninstalled manually.
- if cached_result and not self.PathExists(cached_result):
- cached_result = None
- self._cache['package_apk_checksums'].pop(package, 0)
- if cached_result is not None:
- return list(cached_result)
- # 'pm path' is liable to incorrectly exit with a nonzero number starting
- # in Lollipop.
- # TODO(jbudorick): Check if this is fixed as new Android versions are
- # released to put an upper bound on this.
- should_check_return = (self.build_version_sdk < version_codes.LOLLIPOP)
- output = self.RunShellCommand(
- ['pm', 'path', package], check_return=should_check_return)
- apks = []
- for line in output:
- if not line.startswith('package:'):
- continue
- apks.append(line[len('package:'):])
- if not apks and output:
- raise device_errors.CommandFailedError(
- 'pm path returned: %r' % '\n'.join(output), str(self))
- self._cache['package_apk_paths'][package] = list(apks)
- return apks
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def GetApplicationVersion(self, package, timeout=None, retries=None):
- """Get the version name of a package installed on the device.
-
- Args:
- package: Name of the package.
-
- Returns:
- A string with the version name or None if the package is not found
- on the device.
- """
- output = self.RunShellCommand(
- ['dumpsys', 'package', package], check_return=True)
- if not output:
- return None
- for line in output:
- line = line.strip()
- if line.startswith('versionName='):
- return line[len('versionName='):]
- raise device_errors.CommandFailedError(
- 'Version name for %s not found on dumpsys output' % package, str(self))
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def GetApplicationDataDirectory(self, package, timeout=None, retries=None):
- """Get the data directory on the device for the given package.
-
- Args:
- package: Name of the package.
-
- Returns:
- The package's data directory.
- Raises:
- CommandFailedError if the package's data directory can't be found,
- whether because it's not installed or otherwise.
- """
- output = self._RunPipedShellCommand(
- 'pm dump %s | grep dataDir=' % cmd_helper.SingleQuote(package))
- for line in output:
- _, _, dataDir = line.partition('dataDir=')
- if dataDir:
- return dataDir
- raise device_errors.CommandFailedError(
- 'Could not find data directory for %s', package)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None):
- """Wait for the device to fully boot.
-
- This means waiting for the device to boot, the package manager to be
- available, and the SD card to be ready. It can optionally mean waiting
- for wifi to come up, too.
-
- Args:
- wifi: A boolean indicating if we should wait for wifi to come up or not.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandFailedError on failure.
- CommandTimeoutError if one of the component waits times out.
- DeviceUnreachableError if the device becomes unresponsive.
- """
- def sd_card_ready():
- try:
- self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()],
- check_return=True)
- return True
- except device_errors.AdbCommandFailedError:
- return False
-
- def pm_ready():
- try:
- return self._GetApplicationPathsInternal('android', skip_cache=True)
- except device_errors.CommandFailedError:
- return False
-
- def boot_completed():
- try:
- return self.GetProp('sys.boot_completed', cache=False) == '1'
- except device_errors.CommandFailedError:
- return False
-
- def wifi_enabled():
- return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'],
- check_return=False)
-
- self.adb.WaitForDevice()
- timeout_retry.WaitFor(sd_card_ready)
- timeout_retry.WaitFor(pm_ready)
- timeout_retry.WaitFor(boot_completed)
- if wifi:
- timeout_retry.WaitFor(wifi_enabled)
-
- REBOOT_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT
-
- @decorators.WithTimeoutAndRetriesFromInstance(
- min_default_timeout=REBOOT_DEFAULT_TIMEOUT)
- def Reboot(self, block=True, wifi=False, timeout=None, retries=None):
- """Reboot the device.
-
- Args:
- block: A boolean indicating if we should wait for the reboot to complete.
- wifi: A boolean indicating if we should wait for wifi to be enabled after
- the reboot. The option has no effect unless |block| is also True.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- def device_offline():
- return not self.IsOnline()
-
- self.adb.Reboot()
- self._ClearCache()
- timeout_retry.WaitFor(device_offline, wait_period=1)
- if block:
- self.WaitUntilFullyBooted(wifi=wifi)
-
- INSTALL_DEFAULT_TIMEOUT = 4 * _DEFAULT_TIMEOUT
-
- @decorators.WithTimeoutAndRetriesFromInstance(
- min_default_timeout=INSTALL_DEFAULT_TIMEOUT)
- def Install(self, apk, allow_downgrade=False, reinstall=False,
- permissions=None, timeout=None, retries=None):
- """Install an APK.
-
- Noop if an identical APK is already installed.
-
- Args:
- apk: An ApkHelper instance or string containing the path to the APK.
- allow_downgrade: A boolean indicating if we should allow downgrades.
- reinstall: A boolean indicating if we should keep any existing app data.
- permissions: Set of permissions to set. If not set, finds permissions with
- apk helper. To set no permissions, pass [].
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandFailedError if the installation fails.
- CommandTimeoutError if the installation times out.
- DeviceUnreachableError on missing device.
- """
- self._InstallInternal(apk, None, allow_downgrade=allow_downgrade,
- reinstall=reinstall, permissions=permissions)
-
- @decorators.WithTimeoutAndRetriesFromInstance(
- min_default_timeout=INSTALL_DEFAULT_TIMEOUT)
- def InstallSplitApk(self, base_apk, split_apks, allow_downgrade=False,
- reinstall=False, allow_cached_props=False,
- permissions=None, timeout=None, retries=None):
- """Install a split APK.
-
- Noop if all of the APK splits are already installed.
-
- Args:
- base_apk: An ApkHelper instance or string containing the path to the base
- APK.
- split_apks: A list of strings of paths of all of the APK splits.
- allow_downgrade: A boolean indicating if we should allow downgrades.
- reinstall: A boolean indicating if we should keep any existing app data.
- allow_cached_props: Whether to use cached values for device properties.
- permissions: Set of permissions to set. If not set, finds permissions with
- apk helper. To set no permissions, pass [].
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandFailedError if the installation fails.
- CommandTimeoutError if the installation times out.
- DeviceUnreachableError on missing device.
- DeviceVersionError if device SDK is less than Android L.
- """
- self._InstallInternal(base_apk, split_apks, reinstall=reinstall,
- allow_cached_props=allow_cached_props,
- permissions=permissions,
- allow_downgrade=allow_downgrade)
-
- def _InstallInternal(self, base_apk, split_apks, allow_downgrade=False,
- reinstall=False, allow_cached_props=False,
- permissions=None):
- if split_apks:
- self._CheckSdkLevel(version_codes.LOLLIPOP)
-
- base_apk = apk_helper.ToHelper(base_apk)
-
- all_apks = [base_apk.path]
- if split_apks:
- all_apks += split_select.SelectSplits(
- self, base_apk.path, split_apks, allow_cached_props=allow_cached_props)
- if len(all_apks) == 1:
- logger.warning('split-select did not select any from %s', split_apks)
-
- missing_apks = [apk for apk in all_apks if not os.path.exists(apk)]
- if missing_apks:
- raise device_errors.CommandFailedError(
- 'Attempted to install non-existent apks: %s'
- % pprint.pformat(missing_apks))
-
- package_name = base_apk.GetPackageName()
- device_apk_paths = self._GetApplicationPathsInternal(package_name)
-
- apks_to_install = None
- host_checksums = None
- if not device_apk_paths:
- apks_to_install = all_apks
- elif len(device_apk_paths) > 1 and not split_apks:
- logger.warning(
- 'Installing non-split APK when split APK was previously installed')
- apks_to_install = all_apks
- elif len(device_apk_paths) == 1 and split_apks:
- logger.warning(
- 'Installing split APK when non-split APK was previously installed')
- apks_to_install = all_apks
- else:
- try:
- apks_to_install, host_checksums = (
- self._ComputeStaleApks(package_name, all_apks))
- except EnvironmentError as e:
- logger.warning('Error calculating md5: %s', e)
- apks_to_install, host_checksums = all_apks, None
- if apks_to_install and not reinstall:
- self.Uninstall(package_name)
- apks_to_install = all_apks
-
- if apks_to_install:
- # Assume that we won't know the resulting device state.
- self._cache['package_apk_paths'].pop(package_name, 0)
- self._cache['package_apk_checksums'].pop(package_name, 0)
- if split_apks:
- partial = package_name if len(apks_to_install) < len(all_apks) else None
- self.adb.InstallMultiple(
- apks_to_install, partial=partial, reinstall=reinstall,
- allow_downgrade=allow_downgrade)
- else:
- self.adb.Install(
- base_apk.path, reinstall=reinstall, allow_downgrade=allow_downgrade)
- if (permissions is None
- and self.build_version_sdk >= version_codes.MARSHMALLOW):
- permissions = base_apk.GetPermissions()
- self.GrantPermissions(package_name, permissions)
- # Upon success, we know the device checksums, but not their paths.
- if host_checksums is not None:
- self._cache['package_apk_checksums'][package_name] = host_checksums
- else:
- # Running adb install terminates running instances of the app, so to be
- # consistent, we explicitly terminate it when skipping the install.
- self.ForceStop(package_name)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def Uninstall(self, package_name, keep_data=False, timeout=None,
- retries=None):
- """Remove the app |package_name| from the device.
-
- This is a no-op if the app is not already installed.
-
- Args:
- package_name: The package to uninstall.
- keep_data: (optional) Whether to keep the data and cache directories.
- timeout: Timeout in seconds.
- retries: Number of retries.
-
- Raises:
- CommandFailedError if the uninstallation fails.
- CommandTimeoutError if the uninstallation times out.
- DeviceUnreachableError on missing device.
- """
- installed = self._GetApplicationPathsInternal(package_name)
- if not installed:
- return
- try:
- self.adb.Uninstall(package_name, keep_data)
- self._cache['package_apk_paths'][package_name] = []
- self._cache['package_apk_checksums'][package_name] = set()
- except:
- # Clear cache since we can't be sure of the state.
- self._cache['package_apk_paths'].pop(package_name, 0)
- self._cache['package_apk_checksums'].pop(package_name, 0)
- raise
-
- def _CheckSdkLevel(self, required_sdk_level):
- """Raises an exception if the device does not have the required SDK level.
- """
- if self.build_version_sdk < required_sdk_level:
- raise device_errors.DeviceVersionError(
- ('Requires SDK level %s, device is SDK level %s' %
- (required_sdk_level, self.build_version_sdk)),
- device_serial=self.serial)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def RunShellCommand(self, cmd, shell=False, check_return=False, cwd=None,
- env=None, run_as=None, as_root=False, single_line=False,
- large_output=False, raw_output=False, timeout=None,
- retries=None):
- """Run an ADB shell command.
-
- The command to run |cmd| should be a sequence of program arguments
- (preferred) or a single string with a shell script to run.
-
- When |cmd| is a sequence, it is assumed to contain the name of the command
- to run followed by its arguments. In this case, arguments are passed to the
- command exactly as given, preventing any further processing by the shell.
- This allows callers to easily pass arguments with spaces or special
- characters without having to worry about quoting rules. Whenever possible,
- it is recomended to pass |cmd| as a sequence.
-
- When |cmd| is passed as a single string, |shell| should be set to True.
- The command will be interpreted and run by the shell on the device,
- allowing the use of shell features such as pipes, wildcards, or variables.
- Failing to set shell=True will issue a warning, but this will be changed
- to a hard failure in the future (see: catapult:#3242).
-
- This behaviour is consistent with that of command runners in cmd_helper as
- well as Python's own subprocess.Popen.
-
- TODO(perezju) Change the default of |check_return| to True when callers
- have switched to the new behaviour.
-
- Args:
- cmd: A sequence containing the command to run and its arguments, or a
- string with a shell script to run (should also set shell=True).
- shell: A boolean indicating whether shell features may be used in |cmd|.
- check_return: A boolean indicating whether or not the return code should
- be checked.
- cwd: The device directory in which the command should be run.
- env: The environment variables with which the command should be run.
- run_as: A string containing the package as which the command should be
- run.
- as_root: A boolean indicating whether the shell command should be run
- with root privileges.
- single_line: A boolean indicating if only a single line of output is
- expected.
- large_output: Uses a work-around for large shell command output. Without
- this large output will be truncated.
- raw_output: Whether to only return the raw output
- (no splitting into lines).
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- If single_line is False, the output of the command as a list of lines,
- otherwise, a string with the unique line of output emmited by the command
- (with the optional newline at the end stripped).
-
- Raises:
- AdbCommandFailedError if check_return is True and the exit code of
- the command run on the device is non-zero.
- CommandFailedError if single_line is True but the output contains two or
- more lines.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- def env_quote(key, value):
- if not DeviceUtils._VALID_SHELL_VARIABLE.match(key):
- raise KeyError('Invalid shell variable name %r' % key)
- # using double quotes here to allow interpolation of shell variables
- return '%s=%s' % (key, cmd_helper.DoubleQuote(value))
-
- def run(cmd):
- return self.adb.Shell(cmd)
-
- def handle_check_return(cmd):
- try:
- return run(cmd)
- except device_errors.AdbCommandFailedError as exc:
- if check_return:
- raise
- else:
- return exc.output
-
- def handle_large_command(cmd):
- if len(cmd) < self._MAX_ADB_COMMAND_LENGTH:
- return handle_check_return(cmd)
- else:
- with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script:
- self._WriteFileWithPush(script.name, cmd)
- logger.info('Large shell command will be run from file: %s ...',
- cmd[:self._MAX_ADB_COMMAND_LENGTH])
- return handle_check_return('sh %s' % script.name_quoted)
-
- def handle_large_output(cmd, large_output_mode):
- if large_output_mode:
- with device_temp_file.DeviceTempFile(self.adb) as large_output_file:
- cmd = '( %s )>%s' % (cmd, large_output_file.name)
- logger.debug('Large output mode enabled. Will write output to '
- 'device and read results from file.')
- handle_large_command(cmd)
- return self.ReadFile(large_output_file.name, force_pull=True)
- else:
- try:
- return handle_large_command(cmd)
- except device_errors.AdbCommandFailedError as exc:
- if exc.status is None:
- logger.error(_FormatPartialOutputError(exc.output))
- logger.warning('Attempting to run in large_output mode.')
- logger.warning('Use RunShellCommand(..., large_output=True) for '
- 'shell commands that expect a lot of output.')
- return handle_large_output(cmd, True)
- else:
- raise
-
- if isinstance(cmd, basestring):
- if not shell:
- logging.warning(
- 'The command to run should preferably be passed as a sequence of'
- ' args. If shell features are needed (pipes, wildcards, variables)'
- ' clients should explicitly set shell=True.')
- else:
- cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd)
- if env:
- env = ' '.join(env_quote(k, v) for k, v in env.iteritems())
- cmd = '%s %s' % (env, cmd)
- if cwd:
- cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd)
- if run_as:
- cmd = 'run-as %s sh -c %s' % (cmd_helper.SingleQuote(run_as),
- cmd_helper.SingleQuote(cmd))
- if as_root and self.NeedsSU():
- # "su -c sh -c" allows using shell features in |cmd|
- cmd = self._Su('sh -c %s' % cmd_helper.SingleQuote(cmd))
-
- output = handle_large_output(cmd, large_output)
-
- if raw_output:
- return output
-
- output = output.splitlines()
- if single_line:
- if not output:
- return ''
- elif len(output) == 1:
- return output[0]
- else:
- msg = 'one line of output was expected, but got: %s'
- raise device_errors.CommandFailedError(msg % output, str(self))
- else:
- return output
-
- def _RunPipedShellCommand(self, script, **kwargs):
- PIPESTATUS_LEADER = 'PIPESTATUS: '
-
- script += '; echo "%s${PIPESTATUS[@]}"' % PIPESTATUS_LEADER
- kwargs.update(shell=True, check_return=True)
- output = self.RunShellCommand(script, **kwargs)
- pipestatus_line = output[-1]
-
- if not pipestatus_line.startswith(PIPESTATUS_LEADER):
- logger.error('Pipe exit statuses of shell script missing.')
- raise device_errors.AdbShellCommandFailedError(
- script, output, status=None,
- device_serial=self.serial)
-
- output = output[:-1]
- statuses = [
- int(s) for s in pipestatus_line[len(PIPESTATUS_LEADER):].split()]
- if any(statuses):
- raise device_errors.AdbShellCommandFailedError(
- script, output, status=statuses,
- device_serial=self.serial)
- return output
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def KillAll(self, process_name, exact=False, signum=device_signal.SIGKILL,
- as_root=False, blocking=False, quiet=False,
- timeout=None, retries=None):
- """Kill all processes with the given name on the device.
-
- Args:
- process_name: A string containing the name of the process to kill.
- exact: A boolean indicating whether to kill all processes matching
- the string |process_name| exactly, or all of those which contain
- |process_name| as a substring. Defaults to False.
- signum: An integer containing the signal number to send to kill. Defaults
- to SIGKILL (9).
- as_root: A boolean indicating whether the kill should be executed with
- root privileges.
- blocking: A boolean indicating whether we should wait until all processes
- with the given |process_name| are dead.
- quiet: A boolean indicating whether to ignore the fact that no processes
- to kill were found.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- The number of processes attempted to kill.
-
- Raises:
- CommandFailedError if no process was killed and |quiet| is False.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- procs_pids = self.GetPids(process_name)
- if exact:
- procs_pids = {process_name: procs_pids.get(process_name, [])}
- pids = set(itertools.chain(*procs_pids.values()))
- if not pids:
- if quiet:
- return 0
- else:
- raise device_errors.CommandFailedError(
- 'No process "%s"' % process_name, str(self))
-
- logger.info(
- 'KillAll(%r, ...) attempting to kill the following:', process_name)
- for name, ids in procs_pids.iteritems():
- for i in ids:
- logger.info(' %05s %s', str(i), name)
-
- cmd = ['kill', '-%d' % signum] + sorted(pids)
- self.RunShellCommand(cmd, as_root=as_root, check_return=True)
-
- def all_pids_killed():
- procs_pids_remain = self.GetPids(process_name)
- return not pids.intersection(itertools.chain(*procs_pids_remain.values()))
-
- if blocking:
- timeout_retry.WaitFor(all_pids_killed, wait_period=0.1)
-
- return len(pids)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def StartActivity(self, intent_obj, blocking=False, trace_file_name=None,
- force_stop=False, timeout=None, retries=None):
- """Start package's activity on the device.
-
- Args:
- intent_obj: An Intent object to send.
- blocking: A boolean indicating whether we should wait for the activity to
- finish launching.
- trace_file_name: If present, a string that both indicates that we want to
- profile the activity and contains the path to which the
- trace should be saved.
- force_stop: A boolean indicating whether we should stop the activity
- before starting it.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandFailedError if the activity could not be started.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- cmd = ['am', 'start']
- if blocking:
- cmd.append('-W')
- if trace_file_name:
- cmd.extend(['--start-profiler', trace_file_name])
- if force_stop:
- cmd.append('-S')
- cmd.extend(intent_obj.am_args)
- for line in self.RunShellCommand(cmd, check_return=True):
- if line.startswith('Error:'):
- raise device_errors.CommandFailedError(line, str(self))
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def StartInstrumentation(self, component, finish=True, raw=False,
- extras=None, timeout=None, retries=None):
- if extras is None:
- extras = {}
-
- cmd = ['am', 'instrument']
- if finish:
- cmd.append('-w')
- if raw:
- cmd.append('-r')
- for k, v in extras.iteritems():
- cmd.extend(['-e', str(k), str(v)])
- cmd.append(component)
-
- # Store the package name in a shell variable to help the command stay under
- # the _MAX_ADB_COMMAND_LENGTH limit.
- package = component.split('/')[0]
- shell_snippet = 'p=%s;%s' % (package,
- cmd_helper.ShrinkToSnippet(cmd, 'p', package))
- return self.RunShellCommand(shell_snippet, shell=True, check_return=True,
- large_output=True)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def BroadcastIntent(self, intent_obj, timeout=None, retries=None):
- """Send a broadcast intent.
-
- Args:
- intent: An Intent to broadcast.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- cmd = ['am', 'broadcast'] + intent_obj.am_args
- self.RunShellCommand(cmd, check_return=True)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def GoHome(self, timeout=None, retries=None):
- """Return to the home screen and obtain launcher focus.
-
- This command launches the home screen and attempts to obtain
- launcher focus until the timeout is reached.
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- def is_launcher_focused():
- output = self.RunShellCommand(['dumpsys', 'window', 'windows'],
- check_return=True, large_output=True)
- return any(self._LAUNCHER_FOCUSED_RE.match(l) for l in output)
-
- def dismiss_popups():
- # There is a dialog present; attempt to get rid of it.
- # Not all dialogs can be dismissed with back.
- self.SendKeyEvent(keyevent.KEYCODE_ENTER)
- self.SendKeyEvent(keyevent.KEYCODE_BACK)
- return is_launcher_focused()
-
- # If Home is already focused, return early to avoid unnecessary work.
- if is_launcher_focused():
- return
-
- self.StartActivity(
- intent.Intent(action='android.intent.action.MAIN',
- category='android.intent.category.HOME'),
- blocking=True)
-
- if not is_launcher_focused():
- timeout_retry.WaitFor(dismiss_popups, wait_period=1)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def ForceStop(self, package, timeout=None, retries=None):
- """Close the application.
-
- Args:
- package: A string containing the name of the package to stop.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- cmd = 'p=%s;if [[ "$(ps)" = *$p* ]]; then am force-stop $p; fi'
- self.RunShellCommand(cmd % package, shell=True, check_return=True)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def ClearApplicationState(
- self, package, permissions=None, timeout=None, retries=None):
- """Clear all state for the given package.
-
- Args:
- package: A string containing the name of the package to stop.
- permissions: List of permissions to set after clearing data.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- # Check that the package exists before clearing it for android builds below
- # JB MR2. Necessary because calling pm clear on a package that doesn't exist
- # may never return.
- if ((self.build_version_sdk >= version_codes.JELLY_BEAN_MR2)
- or self._GetApplicationPathsInternal(package)):
- self.RunShellCommand(['pm', 'clear', package], check_return=True)
- self.GrantPermissions(package, permissions)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def SendKeyEvent(self, keycode, timeout=None, retries=None):
- """Sends a keycode to the device.
-
- See the devil.android.sdk.keyevent module for suitable keycode values.
-
- Args:
- keycode: A integer keycode to send to the device.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- self.RunShellCommand(['input', 'keyevent', format(keycode, 'd')],
- check_return=True)
-
- PUSH_CHANGED_FILES_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT
-
- @decorators.WithTimeoutAndRetriesFromInstance(
- min_default_timeout=PUSH_CHANGED_FILES_DEFAULT_TIMEOUT)
- def PushChangedFiles(self, host_device_tuples, timeout=None,
- retries=None, delete_device_stale=False):
- """Push files to the device, skipping files that don't need updating.
-
- When a directory is pushed, it is traversed recursively on the host and
- all files in it are pushed to the device as needed.
- Additionally, if delete_device_stale option is True,
- files that exist on the device but don't exist on the host are deleted.
-
- Args:
- host_device_tuples: A list of (host_path, device_path) tuples, where
- |host_path| is an absolute path of a file or directory on the host
- that should be minimially pushed to the device, and |device_path| is
- an absolute path of the destination on the device.
- timeout: timeout in seconds
- retries: number of retries
- delete_device_stale: option to delete stale files on device
-
- Raises:
- CommandFailedError on failure.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
-
- all_changed_files = []
- all_stale_files = []
- missing_dirs = []
- cache_commit_funcs = []
- for h, d in host_device_tuples:
- assert os.path.isabs(h) and posixpath.isabs(d)
- h = os.path.realpath(h)
- changed_files, up_to_date_files, stale_files, cache_commit_func = (
- self._GetChangedAndStaleFiles(h, d, delete_device_stale))
- all_changed_files += changed_files
- all_stale_files += stale_files
- cache_commit_funcs.append(cache_commit_func)
- if changed_files and not up_to_date_files and not stale_files:
- if os.path.isdir(h):
- missing_dirs.append(d)
- else:
- missing_dirs.append(posixpath.dirname(d))
-
- if delete_device_stale and all_stale_files:
- self.RunShellCommand(['rm', '-f'] + all_stale_files, check_return=True)
-
- if all_changed_files:
- if missing_dirs:
- self.RunShellCommand(['mkdir', '-p'] + missing_dirs, check_return=True)
- self._PushFilesImpl(host_device_tuples, all_changed_files)
- for func in cache_commit_funcs:
- func()
-
- def _GetChangedAndStaleFiles(self, host_path, device_path, track_stale=False):
- """Get files to push and delete
-
- Args:
- host_path: an absolute path of a file or directory on the host
- device_path: an absolute path of a file or directory on the device
- track_stale: whether to bother looking for stale files (slower)
-
- Returns:
- a four-element tuple
- 1st element: a list of (host_files_path, device_files_path) tuples to push
- 2nd element: a list of host_files_path that are up-to-date
- 3rd element: a list of stale files under device_path, or [] when
- track_stale == False
- 4th element: a cache commit function.
- """
- try:
- # Length calculations below assume no trailing /.
- host_path = host_path.rstrip('/')
- device_path = device_path.rstrip('/')
-
- specific_device_paths = [device_path]
- ignore_other_files = not track_stale and os.path.isdir(host_path)
- if ignore_other_files:
- specific_device_paths = []
- for root, _, filenames in os.walk(host_path):
- relative_dir = root[len(host_path) + 1:]
- specific_device_paths.extend(
- posixpath.join(device_path, relative_dir, f) for f in filenames)
-
- def calculate_host_checksums():
- return md5sum.CalculateHostMd5Sums([host_path])
-
- def calculate_device_checksums():
- if self._enable_device_files_cache:
- cache_entry = self._cache['device_path_checksums'].get(device_path)
- if cache_entry and cache_entry[0] == ignore_other_files:
- return dict(cache_entry[1])
-
- sums = md5sum.CalculateDeviceMd5Sums(specific_device_paths, self)
-
- cache_entry = [ignore_other_files, sums]
- self._cache['device_path_checksums'][device_path] = cache_entry
- return dict(sums)
-
- host_checksums, device_checksums = reraiser_thread.RunAsync((
- calculate_host_checksums,
- calculate_device_checksums))
- except EnvironmentError as e:
- logger.warning('Error calculating md5: %s', e)
- return ([(host_path, device_path)], [], [], lambda: 0)
-
- to_push = []
- up_to_date = []
- to_delete = []
- if os.path.isfile(host_path):
- host_checksum = host_checksums.get(host_path)
- device_checksum = device_checksums.get(device_path)
- if host_checksum == device_checksum:
- up_to_date.append(host_path)
- else:
- to_push.append((host_path, device_path))
- else:
- for host_abs_path, host_checksum in host_checksums.iteritems():
- device_abs_path = posixpath.join(
- device_path, os.path.relpath(host_abs_path, host_path))
- device_checksum = device_checksums.pop(device_abs_path, None)
- if device_checksum == host_checksum:
- up_to_date.append(host_abs_path)
- else:
- to_push.append((host_abs_path, device_abs_path))
- to_delete = device_checksums.keys()
-
- def cache_commit_func():
- new_sums = {posixpath.join(device_path, path[len(host_path) + 1:]): val
- for path, val in host_checksums.iteritems()}
- cache_entry = [ignore_other_files, new_sums]
- self._cache['device_path_checksums'][device_path] = cache_entry
-
- return (to_push, up_to_date, to_delete, cache_commit_func)
-
- def _ComputeDeviceChecksumsForApks(self, package_name):
- ret = self._cache['package_apk_checksums'].get(package_name)
- if ret is None:
- device_paths = self._GetApplicationPathsInternal(package_name)
- file_to_checksums = md5sum.CalculateDeviceMd5Sums(device_paths, self)
- ret = set(file_to_checksums.values())
- self._cache['package_apk_checksums'][package_name] = ret
- return ret
-
- def _ComputeStaleApks(self, package_name, host_apk_paths):
- def calculate_host_checksums():
- return md5sum.CalculateHostMd5Sums(host_apk_paths)
-
- def calculate_device_checksums():
- return self._ComputeDeviceChecksumsForApks(package_name)
-
- host_checksums, device_checksums = reraiser_thread.RunAsync((
- calculate_host_checksums, calculate_device_checksums))
- stale_apks = [k for (k, v) in host_checksums.iteritems()
- if v not in device_checksums]
- return stale_apks, set(host_checksums.values())
-
- def _PushFilesImpl(self, host_device_tuples, files):
- if not files:
- return
-
- size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files)
- file_count = len(files)
- dir_size = sum(host_utils.GetRecursiveDiskUsage(h)
- for h, _ in host_device_tuples)
- dir_file_count = 0
- for h, _ in host_device_tuples:
- if os.path.isdir(h):
- dir_file_count += sum(len(f) for _r, _d, f in os.walk(h))
- else:
- dir_file_count += 1
-
- push_duration = self._ApproximateDuration(
- file_count, file_count, size, False)
- dir_push_duration = self._ApproximateDuration(
- len(host_device_tuples), dir_file_count, dir_size, False)
- zip_duration = self._ApproximateDuration(1, 1, size, True)
-
- if (dir_push_duration < push_duration and dir_push_duration < zip_duration
- # TODO(jbudorick): Resume directory pushing once clients have switched
- # to 1.0.36-compatible syntax.
- and False):
- self._PushChangedFilesIndividually(host_device_tuples)
- elif push_duration < zip_duration:
- self._PushChangedFilesIndividually(files)
- elif self._commands_installed is False:
- # Already tried and failed to install unzip command.
- self._PushChangedFilesIndividually(files)
- elif not self._PushChangedFilesZipped(
- files, [d for _, d in host_device_tuples]):
- self._PushChangedFilesIndividually(files)
-
- def _MaybeInstallCommands(self):
- if self._commands_installed is None:
- try:
- if not install_commands.Installed(self):
- install_commands.InstallCommands(self)
- self._commands_installed = True
- except device_errors.CommandFailedError as e:
- logger.warning('unzip not available: %s', str(e))
- self._commands_installed = False
- return self._commands_installed
-
- @staticmethod
- def _ApproximateDuration(adb_calls, file_count, byte_count, is_zipping):
- # We approximate the time to push a set of files to a device as:
- # t = c1 * a + c2 * f + c3 + b / c4 + b / (c5 * c6), where
- # t: total time (sec)
- # c1: adb call time delay (sec)
- # a: number of times adb is called (unitless)
- # c2: push time delay (sec)
- # f: number of files pushed via adb (unitless)
- # c3: zip time delay (sec)
- # c4: zip rate (bytes/sec)
- # b: total number of bytes (bytes)
- # c5: transfer rate (bytes/sec)
- # c6: compression ratio (unitless)
-
- # All of these are approximations.
- ADB_CALL_PENALTY = 0.1 # seconds
- ADB_PUSH_PENALTY = 0.01 # seconds
- ZIP_PENALTY = 2.0 # seconds
- ZIP_RATE = 10000000.0 # bytes / second
- TRANSFER_RATE = 2000000.0 # bytes / second
- COMPRESSION_RATIO = 2.0 # unitless
-
- adb_call_time = ADB_CALL_PENALTY * adb_calls
- adb_push_setup_time = ADB_PUSH_PENALTY * file_count
- if is_zipping:
- zip_time = ZIP_PENALTY + byte_count / ZIP_RATE
- transfer_time = byte_count / (TRANSFER_RATE * COMPRESSION_RATIO)
- else:
- zip_time = 0
- transfer_time = byte_count / TRANSFER_RATE
- return adb_call_time + adb_push_setup_time + zip_time + transfer_time
-
- def _PushChangedFilesIndividually(self, files):
- for h, d in files:
- self.adb.Push(h, d)
-
- def _PushChangedFilesZipped(self, files, dirs):
- with tempfile.NamedTemporaryFile(suffix='.zip') as zip_file:
- zip_proc = multiprocessing.Process(
- target=DeviceUtils._CreateDeviceZip,
- args=(zip_file.name, files))
- zip_proc.start()
- try:
- # While it's zipping, ensure the unzip command exists on the device.
- if not self._MaybeInstallCommands():
- zip_proc.terminate()
- return False
-
- # Warm up NeedsSU cache while we're still zipping.
- self.NeedsSU()
- with device_temp_file.DeviceTempFile(
- self.adb, suffix='.zip') as device_temp:
- zip_proc.join()
- self.adb.Push(zip_file.name, device_temp.name)
- quoted_dirs = ' '.join(cmd_helper.SingleQuote(d) for d in dirs)
- self.RunShellCommand(
- 'unzip %s&&chmod -R 777 %s' % (device_temp.name, quoted_dirs),
- shell=True, as_root=True,
- env={'PATH': '%s:$PATH' % install_commands.BIN_DIR},
- check_return=True)
- finally:
- if zip_proc.is_alive():
- zip_proc.terminate()
- return True
-
- @staticmethod
- def _CreateDeviceZip(zip_path, host_device_tuples):
- with zipfile.ZipFile(zip_path, 'w') as zip_file:
- for host_path, device_path in host_device_tuples:
- zip_utils.WriteToZipFile(zip_file, host_path, device_path)
-
- # TODO(nednguyen): remove this and migrate the callsite to PathExists().
- @decorators.WithTimeoutAndRetriesFromInstance()
- def FileExists(self, device_path, timeout=None, retries=None):
- """Checks whether the given file exists on the device.
-
- Arguments are the same as PathExists.
- """
- return self.PathExists(device_path, timeout=timeout, retries=retries)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def PathExists(self, device_paths, as_root=False, timeout=None, retries=None):
- """Checks whether the given path(s) exists on the device.
-
- Args:
- device_path: A string containing the absolute path to the file on the
- device, or an iterable of paths to check.
- as_root: Whether root permissions should be use to check for the existence
- of the given path(s).
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- True if the all given paths exist on the device, False otherwise.
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- paths = device_paths
- if isinstance(paths, basestring):
- paths = (paths,)
- if not paths:
- return True
- cmd = ['test', '-e', paths[0]]
- for p in paths[1:]:
- cmd.extend(['-a', '-e', p])
- try:
- self.RunShellCommand(cmd, as_root=as_root, check_return=True,
- timeout=timeout, retries=retries)
- return True
- except device_errors.CommandFailedError:
- return False
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def RemovePath(self, device_path, force=False, recursive=False,
- as_root=False, timeout=None, retries=None):
- """Removes the given path(s) from the device.
-
- Args:
- device_path: A string containing the absolute path to the file on the
- device, or an iterable of paths to check.
- force: Whether to remove the path(s) with force (-f).
- recursive: Whether to remove any directories in the path(s) recursively.
- as_root: Whether root permissions should be use to remove the given
- path(s).
- timeout: timeout in seconds
- retries: number of retries
- """
- args = ['rm']
- if force:
- args.append('-f')
- if recursive:
- args.append('-r')
- if isinstance(device_path, basestring):
- args.append(device_path)
- else:
- args.extend(device_path)
- self.RunShellCommand(args, as_root=as_root, check_return=True)
-
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def PullFile(self, device_path, host_path, timeout=None, retries=None):
- """Pull a file from the device.
-
- Args:
- device_path: A string containing the absolute path of the file to pull
- from the device.
- host_path: A string containing the absolute path of the destination on
- the host.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandFailedError on failure.
- CommandTimeoutError on timeout.
- """
- # Create the base dir if it doesn't exist already
- dirname = os.path.dirname(host_path)
- if dirname and not os.path.exists(dirname):
- os.makedirs(dirname)
- self.adb.Pull(device_path, host_path)
-
- def _ReadFileWithPull(self, device_path):
- try:
- d = tempfile.mkdtemp()
- host_temp_path = os.path.join(d, 'tmp_ReadFileWithPull')
- self.adb.Pull(device_path, host_temp_path)
- with open(host_temp_path, 'r') as host_temp:
- return host_temp.read()
- finally:
- if os.path.exists(d):
- shutil.rmtree(d)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def ReadFile(self, device_path, as_root=False, force_pull=False,
- timeout=None, retries=None):
- """Reads the contents of a file from the device.
-
- Args:
- device_path: A string containing the absolute path of the file to read
- from the device.
- as_root: A boolean indicating whether the read should be executed with
- root privileges.
- force_pull: A boolean indicating whether to force the operation to be
- performed by pulling a file from the device. The default is, when the
- contents are short, to retrieve the contents using cat instead.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- The contents of |device_path| as a string. Contents are intepreted using
- universal newlines, so the caller will see them encoded as '\n'. Also,
- all lines will be terminated.
-
- Raises:
- AdbCommandFailedError if the file can't be read.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- def get_size(path):
- return self.FileSize(path, as_root=as_root)
-
- if (not force_pull
- and 0 < get_size(device_path) <= self._MAX_ADB_OUTPUT_LENGTH):
- return _JoinLines(self.RunShellCommand(
- ['cat', device_path], as_root=as_root, check_return=True))
- elif as_root and self.NeedsSU():
- with device_temp_file.DeviceTempFile(self.adb) as device_temp:
- cmd = 'SRC=%s DEST=%s;cp "$SRC" "$DEST" && chmod 666 "$DEST"' % (
- cmd_helper.SingleQuote(device_path),
- cmd_helper.SingleQuote(device_temp.name))
- self.RunShellCommand(cmd, shell=True, as_root=True, check_return=True)
- return self._ReadFileWithPull(device_temp.name)
- else:
- return self._ReadFileWithPull(device_path)
-
- def _WriteFileWithPush(self, device_path, contents):
- with tempfile.NamedTemporaryFile() as host_temp:
- host_temp.write(contents)
- host_temp.flush()
- self.adb.Push(host_temp.name, device_path)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def WriteFile(self, device_path, contents, as_root=False, force_push=False,
- timeout=None, retries=None):
- """Writes |contents| to a file on the device.
-
- Args:
- device_path: A string containing the absolute path to the file to write
- on the device.
- contents: A string containing the data to write to the device.
- as_root: A boolean indicating whether the write should be executed with
- root privileges (if available).
- force_push: A boolean indicating whether to force the operation to be
- performed by pushing a file to the device. The default is, when the
- contents are short, to pass the contents using a shell script instead.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandFailedError if the file could not be written on the device.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- if not force_push and len(contents) < self._MAX_ADB_COMMAND_LENGTH:
- # If the contents are small, for efficieny we write the contents with
- # a shell command rather than pushing a file.
- cmd = 'echo -n %s > %s' % (cmd_helper.SingleQuote(contents),
- cmd_helper.SingleQuote(device_path))
- self.RunShellCommand(cmd, shell=True, as_root=as_root, check_return=True)
- elif as_root and self.NeedsSU():
- # Adb does not allow to "push with su", so we first push to a temp file
- # on a safe location, and then copy it to the desired location with su.
- with device_temp_file.DeviceTempFile(self.adb) as device_temp:
- self._WriteFileWithPush(device_temp.name, contents)
- # Here we need 'cp' rather than 'mv' because the temp and
- # destination files might be on different file systems (e.g.
- # on internal storage and an external sd card).
- self.RunShellCommand(['cp', device_temp.name, device_path],
- as_root=True, check_return=True)
- else:
- # If root is not needed, we can push directly to the desired location.
- self._WriteFileWithPush(device_path, contents)
-
- def _ParseLongLsOutput(self, device_path, as_root=False, **kwargs):
- """Run and scrape the output of 'ls -a -l' on a device directory."""
- device_path = posixpath.join(device_path, '') # Force trailing '/'.
- output = self.RunShellCommand(
- ['ls', '-a', '-l', device_path], as_root=as_root,
- check_return=True, env={'TZ': 'utc'}, **kwargs)
- if output and output[0].startswith('total '):
- output.pop(0) # pylint: disable=maybe-no-member
-
- entries = []
- for line in output:
- m = _LONG_LS_OUTPUT_RE.match(line)
- if m:
- if m.group('filename') not in ['.', '..']:
- entries.append(m.groupdict())
- else:
- logger.info('Skipping: %s', line)
-
- return entries
-
- def ListDirectory(self, device_path, as_root=False, **kwargs):
- """List all files on a device directory.
-
- Mirroring os.listdir (and most client expectations) the resulting list
- does not include the special entries '.' and '..' even if they are present
- in the directory.
-
- Args:
- device_path: A string containing the path of the directory on the device
- to list.
- as_root: A boolean indicating whether the to use root privileges to list
- the directory contents.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- A list of filenames for all entries contained in the directory.
-
- Raises:
- AdbCommandFailedError if |device_path| does not specify a valid and
- accessible directory in the device.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- entries = self._ParseLongLsOutput(device_path, as_root=as_root, **kwargs)
- return [d['filename'] for d in entries]
-
- def StatDirectory(self, device_path, as_root=False, **kwargs):
- """List file and stat info for all entries on a device directory.
-
- Implementation notes: this is currently implemented by parsing the output
- of 'ls -a -l' on the device. Whether possible and convenient, we attempt to
- make parsing strict and return values mirroring those of the standard |os|
- and |stat| Python modules.
-
- Mirroring os.listdir (and most client expectations) the resulting list
- does not include the special entries '.' and '..' even if they are present
- in the directory.
-
- Args:
- device_path: A string containing the path of the directory on the device
- to list.
- as_root: A boolean indicating whether the to use root privileges to list
- the directory contents.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- A list of dictionaries, each containing the following keys:
- filename: A string with the file name.
- st_mode: File permissions, use the stat module to interpret these.
- st_nlink: Number of hard links (may be missing).
- st_owner: A string with the user name of the owner.
- st_group: A string with the group name of the owner.
- st_rdev_pair: Device type as (major, minior) (only if inode device).
- st_size: Size of file, in bytes (may be missing for non-regular files).
- st_mtime: Time of most recent modification, in seconds since epoch
- (although resolution is in minutes).
- symbolic_link_to: If entry is a symbolic link, path where it points to;
- missing otherwise.
-
- Raises:
- AdbCommandFailedError if |device_path| does not specify a valid and
- accessible directory in the device.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- entries = self._ParseLongLsOutput(device_path, as_root=as_root, **kwargs)
- for d in entries:
- for key, value in d.items():
- if value is None:
- del d[key] # Remove missing fields.
- d['st_mode'] = _ParseModeString(d['st_mode'])
- d['st_mtime'] = calendar.timegm(
- time.strptime(d['st_mtime'], _LS_DATE_FORMAT))
- for key in ['st_nlink', 'st_size', 'st_rdev_major', 'st_rdev_minor']:
- if key in d:
- d[key] = int(d[key])
- if 'st_rdev_major' in d and 'st_rdev_minor' in d:
- d['st_rdev_pair'] = (d.pop('st_rdev_major'), d.pop('st_rdev_minor'))
- return entries
-
- def StatPath(self, device_path, as_root=False, **kwargs):
- """Get the stat attributes of a file or directory on the device.
-
- Args:
- device_path: A string containing the path of a file or directory from
- which to get attributes.
- as_root: A boolean indicating whether the to use root privileges to
- access the file information.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- A dictionary with the stat info collected; see StatDirectory for details.
-
- Raises:
- CommandFailedError if device_path cannot be found on the device.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- dirname, filename = posixpath.split(posixpath.normpath(device_path))
- for entry in self.StatDirectory(dirname, as_root=as_root, **kwargs):
- if entry['filename'] == filename:
- return entry
- raise device_errors.CommandFailedError(
- 'Cannot find file or directory: %r' % device_path, str(self))
-
- def FileSize(self, device_path, as_root=False, **kwargs):
- """Get the size of a file on the device.
-
- Note: This is implemented by parsing the output of the 'ls' command on
- the device. On some Android versions, when passing a directory or special
- file, the size is *not* reported and this function will throw an exception.
-
- Args:
- device_path: A string containing the path of a file on the device.
- as_root: A boolean indicating whether the to use root privileges to
- access the file information.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- The size of the file in bytes.
-
- Raises:
- CommandFailedError if device_path cannot be found on the device, or
- its size cannot be determited for some reason.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- entry = self.StatPath(device_path, as_root=as_root, **kwargs)
- try:
- return entry['st_size']
- except KeyError:
- raise device_errors.CommandFailedError(
- 'Could not determine the size of: %s' % device_path, str(self))
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def SetJavaAsserts(self, enabled, timeout=None, retries=None):
- """Enables or disables Java asserts.
-
- Args:
- enabled: A boolean indicating whether Java asserts should be enabled
- or disabled.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- True if the device-side property changed and a restart is required as a
- result, False otherwise.
-
- Raises:
- CommandTimeoutError on timeout.
- """
- def find_property(lines, property_name):
- for index, line in enumerate(lines):
- if line.strip() == '':
- continue
- key_value = tuple(s.strip() for s in line.split('=', 1))
- if len(key_value) != 2:
- continue
- key, value = key_value
- if key == property_name:
- return index, value
- return None, ''
-
- new_value = 'all' if enabled else ''
-
- # First ensure the desired property is persisted.
- try:
- properties = self.ReadFile(self.LOCAL_PROPERTIES_PATH).splitlines()
- except device_errors.CommandFailedError:
- properties = []
- index, value = find_property(properties, self.JAVA_ASSERT_PROPERTY)
- if new_value != value:
- if new_value:
- new_line = '%s=%s' % (self.JAVA_ASSERT_PROPERTY, new_value)
- if index is None:
- properties.append(new_line)
- else:
- properties[index] = new_line
- else:
- assert index is not None # since new_value == '' and new_value != value
- properties.pop(index)
- self.WriteFile(self.LOCAL_PROPERTIES_PATH, _JoinLines(properties))
-
- # Next, check the current runtime value is what we need, and
- # if not, set it and report that a reboot is required.
- value = self.GetProp(self.JAVA_ASSERT_PROPERTY)
- if new_value != value:
- self.SetProp(self.JAVA_ASSERT_PROPERTY, new_value)
- return True
- else:
- return False
-
- def GetLanguage(self, cache=False):
- """Returns the language setting on the device.
- Args:
- cache: Whether to use cached properties when available.
- """
- return self.GetProp('persist.sys.language', cache=cache)
-
- def GetCountry(self, cache=False):
- """Returns the country setting on the device.
-
- Args:
- cache: Whether to use cached properties when available.
- """
- return self.GetProp('persist.sys.country', cache=cache)
-
- @property
- def screen_density(self):
- """Returns the screen density of the device."""
- DPI_TO_DENSITY = {
- 120: 'ldpi',
- 160: 'mdpi',
- 240: 'hdpi',
- 320: 'xhdpi',
- 480: 'xxhdpi',
- 640: 'xxxhdpi',
- }
- return DPI_TO_DENSITY.get(self.pixel_density, 'tvdpi')
-
- @property
- def pixel_density(self):
- return int(self.GetProp('ro.sf.lcd_density', cache=True))
-
- @property
- def build_description(self):
- """Returns the build description of the system.
-
- For example:
- nakasi-user 4.4.4 KTU84P 1227136 release-keys
- """
- return self.GetProp('ro.build.description', cache=True)
-
- @property
- def build_fingerprint(self):
- """Returns the build fingerprint of the system.
-
- For example:
- google/nakasi/grouper:4.4.4/KTU84P/1227136:user/release-keys
- """
- return self.GetProp('ro.build.fingerprint', cache=True)
-
- @property
- def build_id(self):
- """Returns the build ID of the system (e.g. 'KTU84P')."""
- return self.GetProp('ro.build.id', cache=True)
-
- @property
- def build_product(self):
- """Returns the build product of the system (e.g. 'grouper')."""
- return self.GetProp('ro.build.product', cache=True)
-
- @property
- def build_type(self):
- """Returns the build type of the system (e.g. 'user')."""
- return self.GetProp('ro.build.type', cache=True)
-
- @property
- def build_version_sdk(self):
- """Returns the build version sdk of the system as a number (e.g. 19).
-
- For version code numbers see:
- http://developer.android.com/reference/android/os/Build.VERSION_CODES.html
-
- For named constants see devil.android.sdk.version_codes
-
- Raises:
- CommandFailedError if the build version sdk is not a number.
- """
- value = self.GetProp('ro.build.version.sdk', cache=True)
- try:
- return int(value)
- except ValueError:
- raise device_errors.CommandFailedError(
- 'Invalid build version sdk: %r' % value)
-
- @property
- def product_cpu_abi(self):
- """Returns the product cpu abi of the device (e.g. 'armeabi-v7a')."""
- return self.GetProp('ro.product.cpu.abi', cache=True)
-
- @property
- def product_model(self):
- """Returns the name of the product model (e.g. 'Nexus 7')."""
- return self.GetProp('ro.product.model', cache=True)
-
- @property
- def product_name(self):
- """Returns the product name of the device (e.g. 'nakasi')."""
- return self.GetProp('ro.product.name', cache=True)
-
- @property
- def product_board(self):
- """Returns the product board name of the device (e.g. 'shamu')."""
- return self.GetProp('ro.product.board', cache=True)
-
- def _EnsureCacheInitialized(self):
- """Populates cache token, runs getprop and fetches $EXTERNAL_STORAGE."""
- if self._cache['token']:
- return
- with self._cache_lock:
- if self._cache['token']:
- return
- # Change the token every time to ensure that it will match only the
- # previously dumped cache.
- token = str(uuid.uuid1())
- cmd = (
- 'c=/data/local/tmp/cache_token;'
- 'echo $EXTERNAL_STORAGE;'
- 'cat $c 2>/dev/null||echo;'
- 'echo "%s">$c &&' % token +
- 'getprop'
- )
- output = self.RunShellCommand(
- cmd, shell=True, check_return=True, large_output=True)
- # Error-checking for this existing is done in GetExternalStoragePath().
- self._cache['external_storage'] = output[0]
- self._cache['prev_token'] = output[1]
- output = output[2:]
-
- prop_cache = self._cache['getprop']
- prop_cache.clear()
- for key, value in _GETPROP_RE.findall(''.join(output)):
- prop_cache[key] = value
- self._cache['token'] = token
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def GetProp(self, property_name, cache=False, timeout=None, retries=None):
- """Gets a property from the device.
-
- Args:
- property_name: A string containing the name of the property to get from
- the device.
- cache: Whether to use cached properties when available.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- The value of the device's |property_name| property.
-
- Raises:
- CommandTimeoutError on timeout.
- """
- assert isinstance(property_name, basestring), (
- "property_name is not a string: %r" % property_name)
-
- if cache:
- # It takes ~120ms to query a single property, and ~130ms to query all
- # properties. So, when caching we always query all properties.
- self._EnsureCacheInitialized()
- else:
- # timeout and retries are handled down at run shell, because we don't
- # want to apply them in the other branch when reading from the cache
- value = self.RunShellCommand(
- ['getprop', property_name], single_line=True, check_return=True,
- timeout=timeout, retries=retries)
- self._cache['getprop'][property_name] = value
- # Non-existent properties are treated as empty strings by getprop.
- return self._cache['getprop'].get(property_name, '')
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def SetProp(self, property_name, value, check=False, timeout=None,
- retries=None):
- """Sets a property on the device.
-
- Args:
- property_name: A string containing the name of the property to set on
- the device.
- value: A string containing the value to set to the property on the
- device.
- check: A boolean indicating whether to check that the property was
- successfully set on the device.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandFailedError if check is true and the property was not correctly
- set on the device (e.g. because it is not rooted).
- CommandTimeoutError on timeout.
- """
- assert isinstance(property_name, basestring), (
- "property_name is not a string: %r" % property_name)
- assert isinstance(value, basestring), "value is not a string: %r" % value
-
- self.RunShellCommand(['setprop', property_name, value], check_return=True)
- prop_cache = self._cache['getprop']
- if property_name in prop_cache:
- del prop_cache[property_name]
- # TODO(perezju) remove the option and make the check mandatory, but using a
- # single shell script to both set- and getprop.
- if check and value != self.GetProp(property_name, cache=False):
- raise device_errors.CommandFailedError(
- 'Unable to set property %r on the device to %r'
- % (property_name, value), str(self))
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def GetABI(self, timeout=None, retries=None):
- """Gets the device main ABI.
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- The device's main ABI name.
-
- Raises:
- CommandTimeoutError on timeout.
- """
- return self.GetProp('ro.product.cpu.abi', cache=True)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def GetPids(self, process_name=None, timeout=None, retries=None):
- """Returns the PIDs of processes containing the given name as substring.
-
- Note that the |process_name| is often the package name.
-
- Args:
- process_name: A string containing the process name to get the PIDs for.
- If missing returns PIDs for all processes.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- A dict mapping process name to a list of PIDs for each process that
- contained the provided |process_name|.
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- procs_pids = collections.defaultdict(list)
- try:
- ps_cmd = 'ps'
- # ps behavior was changed in Android above N, http://crbug.com/686716
- if (self.build_version_sdk >= version_codes.NOUGAT_MR1
- and self.build_id[0] > 'N'):
- ps_cmd = 'ps -e'
- if process_name:
- ps_output = self._RunPipedShellCommand(
- '%s | grep -F %s' % (ps_cmd, cmd_helper.SingleQuote(process_name)))
- else:
- ps_output = self.RunShellCommand(
- ps_cmd.split(), check_return=True, large_output=True)
- except device_errors.AdbShellCommandFailedError as e:
- if e.status and isinstance(e.status, list) and not e.status[0]:
- # If ps succeeded but grep failed, there were no processes with the
- # given name.
- return procs_pids
- else:
- raise
-
- process_name = process_name or ''
- for line in ps_output:
- try:
- ps_data = line.split()
- pid, process = ps_data[1], ps_data[-1]
- if process_name in process and pid != 'PID':
- procs_pids[process].append(pid)
- except IndexError:
- pass
- return procs_pids
-
- def GetApplicationPids(self, process_name, at_most_one=False, **kwargs):
- """Returns the PID or PIDs of a given process name.
-
- Note that the |process_name|, often the package name, must match exactly.
-
- Args:
- process_name: A string containing the process name to get the PIDs for.
- at_most_one: A boolean indicating that at most one PID is expected to
- be found.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- A list of the PIDs for the named process. If at_most_one=True returns
- the single PID found or None otherwise.
-
- Raises:
- CommandFailedError if at_most_one=True and more than one PID is found
- for the named process.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- pids = self.GetPids(process_name, **kwargs).get(process_name, [])
- if at_most_one:
- if len(pids) > 1:
- raise device_errors.CommandFailedError(
- 'Expected a single process but found PIDs: %s.' % ', '.join(pids),
- device_serial=str(self))
- return pids[0] if pids else None
- else:
- return pids
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def GetEnforce(self, timeout=None, retries=None):
- """Get the current mode of SELinux.
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- True (enforcing), False (permissive), or None (disabled).
-
- Raises:
- CommandFailedError on failure.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- output = self.RunShellCommand(
- ['getenforce'], check_return=True, single_line=True).lower()
- if output not in _SELINUX_MODE:
- raise device_errors.CommandFailedError(
- 'Unexpected getenforce output: %s' % output)
- return _SELINUX_MODE[output]
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def SetEnforce(self, enabled, timeout=None, retries=None):
- """Modify the mode SELinux is running in.
-
- Args:
- enabled: a boolean indicating whether to put SELinux in encorcing mode
- (if True), or permissive mode (otherwise).
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandFailedError on failure.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- self.RunShellCommand(
- ['setenforce', '1' if int(enabled) else '0'], as_root=True,
- check_return=True)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def TakeScreenshot(self, host_path=None, timeout=None, retries=None):
- """Takes a screenshot of the device.
-
- Args:
- host_path: A string containing the path on the host to save the
- screenshot to. If None, a file name in the current
- directory will be generated.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- The name of the file on the host to which the screenshot was saved.
-
- Raises:
- CommandFailedError on failure.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
- """
- if not host_path:
- host_path = os.path.abspath('screenshot-%s-%s.png' % (
- self.serial, _GetTimeStamp()))
- with device_temp_file.DeviceTempFile(self.adb, suffix='.png') as device_tmp:
- self.RunShellCommand(['/system/bin/screencap', '-p', device_tmp.name],
- check_return=True)
- self.PullFile(device_tmp.name, host_path)
- return host_path
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def GetMemoryUsageForPid(self, pid, timeout=None, retries=None):
- """Gets the memory usage for the given PID.
-
- Args:
- pid: PID of the process.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- A dict containing memory usage statistics for the PID. May include:
- Size, Rss, Pss, Shared_Clean, Shared_Dirty, Private_Clean,
- Private_Dirty, VmHWM
-
- Raises:
- CommandTimeoutError on timeout.
- """
- result = collections.defaultdict(int)
-
- try:
- result.update(self._GetMemoryUsageForPidFromSmaps(pid))
- except device_errors.CommandFailedError:
- logger.exception('Error getting memory usage from smaps')
-
- try:
- result.update(self._GetMemoryUsageForPidFromStatus(pid))
- except device_errors.CommandFailedError:
- logger.exception('Error getting memory usage from status')
-
- return result
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def DismissCrashDialogIfNeeded(self, timeout=None, retries=None):
- """Dismiss the error/ANR dialog if present.
-
- Returns: Name of the crashed package if a dialog is focused,
- None otherwise.
- """
- def _FindFocusedWindow():
- match = None
- # TODO(jbudorick): Try to grep the output on the device instead of using
- # large_output if/when DeviceUtils exposes a public interface for piped
- # shell command handling.
- for line in self.RunShellCommand(['dumpsys', 'window', 'windows'],
- check_return=True, large_output=True):
- match = re.match(_CURRENT_FOCUS_CRASH_RE, line)
- if match:
- break
- return match
-
- match = _FindFocusedWindow()
- if not match:
- return None
- package = match.group(2)
- logger.warning('Trying to dismiss %s dialog for %s', *match.groups())
- self.SendKeyEvent(keyevent.KEYCODE_DPAD_RIGHT)
- self.SendKeyEvent(keyevent.KEYCODE_DPAD_RIGHT)
- self.SendKeyEvent(keyevent.KEYCODE_ENTER)
- match = _FindFocusedWindow()
- if match:
- logger.error('Still showing a %s dialog for %s', *match.groups())
- return package
-
- def _GetMemoryUsageForPidFromSmaps(self, pid):
- SMAPS_COLUMNS = (
- 'Size', 'Rss', 'Pss', 'Shared_Clean', 'Shared_Dirty', 'Private_Clean',
- 'Private_Dirty')
-
- showmap_out = self._RunPipedShellCommand(
- 'showmap %d | grep TOTAL' % int(pid), as_root=True)
-
- split_totals = showmap_out[-1].split()
- if (not split_totals
- or len(split_totals) != 9
- or split_totals[-1] != 'TOTAL'):
- raise device_errors.CommandFailedError(
- 'Invalid output from showmap: %s' % '\n'.join(showmap_out))
-
- return dict(itertools.izip(SMAPS_COLUMNS, (int(n) for n in split_totals)))
-
- def _GetMemoryUsageForPidFromStatus(self, pid):
- for line in self.ReadFile(
- '/proc/%s/status' % str(pid), as_root=True).splitlines():
- if line.startswith('VmHWM:'):
- return {'VmHWM': int(line.split()[1])}
- raise device_errors.CommandFailedError(
- 'Could not find memory peak value for pid %s', str(pid))
-
- def GetLogcatMonitor(self, *args, **kwargs):
- """Returns a new LogcatMonitor associated with this device.
-
- Parameters passed to this function are passed directly to
- |logcat_monitor.LogcatMonitor| and are documented there.
- """
- return logcat_monitor.LogcatMonitor(self.adb, *args, **kwargs)
-
- def GetClientCache(self, client_name):
- """Returns client cache."""
- if client_name not in self._client_caches:
- self._client_caches[client_name] = {}
- return self._client_caches[client_name]
-
- def _ClearCache(self):
- """Clears all caches."""
- for client in self._client_caches:
- self._client_caches[client].clear()
- self._cache = {
- # Map of packageId -> list of on-device .apk paths
- 'package_apk_paths': {},
- # Set of packageId that were loaded from LoadCacheData and not yet
- # verified.
- 'package_apk_paths_to_verify': set(),
- # Map of packageId -> set of on-device .apk checksums
- 'package_apk_checksums': {},
- # Map of property_name -> value
- 'getprop': {},
- # Map of device_path -> [ignore_other_files, map of path->checksum]
- 'device_path_checksums': {},
- # Location of sdcard ($EXTERNAL_STORAGE).
- 'external_storage': None,
- # Token used to detect when LoadCacheData is stale.
- 'token': None,
- 'prev_token': None,
- }
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def LoadCacheData(self, data, timeout=None, retries=None):
- """Initializes the cache from data created using DumpCacheData.
-
- The cache is used only if its token matches the one found on the device.
- This prevents a stale cache from being used (which can happen when sharing
- devices).
-
- Args:
- data: A previously serialized cache (string).
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- Whether the cache was loaded.
- """
- obj = json.loads(data)
- self._EnsureCacheInitialized()
- given_token = obj.get('token')
- if not given_token or self._cache['prev_token'] != given_token:
- logger.warning('Stale cache detected. Not using it.')
- return False
-
- self._cache['package_apk_paths'] = obj.get('package_apk_paths', {})
- # When using a cache across script invokations, verify that apps have
- # not been uninstalled.
- self._cache['package_apk_paths_to_verify'] = set(
- self._cache['package_apk_paths'].iterkeys())
-
- package_apk_checksums = obj.get('package_apk_checksums', {})
- for k, v in package_apk_checksums.iteritems():
- package_apk_checksums[k] = set(v)
- self._cache['package_apk_checksums'] = package_apk_checksums
- device_path_checksums = obj.get('device_path_checksums', {})
- self._cache['device_path_checksums'] = device_path_checksums
- return True
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def DumpCacheData(self, timeout=None, retries=None):
- """Dumps the current cache state to a string.
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- A serialized cache as a string.
- """
- self._EnsureCacheInitialized()
- obj = {}
- obj['token'] = self._cache['token']
- obj['package_apk_paths'] = self._cache['package_apk_paths']
- obj['package_apk_checksums'] = self._cache['package_apk_checksums']
- # JSON can't handle sets.
- for k, v in obj['package_apk_checksums'].iteritems():
- obj['package_apk_checksums'][k] = list(v)
- obj['device_path_checksums'] = self._cache['device_path_checksums']
- return json.dumps(obj, separators=(',', ':'))
-
- @classmethod
- def parallel(cls, devices, async=False):
- """Creates a Parallelizer to operate over the provided list of devices.
-
- Args:
- devices: A list of either DeviceUtils instances or objects from
- from which DeviceUtils instances can be constructed. If None,
- all attached devices will be used.
- async: If true, returns a Parallelizer that runs operations
- asynchronously.
-
- Returns:
- A Parallelizer operating over |devices|.
- """
- devices = [d if isinstance(d, cls) else cls(d) for d in devices]
- if async:
- return parallelizer.Parallelizer(devices)
- else:
- return parallelizer.SyncParallelizer(devices)
-
- @classmethod
- def HealthyDevices(cls, blacklist=None, device_arg='default', **kwargs):
- """Returns a list of DeviceUtils instances.
-
- Returns a list of DeviceUtils instances that are attached, not blacklisted,
- and optionally filtered by --device flags or ANDROID_SERIAL environment
- variable.
-
- Args:
- blacklist: A DeviceBlacklist instance (optional). Device serials in this
- blacklist will never be returned, but a warning will be logged if they
- otherwise would have been.
- device_arg: The value of the --device flag. This can be:
- 'default' -> Same as [], but returns an empty list rather than raise a
- NoDevicesError.
- [] -> Returns all devices, unless $ANDROID_SERIAL is set.
- None -> Use $ANDROID_SERIAL if set, otherwise looks for a single
- attached device. Raises an exception if multiple devices are
- attached.
- 'serial' -> Returns an instance for the given serial, if not
- blacklisted.
- ['A', 'B', ...] -> Returns instances for the subset that is not
- blacklisted.
- A device serial, or a list of device serials (optional).
-
- Returns:
- A list of DeviceUtils instances.
-
- Raises:
- NoDevicesError: Raised when no non-blacklisted devices exist and
- device_arg is passed.
- MultipleDevicesError: Raise when multiple devices exist, but |device_arg|
- is None.
- """
- allow_no_devices = False
- if device_arg == 'default':
- allow_no_devices = True
- device_arg = ()
-
- select_multiple = True
- if not (isinstance(device_arg, tuple) or isinstance(device_arg, list)):
- select_multiple = False
- if device_arg:
- device_arg = (device_arg,)
-
- blacklisted_devices = blacklist.Read() if blacklist else []
-
- # adb looks for ANDROID_SERIAL, so support it as well.
- android_serial = os.environ.get('ANDROID_SERIAL')
- if not device_arg and android_serial:
- device_arg = (android_serial,)
-
- def blacklisted(serial):
- if serial in blacklisted_devices:
- logger.warning('Device %s is blacklisted.', serial)
- return True
- return False
-
- if device_arg:
- devices = [cls(x, **kwargs) for x in device_arg if not blacklisted(x)]
- else:
- devices = []
- for adb in adb_wrapper.AdbWrapper.Devices():
- if not blacklisted(adb.GetDeviceSerial()):
- devices.append(cls(_CreateAdbWrapper(adb), **kwargs))
-
- if len(devices) == 0 and not allow_no_devices:
- raise device_errors.NoDevicesError()
- if len(devices) > 1 and not select_multiple:
- raise device_errors.MultipleDevicesError(devices)
- return sorted(devices)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def RestartAdbd(self, timeout=None, retries=None):
- logger.info('Restarting adbd on device.')
- with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script:
- self.WriteFile(script.name, _RESTART_ADBD_SCRIPT)
- self.RunShellCommand(
- ['source', script.name], check_return=True, as_root=True)
- self.adb.WaitForDevice()
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def GrantPermissions(self, package, permissions, timeout=None, retries=None):
- # Permissions only need to be set on M and above because of the changes to
- # the permission model.
- if not permissions or self.build_version_sdk < version_codes.MARSHMALLOW:
- return
- logger.info('Setting permissions for %s.', package)
- permissions = [p for p in permissions if p not in _PERMISSIONS_BLACKLIST]
- if ('android.permission.WRITE_EXTERNAL_STORAGE' in permissions
- and 'android.permission.READ_EXTERNAL_STORAGE' not in permissions):
- permissions.append('android.permission.READ_EXTERNAL_STORAGE')
- cmd = '&&'.join('pm grant %s %s' % (package, p) for p in permissions)
- if cmd:
- output = self.RunShellCommand(cmd, shell=True, check_return=True)
- if output:
- logger.warning('Possible problem when granting permissions. Blacklist '
- 'may need to be updated.')
- for line in output:
- logger.warning(' %s', line)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def IsScreenOn(self, timeout=None, retries=None):
- """Determines if screen is on.
-
- Dumpsys input_method exposes screen on/off state. Below is an explination of
- the states.
-
- Pre-L:
- On: mScreenOn=true
- Off: mScreenOn=false
- L+:
- On: mInteractive=true
- Off: mInteractive=false
-
- Returns:
- True if screen is on, false if it is off.
-
- Raises:
- device_errors.CommandFailedError: If screen state cannot be found.
- """
- if self.build_version_sdk < version_codes.LOLLIPOP:
- input_check = 'mScreenOn'
- check_value = 'mScreenOn=true'
- else:
- input_check = 'mInteractive'
- check_value = 'mInteractive=true'
- dumpsys_out = self._RunPipedShellCommand(
- 'dumpsys input_method | grep %s' % input_check)
- if not dumpsys_out:
- raise device_errors.CommandFailedError(
- 'Unable to detect screen state', str(self))
- return check_value in dumpsys_out[0]
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def SetScreen(self, on, timeout=None, retries=None):
- """Turns screen on and off.
-
- Args:
- on: bool to decide state to switch to. True = on False = off.
- """
- def screen_test():
- return self.IsScreenOn() == on
-
- if screen_test():
- logger.info('Screen already in expected state.')
- return
- self.SendKeyEvent(keyevent.KEYCODE_POWER)
- timeout_retry.WaitFor(screen_test, wait_period=1)
diff --git a/third_party/catapult/devil/devil/android/device_utils_devicetest.py b/third_party/catapult/devil/devil/android/device_utils_devicetest.py
deleted file mode 100755
index e69cc90991..0000000000
--- a/third_party/catapult/devil/devil/android/device_utils_devicetest.py
+++ /dev/null
@@ -1,229 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""
-Unit tests for the contents of device_utils.py (mostly DeviceUtils).
-The test will invoke real devices
-"""
-
-import os
-import posixpath
-import sys
-import tempfile
-import unittest
-
-if __name__ == '__main__':
- sys.path.append(
- os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', )))
-
-from devil.android import device_test_case
-from devil.android import device_utils
-from devil.android.sdk import adb_wrapper
-from devil.utils import cmd_helper
-
-_OLD_CONTENTS = "foo"
-_NEW_CONTENTS = "bar"
-_DEVICE_DIR = "/data/local/tmp/device_utils_test"
-_SUB_DIR = "sub"
-_SUB_DIR1 = "sub1"
-_SUB_DIR2 = "sub2"
-
-
-class DeviceUtilsPushDeleteFilesTest(device_test_case.DeviceTestCase):
-
- def setUp(self):
- super(DeviceUtilsPushDeleteFilesTest, self).setUp()
- self.adb = adb_wrapper.AdbWrapper(self.serial)
- self.adb.WaitForDevice()
- self.device = device_utils.DeviceUtils(
- self.adb, default_timeout=10, default_retries=0)
-
- @staticmethod
- def _MakeTempFile(contents):
- """Make a temporary file with the given contents.
-
- Args:
- contents: string to write to the temporary file.
-
- Returns:
- the tuple contains the absolute path to the file and the file name
- """
- fi, path = tempfile.mkstemp(text=True)
- with os.fdopen(fi, 'w') as f:
- f.write(contents)
- file_name = os.path.basename(path)
- return (path, file_name)
-
- @staticmethod
- def _MakeTempFileGivenDir(directory, contents):
- """Make a temporary file under the given directory
- with the given contents
-
- Args:
- directory: the temp directory to create the file
- contents: string to write to the temp file
-
- Returns:
- the list contains the absolute path to the file and the file name
- """
- fi, path = tempfile.mkstemp(dir=directory, text=True)
- with os.fdopen(fi, 'w') as f:
- f.write(contents)
- file_name = os.path.basename(path)
- return (path, file_name)
-
- @staticmethod
- def _ChangeTempFile(path, contents):
- with os.open(path, 'w') as f:
- f.write(contents)
-
- @staticmethod
- def _DeleteTempFile(path):
- os.remove(path)
-
- def testPushChangedFiles_noFileChange(self):
- (host_file_path, file_name) = self._MakeTempFile(_OLD_CONTENTS)
- device_file_path = "%s/%s" % (_DEVICE_DIR, file_name)
- self.adb.Push(host_file_path, device_file_path)
- self.device.PushChangedFiles([(host_file_path, device_file_path)])
- result = self.device.RunShellCommand(
- ['cat', device_file_path], check_return=True, single_line=True)
- self.assertEqual(_OLD_CONTENTS, result)
-
- cmd_helper.RunCmd(['rm', host_file_path])
- self.device.RemovePath(_DEVICE_DIR, recursive=True, force=True)
-
- def testPushChangedFiles_singleFileChange(self):
- (host_file_path, file_name) = self._MakeTempFile(_OLD_CONTENTS)
- device_file_path = "%s/%s" % (_DEVICE_DIR, file_name)
- self.adb.Push(host_file_path, device_file_path)
-
- with open(host_file_path, 'w') as f:
- f.write(_NEW_CONTENTS)
- self.device.PushChangedFiles([(host_file_path, device_file_path)])
- result = self.device.RunShellCommand(
- ['cat', device_file_path], check_return=True, single_line=True)
- self.assertEqual(_NEW_CONTENTS, result)
-
- cmd_helper.RunCmd(['rm', host_file_path])
- self.device.RemovePath(_DEVICE_DIR, recursive=True, force=True)
-
- def testDeleteFiles(self):
- host_tmp_dir = tempfile.mkdtemp()
- (host_file_path, file_name) = self._MakeTempFileGivenDir(
- host_tmp_dir, _OLD_CONTENTS)
-
- device_file_path = "%s/%s" % (_DEVICE_DIR, file_name)
- self.adb.Push(host_file_path, device_file_path)
-
- cmd_helper.RunCmd(['rm', host_file_path])
- self.device.PushChangedFiles([(host_tmp_dir, _DEVICE_DIR)],
- delete_device_stale=True)
- filenames = self.device.ListDirectory(_DEVICE_DIR)
- self.assertEqual([], filenames)
-
- cmd_helper.RunCmd(['rm', '-rf', host_tmp_dir])
- self.device.RemovePath(_DEVICE_DIR, recursive=True, force=True)
-
- def testPushAndDeleteFiles_noSubDir(self):
- host_tmp_dir = tempfile.mkdtemp()
- (host_file_path1, file_name1) = self._MakeTempFileGivenDir(
- host_tmp_dir, _OLD_CONTENTS)
- (host_file_path2, file_name2) = self._MakeTempFileGivenDir(
- host_tmp_dir, _OLD_CONTENTS)
-
- device_file_path1 = "%s/%s" % (_DEVICE_DIR, file_name1)
- device_file_path2 = "%s/%s" % (_DEVICE_DIR, file_name2)
- self.adb.Push(host_file_path1, device_file_path1)
- self.adb.Push(host_file_path2, device_file_path2)
-
- with open(host_file_path1, 'w') as f:
- f.write(_NEW_CONTENTS)
- cmd_helper.RunCmd(['rm', host_file_path2])
-
- self.device.PushChangedFiles([(host_tmp_dir, _DEVICE_DIR)],
- delete_device_stale=True)
- result = self.device.RunShellCommand(
- ['cat', device_file_path1], check_return=True, single_line=True)
- self.assertEqual(_NEW_CONTENTS, result)
-
- filenames = self.device.ListDirectory(_DEVICE_DIR)
- self.assertEqual([file_name1], filenames)
-
- cmd_helper.RunCmd(['rm', '-rf', host_tmp_dir])
- self.device.RemovePath(_DEVICE_DIR, recursive=True, force=True)
-
- def testPushAndDeleteFiles_SubDir(self):
- host_tmp_dir = tempfile.mkdtemp()
- host_sub_dir1 = "%s/%s" % (host_tmp_dir, _SUB_DIR1)
- host_sub_dir2 = "%s/%s/%s" % (host_tmp_dir, _SUB_DIR, _SUB_DIR2)
- cmd_helper.RunCmd(['mkdir', '-p', host_sub_dir1])
- cmd_helper.RunCmd(['mkdir', '-p', host_sub_dir2])
-
- (host_file_path1, file_name1) = self._MakeTempFileGivenDir(
- host_tmp_dir, _OLD_CONTENTS)
- (host_file_path2, file_name2) = self._MakeTempFileGivenDir(
- host_tmp_dir, _OLD_CONTENTS)
- (host_file_path3, file_name3) = self._MakeTempFileGivenDir(
- host_sub_dir1, _OLD_CONTENTS)
- (host_file_path4, file_name4) = self._MakeTempFileGivenDir(
- host_sub_dir2, _OLD_CONTENTS)
-
- device_file_path1 = "%s/%s" % (_DEVICE_DIR, file_name1)
- device_file_path2 = "%s/%s" % (_DEVICE_DIR, file_name2)
- device_file_path3 = "%s/%s/%s" % (_DEVICE_DIR, _SUB_DIR1, file_name3)
- device_file_path4 = "%s/%s/%s/%s" % (_DEVICE_DIR, _SUB_DIR,
- _SUB_DIR2, file_name4)
-
- self.adb.Push(host_file_path1, device_file_path1)
- self.adb.Push(host_file_path2, device_file_path2)
- self.adb.Push(host_file_path3, device_file_path3)
- self.adb.Push(host_file_path4, device_file_path4)
-
- with open(host_file_path1, 'w') as f:
- f.write(_NEW_CONTENTS)
- cmd_helper.RunCmd(['rm', host_file_path2])
- cmd_helper.RunCmd(['rm', host_file_path4])
-
- self.device.PushChangedFiles([(host_tmp_dir, _DEVICE_DIR)],
- delete_device_stale=True)
- result = self.device.RunShellCommand(
- ['cat', device_file_path1], check_return=True, single_line=True)
- self.assertEqual(_NEW_CONTENTS, result)
-
- filenames = self.device.ListDirectory(_DEVICE_DIR)
- self.assertIn(file_name1, filenames)
- self.assertIn(_SUB_DIR1, filenames)
- self.assertIn(_SUB_DIR, filenames)
- self.assertEqual(3, len(filenames))
-
- result = self.device.RunShellCommand(
- ['cat', device_file_path3], check_return=True, single_line=True)
- self.assertEqual(_OLD_CONTENTS, result)
-
- filenames = self.device.ListDirectory(
- posixpath.join(_DEVICE_DIR, _SUB_DIR, _SUB_DIR2))
- self.assertEqual([], filenames)
-
- cmd_helper.RunCmd(['rm', '-rf', host_tmp_dir])
- self.device.RemovePath(_DEVICE_DIR, recursive=True, force=True)
-
- def testRestartAdbd(self):
- def get_adbd_pid():
- # TODO(catapult:#3215): Migrate to device.GetPids().
- ps_output = self.device.RunShellCommand(['ps'], check_return=True)
- for ps_line in ps_output:
- if 'adbd' in ps_line:
- return ps_line.split()[1]
- self.fail('Unable to find adbd')
-
- old_adbd_pid = get_adbd_pid()
- self.device.RestartAdbd()
- new_adbd_pid = get_adbd_pid()
- self.assertNotEqual(old_adbd_pid, new_adbd_pid)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/third_party/catapult/devil/devil/android/device_utils_test.py b/third_party/catapult/devil/devil/android/device_utils_test.py
deleted file mode 100755
index 2490209631..0000000000
--- a/third_party/catapult/devil/devil/android/device_utils_test.py
+++ /dev/null
@@ -1,2900 +0,0 @@
-#!/usr/bin/env python
-# 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.
-
-"""
-Unit tests for the contents of device_utils.py (mostly DeviceUtils).
-"""
-
-# pylint: disable=protected-access
-# pylint: disable=unused-argument
-
-import json
-import logging
-import os
-import stat
-import unittest
-
-from devil import devil_env
-from devil.android import device_errors
-from devil.android import device_signal
-from devil.android import device_utils
-from devil.android.sdk import adb_wrapper
-from devil.android.sdk import intent
-from devil.android.sdk import keyevent
-from devil.android.sdk import version_codes
-from devil.utils import cmd_helper
-from devil.utils import mock_calls
-
-with devil_env.SysPath(devil_env.PYMOCK_PATH):
- import mock # pylint: disable=import-error
-
-
-class AnyStringWith(object):
- def __init__(self, value):
- self._value = value
-
- def __eq__(self, other):
- return self._value in other
-
- def __repr__(self):
- return '<AnyStringWith: %s>' % self._value
-
-
-class _MockApkHelper(object):
-
- def __init__(self, path, package_name, perms=None):
- self.path = path
- self.package_name = package_name
- self.perms = perms
-
- def GetPackageName(self):
- return self.package_name
-
- def GetPermissions(self):
- return self.perms
-
-
-class _MockMultipleDevicesError(Exception):
- pass
-
-
-class DeviceUtilsInitTest(unittest.TestCase):
-
- def testInitWithStr(self):
- serial_as_str = str('0123456789abcdef')
- d = device_utils.DeviceUtils('0123456789abcdef')
- self.assertEqual(serial_as_str, d.adb.GetDeviceSerial())
-
- def testInitWithUnicode(self):
- serial_as_unicode = unicode('fedcba9876543210')
- d = device_utils.DeviceUtils(serial_as_unicode)
- self.assertEqual(serial_as_unicode, d.adb.GetDeviceSerial())
-
- def testInitWithAdbWrapper(self):
- serial = '123456789abcdef0'
- a = adb_wrapper.AdbWrapper(serial)
- d = device_utils.DeviceUtils(a)
- self.assertEqual(serial, d.adb.GetDeviceSerial())
-
- def testInitWithMissing_fails(self):
- with self.assertRaises(ValueError):
- device_utils.DeviceUtils(None)
- with self.assertRaises(ValueError):
- device_utils.DeviceUtils('')
-
-
-class DeviceUtilsGetAVDsTest(mock_calls.TestCase):
-
- def testGetAVDs(self):
- mocked_attrs = {
- 'android_sdk': '/my/sdk/path'
- }
- with mock.patch('devil.devil_env._Environment.LocalPath',
- mock.Mock(side_effect=lambda a: mocked_attrs[a])):
- with self.assertCall(
- mock.call.devil.utils.cmd_helper.GetCmdOutput(
- [mock.ANY, 'list', 'avd']),
- 'Available Android Virtual Devices:\n'
- ' Name: my_android5.0\n'
- ' Path: /some/path/to/.android/avd/my_android5.0.avd\n'
- ' Target: Android 5.0 (API level 21)\n'
- ' Tag/ABI: default/x86\n'
- ' Skin: WVGA800\n'):
- self.assertEquals(['my_android5.0'], device_utils.GetAVDs())
-
-
-class DeviceUtilsRestartServerTest(mock_calls.TestCase):
-
- @mock.patch('time.sleep', mock.Mock())
- def testRestartServer_succeeds(self):
- with self.assertCalls(
- mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.KillServer(),
- (mock.call.devil.utils.cmd_helper.GetCmdStatusAndOutput(
- ['pgrep', 'adb']),
- (1, '')),
- mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.StartServer(),
- (mock.call.devil.utils.cmd_helper.GetCmdStatusAndOutput(
- ['pgrep', 'adb']),
- (1, '')),
- (mock.call.devil.utils.cmd_helper.GetCmdStatusAndOutput(
- ['pgrep', 'adb']),
- (0, '123\n'))):
- device_utils.RestartServer()
-
-
-class MockTempFile(object):
-
- def __init__(self, name='/tmp/some/file'):
- self.file = mock.MagicMock(spec=file)
- self.file.name = name
- self.file.name_quoted = cmd_helper.SingleQuote(name)
-
- def __enter__(self):
- return self.file
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- pass
-
- @property
- def name(self):
- return self.file.name
-
-
-class _PatchedFunction(object):
-
- def __init__(self, patched=None, mocked=None):
- self.patched = patched
- self.mocked = mocked
-
-
-def _AdbWrapperMock(test_serial, is_ready=True):
- adb = mock.Mock(spec=adb_wrapper.AdbWrapper)
- adb.__str__ = mock.Mock(return_value=test_serial)
- adb.GetDeviceSerial.return_value = test_serial
- adb.is_ready = is_ready
- return adb
-
-
-class DeviceUtilsTest(mock_calls.TestCase):
-
- def setUp(self):
- self.adb = _AdbWrapperMock('0123456789abcdef')
- self.device = device_utils.DeviceUtils(
- self.adb, default_timeout=10, default_retries=0)
- self.watchMethodCalls(self.call.adb, ignore=['GetDeviceSerial'])
-
- def AdbCommandError(self, args=None, output=None, status=None, msg=None):
- if args is None:
- args = ['[unspecified]']
- return mock.Mock(side_effect=device_errors.AdbCommandFailedError(
- args, output, status, msg, str(self.device)))
-
- def CommandError(self, msg=None):
- if msg is None:
- msg = 'Command failed'
- return mock.Mock(side_effect=device_errors.CommandFailedError(
- msg, str(self.device)))
-
- def ShellError(self, output=None, status=1):
- def action(cmd, *args, **kwargs):
- raise device_errors.AdbShellCommandFailedError(
- cmd, output, status, str(self.device))
- if output is None:
- output = 'Permission denied\n'
- return action
-
- def TimeoutError(self, msg=None):
- if msg is None:
- msg = 'Operation timed out'
- return mock.Mock(side_effect=device_errors.CommandTimeoutError(
- msg, str(self.device)))
-
- def EnsureCacheInitialized(self, props=None, sdcard='/sdcard'):
- props = props or []
- ret = [sdcard, 'TOKEN'] + props
- return (self.call.device.RunShellCommand(
- AnyStringWith('getprop'),
- shell=True, check_return=True, large_output=True), ret)
-
-
-class DeviceUtilsEqTest(DeviceUtilsTest):
-
- def testEq_equal_deviceUtils(self):
- other = device_utils.DeviceUtils(_AdbWrapperMock('0123456789abcdef'))
- self.assertTrue(self.device == other)
- self.assertTrue(other == self.device)
-
- def testEq_equal_adbWrapper(self):
- other = adb_wrapper.AdbWrapper('0123456789abcdef')
- self.assertTrue(self.device == other)
- self.assertTrue(other == self.device)
-
- def testEq_equal_string(self):
- other = '0123456789abcdef'
- self.assertTrue(self.device == other)
- self.assertTrue(other == self.device)
-
- def testEq_devicesNotEqual(self):
- other = device_utils.DeviceUtils(_AdbWrapperMock('0123456789abcdee'))
- self.assertFalse(self.device == other)
- self.assertFalse(other == self.device)
-
- def testEq_identity(self):
- self.assertTrue(self.device == self.device)
-
- def testEq_serialInList(self):
- devices = [self.device]
- self.assertTrue('0123456789abcdef' in devices)
-
-
-class DeviceUtilsLtTest(DeviceUtilsTest):
-
- def testLt_lessThan(self):
- other = device_utils.DeviceUtils(_AdbWrapperMock('ffffffffffffffff'))
- self.assertTrue(self.device < other)
- self.assertTrue(other > self.device)
-
- def testLt_greaterThan_lhs(self):
- other = device_utils.DeviceUtils(_AdbWrapperMock('0000000000000000'))
- self.assertFalse(self.device < other)
- self.assertFalse(other > self.device)
-
- def testLt_equal(self):
- other = device_utils.DeviceUtils(_AdbWrapperMock('0123456789abcdef'))
- self.assertFalse(self.device < other)
- self.assertFalse(other > self.device)
-
- def testLt_sorted(self):
- devices = [
- device_utils.DeviceUtils(_AdbWrapperMock('ffffffffffffffff')),
- device_utils.DeviceUtils(_AdbWrapperMock('0000000000000000')),
- ]
- sorted_devices = sorted(devices)
- self.assertEquals('0000000000000000',
- sorted_devices[0].adb.GetDeviceSerial())
- self.assertEquals('ffffffffffffffff',
- sorted_devices[1].adb.GetDeviceSerial())
-
-
-class DeviceUtilsStrTest(DeviceUtilsTest):
-
- def testStr_returnsSerial(self):
- with self.assertCalls(
- (self.call.adb.GetDeviceSerial(), '0123456789abcdef')):
- self.assertEqual('0123456789abcdef', str(self.device))
-
-
-class DeviceUtilsIsOnlineTest(DeviceUtilsTest):
-
- def testIsOnline_true(self):
- with self.assertCall(self.call.adb.GetState(), 'device'):
- self.assertTrue(self.device.IsOnline())
-
- def testIsOnline_false(self):
- with self.assertCall(self.call.adb.GetState(), 'offline'):
- self.assertFalse(self.device.IsOnline())
-
- def testIsOnline_error(self):
- with self.assertCall(self.call.adb.GetState(), self.CommandError()):
- self.assertFalse(self.device.IsOnline())
-
-
-class DeviceUtilsHasRootTest(DeviceUtilsTest):
-
- def testHasRoot_true(self):
- with self.patch_call(self.call.device.product_name,
- return_value='notasailfish'), (
- self.assertCall(self.call.adb.Shell('ls /root'), 'foo\n')):
- self.assertTrue(self.device.HasRoot())
-
- def testhasRootSpecial_true(self):
- with self.patch_call(self.call.device.product_name,
- return_value='sailfish'), (
- self.assertCall(self.call.adb.Shell('getprop service.adb.root'),
- '1\n')):
- self.assertTrue(self.device.HasRoot())
-
- def testHasRoot_false(self):
- with self.patch_call(self.call.device.product_name,
- return_value='notasailfish'), (
- self.assertCall(self.call.adb.Shell('ls /root'),
- self.ShellError())):
- self.assertFalse(self.device.HasRoot())
-
- def testHasRootSpecial_false(self):
- with self.patch_call(self.call.device.product_name,
- return_value='sailfish'), (
- self.assertCall(self.call.adb.Shell('getprop service.adb.root'),
- '\n')):
- self.assertFalse(self.device.HasRoot())
-
-
-class DeviceUtilsEnableRootTest(DeviceUtilsTest):
-
- def testEnableRoot_succeeds(self):
- with self.assertCalls(
- (self.call.device.IsUserBuild(), False),
- self.call.adb.Root(),
- self.call.device.WaitUntilFullyBooted()):
- self.device.EnableRoot()
-
- def testEnableRoot_userBuild(self):
- with self.assertCalls(
- (self.call.device.IsUserBuild(), True)):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.EnableRoot()
-
- def testEnableRoot_rootFails(self):
- with self.assertCalls(
- (self.call.device.IsUserBuild(), False),
- (self.call.adb.Root(), self.CommandError())):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.EnableRoot()
-
-
-class DeviceUtilsIsUserBuildTest(DeviceUtilsTest):
-
- def testIsUserBuild_yes(self):
- with self.assertCall(
- self.call.device.GetProp('ro.build.type', cache=True), 'user'):
- self.assertTrue(self.device.IsUserBuild())
-
- def testIsUserBuild_no(self):
- with self.assertCall(
- self.call.device.GetProp('ro.build.type', cache=True), 'userdebug'):
- self.assertFalse(self.device.IsUserBuild())
-
-
-class DeviceUtilsGetExternalStoragePathTest(DeviceUtilsTest):
-
- def testGetExternalStoragePath_succeeds(self):
- with self.assertCalls(
- self.EnsureCacheInitialized(sdcard='/fake/storage/path')):
- self.assertEquals('/fake/storage/path',
- self.device.GetExternalStoragePath())
-
- def testGetExternalStoragePath_fails(self):
- with self.assertCalls(
- self.EnsureCacheInitialized(sdcard='')):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.GetExternalStoragePath()
-
-
-class DeviceUtilsGetApplicationPathsInternalTest(DeviceUtilsTest):
-
- def testGetApplicationPathsInternal_exists(self):
- with self.assertCalls(
- (self.call.device.GetProp('ro.build.version.sdk', cache=True), '19'),
- (self.call.device.RunShellCommand(
- ['pm', 'path', 'android'], check_return=True),
- ['package:/path/to/android.apk'])):
- self.assertEquals(['/path/to/android.apk'],
- self.device._GetApplicationPathsInternal('android'))
-
- def testGetApplicationPathsInternal_notExists(self):
- with self.assertCalls(
- (self.call.device.GetProp('ro.build.version.sdk', cache=True), '19'),
- (self.call.device.RunShellCommand(
- ['pm', 'path', 'not.installed.app'], check_return=True),
- '')):
- self.assertEquals([],
- self.device._GetApplicationPathsInternal('not.installed.app'))
-
- def testGetApplicationPathsInternal_garbageFirstLine(self):
- with self.assertCalls(
- (self.call.device.GetProp('ro.build.version.sdk', cache=True), '19'),
- (self.call.device.RunShellCommand(
- ['pm', 'path', 'android'], check_return=True),
- ['garbage first line'])):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device._GetApplicationPathsInternal('android')
-
- def testGetApplicationPathsInternal_fails(self):
- with self.assertCalls(
- (self.call.device.GetProp('ro.build.version.sdk', cache=True), '19'),
- (self.call.device.RunShellCommand(
- ['pm', 'path', 'android'], check_return=True),
- self.CommandError('ERROR. Is package manager running?\n'))):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device._GetApplicationPathsInternal('android')
-
-
-class DeviceUtils_GetApplicationVersionTest(DeviceUtilsTest):
-
- def test_GetApplicationVersion_exists(self):
- with self.assertCalls(
- (self.call.adb.Shell('dumpsys package com.android.chrome'),
- 'Packages:\n'
- ' Package [com.android.chrome] (3901ecfb):\n'
- ' userId=1234 gids=[123, 456, 789]\n'
- ' pkg=Package{1fecf634 com.android.chrome}\n'
- ' versionName=45.0.1234.7\n')):
- self.assertEquals('45.0.1234.7',
- self.device.GetApplicationVersion('com.android.chrome'))
-
- def test_GetApplicationVersion_notExists(self):
- with self.assertCalls(
- (self.call.adb.Shell('dumpsys package com.android.chrome'), '')):
- self.assertEquals(None,
- self.device.GetApplicationVersion('com.android.chrome'))
-
- def test_GetApplicationVersion_fails(self):
- with self.assertCalls(
- (self.call.adb.Shell('dumpsys package com.android.chrome'),
- 'Packages:\n'
- ' Package [com.android.chrome] (3901ecfb):\n'
- ' userId=1234 gids=[123, 456, 789]\n'
- ' pkg=Package{1fecf634 com.android.chrome}\n')):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.GetApplicationVersion('com.android.chrome')
-
-
-class DeviceUtilsGetApplicationDataDirectoryTest(DeviceUtilsTest):
-
- def testGetApplicationDataDirectory_exists(self):
- with self.assertCall(
- self.call.device._RunPipedShellCommand(
- 'pm dump foo.bar.baz | grep dataDir='),
- ['dataDir=/data/data/foo.bar.baz']):
- self.assertEquals(
- '/data/data/foo.bar.baz',
- self.device.GetApplicationDataDirectory('foo.bar.baz'))
-
- def testGetApplicationDataDirectory_notExists(self):
- with self.assertCall(
- self.call.device._RunPipedShellCommand(
- 'pm dump foo.bar.baz | grep dataDir='),
- self.ShellError()):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.GetApplicationDataDirectory('foo.bar.baz')
-
-
-@mock.patch('time.sleep', mock.Mock())
-class DeviceUtilsWaitUntilFullyBootedTest(DeviceUtilsTest):
-
- def testWaitUntilFullyBooted_succeedsNoWifi(self):
- with self.assertCalls(
- self.call.adb.WaitForDevice(),
- # sd_card_ready
- (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
- (self.call.adb.Shell('test -d /fake/storage/path'), ''),
- # pm_ready
- (self.call.device._GetApplicationPathsInternal('android',
- skip_cache=True),
- ['package:/some/fake/path']),
- # boot_completed
- (self.call.device.GetProp('sys.boot_completed', cache=False), '1')):
- self.device.WaitUntilFullyBooted(wifi=False)
-
- def testWaitUntilFullyBooted_succeedsWithWifi(self):
- with self.assertCalls(
- self.call.adb.WaitForDevice(),
- # sd_card_ready
- (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
- (self.call.adb.Shell('test -d /fake/storage/path'), ''),
- # pm_ready
- (self.call.device._GetApplicationPathsInternal('android',
- skip_cache=True),
- ['package:/some/fake/path']),
- # boot_completed
- (self.call.device.GetProp('sys.boot_completed', cache=False), '1'),
- # wifi_enabled
- (self.call.adb.Shell('dumpsys wifi'),
- 'stuff\nWi-Fi is enabled\nmore stuff\n')):
- self.device.WaitUntilFullyBooted(wifi=True)
-
- def testWaitUntilFullyBooted_deviceNotInitiallyAvailable(self):
- with self.assertCalls(
- self.call.adb.WaitForDevice(),
- # sd_card_ready
- (self.call.device.GetExternalStoragePath(), self.AdbCommandError()),
- # sd_card_ready
- (self.call.device.GetExternalStoragePath(), self.AdbCommandError()),
- # sd_card_ready
- (self.call.device.GetExternalStoragePath(), self.AdbCommandError()),
- # sd_card_ready
- (self.call.device.GetExternalStoragePath(), self.AdbCommandError()),
- # sd_card_ready
- (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
- (self.call.adb.Shell('test -d /fake/storage/path'), ''),
- # pm_ready
- (self.call.device._GetApplicationPathsInternal('android',
- skip_cache=True),
- ['package:/some/fake/path']),
- # boot_completed
- (self.call.device.GetProp('sys.boot_completed', cache=False), '1')):
- self.device.WaitUntilFullyBooted(wifi=False)
-
- def testWaitUntilFullyBooted_deviceBrieflyOffline(self):
- with self.assertCalls(
- self.call.adb.WaitForDevice(),
- # sd_card_ready
- (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
- (self.call.adb.Shell('test -d /fake/storage/path'), ''),
- # pm_ready
- (self.call.device._GetApplicationPathsInternal('android',
- skip_cache=True),
- ['package:/some/fake/path']),
- # boot_completed
- (self.call.device.GetProp('sys.boot_completed', cache=False),
- self.AdbCommandError()),
- # boot_completed
- (self.call.device.GetProp('sys.boot_completed', cache=False), '1')):
- self.device.WaitUntilFullyBooted(wifi=False)
-
- def testWaitUntilFullyBooted_sdCardReadyFails_noPath(self):
- with self.assertCalls(
- self.call.adb.WaitForDevice(),
- # sd_card_ready
- (self.call.device.GetExternalStoragePath(), self.CommandError())):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.WaitUntilFullyBooted(wifi=False)
-
- def testWaitUntilFullyBooted_sdCardReadyFails_notExists(self):
- with self.assertCalls(
- self.call.adb.WaitForDevice(),
- # sd_card_ready
- (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
- (self.call.adb.Shell('test -d /fake/storage/path'), self.ShellError()),
- # sd_card_ready
- (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
- (self.call.adb.Shell('test -d /fake/storage/path'), self.ShellError()),
- # sd_card_ready
- (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
- (self.call.adb.Shell('test -d /fake/storage/path'),
- self.TimeoutError())):
- with self.assertRaises(device_errors.CommandTimeoutError):
- self.device.WaitUntilFullyBooted(wifi=False)
-
- def testWaitUntilFullyBooted_devicePmFails(self):
- with self.assertCalls(
- self.call.adb.WaitForDevice(),
- # sd_card_ready
- (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
- (self.call.adb.Shell('test -d /fake/storage/path'), ''),
- # pm_ready
- (self.call.device._GetApplicationPathsInternal('android',
- skip_cache=True),
- self.CommandError()),
- # pm_ready
- (self.call.device._GetApplicationPathsInternal('android',
- skip_cache=True),
- self.CommandError()),
- # pm_ready
- (self.call.device._GetApplicationPathsInternal('android',
- skip_cache=True),
- self.TimeoutError())):
- with self.assertRaises(device_errors.CommandTimeoutError):
- self.device.WaitUntilFullyBooted(wifi=False)
-
- def testWaitUntilFullyBooted_bootFails(self):
- with self.assertCalls(
- self.call.adb.WaitForDevice(),
- # sd_card_ready
- (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
- (self.call.adb.Shell('test -d /fake/storage/path'), ''),
- # pm_ready
- (self.call.device._GetApplicationPathsInternal('android',
- skip_cache=True),
- ['package:/some/fake/path']),
- # boot_completed
- (self.call.device.GetProp('sys.boot_completed', cache=False), '0'),
- # boot_completed
- (self.call.device.GetProp('sys.boot_completed', cache=False), '0'),
- # boot_completed
- (self.call.device.GetProp('sys.boot_completed', cache=False),
- self.TimeoutError())):
- with self.assertRaises(device_errors.CommandTimeoutError):
- self.device.WaitUntilFullyBooted(wifi=False)
-
- def testWaitUntilFullyBooted_wifiFails(self):
- with self.assertCalls(
- self.call.adb.WaitForDevice(),
- # sd_card_ready
- (self.call.device.GetExternalStoragePath(), '/fake/storage/path'),
- (self.call.adb.Shell('test -d /fake/storage/path'), ''),
- # pm_ready
- (self.call.device._GetApplicationPathsInternal('android',
- skip_cache=True),
- ['package:/some/fake/path']),
- # boot_completed
- (self.call.device.GetProp('sys.boot_completed', cache=False), '1'),
- # wifi_enabled
- (self.call.adb.Shell('dumpsys wifi'), 'stuff\nmore stuff\n'),
- # wifi_enabled
- (self.call.adb.Shell('dumpsys wifi'), 'stuff\nmore stuff\n'),
- # wifi_enabled
- (self.call.adb.Shell('dumpsys wifi'), self.TimeoutError())):
- with self.assertRaises(device_errors.CommandTimeoutError):
- self.device.WaitUntilFullyBooted(wifi=True)
-
-
-@mock.patch('time.sleep', mock.Mock())
-class DeviceUtilsRebootTest(DeviceUtilsTest):
-
- def testReboot_nonBlocking(self):
- with self.assertCalls(
- self.call.adb.Reboot(),
- (self.call.device.IsOnline(), True),
- (self.call.device.IsOnline(), False)):
- self.device.Reboot(block=False)
-
- def testReboot_blocking(self):
- with self.assertCalls(
- self.call.adb.Reboot(),
- (self.call.device.IsOnline(), True),
- (self.call.device.IsOnline(), False),
- self.call.device.WaitUntilFullyBooted(wifi=False)):
- self.device.Reboot(block=True)
-
- def testReboot_blockUntilWifi(self):
- with self.assertCalls(
- self.call.adb.Reboot(),
- (self.call.device.IsOnline(), True),
- (self.call.device.IsOnline(), False),
- self.call.device.WaitUntilFullyBooted(wifi=True)):
- self.device.Reboot(block=True, wifi=True)
-
-
-class DeviceUtilsInstallTest(DeviceUtilsTest):
-
- mock_apk = _MockApkHelper('/fake/test/app.apk', 'test.package', ['p1'])
-
- def testInstall_noPriorInstall(self):
- with self.patch_call(self.call.device.build_version_sdk, return_value=23):
- with self.assertCalls(
- (mock.call.os.path.exists('/fake/test/app.apk'), True),
- (self.call.device._GetApplicationPathsInternal('test.package'), []),
- self.call.adb.Install('/fake/test/app.apk', reinstall=False,
- allow_downgrade=False),
- (self.call.device.GrantPermissions('test.package', ['p1']), [])):
- self.device.Install(DeviceUtilsInstallTest.mock_apk, retries=0)
-
- def testInstall_permissionsPreM(self):
- with self.patch_call(self.call.device.build_version_sdk, return_value=20):
- with self.assertCalls(
- (mock.call.os.path.exists('/fake/test/app.apk'), True),
- (self.call.device._GetApplicationPathsInternal('test.package'), []),
- (self.call.adb.Install('/fake/test/app.apk', reinstall=False,
- allow_downgrade=False))):
- self.device.Install(DeviceUtilsInstallTest.mock_apk, retries=0)
-
- def testInstall_findPermissions(self):
- with self.patch_call(self.call.device.build_version_sdk, return_value=23):
- with self.assertCalls(
- (mock.call.os.path.exists('/fake/test/app.apk'), True),
- (self.call.device._GetApplicationPathsInternal('test.package'), []),
- (self.call.adb.Install('/fake/test/app.apk', reinstall=False,
- allow_downgrade=False)),
- (self.call.device.GrantPermissions('test.package', ['p1']), [])):
- self.device.Install(DeviceUtilsInstallTest.mock_apk, retries=0)
-
- def testInstall_passPermissions(self):
- with self.assertCalls(
- (mock.call.os.path.exists('/fake/test/app.apk'), True),
- (self.call.device._GetApplicationPathsInternal('test.package'), []),
- (self.call.adb.Install('/fake/test/app.apk', reinstall=False,
- allow_downgrade=False)),
- (self.call.device.GrantPermissions('test.package', ['p1', 'p2']), [])):
- self.device.Install(DeviceUtilsInstallTest.mock_apk, retries=0,
- permissions=['p1', 'p2'])
-
- def testInstall_differentPriorInstall(self):
- with self.assertCalls(
- (mock.call.os.path.exists('/fake/test/app.apk'), True),
- (self.call.device._GetApplicationPathsInternal('test.package'),
- ['/fake/data/app/test.package.apk']),
- (self.call.device._ComputeStaleApks('test.package',
- ['/fake/test/app.apk']),
- (['/fake/test/app.apk'], None)),
- self.call.device.Uninstall('test.package'),
- self.call.adb.Install('/fake/test/app.apk', reinstall=False,
- allow_downgrade=False)):
- self.device.Install(DeviceUtilsInstallTest.mock_apk, retries=0,
- permissions=[])
-
- def testInstall_differentPriorInstall_reinstall(self):
- with self.assertCalls(
- (mock.call.os.path.exists('/fake/test/app.apk'), True),
- (self.call.device._GetApplicationPathsInternal('test.package'),
- ['/fake/data/app/test.package.apk']),
- (self.call.device._ComputeStaleApks('test.package',
- ['/fake/test/app.apk']),
- (['/fake/test/app.apk'], None)),
- self.call.adb.Install('/fake/test/app.apk', reinstall=True,
- allow_downgrade=False)):
- self.device.Install(DeviceUtilsInstallTest.mock_apk,
- reinstall=True, retries=0, permissions=[])
-
- def testInstall_identicalPriorInstall_reinstall(self):
- with self.assertCalls(
- (mock.call.os.path.exists('/fake/test/app.apk'), True),
- (self.call.device._GetApplicationPathsInternal('test.package'),
- ['/fake/data/app/test.package.apk']),
- (self.call.device._ComputeStaleApks('test.package',
- ['/fake/test/app.apk']),
- ([], None)),
- (self.call.device.ForceStop('test.package'))):
- self.device.Install(DeviceUtilsInstallTest.mock_apk,
- reinstall=True, retries=0, permissions=[])
-
- def testInstall_missingApk(self):
- with self.assertCalls(
- (mock.call.os.path.exists('/fake/test/app.apk'), False)):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.Install(DeviceUtilsInstallTest.mock_apk, retries=0)
-
- def testInstall_fails(self):
- with self.assertCalls(
- (mock.call.os.path.exists('/fake/test/app.apk'), True),
- (self.call.device._GetApplicationPathsInternal('test.package'), []),
- (self.call.adb.Install('/fake/test/app.apk', reinstall=False,
- allow_downgrade=False),
- self.CommandError('Failure\r\n'))):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.Install(DeviceUtilsInstallTest.mock_apk, retries=0)
-
- def testInstall_downgrade(self):
- with self.assertCalls(
- (mock.call.os.path.exists('/fake/test/app.apk'), True),
- (self.call.device._GetApplicationPathsInternal('test.package'),
- ['/fake/data/app/test.package.apk']),
- (self.call.device._ComputeStaleApks('test.package',
- ['/fake/test/app.apk']),
- (['/fake/test/app.apk'], None)),
- self.call.adb.Install('/fake/test/app.apk', reinstall=True,
- allow_downgrade=True)):
- self.device.Install(DeviceUtilsInstallTest.mock_apk,
- reinstall=True, retries=0, permissions=[], allow_downgrade=True)
-
-
-class DeviceUtilsInstallSplitApkTest(DeviceUtilsTest):
-
- mock_apk = _MockApkHelper('base.apk', 'test.package', ['p1'])
-
- def testInstallSplitApk_noPriorInstall(self):
- with self.assertCalls(
- (self.call.device._CheckSdkLevel(21)),
- (mock.call.devil.android.sdk.split_select.SelectSplits(
- self.device, 'base.apk',
- ['split1.apk', 'split2.apk', 'split3.apk'],
- allow_cached_props=False),
- ['split2.apk']),
- (mock.call.os.path.exists('base.apk'), True),
- (mock.call.os.path.exists('split2.apk'), True),
- (self.call.device._GetApplicationPathsInternal('test.package'), []),
- (self.call.adb.InstallMultiple(
- ['base.apk', 'split2.apk'], partial=None, reinstall=False,
- allow_downgrade=False))):
- self.device.InstallSplitApk(DeviceUtilsInstallSplitApkTest.mock_apk,
- ['split1.apk', 'split2.apk', 'split3.apk'], permissions=[], retries=0)
-
- def testInstallSplitApk_partialInstall(self):
- with self.assertCalls(
- (self.call.device._CheckSdkLevel(21)),
- (mock.call.devil.android.sdk.split_select.SelectSplits(
- self.device, 'base.apk',
- ['split1.apk', 'split2.apk', 'split3.apk'],
- allow_cached_props=False),
- ['split2.apk']),
- (mock.call.os.path.exists('base.apk'), True),
- (mock.call.os.path.exists('split2.apk'), True),
- (self.call.device._GetApplicationPathsInternal('test.package'),
- ['base-on-device.apk', 'split2-on-device.apk']),
- (self.call.device._ComputeStaleApks('test.package',
- ['base.apk', 'split2.apk']),
- (['split2.apk'], None)),
- (self.call.adb.InstallMultiple(
- ['split2.apk'], partial='test.package', reinstall=True,
- allow_downgrade=False))):
- self.device.InstallSplitApk(DeviceUtilsInstallSplitApkTest.mock_apk,
- ['split1.apk', 'split2.apk', 'split3.apk'],
- reinstall=True, permissions=[], retries=0)
-
- def testInstallSplitApk_downgrade(self):
- with self.assertCalls(
- (self.call.device._CheckSdkLevel(21)),
- (mock.call.devil.android.sdk.split_select.SelectSplits(
- self.device, 'base.apk',
- ['split1.apk', 'split2.apk', 'split3.apk'],
- allow_cached_props=False),
- ['split2.apk']),
- (mock.call.os.path.exists('base.apk'), True),
- (mock.call.os.path.exists('split2.apk'), True),
- (self.call.device._GetApplicationPathsInternal('test.package'),
- ['base-on-device.apk', 'split2-on-device.apk']),
- (self.call.device._ComputeStaleApks('test.package',
- ['base.apk', 'split2.apk']),
- (['split2.apk'], None)),
- (self.call.adb.InstallMultiple(
- ['split2.apk'], partial='test.package', reinstall=True,
- allow_downgrade=True))):
- self.device.InstallSplitApk(DeviceUtilsInstallSplitApkTest.mock_apk,
- ['split1.apk', 'split2.apk', 'split3.apk'],
- reinstall=True, permissions=[], retries=0,
- allow_downgrade=True)
-
- def testInstallSplitApk_missingSplit(self):
- with self.assertCalls(
- (self.call.device._CheckSdkLevel(21)),
- (mock.call.devil.android.sdk.split_select.SelectSplits(
- self.device, 'base.apk',
- ['split1.apk', 'split2.apk', 'split3.apk'],
- allow_cached_props=False),
- ['split2.apk']),
- (mock.call.os.path.exists('base.apk'), True),
- (mock.call.os.path.exists('split2.apk'), False)):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.InstallSplitApk(DeviceUtilsInstallSplitApkTest.mock_apk,
- ['split1.apk', 'split2.apk', 'split3.apk'], permissions=[],
- retries=0)
-
-
-class DeviceUtilsUninstallTest(DeviceUtilsTest):
-
- def testUninstall_callsThrough(self):
- with self.assertCalls(
- (self.call.device._GetApplicationPathsInternal('test.package'),
- ['/path.apk']),
- self.call.adb.Uninstall('test.package', True)):
- self.device.Uninstall('test.package', True)
-
- def testUninstall_noop(self):
- with self.assertCalls(
- (self.call.device._GetApplicationPathsInternal('test.package'), [])):
- self.device.Uninstall('test.package', True)
-
-
-class DeviceUtilsSuTest(DeviceUtilsTest):
-
- def testSu_preM(self):
- with self.patch_call(
- self.call.device.build_version_sdk,
- return_value=version_codes.LOLLIPOP_MR1):
- self.assertEquals('su -c foo', self.device._Su('foo'))
-
- def testSu_mAndAbove(self):
- with self.patch_call(
- self.call.device.build_version_sdk,
- return_value=version_codes.MARSHMALLOW):
- self.assertEquals('su 0 foo', self.device._Su('foo'))
-
-
-class DeviceUtilsRunShellCommandTest(DeviceUtilsTest):
-
- def setUp(self):
- super(DeviceUtilsRunShellCommandTest, self).setUp()
- self.device.NeedsSU = mock.Mock(return_value=False)
-
- def testRunShellCommand_commandAsList(self):
- with self.assertCall(self.call.adb.Shell('pm list packages'), ''):
- self.device.RunShellCommand(
- ['pm', 'list', 'packages'], check_return=True)
-
- def testRunShellCommand_commandAsListQuoted(self):
- with self.assertCall(self.call.adb.Shell("echo 'hello world' '$10'"), ''):
- self.device.RunShellCommand(
- ['echo', 'hello world', '$10'], check_return=True)
-
- def testRunShellCommand_commandAsString(self):
- with self.assertCall(self.call.adb.Shell('echo "$VAR"'), ''):
- self.device.RunShellCommand(
- 'echo "$VAR"', shell=True, check_return=True)
-
- def testNewRunShellImpl_withEnv(self):
- with self.assertCall(
- self.call.adb.Shell('VAR=some_string echo "$VAR"'), ''):
- self.device.RunShellCommand(
- 'echo "$VAR"', shell=True, check_return=True,
- env={'VAR': 'some_string'})
-
- def testNewRunShellImpl_withEnvQuoted(self):
- with self.assertCall(
- self.call.adb.Shell('PATH="$PATH:/other/path" run_this'), ''):
- self.device.RunShellCommand(
- ['run_this'], check_return=True, env={'PATH': '$PATH:/other/path'})
-
- def testNewRunShellImpl_withEnv_failure(self):
- with self.assertRaises(KeyError):
- self.device.RunShellCommand(
- ['some_cmd'], check_return=True, env={'INVALID NAME': 'value'})
-
- def testNewRunShellImpl_withCwd(self):
- with self.assertCall(self.call.adb.Shell('cd /some/test/path && ls'), ''):
- self.device.RunShellCommand(
- ['ls'], check_return=True, cwd='/some/test/path')
-
- def testNewRunShellImpl_withCwdQuoted(self):
- with self.assertCall(
- self.call.adb.Shell("cd '/some test/path with/spaces' && ls"), ''):
- self.device.RunShellCommand(
- ['ls'], check_return=True, cwd='/some test/path with/spaces')
-
- def testRunShellCommand_withHugeCmd(self):
- payload = 'hi! ' * 1024
- expected_cmd = "echo '%s'" % payload
- with self.assertCalls(
- (mock.call.devil.android.device_temp_file.DeviceTempFile(
- self.adb, suffix='.sh'), MockTempFile('/sdcard/temp-123.sh')),
- self.call.device._WriteFileWithPush('/sdcard/temp-123.sh', expected_cmd),
- (self.call.adb.Shell('sh /sdcard/temp-123.sh'), payload + '\n')):
- self.assertEquals(
- [payload],
- self.device.RunShellCommand(['echo', payload], check_return=True))
-
- def testRunShellCommand_withHugeCmdAndSu(self):
- payload = 'hi! ' * 1024
- expected_cmd_without_su = """sh -c 'echo '"'"'%s'"'"''""" % payload
- expected_cmd = 'su -c %s' % expected_cmd_without_su
- with self.assertCalls(
- (self.call.device.NeedsSU(), True),
- (self.call.device._Su(expected_cmd_without_su), expected_cmd),
- (mock.call.devil.android.device_temp_file.DeviceTempFile(
- self.adb, suffix='.sh'), MockTempFile('/sdcard/temp-123.sh')),
- self.call.device._WriteFileWithPush('/sdcard/temp-123.sh', expected_cmd),
- (self.call.adb.Shell('sh /sdcard/temp-123.sh'), payload + '\n')):
- self.assertEquals(
- [payload],
- self.device.RunShellCommand(
- ['echo', payload], check_return=True, as_root=True))
-
- def testRunShellCommand_withSu(self):
- expected_cmd_without_su = "sh -c 'setprop service.adb.root 0'"
- expected_cmd = 'su -c %s' % expected_cmd_without_su
- with self.assertCalls(
- (self.call.device.NeedsSU(), True),
- (self.call.device._Su(expected_cmd_without_su), expected_cmd),
- (self.call.adb.Shell(expected_cmd), '')):
- self.device.RunShellCommand(
- ['setprop', 'service.adb.root', '0'],
- check_return=True, as_root=True)
-
- def testRunShellCommand_withRunAs(self):
- expected_cmd_without_run_as = "sh -c 'mkdir -p files'"
- expected_cmd = (
- 'run-as org.devil.test_package %s' % expected_cmd_without_run_as)
- with self.assertCall(self.call.adb.Shell(expected_cmd), ''):
- self.device.RunShellCommand(
- ['mkdir', '-p', 'files'],
- check_return=True, run_as='org.devil.test_package')
-
- def testRunShellCommand_withRunAsAndSu(self):
- expected_cmd_with_nothing = "sh -c 'mkdir -p files'"
- expected_cmd_with_run_as = (
- 'run-as org.devil.test_package %s' % expected_cmd_with_nothing)
- expected_cmd_without_su = (
- 'sh -c %s' % cmd_helper.SingleQuote(expected_cmd_with_run_as))
- expected_cmd = 'su -c %s' % expected_cmd_without_su
- with self.assertCalls(
- (self.call.device.NeedsSU(), True),
- (self.call.device._Su(expected_cmd_without_su), expected_cmd),
- (self.call.adb.Shell(expected_cmd), '')):
- self.device.RunShellCommand(
- ['mkdir', '-p', 'files'],
- check_return=True, run_as='org.devil.test_package',
- as_root=True)
-
- def testRunShellCommand_manyLines(self):
- cmd = 'ls /some/path'
- with self.assertCall(self.call.adb.Shell(cmd), 'file1\nfile2\nfile3\n'):
- self.assertEquals(
- ['file1', 'file2', 'file3'],
- self.device.RunShellCommand(cmd.split(), check_return=True))
-
- def testRunShellCommand_manyLinesRawOutput(self):
- cmd = 'ls /some/path'
- with self.assertCall(self.call.adb.Shell(cmd), '\rfile1\nfile2\r\nfile3\n'):
- self.assertEquals(
- '\rfile1\nfile2\r\nfile3\n',
- self.device.RunShellCommand(
- cmd.split(), check_return=True, raw_output=True))
-
- def testRunShellCommand_singleLine_success(self):
- cmd = 'echo $VALUE'
- with self.assertCall(self.call.adb.Shell(cmd), 'some value\n'):
- self.assertEquals(
- 'some value',
- self.device.RunShellCommand(
- cmd, shell=True, check_return=True, single_line=True))
-
- def testRunShellCommand_singleLine_successEmptyLine(self):
- cmd = 'echo $VALUE'
- with self.assertCall(self.call.adb.Shell(cmd), '\n'):
- self.assertEquals(
- '',
- self.device.RunShellCommand(
- cmd, shell=True, check_return=True, single_line=True))
-
- def testRunShellCommand_singleLine_successWithoutEndLine(self):
- cmd = 'echo -n $VALUE'
- with self.assertCall(self.call.adb.Shell(cmd), 'some value'):
- self.assertEquals(
- 'some value',
- self.device.RunShellCommand(
- cmd, shell=True, check_return=True, single_line=True))
-
- def testRunShellCommand_singleLine_successNoOutput(self):
- cmd = 'echo -n $VALUE'
- with self.assertCall(self.call.adb.Shell(cmd), ''):
- self.assertEquals(
- '',
- self.device.RunShellCommand(
- cmd, shell=True, check_return=True, single_line=True))
-
- def testRunShellCommand_singleLine_failTooManyLines(self):
- cmd = 'echo $VALUE'
- with self.assertCall(self.call.adb.Shell(cmd),
- 'some value\nanother value\n'):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.RunShellCommand(
- cmd, shell=True, check_return=True, single_line=True)
-
- def testRunShellCommand_checkReturn_success(self):
- cmd = 'echo $ANDROID_DATA'
- output = '/data\n'
- with self.assertCall(self.call.adb.Shell(cmd), output):
- self.assertEquals(
- [output.rstrip()],
- self.device.RunShellCommand(cmd, shell=True, check_return=True))
-
- def testRunShellCommand_checkReturn_failure(self):
- cmd = 'ls /root'
- output = 'opendir failed, Permission denied\n'
- with self.assertCall(self.call.adb.Shell(cmd), self.ShellError(output)):
- with self.assertRaises(device_errors.AdbCommandFailedError):
- self.device.RunShellCommand(cmd.split(), check_return=True)
-
- def testRunShellCommand_checkReturn_disabled(self):
- cmd = 'ls /root'
- output = 'opendir failed, Permission denied\n'
- with self.assertCall(self.call.adb.Shell(cmd), self.ShellError(output)):
- self.assertEquals(
- [output.rstrip()],
- self.device.RunShellCommand(cmd.split(), check_return=False))
-
- def testRunShellCommand_largeOutput_enabled(self):
- cmd = 'echo $VALUE'
- temp_file = MockTempFile('/sdcard/temp-123')
- cmd_redirect = '( %s )>%s' % (cmd, temp_file.name)
- with self.assertCalls(
- (mock.call.devil.android.device_temp_file.DeviceTempFile(self.adb),
- temp_file),
- (self.call.adb.Shell(cmd_redirect)),
- (self.call.device.ReadFile(temp_file.name, force_pull=True),
- 'something')):
- self.assertEquals(
- ['something'],
- self.device.RunShellCommand(
- cmd, shell=True, large_output=True, check_return=True))
-
- def testRunShellCommand_largeOutput_disabledNoTrigger(self):
- cmd = 'something'
- with self.assertCall(self.call.adb.Shell(cmd), self.ShellError('')):
- with self.assertRaises(device_errors.AdbCommandFailedError):
- self.device.RunShellCommand([cmd], check_return=True)
-
- def testRunShellCommand_largeOutput_disabledTrigger(self):
- cmd = 'echo $VALUE'
- temp_file = MockTempFile('/sdcard/temp-123')
- cmd_redirect = '( %s )>%s' % (cmd, temp_file.name)
- with self.assertCalls(
- (self.call.adb.Shell(cmd), self.ShellError('', None)),
- (mock.call.devil.android.device_temp_file.DeviceTempFile(self.adb),
- temp_file),
- (self.call.adb.Shell(cmd_redirect)),
- (self.call.device.ReadFile(mock.ANY, force_pull=True),
- 'something')):
- self.assertEquals(
- ['something'],
- self.device.RunShellCommand(cmd, shell=True, check_return=True))
-
-
-class DeviceUtilsRunPipedShellCommandTest(DeviceUtilsTest):
-
- def testRunPipedShellCommand_success(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- 'ps | grep foo; echo "PIPESTATUS: ${PIPESTATUS[@]}"',
- shell=True, check_return=True),
- ['This line contains foo', 'PIPESTATUS: 0 0']):
- self.assertEquals(['This line contains foo'],
- self.device._RunPipedShellCommand('ps | grep foo'))
-
- def testRunPipedShellCommand_firstCommandFails(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- 'ps | grep foo; echo "PIPESTATUS: ${PIPESTATUS[@]}"',
- shell=True, check_return=True),
- ['PIPESTATUS: 1 0']):
- with self.assertRaises(device_errors.AdbShellCommandFailedError) as ec:
- self.device._RunPipedShellCommand('ps | grep foo')
- self.assertEquals([1, 0], ec.exception.status)
-
- def testRunPipedShellCommand_secondCommandFails(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- 'ps | grep foo; echo "PIPESTATUS: ${PIPESTATUS[@]}"',
- shell=True, check_return=True),
- ['PIPESTATUS: 0 1']):
- with self.assertRaises(device_errors.AdbShellCommandFailedError) as ec:
- self.device._RunPipedShellCommand('ps | grep foo')
- self.assertEquals([0, 1], ec.exception.status)
-
- def testRunPipedShellCommand_outputCutOff(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- 'ps | grep foo; echo "PIPESTATUS: ${PIPESTATUS[@]}"',
- shell=True, check_return=True),
- ['foo.bar'] * 256 + ['foo.ba']):
- with self.assertRaises(device_errors.AdbShellCommandFailedError) as ec:
- self.device._RunPipedShellCommand('ps | grep foo')
- self.assertIs(None, ec.exception.status)
-
-
-@mock.patch('time.sleep', mock.Mock())
-class DeviceUtilsKillAllTest(DeviceUtilsTest):
-
- def testKillAll_noMatchingProcessesFailure(self):
- with self.assertCall(self.call.device.GetPids('test_process'), {}):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.KillAll('test_process')
-
- def testKillAll_noMatchingProcessesQuiet(self):
- with self.assertCall(self.call.device.GetPids('test_process'), {}):
- self.assertEqual(0, self.device.KillAll('test_process', quiet=True))
-
- def testKillAll_nonblocking(self):
- with self.assertCalls(
- (self.call.device.GetPids('some.process'),
- {'some.process': ['1234'], 'some.processing.thing': ['5678']}),
- (self.call.adb.Shell('kill -9 1234 5678'), '')):
- self.assertEquals(
- 2, self.device.KillAll('some.process', blocking=False))
-
- def testKillAll_blocking(self):
- with self.assertCalls(
- (self.call.device.GetPids('some.process'),
- {'some.process': ['1234'], 'some.processing.thing': ['5678']}),
- (self.call.adb.Shell('kill -9 1234 5678'), ''),
- (self.call.device.GetPids('some.process'),
- {'some.processing.thing': ['5678']}),
- (self.call.device.GetPids('some.process'),
- {'some.process': ['1111']})): # Other instance with different pid.
- self.assertEquals(
- 2, self.device.KillAll('some.process', blocking=True))
-
- def testKillAll_exactNonblocking(self):
- with self.assertCalls(
- (self.call.device.GetPids('some.process'),
- {'some.process': ['1234'], 'some.processing.thing': ['5678']}),
- (self.call.adb.Shell('kill -9 1234'), '')):
- self.assertEquals(
- 1, self.device.KillAll('some.process', exact=True, blocking=False))
-
- def testKillAll_exactBlocking(self):
- with self.assertCalls(
- (self.call.device.GetPids('some.process'),
- {'some.process': ['1234'], 'some.processing.thing': ['5678']}),
- (self.call.adb.Shell('kill -9 1234'), ''),
- (self.call.device.GetPids('some.process'),
- {'some.process': ['1234'], 'some.processing.thing': ['5678']}),
- (self.call.device.GetPids('some.process'),
- {'some.processing.thing': ['5678']})):
- self.assertEquals(
- 1, self.device.KillAll('some.process', exact=True, blocking=True))
-
- def testKillAll_root(self):
- with self.assertCalls(
- (self.call.device.GetPids('some.process'), {'some.process': ['1234']}),
- (self.call.device.NeedsSU(), True),
- (self.call.device._Su("sh -c 'kill -9 1234'"),
- "su -c sh -c 'kill -9 1234'"),
- (self.call.adb.Shell("su -c sh -c 'kill -9 1234'"), '')):
- self.assertEquals(
- 1, self.device.KillAll('some.process', as_root=True))
-
- def testKillAll_sigterm(self):
- with self.assertCalls(
- (self.call.device.GetPids('some.process'),
- {'some.process': ['1234']}),
- (self.call.adb.Shell('kill -15 1234'), '')):
- self.assertEquals(
- 1, self.device.KillAll('some.process', signum=device_signal.SIGTERM))
-
- def testKillAll_multipleInstances(self):
- with self.assertCalls(
- (self.call.device.GetPids('some.process'),
- {'some.process': ['1234', '4567']}),
- (self.call.adb.Shell('kill -15 1234 4567'), '')):
- self.assertEquals(
- 2, self.device.KillAll('some.process', signum=device_signal.SIGTERM))
-
-
-class DeviceUtilsStartActivityTest(DeviceUtilsTest):
-
- def testStartActivity_actionOnly(self):
- test_intent = intent.Intent(action='android.intent.action.VIEW')
- with self.assertCall(
- self.call.adb.Shell('am start '
- '-a android.intent.action.VIEW'),
- 'Starting: Intent { act=android.intent.action.VIEW }'):
- self.device.StartActivity(test_intent)
-
- def testStartActivity_success(self):
- test_intent = intent.Intent(action='android.intent.action.VIEW',
- package='test.package',
- activity='.Main')
- with self.assertCall(
- self.call.adb.Shell('am start '
- '-a android.intent.action.VIEW '
- '-n test.package/.Main'),
- 'Starting: Intent { act=android.intent.action.VIEW }'):
- self.device.StartActivity(test_intent)
-
- def testStartActivity_failure(self):
- test_intent = intent.Intent(action='android.intent.action.VIEW',
- package='test.package',
- activity='.Main')
- with self.assertCall(
- self.call.adb.Shell('am start '
- '-a android.intent.action.VIEW '
- '-n test.package/.Main'),
- 'Error: Failed to start test activity'):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.StartActivity(test_intent)
-
- def testStartActivity_blocking(self):
- test_intent = intent.Intent(action='android.intent.action.VIEW',
- package='test.package',
- activity='.Main')
- with self.assertCall(
- self.call.adb.Shell('am start '
- '-W '
- '-a android.intent.action.VIEW '
- '-n test.package/.Main'),
- 'Starting: Intent { act=android.intent.action.VIEW }'):
- self.device.StartActivity(test_intent, blocking=True)
-
- def testStartActivity_withCategory(self):
- test_intent = intent.Intent(action='android.intent.action.VIEW',
- package='test.package',
- activity='.Main',
- category='android.intent.category.HOME')
- with self.assertCall(
- self.call.adb.Shell('am start '
- '-a android.intent.action.VIEW '
- '-c android.intent.category.HOME '
- '-n test.package/.Main'),
- 'Starting: Intent { act=android.intent.action.VIEW }'):
- self.device.StartActivity(test_intent)
-
- def testStartActivity_withMultipleCategories(self):
- test_intent = intent.Intent(action='android.intent.action.VIEW',
- package='test.package',
- activity='.Main',
- category=['android.intent.category.HOME',
- 'android.intent.category.BROWSABLE'])
- with self.assertCall(
- self.call.adb.Shell('am start '
- '-a android.intent.action.VIEW '
- '-c android.intent.category.HOME '
- '-c android.intent.category.BROWSABLE '
- '-n test.package/.Main'),
- 'Starting: Intent { act=android.intent.action.VIEW }'):
- self.device.StartActivity(test_intent)
-
- def testStartActivity_withData(self):
- test_intent = intent.Intent(action='android.intent.action.VIEW',
- package='test.package',
- activity='.Main',
- data='http://www.google.com/')
- with self.assertCall(
- self.call.adb.Shell('am start '
- '-a android.intent.action.VIEW '
- '-d http://www.google.com/ '
- '-n test.package/.Main'),
- 'Starting: Intent { act=android.intent.action.VIEW }'):
- self.device.StartActivity(test_intent)
-
- def testStartActivity_withStringExtra(self):
- test_intent = intent.Intent(action='android.intent.action.VIEW',
- package='test.package',
- activity='.Main',
- extras={'foo': 'test'})
- with self.assertCall(
- self.call.adb.Shell('am start '
- '-a android.intent.action.VIEW '
- '-n test.package/.Main '
- '--es foo test'),
- 'Starting: Intent { act=android.intent.action.VIEW }'):
- self.device.StartActivity(test_intent)
-
- def testStartActivity_withBoolExtra(self):
- test_intent = intent.Intent(action='android.intent.action.VIEW',
- package='test.package',
- activity='.Main',
- extras={'foo': True})
- with self.assertCall(
- self.call.adb.Shell('am start '
- '-a android.intent.action.VIEW '
- '-n test.package/.Main '
- '--ez foo True'),
- 'Starting: Intent { act=android.intent.action.VIEW }'):
- self.device.StartActivity(test_intent)
-
- def testStartActivity_withIntExtra(self):
- test_intent = intent.Intent(action='android.intent.action.VIEW',
- package='test.package',
- activity='.Main',
- extras={'foo': 123})
- with self.assertCall(
- self.call.adb.Shell('am start '
- '-a android.intent.action.VIEW '
- '-n test.package/.Main '
- '--ei foo 123'),
- 'Starting: Intent { act=android.intent.action.VIEW }'):
- self.device.StartActivity(test_intent)
-
- def testStartActivity_withTraceFile(self):
- test_intent = intent.Intent(action='android.intent.action.VIEW',
- package='test.package',
- activity='.Main')
- with self.assertCall(
- self.call.adb.Shell('am start '
- '--start-profiler test_trace_file.out '
- '-a android.intent.action.VIEW '
- '-n test.package/.Main'),
- 'Starting: Intent { act=android.intent.action.VIEW }'):
- self.device.StartActivity(test_intent,
- trace_file_name='test_trace_file.out')
-
- def testStartActivity_withForceStop(self):
- test_intent = intent.Intent(action='android.intent.action.VIEW',
- package='test.package',
- activity='.Main')
- with self.assertCall(
- self.call.adb.Shell('am start '
- '-S '
- '-a android.intent.action.VIEW '
- '-n test.package/.Main'),
- 'Starting: Intent { act=android.intent.action.VIEW }'):
- self.device.StartActivity(test_intent, force_stop=True)
-
- def testStartActivity_withFlags(self):
- test_intent = intent.Intent(action='android.intent.action.VIEW',
- package='test.package',
- activity='.Main',
- flags=[
- intent.FLAG_ACTIVITY_NEW_TASK,
- intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
- ])
- with self.assertCall(
- self.call.adb.Shell('am start '
- '-a android.intent.action.VIEW '
- '-n test.package/.Main '
- '-f 0x10200000'),
- 'Starting: Intent { act=android.intent.action.VIEW }'):
- self.device.StartActivity(test_intent)
-
-
-class DeviceUtilsStartInstrumentationTest(DeviceUtilsTest):
-
- def testStartInstrumentation_nothing(self):
- with self.assertCalls(
- self.call.device.RunShellCommand(
- 'p=test.package;am instrument "$p"/.TestInstrumentation',
- shell=True, check_return=True, large_output=True)):
- self.device.StartInstrumentation(
- 'test.package/.TestInstrumentation',
- finish=False, raw=False, extras=None)
-
- def testStartInstrumentation_finish(self):
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- 'p=test.package;am instrument -w "$p"/.TestInstrumentation',
- shell=True, check_return=True, large_output=True),
- ['OK (1 test)'])):
- output = self.device.StartInstrumentation(
- 'test.package/.TestInstrumentation',
- finish=True, raw=False, extras=None)
- self.assertEquals(['OK (1 test)'], output)
-
- def testStartInstrumentation_raw(self):
- with self.assertCalls(
- self.call.device.RunShellCommand(
- 'p=test.package;am instrument -r "$p"/.TestInstrumentation',
- shell=True, check_return=True, large_output=True)):
- self.device.StartInstrumentation(
- 'test.package/.TestInstrumentation',
- finish=False, raw=True, extras=None)
-
- def testStartInstrumentation_extras(self):
- with self.assertCalls(
- self.call.device.RunShellCommand(
- 'p=test.package;am instrument -e "$p".foo Foo -e bar \'Val \'"$p" '
- '"$p"/.TestInstrumentation',
- shell=True, check_return=True, large_output=True)):
- self.device.StartInstrumentation(
- 'test.package/.TestInstrumentation',
- finish=False, raw=False, extras={'test.package.foo': 'Foo',
- 'bar': 'Val test.package'})
-
-
-class DeviceUtilsBroadcastIntentTest(DeviceUtilsTest):
-
- def testBroadcastIntent_noExtras(self):
- test_intent = intent.Intent(action='test.package.with.an.INTENT')
- with self.assertCall(
- self.call.adb.Shell('am broadcast -a test.package.with.an.INTENT'),
- 'Broadcasting: Intent { act=test.package.with.an.INTENT } '):
- self.device.BroadcastIntent(test_intent)
-
- def testBroadcastIntent_withExtra(self):
- test_intent = intent.Intent(action='test.package.with.an.INTENT',
- extras={'foo': 'bar value'})
- with self.assertCall(
- self.call.adb.Shell(
- "am broadcast -a test.package.with.an.INTENT --es foo 'bar value'"),
- 'Broadcasting: Intent { act=test.package.with.an.INTENT } '):
- self.device.BroadcastIntent(test_intent)
-
- def testBroadcastIntent_withExtra_noValue(self):
- test_intent = intent.Intent(action='test.package.with.an.INTENT',
- extras={'foo': None})
- with self.assertCall(
- self.call.adb.Shell(
- 'am broadcast -a test.package.with.an.INTENT --esn foo'),
- 'Broadcasting: Intent { act=test.package.with.an.INTENT } '):
- self.device.BroadcastIntent(test_intent)
-
-
-class DeviceUtilsGoHomeTest(DeviceUtilsTest):
-
- def testGoHome_popupsExist(self):
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- ['dumpsys', 'window', 'windows'], check_return=True,
- large_output=True), []),
- (self.call.device.RunShellCommand(
- ['am', 'start', '-W', '-a', 'android.intent.action.MAIN',
- '-c', 'android.intent.category.HOME'], check_return=True),
- 'Starting: Intent { act=android.intent.action.MAIN }\r\n'''),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'window', 'windows'], check_return=True,
- large_output=True), []),
- (self.call.device.RunShellCommand(
- ['input', 'keyevent', '66'], check_return=True)),
- (self.call.device.RunShellCommand(
- ['input', 'keyevent', '4'], check_return=True)),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'window', 'windows'], check_return=True,
- large_output=True),
- ['mCurrentFocus Launcher'])):
- self.device.GoHome()
-
- def testGoHome_willRetry(self):
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- ['dumpsys', 'window', 'windows'], check_return=True,
- large_output=True), []),
- (self.call.device.RunShellCommand(
- ['am', 'start', '-W', '-a', 'android.intent.action.MAIN',
- '-c', 'android.intent.category.HOME'], check_return=True),
- 'Starting: Intent { act=android.intent.action.MAIN }\r\n'''),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'window', 'windows'], check_return=True,
- large_output=True), []),
- (self.call.device.RunShellCommand(
- ['input', 'keyevent', '66'], check_return=True,)),
- (self.call.device.RunShellCommand(
- ['input', 'keyevent', '4'], check_return=True)),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'window', 'windows'], check_return=True,
- large_output=True), []),
- (self.call.device.RunShellCommand(
- ['input', 'keyevent', '66'], check_return=True)),
- (self.call.device.RunShellCommand(
- ['input', 'keyevent', '4'], check_return=True)),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'window', 'windows'], check_return=True,
- large_output=True),
- self.TimeoutError())):
- with self.assertRaises(device_errors.CommandTimeoutError):
- self.device.GoHome()
-
- def testGoHome_alreadyFocused(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['dumpsys', 'window', 'windows'], check_return=True,
- large_output=True),
- ['mCurrentFocus Launcher']):
- self.device.GoHome()
-
- def testGoHome_alreadyFocusedAlternateCase(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['dumpsys', 'window', 'windows'], check_return=True,
- large_output=True),
- [' mCurrentFocus .launcher/.']):
- self.device.GoHome()
-
- def testGoHome_obtainsFocusAfterGoingHome(self):
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- ['dumpsys', 'window', 'windows'], check_return=True,
- large_output=True), []),
- (self.call.device.RunShellCommand(
- ['am', 'start', '-W', '-a', 'android.intent.action.MAIN',
- '-c', 'android.intent.category.HOME'], check_return=True),
- 'Starting: Intent { act=android.intent.action.MAIN }\r\n'''),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'window', 'windows'], check_return=True,
- large_output=True),
- ['mCurrentFocus Launcher'])):
- self.device.GoHome()
-
-
-class DeviceUtilsForceStopTest(DeviceUtilsTest):
-
- def testForceStop(self):
- with self.assertCall(
- self.call.adb.Shell('p=test.package;if [[ "$(ps)" = *$p* ]]; then '
- 'am force-stop $p; fi'),
- ''):
- self.device.ForceStop('test.package')
-
-
-class DeviceUtilsClearApplicationStateTest(DeviceUtilsTest):
-
- def testClearApplicationState_setPermissions(self):
- with self.assertCalls(
- (self.call.device.GetProp('ro.build.version.sdk', cache=True), '17'),
- (self.call.device._GetApplicationPathsInternal('this.package.exists'),
- ['/data/app/this.package.exists.apk']),
- (self.call.device.RunShellCommand(
- ['pm', 'clear', 'this.package.exists'],
- check_return=True),
- ['Success']),
- (self.call.device.GrantPermissions(
- 'this.package.exists', ['p1']), [])):
- self.device.ClearApplicationState(
- 'this.package.exists', permissions=['p1'])
-
- def testClearApplicationState_packageDoesntExist(self):
- with self.assertCalls(
- (self.call.device.GetProp('ro.build.version.sdk', cache=True), '11'),
- (self.call.device._GetApplicationPathsInternal('does.not.exist'),
- [])):
- self.device.ClearApplicationState('does.not.exist')
-
- def testClearApplicationState_packageDoesntExistOnAndroidJBMR2OrAbove(self):
- with self.assertCalls(
- (self.call.device.GetProp('ro.build.version.sdk', cache=True), '18'),
- (self.call.device.RunShellCommand(
- ['pm', 'clear', 'this.package.does.not.exist'],
- check_return=True),
- ['Failed'])):
- self.device.ClearApplicationState('this.package.does.not.exist')
-
- def testClearApplicationState_packageExists(self):
- with self.assertCalls(
- (self.call.device.GetProp('ro.build.version.sdk', cache=True), '17'),
- (self.call.device._GetApplicationPathsInternal('this.package.exists'),
- ['/data/app/this.package.exists.apk']),
- (self.call.device.RunShellCommand(
- ['pm', 'clear', 'this.package.exists'],
- check_return=True),
- ['Success'])):
- self.device.ClearApplicationState('this.package.exists')
-
- def testClearApplicationState_packageExistsOnAndroidJBMR2OrAbove(self):
- with self.assertCalls(
- (self.call.device.GetProp('ro.build.version.sdk', cache=True), '18'),
- (self.call.device.RunShellCommand(
- ['pm', 'clear', 'this.package.exists'],
- check_return=True),
- ['Success'])):
- self.device.ClearApplicationState('this.package.exists')
-
-
-class DeviceUtilsSendKeyEventTest(DeviceUtilsTest):
-
- def testSendKeyEvent(self):
- with self.assertCall(self.call.adb.Shell('input keyevent 66'), ''):
- self.device.SendKeyEvent(66)
-
-
-class DeviceUtilsPushChangedFilesIndividuallyTest(DeviceUtilsTest):
-
- def testPushChangedFilesIndividually_empty(self):
- test_files = []
- with self.assertCalls():
- self.device._PushChangedFilesIndividually(test_files)
-
- def testPushChangedFilesIndividually_single(self):
- test_files = [('/test/host/path', '/test/device/path')]
- with self.assertCalls(self.call.adb.Push(*test_files[0])):
- self.device._PushChangedFilesIndividually(test_files)
-
- def testPushChangedFilesIndividually_multiple(self):
- test_files = [
- ('/test/host/path/file1', '/test/device/path/file1'),
- ('/test/host/path/file2', '/test/device/path/file2')]
- with self.assertCalls(
- self.call.adb.Push(*test_files[0]),
- self.call.adb.Push(*test_files[1])):
- self.device._PushChangedFilesIndividually(test_files)
-
-
-class DeviceUtilsPushChangedFilesZippedTest(DeviceUtilsTest):
-
- def testPushChangedFilesZipped_noUnzipCommand(self):
- test_files = [('/test/host/path/file1', '/test/device/path/file1')]
- mock_zip_temp = mock.mock_open()
- mock_zip_temp.return_value.name = '/test/temp/file/tmp.zip'
- with self.assertCalls(
- (mock.call.tempfile.NamedTemporaryFile(suffix='.zip'), mock_zip_temp),
- (mock.call.multiprocessing.Process(
- target=device_utils.DeviceUtils._CreateDeviceZip,
- args=('/test/temp/file/tmp.zip', test_files)), mock.Mock()),
- (self.call.device._MaybeInstallCommands(), False)):
- self.assertFalse(self.device._PushChangedFilesZipped(test_files,
- ['/test/dir']))
-
- def _testPushChangedFilesZipped_spec(self, test_files):
- mock_zip_temp = mock.mock_open()
- mock_zip_temp.return_value.name = '/test/temp/file/tmp.zip'
- with self.assertCalls(
- (mock.call.tempfile.NamedTemporaryFile(suffix='.zip'), mock_zip_temp),
- (mock.call.multiprocessing.Process(
- target=device_utils.DeviceUtils._CreateDeviceZip,
- args=('/test/temp/file/tmp.zip', test_files)), mock.Mock()),
- (self.call.device._MaybeInstallCommands(), True),
- (self.call.device.NeedsSU(), True),
- (mock.call.devil.android.device_temp_file.DeviceTempFile(self.adb,
- suffix='.zip'),
- MockTempFile('/test/sdcard/foo123.zip')),
- self.call.adb.Push(
- '/test/temp/file/tmp.zip', '/test/sdcard/foo123.zip'),
- self.call.device.RunShellCommand(
- 'unzip /test/sdcard/foo123.zip&&chmod -R 777 /test/dir',
- shell=True, as_root=True,
- env={'PATH': '/data/local/tmp/bin:$PATH'},
- check_return=True)):
- self.assertTrue(self.device._PushChangedFilesZipped(test_files,
- ['/test/dir']))
-
- def testPushChangedFilesZipped_single(self):
- self._testPushChangedFilesZipped_spec(
- [('/test/host/path/file1', '/test/device/path/file1')])
-
- def testPushChangedFilesZipped_multiple(self):
- self._testPushChangedFilesZipped_spec(
- [('/test/host/path/file1', '/test/device/path/file1'),
- ('/test/host/path/file2', '/test/device/path/file2')])
-
-
-class DeviceUtilsPathExistsTest(DeviceUtilsTest):
-
- def testPathExists_pathExists(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['test', '-e', '/path/file exists'],
- as_root=False, check_return=True, timeout=10, retries=0),
- []):
- self.assertTrue(self.device.PathExists('/path/file exists'))
-
- def testPathExists_multiplePathExists(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['test', '-e', '/path 1', '-a', '-e', '/path2'],
- as_root=False, check_return=True, timeout=10, retries=0),
- []):
- self.assertTrue(self.device.PathExists(('/path 1', '/path2')))
-
- def testPathExists_pathDoesntExist(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['test', '-e', '/path/file.not.exists'],
- as_root=False, check_return=True, timeout=10, retries=0),
- self.ShellError()):
- self.assertFalse(self.device.PathExists('/path/file.not.exists'))
-
- def testPathExists_asRoot(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['test', '-e', '/root/path/exists'],
- as_root=True, check_return=True, timeout=10, retries=0),
- self.ShellError()):
- self.assertFalse(
- self.device.PathExists('/root/path/exists', as_root=True))
-
- def testFileExists_pathDoesntExist(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['test', '-e', '/path/file.not.exists'],
- as_root=False, check_return=True, timeout=10, retries=0),
- self.ShellError()):
- self.assertFalse(self.device.FileExists('/path/file.not.exists'))
-
-
-class DeviceUtilsRemovePathTest(DeviceUtilsTest):
-
- def testRemovePath_regular(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['rm', 'some file'], as_root=False, check_return=True),
- []):
- self.device.RemovePath('some file')
-
- def testRemovePath_withForce(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['rm', '-f', 'some file'], as_root=False, check_return=True),
- []):
- self.device.RemovePath('some file', force=True)
-
- def testRemovePath_recursively(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['rm', '-r', '/remove/this/dir'], as_root=False, check_return=True),
- []):
- self.device.RemovePath('/remove/this/dir', recursive=True)
-
- def testRemovePath_withRoot(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['rm', 'some file'], as_root=True, check_return=True),
- []):
- self.device.RemovePath('some file', as_root=True)
-
- def testRemovePath_manyPaths(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['rm', 'eeny', 'meeny', 'miny', 'moe'],
- as_root=False, check_return=True),
- []):
- self.device.RemovePath(['eeny', 'meeny', 'miny', 'moe'])
-
-
-class DeviceUtilsPullFileTest(DeviceUtilsTest):
-
- def testPullFile_existsOnDevice(self):
- with mock.patch('os.path.exists', return_value=True):
- with self.assertCall(
- self.call.adb.Pull('/data/app/test.file.exists',
- '/test/file/host/path')):
- self.device.PullFile('/data/app/test.file.exists',
- '/test/file/host/path')
-
- def testPullFile_doesntExistOnDevice(self):
- with mock.patch('os.path.exists', return_value=True):
- with self.assertCall(
- self.call.adb.Pull('/data/app/test.file.does.not.exist',
- '/test/file/host/path'),
- self.CommandError('remote object does not exist')):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.PullFile('/data/app/test.file.does.not.exist',
- '/test/file/host/path')
-
-
-class DeviceUtilsReadFileTest(DeviceUtilsTest):
-
- def testReadFileWithPull_success(self):
- tmp_host_dir = '/tmp/dir/on.host/'
- tmp_host = MockTempFile('/tmp/dir/on.host/tmp_ReadFileWithPull')
- tmp_host.file.read.return_value = 'some interesting contents'
- with self.assertCalls(
- (mock.call.tempfile.mkdtemp(), tmp_host_dir),
- (self.call.adb.Pull('/path/to/device/file', mock.ANY)),
- (mock.call.__builtin__.open(mock.ANY, 'r'), tmp_host),
- (mock.call.os.path.exists(tmp_host_dir), True),
- (mock.call.shutil.rmtree(tmp_host_dir), None)):
- self.assertEquals('some interesting contents',
- self.device._ReadFileWithPull('/path/to/device/file'))
- tmp_host.file.read.assert_called_once_with()
-
- def testReadFileWithPull_rejected(self):
- tmp_host_dir = '/tmp/dir/on.host/'
- with self.assertCalls(
- (mock.call.tempfile.mkdtemp(), tmp_host_dir),
- (self.call.adb.Pull('/path/to/device/file', mock.ANY),
- self.CommandError()),
- (mock.call.os.path.exists(tmp_host_dir), True),
- (mock.call.shutil.rmtree(tmp_host_dir), None)):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device._ReadFileWithPull('/path/to/device/file')
-
- def testReadFile_exists(self):
- with self.assertCalls(
- (self.call.device.FileSize('/read/this/test/file', as_root=False), 256),
- (self.call.device.RunShellCommand(
- ['cat', '/read/this/test/file'],
- as_root=False, check_return=True),
- ['this is a test file'])):
- self.assertEqual('this is a test file\n',
- self.device.ReadFile('/read/this/test/file'))
-
- def testReadFile_exists2(self):
- # Same as testReadFile_exists, but uses Android N ls output.
- with self.assertCalls(
- (self.call.device.FileSize('/read/this/test/file', as_root=False), 256),
- (self.call.device.RunShellCommand(
- ['cat', '/read/this/test/file'],
- as_root=False, check_return=True),
- ['this is a test file'])):
- self.assertEqual('this is a test file\n',
- self.device.ReadFile('/read/this/test/file'))
-
- def testReadFile_doesNotExist(self):
- with self.assertCall(
- self.call.device.FileSize('/this/file/does.not.exist', as_root=False),
- self.CommandError('File does not exist')):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.ReadFile('/this/file/does.not.exist')
-
- def testReadFile_zeroSize(self):
- with self.assertCalls(
- (self.call.device.FileSize('/this/file/has/zero/size', as_root=False),
- 0),
- (self.call.device._ReadFileWithPull('/this/file/has/zero/size'),
- 'but it has contents\n')):
- self.assertEqual('but it has contents\n',
- self.device.ReadFile('/this/file/has/zero/size'))
-
- def testReadFile_withSU(self):
- with self.assertCalls(
- (self.call.device.FileSize(
- '/this/file/can.be.read.with.su', as_root=True), 256),
- (self.call.device.RunShellCommand(
- ['cat', '/this/file/can.be.read.with.su'],
- as_root=True, check_return=True),
- ['this is a test file', 'read with su'])):
- self.assertEqual(
- 'this is a test file\nread with su\n',
- self.device.ReadFile('/this/file/can.be.read.with.su',
- as_root=True))
-
- def testReadFile_withPull(self):
- contents = 'a' * 123456
- with self.assertCalls(
- (self.call.device.FileSize('/read/this/big/test/file', as_root=False),
- 123456),
- (self.call.device._ReadFileWithPull('/read/this/big/test/file'),
- contents)):
- self.assertEqual(
- contents, self.device.ReadFile('/read/this/big/test/file'))
-
- def testReadFile_withPullAndSU(self):
- contents = 'b' * 123456
- with self.assertCalls(
- (self.call.device.FileSize(
- '/this/big/file/can.be.read.with.su', as_root=True), 123456),
- (self.call.device.NeedsSU(), True),
- (mock.call.devil.android.device_temp_file.DeviceTempFile(self.adb),
- MockTempFile('/sdcard/tmp/on.device')),
- self.call.device.RunShellCommand(
- 'SRC=/this/big/file/can.be.read.with.su DEST=/sdcard/tmp/on.device;'
- 'cp "$SRC" "$DEST" && chmod 666 "$DEST"',
- shell=True, as_root=True, check_return=True),
- (self.call.device._ReadFileWithPull('/sdcard/tmp/on.device'),
- contents)):
- self.assertEqual(
- contents,
- self.device.ReadFile('/this/big/file/can.be.read.with.su',
- as_root=True))
-
- def testReadFile_forcePull(self):
- contents = 'a' * 123456
- with self.assertCall(
- self.call.device._ReadFileWithPull('/read/this/big/test/file'),
- contents):
- self.assertEqual(
- contents,
- self.device.ReadFile('/read/this/big/test/file', force_pull=True))
-
-
-class DeviceUtilsWriteFileTest(DeviceUtilsTest):
-
- def testWriteFileWithPush_success(self):
- tmp_host = MockTempFile('/tmp/file/on.host')
- contents = 'some interesting contents'
- with self.assertCalls(
- (mock.call.tempfile.NamedTemporaryFile(), tmp_host),
- self.call.adb.Push('/tmp/file/on.host', '/path/to/device/file')):
- self.device._WriteFileWithPush('/path/to/device/file', contents)
- tmp_host.file.write.assert_called_once_with(contents)
-
- def testWriteFileWithPush_rejected(self):
- tmp_host = MockTempFile('/tmp/file/on.host')
- contents = 'some interesting contents'
- with self.assertCalls(
- (mock.call.tempfile.NamedTemporaryFile(), tmp_host),
- (self.call.adb.Push('/tmp/file/on.host', '/path/to/device/file'),
- self.CommandError())):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device._WriteFileWithPush('/path/to/device/file', contents)
-
- def testWriteFile_withPush(self):
- contents = 'some large contents ' * 26 # 20 * 26 = 520 chars
- with self.assertCalls(
- self.call.device._WriteFileWithPush('/path/to/device/file', contents)):
- self.device.WriteFile('/path/to/device/file', contents)
-
- def testWriteFile_withPushForced(self):
- contents = 'tiny contents'
- with self.assertCalls(
- self.call.device._WriteFileWithPush('/path/to/device/file', contents)):
- self.device.WriteFile('/path/to/device/file', contents, force_push=True)
-
- def testWriteFile_withPushAndSU(self):
- contents = 'some large contents ' * 26 # 20 * 26 = 520 chars
- with self.assertCalls(
- (self.call.device.NeedsSU(), True),
- (mock.call.devil.android.device_temp_file.DeviceTempFile(self.adb),
- MockTempFile('/sdcard/tmp/on.device')),
- self.call.device._WriteFileWithPush('/sdcard/tmp/on.device', contents),
- self.call.device.RunShellCommand(
- ['cp', '/sdcard/tmp/on.device', '/path/to/device/file'],
- as_root=True, check_return=True)):
- self.device.WriteFile('/path/to/device/file', contents, as_root=True)
-
- def testWriteFile_withEcho(self):
- with self.assertCall(self.call.adb.Shell(
- "echo -n the.contents > /test/file/to.write"), ''):
- self.device.WriteFile('/test/file/to.write', 'the.contents')
-
- def testWriteFile_withEchoAndQuotes(self):
- with self.assertCall(self.call.adb.Shell(
- "echo -n 'the contents' > '/test/file/to write'"), ''):
- self.device.WriteFile('/test/file/to write', 'the contents')
-
- def testWriteFile_withEchoAndSU(self):
- expected_cmd_without_su = "sh -c 'echo -n contents > /test/file'"
- expected_cmd = 'su -c %s' % expected_cmd_without_su
- with self.assertCalls(
- (self.call.device.NeedsSU(), True),
- (self.call.device._Su(expected_cmd_without_su), expected_cmd),
- (self.call.adb.Shell(expected_cmd),
- '')):
- self.device.WriteFile('/test/file', 'contents', as_root=True)
-
-
-class DeviceUtilsStatDirectoryTest(DeviceUtilsTest):
- # Note: Also tests ListDirectory in testStatDirectory_fileList.
-
- EXAMPLE_LS_OUTPUT = [
- 'total 12345',
- 'drwxr-xr-x 19 root root 0 1970-04-06 18:03 .',
- 'drwxr-xr-x 19 root root 0 1970-04-06 18:03 ..',
- 'drwxr-xr-x 6 root root 1970-01-01 00:00 some_dir',
- '-rw-r--r-- 1 root root 723 1971-01-01 07:04 some_file',
- '-rw-r----- 1 root root 327 2009-02-13 23:30 My Music File',
- # Older Android versions do not print st_nlink
- 'lrwxrwxrwx root root 1970-01-01 00:00 lnk -> /some/path',
- 'srwxrwx--- system system 2016-05-31 17:25 a_socket1',
- 'drwxrwxrwt system misc 1970-11-23 02:25 tmp',
- 'drwxr-s--- system shell 1970-11-23 02:24 my_cmd',
- 'cr--r----- root system 10, 183 1971-01-01 07:04 random',
- 'brw------- root root 7, 0 1971-01-01 07:04 block_dev',
- '-rwS------ root shell 157404 2015-04-13 15:44 silly',
- ]
-
- FILENAMES = [
- 'some_dir', 'some_file', 'My Music File', 'lnk', 'a_socket1',
- 'tmp', 'my_cmd', 'random', 'block_dev', 'silly']
-
- def getStatEntries(self, path_given='/', path_listed='/'):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['ls', '-a', '-l', path_listed],
- check_return=True, as_root=False, env={'TZ': 'utc'}),
- self.EXAMPLE_LS_OUTPUT):
- entries = self.device.StatDirectory(path_given)
- return {f['filename']: f for f in entries}
-
- def getListEntries(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['ls', '-a', '-l', '/'],
- check_return=True, as_root=False, env={'TZ': 'utc'}),
- self.EXAMPLE_LS_OUTPUT):
- return self.device.ListDirectory('/')
-
- def testStatDirectory_forceTrailingSlash(self):
- self.getStatEntries(path_given='/foo/bar/', path_listed='/foo/bar/')
- self.getStatEntries(path_given='/foo/bar', path_listed='/foo/bar/')
-
- def testStatDirectory_fileList(self):
- self.assertItemsEqual(self.getStatEntries().keys(), self.FILENAMES)
- self.assertItemsEqual(self.getListEntries(), self.FILENAMES)
-
- def testStatDirectory_fileModes(self):
- expected_modes = (
- ('some_dir', stat.S_ISDIR),
- ('some_file', stat.S_ISREG),
- ('lnk', stat.S_ISLNK),
- ('a_socket1', stat.S_ISSOCK),
- ('block_dev', stat.S_ISBLK),
- ('random', stat.S_ISCHR),
- )
- entries = self.getStatEntries()
- for filename, check in expected_modes:
- self.assertTrue(check(entries[filename]['st_mode']))
-
- def testStatDirectory_filePermissions(self):
- should_have = (
- ('some_file', stat.S_IWUSR), # Owner can write.
- ('tmp', stat.S_IXOTH), # Others can execute.
- ('tmp', stat.S_ISVTX), # Has sticky bit.
- ('my_cmd', stat.S_ISGID), # Has set-group-ID bit.
- ('silly', stat.S_ISUID), # Has set UID bit.
- )
- should_not_have = (
- ('some_file', stat.S_IWOTH), # Others can't write.
- ('block_dev', stat.S_IRGRP), # Group can't read.
- ('silly', stat.S_IXUSR), # Owner can't execute.
- )
- entries = self.getStatEntries()
- for filename, bit in should_have:
- self.assertTrue(entries[filename]['st_mode'] & bit)
- for filename, bit in should_not_have:
- self.assertFalse(entries[filename]['st_mode'] & bit)
-
- def testStatDirectory_numHardLinks(self):
- entries = self.getStatEntries()
- self.assertEqual(entries['some_dir']['st_nlink'], 6)
- self.assertEqual(entries['some_file']['st_nlink'], 1)
- self.assertFalse('st_nlink' in entries['tmp'])
-
- def testStatDirectory_fileOwners(self):
- entries = self.getStatEntries()
- self.assertEqual(entries['some_dir']['st_owner'], 'root')
- self.assertEqual(entries['my_cmd']['st_owner'], 'system')
- self.assertEqual(entries['my_cmd']['st_group'], 'shell')
- self.assertEqual(entries['tmp']['st_group'], 'misc')
-
- def testStatDirectory_fileSize(self):
- entries = self.getStatEntries()
- self.assertEqual(entries['some_file']['st_size'], 723)
- self.assertEqual(entries['My Music File']['st_size'], 327)
- # Sizes are sometimes not reported for non-regular files, don't try to
- # guess the size in those cases.
- self.assertFalse('st_size' in entries['some_dir'])
-
- def testStatDirectory_fileDateTime(self):
- entries = self.getStatEntries()
- self.assertEqual(entries['some_dir']['st_mtime'], 0) # Epoch!
- self.assertEqual(entries['My Music File']['st_mtime'], 1234567800)
-
- def testStatDirectory_deviceType(self):
- entries = self.getStatEntries()
- self.assertEqual(entries['random']['st_rdev_pair'], (10, 183))
- self.assertEqual(entries['block_dev']['st_rdev_pair'], (7, 0))
-
- def testStatDirectory_symbolicLinks(self):
- entries = self.getStatEntries()
- self.assertEqual(entries['lnk']['symbolic_link_to'], '/some/path')
- for d in entries.itervalues():
- self.assertEqual('symbolic_link_to' in d, stat.S_ISLNK(d['st_mode']))
-
-
-class DeviceUtilsStatPathTest(DeviceUtilsTest):
-
- EXAMPLE_DIRECTORY = [
- {'filename': 'foo.txt', 'st_size': 123, 'st_time': 456},
- {'filename': 'some_dir', 'st_time': 0}
- ]
- INDEX = {e['filename']: e for e in EXAMPLE_DIRECTORY}
-
- def testStatPath_file(self):
- with self.assertCall(
- self.call.device.StatDirectory('/data/local/tmp', as_root=False),
- self.EXAMPLE_DIRECTORY):
- self.assertEquals(self.INDEX['foo.txt'],
- self.device.StatPath('/data/local/tmp/foo.txt'))
-
- def testStatPath_directory(self):
- with self.assertCall(
- self.call.device.StatDirectory('/data/local/tmp', as_root=False),
- self.EXAMPLE_DIRECTORY):
- self.assertEquals(self.INDEX['some_dir'],
- self.device.StatPath('/data/local/tmp/some_dir'))
-
- def testStatPath_directoryWithTrailingSlash(self):
- with self.assertCall(
- self.call.device.StatDirectory('/data/local/tmp', as_root=False),
- self.EXAMPLE_DIRECTORY):
- self.assertEquals(self.INDEX['some_dir'],
- self.device.StatPath('/data/local/tmp/some_dir/'))
-
- def testStatPath_doesNotExist(self):
- with self.assertCall(
- self.call.device.StatDirectory('/data/local/tmp', as_root=False),
- self.EXAMPLE_DIRECTORY):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.StatPath('/data/local/tmp/does.not.exist.txt')
-
-
-class DeviceUtilsFileSizeTest(DeviceUtilsTest):
-
- EXAMPLE_DIRECTORY = [
- {'filename': 'foo.txt', 'st_size': 123, 'st_mtime': 456},
- {'filename': 'some_dir', 'st_mtime': 0}
- ]
-
- def testFileSize_file(self):
- with self.assertCall(
- self.call.device.StatDirectory('/data/local/tmp', as_root=False),
- self.EXAMPLE_DIRECTORY):
- self.assertEquals(123,
- self.device.FileSize('/data/local/tmp/foo.txt'))
-
- def testFileSize_doesNotExist(self):
- with self.assertCall(
- self.call.device.StatDirectory('/data/local/tmp', as_root=False),
- self.EXAMPLE_DIRECTORY):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.FileSize('/data/local/tmp/does.not.exist.txt')
-
- def testFileSize_directoryWithNoSize(self):
- with self.assertCall(
- self.call.device.StatDirectory('/data/local/tmp', as_root=False),
- self.EXAMPLE_DIRECTORY):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.FileSize('/data/local/tmp/some_dir')
-
-
-class DeviceUtilsSetJavaAssertsTest(DeviceUtilsTest):
-
- def testSetJavaAsserts_enable(self):
- with self.assertCalls(
- (self.call.device.ReadFile(self.device.LOCAL_PROPERTIES_PATH),
- 'some.example.prop=with an example value\n'
- 'some.other.prop=value_ok\n'),
- self.call.device.WriteFile(
- self.device.LOCAL_PROPERTIES_PATH,
- 'some.example.prop=with an example value\n'
- 'some.other.prop=value_ok\n'
- 'dalvik.vm.enableassertions=all\n'),
- (self.call.device.GetProp('dalvik.vm.enableassertions'), ''),
- self.call.device.SetProp('dalvik.vm.enableassertions', 'all')):
- self.assertTrue(self.device.SetJavaAsserts(True))
-
- def testSetJavaAsserts_disable(self):
- with self.assertCalls(
- (self.call.device.ReadFile(self.device.LOCAL_PROPERTIES_PATH),
- 'some.example.prop=with an example value\n'
- 'dalvik.vm.enableassertions=all\n'
- 'some.other.prop=value_ok\n'),
- self.call.device.WriteFile(
- self.device.LOCAL_PROPERTIES_PATH,
- 'some.example.prop=with an example value\n'
- 'some.other.prop=value_ok\n'),
- (self.call.device.GetProp('dalvik.vm.enableassertions'), 'all'),
- self.call.device.SetProp('dalvik.vm.enableassertions', '')):
- self.assertTrue(self.device.SetJavaAsserts(False))
-
- def testSetJavaAsserts_alreadyEnabled(self):
- with self.assertCalls(
- (self.call.device.ReadFile(self.device.LOCAL_PROPERTIES_PATH),
- 'some.example.prop=with an example value\n'
- 'dalvik.vm.enableassertions=all\n'
- 'some.other.prop=value_ok\n'),
- (self.call.device.GetProp('dalvik.vm.enableassertions'), 'all')):
- self.assertFalse(self.device.SetJavaAsserts(True))
-
- def testSetJavaAsserts_malformedLocalProp(self):
- with self.assertCalls(
- (self.call.device.ReadFile(self.device.LOCAL_PROPERTIES_PATH),
- 'some.example.prop=with an example value\n'
- 'malformed_property\n'
- 'dalvik.vm.enableassertions=all\n'
- 'some.other.prop=value_ok\n'),
- (self.call.device.GetProp('dalvik.vm.enableassertions'), 'all')):
- self.assertFalse(self.device.SetJavaAsserts(True))
-
-
-class DeviceUtilsEnsureCacheInitializedTest(DeviceUtilsTest):
-
- def testEnsureCacheInitialized_noCache_success(self):
- self.assertIsNone(self.device._cache['token'])
- with self.assertCall(
- self.call.device.RunShellCommand(
- AnyStringWith('getprop'),
- shell=True, check_return=True, large_output=True),
- ['/sdcard', 'TOKEN']):
- self.device._EnsureCacheInitialized()
- self.assertIsNotNone(self.device._cache['token'])
-
- def testEnsureCacheInitialized_noCache_failure(self):
- self.assertIsNone(self.device._cache['token'])
- with self.assertCall(
- self.call.device.RunShellCommand(
- AnyStringWith('getprop'),
- shell=True, check_return=True, large_output=True),
- self.TimeoutError()):
- with self.assertRaises(device_errors.CommandTimeoutError):
- self.device._EnsureCacheInitialized()
- self.assertIsNone(self.device._cache['token'])
-
- def testEnsureCacheInitialized_cache(self):
- self.device._cache['token'] = 'TOKEN'
- with self.assertCalls():
- self.device._EnsureCacheInitialized()
- self.assertIsNotNone(self.device._cache['token'])
-
-
-class DeviceUtilsGetPropTest(DeviceUtilsTest):
-
- def testGetProp_exists(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['getprop', 'test.property'], check_return=True, single_line=True,
- timeout=self.device._default_timeout,
- retries=self.device._default_retries),
- 'property_value'):
- self.assertEqual('property_value',
- self.device.GetProp('test.property'))
-
- def testGetProp_doesNotExist(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['getprop', 'property.does.not.exist'],
- check_return=True, single_line=True,
- timeout=self.device._default_timeout,
- retries=self.device._default_retries),
- ''):
- self.assertEqual('', self.device.GetProp('property.does.not.exist'))
-
- def testGetProp_cachedRoProp(self):
- with self.assertCalls(
- self.EnsureCacheInitialized(props=['[ro.build.type]: [userdebug]'])):
- self.assertEqual('userdebug',
- self.device.GetProp('ro.build.type', cache=True))
- self.assertEqual('userdebug',
- self.device.GetProp('ro.build.type', cache=True))
-
-
-class DeviceUtilsSetPropTest(DeviceUtilsTest):
-
- def testSetProp(self):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['setprop', 'test.property', 'test value'], check_return=True)):
- self.device.SetProp('test.property', 'test value')
-
- def testSetProp_check_succeeds(self):
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- ['setprop', 'test.property', 'new_value'], check_return=True)),
- (self.call.device.GetProp('test.property', cache=False), 'new_value')):
- self.device.SetProp('test.property', 'new_value', check=True)
-
- def testSetProp_check_fails(self):
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- ['setprop', 'test.property', 'new_value'], check_return=True)),
- (self.call.device.GetProp('test.property', cache=False), 'old_value')):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.SetProp('test.property', 'new_value', check=True)
-
-
-class DeviceUtilsGetPidsTest(DeviceUtilsTest):
- def setUp(self):
- super(DeviceUtilsGetPidsTest, self).setUp()
- self.sample_output = [
- 'USER PID PPID VSIZE RSS WCHAN PC NAME',
- 'user 1001 100 1024 1024 ffffffff 00000000 one.match',
- 'user 1002 100 1024 1024 ffffffff 00000000 two.match',
- 'user 1003 100 1024 1024 ffffffff 00000000 three.match',
- 'user 1234 100 1024 1024 ffffffff 00000000 my$process',
- 'user 1000 100 1024 1024 ffffffff 00000000 foo',
- 'user 1236 100 1024 1024 ffffffff 00000000 foo',
- ]
-
- def _grepOutput(self, substring):
- return [line for line in self.sample_output if substring in line]
-
- def testGetPids_sdkGreaterThanNougatMR1(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=(version_codes.NOUGAT_MR1 + 1)):
- with self.patch_call(self.call.device.build_id,
- return_value='ZZZ99Z'):
- with self.assertCall(
- self.call.device._RunPipedShellCommand(
- 'ps -e | grep -F example.process'), []):
- self.device.GetPids('example.process')
-
- def testGetPids_noMatches(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.LOLLIPOP):
- with self.assertCall(
- self.call.device._RunPipedShellCommand('ps | grep -F does.not.match'),
- self._grepOutput('does.not.match')):
- self.assertEqual({}, self.device.GetPids('does.not.match'))
-
- def testGetPids_oneMatch(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.LOLLIPOP):
- with self.assertCall(
- self.call.device._RunPipedShellCommand('ps | grep -F one.match'),
- self._grepOutput('one.match')):
- self.assertEqual(
- {'one.match': ['1001']},
- self.device.GetPids('one.match'))
-
- def testGetPids_multipleMatches(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.LOLLIPOP):
- with self.assertCall(
- self.call.device._RunPipedShellCommand('ps | grep -F match'),
- self._grepOutput('match')):
- self.assertEqual(
- {'one.match': ['1001'],
- 'two.match': ['1002'],
- 'three.match': ['1003']},
- self.device.GetPids('match'))
-
- def testGetPids_quotable(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.LOLLIPOP):
- with self.assertCall(
- self.call.device._RunPipedShellCommand("ps | grep -F 'my$process'"),
- self._grepOutput('my$process')):
- self.assertEqual(
- {'my$process': ['1234']}, self.device.GetPids('my$process'))
-
- def testGetPids_multipleInstances(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.LOLLIPOP):
- with self.assertCall(
- self.call.device._RunPipedShellCommand('ps | grep -F foo'),
- self._grepOutput('foo')):
- self.assertEqual(
- {'foo': ['1000', '1236']},
- self.device.GetPids('foo'))
-
- def testGetPids_allProcesses(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.LOLLIPOP):
- with self.assertCall(
- self.call.device.RunShellCommand(
- ['ps'], check_return=True, large_output=True),
- self.sample_output):
- self.assertEqual(
- {'one.match': ['1001'],
- 'two.match': ['1002'],
- 'three.match': ['1003'],
- 'my$process': ['1234'],
- 'foo': ['1000', '1236']},
- self.device.GetPids())
-
- def testGetApplicationPids_notFound(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.LOLLIPOP):
- with self.assertCall(
- self.call.device._RunPipedShellCommand('ps | grep -F match'),
- self._grepOutput('match')):
- # No PIDs found, process name should be exact match.
- self.assertEqual([], self.device.GetApplicationPids('match'))
-
- def testGetApplicationPids_foundOne(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.LOLLIPOP):
- with self.assertCall(
- self.call.device._RunPipedShellCommand('ps | grep -F one.match'),
- self._grepOutput('one.match')):
- self.assertEqual(['1001'], self.device.GetApplicationPids('one.match'))
-
- def testGetApplicationPids_foundMany(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.LOLLIPOP):
- with self.assertCall(
- self.call.device._RunPipedShellCommand('ps | grep -F foo'),
- self._grepOutput('foo')):
- self.assertEqual(
- ['1000', '1236'],
- self.device.GetApplicationPids('foo'))
-
- def testGetApplicationPids_atMostOneNotFound(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.LOLLIPOP):
- with self.assertCall(
- self.call.device._RunPipedShellCommand('ps | grep -F match'),
- self._grepOutput('match')):
- # No PIDs found, process name should be exact match.
- self.assertEqual(
- None,
- self.device.GetApplicationPids('match', at_most_one=True))
-
- def testGetApplicationPids_atMostOneFound(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.LOLLIPOP):
- with self.assertCall(
- self.call.device._RunPipedShellCommand('ps | grep -F one.match'),
- self._grepOutput('one.match')):
- self.assertEqual(
- '1001',
- self.device.GetApplicationPids('one.match', at_most_one=True))
-
- def testGetApplicationPids_atMostOneFoundTooMany(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.LOLLIPOP):
- with self.assertRaises(device_errors.CommandFailedError):
- with self.assertCall(
- self.call.device._RunPipedShellCommand('ps | grep -F foo'),
- self._grepOutput('foo')):
- self.device.GetApplicationPids('foo', at_most_one=True)
-
-
-class DeviceUtilsGetSetEnforce(DeviceUtilsTest):
-
- def testGetEnforce_Enforcing(self):
- with self.assertCall(self.call.adb.Shell('getenforce'), 'Enforcing'):
- self.assertEqual(True, self.device.GetEnforce())
-
- def testGetEnforce_Permissive(self):
- with self.assertCall(self.call.adb.Shell('getenforce'), 'Permissive'):
- self.assertEqual(False, self.device.GetEnforce())
-
- def testGetEnforce_Disabled(self):
- with self.assertCall(self.call.adb.Shell('getenforce'), 'Disabled'):
- self.assertEqual(None, self.device.GetEnforce())
-
- def testSetEnforce_Enforcing(self):
- with self.assertCalls(
- (self.call.device.NeedsSU(), False),
- (self.call.adb.Shell('setenforce 1'), '')):
- self.device.SetEnforce(enabled=True)
-
- def testSetEnforce_Permissive(self):
- with self.assertCalls(
- (self.call.device.NeedsSU(), False),
- (self.call.adb.Shell('setenforce 0'), '')):
- self.device.SetEnforce(enabled=False)
-
- def testSetEnforce_EnforcingWithInt(self):
- with self.assertCalls(
- (self.call.device.NeedsSU(), False),
- (self.call.adb.Shell('setenforce 1'), '')):
- self.device.SetEnforce(enabled=1)
-
- def testSetEnforce_PermissiveWithInt(self):
- with self.assertCalls(
- (self.call.device.NeedsSU(), False),
- (self.call.adb.Shell('setenforce 0'), '')):
- self.device.SetEnforce(enabled=0)
-
- def testSetEnforce_EnforcingWithStr(self):
- with self.assertCalls(
- (self.call.device.NeedsSU(), False),
- (self.call.adb.Shell('setenforce 1'), '')):
- self.device.SetEnforce(enabled='1')
-
- def testSetEnforce_PermissiveWithStr(self):
- with self.assertCalls(
- (self.call.device.NeedsSU(), False),
- (self.call.adb.Shell('setenforce 0'), '')):
- self.device.SetEnforce(enabled='0') # Not recommended but it works!
-
-
-class DeviceUtilsTakeScreenshotTest(DeviceUtilsTest):
-
- def testTakeScreenshot_fileNameProvided(self):
- with self.assertCalls(
- (mock.call.devil.android.device_temp_file.DeviceTempFile(
- self.adb, suffix='.png'),
- MockTempFile('/tmp/path/temp-123.png')),
- (self.call.adb.Shell('/system/bin/screencap -p /tmp/path/temp-123.png'),
- ''),
- self.call.device.PullFile('/tmp/path/temp-123.png',
- '/test/host/screenshot.png')):
- self.device.TakeScreenshot('/test/host/screenshot.png')
-
-
-class DeviceUtilsGetMemoryUsageForPidTest(DeviceUtilsTest):
-
- def setUp(self):
- super(DeviceUtilsGetMemoryUsageForPidTest, self).setUp()
-
- def testGetMemoryUsageForPid_validPid(self):
- with self.assertCalls(
- (self.call.device._RunPipedShellCommand(
- 'showmap 1234 | grep TOTAL', as_root=True),
- ['100 101 102 103 104 105 106 107 TOTAL']),
- (self.call.device.ReadFile('/proc/1234/status', as_root=True),
- 'VmHWM: 1024 kB\n')):
- self.assertEqual(
- {
- 'Size': 100,
- 'Rss': 101,
- 'Pss': 102,
- 'Shared_Clean': 103,
- 'Shared_Dirty': 104,
- 'Private_Clean': 105,
- 'Private_Dirty': 106,
- 'VmHWM': 1024
- },
- self.device.GetMemoryUsageForPid(1234))
-
- def testGetMemoryUsageForPid_noSmaps(self):
- with self.assertCalls(
- (self.call.device._RunPipedShellCommand(
- 'showmap 4321 | grep TOTAL', as_root=True),
- ['cannot open /proc/4321/smaps: No such file or directory']),
- (self.call.device.ReadFile('/proc/4321/status', as_root=True),
- 'VmHWM: 1024 kb\n')):
- self.assertEquals({'VmHWM': 1024}, self.device.GetMemoryUsageForPid(4321))
-
- def testGetMemoryUsageForPid_noStatus(self):
- with self.assertCalls(
- (self.call.device._RunPipedShellCommand(
- 'showmap 4321 | grep TOTAL', as_root=True),
- ['100 101 102 103 104 105 106 107 TOTAL']),
- (self.call.device.ReadFile('/proc/4321/status', as_root=True),
- self.CommandError())):
- self.assertEquals(
- {
- 'Size': 100,
- 'Rss': 101,
- 'Pss': 102,
- 'Shared_Clean': 103,
- 'Shared_Dirty': 104,
- 'Private_Clean': 105,
- 'Private_Dirty': 106,
- },
- self.device.GetMemoryUsageForPid(4321))
-
-
-class DeviceUtilsDismissCrashDialogIfNeededTest(DeviceUtilsTest):
-
- def testDismissCrashDialogIfNeeded_crashedPageckageNotFound(self):
- sample_dumpsys_output = '''
-WINDOW MANAGER WINDOWS (dumpsys window windows)
- Window #11 Window{f8b647a u0 SearchPanel}:
- mDisplayId=0 mSession=Session{8 94:122} mClient=android.os.BinderProxy@1ba5
- mOwnerUid=100 mShowToOwnerOnly=false package=com.android.systemui appop=NONE
- mAttrs=WM.LayoutParams{(0,0)(fillxfill) gr=#53 sim=#31 ty=2024 fl=100
- Requested w=1080 h=1920 mLayoutSeq=426
- mBaseLayer=211000 mSubLayer=0 mAnimLayer=211000+0=211000 mLastLayer=211000
-'''
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- ['dumpsys', 'window', 'windows'], check_return=True,
- large_output=True), sample_dumpsys_output.split('\n'))):
- package_name = self.device.DismissCrashDialogIfNeeded()
- self.assertIsNone(package_name)
-
- def testDismissCrashDialogIfNeeded_crashedPageckageFound(self):
- sample_dumpsys_output = '''
-WINDOW MANAGER WINDOWS (dumpsys window windows)
- Window #11 Window{f8b647a u0 SearchPanel}:
- mDisplayId=0 mSession=Session{8 94:122} mClient=android.os.BinderProxy@1ba5
- mOwnerUid=102 mShowToOwnerOnly=false package=com.android.systemui appop=NONE
- mAttrs=WM.LayoutParams{(0,0)(fillxfill) gr=#53 sim=#31 ty=2024 fl=100
- Requested w=1080 h=1920 mLayoutSeq=426
- mBaseLayer=211000 mSubLayer=0 mAnimLayer=211000+0=211000 mLastLayer=211000
- mHasPermanentDpad=false
- mCurrentFocus=Window{3a27740f u0 Application Error: com.android.chrome}
- mFocusedApp=AppWindowToken{470af6f token=Token{272ec24e ActivityRecord{t894}}}
-'''
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- ['dumpsys', 'window', 'windows'], check_return=True,
- large_output=True), sample_dumpsys_output.split('\n')),
- (self.call.device.RunShellCommand(
- ['input', 'keyevent', '22'], check_return=True)),
- (self.call.device.RunShellCommand(
- ['input', 'keyevent', '22'], check_return=True)),
- (self.call.device.RunShellCommand(
- ['input', 'keyevent', '66'], check_return=True)),
- (self.call.device.RunShellCommand(
- ['dumpsys', 'window', 'windows'], check_return=True,
- large_output=True), [])):
- package_name = self.device.DismissCrashDialogIfNeeded()
- self.assertEqual(package_name, 'com.android.chrome')
-
-
-class DeviceUtilsClientCache(DeviceUtilsTest):
-
- def testClientCache_twoCaches(self):
- self.device._cache['test'] = 0
- client_cache_one = self.device.GetClientCache('ClientOne')
- client_cache_one['test'] = 1
- client_cache_two = self.device.GetClientCache('ClientTwo')
- client_cache_two['test'] = 2
- self.assertEqual(self.device._cache['test'], 0)
- self.assertEqual(client_cache_one, {'test': 1})
- self.assertEqual(client_cache_two, {'test': 2})
- self.device._ClearCache()
- self.assertTrue('test' not in self.device._cache)
- self.assertEqual(client_cache_one, {})
- self.assertEqual(client_cache_two, {})
-
- def testClientCache_multipleInstances(self):
- client_cache_one = self.device.GetClientCache('ClientOne')
- client_cache_one['test'] = 1
- client_cache_two = self.device.GetClientCache('ClientOne')
- self.assertEqual(client_cache_one, {'test': 1})
- self.assertEqual(client_cache_two, {'test': 1})
- self.device._ClearCache()
- self.assertEqual(client_cache_one, {})
- self.assertEqual(client_cache_two, {})
-
-
-class DeviceUtilsHealthyDevicesTest(mock_calls.TestCase):
-
- def testHealthyDevices_emptyBlacklist_defaultDeviceArg(self):
- test_serials = ['0123456789abcdef', 'fedcba9876543210']
- with self.assertCalls(
- (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
- [_AdbWrapperMock(s) for s in test_serials])):
- blacklist = mock.NonCallableMock(**{'Read.return_value': []})
- devices = device_utils.DeviceUtils.HealthyDevices(blacklist)
- for serial, device in zip(test_serials, devices):
- self.assertTrue(isinstance(device, device_utils.DeviceUtils))
- self.assertEquals(serial, device.adb.GetDeviceSerial())
-
- def testHealthyDevices_blacklist_defaultDeviceArg(self):
- test_serials = ['0123456789abcdef', 'fedcba9876543210']
- with self.assertCalls(
- (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
- [_AdbWrapperMock(s) for s in test_serials])):
- blacklist = mock.NonCallableMock(
- **{'Read.return_value': ['fedcba9876543210']})
- devices = device_utils.DeviceUtils.HealthyDevices(blacklist)
- self.assertEquals(1, len(devices))
- self.assertTrue(isinstance(devices[0], device_utils.DeviceUtils))
- self.assertEquals('0123456789abcdef', devices[0].adb.GetDeviceSerial())
-
- def testHealthyDevices_noneDeviceArg_multiple_attached(self):
- test_serials = ['0123456789abcdef', 'fedcba9876543210']
- with self.assertCalls(
- (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
- [_AdbWrapperMock(s) for s in test_serials]),
- (mock.call.devil.android.device_errors.MultipleDevicesError(mock.ANY),
- _MockMultipleDevicesError())):
- with self.assertRaises(_MockMultipleDevicesError):
- device_utils.DeviceUtils.HealthyDevices(device_arg=None)
-
- def testHealthyDevices_noneDeviceArg_one_attached(self):
- test_serials = ['0123456789abcdef']
- with self.assertCalls(
- (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
- [_AdbWrapperMock(s) for s in test_serials])):
- devices = device_utils.DeviceUtils.HealthyDevices(device_arg=None)
- self.assertEquals(1, len(devices))
-
- def testHealthyDevices_noneDeviceArg_no_attached(self):
- test_serials = []
- with self.assertCalls(
- (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
- [_AdbWrapperMock(s) for s in test_serials])):
- with self.assertRaises(device_errors.NoDevicesError):
- device_utils.DeviceUtils.HealthyDevices(device_arg=None)
-
- def testHealthyDevices_noneDeviceArg_multiple_attached_ANDROID_SERIAL(self):
- try:
- os.environ['ANDROID_SERIAL'] = '0123456789abcdef'
- with self.assertCalls(): # Should skip adb devices when device is known.
- device_utils.DeviceUtils.HealthyDevices(device_arg=None)
- finally:
- del os.environ['ANDROID_SERIAL']
-
- def testHealthyDevices_stringDeviceArg(self):
- with self.assertCalls(): # Should skip adb devices when device is known.
- devices = device_utils.DeviceUtils.HealthyDevices(
- device_arg='0123456789abcdef')
- self.assertEquals(1, len(devices))
-
- def testHealthyDevices_EmptyListDeviceArg_multiple_attached(self):
- test_serials = ['0123456789abcdef', 'fedcba9876543210']
- with self.assertCalls(
- (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
- [_AdbWrapperMock(s) for s in test_serials])):
- devices = device_utils.DeviceUtils.HealthyDevices(device_arg=())
- self.assertEquals(2, len(devices))
-
- def testHealthyDevices_EmptyListDeviceArg_ANDROID_SERIAL(self):
- try:
- os.environ['ANDROID_SERIAL'] = '0123456789abcdef'
- with self.assertCalls(): # Should skip adb devices when device is known.
- devices = device_utils.DeviceUtils.HealthyDevices(device_arg=())
- finally:
- del os.environ['ANDROID_SERIAL']
- self.assertEquals(1, len(devices))
-
- def testHealthyDevices_EmptyListDeviceArg_no_attached(self):
- test_serials = []
- with self.assertCalls(
- (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
- [_AdbWrapperMock(s) for s in test_serials])):
- with self.assertRaises(device_errors.NoDevicesError):
- device_utils.DeviceUtils.HealthyDevices(device_arg=[])
-
- def testHealthyDevices_ListDeviceArg(self):
- device_arg = ['0123456789abcdef', 'fedcba9876543210']
- try:
- os.environ['ANDROID_SERIAL'] = 'should-not-apply'
- with self.assertCalls(): # Should skip adb devices when device is known.
- devices = device_utils.DeviceUtils.HealthyDevices(device_arg=device_arg)
- finally:
- del os.environ['ANDROID_SERIAL']
- self.assertEquals(2, len(devices))
-
-
-class DeviceUtilsRestartAdbdTest(DeviceUtilsTest):
-
- def testAdbdRestart(self):
- mock_temp_file = '/sdcard/temp-123.sh'
- with self.assertCalls(
- (mock.call.devil.android.device_temp_file.DeviceTempFile(
- self.adb, suffix='.sh'), MockTempFile(mock_temp_file)),
- self.call.device.WriteFile(mock.ANY, mock.ANY),
- (self.call.device.RunShellCommand(
- ['source', mock_temp_file], check_return=True, as_root=True)),
- self.call.adb.WaitForDevice()):
- self.device.RestartAdbd()
-
-
-class DeviceUtilsGrantPermissionsTest(DeviceUtilsTest):
-
- def testGrantPermissions_none(self):
- self.device.GrantPermissions('package', [])
-
- def testGrantPermissions_underM(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.LOLLIPOP):
- self.device.GrantPermissions('package', ['p1'])
-
- def testGrantPermissions_one(self):
- permissions_cmd = 'pm grant package p1'
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.MARSHMALLOW):
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- permissions_cmd, shell=True, check_return=True), [])):
- self.device.GrantPermissions('package', ['p1'])
-
- def testGrantPermissions_multiple(self):
- permissions_cmd = 'pm grant package p1&&pm grant package p2'
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.MARSHMALLOW):
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- permissions_cmd, shell=True, check_return=True), [])):
- self.device.GrantPermissions('package', ['p1', 'p2'])
-
- def testGrantPermissions_WriteExtrnalStorage(self):
- permissions_cmd = (
- 'pm grant package android.permission.WRITE_EXTERNAL_STORAGE&&'
- 'pm grant package android.permission.READ_EXTERNAL_STORAGE')
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.MARSHMALLOW):
- with self.assertCalls(
- (self.call.device.RunShellCommand(
- permissions_cmd, shell=True, check_return=True), [])):
- self.device.GrantPermissions(
- 'package', ['android.permission.WRITE_EXTERNAL_STORAGE'])
-
- def testGrantPermissions_BlackList(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.MARSHMALLOW):
- self.device.GrantPermissions(
- 'package', ['android.permission.ACCESS_MOCK_LOCATION'])
-
-
-class DeviecUtilsIsScreenOn(DeviceUtilsTest):
-
- _L_SCREEN_ON = ['test=test mInteractive=true']
- _K_SCREEN_ON = ['test=test mScreenOn=true']
- _L_SCREEN_OFF = ['mInteractive=false']
- _K_SCREEN_OFF = ['mScreenOn=false']
-
- def testIsScreenOn_onPreL(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.KITKAT):
- with self.assertCalls(
- (self.call.device._RunPipedShellCommand(
- 'dumpsys input_method | grep mScreenOn'), self._K_SCREEN_ON)):
- self.assertTrue(self.device.IsScreenOn())
-
- def testIsScreenOn_onL(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.LOLLIPOP):
- with self.assertCalls(
- (self.call.device._RunPipedShellCommand(
- 'dumpsys input_method | grep mInteractive'), self._L_SCREEN_ON)):
- self.assertTrue(self.device.IsScreenOn())
-
- def testIsScreenOn_offPreL(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.KITKAT):
- with self.assertCalls(
- (self.call.device._RunPipedShellCommand(
- 'dumpsys input_method | grep mScreenOn'), self._K_SCREEN_OFF)):
- self.assertFalse(self.device.IsScreenOn())
-
- def testIsScreenOn_offL(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.LOLLIPOP):
- with self.assertCalls(
- (self.call.device._RunPipedShellCommand(
- 'dumpsys input_method | grep mInteractive'), self._L_SCREEN_OFF)):
- self.assertFalse(self.device.IsScreenOn())
-
- def testIsScreenOn_noOutput(self):
- with self.patch_call(self.call.device.build_version_sdk,
- return_value=version_codes.LOLLIPOP):
- with self.assertCalls(
- (self.call.device._RunPipedShellCommand(
- 'dumpsys input_method | grep mInteractive'), [])):
- with self.assertRaises(device_errors.CommandFailedError):
- self.device.IsScreenOn()
-
-
-class DeviecUtilsSetScreen(DeviceUtilsTest):
-
- @mock.patch('time.sleep', mock.Mock())
- def testSetScren_alreadySet(self):
- with self.assertCalls(
- (self.call.device.IsScreenOn(), False)):
- self.device.SetScreen(False)
-
- @mock.patch('time.sleep', mock.Mock())
- def testSetScreen_on(self):
- with self.assertCalls(
- (self.call.device.IsScreenOn(), False),
- (self.call.device.SendKeyEvent(keyevent.KEYCODE_POWER), None),
- (self.call.device.IsScreenOn(), True)):
- self.device.SetScreen(True)
-
- @mock.patch('time.sleep', mock.Mock())
- def testSetScreen_off(self):
- with self.assertCalls(
- (self.call.device.IsScreenOn(), True),
- (self.call.device.SendKeyEvent(keyevent.KEYCODE_POWER), None),
- (self.call.device.IsScreenOn(), False)):
- self.device.SetScreen(False)
-
- @mock.patch('time.sleep', mock.Mock())
- def testSetScreen_slow(self):
- with self.assertCalls(
- (self.call.device.IsScreenOn(), True),
- (self.call.device.SendKeyEvent(keyevent.KEYCODE_POWER), None),
- (self.call.device.IsScreenOn(), True),
- (self.call.device.IsScreenOn(), True),
- (self.call.device.IsScreenOn(), False)):
- self.device.SetScreen(False)
-
-class DeviecUtilsLoadCacheData(DeviceUtilsTest):
-
- def testTokenMissing(self):
- with self.assertCalls(
- self.EnsureCacheInitialized()):
- self.assertFalse(self.device.LoadCacheData('{}'))
-
- def testTokenStale(self):
- with self.assertCalls(
- self.EnsureCacheInitialized()):
- self.assertFalse(self.device.LoadCacheData('{"token":"foo"}'))
-
- def testTokenMatches(self):
- with self.assertCalls(
- self.EnsureCacheInitialized()):
- self.assertTrue(self.device.LoadCacheData('{"token":"TOKEN"}'))
-
- def testDumpThenLoad(self):
- with self.assertCalls(
- self.EnsureCacheInitialized()):
- data = json.loads(self.device.DumpCacheData())
- data['token'] = 'TOKEN'
- self.assertTrue(self.device.LoadCacheData(json.dumps(data)))
-
-
-if __name__ == '__main__':
- logging.getLogger().setLevel(logging.DEBUG)
- unittest.main(verbosity=2)
diff --git a/third_party/catapult/devil/devil/android/fastboot_utils.py b/third_party/catapult/devil/devil/android/fastboot_utils.py
deleted file mode 100644
index 3bd3ee8b6e..0000000000
--- a/third_party/catapult/devil/devil/android/fastboot_utils.py
+++ /dev/null
@@ -1,256 +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.
-
-"""Provides a variety of device interactions based on fastboot."""
-# pylint: disable=unused-argument
-
-import collections
-import contextlib
-import fnmatch
-import logging
-import os
-import re
-
-from devil.android import decorators
-from devil.android import device_errors
-from devil.android.sdk import fastboot
-from devil.utils import timeout_retry
-
-logger = logging.getLogger(__name__)
-
-_DEFAULT_TIMEOUT = 30
-_DEFAULT_RETRIES = 3
-_FASTBOOT_REBOOT_TIMEOUT = 10 * _DEFAULT_TIMEOUT
-_KNOWN_PARTITIONS = collections.OrderedDict([
- ('bootloader', {'image': 'bootloader*.img', 'restart': True}),
- ('radio', {'image': 'radio*.img', 'restart': True}),
- ('boot', {'image': 'boot.img'}),
- ('recovery', {'image': 'recovery.img'}),
- ('system', {'image': 'system.img'}),
- ('userdata', {'image': 'userdata.img', 'wipe_only': True}),
- ('cache', {'image': 'cache.img', 'wipe_only': True}),
- ('vendor', {'image': 'vendor*.img', 'optional': True}),
- ])
-ALL_PARTITIONS = _KNOWN_PARTITIONS.keys()
-
-
-def _FindAndVerifyPartitionsAndImages(partitions, directory):
- """Validate partitions and images.
-
- Validate all partition names and partition directories. Cannot stop mid
- flash so its important to validate everything first.
-
- Args:
- Partitions: partitions to be tested.
- directory: directory containing the images.
-
- Returns:
- Dictionary with exact partition, image name mapping.
- """
-
- files = os.listdir(directory)
- return_dict = collections.OrderedDict()
-
- def find_file(pattern):
- for filename in files:
- if fnmatch.fnmatch(filename, pattern):
- return os.path.join(directory, filename)
- return None
- for partition in partitions:
- partition_info = _KNOWN_PARTITIONS[partition]
- image_file = find_file(partition_info['image'])
- if image_file:
- return_dict[partition] = image_file
- elif not partition_info.get('optional'):
- raise device_errors.FastbootCommandFailedError(
- 'Failed to flash device. Could not find image for %s.',
- partition_info['image'])
- return return_dict
-
-
-class FastbootUtils(object):
-
- _FASTBOOT_WAIT_TIME = 1
- _BOARD_VERIFICATION_FILE = 'android-info.txt'
-
- def __init__(self, device, fastbooter=None, default_timeout=_DEFAULT_TIMEOUT,
- default_retries=_DEFAULT_RETRIES):
- """FastbootUtils constructor.
-
- Example Usage to flash a device:
- fastboot = fastboot_utils.FastbootUtils(device)
- fastboot.FlashDevice('/path/to/build/directory')
-
- Args:
- device: A DeviceUtils instance.
- fastbooter: Optional fastboot object. If none is passed, one will
- be created.
- default_timeout: An integer containing the default number of seconds to
- wait for an operation to complete if no explicit value is provided.
- default_retries: An integer containing the default number or times an
- operation should be retried on failure if no explicit value is provided.
- """
- self._device = device
- self._board = device.product_board
- self._serial = str(device)
- self._default_timeout = default_timeout
- self._default_retries = default_retries
- if fastbooter:
- self.fastboot = fastbooter
- else:
- self.fastboot = fastboot.Fastboot(self._serial)
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def WaitForFastbootMode(self, timeout=None, retries=None):
- """Wait for device to boot into fastboot mode.
-
- This waits for the device serial to show up in fastboot devices output.
- """
- def fastboot_mode():
- return self._serial in self.fastboot.Devices()
-
- timeout_retry.WaitFor(fastboot_mode, wait_period=self._FASTBOOT_WAIT_TIME)
-
- @decorators.WithTimeoutAndRetriesFromInstance(
- min_default_timeout=_FASTBOOT_REBOOT_TIMEOUT)
- def EnableFastbootMode(self, timeout=None, retries=None):
- """Reboots phone into fastboot mode.
-
- Roots phone if needed, then reboots phone into fastboot mode and waits.
- """
- self._device.EnableRoot()
- self._device.adb.Reboot(to_bootloader=True)
- self.WaitForFastbootMode()
-
- @decorators.WithTimeoutAndRetriesFromInstance(
- min_default_timeout=_FASTBOOT_REBOOT_TIMEOUT)
- def Reboot(
- self, bootloader=False, wait_for_reboot=True, timeout=None, retries=None):
- """Reboots out of fastboot mode.
-
- It reboots the phone either back into fastboot, or to a regular boot. It
- then blocks until the device is ready.
-
- Args:
- bootloader: If set to True, reboots back into bootloader.
- """
- if bootloader:
- self.fastboot.RebootBootloader()
- self.WaitForFastbootMode()
- else:
- self.fastboot.Reboot()
- if wait_for_reboot:
- self._device.WaitUntilFullyBooted(timeout=_FASTBOOT_REBOOT_TIMEOUT)
-
- def _VerifyBoard(self, directory):
- """Validate as best as possible that the android build matches the device.
-
- Goes through build files and checks if the board name is mentioned in the
- |self._BOARD_VERIFICATION_FILE| or in the build archive.
-
- Args:
- directory: directory where build files are located.
- """
- files = os.listdir(directory)
- board_regex = re.compile(r'require board=(\w+)')
- if self._BOARD_VERIFICATION_FILE in files:
- with open(os.path.join(directory, self._BOARD_VERIFICATION_FILE)) as f:
- for line in f:
- m = board_regex.match(line)
- if m:
- board_name = m.group(1)
- if board_name == self._board:
- return True
- elif board_name:
- return False
- else:
- logger.warning('No board type found in %s.',
- self._BOARD_VERIFICATION_FILE)
- else:
- logger.warning('%s not found. Unable to use it to verify device.',
- self._BOARD_VERIFICATION_FILE)
-
- zip_regex = re.compile(r'.*%s.*\.zip' % re.escape(self._board))
- for f in files:
- if zip_regex.match(f):
- return True
-
- return False
-
- def _FlashPartitions(self, partitions, directory, wipe=False, force=False):
- """Flashes all given partiitons with all given images.
-
- Args:
- partitions: List of partitions to flash.
- directory: Directory where all partitions can be found.
- wipe: If set to true, will automatically detect if cache and userdata
- partitions are sent, and if so ignore them.
- force: boolean to decide to ignore board name safety checks.
-
- Raises:
- device_errors.CommandFailedError(): If image cannot be found or if bad
- partition name is give.
- """
- if not self._VerifyBoard(directory):
- if force:
- logger.warning('Could not verify build is meant to be installed on '
- 'the current device type, but force flag is set. '
- 'Flashing device. Possibly dangerous operation.')
- else:
- raise device_errors.CommandFailedError(
- 'Could not verify build is meant to be installed on the current '
- 'device type. Run again with force=True to force flashing with an '
- 'unverified board.')
-
- flash_image_files = _FindAndVerifyPartitionsAndImages(partitions, directory)
- partitions = flash_image_files.keys()
- for partition in partitions:
- if _KNOWN_PARTITIONS[partition].get('wipe_only') and not wipe:
- logger.info(
- 'Not flashing in wipe mode. Skipping partition %s.', partition)
- else:
- logger.info(
- 'Flashing %s with %s', partition, flash_image_files[partition])
- self.fastboot.Flash(partition, flash_image_files[partition])
- if _KNOWN_PARTITIONS[partition].get('restart', False):
- self.Reboot(bootloader=True)
-
- @contextlib.contextmanager
- def FastbootMode(self, wait_for_reboot=True, timeout=None, retries=None):
- """Context manager that enables fastboot mode, and reboots after.
-
- Example usage:
- with FastbootMode():
- Flash Device
- # Anything that runs after flashing.
- """
- self.EnableFastbootMode()
- self.fastboot.SetOemOffModeCharge(False)
- try:
- yield self
- finally:
- self.fastboot.SetOemOffModeCharge(True)
- self.Reboot(wait_for_reboot=wait_for_reboot)
-
- def FlashDevice(self, directory, partitions=None, wipe=False):
- """Flash device with build in |directory|.
-
- Directory must contain bootloader, radio, boot, recovery, system, userdata,
- and cache .img files from an android build. This is a dangerous operation so
- use with care.
-
- Args:
- fastboot: A FastbootUtils instance.
- directory: Directory with build files.
- wipe: Wipes cache and userdata if set to true.
- partitions: List of partitions to flash. Defaults to all.
- """
- if partitions is None:
- partitions = ALL_PARTITIONS
- # If a device is wiped, then it will no longer have adb keys so it cannot be
- # communicated with to verify that it is rebooted. It is up to the user of
- # this script to ensure that the adb keys are set on the device after using
- # this to wipe a device.
- with self.FastbootMode(wait_for_reboot=not wipe):
- self._FlashPartitions(partitions, directory, wipe=wipe)
diff --git a/third_party/catapult/devil/devil/android/fastboot_utils_test.py b/third_party/catapult/devil/devil/android/fastboot_utils_test.py
deleted file mode 100755
index 05629746e5..0000000000
--- a/third_party/catapult/devil/devil/android/fastboot_utils_test.py
+++ /dev/null
@@ -1,375 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""
-Unit tests for the contents of fastboot_utils.py
-"""
-
-# pylint: disable=protected-access,unused-argument
-
-import collections
-import io
-import logging
-import unittest
-
-from devil import devil_env
-from devil.android import device_errors
-from devil.android import device_utils
-from devil.android import fastboot_utils
-from devil.android.sdk import fastboot
-from devil.utils import mock_calls
-
-with devil_env.SysPath(devil_env.PYMOCK_PATH):
- import mock # pylint: disable=import-error
-
-_BOARD = 'board_type'
-_SERIAL = '0123456789abcdef'
-_PARTITIONS = [
- 'bootloader', 'radio', 'boot', 'recovery', 'system', 'userdata', 'cache']
-_IMAGES = collections.OrderedDict([
- ('bootloader', 'bootloader.img'),
- ('radio', 'radio.img'),
- ('boot', 'boot.img'),
- ('recovery', 'recovery.img'),
- ('system', 'system.img'),
- ('userdata', 'userdata.img'),
- ('cache', 'cache.img')
-])
-_VALID_FILES = [_BOARD + '.zip', 'android-info.txt']
-_INVALID_FILES = ['test.zip', 'android-info.txt']
-
-
-class MockFile(object):
-
- def __init__(self, name='/tmp/some/file'):
- self.file = mock.MagicMock(spec=file)
- self.file.name = name
-
- def __enter__(self):
- return self.file
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- pass
-
- @property
- def name(self):
- return self.file.name
-
-
-def _FastbootWrapperMock(test_serial):
- fastbooter = mock.Mock(spec=fastboot.Fastboot)
- fastbooter.__str__ = mock.Mock(return_value=test_serial)
- fastbooter.Devices.return_value = [test_serial]
- return fastbooter
-
-
-def _DeviceUtilsMock(test_serial):
- device = mock.Mock(spec=device_utils.DeviceUtils)
- device.__str__ = mock.Mock(return_value=test_serial)
- device.product_board = mock.Mock(return_value=_BOARD)
- device.adb = mock.Mock()
- return device
-
-
-class FastbootUtilsTest(mock_calls.TestCase):
-
- def setUp(self):
- self.device_utils_mock = _DeviceUtilsMock(_SERIAL)
- self.fastboot_wrapper = _FastbootWrapperMock(_SERIAL)
- self.fastboot = fastboot_utils.FastbootUtils(
- self.device_utils_mock, fastbooter=self.fastboot_wrapper,
- default_timeout=2, default_retries=0)
- self.fastboot._board = _BOARD
-
-
-class FastbootUtilsInitTest(FastbootUtilsTest):
-
- def testInitWithDeviceUtil(self):
- f = fastboot_utils.FastbootUtils(self.device_utils_mock)
- self.assertEqual(str(self.device_utils_mock), str(f._device))
-
- def testInitWithMissing_fails(self):
- with self.assertRaises(AttributeError):
- fastboot_utils.FastbootUtils(None)
- with self.assertRaises(AttributeError):
- fastboot_utils.FastbootUtils('')
-
- def testPartitionOrdering(self):
- parts = ['bootloader', 'radio', 'boot', 'recovery', 'system', 'userdata',
- 'cache', 'vendor']
- self.assertListEqual(fastboot_utils.ALL_PARTITIONS, parts)
-
-
-class FastbootUtilsWaitForFastbootMode(FastbootUtilsTest):
-
- # If this test fails by timing out after 1 second.
- @mock.patch('time.sleep', mock.Mock())
- def testWaitForFastbootMode(self):
- self.fastboot.WaitForFastbootMode()
-
-
-class FastbootUtilsEnableFastbootMode(FastbootUtilsTest):
-
- def testEnableFastbootMode(self):
- with self.assertCalls(
- self.call.fastboot._device.EnableRoot(),
- self.call.fastboot._device.adb.Reboot(to_bootloader=True),
- self.call.fastboot.WaitForFastbootMode()):
- self.fastboot.EnableFastbootMode()
-
-
-class FastbootUtilsReboot(FastbootUtilsTest):
-
- def testReboot_bootloader(self):
- with self.assertCalls(
- self.call.fastboot.fastboot.RebootBootloader(),
- self.call.fastboot.WaitForFastbootMode()):
- self.fastboot.Reboot(bootloader=True)
-
- def testReboot_normal(self):
- with self.assertCalls(
- self.call.fastboot.fastboot.Reboot(),
- self.call.fastboot._device.WaitUntilFullyBooted(timeout=mock.ANY)):
- self.fastboot.Reboot()
-
-
-class FastbootUtilsFlashPartitions(FastbootUtilsTest):
-
- def testFlashPartitions_wipe(self):
- with self.assertCalls(
- (self.call.fastboot._VerifyBoard('test'), True),
- (mock.call.devil.android.fastboot_utils.
- _FindAndVerifyPartitionsAndImages(_PARTITIONS, 'test'), _IMAGES),
- (self.call.fastboot.fastboot.Flash('bootloader', 'bootloader.img')),
- (self.call.fastboot.Reboot(bootloader=True)),
- (self.call.fastboot.fastboot.Flash('radio', 'radio.img')),
- (self.call.fastboot.Reboot(bootloader=True)),
- (self.call.fastboot.fastboot.Flash('boot', 'boot.img')),
- (self.call.fastboot.fastboot.Flash('recovery', 'recovery.img')),
- (self.call.fastboot.fastboot.Flash('system', 'system.img')),
- (self.call.fastboot.fastboot.Flash('userdata', 'userdata.img')),
- (self.call.fastboot.fastboot.Flash('cache', 'cache.img'))):
- self.fastboot._FlashPartitions(_PARTITIONS, 'test', wipe=True)
-
- def testFlashPartitions_noWipe(self):
- with self.assertCalls(
- (self.call.fastboot._VerifyBoard('test'), True),
- (mock.call.devil.android.fastboot_utils.
- _FindAndVerifyPartitionsAndImages(_PARTITIONS, 'test'), _IMAGES),
- (self.call.fastboot.fastboot.Flash('bootloader', 'bootloader.img')),
- (self.call.fastboot.Reboot(bootloader=True)),
- (self.call.fastboot.fastboot.Flash('radio', 'radio.img')),
- (self.call.fastboot.Reboot(bootloader=True)),
- (self.call.fastboot.fastboot.Flash('boot', 'boot.img')),
- (self.call.fastboot.fastboot.Flash('recovery', 'recovery.img')),
- (self.call.fastboot.fastboot.Flash('system', 'system.img'))):
- self.fastboot._FlashPartitions(_PARTITIONS, 'test')
-
-
-class FastbootUtilsFastbootMode(FastbootUtilsTest):
-
- def testFastbootMode_goodWait(self):
- with self.assertCalls(
- self.call.fastboot.EnableFastbootMode(),
- self.call.fastboot.fastboot.SetOemOffModeCharge(False),
- self.call.fastboot.fastboot.SetOemOffModeCharge(True),
- self.call.fastboot.Reboot(wait_for_reboot=True)):
- with self.fastboot.FastbootMode() as fbm:
- self.assertEqual(self.fastboot, fbm)
-
- def testFastbootMode_goodNoWait(self):
- with self.assertCalls(
- self.call.fastboot.EnableFastbootMode(),
- self.call.fastboot.fastboot.SetOemOffModeCharge(False),
- self.call.fastboot.fastboot.SetOemOffModeCharge(True),
- self.call.fastboot.Reboot(wait_for_reboot=False)):
- with self.fastboot.FastbootMode(wait_for_reboot=False) as fbm:
- self.assertEqual(self.fastboot, fbm)
-
- def testFastbootMode_exception(self):
- with self.assertCalls(
- self.call.fastboot.EnableFastbootMode(),
- self.call.fastboot.fastboot.SetOemOffModeCharge(False),
- self.call.fastboot.fastboot.SetOemOffModeCharge(True),
- self.call.fastboot.Reboot(wait_for_reboot=True)):
- with self.assertRaises(NotImplementedError):
- with self.fastboot.FastbootMode() as fbm:
- self.assertEqual(self.fastboot, fbm)
- raise NotImplementedError
-
- def testFastbootMode_exceptionInEnableFastboot(self):
- self.fastboot.EnableFastbootMode = mock.Mock()
- self.fastboot.EnableFastbootMode.side_effect = NotImplementedError
- with self.assertRaises(NotImplementedError):
- with self.fastboot.FastbootMode():
- pass
-
-
-class FastbootUtilsVerifyBoard(FastbootUtilsTest):
-
- def testVerifyBoard_bothValid(self):
- mock_file = io.StringIO(u'require board=%s\n' % _BOARD)
- with mock.patch('__builtin__.open', return_value=mock_file, create=True):
- with mock.patch('os.listdir', return_value=_VALID_FILES):
- self.assertTrue(self.fastboot._VerifyBoard('test'))
-
- def testVerifyBoard_BothNotValid(self):
- mock_file = io.StringIO(u'abc')
- with mock.patch('__builtin__.open', return_value=mock_file, create=True):
- with mock.patch('os.listdir', return_value=_INVALID_FILES):
- self.assertFalse(self.assertFalse(self.fastboot._VerifyBoard('test')))
-
- def testVerifyBoard_FileNotFoundZipValid(self):
- with mock.patch('os.listdir', return_value=[_BOARD + '.zip']):
- self.assertTrue(self.fastboot._VerifyBoard('test'))
-
- def testVerifyBoard_ZipNotFoundFileValid(self):
- mock_file = io.StringIO(u'require board=%s\n' % _BOARD)
- with mock.patch('__builtin__.open', return_value=mock_file, create=True):
- with mock.patch('os.listdir', return_value=['android-info.txt']):
- self.assertTrue(self.fastboot._VerifyBoard('test'))
-
- def testVerifyBoard_zipNotValidFileIs(self):
- mock_file = io.StringIO(u'require board=%s\n' % _BOARD)
- with mock.patch('__builtin__.open', return_value=mock_file, create=True):
- with mock.patch('os.listdir', return_value=_INVALID_FILES):
- self.assertTrue(self.fastboot._VerifyBoard('test'))
-
- def testVerifyBoard_fileNotValidZipIs(self):
- mock_file = io.StringIO(u'require board=WrongBoard')
- with mock.patch('__builtin__.open', return_value=mock_file, create=True):
- with mock.patch('os.listdir', return_value=_VALID_FILES):
- self.assertFalse(self.fastboot._VerifyBoard('test'))
-
- def testVerifyBoard_noBoardInFileValidZip(self):
- mock_file = io.StringIO(u'Regex wont match')
- with mock.patch('__builtin__.open', return_value=mock_file, create=True):
- with mock.patch('os.listdir', return_value=_VALID_FILES):
- self.assertTrue(self.fastboot._VerifyBoard('test'))
-
- def testVerifyBoard_noBoardInFileInvalidZip(self):
- mock_file = io.StringIO(u'Regex wont match')
- with mock.patch('__builtin__.open', return_value=mock_file, create=True):
- with mock.patch('os.listdir', return_value=_INVALID_FILES):
- self.assertFalse(self.fastboot._VerifyBoard('test'))
-
-
-class FastbootUtilsFindAndVerifyPartitionsAndImages(FastbootUtilsTest):
-
- def testFindAndVerifyPartitionsAndImages_validNoVendor(self):
- PARTITIONS = [
- 'bootloader', 'radio', 'boot', 'recovery', 'system', 'userdata',
- 'cache', 'vendor'
- ]
- files = [
- 'bootloader-test-.img',
- 'radio123.img',
- 'boot.img',
- 'recovery.img',
- 'system.img',
- 'userdata.img',
- 'cache.img'
- ]
- img_check = collections.OrderedDict([
- ('bootloader', 'test/bootloader-test-.img'),
- ('radio', 'test/radio123.img'),
- ('boot', 'test/boot.img'),
- ('recovery', 'test/recovery.img'),
- ('system', 'test/system.img'),
- ('userdata', 'test/userdata.img'),
- ('cache', 'test/cache.img'),
- ])
- parts_check = [
- 'bootloader', 'radio', 'boot', 'recovery', 'system', 'userdata',
- 'cache'
- ]
- with mock.patch('os.listdir', return_value=files):
- imgs = fastboot_utils._FindAndVerifyPartitionsAndImages(
- PARTITIONS, 'test')
- parts = imgs.keys()
- self.assertDictEqual(imgs, img_check)
- self.assertListEqual(parts, parts_check)
-
- def testFindAndVerifyPartitionsAndImages_validVendor(self):
- PARTITIONS = [
- 'bootloader', 'radio', 'boot', 'recovery', 'system', 'userdata',
- 'cache', 'vendor'
- ]
- files = [
- 'bootloader-test-.img',
- 'radio123.img',
- 'boot.img',
- 'recovery.img',
- 'system.img',
- 'userdata.img',
- 'cache.img',
- 'vendor.img'
- ]
- img_check = {
- 'bootloader': 'test/bootloader-test-.img',
- 'radio': 'test/radio123.img',
- 'boot': 'test/boot.img',
- 'recovery': 'test/recovery.img',
- 'system': 'test/system.img',
- 'userdata': 'test/userdata.img',
- 'cache': 'test/cache.img',
- 'vendor': 'test/vendor.img',
- }
- parts_check = [
- 'bootloader', 'radio', 'boot', 'recovery', 'system', 'userdata',
- 'cache', 'vendor'
- ]
-
- with mock.patch('os.listdir', return_value=files):
- imgs = fastboot_utils._FindAndVerifyPartitionsAndImages(
- PARTITIONS, 'test')
- parts = imgs.keys()
- self.assertDictEqual(imgs, img_check)
- self.assertListEqual(parts, parts_check)
-
- def testFindAndVerifyPartitionsAndImages_badPartition(self):
- with mock.patch('os.listdir', return_value=['test']):
- with self.assertRaises(KeyError):
- fastboot_utils._FindAndVerifyPartitionsAndImages(['test'], 'test')
-
- def testFindAndVerifyPartitionsAndImages_noFile(self):
- with mock.patch('os.listdir', return_value=['test']):
- with self.assertRaises(device_errors.FastbootCommandFailedError):
- fastboot_utils._FindAndVerifyPartitionsAndImages(['cache'], 'test')
-
-
-class FastbootUtilsFlashDevice(FastbootUtilsTest):
-
- def testFlashDevice_wipe(self):
- with self.assertCalls(
- self.call.fastboot.EnableFastbootMode(),
- self.call.fastboot.fastboot.SetOemOffModeCharge(False),
- self.call.fastboot._FlashPartitions(mock.ANY, 'test', wipe=True),
- self.call.fastboot.fastboot.SetOemOffModeCharge(True),
- self.call.fastboot.Reboot(wait_for_reboot=False)):
- self.fastboot.FlashDevice('test', wipe=True)
-
- def testFlashDevice_noWipe(self):
- with self.assertCalls(
- self.call.fastboot.EnableFastbootMode(),
- self.call.fastboot.fastboot.SetOemOffModeCharge(False),
- self.call.fastboot._FlashPartitions(mock.ANY, 'test', wipe=False),
- self.call.fastboot.fastboot.SetOemOffModeCharge(True),
- self.call.fastboot.Reboot(wait_for_reboot=True)):
- self.fastboot.FlashDevice('test', wipe=False)
-
- def testFlashDevice_partitions(self):
- with self.assertCalls(
- self.call.fastboot.EnableFastbootMode(),
- self.call.fastboot.fastboot.SetOemOffModeCharge(False),
- self.call.fastboot._FlashPartitions(['boot'], 'test', wipe=False),
- self.call.fastboot.fastboot.SetOemOffModeCharge(True),
- self.call.fastboot.Reboot(wait_for_reboot=True)):
- self.fastboot.FlashDevice('test', partitions=['boot'], wipe=False)
-
-
-if __name__ == '__main__':
- logging.getLogger().setLevel(logging.DEBUG)
- unittest.main(verbosity=2)
diff --git a/third_party/catapult/devil/devil/android/flag_changer.py b/third_party/catapult/devil/devil/android/flag_changer.py
deleted file mode 100644
index b2ee8b163a..0000000000
--- a/third_party/catapult/devil/devil/android/flag_changer.py
+++ /dev/null
@@ -1,300 +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.
-
-import contextlib
-import logging
-import posixpath
-import re
-
-from devil.android.sdk import version_codes
-
-
-logger = logging.getLogger(__name__)
-
-
-_CMDLINE_DIR = '/data/local/tmp'
-_CMDLINE_DIR_LEGACY = '/data/local'
-_RE_NEEDS_QUOTING = re.compile(r'[^\w-]') # Not in: alphanumeric or hyphens.
-_QUOTES = '"\'' # Either a single or a double quote.
-_ESCAPE = '\\' # A backslash.
-
-
-@contextlib.contextmanager
-def CustomCommandLineFlags(device, cmdline_name, flags):
- """Context manager to change Chrome's command line temporarily.
-
- Example:
-
- with flag_changer.TemporaryCommandLineFlags(device, name, flags):
- # Launching Chrome will use the provided flags.
-
- # Previous set of flags on the device is now restored.
-
- Args:
- device: A DeviceUtils instance.
- cmdline_name: Name of the command line file where to store flags.
- flags: A sequence of command line flags to set.
- """
- # On Android N and above, we need to temporarily set SELinux to permissive
- # so that Chrome is allowed to read the command line file.
- # TODO(crbug.com/699082): Remove when a solution to avoid this is implemented.
- needs_permissive = (
- device.build_version_sdk >= version_codes.NOUGAT and
- device.GetEnforce())
- if needs_permissive:
- device.SetEnforce(enabled=False)
- try:
- changer = FlagChanger(device, cmdline_name)
- try:
- changer.ReplaceFlags(flags)
- yield
- finally:
- changer.Restore()
- finally:
- if needs_permissive:
- device.SetEnforce(enabled=True)
-
-
-class FlagChanger(object):
- """Changes the flags Chrome runs with.
-
- Flags can be temporarily set for a particular set of unit tests. These
- tests should call Restore() to revert the flags to their original state
- once the tests have completed.
- """
-
- def __init__(self, device, cmdline_file):
- """Initializes the FlagChanger and records the original arguments.
-
- Args:
- device: A DeviceUtils instance.
- cmdline_file: Name of the command line file where to store flags.
- """
- self._device = device
-
- if posixpath.sep in cmdline_file:
- raise ValueError(
- 'cmdline_file should be a file name only, do not include path'
- ' separators in: %s' % cmdline_file)
- self._cmdline_path = posixpath.join(_CMDLINE_DIR, cmdline_file)
-
- cmdline_path_legacy = posixpath.join(_CMDLINE_DIR_LEGACY, cmdline_file)
- if self._device.PathExists(cmdline_path_legacy):
- logging.warning(
- 'Removing legacy command line file %r.', cmdline_path_legacy)
- self._device.RemovePath(cmdline_path_legacy, as_root=True)
-
- self._state_stack = [None] # Actual state is set by GetCurrentFlags().
- self.GetCurrentFlags()
-
- def GetCurrentFlags(self):
- """Read the current flags currently stored in the device.
-
- Also updates the internal state of the flag_changer.
-
- Returns:
- A list of flags.
- """
- if self._device.PathExists(self._cmdline_path):
- command_line = self._device.ReadFile(self._cmdline_path).strip()
- else:
- command_line = ''
- flags = _ParseFlags(command_line)
-
- # Store the flags as a set to facilitate adding and removing flags.
- self._state_stack[-1] = set(flags)
- return flags
-
- def ReplaceFlags(self, flags):
- """Replaces the flags in the command line with the ones provided.
- Saves the current flags state on the stack, so a call to Restore will
- change the state back to the one preceeding the call to ReplaceFlags.
-
- Args:
- flags: A sequence of command line flags to set, eg. ['--single-process'].
- Note: this should include flags only, not the name of a command
- to run (ie. there is no need to start the sequence with 'chrome').
-
- Returns:
- A list with the flags now stored on the device.
- """
- new_flags = set(flags)
- self._state_stack.append(new_flags)
- return self._UpdateCommandLineFile()
-
- def AddFlags(self, flags):
- """Appends flags to the command line if they aren't already there.
- Saves the current flags state on the stack, so a call to Restore will
- change the state back to the one preceeding the call to AddFlags.
-
- Args:
- flags: A sequence of flags to add on, eg. ['--single-process'].
-
- Returns:
- A list with the flags now stored on the device.
- """
- return self.PushFlags(add=flags)
-
- def RemoveFlags(self, flags):
- """Removes flags from the command line, if they exist.
- Saves the current flags state on the stack, so a call to Restore will
- change the state back to the one preceeding the call to RemoveFlags.
-
- Note that calling RemoveFlags after AddFlags will result in having
- two nested states.
-
- Args:
- flags: A sequence of flags to remove, eg. ['--single-process']. Note
- that we expect a complete match when removing flags; if you want
- to remove a switch with a value, you must use the exact string
- used to add it in the first place.
-
- Returns:
- A list with the flags now stored on the device.
- """
- return self.PushFlags(remove=flags)
-
- def PushFlags(self, add=None, remove=None):
- """Appends and removes flags to/from the command line if they aren't already
- there. Saves the current flags state on the stack, so a call to Restore
- will change the state back to the one preceeding the call to PushFlags.
-
- Args:
- add: A list of flags to add on, eg. ['--single-process'].
- remove: A list of flags to remove, eg. ['--single-process']. Note that we
- expect a complete match when removing flags; if you want to remove
- a switch with a value, you must use the exact string used to add
- it in the first place.
-
- Returns:
- A list with the flags now stored on the device.
- """
- new_flags = self._state_stack[-1].copy()
- if add:
- new_flags.update(add)
- if remove:
- new_flags.difference_update(remove)
- return self.ReplaceFlags(new_flags)
-
- def Restore(self):
- """Restores the flags to their state prior to the last AddFlags or
- RemoveFlags call.
-
- Returns:
- A list with the flags now stored on the device.
- """
- # The initial state must always remain on the stack.
- assert len(self._state_stack) > 1, (
- "Mismatch between calls to Add/RemoveFlags and Restore")
- self._state_stack.pop()
- return self._UpdateCommandLineFile()
-
- def _UpdateCommandLineFile(self):
- """Writes out the command line to the file, or removes it if empty.
-
- Returns:
- A list with the flags now stored on the device.
- """
- command_line = _SerializeFlags(self._state_stack[-1])
- if command_line is not None:
- self._device.WriteFile(self._cmdline_path, command_line)
- else:
- self._device.RemovePath(self._cmdline_path, force=True)
-
- current_flags = self.GetCurrentFlags()
- logger.info('Flags now set on the device: %s', current_flags)
- return current_flags
-
-
-def _ParseFlags(line):
- """Parse the string containing the command line into a list of flags.
-
- It's a direct port of CommandLine.java::tokenizeQuotedArguments.
-
- The first token is assumed to be the (unused) program name and stripped off
- from the list of flags.
-
- Args:
- line: A string containing the entire command line. The first token is
- assumed to be the program name.
-
- Returns:
- A list of flags, with quoting removed.
- """
- flags = []
- current_quote = None
- current_flag = None
-
- for c in line:
- # Detect start or end of quote block.
- if (current_quote is None and c in _QUOTES) or c == current_quote:
- if current_flag is not None and current_flag[-1] == _ESCAPE:
- # Last char was a backslash; pop it, and treat c as a literal.
- current_flag = current_flag[:-1] + c
- else:
- current_quote = c if current_quote is None else None
- elif current_quote is None and c.isspace():
- if current_flag is not None:
- flags.append(current_flag)
- current_flag = None
- else:
- if current_flag is None:
- current_flag = ''
- current_flag += c
-
- if current_flag is not None:
- if current_quote is not None:
- logger.warning('Unterminated quoted argument: ' + current_flag)
- flags.append(current_flag)
-
- # Return everything but the program name.
- return flags[1:]
-
-
-def _SerializeFlags(flags):
- """Serialize a sequence of flags into a command line string.
-
- Args:
- flags: A sequence of strings with individual flags.
-
- Returns:
- A line with the command line contents to save; or None if the sequence of
- flags is empty.
- """
- if flags:
- # The first command line argument doesn't matter as we are not actually
- # launching the chrome executable using this command line.
- args = ['_']
- args.extend(_QuoteFlag(f) for f in flags)
- return ' '.join(args)
- else:
- return None
-
-
-def _QuoteFlag(flag):
- """Validate and quote a single flag.
-
- Args:
- A string with the flag to quote.
-
- Returns:
- A string with the flag quoted so that it can be parsed by the algorithm
- in _ParseFlags; or None if the flag does not appear to be valid.
- """
- if '=' in flag:
- key, value = flag.split('=', 1)
- else:
- key, value = flag, None
-
- if not flag or _RE_NEEDS_QUOTING.search(key):
- # Probably not a valid flag, but quote the whole thing so it can be
- # parsed back correctly.
- return '"%s"' % flag.replace('"', r'\"')
-
- if value is None:
- return key
-
- if _RE_NEEDS_QUOTING.search(value):
- value = '"%s"' % value.replace('"', r'\"')
- return '='.join([key, value])
diff --git a/third_party/catapult/devil/devil/android/flag_changer_devicetest.py b/third_party/catapult/devil/devil/android/flag_changer_devicetest.py
deleted file mode 100644
index b75504b52a..0000000000
--- a/third_party/catapult/devil/devil/android/flag_changer_devicetest.py
+++ /dev/null
@@ -1,88 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-"""
-Unit tests for the contents of flag_changer.py.
-The test will invoke real devices
-"""
-
-import os
-import posixpath
-import sys
-import unittest
-
-if __name__ == '__main__':
- sys.path.append(
- os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', )))
-
-from devil.android import device_test_case
-from devil.android import device_utils
-from devil.android import flag_changer
-from devil.android.sdk import adb_wrapper
-
-
-_CMDLINE_FILE = 'dummy-command-line'
-
-
-class FlagChangerTest(device_test_case.DeviceTestCase):
-
- def setUp(self):
- super(FlagChangerTest, self).setUp()
- self.adb = adb_wrapper.AdbWrapper(self.serial)
- self.adb.WaitForDevice()
- self.device = device_utils.DeviceUtils(
- self.adb, default_timeout=10, default_retries=0)
- # pylint: disable=protected-access
- self.cmdline_path = posixpath.join(flag_changer._CMDLINE_DIR, _CMDLINE_FILE)
- self.cmdline_path_legacy = posixpath.join(
- flag_changer._CMDLINE_DIR_LEGACY, _CMDLINE_FILE)
-
- def tearDown(self):
- super(FlagChangerTest, self).tearDown()
- self.device.RemovePath(
- [self.cmdline_path, self.cmdline_path_legacy], force=True, as_root=True)
-
- def testFlagChanger_restoreFlags(self):
- if not self.device.HasRoot():
- self.skipTest('Test needs a rooted device')
-
- # Write some custom chrome command line flags.
- self.device.WriteFile(
- self.cmdline_path, 'chrome --some --old --flags')
-
- # Write some more flags on a command line file in the legacy location.
- self.device.WriteFile(
- self.cmdline_path_legacy, 'some --stray --flags', as_root=True)
- self.assertTrue(self.device.PathExists(self.cmdline_path_legacy))
-
- changer = flag_changer.FlagChanger(self.device, _CMDLINE_FILE)
-
- # Legacy command line file is removed, ensuring Chrome picks up the
- # right file.
- self.assertFalse(self.device.PathExists(self.cmdline_path_legacy))
-
- # Write some new files, and check they are set.
- new_flags = ['--my', '--new', '--flags=with special value']
- self.assertItemsEqual(
- changer.ReplaceFlags(new_flags),
- new_flags)
-
- # Restore and go back to the old flags.
- self.assertItemsEqual(
- changer.Restore(),
- ['--some', '--old', '--flags'])
-
- def testFlagChanger_removeFlags(self):
- self.device.RemovePath(self.cmdline_path, force=True)
- self.assertFalse(self.device.PathExists(self.cmdline_path))
-
- with flag_changer.CustomCommandLineFlags(
- self.device, _CMDLINE_FILE, ['--some', '--flags']):
- self.assertTrue(self.device.PathExists(self.cmdline_path))
-
- self.assertFalse(self.device.PathExists(self.cmdline_path))
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/third_party/catapult/devil/devil/android/flag_changer_test.py b/third_party/catapult/devil/devil/android/flag_changer_test.py
deleted file mode 100755
index 5342cf44d0..0000000000
--- a/third_party/catapult/devil/devil/android/flag_changer_test.py
+++ /dev/null
@@ -1,135 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import posixpath
-import unittest
-
-from devil.android import flag_changer
-
-
-_CMDLINE_FILE = 'chrome-command-line'
-
-
-class _FakeDevice(object):
- def __init__(self):
- self.build_type = 'user'
- self.has_root = True
- self.file_system = {}
-
- def HasRoot(self):
- return self.has_root
-
- def PathExists(self, filepath):
- return filepath in self.file_system
-
- def RemovePath(self, path, **_kwargs):
- self.file_system.pop(path)
-
- def WriteFile(self, path, contents, **_kwargs):
- self.file_system[path] = contents
-
- def ReadFile(self, path, **_kwargs):
- return self.file_system[path]
-
-
-class FlagChangerTest(unittest.TestCase):
- def setUp(self):
- self.device = _FakeDevice()
- # pylint: disable=protected-access
- self.cmdline_path = posixpath.join(flag_changer._CMDLINE_DIR, _CMDLINE_FILE)
- self.cmdline_path_legacy = posixpath.join(
- flag_changer._CMDLINE_DIR_LEGACY, _CMDLINE_FILE)
-
- def testFlagChanger_removeLegacyCmdLine(self):
- self.device.WriteFile(self.cmdline_path_legacy, 'chrome --old --stuff')
- self.assertTrue(self.device.PathExists(self.cmdline_path_legacy))
-
- changer = flag_changer.FlagChanger(self.device, 'chrome-command-line')
- self.assertEquals(
- changer._cmdline_path, # pylint: disable=protected-access
- self.cmdline_path)
- self.assertFalse(self.device.PathExists(self.cmdline_path_legacy))
-
- def testFlagChanger_mustBeFileName(self):
- with self.assertRaises(ValueError):
- flag_changer.FlagChanger(self.device, '/data/local/chrome-command-line')
-
-
-class ParseSerializeFlagsTest(unittest.TestCase):
- def _testQuoteFlag(self, flag, expected_quoted_flag):
- # Start with an unquoted flag, check that it's quoted as expected.
- # pylint: disable=protected-access
- quoted_flag = flag_changer._QuoteFlag(flag)
- self.assertEqual(quoted_flag, expected_quoted_flag)
- # Check that it survives a round-trip.
- parsed_flags = flag_changer._ParseFlags('_ %s' % quoted_flag)
- self.assertEqual(len(parsed_flags), 1)
- self.assertEqual(flag, parsed_flags[0])
-
- def testQuoteFlag_simple(self):
- self._testQuoteFlag('--simple-flag', '--simple-flag')
-
- def testQuoteFlag_withSimpleValue(self):
- self._testQuoteFlag('--key=value', '--key=value')
-
- def testQuoteFlag_withQuotedValue1(self):
- self._testQuoteFlag('--key=valueA valueB', '--key="valueA valueB"')
-
- def testQuoteFlag_withQuotedValue2(self):
- self._testQuoteFlag(
- '--key=this "should" work', r'--key="this \"should\" work"')
-
- def testQuoteFlag_withQuotedValue3(self):
- self._testQuoteFlag(
- "--key=this is 'fine' too", '''--key="this is 'fine' too"''')
-
- def testQuoteFlag_withQuotedValue4(self):
- self._testQuoteFlag(
- "--key='I really want to keep these quotes'",
- '''--key="'I really want to keep these quotes'"''')
-
- def testQuoteFlag_withQuotedValue5(self):
- self._testQuoteFlag(
- "--this is a strange=flag", '"--this is a strange=flag"')
-
- def testQuoteFlag_withEmptyValue(self):
- self._testQuoteFlag('--some-flag=', '--some-flag=')
-
- def _testParseCmdLine(self, command_line, expected_flags):
- # Start with a command line, check that flags are parsed as expected.
- # pylint: disable=protected-access
- flags = flag_changer._ParseFlags(command_line)
- self.assertItemsEqual(flags, expected_flags)
-
- # Check that flags survive a round-trip.
- # Note: Although new_command_line and command_line may not match, they
- # should describe the same set of flags.
- new_command_line = flag_changer._SerializeFlags(flags)
- new_flags = flag_changer._ParseFlags(new_command_line)
- self.assertItemsEqual(new_flags, expected_flags)
-
- def testParseCmdLine_simple(self):
- self._testParseCmdLine(
- 'chrome --foo --bar="a b" --baz=true --fine="ok"',
- ['--foo', '--bar=a b', '--baz=true', '--fine=ok'])
-
- def testParseCmdLine_withFancyQuotes(self):
- self._testParseCmdLine(
- r'''_ --foo="this 'is' ok"
- --bar='this \'is\' too'
- --baz="this \'is\' tricky"
- ''',
- ["--foo=this 'is' ok",
- "--bar=this 'is' too",
- r"--baz=this \'is\' tricky"])
-
- def testParseCmdLine_withUnterminatedQuote(self):
- self._testParseCmdLine(
- '_ --foo --bar="I forgot something',
- ['--foo', '--bar=I forgot something'])
-
-
-if __name__ == '__main__':
- unittest.main(verbosity=2)
diff --git a/third_party/catapult/devil/devil/android/forwarder.py b/third_party/catapult/devil/devil/android/forwarder.py
deleted file mode 100644
index 244f555af4..0000000000
--- a/third_party/catapult/devil/devil/android/forwarder.py
+++ /dev/null
@@ -1,464 +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.
-
-# pylint: disable=W0212
-
-import fcntl
-import logging
-import os
-import psutil
-
-from devil import base_error
-from devil import devil_env
-from devil.android import device_errors
-from devil.android.constants import file_system
-from devil.android.sdk import adb_wrapper
-from devil.android.valgrind_tools import base_tool
-from devil.utils import cmd_helper
-
-logger = logging.getLogger(__name__)
-
-# If passed as the device port, this will tell the forwarder to allocate
-# a dynamic port on the device. The actual port can then be retrieved with
-# Forwarder.DevicePortForHostPort.
-DYNAMIC_DEVICE_PORT = 0
-
-
-def _GetProcessStartTime(pid):
- return psutil.Process(pid).create_time
-
-
-def _LogMapFailureDiagnostics(device):
- # The host forwarder daemon logs to /tmp/host_forwarder_log, so print the end
- # of that.
- try:
- with open('/tmp/host_forwarder_log') as host_forwarder_log:
- logger.info('Last 50 lines of the host forwarder daemon log:')
- for line in host_forwarder_log.read().splitlines()[-50:]:
- logger.info(' %s', line)
- except Exception: # pylint: disable=broad-except
- # Grabbing the host forwarder log is best-effort. Ignore all errors.
- logger.warning('Failed to get the contents of host_forwarder_log.')
-
- # The device forwarder daemon logs to the logcat, so print the end of that.
- try:
- logger.info('Last 50 lines of logcat:')
- for logcat_line in device.adb.Logcat(dump=True)[-50:]:
- logger.info(' %s', logcat_line)
- except device_errors.CommandFailedError:
- # Grabbing the device forwarder log is also best-effort. Ignore all errors.
- logger.warning('Failed to get the contents of the logcat.')
-
- # Log alive device forwarders.
- try:
- ps_out = device.RunShellCommand(['ps'], check_return=True)
- logger.info('Currently running device_forwarders:')
- for line in ps_out:
- if 'device_forwarder' in line:
- logger.info(' %s', line)
- except device_errors.CommandFailedError:
- logger.warning('Failed to list currently running device_forwarder '
- 'instances.')
-
-
-class _FileLock(object):
- """With statement-aware implementation of a file lock.
-
- File locks are needed for cross-process synchronization when the
- multiprocessing Python module is used.
- """
-
- def __init__(self, path):
- self._fd = -1
- self._path = path
-
- def __enter__(self):
- self._fd = os.open(self._path, os.O_RDONLY | os.O_CREAT)
- if self._fd < 0:
- raise Exception('Could not open file %s for reading' % self._path)
- fcntl.flock(self._fd, fcntl.LOCK_EX)
-
- def __exit__(self, _exception_type, _exception_value, traceback):
- fcntl.flock(self._fd, fcntl.LOCK_UN)
- os.close(self._fd)
-
-
-class HostForwarderError(base_error.BaseError):
- """Exception for failures involving host_forwarder."""
-
- def __init__(self, message):
- super(HostForwarderError, self).__init__(message)
-
-
-class Forwarder(object):
- """Thread-safe class to manage port forwards from the device to the host."""
-
- _DEVICE_FORWARDER_FOLDER = (file_system.TEST_EXECUTABLE_DIR +
- '/forwarder/')
- _DEVICE_FORWARDER_PATH = (file_system.TEST_EXECUTABLE_DIR +
- '/forwarder/device_forwarder')
- _LOCK_PATH = '/tmp/chrome.forwarder.lock'
- # Defined in host_forwarder_main.cc
- _HOST_FORWARDER_LOG = '/tmp/host_forwarder_log'
-
- _TIMEOUT = 60 # seconds
-
- _instance = None
-
- @staticmethod
- def Map(port_pairs, device, tool=None):
- """Runs the forwarder.
-
- Args:
- port_pairs: A list of tuples (device_port, host_port) to forward. Note
- that you can specify 0 as a device_port, in which case a
- port will by dynamically assigned on the device. You can
- get the number of the assigned port using the
- DevicePortForHostPort method.
- device: A DeviceUtils instance.
- tool: Tool class to use to get wrapper, if necessary, for executing the
- forwarder (see valgrind_tools.py).
-
- Raises:
- Exception on failure to forward the port.
- """
- if not tool:
- tool = base_tool.BaseTool()
- with _FileLock(Forwarder._LOCK_PATH):
- instance = Forwarder._GetInstanceLocked(tool)
- instance._InitDeviceLocked(device, tool)
-
- device_serial = str(device)
- map_arg_lists = [
- ['--adb=' + adb_wrapper.AdbWrapper.GetAdbPath(),
- '--serial-id=' + device_serial,
- '--map', str(device_port), str(host_port)]
- for device_port, host_port in port_pairs]
- logger.info('Forwarding using commands: %s', map_arg_lists)
-
- for map_arg_list in map_arg_lists:
- try:
- map_cmd = [instance._host_forwarder_path] + map_arg_list
- (exit_code, output) = cmd_helper.GetCmdStatusAndOutputWithTimeout(
- map_cmd, Forwarder._TIMEOUT)
- except cmd_helper.TimeoutError as e:
- raise HostForwarderError(
- '`%s` timed out:\n%s' % (' '.join(map_cmd), e.output))
- except OSError as e:
- if e.errno == 2:
- raise HostForwarderError(
- 'Unable to start host forwarder. '
- 'Make sure you have built host_forwarder.')
- else: raise
- if exit_code != 0:
- try:
- instance._KillDeviceLocked(device, tool)
- except device_errors.CommandFailedError:
- # We don't want the failure to kill the device forwarder to
- # supersede the original failure to map.
- logging.warning(
- 'Failed to kill the device forwarder after map failure: %s',
- str(e))
- _LogMapFailureDiagnostics(device)
- formatted_output = ('\n'.join(output) if isinstance(output, list)
- else output)
- raise HostForwarderError(
- '`%s` exited with %d:\n%s' % (
- ' '.join(map_cmd),
- exit_code,
- formatted_output))
- tokens = output.split(':')
- if len(tokens) != 2:
- raise HostForwarderError(
- 'Unexpected host forwarder output "%s", '
- 'expected "device_port:host_port"' % output)
- device_port = int(tokens[0])
- host_port = int(tokens[1])
- serial_with_port = (device_serial, device_port)
- instance._device_to_host_port_map[serial_with_port] = host_port
- instance._host_to_device_port_map[host_port] = serial_with_port
- logger.info('Forwarding device port: %d to host port: %d.',
- device_port, host_port)
-
- @staticmethod
- def UnmapDevicePort(device_port, device):
- """Unmaps a previously forwarded device port.
-
- Args:
- device: A DeviceUtils instance.
- device_port: A previously forwarded port (through Map()).
- """
- with _FileLock(Forwarder._LOCK_PATH):
- Forwarder._UnmapDevicePortLocked(device_port, device)
-
- @staticmethod
- def UnmapAllDevicePorts(device):
- """Unmaps all the previously forwarded ports for the provided device.
-
- Args:
- device: A DeviceUtils instance.
- port_pairs: A list of tuples (device_port, host_port) to unmap.
- """
- with _FileLock(Forwarder._LOCK_PATH):
- instance = Forwarder._GetInstanceLocked(None)
- unmap_all_cmd = [
- instance._host_forwarder_path,
- '--adb=%s' % adb_wrapper.AdbWrapper.GetAdbPath(),
- '--serial-id=%s' % device.serial,
- '--unmap-all'
- ]
- try:
- exit_code, output = cmd_helper.GetCmdStatusAndOutputWithTimeout(
- unmap_all_cmd, Forwarder._TIMEOUT)
- except cmd_helper.TimeoutError as e:
- raise HostForwarderError(
- '`%s` timed out:\n%s' % (' '.join(unmap_all_cmd), e.output))
- if exit_code != 0:
- error_msg = [
- '`%s` exited with %d' % (' '.join(unmap_all_cmd), exit_code)]
- if isinstance(output, list):
- error_msg += output
- else:
- error_msg += [output]
- raise HostForwarderError('\n'.join(error_msg))
-
- # Clean out any entries from the device & host map.
- device_map = instance._device_to_host_port_map
- host_map = instance._host_to_device_port_map
- for device_serial_and_port, host_port in device_map.items():
- device_serial = device_serial_and_port[0]
- if device_serial == device.serial:
- del device_map[device_serial_and_port]
- del host_map[host_port]
-
- # Kill the device forwarder.
- tool = base_tool.BaseTool()
- instance._KillDeviceLocked(device, tool)
-
- @staticmethod
- def DevicePortForHostPort(host_port):
- """Returns the device port that corresponds to a given host port."""
- with _FileLock(Forwarder._LOCK_PATH):
- serial_and_port = Forwarder._GetInstanceLocked(
- None)._host_to_device_port_map.get(host_port)
- return serial_and_port[1] if serial_and_port else None
-
- @staticmethod
- def RemoveHostLog():
- if os.path.exists(Forwarder._HOST_FORWARDER_LOG):
- os.unlink(Forwarder._HOST_FORWARDER_LOG)
-
- @staticmethod
- def GetHostLog():
- if not os.path.exists(Forwarder._HOST_FORWARDER_LOG):
- return ''
- with file(Forwarder._HOST_FORWARDER_LOG, 'r') as f:
- return f.read()
-
- @staticmethod
- def _GetInstanceLocked(tool):
- """Returns the singleton instance.
-
- Note that the global lock must be acquired before calling this method.
-
- Args:
- tool: Tool class to use to get wrapper, if necessary, for executing the
- forwarder (see valgrind_tools.py).
- """
- if not Forwarder._instance:
- Forwarder._instance = Forwarder(tool)
- return Forwarder._instance
-
- def __init__(self, tool):
- """Constructs a new instance of Forwarder.
-
- Note that Forwarder is a singleton therefore this constructor should be
- called only once.
-
- Args:
- tool: Tool class to use to get wrapper, if necessary, for executing the
- forwarder (see valgrind_tools.py).
- """
- assert not Forwarder._instance
- self._tool = tool
- self._initialized_devices = set()
- self._device_to_host_port_map = dict()
- self._host_to_device_port_map = dict()
- self._host_forwarder_path = devil_env.config.FetchPath('forwarder_host')
- assert os.path.exists(self._host_forwarder_path), 'Please build forwarder2'
- self._InitHostLocked()
-
- @staticmethod
- def _UnmapDevicePortLocked(device_port, device):
- """Internal method used by UnmapDevicePort().
-
- Note that the global lock must be acquired before calling this method.
- """
- instance = Forwarder._GetInstanceLocked(None)
- serial = str(device)
- serial_with_port = (serial, device_port)
- if not serial_with_port in instance._device_to_host_port_map:
- logger.error('Trying to unmap non-forwarded port %d', device_port)
- return
-
- host_port = instance._device_to_host_port_map[serial_with_port]
- del instance._device_to_host_port_map[serial_with_port]
- del instance._host_to_device_port_map[host_port]
-
- unmap_cmd = [
- instance._host_forwarder_path,
- '--adb=%s' % adb_wrapper.AdbWrapper.GetAdbPath(),
- '--serial-id=%s' % serial,
- '--unmap', str(device_port)
- ]
- try:
- (exit_code, output) = cmd_helper.GetCmdStatusAndOutputWithTimeout(
- unmap_cmd, Forwarder._TIMEOUT)
- except cmd_helper.TimeoutError as e:
- raise HostForwarderError(
- '`%s` timed out:\n%s' % (' '.join(unmap_cmd), e.output))
- if exit_code != 0:
- logger.error(
- '`%s` exited with %d:\n%s',
- ' '.join(unmap_cmd),
- exit_code,
- '\n'.join(output) if isinstance(output, list) else output)
-
- @staticmethod
- def _GetPidForLock():
- """Returns the PID used for host_forwarder initialization.
-
- The PID of the "sharder" is used to handle multiprocessing. The "sharder"
- is the initial process that forks that is the parent process.
- """
- return os.getpgrp()
-
- def _InitHostLocked(self):
- """Initializes the host forwarder daemon.
-
- Note that the global lock must be acquired before calling this method. This
- method kills any existing host_forwarder process that could be stale.
- """
- # See if the host_forwarder daemon was already initialized by a concurrent
- # process or thread (in case multi-process sharding is not used).
- pid_for_lock = Forwarder._GetPidForLock()
- fd = os.open(Forwarder._LOCK_PATH, os.O_RDWR | os.O_CREAT)
- with os.fdopen(fd, 'r+') as pid_file:
- pid_with_start_time = pid_file.readline()
- if pid_with_start_time:
- (pid, process_start_time) = pid_with_start_time.split(':')
- if pid == str(pid_for_lock):
- if process_start_time == str(_GetProcessStartTime(pid_for_lock)):
- return
- self._KillHostLocked()
- pid_file.seek(0)
- pid_file.write(
- '%s:%s' % (pid_for_lock, str(_GetProcessStartTime(pid_for_lock))))
- pid_file.truncate()
-
- def _InitDeviceLocked(self, device, tool):
- """Initializes the device_forwarder daemon for a specific device (once).
-
- Note that the global lock must be acquired before calling this method. This
- method kills any existing device_forwarder daemon on the device that could
- be stale, pushes the latest version of the daemon (to the device) and starts
- it.
-
- Args:
- device: A DeviceUtils instance.
- tool: Tool class to use to get wrapper, if necessary, for executing the
- forwarder (see valgrind_tools.py).
- """
- device_serial = str(device)
- if device_serial in self._initialized_devices:
- return
- try:
- self._KillDeviceLocked(device, tool)
- except device_errors.CommandFailedError:
- logger.warning('Failed to kill device forwarder. Rebooting.')
- device.Reboot()
- forwarder_device_path_on_host = devil_env.config.FetchPath(
- 'forwarder_device', device=device)
- forwarder_device_path_on_device = (
- Forwarder._DEVICE_FORWARDER_FOLDER
- if os.path.isdir(forwarder_device_path_on_host)
- else Forwarder._DEVICE_FORWARDER_PATH)
- device.PushChangedFiles([(
- forwarder_device_path_on_host,
- forwarder_device_path_on_device)])
-
- cmd = [Forwarder._DEVICE_FORWARDER_PATH]
- wrapper = tool.GetUtilWrapper()
- if wrapper:
- cmd.insert(0, wrapper)
- device.RunShellCommand(
- cmd, env={'LD_LIBRARY_PATH': Forwarder._DEVICE_FORWARDER_FOLDER},
- check_return=True)
- self._initialized_devices.add(device_serial)
-
- @staticmethod
- def KillHost():
- """Kills the forwarder process running on the host."""
- with _FileLock(Forwarder._LOCK_PATH):
- Forwarder._GetInstanceLocked(None)._KillHostLocked()
-
- def _KillHostLocked(self):
- """Kills the forwarder process running on the host.
-
- Note that the global lock must be acquired before calling this method.
- """
- logger.info('Killing host_forwarder.')
- try:
- kill_cmd = [self._host_forwarder_path, '--kill-server']
- (exit_code, _o) = cmd_helper.GetCmdStatusAndOutputWithTimeout(
- kill_cmd, Forwarder._TIMEOUT)
- if exit_code != 0:
- kill_cmd = ['pkill', '-9', 'host_forwarder']
- (exit_code, output) = cmd_helper.GetCmdStatusAndOutputWithTimeout(
- kill_cmd, Forwarder._TIMEOUT)
- if exit_code != 0:
- raise HostForwarderError(
- '%s exited with %d:\n%s' % (
- self._host_forwarder_path,
- exit_code,
- '\n'.join(output) if isinstance(output, list) else output))
- except cmd_helper.TimeoutError as e:
- raise HostForwarderError(
- '`%s` timed out:\n%s' % (' '.join(kill_cmd), e.output))
-
- @staticmethod
- def KillDevice(device, tool=None):
- """Kills the forwarder process running on the device.
-
- Args:
- device: Instance of DeviceUtils for talking to the device.
- tool: Wrapper tool (e.g. valgrind) that can be used to execute the device
- forwarder (see valgrind_tools.py).
- """
- with _FileLock(Forwarder._LOCK_PATH):
- Forwarder._GetInstanceLocked(None)._KillDeviceLocked(
- device, tool or base_tool.BaseTool())
-
- def _KillDeviceLocked(self, device, tool):
- """Kills the forwarder process running on the device.
-
- Note that the global lock must be acquired before calling this method.
-
- Args:
- device: Instance of DeviceUtils for talking to the device.
- tool: Wrapper tool (e.g. valgrind) that can be used to execute the device
- forwarder (see valgrind_tools.py).
- """
- logger.info('Killing device_forwarder.')
- self._initialized_devices.discard(device.serial)
- if not device.FileExists(Forwarder._DEVICE_FORWARDER_PATH):
- return
-
- cmd = [Forwarder._DEVICE_FORWARDER_PATH, '--kill-server']
- wrapper = tool.GetUtilWrapper()
- if wrapper:
- cmd.insert(0, wrapper)
- device.RunShellCommand(
- cmd, env={'LD_LIBRARY_PATH': Forwarder._DEVICE_FORWARDER_FOLDER},
- check_return=True)
diff --git a/third_party/catapult/devil/devil/android/install_commands.py b/third_party/catapult/devil/devil/android/install_commands.py
deleted file mode 100644
index c8da869602..0000000000
--- a/third_party/catapult/devil/devil/android/install_commands.py
+++ /dev/null
@@ -1,57 +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.
-
-import os
-import posixpath
-
-from devil import devil_env
-from devil.android import device_errors
-from devil.android.constants import file_system
-
-BIN_DIR = '%s/bin' % file_system.TEST_EXECUTABLE_DIR
-_FRAMEWORK_DIR = '%s/framework' % file_system.TEST_EXECUTABLE_DIR
-
-_COMMANDS = {
- 'unzip': 'org.chromium.android.commands.unzip.Unzip',
-}
-
-_SHELL_COMMAND_FORMAT = (
-"""#!/system/bin/sh
-base=%s
-export CLASSPATH=$base/framework/chromium_commands.jar
-exec app_process $base/bin %s $@
-""")
-
-
-def Installed(device):
- paths = [posixpath.join(BIN_DIR, c) for c in _COMMANDS]
- paths.append(posixpath.join(_FRAMEWORK_DIR, 'chromium_commands.jar'))
- return device.PathExists(paths)
-
-
-def InstallCommands(device):
- if device.IsUserBuild():
- raise device_errors.CommandFailedError(
- 'chromium_commands currently requires a userdebug build.',
- device_serial=device.adb.GetDeviceSerial())
-
- chromium_commands_jar_path = devil_env.config.FetchPath('chromium_commands')
- if not os.path.exists(chromium_commands_jar_path):
- raise device_errors.CommandFailedError(
- '%s not found. Please build chromium_commands.'
- % chromium_commands_jar_path)
-
- device.RunShellCommand(
- ['mkdir', '-p', BIN_DIR, _FRAMEWORK_DIR], check_return=True)
- for command, main_class in _COMMANDS.iteritems():
- shell_command = _SHELL_COMMAND_FORMAT % (
- file_system.TEST_EXECUTABLE_DIR, main_class)
- shell_file = '%s/%s' % (BIN_DIR, command)
- device.WriteFile(shell_file, shell_command)
- device.RunShellCommand(
- ['chmod', '755', shell_file], check_return=True)
-
- device.adb.Push(
- chromium_commands_jar_path,
- '%s/chromium_commands.jar' % _FRAMEWORK_DIR)
diff --git a/third_party/catapult/devil/devil/android/logcat_monitor.py b/third_party/catapult/devil/devil/android/logcat_monitor.py
deleted file mode 100644
index 0aece87dee..0000000000
--- a/third_party/catapult/devil/devil/android/logcat_monitor.py
+++ /dev/null
@@ -1,255 +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.
-
-# pylint: disable=unused-argument
-
-import errno
-import logging
-import os
-import re
-import shutil
-import tempfile
-import threading
-import time
-
-from devil.android import decorators
-from devil.android import device_errors
-from devil.android.sdk import adb_wrapper
-from devil.utils import reraiser_thread
-
-logger = logging.getLogger(__name__)
-
-
-class LogcatMonitor(object):
-
- _RECORD_ITER_TIMEOUT = 2.0
- _RECORD_THREAD_JOIN_WAIT = 5.0
- _WAIT_TIME = 0.2
- _THREADTIME_RE_FORMAT = (
- r'(?P<date>\S*) +(?P<time>\S*) +(?P<proc_id>%s) +(?P<thread_id>%s) +'
- r'(?P<log_level>%s) +(?P<component>%s) *: +(?P<message>%s)$')
-
- def __init__(self, adb, clear=True, filter_specs=None, output_file=None):
- """Create a LogcatMonitor instance.
-
- Args:
- adb: An instance of adb_wrapper.AdbWrapper.
- clear: If True, clear the logcat when monitoring starts.
- filter_specs: An optional list of '<tag>[:priority]' strings.
- output_file: File path to save recorded logcat.
- """
- if isinstance(adb, adb_wrapper.AdbWrapper):
- self._adb = adb
- else:
- raise ValueError('Unsupported type passed for argument "device"')
- self._clear = clear
- self._filter_specs = filter_specs
- self._output_file = output_file
- self._record_file = None
- self._record_file_lock = threading.Lock()
- self._record_thread = None
- self._stop_recording_event = threading.Event()
-
- @property
- def output_file(self):
- return self._output_file
-
- @decorators.WithTimeoutAndRetriesDefaults(10, 0)
- def WaitFor(self, success_regex, failure_regex=None, timeout=None,
- retries=None):
- """Wait for a matching logcat line or until a timeout occurs.
-
- This will attempt to match lines in the logcat against both |success_regex|
- and |failure_regex| (if provided). Note that this calls re.search on each
- logcat line, not re.match, so the provided regular expressions don't have
- to match an entire line.
-
- Args:
- success_regex: The regular expression to search for.
- failure_regex: An optional regular expression that, if hit, causes this
- to stop looking for a match. Can be None.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- A match object if |success_regex| matches a part of a logcat line, or
- None if |failure_regex| matches a part of a logcat line.
- Raises:
- CommandFailedError on logcat failure (NOT on a |failure_regex| match).
- CommandTimeoutError if no logcat line matching either |success_regex| or
- |failure_regex| is found in |timeout| seconds.
- DeviceUnreachableError if the device becomes unreachable.
- LogcatMonitorCommandError when calling |WaitFor| while not recording
- logcat.
- """
- if self._record_thread is None:
- raise LogcatMonitorCommandError(
- 'Must be recording logcat when calling |WaitFor|',
- device_serial=str(self._adb))
- if isinstance(success_regex, basestring):
- success_regex = re.compile(success_regex)
- if isinstance(failure_regex, basestring):
- failure_regex = re.compile(failure_regex)
-
- logger.debug('Waiting %d seconds for "%s"', timeout, success_regex.pattern)
-
- # NOTE This will continue looping until:
- # - success_regex matches a line, in which case the match object is
- # returned.
- # - failure_regex matches a line, in which case None is returned
- # - the timeout is hit, in which case a CommandTimeoutError is raised.
- with open(self._record_file.name, 'r') as f:
- while True:
- line = f.readline()
- if line:
- m = success_regex.search(line)
- if m:
- return m
- if failure_regex and failure_regex.search(line):
- return None
- else:
- time.sleep(self._WAIT_TIME)
-
- def FindAll(self, message_regex, proc_id=None, thread_id=None, log_level=None,
- component=None):
- """Finds all lines in the logcat that match the provided constraints.
-
- Args:
- message_regex: The regular expression that the <message> section must
- match.
- proc_id: The process ID to match. If None, matches any process ID.
- thread_id: The thread ID to match. If None, matches any thread ID.
- log_level: The log level to match. If None, matches any log level.
- component: The component to match. If None, matches any component.
-
- Raises:
- LogcatMonitorCommandError when calling |FindAll| before recording logcat.
-
- Yields:
- A match object for each matching line in the logcat. The match object
- will always contain, in addition to groups defined in |message_regex|,
- the following named groups: 'date', 'time', 'proc_id', 'thread_id',
- 'log_level', 'component', and 'message'.
- """
- if self._record_file is None:
- raise LogcatMonitorCommandError(
- 'Must have recorded or be recording a logcat to call |FindAll|',
- device_serial=str(self._adb))
- if proc_id is None:
- proc_id = r'\d+'
- if thread_id is None:
- thread_id = r'\d+'
- if log_level is None:
- log_level = r'[VDIWEF]'
- if component is None:
- component = r'[^\s:]+'
- # pylint: disable=protected-access
- threadtime_re = re.compile(
- type(self)._THREADTIME_RE_FORMAT % (
- proc_id, thread_id, log_level, component, message_regex))
-
- with open(self._record_file.name, 'r') as f:
- for line in f:
- m = re.match(threadtime_re, line)
- if m:
- yield m
-
- def _StartRecording(self):
- """Starts recording logcat to file.
-
- Function spawns a thread that records logcat to file and will not die
- until |StopRecording| is called.
- """
- def record_to_file():
- # Write the log with line buffering so the consumer sees each individual
- # line.
- for data in self._adb.Logcat(filter_specs=self._filter_specs,
- logcat_format='threadtime',
- iter_timeout=self._RECORD_ITER_TIMEOUT):
- if self._stop_recording_event.isSet():
- return
-
- if data is None:
- # Logcat can yield None if the iter_timeout is hit.
- continue
-
- with self._record_file_lock:
- if self._record_file and not self._record_file.closed:
- self._record_file.write(data + '\n')
-
- self._stop_recording_event.clear()
- if not self._record_thread:
- self._record_thread = reraiser_thread.ReraiserThread(record_to_file)
- self._record_thread.start()
-
- def _StopRecording(self):
- """Finish recording logcat."""
- if self._record_thread:
- self._stop_recording_event.set()
- self._record_thread.join(timeout=self._RECORD_THREAD_JOIN_WAIT)
- self._record_thread.ReraiseIfException()
- self._record_thread = None
-
- def Start(self):
- """Starts the logcat monitor.
-
- Clears the logcat if |clear| was set in |__init__|.
- """
- if self._clear:
- self._adb.Logcat(clear=True)
- if not self._record_file:
- self._record_file = tempfile.NamedTemporaryFile(mode='a', bufsize=1)
- self._StartRecording()
-
- def Stop(self):
- """Stops the logcat monitor.
-
- Stops recording the logcat. Copies currently recorded logcat to
- |self._output_file|.
- """
- self._StopRecording()
- with self._record_file_lock:
- if self._record_file and self._output_file:
- try:
- os.makedirs(os.path.dirname(self._output_file))
- except OSError as e:
- if e.errno != errno.EEXIST:
- raise
- shutil.copy(self._record_file.name, self._output_file)
-
- def Close(self):
- """Closes logcat recording file.
-
- Should be called when finished using the logcat monitor.
- """
- with self._record_file_lock:
- if self._record_file:
- self._record_file.close()
- self._record_file = None
-
- def __enter__(self):
- """Starts the logcat monitor."""
- self.Start()
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- """Stops the logcat monitor."""
- self.Stop()
-
- def __del__(self):
- """Closes logcat recording file in case |Close| was never called."""
- with self._record_file_lock:
- if self._record_file:
- logger.warning(
- 'Need to call |Close| on the logcat monitor when done!')
- self._record_file.close()
-
- @property
- def adb(self):
- return self._adb
-
-
-class LogcatMonitorCommandError(device_errors.CommandFailedError):
- """Exception for errors with logcat monitor commands."""
- pass
diff --git a/third_party/catapult/devil/devil/android/logcat_monitor_test.py b/third_party/catapult/devil/devil/android/logcat_monitor_test.py
deleted file mode 100755
index 8fb4d74bbc..0000000000
--- a/third_party/catapult/devil/devil/android/logcat_monitor_test.py
+++ /dev/null
@@ -1,230 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# pylint: disable=protected-access
-
-import itertools
-import threading
-import unittest
-
-from devil import devil_env
-from devil.android import logcat_monitor
-from devil.android.sdk import adb_wrapper
-
-with devil_env.SysPath(devil_env.PYMOCK_PATH):
- import mock # pylint: disable=import-error
-
-
-def _CreateTestLog(raw_logcat=None):
- test_adb = adb_wrapper.AdbWrapper('0123456789abcdef')
- test_adb.Logcat = mock.Mock(return_value=(l for l in raw_logcat))
- test_log = logcat_monitor.LogcatMonitor(test_adb, clear=False)
- return test_log
-
-
-class LogcatMonitorTest(unittest.TestCase):
-
- _TEST_THREADTIME_LOGCAT_DATA = [
- '01-01 01:02:03.456 7890 0987 V LogcatMonitorTest: '
- 'verbose logcat monitor test message 1',
- '01-01 01:02:03.457 8901 1098 D LogcatMonitorTest: '
- 'debug logcat monitor test message 2',
- '01-01 01:02:03.458 9012 2109 I LogcatMonitorTest: '
- 'info logcat monitor test message 3',
- '01-01 01:02:03.459 0123 3210 W LogcatMonitorTest: '
- 'warning logcat monitor test message 4',
- '01-01 01:02:03.460 1234 4321 E LogcatMonitorTest: '
- 'error logcat monitor test message 5',
- '01-01 01:02:03.461 2345 5432 F LogcatMonitorTest: '
- 'fatal logcat monitor test message 6',
- '01-01 01:02:03.462 3456 6543 D LogcatMonitorTest: '
- 'last line'
- ]
-
- def assertIterEqual(self, expected_iter, actual_iter):
- for expected, actual in itertools.izip_longest(expected_iter, actual_iter):
- self.assertIsNotNone(
- expected,
- msg='actual has unexpected elements starting with %s' % str(actual))
- self.assertIsNotNone(
- actual,
- msg='actual is missing elements starting with %s' % str(expected))
- self.assertEqual(actual.group('proc_id'), expected[0])
- self.assertEqual(actual.group('thread_id'), expected[1])
- self.assertEqual(actual.group('log_level'), expected[2])
- self.assertEqual(actual.group('component'), expected[3])
- self.assertEqual(actual.group('message'), expected[4])
-
- with self.assertRaises(StopIteration):
- next(actual_iter)
- with self.assertRaises(StopIteration):
- next(expected_iter)
-
- @mock.patch('time.sleep', mock.Mock())
- def testWaitFor_success(self):
- test_log = _CreateTestLog(
- raw_logcat=type(self)._TEST_THREADTIME_LOGCAT_DATA)
- test_log.Start()
- actual_match = test_log.WaitFor(r'.*(fatal|error) logcat monitor.*', None)
- self.assertTrue(actual_match)
- self.assertEqual(
- '01-01 01:02:03.460 1234 4321 E LogcatMonitorTest: '
- 'error logcat monitor test message 5',
- actual_match.group(0))
- self.assertEqual('error', actual_match.group(1))
- test_log.Stop()
- test_log.Close()
-
- @mock.patch('time.sleep', mock.Mock())
- def testWaitFor_failure(self):
- test_log = _CreateTestLog(
- raw_logcat=type(self)._TEST_THREADTIME_LOGCAT_DATA)
- test_log.Start()
- actual_match = test_log.WaitFor(
- r'.*My Success Regex.*', r'.*(fatal|error) logcat monitor.*')
- self.assertIsNone(actual_match)
- test_log.Stop()
- test_log.Close()
-
- @mock.patch('time.sleep', mock.Mock())
- def testWaitFor_buffering(self):
- # Simulate an adb log stream which does not complete until the test tells it
- # to. This checks that the log matcher can receive individual lines from the
- # log reader thread even if adb is not producing enough output to fill an
- # entire file io buffer.
- finished_lock = threading.Lock()
- finished_lock.acquire()
-
- def LogGenerator():
- for line in type(self)._TEST_THREADTIME_LOGCAT_DATA:
- yield line
- finished_lock.acquire()
-
- test_adb = adb_wrapper.AdbWrapper('0123456789abcdef')
- test_adb.Logcat = mock.Mock(return_value=LogGenerator())
- test_log = logcat_monitor.LogcatMonitor(test_adb, clear=False)
- test_log.Start()
-
- actual_match = test_log.WaitFor(r'.*last line.*', None)
- finished_lock.release()
- self.assertTrue(actual_match)
- test_log.Stop()
- test_log.Close()
-
- @mock.patch('time.sleep', mock.Mock())
- def testFindAll_defaults(self):
- test_log = _CreateTestLog(
- raw_logcat=type(self)._TEST_THREADTIME_LOGCAT_DATA)
- test_log.Start()
- test_log.WaitFor(r'.*last line.*', None)
- test_log.Stop()
- expected_results = [
- ('7890', '0987', 'V', 'LogcatMonitorTest',
- 'verbose logcat monitor test message 1'),
- ('8901', '1098', 'D', 'LogcatMonitorTest',
- 'debug logcat monitor test message 2'),
- ('9012', '2109', 'I', 'LogcatMonitorTest',
- 'info logcat monitor test message 3'),
- ('0123', '3210', 'W', 'LogcatMonitorTest',
- 'warning logcat monitor test message 4'),
- ('1234', '4321', 'E', 'LogcatMonitorTest',
- 'error logcat monitor test message 5'),
- ('2345', '5432', 'F', 'LogcatMonitorTest',
- 'fatal logcat monitor test message 6')]
- actual_results = test_log.FindAll(r'\S* logcat monitor test message \d')
- self.assertIterEqual(iter(expected_results), actual_results)
- test_log.Close()
-
- @mock.patch('time.sleep', mock.Mock())
- def testFindAll_defaults_miss(self):
- test_log = _CreateTestLog(
- raw_logcat=type(self)._TEST_THREADTIME_LOGCAT_DATA)
- test_log.Start()
- test_log.WaitFor(r'.*last line.*', None)
- test_log.Stop()
- expected_results = []
- actual_results = test_log.FindAll(r'\S* nothing should match this \d')
- self.assertIterEqual(iter(expected_results), actual_results)
- test_log.Close()
-
- @mock.patch('time.sleep', mock.Mock())
- def testFindAll_filterProcId(self):
- test_log = _CreateTestLog(
- raw_logcat=type(self)._TEST_THREADTIME_LOGCAT_DATA)
- test_log.Start()
- test_log.WaitFor(r'.*last line.*', None)
- test_log.Stop()
- actual_results = test_log.FindAll(
- r'\S* logcat monitor test message \d', proc_id=1234)
- expected_results = [
- ('1234', '4321', 'E', 'LogcatMonitorTest',
- 'error logcat monitor test message 5')]
- self.assertIterEqual(iter(expected_results), actual_results)
- test_log.Close()
-
- @mock.patch('time.sleep', mock.Mock())
- def testFindAll_filterThreadId(self):
- test_log = _CreateTestLog(
- raw_logcat=type(self)._TEST_THREADTIME_LOGCAT_DATA)
- test_log.Start()
- test_log.WaitFor(r'.*last line.*', None)
- test_log.Stop()
- actual_results = test_log.FindAll(
- r'\S* logcat monitor test message \d', thread_id=2109)
- expected_results = [
- ('9012', '2109', 'I', 'LogcatMonitorTest',
- 'info logcat monitor test message 3')]
- self.assertIterEqual(iter(expected_results), actual_results)
- test_log.Close()
-
- @mock.patch('time.sleep', mock.Mock())
- def testFindAll_filterLogLevel(self):
- test_log = _CreateTestLog(
- raw_logcat=type(self)._TEST_THREADTIME_LOGCAT_DATA)
- test_log.Start()
- test_log.WaitFor(r'.*last line.*', None)
- test_log.Stop()
- actual_results = test_log.FindAll(
- r'\S* logcat monitor test message \d', log_level=r'[DW]')
- expected_results = [
- ('8901', '1098', 'D', 'LogcatMonitorTest',
- 'debug logcat monitor test message 2'),
- ('0123', '3210', 'W', 'LogcatMonitorTest',
- 'warning logcat monitor test message 4')
- ]
- self.assertIterEqual(iter(expected_results), actual_results)
- test_log.Close()
-
- @mock.patch('time.sleep', mock.Mock())
- def testFindAll_filterComponent(self):
- test_log = _CreateTestLog(
- raw_logcat=type(self)._TEST_THREADTIME_LOGCAT_DATA)
- test_log.Start()
- test_log.WaitFor(r'.*last line.*', None)
- test_log.Stop()
- actual_results = test_log.FindAll(r'.*', component='LogcatMonitorTest')
- expected_results = [
- ('7890', '0987', 'V', 'LogcatMonitorTest',
- 'verbose logcat monitor test message 1'),
- ('8901', '1098', 'D', 'LogcatMonitorTest',
- 'debug logcat monitor test message 2'),
- ('9012', '2109', 'I', 'LogcatMonitorTest',
- 'info logcat monitor test message 3'),
- ('0123', '3210', 'W', 'LogcatMonitorTest',
- 'warning logcat monitor test message 4'),
- ('1234', '4321', 'E', 'LogcatMonitorTest',
- 'error logcat monitor test message 5'),
- ('2345', '5432', 'F', 'LogcatMonitorTest',
- 'fatal logcat monitor test message 6'),
- ('3456', '6543', 'D', 'LogcatMonitorTest',
- 'last line')
- ]
- self.assertIterEqual(iter(expected_results), actual_results)
- test_log.Close()
-
-
-if __name__ == '__main__':
- unittest.main(verbosity=2)
-
diff --git a/third_party/catapult/devil/devil/android/md5sum.py b/third_party/catapult/devil/devil/android/md5sum.py
deleted file mode 100644
index 6dece9e8d7..0000000000
--- a/third_party/catapult/devil/devil/android/md5sum.py
+++ /dev/null
@@ -1,120 +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.
-
-import os
-import posixpath
-import re
-
-from devil import devil_env
-from devil.android import device_errors
-from devil.utils import cmd_helper
-
-MD5SUM_DEVICE_LIB_PATH = '/data/local/tmp/md5sum'
-MD5SUM_DEVICE_BIN_PATH = MD5SUM_DEVICE_LIB_PATH + '/md5sum_bin'
-
-_STARTS_WITH_CHECKSUM_RE = re.compile(r'^\s*[0-9a-fA-F]{32}\s+')
-
-
-def CalculateHostMd5Sums(paths):
- """Calculates the MD5 sum value for all items in |paths|.
-
- Directories are traversed recursively and the MD5 sum of each file found is
- reported in the result.
-
- Args:
- paths: A list of host paths to md5sum.
- Returns:
- A dict mapping file paths to their respective md5sum checksums.
- """
- if isinstance(paths, basestring):
- paths = [paths]
-
- md5sum_bin_host_path = devil_env.config.FetchPath('md5sum_host')
- if not os.path.exists(md5sum_bin_host_path):
- raise IOError('File not built: %s' % md5sum_bin_host_path)
- out = cmd_helper.GetCmdOutput(
- [md5sum_bin_host_path] + [os.path.realpath(p) for p in paths])
-
- return _ParseMd5SumOutput(out.splitlines())
-
-
-def CalculateDeviceMd5Sums(paths, device):
- """Calculates the MD5 sum value for all items in |paths|.
-
- Directories are traversed recursively and the MD5 sum of each file found is
- reported in the result.
-
- Args:
- paths: A list of device paths to md5sum.
- Returns:
- A dict mapping file paths to their respective md5sum checksums.
- """
- if not paths:
- return {}
-
- if isinstance(paths, basestring):
- paths = [paths]
- # Allow generators
- paths = list(paths)
-
- md5sum_dist_path = devil_env.config.FetchPath('md5sum_device', device=device)
-
- if os.path.isdir(md5sum_dist_path):
- md5sum_dist_bin_path = os.path.join(md5sum_dist_path, 'md5sum_bin')
- else:
- md5sum_dist_bin_path = md5sum_dist_path
-
- if not os.path.exists(md5sum_dist_path):
- raise IOError('File not built: %s' % md5sum_dist_path)
- md5sum_file_size = os.path.getsize(md5sum_dist_bin_path)
-
- # For better performance, make the script as small as possible to try and
- # avoid needing to write to an intermediary file (which RunShellCommand will
- # do if necessary).
- md5sum_script = 'a=%s;' % MD5SUM_DEVICE_BIN_PATH
- # Check if the binary is missing or has changed (using its file size as an
- # indicator), and trigger a (re-)push via the exit code.
- md5sum_script += '! [[ $(ls -l $a) = *%d* ]]&&exit 2;' % md5sum_file_size
- # Make sure it can find libbase.so
- md5sum_script += 'export LD_LIBRARY_PATH=%s;' % MD5SUM_DEVICE_LIB_PATH
- if len(paths) > 1:
- prefix = posixpath.commonprefix(paths)
- if len(prefix) > 4:
- md5sum_script += 'p="%s";' % prefix
- paths = ['$p"%s"' % p[len(prefix):] for p in paths]
-
- md5sum_script += ';'.join('$a %s' % p for p in paths)
- # Don't fail the script if the last md5sum fails (due to file not found)
- # Note: ":" is equivalent to "true".
- md5sum_script += ';:'
- try:
- out = device.RunShellCommand(md5sum_script, shell=True, check_return=True)
- except device_errors.AdbShellCommandFailedError as e:
- # Push the binary only if it is found to not exist
- # (faster than checking up-front).
- if e.status == 2:
- # If files were previously pushed as root (adbd running as root), trying
- # to re-push as non-root causes the push command to report success, but
- # actually fail. So, wipe the directory first.
- device.RunShellCommand(['rm', '-rf', MD5SUM_DEVICE_LIB_PATH],
- as_root=True, check_return=True)
- if os.path.isdir(md5sum_dist_path):
- device.adb.Push(md5sum_dist_path, MD5SUM_DEVICE_LIB_PATH)
- else:
- mkdir_cmd = 'a=%s;[[ -e $a ]] || mkdir $a' % MD5SUM_DEVICE_LIB_PATH
- device.RunShellCommand(mkdir_cmd, shell=True, check_return=True)
- device.adb.Push(md5sum_dist_bin_path, MD5SUM_DEVICE_BIN_PATH)
-
- out = device.RunShellCommand(md5sum_script, shell=True, check_return=True)
- else:
- raise
-
- return _ParseMd5SumOutput(out)
-
-
-def _ParseMd5SumOutput(out):
- hash_and_path = (l.split(None, 1) for l in out
- if l and _STARTS_WITH_CHECKSUM_RE.match(l))
- return dict((p, h) for h, p in hash_and_path)
-
diff --git a/third_party/catapult/devil/devil/android/md5sum_test.py b/third_party/catapult/devil/devil/android/md5sum_test.py
deleted file mode 100755
index c9b4954540..0000000000
--- a/third_party/catapult/devil/devil/android/md5sum_test.py
+++ /dev/null
@@ -1,237 +0,0 @@
-#!/usr/bin/env python
-# 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 os
-import unittest
-
-from devil import devil_env
-from devil.android import device_errors
-from devil.android import md5sum
-
-with devil_env.SysPath(devil_env.PYMOCK_PATH):
- import mock # pylint: disable=import-error
-
-TEST_OUT_DIR = os.path.join('test', 'out', 'directory')
-HOST_MD5_EXECUTABLE = os.path.join(TEST_OUT_DIR, 'md5sum_bin_host')
-MD5_DIST = os.path.join(TEST_OUT_DIR, 'md5sum_dist')
-
-
-class Md5SumTest(unittest.TestCase):
-
- def setUp(self):
- mocked_attrs = {
- 'md5sum_host': HOST_MD5_EXECUTABLE,
- 'md5sum_device': MD5_DIST,
- }
- self._patchers = [
- mock.patch('devil.devil_env._Environment.FetchPath',
- mock.Mock(side_effect=lambda a, device=None: mocked_attrs[a])),
- mock.patch('os.path.exists',
- new=mock.Mock(return_value=True)),
- ]
- for p in self._patchers:
- p.start()
-
- def tearDown(self):
- for p in self._patchers:
- p.stop()
-
- def testCalculateHostMd5Sums_singlePath(self):
- test_path = '/test/host/file.dat'
- mock_get_cmd_output = mock.Mock(
- return_value='0123456789abcdeffedcba9876543210 /test/host/file.dat')
- with mock.patch('devil.utils.cmd_helper.GetCmdOutput',
- new=mock_get_cmd_output):
- out = md5sum.CalculateHostMd5Sums(test_path)
- self.assertEquals(1, len(out))
- self.assertTrue('/test/host/file.dat' in out)
- self.assertEquals('0123456789abcdeffedcba9876543210',
- out['/test/host/file.dat'])
- mock_get_cmd_output.assert_called_once_with(
- [HOST_MD5_EXECUTABLE, '/test/host/file.dat'])
-
- def testCalculateHostMd5Sums_list(self):
- test_paths = ['/test/host/file0.dat', '/test/host/file1.dat']
- mock_get_cmd_output = mock.Mock(
- return_value='0123456789abcdeffedcba9876543210 /test/host/file0.dat\n'
- '123456789abcdef00fedcba987654321 /test/host/file1.dat\n')
- with mock.patch('devil.utils.cmd_helper.GetCmdOutput',
- new=mock_get_cmd_output):
- out = md5sum.CalculateHostMd5Sums(test_paths)
- self.assertEquals(2, len(out))
- self.assertTrue('/test/host/file0.dat' in out)
- self.assertEquals('0123456789abcdeffedcba9876543210',
- out['/test/host/file0.dat'])
- self.assertTrue('/test/host/file1.dat' in out)
- self.assertEquals('123456789abcdef00fedcba987654321',
- out['/test/host/file1.dat'])
- mock_get_cmd_output.assert_called_once_with(
- [HOST_MD5_EXECUTABLE, '/test/host/file0.dat',
- '/test/host/file1.dat'])
-
- def testCalculateHostMd5Sums_generator(self):
- test_paths = ('/test/host/' + p for p in ['file0.dat', 'file1.dat'])
- mock_get_cmd_output = mock.Mock(
- return_value='0123456789abcdeffedcba9876543210 /test/host/file0.dat\n'
- '123456789abcdef00fedcba987654321 /test/host/file1.dat\n')
- with mock.patch('devil.utils.cmd_helper.GetCmdOutput',
- new=mock_get_cmd_output):
- out = md5sum.CalculateHostMd5Sums(test_paths)
- self.assertEquals(2, len(out))
- self.assertTrue('/test/host/file0.dat' in out)
- self.assertEquals('0123456789abcdeffedcba9876543210',
- out['/test/host/file0.dat'])
- self.assertTrue('/test/host/file1.dat' in out)
- self.assertEquals('123456789abcdef00fedcba987654321',
- out['/test/host/file1.dat'])
- mock_get_cmd_output.assert_called_once_with(
- [HOST_MD5_EXECUTABLE, '/test/host/file0.dat', '/test/host/file1.dat'])
-
- def testCalculateDeviceMd5Sums_noPaths(self):
- device = mock.NonCallableMock()
- device.RunShellCommand = mock.Mock(side_effect=Exception())
-
- out = md5sum.CalculateDeviceMd5Sums([], device)
- self.assertEquals(0, len(out))
-
- def testCalculateDeviceMd5Sums_singlePath(self):
- test_path = '/storage/emulated/legacy/test/file.dat'
-
- device = mock.NonCallableMock()
- device_md5sum_output = [
- '0123456789abcdeffedcba9876543210 '
- '/storage/emulated/legacy/test/file.dat',
- ]
- device.RunShellCommand = mock.Mock(return_value=device_md5sum_output)
-
- with mock.patch('os.path.getsize', return_value=1337):
- out = md5sum.CalculateDeviceMd5Sums(test_path, device)
- self.assertEquals(1, len(out))
- self.assertTrue('/storage/emulated/legacy/test/file.dat' in out)
- self.assertEquals('0123456789abcdeffedcba9876543210',
- out['/storage/emulated/legacy/test/file.dat'])
- self.assertEquals(1, len(device.RunShellCommand.call_args_list))
-
- def testCalculateDeviceMd5Sums_list(self):
- test_path = ['/storage/emulated/legacy/test/file0.dat',
- '/storage/emulated/legacy/test/file1.dat']
- device = mock.NonCallableMock()
- device_md5sum_output = [
- '0123456789abcdeffedcba9876543210 '
- '/storage/emulated/legacy/test/file0.dat',
- '123456789abcdef00fedcba987654321 '
- '/storage/emulated/legacy/test/file1.dat',
- ]
- device.RunShellCommand = mock.Mock(return_value=device_md5sum_output)
-
- with mock.patch('os.path.getsize', return_value=1337):
- out = md5sum.CalculateDeviceMd5Sums(test_path, device)
- self.assertEquals(2, len(out))
- self.assertTrue('/storage/emulated/legacy/test/file0.dat' in out)
- self.assertEquals('0123456789abcdeffedcba9876543210',
- out['/storage/emulated/legacy/test/file0.dat'])
- self.assertTrue('/storage/emulated/legacy/test/file1.dat' in out)
- self.assertEquals('123456789abcdef00fedcba987654321',
- out['/storage/emulated/legacy/test/file1.dat'])
- self.assertEquals(1, len(device.RunShellCommand.call_args_list))
-
- def testCalculateDeviceMd5Sums_generator(self):
- test_path = ('/storage/emulated/legacy/test/file%d.dat' % n
- for n in xrange(0, 2))
-
- device = mock.NonCallableMock()
- device_md5sum_output = [
- '0123456789abcdeffedcba9876543210 '
- '/storage/emulated/legacy/test/file0.dat',
- '123456789abcdef00fedcba987654321 '
- '/storage/emulated/legacy/test/file1.dat',
- ]
- device.RunShellCommand = mock.Mock(return_value=device_md5sum_output)
-
- with mock.patch('os.path.getsize', return_value=1337):
- out = md5sum.CalculateDeviceMd5Sums(test_path, device)
- self.assertEquals(2, len(out))
- self.assertTrue('/storage/emulated/legacy/test/file0.dat' in out)
- self.assertEquals('0123456789abcdeffedcba9876543210',
- out['/storage/emulated/legacy/test/file0.dat'])
- self.assertTrue('/storage/emulated/legacy/test/file1.dat' in out)
- self.assertEquals('123456789abcdef00fedcba987654321',
- out['/storage/emulated/legacy/test/file1.dat'])
- self.assertEquals(1, len(device.RunShellCommand.call_args_list))
-
- def testCalculateDeviceMd5Sums_singlePath_linkerWarning(self):
- # See crbug/479966
- test_path = '/storage/emulated/legacy/test/file.dat'
-
- device = mock.NonCallableMock()
- device_md5sum_output = [
- 'WARNING: linker: /data/local/tmp/md5sum/md5sum_bin: '
- 'unused DT entry: type 0x1d arg 0x15db',
- 'THIS_IS_NOT_A_VALID_CHECKSUM_ZZZ some random text',
- '0123456789abcdeffedcba9876543210 '
- '/storage/emulated/legacy/test/file.dat',
- ]
- device.RunShellCommand = mock.Mock(return_value=device_md5sum_output)
-
- with mock.patch('os.path.getsize', return_value=1337):
- out = md5sum.CalculateDeviceMd5Sums(test_path, device)
- self.assertEquals(1, len(out))
- self.assertTrue('/storage/emulated/legacy/test/file.dat' in out)
- self.assertEquals('0123456789abcdeffedcba9876543210',
- out['/storage/emulated/legacy/test/file.dat'])
- self.assertEquals(1, len(device.RunShellCommand.call_args_list))
-
- def testCalculateDeviceMd5Sums_list_fileMissing(self):
- test_path = ['/storage/emulated/legacy/test/file0.dat',
- '/storage/emulated/legacy/test/file1.dat']
- device = mock.NonCallableMock()
- device_md5sum_output = [
- '0123456789abcdeffedcba9876543210 '
- '/storage/emulated/legacy/test/file0.dat',
- '[0819/203513:ERROR:md5sum.cc(25)] Could not open file asdf',
- '',
- ]
- device.RunShellCommand = mock.Mock(return_value=device_md5sum_output)
-
- with mock.patch('os.path.getsize', return_value=1337):
- out = md5sum.CalculateDeviceMd5Sums(test_path, device)
- self.assertEquals(1, len(out))
- self.assertTrue('/storage/emulated/legacy/test/file0.dat' in out)
- self.assertEquals('0123456789abcdeffedcba9876543210',
- out['/storage/emulated/legacy/test/file0.dat'])
- self.assertEquals(1, len(device.RunShellCommand.call_args_list))
-
- def testCalculateDeviceMd5Sums_requiresBinary(self):
- test_path = '/storage/emulated/legacy/test/file.dat'
-
- device = mock.NonCallableMock()
- device.adb = mock.NonCallableMock()
- device.adb.Push = mock.Mock()
- device_md5sum_output = [
- 'WARNING: linker: /data/local/tmp/md5sum/md5sum_bin: '
- 'unused DT entry: type 0x1d arg 0x15db',
- 'THIS_IS_NOT_A_VALID_CHECKSUM_ZZZ some random text',
- '0123456789abcdeffedcba9876543210 '
- '/storage/emulated/legacy/test/file.dat',
- ]
- error = device_errors.AdbShellCommandFailedError('cmd', 'out', 2)
- device.RunShellCommand = mock.Mock(
- side_effect=(error, '', device_md5sum_output))
-
- with mock.patch('os.path.isdir', return_value=True), (
- mock.patch('os.path.getsize', return_value=1337)):
- out = md5sum.CalculateDeviceMd5Sums(test_path, device)
- self.assertEquals(1, len(out))
- self.assertTrue('/storage/emulated/legacy/test/file.dat' in out)
- self.assertEquals('0123456789abcdeffedcba9876543210',
- out['/storage/emulated/legacy/test/file.dat'])
- self.assertEquals(3, len(device.RunShellCommand.call_args_list))
- device.adb.Push.assert_called_once_with(
- 'test/out/directory/md5sum_dist', '/data/local/tmp/md5sum')
-
-
-if __name__ == '__main__':
- unittest.main(verbosity=2)
-
diff --git a/third_party/catapult/devil/devil/android/perf/__init__.py b/third_party/catapult/devil/devil/android/perf/__init__.py
deleted file mode 100644
index 50b23dff63..0000000000
--- a/third_party/catapult/devil/devil/android/perf/__init__.py
+++ /dev/null
@@ -1,3 +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.
diff --git a/third_party/catapult/devil/devil/android/perf/cache_control.py b/third_party/catapult/devil/devil/android/perf/cache_control.py
deleted file mode 100644
index 27782b50b2..0000000000
--- a/third_party/catapult/devil/devil/android/perf/cache_control.py
+++ /dev/null
@@ -1,15 +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.
-
-
-class CacheControl(object):
- _DROP_CACHES = '/proc/sys/vm/drop_caches'
-
- def __init__(self, device):
- self._device = device
-
- def DropRamCaches(self):
- """Drops the filesystem ram caches for performance testing."""
- self._device.RunShellCommand(['sync'], check_return=True, as_root=True)
- self._device.WriteFile(CacheControl._DROP_CACHES, '3', as_root=True)
diff --git a/third_party/catapult/devil/devil/android/perf/perf_control.py b/third_party/catapult/devil/devil/android/perf/perf_control.py
deleted file mode 100644
index 06a5db61e5..0000000000
--- a/third_party/catapult/devil/devil/android/perf/perf_control.py
+++ /dev/null
@@ -1,210 +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.
-
-import atexit
-import logging
-import re
-
-from devil.android import device_errors
-
-logger = logging.getLogger(__name__)
-
-
-class PerfControl(object):
- """Provides methods for setting the performance mode of a device."""
-
- _AVAILABLE_GOVERNORS_REL_PATH = 'cpufreq/scaling_available_governors'
- _CPU_FILE_PATTERN = re.compile(r'^cpu\d+$')
- _CPU_PATH = '/sys/devices/system/cpu'
- _KERNEL_MAX = '/sys/devices/system/cpu/kernel_max'
-
- def __init__(self, device):
- self._device = device
- self._cpu_files = [
- filename
- for filename in self._device.ListDirectory(self._CPU_PATH, as_root=True)
- if self._CPU_FILE_PATTERN.match(filename)]
- assert self._cpu_files, 'Failed to detect CPUs.'
- self._cpu_file_list = ' '.join(self._cpu_files)
- logger.info('CPUs found: %s', self._cpu_file_list)
-
- self._have_mpdecision = self._device.FileExists('/system/bin/mpdecision')
-
- raw = self._ReadEachCpuFile(self._AVAILABLE_GOVERNORS_REL_PATH)
- self._available_governors = [
- (cpu, raw_governors.strip().split() if not exit_code else None)
- for cpu, raw_governors, exit_code in raw]
-
- def SetHighPerfMode(self):
- """Sets the highest stable performance mode for the device."""
- try:
- self._device.EnableRoot()
- except device_errors.CommandFailedError:
- message = 'Need root for performance mode. Results may be NOISY!!'
- logger.warning(message)
- # Add an additional warning at exit, such that it's clear that any results
- # may be different/noisy (due to the lack of intended performance mode).
- atexit.register(logger.warning, message)
- return
-
- product_model = self._device.product_model
- # TODO(epenner): Enable on all devices (http://crbug.com/383566)
- if 'Nexus 4' == product_model:
- self._ForceAllCpusOnline(True)
- if not self._AllCpusAreOnline():
- logger.warning('Failed to force CPUs online. Results may be NOISY!')
- self.SetScalingGovernor('performance')
- elif 'Nexus 5' == product_model:
- self._ForceAllCpusOnline(True)
- if not self._AllCpusAreOnline():
- logger.warning('Failed to force CPUs online. Results may be NOISY!')
- self.SetScalingGovernor('performance')
- self._SetScalingMaxFreq(1190400)
- self._SetMaxGpuClock(200000000)
- else:
- self.SetScalingGovernor('performance')
-
- def SetPerfProfilingMode(self):
- """Enables all cores for reliable perf profiling."""
- self._ForceAllCpusOnline(True)
- self.SetScalingGovernor('performance')
- if not self._AllCpusAreOnline():
- if not self._device.HasRoot():
- raise RuntimeError('Need root to force CPUs online.')
- raise RuntimeError('Failed to force CPUs online.')
-
- def SetDefaultPerfMode(self):
- """Sets the performance mode for the device to its default mode."""
- if not self._device.HasRoot():
- return
- product_model = self._device.product_model
- if 'Nexus 5' == product_model:
- if self._AllCpusAreOnline():
- self._SetScalingMaxFreq(2265600)
- self._SetMaxGpuClock(450000000)
-
- governor_mode = {
- 'GT-I9300': 'pegasusq',
- 'Galaxy Nexus': 'interactive',
- 'Nexus 4': 'ondemand',
- 'Nexus 5': 'ondemand',
- 'Nexus 7': 'interactive',
- 'Nexus 10': 'interactive'
- }.get(product_model, 'ondemand')
- self.SetScalingGovernor(governor_mode)
- self._ForceAllCpusOnline(False)
-
- def GetCpuInfo(self):
- online = (output.rstrip() == '1' and status == 0
- for (_, output, status) in self._ForEachCpu('cat "$CPU/online"'))
- governor = (output.rstrip() if status == 0 else None
- for (_, output, status)
- in self._ForEachCpu('cat "$CPU/cpufreq/scaling_governor"'))
- return zip(self._cpu_files, online, governor)
-
- def _ForEachCpu(self, cmd):
- script = '; '.join([
- 'for CPU in %s' % self._cpu_file_list,
- 'do %s' % cmd,
- 'echo -n "%~%$?%~%"',
- 'done'
- ])
- output = self._device.RunShellCommand(
- script, cwd=self._CPU_PATH, check_return=True, as_root=True, shell=True)
- output = '\n'.join(output).split('%~%')
- return zip(self._cpu_files, output[0::2], (int(c) for c in output[1::2]))
-
- def _WriteEachCpuFile(self, path, value):
- self._ConditionallyWriteEachCpuFile(path, value, condition='true')
-
- def _ConditionallyWriteEachCpuFile(self, path, value, condition):
- template = (
- '{condition} && test -e "$CPU/{path}" && echo {value} > "$CPU/{path}"')
- results = self._ForEachCpu(
- template.format(path=path, value=value, condition=condition))
- cpus = ' '.join(cpu for (cpu, _, status) in results if status == 0)
- if cpus:
- logger.info('Successfully set %s to %r on: %s', path, value, cpus)
- else:
- logger.warning('Failed to set %s to %r on any cpus', path, value)
-
- def _ReadEachCpuFile(self, path):
- return self._ForEachCpu(
- 'cat "$CPU/{path}"'.format(path=path))
-
- def SetScalingGovernor(self, value):
- """Sets the scaling governor to the given value on all possible CPUs.
-
- This does not attempt to set a governor to a value not reported as available
- on the corresponding CPU.
-
- Args:
- value: [string] The new governor value.
- """
- condition = 'test -e "{path}" && grep -q {value} {path}'.format(
- path=('${CPU}/%s' % self._AVAILABLE_GOVERNORS_REL_PATH),
- value=value)
- self._ConditionallyWriteEachCpuFile(
- 'cpufreq/scaling_governor', value, condition)
-
- def GetScalingGovernor(self):
- """Gets the currently set governor for each CPU.
-
- Returns:
- An iterable of 2-tuples, each containing the cpu and the current
- governor.
- """
- raw = self._ReadEachCpuFile('cpufreq/scaling_governor')
- return [
- (cpu, raw_governor.strip() if not exit_code else None)
- for cpu, raw_governor, exit_code in raw]
-
- def ListAvailableGovernors(self):
- """Returns the list of available governors for each CPU.
-
- Returns:
- An iterable of 2-tuples, each containing the cpu and a list of available
- governors for that cpu.
- """
- return self._available_governors
-
- def _SetScalingMaxFreq(self, value):
- self._WriteEachCpuFile('cpufreq/scaling_max_freq', '%d' % value)
-
- def _SetMaxGpuClock(self, value):
- self._device.WriteFile('/sys/class/kgsl/kgsl-3d0/max_gpuclk',
- str(value),
- as_root=True)
-
- def _AllCpusAreOnline(self):
- results = self._ForEachCpu('cat "$CPU/online"')
- # TODO(epenner): Investigate why file may be missing
- # (http://crbug.com/397118)
- return all(output.rstrip() == '1' and status == 0
- for (cpu, output, status) in results
- if cpu != 'cpu0')
-
- def _ForceAllCpusOnline(self, force_online):
- """Enable all CPUs on a device.
-
- Some vendors (or only Qualcomm?) hot-plug their CPUs, which can add noise
- to measurements:
- - In perf, samples are only taken for the CPUs that are online when the
- measurement is started.
- - The scaling governor can't be set for an offline CPU and frequency scaling
- on newly enabled CPUs adds noise to both perf and tracing measurements.
-
- It appears Qualcomm is the only vendor that hot-plugs CPUs, and on Qualcomm
- this is done by "mpdecision".
-
- """
- if self._have_mpdecision:
- cmd = ['stop', 'mpdecision'] if force_online else ['start', 'mpdecision']
- self._device.RunShellCommand(cmd, check_return=True, as_root=True)
-
- if not self._have_mpdecision and not self._AllCpusAreOnline():
- logger.warning('Unexpected cpu hot plugging detected.')
-
- if force_online:
- self._ForEachCpu('echo 1 > "$CPU/online"')
diff --git a/third_party/catapult/devil/devil/android/perf/perf_control_devicetest.py b/third_party/catapult/devil/devil/android/perf/perf_control_devicetest.py
deleted file mode 100644
index b64580306b..0000000000
--- a/third_party/catapult/devil/devil/android/perf/perf_control_devicetest.py
+++ /dev/null
@@ -1,38 +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.
-# pylint: disable=W0212
-
-import os
-import sys
-import unittest
-
-sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
-
-from devil.android import device_test_case
-from devil.android import device_utils
-from devil.android.perf import perf_control
-
-
-class TestPerfControl(device_test_case.DeviceTestCase):
-
- def setUp(self):
- super(TestPerfControl, self).setUp()
- if not os.getenv('BUILDTYPE'):
- os.environ['BUILDTYPE'] = 'Debug'
- self._device = device_utils.DeviceUtils(self.serial)
-
- def testHighPerfMode(self):
- perf = perf_control.PerfControl(self._device)
- try:
- perf.SetPerfProfilingMode()
- cpu_info = perf.GetCpuInfo()
- self.assertEquals(len(perf._cpu_files), len(cpu_info))
- for _, online, governor in cpu_info:
- self.assertTrue(online)
- self.assertEquals('performance', governor)
- finally:
- perf.SetDefaultPerfMode()
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/third_party/catapult/devil/devil/android/perf/surface_stats_collector.py b/third_party/catapult/devil/devil/android/perf/surface_stats_collector.py
deleted file mode 100644
index 25079f310e..0000000000
--- a/third_party/catapult/devil/devil/android/perf/surface_stats_collector.py
+++ /dev/null
@@ -1,186 +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.
-
-import Queue
-import threading
-
-
-# Log marker containing SurfaceTexture timestamps.
-_SURFACE_TEXTURE_TIMESTAMPS_MESSAGE = 'SurfaceTexture update timestamps'
-_SURFACE_TEXTURE_TIMESTAMP_RE = r'\d+'
-
-
-class SurfaceStatsCollector(object):
- """Collects surface stats for a SurfaceView from the output of SurfaceFlinger.
-
- Args:
- device: A DeviceUtils instance.
- """
-
- def __init__(self, device):
- self._device = device
- self._collector_thread = None
- self._surface_before = None
- self._get_data_event = None
- self._data_queue = None
- self._stop_event = None
- self._warn_about_empty_data = True
-
- def DisableWarningAboutEmptyData(self):
- self._warn_about_empty_data = False
-
- def Start(self):
- assert not self._collector_thread
-
- if self._ClearSurfaceFlingerLatencyData():
- self._get_data_event = threading.Event()
- self._stop_event = threading.Event()
- self._data_queue = Queue.Queue()
- self._collector_thread = threading.Thread(target=self._CollectorThread)
- self._collector_thread.start()
- else:
- raise Exception('SurfaceFlinger not supported on this device.')
-
- def Stop(self):
- assert self._collector_thread
- (refresh_period, timestamps) = self._GetDataFromThread()
- if self._collector_thread:
- self._stop_event.set()
- self._collector_thread.join()
- self._collector_thread = None
- return (refresh_period, timestamps)
-
- def _CollectorThread(self):
- last_timestamp = 0
- timestamps = []
- retries = 0
-
- while not self._stop_event.is_set():
- self._get_data_event.wait(1)
- try:
- refresh_period, new_timestamps = self._GetSurfaceFlingerFrameData()
- if refresh_period is None or timestamps is None:
- retries += 1
- if retries < 3:
- continue
- if last_timestamp:
- # Some data has already been collected, but either the app
- # was closed or there's no new data. Signal the main thread and
- # wait.
- self._data_queue.put((None, None))
- self._stop_event.wait()
- break
- raise Exception('Unable to get surface flinger latency data')
-
- timestamps += [timestamp for timestamp in new_timestamps
- if timestamp > last_timestamp]
- if len(timestamps):
- last_timestamp = timestamps[-1]
-
- if self._get_data_event.is_set():
- self._get_data_event.clear()
- self._data_queue.put((refresh_period, timestamps))
- timestamps = []
- except Exception as e:
- # On any error, before aborting, put the exception into _data_queue to
- # prevent the main thread from waiting at _data_queue.get() infinitely.
- self._data_queue.put(e)
- raise
-
- def _GetDataFromThread(self):
- self._get_data_event.set()
- ret = self._data_queue.get()
- if isinstance(ret, Exception):
- raise ret
- return ret
-
- def _ClearSurfaceFlingerLatencyData(self):
- """Clears the SurfaceFlinger latency data.
-
- Returns:
- True if SurfaceFlinger latency is supported by the device, otherwise
- False.
- """
- # The command returns nothing if it is supported, otherwise returns many
- # lines of result just like 'dumpsys SurfaceFlinger'.
- results = self._device.RunShellCommand(
- ['dumpsys', 'SurfaceFlinger', '--latency-clear', 'SurfaceView'],
- check_return=True)
- return not len(results)
-
- def GetSurfaceFlingerPid(self):
- pids_dict = self._device.GetPids('surfaceflinger')
- if not pids_dict:
- raise Exception('Unable to get surface flinger process id')
- # TODO(cataput:#3378): Do more strict checks in GetPids when possible.
- # For now it just returns the first pid found of some matching process.
- return pids_dict.popitem()[1][0]
-
- def _GetSurfaceFlingerFrameData(self):
- """Returns collected SurfaceFlinger frame timing data.
-
- Returns:
- A tuple containing:
- - The display's nominal refresh period in milliseconds.
- - A list of timestamps signifying frame presentation times in
- milliseconds.
- The return value may be (None, None) if there was no data collected (for
- example, if the app was closed before the collector thread has finished).
- """
- # adb shell dumpsys SurfaceFlinger --latency <window name>
- # prints some information about the last 128 frames displayed in
- # that window.
- # The data returned looks like this:
- # 16954612
- # 7657467895508 7657482691352 7657493499756
- # 7657484466553 7657499645964 7657511077881
- # 7657500793457 7657516600576 7657527404785
- # (...)
- #
- # The first line is the refresh period (here 16.95 ms), it is followed
- # by 128 lines w/ 3 timestamps in nanosecond each:
- # A) when the app started to draw
- # B) the vsync immediately preceding SF submitting the frame to the h/w
- # C) timestamp immediately after SF submitted that frame to the h/w
- #
- # The difference between the 1st and 3rd timestamp is the frame-latency.
- # An interesting data is when the frame latency crosses a refresh period
- # boundary, this can be calculated this way:
- #
- # ceil((C - A) / refresh-period)
- #
- # (each time the number above changes, we have a "jank").
- # If this happens a lot during an animation, the animation appears
- # janky, even if it runs at 60 fps in average.
- #
- # We use the special "SurfaceView" window name because the statistics for
- # the activity's main window are not updated when the main web content is
- # composited into a SurfaceView.
- results = self._device.RunShellCommand(
- ['dumpsys', 'SurfaceFlinger', '--latency', 'SurfaceView'],
- check_return=True)
- if not len(results):
- return (None, None)
-
- timestamps = []
- nanoseconds_per_millisecond = 1e6
- refresh_period = long(results[0]) / nanoseconds_per_millisecond
-
- # If a fence associated with a frame is still pending when we query the
- # latency data, SurfaceFlinger gives the frame a timestamp of INT64_MAX.
- # Since we only care about completed frames, we will ignore any timestamps
- # with this value.
- pending_fence_timestamp = (1 << 63) - 1
-
- for line in results[1:]:
- fields = line.split()
- if len(fields) != 3:
- continue
- timestamp = long(fields[1])
- if timestamp == pending_fence_timestamp:
- continue
- timestamp /= nanoseconds_per_millisecond
- timestamps.append(timestamp)
-
- return (refresh_period, timestamps)
diff --git a/third_party/catapult/devil/devil/android/perf/thermal_throttle.py b/third_party/catapult/devil/devil/android/perf/thermal_throttle.py
deleted file mode 100644
index 546a92e092..0000000000
--- a/third_party/catapult/devil/devil/android/perf/thermal_throttle.py
+++ /dev/null
@@ -1,135 +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.
-
-import logging
-
-logger = logging.getLogger(__name__)
-
-
-class OmapThrottlingDetector(object):
- """Class to detect and track thermal throttling on an OMAP 4."""
- OMAP_TEMP_FILE = ('/sys/devices/platform/omap/omap_temp_sensor.0/'
- 'temperature')
-
- @staticmethod
- def IsSupported(device):
- return device.FileExists(OmapThrottlingDetector.OMAP_TEMP_FILE)
-
- def __init__(self, device):
- self._device = device
-
- @staticmethod
- def BecameThrottled(log_line):
- return 'omap_thermal_throttle' in log_line
-
- @staticmethod
- def BecameUnthrottled(log_line):
- return 'omap_thermal_unthrottle' in log_line
-
- @staticmethod
- def GetThrottlingTemperature(log_line):
- if 'throttle_delayed_work_fn' in log_line:
- return float([s for s in log_line.split() if s.isdigit()][0]) / 1000.0
-
- def GetCurrentTemperature(self):
- tempdata = self._device.ReadFile(OmapThrottlingDetector.OMAP_TEMP_FILE)
- return float(tempdata) / 1000.0
-
-
-class ExynosThrottlingDetector(object):
- """Class to detect and track thermal throttling on an Exynos 5."""
- @staticmethod
- def IsSupported(device):
- return device.FileExists('/sys/bus/exynos5-core')
-
- def __init__(self, device):
- pass
-
- @staticmethod
- def BecameThrottled(log_line):
- return 'exynos_tmu: Throttling interrupt' in log_line
-
- @staticmethod
- def BecameUnthrottled(log_line):
- return 'exynos_thermal_unthrottle: not throttling' in log_line
-
- @staticmethod
- def GetThrottlingTemperature(_log_line):
- return None
-
- @staticmethod
- def GetCurrentTemperature():
- return None
-
-
-class ThermalThrottle(object):
- """Class to detect and track thermal throttling.
-
- Usage:
- Wait for IsThrottled() to be False before running test
- After running test call HasBeenThrottled() to find out if the
- test run was affected by thermal throttling.
- """
-
- def __init__(self, device):
- self._device = device
- self._throttled = False
- self._detector = None
- if OmapThrottlingDetector.IsSupported(device):
- self._detector = OmapThrottlingDetector(device)
- elif ExynosThrottlingDetector.IsSupported(device):
- self._detector = ExynosThrottlingDetector(device)
-
- def HasBeenThrottled(self):
- """True if there has been any throttling since the last call to
- HasBeenThrottled or IsThrottled.
- """
- return self._ReadLog()
-
- def IsThrottled(self):
- """True if currently throttled."""
- self._ReadLog()
- return self._throttled
-
- def _ReadLog(self):
- if not self._detector:
- return False
- has_been_throttled = False
- serial_number = str(self._device)
- log = self._device.RunShellCommand(
- ['dmesg', '-c'], large_output=True, check_return=True)
- degree_symbol = unichr(0x00B0)
- for line in log:
- if self._detector.BecameThrottled(line):
- if not self._throttled:
- logger.warning('>>> Device %s thermally throttled', serial_number)
- self._throttled = True
- has_been_throttled = True
- elif self._detector.BecameUnthrottled(line):
- if self._throttled:
- logger.warning('>>> Device %s thermally unthrottled', serial_number)
- self._throttled = False
- has_been_throttled = True
- temperature = self._detector.GetThrottlingTemperature(line)
- if temperature is not None:
- logger.info(u'Device %s thermally throttled at %3.1f%sC',
- serial_number, temperature, degree_symbol)
-
- if logger.isEnabledFor(logging.DEBUG):
- # Print current temperature of CPU SoC.
- temperature = self._detector.GetCurrentTemperature()
- if temperature is not None:
- logger.debug(u'Current SoC temperature of %s = %3.1f%sC',
- serial_number, temperature, degree_symbol)
-
- # Print temperature of battery, to give a system temperature
- dumpsys_log = self._device.RunShellCommand(
- ['dumpsys', 'battery'], check_return=True)
- for line in dumpsys_log:
- if 'temperature' in line:
- btemp = float([s for s in line.split() if s.isdigit()][0]) / 10.0
- logger.debug(u'Current battery temperature of %s = %3.1f%sC',
- serial_number, btemp, degree_symbol)
-
- return has_been_throttled
diff --git a/third_party/catapult/devil/devil/android/ports.py b/third_party/catapult/devil/devil/android/ports.py
deleted file mode 100644
index 1d4e5f21fe..0000000000
--- a/third_party/catapult/devil/devil/android/ports.py
+++ /dev/null
@@ -1,178 +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.
-
-"""Functions that deal with local and device ports."""
-
-import contextlib
-import fcntl
-import httplib
-import logging
-import os
-import socket
-import traceback
-
-logger = logging.getLogger(__name__)
-
-# The net test server is started from port 10201.
-_TEST_SERVER_PORT_FIRST = 10201
-_TEST_SERVER_PORT_LAST = 30000
-# A file to record next valid port of test server.
-_TEST_SERVER_PORT_FILE = '/tmp/test_server_port'
-_TEST_SERVER_PORT_LOCKFILE = '/tmp/test_server_port.lock'
-
-
-# The following two methods are used to allocate the port source for various
-# types of test servers. Because some net-related tests can be run on shards at
-# same time, it's important to have a mechanism to allocate the port
-# process-safe. In here, we implement the safe port allocation by leveraging
-# flock.
-def ResetTestServerPortAllocation():
- """Resets the port allocation to start from TEST_SERVER_PORT_FIRST.
-
- Returns:
- Returns True if reset successes. Otherwise returns False.
- """
- try:
- with open(_TEST_SERVER_PORT_FILE, 'w') as fp:
- fp.write('%d' % _TEST_SERVER_PORT_FIRST)
- return True
- except Exception: # pylint: disable=broad-except
- logger.exception('Error while resetting port allocation')
- return False
-
-
-def AllocateTestServerPort():
- """Allocates a port incrementally.
-
- Returns:
- Returns a valid port which should be in between TEST_SERVER_PORT_FIRST and
- TEST_SERVER_PORT_LAST. Returning 0 means no more valid port can be used.
- """
- port = 0
- ports_tried = []
- try:
- fp_lock = open(_TEST_SERVER_PORT_LOCKFILE, 'w')
- fcntl.flock(fp_lock, fcntl.LOCK_EX)
- # Get current valid port and calculate next valid port.
- if not os.path.exists(_TEST_SERVER_PORT_FILE):
- ResetTestServerPortAllocation()
- with open(_TEST_SERVER_PORT_FILE, 'r+') as fp:
- port = int(fp.read())
- ports_tried.append(port)
- while not IsHostPortAvailable(port):
- port += 1
- ports_tried.append(port)
- if (port > _TEST_SERVER_PORT_LAST or
- port < _TEST_SERVER_PORT_FIRST):
- port = 0
- else:
- fp.seek(0, os.SEEK_SET)
- fp.write('%d' % (port + 1))
- except Exception: # pylint: disable=broad-except
- logger.exception('Error while allocating port')
- finally:
- if fp_lock:
- fcntl.flock(fp_lock, fcntl.LOCK_UN)
- fp_lock.close()
- if port:
- logger.info('Allocate port %d for test server.', port)
- else:
- logger.error('Could not allocate port for test server. '
- 'List of ports tried: %s', str(ports_tried))
- return port
-
-
-def IsHostPortAvailable(host_port):
- """Checks whether the specified host port is available.
-
- Args:
- host_port: Port on host to check.
-
- Returns:
- True if the port on host is available, otherwise returns False.
- """
- s = socket.socket()
- try:
- s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- s.bind(('', host_port))
- s.close()
- return True
- except socket.error:
- return False
-
-
-def IsDevicePortUsed(device, device_port, state=''):
- """Checks whether the specified device port is used or not.
-
- Args:
- device: A DeviceUtils instance.
- device_port: Port on device we want to check.
- state: String of the specified state. Default is empty string, which
- means any state.
-
- Returns:
- True if the port on device is already used, otherwise returns False.
- """
- base_urls = ('127.0.0.1:%d' % device_port, 'localhost:%d' % device_port)
- netstat_results = device.RunShellCommand(
- ['netstat', '-a'], check_return=True, large_output=True)
- for single_connect in netstat_results:
- # Column 3 is the local address which we want to check with.
- connect_results = single_connect.split()
- if connect_results[0] != 'tcp':
- continue
- if len(connect_results) < 6:
- raise Exception('Unexpected format while parsing netstat line: ' +
- single_connect)
- is_state_match = connect_results[5] == state if state else True
- if connect_results[3] in base_urls and is_state_match:
- return True
- return False
-
-
-def IsHttpServerConnectable(host, port, tries=3, command='GET', path='/',
- expected_read='', timeout=2):
- """Checks whether the specified http server is ready to serve request or not.
-
- Args:
- host: Host name of the HTTP server.
- port: Port number of the HTTP server.
- tries: How many times we want to test the connection. The default value is
- 3.
- command: The http command we use to connect to HTTP server. The default
- command is 'GET'.
- path: The path we use when connecting to HTTP server. The default path is
- '/'.
- expected_read: The content we expect to read from the response. The default
- value is ''.
- timeout: Timeout (in seconds) for each http connection. The default is 2s.
-
- Returns:
- Tuple of (connect status, client error). connect status is a boolean value
- to indicate whether the server is connectable. client_error is the error
- message the server returns when connect status is false.
- """
- assert tries >= 1
- for i in xrange(0, tries):
- client_error = None
- try:
- with contextlib.closing(httplib.HTTPConnection(
- host, port, timeout=timeout)) as http:
- # Output some debug information when we have tried more than 2 times.
- http.set_debuglevel(i >= 2)
- http.request(command, path)
- r = http.getresponse()
- content = r.read()
- if r.status == 200 and r.reason == 'OK' and content == expected_read:
- return (True, '')
- client_error = ('Bad response: %s %s version %s\n ' %
- (r.status, r.reason, r.version) +
- '\n '.join([': '.join(h) for h in r.getheaders()]))
- except (httplib.HTTPException, socket.error) as e:
- # Probably too quick connecting: try again.
- exception_error_msgs = traceback.format_exception_only(type(e), e)
- if exception_error_msgs:
- client_error = ''.join(exception_error_msgs)
- # Only returns last client_error.
- return (False, client_error or 'Timeout')
diff --git a/third_party/catapult/devil/devil/android/sdk/__init__.py b/third_party/catapult/devil/devil/android/sdk/__init__.py
deleted file mode 100644
index f95d3b27b4..0000000000
--- a/third_party/catapult/devil/devil/android/sdk/__init__.py
+++ /dev/null
@@ -1,6 +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.
-
-# This package is intended for modules that are very tightly coupled to
-# tools or APIs from the Android SDK.
diff --git a/third_party/catapult/devil/devil/android/sdk/aapt.py b/third_party/catapult/devil/devil/android/sdk/aapt.py
deleted file mode 100644
index 7ae3a9380a..0000000000
--- a/third_party/catapult/devil/devil/android/sdk/aapt.py
+++ /dev/null
@@ -1,43 +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.
-
-"""This module wraps the Android Asset Packaging Tool."""
-
-from devil.android.sdk import build_tools
-from devil.utils import cmd_helper
-from devil.utils import lazy
-
-
-_aapt_path = lazy.WeakConstant(lambda: build_tools.GetPath('aapt'))
-
-
-def _RunAaptCmd(args):
- """Runs an aapt command.
-
- Args:
- args: A list of arguments for aapt.
-
- Returns:
- The output of the command.
- """
- cmd = [_aapt_path.read()] + args
- status, output = cmd_helper.GetCmdStatusAndOutput(cmd)
- if status != 0:
- raise Exception('Failed running aapt command: "%s" with output "%s".' %
- (' '.join(cmd), output))
- return output
-
-
-def Dump(what, apk, assets=None):
- """Returns the output of the aapt dump command.
-
- Args:
- what: What you want to dump.
- apk: Path to apk you want to dump information for.
- assets: List of assets in apk you want to dump information for.
- """
- assets = assets or []
- if isinstance(assets, basestring):
- assets = [assets]
- return _RunAaptCmd(['dump', what, apk] + assets).splitlines()
diff --git a/third_party/catapult/devil/devil/android/sdk/adb_compatibility_devicetest.py b/third_party/catapult/devil/devil/android/sdk/adb_compatibility_devicetest.py
deleted file mode 100644
index cbe2a1b6a0..0000000000
--- a/third_party/catapult/devil/devil/android/sdk/adb_compatibility_devicetest.py
+++ /dev/null
@@ -1,230 +0,0 @@
-#!/usr/bin/env python
-# 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 posixpath
-import random
-import signal
-import sys
-import unittest
-
-_CATAPULT_BASE_DIR = os.path.abspath(os.path.join(
- os.path.dirname(__file__), '..', '..', '..', '..'))
-
-sys.path.append(os.path.join(_CATAPULT_BASE_DIR, 'devil'))
-from devil import devil_env
-from devil.android import device_errors
-from devil.android import device_test_case
-from devil.android.sdk import adb_wrapper
-from devil.utils import cmd_helper
-from devil.utils import timeout_retry
-
-
-_TEST_DATA_DIR = os.path.abspath(os.path.join(
- os.path.dirname(__file__), 'test', 'data'))
-
-
-def _hostAdbPids():
- ps_status, ps_output = cmd_helper.GetCmdStatusAndOutput(
- ['pgrep', '-l', 'adb'])
- if ps_status != 0:
- return []
-
- pids_and_names = (line.split() for line in ps_output.splitlines())
- return [int(pid) for pid, name in pids_and_names
- if name == 'adb']
-
-
-class AdbCompatibilityTest(device_test_case.DeviceTestCase):
-
- @classmethod
- def setUpClass(cls):
- custom_adb_path = os.environ.get('ADB_PATH')
- custom_deps = {
- 'config_type': 'BaseConfig',
- 'dependencies': {},
- }
- if custom_adb_path:
- custom_deps['dependencies']['adb'] = {
- 'file_info': {
- devil_env.GetPlatform(): {
- 'local_paths': [custom_adb_path],
- },
- },
- }
- devil_env.config.Initialize(configs=[custom_deps])
-
- def testStartServer(self):
- # Manually kill off any instances of adb.
- adb_pids = _hostAdbPids()
- for p in adb_pids:
- os.kill(p, signal.SIGKILL)
-
- self.assertIsNotNone(
- timeout_retry.WaitFor(
- lambda: not _hostAdbPids(), wait_period=0.1, max_tries=10))
-
- # start the adb server
- start_server_status, _ = cmd_helper.GetCmdStatusAndOutput(
- [adb_wrapper.AdbWrapper.GetAdbPath(), 'start-server'])
-
- # verify that the server is now online
- self.assertEquals(0, start_server_status)
- self.assertIsNotNone(
- timeout_retry.WaitFor(
- lambda: bool(_hostAdbPids()), wait_period=0.1, max_tries=10))
-
- def testKillServer(self):
- adb_pids = _hostAdbPids()
- if not adb_pids:
- adb_wrapper.AdbWrapper.StartServer()
-
- adb_pids = _hostAdbPids()
- self.assertGreaterEqual(len(adb_pids), 1)
-
- kill_server_status, _ = cmd_helper.GetCmdStatusAndOutput(
- [adb_wrapper.AdbWrapper.GetAdbPath(), 'kill-server'])
- self.assertEqual(0, kill_server_status)
-
- adb_pids = _hostAdbPids()
- self.assertEqual(0, len(adb_pids))
-
- def testDevices(self):
- devices = adb_wrapper.AdbWrapper.Devices()
- self.assertNotEqual(0, len(devices), 'No devices found.')
-
- def getTestInstance(self):
- """Creates a real AdbWrapper instance for testing."""
- return adb_wrapper.AdbWrapper(self.serial)
-
- def testShell(self):
- under_test = self.getTestInstance()
- shell_ls_result = under_test.Shell('ls')
- self.assertIsInstance(shell_ls_result, str)
- self.assertTrue(bool(shell_ls_result))
-
- def testShell_failed(self):
- under_test = self.getTestInstance()
- with self.assertRaises(device_errors.AdbShellCommandFailedError):
- under_test.Shell('ls /foo/bar/baz')
-
- def testShell_externalStorageDefined(self):
- under_test = self.getTestInstance()
- external_storage = under_test.Shell('echo $EXTERNAL_STORAGE')
- self.assertIsInstance(external_storage, str)
- self.assertTrue(posixpath.isabs(external_storage))
-
- @contextlib.contextmanager
- def getTestPushDestination(self, under_test):
- """Creates a temporary directory suitable for pushing to."""
- external_storage = under_test.Shell('echo $EXTERNAL_STORAGE').strip()
- if not external_storage:
- self.skipTest('External storage not available.')
- while True:
- random_hex = hex(random.randint(0, 2 ** 52))[2:]
- name = 'tmp_push_test%s' % random_hex
- path = posixpath.join(external_storage, name)
- try:
- under_test.Shell('ls %s' % path)
- except device_errors.AdbShellCommandFailedError:
- break
- under_test.Shell('mkdir %s' % path)
- try:
- yield path
- finally:
- under_test.Shell('rm -rf %s' % path)
-
- def testPush_fileToFile(self):
- under_test = self.getTestInstance()
- with self.getTestPushDestination(under_test) as push_target_directory:
- src = os.path.join(_TEST_DATA_DIR, 'push_file.txt')
- dest = posixpath.join(push_target_directory, 'push_file.txt')
- with self.assertRaises(device_errors.AdbShellCommandFailedError):
- under_test.Shell('ls %s' % dest)
- under_test.Push(src, dest)
- self.assertEquals(dest, under_test.Shell('ls %s' % dest).strip())
-
- def testPush_fileToDirectory(self):
- under_test = self.getTestInstance()
- with self.getTestPushDestination(under_test) as push_target_directory:
- src = os.path.join(_TEST_DATA_DIR, 'push_file.txt')
- dest = push_target_directory
- resulting_file = posixpath.join(dest, 'push_file.txt')
- with self.assertRaises(device_errors.AdbShellCommandFailedError):
- under_test.Shell('ls %s' % resulting_file)
- under_test.Push(src, dest)
- self.assertEquals(
- resulting_file,
- under_test.Shell('ls %s' % resulting_file).strip())
-
- def testPush_directoryToDirectory(self):
- under_test = self.getTestInstance()
- with self.getTestPushDestination(under_test) as push_target_directory:
- src = os.path.join(_TEST_DATA_DIR, 'push_directory')
- dest = posixpath.join(push_target_directory, 'push_directory')
- with self.assertRaises(device_errors.AdbShellCommandFailedError):
- under_test.Shell('ls %s' % dest)
- under_test.Push(src, dest)
- self.assertEquals(
- sorted(os.listdir(src)),
- sorted(under_test.Shell('ls %s' % dest).strip().split()))
-
- def testPush_directoryToExistingDirectory(self):
- under_test = self.getTestInstance()
- with self.getTestPushDestination(under_test) as push_target_directory:
- src = os.path.join(_TEST_DATA_DIR, 'push_directory')
- dest = push_target_directory
- resulting_directory = posixpath.join(dest, 'push_directory')
- with self.assertRaises(device_errors.AdbShellCommandFailedError):
- under_test.Shell('ls %s' % resulting_directory)
- under_test.Shell('mkdir %s' % resulting_directory)
- under_test.Push(src, dest)
- self.assertEquals(
- sorted(os.listdir(src)),
- sorted(under_test.Shell('ls %s' % resulting_directory).split()))
-
- # TODO(jbudorick): Implement tests for the following:
- # taskset -c
- # devices [-l]
- # pull
- # shell
- # ls
- # logcat [-c] [-d] [-v] [-b]
- # forward [--remove] [--list]
- # jdwp
- # install [-l] [-r] [-s] [-d]
- # install-multiple [-l] [-r] [-s] [-d] [-p]
- # uninstall [-k]
- # backup -f [-apk] [-shared] [-nosystem] [-all]
- # restore
- # wait-for-device
- # get-state (BROKEN IN THE M SDK)
- # get-devpath
- # remount
- # reboot
- # reboot-bootloader
- # root
- # emu
-
- @classmethod
- def tearDownClass(cls):
- print
- print
- print 'tested %s' % adb_wrapper.AdbWrapper.GetAdbPath()
- print ' %s' % adb_wrapper.AdbWrapper.Version()
- print 'connected devices:'
- try:
- for d in adb_wrapper.AdbWrapper.Devices():
- print ' %s' % d
- except device_errors.AdbCommandFailedError:
- print ' <failed to list devices>'
- raise
- finally:
- print
-
-
-if __name__ == '__main__':
- sys.exit(unittest.main())
diff --git a/third_party/catapult/devil/devil/android/sdk/adb_wrapper.py b/third_party/catapult/devil/devil/android/sdk/adb_wrapper.py
deleted file mode 100644
index 7f6b8d952b..0000000000
--- a/third_party/catapult/devil/devil/android/sdk/adb_wrapper.py
+++ /dev/null
@@ -1,917 +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.
-
-"""This module wraps Android's adb tool.
-
-This is a thin wrapper around the adb interface. Any additional complexity
-should be delegated to a higher level (ex. DeviceUtils).
-"""
-
-import collections
-import distutils.version
-import errno
-import logging
-import os
-import posixpath
-import re
-import subprocess
-
-from devil import devil_env
-from devil.android import decorators
-from devil.android import device_errors
-from devil.utils import cmd_helper
-from devil.utils import lazy
-from devil.utils import timeout_retry
-
-with devil_env.SysPath(devil_env.DEPENDENCY_MANAGER_PATH):
- import dependency_manager # pylint: disable=import-error
-
-logger = logging.getLogger(__name__)
-
-
-ADB_KEYS_FILE = '/data/misc/adb/adb_keys'
-
-DEFAULT_TIMEOUT = 30
-DEFAULT_RETRIES = 2
-
-_ADB_VERSION_RE = re.compile(r'Android Debug Bridge version (\d+\.\d+\.\d+)')
-_EMULATOR_RE = re.compile(r'^emulator-[0-9]+$')
-_READY_STATE = 'device'
-_VERITY_DISABLE_RE = re.compile(r'Verity (already )?disabled')
-_VERITY_ENABLE_RE = re.compile(r'Verity (already )?enabled')
-
-
-def VerifyLocalFileExists(path):
- """Verifies a local file exists.
-
- Args:
- path: Path to the local file.
-
- Raises:
- IOError: If the file doesn't exist.
- """
- if not os.path.exists(path):
- raise IOError(errno.ENOENT, os.strerror(errno.ENOENT), path)
-
-
-def _FindAdb():
- try:
- return devil_env.config.LocalPath('adb')
- except dependency_manager.NoPathFoundError:
- pass
-
- try:
- return os.path.join(devil_env.config.LocalPath('android_sdk'),
- 'platform-tools', 'adb')
- except dependency_manager.NoPathFoundError:
- pass
-
- try:
- return devil_env.config.FetchPath('adb')
- except dependency_manager.NoPathFoundError:
- raise device_errors.NoAdbError()
-
-
-def _GetVersion():
- # pylint: disable=protected-access
- raw_version = AdbWrapper._RunAdbCmd(['version'], timeout=2, retries=0)
- for l in raw_version.splitlines():
- m = _ADB_VERSION_RE.search(l)
- if m:
- return m.group(1)
- return None
-
-
-def _ShouldRetryAdbCmd(exc):
- return not isinstance(exc, device_errors.NoAdbError)
-
-
-DeviceStat = collections.namedtuple('DeviceStat',
- ['st_mode', 'st_size', 'st_time'])
-
-
-def _IsExtraneousLine(line, send_cmd):
- """Determine if a line read from stdout in persistent shell is extraneous.
-
- The results output to stdout by the persistent shell process
- (in PersistentShell below) often include "extraneous" lines that are
- not part of the output of the shell command. These "extraneous" lines
- do not always appear and are of two forms: shell prompt lines and lines
- that just duplicate what the input command was. This function
- detects these extraneous lines. Since all these lines have the
- original command in them, that is what it detects ror.
-
- Args:
- line: Output line to check.
- send_cmd: Command that was sent to adb persistent shell.
- """
- return send_cmd.rstrip() in line
-
-
-class AdbWrapper(object):
- """A wrapper around a local Android Debug Bridge executable."""
-
- _adb_path = lazy.WeakConstant(_FindAdb)
- _adb_version = lazy.WeakConstant(_GetVersion)
-
- def __init__(self, device_serial):
- """Initializes the AdbWrapper.
-
- Args:
- device_serial: The device serial number as a string.
- """
- if not device_serial:
- raise ValueError('A device serial must be specified')
- self._device_serial = str(device_serial)
-
- class PersistentShell(object):
- '''Class to use persistent shell for ADB.
-
- This class allows a persistent ADB shell to be created, where multiple
- commands can be passed into it. This avoids the overhead of starting
- up a new ADB shell for each command.
-
- Example of use:
- with PersistentShell('123456789') as pshell:
- pshell.RunCommand('which ls')
- pshell.RunCommandAndClose('echo TEST')
- '''
- def __init__(self, serial):
- """Initialization function:
-
- Args:
- serial: Serial number of device.
- """
- self._cmd = [AdbWrapper.GetAdbPath(), '-s', serial, 'shell']
- self._process = None
-
- def __enter__(self):
- self.Start()
- self.WaitForReady()
- return self
-
- def __exit__(self, exc_type, exc_value, tb):
- self.Stop()
-
- def Start(self):
- """Start the shell."""
- if self._process is not None:
- raise RuntimeError('Persistent shell already running.')
- self._process = subprocess.Popen(self._cmd,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- shell=False)
-
- def WaitForReady(self):
- """Wait for the shell to be ready after starting.
-
- Sends an echo command, then waits until it gets a response.
- """
- self._process.stdin.write('echo\n')
- output_line = self._process.stdout.readline()
- while output_line.rstrip() != '':
- output_line = self._process.stdout.readline()
-
- def RunCommand(self, command, close=False):
- """Runs an ADB command and returns the output.
-
- Note that there can be approximately 40 ms of additional latency
- between sending the command and receiving the results if close=False
- due to the use of Nagle's algorithm in the TCP socket between the
- adb server and client. To avoid this extra latency, set close=True.
-
- Args:
- command: Command to send.
- Returns:
- The command output, given as a list of lines, and the exit code
- """
-
- if close:
- def run_cmd(cmd):
- send_cmd = '( %s ); echo $?; exit;\n' % cmd.rstrip()
- (output, _) = self._process.communicate(send_cmd)
- self._process = None
- for x in output.splitlines():
- yield x
-
- else:
- def run_cmd(cmd):
- send_cmd = '( %s ); echo DONE:$?;\n' % cmd.rstrip()
- self._process.stdin.write(send_cmd)
- while True:
- output_line = self._process.stdout.readline().rstrip()
- if output_line[:5] == 'DONE:':
- yield output_line[5:]
- break
- yield output_line
-
- result = [line for line in run_cmd(command)
- if not _IsExtraneousLine(line, command)]
-
- return (result[:-1], int(result[-1]))
-
- def Stop(self):
- """Stops the ADB process if it is still running."""
- if self._process is not None:
- self._process.stdin.write('exit\n')
- self._process = None
-
- @classmethod
- def GetAdbPath(cls):
- return cls._adb_path.read()
-
- @classmethod
- def Version(cls):
- return cls._adb_version.read()
-
- @classmethod
- def _BuildAdbCmd(cls, args, device_serial, cpu_affinity=None):
- if cpu_affinity is not None:
- cmd = ['taskset', '-c', str(cpu_affinity)]
- else:
- cmd = []
- cmd.append(cls.GetAdbPath())
- if device_serial is not None:
- cmd.extend(['-s', device_serial])
- cmd.extend(args)
- return cmd
-
- # pylint: disable=unused-argument
- @classmethod
- @decorators.WithTimeoutAndConditionalRetries(_ShouldRetryAdbCmd)
- def _RunAdbCmd(cls, args, timeout=None, retries=None, device_serial=None,
- check_error=True, cpu_affinity=None):
- # pylint: disable=no-member
- try:
- status, output = cmd_helper.GetCmdStatusAndOutputWithTimeout(
- cls._BuildAdbCmd(args, device_serial, cpu_affinity=cpu_affinity),
- timeout_retry.CurrentTimeoutThreadGroup().GetRemainingTime())
- except OSError as e:
- if e.errno in (errno.ENOENT, errno.ENOEXEC):
- raise device_errors.NoAdbError(msg=str(e))
- else:
- raise
-
- if status != 0:
- raise device_errors.AdbCommandFailedError(
- args, output, status, device_serial)
- # This catches some errors, including when the device drops offline;
- # unfortunately adb is very inconsistent with error reporting so many
- # command failures present differently.
- if check_error and output.startswith('error:'):
- raise device_errors.AdbCommandFailedError(args, output)
- return output
- # pylint: enable=unused-argument
-
- def _RunDeviceAdbCmd(self, args, timeout, retries, check_error=True):
- """Runs an adb command on the device associated with this object.
-
- Args:
- args: A list of arguments to adb.
- timeout: Timeout in seconds.
- retries: Number of retries.
- check_error: Check that the command doesn't return an error message. This
- does NOT check the exit status of shell commands.
-
- Returns:
- The output of the command.
- """
- return self._RunAdbCmd(args, timeout=timeout, retries=retries,
- device_serial=self._device_serial,
- check_error=check_error)
-
- def _IterRunDeviceAdbCmd(self, args, iter_timeout, timeout):
- """Runs an adb command and returns an iterator over its output lines.
-
- Args:
- args: A list of arguments to adb.
- iter_timeout: Timeout for each iteration in seconds.
- timeout: Timeout for the entire command in seconds.
-
- Yields:
- The output of the command line by line.
- """
- return cmd_helper.IterCmdOutputLines(
- self._BuildAdbCmd(args, self._device_serial),
- iter_timeout=iter_timeout,
- timeout=timeout)
-
- def __eq__(self, other):
- """Consider instances equal if they refer to the same device.
-
- Args:
- other: The instance to compare equality with.
-
- Returns:
- True if the instances are considered equal, false otherwise.
- """
- return self._device_serial == str(other)
-
- def __str__(self):
- """The string representation of an instance.
-
- Returns:
- The device serial number as a string.
- """
- return self._device_serial
-
- def __repr__(self):
- return '%s(\'%s\')' % (self.__class__.__name__, self)
-
- # pylint: disable=unused-argument
- @classmethod
- def IsServerOnline(cls):
- status, output = cmd_helper.GetCmdStatusAndOutput(['pgrep', 'adb'])
- output = [int(x) for x in output.split()]
- logger.info('PIDs for adb found: %r', output)
- return status == 0
- # pylint: enable=unused-argument
-
- @classmethod
- def KillServer(cls, timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES):
- cls._RunAdbCmd(['kill-server'], timeout=timeout, retries=retries)
-
- @classmethod
- def StartServer(cls, timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES):
- # CPU affinity is used to reduce adb instability http://crbug.com/268450
- cls._RunAdbCmd(['start-server'], timeout=timeout, retries=retries,
- cpu_affinity=0)
-
- @classmethod
- def GetDevices(cls, timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES):
- """DEPRECATED. Refer to Devices(...) below."""
- # TODO(jbudorick): Remove this function once no more clients are using it.
- return cls.Devices(timeout=timeout, retries=retries)
-
- @classmethod
- def Devices(cls, desired_state=_READY_STATE, long_list=False,
- timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES):
- """Get the list of active attached devices.
-
- Args:
- desired_state: If not None, limit the devices returned to only those
- in the given state.
- long_list: Whether to use the long listing format.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-
- Yields:
- AdbWrapper instances.
- """
- lines = cls._RawDevices(long_list=long_list, timeout=timeout,
- retries=retries)
- if long_list:
- return [
- [AdbWrapper(line[0])] + line[1:]
- for line in lines
- if (len(line) >= 2 and (not desired_state or line[1] == desired_state))
- ]
- else:
- return [
- AdbWrapper(line[0])
- for line in lines
- if (len(line) == 2 and (not desired_state or line[1] == desired_state))
- ]
-
- @classmethod
- def _RawDevices(cls, long_list=False, timeout=DEFAULT_TIMEOUT,
- retries=DEFAULT_RETRIES):
- cmd = ['devices']
- if long_list:
- cmd.append('-l')
- output = cls._RunAdbCmd(cmd, timeout=timeout, retries=retries)
- return [line.split() for line in output.splitlines()[1:]]
-
- def GetDeviceSerial(self):
- """Gets the device serial number associated with this object.
-
- Returns:
- Device serial number as a string.
- """
- return self._device_serial
-
- def Push(self, local, remote, timeout=60 * 5, retries=DEFAULT_RETRIES):
- """Pushes a file from the host to the device.
-
- Args:
- local: Path on the host filesystem.
- remote: Path on the device filesystem.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
- """
- VerifyLocalFileExists(local)
-
- if (distutils.version.LooseVersion(self.Version()) <
- distutils.version.LooseVersion('1.0.36')):
-
- # Different versions of adb handle pushing a directory to an existing
- # directory differently.
-
- # In the version packaged with the M SDK, 1.0.32, the following push:
- # foo/bar -> /sdcard/foo/bar
- # where bar is an existing directory both on the host and the device
- # results in the contents of bar/ on the host being pushed to bar/ on
- # the device, i.e.
- # foo/bar/A -> /sdcard/foo/bar/A
- # foo/bar/B -> /sdcard/foo/bar/B
- # ... etc.
-
- # In the version packaged with the N SDK, 1.0.36, the same push under
- # the same conditions results in a second bar/ directory being created
- # underneath the first bar/ directory on the device, i.e.
- # foo/bar/A -> /sdcard/foo/bar/bar/A
- # foo/bar/B -> /sdcard/foo/bar/bar/B
- # ... etc.
-
- # In order to provide a consistent interface to clients, we check whether
- # the target is an existing directory on the device and, if so, modifies
- # the target passed to adb to emulate the behavior on 1.0.36 and above.
-
- # Note that this behavior may have started before 1.0.36; that's simply
- # the earliest version we've confirmed thus far.
-
- try:
- self.Shell('test -d %s' % remote, timeout=timeout, retries=retries)
- remote = posixpath.join(remote, posixpath.basename(local))
- except device_errors.AdbShellCommandFailedError:
- # The target directory doesn't exist on the device, so we can use it
- # without modification.
- pass
-
- self._RunDeviceAdbCmd(['push', local, remote], timeout, retries)
-
- def Pull(self, remote, local, timeout=60 * 5, retries=DEFAULT_RETRIES):
- """Pulls a file from the device to the host.
-
- Args:
- remote: Path on the device filesystem.
- local: Path on the host filesystem.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
- """
- cmd = ['pull', remote, local]
- self._RunDeviceAdbCmd(cmd, timeout, retries)
- try:
- VerifyLocalFileExists(local)
- except IOError:
- raise device_errors.AdbCommandFailedError(
- cmd,
- 'File pulled from the device did not arrive on the host: %s' % local,
- device_serial=str(self))
-
- def Shell(self, command, expect_status=0, timeout=DEFAULT_TIMEOUT,
- retries=DEFAULT_RETRIES):
- """Runs a shell command on the device.
-
- Args:
- command: A string with the shell command to run.
- expect_status: (optional) Check that the command's exit status matches
- this value. Default is 0. If set to None the test is skipped.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-
- Returns:
- The output of the shell command as a string.
-
- Raises:
- device_errors.AdbCommandFailedError: If the exit status doesn't match
- |expect_status|.
- """
- if expect_status is None:
- args = ['shell', command]
- else:
- args = ['shell', '( %s );echo %%$?' % command.rstrip()]
- output = self._RunDeviceAdbCmd(args, timeout, retries, check_error=False)
- if expect_status is not None:
- output_end = output.rfind('%')
- if output_end < 0:
- # causes the status string to become empty and raise a ValueError
- output_end = len(output)
-
- try:
- status = int(output[output_end + 1:])
- except ValueError:
- logger.warning('exit status of shell command %r missing.', command)
- raise device_errors.AdbShellCommandFailedError(
- command, output, status=None, device_serial=self._device_serial)
- output = output[:output_end]
- if status != expect_status:
- raise device_errors.AdbShellCommandFailedError(
- command, output, status=status, device_serial=self._device_serial)
- return output
-
- def IterShell(self, command, timeout):
- """Runs a shell command and returns an iterator over its output lines.
-
- Args:
- command: A string with the shell command to run.
- timeout: Timeout in seconds.
-
- Yields:
- The output of the command line by line.
- """
- args = ['shell', command]
- return cmd_helper.IterCmdOutputLines(
- self._BuildAdbCmd(args, self._device_serial), timeout=timeout)
-
- def Ls(self, path, timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES):
- """List the contents of a directory on the device.
-
- Args:
- path: Path on the device filesystem.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-
- Returns:
- A list of pairs (filename, stat) for each file found in the directory,
- where the stat object has the properties: st_mode, st_size, and st_time.
-
- Raises:
- AdbCommandFailedError if |path| does not specify a valid and accessible
- directory in the device, or the output of "adb ls" command is less
- than four columns
- """
- def ParseLine(line, cmd):
- cols = line.split(None, 3)
- if len(cols) < 4:
- raise device_errors.AdbCommandFailedError(
- cmd, line, "the output should be 4 columns, but is only %d columns"
- % len(cols), device_serial=self._device_serial)
- filename = cols.pop()
- stat = DeviceStat(*[int(num, base=16) for num in cols])
- return (filename, stat)
-
- cmd = ['ls', path]
- lines = self._RunDeviceAdbCmd(
- cmd, timeout=timeout, retries=retries).splitlines()
- if lines:
- return [ParseLine(line, cmd) for line in lines]
- else:
- raise device_errors.AdbCommandFailedError(
- cmd, 'path does not specify an accessible directory in the device',
- device_serial=self._device_serial)
-
- def Logcat(self, clear=False, dump=False, filter_specs=None,
- logcat_format=None, ring_buffer=None, iter_timeout=None,
- timeout=None, retries=DEFAULT_RETRIES):
- """Get an iterable over the logcat output.
-
- Args:
- clear: If true, clear the logcat.
- dump: If true, dump the current logcat contents.
- filter_specs: If set, a list of specs to filter the logcat.
- logcat_format: If set, the format in which the logcat should be output.
- Options include "brief", "process", "tag", "thread", "raw", "time",
- "threadtime", and "long"
- ring_buffer: If set, a list of alternate ring buffers to request.
- Options include "main", "system", "radio", "events", "crash" or "all".
- The default is equivalent to ["main", "system", "crash"].
- iter_timeout: If set and neither clear nor dump is set, the number of
- seconds to wait between iterations. If no line is found before the
- given number of seconds elapses, the iterable will yield None.
- timeout: (optional) If set, timeout per try in seconds. If clear or dump
- is set, defaults to DEFAULT_TIMEOUT.
- retries: (optional) If clear or dump is set, the number of retries to
- attempt. Otherwise, does nothing.
-
- Yields:
- logcat output line by line.
- """
- cmd = ['logcat']
- use_iter = True
- if clear:
- cmd.append('-c')
- use_iter = False
- if dump:
- cmd.append('-d')
- use_iter = False
- if logcat_format:
- cmd.extend(['-v', logcat_format])
- if ring_buffer:
- for buffer_name in ring_buffer:
- cmd.extend(['-b', buffer_name])
- if filter_specs:
- cmd.extend(filter_specs)
-
- if use_iter:
- return self._IterRunDeviceAdbCmd(cmd, iter_timeout, timeout)
- else:
- timeout = timeout if timeout is not None else DEFAULT_TIMEOUT
- return self._RunDeviceAdbCmd(cmd, timeout, retries).splitlines()
-
- def Forward(self, local, remote, allow_rebind=False,
- timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES):
- """Forward socket connections from the local socket to the remote socket.
-
- Sockets are specified by one of:
- tcp:<port>
- localabstract:<unix domain socket name>
- localreserved:<unix domain socket name>
- localfilesystem:<unix domain socket name>
- dev:<character device name>
- jdwp:<process pid> (remote only)
-
- Args:
- local: The host socket.
- remote: The device socket.
- allow_rebind: A boolean indicating whether adb may rebind a local socket;
- otherwise, the default, an exception is raised if the local socket is
- already being forwarded.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
- """
- cmd = ['forward']
- if not allow_rebind:
- cmd.append('--no-rebind')
- cmd.extend([str(local), str(remote)])
- self._RunDeviceAdbCmd(cmd, timeout, retries)
-
- def ForwardRemove(self, local, timeout=DEFAULT_TIMEOUT,
- retries=DEFAULT_RETRIES):
- """Remove a forward socket connection.
-
- Args:
- local: The host socket.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
- """
- self._RunDeviceAdbCmd(['forward', '--remove', str(local)], timeout,
- retries)
-
- def ForwardList(self, timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES):
- """List all currently forwarded socket connections.
-
- Args:
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
- Returns:
- The output of adb forward --list as a string.
- """
- if (distutils.version.LooseVersion(self.Version()) >=
- distutils.version.LooseVersion('1.0.36')):
- # Starting in 1.0.36, this can occasionally fail with a protocol fault.
- # As this interrupts all connections with all devices, we instead just
- # return an empty list. This may give clients an inaccurate result, but
- # that's usually better than crashing the adb server.
-
- # TODO(jbudorick): Determine an appropriate upper version bound for this
- # once b/31811775 is fixed.
- return ''
-
- return self._RunDeviceAdbCmd(['forward', '--list'], timeout, retries)
-
- def JDWP(self, timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES):
- """List of PIDs of processes hosting a JDWP transport.
-
- Args:
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-
- Returns:
- A list of PIDs as strings.
- """
- return [a.strip() for a in
- self._RunDeviceAdbCmd(['jdwp'], timeout, retries).split('\n')]
-
- def Install(self, apk_path, forward_lock=False, allow_downgrade=False,
- reinstall=False, sd_card=False, timeout=60 * 2,
- retries=DEFAULT_RETRIES):
- """Install an apk on the device.
-
- Args:
- apk_path: Host path to the APK file.
- forward_lock: (optional) If set forward-locks the app.
- allow_downgrade: (optional) If set, allows for downgrades.
- reinstall: (optional) If set reinstalls the app, keeping its data.
- sd_card: (optional) If set installs on the SD card.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
- """
- VerifyLocalFileExists(apk_path)
- cmd = ['install']
- if forward_lock:
- cmd.append('-l')
- if reinstall:
- cmd.append('-r')
- if sd_card:
- cmd.append('-s')
- if allow_downgrade:
- cmd.append('-d')
- cmd.append(apk_path)
- output = self._RunDeviceAdbCmd(cmd, timeout, retries)
- if 'Success' not in output:
- raise device_errors.AdbCommandFailedError(
- cmd, output, device_serial=self._device_serial)
-
- def InstallMultiple(self, apk_paths, forward_lock=False, reinstall=False,
- sd_card=False, allow_downgrade=False, partial=False,
- timeout=60 * 2, retries=DEFAULT_RETRIES):
- """Install an apk with splits on the device.
-
- Args:
- apk_paths: Host path to the APK file.
- forward_lock: (optional) If set forward-locks the app.
- reinstall: (optional) If set reinstalls the app, keeping its data.
- sd_card: (optional) If set installs on the SD card.
- allow_downgrade: (optional) Allow versionCode downgrade.
- partial: (optional) Package ID if apk_paths doesn't include all .apks.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
- """
- for path in apk_paths:
- VerifyLocalFileExists(path)
- cmd = ['install-multiple']
- if forward_lock:
- cmd.append('-l')
- if reinstall:
- cmd.append('-r')
- if sd_card:
- cmd.append('-s')
- if allow_downgrade:
- cmd.append('-d')
- if partial:
- cmd.extend(('-p', partial))
- cmd.extend(apk_paths)
- output = self._RunDeviceAdbCmd(cmd, timeout, retries)
- if 'Success' not in output:
- raise device_errors.AdbCommandFailedError(
- cmd, output, device_serial=self._device_serial)
-
- def Uninstall(self, package, keep_data=False, timeout=DEFAULT_TIMEOUT,
- retries=DEFAULT_RETRIES):
- """Remove the app |package| from the device.
-
- Args:
- package: The package to uninstall.
- keep_data: (optional) If set keep the data and cache directories.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
- """
- cmd = ['uninstall']
- if keep_data:
- cmd.append('-k')
- cmd.append(package)
- output = self._RunDeviceAdbCmd(cmd, timeout, retries)
- if 'Failure' in output or 'Exception' in output:
- raise device_errors.AdbCommandFailedError(
- cmd, output, device_serial=self._device_serial)
-
- def Backup(self, path, packages=None, apk=False, shared=False,
- nosystem=True, include_all=False, timeout=DEFAULT_TIMEOUT,
- retries=DEFAULT_RETRIES):
- """Write an archive of the device's data to |path|.
-
- Args:
- path: Local path to store the backup file.
- packages: List of to packages to be backed up.
- apk: (optional) If set include the .apk files in the archive.
- shared: (optional) If set buckup the device's SD card.
- nosystem: (optional) If set exclude system applications.
- include_all: (optional) If set back up all installed applications and
- |packages| is optional.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
- """
- cmd = ['backup', '-f', path]
- if apk:
- cmd.append('-apk')
- if shared:
- cmd.append('-shared')
- if nosystem:
- cmd.append('-nosystem')
- if include_all:
- cmd.append('-all')
- if packages:
- cmd.extend(packages)
- assert bool(packages) ^ bool(include_all), (
- 'Provide \'packages\' or set \'include_all\' but not both.')
- ret = self._RunDeviceAdbCmd(cmd, timeout, retries)
- VerifyLocalFileExists(path)
- return ret
-
- def Restore(self, path, timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES):
- """Restore device contents from the backup archive.
-
- Args:
- path: Host path to the backup archive.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
- """
- VerifyLocalFileExists(path)
- self._RunDeviceAdbCmd(['restore'] + [path], timeout, retries)
-
- def WaitForDevice(self, timeout=60 * 5, retries=DEFAULT_RETRIES):
- """Block until the device is online.
-
- Args:
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
- """
- self._RunDeviceAdbCmd(['wait-for-device'], timeout, retries)
-
- def GetState(self, timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES):
- """Get device state.
-
- Args:
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-
- Returns:
- One of 'offline', 'bootloader', or 'device'.
- """
- # TODO(jbudorick): Revert to using get-state once it doesn't cause a
- # a protocol fault.
- # return self._RunDeviceAdbCmd(['get-state'], timeout, retries).strip()
-
- lines = self._RawDevices(timeout=timeout, retries=retries)
- for line in lines:
- if len(line) >= 2 and line[0] == self._device_serial:
- return line[1]
- return 'offline'
-
- def GetDevPath(self, timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES):
- """Gets the device path.
-
- Args:
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-
- Returns:
- The device path (e.g. usb:3-4)
- """
- return self._RunDeviceAdbCmd(['get-devpath'], timeout, retries)
-
- def Remount(self, timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES):
- """Remounts the /system partition on the device read-write."""
- self._RunDeviceAdbCmd(['remount'], timeout, retries)
-
- def Reboot(self, to_bootloader=False, timeout=60 * 5,
- retries=DEFAULT_RETRIES):
- """Reboots the device.
-
- Args:
- to_bootloader: (optional) If set reboots to the bootloader.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
- """
- if to_bootloader:
- cmd = ['reboot-bootloader']
- else:
- cmd = ['reboot']
- self._RunDeviceAdbCmd(cmd, timeout, retries)
-
- def Root(self, timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES):
- """Restarts the adbd daemon with root permissions, if possible.
-
- Args:
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
- """
- output = self._RunDeviceAdbCmd(['root'], timeout, retries)
- if 'cannot' in output:
- raise device_errors.AdbCommandFailedError(
- ['root'], output, device_serial=self._device_serial)
-
- def Emu(self, cmd, timeout=DEFAULT_TIMEOUT,
- retries=DEFAULT_RETRIES):
- """Runs an emulator console command.
-
- See http://developer.android.com/tools/devices/emulator.html#console
-
- Args:
- cmd: The command to run on the emulator console.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-
- Returns:
- The output of the emulator console command.
- """
- if isinstance(cmd, basestring):
- cmd = [cmd]
- return self._RunDeviceAdbCmd(['emu'] + cmd, timeout, retries)
-
- def DisableVerity(self, timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES):
- """Disable Marshmallow's Verity security feature"""
- output = self._RunDeviceAdbCmd(['disable-verity'], timeout, retries)
- if output and not _VERITY_DISABLE_RE.search(output):
- raise device_errors.AdbCommandFailedError(
- ['disable-verity'], output, device_serial=self._device_serial)
-
- def EnableVerity(self, timeout=DEFAULT_TIMEOUT, retries=DEFAULT_RETRIES):
- """Enable Marshmallow's Verity security feature"""
- output = self._RunDeviceAdbCmd(['enable-verity'], timeout, retries)
- if output and not _VERITY_ENABLE_RE.search(output):
- raise device_errors.AdbCommandFailedError(
- ['enable-verity'], output, device_serial=self._device_serial)
-
- @property
- def is_emulator(self):
- return _EMULATOR_RE.match(self._device_serial)
-
- @property
- def is_ready(self):
- try:
- return self.GetState() == _READY_STATE
- except device_errors.CommandFailedError:
- return False
diff --git a/third_party/catapult/devil/devil/android/sdk/adb_wrapper_devicetest.py b/third_party/catapult/devil/devil/android/sdk/adb_wrapper_devicetest.py
deleted file mode 100755
index d97d56a1a2..0000000000
--- a/third_party/catapult/devil/devil/android/sdk/adb_wrapper_devicetest.py
+++ /dev/null
@@ -1,118 +0,0 @@
-#!/usr/bin/env python
-
-# 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.
-
-"""Tests for the AdbWrapper class."""
-
-import os
-import tempfile
-import time
-import unittest
-
-from devil.android import device_test_case
-from devil.android import device_errors
-from devil.android.sdk import adb_wrapper
-
-
-class TestAdbWrapper(device_test_case.DeviceTestCase):
-
- def setUp(self):
- super(TestAdbWrapper, self).setUp()
- self._adb = adb_wrapper.AdbWrapper(self.serial)
- self._adb.WaitForDevice()
-
- @staticmethod
- def _MakeTempFile(contents):
- """Make a temporary file with the given contents.
-
- Args:
- contents: string to write to the temporary file.
-
- Returns:
- The absolute path to the file.
- """
- fi, path = tempfile.mkstemp()
- with os.fdopen(fi, 'wb') as f:
- f.write(contents)
- return path
-
- def testShell(self):
- output = self._adb.Shell('echo test', expect_status=0)
- self.assertEqual(output.strip(), 'test')
- output = self._adb.Shell('echo test')
- self.assertEqual(output.strip(), 'test')
- with self.assertRaises(device_errors.AdbCommandFailedError):
- self._adb.Shell('echo test', expect_status=1)
-
- @unittest.skip("https://github.com/catapult-project/catapult/issues/2574")
- def testPersistentShell(self):
- # We need to access the device serial number here in order
- # to create the persistent shell.
- serial = self._adb.GetDeviceSerial() # pylint: disable=protected-access
- with self._adb.PersistentShell(serial) as pshell:
- (res1, code1) = pshell.RunCommand('echo TEST')
- (res2, code2) = pshell.RunCommand('echo TEST2')
- self.assertEqual(len(res1), 1)
- self.assertEqual(res1[0], 'TEST')
- self.assertEqual(res2[-1], 'TEST2')
- self.assertEqual(code1, 0)
- self.assertEqual(code2, 0)
-
- def testPushLsPull(self):
- path = self._MakeTempFile('foo')
- device_path = '/data/local/tmp/testfile.txt'
- local_tmpdir = os.path.dirname(path)
- self._adb.Push(path, device_path)
- files = dict(self._adb.Ls('/data/local/tmp'))
- self.assertTrue('testfile.txt' in files)
- self.assertEquals(3, files['testfile.txt'].st_size)
- self.assertEqual(self._adb.Shell('cat %s' % device_path), 'foo')
- self._adb.Pull(device_path, local_tmpdir)
- with open(os.path.join(local_tmpdir, 'testfile.txt'), 'r') as f:
- self.assertEqual(f.read(), 'foo')
-
- def testInstall(self):
- path = self._MakeTempFile('foo')
- with self.assertRaises(device_errors.AdbCommandFailedError):
- self._adb.Install(path)
-
- def testForward(self):
- with self.assertRaises(device_errors.AdbCommandFailedError):
- self._adb.Forward(0, 0)
-
- def testUninstall(self):
- with self.assertRaises(device_errors.AdbCommandFailedError):
- self._adb.Uninstall('some.nonexistant.package')
-
- def testRebootWaitForDevice(self):
- self._adb.Reboot()
- print 'waiting for device to reboot...'
- while self._adb.GetState() == 'device':
- time.sleep(1)
- self._adb.WaitForDevice()
- self.assertEqual(self._adb.GetState(), 'device')
- print 'waiting for package manager...'
- while True:
- try:
- android_path = self._adb.Shell('pm path android')
- except device_errors.AdbShellCommandFailedError:
- android_path = None
- if android_path and 'package:' in android_path:
- break
- time.sleep(1)
-
- def testRootRemount(self):
- self._adb.Root()
- while True:
- try:
- self._adb.Shell('start')
- break
- except device_errors.AdbCommandFailedError:
- time.sleep(1)
- self._adb.Remount()
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/third_party/catapult/devil/devil/android/sdk/adb_wrapper_test.py b/third_party/catapult/devil/devil/android/sdk/adb_wrapper_test.py
deleted file mode 100755
index ef08661208..0000000000
--- a/third_party/catapult/devil/devil/android/sdk/adb_wrapper_test.py
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""
-Unit tests for some APIs with conditional logic in adb_wrapper.py
-"""
-
-import unittest
-
-from devil import devil_env
-from devil.android import device_errors
-from devil.android.sdk import adb_wrapper
-
-with devil_env.SysPath(devil_env.PYMOCK_PATH):
- import mock # pylint: disable=import-error
-
-
-class AdbWrapperTest(unittest.TestCase):
- def setUp(self):
- self.adb = adb_wrapper.AdbWrapper('ABC12345678')
-
- def _MockRunDeviceAdbCmd(self, return_value):
- return mock.patch.object(
- self.adb,
- '_RunDeviceAdbCmd',
- mock.Mock(side_effect=None, return_value=return_value))
-
- def testDisableVerityWhenDisabled(self):
- with self._MockRunDeviceAdbCmd('Verity already disabled on /system'):
- self.adb.DisableVerity()
-
- def testDisableVerityWhenEnabled(self):
- with self._MockRunDeviceAdbCmd(
- 'Verity disabled on /system\nNow reboot your device for settings to '
- 'take effect'):
- self.adb.DisableVerity()
-
- def testEnableVerityWhenEnabled(self):
- with self._MockRunDeviceAdbCmd('Verity already enabled on /system'):
- self.adb.EnableVerity()
-
- def testEnableVerityWhenDisabled(self):
- with self._MockRunDeviceAdbCmd(
- 'Verity enabled on /system\nNow reboot your device for settings to '
- 'take effect'):
- self.adb.EnableVerity()
-
- def testFailEnableVerity(self):
- with self._MockRunDeviceAdbCmd('error: closed'):
- self.assertRaises(
- device_errors.AdbCommandFailedError, self.adb.EnableVerity)
-
- def testFailDisableVerity(self):
- with self._MockRunDeviceAdbCmd('error: closed'):
- self.assertRaises(
- device_errors.AdbCommandFailedError, self.adb.DisableVerity)
-
diff --git a/third_party/catapult/devil/devil/android/sdk/build_tools.py b/third_party/catapult/devil/devil/android/sdk/build_tools.py
deleted file mode 100644
index 99083d9904..0000000000
--- a/third_party/catapult/devil/devil/android/sdk/build_tools.py
+++ /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.
-
-import os
-
-from devil import devil_env
-from devil.utils import lazy
-
-with devil_env.SysPath(devil_env.DEPENDENCY_MANAGER_PATH):
- import dependency_manager # pylint: disable=import-error
-
-
-def GetPath(build_tool):
- try:
- return devil_env.config.LocalPath(build_tool)
- except dependency_manager.NoPathFoundError:
- pass
-
- try:
- return _PathInLocalSdk(build_tool)
- except dependency_manager.NoPathFoundError:
- pass
-
- return devil_env.config.FetchPath(build_tool)
-
-
-def _PathInLocalSdk(build_tool):
- build_tools_path = _build_tools_path.read()
- return (os.path.join(build_tools_path, build_tool) if build_tools_path
- else None)
-
-
-def _FindBuildTools():
- android_sdk_path = devil_env.config.LocalPath('android_sdk')
- if not android_sdk_path:
- return None
-
- build_tools_contents = os.listdir(
- os.path.join(android_sdk_path, 'build-tools'))
-
- if not build_tools_contents:
- return None
- else:
- if len(build_tools_contents) > 1:
- build_tools_contents.sort()
- return os.path.join(android_sdk_path, 'build-tools',
- build_tools_contents[-1])
-
-
-_build_tools_path = lazy.WeakConstant(_FindBuildTools)
diff --git a/third_party/catapult/devil/devil/android/sdk/dexdump.py b/third_party/catapult/devil/devil/android/sdk/dexdump.py
deleted file mode 100644
index 992366e84a..0000000000
--- a/third_party/catapult/devil/devil/android/sdk/dexdump.py
+++ /dev/null
@@ -1,31 +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.
-
-from devil.android.sdk import build_tools
-from devil.utils import cmd_helper
-from devil.utils import lazy
-
-
-_dexdump_path = lazy.WeakConstant(lambda: build_tools.GetPath('dexdump'))
-
-
-def DexDump(dexfiles, file_summary=False):
- """A wrapper around the Android SDK's dexdump tool.
-
- Args:
- dexfiles: The dexfile or list of dex files to dump.
- file_summary: Display summary information from the file header. (-f)
-
- Returns:
- An iterable over the output lines.
- """
- # TODO(jbudorick): Add support for more options as necessary.
- if isinstance(dexfiles, basestring):
- dexfiles = [dexfiles]
- args = [_dexdump_path.read()] + dexfiles
- if file_summary:
- args.append('-f')
-
- return cmd_helper.IterCmdOutputLines(args)
-
diff --git a/third_party/catapult/devil/devil/android/sdk/fastboot.py b/third_party/catapult/devil/devil/android/sdk/fastboot.py
deleted file mode 100644
index d7f9f624bb..0000000000
--- a/third_party/catapult/devil/devil/android/sdk/fastboot.py
+++ /dev/null
@@ -1,98 +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.
-
-"""This module wraps Android's fastboot tool.
-
-This is a thin wrapper around the fastboot interface. Any additional complexity
-should be delegated to a higher level (ex. FastbootUtils).
-"""
-# pylint: disable=unused-argument
-
-from devil import devil_env
-from devil.android import decorators
-from devil.android import device_errors
-from devil.utils import cmd_helper
-from devil.utils import lazy
-
-_DEFAULT_TIMEOUT = 30
-_DEFAULT_RETRIES = 3
-_FLASH_TIMEOUT = _DEFAULT_TIMEOUT * 10
-
-
-class Fastboot(object):
-
- _fastboot_path = lazy.WeakConstant(
- lambda: devil_env.config.FetchPath('fastboot'))
-
- def __init__(self, device_serial, default_timeout=_DEFAULT_TIMEOUT,
- default_retries=_DEFAULT_RETRIES):
- """Initializes the FastbootWrapper.
-
- Args:
- device_serial: The device serial number as a string.
- """
- if not device_serial:
- raise ValueError('A device serial must be specified')
- self._device_serial = str(device_serial)
- self._default_timeout = default_timeout
- self._default_retries = default_retries
-
- def _RunFastbootCommand(self, cmd):
- """Run a command line command using the fastboot android tool.
-
- Args:
- cmd: Command to run. Must be list of args, the first one being the command
-
- Returns:
- output of command.
-
- Raises:
- TypeError: If cmd is not of type list.
- """
- if type(cmd) == list:
- cmd = [self._fastboot_path.read(), '-s', self._device_serial] + cmd
- else:
- raise TypeError(
- 'Command for _RunFastbootCommand must be a list.')
- status, output = cmd_helper.GetCmdStatusAndOutput(cmd)
- if int(status) != 0:
- raise device_errors.FastbootCommandFailedError(
- cmd, output, status, self._device_serial)
- return output
-
- @decorators.WithTimeoutAndRetriesDefaults(_FLASH_TIMEOUT, 0)
- def Flash(self, partition, image, timeout=None, retries=None):
- """Flash partition with img.
-
- Args:
- partition: Partition to be flashed.
- image: location of image to flash with.
- """
- self._RunFastbootCommand(['flash', partition, image])
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def Devices(self, timeout=None, retries=None):
- """Outputs list of devices in fastboot mode."""
- output = self._RunFastbootCommand(['devices'])
- return [line.split()[0] for line in output.splitlines()]
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def RebootBootloader(self, timeout=None, retries=None):
- """Reboot from fastboot, into fastboot."""
- self._RunFastbootCommand(['reboot-bootloader'])
-
- @decorators.WithTimeoutAndRetriesDefaults(_FLASH_TIMEOUT, 0)
- def Reboot(self, timeout=None, retries=None):
- """Reboot from fastboot to normal usage"""
- self._RunFastbootCommand(['reboot'])
-
- @decorators.WithTimeoutAndRetriesFromInstance()
- def SetOemOffModeCharge(self, value, timeout=None, retries=None):
- """Sets off mode charging
-
- Args:
- value: boolean value to set off-mode-charging on or off.
- """
- self._RunFastbootCommand(
- ['oem', 'off-mode-charge', str(int(value))])
diff --git a/third_party/catapult/devil/devil/android/sdk/gce_adb_wrapper.py b/third_party/catapult/devil/devil/android/sdk/gce_adb_wrapper.py
deleted file mode 100644
index 71600f40a6..0000000000
--- a/third_party/catapult/devil/devil/android/sdk/gce_adb_wrapper.py
+++ /dev/null
@@ -1,154 +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.
-
-"""Provides a work around for various adb commands on android gce instances.
-
-Some adb commands don't work well when the device is a cloud vm, namely
-'push' and 'pull'. With gce instances, moving files through adb can be
-painfully slow and hit timeouts, so the methods here just use scp instead.
-"""
-# pylint: disable=unused-argument
-
-import logging
-import os
-import subprocess
-
-from devil.android import device_errors
-from devil.android.sdk import adb_wrapper
-from devil.utils import cmd_helper
-
-logger = logging.getLogger(__name__)
-
-
-class GceAdbWrapper(adb_wrapper.AdbWrapper):
-
- def __init__(self, device_serial):
- super(GceAdbWrapper, self).__init__(device_serial)
- self._Connect()
- self.Root()
- self._instance_ip = self.Shell('getprop net.gce.ip').strip()
-
- def _Connect(self, timeout=adb_wrapper.DEFAULT_TIMEOUT,
- retries=adb_wrapper.DEFAULT_RETRIES):
- """Connects ADB to the android gce instance."""
- cmd = ['connect', self._device_serial]
- output = self._RunAdbCmd(cmd, timeout=timeout, retries=retries)
- if 'unable to connect' in output:
- raise device_errors.AdbCommandFailedError(cmd, output)
- self.WaitForDevice()
-
- # override
- def Root(self, **kwargs):
- super(GceAdbWrapper, self).Root()
- self._Connect()
-
- # override
- def Push(self, local, remote, **kwargs):
- """Pushes an object from the host to the gce instance.
-
- Args:
- local: Path on the host filesystem.
- remote: Path on the instance filesystem.
- """
- adb_wrapper.VerifyLocalFileExists(local)
- if os.path.isdir(local):
- self.Shell('mkdir -p %s' % cmd_helper.SingleQuote(remote))
-
- # When the object to be pushed is a directory, adb merges the source dir
- # with the destination dir. So if local is a dir, just scp its contents.
- for f in os.listdir(local):
- self._PushObject(os.path.join(local, f), os.path.join(remote, f))
- self.Shell('chmod 777 %s' %
- cmd_helper.SingleQuote(os.path.join(remote, f)))
- else:
- parent_dir = remote[0:remote.rfind('/')]
- if parent_dir:
- self.Shell('mkdir -p %s' % cmd_helper.SingleQuote(parent_dir))
- self._PushObject(local, remote)
- self.Shell('chmod 777 %s' % cmd_helper.SingleQuote(remote))
-
- def _PushObject(self, local, remote):
- """Copies an object from the host to the gce instance using scp.
-
- Args:
- local: Path on the host filesystem.
- remote: Path on the instance filesystem.
- """
- cmd = [
- 'scp',
- '-r',
- '-o', 'UserKnownHostsFile=/dev/null',
- '-o', 'StrictHostKeyChecking=no',
- local,
- 'root@%s:%s' % (self._instance_ip, remote)
- ]
- status, _ = cmd_helper.GetCmdStatusAndOutput(cmd)
- if status:
- raise device_errors.AdbCommandFailedError(
- cmd, 'File not reachable on host: %s' % local,
- device_serial=str(self))
-
- # override
- def Pull(self, remote, local, **kwargs):
- """Pulls a file from the gce instance to the host.
-
- Args:
- remote: Path on the instance filesystem.
- local: Path on the host filesystem.
- """
- cmd = [
- 'scp',
- '-p',
- '-r',
- '-o', 'UserKnownHostsFile=/dev/null',
- '-o', 'StrictHostKeyChecking=no',
- 'root@%s:%s' % (self._instance_ip, remote),
- local,
- ]
- status, _ = cmd_helper.GetCmdStatusAndOutput(cmd)
- if status:
- raise device_errors.AdbCommandFailedError(
- cmd, 'File not reachable on host: %s' % local,
- device_serial=str(self))
-
- try:
- adb_wrapper.VerifyLocalFileExists(local)
- except (subprocess.CalledProcessError, IOError):
- logger.exception('Error when pulling files from android instance.')
- raise device_errors.AdbCommandFailedError(
- cmd, 'File not reachable on host: %s' % local,
- device_serial=str(self))
-
- # override
- def Install(self, apk_path, forward_lock=False, reinstall=False,
- sd_card=False, **kwargs):
- """Installs an apk on the gce instance
-
- Args:
- apk_path: Host path to the APK file.
- forward_lock: (optional) If set forward-locks the app.
- reinstall: (optional) If set reinstalls the app, keeping its data.
- sd_card: (optional) If set installs on the SD card.
- """
- adb_wrapper.VerifyLocalFileExists(apk_path)
- cmd = ['install']
- if forward_lock:
- cmd.append('-l')
- if reinstall:
- cmd.append('-r')
- if sd_card:
- cmd.append('-s')
- self.Push(apk_path, '/data/local/tmp/tmp.apk')
- cmd = ['pm'] + cmd
- cmd.append('/data/local/tmp/tmp.apk')
- output = self.Shell(' '.join(cmd))
- self.Shell('rm /data/local/tmp/tmp.apk')
- if 'Success' not in output:
- raise device_errors.AdbCommandFailedError(
- cmd, output, device_serial=self._device_serial)
-
- # override
- @property
- def is_emulator(self):
- return True
diff --git a/third_party/catapult/devil/devil/android/sdk/intent.py b/third_party/catapult/devil/devil/android/sdk/intent.py
deleted file mode 100644
index cdefb4632f..0000000000
--- a/third_party/catapult/devil/devil/android/sdk/intent.py
+++ /dev/null
@@ -1,129 +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.
-
-"""Manages intents and associated information.
-
-This is generally intended to be used with functions that calls Android's
-Am command.
-"""
-
-# Some common flag constants that can be used to construct intents.
-# Full list: http://developer.android.com/reference/android/content/Intent.html
-FLAG_ACTIVITY_CLEAR_TASK = 0x00008000
-FLAG_ACTIVITY_CLEAR_TOP = 0x04000000
-FLAG_ACTIVITY_NEW_TASK = 0x10000000
-FLAG_ACTIVITY_REORDER_TO_FRONT = 0x00020000
-FLAG_ACTIVITY_RESET_TASK_IF_NEEDED = 0x00200000
-
-
-def _bitwise_or(flags):
- result = 0
- for flag in flags:
- result |= flag
- return result
-
-
-class Intent(object):
-
- def __init__(self, action='android.intent.action.VIEW', activity=None,
- category=None, component=None, data=None, extras=None,
- flags=None, package=None):
- """Creates an Intent.
-
- Args:
- action: A string containing the action.
- activity: A string that, with |package|, can be used to specify the
- component.
- category: A string or list containing any categories.
- component: A string that specifies the component to send the intent to.
- data: A string containing a data URI.
- extras: A dict containing extra parameters to be passed along with the
- intent.
- flags: A sequence of flag constants to be passed with the intent.
- package: A string that, with activity, can be used to specify the
- component.
- """
- self._action = action
- self._activity = activity
- if isinstance(category, list) or category is None:
- self._category = category
- else:
- self._category = [category]
- self._component = component
- self._data = data
- self._extras = extras
- self._flags = '0x%0.8x' % _bitwise_or(flags) if flags else None
- self._package = package
-
- if self._component and '/' in component:
- self._package, self._activity = component.split('/', 1)
- elif self._package and self._activity:
- self._component = '%s/%s' % (package, activity)
-
- @property
- def action(self):
- return self._action
-
- @property
- def activity(self):
- return self._activity
-
- @property
- def category(self):
- return self._category
-
- @property
- def component(self):
- return self._component
-
- @property
- def data(self):
- return self._data
-
- @property
- def extras(self):
- return self._extras
-
- @property
- def flags(self):
- return self._flags
-
- @property
- def package(self):
- return self._package
-
- @property
- def am_args(self):
- """Returns the intent as a list of arguments for the activity manager.
-
- For details refer to the specification at:
- - http://developer.android.com/tools/help/adb.html#IntentSpec
- """
- args = []
- if self.action:
- args.extend(['-a', self.action])
- if self.data:
- args.extend(['-d', self.data])
- if self.category:
- args.extend(arg for cat in self.category for arg in ('-c', cat))
- if self.component:
- args.extend(['-n', self.component])
- if self.flags:
- args.extend(['-f', self.flags])
- if self.extras:
- for key, value in self.extras.iteritems():
- if value is None:
- args.extend(['--esn', key])
- elif isinstance(value, str):
- args.extend(['--es', key, value])
- elif isinstance(value, bool):
- args.extend(['--ez', key, str(value)])
- elif isinstance(value, int):
- args.extend(['--ei', key, str(value)])
- elif isinstance(value, float):
- args.extend(['--ef', key, str(value)])
- else:
- raise NotImplementedError(
- 'Intent does not know how to pass %s extras' % type(value))
- return args
diff --git a/third_party/catapult/devil/devil/android/sdk/keyevent.py b/third_party/catapult/devil/devil/android/sdk/keyevent.py
deleted file mode 100644
index 657dc963b9..0000000000
--- a/third_party/catapult/devil/devil/android/sdk/keyevent.py
+++ /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.
-
-"""Android KeyEvent constants.
-
-http://developer.android.com/reference/android/view/KeyEvent.html
-"""
-
-KEYCODE_BACK = 4
-
-KEYCODE_0 = 7
-KEYCODE_1 = 8
-KEYCODE_2 = 9
-KEYCODE_3 = 10
-KEYCODE_4 = 11
-KEYCODE_5 = 12
-KEYCODE_6 = 13
-KEYCODE_7 = 14
-KEYCODE_8 = 15
-KEYCODE_9 = 16
-
-KEYCODE_DPAD_RIGHT = 22
-
-KEYCODE_POWER = 26
-
-KEYCODE_A = 29
-KEYCODE_B = 30
-KEYCODE_C = 31
-KEYCODE_D = 32
-KEYCODE_E = 33
-KEYCODE_F = 34
-KEYCODE_G = 35
-KEYCODE_H = 36
-KEYCODE_I = 37
-KEYCODE_J = 38
-KEYCODE_K = 39
-KEYCODE_L = 40
-KEYCODE_M = 41
-KEYCODE_N = 42
-KEYCODE_O = 43
-KEYCODE_P = 44
-KEYCODE_Q = 45
-KEYCODE_R = 46
-KEYCODE_S = 47
-KEYCODE_T = 48
-KEYCODE_U = 49
-KEYCODE_V = 50
-KEYCODE_W = 51
-KEYCODE_X = 52
-KEYCODE_Y = 53
-KEYCODE_Z = 54
-
-KEYCODE_PERIOD = 56
-
-KEYCODE_SPACE = 62
-
-KEYCODE_ENTER = 66
-KEYCODE_DEL = 67
-
-KEYCODE_MENU = 82
-
-KEYCODE_APP_SWITCH = 187
diff --git a/third_party/catapult/devil/devil/android/sdk/shared_prefs.py b/third_party/catapult/devil/devil/android/sdk/shared_prefs.py
deleted file mode 100644
index 2fa2e6a186..0000000000
--- a/third_party/catapult/devil/devil/android/sdk/shared_prefs.py
+++ /dev/null
@@ -1,420 +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.
-
-"""Helper object to read and modify Shared Preferences from Android apps.
-
-See e.g.:
- http://developer.android.com/reference/android/content/SharedPreferences.html
-"""
-
-import logging
-import posixpath
-
-from devil.android import device_errors
-from devil.android.sdk import version_codes
-from xml.etree import ElementTree
-
-logger = logging.getLogger(__name__)
-
-
-_XML_DECLARATION = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-
-
-class BasePref(object):
- """Base class for getting/setting the value of a specific preference type.
-
- Should not be instantiated directly. The SharedPrefs collection will
- instantiate the appropriate subclasses, which directly manipulate the
- underlying xml document, to parse and serialize values according to their
- type.
-
- Args:
- elem: An xml ElementTree object holding the preference data.
-
- Properties:
- tag_name: A string with the tag that must be used for this preference type.
- """
- tag_name = None
-
- def __init__(self, elem):
- if elem.tag != type(self).tag_name:
- raise TypeError('Property %r has type %r, but trying to access as %r' %
- (elem.get('name'), elem.tag, type(self).tag_name))
- self._elem = elem
-
- def __str__(self):
- """Get the underlying xml element as a string."""
- return ElementTree.tostring(self._elem)
-
- def get(self):
- """Get the value of this preference."""
- return self._elem.get('value')
-
- def set(self, value):
- """Set from a value casted as a string."""
- self._elem.set('value', str(value))
-
- @property
- def has_value(self):
- """Check whether the element has a value."""
- return self._elem.get('value') is not None
-
-
-class BooleanPref(BasePref):
- """Class for getting/setting a preference with a boolean value.
-
- The underlying xml element has the form, e.g.:
- <boolean name="featureEnabled" value="false" />
- """
- tag_name = 'boolean'
- VALUES = {'true': True, 'false': False}
-
- def get(self):
- """Get the value as a Python bool."""
- return type(self).VALUES[super(BooleanPref, self).get()]
-
- def set(self, value):
- """Set from a value casted as a bool."""
- super(BooleanPref, self).set('true' if value else 'false')
-
-
-class FloatPref(BasePref):
- """Class for getting/setting a preference with a float value.
-
- The underlying xml element has the form, e.g.:
- <float name="someMetric" value="4.7" />
- """
- tag_name = 'float'
-
- def get(self):
- """Get the value as a Python float."""
- return float(super(FloatPref, self).get())
-
-
-class IntPref(BasePref):
- """Class for getting/setting a preference with an int value.
-
- The underlying xml element has the form, e.g.:
- <int name="aCounter" value="1234" />
- """
- tag_name = 'int'
-
- def get(self):
- """Get the value as a Python int."""
- return int(super(IntPref, self).get())
-
-
-class LongPref(IntPref):
- """Class for getting/setting a preference with a long value.
-
- The underlying xml element has the form, e.g.:
- <long name="aLongCounter" value="1234" />
-
- We use the same implementation from IntPref.
- """
- tag_name = 'long'
-
-
-class StringPref(BasePref):
- """Class for getting/setting a preference with a string value.
-
- The underlying xml element has the form, e.g.:
- <string name="someHashValue">249b3e5af13d4db2</string>
- """
- tag_name = 'string'
-
- def get(self):
- """Get the value as a Python string."""
- return self._elem.text
-
- def set(self, value):
- """Set from a value casted as a string."""
- self._elem.text = str(value)
-
-
-class StringSetPref(StringPref):
- """Class for getting/setting a preference with a set of string values.
-
- The underlying xml element has the form, e.g.:
- <set name="managed_apps">
- <string>com.mine.app1</string>
- <string>com.mine.app2</string>
- <string>com.mine.app3</string>
- </set>
- """
- tag_name = 'set'
-
- def get(self):
- """Get a list with the string values contained."""
- value = []
- for child in self._elem:
- assert child.tag == 'string'
- value.append(child.text)
- return value
-
- def set(self, value):
- """Set from a sequence of values, each casted as a string."""
- for child in list(self._elem):
- self._elem.remove(child)
- for item in value:
- ElementTree.SubElement(self._elem, 'string').text = str(item)
-
-
-_PREF_TYPES = {c.tag_name: c for c in [BooleanPref, FloatPref, IntPref,
- LongPref, StringPref, StringSetPref]}
-
-
-class SharedPrefs(object):
-
- def __init__(self, device, package, filename):
- """Helper object to read and update "Shared Prefs" of Android apps.
-
- Such files typically look like, e.g.:
-
- <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
- <map>
- <int name="databaseVersion" value="107" />
- <boolean name="featureEnabled" value="false" />
- <string name="someHashValue">249b3e5af13d4db2</string>
- </map>
-
- Example usage:
-
- prefs = shared_prefs.SharedPrefs(device, 'com.my.app', 'my_prefs.xml')
- prefs.Load()
- prefs.GetString('someHashValue') # => '249b3e5af13d4db2'
- prefs.SetInt('databaseVersion', 42)
- prefs.Remove('featureEnabled')
- prefs.Commit()
-
- The object may also be used as a context manager to automatically load and
- commit, respectively, upon entering and leaving the context.
-
- Args:
- device: A DeviceUtils object.
- package: A string with the package name of the app that owns the shared
- preferences file.
- filename: A string with the name of the preferences file to read/write.
- """
- self._device = device
- self._xml = None
- self._package = package
- self._filename = filename
- self._path = '/data/data/%s/shared_prefs/%s' % (package, filename)
- self._changed = False
-
- def __repr__(self):
- """Get a useful printable representation of the object."""
- return '<{cls} file {filename} for {package} on {device}>'.format(
- cls=type(self).__name__, filename=self.filename, package=self.package,
- device=str(self._device))
-
- def __str__(self):
- """Get the underlying xml document as a string."""
- return _XML_DECLARATION + ElementTree.tostring(self.xml)
-
- @property
- def package(self):
- """Get the package name of the app that owns the shared preferences."""
- return self._package
-
- @property
- def filename(self):
- """Get the filename of the shared preferences file."""
- return self._filename
-
- @property
- def path(self):
- """Get the full path to the shared preferences file on the device."""
- return self._path
-
- @property
- def changed(self):
- """True if properties have changed and a commit would be needed."""
- return self._changed
-
- @property
- def xml(self):
- """Get the underlying xml document as an ElementTree object."""
- if self._xml is None:
- self._xml = ElementTree.Element('map')
- return self._xml
-
- def Load(self):
- """Load the shared preferences file from the device.
-
- A empty xml document, which may be modified and saved on |commit|, is
- created if the file does not already exist.
- """
- if self._device.FileExists(self.path):
- self._xml = ElementTree.fromstring(
- self._device.ReadFile(self.path, as_root=True))
- assert self._xml.tag == 'map'
- else:
- self._xml = None
- self._changed = False
-
- def Clear(self):
- """Clear all of the preferences contained in this object."""
- if self._xml is not None and len(self): # only clear if not already empty
- self._xml = None
- self._changed = True
-
- def Commit(self):
- """Save the current set of preferences to the device.
-
- Only actually saves if some preferences have been modified.
- """
- if not self.changed:
- return
- self._device.RunShellCommand(
- ['mkdir', '-p', posixpath.dirname(self.path)],
- as_root=True, check_return=True)
- self._device.WriteFile(self.path, str(self), as_root=True)
- # Creating the directory/file can cause issues with SELinux if they did
- # not already exist. As a workaround, apply the package's security context
- # to the shared_prefs directory, which mimics the behavior of a file
- # created by the app itself
- if self._device.build_version_sdk >= version_codes.MARSHMALLOW:
- security_context = self._GetSecurityContext(self.package)
- if security_context == None:
- raise device_errors.CommandFailedError(
- 'Failed to get security context for %s' % self.package)
- self._device.RunShellCommand(
- ['chcon', '-R', security_context,
- '/data/data/%s/shared_prefs' % self.package],
- as_root=True, check_return=True)
- self._device.KillAll(self.package, exact=True, as_root=True, quiet=True)
- self._changed = False
-
- def __len__(self):
- """Get the number of preferences in this collection."""
- return len(self.xml)
-
- def PropertyType(self, key):
- """Get the type (i.e. tag name) of a property in the collection."""
- return self._GetChild(key).tag
-
- def HasProperty(self, key):
- try:
- self._GetChild(key)
- return True
- except KeyError:
- return False
-
- def GetBoolean(self, key):
- """Get a boolean property."""
- return BooleanPref(self._GetChild(key)).get()
-
- def SetBoolean(self, key, value):
- """Set a boolean property."""
- self._SetPrefValue(key, value, BooleanPref)
-
- def GetFloat(self, key):
- """Get a float property."""
- return FloatPref(self._GetChild(key)).get()
-
- def SetFloat(self, key, value):
- """Set a float property."""
- self._SetPrefValue(key, value, FloatPref)
-
- def GetInt(self, key):
- """Get an int property."""
- return IntPref(self._GetChild(key)).get()
-
- def SetInt(self, key, value):
- """Set an int property."""
- self._SetPrefValue(key, value, IntPref)
-
- def GetLong(self, key):
- """Get a long property."""
- return LongPref(self._GetChild(key)).get()
-
- def SetLong(self, key, value):
- """Set a long property."""
- self._SetPrefValue(key, value, LongPref)
-
- def GetString(self, key):
- """Get a string property."""
- return StringPref(self._GetChild(key)).get()
-
- def SetString(self, key, value):
- """Set a string property."""
- self._SetPrefValue(key, value, StringPref)
-
- def GetStringSet(self, key):
- """Get a string set property."""
- return StringSetPref(self._GetChild(key)).get()
-
- def SetStringSet(self, key, value):
- """Set a string set property."""
- self._SetPrefValue(key, value, StringSetPref)
-
- def Remove(self, key):
- """Remove a preference from the collection."""
- self.xml.remove(self._GetChild(key))
-
- def AsDict(self):
- """Return the properties and their values as a dictionary."""
- d = {}
- for child in self.xml:
- pref = _PREF_TYPES[child.tag](child)
- d[child.get('name')] = pref.get()
- return d
-
- def __enter__(self):
- """Load preferences file from the device when entering a context."""
- self.Load()
- return self
-
- def __exit__(self, exc_type, _exc_value, _traceback):
- """Save preferences file to the device when leaving a context."""
- if not exc_type:
- self.Commit()
-
- def _GetChild(self, key):
- """Get the underlying xml node that holds the property of a given key.
-
- Raises:
- KeyError when the key is not found in the collection.
- """
- for child in self.xml:
- if child.get('name') == key:
- return child
- raise KeyError(key)
-
- def _SetPrefValue(self, key, value, pref_cls):
- """Set the value of a property.
-
- Args:
- key: The key of the property to set.
- value: The new value of the property.
- pref_cls: A subclass of BasePref used to access the property.
-
- Raises:
- TypeError when the key already exists but with a different type.
- """
- try:
- pref = pref_cls(self._GetChild(key))
- old_value = pref.get()
- except KeyError:
- pref = pref_cls(ElementTree.SubElement(
- self.xml, pref_cls.tag_name, {'name': key}))
- old_value = None
- if old_value != value:
- pref.set(value)
- self._changed = True
- logger.info('Setting property: %s', pref)
-
- def _GetSecurityContext(self, package):
- for line in self._device.RunShellCommand(['ls', '-Z', '/data/data/'],
- as_root=True, check_return=True):
- split_line = line.split()
- # ls -Z output differs between Android versions, but the package is
- # always last and the context always starts with "u:object"
- if split_line[-1] == package:
- for column in split_line:
- if column.startswith('u:object'):
- return column
- return None
diff --git a/third_party/catapult/devil/devil/android/sdk/shared_prefs_test.py b/third_party/catapult/devil/devil/android/sdk/shared_prefs_test.py
deleted file mode 100755
index 4c31c569fd..0000000000
--- a/third_party/catapult/devil/devil/android/sdk/shared_prefs_test.py
+++ /dev/null
@@ -1,171 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""
-Unit tests for the contents of shared_prefs.py (mostly SharedPrefs).
-"""
-
-import logging
-import unittest
-
-from devil import devil_env
-from devil.android import device_utils
-from devil.android.sdk import shared_prefs
-from devil.android.sdk import version_codes
-
-with devil_env.SysPath(devil_env.PYMOCK_PATH):
- import mock # pylint: disable=import-error
-
-
-def MockDeviceWithFiles(files=None):
- if files is None:
- files = {}
-
- def file_exists(path):
- return path in files
-
- def write_file(path, contents, **_kwargs):
- files[path] = contents
-
- def read_file(path, **_kwargs):
- return files[path]
-
- device = mock.MagicMock(spec=device_utils.DeviceUtils)
- device.FileExists = mock.Mock(side_effect=file_exists)
- device.WriteFile = mock.Mock(side_effect=write_file)
- device.ReadFile = mock.Mock(side_effect=read_file)
- return device
-
-
-class SharedPrefsTest(unittest.TestCase):
-
- def setUp(self):
- self.device = MockDeviceWithFiles({
- '/data/data/com.some.package/shared_prefs/prefs.xml':
- "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
- '<map>\n'
- ' <int name="databaseVersion" value="107" />\n'
- ' <boolean name="featureEnabled" value="false" />\n'
- ' <string name="someHashValue">249b3e5af13d4db2</string>\n'
- '</map>'})
- self.expected_data = {'databaseVersion': 107,
- 'featureEnabled': False,
- 'someHashValue': '249b3e5af13d4db2'}
-
- def testPropertyLifetime(self):
- prefs = shared_prefs.SharedPrefs(
- self.device, 'com.some.package', 'prefs.xml')
- self.assertEquals(len(prefs), 0) # collection is empty before loading
- prefs.SetInt('myValue', 444)
- self.assertEquals(len(prefs), 1)
- self.assertEquals(prefs.GetInt('myValue'), 444)
- self.assertTrue(prefs.HasProperty('myValue'))
- prefs.Remove('myValue')
- self.assertEquals(len(prefs), 0)
- self.assertFalse(prefs.HasProperty('myValue'))
- with self.assertRaises(KeyError):
- prefs.GetInt('myValue')
-
- def testPropertyType(self):
- prefs = shared_prefs.SharedPrefs(
- self.device, 'com.some.package', 'prefs.xml')
- prefs.SetInt('myValue', 444)
- self.assertEquals(prefs.PropertyType('myValue'), 'int')
- with self.assertRaises(TypeError):
- prefs.GetString('myValue')
- with self.assertRaises(TypeError):
- prefs.SetString('myValue', 'hello')
-
- def testLoad(self):
- prefs = shared_prefs.SharedPrefs(
- self.device, 'com.some.package', 'prefs.xml')
- self.assertEquals(len(prefs), 0) # collection is empty before loading
- prefs.Load()
- self.assertEquals(len(prefs), len(self.expected_data))
- self.assertEquals(prefs.AsDict(), self.expected_data)
- self.assertFalse(prefs.changed)
-
- def testClear(self):
- prefs = shared_prefs.SharedPrefs(
- self.device, 'com.some.package', 'prefs.xml')
- prefs.Load()
- self.assertEquals(prefs.AsDict(), self.expected_data)
- self.assertFalse(prefs.changed)
- prefs.Clear()
- self.assertEquals(len(prefs), 0) # collection is empty now
- self.assertTrue(prefs.changed)
-
- def testCommit(self):
- type(self.device).build_version_sdk = mock.PropertyMock(
- return_value=version_codes.LOLLIPOP_MR1)
- prefs = shared_prefs.SharedPrefs(
- self.device, 'com.some.package', 'other_prefs.xml')
- self.assertFalse(self.device.FileExists(prefs.path)) # file does not exist
- prefs.Load()
- self.assertEquals(len(prefs), 0) # file did not exist, collection is empty
- prefs.SetInt('magicNumber', 42)
- prefs.SetFloat('myMetric', 3.14)
- prefs.SetLong('bigNumner', 6000000000)
- prefs.SetStringSet('apps', ['gmail', 'chrome', 'music'])
- self.assertFalse(self.device.FileExists(prefs.path)) # still does not exist
- self.assertTrue(prefs.changed)
- prefs.Commit()
- self.assertTrue(self.device.FileExists(prefs.path)) # should exist now
- self.device.KillAll.assert_called_once_with(prefs.package, exact=True,
- as_root=True, quiet=True)
- self.assertFalse(prefs.changed)
-
- prefs = shared_prefs.SharedPrefs(
- self.device, 'com.some.package', 'other_prefs.xml')
- self.assertEquals(len(prefs), 0) # collection is empty before loading
- prefs.Load()
- self.assertEquals(prefs.AsDict(), {
- 'magicNumber': 42,
- 'myMetric': 3.14,
- 'bigNumner': 6000000000,
- 'apps': ['gmail', 'chrome', 'music']}) # data survived roundtrip
-
- def testAsContextManager_onlyReads(self):
- with shared_prefs.SharedPrefs(
- self.device, 'com.some.package', 'prefs.xml') as prefs:
- self.assertEquals(prefs.AsDict(), self.expected_data) # loaded and ready
- self.assertEquals(self.device.WriteFile.call_args_list, []) # did not write
-
- def testAsContextManager_readAndWrite(self):
- type(self.device).build_version_sdk = mock.PropertyMock(
- return_value=version_codes.LOLLIPOP_MR1)
- with shared_prefs.SharedPrefs(
- self.device, 'com.some.package', 'prefs.xml') as prefs:
- prefs.SetBoolean('featureEnabled', True)
- prefs.Remove('someHashValue')
- prefs.SetString('newString', 'hello')
-
- self.assertTrue(self.device.WriteFile.called) # did write
- with shared_prefs.SharedPrefs(
- self.device, 'com.some.package', 'prefs.xml') as prefs:
- # changes persisted
- self.assertTrue(prefs.GetBoolean('featureEnabled'))
- self.assertFalse(prefs.HasProperty('someHashValue'))
- self.assertEquals(prefs.GetString('newString'), 'hello')
- self.assertTrue(prefs.HasProperty('databaseVersion')) # still there
-
- def testAsContextManager_commitAborted(self):
- with self.assertRaises(TypeError):
- with shared_prefs.SharedPrefs(
- self.device, 'com.some.package', 'prefs.xml') as prefs:
- prefs.SetBoolean('featureEnabled', True)
- prefs.Remove('someHashValue')
- prefs.SetString('newString', 'hello')
- prefs.SetInt('newString', 123) # oops!
-
- self.assertEquals(self.device.WriteFile.call_args_list, []) # did not write
- with shared_prefs.SharedPrefs(
- self.device, 'com.some.package', 'prefs.xml') as prefs:
- # contents were not modified
- self.assertEquals(prefs.AsDict(), self.expected_data)
-
-if __name__ == '__main__':
- logging.getLogger().setLevel(logging.DEBUG)
- unittest.main(verbosity=2)
diff --git a/third_party/catapult/devil/devil/android/sdk/split_select.py b/third_party/catapult/devil/devil/android/sdk/split_select.py
deleted file mode 100644
index 6c3d231a77..0000000000
--- a/third_party/catapult/devil/devil/android/sdk/split_select.py
+++ /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.
-
-"""This module wraps Android's split-select tool."""
-
-from devil.android.sdk import build_tools
-from devil.utils import cmd_helper
-from devil.utils import lazy
-
-
-_split_select_path = lazy.WeakConstant(
- lambda: build_tools.GetPath('split-select'))
-
-
-def _RunSplitSelectCmd(args):
- """Runs a split-select command.
-
- Args:
- args: A list of arguments for split-select.
-
- Returns:
- The output of the command.
- """
- cmd = [_split_select_path.read()] + args
- status, output = cmd_helper.GetCmdStatusAndOutput(cmd)
- if status != 0:
- raise Exception('Failed running command "%s" with output "%s".' %
- (' '.join(cmd), output))
- return output
-
-
-def _SplitConfig(device, allow_cached_props=False):
- """Returns a config specifying which APK splits are required by the device.
-
- Args:
- device: A DeviceUtils object.
- allow_cached_props: Whether to use cached values for device properties.
- """
- return ('%s-r%s-%s:%s' %
- (device.GetLanguage(cache=allow_cached_props),
- device.GetCountry(cache=allow_cached_props),
- device.screen_density,
- device.product_cpu_abi))
-
-
-def SelectSplits(device, base_apk, split_apks, allow_cached_props=False):
- """Determines which APK splits the device requires.
-
- Args:
- device: A DeviceUtils object.
- base_apk: The path of the base APK.
- split_apks: A list of paths of APK splits.
- allow_cached_props: Whether to use cached values for device properties.
-
- Returns:
- The list of APK splits that the device requires.
- """
- config = _SplitConfig(device, allow_cached_props=allow_cached_props)
- args = ['--target', config, '--base', base_apk]
- for split in split_apks:
- args.extend(['--split', split])
- return _RunSplitSelectCmd(args).splitlines()
diff --git a/third_party/catapult/devil/devil/android/sdk/test/data/push_directory/push_directory_contents.txt b/third_party/catapult/devil/devil/android/sdk/test/data/push_directory/push_directory_contents.txt
deleted file mode 100644
index 573df2e9b2..0000000000
--- a/third_party/catapult/devil/devil/android/sdk/test/data/push_directory/push_directory_contents.txt
+++ /dev/null
@@ -1 +0,0 @@
-Goodnight, moon.
diff --git a/third_party/catapult/devil/devil/android/sdk/test/data/push_file.txt b/third_party/catapult/devil/devil/android/sdk/test/data/push_file.txt
deleted file mode 100644
index af5626b4a1..0000000000
--- a/third_party/catapult/devil/devil/android/sdk/test/data/push_file.txt
+++ /dev/null
@@ -1 +0,0 @@
-Hello, world!
diff --git a/third_party/catapult/devil/devil/android/sdk/version_codes.py b/third_party/catapult/devil/devil/android/sdk/version_codes.py
deleted file mode 100644
index 3f03cbac6c..0000000000
--- a/third_party/catapult/devil/devil/android/sdk/version_codes.py
+++ /dev/null
@@ -1,20 +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.
-
-"""Android SDK version codes.
-
-http://developer.android.com/reference/android/os/Build.VERSION_CODES.html
-"""
-
-JELLY_BEAN = 16
-JELLY_BEAN_MR1 = 17
-JELLY_BEAN_MR2 = 18
-KITKAT = 19
-KITKAT_WATCH = 20
-LOLLIPOP = 21
-LOLLIPOP_MR1 = 22
-MARSHMALLOW = 23
-NOUGAT = 24
-NOUGAT_MR1 = 25
-
diff --git a/third_party/catapult/devil/devil/android/settings.py b/third_party/catapult/devil/devil/android/settings.py
deleted file mode 100644
index 886b2661d3..0000000000
--- a/third_party/catapult/devil/devil/android/settings.py
+++ /dev/null
@@ -1,273 +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.
-
-import logging
-
-logger = logging.getLogger(__name__)
-
-_LOCK_SCREEN_SETTINGS_PATH = '/data/system/locksettings.db'
-_ALTERNATE_LOCK_SCREEN_SETTINGS_PATH = (
- '/data/data/com.android.providers.settings/databases/settings.db')
-PASSWORD_QUALITY_UNSPECIFIED = '0'
-_COMPATIBLE_BUILD_TYPES = ['userdebug', 'eng']
-
-
-ENABLE_LOCATION_SETTINGS = [
- # Note that setting these in this order is required in order for all of
- # them to take and stick through a reboot.
- ('com.google.settings/partner', [
- ('use_location_for_services', 1),
- ]),
- ('settings/secure', [
- # Ensure Geolocation is enabled and allowed for tests.
- ('location_providers_allowed', 'gps,network'),
- ]),
- ('com.google.settings/partner', [
- ('network_location_opt_in', 1),
- ])
-]
-
-DISABLE_LOCATION_SETTINGS = [
- ('com.google.settings/partner', [
- ('use_location_for_services', 0),
- ]),
- ('settings/secure', [
- # Ensure Geolocation is disabled.
- ('location_providers_allowed', ''),
- ]),
-]
-
-ENABLE_MOCK_LOCATION_SETTINGS = [
- ('settings/secure', [
- ('mock_location', 1),
- ]),
-]
-
-DISABLE_MOCK_LOCATION_SETTINGS = [
- ('settings/secure', [
- ('mock_location', 0),
- ]),
-]
-
-DETERMINISTIC_DEVICE_SETTINGS = [
- ('settings/global', [
- ('assisted_gps_enabled', 0),
-
- # Disable "auto time" and "auto time zone" to avoid network-provided time
- # to overwrite the device's datetime and timezone synchronized from host
- # when running tests later. See b/6569849.
- ('auto_time', 0),
- ('auto_time_zone', 0),
-
- ('development_settings_enabled', 1),
-
- # Flag for allowing ActivityManagerService to send ACTION_APP_ERROR intents
- # on application crashes and ANRs. If this is disabled, the crash/ANR dialog
- # will never display the "Report" button.
- # Type: int ( 0 = disallow, 1 = allow )
- ('send_action_app_error', 0),
-
- ('stay_on_while_plugged_in', 3),
-
- ('verifier_verify_adb_installs', 0),
- ]),
- ('settings/secure', [
- ('allowed_geolocation_origins',
- 'http://www.google.co.uk http://www.google.com'),
-
- # Ensure that we never get random dialogs like "Unfortunately the process
- # android.process.acore has stopped", which steal the focus, and make our
- # automation fail (because the dialog steals the focus then mistakenly
- # receives the injected user input events).
- ('anr_show_background', 0),
-
- ('lockscreen.disabled', 1),
-
- ('screensaver_enabled', 0),
-
- ('skip_first_use_hints', 1),
- ]),
- ('settings/system', [
- # Don't want devices to accidentally rotate the screen as that could
- # affect performance measurements.
- ('accelerometer_rotation', 0),
-
- ('lockscreen.disabled', 1),
-
- # Turn down brightness and disable auto-adjust so that devices run cooler.
- ('screen_brightness', 5),
- ('screen_brightness_mode', 0),
-
- ('user_rotation', 0),
- ]),
-]
-
-NETWORK_DISABLED_SETTINGS = [
- ('settings/global', [
- ('airplane_mode_on', 1),
- ('wifi_on', 0),
- ]),
-]
-
-
-class ContentSettings(dict):
-
- """A dict interface to interact with device content settings.
-
- System properties are key/value pairs as exposed by adb shell content.
- """
-
- def __init__(self, table, device):
- super(ContentSettings, self).__init__()
- self._table = table
- self._device = device
-
- @staticmethod
- def _GetTypeBinding(value):
- if isinstance(value, bool):
- return 'b'
- if isinstance(value, float):
- return 'f'
- if isinstance(value, int):
- return 'i'
- if isinstance(value, long):
- return 'l'
- if isinstance(value, str):
- return 's'
- raise ValueError('Unsupported type %s' % type(value))
-
- def iteritems(self):
- # Example row:
- # 'Row: 0 _id=13, name=logging_id2, value=-1fccbaa546705b05'
- for row in self._device.RunShellCommand(
- ['content', 'query', '--uri', 'content://%s' % self._table],
- check_return=True, as_root=True):
- fields = row.split(', ')
- key = None
- value = None
- for field in fields:
- k, _, v = field.partition('=')
- if k == 'name':
- key = v
- elif k == 'value':
- value = v
- if not key:
- continue
- if not value:
- value = ''
- yield key, value
-
- def __getitem__(self, key):
- return self._device.RunShellCommand(
- ['content', 'query', '--uri', 'content://%s' % self._table,
- '--where', "name='%s'" % key],
- check_return=True, as_root=True).strip()
-
- def __setitem__(self, key, value):
- if key in self:
- self._device.RunShellCommand(
- ['content', 'update', '--uri', 'content://%s' % self._table,
- '--bind', 'value:%s:%s' % (self._GetTypeBinding(value), value),
- '--where', "name='%s'" % key],
- check_return=True, as_root=True)
- else:
- self._device.RunShellCommand(
- ['content', 'insert', '--uri', 'content://%s' % self._table,
- '--bind', 'name:%s:%s' % (self._GetTypeBinding(key), key),
- '--bind', 'value:%s:%s' % (self._GetTypeBinding(value), value)],
- check_return=True, as_root=True)
-
- def __delitem__(self, key):
- self._device.RunShellCommand(
- ['content', 'delete', '--uri', 'content://%s' % self._table,
- '--bind', 'name:%s:%s' % (self._GetTypeBinding(key), key)],
- check_return=True, as_root=True)
-
-
-def ConfigureContentSettings(device, desired_settings):
- """Configures device content setings from a list.
-
- Many settings are documented at:
- http://developer.android.com/reference/android/provider/Settings.Global.html
- http://developer.android.com/reference/android/provider/Settings.Secure.html
- http://developer.android.com/reference/android/provider/Settings.System.html
-
- Many others are undocumented.
-
- Args:
- device: A DeviceUtils instance for the device to configure.
- desired_settings: A list of (table, [(key: value), ...]) for all
- settings to configure.
- """
- for table, key_value in desired_settings:
- settings = ContentSettings(table, device)
- for key, value in key_value:
- settings[key] = value
- logger.info('\n%s %s', table, (80 - len(table)) * '-')
- for key, value in sorted(settings.iteritems()):
- logger.info('\t%s: %s', key, value)
-
-
-def SetLockScreenSettings(device):
- """Sets lock screen settings on the device.
-
- On certain device/Android configurations we need to disable the lock screen in
- a different database. Additionally, the password type must be set to
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED.
- Lock screen settings are stored in sqlite on the device in:
- /data/system/locksettings.db
-
- IMPORTANT: The first column is used as a primary key so that all rows with the
- same value for that column are removed from the table prior to inserting the
- new values.
-
- Args:
- device: A DeviceUtils instance for the device to configure.
-
- Raises:
- Exception if the setting was not properly set.
- """
- if device.build_type not in _COMPATIBLE_BUILD_TYPES:
- logger.warning('Unable to disable lockscreen on %s builds.',
- device.build_type)
- return
-
- def get_lock_settings(table):
- return [(table, 'lockscreen.disabled', '1'),
- (table, 'lockscreen.password_type', PASSWORD_QUALITY_UNSPECIFIED),
- (table, 'lockscreen.password_type_alternate',
- PASSWORD_QUALITY_UNSPECIFIED)]
-
- if device.FileExists(_LOCK_SCREEN_SETTINGS_PATH):
- db = _LOCK_SCREEN_SETTINGS_PATH
- locksettings = get_lock_settings('locksettings')
- columns = ['name', 'user', 'value']
- generate_values = lambda k, v: [k, '0', v]
- elif device.FileExists(_ALTERNATE_LOCK_SCREEN_SETTINGS_PATH):
- db = _ALTERNATE_LOCK_SCREEN_SETTINGS_PATH
- locksettings = get_lock_settings('secure') + get_lock_settings('system')
- columns = ['name', 'value']
- generate_values = lambda k, v: [k, v]
- else:
- logger.warning('Unable to find database file to set lock screen settings.')
- return
-
- for table, key, value in locksettings:
- # Set the lockscreen setting for default user '0'
- values = generate_values(key, value)
-
- cmd = """begin transaction;
-delete from '%(table)s' where %(primary_key)s='%(primary_value)s';
-insert into '%(table)s' (%(columns)s) values (%(values)s);
-commit transaction;""" % {
- 'table': table,
- 'primary_key': columns[0],
- 'primary_value': values[0],
- 'columns': ', '.join(columns),
- 'values': ', '.join(["'%s'" % value for value in values])
- }
- output_msg = device.RunShellCommand(
- ['sqlite3', db, cmd], check_return=True, as_root=True)
- if output_msg:
- logger.info(' '.join(output_msg))
diff --git a/third_party/catapult/devil/devil/android/tools/__init__.py b/third_party/catapult/devil/devil/android/tools/__init__.py
deleted file mode 100644
index 50b23dff63..0000000000
--- a/third_party/catapult/devil/devil/android/tools/__init__.py
+++ /dev/null
@@ -1,3 +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.
diff --git a/third_party/catapult/devil/devil/android/tools/adb_run_shell_cmd.py b/third_party/catapult/devil/devil/android/tools/adb_run_shell_cmd.py
deleted file mode 100755
index 77b67e8446..0000000000
--- a/third_party/catapult/devil/devil/android/tools/adb_run_shell_cmd.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import argparse
-import json
-import os
-import sys
-
-if __name__ == '__main__':
- sys.path.append(
- os.path.abspath(os.path.join(os.path.dirname(__file__),
- '..', '..', '..')))
-
-from devil.android import device_blacklist
-from devil.android import device_utils
-from devil.utils import run_tests_helper
-
-
-def main():
- parser = argparse.ArgumentParser(
- 'Run an adb shell command on selected devices')
- parser.add_argument('cmd', help='Adb shell command to run.', nargs="+")
- parser.add_argument('-d', '--device', action='append', dest='devices',
- default=[],
- help='Device to run cmd on. Runs on all devices if not '
- 'specified. Set multiple times for multiple devices')
- parser.add_argument('-v', '--verbose', default=0, action='count',
- help='Verbose level (multiple times for more)')
- parser.add_argument('--blacklist-file', help='Device blacklist file.')
- parser.add_argument('--as-root', action='store_true', help='Run as root.')
- parser.add_argument('--json-output',
- help='File to dump json output to.')
- args = parser.parse_args()
- run_tests_helper.SetLogLevel(args.verbose)
-
- args.blacklist_file = device_blacklist.Blacklist(
- args.blacklist_file) if args.blacklist_file else None
- devices = device_utils.DeviceUtils.HealthyDevices(
- blacklist=args.blacklist_file, device_arg=args.devices)
-
- p_out = (device_utils.DeviceUtils.parallel(devices).RunShellCommand(
- args.cmd, large_output=True, as_root=args.as_root, check_return=True)
- .pGet(None))
-
- data = {}
- for device, output in zip(devices, p_out):
- for line in output:
- print '%s: %s' % (device, line)
- data[str(device)] = output
-
- if args.json_output:
- with open(args.json_output, 'w') as f:
- json.dump(data, f)
-
- return 0
-
-
-if __name__ == '__main__':
- sys.exit(main())
diff --git a/third_party/catapult/devil/devil/android/tools/cpufreq.py b/third_party/catapult/devil/devil/android/tools/cpufreq.py
deleted file mode 100755
index 97deaf042c..0000000000
--- a/third_party/catapult/devil/devil/android/tools/cpufreq.py
+++ /dev/null
@@ -1,87 +0,0 @@
-#! /usr/bin/env python
-# 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.
-
-"""A script to manipulate device CPU frequency."""
-
-import argparse
-import os
-import pprint
-import sys
-
-if __name__ == '__main__':
- sys.path.append(
- os.path.abspath(os.path.join(os.path.dirname(__file__),
- '..', '..', '..')))
-
-from devil import devil_env
-from devil.android import device_utils
-from devil.android.perf import perf_control
-from devil.utils import run_tests_helper
-
-
-def SetScalingGovernor(device, args):
- p = perf_control.PerfControl(device)
- p.SetScalingGovernor(args.governor)
-
-
-def GetScalingGovernor(device, _args):
- p = perf_control.PerfControl(device)
- for cpu, governor in p.GetScalingGovernor():
- print '%s %s: %s' % (str(device), cpu, governor)
-
-
-def ListAvailableGovernors(device, _args):
- p = perf_control.PerfControl(device)
- for cpu, governors in p.ListAvailableGovernors():
- print '%s %s: %s' % (str(device), cpu, pprint.pformat(governors))
-
-
-def main(raw_args):
- parser = argparse.ArgumentParser()
- parser.add_argument(
- '--adb-path',
- help='ADB binary path.')
- parser.add_argument(
- '--device', dest='devices', action='append', default=[],
- help='Devices for which the governor should be set. Defaults to all.')
- parser.add_argument(
- '-v', '--verbose', action='count',
- help='Log more.')
-
- subparsers = parser.add_subparsers()
-
- set_governor = subparsers.add_parser('set-governor')
- set_governor.add_argument(
- 'governor',
- help='Desired CPU governor.')
- set_governor.set_defaults(func=SetScalingGovernor)
-
- get_governor = subparsers.add_parser('get-governor')
- get_governor.set_defaults(func=GetScalingGovernor)
-
- list_governors = subparsers.add_parser('list-governors')
- list_governors.set_defaults(func=ListAvailableGovernors)
-
- args = parser.parse_args(raw_args)
-
- run_tests_helper.SetLogLevel(args.verbose)
-
- devil_dynamic_config = devil_env.EmptyConfig()
- if args.adb_path:
- devil_dynamic_config['dependencies'].update(
- devil_env.LocalConfigItem(
- 'adb', devil_env.GetPlatform(), args.adb_path))
- devil_env.config.Initialize(configs=[devil_dynamic_config])
-
- devices = device_utils.DeviceUtils.HealthyDevices(device_arg=args.devices)
-
- parallel_devices = device_utils.DeviceUtils.parallel(devices)
- parallel_devices.pMap(args.func, args)
-
- return 0
-
-
-if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))
diff --git a/third_party/catapult/devil/devil/android/tools/device_monitor.py b/third_party/catapult/devil/devil/android/tools/device_monitor.py
deleted file mode 100755
index 49214a9282..0000000000
--- a/third_party/catapult/devil/devil/android/tools/device_monitor.py
+++ /dev/null
@@ -1,231 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Launches a daemon to monitor android device temperatures & status.
-
-This script will repeatedly poll the given devices for their temperatures and
-status every 60 seconds and dump the stats to file on the host.
-"""
-
-import argparse
-import collections
-import json
-import logging
-import logging.handlers
-import os
-import re
-import socket
-import sys
-import time
-
-if __name__ == '__main__':
- sys.path.append(
- os.path.abspath(os.path.join(os.path.dirname(__file__),
- '..', '..', '..')))
-
-from devil import devil_env
-from devil.android import battery_utils
-from devil.android import device_blacklist
-from devil.android import device_errors
-from devil.android import device_utils
-
-
-# Various names of sensors used to measure cpu temp
-CPU_TEMP_SENSORS = [
- # most nexus devices
- 'tsens_tz_sensor0',
- # android one
- 'mtktscpu',
- # nexus 9
- 'CPU-therm',
-]
-
-DEVICE_FILE_VERSION = 1
-# TODO(bpastene): Remove the old file once sysmon has been updated to read the
-# new status file.
-DEVICE_FILES = [
- os.path.join(os.path.expanduser('~'), 'android_device_status.json'),
- os.path.join(
- os.path.expanduser('~'), '.android',
- '%s__android_device_status.json' % socket.gethostname().split('.')[0]
- ),
-]
-
-MEM_INFO_REGEX = re.compile(r'.*?\:\s*(\d+)\s*kB') # ex: 'MemTotal: 185735 kB'
-
-
-def get_device_status(device):
- """Polls the given device for various info.
-
- Returns: A dict of the following format:
- {
- 'battery': {
- 'level': 100,
- 'temperature': 123
- },
- 'build': {
- 'build.id': 'ABC12D',
- 'product.device': 'chickenofthesea'
- },
- 'mem': {
- 'avail': 1000000,
- 'total': 1234567,
- },
- 'processes': 123,
- 'state': 'good',
- 'temp': {
- 'some_sensor': 30
- },
- 'uptime': 1234.56,
- }
- """
- status = collections.defaultdict(dict)
-
- # Battery
- battery = battery_utils.BatteryUtils(device)
- battery_info = battery.GetBatteryInfo()
- try:
- level = int(battery_info.get('level'))
- except (KeyError, TypeError, ValueError):
- level = None
- if level and level >= 0 and level <= 100:
- status['battery']['level'] = level
- try:
- temperature = int(battery_info.get('temperature'))
- except (KeyError, TypeError, ValueError):
- temperature = None
- if temperature:
- status['battery']['temperature'] = temperature
-
- # Build
- status['build']['build.id'] = device.build_id
- status['build']['product.device'] = device.build_product
-
- # Memory
- mem_info = ''
- try:
- mem_info = device.ReadFile('/proc/meminfo')
- except device_errors.AdbShellCommandFailedError:
- logging.exception('Unable to read /proc/meminfo')
- for line in mem_info.splitlines():
- match = MEM_INFO_REGEX.match(line)
- if match:
- try:
- value = int(match.group(1))
- except ValueError:
- continue
- key = line.split(':')[0].strip()
- if 'MemTotal' == key:
- status['mem']['total'] = value
- elif 'MemFree' == key:
- status['mem']['free'] = value
-
- # Process
- try:
- # TODO(catapult:#3215): Migrate to device.GetPids()
- lines = device.RunShellCommand(['ps'], check_return=True)
- status['processes'] = len(lines) - 1 # Ignore the header row.
- except device_errors.AdbShellCommandFailedError:
- logging.exception('Unable to count process list.')
-
- # CPU Temps
- # Find a thermal sensor that matches one in CPU_TEMP_SENSORS and read its
- # temperature.
- files = []
- try:
- files = device.RunShellCommand(
- 'grep -lE "%s" /sys/class/thermal/thermal_zone*/type' % '|'.join(
- CPU_TEMP_SENSORS), shell=True, check_return=True)
- except device_errors.AdbShellCommandFailedError:
- logging.exception('Unable to list thermal sensors.')
- for f in files:
- try:
- sensor_name = device.ReadFile(f).strip()
- temp = float(device.ReadFile(f[:-4] + 'temp').strip()) # s/type^/temp
- status['temp'][sensor_name] = temp
- except (device_errors.AdbShellCommandFailedError, ValueError):
- logging.exception('Unable to read thermal sensor %s', f)
-
- # Uptime
- try:
- uptimes = device.ReadFile('/proc/uptime').split()
- status['uptime'] = float(uptimes[0]) # Take the first field (actual uptime)
- except (device_errors.AdbShellCommandFailedError, ValueError):
- logging.exception('Unable to read /proc/uptime')
-
- status['state'] = 'available'
- return status
-
-
-def get_all_status(blacklist):
- status_dict = {
- 'version': DEVICE_FILE_VERSION,
- 'devices': {},
- }
-
- healthy_devices = device_utils.DeviceUtils.HealthyDevices(blacklist)
- parallel_devices = device_utils.DeviceUtils.parallel(healthy_devices)
- results = parallel_devices.pMap(get_device_status).pGet(None)
-
- status_dict['devices'] = {
- device.serial: result for device, result in zip(healthy_devices, results)
- }
-
- if blacklist:
- for device, reason in blacklist.Read().iteritems():
- status_dict['devices'][device] = {
- 'state': reason.get('reason', 'blacklisted')}
-
- status_dict['timestamp'] = time.time()
- return status_dict
-
-
-def main(argv):
- """Launches the device monitor.
-
- Polls the devices for their battery and cpu temperatures and scans the
- blacklist file every 60 seconds and dumps the data to DEVICE_FILE.
- """
-
- parser = argparse.ArgumentParser(
- description='Launches the device monitor.')
- parser.add_argument('--adb-path', help='Path to adb binary.')
- parser.add_argument('--blacklist-file', help='Path to device blacklist file.')
- args = parser.parse_args(argv)
-
- logger = logging.getLogger()
- logger.setLevel(logging.DEBUG)
- handler = logging.handlers.RotatingFileHandler(
- '/tmp/device_monitor.log', maxBytes=10 * 1024 * 1024, backupCount=5)
- fmt = logging.Formatter('%(asctime)s %(levelname)s %(message)s',
- datefmt='%y%m%d %H:%M:%S')
- handler.setFormatter(fmt)
- logger.addHandler(handler)
-
- devil_dynamic_config = devil_env.EmptyConfig()
- if args.adb_path:
- devil_dynamic_config['dependencies'].update(
- devil_env.LocalConfigItem(
- 'adb', devil_env.GetPlatform(), args.adb_path))
-
- devil_env.config.Initialize(configs=[devil_dynamic_config])
-
- blacklist = (device_blacklist.Blacklist(args.blacklist_file)
- if args.blacklist_file else None)
-
- logging.info('Device monitor running with pid %d, adb: %s, blacklist: %s',
- os.getpid(), args.adb_path, args.blacklist_file)
- while True:
- start = time.time()
- status_dict = get_all_status(blacklist)
- for device_file in DEVICE_FILES:
- with open(device_file, 'wb') as f:
- json.dump(status_dict, f, indent=2, sort_keys=True)
- logging.info('Got status of all devices in %.2fs.', time.time() - start)
- time.sleep(60)
-
-
-if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))
diff --git a/third_party/catapult/devil/devil/android/tools/device_monitor_test.py b/third_party/catapult/devil/devil/android/tools/device_monitor_test.py
deleted file mode 100755
index e39e324b36..0000000000
--- a/third_party/catapult/devil/devil/android/tools/device_monitor_test.py
+++ /dev/null
@@ -1,168 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import os
-import sys
-import unittest
-
-if __name__ == '__main__':
- sys.path.append(
- os.path.abspath(os.path.join(os.path.dirname(__file__),
- '..', '..', '..')))
-
-from devil import devil_env
-from devil.android import device_errors
-from devil.android import device_utils
-from devil.android.tools import device_monitor
-
-with devil_env.SysPath(devil_env.PYMOCK_PATH):
- import mock # pylint: disable=import-error
-
-
-class DeviceMonitorTest(unittest.TestCase):
-
- def setUp(self):
- self.device = mock.Mock(spec=device_utils.DeviceUtils,
- serial='device_cereal', build_id='abc123', build_product='clownfish')
- self.file_contents = {
- '/proc/meminfo': """
- MemTotal: 1234567 kB
- MemFree: 1000000 kB
- MemUsed: 234567 kB
- """,
- '/sys/class/thermal/thermal_zone0/type': 'CPU-therm',
- '/sys/class/thermal/thermal_zone0/temp': '30',
- '/proc/uptime': '12345 99999',
- }
- self.device.ReadFile = mock.MagicMock(
- side_effect=lambda file_name: self.file_contents[file_name])
-
- self.cmd_outputs = {
- 'ps': ['headers', 'p1', 'p2', 'p3', 'p4', 'p5'],
- 'grep': ['/sys/class/thermal/thermal_zone0/type'],
- }
-
- def mock_run_shell(cmd, **_kwargs):
- args = cmd.split() if isinstance(cmd, basestring) else cmd
- try:
- return self.cmd_outputs[args[0]]
- except KeyError:
- raise device_errors.AdbShellCommandFailedError(cmd, None, None)
-
- self.device.RunShellCommand = mock.MagicMock(side_effect=mock_run_shell)
-
- self.battery = mock.Mock()
- self.battery.GetBatteryInfo = mock.MagicMock(
- return_value={'level': '80', 'temperature': '123'})
-
- self.expected_status = {
- 'device_cereal': {
- 'processes': 5,
- 'temp': {
- 'CPU-therm': 30.0
- },
- 'battery': {
- 'temperature': 123,
- 'level': 80
- },
- 'uptime': 12345.0,
- 'mem': {
- 'total': 1234567,
- 'free': 1000000
- },
- 'build': {
- 'build.id': 'abc123',
- 'product.device': 'clownfish',
- },
- 'state': 'available',
- }
- }
-
- @mock.patch('devil.android.battery_utils.BatteryUtils')
- @mock.patch('devil.android.device_utils.DeviceUtils.HealthyDevices')
- def test_getStats(self, get_devices, get_battery):
- get_devices.return_value = [self.device]
- get_battery.return_value = self.battery
-
- status = device_monitor.get_all_status(None)
- self.assertEquals(self.expected_status, status['devices'])
-
- @mock.patch('devil.android.battery_utils.BatteryUtils')
- @mock.patch('devil.android.device_utils.DeviceUtils.HealthyDevices')
- def test_getStatsNoBattery(self, get_devices, get_battery):
- get_devices.return_value = [self.device]
- get_battery.return_value = self.battery
- broken_battery_info = mock.Mock()
- broken_battery_info.GetBatteryInfo = mock.MagicMock(
- return_value={'level': '-1', 'temperature': 'not_a_number'})
- get_battery.return_value = broken_battery_info
-
- # Should be same status dict but without battery stats.
- expected_status_no_battery = self.expected_status.copy()
- expected_status_no_battery['device_cereal'].pop('battery')
-
- status = device_monitor.get_all_status(None)
- self.assertEquals(expected_status_no_battery, status['devices'])
-
- @mock.patch('devil.android.battery_utils.BatteryUtils')
- @mock.patch('devil.android.device_utils.DeviceUtils.HealthyDevices')
- def test_getStatsNoPs(self, get_devices, get_battery):
- get_devices.return_value = [self.device]
- get_battery.return_value = self.battery
- del self.cmd_outputs['ps'] # Throw exception on run shell ps command.
-
- # Should be same status dict but without process stats.
- expected_status_no_ps = self.expected_status.copy()
- expected_status_no_ps['device_cereal'].pop('processes')
-
- status = device_monitor.get_all_status(None)
- self.assertEquals(expected_status_no_ps, status['devices'])
-
- @mock.patch('devil.android.battery_utils.BatteryUtils')
- @mock.patch('devil.android.device_utils.DeviceUtils.HealthyDevices')
- def test_getStatsNoSensors(self, get_devices, get_battery):
- get_devices.return_value = [self.device]
- get_battery.return_value = self.battery
- del self.cmd_outputs['grep'] # Throw exception on run shell grep command.
-
- # Should be same status dict but without temp stats.
- expected_status_no_temp = self.expected_status.copy()
- expected_status_no_temp['device_cereal'].pop('temp')
-
- status = device_monitor.get_all_status(None)
- self.assertEquals(expected_status_no_temp, status['devices'])
-
- @mock.patch('devil.android.battery_utils.BatteryUtils')
- @mock.patch('devil.android.device_utils.DeviceUtils.HealthyDevices')
- def test_getStatsWithBlacklist(self, get_devices, get_battery):
- get_devices.return_value = [self.device]
- get_battery.return_value = self.battery
- blacklist = mock.Mock()
- blacklist.Read = mock.MagicMock(
- return_value={'bad_device': {'reason': 'offline'}})
-
- # Should be same status dict but with extra blacklisted device.
- expected_status = self.expected_status.copy()
- expected_status['bad_device'] = {'state': 'offline'}
-
- status = device_monitor.get_all_status(blacklist)
- self.assertEquals(expected_status, status['devices'])
-
- @mock.patch('devil.android.battery_utils.BatteryUtils')
- @mock.patch('devil.android.device_utils.DeviceUtils.HealthyDevices')
- def test_brokenTempValue(self, get_devices, get_battery):
- self.file_contents['/sys/class/thermal/thermal_zone0/temp'] = 'n0t a numb3r'
- get_devices.return_value = [self.device]
- get_battery.return_value = self.battery
-
- expected_status_no_temp = self.expected_status.copy()
- expected_status_no_temp['device_cereal'].pop('temp')
-
- status = device_monitor.get_all_status(None)
- self.assertEquals(self.expected_status, status['devices'])
-
-
-if __name__ == '__main__':
- sys.exit(unittest.main())
diff --git a/third_party/catapult/devil/devil/android/tools/device_recovery.py b/third_party/catapult/devil/devil/android/tools/device_recovery.py
deleted file mode 100755
index 57857b1ed1..0000000000
--- a/third_party/catapult/devil/devil/android/tools/device_recovery.py
+++ /dev/null
@@ -1,208 +0,0 @@
-#!/usr/bin/env python
-# 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.
-
-"""A script to recover devices in a known bad state."""
-
-import argparse
-import logging
-import os
-import psutil
-import signal
-import sys
-
-if __name__ == '__main__':
- sys.path.append(
- os.path.abspath(os.path.join(os.path.dirname(__file__),
- '..', '..', '..')))
-from devil import devil_env
-from devil.android import device_blacklist
-from devil.android import device_errors
-from devil.android import device_utils
-from devil.android.tools import device_status
-from devil.utils import lsusb
-# TODO(jbudorick): Resolve this after experimenting w/ disabling the USB reset.
-from devil.utils import reset_usb # pylint: disable=unused-import
-from devil.utils import run_tests_helper
-
-logger = logging.getLogger(__name__)
-
-
-def KillAllAdb():
- def get_all_adb():
- for p in psutil.process_iter():
- try:
- if 'adb' in p.name:
- yield p
- except (psutil.NoSuchProcess, psutil.AccessDenied):
- pass
-
- for sig in [signal.SIGTERM, signal.SIGQUIT, signal.SIGKILL]:
- for p in get_all_adb():
- try:
- logger.info('kill %d %d (%s [%s])', sig, p.pid, p.name,
- ' '.join(p.cmdline))
- p.send_signal(sig)
- except (psutil.NoSuchProcess, psutil.AccessDenied):
- pass
- for p in get_all_adb():
- try:
- logger.error('Unable to kill %d (%s [%s])', p.pid, p.name,
- ' '.join(p.cmdline))
- except (psutil.NoSuchProcess, psutil.AccessDenied):
- pass
-
-
-def RecoverDevice(device, blacklist, should_reboot=lambda device: True):
- if device_status.IsBlacklisted(device.adb.GetDeviceSerial(),
- blacklist):
- logger.debug('%s is blacklisted, skipping recovery.', str(device))
- return
-
- if should_reboot(device):
- try:
- device.WaitUntilFullyBooted(retries=0)
- except (device_errors.CommandTimeoutError,
- device_errors.CommandFailedError):
- logger.exception('Failure while waiting for %s. '
- 'Attempting to recover.', str(device))
- try:
- try:
- device.Reboot(block=False, timeout=5, retries=0)
- except device_errors.CommandTimeoutError:
- logger.warning('Timed out while attempting to reboot %s normally.'
- 'Attempting alternative reboot.', str(device))
- # The device drops offline before we can grab the exit code, so
- # we don't check for status.
- try:
- device.adb.Root()
- finally:
- # We are already in a failure mode, attempt to reboot regardless of
- # what device.adb.Root() returns. If the sysrq reboot fails an
- # exception willbe thrown at that level.
- device.adb.Shell('echo b > /proc/sysrq-trigger', expect_status=None,
- timeout=5, retries=0)
- except device_errors.CommandFailedError:
- logger.exception('Failed to reboot %s.', str(device))
- if blacklist:
- blacklist.Extend([device.adb.GetDeviceSerial()],
- reason='reboot_failure')
- except device_errors.CommandTimeoutError:
- logger.exception('Timed out while rebooting %s.', str(device))
- if blacklist:
- blacklist.Extend([device.adb.GetDeviceSerial()],
- reason='reboot_timeout')
-
- try:
- device.WaitUntilFullyBooted(
- retries=0, timeout=device.REBOOT_DEFAULT_TIMEOUT)
- except device_errors.CommandFailedError:
- logger.exception('Failure while waiting for %s.', str(device))
- if blacklist:
- blacklist.Extend([device.adb.GetDeviceSerial()],
- reason='reboot_failure')
- except device_errors.CommandTimeoutError:
- logger.exception('Timed out while waiting for %s.', str(device))
- if blacklist:
- blacklist.Extend([device.adb.GetDeviceSerial()],
- reason='reboot_timeout')
-
-
-def RecoverDevices(devices, blacklist, enable_usb_reset=False):
- """Attempts to recover any inoperable devices in the provided list.
-
- Args:
- devices: The list of devices to attempt to recover.
- blacklist: The current device blacklist, which will be used then
- reset.
- """
-
- statuses = device_status.DeviceStatus(devices, blacklist)
-
- should_restart_usb = set(
- status['serial'] for status in statuses
- if (not status['usb_status']
- or status['adb_status'] in ('offline', 'missing')))
- should_restart_adb = should_restart_usb.union(set(
- status['serial'] for status in statuses
- if status['adb_status'] == 'unauthorized'))
- should_reboot_device = should_restart_adb.union(set(
- status['serial'] for status in statuses
- if status['blacklisted']))
-
- logger.debug('Should restart USB for:')
- for d in should_restart_usb:
- logger.debug(' %s', d)
- logger.debug('Should restart ADB for:')
- for d in should_restart_adb:
- logger.debug(' %s', d)
- logger.debug('Should reboot:')
- for d in should_reboot_device:
- logger.debug(' %s', d)
-
- if blacklist:
- blacklist.Reset()
-
- if should_restart_adb:
- KillAllAdb()
- for serial in should_restart_usb:
- try:
- # TODO(crbug.com/642194): Resetting may be causing more harm
- # (specifically, kernel panics) than it does good.
- if enable_usb_reset:
- reset_usb.reset_android_usb(serial)
- else:
- logger.warning('USB reset disabled for %s (crbug.com/642914)',
- serial)
- except IOError:
- logger.exception('Unable to reset USB for %s.', serial)
- if blacklist:
- blacklist.Extend([serial], reason='USB failure')
- except device_errors.DeviceUnreachableError:
- logger.exception('Unable to reset USB for %s.', serial)
- if blacklist:
- blacklist.Extend([serial], reason='offline')
-
- device_utils.DeviceUtils.parallel(devices).pMap(
- RecoverDevice, blacklist,
- should_reboot=lambda device: device.serial in should_reboot_device)
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument('--adb-path',
- help='Absolute path to the adb binary to use.')
- parser.add_argument('--blacklist-file', help='Device blacklist JSON file.')
- parser.add_argument('--known-devices-file', action='append', default=[],
- dest='known_devices_files',
- help='Path to known device lists.')
- parser.add_argument('--enable-usb-reset', action='store_true',
- help='Reset USB if necessary.')
- parser.add_argument('-v', '--verbose', action='count', default=1,
- help='Log more information.')
-
- args = parser.parse_args()
- run_tests_helper.SetLogLevel(args.verbose)
-
- devil_dynamic_config = devil_env.EmptyConfig()
- if args.adb_path:
- devil_dynamic_config['dependencies'].update(
- devil_env.LocalConfigItem(
- 'adb', devil_env.GetPlatform(), args.adb_path))
- devil_env.config.Initialize(configs=[devil_dynamic_config])
-
- blacklist = (device_blacklist.Blacklist(args.blacklist_file)
- if args.blacklist_file
- else None)
-
- expected_devices = device_status.GetExpectedDevices(args.known_devices_files)
- usb_devices = set(lsusb.get_android_devices())
- devices = [device_utils.DeviceUtils(s)
- for s in expected_devices.union(usb_devices)]
-
- RecoverDevices(devices, blacklist, enable_usb_reset=args.enable_usb_reset)
-
-
-if __name__ == '__main__':
- sys.exit(main())
diff --git a/third_party/catapult/devil/devil/android/tools/device_status.py b/third_party/catapult/devil/devil/android/tools/device_status.py
deleted file mode 100755
index 167d66c4b5..0000000000
--- a/third_party/catapult/devil/devil/android/tools/device_status.py
+++ /dev/null
@@ -1,313 +0,0 @@
-#!/usr/bin/env python
-# 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.
-
-"""A script to keep track of devices across builds and report state."""
-
-import argparse
-import json
-import logging
-import os
-import re
-import sys
-
-if __name__ == '__main__':
- sys.path.append(
- os.path.abspath(os.path.join(os.path.dirname(__file__),
- '..', '..', '..')))
-from devil import devil_env
-from devil.android import battery_utils
-from devil.android import device_blacklist
-from devil.android import device_errors
-from devil.android import device_list
-from devil.android import device_utils
-from devil.android.sdk import adb_wrapper
-from devil.constants import exit_codes
-from devil.utils import lsusb
-from devil.utils import run_tests_helper
-
-logger = logging.getLogger(__name__)
-
-_RE_DEVICE_ID = re.compile(r'Device ID = (\d+)')
-
-
-def IsBlacklisted(serial, blacklist):
- return blacklist and serial in blacklist.Read()
-
-
-def _BatteryStatus(device, blacklist):
- battery_info = {}
- try:
- battery = battery_utils.BatteryUtils(device)
- battery_info = battery.GetBatteryInfo(timeout=5)
- battery_level = int(battery_info.get('level', 100))
-
- if battery_level < 15:
- logger.error('Critically low battery level (%d)', battery_level)
- battery = battery_utils.BatteryUtils(device)
- if not battery.GetCharging():
- battery.SetCharging(True)
- if blacklist:
- blacklist.Extend([device.adb.GetDeviceSerial()], reason='low_battery')
-
- except device_errors.CommandFailedError:
- logger.exception('Failed to get battery information for %s',
- str(device))
-
- return battery_info
-
-
-def _IMEISlice(device):
- imei_slice = ''
- try:
- for l in device.RunShellCommand(['dumpsys', 'iphonesubinfo'],
- check_return=True, timeout=5):
- m = _RE_DEVICE_ID.match(l)
- if m:
- imei_slice = m.group(1)[-6:]
- except device_errors.CommandFailedError:
- logger.exception('Failed to get IMEI slice for %s', str(device))
-
- return imei_slice
-
-
-def DeviceStatus(devices, blacklist):
- """Generates status information for the given devices.
-
- Args:
- devices: The devices to generate status for.
- blacklist: The current device blacklist.
- Returns:
- A dict of the following form:
- {
- '<serial>': {
- 'serial': '<serial>',
- 'adb_status': str,
- 'usb_status': bool,
- 'blacklisted': bool,
- # only if the device is connected and not blacklisted
- 'type': ro.build.product,
- 'build': ro.build.id,
- 'build_detail': ro.build.fingerprint,
- 'battery': {
- ...
- },
- 'imei_slice': str,
- 'wifi_ip': str,
- },
- ...
- }
- """
- adb_devices = {
- a[0].GetDeviceSerial(): a
- for a in adb_wrapper.AdbWrapper.Devices(desired_state=None, long_list=True)
- }
- usb_devices = set(lsusb.get_android_devices())
-
- def blacklisting_device_status(device):
- serial = device.adb.GetDeviceSerial()
- adb_status = (
- adb_devices[serial][1] if serial in adb_devices
- else 'missing')
- usb_status = bool(serial in usb_devices)
-
- device_status = {
- 'serial': serial,
- 'adb_status': adb_status,
- 'usb_status': usb_status,
- }
-
- if not IsBlacklisted(serial, blacklist):
- if adb_status == 'device':
- try:
- build_product = device.build_product
- build_id = device.build_id
- build_fingerprint = device.build_fingerprint
- build_description = device.build_description
- wifi_ip = device.GetProp('dhcp.wlan0.ipaddress')
- battery_info = _BatteryStatus(device, blacklist)
- imei_slice = _IMEISlice(device)
-
- if (device.product_name == 'mantaray' and
- battery_info.get('AC powered', None) != 'true'):
- logger.error('Mantaray device not connected to AC power.')
-
- device_status.update({
- 'ro.build.product': build_product,
- 'ro.build.id': build_id,
- 'ro.build.fingerprint': build_fingerprint,
- 'ro.build.description': build_description,
- 'battery': battery_info,
- 'imei_slice': imei_slice,
- 'wifi_ip': wifi_ip,
- })
-
- except device_errors.CommandFailedError:
- logger.exception('Failure while getting device status for %s.',
- str(device))
- if blacklist:
- blacklist.Extend([serial], reason='status_check_failure')
-
- except device_errors.CommandTimeoutError:
- logger.exception('Timeout while getting device status for %s.',
- str(device))
- if blacklist:
- blacklist.Extend([serial], reason='status_check_timeout')
-
- elif blacklist:
- blacklist.Extend([serial],
- reason=adb_status if usb_status else 'offline')
-
- device_status['blacklisted'] = IsBlacklisted(serial, blacklist)
-
- return device_status
-
- parallel_devices = device_utils.DeviceUtils.parallel(devices)
- statuses = parallel_devices.pMap(blacklisting_device_status).pGet(None)
- return statuses
-
-
-def _LogStatuses(statuses):
- # Log the state of all devices.
- for status in statuses:
- logger.info(status['serial'])
- adb_status = status.get('adb_status')
- blacklisted = status.get('blacklisted')
- logger.info(' USB status: %s',
- 'online' if status.get('usb_status') else 'offline')
- logger.info(' ADB status: %s', adb_status)
- logger.info(' Blacklisted: %s', str(blacklisted))
- if adb_status == 'device' and not blacklisted:
- logger.info(' Device type: %s', status.get('ro.build.product'))
- logger.info(' OS build: %s', status.get('ro.build.id'))
- logger.info(' OS build fingerprint: %s',
- status.get('ro.build.fingerprint'))
- logger.info(' Battery state:')
- for k, v in status.get('battery', {}).iteritems():
- logger.info(' %s: %s', k, v)
- logger.info(' IMEI slice: %s', status.get('imei_slice'))
- logger.info(' WiFi IP: %s', status.get('wifi_ip'))
-
-
-def _WriteBuildbotFile(file_path, statuses):
- buildbot_path, _ = os.path.split(file_path)
- if os.path.exists(buildbot_path):
- with open(file_path, 'w') as f:
- for status in statuses:
- try:
- if status['adb_status'] == 'device':
- f.write('{serial} {adb_status} {build_product} {build_id} '
- '{temperature:.1f}C {level}%\n'.format(
- serial=status['serial'],
- adb_status=status['adb_status'],
- build_product=status['type'],
- build_id=status['build'],
- temperature=float(status['battery']['temperature']) / 10,
- level=status['battery']['level']
- ))
- elif status.get('usb_status', False):
- f.write('{serial} {adb_status}\n'.format(
- serial=status['serial'],
- adb_status=status['adb_status']
- ))
- else:
- f.write('{serial} offline\n'.format(
- serial=status['serial']
- ))
- except Exception: # pylint: disable=broad-except
- pass
-
-
-def GetExpectedDevices(known_devices_files):
- expected_devices = set()
- try:
- for path in known_devices_files:
- if os.path.exists(path):
- expected_devices.update(device_list.GetPersistentDeviceList(path))
- else:
- logger.warning('Could not find known devices file: %s', path)
- except IOError:
- logger.warning('Problem reading %s, skipping.', path)
-
- logger.info('Expected devices:')
- for device in expected_devices:
- logger.info(' %s', device)
- return expected_devices
-
-
-def AddArguments(parser):
- parser.add_argument('--json-output',
- help='Output JSON information into a specified file.')
- parser.add_argument('--adb-path',
- help='Absolute path to the adb binary to use.')
- parser.add_argument('--blacklist-file', help='Device blacklist JSON file.')
- parser.add_argument('--known-devices-file', action='append', default=[],
- dest='known_devices_files',
- help='Path to known device lists.')
- parser.add_argument('--buildbot-path', '-b',
- default='/home/chrome-bot/.adb_device_info',
- help='Absolute path to buildbot file location')
- parser.add_argument('-v', '--verbose', action='count', default=1,
- help='Log more information.')
- parser.add_argument('-w', '--overwrite-known-devices-files',
- action='store_true',
- help='If set, overwrites known devices files wiht new '
- 'values.')
-
-def main():
- parser = argparse.ArgumentParser()
- AddArguments(parser)
- args = parser.parse_args()
-
- run_tests_helper.SetLogLevel(args.verbose)
-
- devil_dynamic_config = devil_env.EmptyConfig()
-
- if args.adb_path:
- devil_dynamic_config['dependencies'].update(
- devil_env.LocalConfigItem(
- 'adb', devil_env.GetPlatform(), args.adb_path))
- devil_env.config.Initialize(configs=[devil_dynamic_config])
-
- blacklist = (device_blacklist.Blacklist(args.blacklist_file)
- if args.blacklist_file
- else None)
-
- expected_devices = GetExpectedDevices(args.known_devices_files)
- usb_devices = set(lsusb.get_android_devices())
- devices = [device_utils.DeviceUtils(s)
- for s in expected_devices.union(usb_devices)]
-
- statuses = DeviceStatus(devices, blacklist)
-
- # Log the state of all devices.
- _LogStatuses(statuses)
-
- # Update the last devices file(s).
- if args.overwrite_known_devices_files:
- for path in args.known_devices_files:
- device_list.WritePersistentDeviceList(
- path, [status['serial'] for status in statuses])
-
- # Write device info to file for buildbot info display.
- _WriteBuildbotFile(args.buildbot_path, statuses)
-
- # Dump the device statuses to JSON.
- if args.json_output:
- with open(args.json_output, 'wb') as f:
- f.write(json.dumps(
- statuses, indent=4, sort_keys=True, separators=(',', ': ')))
-
- live_devices = [status['serial'] for status in statuses
- if (status['adb_status'] == 'device'
- and not IsBlacklisted(status['serial'], blacklist))]
-
- # If all devices failed, or if there are no devices, it's an infra error.
- if not live_devices:
- logger.error('No available devices.')
- return 0 if live_devices else exit_codes.INFRA
-
-
-if __name__ == '__main__':
- sys.exit(main())
diff --git a/third_party/catapult/devil/devil/android/tools/flash_device.py b/third_party/catapult/devil/devil/android/tools/flash_device.py
deleted file mode 100755
index d13c1df72c..0000000000
--- a/third_party/catapult/devil/devil/android/tools/flash_device.py
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import argparse
-import logging
-import os
-import sys
-
-if __name__ == '__main__':
- sys.path.append(os.path.abspath(os.path.join(
- os.path.dirname(__file__), '..', '..', '..')))
-from devil.android import device_blacklist
-from devil.android import device_utils
-from devil.android import fastboot_utils
-from devil.android.tools import script_common
-from devil.constants import exit_codes
-from devil.utils import run_tests_helper
-
-logger = logging.getLogger(__name__)
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument('build_path', help='Path to android build.')
- parser.add_argument('-d', '--device', dest='devices', action='append',
- help='Device(s) to flash.')
- parser.add_argument('-v', '--verbose', default=0, action='count',
- help='Verbose level (multiple times for more)')
- parser.add_argument('-w', '--wipe', action='store_true',
- help='If set, wipes user data')
- parser.add_argument('--blacklist-file', help='Device blacklist file.')
- args = parser.parse_args()
- run_tests_helper.SetLogLevel(args.verbose)
-
- if args.blacklist_file:
- blacklist = device_blacklist.Blacklist(args.blacklist_file).Read()
- if blacklist:
- logger.critical('Device(s) in blacklist, not flashing devices:')
- for key in blacklist:
- logger.critical(' %s', key)
- return exit_codes.INFRA
-
- flashed_devices = []
- failed_devices = []
-
- def flash(device):
- fastboot = fastboot_utils.FastbootUtils(device)
- try:
- fastboot.FlashDevice(args.build_path, wipe=args.wipe)
- flashed_devices.append(device)
- except Exception: # pylint: disable=broad-except
- logger.exception('Device %s failed to flash.', str(device))
- failed_devices.append(device)
-
- devices = script_common.GetDevices(args.devices, args.blacklist_file)
- device_utils.DeviceUtils.parallel(devices).pMap(flash)
-
- if flashed_devices:
- logger.info('The following devices were flashed:')
- logger.info(' %s', ' '.join(str(d) for d in flashed_devices))
- if failed_devices:
- logger.critical('The following devices failed to flash:')
- logger.critical(' %s', ' '.join(str(d) for d in failed_devices))
- return exit_codes.INFRA
- return 0
-
-if __name__ == '__main__':
- sys.exit(main())
diff --git a/third_party/catapult/devil/devil/android/tools/keyboard.py b/third_party/catapult/devil/devil/android/tools/keyboard.py
deleted file mode 100755
index 31daf59edb..0000000000
--- a/third_party/catapult/devil/devil/android/tools/keyboard.py
+++ /dev/null
@@ -1,129 +0,0 @@
-#!/usr/bin/env python
-# 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.
-
-"""Use your keyboard as your phone's keyboard. Experimental."""
-
-import argparse
-import copy
-import os
-import sys
-import termios
-import tty
-
-if __name__ == '__main__':
- sys.path.append(
- os.path.abspath(os.path.join(os.path.dirname(__file__),
- '..', '..', '..')))
-from devil import base_error
-from devil.android.sdk import keyevent
-from devil.android.tools import script_common
-from devil.utils import run_tests_helper
-
-
-_KEY_MAPPING = {
- '\x08': keyevent.KEYCODE_DEL,
- '\x0a': keyevent.KEYCODE_ENTER,
- ' ': keyevent.KEYCODE_SPACE,
- '.': keyevent.KEYCODE_PERIOD,
- '0': keyevent.KEYCODE_0,
- '1': keyevent.KEYCODE_1,
- '2': keyevent.KEYCODE_2,
- '3': keyevent.KEYCODE_3,
- '4': keyevent.KEYCODE_4,
- '5': keyevent.KEYCODE_5,
- '6': keyevent.KEYCODE_6,
- '7': keyevent.KEYCODE_7,
- '8': keyevent.KEYCODE_8,
- '9': keyevent.KEYCODE_9,
- 'a': keyevent.KEYCODE_A,
- 'b': keyevent.KEYCODE_B,
- 'c': keyevent.KEYCODE_C,
- 'd': keyevent.KEYCODE_D,
- 'e': keyevent.KEYCODE_E,
- 'f': keyevent.KEYCODE_F,
- 'g': keyevent.KEYCODE_G,
- 'h': keyevent.KEYCODE_H,
- 'i': keyevent.KEYCODE_I,
- 'j': keyevent.KEYCODE_J,
- 'k': keyevent.KEYCODE_K,
- 'l': keyevent.KEYCODE_L,
- 'm': keyevent.KEYCODE_M,
- 'n': keyevent.KEYCODE_N,
- 'o': keyevent.KEYCODE_O,
- 'p': keyevent.KEYCODE_P,
- 'q': keyevent.KEYCODE_Q,
- 'r': keyevent.KEYCODE_R,
- 's': keyevent.KEYCODE_S,
- 't': keyevent.KEYCODE_T,
- 'u': keyevent.KEYCODE_U,
- 'v': keyevent.KEYCODE_V,
- 'w': keyevent.KEYCODE_W,
- 'x': keyevent.KEYCODE_X,
- 'y': keyevent.KEYCODE_Y,
- 'z': keyevent.KEYCODE_Z,
- '\x7f': keyevent.KEYCODE_DEL,
-}
-
-
-def Keyboard(device, stream_itr):
- try:
- for c in stream_itr:
- k = _KEY_MAPPING.get(c)
- if k:
- device.SendKeyEvent(k)
- else:
- print
- print '(No mapping for character 0x%x)' % ord(c)
- except KeyboardInterrupt:
- pass
-
-
-def AddArguments(parser):
- parser.add_argument('-d', '--device', action='append', dest='devices',
- metavar='DEVICE', help='device serial')
- parser.add_argument('-v', '--verbose', action='count', help='print more')
-
-
-class MultipleDevicesError(base_error.BaseError):
- def __init__(self, devices):
- super(MultipleDevicesError, self).__init__(
- 'More than one device found: %s' % ', '.join(str(d) for d in devices))
-
-
-def main(raw_args):
- parser = argparse.ArgumentParser(
- description="Use your keyboard as your phone's keyboard.")
- AddArguments(parser)
- args = parser.parse_args(raw_args)
-
- run_tests_helper.SetLogLevel(args.verbose)
-
- devices = script_common.GetDevices(args.devices, None)
- if len(devices) > 1:
- raise MultipleDevicesError(devices)
-
- def next_char():
- while True:
- yield sys.stdin.read(1)
-
- try:
- fd = sys.stdin.fileno()
-
- # See man 3 termios for more info on what this is doing.
- old_attrs = termios.tcgetattr(fd)
- new_attrs = copy.deepcopy(old_attrs)
- new_attrs[tty.LFLAG] = new_attrs[tty.LFLAG] & ~(termios.ICANON)
- new_attrs[tty.CC][tty.VMIN] = 1
- new_attrs[tty.CC][tty.VTIME] = 0
- termios.tcsetattr(fd, termios.TCSAFLUSH, new_attrs)
-
- Keyboard(devices[0], next_char())
- finally:
- termios.tcsetattr(fd, termios.TCSAFLUSH, old_attrs)
- return 0
-
-
-if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))
diff --git a/third_party/catapult/devil/devil/android/tools/provision_devices.py b/third_party/catapult/devil/devil/android/tools/provision_devices.py
deleted file mode 100755
index 7374290c79..0000000000
--- a/third_party/catapult/devil/devil/android/tools/provision_devices.py
+++ /dev/null
@@ -1,637 +0,0 @@
-#!/usr/bin/env python
-#
-# 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.
-
-"""Provisions Android devices with settings required for bots.
-
-Usage:
- ./provision_devices.py [-d <device serial number>]
-"""
-
-import argparse
-import datetime
-import json
-import logging
-import os
-import posixpath
-import re
-import sys
-import time
-
-# Import _strptime before threaded code. datetime.datetime.strptime is
-# threadsafe except for the initial import of the _strptime module.
-# See crbug.com/584730 and https://bugs.python.org/issue7980.
-import _strptime # pylint: disable=unused-import
-
-if __name__ == '__main__':
- sys.path.append(
- os.path.abspath(os.path.join(os.path.dirname(__file__),
- '..', '..', '..')))
-
-from devil import devil_env
-from devil.android import battery_utils
-from devil.android import device_blacklist
-from devil.android import device_errors
-from devil.android import device_temp_file
-from devil.android import device_utils
-from devil.android import settings
-from devil.android.constants import chrome
-from devil.android.sdk import adb_wrapper
-from devil.android.sdk import intent
-from devil.android.sdk import keyevent
-from devil.android.sdk import version_codes
-from devil.android.tools import script_common
-from devil.constants import exit_codes
-from devil.utils import run_tests_helper
-from devil.utils import timeout_retry
-
-logger = logging.getLogger(__name__)
-
-_SYSTEM_APP_DIRECTORIES = ['/system/app/', '/system/priv-app/']
-_SYSTEM_WEBVIEW_NAMES = ['webview', 'WebViewGoogle']
-_CHROME_PACKAGE_REGEX = re.compile('.*chrom.*')
-_TOMBSTONE_REGEX = re.compile('tombstone.*')
-
-
-class _DEFAULT_TIMEOUTS(object):
- # L can take a while to reboot after a wipe.
- LOLLIPOP = 600
- PRE_LOLLIPOP = 180
-
- HELP_TEXT = '{}s on L, {}s on pre-L'.format(LOLLIPOP, PRE_LOLLIPOP)
-
-
-class ProvisionStep(object):
-
- def __init__(self, cmd, reboot=False):
- self.cmd = cmd
- self.reboot = reboot
-
-
-def ProvisionDevices(
- devices,
- blacklist_file,
- adb_key_files=None,
- disable_location=False,
- disable_mock_location=False,
- disable_network=False,
- disable_system_chrome=False,
- emulators=False,
- enable_java_debug=False,
- max_battery_temp=None,
- min_battery_level=None,
- output_device_blacklist=None,
- reboot_timeout=None,
- remove_system_webview=False,
- system_app_remove_list=None,
- wipe=True):
- blacklist = (device_blacklist.Blacklist(blacklist_file)
- if blacklist_file
- else None)
- system_app_remove_list = system_app_remove_list or []
- try:
- devices = script_common.GetDevices(devices, blacklist)
- except device_errors.NoDevicesError:
- logging.error('No available devices to provision.')
- if blacklist:
- logging.error('Local device blacklist: %s', blacklist.Read())
- raise
- devices = [d for d in devices
- if not emulators or d.adb.is_emulator]
- parallel_devices = device_utils.DeviceUtils.parallel(devices)
-
- steps = []
- if wipe:
- steps += [ProvisionStep(lambda d: Wipe(d, adb_key_files), reboot=True)]
- steps += [ProvisionStep(
- lambda d: SetProperties(d, enable_java_debug, disable_location,
- disable_mock_location),
- reboot=not emulators)]
-
- if disable_network:
- steps.append(ProvisionStep(DisableNetwork))
-
- if disable_system_chrome:
- steps.append(ProvisionStep(DisableSystemChrome))
-
- if max_battery_temp:
- steps.append(ProvisionStep(
- lambda d: WaitForTemperature(d, max_battery_temp)))
-
- if min_battery_level:
- steps.append(ProvisionStep(
- lambda d: WaitForCharge(d, min_battery_level)))
-
- if remove_system_webview:
- system_app_remove_list.extend(_SYSTEM_WEBVIEW_NAMES)
-
- if system_app_remove_list:
- steps.append(ProvisionStep(
- lambda d: RemoveSystemApps(d, system_app_remove_list)))
-
- steps.append(ProvisionStep(SetDate))
- steps.append(ProvisionStep(CheckExternalStorage))
-
- parallel_devices.pMap(ProvisionDevice, steps, blacklist, reboot_timeout)
-
- blacklisted_devices = blacklist.Read() if blacklist else []
- if output_device_blacklist:
- with open(output_device_blacklist, 'w') as f:
- json.dump(blacklisted_devices, f)
- if all(d in blacklisted_devices for d in devices):
- raise device_errors.NoDevicesError
- return 0
-
-
-def ProvisionDevice(device, steps, blacklist, reboot_timeout=None):
- try:
- if not reboot_timeout:
- if device.build_version_sdk >= version_codes.LOLLIPOP:
- reboot_timeout = _DEFAULT_TIMEOUTS.LOLLIPOP
- else:
- reboot_timeout = _DEFAULT_TIMEOUTS.PRE_LOLLIPOP
-
- for step in steps:
- try:
- device.WaitUntilFullyBooted(timeout=reboot_timeout, retries=0)
- except device_errors.CommandTimeoutError:
- logger.error('Device did not finish booting. Will try to reboot.')
- device.Reboot(timeout=reboot_timeout)
- step.cmd(device)
- if step.reboot:
- device.Reboot(False, retries=0)
- device.adb.WaitForDevice()
-
- except device_errors.CommandTimeoutError:
- logger.exception('Timed out waiting for device %s. Adding to blacklist.',
- str(device))
- if blacklist:
- blacklist.Extend([str(device)], reason='provision_timeout')
-
- except device_errors.CommandFailedError:
- logger.exception('Failed to provision device %s. Adding to blacklist.',
- str(device))
- if blacklist:
- blacklist.Extend([str(device)], reason='provision_failure')
-
-
-def Wipe(device, adb_key_files=None):
- if (device.IsUserBuild() or
- device.build_version_sdk >= version_codes.MARSHMALLOW):
- WipeChromeData(device)
-
- package = "com.google.android.gms"
- version_name = device.GetApplicationVersion(package)
- logger.info("Version name for %s is %s", package, version_name)
- else:
- WipeDevice(device, adb_key_files)
-
-
-def WipeChromeData(device):
- """Wipes chrome specific data from device
-
- (1) uninstall any app whose name matches *chrom*, except
- com.android.chrome, which is the chrome stable package. Doing so also
- removes the corresponding dirs under /data/data/ and /data/app/
- (2) remove any dir under /data/app-lib/ whose name matches *chrom*
- (3) remove any files under /data/tombstones/ whose name matches "tombstone*"
- (4) remove /data/local.prop if there is any
- (5) remove /data/local/chrome-command-line if there is any
- (6) remove anything under /data/local/.config/ if the dir exists
- (this is telemetry related)
- (7) remove anything under /data/local/tmp/
-
- Arguments:
- device: the device to wipe
- """
- try:
- if device.IsUserBuild():
- _UninstallIfMatch(device, _CHROME_PACKAGE_REGEX,
- chrome.PACKAGE_INFO['chrome_stable'].package)
- device.RunShellCommand('rm -rf %s/*' % device.GetExternalStoragePath(),
- shell=True, check_return=True)
- device.RunShellCommand('rm -rf /data/local/tmp/*',
- shell=True, check_return=True)
- else:
- device.EnableRoot()
- _UninstallIfMatch(device, _CHROME_PACKAGE_REGEX,
- chrome.PACKAGE_INFO['chrome_stable'].package)
- _WipeUnderDirIfMatch(device, '/data/app-lib/', _CHROME_PACKAGE_REGEX)
- _WipeUnderDirIfMatch(device, '/data/tombstones/', _TOMBSTONE_REGEX)
-
- _WipeFileOrDir(device, '/data/local.prop')
- _WipeFileOrDir(device, '/data/local/chrome-command-line')
- _WipeFileOrDir(device, '/data/local/.config/')
- _WipeFileOrDir(device, '/data/local/tmp/')
- device.RunShellCommand('rm -rf %s/*' % device.GetExternalStoragePath(),
- shell=True, check_return=True)
- except device_errors.CommandFailedError:
- logger.exception('Possible failure while wiping the device. '
- 'Attempting to continue.')
-
-
-def _UninstallIfMatch(device, pattern, app_to_keep):
- installed_packages = device.RunShellCommand(
- ['pm', 'list', 'packages'], check_return=True)
- installed_system_packages = [
- pkg.split(':')[1] for pkg in device.RunShellCommand(
- ['pm', 'list', 'packages', '-s'], check_return=True)]
- for package_output in installed_packages:
- package = package_output.split(":")[1]
- if pattern.match(package) and not package == app_to_keep:
- if not device.IsUserBuild() or package not in installed_system_packages:
- device.Uninstall(package)
-
-
-def _WipeUnderDirIfMatch(device, path, pattern):
- for filename in device.ListDirectory(path):
- if pattern.match(filename):
- _WipeFileOrDir(device, posixpath.join(path, filename))
-
-
-def _WipeFileOrDir(device, path):
- if device.PathExists(path):
- device.RunShellCommand(['rm', '-rf', path], check_return=True)
-
-
-def WipeDevice(device, adb_key_files):
- """Wipes data from device, keeping only the adb_keys for authorization.
-
- After wiping data on a device that has been authorized, adb can still
- communicate with the device, but after reboot the device will need to be
- re-authorized because the adb keys file is stored in /data/misc/adb/.
- Thus, adb_keys file is rewritten so the device does not need to be
- re-authorized.
-
- Arguments:
- device: the device to wipe
- """
- try:
- device.EnableRoot()
- device_authorized = device.FileExists(adb_wrapper.ADB_KEYS_FILE)
- if device_authorized:
- adb_keys = device.ReadFile(adb_wrapper.ADB_KEYS_FILE,
- as_root=True).splitlines()
- device.RunShellCommand(['wipe', 'data'],
- as_root=True, check_return=True)
- device.adb.WaitForDevice()
-
- if device_authorized:
- adb_keys_set = set(adb_keys)
- for adb_key_file in adb_key_files or []:
- try:
- with open(adb_key_file, 'r') as f:
- adb_public_keys = f.readlines()
- adb_keys_set.update(adb_public_keys)
- except IOError:
- logger.warning('Unable to find adb keys file %s.', adb_key_file)
- _WriteAdbKeysFile(device, '\n'.join(adb_keys_set))
- except device_errors.CommandFailedError:
- logger.exception('Possible failure while wiping the device. '
- 'Attempting to continue.')
-
-
-def _WriteAdbKeysFile(device, adb_keys_string):
- dir_path = posixpath.dirname(adb_wrapper.ADB_KEYS_FILE)
- device.RunShellCommand(['mkdir', '-p', dir_path],
- as_root=True, check_return=True)
- device.RunShellCommand(['restorecon', dir_path],
- as_root=True, check_return=True)
- device.WriteFile(adb_wrapper.ADB_KEYS_FILE, adb_keys_string, as_root=True)
- device.RunShellCommand(['restorecon', adb_wrapper.ADB_KEYS_FILE],
- as_root=True, check_return=True)
-
-
-def SetProperties(device, enable_java_debug, disable_location,
- disable_mock_location):
- try:
- device.EnableRoot()
- except device_errors.CommandFailedError as e:
- logger.warning(str(e))
-
- if not device.IsUserBuild():
- _ConfigureLocalProperties(device, enable_java_debug)
- else:
- logger.warning('Cannot configure properties in user builds.')
- settings.ConfigureContentSettings(
- device, settings.DETERMINISTIC_DEVICE_SETTINGS)
- if disable_location:
- settings.ConfigureContentSettings(
- device, settings.DISABLE_LOCATION_SETTINGS)
- else:
- settings.ConfigureContentSettings(
- device, settings.ENABLE_LOCATION_SETTINGS)
-
- if disable_mock_location:
- settings.ConfigureContentSettings(
- device, settings.DISABLE_MOCK_LOCATION_SETTINGS)
- else:
- settings.ConfigureContentSettings(
- device, settings.ENABLE_MOCK_LOCATION_SETTINGS)
-
- settings.SetLockScreenSettings(device)
-
- # Some device types can momentarily disappear after setting properties.
- device.adb.WaitForDevice()
-
-
-def DisableNetwork(device):
- settings.ConfigureContentSettings(
- device, settings.NETWORK_DISABLED_SETTINGS)
- if device.build_version_sdk >= version_codes.MARSHMALLOW:
- # Ensure that NFC is also switched off.
- device.RunShellCommand(['svc', 'nfc', 'disable'],
- as_root=True, check_return=True)
-
-
-def DisableSystemChrome(device):
- # The system chrome version on the device interferes with some tests.
- device.RunShellCommand(['pm', 'disable', 'com.android.chrome'],
- check_return=True)
-
-
-def _RemoveSystemApp(device, system_app):
- found_paths = []
- for directory in _SYSTEM_APP_DIRECTORIES:
- path = os.path.join(directory, system_app)
- if device.PathExists(path):
- found_paths.append(path)
- if not found_paths:
- logger.warning('Could not find install location for system app %s',
- system_app)
- device.RemovePath(found_paths, force=True, recursive=True)
-
-def RemoveSystemApps(device, system_app_remove_list):
- """Attempts to remove the provided system apps from the given device.
-
- Arguments:
- device: The device to remove the system apps from.
- system_app_remove_list: A list of app names to remove, e.g.
- ['WebViewGoogle', 'GoogleVrCore']
- """
- device.EnableRoot()
- if device.HasRoot():
- # Disable Marshmallow's Verity security feature
- if device.build_version_sdk >= version_codes.MARSHMALLOW:
- logger.info('Disabling Verity on %s', device.serial)
- device.adb.DisableVerity()
- device.Reboot()
- device.WaitUntilFullyBooted()
- device.EnableRoot()
-
- device.adb.Remount()
- device.RunShellCommand(['stop'], check_return=True)
- for system_app in system_app_remove_list:
- _RemoveSystemApp(device, system_app)
- device.RunShellCommand(['start'], check_return=True)
- else:
- raise device_errors.CommandFailedError(
- 'Failed to remove system apps from non-rooted device', str(device))
-
-
-def _ConfigureLocalProperties(device, java_debug=True):
- """Set standard readonly testing device properties prior to reboot."""
- local_props = [
- 'persist.sys.usb.config=adb',
- 'ro.monkey=1',
- 'ro.test_harness=1',
- 'ro.audio.silent=1',
- 'ro.setupwizard.mode=DISABLED',
- ]
- if java_debug:
- local_props.append(
- '%s=all' % device_utils.DeviceUtils.JAVA_ASSERT_PROPERTY)
- local_props.append('debug.checkjni=1')
- try:
- device.WriteFile(
- device.LOCAL_PROPERTIES_PATH,
- '\n'.join(local_props), as_root=True)
- # Android will not respect the local props file if it is world writable.
- device.RunShellCommand(
- ['chmod', '644', device.LOCAL_PROPERTIES_PATH],
- as_root=True, check_return=True)
- except device_errors.CommandFailedError:
- logger.exception('Failed to configure local properties.')
-
-
-def FinishProvisioning(device):
- # The lockscreen can't be disabled on user builds, so send a keyevent
- # to unlock it.
- if device.IsUserBuild():
- device.SendKeyEvent(keyevent.KEYCODE_MENU)
-
-
-def WaitForCharge(device, min_battery_level):
- battery = battery_utils.BatteryUtils(device)
- try:
- battery.ChargeDeviceToLevel(min_battery_level)
- except device_errors.DeviceChargingError:
- device.Reboot()
- battery.ChargeDeviceToLevel(min_battery_level)
-
-
-def WaitForTemperature(device, max_battery_temp):
- try:
- battery = battery_utils.BatteryUtils(device)
- battery.LetBatteryCoolToTemperature(max_battery_temp)
- except device_errors.CommandFailedError:
- logger.exception('Unable to let battery cool to specified temperature.')
-
-
-def SetDate(device):
- def _set_and_verify_date():
- if device.build_version_sdk >= version_codes.MARSHMALLOW:
- date_format = '%m%d%H%M%Y.%S'
- set_date_command = ['date', '-u']
- get_date_command = ['date', '-u']
- else:
- date_format = '%Y%m%d.%H%M%S'
- set_date_command = ['date', '-s']
- get_date_command = ['date']
-
- # TODO(jbudorick): This is wrong on pre-M devices -- get/set are
- # dealing in local time, but we're setting based on GMT.
- strgmtime = time.strftime(date_format, time.gmtime())
- set_date_command.append(strgmtime)
- device.RunShellCommand(set_date_command, as_root=True, check_return=True)
-
- get_date_command.append('+"%Y%m%d.%H%M%S"')
- device_time = device.RunShellCommand(
- get_date_command, check_return=True,
- as_root=True, single_line=True).replace('"', '')
- device_time = datetime.datetime.strptime(device_time, "%Y%m%d.%H%M%S")
- correct_time = datetime.datetime.strptime(strgmtime, date_format)
- tdelta = (correct_time - device_time).seconds
- if tdelta <= 1:
- logger.info('Date/time successfully set on %s', device)
- return True
- else:
- logger.error('Date mismatch. Device: %s Correct: %s',
- device_time.isoformat(), correct_time.isoformat())
- return False
-
- # Sometimes the date is not set correctly on the devices. Retry on failure.
- if device.IsUserBuild():
- # TODO(bpastene): Figure out how to set the date & time on user builds.
- pass
- else:
- if not timeout_retry.WaitFor(
- _set_and_verify_date, wait_period=1, max_tries=2):
- raise device_errors.CommandFailedError(
- 'Failed to set date & time.', device_serial=str(device))
- device.EnableRoot()
- device.BroadcastIntent(
- intent.Intent(action='android.intent.action.TIME_SET'))
-
-
-def LogDeviceProperties(device):
- props = device.RunShellCommand(['getprop'], check_return=True)
- for prop in props:
- logger.info(' %s', prop)
-
-
-def CheckExternalStorage(device):
- """Checks that storage is writable and if not makes it writable.
-
- Arguments:
- device: The device to check.
- """
- try:
- with device_temp_file.DeviceTempFile(
- device.adb, suffix='.sh', dir=device.GetExternalStoragePath()) as f:
- device.WriteFile(f.name, 'test')
- except device_errors.CommandFailedError:
- logger.info('External storage not writable. Remounting / as RW')
- device.RunShellCommand(['mount', '-o', 'remount,rw', '/'],
- check_return=True, as_root=True)
- device.EnableRoot()
- with device_temp_file.DeviceTempFile(
- device.adb, suffix='.sh', dir=device.GetExternalStoragePath()) as f:
- device.WriteFile(f.name, 'test')
-
-
-def main(raw_args):
- # Recommended options on perf bots:
- # --disable-network
- # TODO(tonyg): We eventually want network on. However, currently radios
- # can cause perfbots to drain faster than they charge.
- # --min-battery-level 95
- # Some perf bots run benchmarks with USB charging disabled which leads
- # to gradual draining of the battery. We must wait for a full charge
- # before starting a run in order to keep the devices online.
-
- parser = argparse.ArgumentParser(
- description='Provision Android devices with settings required for bots.')
- parser.add_argument(
- '--adb-key-files', type=str, nargs='+',
- help='list of adb keys to push to device')
- parser.add_argument(
- '--adb-path',
- help='Absolute path to the adb binary to use.')
- parser.add_argument('--blacklist-file', help='Device blacklist JSON file.')
- parser.add_argument(
- '-d', '--device', metavar='SERIAL', action='append', dest='devices',
- help='the serial number of the device to be provisioned '
- '(the default is to provision all devices attached)')
- parser.add_argument(
- '--disable-location', action='store_true',
- help='disable Google location services on devices')
- parser.add_argument(
- '--disable-mock-location', action='store_true', default=False,
- help='Set ALLOW_MOCK_LOCATION to false')
- parser.add_argument(
- '--disable-network', action='store_true',
- help='disable network access on devices')
- parser.add_argument(
- '--disable-java-debug', action='store_false',
- dest='enable_java_debug', default=True,
- help='disable Java property asserts and JNI checking')
- parser.add_argument(
- '--disable-system-chrome', action='store_true',
- help='Disable the system chrome from devices.')
- parser.add_argument(
- '--emulators', action='store_true',
- help='provision only emulators and ignore usb devices '
- '(this will not wipe emulators)')
- parser.add_argument(
- '--max-battery-temp', type=int, metavar='NUM',
- help='Wait for the battery to have this temp or lower.')
- parser.add_argument(
- '--min-battery-level', type=int, metavar='NUM',
- help='wait for the device to reach this minimum battery'
- ' level before trying to continue')
- parser.add_argument(
- '--output-device-blacklist',
- help='Json file to output the device blacklist.')
- parser.add_argument(
- '--reboot-timeout', metavar='SECS', type=int,
- help='when wiping the device, max number of seconds to'
- ' wait after each reboot '
- '(default: %s)' % _DEFAULT_TIMEOUTS.HELP_TEXT)
- parser.add_argument(
- '--remove-system-apps', nargs='*', dest='system_app_remove_list',
- help='the names of system apps to remove')
- parser.add_argument(
- '--remove-system-webview', action='store_true',
- help='Remove the system webview from devices.')
- parser.add_argument(
- '--skip-wipe', action='store_true', default=False,
- help='do not wipe device data during provisioning')
- parser.add_argument(
- '-v', '--verbose', action='count', default=1,
- help='Log more information.')
-
- # No-op arguments for compatibility with build/android/provision_devices.py.
- # TODO(jbudorick): Remove these once all callers have stopped using them.
- parser.add_argument(
- '--chrome-specific-wipe', action='store_true',
- help=argparse.SUPPRESS)
- parser.add_argument(
- '--phase', action='append',
- help=argparse.SUPPRESS)
- parser.add_argument(
- '-r', '--auto-reconnect', action='store_true',
- help=argparse.SUPPRESS)
- parser.add_argument(
- '-t', '--target',
- help=argparse.SUPPRESS)
-
- args = parser.parse_args(raw_args)
-
- run_tests_helper.SetLogLevel(args.verbose)
-
- devil_dynamic_config = devil_env.EmptyConfig()
- if args.adb_path:
- devil_dynamic_config['dependencies'].update(
- devil_env.LocalConfigItem(
- 'adb', devil_env.GetPlatform(), args.adb_path))
-
- devil_env.config.Initialize(configs=[devil_dynamic_config])
-
- try:
- return ProvisionDevices(
- args.devices,
- args.blacklist_file,
- adb_key_files=args.adb_key_files,
- disable_location=args.disable_location,
- disable_mock_location=args.disable_mock_location,
- disable_network=args.disable_network,
- disable_system_chrome=args.disable_system_chrome,
- emulators=args.emulators,
- enable_java_debug=args.enable_java_debug,
- max_battery_temp=args.max_battery_temp,
- min_battery_level=args.min_battery_level,
- output_device_blacklist=args.output_device_blacklist,
- reboot_timeout=args.reboot_timeout,
- remove_system_webview=args.remove_system_webview,
- system_app_remove_list=args.system_app_remove_list,
- wipe=not args.skip_wipe and not args.emulators)
- except (device_errors.DeviceUnreachableError, device_errors.NoDevicesError):
- logging.exception('Unable to provision local devices.')
- return exit_codes.INFRA
-
-
-if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))
diff --git a/third_party/catapult/devil/devil/android/tools/screenshot.py b/third_party/catapult/devil/devil/android/tools/screenshot.py
deleted file mode 100755
index a264c4f3d4..0000000000
--- a/third_party/catapult/devil/devil/android/tools/screenshot.py
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Takes a screenshot from an Android device."""
-
-import argparse
-import logging
-import os
-import sys
-
-if __name__ == '__main__':
- sys.path.append(os.path.abspath(os.path.join(
- os.path.dirname(__file__), '..', '..', '..')))
-from devil.android import device_utils
-from devil.android.tools import script_common
-
-logger = logging.getLogger(__name__)
-
-
-def main():
- # Parse options.
- parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument('-d', '--device', dest='devices', action='append',
- help='Serial number of Android device to use.')
- parser.add_argument('--blacklist-file', help='Device blacklist JSON file.')
- parser.add_argument('-f', '--file', metavar='FILE',
- help='Save result to file instead of generating a '
- 'timestamped file name.')
- parser.add_argument('-v', '--verbose', action='store_true',
- help='Verbose logging.')
- parser.add_argument('host_file', nargs='?',
- help='File to which the screenshot will be saved.')
-
- args = parser.parse_args()
-
- host_file = args.host_file or args.file
-
- if args.verbose:
- logging.getLogger().setLevel(logging.DEBUG)
-
- devices = script_common.GetDevices(args.devices, args.blacklist_file)
-
- def screenshot(device):
- f = None
- if host_file:
- root, ext = os.path.splitext(host_file)
- f = '%s_%s%s' % (root, str(device), ext)
- f = device.TakeScreenshot(f)
- print 'Screenshot for device %s written to %s' % (
- str(device), os.path.abspath(f))
-
- device_utils.DeviceUtils.parallel(devices).pMap(screenshot)
- return 0
-
-
-if __name__ == '__main__':
- sys.exit(main())
diff --git a/third_party/catapult/devil/devil/android/tools/script_common.py b/third_party/catapult/devil/devil/android/tools/script_common.py
deleted file mode 100644
index f91ad5eea0..0000000000
--- a/third_party/catapult/devil/devil/android/tools/script_common.py
+++ /dev/null
@@ -1,29 +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.
-
-from devil.android import device_blacklist
-from devil.android import device_errors
-from devil.android import device_utils
-
-
-def GetDevices(requested_devices, blacklist_file):
- if not isinstance(blacklist_file, device_blacklist.Blacklist):
- blacklist_file = (device_blacklist.Blacklist(blacklist_file)
- if blacklist_file
- else None)
-
- devices = device_utils.DeviceUtils.HealthyDevices(blacklist_file)
- if not devices:
- raise device_errors.NoDevicesError()
- elif requested_devices:
- requested = set(requested_devices)
- available = set(str(d) for d in devices)
- missing = requested.difference(available)
- if missing:
- raise device_errors.DeviceUnreachableError(next(iter(missing)))
- return sorted(device_utils.DeviceUtils(d)
- for d in available.intersection(requested))
- else:
- return devices
-
diff --git a/third_party/catapult/devil/devil/android/tools/script_common_test.py b/third_party/catapult/devil/devil/android/tools/script_common_test.py
deleted file mode 100755
index a226764557..0000000000
--- a/third_party/catapult/devil/devil/android/tools/script_common_test.py
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-import sys
-import unittest
-
-from devil import devil_env
-from devil.android import device_errors
-from devil.android import device_utils
-from devil.android.tools import script_common
-
-with devil_env.SysPath(devil_env.PYMOCK_PATH):
- import mock # pylint: disable=import-error
-
-
-class ScriptCommonTest(unittest.TestCase):
-
- def testGetDevices_noSpecs(self):
- devices = [
- device_utils.DeviceUtils('123'),
- device_utils.DeviceUtils('456'),
- ]
- with mock.patch('devil.android.device_utils.DeviceUtils.HealthyDevices',
- return_value=devices):
- self.assertEquals(
- devices,
- script_common.GetDevices(None, None))
-
- def testGetDevices_withDevices(self):
- devices = [
- device_utils.DeviceUtils('123'),
- device_utils.DeviceUtils('456'),
- ]
- with mock.patch('devil.android.device_utils.DeviceUtils.HealthyDevices',
- return_value=devices):
- self.assertEquals(
- [device_utils.DeviceUtils('456')],
- script_common.GetDevices(['456'], None))
-
- def testGetDevices_missingDevice(self):
- with mock.patch('devil.android.device_utils.DeviceUtils.HealthyDevices',
- return_value=[device_utils.DeviceUtils('123')]):
- with self.assertRaises(device_errors.DeviceUnreachableError):
- script_common.GetDevices(['456'], None)
-
- def testGetDevices_noDevices(self):
- with mock.patch('devil.android.device_utils.DeviceUtils.HealthyDevices',
- return_value=[]):
- with self.assertRaises(device_errors.NoDevicesError):
- script_common.GetDevices(None, None)
-
-
-if __name__ == '__main__':
- sys.exit(unittest.main())
-
diff --git a/third_party/catapult/devil/devil/android/tools/video_recorder.py b/third_party/catapult/devil/devil/android/tools/video_recorder.py
deleted file mode 100755
index a91e649699..0000000000
--- a/third_party/catapult/devil/devil/android/tools/video_recorder.py
+++ /dev/null
@@ -1,175 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Captures a video from an Android device."""
-
-import argparse
-import logging
-import os
-import threading
-import time
-import sys
-
-if __name__ == '__main__':
- sys.path.append(os.path.abspath(os.path.join(
- os.path.dirname(__file__), '..', '..', '..')))
-from devil.android import device_signal
-from devil.android import device_utils
-from devil.android.tools import script_common
-from devil.utils import cmd_helper
-from devil.utils import reraiser_thread
-from devil.utils import timeout_retry
-
-logger = logging.getLogger(__name__)
-
-
-class VideoRecorder(object):
- """Records a screen capture video from an Android Device (KitKat or newer)."""
-
- def __init__(self, device, megabits_per_second=4, size=None,
- rotate=False):
- """Creates a VideoRecorder instance.
-
- Args:
- device: DeviceUtils instance.
- host_file: Path to the video file to store on the host.
- megabits_per_second: Video bitrate in megabits per second. Allowed range
- from 0.1 to 100 mbps.
- size: Video frame size tuple (width, height) or None to use the device
- default.
- rotate: If True, the video will be rotated 90 degrees.
- """
- self._bit_rate = megabits_per_second * 1000 * 1000
- self._device = device
- self._device_file = (
- '%s/screen-recording.mp4' % device.GetExternalStoragePath())
- self._recorder_thread = None
- self._rotate = rotate
- self._size = size
- self._started = threading.Event()
-
- def __enter__(self):
- self.Start()
-
- def Start(self, timeout=None):
- """Start recording video."""
- def screenrecord_started():
- return bool(self._device.GetPids('screenrecord'))
-
- if screenrecord_started():
- raise Exception("Can't run multiple concurrent video captures.")
-
- self._started.clear()
- self._recorder_thread = reraiser_thread.ReraiserThread(self._Record)
- self._recorder_thread.start()
- timeout_retry.WaitFor(
- screenrecord_started, wait_period=1, max_tries=timeout)
- self._started.wait(timeout)
-
- def _Record(self):
- cmd = ['screenrecord', '--verbose', '--bit-rate', str(self._bit_rate)]
- if self._rotate:
- cmd += ['--rotate']
- if self._size:
- cmd += ['--size', '%dx%d' % self._size]
- cmd += [self._device_file]
- for line in self._device.adb.IterShell(
- ' '.join(cmd_helper.SingleQuote(i) for i in cmd), None):
- if line.startswith('Content area is '):
- self._started.set()
-
- def __exit__(self, _exc_type, _exc_value, _traceback):
- self.Stop()
-
- def Stop(self):
- """Stop recording video."""
- if not self._device.KillAll('screenrecord', signum=device_signal.SIGINT,
- quiet=True):
- logger.warning('Nothing to kill: screenrecord was not running')
- self._recorder_thread.join()
-
- def Pull(self, host_file=None):
- """Pull resulting video file from the device.
-
- Args:
- host_file: Path to the video file to store on the host.
- Returns:
- Output video file name on the host.
- """
- # TODO(jbudorick): Merge filename generation with the logic for doing so in
- # DeviceUtils.
- host_file_name = (
- host_file
- or 'screen-recording-%s-%s.mp4' % (
- str(self._device),
- time.strftime('%Y%m%dT%H%M%S', time.localtime())))
- host_file_name = os.path.abspath(host_file_name)
- self._device.PullFile(self._device_file, host_file_name)
- self._device.RemovePath(self._device_file, force=True)
- return host_file_name
-
-
-def main():
- # Parse options.
- parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument('-d', '--device', dest='devices', action='append',
- help='Serial number of Android device to use.')
- parser.add_argument('--blacklist-file', help='Device blacklist JSON file.')
- parser.add_argument('-f', '--file', metavar='FILE',
- help='Save result to file instead of generating a '
- 'timestamped file name.')
- parser.add_argument('-v', '--verbose', action='store_true',
- help='Verbose logging.')
- parser.add_argument('-b', '--bitrate', default=4, type=float,
- help='Bitrate in megabits/s, from 0.1 to 100 mbps, '
- '%default mbps by default.')
- parser.add_argument('-r', '--rotate', action='store_true',
- help='Rotate video by 90 degrees.')
- parser.add_argument('-s', '--size', metavar='WIDTHxHEIGHT',
- help='Frame size to use instead of the device '
- 'screen size.')
- parser.add_argument('host_file', nargs='?',
- help='File to which the video capture will be written.')
-
- args = parser.parse_args()
-
- host_file = args.host_file or args.file
-
- if args.verbose:
- logging.getLogger().setLevel(logging.DEBUG)
-
- size = (tuple(int(i) for i in args.size.split('x'))
- if args.size
- else None)
-
- def record_video(device, stop_recording):
- recorder = VideoRecorder(
- device, megabits_per_second=args.bitrate, size=size, rotate=args.rotate)
- with recorder:
- stop_recording.wait()
-
- f = None
- if host_file:
- root, ext = os.path.splitext(host_file)
- f = '%s_%s%s' % (root, str(device), ext)
- f = recorder.Pull(f)
- print 'Video written to %s' % os.path.abspath(f)
-
- parallel_devices = device_utils.DeviceUtils.parallel(
- script_common.GetDevices(args.devices, args.blacklist_file),
- async=True)
- stop_recording = threading.Event()
- running_recording = parallel_devices.pMap(record_video, stop_recording)
- print 'Recording. Press Enter to stop.',
- sys.stdout.flush()
- raw_input()
- stop_recording.set()
-
- running_recording.pGet(None)
- return 0
-
-
-if __name__ == '__main__':
- sys.exit(main())
diff --git a/third_party/catapult/devil/devil/android/tools/wait_for_devices.py b/third_party/catapult/devil/devil/android/tools/wait_for_devices.py
deleted file mode 100755
index 4bde2cd4b0..0000000000
--- a/third_party/catapult/devil/devil/android/tools/wait_for_devices.py
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/env python
-# 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.
-
-"""Waits for the given devices to be available."""
-
-import argparse
-import os
-import sys
-
-if __name__ == '__main__':
- sys.path.append(
- os.path.abspath(os.path.join(os.path.dirname(__file__),
- '..', '..', '..')))
-
-from devil import devil_env
-from devil.android import device_utils
-from devil.utils import run_tests_helper
-
-
-def main(raw_args):
- parser = argparse.ArgumentParser()
- parser.add_argument('-v', '--verbose', action='count', help='Log more.')
- parser.add_argument('-t', '--timeout', default=30, type=int,
- help='Seconds to wait for the devices.')
- parser.add_argument('--adb-path', help='ADB binary to use.')
- parser.add_argument('device_serials', nargs='*', metavar='SERIAL',
- help='Serials of the devices to wait for.')
-
- args = parser.parse_args(raw_args)
-
- run_tests_helper.SetLogLevel(args.verbose)
-
- devil_dynamic_config = devil_env.EmptyConfig()
- if args.adb_path:
- devil_dynamic_config['dependencies'].update(
- devil_env.LocalConfigItem(
- 'adb', devil_env.GetPlatform(), args.adb_path))
- devil_env.config.Initialize(configs=[devil_dynamic_config])
-
- devices = device_utils.DeviceUtils.HealthyDevices(
- device_arg=args.device_serials)
- parallel_devices = device_utils.DeviceUtils.parallel(devices)
- parallel_devices.WaitUntilFullyBooted(timeout=args.timeout)
- return 0
-
-
-if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))
diff --git a/third_party/catapult/devil/devil/android/valgrind_tools/__init__.py b/third_party/catapult/devil/devil/android/valgrind_tools/__init__.py
deleted file mode 100644
index 0182d4c176..0000000000
--- a/third_party/catapult/devil/devil/android/valgrind_tools/__init__.py
+++ /dev/null
@@ -1,21 +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.
-"""
-Classes in this package define additional actions that need to be taken to run a
-test under some kind of runtime error detection tool.
-
-The interface is intended to be used as follows.
-
-1. For tests that simply run a native process (i.e. no activity is spawned):
-
-Call tool.CopyFiles(device).
-Prepend test command line with tool.GetTestWrapper().
-
-2. For tests that spawn an activity:
-
-Call tool.CopyFiles(device).
-Call tool.SetupEnvironment().
-Run the test as usual.
-Call tool.CleanUpEnvironment().
-"""
diff --git a/third_party/catapult/devil/devil/android/valgrind_tools/base_tool.py b/third_party/catapult/devil/devil/android/valgrind_tools/base_tool.py
deleted file mode 100644
index 2e6e9af3d3..0000000000
--- a/third_party/catapult/devil/devil/android/valgrind_tools/base_tool.py
+++ /dev/null
@@ -1,53 +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.
-
-
-class BaseTool(object):
- """A tool that does nothing."""
- # pylint: disable=R0201
-
- def __init__(self):
- """Does nothing."""
- pass
-
- def GetTestWrapper(self):
- """Returns a string that is to be prepended to the test command line."""
- return ''
-
- def GetUtilWrapper(self):
- """Returns the wrapper name for the utilities.
-
- Returns:
- A string that is to be prepended to the command line of utility
- processes (forwarder, etc.).
- """
- return ''
-
- @classmethod
- def CopyFiles(cls, device):
- """Copies tool-specific files to the device, create directories, etc."""
- pass
-
- def SetupEnvironment(self):
- """Sets up the system environment for a test.
-
- This is a good place to set system properties.
- """
- pass
-
- def CleanUpEnvironment(self):
- """Cleans up environment."""
- pass
-
- def GetTimeoutScale(self):
- """Returns a multiplier that should be applied to timeout values."""
- return 1.0
-
- def NeedsDebugInfo(self):
- """Whether this tool requires debug info.
-
- Returns:
- True if this tool can not work with stripped binaries.
- """
- return False
diff --git a/third_party/catapult/devil/devil/base_error.py b/third_party/catapult/devil/devil/base_error.py
deleted file mode 100644
index 4b896613dc..0000000000
--- a/third_party/catapult/devil/devil/base_error.py
+++ /dev/null
@@ -1,24 +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.
-
-
-class BaseError(Exception):
- """Base error for all test runner errors."""
-
- def __init__(self, message, is_infra_error=False):
- super(BaseError, self).__init__(message)
- self._is_infra_error = is_infra_error
-
- def __eq__(self, other):
- return (self.message == other.message
- and self.is_infra_error == other.is_infra_error)
-
- def __ne__(self, other):
- return not self == other
-
- @property
- def is_infra_error(self):
- """Property to indicate if error was caused by an infrastructure issue."""
- return self._is_infra_error
-
diff --git a/third_party/catapult/devil/devil/constants/__init__.py b/third_party/catapult/devil/devil/constants/__init__.py
deleted file mode 100644
index 50b23dff63..0000000000
--- a/third_party/catapult/devil/devil/constants/__init__.py
+++ /dev/null
@@ -1,3 +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.
diff --git a/third_party/catapult/devil/devil/constants/exit_codes.py b/third_party/catapult/devil/devil/constants/exit_codes.py
deleted file mode 100644
index aaeca4a871..0000000000
--- a/third_party/catapult/devil/devil/constants/exit_codes.py
+++ /dev/null
@@ -1,9 +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.
-
-"""Common exit codes used by devil."""
-
-ERROR = 1
-INFRA = 87
-WARNING = 88
diff --git a/third_party/catapult/devil/devil/devil_dependencies.json b/third_party/catapult/devil/devil/devil_dependencies.json
deleted file mode 100644
index bed6fe10a2..0000000000
--- a/third_party/catapult/devil/devil/devil_dependencies.json
+++ /dev/null
@@ -1,127 +0,0 @@
-{
- "config_type": "BaseConfig",
- "dependencies": {
- "aapt": {
- "cloud_storage_base_folder": "binary_dependencies",
- "cloud_storage_bucket": "chromium-telemetry",
- "file_info": {
- "linux2_x86_64": {
- "cloud_storage_hash": "16ba3180141a2489d7ec99b39fd6e3434a9a373f",
- "download_path": "../bin/deps/linux2/x86_64/bin/aapt"
- }
- }
- },
- "adb": {
- "cloud_storage_base_folder": "binary_dependencies",
- "cloud_storage_bucket": "chromium-telemetry",
- "file_info": {
- "linux2_x86_64": {
- "cloud_storage_hash": "8bd43e3930f6eec643d5dc64cab9e5bb4ddf4909",
- "download_path": "../bin/deps/linux2/x86_64/bin/adb"
- }
- }
- },
- "android_build_tools_libc++": {
- "cloud_storage_base_folder": "binary_dependencies",
- "cloud_storage_bucket": "chromium-telemetry",
- "file_info": {
- "linux2_x86_64": {
- "cloud_storage_hash": "91cdce1e3bd81b2ac1fd380013896d0e2cdb40a0",
- "download_path": "../bin/deps/linux2/x86_64/lib/libc++.so"
- }
- }
- },
- "chromium_commands": {
- "cloud_storage_base_folder": "binary_dependencies",
- "cloud_storage_bucket": "chromium-telemetry",
- "file_info": {
- "linux2_x86_64": {
- "cloud_storage_hash": "4e22f641e4757309510e8d9f933f5aa504574ab6",
- "download_path": "../bin/deps/linux2/x86_64/lib.java/chromium_commands.dex.jar"
- }
- }
- },
- "dexdump": {
- "cloud_storage_base_folder": "binary_dependencies",
- "cloud_storage_bucket": "chromium-telemetry",
- "file_info": {
- "linux2_x86_64": {
- "cloud_storage_hash": "acfb10f7a868baf9bcf446a2d9f8ed6b5d52c3c6",
- "download_path": "../bin/deps/linux2/x86_64/bin/dexdump"
- }
- }
- },
- "fastboot": {
- "cloud_storage_base_folder": "binary_dependencies",
- "cloud_storage_bucket": "chromium-telemetry",
- "file_info": {
- "linux2_x86_64": {
- "cloud_storage_hash": "db9728166f182800eb9d09e9f036d56e105e8235",
- "download_path": "../bin/deps/linux2/x86_64/bin/fastboot"
- }
- }
- },
- "forwarder_device": {
- "cloud_storage_base_folder": "binary_dependencies",
- "cloud_storage_bucket": "chromium-telemetry",
- "file_info": {
- "android_arm64-v8a": {
- "cloud_storage_hash": "f222268d8442979240d1b18de00911a49e548daa",
- "download_path": "../bin/deps/android/arm64-v8a/bin/forwarder_device"
- },
- "android_armeabi-v7a": {
- "cloud_storage_hash": "c15267bf01c26eb0aea4f61c780bbba460c5c981",
- "download_path": "../bin/deps/android/armeabi-v7a/bin/forwarder_device"
- }
- }
- },
- "forwarder_host": {
- "cloud_storage_base_folder": "binary_dependencies",
- "cloud_storage_bucket": "chromium-telemetry",
- "file_info": {
- "linux2_x86_64": {
- "cloud_storage_hash": "8fe69994b670f028484eed475dbffc838c8a57f7",
- "download_path": "../bin/deps/linux2/x86_64/forwarder_host"
- }
- }
- },
- "md5sum_device": {
- "cloud_storage_base_folder": "binary_dependencies",
- "cloud_storage_bucket": "chromium-telemetry",
- "file_info": {
- "android_arm64-v8a": {
- "cloud_storage_hash": "4e7d2dedd9c6321fdc152b06869e09a3c5817904",
- "download_path": "../bin/deps/android/arm64-v8a/bin/md5sum_device"
- },
- "android_armeabi-v7a": {
- "cloud_storage_hash": "39fd90af0f8828202b687f7128393759181c5e2e",
- "download_path": "../bin/deps/android/armeabi-v7a/bin/md5sum_device"
- },
- "android_x86": {
- "cloud_storage_hash": "d5cf42ab5986a69c31c0177b0df499d6bf708df6",
- "download_path": "../bin/deps/android/x86/bin/md5sum_device"
- }
- }
- },
- "md5sum_host": {
- "cloud_storage_base_folder": "binary_dependencies",
- "cloud_storage_bucket": "chromium-telemetry",
- "file_info": {
- "linux2_x86_64": {
- "cloud_storage_hash": "4db5bd5e9bea8880d8bf2caa59d0efb0acc19f74",
- "download_path": "../bin/deps/linux2/x86_64/bin/md5sum_host"
- }
- }
- },
- "split-select": {
- "cloud_storage_base_folder": "binary_dependencies",
- "cloud_storage_bucket": "chromium-telemetry",
- "file_info": {
- "linux2_x86_64": {
- "cloud_storage_hash": "abb9753a8d3efeea4144e328933931729e01571c",
- "download_path": "../bin/deps/linux2/x86_64/bin/split-select"
- }
- }
- }
- }
-} \ No newline at end of file
diff --git a/third_party/catapult/devil/devil/devil_env.py b/third_party/catapult/devil/devil/devil_env.py
deleted file mode 100644
index aa4fe1ee6d..0000000000
--- a/third_party/catapult/devil/devil/devil_env.py
+++ /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.
-
-import contextlib
-import json
-import logging
-import os
-import platform
-import sys
-import tempfile
-import threading
-
-CATAPULT_ROOT_PATH = os.path.abspath(os.path.join(
- os.path.dirname(__file__), '..', '..'))
-DEPENDENCY_MANAGER_PATH = os.path.join(
- CATAPULT_ROOT_PATH, 'dependency_manager')
-PYMOCK_PATH = os.path.join(
- CATAPULT_ROOT_PATH, 'third_party', 'mock')
-
-
-@contextlib.contextmanager
-def SysPath(path):
- sys.path.append(path)
- yield
- if sys.path[-1] != path:
- sys.path.remove(path)
- else:
- sys.path.pop()
-
-with SysPath(DEPENDENCY_MANAGER_PATH):
- import dependency_manager # pylint: disable=import-error
-
-_ANDROID_BUILD_TOOLS = {'aapt', 'dexdump', 'split-select'}
-
-_DEVIL_DEFAULT_CONFIG = os.path.abspath(os.path.join(
- os.path.dirname(__file__), 'devil_dependencies.json'))
-
-_LEGACY_ENVIRONMENT_VARIABLES = {
- 'ADB_PATH': {
- 'dependency_name': 'adb',
- 'platform': 'linux2_x86_64',
- },
- 'ANDROID_SDK_ROOT': {
- 'dependency_name': 'android_sdk',
- 'platform': 'linux2_x86_64',
- },
-}
-
-
-def EmptyConfig():
- return {
- 'config_type': 'BaseConfig',
- 'dependencies': {}
- }
-
-
-def LocalConfigItem(dependency_name, dependency_platform, dependency_path):
- if isinstance(dependency_path, basestring):
- dependency_path = [dependency_path]
- return {
- dependency_name: {
- 'file_info': {
- dependency_platform: {
- 'local_paths': dependency_path
- },
- },
- },
- }
-
-
-def _GetEnvironmentVariableConfig():
- env_config = EmptyConfig()
- path_config = (
- (os.environ.get(k), v)
- for k, v in _LEGACY_ENVIRONMENT_VARIABLES.iteritems())
- path_config = ((p, c) for p, c in path_config if p)
- for p, c in path_config:
- env_config['dependencies'].update(
- LocalConfigItem(c['dependency_name'], c['platform'], p))
- return env_config
-
-
-class _Environment(object):
-
- def __init__(self):
- self._dm_init_lock = threading.Lock()
- self._dm = None
- self._logging_init_lock = threading.Lock()
- self._logging_initialized = False
-
- def Initialize(self, configs=None, config_files=None):
- """Initialize devil's environment from configuration files.
-
- This uses all configurations provided via |configs| and |config_files|
- to determine the locations of devil's dependencies. Configurations should
- all take the form described by py_utils.dependency_manager.BaseConfig.
- If no configurations are provided, a default one will be used if available.
-
- Args:
- configs: An optional list of dict configurations.
- config_files: An optional list of files to load
- """
-
- # Make sure we only initialize self._dm once.
- with self._dm_init_lock:
- if self._dm is None:
- if configs is None:
- configs = []
-
- env_config = _GetEnvironmentVariableConfig()
- if env_config:
- configs.insert(0, env_config)
- self._InitializeRecursive(
- configs=configs,
- config_files=config_files)
- assert self._dm is not None, 'Failed to create dependency manager.'
-
- def _InitializeRecursive(self, configs=None, config_files=None):
- # This recurses through configs to create temporary files for each and
- # take advantage of context managers to appropriately close those files.
- # TODO(jbudorick): Remove this recursion if/when dependency_manager
- # supports loading configurations directly from a dict.
- if configs:
- with tempfile.NamedTemporaryFile(delete=False) as next_config_file:
- try:
- next_config_file.write(json.dumps(configs[0]))
- next_config_file.close()
- self._InitializeRecursive(
- configs=configs[1:],
- config_files=[next_config_file.name] + (config_files or []))
- finally:
- if os.path.exists(next_config_file.name):
- os.remove(next_config_file.name)
- else:
- config_files = config_files or []
- if 'DEVIL_ENV_CONFIG' in os.environ:
- config_files.append(os.environ.get('DEVIL_ENV_CONFIG'))
- config_files.append(_DEVIL_DEFAULT_CONFIG)
-
- self._dm = dependency_manager.DependencyManager(
- [dependency_manager.BaseConfig(c) for c in config_files])
-
- def InitializeLogging(self, log_level, formatter=None, handler=None):
- if self._logging_initialized:
- return
-
- with self._logging_init_lock:
- if self._logging_initialized:
- return
-
- formatter = formatter or logging.Formatter(
- '%(threadName)-4s %(message)s')
- handler = handler or logging.StreamHandler(sys.stdout)
- handler.setFormatter(formatter)
-
- devil_logger = logging.getLogger('devil')
- devil_logger.setLevel(log_level)
- devil_logger.propagate = False
- devil_logger.addHandler(handler)
-
- import py_utils.cloud_storage
- lock_logger = py_utils.cloud_storage.logger
- lock_logger.setLevel(log_level)
- lock_logger.propagate = False
- lock_logger.addHandler(handler)
-
- self._logging_initialized = True
-
- def FetchPath(self, dependency, arch=None, device=None):
- if self._dm is None:
- self.Initialize()
- if dependency in _ANDROID_BUILD_TOOLS:
- self.FetchPath('android_build_tools_libc++', arch=arch, device=device)
- return self._dm.FetchPath(dependency, GetPlatform(arch, device))
-
- def LocalPath(self, dependency, arch=None, device=None):
- if self._dm is None:
- self.Initialize()
- return self._dm.LocalPath(dependency, GetPlatform(arch, device))
-
- def PrefetchPaths(self, dependencies=None, arch=None, device=None):
- return self._dm.PrefetchPaths(
- GetPlatform(arch, device), dependencies=dependencies)
-
-
-def GetPlatform(arch=None, device=None):
- if arch or device:
- return 'android_%s' % (arch or device.product_cpu_abi)
- return '%s_%s' % (sys.platform, platform.machine())
-
-
-config = _Environment()
-
diff --git a/third_party/catapult/devil/devil/devil_env_test.py b/third_party/catapult/devil/devil/devil_env_test.py
deleted file mode 100755
index e78221a070..0000000000
--- a/third_party/catapult/devil/devil/devil_env_test.py
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# pylint: disable=protected-access
-
-import logging
-import sys
-import unittest
-
-from devil import devil_env
-
-_sys_path_before = list(sys.path)
-with devil_env.SysPath(devil_env.PYMOCK_PATH):
- _sys_path_with_pymock = list(sys.path)
- import mock # pylint: disable=import-error
-_sys_path_after = list(sys.path)
-
-
-class DevilEnvTest(unittest.TestCase):
-
- def testSysPath(self):
- self.assertEquals(_sys_path_before, _sys_path_after)
- self.assertEquals(
- _sys_path_before + [devil_env.PYMOCK_PATH],
- _sys_path_with_pymock)
-
- def testGetEnvironmentVariableConfig_configType(self):
- with mock.patch('os.environ.get',
- mock.Mock(side_effect=lambda _env_var: None)):
- env_config = devil_env._GetEnvironmentVariableConfig()
- self.assertEquals('BaseConfig', env_config.get('config_type'))
-
- def testGetEnvironmentVariableConfig_noEnv(self):
- with mock.patch('os.environ.get',
- mock.Mock(side_effect=lambda _env_var: None)):
- env_config = devil_env._GetEnvironmentVariableConfig()
- self.assertEquals({}, env_config.get('dependencies'))
-
- def testGetEnvironmentVariableConfig_adbPath(self):
- def mock_environment(env_var):
- return '/my/fake/adb/path' if env_var == 'ADB_PATH' else None
-
- with mock.patch('os.environ.get',
- mock.Mock(side_effect=mock_environment)):
- env_config = devil_env._GetEnvironmentVariableConfig()
- self.assertEquals(
- {
- 'adb': {
- 'file_info': {
- 'linux2_x86_64': {
- 'local_paths': ['/my/fake/adb/path'],
- },
- },
- },
- },
- env_config.get('dependencies'))
-
-
-if __name__ == '__main__':
- logging.getLogger().setLevel(logging.DEBUG)
- unittest.main(verbosity=2)
diff --git a/third_party/catapult/devil/devil/utils/__init__.py b/third_party/catapult/devil/devil/utils/__init__.py
deleted file mode 100644
index ff84988dbd..0000000000
--- a/third_party/catapult/devil/devil/utils/__init__.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import os
-import sys
-
-def _JoinPath(*path_parts):
- return os.path.abspath(os.path.join(*path_parts))
-
-
-def _AddDirToPythonPath(*path_parts):
- path = _JoinPath(*path_parts)
- if os.path.isdir(path) and path not in sys.path:
- # Some call sites that use Telemetry assume that sys.path[0] is the
- # directory containing the script, so we add these extra paths to right
- # after sys.path[0].
- sys.path.insert(1, path)
-
-_CATAPULT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
- os.path.pardir, os.path.pardir, os.path.pardir)
-
-_AddDirToPythonPath(_CATAPULT_DIR, 'common', 'battor')
diff --git a/third_party/catapult/devil/devil/utils/battor_device_mapping.py b/third_party/catapult/devil/devil/utils/battor_device_mapping.py
deleted file mode 100755
index 8cabb8304e..0000000000
--- a/third_party/catapult/devil/devil/utils/battor_device_mapping.py
+++ /dev/null
@@ -1,309 +0,0 @@
-#!/usr/bin/python
-# 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 script provides tools to map BattOrs to phones.
-
-Phones are identified by the following string:
-
-"Phone serial number" - Serial number of the phone. This can be
-obtained via 'adb devices' or 'usb-devices', and is not expected
-to change for a given phone.
-
-BattOrs are identified by the following two strings:
-
-"BattOr serial number" - Serial number of the BattOr. This can be
-obtained via 'usb-devices', and is not expected to change for
-a given BattOr.
-
-"BattOr path" - The path of the form '/dev/ttyUSB*' that is used
-to communicate with the BattOr (the battor_agent binary takes
-this BattOr path as a parameter). The BattOr path is frequently
-reassigned by the OS, most often when the device is disconnected
-and then reconnected. Thus, the BattOr path cannot be expected
-to be stable.
-
-In a typical application, the user will require the BattOr path
-for the BattOr that is plugged into a given phone. For instance,
-the user will be running tracing on a particular phone, and will
-need to know which BattOr path to use to communicate with the BattOr
-to get the corresponding power trace.
-
-Getting this mapping requires two steps: (1) determining the
-mapping between phone serial numbers and BattOr serial numbers, and
-(2) getting the BattOr path corresponding to a given BattOr serial
-number.
-
-For step (1), we generate a JSON file giving this mapping. This
-JSON file consists of a list of items of the following form:
-[{'phone': <phone serial 1>, 'battor': <battor serial 1>},
-{'phone': <phone serial 2>, 'battor': <battor serial 2>}, ...]
-
-The default way to generate this JSON file is using the function
-GenerateSerialMapFile, which generates a mapping based on assuming
-that the system has two identical USB hubs connected to it, and
-the phone plugged into physical port number 1 on one hub corresponds
-to the BattOr plugged into physical port number 1 on the other hub,
-and similarly with physical port numbers 2, 3, etc. This generates
-the map file based on the structure at the time GenerateSerialMapFile called.
-Note that after the map file is generated, port numbers are no longer used;
-the user could move around the devices in the ports without affecting
-which phone goes with which BattOr. (Thus, if the user wanted to update the
-mapping to match the new port connections, the user would have to
-re-generate this file.)
-
-The script update_mapping.py will do this updating from the command line.
-
-If the user wanted to specify a custom mapping, the user could instead
-create the JSON file manually. (In this case, hubs would not be necessary
-and the physical ports connected would be irrelevant.)
-
-Step (2) is conducted through the function GetBattOrPathFromPhoneSerial,
-which takes a serial number mapping generated via step (1) and a phone
-serial number, then gets the corresponding BattOr serial number from the
-map and determines its BattOr path (e.g. /dev/ttyUSB0). Since BattOr paths
-can change if devices are connected and disconnected (even if connected
-or disconnected via the same port) this function should be called to
-determine the BattOr path every time before connecting to the BattOr.
-
-Note that if there is only one BattOr connected to the system, then
-GetBattOrPathFromPhoneSerial will always return that BattOr and will ignore
-the mapping file. Thus, if the user never has more than one BattOr connected
-to the system, the user will not need to generate mapping files.
-'''
-
-
-import json
-import collections
-
-from battor import battor_error
-from devil.utils import find_usb_devices
-from devil.utils import usb_hubs
-
-
-def GetBattOrList(device_tree_map):
- return [x for x in find_usb_devices.GetTTYList()
- if IsBattOr(x, device_tree_map)]
-
-
-def IsBattOr(tty_string, device_tree_map):
- (bus, device) = find_usb_devices.GetBusDeviceFromTTY(tty_string)
- node = device_tree_map[bus].FindDeviceNumber(device)
- return '0403:6001' in node.desc
-
-
-def GetBattOrSerialNumbers(device_tree_map):
- for x in find_usb_devices.GetTTYList():
- if IsBattOr(x, device_tree_map):
- (bus, device) = find_usb_devices.GetBusDeviceFromTTY(x)
- devnode = device_tree_map[bus].FindDeviceNumber(device)
- yield devnode.serial
-
-
-def ReadSerialMapFile(filename):
- """Reads JSON file giving phone-to-battor serial number map.
-
- Parses a JSON file consisting of a list of items of the following form:
- [{'phone': <phone serial 1>, 'battor': <battor serial 1>},
- {'phone': <phone serial 2>, 'battor': <battor serial 2>}, ...]
-
- indicating which phone serial numbers should be matched with
- which BattOr serial numbers. Returns dictionary of the form:
-
- {<phone serial 1>: <BattOr serial 1>,
- <phone serial 2>: <BattOr serial 2>}
-
- Args:
- filename: Name of file to read.
- """
- result = {}
- with open(filename, 'r') as infile:
- in_dict = json.load(infile)
- for x in in_dict:
- result[x['phone']] = x['battor']
- return result
-
-def WriteSerialMapFile(filename, serial_map):
- """Writes a map of phone serial numbers to BattOr serial numbers to file.
-
- Writes a JSON file consisting of a list of items of the following form:
- [{'phone': <phone serial 1>, 'battor': <battor serial 1>},
- {'phone': <phone serial 2>, 'battor': <battor serial 2>}, ...]
-
- indicating which phone serial numbers should be matched with
- which BattOr serial numbers. Mapping is based on the physical port numbers
- of the hubs that the BattOrs and phones are connected to.
-
- Args:
- filename: Name of file to write.
- serial_map: Serial map {phone: battor}
- """
- result = []
- for (phone, battor) in serial_map.iteritems():
- result.append({'phone': phone, 'battor': battor})
- with open(filename, 'w') as outfile:
- json.dump(result, outfile)
-
-def GenerateSerialMap(hub_types=None):
- """Generates a map of phone serial numbers to BattOr serial numbers.
-
- Generates a dict of:
- {<phone serial 1>: <battor serial 1>,
- <phone serial 2>: <battor serial 2>}
- indicating which phone serial numbers should be matched with
- which BattOr serial numbers. Mapping is based on the physical port numbers
- of the hubs that the BattOrs and phones are connected to.
-
- Args:
- hub_types: List of hub types to check for. If not specified, checks
- for all defined hub types. (see usb_hubs.py for details)
- """
- if hub_types:
- hub_types = [usb_hubs.GetHubType(x) for x in hub_types]
- else:
- hub_types = usb_hubs.ALL_HUBS
-
- devtree = find_usb_devices.GetBusNumberToDeviceTreeMap()
-
- # List of serial numbers in the system that represent BattOrs.
- battor_serials = list(GetBattOrSerialNumbers(devtree))
-
- # If there's only one BattOr in the system, then a serial number ma
- # is not necessary.
- if len(battor_serials) == 1:
- return {}
-
- # List of dictionaries, one for each hub, that maps the physical
- # port number to the serial number of that hub. For instance, in a 2
- # hub system, this could return [{1:'ab', 2:'cd'}, {1:'jkl', 2:'xyz'}]
- # where 'ab' and 'cd' are the phone serial numbers and 'jkl' and 'xyz'
- # are the BattOr serial numbers.
- port_to_serial = find_usb_devices.GetAllPhysicalPortToSerialMaps(
- hub_types, device_tree_map=devtree)
-
- class serials(object):
- def __init__(self):
- self.phone = None
- self.battor = None
-
- # Map of {physical port number: [phone serial #, BattOr serial #]. This
- # map is populated by executing the code below. For instance, in the above
- # example, after the code below is executed, port_to_devices would equal
- # {1: ['ab', 'jkl'], 2: ['cd', 'xyz']}
- port_to_devices = collections.defaultdict(serials)
- for hub in port_to_serial:
- for (port, serial) in hub.iteritems():
- if serial in battor_serials:
- if port_to_devices[port].battor is not None:
- raise battor_error.BattOrError('Multiple BattOrs on same port number')
- else:
- port_to_devices[port].battor = serial
- else:
- if port_to_devices[port].phone is not None:
- raise battor_error.BattOrError('Multiple phones on same port number')
- else:
- port_to_devices[port].phone = serial
-
- # Turn the port_to_devices map into a map of the form
- # {phone serial number: BattOr serial number}.
- result = {}
- for pair in port_to_devices.values():
- if pair.phone is None:
- continue
- if pair.battor is None:
- raise battor_error.BattOrError(
- 'Phone detected with no corresponding BattOr')
- result[pair.phone] = pair.battor
- return result
-
-def GenerateSerialMapFile(filename, hub_types=None):
- """Generates a serial map file and writes it."""
- WriteSerialMapFile(filename, GenerateSerialMap(hub_types))
-
-def _PhoneToPathMap(serial, serial_map, devtree):
- """Maps phone serial number to TTY path, assuming serial map is provided."""
- try:
- battor_serial = serial_map[serial]
- except KeyError:
- raise battor_error.BattOrError('Serial number not found in serial map.')
- for tree in devtree.values():
- for node in tree.AllNodes():
- if isinstance(node, find_usb_devices.USBDeviceNode):
- if node.serial == battor_serial:
- bus_device_to_tty = find_usb_devices.GetBusDeviceToTTYMap()
- bus_device = (node.bus_num, node.device_num)
- try:
- return bus_device_to_tty[bus_device]
- except KeyError:
- raise battor_error.BattOrError(
- 'Device with given serial number not a BattOr '
- '(does not have TTY path)')
-
-
-def GetBattOrPathFromPhoneSerial(serial, serial_map=None,
- serial_map_file=None):
- """Gets the TTY path (e.g. '/dev/ttyUSB0') to communicate with the BattOr.
-
- (1) If serial_map is given, it is treated as a dictionary mapping
- phone serial numbers to BattOr serial numbers. This function will get the
- TTY path for the given BattOr serial number.
-
- (2) If serial_map_file is given, it is treated as the name of a
- phone-to-BattOr mapping file (generated with GenerateSerialMapFile)
- and this will be loaded and used as the dict to map port numbers to
- BattOr serial numbers.
-
- You can only give one of serial_map and serial_map_file.
-
- Args:
- serial: Serial number of phone connected on the same physical port that
- the BattOr is connected to.
- serial_map: Map of phone serial numbers to BattOr serial numbers, given
- as a dictionary.
- serial_map_file: Map of phone serial numbers to BattOr serial numbers,
- given as a file.
- hub_types: List of hub types to check for. Used only if serial_map_file
- is None.
-
- Returns:
- Device string used to communicate with device.
-
- Raises:
- ValueError: If serial number is not given.
- BattOrError: If BattOr not found or unexpected USB topology.
- """
- # If there's only one BattOr connected to the system, just use that one.
- # This allows for use on, e.g., a developer's workstation with no hubs.
- devtree = find_usb_devices.GetBusNumberToDeviceTreeMap()
- all_battors = GetBattOrList(devtree)
- if len(all_battors) == 1:
- return '/dev/' + all_battors[0]
-
- if not serial:
- raise battor_error.BattOrError(
- 'Two or more BattOrs connected, no serial provided')
-
- if serial_map and serial_map_file:
- raise ValueError('Cannot specify both serial_map and serial_map_file')
-
- if serial_map_file:
- serial_map = ReadSerialMapFile(serial_map_file)
-
- tty_string = _PhoneToPathMap(serial, serial_map, devtree)
-
- if not tty_string:
- raise battor_error.BattOrError(
- 'No device with given serial number detected.')
-
- if IsBattOr(tty_string, devtree):
- return '/dev/' + tty_string
- else:
- raise battor_error.BattOrError(
- 'Device with given serial number is not a BattOr.')
-
-if __name__ == '__main__':
- # Main function for testing purposes
- print GenerateSerialMap()
diff --git a/third_party/catapult/devil/devil/utils/cmd_helper.py b/third_party/catapult/devil/devil/utils/cmd_helper.py
deleted file mode 100644
index 06c105fcc5..0000000000
--- a/third_party/catapult/devil/devil/utils/cmd_helper.py
+++ /dev/null
@@ -1,394 +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.
-
-"""A wrapper for subprocess to make calling shell commands easier."""
-
-import logging
-import os
-import pipes
-import select
-import signal
-import string
-import StringIO
-import subprocess
-import sys
-import time
-
-# fcntl is not available on Windows.
-try:
- import fcntl
-except ImportError:
- fcntl = None
-
-logger = logging.getLogger(__name__)
-
-_SafeShellChars = frozenset(string.ascii_letters + string.digits + '@%_-+=:,./')
-
-
-def SingleQuote(s):
- """Return an shell-escaped version of the string using single quotes.
-
- Reliably quote a string which may contain unsafe characters (e.g. space,
- quote, or other special characters such as '$').
-
- The returned value can be used in a shell command line as one token that gets
- to be interpreted literally.
-
- Args:
- s: The string to quote.
-
- Return:
- The string quoted using single quotes.
- """
- return pipes.quote(s)
-
-
-def DoubleQuote(s):
- """Return an shell-escaped version of the string using double quotes.
-
- Reliably quote a string which may contain unsafe characters (e.g. space
- or quote characters), while retaining some shell features such as variable
- interpolation.
-
- The returned value can be used in a shell command line as one token that gets
- to be further interpreted by the shell.
-
- The set of characters that retain their special meaning may depend on the
- shell implementation. This set usually includes: '$', '`', '\', '!', '*',
- and '@'.
-
- Args:
- s: The string to quote.
-
- Return:
- The string quoted using double quotes.
- """
- if not s:
- return '""'
- elif all(c in _SafeShellChars for c in s):
- return s
- else:
- return '"' + s.replace('"', '\\"') + '"'
-
-
-def ShrinkToSnippet(cmd_parts, var_name, var_value):
- """Constructs a shell snippet for a command using a variable to shrink it.
-
- Takes into account all quoting that needs to happen.
-
- Args:
- cmd_parts: A list of command arguments.
- var_name: The variable that holds var_value.
- var_value: The string to replace in cmd_parts with $var_name
-
- Returns:
- A shell snippet that does not include setting the variable.
- """
- def shrink(value):
- parts = (x and SingleQuote(x) for x in value.split(var_value))
- with_substitutions = ('"$%s"' % var_name).join(parts)
- return with_substitutions or "''"
-
- return ' '.join(shrink(part) for part in cmd_parts)
-
-
-def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None):
- # preexec_fn isn't supported on windows.
- if sys.platform == 'win32':
- preexec_fn = None
- else:
- preexec_fn = lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL)
-
- return subprocess.Popen(
- args=args, cwd=cwd, stdout=stdout, stderr=stderr,
- shell=shell, close_fds=True, env=env, preexec_fn=preexec_fn)
-
-
-def Call(args, stdout=None, stderr=None, shell=None, cwd=None, env=None):
- pipe = Popen(args, stdout=stdout, stderr=stderr, shell=shell, cwd=cwd,
- env=env)
- pipe.communicate()
- return pipe.wait()
-
-
-def RunCmd(args, cwd=None):
- """Opens a subprocess to execute a program and returns its return value.
-
- Args:
- args: A string or a sequence of program arguments. The program to execute is
- the string or the first item in the args sequence.
- cwd: If not None, the subprocess's current directory will be changed to
- |cwd| before it's executed.
-
- Returns:
- Return code from the command execution.
- """
- logger.info(str(args) + ' ' + (cwd or ''))
- return Call(args, cwd=cwd)
-
-
-def GetCmdOutput(args, cwd=None, shell=False):
- """Open a subprocess to execute a program and returns its output.
-
- Args:
- args: A string or a sequence of program arguments. The program to execute is
- the string or the first item in the args sequence.
- cwd: If not None, the subprocess's current directory will be changed to
- |cwd| before it's executed.
- shell: Whether to execute args as a shell command.
-
- Returns:
- Captures and returns the command's stdout.
- Prints the command's stderr to logger (which defaults to stdout).
- """
- (_, output) = GetCmdStatusAndOutput(args, cwd, shell)
- return output
-
-
-def _ValidateAndLogCommand(args, cwd, shell):
- if isinstance(args, basestring):
- if not shell:
- raise Exception('string args must be run with shell=True')
- else:
- if shell:
- raise Exception('array args must be run with shell=False')
- args = ' '.join(SingleQuote(c) for c in args)
- if cwd is None:
- cwd = ''
- else:
- cwd = ':' + cwd
- logger.info('[host]%s> %s', cwd, args)
- return args
-
-
-def GetCmdStatusAndOutput(args, cwd=None, shell=False):
- """Executes a subprocess and returns its exit code and output.
-
- Args:
- args: A string or a sequence of program arguments. The program to execute is
- the string or the first item in the args sequence.
- cwd: If not None, the subprocess's current directory will be changed to
- |cwd| before it's executed.
- shell: Whether to execute args as a shell command. Must be True if args
- is a string and False if args is a sequence.
-
- Returns:
- The 2-tuple (exit code, output).
- """
- status, stdout, stderr = GetCmdStatusOutputAndError(
- args, cwd=cwd, shell=shell)
-
- if stderr:
- logger.critical('STDERR: %s', stderr)
- logger.debug('STDOUT: %s%s', stdout[:4096].rstrip(),
- '<truncated>' if len(stdout) > 4096 else '')
- return (status, stdout)
-
-
-def GetCmdStatusOutputAndError(args, cwd=None, shell=False):
- """Executes a subprocess and returns its exit code, output, and errors.
-
- Args:
- args: A string or a sequence of program arguments. The program to execute is
- the string or the first item in the args sequence.
- cwd: If not None, the subprocess's current directory will be changed to
- |cwd| before it's executed.
- shell: Whether to execute args as a shell command. Must be True if args
- is a string and False if args is a sequence.
-
- Returns:
- The 2-tuple (exit code, output).
- """
- _ValidateAndLogCommand(args, cwd, shell)
- pipe = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
- shell=shell, cwd=cwd)
- stdout, stderr = pipe.communicate()
- return (pipe.returncode, stdout, stderr)
-
-
-class TimeoutError(Exception):
- """Module-specific timeout exception."""
-
- def __init__(self, output=None):
- super(TimeoutError, self).__init__()
- self._output = output
-
- @property
- def output(self):
- return self._output
-
-
-def _IterProcessStdout(process, iter_timeout=None, timeout=None,
- buffer_size=4096, poll_interval=1):
- """Iterate over a process's stdout.
-
- This is intentionally not public.
-
- Args:
- process: The process in question.
- iter_timeout: An optional length of time, in seconds, to wait in
- between each iteration. If no output is received in the given
- time, this generator will yield None.
- timeout: An optional length of time, in seconds, during which
- the process must finish. If it fails to do so, a TimeoutError
- will be raised.
- buffer_size: The maximum number of bytes to read (and thus yield) at once.
- poll_interval: The length of time to wait in calls to `select.select`.
- If iter_timeout is set, the remaining length of time in the iteration
- may take precedence.
- Raises:
- TimeoutError: if timeout is set and the process does not complete.
- Yields:
- basestrings of data or None.
- """
-
- assert fcntl, 'fcntl module is required'
- try:
- # Enable non-blocking reads from the child's stdout.
- child_fd = process.stdout.fileno()
- fl = fcntl.fcntl(child_fd, fcntl.F_GETFL)
- fcntl.fcntl(child_fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
-
- end_time = (time.time() + timeout) if timeout else None
- iter_end_time = (time.time() + iter_timeout) if iter_timeout else None
-
- while True:
- if end_time and time.time() > end_time:
- raise TimeoutError()
- if iter_end_time and time.time() > iter_end_time:
- yield None
- iter_end_time = time.time() + iter_timeout
-
- if iter_end_time:
- iter_aware_poll_interval = min(
- poll_interval,
- max(0, iter_end_time - time.time()))
- else:
- iter_aware_poll_interval = poll_interval
-
- read_fds, _, _ = select.select(
- [child_fd], [], [], iter_aware_poll_interval)
- if child_fd in read_fds:
- data = os.read(child_fd, buffer_size)
- if not data:
- break
- yield data
- if process.poll() is not None:
- break
- finally:
- try:
- if process.returncode is None:
- # Make sure the process doesn't stick around if we fail with an
- # exception.
- process.kill()
- except OSError:
- pass
- process.wait()
-
-
-def GetCmdStatusAndOutputWithTimeout(args, timeout, cwd=None, shell=False,
- logfile=None):
- """Executes a subprocess with a timeout.
-
- Args:
- args: List of arguments to the program, the program to execute is the first
- element.
- timeout: the timeout in seconds or None to wait forever.
- cwd: If not None, the subprocess's current directory will be changed to
- |cwd| before it's executed.
- shell: Whether to execute args as a shell command. Must be True if args
- is a string and False if args is a sequence.
- logfile: Optional file-like object that will receive output from the
- command as it is running.
-
- Returns:
- The 2-tuple (exit code, output).
- Raises:
- TimeoutError on timeout.
- """
- _ValidateAndLogCommand(args, cwd, shell)
- output = StringIO.StringIO()
- process = Popen(args, cwd=cwd, shell=shell, stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
- try:
- for data in _IterProcessStdout(process, timeout=timeout):
- if logfile:
- logfile.write(data)
- output.write(data)
- except TimeoutError:
- raise TimeoutError(output.getvalue())
-
- str_output = output.getvalue()
- logger.debug('STDOUT+STDERR: %s%s', str_output[:4096].rstrip(),
- '<truncated>' if len(str_output) > 4096 else '')
- return process.returncode, str_output
-
-
-def IterCmdOutputLines(args, iter_timeout=None, timeout=None, cwd=None,
- shell=False, check_status=True):
- """Executes a subprocess and continuously yields lines from its output.
-
- Args:
- args: List of arguments to the program, the program to execute is the first
- element.
- iter_timeout: Timeout for each iteration, in seconds.
- timeout: Timeout for the entire command, in seconds.
- cwd: If not None, the subprocess's current directory will be changed to
- |cwd| before it's executed.
- shell: Whether to execute args as a shell command. Must be True if args
- is a string and False if args is a sequence.
- check_status: A boolean indicating whether to check the exit status of the
- process after all output has been read.
- Yields:
- The output of the subprocess, line by line.
-
- Raises:
- CalledProcessError if check_status is True and the process exited with a
- non-zero exit status.
- """
- cmd = _ValidateAndLogCommand(args, cwd, shell)
- process = Popen(args, cwd=cwd, shell=shell, stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
- return _IterCmdOutputLines(
- process, cmd, iter_timeout=iter_timeout, timeout=timeout,
- check_status=check_status)
-
-def _IterCmdOutputLines(process, cmd, iter_timeout=None, timeout=None,
- check_status=True):
- buffer_output = ''
-
- iter_end = None
- cur_iter_timeout = None
- if iter_timeout:
- iter_end = time.time() + iter_timeout
- cur_iter_timeout = iter_timeout
-
- for data in _IterProcessStdout(process, iter_timeout=cur_iter_timeout,
- timeout=timeout):
- if iter_timeout:
- # Check whether the current iteration has timed out.
- cur_iter_timeout = iter_end - time.time()
- if data is None or cur_iter_timeout < 0:
- yield None
- iter_end = time.time() + iter_timeout
- continue
- else:
- assert data is not None, (
- 'Iteration received no data despite no iter_timeout being set. '
- 'cmd: %s' % cmd)
-
- # Construct lines to yield from raw data.
- buffer_output += data
- has_incomplete_line = buffer_output[-1] not in '\r\n'
- lines = buffer_output.splitlines()
- buffer_output = lines.pop() if has_incomplete_line else ''
- for line in lines:
- yield line
- if iter_timeout:
- iter_end = time.time() + iter_timeout
-
- if buffer_output:
- yield buffer_output
- if check_status and process.returncode:
- raise subprocess.CalledProcessError(process.returncode, cmd)
diff --git a/third_party/catapult/devil/devil/utils/cmd_helper_test.py b/third_party/catapult/devil/devil/utils/cmd_helper_test.py
deleted file mode 100755
index 783c4137c8..0000000000
--- a/third_party/catapult/devil/devil/utils/cmd_helper_test.py
+++ /dev/null
@@ -1,262 +0,0 @@
-#!/usr/bin/env python
-# 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.
-
-"""Tests for the cmd_helper module."""
-
-import unittest
-import subprocess
-import time
-
-from devil import devil_env
-from devil.utils import cmd_helper
-
-with devil_env.SysPath(devil_env.PYMOCK_PATH):
- import mock # pylint: disable=import-error
-
-
-class CmdHelperSingleQuoteTest(unittest.TestCase):
-
- def testSingleQuote_basic(self):
- self.assertEquals('hello',
- cmd_helper.SingleQuote('hello'))
-
- def testSingleQuote_withSpaces(self):
- self.assertEquals("'hello world'",
- cmd_helper.SingleQuote('hello world'))
-
- def testSingleQuote_withUnsafeChars(self):
- self.assertEquals("""'hello'"'"'; rm -rf /'""",
- cmd_helper.SingleQuote("hello'; rm -rf /"))
-
- def testSingleQuote_dontExpand(self):
- test_string = 'hello $TEST_VAR'
- cmd = 'TEST_VAR=world; echo %s' % cmd_helper.SingleQuote(test_string)
- self.assertEquals(test_string,
- cmd_helper.GetCmdOutput(cmd, shell=True).rstrip())
-
-
-class CmdHelperDoubleQuoteTest(unittest.TestCase):
-
- def testDoubleQuote_basic(self):
- self.assertEquals('hello',
- cmd_helper.DoubleQuote('hello'))
-
- def testDoubleQuote_withSpaces(self):
- self.assertEquals('"hello world"',
- cmd_helper.DoubleQuote('hello world'))
-
- def testDoubleQuote_withUnsafeChars(self):
- self.assertEquals('''"hello\\"; rm -rf /"''',
- cmd_helper.DoubleQuote('hello"; rm -rf /'))
-
- def testSingleQuote_doExpand(self):
- test_string = 'hello $TEST_VAR'
- cmd = 'TEST_VAR=world; echo %s' % cmd_helper.DoubleQuote(test_string)
- self.assertEquals('hello world',
- cmd_helper.GetCmdOutput(cmd, shell=True).rstrip())
-
-
-class CmdHelperShinkToSnippetTest(unittest.TestCase):
-
- def testShrinkToSnippet_noArgs(self):
- self.assertEquals('foo',
- cmd_helper.ShrinkToSnippet(['foo'], 'a', 'bar'))
- self.assertEquals("'foo foo'",
- cmd_helper.ShrinkToSnippet(['foo foo'], 'a', 'bar'))
- self.assertEquals('"$a"\' bar\'',
- cmd_helper.ShrinkToSnippet(['foo bar'], 'a', 'foo'))
- self.assertEquals('\'foo \'"$a"',
- cmd_helper.ShrinkToSnippet(['foo bar'], 'a', 'bar'))
- self.assertEquals('foo"$a"',
- cmd_helper.ShrinkToSnippet(['foobar'], 'a', 'bar'))
-
- def testShrinkToSnippet_singleArg(self):
- self.assertEquals("foo ''",
- cmd_helper.ShrinkToSnippet(['foo', ''], 'a', 'bar'))
- self.assertEquals("foo foo",
- cmd_helper.ShrinkToSnippet(['foo', 'foo'], 'a', 'bar'))
- self.assertEquals('"$a" "$a"',
- cmd_helper.ShrinkToSnippet(['foo', 'foo'], 'a', 'foo'))
- self.assertEquals('foo "$a""$a"',
- cmd_helper.ShrinkToSnippet(['foo', 'barbar'], 'a', 'bar'))
- self.assertEquals('foo "$a"\' \'"$a"',
- cmd_helper.ShrinkToSnippet(['foo', 'bar bar'], 'a', 'bar'))
- self.assertEquals('foo "$a""$a"\' \'',
- cmd_helper.ShrinkToSnippet(['foo', 'barbar '], 'a', 'bar'))
- self.assertEquals('foo \' \'"$a""$a"\' \'',
- cmd_helper.ShrinkToSnippet(['foo', ' barbar '], 'a', 'bar'))
-
-
-_DEFAULT = 'DEFAULT'
-
-
-class _ProcessOutputEvent(object):
-
- def __init__(self, select_fds=_DEFAULT, read_contents=None, ts=_DEFAULT):
- self.select_fds = select_fds
- self.read_contents = read_contents
- self.ts = ts
-
-
-class _MockProcess(object):
-
- def __init__(self, output_sequence=None, return_value=0):
-
- # Arbitrary.
- fake_stdout_fileno = 25
-
- self.mock_proc = mock.MagicMock(spec=subprocess.Popen)
- self.mock_proc.stdout = mock.MagicMock()
- self.mock_proc.stdout.fileno = mock.MagicMock(
- return_value=fake_stdout_fileno)
- self.mock_proc.returncode = None
-
- self._return_value = return_value
-
- # This links the behavior of os.read, select.select, time.time, and
- # <process>.poll. The output sequence can be thought of as a list of
- # return values for select.select with corresponding return values for
- # the other calls at any time between that select call and the following
- # one. We iterate through the sequence only on calls to select.select.
- #
- # os.read is a special case, though, where we only return a given chunk
- # of data *once* after a given call to select.
-
- if not output_sequence:
- output_sequence = []
-
- # Use an leading element to make the iteration logic work.
- initial_seq_element = _ProcessOutputEvent(
- _DEFAULT, '',
- output_sequence[0].ts if output_sequence else _DEFAULT)
- output_sequence.insert(0, initial_seq_element)
-
- for o in output_sequence:
- if o.select_fds == _DEFAULT:
- if o.read_contents is None:
- o.select_fds = []
- else:
- o.select_fds = [fake_stdout_fileno]
- if o.ts == _DEFAULT:
- o.ts = time.time()
- self._output_sequence = output_sequence
-
- self._output_seq_index = 0
- self._read_flags = [False] * len(output_sequence)
-
- def read_side_effect(*_args, **_kwargs):
- if self._read_flags[self._output_seq_index]:
- return None
- self._read_flags[self._output_seq_index] = True
- return self._output_sequence[self._output_seq_index].read_contents
-
- def select_side_effect(*_args, **_kwargs):
- if self._output_seq_index is None:
- self._output_seq_index = 0
- else:
- self._output_seq_index += 1
- return (self._output_sequence[self._output_seq_index].select_fds,
- None, None)
-
- def time_side_effect(*_args, **_kwargs):
- return self._output_sequence[self._output_seq_index].ts
-
- def poll_side_effect(*_args, **_kwargs):
- if self._output_seq_index >= len(self._output_sequence) - 1:
- self.mock_proc.returncode = self._return_value
- return self.mock_proc.returncode
-
- mock_read = mock.MagicMock(side_effect=read_side_effect)
- mock_select = mock.MagicMock(side_effect=select_side_effect)
- mock_time = mock.MagicMock(side_effect=time_side_effect)
- self.mock_proc.poll = mock.MagicMock(side_effect=poll_side_effect)
-
- # Set up but *do not start* the mocks.
- self._mocks = [
- mock.patch('fcntl.fcntl'),
- mock.patch('os.read', new=mock_read),
- mock.patch('select.select', new=mock_select),
- mock.patch('time.time', new=mock_time),
- ]
-
- def __enter__(self):
- for m in self._mocks:
- m.__enter__()
- return self.mock_proc
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- for m in reversed(self._mocks):
- m.__exit__(exc_type, exc_val, exc_tb)
-
-
-class CmdHelperIterCmdOutputLinesTest(unittest.TestCase):
- """Test IterCmdOutputLines with some calls to the unix 'seq' command."""
-
- # This calls _IterCmdOutputLines rather than IterCmdOutputLines s.t. it
- # can mock the process.
- # pylint: disable=protected-access
-
- _SIMPLE_OUTPUT_SEQUENCE = [
- _ProcessOutputEvent(read_contents='1\n2\n'),
- ]
-
- def testIterCmdOutputLines_success(self):
- with _MockProcess(
- output_sequence=self._SIMPLE_OUTPUT_SEQUENCE) as mock_proc:
- for num, line in enumerate(
- cmd_helper._IterCmdOutputLines(mock_proc, 'mock_proc'), 1):
- self.assertEquals(num, int(line))
-
- def testIterCmdOutputLines_exitStatusFail(self):
- with self.assertRaises(subprocess.CalledProcessError):
- with _MockProcess(output_sequence=self._SIMPLE_OUTPUT_SEQUENCE,
- return_value=1) as mock_proc:
- for num, line in enumerate(
- cmd_helper._IterCmdOutputLines(mock_proc, 'mock_proc'), 1):
- self.assertEquals(num, int(line))
- # after reading all the output we get an exit status of 1
-
- def testIterCmdOutputLines_exitStatusIgnored(self):
- with _MockProcess(output_sequence=self._SIMPLE_OUTPUT_SEQUENCE,
- return_value=1) as mock_proc:
- for num, line in enumerate(
- cmd_helper._IterCmdOutputLines(
- mock_proc, 'mock_proc', check_status=False),
- 1):
- self.assertEquals(num, int(line))
-
- def testIterCmdOutputLines_exitStatusSkipped(self):
- with _MockProcess(output_sequence=self._SIMPLE_OUTPUT_SEQUENCE,
- return_value=1) as mock_proc:
- for num, line in enumerate(
- cmd_helper._IterCmdOutputLines(mock_proc, 'mock_proc'), 1):
- self.assertEquals(num, int(line))
- # no exception will be raised because we don't attempt to read past
- # the end of the output and, thus, the status never gets checked
- if num == 2:
- break
-
- def testIterCmdOutputLines_delay(self):
- output_sequence = [
- _ProcessOutputEvent(read_contents='1\n2\n', ts=1),
- _ProcessOutputEvent(read_contents=None, ts=2),
- _ProcessOutputEvent(read_contents='Awake', ts=10),
- ]
- with _MockProcess(output_sequence=output_sequence) as mock_proc:
- for num, line in enumerate(
- cmd_helper._IterCmdOutputLines(mock_proc, 'mock_proc',
- iter_timeout=5), 1):
- if num <= 2:
- self.assertEquals(num, int(line))
- elif num == 3:
- self.assertEquals(None, line)
- elif num == 4:
- self.assertEquals('Awake', line)
- else:
- self.fail()
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/third_party/catapult/devil/devil/utils/file_utils.py b/third_party/catapult/devil/devil/utils/file_utils.py
deleted file mode 100644
index dc5a9efc94..0000000000
--- a/third_party/catapult/devil/devil/utils/file_utils.py
+++ /dev/null
@@ -1,31 +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.
-
-import os
-
-
-def MergeFiles(dest_file, source_files):
- """Merge list of files into single destination file.
-
- Args:
- dest_file: File to be written to.
- source_files: List of files to be merged. Will be merged in the order they
- appear in the list.
- """
- if not os.path.exists(os.path.dirname(dest_file)):
- os.makedirs(os.path.dirname(dest_file))
- try:
- with open(dest_file, 'w') as dest_f:
- for source_file in source_files:
- with open(source_file, 'r') as source_f:
- dest_f.write(source_f.read())
- except Exception as e: # pylint: disable=broad-except
- # Something went wrong when creating dest_file. Cleaning up.
- try:
- os.remove(dest_file)
- except OSError:
- pass
- raise e
-
-
diff --git a/third_party/catapult/devil/devil/utils/find_usb_devices.py b/third_party/catapult/devil/devil/utils/find_usb_devices.py
deleted file mode 100755
index 0e0f4d5666..0000000000
--- a/third_party/catapult/devil/devil/utils/find_usb_devices.py
+++ /dev/null
@@ -1,532 +0,0 @@
-#!/usr/bin/python
-# 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 re
-import sys
-import argparse
-
-from devil.utils import cmd_helper
-from devil.utils import usb_hubs
-from devil.utils import lsusb
-
-# Note: In the documentation below, "virtual port" refers to the port number
-# as observed by the system (e.g. by usb-devices) and "physical port" refers
-# to the physical numerical label on the physical port e.g. on a USB hub.
-# The mapping between virtual and physical ports is not always the identity
-# (e.g. the port labeled "1" on a USB hub does not always show up as "port 1"
-# when you plug something into it) but, as far as we are aware, the mapping
-# between virtual and physical ports is always the same for a given
-# model of USB hub. When "port number" is referenced without specifying, it
-# means the virtual port number.
-
-
-# Wrapper functions for system commands to get output. These are in wrapper
-# functions so that they can be more easily mocked-out for tests.
-def _GetParsedLSUSBOutput():
- return lsusb.lsusb()
-
-
-def _GetUSBDevicesOutput():
- return cmd_helper.GetCmdOutput(['usb-devices'])
-
-
-def _GetTtyUSBInfo(tty_string):
- cmd = ['udevadm', 'info', '--name=/dev/' + tty_string, '--attribute-walk']
- return cmd_helper.GetCmdOutput(cmd)
-
-
-def _GetCommList():
- return cmd_helper.GetCmdOutput('ls /dev', shell=True)
-
-
-def GetTTYList():
- return [x for x in _GetCommList().splitlines() if 'ttyUSB' in x]
-
-
-# Class to identify nodes in the USB topology. USB topology is organized as
-# a tree.
-class USBNode(object):
- def __init__(self):
- self._port_to_node = {}
-
- @property
- def desc(self):
- raise NotImplementedError
-
- @property
- def info(self):
- raise NotImplementedError
-
- @property
- def device_num(self):
- raise NotImplementedError
-
- @property
- def bus_num(self):
- raise NotImplementedError
-
- def HasPort(self, port):
- """Determines if this device has a device connected to the given port."""
- return port in self._port_to_node
-
- def PortToDevice(self, port):
- """Gets the device connected to the given port on this device."""
- return self._port_to_node[port]
-
- def Display(self, port_chain='', info=False):
- """Displays information about this node and its descendants.
-
- Output format is, e.g. 1:3:3:Device 42 (ID 1234:5678 Some Device)
- meaning that from the bus, if you look at the device connected
- to port 1, then the device connected to port 3 of that,
- then the device connected to port 3 of that, you get the device
- assigned device number 42, which is Some Device. Note that device
- numbers will be reassigned whenever a connected device is powercycled
- or reinserted, but port numbers stay the same as long as the device
- is reinserted back into the same physical port.
-
- Args:
- port_chain: [string] Chain of ports from bus to this node (e.g. '2:4:')
- info: [bool] Whether to display detailed info as well.
- """
- raise NotImplementedError
-
- def AddChild(self, port, device):
- """Adds child to the device tree.
-
- Args:
- port: [int] Port number of the device.
- device: [USBDeviceNode] Device to add.
-
- Raises:
- ValueError: If device already has a child at the given port.
- """
- if self.HasPort(port):
- raise ValueError('Duplicate port number')
- else:
- self._port_to_node[port] = device
-
- def AllNodes(self):
- """Generator that yields this node and all of its descendants.
-
- Yields:
- [USBNode] First this node, then each of its descendants (recursively)
- """
- yield self
- for child_node in self._port_to_node.values():
- for descendant_node in child_node.AllNodes():
- yield descendant_node
-
- def FindDeviceNumber(self, findnum):
- """Find device with given number in tree
-
- Searches the portion of the device tree rooted at this node for
- a device with the given device number.
-
- Args:
- findnum: [int] Device number to search for.
-
- Returns:
- [USBDeviceNode] Node that is found.
- """
- for node in self.AllNodes():
- if node.device_num == findnum:
- return node
- return None
-
-
-class USBDeviceNode(USBNode):
- def __init__(self, bus_num=0, device_num=0, serial=None, info=None):
- """Class that represents a device in USB tree.
-
- Args:
- bus_num: [int] Bus number that this node is attached to.
- device_num: [int] Device number of this device (or 0, if this is a bus)
- serial: [string] Serial number.
- info: [dict] Map giving detailed device info.
- """
- super(USBDeviceNode, self).__init__()
- self._bus_num = bus_num
- self._device_num = device_num
- self._serial = serial
- self._info = {} if info is None else info
-
- #override
- @property
- def desc(self):
- return self._info.get('desc')
-
- #override
- @property
- def info(self):
- return self._info
-
- #override
- @property
- def device_num(self):
- return self._device_num
-
- #override
- @property
- def bus_num(self):
- return self._bus_num
-
- @property
- def serial(self):
- return self._serial
-
- @serial.setter
- def serial(self, serial):
- self._serial = serial
-
- #override
- def Display(self, port_chain='', info=False):
- print '%s Device %d (%s)' % (port_chain, self.device_num, self.desc)
- if info:
- print self.info
- for (port, device) in self._port_to_node.iteritems():
- device.Display('%s%d:' % (port_chain, port), info=info)
-
-
-class USBBusNode(USBNode):
- def __init__(self, bus_num=0):
- """Class that represents a node (either a bus or device) in USB tree.
-
- Args:
- is_bus: [bool] If true, node is bus; if not, node is device.
- bus_num: [int] Bus number that this node is attached to.
- device_num: [int] Device number of this device (or 0, if this is a bus)
- desc: [string] Short description of device.
- serial: [string] Serial number.
- info: [dict] Map giving detailed device info.
- port_to_dev: [dict(int:USBDeviceNode)]
- Maps port # to device connected to port.
- """
- super(USBBusNode, self).__init__()
- self._bus_num = bus_num
-
- #override
- @property
- def desc(self):
- return 'BUS %d' % self._bus_num
-
- #override
- @property
- def info(self):
- return {}
-
- #override
- @property
- def device_num(self):
- return -1
-
- #override
- @property
- def bus_num(self):
- return self._bus_num
-
- #override
- def Display(self, port_chain='', info=False):
- print "=== %s ===" % self.desc
- for (port, device) in self._port_to_node.iteritems():
- device.Display('%s%d:' % (port_chain, port), info=info)
-
-
-_T_LINE_REGEX = re.compile(r'T: Bus=(?P<bus>\d{2}) Lev=(?P<lev>\d{2}) '
- r'Prnt=(?P<prnt>\d{2,3}) Port=(?P<port>\d{2}) '
- r'Cnt=(?P<cnt>\d{2}) Dev#=(?P<dev>.{3}) .*')
-
-_S_LINE_REGEX = re.compile(r'S: SerialNumber=(?P<serial>.*)')
-_LSUSB_BUS_DEVICE_RE = re.compile(r'^Bus (\d{3}) Device (\d{3}): (.*)')
-
-
-def GetBusNumberToDeviceTreeMap(fast=True):
- """Gets devices currently attached.
-
- Args:
- fast [bool]: whether to do it fast (only get description, not
- the whole dictionary, from lsusb)
-
- Returns:
- map of {bus number: bus object}
- where the bus object has all the devices attached to it in a tree.
- """
- if fast:
- info_map = {}
- for line in lsusb.raw_lsusb().splitlines():
- match = _LSUSB_BUS_DEVICE_RE.match(line)
- if match:
- info_map[(int(match.group(1)), int(match.group(2)))] = (
- {'desc':match.group(3)})
- else:
- info_map = {((int(line['bus']), int(line['device']))): line
- for line in _GetParsedLSUSBOutput()}
-
-
- tree = {}
- bus_num = -1
- for line in _GetUSBDevicesOutput().splitlines():
- match = _T_LINE_REGEX.match(line)
- if match:
- bus_num = int(match.group('bus'))
- parent_num = int(match.group('prnt'))
- # usb-devices starts counting ports from 0, so add 1
- port_num = int(match.group('port')) + 1
- device_num = int(match.group('dev'))
-
- # create new bus if necessary
- if bus_num not in tree:
- tree[bus_num] = USBBusNode(bus_num=bus_num)
-
- # create the new device
- new_device = USBDeviceNode(bus_num=bus_num,
- device_num=device_num,
- info=info_map.get((bus_num, device_num),
- {'desc': 'NOT AVAILABLE'}))
-
- # add device to bus
- if parent_num != 0:
- tree[bus_num].FindDeviceNumber(parent_num).AddChild(
- port_num, new_device)
- else:
- tree[bus_num].AddChild(port_num, new_device)
-
- match = _S_LINE_REGEX.match(line)
- if match:
- if bus_num == -1:
- raise ValueError('S line appears before T line in input file')
- # put the serial number in the device
- tree[bus_num].FindDeviceNumber(device_num).serial = match.group('serial')
-
- return tree
-
-
-def GetHubsOnBus(bus, hub_types):
- """Scans for all hubs on a bus of given hub types.
-
- Args:
- bus: [USBNode] Bus object.
- hub_types: [iterable(usb_hubs.HubType)] Possible types of hubs.
-
- Yields:
- Sequence of tuples representing (hub, type of hub)
- """
- for device in bus.AllNodes():
- for hub_type in hub_types:
- if hub_type.IsType(device):
- yield (device, hub_type)
-
-
-def GetPhysicalPortToNodeMap(hub, hub_type):
- """Gets physical-port:node mapping for a given hub.
- Args:
- hub: [USBNode] Hub to get map for.
- hub_type: [usb_hubs.HubType] Which type of hub it is.
-
- Returns:
- Dict of {physical port: node}
- """
- port_device = hub_type.GetPhysicalPortToNodeTuples(hub)
- return {port: device for (port, device) in port_device}
-
-
-def GetPhysicalPortToBusDeviceMap(hub, hub_type):
- """Gets physical-port:(bus#, device#) mapping for a given hub.
- Args:
- hub: [USBNode] Hub to get map for.
- hub_type: [usb_hubs.HubType] Which type of hub it is.
-
- Returns:
- Dict of {physical port: (bus number, device number)}
- """
- port_device = hub_type.GetPhysicalPortToNodeTuples(hub)
- return {port: (device.bus_num, device.device_num)
- for (port, device) in port_device}
-
-
-def GetPhysicalPortToSerialMap(hub, hub_type):
- """Gets physical-port:serial# mapping for a given hub.
-
- Args:
- hub: [USBNode] Hub to get map for.
- hub_type: [usb_hubs.HubType] Which type of hub it is.
-
- Returns:
- Dict of {physical port: serial number)}
- """
- port_device = hub_type.GetPhysicalPortToNodeTuples(hub)
- return {port: device.serial
- for (port, device) in port_device
- if device.serial}
-
-
-def GetPhysicalPortToTTYMap(device, hub_type):
- """Gets physical-port:tty-string mapping for a given hub.
- Args:
- hub: [USBNode] Hub to get map for.
- hub_type: [usb_hubs.HubType] Which type of hub it is.
-
- Returns:
- Dict of {physical port: tty-string)}
- """
- port_device = hub_type.GetPhysicalPortToNodeTuples(device)
- bus_device_to_tty = GetBusDeviceToTTYMap()
- return {port: bus_device_to_tty[(device.bus_num, device.device_num)]
- for (port, device) in port_device
- if (device.bus_num, device.device_num) in bus_device_to_tty}
-
-
-def CollectHubMaps(hub_types, map_func, device_tree_map=None, fast=False):
- """Runs a function on all hubs in the system and collects their output.
-
- Args:
- hub_types: [usb_hubs.HubType] List of possible hub types.
- map_func: [string] Function to run on each hub.
- device_tree: Previously constructed device tree map, if any.
- fast: Whether to construct device tree fast, if not already provided
-
- Yields:
- Sequence of dicts of {physical port: device} where the type of
- device depends on the ident keyword. Each dict is a separate hub.
- """
- if device_tree_map is None:
- device_tree_map = GetBusNumberToDeviceTreeMap(fast=fast)
- for bus in device_tree_map.values():
- for (hub, hub_type) in GetHubsOnBus(bus, hub_types):
- yield map_func(hub, hub_type)
-
-
-def GetAllPhysicalPortToNodeMaps(hub_types, **kwargs):
- return CollectHubMaps(hub_types, GetPhysicalPortToNodeMap, **kwargs)
-
-
-def GetAllPhysicalPortToBusDeviceMaps(hub_types, **kwargs):
- return CollectHubMaps(hub_types, GetPhysicalPortToBusDeviceMap, **kwargs)
-
-
-def GetAllPhysicalPortToSerialMaps(hub_types, **kwargs):
- return CollectHubMaps(hub_types, GetPhysicalPortToSerialMap, **kwargs)
-
-
-def GetAllPhysicalPortToTTYMaps(hub_types, **kwargs):
- return CollectHubMaps(hub_types, GetPhysicalPortToTTYMap, **kwargs)
-
-
-_BUS_NUM_REGEX = re.compile(r'.*ATTRS{busnum}=="(\d*)".*')
-_DEVICE_NUM_REGEX = re.compile(r'.*ATTRS{devnum}=="(\d*)".*')
-
-
-def GetBusDeviceFromTTY(tty_string):
- """Gets bus and device number connected to a ttyUSB port.
-
- Args:
- tty_string: [String] Identifier for ttyUSB (e.g. 'ttyUSB0')
-
- Returns:
- Tuple (bus, device) giving device connected to that ttyUSB.
-
- Raises:
- ValueError: If bus and device information could not be found.
- """
- bus_num = None
- device_num = None
- # Expected output of GetCmdOutput should be something like:
- # looking at device /devices/something/.../.../...
- # KERNELS="ttyUSB0"
- # SUBSYSTEMS=...
- # DRIVERS=...
- # ATTRS{foo}=...
- # ATTRS{bar}=...
- # ...
- for line in _GetTtyUSBInfo(tty_string).splitlines():
- bus_match = _BUS_NUM_REGEX.match(line)
- device_match = _DEVICE_NUM_REGEX.match(line)
- if bus_match and bus_num == None:
- bus_num = int(bus_match.group(1))
- if device_match and device_num == None:
- device_num = int(device_match.group(1))
- if bus_num is None or device_num is None:
- raise ValueError('Info not found')
- return (bus_num, device_num)
-
-
-def GetBusDeviceToTTYMap():
- """Gets all mappings from (bus, device) to ttyUSB string.
-
- Gets mapping from (bus, device) to ttyUSB string (e.g. 'ttyUSB0'),
- for all ttyUSB strings currently active.
-
- Returns:
- [dict] Dict that maps (bus, device) to ttyUSB string
- """
- result = {}
- for tty in GetTTYList():
- result[GetBusDeviceFromTTY(tty)] = tty
- return result
-
-
-# This dictionary described the mapping between physical and
-# virtual ports on a Plugable 7-Port Hub (model USB2-HUB7BC).
-# Keys are the virtual ports, values are the physical port.
-# The entry 4:{1:4, 2:3, 3:2, 4:1} indicates that virtual port
-# 4 connects to another 'virtual' hub that itself has the
-# virtual-to-physical port mapping {1:4, 2:3, 3:2, 4:1}.
-
-
-def TestUSBTopologyScript():
- """Test display and hub identification."""
- # Identification criteria for Plugable 7-Port Hub
- print '==== USB TOPOLOGY SCRIPT TEST ===='
-
- # Display devices
- print '==== DEVICE DISPLAY ===='
- device_trees = GetBusNumberToDeviceTreeMap()
- for device_tree in device_trees.values():
- device_tree.Display()
- print
-
- # Display TTY information about devices plugged into hubs.
- print '==== TTY INFORMATION ===='
- for port_map in GetAllPhysicalPortToTTYMaps(
- usb_hubs.ALL_HUBS, device_tree_map=device_trees):
- print port_map
- print
-
- # Display serial number information about devices plugged into hubs.
- print '==== SERIAL NUMBER INFORMATION ===='
- for port_map in GetAllPhysicalPortToSerialMaps(
- usb_hubs.ALL_HUBS, device_tree_map=device_trees):
- print port_map
-
-
- return 0
-
-
-def parse_options(argv):
- """Parses and checks the command-line options.
-
- Returns:
- A tuple containing the options structure and a list of categories to
- be traced.
- """
- USAGE = '''./find_usb_devices [--help]
- This script shows the mapping between USB devices and port numbers.
- Clients are not intended to call this script from the command line.
- Clients are intended to call the functions in this script directly.
- For instance, GetAllPhysicalPortToSerialMaps(...)
- Running this script with --help will display this message.
- Running this script without --help will display information about
- devices attached, TTY mapping, and serial number mapping,
- for testing purposes. See design document for API documentation.
- '''
- parser = argparse.ArgumentParser(usage=USAGE)
- return parser.parse_args(argv[1:])
-
-def main():
- parse_options(sys.argv)
- TestUSBTopologyScript()
-
-if __name__ == "__main__":
- sys.exit(main())
diff --git a/third_party/catapult/devil/devil/utils/find_usb_devices_test.py b/third_party/catapult/devil/devil/utils/find_usb_devices_test.py
deleted file mode 100755
index e8b00c85ee..0000000000
--- a/third_party/catapult/devil/devil/utils/find_usb_devices_test.py
+++ /dev/null
@@ -1,379 +0,0 @@
-#!/usr/bin/env python
-# 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.
-
-# pylint: disable=protected-access
-
-"""
-Unit tests for the contents of find_usb_devices.py.
-
-Device tree for these tests is as follows:
-Bus 001:
-1: Device 011 "foo"
-2: Device 012 "bar"
-3: Device 013 "baz"
-
-Bus 002:
-1: Device 011 "quux"
-2: Device 020 "My Test HUB" #hub 1
-2:1: Device 021 "battor_p7_h1_t0" #physical port 7 on hub 1, on ttyUSB0
-2:3: Device 022 "battor_p5_h1_t1" #physical port 5 on hub 1, on ttyUSB1
-2:4: Device 023 "My Test Internal HUB" #internal section of hub 1
-2:4:2: Device 024 "battor_p3_h1_t2" #physical port 3 on hub 1, on ttyUSB2
-2:4:3: Device 026 "Not a Battery Monitor" #physical port 1 on hub 1, on ttyUSB3
-2:4:4: Device 025 "battor_p1_h1_t3" #physical port 1 on hub 1, on ttyUSB3
-3: Device 100 "My Test HUB" #hub 2
-3:4: Device 101 "My Test Internal HUB" #internal section of hub 2
-3:4:4: Device 102 "battor_p1_h2_t4" #physical port 1 on hub 2, on ttyusb4
-"""
-
-import logging
-import os
-import unittest
-
-from devil import devil_env
-from devil.utils import battor_device_mapping
-from devil.utils import find_usb_devices
-from devil.utils import lsusb
-from devil.utils import usb_hubs
-
-with devil_env.SysPath(devil_env.PYMOCK_PATH):
- import mock # pylint: disable=import-error
-
-# Output of lsusb.lsusb().
-# We just test that the dictionary is working by creating an
-# "ID number" equal to (bus_num*1000)+device_num and seeing if
-# it is picked up correctly. Also we test the description
-
-DEVLIST = [(1, 11, 'foo'),
- (1, 12, 'bar'),
- (1, 13, 'baz'),
- (2, 11, 'quux'),
- (2, 20, 'My Test HUB'),
- (2, 21, 'ID 0403:6001 battor_p7_h1_t0'),
- (2, 22, 'ID 0403:6001 battor_p5_h1_t1'),
- (2, 23, 'My Test Internal HUB'),
- (2, 24, 'ID 0403:6001 battor_p3_h1_t2'),
- (2, 25, 'ID 0403:6001 battor_p1_h1_t3'),
- (2, 26, 'Not a Battery Monitor'),
- (2, 100, 'My Test HUB'),
- (2, 101, 'My Test Internal HUB'),
- (2, 102, 'ID 0403:6001 battor_p1_h1_t4')]
-
-LSUSB_OUTPUT = [
- {'bus': b, 'device': d, 'desc': t, 'id': (1000*b)+d}
- for (b, d, t) in DEVLIST]
-
-
-# Note: "Lev", "Cnt", "Spd", and "MxCh" are not used by parser,
-# so we just leave them as zeros here. Also note that the port
-# numbers reported here start at 0, so they're 1 less than the
-# port numbers reported elsewhere.
-USB_DEVICES_OUTPUT = '''
-T: Bus=01 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 11 Spd=000 MxCh=00
-S: SerialNumber=FooSerial
-T: Bus=01 Lev=00 Prnt=00 Port=01 Cnt=00 Dev#= 12 Spd=000 MxCh=00
-S: SerialNumber=BarSerial
-T: Bus=01 Lev=00 Prnt=00 Port=02 Cnt=00 Dev#= 13 Spd=000 MxCh=00
-S: SerialNumber=BazSerial
-
-T: Bus=02 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#= 11 Spd=000 MxCh=00
-
-T: Bus=02 Lev=00 Prnt=00 Port=01 Cnt=00 Dev#= 20 Spd=000 MxCh=00
-T: Bus=02 Lev=00 Prnt=20 Port=00 Cnt=00 Dev#= 21 Spd=000 MxCh=00
-S: SerialNumber=BattOr0
-T: Bus=02 Lev=00 Prnt=20 Port=02 Cnt=00 Dev#= 22 Spd=000 MxCh=00
-S: SerialNumber=BattOr1
-T: Bus=02 Lev=00 Prnt=20 Port=03 Cnt=00 Dev#= 23 Spd=000 MxCh=00
-T: Bus=02 Lev=00 Prnt=23 Port=01 Cnt=00 Dev#= 24 Spd=000 MxCh=00
-S: SerialNumber=BattOr2
-T: Bus=02 Lev=00 Prnt=23 Port=03 Cnt=00 Dev#= 25 Spd=000 MxCh=00
-S: SerialNumber=BattOr3
-T: Bus=02 Lev=00 Prnt=23 Port=02 Cnt=00 Dev#= 26 Spd=000 MxCh=00
-
-T: Bus=02 Lev=00 Prnt=00 Port=02 Cnt=00 Dev#=100 Spd=000 MxCh=00
-T: Bus=02 Lev=00 Prnt=100 Port=03 Cnt=00 Dev#=101 Spd=000 MxCh=00
-T: Bus=02 Lev=00 Prnt=101 Port=03 Cnt=00 Dev#=102 Spd=000 MxCh=00
-'''
-
-RAW_LSUSB_OUTPUT = '''
-Bus 001 Device 011: FAST foo
-Bus 001 Device 012: FAST bar
-Bus 001 Device 013: baz
-Bus 002 Device 011: quux
-Bus 002 Device 020: My Test HUB
-Bus 002 Device 021: ID 0403:6001 battor_p7_h1_t0
-Bus 002 Device 022: ID 0403:6001 battor_p5_h1_t1
-Bus 002 Device 023: My Test Internal HUB
-Bus 002 Device 024: ID 0403:6001 battor_p3_h1_t2
-Bus 002 Device 025: ID 0403:6001 battor_p1_h1_t3
-Bus 002 Device 026: Not a Battery Monitor
-Bus 002 Device 100: My Test HUB
-Bus 002 Device 101: My Test Internal HUB
-Bus 002 Device 102: ID 0403:6001 battor_p1_h1_t4
-'''
-
-LIST_TTY_OUTPUT = '''
-ttyUSB0
-Something-else-0
-ttyUSB1
-ttyUSB2
-Something-else-1
-ttyUSB3
-ttyUSB4
-Something-else-2
-ttyUSB5
-'''
-
-# Note: The real output will have multiple lines with
-# ATTRS{busnum} and ATTRS{devnum}, but only the first
-# one counts. Thus the test output duplicates this.
-UDEVADM_USBTTY0_OUTPUT = '''
-ATTRS{busnum}=="2"
-ATTRS{devnum}=="21"
-ATTRS{busnum}=="0"
-ATTRS{devnum}=="0"
-'''
-
-UDEVADM_USBTTY1_OUTPUT = '''
-ATTRS{busnum}=="2"
-ATTRS{devnum}=="22"
-ATTRS{busnum}=="0"
-ATTRS{devnum}=="0"
-'''
-
-UDEVADM_USBTTY2_OUTPUT = '''
-ATTRS{busnum}=="2"
-ATTRS{devnum}=="24"
-ATTRS{busnum}=="0"
-ATTRS{devnum}=="0"
-'''
-
-UDEVADM_USBTTY3_OUTPUT = '''
-ATTRS{busnum}=="2"
-ATTRS{devnum}=="25"
-ATTRS{busnum}=="0"
-ATTRS{devnum}=="0"
-'''
-
-UDEVADM_USBTTY4_OUTPUT = '''
-ATTRS{busnum}=="2"
-ATTRS{devnum}=="102"
-ATTRS{busnum}=="0"
-ATTRS{devnum}=="0"
-'''
-
-UDEVADM_USBTTY5_OUTPUT = '''
-ATTRS{busnum}=="2"
-ATTRS{devnum}=="26"
-ATTRS{busnum}=="0"
-ATTRS{devnum}=="0"
-'''
-
-UDEVADM_OUTPUT_DICT = {
- 'ttyUSB0': UDEVADM_USBTTY0_OUTPUT,
- 'ttyUSB1': UDEVADM_USBTTY1_OUTPUT,
- 'ttyUSB2': UDEVADM_USBTTY2_OUTPUT,
- 'ttyUSB3': UDEVADM_USBTTY3_OUTPUT,
- 'ttyUSB4': UDEVADM_USBTTY4_OUTPUT,
- 'ttyUSB5': UDEVADM_USBTTY5_OUTPUT}
-
-# Identification criteria for Plugable 7-Port Hub
-def isTestHub(node):
- """Check if a node is a Plugable 7-Port Hub
- (Model USB2-HUB7BC)
- The topology of this device is a 4-port hub,
- with another 4-port hub connected on port 4.
- """
- if not isinstance(node, find_usb_devices.USBDeviceNode):
- return False
- if 'Test HUB' not in node.desc:
- return False
- if not node.HasPort(4):
- return False
- return 'Test Internal HUB' in node.PortToDevice(4).desc
-
-TEST_HUB = usb_hubs.HubType(isTestHub,
- {1:7,
- 2:6,
- 3:5,
- 4:{1:4, 2:3, 3:2, 4:1}})
-
-class USBScriptTest(unittest.TestCase):
- def setUp(self):
- find_usb_devices._GetTtyUSBInfo = mock.Mock(
- side_effect=lambda x: UDEVADM_OUTPUT_DICT[x])
- find_usb_devices._GetParsedLSUSBOutput = mock.Mock(
- return_value=LSUSB_OUTPUT)
- find_usb_devices._GetUSBDevicesOutput = mock.Mock(
- return_value=USB_DEVICES_OUTPUT)
- find_usb_devices._GetCommList = mock.Mock(
- return_value=LIST_TTY_OUTPUT)
- lsusb.raw_lsusb = mock.Mock(
- return_value=RAW_LSUSB_OUTPUT)
-
- def testIsBattOr(self):
- bd = find_usb_devices.GetBusNumberToDeviceTreeMap()
- self.assertTrue(battor_device_mapping.IsBattOr('ttyUSB3', bd))
- self.assertFalse(battor_device_mapping.IsBattOr('ttyUSB5', bd))
-
- def testGetBattOrs(self):
- bd = find_usb_devices.GetBusNumberToDeviceTreeMap()
- self.assertEquals(battor_device_mapping.GetBattOrList(bd),
- ['ttyUSB0', 'ttyUSB1', 'ttyUSB2',
- 'ttyUSB3', 'ttyUSB4'])
-
- def testGetTTYDevices(self):
- pp = find_usb_devices.GetAllPhysicalPortToTTYMaps([TEST_HUB])
- result = list(pp)
- self.assertEquals(result[0], {7:'ttyUSB0',
- 5:'ttyUSB1',
- 3:'ttyUSB2',
- 2:'ttyUSB5',
- 1:'ttyUSB3'})
- self.assertEquals(result[1], {1:'ttyUSB4'})
-
- def testGetPortDeviceMapping(self):
- pp = find_usb_devices.GetAllPhysicalPortToBusDeviceMaps([TEST_HUB])
- result = list(pp)
- self.assertEquals(result[0], {7:(2, 21),
- 5:(2, 22),
- 3:(2, 24),
- 2:(2, 26),
- 1:(2, 25)})
- self.assertEquals(result[1], {1:(2, 102)})
-
- def testGetSerialMapping(self):
- pp = find_usb_devices.GetAllPhysicalPortToSerialMaps([TEST_HUB])
- result = list(pp)
- self.assertEquals(result[0], {7:'BattOr0',
- 5:'BattOr1',
- 3:'BattOr2',
- 1:'BattOr3'})
- self.assertEquals(result[1], {})
-
- def testFastDeviceDescriptions(self):
- bd = find_usb_devices.GetBusNumberToDeviceTreeMap()
- dev_foo = bd[1].FindDeviceNumber(11)
- dev_bar = bd[1].FindDeviceNumber(12)
- dev_battor_p7_h1_t0 = bd[2].FindDeviceNumber(21)
- self.assertEquals(dev_foo.desc, 'FAST foo')
- self.assertEquals(dev_bar.desc, 'FAST bar')
- self.assertEquals(dev_battor_p7_h1_t0.desc,
- 'ID 0403:6001 battor_p7_h1_t0')
-
- def testDeviceDescriptions(self):
- bd = find_usb_devices.GetBusNumberToDeviceTreeMap(fast=False)
- dev_foo = bd[1].FindDeviceNumber(11)
- dev_bar = bd[1].FindDeviceNumber(12)
- dev_battor_p7_h1_t0 = bd[2].FindDeviceNumber(21)
- self.assertEquals(dev_foo.desc, 'foo')
- self.assertEquals(dev_bar.desc, 'bar')
- self.assertEquals(dev_battor_p7_h1_t0.desc,
- 'ID 0403:6001 battor_p7_h1_t0')
-
- def testDeviceInformation(self):
- bd = find_usb_devices.GetBusNumberToDeviceTreeMap(fast=False)
- dev_foo = bd[1].FindDeviceNumber(11)
- dev_bar = bd[1].FindDeviceNumber(12)
- dev_battor_p7_h1_t0 = bd[2].FindDeviceNumber(21)
- self.assertEquals(dev_foo.info['id'], 1011)
- self.assertEquals(dev_bar.info['id'], 1012)
- self.assertEquals(dev_battor_p7_h1_t0.info['id'], 2021)
-
- def testSerialNumber(self):
- bd = find_usb_devices.GetBusNumberToDeviceTreeMap(fast=False)
- dev_foo = bd[1].FindDeviceNumber(11)
- dev_bar = bd[1].FindDeviceNumber(12)
- dev_battor_p7_h1_t0 = bd[2].FindDeviceNumber(21)
- self.assertEquals(dev_foo.serial, 'FooSerial')
- self.assertEquals(dev_bar.serial, 'BarSerial')
- self.assertEquals(dev_battor_p7_h1_t0.serial, 'BattOr0')
-
- def testBattOrDictMapping(self):
- map_dict = {'Phone1':'BattOr1', 'Phone2':'BattOr2', 'Phone3':'BattOr3'}
- a1 = battor_device_mapping.GetBattOrPathFromPhoneSerial(
- 'Phone1', serial_map=map_dict)
- a2 = battor_device_mapping.GetBattOrPathFromPhoneSerial(
- 'Phone2', serial_map=map_dict)
- a3 = battor_device_mapping.GetBattOrPathFromPhoneSerial(
- 'Phone3', serial_map=map_dict)
- self.assertEquals(a1, '/dev/ttyUSB1')
- self.assertEquals(a2, '/dev/ttyUSB2')
- self.assertEquals(a3, '/dev/ttyUSB3')
-
- def testBattOrDictFromFileMapping(self):
- try:
- map_dict = {'Phone1':'BattOr1', 'Phone2':'BattOr2', 'Phone3':'BattOr3'}
- curr_dir = os.path.dirname(os.path.realpath(__file__))
- filename = os.path.join(curr_dir, 'test', 'data', 'test_write_map.json')
- battor_device_mapping.WriteSerialMapFile(filename, map_dict)
- a1 = battor_device_mapping.GetBattOrPathFromPhoneSerial(
- 'Phone1', serial_map_file=filename)
- a2 = battor_device_mapping.GetBattOrPathFromPhoneSerial(
- 'Phone2', serial_map_file=filename)
- a3 = battor_device_mapping.GetBattOrPathFromPhoneSerial(
- 'Phone3', serial_map_file=filename)
- finally:
- os.remove(filename)
- self.assertEquals(a1, '/dev/ttyUSB1')
- self.assertEquals(a2, '/dev/ttyUSB2')
- self.assertEquals(a3, '/dev/ttyUSB3')
-
- def testReadSerialMapFile(self):
- curr_dir = os.path.dirname(os.path.realpath(__file__))
- map_dict = battor_device_mapping.ReadSerialMapFile(
- os.path.join(curr_dir, 'test', 'data', 'test_serial_map.json'))
- self.assertEquals(len(map_dict.keys()), 3)
- self.assertEquals(map_dict['Phone1'], 'BattOr1')
- self.assertEquals(map_dict['Phone2'], 'BattOr2')
- self.assertEquals(map_dict['Phone3'], 'BattOr3')
-
-original_PPTSM = find_usb_devices.GetAllPhysicalPortToSerialMaps
-original_PPTTM = find_usb_devices.GetAllPhysicalPortToTTYMaps
-original_GBL = battor_device_mapping.GetBattOrList
-original_GBNDM = find_usb_devices.GetBusNumberToDeviceTreeMap
-original_IB = battor_device_mapping.IsBattOr
-original_GBSM = battor_device_mapping.GetBattOrSerialNumbers
-
-def setup_battor_test(serial, tty, battor, bser=None):
- serial_mapper = mock.Mock(return_value=serial)
- tty_mapper = mock.Mock(return_value=tty)
- battor_lister = mock.Mock(return_value=battor)
- devtree = mock.Mock(return_value=None)
- is_battor = mock.Mock(side_effect=lambda x, y: x in battor)
- battor_serials = mock.Mock(return_value=bser)
- find_usb_devices.GetAllPhysicalPortToSerialMaps = serial_mapper
- find_usb_devices.GetAllPhysicalPortToTTYMaps = tty_mapper
- battor_device_mapping.GetBattOrList = battor_lister
- find_usb_devices.GetBusNumberToDeviceTreeMap = devtree
- battor_device_mapping.IsBattOr = is_battor
- battor_device_mapping.GetBattOrSerialNumbers = battor_serials
-
-class BattOrMappingTest(unittest.TestCase):
- def tearDown(self):
- find_usb_devices.GetAllPhysicalPortToSerialMaps = original_PPTSM
- find_usb_devices.GetAllPhysicalPortToTTYMaps = original_PPTTM
- battor_device_mapping.GetBattOrList = original_GBL
- find_usb_devices.GetBusNumberToDeviceTreeMap = original_GBNDM
- battor_device_mapping.IsBattOr = original_IB
- battor_device_mapping.GetBattOrSerialNumbers = original_GBSM
-
- def test_generate_serial_map(self):
- setup_battor_test([{1:'Phn1', 2:'Phn2', 3:'Phn3'},
- {1:'Bat1', 2:'Bat2', 3:'Bat3'}],
- [{},
- {1:'ttyUSB0', 2:'ttyUSB1', 3:'ttyUSB2'}],
- ['ttyUSB0', 'ttyUSB1', 'ttyUSB2'],
- ['Bat1', 'Bat2', 'Bat3'])
- result = battor_device_mapping.GenerateSerialMap()
- self.assertEqual(len(result), 3)
- self.assertEqual(result['Phn1'], 'Bat1')
- self.assertEqual(result['Phn2'], 'Bat2')
- self.assertEqual(result['Phn3'], 'Bat3')
-
-
-if __name__ == "__main__":
- logging.getLogger().setLevel(logging.DEBUG)
- unittest.main(verbosity=2)
diff --git a/third_party/catapult/devil/devil/utils/geometry.py b/third_party/catapult/devil/devil/utils/geometry.py
deleted file mode 100644
index da21770b3e..0000000000
--- a/third_party/catapult/devil/devil/utils/geometry.py
+++ /dev/null
@@ -1,75 +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.
-
-"""Objects for convenient manipulation of points and other surface areas."""
-
-import collections
-
-
-class Point(collections.namedtuple('Point', ['x', 'y'])):
- """Object to represent an (x, y) point on a surface.
-
- Args:
- x, y: Two numeric coordinates that define the point.
- """
- __slots__ = ()
-
- def __str__(self):
- """Get a useful string representation of the object."""
- return '(%s, %s)' % (self.x, self.y)
-
- def __add__(self, other):
- """Sum of two points, e.g. p + q."""
- if isinstance(other, Point):
- return Point(self.x + other.x, self.y + other.y)
- else:
- return NotImplemented
-
- def __mul__(self, factor):
- """Multiplication on the right is not implemented."""
- # This overrides the default behaviour of a tuple multiplied by a constant
- # on the right, which does not make sense for a Point.
- return NotImplemented
-
- def __rmul__(self, factor):
- """Multiply a point by a scalar factor on the left, e.g. 2 * p."""
- return Point(factor * self.x, factor * self.y)
-
-
-class Rectangle(
- collections.namedtuple('Rectangle', ['top_left', 'bottom_right'])):
- """Object to represent a rectangle on a surface.
-
- Args:
- top_left: A pair of (left, top) coordinates. Might be given as a Point
- or as a two-element sequence (list, tuple, etc.).
- bottom_right: A pair (right, bottom) coordinates.
- """
- __slots__ = ()
-
- def __new__(cls, top_left, bottom_right):
- if not isinstance(top_left, Point):
- top_left = Point(*top_left)
- if not isinstance(bottom_right, Point):
- bottom_right = Point(*bottom_right)
- return super(Rectangle, cls).__new__(cls, top_left, bottom_right)
-
- def __str__(self):
- """Get a useful string representation of the object."""
- return '[%s, %s]' % (self.top_left, self.bottom_right)
-
- @property
- def center(self):
- """Get the point at the center of the rectangle."""
- return 0.5 * (self.top_left + self.bottom_right)
-
- @classmethod
- def FromDict(cls, d):
- """Create a rectangle object from a dictionary.
-
- Args:
- d: A dictionary (or mapping) of the form, e.g., {'top': 0, 'left': 0,
- 'bottom': 1, 'right': 1}.
- """
- return cls(Point(d['left'], d['top']), Point(d['right'], d['bottom']))
diff --git a/third_party/catapult/devil/devil/utils/geometry_test.py b/third_party/catapult/devil/devil/utils/geometry_test.py
deleted file mode 100644
index af69442930..0000000000
--- a/third_party/catapult/devil/devil/utils/geometry_test.py
+++ /dev/null
@@ -1,61 +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.
-
-"""Tests for the geometry module."""
-
-import unittest
-
-from devil.utils import geometry as g
-
-
-class PointTest(unittest.TestCase):
-
- def testStr(self):
- p = g.Point(1, 2)
- self.assertEquals(str(p), '(1, 2)')
-
- def testAdd(self):
- p = g.Point(1, 2)
- q = g.Point(3, 4)
- r = g.Point(4, 6)
- self.assertEquals(p + q, r)
-
- def testAdd_TypeErrorWithInvalidOperands(self):
- # pylint: disable=pointless-statement
- p = g.Point(1, 2)
- with self.assertRaises(TypeError):
- p + 4 # Can't add point and scalar.
- with self.assertRaises(TypeError):
- 4 + p # Can't add scalar and point.
-
- def testMult(self):
- p = g.Point(1, 2)
- r = g.Point(2, 4)
- self.assertEquals(2 * p, r) # Multiply by scalar on the left.
-
- def testMult_TypeErrorWithInvalidOperands(self):
- # pylint: disable=pointless-statement
- p = g.Point(1, 2)
- q = g.Point(2, 4)
- with self.assertRaises(TypeError):
- p * q # Can't multiply points.
- with self.assertRaises(TypeError):
- p * 4 # Can't multiply by a scalar on the right.
-
-
-class RectangleTest(unittest.TestCase):
-
- def testStr(self):
- r = g.Rectangle(g.Point(0, 1), g.Point(2, 3))
- self.assertEquals(str(r), '[(0, 1), (2, 3)]')
-
- def testCenter(self):
- r = g.Rectangle(g.Point(0, 1), g.Point(2, 3))
- c = g.Point(1, 2)
- self.assertEquals(r.center, c)
-
- def testFromJson(self):
- r1 = g.Rectangle(g.Point(0, 1), g.Point(2, 3))
- r2 = g.Rectangle.FromDict({'top': 1, 'left': 0, 'bottom': 3, 'right': 2})
- self.assertEquals(r1, r2)
diff --git a/third_party/catapult/devil/devil/utils/host_utils.py b/third_party/catapult/devil/devil/utils/host_utils.py
deleted file mode 100644
index 580721f127..0000000000
--- a/third_party/catapult/devil/devil/utils/host_utils.py
+++ /dev/null
@@ -1,16 +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.
-
-import os
-
-
-def GetRecursiveDiskUsage(path):
- """Returns the disk usage in bytes of |path|. Similar to `du -sb |path|`."""
- running_size = os.path.getsize(path)
- if os.path.isdir(path):
- for root, dirs, files in os.walk(path):
- running_size += sum([os.path.getsize(os.path.join(root, f))
- for f in files + dirs])
- return running_size
-
diff --git a/third_party/catapult/devil/devil/utils/lazy/__init__.py b/third_party/catapult/devil/devil/utils/lazy/__init__.py
deleted file mode 100644
index 3cc56c0acf..0000000000
--- a/third_party/catapult/devil/devil/utils/lazy/__init__.py
+++ /dev/null
@@ -1,5 +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.
-
-from devil.utils.lazy.weak_constant import WeakConstant
diff --git a/third_party/catapult/devil/devil/utils/lazy/weak_constant.py b/third_party/catapult/devil/devil/utils/lazy/weak_constant.py
deleted file mode 100644
index 3558f29ac6..0000000000
--- a/third_party/catapult/devil/devil/utils/lazy/weak_constant.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import threading
-
-
-class WeakConstant(object):
- """A thread-safe, lazily initialized object.
-
- This does not support modification after initialization. The intended
- constant nature of the object is not enforced, though, hence the "weak".
- """
-
- def __init__(self, initializer):
- self._initialized = False
- self._initializer = initializer
- self._lock = threading.Lock()
- self._val = None
-
- def read(self):
- """Get the object, creating it if necessary."""
- if self._initialized:
- return self._val
- with self._lock:
- if not self._initialized:
- self._val = self._initializer()
- self._initialized = True
- return self._val
diff --git a/third_party/catapult/devil/devil/utils/lsusb.py b/third_party/catapult/devil/devil/utils/lsusb.py
deleted file mode 100644
index 6cbf2567b9..0000000000
--- a/third_party/catapult/devil/devil/utils/lsusb.py
+++ /dev/null
@@ -1,174 +0,0 @@
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import logging
-import re
-
-from devil.utils import cmd_helper
-
-logger = logging.getLogger(__name__)
-
-_COULDNT_OPEN_ERROR_RE = re.compile(r'Couldn\'t open device.*')
-_INDENTATION_RE = re.compile(r'^( *)')
-_LSUSB_BUS_DEVICE_RE = re.compile(r'^Bus (\d{3}) Device (\d{3}): (.*)')
-_LSUSB_ENTRY_RE = re.compile(r'^ *([^ ]+) +([^ ]+) *([^ ].*)?$')
-_LSUSB_GROUP_RE = re.compile(r'^ *([^ ]+.*):$')
-
-
-def _lsusbv_on_device(bus_id, dev_id):
- """Calls lsusb -v on device."""
- _, raw_output = cmd_helper.GetCmdStatusAndOutputWithTimeout(
- ['lsusb', '-v', '-s', '%s:%s' % (bus_id, dev_id)], timeout=10)
-
- device = {'bus': bus_id, 'device': dev_id}
- depth_stack = [device]
-
- # This builds a nested dict -- a tree, basically -- that corresponds
- # to the lsusb output. It looks first for a line containing
- #
- # "Bus <bus number> Device <device number>: ..."
- #
- # and uses that to create the root node. It then parses all remaining
- # lines as a tree, with the indentation level determining the
- # depth of the new node.
- #
- # This expects two kinds of lines:
- # - "groups", which take the form
- # "<Group name>:"
- # and typically have children, and
- # - "entries", which take the form
- # "<entry name> <entry value> <possible entry description>"
- # and typically do not have children (but can).
- #
- # This maintains a stack containing all current ancestor nodes in
- # order to add new nodes to the proper place in the tree.
- # The stack is added to when a new node is parsed. Nodes are removed
- # from the stack when they are either at the same indentation level as
- # or a deeper indentation level than the current line.
- #
- # e.g. the following lsusb output:
- #
- # Bus 123 Device 456: School bus
- # Device Descriptor:
- # bDeviceClass 5 Actual School Bus
- # Configuration Descriptor:
- # bLength 20 Rows
- #
- # would produce the following dict:
- #
- # {
- # 'bus': 123,
- # 'device': 456,
- # 'desc': 'School bus',
- # 'Device Descriptor': {
- # 'bDeviceClass': {
- # '_value': '5',
- # '_desc': 'Actual School Bus',
- # },
- # 'Configuration Descriptor': {
- # 'bLength': {
- # '_value': '20',
- # '_desc': 'Rows',
- # },
- # },
- # }
- # }
- for line in raw_output.splitlines():
- # Ignore blank lines.
- if not line:
- continue
- # Filter out error mesage about opening device.
- if _COULDNT_OPEN_ERROR_RE.match(line):
- continue
- # Find start of device information.
- m = _LSUSB_BUS_DEVICE_RE.match(line)
- if m:
- if m.group(1) != bus_id:
- logger.warning(
- 'Expected bus_id value: %r, seen %r', bus_id, m.group(1))
- if m.group(2) != dev_id:
- logger.warning(
- 'Expected dev_id value: %r, seen %r', dev_id, m.group(2))
- device['desc'] = m.group(3)
- continue
-
- # Skip any lines that aren't indented, as they're not part of the
- # device descriptor.
- indent_match = _INDENTATION_RE.match(line)
- if not indent_match:
- continue
-
- # Determine the indentation depth.
- depth = 1 + len(indent_match.group(1)) / 2
- if depth > len(depth_stack):
- logger.error(
- 'lsusb parsing error: unexpected indentation: "%s"', line)
- continue
-
- # Pop everything off the depth stack that isn't a parent of
- # this element.
- while depth < len(depth_stack):
- depth_stack.pop()
-
- cur = depth_stack[-1]
-
- m = _LSUSB_GROUP_RE.match(line)
- if m:
- new_group = {}
- cur[m.group(1)] = new_group
- depth_stack.append(new_group)
- continue
-
- m = _LSUSB_ENTRY_RE.match(line)
- if m:
- new_entry = {
- '_value': m.group(2),
- '_desc': m.group(3),
- }
- cur[m.group(1)] = new_entry
- depth_stack.append(new_entry)
- continue
-
- logger.error('lsusb parsing error: unrecognized line: "%s"', line)
-
- return device
-
-def lsusb():
- """Call lsusb and return the parsed output."""
- _, lsusb_list_output = cmd_helper.GetCmdStatusAndOutputWithTimeout(
- ['lsusb'], timeout=10)
- devices = []
- for line in lsusb_list_output.splitlines():
- m = _LSUSB_BUS_DEVICE_RE.match(line)
- if m:
- bus_num = m.group(1)
- dev_num = m.group(2)
- try:
- devices.append(_lsusbv_on_device(bus_num, dev_num))
- except cmd_helper.TimeoutError:
- # Will be blacklisted if it is in expected device file, but times out.
- logger.info('lsusb -v %s:%s timed out.', bus_num, dev_num)
- return devices
-
-def raw_lsusb():
- return cmd_helper.GetCmdOutput(['lsusb'])
-
-def get_lsusb_serial(device):
- try:
- return device['Device Descriptor']['iSerial']['_desc']
- except KeyError:
- return None
-
-def _is_android_device(device):
- try:
- # Hubs are not android devices.
- if device['Device Descriptor']['bDeviceClass']['_value'] == '9':
- return False
- except KeyError:
- pass
- return get_lsusb_serial(device) is not None
-
-def get_android_devices():
- android_devices = (d for d in lsusb() if _is_android_device(d))
- return [get_lsusb_serial(d) for d in android_devices]
diff --git a/third_party/catapult/devil/devil/utils/lsusb_test.py b/third_party/catapult/devil/devil/utils/lsusb_test.py
deleted file mode 100755
index f381e72f10..0000000000
--- a/third_party/catapult/devil/devil/utils/lsusb_test.py
+++ /dev/null
@@ -1,250 +0,0 @@
-#!/usr/bin/env python
-# 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.
-
-"""Tests for the cmd_helper module."""
-
-import unittest
-
-from devil import devil_env
-from devil.utils import lsusb
-from devil.utils import mock_calls
-
-with devil_env.SysPath(devil_env.PYMOCK_PATH):
- import mock # pylint: disable=import-error
-
-RAW_OUTPUT = """
-Bus 003 Device 007: ID 18d1:4ee2 Google Inc. Nexus 4 (debug)
-Device Descriptor:
- bLength 18
- bDescriptorType 1
- bcdUSB 2.00
- bDeviceClass 0 (Defined at Interface level)
- bDeviceSubClass 0
- bDeviceProtocol 0
- bMaxPacketSize0 64
- idVendor 0x18d1 Google Inc.
- idProduct 0x4ee2 Nexus 4 (debug)
- bcdDevice 2.28
- iManufacturer 1 LGE
- iProduct 2 Nexus 4
- iSerial 3 01d2450ea194a93b
- bNumConfigurations 1
- Configuration Descriptor:
- bLength 9
- bDescriptorType 2
- wTotalLength 62
- bNumInterfaces 2
- bConfigurationValue 1
- iConfiguration 0
- bmAttributes 0x80
- (Bus Powered)
- MaxPower 500mA
- Interface Descriptor:
- bLength 9
- bDescriptorType 4
- bInterfaceNumber 0
- bAlternateSetting 0
- bNumEndpoints 3
- bInterfaceClass 255 Vendor Specific Class
- bInterfaceSubClass 255 Vendor Specific Subclass
- bInterfaceProtocol 0
- iInterface 4 MTP
- Endpoint Descriptor:
- bLength 7
- bDescriptorType 5
- bEndpointAddress 0x81 EP 1 IN
- bmAttributes 2
- Transfer Type Bulk
- Synch Type None
- Usage Type Data
- wMaxPacketSize 0x0040 1x 64 bytes
- bInterval 0
- Endpoint Descriptor:
- bLength 7
- bDescriptorType 5
- bEndpointAddress 0x01 EP 1 OUT
- bmAttributes 2
- Transfer Type Bulk
- Synch Type None
- Usage Type Data
- wMaxPacketSize 0x0040 1x 64 bytes
- bInterval 0
- Endpoint Descriptor:
- bLength 7
- bDescriptorType 5
- bEndpointAddress 0x82 EP 2 IN
- bmAttributes 3
- Transfer Type Interrupt
- Synch Type None
- Usage Type Data
- wMaxPacketSize 0x001c 1x 28 bytes
- bInterval 6
- Interface Descriptor:
- bLength 9
- bDescriptorType 4
- bInterfaceNumber 1
- bAlternateSetting 0
- bNumEndpoints 2
- bInterfaceClass 255 Vendor Specific Class
- bInterfaceSubClass 66
- bInterfaceProtocol 1
- iInterface 0
- Endpoint Descriptor:
- bLength 7
- bDescriptorType 5
- bEndpointAddress 0x83 EP 3 IN
- bmAttributes 2
- Transfer Type Bulk
- Synch Type None
- Usage Type Data
- wMaxPacketSize 0x0040 1x 64 bytes
- bInterval 0
- Endpoint Descriptor:
- bLength 7
- bDescriptorType 5
- bEndpointAddress 0x02 EP 2 OUT
- bmAttributes 2
- Transfer Type Bulk
- Synch Type None
- Usage Type Data
- wMaxPacketSize 0x0040 1x 64 bytes
- bInterval 0
-Device Qualifier (for other device speed):
- bLength 10
- bDescriptorType 6
- bcdUSB 2.00
- bDeviceClass 0 (Defined at Interface level)
- bDeviceSubClass 0
- bDeviceProtocol 0
- bMaxPacketSize0 64
- bNumConfigurations 1
-Device Status: 0x0000
- (Bus Powered)
-"""
-DEVICE_LIST = 'Bus 003 Device 007: ID 18d1:4ee2 Google Inc. Nexus 4 (debug)'
-
-EXPECTED_RESULT = {
- 'device': '007',
- 'bus': '003',
- 'desc': 'ID 18d1:4ee2 Google Inc. Nexus 4 (debug)',
- 'Device': {
- '_value': 'Status:',
- '_desc': '0x0000',
- '(Bus': {
- '_value': 'Powered)',
- '_desc': None
- }
- },
- 'Device Descriptor': {
- 'bLength': {'_value': '18', '_desc': None},
- 'bcdDevice': {'_value': '2.28', '_desc': None},
- 'bDeviceSubClass': {'_value': '0', '_desc': None},
- 'idVendor': {'_value': '0x18d1', '_desc': 'Google Inc.'},
- 'bcdUSB': {'_value': '2.00', '_desc': None},
- 'bDeviceProtocol': {'_value': '0', '_desc': None},
- 'bDescriptorType': {'_value': '1', '_desc': None},
- 'Configuration Descriptor': {
- 'bLength': {'_value': '9', '_desc': None},
- 'wTotalLength': {'_value': '62', '_desc': None},
- 'bConfigurationValue': {'_value': '1', '_desc': None},
- 'Interface Descriptor': {
- 'bLength': {'_value': '9', '_desc': None},
- 'bAlternateSetting': {'_value': '0', '_desc': None},
- 'bInterfaceNumber': {'_value': '1', '_desc': None},
- 'bNumEndpoints': {'_value': '2', '_desc': None},
- 'bDescriptorType': {'_value': '4', '_desc': None},
- 'bInterfaceSubClass': {'_value': '66', '_desc': None},
- 'bInterfaceClass': {
- '_value': '255',
- '_desc': 'Vendor Specific Class'
- },
- 'bInterfaceProtocol': {'_value': '1', '_desc': None},
- 'Endpoint Descriptor': {
- 'bLength': {'_value': '7', '_desc': None},
- 'bEndpointAddress': {'_value': '0x02', '_desc': 'EP 2 OUT'},
- 'bInterval': {'_value': '0', '_desc': None},
- 'bDescriptorType': {'_value': '5', '_desc': None},
- 'bmAttributes': {
- '_value': '2',
- 'Transfer': {'_value': 'Type', '_desc': 'Bulk'},
- 'Usage': {'_value': 'Type', '_desc': 'Data'},
- '_desc': None,
- 'Synch': {'_value': 'Type', '_desc': 'None'}
- },
- 'wMaxPacketSize': {
- '_value': '0x0040',
- '_desc': '1x 64 bytes'
- }
- },
- 'iInterface': {'_value': '0', '_desc': None}
- },
- 'bDescriptorType': {'_value': '2', '_desc': None},
- 'iConfiguration': {'_value': '0', '_desc': None},
- 'bmAttributes': {
- '_value': '0x80',
- '_desc': None,
- '(Bus': {'_value': 'Powered)', '_desc': None}
- },
- 'bNumInterfaces': {'_value': '2', '_desc': None},
- 'MaxPower': {'_value': '500mA', '_desc': None}
- },
- 'iSerial': {'_value': '3', '_desc': '01d2450ea194a93b'},
- 'idProduct': {'_value': '0x4ee2', '_desc': 'Nexus 4 (debug)'},
- 'iManufacturer': {'_value': '1', '_desc': 'LGE'},
- 'bDeviceClass': {
- '_value': '0',
- '_desc': '(Defined at Interface level)'
- },
- 'iProduct': {'_value': '2', '_desc': 'Nexus 4'},
- 'bMaxPacketSize0': {'_value': '64', '_desc': None},
- 'bNumConfigurations': {'_value': '1', '_desc': None}
- },
- 'Device Qualifier (for other device speed)': {
- 'bLength': {'_value': '10', '_desc': None},
- 'bNumConfigurations': {'_value': '1', '_desc': None},
- 'bDeviceSubClass': {'_value': '0', '_desc': None},
- 'bcdUSB': {'_value': '2.00', '_desc': None},
- 'bDeviceProtocol': {'_value': '0', '_desc': None},
- 'bDescriptorType': {'_value': '6', '_desc': None},
- 'bDeviceClass': {
- '_value': '0',
- '_desc': '(Defined at Interface level)'
- },
- 'bMaxPacketSize0': {'_value': '64', '_desc': None}
- }
-}
-
-
-class LsusbTest(mock_calls.TestCase):
- """Test Lsusb parsing."""
-
- def testLsusb(self):
- with self.assertCalls(
- (mock.call.devil.utils.cmd_helper.GetCmdStatusAndOutputWithTimeout(
- ['lsusb'], timeout=10), (None, DEVICE_LIST)),
- (mock.call.devil.utils.cmd_helper.GetCmdStatusAndOutputWithTimeout(
- ['lsusb', '-v', '-s', '003:007'], timeout=10), (None, RAW_OUTPUT))):
- self.assertDictEqual(lsusb.lsusb().pop(), EXPECTED_RESULT)
-
- def testGetSerial(self):
- with self.assertCalls(
- (mock.call.devil.utils.cmd_helper.GetCmdStatusAndOutputWithTimeout(
- ['lsusb'], timeout=10), (None, DEVICE_LIST)),
- (mock.call.devil.utils.cmd_helper.GetCmdStatusAndOutputWithTimeout(
- ['lsusb', '-v', '-s', '003:007'], timeout=10), (None, RAW_OUTPUT))):
- self.assertEqual(lsusb.get_android_devices(), ['01d2450ea194a93b'])
-
- def testGetLsusbSerial(self):
- with self.assertCalls(
- (mock.call.devil.utils.cmd_helper.GetCmdStatusAndOutputWithTimeout(
- ['lsusb'], timeout=10), (None, DEVICE_LIST)),
- (mock.call.devil.utils.cmd_helper.GetCmdStatusAndOutputWithTimeout(
- ['lsusb', '-v', '-s', '003:007'], timeout=10), (None, RAW_OUTPUT))):
- out = lsusb.lsusb().pop()
- self.assertEqual(lsusb.get_lsusb_serial(out), '01d2450ea194a93b')
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/third_party/catapult/devil/devil/utils/markdown.py b/third_party/catapult/devil/devil/utils/markdown.py
deleted file mode 100755
index 54e7ed5629..0000000000
--- a/third_party/catapult/devil/devil/utils/markdown.py
+++ /dev/null
@@ -1,320 +0,0 @@
-#! /usr/bin/env python
-# 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 argparse
-import imp
-import os
-import re
-import sys
-import textwrap
-import types
-
-# A markdown code block template: https://goo.gl/9EsyRi
-_CODE_BLOCK_FORMAT = '''```{language}
-{code}
-```
-'''
-
-_DEVIL_ROOT = os.path.abspath(os.path.join(
- os.path.dirname(__file__), '..', '..'))
-
-
-def md_bold(raw_text):
- """Returns markdown-formatted bold text."""
- return '**%s**' % md_escape(raw_text, characters='*')
-
-
-def md_code(raw_text, language):
- """Returns a markdown-formatted code block in the given language."""
- return _CODE_BLOCK_FORMAT.format(
- language=language or '',
- code=md_escape(raw_text, characters='`'))
-
-
-def md_escape(raw_text, characters='*_'):
- """Escapes * and _."""
- def escape_char(m):
- return '\\%s' % m.group(0)
- pattern = '[%s]' % re.escape(characters)
- return re.sub(pattern, escape_char, raw_text)
-
-
-def md_heading(raw_text, level):
- """Returns markdown-formatted heading."""
- adjusted_level = min(max(level, 0), 6)
- return '%s%s%s' % (
- '#' * adjusted_level, ' ' if adjusted_level > 0 else '', raw_text)
-
-
-def md_inline_code(raw_text):
- """Returns markdown-formatted inline code."""
- return '`%s`' % md_escape(raw_text, characters='`')
-
-
-def md_italic(raw_text):
- """Returns markdown-formatted italic text."""
- return '*%s*' % md_escape(raw_text, characters='*')
-
-
-def md_link(link_text, link_target):
- """returns a markdown-formatted link."""
- return '[%s](%s)' % (
- md_escape(link_text, characters=']'),
- md_escape(link_target, characters=')'))
-
-
-class MarkdownHelpFormatter(argparse.HelpFormatter):
- """A really bare-bones argparse help formatter that generates valid markdown.
-
- This will generate something like:
-
- usage
-
- # **section heading**:
-
- ## **--argument-one**
-
- ```
- argument-one help text
- ```
-
- """
-
- #override
- def _format_usage(self, usage, actions, groups, prefix):
- usage_text = super(MarkdownHelpFormatter, self)._format_usage(
- usage, actions, groups, prefix)
- return md_code(usage_text, language=None)
-
- #override
- def format_help(self):
- self._root_section.heading = md_heading(self._prog, level=1)
- return super(MarkdownHelpFormatter, self).format_help()
-
- #override
- def start_section(self, heading):
- super(MarkdownHelpFormatter, self).start_section(
- md_heading(heading, level=2))
-
- #override
- def _format_action(self, action):
- lines = []
- action_header = self._format_action_invocation(action)
- lines.append(md_heading(action_header, level=3))
- if action.help:
- lines.append(md_code(self._expand_help(action), language=None))
- lines.extend(['', ''])
- return '\n'.join(lines)
-
-
-class MarkdownHelpAction(argparse.Action):
- def __init__(self, option_strings,
- dest=argparse.SUPPRESS, default=argparse.SUPPRESS,
- **kwargs):
- super(MarkdownHelpAction, self).__init__(
- option_strings=option_strings,
- dest=dest,
- default=default,
- nargs=0,
- **kwargs)
-
- def __call__(self, parser, namespace, values, option_string=None):
- parser.formatter_class = MarkdownHelpFormatter
- parser.print_help()
- parser.exit()
-
-
-def add_md_help_argument(parser):
- """Adds --md-help to the given argparse.ArgumentParser.
-
- Running a script with --md-help will print the help text for that script
- as valid markdown.
-
- Args:
- parser: The ArgumentParser to which --md-help should be added.
- """
- parser.add_argument('--md-help', action=MarkdownHelpAction,
- help='print Markdown-formatted help text and exit.')
-
-
-def load_module_from_path(module_path):
- """Load a module given only the path name.
-
- Also loads package modules as necessary.
-
- Args:
- module_path: An absolute path to a python module.
- Returns:
- The module object for the given path.
- """
- module_names = [os.path.splitext(os.path.basename(module_path))[0]]
- d = os.path.dirname(module_path)
-
- while os.path.exists(os.path.join(d, '__init__.py')):
- module_names.append(os.path.basename(d))
- d = os.path.dirname(d)
-
- d = [d]
-
- module = None
- full_module_name = ''
- for package_name in reversed(module_names):
- if module:
- d = module.__path__
- full_module_name += '.'
- r = imp.find_module(package_name, d)
- full_module_name += package_name
- module = imp.load_module(full_module_name, *r)
- return module
-
-
-def md_module(module_obj, module_path=None, module_link=None):
- """Write markdown documentation for a class.
-
- Documents public classes and functions.
-
- Args:
- class_obj: a types.TypeType object for the class that should be
- documented.
- Returns:
- A list of markdown-formatted lines.
- """
- def should_doc(name):
- return (type(module_obj.__dict__[name]) != types.ModuleType
- and not name.startswith('_'))
-
- stuff_to_doc = sorted(
- obj for name, obj in module_obj.__dict__.iteritems()
- if should_doc(name))
-
- classes_to_doc = []
- functions_to_doc = []
-
- for s in stuff_to_doc:
- if type(s) == types.TypeType:
- classes_to_doc.append(s)
- elif type(s) == types.FunctionType:
- functions_to_doc.append(s)
-
- command = ['devil/utils/markdown.py']
- if module_link:
- command.extend(['--module-link', module_link])
- if module_path:
- command.append(os.path.relpath(module_path, _DEVIL_ROOT))
-
- heading_text = module_obj.__name__
- if module_link:
- heading_text = md_link(heading_text, module_link)
-
- content = [
- md_heading(heading_text, level=1),
- '',
- md_italic('This page was autogenerated by %s'
- % md_inline_code(' '.join(command))),
- '',
- ]
-
- for c in classes_to_doc:
- content += md_class(c)
- for f in functions_to_doc:
- content += md_function(f)
-
- print '\n'.join(content)
-
- return 0
-
-
-def md_class(class_obj):
- """Write markdown documentation for a class.
-
- Documents public methods. Does not currently document subclasses.
-
- Args:
- class_obj: a types.TypeType object for the class that should be
- documented.
- Returns:
- A list of markdown-formatted lines.
- """
- content = [md_heading(md_escape(class_obj.__name__), level=2)]
- content.append('')
- if class_obj.__doc__:
- content.extend(md_docstring(class_obj.__doc__))
-
- def should_doc(name, obj):
- return (type(obj) == types.FunctionType
- and (name.startswith('__') or not name.startswith('_')))
-
- methods_to_doc = sorted(
- obj for name, obj in class_obj.__dict__.iteritems()
- if should_doc(name, obj))
-
- for m in methods_to_doc:
- content.extend(md_function(m, class_obj=class_obj))
-
- return content
-
-
-def md_docstring(docstring):
- """Write a markdown-formatted docstring.
-
- Returns:
- A list of markdown-formatted lines.
- """
- content = []
- lines = textwrap.dedent(docstring).splitlines()
- content.append(md_escape(lines[0]))
- lines = lines[1:]
- while lines and (not lines[0] or lines[0].isspace()):
- lines = lines[1:]
-
- if not all(l.isspace() for l in lines):
- content.append(md_code('\n'.join(lines), language=None))
- content.append('')
- return content
-
-
-def md_function(func_obj, class_obj=None):
- """Write markdown documentation for a function.
-
- Args:
- func_obj: a types.FunctionType object for the function that should be
- documented.
- Returns:
- A list of markdown-formatted lines.
- """
- if class_obj:
- heading_text = '%s.%s' % (class_obj.__name__, func_obj.__name__)
- else:
- heading_text = func_obj.__name__
- content = [md_heading(md_escape(heading_text), level=3)]
- content.append('')
-
- if func_obj.__doc__:
- content.extend(md_docstring(func_obj.__doc__))
-
- return content
-
-
-def main(raw_args):
- """Write markdown documentation for the module at the provided path.
-
- Args:
- raw_args: the raw command-line args. Usually sys.argv[1:].
- Returns:
- An integer exit code. 0 for success, non-zero for failure.
- """
- parser = argparse.ArgumentParser()
- parser.add_argument('--module-link')
- parser.add_argument('module_path', type=os.path.realpath)
- args = parser.parse_args(raw_args)
-
- return md_module(
- load_module_from_path(args.module_path),
- module_link=args.module_link)
-
-
-if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))
-
diff --git a/third_party/catapult/devil/devil/utils/markdown_test.py b/third_party/catapult/devil/devil/utils/markdown_test.py
deleted file mode 100755
index 323776ca1a..0000000000
--- a/third_party/catapult/devil/devil/utils/markdown_test.py
+++ /dev/null
@@ -1,121 +0,0 @@
-#! /usr/bin/env python
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import os
-import sys
-import textwrap
-import unittest
-
-if __name__ == '__main__':
- sys.path.append(
- os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
-
-from devil.utils import markdown
-
-
-class MarkdownTest(unittest.TestCase):
-
- def testBold(self):
- raw = 'foo'
- self.assertEquals('**foo**', markdown.md_bold(raw))
-
- def testBoldContainsStars(self):
- raw = '*foo*'
- self.assertEquals('**\\*foo\\***', markdown.md_bold(raw))
-
- def testCode(self):
- raw = textwrap.dedent("""\
- class MarkdownTest(unittest.TestCase):
- def testCode(self):
- pass""")
-
- expected = textwrap.dedent("""\
- ```python
- class MarkdownTest(unittest.TestCase):
- def testCode(self):
- pass
- ```
- """)
- actual = markdown.md_code(raw, language='python')
- self.assertEquals(expected, actual)
-
- def testCodeContainsTicks(self):
- raw = textwrap.dedent("""\
- This is sample markdown.
- ```c
- // This is a sample code block.
- int main(int argc, char** argv) {
- return 0;
- }
- ```""")
-
- expected = textwrap.dedent("""\
- ```
- This is sample markdown.
- \\`\\`\\`c
- // This is a sample code block.
- int main(int argc, char** argv) {
- return 0;
- }
- \\`\\`\\`
- ```
- """)
- actual = markdown.md_code(raw, language=None)
- self.assertEquals(expected, actual)
-
- def testEscape(self):
- raw = 'text_with_underscores *and stars*'
- expected = 'text\\_with\\_underscores \\*and stars\\*'
- actual = markdown.md_escape(raw)
- self.assertEquals(expected, actual)
-
- def testHeading1(self):
- raw = 'Heading 1'
- self.assertEquals('# Heading 1', markdown.md_heading(raw, level=1))
-
- def testHeading5(self):
- raw = 'Heading 5'
- self.assertEquals('##### Heading 5', markdown.md_heading(raw, level=5))
-
- def testHeading10(self):
- raw = 'Heading 10'
- self.assertEquals('###### Heading 10', markdown.md_heading(raw, level=10))
-
- def testInlineCode(self):
- raw = 'devil.utils.markdown_test'
- self.assertEquals(
- '`devil.utils.markdown_test`', markdown.md_inline_code(raw))
-
- def testInlineCodeContainsTicks(self):
- raw = 'this contains `backticks`'
- self.assertEquals(
- '`this contains \\`backticks\\``', markdown.md_inline_code(raw))
-
- def testItalic(self):
- raw = 'bar'
- self.assertEquals('*bar*', markdown.md_italic(raw))
-
- def testItalicContainsStars(self):
- raw = '*bar*'
- self.assertEquals('*\\*bar\\**', markdown.md_italic(raw))
-
- def testLink(self):
- link_text = 'Devil home'
- link_target = (
- 'https://github.com/catapult-project/catapult/tree/master/devil')
- expected = (
- '[Devil home]'
- '(https://github.com/catapult-project/catapult/tree/master/devil)')
- self.assertEquals(expected, markdown.md_link(link_text, link_target))
-
- def testLinkTextContainsBracket(self):
- link_text = 'foo [] bar'
- link_target = 'https://www.google.com'
- expected = '[foo [\\] bar](https://www.google.com)'
- self.assertEquals(expected, markdown.md_link(link_text, link_target))
-
-
-if __name__ == '__main__':
- unittest.main(verbosity=2)
diff --git a/third_party/catapult/devil/devil/utils/mock_calls.py b/third_party/catapult/devil/devil/utils/mock_calls.py
deleted file mode 100644
index 5ae951e37d..0000000000
--- a/third_party/catapult/devil/devil/utils/mock_calls.py
+++ /dev/null
@@ -1,180 +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.
-
-"""
-A test facility to assert call sequences while mocking their behavior.
-"""
-
-import unittest
-
-from devil import devil_env
-
-with devil_env.SysPath(devil_env.PYMOCK_PATH):
- import mock # pylint: disable=import-error
-
-
-class TestCase(unittest.TestCase):
- """Adds assertCalls to TestCase objects."""
- class _AssertCalls(object):
-
- def __init__(self, test_case, expected_calls, watched):
- def call_action(pair):
- if isinstance(pair, type(mock.call)):
- return (pair, None)
- else:
- return pair
-
- def do_check(call):
- def side_effect(*args, **kwargs):
- received_call = call(*args, **kwargs)
- self._test_case.assertTrue(
- self._expected_calls,
- msg=('Unexpected call: %s' % str(received_call)))
- expected_call, action = self._expected_calls.pop(0)
- self._test_case.assertTrue(
- received_call == expected_call,
- msg=('Expected call mismatch:\n'
- ' expected: %s\n'
- ' received: %s\n'
- % (str(expected_call), str(received_call))))
- if callable(action):
- return action(*args, **kwargs)
- else:
- return action
- return side_effect
-
- self._test_case = test_case
- self._expected_calls = [call_action(pair) for pair in expected_calls]
- watched = watched.copy() # do not pollute the caller's dict
- watched.update((call.parent.name, call.parent)
- for call, _ in self._expected_calls)
- self._patched = [test_case.patch_call(call, side_effect=do_check(call))
- for call in watched.itervalues()]
-
- def __enter__(self):
- for patch in self._patched:
- patch.__enter__()
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- for patch in self._patched:
- patch.__exit__(exc_type, exc_val, exc_tb)
- if exc_type is None:
- missing = ''.join(' expected: %s\n' % str(call)
- for call, _ in self._expected_calls)
- self._test_case.assertFalse(
- missing,
- msg='Expected calls not found:\n' + missing)
-
- def __init__(self, *args, **kwargs):
- super(TestCase, self).__init__(*args, **kwargs)
- self.call = mock.call.self
- self._watched = {}
-
- def call_target(self, call):
- """Resolve a self.call instance to the target it represents.
-
- Args:
- call: a self.call instance, e.g. self.call.adb.Shell
-
- Returns:
- The target object represented by the call, e.g. self.adb.Shell
-
- Raises:
- ValueError if the path of the call does not start with "self", i.e. the
- target of the call is external to the self object.
- AttributeError if the path of the call does not specify a valid
- chain of attributes (without any calls) starting from "self".
- """
- path = call.name.split('.')
- if path.pop(0) != 'self':
- raise ValueError("Target %r outside of 'self' object" % call.name)
- target = self
- for attr in path:
- target = getattr(target, attr)
- return target
-
- def patch_call(self, call, **kwargs):
- """Patch the target of a mock.call instance.
-
- Args:
- call: a mock.call instance identifying a target to patch
- Extra keyword arguments are processed by mock.patch
-
- Returns:
- A context manager to mock/unmock the target of the call
- """
- if call.name.startswith('self.'):
- target = self.call_target(call.parent)
- _, attribute = call.name.rsplit('.', 1)
- if (hasattr(type(target), attribute)
- and isinstance(getattr(type(target), attribute), property)):
- return mock.patch.object(
- type(target), attribute, new_callable=mock.PropertyMock, **kwargs)
- else:
- return mock.patch.object(target, attribute, **kwargs)
- else:
- return mock.patch(call.name, **kwargs)
-
- def watchCalls(self, calls):
- """Add calls to the set of watched calls.
-
- Args:
- calls: a sequence of mock.call instances identifying targets to watch
- """
- self._watched.update((call.name, call) for call in calls)
-
- def watchMethodCalls(self, call, ignore=None):
- """Watch all public methods of the target identified by a self.call.
-
- Args:
- call: a self.call instance indetifying an object
- ignore: a list of public methods to ignore when watching for calls
- """
- target = self.call_target(call)
- if ignore is None:
- ignore = []
- self.watchCalls(getattr(call, method)
- for method in dir(target.__class__)
- if not method.startswith('_') and not method in ignore)
-
- def clearWatched(self):
- """Clear the set of watched calls."""
- self._watched = {}
-
- def assertCalls(self, *calls):
- """A context manager to assert that a sequence of calls is made.
-
- During the assertion, a number of functions and methods will be "watched",
- and any calls made to them is expected to appear---in the exact same order,
- and with the exact same arguments---as specified by the argument |calls|.
-
- By default, the targets of all expected calls are watched. Further targets
- to watch may be added using watchCalls and watchMethodCalls.
-
- Optionaly, each call may be accompanied by an action. If the action is a
- (non-callable) value, this value will be used as the return value given to
- the caller when the matching call is found. Alternatively, if the action is
- a callable, the action will be then called with the same arguments as the
- intercepted call, so that it can provide a return value or perform other
- side effects. If the action is missing, a return value of None is assumed.
-
- Note that mock.Mock objects are often convenient to use as a callable
- action, e.g. to raise exceptions or return other objects which are
- themselves callable.
-
- Args:
- calls: each argument is either a pair (expected_call, action) or just an
- expected_call, where expected_call is a mock.call instance.
-
- Raises:
- AssertionError if the watched targets do not receive the exact sequence
- of calls specified. Missing calls, extra calls, and calls with
- mismatching arguments, all cause the assertion to fail.
- """
- return self._AssertCalls(self, calls, self._watched)
-
- def assertCall(self, call, action=None):
- return self.assertCalls((call, action))
-
diff --git a/third_party/catapult/devil/devil/utils/mock_calls_test.py b/third_party/catapult/devil/devil/utils/mock_calls_test.py
deleted file mode 100755
index 8eb4fc9da4..0000000000
--- a/third_party/catapult/devil/devil/utils/mock_calls_test.py
+++ /dev/null
@@ -1,173 +0,0 @@
-#!/usr/bin/env python
-# 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.
-
-"""
-Unit tests for the contents of mock_calls.py.
-"""
-
-import logging
-import os
-import unittest
-
-from devil import devil_env
-from devil.android.sdk import version_codes
-from devil.utils import mock_calls
-
-with devil_env.SysPath(devil_env.PYMOCK_PATH):
- import mock # pylint: disable=import-error
-
-
-class _DummyAdb(object):
-
- def __str__(self):
- return '0123456789abcdef'
-
- def Push(self, host_path, device_path):
- logging.debug('(device %s) pushing %r to %r', self, host_path, device_path)
-
- def IsOnline(self):
- logging.debug('(device %s) checking device online', self)
- return True
-
- def Shell(self, cmd):
- logging.debug('(device %s) running command %r', self, cmd)
- return "nice output\n"
-
- def Reboot(self):
- logging.debug('(device %s) rebooted!', self)
-
- @property
- def build_version_sdk(self):
- logging.debug('(device %s) getting build_version_sdk', self)
- return version_codes.LOLLIPOP
-
-
-class TestCaseWithAssertCallsTest(mock_calls.TestCase):
-
- def setUp(self):
- self.adb = _DummyAdb()
-
- def ShellError(self):
- def action(cmd):
- raise ValueError('(device %s) command %r is not nice' % (self.adb, cmd))
- return action
-
- def get_answer(self):
- logging.debug("called 'get_answer' of %r object", self)
- return 42
-
- def echo(self, thing):
- logging.debug("called 'echo' of %r object", self)
- return thing
-
- def testCallTarget_succeds(self):
- self.assertEquals(self.adb.Shell,
- self.call_target(self.call.adb.Shell))
-
- def testCallTarget_failsExternal(self):
- with self.assertRaises(ValueError):
- self.call_target(mock.call.sys.getcwd)
-
- def testCallTarget_failsUnknownAttribute(self):
- with self.assertRaises(AttributeError):
- self.call_target(self.call.adb.Run)
-
- def testCallTarget_failsIntermediateCalls(self):
- with self.assertRaises(AttributeError):
- self.call_target(self.call.adb.RunShell('cmd').append)
-
- def testPatchCall_method(self):
- self.assertEquals(42, self.get_answer())
- with self.patch_call(self.call.get_answer, return_value=123):
- self.assertEquals(123, self.get_answer())
- self.assertEquals(42, self.get_answer())
-
- def testPatchCall_attribute_method(self):
- with self.patch_call(self.call.adb.Shell, return_value='hello'):
- self.assertEquals('hello', self.adb.Shell('echo hello'))
-
- def testPatchCall_global(self):
- with self.patch_call(mock.call.os.getcwd, return_value='/some/path'):
- self.assertEquals('/some/path', os.getcwd())
-
- def testPatchCall_withSideEffect(self):
- with self.patch_call(self.call.adb.Shell, side_effect=ValueError):
- with self.assertRaises(ValueError):
- self.adb.Shell('echo hello')
-
- def testPatchCall_property(self):
- self.assertEquals(version_codes.LOLLIPOP, self.adb.build_version_sdk)
- with self.patch_call(
- self.call.adb.build_version_sdk,
- return_value=version_codes.KITKAT):
- self.assertEquals(version_codes.KITKAT, self.adb.build_version_sdk)
- self.assertEquals(version_codes.LOLLIPOP, self.adb.build_version_sdk)
-
- def testAssertCalls_succeeds_simple(self):
- self.assertEquals(42, self.get_answer())
- with self.assertCall(self.call.get_answer(), 123):
- self.assertEquals(123, self.get_answer())
- self.assertEquals(42, self.get_answer())
-
- def testAssertCalls_succeeds_multiple(self):
- with self.assertCalls(
- (mock.call.os.getcwd(), '/some/path'),
- (self.call.echo('hello'), 'hello'),
- (self.call.get_answer(), 11),
- self.call.adb.Push('this_file', 'that_file'),
- (self.call.get_answer(), 12)):
- self.assertEquals(os.getcwd(), '/some/path')
- self.assertEquals('hello', self.echo('hello'))
- self.assertEquals(11, self.get_answer())
- self.adb.Push('this_file', 'that_file')
- self.assertEquals(12, self.get_answer())
-
- def testAsserCalls_succeeds_withAction(self):
- with self.assertCall(
- self.call.adb.Shell('echo hello'), self.ShellError()):
- with self.assertRaises(ValueError):
- self.adb.Shell('echo hello')
-
- def testAssertCalls_fails_tooManyCalls(self):
- with self.assertRaises(AssertionError):
- with self.assertCalls(self.call.adb.IsOnline()):
- self.adb.IsOnline()
- self.adb.IsOnline()
-
- def testAssertCalls_fails_tooFewCalls(self):
- with self.assertRaises(AssertionError):
- with self.assertCalls(self.call.adb.IsOnline()):
- pass
-
- def testAssertCalls_succeeds_extraCalls(self):
- # we are not watching Reboot, so the assertion succeeds
- with self.assertCalls(self.call.adb.IsOnline()):
- self.adb.IsOnline()
- self.adb.Reboot()
-
- def testAssertCalls_fails_extraCalls(self):
- self.watchCalls([self.call.adb.Reboot])
- # this time we are also watching Reboot, so the assertion fails
- with self.assertRaises(AssertionError):
- with self.assertCalls(self.call.adb.IsOnline()):
- self.adb.IsOnline()
- self.adb.Reboot()
-
- def testAssertCalls_succeeds_NoCalls(self):
- self.watchMethodCalls(self.call.adb) # we are watching all adb methods
- with self.assertCalls():
- pass
-
- def testAssertCalls_fails_NoCalls(self):
- self.watchMethodCalls(self.call.adb)
- with self.assertRaises(AssertionError):
- with self.assertCalls():
- self.adb.IsOnline()
-
-
-if __name__ == '__main__':
- logging.getLogger().setLevel(logging.DEBUG)
- unittest.main(verbosity=2)
-
diff --git a/third_party/catapult/devil/devil/utils/parallelizer.py b/third_party/catapult/devil/devil/utils/parallelizer.py
deleted file mode 100644
index 35995251c8..0000000000
--- a/third_party/catapult/devil/devil/utils/parallelizer.py
+++ /dev/null
@@ -1,238 +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.
-
-""" Wrapper that allows method execution in parallel.
-
-This class wraps a list of objects of the same type, emulates their
-interface, and executes any functions called on the objects in parallel
-in ReraiserThreads.
-
-This means that, given a list of objects:
-
- class Foo:
- def __init__(self):
- self.baz = Baz()
-
- def bar(self, my_param):
- // do something
-
- list_of_foos = [Foo(1), Foo(2), Foo(3)]
-
-we can take a sequential operation on that list of objects:
-
- for f in list_of_foos:
- f.bar('Hello')
-
-and run it in parallel across all of the objects:
-
- Parallelizer(list_of_foos).bar('Hello')
-
-It can also handle (non-method) attributes of objects, so that this:
-
- for f in list_of_foos:
- f.baz.myBazMethod()
-
-can be run in parallel with:
-
- Parallelizer(list_of_foos).baz.myBazMethod()
-
-Because it emulates the interface of the wrapped objects, a Parallelizer
-can be passed to a method or function that takes objects of that type:
-
- def DoesSomethingWithFoo(the_foo):
- the_foo.bar('Hello')
- the_foo.bar('world')
- the_foo.baz.myBazMethod
-
- DoesSomethingWithFoo(Parallelizer(list_of_foos))
-
-Note that this class spins up a thread for each object. Using this class
-to parallelize operations that are already fast will incur a net performance
-penalty.
-
-"""
-# pylint: disable=protected-access
-
-from devil.utils import reraiser_thread
-from devil.utils import watchdog_timer
-
-_DEFAULT_TIMEOUT = 30
-_DEFAULT_RETRIES = 3
-
-
-class Parallelizer(object):
- """Allows parallel execution of method calls across a group of objects."""
-
- def __init__(self, objs):
- self._orig_objs = objs
- self._objs = objs
-
- def __getattr__(self, name):
- """Emulate getting the |name| attribute of |self|.
-
- Args:
- name: The name of the attribute to retrieve.
- Returns:
- A Parallelizer emulating the |name| attribute of |self|.
- """
- self.pGet(None)
-
- r = type(self)(self._orig_objs)
- r._objs = [getattr(o, name) for o in self._objs]
- return r
-
- def __getitem__(self, index):
- """Emulate getting the value of |self| at |index|.
-
- Returns:
- A Parallelizer emulating the value of |self| at |index|.
- """
- self.pGet(None)
-
- r = type(self)(self._orig_objs)
- r._objs = [o[index] for o in self._objs]
- return r
-
- def __call__(self, *args, **kwargs):
- """Emulate calling |self| with |args| and |kwargs|.
-
- Note that this call is asynchronous. Call pFinish on the return value to
- block until the call finishes.
-
- Returns:
- A Parallelizer wrapping the ReraiserThreadGroup running the call in
- parallel.
- Raises:
- AttributeError if the wrapped objects aren't callable.
- """
- self.pGet(None)
-
- for o in self._objs:
- if not callable(o):
- raise AttributeError("'%s' is not callable" % o.__name__)
-
- r = type(self)(self._orig_objs)
- r._objs = reraiser_thread.ReraiserThreadGroup(
- [reraiser_thread.ReraiserThread(
- o, args=args, kwargs=kwargs,
- name='%s.%s' % (str(d), o.__name__))
- for d, o in zip(self._orig_objs, self._objs)])
- r._objs.StartAll() # pylint: disable=W0212
- return r
-
- def pFinish(self, timeout):
- """Finish any outstanding asynchronous operations.
-
- Args:
- timeout: The maximum number of seconds to wait for an individual
- result to return, or None to wait forever.
- Returns:
- self, now emulating the return values.
- """
- self._assertNoShadow('pFinish')
- if isinstance(self._objs, reraiser_thread.ReraiserThreadGroup):
- self._objs.JoinAll()
- self._objs = self._objs.GetAllReturnValues(
- watchdog_timer.WatchdogTimer(timeout))
- return self
-
- def pGet(self, timeout):
- """Get the current wrapped objects.
-
- Args:
- timeout: Same as |pFinish|.
- Returns:
- A list of the results, in order of the provided devices.
- Raises:
- Any exception raised by any of the called functions.
- """
- self._assertNoShadow('pGet')
- self.pFinish(timeout)
- return self._objs
-
- def pMap(self, f, *args, **kwargs):
- """Map a function across the current wrapped objects in parallel.
-
- This calls f(o, *args, **kwargs) for each o in the set of wrapped objects.
-
- Note that this call is asynchronous. Call pFinish on the return value to
- block until the call finishes.
-
- Args:
- f: The function to call.
- args: The positional args to pass to f.
- kwargs: The keyword args to pass to f.
- Returns:
- A Parallelizer wrapping the ReraiserThreadGroup running the map in
- parallel.
- """
- self._assertNoShadow('pMap')
- r = type(self)(self._orig_objs)
- r._objs = reraiser_thread.ReraiserThreadGroup(
- [reraiser_thread.ReraiserThread(
- f, args=tuple([o] + list(args)), kwargs=kwargs,
- name='%s(%s)' % (f.__name__, d))
- for d, o in zip(self._orig_objs, self._objs)])
- r._objs.StartAll() # pylint: disable=W0212
- return r
-
- def _assertNoShadow(self, attr_name):
- """Ensures that |attr_name| isn't shadowing part of the wrapped obejcts.
-
- If the wrapped objects _do_ have an |attr_name| attribute, it will be
- inaccessible to clients.
-
- Args:
- attr_name: The attribute to check.
- Raises:
- AssertionError if the wrapped objects have an attribute named 'attr_name'
- or '_assertNoShadow'.
- """
- if isinstance(self._objs, reraiser_thread.ReraiserThreadGroup):
- assert not hasattr(self._objs, '_assertNoShadow')
- assert not hasattr(self._objs, attr_name)
- else:
- assert not any(hasattr(o, '_assertNoShadow') for o in self._objs)
- assert not any(hasattr(o, attr_name) for o in self._objs)
-
-
-class SyncParallelizer(Parallelizer):
- """A Parallelizer that blocks on function calls."""
-
- # override
- def __call__(self, *args, **kwargs):
- """Emulate calling |self| with |args| and |kwargs|.
-
- Note that this call is synchronous.
-
- Returns:
- A Parallelizer emulating the value returned from calling |self| with
- |args| and |kwargs|.
- Raises:
- AttributeError if the wrapped objects aren't callable.
- """
- r = super(SyncParallelizer, self).__call__(*args, **kwargs)
- r.pFinish(None)
- return r
-
- # override
- def pMap(self, f, *args, **kwargs):
- """Map a function across the current wrapped objects in parallel.
-
- This calls f(o, *args, **kwargs) for each o in the set of wrapped objects.
-
- Note that this call is synchronous.
-
- Args:
- f: The function to call.
- args: The positional args to pass to f.
- kwargs: The keyword args to pass to f.
- Returns:
- A Parallelizer wrapping the ReraiserThreadGroup running the map in
- parallel.
- """
- r = super(SyncParallelizer, self).pMap(f, *args, **kwargs)
- r.pFinish(None)
- return r
-
diff --git a/third_party/catapult/devil/devil/utils/parallelizer_test.py b/third_party/catapult/devil/devil/utils/parallelizer_test.py
deleted file mode 100644
index 32ff7ec547..0000000000
--- a/third_party/catapult/devil/devil/utils/parallelizer_test.py
+++ /dev/null
@@ -1,162 +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.
-
-"""Unit tests for the contents of parallelizer.py."""
-
-# pylint: disable=W0212
-# pylint: disable=W0613
-
-import os
-import tempfile
-import time
-import unittest
-
-from devil.utils import parallelizer
-
-
-class ParallelizerTestObject(object):
- """Class used to test parallelizer.Parallelizer."""
-
- parallel = parallelizer.Parallelizer
-
- def __init__(self, thing, completion_file_name=None):
- self._thing = thing
- self._completion_file_name = completion_file_name
- self.helper = ParallelizerTestObjectHelper(thing)
-
- @staticmethod
- def doReturn(what):
- return what
-
- @classmethod
- def doRaise(cls, what):
- raise what
-
- def doSetTheThing(self, new_thing):
- self._thing = new_thing
-
- def doReturnTheThing(self):
- return self._thing
-
- def doRaiseTheThing(self):
- raise self._thing
-
- def doRaiseIfExceptionElseSleepFor(self, sleep_duration):
- if isinstance(self._thing, Exception):
- raise self._thing
- time.sleep(sleep_duration)
- self._write_completion_file()
- return self._thing
-
- def _write_completion_file(self):
- if self._completion_file_name and len(self._completion_file_name):
- with open(self._completion_file_name, 'w+b') as completion_file:
- completion_file.write('complete')
-
- def __getitem__(self, index):
- return self._thing[index]
-
- def __str__(self):
- return type(self).__name__
-
-
-class ParallelizerTestObjectHelper(object):
-
- def __init__(self, thing):
- self._thing = thing
-
- def doReturnStringThing(self):
- return str(self._thing)
-
-
-class ParallelizerTest(unittest.TestCase):
-
- def testInitEmptyList(self):
- r = parallelizer.Parallelizer([]).replace('a', 'b').pGet(0.1)
- self.assertEquals([], r)
-
- def testMethodCall(self):
- test_data = ['abc_foo', 'def_foo', 'ghi_foo']
- expected = ['abc_bar', 'def_bar', 'ghi_bar']
- r = parallelizer.Parallelizer(test_data).replace('_foo', '_bar').pGet(0.1)
- self.assertEquals(expected, r)
-
- def testMutate(self):
- devices = [ParallelizerTestObject(True) for _ in xrange(0, 10)]
- self.assertTrue(all(d.doReturnTheThing() for d in devices))
- ParallelizerTestObject.parallel(devices).doSetTheThing(False).pFinish(1)
- self.assertTrue(not any(d.doReturnTheThing() for d in devices))
-
- def testAllReturn(self):
- devices = [ParallelizerTestObject(True) for _ in xrange(0, 10)]
- results = ParallelizerTestObject.parallel(
- devices).doReturnTheThing().pGet(1)
- self.assertTrue(isinstance(results, list))
- self.assertEquals(10, len(results))
- self.assertTrue(all(results))
-
- def testAllRaise(self):
- devices = [ParallelizerTestObject(Exception('thing %d' % i))
- for i in xrange(0, 10)]
- p = ParallelizerTestObject.parallel(devices).doRaiseTheThing()
- with self.assertRaises(Exception):
- p.pGet(1)
-
- def testOneFailOthersComplete(self):
- parallel_device_count = 10
- exception_index = 7
- exception_msg = 'thing %d' % exception_index
-
- try:
- completion_files = [tempfile.NamedTemporaryFile(delete=False)
- for _ in xrange(0, parallel_device_count)]
- devices = [
- ParallelizerTestObject(
- i if i != exception_index else Exception(exception_msg),
- completion_files[i].name)
- for i in xrange(0, parallel_device_count)]
- for f in completion_files:
- f.close()
- p = ParallelizerTestObject.parallel(devices)
- with self.assertRaises(Exception) as e:
- p.doRaiseIfExceptionElseSleepFor(2).pGet(3)
- self.assertTrue(exception_msg in str(e.exception))
- for i in xrange(0, parallel_device_count):
- with open(completion_files[i].name) as f:
- if i == exception_index:
- self.assertEquals('', f.read())
- else:
- self.assertEquals('complete', f.read())
- finally:
- for f in completion_files:
- os.remove(f.name)
-
- def testReusable(self):
- devices = [ParallelizerTestObject(True) for _ in xrange(0, 10)]
- p = ParallelizerTestObject.parallel(devices)
- results = p.doReturn(True).pGet(1)
- self.assertTrue(all(results))
- results = p.doReturn(True).pGet(1)
- self.assertTrue(all(results))
- with self.assertRaises(Exception):
- results = p.doRaise(Exception('reusableTest')).pGet(1)
-
- def testContained(self):
- devices = [ParallelizerTestObject(i) for i in xrange(0, 10)]
- results = (ParallelizerTestObject.parallel(devices).helper
- .doReturnStringThing().pGet(1))
- self.assertTrue(isinstance(results, list))
- self.assertEquals(10, len(results))
- for i in xrange(0, 10):
- self.assertEquals(str(i), results[i])
-
- def testGetItem(self):
- devices = [ParallelizerTestObject(range(i, i + 10)) for i in xrange(0, 10)]
- results = ParallelizerTestObject.parallel(devices)[9].pGet(1)
- self.assertEquals(range(9, 19), results)
-
-
-if __name__ == '__main__':
- unittest.main(verbosity=2)
-
diff --git a/third_party/catapult/devil/devil/utils/reraiser_thread.py b/third_party/catapult/devil/devil/utils/reraiser_thread.py
deleted file mode 100644
index 56d95f3937..0000000000
--- a/third_party/catapult/devil/devil/utils/reraiser_thread.py
+++ /dev/null
@@ -1,228 +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.
-
-"""Thread and ThreadGroup that reraise exceptions on the main thread."""
-# pylint: disable=W0212
-
-import logging
-import sys
-import threading
-import time
-import traceback
-
-from devil.utils import watchdog_timer
-
-
-class TimeoutError(Exception):
- """Module-specific timeout exception."""
- pass
-
-
-def LogThreadStack(thread, error_log_func=logging.critical):
- """Log the stack for the given thread.
-
- Args:
- thread: a threading.Thread instance.
- error_log_func: Logging function when logging errors.
- """
- stack = sys._current_frames()[thread.ident]
- error_log_func('*' * 80)
- error_log_func('Stack dump for thread %r', thread.name)
- error_log_func('*' * 80)
- for filename, lineno, name, line in traceback.extract_stack(stack):
- error_log_func('File: "%s", line %d, in %s', filename, lineno, name)
- if line:
- error_log_func(' %s', line.strip())
- error_log_func('*' * 80)
-
-
-class ReraiserThread(threading.Thread):
- """Thread class that can reraise exceptions."""
-
- def __init__(self, func, args=None, kwargs=None, name=None):
- """Initialize thread.
-
- Args:
- func: callable to call on a new thread.
- args: list of positional arguments for callable, defaults to empty.
- kwargs: dictionary of keyword arguments for callable, defaults to empty.
- name: thread name, defaults to Thread-N.
- """
- if not name and func.__name__ != '<lambda>':
- name = func.__name__
- super(ReraiserThread, self).__init__(name=name)
- if not args:
- args = []
- if not kwargs:
- kwargs = {}
- self.daemon = True
- self._func = func
- self._args = args
- self._kwargs = kwargs
- self._ret = None
- self._exc_info = None
- self._thread_group = None
-
- def ReraiseIfException(self):
- """Reraise exception if an exception was raised in the thread."""
- if self._exc_info:
- raise self._exc_info[0], self._exc_info[1], self._exc_info[2]
-
- def GetReturnValue(self):
- """Reraise exception if present, otherwise get the return value."""
- self.ReraiseIfException()
- return self._ret
-
- # override
- def run(self):
- """Overrides Thread.run() to add support for reraising exceptions."""
- try:
- self._ret = self._func(*self._args, **self._kwargs)
- except: # pylint: disable=W0702
- self._exc_info = sys.exc_info()
-
-
-class ReraiserThreadGroup(object):
- """A group of ReraiserThread objects."""
-
- def __init__(self, threads=None):
- """Initialize thread group.
-
- Args:
- threads: a list of ReraiserThread objects; defaults to empty.
- """
- self._threads = []
- # Set when a thread from one group has called JoinAll on another. It is used
- # to detect when a there is a TimeoutRetryThread active that links to the
- # current thread.
- self.blocked_parent_thread_group = None
- if threads:
- for thread in threads:
- self.Add(thread)
-
- def Add(self, thread):
- """Add a thread to the group.
-
- Args:
- thread: a ReraiserThread object.
- """
- assert thread._thread_group is None
- thread._thread_group = self
- self._threads.append(thread)
-
- def StartAll(self, will_block=False):
- """Start all threads.
-
- Args:
- will_block: Whether the calling thread will subsequently block on this
- thread group. Causes the active ReraiserThreadGroup (if there is one)
- to be marked as blocking on this thread group.
- """
- if will_block:
- # Multiple threads blocking on the same outer thread should not happen in
- # practice.
- assert not self.blocked_parent_thread_group
- self.blocked_parent_thread_group = CurrentThreadGroup()
- for thread in self._threads:
- thread.start()
-
- def _JoinAll(self, watcher=None, timeout=None):
- """Join all threads without stack dumps.
-
- Reraises exceptions raised by the child threads and supports breaking
- immediately on exceptions raised on the main thread.
-
- Args:
- watcher: Watchdog object providing the thread timeout. If none is
- provided, the thread will never be timed out.
- timeout: An optional number of seconds to wait before timing out the join
- operation. This will not time out the threads.
- """
- if watcher is None:
- watcher = watchdog_timer.WatchdogTimer(None)
- alive_threads = self._threads[:]
- end_time = (time.time() + timeout) if timeout else None
- try:
- while alive_threads and (end_time is None or end_time > time.time()):
- for thread in alive_threads[:]:
- if watcher.IsTimedOut():
- raise TimeoutError('Timed out waiting for %d of %d threads.' %
- (len(alive_threads), len(self._threads)))
- # Allow the main thread to periodically check for interrupts.
- thread.join(0.1)
- if not thread.isAlive():
- alive_threads.remove(thread)
- # All threads are allowed to complete before reraising exceptions.
- for thread in self._threads:
- thread.ReraiseIfException()
- finally:
- self.blocked_parent_thread_group = None
-
- def IsAlive(self):
- """Check whether any of the threads are still alive.
-
- Returns:
- Whether any of the threads are still alive.
- """
- return any(t.isAlive() for t in self._threads)
-
- def JoinAll(self, watcher=None, timeout=None,
- error_log_func=logging.critical):
- """Join all threads.
-
- Reraises exceptions raised by the child threads and supports breaking
- immediately on exceptions raised on the main thread. Unfinished threads'
- stacks will be logged on watchdog timeout.
-
- Args:
- watcher: Watchdog object providing the thread timeout. If none is
- provided, the thread will never be timed out.
- timeout: An optional number of seconds to wait before timing out the join
- operation. This will not time out the threads.
- error_log_func: Logging function when logging errors.
- """
- try:
- self._JoinAll(watcher, timeout)
- except TimeoutError:
- error_log_func('Timed out. Dumping threads.')
- for thread in (t for t in self._threads if t.isAlive()):
- LogThreadStack(thread, error_log_func=error_log_func)
- raise
-
- def GetAllReturnValues(self, watcher=None):
- """Get all return values, joining all threads if necessary.
-
- Args:
- watcher: same as in |JoinAll|. Only used if threads are alive.
- """
- if any([t.isAlive() for t in self._threads]):
- self.JoinAll(watcher)
- return [t.GetReturnValue() for t in self._threads]
-
-
-def CurrentThreadGroup():
- """Returns the ReraiserThreadGroup that owns the running thread.
-
- Returns:
- The current thread group, otherwise None.
- """
- current_thread = threading.current_thread()
- if isinstance(current_thread, ReraiserThread):
- return current_thread._thread_group # pylint: disable=no-member
- return None
-
-
-def RunAsync(funcs, watcher=None):
- """Executes the given functions in parallel and returns their results.
-
- Args:
- funcs: List of functions to perform on their own threads.
- watcher: Watchdog object providing timeout, by default waits forever.
-
- Returns:
- A list of return values in the order of the given functions.
- """
- thread_group = ReraiserThreadGroup(ReraiserThread(f) for f in funcs)
- thread_group.StartAll(will_block=True)
- return thread_group.GetAllReturnValues(watcher=watcher)
diff --git a/third_party/catapult/devil/devil/utils/reraiser_thread_unittest.py b/third_party/catapult/devil/devil/utils/reraiser_thread_unittest.py
deleted file mode 100644
index e3c4e6bee8..0000000000
--- a/third_party/catapult/devil/devil/utils/reraiser_thread_unittest.py
+++ /dev/null
@@ -1,117 +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.
-
-"""Unittests for reraiser_thread.py."""
-
-import threading
-import unittest
-
-from devil.utils import reraiser_thread
-from devil.utils import watchdog_timer
-
-
-class TestException(Exception):
- pass
-
-
-class TestReraiserThread(unittest.TestCase):
- """Tests for reraiser_thread.ReraiserThread."""
-
- def testNominal(self):
- result = [None, None]
-
- def f(a, b=None):
- result[0] = a
- result[1] = b
-
- thread = reraiser_thread.ReraiserThread(f, [1], {'b': 2})
- thread.start()
- thread.join()
- self.assertEqual(result[0], 1)
- self.assertEqual(result[1], 2)
-
- def testRaise(self):
- def f():
- raise TestException
-
- thread = reraiser_thread.ReraiserThread(f)
- thread.start()
- thread.join()
- with self.assertRaises(TestException):
- thread.ReraiseIfException()
-
-
-class TestReraiserThreadGroup(unittest.TestCase):
- """Tests for reraiser_thread.ReraiserThreadGroup."""
-
- def testInit(self):
- ran = [False] * 5
-
- def f(i):
- ran[i] = True
-
- group = reraiser_thread.ReraiserThreadGroup(
- [reraiser_thread.ReraiserThread(f, args=[i]) for i in range(5)])
- group.StartAll()
- group.JoinAll()
- for v in ran:
- self.assertTrue(v)
-
- def testAdd(self):
- ran = [False] * 5
-
- def f(i):
- ran[i] = True
-
- group = reraiser_thread.ReraiserThreadGroup()
- for i in xrange(5):
- group.Add(reraiser_thread.ReraiserThread(f, args=[i]))
- group.StartAll()
- group.JoinAll()
- for v in ran:
- self.assertTrue(v)
-
- def testJoinRaise(self):
- def f():
- raise TestException
- group = reraiser_thread.ReraiserThreadGroup(
- [reraiser_thread.ReraiserThread(f) for _ in xrange(5)])
- group.StartAll()
- with self.assertRaises(TestException):
- group.JoinAll()
-
- def testJoinTimeout(self):
- def f():
- pass
- event = threading.Event()
-
- def g():
- event.wait()
- group = reraiser_thread.ReraiserThreadGroup(
- [reraiser_thread.ReraiserThread(g),
- reraiser_thread.ReraiserThread(f)])
- group.StartAll()
- with self.assertRaises(reraiser_thread.TimeoutError):
- group.JoinAll(watchdog_timer.WatchdogTimer(0.01))
- event.set()
-
-
-class TestRunAsync(unittest.TestCase):
- """Tests for reraiser_thread.RunAsync."""
-
- def testNoArgs(self):
- results = reraiser_thread.RunAsync([])
- self.assertEqual([], results)
-
- def testOneArg(self):
- results = reraiser_thread.RunAsync([lambda: 1])
- self.assertEqual([1], results)
-
- def testTwoArgs(self):
- a, b = reraiser_thread.RunAsync((lambda: 1, lambda: 2))
- self.assertEqual(1, a)
- self.assertEqual(2, b)
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/third_party/catapult/devil/devil/utils/reset_usb.py b/third_party/catapult/devil/devil/utils/reset_usb.py
deleted file mode 100755
index 0335227dca..0000000000
--- a/third_party/catapult/devil/devil/utils/reset_usb.py
+++ /dev/null
@@ -1,111 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import argparse
-import fcntl
-import logging
-import os
-import re
-import sys
-
-if __name__ == '__main__':
- sys.path.append(
- os.path.abspath(os.path.join(os.path.dirname(__file__),
- '..', '..')))
-
-from devil.android import device_errors
-from devil.utils import lsusb
-from devil.utils import run_tests_helper
-
-logger = logging.getLogger(__name__)
-
-_INDENTATION_RE = re.compile(r'^( *)')
-_LSUSB_BUS_DEVICE_RE = re.compile(r'^Bus (\d{3}) Device (\d{3}):')
-_LSUSB_ENTRY_RE = re.compile(r'^ *([^ ]+) +([^ ]+) *([^ ].*)?$')
-_LSUSB_GROUP_RE = re.compile(r'^ *([^ ]+.*):$')
-
-_USBDEVFS_RESET = ord('U') << 8 | 20
-
-
-def reset_usb(bus, device):
- """Reset the USB device with the given bus and device."""
- usb_file_path = '/dev/bus/usb/%03d/%03d' % (bus, device)
- with open(usb_file_path, 'w') as usb_file:
- logger.debug('fcntl.ioctl(%s, %d)', usb_file_path, _USBDEVFS_RESET)
- fcntl.ioctl(usb_file, _USBDEVFS_RESET)
-
-
-def reset_android_usb(serial):
- """Reset the USB device for the given Android device."""
- lsusb_info = lsusb.lsusb()
-
- bus = None
- device = None
- for device_info in lsusb_info:
- device_serial = lsusb.get_lsusb_serial(device_info)
- if device_serial == serial:
- bus = int(device_info.get('bus'))
- device = int(device_info.get('device'))
-
- if bus and device:
- reset_usb(bus, device)
- else:
- raise device_errors.DeviceUnreachableError(
- 'Unable to determine bus(%s) or device(%s) for device %s'
- % (bus, device, serial))
-
-
-def reset_all_android_devices():
- """Reset all USB devices that look like an Android device."""
- _reset_all_matching(lambda i: bool(lsusb.get_lsusb_serial(i)))
-
-
-def _reset_all_matching(condition):
- lsusb_info = lsusb.lsusb()
- for device_info in lsusb_info:
- if int(device_info.get('device')) != 1 and condition(device_info):
- bus = int(device_info.get('bus'))
- device = int(device_info.get('device'))
- try:
- reset_usb(bus, device)
- serial = lsusb.get_lsusb_serial(device_info)
- if serial:
- logger.info(
- 'Reset USB device (bus: %03d, device: %03d, serial: %s)',
- bus, device, serial)
- else:
- logger.info(
- 'Reset USB device (bus: %03d, device: %03d)',
- bus, device)
- except IOError:
- logger.error(
- 'Failed to reset USB device (bus: %03d, device: %03d)',
- bus, device)
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument('-v', '--verbose', action='count')
- parser.add_argument('-s', '--serial')
- parser.add_argument('--bus', type=int)
- parser.add_argument('--device', type=int)
- args = parser.parse_args()
-
- run_tests_helper.SetLogLevel(args.verbose)
-
- if args.serial:
- reset_android_usb(args.serial)
- elif args.bus and args.device:
- reset_usb(args.bus, args.device)
- else:
- parser.error('Unable to determine target. '
- 'Specify --serial or BOTH --bus and --device.')
-
- return 0
-
-
-if __name__ == '__main__':
- sys.exit(main())
-
diff --git a/third_party/catapult/devil/devil/utils/run_tests_helper.py b/third_party/catapult/devil/devil/utils/run_tests_helper.py
deleted file mode 100644
index 7df2da6585..0000000000
--- a/third_party/catapult/devil/devil/utils/run_tests_helper.py
+++ /dev/null
@@ -1,44 +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.
-
-"""Helper functions common to native, java and host-driven test runners."""
-
-import logging
-import sys
-import time
-
-
-class CustomFormatter(logging.Formatter):
- """Custom log formatter."""
-
- # override
- def __init__(self, fmt='%(threadName)-4s %(message)s'):
- # Can't use super() because in older Python versions logging.Formatter does
- # not inherit from object.
- logging.Formatter.__init__(self, fmt=fmt)
- self._creation_time = time.time()
-
- # override
- def format(self, record):
- # Can't use super() because in older Python versions logging.Formatter does
- # not inherit from object.
- msg = logging.Formatter.format(self, record)
- if 'MainThread' in msg[:19]:
- msg = msg.replace('MainThread', 'Main', 1)
- timediff = time.time() - self._creation_time
- return '%s %8.3fs %s' % (record.levelname[0], timediff, msg)
-
-
-def SetLogLevel(verbose_count):
- """Sets log level as |verbose_count|."""
- log_level = logging.WARNING # Default.
- if verbose_count == 1:
- log_level = logging.INFO
- elif verbose_count >= 2:
- log_level = logging.DEBUG
- logger = logging.getLogger()
- logger.setLevel(log_level)
- custom_handler = logging.StreamHandler(sys.stdout)
- custom_handler.setFormatter(CustomFormatter())
- logging.getLogger().addHandler(custom_handler)
diff --git a/third_party/catapult/devil/devil/utils/signal_handler.py b/third_party/catapult/devil/devil/utils/signal_handler.py
deleted file mode 100644
index 1230f8df5f..0000000000
--- a/third_party/catapult/devil/devil/utils/signal_handler.py
+++ /dev/null
@@ -1,48 +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.
-
-import contextlib
-import signal
-
-
-@contextlib.contextmanager
-def SignalHandler(signalnum, handler):
- """Sets the signal handler for the given signal in the wrapped context.
-
- Args:
- signum: The signal for which a handler should be added.
- additional_handler: The handler to add.
- """
- existing_handler = signal.getsignal(signalnum)
-
- try:
- signal.signal(signalnum, handler)
- yield
- finally:
- signal.signal(signalnum, existing_handler)
-
-
-@contextlib.contextmanager
-def AddSignalHandler(signalnum, additional_handler):
- """Adds a signal handler for the given signal in the wrapped context.
-
- This runs the new handler after any existing handler rather than
- replacing the existing handler.
-
- Args:
- signum: The signal for which a handler should be added.
- additional_handler: The handler to add.
- """
- existing_handler = signal.getsignal(signalnum)
-
- def handler(signum, frame):
- if callable(existing_handler):
- existing_handler(signum, frame)
- additional_handler(signum, frame)
-
- try:
- signal.signal(signalnum, handler)
- yield
- finally:
- signal.signal(signalnum, existing_handler)
diff --git a/third_party/catapult/devil/devil/utils/test/data/test_serial_map.json b/third_party/catapult/devil/devil/utils/test/data/test_serial_map.json
deleted file mode 100644
index f0682816a0..0000000000
--- a/third_party/catapult/devil/devil/utils/test/data/test_serial_map.json
+++ /dev/null
@@ -1 +0,0 @@
-[{"phone": "Phone1", "battor": "BattOr1"}, {"phone": "Phone2", "battor": "BattOr2"}, {"phone": "Phone3", "battor": "BattOr3"}]
diff --git a/third_party/catapult/devil/devil/utils/timeout_retry.py b/third_party/catapult/devil/devil/utils/timeout_retry.py
deleted file mode 100644
index d2304629e9..0000000000
--- a/third_party/catapult/devil/devil/utils/timeout_retry.py
+++ /dev/null
@@ -1,175 +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.
-
-"""A utility to run functions with timeouts and retries."""
-# pylint: disable=W0702
-
-import logging
-import threading
-import time
-
-from devil.utils import reraiser_thread
-from devil.utils import watchdog_timer
-
-logger = logging.getLogger(__name__)
-
-
-class TimeoutRetryThreadGroup(reraiser_thread.ReraiserThreadGroup):
-
- def __init__(self, timeout, threads=None):
- super(TimeoutRetryThreadGroup, self).__init__(threads)
- self._watcher = watchdog_timer.WatchdogTimer(timeout)
-
- def GetWatcher(self):
- """Returns the watchdog keeping track of this thread's time."""
- return self._watcher
-
- def GetElapsedTime(self):
- return self._watcher.GetElapsed()
-
- def GetRemainingTime(self, required=0, msg=None):
- """Get the remaining time before the thread times out.
-
- Useful to send as the |timeout| parameter of async IO operations.
-
- Args:
- required: minimum amount of time that will be required to complete, e.g.,
- some sleep or IO operation.
- msg: error message to show if timing out.
-
- Returns:
- The number of seconds remaining before the thread times out, or None
- if the thread never times out.
-
- Raises:
- reraiser_thread.TimeoutError if the remaining time is less than the
- required time.
- """
- remaining = self._watcher.GetRemaining()
- if remaining is not None and remaining < required:
- if msg is None:
- msg = 'Timeout expired'
- if remaining > 0:
- msg += (', wait of %.1f secs required but only %.1f secs left'
- % (required, remaining))
- raise reraiser_thread.TimeoutError(msg)
- return remaining
-
-
-def CurrentTimeoutThreadGroup():
- """Returns the thread group that owns or is blocked on the active thread.
-
- Returns:
- Returns None if no TimeoutRetryThreadGroup is tracking the current thread.
- """
- thread_group = reraiser_thread.CurrentThreadGroup()
- while thread_group:
- if isinstance(thread_group, TimeoutRetryThreadGroup):
- return thread_group
- thread_group = thread_group.blocked_parent_thread_group
- return None
-
-
-def WaitFor(condition, wait_period=5, max_tries=None):
- """Wait for a condition to become true.
-
- Repeatedly call the function condition(), with no arguments, until it returns
- a true value.
-
- If called within a TimeoutRetryThreadGroup, it cooperates nicely with it.
-
- Args:
- condition: function with the condition to check
- wait_period: number of seconds to wait before retrying to check the
- condition
- max_tries: maximum number of checks to make, the default tries forever
- or until the TimeoutRetryThreadGroup expires.
-
- Returns:
- The true value returned by the condition, or None if the condition was
- not met after max_tries.
-
- Raises:
- reraiser_thread.TimeoutError: if the current thread is a
- TimeoutRetryThreadGroup and the timeout expires.
- """
- condition_name = condition.__name__
- timeout_thread_group = CurrentTimeoutThreadGroup()
- while max_tries is None or max_tries > 0:
- result = condition()
- if max_tries is not None:
- max_tries -= 1
- msg = ['condition', repr(condition_name), 'met' if result else 'not met']
- if timeout_thread_group:
- # pylint: disable=no-member
- msg.append('(%.1fs)' % timeout_thread_group.GetElapsedTime())
- logger.info(' '.join(msg))
- if result:
- return result
- if timeout_thread_group:
- # pylint: disable=no-member
- timeout_thread_group.GetRemainingTime(wait_period,
- msg='Timed out waiting for %r' % condition_name)
- time.sleep(wait_period)
- return None
-
-
-def AlwaysRetry(_exception):
- return True
-
-
-def Run(func, timeout, retries, args=None, kwargs=None, desc=None,
- error_log_func=logging.critical, retry_if_func=AlwaysRetry):
- """Runs the passed function in a separate thread with timeouts and retries.
-
- Args:
- func: the function to be wrapped.
- timeout: the timeout in seconds for each try.
- retries: the number of retries.
- args: list of positional args to pass to |func|.
- kwargs: dictionary of keyword args to pass to |func|.
- desc: An optional description of |func| used in logging. If omitted,
- |func.__name__| will be used.
- error_log_func: Logging function when logging errors.
- retry_if_func: Unary callable that takes an exception and returns
- whether |func| should be retried. Defaults to always retrying.
-
- Returns:
- The return value of func(*args, **kwargs).
- """
- if not args:
- args = []
- if not kwargs:
- kwargs = {}
- if not desc:
- desc = func.__name__
-
- num_try = 1
- while True:
- thread_name = 'TimeoutThread-%d-for-%s' % (num_try,
- threading.current_thread().name)
- child_thread = reraiser_thread.ReraiserThread(lambda: func(*args, **kwargs),
- name=thread_name)
- try:
- thread_group = TimeoutRetryThreadGroup(timeout, threads=[child_thread])
- thread_group.StartAll(will_block=True)
- while True:
- thread_group.JoinAll(watcher=thread_group.GetWatcher(), timeout=60,
- error_log_func=error_log_func)
- if thread_group.IsAlive():
- logger.info('Still working on %s', desc)
- else:
- return thread_group.GetAllReturnValues()[0]
- except reraiser_thread.TimeoutError as e:
- # Timeouts already get their stacks logged.
- if num_try > retries or not retry_if_func(e):
- raise
- # Do not catch KeyboardInterrupt.
- except Exception as e: # pylint: disable=broad-except
- if num_try > retries or not retry_if_func(e):
- raise
- error_log_func(
- '(%s) Exception on %s, attempt %d of %d: %r',
- thread_name, desc, num_try, retries + 1, e)
- num_try += 1
diff --git a/third_party/catapult/devil/devil/utils/timeout_retry_unittest.py b/third_party/catapult/devil/devil/utils/timeout_retry_unittest.py
deleted file mode 100755
index 0eeb31a4f1..0000000000
--- a/third_party/catapult/devil/devil/utils/timeout_retry_unittest.py
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/usr/bin/python
-# 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.
-
-"""Unittests for timeout_and_retry.py."""
-
-import logging
-import time
-import unittest
-
-from devil.utils import reraiser_thread
-from devil.utils import timeout_retry
-
-
-_DEFAULT_TIMEOUT = .1
-
-
-class TestException(Exception):
- pass
-
-
-def _CountTries(tries):
- tries[0] += 1
- raise TestException
-
-
-class TestRun(unittest.TestCase):
- """Tests for timeout_retry.Run."""
-
- def testRun(self):
- self.assertTrue(timeout_retry.Run(
- lambda x: x, 30, 3, [True], {}))
-
- def testTimeout(self):
- tries = [0]
-
- def _sleep():
- tries[0] += 1
- time.sleep(1)
-
- self.assertRaises(
- reraiser_thread.TimeoutError, timeout_retry.Run, _sleep, .01, 1,
- error_log_func=logging.debug)
- self.assertEqual(tries[0], 2)
-
- def testRetries(self):
- tries = [0]
- self.assertRaises(
- TestException, timeout_retry.Run, lambda: _CountTries(tries),
- _DEFAULT_TIMEOUT, 3, error_log_func=logging.debug)
- self.assertEqual(tries[0], 4)
-
- def testNoRetries(self):
- tries = [0]
- self.assertRaises(
- TestException, timeout_retry.Run, lambda: _CountTries(tries),
- _DEFAULT_TIMEOUT, 0, error_log_func=logging.debug)
- self.assertEqual(tries[0], 1)
-
- def testReturnValue(self):
- self.assertTrue(timeout_retry.Run(lambda: True, _DEFAULT_TIMEOUT, 3))
-
- def testCurrentTimeoutThreadGroup(self):
- def InnerFunc():
- current_thread_group = timeout_retry.CurrentTimeoutThreadGroup()
- self.assertIsNotNone(current_thread_group)
-
- def InnerInnerFunc():
- self.assertEqual(current_thread_group,
- timeout_retry.CurrentTimeoutThreadGroup())
- return True
- return reraiser_thread.RunAsync((InnerInnerFunc,))[0]
-
- self.assertTrue(timeout_retry.Run(InnerFunc, _DEFAULT_TIMEOUT, 3))
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/third_party/catapult/devil/devil/utils/update_mapping.py b/third_party/catapult/devil/devil/utils/update_mapping.py
deleted file mode 100755
index 6666b9b084..0000000000
--- a/third_party/catapult/devil/devil/utils/update_mapping.py
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env python
-
-# 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 argparse
-import sys
-
-from devil.utils import battor_device_mapping
-
-def parse_options():
- """Parses and checks the command-line options.
-
- Returns:
- A tuple containing the options structure.
- """
- usage = 'Usage: ./update_mapping.py [options]'
- desc = ('Example: ./update_mapping.py -o mapping.json.\n'
- 'This script generates and stores a file that gives the\n'
- 'mapping between phone serial numbers and BattOr serial numbers\n'
- 'Mapping is based on which physical ports on the USB hubs the\n'
- 'devices are plugged in to. For instance, if there are two hubs,\n'
- 'the phone connected to port N on the first hub is mapped to the\n'
- 'BattOr connected to port N on the second hub, for each N.')
- parser = argparse.ArgumentParser(usage=usage, description=desc)
- parser.add_argument('-o', '--output', dest='out_file',
- default='mapping.json', type=str,
- action='store', help='mapping file name')
- parser.add_argument('-u', '--hub', dest='hub_types',
- action='append', choices=['plugable_7port',
- 'plugable_7port_usb3_part2',
- 'plugable_7port_usb3_part3'],
- help='USB hub types.')
- options = parser.parse_args()
- if not options.hub_types:
- options.hub_types = ['plugable_7port', 'plugable_7port_usb3_part2',
- 'plugable_7port_usb3_part3']
- return options
-
-def main():
- options = parse_options()
- battor_device_mapping.GenerateSerialMapFile(options.out_file,
- options.hub_types)
-
-if __name__ == "__main__":
- sys.exit(main())
diff --git a/third_party/catapult/devil/devil/utils/usb_hubs.py b/third_party/catapult/devil/devil/utils/usb_hubs.py
deleted file mode 100644
index 1b9566a356..0000000000
--- a/third_party/catapult/devil/devil/utils/usb_hubs.py
+++ /dev/null
@@ -1,165 +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.
-
-PLUGABLE_7PORT_LAYOUT = {1:7,
- 2:6,
- 3:5,
- 4:{1:4, 2:3, 3:2, 4:1}}
-
-PLUGABLE_7PORT_USB3_LAYOUT = {1:{1:1, 2:2, 3:3, 4:4},
- 2:5,
- 3:6,
- 4:7}
-
-KEEDOX_LAYOUT = {1:1,
- 2:2,
- 3:3,
- 4:{1:4, 2:5, 3:6, 4:7}}
-
-class HubType(object):
- def __init__(self, id_func, port_mapping):
- """Defines a type of hub.
-
- Args:
- id_func: [USBNode -> bool] is a function that can be run on a node
- to determine if the node represents this type of hub.
- port_mapping: [dict(int:(int|dict))] maps virtual to physical port
- numbers. For instance, {3:1, 1:2, 2:3} means that virtual port 3
- corresponds to physical port 1, virtual port 1 corresponds to physical
- port 2, and virtual port 2 corresponds to physical port 3. In the
- case of hubs with "internal" topology, this is represented by nested
- maps. For instance, {1:{1:1,2:2},2:{1:3,2:4}} means, e.g. that the
- device plugged into physical port 3 will show up as being connected
- to port 1, on a device which is connected to port 2 on the hub.
- """
- self._id_func = id_func
- # v2p = "virtual to physical" ports
- self._v2p_port = port_mapping
-
- def IsType(self, node):
- """Determines if the given Node is a hub of this type.
-
- Args:
- node: [USBNode] Node to check.
- """
- return self._id_func(node)
-
- def GetPhysicalPortToNodeTuples(self, node):
- """Gets devices connected to the physical ports on a hub of this type.
-
- Args:
- node: [USBNode] Node representing a hub of this type.
-
- Yields:
- A series of (int, USBNode) tuples giving a physical port
- and the USBNode connected to it.
-
- Raises:
- ValueError: If the given node isn't a hub of this type.
- """
- if self.IsType(node):
- for res in self._GppHelper(node, self._v2p_port):
- yield res
- else:
- raise ValueError('Node must be a hub of this type')
-
- def _GppHelper(self, node, mapping):
- """Helper function for GetPhysicalPortToNodeMap.
-
- Gets devices connected to physical ports, based on device tree
- rooted at the given node and the mapping between virtual and physical
- ports.
-
- Args:
- node: [USBNode] Root of tree to search for devices.
- mapping: [dict] Mapping between virtual and physical ports.
-
- Yields:
- A series of (int, USBNode) tuples giving a physical port
- and the Node connected to it.
- """
- for (virtual, physical) in mapping.iteritems():
- if node.HasPort(virtual):
- if isinstance(physical, dict):
- for res in self._GppHelper(node.PortToDevice(virtual), physical):
- yield res
- else:
- yield (physical, node.PortToDevice(virtual))
-
-def _is_plugable_7port_hub(node):
- """Check if a node is a Plugable 7-Port Hub
- (Model USB2-HUB7BC)
- The topology of this device is a 4-port hub,
- with another 4-port hub connected on port 4.
- """
- if '1a40:0101' not in node.desc:
- return False
- if not node.HasPort(4):
- return False
- return '1a40:0101' in node.PortToDevice(4).desc
-
-# Plugable 7-Port USB-3 Hubs show up twice in the USB devices list; they have
-# two different "branches", one which has USB2 devices and one which has
-# USB3 devices. The "part2" is the "USB-2" branch of the hub, the
-# "part3" is the "USB-3" branch of the hub.
-
-def _is_plugable_7port_usb3_part2_hub(node):
- """Check if a node is the "USB2 branch" of
- a Plugable 7-Port USB-3 Hub (Model USB3-HUB7BC)
- The topology of this device is a 4-port hub,
- with another 4-port hub connected on port 1.
- """
- if '2109:2811' not in node.desc:
- return False
- if not node.HasPort(1):
- return False
- return '2109:2811' in node.PortToDevice(1).desc
-
-def _is_plugable_7port_usb3_part3_hub(node):
- """Check if a node is the "USB3 branch" of
- a Plugable 7-Port USB-3 Hub (Model USB3-HUB7BC)
- The topology of this device is a 4-port hub,
- with another 4-port hub connected on port 1.
- """
- if '2109:8110' not in node.desc:
- return False
- if not node.HasPort(1):
- return False
- return '2109:8110' in node.PortToDevice(1).desc
-
-def _is_keedox_hub(node):
- """Check if a node is a Keedox hub.
- The topology of this device is a 4-port hub,
- with another 4-port hub connected on port 4.
- """
- if '0bda:5411' not in node.desc:
- return False
- if not node.HasPort(4):
- return False
- return '0bda:5411' in node.PortToDevice(4).desc
-
-
-PLUGABLE_7PORT = HubType(_is_plugable_7port_hub, PLUGABLE_7PORT_LAYOUT)
-PLUGABLE_7PORT_USB3_PART2 = HubType(_is_plugable_7port_usb3_part2_hub,
- PLUGABLE_7PORT_USB3_LAYOUT)
-PLUGABLE_7PORT_USB3_PART3 = HubType(_is_plugable_7port_usb3_part3_hub,
- PLUGABLE_7PORT_USB3_LAYOUT)
-KEEDOX = HubType(_is_keedox_hub, KEEDOX_LAYOUT)
-
-ALL_HUBS = [PLUGABLE_7PORT,
- PLUGABLE_7PORT_USB3_PART2,
- PLUGABLE_7PORT_USB3_PART3,
- KEEDOX]
-
-def GetHubType(type_name):
- if type_name == 'plugable_7port':
- return PLUGABLE_7PORT
- if type_name == 'plugable_7port_usb3_part2':
- return PLUGABLE_7PORT_USB3_PART2
- if type_name == 'plugable_7port_usb3_part3':
- return PLUGABLE_7PORT_USB3_PART3
- if type_name == 'keedox':
- return KEEDOX
- else:
- raise ValueError('Invalid hub type')
diff --git a/third_party/catapult/devil/devil/utils/watchdog_timer.py b/third_party/catapult/devil/devil/utils/watchdog_timer.py
deleted file mode 100644
index 2f4c46455b..0000000000
--- a/third_party/catapult/devil/devil/utils/watchdog_timer.py
+++ /dev/null
@@ -1,47 +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.
-
-"""WatchdogTimer timeout objects."""
-
-import time
-
-
-class WatchdogTimer(object):
- """A resetable timeout-based watchdog.
-
- This object is threadsafe.
- """
-
- def __init__(self, timeout):
- """Initializes the watchdog.
-
- Args:
- timeout: The timeout in seconds. If timeout is None it will never timeout.
- """
- self._start_time = time.time()
- self._timeout = timeout
-
- def Reset(self):
- """Resets the timeout countdown."""
- self._start_time = time.time()
-
- def GetElapsed(self):
- """Returns the elapsed time of the watchdog."""
- return time.time() - self._start_time
-
- def GetRemaining(self):
- """Returns the remaining time of the watchdog."""
- if self._timeout:
- return self._timeout - self.GetElapsed()
- else:
- return None
-
- def IsTimedOut(self):
- """Whether the watchdog has timed out.
-
- Returns:
- True if the watchdog has timed out, False otherwise.
- """
- remaining = self.GetRemaining()
- return remaining is not None and remaining < 0
diff --git a/third_party/catapult/devil/devil/utils/zip_utils.py b/third_party/catapult/devil/devil/utils/zip_utils.py
deleted file mode 100644
index eaa6a2df01..0000000000
--- a/third_party/catapult/devil/devil/utils/zip_utils.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import logging
-import os
-import zipfile
-
-logger = logging.getLogger(__name__)
-
-
-def WriteToZipFile(zip_file, path, arc_path):
- """Recursively write |path| to |zip_file| as |arc_path|.
-
- zip_file: An open instance of zipfile.ZipFile.
- path: An absolute path to the file or directory to be zipped.
- arc_path: A relative path within the zip file to which the file or directory
- located at |path| should be written.
- """
- if os.path.isdir(path):
- for dir_path, _, file_names in os.walk(path):
- dir_arc_path = os.path.join(arc_path, os.path.relpath(dir_path, path))
- logger.debug('dir: %s -> %s', dir_path, dir_arc_path)
- zip_file.write(dir_path, dir_arc_path, zipfile.ZIP_STORED)
- for f in file_names:
- file_path = os.path.join(dir_path, f)
- file_arc_path = os.path.join(dir_arc_path, f)
- logger.debug('file: %s -> %s', file_path, file_arc_path)
- zip_file.write(file_path, file_arc_path, zipfile.ZIP_DEFLATED)
- else:
- logger.debug('file: %s -> %s', path, arc_path)
- zip_file.write(path, arc_path, zipfile.ZIP_DEFLATED)
-
diff --git a/third_party/catapult/devil/docs/adb_wrapper.md b/third_party/catapult/devil/docs/adb_wrapper.md
deleted file mode 100644
index a8dc3b057d..0000000000
--- a/third_party/catapult/devil/docs/adb_wrapper.md
+++ /dev/null
@@ -1,388 +0,0 @@
-# [devil.android.sdk.adb_wrapper](https://github.com/catapult-project/catapult/blob/master/devil/devil/android/sdk/adb_wrapper.py)
-
-*This page was autogenerated by `devil/utils/markdown.py --module-link https://github.com/catapult-project/catapult/blob/master/devil/devil/android/sdk/adb_wrapper.py`*
-
-## DeviceStat
-
-DeviceStat(st\_mode, st\_size, st\_time)
-### DeviceStat.\_\_repr\_\_
-
-Return a nicely formatted representation string
-### DeviceStat.\_\_getnewargs\_\_
-
-Return self as a plain tuple. Used by copy and pickle.
-### DeviceStat.\_\_getstate\_\_
-
-Exclude the OrderedDict from pickling
-## AdbWrapper
-
-A wrapper around a local Android Debug Bridge executable.
-### AdbWrapper.GetDeviceSerial
-
-Gets the device serial number associated with this object.
-```
- Returns:
- Device serial number as a string.
-```
-
-
-### AdbWrapper.Push
-
-Pushes a file from the host to the device.
-```
- Args:
- local: Path on the host filesystem.
- remote: Path on the device filesystem.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-```
-
-
-### AdbWrapper.Pull
-
-Pulls a file from the device to the host.
-```
- Args:
- remote: Path on the device filesystem.
- local: Path on the host filesystem.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-```
-
-
-### AdbWrapper.Shell
-
-Runs a shell command on the device.
-```
- Args:
- command: A string with the shell command to run.
- expect_status: (optional) Check that the command's exit status matches
- this value. Default is 0. If set to None the test is skipped.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-
- Returns:
- The output of the shell command as a string.
-
- Raises:
- device_errors.AdbCommandFailedError: If the exit status doesn't match
- |expect_status|.
-```
-
-
-### AdbWrapper.IterShell
-
-Runs a shell command and returns an iterator over its output lines.
-```
- Args:
- command: A string with the shell command to run.
- timeout: Timeout in seconds.
-
- Yields:
- The output of the command line by line.
-```
-
-
-### AdbWrapper.Ls
-
-List the contents of a directory on the device.
-```
- Args:
- path: Path on the device filesystem.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-
- Returns:
- A list of pairs (filename, stat) for each file found in the directory,
- where the stat object has the properties: st_mode, st_size, and st_time.
-
- Raises:
- AdbCommandFailedError if |path| does not specify a valid and accessible
- directory in the device, or the output of "adb ls" command is less
- than four columns
-```
-
-
-### AdbWrapper.Logcat
-
-Get an iterable over the logcat output.
-```
- Args:
- clear: If true, clear the logcat.
- dump: If true, dump the current logcat contents.
- filter_specs: If set, a list of specs to filter the logcat.
- logcat_format: If set, the format in which the logcat should be output.
- Options include "brief", "process", "tag", "thread", "raw", "time",
- "threadtime", and "long"
- ring_buffer: If set, a list of alternate ring buffers to request.
- Options include "main", "system", "radio", "events", "crash" or "all".
- The default is equivalent to ["main", "system", "crash"].
- iter_timeout: If set and neither clear nor dump is set, the number of
- seconds to wait between iterations. If no line is found before the
- given number of seconds elapses, the iterable will yield None.
- timeout: (optional) If set, timeout per try in seconds. If clear or dump
- is set, defaults to DEFAULT_TIMEOUT.
- retries: (optional) If clear or dump is set, the number of retries to
- attempt. Otherwise, does nothing.
-
- Yields:
- logcat output line by line.
-```
-
-
-### AdbWrapper.Forward
-
-Forward socket connections from the local socket to the remote socket.
-```
- Sockets are specified by one of:
- tcp:<port>
- localabstract:<unix domain socket name>
- localreserved:<unix domain socket name>
- localfilesystem:<unix domain socket name>
- dev:<character device name>
- jdwp:<process pid> (remote only)
-
- Args:
- local: The host socket.
- remote: The device socket.
- allow_rebind: A boolean indicating whether adb may rebind a local socket;
- otherwise, the default, an exception is raised if the local socket is
- already being forwarded.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-```
-
-
-### AdbWrapper.ForwardRemove
-
-Remove a forward socket connection.
-```
- Args:
- local: The host socket.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-```
-
-
-### AdbWrapper.ForwardList
-
-List all currently forwarded socket connections.
-```
- Args:
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
- Returns:
- The output of adb forward --list as a string.
-```
-
-
-### AdbWrapper.JDWP
-
-List of PIDs of processes hosting a JDWP transport.
-```
- Args:
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-
- Returns:
- A list of PIDs as strings.
-```
-
-
-### AdbWrapper.Install
-
-Install an apk on the device.
-```
- Args:
- apk_path: Host path to the APK file.
- forward_lock: (optional) If set forward-locks the app.
- allow_downgrade: (optional) If set, allows for downgrades.
- reinstall: (optional) If set reinstalls the app, keeping its data.
- sd_card: (optional) If set installs on the SD card.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-```
-
-
-### AdbWrapper.InstallMultiple
-
-Install an apk with splits on the device.
-```
- Args:
- apk_paths: Host path to the APK file.
- forward_lock: (optional) If set forward-locks the app.
- reinstall: (optional) If set reinstalls the app, keeping its data.
- sd_card: (optional) If set installs on the SD card.
- allow_downgrade: (optional) Allow versionCode downgrade.
- partial: (optional) Package ID if apk_paths doesn't include all .apks.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-```
-
-
-### AdbWrapper.Uninstall
-
-Remove the app |package| from the device.
-```
- Args:
- package: The package to uninstall.
- keep_data: (optional) If set keep the data and cache directories.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-```
-
-
-### AdbWrapper.Backup
-
-Write an archive of the device's data to |path|.
-```
- Args:
- path: Local path to store the backup file.
- packages: List of to packages to be backed up.
- apk: (optional) If set include the .apk files in the archive.
- shared: (optional) If set buckup the device's SD card.
- nosystem: (optional) If set exclude system applications.
- include_all: (optional) If set back up all installed applications and
- |packages| is optional.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-```
-
-
-### AdbWrapper.Restore
-
-Restore device contents from the backup archive.
-```
- Args:
- path: Host path to the backup archive.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-```
-
-
-### AdbWrapper.WaitForDevice
-
-Block until the device is online.
-```
- Args:
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-```
-
-
-### AdbWrapper.GetState
-
-Get device state.
-```
- Args:
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-
- Returns:
- One of 'offline', 'bootloader', or 'device'.
-```
-
-
-### AdbWrapper.GetDevPath
-
-Gets the device path.
-```
- Args:
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-
- Returns:
- The device path (e.g. usb:3-4)
-```
-
-
-### AdbWrapper.Remount
-
-Remounts the /system partition on the device read-write.
-### AdbWrapper.Reboot
-
-Reboots the device.
-```
- Args:
- to_bootloader: (optional) If set reboots to the bootloader.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-```
-
-
-### AdbWrapper.Root
-
-Restarts the adbd daemon with root permissions, if possible.
-```
- Args:
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-```
-
-
-### AdbWrapper.Emu
-
-Runs an emulator console command.
-```
- See http://developer.android.com/tools/devices/emulator.html#console
-
- Args:
- cmd: The command to run on the emulator console.
- timeout: (optional) Timeout per try in seconds.
- retries: (optional) Number of retries to attempt.
-
- Returns:
- The output of the emulator console command.
-```
-
-
-### AdbWrapper.DisableVerity
-
-Disable Marshmallow's Verity security feature
-### AdbWrapper.EnableVerity
-
-Enable Marshmallow's Verity security feature
-### AdbWrapper.\_\_init\_\_
-
-Initializes the AdbWrapper.
-```
- Args:
- device_serial: The device serial number as a string.
-```
-
-
-### AdbWrapper.\_\_eq\_\_
-
-Consider instances equal if they refer to the same device.
-```
- Args:
- other: The instance to compare equality with.
-
- Returns:
- True if the instances are considered equal, false otherwise.
-```
-
-
-### AdbWrapper.\_\_str\_\_
-
-The string representation of an instance.
-```
- Returns:
- The device serial number as a string.
-```
-
-
-### AdbWrapper.\_\_repr\_\_
-
-### VerifyLocalFileExists
-
-Verifies a local file exists.
-```
- Args:
- path: Path to the local file.
-
- Raises:
- IOError: If the file doesn't exist.
-```
-
-
diff --git a/third_party/catapult/devil/docs/device_blacklist.md b/third_party/catapult/devil/docs/device_blacklist.md
deleted file mode 100644
index c6eed5146d..0000000000
--- a/third_party/catapult/devil/docs/device_blacklist.md
+++ /dev/null
@@ -1,59 +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.
--->
-
-# Devil: Device Blacklist
-
-## What is it?
-
-The device blacklist is a per-run list of devices detected to be in a known bad
-state along with the reason they are suspected of being in a bad state (offline,
-not responding, etc). It is stored as a json file. This gets reset every run
-during the device recovery step (currently part of `bb_device_status_check`).
-
-## Bots
-
-On bots, this is normally found at `//out/bad_devices.json`. If you are having
-problems with blacklisted devices locally even though a device is in a good
-state, you can safely delete this file.
-
-# Tools for interacting with device black list.
-
-You can interact with the device blacklist via [devil.android.device\_blacklist](https://cs.chromium.org/chromium/src/third_party/catapult/devil/devil/android/device_blacklist.py).
-This allows for any interaction you would need with a device blacklist:
-
- - Reading
- - Writing
- - Extending
- - Resetting
-
-An example usecase of this is:
-```python
-from devil.android import device_blacklist
-
-blacklist = device_blacklist.Blacklist(blacklist_path)
-blacklisted_devices = blacklist.Read()
-for device in blacklisted_devices:
- print 'Device %s is blacklisted' % device
-blacklist.Reset()
-new_blacklist = {'device_id1': {'timestamp': ts, 'reason': reason}}
-blacklist.Write(new_blacklist)
-blacklist.Extend([device_2, device_3], reason='Reason for blacklisting')
-```
-
-
-## Where it is used.
-
-The blacklist file path is passed directly to the following scripts in chromium:
-
- - [test\_runner.py](https://cs.chromium.org/chromium/src/build/android/test_runner.py)
- - [provision\_devices.py](https://cs.chromium.org/chromium/src/build/android/provision_devices.py)
- - [bb\_device\_status\_check.py](https://cs.chromium.org/chromium/src/build/android/buildbot/bb_device_status_check.py)
-
-The blacklist is also used in the following scripts:
-
- - [device\_status.py](https://cs.chromium.org/chromium/src/third_party/catapult/devil/devil/android/tools/device_status.py)
- - [device\_recovery.py](https://cs.chromium.org/chromium/src/third_party/catapult/devil/devil/android/tools/device_recovery.py)
-
-
diff --git a/third_party/catapult/devil/docs/device_utils.md b/third_party/catapult/devil/docs/device_utils.md
deleted file mode 100644
index b281b26680..0000000000
--- a/third_party/catapult/devil/docs/device_utils.md
+++ /dev/null
@@ -1,1041 +0,0 @@
-# [devil.android.device_utils](https://github.com/catapult-project/catapult/blob/master/devil/devil/android/device_utils.py)
-
-*This page was autogenerated by `devil/utils/markdown.py --module-link https://github.com/catapult-project/catapult/blob/master/devil/devil/android/device_utils.py`*
-
-## DeviceUtils
-
-### DeviceUtils.\_\_init\_\_
-
-DeviceUtils constructor.
-```
- Args:
- device: Either a device serial, an existing AdbWrapper instance, or an
- an existing AndroidCommands instance.
- enable_device_files_cache: For PushChangedFiles(), cache checksums of
- pushed files rather than recomputing them on a subsequent call.
- default_timeout: An integer containing the default number of seconds to
- wait for an operation to complete if no explicit value is provided.
- default_retries: An integer containing the default number or times an
- operation should be retried on failure if no explicit value is provided.
-```
-
-
-### DeviceUtils.\_\_eq\_\_
-
-Checks whether |other| refers to the same device as |self|.
-```
- Args:
- other: The object to compare to. This can be a basestring, an instance
- of adb_wrapper.AdbWrapper, or an instance of DeviceUtils.
- Returns:
- Whether |other| refers to the same device as |self|.
-```
-
-
-### DeviceUtils.\_\_lt\_\_
-
-Compares two instances of DeviceUtils.
-```
- This merely compares their serial numbers.
-
- Args:
- other: The instance of DeviceUtils to compare to.
- Returns:
- Whether |self| is less than |other|.
-```
-
-
-### DeviceUtils.\_\_str\_\_
-
-Returns the device serial.
-### DeviceUtils.NeedsSU
-
-Checks whether 'su' is needed to access protected resources.
-```
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- True if 'su' is available on the device and is needed to to access
- protected resources; False otherwise if either 'su' is not available
- (e.g. because the device has a user build), or not needed (because adbd
- already has root privileges).
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.IsOnline
-
-Checks whether the device is online.
-```
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- True if the device is online, False otherwise.
-
- Raises:
- CommandTimeoutError on timeout.
-```
-
-
-### DeviceUtils.HasRoot
-
-Checks whether or not adbd has root privileges.
-```
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- True if adbd has root privileges, False otherwise.
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.EnableRoot
-
-Restarts adbd with root privileges.
-```
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandFailedError if root could not be enabled.
- CommandTimeoutError on timeout.
-```
-
-
-### DeviceUtils.IsUserBuild
-
-Checks whether or not the device is running a user build.
-```
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- True if the device is running a user build, False otherwise (i.e. if
- it's running a userdebug build).
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.GetExternalStoragePath
-
-Get the device's path to its SD card.
-```
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- The device's path to its SD card.
-
- Raises:
- CommandFailedError if the external storage path could not be determined.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.GetApplicationPaths
-
-Get the paths of the installed apks on the device for the given package.
-```
- Args:
- package: Name of the package.
-
- Returns:
- List of paths to the apks on the device for the given package.
-```
-
-
-### DeviceUtils.GetApplicationVersion
-
-Get the version name of a package installed on the device.
-```
- Args:
- package: Name of the package.
-
- Returns:
- A string with the version name or None if the package is not found
- on the device.
-```
-
-
-### DeviceUtils.GetApplicationDataDirectory
-
-Get the data directory on the device for the given package.
-```
- Args:
- package: Name of the package.
-
- Returns:
- The package's data directory.
- Raises:
- CommandFailedError if the package's data directory can't be found,
- whether because it's not installed or otherwise.
-```
-
-
-### DeviceUtils.WaitUntilFullyBooted
-
-Wait for the device to fully boot.
-```
- This means waiting for the device to boot, the package manager to be
- available, and the SD card to be ready. It can optionally mean waiting
- for wifi to come up, too.
-
- Args:
- wifi: A boolean indicating if we should wait for wifi to come up or not.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandFailedError on failure.
- CommandTimeoutError if one of the component waits times out.
- DeviceUnreachableError if the device becomes unresponsive.
-```
-
-
-### DeviceUtils.Reboot
-
-Reboot the device.
-```
- Args:
- block: A boolean indicating if we should wait for the reboot to complete.
- wifi: A boolean indicating if we should wait for wifi to be enabled after
- the reboot. The option has no effect unless |block| is also True.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.Install
-
-Install an APK.
-```
- Noop if an identical APK is already installed.
-
- Args:
- apk: An ApkHelper instance or string containing the path to the APK.
- allow_downgrade: A boolean indicating if we should allow downgrades.
- reinstall: A boolean indicating if we should keep any existing app data.
- permissions: Set of permissions to set. If not set, finds permissions with
- apk helper. To set no permissions, pass [].
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandFailedError if the installation fails.
- CommandTimeoutError if the installation times out.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.InstallSplitApk
-
-Install a split APK.
-```
- Noop if all of the APK splits are already installed.
-
- Args:
- base_apk: An ApkHelper instance or string containing the path to the base
- APK.
- split_apks: A list of strings of paths of all of the APK splits.
- allow_downgrade: A boolean indicating if we should allow downgrades.
- reinstall: A boolean indicating if we should keep any existing app data.
- allow_cached_props: Whether to use cached values for device properties.
- permissions: Set of permissions to set. If not set, finds permissions with
- apk helper. To set no permissions, pass [].
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandFailedError if the installation fails.
- CommandTimeoutError if the installation times out.
- DeviceUnreachableError on missing device.
- DeviceVersionError if device SDK is less than Android L.
-```
-
-
-### DeviceUtils.Uninstall
-
-Remove the app |package\_name| from the device.
-```
- This is a no-op if the app is not already installed.
-
- Args:
- package_name: The package to uninstall.
- keep_data: (optional) Whether to keep the data and cache directories.
- timeout: Timeout in seconds.
- retries: Number of retries.
-
- Raises:
- CommandFailedError if the uninstallation fails.
- CommandTimeoutError if the uninstallation times out.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.RunShellCommand
-
-Run an ADB shell command.
-```
- The command to run |cmd| should be a sequence of program arguments or else
- a single string.
-
- When |cmd| is a sequence, it is assumed to contain the name of the command
- to run followed by its arguments. In this case, arguments are passed to the
- command exactly as given, without any further processing by the shell. This
- allows to easily pass arguments containing spaces or special characters
- without having to worry about getting quoting right. Whenever possible, it
- is recomended to pass |cmd| as a sequence.
-
- When |cmd| is given as a string, it will be interpreted and run by the
- shell on the device.
-
- This behaviour is consistent with that of command runners in cmd_helper as
- well as Python's own subprocess.Popen.
-
- TODO(perezju) Change the default of |check_return| to True when callers
- have switched to the new behaviour.
-
- Args:
- cmd: A string with the full command to run on the device, or a sequence
- containing the command and its arguments.
- check_return: A boolean indicating whether or not the return code should
- be checked.
- cwd: The device directory in which the command should be run.
- env: The environment variables with which the command should be run.
- run_as: A string containing the package as which the command should be
- run.
- as_root: A boolean indicating whether the shell command should be run
- with root privileges.
- single_line: A boolean indicating if only a single line of output is
- expected.
- large_output: Uses a work-around for large shell command output. Without
- this large output will be truncated.
- raw_output: Whether to only return the raw output
- (no splitting into lines).
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- If single_line is False, the output of the command as a list of lines,
- otherwise, a string with the unique line of output emmited by the command
- (with the optional newline at the end stripped).
-
- Raises:
- AdbCommandFailedError if check_return is True and the exit code of
- the command run on the device is non-zero.
- CommandFailedError if single_line is True but the output contains two or
- more lines.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.KillAll
-
-Kill all processes with the given name on the device.
-```
- Args:
- process_name: A string containing the name of the process to kill.
- exact: A boolean indicating whether to kill all processes matching
- the string |process_name| exactly, or all of those which contain
- |process_name| as a substring. Defaults to False.
- signum: An integer containing the signal number to send to kill. Defaults
- to SIGKILL (9).
- as_root: A boolean indicating whether the kill should be executed with
- root privileges.
- blocking: A boolean indicating whether we should wait until all processes
- with the given |process_name| are dead.
- quiet: A boolean indicating whether to ignore the fact that no processes
- to kill were found.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- The number of processes attempted to kill.
-
- Raises:
- CommandFailedError if no process was killed and |quiet| is False.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.StartActivity
-
-Start package's activity on the device.
-```
- Args:
- intent_obj: An Intent object to send.
- blocking: A boolean indicating whether we should wait for the activity to
- finish launching.
- trace_file_name: If present, a string that both indicates that we want to
- profile the activity and contains the path to which the
- trace should be saved.
- force_stop: A boolean indicating whether we should stop the activity
- before starting it.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandFailedError if the activity could not be started.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.StartInstrumentation
-
-### DeviceUtils.BroadcastIntent
-
-Send a broadcast intent.
-```
- Args:
- intent: An Intent to broadcast.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.GoHome
-
-Return to the home screen and obtain launcher focus.
-```
- This command launches the home screen and attempts to obtain
- launcher focus until the timeout is reached.
-
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.ForceStop
-
-Close the application.
-```
- Args:
- package: A string containing the name of the package to stop.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.ClearApplicationState
-
-Clear all state for the given package.
-```
- Args:
- package: A string containing the name of the package to stop.
- permissions: List of permissions to set after clearing data.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.SendKeyEvent
-
-Sends a keycode to the device.
-```
- See the devil.android.sdk.keyevent module for suitable keycode values.
-
- Args:
- keycode: A integer keycode to send to the device.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.PushChangedFiles
-
-Push files to the device, skipping files that don't need updating.
-```
- When a directory is pushed, it is traversed recursively on the host and
- all files in it are pushed to the device as needed.
- Additionally, if delete_device_stale option is True,
- files that exist on the device but don't exist on the host are deleted.
-
- Args:
- host_device_tuples: A list of (host_path, device_path) tuples, where
- |host_path| is an absolute path of a file or directory on the host
- that should be minimially pushed to the device, and |device_path| is
- an absolute path of the destination on the device.
- timeout: timeout in seconds
- retries: number of retries
- delete_device_stale: option to delete stale files on device
-
- Raises:
- CommandFailedError on failure.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.FileExists
-
-Checks whether the given file exists on the device.
-```
- Arguments are the same as PathExists.
-```
-
-
-### DeviceUtils.PathExists
-
-Checks whether the given path(s) exists on the device.
-```
- Args:
- device_path: A string containing the absolute path to the file on the
- device, or an iterable of paths to check.
- as_root: Whether root permissions should be use to check for the existence
- of the given path(s).
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- True if the all given paths exist on the device, False otherwise.
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.RemovePath
-
-Removes the given path(s) from the device.
-```
- Args:
- device_path: A string containing the absolute path to the file on the
- device, or an iterable of paths to check.
- force: Whether to remove the path(s) with force (-f).
- recursive: Whether to remove any directories in the path(s) recursively.
- as_root: Whether root permissions should be use to remove the given
- path(s).
- timeout: timeout in seconds
- retries: number of retries
-```
-
-
-### DeviceUtils.PullFile
-
-Pull a file from the device.
-```
- Args:
- device_path: A string containing the absolute path of the file to pull
- from the device.
- host_path: A string containing the absolute path of the destination on
- the host.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandFailedError on failure.
- CommandTimeoutError on timeout.
-```
-
-
-### DeviceUtils.ReadFile
-
-Reads the contents of a file from the device.
-```
- Args:
- device_path: A string containing the absolute path of the file to read
- from the device.
- as_root: A boolean indicating whether the read should be executed with
- root privileges.
- force_pull: A boolean indicating whether to force the operation to be
- performed by pulling a file from the device. The default is, when the
- contents are short, to retrieve the contents using cat instead.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- The contents of |device_path| as a string. Contents are intepreted using
- universal newlines, so the caller will see them encoded as '
-'. Also,
- all lines will be terminated.
-
- Raises:
- AdbCommandFailedError if the file can't be read.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.WriteFile
-
-Writes |contents| to a file on the device.
-```
- Args:
- device_path: A string containing the absolute path to the file to write
- on the device.
- contents: A string containing the data to write to the device.
- as_root: A boolean indicating whether the write should be executed with
- root privileges (if available).
- force_push: A boolean indicating whether to force the operation to be
- performed by pushing a file to the device. The default is, when the
- contents are short, to pass the contents using a shell script instead.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandFailedError if the file could not be written on the device.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.ListDirectory
-
-List all files on a device directory.
-```
- Mirroring os.listdir (and most client expectations) the resulting list
- does not include the special entries '.' and '..' even if they are present
- in the directory.
-
- Args:
- device_path: A string containing the path of the directory on the device
- to list.
- as_root: A boolean indicating whether the to use root privileges to list
- the directory contents.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- A list of filenames for all entries contained in the directory.
-
- Raises:
- AdbCommandFailedError if |device_path| does not specify a valid and
- accessible directory in the device.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.StatDirectory
-
-List file and stat info for all entries on a device directory.
-```
- Implementation notes: this is currently implemented by parsing the output
- of 'ls -a -l' on the device. Whether possible and convenient, we attempt to
- make parsing strict and return values mirroring those of the standard |os|
- and |stat| Python modules.
-
- Mirroring os.listdir (and most client expectations) the resulting list
- does not include the special entries '.' and '..' even if they are present
- in the directory.
-
- Args:
- device_path: A string containing the path of the directory on the device
- to list.
- as_root: A boolean indicating whether the to use root privileges to list
- the directory contents.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- A list of dictionaries, each containing the following keys:
- filename: A string with the file name.
- st_mode: File permissions, use the stat module to interpret these.
- st_nlink: Number of hard links (may be missing).
- st_owner: A string with the user name of the owner.
- st_group: A string with the group name of the owner.
- st_rdev_pair: Device type as (major, minior) (only if inode device).
- st_size: Size of file, in bytes (may be missing for non-regular files).
- st_mtime: Time of most recent modification, in seconds since epoch
- (although resolution is in minutes).
- symbolic_link_to: If entry is a symbolic link, path where it points to;
- missing otherwise.
-
- Raises:
- AdbCommandFailedError if |device_path| does not specify a valid and
- accessible directory in the device.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.StatPath
-
-Get the stat attributes of a file or directory on the device.
-```
- Args:
- device_path: A string containing the path of a file or directory from
- which to get attributes.
- as_root: A boolean indicating whether the to use root privileges to
- access the file information.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- A dictionary with the stat info collected; see StatDirectory for details.
-
- Raises:
- CommandFailedError if device_path cannot be found on the device.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.FileSize
-
-Get the size of a file on the device.
-```
- Note: This is implemented by parsing the output of the 'ls' command on
- the device. On some Android versions, when passing a directory or special
- file, the size is *not* reported and this function will throw an exception.
-
- Args:
- device_path: A string containing the path of a file on the device.
- as_root: A boolean indicating whether the to use root privileges to
- access the file information.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- The size of the file in bytes.
-
- Raises:
- CommandFailedError if device_path cannot be found on the device, or
- its size cannot be determited for some reason.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.GetLanguage
-
-Returns the language setting on the device.
-```
- Args:
- cache: Whether to use cached properties when available.
-```
-
-
-### DeviceUtils.SetJavaAsserts
-
-Enables or disables Java asserts.
-```
- Args:
- enabled: A boolean indicating whether Java asserts should be enabled
- or disabled.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- True if the device-side property changed and a restart is required as a
- result, False otherwise.
-
- Raises:
- CommandTimeoutError on timeout.
-```
-
-
-### DeviceUtils.GetCountry
-
-Returns the country setting on the device.
-```
- Args:
- cache: Whether to use cached properties when available.
-```
-
-
-### DeviceUtils.GetProp
-
-Gets a property from the device.
-```
- Args:
- property_name: A string containing the name of the property to get from
- the device.
- cache: Whether to use cached properties when available.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- The value of the device's |property_name| property.
-
- Raises:
- CommandTimeoutError on timeout.
-```
-
-
-### DeviceUtils.SetProp
-
-Sets a property on the device.
-```
- Args:
- property_name: A string containing the name of the property to set on
- the device.
- value: A string containing the value to set to the property on the
- device.
- check: A boolean indicating whether to check that the property was
- successfully set on the device.
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandFailedError if check is true and the property was not correctly
- set on the device (e.g. because it is not rooted).
- CommandTimeoutError on timeout.
-```
-
-
-### DeviceUtils.GetABI
-
-Gets the device main ABI.
-```
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- The device's main ABI name.
-
- Raises:
- CommandTimeoutError on timeout.
-```
-
-
-### DeviceUtils.GetPids
-
-Returns the PIDs of processes with the given name.
-```
- Note that the |process_name| is often the package name.
-
- Args:
- process_name: A string containing the process name to get the PIDs for.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- A dict mapping process name to a list of PIDs for each process that
- contained the provided |process_name|.
-
- Raises:
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.GetEnforce
-
-Get the current mode of SELinux.
-```
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- True (enforcing), False (permissive), or None (disabled).
-
- Raises:
- CommandFailedError on failure.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.SetEnforce
-
-Modify the mode SELinux is running in.
-```
- Args:
- enabled: a boolean indicating whether to put SELinux in encorcing mode
- (if True), or permissive mode (otherwise).
- timeout: timeout in seconds
- retries: number of retries
-
- Raises:
- CommandFailedError on failure.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.TakeScreenshot
-
-Takes a screenshot of the device.
-```
- Args:
- host_path: A string containing the path on the host to save the
- screenshot to. If None, a file name in the current
- directory will be generated.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- The name of the file on the host to which the screenshot was saved.
-
- Raises:
- CommandFailedError on failure.
- CommandTimeoutError on timeout.
- DeviceUnreachableError on missing device.
-```
-
-
-### DeviceUtils.GetMemoryUsageForPid
-
-Gets the memory usage for the given PID.
-```
- Args:
- pid: PID of the process.
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- A dict containing memory usage statistics for the PID. May include:
- Size, Rss, Pss, Shared_Clean, Shared_Dirty, Private_Clean,
- Private_Dirty, VmHWM
-
- Raises:
- CommandTimeoutError on timeout.
-```
-
-
-### DeviceUtils.DismissCrashDialogIfNeeded
-
-Dismiss the error/ANR dialog if present.
-```
- Returns: Name of the crashed package if a dialog is focused,
- None otherwise.
-```
-
-
-### DeviceUtils.GetLogcatMonitor
-
-Returns a new LogcatMonitor associated with this device.
-```
- Parameters passed to this function are passed directly to
- |logcat_monitor.LogcatMonitor| and are documented there.
-```
-
-
-### DeviceUtils.GetClientCache
-
-Returns client cache.
-### DeviceUtils.LoadCacheData
-
-Initializes the cache from data created using DumpCacheData.
-```
- The cache is used only if its token matches the one found on the device.
- This prevents a stale cache from being used (which can happen when sharing
- devices).
-
- Args:
- data: A previously serialized cache (string).
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- Whether the cache was loaded.
-```
-
-
-### DeviceUtils.DumpCacheData
-
-Dumps the current cache state to a string.
-```
- Args:
- timeout: timeout in seconds
- retries: number of retries
-
- Returns:
- A serialized cache as a string.
-```
-
-
-### DeviceUtils.RestartAdbd
-
-### DeviceUtils.GrantPermissions
-
-### DeviceUtils.IsScreenOn
-
-Determines if screen is on.
-```
- Dumpsys input_method exposes screen on/off state. Below is an explination of
- the states.
-
- Pre-L:
- On: mScreenOn=true
- Off: mScreenOn=false
- L+:
- On: mInteractive=true
- Off: mInteractive=false
-
- Returns:
- True if screen is on, false if it is off.
-
- Raises:
- device_errors.CommandFailedError: If screen state cannot be found.
-```
-
-
-### DeviceUtils.SetScreen
-
-Turns screen on and off.
-```
- Args:
- on: bool to decide state to switch to. True = on False = off.
-```
-
-
-### GetAVDs
-
-Returns a list of Android Virtual Devices.
-```
- Returns:
- A list containing the configured AVDs.
-```
-
-
-### RestartServer
-
-Restarts the adb server.
-```
- Raises:
- CommandFailedError if we fail to kill or restart the server.
-```
-
-
diff --git a/third_party/catapult/devil/docs/markdown.md b/third_party/catapult/devil/docs/markdown.md
deleted file mode 100644
index 957dba7d98..0000000000
--- a/third_party/catapult/devil/docs/markdown.md
+++ /dev/null
@@ -1,139 +0,0 @@
-# [devil.utils.markdown](https://github.com/catapult-project/catapult/blob/master/devil/devil/utils/markdown.py)
-
-*This page was autogenerated by `devil/utils/markdown.py --module-link https://github.com/catapult-project/catapult/blob/master/devil/devil/utils/markdown.py`*
-
-## MarkdownHelpAction
-
-### MarkdownHelpAction.\_\_init\_\_
-
-### MarkdownHelpAction.\_\_call\_\_
-
-## MarkdownHelpFormatter
-
-A really bare-bones argparse help formatter that generates valid markdown.
-```
- This will generate something like:
-
- usage
-
- # **section heading**:
-
- ## **--argument-one**
-
- \`\`\`
- argument-one help text
- \`\`\`
-
-```
-
-
-### MarkdownHelpFormatter.format\_help
-
-### MarkdownHelpFormatter.start\_section
-
-### md\_bold
-
-Returns markdown-formatted bold text.
-### md\_code
-
-Returns a markdown-formatted code block in the given language.
-### md\_escape
-
-Escapes \* and \_.
-### md\_heading
-
-Returns markdown-formatted heading.
-### md\_inline\_code
-
-Returns markdown-formatted inline code.
-### md\_italic
-
-Returns markdown-formatted italic text.
-### md\_link
-
-returns a markdown-formatted link.
-### add\_md\_help\_argument
-
-Adds --md-help to the given argparse.ArgumentParser.
-```
- Running a script with --md-help will print the help text for that script
- as valid markdown.
-
- Args:
- parser: The ArgumentParser to which --md-help should be added.
-```
-
-
-### load\_module\_from\_path
-
-Load a module given only the path name.
-```
- Also loads package modules as necessary.
-
- Args:
- module_path: An absolute path to a python module.
- Returns:
- The module object for the given path.
-```
-
-
-### md\_module
-
-Write markdown documentation for a class.
-```
- Documents public classes and functions.
-
- Args:
- class_obj: a types.TypeType object for the class that should be
- documented.
- Returns:
- A list of markdown-formatted lines.
-```
-
-
-### md\_class
-
-Write markdown documentation for a class.
-```
- Documents public methods. Does not currently document subclasses.
-
- Args:
- class_obj: a types.TypeType object for the class that should be
- documented.
- Returns:
- A list of markdown-formatted lines.
-```
-
-
-### md\_docstring
-
-Write a markdown-formatted docstring.
-```
- Returns:
- A list of markdown-formatted lines.
-```
-
-
-### md\_function
-
-Write markdown documentation for a function.
-```
- Args:
- func_obj: a types.FunctionType object for the function that should be
- documented.
- Returns:
- A list of markdown-formatted lines.
-```
-
-
-### main
-
-Write markdown documentation for the module at the provided path.
-```
- Args:
- raw_args: the raw command-line args. Usually sys.argv[1:].
- Returns:
- An integer exit code. 0 for success, non-zero for failure.
-```
-
-
diff --git a/third_party/catapult/devil/docs/persistent_device_list.md b/third_party/catapult/devil/docs/persistent_device_list.md
deleted file mode 100644
index d08d9a9e89..0000000000
--- a/third_party/catapult/devil/docs/persistent_device_list.md
+++ /dev/null
@@ -1,41 +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.
--->
-
-# Devil: Persistent Device List
-
-## What is it?
-
-A persistent device list that stores all expected devices between builds. It
-is used by the perf test runner in order to properly shard tests by device
-affinity. This is important because the same performance test can yield
-meaningfully different results when run on different devices.
-
-## Bots
-
-The list is usually located at one of these locations:
-
- - `/b/build/site_config/.known_devices`.
- - `~/.android`.
-
-Look at recipes listed below in order to find more up to date location.
-
-## Local Runs
-
-The persistent device list is unnecessary for local runs. It is only used on the
-bots that upload data to the perf dashboard.
-
-## Where it is used
-
-The persistent device list is used in performance test recipes via
-[api.chromium\_tests.steps.DynamicPerfTests](https://cs.chromium.org/chromium/build/scripts/slave/recipe_modules/chromium_tests/steps.py?q=DynamicPerfTests).
-For example, the [android/perf](https://cs.chromium.org/chromium/build/scripts/slave/recipes/android/perf.py) recipe uses it like this:
-
-```python
-dynamic_perf_tests = api.chromium_tests.steps.DynamicPerfTests(
- builder['perf_id'], 'android', None,
- known_devices_file=builder.get('known_devices_file', None))
-dynamic_perf_tests.run(api, None)
-```
-
diff --git a/third_party/catapult/devil/pylintrc b/third_party/catapult/devil/pylintrc
deleted file mode 100644
index 7e024a25e7..0000000000
--- a/third_party/catapult/devil/pylintrc
+++ /dev/null
@@ -1,68 +0,0 @@
-[MESSAGES CONTROL]
-
-# Disable the message, report, category or checker with the given id(s).
-# TODO: Shrink this list to as small as possible.
-disable=
- design,
- similarities,
-
- bad-continuation,
- fixme,
- import-error,
- invalid-name,
- locally-disabled,
- locally-enabled,
- missing-docstring,
- star-args,
-
-
-[REPORTS]
-
-# Don't write out full reports, just messages.
-reports=no
-
-
-[BASIC]
-
-# Regular expression which should only match correct function names.
-function-rgx=^(?:(?P<exempt>setUp|tearDown|setUpModule|tearDownModule)|(?P<camel_case>_?[A-Z][a-zA-Z0-9]*))$
-
-# Regular expression which should only match correct method names.
-method-rgx=^(?:(?P<exempt>_[a-z0-9_]+__|get|post|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass)|(?P<camel_case>(_{0,2}|test|assert)[A-Z][a-zA-Z0-9_]*))$
-
-# Regular expression which should only match correct argument names.
-argument-rgx=^[a-z][a-z0-9_]*$
-
-# Regular expression which should only match correct variable names.
-variable-rgx=^[a-z][a-z0-9_]*$
-
-# Good variable names which should always be accepted, separated by a comma.
-good-names=main,_
-
-# List of builtins function names that should not be used, separated by a comma.
-bad-functions=apply,input,reduce
-
-
-[VARIABLES]
-
-# Tells wether we should check for unused import in __init__ files.
-init-import=no
-
-# A regular expression matching names used for dummy variables (i.e. not used).
-dummy-variables-rgx=^_.*$|dummy
-
-
-[TYPECHECK]
-
-# Tells wether missing members accessed in mixin class should be ignored. A
-# mixin class is detected if its name ends with "mixin" (case insensitive).
-ignore-mixin-members=yes
-
-
-[FORMAT]
-
-# Maximum number of lines in a module.
-max-module-lines=10000
-
-# We use two spaces for indents, instead of the usual four spaces or tab.
-indent-string=' '
diff --git a/third_party/jinja2/AUTHORS b/third_party/jinja2/AUTHORS
index 943f625f87..fd6dbfcbfd 100644
--- a/third_party/jinja2/AUTHORS
+++ b/third_party/jinja2/AUTHORS
@@ -17,6 +17,7 @@ Contributors:
- Cameron Knight
- Lawrence Journal-World.
- David Cramer
+- Adrian Mönnich (ThiefMaster)
Patches and suggestions:
diff --git a/third_party/jinja2/Jinja2-2.10.tar.gz.md5 b/third_party/jinja2/Jinja2-2.10.tar.gz.md5
new file mode 100644
index 0000000000..9137ee129a
--- /dev/null
+++ b/third_party/jinja2/Jinja2-2.10.tar.gz.md5
@@ -0,0 +1 @@
+61ef1117f945486472850819b8d1eb3d Jinja2-2.10.tar.gz
diff --git a/third_party/jinja2/Jinja2-2.10.tar.gz.sha512 b/third_party/jinja2/Jinja2-2.10.tar.gz.sha512
new file mode 100644
index 0000000000..087d24c18e
--- /dev/null
+++ b/third_party/jinja2/Jinja2-2.10.tar.gz.sha512
@@ -0,0 +1 @@
+0ea7371be67ffcf19e46dfd06523a45a0806e678a407d54f5f2f3e573982f0959cf82ec5d07b203670309928a62ef71109701ab16547a9bba2ebcdc178cb67f2 Jinja2-2.10.tar.gz
diff --git a/third_party/jinja2/Jinja2-2.8.tar.gz.md5 b/third_party/jinja2/Jinja2-2.8.tar.gz.md5
deleted file mode 100644
index a0eb1b2464..0000000000
--- a/third_party/jinja2/Jinja2-2.8.tar.gz.md5
+++ /dev/null
@@ -1 +0,0 @@
-edb51693fe22c53cee5403775c71a99e Jinja2-2.8.tar.gz
diff --git a/third_party/jinja2/Jinja2-2.8.tar.gz.sha512 b/third_party/jinja2/Jinja2-2.8.tar.gz.sha512
deleted file mode 100644
index 88e4ea6154..0000000000
--- a/third_party/jinja2/Jinja2-2.8.tar.gz.sha512
+++ /dev/null
@@ -1 +0,0 @@
-2e80d6d9ad10dafcce1e6dd24493f5dffc43a17f71a30a650415638e12d3a3891738ebacc569701129214026d062d91a2b10e4f7a2c7b85d801dde26ded1bebb Jinja2-2.8.tar.gz
diff --git a/third_party/jinja2/README.chromium b/third_party/jinja2/README.chromium
index 684ff8ec0a..5246c2f84b 100644
--- a/third_party/jinja2/README.chromium
+++ b/third_party/jinja2/README.chromium
@@ -1,17 +1,17 @@
Name: Jinja2 Python Template Engine
Short Name: jinja2
URL: http://jinja.pocoo.org/
-Version: 2.8
-License: BSD 3-clause License
-License File: NOT_SHIPPED
+Version: 2.10
+License: BSD 3-Clause
+License File: LICENSE
Security Critical: no
Description:
Template engine for code generation in Blink.
-Source: https://pypi.python.org/packages/f2/2f/0b98b06a345a761bec91a079ccae392d282690c2d8272e708f4d10829e22/Jinja2-2.8.tar.gz
-MD5: edb51693fe22c53cee5403775c71a99e
-SHA-1: 4a33c1a0fd585eba2507e8c274a9cd113b1d13ab
+Source: https://pypi.python.org/packages/56/e6/332789f295cf22308386cf5bbd1f4e00ed11484299c5d7383378cf48ba47/Jinja2-2.10.tar.gz
+MD5: 61ef1117f945486472850819b8d1eb3d
+SHA-1: 34b69e5caab12ee37b9df69df9018776c008b7b8
Local Modifications:
This only includes the jinja2 directory from the tarball and the LICENSE and
@@ -19,7 +19,8 @@ AUTHORS files. Unit tests (testsuite directory) have been removed.
Additional chromium-specific files are:
* README.chromium (this file)
* OWNERS
-* install script (get_jinja2.sh)
+* get_jinja2.sh (install script)
+* jinja2.gni (generated by get_jinja2.sh)
* files of hashes (MD5 is also posted on website, SHA-512 computed locally).
Script checks hash then unpacks archive and installs desired files.
Retrieve or update by executing jinja2/get_jinja2.sh from third_party.
diff --git a/third_party/jinja2/__init__.py b/third_party/jinja2/__init__.py
index 029fb2e6e8..42aa763d57 100644
--- a/third_party/jinja2/__init__.py
+++ b/third_party/jinja2/__init__.py
@@ -23,11 +23,11 @@
{% endblock %}
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
__docformat__ = 'restructuredtext en'
-__version__ = '2.8'
+__version__ = '2.10'
# high level interface
from jinja2.environment import Environment, Template
@@ -48,14 +48,14 @@ from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined, \
# exceptions
from jinja2.exceptions import TemplateError, UndefinedError, \
TemplateNotFound, TemplatesNotFound, TemplateSyntaxError, \
- TemplateAssertionError
+ TemplateAssertionError, TemplateRuntimeError
# decorators and public utilities
from jinja2.filters import environmentfilter, contextfilter, \
evalcontextfilter
from jinja2.utils import Markup, escape, clear_caches, \
environmentfunction, evalcontextfunction, contextfunction, \
- is_undefined
+ is_undefined, select_autoescape
__all__ = [
'Environment', 'Template', 'BaseLoader', 'FileSystemLoader',
@@ -64,7 +64,20 @@ __all__ = [
'MemcachedBytecodeCache', 'Undefined', 'DebugUndefined',
'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound',
'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
+ 'TemplateRuntimeError',
'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
'evalcontextfilter', 'evalcontextfunction', 'make_logging_undefined',
+ 'select_autoescape',
]
+
+
+def _patch_async():
+ from jinja2.utils import have_async_gen
+ if have_async_gen:
+ from jinja2.asyncsupport import patch_all
+ patch_all()
+
+
+_patch_async()
+del _patch_async
diff --git a/third_party/jinja2/_compat.py b/third_party/jinja2/_compat.py
index 143962f384..61d85301a4 100644
--- a/third_party/jinja2/_compat.py
+++ b/third_party/jinja2/_compat.py
@@ -45,7 +45,6 @@ if not PY2:
implements_iterator = _identity
implements_to_string = _identity
encode_filename = _identity
- get_next = lambda x: x.__next__
else:
unichr = unichr
@@ -77,8 +76,6 @@ else:
cls.__str__ = lambda x: x.__unicode__().encode('utf-8')
return cls
- get_next = lambda x: x.next
-
def encode_filename(filename):
if isinstance(filename, unicode):
return filename.encode('utf-8')
@@ -86,23 +83,14 @@ else:
def with_metaclass(meta, *bases):
+ """Create a base class with a metaclass."""
# This requires a bit of explanation: the basic idea is to make a
- # dummy metaclass for one level of class instanciation that replaces
- # itself with the actual metaclass. Because of internal type checks
- # we also need to make sure that we downgrade the custom metaclass
- # for one level to something closer to type (that's why __call__ and
- # __init__ comes back from type etc.).
- #
- # This has the advantage over six.with_metaclass in that it does not
- # introduce dummy classes into the final MRO.
- class metaclass(meta):
- __call__ = type.__call__
- __init__ = type.__init__
+ # dummy metaclass for one level of class instantiation that replaces
+ # itself with the actual metaclass.
+ class metaclass(type):
def __new__(cls, name, this_bases, d):
- if this_bases is None:
- return type.__new__(cls, name, (), d)
return meta(name, bases, d)
- return metaclass('temporary_class', None, {})
+ return type.__new__(metaclass, 'temporary_class', (), {})
try:
diff --git a/third_party/jinja2/_identifier.py b/third_party/jinja2/_identifier.py
new file mode 100644
index 0000000000..2eac35d5c3
--- /dev/null
+++ b/third_party/jinja2/_identifier.py
@@ -0,0 +1,2 @@
+# generated by scripts/generate_identifier_pattern.py
+pattern = '·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࣔ-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఃా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഁ-ഃാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳸᳹᷀-᷵᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑅳𑄴𑆀-𑆂𑆳-𑇊𑇀-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯'
diff --git a/third_party/jinja2/_stringdefs.py b/third_party/jinja2/_stringdefs.py
deleted file mode 100644
index da5830e9f1..0000000000
--- a/third_party/jinja2/_stringdefs.py
+++ /dev/null
@@ -1,132 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2._stringdefs
- ~~~~~~~~~~~~~~~~~~
-
- Strings of all Unicode characters of a certain category.
- Used for matching in Unicode-aware languages. Run to regenerate.
-
- Inspired by chartypes_create.py from the MoinMoin project, original
- implementation from Pygments.
-
- :copyright: Copyright 2006-2009 by the Jinja team, see AUTHORS.
- :license: BSD, see LICENSE for details.
-"""
-
-from jinja2._compat import unichr
-
-Cc = u'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f'
-
-Cf = u'\xad\u0600\u0601\u0602\u0603\u06dd\u070f\u17b4\u17b5\u200b\u200c\u200d\u200e\u200f\u202a\u202b\u202c\u202d\u202e\u2060\u2061\u2062\u2063\u206a\u206b\u206c\u206d\u206e\u206f\ufeff\ufff9\ufffa\ufffb'
-
-Cn = u'\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024a\u024b\u024c\u024d\u024e\u024f\u0370\u0371\u0372\u0373\u0376\u0377\u0378\u0379\u037b\u037c\u037d\u037f\u0380\u0381\u0382\u0383\u038b\u038d\u03a2\u03cf\u0487\u04cf\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0557\u0558\u0560\u0588\u058b\u058c\u058d\u058e\u058f\u0590\u05ba\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05eb\u05ec\u05ed\u05ee\u05ef\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa\u05fb\u05fc\u05fd\u05fe\u05ff\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u0616\u0617\u0618\u0619\u061a\u061c\u061d\u0620\u063b\u063c\u063d\u063e\u063f\u065f\u070e\u074b\u074c\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u07b2\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6\u08b7\u08b8\u08b9\u08ba\u08bb\u08bc\u08bd\u08be\u08bf\u08c0\u08c1\u08c2\u08c3\u08c4\u08c5\u08c6\u08c7\u08c8\u08c9\u08ca\u08cb\u08cc\u08cd\u08ce\u08cf\u08d0\u08d1\u08d2\u08d3\u08d4\u08d5\u08d6\u08d7\u08d8\u08d9\u08da\u08db\u08dc\u08dd\u08de\u08df\u08e0\u08e1\u08e2\u08e3\u08e4\u08e5\u08e6\u08e7\u08e8\u08e9\u08ea\u08eb\u08ec\u08ed\u08ee\u08ef\u08f0\u08f1\u08f2\u08f3\u08f4\u08f5\u08f6\u08f7\u08f8\u08f9\u08fa\u08fb\u08fc\u08fd\u08fe\u08ff\u0900\u093a\u093b\u094e\u094f\u0955\u0956\u0957\u0971\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097e\u097f\u0980\u0984\u098d\u098e\u0991\u0992\u09a9\u09b1\u09b3\u09b4\u09b5\u09ba\u09bb\u09c5\u09c6\u09c9\u09ca\u09cf\u09d0\u09d1\u09d2\u09d3\u09d4\u09d5\u09d6\u09d8\u09d9\u09da\u09db\u09de\u09e4\u09e5\u09fb\u09fc\u09fd\u09fe\u09ff\u0a00\u0a04\u0a0b\u0a0c\u0a0d\u0a0e\u0a11\u0a12\u0a29\u0a31\u0a34\u0a37\u0a3a\u0a3b\u0a3d\u0a43\u0a44\u0a45\u0a46\u0a49\u0a4a\u0a4e\u0a4f\u0a50\u0a51\u0a52\u0a53\u0a54\u0a55\u0a56\u0a57\u0a58\u0a5d\u0a5f\u0a60\u0a61\u0a62\u0a63\u0a64\u0a65\u0a75\u0a76\u0a77\u0a78\u0a79\u0a7a\u0a7b\u0a7c\u0a7d\u0a7e\u0a7f\u0a80\u0a84\u0a8e\u0a92\u0aa9\u0ab1\u0ab4\u0aba\u0abb\u0ac6\u0aca\u0ace\u0acf\u0ad1\u0ad2\u0ad3\u0ad4\u0ad5\u0ad6\u0ad7\u0ad8\u0ad9\u0ada\u0adb\u0adc\u0add\u0ade\u0adf\u0ae4\u0ae5\u0af0\u0af2\u0af3\u0af4\u0af5\u0af6\u0af7\u0af8\u0af9\u0afa\u0afb\u0afc\u0afd\u0afe\u0aff\u0b00\u0b04\u0b0d\u0b0e\u0b11\u0b12\u0b29\u0b31\u0b34\u0b3a\u0b3b\u0b44\u0b45\u0b46\u0b49\u0b4a\u0b4e\u0b4f\u0b50\u0b51\u0b52\u0b53\u0b54\u0b55\u0b58\u0b59\u0b5a\u0b5b\u0b5e\u0b62\u0b63\u0b64\u0b65\u0b72\u0b73\u0b74\u0b75\u0b76\u0b77\u0b78\u0b79\u0b7a\u0b7b\u0b7c\u0b7d\u0b7e\u0b7f\u0b80\u0b81\u0b84\u0b8b\u0b8c\u0b8d\u0b91\u0b96\u0b97\u0b98\u0b9b\u0b9d\u0ba0\u0ba1\u0ba2\u0ba5\u0ba6\u0ba7\u0bab\u0bac\u0bad\u0bba\u0bbb\u0bbc\u0bbd\u0bc3\u0bc4\u0bc5\u0bc9\u0bce\u0bcf\u0bd0\u0bd1\u0bd2\u0bd3\u0bd4\u0bd5\u0bd6\u0bd8\u0bd9\u0bda\u0bdb\u0bdc\u0bdd\u0bde\u0bdf\u0be0\u0be1\u0be2\u0be3\u0be4\u0be5\u0bfb\u0bfc\u0bfd\u0bfe\u0bff\u0c00\u0c04\u0c0d\u0c11\u0c29\u0c34\u0c3a\u0c3b\u0c3c\u0c3d\u0c45\u0c49\u0c4e\u0c4f\u0c50\u0c51\u0c52\u0c53\u0c54\u0c57\u0c58\u0c59\u0c5a\u0c5b\u0c5c\u0c5d\u0c5e\u0c5f\u0c62\u0c63\u0c64\u0c65\u0c70\u0c71\u0c72\u0c73\u0c74\u0c75\u0c76\u0c77\u0c78\u0c79\u0c7a\u0c7b\u0c7c\u0c7d\u0c7e\u0c7f\u0c80\u0c81\u0c84\u0c8d\u0c91\u0ca9\u0cb4\u0cba\u0cbb\u0cc5\u0cc9\u0cce\u0ccf\u0cd0\u0cd1\u0cd2\u0cd3\u0cd4\u0cd7\u0cd8\u0cd9\u0cda\u0cdb\u0cdc\u0cdd\u0cdf\u0ce2\u0ce3\u0ce4\u0ce5\u0cf0\u0cf1\u0cf2\u0cf3\u0cf4\u0cf5\u0cf6\u0cf7\u0cf8\u0cf9\u0cfa\u0cfb\u0cfc\u0cfd\u0cfe\u0cff\u0d00\u0d01\u0d04\u0d0d\u0d11\u0d29\u0d3a\u0d3b\u0d3c\u0d3d\u0d44\u0d45\u0d49\u0d4e\u0d4f\u0d50\u0d51\u0d52\u0d53\u0d54\u0d55\u0d56\u0d58\u0d59\u0d5a\u0d5b\u0d5c\u0d5d\u0d5e\u0d5f\u0d62\u0d63\u0d64\u0d65\u0d70\u0d71\u0d72\u0d73\u0d74\u0d75\u0d76\u0d77\u0d78\u0d79\u0d7a\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f\u0d80\u0d81\u0d84\u0d97\u0d98\u0d99\u0db2\u0dbc\u0dbe\u0dbf\u0dc7\u0dc8\u0dc9\u0dcb\u0dcc\u0dcd\u0dce\u0dd5\u0dd7\u0de0\u0de1\u0de2\u0de3\u0de4\u0de5\u0de6\u0de7\u0de8\u0de9\u0dea\u0deb\u0dec\u0ded\u0dee\u0def\u0df0\u0df1\u0df5\u0df6\u0df7\u0df8\u0df9\u0dfa\u0dfb\u0dfc\u0dfd\u0dfe\u0dff\u0e00\u0e3b\u0e3c\u0e3d\u0e3e\u0e5c\u0e5d\u0e5e\u0e5f\u0e60\u0e61\u0e62\u0e63\u0e64\u0e65\u0e66\u0e67\u0e68\u0e69\u0e6a\u0e6b\u0e6c\u0e6d\u0e6e\u0e6f\u0e70\u0e71\u0e72\u0e73\u0e74\u0e75\u0e76\u0e77\u0e78\u0e79\u0e7a\u0e7b\u0e7c\u0e7d\u0e7e\u0e7f\u0e80\u0e83\u0e85\u0e86\u0e89\u0e8b\u0e8c\u0e8e\u0e8f\u0e90\u0e91\u0e92\u0e93\u0e98\u0ea0\u0ea4\u0ea6\u0ea8\u0ea9\u0eac\u0eba\u0ebe\u0ebf\u0ec5\u0ec7\u0ece\u0ecf\u0eda\u0edb\u0ede\u0edf\u0ee0\u0ee1\u0ee2\u0ee3\u0ee4\u0ee5\u0ee6\u0ee7\u0ee8\u0ee9\u0eea\u0eeb\u0eec\u0eed\u0eee\u0eef\u0ef0\u0ef1\u0ef2\u0ef3\u0ef4\u0ef5\u0ef6\u0ef7\u0ef8\u0ef9\u0efa\u0efb\u0efc\u0efd\u0efe\u0eff\u0f48\u0f6b\u0f6c\u0f6d\u0f6e\u0f6f\u0f70\u0f8c\u0f8d\u0f8e\u0f8f\u0f98\u0fbd\u0fcd\u0fce\u0fd2\u0fd3\u0fd4\u0fd5\u0fd6\u0fd7\u0fd8\u0fd9\u0fda\u0fdb\u0fdc\u0fdd\u0fde\u0fdf\u0fe0\u0fe1\u0fe2\u0fe3\u0fe4\u0fe5\u0fe6\u0fe7\u0fe8\u0fe9\u0fea\u0feb\u0fec\u0fed\u0fee\u0fef\u0ff0\u0ff1\u0ff2\u0ff3\u0ff4\u0ff5\u0ff6\u0ff7\u0ff8\u0ff9\u0ffa\u0ffb\u0ffc\u0ffd\u0ffe\u0fff\u1022\u1028\u102b\u1033\u1034\u1035\u103a\u103b\u103c\u103d\u103e\u103f\u105a\u105b\u105c\u105d\u105e\u105f\u1060\u1061\u1062\u1063\u1064\u1065\u1066\u1067\u1068\u1069\u106a\u106b\u106c\u106d\u106e\u106f\u1070\u1071\u1072\u1073\u1074\u1075\u1076\u1077\u1078\u1079\u107a\u107b\u107c\u107d\u107e\u107f\u1080\u1081\u1082\u1083\u1084\u1085\u1086\u1087\u1088\u1089\u108a\u108b\u108c\u108d\u108e\u108f\u1090\u1091\u1092\u1093\u1094\u1095\u1096\u1097\u1098\u1099\u109a\u109b\u109c\u109d\u109e\u109f\u10c6\u10c7\u10c8\u10c9\u10ca\u10cb\u10cc\u10cd\u10ce\u10cf\u10fd\u10fe\u10ff\u115a\u115b\u115c\u115d\u115e\u11a3\u11a4\u11a5\u11a6\u11a7\u11fa\u11fb\u11fc\u11fd\u11fe\u11ff\u1249\u124e\u124f\u1257\u1259\u125e\u125f\u1289\u128e\u128f\u12b1\u12b6\u12b7\u12bf\u12c1\u12c6\u12c7\u12d7\u1311\u1316\u1317\u135b\u135c\u135d\u135e\u137d\u137e\u137f\u139a\u139b\u139c\u139d\u139e\u139f\u13f5\u13f6\u13f7\u13f8\u13f9\u13fa\u13fb\u13fc\u13fd\u13fe\u13ff\u1400\u1677\u1678\u1679\u167a\u167b\u167c\u167d\u167e\u167f\u169d\u169e\u169f\u16f1\u16f2\u16f3\u16f4\u16f5\u16f6\u16f7\u16f8\u16f9\u16fa\u16fb\u16fc\u16fd\u16fe\u16ff\u170d\u1715\u1716\u1717\u1718\u1719\u171a\u171b\u171c\u171d\u171e\u171f\u1737\u1738\u1739\u173a\u173b\u173c\u173d\u173e\u173f\u1754\u1755\u1756\u1757\u1758\u1759\u175a\u175b\u175c\u175d\u175e\u175f\u176d\u1771\u1774\u1775\u1776\u1777\u1778\u1779\u177a\u177b\u177c\u177d\u177e\u177f\u17de\u17df\u17ea\u17eb\u17ec\u17ed\u17ee\u17ef\u17fa\u17fb\u17fc\u17fd\u17fe\u17ff\u180f\u181a\u181b\u181c\u181d\u181e\u181f\u1878\u1879\u187a\u187b\u187c\u187d\u187e\u187f\u18aa\u18ab\u18ac\u18ad\u18ae\u18af\u18b0\u18b1\u18b2\u18b3\u18b4\u18b5\u18b6\u18b7\u18b8\u18b9\u18ba\u18bb\u18bc\u18bd\u18be\u18bf\u18c0\u18c1\u18c2\u18c3\u18c4\u18c5\u18c6\u18c7\u18c8\u18c9\u18ca\u18cb\u18cc\u18cd\u18ce\u18cf\u18d0\u18d1\u18d2\u18d3\u18d4\u18d5\u18d6\u18d7\u18d8\u18d9\u18da\u18db\u18dc\u18dd\u18de\u18df\u18e0\u18e1\u18e2\u18e3\u18e4\u18e5\u18e6\u18e7\u18e8\u18e9\u18ea\u18eb\u18ec\u18ed\u18ee\u18ef\u18f0\u18f1\u18f2\u18f3\u18f4\u18f5\u18f6\u18f7\u18f8\u18f9\u18fa\u18fb\u18fc\u18fd\u18fe\u18ff\u191d\u191e\u191f\u192c\u192d\u192e\u192f\u193c\u193d\u193e\u193f\u1941\u1942\u1943\u196e\u196f\u1975\u1976\u1977\u1978\u1979\u197a\u197b\u197c\u197d\u197e\u197f\u19aa\u19ab\u19ac\u19ad\u19ae\u19af\u19ca\u19cb\u19cc\u19cd\u19ce\u19cf\u19da\u19db\u19dc\u19dd\u1a1c\u1a1d\u1a20\u1a21\u1a22\u1a23\u1a24\u1a25\u1a26\u1a27\u1a28\u1a29\u1a2a\u1a2b\u1a2c\u1a2d\u1a2e\u1a2f\u1a30\u1a31\u1a32\u1a33\u1a34\u1a35\u1a36\u1a37\u1a38\u1a39\u1a3a\u1a3b\u1a3c\u1a3d\u1a3e\u1a3f\u1a40\u1a41\u1a42\u1a43\u1a44\u1a45\u1a46\u1a47\u1a48\u1a49\u1a4a\u1a4b\u1a4c\u1a4d\u1a4e\u1a4f\u1a50\u1a51\u1a52\u1a53\u1a54\u1a55\u1a56\u1a57\u1a58\u1a59\u1a5a\u1a5b\u1a5c\u1a5d\u1a5e\u1a5f\u1a60\u1a61\u1a62\u1a63\u1a64\u1a65\u1a66\u1a67\u1a68\u1a69\u1a6a\u1a6b\u1a6c\u1a6d\u1a6e\u1a6f\u1a70\u1a71\u1a72\u1a73\u1a74\u1a75\u1a76\u1a77\u1a78\u1a79\u1a7a\u1a7b\u1a7c\u1a7d\u1a7e\u1a7f\u1a80\u1a81\u1a82\u1a83\u1a84\u1a85\u1a86\u1a87\u1a88\u1a89\u1a8a\u1a8b\u1a8c\u1a8d\u1a8e\u1a8f\u1a90\u1a91\u1a92\u1a93\u1a94\u1a95\u1a96\u1a97\u1a98\u1a99\u1a9a\u1a9b\u1a9c\u1a9d\u1a9e\u1a9f\u1aa0\u1aa1\u1aa2\u1aa3\u1aa4\u1aa5\u1aa6\u1aa7\u1aa8\u1aa9\u1aaa\u1aab\u1aac\u1aad\u1aae\u1aaf\u1ab0\u1ab1\u1ab2\u1ab3\u1ab4\u1ab5\u1ab6\u1ab7\u1ab8\u1ab9\u1aba\u1abb\u1abc\u1abd\u1abe\u1abf\u1ac0\u1ac1\u1ac2\u1ac3\u1ac4\u1ac5\u1ac6\u1ac7\u1ac8\u1ac9\u1aca\u1acb\u1acc\u1acd\u1ace\u1acf\u1ad0\u1ad1\u1ad2\u1ad3\u1ad4\u1ad5\u1ad6\u1ad7\u1ad8\u1ad9\u1ada\u1adb\u1adc\u1add\u1ade\u1adf\u1ae0\u1ae1\u1ae2\u1ae3\u1ae4\u1ae5\u1ae6\u1ae7\u1ae8\u1ae9\u1aea\u1aeb\u1aec\u1aed\u1aee\u1aef\u1af0\u1af1\u1af2\u1af3\u1af4\u1af5\u1af6\u1af7\u1af8\u1af9\u1afa\u1afb\u1afc\u1afd\u1afe\u1aff\u1b00\u1b01\u1b02\u1b03\u1b04\u1b05\u1b06\u1b07\u1b08\u1b09\u1b0a\u1b0b\u1b0c\u1b0d\u1b0e\u1b0f\u1b10\u1b11\u1b12\u1b13\u1b14\u1b15\u1b16\u1b17\u1b18\u1b19\u1b1a\u1b1b\u1b1c\u1b1d\u1b1e\u1b1f\u1b20\u1b21\u1b22\u1b23\u1b24\u1b25\u1b26\u1b27\u1b28\u1b29\u1b2a\u1b2b\u1b2c\u1b2d\u1b2e\u1b2f\u1b30\u1b31\u1b32\u1b33\u1b34\u1b35\u1b36\u1b37\u1b38\u1b39\u1b3a\u1b3b\u1b3c\u1b3d\u1b3e\u1b3f\u1b40\u1b41\u1b42\u1b43\u1b44\u1b45\u1b46\u1b47\u1b48\u1b49\u1b4a\u1b4b\u1b4c\u1b4d\u1b4e\u1b4f\u1b50\u1b51\u1b52\u1b53\u1b54\u1b55\u1b56\u1b57\u1b58\u1b59\u1b5a\u1b5b\u1b5c\u1b5d\u1b5e\u1b5f\u1b60\u1b61\u1b62\u1b63\u1b64\u1b65\u1b66\u1b67\u1b68\u1b69\u1b6a\u1b6b\u1b6c\u1b6d\u1b6e\u1b6f\u1b70\u1b71\u1b72\u1b73\u1b74\u1b75\u1b76\u1b77\u1b78\u1b79\u1b7a\u1b7b\u1b7c\u1b7d\u1b7e\u1b7f\u1b80\u1b81\u1b82\u1b83\u1b84\u1b85\u1b86\u1b87\u1b88\u1b89\u1b8a\u1b8b\u1b8c\u1b8d\u1b8e\u1b8f\u1b90\u1b91\u1b92\u1b93\u1b94\u1b95\u1b96\u1b97\u1b98\u1b99\u1b9a\u1b9b\u1b9c\u1b9d\u1b9e\u1b9f\u1ba0\u1ba1\u1ba2\u1ba3\u1ba4\u1ba5\u1ba6\u1ba7\u1ba8\u1ba9\u1baa\u1bab\u1bac\u1bad\u1bae\u1baf\u1bb0\u1bb1\u1bb2\u1bb3\u1bb4\u1bb5\u1bb6\u1bb7\u1bb8\u1bb9\u1bba\u1bbb\u1bbc\u1bbd\u1bbe\u1bbf\u1bc0\u1bc1\u1bc2\u1bc3\u1bc4\u1bc5\u1bc6\u1bc7\u1bc8\u1bc9\u1bca\u1bcb\u1bcc\u1bcd\u1bce\u1bcf\u1bd0\u1bd1\u1bd2\u1bd3\u1bd4\u1bd5\u1bd6\u1bd7\u1bd8\u1bd9\u1bda\u1bdb\u1bdc\u1bdd\u1bde\u1bdf\u1be0\u1be1\u1be2\u1be3\u1be4\u1be5\u1be6\u1be7\u1be8\u1be9\u1bea\u1beb\u1bec\u1bed\u1bee\u1bef\u1bf0\u1bf1\u1bf2\u1bf3\u1bf4\u1bf5\u1bf6\u1bf7\u1bf8\u1bf9\u1bfa\u1bfb\u1bfc\u1bfd\u1bfe\u1bff\u1c00\u1c01\u1c02\u1c03\u1c04\u1c05\u1c06\u1c07\u1c08\u1c09\u1c0a\u1c0b\u1c0c\u1c0d\u1c0e\u1c0f\u1c10\u1c11\u1c12\u1c13\u1c14\u1c15\u1c16\u1c17\u1c18\u1c19\u1c1a\u1c1b\u1c1c\u1c1d\u1c1e\u1c1f\u1c20\u1c21\u1c22\u1c23\u1c24\u1c25\u1c26\u1c27\u1c28\u1c29\u1c2a\u1c2b\u1c2c\u1c2d\u1c2e\u1c2f\u1c30\u1c31\u1c32\u1c33\u1c34\u1c35\u1c36\u1c37\u1c38\u1c39\u1c3a\u1c3b\u1c3c\u1c3d\u1c3e\u1c3f\u1c40\u1c41\u1c42\u1c43\u1c44\u1c45\u1c46\u1c47\u1c48\u1c49\u1c4a\u1c4b\u1c4c\u1c4d\u1c4e\u1c4f\u1c50\u1c51\u1c52\u1c53\u1c54\u1c55\u1c56\u1c57\u1c58\u1c59\u1c5a\u1c5b\u1c5c\u1c5d\u1c5e\u1c5f\u1c60\u1c61\u1c62\u1c63\u1c64\u1c65\u1c66\u1c67\u1c68\u1c69\u1c6a\u1c6b\u1c6c\u1c6d\u1c6e\u1c6f\u1c70\u1c71\u1c72\u1c73\u1c74\u1c75\u1c76\u1c77\u1c78\u1c79\u1c7a\u1c7b\u1c7c\u1c7d\u1c7e\u1c7f\u1c80\u1c81\u1c82\u1c83\u1c84\u1c85\u1c86\u1c87\u1c88\u1c89\u1c8a\u1c8b\u1c8c\u1c8d\u1c8e\u1c8f\u1c90\u1c91\u1c92\u1c93\u1c94\u1c95\u1c96\u1c97\u1c98\u1c99\u1c9a\u1c9b\u1c9c\u1c9d\u1c9e\u1c9f\u1ca0\u1ca1\u1ca2\u1ca3\u1ca4\u1ca5\u1ca6\u1ca7\u1ca8\u1ca9\u1caa\u1cab\u1cac\u1cad\u1cae\u1caf\u1cb0\u1cb1\u1cb2\u1cb3\u1cb4\u1cb5\u1cb6\u1cb7\u1cb8\u1cb9\u1cba\u1cbb\u1cbc\u1cbd\u1cbe\u1cbf\u1cc0\u1cc1\u1cc2\u1cc3\u1cc4\u1cc5\u1cc6\u1cc7\u1cc8\u1cc9\u1cca\u1ccb\u1ccc\u1ccd\u1cce\u1ccf\u1cd0\u1cd1\u1cd2\u1cd3\u1cd4\u1cd5\u1cd6\u1cd7\u1cd8\u1cd9\u1cda\u1cdb\u1cdc\u1cdd\u1cde\u1cdf\u1ce0\u1ce1\u1ce2\u1ce3\u1ce4\u1ce5\u1ce6\u1ce7\u1ce8\u1ce9\u1cea\u1ceb\u1cec\u1ced\u1cee\u1cef\u1cf0\u1cf1\u1cf2\u1cf3\u1cf4\u1cf5\u1cf6\u1cf7\u1cf8\u1cf9\u1cfa\u1cfb\u1cfc\u1cfd\u1cfe\u1cff\u1dc4\u1dc5\u1dc6\u1dc7\u1dc8\u1dc9\u1dca\u1dcb\u1dcc\u1dcd\u1dce\u1dcf\u1dd0\u1dd1\u1dd2\u1dd3\u1dd4\u1dd5\u1dd6\u1dd7\u1dd8\u1dd9\u1dda\u1ddb\u1ddc\u1ddd\u1dde\u1ddf\u1de0\u1de1\u1de2\u1de3\u1de4\u1de5\u1de6\u1de7\u1de8\u1de9\u1dea\u1deb\u1dec\u1ded\u1dee\u1def\u1df0\u1df1\u1df2\u1df3\u1df4\u1df5\u1df6\u1df7\u1df8\u1df9\u1dfa\u1dfb\u1dfc\u1dfd\u1dfe\u1dff\u1e9c\u1e9d\u1e9e\u1e9f\u1efa\u1efb\u1efc\u1efd\u1efe\u1eff\u1f16\u1f17\u1f1e\u1f1f\u1f46\u1f47\u1f4e\u1f4f\u1f58\u1f5a\u1f5c\u1f5e\u1f7e\u1f7f\u1fb5\u1fc5\u1fd4\u1fd5\u1fdc\u1ff0\u1ff1\u1ff5\u1fff\u2064\u2065\u2066\u2067\u2068\u2069\u2072\u2073\u208f\u2095\u2096\u2097\u2098\u2099\u209a\u209b\u209c\u209d\u209e\u209f\u20b6\u20b7\u20b8\u20b9\u20ba\u20bb\u20bc\u20bd\u20be\u20bf\u20c0\u20c1\u20c2\u20c3\u20c4\u20c5\u20c6\u20c7\u20c8\u20c9\u20ca\u20cb\u20cc\u20cd\u20ce\u20cf\u20ec\u20ed\u20ee\u20ef\u20f0\u20f1\u20f2\u20f3\u20f4\u20f5\u20f6\u20f7\u20f8\u20f9\u20fa\u20fb\u20fc\u20fd\u20fe\u20ff\u214d\u214e\u214f\u2150\u2151\u2152\u2184\u2185\u2186\u2187\u2188\u2189\u218a\u218b\u218c\u218d\u218e\u218f\u23dc\u23dd\u23de\u23df\u23e0\u23e1\u23e2\u23e3\u23e4\u23e5\u23e6\u23e7\u23e8\u23e9\u23ea\u23eb\u23ec\u23ed\u23ee\u23ef\u23f0\u23f1\u23f2\u23f3\u23f4\u23f5\u23f6\u23f7\u23f8\u23f9\u23fa\u23fb\u23fc\u23fd\u23fe\u23ff\u2427\u2428\u2429\u242a\u242b\u242c\u242d\u242e\u242f\u2430\u2431\u2432\u2433\u2434\u2435\u2436\u2437\u2438\u2439\u243a\u243b\u243c\u243d\u243e\u243f\u244b\u244c\u244d\u244e\u244f\u2450\u2451\u2452\u2453\u2454\u2455\u2456\u2457\u2458\u2459\u245a\u245b\u245c\u245d\u245e\u245f\u269d\u269e\u269f\u26b2\u26b3\u26b4\u26b5\u26b6\u26b7\u26b8\u26b9\u26ba\u26bb\u26bc\u26bd\u26be\u26bf\u26c0\u26c1\u26c2\u26c3\u26c4\u26c5\u26c6\u26c7\u26c8\u26c9\u26ca\u26cb\u26cc\u26cd\u26ce\u26cf\u26d0\u26d1\u26d2\u26d3\u26d4\u26d5\u26d6\u26d7\u26d8\u26d9\u26da\u26db\u26dc\u26dd\u26de\u26df\u26e0\u26e1\u26e2\u26e3\u26e4\u26e5\u26e6\u26e7\u26e8\u26e9\u26ea\u26eb\u26ec\u26ed\u26ee\u26ef\u26f0\u26f1\u26f2\u26f3\u26f4\u26f5\u26f6\u26f7\u26f8\u26f9\u26fa\u26fb\u26fc\u26fd\u26fe\u26ff\u2700\u2705\u270a\u270b\u2728\u274c\u274e\u2753\u2754\u2755\u2757\u275f\u2760\u2795\u2796\u2797\u27b0\u27bf\u27c7\u27c8\u27c9\u27ca\u27cb\u27cc\u27cd\u27ce\u27cf\u27ec\u27ed\u27ee\u27ef\u2b14\u2b15\u2b16\u2b17\u2b18\u2b19\u2b1a\u2b1b\u2b1c\u2b1d\u2b1e\u2b1f\u2b20\u2b21\u2b22\u2b23\u2b24\u2b25\u2b26\u2b27\u2b28\u2b29\u2b2a\u2b2b\u2b2c\u2b2d\u2b2e\u2b2f\u2b30\u2b31\u2b32\u2b33\u2b34\u2b35\u2b36\u2b37\u2b38\u2b39\u2b3a\u2b3b\u2b3c\u2b3d\u2b3e\u2b3f\u2b40\u2b41\u2b42\u2b43\u2b44\u2b45\u2b46\u2b47\u2b48\u2b49\u2b4a\u2b4b\u2b4c\u2b4d\u2b4e\u2b4f\u2b50\u2b51\u2b52\u2b53\u2b54\u2b55\u2b56\u2b57\u2b58\u2b59\u2b5a\u2b5b\u2b5c\u2b5d\u2b5e\u2b5f\u2b60\u2b61\u2b62\u2b63\u2b64\u2b65\u2b66\u2b67\u2b68\u2b69\u2b6a\u2b6b\u2b6c\u2b6d\u2b6e\u2b6f\u2b70\u2b71\u2b72\u2b73\u2b74\u2b75\u2b76\u2b77\u2b78\u2b79\u2b7a\u2b7b\u2b7c\u2b7d\u2b7e\u2b7f\u2b80\u2b81\u2b82\u2b83\u2b84\u2b85\u2b86\u2b87\u2b88\u2b89\u2b8a\u2b8b\u2b8c\u2b8d\u2b8e\u2b8f\u2b90\u2b91\u2b92\u2b93\u2b94\u2b95\u2b96\u2b97\u2b98\u2b99\u2b9a\u2b9b\u2b9c\u2b9d\u2b9e\u2b9f\u2ba0\u2ba1\u2ba2\u2ba3\u2ba4\u2ba5\u2ba6\u2ba7\u2ba8\u2ba9\u2baa\u2bab\u2bac\u2bad\u2bae\u2baf\u2bb0\u2bb1\u2bb2\u2bb3\u2bb4\u2bb5\u2bb6\u2bb7\u2bb8\u2bb9\u2bba\u2bbb\u2bbc\u2bbd\u2bbe\u2bbf\u2bc0\u2bc1\u2bc2\u2bc3\u2bc4\u2bc5\u2bc6\u2bc7\u2bc8\u2bc9\u2bca\u2bcb\u2bcc\u2bcd\u2bce\u2bcf\u2bd0\u2bd1\u2bd2\u2bd3\u2bd4\u2bd5\u2bd6\u2bd7\u2bd8\u2bd9\u2bda\u2bdb\u2bdc\u2bdd\u2bde\u2bdf\u2be0\u2be1\u2be2\u2be3\u2be4\u2be5\u2be6\u2be7\u2be8\u2be9\u2bea\u2beb\u2bec\u2bed\u2bee\u2bef\u2bf0\u2bf1\u2bf2\u2bf3\u2bf4\u2bf5\u2bf6\u2bf7\u2bf8\u2bf9\u2bfa\u2bfb\u2bfc\u2bfd\u2bfe\u2bff\u2c2f\u2c5f\u2c60\u2c61\u2c62\u2c63\u2c64\u2c65\u2c66\u2c67\u2c68\u2c69\u2c6a\u2c6b\u2c6c\u2c6d\u2c6e\u2c6f\u2c70\u2c71\u2c72\u2c73\u2c74\u2c75\u2c76\u2c77\u2c78\u2c79\u2c7a\u2c7b\u2c7c\u2c7d\u2c7e\u2c7f\u2ceb\u2cec\u2ced\u2cee\u2cef\u2cf0\u2cf1\u2cf2\u2cf3\u2cf4\u2cf5\u2cf6\u2cf7\u2cf8\u2d26\u2d27\u2d28\u2d29\u2d2a\u2d2b\u2d2c\u2d2d\u2d2e\u2d2f\u2d66\u2d67\u2d68\u2d69\u2d6a\u2d6b\u2d6c\u2d6d\u2d6e\u2d70\u2d71\u2d72\u2d73\u2d74\u2d75\u2d76\u2d77\u2d78\u2d79\u2d7a\u2d7b\u2d7c\u2d7d\u2d7e\u2d7f\u2d97\u2d98\u2d99\u2d9a\u2d9b\u2d9c\u2d9d\u2d9e\u2d9f\u2da7\u2daf\u2db7\u2dbf\u2dc7\u2dcf\u2dd7\u2ddf\u2de0\u2de1\u2de2\u2de3\u2de4\u2de5\u2de6\u2de7\u2de8\u2de9\u2dea\u2deb\u2dec\u2ded\u2dee\u2def\u2df0\u2df1\u2df2\u2df3\u2df4\u2df5\u2df6\u2df7\u2df8\u2df9\u2dfa\u2dfb\u2dfc\u2dfd\u2dfe\u2dff\u2e18\u2e19\u2e1a\u2e1b\u2e1e\u2e1f\u2e20\u2e21\u2e22\u2e23\u2e24\u2e25\u2e26\u2e27\u2e28\u2e29\u2e2a\u2e2b\u2e2c\u2e2d\u2e2e\u2e2f\u2e30\u2e31\u2e32\u2e33\u2e34\u2e35\u2e36\u2e37\u2e38\u2e39\u2e3a\u2e3b\u2e3c\u2e3d\u2e3e\u2e3f\u2e40\u2e41\u2e42\u2e43\u2e44\u2e45\u2e46\u2e47\u2e48\u2e49\u2e4a\u2e4b\u2e4c\u2e4d\u2e4e\u2e4f\u2e50\u2e51\u2e52\u2e53\u2e54\u2e55\u2e56\u2e57\u2e58\u2e59\u2e5a\u2e5b\u2e5c\u2e5d\u2e5e\u2e5f\u2e60\u2e61\u2e62\u2e63\u2e64\u2e65\u2e66\u2e67\u2e68\u2e69\u2e6a\u2e6b\u2e6c\u2e6d\u2e6e\u2e6f\u2e70\u2e71\u2e72\u2e73\u2e74\u2e75\u2e76\u2e77\u2e78\u2e79\u2e7a\u2e7b\u2e7c\u2e7d\u2e7e\u2e7f\u2e9a\u2ef4\u2ef5\u2ef6\u2ef7\u2ef8\u2ef9\u2efa\u2efb\u2efc\u2efd\u2efe\u2eff\u2fd6\u2fd7\u2fd8\u2fd9\u2fda\u2fdb\u2fdc\u2fdd\u2fde\u2fdf\u2fe0\u2fe1\u2fe2\u2fe3\u2fe4\u2fe5\u2fe6\u2fe7\u2fe8\u2fe9\u2fea\u2feb\u2fec\u2fed\u2fee\u2fef\u2ffc\u2ffd\u2ffe\u2fff\u3040\u3097\u3098\u3100\u3101\u3102\u3103\u3104\u312d\u312e\u312f\u3130\u318f\u31b8\u31b9\u31ba\u31bb\u31bc\u31bd\u31be\u31bf\u31d0\u31d1\u31d2\u31d3\u31d4\u31d5\u31d6\u31d7\u31d8\u31d9\u31da\u31db\u31dc\u31dd\u31de\u31df\u31e0\u31e1\u31e2\u31e3\u31e4\u31e5\u31e6\u31e7\u31e8\u31e9\u31ea\u31eb\u31ec\u31ed\u31ee\u31ef\u321f\u3244\u3245\u3246\u3247\u3248\u3249\u324a\u324b\u324c\u324d\u324e\u324f\u32ff\u4db6\u4db7\u4db8\u4db9\u4dba\u4dbb\u4dbc\u4dbd\u4dbe\u4dbf\u9fbc\u9fbd\u9fbe\u9fbf\u9fc0\u9fc1\u9fc2\u9fc3\u9fc4\u9fc5\u9fc6\u9fc7\u9fc8\u9fc9\u9fca\u9fcb\u9fcc\u9fcd\u9fce\u9fcf\u9fd0\u9fd1\u9fd2\u9fd3\u9fd4\u9fd5\u9fd6\u9fd7\u9fd8\u9fd9\u9fda\u9fdb\u9fdc\u9fdd\u9fde\u9fdf\u9fe0\u9fe1\u9fe2\u9fe3\u9fe4\u9fe5\u9fe6\u9fe7\u9fe8\u9fe9\u9fea\u9feb\u9fec\u9fed\u9fee\u9fef\u9ff0\u9ff1\u9ff2\u9ff3\u9ff4\u9ff5\u9ff6\u9ff7\u9ff8\u9ff9\u9ffa\u9ffb\u9ffc\u9ffd\u9ffe\u9fff\ua48d\ua48e\ua48f\ua4c7\ua4c8\ua4c9\ua4ca\ua4cb\ua4cc\ua4cd\ua4ce\ua4cf\ua4d0\ua4d1\ua4d2\ua4d3\ua4d4\ua4d5\ua4d6\ua4d7\ua4d8\ua4d9\ua4da\ua4db\ua4dc\ua4dd\ua4de\ua4df\ua4e0\ua4e1\ua4e2\ua4e3\ua4e4\ua4e5\ua4e6\ua4e7\ua4e8\ua4e9\ua4ea\ua4eb\ua4ec\ua4ed\ua4ee\ua4ef\ua4f0\ua4f1\ua4f2\ua4f3\ua4f4\ua4f5\ua4f6\ua4f7\ua4f8\ua4f9\ua4fa\ua4fb\ua4fc\ua4fd\ua4fe\ua4ff\ua500\ua501\ua502\ua503\ua504\ua505\ua506\ua507\ua508\ua509\ua50a\ua50b\ua50c\ua50d\ua50e\ua50f\ua510\ua511\ua512\ua513\ua514\ua515\ua516\ua517\ua518\ua519\ua51a\ua51b\ua51c\ua51d\ua51e\ua51f\ua520\ua521\ua522\ua523\ua524\ua525\ua526\ua527\ua528\ua529\ua52a\ua52b\ua52c\ua52d\ua52e\ua52f\ua530\ua531\ua532\ua533\ua534\ua535\ua536\ua537\ua538\ua539\ua53a\ua53b\ua53c\ua53d\ua53e\ua53f\ua540\ua541\ua542\ua543\ua544\ua545\ua546\ua547\ua548\ua549\ua54a\ua54b\ua54c\ua54d\ua54e\ua54f\ua550\ua551\ua552\ua553\ua554\ua555\ua556\ua557\ua558\ua559\ua55a\ua55b\ua55c\ua55d\ua55e\ua55f\ua560\ua561\ua562\ua563\ua564\ua565\ua566\ua567\ua568\ua569\ua56a\ua56b\ua56c\ua56d\ua56e\ua56f\ua570\ua571\ua572\ua573\ua574\ua575\ua576\ua577\ua578\ua579\ua57a\ua57b\ua57c\ua57d\ua57e\ua57f\ua580\ua581\ua582\ua583\ua584\ua585\ua586\ua587\ua588\ua589\ua58a\ua58b\ua58c\ua58d\ua58e\ua58f\ua590\ua591\ua592\ua593\ua594\ua595\ua596\ua597\ua598\ua599\ua59a\ua59b\ua59c\ua59d\ua59e\ua59f\ua5a0\ua5a1\ua5a2\ua5a3\ua5a4\ua5a5\ua5a6\ua5a7\ua5a8\ua5a9\ua5aa\ua5ab\ua5ac\ua5ad\ua5ae\ua5af\ua5b0\ua5b1\ua5b2\ua5b3\ua5b4\ua5b5\ua5b6\ua5b7\ua5b8\ua5b9\ua5ba\ua5bb\ua5bc\ua5bd\ua5be\ua5bf\ua5c0\ua5c1\ua5c2\ua5c3\ua5c4\ua5c5\ua5c6\ua5c7\ua5c8\ua5c9\ua5ca\ua5cb\ua5cc\ua5cd\ua5ce\ua5cf\ua5d0\ua5d1\ua5d2\ua5d3\ua5d4\ua5d5\ua5d6\ua5d7\ua5d8\ua5d9\ua5da\ua5db\ua5dc\ua5dd\ua5de\ua5df\ua5e0\ua5e1\ua5e2\ua5e3\ua5e4\ua5e5\ua5e6\ua5e7\ua5e8\ua5e9\ua5ea\ua5eb\ua5ec\ua5ed\ua5ee\ua5ef\ua5f0\ua5f1\ua5f2\ua5f3\ua5f4\ua5f5\ua5f6\ua5f7\ua5f8\ua5f9\ua5fa\ua5fb\ua5fc\ua5fd\ua5fe\ua5ff\ua600\ua601\ua602\ua603\ua604\ua605\ua606\ua607\ua608\ua609\ua60a\ua60b\ua60c\ua60d\ua60e\ua60f\ua610\ua611\ua612\ua613\ua614\ua615\ua616\ua617\ua618\ua619\ua61a\ua61b\ua61c\ua61d\ua61e\ua61f\ua620\ua621\ua622\ua623\ua624\ua625\ua626\ua627\ua628\ua629\ua62a\ua62b\ua62c\ua62d\ua62e\ua62f\ua630\ua631\ua632\ua633\ua634\ua635\ua636\ua637\ua638\ua639\ua63a\ua63b\ua63c\ua63d\ua63e\ua63f\ua640\ua641\ua642\ua643\ua644\ua645\ua646\ua647\ua648\ua649\ua64a\ua64b\ua64c\ua64d\ua64e\ua64f\ua650\ua651\ua652\ua653\ua654\ua655\ua656\ua657\ua658\ua659\ua65a\ua65b\ua65c\ua65d\ua65e\ua65f\ua660\ua661\ua662\ua663\ua664\ua665\ua666\ua667\ua668\ua669\ua66a\ua66b\ua66c\ua66d\ua66e\ua66f\ua670\ua671\ua672\ua673\ua674\ua675\ua676\ua677\ua678\ua679\ua67a\ua67b\ua67c\ua67d\ua67e\ua67f\ua680\ua681\ua682\ua683\ua684\ua685\ua686\ua687\ua688\ua689\ua68a\ua68b\ua68c\ua68d\ua68e\ua68f\ua690\ua691\ua692\ua693\ua694\ua695\ua696\ua697\ua698\ua699\ua69a\ua69b\ua69c\ua69d\ua69e\ua69f\ua6a0\ua6a1\ua6a2\ua6a3\ua6a4\ua6a5\ua6a6\ua6a7\ua6a8\ua6a9\ua6aa\ua6ab\ua6ac\ua6ad\ua6ae\ua6af\ua6b0\ua6b1\ua6b2\ua6b3\ua6b4\ua6b5\ua6b6\ua6b7\ua6b8\ua6b9\ua6ba\ua6bb\ua6bc\ua6bd\ua6be\ua6bf\ua6c0\ua6c1\ua6c2\ua6c3\ua6c4\ua6c5\ua6c6\ua6c7\ua6c8\ua6c9\ua6ca\ua6cb\ua6cc\ua6cd\ua6ce\ua6cf\ua6d0\ua6d1\ua6d2\ua6d3\ua6d4\ua6d5\ua6d6\ua6d7\ua6d8\ua6d9\ua6da\ua6db\ua6dc\ua6dd\ua6de\ua6df\ua6e0\ua6e1\ua6e2\ua6e3\ua6e4\ua6e5\ua6e6\ua6e7\ua6e8\ua6e9\ua6ea\ua6eb\ua6ec\ua6ed\ua6ee\ua6ef\ua6f0\ua6f1\ua6f2\ua6f3\ua6f4\ua6f5\ua6f6\ua6f7\ua6f8\ua6f9\ua6fa\ua6fb\ua6fc\ua6fd\ua6fe\ua6ff\ua717\ua718\ua719\ua71a\ua71b\ua71c\ua71d\ua71e\ua71f\ua720\ua721\ua722\ua723\ua724\ua725\ua726\ua727\ua728\ua729\ua72a\ua72b\ua72c\ua72d\ua72e\ua72f\ua730\ua731\ua732\ua733\ua734\ua735\ua736\ua737\ua738\ua739\ua73a\ua73b\ua73c\ua73d\ua73e\ua73f\ua740\ua741\ua742\ua743\ua744\ua745\ua746\ua747\ua748\ua749\ua74a\ua74b\ua74c\ua74d\ua74e\ua74f\ua750\ua751\ua752\ua753\ua754\ua755\ua756\ua757\ua758\ua759\ua75a\ua75b\ua75c\ua75d\ua75e\ua75f\ua760\ua761\ua762\ua763\ua764\ua765\ua766\ua767\ua768\ua769\ua76a\ua76b\ua76c\ua76d\ua76e\ua76f\ua770\ua771\ua772\ua773\ua774\ua775\ua776\ua777\ua778\ua779\ua77a\ua77b\ua77c\ua77d\ua77e\ua77f\ua780\ua781\ua782\ua783\ua784\ua785\ua786\ua787\ua788\ua789\ua78a\ua78b\ua78c\ua78d\ua78e\ua78f\ua790\ua791\ua792\ua793\ua794\ua795\ua796\ua797\ua798\ua799\ua79a\ua79b\ua79c\ua79d\ua79e\ua79f\ua7a0\ua7a1\ua7a2\ua7a3\ua7a4\ua7a5\ua7a6\ua7a7\ua7a8\ua7a9\ua7aa\ua7ab\ua7ac\ua7ad\ua7ae\ua7af\ua7b0\ua7b1\ua7b2\ua7b3\ua7b4\ua7b5\ua7b6\ua7b7\ua7b8\ua7b9\ua7ba\ua7bb\ua7bc\ua7bd\ua7be\ua7bf\ua7c0\ua7c1\ua7c2\ua7c3\ua7c4\ua7c5\ua7c6\ua7c7\ua7c8\ua7c9\ua7ca\ua7cb\ua7cc\ua7cd\ua7ce\ua7cf\ua7d0\ua7d1\ua7d2\ua7d3\ua7d4\ua7d5\ua7d6\ua7d7\ua7d8\ua7d9\ua7da\ua7db\ua7dc\ua7dd\ua7de\ua7df\ua7e0\ua7e1\ua7e2\ua7e3\ua7e4\ua7e5\ua7e6\ua7e7\ua7e8\ua7e9\ua7ea\ua7eb\ua7ec\ua7ed\ua7ee\ua7ef\ua7f0\ua7f1\ua7f2\ua7f3\ua7f4\ua7f5\ua7f6\ua7f7\ua7f8\ua7f9\ua7fa\ua7fb\ua7fc\ua7fd\ua7fe\ua7ff\ua82c\ua82d\ua82e\ua82f\ua830\ua831\ua832\ua833\ua834\ua835\ua836\ua837\ua838\ua839\ua83a\ua83b\ua83c\ua83d\ua83e\ua83f\ua840\ua841\ua842\ua843\ua844\ua845\ua846\ua847\ua848\ua849\ua84a\ua84b\ua84c\ua84d\ua84e\ua84f\ua850\ua851\ua852\ua853\ua854\ua855\ua856\ua857\ua858\ua859\ua85a\ua85b\ua85c\ua85d\ua85e\ua85f\ua860\ua861\ua862\ua863\ua864\ua865\ua866\ua867\ua868\ua869\ua86a\ua86b\ua86c\ua86d\ua86e\ua86f\ua870\ua871\ua872\ua873\ua874\ua875\ua876\ua877\ua878\ua879\ua87a\ua87b\ua87c\ua87d\ua87e\ua87f\ua880\ua881\ua882\ua883\ua884\ua885\ua886\ua887\ua888\ua889\ua88a\ua88b\ua88c\ua88d\ua88e\ua88f\ua890\ua891\ua892\ua893\ua894\ua895\ua896\ua897\ua898\ua899\ua89a\ua89b\ua89c\ua89d\ua89e\ua89f\ua8a0\ua8a1\ua8a2\ua8a3\ua8a4\ua8a5\ua8a6\ua8a7\ua8a8\ua8a9\ua8aa\ua8ab\ua8ac\ua8ad\ua8ae\ua8af\ua8b0\ua8b1\ua8b2\ua8b3\ua8b4\ua8b5\ua8b6\ua8b7\ua8b8\ua8b9\ua8ba\ua8bb\ua8bc\ua8bd\ua8be\ua8bf\ua8c0\ua8c1\ua8c2\ua8c3\ua8c4\ua8c5\ua8c6\ua8c7\ua8c8\ua8c9\ua8ca\ua8cb\ua8cc\ua8cd\ua8ce\ua8cf\ua8d0\ua8d1\ua8d2\ua8d3\ua8d4\ua8d5\ua8d6\ua8d7\ua8d8\ua8d9\ua8da\ua8db\ua8dc\ua8dd\ua8de\ua8df\ua8e0\ua8e1\ua8e2\ua8e3\ua8e4\ua8e5\ua8e6\ua8e7\ua8e8\ua8e9\ua8ea\ua8eb\ua8ec\ua8ed\ua8ee\ua8ef\ua8f0\ua8f1\ua8f2\ua8f3\ua8f4\ua8f5\ua8f6\ua8f7\ua8f8\ua8f9\ua8fa\ua8fb\ua8fc\ua8fd\ua8fe\ua8ff\ua900\ua901\ua902\ua903\ua904\ua905\ua906\ua907\ua908\ua909\ua90a\ua90b\ua90c\ua90d\ua90e\ua90f\ua910\ua911\ua912\ua913\ua914\ua915\ua916\ua917\ua918\ua919\ua91a\ua91b\ua91c\ua91d\ua91e\ua91f\ua920\ua921\ua922\ua923\ua924\ua925\ua926\ua927\ua928\ua929\ua92a\ua92b\ua92c\ua92d\ua92e\ua92f\ua930\ua931\ua932\ua933\ua934\ua935\ua936\ua937\ua938\ua939\ua93a\ua93b\ua93c\ua93d\ua93e\ua93f\ua940\ua941\ua942\ua943\ua944\ua945\ua946\ua947\ua948\ua949\ua94a\ua94b\ua94c\ua94d\ua94e\ua94f\ua950\ua951\ua952\ua953\ua954\ua955\ua956\ua957\ua958\ua959\ua95a\ua95b\ua95c\ua95d\ua95e\ua95f\ua960\ua961\ua962\ua963\ua964\ua965\ua966\ua967\ua968\ua969\ua96a\ua96b\ua96c\ua96d\ua96e\ua96f\ua970\ua971\ua972\ua973\ua974\ua975\ua976\ua977\ua978\ua979\ua97a\ua97b\ua97c\ua97d\ua97e\ua97f\ua980\ua981\ua982\ua983\ua984\ua985\ua986\ua987\ua988\ua989\ua98a\ua98b\ua98c\ua98d\ua98e\ua98f\ua990\ua991\ua992\ua993\ua994\ua995\ua996\ua997\ua998\ua999\ua99a\ua99b\ua99c\ua99d\ua99e\ua99f\ua9a0\ua9a1\ua9a2\ua9a3\ua9a4\ua9a5\ua9a6\ua9a7\ua9a8\ua9a9\ua9aa\ua9ab\ua9ac\ua9ad\ua9ae\ua9af\ua9b0\ua9b1\ua9b2\ua9b3\ua9b4\ua9b5\ua9b6\ua9b7\ua9b8\ua9b9\ua9ba\ua9bb\ua9bc\ua9bd\ua9be\ua9bf\ua9c0\ua9c1\ua9c2\ua9c3\ua9c4\ua9c5\ua9c6\ua9c7\ua9c8\ua9c9\ua9ca\ua9cb\ua9cc\ua9cd\ua9ce\ua9cf\ua9d0\ua9d1\ua9d2\ua9d3\ua9d4\ua9d5\ua9d6\ua9d7\ua9d8\ua9d9\ua9da\ua9db\ua9dc\ua9dd\ua9de\ua9df\ua9e0\ua9e1\ua9e2\ua9e3\ua9e4\ua9e5\ua9e6\ua9e7\ua9e8\ua9e9\ua9ea\ua9eb\ua9ec\ua9ed\ua9ee\ua9ef\ua9f0\ua9f1\ua9f2\ua9f3\ua9f4\ua9f5\ua9f6\ua9f7\ua9f8\ua9f9\ua9fa\ua9fb\ua9fc\ua9fd\ua9fe\ua9ff\uaa00\uaa01\uaa02\uaa03\uaa04\uaa05\uaa06\uaa07\uaa08\uaa09\uaa0a\uaa0b\uaa0c\uaa0d\uaa0e\uaa0f\uaa10\uaa11\uaa12\uaa13\uaa14\uaa15\uaa16\uaa17\uaa18\uaa19\uaa1a\uaa1b\uaa1c\uaa1d\uaa1e\uaa1f\uaa20\uaa21\uaa22\uaa23\uaa24\uaa25\uaa26\uaa27\uaa28\uaa29\uaa2a\uaa2b\uaa2c\uaa2d\uaa2e\uaa2f\uaa30\uaa31\uaa32\uaa33\uaa34\uaa35\uaa36\uaa37\uaa38\uaa39\uaa3a\uaa3b\uaa3c\uaa3d\uaa3e\uaa3f\uaa40\uaa41\uaa42\uaa43\uaa44\uaa45\uaa46\uaa47\uaa48\uaa49\uaa4a\uaa4b\uaa4c\uaa4d\uaa4e\uaa4f\uaa50\uaa51\uaa52\uaa53\uaa54\uaa55\uaa56\uaa57\uaa58\uaa59\uaa5a\uaa5b\uaa5c\uaa5d\uaa5e\uaa5f\uaa60\uaa61\uaa62\uaa63\uaa64\uaa65\uaa66\uaa67\uaa68\uaa69\uaa6a\uaa6b\uaa6c\uaa6d\uaa6e\uaa6f\uaa70\uaa71\uaa72\uaa73\uaa74\uaa75\uaa76\uaa77\uaa78\uaa79\uaa7a\uaa7b\uaa7c\uaa7d\uaa7e\uaa7f\uaa80\uaa81\uaa82\uaa83\uaa84\uaa85\uaa86\uaa87\uaa88\uaa89\uaa8a\uaa8b\uaa8c\uaa8d\uaa8e\uaa8f\uaa90\uaa91\uaa92\uaa93\uaa94\uaa95\uaa96\uaa97\uaa98\uaa99\uaa9a\uaa9b\uaa9c\uaa9d\uaa9e\uaa9f\uaaa0\uaaa1\uaaa2\uaaa3\uaaa4\uaaa5\uaaa6\uaaa7\uaaa8\uaaa9\uaaaa\uaaab\uaaac\uaaad\uaaae\uaaaf\uaab0\uaab1\uaab2\uaab3\uaab4\uaab5\uaab6\uaab7\uaab8\uaab9\uaaba\uaabb\uaabc\uaabd\uaabe\uaabf\uaac0\uaac1\uaac2\uaac3\uaac4\uaac5\uaac6\uaac7\uaac8\uaac9\uaaca\uaacb\uaacc\uaacd\uaace\uaacf\uaad0\uaad1\uaad2\uaad3\uaad4\uaad5\uaad6\uaad7\uaad8\uaad9\uaada\uaadb\uaadc\uaadd\uaade\uaadf\uaae0\uaae1\uaae2\uaae3\uaae4\uaae5\uaae6\uaae7\uaae8\uaae9\uaaea\uaaeb\uaaec\uaaed\uaaee\uaaef\uaaf0\uaaf1\uaaf2\uaaf3\uaaf4\uaaf5\uaaf6\uaaf7\uaaf8\uaaf9\uaafa\uaafb\uaafc\uaafd\uaafe\uaaff\uab00\uab01\uab02\uab03\uab04\uab05\uab06\uab07\uab08\uab09\uab0a\uab0b\uab0c\uab0d\uab0e\uab0f\uab10\uab11\uab12\uab13\uab14\uab15\uab16\uab17\uab18\uab19\uab1a\uab1b\uab1c\uab1d\uab1e\uab1f\uab20\uab21\uab22\uab23\uab24\uab25\uab26\uab27\uab28\uab29\uab2a\uab2b\uab2c\uab2d\uab2e\uab2f\uab30\uab31\uab32\uab33\uab34\uab35\uab36\uab37\uab38\uab39\uab3a\uab3b\uab3c\uab3d\uab3e\uab3f\uab40\uab41\uab42\uab43\uab44\uab45\uab46\uab47\uab48\uab49\uab4a\uab4b\uab4c\uab4d\uab4e\uab4f\uab50\uab51\uab52\uab53\uab54\uab55\uab56\uab57\uab58\uab59\uab5a\uab5b\uab5c\uab5d\uab5e\uab5f\uab60\uab61\uab62\uab63\uab64\uab65\uab66\uab67\uab68\uab69\uab6a\uab6b\uab6c\uab6d\uab6e\uab6f\uab70\uab71\uab72\uab73\uab74\uab75\uab76\uab77\uab78\uab79\uab7a\uab7b\uab7c\uab7d\uab7e\uab7f\uab80\uab81\uab82\uab83\uab84\uab85\uab86\uab87\uab88\uab89\uab8a\uab8b\uab8c\uab8d\uab8e\uab8f\uab90\uab91\uab92\uab93\uab94\uab95\uab96\uab97\uab98\uab99\uab9a\uab9b\uab9c\uab9d\uab9e\uab9f\uaba0\uaba1\uaba2\uaba3\uaba4\uaba5\uaba6\uaba7\uaba8\uaba9\uabaa\uabab\uabac\uabad\uabae\uabaf\uabb0\uabb1\uabb2\uabb3\uabb4\uabb5\uabb6\uabb7\uabb8\uabb9\uabba\uabbb\uabbc\uabbd\uabbe\uabbf\uabc0\uabc1\uabc2\uabc3\uabc4\uabc5\uabc6\uabc7\uabc8\uabc9\uabca\uabcb\uabcc\uabcd\uabce\uabcf\uabd0\uabd1\uabd2\uabd3\uabd4\uabd5\uabd6\uabd7\uabd8\uabd9\uabda\uabdb\uabdc\uabdd\uabde\uabdf\uabe0\uabe1\uabe2\uabe3\uabe4\uabe5\uabe6\uabe7\uabe8\uabe9\uabea\uabeb\uabec\uabed\uabee\uabef\uabf0\uabf1\uabf2\uabf3\uabf4\uabf5\uabf6\uabf7\uabf8\uabf9\uabfa\uabfb\uabfc\uabfd\uabfe\uabff\ud7a4\ud7a5\ud7a6\ud7a7\ud7a8\ud7a9\ud7aa\ud7ab\ud7ac\ud7ad\ud7ae\ud7af\ud7b0\ud7b1\ud7b2\ud7b3\ud7b4\ud7b5\ud7b6\ud7b7\ud7b8\ud7b9\ud7ba\ud7bb\ud7bc\ud7bd\ud7be\ud7bf\ud7c0\ud7c1\ud7c2\ud7c3\ud7c4\ud7c5\ud7c6\ud7c7\ud7c8\ud7c9\ud7ca\ud7cb\ud7cc\ud7cd\ud7ce\ud7cf\ud7d0\ud7d1\ud7d2\ud7d3\ud7d4\ud7d5\ud7d6\ud7d7\ud7d8\ud7d9\ud7da\ud7db\ud7dc\ud7dd\ud7de\ud7df\ud7e0\ud7e1\ud7e2\ud7e3\ud7e4\ud7e5\ud7e6\ud7e7\ud7e8\ud7e9\ud7ea\ud7eb\ud7ec\ud7ed\ud7ee\ud7ef\ud7f0\ud7f1\ud7f2\ud7f3\ud7f4\ud7f5\ud7f6\ud7f7\ud7f8\ud7f9\ud7fa\ud7fb\ud7fc\ud7fd\ud7fe\ud7ff\ufa2e\ufa2f\ufa6b\ufa6c\ufa6d\ufa6e\ufa6f\ufada\ufadb\ufadc\ufadd\ufade\ufadf\ufae0\ufae1\ufae2\ufae3\ufae4\ufae5\ufae6\ufae7\ufae8\ufae9\ufaea\ufaeb\ufaec\ufaed\ufaee\ufaef\ufaf0\ufaf1\ufaf2\ufaf3\ufaf4\ufaf5\ufaf6\ufaf7\ufaf8\ufaf9\ufafa\ufafb\ufafc\ufafd\ufafe\ufaff\ufb07\ufb08\ufb09\ufb0a\ufb0b\ufb0c\ufb0d\ufb0e\ufb0f\ufb10\ufb11\ufb12\ufb18\ufb19\ufb1a\ufb1b\ufb1c\ufb37\ufb3d\ufb3f\ufb42\ufb45\ufbb2\ufbb3\ufbb4\ufbb5\ufbb6\ufbb7\ufbb8\ufbb9\ufbba\ufbbb\ufbbc\ufbbd\ufbbe\ufbbf\ufbc0\ufbc1\ufbc2\ufbc3\ufbc4\ufbc5\ufbc6\ufbc7\ufbc8\ufbc9\ufbca\ufbcb\ufbcc\ufbcd\ufbce\ufbcf\ufbd0\ufbd1\ufbd2\ufd40\ufd41\ufd42\ufd43\ufd44\ufd45\ufd46\ufd47\ufd48\ufd49\ufd4a\ufd4b\ufd4c\ufd4d\ufd4e\ufd4f\ufd90\ufd91\ufdc8\ufdc9\ufdca\ufdcb\ufdcc\ufdcd\ufdce\ufdcf\ufdd0\ufdd1\ufdd2\ufdd3\ufdd4\ufdd5\ufdd6\ufdd7\ufdd8\ufdd9\ufdda\ufddb\ufddc\ufddd\ufdde\ufddf\ufde0\ufde1\ufde2\ufde3\ufde4\ufde5\ufde6\ufde7\ufde8\ufde9\ufdea\ufdeb\ufdec\ufded\ufdee\ufdef\ufdfe\ufdff\ufe1a\ufe1b\ufe1c\ufe1d\ufe1e\ufe1f\ufe24\ufe25\ufe26\ufe27\ufe28\ufe29\ufe2a\ufe2b\ufe2c\ufe2d\ufe2e\ufe2f\ufe53\ufe67\ufe6c\ufe6d\ufe6e\ufe6f\ufe75\ufefd\ufefe\uff00\uffbf\uffc0\uffc1\uffc8\uffc9\uffd0\uffd1\uffd8\uffd9\uffdd\uffde\uffdf\uffe7\uffef\ufff0\ufff1\ufff2\ufff3\ufff4\ufff5\ufff6\ufff7\ufff8\ufffe'
-
-Co = u'\ue000\ue001\ue002\ue003\ue004\ue005\ue006\ue007\ue008\ue009\ue00a\ue00b\ue00c\ue00d\ue00e\ue00f\ue010\ue011\ue012\ue013\ue014\ue015\ue016\ue017\ue018\ue019\ue01a\ue01b\ue01c\ue01d\ue01e\ue01f\ue020\ue021\ue022\ue023\ue024\ue025\ue026\ue027\ue028\ue029\ue02a\ue02b\ue02c\ue02d\ue02e\ue02f\ue030\ue031\ue032\ue033\ue034\ue035\ue036\ue037\ue038\ue039\ue03a\ue03b\ue03c\ue03d\ue03e\ue03f\ue040\ue041\ue042\ue043\ue044\ue045\ue046\ue047\ue048\ue049\ue04a\ue04b\ue04c\ue04d\ue04e\ue04f\ue050\ue051\ue052\ue053\ue054\ue055\ue056\ue057\ue058\ue059\ue05a\ue05b\ue05c\ue05d\ue05e\ue05f\ue060\ue061\ue062\ue063\ue064\ue065\ue066\ue067\ue068\ue069\ue06a\ue06b\ue06c\ue06d\ue06e\ue06f\ue070\ue071\ue072\ue073\ue074\ue075\ue076\ue077\ue078\ue079\ue07a\ue07b\ue07c\ue07d\ue07e\ue07f\ue080\ue081\ue082\ue083\ue084\ue085\ue086\ue087\ue088\ue089\ue08a\ue08b\ue08c\ue08d\ue08e\ue08f\ue090\ue091\ue092\ue093\ue094\ue095\ue096\ue097\ue098\ue099\ue09a\ue09b\ue09c\ue09d\ue09e\ue09f\ue0a0\ue0a1\ue0a2\ue0a3\ue0a4\ue0a5\ue0a6\ue0a7\ue0a8\ue0a9\ue0aa\ue0ab\ue0ac\ue0ad\ue0ae\ue0af\ue0b0\ue0b1\ue0b2\ue0b3\ue0b4\ue0b5\ue0b6\ue0b7\ue0b8\ue0b9\ue0ba\ue0bb\ue0bc\ue0bd\ue0be\ue0bf\ue0c0\ue0c1\ue0c2\ue0c3\ue0c4\ue0c5\ue0c6\ue0c7\ue0c8\ue0c9\ue0ca\ue0cb\ue0cc\ue0cd\ue0ce\ue0cf\ue0d0\ue0d1\ue0d2\ue0d3\ue0d4\ue0d5\ue0d6\ue0d7\ue0d8\ue0d9\ue0da\ue0db\ue0dc\ue0dd\ue0de\ue0df\ue0e0\ue0e1\ue0e2\ue0e3\ue0e4\ue0e5\ue0e6\ue0e7\ue0e8\ue0e9\ue0ea\ue0eb\ue0ec\ue0ed\ue0ee\ue0ef\ue0f0\ue0f1\ue0f2\ue0f3\ue0f4\ue0f5\ue0f6\ue0f7\ue0f8\ue0f9\ue0fa\ue0fb\ue0fc\ue0fd\ue0fe\ue0ff\ue100\ue101\ue102\ue103\ue104\ue105\ue106\ue107\ue108\ue109\ue10a\ue10b\ue10c\ue10d\ue10e\ue10f\ue110\ue111\ue112\ue113\ue114\ue115\ue116\ue117\ue118\ue119\ue11a\ue11b\ue11c\ue11d\ue11e\ue11f\ue120\ue121\ue122\ue123\ue124\ue125\ue126\ue127\ue128\ue129\ue12a\ue12b\ue12c\ue12d\ue12e\ue12f\ue130\ue131\ue132\ue133\ue134\ue135\ue136\ue137\ue138\ue139\ue13a\ue13b\ue13c\ue13d\ue13e\ue13f\ue140\ue141\ue142\ue143\ue144\ue145\ue146\ue147\ue148\ue149\ue14a\ue14b\ue14c\ue14d\ue14e\ue14f\ue150\ue151\ue152\ue153\ue154\ue155\ue156\ue157\ue158\ue159\ue15a\ue15b\ue15c\ue15d\ue15e\ue15f\ue160\ue161\ue162\ue163\ue164\ue165\ue166\ue167\ue168\ue169\ue16a\ue16b\ue16c\ue16d\ue16e\ue16f\ue170\ue171\ue172\ue173\ue174\ue175\ue176\ue177\ue178\ue179\ue17a\ue17b\ue17c\ue17d\ue17e\ue17f\ue180\ue181\ue182\ue183\ue184\ue185\ue186\ue187\ue188\ue189\ue18a\ue18b\ue18c\ue18d\ue18e\ue18f\ue190\ue191\ue192\ue193\ue194\ue195\ue196\ue197\ue198\ue199\ue19a\ue19b\ue19c\ue19d\ue19e\ue19f\ue1a0\ue1a1\ue1a2\ue1a3\ue1a4\ue1a5\ue1a6\ue1a7\ue1a8\ue1a9\ue1aa\ue1ab\ue1ac\ue1ad\ue1ae\ue1af\ue1b0\ue1b1\ue1b2\ue1b3\ue1b4\ue1b5\ue1b6\ue1b7\ue1b8\ue1b9\ue1ba\ue1bb\ue1bc\ue1bd\ue1be\ue1bf\ue1c0\ue1c1\ue1c2\ue1c3\ue1c4\ue1c5\ue1c6\ue1c7\ue1c8\ue1c9\ue1ca\ue1cb\ue1cc\ue1cd\ue1ce\ue1cf\ue1d0\ue1d1\ue1d2\ue1d3\ue1d4\ue1d5\ue1d6\ue1d7\ue1d8\ue1d9\ue1da\ue1db\ue1dc\ue1dd\ue1de\ue1df\ue1e0\ue1e1\ue1e2\ue1e3\ue1e4\ue1e5\ue1e6\ue1e7\ue1e8\ue1e9\ue1ea\ue1eb\ue1ec\ue1ed\ue1ee\ue1ef\ue1f0\ue1f1\ue1f2\ue1f3\ue1f4\ue1f5\ue1f6\ue1f7\ue1f8\ue1f9\ue1fa\ue1fb\ue1fc\ue1fd\ue1fe\ue1ff\ue200\ue201\ue202\ue203\ue204\ue205\ue206\ue207\ue208\ue209\ue20a\ue20b\ue20c\ue20d\ue20e\ue20f\ue210\ue211\ue212\ue213\ue214\ue215\ue216\ue217\ue218\ue219\ue21a\ue21b\ue21c\ue21d\ue21e\ue21f\ue220\ue221\ue222\ue223\ue224\ue225\ue226\ue227\ue228\ue229\ue22a\ue22b\ue22c\ue22d\ue22e\ue22f\ue230\ue231\ue232\ue233\ue234\ue235\ue236\ue237\ue238\ue239\ue23a\ue23b\ue23c\ue23d\ue23e\ue23f\ue240\ue241\ue242\ue243\ue244\ue245\ue246\ue247\ue248\ue249\ue24a\ue24b\ue24c\ue24d\ue24e\ue24f\ue250\ue251\ue252\ue253\ue254\ue255\ue256\ue257\ue258\ue259\ue25a\ue25b\ue25c\ue25d\ue25e\ue25f\ue260\ue261\ue262\ue263\ue264\ue265\ue266\ue267\ue268\ue269\ue26a\ue26b\ue26c\ue26d\ue26e\ue26f\ue270\ue271\ue272\ue273\ue274\ue275\ue276\ue277\ue278\ue279\ue27a\ue27b\ue27c\ue27d\ue27e\ue27f\ue280\ue281\ue282\ue283\ue284\ue285\ue286\ue287\ue288\ue289\ue28a\ue28b\ue28c\ue28d\ue28e\ue28f\ue290\ue291\ue292\ue293\ue294\ue295\ue296\ue297\ue298\ue299\ue29a\ue29b\ue29c\ue29d\ue29e\ue29f\ue2a0\ue2a1\ue2a2\ue2a3\ue2a4\ue2a5\ue2a6\ue2a7\ue2a8\ue2a9\ue2aa\ue2ab\ue2ac\ue2ad\ue2ae\ue2af\ue2b0\ue2b1\ue2b2\ue2b3\ue2b4\ue2b5\ue2b6\ue2b7\ue2b8\ue2b9\ue2ba\ue2bb\ue2bc\ue2bd\ue2be\ue2bf\ue2c0\ue2c1\ue2c2\ue2c3\ue2c4\ue2c5\ue2c6\ue2c7\ue2c8\ue2c9\ue2ca\ue2cb\ue2cc\ue2cd\ue2ce\ue2cf\ue2d0\ue2d1\ue2d2\ue2d3\ue2d4\ue2d5\ue2d6\ue2d7\ue2d8\ue2d9\ue2da\ue2db\ue2dc\ue2dd\ue2de\ue2df\ue2e0\ue2e1\ue2e2\ue2e3\ue2e4\ue2e5\ue2e6\ue2e7\ue2e8\ue2e9\ue2ea\ue2eb\ue2ec\ue2ed\ue2ee\ue2ef\ue2f0\ue2f1\ue2f2\ue2f3\ue2f4\ue2f5\ue2f6\ue2f7\ue2f8\ue2f9\ue2fa\ue2fb\ue2fc\ue2fd\ue2fe\ue2ff\ue300\ue301\ue302\ue303\ue304\ue305\ue306\ue307\ue308\ue309\ue30a\ue30b\ue30c\ue30d\ue30e\ue30f\ue310\ue311\ue312\ue313\ue314\ue315\ue316\ue317\ue318\ue319\ue31a\ue31b\ue31c\ue31d\ue31e\ue31f\ue320\ue321\ue322\ue323\ue324\ue325\ue326\ue327\ue328\ue329\ue32a\ue32b\ue32c\ue32d\ue32e\ue32f\ue330\ue331\ue332\ue333\ue334\ue335\ue336\ue337\ue338\ue339\ue33a\ue33b\ue33c\ue33d\ue33e\ue33f\ue340\ue341\ue342\ue343\ue344\ue345\ue346\ue347\ue348\ue349\ue34a\ue34b\ue34c\ue34d\ue34e\ue34f\ue350\ue351\ue352\ue353\ue354\ue355\ue356\ue357\ue358\ue359\ue35a\ue35b\ue35c\ue35d\ue35e\ue35f\ue360\ue361\ue362\ue363\ue364\ue365\ue366\ue367\ue368\ue369\ue36a\ue36b\ue36c\ue36d\ue36e\ue36f\ue370\ue371\ue372\ue373\ue374\ue375\ue376\ue377\ue378\ue379\ue37a\ue37b\ue37c\ue37d\ue37e\ue37f\ue380\ue381\ue382\ue383\ue384\ue385\ue386\ue387\ue388\ue389\ue38a\ue38b\ue38c\ue38d\ue38e\ue38f\ue390\ue391\ue392\ue393\ue394\ue395\ue396\ue397\ue398\ue399\ue39a\ue39b\ue39c\ue39d\ue39e\ue39f\ue3a0\ue3a1\ue3a2\ue3a3\ue3a4\ue3a5\ue3a6\ue3a7\ue3a8\ue3a9\ue3aa\ue3ab\ue3ac\ue3ad\ue3ae\ue3af\ue3b0\ue3b1\ue3b2\ue3b3\ue3b4\ue3b5\ue3b6\ue3b7\ue3b8\ue3b9\ue3ba\ue3bb\ue3bc\ue3bd\ue3be\ue3bf\ue3c0\ue3c1\ue3c2\ue3c3\ue3c4\ue3c5\ue3c6\ue3c7\ue3c8\ue3c9\ue3ca\ue3cb\ue3cc\ue3cd\ue3ce\ue3cf\ue3d0\ue3d1\ue3d2\ue3d3\ue3d4\ue3d5\ue3d6\ue3d7\ue3d8\ue3d9\ue3da\ue3db\ue3dc\ue3dd\ue3de\ue3df\ue3e0\ue3e1\ue3e2\ue3e3\ue3e4\ue3e5\ue3e6\ue3e7\ue3e8\ue3e9\ue3ea\ue3eb\ue3ec\ue3ed\ue3ee\ue3ef\ue3f0\ue3f1\ue3f2\ue3f3\ue3f4\ue3f5\ue3f6\ue3f7\ue3f8\ue3f9\ue3fa\ue3fb\ue3fc\ue3fd\ue3fe\ue3ff\ue400\ue401\ue402\ue403\ue404\ue405\ue406\ue407\ue408\ue409\ue40a\ue40b\ue40c\ue40d\ue40e\ue40f\ue410\ue411\ue412\ue413\ue414\ue415\ue416\ue417\ue418\ue419\ue41a\ue41b\ue41c\ue41d\ue41e\ue41f\ue420\ue421\ue422\ue423\ue424\ue425\ue426\ue427\ue428\ue429\ue42a\ue42b\ue42c\ue42d\ue42e\ue42f\ue430\ue431\ue432\ue433\ue434\ue435\ue436\ue437\ue438\ue439\ue43a\ue43b\ue43c\ue43d\ue43e\ue43f\ue440\ue441\ue442\ue443\ue444\ue445\ue446\ue447\ue448\ue449\ue44a\ue44b\ue44c\ue44d\ue44e\ue44f\ue450\ue451\ue452\ue453\ue454\ue455\ue456\ue457\ue458\ue459\ue45a\ue45b\ue45c\ue45d\ue45e\ue45f\ue460\ue461\ue462\ue463\ue464\ue465\ue466\ue467\ue468\ue469\ue46a\ue46b\ue46c\ue46d\ue46e\ue46f\ue470\ue471\ue472\ue473\ue474\ue475\ue476\ue477\ue478\ue479\ue47a\ue47b\ue47c\ue47d\ue47e\ue47f\ue480\ue481\ue482\ue483\ue484\ue485\ue486\ue487\ue488\ue489\ue48a\ue48b\ue48c\ue48d\ue48e\ue48f\ue490\ue491\ue492\ue493\ue494\ue495\ue496\ue497\ue498\ue499\ue49a\ue49b\ue49c\ue49d\ue49e\ue49f\ue4a0\ue4a1\ue4a2\ue4a3\ue4a4\ue4a5\ue4a6\ue4a7\ue4a8\ue4a9\ue4aa\ue4ab\ue4ac\ue4ad\ue4ae\ue4af\ue4b0\ue4b1\ue4b2\ue4b3\ue4b4\ue4b5\ue4b6\ue4b7\ue4b8\ue4b9\ue4ba\ue4bb\ue4bc\ue4bd\ue4be\ue4bf\ue4c0\ue4c1\ue4c2\ue4c3\ue4c4\ue4c5\ue4c6\ue4c7\ue4c8\ue4c9\ue4ca\ue4cb\ue4cc\ue4cd\ue4ce\ue4cf\ue4d0\ue4d1\ue4d2\ue4d3\ue4d4\ue4d5\ue4d6\ue4d7\ue4d8\ue4d9\ue4da\ue4db\ue4dc\ue4dd\ue4de\ue4df\ue4e0\ue4e1\ue4e2\ue4e3\ue4e4\ue4e5\ue4e6\ue4e7\ue4e8\ue4e9\ue4ea\ue4eb\ue4ec\ue4ed\ue4ee\ue4ef\ue4f0\ue4f1\ue4f2\ue4f3\ue4f4\ue4f5\ue4f6\ue4f7\ue4f8\ue4f9\ue4fa\ue4fb\ue4fc\ue4fd\ue4fe\ue4ff\ue500\ue501\ue502\ue503\ue504\ue505\ue506\ue507\ue508\ue509\ue50a\ue50b\ue50c\ue50d\ue50e\ue50f\ue510\ue511\ue512\ue513\ue514\ue515\ue516\ue517\ue518\ue519\ue51a\ue51b\ue51c\ue51d\ue51e\ue51f\ue520\ue521\ue522\ue523\ue524\ue525\ue526\ue527\ue528\ue529\ue52a\ue52b\ue52c\ue52d\ue52e\ue52f\ue530\ue531\ue532\ue533\ue534\ue535\ue536\ue537\ue538\ue539\ue53a\ue53b\ue53c\ue53d\ue53e\ue53f\ue540\ue541\ue542\ue543\ue544\ue545\ue546\ue547\ue548\ue549\ue54a\ue54b\ue54c\ue54d\ue54e\ue54f\ue550\ue551\ue552\ue553\ue554\ue555\ue556\ue557\ue558\ue559\ue55a\ue55b\ue55c\ue55d\ue55e\ue55f\ue560\ue561\ue562\ue563\ue564\ue565\ue566\ue567\ue568\ue569\ue56a\ue56b\ue56c\ue56d\ue56e\ue56f\ue570\ue571\ue572\ue573\ue574\ue575\ue576\ue577\ue578\ue579\ue57a\ue57b\ue57c\ue57d\ue57e\ue57f\ue580\ue581\ue582\ue583\ue584\ue585\ue586\ue587\ue588\ue589\ue58a\ue58b\ue58c\ue58d\ue58e\ue58f\ue590\ue591\ue592\ue593\ue594\ue595\ue596\ue597\ue598\ue599\ue59a\ue59b\ue59c\ue59d\ue59e\ue59f\ue5a0\ue5a1\ue5a2\ue5a3\ue5a4\ue5a5\ue5a6\ue5a7\ue5a8\ue5a9\ue5aa\ue5ab\ue5ac\ue5ad\ue5ae\ue5af\ue5b0\ue5b1\ue5b2\ue5b3\ue5b4\ue5b5\ue5b6\ue5b7\ue5b8\ue5b9\ue5ba\ue5bb\ue5bc\ue5bd\ue5be\ue5bf\ue5c0\ue5c1\ue5c2\ue5c3\ue5c4\ue5c5\ue5c6\ue5c7\ue5c8\ue5c9\ue5ca\ue5cb\ue5cc\ue5cd\ue5ce\ue5cf\ue5d0\ue5d1\ue5d2\ue5d3\ue5d4\ue5d5\ue5d6\ue5d7\ue5d8\ue5d9\ue5da\ue5db\ue5dc\ue5dd\ue5de\ue5df\ue5e0\ue5e1\ue5e2\ue5e3\ue5e4\ue5e5\ue5e6\ue5e7\ue5e8\ue5e9\ue5ea\ue5eb\ue5ec\ue5ed\ue5ee\ue5ef\ue5f0\ue5f1\ue5f2\ue5f3\ue5f4\ue5f5\ue5f6\ue5f7\ue5f8\ue5f9\ue5fa\ue5fb\ue5fc\ue5fd\ue5fe\ue5ff\ue600\ue601\ue602\ue603\ue604\ue605\ue606\ue607\ue608\ue609\ue60a\ue60b\ue60c\ue60d\ue60e\ue60f\ue610\ue611\ue612\ue613\ue614\ue615\ue616\ue617\ue618\ue619\ue61a\ue61b\ue61c\ue61d\ue61e\ue61f\ue620\ue621\ue622\ue623\ue624\ue625\ue626\ue627\ue628\ue629\ue62a\ue62b\ue62c\ue62d\ue62e\ue62f\ue630\ue631\ue632\ue633\ue634\ue635\ue636\ue637\ue638\ue639\ue63a\ue63b\ue63c\ue63d\ue63e\ue63f\ue640\ue641\ue642\ue643\ue644\ue645\ue646\ue647\ue648\ue649\ue64a\ue64b\ue64c\ue64d\ue64e\ue64f\ue650\ue651\ue652\ue653\ue654\ue655\ue656\ue657\ue658\ue659\ue65a\ue65b\ue65c\ue65d\ue65e\ue65f\ue660\ue661\ue662\ue663\ue664\ue665\ue666\ue667\ue668\ue669\ue66a\ue66b\ue66c\ue66d\ue66e\ue66f\ue670\ue671\ue672\ue673\ue674\ue675\ue676\ue677\ue678\ue679\ue67a\ue67b\ue67c\ue67d\ue67e\ue67f\ue680\ue681\ue682\ue683\ue684\ue685\ue686\ue687\ue688\ue689\ue68a\ue68b\ue68c\ue68d\ue68e\ue68f\ue690\ue691\ue692\ue693\ue694\ue695\ue696\ue697\ue698\ue699\ue69a\ue69b\ue69c\ue69d\ue69e\ue69f\ue6a0\ue6a1\ue6a2\ue6a3\ue6a4\ue6a5\ue6a6\ue6a7\ue6a8\ue6a9\ue6aa\ue6ab\ue6ac\ue6ad\ue6ae\ue6af\ue6b0\ue6b1\ue6b2\ue6b3\ue6b4\ue6b5\ue6b6\ue6b7\ue6b8\ue6b9\ue6ba\ue6bb\ue6bc\ue6bd\ue6be\ue6bf\ue6c0\ue6c1\ue6c2\ue6c3\ue6c4\ue6c5\ue6c6\ue6c7\ue6c8\ue6c9\ue6ca\ue6cb\ue6cc\ue6cd\ue6ce\ue6cf\ue6d0\ue6d1\ue6d2\ue6d3\ue6d4\ue6d5\ue6d6\ue6d7\ue6d8\ue6d9\ue6da\ue6db\ue6dc\ue6dd\ue6de\ue6df\ue6e0\ue6e1\ue6e2\ue6e3\ue6e4\ue6e5\ue6e6\ue6e7\ue6e8\ue6e9\ue6ea\ue6eb\ue6ec\ue6ed\ue6ee\ue6ef\ue6f0\ue6f1\ue6f2\ue6f3\ue6f4\ue6f5\ue6f6\ue6f7\ue6f8\ue6f9\ue6fa\ue6fb\ue6fc\ue6fd\ue6fe\ue6ff\ue700\ue701\ue702\ue703\ue704\ue705\ue706\ue707\ue708\ue709\ue70a\ue70b\ue70c\ue70d\ue70e\ue70f\ue710\ue711\ue712\ue713\ue714\ue715\ue716\ue717\ue718\ue719\ue71a\ue71b\ue71c\ue71d\ue71e\ue71f\ue720\ue721\ue722\ue723\ue724\ue725\ue726\ue727\ue728\ue729\ue72a\ue72b\ue72c\ue72d\ue72e\ue72f\ue730\ue731\ue732\ue733\ue734\ue735\ue736\ue737\ue738\ue739\ue73a\ue73b\ue73c\ue73d\ue73e\ue73f\ue740\ue741\ue742\ue743\ue744\ue745\ue746\ue747\ue748\ue749\ue74a\ue74b\ue74c\ue74d\ue74e\ue74f\ue750\ue751\ue752\ue753\ue754\ue755\ue756\ue757\ue758\ue759\ue75a\ue75b\ue75c\ue75d\ue75e\ue75f\ue760\ue761\ue762\ue763\ue764\ue765\ue766\ue767\ue768\ue769\ue76a\ue76b\ue76c\ue76d\ue76e\ue76f\ue770\ue771\ue772\ue773\ue774\ue775\ue776\ue777\ue778\ue779\ue77a\ue77b\ue77c\ue77d\ue77e\ue77f\ue780\ue781\ue782\ue783\ue784\ue785\ue786\ue787\ue788\ue789\ue78a\ue78b\ue78c\ue78d\ue78e\ue78f\ue790\ue791\ue792\ue793\ue794\ue795\ue796\ue797\ue798\ue799\ue79a\ue79b\ue79c\ue79d\ue79e\ue79f\ue7a0\ue7a1\ue7a2\ue7a3\ue7a4\ue7a5\ue7a6\ue7a7\ue7a8\ue7a9\ue7aa\ue7ab\ue7ac\ue7ad\ue7ae\ue7af\ue7b0\ue7b1\ue7b2\ue7b3\ue7b4\ue7b5\ue7b6\ue7b7\ue7b8\ue7b9\ue7ba\ue7bb\ue7bc\ue7bd\ue7be\ue7bf\ue7c0\ue7c1\ue7c2\ue7c3\ue7c4\ue7c5\ue7c6\ue7c7\ue7c8\ue7c9\ue7ca\ue7cb\ue7cc\ue7cd\ue7ce\ue7cf\ue7d0\ue7d1\ue7d2\ue7d3\ue7d4\ue7d5\ue7d6\ue7d7\ue7d8\ue7d9\ue7da\ue7db\ue7dc\ue7dd\ue7de\ue7df\ue7e0\ue7e1\ue7e2\ue7e3\ue7e4\ue7e5\ue7e6\ue7e7\ue7e8\ue7e9\ue7ea\ue7eb\ue7ec\ue7ed\ue7ee\ue7ef\ue7f0\ue7f1\ue7f2\ue7f3\ue7f4\ue7f5\ue7f6\ue7f7\ue7f8\ue7f9\ue7fa\ue7fb\ue7fc\ue7fd\ue7fe\ue7ff\ue800\ue801\ue802\ue803\ue804\ue805\ue806\ue807\ue808\ue809\ue80a\ue80b\ue80c\ue80d\ue80e\ue80f\ue810\ue811\ue812\ue813\ue814\ue815\ue816\ue817\ue818\ue819\ue81a\ue81b\ue81c\ue81d\ue81e\ue81f\ue820\ue821\ue822\ue823\ue824\ue825\ue826\ue827\ue828\ue829\ue82a\ue82b\ue82c\ue82d\ue82e\ue82f\ue830\ue831\ue832\ue833\ue834\ue835\ue836\ue837\ue838\ue839\ue83a\ue83b\ue83c\ue83d\ue83e\ue83f\ue840\ue841\ue842\ue843\ue844\ue845\ue846\ue847\ue848\ue849\ue84a\ue84b\ue84c\ue84d\ue84e\ue84f\ue850\ue851\ue852\ue853\ue854\ue855\ue856\ue857\ue858\ue859\ue85a\ue85b\ue85c\ue85d\ue85e\ue85f\ue860\ue861\ue862\ue863\ue864\ue865\ue866\ue867\ue868\ue869\ue86a\ue86b\ue86c\ue86d\ue86e\ue86f\ue870\ue871\ue872\ue873\ue874\ue875\ue876\ue877\ue878\ue879\ue87a\ue87b\ue87c\ue87d\ue87e\ue87f\ue880\ue881\ue882\ue883\ue884\ue885\ue886\ue887\ue888\ue889\ue88a\ue88b\ue88c\ue88d\ue88e\ue88f\ue890\ue891\ue892\ue893\ue894\ue895\ue896\ue897\ue898\ue899\ue89a\ue89b\ue89c\ue89d\ue89e\ue89f\ue8a0\ue8a1\ue8a2\ue8a3\ue8a4\ue8a5\ue8a6\ue8a7\ue8a8\ue8a9\ue8aa\ue8ab\ue8ac\ue8ad\ue8ae\ue8af\ue8b0\ue8b1\ue8b2\ue8b3\ue8b4\ue8b5\ue8b6\ue8b7\ue8b8\ue8b9\ue8ba\ue8bb\ue8bc\ue8bd\ue8be\ue8bf\ue8c0\ue8c1\ue8c2\ue8c3\ue8c4\ue8c5\ue8c6\ue8c7\ue8c8\ue8c9\ue8ca\ue8cb\ue8cc\ue8cd\ue8ce\ue8cf\ue8d0\ue8d1\ue8d2\ue8d3\ue8d4\ue8d5\ue8d6\ue8d7\ue8d8\ue8d9\ue8da\ue8db\ue8dc\ue8dd\ue8de\ue8df\ue8e0\ue8e1\ue8e2\ue8e3\ue8e4\ue8e5\ue8e6\ue8e7\ue8e8\ue8e9\ue8ea\ue8eb\ue8ec\ue8ed\ue8ee\ue8ef\ue8f0\ue8f1\ue8f2\ue8f3\ue8f4\ue8f5\ue8f6\ue8f7\ue8f8\ue8f9\ue8fa\ue8fb\ue8fc\ue8fd\ue8fe\ue8ff\ue900\ue901\ue902\ue903\ue904\ue905\ue906\ue907\ue908\ue909\ue90a\ue90b\ue90c\ue90d\ue90e\ue90f\ue910\ue911\ue912\ue913\ue914\ue915\ue916\ue917\ue918\ue919\ue91a\ue91b\ue91c\ue91d\ue91e\ue91f\ue920\ue921\ue922\ue923\ue924\ue925\ue926\ue927\ue928\ue929\ue92a\ue92b\ue92c\ue92d\ue92e\ue92f\ue930\ue931\ue932\ue933\ue934\ue935\ue936\ue937\ue938\ue939\ue93a\ue93b\ue93c\ue93d\ue93e\ue93f\ue940\ue941\ue942\ue943\ue944\ue945\ue946\ue947\ue948\ue949\ue94a\ue94b\ue94c\ue94d\ue94e\ue94f\ue950\ue951\ue952\ue953\ue954\ue955\ue956\ue957\ue958\ue959\ue95a\ue95b\ue95c\ue95d\ue95e\ue95f\ue960\ue961\ue962\ue963\ue964\ue965\ue966\ue967\ue968\ue969\ue96a\ue96b\ue96c\ue96d\ue96e\ue96f\ue970\ue971\ue972\ue973\ue974\ue975\ue976\ue977\ue978\ue979\ue97a\ue97b\ue97c\ue97d\ue97e\ue97f\ue980\ue981\ue982\ue983\ue984\ue985\ue986\ue987\ue988\ue989\ue98a\ue98b\ue98c\ue98d\ue98e\ue98f\ue990\ue991\ue992\ue993\ue994\ue995\ue996\ue997\ue998\ue999\ue99a\ue99b\ue99c\ue99d\ue99e\ue99f\ue9a0\ue9a1\ue9a2\ue9a3\ue9a4\ue9a5\ue9a6\ue9a7\ue9a8\ue9a9\ue9aa\ue9ab\ue9ac\ue9ad\ue9ae\ue9af\ue9b0\ue9b1\ue9b2\ue9b3\ue9b4\ue9b5\ue9b6\ue9b7\ue9b8\ue9b9\ue9ba\ue9bb\ue9bc\ue9bd\ue9be\ue9bf\ue9c0\ue9c1\ue9c2\ue9c3\ue9c4\ue9c5\ue9c6\ue9c7\ue9c8\ue9c9\ue9ca\ue9cb\ue9cc\ue9cd\ue9ce\ue9cf\ue9d0\ue9d1\ue9d2\ue9d3\ue9d4\ue9d5\ue9d6\ue9d7\ue9d8\ue9d9\ue9da\ue9db\ue9dc\ue9dd\ue9de\ue9df\ue9e0\ue9e1\ue9e2\ue9e3\ue9e4\ue9e5\ue9e6\ue9e7\ue9e8\ue9e9\ue9ea\ue9eb\ue9ec\ue9ed\ue9ee\ue9ef\ue9f0\ue9f1\ue9f2\ue9f3\ue9f4\ue9f5\ue9f6\ue9f7\ue9f8\ue9f9\ue9fa\ue9fb\ue9fc\ue9fd\ue9fe\ue9ff\uea00\uea01\uea02\uea03\uea04\uea05\uea06\uea07\uea08\uea09\uea0a\uea0b\uea0c\uea0d\uea0e\uea0f\uea10\uea11\uea12\uea13\uea14\uea15\uea16\uea17\uea18\uea19\uea1a\uea1b\uea1c\uea1d\uea1e\uea1f\uea20\uea21\uea22\uea23\uea24\uea25\uea26\uea27\uea28\uea29\uea2a\uea2b\uea2c\uea2d\uea2e\uea2f\uea30\uea31\uea32\uea33\uea34\uea35\uea36\uea37\uea38\uea39\uea3a\uea3b\uea3c\uea3d\uea3e\uea3f\uea40\uea41\uea42\uea43\uea44\uea45\uea46\uea47\uea48\uea49\uea4a\uea4b\uea4c\uea4d\uea4e\uea4f\uea50\uea51\uea52\uea53\uea54\uea55\uea56\uea57\uea58\uea59\uea5a\uea5b\uea5c\uea5d\uea5e\uea5f\uea60\uea61\uea62\uea63\uea64\uea65\uea66\uea67\uea68\uea69\uea6a\uea6b\uea6c\uea6d\uea6e\uea6f\uea70\uea71\uea72\uea73\uea74\uea75\uea76\uea77\uea78\uea79\uea7a\uea7b\uea7c\uea7d\uea7e\uea7f\uea80\uea81\uea82\uea83\uea84\uea85\uea86\uea87\uea88\uea89\uea8a\uea8b\uea8c\uea8d\uea8e\uea8f\uea90\uea91\uea92\uea93\uea94\uea95\uea96\uea97\uea98\uea99\uea9a\uea9b\uea9c\uea9d\uea9e\uea9f\ueaa0\ueaa1\ueaa2\ueaa3\ueaa4\ueaa5\ueaa6\ueaa7\ueaa8\ueaa9\ueaaa\ueaab\ueaac\ueaad\ueaae\ueaaf\ueab0\ueab1\ueab2\ueab3\ueab4\ueab5\ueab6\ueab7\ueab8\ueab9\ueaba\ueabb\ueabc\ueabd\ueabe\ueabf\ueac0\ueac1\ueac2\ueac3\ueac4\ueac5\ueac6\ueac7\ueac8\ueac9\ueaca\ueacb\ueacc\ueacd\ueace\ueacf\uead0\uead1\uead2\uead3\uead4\uead5\uead6\uead7\uead8\uead9\ueada\ueadb\ueadc\ueadd\ueade\ueadf\ueae0\ueae1\ueae2\ueae3\ueae4\ueae5\ueae6\ueae7\ueae8\ueae9\ueaea\ueaeb\ueaec\ueaed\ueaee\ueaef\ueaf0\ueaf1\ueaf2\ueaf3\ueaf4\ueaf5\ueaf6\ueaf7\ueaf8\ueaf9\ueafa\ueafb\ueafc\ueafd\ueafe\ueaff\ueb00\ueb01\ueb02\ueb03\ueb04\ueb05\ueb06\ueb07\ueb08\ueb09\ueb0a\ueb0b\ueb0c\ueb0d\ueb0e\ueb0f\ueb10\ueb11\ueb12\ueb13\ueb14\ueb15\ueb16\ueb17\ueb18\ueb19\ueb1a\ueb1b\ueb1c\ueb1d\ueb1e\ueb1f\ueb20\ueb21\ueb22\ueb23\ueb24\ueb25\ueb26\ueb27\ueb28\ueb29\ueb2a\ueb2b\ueb2c\ueb2d\ueb2e\ueb2f\ueb30\ueb31\ueb32\ueb33\ueb34\ueb35\ueb36\ueb37\ueb38\ueb39\ueb3a\ueb3b\ueb3c\ueb3d\ueb3e\ueb3f\ueb40\ueb41\ueb42\ueb43\ueb44\ueb45\ueb46\ueb47\ueb48\ueb49\ueb4a\ueb4b\ueb4c\ueb4d\ueb4e\ueb4f\ueb50\ueb51\ueb52\ueb53\ueb54\ueb55\ueb56\ueb57\ueb58\ueb59\ueb5a\ueb5b\ueb5c\ueb5d\ueb5e\ueb5f\ueb60\ueb61\ueb62\ueb63\ueb64\ueb65\ueb66\ueb67\ueb68\ueb69\ueb6a\ueb6b\ueb6c\ueb6d\ueb6e\ueb6f\ueb70\ueb71\ueb72\ueb73\ueb74\ueb75\ueb76\ueb77\ueb78\ueb79\ueb7a\ueb7b\ueb7c\ueb7d\ueb7e\ueb7f\ueb80\ueb81\ueb82\ueb83\ueb84\ueb85\ueb86\ueb87\ueb88\ueb89\ueb8a\ueb8b\ueb8c\ueb8d\ueb8e\ueb8f\ueb90\ueb91\ueb92\ueb93\ueb94\ueb95\ueb96\ueb97\ueb98\ueb99\ueb9a\ueb9b\ueb9c\ueb9d\ueb9e\ueb9f\ueba0\ueba1\ueba2\ueba3\ueba4\ueba5\ueba6\ueba7\ueba8\ueba9\uebaa\uebab\uebac\uebad\uebae\uebaf\uebb0\uebb1\uebb2\uebb3\uebb4\uebb5\uebb6\uebb7\uebb8\uebb9\uebba\uebbb\uebbc\uebbd\uebbe\uebbf\uebc0\uebc1\uebc2\uebc3\uebc4\uebc5\uebc6\uebc7\uebc8\uebc9\uebca\uebcb\uebcc\uebcd\uebce\uebcf\uebd0\uebd1\uebd2\uebd3\uebd4\uebd5\uebd6\uebd7\uebd8\uebd9\uebda\uebdb\uebdc\uebdd\uebde\uebdf\uebe0\uebe1\uebe2\uebe3\uebe4\uebe5\uebe6\uebe7\uebe8\uebe9\uebea\uebeb\uebec\uebed\uebee\uebef\uebf0\uebf1\uebf2\uebf3\uebf4\uebf5\uebf6\uebf7\uebf8\uebf9\uebfa\uebfb\uebfc\uebfd\uebfe\uebff\uec00\uec01\uec02\uec03\uec04\uec05\uec06\uec07\uec08\uec09\uec0a\uec0b\uec0c\uec0d\uec0e\uec0f\uec10\uec11\uec12\uec13\uec14\uec15\uec16\uec17\uec18\uec19\uec1a\uec1b\uec1c\uec1d\uec1e\uec1f\uec20\uec21\uec22\uec23\uec24\uec25\uec26\uec27\uec28\uec29\uec2a\uec2b\uec2c\uec2d\uec2e\uec2f\uec30\uec31\uec32\uec33\uec34\uec35\uec36\uec37\uec38\uec39\uec3a\uec3b\uec3c\uec3d\uec3e\uec3f\uec40\uec41\uec42\uec43\uec44\uec45\uec46\uec47\uec48\uec49\uec4a\uec4b\uec4c\uec4d\uec4e\uec4f\uec50\uec51\uec52\uec53\uec54\uec55\uec56\uec57\uec58\uec59\uec5a\uec5b\uec5c\uec5d\uec5e\uec5f\uec60\uec61\uec62\uec63\uec64\uec65\uec66\uec67\uec68\uec69\uec6a\uec6b\uec6c\uec6d\uec6e\uec6f\uec70\uec71\uec72\uec73\uec74\uec75\uec76\uec77\uec78\uec79\uec7a\uec7b\uec7c\uec7d\uec7e\uec7f\uec80\uec81\uec82\uec83\uec84\uec85\uec86\uec87\uec88\uec89\uec8a\uec8b\uec8c\uec8d\uec8e\uec8f\uec90\uec91\uec92\uec93\uec94\uec95\uec96\uec97\uec98\uec99\uec9a\uec9b\uec9c\uec9d\uec9e\uec9f\ueca0\ueca1\ueca2\ueca3\ueca4\ueca5\ueca6\ueca7\ueca8\ueca9\uecaa\uecab\uecac\uecad\uecae\uecaf\uecb0\uecb1\uecb2\uecb3\uecb4\uecb5\uecb6\uecb7\uecb8\uecb9\uecba\uecbb\uecbc\uecbd\uecbe\uecbf\uecc0\uecc1\uecc2\uecc3\uecc4\uecc5\uecc6\uecc7\uecc8\uecc9\uecca\ueccb\ueccc\ueccd\uecce\ueccf\uecd0\uecd1\uecd2\uecd3\uecd4\uecd5\uecd6\uecd7\uecd8\uecd9\uecda\uecdb\uecdc\uecdd\uecde\uecdf\uece0\uece1\uece2\uece3\uece4\uece5\uece6\uece7\uece8\uece9\uecea\ueceb\uecec\ueced\uecee\uecef\uecf0\uecf1\uecf2\uecf3\uecf4\uecf5\uecf6\uecf7\uecf8\uecf9\uecfa\uecfb\uecfc\uecfd\uecfe\uecff\ued00\ued01\ued02\ued03\ued04\ued05\ued06\ued07\ued08\ued09\ued0a\ued0b\ued0c\ued0d\ued0e\ued0f\ued10\ued11\ued12\ued13\ued14\ued15\ued16\ued17\ued18\ued19\ued1a\ued1b\ued1c\ued1d\ued1e\ued1f\ued20\ued21\ued22\ued23\ued24\ued25\ued26\ued27\ued28\ued29\ued2a\ued2b\ued2c\ued2d\ued2e\ued2f\ued30\ued31\ued32\ued33\ued34\ued35\ued36\ued37\ued38\ued39\ued3a\ued3b\ued3c\ued3d\ued3e\ued3f\ued40\ued41\ued42\ued43\ued44\ued45\ued46\ued47\ued48\ued49\ued4a\ued4b\ued4c\ued4d\ued4e\ued4f\ued50\ued51\ued52\ued53\ued54\ued55\ued56\ued57\ued58\ued59\ued5a\ued5b\ued5c\ued5d\ued5e\ued5f\ued60\ued61\ued62\ued63\ued64\ued65\ued66\ued67\ued68\ued69\ued6a\ued6b\ued6c\ued6d\ued6e\ued6f\ued70\ued71\ued72\ued73\ued74\ued75\ued76\ued77\ued78\ued79\ued7a\ued7b\ued7c\ued7d\ued7e\ued7f\ued80\ued81\ued82\ued83\ued84\ued85\ued86\ued87\ued88\ued89\ued8a\ued8b\ued8c\ued8d\ued8e\ued8f\ued90\ued91\ued92\ued93\ued94\ued95\ued96\ued97\ued98\ued99\ued9a\ued9b\ued9c\ued9d\ued9e\ued9f\ueda0\ueda1\ueda2\ueda3\ueda4\ueda5\ueda6\ueda7\ueda8\ueda9\uedaa\uedab\uedac\uedad\uedae\uedaf\uedb0\uedb1\uedb2\uedb3\uedb4\uedb5\uedb6\uedb7\uedb8\uedb9\uedba\uedbb\uedbc\uedbd\uedbe\uedbf\uedc0\uedc1\uedc2\uedc3\uedc4\uedc5\uedc6\uedc7\uedc8\uedc9\uedca\uedcb\uedcc\uedcd\uedce\uedcf\uedd0\uedd1\uedd2\uedd3\uedd4\uedd5\uedd6\uedd7\uedd8\uedd9\uedda\ueddb\ueddc\ueddd\uedde\ueddf\uede0\uede1\uede2\uede3\uede4\uede5\uede6\uede7\uede8\uede9\uedea\uedeb\uedec\ueded\uedee\uedef\uedf0\uedf1\uedf2\uedf3\uedf4\uedf5\uedf6\uedf7\uedf8\uedf9\uedfa\uedfb\uedfc\uedfd\uedfe\uedff\uee00\uee01\uee02\uee03\uee04\uee05\uee06\uee07\uee08\uee09\uee0a\uee0b\uee0c\uee0d\uee0e\uee0f\uee10\uee11\uee12\uee13\uee14\uee15\uee16\uee17\uee18\uee19\uee1a\uee1b\uee1c\uee1d\uee1e\uee1f\uee20\uee21\uee22\uee23\uee24\uee25\uee26\uee27\uee28\uee29\uee2a\uee2b\uee2c\uee2d\uee2e\uee2f\uee30\uee31\uee32\uee33\uee34\uee35\uee36\uee37\uee38\uee39\uee3a\uee3b\uee3c\uee3d\uee3e\uee3f\uee40\uee41\uee42\uee43\uee44\uee45\uee46\uee47\uee48\uee49\uee4a\uee4b\uee4c\uee4d\uee4e\uee4f\uee50\uee51\uee52\uee53\uee54\uee55\uee56\uee57\uee58\uee59\uee5a\uee5b\uee5c\uee5d\uee5e\uee5f\uee60\uee61\uee62\uee63\uee64\uee65\uee66\uee67\uee68\uee69\uee6a\uee6b\uee6c\uee6d\uee6e\uee6f\uee70\uee71\uee72\uee73\uee74\uee75\uee76\uee77\uee78\uee79\uee7a\uee7b\uee7c\uee7d\uee7e\uee7f\uee80\uee81\uee82\uee83\uee84\uee85\uee86\uee87\uee88\uee89\uee8a\uee8b\uee8c\uee8d\uee8e\uee8f\uee90\uee91\uee92\uee93\uee94\uee95\uee96\uee97\uee98\uee99\uee9a\uee9b\uee9c\uee9d\uee9e\uee9f\ueea0\ueea1\ueea2\ueea3\ueea4\ueea5\ueea6\ueea7\ueea8\ueea9\ueeaa\ueeab\ueeac\ueead\ueeae\ueeaf\ueeb0\ueeb1\ueeb2\ueeb3\ueeb4\ueeb5\ueeb6\ueeb7\ueeb8\ueeb9\ueeba\ueebb\ueebc\ueebd\ueebe\ueebf\ueec0\ueec1\ueec2\ueec3\ueec4\ueec5\ueec6\ueec7\ueec8\ueec9\ueeca\ueecb\ueecc\ueecd\ueece\ueecf\ueed0\ueed1\ueed2\ueed3\ueed4\ueed5\ueed6\ueed7\ueed8\ueed9\ueeda\ueedb\ueedc\ueedd\ueede\ueedf\ueee0\ueee1\ueee2\ueee3\ueee4\ueee5\ueee6\ueee7\ueee8\ueee9\ueeea\ueeeb\ueeec\ueeed\ueeee\ueeef\ueef0\ueef1\ueef2\ueef3\ueef4\ueef5\ueef6\ueef7\ueef8\ueef9\ueefa\ueefb\ueefc\ueefd\ueefe\ueeff\uef00\uef01\uef02\uef03\uef04\uef05\uef06\uef07\uef08\uef09\uef0a\uef0b\uef0c\uef0d\uef0e\uef0f\uef10\uef11\uef12\uef13\uef14\uef15\uef16\uef17\uef18\uef19\uef1a\uef1b\uef1c\uef1d\uef1e\uef1f\uef20\uef21\uef22\uef23\uef24\uef25\uef26\uef27\uef28\uef29\uef2a\uef2b\uef2c\uef2d\uef2e\uef2f\uef30\uef31\uef32\uef33\uef34\uef35\uef36\uef37\uef38\uef39\uef3a\uef3b\uef3c\uef3d\uef3e\uef3f\uef40\uef41\uef42\uef43\uef44\uef45\uef46\uef47\uef48\uef49\uef4a\uef4b\uef4c\uef4d\uef4e\uef4f\uef50\uef51\uef52\uef53\uef54\uef55\uef56\uef57\uef58\uef59\uef5a\uef5b\uef5c\uef5d\uef5e\uef5f\uef60\uef61\uef62\uef63\uef64\uef65\uef66\uef67\uef68\uef69\uef6a\uef6b\uef6c\uef6d\uef6e\uef6f\uef70\uef71\uef72\uef73\uef74\uef75\uef76\uef77\uef78\uef79\uef7a\uef7b\uef7c\uef7d\uef7e\uef7f\uef80\uef81\uef82\uef83\uef84\uef85\uef86\uef87\uef88\uef89\uef8a\uef8b\uef8c\uef8d\uef8e\uef8f\uef90\uef91\uef92\uef93\uef94\uef95\uef96\uef97\uef98\uef99\uef9a\uef9b\uef9c\uef9d\uef9e\uef9f\uefa0\uefa1\uefa2\uefa3\uefa4\uefa5\uefa6\uefa7\uefa8\uefa9\uefaa\uefab\uefac\uefad\uefae\uefaf\uefb0\uefb1\uefb2\uefb3\uefb4\uefb5\uefb6\uefb7\uefb8\uefb9\uefba\uefbb\uefbc\uefbd\uefbe\uefbf\uefc0\uefc1\uefc2\uefc3\uefc4\uefc5\uefc6\uefc7\uefc8\uefc9\uefca\uefcb\uefcc\uefcd\uefce\uefcf\uefd0\uefd1\uefd2\uefd3\uefd4\uefd5\uefd6\uefd7\uefd8\uefd9\uefda\uefdb\uefdc\uefdd\uefde\uefdf\uefe0\uefe1\uefe2\uefe3\uefe4\uefe5\uefe6\uefe7\uefe8\uefe9\uefea\uefeb\uefec\uefed\uefee\uefef\ueff0\ueff1\ueff2\ueff3\ueff4\ueff5\ueff6\ueff7\ueff8\ueff9\ueffa\ueffb\ueffc\ueffd\ueffe\uefff\uf000\uf001\uf002\uf003\uf004\uf005\uf006\uf007\uf008\uf009\uf00a\uf00b\uf00c\uf00d\uf00e\uf00f\uf010\uf011\uf012\uf013\uf014\uf015\uf016\uf017\uf018\uf019\uf01a\uf01b\uf01c\uf01d\uf01e\uf01f\uf020\uf021\uf022\uf023\uf024\uf025\uf026\uf027\uf028\uf029\uf02a\uf02b\uf02c\uf02d\uf02e\uf02f\uf030\uf031\uf032\uf033\uf034\uf035\uf036\uf037\uf038\uf039\uf03a\uf03b\uf03c\uf03d\uf03e\uf03f\uf040\uf041\uf042\uf043\uf044\uf045\uf046\uf047\uf048\uf049\uf04a\uf04b\uf04c\uf04d\uf04e\uf04f\uf050\uf051\uf052\uf053\uf054\uf055\uf056\uf057\uf058\uf059\uf05a\uf05b\uf05c\uf05d\uf05e\uf05f\uf060\uf061\uf062\uf063\uf064\uf065\uf066\uf067\uf068\uf069\uf06a\uf06b\uf06c\uf06d\uf06e\uf06f\uf070\uf071\uf072\uf073\uf074\uf075\uf076\uf077\uf078\uf079\uf07a\uf07b\uf07c\uf07d\uf07e\uf07f\uf080\uf081\uf082\uf083\uf084\uf085\uf086\uf087\uf088\uf089\uf08a\uf08b\uf08c\uf08d\uf08e\uf08f\uf090\uf091\uf092\uf093\uf094\uf095\uf096\uf097\uf098\uf099\uf09a\uf09b\uf09c\uf09d\uf09e\uf09f\uf0a0\uf0a1\uf0a2\uf0a3\uf0a4\uf0a5\uf0a6\uf0a7\uf0a8\uf0a9\uf0aa\uf0ab\uf0ac\uf0ad\uf0ae\uf0af\uf0b0\uf0b1\uf0b2\uf0b3\uf0b4\uf0b5\uf0b6\uf0b7\uf0b8\uf0b9\uf0ba\uf0bb\uf0bc\uf0bd\uf0be\uf0bf\uf0c0\uf0c1\uf0c2\uf0c3\uf0c4\uf0c5\uf0c6\uf0c7\uf0c8\uf0c9\uf0ca\uf0cb\uf0cc\uf0cd\uf0ce\uf0cf\uf0d0\uf0d1\uf0d2\uf0d3\uf0d4\uf0d5\uf0d6\uf0d7\uf0d8\uf0d9\uf0da\uf0db\uf0dc\uf0dd\uf0de\uf0df\uf0e0\uf0e1\uf0e2\uf0e3\uf0e4\uf0e5\uf0e6\uf0e7\uf0e8\uf0e9\uf0ea\uf0eb\uf0ec\uf0ed\uf0ee\uf0ef\uf0f0\uf0f1\uf0f2\uf0f3\uf0f4\uf0f5\uf0f6\uf0f7\uf0f8\uf0f9\uf0fa\uf0fb\uf0fc\uf0fd\uf0fe\uf0ff\uf100\uf101\uf102\uf103\uf104\uf105\uf106\uf107\uf108\uf109\uf10a\uf10b\uf10c\uf10d\uf10e\uf10f\uf110\uf111\uf112\uf113\uf114\uf115\uf116\uf117\uf118\uf119\uf11a\uf11b\uf11c\uf11d\uf11e\uf11f\uf120\uf121\uf122\uf123\uf124\uf125\uf126\uf127\uf128\uf129\uf12a\uf12b\uf12c\uf12d\uf12e\uf12f\uf130\uf131\uf132\uf133\uf134\uf135\uf136\uf137\uf138\uf139\uf13a\uf13b\uf13c\uf13d\uf13e\uf13f\uf140\uf141\uf142\uf143\uf144\uf145\uf146\uf147\uf148\uf149\uf14a\uf14b\uf14c\uf14d\uf14e\uf14f\uf150\uf151\uf152\uf153\uf154\uf155\uf156\uf157\uf158\uf159\uf15a\uf15b\uf15c\uf15d\uf15e\uf15f\uf160\uf161\uf162\uf163\uf164\uf165\uf166\uf167\uf168\uf169\uf16a\uf16b\uf16c\uf16d\uf16e\uf16f\uf170\uf171\uf172\uf173\uf174\uf175\uf176\uf177\uf178\uf179\uf17a\uf17b\uf17c\uf17d\uf17e\uf17f\uf180\uf181\uf182\uf183\uf184\uf185\uf186\uf187\uf188\uf189\uf18a\uf18b\uf18c\uf18d\uf18e\uf18f\uf190\uf191\uf192\uf193\uf194\uf195\uf196\uf197\uf198\uf199\uf19a\uf19b\uf19c\uf19d\uf19e\uf19f\uf1a0\uf1a1\uf1a2\uf1a3\uf1a4\uf1a5\uf1a6\uf1a7\uf1a8\uf1a9\uf1aa\uf1ab\uf1ac\uf1ad\uf1ae\uf1af\uf1b0\uf1b1\uf1b2\uf1b3\uf1b4\uf1b5\uf1b6\uf1b7\uf1b8\uf1b9\uf1ba\uf1bb\uf1bc\uf1bd\uf1be\uf1bf\uf1c0\uf1c1\uf1c2\uf1c3\uf1c4\uf1c5\uf1c6\uf1c7\uf1c8\uf1c9\uf1ca\uf1cb\uf1cc\uf1cd\uf1ce\uf1cf\uf1d0\uf1d1\uf1d2\uf1d3\uf1d4\uf1d5\uf1d6\uf1d7\uf1d8\uf1d9\uf1da\uf1db\uf1dc\uf1dd\uf1de\uf1df\uf1e0\uf1e1\uf1e2\uf1e3\uf1e4\uf1e5\uf1e6\uf1e7\uf1e8\uf1e9\uf1ea\uf1eb\uf1ec\uf1ed\uf1ee\uf1ef\uf1f0\uf1f1\uf1f2\uf1f3\uf1f4\uf1f5\uf1f6\uf1f7\uf1f8\uf1f9\uf1fa\uf1fb\uf1fc\uf1fd\uf1fe\uf1ff\uf200\uf201\uf202\uf203\uf204\uf205\uf206\uf207\uf208\uf209\uf20a\uf20b\uf20c\uf20d\uf20e\uf20f\uf210\uf211\uf212\uf213\uf214\uf215\uf216\uf217\uf218\uf219\uf21a\uf21b\uf21c\uf21d\uf21e\uf21f\uf220\uf221\uf222\uf223\uf224\uf225\uf226\uf227\uf228\uf229\uf22a\uf22b\uf22c\uf22d\uf22e\uf22f\uf230\uf231\uf232\uf233\uf234\uf235\uf236\uf237\uf238\uf239\uf23a\uf23b\uf23c\uf23d\uf23e\uf23f\uf240\uf241\uf242\uf243\uf244\uf245\uf246\uf247\uf248\uf249\uf24a\uf24b\uf24c\uf24d\uf24e\uf24f\uf250\uf251\uf252\uf253\uf254\uf255\uf256\uf257\uf258\uf259\uf25a\uf25b\uf25c\uf25d\uf25e\uf25f\uf260\uf261\uf262\uf263\uf264\uf265\uf266\uf267\uf268\uf269\uf26a\uf26b\uf26c\uf26d\uf26e\uf26f\uf270\uf271\uf272\uf273\uf274\uf275\uf276\uf277\uf278\uf279\uf27a\uf27b\uf27c\uf27d\uf27e\uf27f\uf280\uf281\uf282\uf283\uf284\uf285\uf286\uf287\uf288\uf289\uf28a\uf28b\uf28c\uf28d\uf28e\uf28f\uf290\uf291\uf292\uf293\uf294\uf295\uf296\uf297\uf298\uf299\uf29a\uf29b\uf29c\uf29d\uf29e\uf29f\uf2a0\uf2a1\uf2a2\uf2a3\uf2a4\uf2a5\uf2a6\uf2a7\uf2a8\uf2a9\uf2aa\uf2ab\uf2ac\uf2ad\uf2ae\uf2af\uf2b0\uf2b1\uf2b2\uf2b3\uf2b4\uf2b5\uf2b6\uf2b7\uf2b8\uf2b9\uf2ba\uf2bb\uf2bc\uf2bd\uf2be\uf2bf\uf2c0\uf2c1\uf2c2\uf2c3\uf2c4\uf2c5\uf2c6\uf2c7\uf2c8\uf2c9\uf2ca\uf2cb\uf2cc\uf2cd\uf2ce\uf2cf\uf2d0\uf2d1\uf2d2\uf2d3\uf2d4\uf2d5\uf2d6\uf2d7\uf2d8\uf2d9\uf2da\uf2db\uf2dc\uf2dd\uf2de\uf2df\uf2e0\uf2e1\uf2e2\uf2e3\uf2e4\uf2e5\uf2e6\uf2e7\uf2e8\uf2e9\uf2ea\uf2eb\uf2ec\uf2ed\uf2ee\uf2ef\uf2f0\uf2f1\uf2f2\uf2f3\uf2f4\uf2f5\uf2f6\uf2f7\uf2f8\uf2f9\uf2fa\uf2fb\uf2fc\uf2fd\uf2fe\uf2ff\uf300\uf301\uf302\uf303\uf304\uf305\uf306\uf307\uf308\uf309\uf30a\uf30b\uf30c\uf30d\uf30e\uf30f\uf310\uf311\uf312\uf313\uf314\uf315\uf316\uf317\uf318\uf319\uf31a\uf31b\uf31c\uf31d\uf31e\uf31f\uf320\uf321\uf322\uf323\uf324\uf325\uf326\uf327\uf328\uf329\uf32a\uf32b\uf32c\uf32d\uf32e\uf32f\uf330\uf331\uf332\uf333\uf334\uf335\uf336\uf337\uf338\uf339\uf33a\uf33b\uf33c\uf33d\uf33e\uf33f\uf340\uf341\uf342\uf343\uf344\uf345\uf346\uf347\uf348\uf349\uf34a\uf34b\uf34c\uf34d\uf34e\uf34f\uf350\uf351\uf352\uf353\uf354\uf355\uf356\uf357\uf358\uf359\uf35a\uf35b\uf35c\uf35d\uf35e\uf35f\uf360\uf361\uf362\uf363\uf364\uf365\uf366\uf367\uf368\uf369\uf36a\uf36b\uf36c\uf36d\uf36e\uf36f\uf370\uf371\uf372\uf373\uf374\uf375\uf376\uf377\uf378\uf379\uf37a\uf37b\uf37c\uf37d\uf37e\uf37f\uf380\uf381\uf382\uf383\uf384\uf385\uf386\uf387\uf388\uf389\uf38a\uf38b\uf38c\uf38d\uf38e\uf38f\uf390\uf391\uf392\uf393\uf394\uf395\uf396\uf397\uf398\uf399\uf39a\uf39b\uf39c\uf39d\uf39e\uf39f\uf3a0\uf3a1\uf3a2\uf3a3\uf3a4\uf3a5\uf3a6\uf3a7\uf3a8\uf3a9\uf3aa\uf3ab\uf3ac\uf3ad\uf3ae\uf3af\uf3b0\uf3b1\uf3b2\uf3b3\uf3b4\uf3b5\uf3b6\uf3b7\uf3b8\uf3b9\uf3ba\uf3bb\uf3bc\uf3bd\uf3be\uf3bf\uf3c0\uf3c1\uf3c2\uf3c3\uf3c4\uf3c5\uf3c6\uf3c7\uf3c8\uf3c9\uf3ca\uf3cb\uf3cc\uf3cd\uf3ce\uf3cf\uf3d0\uf3d1\uf3d2\uf3d3\uf3d4\uf3d5\uf3d6\uf3d7\uf3d8\uf3d9\uf3da\uf3db\uf3dc\uf3dd\uf3de\uf3df\uf3e0\uf3e1\uf3e2\uf3e3\uf3e4\uf3e5\uf3e6\uf3e7\uf3e8\uf3e9\uf3ea\uf3eb\uf3ec\uf3ed\uf3ee\uf3ef\uf3f0\uf3f1\uf3f2\uf3f3\uf3f4\uf3f5\uf3f6\uf3f7\uf3f8\uf3f9\uf3fa\uf3fb\uf3fc\uf3fd\uf3fe\uf3ff\uf400\uf401\uf402\uf403\uf404\uf405\uf406\uf407\uf408\uf409\uf40a\uf40b\uf40c\uf40d\uf40e\uf40f\uf410\uf411\uf412\uf413\uf414\uf415\uf416\uf417\uf418\uf419\uf41a\uf41b\uf41c\uf41d\uf41e\uf41f\uf420\uf421\uf422\uf423\uf424\uf425\uf426\uf427\uf428\uf429\uf42a\uf42b\uf42c\uf42d\uf42e\uf42f\uf430\uf431\uf432\uf433\uf434\uf435\uf436\uf437\uf438\uf439\uf43a\uf43b\uf43c\uf43d\uf43e\uf43f\uf440\uf441\uf442\uf443\uf444\uf445\uf446\uf447\uf448\uf449\uf44a\uf44b\uf44c\uf44d\uf44e\uf44f\uf450\uf451\uf452\uf453\uf454\uf455\uf456\uf457\uf458\uf459\uf45a\uf45b\uf45c\uf45d\uf45e\uf45f\uf460\uf461\uf462\uf463\uf464\uf465\uf466\uf467\uf468\uf469\uf46a\uf46b\uf46c\uf46d\uf46e\uf46f\uf470\uf471\uf472\uf473\uf474\uf475\uf476\uf477\uf478\uf479\uf47a\uf47b\uf47c\uf47d\uf47e\uf47f\uf480\uf481\uf482\uf483\uf484\uf485\uf486\uf487\uf488\uf489\uf48a\uf48b\uf48c\uf48d\uf48e\uf48f\uf490\uf491\uf492\uf493\uf494\uf495\uf496\uf497\uf498\uf499\uf49a\uf49b\uf49c\uf49d\uf49e\uf49f\uf4a0\uf4a1\uf4a2\uf4a3\uf4a4\uf4a5\uf4a6\uf4a7\uf4a8\uf4a9\uf4aa\uf4ab\uf4ac\uf4ad\uf4ae\uf4af\uf4b0\uf4b1\uf4b2\uf4b3\uf4b4\uf4b5\uf4b6\uf4b7\uf4b8\uf4b9\uf4ba\uf4bb\uf4bc\uf4bd\uf4be\uf4bf\uf4c0\uf4c1\uf4c2\uf4c3\uf4c4\uf4c5\uf4c6\uf4c7\uf4c8\uf4c9\uf4ca\uf4cb\uf4cc\uf4cd\uf4ce\uf4cf\uf4d0\uf4d1\uf4d2\uf4d3\uf4d4\uf4d5\uf4d6\uf4d7\uf4d8\uf4d9\uf4da\uf4db\uf4dc\uf4dd\uf4de\uf4df\uf4e0\uf4e1\uf4e2\uf4e3\uf4e4\uf4e5\uf4e6\uf4e7\uf4e8\uf4e9\uf4ea\uf4eb\uf4ec\uf4ed\uf4ee\uf4ef\uf4f0\uf4f1\uf4f2\uf4f3\uf4f4\uf4f5\uf4f6\uf4f7\uf4f8\uf4f9\uf4fa\uf4fb\uf4fc\uf4fd\uf4fe\uf4ff\uf500\uf501\uf502\uf503\uf504\uf505\uf506\uf507\uf508\uf509\uf50a\uf50b\uf50c\uf50d\uf50e\uf50f\uf510\uf511\uf512\uf513\uf514\uf515\uf516\uf517\uf518\uf519\uf51a\uf51b\uf51c\uf51d\uf51e\uf51f\uf520\uf521\uf522\uf523\uf524\uf525\uf526\uf527\uf528\uf529\uf52a\uf52b\uf52c\uf52d\uf52e\uf52f\uf530\uf531\uf532\uf533\uf534\uf535\uf536\uf537\uf538\uf539\uf53a\uf53b\uf53c\uf53d\uf53e\uf53f\uf540\uf541\uf542\uf543\uf544\uf545\uf546\uf547\uf548\uf549\uf54a\uf54b\uf54c\uf54d\uf54e\uf54f\uf550\uf551\uf552\uf553\uf554\uf555\uf556\uf557\uf558\uf559\uf55a\uf55b\uf55c\uf55d\uf55e\uf55f\uf560\uf561\uf562\uf563\uf564\uf565\uf566\uf567\uf568\uf569\uf56a\uf56b\uf56c\uf56d\uf56e\uf56f\uf570\uf571\uf572\uf573\uf574\uf575\uf576\uf577\uf578\uf579\uf57a\uf57b\uf57c\uf57d\uf57e\uf57f\uf580\uf581\uf582\uf583\uf584\uf585\uf586\uf587\uf588\uf589\uf58a\uf58b\uf58c\uf58d\uf58e\uf58f\uf590\uf591\uf592\uf593\uf594\uf595\uf596\uf597\uf598\uf599\uf59a\uf59b\uf59c\uf59d\uf59e\uf59f\uf5a0\uf5a1\uf5a2\uf5a3\uf5a4\uf5a5\uf5a6\uf5a7\uf5a8\uf5a9\uf5aa\uf5ab\uf5ac\uf5ad\uf5ae\uf5af\uf5b0\uf5b1\uf5b2\uf5b3\uf5b4\uf5b5\uf5b6\uf5b7\uf5b8\uf5b9\uf5ba\uf5bb\uf5bc\uf5bd\uf5be\uf5bf\uf5c0\uf5c1\uf5c2\uf5c3\uf5c4\uf5c5\uf5c6\uf5c7\uf5c8\uf5c9\uf5ca\uf5cb\uf5cc\uf5cd\uf5ce\uf5cf\uf5d0\uf5d1\uf5d2\uf5d3\uf5d4\uf5d5\uf5d6\uf5d7\uf5d8\uf5d9\uf5da\uf5db\uf5dc\uf5dd\uf5de\uf5df\uf5e0\uf5e1\uf5e2\uf5e3\uf5e4\uf5e5\uf5e6\uf5e7\uf5e8\uf5e9\uf5ea\uf5eb\uf5ec\uf5ed\uf5ee\uf5ef\uf5f0\uf5f1\uf5f2\uf5f3\uf5f4\uf5f5\uf5f6\uf5f7\uf5f8\uf5f9\uf5fa\uf5fb\uf5fc\uf5fd\uf5fe\uf5ff\uf600\uf601\uf602\uf603\uf604\uf605\uf606\uf607\uf608\uf609\uf60a\uf60b\uf60c\uf60d\uf60e\uf60f\uf610\uf611\uf612\uf613\uf614\uf615\uf616\uf617\uf618\uf619\uf61a\uf61b\uf61c\uf61d\uf61e\uf61f\uf620\uf621\uf622\uf623\uf624\uf625\uf626\uf627\uf628\uf629\uf62a\uf62b\uf62c\uf62d\uf62e\uf62f\uf630\uf631\uf632\uf633\uf634\uf635\uf636\uf637\uf638\uf639\uf63a\uf63b\uf63c\uf63d\uf63e\uf63f\uf640\uf641\uf642\uf643\uf644\uf645\uf646\uf647\uf648\uf649\uf64a\uf64b\uf64c\uf64d\uf64e\uf64f\uf650\uf651\uf652\uf653\uf654\uf655\uf656\uf657\uf658\uf659\uf65a\uf65b\uf65c\uf65d\uf65e\uf65f\uf660\uf661\uf662\uf663\uf664\uf665\uf666\uf667\uf668\uf669\uf66a\uf66b\uf66c\uf66d\uf66e\uf66f\uf670\uf671\uf672\uf673\uf674\uf675\uf676\uf677\uf678\uf679\uf67a\uf67b\uf67c\uf67d\uf67e\uf67f\uf680\uf681\uf682\uf683\uf684\uf685\uf686\uf687\uf688\uf689\uf68a\uf68b\uf68c\uf68d\uf68e\uf68f\uf690\uf691\uf692\uf693\uf694\uf695\uf696\uf697\uf698\uf699\uf69a\uf69b\uf69c\uf69d\uf69e\uf69f\uf6a0\uf6a1\uf6a2\uf6a3\uf6a4\uf6a5\uf6a6\uf6a7\uf6a8\uf6a9\uf6aa\uf6ab\uf6ac\uf6ad\uf6ae\uf6af\uf6b0\uf6b1\uf6b2\uf6b3\uf6b4\uf6b5\uf6b6\uf6b7\uf6b8\uf6b9\uf6ba\uf6bb\uf6bc\uf6bd\uf6be\uf6bf\uf6c0\uf6c1\uf6c2\uf6c3\uf6c4\uf6c5\uf6c6\uf6c7\uf6c8\uf6c9\uf6ca\uf6cb\uf6cc\uf6cd\uf6ce\uf6cf\uf6d0\uf6d1\uf6d2\uf6d3\uf6d4\uf6d5\uf6d6\uf6d7\uf6d8\uf6d9\uf6da\uf6db\uf6dc\uf6dd\uf6de\uf6df\uf6e0\uf6e1\uf6e2\uf6e3\uf6e4\uf6e5\uf6e6\uf6e7\uf6e8\uf6e9\uf6ea\uf6eb\uf6ec\uf6ed\uf6ee\uf6ef\uf6f0\uf6f1\uf6f2\uf6f3\uf6f4\uf6f5\uf6f6\uf6f7\uf6f8\uf6f9\uf6fa\uf6fb\uf6fc\uf6fd\uf6fe\uf6ff\uf700\uf701\uf702\uf703\uf704\uf705\uf706\uf707\uf708\uf709\uf70a\uf70b\uf70c\uf70d\uf70e\uf70f\uf710\uf711\uf712\uf713\uf714\uf715\uf716\uf717\uf718\uf719\uf71a\uf71b\uf71c\uf71d\uf71e\uf71f\uf720\uf721\uf722\uf723\uf724\uf725\uf726\uf727\uf728\uf729\uf72a\uf72b\uf72c\uf72d\uf72e\uf72f\uf730\uf731\uf732\uf733\uf734\uf735\uf736\uf737\uf738\uf739\uf73a\uf73b\uf73c\uf73d\uf73e\uf73f\uf740\uf741\uf742\uf743\uf744\uf745\uf746\uf747\uf748\uf749\uf74a\uf74b\uf74c\uf74d\uf74e\uf74f\uf750\uf751\uf752\uf753\uf754\uf755\uf756\uf757\uf758\uf759\uf75a\uf75b\uf75c\uf75d\uf75e\uf75f\uf760\uf761\uf762\uf763\uf764\uf765\uf766\uf767\uf768\uf769\uf76a\uf76b\uf76c\uf76d\uf76e\uf76f\uf770\uf771\uf772\uf773\uf774\uf775\uf776\uf777\uf778\uf779\uf77a\uf77b\uf77c\uf77d\uf77e\uf77f\uf780\uf781\uf782\uf783\uf784\uf785\uf786\uf787\uf788\uf789\uf78a\uf78b\uf78c\uf78d\uf78e\uf78f\uf790\uf791\uf792\uf793\uf794\uf795\uf796\uf797\uf798\uf799\uf79a\uf79b\uf79c\uf79d\uf79e\uf79f\uf7a0\uf7a1\uf7a2\uf7a3\uf7a4\uf7a5\uf7a6\uf7a7\uf7a8\uf7a9\uf7aa\uf7ab\uf7ac\uf7ad\uf7ae\uf7af\uf7b0\uf7b1\uf7b2\uf7b3\uf7b4\uf7b5\uf7b6\uf7b7\uf7b8\uf7b9\uf7ba\uf7bb\uf7bc\uf7bd\uf7be\uf7bf\uf7c0\uf7c1\uf7c2\uf7c3\uf7c4\uf7c5\uf7c6\uf7c7\uf7c8\uf7c9\uf7ca\uf7cb\uf7cc\uf7cd\uf7ce\uf7cf\uf7d0\uf7d1\uf7d2\uf7d3\uf7d4\uf7d5\uf7d6\uf7d7\uf7d8\uf7d9\uf7da\uf7db\uf7dc\uf7dd\uf7de\uf7df\uf7e0\uf7e1\uf7e2\uf7e3\uf7e4\uf7e5\uf7e6\uf7e7\uf7e8\uf7e9\uf7ea\uf7eb\uf7ec\uf7ed\uf7ee\uf7ef\uf7f0\uf7f1\uf7f2\uf7f3\uf7f4\uf7f5\uf7f6\uf7f7\uf7f8\uf7f9\uf7fa\uf7fb\uf7fc\uf7fd\uf7fe\uf7ff\uf800\uf801\uf802\uf803\uf804\uf805\uf806\uf807\uf808\uf809\uf80a\uf80b\uf80c\uf80d\uf80e\uf80f\uf810\uf811\uf812\uf813\uf814\uf815\uf816\uf817\uf818\uf819\uf81a\uf81b\uf81c\uf81d\uf81e\uf81f\uf820\uf821\uf822\uf823\uf824\uf825\uf826\uf827\uf828\uf829\uf82a\uf82b\uf82c\uf82d\uf82e\uf82f\uf830\uf831\uf832\uf833\uf834\uf835\uf836\uf837\uf838\uf839\uf83a\uf83b\uf83c\uf83d\uf83e\uf83f\uf840\uf841\uf842\uf843\uf844\uf845\uf846\uf847\uf848\uf849\uf84a\uf84b\uf84c\uf84d\uf84e\uf84f\uf850\uf851\uf852\uf853\uf854\uf855\uf856\uf857\uf858\uf859\uf85a\uf85b\uf85c\uf85d\uf85e\uf85f\uf860\uf861\uf862\uf863\uf864\uf865\uf866\uf867\uf868\uf869\uf86a\uf86b\uf86c\uf86d\uf86e\uf86f\uf870\uf871\uf872\uf873\uf874\uf875\uf876\uf877\uf878\uf879\uf87a\uf87b\uf87c\uf87d\uf87e\uf87f\uf880\uf881\uf882\uf883\uf884\uf885\uf886\uf887\uf888\uf889\uf88a\uf88b\uf88c\uf88d\uf88e\uf88f\uf890\uf891\uf892\uf893\uf894\uf895\uf896\uf897\uf898\uf899\uf89a\uf89b\uf89c\uf89d\uf89e\uf89f\uf8a0\uf8a1\uf8a2\uf8a3\uf8a4\uf8a5\uf8a6\uf8a7\uf8a8\uf8a9\uf8aa\uf8ab\uf8ac\uf8ad\uf8ae\uf8af\uf8b0\uf8b1\uf8b2\uf8b3\uf8b4\uf8b5\uf8b6\uf8b7\uf8b8\uf8b9\uf8ba\uf8bb\uf8bc\uf8bd\uf8be\uf8bf\uf8c0\uf8c1\uf8c2\uf8c3\uf8c4\uf8c5\uf8c6\uf8c7\uf8c8\uf8c9\uf8ca\uf8cb\uf8cc\uf8cd\uf8ce\uf8cf\uf8d0\uf8d1\uf8d2\uf8d3\uf8d4\uf8d5\uf8d6\uf8d7\uf8d8\uf8d9\uf8da\uf8db\uf8dc\uf8dd\uf8de\uf8df\uf8e0\uf8e1\uf8e2\uf8e3\uf8e4\uf8e5\uf8e6\uf8e7\uf8e8\uf8e9\uf8ea\uf8eb\uf8ec\uf8ed\uf8ee\uf8ef\uf8f0\uf8f1\uf8f2\uf8f3\uf8f4\uf8f5\uf8f6\uf8f7\uf8f8\uf8f9\uf8fa\uf8fb\uf8fc\uf8fd\uf8fe\uf8ff'
-
-try:
- Cs = eval(r"'\ud800\ud801\ud802\ud803\ud804\ud805\ud806\ud807\ud808\ud809\ud80a\ud80b\ud80c\ud80d\ud80e\ud80f\ud810\ud811\ud812\ud813\ud814\ud815\ud816\ud817\ud818\ud819\ud81a\ud81b\ud81c\ud81d\ud81e\ud81f\ud820\ud821\ud822\ud823\ud824\ud825\ud826\ud827\ud828\ud829\ud82a\ud82b\ud82c\ud82d\ud82e\ud82f\ud830\ud831\ud832\ud833\ud834\ud835\ud836\ud837\ud838\ud839\ud83a\ud83b\ud83c\ud83d\ud83e\ud83f\ud840\ud841\ud842\ud843\ud844\ud845\ud846\ud847\ud848\ud849\ud84a\ud84b\ud84c\ud84d\ud84e\ud84f\ud850\ud851\ud852\ud853\ud854\ud855\ud856\ud857\ud858\ud859\ud85a\ud85b\ud85c\ud85d\ud85e\ud85f\ud860\ud861\ud862\ud863\ud864\ud865\ud866\ud867\ud868\ud869\ud86a\ud86b\ud86c\ud86d\ud86e\ud86f\ud870\ud871\ud872\ud873\ud874\ud875\ud876\ud877\ud878\ud879\ud87a\ud87b\ud87c\ud87d\ud87e\ud87f\ud880\ud881\ud882\ud883\ud884\ud885\ud886\ud887\ud888\ud889\ud88a\ud88b\ud88c\ud88d\ud88e\ud88f\ud890\ud891\ud892\ud893\ud894\ud895\ud896\ud897\ud898\ud899\ud89a\ud89b\ud89c\ud89d\ud89e\ud89f\ud8a0\ud8a1\ud8a2\ud8a3\ud8a4\ud8a5\ud8a6\ud8a7\ud8a8\ud8a9\ud8aa\ud8ab\ud8ac\ud8ad\ud8ae\ud8af\ud8b0\ud8b1\ud8b2\ud8b3\ud8b4\ud8b5\ud8b6\ud8b7\ud8b8\ud8b9\ud8ba\ud8bb\ud8bc\ud8bd\ud8be\ud8bf\ud8c0\ud8c1\ud8c2\ud8c3\ud8c4\ud8c5\ud8c6\ud8c7\ud8c8\ud8c9\ud8ca\ud8cb\ud8cc\ud8cd\ud8ce\ud8cf\ud8d0\ud8d1\ud8d2\ud8d3\ud8d4\ud8d5\ud8d6\ud8d7\ud8d8\ud8d9\ud8da\ud8db\ud8dc\ud8dd\ud8de\ud8df\ud8e0\ud8e1\ud8e2\ud8e3\ud8e4\ud8e5\ud8e6\ud8e7\ud8e8\ud8e9\ud8ea\ud8eb\ud8ec\ud8ed\ud8ee\ud8ef\ud8f0\ud8f1\ud8f2\ud8f3\ud8f4\ud8f5\ud8f6\ud8f7\ud8f8\ud8f9\ud8fa\ud8fb\ud8fc\ud8fd\ud8fe\ud8ff\ud900\ud901\ud902\ud903\ud904\ud905\ud906\ud907\ud908\ud909\ud90a\ud90b\ud90c\ud90d\ud90e\ud90f\ud910\ud911\ud912\ud913\ud914\ud915\ud916\ud917\ud918\ud919\ud91a\ud91b\ud91c\ud91d\ud91e\ud91f\ud920\ud921\ud922\ud923\ud924\ud925\ud926\ud927\ud928\ud929\ud92a\ud92b\ud92c\ud92d\ud92e\ud92f\ud930\ud931\ud932\ud933\ud934\ud935\ud936\ud937\ud938\ud939\ud93a\ud93b\ud93c\ud93d\ud93e\ud93f\ud940\ud941\ud942\ud943\ud944\ud945\ud946\ud947\ud948\ud949\ud94a\ud94b\ud94c\ud94d\ud94e\ud94f\ud950\ud951\ud952\ud953\ud954\ud955\ud956\ud957\ud958\ud959\ud95a\ud95b\ud95c\ud95d\ud95e\ud95f\ud960\ud961\ud962\ud963\ud964\ud965\ud966\ud967\ud968\ud969\ud96a\ud96b\ud96c\ud96d\ud96e\ud96f\ud970\ud971\ud972\ud973\ud974\ud975\ud976\ud977\ud978\ud979\ud97a\ud97b\ud97c\ud97d\ud97e\ud97f\ud980\ud981\ud982\ud983\ud984\ud985\ud986\ud987\ud988\ud989\ud98a\ud98b\ud98c\ud98d\ud98e\ud98f\ud990\ud991\ud992\ud993\ud994\ud995\ud996\ud997\ud998\ud999\ud99a\ud99b\ud99c\ud99d\ud99e\ud99f\ud9a0\ud9a1\ud9a2\ud9a3\ud9a4\ud9a5\ud9a6\ud9a7\ud9a8\ud9a9\ud9aa\ud9ab\ud9ac\ud9ad\ud9ae\ud9af\ud9b0\ud9b1\ud9b2\ud9b3\ud9b4\ud9b5\ud9b6\ud9b7\ud9b8\ud9b9\ud9ba\ud9bb\ud9bc\ud9bd\ud9be\ud9bf\ud9c0\ud9c1\ud9c2\ud9c3\ud9c4\ud9c5\ud9c6\ud9c7\ud9c8\ud9c9\ud9ca\ud9cb\ud9cc\ud9cd\ud9ce\ud9cf\ud9d0\ud9d1\ud9d2\ud9d3\ud9d4\ud9d5\ud9d6\ud9d7\ud9d8\ud9d9\ud9da\ud9db\ud9dc\ud9dd\ud9de\ud9df\ud9e0\ud9e1\ud9e2\ud9e3\ud9e4\ud9e5\ud9e6\ud9e7\ud9e8\ud9e9\ud9ea\ud9eb\ud9ec\ud9ed\ud9ee\ud9ef\ud9f0\ud9f1\ud9f2\ud9f3\ud9f4\ud9f5\ud9f6\ud9f7\ud9f8\ud9f9\ud9fa\ud9fb\ud9fc\ud9fd\ud9fe\ud9ff\uda00\uda01\uda02\uda03\uda04\uda05\uda06\uda07\uda08\uda09\uda0a\uda0b\uda0c\uda0d\uda0e\uda0f\uda10\uda11\uda12\uda13\uda14\uda15\uda16\uda17\uda18\uda19\uda1a\uda1b\uda1c\uda1d\uda1e\uda1f\uda20\uda21\uda22\uda23\uda24\uda25\uda26\uda27\uda28\uda29\uda2a\uda2b\uda2c\uda2d\uda2e\uda2f\uda30\uda31\uda32\uda33\uda34\uda35\uda36\uda37\uda38\uda39\uda3a\uda3b\uda3c\uda3d\uda3e\uda3f\uda40\uda41\uda42\uda43\uda44\uda45\uda46\uda47\uda48\uda49\uda4a\uda4b\uda4c\uda4d\uda4e\uda4f\uda50\uda51\uda52\uda53\uda54\uda55\uda56\uda57\uda58\uda59\uda5a\uda5b\uda5c\uda5d\uda5e\uda5f\uda60\uda61\uda62\uda63\uda64\uda65\uda66\uda67\uda68\uda69\uda6a\uda6b\uda6c\uda6d\uda6e\uda6f\uda70\uda71\uda72\uda73\uda74\uda75\uda76\uda77\uda78\uda79\uda7a\uda7b\uda7c\uda7d\uda7e\uda7f\uda80\uda81\uda82\uda83\uda84\uda85\uda86\uda87\uda88\uda89\uda8a\uda8b\uda8c\uda8d\uda8e\uda8f\uda90\uda91\uda92\uda93\uda94\uda95\uda96\uda97\uda98\uda99\uda9a\uda9b\uda9c\uda9d\uda9e\uda9f\udaa0\udaa1\udaa2\udaa3\udaa4\udaa5\udaa6\udaa7\udaa8\udaa9\udaaa\udaab\udaac\udaad\udaae\udaaf\udab0\udab1\udab2\udab3\udab4\udab5\udab6\udab7\udab8\udab9\udaba\udabb\udabc\udabd\udabe\udabf\udac0\udac1\udac2\udac3\udac4\udac5\udac6\udac7\udac8\udac9\udaca\udacb\udacc\udacd\udace\udacf\udad0\udad1\udad2\udad3\udad4\udad5\udad6\udad7\udad8\udad9\udada\udadb\udadc\udadd\udade\udadf\udae0\udae1\udae2\udae3\udae4\udae5\udae6\udae7\udae8\udae9\udaea\udaeb\udaec\udaed\udaee\udaef\udaf0\udaf1\udaf2\udaf3\udaf4\udaf5\udaf6\udaf7\udaf8\udaf9\udafa\udafb\udafc\udafd\udafe\udaff\udb00\udb01\udb02\udb03\udb04\udb05\udb06\udb07\udb08\udb09\udb0a\udb0b\udb0c\udb0d\udb0e\udb0f\udb10\udb11\udb12\udb13\udb14\udb15\udb16\udb17\udb18\udb19\udb1a\udb1b\udb1c\udb1d\udb1e\udb1f\udb20\udb21\udb22\udb23\udb24\udb25\udb26\udb27\udb28\udb29\udb2a\udb2b\udb2c\udb2d\udb2e\udb2f\udb30\udb31\udb32\udb33\udb34\udb35\udb36\udb37\udb38\udb39\udb3a\udb3b\udb3c\udb3d\udb3e\udb3f\udb40\udb41\udb42\udb43\udb44\udb45\udb46\udb47\udb48\udb49\udb4a\udb4b\udb4c\udb4d\udb4e\udb4f\udb50\udb51\udb52\udb53\udb54\udb55\udb56\udb57\udb58\udb59\udb5a\udb5b\udb5c\udb5d\udb5e\udb5f\udb60\udb61\udb62\udb63\udb64\udb65\udb66\udb67\udb68\udb69\udb6a\udb6b\udb6c\udb6d\udb6e\udb6f\udb70\udb71\udb72\udb73\udb74\udb75\udb76\udb77\udb78\udb79\udb7a\udb7b\udb7c\udb7d\udb7e\udb7f\udb80\udb81\udb82\udb83\udb84\udb85\udb86\udb87\udb88\udb89\udb8a\udb8b\udb8c\udb8d\udb8e\udb8f\udb90\udb91\udb92\udb93\udb94\udb95\udb96\udb97\udb98\udb99\udb9a\udb9b\udb9c\udb9d\udb9e\udb9f\udba0\udba1\udba2\udba3\udba4\udba5\udba6\udba7\udba8\udba9\udbaa\udbab\udbac\udbad\udbae\udbaf\udbb0\udbb1\udbb2\udbb3\udbb4\udbb5\udbb6\udbb7\udbb8\udbb9\udbba\udbbb\udbbc\udbbd\udbbe\udbbf\udbc0\udbc1\udbc2\udbc3\udbc4\udbc5\udbc6\udbc7\udbc8\udbc9\udbca\udbcb\udbcc\udbcd\udbce\udbcf\udbd0\udbd1\udbd2\udbd3\udbd4\udbd5\udbd6\udbd7\udbd8\udbd9\udbda\udbdb\udbdc\udbdd\udbde\udbdf\udbe0\udbe1\udbe2\udbe3\udbe4\udbe5\udbe6\udbe7\udbe8\udbe9\udbea\udbeb\udbec\udbed\udbee\udbef\udbf0\udbf1\udbf2\udbf3\udbf4\udbf5\udbf6\udbf7\udbf8\udbf9\udbfa\udbfb\udbfc\udbfd\udbfe\U0010fc00\udc01\udc02\udc03\udc04\udc05\udc06\udc07\udc08\udc09\udc0a\udc0b\udc0c\udc0d\udc0e\udc0f\udc10\udc11\udc12\udc13\udc14\udc15\udc16\udc17\udc18\udc19\udc1a\udc1b\udc1c\udc1d\udc1e\udc1f\udc20\udc21\udc22\udc23\udc24\udc25\udc26\udc27\udc28\udc29\udc2a\udc2b\udc2c\udc2d\udc2e\udc2f\udc30\udc31\udc32\udc33\udc34\udc35\udc36\udc37\udc38\udc39\udc3a\udc3b\udc3c\udc3d\udc3e\udc3f\udc40\udc41\udc42\udc43\udc44\udc45\udc46\udc47\udc48\udc49\udc4a\udc4b\udc4c\udc4d\udc4e\udc4f\udc50\udc51\udc52\udc53\udc54\udc55\udc56\udc57\udc58\udc59\udc5a\udc5b\udc5c\udc5d\udc5e\udc5f\udc60\udc61\udc62\udc63\udc64\udc65\udc66\udc67\udc68\udc69\udc6a\udc6b\udc6c\udc6d\udc6e\udc6f\udc70\udc71\udc72\udc73\udc74\udc75\udc76\udc77\udc78\udc79\udc7a\udc7b\udc7c\udc7d\udc7e\udc7f\udc80\udc81\udc82\udc83\udc84\udc85\udc86\udc87\udc88\udc89\udc8a\udc8b\udc8c\udc8d\udc8e\udc8f\udc90\udc91\udc92\udc93\udc94\udc95\udc96\udc97\udc98\udc99\udc9a\udc9b\udc9c\udc9d\udc9e\udc9f\udca0\udca1\udca2\udca3\udca4\udca5\udca6\udca7\udca8\udca9\udcaa\udcab\udcac\udcad\udcae\udcaf\udcb0\udcb1\udcb2\udcb3\udcb4\udcb5\udcb6\udcb7\udcb8\udcb9\udcba\udcbb\udcbc\udcbd\udcbe\udcbf\udcc0\udcc1\udcc2\udcc3\udcc4\udcc5\udcc6\udcc7\udcc8\udcc9\udcca\udccb\udccc\udccd\udcce\udccf\udcd0\udcd1\udcd2\udcd3\udcd4\udcd5\udcd6\udcd7\udcd8\udcd9\udcda\udcdb\udcdc\udcdd\udcde\udcdf\udce0\udce1\udce2\udce3\udce4\udce5\udce6\udce7\udce8\udce9\udcea\udceb\udcec\udced\udcee\udcef\udcf0\udcf1\udcf2\udcf3\udcf4\udcf5\udcf6\udcf7\udcf8\udcf9\udcfa\udcfb\udcfc\udcfd\udcfe\udcff\udd00\udd01\udd02\udd03\udd04\udd05\udd06\udd07\udd08\udd09\udd0a\udd0b\udd0c\udd0d\udd0e\udd0f\udd10\udd11\udd12\udd13\udd14\udd15\udd16\udd17\udd18\udd19\udd1a\udd1b\udd1c\udd1d\udd1e\udd1f\udd20\udd21\udd22\udd23\udd24\udd25\udd26\udd27\udd28\udd29\udd2a\udd2b\udd2c\udd2d\udd2e\udd2f\udd30\udd31\udd32\udd33\udd34\udd35\udd36\udd37\udd38\udd39\udd3a\udd3b\udd3c\udd3d\udd3e\udd3f\udd40\udd41\udd42\udd43\udd44\udd45\udd46\udd47\udd48\udd49\udd4a\udd4b\udd4c\udd4d\udd4e\udd4f\udd50\udd51\udd52\udd53\udd54\udd55\udd56\udd57\udd58\udd59\udd5a\udd5b\udd5c\udd5d\udd5e\udd5f\udd60\udd61\udd62\udd63\udd64\udd65\udd66\udd67\udd68\udd69\udd6a\udd6b\udd6c\udd6d\udd6e\udd6f\udd70\udd71\udd72\udd73\udd74\udd75\udd76\udd77\udd78\udd79\udd7a\udd7b\udd7c\udd7d\udd7e\udd7f\udd80\udd81\udd82\udd83\udd84\udd85\udd86\udd87\udd88\udd89\udd8a\udd8b\udd8c\udd8d\udd8e\udd8f\udd90\udd91\udd92\udd93\udd94\udd95\udd96\udd97\udd98\udd99\udd9a\udd9b\udd9c\udd9d\udd9e\udd9f\udda0\udda1\udda2\udda3\udda4\udda5\udda6\udda7\udda8\udda9\uddaa\uddab\uddac\uddad\uddae\uddaf\uddb0\uddb1\uddb2\uddb3\uddb4\uddb5\uddb6\uddb7\uddb8\uddb9\uddba\uddbb\uddbc\uddbd\uddbe\uddbf\uddc0\uddc1\uddc2\uddc3\uddc4\uddc5\uddc6\uddc7\uddc8\uddc9\uddca\uddcb\uddcc\uddcd\uddce\uddcf\uddd0\uddd1\uddd2\uddd3\uddd4\uddd5\uddd6\uddd7\uddd8\uddd9\uddda\udddb\udddc\udddd\uddde\udddf\udde0\udde1\udde2\udde3\udde4\udde5\udde6\udde7\udde8\udde9\uddea\uddeb\uddec\udded\uddee\uddef\uddf0\uddf1\uddf2\uddf3\uddf4\uddf5\uddf6\uddf7\uddf8\uddf9\uddfa\uddfb\uddfc\uddfd\uddfe\uddff\ude00\ude01\ude02\ude03\ude04\ude05\ude06\ude07\ude08\ude09\ude0a\ude0b\ude0c\ude0d\ude0e\ude0f\ude10\ude11\ude12\ude13\ude14\ude15\ude16\ude17\ude18\ude19\ude1a\ude1b\ude1c\ude1d\ude1e\ude1f\ude20\ude21\ude22\ude23\ude24\ude25\ude26\ude27\ude28\ude29\ude2a\ude2b\ude2c\ude2d\ude2e\ude2f\ude30\ude31\ude32\ude33\ude34\ude35\ude36\ude37\ude38\ude39\ude3a\ude3b\ude3c\ude3d\ude3e\ude3f\ude40\ude41\ude42\ude43\ude44\ude45\ude46\ude47\ude48\ude49\ude4a\ude4b\ude4c\ude4d\ude4e\ude4f\ude50\ude51\ude52\ude53\ude54\ude55\ude56\ude57\ude58\ude59\ude5a\ude5b\ude5c\ude5d\ude5e\ude5f\ude60\ude61\ude62\ude63\ude64\ude65\ude66\ude67\ude68\ude69\ude6a\ude6b\ude6c\ude6d\ude6e\ude6f\ude70\ude71\ude72\ude73\ude74\ude75\ude76\ude77\ude78\ude79\ude7a\ude7b\ude7c\ude7d\ude7e\ude7f\ude80\ude81\ude82\ude83\ude84\ude85\ude86\ude87\ude88\ude89\ude8a\ude8b\ude8c\ude8d\ude8e\ude8f\ude90\ude91\ude92\ude93\ude94\ude95\ude96\ude97\ude98\ude99\ude9a\ude9b\ude9c\ude9d\ude9e\ude9f\udea0\udea1\udea2\udea3\udea4\udea5\udea6\udea7\udea8\udea9\udeaa\udeab\udeac\udead\udeae\udeaf\udeb0\udeb1\udeb2\udeb3\udeb4\udeb5\udeb6\udeb7\udeb8\udeb9\udeba\udebb\udebc\udebd\udebe\udebf\udec0\udec1\udec2\udec3\udec4\udec5\udec6\udec7\udec8\udec9\udeca\udecb\udecc\udecd\udece\udecf\uded0\uded1\uded2\uded3\uded4\uded5\uded6\uded7\uded8\uded9\udeda\udedb\udedc\udedd\udede\udedf\udee0\udee1\udee2\udee3\udee4\udee5\udee6\udee7\udee8\udee9\udeea\udeeb\udeec\udeed\udeee\udeef\udef0\udef1\udef2\udef3\udef4\udef5\udef6\udef7\udef8\udef9\udefa\udefb\udefc\udefd\udefe\udeff\udf00\udf01\udf02\udf03\udf04\udf05\udf06\udf07\udf08\udf09\udf0a\udf0b\udf0c\udf0d\udf0e\udf0f\udf10\udf11\udf12\udf13\udf14\udf15\udf16\udf17\udf18\udf19\udf1a\udf1b\udf1c\udf1d\udf1e\udf1f\udf20\udf21\udf22\udf23\udf24\udf25\udf26\udf27\udf28\udf29\udf2a\udf2b\udf2c\udf2d\udf2e\udf2f\udf30\udf31\udf32\udf33\udf34\udf35\udf36\udf37\udf38\udf39\udf3a\udf3b\udf3c\udf3d\udf3e\udf3f\udf40\udf41\udf42\udf43\udf44\udf45\udf46\udf47\udf48\udf49\udf4a\udf4b\udf4c\udf4d\udf4e\udf4f\udf50\udf51\udf52\udf53\udf54\udf55\udf56\udf57\udf58\udf59\udf5a\udf5b\udf5c\udf5d\udf5e\udf5f\udf60\udf61\udf62\udf63\udf64\udf65\udf66\udf67\udf68\udf69\udf6a\udf6b\udf6c\udf6d\udf6e\udf6f\udf70\udf71\udf72\udf73\udf74\udf75\udf76\udf77\udf78\udf79\udf7a\udf7b\udf7c\udf7d\udf7e\udf7f\udf80\udf81\udf82\udf83\udf84\udf85\udf86\udf87\udf88\udf89\udf8a\udf8b\udf8c\udf8d\udf8e\udf8f\udf90\udf91\udf92\udf93\udf94\udf95\udf96\udf97\udf98\udf99\udf9a\udf9b\udf9c\udf9d\udf9e\udf9f\udfa0\udfa1\udfa2\udfa3\udfa4\udfa5\udfa6\udfa7\udfa8\udfa9\udfaa\udfab\udfac\udfad\udfae\udfaf\udfb0\udfb1\udfb2\udfb3\udfb4\udfb5\udfb6\udfb7\udfb8\udfb9\udfba\udfbb\udfbc\udfbd\udfbe\udfbf\udfc0\udfc1\udfc2\udfc3\udfc4\udfc5\udfc6\udfc7\udfc8\udfc9\udfca\udfcb\udfcc\udfcd\udfce\udfcf\udfd0\udfd1\udfd2\udfd3\udfd4\udfd5\udfd6\udfd7\udfd8\udfd9\udfda\udfdb\udfdc\udfdd\udfde\udfdf\udfe0\udfe1\udfe2\udfe3\udfe4\udfe5\udfe6\udfe7\udfe8\udfe9\udfea\udfeb\udfec\udfed\udfee\udfef\udff0\udff1\udff2\udff3\udff4\udff5\udff6\udff7\udff8\udff9\udffa\udffb\udffc\udffd\udffe\udfff'")
-except UnicodeDecodeError:
- Cs = '' # Jython can't handle isolated surrogates
-
-Ll = u'abcdefghijklmnopqrstuvwxyz\xaa\xb5\xba\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\u0101\u0103\u0105\u0107\u0109\u010b\u010d\u010f\u0111\u0113\u0115\u0117\u0119\u011b\u011d\u011f\u0121\u0123\u0125\u0127\u0129\u012b\u012d\u012f\u0131\u0133\u0135\u0137\u0138\u013a\u013c\u013e\u0140\u0142\u0144\u0146\u0148\u0149\u014b\u014d\u014f\u0151\u0153\u0155\u0157\u0159\u015b\u015d\u015f\u0161\u0163\u0165\u0167\u0169\u016b\u016d\u016f\u0171\u0173\u0175\u0177\u017a\u017c\u017e\u017f\u0180\u0183\u0185\u0188\u018c\u018d\u0192\u0195\u0199\u019a\u019b\u019e\u01a1\u01a3\u01a5\u01a8\u01aa\u01ab\u01ad\u01b0\u01b4\u01b6\u01b9\u01ba\u01bd\u01be\u01bf\u01c6\u01c9\u01cc\u01ce\u01d0\u01d2\u01d4\u01d6\u01d8\u01da\u01dc\u01dd\u01df\u01e1\u01e3\u01e5\u01e7\u01e9\u01eb\u01ed\u01ef\u01f0\u01f3\u01f5\u01f9\u01fb\u01fd\u01ff\u0201\u0203\u0205\u0207\u0209\u020b\u020d\u020f\u0211\u0213\u0215\u0217\u0219\u021b\u021d\u021f\u0221\u0223\u0225\u0227\u0229\u022b\u022d\u022f\u0231\u0233\u0234\u0235\u0236\u0237\u0238\u0239\u023c\u023f\u0240\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u02ae\u02af\u0390\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca\u03cb\u03cc\u03cd\u03ce\u03d0\u03d1\u03d5\u03d6\u03d7\u03d9\u03db\u03dd\u03df\u03e1\u03e3\u03e5\u03e7\u03e9\u03eb\u03ed\u03ef\u03f0\u03f1\u03f2\u03f3\u03f5\u03f8\u03fb\u03fc\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0461\u0463\u0465\u0467\u0469\u046b\u046d\u046f\u0471\u0473\u0475\u0477\u0479\u047b\u047d\u047f\u0481\u048b\u048d\u048f\u0491\u0493\u0495\u0497\u0499\u049b\u049d\u049f\u04a1\u04a3\u04a5\u04a7\u04a9\u04ab\u04ad\u04af\u04b1\u04b3\u04b5\u04b7\u04b9\u04bb\u04bd\u04bf\u04c2\u04c4\u04c6\u04c8\u04ca\u04cc\u04ce\u04d1\u04d3\u04d5\u04d7\u04d9\u04db\u04dd\u04df\u04e1\u04e3\u04e5\u04e7\u04e9\u04eb\u04ed\u04ef\u04f1\u04f3\u04f5\u04f7\u04f9\u0501\u0503\u0505\u0507\u0509\u050b\u050d\u050f\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582\u0583\u0584\u0585\u0586\u0587\u1d00\u1d01\u1d02\u1d03\u1d04\u1d05\u1d06\u1d07\u1d08\u1d09\u1d0a\u1d0b\u1d0c\u1d0d\u1d0e\u1d0f\u1d10\u1d11\u1d12\u1d13\u1d14\u1d15\u1d16\u1d17\u1d18\u1d19\u1d1a\u1d1b\u1d1c\u1d1d\u1d1e\u1d1f\u1d20\u1d21\u1d22\u1d23\u1d24\u1d25\u1d26\u1d27\u1d28\u1d29\u1d2a\u1d2b\u1d62\u1d63\u1d64\u1d65\u1d66\u1d67\u1d68\u1d69\u1d6a\u1d6b\u1d6c\u1d6d\u1d6e\u1d6f\u1d70\u1d71\u1d72\u1d73\u1d74\u1d75\u1d76\u1d77\u1d79\u1d7a\u1d7b\u1d7c\u1d7d\u1d7e\u1d7f\u1d80\u1d81\u1d82\u1d83\u1d84\u1d85\u1d86\u1d87\u1d88\u1d89\u1d8a\u1d8b\u1d8c\u1d8d\u1d8e\u1d8f\u1d90\u1d91\u1d92\u1d93\u1d94\u1d95\u1d96\u1d97\u1d98\u1d99\u1d9a\u1e01\u1e03\u1e05\u1e07\u1e09\u1e0b\u1e0d\u1e0f\u1e11\u1e13\u1e15\u1e17\u1e19\u1e1b\u1e1d\u1e1f\u1e21\u1e23\u1e25\u1e27\u1e29\u1e2b\u1e2d\u1e2f\u1e31\u1e33\u1e35\u1e37\u1e39\u1e3b\u1e3d\u1e3f\u1e41\u1e43\u1e45\u1e47\u1e49\u1e4b\u1e4d\u1e4f\u1e51\u1e53\u1e55\u1e57\u1e59\u1e5b\u1e5d\u1e5f\u1e61\u1e63\u1e65\u1e67\u1e69\u1e6b\u1e6d\u1e6f\u1e71\u1e73\u1e75\u1e77\u1e79\u1e7b\u1e7d\u1e7f\u1e81\u1e83\u1e85\u1e87\u1e89\u1e8b\u1e8d\u1e8f\u1e91\u1e93\u1e95\u1e96\u1e97\u1e98\u1e99\u1e9a\u1e9b\u1ea1\u1ea3\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u1eb9\u1ebb\u1ebd\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u1ec9\u1ecb\u1ecd\u1ecf\u1ed1\u1ed3\u1ed5\u1ed7\u1ed9\u1edb\u1edd\u1edf\u1ee1\u1ee3\u1ee5\u1ee7\u1ee9\u1eeb\u1eed\u1eef\u1ef1\u1ef3\u1ef5\u1ef7\u1ef9\u1f00\u1f01\u1f02\u1f03\u1f04\u1f05\u1f06\u1f07\u1f10\u1f11\u1f12\u1f13\u1f14\u1f15\u1f20\u1f21\u1f22\u1f23\u1f24\u1f25\u1f26\u1f27\u1f30\u1f31\u1f32\u1f33\u1f34\u1f35\u1f36\u1f37\u1f40\u1f41\u1f42\u1f43\u1f44\u1f45\u1f50\u1f51\u1f52\u1f53\u1f54\u1f55\u1f56\u1f57\u1f60\u1f61\u1f62\u1f63\u1f64\u1f65\u1f66\u1f67\u1f70\u1f71\u1f72\u1f73\u1f74\u1f75\u1f76\u1f77\u1f78\u1f79\u1f7a\u1f7b\u1f7c\u1f7d\u1f80\u1f81\u1f82\u1f83\u1f84\u1f85\u1f86\u1f87\u1f90\u1f91\u1f92\u1f93\u1f94\u1f95\u1f96\u1f97\u1fa0\u1fa1\u1fa2\u1fa3\u1fa4\u1fa5\u1fa6\u1fa7\u1fb0\u1fb1\u1fb2\u1fb3\u1fb4\u1fb6\u1fb7\u1fbe\u1fc2\u1fc3\u1fc4\u1fc6\u1fc7\u1fd0\u1fd1\u1fd2\u1fd3\u1fd6\u1fd7\u1fe0\u1fe1\u1fe2\u1fe3\u1fe4\u1fe5\u1fe6\u1fe7\u1ff2\u1ff3\u1ff4\u1ff6\u1ff7\u2071\u207f\u210a\u210e\u210f\u2113\u212f\u2134\u2139\u213c\u213d\u2146\u2147\u2148\u2149\u2c30\u2c31\u2c32\u2c33\u2c34\u2c35\u2c36\u2c37\u2c38\u2c39\u2c3a\u2c3b\u2c3c\u2c3d\u2c3e\u2c3f\u2c40\u2c41\u2c42\u2c43\u2c44\u2c45\u2c46\u2c47\u2c48\u2c49\u2c4a\u2c4b\u2c4c\u2c4d\u2c4e\u2c4f\u2c50\u2c51\u2c52\u2c53\u2c54\u2c55\u2c56\u2c57\u2c58\u2c59\u2c5a\u2c5b\u2c5c\u2c5d\u2c5e\u2c81\u2c83\u2c85\u2c87\u2c89\u2c8b\u2c8d\u2c8f\u2c91\u2c93\u2c95\u2c97\u2c99\u2c9b\u2c9d\u2c9f\u2ca1\u2ca3\u2ca5\u2ca7\u2ca9\u2cab\u2cad\u2caf\u2cb1\u2cb3\u2cb5\u2cb7\u2cb9\u2cbb\u2cbd\u2cbf\u2cc1\u2cc3\u2cc5\u2cc7\u2cc9\u2ccb\u2ccd\u2ccf\u2cd1\u2cd3\u2cd5\u2cd7\u2cd9\u2cdb\u2cdd\u2cdf\u2ce1\u2ce3\u2ce4\u2d00\u2d01\u2d02\u2d03\u2d04\u2d05\u2d06\u2d07\u2d08\u2d09\u2d0a\u2d0b\u2d0c\u2d0d\u2d0e\u2d0f\u2d10\u2d11\u2d12\u2d13\u2d14\u2d15\u2d16\u2d17\u2d18\u2d19\u2d1a\u2d1b\u2d1c\u2d1d\u2d1e\u2d1f\u2d20\u2d21\u2d22\u2d23\u2d24\u2d25\ufb00\ufb01\ufb02\ufb03\ufb04\ufb05\ufb06\ufb13\ufb14\ufb15\ufb16\ufb17\uff41\uff42\uff43\uff44\uff45\uff46\uff47\uff48\uff49\uff4a\uff4b\uff4c\uff4d\uff4e\uff4f\uff50\uff51\uff52\uff53\uff54\uff55\uff56\uff57\uff58\uff59\uff5a'
-
-Lm = u'\u02b0\u02b1\u02b2\u02b3\u02b4\u02b5\u02b6\u02b7\u02b8\u02b9\u02ba\u02bb\u02bc\u02bd\u02be\u02bf\u02c0\u02c1\u02c6\u02c7\u02c8\u02c9\u02ca\u02cb\u02cc\u02cd\u02ce\u02cf\u02d0\u02d1\u02e0\u02e1\u02e2\u02e3\u02e4\u02ee\u037a\u0559\u0640\u06e5\u06e6\u0e46\u0ec6\u10fc\u17d7\u1843\u1d2c\u1d2d\u1d2e\u1d2f\u1d30\u1d31\u1d32\u1d33\u1d34\u1d35\u1d36\u1d37\u1d38\u1d39\u1d3a\u1d3b\u1d3c\u1d3d\u1d3e\u1d3f\u1d40\u1d41\u1d42\u1d43\u1d44\u1d45\u1d46\u1d47\u1d48\u1d49\u1d4a\u1d4b\u1d4c\u1d4d\u1d4e\u1d4f\u1d50\u1d51\u1d52\u1d53\u1d54\u1d55\u1d56\u1d57\u1d58\u1d59\u1d5a\u1d5b\u1d5c\u1d5d\u1d5e\u1d5f\u1d60\u1d61\u1d78\u1d9b\u1d9c\u1d9d\u1d9e\u1d9f\u1da0\u1da1\u1da2\u1da3\u1da4\u1da5\u1da6\u1da7\u1da8\u1da9\u1daa\u1dab\u1dac\u1dad\u1dae\u1daf\u1db0\u1db1\u1db2\u1db3\u1db4\u1db5\u1db6\u1db7\u1db8\u1db9\u1dba\u1dbb\u1dbc\u1dbd\u1dbe\u1dbf\u2090\u2091\u2092\u2093\u2094\u2d6f\u3005\u3031\u3032\u3033\u3034\u3035\u303b\u309d\u309e\u30fc\u30fd\u30fe\ua015\uff70\uff9e\uff9f'
-
-Lo = u'\u01bb\u01c0\u01c1\u01c2\u01c3\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6\u05e7\u05e8\u05e9\u05ea\u05f0\u05f1\u05f2\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u066e\u066f\u0671\u0672\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d5\u06ee\u06ef\u06fa\u06fb\u06fc\u06ff\u0710\u0712\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u074d\u074e\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07b1\u0904\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u090c\u090d\u090e\u090f\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u0929\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093d\u0950\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u097d\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098c\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9\u09bd\u09ce\u09dc\u09dd\u09df\u09e0\u09e1\u09f0\u09f1\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a\u0a0f\u0a10\u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\u0a1b\u0a1c\u0a1d\u0a1e\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24\u0a25\u0a26\u0a27\u0a28\u0a2a\u0a2b\u0a2c\u0a2d\u0a2e\u0a2f\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59\u0a5a\u0a5b\u0a5c\u0a5e\u0a72\u0a73\u0a74\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\u0a8c\u0a8d\u0a8f\u0a90\u0a91\u0a93\u0a94\u0a95\u0a96\u0a97\u0a98\u0a99\u0a9a\u0a9b\u0a9c\u0a9d\u0a9e\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4\u0aa5\u0aa6\u0aa7\u0aa8\u0aaa\u0aab\u0aac\u0aad\u0aae\u0aaf\u0ab0\u0ab2\u0ab3\u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\u0b0c\u0b0f\u0b10\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\u0b1b\u0b1c\u0b1d\u0b1e\u0b1f\u0b20\u0b21\u0b22\u0b23\u0b24\u0b25\u0b26\u0b27\u0b28\u0b2a\u0b2b\u0b2c\u0b2d\u0b2e\u0b2f\u0b30\u0b32\u0b33\u0b35\u0b36\u0b37\u0b38\u0b39\u0b3d\u0b5c\u0b5d\u0b5f\u0b60\u0b61\u0b71\u0b83\u0b85\u0b86\u0b87\u0b88\u0b89\u0b8a\u0b8e\u0b8f\u0b90\u0b92\u0b93\u0b94\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8\u0ba9\u0baa\u0bae\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c0c\u0c0e\u0c0f\u0c10\u0c12\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\u0c1b\u0c1c\u0c1d\u0c1e\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24\u0c25\u0c26\u0c27\u0c28\u0c2a\u0c2b\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33\u0c35\u0c36\u0c37\u0c38\u0c39\u0c60\u0c61\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a\u0c8b\u0c8c\u0c8e\u0c8f\u0c90\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\u0c9b\u0c9c\u0c9d\u0c9e\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0caa\u0cab\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2\u0cb3\u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\u0d0c\u0d0e\u0d0f\u0d10\u0d12\u0d13\u0d14\u0d15\u0d16\u0d17\u0d18\u0d19\u0d1a\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24\u0d25\u0d26\u0d27\u0d28\u0d2a\u0d2b\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39\u0d60\u0d61\u0d85\u0d86\u0d87\u0d88\u0d89\u0d8a\u0d8b\u0d8c\u0d8d\u0d8e\u0d8f\u0d90\u0d91\u0d92\u0d93\u0d94\u0d95\u0d96\u0d9a\u0d9b\u0d9c\u0d9d\u0d9e\u0d9f\u0da0\u0da1\u0da2\u0da3\u0da4\u0da5\u0da6\u0da7\u0da8\u0da9\u0daa\u0dab\u0dac\u0dad\u0dae\u0daf\u0db0\u0db1\u0db3\u0db4\u0db5\u0db6\u0db7\u0db8\u0db9\u0dba\u0dbb\u0dbd\u0dc0\u0dc1\u0dc2\u0dc3\u0dc4\u0dc5\u0dc6\u0e01\u0e02\u0e03\u0e04\u0e05\u0e06\u0e07\u0e08\u0e09\u0e0a\u0e0b\u0e0c\u0e0d\u0e0e\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17\u0e18\u0e19\u0e1a\u0e1b\u0e1c\u0e1d\u0e1e\u0e1f\u0e20\u0e21\u0e22\u0e23\u0e24\u0e25\u0e26\u0e27\u0e28\u0e29\u0e2a\u0e2b\u0e2c\u0e2d\u0e2e\u0e2f\u0e30\u0e32\u0e33\u0e40\u0e41\u0e42\u0e43\u0e44\u0e45\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94\u0e95\u0e96\u0e97\u0e99\u0e9a\u0e9b\u0e9c\u0e9d\u0e9e\u0e9f\u0ea1\u0ea2\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead\u0eae\u0eaf\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0\u0ec1\u0ec2\u0ec3\u0ec4\u0edc\u0edd\u0f00\u0f40\u0f41\u0f42\u0f43\u0f44\u0f45\u0f46\u0f47\u0f49\u0f4a\u0f4b\u0f4c\u0f4d\u0f4e\u0f4f\u0f50\u0f51\u0f52\u0f53\u0f54\u0f55\u0f56\u0f57\u0f58\u0f59\u0f5a\u0f5b\u0f5c\u0f5d\u0f5e\u0f5f\u0f60\u0f61\u0f62\u0f63\u0f64\u0f65\u0f66\u0f67\u0f68\u0f69\u0f6a\u0f88\u0f89\u0f8a\u0f8b\u1000\u1001\u1002\u1003\u1004\u1005\u1006\u1007\u1008\u1009\u100a\u100b\u100c\u100d\u100e\u100f\u1010\u1011\u1012\u1013\u1014\u1015\u1016\u1017\u1018\u1019\u101a\u101b\u101c\u101d\u101e\u101f\u1020\u1021\u1023\u1024\u1025\u1026\u1027\u1029\u102a\u1050\u1051\u1052\u1053\u1054\u1055\u10d0\u10d1\u10d2\u10d3\u10d4\u10d5\u10d6\u10d7\u10d8\u10d9\u10da\u10db\u10dc\u10dd\u10de\u10df\u10e0\u10e1\u10e2\u10e3\u10e4\u10e5\u10e6\u10e7\u10e8\u10e9\u10ea\u10eb\u10ec\u10ed\u10ee\u10ef\u10f0\u10f1\u10f2\u10f3\u10f4\u10f5\u10f6\u10f7\u10f8\u10f9\u10fa\u1100\u1101\u1102\u1103\u1104\u1105\u1106\u1107\u1108\u1109\u110a\u110b\u110c\u110d\u110e\u110f\u1110\u1111\u1112\u1113\u1114\u1115\u1116\u1117\u1118\u1119\u111a\u111b\u111c\u111d\u111e\u111f\u1120\u1121\u1122\u1123\u1124\u1125\u1126\u1127\u1128\u1129\u112a\u112b\u112c\u112d\u112e\u112f\u1130\u1131\u1132\u1133\u1134\u1135\u1136\u1137\u1138\u1139\u113a\u113b\u113c\u113d\u113e\u113f\u1140\u1141\u1142\u1143\u1144\u1145\u1146\u1147\u1148\u1149\u114a\u114b\u114c\u114d\u114e\u114f\u1150\u1151\u1152\u1153\u1154\u1155\u1156\u1157\u1158\u1159\u115f\u1160\u1161\u1162\u1163\u1164\u1165\u1166\u1167\u1168\u1169\u116a\u116b\u116c\u116d\u116e\u116f\u1170\u1171\u1172\u1173\u1174\u1175\u1176\u1177\u1178\u1179\u117a\u117b\u117c\u117d\u117e\u117f\u1180\u1181\u1182\u1183\u1184\u1185\u1186\u1187\u1188\u1189\u118a\u118b\u118c\u118d\u118e\u118f\u1190\u1191\u1192\u1193\u1194\u1195\u1196\u1197\u1198\u1199\u119a\u119b\u119c\u119d\u119e\u119f\u11a0\u11a1\u11a2\u11a8\u11a9\u11aa\u11ab\u11ac\u11ad\u11ae\u11af\u11b0\u11b1\u11b2\u11b3\u11b4\u11b5\u11b6\u11b7\u11b8\u11b9\u11ba\u11bb\u11bc\u11bd\u11be\u11bf\u11c0\u11c1\u11c2\u11c3\u11c4\u11c5\u11c6\u11c7\u11c8\u11c9\u11ca\u11cb\u11cc\u11cd\u11ce\u11cf\u11d0\u11d1\u11d2\u11d3\u11d4\u11d5\u11d6\u11d7\u11d8\u11d9\u11da\u11db\u11dc\u11dd\u11de\u11df\u11e0\u11e1\u11e2\u11e3\u11e4\u11e5\u11e6\u11e7\u11e8\u11e9\u11ea\u11eb\u11ec\u11ed\u11ee\u11ef\u11f0\u11f1\u11f2\u11f3\u11f4\u11f5\u11f6\u11f7\u11f8\u11f9\u1200\u1201\u1202\u1203\u1204\u1205\u1206\u1207\u1208\u1209\u120a\u120b\u120c\u120d\u120e\u120f\u1210\u1211\u1212\u1213\u1214\u1215\u1216\u1217\u1218\u1219\u121a\u121b\u121c\u121d\u121e\u121f\u1220\u1221\u1222\u1223\u1224\u1225\u1226\u1227\u1228\u1229\u122a\u122b\u122c\u122d\u122e\u122f\u1230\u1231\u1232\u1233\u1234\u1235\u1236\u1237\u1238\u1239\u123a\u123b\u123c\u123d\u123e\u123f\u1240\u1241\u1242\u1243\u1244\u1245\u1246\u1247\u1248\u124a\u124b\u124c\u124d\u1250\u1251\u1252\u1253\u1254\u1255\u1256\u1258\u125a\u125b\u125c\u125d\u1260\u1261\u1262\u1263\u1264\u1265\u1266\u1267\u1268\u1269\u126a\u126b\u126c\u126d\u126e\u126f\u1270\u1271\u1272\u1273\u1274\u1275\u1276\u1277\u1278\u1279\u127a\u127b\u127c\u127d\u127e\u127f\u1280\u1281\u1282\u1283\u1284\u1285\u1286\u1287\u1288\u128a\u128b\u128c\u128d\u1290\u1291\u1292\u1293\u1294\u1295\u1296\u1297\u1298\u1299\u129a\u129b\u129c\u129d\u129e\u129f\u12a0\u12a1\u12a2\u12a3\u12a4\u12a5\u12a6\u12a7\u12a8\u12a9\u12aa\u12ab\u12ac\u12ad\u12ae\u12af\u12b0\u12b2\u12b3\u12b4\u12b5\u12b8\u12b9\u12ba\u12bb\u12bc\u12bd\u12be\u12c0\u12c2\u12c3\u12c4\u12c5\u12c8\u12c9\u12ca\u12cb\u12cc\u12cd\u12ce\u12cf\u12d0\u12d1\u12d2\u12d3\u12d4\u12d5\u12d6\u12d8\u12d9\u12da\u12db\u12dc\u12dd\u12de\u12df\u12e0\u12e1\u12e2\u12e3\u12e4\u12e5\u12e6\u12e7\u12e8\u12e9\u12ea\u12eb\u12ec\u12ed\u12ee\u12ef\u12f0\u12f1\u12f2\u12f3\u12f4\u12f5\u12f6\u12f7\u12f8\u12f9\u12fa\u12fb\u12fc\u12fd\u12fe\u12ff\u1300\u1301\u1302\u1303\u1304\u1305\u1306\u1307\u1308\u1309\u130a\u130b\u130c\u130d\u130e\u130f\u1310\u1312\u1313\u1314\u1315\u1318\u1319\u131a\u131b\u131c\u131d\u131e\u131f\u1320\u1321\u1322\u1323\u1324\u1325\u1326\u1327\u1328\u1329\u132a\u132b\u132c\u132d\u132e\u132f\u1330\u1331\u1332\u1333\u1334\u1335\u1336\u1337\u1338\u1339\u133a\u133b\u133c\u133d\u133e\u133f\u1340\u1341\u1342\u1343\u1344\u1345\u1346\u1347\u1348\u1349\u134a\u134b\u134c\u134d\u134e\u134f\u1350\u1351\u1352\u1353\u1354\u1355\u1356\u1357\u1358\u1359\u135a\u1380\u1381\u1382\u1383\u1384\u1385\u1386\u1387\u1388\u1389\u138a\u138b\u138c\u138d\u138e\u138f\u13a0\u13a1\u13a2\u13a3\u13a4\u13a5\u13a6\u13a7\u13a8\u13a9\u13aa\u13ab\u13ac\u13ad\u13ae\u13af\u13b0\u13b1\u13b2\u13b3\u13b4\u13b5\u13b6\u13b7\u13b8\u13b9\u13ba\u13bb\u13bc\u13bd\u13be\u13bf\u13c0\u13c1\u13c2\u13c3\u13c4\u13c5\u13c6\u13c7\u13c8\u13c9\u13ca\u13cb\u13cc\u13cd\u13ce\u13cf\u13d0\u13d1\u13d2\u13d3\u13d4\u13d5\u13d6\u13d7\u13d8\u13d9\u13da\u13db\u13dc\u13dd\u13de\u13df\u13e0\u13e1\u13e2\u13e3\u13e4\u13e5\u13e6\u13e7\u13e8\u13e9\u13ea\u13eb\u13ec\u13ed\u13ee\u13ef\u13f0\u13f1\u13f2\u13f3\u13f4\u1401\u1402\u1403\u1404\u1405\u1406\u1407\u1408\u1409\u140a\u140b\u140c\u140d\u140e\u140f\u1410\u1411\u1412\u1413\u1414\u1415\u1416\u1417\u1418\u1419\u141a\u141b\u141c\u141d\u141e\u141f\u1420\u1421\u1422\u1423\u1424\u1425\u1426\u1427\u1428\u1429\u142a\u142b\u142c\u142d\u142e\u142f\u1430\u1431\u1432\u1433\u1434\u1435\u1436\u1437\u1438\u1439\u143a\u143b\u143c\u143d\u143e\u143f\u1440\u1441\u1442\u1443\u1444\u1445\u1446\u1447\u1448\u1449\u144a\u144b\u144c\u144d\u144e\u144f\u1450\u1451\u1452\u1453\u1454\u1455\u1456\u1457\u1458\u1459\u145a\u145b\u145c\u145d\u145e\u145f\u1460\u1461\u1462\u1463\u1464\u1465\u1466\u1467\u1468\u1469\u146a\u146b\u146c\u146d\u146e\u146f\u1470\u1471\u1472\u1473\u1474\u1475\u1476\u1477\u1478\u1479\u147a\u147b\u147c\u147d\u147e\u147f\u1480\u1481\u1482\u1483\u1484\u1485\u1486\u1487\u1488\u1489\u148a\u148b\u148c\u148d\u148e\u148f\u1490\u1491\u1492\u1493\u1494\u1495\u1496\u1497\u1498\u1499\u149a\u149b\u149c\u149d\u149e\u149f\u14a0\u14a1\u14a2\u14a3\u14a4\u14a5\u14a6\u14a7\u14a8\u14a9\u14aa\u14ab\u14ac\u14ad\u14ae\u14af\u14b0\u14b1\u14b2\u14b3\u14b4\u14b5\u14b6\u14b7\u14b8\u14b9\u14ba\u14bb\u14bc\u14bd\u14be\u14bf\u14c0\u14c1\u14c2\u14c3\u14c4\u14c5\u14c6\u14c7\u14c8\u14c9\u14ca\u14cb\u14cc\u14cd\u14ce\u14cf\u14d0\u14d1\u14d2\u14d3\u14d4\u14d5\u14d6\u14d7\u14d8\u14d9\u14da\u14db\u14dc\u14dd\u14de\u14df\u14e0\u14e1\u14e2\u14e3\u14e4\u14e5\u14e6\u14e7\u14e8\u14e9\u14ea\u14eb\u14ec\u14ed\u14ee\u14ef\u14f0\u14f1\u14f2\u14f3\u14f4\u14f5\u14f6\u14f7\u14f8\u14f9\u14fa\u14fb\u14fc\u14fd\u14fe\u14ff\u1500\u1501\u1502\u1503\u1504\u1505\u1506\u1507\u1508\u1509\u150a\u150b\u150c\u150d\u150e\u150f\u1510\u1511\u1512\u1513\u1514\u1515\u1516\u1517\u1518\u1519\u151a\u151b\u151c\u151d\u151e\u151f\u1520\u1521\u1522\u1523\u1524\u1525\u1526\u1527\u1528\u1529\u152a\u152b\u152c\u152d\u152e\u152f\u1530\u1531\u1532\u1533\u1534\u1535\u1536\u1537\u1538\u1539\u153a\u153b\u153c\u153d\u153e\u153f\u1540\u1541\u1542\u1543\u1544\u1545\u1546\u1547\u1548\u1549\u154a\u154b\u154c\u154d\u154e\u154f\u1550\u1551\u1552\u1553\u1554\u1555\u1556\u1557\u1558\u1559\u155a\u155b\u155c\u155d\u155e\u155f\u1560\u1561\u1562\u1563\u1564\u1565\u1566\u1567\u1568\u1569\u156a\u156b\u156c\u156d\u156e\u156f\u1570\u1571\u1572\u1573\u1574\u1575\u1576\u1577\u1578\u1579\u157a\u157b\u157c\u157d\u157e\u157f\u1580\u1581\u1582\u1583\u1584\u1585\u1586\u1587\u1588\u1589\u158a\u158b\u158c\u158d\u158e\u158f\u1590\u1591\u1592\u1593\u1594\u1595\u1596\u1597\u1598\u1599\u159a\u159b\u159c\u159d\u159e\u159f\u15a0\u15a1\u15a2\u15a3\u15a4\u15a5\u15a6\u15a7\u15a8\u15a9\u15aa\u15ab\u15ac\u15ad\u15ae\u15af\u15b0\u15b1\u15b2\u15b3\u15b4\u15b5\u15b6\u15b7\u15b8\u15b9\u15ba\u15bb\u15bc\u15bd\u15be\u15bf\u15c0\u15c1\u15c2\u15c3\u15c4\u15c5\u15c6\u15c7\u15c8\u15c9\u15ca\u15cb\u15cc\u15cd\u15ce\u15cf\u15d0\u15d1\u15d2\u15d3\u15d4\u15d5\u15d6\u15d7\u15d8\u15d9\u15da\u15db\u15dc\u15dd\u15de\u15df\u15e0\u15e1\u15e2\u15e3\u15e4\u15e5\u15e6\u15e7\u15e8\u15e9\u15ea\u15eb\u15ec\u15ed\u15ee\u15ef\u15f0\u15f1\u15f2\u15f3\u15f4\u15f5\u15f6\u15f7\u15f8\u15f9\u15fa\u15fb\u15fc\u15fd\u15fe\u15ff\u1600\u1601\u1602\u1603\u1604\u1605\u1606\u1607\u1608\u1609\u160a\u160b\u160c\u160d\u160e\u160f\u1610\u1611\u1612\u1613\u1614\u1615\u1616\u1617\u1618\u1619\u161a\u161b\u161c\u161d\u161e\u161f\u1620\u1621\u1622\u1623\u1624\u1625\u1626\u1627\u1628\u1629\u162a\u162b\u162c\u162d\u162e\u162f\u1630\u1631\u1632\u1633\u1634\u1635\u1636\u1637\u1638\u1639\u163a\u163b\u163c\u163d\u163e\u163f\u1640\u1641\u1642\u1643\u1644\u1645\u1646\u1647\u1648\u1649\u164a\u164b\u164c\u164d\u164e\u164f\u1650\u1651\u1652\u1653\u1654\u1655\u1656\u1657\u1658\u1659\u165a\u165b\u165c\u165d\u165e\u165f\u1660\u1661\u1662\u1663\u1664\u1665\u1666\u1667\u1668\u1669\u166a\u166b\u166c\u166f\u1670\u1671\u1672\u1673\u1674\u1675\u1676\u1681\u1682\u1683\u1684\u1685\u1686\u1687\u1688\u1689\u168a\u168b\u168c\u168d\u168e\u168f\u1690\u1691\u1692\u1693\u1694\u1695\u1696\u1697\u1698\u1699\u169a\u16a0\u16a1\u16a2\u16a3\u16a4\u16a5\u16a6\u16a7\u16a8\u16a9\u16aa\u16ab\u16ac\u16ad\u16ae\u16af\u16b0\u16b1\u16b2\u16b3\u16b4\u16b5\u16b6\u16b7\u16b8\u16b9\u16ba\u16bb\u16bc\u16bd\u16be\u16bf\u16c0\u16c1\u16c2\u16c3\u16c4\u16c5\u16c6\u16c7\u16c8\u16c9\u16ca\u16cb\u16cc\u16cd\u16ce\u16cf\u16d0\u16d1\u16d2\u16d3\u16d4\u16d5\u16d6\u16d7\u16d8\u16d9\u16da\u16db\u16dc\u16dd\u16de\u16df\u16e0\u16e1\u16e2\u16e3\u16e4\u16e5\u16e6\u16e7\u16e8\u16e9\u16ea\u1700\u1701\u1702\u1703\u1704\u1705\u1706\u1707\u1708\u1709\u170a\u170b\u170c\u170e\u170f\u1710\u1711\u1720\u1721\u1722\u1723\u1724\u1725\u1726\u1727\u1728\u1729\u172a\u172b\u172c\u172d\u172e\u172f\u1730\u1731\u1740\u1741\u1742\u1743\u1744\u1745\u1746\u1747\u1748\u1749\u174a\u174b\u174c\u174d\u174e\u174f\u1750\u1751\u1760\u1761\u1762\u1763\u1764\u1765\u1766\u1767\u1768\u1769\u176a\u176b\u176c\u176e\u176f\u1770\u1780\u1781\u1782\u1783\u1784\u1785\u1786\u1787\u1788\u1789\u178a\u178b\u178c\u178d\u178e\u178f\u1790\u1791\u1792\u1793\u1794\u1795\u1796\u1797\u1798\u1799\u179a\u179b\u179c\u179d\u179e\u179f\u17a0\u17a1\u17a2\u17a3\u17a4\u17a5\u17a6\u17a7\u17a8\u17a9\u17aa\u17ab\u17ac\u17ad\u17ae\u17af\u17b0\u17b1\u17b2\u17b3\u17dc\u1820\u1821\u1822\u1823\u1824\u1825\u1826\u1827\u1828\u1829\u182a\u182b\u182c\u182d\u182e\u182f\u1830\u1831\u1832\u1833\u1834\u1835\u1836\u1837\u1838\u1839\u183a\u183b\u183c\u183d\u183e\u183f\u1840\u1841\u1842\u1844\u1845\u1846\u1847\u1848\u1849\u184a\u184b\u184c\u184d\u184e\u184f\u1850\u1851\u1852\u1853\u1854\u1855\u1856\u1857\u1858\u1859\u185a\u185b\u185c\u185d\u185e\u185f\u1860\u1861\u1862\u1863\u1864\u1865\u1866\u1867\u1868\u1869\u186a\u186b\u186c\u186d\u186e\u186f\u1870\u1871\u1872\u1873\u1874\u1875\u1876\u1877\u1880\u1881\u1882\u1883\u1884\u1885\u1886\u1887\u1888\u1889\u188a\u188b\u188c\u188d\u188e\u188f\u1890\u1891\u1892\u1893\u1894\u1895\u1896\u1897\u1898\u1899\u189a\u189b\u189c\u189d\u189e\u189f\u18a0\u18a1\u18a2\u18a3\u18a4\u18a5\u18a6\u18a7\u18a8\u1900\u1901\u1902\u1903\u1904\u1905\u1906\u1907\u1908\u1909\u190a\u190b\u190c\u190d\u190e\u190f\u1910\u1911\u1912\u1913\u1914\u1915\u1916\u1917\u1918\u1919\u191a\u191b\u191c\u1950\u1951\u1952\u1953\u1954\u1955\u1956\u1957\u1958\u1959\u195a\u195b\u195c\u195d\u195e\u195f\u1960\u1961\u1962\u1963\u1964\u1965\u1966\u1967\u1968\u1969\u196a\u196b\u196c\u196d\u1970\u1971\u1972\u1973\u1974\u1980\u1981\u1982\u1983\u1984\u1985\u1986\u1987\u1988\u1989\u198a\u198b\u198c\u198d\u198e\u198f\u1990\u1991\u1992\u1993\u1994\u1995\u1996\u1997\u1998\u1999\u199a\u199b\u199c\u199d\u199e\u199f\u19a0\u19a1\u19a2\u19a3\u19a4\u19a5\u19a6\u19a7\u19a8\u19a9\u19c1\u19c2\u19c3\u19c4\u19c5\u19c6\u19c7\u1a00\u1a01\u1a02\u1a03\u1a04\u1a05\u1a06\u1a07\u1a08\u1a09\u1a0a\u1a0b\u1a0c\u1a0d\u1a0e\u1a0f\u1a10\u1a11\u1a12\u1a13\u1a14\u1a15\u1a16\u2135\u2136\u2137\u2138\u2d30\u2d31\u2d32\u2d33\u2d34\u2d35\u2d36\u2d37\u2d38\u2d39\u2d3a\u2d3b\u2d3c\u2d3d\u2d3e\u2d3f\u2d40\u2d41\u2d42\u2d43\u2d44\u2d45\u2d46\u2d47\u2d48\u2d49\u2d4a\u2d4b\u2d4c\u2d4d\u2d4e\u2d4f\u2d50\u2d51\u2d52\u2d53\u2d54\u2d55\u2d56\u2d57\u2d58\u2d59\u2d5a\u2d5b\u2d5c\u2d5d\u2d5e\u2d5f\u2d60\u2d61\u2d62\u2d63\u2d64\u2d65\u2d80\u2d81\u2d82\u2d83\u2d84\u2d85\u2d86\u2d87\u2d88\u2d89\u2d8a\u2d8b\u2d8c\u2d8d\u2d8e\u2d8f\u2d90\u2d91\u2d92\u2d93\u2d94\u2d95\u2d96\u2da0\u2da1\u2da2\u2da3\u2da4\u2da5\u2da6\u2da8\u2da9\u2daa\u2dab\u2dac\u2dad\u2dae\u2db0\u2db1\u2db2\u2db3\u2db4\u2db5\u2db6\u2db8\u2db9\u2dba\u2dbb\u2dbc\u2dbd\u2dbe\u2dc0\u2dc1\u2dc2\u2dc3\u2dc4\u2dc5\u2dc6\u2dc8\u2dc9\u2dca\u2dcb\u2dcc\u2dcd\u2dce\u2dd0\u2dd1\u2dd2\u2dd3\u2dd4\u2dd5\u2dd6\u2dd8\u2dd9\u2dda\u2ddb\u2ddc\u2ddd\u2dde\u3006\u303c\u3041\u3042\u3043\u3044\u3045\u3046\u3047\u3048\u3049\u304a\u304b\u304c\u304d\u304e\u304f\u3050\u3051\u3052\u3053\u3054\u3055\u3056\u3057\u3058\u3059\u305a\u305b\u305c\u305d\u305e\u305f\u3060\u3061\u3062\u3063\u3064\u3065\u3066\u3067\u3068\u3069\u306a\u306b\u306c\u306d\u306e\u306f\u3070\u3071\u3072\u3073\u3074\u3075\u3076\u3077\u3078\u3079\u307a\u307b\u307c\u307d\u307e\u307f\u3080\u3081\u3082\u3083\u3084\u3085\u3086\u3087\u3088\u3089\u308a\u308b\u308c\u308d\u308e\u308f\u3090\u3091\u3092\u3093\u3094\u3095\u3096\u309f\u30a1\u30a2\u30a3\u30a4\u30a5\u30a6\u30a7\u30a8\u30a9\u30aa\u30ab\u30ac\u30ad\u30ae\u30af\u30b0\u30b1\u30b2\u30b3\u30b4\u30b5\u30b6\u30b7\u30b8\u30b9\u30ba\u30bb\u30bc\u30bd\u30be\u30bf\u30c0\u30c1\u30c2\u30c3\u30c4\u30c5\u30c6\u30c7\u30c8\u30c9\u30ca\u30cb\u30cc\u30cd\u30ce\u30cf\u30d0\u30d1\u30d2\u30d3\u30d4\u30d5\u30d6\u30d7\u30d8\u30d9\u30da\u30db\u30dc\u30dd\u30de\u30df\u30e0\u30e1\u30e2\u30e3\u30e4\u30e5\u30e6\u30e7\u30e8\u30e9\u30ea\u30eb\u30ec\u30ed\u30ee\u30ef\u30f0\u30f1\u30f2\u30f3\u30f4\u30f5\u30f6\u30f7\u30f8\u30f9\u30fa\u30ff\u3105\u3106\u3107\u3108\u3109\u310a\u310b\u310c\u310d\u310e\u310f\u3110\u3111\u3112\u3113\u3114\u3115\u3116\u3117\u3118\u3119\u311a\u311b\u311c\u311d\u311e\u311f\u3120\u3121\u3122\u3123\u3124\u3125\u3126\u3127\u3128\u3129\u312a\u312b\u312c\u3131\u3132\u3133\u3134\u3135\u3136\u3137\u3138\u3139\u313a\u313b\u313c\u313d\u313e\u313f\u3140\u3141\u3142\u3143\u3144\u3145\u3146\u3147\u3148\u3149\u314a\u314b\u314c\u314d\u314e\u314f\u3150\u3151\u3152\u3153\u3154\u3155\u3156\u3157\u3158\u3159\u315a\u315b\u315c\u315d\u315e\u315f\u3160\u3161\u3162\u3163\u3164\u3165\u3166\u3167\u3168\u3169\u316a\u316b\u316c\u316d\u316e\u316f\u3170\u3171\u3172\u3173\u3174\u3175\u3176\u3177\u3178\u3179\u317a\u317b\u317c\u317d\u317e\u317f\u3180\u3181\u3182\u3183\u3184\u3185\u3186\u3187\u3188\u3189\u318a\u318b\u318c\u318d\u318e\u31a0\u31a1\u31a2\u31a3\u31a4\u31a5\u31a6\u31a7\u31a8\u31a9\u31aa\u31ab\u31ac\u31ad\u31ae\u31af\u31b0\u31b1\u31b2\u31b3\u31b4\u31b5\u31b6\u31b7\u31f0\u31f1\u31f2\u31f3\u31f4\u31f5\u31f6\u31f7\u31f8\u31f9\u31fa\u31fb\u31fc\u31fd\u31fe\u31ff\u3400\u3401\u3402\u3403\u3404\u3405\u3406\u3407\u3408\u3409\u340a\u340b\u340c\u340d\u340e\u340f\u3410\u3411\u3412\u3413\u3414\u3415\u3416\u3417\u3418\u3419\u341a\u341b\u341c\u341d\u341e\u341f\u3420\u3421\u3422\u3423\u3424\u3425\u3426\u3427\u3428\u3429\u342a\u342b\u342c\u342d\u342e\u342f\u3430\u3431\u3432\u3433\u3434\u3435\u3436\u3437\u3438\u3439\u343a\u343b\u343c\u343d\u343e\u343f\u3440\u3441\u3442\u3443\u3444\u3445\u3446\u3447\u3448\u3449\u344a\u344b\u344c\u344d\u344e\u344f\u3450\u3451\u3452\u3453\u3454\u3455\u3456\u3457\u3458\u3459\u345a\u345b\u345c\u345d\u345e\u345f\u3460\u3461\u3462\u3463\u3464\u3465\u3466\u3467\u3468\u3469\u346a\u346b\u346c\u346d\u346e\u346f\u3470\u3471\u3472\u3473\u3474\u3475\u3476\u3477\u3478\u3479\u347a\u347b\u347c\u347d\u347e\u347f\u3480\u3481\u3482\u3483\u3484\u3485\u3486\u3487\u3488\u3489\u348a\u348b\u348c\u348d\u348e\u348f\u3490\u3491\u3492\u3493\u3494\u3495\u3496\u3497\u3498\u3499\u349a\u349b\u349c\u349d\u349e\u349f\u34a0\u34a1\u34a2\u34a3\u34a4\u34a5\u34a6\u34a7\u34a8\u34a9\u34aa\u34ab\u34ac\u34ad\u34ae\u34af\u34b0\u34b1\u34b2\u34b3\u34b4\u34b5\u34b6\u34b7\u34b8\u34b9\u34ba\u34bb\u34bc\u34bd\u34be\u34bf\u34c0\u34c1\u34c2\u34c3\u34c4\u34c5\u34c6\u34c7\u34c8\u34c9\u34ca\u34cb\u34cc\u34cd\u34ce\u34cf\u34d0\u34d1\u34d2\u34d3\u34d4\u34d5\u34d6\u34d7\u34d8\u34d9\u34da\u34db\u34dc\u34dd\u34de\u34df\u34e0\u34e1\u34e2\u34e3\u34e4\u34e5\u34e6\u34e7\u34e8\u34e9\u34ea\u34eb\u34ec\u34ed\u34ee\u34ef\u34f0\u34f1\u34f2\u34f3\u34f4\u34f5\u34f6\u34f7\u34f8\u34f9\u34fa\u34fb\u34fc\u34fd\u34fe\u34ff\u3500\u3501\u3502\u3503\u3504\u3505\u3506\u3507\u3508\u3509\u350a\u350b\u350c\u350d\u350e\u350f\u3510\u3511\u3512\u3513\u3514\u3515\u3516\u3517\u3518\u3519\u351a\u351b\u351c\u351d\u351e\u351f\u3520\u3521\u3522\u3523\u3524\u3525\u3526\u3527\u3528\u3529\u352a\u352b\u352c\u352d\u352e\u352f\u3530\u3531\u3532\u3533\u3534\u3535\u3536\u3537\u3538\u3539\u353a\u353b\u353c\u353d\u353e\u353f\u3540\u3541\u3542\u3543\u3544\u3545\u3546\u3547\u3548\u3549\u354a\u354b\u354c\u354d\u354e\u354f\u3550\u3551\u3552\u3553\u3554\u3555\u3556\u3557\u3558\u3559\u355a\u355b\u355c\u355d\u355e\u355f\u3560\u3561\u3562\u3563\u3564\u3565\u3566\u3567\u3568\u3569\u356a\u356b\u356c\u356d\u356e\u356f\u3570\u3571\u3572\u3573\u3574\u3575\u3576\u3577\u3578\u3579\u357a\u357b\u357c\u357d\u357e\u357f\u3580\u3581\u3582\u3583\u3584\u3585\u3586\u3587\u3588\u3589\u358a\u358b\u358c\u358d\u358e\u358f\u3590\u3591\u3592\u3593\u3594\u3595\u3596\u3597\u3598\u3599\u359a\u359b\u359c\u359d\u359e\u359f\u35a0\u35a1\u35a2\u35a3\u35a4\u35a5\u35a6\u35a7\u35a8\u35a9\u35aa\u35ab\u35ac\u35ad\u35ae\u35af\u35b0\u35b1\u35b2\u35b3\u35b4\u35b5\u35b6\u35b7\u35b8\u35b9\u35ba\u35bb\u35bc\u35bd\u35be\u35bf\u35c0\u35c1\u35c2\u35c3\u35c4\u35c5\u35c6\u35c7\u35c8\u35c9\u35ca\u35cb\u35cc\u35cd\u35ce\u35cf\u35d0\u35d1\u35d2\u35d3\u35d4\u35d5\u35d6\u35d7\u35d8\u35d9\u35da\u35db\u35dc\u35dd\u35de\u35df\u35e0\u35e1\u35e2\u35e3\u35e4\u35e5\u35e6\u35e7\u35e8\u35e9\u35ea\u35eb\u35ec\u35ed\u35ee\u35ef\u35f0\u35f1\u35f2\u35f3\u35f4\u35f5\u35f6\u35f7\u35f8\u35f9\u35fa\u35fb\u35fc\u35fd\u35fe\u35ff\u3600\u3601\u3602\u3603\u3604\u3605\u3606\u3607\u3608\u3609\u360a\u360b\u360c\u360d\u360e\u360f\u3610\u3611\u3612\u3613\u3614\u3615\u3616\u3617\u3618\u3619\u361a\u361b\u361c\u361d\u361e\u361f\u3620\u3621\u3622\u3623\u3624\u3625\u3626\u3627\u3628\u3629\u362a\u362b\u362c\u362d\u362e\u362f\u3630\u3631\u3632\u3633\u3634\u3635\u3636\u3637\u3638\u3639\u363a\u363b\u363c\u363d\u363e\u363f\u3640\u3641\u3642\u3643\u3644\u3645\u3646\u3647\u3648\u3649\u364a\u364b\u364c\u364d\u364e\u364f\u3650\u3651\u3652\u3653\u3654\u3655\u3656\u3657\u3658\u3659\u365a\u365b\u365c\u365d\u365e\u365f\u3660\u3661\u3662\u3663\u3664\u3665\u3666\u3667\u3668\u3669\u366a\u366b\u366c\u366d\u366e\u366f\u3670\u3671\u3672\u3673\u3674\u3675\u3676\u3677\u3678\u3679\u367a\u367b\u367c\u367d\u367e\u367f\u3680\u3681\u3682\u3683\u3684\u3685\u3686\u3687\u3688\u3689\u368a\u368b\u368c\u368d\u368e\u368f\u3690\u3691\u3692\u3693\u3694\u3695\u3696\u3697\u3698\u3699\u369a\u369b\u369c\u369d\u369e\u369f\u36a0\u36a1\u36a2\u36a3\u36a4\u36a5\u36a6\u36a7\u36a8\u36a9\u36aa\u36ab\u36ac\u36ad\u36ae\u36af\u36b0\u36b1\u36b2\u36b3\u36b4\u36b5\u36b6\u36b7\u36b8\u36b9\u36ba\u36bb\u36bc\u36bd\u36be\u36bf\u36c0\u36c1\u36c2\u36c3\u36c4\u36c5\u36c6\u36c7\u36c8\u36c9\u36ca\u36cb\u36cc\u36cd\u36ce\u36cf\u36d0\u36d1\u36d2\u36d3\u36d4\u36d5\u36d6\u36d7\u36d8\u36d9\u36da\u36db\u36dc\u36dd\u36de\u36df\u36e0\u36e1\u36e2\u36e3\u36e4\u36e5\u36e6\u36e7\u36e8\u36e9\u36ea\u36eb\u36ec\u36ed\u36ee\u36ef\u36f0\u36f1\u36f2\u36f3\u36f4\u36f5\u36f6\u36f7\u36f8\u36f9\u36fa\u36fb\u36fc\u36fd\u36fe\u36ff\u3700\u3701\u3702\u3703\u3704\u3705\u3706\u3707\u3708\u3709\u370a\u370b\u370c\u370d\u370e\u370f\u3710\u3711\u3712\u3713\u3714\u3715\u3716\u3717\u3718\u3719\u371a\u371b\u371c\u371d\u371e\u371f\u3720\u3721\u3722\u3723\u3724\u3725\u3726\u3727\u3728\u3729\u372a\u372b\u372c\u372d\u372e\u372f\u3730\u3731\u3732\u3733\u3734\u3735\u3736\u3737\u3738\u3739\u373a\u373b\u373c\u373d\u373e\u373f\u3740\u3741\u3742\u3743\u3744\u3745\u3746\u3747\u3748\u3749\u374a\u374b\u374c\u374d\u374e\u374f\u3750\u3751\u3752\u3753\u3754\u3755\u3756\u3757\u3758\u3759\u375a\u375b\u375c\u375d\u375e\u375f\u3760\u3761\u3762\u3763\u3764\u3765\u3766\u3767\u3768\u3769\u376a\u376b\u376c\u376d\u376e\u376f\u3770\u3771\u3772\u3773\u3774\u3775\u3776\u3777\u3778\u3779\u377a\u377b\u377c\u377d\u377e\u377f\u3780\u3781\u3782\u3783\u3784\u3785\u3786\u3787\u3788\u3789\u378a\u378b\u378c\u378d\u378e\u378f\u3790\u3791\u3792\u3793\u3794\u3795\u3796\u3797\u3798\u3799\u379a\u379b\u379c\u379d\u379e\u379f\u37a0\u37a1\u37a2\u37a3\u37a4\u37a5\u37a6\u37a7\u37a8\u37a9\u37aa\u37ab\u37ac\u37ad\u37ae\u37af\u37b0\u37b1\u37b2\u37b3\u37b4\u37b5\u37b6\u37b7\u37b8\u37b9\u37ba\u37bb\u37bc\u37bd\u37be\u37bf\u37c0\u37c1\u37c2\u37c3\u37c4\u37c5\u37c6\u37c7\u37c8\u37c9\u37ca\u37cb\u37cc\u37cd\u37ce\u37cf\u37d0\u37d1\u37d2\u37d3\u37d4\u37d5\u37d6\u37d7\u37d8\u37d9\u37da\u37db\u37dc\u37dd\u37de\u37df\u37e0\u37e1\u37e2\u37e3\u37e4\u37e5\u37e6\u37e7\u37e8\u37e9\u37ea\u37eb\u37ec\u37ed\u37ee\u37ef\u37f0\u37f1\u37f2\u37f3\u37f4\u37f5\u37f6\u37f7\u37f8\u37f9\u37fa\u37fb\u37fc\u37fd\u37fe\u37ff\u3800\u3801\u3802\u3803\u3804\u3805\u3806\u3807\u3808\u3809\u380a\u380b\u380c\u380d\u380e\u380f\u3810\u3811\u3812\u3813\u3814\u3815\u3816\u3817\u3818\u3819\u381a\u381b\u381c\u381d\u381e\u381f\u3820\u3821\u3822\u3823\u3824\u3825\u3826\u3827\u3828\u3829\u382a\u382b\u382c\u382d\u382e\u382f\u3830\u3831\u3832\u3833\u3834\u3835\u3836\u3837\u3838\u3839\u383a\u383b\u383c\u383d\u383e\u383f\u3840\u3841\u3842\u3843\u3844\u3845\u3846\u3847\u3848\u3849\u384a\u384b\u384c\u384d\u384e\u384f\u3850\u3851\u3852\u3853\u3854\u3855\u3856\u3857\u3858\u3859\u385a\u385b\u385c\u385d\u385e\u385f\u3860\u3861\u3862\u3863\u3864\u3865\u3866\u3867\u3868\u3869\u386a\u386b\u386c\u386d\u386e\u386f\u3870\u3871\u3872\u3873\u3874\u3875\u3876\u3877\u3878\u3879\u387a\u387b\u387c\u387d\u387e\u387f\u3880\u3881\u3882\u3883\u3884\u3885\u3886\u3887\u3888\u3889\u388a\u388b\u388c\u388d\u388e\u388f\u3890\u3891\u3892\u3893\u3894\u3895\u3896\u3897\u3898\u3899\u389a\u389b\u389c\u389d\u389e\u389f\u38a0\u38a1\u38a2\u38a3\u38a4\u38a5\u38a6\u38a7\u38a8\u38a9\u38aa\u38ab\u38ac\u38ad\u38ae\u38af\u38b0\u38b1\u38b2\u38b3\u38b4\u38b5\u38b6\u38b7\u38b8\u38b9\u38ba\u38bb\u38bc\u38bd\u38be\u38bf\u38c0\u38c1\u38c2\u38c3\u38c4\u38c5\u38c6\u38c7\u38c8\u38c9\u38ca\u38cb\u38cc\u38cd\u38ce\u38cf\u38d0\u38d1\u38d2\u38d3\u38d4\u38d5\u38d6\u38d7\u38d8\u38d9\u38da\u38db\u38dc\u38dd\u38de\u38df\u38e0\u38e1\u38e2\u38e3\u38e4\u38e5\u38e6\u38e7\u38e8\u38e9\u38ea\u38eb\u38ec\u38ed\u38ee\u38ef\u38f0\u38f1\u38f2\u38f3\u38f4\u38f5\u38f6\u38f7\u38f8\u38f9\u38fa\u38fb\u38fc\u38fd\u38fe\u38ff\u3900\u3901\u3902\u3903\u3904\u3905\u3906\u3907\u3908\u3909\u390a\u390b\u390c\u390d\u390e\u390f\u3910\u3911\u3912\u3913\u3914\u3915\u3916\u3917\u3918\u3919\u391a\u391b\u391c\u391d\u391e\u391f\u3920\u3921\u3922\u3923\u3924\u3925\u3926\u3927\u3928\u3929\u392a\u392b\u392c\u392d\u392e\u392f\u3930\u3931\u3932\u3933\u3934\u3935\u3936\u3937\u3938\u3939\u393a\u393b\u393c\u393d\u393e\u393f\u3940\u3941\u3942\u3943\u3944\u3945\u3946\u3947\u3948\u3949\u394a\u394b\u394c\u394d\u394e\u394f\u3950\u3951\u3952\u3953\u3954\u3955\u3956\u3957\u3958\u3959\u395a\u395b\u395c\u395d\u395e\u395f\u3960\u3961\u3962\u3963\u3964\u3965\u3966\u3967\u3968\u3969\u396a\u396b\u396c\u396d\u396e\u396f\u3970\u3971\u3972\u3973\u3974\u3975\u3976\u3977\u3978\u3979\u397a\u397b\u397c\u397d\u397e\u397f\u3980\u3981\u3982\u3983\u3984\u3985\u3986\u3987\u3988\u3989\u398a\u398b\u398c\u398d\u398e\u398f\u3990\u3991\u3992\u3993\u3994\u3995\u3996\u3997\u3998\u3999\u399a\u399b\u399c\u399d\u399e\u399f\u39a0\u39a1\u39a2\u39a3\u39a4\u39a5\u39a6\u39a7\u39a8\u39a9\u39aa\u39ab\u39ac\u39ad\u39ae\u39af\u39b0\u39b1\u39b2\u39b3\u39b4\u39b5\u39b6\u39b7\u39b8\u39b9\u39ba\u39bb\u39bc\u39bd\u39be\u39bf\u39c0\u39c1\u39c2\u39c3\u39c4\u39c5\u39c6\u39c7\u39c8\u39c9\u39ca\u39cb\u39cc\u39cd\u39ce\u39cf\u39d0\u39d1\u39d2\u39d3\u39d4\u39d5\u39d6\u39d7\u39d8\u39d9\u39da\u39db\u39dc\u39dd\u39de\u39df\u39e0\u39e1\u39e2\u39e3\u39e4\u39e5\u39e6\u39e7\u39e8\u39e9\u39ea\u39eb\u39ec\u39ed\u39ee\u39ef\u39f0\u39f1\u39f2\u39f3\u39f4\u39f5\u39f6\u39f7\u39f8\u39f9\u39fa\u39fb\u39fc\u39fd\u39fe\u39ff\u3a00\u3a01\u3a02\u3a03\u3a04\u3a05\u3a06\u3a07\u3a08\u3a09\u3a0a\u3a0b\u3a0c\u3a0d\u3a0e\u3a0f\u3a10\u3a11\u3a12\u3a13\u3a14\u3a15\u3a16\u3a17\u3a18\u3a19\u3a1a\u3a1b\u3a1c\u3a1d\u3a1e\u3a1f\u3a20\u3a21\u3a22\u3a23\u3a24\u3a25\u3a26\u3a27\u3a28\u3a29\u3a2a\u3a2b\u3a2c\u3a2d\u3a2e\u3a2f\u3a30\u3a31\u3a32\u3a33\u3a34\u3a35\u3a36\u3a37\u3a38\u3a39\u3a3a\u3a3b\u3a3c\u3a3d\u3a3e\u3a3f\u3a40\u3a41\u3a42\u3a43\u3a44\u3a45\u3a46\u3a47\u3a48\u3a49\u3a4a\u3a4b\u3a4c\u3a4d\u3a4e\u3a4f\u3a50\u3a51\u3a52\u3a53\u3a54\u3a55\u3a56\u3a57\u3a58\u3a59\u3a5a\u3a5b\u3a5c\u3a5d\u3a5e\u3a5f\u3a60\u3a61\u3a62\u3a63\u3a64\u3a65\u3a66\u3a67\u3a68\u3a69\u3a6a\u3a6b\u3a6c\u3a6d\u3a6e\u3a6f\u3a70\u3a71\u3a72\u3a73\u3a74\u3a75\u3a76\u3a77\u3a78\u3a79\u3a7a\u3a7b\u3a7c\u3a7d\u3a7e\u3a7f\u3a80\u3a81\u3a82\u3a83\u3a84\u3a85\u3a86\u3a87\u3a88\u3a89\u3a8a\u3a8b\u3a8c\u3a8d\u3a8e\u3a8f\u3a90\u3a91\u3a92\u3a93\u3a94\u3a95\u3a96\u3a97\u3a98\u3a99\u3a9a\u3a9b\u3a9c\u3a9d\u3a9e\u3a9f\u3aa0\u3aa1\u3aa2\u3aa3\u3aa4\u3aa5\u3aa6\u3aa7\u3aa8\u3aa9\u3aaa\u3aab\u3aac\u3aad\u3aae\u3aaf\u3ab0\u3ab1\u3ab2\u3ab3\u3ab4\u3ab5\u3ab6\u3ab7\u3ab8\u3ab9\u3aba\u3abb\u3abc\u3abd\u3abe\u3abf\u3ac0\u3ac1\u3ac2\u3ac3\u3ac4\u3ac5\u3ac6\u3ac7\u3ac8\u3ac9\u3aca\u3acb\u3acc\u3acd\u3ace\u3acf\u3ad0\u3ad1\u3ad2\u3ad3\u3ad4\u3ad5\u3ad6\u3ad7\u3ad8\u3ad9\u3ada\u3adb\u3adc\u3add\u3ade\u3adf\u3ae0\u3ae1\u3ae2\u3ae3\u3ae4\u3ae5\u3ae6\u3ae7\u3ae8\u3ae9\u3aea\u3aeb\u3aec\u3aed\u3aee\u3aef\u3af0\u3af1\u3af2\u3af3\u3af4\u3af5\u3af6\u3af7\u3af8\u3af9\u3afa\u3afb\u3afc\u3afd\u3afe\u3aff\u3b00\u3b01\u3b02\u3b03\u3b04\u3b05\u3b06\u3b07\u3b08\u3b09\u3b0a\u3b0b\u3b0c\u3b0d\u3b0e\u3b0f\u3b10\u3b11\u3b12\u3b13\u3b14\u3b15\u3b16\u3b17\u3b18\u3b19\u3b1a\u3b1b\u3b1c\u3b1d\u3b1e\u3b1f\u3b20\u3b21\u3b22\u3b23\u3b24\u3b25\u3b26\u3b27\u3b28\u3b29\u3b2a\u3b2b\u3b2c\u3b2d\u3b2e\u3b2f\u3b30\u3b31\u3b32\u3b33\u3b34\u3b35\u3b36\u3b37\u3b38\u3b39\u3b3a\u3b3b\u3b3c\u3b3d\u3b3e\u3b3f\u3b40\u3b41\u3b42\u3b43\u3b44\u3b45\u3b46\u3b47\u3b48\u3b49\u3b4a\u3b4b\u3b4c\u3b4d\u3b4e\u3b4f\u3b50\u3b51\u3b52\u3b53\u3b54\u3b55\u3b56\u3b57\u3b58\u3b59\u3b5a\u3b5b\u3b5c\u3b5d\u3b5e\u3b5f\u3b60\u3b61\u3b62\u3b63\u3b64\u3b65\u3b66\u3b67\u3b68\u3b69\u3b6a\u3b6b\u3b6c\u3b6d\u3b6e\u3b6f\u3b70\u3b71\u3b72\u3b73\u3b74\u3b75\u3b76\u3b77\u3b78\u3b79\u3b7a\u3b7b\u3b7c\u3b7d\u3b7e\u3b7f\u3b80\u3b81\u3b82\u3b83\u3b84\u3b85\u3b86\u3b87\u3b88\u3b89\u3b8a\u3b8b\u3b8c\u3b8d\u3b8e\u3b8f\u3b90\u3b91\u3b92\u3b93\u3b94\u3b95\u3b96\u3b97\u3b98\u3b99\u3b9a\u3b9b\u3b9c\u3b9d\u3b9e\u3b9f\u3ba0\u3ba1\u3ba2\u3ba3\u3ba4\u3ba5\u3ba6\u3ba7\u3ba8\u3ba9\u3baa\u3bab\u3bac\u3bad\u3bae\u3baf\u3bb0\u3bb1\u3bb2\u3bb3\u3bb4\u3bb5\u3bb6\u3bb7\u3bb8\u3bb9\u3bba\u3bbb\u3bbc\u3bbd\u3bbe\u3bbf\u3bc0\u3bc1\u3bc2\u3bc3\u3bc4\u3bc5\u3bc6\u3bc7\u3bc8\u3bc9\u3bca\u3bcb\u3bcc\u3bcd\u3bce\u3bcf\u3bd0\u3bd1\u3bd2\u3bd3\u3bd4\u3bd5\u3bd6\u3bd7\u3bd8\u3bd9\u3bda\u3bdb\u3bdc\u3bdd\u3bde\u3bdf\u3be0\u3be1\u3be2\u3be3\u3be4\u3be5\u3be6\u3be7\u3be8\u3be9\u3bea\u3beb\u3bec\u3bed\u3bee\u3bef\u3bf0\u3bf1\u3bf2\u3bf3\u3bf4\u3bf5\u3bf6\u3bf7\u3bf8\u3bf9\u3bfa\u3bfb\u3bfc\u3bfd\u3bfe\u3bff\u3c00\u3c01\u3c02\u3c03\u3c04\u3c05\u3c06\u3c07\u3c08\u3c09\u3c0a\u3c0b\u3c0c\u3c0d\u3c0e\u3c0f\u3c10\u3c11\u3c12\u3c13\u3c14\u3c15\u3c16\u3c17\u3c18\u3c19\u3c1a\u3c1b\u3c1c\u3c1d\u3c1e\u3c1f\u3c20\u3c21\u3c22\u3c23\u3c24\u3c25\u3c26\u3c27\u3c28\u3c29\u3c2a\u3c2b\u3c2c\u3c2d\u3c2e\u3c2f\u3c30\u3c31\u3c32\u3c33\u3c34\u3c35\u3c36\u3c37\u3c38\u3c39\u3c3a\u3c3b\u3c3c\u3c3d\u3c3e\u3c3f\u3c40\u3c41\u3c42\u3c43\u3c44\u3c45\u3c46\u3c47\u3c48\u3c49\u3c4a\u3c4b\u3c4c\u3c4d\u3c4e\u3c4f\u3c50\u3c51\u3c52\u3c53\u3c54\u3c55\u3c56\u3c57\u3c58\u3c59\u3c5a\u3c5b\u3c5c\u3c5d\u3c5e\u3c5f\u3c60\u3c61\u3c62\u3c63\u3c64\u3c65\u3c66\u3c67\u3c68\u3c69\u3c6a\u3c6b\u3c6c\u3c6d\u3c6e\u3c6f\u3c70\u3c71\u3c72\u3c73\u3c74\u3c75\u3c76\u3c77\u3c78\u3c79\u3c7a\u3c7b\u3c7c\u3c7d\u3c7e\u3c7f\u3c80\u3c81\u3c82\u3c83\u3c84\u3c85\u3c86\u3c87\u3c88\u3c89\u3c8a\u3c8b\u3c8c\u3c8d\u3c8e\u3c8f\u3c90\u3c91\u3c92\u3c93\u3c94\u3c95\u3c96\u3c97\u3c98\u3c99\u3c9a\u3c9b\u3c9c\u3c9d\u3c9e\u3c9f\u3ca0\u3ca1\u3ca2\u3ca3\u3ca4\u3ca5\u3ca6\u3ca7\u3ca8\u3ca9\u3caa\u3cab\u3cac\u3cad\u3cae\u3caf\u3cb0\u3cb1\u3cb2\u3cb3\u3cb4\u3cb5\u3cb6\u3cb7\u3cb8\u3cb9\u3cba\u3cbb\u3cbc\u3cbd\u3cbe\u3cbf\u3cc0\u3cc1\u3cc2\u3cc3\u3cc4\u3cc5\u3cc6\u3cc7\u3cc8\u3cc9\u3cca\u3ccb\u3ccc\u3ccd\u3cce\u3ccf\u3cd0\u3cd1\u3cd2\u3cd3\u3cd4\u3cd5\u3cd6\u3cd7\u3cd8\u3cd9\u3cda\u3cdb\u3cdc\u3cdd\u3cde\u3cdf\u3ce0\u3ce1\u3ce2\u3ce3\u3ce4\u3ce5\u3ce6\u3ce7\u3ce8\u3ce9\u3cea\u3ceb\u3cec\u3ced\u3cee\u3cef\u3cf0\u3cf1\u3cf2\u3cf3\u3cf4\u3cf5\u3cf6\u3cf7\u3cf8\u3cf9\u3cfa\u3cfb\u3cfc\u3cfd\u3cfe\u3cff\u3d00\u3d01\u3d02\u3d03\u3d04\u3d05\u3d06\u3d07\u3d08\u3d09\u3d0a\u3d0b\u3d0c\u3d0d\u3d0e\u3d0f\u3d10\u3d11\u3d12\u3d13\u3d14\u3d15\u3d16\u3d17\u3d18\u3d19\u3d1a\u3d1b\u3d1c\u3d1d\u3d1e\u3d1f\u3d20\u3d21\u3d22\u3d23\u3d24\u3d25\u3d26\u3d27\u3d28\u3d29\u3d2a\u3d2b\u3d2c\u3d2d\u3d2e\u3d2f\u3d30\u3d31\u3d32\u3d33\u3d34\u3d35\u3d36\u3d37\u3d38\u3d39\u3d3a\u3d3b\u3d3c\u3d3d\u3d3e\u3d3f\u3d40\u3d41\u3d42\u3d43\u3d44\u3d45\u3d46\u3d47\u3d48\u3d49\u3d4a\u3d4b\u3d4c\u3d4d\u3d4e\u3d4f\u3d50\u3d51\u3d52\u3d53\u3d54\u3d55\u3d56\u3d57\u3d58\u3d59\u3d5a\u3d5b\u3d5c\u3d5d\u3d5e\u3d5f\u3d60\u3d61\u3d62\u3d63\u3d64\u3d65\u3d66\u3d67\u3d68\u3d69\u3d6a\u3d6b\u3d6c\u3d6d\u3d6e\u3d6f\u3d70\u3d71\u3d72\u3d73\u3d74\u3d75\u3d76\u3d77\u3d78\u3d79\u3d7a\u3d7b\u3d7c\u3d7d\u3d7e\u3d7f\u3d80\u3d81\u3d82\u3d83\u3d84\u3d85\u3d86\u3d87\u3d88\u3d89\u3d8a\u3d8b\u3d8c\u3d8d\u3d8e\u3d8f\u3d90\u3d91\u3d92\u3d93\u3d94\u3d95\u3d96\u3d97\u3d98\u3d99\u3d9a\u3d9b\u3d9c\u3d9d\u3d9e\u3d9f\u3da0\u3da1\u3da2\u3da3\u3da4\u3da5\u3da6\u3da7\u3da8\u3da9\u3daa\u3dab\u3dac\u3dad\u3dae\u3daf\u3db0\u3db1\u3db2\u3db3\u3db4\u3db5\u3db6\u3db7\u3db8\u3db9\u3dba\u3dbb\u3dbc\u3dbd\u3dbe\u3dbf\u3dc0\u3dc1\u3dc2\u3dc3\u3dc4\u3dc5\u3dc6\u3dc7\u3dc8\u3dc9\u3dca\u3dcb\u3dcc\u3dcd\u3dce\u3dcf\u3dd0\u3dd1\u3dd2\u3dd3\u3dd4\u3dd5\u3dd6\u3dd7\u3dd8\u3dd9\u3dda\u3ddb\u3ddc\u3ddd\u3dde\u3ddf\u3de0\u3de1\u3de2\u3de3\u3de4\u3de5\u3de6\u3de7\u3de8\u3de9\u3dea\u3deb\u3dec\u3ded\u3dee\u3def\u3df0\u3df1\u3df2\u3df3\u3df4\u3df5\u3df6\u3df7\u3df8\u3df9\u3dfa\u3dfb\u3dfc\u3dfd\u3dfe\u3dff\u3e00\u3e01\u3e02\u3e03\u3e04\u3e05\u3e06\u3e07\u3e08\u3e09\u3e0a\u3e0b\u3e0c\u3e0d\u3e0e\u3e0f\u3e10\u3e11\u3e12\u3e13\u3e14\u3e15\u3e16\u3e17\u3e18\u3e19\u3e1a\u3e1b\u3e1c\u3e1d\u3e1e\u3e1f\u3e20\u3e21\u3e22\u3e23\u3e24\u3e25\u3e26\u3e27\u3e28\u3e29\u3e2a\u3e2b\u3e2c\u3e2d\u3e2e\u3e2f\u3e30\u3e31\u3e32\u3e33\u3e34\u3e35\u3e36\u3e37\u3e38\u3e39\u3e3a\u3e3b\u3e3c\u3e3d\u3e3e\u3e3f\u3e40\u3e41\u3e42\u3e43\u3e44\u3e45\u3e46\u3e47\u3e48\u3e49\u3e4a\u3e4b\u3e4c\u3e4d\u3e4e\u3e4f\u3e50\u3e51\u3e52\u3e53\u3e54\u3e55\u3e56\u3e57\u3e58\u3e59\u3e5a\u3e5b\u3e5c\u3e5d\u3e5e\u3e5f\u3e60\u3e61\u3e62\u3e63\u3e64\u3e65\u3e66\u3e67\u3e68\u3e69\u3e6a\u3e6b\u3e6c\u3e6d\u3e6e\u3e6f\u3e70\u3e71\u3e72\u3e73\u3e74\u3e75\u3e76\u3e77\u3e78\u3e79\u3e7a\u3e7b\u3e7c\u3e7d\u3e7e\u3e7f\u3e80\u3e81\u3e82\u3e83\u3e84\u3e85\u3e86\u3e87\u3e88\u3e89\u3e8a\u3e8b\u3e8c\u3e8d\u3e8e\u3e8f\u3e90\u3e91\u3e92\u3e93\u3e94\u3e95\u3e96\u3e97\u3e98\u3e99\u3e9a\u3e9b\u3e9c\u3e9d\u3e9e\u3e9f\u3ea0\u3ea1\u3ea2\u3ea3\u3ea4\u3ea5\u3ea6\u3ea7\u3ea8\u3ea9\u3eaa\u3eab\u3eac\u3ead\u3eae\u3eaf\u3eb0\u3eb1\u3eb2\u3eb3\u3eb4\u3eb5\u3eb6\u3eb7\u3eb8\u3eb9\u3eba\u3ebb\u3ebc\u3ebd\u3ebe\u3ebf\u3ec0\u3ec1\u3ec2\u3ec3\u3ec4\u3ec5\u3ec6\u3ec7\u3ec8\u3ec9\u3eca\u3ecb\u3ecc\u3ecd\u3ece\u3ecf\u3ed0\u3ed1\u3ed2\u3ed3\u3ed4\u3ed5\u3ed6\u3ed7\u3ed8\u3ed9\u3eda\u3edb\u3edc\u3edd\u3ede\u3edf\u3ee0\u3ee1\u3ee2\u3ee3\u3ee4\u3ee5\u3ee6\u3ee7\u3ee8\u3ee9\u3eea\u3eeb\u3eec\u3eed\u3eee\u3eef\u3ef0\u3ef1\u3ef2\u3ef3\u3ef4\u3ef5\u3ef6\u3ef7\u3ef8\u3ef9\u3efa\u3efb\u3efc\u3efd\u3efe\u3eff\u3f00\u3f01\u3f02\u3f03\u3f04\u3f05\u3f06\u3f07\u3f08\u3f09\u3f0a\u3f0b\u3f0c\u3f0d\u3f0e\u3f0f\u3f10\u3f11\u3f12\u3f13\u3f14\u3f15\u3f16\u3f17\u3f18\u3f19\u3f1a\u3f1b\u3f1c\u3f1d\u3f1e\u3f1f\u3f20\u3f21\u3f22\u3f23\u3f24\u3f25\u3f26\u3f27\u3f28\u3f29\u3f2a\u3f2b\u3f2c\u3f2d\u3f2e\u3f2f\u3f30\u3f31\u3f32\u3f33\u3f34\u3f35\u3f36\u3f37\u3f38\u3f39\u3f3a\u3f3b\u3f3c\u3f3d\u3f3e\u3f3f\u3f40\u3f41\u3f42\u3f43\u3f44\u3f45\u3f46\u3f47\u3f48\u3f49\u3f4a\u3f4b\u3f4c\u3f4d\u3f4e\u3f4f\u3f50\u3f51\u3f52\u3f53\u3f54\u3f55\u3f56\u3f57\u3f58\u3f59\u3f5a\u3f5b\u3f5c\u3f5d\u3f5e\u3f5f\u3f60\u3f61\u3f62\u3f63\u3f64\u3f65\u3f66\u3f67\u3f68\u3f69\u3f6a\u3f6b\u3f6c\u3f6d\u3f6e\u3f6f\u3f70\u3f71\u3f72\u3f73\u3f74\u3f75\u3f76\u3f77\u3f78\u3f79\u3f7a\u3f7b\u3f7c\u3f7d\u3f7e\u3f7f\u3f80\u3f81\u3f82\u3f83\u3f84\u3f85\u3f86\u3f87\u3f88\u3f89\u3f8a\u3f8b\u3f8c\u3f8d\u3f8e\u3f8f\u3f90\u3f91\u3f92\u3f93\u3f94\u3f95\u3f96\u3f97\u3f98\u3f99\u3f9a\u3f9b\u3f9c\u3f9d\u3f9e\u3f9f\u3fa0\u3fa1\u3fa2\u3fa3\u3fa4\u3fa5\u3fa6\u3fa7\u3fa8\u3fa9\u3faa\u3fab\u3fac\u3fad\u3fae\u3faf\u3fb0\u3fb1\u3fb2\u3fb3\u3fb4\u3fb5\u3fb6\u3fb7\u3fb8\u3fb9\u3fba\u3fbb\u3fbc\u3fbd\u3fbe\u3fbf\u3fc0\u3fc1\u3fc2\u3fc3\u3fc4\u3fc5\u3fc6\u3fc7\u3fc8\u3fc9\u3fca\u3fcb\u3fcc\u3fcd\u3fce\u3fcf\u3fd0\u3fd1\u3fd2\u3fd3\u3fd4\u3fd5\u3fd6\u3fd7\u3fd8\u3fd9\u3fda\u3fdb\u3fdc\u3fdd\u3fde\u3fdf\u3fe0\u3fe1\u3fe2\u3fe3\u3fe4\u3fe5\u3fe6\u3fe7\u3fe8\u3fe9\u3fea\u3feb\u3fec\u3fed\u3fee\u3fef\u3ff0\u3ff1\u3ff2\u3ff3\u3ff4\u3ff5\u3ff6\u3ff7\u3ff8\u3ff9\u3ffa\u3ffb\u3ffc\u3ffd\u3ffe\u3fff\u4000\u4001\u4002\u4003\u4004\u4005\u4006\u4007\u4008\u4009\u400a\u400b\u400c\u400d\u400e\u400f\u4010\u4011\u4012\u4013\u4014\u4015\u4016\u4017\u4018\u4019\u401a\u401b\u401c\u401d\u401e\u401f\u4020\u4021\u4022\u4023\u4024\u4025\u4026\u4027\u4028\u4029\u402a\u402b\u402c\u402d\u402e\u402f\u4030\u4031\u4032\u4033\u4034\u4035\u4036\u4037\u4038\u4039\u403a\u403b\u403c\u403d\u403e\u403f\u4040\u4041\u4042\u4043\u4044\u4045\u4046\u4047\u4048\u4049\u404a\u404b\u404c\u404d\u404e\u404f\u4050\u4051\u4052\u4053\u4054\u4055\u4056\u4057\u4058\u4059\u405a\u405b\u405c\u405d\u405e\u405f\u4060\u4061\u4062\u4063\u4064\u4065\u4066\u4067\u4068\u4069\u406a\u406b\u406c\u406d\u406e\u406f\u4070\u4071\u4072\u4073\u4074\u4075\u4076\u4077\u4078\u4079\u407a\u407b\u407c\u407d\u407e\u407f\u4080\u4081\u4082\u4083\u4084\u4085\u4086\u4087\u4088\u4089\u408a\u408b\u408c\u408d\u408e\u408f\u4090\u4091\u4092\u4093\u4094\u4095\u4096\u4097\u4098\u4099\u409a\u409b\u409c\u409d\u409e\u409f\u40a0\u40a1\u40a2\u40a3\u40a4\u40a5\u40a6\u40a7\u40a8\u40a9\u40aa\u40ab\u40ac\u40ad\u40ae\u40af\u40b0\u40b1\u40b2\u40b3\u40b4\u40b5\u40b6\u40b7\u40b8\u40b9\u40ba\u40bb\u40bc\u40bd\u40be\u40bf\u40c0\u40c1\u40c2\u40c3\u40c4\u40c5\u40c6\u40c7\u40c8\u40c9\u40ca\u40cb\u40cc\u40cd\u40ce\u40cf\u40d0\u40d1\u40d2\u40d3\u40d4\u40d5\u40d6\u40d7\u40d8\u40d9\u40da\u40db\u40dc\u40dd\u40de\u40df\u40e0\u40e1\u40e2\u40e3\u40e4\u40e5\u40e6\u40e7\u40e8\u40e9\u40ea\u40eb\u40ec\u40ed\u40ee\u40ef\u40f0\u40f1\u40f2\u40f3\u40f4\u40f5\u40f6\u40f7\u40f8\u40f9\u40fa\u40fb\u40fc\u40fd\u40fe\u40ff\u4100\u4101\u4102\u4103\u4104\u4105\u4106\u4107\u4108\u4109\u410a\u410b\u410c\u410d\u410e\u410f\u4110\u4111\u4112\u4113\u4114\u4115\u4116\u4117\u4118\u4119\u411a\u411b\u411c\u411d\u411e\u411f\u4120\u4121\u4122\u4123\u4124\u4125\u4126\u4127\u4128\u4129\u412a\u412b\u412c\u412d\u412e\u412f\u4130\u4131\u4132\u4133\u4134\u4135\u4136\u4137\u4138\u4139\u413a\u413b\u413c\u413d\u413e\u413f\u4140\u4141\u4142\u4143\u4144\u4145\u4146\u4147\u4148\u4149\u414a\u414b\u414c\u414d\u414e\u414f\u4150\u4151\u4152\u4153\u4154\u4155\u4156\u4157\u4158\u4159\u415a\u415b\u415c\u415d\u415e\u415f\u4160\u4161\u4162\u4163\u4164\u4165\u4166\u4167\u4168\u4169\u416a\u416b\u416c\u416d\u416e\u416f\u4170\u4171\u4172\u4173\u4174\u4175\u4176\u4177\u4178\u4179\u417a\u417b\u417c\u417d\u417e\u417f\u4180\u4181\u4182\u4183\u4184\u4185\u4186\u4187\u4188\u4189\u418a\u418b\u418c\u418d\u418e\u418f\u4190\u4191\u4192\u4193\u4194\u4195\u4196\u4197\u4198\u4199\u419a\u419b\u419c\u419d\u419e\u419f\u41a0\u41a1\u41a2\u41a3\u41a4\u41a5\u41a6\u41a7\u41a8\u41a9\u41aa\u41ab\u41ac\u41ad\u41ae\u41af\u41b0\u41b1\u41b2\u41b3\u41b4\u41b5\u41b6\u41b7\u41b8\u41b9\u41ba\u41bb\u41bc\u41bd\u41be\u41bf\u41c0\u41c1\u41c2\u41c3\u41c4\u41c5\u41c6\u41c7\u41c8\u41c9\u41ca\u41cb\u41cc\u41cd\u41ce\u41cf\u41d0\u41d1\u41d2\u41d3\u41d4\u41d5\u41d6\u41d7\u41d8\u41d9\u41da\u41db\u41dc\u41dd\u41de\u41df\u41e0\u41e1\u41e2\u41e3\u41e4\u41e5\u41e6\u41e7\u41e8\u41e9\u41ea\u41eb\u41ec\u41ed\u41ee\u41ef\u41f0\u41f1\u41f2\u41f3\u41f4\u41f5\u41f6\u41f7\u41f8\u41f9\u41fa\u41fb\u41fc\u41fd\u41fe\u41ff\u4200\u4201\u4202\u4203\u4204\u4205\u4206\u4207\u4208\u4209\u420a\u420b\u420c\u420d\u420e\u420f\u4210\u4211\u4212\u4213\u4214\u4215\u4216\u4217\u4218\u4219\u421a\u421b\u421c\u421d\u421e\u421f\u4220\u4221\u4222\u4223\u4224\u4225\u4226\u4227\u4228\u4229\u422a\u422b\u422c\u422d\u422e\u422f\u4230\u4231\u4232\u4233\u4234\u4235\u4236\u4237\u4238\u4239\u423a\u423b\u423c\u423d\u423e\u423f\u4240\u4241\u4242\u4243\u4244\u4245\u4246\u4247\u4248\u4249\u424a\u424b\u424c\u424d\u424e\u424f\u4250\u4251\u4252\u4253\u4254\u4255\u4256\u4257\u4258\u4259\u425a\u425b\u425c\u425d\u425e\u425f\u4260\u4261\u4262\u4263\u4264\u4265\u4266\u4267\u4268\u4269\u426a\u426b\u426c\u426d\u426e\u426f\u4270\u4271\u4272\u4273\u4274\u4275\u4276\u4277\u4278\u4279\u427a\u427b\u427c\u427d\u427e\u427f\u4280\u4281\u4282\u4283\u4284\u4285\u4286\u4287\u4288\u4289\u428a\u428b\u428c\u428d\u428e\u428f\u4290\u4291\u4292\u4293\u4294\u4295\u4296\u4297\u4298\u4299\u429a\u429b\u429c\u429d\u429e\u429f\u42a0\u42a1\u42a2\u42a3\u42a4\u42a5\u42a6\u42a7\u42a8\u42a9\u42aa\u42ab\u42ac\u42ad\u42ae\u42af\u42b0\u42b1\u42b2\u42b3\u42b4\u42b5\u42b6\u42b7\u42b8\u42b9\u42ba\u42bb\u42bc\u42bd\u42be\u42bf\u42c0\u42c1\u42c2\u42c3\u42c4\u42c5\u42c6\u42c7\u42c8\u42c9\u42ca\u42cb\u42cc\u42cd\u42ce\u42cf\u42d0\u42d1\u42d2\u42d3\u42d4\u42d5\u42d6\u42d7\u42d8\u42d9\u42da\u42db\u42dc\u42dd\u42de\u42df\u42e0\u42e1\u42e2\u42e3\u42e4\u42e5\u42e6\u42e7\u42e8\u42e9\u42ea\u42eb\u42ec\u42ed\u42ee\u42ef\u42f0\u42f1\u42f2\u42f3\u42f4\u42f5\u42f6\u42f7\u42f8\u42f9\u42fa\u42fb\u42fc\u42fd\u42fe\u42ff\u4300\u4301\u4302\u4303\u4304\u4305\u4306\u4307\u4308\u4309\u430a\u430b\u430c\u430d\u430e\u430f\u4310\u4311\u4312\u4313\u4314\u4315\u4316\u4317\u4318\u4319\u431a\u431b\u431c\u431d\u431e\u431f\u4320\u4321\u4322\u4323\u4324\u4325\u4326\u4327\u4328\u4329\u432a\u432b\u432c\u432d\u432e\u432f\u4330\u4331\u4332\u4333\u4334\u4335\u4336\u4337\u4338\u4339\u433a\u433b\u433c\u433d\u433e\u433f\u4340\u4341\u4342\u4343\u4344\u4345\u4346\u4347\u4348\u4349\u434a\u434b\u434c\u434d\u434e\u434f\u4350\u4351\u4352\u4353\u4354\u4355\u4356\u4357\u4358\u4359\u435a\u435b\u435c\u435d\u435e\u435f\u4360\u4361\u4362\u4363\u4364\u4365\u4366\u4367\u4368\u4369\u436a\u436b\u436c\u436d\u436e\u436f\u4370\u4371\u4372\u4373\u4374\u4375\u4376\u4377\u4378\u4379\u437a\u437b\u437c\u437d\u437e\u437f\u4380\u4381\u4382\u4383\u4384\u4385\u4386\u4387\u4388\u4389\u438a\u438b\u438c\u438d\u438e\u438f\u4390\u4391\u4392\u4393\u4394\u4395\u4396\u4397\u4398\u4399\u439a\u439b\u439c\u439d\u439e\u439f\u43a0\u43a1\u43a2\u43a3\u43a4\u43a5\u43a6\u43a7\u43a8\u43a9\u43aa\u43ab\u43ac\u43ad\u43ae\u43af\u43b0\u43b1\u43b2\u43b3\u43b4\u43b5\u43b6\u43b7\u43b8\u43b9\u43ba\u43bb\u43bc\u43bd\u43be\u43bf\u43c0\u43c1\u43c2\u43c3\u43c4\u43c5\u43c6\u43c7\u43c8\u43c9\u43ca\u43cb\u43cc\u43cd\u43ce\u43cf\u43d0\u43d1\u43d2\u43d3\u43d4\u43d5\u43d6\u43d7\u43d8\u43d9\u43da\u43db\u43dc\u43dd\u43de\u43df\u43e0\u43e1\u43e2\u43e3\u43e4\u43e5\u43e6\u43e7\u43e8\u43e9\u43ea\u43eb\u43ec\u43ed\u43ee\u43ef\u43f0\u43f1\u43f2\u43f3\u43f4\u43f5\u43f6\u43f7\u43f8\u43f9\u43fa\u43fb\u43fc\u43fd\u43fe\u43ff\u4400\u4401\u4402\u4403\u4404\u4405\u4406\u4407\u4408\u4409\u440a\u440b\u440c\u440d\u440e\u440f\u4410\u4411\u4412\u4413\u4414\u4415\u4416\u4417\u4418\u4419\u441a\u441b\u441c\u441d\u441e\u441f\u4420\u4421\u4422\u4423\u4424\u4425\u4426\u4427\u4428\u4429\u442a\u442b\u442c\u442d\u442e\u442f\u4430\u4431\u4432\u4433\u4434\u4435\u4436\u4437\u4438\u4439\u443a\u443b\u443c\u443d\u443e\u443f\u4440\u4441\u4442\u4443\u4444\u4445\u4446\u4447\u4448\u4449\u444a\u444b\u444c\u444d\u444e\u444f\u4450\u4451\u4452\u4453\u4454\u4455\u4456\u4457\u4458\u4459\u445a\u445b\u445c\u445d\u445e\u445f\u4460\u4461\u4462\u4463\u4464\u4465\u4466\u4467\u4468\u4469\u446a\u446b\u446c\u446d\u446e\u446f\u4470\u4471\u4472\u4473\u4474\u4475\u4476\u4477\u4478\u4479\u447a\u447b\u447c\u447d\u447e\u447f\u4480\u4481\u4482\u4483\u4484\u4485\u4486\u4487\u4488\u4489\u448a\u448b\u448c\u448d\u448e\u448f\u4490\u4491\u4492\u4493\u4494\u4495\u4496\u4497\u4498\u4499\u449a\u449b\u449c\u449d\u449e\u449f\u44a0\u44a1\u44a2\u44a3\u44a4\u44a5\u44a6\u44a7\u44a8\u44a9\u44aa\u44ab\u44ac\u44ad\u44ae\u44af\u44b0\u44b1\u44b2\u44b3\u44b4\u44b5\u44b6\u44b7\u44b8\u44b9\u44ba\u44bb\u44bc\u44bd\u44be\u44bf\u44c0\u44c1\u44c2\u44c3\u44c4\u44c5\u44c6\u44c7\u44c8\u44c9\u44ca\u44cb\u44cc\u44cd\u44ce\u44cf\u44d0\u44d1\u44d2\u44d3\u44d4\u44d5\u44d6\u44d7\u44d8\u44d9\u44da\u44db\u44dc\u44dd\u44de\u44df\u44e0\u44e1\u44e2\u44e3\u44e4\u44e5\u44e6\u44e7\u44e8\u44e9\u44ea\u44eb\u44ec\u44ed\u44ee\u44ef\u44f0\u44f1\u44f2\u44f3\u44f4\u44f5\u44f6\u44f7\u44f8\u44f9\u44fa\u44fb\u44fc\u44fd\u44fe\u44ff\u4500\u4501\u4502\u4503\u4504\u4505\u4506\u4507\u4508\u4509\u450a\u450b\u450c\u450d\u450e\u450f\u4510\u4511\u4512\u4513\u4514\u4515\u4516\u4517\u4518\u4519\u451a\u451b\u451c\u451d\u451e\u451f\u4520\u4521\u4522\u4523\u4524\u4525\u4526\u4527\u4528\u4529\u452a\u452b\u452c\u452d\u452e\u452f\u4530\u4531\u4532\u4533\u4534\u4535\u4536\u4537\u4538\u4539\u453a\u453b\u453c\u453d\u453e\u453f\u4540\u4541\u4542\u4543\u4544\u4545\u4546\u4547\u4548\u4549\u454a\u454b\u454c\u454d\u454e\u454f\u4550\u4551\u4552\u4553\u4554\u4555\u4556\u4557\u4558\u4559\u455a\u455b\u455c\u455d\u455e\u455f\u4560\u4561\u4562\u4563\u4564\u4565\u4566\u4567\u4568\u4569\u456a\u456b\u456c\u456d\u456e\u456f\u4570\u4571\u4572\u4573\u4574\u4575\u4576\u4577\u4578\u4579\u457a\u457b\u457c\u457d\u457e\u457f\u4580\u4581\u4582\u4583\u4584\u4585\u4586\u4587\u4588\u4589\u458a\u458b\u458c\u458d\u458e\u458f\u4590\u4591\u4592\u4593\u4594\u4595\u4596\u4597\u4598\u4599\u459a\u459b\u459c\u459d\u459e\u459f\u45a0\u45a1\u45a2\u45a3\u45a4\u45a5\u45a6\u45a7\u45a8\u45a9\u45aa\u45ab\u45ac\u45ad\u45ae\u45af\u45b0\u45b1\u45b2\u45b3\u45b4\u45b5\u45b6\u45b7\u45b8\u45b9\u45ba\u45bb\u45bc\u45bd\u45be\u45bf\u45c0\u45c1\u45c2\u45c3\u45c4\u45c5\u45c6\u45c7\u45c8\u45c9\u45ca\u45cb\u45cc\u45cd\u45ce\u45cf\u45d0\u45d1\u45d2\u45d3\u45d4\u45d5\u45d6\u45d7\u45d8\u45d9\u45da\u45db\u45dc\u45dd\u45de\u45df\u45e0\u45e1\u45e2\u45e3\u45e4\u45e5\u45e6\u45e7\u45e8\u45e9\u45ea\u45eb\u45ec\u45ed\u45ee\u45ef\u45f0\u45f1\u45f2\u45f3\u45f4\u45f5\u45f6\u45f7\u45f8\u45f9\u45fa\u45fb\u45fc\u45fd\u45fe\u45ff\u4600\u4601\u4602\u4603\u4604\u4605\u4606\u4607\u4608\u4609\u460a\u460b\u460c\u460d\u460e\u460f\u4610\u4611\u4612\u4613\u4614\u4615\u4616\u4617\u4618\u4619\u461a\u461b\u461c\u461d\u461e\u461f\u4620\u4621\u4622\u4623\u4624\u4625\u4626\u4627\u4628\u4629\u462a\u462b\u462c\u462d\u462e\u462f\u4630\u4631\u4632\u4633\u4634\u4635\u4636\u4637\u4638\u4639\u463a\u463b\u463c\u463d\u463e\u463f\u4640\u4641\u4642\u4643\u4644\u4645\u4646\u4647\u4648\u4649\u464a\u464b\u464c\u464d\u464e\u464f\u4650\u4651\u4652\u4653\u4654\u4655\u4656\u4657\u4658\u4659\u465a\u465b\u465c\u465d\u465e\u465f\u4660\u4661\u4662\u4663\u4664\u4665\u4666\u4667\u4668\u4669\u466a\u466b\u466c\u466d\u466e\u466f\u4670\u4671\u4672\u4673\u4674\u4675\u4676\u4677\u4678\u4679\u467a\u467b\u467c\u467d\u467e\u467f\u4680\u4681\u4682\u4683\u4684\u4685\u4686\u4687\u4688\u4689\u468a\u468b\u468c\u468d\u468e\u468f\u4690\u4691\u4692\u4693\u4694\u4695\u4696\u4697\u4698\u4699\u469a\u469b\u469c\u469d\u469e\u469f\u46a0\u46a1\u46a2\u46a3\u46a4\u46a5\u46a6\u46a7\u46a8\u46a9\u46aa\u46ab\u46ac\u46ad\u46ae\u46af\u46b0\u46b1\u46b2\u46b3\u46b4\u46b5\u46b6\u46b7\u46b8\u46b9\u46ba\u46bb\u46bc\u46bd\u46be\u46bf\u46c0\u46c1\u46c2\u46c3\u46c4\u46c5\u46c6\u46c7\u46c8\u46c9\u46ca\u46cb\u46cc\u46cd\u46ce\u46cf\u46d0\u46d1\u46d2\u46d3\u46d4\u46d5\u46d6\u46d7\u46d8\u46d9\u46da\u46db\u46dc\u46dd\u46de\u46df\u46e0\u46e1\u46e2\u46e3\u46e4\u46e5\u46e6\u46e7\u46e8\u46e9\u46ea\u46eb\u46ec\u46ed\u46ee\u46ef\u46f0\u46f1\u46f2\u46f3\u46f4\u46f5\u46f6\u46f7\u46f8\u46f9\u46fa\u46fb\u46fc\u46fd\u46fe\u46ff\u4700\u4701\u4702\u4703\u4704\u4705\u4706\u4707\u4708\u4709\u470a\u470b\u470c\u470d\u470e\u470f\u4710\u4711\u4712\u4713\u4714\u4715\u4716\u4717\u4718\u4719\u471a\u471b\u471c\u471d\u471e\u471f\u4720\u4721\u4722\u4723\u4724\u4725\u4726\u4727\u4728\u4729\u472a\u472b\u472c\u472d\u472e\u472f\u4730\u4731\u4732\u4733\u4734\u4735\u4736\u4737\u4738\u4739\u473a\u473b\u473c\u473d\u473e\u473f\u4740\u4741\u4742\u4743\u4744\u4745\u4746\u4747\u4748\u4749\u474a\u474b\u474c\u474d\u474e\u474f\u4750\u4751\u4752\u4753\u4754\u4755\u4756\u4757\u4758\u4759\u475a\u475b\u475c\u475d\u475e\u475f\u4760\u4761\u4762\u4763\u4764\u4765\u4766\u4767\u4768\u4769\u476a\u476b\u476c\u476d\u476e\u476f\u4770\u4771\u4772\u4773\u4774\u4775\u4776\u4777\u4778\u4779\u477a\u477b\u477c\u477d\u477e\u477f\u4780\u4781\u4782\u4783\u4784\u4785\u4786\u4787\u4788\u4789\u478a\u478b\u478c\u478d\u478e\u478f\u4790\u4791\u4792\u4793\u4794\u4795\u4796\u4797\u4798\u4799\u479a\u479b\u479c\u479d\u479e\u479f\u47a0\u47a1\u47a2\u47a3\u47a4\u47a5\u47a6\u47a7\u47a8\u47a9\u47aa\u47ab\u47ac\u47ad\u47ae\u47af\u47b0\u47b1\u47b2\u47b3\u47b4\u47b5\u47b6\u47b7\u47b8\u47b9\u47ba\u47bb\u47bc\u47bd\u47be\u47bf\u47c0\u47c1\u47c2\u47c3\u47c4\u47c5\u47c6\u47c7\u47c8\u47c9\u47ca\u47cb\u47cc\u47cd\u47ce\u47cf\u47d0\u47d1\u47d2\u47d3\u47d4\u47d5\u47d6\u47d7\u47d8\u47d9\u47da\u47db\u47dc\u47dd\u47de\u47df\u47e0\u47e1\u47e2\u47e3\u47e4\u47e5\u47e6\u47e7\u47e8\u47e9\u47ea\u47eb\u47ec\u47ed\u47ee\u47ef\u47f0\u47f1\u47f2\u47f3\u47f4\u47f5\u47f6\u47f7\u47f8\u47f9\u47fa\u47fb\u47fc\u47fd\u47fe\u47ff\u4800\u4801\u4802\u4803\u4804\u4805\u4806\u4807\u4808\u4809\u480a\u480b\u480c\u480d\u480e\u480f\u4810\u4811\u4812\u4813\u4814\u4815\u4816\u4817\u4818\u4819\u481a\u481b\u481c\u481d\u481e\u481f\u4820\u4821\u4822\u4823\u4824\u4825\u4826\u4827\u4828\u4829\u482a\u482b\u482c\u482d\u482e\u482f\u4830\u4831\u4832\u4833\u4834\u4835\u4836\u4837\u4838\u4839\u483a\u483b\u483c\u483d\u483e\u483f\u4840\u4841\u4842\u4843\u4844\u4845\u4846\u4847\u4848\u4849\u484a\u484b\u484c\u484d\u484e\u484f\u4850\u4851\u4852\u4853\u4854\u4855\u4856\u4857\u4858\u4859\u485a\u485b\u485c\u485d\u485e\u485f\u4860\u4861\u4862\u4863\u4864\u4865\u4866\u4867\u4868\u4869\u486a\u486b\u486c\u486d\u486e\u486f\u4870\u4871\u4872\u4873\u4874\u4875\u4876\u4877\u4878\u4879\u487a\u487b\u487c\u487d\u487e\u487f\u4880\u4881\u4882\u4883\u4884\u4885\u4886\u4887\u4888\u4889\u488a\u488b\u488c\u488d\u488e\u488f\u4890\u4891\u4892\u4893\u4894\u4895\u4896\u4897\u4898\u4899\u489a\u489b\u489c\u489d\u489e\u489f\u48a0\u48a1\u48a2\u48a3\u48a4\u48a5\u48a6\u48a7\u48a8\u48a9\u48aa\u48ab\u48ac\u48ad\u48ae\u48af\u48b0\u48b1\u48b2\u48b3\u48b4\u48b5\u48b6\u48b7\u48b8\u48b9\u48ba\u48bb\u48bc\u48bd\u48be\u48bf\u48c0\u48c1\u48c2\u48c3\u48c4\u48c5\u48c6\u48c7\u48c8\u48c9\u48ca\u48cb\u48cc\u48cd\u48ce\u48cf\u48d0\u48d1\u48d2\u48d3\u48d4\u48d5\u48d6\u48d7\u48d8\u48d9\u48da\u48db\u48dc\u48dd\u48de\u48df\u48e0\u48e1\u48e2\u48e3\u48e4\u48e5\u48e6\u48e7\u48e8\u48e9\u48ea\u48eb\u48ec\u48ed\u48ee\u48ef\u48f0\u48f1\u48f2\u48f3\u48f4\u48f5\u48f6\u48f7\u48f8\u48f9\u48fa\u48fb\u48fc\u48fd\u48fe\u48ff\u4900\u4901\u4902\u4903\u4904\u4905\u4906\u4907\u4908\u4909\u490a\u490b\u490c\u490d\u490e\u490f\u4910\u4911\u4912\u4913\u4914\u4915\u4916\u4917\u4918\u4919\u491a\u491b\u491c\u491d\u491e\u491f\u4920\u4921\u4922\u4923\u4924\u4925\u4926\u4927\u4928\u4929\u492a\u492b\u492c\u492d\u492e\u492f\u4930\u4931\u4932\u4933\u4934\u4935\u4936\u4937\u4938\u4939\u493a\u493b\u493c\u493d\u493e\u493f\u4940\u4941\u4942\u4943\u4944\u4945\u4946\u4947\u4948\u4949\u494a\u494b\u494c\u494d\u494e\u494f\u4950\u4951\u4952\u4953\u4954\u4955\u4956\u4957\u4958\u4959\u495a\u495b\u495c\u495d\u495e\u495f\u4960\u4961\u4962\u4963\u4964\u4965\u4966\u4967\u4968\u4969\u496a\u496b\u496c\u496d\u496e\u496f\u4970\u4971\u4972\u4973\u4974\u4975\u4976\u4977\u4978\u4979\u497a\u497b\u497c\u497d\u497e\u497f\u4980\u4981\u4982\u4983\u4984\u4985\u4986\u4987\u4988\u4989\u498a\u498b\u498c\u498d\u498e\u498f\u4990\u4991\u4992\u4993\u4994\u4995\u4996\u4997\u4998\u4999\u499a\u499b\u499c\u499d\u499e\u499f\u49a0\u49a1\u49a2\u49a3\u49a4\u49a5\u49a6\u49a7\u49a8\u49a9\u49aa\u49ab\u49ac\u49ad\u49ae\u49af\u49b0\u49b1\u49b2\u49b3\u49b4\u49b5\u49b6\u49b7\u49b8\u49b9\u49ba\u49bb\u49bc\u49bd\u49be\u49bf\u49c0\u49c1\u49c2\u49c3\u49c4\u49c5\u49c6\u49c7\u49c8\u49c9\u49ca\u49cb\u49cc\u49cd\u49ce\u49cf\u49d0\u49d1\u49d2\u49d3\u49d4\u49d5\u49d6\u49d7\u49d8\u49d9\u49da\u49db\u49dc\u49dd\u49de\u49df\u49e0\u49e1\u49e2\u49e3\u49e4\u49e5\u49e6\u49e7\u49e8\u49e9\u49ea\u49eb\u49ec\u49ed\u49ee\u49ef\u49f0\u49f1\u49f2\u49f3\u49f4\u49f5\u49f6\u49f7\u49f8\u49f9\u49fa\u49fb\u49fc\u49fd\u49fe\u49ff\u4a00\u4a01\u4a02\u4a03\u4a04\u4a05\u4a06\u4a07\u4a08\u4a09\u4a0a\u4a0b\u4a0c\u4a0d\u4a0e\u4a0f\u4a10\u4a11\u4a12\u4a13\u4a14\u4a15\u4a16\u4a17\u4a18\u4a19\u4a1a\u4a1b\u4a1c\u4a1d\u4a1e\u4a1f\u4a20\u4a21\u4a22\u4a23\u4a24\u4a25\u4a26\u4a27\u4a28\u4a29\u4a2a\u4a2b\u4a2c\u4a2d\u4a2e\u4a2f\u4a30\u4a31\u4a32\u4a33\u4a34\u4a35\u4a36\u4a37\u4a38\u4a39\u4a3a\u4a3b\u4a3c\u4a3d\u4a3e\u4a3f\u4a40\u4a41\u4a42\u4a43\u4a44\u4a45\u4a46\u4a47\u4a48\u4a49\u4a4a\u4a4b\u4a4c\u4a4d\u4a4e\u4a4f\u4a50\u4a51\u4a52\u4a53\u4a54\u4a55\u4a56\u4a57\u4a58\u4a59\u4a5a\u4a5b\u4a5c\u4a5d\u4a5e\u4a5f\u4a60\u4a61\u4a62\u4a63\u4a64\u4a65\u4a66\u4a67\u4a68\u4a69\u4a6a\u4a6b\u4a6c\u4a6d\u4a6e\u4a6f\u4a70\u4a71\u4a72\u4a73\u4a74\u4a75\u4a76\u4a77\u4a78\u4a79\u4a7a\u4a7b\u4a7c\u4a7d\u4a7e\u4a7f\u4a80\u4a81\u4a82\u4a83\u4a84\u4a85\u4a86\u4a87\u4a88\u4a89\u4a8a\u4a8b\u4a8c\u4a8d\u4a8e\u4a8f\u4a90\u4a91\u4a92\u4a93\u4a94\u4a95\u4a96\u4a97\u4a98\u4a99\u4a9a\u4a9b\u4a9c\u4a9d\u4a9e\u4a9f\u4aa0\u4aa1\u4aa2\u4aa3\u4aa4\u4aa5\u4aa6\u4aa7\u4aa8\u4aa9\u4aaa\u4aab\u4aac\u4aad\u4aae\u4aaf\u4ab0\u4ab1\u4ab2\u4ab3\u4ab4\u4ab5\u4ab6\u4ab7\u4ab8\u4ab9\u4aba\u4abb\u4abc\u4abd\u4abe\u4abf\u4ac0\u4ac1\u4ac2\u4ac3\u4ac4\u4ac5\u4ac6\u4ac7\u4ac8\u4ac9\u4aca\u4acb\u4acc\u4acd\u4ace\u4acf\u4ad0\u4ad1\u4ad2\u4ad3\u4ad4\u4ad5\u4ad6\u4ad7\u4ad8\u4ad9\u4ada\u4adb\u4adc\u4add\u4ade\u4adf\u4ae0\u4ae1\u4ae2\u4ae3\u4ae4\u4ae5\u4ae6\u4ae7\u4ae8\u4ae9\u4aea\u4aeb\u4aec\u4aed\u4aee\u4aef\u4af0\u4af1\u4af2\u4af3\u4af4\u4af5\u4af6\u4af7\u4af8\u4af9\u4afa\u4afb\u4afc\u4afd\u4afe\u4aff\u4b00\u4b01\u4b02\u4b03\u4b04\u4b05\u4b06\u4b07\u4b08\u4b09\u4b0a\u4b0b\u4b0c\u4b0d\u4b0e\u4b0f\u4b10\u4b11\u4b12\u4b13\u4b14\u4b15\u4b16\u4b17\u4b18\u4b19\u4b1a\u4b1b\u4b1c\u4b1d\u4b1e\u4b1f\u4b20\u4b21\u4b22\u4b23\u4b24\u4b25\u4b26\u4b27\u4b28\u4b29\u4b2a\u4b2b\u4b2c\u4b2d\u4b2e\u4b2f\u4b30\u4b31\u4b32\u4b33\u4b34\u4b35\u4b36\u4b37\u4b38\u4b39\u4b3a\u4b3b\u4b3c\u4b3d\u4b3e\u4b3f\u4b40\u4b41\u4b42\u4b43\u4b44\u4b45\u4b46\u4b47\u4b48\u4b49\u4b4a\u4b4b\u4b4c\u4b4d\u4b4e\u4b4f\u4b50\u4b51\u4b52\u4b53\u4b54\u4b55\u4b56\u4b57\u4b58\u4b59\u4b5a\u4b5b\u4b5c\u4b5d\u4b5e\u4b5f\u4b60\u4b61\u4b62\u4b63\u4b64\u4b65\u4b66\u4b67\u4b68\u4b69\u4b6a\u4b6b\u4b6c\u4b6d\u4b6e\u4b6f\u4b70\u4b71\u4b72\u4b73\u4b74\u4b75\u4b76\u4b77\u4b78\u4b79\u4b7a\u4b7b\u4b7c\u4b7d\u4b7e\u4b7f\u4b80\u4b81\u4b82\u4b83\u4b84\u4b85\u4b86\u4b87\u4b88\u4b89\u4b8a\u4b8b\u4b8c\u4b8d\u4b8e\u4b8f\u4b90\u4b91\u4b92\u4b93\u4b94\u4b95\u4b96\u4b97\u4b98\u4b99\u4b9a\u4b9b\u4b9c\u4b9d\u4b9e\u4b9f\u4ba0\u4ba1\u4ba2\u4ba3\u4ba4\u4ba5\u4ba6\u4ba7\u4ba8\u4ba9\u4baa\u4bab\u4bac\u4bad\u4bae\u4baf\u4bb0\u4bb1\u4bb2\u4bb3\u4bb4\u4bb5\u4bb6\u4bb7\u4bb8\u4bb9\u4bba\u4bbb\u4bbc\u4bbd\u4bbe\u4bbf\u4bc0\u4bc1\u4bc2\u4bc3\u4bc4\u4bc5\u4bc6\u4bc7\u4bc8\u4bc9\u4bca\u4bcb\u4bcc\u4bcd\u4bce\u4bcf\u4bd0\u4bd1\u4bd2\u4bd3\u4bd4\u4bd5\u4bd6\u4bd7\u4bd8\u4bd9\u4bda\u4bdb\u4bdc\u4bdd\u4bde\u4bdf\u4be0\u4be1\u4be2\u4be3\u4be4\u4be5\u4be6\u4be7\u4be8\u4be9\u4bea\u4beb\u4bec\u4bed\u4bee\u4bef\u4bf0\u4bf1\u4bf2\u4bf3\u4bf4\u4bf5\u4bf6\u4bf7\u4bf8\u4bf9\u4bfa\u4bfb\u4bfc\u4bfd\u4bfe\u4bff\u4c00\u4c01\u4c02\u4c03\u4c04\u4c05\u4c06\u4c07\u4c08\u4c09\u4c0a\u4c0b\u4c0c\u4c0d\u4c0e\u4c0f\u4c10\u4c11\u4c12\u4c13\u4c14\u4c15\u4c16\u4c17\u4c18\u4c19\u4c1a\u4c1b\u4c1c\u4c1d\u4c1e\u4c1f\u4c20\u4c21\u4c22\u4c23\u4c24\u4c25\u4c26\u4c27\u4c28\u4c29\u4c2a\u4c2b\u4c2c\u4c2d\u4c2e\u4c2f\u4c30\u4c31\u4c32\u4c33\u4c34\u4c35\u4c36\u4c37\u4c38\u4c39\u4c3a\u4c3b\u4c3c\u4c3d\u4c3e\u4c3f\u4c40\u4c41\u4c42\u4c43\u4c44\u4c45\u4c46\u4c47\u4c48\u4c49\u4c4a\u4c4b\u4c4c\u4c4d\u4c4e\u4c4f\u4c50\u4c51\u4c52\u4c53\u4c54\u4c55\u4c56\u4c57\u4c58\u4c59\u4c5a\u4c5b\u4c5c\u4c5d\u4c5e\u4c5f\u4c60\u4c61\u4c62\u4c63\u4c64\u4c65\u4c66\u4c67\u4c68\u4c69\u4c6a\u4c6b\u4c6c\u4c6d\u4c6e\u4c6f\u4c70\u4c71\u4c72\u4c73\u4c74\u4c75\u4c76\u4c77\u4c78\u4c79\u4c7a\u4c7b\u4c7c\u4c7d\u4c7e\u4c7f\u4c80\u4c81\u4c82\u4c83\u4c84\u4c85\u4c86\u4c87\u4c88\u4c89\u4c8a\u4c8b\u4c8c\u4c8d\u4c8e\u4c8f\u4c90\u4c91\u4c92\u4c93\u4c94\u4c95\u4c96\u4c97\u4c98\u4c99\u4c9a\u4c9b\u4c9c\u4c9d\u4c9e\u4c9f\u4ca0\u4ca1\u4ca2\u4ca3\u4ca4\u4ca5\u4ca6\u4ca7\u4ca8\u4ca9\u4caa\u4cab\u4cac\u4cad\u4cae\u4caf\u4cb0\u4cb1\u4cb2\u4cb3\u4cb4\u4cb5\u4cb6\u4cb7\u4cb8\u4cb9\u4cba\u4cbb\u4cbc\u4cbd\u4cbe\u4cbf\u4cc0\u4cc1\u4cc2\u4cc3\u4cc4\u4cc5\u4cc6\u4cc7\u4cc8\u4cc9\u4cca\u4ccb\u4ccc\u4ccd\u4cce\u4ccf\u4cd0\u4cd1\u4cd2\u4cd3\u4cd4\u4cd5\u4cd6\u4cd7\u4cd8\u4cd9\u4cda\u4cdb\u4cdc\u4cdd\u4cde\u4cdf\u4ce0\u4ce1\u4ce2\u4ce3\u4ce4\u4ce5\u4ce6\u4ce7\u4ce8\u4ce9\u4cea\u4ceb\u4cec\u4ced\u4cee\u4cef\u4cf0\u4cf1\u4cf2\u4cf3\u4cf4\u4cf5\u4cf6\u4cf7\u4cf8\u4cf9\u4cfa\u4cfb\u4cfc\u4cfd\u4cfe\u4cff\u4d00\u4d01\u4d02\u4d03\u4d04\u4d05\u4d06\u4d07\u4d08\u4d09\u4d0a\u4d0b\u4d0c\u4d0d\u4d0e\u4d0f\u4d10\u4d11\u4d12\u4d13\u4d14\u4d15\u4d16\u4d17\u4d18\u4d19\u4d1a\u4d1b\u4d1c\u4d1d\u4d1e\u4d1f\u4d20\u4d21\u4d22\u4d23\u4d24\u4d25\u4d26\u4d27\u4d28\u4d29\u4d2a\u4d2b\u4d2c\u4d2d\u4d2e\u4d2f\u4d30\u4d31\u4d32\u4d33\u4d34\u4d35\u4d36\u4d37\u4d38\u4d39\u4d3a\u4d3b\u4d3c\u4d3d\u4d3e\u4d3f\u4d40\u4d41\u4d42\u4d43\u4d44\u4d45\u4d46\u4d47\u4d48\u4d49\u4d4a\u4d4b\u4d4c\u4d4d\u4d4e\u4d4f\u4d50\u4d51\u4d52\u4d53\u4d54\u4d55\u4d56\u4d57\u4d58\u4d59\u4d5a\u4d5b\u4d5c\u4d5d\u4d5e\u4d5f\u4d60\u4d61\u4d62\u4d63\u4d64\u4d65\u4d66\u4d67\u4d68\u4d69\u4d6a\u4d6b\u4d6c\u4d6d\u4d6e\u4d6f\u4d70\u4d71\u4d72\u4d73\u4d74\u4d75\u4d76\u4d77\u4d78\u4d79\u4d7a\u4d7b\u4d7c\u4d7d\u4d7e\u4d7f\u4d80\u4d81\u4d82\u4d83\u4d84\u4d85\u4d86\u4d87\u4d88\u4d89\u4d8a\u4d8b\u4d8c\u4d8d\u4d8e\u4d8f\u4d90\u4d91\u4d92\u4d93\u4d94\u4d95\u4d96\u4d97\u4d98\u4d99\u4d9a\u4d9b\u4d9c\u4d9d\u4d9e\u4d9f\u4da0\u4da1\u4da2\u4da3\u4da4\u4da5\u4da6\u4da7\u4da8\u4da9\u4daa\u4dab\u4dac\u4dad\u4dae\u4daf\u4db0\u4db1\u4db2\u4db3\u4db4\u4db5\u4e00\u4e01\u4e02\u4e03\u4e04\u4e05\u4e06\u4e07\u4e08\u4e09\u4e0a\u4e0b\u4e0c\u4e0d\u4e0e\u4e0f\u4e10\u4e11\u4e12\u4e13\u4e14\u4e15\u4e16\u4e17\u4e18\u4e19\u4e1a\u4e1b\u4e1c\u4e1d\u4e1e\u4e1f\u4e20\u4e21\u4e22\u4e23\u4e24\u4e25\u4e26\u4e27\u4e28\u4e29\u4e2a\u4e2b\u4e2c\u4e2d\u4e2e\u4e2f\u4e30\u4e31\u4e32\u4e33\u4e34\u4e35\u4e36\u4e37\u4e38\u4e39\u4e3a\u4e3b\u4e3c\u4e3d\u4e3e\u4e3f\u4e40\u4e41\u4e42\u4e43\u4e44\u4e45\u4e46\u4e47\u4e48\u4e49\u4e4a\u4e4b\u4e4c\u4e4d\u4e4e\u4e4f\u4e50\u4e51\u4e52\u4e53\u4e54\u4e55\u4e56\u4e57\u4e58\u4e59\u4e5a\u4e5b\u4e5c\u4e5d\u4e5e\u4e5f\u4e60\u4e61\u4e62\u4e63\u4e64\u4e65\u4e66\u4e67\u4e68\u4e69\u4e6a\u4e6b\u4e6c\u4e6d\u4e6e\u4e6f\u4e70\u4e71\u4e72\u4e73\u4e74\u4e75\u4e76\u4e77\u4e78\u4e79\u4e7a\u4e7b\u4e7c\u4e7d\u4e7e\u4e7f\u4e80\u4e81\u4e82\u4e83\u4e84\u4e85\u4e86\u4e87\u4e88\u4e89\u4e8a\u4e8b\u4e8c\u4e8d\u4e8e\u4e8f\u4e90\u4e91\u4e92\u4e93\u4e94\u4e95\u4e96\u4e97\u4e98\u4e99\u4e9a\u4e9b\u4e9c\u4e9d\u4e9e\u4e9f\u4ea0\u4ea1\u4ea2\u4ea3\u4ea4\u4ea5\u4ea6\u4ea7\u4ea8\u4ea9\u4eaa\u4eab\u4eac\u4ead\u4eae\u4eaf\u4eb0\u4eb1\u4eb2\u4eb3\u4eb4\u4eb5\u4eb6\u4eb7\u4eb8\u4eb9\u4eba\u4ebb\u4ebc\u4ebd\u4ebe\u4ebf\u4ec0\u4ec1\u4ec2\u4ec3\u4ec4\u4ec5\u4ec6\u4ec7\u4ec8\u4ec9\u4eca\u4ecb\u4ecc\u4ecd\u4ece\u4ecf\u4ed0\u4ed1\u4ed2\u4ed3\u4ed4\u4ed5\u4ed6\u4ed7\u4ed8\u4ed9\u4eda\u4edb\u4edc\u4edd\u4ede\u4edf\u4ee0\u4ee1\u4ee2\u4ee3\u4ee4\u4ee5\u4ee6\u4ee7\u4ee8\u4ee9\u4eea\u4eeb\u4eec\u4eed\u4eee\u4eef\u4ef0\u4ef1\u4ef2\u4ef3\u4ef4\u4ef5\u4ef6\u4ef7\u4ef8\u4ef9\u4efa\u4efb\u4efc\u4efd\u4efe\u4eff\u4f00\u4f01\u4f02\u4f03\u4f04\u4f05\u4f06\u4f07\u4f08\u4f09\u4f0a\u4f0b\u4f0c\u4f0d\u4f0e\u4f0f\u4f10\u4f11\u4f12\u4f13\u4f14\u4f15\u4f16\u4f17\u4f18\u4f19\u4f1a\u4f1b\u4f1c\u4f1d\u4f1e\u4f1f\u4f20\u4f21\u4f22\u4f23\u4f24\u4f25\u4f26\u4f27\u4f28\u4f29\u4f2a\u4f2b\u4f2c\u4f2d\u4f2e\u4f2f\u4f30\u4f31\u4f32\u4f33\u4f34\u4f35\u4f36\u4f37\u4f38\u4f39\u4f3a\u4f3b\u4f3c\u4f3d\u4f3e\u4f3f\u4f40\u4f41\u4f42\u4f43\u4f44\u4f45\u4f46\u4f47\u4f48\u4f49\u4f4a\u4f4b\u4f4c\u4f4d\u4f4e\u4f4f\u4f50\u4f51\u4f52\u4f53\u4f54\u4f55\u4f56\u4f57\u4f58\u4f59\u4f5a\u4f5b\u4f5c\u4f5d\u4f5e\u4f5f\u4f60\u4f61\u4f62\u4f63\u4f64\u4f65\u4f66\u4f67\u4f68\u4f69\u4f6a\u4f6b\u4f6c\u4f6d\u4f6e\u4f6f\u4f70\u4f71\u4f72\u4f73\u4f74\u4f75\u4f76\u4f77\u4f78\u4f79\u4f7a\u4f7b\u4f7c\u4f7d\u4f7e\u4f7f\u4f80\u4f81\u4f82\u4f83\u4f84\u4f85\u4f86\u4f87\u4f88\u4f89\u4f8a\u4f8b\u4f8c\u4f8d\u4f8e\u4f8f\u4f90\u4f91\u4f92\u4f93\u4f94\u4f95\u4f96\u4f97\u4f98\u4f99\u4f9a\u4f9b\u4f9c\u4f9d\u4f9e\u4f9f\u4fa0\u4fa1\u4fa2\u4fa3\u4fa4\u4fa5\u4fa6\u4fa7\u4fa8\u4fa9\u4faa\u4fab\u4fac\u4fad\u4fae\u4faf\u4fb0\u4fb1\u4fb2\u4fb3\u4fb4\u4fb5\u4fb6\u4fb7\u4fb8\u4fb9\u4fba\u4fbb\u4fbc\u4fbd\u4fbe\u4fbf\u4fc0\u4fc1\u4fc2\u4fc3\u4fc4\u4fc5\u4fc6\u4fc7\u4fc8\u4fc9\u4fca\u4fcb\u4fcc\u4fcd\u4fce\u4fcf\u4fd0\u4fd1\u4fd2\u4fd3\u4fd4\u4fd5\u4fd6\u4fd7\u4fd8\u4fd9\u4fda\u4fdb\u4fdc\u4fdd\u4fde\u4fdf\u4fe0\u4fe1\u4fe2\u4fe3\u4fe4\u4fe5\u4fe6\u4fe7\u4fe8\u4fe9\u4fea\u4feb\u4fec\u4fed\u4fee\u4fef\u4ff0\u4ff1\u4ff2\u4ff3\u4ff4\u4ff5\u4ff6\u4ff7\u4ff8\u4ff9\u4ffa\u4ffb\u4ffc\u4ffd\u4ffe\u4fff\u5000\u5001\u5002\u5003\u5004\u5005\u5006\u5007\u5008\u5009\u500a\u500b\u500c\u500d\u500e\u500f\u5010\u5011\u5012\u5013\u5014\u5015\u5016\u5017\u5018\u5019\u501a\u501b\u501c\u501d\u501e\u501f\u5020\u5021\u5022\u5023\u5024\u5025\u5026\u5027\u5028\u5029\u502a\u502b\u502c\u502d\u502e\u502f\u5030\u5031\u5032\u5033\u5034\u5035\u5036\u5037\u5038\u5039\u503a\u503b\u503c\u503d\u503e\u503f\u5040\u5041\u5042\u5043\u5044\u5045\u5046\u5047\u5048\u5049\u504a\u504b\u504c\u504d\u504e\u504f\u5050\u5051\u5052\u5053\u5054\u5055\u5056\u5057\u5058\u5059\u505a\u505b\u505c\u505d\u505e\u505f\u5060\u5061\u5062\u5063\u5064\u5065\u5066\u5067\u5068\u5069\u506a\u506b\u506c\u506d\u506e\u506f\u5070\u5071\u5072\u5073\u5074\u5075\u5076\u5077\u5078\u5079\u507a\u507b\u507c\u507d\u507e\u507f\u5080\u5081\u5082\u5083\u5084\u5085\u5086\u5087\u5088\u5089\u508a\u508b\u508c\u508d\u508e\u508f\u5090\u5091\u5092\u5093\u5094\u5095\u5096\u5097\u5098\u5099\u509a\u509b\u509c\u509d\u509e\u509f\u50a0\u50a1\u50a2\u50a3\u50a4\u50a5\u50a6\u50a7\u50a8\u50a9\u50aa\u50ab\u50ac\u50ad\u50ae\u50af\u50b0\u50b1\u50b2\u50b3\u50b4\u50b5\u50b6\u50b7\u50b8\u50b9\u50ba\u50bb\u50bc\u50bd\u50be\u50bf\u50c0\u50c1\u50c2\u50c3\u50c4\u50c5\u50c6\u50c7\u50c8\u50c9\u50ca\u50cb\u50cc\u50cd\u50ce\u50cf\u50d0\u50d1\u50d2\u50d3\u50d4\u50d5\u50d6\u50d7\u50d8\u50d9\u50da\u50db\u50dc\u50dd\u50de\u50df\u50e0\u50e1\u50e2\u50e3\u50e4\u50e5\u50e6\u50e7\u50e8\u50e9\u50ea\u50eb\u50ec\u50ed\u50ee\u50ef\u50f0\u50f1\u50f2\u50f3\u50f4\u50f5\u50f6\u50f7\u50f8\u50f9\u50fa\u50fb\u50fc\u50fd\u50fe\u50ff\u5100\u5101\u5102\u5103\u5104\u5105\u5106\u5107\u5108\u5109\u510a\u510b\u510c\u510d\u510e\u510f\u5110\u5111\u5112\u5113\u5114\u5115\u5116\u5117\u5118\u5119\u511a\u511b\u511c\u511d\u511e\u511f\u5120\u5121\u5122\u5123\u5124\u5125\u5126\u5127\u5128\u5129\u512a\u512b\u512c\u512d\u512e\u512f\u5130\u5131\u5132\u5133\u5134\u5135\u5136\u5137\u5138\u5139\u513a\u513b\u513c\u513d\u513e\u513f\u5140\u5141\u5142\u5143\u5144\u5145\u5146\u5147\u5148\u5149\u514a\u514b\u514c\u514d\u514e\u514f\u5150\u5151\u5152\u5153\u5154\u5155\u5156\u5157\u5158\u5159\u515a\u515b\u515c\u515d\u515e\u515f\u5160\u5161\u5162\u5163\u5164\u5165\u5166\u5167\u5168\u5169\u516a\u516b\u516c\u516d\u516e\u516f\u5170\u5171\u5172\u5173\u5174\u5175\u5176\u5177\u5178\u5179\u517a\u517b\u517c\u517d\u517e\u517f\u5180\u5181\u5182\u5183\u5184\u5185\u5186\u5187\u5188\u5189\u518a\u518b\u518c\u518d\u518e\u518f\u5190\u5191\u5192\u5193\u5194\u5195\u5196\u5197\u5198\u5199\u519a\u519b\u519c\u519d\u519e\u519f\u51a0\u51a1\u51a2\u51a3\u51a4\u51a5\u51a6\u51a7\u51a8\u51a9\u51aa\u51ab\u51ac\u51ad\u51ae\u51af\u51b0\u51b1\u51b2\u51b3\u51b4\u51b5\u51b6\u51b7\u51b8\u51b9\u51ba\u51bb\u51bc\u51bd\u51be\u51bf\u51c0\u51c1\u51c2\u51c3\u51c4\u51c5\u51c6\u51c7\u51c8\u51c9\u51ca\u51cb\u51cc\u51cd\u51ce\u51cf\u51d0\u51d1\u51d2\u51d3\u51d4\u51d5\u51d6\u51d7\u51d8\u51d9\u51da\u51db\u51dc\u51dd\u51de\u51df\u51e0\u51e1\u51e2\u51e3\u51e4\u51e5\u51e6\u51e7\u51e8\u51e9\u51ea\u51eb\u51ec\u51ed\u51ee\u51ef\u51f0\u51f1\u51f2\u51f3\u51f4\u51f5\u51f6\u51f7\u51f8\u51f9\u51fa\u51fb\u51fc\u51fd\u51fe\u51ff\u5200\u5201\u5202\u5203\u5204\u5205\u5206\u5207\u5208\u5209\u520a\u520b\u520c\u520d\u520e\u520f\u5210\u5211\u5212\u5213\u5214\u5215\u5216\u5217\u5218\u5219\u521a\u521b\u521c\u521d\u521e\u521f\u5220\u5221\u5222\u5223\u5224\u5225\u5226\u5227\u5228\u5229\u522a\u522b\u522c\u522d\u522e\u522f\u5230\u5231\u5232\u5233\u5234\u5235\u5236\u5237\u5238\u5239\u523a\u523b\u523c\u523d\u523e\u523f\u5240\u5241\u5242\u5243\u5244\u5245\u5246\u5247\u5248\u5249\u524a\u524b\u524c\u524d\u524e\u524f\u5250\u5251\u5252\u5253\u5254\u5255\u5256\u5257\u5258\u5259\u525a\u525b\u525c\u525d\u525e\u525f\u5260\u5261\u5262\u5263\u5264\u5265\u5266\u5267\u5268\u5269\u526a\u526b\u526c\u526d\u526e\u526f\u5270\u5271\u5272\u5273\u5274\u5275\u5276\u5277\u5278\u5279\u527a\u527b\u527c\u527d\u527e\u527f\u5280\u5281\u5282\u5283\u5284\u5285\u5286\u5287\u5288\u5289\u528a\u528b\u528c\u528d\u528e\u528f\u5290\u5291\u5292\u5293\u5294\u5295\u5296\u5297\u5298\u5299\u529a\u529b\u529c\u529d\u529e\u529f\u52a0\u52a1\u52a2\u52a3\u52a4\u52a5\u52a6\u52a7\u52a8\u52a9\u52aa\u52ab\u52ac\u52ad\u52ae\u52af\u52b0\u52b1\u52b2\u52b3\u52b4\u52b5\u52b6\u52b7\u52b8\u52b9\u52ba\u52bb\u52bc\u52bd\u52be\u52bf\u52c0\u52c1\u52c2\u52c3\u52c4\u52c5\u52c6\u52c7\u52c8\u52c9\u52ca\u52cb\u52cc\u52cd\u52ce\u52cf\u52d0\u52d1\u52d2\u52d3\u52d4\u52d5\u52d6\u52d7\u52d8\u52d9\u52da\u52db\u52dc\u52dd\u52de\u52df\u52e0\u52e1\u52e2\u52e3\u52e4\u52e5\u52e6\u52e7\u52e8\u52e9\u52ea\u52eb\u52ec\u52ed\u52ee\u52ef\u52f0\u52f1\u52f2\u52f3\u52f4\u52f5\u52f6\u52f7\u52f8\u52f9\u52fa\u52fb\u52fc\u52fd\u52fe\u52ff\u5300\u5301\u5302\u5303\u5304\u5305\u5306\u5307\u5308\u5309\u530a\u530b\u530c\u530d\u530e\u530f\u5310\u5311\u5312\u5313\u5314\u5315\u5316\u5317\u5318\u5319\u531a\u531b\u531c\u531d\u531e\u531f\u5320\u5321\u5322\u5323\u5324\u5325\u5326\u5327\u5328\u5329\u532a\u532b\u532c\u532d\u532e\u532f\u5330\u5331\u5332\u5333\u5334\u5335\u5336\u5337\u5338\u5339\u533a\u533b\u533c\u533d\u533e\u533f\u5340\u5341\u5342\u5343\u5344\u5345\u5346\u5347\u5348\u5349\u534a\u534b\u534c\u534d\u534e\u534f\u5350\u5351\u5352\u5353\u5354\u5355\u5356\u5357\u5358\u5359\u535a\u535b\u535c\u535d\u535e\u535f\u5360\u5361\u5362\u5363\u5364\u5365\u5366\u5367\u5368\u5369\u536a\u536b\u536c\u536d\u536e\u536f\u5370\u5371\u5372\u5373\u5374\u5375\u5376\u5377\u5378\u5379\u537a\u537b\u537c\u537d\u537e\u537f\u5380\u5381\u5382\u5383\u5384\u5385\u5386\u5387\u5388\u5389\u538a\u538b\u538c\u538d\u538e\u538f\u5390\u5391\u5392\u5393\u5394\u5395\u5396\u5397\u5398\u5399\u539a\u539b\u539c\u539d\u539e\u539f\u53a0\u53a1\u53a2\u53a3\u53a4\u53a5\u53a6\u53a7\u53a8\u53a9\u53aa\u53ab\u53ac\u53ad\u53ae\u53af\u53b0\u53b1\u53b2\u53b3\u53b4\u53b5\u53b6\u53b7\u53b8\u53b9\u53ba\u53bb\u53bc\u53bd\u53be\u53bf\u53c0\u53c1\u53c2\u53c3\u53c4\u53c5\u53c6\u53c7\u53c8\u53c9\u53ca\u53cb\u53cc\u53cd\u53ce\u53cf\u53d0\u53d1\u53d2\u53d3\u53d4\u53d5\u53d6\u53d7\u53d8\u53d9\u53da\u53db\u53dc\u53dd\u53de\u53df\u53e0\u53e1\u53e2\u53e3\u53e4\u53e5\u53e6\u53e7\u53e8\u53e9\u53ea\u53eb\u53ec\u53ed\u53ee\u53ef\u53f0\u53f1\u53f2\u53f3\u53f4\u53f5\u53f6\u53f7\u53f8\u53f9\u53fa\u53fb\u53fc\u53fd\u53fe\u53ff\u5400\u5401\u5402\u5403\u5404\u5405\u5406\u5407\u5408\u5409\u540a\u540b\u540c\u540d\u540e\u540f\u5410\u5411\u5412\u5413\u5414\u5415\u5416\u5417\u5418\u5419\u541a\u541b\u541c\u541d\u541e\u541f\u5420\u5421\u5422\u5423\u5424\u5425\u5426\u5427\u5428\u5429\u542a\u542b\u542c\u542d\u542e\u542f\u5430\u5431\u5432\u5433\u5434\u5435\u5436\u5437\u5438\u5439\u543a\u543b\u543c\u543d\u543e\u543f\u5440\u5441\u5442\u5443\u5444\u5445\u5446\u5447\u5448\u5449\u544a\u544b\u544c\u544d\u544e\u544f\u5450\u5451\u5452\u5453\u5454\u5455\u5456\u5457\u5458\u5459\u545a\u545b\u545c\u545d\u545e\u545f\u5460\u5461\u5462\u5463\u5464\u5465\u5466\u5467\u5468\u5469\u546a\u546b\u546c\u546d\u546e\u546f\u5470\u5471\u5472\u5473\u5474\u5475\u5476\u5477\u5478\u5479\u547a\u547b\u547c\u547d\u547e\u547f\u5480\u5481\u5482\u5483\u5484\u5485\u5486\u5487\u5488\u5489\u548a\u548b\u548c\u548d\u548e\u548f\u5490\u5491\u5492\u5493\u5494\u5495\u5496\u5497\u5498\u5499\u549a\u549b\u549c\u549d\u549e\u549f\u54a0\u54a1\u54a2\u54a3\u54a4\u54a5\u54a6\u54a7\u54a8\u54a9\u54aa\u54ab\u54ac\u54ad\u54ae\u54af\u54b0\u54b1\u54b2\u54b3\u54b4\u54b5\u54b6\u54b7\u54b8\u54b9\u54ba\u54bb\u54bc\u54bd\u54be\u54bf\u54c0\u54c1\u54c2\u54c3\u54c4\u54c5\u54c6\u54c7\u54c8\u54c9\u54ca\u54cb\u54cc\u54cd\u54ce\u54cf\u54d0\u54d1\u54d2\u54d3\u54d4\u54d5\u54d6\u54d7\u54d8\u54d9\u54da\u54db\u54dc\u54dd\u54de\u54df\u54e0\u54e1\u54e2\u54e3\u54e4\u54e5\u54e6\u54e7\u54e8\u54e9\u54ea\u54eb\u54ec\u54ed\u54ee\u54ef\u54f0\u54f1\u54f2\u54f3\u54f4\u54f5\u54f6\u54f7\u54f8\u54f9\u54fa\u54fb\u54fc\u54fd\u54fe\u54ff\u5500\u5501\u5502\u5503\u5504\u5505\u5506\u5507\u5508\u5509\u550a\u550b\u550c\u550d\u550e\u550f\u5510\u5511\u5512\u5513\u5514\u5515\u5516\u5517\u5518\u5519\u551a\u551b\u551c\u551d\u551e\u551f\u5520\u5521\u5522\u5523\u5524\u5525\u5526\u5527\u5528\u5529\u552a\u552b\u552c\u552d\u552e\u552f\u5530\u5531\u5532\u5533\u5534\u5535\u5536\u5537\u5538\u5539\u553a\u553b\u553c\u553d\u553e\u553f\u5540\u5541\u5542\u5543\u5544\u5545\u5546\u5547\u5548\u5549\u554a\u554b\u554c\u554d\u554e\u554f\u5550\u5551\u5552\u5553\u5554\u5555\u5556\u5557\u5558\u5559\u555a\u555b\u555c\u555d\u555e\u555f\u5560\u5561\u5562\u5563\u5564\u5565\u5566\u5567\u5568\u5569\u556a\u556b\u556c\u556d\u556e\u556f\u5570\u5571\u5572\u5573\u5574\u5575\u5576\u5577\u5578\u5579\u557a\u557b\u557c\u557d\u557e\u557f\u5580\u5581\u5582\u5583\u5584\u5585\u5586\u5587\u5588\u5589\u558a\u558b\u558c\u558d\u558e\u558f\u5590\u5591\u5592\u5593\u5594\u5595\u5596\u5597\u5598\u5599\u559a\u559b\u559c\u559d\u559e\u559f\u55a0\u55a1\u55a2\u55a3\u55a4\u55a5\u55a6\u55a7\u55a8\u55a9\u55aa\u55ab\u55ac\u55ad\u55ae\u55af\u55b0\u55b1\u55b2\u55b3\u55b4\u55b5\u55b6\u55b7\u55b8\u55b9\u55ba\u55bb\u55bc\u55bd\u55be\u55bf\u55c0\u55c1\u55c2\u55c3\u55c4\u55c5\u55c6\u55c7\u55c8\u55c9\u55ca\u55cb\u55cc\u55cd\u55ce\u55cf\u55d0\u55d1\u55d2\u55d3\u55d4\u55d5\u55d6\u55d7\u55d8\u55d9\u55da\u55db\u55dc\u55dd\u55de\u55df\u55e0\u55e1\u55e2\u55e3\u55e4\u55e5\u55e6\u55e7\u55e8\u55e9\u55ea\u55eb\u55ec\u55ed\u55ee\u55ef\u55f0\u55f1\u55f2\u55f3\u55f4\u55f5\u55f6\u55f7\u55f8\u55f9\u55fa\u55fb\u55fc\u55fd\u55fe\u55ff\u5600\u5601\u5602\u5603\u5604\u5605\u5606\u5607\u5608\u5609\u560a\u560b\u560c\u560d\u560e\u560f\u5610\u5611\u5612\u5613\u5614\u5615\u5616\u5617\u5618\u5619\u561a\u561b\u561c\u561d\u561e\u561f\u5620\u5621\u5622\u5623\u5624\u5625\u5626\u5627\u5628\u5629\u562a\u562b\u562c\u562d\u562e\u562f\u5630\u5631\u5632\u5633\u5634\u5635\u5636\u5637\u5638\u5639\u563a\u563b\u563c\u563d\u563e\u563f\u5640\u5641\u5642\u5643\u5644\u5645\u5646\u5647\u5648\u5649\u564a\u564b\u564c\u564d\u564e\u564f\u5650\u5651\u5652\u5653\u5654\u5655\u5656\u5657\u5658\u5659\u565a\u565b\u565c\u565d\u565e\u565f\u5660\u5661\u5662\u5663\u5664\u5665\u5666\u5667\u5668\u5669\u566a\u566b\u566c\u566d\u566e\u566f\u5670\u5671\u5672\u5673\u5674\u5675\u5676\u5677\u5678\u5679\u567a\u567b\u567c\u567d\u567e\u567f\u5680\u5681\u5682\u5683\u5684\u5685\u5686\u5687\u5688\u5689\u568a\u568b\u568c\u568d\u568e\u568f\u5690\u5691\u5692\u5693\u5694\u5695\u5696\u5697\u5698\u5699\u569a\u569b\u569c\u569d\u569e\u569f\u56a0\u56a1\u56a2\u56a3\u56a4\u56a5\u56a6\u56a7\u56a8\u56a9\u56aa\u56ab\u56ac\u56ad\u56ae\u56af\u56b0\u56b1\u56b2\u56b3\u56b4\u56b5\u56b6\u56b7\u56b8\u56b9\u56ba\u56bb\u56bc\u56bd\u56be\u56bf\u56c0\u56c1\u56c2\u56c3\u56c4\u56c5\u56c6\u56c7\u56c8\u56c9\u56ca\u56cb\u56cc\u56cd\u56ce\u56cf\u56d0\u56d1\u56d2\u56d3\u56d4\u56d5\u56d6\u56d7\u56d8\u56d9\u56da\u56db\u56dc\u56dd\u56de\u56df\u56e0\u56e1\u56e2\u56e3\u56e4\u56e5\u56e6\u56e7\u56e8\u56e9\u56ea\u56eb\u56ec\u56ed\u56ee\u56ef\u56f0\u56f1\u56f2\u56f3\u56f4\u56f5\u56f6\u56f7\u56f8\u56f9\u56fa\u56fb\u56fc\u56fd\u56fe\u56ff\u5700\u5701\u5702\u5703\u5704\u5705\u5706\u5707\u5708\u5709\u570a\u570b\u570c\u570d\u570e\u570f\u5710\u5711\u5712\u5713\u5714\u5715\u5716\u5717\u5718\u5719\u571a\u571b\u571c\u571d\u571e\u571f\u5720\u5721\u5722\u5723\u5724\u5725\u5726\u5727\u5728\u5729\u572a\u572b\u572c\u572d\u572e\u572f\u5730\u5731\u5732\u5733\u5734\u5735\u5736\u5737\u5738\u5739\u573a\u573b\u573c\u573d\u573e\u573f\u5740\u5741\u5742\u5743\u5744\u5745\u5746\u5747\u5748\u5749\u574a\u574b\u574c\u574d\u574e\u574f\u5750\u5751\u5752\u5753\u5754\u5755\u5756\u5757\u5758\u5759\u575a\u575b\u575c\u575d\u575e\u575f\u5760\u5761\u5762\u5763\u5764\u5765\u5766\u5767\u5768\u5769\u576a\u576b\u576c\u576d\u576e\u576f\u5770\u5771\u5772\u5773\u5774\u5775\u5776\u5777\u5778\u5779\u577a\u577b\u577c\u577d\u577e\u577f\u5780\u5781\u5782\u5783\u5784\u5785\u5786\u5787\u5788\u5789\u578a\u578b\u578c\u578d\u578e\u578f\u5790\u5791\u5792\u5793\u5794\u5795\u5796\u5797\u5798\u5799\u579a\u579b\u579c\u579d\u579e\u579f\u57a0\u57a1\u57a2\u57a3\u57a4\u57a5\u57a6\u57a7\u57a8\u57a9\u57aa\u57ab\u57ac\u57ad\u57ae\u57af\u57b0\u57b1\u57b2\u57b3\u57b4\u57b5\u57b6\u57b7\u57b8\u57b9\u57ba\u57bb\u57bc\u57bd\u57be\u57bf\u57c0\u57c1\u57c2\u57c3\u57c4\u57c5\u57c6\u57c7\u57c8\u57c9\u57ca\u57cb\u57cc\u57cd\u57ce\u57cf\u57d0\u57d1\u57d2\u57d3\u57d4\u57d5\u57d6\u57d7\u57d8\u57d9\u57da\u57db\u57dc\u57dd\u57de\u57df\u57e0\u57e1\u57e2\u57e3\u57e4\u57e5\u57e6\u57e7\u57e8\u57e9\u57ea\u57eb\u57ec\u57ed\u57ee\u57ef\u57f0\u57f1\u57f2\u57f3\u57f4\u57f5\u57f6\u57f7\u57f8\u57f9\u57fa\u57fb\u57fc\u57fd\u57fe\u57ff\u5800\u5801\u5802\u5803\u5804\u5805\u5806\u5807\u5808\u5809\u580a\u580b\u580c\u580d\u580e\u580f\u5810\u5811\u5812\u5813\u5814\u5815\u5816\u5817\u5818\u5819\u581a\u581b\u581c\u581d\u581e\u581f\u5820\u5821\u5822\u5823\u5824\u5825\u5826\u5827\u5828\u5829\u582a\u582b\u582c\u582d\u582e\u582f\u5830\u5831\u5832\u5833\u5834\u5835\u5836\u5837\u5838\u5839\u583a\u583b\u583c\u583d\u583e\u583f\u5840\u5841\u5842\u5843\u5844\u5845\u5846\u5847\u5848\u5849\u584a\u584b\u584c\u584d\u584e\u584f\u5850\u5851\u5852\u5853\u5854\u5855\u5856\u5857\u5858\u5859\u585a\u585b\u585c\u585d\u585e\u585f\u5860\u5861\u5862\u5863\u5864\u5865\u5866\u5867\u5868\u5869\u586a\u586b\u586c\u586d\u586e\u586f\u5870\u5871\u5872\u5873\u5874\u5875\u5876\u5877\u5878\u5879\u587a\u587b\u587c\u587d\u587e\u587f\u5880\u5881\u5882\u5883\u5884\u5885\u5886\u5887\u5888\u5889\u588a\u588b\u588c\u588d\u588e\u588f\u5890\u5891\u5892\u5893\u5894\u5895\u5896\u5897\u5898\u5899\u589a\u589b\u589c\u589d\u589e\u589f\u58a0\u58a1\u58a2\u58a3\u58a4\u58a5\u58a6\u58a7\u58a8\u58a9\u58aa\u58ab\u58ac\u58ad\u58ae\u58af\u58b0\u58b1\u58b2\u58b3\u58b4\u58b5\u58b6\u58b7\u58b8\u58b9\u58ba\u58bb\u58bc\u58bd\u58be\u58bf\u58c0\u58c1\u58c2\u58c3\u58c4\u58c5\u58c6\u58c7\u58c8\u58c9\u58ca\u58cb\u58cc\u58cd\u58ce\u58cf\u58d0\u58d1\u58d2\u58d3\u58d4\u58d5\u58d6\u58d7\u58d8\u58d9\u58da\u58db\u58dc\u58dd\u58de\u58df\u58e0\u58e1\u58e2\u58e3\u58e4\u58e5\u58e6\u58e7\u58e8\u58e9\u58ea\u58eb\u58ec\u58ed\u58ee\u58ef\u58f0\u58f1\u58f2\u58f3\u58f4\u58f5\u58f6\u58f7\u58f8\u58f9\u58fa\u58fb\u58fc\u58fd\u58fe\u58ff\u5900\u5901\u5902\u5903\u5904\u5905\u5906\u5907\u5908\u5909\u590a\u590b\u590c\u590d\u590e\u590f\u5910\u5911\u5912\u5913\u5914\u5915\u5916\u5917\u5918\u5919\u591a\u591b\u591c\u591d\u591e\u591f\u5920\u5921\u5922\u5923\u5924\u5925\u5926\u5927\u5928\u5929\u592a\u592b\u592c\u592d\u592e\u592f\u5930\u5931\u5932\u5933\u5934\u5935\u5936\u5937\u5938\u5939\u593a\u593b\u593c\u593d\u593e\u593f\u5940\u5941\u5942\u5943\u5944\u5945\u5946\u5947\u5948\u5949\u594a\u594b\u594c\u594d\u594e\u594f\u5950\u5951\u5952\u5953\u5954\u5955\u5956\u5957\u5958\u5959\u595a\u595b\u595c\u595d\u595e\u595f\u5960\u5961\u5962\u5963\u5964\u5965\u5966\u5967\u5968\u5969\u596a\u596b\u596c\u596d\u596e\u596f\u5970\u5971\u5972\u5973\u5974\u5975\u5976\u5977\u5978\u5979\u597a\u597b\u597c\u597d\u597e\u597f\u5980\u5981\u5982\u5983\u5984\u5985\u5986\u5987\u5988\u5989\u598a\u598b\u598c\u598d\u598e\u598f\u5990\u5991\u5992\u5993\u5994\u5995\u5996\u5997\u5998\u5999\u599a\u599b\u599c\u599d\u599e\u599f\u59a0\u59a1\u59a2\u59a3\u59a4\u59a5\u59a6\u59a7\u59a8\u59a9\u59aa\u59ab\u59ac\u59ad\u59ae\u59af\u59b0\u59b1\u59b2\u59b3\u59b4\u59b5\u59b6\u59b7\u59b8\u59b9\u59ba\u59bb\u59bc\u59bd\u59be\u59bf\u59c0\u59c1\u59c2\u59c3\u59c4\u59c5\u59c6\u59c7\u59c8\u59c9\u59ca\u59cb\u59cc\u59cd\u59ce\u59cf\u59d0\u59d1\u59d2\u59d3\u59d4\u59d5\u59d6\u59d7\u59d8\u59d9\u59da\u59db\u59dc\u59dd\u59de\u59df\u59e0\u59e1\u59e2\u59e3\u59e4\u59e5\u59e6\u59e7\u59e8\u59e9\u59ea\u59eb\u59ec\u59ed\u59ee\u59ef\u59f0\u59f1\u59f2\u59f3\u59f4\u59f5\u59f6\u59f7\u59f8\u59f9\u59fa\u59fb\u59fc\u59fd\u59fe\u59ff\u5a00\u5a01\u5a02\u5a03\u5a04\u5a05\u5a06\u5a07\u5a08\u5a09\u5a0a\u5a0b\u5a0c\u5a0d\u5a0e\u5a0f\u5a10\u5a11\u5a12\u5a13\u5a14\u5a15\u5a16\u5a17\u5a18\u5a19\u5a1a\u5a1b\u5a1c\u5a1d\u5a1e\u5a1f\u5a20\u5a21\u5a22\u5a23\u5a24\u5a25\u5a26\u5a27\u5a28\u5a29\u5a2a\u5a2b\u5a2c\u5a2d\u5a2e\u5a2f\u5a30\u5a31\u5a32\u5a33\u5a34\u5a35\u5a36\u5a37\u5a38\u5a39\u5a3a\u5a3b\u5a3c\u5a3d\u5a3e\u5a3f\u5a40\u5a41\u5a42\u5a43\u5a44\u5a45\u5a46\u5a47\u5a48\u5a49\u5a4a\u5a4b\u5a4c\u5a4d\u5a4e\u5a4f\u5a50\u5a51\u5a52\u5a53\u5a54\u5a55\u5a56\u5a57\u5a58\u5a59\u5a5a\u5a5b\u5a5c\u5a5d\u5a5e\u5a5f\u5a60\u5a61\u5a62\u5a63\u5a64\u5a65\u5a66\u5a67\u5a68\u5a69\u5a6a\u5a6b\u5a6c\u5a6d\u5a6e\u5a6f\u5a70\u5a71\u5a72\u5a73\u5a74\u5a75\u5a76\u5a77\u5a78\u5a79\u5a7a\u5a7b\u5a7c\u5a7d\u5a7e\u5a7f\u5a80\u5a81\u5a82\u5a83\u5a84\u5a85\u5a86\u5a87\u5a88\u5a89\u5a8a\u5a8b\u5a8c\u5a8d\u5a8e\u5a8f\u5a90\u5a91\u5a92\u5a93\u5a94\u5a95\u5a96\u5a97\u5a98\u5a99\u5a9a\u5a9b\u5a9c\u5a9d\u5a9e\u5a9f\u5aa0\u5aa1\u5aa2\u5aa3\u5aa4\u5aa5\u5aa6\u5aa7\u5aa8\u5aa9\u5aaa\u5aab\u5aac\u5aad\u5aae\u5aaf\u5ab0\u5ab1\u5ab2\u5ab3\u5ab4\u5ab5\u5ab6\u5ab7\u5ab8\u5ab9\u5aba\u5abb\u5abc\u5abd\u5abe\u5abf\u5ac0\u5ac1\u5ac2\u5ac3\u5ac4\u5ac5\u5ac6\u5ac7\u5ac8\u5ac9\u5aca\u5acb\u5acc\u5acd\u5ace\u5acf\u5ad0\u5ad1\u5ad2\u5ad3\u5ad4\u5ad5\u5ad6\u5ad7\u5ad8\u5ad9\u5ada\u5adb\u5adc\u5add\u5ade\u5adf\u5ae0\u5ae1\u5ae2\u5ae3\u5ae4\u5ae5\u5ae6\u5ae7\u5ae8\u5ae9\u5aea\u5aeb\u5aec\u5aed\u5aee\u5aef\u5af0\u5af1\u5af2\u5af3\u5af4\u5af5\u5af6\u5af7\u5af8\u5af9\u5afa\u5afb\u5afc\u5afd\u5afe\u5aff\u5b00\u5b01\u5b02\u5b03\u5b04\u5b05\u5b06\u5b07\u5b08\u5b09\u5b0a\u5b0b\u5b0c\u5b0d\u5b0e\u5b0f\u5b10\u5b11\u5b12\u5b13\u5b14\u5b15\u5b16\u5b17\u5b18\u5b19\u5b1a\u5b1b\u5b1c\u5b1d\u5b1e\u5b1f\u5b20\u5b21\u5b22\u5b23\u5b24\u5b25\u5b26\u5b27\u5b28\u5b29\u5b2a\u5b2b\u5b2c\u5b2d\u5b2e\u5b2f\u5b30\u5b31\u5b32\u5b33\u5b34\u5b35\u5b36\u5b37\u5b38\u5b39\u5b3a\u5b3b\u5b3c\u5b3d\u5b3e\u5b3f\u5b40\u5b41\u5b42\u5b43\u5b44\u5b45\u5b46\u5b47\u5b48\u5b49\u5b4a\u5b4b\u5b4c\u5b4d\u5b4e\u5b4f\u5b50\u5b51\u5b52\u5b53\u5b54\u5b55\u5b56\u5b57\u5b58\u5b59\u5b5a\u5b5b\u5b5c\u5b5d\u5b5e\u5b5f\u5b60\u5b61\u5b62\u5b63\u5b64\u5b65\u5b66\u5b67\u5b68\u5b69\u5b6a\u5b6b\u5b6c\u5b6d\u5b6e\u5b6f\u5b70\u5b71\u5b72\u5b73\u5b74\u5b75\u5b76\u5b77\u5b78\u5b79\u5b7a\u5b7b\u5b7c\u5b7d\u5b7e\u5b7f\u5b80\u5b81\u5b82\u5b83\u5b84\u5b85\u5b86\u5b87\u5b88\u5b89\u5b8a\u5b8b\u5b8c\u5b8d\u5b8e\u5b8f\u5b90\u5b91\u5b92\u5b93\u5b94\u5b95\u5b96\u5b97\u5b98\u5b99\u5b9a\u5b9b\u5b9c\u5b9d\u5b9e\u5b9f\u5ba0\u5ba1\u5ba2\u5ba3\u5ba4\u5ba5\u5ba6\u5ba7\u5ba8\u5ba9\u5baa\u5bab\u5bac\u5bad\u5bae\u5baf\u5bb0\u5bb1\u5bb2\u5bb3\u5bb4\u5bb5\u5bb6\u5bb7\u5bb8\u5bb9\u5bba\u5bbb\u5bbc\u5bbd\u5bbe\u5bbf\u5bc0\u5bc1\u5bc2\u5bc3\u5bc4\u5bc5\u5bc6\u5bc7\u5bc8\u5bc9\u5bca\u5bcb\u5bcc\u5bcd\u5bce\u5bcf\u5bd0\u5bd1\u5bd2\u5bd3\u5bd4\u5bd5\u5bd6\u5bd7\u5bd8\u5bd9\u5bda\u5bdb\u5bdc\u5bdd\u5bde\u5bdf\u5be0\u5be1\u5be2\u5be3\u5be4\u5be5\u5be6\u5be7\u5be8\u5be9\u5bea\u5beb\u5bec\u5bed\u5bee\u5bef\u5bf0\u5bf1\u5bf2\u5bf3\u5bf4\u5bf5\u5bf6\u5bf7\u5bf8\u5bf9\u5bfa\u5bfb\u5bfc\u5bfd\u5bfe\u5bff\u5c00\u5c01\u5c02\u5c03\u5c04\u5c05\u5c06\u5c07\u5c08\u5c09\u5c0a\u5c0b\u5c0c\u5c0d\u5c0e\u5c0f\u5c10\u5c11\u5c12\u5c13\u5c14\u5c15\u5c16\u5c17\u5c18\u5c19\u5c1a\u5c1b\u5c1c\u5c1d\u5c1e\u5c1f\u5c20\u5c21\u5c22\u5c23\u5c24\u5c25\u5c26\u5c27\u5c28\u5c29\u5c2a\u5c2b\u5c2c\u5c2d\u5c2e\u5c2f\u5c30\u5c31\u5c32\u5c33\u5c34\u5c35\u5c36\u5c37\u5c38\u5c39\u5c3a\u5c3b\u5c3c\u5c3d\u5c3e\u5c3f\u5c40\u5c41\u5c42\u5c43\u5c44\u5c45\u5c46\u5c47\u5c48\u5c49\u5c4a\u5c4b\u5c4c\u5c4d\u5c4e\u5c4f\u5c50\u5c51\u5c52\u5c53\u5c54\u5c55\u5c56\u5c57\u5c58\u5c59\u5c5a\u5c5b\u5c5c\u5c5d\u5c5e\u5c5f\u5c60\u5c61\u5c62\u5c63\u5c64\u5c65\u5c66\u5c67\u5c68\u5c69\u5c6a\u5c6b\u5c6c\u5c6d\u5c6e\u5c6f\u5c70\u5c71\u5c72\u5c73\u5c74\u5c75\u5c76\u5c77\u5c78\u5c79\u5c7a\u5c7b\u5c7c\u5c7d\u5c7e\u5c7f\u5c80\u5c81\u5c82\u5c83\u5c84\u5c85\u5c86\u5c87\u5c88\u5c89\u5c8a\u5c8b\u5c8c\u5c8d\u5c8e\u5c8f\u5c90\u5c91\u5c92\u5c93\u5c94\u5c95\u5c96\u5c97\u5c98\u5c99\u5c9a\u5c9b\u5c9c\u5c9d\u5c9e\u5c9f\u5ca0\u5ca1\u5ca2\u5ca3\u5ca4\u5ca5\u5ca6\u5ca7\u5ca8\u5ca9\u5caa\u5cab\u5cac\u5cad\u5cae\u5caf\u5cb0\u5cb1\u5cb2\u5cb3\u5cb4\u5cb5\u5cb6\u5cb7\u5cb8\u5cb9\u5cba\u5cbb\u5cbc\u5cbd\u5cbe\u5cbf\u5cc0\u5cc1\u5cc2\u5cc3\u5cc4\u5cc5\u5cc6\u5cc7\u5cc8\u5cc9\u5cca\u5ccb\u5ccc\u5ccd\u5cce\u5ccf\u5cd0\u5cd1\u5cd2\u5cd3\u5cd4\u5cd5\u5cd6\u5cd7\u5cd8\u5cd9\u5cda\u5cdb\u5cdc\u5cdd\u5cde\u5cdf\u5ce0\u5ce1\u5ce2\u5ce3\u5ce4\u5ce5\u5ce6\u5ce7\u5ce8\u5ce9\u5cea\u5ceb\u5cec\u5ced\u5cee\u5cef\u5cf0\u5cf1\u5cf2\u5cf3\u5cf4\u5cf5\u5cf6\u5cf7\u5cf8\u5cf9\u5cfa\u5cfb\u5cfc\u5cfd\u5cfe\u5cff\u5d00\u5d01\u5d02\u5d03\u5d04\u5d05\u5d06\u5d07\u5d08\u5d09\u5d0a\u5d0b\u5d0c\u5d0d\u5d0e\u5d0f\u5d10\u5d11\u5d12\u5d13\u5d14\u5d15\u5d16\u5d17\u5d18\u5d19\u5d1a\u5d1b\u5d1c\u5d1d\u5d1e\u5d1f\u5d20\u5d21\u5d22\u5d23\u5d24\u5d25\u5d26\u5d27\u5d28\u5d29\u5d2a\u5d2b\u5d2c\u5d2d\u5d2e\u5d2f\u5d30\u5d31\u5d32\u5d33\u5d34\u5d35\u5d36\u5d37\u5d38\u5d39\u5d3a\u5d3b\u5d3c\u5d3d\u5d3e\u5d3f\u5d40\u5d41\u5d42\u5d43\u5d44\u5d45\u5d46\u5d47\u5d48\u5d49\u5d4a\u5d4b\u5d4c\u5d4d\u5d4e\u5d4f\u5d50\u5d51\u5d52\u5d53\u5d54\u5d55\u5d56\u5d57\u5d58\u5d59\u5d5a\u5d5b\u5d5c\u5d5d\u5d5e\u5d5f\u5d60\u5d61\u5d62\u5d63\u5d64\u5d65\u5d66\u5d67\u5d68\u5d69\u5d6a\u5d6b\u5d6c\u5d6d\u5d6e\u5d6f\u5d70\u5d71\u5d72\u5d73\u5d74\u5d75\u5d76\u5d77\u5d78\u5d79\u5d7a\u5d7b\u5d7c\u5d7d\u5d7e\u5d7f\u5d80\u5d81\u5d82\u5d83\u5d84\u5d85\u5d86\u5d87\u5d88\u5d89\u5d8a\u5d8b\u5d8c\u5d8d\u5d8e\u5d8f\u5d90\u5d91\u5d92\u5d93\u5d94\u5d95\u5d96\u5d97\u5d98\u5d99\u5d9a\u5d9b\u5d9c\u5d9d\u5d9e\u5d9f\u5da0\u5da1\u5da2\u5da3\u5da4\u5da5\u5da6\u5da7\u5da8\u5da9\u5daa\u5dab\u5dac\u5dad\u5dae\u5daf\u5db0\u5db1\u5db2\u5db3\u5db4\u5db5\u5db6\u5db7\u5db8\u5db9\u5dba\u5dbb\u5dbc\u5dbd\u5dbe\u5dbf\u5dc0\u5dc1\u5dc2\u5dc3\u5dc4\u5dc5\u5dc6\u5dc7\u5dc8\u5dc9\u5dca\u5dcb\u5dcc\u5dcd\u5dce\u5dcf\u5dd0\u5dd1\u5dd2\u5dd3\u5dd4\u5dd5\u5dd6\u5dd7\u5dd8\u5dd9\u5dda\u5ddb\u5ddc\u5ddd\u5dde\u5ddf\u5de0\u5de1\u5de2\u5de3\u5de4\u5de5\u5de6\u5de7\u5de8\u5de9\u5dea\u5deb\u5dec\u5ded\u5dee\u5def\u5df0\u5df1\u5df2\u5df3\u5df4\u5df5\u5df6\u5df7\u5df8\u5df9\u5dfa\u5dfb\u5dfc\u5dfd\u5dfe\u5dff\u5e00\u5e01\u5e02\u5e03\u5e04\u5e05\u5e06\u5e07\u5e08\u5e09\u5e0a\u5e0b\u5e0c\u5e0d\u5e0e\u5e0f\u5e10\u5e11\u5e12\u5e13\u5e14\u5e15\u5e16\u5e17\u5e18\u5e19\u5e1a\u5e1b\u5e1c\u5e1d\u5e1e\u5e1f\u5e20\u5e21\u5e22\u5e23\u5e24\u5e25\u5e26\u5e27\u5e28\u5e29\u5e2a\u5e2b\u5e2c\u5e2d\u5e2e\u5e2f\u5e30\u5e31\u5e32\u5e33\u5e34\u5e35\u5e36\u5e37\u5e38\u5e39\u5e3a\u5e3b\u5e3c\u5e3d\u5e3e\u5e3f\u5e40\u5e41\u5e42\u5e43\u5e44\u5e45\u5e46\u5e47\u5e48\u5e49\u5e4a\u5e4b\u5e4c\u5e4d\u5e4e\u5e4f\u5e50\u5e51\u5e52\u5e53\u5e54\u5e55\u5e56\u5e57\u5e58\u5e59\u5e5a\u5e5b\u5e5c\u5e5d\u5e5e\u5e5f\u5e60\u5e61\u5e62\u5e63\u5e64\u5e65\u5e66\u5e67\u5e68\u5e69\u5e6a\u5e6b\u5e6c\u5e6d\u5e6e\u5e6f\u5e70\u5e71\u5e72\u5e73\u5e74\u5e75\u5e76\u5e77\u5e78\u5e79\u5e7a\u5e7b\u5e7c\u5e7d\u5e7e\u5e7f\u5e80\u5e81\u5e82\u5e83\u5e84\u5e85\u5e86\u5e87\u5e88\u5e89\u5e8a\u5e8b\u5e8c\u5e8d\u5e8e\u5e8f\u5e90\u5e91\u5e92\u5e93\u5e94\u5e95\u5e96\u5e97\u5e98\u5e99\u5e9a\u5e9b\u5e9c\u5e9d\u5e9e\u5e9f\u5ea0\u5ea1\u5ea2\u5ea3\u5ea4\u5ea5\u5ea6\u5ea7\u5ea8\u5ea9\u5eaa\u5eab\u5eac\u5ead\u5eae\u5eaf\u5eb0\u5eb1\u5eb2\u5eb3\u5eb4\u5eb5\u5eb6\u5eb7\u5eb8\u5eb9\u5eba\u5ebb\u5ebc\u5ebd\u5ebe\u5ebf\u5ec0\u5ec1\u5ec2\u5ec3\u5ec4\u5ec5\u5ec6\u5ec7\u5ec8\u5ec9\u5eca\u5ecb\u5ecc\u5ecd\u5ece\u5ecf\u5ed0\u5ed1\u5ed2\u5ed3\u5ed4\u5ed5\u5ed6\u5ed7\u5ed8\u5ed9\u5eda\u5edb\u5edc\u5edd\u5ede\u5edf\u5ee0\u5ee1\u5ee2\u5ee3\u5ee4\u5ee5\u5ee6\u5ee7\u5ee8\u5ee9\u5eea\u5eeb\u5eec\u5eed\u5eee\u5eef\u5ef0\u5ef1\u5ef2\u5ef3\u5ef4\u5ef5\u5ef6\u5ef7\u5ef8\u5ef9\u5efa\u5efb\u5efc\u5efd\u5efe\u5eff\u5f00\u5f01\u5f02\u5f03\u5f04\u5f05\u5f06\u5f07\u5f08\u5f09\u5f0a\u5f0b\u5f0c\u5f0d\u5f0e\u5f0f\u5f10\u5f11\u5f12\u5f13\u5f14\u5f15\u5f16\u5f17\u5f18\u5f19\u5f1a\u5f1b\u5f1c\u5f1d\u5f1e\u5f1f\u5f20\u5f21\u5f22\u5f23\u5f24\u5f25\u5f26\u5f27\u5f28\u5f29\u5f2a\u5f2b\u5f2c\u5f2d\u5f2e\u5f2f\u5f30\u5f31\u5f32\u5f33\u5f34\u5f35\u5f36\u5f37\u5f38\u5f39\u5f3a\u5f3b\u5f3c\u5f3d\u5f3e\u5f3f\u5f40\u5f41\u5f42\u5f43\u5f44\u5f45\u5f46\u5f47\u5f48\u5f49\u5f4a\u5f4b\u5f4c\u5f4d\u5f4e\u5f4f\u5f50\u5f51\u5f52\u5f53\u5f54\u5f55\u5f56\u5f57\u5f58\u5f59\u5f5a\u5f5b\u5f5c\u5f5d\u5f5e\u5f5f\u5f60\u5f61\u5f62\u5f63\u5f64\u5f65\u5f66\u5f67\u5f68\u5f69\u5f6a\u5f6b\u5f6c\u5f6d\u5f6e\u5f6f\u5f70\u5f71\u5f72\u5f73\u5f74\u5f75\u5f76\u5f77\u5f78\u5f79\u5f7a\u5f7b\u5f7c\u5f7d\u5f7e\u5f7f\u5f80\u5f81\u5f82\u5f83\u5f84\u5f85\u5f86\u5f87\u5f88\u5f89\u5f8a\u5f8b\u5f8c\u5f8d\u5f8e\u5f8f\u5f90\u5f91\u5f92\u5f93\u5f94\u5f95\u5f96\u5f97\u5f98\u5f99\u5f9a\u5f9b\u5f9c\u5f9d\u5f9e\u5f9f\u5fa0\u5fa1\u5fa2\u5fa3\u5fa4\u5fa5\u5fa6\u5fa7\u5fa8\u5fa9\u5faa\u5fab\u5fac\u5fad\u5fae\u5faf\u5fb0\u5fb1\u5fb2\u5fb3\u5fb4\u5fb5\u5fb6\u5fb7\u5fb8\u5fb9\u5fba\u5fbb\u5fbc\u5fbd\u5fbe\u5fbf\u5fc0\u5fc1\u5fc2\u5fc3\u5fc4\u5fc5\u5fc6\u5fc7\u5fc8\u5fc9\u5fca\u5fcb\u5fcc\u5fcd\u5fce\u5fcf\u5fd0\u5fd1\u5fd2\u5fd3\u5fd4\u5fd5\u5fd6\u5fd7\u5fd8\u5fd9\u5fda\u5fdb\u5fdc\u5fdd\u5fde\u5fdf\u5fe0\u5fe1\u5fe2\u5fe3\u5fe4\u5fe5\u5fe6\u5fe7\u5fe8\u5fe9\u5fea\u5feb\u5fec\u5fed\u5fee\u5fef\u5ff0\u5ff1\u5ff2\u5ff3\u5ff4\u5ff5\u5ff6\u5ff7\u5ff8\u5ff9\u5ffa\u5ffb\u5ffc\u5ffd\u5ffe\u5fff\u6000\u6001\u6002\u6003\u6004\u6005\u6006\u6007\u6008\u6009\u600a\u600b\u600c\u600d\u600e\u600f\u6010\u6011\u6012\u6013\u6014\u6015\u6016\u6017\u6018\u6019\u601a\u601b\u601c\u601d\u601e\u601f\u6020\u6021\u6022\u6023\u6024\u6025\u6026\u6027\u6028\u6029\u602a\u602b\u602c\u602d\u602e\u602f\u6030\u6031\u6032\u6033\u6034\u6035\u6036\u6037\u6038\u6039\u603a\u603b\u603c\u603d\u603e\u603f\u6040\u6041\u6042\u6043\u6044\u6045\u6046\u6047\u6048\u6049\u604a\u604b\u604c\u604d\u604e\u604f\u6050\u6051\u6052\u6053\u6054\u6055\u6056\u6057\u6058\u6059\u605a\u605b\u605c\u605d\u605e\u605f\u6060\u6061\u6062\u6063\u6064\u6065\u6066\u6067\u6068\u6069\u606a\u606b\u606c\u606d\u606e\u606f\u6070\u6071\u6072\u6073\u6074\u6075\u6076\u6077\u6078\u6079\u607a\u607b\u607c\u607d\u607e\u607f\u6080\u6081\u6082\u6083\u6084\u6085\u6086\u6087\u6088\u6089\u608a\u608b\u608c\u608d\u608e\u608f\u6090\u6091\u6092\u6093\u6094\u6095\u6096\u6097\u6098\u6099\u609a\u609b\u609c\u609d\u609e\u609f\u60a0\u60a1\u60a2\u60a3\u60a4\u60a5\u60a6\u60a7\u60a8\u60a9\u60aa\u60ab\u60ac\u60ad\u60ae\u60af\u60b0\u60b1\u60b2\u60b3\u60b4\u60b5\u60b6\u60b7\u60b8\u60b9\u60ba\u60bb\u60bc\u60bd\u60be\u60bf\u60c0\u60c1\u60c2\u60c3\u60c4\u60c5\u60c6\u60c7\u60c8\u60c9\u60ca\u60cb\u60cc\u60cd\u60ce\u60cf\u60d0\u60d1\u60d2\u60d3\u60d4\u60d5\u60d6\u60d7\u60d8\u60d9\u60da\u60db\u60dc\u60dd\u60de\u60df\u60e0\u60e1\u60e2\u60e3\u60e4\u60e5\u60e6\u60e7\u60e8\u60e9\u60ea\u60eb\u60ec\u60ed\u60ee\u60ef\u60f0\u60f1\u60f2\u60f3\u60f4\u60f5\u60f6\u60f7\u60f8\u60f9\u60fa\u60fb\u60fc\u60fd\u60fe\u60ff\u6100\u6101\u6102\u6103\u6104\u6105\u6106\u6107\u6108\u6109\u610a\u610b\u610c\u610d\u610e\u610f\u6110\u6111\u6112\u6113\u6114\u6115\u6116\u6117\u6118\u6119\u611a\u611b\u611c\u611d\u611e\u611f\u6120\u6121\u6122\u6123\u6124\u6125\u6126\u6127\u6128\u6129\u612a\u612b\u612c\u612d\u612e\u612f\u6130\u6131\u6132\u6133\u6134\u6135\u6136\u6137\u6138\u6139\u613a\u613b\u613c\u613d\u613e\u613f\u6140\u6141\u6142\u6143\u6144\u6145\u6146\u6147\u6148\u6149\u614a\u614b\u614c\u614d\u614e\u614f\u6150\u6151\u6152\u6153\u6154\u6155\u6156\u6157\u6158\u6159\u615a\u615b\u615c\u615d\u615e\u615f\u6160\u6161\u6162\u6163\u6164\u6165\u6166\u6167\u6168\u6169\u616a\u616b\u616c\u616d\u616e\u616f\u6170\u6171\u6172\u6173\u6174\u6175\u6176\u6177\u6178\u6179\u617a\u617b\u617c\u617d\u617e\u617f\u6180\u6181\u6182\u6183\u6184\u6185\u6186\u6187\u6188\u6189\u618a\u618b\u618c\u618d\u618e\u618f\u6190\u6191\u6192\u6193\u6194\u6195\u6196\u6197\u6198\u6199\u619a\u619b\u619c\u619d\u619e\u619f\u61a0\u61a1\u61a2\u61a3\u61a4\u61a5\u61a6\u61a7\u61a8\u61a9\u61aa\u61ab\u61ac\u61ad\u61ae\u61af\u61b0\u61b1\u61b2\u61b3\u61b4\u61b5\u61b6\u61b7\u61b8\u61b9\u61ba\u61bb\u61bc\u61bd\u61be\u61bf\u61c0\u61c1\u61c2\u61c3\u61c4\u61c5\u61c6\u61c7\u61c8\u61c9\u61ca\u61cb\u61cc\u61cd\u61ce\u61cf\u61d0\u61d1\u61d2\u61d3\u61d4\u61d5\u61d6\u61d7\u61d8\u61d9\u61da\u61db\u61dc\u61dd\u61de\u61df\u61e0\u61e1\u61e2\u61e3\u61e4\u61e5\u61e6\u61e7\u61e8\u61e9\u61ea\u61eb\u61ec\u61ed\u61ee\u61ef\u61f0\u61f1\u61f2\u61f3\u61f4\u61f5\u61f6\u61f7\u61f8\u61f9\u61fa\u61fb\u61fc\u61fd\u61fe\u61ff\u6200\u6201\u6202\u6203\u6204\u6205\u6206\u6207\u6208\u6209\u620a\u620b\u620c\u620d\u620e\u620f\u6210\u6211\u6212\u6213\u6214\u6215\u6216\u6217\u6218\u6219\u621a\u621b\u621c\u621d\u621e\u621f\u6220\u6221\u6222\u6223\u6224\u6225\u6226\u6227\u6228\u6229\u622a\u622b\u622c\u622d\u622e\u622f\u6230\u6231\u6232\u6233\u6234\u6235\u6236\u6237\u6238\u6239\u623a\u623b\u623c\u623d\u623e\u623f\u6240\u6241\u6242\u6243\u6244\u6245\u6246\u6247\u6248\u6249\u624a\u624b\u624c\u624d\u624e\u624f\u6250\u6251\u6252\u6253\u6254\u6255\u6256\u6257\u6258\u6259\u625a\u625b\u625c\u625d\u625e\u625f\u6260\u6261\u6262\u6263\u6264\u6265\u6266\u6267\u6268\u6269\u626a\u626b\u626c\u626d\u626e\u626f\u6270\u6271\u6272\u6273\u6274\u6275\u6276\u6277\u6278\u6279\u627a\u627b\u627c\u627d\u627e\u627f\u6280\u6281\u6282\u6283\u6284\u6285\u6286\u6287\u6288\u6289\u628a\u628b\u628c\u628d\u628e\u628f\u6290\u6291\u6292\u6293\u6294\u6295\u6296\u6297\u6298\u6299\u629a\u629b\u629c\u629d\u629e\u629f\u62a0\u62a1\u62a2\u62a3\u62a4\u62a5\u62a6\u62a7\u62a8\u62a9\u62aa\u62ab\u62ac\u62ad\u62ae\u62af\u62b0\u62b1\u62b2\u62b3\u62b4\u62b5\u62b6\u62b7\u62b8\u62b9\u62ba\u62bb\u62bc\u62bd\u62be\u62bf\u62c0\u62c1\u62c2\u62c3\u62c4\u62c5\u62c6\u62c7\u62c8\u62c9\u62ca\u62cb\u62cc\u62cd\u62ce\u62cf\u62d0\u62d1\u62d2\u62d3\u62d4\u62d5\u62d6\u62d7\u62d8\u62d9\u62da\u62db\u62dc\u62dd\u62de\u62df\u62e0\u62e1\u62e2\u62e3\u62e4\u62e5\u62e6\u62e7\u62e8\u62e9\u62ea\u62eb\u62ec\u62ed\u62ee\u62ef\u62f0\u62f1\u62f2\u62f3\u62f4\u62f5\u62f6\u62f7\u62f8\u62f9\u62fa\u62fb\u62fc\u62fd\u62fe\u62ff\u6300\u6301\u6302\u6303\u6304\u6305\u6306\u6307\u6308\u6309\u630a\u630b\u630c\u630d\u630e\u630f\u6310\u6311\u6312\u6313\u6314\u6315\u6316\u6317\u6318\u6319\u631a\u631b\u631c\u631d\u631e\u631f\u6320\u6321\u6322\u6323\u6324\u6325\u6326\u6327\u6328\u6329\u632a\u632b\u632c\u632d\u632e\u632f\u6330\u6331\u6332\u6333\u6334\u6335\u6336\u6337\u6338\u6339\u633a\u633b\u633c\u633d\u633e\u633f\u6340\u6341\u6342\u6343\u6344\u6345\u6346\u6347\u6348\u6349\u634a\u634b\u634c\u634d\u634e\u634f\u6350\u6351\u6352\u6353\u6354\u6355\u6356\u6357\u6358\u6359\u635a\u635b\u635c\u635d\u635e\u635f\u6360\u6361\u6362\u6363\u6364\u6365\u6366\u6367\u6368\u6369\u636a\u636b\u636c\u636d\u636e\u636f\u6370\u6371\u6372\u6373\u6374\u6375\u6376\u6377\u6378\u6379\u637a\u637b\u637c\u637d\u637e\u637f\u6380\u6381\u6382\u6383\u6384\u6385\u6386\u6387\u6388\u6389\u638a\u638b\u638c\u638d\u638e\u638f\u6390\u6391\u6392\u6393\u6394\u6395\u6396\u6397\u6398\u6399\u639a\u639b\u639c\u639d\u639e\u639f\u63a0\u63a1\u63a2\u63a3\u63a4\u63a5\u63a6\u63a7\u63a8\u63a9\u63aa\u63ab\u63ac\u63ad\u63ae\u63af\u63b0\u63b1\u63b2\u63b3\u63b4\u63b5\u63b6\u63b7\u63b8\u63b9\u63ba\u63bb\u63bc\u63bd\u63be\u63bf\u63c0\u63c1\u63c2\u63c3\u63c4\u63c5\u63c6\u63c7\u63c8\u63c9\u63ca\u63cb\u63cc\u63cd\u63ce\u63cf\u63d0\u63d1\u63d2\u63d3\u63d4\u63d5\u63d6\u63d7\u63d8\u63d9\u63da\u63db\u63dc\u63dd\u63de\u63df\u63e0\u63e1\u63e2\u63e3\u63e4\u63e5\u63e6\u63e7\u63e8\u63e9\u63ea\u63eb\u63ec\u63ed\u63ee\u63ef\u63f0\u63f1\u63f2\u63f3\u63f4\u63f5\u63f6\u63f7\u63f8\u63f9\u63fa\u63fb\u63fc\u63fd\u63fe\u63ff\u6400\u6401\u6402\u6403\u6404\u6405\u6406\u6407\u6408\u6409\u640a\u640b\u640c\u640d\u640e\u640f\u6410\u6411\u6412\u6413\u6414\u6415\u6416\u6417\u6418\u6419\u641a\u641b\u641c\u641d\u641e\u641f\u6420\u6421\u6422\u6423\u6424\u6425\u6426\u6427\u6428\u6429\u642a\u642b\u642c\u642d\u642e\u642f\u6430\u6431\u6432\u6433\u6434\u6435\u6436\u6437\u6438\u6439\u643a\u643b\u643c\u643d\u643e\u643f\u6440\u6441\u6442\u6443\u6444\u6445\u6446\u6447\u6448\u6449\u644a\u644b\u644c\u644d\u644e\u644f\u6450\u6451\u6452\u6453\u6454\u6455\u6456\u6457\u6458\u6459\u645a\u645b\u645c\u645d\u645e\u645f\u6460\u6461\u6462\u6463\u6464\u6465\u6466\u6467\u6468\u6469\u646a\u646b\u646c\u646d\u646e\u646f\u6470\u6471\u6472\u6473\u6474\u6475\u6476\u6477\u6478\u6479\u647a\u647b\u647c\u647d\u647e\u647f\u6480\u6481\u6482\u6483\u6484\u6485\u6486\u6487\u6488\u6489\u648a\u648b\u648c\u648d\u648e\u648f\u6490\u6491\u6492\u6493\u6494\u6495\u6496\u6497\u6498\u6499\u649a\u649b\u649c\u649d\u649e\u649f\u64a0\u64a1\u64a2\u64a3\u64a4\u64a5\u64a6\u64a7\u64a8\u64a9\u64aa\u64ab\u64ac\u64ad\u64ae\u64af\u64b0\u64b1\u64b2\u64b3\u64b4\u64b5\u64b6\u64b7\u64b8\u64b9\u64ba\u64bb\u64bc\u64bd\u64be\u64bf\u64c0\u64c1\u64c2\u64c3\u64c4\u64c5\u64c6\u64c7\u64c8\u64c9\u64ca\u64cb\u64cc\u64cd\u64ce\u64cf\u64d0\u64d1\u64d2\u64d3\u64d4\u64d5\u64d6\u64d7\u64d8\u64d9\u64da\u64db\u64dc\u64dd\u64de\u64df\u64e0\u64e1\u64e2\u64e3\u64e4\u64e5\u64e6\u64e7\u64e8\u64e9\u64ea\u64eb\u64ec\u64ed\u64ee\u64ef\u64f0\u64f1\u64f2\u64f3\u64f4\u64f5\u64f6\u64f7\u64f8\u64f9\u64fa\u64fb\u64fc\u64fd\u64fe\u64ff\u6500\u6501\u6502\u6503\u6504\u6505\u6506\u6507\u6508\u6509\u650a\u650b\u650c\u650d\u650e\u650f\u6510\u6511\u6512\u6513\u6514\u6515\u6516\u6517\u6518\u6519\u651a\u651b\u651c\u651d\u651e\u651f\u6520\u6521\u6522\u6523\u6524\u6525\u6526\u6527\u6528\u6529\u652a\u652b\u652c\u652d\u652e\u652f\u6530\u6531\u6532\u6533\u6534\u6535\u6536\u6537\u6538\u6539\u653a\u653b\u653c\u653d\u653e\u653f\u6540\u6541\u6542\u6543\u6544\u6545\u6546\u6547\u6548\u6549\u654a\u654b\u654c\u654d\u654e\u654f\u6550\u6551\u6552\u6553\u6554\u6555\u6556\u6557\u6558\u6559\u655a\u655b\u655c\u655d\u655e\u655f\u6560\u6561\u6562\u6563\u6564\u6565\u6566\u6567\u6568\u6569\u656a\u656b\u656c\u656d\u656e\u656f\u6570\u6571\u6572\u6573\u6574\u6575\u6576\u6577\u6578\u6579\u657a\u657b\u657c\u657d\u657e\u657f\u6580\u6581\u6582\u6583\u6584\u6585\u6586\u6587\u6588\u6589\u658a\u658b\u658c\u658d\u658e\u658f\u6590\u6591\u6592\u6593\u6594\u6595\u6596\u6597\u6598\u6599\u659a\u659b\u659c\u659d\u659e\u659f\u65a0\u65a1\u65a2\u65a3\u65a4\u65a5\u65a6\u65a7\u65a8\u65a9\u65aa\u65ab\u65ac\u65ad\u65ae\u65af\u65b0\u65b1\u65b2\u65b3\u65b4\u65b5\u65b6\u65b7\u65b8\u65b9\u65ba\u65bb\u65bc\u65bd\u65be\u65bf\u65c0\u65c1\u65c2\u65c3\u65c4\u65c5\u65c6\u65c7\u65c8\u65c9\u65ca\u65cb\u65cc\u65cd\u65ce\u65cf\u65d0\u65d1\u65d2\u65d3\u65d4\u65d5\u65d6\u65d7\u65d8\u65d9\u65da\u65db\u65dc\u65dd\u65de\u65df\u65e0\u65e1\u65e2\u65e3\u65e4\u65e5\u65e6\u65e7\u65e8\u65e9\u65ea\u65eb\u65ec\u65ed\u65ee\u65ef\u65f0\u65f1\u65f2\u65f3\u65f4\u65f5\u65f6\u65f7\u65f8\u65f9\u65fa\u65fb\u65fc\u65fd\u65fe\u65ff\u6600\u6601\u6602\u6603\u6604\u6605\u6606\u6607\u6608\u6609\u660a\u660b\u660c\u660d\u660e\u660f\u6610\u6611\u6612\u6613\u6614\u6615\u6616\u6617\u6618\u6619\u661a\u661b\u661c\u661d\u661e\u661f\u6620\u6621\u6622\u6623\u6624\u6625\u6626\u6627\u6628\u6629\u662a\u662b\u662c\u662d\u662e\u662f\u6630\u6631\u6632\u6633\u6634\u6635\u6636\u6637\u6638\u6639\u663a\u663b\u663c\u663d\u663e\u663f\u6640\u6641\u6642\u6643\u6644\u6645\u6646\u6647\u6648\u6649\u664a\u664b\u664c\u664d\u664e\u664f\u6650\u6651\u6652\u6653\u6654\u6655\u6656\u6657\u6658\u6659\u665a\u665b\u665c\u665d\u665e\u665f\u6660\u6661\u6662\u6663\u6664\u6665\u6666\u6667\u6668\u6669\u666a\u666b\u666c\u666d\u666e\u666f\u6670\u6671\u6672\u6673\u6674\u6675\u6676\u6677\u6678\u6679\u667a\u667b\u667c\u667d\u667e\u667f\u6680\u6681\u6682\u6683\u6684\u6685\u6686\u6687\u6688\u6689\u668a\u668b\u668c\u668d\u668e\u668f\u6690\u6691\u6692\u6693\u6694\u6695\u6696\u6697\u6698\u6699\u669a\u669b\u669c\u669d\u669e\u669f\u66a0\u66a1\u66a2\u66a3\u66a4\u66a5\u66a6\u66a7\u66a8\u66a9\u66aa\u66ab\u66ac\u66ad\u66ae\u66af\u66b0\u66b1\u66b2\u66b3\u66b4\u66b5\u66b6\u66b7\u66b8\u66b9\u66ba\u66bb\u66bc\u66bd\u66be\u66bf\u66c0\u66c1\u66c2\u66c3\u66c4\u66c5\u66c6\u66c7\u66c8\u66c9\u66ca\u66cb\u66cc\u66cd\u66ce\u66cf\u66d0\u66d1\u66d2\u66d3\u66d4\u66d5\u66d6\u66d7\u66d8\u66d9\u66da\u66db\u66dc\u66dd\u66de\u66df\u66e0\u66e1\u66e2\u66e3\u66e4\u66e5\u66e6\u66e7\u66e8\u66e9\u66ea\u66eb\u66ec\u66ed\u66ee\u66ef\u66f0\u66f1\u66f2\u66f3\u66f4\u66f5\u66f6\u66f7\u66f8\u66f9\u66fa\u66fb\u66fc\u66fd\u66fe\u66ff\u6700\u6701\u6702\u6703\u6704\u6705\u6706\u6707\u6708\u6709\u670a\u670b\u670c\u670d\u670e\u670f\u6710\u6711\u6712\u6713\u6714\u6715\u6716\u6717\u6718\u6719\u671a\u671b\u671c\u671d\u671e\u671f\u6720\u6721\u6722\u6723\u6724\u6725\u6726\u6727\u6728\u6729\u672a\u672b\u672c\u672d\u672e\u672f\u6730\u6731\u6732\u6733\u6734\u6735\u6736\u6737\u6738\u6739\u673a\u673b\u673c\u673d\u673e\u673f\u6740\u6741\u6742\u6743\u6744\u6745\u6746\u6747\u6748\u6749\u674a\u674b\u674c\u674d\u674e\u674f\u6750\u6751\u6752\u6753\u6754\u6755\u6756\u6757\u6758\u6759\u675a\u675b\u675c\u675d\u675e\u675f\u6760\u6761\u6762\u6763\u6764\u6765\u6766\u6767\u6768\u6769\u676a\u676b\u676c\u676d\u676e\u676f\u6770\u6771\u6772\u6773\u6774\u6775\u6776\u6777\u6778\u6779\u677a\u677b\u677c\u677d\u677e\u677f\u6780\u6781\u6782\u6783\u6784\u6785\u6786\u6787\u6788\u6789\u678a\u678b\u678c\u678d\u678e\u678f\u6790\u6791\u6792\u6793\u6794\u6795\u6796\u6797\u6798\u6799\u679a\u679b\u679c\u679d\u679e\u679f\u67a0\u67a1\u67a2\u67a3\u67a4\u67a5\u67a6\u67a7\u67a8\u67a9\u67aa\u67ab\u67ac\u67ad\u67ae\u67af\u67b0\u67b1\u67b2\u67b3\u67b4\u67b5\u67b6\u67b7\u67b8\u67b9\u67ba\u67bb\u67bc\u67bd\u67be\u67bf\u67c0\u67c1\u67c2\u67c3\u67c4\u67c5\u67c6\u67c7\u67c8\u67c9\u67ca\u67cb\u67cc\u67cd\u67ce\u67cf\u67d0\u67d1\u67d2\u67d3\u67d4\u67d5\u67d6\u67d7\u67d8\u67d9\u67da\u67db\u67dc\u67dd\u67de\u67df\u67e0\u67e1\u67e2\u67e3\u67e4\u67e5\u67e6\u67e7\u67e8\u67e9\u67ea\u67eb\u67ec\u67ed\u67ee\u67ef\u67f0\u67f1\u67f2\u67f3\u67f4\u67f5\u67f6\u67f7\u67f8\u67f9\u67fa\u67fb\u67fc\u67fd\u67fe\u67ff\u6800\u6801\u6802\u6803\u6804\u6805\u6806\u6807\u6808\u6809\u680a\u680b\u680c\u680d\u680e\u680f\u6810\u6811\u6812\u6813\u6814\u6815\u6816\u6817\u6818\u6819\u681a\u681b\u681c\u681d\u681e\u681f\u6820\u6821\u6822\u6823\u6824\u6825\u6826\u6827\u6828\u6829\u682a\u682b\u682c\u682d\u682e\u682f\u6830\u6831\u6832\u6833\u6834\u6835\u6836\u6837\u6838\u6839\u683a\u683b\u683c\u683d\u683e\u683f\u6840\u6841\u6842\u6843\u6844\u6845\u6846\u6847\u6848\u6849\u684a\u684b\u684c\u684d\u684e\u684f\u6850\u6851\u6852\u6853\u6854\u6855\u6856\u6857\u6858\u6859\u685a\u685b\u685c\u685d\u685e\u685f\u6860\u6861\u6862\u6863\u6864\u6865\u6866\u6867\u6868\u6869\u686a\u686b\u686c\u686d\u686e\u686f\u6870\u6871\u6872\u6873\u6874\u6875\u6876\u6877\u6878\u6879\u687a\u687b\u687c\u687d\u687e\u687f\u6880\u6881\u6882\u6883\u6884\u6885\u6886\u6887\u6888\u6889\u688a\u688b\u688c\u688d\u688e\u688f\u6890\u6891\u6892\u6893\u6894\u6895\u6896\u6897\u6898\u6899\u689a\u689b\u689c\u689d\u689e\u689f\u68a0\u68a1\u68a2\u68a3\u68a4\u68a5\u68a6\u68a7\u68a8\u68a9\u68aa\u68ab\u68ac\u68ad\u68ae\u68af\u68b0\u68b1\u68b2\u68b3\u68b4\u68b5\u68b6\u68b7\u68b8\u68b9\u68ba\u68bb\u68bc\u68bd\u68be\u68bf\u68c0\u68c1\u68c2\u68c3\u68c4\u68c5\u68c6\u68c7\u68c8\u68c9\u68ca\u68cb\u68cc\u68cd\u68ce\u68cf\u68d0\u68d1\u68d2\u68d3\u68d4\u68d5\u68d6\u68d7\u68d8\u68d9\u68da\u68db\u68dc\u68dd\u68de\u68df\u68e0\u68e1\u68e2\u68e3\u68e4\u68e5\u68e6\u68e7\u68e8\u68e9\u68ea\u68eb\u68ec\u68ed\u68ee\u68ef\u68f0\u68f1\u68f2\u68f3\u68f4\u68f5\u68f6\u68f7\u68f8\u68f9\u68fa\u68fb\u68fc\u68fd\u68fe\u68ff\u6900\u6901\u6902\u6903\u6904\u6905\u6906\u6907\u6908\u6909\u690a\u690b\u690c\u690d\u690e\u690f\u6910\u6911\u6912\u6913\u6914\u6915\u6916\u6917\u6918\u6919\u691a\u691b\u691c\u691d\u691e\u691f\u6920\u6921\u6922\u6923\u6924\u6925\u6926\u6927\u6928\u6929\u692a\u692b\u692c\u692d\u692e\u692f\u6930\u6931\u6932\u6933\u6934\u6935\u6936\u6937\u6938\u6939\u693a\u693b\u693c\u693d\u693e\u693f\u6940\u6941\u6942\u6943\u6944\u6945\u6946\u6947\u6948\u6949\u694a\u694b\u694c\u694d\u694e\u694f\u6950\u6951\u6952\u6953\u6954\u6955\u6956\u6957\u6958\u6959\u695a\u695b\u695c\u695d\u695e\u695f\u6960\u6961\u6962\u6963\u6964\u6965\u6966\u6967\u6968\u6969\u696a\u696b\u696c\u696d\u696e\u696f\u6970\u6971\u6972\u6973\u6974\u6975\u6976\u6977\u6978\u6979\u697a\u697b\u697c\u697d\u697e\u697f\u6980\u6981\u6982\u6983\u6984\u6985\u6986\u6987\u6988\u6989\u698a\u698b\u698c\u698d\u698e\u698f\u6990\u6991\u6992\u6993\u6994\u6995\u6996\u6997\u6998\u6999\u699a\u699b\u699c\u699d\u699e\u699f\u69a0\u69a1\u69a2\u69a3\u69a4\u69a5\u69a6\u69a7\u69a8\u69a9\u69aa\u69ab\u69ac\u69ad\u69ae\u69af\u69b0\u69b1\u69b2\u69b3\u69b4\u69b5\u69b6\u69b7\u69b8\u69b9\u69ba\u69bb\u69bc\u69bd\u69be\u69bf\u69c0\u69c1\u69c2\u69c3\u69c4\u69c5\u69c6\u69c7\u69c8\u69c9\u69ca\u69cb\u69cc\u69cd\u69ce\u69cf\u69d0\u69d1\u69d2\u69d3\u69d4\u69d5\u69d6\u69d7\u69d8\u69d9\u69da\u69db\u69dc\u69dd\u69de\u69df\u69e0\u69e1\u69e2\u69e3\u69e4\u69e5\u69e6\u69e7\u69e8\u69e9\u69ea\u69eb\u69ec\u69ed\u69ee\u69ef\u69f0\u69f1\u69f2\u69f3\u69f4\u69f5\u69f6\u69f7\u69f8\u69f9\u69fa\u69fb\u69fc\u69fd\u69fe\u69ff\u6a00\u6a01\u6a02\u6a03\u6a04\u6a05\u6a06\u6a07\u6a08\u6a09\u6a0a\u6a0b\u6a0c\u6a0d\u6a0e\u6a0f\u6a10\u6a11\u6a12\u6a13\u6a14\u6a15\u6a16\u6a17\u6a18\u6a19\u6a1a\u6a1b\u6a1c\u6a1d\u6a1e\u6a1f\u6a20\u6a21\u6a22\u6a23\u6a24\u6a25\u6a26\u6a27\u6a28\u6a29\u6a2a\u6a2b\u6a2c\u6a2d\u6a2e\u6a2f\u6a30\u6a31\u6a32\u6a33\u6a34\u6a35\u6a36\u6a37\u6a38\u6a39\u6a3a\u6a3b\u6a3c\u6a3d\u6a3e\u6a3f\u6a40\u6a41\u6a42\u6a43\u6a44\u6a45\u6a46\u6a47\u6a48\u6a49\u6a4a\u6a4b\u6a4c\u6a4d\u6a4e\u6a4f\u6a50\u6a51\u6a52\u6a53\u6a54\u6a55\u6a56\u6a57\u6a58\u6a59\u6a5a\u6a5b\u6a5c\u6a5d\u6a5e\u6a5f\u6a60\u6a61\u6a62\u6a63\u6a64\u6a65\u6a66\u6a67\u6a68\u6a69\u6a6a\u6a6b\u6a6c\u6a6d\u6a6e\u6a6f\u6a70\u6a71\u6a72\u6a73\u6a74\u6a75\u6a76\u6a77\u6a78\u6a79\u6a7a\u6a7b\u6a7c\u6a7d\u6a7e\u6a7f\u6a80\u6a81\u6a82\u6a83\u6a84\u6a85\u6a86\u6a87\u6a88\u6a89\u6a8a\u6a8b\u6a8c\u6a8d\u6a8e\u6a8f\u6a90\u6a91\u6a92\u6a93\u6a94\u6a95\u6a96\u6a97\u6a98\u6a99\u6a9a\u6a9b\u6a9c\u6a9d\u6a9e\u6a9f\u6aa0\u6aa1\u6aa2\u6aa3\u6aa4\u6aa5\u6aa6\u6aa7\u6aa8\u6aa9\u6aaa\u6aab\u6aac\u6aad\u6aae\u6aaf\u6ab0\u6ab1\u6ab2\u6ab3\u6ab4\u6ab5\u6ab6\u6ab7\u6ab8\u6ab9\u6aba\u6abb\u6abc\u6abd\u6abe\u6abf\u6ac0\u6ac1\u6ac2\u6ac3\u6ac4\u6ac5\u6ac6\u6ac7\u6ac8\u6ac9\u6aca\u6acb\u6acc\u6acd\u6ace\u6acf\u6ad0\u6ad1\u6ad2\u6ad3\u6ad4\u6ad5\u6ad6\u6ad7\u6ad8\u6ad9\u6ada\u6adb\u6adc\u6add\u6ade\u6adf\u6ae0\u6ae1\u6ae2\u6ae3\u6ae4\u6ae5\u6ae6\u6ae7\u6ae8\u6ae9\u6aea\u6aeb\u6aec\u6aed\u6aee\u6aef\u6af0\u6af1\u6af2\u6af3\u6af4\u6af5\u6af6\u6af7\u6af8\u6af9\u6afa\u6afb\u6afc\u6afd\u6afe\u6aff\u6b00\u6b01\u6b02\u6b03\u6b04\u6b05\u6b06\u6b07\u6b08\u6b09\u6b0a\u6b0b\u6b0c\u6b0d\u6b0e\u6b0f\u6b10\u6b11\u6b12\u6b13\u6b14\u6b15\u6b16\u6b17\u6b18\u6b19\u6b1a\u6b1b\u6b1c\u6b1d\u6b1e\u6b1f\u6b20\u6b21\u6b22\u6b23\u6b24\u6b25\u6b26\u6b27\u6b28\u6b29\u6b2a\u6b2b\u6b2c\u6b2d\u6b2e\u6b2f\u6b30\u6b31\u6b32\u6b33\u6b34\u6b35\u6b36\u6b37\u6b38\u6b39\u6b3a\u6b3b\u6b3c\u6b3d\u6b3e\u6b3f\u6b40\u6b41\u6b42\u6b43\u6b44\u6b45\u6b46\u6b47\u6b48\u6b49\u6b4a\u6b4b\u6b4c\u6b4d\u6b4e\u6b4f\u6b50\u6b51\u6b52\u6b53\u6b54\u6b55\u6b56\u6b57\u6b58\u6b59\u6b5a\u6b5b\u6b5c\u6b5d\u6b5e\u6b5f\u6b60\u6b61\u6b62\u6b63\u6b64\u6b65\u6b66\u6b67\u6b68\u6b69\u6b6a\u6b6b\u6b6c\u6b6d\u6b6e\u6b6f\u6b70\u6b71\u6b72\u6b73\u6b74\u6b75\u6b76\u6b77\u6b78\u6b79\u6b7a\u6b7b\u6b7c\u6b7d\u6b7e\u6b7f\u6b80\u6b81\u6b82\u6b83\u6b84\u6b85\u6b86\u6b87\u6b88\u6b89\u6b8a\u6b8b\u6b8c\u6b8d\u6b8e\u6b8f\u6b90\u6b91\u6b92\u6b93\u6b94\u6b95\u6b96\u6b97\u6b98\u6b99\u6b9a\u6b9b\u6b9c\u6b9d\u6b9e\u6b9f\u6ba0\u6ba1\u6ba2\u6ba3\u6ba4\u6ba5\u6ba6\u6ba7\u6ba8\u6ba9\u6baa\u6bab\u6bac\u6bad\u6bae\u6baf\u6bb0\u6bb1\u6bb2\u6bb3\u6bb4\u6bb5\u6bb6\u6bb7\u6bb8\u6bb9\u6bba\u6bbb\u6bbc\u6bbd\u6bbe\u6bbf\u6bc0\u6bc1\u6bc2\u6bc3\u6bc4\u6bc5\u6bc6\u6bc7\u6bc8\u6bc9\u6bca\u6bcb\u6bcc\u6bcd\u6bce\u6bcf\u6bd0\u6bd1\u6bd2\u6bd3\u6bd4\u6bd5\u6bd6\u6bd7\u6bd8\u6bd9\u6bda\u6bdb\u6bdc\u6bdd\u6bde\u6bdf\u6be0\u6be1\u6be2\u6be3\u6be4\u6be5\u6be6\u6be7\u6be8\u6be9\u6bea\u6beb\u6bec\u6bed\u6bee\u6bef\u6bf0\u6bf1\u6bf2\u6bf3\u6bf4\u6bf5\u6bf6\u6bf7\u6bf8\u6bf9\u6bfa\u6bfb\u6bfc\u6bfd\u6bfe\u6bff\u6c00\u6c01\u6c02\u6c03\u6c04\u6c05\u6c06\u6c07\u6c08\u6c09\u6c0a\u6c0b\u6c0c\u6c0d\u6c0e\u6c0f\u6c10\u6c11\u6c12\u6c13\u6c14\u6c15\u6c16\u6c17\u6c18\u6c19\u6c1a\u6c1b\u6c1c\u6c1d\u6c1e\u6c1f\u6c20\u6c21\u6c22\u6c23\u6c24\u6c25\u6c26\u6c27\u6c28\u6c29\u6c2a\u6c2b\u6c2c\u6c2d\u6c2e\u6c2f\u6c30\u6c31\u6c32\u6c33\u6c34\u6c35\u6c36\u6c37\u6c38\u6c39\u6c3a\u6c3b\u6c3c\u6c3d\u6c3e\u6c3f\u6c40\u6c41\u6c42\u6c43\u6c44\u6c45\u6c46\u6c47\u6c48\u6c49\u6c4a\u6c4b\u6c4c\u6c4d\u6c4e\u6c4f\u6c50\u6c51\u6c52\u6c53\u6c54\u6c55\u6c56\u6c57\u6c58\u6c59\u6c5a\u6c5b\u6c5c\u6c5d\u6c5e\u6c5f\u6c60\u6c61\u6c62\u6c63\u6c64\u6c65\u6c66\u6c67\u6c68\u6c69\u6c6a\u6c6b\u6c6c\u6c6d\u6c6e\u6c6f\u6c70\u6c71\u6c72\u6c73\u6c74\u6c75\u6c76\u6c77\u6c78\u6c79\u6c7a\u6c7b\u6c7c\u6c7d\u6c7e\u6c7f\u6c80\u6c81\u6c82\u6c83\u6c84\u6c85\u6c86\u6c87\u6c88\u6c89\u6c8a\u6c8b\u6c8c\u6c8d\u6c8e\u6c8f\u6c90\u6c91\u6c92\u6c93\u6c94\u6c95\u6c96\u6c97\u6c98\u6c99\u6c9a\u6c9b\u6c9c\u6c9d\u6c9e\u6c9f\u6ca0\u6ca1\u6ca2\u6ca3\u6ca4\u6ca5\u6ca6\u6ca7\u6ca8\u6ca9\u6caa\u6cab\u6cac\u6cad\u6cae\u6caf\u6cb0\u6cb1\u6cb2\u6cb3\u6cb4\u6cb5\u6cb6\u6cb7\u6cb8\u6cb9\u6cba\u6cbb\u6cbc\u6cbd\u6cbe\u6cbf\u6cc0\u6cc1\u6cc2\u6cc3\u6cc4\u6cc5\u6cc6\u6cc7\u6cc8\u6cc9\u6cca\u6ccb\u6ccc\u6ccd\u6cce\u6ccf\u6cd0\u6cd1\u6cd2\u6cd3\u6cd4\u6cd5\u6cd6\u6cd7\u6cd8\u6cd9\u6cda\u6cdb\u6cdc\u6cdd\u6cde\u6cdf\u6ce0\u6ce1\u6ce2\u6ce3\u6ce4\u6ce5\u6ce6\u6ce7\u6ce8\u6ce9\u6cea\u6ceb\u6cec\u6ced\u6cee\u6cef\u6cf0\u6cf1\u6cf2\u6cf3\u6cf4\u6cf5\u6cf6\u6cf7\u6cf8\u6cf9\u6cfa\u6cfb\u6cfc\u6cfd\u6cfe\u6cff\u6d00\u6d01\u6d02\u6d03\u6d04\u6d05\u6d06\u6d07\u6d08\u6d09\u6d0a\u6d0b\u6d0c\u6d0d\u6d0e\u6d0f\u6d10\u6d11\u6d12\u6d13\u6d14\u6d15\u6d16\u6d17\u6d18\u6d19\u6d1a\u6d1b\u6d1c\u6d1d\u6d1e\u6d1f\u6d20\u6d21\u6d22\u6d23\u6d24\u6d25\u6d26\u6d27\u6d28\u6d29\u6d2a\u6d2b\u6d2c\u6d2d\u6d2e\u6d2f\u6d30\u6d31\u6d32\u6d33\u6d34\u6d35\u6d36\u6d37\u6d38\u6d39\u6d3a\u6d3b\u6d3c\u6d3d\u6d3e\u6d3f\u6d40\u6d41\u6d42\u6d43\u6d44\u6d45\u6d46\u6d47\u6d48\u6d49\u6d4a\u6d4b\u6d4c\u6d4d\u6d4e\u6d4f\u6d50\u6d51\u6d52\u6d53\u6d54\u6d55\u6d56\u6d57\u6d58\u6d59\u6d5a\u6d5b\u6d5c\u6d5d\u6d5e\u6d5f\u6d60\u6d61\u6d62\u6d63\u6d64\u6d65\u6d66\u6d67\u6d68\u6d69\u6d6a\u6d6b\u6d6c\u6d6d\u6d6e\u6d6f\u6d70\u6d71\u6d72\u6d73\u6d74\u6d75\u6d76\u6d77\u6d78\u6d79\u6d7a\u6d7b\u6d7c\u6d7d\u6d7e\u6d7f\u6d80\u6d81\u6d82\u6d83\u6d84\u6d85\u6d86\u6d87\u6d88\u6d89\u6d8a\u6d8b\u6d8c\u6d8d\u6d8e\u6d8f\u6d90\u6d91\u6d92\u6d93\u6d94\u6d95\u6d96\u6d97\u6d98\u6d99\u6d9a\u6d9b\u6d9c\u6d9d\u6d9e\u6d9f\u6da0\u6da1\u6da2\u6da3\u6da4\u6da5\u6da6\u6da7\u6da8\u6da9\u6daa\u6dab\u6dac\u6dad\u6dae\u6daf\u6db0\u6db1\u6db2\u6db3\u6db4\u6db5\u6db6\u6db7\u6db8\u6db9\u6dba\u6dbb\u6dbc\u6dbd\u6dbe\u6dbf\u6dc0\u6dc1\u6dc2\u6dc3\u6dc4\u6dc5\u6dc6\u6dc7\u6dc8\u6dc9\u6dca\u6dcb\u6dcc\u6dcd\u6dce\u6dcf\u6dd0\u6dd1\u6dd2\u6dd3\u6dd4\u6dd5\u6dd6\u6dd7\u6dd8\u6dd9\u6dda\u6ddb\u6ddc\u6ddd\u6dde\u6ddf\u6de0\u6de1\u6de2\u6de3\u6de4\u6de5\u6de6\u6de7\u6de8\u6de9\u6dea\u6deb\u6dec\u6ded\u6dee\u6def\u6df0\u6df1\u6df2\u6df3\u6df4\u6df5\u6df6\u6df7\u6df8\u6df9\u6dfa\u6dfb\u6dfc\u6dfd\u6dfe\u6dff\u6e00\u6e01\u6e02\u6e03\u6e04\u6e05\u6e06\u6e07\u6e08\u6e09\u6e0a\u6e0b\u6e0c\u6e0d\u6e0e\u6e0f\u6e10\u6e11\u6e12\u6e13\u6e14\u6e15\u6e16\u6e17\u6e18\u6e19\u6e1a\u6e1b\u6e1c\u6e1d\u6e1e\u6e1f\u6e20\u6e21\u6e22\u6e23\u6e24\u6e25\u6e26\u6e27\u6e28\u6e29\u6e2a\u6e2b\u6e2c\u6e2d\u6e2e\u6e2f\u6e30\u6e31\u6e32\u6e33\u6e34\u6e35\u6e36\u6e37\u6e38\u6e39\u6e3a\u6e3b\u6e3c\u6e3d\u6e3e\u6e3f\u6e40\u6e41\u6e42\u6e43\u6e44\u6e45\u6e46\u6e47\u6e48\u6e49\u6e4a\u6e4b\u6e4c\u6e4d\u6e4e\u6e4f\u6e50\u6e51\u6e52\u6e53\u6e54\u6e55\u6e56\u6e57\u6e58\u6e59\u6e5a\u6e5b\u6e5c\u6e5d\u6e5e\u6e5f\u6e60\u6e61\u6e62\u6e63\u6e64\u6e65\u6e66\u6e67\u6e68\u6e69\u6e6a\u6e6b\u6e6c\u6e6d\u6e6e\u6e6f\u6e70\u6e71\u6e72\u6e73\u6e74\u6e75\u6e76\u6e77\u6e78\u6e79\u6e7a\u6e7b\u6e7c\u6e7d\u6e7e\u6e7f\u6e80\u6e81\u6e82\u6e83\u6e84\u6e85\u6e86\u6e87\u6e88\u6e89\u6e8a\u6e8b\u6e8c\u6e8d\u6e8e\u6e8f\u6e90\u6e91\u6e92\u6e93\u6e94\u6e95\u6e96\u6e97\u6e98\u6e99\u6e9a\u6e9b\u6e9c\u6e9d\u6e9e\u6e9f\u6ea0\u6ea1\u6ea2\u6ea3\u6ea4\u6ea5\u6ea6\u6ea7\u6ea8\u6ea9\u6eaa\u6eab\u6eac\u6ead\u6eae\u6eaf\u6eb0\u6eb1\u6eb2\u6eb3\u6eb4\u6eb5\u6eb6\u6eb7\u6eb8\u6eb9\u6eba\u6ebb\u6ebc\u6ebd\u6ebe\u6ebf\u6ec0\u6ec1\u6ec2\u6ec3\u6ec4\u6ec5\u6ec6\u6ec7\u6ec8\u6ec9\u6eca\u6ecb\u6ecc\u6ecd\u6ece\u6ecf\u6ed0\u6ed1\u6ed2\u6ed3\u6ed4\u6ed5\u6ed6\u6ed7\u6ed8\u6ed9\u6eda\u6edb\u6edc\u6edd\u6ede\u6edf\u6ee0\u6ee1\u6ee2\u6ee3\u6ee4\u6ee5\u6ee6\u6ee7\u6ee8\u6ee9\u6eea\u6eeb\u6eec\u6eed\u6eee\u6eef\u6ef0\u6ef1\u6ef2\u6ef3\u6ef4\u6ef5\u6ef6\u6ef7\u6ef8\u6ef9\u6efa\u6efb\u6efc\u6efd\u6efe\u6eff\u6f00\u6f01\u6f02\u6f03\u6f04\u6f05\u6f06\u6f07\u6f08\u6f09\u6f0a\u6f0b\u6f0c\u6f0d\u6f0e\u6f0f\u6f10\u6f11\u6f12\u6f13\u6f14\u6f15\u6f16\u6f17\u6f18\u6f19\u6f1a\u6f1b\u6f1c\u6f1d\u6f1e\u6f1f\u6f20\u6f21\u6f22\u6f23\u6f24\u6f25\u6f26\u6f27\u6f28\u6f29\u6f2a\u6f2b\u6f2c\u6f2d\u6f2e\u6f2f\u6f30\u6f31\u6f32\u6f33\u6f34\u6f35\u6f36\u6f37\u6f38\u6f39\u6f3a\u6f3b\u6f3c\u6f3d\u6f3e\u6f3f\u6f40\u6f41\u6f42\u6f43\u6f44\u6f45\u6f46\u6f47\u6f48\u6f49\u6f4a\u6f4b\u6f4c\u6f4d\u6f4e\u6f4f\u6f50\u6f51\u6f52\u6f53\u6f54\u6f55\u6f56\u6f57\u6f58\u6f59\u6f5a\u6f5b\u6f5c\u6f5d\u6f5e\u6f5f\u6f60\u6f61\u6f62\u6f63\u6f64\u6f65\u6f66\u6f67\u6f68\u6f69\u6f6a\u6f6b\u6f6c\u6f6d\u6f6e\u6f6f\u6f70\u6f71\u6f72\u6f73\u6f74\u6f75\u6f76\u6f77\u6f78\u6f79\u6f7a\u6f7b\u6f7c\u6f7d\u6f7e\u6f7f\u6f80\u6f81\u6f82\u6f83\u6f84\u6f85\u6f86\u6f87\u6f88\u6f89\u6f8a\u6f8b\u6f8c\u6f8d\u6f8e\u6f8f\u6f90\u6f91\u6f92\u6f93\u6f94\u6f95\u6f96\u6f97\u6f98\u6f99\u6f9a\u6f9b\u6f9c\u6f9d\u6f9e\u6f9f\u6fa0\u6fa1\u6fa2\u6fa3\u6fa4\u6fa5\u6fa6\u6fa7\u6fa8\u6fa9\u6faa\u6fab\u6fac\u6fad\u6fae\u6faf\u6fb0\u6fb1\u6fb2\u6fb3\u6fb4\u6fb5\u6fb6\u6fb7\u6fb8\u6fb9\u6fba\u6fbb\u6fbc\u6fbd\u6fbe\u6fbf\u6fc0\u6fc1\u6fc2\u6fc3\u6fc4\u6fc5\u6fc6\u6fc7\u6fc8\u6fc9\u6fca\u6fcb\u6fcc\u6fcd\u6fce\u6fcf\u6fd0\u6fd1\u6fd2\u6fd3\u6fd4\u6fd5\u6fd6\u6fd7\u6fd8\u6fd9\u6fda\u6fdb\u6fdc\u6fdd\u6fde\u6fdf\u6fe0\u6fe1\u6fe2\u6fe3\u6fe4\u6fe5\u6fe6\u6fe7\u6fe8\u6fe9\u6fea\u6feb\u6fec\u6fed\u6fee\u6fef\u6ff0\u6ff1\u6ff2\u6ff3\u6ff4\u6ff5\u6ff6\u6ff7\u6ff8\u6ff9\u6ffa\u6ffb\u6ffc\u6ffd\u6ffe\u6fff\u7000\u7001\u7002\u7003\u7004\u7005\u7006\u7007\u7008\u7009\u700a\u700b\u700c\u700d\u700e\u700f\u7010\u7011\u7012\u7013\u7014\u7015\u7016\u7017\u7018\u7019\u701a\u701b\u701c\u701d\u701e\u701f\u7020\u7021\u7022\u7023\u7024\u7025\u7026\u7027\u7028\u7029\u702a\u702b\u702c\u702d\u702e\u702f\u7030\u7031\u7032\u7033\u7034\u7035\u7036\u7037\u7038\u7039\u703a\u703b\u703c\u703d\u703e\u703f\u7040\u7041\u7042\u7043\u7044\u7045\u7046\u7047\u7048\u7049\u704a\u704b\u704c\u704d\u704e\u704f\u7050\u7051\u7052\u7053\u7054\u7055\u7056\u7057\u7058\u7059\u705a\u705b\u705c\u705d\u705e\u705f\u7060\u7061\u7062\u7063\u7064\u7065\u7066\u7067\u7068\u7069\u706a\u706b\u706c\u706d\u706e\u706f\u7070\u7071\u7072\u7073\u7074\u7075\u7076\u7077\u7078\u7079\u707a\u707b\u707c\u707d\u707e\u707f\u7080\u7081\u7082\u7083\u7084\u7085\u7086\u7087\u7088\u7089\u708a\u708b\u708c\u708d\u708e\u708f\u7090\u7091\u7092\u7093\u7094\u7095\u7096\u7097\u7098\u7099\u709a\u709b\u709c\u709d\u709e\u709f\u70a0\u70a1\u70a2\u70a3\u70a4\u70a5\u70a6\u70a7\u70a8\u70a9\u70aa\u70ab\u70ac\u70ad\u70ae\u70af\u70b0\u70b1\u70b2\u70b3\u70b4\u70b5\u70b6\u70b7\u70b8\u70b9\u70ba\u70bb\u70bc\u70bd\u70be\u70bf\u70c0\u70c1\u70c2\u70c3\u70c4\u70c5\u70c6\u70c7\u70c8\u70c9\u70ca\u70cb\u70cc\u70cd\u70ce\u70cf\u70d0\u70d1\u70d2\u70d3\u70d4\u70d5\u70d6\u70d7\u70d8\u70d9\u70da\u70db\u70dc\u70dd\u70de\u70df\u70e0\u70e1\u70e2\u70e3\u70e4\u70e5\u70e6\u70e7\u70e8\u70e9\u70ea\u70eb\u70ec\u70ed\u70ee\u70ef\u70f0\u70f1\u70f2\u70f3\u70f4\u70f5\u70f6\u70f7\u70f8\u70f9\u70fa\u70fb\u70fc\u70fd\u70fe\u70ff\u7100\u7101\u7102\u7103\u7104\u7105\u7106\u7107\u7108\u7109\u710a\u710b\u710c\u710d\u710e\u710f\u7110\u7111\u7112\u7113\u7114\u7115\u7116\u7117\u7118\u7119\u711a\u711b\u711c\u711d\u711e\u711f\u7120\u7121\u7122\u7123\u7124\u7125\u7126\u7127\u7128\u7129\u712a\u712b\u712c\u712d\u712e\u712f\u7130\u7131\u7132\u7133\u7134\u7135\u7136\u7137\u7138\u7139\u713a\u713b\u713c\u713d\u713e\u713f\u7140\u7141\u7142\u7143\u7144\u7145\u7146\u7147\u7148\u7149\u714a\u714b\u714c\u714d\u714e\u714f\u7150\u7151\u7152\u7153\u7154\u7155\u7156\u7157\u7158\u7159\u715a\u715b\u715c\u715d\u715e\u715f\u7160\u7161\u7162\u7163\u7164\u7165\u7166\u7167\u7168\u7169\u716a\u716b\u716c\u716d\u716e\u716f\u7170\u7171\u7172\u7173\u7174\u7175\u7176\u7177\u7178\u7179\u717a\u717b\u717c\u717d\u717e\u717f\u7180\u7181\u7182\u7183\u7184\u7185\u7186\u7187\u7188\u7189\u718a\u718b\u718c\u718d\u718e\u718f\u7190\u7191\u7192\u7193\u7194\u7195\u7196\u7197\u7198\u7199\u719a\u719b\u719c\u719d\u719e\u719f\u71a0\u71a1\u71a2\u71a3\u71a4\u71a5\u71a6\u71a7\u71a8\u71a9\u71aa\u71ab\u71ac\u71ad\u71ae\u71af\u71b0\u71b1\u71b2\u71b3\u71b4\u71b5\u71b6\u71b7\u71b8\u71b9\u71ba\u71bb\u71bc\u71bd\u71be\u71bf\u71c0\u71c1\u71c2\u71c3\u71c4\u71c5\u71c6\u71c7\u71c8\u71c9\u71ca\u71cb\u71cc\u71cd\u71ce\u71cf\u71d0\u71d1\u71d2\u71d3\u71d4\u71d5\u71d6\u71d7\u71d8\u71d9\u71da\u71db\u71dc\u71dd\u71de\u71df\u71e0\u71e1\u71e2\u71e3\u71e4\u71e5\u71e6\u71e7\u71e8\u71e9\u71ea\u71eb\u71ec\u71ed\u71ee\u71ef\u71f0\u71f1\u71f2\u71f3\u71f4\u71f5\u71f6\u71f7\u71f8\u71f9\u71fa\u71fb\u71fc\u71fd\u71fe\u71ff\u7200\u7201\u7202\u7203\u7204\u7205\u7206\u7207\u7208\u7209\u720a\u720b\u720c\u720d\u720e\u720f\u7210\u7211\u7212\u7213\u7214\u7215\u7216\u7217\u7218\u7219\u721a\u721b\u721c\u721d\u721e\u721f\u7220\u7221\u7222\u7223\u7224\u7225\u7226\u7227\u7228\u7229\u722a\u722b\u722c\u722d\u722e\u722f\u7230\u7231\u7232\u7233\u7234\u7235\u7236\u7237\u7238\u7239\u723a\u723b\u723c\u723d\u723e\u723f\u7240\u7241\u7242\u7243\u7244\u7245\u7246\u7247\u7248\u7249\u724a\u724b\u724c\u724d\u724e\u724f\u7250\u7251\u7252\u7253\u7254\u7255\u7256\u7257\u7258\u7259\u725a\u725b\u725c\u725d\u725e\u725f\u7260\u7261\u7262\u7263\u7264\u7265\u7266\u7267\u7268\u7269\u726a\u726b\u726c\u726d\u726e\u726f\u7270\u7271\u7272\u7273\u7274\u7275\u7276\u7277\u7278\u7279\u727a\u727b\u727c\u727d\u727e\u727f\u7280\u7281\u7282\u7283\u7284\u7285\u7286\u7287\u7288\u7289\u728a\u728b\u728c\u728d\u728e\u728f\u7290\u7291\u7292\u7293\u7294\u7295\u7296\u7297\u7298\u7299\u729a\u729b\u729c\u729d\u729e\u729f\u72a0\u72a1\u72a2\u72a3\u72a4\u72a5\u72a6\u72a7\u72a8\u72a9\u72aa\u72ab\u72ac\u72ad\u72ae\u72af\u72b0\u72b1\u72b2\u72b3\u72b4\u72b5\u72b6\u72b7\u72b8\u72b9\u72ba\u72bb\u72bc\u72bd\u72be\u72bf\u72c0\u72c1\u72c2\u72c3\u72c4\u72c5\u72c6\u72c7\u72c8\u72c9\u72ca\u72cb\u72cc\u72cd\u72ce\u72cf\u72d0\u72d1\u72d2\u72d3\u72d4\u72d5\u72d6\u72d7\u72d8\u72d9\u72da\u72db\u72dc\u72dd\u72de\u72df\u72e0\u72e1\u72e2\u72e3\u72e4\u72e5\u72e6\u72e7\u72e8\u72e9\u72ea\u72eb\u72ec\u72ed\u72ee\u72ef\u72f0\u72f1\u72f2\u72f3\u72f4\u72f5\u72f6\u72f7\u72f8\u72f9\u72fa\u72fb\u72fc\u72fd\u72fe\u72ff\u7300\u7301\u7302\u7303\u7304\u7305\u7306\u7307\u7308\u7309\u730a\u730b\u730c\u730d\u730e\u730f\u7310\u7311\u7312\u7313\u7314\u7315\u7316\u7317\u7318\u7319\u731a\u731b\u731c\u731d\u731e\u731f\u7320\u7321\u7322\u7323\u7324\u7325\u7326\u7327\u7328\u7329\u732a\u732b\u732c\u732d\u732e\u732f\u7330\u7331\u7332\u7333\u7334\u7335\u7336\u7337\u7338\u7339\u733a\u733b\u733c\u733d\u733e\u733f\u7340\u7341\u7342\u7343\u7344\u7345\u7346\u7347\u7348\u7349\u734a\u734b\u734c\u734d\u734e\u734f\u7350\u7351\u7352\u7353\u7354\u7355\u7356\u7357\u7358\u7359\u735a\u735b\u735c\u735d\u735e\u735f\u7360\u7361\u7362\u7363\u7364\u7365\u7366\u7367\u7368\u7369\u736a\u736b\u736c\u736d\u736e\u736f\u7370\u7371\u7372\u7373\u7374\u7375\u7376\u7377\u7378\u7379\u737a\u737b\u737c\u737d\u737e\u737f\u7380\u7381\u7382\u7383\u7384\u7385\u7386\u7387\u7388\u7389\u738a\u738b\u738c\u738d\u738e\u738f\u7390\u7391\u7392\u7393\u7394\u7395\u7396\u7397\u7398\u7399\u739a\u739b\u739c\u739d\u739e\u739f\u73a0\u73a1\u73a2\u73a3\u73a4\u73a5\u73a6\u73a7\u73a8\u73a9\u73aa\u73ab\u73ac\u73ad\u73ae\u73af\u73b0\u73b1\u73b2\u73b3\u73b4\u73b5\u73b6\u73b7\u73b8\u73b9\u73ba\u73bb\u73bc\u73bd\u73be\u73bf\u73c0\u73c1\u73c2\u73c3\u73c4\u73c5\u73c6\u73c7\u73c8\u73c9\u73ca\u73cb\u73cc\u73cd\u73ce\u73cf\u73d0\u73d1\u73d2\u73d3\u73d4\u73d5\u73d6\u73d7\u73d8\u73d9\u73da\u73db\u73dc\u73dd\u73de\u73df\u73e0\u73e1\u73e2\u73e3\u73e4\u73e5\u73e6\u73e7\u73e8\u73e9\u73ea\u73eb\u73ec\u73ed\u73ee\u73ef\u73f0\u73f1\u73f2\u73f3\u73f4\u73f5\u73f6\u73f7\u73f8\u73f9\u73fa\u73fb\u73fc\u73fd\u73fe\u73ff\u7400\u7401\u7402\u7403\u7404\u7405\u7406\u7407\u7408\u7409\u740a\u740b\u740c\u740d\u740e\u740f\u7410\u7411\u7412\u7413\u7414\u7415\u7416\u7417\u7418\u7419\u741a\u741b\u741c\u741d\u741e\u741f\u7420\u7421\u7422\u7423\u7424\u7425\u7426\u7427\u7428\u7429\u742a\u742b\u742c\u742d\u742e\u742f\u7430\u7431\u7432\u7433\u7434\u7435\u7436\u7437\u7438\u7439\u743a\u743b\u743c\u743d\u743e\u743f\u7440\u7441\u7442\u7443\u7444\u7445\u7446\u7447\u7448\u7449\u744a\u744b\u744c\u744d\u744e\u744f\u7450\u7451\u7452\u7453\u7454\u7455\u7456\u7457\u7458\u7459\u745a\u745b\u745c\u745d\u745e\u745f\u7460\u7461\u7462\u7463\u7464\u7465\u7466\u7467\u7468\u7469\u746a\u746b\u746c\u746d\u746e\u746f\u7470\u7471\u7472\u7473\u7474\u7475\u7476\u7477\u7478\u7479\u747a\u747b\u747c\u747d\u747e\u747f\u7480\u7481\u7482\u7483\u7484\u7485\u7486\u7487\u7488\u7489\u748a\u748b\u748c\u748d\u748e\u748f\u7490\u7491\u7492\u7493\u7494\u7495\u7496\u7497\u7498\u7499\u749a\u749b\u749c\u749d\u749e\u749f\u74a0\u74a1\u74a2\u74a3\u74a4\u74a5\u74a6\u74a7\u74a8\u74a9\u74aa\u74ab\u74ac\u74ad\u74ae\u74af\u74b0\u74b1\u74b2\u74b3\u74b4\u74b5\u74b6\u74b7\u74b8\u74b9\u74ba\u74bb\u74bc\u74bd\u74be\u74bf\u74c0\u74c1\u74c2\u74c3\u74c4\u74c5\u74c6\u74c7\u74c8\u74c9\u74ca\u74cb\u74cc\u74cd\u74ce\u74cf\u74d0\u74d1\u74d2\u74d3\u74d4\u74d5\u74d6\u74d7\u74d8\u74d9\u74da\u74db\u74dc\u74dd\u74de\u74df\u74e0\u74e1\u74e2\u74e3\u74e4\u74e5\u74e6\u74e7\u74e8\u74e9\u74ea\u74eb\u74ec\u74ed\u74ee\u74ef\u74f0\u74f1\u74f2\u74f3\u74f4\u74f5\u74f6\u74f7\u74f8\u74f9\u74fa\u74fb\u74fc\u74fd\u74fe\u74ff\u7500\u7501\u7502\u7503\u7504\u7505\u7506\u7507\u7508\u7509\u750a\u750b\u750c\u750d\u750e\u750f\u7510\u7511\u7512\u7513\u7514\u7515\u7516\u7517\u7518\u7519\u751a\u751b\u751c\u751d\u751e\u751f\u7520\u7521\u7522\u7523\u7524\u7525\u7526\u7527\u7528\u7529\u752a\u752b\u752c\u752d\u752e\u752f\u7530\u7531\u7532\u7533\u7534\u7535\u7536\u7537\u7538\u7539\u753a\u753b\u753c\u753d\u753e\u753f\u7540\u7541\u7542\u7543\u7544\u7545\u7546\u7547\u7548\u7549\u754a\u754b\u754c\u754d\u754e\u754f\u7550\u7551\u7552\u7553\u7554\u7555\u7556\u7557\u7558\u7559\u755a\u755b\u755c\u755d\u755e\u755f\u7560\u7561\u7562\u7563\u7564\u7565\u7566\u7567\u7568\u7569\u756a\u756b\u756c\u756d\u756e\u756f\u7570\u7571\u7572\u7573\u7574\u7575\u7576\u7577\u7578\u7579\u757a\u757b\u757c\u757d\u757e\u757f\u7580\u7581\u7582\u7583\u7584\u7585\u7586\u7587\u7588\u7589\u758a\u758b\u758c\u758d\u758e\u758f\u7590\u7591\u7592\u7593\u7594\u7595\u7596\u7597\u7598\u7599\u759a\u759b\u759c\u759d\u759e\u759f\u75a0\u75a1\u75a2\u75a3\u75a4\u75a5\u75a6\u75a7\u75a8\u75a9\u75aa\u75ab\u75ac\u75ad\u75ae\u75af\u75b0\u75b1\u75b2\u75b3\u75b4\u75b5\u75b6\u75b7\u75b8\u75b9\u75ba\u75bb\u75bc\u75bd\u75be\u75bf\u75c0\u75c1\u75c2\u75c3\u75c4\u75c5\u75c6\u75c7\u75c8\u75c9\u75ca\u75cb\u75cc\u75cd\u75ce\u75cf\u75d0\u75d1\u75d2\u75d3\u75d4\u75d5\u75d6\u75d7\u75d8\u75d9\u75da\u75db\u75dc\u75dd\u75de\u75df\u75e0\u75e1\u75e2\u75e3\u75e4\u75e5\u75e6\u75e7\u75e8\u75e9\u75ea\u75eb\u75ec\u75ed\u75ee\u75ef\u75f0\u75f1\u75f2\u75f3\u75f4\u75f5\u75f6\u75f7\u75f8\u75f9\u75fa\u75fb\u75fc\u75fd\u75fe\u75ff\u7600\u7601\u7602\u7603\u7604\u7605\u7606\u7607\u7608\u7609\u760a\u760b\u760c\u760d\u760e\u760f\u7610\u7611\u7612\u7613\u7614\u7615\u7616\u7617\u7618\u7619\u761a\u761b\u761c\u761d\u761e\u761f\u7620\u7621\u7622\u7623\u7624\u7625\u7626\u7627\u7628\u7629\u762a\u762b\u762c\u762d\u762e\u762f\u7630\u7631\u7632\u7633\u7634\u7635\u7636\u7637\u7638\u7639\u763a\u763b\u763c\u763d\u763e\u763f\u7640\u7641\u7642\u7643\u7644\u7645\u7646\u7647\u7648\u7649\u764a\u764b\u764c\u764d\u764e\u764f\u7650\u7651\u7652\u7653\u7654\u7655\u7656\u7657\u7658\u7659\u765a\u765b\u765c\u765d\u765e\u765f\u7660\u7661\u7662\u7663\u7664\u7665\u7666\u7667\u7668\u7669\u766a\u766b\u766c\u766d\u766e\u766f\u7670\u7671\u7672\u7673\u7674\u7675\u7676\u7677\u7678\u7679\u767a\u767b\u767c\u767d\u767e\u767f\u7680\u7681\u7682\u7683\u7684\u7685\u7686\u7687\u7688\u7689\u768a\u768b\u768c\u768d\u768e\u768f\u7690\u7691\u7692\u7693\u7694\u7695\u7696\u7697\u7698\u7699\u769a\u769b\u769c\u769d\u769e\u769f\u76a0\u76a1\u76a2\u76a3\u76a4\u76a5\u76a6\u76a7\u76a8\u76a9\u76aa\u76ab\u76ac\u76ad\u76ae\u76af\u76b0\u76b1\u76b2\u76b3\u76b4\u76b5\u76b6\u76b7\u76b8\u76b9\u76ba\u76bb\u76bc\u76bd\u76be\u76bf\u76c0\u76c1\u76c2\u76c3\u76c4\u76c5\u76c6\u76c7\u76c8\u76c9\u76ca\u76cb\u76cc\u76cd\u76ce\u76cf\u76d0\u76d1\u76d2\u76d3\u76d4\u76d5\u76d6\u76d7\u76d8\u76d9\u76da\u76db\u76dc\u76dd\u76de\u76df\u76e0\u76e1\u76e2\u76e3\u76e4\u76e5\u76e6\u76e7\u76e8\u76e9\u76ea\u76eb\u76ec\u76ed\u76ee\u76ef\u76f0\u76f1\u76f2\u76f3\u76f4\u76f5\u76f6\u76f7\u76f8\u76f9\u76fa\u76fb\u76fc\u76fd\u76fe\u76ff\u7700\u7701\u7702\u7703\u7704\u7705\u7706\u7707\u7708\u7709\u770a\u770b\u770c\u770d\u770e\u770f\u7710\u7711\u7712\u7713\u7714\u7715\u7716\u7717\u7718\u7719\u771a\u771b\u771c\u771d\u771e\u771f\u7720\u7721\u7722\u7723\u7724\u7725\u7726\u7727\u7728\u7729\u772a\u772b\u772c\u772d\u772e\u772f\u7730\u7731\u7732\u7733\u7734\u7735\u7736\u7737\u7738\u7739\u773a\u773b\u773c\u773d\u773e\u773f\u7740\u7741\u7742\u7743\u7744\u7745\u7746\u7747\u7748\u7749\u774a\u774b\u774c\u774d\u774e\u774f\u7750\u7751\u7752\u7753\u7754\u7755\u7756\u7757\u7758\u7759\u775a\u775b\u775c\u775d\u775e\u775f\u7760\u7761\u7762\u7763\u7764\u7765\u7766\u7767\u7768\u7769\u776a\u776b\u776c\u776d\u776e\u776f\u7770\u7771\u7772\u7773\u7774\u7775\u7776\u7777\u7778\u7779\u777a\u777b\u777c\u777d\u777e\u777f\u7780\u7781\u7782\u7783\u7784\u7785\u7786\u7787\u7788\u7789\u778a\u778b\u778c\u778d\u778e\u778f\u7790\u7791\u7792\u7793\u7794\u7795\u7796\u7797\u7798\u7799\u779a\u779b\u779c\u779d\u779e\u779f\u77a0\u77a1\u77a2\u77a3\u77a4\u77a5\u77a6\u77a7\u77a8\u77a9\u77aa\u77ab\u77ac\u77ad\u77ae\u77af\u77b0\u77b1\u77b2\u77b3\u77b4\u77b5\u77b6\u77b7\u77b8\u77b9\u77ba\u77bb\u77bc\u77bd\u77be\u77bf\u77c0\u77c1\u77c2\u77c3\u77c4\u77c5\u77c6\u77c7\u77c8\u77c9\u77ca\u77cb\u77cc\u77cd\u77ce\u77cf\u77d0\u77d1\u77d2\u77d3\u77d4\u77d5\u77d6\u77d7\u77d8\u77d9\u77da\u77db\u77dc\u77dd\u77de\u77df\u77e0\u77e1\u77e2\u77e3\u77e4\u77e5\u77e6\u77e7\u77e8\u77e9\u77ea\u77eb\u77ec\u77ed\u77ee\u77ef\u77f0\u77f1\u77f2\u77f3\u77f4\u77f5\u77f6\u77f7\u77f8\u77f9\u77fa\u77fb\u77fc\u77fd\u77fe\u77ff\u7800\u7801\u7802\u7803\u7804\u7805\u7806\u7807\u7808\u7809\u780a\u780b\u780c\u780d\u780e\u780f\u7810\u7811\u7812\u7813\u7814\u7815\u7816\u7817\u7818\u7819\u781a\u781b\u781c\u781d\u781e\u781f\u7820\u7821\u7822\u7823\u7824\u7825\u7826\u7827\u7828\u7829\u782a\u782b\u782c\u782d\u782e\u782f\u7830\u7831\u7832\u7833\u7834\u7835\u7836\u7837\u7838\u7839\u783a\u783b\u783c\u783d\u783e\u783f\u7840\u7841\u7842\u7843\u7844\u7845\u7846\u7847\u7848\u7849\u784a\u784b\u784c\u784d\u784e\u784f\u7850\u7851\u7852\u7853\u7854\u7855\u7856\u7857\u7858\u7859\u785a\u785b\u785c\u785d\u785e\u785f\u7860\u7861\u7862\u7863\u7864\u7865\u7866\u7867\u7868\u7869\u786a\u786b\u786c\u786d\u786e\u786f\u7870\u7871\u7872\u7873\u7874\u7875\u7876\u7877\u7878\u7879\u787a\u787b\u787c\u787d\u787e\u787f\u7880\u7881\u7882\u7883\u7884\u7885\u7886\u7887\u7888\u7889\u788a\u788b\u788c\u788d\u788e\u788f\u7890\u7891\u7892\u7893\u7894\u7895\u7896\u7897\u7898\u7899\u789a\u789b\u789c\u789d\u789e\u789f\u78a0\u78a1\u78a2\u78a3\u78a4\u78a5\u78a6\u78a7\u78a8\u78a9\u78aa\u78ab\u78ac\u78ad\u78ae\u78af\u78b0\u78b1\u78b2\u78b3\u78b4\u78b5\u78b6\u78b7\u78b8\u78b9\u78ba\u78bb\u78bc\u78bd\u78be\u78bf\u78c0\u78c1\u78c2\u78c3\u78c4\u78c5\u78c6\u78c7\u78c8\u78c9\u78ca\u78cb\u78cc\u78cd\u78ce\u78cf\u78d0\u78d1\u78d2\u78d3\u78d4\u78d5\u78d6\u78d7\u78d8\u78d9\u78da\u78db\u78dc\u78dd\u78de\u78df\u78e0\u78e1\u78e2\u78e3\u78e4\u78e5\u78e6\u78e7\u78e8\u78e9\u78ea\u78eb\u78ec\u78ed\u78ee\u78ef\u78f0\u78f1\u78f2\u78f3\u78f4\u78f5\u78f6\u78f7\u78f8\u78f9\u78fa\u78fb\u78fc\u78fd\u78fe\u78ff\u7900\u7901\u7902\u7903\u7904\u7905\u7906\u7907\u7908\u7909\u790a\u790b\u790c\u790d\u790e\u790f\u7910\u7911\u7912\u7913\u7914\u7915\u7916\u7917\u7918\u7919\u791a\u791b\u791c\u791d\u791e\u791f\u7920\u7921\u7922\u7923\u7924\u7925\u7926\u7927\u7928\u7929\u792a\u792b\u792c\u792d\u792e\u792f\u7930\u7931\u7932\u7933\u7934\u7935\u7936\u7937\u7938\u7939\u793a\u793b\u793c\u793d\u793e\u793f\u7940\u7941\u7942\u7943\u7944\u7945\u7946\u7947\u7948\u7949\u794a\u794b\u794c\u794d\u794e\u794f\u7950\u7951\u7952\u7953\u7954\u7955\u7956\u7957\u7958\u7959\u795a\u795b\u795c\u795d\u795e\u795f\u7960\u7961\u7962\u7963\u7964\u7965\u7966\u7967\u7968\u7969\u796a\u796b\u796c\u796d\u796e\u796f\u7970\u7971\u7972\u7973\u7974\u7975\u7976\u7977\u7978\u7979\u797a\u797b\u797c\u797d\u797e\u797f\u7980\u7981\u7982\u7983\u7984\u7985\u7986\u7987\u7988\u7989\u798a\u798b\u798c\u798d\u798e\u798f\u7990\u7991\u7992\u7993\u7994\u7995\u7996\u7997\u7998\u7999\u799a\u799b\u799c\u799d\u799e\u799f\u79a0\u79a1\u79a2\u79a3\u79a4\u79a5\u79a6\u79a7\u79a8\u79a9\u79aa\u79ab\u79ac\u79ad\u79ae\u79af\u79b0\u79b1\u79b2\u79b3\u79b4\u79b5\u79b6\u79b7\u79b8\u79b9\u79ba\u79bb\u79bc\u79bd\u79be\u79bf\u79c0\u79c1\u79c2\u79c3\u79c4\u79c5\u79c6\u79c7\u79c8\u79c9\u79ca\u79cb\u79cc\u79cd\u79ce\u79cf\u79d0\u79d1\u79d2\u79d3\u79d4\u79d5\u79d6\u79d7\u79d8\u79d9\u79da\u79db\u79dc\u79dd\u79de\u79df\u79e0\u79e1\u79e2\u79e3\u79e4\u79e5\u79e6\u79e7\u79e8\u79e9\u79ea\u79eb\u79ec\u79ed\u79ee\u79ef\u79f0\u79f1\u79f2\u79f3\u79f4\u79f5\u79f6\u79f7\u79f8\u79f9\u79fa\u79fb\u79fc\u79fd\u79fe\u79ff\u7a00\u7a01\u7a02\u7a03\u7a04\u7a05\u7a06\u7a07\u7a08\u7a09\u7a0a\u7a0b\u7a0c\u7a0d\u7a0e\u7a0f\u7a10\u7a11\u7a12\u7a13\u7a14\u7a15\u7a16\u7a17\u7a18\u7a19\u7a1a\u7a1b\u7a1c\u7a1d\u7a1e\u7a1f\u7a20\u7a21\u7a22\u7a23\u7a24\u7a25\u7a26\u7a27\u7a28\u7a29\u7a2a\u7a2b\u7a2c\u7a2d\u7a2e\u7a2f\u7a30\u7a31\u7a32\u7a33\u7a34\u7a35\u7a36\u7a37\u7a38\u7a39\u7a3a\u7a3b\u7a3c\u7a3d\u7a3e\u7a3f\u7a40\u7a41\u7a42\u7a43\u7a44\u7a45\u7a46\u7a47\u7a48\u7a49\u7a4a\u7a4b\u7a4c\u7a4d\u7a4e\u7a4f\u7a50\u7a51\u7a52\u7a53\u7a54\u7a55\u7a56\u7a57\u7a58\u7a59\u7a5a\u7a5b\u7a5c\u7a5d\u7a5e\u7a5f\u7a60\u7a61\u7a62\u7a63\u7a64\u7a65\u7a66\u7a67\u7a68\u7a69\u7a6a\u7a6b\u7a6c\u7a6d\u7a6e\u7a6f\u7a70\u7a71\u7a72\u7a73\u7a74\u7a75\u7a76\u7a77\u7a78\u7a79\u7a7a\u7a7b\u7a7c\u7a7d\u7a7e\u7a7f\u7a80\u7a81\u7a82\u7a83\u7a84\u7a85\u7a86\u7a87\u7a88\u7a89\u7a8a\u7a8b\u7a8c\u7a8d\u7a8e\u7a8f\u7a90\u7a91\u7a92\u7a93\u7a94\u7a95\u7a96\u7a97\u7a98\u7a99\u7a9a\u7a9b\u7a9c\u7a9d\u7a9e\u7a9f\u7aa0\u7aa1\u7aa2\u7aa3\u7aa4\u7aa5\u7aa6\u7aa7\u7aa8\u7aa9\u7aaa\u7aab\u7aac\u7aad\u7aae\u7aaf\u7ab0\u7ab1\u7ab2\u7ab3\u7ab4\u7ab5\u7ab6\u7ab7\u7ab8\u7ab9\u7aba\u7abb\u7abc\u7abd\u7abe\u7abf\u7ac0\u7ac1\u7ac2\u7ac3\u7ac4\u7ac5\u7ac6\u7ac7\u7ac8\u7ac9\u7aca\u7acb\u7acc\u7acd\u7ace\u7acf\u7ad0\u7ad1\u7ad2\u7ad3\u7ad4\u7ad5\u7ad6\u7ad7\u7ad8\u7ad9\u7ada\u7adb\u7adc\u7add\u7ade\u7adf\u7ae0\u7ae1\u7ae2\u7ae3\u7ae4\u7ae5\u7ae6\u7ae7\u7ae8\u7ae9\u7aea\u7aeb\u7aec\u7aed\u7aee\u7aef\u7af0\u7af1\u7af2\u7af3\u7af4\u7af5\u7af6\u7af7\u7af8\u7af9\u7afa\u7afb\u7afc\u7afd\u7afe\u7aff\u7b00\u7b01\u7b02\u7b03\u7b04\u7b05\u7b06\u7b07\u7b08\u7b09\u7b0a\u7b0b\u7b0c\u7b0d\u7b0e\u7b0f\u7b10\u7b11\u7b12\u7b13\u7b14\u7b15\u7b16\u7b17\u7b18\u7b19\u7b1a\u7b1b\u7b1c\u7b1d\u7b1e\u7b1f\u7b20\u7b21\u7b22\u7b23\u7b24\u7b25\u7b26\u7b27\u7b28\u7b29\u7b2a\u7b2b\u7b2c\u7b2d\u7b2e\u7b2f\u7b30\u7b31\u7b32\u7b33\u7b34\u7b35\u7b36\u7b37\u7b38\u7b39\u7b3a\u7b3b\u7b3c\u7b3d\u7b3e\u7b3f\u7b40\u7b41\u7b42\u7b43\u7b44\u7b45\u7b46\u7b47\u7b48\u7b49\u7b4a\u7b4b\u7b4c\u7b4d\u7b4e\u7b4f\u7b50\u7b51\u7b52\u7b53\u7b54\u7b55\u7b56\u7b57\u7b58\u7b59\u7b5a\u7b5b\u7b5c\u7b5d\u7b5e\u7b5f\u7b60\u7b61\u7b62\u7b63\u7b64\u7b65\u7b66\u7b67\u7b68\u7b69\u7b6a\u7b6b\u7b6c\u7b6d\u7b6e\u7b6f\u7b70\u7b71\u7b72\u7b73\u7b74\u7b75\u7b76\u7b77\u7b78\u7b79\u7b7a\u7b7b\u7b7c\u7b7d\u7b7e\u7b7f\u7b80\u7b81\u7b82\u7b83\u7b84\u7b85\u7b86\u7b87\u7b88\u7b89\u7b8a\u7b8b\u7b8c\u7b8d\u7b8e\u7b8f\u7b90\u7b91\u7b92\u7b93\u7b94\u7b95\u7b96\u7b97\u7b98\u7b99\u7b9a\u7b9b\u7b9c\u7b9d\u7b9e\u7b9f\u7ba0\u7ba1\u7ba2\u7ba3\u7ba4\u7ba5\u7ba6\u7ba7\u7ba8\u7ba9\u7baa\u7bab\u7bac\u7bad\u7bae\u7baf\u7bb0\u7bb1\u7bb2\u7bb3\u7bb4\u7bb5\u7bb6\u7bb7\u7bb8\u7bb9\u7bba\u7bbb\u7bbc\u7bbd\u7bbe\u7bbf\u7bc0\u7bc1\u7bc2\u7bc3\u7bc4\u7bc5\u7bc6\u7bc7\u7bc8\u7bc9\u7bca\u7bcb\u7bcc\u7bcd\u7bce\u7bcf\u7bd0\u7bd1\u7bd2\u7bd3\u7bd4\u7bd5\u7bd6\u7bd7\u7bd8\u7bd9\u7bda\u7bdb\u7bdc\u7bdd\u7bde\u7bdf\u7be0\u7be1\u7be2\u7be3\u7be4\u7be5\u7be6\u7be7\u7be8\u7be9\u7bea\u7beb\u7bec\u7bed\u7bee\u7bef\u7bf0\u7bf1\u7bf2\u7bf3\u7bf4\u7bf5\u7bf6\u7bf7\u7bf8\u7bf9\u7bfa\u7bfb\u7bfc\u7bfd\u7bfe\u7bff\u7c00\u7c01\u7c02\u7c03\u7c04\u7c05\u7c06\u7c07\u7c08\u7c09\u7c0a\u7c0b\u7c0c\u7c0d\u7c0e\u7c0f\u7c10\u7c11\u7c12\u7c13\u7c14\u7c15\u7c16\u7c17\u7c18\u7c19\u7c1a\u7c1b\u7c1c\u7c1d\u7c1e\u7c1f\u7c20\u7c21\u7c22\u7c23\u7c24\u7c25\u7c26\u7c27\u7c28\u7c29\u7c2a\u7c2b\u7c2c\u7c2d\u7c2e\u7c2f\u7c30\u7c31\u7c32\u7c33\u7c34\u7c35\u7c36\u7c37\u7c38\u7c39\u7c3a\u7c3b\u7c3c\u7c3d\u7c3e\u7c3f\u7c40\u7c41\u7c42\u7c43\u7c44\u7c45\u7c46\u7c47\u7c48\u7c49\u7c4a\u7c4b\u7c4c\u7c4d\u7c4e\u7c4f\u7c50\u7c51\u7c52\u7c53\u7c54\u7c55\u7c56\u7c57\u7c58\u7c59\u7c5a\u7c5b\u7c5c\u7c5d\u7c5e\u7c5f\u7c60\u7c61\u7c62\u7c63\u7c64\u7c65\u7c66\u7c67\u7c68\u7c69\u7c6a\u7c6b\u7c6c\u7c6d\u7c6e\u7c6f\u7c70\u7c71\u7c72\u7c73\u7c74\u7c75\u7c76\u7c77\u7c78\u7c79\u7c7a\u7c7b\u7c7c\u7c7d\u7c7e\u7c7f\u7c80\u7c81\u7c82\u7c83\u7c84\u7c85\u7c86\u7c87\u7c88\u7c89\u7c8a\u7c8b\u7c8c\u7c8d\u7c8e\u7c8f\u7c90\u7c91\u7c92\u7c93\u7c94\u7c95\u7c96\u7c97\u7c98\u7c99\u7c9a\u7c9b\u7c9c\u7c9d\u7c9e\u7c9f\u7ca0\u7ca1\u7ca2\u7ca3\u7ca4\u7ca5\u7ca6\u7ca7\u7ca8\u7ca9\u7caa\u7cab\u7cac\u7cad\u7cae\u7caf\u7cb0\u7cb1\u7cb2\u7cb3\u7cb4\u7cb5\u7cb6\u7cb7\u7cb8\u7cb9\u7cba\u7cbb\u7cbc\u7cbd\u7cbe\u7cbf\u7cc0\u7cc1\u7cc2\u7cc3\u7cc4\u7cc5\u7cc6\u7cc7\u7cc8\u7cc9\u7cca\u7ccb\u7ccc\u7ccd\u7cce\u7ccf\u7cd0\u7cd1\u7cd2\u7cd3\u7cd4\u7cd5\u7cd6\u7cd7\u7cd8\u7cd9\u7cda\u7cdb\u7cdc\u7cdd\u7cde\u7cdf\u7ce0\u7ce1\u7ce2\u7ce3\u7ce4\u7ce5\u7ce6\u7ce7\u7ce8\u7ce9\u7cea\u7ceb\u7cec\u7ced\u7cee\u7cef\u7cf0\u7cf1\u7cf2\u7cf3\u7cf4\u7cf5\u7cf6\u7cf7\u7cf8\u7cf9\u7cfa\u7cfb\u7cfc\u7cfd\u7cfe\u7cff\u7d00\u7d01\u7d02\u7d03\u7d04\u7d05\u7d06\u7d07\u7d08\u7d09\u7d0a\u7d0b\u7d0c\u7d0d\u7d0e\u7d0f\u7d10\u7d11\u7d12\u7d13\u7d14\u7d15\u7d16\u7d17\u7d18\u7d19\u7d1a\u7d1b\u7d1c\u7d1d\u7d1e\u7d1f\u7d20\u7d21\u7d22\u7d23\u7d24\u7d25\u7d26\u7d27\u7d28\u7d29\u7d2a\u7d2b\u7d2c\u7d2d\u7d2e\u7d2f\u7d30\u7d31\u7d32\u7d33\u7d34\u7d35\u7d36\u7d37\u7d38\u7d39\u7d3a\u7d3b\u7d3c\u7d3d\u7d3e\u7d3f\u7d40\u7d41\u7d42\u7d43\u7d44\u7d45\u7d46\u7d47\u7d48\u7d49\u7d4a\u7d4b\u7d4c\u7d4d\u7d4e\u7d4f\u7d50\u7d51\u7d52\u7d53\u7d54\u7d55\u7d56\u7d57\u7d58\u7d59\u7d5a\u7d5b\u7d5c\u7d5d\u7d5e\u7d5f\u7d60\u7d61\u7d62\u7d63\u7d64\u7d65\u7d66\u7d67\u7d68\u7d69\u7d6a\u7d6b\u7d6c\u7d6d\u7d6e\u7d6f\u7d70\u7d71\u7d72\u7d73\u7d74\u7d75\u7d76\u7d77\u7d78\u7d79\u7d7a\u7d7b\u7d7c\u7d7d\u7d7e\u7d7f\u7d80\u7d81\u7d82\u7d83\u7d84\u7d85\u7d86\u7d87\u7d88\u7d89\u7d8a\u7d8b\u7d8c\u7d8d\u7d8e\u7d8f\u7d90\u7d91\u7d92\u7d93\u7d94\u7d95\u7d96\u7d97\u7d98\u7d99\u7d9a\u7d9b\u7d9c\u7d9d\u7d9e\u7d9f\u7da0\u7da1\u7da2\u7da3\u7da4\u7da5\u7da6\u7da7\u7da8\u7da9\u7daa\u7dab\u7dac\u7dad\u7dae\u7daf\u7db0\u7db1\u7db2\u7db3\u7db4\u7db5\u7db6\u7db7\u7db8\u7db9\u7dba\u7dbb\u7dbc\u7dbd\u7dbe\u7dbf\u7dc0\u7dc1\u7dc2\u7dc3\u7dc4\u7dc5\u7dc6\u7dc7\u7dc8\u7dc9\u7dca\u7dcb\u7dcc\u7dcd\u7dce\u7dcf\u7dd0\u7dd1\u7dd2\u7dd3\u7dd4\u7dd5\u7dd6\u7dd7\u7dd8\u7dd9\u7dda\u7ddb\u7ddc\u7ddd\u7dde\u7ddf\u7de0\u7de1\u7de2\u7de3\u7de4\u7de5\u7de6\u7de7\u7de8\u7de9\u7dea\u7deb\u7dec\u7ded\u7dee\u7def\u7df0\u7df1\u7df2\u7df3\u7df4\u7df5\u7df6\u7df7\u7df8\u7df9\u7dfa\u7dfb\u7dfc\u7dfd\u7dfe\u7dff\u7e00\u7e01\u7e02\u7e03\u7e04\u7e05\u7e06\u7e07\u7e08\u7e09\u7e0a\u7e0b\u7e0c\u7e0d\u7e0e\u7e0f\u7e10\u7e11\u7e12\u7e13\u7e14\u7e15\u7e16\u7e17\u7e18\u7e19\u7e1a\u7e1b\u7e1c\u7e1d\u7e1e\u7e1f\u7e20\u7e21\u7e22\u7e23\u7e24\u7e25\u7e26\u7e27\u7e28\u7e29\u7e2a\u7e2b\u7e2c\u7e2d\u7e2e\u7e2f\u7e30\u7e31\u7e32\u7e33\u7e34\u7e35\u7e36\u7e37\u7e38\u7e39\u7e3a\u7e3b\u7e3c\u7e3d\u7e3e\u7e3f\u7e40\u7e41\u7e42\u7e43\u7e44\u7e45\u7e46\u7e47\u7e48\u7e49\u7e4a\u7e4b\u7e4c\u7e4d\u7e4e\u7e4f\u7e50\u7e51\u7e52\u7e53\u7e54\u7e55\u7e56\u7e57\u7e58\u7e59\u7e5a\u7e5b\u7e5c\u7e5d\u7e5e\u7e5f\u7e60\u7e61\u7e62\u7e63\u7e64\u7e65\u7e66\u7e67\u7e68\u7e69\u7e6a\u7e6b\u7e6c\u7e6d\u7e6e\u7e6f\u7e70\u7e71\u7e72\u7e73\u7e74\u7e75\u7e76\u7e77\u7e78\u7e79\u7e7a\u7e7b\u7e7c\u7e7d\u7e7e\u7e7f\u7e80\u7e81\u7e82\u7e83\u7e84\u7e85\u7e86\u7e87\u7e88\u7e89\u7e8a\u7e8b\u7e8c\u7e8d\u7e8e\u7e8f\u7e90\u7e91\u7e92\u7e93\u7e94\u7e95\u7e96\u7e97\u7e98\u7e99\u7e9a\u7e9b\u7e9c\u7e9d\u7e9e\u7e9f\u7ea0\u7ea1\u7ea2\u7ea3\u7ea4\u7ea5\u7ea6\u7ea7\u7ea8\u7ea9\u7eaa\u7eab\u7eac\u7ead\u7eae\u7eaf\u7eb0\u7eb1\u7eb2\u7eb3\u7eb4\u7eb5\u7eb6\u7eb7\u7eb8\u7eb9\u7eba\u7ebb\u7ebc\u7ebd\u7ebe\u7ebf\u7ec0\u7ec1\u7ec2\u7ec3\u7ec4\u7ec5\u7ec6\u7ec7\u7ec8\u7ec9\u7eca\u7ecb\u7ecc\u7ecd\u7ece\u7ecf\u7ed0\u7ed1\u7ed2\u7ed3\u7ed4\u7ed5\u7ed6\u7ed7\u7ed8\u7ed9\u7eda\u7edb\u7edc\u7edd\u7ede\u7edf\u7ee0\u7ee1\u7ee2\u7ee3\u7ee4\u7ee5\u7ee6\u7ee7\u7ee8\u7ee9\u7eea\u7eeb\u7eec\u7eed\u7eee\u7eef\u7ef0\u7ef1\u7ef2\u7ef3\u7ef4\u7ef5\u7ef6\u7ef7\u7ef8\u7ef9\u7efa\u7efb\u7efc\u7efd\u7efe\u7eff\u7f00\u7f01\u7f02\u7f03\u7f04\u7f05\u7f06\u7f07\u7f08\u7f09\u7f0a\u7f0b\u7f0c\u7f0d\u7f0e\u7f0f\u7f10\u7f11\u7f12\u7f13\u7f14\u7f15\u7f16\u7f17\u7f18\u7f19\u7f1a\u7f1b\u7f1c\u7f1d\u7f1e\u7f1f\u7f20\u7f21\u7f22\u7f23\u7f24\u7f25\u7f26\u7f27\u7f28\u7f29\u7f2a\u7f2b\u7f2c\u7f2d\u7f2e\u7f2f\u7f30\u7f31\u7f32\u7f33\u7f34\u7f35\u7f36\u7f37\u7f38\u7f39\u7f3a\u7f3b\u7f3c\u7f3d\u7f3e\u7f3f\u7f40\u7f41\u7f42\u7f43\u7f44\u7f45\u7f46\u7f47\u7f48\u7f49\u7f4a\u7f4b\u7f4c\u7f4d\u7f4e\u7f4f\u7f50\u7f51\u7f52\u7f53\u7f54\u7f55\u7f56\u7f57\u7f58\u7f59\u7f5a\u7f5b\u7f5c\u7f5d\u7f5e\u7f5f\u7f60\u7f61\u7f62\u7f63\u7f64\u7f65\u7f66\u7f67\u7f68\u7f69\u7f6a\u7f6b\u7f6c\u7f6d\u7f6e\u7f6f\u7f70\u7f71\u7f72\u7f73\u7f74\u7f75\u7f76\u7f77\u7f78\u7f79\u7f7a\u7f7b\u7f7c\u7f7d\u7f7e\u7f7f\u7f80\u7f81\u7f82\u7f83\u7f84\u7f85\u7f86\u7f87\u7f88\u7f89\u7f8a\u7f8b\u7f8c\u7f8d\u7f8e\u7f8f\u7f90\u7f91\u7f92\u7f93\u7f94\u7f95\u7f96\u7f97\u7f98\u7f99\u7f9a\u7f9b\u7f9c\u7f9d\u7f9e\u7f9f\u7fa0\u7fa1\u7fa2\u7fa3\u7fa4\u7fa5\u7fa6\u7fa7\u7fa8\u7fa9\u7faa\u7fab\u7fac\u7fad\u7fae\u7faf\u7fb0\u7fb1\u7fb2\u7fb3\u7fb4\u7fb5\u7fb6\u7fb7\u7fb8\u7fb9\u7fba\u7fbb\u7fbc\u7fbd\u7fbe\u7fbf\u7fc0\u7fc1\u7fc2\u7fc3\u7fc4\u7fc5\u7fc6\u7fc7\u7fc8\u7fc9\u7fca\u7fcb\u7fcc\u7fcd\u7fce\u7fcf\u7fd0\u7fd1\u7fd2\u7fd3\u7fd4\u7fd5\u7fd6\u7fd7\u7fd8\u7fd9\u7fda\u7fdb\u7fdc\u7fdd\u7fde\u7fdf\u7fe0\u7fe1\u7fe2\u7fe3\u7fe4\u7fe5\u7fe6\u7fe7\u7fe8\u7fe9\u7fea\u7feb\u7fec\u7fed\u7fee\u7fef\u7ff0\u7ff1\u7ff2\u7ff3\u7ff4\u7ff5\u7ff6\u7ff7\u7ff8\u7ff9\u7ffa\u7ffb\u7ffc\u7ffd\u7ffe\u7fff\u8000\u8001\u8002\u8003\u8004\u8005\u8006\u8007\u8008\u8009\u800a\u800b\u800c\u800d\u800e\u800f\u8010\u8011\u8012\u8013\u8014\u8015\u8016\u8017\u8018\u8019\u801a\u801b\u801c\u801d\u801e\u801f\u8020\u8021\u8022\u8023\u8024\u8025\u8026\u8027\u8028\u8029\u802a\u802b\u802c\u802d\u802e\u802f\u8030\u8031\u8032\u8033\u8034\u8035\u8036\u8037\u8038\u8039\u803a\u803b\u803c\u803d\u803e\u803f\u8040\u8041\u8042\u8043\u8044\u8045\u8046\u8047\u8048\u8049\u804a\u804b\u804c\u804d\u804e\u804f\u8050\u8051\u8052\u8053\u8054\u8055\u8056\u8057\u8058\u8059\u805a\u805b\u805c\u805d\u805e\u805f\u8060\u8061\u8062\u8063\u8064\u8065\u8066\u8067\u8068\u8069\u806a\u806b\u806c\u806d\u806e\u806f\u8070\u8071\u8072\u8073\u8074\u8075\u8076\u8077\u8078\u8079\u807a\u807b\u807c\u807d\u807e\u807f\u8080\u8081\u8082\u8083\u8084\u8085\u8086\u8087\u8088\u8089\u808a\u808b\u808c\u808d\u808e\u808f\u8090\u8091\u8092\u8093\u8094\u8095\u8096\u8097\u8098\u8099\u809a\u809b\u809c\u809d\u809e\u809f\u80a0\u80a1\u80a2\u80a3\u80a4\u80a5\u80a6\u80a7\u80a8\u80a9\u80aa\u80ab\u80ac\u80ad\u80ae\u80af\u80b0\u80b1\u80b2\u80b3\u80b4\u80b5\u80b6\u80b7\u80b8\u80b9\u80ba\u80bb\u80bc\u80bd\u80be\u80bf\u80c0\u80c1\u80c2\u80c3\u80c4\u80c5\u80c6\u80c7\u80c8\u80c9\u80ca\u80cb\u80cc\u80cd\u80ce\u80cf\u80d0\u80d1\u80d2\u80d3\u80d4\u80d5\u80d6\u80d7\u80d8\u80d9\u80da\u80db\u80dc\u80dd\u80de\u80df\u80e0\u80e1\u80e2\u80e3\u80e4\u80e5\u80e6\u80e7\u80e8\u80e9\u80ea\u80eb\u80ec\u80ed\u80ee\u80ef\u80f0\u80f1\u80f2\u80f3\u80f4\u80f5\u80f6\u80f7\u80f8\u80f9\u80fa\u80fb\u80fc\u80fd\u80fe\u80ff\u8100\u8101\u8102\u8103\u8104\u8105\u8106\u8107\u8108\u8109\u810a\u810b\u810c\u810d\u810e\u810f\u8110\u8111\u8112\u8113\u8114\u8115\u8116\u8117\u8118\u8119\u811a\u811b\u811c\u811d\u811e\u811f\u8120\u8121\u8122\u8123\u8124\u8125\u8126\u8127\u8128\u8129\u812a\u812b\u812c\u812d\u812e\u812f\u8130\u8131\u8132\u8133\u8134\u8135\u8136\u8137\u8138\u8139\u813a\u813b\u813c\u813d\u813e\u813f\u8140\u8141\u8142\u8143\u8144\u8145\u8146\u8147\u8148\u8149\u814a\u814b\u814c\u814d\u814e\u814f\u8150\u8151\u8152\u8153\u8154\u8155\u8156\u8157\u8158\u8159\u815a\u815b\u815c\u815d\u815e\u815f\u8160\u8161\u8162\u8163\u8164\u8165\u8166\u8167\u8168\u8169\u816a\u816b\u816c\u816d\u816e\u816f\u8170\u8171\u8172\u8173\u8174\u8175\u8176\u8177\u8178\u8179\u817a\u817b\u817c\u817d\u817e\u817f\u8180\u8181\u8182\u8183\u8184\u8185\u8186\u8187\u8188\u8189\u818a\u818b\u818c\u818d\u818e\u818f\u8190\u8191\u8192\u8193\u8194\u8195\u8196\u8197\u8198\u8199\u819a\u819b\u819c\u819d\u819e\u819f\u81a0\u81a1\u81a2\u81a3\u81a4\u81a5\u81a6\u81a7\u81a8\u81a9\u81aa\u81ab\u81ac\u81ad\u81ae\u81af\u81b0\u81b1\u81b2\u81b3\u81b4\u81b5\u81b6\u81b7\u81b8\u81b9\u81ba\u81bb\u81bc\u81bd\u81be\u81bf\u81c0\u81c1\u81c2\u81c3\u81c4\u81c5\u81c6\u81c7\u81c8\u81c9\u81ca\u81cb\u81cc\u81cd\u81ce\u81cf\u81d0\u81d1\u81d2\u81d3\u81d4\u81d5\u81d6\u81d7\u81d8\u81d9\u81da\u81db\u81dc\u81dd\u81de\u81df\u81e0\u81e1\u81e2\u81e3\u81e4\u81e5\u81e6\u81e7\u81e8\u81e9\u81ea\u81eb\u81ec\u81ed\u81ee\u81ef\u81f0\u81f1\u81f2\u81f3\u81f4\u81f5\u81f6\u81f7\u81f8\u81f9\u81fa\u81fb\u81fc\u81fd\u81fe\u81ff\u8200\u8201\u8202\u8203\u8204\u8205\u8206\u8207\u8208\u8209\u820a\u820b\u820c\u820d\u820e\u820f\u8210\u8211\u8212\u8213\u8214\u8215\u8216\u8217\u8218\u8219\u821a\u821b\u821c\u821d\u821e\u821f\u8220\u8221\u8222\u8223\u8224\u8225\u8226\u8227\u8228\u8229\u822a\u822b\u822c\u822d\u822e\u822f\u8230\u8231\u8232\u8233\u8234\u8235\u8236\u8237\u8238\u8239\u823a\u823b\u823c\u823d\u823e\u823f\u8240\u8241\u8242\u8243\u8244\u8245\u8246\u8247\u8248\u8249\u824a\u824b\u824c\u824d\u824e\u824f\u8250\u8251\u8252\u8253\u8254\u8255\u8256\u8257\u8258\u8259\u825a\u825b\u825c\u825d\u825e\u825f\u8260\u8261\u8262\u8263\u8264\u8265\u8266\u8267\u8268\u8269\u826a\u826b\u826c\u826d\u826e\u826f\u8270\u8271\u8272\u8273\u8274\u8275\u8276\u8277\u8278\u8279\u827a\u827b\u827c\u827d\u827e\u827f\u8280\u8281\u8282\u8283\u8284\u8285\u8286\u8287\u8288\u8289\u828a\u828b\u828c\u828d\u828e\u828f\u8290\u8291\u8292\u8293\u8294\u8295\u8296\u8297\u8298\u8299\u829a\u829b\u829c\u829d\u829e\u829f\u82a0\u82a1\u82a2\u82a3\u82a4\u82a5\u82a6\u82a7\u82a8\u82a9\u82aa\u82ab\u82ac\u82ad\u82ae\u82af\u82b0\u82b1\u82b2\u82b3\u82b4\u82b5\u82b6\u82b7\u82b8\u82b9\u82ba\u82bb\u82bc\u82bd\u82be\u82bf\u82c0\u82c1\u82c2\u82c3\u82c4\u82c5\u82c6\u82c7\u82c8\u82c9\u82ca\u82cb\u82cc\u82cd\u82ce\u82cf\u82d0\u82d1\u82d2\u82d3\u82d4\u82d5\u82d6\u82d7\u82d8\u82d9\u82da\u82db\u82dc\u82dd\u82de\u82df\u82e0\u82e1\u82e2\u82e3\u82e4\u82e5\u82e6\u82e7\u82e8\u82e9\u82ea\u82eb\u82ec\u82ed\u82ee\u82ef\u82f0\u82f1\u82f2\u82f3\u82f4\u82f5\u82f6\u82f7\u82f8\u82f9\u82fa\u82fb\u82fc\u82fd\u82fe\u82ff\u8300\u8301\u8302\u8303\u8304\u8305\u8306\u8307\u8308\u8309\u830a\u830b\u830c\u830d\u830e\u830f\u8310\u8311\u8312\u8313\u8314\u8315\u8316\u8317\u8318\u8319\u831a\u831b\u831c\u831d\u831e\u831f\u8320\u8321\u8322\u8323\u8324\u8325\u8326\u8327\u8328\u8329\u832a\u832b\u832c\u832d\u832e\u832f\u8330\u8331\u8332\u8333\u8334\u8335\u8336\u8337\u8338\u8339\u833a\u833b\u833c\u833d\u833e\u833f\u8340\u8341\u8342\u8343\u8344\u8345\u8346\u8347\u8348\u8349\u834a\u834b\u834c\u834d\u834e\u834f\u8350\u8351\u8352\u8353\u8354\u8355\u8356\u8357\u8358\u8359\u835a\u835b\u835c\u835d\u835e\u835f\u8360\u8361\u8362\u8363\u8364\u8365\u8366\u8367\u8368\u8369\u836a\u836b\u836c\u836d\u836e\u836f\u8370\u8371\u8372\u8373\u8374\u8375\u8376\u8377\u8378\u8379\u837a\u837b\u837c\u837d\u837e\u837f\u8380\u8381\u8382\u8383\u8384\u8385\u8386\u8387\u8388\u8389\u838a\u838b\u838c\u838d\u838e\u838f\u8390\u8391\u8392\u8393\u8394\u8395\u8396\u8397\u8398\u8399\u839a\u839b\u839c\u839d\u839e\u839f\u83a0\u83a1\u83a2\u83a3\u83a4\u83a5\u83a6\u83a7\u83a8\u83a9\u83aa\u83ab\u83ac\u83ad\u83ae\u83af\u83b0\u83b1\u83b2\u83b3\u83b4\u83b5\u83b6\u83b7\u83b8\u83b9\u83ba\u83bb\u83bc\u83bd\u83be\u83bf\u83c0\u83c1\u83c2\u83c3\u83c4\u83c5\u83c6\u83c7\u83c8\u83c9\u83ca\u83cb\u83cc\u83cd\u83ce\u83cf\u83d0\u83d1\u83d2\u83d3\u83d4\u83d5\u83d6\u83d7\u83d8\u83d9\u83da\u83db\u83dc\u83dd\u83de\u83df\u83e0\u83e1\u83e2\u83e3\u83e4\u83e5\u83e6\u83e7\u83e8\u83e9\u83ea\u83eb\u83ec\u83ed\u83ee\u83ef\u83f0\u83f1\u83f2\u83f3\u83f4\u83f5\u83f6\u83f7\u83f8\u83f9\u83fa\u83fb\u83fc\u83fd\u83fe\u83ff\u8400\u8401\u8402\u8403\u8404\u8405\u8406\u8407\u8408\u8409\u840a\u840b\u840c\u840d\u840e\u840f\u8410\u8411\u8412\u8413\u8414\u8415\u8416\u8417\u8418\u8419\u841a\u841b\u841c\u841d\u841e\u841f\u8420\u8421\u8422\u8423\u8424\u8425\u8426\u8427\u8428\u8429\u842a\u842b\u842c\u842d\u842e\u842f\u8430\u8431\u8432\u8433\u8434\u8435\u8436\u8437\u8438\u8439\u843a\u843b\u843c\u843d\u843e\u843f\u8440\u8441\u8442\u8443\u8444\u8445\u8446\u8447\u8448\u8449\u844a\u844b\u844c\u844d\u844e\u844f\u8450\u8451\u8452\u8453\u8454\u8455\u8456\u8457\u8458\u8459\u845a\u845b\u845c\u845d\u845e\u845f\u8460\u8461\u8462\u8463\u8464\u8465\u8466\u8467\u8468\u8469\u846a\u846b\u846c\u846d\u846e\u846f\u8470\u8471\u8472\u8473\u8474\u8475\u8476\u8477\u8478\u8479\u847a\u847b\u847c\u847d\u847e\u847f\u8480\u8481\u8482\u8483\u8484\u8485\u8486\u8487\u8488\u8489\u848a\u848b\u848c\u848d\u848e\u848f\u8490\u8491\u8492\u8493\u8494\u8495\u8496\u8497\u8498\u8499\u849a\u849b\u849c\u849d\u849e\u849f\u84a0\u84a1\u84a2\u84a3\u84a4\u84a5\u84a6\u84a7\u84a8\u84a9\u84aa\u84ab\u84ac\u84ad\u84ae\u84af\u84b0\u84b1\u84b2\u84b3\u84b4\u84b5\u84b6\u84b7\u84b8\u84b9\u84ba\u84bb\u84bc\u84bd\u84be\u84bf\u84c0\u84c1\u84c2\u84c3\u84c4\u84c5\u84c6\u84c7\u84c8\u84c9\u84ca\u84cb\u84cc\u84cd\u84ce\u84cf\u84d0\u84d1\u84d2\u84d3\u84d4\u84d5\u84d6\u84d7\u84d8\u84d9\u84da\u84db\u84dc\u84dd\u84de\u84df\u84e0\u84e1\u84e2\u84e3\u84e4\u84e5\u84e6\u84e7\u84e8\u84e9\u84ea\u84eb\u84ec\u84ed\u84ee\u84ef\u84f0\u84f1\u84f2\u84f3\u84f4\u84f5\u84f6\u84f7\u84f8\u84f9\u84fa\u84fb\u84fc\u84fd\u84fe\u84ff\u8500\u8501\u8502\u8503\u8504\u8505\u8506\u8507\u8508\u8509\u850a\u850b\u850c\u850d\u850e\u850f\u8510\u8511\u8512\u8513\u8514\u8515\u8516\u8517\u8518\u8519\u851a\u851b\u851c\u851d\u851e\u851f\u8520\u8521\u8522\u8523\u8524\u8525\u8526\u8527\u8528\u8529\u852a\u852b\u852c\u852d\u852e\u852f\u8530\u8531\u8532\u8533\u8534\u8535\u8536\u8537\u8538\u8539\u853a\u853b\u853c\u853d\u853e\u853f\u8540\u8541\u8542\u8543\u8544\u8545\u8546\u8547\u8548\u8549\u854a\u854b\u854c\u854d\u854e\u854f\u8550\u8551\u8552\u8553\u8554\u8555\u8556\u8557\u8558\u8559\u855a\u855b\u855c\u855d\u855e\u855f\u8560\u8561\u8562\u8563\u8564\u8565\u8566\u8567\u8568\u8569\u856a\u856b\u856c\u856d\u856e\u856f\u8570\u8571\u8572\u8573\u8574\u8575\u8576\u8577\u8578\u8579\u857a\u857b\u857c\u857d\u857e\u857f\u8580\u8581\u8582\u8583\u8584\u8585\u8586\u8587\u8588\u8589\u858a\u858b\u858c\u858d\u858e\u858f\u8590\u8591\u8592\u8593\u8594\u8595\u8596\u8597\u8598\u8599\u859a\u859b\u859c\u859d\u859e\u859f\u85a0\u85a1\u85a2\u85a3\u85a4\u85a5\u85a6\u85a7\u85a8\u85a9\u85aa\u85ab\u85ac\u85ad\u85ae\u85af\u85b0\u85b1\u85b2\u85b3\u85b4\u85b5\u85b6\u85b7\u85b8\u85b9\u85ba\u85bb\u85bc\u85bd\u85be\u85bf\u85c0\u85c1\u85c2\u85c3\u85c4\u85c5\u85c6\u85c7\u85c8\u85c9\u85ca\u85cb\u85cc\u85cd\u85ce\u85cf\u85d0\u85d1\u85d2\u85d3\u85d4\u85d5\u85d6\u85d7\u85d8\u85d9\u85da\u85db\u85dc\u85dd\u85de\u85df\u85e0\u85e1\u85e2\u85e3\u85e4\u85e5\u85e6\u85e7\u85e8\u85e9\u85ea\u85eb\u85ec\u85ed\u85ee\u85ef\u85f0\u85f1\u85f2\u85f3\u85f4\u85f5\u85f6\u85f7\u85f8\u85f9\u85fa\u85fb\u85fc\u85fd\u85fe\u85ff\u8600\u8601\u8602\u8603\u8604\u8605\u8606\u8607\u8608\u8609\u860a\u860b\u860c\u860d\u860e\u860f\u8610\u8611\u8612\u8613\u8614\u8615\u8616\u8617\u8618\u8619\u861a\u861b\u861c\u861d\u861e\u861f\u8620\u8621\u8622\u8623\u8624\u8625\u8626\u8627\u8628\u8629\u862a\u862b\u862c\u862d\u862e\u862f\u8630\u8631\u8632\u8633\u8634\u8635\u8636\u8637\u8638\u8639\u863a\u863b\u863c\u863d\u863e\u863f\u8640\u8641\u8642\u8643\u8644\u8645\u8646\u8647\u8648\u8649\u864a\u864b\u864c\u864d\u864e\u864f\u8650\u8651\u8652\u8653\u8654\u8655\u8656\u8657\u8658\u8659\u865a\u865b\u865c\u865d\u865e\u865f\u8660\u8661\u8662\u8663\u8664\u8665\u8666\u8667\u8668\u8669\u866a\u866b\u866c\u866d\u866e\u866f\u8670\u8671\u8672\u8673\u8674\u8675\u8676\u8677\u8678\u8679\u867a\u867b\u867c\u867d\u867e\u867f\u8680\u8681\u8682\u8683\u8684\u8685\u8686\u8687\u8688\u8689\u868a\u868b\u868c\u868d\u868e\u868f\u8690\u8691\u8692\u8693\u8694\u8695\u8696\u8697\u8698\u8699\u869a\u869b\u869c\u869d\u869e\u869f\u86a0\u86a1\u86a2\u86a3\u86a4\u86a5\u86a6\u86a7\u86a8\u86a9\u86aa\u86ab\u86ac\u86ad\u86ae\u86af\u86b0\u86b1\u86b2\u86b3\u86b4\u86b5\u86b6\u86b7\u86b8\u86b9\u86ba\u86bb\u86bc\u86bd\u86be\u86bf\u86c0\u86c1\u86c2\u86c3\u86c4\u86c5\u86c6\u86c7\u86c8\u86c9\u86ca\u86cb\u86cc\u86cd\u86ce\u86cf\u86d0\u86d1\u86d2\u86d3\u86d4\u86d5\u86d6\u86d7\u86d8\u86d9\u86da\u86db\u86dc\u86dd\u86de\u86df\u86e0\u86e1\u86e2\u86e3\u86e4\u86e5\u86e6\u86e7\u86e8\u86e9\u86ea\u86eb\u86ec\u86ed\u86ee\u86ef\u86f0\u86f1\u86f2\u86f3\u86f4\u86f5\u86f6\u86f7\u86f8\u86f9\u86fa\u86fb\u86fc\u86fd\u86fe\u86ff\u8700\u8701\u8702\u8703\u8704\u8705\u8706\u8707\u8708\u8709\u870a\u870b\u870c\u870d\u870e\u870f\u8710\u8711\u8712\u8713\u8714\u8715\u8716\u8717\u8718\u8719\u871a\u871b\u871c\u871d\u871e\u871f\u8720\u8721\u8722\u8723\u8724\u8725\u8726\u8727\u8728\u8729\u872a\u872b\u872c\u872d\u872e\u872f\u8730\u8731\u8732\u8733\u8734\u8735\u8736\u8737\u8738\u8739\u873a\u873b\u873c\u873d\u873e\u873f\u8740\u8741\u8742\u8743\u8744\u8745\u8746\u8747\u8748\u8749\u874a\u874b\u874c\u874d\u874e\u874f\u8750\u8751\u8752\u8753\u8754\u8755\u8756\u8757\u8758\u8759\u875a\u875b\u875c\u875d\u875e\u875f\u8760\u8761\u8762\u8763\u8764\u8765\u8766\u8767\u8768\u8769\u876a\u876b\u876c\u876d\u876e\u876f\u8770\u8771\u8772\u8773\u8774\u8775\u8776\u8777\u8778\u8779\u877a\u877b\u877c\u877d\u877e\u877f\u8780\u8781\u8782\u8783\u8784\u8785\u8786\u8787\u8788\u8789\u878a\u878b\u878c\u878d\u878e\u878f\u8790\u8791\u8792\u8793\u8794\u8795\u8796\u8797\u8798\u8799\u879a\u879b\u879c\u879d\u879e\u879f\u87a0\u87a1\u87a2\u87a3\u87a4\u87a5\u87a6\u87a7\u87a8\u87a9\u87aa\u87ab\u87ac\u87ad\u87ae\u87af\u87b0\u87b1\u87b2\u87b3\u87b4\u87b5\u87b6\u87b7\u87b8\u87b9\u87ba\u87bb\u87bc\u87bd\u87be\u87bf\u87c0\u87c1\u87c2\u87c3\u87c4\u87c5\u87c6\u87c7\u87c8\u87c9\u87ca\u87cb\u87cc\u87cd\u87ce\u87cf\u87d0\u87d1\u87d2\u87d3\u87d4\u87d5\u87d6\u87d7\u87d8\u87d9\u87da\u87db\u87dc\u87dd\u87de\u87df\u87e0\u87e1\u87e2\u87e3\u87e4\u87e5\u87e6\u87e7\u87e8\u87e9\u87ea\u87eb\u87ec\u87ed\u87ee\u87ef\u87f0\u87f1\u87f2\u87f3\u87f4\u87f5\u87f6\u87f7\u87f8\u87f9\u87fa\u87fb\u87fc\u87fd\u87fe\u87ff\u8800\u8801\u8802\u8803\u8804\u8805\u8806\u8807\u8808\u8809\u880a\u880b\u880c\u880d\u880e\u880f\u8810\u8811\u8812\u8813\u8814\u8815\u8816\u8817\u8818\u8819\u881a\u881b\u881c\u881d\u881e\u881f\u8820\u8821\u8822\u8823\u8824\u8825\u8826\u8827\u8828\u8829\u882a\u882b\u882c\u882d\u882e\u882f\u8830\u8831\u8832\u8833\u8834\u8835\u8836\u8837\u8838\u8839\u883a\u883b\u883c\u883d\u883e\u883f\u8840\u8841\u8842\u8843\u8844\u8845\u8846\u8847\u8848\u8849\u884a\u884b\u884c\u884d\u884e\u884f\u8850\u8851\u8852\u8853\u8854\u8855\u8856\u8857\u8858\u8859\u885a\u885b\u885c\u885d\u885e\u885f\u8860\u8861\u8862\u8863\u8864\u8865\u8866\u8867\u8868\u8869\u886a\u886b\u886c\u886d\u886e\u886f\u8870\u8871\u8872\u8873\u8874\u8875\u8876\u8877\u8878\u8879\u887a\u887b\u887c\u887d\u887e\u887f\u8880\u8881\u8882\u8883\u8884\u8885\u8886\u8887\u8888\u8889\u888a\u888b\u888c\u888d\u888e\u888f\u8890\u8891\u8892\u8893\u8894\u8895\u8896\u8897\u8898\u8899\u889a\u889b\u889c\u889d\u889e\u889f\u88a0\u88a1\u88a2\u88a3\u88a4\u88a5\u88a6\u88a7\u88a8\u88a9\u88aa\u88ab\u88ac\u88ad\u88ae\u88af\u88b0\u88b1\u88b2\u88b3\u88b4\u88b5\u88b6\u88b7\u88b8\u88b9\u88ba\u88bb\u88bc\u88bd\u88be\u88bf\u88c0\u88c1\u88c2\u88c3\u88c4\u88c5\u88c6\u88c7\u88c8\u88c9\u88ca\u88cb\u88cc\u88cd\u88ce\u88cf\u88d0\u88d1\u88d2\u88d3\u88d4\u88d5\u88d6\u88d7\u88d8\u88d9\u88da\u88db\u88dc\u88dd\u88de\u88df\u88e0\u88e1\u88e2\u88e3\u88e4\u88e5\u88e6\u88e7\u88e8\u88e9\u88ea\u88eb\u88ec\u88ed\u88ee\u88ef\u88f0\u88f1\u88f2\u88f3\u88f4\u88f5\u88f6\u88f7\u88f8\u88f9\u88fa\u88fb\u88fc\u88fd\u88fe\u88ff\u8900\u8901\u8902\u8903\u8904\u8905\u8906\u8907\u8908\u8909\u890a\u890b\u890c\u890d\u890e\u890f\u8910\u8911\u8912\u8913\u8914\u8915\u8916\u8917\u8918\u8919\u891a\u891b\u891c\u891d\u891e\u891f\u8920\u8921\u8922\u8923\u8924\u8925\u8926\u8927\u8928\u8929\u892a\u892b\u892c\u892d\u892e\u892f\u8930\u8931\u8932\u8933\u8934\u8935\u8936\u8937\u8938\u8939\u893a\u893b\u893c\u893d\u893e\u893f\u8940\u8941\u8942\u8943\u8944\u8945\u8946\u8947\u8948\u8949\u894a\u894b\u894c\u894d\u894e\u894f\u8950\u8951\u8952\u8953\u8954\u8955\u8956\u8957\u8958\u8959\u895a\u895b\u895c\u895d\u895e\u895f\u8960\u8961\u8962\u8963\u8964\u8965\u8966\u8967\u8968\u8969\u896a\u896b\u896c\u896d\u896e\u896f\u8970\u8971\u8972\u8973\u8974\u8975\u8976\u8977\u8978\u8979\u897a\u897b\u897c\u897d\u897e\u897f\u8980\u8981\u8982\u8983\u8984\u8985\u8986\u8987\u8988\u8989\u898a\u898b\u898c\u898d\u898e\u898f\u8990\u8991\u8992\u8993\u8994\u8995\u8996\u8997\u8998\u8999\u899a\u899b\u899c\u899d\u899e\u899f\u89a0\u89a1\u89a2\u89a3\u89a4\u89a5\u89a6\u89a7\u89a8\u89a9\u89aa\u89ab\u89ac\u89ad\u89ae\u89af\u89b0\u89b1\u89b2\u89b3\u89b4\u89b5\u89b6\u89b7\u89b8\u89b9\u89ba\u89bb\u89bc\u89bd\u89be\u89bf\u89c0\u89c1\u89c2\u89c3\u89c4\u89c5\u89c6\u89c7\u89c8\u89c9\u89ca\u89cb\u89cc\u89cd\u89ce\u89cf\u89d0\u89d1\u89d2\u89d3\u89d4\u89d5\u89d6\u89d7\u89d8\u89d9\u89da\u89db\u89dc\u89dd\u89de\u89df\u89e0\u89e1\u89e2\u89e3\u89e4\u89e5\u89e6\u89e7\u89e8\u89e9\u89ea\u89eb\u89ec\u89ed\u89ee\u89ef\u89f0\u89f1\u89f2\u89f3\u89f4\u89f5\u89f6\u89f7\u89f8\u89f9\u89fa\u89fb\u89fc\u89fd\u89fe\u89ff\u8a00\u8a01\u8a02\u8a03\u8a04\u8a05\u8a06\u8a07\u8a08\u8a09\u8a0a\u8a0b\u8a0c\u8a0d\u8a0e\u8a0f\u8a10\u8a11\u8a12\u8a13\u8a14\u8a15\u8a16\u8a17\u8a18\u8a19\u8a1a\u8a1b\u8a1c\u8a1d\u8a1e\u8a1f\u8a20\u8a21\u8a22\u8a23\u8a24\u8a25\u8a26\u8a27\u8a28\u8a29\u8a2a\u8a2b\u8a2c\u8a2d\u8a2e\u8a2f\u8a30\u8a31\u8a32\u8a33\u8a34\u8a35\u8a36\u8a37\u8a38\u8a39\u8a3a\u8a3b\u8a3c\u8a3d\u8a3e\u8a3f\u8a40\u8a41\u8a42\u8a43\u8a44\u8a45\u8a46\u8a47\u8a48\u8a49\u8a4a\u8a4b\u8a4c\u8a4d\u8a4e\u8a4f\u8a50\u8a51\u8a52\u8a53\u8a54\u8a55\u8a56\u8a57\u8a58\u8a59\u8a5a\u8a5b\u8a5c\u8a5d\u8a5e\u8a5f\u8a60\u8a61\u8a62\u8a63\u8a64\u8a65\u8a66\u8a67\u8a68\u8a69\u8a6a\u8a6b\u8a6c\u8a6d\u8a6e\u8a6f\u8a70\u8a71\u8a72\u8a73\u8a74\u8a75\u8a76\u8a77\u8a78\u8a79\u8a7a\u8a7b\u8a7c\u8a7d\u8a7e\u8a7f\u8a80\u8a81\u8a82\u8a83\u8a84\u8a85\u8a86\u8a87\u8a88\u8a89\u8a8a\u8a8b\u8a8c\u8a8d\u8a8e\u8a8f\u8a90\u8a91\u8a92\u8a93\u8a94\u8a95\u8a96\u8a97\u8a98\u8a99\u8a9a\u8a9b\u8a9c\u8a9d\u8a9e\u8a9f\u8aa0\u8aa1\u8aa2\u8aa3\u8aa4\u8aa5\u8aa6\u8aa7\u8aa8\u8aa9\u8aaa\u8aab\u8aac\u8aad\u8aae\u8aaf\u8ab0\u8ab1\u8ab2\u8ab3\u8ab4\u8ab5\u8ab6\u8ab7\u8ab8\u8ab9\u8aba\u8abb\u8abc\u8abd\u8abe\u8abf\u8ac0\u8ac1\u8ac2\u8ac3\u8ac4\u8ac5\u8ac6\u8ac7\u8ac8\u8ac9\u8aca\u8acb\u8acc\u8acd\u8ace\u8acf\u8ad0\u8ad1\u8ad2\u8ad3\u8ad4\u8ad5\u8ad6\u8ad7\u8ad8\u8ad9\u8ada\u8adb\u8adc\u8add\u8ade\u8adf\u8ae0\u8ae1\u8ae2\u8ae3\u8ae4\u8ae5\u8ae6\u8ae7\u8ae8\u8ae9\u8aea\u8aeb\u8aec\u8aed\u8aee\u8aef\u8af0\u8af1\u8af2\u8af3\u8af4\u8af5\u8af6\u8af7\u8af8\u8af9\u8afa\u8afb\u8afc\u8afd\u8afe\u8aff\u8b00\u8b01\u8b02\u8b03\u8b04\u8b05\u8b06\u8b07\u8b08\u8b09\u8b0a\u8b0b\u8b0c\u8b0d\u8b0e\u8b0f\u8b10\u8b11\u8b12\u8b13\u8b14\u8b15\u8b16\u8b17\u8b18\u8b19\u8b1a\u8b1b\u8b1c\u8b1d\u8b1e\u8b1f\u8b20\u8b21\u8b22\u8b23\u8b24\u8b25\u8b26\u8b27\u8b28\u8b29\u8b2a\u8b2b\u8b2c\u8b2d\u8b2e\u8b2f\u8b30\u8b31\u8b32\u8b33\u8b34\u8b35\u8b36\u8b37\u8b38\u8b39\u8b3a\u8b3b\u8b3c\u8b3d\u8b3e\u8b3f\u8b40\u8b41\u8b42\u8b43\u8b44\u8b45\u8b46\u8b47\u8b48\u8b49\u8b4a\u8b4b\u8b4c\u8b4d\u8b4e\u8b4f\u8b50\u8b51\u8b52\u8b53\u8b54\u8b55\u8b56\u8b57\u8b58\u8b59\u8b5a\u8b5b\u8b5c\u8b5d\u8b5e\u8b5f\u8b60\u8b61\u8b62\u8b63\u8b64\u8b65\u8b66\u8b67\u8b68\u8b69\u8b6a\u8b6b\u8b6c\u8b6d\u8b6e\u8b6f\u8b70\u8b71\u8b72\u8b73\u8b74\u8b75\u8b76\u8b77\u8b78\u8b79\u8b7a\u8b7b\u8b7c\u8b7d\u8b7e\u8b7f\u8b80\u8b81\u8b82\u8b83\u8b84\u8b85\u8b86\u8b87\u8b88\u8b89\u8b8a\u8b8b\u8b8c\u8b8d\u8b8e\u8b8f\u8b90\u8b91\u8b92\u8b93\u8b94\u8b95\u8b96\u8b97\u8b98\u8b99\u8b9a\u8b9b\u8b9c\u8b9d\u8b9e\u8b9f\u8ba0\u8ba1\u8ba2\u8ba3\u8ba4\u8ba5\u8ba6\u8ba7\u8ba8\u8ba9\u8baa\u8bab\u8bac\u8bad\u8bae\u8baf\u8bb0\u8bb1\u8bb2\u8bb3\u8bb4\u8bb5\u8bb6\u8bb7\u8bb8\u8bb9\u8bba\u8bbb\u8bbc\u8bbd\u8bbe\u8bbf\u8bc0\u8bc1\u8bc2\u8bc3\u8bc4\u8bc5\u8bc6\u8bc7\u8bc8\u8bc9\u8bca\u8bcb\u8bcc\u8bcd\u8bce\u8bcf\u8bd0\u8bd1\u8bd2\u8bd3\u8bd4\u8bd5\u8bd6\u8bd7\u8bd8\u8bd9\u8bda\u8bdb\u8bdc\u8bdd\u8bde\u8bdf\u8be0\u8be1\u8be2\u8be3\u8be4\u8be5\u8be6\u8be7\u8be8\u8be9\u8bea\u8beb\u8bec\u8bed\u8bee\u8bef\u8bf0\u8bf1\u8bf2\u8bf3\u8bf4\u8bf5\u8bf6\u8bf7\u8bf8\u8bf9\u8bfa\u8bfb\u8bfc\u8bfd\u8bfe\u8bff\u8c00\u8c01\u8c02\u8c03\u8c04\u8c05\u8c06\u8c07\u8c08\u8c09\u8c0a\u8c0b\u8c0c\u8c0d\u8c0e\u8c0f\u8c10\u8c11\u8c12\u8c13\u8c14\u8c15\u8c16\u8c17\u8c18\u8c19\u8c1a\u8c1b\u8c1c\u8c1d\u8c1e\u8c1f\u8c20\u8c21\u8c22\u8c23\u8c24\u8c25\u8c26\u8c27\u8c28\u8c29\u8c2a\u8c2b\u8c2c\u8c2d\u8c2e\u8c2f\u8c30\u8c31\u8c32\u8c33\u8c34\u8c35\u8c36\u8c37\u8c38\u8c39\u8c3a\u8c3b\u8c3c\u8c3d\u8c3e\u8c3f\u8c40\u8c41\u8c42\u8c43\u8c44\u8c45\u8c46\u8c47\u8c48\u8c49\u8c4a\u8c4b\u8c4c\u8c4d\u8c4e\u8c4f\u8c50\u8c51\u8c52\u8c53\u8c54\u8c55\u8c56\u8c57\u8c58\u8c59\u8c5a\u8c5b\u8c5c\u8c5d\u8c5e\u8c5f\u8c60\u8c61\u8c62\u8c63\u8c64\u8c65\u8c66\u8c67\u8c68\u8c69\u8c6a\u8c6b\u8c6c\u8c6d\u8c6e\u8c6f\u8c70\u8c71\u8c72\u8c73\u8c74\u8c75\u8c76\u8c77\u8c78\u8c79\u8c7a\u8c7b\u8c7c\u8c7d\u8c7e\u8c7f\u8c80\u8c81\u8c82\u8c83\u8c84\u8c85\u8c86\u8c87\u8c88\u8c89\u8c8a\u8c8b\u8c8c\u8c8d\u8c8e\u8c8f\u8c90\u8c91\u8c92\u8c93\u8c94\u8c95\u8c96\u8c97\u8c98\u8c99\u8c9a\u8c9b\u8c9c\u8c9d\u8c9e\u8c9f\u8ca0\u8ca1\u8ca2\u8ca3\u8ca4\u8ca5\u8ca6\u8ca7\u8ca8\u8ca9\u8caa\u8cab\u8cac\u8cad\u8cae\u8caf\u8cb0\u8cb1\u8cb2\u8cb3\u8cb4\u8cb5\u8cb6\u8cb7\u8cb8\u8cb9\u8cba\u8cbb\u8cbc\u8cbd\u8cbe\u8cbf\u8cc0\u8cc1\u8cc2\u8cc3\u8cc4\u8cc5\u8cc6\u8cc7\u8cc8\u8cc9\u8cca\u8ccb\u8ccc\u8ccd\u8cce\u8ccf\u8cd0\u8cd1\u8cd2\u8cd3\u8cd4\u8cd5\u8cd6\u8cd7\u8cd8\u8cd9\u8cda\u8cdb\u8cdc\u8cdd\u8cde\u8cdf\u8ce0\u8ce1\u8ce2\u8ce3\u8ce4\u8ce5\u8ce6\u8ce7\u8ce8\u8ce9\u8cea\u8ceb\u8cec\u8ced\u8cee\u8cef\u8cf0\u8cf1\u8cf2\u8cf3\u8cf4\u8cf5\u8cf6\u8cf7\u8cf8\u8cf9\u8cfa\u8cfb\u8cfc\u8cfd\u8cfe\u8cff\u8d00\u8d01\u8d02\u8d03\u8d04\u8d05\u8d06\u8d07\u8d08\u8d09\u8d0a\u8d0b\u8d0c\u8d0d\u8d0e\u8d0f\u8d10\u8d11\u8d12\u8d13\u8d14\u8d15\u8d16\u8d17\u8d18\u8d19\u8d1a\u8d1b\u8d1c\u8d1d\u8d1e\u8d1f\u8d20\u8d21\u8d22\u8d23\u8d24\u8d25\u8d26\u8d27\u8d28\u8d29\u8d2a\u8d2b\u8d2c\u8d2d\u8d2e\u8d2f\u8d30\u8d31\u8d32\u8d33\u8d34\u8d35\u8d36\u8d37\u8d38\u8d39\u8d3a\u8d3b\u8d3c\u8d3d\u8d3e\u8d3f\u8d40\u8d41\u8d42\u8d43\u8d44\u8d45\u8d46\u8d47\u8d48\u8d49\u8d4a\u8d4b\u8d4c\u8d4d\u8d4e\u8d4f\u8d50\u8d51\u8d52\u8d53\u8d54\u8d55\u8d56\u8d57\u8d58\u8d59\u8d5a\u8d5b\u8d5c\u8d5d\u8d5e\u8d5f\u8d60\u8d61\u8d62\u8d63\u8d64\u8d65\u8d66\u8d67\u8d68\u8d69\u8d6a\u8d6b\u8d6c\u8d6d\u8d6e\u8d6f\u8d70\u8d71\u8d72\u8d73\u8d74\u8d75\u8d76\u8d77\u8d78\u8d79\u8d7a\u8d7b\u8d7c\u8d7d\u8d7e\u8d7f\u8d80\u8d81\u8d82\u8d83\u8d84\u8d85\u8d86\u8d87\u8d88\u8d89\u8d8a\u8d8b\u8d8c\u8d8d\u8d8e\u8d8f\u8d90\u8d91\u8d92\u8d93\u8d94\u8d95\u8d96\u8d97\u8d98\u8d99\u8d9a\u8d9b\u8d9c\u8d9d\u8d9e\u8d9f\u8da0\u8da1\u8da2\u8da3\u8da4\u8da5\u8da6\u8da7\u8da8\u8da9\u8daa\u8dab\u8dac\u8dad\u8dae\u8daf\u8db0\u8db1\u8db2\u8db3\u8db4\u8db5\u8db6\u8db7\u8db8\u8db9\u8dba\u8dbb\u8dbc\u8dbd\u8dbe\u8dbf\u8dc0\u8dc1\u8dc2\u8dc3\u8dc4\u8dc5\u8dc6\u8dc7\u8dc8\u8dc9\u8dca\u8dcb\u8dcc\u8dcd\u8dce\u8dcf\u8dd0\u8dd1\u8dd2\u8dd3\u8dd4\u8dd5\u8dd6\u8dd7\u8dd8\u8dd9\u8dda\u8ddb\u8ddc\u8ddd\u8dde\u8ddf\u8de0\u8de1\u8de2\u8de3\u8de4\u8de5\u8de6\u8de7\u8de8\u8de9\u8dea\u8deb\u8dec\u8ded\u8dee\u8def\u8df0\u8df1\u8df2\u8df3\u8df4\u8df5\u8df6\u8df7\u8df8\u8df9\u8dfa\u8dfb\u8dfc\u8dfd\u8dfe\u8dff\u8e00\u8e01\u8e02\u8e03\u8e04\u8e05\u8e06\u8e07\u8e08\u8e09\u8e0a\u8e0b\u8e0c\u8e0d\u8e0e\u8e0f\u8e10\u8e11\u8e12\u8e13\u8e14\u8e15\u8e16\u8e17\u8e18\u8e19\u8e1a\u8e1b\u8e1c\u8e1d\u8e1e\u8e1f\u8e20\u8e21\u8e22\u8e23\u8e24\u8e25\u8e26\u8e27\u8e28\u8e29\u8e2a\u8e2b\u8e2c\u8e2d\u8e2e\u8e2f\u8e30\u8e31\u8e32\u8e33\u8e34\u8e35\u8e36\u8e37\u8e38\u8e39\u8e3a\u8e3b\u8e3c\u8e3d\u8e3e\u8e3f\u8e40\u8e41\u8e42\u8e43\u8e44\u8e45\u8e46\u8e47\u8e48\u8e49\u8e4a\u8e4b\u8e4c\u8e4d\u8e4e\u8e4f\u8e50\u8e51\u8e52\u8e53\u8e54\u8e55\u8e56\u8e57\u8e58\u8e59\u8e5a\u8e5b\u8e5c\u8e5d\u8e5e\u8e5f\u8e60\u8e61\u8e62\u8e63\u8e64\u8e65\u8e66\u8e67\u8e68\u8e69\u8e6a\u8e6b\u8e6c\u8e6d\u8e6e\u8e6f\u8e70\u8e71\u8e72\u8e73\u8e74\u8e75\u8e76\u8e77\u8e78\u8e79\u8e7a\u8e7b\u8e7c\u8e7d\u8e7e\u8e7f\u8e80\u8e81\u8e82\u8e83\u8e84\u8e85\u8e86\u8e87\u8e88\u8e89\u8e8a\u8e8b\u8e8c\u8e8d\u8e8e\u8e8f\u8e90\u8e91\u8e92\u8e93\u8e94\u8e95\u8e96\u8e97\u8e98\u8e99\u8e9a\u8e9b\u8e9c\u8e9d\u8e9e\u8e9f\u8ea0\u8ea1\u8ea2\u8ea3\u8ea4\u8ea5\u8ea6\u8ea7\u8ea8\u8ea9\u8eaa\u8eab\u8eac\u8ead\u8eae\u8eaf\u8eb0\u8eb1\u8eb2\u8eb3\u8eb4\u8eb5\u8eb6\u8eb7\u8eb8\u8eb9\u8eba\u8ebb\u8ebc\u8ebd\u8ebe\u8ebf\u8ec0\u8ec1\u8ec2\u8ec3\u8ec4\u8ec5\u8ec6\u8ec7\u8ec8\u8ec9\u8eca\u8ecb\u8ecc\u8ecd\u8ece\u8ecf\u8ed0\u8ed1\u8ed2\u8ed3\u8ed4\u8ed5\u8ed6\u8ed7\u8ed8\u8ed9\u8eda\u8edb\u8edc\u8edd\u8ede\u8edf\u8ee0\u8ee1\u8ee2\u8ee3\u8ee4\u8ee5\u8ee6\u8ee7\u8ee8\u8ee9\u8eea\u8eeb\u8eec\u8eed\u8eee\u8eef\u8ef0\u8ef1\u8ef2\u8ef3\u8ef4\u8ef5\u8ef6\u8ef7\u8ef8\u8ef9\u8efa\u8efb\u8efc\u8efd\u8efe\u8eff\u8f00\u8f01\u8f02\u8f03\u8f04\u8f05\u8f06\u8f07\u8f08\u8f09\u8f0a\u8f0b\u8f0c\u8f0d\u8f0e\u8f0f\u8f10\u8f11\u8f12\u8f13\u8f14\u8f15\u8f16\u8f17\u8f18\u8f19\u8f1a\u8f1b\u8f1c\u8f1d\u8f1e\u8f1f\u8f20\u8f21\u8f22\u8f23\u8f24\u8f25\u8f26\u8f27\u8f28\u8f29\u8f2a\u8f2b\u8f2c\u8f2d\u8f2e\u8f2f\u8f30\u8f31\u8f32\u8f33\u8f34\u8f35\u8f36\u8f37\u8f38\u8f39\u8f3a\u8f3b\u8f3c\u8f3d\u8f3e\u8f3f\u8f40\u8f41\u8f42\u8f43\u8f44\u8f45\u8f46\u8f47\u8f48\u8f49\u8f4a\u8f4b\u8f4c\u8f4d\u8f4e\u8f4f\u8f50\u8f51\u8f52\u8f53\u8f54\u8f55\u8f56\u8f57\u8f58\u8f59\u8f5a\u8f5b\u8f5c\u8f5d\u8f5e\u8f5f\u8f60\u8f61\u8f62\u8f63\u8f64\u8f65\u8f66\u8f67\u8f68\u8f69\u8f6a\u8f6b\u8f6c\u8f6d\u8f6e\u8f6f\u8f70\u8f71\u8f72\u8f73\u8f74\u8f75\u8f76\u8f77\u8f78\u8f79\u8f7a\u8f7b\u8f7c\u8f7d\u8f7e\u8f7f\u8f80\u8f81\u8f82\u8f83\u8f84\u8f85\u8f86\u8f87\u8f88\u8f89\u8f8a\u8f8b\u8f8c\u8f8d\u8f8e\u8f8f\u8f90\u8f91\u8f92\u8f93\u8f94\u8f95\u8f96\u8f97\u8f98\u8f99\u8f9a\u8f9b\u8f9c\u8f9d\u8f9e\u8f9f\u8fa0\u8fa1\u8fa2\u8fa3\u8fa4\u8fa5\u8fa6\u8fa7\u8fa8\u8fa9\u8faa\u8fab\u8fac\u8fad\u8fae\u8faf\u8fb0\u8fb1\u8fb2\u8fb3\u8fb4\u8fb5\u8fb6\u8fb7\u8fb8\u8fb9\u8fba\u8fbb\u8fbc\u8fbd\u8fbe\u8fbf\u8fc0\u8fc1\u8fc2\u8fc3\u8fc4\u8fc5\u8fc6\u8fc7\u8fc8\u8fc9\u8fca\u8fcb\u8fcc\u8fcd\u8fce\u8fcf\u8fd0\u8fd1\u8fd2\u8fd3\u8fd4\u8fd5\u8fd6\u8fd7\u8fd8\u8fd9\u8fda\u8fdb\u8fdc\u8fdd\u8fde\u8fdf\u8fe0\u8fe1\u8fe2\u8fe3\u8fe4\u8fe5\u8fe6\u8fe7\u8fe8\u8fe9\u8fea\u8feb\u8fec\u8fed\u8fee\u8fef\u8ff0\u8ff1\u8ff2\u8ff3\u8ff4\u8ff5\u8ff6\u8ff7\u8ff8\u8ff9\u8ffa\u8ffb\u8ffc\u8ffd\u8ffe\u8fff\u9000\u9001\u9002\u9003\u9004\u9005\u9006\u9007\u9008\u9009\u900a\u900b\u900c\u900d\u900e\u900f\u9010\u9011\u9012\u9013\u9014\u9015\u9016\u9017\u9018\u9019\u901a\u901b\u901c\u901d\u901e\u901f\u9020\u9021\u9022\u9023\u9024\u9025\u9026\u9027\u9028\u9029\u902a\u902b\u902c\u902d\u902e\u902f\u9030\u9031\u9032\u9033\u9034\u9035\u9036\u9037\u9038\u9039\u903a\u903b\u903c\u903d\u903e\u903f\u9040\u9041\u9042\u9043\u9044\u9045\u9046\u9047\u9048\u9049\u904a\u904b\u904c\u904d\u904e\u904f\u9050\u9051\u9052\u9053\u9054\u9055\u9056\u9057\u9058\u9059\u905a\u905b\u905c\u905d\u905e\u905f\u9060\u9061\u9062\u9063\u9064\u9065\u9066\u9067\u9068\u9069\u906a\u906b\u906c\u906d\u906e\u906f\u9070\u9071\u9072\u9073\u9074\u9075\u9076\u9077\u9078\u9079\u907a\u907b\u907c\u907d\u907e\u907f\u9080\u9081\u9082\u9083\u9084\u9085\u9086\u9087\u9088\u9089\u908a\u908b\u908c\u908d\u908e\u908f\u9090\u9091\u9092\u9093\u9094\u9095\u9096\u9097\u9098\u9099\u909a\u909b\u909c\u909d\u909e\u909f\u90a0\u90a1\u90a2\u90a3\u90a4\u90a5\u90a6\u90a7\u90a8\u90a9\u90aa\u90ab\u90ac\u90ad\u90ae\u90af\u90b0\u90b1\u90b2\u90b3\u90b4\u90b5\u90b6\u90b7\u90b8\u90b9\u90ba\u90bb\u90bc\u90bd\u90be\u90bf\u90c0\u90c1\u90c2\u90c3\u90c4\u90c5\u90c6\u90c7\u90c8\u90c9\u90ca\u90cb\u90cc\u90cd\u90ce\u90cf\u90d0\u90d1\u90d2\u90d3\u90d4\u90d5\u90d6\u90d7\u90d8\u90d9\u90da\u90db\u90dc\u90dd\u90de\u90df\u90e0\u90e1\u90e2\u90e3\u90e4\u90e5\u90e6\u90e7\u90e8\u90e9\u90ea\u90eb\u90ec\u90ed\u90ee\u90ef\u90f0\u90f1\u90f2\u90f3\u90f4\u90f5\u90f6\u90f7\u90f8\u90f9\u90fa\u90fb\u90fc\u90fd\u90fe\u90ff\u9100\u9101\u9102\u9103\u9104\u9105\u9106\u9107\u9108\u9109\u910a\u910b\u910c\u910d\u910e\u910f\u9110\u9111\u9112\u9113\u9114\u9115\u9116\u9117\u9118\u9119\u911a\u911b\u911c\u911d\u911e\u911f\u9120\u9121\u9122\u9123\u9124\u9125\u9126\u9127\u9128\u9129\u912a\u912b\u912c\u912d\u912e\u912f\u9130\u9131\u9132\u9133\u9134\u9135\u9136\u9137\u9138\u9139\u913a\u913b\u913c\u913d\u913e\u913f\u9140\u9141\u9142\u9143\u9144\u9145\u9146\u9147\u9148\u9149\u914a\u914b\u914c\u914d\u914e\u914f\u9150\u9151\u9152\u9153\u9154\u9155\u9156\u9157\u9158\u9159\u915a\u915b\u915c\u915d\u915e\u915f\u9160\u9161\u9162\u9163\u9164\u9165\u9166\u9167\u9168\u9169\u916a\u916b\u916c\u916d\u916e\u916f\u9170\u9171\u9172\u9173\u9174\u9175\u9176\u9177\u9178\u9179\u917a\u917b\u917c\u917d\u917e\u917f\u9180\u9181\u9182\u9183\u9184\u9185\u9186\u9187\u9188\u9189\u918a\u918b\u918c\u918d\u918e\u918f\u9190\u9191\u9192\u9193\u9194\u9195\u9196\u9197\u9198\u9199\u919a\u919b\u919c\u919d\u919e\u919f\u91a0\u91a1\u91a2\u91a3\u91a4\u91a5\u91a6\u91a7\u91a8\u91a9\u91aa\u91ab\u91ac\u91ad\u91ae\u91af\u91b0\u91b1\u91b2\u91b3\u91b4\u91b5\u91b6\u91b7\u91b8\u91b9\u91ba\u91bb\u91bc\u91bd\u91be\u91bf\u91c0\u91c1\u91c2\u91c3\u91c4\u91c5\u91c6\u91c7\u91c8\u91c9\u91ca\u91cb\u91cc\u91cd\u91ce\u91cf\u91d0\u91d1\u91d2\u91d3\u91d4\u91d5\u91d6\u91d7\u91d8\u91d9\u91da\u91db\u91dc\u91dd\u91de\u91df\u91e0\u91e1\u91e2\u91e3\u91e4\u91e5\u91e6\u91e7\u91e8\u91e9\u91ea\u91eb\u91ec\u91ed\u91ee\u91ef\u91f0\u91f1\u91f2\u91f3\u91f4\u91f5\u91f6\u91f7\u91f8\u91f9\u91fa\u91fb\u91fc\u91fd\u91fe\u91ff\u9200\u9201\u9202\u9203\u9204\u9205\u9206\u9207\u9208\u9209\u920a\u920b\u920c\u920d\u920e\u920f\u9210\u9211\u9212\u9213\u9214\u9215\u9216\u9217\u9218\u9219\u921a\u921b\u921c\u921d\u921e\u921f\u9220\u9221\u9222\u9223\u9224\u9225\u9226\u9227\u9228\u9229\u922a\u922b\u922c\u922d\u922e\u922f\u9230\u9231\u9232\u9233\u9234\u9235\u9236\u9237\u9238\u9239\u923a\u923b\u923c\u923d\u923e\u923f\u9240\u9241\u9242\u9243\u9244\u9245\u9246\u9247\u9248\u9249\u924a\u924b\u924c\u924d\u924e\u924f\u9250\u9251\u9252\u9253\u9254\u9255\u9256\u9257\u9258\u9259\u925a\u925b\u925c\u925d\u925e\u925f\u9260\u9261\u9262\u9263\u9264\u9265\u9266\u9267\u9268\u9269\u926a\u926b\u926c\u926d\u926e\u926f\u9270\u9271\u9272\u9273\u9274\u9275\u9276\u9277\u9278\u9279\u927a\u927b\u927c\u927d\u927e\u927f\u9280\u9281\u9282\u9283\u9284\u9285\u9286\u9287\u9288\u9289\u928a\u928b\u928c\u928d\u928e\u928f\u9290\u9291\u9292\u9293\u9294\u9295\u9296\u9297\u9298\u9299\u929a\u929b\u929c\u929d\u929e\u929f\u92a0\u92a1\u92a2\u92a3\u92a4\u92a5\u92a6\u92a7\u92a8\u92a9\u92aa\u92ab\u92ac\u92ad\u92ae\u92af\u92b0\u92b1\u92b2\u92b3\u92b4\u92b5\u92b6\u92b7\u92b8\u92b9\u92ba\u92bb\u92bc\u92bd\u92be\u92bf\u92c0\u92c1\u92c2\u92c3\u92c4\u92c5\u92c6\u92c7\u92c8\u92c9\u92ca\u92cb\u92cc\u92cd\u92ce\u92cf\u92d0\u92d1\u92d2\u92d3\u92d4\u92d5\u92d6\u92d7\u92d8\u92d9\u92da\u92db\u92dc\u92dd\u92de\u92df\u92e0\u92e1\u92e2\u92e3\u92e4\u92e5\u92e6\u92e7\u92e8\u92e9\u92ea\u92eb\u92ec\u92ed\u92ee\u92ef\u92f0\u92f1\u92f2\u92f3\u92f4\u92f5\u92f6\u92f7\u92f8\u92f9\u92fa\u92fb\u92fc\u92fd\u92fe\u92ff\u9300\u9301\u9302\u9303\u9304\u9305\u9306\u9307\u9308\u9309\u930a\u930b\u930c\u930d\u930e\u930f\u9310\u9311\u9312\u9313\u9314\u9315\u9316\u9317\u9318\u9319\u931a\u931b\u931c\u931d\u931e\u931f\u9320\u9321\u9322\u9323\u9324\u9325\u9326\u9327\u9328\u9329\u932a\u932b\u932c\u932d\u932e\u932f\u9330\u9331\u9332\u9333\u9334\u9335\u9336\u9337\u9338\u9339\u933a\u933b\u933c\u933d\u933e\u933f\u9340\u9341\u9342\u9343\u9344\u9345\u9346\u9347\u9348\u9349\u934a\u934b\u934c\u934d\u934e\u934f\u9350\u9351\u9352\u9353\u9354\u9355\u9356\u9357\u9358\u9359\u935a\u935b\u935c\u935d\u935e\u935f\u9360\u9361\u9362\u9363\u9364\u9365\u9366\u9367\u9368\u9369\u936a\u936b\u936c\u936d\u936e\u936f\u9370\u9371\u9372\u9373\u9374\u9375\u9376\u9377\u9378\u9379\u937a\u937b\u937c\u937d\u937e\u937f\u9380\u9381\u9382\u9383\u9384\u9385\u9386\u9387\u9388\u9389\u938a\u938b\u938c\u938d\u938e\u938f\u9390\u9391\u9392\u9393\u9394\u9395\u9396\u9397\u9398\u9399\u939a\u939b\u939c\u939d\u939e\u939f\u93a0\u93a1\u93a2\u93a3\u93a4\u93a5\u93a6\u93a7\u93a8\u93a9\u93aa\u93ab\u93ac\u93ad\u93ae\u93af\u93b0\u93b1\u93b2\u93b3\u93b4\u93b5\u93b6\u93b7\u93b8\u93b9\u93ba\u93bb\u93bc\u93bd\u93be\u93bf\u93c0\u93c1\u93c2\u93c3\u93c4\u93c5\u93c6\u93c7\u93c8\u93c9\u93ca\u93cb\u93cc\u93cd\u93ce\u93cf\u93d0\u93d1\u93d2\u93d3\u93d4\u93d5\u93d6\u93d7\u93d8\u93d9\u93da\u93db\u93dc\u93dd\u93de\u93df\u93e0\u93e1\u93e2\u93e3\u93e4\u93e5\u93e6\u93e7\u93e8\u93e9\u93ea\u93eb\u93ec\u93ed\u93ee\u93ef\u93f0\u93f1\u93f2\u93f3\u93f4\u93f5\u93f6\u93f7\u93f8\u93f9\u93fa\u93fb\u93fc\u93fd\u93fe\u93ff\u9400\u9401\u9402\u9403\u9404\u9405\u9406\u9407\u9408\u9409\u940a\u940b\u940c\u940d\u940e\u940f\u9410\u9411\u9412\u9413\u9414\u9415\u9416\u9417\u9418\u9419\u941a\u941b\u941c\u941d\u941e\u941f\u9420\u9421\u9422\u9423\u9424\u9425\u9426\u9427\u9428\u9429\u942a\u942b\u942c\u942d\u942e\u942f\u9430\u9431\u9432\u9433\u9434\u9435\u9436\u9437\u9438\u9439\u943a\u943b\u943c\u943d\u943e\u943f\u9440\u9441\u9442\u9443\u9444\u9445\u9446\u9447\u9448\u9449\u944a\u944b\u944c\u944d\u944e\u944f\u9450\u9451\u9452\u9453\u9454\u9455\u9456\u9457\u9458\u9459\u945a\u945b\u945c\u945d\u945e\u945f\u9460\u9461\u9462\u9463\u9464\u9465\u9466\u9467\u9468\u9469\u946a\u946b\u946c\u946d\u946e\u946f\u9470\u9471\u9472\u9473\u9474\u9475\u9476\u9477\u9478\u9479\u947a\u947b\u947c\u947d\u947e\u947f\u9480\u9481\u9482\u9483\u9484\u9485\u9486\u9487\u9488\u9489\u948a\u948b\u948c\u948d\u948e\u948f\u9490\u9491\u9492\u9493\u9494\u9495\u9496\u9497\u9498\u9499\u949a\u949b\u949c\u949d\u949e\u949f\u94a0\u94a1\u94a2\u94a3\u94a4\u94a5\u94a6\u94a7\u94a8\u94a9\u94aa\u94ab\u94ac\u94ad\u94ae\u94af\u94b0\u94b1\u94b2\u94b3\u94b4\u94b5\u94b6\u94b7\u94b8\u94b9\u94ba\u94bb\u94bc\u94bd\u94be\u94bf\u94c0\u94c1\u94c2\u94c3\u94c4\u94c5\u94c6\u94c7\u94c8\u94c9\u94ca\u94cb\u94cc\u94cd\u94ce\u94cf\u94d0\u94d1\u94d2\u94d3\u94d4\u94d5\u94d6\u94d7\u94d8\u94d9\u94da\u94db\u94dc\u94dd\u94de\u94df\u94e0\u94e1\u94e2\u94e3\u94e4\u94e5\u94e6\u94e7\u94e8\u94e9\u94ea\u94eb\u94ec\u94ed\u94ee\u94ef\u94f0\u94f1\u94f2\u94f3\u94f4\u94f5\u94f6\u94f7\u94f8\u94f9\u94fa\u94fb\u94fc\u94fd\u94fe\u94ff\u9500\u9501\u9502\u9503\u9504\u9505\u9506\u9507\u9508\u9509\u950a\u950b\u950c\u950d\u950e\u950f\u9510\u9511\u9512\u9513\u9514\u9515\u9516\u9517\u9518\u9519\u951a\u951b\u951c\u951d\u951e\u951f\u9520\u9521\u9522\u9523\u9524\u9525\u9526\u9527\u9528\u9529\u952a\u952b\u952c\u952d\u952e\u952f\u9530\u9531\u9532\u9533\u9534\u9535\u9536\u9537\u9538\u9539\u953a\u953b\u953c\u953d\u953e\u953f\u9540\u9541\u9542\u9543\u9544\u9545\u9546\u9547\u9548\u9549\u954a\u954b\u954c\u954d\u954e\u954f\u9550\u9551\u9552\u9553\u9554\u9555\u9556\u9557\u9558\u9559\u955a\u955b\u955c\u955d\u955e\u955f\u9560\u9561\u9562\u9563\u9564\u9565\u9566\u9567\u9568\u9569\u956a\u956b\u956c\u956d\u956e\u956f\u9570\u9571\u9572\u9573\u9574\u9575\u9576\u9577\u9578\u9579\u957a\u957b\u957c\u957d\u957e\u957f\u9580\u9581\u9582\u9583\u9584\u9585\u9586\u9587\u9588\u9589\u958a\u958b\u958c\u958d\u958e\u958f\u9590\u9591\u9592\u9593\u9594\u9595\u9596\u9597\u9598\u9599\u959a\u959b\u959c\u959d\u959e\u959f\u95a0\u95a1\u95a2\u95a3\u95a4\u95a5\u95a6\u95a7\u95a8\u95a9\u95aa\u95ab\u95ac\u95ad\u95ae\u95af\u95b0\u95b1\u95b2\u95b3\u95b4\u95b5\u95b6\u95b7\u95b8\u95b9\u95ba\u95bb\u95bc\u95bd\u95be\u95bf\u95c0\u95c1\u95c2\u95c3\u95c4\u95c5\u95c6\u95c7\u95c8\u95c9\u95ca\u95cb\u95cc\u95cd\u95ce\u95cf\u95d0\u95d1\u95d2\u95d3\u95d4\u95d5\u95d6\u95d7\u95d8\u95d9\u95da\u95db\u95dc\u95dd\u95de\u95df\u95e0\u95e1\u95e2\u95e3\u95e4\u95e5\u95e6\u95e7\u95e8\u95e9\u95ea\u95eb\u95ec\u95ed\u95ee\u95ef\u95f0\u95f1\u95f2\u95f3\u95f4\u95f5\u95f6\u95f7\u95f8\u95f9\u95fa\u95fb\u95fc\u95fd\u95fe\u95ff\u9600\u9601\u9602\u9603\u9604\u9605\u9606\u9607\u9608\u9609\u960a\u960b\u960c\u960d\u960e\u960f\u9610\u9611\u9612\u9613\u9614\u9615\u9616\u9617\u9618\u9619\u961a\u961b\u961c\u961d\u961e\u961f\u9620\u9621\u9622\u9623\u9624\u9625\u9626\u9627\u9628\u9629\u962a\u962b\u962c\u962d\u962e\u962f\u9630\u9631\u9632\u9633\u9634\u9635\u9636\u9637\u9638\u9639\u963a\u963b\u963c\u963d\u963e\u963f\u9640\u9641\u9642\u9643\u9644\u9645\u9646\u9647\u9648\u9649\u964a\u964b\u964c\u964d\u964e\u964f\u9650\u9651\u9652\u9653\u9654\u9655\u9656\u9657\u9658\u9659\u965a\u965b\u965c\u965d\u965e\u965f\u9660\u9661\u9662\u9663\u9664\u9665\u9666\u9667\u9668\u9669\u966a\u966b\u966c\u966d\u966e\u966f\u9670\u9671\u9672\u9673\u9674\u9675\u9676\u9677\u9678\u9679\u967a\u967b\u967c\u967d\u967e\u967f\u9680\u9681\u9682\u9683\u9684\u9685\u9686\u9687\u9688\u9689\u968a\u968b\u968c\u968d\u968e\u968f\u9690\u9691\u9692\u9693\u9694\u9695\u9696\u9697\u9698\u9699\u969a\u969b\u969c\u969d\u969e\u969f\u96a0\u96a1\u96a2\u96a3\u96a4\u96a5\u96a6\u96a7\u96a8\u96a9\u96aa\u96ab\u96ac\u96ad\u96ae\u96af\u96b0\u96b1\u96b2\u96b3\u96b4\u96b5\u96b6\u96b7\u96b8\u96b9\u96ba\u96bb\u96bc\u96bd\u96be\u96bf\u96c0\u96c1\u96c2\u96c3\u96c4\u96c5\u96c6\u96c7\u96c8\u96c9\u96ca\u96cb\u96cc\u96cd\u96ce\u96cf\u96d0\u96d1\u96d2\u96d3\u96d4\u96d5\u96d6\u96d7\u96d8\u96d9\u96da\u96db\u96dc\u96dd\u96de\u96df\u96e0\u96e1\u96e2\u96e3\u96e4\u96e5\u96e6\u96e7\u96e8\u96e9\u96ea\u96eb\u96ec\u96ed\u96ee\u96ef\u96f0\u96f1\u96f2\u96f3\u96f4\u96f5\u96f6\u96f7\u96f8\u96f9\u96fa\u96fb\u96fc\u96fd\u96fe\u96ff\u9700\u9701\u9702\u9703\u9704\u9705\u9706\u9707\u9708\u9709\u970a\u970b\u970c\u970d\u970e\u970f\u9710\u9711\u9712\u9713\u9714\u9715\u9716\u9717\u9718\u9719\u971a\u971b\u971c\u971d\u971e\u971f\u9720\u9721\u9722\u9723\u9724\u9725\u9726\u9727\u9728\u9729\u972a\u972b\u972c\u972d\u972e\u972f\u9730\u9731\u9732\u9733\u9734\u9735\u9736\u9737\u9738\u9739\u973a\u973b\u973c\u973d\u973e\u973f\u9740\u9741\u9742\u9743\u9744\u9745\u9746\u9747\u9748\u9749\u974a\u974b\u974c\u974d\u974e\u974f\u9750\u9751\u9752\u9753\u9754\u9755\u9756\u9757\u9758\u9759\u975a\u975b\u975c\u975d\u975e\u975f\u9760\u9761\u9762\u9763\u9764\u9765\u9766\u9767\u9768\u9769\u976a\u976b\u976c\u976d\u976e\u976f\u9770\u9771\u9772\u9773\u9774\u9775\u9776\u9777\u9778\u9779\u977a\u977b\u977c\u977d\u977e\u977f\u9780\u9781\u9782\u9783\u9784\u9785\u9786\u9787\u9788\u9789\u978a\u978b\u978c\u978d\u978e\u978f\u9790\u9791\u9792\u9793\u9794\u9795\u9796\u9797\u9798\u9799\u979a\u979b\u979c\u979d\u979e\u979f\u97a0\u97a1\u97a2\u97a3\u97a4\u97a5\u97a6\u97a7\u97a8\u97a9\u97aa\u97ab\u97ac\u97ad\u97ae\u97af\u97b0\u97b1\u97b2\u97b3\u97b4\u97b5\u97b6\u97b7\u97b8\u97b9\u97ba\u97bb\u97bc\u97bd\u97be\u97bf\u97c0\u97c1\u97c2\u97c3\u97c4\u97c5\u97c6\u97c7\u97c8\u97c9\u97ca\u97cb\u97cc\u97cd\u97ce\u97cf\u97d0\u97d1\u97d2\u97d3\u97d4\u97d5\u97d6\u97d7\u97d8\u97d9\u97da\u97db\u97dc\u97dd\u97de\u97df\u97e0\u97e1\u97e2\u97e3\u97e4\u97e5\u97e6\u97e7\u97e8\u97e9\u97ea\u97eb\u97ec\u97ed\u97ee\u97ef\u97f0\u97f1\u97f2\u97f3\u97f4\u97f5\u97f6\u97f7\u97f8\u97f9\u97fa\u97fb\u97fc\u97fd\u97fe\u97ff\u9800\u9801\u9802\u9803\u9804\u9805\u9806\u9807\u9808\u9809\u980a\u980b\u980c\u980d\u980e\u980f\u9810\u9811\u9812\u9813\u9814\u9815\u9816\u9817\u9818\u9819\u981a\u981b\u981c\u981d\u981e\u981f\u9820\u9821\u9822\u9823\u9824\u9825\u9826\u9827\u9828\u9829\u982a\u982b\u982c\u982d\u982e\u982f\u9830\u9831\u9832\u9833\u9834\u9835\u9836\u9837\u9838\u9839\u983a\u983b\u983c\u983d\u983e\u983f\u9840\u9841\u9842\u9843\u9844\u9845\u9846\u9847\u9848\u9849\u984a\u984b\u984c\u984d\u984e\u984f\u9850\u9851\u9852\u9853\u9854\u9855\u9856\u9857\u9858\u9859\u985a\u985b\u985c\u985d\u985e\u985f\u9860\u9861\u9862\u9863\u9864\u9865\u9866\u9867\u9868\u9869\u986a\u986b\u986c\u986d\u986e\u986f\u9870\u9871\u9872\u9873\u9874\u9875\u9876\u9877\u9878\u9879\u987a\u987b\u987c\u987d\u987e\u987f\u9880\u9881\u9882\u9883\u9884\u9885\u9886\u9887\u9888\u9889\u988a\u988b\u988c\u988d\u988e\u988f\u9890\u9891\u9892\u9893\u9894\u9895\u9896\u9897\u9898\u9899\u989a\u989b\u989c\u989d\u989e\u989f\u98a0\u98a1\u98a2\u98a3\u98a4\u98a5\u98a6\u98a7\u98a8\u98a9\u98aa\u98ab\u98ac\u98ad\u98ae\u98af\u98b0\u98b1\u98b2\u98b3\u98b4\u98b5\u98b6\u98b7\u98b8\u98b9\u98ba\u98bb\u98bc\u98bd\u98be\u98bf\u98c0\u98c1\u98c2\u98c3\u98c4\u98c5\u98c6\u98c7\u98c8\u98c9\u98ca\u98cb\u98cc\u98cd\u98ce\u98cf\u98d0\u98d1\u98d2\u98d3\u98d4\u98d5\u98d6\u98d7\u98d8\u98d9\u98da\u98db\u98dc\u98dd\u98de\u98df\u98e0\u98e1\u98e2\u98e3\u98e4\u98e5\u98e6\u98e7\u98e8\u98e9\u98ea\u98eb\u98ec\u98ed\u98ee\u98ef\u98f0\u98f1\u98f2\u98f3\u98f4\u98f5\u98f6\u98f7\u98f8\u98f9\u98fa\u98fb\u98fc\u98fd\u98fe\u98ff\u9900\u9901\u9902\u9903\u9904\u9905\u9906\u9907\u9908\u9909\u990a\u990b\u990c\u990d\u990e\u990f\u9910\u9911\u9912\u9913\u9914\u9915\u9916\u9917\u9918\u9919\u991a\u991b\u991c\u991d\u991e\u991f\u9920\u9921\u9922\u9923\u9924\u9925\u9926\u9927\u9928\u9929\u992a\u992b\u992c\u992d\u992e\u992f\u9930\u9931\u9932\u9933\u9934\u9935\u9936\u9937\u9938\u9939\u993a\u993b\u993c\u993d\u993e\u993f\u9940\u9941\u9942\u9943\u9944\u9945\u9946\u9947\u9948\u9949\u994a\u994b\u994c\u994d\u994e\u994f\u9950\u9951\u9952\u9953\u9954\u9955\u9956\u9957\u9958\u9959\u995a\u995b\u995c\u995d\u995e\u995f\u9960\u9961\u9962\u9963\u9964\u9965\u9966\u9967\u9968\u9969\u996a\u996b\u996c\u996d\u996e\u996f\u9970\u9971\u9972\u9973\u9974\u9975\u9976\u9977\u9978\u9979\u997a\u997b\u997c\u997d\u997e\u997f\u9980\u9981\u9982\u9983\u9984\u9985\u9986\u9987\u9988\u9989\u998a\u998b\u998c\u998d\u998e\u998f\u9990\u9991\u9992\u9993\u9994\u9995\u9996\u9997\u9998\u9999\u999a\u999b\u999c\u999d\u999e\u999f\u99a0\u99a1\u99a2\u99a3\u99a4\u99a5\u99a6\u99a7\u99a8\u99a9\u99aa\u99ab\u99ac\u99ad\u99ae\u99af\u99b0\u99b1\u99b2\u99b3\u99b4\u99b5\u99b6\u99b7\u99b8\u99b9\u99ba\u99bb\u99bc\u99bd\u99be\u99bf\u99c0\u99c1\u99c2\u99c3\u99c4\u99c5\u99c6\u99c7\u99c8\u99c9\u99ca\u99cb\u99cc\u99cd\u99ce\u99cf\u99d0\u99d1\u99d2\u99d3\u99d4\u99d5\u99d6\u99d7\u99d8\u99d9\u99da\u99db\u99dc\u99dd\u99de\u99df\u99e0\u99e1\u99e2\u99e3\u99e4\u99e5\u99e6\u99e7\u99e8\u99e9\u99ea\u99eb\u99ec\u99ed\u99ee\u99ef\u99f0\u99f1\u99f2\u99f3\u99f4\u99f5\u99f6\u99f7\u99f8\u99f9\u99fa\u99fb\u99fc\u99fd\u99fe\u99ff\u9a00\u9a01\u9a02\u9a03\u9a04\u9a05\u9a06\u9a07\u9a08\u9a09\u9a0a\u9a0b\u9a0c\u9a0d\u9a0e\u9a0f\u9a10\u9a11\u9a12\u9a13\u9a14\u9a15\u9a16\u9a17\u9a18\u9a19\u9a1a\u9a1b\u9a1c\u9a1d\u9a1e\u9a1f\u9a20\u9a21\u9a22\u9a23\u9a24\u9a25\u9a26\u9a27\u9a28\u9a29\u9a2a\u9a2b\u9a2c\u9a2d\u9a2e\u9a2f\u9a30\u9a31\u9a32\u9a33\u9a34\u9a35\u9a36\u9a37\u9a38\u9a39\u9a3a\u9a3b\u9a3c\u9a3d\u9a3e\u9a3f\u9a40\u9a41\u9a42\u9a43\u9a44\u9a45\u9a46\u9a47\u9a48\u9a49\u9a4a\u9a4b\u9a4c\u9a4d\u9a4e\u9a4f\u9a50\u9a51\u9a52\u9a53\u9a54\u9a55\u9a56\u9a57\u9a58\u9a59\u9a5a\u9a5b\u9a5c\u9a5d\u9a5e\u9a5f\u9a60\u9a61\u9a62\u9a63\u9a64\u9a65\u9a66\u9a67\u9a68\u9a69\u9a6a\u9a6b\u9a6c\u9a6d\u9a6e\u9a6f\u9a70\u9a71\u9a72\u9a73\u9a74\u9a75\u9a76\u9a77\u9a78\u9a79\u9a7a\u9a7b\u9a7c\u9a7d\u9a7e\u9a7f\u9a80\u9a81\u9a82\u9a83\u9a84\u9a85\u9a86\u9a87\u9a88\u9a89\u9a8a\u9a8b\u9a8c\u9a8d\u9a8e\u9a8f\u9a90\u9a91\u9a92\u9a93\u9a94\u9a95\u9a96\u9a97\u9a98\u9a99\u9a9a\u9a9b\u9a9c\u9a9d\u9a9e\u9a9f\u9aa0\u9aa1\u9aa2\u9aa3\u9aa4\u9aa5\u9aa6\u9aa7\u9aa8\u9aa9\u9aaa\u9aab\u9aac\u9aad\u9aae\u9aaf\u9ab0\u9ab1\u9ab2\u9ab3\u9ab4\u9ab5\u9ab6\u9ab7\u9ab8\u9ab9\u9aba\u9abb\u9abc\u9abd\u9abe\u9abf\u9ac0\u9ac1\u9ac2\u9ac3\u9ac4\u9ac5\u9ac6\u9ac7\u9ac8\u9ac9\u9aca\u9acb\u9acc\u9acd\u9ace\u9acf\u9ad0\u9ad1\u9ad2\u9ad3\u9ad4\u9ad5\u9ad6\u9ad7\u9ad8\u9ad9\u9ada\u9adb\u9adc\u9add\u9ade\u9adf\u9ae0\u9ae1\u9ae2\u9ae3\u9ae4\u9ae5\u9ae6\u9ae7\u9ae8\u9ae9\u9aea\u9aeb\u9aec\u9aed\u9aee\u9aef\u9af0\u9af1\u9af2\u9af3\u9af4\u9af5\u9af6\u9af7\u9af8\u9af9\u9afa\u9afb\u9afc\u9afd\u9afe\u9aff\u9b00\u9b01\u9b02\u9b03\u9b04\u9b05\u9b06\u9b07\u9b08\u9b09\u9b0a\u9b0b\u9b0c\u9b0d\u9b0e\u9b0f\u9b10\u9b11\u9b12\u9b13\u9b14\u9b15\u9b16\u9b17\u9b18\u9b19\u9b1a\u9b1b\u9b1c\u9b1d\u9b1e\u9b1f\u9b20\u9b21\u9b22\u9b23\u9b24\u9b25\u9b26\u9b27\u9b28\u9b29\u9b2a\u9b2b\u9b2c\u9b2d\u9b2e\u9b2f\u9b30\u9b31\u9b32\u9b33\u9b34\u9b35\u9b36\u9b37\u9b38\u9b39\u9b3a\u9b3b\u9b3c\u9b3d\u9b3e\u9b3f\u9b40\u9b41\u9b42\u9b43\u9b44\u9b45\u9b46\u9b47\u9b48\u9b49\u9b4a\u9b4b\u9b4c\u9b4d\u9b4e\u9b4f\u9b50\u9b51\u9b52\u9b53\u9b54\u9b55\u9b56\u9b57\u9b58\u9b59\u9b5a\u9b5b\u9b5c\u9b5d\u9b5e\u9b5f\u9b60\u9b61\u9b62\u9b63\u9b64\u9b65\u9b66\u9b67\u9b68\u9b69\u9b6a\u9b6b\u9b6c\u9b6d\u9b6e\u9b6f\u9b70\u9b71\u9b72\u9b73\u9b74\u9b75\u9b76\u9b77\u9b78\u9b79\u9b7a\u9b7b\u9b7c\u9b7d\u9b7e\u9b7f\u9b80\u9b81\u9b82\u9b83\u9b84\u9b85\u9b86\u9b87\u9b88\u9b89\u9b8a\u9b8b\u9b8c\u9b8d\u9b8e\u9b8f\u9b90\u9b91\u9b92\u9b93\u9b94\u9b95\u9b96\u9b97\u9b98\u9b99\u9b9a\u9b9b\u9b9c\u9b9d\u9b9e\u9b9f\u9ba0\u9ba1\u9ba2\u9ba3\u9ba4\u9ba5\u9ba6\u9ba7\u9ba8\u9ba9\u9baa\u9bab\u9bac\u9bad\u9bae\u9baf\u9bb0\u9bb1\u9bb2\u9bb3\u9bb4\u9bb5\u9bb6\u9bb7\u9bb8\u9bb9\u9bba\u9bbb\u9bbc\u9bbd\u9bbe\u9bbf\u9bc0\u9bc1\u9bc2\u9bc3\u9bc4\u9bc5\u9bc6\u9bc7\u9bc8\u9bc9\u9bca\u9bcb\u9bcc\u9bcd\u9bce\u9bcf\u9bd0\u9bd1\u9bd2\u9bd3\u9bd4\u9bd5\u9bd6\u9bd7\u9bd8\u9bd9\u9bda\u9bdb\u9bdc\u9bdd\u9bde\u9bdf\u9be0\u9be1\u9be2\u9be3\u9be4\u9be5\u9be6\u9be7\u9be8\u9be9\u9bea\u9beb\u9bec\u9bed\u9bee\u9bef\u9bf0\u9bf1\u9bf2\u9bf3\u9bf4\u9bf5\u9bf6\u9bf7\u9bf8\u9bf9\u9bfa\u9bfb\u9bfc\u9bfd\u9bfe\u9bff\u9c00\u9c01\u9c02\u9c03\u9c04\u9c05\u9c06\u9c07\u9c08\u9c09\u9c0a\u9c0b\u9c0c\u9c0d\u9c0e\u9c0f\u9c10\u9c11\u9c12\u9c13\u9c14\u9c15\u9c16\u9c17\u9c18\u9c19\u9c1a\u9c1b\u9c1c\u9c1d\u9c1e\u9c1f\u9c20\u9c21\u9c22\u9c23\u9c24\u9c25\u9c26\u9c27\u9c28\u9c29\u9c2a\u9c2b\u9c2c\u9c2d\u9c2e\u9c2f\u9c30\u9c31\u9c32\u9c33\u9c34\u9c35\u9c36\u9c37\u9c38\u9c39\u9c3a\u9c3b\u9c3c\u9c3d\u9c3e\u9c3f\u9c40\u9c41\u9c42\u9c43\u9c44\u9c45\u9c46\u9c47\u9c48\u9c49\u9c4a\u9c4b\u9c4c\u9c4d\u9c4e\u9c4f\u9c50\u9c51\u9c52\u9c53\u9c54\u9c55\u9c56\u9c57\u9c58\u9c59\u9c5a\u9c5b\u9c5c\u9c5d\u9c5e\u9c5f\u9c60\u9c61\u9c62\u9c63\u9c64\u9c65\u9c66\u9c67\u9c68\u9c69\u9c6a\u9c6b\u9c6c\u9c6d\u9c6e\u9c6f\u9c70\u9c71\u9c72\u9c73\u9c74\u9c75\u9c76\u9c77\u9c78\u9c79\u9c7a\u9c7b\u9c7c\u9c7d\u9c7e\u9c7f\u9c80\u9c81\u9c82\u9c83\u9c84\u9c85\u9c86\u9c87\u9c88\u9c89\u9c8a\u9c8b\u9c8c\u9c8d\u9c8e\u9c8f\u9c90\u9c91\u9c92\u9c93\u9c94\u9c95\u9c96\u9c97\u9c98\u9c99\u9c9a\u9c9b\u9c9c\u9c9d\u9c9e\u9c9f\u9ca0\u9ca1\u9ca2\u9ca3\u9ca4\u9ca5\u9ca6\u9ca7\u9ca8\u9ca9\u9caa\u9cab\u9cac\u9cad\u9cae\u9caf\u9cb0\u9cb1\u9cb2\u9cb3\u9cb4\u9cb5\u9cb6\u9cb7\u9cb8\u9cb9\u9cba\u9cbb\u9cbc\u9cbd\u9cbe\u9cbf\u9cc0\u9cc1\u9cc2\u9cc3\u9cc4\u9cc5\u9cc6\u9cc7\u9cc8\u9cc9\u9cca\u9ccb\u9ccc\u9ccd\u9cce\u9ccf\u9cd0\u9cd1\u9cd2\u9cd3\u9cd4\u9cd5\u9cd6\u9cd7\u9cd8\u9cd9\u9cda\u9cdb\u9cdc\u9cdd\u9cde\u9cdf\u9ce0\u9ce1\u9ce2\u9ce3\u9ce4\u9ce5\u9ce6\u9ce7\u9ce8\u9ce9\u9cea\u9ceb\u9cec\u9ced\u9cee\u9cef\u9cf0\u9cf1\u9cf2\u9cf3\u9cf4\u9cf5\u9cf6\u9cf7\u9cf8\u9cf9\u9cfa\u9cfb\u9cfc\u9cfd\u9cfe\u9cff\u9d00\u9d01\u9d02\u9d03\u9d04\u9d05\u9d06\u9d07\u9d08\u9d09\u9d0a\u9d0b\u9d0c\u9d0d\u9d0e\u9d0f\u9d10\u9d11\u9d12\u9d13\u9d14\u9d15\u9d16\u9d17\u9d18\u9d19\u9d1a\u9d1b\u9d1c\u9d1d\u9d1e\u9d1f\u9d20\u9d21\u9d22\u9d23\u9d24\u9d25\u9d26\u9d27\u9d28\u9d29\u9d2a\u9d2b\u9d2c\u9d2d\u9d2e\u9d2f\u9d30\u9d31\u9d32\u9d33\u9d34\u9d35\u9d36\u9d37\u9d38\u9d39\u9d3a\u9d3b\u9d3c\u9d3d\u9d3e\u9d3f\u9d40\u9d41\u9d42\u9d43\u9d44\u9d45\u9d46\u9d47\u9d48\u9d49\u9d4a\u9d4b\u9d4c\u9d4d\u9d4e\u9d4f\u9d50\u9d51\u9d52\u9d53\u9d54\u9d55\u9d56\u9d57\u9d58\u9d59\u9d5a\u9d5b\u9d5c\u9d5d\u9d5e\u9d5f\u9d60\u9d61\u9d62\u9d63\u9d64\u9d65\u9d66\u9d67\u9d68\u9d69\u9d6a\u9d6b\u9d6c\u9d6d\u9d6e\u9d6f\u9d70\u9d71\u9d72\u9d73\u9d74\u9d75\u9d76\u9d77\u9d78\u9d79\u9d7a\u9d7b\u9d7c\u9d7d\u9d7e\u9d7f\u9d80\u9d81\u9d82\u9d83\u9d84\u9d85\u9d86\u9d87\u9d88\u9d89\u9d8a\u9d8b\u9d8c\u9d8d\u9d8e\u9d8f\u9d90\u9d91\u9d92\u9d93\u9d94\u9d95\u9d96\u9d97\u9d98\u9d99\u9d9a\u9d9b\u9d9c\u9d9d\u9d9e\u9d9f\u9da0\u9da1\u9da2\u9da3\u9da4\u9da5\u9da6\u9da7\u9da8\u9da9\u9daa\u9dab\u9dac\u9dad\u9dae\u9daf\u9db0\u9db1\u9db2\u9db3\u9db4\u9db5\u9db6\u9db7\u9db8\u9db9\u9dba\u9dbb\u9dbc\u9dbd\u9dbe\u9dbf\u9dc0\u9dc1\u9dc2\u9dc3\u9dc4\u9dc5\u9dc6\u9dc7\u9dc8\u9dc9\u9dca\u9dcb\u9dcc\u9dcd\u9dce\u9dcf\u9dd0\u9dd1\u9dd2\u9dd3\u9dd4\u9dd5\u9dd6\u9dd7\u9dd8\u9dd9\u9dda\u9ddb\u9ddc\u9ddd\u9dde\u9ddf\u9de0\u9de1\u9de2\u9de3\u9de4\u9de5\u9de6\u9de7\u9de8\u9de9\u9dea\u9deb\u9dec\u9ded\u9dee\u9def\u9df0\u9df1\u9df2\u9df3\u9df4\u9df5\u9df6\u9df7\u9df8\u9df9\u9dfa\u9dfb\u9dfc\u9dfd\u9dfe\u9dff\u9e00\u9e01\u9e02\u9e03\u9e04\u9e05\u9e06\u9e07\u9e08\u9e09\u9e0a\u9e0b\u9e0c\u9e0d\u9e0e\u9e0f\u9e10\u9e11\u9e12\u9e13\u9e14\u9e15\u9e16\u9e17\u9e18\u9e19\u9e1a\u9e1b\u9e1c\u9e1d\u9e1e\u9e1f\u9e20\u9e21\u9e22\u9e23\u9e24\u9e25\u9e26\u9e27\u9e28\u9e29\u9e2a\u9e2b\u9e2c\u9e2d\u9e2e\u9e2f\u9e30\u9e31\u9e32\u9e33\u9e34\u9e35\u9e36\u9e37\u9e38\u9e39\u9e3a\u9e3b\u9e3c\u9e3d\u9e3e\u9e3f\u9e40\u9e41\u9e42\u9e43\u9e44\u9e45\u9e46\u9e47\u9e48\u9e49\u9e4a\u9e4b\u9e4c\u9e4d\u9e4e\u9e4f\u9e50\u9e51\u9e52\u9e53\u9e54\u9e55\u9e56\u9e57\u9e58\u9e59\u9e5a\u9e5b\u9e5c\u9e5d\u9e5e\u9e5f\u9e60\u9e61\u9e62\u9e63\u9e64\u9e65\u9e66\u9e67\u9e68\u9e69\u9e6a\u9e6b\u9e6c\u9e6d\u9e6e\u9e6f\u9e70\u9e71\u9e72\u9e73\u9e74\u9e75\u9e76\u9e77\u9e78\u9e79\u9e7a\u9e7b\u9e7c\u9e7d\u9e7e\u9e7f\u9e80\u9e81\u9e82\u9e83\u9e84\u9e85\u9e86\u9e87\u9e88\u9e89\u9e8a\u9e8b\u9e8c\u9e8d\u9e8e\u9e8f\u9e90\u9e91\u9e92\u9e93\u9e94\u9e95\u9e96\u9e97\u9e98\u9e99\u9e9a\u9e9b\u9e9c\u9e9d\u9e9e\u9e9f\u9ea0\u9ea1\u9ea2\u9ea3\u9ea4\u9ea5\u9ea6\u9ea7\u9ea8\u9ea9\u9eaa\u9eab\u9eac\u9ead\u9eae\u9eaf\u9eb0\u9eb1\u9eb2\u9eb3\u9eb4\u9eb5\u9eb6\u9eb7\u9eb8\u9eb9\u9eba\u9ebb\u9ebc\u9ebd\u9ebe\u9ebf\u9ec0\u9ec1\u9ec2\u9ec3\u9ec4\u9ec5\u9ec6\u9ec7\u9ec8\u9ec9\u9eca\u9ecb\u9ecc\u9ecd\u9ece\u9ecf\u9ed0\u9ed1\u9ed2\u9ed3\u9ed4\u9ed5\u9ed6\u9ed7\u9ed8\u9ed9\u9eda\u9edb\u9edc\u9edd\u9ede\u9edf\u9ee0\u9ee1\u9ee2\u9ee3\u9ee4\u9ee5\u9ee6\u9ee7\u9ee8\u9ee9\u9eea\u9eeb\u9eec\u9eed\u9eee\u9eef\u9ef0\u9ef1\u9ef2\u9ef3\u9ef4\u9ef5\u9ef6\u9ef7\u9ef8\u9ef9\u9efa\u9efb\u9efc\u9efd\u9efe\u9eff\u9f00\u9f01\u9f02\u9f03\u9f04\u9f05\u9f06\u9f07\u9f08\u9f09\u9f0a\u9f0b\u9f0c\u9f0d\u9f0e\u9f0f\u9f10\u9f11\u9f12\u9f13\u9f14\u9f15\u9f16\u9f17\u9f18\u9f19\u9f1a\u9f1b\u9f1c\u9f1d\u9f1e\u9f1f\u9f20\u9f21\u9f22\u9f23\u9f24\u9f25\u9f26\u9f27\u9f28\u9f29\u9f2a\u9f2b\u9f2c\u9f2d\u9f2e\u9f2f\u9f30\u9f31\u9f32\u9f33\u9f34\u9f35\u9f36\u9f37\u9f38\u9f39\u9f3a\u9f3b\u9f3c\u9f3d\u9f3e\u9f3f\u9f40\u9f41\u9f42\u9f43\u9f44\u9f45\u9f46\u9f47\u9f48\u9f49\u9f4a\u9f4b\u9f4c\u9f4d\u9f4e\u9f4f\u9f50\u9f51\u9f52\u9f53\u9f54\u9f55\u9f56\u9f57\u9f58\u9f59\u9f5a\u9f5b\u9f5c\u9f5d\u9f5e\u9f5f\u9f60\u9f61\u9f62\u9f63\u9f64\u9f65\u9f66\u9f67\u9f68\u9f69\u9f6a\u9f6b\u9f6c\u9f6d\u9f6e\u9f6f\u9f70\u9f71\u9f72\u9f73\u9f74\u9f75\u9f76\u9f77\u9f78\u9f79\u9f7a\u9f7b\u9f7c\u9f7d\u9f7e\u9f7f\u9f80\u9f81\u9f82\u9f83\u9f84\u9f85\u9f86\u9f87\u9f88\u9f89\u9f8a\u9f8b\u9f8c\u9f8d\u9f8e\u9f8f\u9f90\u9f91\u9f92\u9f93\u9f94\u9f95\u9f96\u9f97\u9f98\u9f99\u9f9a\u9f9b\u9f9c\u9f9d\u9f9e\u9f9f\u9fa0\u9fa1\u9fa2\u9fa3\u9fa4\u9fa5\u9fa6\u9fa7\u9fa8\u9fa9\u9faa\u9fab\u9fac\u9fad\u9fae\u9faf\u9fb0\u9fb1\u9fb2\u9fb3\u9fb4\u9fb5\u9fb6\u9fb7\u9fb8\u9fb9\u9fba\u9fbb\ua000\ua001\ua002\ua003\ua004\ua005\ua006\ua007\ua008\ua009\ua00a\ua00b\ua00c\ua00d\ua00e\ua00f\ua010\ua011\ua012\ua013\ua014\ua016\ua017\ua018\ua019\ua01a\ua01b\ua01c\ua01d\ua01e\ua01f\ua020\ua021\ua022\ua023\ua024\ua025\ua026\ua027\ua028\ua029\ua02a\ua02b\ua02c\ua02d\ua02e\ua02f\ua030\ua031\ua032\ua033\ua034\ua035\ua036\ua037\ua038\ua039\ua03a\ua03b\ua03c\ua03d\ua03e\ua03f\ua040\ua041\ua042\ua043\ua044\ua045\ua046\ua047\ua048\ua049\ua04a\ua04b\ua04c\ua04d\ua04e\ua04f\ua050\ua051\ua052\ua053\ua054\ua055\ua056\ua057\ua058\ua059\ua05a\ua05b\ua05c\ua05d\ua05e\ua05f\ua060\ua061\ua062\ua063\ua064\ua065\ua066\ua067\ua068\ua069\ua06a\ua06b\ua06c\ua06d\ua06e\ua06f\ua070\ua071\ua072\ua073\ua074\ua075\ua076\ua077\ua078\ua079\ua07a\ua07b\ua07c\ua07d\ua07e\ua07f\ua080\ua081\ua082\ua083\ua084\ua085\ua086\ua087\ua088\ua089\ua08a\ua08b\ua08c\ua08d\ua08e\ua08f\ua090\ua091\ua092\ua093\ua094\ua095\ua096\ua097\ua098\ua099\ua09a\ua09b\ua09c\ua09d\ua09e\ua09f\ua0a0\ua0a1\ua0a2\ua0a3\ua0a4\ua0a5\ua0a6\ua0a7\ua0a8\ua0a9\ua0aa\ua0ab\ua0ac\ua0ad\ua0ae\ua0af\ua0b0\ua0b1\ua0b2\ua0b3\ua0b4\ua0b5\ua0b6\ua0b7\ua0b8\ua0b9\ua0ba\ua0bb\ua0bc\ua0bd\ua0be\ua0bf\ua0c0\ua0c1\ua0c2\ua0c3\ua0c4\ua0c5\ua0c6\ua0c7\ua0c8\ua0c9\ua0ca\ua0cb\ua0cc\ua0cd\ua0ce\ua0cf\ua0d0\ua0d1\ua0d2\ua0d3\ua0d4\ua0d5\ua0d6\ua0d7\ua0d8\ua0d9\ua0da\ua0db\ua0dc\ua0dd\ua0de\ua0df\ua0e0\ua0e1\ua0e2\ua0e3\ua0e4\ua0e5\ua0e6\ua0e7\ua0e8\ua0e9\ua0ea\ua0eb\ua0ec\ua0ed\ua0ee\ua0ef\ua0f0\ua0f1\ua0f2\ua0f3\ua0f4\ua0f5\ua0f6\ua0f7\ua0f8\ua0f9\ua0fa\ua0fb\ua0fc\ua0fd\ua0fe\ua0ff\ua100\ua101\ua102\ua103\ua104\ua105\ua106\ua107\ua108\ua109\ua10a\ua10b\ua10c\ua10d\ua10e\ua10f\ua110\ua111\ua112\ua113\ua114\ua115\ua116\ua117\ua118\ua119\ua11a\ua11b\ua11c\ua11d\ua11e\ua11f\ua120\ua121\ua122\ua123\ua124\ua125\ua126\ua127\ua128\ua129\ua12a\ua12b\ua12c\ua12d\ua12e\ua12f\ua130\ua131\ua132\ua133\ua134\ua135\ua136\ua137\ua138\ua139\ua13a\ua13b\ua13c\ua13d\ua13e\ua13f\ua140\ua141\ua142\ua143\ua144\ua145\ua146\ua147\ua148\ua149\ua14a\ua14b\ua14c\ua14d\ua14e\ua14f\ua150\ua151\ua152\ua153\ua154\ua155\ua156\ua157\ua158\ua159\ua15a\ua15b\ua15c\ua15d\ua15e\ua15f\ua160\ua161\ua162\ua163\ua164\ua165\ua166\ua167\ua168\ua169\ua16a\ua16b\ua16c\ua16d\ua16e\ua16f\ua170\ua171\ua172\ua173\ua174\ua175\ua176\ua177\ua178\ua179\ua17a\ua17b\ua17c\ua17d\ua17e\ua17f\ua180\ua181\ua182\ua183\ua184\ua185\ua186\ua187\ua188\ua189\ua18a\ua18b\ua18c\ua18d\ua18e\ua18f\ua190\ua191\ua192\ua193\ua194\ua195\ua196\ua197\ua198\ua199\ua19a\ua19b\ua19c\ua19d\ua19e\ua19f\ua1a0\ua1a1\ua1a2\ua1a3\ua1a4\ua1a5\ua1a6\ua1a7\ua1a8\ua1a9\ua1aa\ua1ab\ua1ac\ua1ad\ua1ae\ua1af\ua1b0\ua1b1\ua1b2\ua1b3\ua1b4\ua1b5\ua1b6\ua1b7\ua1b8\ua1b9\ua1ba\ua1bb\ua1bc\ua1bd\ua1be\ua1bf\ua1c0\ua1c1\ua1c2\ua1c3\ua1c4\ua1c5\ua1c6\ua1c7\ua1c8\ua1c9\ua1ca\ua1cb\ua1cc\ua1cd\ua1ce\ua1cf\ua1d0\ua1d1\ua1d2\ua1d3\ua1d4\ua1d5\ua1d6\ua1d7\ua1d8\ua1d9\ua1da\ua1db\ua1dc\ua1dd\ua1de\ua1df\ua1e0\ua1e1\ua1e2\ua1e3\ua1e4\ua1e5\ua1e6\ua1e7\ua1e8\ua1e9\ua1ea\ua1eb\ua1ec\ua1ed\ua1ee\ua1ef\ua1f0\ua1f1\ua1f2\ua1f3\ua1f4\ua1f5\ua1f6\ua1f7\ua1f8\ua1f9\ua1fa\ua1fb\ua1fc\ua1fd\ua1fe\ua1ff\ua200\ua201\ua202\ua203\ua204\ua205\ua206\ua207\ua208\ua209\ua20a\ua20b\ua20c\ua20d\ua20e\ua20f\ua210\ua211\ua212\ua213\ua214\ua215\ua216\ua217\ua218\ua219\ua21a\ua21b\ua21c\ua21d\ua21e\ua21f\ua220\ua221\ua222\ua223\ua224\ua225\ua226\ua227\ua228\ua229\ua22a\ua22b\ua22c\ua22d\ua22e\ua22f\ua230\ua231\ua232\ua233\ua234\ua235\ua236\ua237\ua238\ua239\ua23a\ua23b\ua23c\ua23d\ua23e\ua23f\ua240\ua241\ua242\ua243\ua244\ua245\ua246\ua247\ua248\ua249\ua24a\ua24b\ua24c\ua24d\ua24e\ua24f\ua250\ua251\ua252\ua253\ua254\ua255\ua256\ua257\ua258\ua259\ua25a\ua25b\ua25c\ua25d\ua25e\ua25f\ua260\ua261\ua262\ua263\ua264\ua265\ua266\ua267\ua268\ua269\ua26a\ua26b\ua26c\ua26d\ua26e\ua26f\ua270\ua271\ua272\ua273\ua274\ua275\ua276\ua277\ua278\ua279\ua27a\ua27b\ua27c\ua27d\ua27e\ua27f\ua280\ua281\ua282\ua283\ua284\ua285\ua286\ua287\ua288\ua289\ua28a\ua28b\ua28c\ua28d\ua28e\ua28f\ua290\ua291\ua292\ua293\ua294\ua295\ua296\ua297\ua298\ua299\ua29a\ua29b\ua29c\ua29d\ua29e\ua29f\ua2a0\ua2a1\ua2a2\ua2a3\ua2a4\ua2a5\ua2a6\ua2a7\ua2a8\ua2a9\ua2aa\ua2ab\ua2ac\ua2ad\ua2ae\ua2af\ua2b0\ua2b1\ua2b2\ua2b3\ua2b4\ua2b5\ua2b6\ua2b7\ua2b8\ua2b9\ua2ba\ua2bb\ua2bc\ua2bd\ua2be\ua2bf\ua2c0\ua2c1\ua2c2\ua2c3\ua2c4\ua2c5\ua2c6\ua2c7\ua2c8\ua2c9\ua2ca\ua2cb\ua2cc\ua2cd\ua2ce\ua2cf\ua2d0\ua2d1\ua2d2\ua2d3\ua2d4\ua2d5\ua2d6\ua2d7\ua2d8\ua2d9\ua2da\ua2db\ua2dc\ua2dd\ua2de\ua2df\ua2e0\ua2e1\ua2e2\ua2e3\ua2e4\ua2e5\ua2e6\ua2e7\ua2e8\ua2e9\ua2ea\ua2eb\ua2ec\ua2ed\ua2ee\ua2ef\ua2f0\ua2f1\ua2f2\ua2f3\ua2f4\ua2f5\ua2f6\ua2f7\ua2f8\ua2f9\ua2fa\ua2fb\ua2fc\ua2fd\ua2fe\ua2ff\ua300\ua301\ua302\ua303\ua304\ua305\ua306\ua307\ua308\ua309\ua30a\ua30b\ua30c\ua30d\ua30e\ua30f\ua310\ua311\ua312\ua313\ua314\ua315\ua316\ua317\ua318\ua319\ua31a\ua31b\ua31c\ua31d\ua31e\ua31f\ua320\ua321\ua322\ua323\ua324\ua325\ua326\ua327\ua328\ua329\ua32a\ua32b\ua32c\ua32d\ua32e\ua32f\ua330\ua331\ua332\ua333\ua334\ua335\ua336\ua337\ua338\ua339\ua33a\ua33b\ua33c\ua33d\ua33e\ua33f\ua340\ua341\ua342\ua343\ua344\ua345\ua346\ua347\ua348\ua349\ua34a\ua34b\ua34c\ua34d\ua34e\ua34f\ua350\ua351\ua352\ua353\ua354\ua355\ua356\ua357\ua358\ua359\ua35a\ua35b\ua35c\ua35d\ua35e\ua35f\ua360\ua361\ua362\ua363\ua364\ua365\ua366\ua367\ua368\ua369\ua36a\ua36b\ua36c\ua36d\ua36e\ua36f\ua370\ua371\ua372\ua373\ua374\ua375\ua376\ua377\ua378\ua379\ua37a\ua37b\ua37c\ua37d\ua37e\ua37f\ua380\ua381\ua382\ua383\ua384\ua385\ua386\ua387\ua388\ua389\ua38a\ua38b\ua38c\ua38d\ua38e\ua38f\ua390\ua391\ua392\ua393\ua394\ua395\ua396\ua397\ua398\ua399\ua39a\ua39b\ua39c\ua39d\ua39e\ua39f\ua3a0\ua3a1\ua3a2\ua3a3\ua3a4\ua3a5\ua3a6\ua3a7\ua3a8\ua3a9\ua3aa\ua3ab\ua3ac\ua3ad\ua3ae\ua3af\ua3b0\ua3b1\ua3b2\ua3b3\ua3b4\ua3b5\ua3b6\ua3b7\ua3b8\ua3b9\ua3ba\ua3bb\ua3bc\ua3bd\ua3be\ua3bf\ua3c0\ua3c1\ua3c2\ua3c3\ua3c4\ua3c5\ua3c6\ua3c7\ua3c8\ua3c9\ua3ca\ua3cb\ua3cc\ua3cd\ua3ce\ua3cf\ua3d0\ua3d1\ua3d2\ua3d3\ua3d4\ua3d5\ua3d6\ua3d7\ua3d8\ua3d9\ua3da\ua3db\ua3dc\ua3dd\ua3de\ua3df\ua3e0\ua3e1\ua3e2\ua3e3\ua3e4\ua3e5\ua3e6\ua3e7\ua3e8\ua3e9\ua3ea\ua3eb\ua3ec\ua3ed\ua3ee\ua3ef\ua3f0\ua3f1\ua3f2\ua3f3\ua3f4\ua3f5\ua3f6\ua3f7\ua3f8\ua3f9\ua3fa\ua3fb\ua3fc\ua3fd\ua3fe\ua3ff\ua400\ua401\ua402\ua403\ua404\ua405\ua406\ua407\ua408\ua409\ua40a\ua40b\ua40c\ua40d\ua40e\ua40f\ua410\ua411\ua412\ua413\ua414\ua415\ua416\ua417\ua418\ua419\ua41a\ua41b\ua41c\ua41d\ua41e\ua41f\ua420\ua421\ua422\ua423\ua424\ua425\ua426\ua427\ua428\ua429\ua42a\ua42b\ua42c\ua42d\ua42e\ua42f\ua430\ua431\ua432\ua433\ua434\ua435\ua436\ua437\ua438\ua439\ua43a\ua43b\ua43c\ua43d\ua43e\ua43f\ua440\ua441\ua442\ua443\ua444\ua445\ua446\ua447\ua448\ua449\ua44a\ua44b\ua44c\ua44d\ua44e\ua44f\ua450\ua451\ua452\ua453\ua454\ua455\ua456\ua457\ua458\ua459\ua45a\ua45b\ua45c\ua45d\ua45e\ua45f\ua460\ua461\ua462\ua463\ua464\ua465\ua466\ua467\ua468\ua469\ua46a\ua46b\ua46c\ua46d\ua46e\ua46f\ua470\ua471\ua472\ua473\ua474\ua475\ua476\ua477\ua478\ua479\ua47a\ua47b\ua47c\ua47d\ua47e\ua47f\ua480\ua481\ua482\ua483\ua484\ua485\ua486\ua487\ua488\ua489\ua48a\ua48b\ua48c\ua800\ua801\ua803\ua804\ua805\ua807\ua808\ua809\ua80a\ua80c\ua80d\ua80e\ua80f\ua810\ua811\ua812\ua813\ua814\ua815\ua816\ua817\ua818\ua819\ua81a\ua81b\ua81c\ua81d\ua81e\ua81f\ua820\ua821\ua822\uac00\uac01\uac02\uac03\uac04\uac05\uac06\uac07\uac08\uac09\uac0a\uac0b\uac0c\uac0d\uac0e\uac0f\uac10\uac11\uac12\uac13\uac14\uac15\uac16\uac17\uac18\uac19\uac1a\uac1b\uac1c\uac1d\uac1e\uac1f\uac20\uac21\uac22\uac23\uac24\uac25\uac26\uac27\uac28\uac29\uac2a\uac2b\uac2c\uac2d\uac2e\uac2f\uac30\uac31\uac32\uac33\uac34\uac35\uac36\uac37\uac38\uac39\uac3a\uac3b\uac3c\uac3d\uac3e\uac3f\uac40\uac41\uac42\uac43\uac44\uac45\uac46\uac47\uac48\uac49\uac4a\uac4b\uac4c\uac4d\uac4e\uac4f\uac50\uac51\uac52\uac53\uac54\uac55\uac56\uac57\uac58\uac59\uac5a\uac5b\uac5c\uac5d\uac5e\uac5f\uac60\uac61\uac62\uac63\uac64\uac65\uac66\uac67\uac68\uac69\uac6a\uac6b\uac6c\uac6d\uac6e\uac6f\uac70\uac71\uac72\uac73\uac74\uac75\uac76\uac77\uac78\uac79\uac7a\uac7b\uac7c\uac7d\uac7e\uac7f\uac80\uac81\uac82\uac83\uac84\uac85\uac86\uac87\uac88\uac89\uac8a\uac8b\uac8c\uac8d\uac8e\uac8f\uac90\uac91\uac92\uac93\uac94\uac95\uac96\uac97\uac98\uac99\uac9a\uac9b\uac9c\uac9d\uac9e\uac9f\uaca0\uaca1\uaca2\uaca3\uaca4\uaca5\uaca6\uaca7\uaca8\uaca9\uacaa\uacab\uacac\uacad\uacae\uacaf\uacb0\uacb1\uacb2\uacb3\uacb4\uacb5\uacb6\uacb7\uacb8\uacb9\uacba\uacbb\uacbc\uacbd\uacbe\uacbf\uacc0\uacc1\uacc2\uacc3\uacc4\uacc5\uacc6\uacc7\uacc8\uacc9\uacca\uaccb\uaccc\uaccd\uacce\uaccf\uacd0\uacd1\uacd2\uacd3\uacd4\uacd5\uacd6\uacd7\uacd8\uacd9\uacda\uacdb\uacdc\uacdd\uacde\uacdf\uace0\uace1\uace2\uace3\uace4\uace5\uace6\uace7\uace8\uace9\uacea\uaceb\uacec\uaced\uacee\uacef\uacf0\uacf1\uacf2\uacf3\uacf4\uacf5\uacf6\uacf7\uacf8\uacf9\uacfa\uacfb\uacfc\uacfd\uacfe\uacff\uad00\uad01\uad02\uad03\uad04\uad05\uad06\uad07\uad08\uad09\uad0a\uad0b\uad0c\uad0d\uad0e\uad0f\uad10\uad11\uad12\uad13\uad14\uad15\uad16\uad17\uad18\uad19\uad1a\uad1b\uad1c\uad1d\uad1e\uad1f\uad20\uad21\uad22\uad23\uad24\uad25\uad26\uad27\uad28\uad29\uad2a\uad2b\uad2c\uad2d\uad2e\uad2f\uad30\uad31\uad32\uad33\uad34\uad35\uad36\uad37\uad38\uad39\uad3a\uad3b\uad3c\uad3d\uad3e\uad3f\uad40\uad41\uad42\uad43\uad44\uad45\uad46\uad47\uad48\uad49\uad4a\uad4b\uad4c\uad4d\uad4e\uad4f\uad50\uad51\uad52\uad53\uad54\uad55\uad56\uad57\uad58\uad59\uad5a\uad5b\uad5c\uad5d\uad5e\uad5f\uad60\uad61\uad62\uad63\uad64\uad65\uad66\uad67\uad68\uad69\uad6a\uad6b\uad6c\uad6d\uad6e\uad6f\uad70\uad71\uad72\uad73\uad74\uad75\uad76\uad77\uad78\uad79\uad7a\uad7b\uad7c\uad7d\uad7e\uad7f\uad80\uad81\uad82\uad83\uad84\uad85\uad86\uad87\uad88\uad89\uad8a\uad8b\uad8c\uad8d\uad8e\uad8f\uad90\uad91\uad92\uad93\uad94\uad95\uad96\uad97\uad98\uad99\uad9a\uad9b\uad9c\uad9d\uad9e\uad9f\uada0\uada1\uada2\uada3\uada4\uada5\uada6\uada7\uada8\uada9\uadaa\uadab\uadac\uadad\uadae\uadaf\uadb0\uadb1\uadb2\uadb3\uadb4\uadb5\uadb6\uadb7\uadb8\uadb9\uadba\uadbb\uadbc\uadbd\uadbe\uadbf\uadc0\uadc1\uadc2\uadc3\uadc4\uadc5\uadc6\uadc7\uadc8\uadc9\uadca\uadcb\uadcc\uadcd\uadce\uadcf\uadd0\uadd1\uadd2\uadd3\uadd4\uadd5\uadd6\uadd7\uadd8\uadd9\uadda\uaddb\uaddc\uaddd\uadde\uaddf\uade0\uade1\uade2\uade3\uade4\uade5\uade6\uade7\uade8\uade9\uadea\uadeb\uadec\uaded\uadee\uadef\uadf0\uadf1\uadf2\uadf3\uadf4\uadf5\uadf6\uadf7\uadf8\uadf9\uadfa\uadfb\uadfc\uadfd\uadfe\uadff\uae00\uae01\uae02\uae03\uae04\uae05\uae06\uae07\uae08\uae09\uae0a\uae0b\uae0c\uae0d\uae0e\uae0f\uae10\uae11\uae12\uae13\uae14\uae15\uae16\uae17\uae18\uae19\uae1a\uae1b\uae1c\uae1d\uae1e\uae1f\uae20\uae21\uae22\uae23\uae24\uae25\uae26\uae27\uae28\uae29\uae2a\uae2b\uae2c\uae2d\uae2e\uae2f\uae30\uae31\uae32\uae33\uae34\uae35\uae36\uae37\uae38\uae39\uae3a\uae3b\uae3c\uae3d\uae3e\uae3f\uae40\uae41\uae42\uae43\uae44\uae45\uae46\uae47\uae48\uae49\uae4a\uae4b\uae4c\uae4d\uae4e\uae4f\uae50\uae51\uae52\uae53\uae54\uae55\uae56\uae57\uae58\uae59\uae5a\uae5b\uae5c\uae5d\uae5e\uae5f\uae60\uae61\uae62\uae63\uae64\uae65\uae66\uae67\uae68\uae69\uae6a\uae6b\uae6c\uae6d\uae6e\uae6f\uae70\uae71\uae72\uae73\uae74\uae75\uae76\uae77\uae78\uae79\uae7a\uae7b\uae7c\uae7d\uae7e\uae7f\uae80\uae81\uae82\uae83\uae84\uae85\uae86\uae87\uae88\uae89\uae8a\uae8b\uae8c\uae8d\uae8e\uae8f\uae90\uae91\uae92\uae93\uae94\uae95\uae96\uae97\uae98\uae99\uae9a\uae9b\uae9c\uae9d\uae9e\uae9f\uaea0\uaea1\uaea2\uaea3\uaea4\uaea5\uaea6\uaea7\uaea8\uaea9\uaeaa\uaeab\uaeac\uaead\uaeae\uaeaf\uaeb0\uaeb1\uaeb2\uaeb3\uaeb4\uaeb5\uaeb6\uaeb7\uaeb8\uaeb9\uaeba\uaebb\uaebc\uaebd\uaebe\uaebf\uaec0\uaec1\uaec2\uaec3\uaec4\uaec5\uaec6\uaec7\uaec8\uaec9\uaeca\uaecb\uaecc\uaecd\uaece\uaecf\uaed0\uaed1\uaed2\uaed3\uaed4\uaed5\uaed6\uaed7\uaed8\uaed9\uaeda\uaedb\uaedc\uaedd\uaede\uaedf\uaee0\uaee1\uaee2\uaee3\uaee4\uaee5\uaee6\uaee7\uaee8\uaee9\uaeea\uaeeb\uaeec\uaeed\uaeee\uaeef\uaef0\uaef1\uaef2\uaef3\uaef4\uaef5\uaef6\uaef7\uaef8\uaef9\uaefa\uaefb\uaefc\uaefd\uaefe\uaeff\uaf00\uaf01\uaf02\uaf03\uaf04\uaf05\uaf06\uaf07\uaf08\uaf09\uaf0a\uaf0b\uaf0c\uaf0d\uaf0e\uaf0f\uaf10\uaf11\uaf12\uaf13\uaf14\uaf15\uaf16\uaf17\uaf18\uaf19\uaf1a\uaf1b\uaf1c\uaf1d\uaf1e\uaf1f\uaf20\uaf21\uaf22\uaf23\uaf24\uaf25\uaf26\uaf27\uaf28\uaf29\uaf2a\uaf2b\uaf2c\uaf2d\uaf2e\uaf2f\uaf30\uaf31\uaf32\uaf33\uaf34\uaf35\uaf36\uaf37\uaf38\uaf39\uaf3a\uaf3b\uaf3c\uaf3d\uaf3e\uaf3f\uaf40\uaf41\uaf42\uaf43\uaf44\uaf45\uaf46\uaf47\uaf48\uaf49\uaf4a\uaf4b\uaf4c\uaf4d\uaf4e\uaf4f\uaf50\uaf51\uaf52\uaf53\uaf54\uaf55\uaf56\uaf57\uaf58\uaf59\uaf5a\uaf5b\uaf5c\uaf5d\uaf5e\uaf5f\uaf60\uaf61\uaf62\uaf63\uaf64\uaf65\uaf66\uaf67\uaf68\uaf69\uaf6a\uaf6b\uaf6c\uaf6d\uaf6e\uaf6f\uaf70\uaf71\uaf72\uaf73\uaf74\uaf75\uaf76\uaf77\uaf78\uaf79\uaf7a\uaf7b\uaf7c\uaf7d\uaf7e\uaf7f\uaf80\uaf81\uaf82\uaf83\uaf84\uaf85\uaf86\uaf87\uaf88\uaf89\uaf8a\uaf8b\uaf8c\uaf8d\uaf8e\uaf8f\uaf90\uaf91\uaf92\uaf93\uaf94\uaf95\uaf96\uaf97\uaf98\uaf99\uaf9a\uaf9b\uaf9c\uaf9d\uaf9e\uaf9f\uafa0\uafa1\uafa2\uafa3\uafa4\uafa5\uafa6\uafa7\uafa8\uafa9\uafaa\uafab\uafac\uafad\uafae\uafaf\uafb0\uafb1\uafb2\uafb3\uafb4\uafb5\uafb6\uafb7\uafb8\uafb9\uafba\uafbb\uafbc\uafbd\uafbe\uafbf\uafc0\uafc1\uafc2\uafc3\uafc4\uafc5\uafc6\uafc7\uafc8\uafc9\uafca\uafcb\uafcc\uafcd\uafce\uafcf\uafd0\uafd1\uafd2\uafd3\uafd4\uafd5\uafd6\uafd7\uafd8\uafd9\uafda\uafdb\uafdc\uafdd\uafde\uafdf\uafe0\uafe1\uafe2\uafe3\uafe4\uafe5\uafe6\uafe7\uafe8\uafe9\uafea\uafeb\uafec\uafed\uafee\uafef\uaff0\uaff1\uaff2\uaff3\uaff4\uaff5\uaff6\uaff7\uaff8\uaff9\uaffa\uaffb\uaffc\uaffd\uaffe\uafff\ub000\ub001\ub002\ub003\ub004\ub005\ub006\ub007\ub008\ub009\ub00a\ub00b\ub00c\ub00d\ub00e\ub00f\ub010\ub011\ub012\ub013\ub014\ub015\ub016\ub017\ub018\ub019\ub01a\ub01b\ub01c\ub01d\ub01e\ub01f\ub020\ub021\ub022\ub023\ub024\ub025\ub026\ub027\ub028\ub029\ub02a\ub02b\ub02c\ub02d\ub02e\ub02f\ub030\ub031\ub032\ub033\ub034\ub035\ub036\ub037\ub038\ub039\ub03a\ub03b\ub03c\ub03d\ub03e\ub03f\ub040\ub041\ub042\ub043\ub044\ub045\ub046\ub047\ub048\ub049\ub04a\ub04b\ub04c\ub04d\ub04e\ub04f\ub050\ub051\ub052\ub053\ub054\ub055\ub056\ub057\ub058\ub059\ub05a\ub05b\ub05c\ub05d\ub05e\ub05f\ub060\ub061\ub062\ub063\ub064\ub065\ub066\ub067\ub068\ub069\ub06a\ub06b\ub06c\ub06d\ub06e\ub06f\ub070\ub071\ub072\ub073\ub074\ub075\ub076\ub077\ub078\ub079\ub07a\ub07b\ub07c\ub07d\ub07e\ub07f\ub080\ub081\ub082\ub083\ub084\ub085\ub086\ub087\ub088\ub089\ub08a\ub08b\ub08c\ub08d\ub08e\ub08f\ub090\ub091\ub092\ub093\ub094\ub095\ub096\ub097\ub098\ub099\ub09a\ub09b\ub09c\ub09d\ub09e\ub09f\ub0a0\ub0a1\ub0a2\ub0a3\ub0a4\ub0a5\ub0a6\ub0a7\ub0a8\ub0a9\ub0aa\ub0ab\ub0ac\ub0ad\ub0ae\ub0af\ub0b0\ub0b1\ub0b2\ub0b3\ub0b4\ub0b5\ub0b6\ub0b7\ub0b8\ub0b9\ub0ba\ub0bb\ub0bc\ub0bd\ub0be\ub0bf\ub0c0\ub0c1\ub0c2\ub0c3\ub0c4\ub0c5\ub0c6\ub0c7\ub0c8\ub0c9\ub0ca\ub0cb\ub0cc\ub0cd\ub0ce\ub0cf\ub0d0\ub0d1\ub0d2\ub0d3\ub0d4\ub0d5\ub0d6\ub0d7\ub0d8\ub0d9\ub0da\ub0db\ub0dc\ub0dd\ub0de\ub0df\ub0e0\ub0e1\ub0e2\ub0e3\ub0e4\ub0e5\ub0e6\ub0e7\ub0e8\ub0e9\ub0ea\ub0eb\ub0ec\ub0ed\ub0ee\ub0ef\ub0f0\ub0f1\ub0f2\ub0f3\ub0f4\ub0f5\ub0f6\ub0f7\ub0f8\ub0f9\ub0fa\ub0fb\ub0fc\ub0fd\ub0fe\ub0ff\ub100\ub101\ub102\ub103\ub104\ub105\ub106\ub107\ub108\ub109\ub10a\ub10b\ub10c\ub10d\ub10e\ub10f\ub110\ub111\ub112\ub113\ub114\ub115\ub116\ub117\ub118\ub119\ub11a\ub11b\ub11c\ub11d\ub11e\ub11f\ub120\ub121\ub122\ub123\ub124\ub125\ub126\ub127\ub128\ub129\ub12a\ub12b\ub12c\ub12d\ub12e\ub12f\ub130\ub131\ub132\ub133\ub134\ub135\ub136\ub137\ub138\ub139\ub13a\ub13b\ub13c\ub13d\ub13e\ub13f\ub140\ub141\ub142\ub143\ub144\ub145\ub146\ub147\ub148\ub149\ub14a\ub14b\ub14c\ub14d\ub14e\ub14f\ub150\ub151\ub152\ub153\ub154\ub155\ub156\ub157\ub158\ub159\ub15a\ub15b\ub15c\ub15d\ub15e\ub15f\ub160\ub161\ub162\ub163\ub164\ub165\ub166\ub167\ub168\ub169\ub16a\ub16b\ub16c\ub16d\ub16e\ub16f\ub170\ub171\ub172\ub173\ub174\ub175\ub176\ub177\ub178\ub179\ub17a\ub17b\ub17c\ub17d\ub17e\ub17f\ub180\ub181\ub182\ub183\ub184\ub185\ub186\ub187\ub188\ub189\ub18a\ub18b\ub18c\ub18d\ub18e\ub18f\ub190\ub191\ub192\ub193\ub194\ub195\ub196\ub197\ub198\ub199\ub19a\ub19b\ub19c\ub19d\ub19e\ub19f\ub1a0\ub1a1\ub1a2\ub1a3\ub1a4\ub1a5\ub1a6\ub1a7\ub1a8\ub1a9\ub1aa\ub1ab\ub1ac\ub1ad\ub1ae\ub1af\ub1b0\ub1b1\ub1b2\ub1b3\ub1b4\ub1b5\ub1b6\ub1b7\ub1b8\ub1b9\ub1ba\ub1bb\ub1bc\ub1bd\ub1be\ub1bf\ub1c0\ub1c1\ub1c2\ub1c3\ub1c4\ub1c5\ub1c6\ub1c7\ub1c8\ub1c9\ub1ca\ub1cb\ub1cc\ub1cd\ub1ce\ub1cf\ub1d0\ub1d1\ub1d2\ub1d3\ub1d4\ub1d5\ub1d6\ub1d7\ub1d8\ub1d9\ub1da\ub1db\ub1dc\ub1dd\ub1de\ub1df\ub1e0\ub1e1\ub1e2\ub1e3\ub1e4\ub1e5\ub1e6\ub1e7\ub1e8\ub1e9\ub1ea\ub1eb\ub1ec\ub1ed\ub1ee\ub1ef\ub1f0\ub1f1\ub1f2\ub1f3\ub1f4\ub1f5\ub1f6\ub1f7\ub1f8\ub1f9\ub1fa\ub1fb\ub1fc\ub1fd\ub1fe\ub1ff\ub200\ub201\ub202\ub203\ub204\ub205\ub206\ub207\ub208\ub209\ub20a\ub20b\ub20c\ub20d\ub20e\ub20f\ub210\ub211\ub212\ub213\ub214\ub215\ub216\ub217\ub218\ub219\ub21a\ub21b\ub21c\ub21d\ub21e\ub21f\ub220\ub221\ub222\ub223\ub224\ub225\ub226\ub227\ub228\ub229\ub22a\ub22b\ub22c\ub22d\ub22e\ub22f\ub230\ub231\ub232\ub233\ub234\ub235\ub236\ub237\ub238\ub239\ub23a\ub23b\ub23c\ub23d\ub23e\ub23f\ub240\ub241\ub242\ub243\ub244\ub245\ub246\ub247\ub248\ub249\ub24a\ub24b\ub24c\ub24d\ub24e\ub24f\ub250\ub251\ub252\ub253\ub254\ub255\ub256\ub257\ub258\ub259\ub25a\ub25b\ub25c\ub25d\ub25e\ub25f\ub260\ub261\ub262\ub263\ub264\ub265\ub266\ub267\ub268\ub269\ub26a\ub26b\ub26c\ub26d\ub26e\ub26f\ub270\ub271\ub272\ub273\ub274\ub275\ub276\ub277\ub278\ub279\ub27a\ub27b\ub27c\ub27d\ub27e\ub27f\ub280\ub281\ub282\ub283\ub284\ub285\ub286\ub287\ub288\ub289\ub28a\ub28b\ub28c\ub28d\ub28e\ub28f\ub290\ub291\ub292\ub293\ub294\ub295\ub296\ub297\ub298\ub299\ub29a\ub29b\ub29c\ub29d\ub29e\ub29f\ub2a0\ub2a1\ub2a2\ub2a3\ub2a4\ub2a5\ub2a6\ub2a7\ub2a8\ub2a9\ub2aa\ub2ab\ub2ac\ub2ad\ub2ae\ub2af\ub2b0\ub2b1\ub2b2\ub2b3\ub2b4\ub2b5\ub2b6\ub2b7\ub2b8\ub2b9\ub2ba\ub2bb\ub2bc\ub2bd\ub2be\ub2bf\ub2c0\ub2c1\ub2c2\ub2c3\ub2c4\ub2c5\ub2c6\ub2c7\ub2c8\ub2c9\ub2ca\ub2cb\ub2cc\ub2cd\ub2ce\ub2cf\ub2d0\ub2d1\ub2d2\ub2d3\ub2d4\ub2d5\ub2d6\ub2d7\ub2d8\ub2d9\ub2da\ub2db\ub2dc\ub2dd\ub2de\ub2df\ub2e0\ub2e1\ub2e2\ub2e3\ub2e4\ub2e5\ub2e6\ub2e7\ub2e8\ub2e9\ub2ea\ub2eb\ub2ec\ub2ed\ub2ee\ub2ef\ub2f0\ub2f1\ub2f2\ub2f3\ub2f4\ub2f5\ub2f6\ub2f7\ub2f8\ub2f9\ub2fa\ub2fb\ub2fc\ub2fd\ub2fe\ub2ff\ub300\ub301\ub302\ub303\ub304\ub305\ub306\ub307\ub308\ub309\ub30a\ub30b\ub30c\ub30d\ub30e\ub30f\ub310\ub311\ub312\ub313\ub314\ub315\ub316\ub317\ub318\ub319\ub31a\ub31b\ub31c\ub31d\ub31e\ub31f\ub320\ub321\ub322\ub323\ub324\ub325\ub326\ub327\ub328\ub329\ub32a\ub32b\ub32c\ub32d\ub32e\ub32f\ub330\ub331\ub332\ub333\ub334\ub335\ub336\ub337\ub338\ub339\ub33a\ub33b\ub33c\ub33d\ub33e\ub33f\ub340\ub341\ub342\ub343\ub344\ub345\ub346\ub347\ub348\ub349\ub34a\ub34b\ub34c\ub34d\ub34e\ub34f\ub350\ub351\ub352\ub353\ub354\ub355\ub356\ub357\ub358\ub359\ub35a\ub35b\ub35c\ub35d\ub35e\ub35f\ub360\ub361\ub362\ub363\ub364\ub365\ub366\ub367\ub368\ub369\ub36a\ub36b\ub36c\ub36d\ub36e\ub36f\ub370\ub371\ub372\ub373\ub374\ub375\ub376\ub377\ub378\ub379\ub37a\ub37b\ub37c\ub37d\ub37e\ub37f\ub380\ub381\ub382\ub383\ub384\ub385\ub386\ub387\ub388\ub389\ub38a\ub38b\ub38c\ub38d\ub38e\ub38f\ub390\ub391\ub392\ub393\ub394\ub395\ub396\ub397\ub398\ub399\ub39a\ub39b\ub39c\ub39d\ub39e\ub39f\ub3a0\ub3a1\ub3a2\ub3a3\ub3a4\ub3a5\ub3a6\ub3a7\ub3a8\ub3a9\ub3aa\ub3ab\ub3ac\ub3ad\ub3ae\ub3af\ub3b0\ub3b1\ub3b2\ub3b3\ub3b4\ub3b5\ub3b6\ub3b7\ub3b8\ub3b9\ub3ba\ub3bb\ub3bc\ub3bd\ub3be\ub3bf\ub3c0\ub3c1\ub3c2\ub3c3\ub3c4\ub3c5\ub3c6\ub3c7\ub3c8\ub3c9\ub3ca\ub3cb\ub3cc\ub3cd\ub3ce\ub3cf\ub3d0\ub3d1\ub3d2\ub3d3\ub3d4\ub3d5\ub3d6\ub3d7\ub3d8\ub3d9\ub3da\ub3db\ub3dc\ub3dd\ub3de\ub3df\ub3e0\ub3e1\ub3e2\ub3e3\ub3e4\ub3e5\ub3e6\ub3e7\ub3e8\ub3e9\ub3ea\ub3eb\ub3ec\ub3ed\ub3ee\ub3ef\ub3f0\ub3f1\ub3f2\ub3f3\ub3f4\ub3f5\ub3f6\ub3f7\ub3f8\ub3f9\ub3fa\ub3fb\ub3fc\ub3fd\ub3fe\ub3ff\ub400\ub401\ub402\ub403\ub404\ub405\ub406\ub407\ub408\ub409\ub40a\ub40b\ub40c\ub40d\ub40e\ub40f\ub410\ub411\ub412\ub413\ub414\ub415\ub416\ub417\ub418\ub419\ub41a\ub41b\ub41c\ub41d\ub41e\ub41f\ub420\ub421\ub422\ub423\ub424\ub425\ub426\ub427\ub428\ub429\ub42a\ub42b\ub42c\ub42d\ub42e\ub42f\ub430\ub431\ub432\ub433\ub434\ub435\ub436\ub437\ub438\ub439\ub43a\ub43b\ub43c\ub43d\ub43e\ub43f\ub440\ub441\ub442\ub443\ub444\ub445\ub446\ub447\ub448\ub449\ub44a\ub44b\ub44c\ub44d\ub44e\ub44f\ub450\ub451\ub452\ub453\ub454\ub455\ub456\ub457\ub458\ub459\ub45a\ub45b\ub45c\ub45d\ub45e\ub45f\ub460\ub461\ub462\ub463\ub464\ub465\ub466\ub467\ub468\ub469\ub46a\ub46b\ub46c\ub46d\ub46e\ub46f\ub470\ub471\ub472\ub473\ub474\ub475\ub476\ub477\ub478\ub479\ub47a\ub47b\ub47c\ub47d\ub47e\ub47f\ub480\ub481\ub482\ub483\ub484\ub485\ub486\ub487\ub488\ub489\ub48a\ub48b\ub48c\ub48d\ub48e\ub48f\ub490\ub491\ub492\ub493\ub494\ub495\ub496\ub497\ub498\ub499\ub49a\ub49b\ub49c\ub49d\ub49e\ub49f\ub4a0\ub4a1\ub4a2\ub4a3\ub4a4\ub4a5\ub4a6\ub4a7\ub4a8\ub4a9\ub4aa\ub4ab\ub4ac\ub4ad\ub4ae\ub4af\ub4b0\ub4b1\ub4b2\ub4b3\ub4b4\ub4b5\ub4b6\ub4b7\ub4b8\ub4b9\ub4ba\ub4bb\ub4bc\ub4bd\ub4be\ub4bf\ub4c0\ub4c1\ub4c2\ub4c3\ub4c4\ub4c5\ub4c6\ub4c7\ub4c8\ub4c9\ub4ca\ub4cb\ub4cc\ub4cd\ub4ce\ub4cf\ub4d0\ub4d1\ub4d2\ub4d3\ub4d4\ub4d5\ub4d6\ub4d7\ub4d8\ub4d9\ub4da\ub4db\ub4dc\ub4dd\ub4de\ub4df\ub4e0\ub4e1\ub4e2\ub4e3\ub4e4\ub4e5\ub4e6\ub4e7\ub4e8\ub4e9\ub4ea\ub4eb\ub4ec\ub4ed\ub4ee\ub4ef\ub4f0\ub4f1\ub4f2\ub4f3\ub4f4\ub4f5\ub4f6\ub4f7\ub4f8\ub4f9\ub4fa\ub4fb\ub4fc\ub4fd\ub4fe\ub4ff\ub500\ub501\ub502\ub503\ub504\ub505\ub506\ub507\ub508\ub509\ub50a\ub50b\ub50c\ub50d\ub50e\ub50f\ub510\ub511\ub512\ub513\ub514\ub515\ub516\ub517\ub518\ub519\ub51a\ub51b\ub51c\ub51d\ub51e\ub51f\ub520\ub521\ub522\ub523\ub524\ub525\ub526\ub527\ub528\ub529\ub52a\ub52b\ub52c\ub52d\ub52e\ub52f\ub530\ub531\ub532\ub533\ub534\ub535\ub536\ub537\ub538\ub539\ub53a\ub53b\ub53c\ub53d\ub53e\ub53f\ub540\ub541\ub542\ub543\ub544\ub545\ub546\ub547\ub548\ub549\ub54a\ub54b\ub54c\ub54d\ub54e\ub54f\ub550\ub551\ub552\ub553\ub554\ub555\ub556\ub557\ub558\ub559\ub55a\ub55b\ub55c\ub55d\ub55e\ub55f\ub560\ub561\ub562\ub563\ub564\ub565\ub566\ub567\ub568\ub569\ub56a\ub56b\ub56c\ub56d\ub56e\ub56f\ub570\ub571\ub572\ub573\ub574\ub575\ub576\ub577\ub578\ub579\ub57a\ub57b\ub57c\ub57d\ub57e\ub57f\ub580\ub581\ub582\ub583\ub584\ub585\ub586\ub587\ub588\ub589\ub58a\ub58b\ub58c\ub58d\ub58e\ub58f\ub590\ub591\ub592\ub593\ub594\ub595\ub596\ub597\ub598\ub599\ub59a\ub59b\ub59c\ub59d\ub59e\ub59f\ub5a0\ub5a1\ub5a2\ub5a3\ub5a4\ub5a5\ub5a6\ub5a7\ub5a8\ub5a9\ub5aa\ub5ab\ub5ac\ub5ad\ub5ae\ub5af\ub5b0\ub5b1\ub5b2\ub5b3\ub5b4\ub5b5\ub5b6\ub5b7\ub5b8\ub5b9\ub5ba\ub5bb\ub5bc\ub5bd\ub5be\ub5bf\ub5c0\ub5c1\ub5c2\ub5c3\ub5c4\ub5c5\ub5c6\ub5c7\ub5c8\ub5c9\ub5ca\ub5cb\ub5cc\ub5cd\ub5ce\ub5cf\ub5d0\ub5d1\ub5d2\ub5d3\ub5d4\ub5d5\ub5d6\ub5d7\ub5d8\ub5d9\ub5da\ub5db\ub5dc\ub5dd\ub5de\ub5df\ub5e0\ub5e1\ub5e2\ub5e3\ub5e4\ub5e5\ub5e6\ub5e7\ub5e8\ub5e9\ub5ea\ub5eb\ub5ec\ub5ed\ub5ee\ub5ef\ub5f0\ub5f1\ub5f2\ub5f3\ub5f4\ub5f5\ub5f6\ub5f7\ub5f8\ub5f9\ub5fa\ub5fb\ub5fc\ub5fd\ub5fe\ub5ff\ub600\ub601\ub602\ub603\ub604\ub605\ub606\ub607\ub608\ub609\ub60a\ub60b\ub60c\ub60d\ub60e\ub60f\ub610\ub611\ub612\ub613\ub614\ub615\ub616\ub617\ub618\ub619\ub61a\ub61b\ub61c\ub61d\ub61e\ub61f\ub620\ub621\ub622\ub623\ub624\ub625\ub626\ub627\ub628\ub629\ub62a\ub62b\ub62c\ub62d\ub62e\ub62f\ub630\ub631\ub632\ub633\ub634\ub635\ub636\ub637\ub638\ub639\ub63a\ub63b\ub63c\ub63d\ub63e\ub63f\ub640\ub641\ub642\ub643\ub644\ub645\ub646\ub647\ub648\ub649\ub64a\ub64b\ub64c\ub64d\ub64e\ub64f\ub650\ub651\ub652\ub653\ub654\ub655\ub656\ub657\ub658\ub659\ub65a\ub65b\ub65c\ub65d\ub65e\ub65f\ub660\ub661\ub662\ub663\ub664\ub665\ub666\ub667\ub668\ub669\ub66a\ub66b\ub66c\ub66d\ub66e\ub66f\ub670\ub671\ub672\ub673\ub674\ub675\ub676\ub677\ub678\ub679\ub67a\ub67b\ub67c\ub67d\ub67e\ub67f\ub680\ub681\ub682\ub683\ub684\ub685\ub686\ub687\ub688\ub689\ub68a\ub68b\ub68c\ub68d\ub68e\ub68f\ub690\ub691\ub692\ub693\ub694\ub695\ub696\ub697\ub698\ub699\ub69a\ub69b\ub69c\ub69d\ub69e\ub69f\ub6a0\ub6a1\ub6a2\ub6a3\ub6a4\ub6a5\ub6a6\ub6a7\ub6a8\ub6a9\ub6aa\ub6ab\ub6ac\ub6ad\ub6ae\ub6af\ub6b0\ub6b1\ub6b2\ub6b3\ub6b4\ub6b5\ub6b6\ub6b7\ub6b8\ub6b9\ub6ba\ub6bb\ub6bc\ub6bd\ub6be\ub6bf\ub6c0\ub6c1\ub6c2\ub6c3\ub6c4\ub6c5\ub6c6\ub6c7\ub6c8\ub6c9\ub6ca\ub6cb\ub6cc\ub6cd\ub6ce\ub6cf\ub6d0\ub6d1\ub6d2\ub6d3\ub6d4\ub6d5\ub6d6\ub6d7\ub6d8\ub6d9\ub6da\ub6db\ub6dc\ub6dd\ub6de\ub6df\ub6e0\ub6e1\ub6e2\ub6e3\ub6e4\ub6e5\ub6e6\ub6e7\ub6e8\ub6e9\ub6ea\ub6eb\ub6ec\ub6ed\ub6ee\ub6ef\ub6f0\ub6f1\ub6f2\ub6f3\ub6f4\ub6f5\ub6f6\ub6f7\ub6f8\ub6f9\ub6fa\ub6fb\ub6fc\ub6fd\ub6fe\ub6ff\ub700\ub701\ub702\ub703\ub704\ub705\ub706\ub707\ub708\ub709\ub70a\ub70b\ub70c\ub70d\ub70e\ub70f\ub710\ub711\ub712\ub713\ub714\ub715\ub716\ub717\ub718\ub719\ub71a\ub71b\ub71c\ub71d\ub71e\ub71f\ub720\ub721\ub722\ub723\ub724\ub725\ub726\ub727\ub728\ub729\ub72a\ub72b\ub72c\ub72d\ub72e\ub72f\ub730\ub731\ub732\ub733\ub734\ub735\ub736\ub737\ub738\ub739\ub73a\ub73b\ub73c\ub73d\ub73e\ub73f\ub740\ub741\ub742\ub743\ub744\ub745\ub746\ub747\ub748\ub749\ub74a\ub74b\ub74c\ub74d\ub74e\ub74f\ub750\ub751\ub752\ub753\ub754\ub755\ub756\ub757\ub758\ub759\ub75a\ub75b\ub75c\ub75d\ub75e\ub75f\ub760\ub761\ub762\ub763\ub764\ub765\ub766\ub767\ub768\ub769\ub76a\ub76b\ub76c\ub76d\ub76e\ub76f\ub770\ub771\ub772\ub773\ub774\ub775\ub776\ub777\ub778\ub779\ub77a\ub77b\ub77c\ub77d\ub77e\ub77f\ub780\ub781\ub782\ub783\ub784\ub785\ub786\ub787\ub788\ub789\ub78a\ub78b\ub78c\ub78d\ub78e\ub78f\ub790\ub791\ub792\ub793\ub794\ub795\ub796\ub797\ub798\ub799\ub79a\ub79b\ub79c\ub79d\ub79e\ub79f\ub7a0\ub7a1\ub7a2\ub7a3\ub7a4\ub7a5\ub7a6\ub7a7\ub7a8\ub7a9\ub7aa\ub7ab\ub7ac\ub7ad\ub7ae\ub7af\ub7b0\ub7b1\ub7b2\ub7b3\ub7b4\ub7b5\ub7b6\ub7b7\ub7b8\ub7b9\ub7ba\ub7bb\ub7bc\ub7bd\ub7be\ub7bf\ub7c0\ub7c1\ub7c2\ub7c3\ub7c4\ub7c5\ub7c6\ub7c7\ub7c8\ub7c9\ub7ca\ub7cb\ub7cc\ub7cd\ub7ce\ub7cf\ub7d0\ub7d1\ub7d2\ub7d3\ub7d4\ub7d5\ub7d6\ub7d7\ub7d8\ub7d9\ub7da\ub7db\ub7dc\ub7dd\ub7de\ub7df\ub7e0\ub7e1\ub7e2\ub7e3\ub7e4\ub7e5\ub7e6\ub7e7\ub7e8\ub7e9\ub7ea\ub7eb\ub7ec\ub7ed\ub7ee\ub7ef\ub7f0\ub7f1\ub7f2\ub7f3\ub7f4\ub7f5\ub7f6\ub7f7\ub7f8\ub7f9\ub7fa\ub7fb\ub7fc\ub7fd\ub7fe\ub7ff\ub800\ub801\ub802\ub803\ub804\ub805\ub806\ub807\ub808\ub809\ub80a\ub80b\ub80c\ub80d\ub80e\ub80f\ub810\ub811\ub812\ub813\ub814\ub815\ub816\ub817\ub818\ub819\ub81a\ub81b\ub81c\ub81d\ub81e\ub81f\ub820\ub821\ub822\ub823\ub824\ub825\ub826\ub827\ub828\ub829\ub82a\ub82b\ub82c\ub82d\ub82e\ub82f\ub830\ub831\ub832\ub833\ub834\ub835\ub836\ub837\ub838\ub839\ub83a\ub83b\ub83c\ub83d\ub83e\ub83f\ub840\ub841\ub842\ub843\ub844\ub845\ub846\ub847\ub848\ub849\ub84a\ub84b\ub84c\ub84d\ub84e\ub84f\ub850\ub851\ub852\ub853\ub854\ub855\ub856\ub857\ub858\ub859\ub85a\ub85b\ub85c\ub85d\ub85e\ub85f\ub860\ub861\ub862\ub863\ub864\ub865\ub866\ub867\ub868\ub869\ub86a\ub86b\ub86c\ub86d\ub86e\ub86f\ub870\ub871\ub872\ub873\ub874\ub875\ub876\ub877\ub878\ub879\ub87a\ub87b\ub87c\ub87d\ub87e\ub87f\ub880\ub881\ub882\ub883\ub884\ub885\ub886\ub887\ub888\ub889\ub88a\ub88b\ub88c\ub88d\ub88e\ub88f\ub890\ub891\ub892\ub893\ub894\ub895\ub896\ub897\ub898\ub899\ub89a\ub89b\ub89c\ub89d\ub89e\ub89f\ub8a0\ub8a1\ub8a2\ub8a3\ub8a4\ub8a5\ub8a6\ub8a7\ub8a8\ub8a9\ub8aa\ub8ab\ub8ac\ub8ad\ub8ae\ub8af\ub8b0\ub8b1\ub8b2\ub8b3\ub8b4\ub8b5\ub8b6\ub8b7\ub8b8\ub8b9\ub8ba\ub8bb\ub8bc\ub8bd\ub8be\ub8bf\ub8c0\ub8c1\ub8c2\ub8c3\ub8c4\ub8c5\ub8c6\ub8c7\ub8c8\ub8c9\ub8ca\ub8cb\ub8cc\ub8cd\ub8ce\ub8cf\ub8d0\ub8d1\ub8d2\ub8d3\ub8d4\ub8d5\ub8d6\ub8d7\ub8d8\ub8d9\ub8da\ub8db\ub8dc\ub8dd\ub8de\ub8df\ub8e0\ub8e1\ub8e2\ub8e3\ub8e4\ub8e5\ub8e6\ub8e7\ub8e8\ub8e9\ub8ea\ub8eb\ub8ec\ub8ed\ub8ee\ub8ef\ub8f0\ub8f1\ub8f2\ub8f3\ub8f4\ub8f5\ub8f6\ub8f7\ub8f8\ub8f9\ub8fa\ub8fb\ub8fc\ub8fd\ub8fe\ub8ff\ub900\ub901\ub902\ub903\ub904\ub905\ub906\ub907\ub908\ub909\ub90a\ub90b\ub90c\ub90d\ub90e\ub90f\ub910\ub911\ub912\ub913\ub914\ub915\ub916\ub917\ub918\ub919\ub91a\ub91b\ub91c\ub91d\ub91e\ub91f\ub920\ub921\ub922\ub923\ub924\ub925\ub926\ub927\ub928\ub929\ub92a\ub92b\ub92c\ub92d\ub92e\ub92f\ub930\ub931\ub932\ub933\ub934\ub935\ub936\ub937\ub938\ub939\ub93a\ub93b\ub93c\ub93d\ub93e\ub93f\ub940\ub941\ub942\ub943\ub944\ub945\ub946\ub947\ub948\ub949\ub94a\ub94b\ub94c\ub94d\ub94e\ub94f\ub950\ub951\ub952\ub953\ub954\ub955\ub956\ub957\ub958\ub959\ub95a\ub95b\ub95c\ub95d\ub95e\ub95f\ub960\ub961\ub962\ub963\ub964\ub965\ub966\ub967\ub968\ub969\ub96a\ub96b\ub96c\ub96d\ub96e\ub96f\ub970\ub971\ub972\ub973\ub974\ub975\ub976\ub977\ub978\ub979\ub97a\ub97b\ub97c\ub97d\ub97e\ub97f\ub980\ub981\ub982\ub983\ub984\ub985\ub986\ub987\ub988\ub989\ub98a\ub98b\ub98c\ub98d\ub98e\ub98f\ub990\ub991\ub992\ub993\ub994\ub995\ub996\ub997\ub998\ub999\ub99a\ub99b\ub99c\ub99d\ub99e\ub99f\ub9a0\ub9a1\ub9a2\ub9a3\ub9a4\ub9a5\ub9a6\ub9a7\ub9a8\ub9a9\ub9aa\ub9ab\ub9ac\ub9ad\ub9ae\ub9af\ub9b0\ub9b1\ub9b2\ub9b3\ub9b4\ub9b5\ub9b6\ub9b7\ub9b8\ub9b9\ub9ba\ub9bb\ub9bc\ub9bd\ub9be\ub9bf\ub9c0\ub9c1\ub9c2\ub9c3\ub9c4\ub9c5\ub9c6\ub9c7\ub9c8\ub9c9\ub9ca\ub9cb\ub9cc\ub9cd\ub9ce\ub9cf\ub9d0\ub9d1\ub9d2\ub9d3\ub9d4\ub9d5\ub9d6\ub9d7\ub9d8\ub9d9\ub9da\ub9db\ub9dc\ub9dd\ub9de\ub9df\ub9e0\ub9e1\ub9e2\ub9e3\ub9e4\ub9e5\ub9e6\ub9e7\ub9e8\ub9e9\ub9ea\ub9eb\ub9ec\ub9ed\ub9ee\ub9ef\ub9f0\ub9f1\ub9f2\ub9f3\ub9f4\ub9f5\ub9f6\ub9f7\ub9f8\ub9f9\ub9fa\ub9fb\ub9fc\ub9fd\ub9fe\ub9ff\uba00\uba01\uba02\uba03\uba04\uba05\uba06\uba07\uba08\uba09\uba0a\uba0b\uba0c\uba0d\uba0e\uba0f\uba10\uba11\uba12\uba13\uba14\uba15\uba16\uba17\uba18\uba19\uba1a\uba1b\uba1c\uba1d\uba1e\uba1f\uba20\uba21\uba22\uba23\uba24\uba25\uba26\uba27\uba28\uba29\uba2a\uba2b\uba2c\uba2d\uba2e\uba2f\uba30\uba31\uba32\uba33\uba34\uba35\uba36\uba37\uba38\uba39\uba3a\uba3b\uba3c\uba3d\uba3e\uba3f\uba40\uba41\uba42\uba43\uba44\uba45\uba46\uba47\uba48\uba49\uba4a\uba4b\uba4c\uba4d\uba4e\uba4f\uba50\uba51\uba52\uba53\uba54\uba55\uba56\uba57\uba58\uba59\uba5a\uba5b\uba5c\uba5d\uba5e\uba5f\uba60\uba61\uba62\uba63\uba64\uba65\uba66\uba67\uba68\uba69\uba6a\uba6b\uba6c\uba6d\uba6e\uba6f\uba70\uba71\uba72\uba73\uba74\uba75\uba76\uba77\uba78\uba79\uba7a\uba7b\uba7c\uba7d\uba7e\uba7f\uba80\uba81\uba82\uba83\uba84\uba85\uba86\uba87\uba88\uba89\uba8a\uba8b\uba8c\uba8d\uba8e\uba8f\uba90\uba91\uba92\uba93\uba94\uba95\uba96\uba97\uba98\uba99\uba9a\uba9b\uba9c\uba9d\uba9e\uba9f\ubaa0\ubaa1\ubaa2\ubaa3\ubaa4\ubaa5\ubaa6\ubaa7\ubaa8\ubaa9\ubaaa\ubaab\ubaac\ubaad\ubaae\ubaaf\ubab0\ubab1\ubab2\ubab3\ubab4\ubab5\ubab6\ubab7\ubab8\ubab9\ubaba\ubabb\ubabc\ubabd\ubabe\ubabf\ubac0\ubac1\ubac2\ubac3\ubac4\ubac5\ubac6\ubac7\ubac8\ubac9\ubaca\ubacb\ubacc\ubacd\ubace\ubacf\ubad0\ubad1\ubad2\ubad3\ubad4\ubad5\ubad6\ubad7\ubad8\ubad9\ubada\ubadb\ubadc\ubadd\ubade\ubadf\ubae0\ubae1\ubae2\ubae3\ubae4\ubae5\ubae6\ubae7\ubae8\ubae9\ubaea\ubaeb\ubaec\ubaed\ubaee\ubaef\ubaf0\ubaf1\ubaf2\ubaf3\ubaf4\ubaf5\ubaf6\ubaf7\ubaf8\ubaf9\ubafa\ubafb\ubafc\ubafd\ubafe\ubaff\ubb00\ubb01\ubb02\ubb03\ubb04\ubb05\ubb06\ubb07\ubb08\ubb09\ubb0a\ubb0b\ubb0c\ubb0d\ubb0e\ubb0f\ubb10\ubb11\ubb12\ubb13\ubb14\ubb15\ubb16\ubb17\ubb18\ubb19\ubb1a\ubb1b\ubb1c\ubb1d\ubb1e\ubb1f\ubb20\ubb21\ubb22\ubb23\ubb24\ubb25\ubb26\ubb27\ubb28\ubb29\ubb2a\ubb2b\ubb2c\ubb2d\ubb2e\ubb2f\ubb30\ubb31\ubb32\ubb33\ubb34\ubb35\ubb36\ubb37\ubb38\ubb39\ubb3a\ubb3b\ubb3c\ubb3d\ubb3e\ubb3f\ubb40\ubb41\ubb42\ubb43\ubb44\ubb45\ubb46\ubb47\ubb48\ubb49\ubb4a\ubb4b\ubb4c\ubb4d\ubb4e\ubb4f\ubb50\ubb51\ubb52\ubb53\ubb54\ubb55\ubb56\ubb57\ubb58\ubb59\ubb5a\ubb5b\ubb5c\ubb5d\ubb5e\ubb5f\ubb60\ubb61\ubb62\ubb63\ubb64\ubb65\ubb66\ubb67\ubb68\ubb69\ubb6a\ubb6b\ubb6c\ubb6d\ubb6e\ubb6f\ubb70\ubb71\ubb72\ubb73\ubb74\ubb75\ubb76\ubb77\ubb78\ubb79\ubb7a\ubb7b\ubb7c\ubb7d\ubb7e\ubb7f\ubb80\ubb81\ubb82\ubb83\ubb84\ubb85\ubb86\ubb87\ubb88\ubb89\ubb8a\ubb8b\ubb8c\ubb8d\ubb8e\ubb8f\ubb90\ubb91\ubb92\ubb93\ubb94\ubb95\ubb96\ubb97\ubb98\ubb99\ubb9a\ubb9b\ubb9c\ubb9d\ubb9e\ubb9f\ubba0\ubba1\ubba2\ubba3\ubba4\ubba5\ubba6\ubba7\ubba8\ubba9\ubbaa\ubbab\ubbac\ubbad\ubbae\ubbaf\ubbb0\ubbb1\ubbb2\ubbb3\ubbb4\ubbb5\ubbb6\ubbb7\ubbb8\ubbb9\ubbba\ubbbb\ubbbc\ubbbd\ubbbe\ubbbf\ubbc0\ubbc1\ubbc2\ubbc3\ubbc4\ubbc5\ubbc6\ubbc7\ubbc8\ubbc9\ubbca\ubbcb\ubbcc\ubbcd\ubbce\ubbcf\ubbd0\ubbd1\ubbd2\ubbd3\ubbd4\ubbd5\ubbd6\ubbd7\ubbd8\ubbd9\ubbda\ubbdb\ubbdc\ubbdd\ubbde\ubbdf\ubbe0\ubbe1\ubbe2\ubbe3\ubbe4\ubbe5\ubbe6\ubbe7\ubbe8\ubbe9\ubbea\ubbeb\ubbec\ubbed\ubbee\ubbef\ubbf0\ubbf1\ubbf2\ubbf3\ubbf4\ubbf5\ubbf6\ubbf7\ubbf8\ubbf9\ubbfa\ubbfb\ubbfc\ubbfd\ubbfe\ubbff\ubc00\ubc01\ubc02\ubc03\ubc04\ubc05\ubc06\ubc07\ubc08\ubc09\ubc0a\ubc0b\ubc0c\ubc0d\ubc0e\ubc0f\ubc10\ubc11\ubc12\ubc13\ubc14\ubc15\ubc16\ubc17\ubc18\ubc19\ubc1a\ubc1b\ubc1c\ubc1d\ubc1e\ubc1f\ubc20\ubc21\ubc22\ubc23\ubc24\ubc25\ubc26\ubc27\ubc28\ubc29\ubc2a\ubc2b\ubc2c\ubc2d\ubc2e\ubc2f\ubc30\ubc31\ubc32\ubc33\ubc34\ubc35\ubc36\ubc37\ubc38\ubc39\ubc3a\ubc3b\ubc3c\ubc3d\ubc3e\ubc3f\ubc40\ubc41\ubc42\ubc43\ubc44\ubc45\ubc46\ubc47\ubc48\ubc49\ubc4a\ubc4b\ubc4c\ubc4d\ubc4e\ubc4f\ubc50\ubc51\ubc52\ubc53\ubc54\ubc55\ubc56\ubc57\ubc58\ubc59\ubc5a\ubc5b\ubc5c\ubc5d\ubc5e\ubc5f\ubc60\ubc61\ubc62\ubc63\ubc64\ubc65\ubc66\ubc67\ubc68\ubc69\ubc6a\ubc6b\ubc6c\ubc6d\ubc6e\ubc6f\ubc70\ubc71\ubc72\ubc73\ubc74\ubc75\ubc76\ubc77\ubc78\ubc79\ubc7a\ubc7b\ubc7c\ubc7d\ubc7e\ubc7f\ubc80\ubc81\ubc82\ubc83\ubc84\ubc85\ubc86\ubc87\ubc88\ubc89\ubc8a\ubc8b\ubc8c\ubc8d\ubc8e\ubc8f\ubc90\ubc91\ubc92\ubc93\ubc94\ubc95\ubc96\ubc97\ubc98\ubc99\ubc9a\ubc9b\ubc9c\ubc9d\ubc9e\ubc9f\ubca0\ubca1\ubca2\ubca3\ubca4\ubca5\ubca6\ubca7\ubca8\ubca9\ubcaa\ubcab\ubcac\ubcad\ubcae\ubcaf\ubcb0\ubcb1\ubcb2\ubcb3\ubcb4\ubcb5\ubcb6\ubcb7\ubcb8\ubcb9\ubcba\ubcbb\ubcbc\ubcbd\ubcbe\ubcbf\ubcc0\ubcc1\ubcc2\ubcc3\ubcc4\ubcc5\ubcc6\ubcc7\ubcc8\ubcc9\ubcca\ubccb\ubccc\ubccd\ubcce\ubccf\ubcd0\ubcd1\ubcd2\ubcd3\ubcd4\ubcd5\ubcd6\ubcd7\ubcd8\ubcd9\ubcda\ubcdb\ubcdc\ubcdd\ubcde\ubcdf\ubce0\ubce1\ubce2\ubce3\ubce4\ubce5\ubce6\ubce7\ubce8\ubce9\ubcea\ubceb\ubcec\ubced\ubcee\ubcef\ubcf0\ubcf1\ubcf2\ubcf3\ubcf4\ubcf5\ubcf6\ubcf7\ubcf8\ubcf9\ubcfa\ubcfb\ubcfc\ubcfd\ubcfe\ubcff\ubd00\ubd01\ubd02\ubd03\ubd04\ubd05\ubd06\ubd07\ubd08\ubd09\ubd0a\ubd0b\ubd0c\ubd0d\ubd0e\ubd0f\ubd10\ubd11\ubd12\ubd13\ubd14\ubd15\ubd16\ubd17\ubd18\ubd19\ubd1a\ubd1b\ubd1c\ubd1d\ubd1e\ubd1f\ubd20\ubd21\ubd22\ubd23\ubd24\ubd25\ubd26\ubd27\ubd28\ubd29\ubd2a\ubd2b\ubd2c\ubd2d\ubd2e\ubd2f\ubd30\ubd31\ubd32\ubd33\ubd34\ubd35\ubd36\ubd37\ubd38\ubd39\ubd3a\ubd3b\ubd3c\ubd3d\ubd3e\ubd3f\ubd40\ubd41\ubd42\ubd43\ubd44\ubd45\ubd46\ubd47\ubd48\ubd49\ubd4a\ubd4b\ubd4c\ubd4d\ubd4e\ubd4f\ubd50\ubd51\ubd52\ubd53\ubd54\ubd55\ubd56\ubd57\ubd58\ubd59\ubd5a\ubd5b\ubd5c\ubd5d\ubd5e\ubd5f\ubd60\ubd61\ubd62\ubd63\ubd64\ubd65\ubd66\ubd67\ubd68\ubd69\ubd6a\ubd6b\ubd6c\ubd6d\ubd6e\ubd6f\ubd70\ubd71\ubd72\ubd73\ubd74\ubd75\ubd76\ubd77\ubd78\ubd79\ubd7a\ubd7b\ubd7c\ubd7d\ubd7e\ubd7f\ubd80\ubd81\ubd82\ubd83\ubd84\ubd85\ubd86\ubd87\ubd88\ubd89\ubd8a\ubd8b\ubd8c\ubd8d\ubd8e\ubd8f\ubd90\ubd91\ubd92\ubd93\ubd94\ubd95\ubd96\ubd97\ubd98\ubd99\ubd9a\ubd9b\ubd9c\ubd9d\ubd9e\ubd9f\ubda0\ubda1\ubda2\ubda3\ubda4\ubda5\ubda6\ubda7\ubda8\ubda9\ubdaa\ubdab\ubdac\ubdad\ubdae\ubdaf\ubdb0\ubdb1\ubdb2\ubdb3\ubdb4\ubdb5\ubdb6\ubdb7\ubdb8\ubdb9\ubdba\ubdbb\ubdbc\ubdbd\ubdbe\ubdbf\ubdc0\ubdc1\ubdc2\ubdc3\ubdc4\ubdc5\ubdc6\ubdc7\ubdc8\ubdc9\ubdca\ubdcb\ubdcc\ubdcd\ubdce\ubdcf\ubdd0\ubdd1\ubdd2\ubdd3\ubdd4\ubdd5\ubdd6\ubdd7\ubdd8\ubdd9\ubdda\ubddb\ubddc\ubddd\ubdde\ubddf\ubde0\ubde1\ubde2\ubde3\ubde4\ubde5\ubde6\ubde7\ubde8\ubde9\ubdea\ubdeb\ubdec\ubded\ubdee\ubdef\ubdf0\ubdf1\ubdf2\ubdf3\ubdf4\ubdf5\ubdf6\ubdf7\ubdf8\ubdf9\ubdfa\ubdfb\ubdfc\ubdfd\ubdfe\ubdff\ube00\ube01\ube02\ube03\ube04\ube05\ube06\ube07\ube08\ube09\ube0a\ube0b\ube0c\ube0d\ube0e\ube0f\ube10\ube11\ube12\ube13\ube14\ube15\ube16\ube17\ube18\ube19\ube1a\ube1b\ube1c\ube1d\ube1e\ube1f\ube20\ube21\ube22\ube23\ube24\ube25\ube26\ube27\ube28\ube29\ube2a\ube2b\ube2c\ube2d\ube2e\ube2f\ube30\ube31\ube32\ube33\ube34\ube35\ube36\ube37\ube38\ube39\ube3a\ube3b\ube3c\ube3d\ube3e\ube3f\ube40\ube41\ube42\ube43\ube44\ube45\ube46\ube47\ube48\ube49\ube4a\ube4b\ube4c\ube4d\ube4e\ube4f\ube50\ube51\ube52\ube53\ube54\ube55\ube56\ube57\ube58\ube59\ube5a\ube5b\ube5c\ube5d\ube5e\ube5f\ube60\ube61\ube62\ube63\ube64\ube65\ube66\ube67\ube68\ube69\ube6a\ube6b\ube6c\ube6d\ube6e\ube6f\ube70\ube71\ube72\ube73\ube74\ube75\ube76\ube77\ube78\ube79\ube7a\ube7b\ube7c\ube7d\ube7e\ube7f\ube80\ube81\ube82\ube83\ube84\ube85\ube86\ube87\ube88\ube89\ube8a\ube8b\ube8c\ube8d\ube8e\ube8f\ube90\ube91\ube92\ube93\ube94\ube95\ube96\ube97\ube98\ube99\ube9a\ube9b\ube9c\ube9d\ube9e\ube9f\ubea0\ubea1\ubea2\ubea3\ubea4\ubea5\ubea6\ubea7\ubea8\ubea9\ubeaa\ubeab\ubeac\ubead\ubeae\ubeaf\ubeb0\ubeb1\ubeb2\ubeb3\ubeb4\ubeb5\ubeb6\ubeb7\ubeb8\ubeb9\ubeba\ubebb\ubebc\ubebd\ubebe\ubebf\ubec0\ubec1\ubec2\ubec3\ubec4\ubec5\ubec6\ubec7\ubec8\ubec9\ubeca\ubecb\ubecc\ubecd\ubece\ubecf\ubed0\ubed1\ubed2\ubed3\ubed4\ubed5\ubed6\ubed7\ubed8\ubed9\ubeda\ubedb\ubedc\ubedd\ubede\ubedf\ubee0\ubee1\ubee2\ubee3\ubee4\ubee5\ubee6\ubee7\ubee8\ubee9\ubeea\ubeeb\ubeec\ubeed\ubeee\ubeef\ubef0\ubef1\ubef2\ubef3\ubef4\ubef5\ubef6\ubef7\ubef8\ubef9\ubefa\ubefb\ubefc\ubefd\ubefe\ubeff\ubf00\ubf01\ubf02\ubf03\ubf04\ubf05\ubf06\ubf07\ubf08\ubf09\ubf0a\ubf0b\ubf0c\ubf0d\ubf0e\ubf0f\ubf10\ubf11\ubf12\ubf13\ubf14\ubf15\ubf16\ubf17\ubf18\ubf19\ubf1a\ubf1b\ubf1c\ubf1d\ubf1e\ubf1f\ubf20\ubf21\ubf22\ubf23\ubf24\ubf25\ubf26\ubf27\ubf28\ubf29\ubf2a\ubf2b\ubf2c\ubf2d\ubf2e\ubf2f\ubf30\ubf31\ubf32\ubf33\ubf34\ubf35\ubf36\ubf37\ubf38\ubf39\ubf3a\ubf3b\ubf3c\ubf3d\ubf3e\ubf3f\ubf40\ubf41\ubf42\ubf43\ubf44\ubf45\ubf46\ubf47\ubf48\ubf49\ubf4a\ubf4b\ubf4c\ubf4d\ubf4e\ubf4f\ubf50\ubf51\ubf52\ubf53\ubf54\ubf55\ubf56\ubf57\ubf58\ubf59\ubf5a\ubf5b\ubf5c\ubf5d\ubf5e\ubf5f\ubf60\ubf61\ubf62\ubf63\ubf64\ubf65\ubf66\ubf67\ubf68\ubf69\ubf6a\ubf6b\ubf6c\ubf6d\ubf6e\ubf6f\ubf70\ubf71\ubf72\ubf73\ubf74\ubf75\ubf76\ubf77\ubf78\ubf79\ubf7a\ubf7b\ubf7c\ubf7d\ubf7e\ubf7f\ubf80\ubf81\ubf82\ubf83\ubf84\ubf85\ubf86\ubf87\ubf88\ubf89\ubf8a\ubf8b\ubf8c\ubf8d\ubf8e\ubf8f\ubf90\ubf91\ubf92\ubf93\ubf94\ubf95\ubf96\ubf97\ubf98\ubf99\ubf9a\ubf9b\ubf9c\ubf9d\ubf9e\ubf9f\ubfa0\ubfa1\ubfa2\ubfa3\ubfa4\ubfa5\ubfa6\ubfa7\ubfa8\ubfa9\ubfaa\ubfab\ubfac\ubfad\ubfae\ubfaf\ubfb0\ubfb1\ubfb2\ubfb3\ubfb4\ubfb5\ubfb6\ubfb7\ubfb8\ubfb9\ubfba\ubfbb\ubfbc\ubfbd\ubfbe\ubfbf\ubfc0\ubfc1\ubfc2\ubfc3\ubfc4\ubfc5\ubfc6\ubfc7\ubfc8\ubfc9\ubfca\ubfcb\ubfcc\ubfcd\ubfce\ubfcf\ubfd0\ubfd1\ubfd2\ubfd3\ubfd4\ubfd5\ubfd6\ubfd7\ubfd8\ubfd9\ubfda\ubfdb\ubfdc\ubfdd\ubfde\ubfdf\ubfe0\ubfe1\ubfe2\ubfe3\ubfe4\ubfe5\ubfe6\ubfe7\ubfe8\ubfe9\ubfea\ubfeb\ubfec\ubfed\ubfee\ubfef\ubff0\ubff1\ubff2\ubff3\ubff4\ubff5\ubff6\ubff7\ubff8\ubff9\ubffa\ubffb\ubffc\ubffd\ubffe\ubfff\uc000\uc001\uc002\uc003\uc004\uc005\uc006\uc007\uc008\uc009\uc00a\uc00b\uc00c\uc00d\uc00e\uc00f\uc010\uc011\uc012\uc013\uc014\uc015\uc016\uc017\uc018\uc019\uc01a\uc01b\uc01c\uc01d\uc01e\uc01f\uc020\uc021\uc022\uc023\uc024\uc025\uc026\uc027\uc028\uc029\uc02a\uc02b\uc02c\uc02d\uc02e\uc02f\uc030\uc031\uc032\uc033\uc034\uc035\uc036\uc037\uc038\uc039\uc03a\uc03b\uc03c\uc03d\uc03e\uc03f\uc040\uc041\uc042\uc043\uc044\uc045\uc046\uc047\uc048\uc049\uc04a\uc04b\uc04c\uc04d\uc04e\uc04f\uc050\uc051\uc052\uc053\uc054\uc055\uc056\uc057\uc058\uc059\uc05a\uc05b\uc05c\uc05d\uc05e\uc05f\uc060\uc061\uc062\uc063\uc064\uc065\uc066\uc067\uc068\uc069\uc06a\uc06b\uc06c\uc06d\uc06e\uc06f\uc070\uc071\uc072\uc073\uc074\uc075\uc076\uc077\uc078\uc079\uc07a\uc07b\uc07c\uc07d\uc07e\uc07f\uc080\uc081\uc082\uc083\uc084\uc085\uc086\uc087\uc088\uc089\uc08a\uc08b\uc08c\uc08d\uc08e\uc08f\uc090\uc091\uc092\uc093\uc094\uc095\uc096\uc097\uc098\uc099\uc09a\uc09b\uc09c\uc09d\uc09e\uc09f\uc0a0\uc0a1\uc0a2\uc0a3\uc0a4\uc0a5\uc0a6\uc0a7\uc0a8\uc0a9\uc0aa\uc0ab\uc0ac\uc0ad\uc0ae\uc0af\uc0b0\uc0b1\uc0b2\uc0b3\uc0b4\uc0b5\uc0b6\uc0b7\uc0b8\uc0b9\uc0ba\uc0bb\uc0bc\uc0bd\uc0be\uc0bf\uc0c0\uc0c1\uc0c2\uc0c3\uc0c4\uc0c5\uc0c6\uc0c7\uc0c8\uc0c9\uc0ca\uc0cb\uc0cc\uc0cd\uc0ce\uc0cf\uc0d0\uc0d1\uc0d2\uc0d3\uc0d4\uc0d5\uc0d6\uc0d7\uc0d8\uc0d9\uc0da\uc0db\uc0dc\uc0dd\uc0de\uc0df\uc0e0\uc0e1\uc0e2\uc0e3\uc0e4\uc0e5\uc0e6\uc0e7\uc0e8\uc0e9\uc0ea\uc0eb\uc0ec\uc0ed\uc0ee\uc0ef\uc0f0\uc0f1\uc0f2\uc0f3\uc0f4\uc0f5\uc0f6\uc0f7\uc0f8\uc0f9\uc0fa\uc0fb\uc0fc\uc0fd\uc0fe\uc0ff\uc100\uc101\uc102\uc103\uc104\uc105\uc106\uc107\uc108\uc109\uc10a\uc10b\uc10c\uc10d\uc10e\uc10f\uc110\uc111\uc112\uc113\uc114\uc115\uc116\uc117\uc118\uc119\uc11a\uc11b\uc11c\uc11d\uc11e\uc11f\uc120\uc121\uc122\uc123\uc124\uc125\uc126\uc127\uc128\uc129\uc12a\uc12b\uc12c\uc12d\uc12e\uc12f\uc130\uc131\uc132\uc133\uc134\uc135\uc136\uc137\uc138\uc139\uc13a\uc13b\uc13c\uc13d\uc13e\uc13f\uc140\uc141\uc142\uc143\uc144\uc145\uc146\uc147\uc148\uc149\uc14a\uc14b\uc14c\uc14d\uc14e\uc14f\uc150\uc151\uc152\uc153\uc154\uc155\uc156\uc157\uc158\uc159\uc15a\uc15b\uc15c\uc15d\uc15e\uc15f\uc160\uc161\uc162\uc163\uc164\uc165\uc166\uc167\uc168\uc169\uc16a\uc16b\uc16c\uc16d\uc16e\uc16f\uc170\uc171\uc172\uc173\uc174\uc175\uc176\uc177\uc178\uc179\uc17a\uc17b\uc17c\uc17d\uc17e\uc17f\uc180\uc181\uc182\uc183\uc184\uc185\uc186\uc187\uc188\uc189\uc18a\uc18b\uc18c\uc18d\uc18e\uc18f\uc190\uc191\uc192\uc193\uc194\uc195\uc196\uc197\uc198\uc199\uc19a\uc19b\uc19c\uc19d\uc19e\uc19f\uc1a0\uc1a1\uc1a2\uc1a3\uc1a4\uc1a5\uc1a6\uc1a7\uc1a8\uc1a9\uc1aa\uc1ab\uc1ac\uc1ad\uc1ae\uc1af\uc1b0\uc1b1\uc1b2\uc1b3\uc1b4\uc1b5\uc1b6\uc1b7\uc1b8\uc1b9\uc1ba\uc1bb\uc1bc\uc1bd\uc1be\uc1bf\uc1c0\uc1c1\uc1c2\uc1c3\uc1c4\uc1c5\uc1c6\uc1c7\uc1c8\uc1c9\uc1ca\uc1cb\uc1cc\uc1cd\uc1ce\uc1cf\uc1d0\uc1d1\uc1d2\uc1d3\uc1d4\uc1d5\uc1d6\uc1d7\uc1d8\uc1d9\uc1da\uc1db\uc1dc\uc1dd\uc1de\uc1df\uc1e0\uc1e1\uc1e2\uc1e3\uc1e4\uc1e5\uc1e6\uc1e7\uc1e8\uc1e9\uc1ea\uc1eb\uc1ec\uc1ed\uc1ee\uc1ef\uc1f0\uc1f1\uc1f2\uc1f3\uc1f4\uc1f5\uc1f6\uc1f7\uc1f8\uc1f9\uc1fa\uc1fb\uc1fc\uc1fd\uc1fe\uc1ff\uc200\uc201\uc202\uc203\uc204\uc205\uc206\uc207\uc208\uc209\uc20a\uc20b\uc20c\uc20d\uc20e\uc20f\uc210\uc211\uc212\uc213\uc214\uc215\uc216\uc217\uc218\uc219\uc21a\uc21b\uc21c\uc21d\uc21e\uc21f\uc220\uc221\uc222\uc223\uc224\uc225\uc226\uc227\uc228\uc229\uc22a\uc22b\uc22c\uc22d\uc22e\uc22f\uc230\uc231\uc232\uc233\uc234\uc235\uc236\uc237\uc238\uc239\uc23a\uc23b\uc23c\uc23d\uc23e\uc23f\uc240\uc241\uc242\uc243\uc244\uc245\uc246\uc247\uc248\uc249\uc24a\uc24b\uc24c\uc24d\uc24e\uc24f\uc250\uc251\uc252\uc253\uc254\uc255\uc256\uc257\uc258\uc259\uc25a\uc25b\uc25c\uc25d\uc25e\uc25f\uc260\uc261\uc262\uc263\uc264\uc265\uc266\uc267\uc268\uc269\uc26a\uc26b\uc26c\uc26d\uc26e\uc26f\uc270\uc271\uc272\uc273\uc274\uc275\uc276\uc277\uc278\uc279\uc27a\uc27b\uc27c\uc27d\uc27e\uc27f\uc280\uc281\uc282\uc283\uc284\uc285\uc286\uc287\uc288\uc289\uc28a\uc28b\uc28c\uc28d\uc28e\uc28f\uc290\uc291\uc292\uc293\uc294\uc295\uc296\uc297\uc298\uc299\uc29a\uc29b\uc29c\uc29d\uc29e\uc29f\uc2a0\uc2a1\uc2a2\uc2a3\uc2a4\uc2a5\uc2a6\uc2a7\uc2a8\uc2a9\uc2aa\uc2ab\uc2ac\uc2ad\uc2ae\uc2af\uc2b0\uc2b1\uc2b2\uc2b3\uc2b4\uc2b5\uc2b6\uc2b7\uc2b8\uc2b9\uc2ba\uc2bb\uc2bc\uc2bd\uc2be\uc2bf\uc2c0\uc2c1\uc2c2\uc2c3\uc2c4\uc2c5\uc2c6\uc2c7\uc2c8\uc2c9\uc2ca\uc2cb\uc2cc\uc2cd\uc2ce\uc2cf\uc2d0\uc2d1\uc2d2\uc2d3\uc2d4\uc2d5\uc2d6\uc2d7\uc2d8\uc2d9\uc2da\uc2db\uc2dc\uc2dd\uc2de\uc2df\uc2e0\uc2e1\uc2e2\uc2e3\uc2e4\uc2e5\uc2e6\uc2e7\uc2e8\uc2e9\uc2ea\uc2eb\uc2ec\uc2ed\uc2ee\uc2ef\uc2f0\uc2f1\uc2f2\uc2f3\uc2f4\uc2f5\uc2f6\uc2f7\uc2f8\uc2f9\uc2fa\uc2fb\uc2fc\uc2fd\uc2fe\uc2ff\uc300\uc301\uc302\uc303\uc304\uc305\uc306\uc307\uc308\uc309\uc30a\uc30b\uc30c\uc30d\uc30e\uc30f\uc310\uc311\uc312\uc313\uc314\uc315\uc316\uc317\uc318\uc319\uc31a\uc31b\uc31c\uc31d\uc31e\uc31f\uc320\uc321\uc322\uc323\uc324\uc325\uc326\uc327\uc328\uc329\uc32a\uc32b\uc32c\uc32d\uc32e\uc32f\uc330\uc331\uc332\uc333\uc334\uc335\uc336\uc337\uc338\uc339\uc33a\uc33b\uc33c\uc33d\uc33e\uc33f\uc340\uc341\uc342\uc343\uc344\uc345\uc346\uc347\uc348\uc349\uc34a\uc34b\uc34c\uc34d\uc34e\uc34f\uc350\uc351\uc352\uc353\uc354\uc355\uc356\uc357\uc358\uc359\uc35a\uc35b\uc35c\uc35d\uc35e\uc35f\uc360\uc361\uc362\uc363\uc364\uc365\uc366\uc367\uc368\uc369\uc36a\uc36b\uc36c\uc36d\uc36e\uc36f\uc370\uc371\uc372\uc373\uc374\uc375\uc376\uc377\uc378\uc379\uc37a\uc37b\uc37c\uc37d\uc37e\uc37f\uc380\uc381\uc382\uc383\uc384\uc385\uc386\uc387\uc388\uc389\uc38a\uc38b\uc38c\uc38d\uc38e\uc38f\uc390\uc391\uc392\uc393\uc394\uc395\uc396\uc397\uc398\uc399\uc39a\uc39b\uc39c\uc39d\uc39e\uc39f\uc3a0\uc3a1\uc3a2\uc3a3\uc3a4\uc3a5\uc3a6\uc3a7\uc3a8\uc3a9\uc3aa\uc3ab\uc3ac\uc3ad\uc3ae\uc3af\uc3b0\uc3b1\uc3b2\uc3b3\uc3b4\uc3b5\uc3b6\uc3b7\uc3b8\uc3b9\uc3ba\uc3bb\uc3bc\uc3bd\uc3be\uc3bf\uc3c0\uc3c1\uc3c2\uc3c3\uc3c4\uc3c5\uc3c6\uc3c7\uc3c8\uc3c9\uc3ca\uc3cb\uc3cc\uc3cd\uc3ce\uc3cf\uc3d0\uc3d1\uc3d2\uc3d3\uc3d4\uc3d5\uc3d6\uc3d7\uc3d8\uc3d9\uc3da\uc3db\uc3dc\uc3dd\uc3de\uc3df\uc3e0\uc3e1\uc3e2\uc3e3\uc3e4\uc3e5\uc3e6\uc3e7\uc3e8\uc3e9\uc3ea\uc3eb\uc3ec\uc3ed\uc3ee\uc3ef\uc3f0\uc3f1\uc3f2\uc3f3\uc3f4\uc3f5\uc3f6\uc3f7\uc3f8\uc3f9\uc3fa\uc3fb\uc3fc\uc3fd\uc3fe\uc3ff\uc400\uc401\uc402\uc403\uc404\uc405\uc406\uc407\uc408\uc409\uc40a\uc40b\uc40c\uc40d\uc40e\uc40f\uc410\uc411\uc412\uc413\uc414\uc415\uc416\uc417\uc418\uc419\uc41a\uc41b\uc41c\uc41d\uc41e\uc41f\uc420\uc421\uc422\uc423\uc424\uc425\uc426\uc427\uc428\uc429\uc42a\uc42b\uc42c\uc42d\uc42e\uc42f\uc430\uc431\uc432\uc433\uc434\uc435\uc436\uc437\uc438\uc439\uc43a\uc43b\uc43c\uc43d\uc43e\uc43f\uc440\uc441\uc442\uc443\uc444\uc445\uc446\uc447\uc448\uc449\uc44a\uc44b\uc44c\uc44d\uc44e\uc44f\uc450\uc451\uc452\uc453\uc454\uc455\uc456\uc457\uc458\uc459\uc45a\uc45b\uc45c\uc45d\uc45e\uc45f\uc460\uc461\uc462\uc463\uc464\uc465\uc466\uc467\uc468\uc469\uc46a\uc46b\uc46c\uc46d\uc46e\uc46f\uc470\uc471\uc472\uc473\uc474\uc475\uc476\uc477\uc478\uc479\uc47a\uc47b\uc47c\uc47d\uc47e\uc47f\uc480\uc481\uc482\uc483\uc484\uc485\uc486\uc487\uc488\uc489\uc48a\uc48b\uc48c\uc48d\uc48e\uc48f\uc490\uc491\uc492\uc493\uc494\uc495\uc496\uc497\uc498\uc499\uc49a\uc49b\uc49c\uc49d\uc49e\uc49f\uc4a0\uc4a1\uc4a2\uc4a3\uc4a4\uc4a5\uc4a6\uc4a7\uc4a8\uc4a9\uc4aa\uc4ab\uc4ac\uc4ad\uc4ae\uc4af\uc4b0\uc4b1\uc4b2\uc4b3\uc4b4\uc4b5\uc4b6\uc4b7\uc4b8\uc4b9\uc4ba\uc4bb\uc4bc\uc4bd\uc4be\uc4bf\uc4c0\uc4c1\uc4c2\uc4c3\uc4c4\uc4c5\uc4c6\uc4c7\uc4c8\uc4c9\uc4ca\uc4cb\uc4cc\uc4cd\uc4ce\uc4cf\uc4d0\uc4d1\uc4d2\uc4d3\uc4d4\uc4d5\uc4d6\uc4d7\uc4d8\uc4d9\uc4da\uc4db\uc4dc\uc4dd\uc4de\uc4df\uc4e0\uc4e1\uc4e2\uc4e3\uc4e4\uc4e5\uc4e6\uc4e7\uc4e8\uc4e9\uc4ea\uc4eb\uc4ec\uc4ed\uc4ee\uc4ef\uc4f0\uc4f1\uc4f2\uc4f3\uc4f4\uc4f5\uc4f6\uc4f7\uc4f8\uc4f9\uc4fa\uc4fb\uc4fc\uc4fd\uc4fe\uc4ff\uc500\uc501\uc502\uc503\uc504\uc505\uc506\uc507\uc508\uc509\uc50a\uc50b\uc50c\uc50d\uc50e\uc50f\uc510\uc511\uc512\uc513\uc514\uc515\uc516\uc517\uc518\uc519\uc51a\uc51b\uc51c\uc51d\uc51e\uc51f\uc520\uc521\uc522\uc523\uc524\uc525\uc526\uc527\uc528\uc529\uc52a\uc52b\uc52c\uc52d\uc52e\uc52f\uc530\uc531\uc532\uc533\uc534\uc535\uc536\uc537\uc538\uc539\uc53a\uc53b\uc53c\uc53d\uc53e\uc53f\uc540\uc541\uc542\uc543\uc544\uc545\uc546\uc547\uc548\uc549\uc54a\uc54b\uc54c\uc54d\uc54e\uc54f\uc550\uc551\uc552\uc553\uc554\uc555\uc556\uc557\uc558\uc559\uc55a\uc55b\uc55c\uc55d\uc55e\uc55f\uc560\uc561\uc562\uc563\uc564\uc565\uc566\uc567\uc568\uc569\uc56a\uc56b\uc56c\uc56d\uc56e\uc56f\uc570\uc571\uc572\uc573\uc574\uc575\uc576\uc577\uc578\uc579\uc57a\uc57b\uc57c\uc57d\uc57e\uc57f\uc580\uc581\uc582\uc583\uc584\uc585\uc586\uc587\uc588\uc589\uc58a\uc58b\uc58c\uc58d\uc58e\uc58f\uc590\uc591\uc592\uc593\uc594\uc595\uc596\uc597\uc598\uc599\uc59a\uc59b\uc59c\uc59d\uc59e\uc59f\uc5a0\uc5a1\uc5a2\uc5a3\uc5a4\uc5a5\uc5a6\uc5a7\uc5a8\uc5a9\uc5aa\uc5ab\uc5ac\uc5ad\uc5ae\uc5af\uc5b0\uc5b1\uc5b2\uc5b3\uc5b4\uc5b5\uc5b6\uc5b7\uc5b8\uc5b9\uc5ba\uc5bb\uc5bc\uc5bd\uc5be\uc5bf\uc5c0\uc5c1\uc5c2\uc5c3\uc5c4\uc5c5\uc5c6\uc5c7\uc5c8\uc5c9\uc5ca\uc5cb\uc5cc\uc5cd\uc5ce\uc5cf\uc5d0\uc5d1\uc5d2\uc5d3\uc5d4\uc5d5\uc5d6\uc5d7\uc5d8\uc5d9\uc5da\uc5db\uc5dc\uc5dd\uc5de\uc5df\uc5e0\uc5e1\uc5e2\uc5e3\uc5e4\uc5e5\uc5e6\uc5e7\uc5e8\uc5e9\uc5ea\uc5eb\uc5ec\uc5ed\uc5ee\uc5ef\uc5f0\uc5f1\uc5f2\uc5f3\uc5f4\uc5f5\uc5f6\uc5f7\uc5f8\uc5f9\uc5fa\uc5fb\uc5fc\uc5fd\uc5fe\uc5ff\uc600\uc601\uc602\uc603\uc604\uc605\uc606\uc607\uc608\uc609\uc60a\uc60b\uc60c\uc60d\uc60e\uc60f\uc610\uc611\uc612\uc613\uc614\uc615\uc616\uc617\uc618\uc619\uc61a\uc61b\uc61c\uc61d\uc61e\uc61f\uc620\uc621\uc622\uc623\uc624\uc625\uc626\uc627\uc628\uc629\uc62a\uc62b\uc62c\uc62d\uc62e\uc62f\uc630\uc631\uc632\uc633\uc634\uc635\uc636\uc637\uc638\uc639\uc63a\uc63b\uc63c\uc63d\uc63e\uc63f\uc640\uc641\uc642\uc643\uc644\uc645\uc646\uc647\uc648\uc649\uc64a\uc64b\uc64c\uc64d\uc64e\uc64f\uc650\uc651\uc652\uc653\uc654\uc655\uc656\uc657\uc658\uc659\uc65a\uc65b\uc65c\uc65d\uc65e\uc65f\uc660\uc661\uc662\uc663\uc664\uc665\uc666\uc667\uc668\uc669\uc66a\uc66b\uc66c\uc66d\uc66e\uc66f\uc670\uc671\uc672\uc673\uc674\uc675\uc676\uc677\uc678\uc679\uc67a\uc67b\uc67c\uc67d\uc67e\uc67f\uc680\uc681\uc682\uc683\uc684\uc685\uc686\uc687\uc688\uc689\uc68a\uc68b\uc68c\uc68d\uc68e\uc68f\uc690\uc691\uc692\uc693\uc694\uc695\uc696\uc697\uc698\uc699\uc69a\uc69b\uc69c\uc69d\uc69e\uc69f\uc6a0\uc6a1\uc6a2\uc6a3\uc6a4\uc6a5\uc6a6\uc6a7\uc6a8\uc6a9\uc6aa\uc6ab\uc6ac\uc6ad\uc6ae\uc6af\uc6b0\uc6b1\uc6b2\uc6b3\uc6b4\uc6b5\uc6b6\uc6b7\uc6b8\uc6b9\uc6ba\uc6bb\uc6bc\uc6bd\uc6be\uc6bf\uc6c0\uc6c1\uc6c2\uc6c3\uc6c4\uc6c5\uc6c6\uc6c7\uc6c8\uc6c9\uc6ca\uc6cb\uc6cc\uc6cd\uc6ce\uc6cf\uc6d0\uc6d1\uc6d2\uc6d3\uc6d4\uc6d5\uc6d6\uc6d7\uc6d8\uc6d9\uc6da\uc6db\uc6dc\uc6dd\uc6de\uc6df\uc6e0\uc6e1\uc6e2\uc6e3\uc6e4\uc6e5\uc6e6\uc6e7\uc6e8\uc6e9\uc6ea\uc6eb\uc6ec\uc6ed\uc6ee\uc6ef\uc6f0\uc6f1\uc6f2\uc6f3\uc6f4\uc6f5\uc6f6\uc6f7\uc6f8\uc6f9\uc6fa\uc6fb\uc6fc\uc6fd\uc6fe\uc6ff\uc700\uc701\uc702\uc703\uc704\uc705\uc706\uc707\uc708\uc709\uc70a\uc70b\uc70c\uc70d\uc70e\uc70f\uc710\uc711\uc712\uc713\uc714\uc715\uc716\uc717\uc718\uc719\uc71a\uc71b\uc71c\uc71d\uc71e\uc71f\uc720\uc721\uc722\uc723\uc724\uc725\uc726\uc727\uc728\uc729\uc72a\uc72b\uc72c\uc72d\uc72e\uc72f\uc730\uc731\uc732\uc733\uc734\uc735\uc736\uc737\uc738\uc739\uc73a\uc73b\uc73c\uc73d\uc73e\uc73f\uc740\uc741\uc742\uc743\uc744\uc745\uc746\uc747\uc748\uc749\uc74a\uc74b\uc74c\uc74d\uc74e\uc74f\uc750\uc751\uc752\uc753\uc754\uc755\uc756\uc757\uc758\uc759\uc75a\uc75b\uc75c\uc75d\uc75e\uc75f\uc760\uc761\uc762\uc763\uc764\uc765\uc766\uc767\uc768\uc769\uc76a\uc76b\uc76c\uc76d\uc76e\uc76f\uc770\uc771\uc772\uc773\uc774\uc775\uc776\uc777\uc778\uc779\uc77a\uc77b\uc77c\uc77d\uc77e\uc77f\uc780\uc781\uc782\uc783\uc784\uc785\uc786\uc787\uc788\uc789\uc78a\uc78b\uc78c\uc78d\uc78e\uc78f\uc790\uc791\uc792\uc793\uc794\uc795\uc796\uc797\uc798\uc799\uc79a\uc79b\uc79c\uc79d\uc79e\uc79f\uc7a0\uc7a1\uc7a2\uc7a3\uc7a4\uc7a5\uc7a6\uc7a7\uc7a8\uc7a9\uc7aa\uc7ab\uc7ac\uc7ad\uc7ae\uc7af\uc7b0\uc7b1\uc7b2\uc7b3\uc7b4\uc7b5\uc7b6\uc7b7\uc7b8\uc7b9\uc7ba\uc7bb\uc7bc\uc7bd\uc7be\uc7bf\uc7c0\uc7c1\uc7c2\uc7c3\uc7c4\uc7c5\uc7c6\uc7c7\uc7c8\uc7c9\uc7ca\uc7cb\uc7cc\uc7cd\uc7ce\uc7cf\uc7d0\uc7d1\uc7d2\uc7d3\uc7d4\uc7d5\uc7d6\uc7d7\uc7d8\uc7d9\uc7da\uc7db\uc7dc\uc7dd\uc7de\uc7df\uc7e0\uc7e1\uc7e2\uc7e3\uc7e4\uc7e5\uc7e6\uc7e7\uc7e8\uc7e9\uc7ea\uc7eb\uc7ec\uc7ed\uc7ee\uc7ef\uc7f0\uc7f1\uc7f2\uc7f3\uc7f4\uc7f5\uc7f6\uc7f7\uc7f8\uc7f9\uc7fa\uc7fb\uc7fc\uc7fd\uc7fe\uc7ff\uc800\uc801\uc802\uc803\uc804\uc805\uc806\uc807\uc808\uc809\uc80a\uc80b\uc80c\uc80d\uc80e\uc80f\uc810\uc811\uc812\uc813\uc814\uc815\uc816\uc817\uc818\uc819\uc81a\uc81b\uc81c\uc81d\uc81e\uc81f\uc820\uc821\uc822\uc823\uc824\uc825\uc826\uc827\uc828\uc829\uc82a\uc82b\uc82c\uc82d\uc82e\uc82f\uc830\uc831\uc832\uc833\uc834\uc835\uc836\uc837\uc838\uc839\uc83a\uc83b\uc83c\uc83d\uc83e\uc83f\uc840\uc841\uc842\uc843\uc844\uc845\uc846\uc847\uc848\uc849\uc84a\uc84b\uc84c\uc84d\uc84e\uc84f\uc850\uc851\uc852\uc853\uc854\uc855\uc856\uc857\uc858\uc859\uc85a\uc85b\uc85c\uc85d\uc85e\uc85f\uc860\uc861\uc862\uc863\uc864\uc865\uc866\uc867\uc868\uc869\uc86a\uc86b\uc86c\uc86d\uc86e\uc86f\uc870\uc871\uc872\uc873\uc874\uc875\uc876\uc877\uc878\uc879\uc87a\uc87b\uc87c\uc87d\uc87e\uc87f\uc880\uc881\uc882\uc883\uc884\uc885\uc886\uc887\uc888\uc889\uc88a\uc88b\uc88c\uc88d\uc88e\uc88f\uc890\uc891\uc892\uc893\uc894\uc895\uc896\uc897\uc898\uc899\uc89a\uc89b\uc89c\uc89d\uc89e\uc89f\uc8a0\uc8a1\uc8a2\uc8a3\uc8a4\uc8a5\uc8a6\uc8a7\uc8a8\uc8a9\uc8aa\uc8ab\uc8ac\uc8ad\uc8ae\uc8af\uc8b0\uc8b1\uc8b2\uc8b3\uc8b4\uc8b5\uc8b6\uc8b7\uc8b8\uc8b9\uc8ba\uc8bb\uc8bc\uc8bd\uc8be\uc8bf\uc8c0\uc8c1\uc8c2\uc8c3\uc8c4\uc8c5\uc8c6\uc8c7\uc8c8\uc8c9\uc8ca\uc8cb\uc8cc\uc8cd\uc8ce\uc8cf\uc8d0\uc8d1\uc8d2\uc8d3\uc8d4\uc8d5\uc8d6\uc8d7\uc8d8\uc8d9\uc8da\uc8db\uc8dc\uc8dd\uc8de\uc8df\uc8e0\uc8e1\uc8e2\uc8e3\uc8e4\uc8e5\uc8e6\uc8e7\uc8e8\uc8e9\uc8ea\uc8eb\uc8ec\uc8ed\uc8ee\uc8ef\uc8f0\uc8f1\uc8f2\uc8f3\uc8f4\uc8f5\uc8f6\uc8f7\uc8f8\uc8f9\uc8fa\uc8fb\uc8fc\uc8fd\uc8fe\uc8ff\uc900\uc901\uc902\uc903\uc904\uc905\uc906\uc907\uc908\uc909\uc90a\uc90b\uc90c\uc90d\uc90e\uc90f\uc910\uc911\uc912\uc913\uc914\uc915\uc916\uc917\uc918\uc919\uc91a\uc91b\uc91c\uc91d\uc91e\uc91f\uc920\uc921\uc922\uc923\uc924\uc925\uc926\uc927\uc928\uc929\uc92a\uc92b\uc92c\uc92d\uc92e\uc92f\uc930\uc931\uc932\uc933\uc934\uc935\uc936\uc937\uc938\uc939\uc93a\uc93b\uc93c\uc93d\uc93e\uc93f\uc940\uc941\uc942\uc943\uc944\uc945\uc946\uc947\uc948\uc949\uc94a\uc94b\uc94c\uc94d\uc94e\uc94f\uc950\uc951\uc952\uc953\uc954\uc955\uc956\uc957\uc958\uc959\uc95a\uc95b\uc95c\uc95d\uc95e\uc95f\uc960\uc961\uc962\uc963\uc964\uc965\uc966\uc967\uc968\uc969\uc96a\uc96b\uc96c\uc96d\uc96e\uc96f\uc970\uc971\uc972\uc973\uc974\uc975\uc976\uc977\uc978\uc979\uc97a\uc97b\uc97c\uc97d\uc97e\uc97f\uc980\uc981\uc982\uc983\uc984\uc985\uc986\uc987\uc988\uc989\uc98a\uc98b\uc98c\uc98d\uc98e\uc98f\uc990\uc991\uc992\uc993\uc994\uc995\uc996\uc997\uc998\uc999\uc99a\uc99b\uc99c\uc99d\uc99e\uc99f\uc9a0\uc9a1\uc9a2\uc9a3\uc9a4\uc9a5\uc9a6\uc9a7\uc9a8\uc9a9\uc9aa\uc9ab\uc9ac\uc9ad\uc9ae\uc9af\uc9b0\uc9b1\uc9b2\uc9b3\uc9b4\uc9b5\uc9b6\uc9b7\uc9b8\uc9b9\uc9ba\uc9bb\uc9bc\uc9bd\uc9be\uc9bf\uc9c0\uc9c1\uc9c2\uc9c3\uc9c4\uc9c5\uc9c6\uc9c7\uc9c8\uc9c9\uc9ca\uc9cb\uc9cc\uc9cd\uc9ce\uc9cf\uc9d0\uc9d1\uc9d2\uc9d3\uc9d4\uc9d5\uc9d6\uc9d7\uc9d8\uc9d9\uc9da\uc9db\uc9dc\uc9dd\uc9de\uc9df\uc9e0\uc9e1\uc9e2\uc9e3\uc9e4\uc9e5\uc9e6\uc9e7\uc9e8\uc9e9\uc9ea\uc9eb\uc9ec\uc9ed\uc9ee\uc9ef\uc9f0\uc9f1\uc9f2\uc9f3\uc9f4\uc9f5\uc9f6\uc9f7\uc9f8\uc9f9\uc9fa\uc9fb\uc9fc\uc9fd\uc9fe\uc9ff\uca00\uca01\uca02\uca03\uca04\uca05\uca06\uca07\uca08\uca09\uca0a\uca0b\uca0c\uca0d\uca0e\uca0f\uca10\uca11\uca12\uca13\uca14\uca15\uca16\uca17\uca18\uca19\uca1a\uca1b\uca1c\uca1d\uca1e\uca1f\uca20\uca21\uca22\uca23\uca24\uca25\uca26\uca27\uca28\uca29\uca2a\uca2b\uca2c\uca2d\uca2e\uca2f\uca30\uca31\uca32\uca33\uca34\uca35\uca36\uca37\uca38\uca39\uca3a\uca3b\uca3c\uca3d\uca3e\uca3f\uca40\uca41\uca42\uca43\uca44\uca45\uca46\uca47\uca48\uca49\uca4a\uca4b\uca4c\uca4d\uca4e\uca4f\uca50\uca51\uca52\uca53\uca54\uca55\uca56\uca57\uca58\uca59\uca5a\uca5b\uca5c\uca5d\uca5e\uca5f\uca60\uca61\uca62\uca63\uca64\uca65\uca66\uca67\uca68\uca69\uca6a\uca6b\uca6c\uca6d\uca6e\uca6f\uca70\uca71\uca72\uca73\uca74\uca75\uca76\uca77\uca78\uca79\uca7a\uca7b\uca7c\uca7d\uca7e\uca7f\uca80\uca81\uca82\uca83\uca84\uca85\uca86\uca87\uca88\uca89\uca8a\uca8b\uca8c\uca8d\uca8e\uca8f\uca90\uca91\uca92\uca93\uca94\uca95\uca96\uca97\uca98\uca99\uca9a\uca9b\uca9c\uca9d\uca9e\uca9f\ucaa0\ucaa1\ucaa2\ucaa3\ucaa4\ucaa5\ucaa6\ucaa7\ucaa8\ucaa9\ucaaa\ucaab\ucaac\ucaad\ucaae\ucaaf\ucab0\ucab1\ucab2\ucab3\ucab4\ucab5\ucab6\ucab7\ucab8\ucab9\ucaba\ucabb\ucabc\ucabd\ucabe\ucabf\ucac0\ucac1\ucac2\ucac3\ucac4\ucac5\ucac6\ucac7\ucac8\ucac9\ucaca\ucacb\ucacc\ucacd\ucace\ucacf\ucad0\ucad1\ucad2\ucad3\ucad4\ucad5\ucad6\ucad7\ucad8\ucad9\ucada\ucadb\ucadc\ucadd\ucade\ucadf\ucae0\ucae1\ucae2\ucae3\ucae4\ucae5\ucae6\ucae7\ucae8\ucae9\ucaea\ucaeb\ucaec\ucaed\ucaee\ucaef\ucaf0\ucaf1\ucaf2\ucaf3\ucaf4\ucaf5\ucaf6\ucaf7\ucaf8\ucaf9\ucafa\ucafb\ucafc\ucafd\ucafe\ucaff\ucb00\ucb01\ucb02\ucb03\ucb04\ucb05\ucb06\ucb07\ucb08\ucb09\ucb0a\ucb0b\ucb0c\ucb0d\ucb0e\ucb0f\ucb10\ucb11\ucb12\ucb13\ucb14\ucb15\ucb16\ucb17\ucb18\ucb19\ucb1a\ucb1b\ucb1c\ucb1d\ucb1e\ucb1f\ucb20\ucb21\ucb22\ucb23\ucb24\ucb25\ucb26\ucb27\ucb28\ucb29\ucb2a\ucb2b\ucb2c\ucb2d\ucb2e\ucb2f\ucb30\ucb31\ucb32\ucb33\ucb34\ucb35\ucb36\ucb37\ucb38\ucb39\ucb3a\ucb3b\ucb3c\ucb3d\ucb3e\ucb3f\ucb40\ucb41\ucb42\ucb43\ucb44\ucb45\ucb46\ucb47\ucb48\ucb49\ucb4a\ucb4b\ucb4c\ucb4d\ucb4e\ucb4f\ucb50\ucb51\ucb52\ucb53\ucb54\ucb55\ucb56\ucb57\ucb58\ucb59\ucb5a\ucb5b\ucb5c\ucb5d\ucb5e\ucb5f\ucb60\ucb61\ucb62\ucb63\ucb64\ucb65\ucb66\ucb67\ucb68\ucb69\ucb6a\ucb6b\ucb6c\ucb6d\ucb6e\ucb6f\ucb70\ucb71\ucb72\ucb73\ucb74\ucb75\ucb76\ucb77\ucb78\ucb79\ucb7a\ucb7b\ucb7c\ucb7d\ucb7e\ucb7f\ucb80\ucb81\ucb82\ucb83\ucb84\ucb85\ucb86\ucb87\ucb88\ucb89\ucb8a\ucb8b\ucb8c\ucb8d\ucb8e\ucb8f\ucb90\ucb91\ucb92\ucb93\ucb94\ucb95\ucb96\ucb97\ucb98\ucb99\ucb9a\ucb9b\ucb9c\ucb9d\ucb9e\ucb9f\ucba0\ucba1\ucba2\ucba3\ucba4\ucba5\ucba6\ucba7\ucba8\ucba9\ucbaa\ucbab\ucbac\ucbad\ucbae\ucbaf\ucbb0\ucbb1\ucbb2\ucbb3\ucbb4\ucbb5\ucbb6\ucbb7\ucbb8\ucbb9\ucbba\ucbbb\ucbbc\ucbbd\ucbbe\ucbbf\ucbc0\ucbc1\ucbc2\ucbc3\ucbc4\ucbc5\ucbc6\ucbc7\ucbc8\ucbc9\ucbca\ucbcb\ucbcc\ucbcd\ucbce\ucbcf\ucbd0\ucbd1\ucbd2\ucbd3\ucbd4\ucbd5\ucbd6\ucbd7\ucbd8\ucbd9\ucbda\ucbdb\ucbdc\ucbdd\ucbde\ucbdf\ucbe0\ucbe1\ucbe2\ucbe3\ucbe4\ucbe5\ucbe6\ucbe7\ucbe8\ucbe9\ucbea\ucbeb\ucbec\ucbed\ucbee\ucbef\ucbf0\ucbf1\ucbf2\ucbf3\ucbf4\ucbf5\ucbf6\ucbf7\ucbf8\ucbf9\ucbfa\ucbfb\ucbfc\ucbfd\ucbfe\ucbff\ucc00\ucc01\ucc02\ucc03\ucc04\ucc05\ucc06\ucc07\ucc08\ucc09\ucc0a\ucc0b\ucc0c\ucc0d\ucc0e\ucc0f\ucc10\ucc11\ucc12\ucc13\ucc14\ucc15\ucc16\ucc17\ucc18\ucc19\ucc1a\ucc1b\ucc1c\ucc1d\ucc1e\ucc1f\ucc20\ucc21\ucc22\ucc23\ucc24\ucc25\ucc26\ucc27\ucc28\ucc29\ucc2a\ucc2b\ucc2c\ucc2d\ucc2e\ucc2f\ucc30\ucc31\ucc32\ucc33\ucc34\ucc35\ucc36\ucc37\ucc38\ucc39\ucc3a\ucc3b\ucc3c\ucc3d\ucc3e\ucc3f\ucc40\ucc41\ucc42\ucc43\ucc44\ucc45\ucc46\ucc47\ucc48\ucc49\ucc4a\ucc4b\ucc4c\ucc4d\ucc4e\ucc4f\ucc50\ucc51\ucc52\ucc53\ucc54\ucc55\ucc56\ucc57\ucc58\ucc59\ucc5a\ucc5b\ucc5c\ucc5d\ucc5e\ucc5f\ucc60\ucc61\ucc62\ucc63\ucc64\ucc65\ucc66\ucc67\ucc68\ucc69\ucc6a\ucc6b\ucc6c\ucc6d\ucc6e\ucc6f\ucc70\ucc71\ucc72\ucc73\ucc74\ucc75\ucc76\ucc77\ucc78\ucc79\ucc7a\ucc7b\ucc7c\ucc7d\ucc7e\ucc7f\ucc80\ucc81\ucc82\ucc83\ucc84\ucc85\ucc86\ucc87\ucc88\ucc89\ucc8a\ucc8b\ucc8c\ucc8d\ucc8e\ucc8f\ucc90\ucc91\ucc92\ucc93\ucc94\ucc95\ucc96\ucc97\ucc98\ucc99\ucc9a\ucc9b\ucc9c\ucc9d\ucc9e\ucc9f\ucca0\ucca1\ucca2\ucca3\ucca4\ucca5\ucca6\ucca7\ucca8\ucca9\uccaa\uccab\uccac\uccad\uccae\uccaf\uccb0\uccb1\uccb2\uccb3\uccb4\uccb5\uccb6\uccb7\uccb8\uccb9\uccba\uccbb\uccbc\uccbd\uccbe\uccbf\uccc0\uccc1\uccc2\uccc3\uccc4\uccc5\uccc6\uccc7\uccc8\uccc9\uccca\ucccb\ucccc\ucccd\uccce\ucccf\uccd0\uccd1\uccd2\uccd3\uccd4\uccd5\uccd6\uccd7\uccd8\uccd9\uccda\uccdb\uccdc\uccdd\uccde\uccdf\ucce0\ucce1\ucce2\ucce3\ucce4\ucce5\ucce6\ucce7\ucce8\ucce9\uccea\ucceb\uccec\ucced\uccee\uccef\uccf0\uccf1\uccf2\uccf3\uccf4\uccf5\uccf6\uccf7\uccf8\uccf9\uccfa\uccfb\uccfc\uccfd\uccfe\uccff\ucd00\ucd01\ucd02\ucd03\ucd04\ucd05\ucd06\ucd07\ucd08\ucd09\ucd0a\ucd0b\ucd0c\ucd0d\ucd0e\ucd0f\ucd10\ucd11\ucd12\ucd13\ucd14\ucd15\ucd16\ucd17\ucd18\ucd19\ucd1a\ucd1b\ucd1c\ucd1d\ucd1e\ucd1f\ucd20\ucd21\ucd22\ucd23\ucd24\ucd25\ucd26\ucd27\ucd28\ucd29\ucd2a\ucd2b\ucd2c\ucd2d\ucd2e\ucd2f\ucd30\ucd31\ucd32\ucd33\ucd34\ucd35\ucd36\ucd37\ucd38\ucd39\ucd3a\ucd3b\ucd3c\ucd3d\ucd3e\ucd3f\ucd40\ucd41\ucd42\ucd43\ucd44\ucd45\ucd46\ucd47\ucd48\ucd49\ucd4a\ucd4b\ucd4c\ucd4d\ucd4e\ucd4f\ucd50\ucd51\ucd52\ucd53\ucd54\ucd55\ucd56\ucd57\ucd58\ucd59\ucd5a\ucd5b\ucd5c\ucd5d\ucd5e\ucd5f\ucd60\ucd61\ucd62\ucd63\ucd64\ucd65\ucd66\ucd67\ucd68\ucd69\ucd6a\ucd6b\ucd6c\ucd6d\ucd6e\ucd6f\ucd70\ucd71\ucd72\ucd73\ucd74\ucd75\ucd76\ucd77\ucd78\ucd79\ucd7a\ucd7b\ucd7c\ucd7d\ucd7e\ucd7f\ucd80\ucd81\ucd82\ucd83\ucd84\ucd85\ucd86\ucd87\ucd88\ucd89\ucd8a\ucd8b\ucd8c\ucd8d\ucd8e\ucd8f\ucd90\ucd91\ucd92\ucd93\ucd94\ucd95\ucd96\ucd97\ucd98\ucd99\ucd9a\ucd9b\ucd9c\ucd9d\ucd9e\ucd9f\ucda0\ucda1\ucda2\ucda3\ucda4\ucda5\ucda6\ucda7\ucda8\ucda9\ucdaa\ucdab\ucdac\ucdad\ucdae\ucdaf\ucdb0\ucdb1\ucdb2\ucdb3\ucdb4\ucdb5\ucdb6\ucdb7\ucdb8\ucdb9\ucdba\ucdbb\ucdbc\ucdbd\ucdbe\ucdbf\ucdc0\ucdc1\ucdc2\ucdc3\ucdc4\ucdc5\ucdc6\ucdc7\ucdc8\ucdc9\ucdca\ucdcb\ucdcc\ucdcd\ucdce\ucdcf\ucdd0\ucdd1\ucdd2\ucdd3\ucdd4\ucdd5\ucdd6\ucdd7\ucdd8\ucdd9\ucdda\ucddb\ucddc\ucddd\ucdde\ucddf\ucde0\ucde1\ucde2\ucde3\ucde4\ucde5\ucde6\ucde7\ucde8\ucde9\ucdea\ucdeb\ucdec\ucded\ucdee\ucdef\ucdf0\ucdf1\ucdf2\ucdf3\ucdf4\ucdf5\ucdf6\ucdf7\ucdf8\ucdf9\ucdfa\ucdfb\ucdfc\ucdfd\ucdfe\ucdff\uce00\uce01\uce02\uce03\uce04\uce05\uce06\uce07\uce08\uce09\uce0a\uce0b\uce0c\uce0d\uce0e\uce0f\uce10\uce11\uce12\uce13\uce14\uce15\uce16\uce17\uce18\uce19\uce1a\uce1b\uce1c\uce1d\uce1e\uce1f\uce20\uce21\uce22\uce23\uce24\uce25\uce26\uce27\uce28\uce29\uce2a\uce2b\uce2c\uce2d\uce2e\uce2f\uce30\uce31\uce32\uce33\uce34\uce35\uce36\uce37\uce38\uce39\uce3a\uce3b\uce3c\uce3d\uce3e\uce3f\uce40\uce41\uce42\uce43\uce44\uce45\uce46\uce47\uce48\uce49\uce4a\uce4b\uce4c\uce4d\uce4e\uce4f\uce50\uce51\uce52\uce53\uce54\uce55\uce56\uce57\uce58\uce59\uce5a\uce5b\uce5c\uce5d\uce5e\uce5f\uce60\uce61\uce62\uce63\uce64\uce65\uce66\uce67\uce68\uce69\uce6a\uce6b\uce6c\uce6d\uce6e\uce6f\uce70\uce71\uce72\uce73\uce74\uce75\uce76\uce77\uce78\uce79\uce7a\uce7b\uce7c\uce7d\uce7e\uce7f\uce80\uce81\uce82\uce83\uce84\uce85\uce86\uce87\uce88\uce89\uce8a\uce8b\uce8c\uce8d\uce8e\uce8f\uce90\uce91\uce92\uce93\uce94\uce95\uce96\uce97\uce98\uce99\uce9a\uce9b\uce9c\uce9d\uce9e\uce9f\ucea0\ucea1\ucea2\ucea3\ucea4\ucea5\ucea6\ucea7\ucea8\ucea9\uceaa\uceab\uceac\ucead\uceae\uceaf\uceb0\uceb1\uceb2\uceb3\uceb4\uceb5\uceb6\uceb7\uceb8\uceb9\uceba\ucebb\ucebc\ucebd\ucebe\ucebf\ucec0\ucec1\ucec2\ucec3\ucec4\ucec5\ucec6\ucec7\ucec8\ucec9\uceca\ucecb\ucecc\ucecd\ucece\ucecf\uced0\uced1\uced2\uced3\uced4\uced5\uced6\uced7\uced8\uced9\uceda\ucedb\ucedc\ucedd\ucede\ucedf\ucee0\ucee1\ucee2\ucee3\ucee4\ucee5\ucee6\ucee7\ucee8\ucee9\uceea\uceeb\uceec\uceed\uceee\uceef\ucef0\ucef1\ucef2\ucef3\ucef4\ucef5\ucef6\ucef7\ucef8\ucef9\ucefa\ucefb\ucefc\ucefd\ucefe\uceff\ucf00\ucf01\ucf02\ucf03\ucf04\ucf05\ucf06\ucf07\ucf08\ucf09\ucf0a\ucf0b\ucf0c\ucf0d\ucf0e\ucf0f\ucf10\ucf11\ucf12\ucf13\ucf14\ucf15\ucf16\ucf17\ucf18\ucf19\ucf1a\ucf1b\ucf1c\ucf1d\ucf1e\ucf1f\ucf20\ucf21\ucf22\ucf23\ucf24\ucf25\ucf26\ucf27\ucf28\ucf29\ucf2a\ucf2b\ucf2c\ucf2d\ucf2e\ucf2f\ucf30\ucf31\ucf32\ucf33\ucf34\ucf35\ucf36\ucf37\ucf38\ucf39\ucf3a\ucf3b\ucf3c\ucf3d\ucf3e\ucf3f\ucf40\ucf41\ucf42\ucf43\ucf44\ucf45\ucf46\ucf47\ucf48\ucf49\ucf4a\ucf4b\ucf4c\ucf4d\ucf4e\ucf4f\ucf50\ucf51\ucf52\ucf53\ucf54\ucf55\ucf56\ucf57\ucf58\ucf59\ucf5a\ucf5b\ucf5c\ucf5d\ucf5e\ucf5f\ucf60\ucf61\ucf62\ucf63\ucf64\ucf65\ucf66\ucf67\ucf68\ucf69\ucf6a\ucf6b\ucf6c\ucf6d\ucf6e\ucf6f\ucf70\ucf71\ucf72\ucf73\ucf74\ucf75\ucf76\ucf77\ucf78\ucf79\ucf7a\ucf7b\ucf7c\ucf7d\ucf7e\ucf7f\ucf80\ucf81\ucf82\ucf83\ucf84\ucf85\ucf86\ucf87\ucf88\ucf89\ucf8a\ucf8b\ucf8c\ucf8d\ucf8e\ucf8f\ucf90\ucf91\ucf92\ucf93\ucf94\ucf95\ucf96\ucf97\ucf98\ucf99\ucf9a\ucf9b\ucf9c\ucf9d\ucf9e\ucf9f\ucfa0\ucfa1\ucfa2\ucfa3\ucfa4\ucfa5\ucfa6\ucfa7\ucfa8\ucfa9\ucfaa\ucfab\ucfac\ucfad\ucfae\ucfaf\ucfb0\ucfb1\ucfb2\ucfb3\ucfb4\ucfb5\ucfb6\ucfb7\ucfb8\ucfb9\ucfba\ucfbb\ucfbc\ucfbd\ucfbe\ucfbf\ucfc0\ucfc1\ucfc2\ucfc3\ucfc4\ucfc5\ucfc6\ucfc7\ucfc8\ucfc9\ucfca\ucfcb\ucfcc\ucfcd\ucfce\ucfcf\ucfd0\ucfd1\ucfd2\ucfd3\ucfd4\ucfd5\ucfd6\ucfd7\ucfd8\ucfd9\ucfda\ucfdb\ucfdc\ucfdd\ucfde\ucfdf\ucfe0\ucfe1\ucfe2\ucfe3\ucfe4\ucfe5\ucfe6\ucfe7\ucfe8\ucfe9\ucfea\ucfeb\ucfec\ucfed\ucfee\ucfef\ucff0\ucff1\ucff2\ucff3\ucff4\ucff5\ucff6\ucff7\ucff8\ucff9\ucffa\ucffb\ucffc\ucffd\ucffe\ucfff\ud000\ud001\ud002\ud003\ud004\ud005\ud006\ud007\ud008\ud009\ud00a\ud00b\ud00c\ud00d\ud00e\ud00f\ud010\ud011\ud012\ud013\ud014\ud015\ud016\ud017\ud018\ud019\ud01a\ud01b\ud01c\ud01d\ud01e\ud01f\ud020\ud021\ud022\ud023\ud024\ud025\ud026\ud027\ud028\ud029\ud02a\ud02b\ud02c\ud02d\ud02e\ud02f\ud030\ud031\ud032\ud033\ud034\ud035\ud036\ud037\ud038\ud039\ud03a\ud03b\ud03c\ud03d\ud03e\ud03f\ud040\ud041\ud042\ud043\ud044\ud045\ud046\ud047\ud048\ud049\ud04a\ud04b\ud04c\ud04d\ud04e\ud04f\ud050\ud051\ud052\ud053\ud054\ud055\ud056\ud057\ud058\ud059\ud05a\ud05b\ud05c\ud05d\ud05e\ud05f\ud060\ud061\ud062\ud063\ud064\ud065\ud066\ud067\ud068\ud069\ud06a\ud06b\ud06c\ud06d\ud06e\ud06f\ud070\ud071\ud072\ud073\ud074\ud075\ud076\ud077\ud078\ud079\ud07a\ud07b\ud07c\ud07d\ud07e\ud07f\ud080\ud081\ud082\ud083\ud084\ud085\ud086\ud087\ud088\ud089\ud08a\ud08b\ud08c\ud08d\ud08e\ud08f\ud090\ud091\ud092\ud093\ud094\ud095\ud096\ud097\ud098\ud099\ud09a\ud09b\ud09c\ud09d\ud09e\ud09f\ud0a0\ud0a1\ud0a2\ud0a3\ud0a4\ud0a5\ud0a6\ud0a7\ud0a8\ud0a9\ud0aa\ud0ab\ud0ac\ud0ad\ud0ae\ud0af\ud0b0\ud0b1\ud0b2\ud0b3\ud0b4\ud0b5\ud0b6\ud0b7\ud0b8\ud0b9\ud0ba\ud0bb\ud0bc\ud0bd\ud0be\ud0bf\ud0c0\ud0c1\ud0c2\ud0c3\ud0c4\ud0c5\ud0c6\ud0c7\ud0c8\ud0c9\ud0ca\ud0cb\ud0cc\ud0cd\ud0ce\ud0cf\ud0d0\ud0d1\ud0d2\ud0d3\ud0d4\ud0d5\ud0d6\ud0d7\ud0d8\ud0d9\ud0da\ud0db\ud0dc\ud0dd\ud0de\ud0df\ud0e0\ud0e1\ud0e2\ud0e3\ud0e4\ud0e5\ud0e6\ud0e7\ud0e8\ud0e9\ud0ea\ud0eb\ud0ec\ud0ed\ud0ee\ud0ef\ud0f0\ud0f1\ud0f2\ud0f3\ud0f4\ud0f5\ud0f6\ud0f7\ud0f8\ud0f9\ud0fa\ud0fb\ud0fc\ud0fd\ud0fe\ud0ff\ud100\ud101\ud102\ud103\ud104\ud105\ud106\ud107\ud108\ud109\ud10a\ud10b\ud10c\ud10d\ud10e\ud10f\ud110\ud111\ud112\ud113\ud114\ud115\ud116\ud117\ud118\ud119\ud11a\ud11b\ud11c\ud11d\ud11e\ud11f\ud120\ud121\ud122\ud123\ud124\ud125\ud126\ud127\ud128\ud129\ud12a\ud12b\ud12c\ud12d\ud12e\ud12f\ud130\ud131\ud132\ud133\ud134\ud135\ud136\ud137\ud138\ud139\ud13a\ud13b\ud13c\ud13d\ud13e\ud13f\ud140\ud141\ud142\ud143\ud144\ud145\ud146\ud147\ud148\ud149\ud14a\ud14b\ud14c\ud14d\ud14e\ud14f\ud150\ud151\ud152\ud153\ud154\ud155\ud156\ud157\ud158\ud159\ud15a\ud15b\ud15c\ud15d\ud15e\ud15f\ud160\ud161\ud162\ud163\ud164\ud165\ud166\ud167\ud168\ud169\ud16a\ud16b\ud16c\ud16d\ud16e\ud16f\ud170\ud171\ud172\ud173\ud174\ud175\ud176\ud177\ud178\ud179\ud17a\ud17b\ud17c\ud17d\ud17e\ud17f\ud180\ud181\ud182\ud183\ud184\ud185\ud186\ud187\ud188\ud189\ud18a\ud18b\ud18c\ud18d\ud18e\ud18f\ud190\ud191\ud192\ud193\ud194\ud195\ud196\ud197\ud198\ud199\ud19a\ud19b\ud19c\ud19d\ud19e\ud19f\ud1a0\ud1a1\ud1a2\ud1a3\ud1a4\ud1a5\ud1a6\ud1a7\ud1a8\ud1a9\ud1aa\ud1ab\ud1ac\ud1ad\ud1ae\ud1af\ud1b0\ud1b1\ud1b2\ud1b3\ud1b4\ud1b5\ud1b6\ud1b7\ud1b8\ud1b9\ud1ba\ud1bb\ud1bc\ud1bd\ud1be\ud1bf\ud1c0\ud1c1\ud1c2\ud1c3\ud1c4\ud1c5\ud1c6\ud1c7\ud1c8\ud1c9\ud1ca\ud1cb\ud1cc\ud1cd\ud1ce\ud1cf\ud1d0\ud1d1\ud1d2\ud1d3\ud1d4\ud1d5\ud1d6\ud1d7\ud1d8\ud1d9\ud1da\ud1db\ud1dc\ud1dd\ud1de\ud1df\ud1e0\ud1e1\ud1e2\ud1e3\ud1e4\ud1e5\ud1e6\ud1e7\ud1e8\ud1e9\ud1ea\ud1eb\ud1ec\ud1ed\ud1ee\ud1ef\ud1f0\ud1f1\ud1f2\ud1f3\ud1f4\ud1f5\ud1f6\ud1f7\ud1f8\ud1f9\ud1fa\ud1fb\ud1fc\ud1fd\ud1fe\ud1ff\ud200\ud201\ud202\ud203\ud204\ud205\ud206\ud207\ud208\ud209\ud20a\ud20b\ud20c\ud20d\ud20e\ud20f\ud210\ud211\ud212\ud213\ud214\ud215\ud216\ud217\ud218\ud219\ud21a\ud21b\ud21c\ud21d\ud21e\ud21f\ud220\ud221\ud222\ud223\ud224\ud225\ud226\ud227\ud228\ud229\ud22a\ud22b\ud22c\ud22d\ud22e\ud22f\ud230\ud231\ud232\ud233\ud234\ud235\ud236\ud237\ud238\ud239\ud23a\ud23b\ud23c\ud23d\ud23e\ud23f\ud240\ud241\ud242\ud243\ud244\ud245\ud246\ud247\ud248\ud249\ud24a\ud24b\ud24c\ud24d\ud24e\ud24f\ud250\ud251\ud252\ud253\ud254\ud255\ud256\ud257\ud258\ud259\ud25a\ud25b\ud25c\ud25d\ud25e\ud25f\ud260\ud261\ud262\ud263\ud264\ud265\ud266\ud267\ud268\ud269\ud26a\ud26b\ud26c\ud26d\ud26e\ud26f\ud270\ud271\ud272\ud273\ud274\ud275\ud276\ud277\ud278\ud279\ud27a\ud27b\ud27c\ud27d\ud27e\ud27f\ud280\ud281\ud282\ud283\ud284\ud285\ud286\ud287\ud288\ud289\ud28a\ud28b\ud28c\ud28d\ud28e\ud28f\ud290\ud291\ud292\ud293\ud294\ud295\ud296\ud297\ud298\ud299\ud29a\ud29b\ud29c\ud29d\ud29e\ud29f\ud2a0\ud2a1\ud2a2\ud2a3\ud2a4\ud2a5\ud2a6\ud2a7\ud2a8\ud2a9\ud2aa\ud2ab\ud2ac\ud2ad\ud2ae\ud2af\ud2b0\ud2b1\ud2b2\ud2b3\ud2b4\ud2b5\ud2b6\ud2b7\ud2b8\ud2b9\ud2ba\ud2bb\ud2bc\ud2bd\ud2be\ud2bf\ud2c0\ud2c1\ud2c2\ud2c3\ud2c4\ud2c5\ud2c6\ud2c7\ud2c8\ud2c9\ud2ca\ud2cb\ud2cc\ud2cd\ud2ce\ud2cf\ud2d0\ud2d1\ud2d2\ud2d3\ud2d4\ud2d5\ud2d6\ud2d7\ud2d8\ud2d9\ud2da\ud2db\ud2dc\ud2dd\ud2de\ud2df\ud2e0\ud2e1\ud2e2\ud2e3\ud2e4\ud2e5\ud2e6\ud2e7\ud2e8\ud2e9\ud2ea\ud2eb\ud2ec\ud2ed\ud2ee\ud2ef\ud2f0\ud2f1\ud2f2\ud2f3\ud2f4\ud2f5\ud2f6\ud2f7\ud2f8\ud2f9\ud2fa\ud2fb\ud2fc\ud2fd\ud2fe\ud2ff\ud300\ud301\ud302\ud303\ud304\ud305\ud306\ud307\ud308\ud309\ud30a\ud30b\ud30c\ud30d\ud30e\ud30f\ud310\ud311\ud312\ud313\ud314\ud315\ud316\ud317\ud318\ud319\ud31a\ud31b\ud31c\ud31d\ud31e\ud31f\ud320\ud321\ud322\ud323\ud324\ud325\ud326\ud327\ud328\ud329\ud32a\ud32b\ud32c\ud32d\ud32e\ud32f\ud330\ud331\ud332\ud333\ud334\ud335\ud336\ud337\ud338\ud339\ud33a\ud33b\ud33c\ud33d\ud33e\ud33f\ud340\ud341\ud342\ud343\ud344\ud345\ud346\ud347\ud348\ud349\ud34a\ud34b\ud34c\ud34d\ud34e\ud34f\ud350\ud351\ud352\ud353\ud354\ud355\ud356\ud357\ud358\ud359\ud35a\ud35b\ud35c\ud35d\ud35e\ud35f\ud360\ud361\ud362\ud363\ud364\ud365\ud366\ud367\ud368\ud369\ud36a\ud36b\ud36c\ud36d\ud36e\ud36f\ud370\ud371\ud372\ud373\ud374\ud375\ud376\ud377\ud378\ud379\ud37a\ud37b\ud37c\ud37d\ud37e\ud37f\ud380\ud381\ud382\ud383\ud384\ud385\ud386\ud387\ud388\ud389\ud38a\ud38b\ud38c\ud38d\ud38e\ud38f\ud390\ud391\ud392\ud393\ud394\ud395\ud396\ud397\ud398\ud399\ud39a\ud39b\ud39c\ud39d\ud39e\ud39f\ud3a0\ud3a1\ud3a2\ud3a3\ud3a4\ud3a5\ud3a6\ud3a7\ud3a8\ud3a9\ud3aa\ud3ab\ud3ac\ud3ad\ud3ae\ud3af\ud3b0\ud3b1\ud3b2\ud3b3\ud3b4\ud3b5\ud3b6\ud3b7\ud3b8\ud3b9\ud3ba\ud3bb\ud3bc\ud3bd\ud3be\ud3bf\ud3c0\ud3c1\ud3c2\ud3c3\ud3c4\ud3c5\ud3c6\ud3c7\ud3c8\ud3c9\ud3ca\ud3cb\ud3cc\ud3cd\ud3ce\ud3cf\ud3d0\ud3d1\ud3d2\ud3d3\ud3d4\ud3d5\ud3d6\ud3d7\ud3d8\ud3d9\ud3da\ud3db\ud3dc\ud3dd\ud3de\ud3df\ud3e0\ud3e1\ud3e2\ud3e3\ud3e4\ud3e5\ud3e6\ud3e7\ud3e8\ud3e9\ud3ea\ud3eb\ud3ec\ud3ed\ud3ee\ud3ef\ud3f0\ud3f1\ud3f2\ud3f3\ud3f4\ud3f5\ud3f6\ud3f7\ud3f8\ud3f9\ud3fa\ud3fb\ud3fc\ud3fd\ud3fe\ud3ff\ud400\ud401\ud402\ud403\ud404\ud405\ud406\ud407\ud408\ud409\ud40a\ud40b\ud40c\ud40d\ud40e\ud40f\ud410\ud411\ud412\ud413\ud414\ud415\ud416\ud417\ud418\ud419\ud41a\ud41b\ud41c\ud41d\ud41e\ud41f\ud420\ud421\ud422\ud423\ud424\ud425\ud426\ud427\ud428\ud429\ud42a\ud42b\ud42c\ud42d\ud42e\ud42f\ud430\ud431\ud432\ud433\ud434\ud435\ud436\ud437\ud438\ud439\ud43a\ud43b\ud43c\ud43d\ud43e\ud43f\ud440\ud441\ud442\ud443\ud444\ud445\ud446\ud447\ud448\ud449\ud44a\ud44b\ud44c\ud44d\ud44e\ud44f\ud450\ud451\ud452\ud453\ud454\ud455\ud456\ud457\ud458\ud459\ud45a\ud45b\ud45c\ud45d\ud45e\ud45f\ud460\ud461\ud462\ud463\ud464\ud465\ud466\ud467\ud468\ud469\ud46a\ud46b\ud46c\ud46d\ud46e\ud46f\ud470\ud471\ud472\ud473\ud474\ud475\ud476\ud477\ud478\ud479\ud47a\ud47b\ud47c\ud47d\ud47e\ud47f\ud480\ud481\ud482\ud483\ud484\ud485\ud486\ud487\ud488\ud489\ud48a\ud48b\ud48c\ud48d\ud48e\ud48f\ud490\ud491\ud492\ud493\ud494\ud495\ud496\ud497\ud498\ud499\ud49a\ud49b\ud49c\ud49d\ud49e\ud49f\ud4a0\ud4a1\ud4a2\ud4a3\ud4a4\ud4a5\ud4a6\ud4a7\ud4a8\ud4a9\ud4aa\ud4ab\ud4ac\ud4ad\ud4ae\ud4af\ud4b0\ud4b1\ud4b2\ud4b3\ud4b4\ud4b5\ud4b6\ud4b7\ud4b8\ud4b9\ud4ba\ud4bb\ud4bc\ud4bd\ud4be\ud4bf\ud4c0\ud4c1\ud4c2\ud4c3\ud4c4\ud4c5\ud4c6\ud4c7\ud4c8\ud4c9\ud4ca\ud4cb\ud4cc\ud4cd\ud4ce\ud4cf\ud4d0\ud4d1\ud4d2\ud4d3\ud4d4\ud4d5\ud4d6\ud4d7\ud4d8\ud4d9\ud4da\ud4db\ud4dc\ud4dd\ud4de\ud4df\ud4e0\ud4e1\ud4e2\ud4e3\ud4e4\ud4e5\ud4e6\ud4e7\ud4e8\ud4e9\ud4ea\ud4eb\ud4ec\ud4ed\ud4ee\ud4ef\ud4f0\ud4f1\ud4f2\ud4f3\ud4f4\ud4f5\ud4f6\ud4f7\ud4f8\ud4f9\ud4fa\ud4fb\ud4fc\ud4fd\ud4fe\ud4ff\ud500\ud501\ud502\ud503\ud504\ud505\ud506\ud507\ud508\ud509\ud50a\ud50b\ud50c\ud50d\ud50e\ud50f\ud510\ud511\ud512\ud513\ud514\ud515\ud516\ud517\ud518\ud519\ud51a\ud51b\ud51c\ud51d\ud51e\ud51f\ud520\ud521\ud522\ud523\ud524\ud525\ud526\ud527\ud528\ud529\ud52a\ud52b\ud52c\ud52d\ud52e\ud52f\ud530\ud531\ud532\ud533\ud534\ud535\ud536\ud537\ud538\ud539\ud53a\ud53b\ud53c\ud53d\ud53e\ud53f\ud540\ud541\ud542\ud543\ud544\ud545\ud546\ud547\ud548\ud549\ud54a\ud54b\ud54c\ud54d\ud54e\ud54f\ud550\ud551\ud552\ud553\ud554\ud555\ud556\ud557\ud558\ud559\ud55a\ud55b\ud55c\ud55d\ud55e\ud55f\ud560\ud561\ud562\ud563\ud564\ud565\ud566\ud567\ud568\ud569\ud56a\ud56b\ud56c\ud56d\ud56e\ud56f\ud570\ud571\ud572\ud573\ud574\ud575\ud576\ud577\ud578\ud579\ud57a\ud57b\ud57c\ud57d\ud57e\ud57f\ud580\ud581\ud582\ud583\ud584\ud585\ud586\ud587\ud588\ud589\ud58a\ud58b\ud58c\ud58d\ud58e\ud58f\ud590\ud591\ud592\ud593\ud594\ud595\ud596\ud597\ud598\ud599\ud59a\ud59b\ud59c\ud59d\ud59e\ud59f\ud5a0\ud5a1\ud5a2\ud5a3\ud5a4\ud5a5\ud5a6\ud5a7\ud5a8\ud5a9\ud5aa\ud5ab\ud5ac\ud5ad\ud5ae\ud5af\ud5b0\ud5b1\ud5b2\ud5b3\ud5b4\ud5b5\ud5b6\ud5b7\ud5b8\ud5b9\ud5ba\ud5bb\ud5bc\ud5bd\ud5be\ud5bf\ud5c0\ud5c1\ud5c2\ud5c3\ud5c4\ud5c5\ud5c6\ud5c7\ud5c8\ud5c9\ud5ca\ud5cb\ud5cc\ud5cd\ud5ce\ud5cf\ud5d0\ud5d1\ud5d2\ud5d3\ud5d4\ud5d5\ud5d6\ud5d7\ud5d8\ud5d9\ud5da\ud5db\ud5dc\ud5dd\ud5de\ud5df\ud5e0\ud5e1\ud5e2\ud5e3\ud5e4\ud5e5\ud5e6\ud5e7\ud5e8\ud5e9\ud5ea\ud5eb\ud5ec\ud5ed\ud5ee\ud5ef\ud5f0\ud5f1\ud5f2\ud5f3\ud5f4\ud5f5\ud5f6\ud5f7\ud5f8\ud5f9\ud5fa\ud5fb\ud5fc\ud5fd\ud5fe\ud5ff\ud600\ud601\ud602\ud603\ud604\ud605\ud606\ud607\ud608\ud609\ud60a\ud60b\ud60c\ud60d\ud60e\ud60f\ud610\ud611\ud612\ud613\ud614\ud615\ud616\ud617\ud618\ud619\ud61a\ud61b\ud61c\ud61d\ud61e\ud61f\ud620\ud621\ud622\ud623\ud624\ud625\ud626\ud627\ud628\ud629\ud62a\ud62b\ud62c\ud62d\ud62e\ud62f\ud630\ud631\ud632\ud633\ud634\ud635\ud636\ud637\ud638\ud639\ud63a\ud63b\ud63c\ud63d\ud63e\ud63f\ud640\ud641\ud642\ud643\ud644\ud645\ud646\ud647\ud648\ud649\ud64a\ud64b\ud64c\ud64d\ud64e\ud64f\ud650\ud651\ud652\ud653\ud654\ud655\ud656\ud657\ud658\ud659\ud65a\ud65b\ud65c\ud65d\ud65e\ud65f\ud660\ud661\ud662\ud663\ud664\ud665\ud666\ud667\ud668\ud669\ud66a\ud66b\ud66c\ud66d\ud66e\ud66f\ud670\ud671\ud672\ud673\ud674\ud675\ud676\ud677\ud678\ud679\ud67a\ud67b\ud67c\ud67d\ud67e\ud67f\ud680\ud681\ud682\ud683\ud684\ud685\ud686\ud687\ud688\ud689\ud68a\ud68b\ud68c\ud68d\ud68e\ud68f\ud690\ud691\ud692\ud693\ud694\ud695\ud696\ud697\ud698\ud699\ud69a\ud69b\ud69c\ud69d\ud69e\ud69f\ud6a0\ud6a1\ud6a2\ud6a3\ud6a4\ud6a5\ud6a6\ud6a7\ud6a8\ud6a9\ud6aa\ud6ab\ud6ac\ud6ad\ud6ae\ud6af\ud6b0\ud6b1\ud6b2\ud6b3\ud6b4\ud6b5\ud6b6\ud6b7\ud6b8\ud6b9\ud6ba\ud6bb\ud6bc\ud6bd\ud6be\ud6bf\ud6c0\ud6c1\ud6c2\ud6c3\ud6c4\ud6c5\ud6c6\ud6c7\ud6c8\ud6c9\ud6ca\ud6cb\ud6cc\ud6cd\ud6ce\ud6cf\ud6d0\ud6d1\ud6d2\ud6d3\ud6d4\ud6d5\ud6d6\ud6d7\ud6d8\ud6d9\ud6da\ud6db\ud6dc\ud6dd\ud6de\ud6df\ud6e0\ud6e1\ud6e2\ud6e3\ud6e4\ud6e5\ud6e6\ud6e7\ud6e8\ud6e9\ud6ea\ud6eb\ud6ec\ud6ed\ud6ee\ud6ef\ud6f0\ud6f1\ud6f2\ud6f3\ud6f4\ud6f5\ud6f6\ud6f7\ud6f8\ud6f9\ud6fa\ud6fb\ud6fc\ud6fd\ud6fe\ud6ff\ud700\ud701\ud702\ud703\ud704\ud705\ud706\ud707\ud708\ud709\ud70a\ud70b\ud70c\ud70d\ud70e\ud70f\ud710\ud711\ud712\ud713\ud714\ud715\ud716\ud717\ud718\ud719\ud71a\ud71b\ud71c\ud71d\ud71e\ud71f\ud720\ud721\ud722\ud723\ud724\ud725\ud726\ud727\ud728\ud729\ud72a\ud72b\ud72c\ud72d\ud72e\ud72f\ud730\ud731\ud732\ud733\ud734\ud735\ud736\ud737\ud738\ud739\ud73a\ud73b\ud73c\ud73d\ud73e\ud73f\ud740\ud741\ud742\ud743\ud744\ud745\ud746\ud747\ud748\ud749\ud74a\ud74b\ud74c\ud74d\ud74e\ud74f\ud750\ud751\ud752\ud753\ud754\ud755\ud756\ud757\ud758\ud759\ud75a\ud75b\ud75c\ud75d\ud75e\ud75f\ud760\ud761\ud762\ud763\ud764\ud765\ud766\ud767\ud768\ud769\ud76a\ud76b\ud76c\ud76d\ud76e\ud76f\ud770\ud771\ud772\ud773\ud774\ud775\ud776\ud777\ud778\ud779\ud77a\ud77b\ud77c\ud77d\ud77e\ud77f\ud780\ud781\ud782\ud783\ud784\ud785\ud786\ud787\ud788\ud789\ud78a\ud78b\ud78c\ud78d\ud78e\ud78f\ud790\ud791\ud792\ud793\ud794\ud795\ud796\ud797\ud798\ud799\ud79a\ud79b\ud79c\ud79d\ud79e\ud79f\ud7a0\ud7a1\ud7a2\ud7a3\uf900\uf901\uf902\uf903\uf904\uf905\uf906\uf907\uf908\uf909\uf90a\uf90b\uf90c\uf90d\uf90e\uf90f\uf910\uf911\uf912\uf913\uf914\uf915\uf916\uf917\uf918\uf919\uf91a\uf91b\uf91c\uf91d\uf91e\uf91f\uf920\uf921\uf922\uf923\uf924\uf925\uf926\uf927\uf928\uf929\uf92a\uf92b\uf92c\uf92d\uf92e\uf92f\uf930\uf931\uf932\uf933\uf934\uf935\uf936\uf937\uf938\uf939\uf93a\uf93b\uf93c\uf93d\uf93e\uf93f\uf940\uf941\uf942\uf943\uf944\uf945\uf946\uf947\uf948\uf949\uf94a\uf94b\uf94c\uf94d\uf94e\uf94f\uf950\uf951\uf952\uf953\uf954\uf955\uf956\uf957\uf958\uf959\uf95a\uf95b\uf95c\uf95d\uf95e\uf95f\uf960\uf961\uf962\uf963\uf964\uf965\uf966\uf967\uf968\uf969\uf96a\uf96b\uf96c\uf96d\uf96e\uf96f\uf970\uf971\uf972\uf973\uf974\uf975\uf976\uf977\uf978\uf979\uf97a\uf97b\uf97c\uf97d\uf97e\uf97f\uf980\uf981\uf982\uf983\uf984\uf985\uf986\uf987\uf988\uf989\uf98a\uf98b\uf98c\uf98d\uf98e\uf98f\uf990\uf991\uf992\uf993\uf994\uf995\uf996\uf997\uf998\uf999\uf99a\uf99b\uf99c\uf99d\uf99e\uf99f\uf9a0\uf9a1\uf9a2\uf9a3\uf9a4\uf9a5\uf9a6\uf9a7\uf9a8\uf9a9\uf9aa\uf9ab\uf9ac\uf9ad\uf9ae\uf9af\uf9b0\uf9b1\uf9b2\uf9b3\uf9b4\uf9b5\uf9b6\uf9b7\uf9b8\uf9b9\uf9ba\uf9bb\uf9bc\uf9bd\uf9be\uf9bf\uf9c0\uf9c1\uf9c2\uf9c3\uf9c4\uf9c5\uf9c6\uf9c7\uf9c8\uf9c9\uf9ca\uf9cb\uf9cc\uf9cd\uf9ce\uf9cf\uf9d0\uf9d1\uf9d2\uf9d3\uf9d4\uf9d5\uf9d6\uf9d7\uf9d8\uf9d9\uf9da\uf9db\uf9dc\uf9dd\uf9de\uf9df\uf9e0\uf9e1\uf9e2\uf9e3\uf9e4\uf9e5\uf9e6\uf9e7\uf9e8\uf9e9\uf9ea\uf9eb\uf9ec\uf9ed\uf9ee\uf9ef\uf9f0\uf9f1\uf9f2\uf9f3\uf9f4\uf9f5\uf9f6\uf9f7\uf9f8\uf9f9\uf9fa\uf9fb\uf9fc\uf9fd\uf9fe\uf9ff\ufa00\ufa01\ufa02\ufa03\ufa04\ufa05\ufa06\ufa07\ufa08\ufa09\ufa0a\ufa0b\ufa0c\ufa0d\ufa0e\ufa0f\ufa10\ufa11\ufa12\ufa13\ufa14\ufa15\ufa16\ufa17\ufa18\ufa19\ufa1a\ufa1b\ufa1c\ufa1d\ufa1e\ufa1f\ufa20\ufa21\ufa22\ufa23\ufa24\ufa25\ufa26\ufa27\ufa28\ufa29\ufa2a\ufa2b\ufa2c\ufa2d\ufa30\ufa31\ufa32\ufa33\ufa34\ufa35\ufa36\ufa37\ufa38\ufa39\ufa3a\ufa3b\ufa3c\ufa3d\ufa3e\ufa3f\ufa40\ufa41\ufa42\ufa43\ufa44\ufa45\ufa46\ufa47\ufa48\ufa49\ufa4a\ufa4b\ufa4c\ufa4d\ufa4e\ufa4f\ufa50\ufa51\ufa52\ufa53\ufa54\ufa55\ufa56\ufa57\ufa58\ufa59\ufa5a\ufa5b\ufa5c\ufa5d\ufa5e\ufa5f\ufa60\ufa61\ufa62\ufa63\ufa64\ufa65\ufa66\ufa67\ufa68\ufa69\ufa6a\ufa70\ufa71\ufa72\ufa73\ufa74\ufa75\ufa76\ufa77\ufa78\ufa79\ufa7a\ufa7b\ufa7c\ufa7d\ufa7e\ufa7f\ufa80\ufa81\ufa82\ufa83\ufa84\ufa85\ufa86\ufa87\ufa88\ufa89\ufa8a\ufa8b\ufa8c\ufa8d\ufa8e\ufa8f\ufa90\ufa91\ufa92\ufa93\ufa94\ufa95\ufa96\ufa97\ufa98\ufa99\ufa9a\ufa9b\ufa9c\ufa9d\ufa9e\ufa9f\ufaa0\ufaa1\ufaa2\ufaa3\ufaa4\ufaa5\ufaa6\ufaa7\ufaa8\ufaa9\ufaaa\ufaab\ufaac\ufaad\ufaae\ufaaf\ufab0\ufab1\ufab2\ufab3\ufab4\ufab5\ufab6\ufab7\ufab8\ufab9\ufaba\ufabb\ufabc\ufabd\ufabe\ufabf\ufac0\ufac1\ufac2\ufac3\ufac4\ufac5\ufac6\ufac7\ufac8\ufac9\ufaca\ufacb\ufacc\ufacd\uface\ufacf\ufad0\ufad1\ufad2\ufad3\ufad4\ufad5\ufad6\ufad7\ufad8\ufad9\ufb1d\ufb1f\ufb20\ufb21\ufb22\ufb23\ufb24\ufb25\ufb26\ufb27\ufb28\ufb2a\ufb2b\ufb2c\ufb2d\ufb2e\ufb2f\ufb30\ufb31\ufb32\ufb33\ufb34\ufb35\ufb36\ufb38\ufb39\ufb3a\ufb3b\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46\ufb47\ufb48\ufb49\ufb4a\ufb4b\ufb4c\ufb4d\ufb4e\ufb4f\ufb50\ufb51\ufb52\ufb53\ufb54\ufb55\ufb56\ufb57\ufb58\ufb59\ufb5a\ufb5b\ufb5c\ufb5d\ufb5e\ufb5f\ufb60\ufb61\ufb62\ufb63\ufb64\ufb65\ufb66\ufb67\ufb68\ufb69\ufb6a\ufb6b\ufb6c\ufb6d\ufb6e\ufb6f\ufb70\ufb71\ufb72\ufb73\ufb74\ufb75\ufb76\ufb77\ufb78\ufb79\ufb7a\ufb7b\ufb7c\ufb7d\ufb7e\ufb7f\ufb80\ufb81\ufb82\ufb83\ufb84\ufb85\ufb86\ufb87\ufb88\ufb89\ufb8a\ufb8b\ufb8c\ufb8d\ufb8e\ufb8f\ufb90\ufb91\ufb92\ufb93\ufb94\ufb95\ufb96\ufb97\ufb98\ufb99\ufb9a\ufb9b\ufb9c\ufb9d\ufb9e\ufb9f\ufba0\ufba1\ufba2\ufba3\ufba4\ufba5\ufba6\ufba7\ufba8\ufba9\ufbaa\ufbab\ufbac\ufbad\ufbae\ufbaf\ufbb0\ufbb1\ufbd3\ufbd4\ufbd5\ufbd6\ufbd7\ufbd8\ufbd9\ufbda\ufbdb\ufbdc\ufbdd\ufbde\ufbdf\ufbe0\ufbe1\ufbe2\ufbe3\ufbe4\ufbe5\ufbe6\ufbe7\ufbe8\ufbe9\ufbea\ufbeb\ufbec\ufbed\ufbee\ufbef\ufbf0\ufbf1\ufbf2\ufbf3\ufbf4\ufbf5\ufbf6\ufbf7\ufbf8\ufbf9\ufbfa\ufbfb\ufbfc\ufbfd\ufbfe\ufbff\ufc00\ufc01\ufc02\ufc03\ufc04\ufc05\ufc06\ufc07\ufc08\ufc09\ufc0a\ufc0b\ufc0c\ufc0d\ufc0e\ufc0f\ufc10\ufc11\ufc12\ufc13\ufc14\ufc15\ufc16\ufc17\ufc18\ufc19\ufc1a\ufc1b\ufc1c\ufc1d\ufc1e\ufc1f\ufc20\ufc21\ufc22\ufc23\ufc24\ufc25\ufc26\ufc27\ufc28\ufc29\ufc2a\ufc2b\ufc2c\ufc2d\ufc2e\ufc2f\ufc30\ufc31\ufc32\ufc33\ufc34\ufc35\ufc36\ufc37\ufc38\ufc39\ufc3a\ufc3b\ufc3c\ufc3d\ufc3e\ufc3f\ufc40\ufc41\ufc42\ufc43\ufc44\ufc45\ufc46\ufc47\ufc48\ufc49\ufc4a\ufc4b\ufc4c\ufc4d\ufc4e\ufc4f\ufc50\ufc51\ufc52\ufc53\ufc54\ufc55\ufc56\ufc57\ufc58\ufc59\ufc5a\ufc5b\ufc5c\ufc5d\ufc5e\ufc5f\ufc60\ufc61\ufc62\ufc63\ufc64\ufc65\ufc66\ufc67\ufc68\ufc69\ufc6a\ufc6b\ufc6c\ufc6d\ufc6e\ufc6f\ufc70\ufc71\ufc72\ufc73\ufc74\ufc75\ufc76\ufc77\ufc78\ufc79\ufc7a\ufc7b\ufc7c\ufc7d\ufc7e\ufc7f\ufc80\ufc81\ufc82\ufc83\ufc84\ufc85\ufc86\ufc87\ufc88\ufc89\ufc8a\ufc8b\ufc8c\ufc8d\ufc8e\ufc8f\ufc90\ufc91\ufc92\ufc93\ufc94\ufc95\ufc96\ufc97\ufc98\ufc99\ufc9a\ufc9b\ufc9c\ufc9d\ufc9e\ufc9f\ufca0\ufca1\ufca2\ufca3\ufca4\ufca5\ufca6\ufca7\ufca8\ufca9\ufcaa\ufcab\ufcac\ufcad\ufcae\ufcaf\ufcb0\ufcb1\ufcb2\ufcb3\ufcb4\ufcb5\ufcb6\ufcb7\ufcb8\ufcb9\ufcba\ufcbb\ufcbc\ufcbd\ufcbe\ufcbf\ufcc0\ufcc1\ufcc2\ufcc3\ufcc4\ufcc5\ufcc6\ufcc7\ufcc8\ufcc9\ufcca\ufccb\ufccc\ufccd\ufcce\ufccf\ufcd0\ufcd1\ufcd2\ufcd3\ufcd4\ufcd5\ufcd6\ufcd7\ufcd8\ufcd9\ufcda\ufcdb\ufcdc\ufcdd\ufcde\ufcdf\ufce0\ufce1\ufce2\ufce3\ufce4\ufce5\ufce6\ufce7\ufce8\ufce9\ufcea\ufceb\ufcec\ufced\ufcee\ufcef\ufcf0\ufcf1\ufcf2\ufcf3\ufcf4\ufcf5\ufcf6\ufcf7\ufcf8\ufcf9\ufcfa\ufcfb\ufcfc\ufcfd\ufcfe\ufcff\ufd00\ufd01\ufd02\ufd03\ufd04\ufd05\ufd06\ufd07\ufd08\ufd09\ufd0a\ufd0b\ufd0c\ufd0d\ufd0e\ufd0f\ufd10\ufd11\ufd12\ufd13\ufd14\ufd15\ufd16\ufd17\ufd18\ufd19\ufd1a\ufd1b\ufd1c\ufd1d\ufd1e\ufd1f\ufd20\ufd21\ufd22\ufd23\ufd24\ufd25\ufd26\ufd27\ufd28\ufd29\ufd2a\ufd2b\ufd2c\ufd2d\ufd2e\ufd2f\ufd30\ufd31\ufd32\ufd33\ufd34\ufd35\ufd36\ufd37\ufd38\ufd39\ufd3a\ufd3b\ufd3c\ufd3d\ufd50\ufd51\ufd52\ufd53\ufd54\ufd55\ufd56\ufd57\ufd58\ufd59\ufd5a\ufd5b\ufd5c\ufd5d\ufd5e\ufd5f\ufd60\ufd61\ufd62\ufd63\ufd64\ufd65\ufd66\ufd67\ufd68\ufd69\ufd6a\ufd6b\ufd6c\ufd6d\ufd6e\ufd6f\ufd70\ufd71\ufd72\ufd73\ufd74\ufd75\ufd76\ufd77\ufd78\ufd79\ufd7a\ufd7b\ufd7c\ufd7d\ufd7e\ufd7f\ufd80\ufd81\ufd82\ufd83\ufd84\ufd85\ufd86\ufd87\ufd88\ufd89\ufd8a\ufd8b\ufd8c\ufd8d\ufd8e\ufd8f\ufd92\ufd93\ufd94\ufd95\ufd96\ufd97\ufd98\ufd99\ufd9a\ufd9b\ufd9c\ufd9d\ufd9e\ufd9f\ufda0\ufda1\ufda2\ufda3\ufda4\ufda5\ufda6\ufda7\ufda8\ufda9\ufdaa\ufdab\ufdac\ufdad\ufdae\ufdaf\ufdb0\ufdb1\ufdb2\ufdb3\ufdb4\ufdb5\ufdb6\ufdb7\ufdb8\ufdb9\ufdba\ufdbb\ufdbc\ufdbd\ufdbe\ufdbf\ufdc0\ufdc1\ufdc2\ufdc3\ufdc4\ufdc5\ufdc6\ufdc7\ufdf0\ufdf1\ufdf2\ufdf3\ufdf4\ufdf5\ufdf6\ufdf7\ufdf8\ufdf9\ufdfa\ufdfb\ufe70\ufe71\ufe72\ufe73\ufe74\ufe76\ufe77\ufe78\ufe79\ufe7a\ufe7b\ufe7c\ufe7d\ufe7e\ufe7f\ufe80\ufe81\ufe82\ufe83\ufe84\ufe85\ufe86\ufe87\ufe88\ufe89\ufe8a\ufe8b\ufe8c\ufe8d\ufe8e\ufe8f\ufe90\ufe91\ufe92\ufe93\ufe94\ufe95\ufe96\ufe97\ufe98\ufe99\ufe9a\ufe9b\ufe9c\ufe9d\ufe9e\ufe9f\ufea0\ufea1\ufea2\ufea3\ufea4\ufea5\ufea6\ufea7\ufea8\ufea9\ufeaa\ufeab\ufeac\ufead\ufeae\ufeaf\ufeb0\ufeb1\ufeb2\ufeb3\ufeb4\ufeb5\ufeb6\ufeb7\ufeb8\ufeb9\ufeba\ufebb\ufebc\ufebd\ufebe\ufebf\ufec0\ufec1\ufec2\ufec3\ufec4\ufec5\ufec6\ufec7\ufec8\ufec9\ufeca\ufecb\ufecc\ufecd\ufece\ufecf\ufed0\ufed1\ufed2\ufed3\ufed4\ufed5\ufed6\ufed7\ufed8\ufed9\ufeda\ufedb\ufedc\ufedd\ufede\ufedf\ufee0\ufee1\ufee2\ufee3\ufee4\ufee5\ufee6\ufee7\ufee8\ufee9\ufeea\ufeeb\ufeec\ufeed\ufeee\ufeef\ufef0\ufef1\ufef2\ufef3\ufef4\ufef5\ufef6\ufef7\ufef8\ufef9\ufefa\ufefb\ufefc\uff66\uff67\uff68\uff69\uff6a\uff6b\uff6c\uff6d\uff6e\uff6f\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79\uff7a\uff7b\uff7c\uff7d\uff7e\uff7f\uff80\uff81\uff82\uff83\uff84\uff85\uff86\uff87\uff88\uff89\uff8a\uff8b\uff8c\uff8d\uff8e\uff8f\uff90\uff91\uff92\uff93\uff94\uff95\uff96\uff97\uff98\uff99\uff9a\uff9b\uff9c\uff9d\uffa0\uffa1\uffa2\uffa3\uffa4\uffa5\uffa6\uffa7\uffa8\uffa9\uffaa\uffab\uffac\uffad\uffae\uffaf\uffb0\uffb1\uffb2\uffb3\uffb4\uffb5\uffb6\uffb7\uffb8\uffb9\uffba\uffbb\uffbc\uffbd\uffbe\uffc2\uffc3\uffc4\uffc5\uffc6\uffc7\uffca\uffcb\uffcc\uffcd\uffce\uffcf\uffd2\uffd3\uffd4\uffd5\uffd6\uffd7\uffda\uffdb\uffdc'
-
-Lt = u'\u01c5\u01c8\u01cb\u01f2\u1f88\u1f89\u1f8a\u1f8b\u1f8c\u1f8d\u1f8e\u1f8f\u1f98\u1f99\u1f9a\u1f9b\u1f9c\u1f9d\u1f9e\u1f9f\u1fa8\u1fa9\u1faa\u1fab\u1fac\u1fad\u1fae\u1faf\u1fbc\u1fcc\u1ffc'
-
-Lu = u'ABCDEFGHIJKLMNOPQRSTUVWXYZ\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd8\xd9\xda\xdb\xdc\xdd\xde\u0100\u0102\u0104\u0106\u0108\u010a\u010c\u010e\u0110\u0112\u0114\u0116\u0118\u011a\u011c\u011e\u0120\u0122\u0124\u0126\u0128\u012a\u012c\u012e\u0130\u0132\u0134\u0136\u0139\u013b\u013d\u013f\u0141\u0143\u0145\u0147\u014a\u014c\u014e\u0150\u0152\u0154\u0156\u0158\u015a\u015c\u015e\u0160\u0162\u0164\u0166\u0168\u016a\u016c\u016e\u0170\u0172\u0174\u0176\u0178\u0179\u017b\u017d\u0181\u0182\u0184\u0186\u0187\u0189\u018a\u018b\u018e\u018f\u0190\u0191\u0193\u0194\u0196\u0197\u0198\u019c\u019d\u019f\u01a0\u01a2\u01a4\u01a6\u01a7\u01a9\u01ac\u01ae\u01af\u01b1\u01b2\u01b3\u01b5\u01b7\u01b8\u01bc\u01c4\u01c7\u01ca\u01cd\u01cf\u01d1\u01d3\u01d5\u01d7\u01d9\u01db\u01de\u01e0\u01e2\u01e4\u01e6\u01e8\u01ea\u01ec\u01ee\u01f1\u01f4\u01f6\u01f7\u01f8\u01fa\u01fc\u01fe\u0200\u0202\u0204\u0206\u0208\u020a\u020c\u020e\u0210\u0212\u0214\u0216\u0218\u021a\u021c\u021e\u0220\u0222\u0224\u0226\u0228\u022a\u022c\u022e\u0230\u0232\u023a\u023b\u023d\u023e\u0241\u0386\u0388\u0389\u038a\u038c\u038e\u038f\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03d2\u03d3\u03d4\u03d8\u03da\u03dc\u03de\u03e0\u03e2\u03e4\u03e6\u03e8\u03ea\u03ec\u03ee\u03f4\u03f7\u03f9\u03fa\u03fd\u03fe\u03ff\u0400\u0401\u0402\u0403\u0404\u0405\u0406\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e\u042f\u0460\u0462\u0464\u0466\u0468\u046a\u046c\u046e\u0470\u0472\u0474\u0476\u0478\u047a\u047c\u047e\u0480\u048a\u048c\u048e\u0490\u0492\u0494\u0496\u0498\u049a\u049c\u049e\u04a0\u04a2\u04a4\u04a6\u04a8\u04aa\u04ac\u04ae\u04b0\u04b2\u04b4\u04b6\u04b8\u04ba\u04bc\u04be\u04c0\u04c1\u04c3\u04c5\u04c7\u04c9\u04cb\u04cd\u04d0\u04d2\u04d4\u04d6\u04d8\u04da\u04dc\u04de\u04e0\u04e2\u04e4\u04e6\u04e8\u04ea\u04ec\u04ee\u04f0\u04f2\u04f4\u04f6\u04f8\u0500\u0502\u0504\u0506\u0508\u050a\u050c\u050e\u0531\u0532\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u10a0\u10a1\u10a2\u10a3\u10a4\u10a5\u10a6\u10a7\u10a8\u10a9\u10aa\u10ab\u10ac\u10ad\u10ae\u10af\u10b0\u10b1\u10b2\u10b3\u10b4\u10b5\u10b6\u10b7\u10b8\u10b9\u10ba\u10bb\u10bc\u10bd\u10be\u10bf\u10c0\u10c1\u10c2\u10c3\u10c4\u10c5\u1e00\u1e02\u1e04\u1e06\u1e08\u1e0a\u1e0c\u1e0e\u1e10\u1e12\u1e14\u1e16\u1e18\u1e1a\u1e1c\u1e1e\u1e20\u1e22\u1e24\u1e26\u1e28\u1e2a\u1e2c\u1e2e\u1e30\u1e32\u1e34\u1e36\u1e38\u1e3a\u1e3c\u1e3e\u1e40\u1e42\u1e44\u1e46\u1e48\u1e4a\u1e4c\u1e4e\u1e50\u1e52\u1e54\u1e56\u1e58\u1e5a\u1e5c\u1e5e\u1e60\u1e62\u1e64\u1e66\u1e68\u1e6a\u1e6c\u1e6e\u1e70\u1e72\u1e74\u1e76\u1e78\u1e7a\u1e7c\u1e7e\u1e80\u1e82\u1e84\u1e86\u1e88\u1e8a\u1e8c\u1e8e\u1e90\u1e92\u1e94\u1ea0\u1ea2\u1ea4\u1ea6\u1ea8\u1eaa\u1eac\u1eae\u1eb0\u1eb2\u1eb4\u1eb6\u1eb8\u1eba\u1ebc\u1ebe\u1ec0\u1ec2\u1ec4\u1ec6\u1ec8\u1eca\u1ecc\u1ece\u1ed0\u1ed2\u1ed4\u1ed6\u1ed8\u1eda\u1edc\u1ede\u1ee0\u1ee2\u1ee4\u1ee6\u1ee8\u1eea\u1eec\u1eee\u1ef0\u1ef2\u1ef4\u1ef6\u1ef8\u1f08\u1f09\u1f0a\u1f0b\u1f0c\u1f0d\u1f0e\u1f0f\u1f18\u1f19\u1f1a\u1f1b\u1f1c\u1f1d\u1f28\u1f29\u1f2a\u1f2b\u1f2c\u1f2d\u1f2e\u1f2f\u1f38\u1f39\u1f3a\u1f3b\u1f3c\u1f3d\u1f3e\u1f3f\u1f48\u1f49\u1f4a\u1f4b\u1f4c\u1f4d\u1f59\u1f5b\u1f5d\u1f5f\u1f68\u1f69\u1f6a\u1f6b\u1f6c\u1f6d\u1f6e\u1f6f\u1fb8\u1fb9\u1fba\u1fbb\u1fc8\u1fc9\u1fca\u1fcb\u1fd8\u1fd9\u1fda\u1fdb\u1fe8\u1fe9\u1fea\u1feb\u1fec\u1ff8\u1ff9\u1ffa\u1ffb\u2102\u2107\u210b\u210c\u210d\u2110\u2111\u2112\u2115\u2119\u211a\u211b\u211c\u211d\u2124\u2126\u2128\u212a\u212b\u212c\u212d\u2130\u2131\u2133\u213e\u213f\u2145\u2c00\u2c01\u2c02\u2c03\u2c04\u2c05\u2c06\u2c07\u2c08\u2c09\u2c0a\u2c0b\u2c0c\u2c0d\u2c0e\u2c0f\u2c10\u2c11\u2c12\u2c13\u2c14\u2c15\u2c16\u2c17\u2c18\u2c19\u2c1a\u2c1b\u2c1c\u2c1d\u2c1e\u2c1f\u2c20\u2c21\u2c22\u2c23\u2c24\u2c25\u2c26\u2c27\u2c28\u2c29\u2c2a\u2c2b\u2c2c\u2c2d\u2c2e\u2c80\u2c82\u2c84\u2c86\u2c88\u2c8a\u2c8c\u2c8e\u2c90\u2c92\u2c94\u2c96\u2c98\u2c9a\u2c9c\u2c9e\u2ca0\u2ca2\u2ca4\u2ca6\u2ca8\u2caa\u2cac\u2cae\u2cb0\u2cb2\u2cb4\u2cb6\u2cb8\u2cba\u2cbc\u2cbe\u2cc0\u2cc2\u2cc4\u2cc6\u2cc8\u2cca\u2ccc\u2cce\u2cd0\u2cd2\u2cd4\u2cd6\u2cd8\u2cda\u2cdc\u2cde\u2ce0\u2ce2\uff21\uff22\uff23\uff24\uff25\uff26\uff27\uff28\uff29\uff2a\uff2b\uff2c\uff2d\uff2e\uff2f\uff30\uff31\uff32\uff33\uff34\uff35\uff36\uff37\uff38\uff39\uff3a'
-
-Mc = u'\u0903\u093e\u093f\u0940\u0949\u094a\u094b\u094c\u0982\u0983\u09be\u09bf\u09c0\u09c7\u09c8\u09cb\u09cc\u09d7\u0a03\u0a3e\u0a3f\u0a40\u0a83\u0abe\u0abf\u0ac0\u0ac9\u0acb\u0acc\u0b02\u0b03\u0b3e\u0b40\u0b47\u0b48\u0b4b\u0b4c\u0b57\u0bbe\u0bbf\u0bc1\u0bc2\u0bc6\u0bc7\u0bc8\u0bca\u0bcb\u0bcc\u0bd7\u0c01\u0c02\u0c03\u0c41\u0c42\u0c43\u0c44\u0c82\u0c83\u0cbe\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4\u0cc7\u0cc8\u0cca\u0ccb\u0cd5\u0cd6\u0d02\u0d03\u0d3e\u0d3f\u0d40\u0d46\u0d47\u0d48\u0d4a\u0d4b\u0d4c\u0d57\u0d82\u0d83\u0dcf\u0dd0\u0dd1\u0dd8\u0dd9\u0dda\u0ddb\u0ddc\u0ddd\u0dde\u0ddf\u0df2\u0df3\u0f3e\u0f3f\u0f7f\u102c\u1031\u1038\u1056\u1057\u17b6\u17be\u17bf\u17c0\u17c1\u17c2\u17c3\u17c4\u17c5\u17c7\u17c8\u1923\u1924\u1925\u1926\u1929\u192a\u192b\u1930\u1931\u1933\u1934\u1935\u1936\u1937\u1938\u19b0\u19b1\u19b2\u19b3\u19b4\u19b5\u19b6\u19b7\u19b8\u19b9\u19ba\u19bb\u19bc\u19bd\u19be\u19bf\u19c0\u19c8\u19c9\u1a19\u1a1a\u1a1b\ua802\ua823\ua824\ua827'
-
-Me = u'\u0488\u0489\u06de\u20dd\u20de\u20df\u20e0\u20e2\u20e3\u20e4'
-
-Mn = u'\u0300\u0301\u0302\u0303\u0304\u0305\u0306\u0307\u0308\u0309\u030a\u030b\u030c\u030d\u030e\u030f\u0310\u0311\u0312\u0313\u0314\u0315\u0316\u0317\u0318\u0319\u031a\u031b\u031c\u031d\u031e\u031f\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032a\u032b\u032c\u032d\u032e\u032f\u0330\u0331\u0332\u0333\u0334\u0335\u0336\u0337\u0338\u0339\u033a\u033b\u033c\u033d\u033e\u033f\u0340\u0341\u0342\u0343\u0344\u0345\u0346\u0347\u0348\u0349\u034a\u034b\u034c\u034d\u034e\u034f\u0350\u0351\u0352\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035a\u035b\u035c\u035d\u035e\u035f\u0360\u0361\u0362\u0363\u0364\u0365\u0366\u0367\u0368\u0369\u036a\u036b\u036c\u036d\u036e\u036f\u0483\u0484\u0485\u0486\u0591\u0592\u0593\u0594\u0595\u0596\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05bb\u05bc\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610\u0611\u0612\u0613\u0614\u0615\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e\u0670\u06d6\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e7\u06e8\u06ea\u06eb\u06ec\u06ed\u0711\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u0901\u0902\u093c\u0941\u0942\u0943\u0944\u0945\u0946\u0947\u0948\u094d\u0951\u0952\u0953\u0954\u0962\u0963\u0981\u09bc\u09c1\u09c2\u09c3\u09c4\u09cd\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b\u0a4c\u0a4d\u0a70\u0a71\u0a81\u0a82\u0abc\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3f\u0b41\u0b42\u0b43\u0b4d\u0b56\u0b82\u0bc0\u0bcd\u0c3e\u0c3f\u0c40\u0c46\u0c47\u0c48\u0c4a\u0c4b\u0c4c\u0c4d\u0c55\u0c56\u0cbc\u0cbf\u0cc6\u0ccc\u0ccd\u0d41\u0d42\u0d43\u0d4d\u0dca\u0dd2\u0dd3\u0dd4\u0dd6\u0e31\u0e34\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e47\u0e48\u0e49\u0e4a\u0e4b\u0e4c\u0e4d\u0e4e\u0eb1\u0eb4\u0eb5\u0eb6\u0eb7\u0eb8\u0eb9\u0ebb\u0ebc\u0ec8\u0ec9\u0eca\u0ecb\u0ecc\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71\u0f72\u0f73\u0f74\u0f75\u0f76\u0f77\u0f78\u0f79\u0f7a\u0f7b\u0f7c\u0f7d\u0f7e\u0f80\u0f81\u0f82\u0f83\u0f84\u0f86\u0f87\u0f90\u0f91\u0f92\u0f93\u0f94\u0f95\u0f96\u0f97\u0f99\u0f9a\u0f9b\u0f9c\u0f9d\u0f9e\u0f9f\u0fa0\u0fa1\u0fa2\u0fa3\u0fa4\u0fa5\u0fa6\u0fa7\u0fa8\u0fa9\u0faa\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u0fb1\u0fb2\u0fb3\u0fb4\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba\u0fbb\u0fbc\u0fc6\u102d\u102e\u102f\u1030\u1032\u1036\u1037\u1039\u1058\u1059\u135f\u1712\u1713\u1714\u1732\u1733\u1734\u1752\u1753\u1772\u1773\u17b7\u17b8\u17b9\u17ba\u17bb\u17bc\u17bd\u17c6\u17c9\u17ca\u17cb\u17cc\u17cd\u17ce\u17cf\u17d0\u17d1\u17d2\u17d3\u17dd\u180b\u180c\u180d\u18a9\u1920\u1921\u1922\u1927\u1928\u1932\u1939\u193a\u193b\u1a17\u1a18\u1dc0\u1dc1\u1dc2\u1dc3\u20d0\u20d1\u20d2\u20d3\u20d4\u20d5\u20d6\u20d7\u20d8\u20d9\u20da\u20db\u20dc\u20e1\u20e5\u20e6\u20e7\u20e8\u20e9\u20ea\u20eb\u302a\u302b\u302c\u302d\u302e\u302f\u3099\u309a\ua806\ua80b\ua825\ua826\ufb1e\ufe00\ufe01\ufe02\ufe03\ufe04\ufe05\ufe06\ufe07\ufe08\ufe09\ufe0a\ufe0b\ufe0c\ufe0d\ufe0e\ufe0f\ufe20\ufe21\ufe22\ufe23'
-
-Nd = u'0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e\u0a6f\u0ae6\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0be6\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee\u0cef\u0d66\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56\u0e57\u0e58\u0e59\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u17e0\u17e1\u17e2\u17e3\u17e4\u17e5\u17e6\u17e7\u17e8\u17e9\u1810\u1811\u1812\u1813\u1814\u1815\u1816\u1817\u1818\u1819\u1946\u1947\u1948\u1949\u194a\u194b\u194c\u194d\u194e\u194f\u19d0\u19d1\u19d2\u19d3\u19d4\u19d5\u19d6\u19d7\u19d8\u19d9\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19'
-
-Nl = u'\u16ee\u16ef\u16f0\u2160\u2161\u2162\u2163\u2164\u2165\u2166\u2167\u2168\u2169\u216a\u216b\u216c\u216d\u216e\u216f\u2170\u2171\u2172\u2173\u2174\u2175\u2176\u2177\u2178\u2179\u217a\u217b\u217c\u217d\u217e\u217f\u2180\u2181\u2182\u2183\u3007\u3021\u3022\u3023\u3024\u3025\u3026\u3027\u3028\u3029\u3038\u3039\u303a'
-
-No = u'\xb2\xb3\xb9\xbc\xbd\xbe\u09f4\u09f5\u09f6\u09f7\u09f8\u09f9\u0bf0\u0bf1\u0bf2\u0f2a\u0f2b\u0f2c\u0f2d\u0f2e\u0f2f\u0f30\u0f31\u0f32\u0f33\u1369\u136a\u136b\u136c\u136d\u136e\u136f\u1370\u1371\u1372\u1373\u1374\u1375\u1376\u1377\u1378\u1379\u137a\u137b\u137c\u17f0\u17f1\u17f2\u17f3\u17f4\u17f5\u17f6\u17f7\u17f8\u17f9\u2070\u2074\u2075\u2076\u2077\u2078\u2079\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u2153\u2154\u2155\u2156\u2157\u2158\u2159\u215a\u215b\u215c\u215d\u215e\u215f\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2469\u246a\u246b\u246c\u246d\u246e\u246f\u2470\u2471\u2472\u2473\u2474\u2475\u2476\u2477\u2478\u2479\u247a\u247b\u247c\u247d\u247e\u247f\u2480\u2481\u2482\u2483\u2484\u2485\u2486\u2487\u2488\u2489\u248a\u248b\u248c\u248d\u248e\u248f\u2490\u2491\u2492\u2493\u2494\u2495\u2496\u2497\u2498\u2499\u249a\u249b\u24ea\u24eb\u24ec\u24ed\u24ee\u24ef\u24f0\u24f1\u24f2\u24f3\u24f4\u24f5\u24f6\u24f7\u24f8\u24f9\u24fa\u24fb\u24fc\u24fd\u24fe\u24ff\u2776\u2777\u2778\u2779\u277a\u277b\u277c\u277d\u277e\u277f\u2780\u2781\u2782\u2783\u2784\u2785\u2786\u2787\u2788\u2789\u278a\u278b\u278c\u278d\u278e\u278f\u2790\u2791\u2792\u2793\u2cfd\u3192\u3193\u3194\u3195\u3220\u3221\u3222\u3223\u3224\u3225\u3226\u3227\u3228\u3229\u3251\u3252\u3253\u3254\u3255\u3256\u3257\u3258\u3259\u325a\u325b\u325c\u325d\u325e\u325f\u3280\u3281\u3282\u3283\u3284\u3285\u3286\u3287\u3288\u3289\u32b1\u32b2\u32b3\u32b4\u32b5\u32b6\u32b7\u32b8\u32b9\u32ba\u32bb\u32bc\u32bd\u32be\u32bf'
-
-Pc = u'_\u203f\u2040\u2054\ufe33\ufe34\ufe4d\ufe4e\ufe4f\uff3f'
-
-Pd = u'-\u058a\u1806\u2010\u2011\u2012\u2013\u2014\u2015\u2e17\u301c\u3030\u30a0\ufe31\ufe32\ufe58\ufe63\uff0d'
-
-Pe = u')]}\u0f3b\u0f3d\u169c\u2046\u207e\u208e\u232a\u23b5\u2769\u276b\u276d\u276f\u2771\u2773\u2775\u27c6\u27e7\u27e9\u27eb\u2984\u2986\u2988\u298a\u298c\u298e\u2990\u2992\u2994\u2996\u2998\u29d9\u29db\u29fd\u3009\u300b\u300d\u300f\u3011\u3015\u3017\u3019\u301b\u301e\u301f\ufd3f\ufe18\ufe36\ufe38\ufe3a\ufe3c\ufe3e\ufe40\ufe42\ufe44\ufe48\ufe5a\ufe5c\ufe5e\uff09\uff3d\uff5d\uff60\uff63'
-
-Pf = u'\xbb\u2019\u201d\u203a\u2e03\u2e05\u2e0a\u2e0d\u2e1d'
-
-Pi = u'\xab\u2018\u201b\u201c\u201f\u2039\u2e02\u2e04\u2e09\u2e0c\u2e1c'
-
-Po = u'!"#%&\'*,./:;?@\\\xa1\xb7\xbf\u037e\u0387\u055a\u055b\u055c\u055d\u055e\u055f\u0589\u05be\u05c0\u05c3\u05c6\u05f3\u05f4\u060c\u060d\u061b\u061e\u061f\u066a\u066b\u066c\u066d\u06d4\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u0964\u0965\u0970\u0df4\u0e4f\u0e5a\u0e5b\u0f04\u0f05\u0f06\u0f07\u0f08\u0f09\u0f0a\u0f0b\u0f0c\u0f0d\u0f0e\u0f0f\u0f10\u0f11\u0f12\u0f85\u0fd0\u0fd1\u104a\u104b\u104c\u104d\u104e\u104f\u10fb\u1361\u1362\u1363\u1364\u1365\u1366\u1367\u1368\u166d\u166e\u16eb\u16ec\u16ed\u1735\u1736\u17d4\u17d5\u17d6\u17d8\u17d9\u17da\u1800\u1801\u1802\u1803\u1804\u1805\u1807\u1808\u1809\u180a\u1944\u1945\u19de\u19df\u1a1e\u1a1f\u2016\u2017\u2020\u2021\u2022\u2023\u2024\u2025\u2026\u2027\u2030\u2031\u2032\u2033\u2034\u2035\u2036\u2037\u2038\u203b\u203c\u203d\u203e\u2041\u2042\u2043\u2047\u2048\u2049\u204a\u204b\u204c\u204d\u204e\u204f\u2050\u2051\u2053\u2055\u2056\u2057\u2058\u2059\u205a\u205b\u205c\u205d\u205e\u23b6\u2cf9\u2cfa\u2cfb\u2cfc\u2cfe\u2cff\u2e00\u2e01\u2e06\u2e07\u2e08\u2e0b\u2e0e\u2e0f\u2e10\u2e11\u2e12\u2e13\u2e14\u2e15\u2e16\u3001\u3002\u3003\u303d\u30fb\ufe10\ufe11\ufe12\ufe13\ufe14\ufe15\ufe16\ufe19\ufe30\ufe45\ufe46\ufe49\ufe4a\ufe4b\ufe4c\ufe50\ufe51\ufe52\ufe54\ufe55\ufe56\ufe57\ufe5f\ufe60\ufe61\ufe68\ufe6a\ufe6b\uff01\uff02\uff03\uff05\uff06\uff07\uff0a\uff0c\uff0e\uff0f\uff1a\uff1b\uff1f\uff20\uff3c\uff61\uff64\uff65'
-
-Ps = u'([{\u0f3a\u0f3c\u169b\u201a\u201e\u2045\u207d\u208d\u2329\u23b4\u2768\u276a\u276c\u276e\u2770\u2772\u2774\u27c5\u27e6\u27e8\u27ea\u2983\u2985\u2987\u2989\u298b\u298d\u298f\u2991\u2993\u2995\u2997\u29d8\u29da\u29fc\u3008\u300a\u300c\u300e\u3010\u3014\u3016\u3018\u301a\u301d\ufd3e\ufe17\ufe35\ufe37\ufe39\ufe3b\ufe3d\ufe3f\ufe41\ufe43\ufe47\ufe59\ufe5b\ufe5d\uff08\uff3b\uff5b\uff5f\uff62'
-
-Sc = u'$\xa2\xa3\xa4\xa5\u060b\u09f2\u09f3\u0af1\u0bf9\u0e3f\u17db\u20a0\u20a1\u20a2\u20a3\u20a4\u20a5\u20a6\u20a7\u20a8\u20a9\u20aa\u20ab\u20ac\u20ad\u20ae\u20af\u20b0\u20b1\u20b2\u20b3\u20b4\u20b5\ufdfc\ufe69\uff04\uffe0\uffe1\uffe5\uffe6'
-
-Sk = u'^`\xa8\xaf\xb4\xb8\u02c2\u02c3\u02c4\u02c5\u02d2\u02d3\u02d4\u02d5\u02d6\u02d7\u02d8\u02d9\u02da\u02db\u02dc\u02dd\u02de\u02df\u02e5\u02e6\u02e7\u02e8\u02e9\u02ea\u02eb\u02ec\u02ed\u02ef\u02f0\u02f1\u02f2\u02f3\u02f4\u02f5\u02f6\u02f7\u02f8\u02f9\u02fa\u02fb\u02fc\u02fd\u02fe\u02ff\u0374\u0375\u0384\u0385\u1fbd\u1fbf\u1fc0\u1fc1\u1fcd\u1fce\u1fcf\u1fdd\u1fde\u1fdf\u1fed\u1fee\u1fef\u1ffd\u1ffe\u309b\u309c\ua700\ua701\ua702\ua703\ua704\ua705\ua706\ua707\ua708\ua709\ua70a\ua70b\ua70c\ua70d\ua70e\ua70f\ua710\ua711\ua712\ua713\ua714\ua715\ua716\uff3e\uff40\uffe3'
-
-Sm = u'+<=>|~\xac\xb1\xd7\xf7\u03f6\u2044\u2052\u207a\u207b\u207c\u208a\u208b\u208c\u2140\u2141\u2142\u2143\u2144\u214b\u2190\u2191\u2192\u2193\u2194\u219a\u219b\u21a0\u21a3\u21a6\u21ae\u21ce\u21cf\u21d2\u21d4\u21f4\u21f5\u21f6\u21f7\u21f8\u21f9\u21fa\u21fb\u21fc\u21fd\u21fe\u21ff\u2200\u2201\u2202\u2203\u2204\u2205\u2206\u2207\u2208\u2209\u220a\u220b\u220c\u220d\u220e\u220f\u2210\u2211\u2212\u2213\u2214\u2215\u2216\u2217\u2218\u2219\u221a\u221b\u221c\u221d\u221e\u221f\u2220\u2221\u2222\u2223\u2224\u2225\u2226\u2227\u2228\u2229\u222a\u222b\u222c\u222d\u222e\u222f\u2230\u2231\u2232\u2233\u2234\u2235\u2236\u2237\u2238\u2239\u223a\u223b\u223c\u223d\u223e\u223f\u2240\u2241\u2242\u2243\u2244\u2245\u2246\u2247\u2248\u2249\u224a\u224b\u224c\u224d\u224e\u224f\u2250\u2251\u2252\u2253\u2254\u2255\u2256\u2257\u2258\u2259\u225a\u225b\u225c\u225d\u225e\u225f\u2260\u2261\u2262\u2263\u2264\u2265\u2266\u2267\u2268\u2269\u226a\u226b\u226c\u226d\u226e\u226f\u2270\u2271\u2272\u2273\u2274\u2275\u2276\u2277\u2278\u2279\u227a\u227b\u227c\u227d\u227e\u227f\u2280\u2281\u2282\u2283\u2284\u2285\u2286\u2287\u2288\u2289\u228a\u228b\u228c\u228d\u228e\u228f\u2290\u2291\u2292\u2293\u2294\u2295\u2296\u2297\u2298\u2299\u229a\u229b\u229c\u229d\u229e\u229f\u22a0\u22a1\u22a2\u22a3\u22a4\u22a5\u22a6\u22a7\u22a8\u22a9\u22aa\u22ab\u22ac\u22ad\u22ae\u22af\u22b0\u22b1\u22b2\u22b3\u22b4\u22b5\u22b6\u22b7\u22b8\u22b9\u22ba\u22bb\u22bc\u22bd\u22be\u22bf\u22c0\u22c1\u22c2\u22c3\u22c4\u22c5\u22c6\u22c7\u22c8\u22c9\u22ca\u22cb\u22cc\u22cd\u22ce\u22cf\u22d0\u22d1\u22d2\u22d3\u22d4\u22d5\u22d6\u22d7\u22d8\u22d9\u22da\u22db\u22dc\u22dd\u22de\u22df\u22e0\u22e1\u22e2\u22e3\u22e4\u22e5\u22e6\u22e7\u22e8\u22e9\u22ea\u22eb\u22ec\u22ed\u22ee\u22ef\u22f0\u22f1\u22f2\u22f3\u22f4\u22f5\u22f6\u22f7\u22f8\u22f9\u22fa\u22fb\u22fc\u22fd\u22fe\u22ff\u2308\u2309\u230a\u230b\u2320\u2321\u237c\u239b\u239c\u239d\u239e\u239f\u23a0\u23a1\u23a2\u23a3\u23a4\u23a5\u23a6\u23a7\u23a8\u23a9\u23aa\u23ab\u23ac\u23ad\u23ae\u23af\u23b0\u23b1\u23b2\u23b3\u25b7\u25c1\u25f8\u25f9\u25fa\u25fb\u25fc\u25fd\u25fe\u25ff\u266f\u27c0\u27c1\u27c2\u27c3\u27c4\u27d0\u27d1\u27d2\u27d3\u27d4\u27d5\u27d6\u27d7\u27d8\u27d9\u27da\u27db\u27dc\u27dd\u27de\u27df\u27e0\u27e1\u27e2\u27e3\u27e4\u27e5\u27f0\u27f1\u27f2\u27f3\u27f4\u27f5\u27f6\u27f7\u27f8\u27f9\u27fa\u27fb\u27fc\u27fd\u27fe\u27ff\u2900\u2901\u2902\u2903\u2904\u2905\u2906\u2907\u2908\u2909\u290a\u290b\u290c\u290d\u290e\u290f\u2910\u2911\u2912\u2913\u2914\u2915\u2916\u2917\u2918\u2919\u291a\u291b\u291c\u291d\u291e\u291f\u2920\u2921\u2922\u2923\u2924\u2925\u2926\u2927\u2928\u2929\u292a\u292b\u292c\u292d\u292e\u292f\u2930\u2931\u2932\u2933\u2934\u2935\u2936\u2937\u2938\u2939\u293a\u293b\u293c\u293d\u293e\u293f\u2940\u2941\u2942\u2943\u2944\u2945\u2946\u2947\u2948\u2949\u294a\u294b\u294c\u294d\u294e\u294f\u2950\u2951\u2952\u2953\u2954\u2955\u2956\u2957\u2958\u2959\u295a\u295b\u295c\u295d\u295e\u295f\u2960\u2961\u2962\u2963\u2964\u2965\u2966\u2967\u2968\u2969\u296a\u296b\u296c\u296d\u296e\u296f\u2970\u2971\u2972\u2973\u2974\u2975\u2976\u2977\u2978\u2979\u297a\u297b\u297c\u297d\u297e\u297f\u2980\u2981\u2982\u2999\u299a\u299b\u299c\u299d\u299e\u299f\u29a0\u29a1\u29a2\u29a3\u29a4\u29a5\u29a6\u29a7\u29a8\u29a9\u29aa\u29ab\u29ac\u29ad\u29ae\u29af\u29b0\u29b1\u29b2\u29b3\u29b4\u29b5\u29b6\u29b7\u29b8\u29b9\u29ba\u29bb\u29bc\u29bd\u29be\u29bf\u29c0\u29c1\u29c2\u29c3\u29c4\u29c5\u29c6\u29c7\u29c8\u29c9\u29ca\u29cb\u29cc\u29cd\u29ce\u29cf\u29d0\u29d1\u29d2\u29d3\u29d4\u29d5\u29d6\u29d7\u29dc\u29dd\u29de\u29df\u29e0\u29e1\u29e2\u29e3\u29e4\u29e5\u29e6\u29e7\u29e8\u29e9\u29ea\u29eb\u29ec\u29ed\u29ee\u29ef\u29f0\u29f1\u29f2\u29f3\u29f4\u29f5\u29f6\u29f7\u29f8\u29f9\u29fa\u29fb\u29fe\u29ff\u2a00\u2a01\u2a02\u2a03\u2a04\u2a05\u2a06\u2a07\u2a08\u2a09\u2a0a\u2a0b\u2a0c\u2a0d\u2a0e\u2a0f\u2a10\u2a11\u2a12\u2a13\u2a14\u2a15\u2a16\u2a17\u2a18\u2a19\u2a1a\u2a1b\u2a1c\u2a1d\u2a1e\u2a1f\u2a20\u2a21\u2a22\u2a23\u2a24\u2a25\u2a26\u2a27\u2a28\u2a29\u2a2a\u2a2b\u2a2c\u2a2d\u2a2e\u2a2f\u2a30\u2a31\u2a32\u2a33\u2a34\u2a35\u2a36\u2a37\u2a38\u2a39\u2a3a\u2a3b\u2a3c\u2a3d\u2a3e\u2a3f\u2a40\u2a41\u2a42\u2a43\u2a44\u2a45\u2a46\u2a47\u2a48\u2a49\u2a4a\u2a4b\u2a4c\u2a4d\u2a4e\u2a4f\u2a50\u2a51\u2a52\u2a53\u2a54\u2a55\u2a56\u2a57\u2a58\u2a59\u2a5a\u2a5b\u2a5c\u2a5d\u2a5e\u2a5f\u2a60\u2a61\u2a62\u2a63\u2a64\u2a65\u2a66\u2a67\u2a68\u2a69\u2a6a\u2a6b\u2a6c\u2a6d\u2a6e\u2a6f\u2a70\u2a71\u2a72\u2a73\u2a74\u2a75\u2a76\u2a77\u2a78\u2a79\u2a7a\u2a7b\u2a7c\u2a7d\u2a7e\u2a7f\u2a80\u2a81\u2a82\u2a83\u2a84\u2a85\u2a86\u2a87\u2a88\u2a89\u2a8a\u2a8b\u2a8c\u2a8d\u2a8e\u2a8f\u2a90\u2a91\u2a92\u2a93\u2a94\u2a95\u2a96\u2a97\u2a98\u2a99\u2a9a\u2a9b\u2a9c\u2a9d\u2a9e\u2a9f\u2aa0\u2aa1\u2aa2\u2aa3\u2aa4\u2aa5\u2aa6\u2aa7\u2aa8\u2aa9\u2aaa\u2aab\u2aac\u2aad\u2aae\u2aaf\u2ab0\u2ab1\u2ab2\u2ab3\u2ab4\u2ab5\u2ab6\u2ab7\u2ab8\u2ab9\u2aba\u2abb\u2abc\u2abd\u2abe\u2abf\u2ac0\u2ac1\u2ac2\u2ac3\u2ac4\u2ac5\u2ac6\u2ac7\u2ac8\u2ac9\u2aca\u2acb\u2acc\u2acd\u2ace\u2acf\u2ad0\u2ad1\u2ad2\u2ad3\u2ad4\u2ad5\u2ad6\u2ad7\u2ad8\u2ad9\u2ada\u2adb\u2adc\u2add\u2ade\u2adf\u2ae0\u2ae1\u2ae2\u2ae3\u2ae4\u2ae5\u2ae6\u2ae7\u2ae8\u2ae9\u2aea\u2aeb\u2aec\u2aed\u2aee\u2aef\u2af0\u2af1\u2af2\u2af3\u2af4\u2af5\u2af6\u2af7\u2af8\u2af9\u2afa\u2afb\u2afc\u2afd\u2afe\u2aff\ufb29\ufe62\ufe64\ufe65\ufe66\uff0b\uff1c\uff1d\uff1e\uff5c\uff5e\uffe2\uffe9\uffea\uffeb\uffec'
-
-So = u'\xa6\xa7\xa9\xae\xb0\xb6\u0482\u060e\u060f\u06e9\u06fd\u06fe\u09fa\u0b70\u0bf3\u0bf4\u0bf5\u0bf6\u0bf7\u0bf8\u0bfa\u0f01\u0f02\u0f03\u0f13\u0f14\u0f15\u0f16\u0f17\u0f1a\u0f1b\u0f1c\u0f1d\u0f1e\u0f1f\u0f34\u0f36\u0f38\u0fbe\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0fc5\u0fc7\u0fc8\u0fc9\u0fca\u0fcb\u0fcc\u0fcf\u1360\u1390\u1391\u1392\u1393\u1394\u1395\u1396\u1397\u1398\u1399\u1940\u19e0\u19e1\u19e2\u19e3\u19e4\u19e5\u19e6\u19e7\u19e8\u19e9\u19ea\u19eb\u19ec\u19ed\u19ee\u19ef\u19f0\u19f1\u19f2\u19f3\u19f4\u19f5\u19f6\u19f7\u19f8\u19f9\u19fa\u19fb\u19fc\u19fd\u19fe\u19ff\u2100\u2101\u2103\u2104\u2105\u2106\u2108\u2109\u2114\u2116\u2117\u2118\u211e\u211f\u2120\u2121\u2122\u2123\u2125\u2127\u2129\u212e\u2132\u213a\u213b\u214a\u214c\u2195\u2196\u2197\u2198\u2199\u219c\u219d\u219e\u219f\u21a1\u21a2\u21a4\u21a5\u21a7\u21a8\u21a9\u21aa\u21ab\u21ac\u21ad\u21af\u21b0\u21b1\u21b2\u21b3\u21b4\u21b5\u21b6\u21b7\u21b8\u21b9\u21ba\u21bb\u21bc\u21bd\u21be\u21bf\u21c0\u21c1\u21c2\u21c3\u21c4\u21c5\u21c6\u21c7\u21c8\u21c9\u21ca\u21cb\u21cc\u21cd\u21d0\u21d1\u21d3\u21d5\u21d6\u21d7\u21d8\u21d9\u21da\u21db\u21dc\u21dd\u21de\u21df\u21e0\u21e1\u21e2\u21e3\u21e4\u21e5\u21e6\u21e7\u21e8\u21e9\u21ea\u21eb\u21ec\u21ed\u21ee\u21ef\u21f0\u21f1\u21f2\u21f3\u2300\u2301\u2302\u2303\u2304\u2305\u2306\u2307\u230c\u230d\u230e\u230f\u2310\u2311\u2312\u2313\u2314\u2315\u2316\u2317\u2318\u2319\u231a\u231b\u231c\u231d\u231e\u231f\u2322\u2323\u2324\u2325\u2326\u2327\u2328\u232b\u232c\u232d\u232e\u232f\u2330\u2331\u2332\u2333\u2334\u2335\u2336\u2337\u2338\u2339\u233a\u233b\u233c\u233d\u233e\u233f\u2340\u2341\u2342\u2343\u2344\u2345\u2346\u2347\u2348\u2349\u234a\u234b\u234c\u234d\u234e\u234f\u2350\u2351\u2352\u2353\u2354\u2355\u2356\u2357\u2358\u2359\u235a\u235b\u235c\u235d\u235e\u235f\u2360\u2361\u2362\u2363\u2364\u2365\u2366\u2367\u2368\u2369\u236a\u236b\u236c\u236d\u236e\u236f\u2370\u2371\u2372\u2373\u2374\u2375\u2376\u2377\u2378\u2379\u237a\u237b\u237d\u237e\u237f\u2380\u2381\u2382\u2383\u2384\u2385\u2386\u2387\u2388\u2389\u238a\u238b\u238c\u238d\u238e\u238f\u2390\u2391\u2392\u2393\u2394\u2395\u2396\u2397\u2398\u2399\u239a\u23b7\u23b8\u23b9\u23ba\u23bb\u23bc\u23bd\u23be\u23bf\u23c0\u23c1\u23c2\u23c3\u23c4\u23c5\u23c6\u23c7\u23c8\u23c9\u23ca\u23cb\u23cc\u23cd\u23ce\u23cf\u23d0\u23d1\u23d2\u23d3\u23d4\u23d5\u23d6\u23d7\u23d8\u23d9\u23da\u23db\u2400\u2401\u2402\u2403\u2404\u2405\u2406\u2407\u2408\u2409\u240a\u240b\u240c\u240d\u240e\u240f\u2410\u2411\u2412\u2413\u2414\u2415\u2416\u2417\u2418\u2419\u241a\u241b\u241c\u241d\u241e\u241f\u2420\u2421\u2422\u2423\u2424\u2425\u2426\u2440\u2441\u2442\u2443\u2444\u2445\u2446\u2447\u2448\u2449\u244a\u249c\u249d\u249e\u249f\u24a0\u24a1\u24a2\u24a3\u24a4\u24a5\u24a6\u24a7\u24a8\u24a9\u24aa\u24ab\u24ac\u24ad\u24ae\u24af\u24b0\u24b1\u24b2\u24b3\u24b4\u24b5\u24b6\u24b7\u24b8\u24b9\u24ba\u24bb\u24bc\u24bd\u24be\u24bf\u24c0\u24c1\u24c2\u24c3\u24c4\u24c5\u24c6\u24c7\u24c8\u24c9\u24ca\u24cb\u24cc\u24cd\u24ce\u24cf\u24d0\u24d1\u24d2\u24d3\u24d4\u24d5\u24d6\u24d7\u24d8\u24d9\u24da\u24db\u24dc\u24dd\u24de\u24df\u24e0\u24e1\u24e2\u24e3\u24e4\u24e5\u24e6\u24e7\u24e8\u24e9\u2500\u2501\u2502\u2503\u2504\u2505\u2506\u2507\u2508\u2509\u250a\u250b\u250c\u250d\u250e\u250f\u2510\u2511\u2512\u2513\u2514\u2515\u2516\u2517\u2518\u2519\u251a\u251b\u251c\u251d\u251e\u251f\u2520\u2521\u2522\u2523\u2524\u2525\u2526\u2527\u2528\u2529\u252a\u252b\u252c\u252d\u252e\u252f\u2530\u2531\u2532\u2533\u2534\u2535\u2536\u2537\u2538\u2539\u253a\u253b\u253c\u253d\u253e\u253f\u2540\u2541\u2542\u2543\u2544\u2545\u2546\u2547\u2548\u2549\u254a\u254b\u254c\u254d\u254e\u254f\u2550\u2551\u2552\u2553\u2554\u2555\u2556\u2557\u2558\u2559\u255a\u255b\u255c\u255d\u255e\u255f\u2560\u2561\u2562\u2563\u2564\u2565\u2566\u2567\u2568\u2569\u256a\u256b\u256c\u256d\u256e\u256f\u2570\u2571\u2572\u2573\u2574\u2575\u2576\u2577\u2578\u2579\u257a\u257b\u257c\u257d\u257e\u257f\u2580\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588\u2589\u258a\u258b\u258c\u258d\u258e\u258f\u2590\u2591\u2592\u2593\u2594\u2595\u2596\u2597\u2598\u2599\u259a\u259b\u259c\u259d\u259e\u259f\u25a0\u25a1\u25a2\u25a3\u25a4\u25a5\u25a6\u25a7\u25a8\u25a9\u25aa\u25ab\u25ac\u25ad\u25ae\u25af\u25b0\u25b1\u25b2\u25b3\u25b4\u25b5\u25b6\u25b8\u25b9\u25ba\u25bb\u25bc\u25bd\u25be\u25bf\u25c0\u25c2\u25c3\u25c4\u25c5\u25c6\u25c7\u25c8\u25c9\u25ca\u25cb\u25cc\u25cd\u25ce\u25cf\u25d0\u25d1\u25d2\u25d3\u25d4\u25d5\u25d6\u25d7\u25d8\u25d9\u25da\u25db\u25dc\u25dd\u25de\u25df\u25e0\u25e1\u25e2\u25e3\u25e4\u25e5\u25e6\u25e7\u25e8\u25e9\u25ea\u25eb\u25ec\u25ed\u25ee\u25ef\u25f0\u25f1\u25f2\u25f3\u25f4\u25f5\u25f6\u25f7\u2600\u2601\u2602\u2603\u2604\u2605\u2606\u2607\u2608\u2609\u260a\u260b\u260c\u260d\u260e\u260f\u2610\u2611\u2612\u2613\u2614\u2615\u2616\u2617\u2618\u2619\u261a\u261b\u261c\u261d\u261e\u261f\u2620\u2621\u2622\u2623\u2624\u2625\u2626\u2627\u2628\u2629\u262a\u262b\u262c\u262d\u262e\u262f\u2630\u2631\u2632\u2633\u2634\u2635\u2636\u2637\u2638\u2639\u263a\u263b\u263c\u263d\u263e\u263f\u2640\u2641\u2642\u2643\u2644\u2645\u2646\u2647\u2648\u2649\u264a\u264b\u264c\u264d\u264e\u264f\u2650\u2651\u2652\u2653\u2654\u2655\u2656\u2657\u2658\u2659\u265a\u265b\u265c\u265d\u265e\u265f\u2660\u2661\u2662\u2663\u2664\u2665\u2666\u2667\u2668\u2669\u266a\u266b\u266c\u266d\u266e\u2670\u2671\u2672\u2673\u2674\u2675\u2676\u2677\u2678\u2679\u267a\u267b\u267c\u267d\u267e\u267f\u2680\u2681\u2682\u2683\u2684\u2685\u2686\u2687\u2688\u2689\u268a\u268b\u268c\u268d\u268e\u268f\u2690\u2691\u2692\u2693\u2694\u2695\u2696\u2697\u2698\u2699\u269a\u269b\u269c\u26a0\u26a1\u26a2\u26a3\u26a4\u26a5\u26a6\u26a7\u26a8\u26a9\u26aa\u26ab\u26ac\u26ad\u26ae\u26af\u26b0\u26b1\u2701\u2702\u2703\u2704\u2706\u2707\u2708\u2709\u270c\u270d\u270e\u270f\u2710\u2711\u2712\u2713\u2714\u2715\u2716\u2717\u2718\u2719\u271a\u271b\u271c\u271d\u271e\u271f\u2720\u2721\u2722\u2723\u2724\u2725\u2726\u2727\u2729\u272a\u272b\u272c\u272d\u272e\u272f\u2730\u2731\u2732\u2733\u2734\u2735\u2736\u2737\u2738\u2739\u273a\u273b\u273c\u273d\u273e\u273f\u2740\u2741\u2742\u2743\u2744\u2745\u2746\u2747\u2748\u2749\u274a\u274b\u274d\u274f\u2750\u2751\u2752\u2756\u2758\u2759\u275a\u275b\u275c\u275d\u275e\u2761\u2762\u2763\u2764\u2765\u2766\u2767\u2794\u2798\u2799\u279a\u279b\u279c\u279d\u279e\u279f\u27a0\u27a1\u27a2\u27a3\u27a4\u27a5\u27a6\u27a7\u27a8\u27a9\u27aa\u27ab\u27ac\u27ad\u27ae\u27af\u27b1\u27b2\u27b3\u27b4\u27b5\u27b6\u27b7\u27b8\u27b9\u27ba\u27bb\u27bc\u27bd\u27be\u2800\u2801\u2802\u2803\u2804\u2805\u2806\u2807\u2808\u2809\u280a\u280b\u280c\u280d\u280e\u280f\u2810\u2811\u2812\u2813\u2814\u2815\u2816\u2817\u2818\u2819\u281a\u281b\u281c\u281d\u281e\u281f\u2820\u2821\u2822\u2823\u2824\u2825\u2826\u2827\u2828\u2829\u282a\u282b\u282c\u282d\u282e\u282f\u2830\u2831\u2832\u2833\u2834\u2835\u2836\u2837\u2838\u2839\u283a\u283b\u283c\u283d\u283e\u283f\u2840\u2841\u2842\u2843\u2844\u2845\u2846\u2847\u2848\u2849\u284a\u284b\u284c\u284d\u284e\u284f\u2850\u2851\u2852\u2853\u2854\u2855\u2856\u2857\u2858\u2859\u285a\u285b\u285c\u285d\u285e\u285f\u2860\u2861\u2862\u2863\u2864\u2865\u2866\u2867\u2868\u2869\u286a\u286b\u286c\u286d\u286e\u286f\u2870\u2871\u2872\u2873\u2874\u2875\u2876\u2877\u2878\u2879\u287a\u287b\u287c\u287d\u287e\u287f\u2880\u2881\u2882\u2883\u2884\u2885\u2886\u2887\u2888\u2889\u288a\u288b\u288c\u288d\u288e\u288f\u2890\u2891\u2892\u2893\u2894\u2895\u2896\u2897\u2898\u2899\u289a\u289b\u289c\u289d\u289e\u289f\u28a0\u28a1\u28a2\u28a3\u28a4\u28a5\u28a6\u28a7\u28a8\u28a9\u28aa\u28ab\u28ac\u28ad\u28ae\u28af\u28b0\u28b1\u28b2\u28b3\u28b4\u28b5\u28b6\u28b7\u28b8\u28b9\u28ba\u28bb\u28bc\u28bd\u28be\u28bf\u28c0\u28c1\u28c2\u28c3\u28c4\u28c5\u28c6\u28c7\u28c8\u28c9\u28ca\u28cb\u28cc\u28cd\u28ce\u28cf\u28d0\u28d1\u28d2\u28d3\u28d4\u28d5\u28d6\u28d7\u28d8\u28d9\u28da\u28db\u28dc\u28dd\u28de\u28df\u28e0\u28e1\u28e2\u28e3\u28e4\u28e5\u28e6\u28e7\u28e8\u28e9\u28ea\u28eb\u28ec\u28ed\u28ee\u28ef\u28f0\u28f1\u28f2\u28f3\u28f4\u28f5\u28f6\u28f7\u28f8\u28f9\u28fa\u28fb\u28fc\u28fd\u28fe\u28ff\u2b00\u2b01\u2b02\u2b03\u2b04\u2b05\u2b06\u2b07\u2b08\u2b09\u2b0a\u2b0b\u2b0c\u2b0d\u2b0e\u2b0f\u2b10\u2b11\u2b12\u2b13\u2ce5\u2ce6\u2ce7\u2ce8\u2ce9\u2cea\u2e80\u2e81\u2e82\u2e83\u2e84\u2e85\u2e86\u2e87\u2e88\u2e89\u2e8a\u2e8b\u2e8c\u2e8d\u2e8e\u2e8f\u2e90\u2e91\u2e92\u2e93\u2e94\u2e95\u2e96\u2e97\u2e98\u2e99\u2e9b\u2e9c\u2e9d\u2e9e\u2e9f\u2ea0\u2ea1\u2ea2\u2ea3\u2ea4\u2ea5\u2ea6\u2ea7\u2ea8\u2ea9\u2eaa\u2eab\u2eac\u2ead\u2eae\u2eaf\u2eb0\u2eb1\u2eb2\u2eb3\u2eb4\u2eb5\u2eb6\u2eb7\u2eb8\u2eb9\u2eba\u2ebb\u2ebc\u2ebd\u2ebe\u2ebf\u2ec0\u2ec1\u2ec2\u2ec3\u2ec4\u2ec5\u2ec6\u2ec7\u2ec8\u2ec9\u2eca\u2ecb\u2ecc\u2ecd\u2ece\u2ecf\u2ed0\u2ed1\u2ed2\u2ed3\u2ed4\u2ed5\u2ed6\u2ed7\u2ed8\u2ed9\u2eda\u2edb\u2edc\u2edd\u2ede\u2edf\u2ee0\u2ee1\u2ee2\u2ee3\u2ee4\u2ee5\u2ee6\u2ee7\u2ee8\u2ee9\u2eea\u2eeb\u2eec\u2eed\u2eee\u2eef\u2ef0\u2ef1\u2ef2\u2ef3\u2f00\u2f01\u2f02\u2f03\u2f04\u2f05\u2f06\u2f07\u2f08\u2f09\u2f0a\u2f0b\u2f0c\u2f0d\u2f0e\u2f0f\u2f10\u2f11\u2f12\u2f13\u2f14\u2f15\u2f16\u2f17\u2f18\u2f19\u2f1a\u2f1b\u2f1c\u2f1d\u2f1e\u2f1f\u2f20\u2f21\u2f22\u2f23\u2f24\u2f25\u2f26\u2f27\u2f28\u2f29\u2f2a\u2f2b\u2f2c\u2f2d\u2f2e\u2f2f\u2f30\u2f31\u2f32\u2f33\u2f34\u2f35\u2f36\u2f37\u2f38\u2f39\u2f3a\u2f3b\u2f3c\u2f3d\u2f3e\u2f3f\u2f40\u2f41\u2f42\u2f43\u2f44\u2f45\u2f46\u2f47\u2f48\u2f49\u2f4a\u2f4b\u2f4c\u2f4d\u2f4e\u2f4f\u2f50\u2f51\u2f52\u2f53\u2f54\u2f55\u2f56\u2f57\u2f58\u2f59\u2f5a\u2f5b\u2f5c\u2f5d\u2f5e\u2f5f\u2f60\u2f61\u2f62\u2f63\u2f64\u2f65\u2f66\u2f67\u2f68\u2f69\u2f6a\u2f6b\u2f6c\u2f6d\u2f6e\u2f6f\u2f70\u2f71\u2f72\u2f73\u2f74\u2f75\u2f76\u2f77\u2f78\u2f79\u2f7a\u2f7b\u2f7c\u2f7d\u2f7e\u2f7f\u2f80\u2f81\u2f82\u2f83\u2f84\u2f85\u2f86\u2f87\u2f88\u2f89\u2f8a\u2f8b\u2f8c\u2f8d\u2f8e\u2f8f\u2f90\u2f91\u2f92\u2f93\u2f94\u2f95\u2f96\u2f97\u2f98\u2f99\u2f9a\u2f9b\u2f9c\u2f9d\u2f9e\u2f9f\u2fa0\u2fa1\u2fa2\u2fa3\u2fa4\u2fa5\u2fa6\u2fa7\u2fa8\u2fa9\u2faa\u2fab\u2fac\u2fad\u2fae\u2faf\u2fb0\u2fb1\u2fb2\u2fb3\u2fb4\u2fb5\u2fb6\u2fb7\u2fb8\u2fb9\u2fba\u2fbb\u2fbc\u2fbd\u2fbe\u2fbf\u2fc0\u2fc1\u2fc2\u2fc3\u2fc4\u2fc5\u2fc6\u2fc7\u2fc8\u2fc9\u2fca\u2fcb\u2fcc\u2fcd\u2fce\u2fcf\u2fd0\u2fd1\u2fd2\u2fd3\u2fd4\u2fd5\u2ff0\u2ff1\u2ff2\u2ff3\u2ff4\u2ff5\u2ff6\u2ff7\u2ff8\u2ff9\u2ffa\u2ffb\u3004\u3012\u3013\u3020\u3036\u3037\u303e\u303f\u3190\u3191\u3196\u3197\u3198\u3199\u319a\u319b\u319c\u319d\u319e\u319f\u31c0\u31c1\u31c2\u31c3\u31c4\u31c5\u31c6\u31c7\u31c8\u31c9\u31ca\u31cb\u31cc\u31cd\u31ce\u31cf\u3200\u3201\u3202\u3203\u3204\u3205\u3206\u3207\u3208\u3209\u320a\u320b\u320c\u320d\u320e\u320f\u3210\u3211\u3212\u3213\u3214\u3215\u3216\u3217\u3218\u3219\u321a\u321b\u321c\u321d\u321e\u322a\u322b\u322c\u322d\u322e\u322f\u3230\u3231\u3232\u3233\u3234\u3235\u3236\u3237\u3238\u3239\u323a\u323b\u323c\u323d\u323e\u323f\u3240\u3241\u3242\u3243\u3250\u3260\u3261\u3262\u3263\u3264\u3265\u3266\u3267\u3268\u3269\u326a\u326b\u326c\u326d\u326e\u326f\u3270\u3271\u3272\u3273\u3274\u3275\u3276\u3277\u3278\u3279\u327a\u327b\u327c\u327d\u327e\u327f\u328a\u328b\u328c\u328d\u328e\u328f\u3290\u3291\u3292\u3293\u3294\u3295\u3296\u3297\u3298\u3299\u329a\u329b\u329c\u329d\u329e\u329f\u32a0\u32a1\u32a2\u32a3\u32a4\u32a5\u32a6\u32a7\u32a8\u32a9\u32aa\u32ab\u32ac\u32ad\u32ae\u32af\u32b0\u32c0\u32c1\u32c2\u32c3\u32c4\u32c5\u32c6\u32c7\u32c8\u32c9\u32ca\u32cb\u32cc\u32cd\u32ce\u32cf\u32d0\u32d1\u32d2\u32d3\u32d4\u32d5\u32d6\u32d7\u32d8\u32d9\u32da\u32db\u32dc\u32dd\u32de\u32df\u32e0\u32e1\u32e2\u32e3\u32e4\u32e5\u32e6\u32e7\u32e8\u32e9\u32ea\u32eb\u32ec\u32ed\u32ee\u32ef\u32f0\u32f1\u32f2\u32f3\u32f4\u32f5\u32f6\u32f7\u32f8\u32f9\u32fa\u32fb\u32fc\u32fd\u32fe\u3300\u3301\u3302\u3303\u3304\u3305\u3306\u3307\u3308\u3309\u330a\u330b\u330c\u330d\u330e\u330f\u3310\u3311\u3312\u3313\u3314\u3315\u3316\u3317\u3318\u3319\u331a\u331b\u331c\u331d\u331e\u331f\u3320\u3321\u3322\u3323\u3324\u3325\u3326\u3327\u3328\u3329\u332a\u332b\u332c\u332d\u332e\u332f\u3330\u3331\u3332\u3333\u3334\u3335\u3336\u3337\u3338\u3339\u333a\u333b\u333c\u333d\u333e\u333f\u3340\u3341\u3342\u3343\u3344\u3345\u3346\u3347\u3348\u3349\u334a\u334b\u334c\u334d\u334e\u334f\u3350\u3351\u3352\u3353\u3354\u3355\u3356\u3357\u3358\u3359\u335a\u335b\u335c\u335d\u335e\u335f\u3360\u3361\u3362\u3363\u3364\u3365\u3366\u3367\u3368\u3369\u336a\u336b\u336c\u336d\u336e\u336f\u3370\u3371\u3372\u3373\u3374\u3375\u3376\u3377\u3378\u3379\u337a\u337b\u337c\u337d\u337e\u337f\u3380\u3381\u3382\u3383\u3384\u3385\u3386\u3387\u3388\u3389\u338a\u338b\u338c\u338d\u338e\u338f\u3390\u3391\u3392\u3393\u3394\u3395\u3396\u3397\u3398\u3399\u339a\u339b\u339c\u339d\u339e\u339f\u33a0\u33a1\u33a2\u33a3\u33a4\u33a5\u33a6\u33a7\u33a8\u33a9\u33aa\u33ab\u33ac\u33ad\u33ae\u33af\u33b0\u33b1\u33b2\u33b3\u33b4\u33b5\u33b6\u33b7\u33b8\u33b9\u33ba\u33bb\u33bc\u33bd\u33be\u33bf\u33c0\u33c1\u33c2\u33c3\u33c4\u33c5\u33c6\u33c7\u33c8\u33c9\u33ca\u33cb\u33cc\u33cd\u33ce\u33cf\u33d0\u33d1\u33d2\u33d3\u33d4\u33d5\u33d6\u33d7\u33d8\u33d9\u33da\u33db\u33dc\u33dd\u33de\u33df\u33e0\u33e1\u33e2\u33e3\u33e4\u33e5\u33e6\u33e7\u33e8\u33e9\u33ea\u33eb\u33ec\u33ed\u33ee\u33ef\u33f0\u33f1\u33f2\u33f3\u33f4\u33f5\u33f6\u33f7\u33f8\u33f9\u33fa\u33fb\u33fc\u33fd\u33fe\u33ff\u4dc0\u4dc1\u4dc2\u4dc3\u4dc4\u4dc5\u4dc6\u4dc7\u4dc8\u4dc9\u4dca\u4dcb\u4dcc\u4dcd\u4dce\u4dcf\u4dd0\u4dd1\u4dd2\u4dd3\u4dd4\u4dd5\u4dd6\u4dd7\u4dd8\u4dd9\u4dda\u4ddb\u4ddc\u4ddd\u4dde\u4ddf\u4de0\u4de1\u4de2\u4de3\u4de4\u4de5\u4de6\u4de7\u4de8\u4de9\u4dea\u4deb\u4dec\u4ded\u4dee\u4def\u4df0\u4df1\u4df2\u4df3\u4df4\u4df5\u4df6\u4df7\u4df8\u4df9\u4dfa\u4dfb\u4dfc\u4dfd\u4dfe\u4dff\ua490\ua491\ua492\ua493\ua494\ua495\ua496\ua497\ua498\ua499\ua49a\ua49b\ua49c\ua49d\ua49e\ua49f\ua4a0\ua4a1\ua4a2\ua4a3\ua4a4\ua4a5\ua4a6\ua4a7\ua4a8\ua4a9\ua4aa\ua4ab\ua4ac\ua4ad\ua4ae\ua4af\ua4b0\ua4b1\ua4b2\ua4b3\ua4b4\ua4b5\ua4b6\ua4b7\ua4b8\ua4b9\ua4ba\ua4bb\ua4bc\ua4bd\ua4be\ua4bf\ua4c0\ua4c1\ua4c2\ua4c3\ua4c4\ua4c5\ua4c6\ua828\ua829\ua82a\ua82b\ufdfd\uffe4\uffe8\uffed\uffee\ufffc\ufffd'
-
-Zl = u'\u2028'
-
-Zp = u'\u2029'
-
-Zs = u' \xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000'
-
-cats = ['Cc', 'Cf', 'Cn', 'Co', 'Cs', 'Ll', 'Lm', 'Lo', 'Lt', 'Lu', 'Mc', 'Me', 'Mn', 'Nd', 'Nl', 'No', 'Pc', 'Pd', 'Pe', 'Pf', 'Pi', 'Po', 'Ps', 'Sc', 'Sk', 'Sm', 'So', 'Zl', 'Zp', 'Zs']
-
-def combine(*args):
- return u''.join([globals()[cat] for cat in args])
-
-xid_start = u'\u0041-\u005A\u005F\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u01BA\u01BB\u01BC-\u01BF\u01C0-\u01C3\u01C4-\u0241\u0250-\u02AF\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EE\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03CE\u03D0-\u03F5\u03F7-\u0481\u048A-\u04CE\u04D0-\u04F9\u0500-\u050F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0621-\u063A\u0640\u0641-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u076D\u0780-\u07A5\u07B1\u0904-\u0939\u093D\u0950\u0958-\u0961\u097D\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C60-\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D60-\u0D61\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E40-\u0E45\u0E46\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDD\u0F00\u0F40-\u0F47\u0F49-\u0F6A\u0F88-\u0F8B\u1000-\u1021\u1023-\u1027\u1029-\u102A\u1050-\u1055\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1159\u115F-\u11A2\u11A8-\u11F9\u1200-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u1676\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1842\u1843\u1844-\u1877\u1880-\u18A8\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19A9\u19C1-\u19C7\u1A00-\u1A16\u1D00-\u1D2B\u1D2C-\u1D61\u1D62-\u1D77\u1D78\u1D79-\u1D9A\u1D9B-\u1DBF\u1E00-\u1E9B\u1EA0-\u1EF9\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u2094\u2102\u2107\u210A-\u2113\u2115\u2118\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212E\u212F-\u2131\u2133-\u2134\u2135-\u2138\u2139\u213C-\u213F\u2145-\u2149\u2160-\u2183\u2C00-\u2C2E\u2C30-\u2C5E\u2C80-\u2CE4\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005\u3006\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303A\u303B\u303C\u3041-\u3096\u309D-\u309E\u309F\u30A1-\u30FA\u30FC-\u30FE\u30FF\u3105-\u312C\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FBB\uA000-\uA014\uA015\uA016-\uA48C\uA800-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uAC00-\uD7A3\uF900-\uFA2D\uFA30-\uFA6A\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFC5D\uFC64-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDF9\uFE71\uFE73\uFE77\uFE79\uFE7B\uFE7D\uFE7F-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFF6F\uFF70\uFF71-\uFF9D\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC'
-
-xid_continue = u'\u0030-\u0039\u0041-\u005A\u005F\u0061-\u007A\u00AA\u00B5\u00B7\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u01BA\u01BB\u01BC-\u01BF\u01C0-\u01C3\u01C4-\u0241\u0250-\u02AF\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EE\u0300-\u036F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03CE\u03D0-\u03F5\u03F7-\u0481\u0483-\u0486\u048A-\u04CE\u04D0-\u04F9\u0500-\u050F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05B9\u05BB-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u0615\u0621-\u063A\u0640\u0641-\u064A\u064B-\u065E\u0660-\u0669\u066E-\u066F\u0670\u0671-\u06D3\u06D5\u06D6-\u06DC\u06DF-\u06E4\u06E5-\u06E6\u06E7-\u06E8\u06EA-\u06ED\u06EE-\u06EF\u06F0-\u06F9\u06FA-\u06FC\u06FF\u0710\u0711\u0712-\u072F\u0730-\u074A\u074D-\u076D\u0780-\u07A5\u07A6-\u07B0\u07B1\u0901-\u0902\u0903\u0904-\u0939\u093C\u093D\u093E-\u0940\u0941-\u0948\u0949-\u094C\u094D\u0950\u0951-\u0954\u0958-\u0961\u0962-\u0963\u0966-\u096F\u097D\u0981\u0982-\u0983\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC\u09BD\u09BE-\u09C0\u09C1-\u09C4\u09C7-\u09C8\u09CB-\u09CC\u09CD\u09CE\u09D7\u09DC-\u09DD\u09DF-\u09E1\u09E2-\u09E3\u09E6-\u09EF\u09F0-\u09F1\u0A01-\u0A02\u0A03\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A3C\u0A3E-\u0A40\u0A41-\u0A42\u0A47-\u0A48\u0A4B-\u0A4D\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A70-\u0A71\u0A72-\u0A74\u0A81-\u0A82\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABC\u0ABD\u0ABE-\u0AC0\u0AC1-\u0AC5\u0AC7-\u0AC8\u0AC9\u0ACB-\u0ACC\u0ACD\u0AD0\u0AE0-\u0AE1\u0AE2-\u0AE3\u0AE6-\u0AEF\u0B01\u0B02-\u0B03\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3C\u0B3D\u0B3E\u0B3F\u0B40\u0B41-\u0B43\u0B47-\u0B48\u0B4B-\u0B4C\u0B4D\u0B56\u0B57\u0B5C-\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BBF\u0BC0\u0BC1-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BCD\u0BD7\u0BE6-\u0BEF\u0C01-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3E-\u0C40\u0C41-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55-\u0C56\u0C60-\u0C61\u0C66-\u0C6F\u0C82-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC\u0CBD\u0CBE\u0CBF\u0CC0-\u0CC4\u0CC6\u0CC7-\u0CC8\u0CCA-\u0CCB\u0CCC-\u0CCD\u0CD5-\u0CD6\u0CDE\u0CE0-\u0CE1\u0CE6-\u0CEF\u0D02-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3E-\u0D40\u0D41-\u0D43\u0D46-\u0D48\u0D4A-\u0D4C\u0D4D\u0D57\u0D60-\u0D61\u0D66-\u0D6F\u0D82-\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD1\u0DD2-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2-\u0DF3\u0E01-\u0E30\u0E31\u0E32-\u0E33\u0E34-\u0E3A\u0E40-\u0E45\u0E46\u0E47-\u0E4E\u0E50-\u0E59\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB1\u0EB2-\u0EB3\u0EB4-\u0EB9\u0EBB-\u0EBC\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDD\u0F00\u0F18-\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F3F\u0F40-\u0F47\u0F49-\u0F6A\u0F71-\u0F7E\u0F7F\u0F80-\u0F84\u0F86-\u0F87\u0F88-\u0F8B\u0F90-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1021\u1023-\u1027\u1029-\u102A\u102C\u102D-\u1030\u1031\u1032\u1036-\u1037\u1038\u1039\u1040-\u1049\u1050-\u1055\u1056-\u1057\u1058-\u1059\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1159\u115F-\u11A2\u11A8-\u11F9\u1200-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135F\u1369-\u1371\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u1676\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-\u1711\u1712-\u1714\u1720-\u1731\u1732-\u1734\u1740-\u1751\u1752-\u1753\u1760-\u176C\u176E-\u1770\u1772-\u1773\u1780-\u17B3\u17B6\u17B7-\u17BD\u17BE-\u17C5\u17C6\u17C7-\u17C8\u17C9-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1842\u1843\u1844-\u1877\u1880-\u18A8\u18A9\u1900-\u191C\u1920-\u1922\u1923-\u1926\u1927-\u1928\u1929-\u192B\u1930-\u1931\u1932\u1933-\u1938\u1939-\u193B\u1946-\u194F\u1950-\u196D\u1970-\u1974\u1980-\u19A9\u19B0-\u19C0\u19C1-\u19C7\u19C8-\u19C9\u19D0-\u19D9\u1A00-\u1A16\u1A17-\u1A18\u1A19-\u1A1B\u1D00-\u1D2B\u1D2C-\u1D61\u1D62-\u1D77\u1D78\u1D79-\u1D9A\u1D9B-\u1DBF\u1DC0-\u1DC3\u1E00-\u1E9B\u1EA0-\u1EF9\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u203F-\u2040\u2054\u2071\u207F\u2090-\u2094\u20D0-\u20DC\u20E1\u20E5-\u20EB\u2102\u2107\u210A-\u2113\u2115\u2118\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212E\u212F-\u2131\u2133-\u2134\u2135-\u2138\u2139\u213C-\u213F\u2145-\u2149\u2160-\u2183\u2C00-\u2C2E\u2C30-\u2C5E\u2C80-\u2CE4\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005\u3006\u3007\u3021-\u3029\u302A-\u302F\u3031-\u3035\u3038-\u303A\u303B\u303C\u3041-\u3096\u3099-\u309A\u309D-\u309E\u309F\u30A1-\u30FA\u30FC-\u30FE\u30FF\u3105-\u312C\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FBB\uA000-\uA014\uA015\uA016-\uA48C\uA800-\uA801\uA802\uA803-\uA805\uA806\uA807-\uA80A\uA80B\uA80C-\uA822\uA823-\uA824\uA825-\uA826\uA827\uAC00-\uD7A3\uF900-\uFA2D\uFA30-\uFA6A\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1E\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFC5D\uFC64-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDF9\uFE00-\uFE0F\uFE20-\uFE23\uFE33-\uFE34\uFE4D-\uFE4F\uFE71\uFE73\uFE77\uFE79\uFE7B\uFE7D\uFE7F-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFF6F\uFF70\uFF71-\uFF9D\uFF9E-\uFF9F\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC'
-
-def allexcept(*args):
- newcats = cats[:]
- for arg in args:
- newcats.remove(arg)
- return u''.join([globals()[cat] for cat in newcats])
-
-if __name__ == '__main__':
- import unicodedata
-
- categories = {}
-
- f = open(__file__.rstrip('co'))
- try:
- content = f.read()
- finally:
- f.close()
-
- header = content[:content.find('Cc =')]
- footer = content[content.find("def combine("):]
-
- for code in range(65535):
- c = unichr(code)
- cat = unicodedata.category(c)
- categories.setdefault(cat, []).append(c)
-
- f = open(__file__, 'w')
- f.write(header)
-
- for cat in sorted(categories):
- val = u''.join(categories[cat])
- if cat == 'Cs':
- # Jython can't handle isolated surrogates
- f.write("""\
-try:
- Cs = eval(r"%r")
-except UnicodeDecodeError:
- Cs = '' # Jython can't handle isolated surrogates\n\n""" % val)
- else:
- f.write('%s = %r\n\n' % (cat, val))
- f.write('cats = %r\n\n' % sorted(categories.keys()))
-
- f.write(footer)
- f.close()
diff --git a/third_party/jinja2/asyncfilters.py b/third_party/jinja2/asyncfilters.py
new file mode 100644
index 0000000000..5c1f46d7fa
--- /dev/null
+++ b/third_party/jinja2/asyncfilters.py
@@ -0,0 +1,146 @@
+from functools import wraps
+
+from jinja2.asyncsupport import auto_aiter
+from jinja2 import filters
+
+
+async def auto_to_seq(value):
+ seq = []
+ if hasattr(value, '__aiter__'):
+ async for item in value:
+ seq.append(item)
+ else:
+ for item in value:
+ seq.append(item)
+ return seq
+
+
+async def async_select_or_reject(args, kwargs, modfunc, lookup_attr):
+ seq, func = filters.prepare_select_or_reject(
+ args, kwargs, modfunc, lookup_attr)
+ if seq:
+ async for item in auto_aiter(seq):
+ if func(item):
+ yield item
+
+
+def dualfilter(normal_filter, async_filter):
+ wrap_evalctx = False
+ if getattr(normal_filter, 'environmentfilter', False):
+ is_async = lambda args: args[0].is_async
+ wrap_evalctx = False
+ else:
+ if not getattr(normal_filter, 'evalcontextfilter', False) and \
+ not getattr(normal_filter, 'contextfilter', False):
+ wrap_evalctx = True
+ is_async = lambda args: args[0].environment.is_async
+
+ @wraps(normal_filter)
+ def wrapper(*args, **kwargs):
+ b = is_async(args)
+ if wrap_evalctx:
+ args = args[1:]
+ if b:
+ return async_filter(*args, **kwargs)
+ return normal_filter(*args, **kwargs)
+
+ if wrap_evalctx:
+ wrapper.evalcontextfilter = True
+
+ wrapper.asyncfiltervariant = True
+
+ return wrapper
+
+
+def asyncfiltervariant(original):
+ def decorator(f):
+ return dualfilter(original, f)
+ return decorator
+
+
+@asyncfiltervariant(filters.do_first)
+async def do_first(environment, seq):
+ try:
+ return await auto_aiter(seq).__anext__()
+ except StopAsyncIteration:
+ return environment.undefined('No first item, sequence was empty.')
+
+
+@asyncfiltervariant(filters.do_groupby)
+async def do_groupby(environment, value, attribute):
+ expr = filters.make_attrgetter(environment, attribute)
+ return [filters._GroupTuple(key, await auto_to_seq(values))
+ for key, values in filters.groupby(sorted(
+ await auto_to_seq(value), key=expr), expr)]
+
+
+@asyncfiltervariant(filters.do_join)
+async def do_join(eval_ctx, value, d=u'', attribute=None):
+ return filters.do_join(eval_ctx, await auto_to_seq(value), d, attribute)
+
+
+@asyncfiltervariant(filters.do_list)
+async def do_list(value):
+ return await auto_to_seq(value)
+
+
+@asyncfiltervariant(filters.do_reject)
+async def do_reject(*args, **kwargs):
+ return async_select_or_reject(args, kwargs, lambda x: not x, False)
+
+
+@asyncfiltervariant(filters.do_rejectattr)
+async def do_rejectattr(*args, **kwargs):
+ return async_select_or_reject(args, kwargs, lambda x: not x, True)
+
+
+@asyncfiltervariant(filters.do_select)
+async def do_select(*args, **kwargs):
+ return async_select_or_reject(args, kwargs, lambda x: x, False)
+
+
+@asyncfiltervariant(filters.do_selectattr)
+async def do_selectattr(*args, **kwargs):
+ return async_select_or_reject(args, kwargs, lambda x: x, True)
+
+
+@asyncfiltervariant(filters.do_map)
+async def do_map(*args, **kwargs):
+ seq, func = filters.prepare_map(args, kwargs)
+ if seq:
+ async for item in auto_aiter(seq):
+ yield func(item)
+
+
+@asyncfiltervariant(filters.do_sum)
+async def do_sum(environment, iterable, attribute=None, start=0):
+ rv = start
+ if attribute is not None:
+ func = filters.make_attrgetter(environment, attribute)
+ else:
+ func = lambda x: x
+ async for item in auto_aiter(iterable):
+ rv += func(item)
+ return rv
+
+
+@asyncfiltervariant(filters.do_slice)
+async def do_slice(value, slices, fill_with=None):
+ return filters.do_slice(await auto_to_seq(value), slices, fill_with)
+
+
+ASYNC_FILTERS = {
+ 'first': do_first,
+ 'groupby': do_groupby,
+ 'join': do_join,
+ 'list': do_list,
+ # we intentionally do not support do_last because that would be
+ # ridiculous
+ 'reject': do_reject,
+ 'rejectattr': do_rejectattr,
+ 'map': do_map,
+ 'select': do_select,
+ 'selectattr': do_selectattr,
+ 'sum': do_sum,
+ 'slice': do_slice,
+}
diff --git a/third_party/jinja2/asyncsupport.py b/third_party/jinja2/asyncsupport.py
new file mode 100644
index 0000000000..b1e7b5ce9a
--- /dev/null
+++ b/third_party/jinja2/asyncsupport.py
@@ -0,0 +1,256 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.asyncsupport
+ ~~~~~~~~~~~~~~~~~~~
+
+ Has all the code for async support which is implemented as a patch
+ for supported Python versions.
+
+ :copyright: (c) 2017 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import sys
+import asyncio
+import inspect
+from functools import update_wrapper
+
+from jinja2.utils import concat, internalcode, Markup
+from jinja2.environment import TemplateModule
+from jinja2.runtime import LoopContextBase, _last_iteration
+
+
+async def concat_async(async_gen):
+ rv = []
+ async def collect():
+ async for event in async_gen:
+ rv.append(event)
+ await collect()
+ return concat(rv)
+
+
+async def generate_async(self, *args, **kwargs):
+ vars = dict(*args, **kwargs)
+ try:
+ async for event in self.root_render_func(self.new_context(vars)):
+ yield event
+ except Exception:
+ exc_info = sys.exc_info()
+ else:
+ return
+ yield self.environment.handle_exception(exc_info, True)
+
+
+def wrap_generate_func(original_generate):
+ def _convert_generator(self, loop, args, kwargs):
+ async_gen = self.generate_async(*args, **kwargs)
+ try:
+ while 1:
+ yield loop.run_until_complete(async_gen.__anext__())
+ except StopAsyncIteration:
+ pass
+ def generate(self, *args, **kwargs):
+ if not self.environment.is_async:
+ return original_generate(self, *args, **kwargs)
+ return _convert_generator(self, asyncio.get_event_loop(), args, kwargs)
+ return update_wrapper(generate, original_generate)
+
+
+async def render_async(self, *args, **kwargs):
+ if not self.environment.is_async:
+ raise RuntimeError('The environment was not created with async mode '
+ 'enabled.')
+
+ vars = dict(*args, **kwargs)
+ ctx = self.new_context(vars)
+
+ try:
+ return await concat_async(self.root_render_func(ctx))
+ except Exception:
+ exc_info = sys.exc_info()
+ return self.environment.handle_exception(exc_info, True)
+
+
+def wrap_render_func(original_render):
+ def render(self, *args, **kwargs):
+ if not self.environment.is_async:
+ return original_render(self, *args, **kwargs)
+ loop = asyncio.get_event_loop()
+ return loop.run_until_complete(self.render_async(*args, **kwargs))
+ return update_wrapper(render, original_render)
+
+
+def wrap_block_reference_call(original_call):
+ @internalcode
+ async def async_call(self):
+ rv = await concat_async(self._stack[self._depth](self._context))
+ if self._context.eval_ctx.autoescape:
+ rv = Markup(rv)
+ return rv
+
+ @internalcode
+ def __call__(self):
+ if not self._context.environment.is_async:
+ return original_call(self)
+ return async_call(self)
+
+ return update_wrapper(__call__, original_call)
+
+
+def wrap_macro_invoke(original_invoke):
+ @internalcode
+ async def async_invoke(self, arguments, autoescape):
+ rv = await self._func(*arguments)
+ if autoescape:
+ rv = Markup(rv)
+ return rv
+
+ @internalcode
+ def _invoke(self, arguments, autoescape):
+ if not self._environment.is_async:
+ return original_invoke(self, arguments, autoescape)
+ return async_invoke(self, arguments, autoescape)
+ return update_wrapper(_invoke, original_invoke)
+
+
+@internalcode
+async def get_default_module_async(self):
+ if self._module is not None:
+ return self._module
+ self._module = rv = await self.make_module_async()
+ return rv
+
+
+def wrap_default_module(original_default_module):
+ @internalcode
+ def _get_default_module(self):
+ if self.environment.is_async:
+ raise RuntimeError('Template module attribute is unavailable '
+ 'in async mode')
+ return original_default_module(self)
+ return _get_default_module
+
+
+async def make_module_async(self, vars=None, shared=False, locals=None):
+ context = self.new_context(vars, shared, locals)
+ body_stream = []
+ async for item in self.root_render_func(context):
+ body_stream.append(item)
+ return TemplateModule(self, context, body_stream)
+
+
+def patch_template():
+ from jinja2 import Template
+ Template.generate = wrap_generate_func(Template.generate)
+ Template.generate_async = update_wrapper(
+ generate_async, Template.generate_async)
+ Template.render_async = update_wrapper(
+ render_async, Template.render_async)
+ Template.render = wrap_render_func(Template.render)
+ Template._get_default_module = wrap_default_module(
+ Template._get_default_module)
+ Template._get_default_module_async = get_default_module_async
+ Template.make_module_async = update_wrapper(
+ make_module_async, Template.make_module_async)
+
+
+def patch_runtime():
+ from jinja2.runtime import BlockReference, Macro
+ BlockReference.__call__ = wrap_block_reference_call(
+ BlockReference.__call__)
+ Macro._invoke = wrap_macro_invoke(Macro._invoke)
+
+
+def patch_filters():
+ from jinja2.filters import FILTERS
+ from jinja2.asyncfilters import ASYNC_FILTERS
+ FILTERS.update(ASYNC_FILTERS)
+
+
+def patch_all():
+ patch_template()
+ patch_runtime()
+ patch_filters()
+
+
+async def auto_await(value):
+ if inspect.isawaitable(value):
+ return await value
+ return value
+
+
+async def auto_aiter(iterable):
+ if hasattr(iterable, '__aiter__'):
+ async for item in iterable:
+ yield item
+ return
+ for item in iterable:
+ yield item
+
+
+class AsyncLoopContext(LoopContextBase):
+
+ def __init__(self, async_iterator, undefined, after, length, recurse=None,
+ depth0=0):
+ LoopContextBase.__init__(self, undefined, recurse, depth0)
+ self._async_iterator = async_iterator
+ self._after = after
+ self._length = length
+
+ @property
+ def length(self):
+ if self._length is None:
+ raise TypeError('Loop length for some iterators cannot be '
+ 'lazily calculated in async mode')
+ return self._length
+
+ def __aiter__(self):
+ return AsyncLoopContextIterator(self)
+
+
+class AsyncLoopContextIterator(object):
+ __slots__ = ('context',)
+
+ def __init__(self, context):
+ self.context = context
+
+ def __aiter__(self):
+ return self
+
+ async def __anext__(self):
+ ctx = self.context
+ ctx.index0 += 1
+ if ctx._after is _last_iteration:
+ raise StopAsyncIteration()
+ ctx._before = ctx._current
+ ctx._current = ctx._after
+ try:
+ ctx._after = await ctx._async_iterator.__anext__()
+ except StopAsyncIteration:
+ ctx._after = _last_iteration
+ return ctx._current, ctx
+
+
+async def make_async_loop_context(iterable, undefined, recurse=None, depth0=0):
+ # Length is more complicated and less efficient in async mode. The
+ # reason for this is that we cannot know if length will be used
+ # upfront but because length is a property we cannot lazily execute it
+ # later. This means that we need to buffer it up and measure :(
+ #
+ # We however only do this for actual iterators, not for async
+ # iterators as blocking here does not seem like the best idea in the
+ # world.
+ try:
+ length = len(iterable)
+ except (TypeError, AttributeError):
+ if not hasattr(iterable, '__aiter__'):
+ iterable = tuple(iterable)
+ length = len(iterable)
+ else:
+ length = None
+ async_iterator = auto_aiter(iterable)
+ try:
+ after = await async_iterator.__anext__()
+ except StopAsyncIteration:
+ after = _last_iteration
+ return AsyncLoopContext(async_iterator, undefined, after, length, recurse,
+ depth0)
diff --git a/third_party/jinja2/bccache.py b/third_party/jinja2/bccache.py
index f5bd3145f6..080e527cab 100644
--- a/third_party/jinja2/bccache.py
+++ b/third_party/jinja2/bccache.py
@@ -11,7 +11,7 @@
Situations where this is useful are often forking web applications that
are initialized on the first request.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD.
"""
from os import path, listdir
@@ -45,7 +45,7 @@ else:
return marshal.loads(f.read())
-bc_version = 2
+bc_version = 3
# magic version used to only change with new jinja versions. With 2.6
# we change this to also take Python version changes into account. The
@@ -297,7 +297,7 @@ class MemcachedBytecodeCache(BytecodeCache):
Libraries compatible with this class:
- `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache
- - `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_
+ - `python-memcached <https://www.tummy.com/Community/software/python-memcached/>`_
- `cmemcache <http://gijsbert.org/cmemcache/>`_
(Unfortunately the django cache interface is not compatible because it
diff --git a/third_party/jinja2/compiler.py b/third_party/jinja2/compiler.py
index fad007b596..d534a82739 100644
--- a/third_party/jinja2/compiler.py
+++ b/third_party/jinja2/compiler.py
@@ -5,19 +5,23 @@
Compiles nodes into python code.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
from itertools import chain
from copy import deepcopy
from keyword import iskeyword as is_python_keyword
+from functools import update_wrapper
from jinja2 import nodes
from jinja2.nodes import EvalContext
from jinja2.visitor import NodeVisitor
+from jinja2.optimizer import Optimizer
from jinja2.exceptions import TemplateAssertionError
from jinja2.utils import Markup, concat, escape
from jinja2._compat import range_type, text_type, string_types, \
- iteritems, NativeStringIO, imap
+ iteritems, NativeStringIO, imap, izip
+from jinja2.idtracking import Symbols, VAR_LOAD_PARAMETER, \
+ VAR_LOAD_RESOLVE, VAR_LOAD_ALIAS, VAR_LOAD_UNDEFINED
operators = {
@@ -38,27 +42,43 @@ if hasattr(dict, 'iteritems'):
else:
dict_item_iter = 'items'
+code_features = ['division']
-# does if 0: dummy(x) get us x into the scope?
-def unoptimize_before_dead_code():
- x = 42
- def f():
- if 0: dummy(x)
- return f
+# does this python version support generator stops? (PEP 0479)
+try:
+ exec('from __future__ import generator_stop')
+ code_features.append('generator_stop')
+except SyntaxError:
+ pass
+
+# does this python version support yield from?
+try:
+ exec('def f(): yield from x()')
+except SyntaxError:
+ supports_yield_from = False
+else:
+ supports_yield_from = True
-# The getattr is necessary for pypy which does not set this attribute if
-# no closure is on the function
-unoptimize_before_dead_code = bool(
- getattr(unoptimize_before_dead_code(), '__closure__', None))
+
+def optimizeconst(f):
+ def new_func(self, node, frame, **kwargs):
+ # Only optimize if the frame is not volatile
+ if self.optimized and not frame.eval_ctx.volatile:
+ new_node = self.optimizer.visit(node, frame.eval_ctx)
+ if new_node != node:
+ return self.visit(new_node, frame)
+ return f(self, node, frame, **kwargs)
+ return update_wrapper(new_func, f)
def generate(node, environment, name, filename, stream=None,
- defer_init=False):
+ defer_init=False, optimized=True):
"""Generate the python source for a node tree."""
if not isinstance(node, nodes.Template):
raise TypeError('Can\'t compile non template nodes')
generator = environment.code_generator_class(environment, name, filename,
- stream, defer_init)
+ stream, defer_init,
+ optimized)
generator.visit(node)
if stream is None:
return generator.stream.getvalue()
@@ -68,15 +88,14 @@ def has_safe_repr(value):
"""Does the node have a safe representation?"""
if value is None or value is NotImplemented or value is Ellipsis:
return True
- if isinstance(value, (bool, int, float, complex, range_type,
- Markup) + string_types):
+ if type(value) in (bool, int, float, complex, range_type, Markup) + string_types:
return True
- if isinstance(value, (tuple, list, set, frozenset)):
+ if type(value) in (tuple, list, set, frozenset):
for item in value:
if not has_safe_repr(item):
return False
return True
- elif isinstance(value, dict):
+ elif type(value) is dict:
for key, value in iteritems(value):
if not has_safe_repr(key):
return False
@@ -99,49 +118,22 @@ def find_undeclared(nodes, names):
return visitor.undeclared
-class Identifiers(object):
- """Tracks the status of identifiers in frames."""
-
- def __init__(self):
- # variables that are known to be declared (probably from outer
- # frames or because they are special for the frame)
- self.declared = set()
-
- # undeclared variables from outer scopes
- self.outer_undeclared = set()
-
- # names that are accessed without being explicitly declared by
- # this one or any of the outer scopes. Names can appear both in
- # declared and undeclared.
- self.undeclared = set()
-
- # names that are declared locally
- self.declared_locally = set()
+class MacroRef(object):
- # names that are declared by parameters
- self.declared_parameter = set()
-
- def add_special(self, name):
- """Register a special name like `loop`."""
- self.undeclared.discard(name)
- self.declared.add(name)
-
- def is_declared(self, name):
- """Check if a name is declared in this or an outer scope."""
- if name in self.declared_locally or name in self.declared_parameter:
- return True
- return name in self.declared
-
- def copy(self):
- return deepcopy(self)
+ def __init__(self, node):
+ self.node = node
+ self.accesses_caller = False
+ self.accesses_kwargs = False
+ self.accesses_varargs = False
class Frame(object):
"""Holds compile time information for us."""
- def __init__(self, eval_ctx, parent=None):
+ def __init__(self, eval_ctx, parent=None, level=None):
self.eval_ctx = eval_ctx
- self.identifiers = Identifiers()
+ self.symbols = Symbols(parent and parent.symbols or None,
+ level=level)
# a toplevel frame is the root + soft frames such as if conditions.
self.toplevel = False
@@ -164,58 +156,31 @@ class Frame(object):
# the name of the block we're in, otherwise None.
self.block = parent and parent.block or None
- # a set of actually assigned names
- self.assigned_names = set()
-
# the parent of this frame
self.parent = parent
if parent is not None:
- self.identifiers.declared.update(
- parent.identifiers.declared |
- parent.identifiers.declared_parameter |
- parent.assigned_names
- )
- self.identifiers.outer_undeclared.update(
- parent.identifiers.undeclared -
- self.identifiers.declared
- )
self.buffer = parent.buffer
def copy(self):
"""Create a copy of the current one."""
rv = object.__new__(self.__class__)
rv.__dict__.update(self.__dict__)
- rv.identifiers = object.__new__(self.identifiers.__class__)
- rv.identifiers.__dict__.update(self.identifiers.__dict__)
+ rv.symbols = self.symbols.copy()
return rv
- def inspect(self, nodes):
- """Walk the node and check for identifiers. If the scope is hard (eg:
- enforce on a python level) overrides from outer scopes are tracked
- differently.
- """
- visitor = FrameIdentifierVisitor(self.identifiers)
- for node in nodes:
- visitor.visit(node)
-
- def find_shadowed(self, extra=()):
- """Find all the shadowed names. extra is an iterable of variables
- that may be defined with `add_special` which may occour scoped.
- """
- i = self.identifiers
- return (i.declared | i.outer_undeclared) & \
- (i.declared_locally | i.declared_parameter) | \
- set(x for x in extra if i.is_declared(x))
-
- def inner(self):
+ def inner(self, isolated=False):
"""Return an inner frame."""
+ if isolated:
+ return Frame(self.eval_ctx, level=self.symbols.level + 1)
return Frame(self.eval_ctx, self)
def soft(self):
"""Return a soft frame. A soft frame may not be modified as
standalone thing as it shares the resources with the frame it
was created of, but it's not a rootlevel frame any longer.
+
+ This is only used to implement if-statements.
"""
rv = self.copy()
rv.rootlevel = False
@@ -269,95 +234,6 @@ class UndeclaredNameVisitor(NodeVisitor):
"""Stop visiting a blocks."""
-class FrameIdentifierVisitor(NodeVisitor):
- """A visitor for `Frame.inspect`."""
-
- def __init__(self, identifiers):
- self.identifiers = identifiers
-
- def visit_Name(self, node):
- """All assignments to names go through this function."""
- if node.ctx == 'store':
- self.identifiers.declared_locally.add(node.name)
- elif node.ctx == 'param':
- self.identifiers.declared_parameter.add(node.name)
- elif node.ctx == 'load' and not \
- self.identifiers.is_declared(node.name):
- self.identifiers.undeclared.add(node.name)
-
- def visit_If(self, node):
- self.visit(node.test)
- real_identifiers = self.identifiers
-
- old_names = real_identifiers.declared_locally | \
- real_identifiers.declared_parameter
-
- def inner_visit(nodes):
- if not nodes:
- return set()
- self.identifiers = real_identifiers.copy()
- for subnode in nodes:
- self.visit(subnode)
- rv = self.identifiers.declared_locally - old_names
- # we have to remember the undeclared variables of this branch
- # because we will have to pull them.
- real_identifiers.undeclared.update(self.identifiers.undeclared)
- self.identifiers = real_identifiers
- return rv
-
- body = inner_visit(node.body)
- else_ = inner_visit(node.else_ or ())
-
- # the differences between the two branches are also pulled as
- # undeclared variables
- real_identifiers.undeclared.update(body.symmetric_difference(else_) -
- real_identifiers.declared)
-
- # remember those that are declared.
- real_identifiers.declared_locally.update(body | else_)
-
- def visit_Macro(self, node):
- self.identifiers.declared_locally.add(node.name)
-
- def visit_Import(self, node):
- self.generic_visit(node)
- self.identifiers.declared_locally.add(node.target)
-
- def visit_FromImport(self, node):
- self.generic_visit(node)
- for name in node.names:
- if isinstance(name, tuple):
- self.identifiers.declared_locally.add(name[1])
- else:
- self.identifiers.declared_locally.add(name)
-
- def visit_Assign(self, node):
- """Visit assignments in the correct order."""
- self.visit(node.node)
- self.visit(node.target)
-
- def visit_For(self, node):
- """Visiting stops at for blocks. However the block sequence
- is visited as part of the outer scope.
- """
- self.visit(node.iter)
-
- def visit_CallBlock(self, node):
- self.visit(node.call)
-
- def visit_FilterBlock(self, node):
- self.visit(node.filter)
-
- def visit_AssignBlock(self, node):
- """Stop visiting at block assigns."""
-
- def visit_Scope(self, node):
- """Stop visiting at scopes."""
-
- def visit_Block(self, node):
- """Stop visiting at blocks."""
-
-
class CompilerExit(Exception):
"""Raised if the compiler encountered a situation where it just
doesn't make sense to further process the code. Any block that
@@ -368,7 +244,7 @@ class CompilerExit(Exception):
class CodeGenerator(NodeVisitor):
def __init__(self, environment, name, filename, stream=None,
- defer_init=False):
+ defer_init=False, optimized=True):
if stream is None:
stream = NativeStringIO()
self.environment = environment
@@ -377,6 +253,9 @@ class CodeGenerator(NodeVisitor):
self.stream = stream
self.created_block_context = False
self.defer_init = defer_init
+ self.optimized = optimized
+ if optimized:
+ self.optimizer = Optimizer(environment)
# aliases for imports
self.import_aliases = {}
@@ -420,6 +299,15 @@ class CodeGenerator(NodeVisitor):
# the current indentation
self._indentation = 0
+ # Tracks toplevel assignments
+ self._assign_stack = []
+
+ # Tracks parameter definition blocks
+ self._param_def_block = []
+
+ # Tracks the current context.
+ self._context_reference_stack = ['context']
+
# -- Various compilation helpers
def fail(self, msg, lineno):
@@ -436,21 +324,23 @@ class CodeGenerator(NodeVisitor):
frame.buffer = self.temporary_identifier()
self.writeline('%s = []' % frame.buffer)
- def return_buffer_contents(self, frame):
+ def return_buffer_contents(self, frame, force_unescaped=False):
"""Return the buffer contents of the frame."""
- if frame.eval_ctx.volatile:
- self.writeline('if context.eval_ctx.autoescape:')
- self.indent()
- self.writeline('return Markup(concat(%s))' % frame.buffer)
- self.outdent()
- self.writeline('else:')
- self.indent()
- self.writeline('return concat(%s)' % frame.buffer)
- self.outdent()
- elif frame.eval_ctx.autoescape:
- self.writeline('return Markup(concat(%s))' % frame.buffer)
- else:
- self.writeline('return concat(%s)' % frame.buffer)
+ if not force_unescaped:
+ if frame.eval_ctx.volatile:
+ self.writeline('if context.eval_ctx.autoescape:')
+ self.indent()
+ self.writeline('return Markup(concat(%s))' % frame.buffer)
+ self.outdent()
+ self.writeline('else:')
+ self.indent()
+ self.writeline('return concat(%s)' % frame.buffer)
+ self.outdent()
+ return
+ elif frame.eval_ctx.autoescape:
+ self.writeline('return Markup(concat(%s))' % frame.buffer)
+ return
+ self.writeline('return concat(%s)' % frame.buffer)
def indent(self):
"""Indent by one."""
@@ -480,14 +370,10 @@ class CodeGenerator(NodeVisitor):
def blockvisit(self, nodes, frame):
"""Visit a list of nodes as block in a frame. If the current frame
- is no buffer a dummy ``if 0: yield None`` is written automatically
- unless the force_generator parameter is set to False.
+ is no buffer a dummy ``if 0: yield None`` is written automatically.
"""
- if frame.buffer is None:
- self.writeline('if 0: yield None')
- else:
- self.writeline('pass')
try:
+ self.writeline('pass')
for node in nodes:
self.visit(node, frame)
except CompilerExit:
@@ -573,11 +459,6 @@ class CodeGenerator(NodeVisitor):
self.write(', **')
self.visit(node.dyn_kwargs, frame)
- def pull_locals(self, frame):
- """Pull all the references identifiers into the local scope."""
- for name in frame.identifiers.undeclared:
- self.writeline('l_%s = context.resolve(%r)' % (name, name))
-
def pull_dependencies(self, nodes):
"""Pull all the dependencies."""
visitor = DependencyFinderVisitor()
@@ -591,163 +472,123 @@ class CodeGenerator(NodeVisitor):
self.writeline('%s = environment.%s[%r]' %
(mapping[name], dependency, name))
- def unoptimize_scope(self, frame):
- """Disable Python optimizations for the frame."""
- # XXX: this is not that nice but it has no real overhead. It
- # mainly works because python finds the locals before dead code
- # is removed. If that breaks we have to add a dummy function
- # that just accepts the arguments and does nothing.
- if frame.identifiers.declared:
- self.writeline('%sdummy(%s)' % (
- unoptimize_before_dead_code and 'if 0: ' or '',
- ', '.join('l_' + name for name in frame.identifiers.declared)
- ))
-
- def push_scope(self, frame, extra_vars=()):
- """This function returns all the shadowed variables in a dict
- in the form name: alias and will write the required assignments
- into the current scope. No indentation takes place.
-
- This also predefines locally declared variables from the loop
- body because under some circumstances it may be the case that
-
- `extra_vars` is passed to `Frame.find_shadowed`.
- """
- aliases = {}
- for name in frame.find_shadowed(extra_vars):
- aliases[name] = ident = self.temporary_identifier()
- self.writeline('%s = l_%s' % (ident, name))
- to_declare = set()
- for name in frame.identifiers.declared_locally:
- if name not in aliases:
- to_declare.add('l_' + name)
- if to_declare:
- self.writeline(' = '.join(to_declare) + ' = missing')
- return aliases
-
- def pop_scope(self, aliases, frame):
- """Restore all aliases and delete unused variables."""
- for name, alias in iteritems(aliases):
- self.writeline('l_%s = %s' % (name, alias))
- to_delete = set()
- for name in frame.identifiers.declared_locally:
- if name not in aliases:
- to_delete.add('l_' + name)
- if to_delete:
- # we cannot use the del statement here because enclosed
- # scopes can trigger a SyntaxError:
- # a = 42; b = lambda: a; del a
- self.writeline(' = '.join(to_delete) + ' = missing')
-
- def function_scoping(self, node, frame, children=None,
- find_special=True):
- """In Jinja a few statements require the help of anonymous
- functions. Those are currently macros and call blocks and in
- the future also recursive loops. As there is currently
- technical limitation that doesn't allow reading and writing a
- variable in a scope where the initial value is coming from an
- outer scope, this function tries to fall back with a common
- error message. Additionally the frame passed is modified so
- that the argumetns are collected and callers are looked up.
-
- This will return the modified frame.
- """
- # we have to iterate twice over it, make sure that works
- if children is None:
- children = node.iter_child_nodes()
- children = list(children)
- func_frame = frame.inner()
- func_frame.inspect(children)
-
- # variables that are undeclared (accessed before declaration) and
- # declared locally *and* part of an outside scope raise a template
- # assertion error. Reason: we can't generate reasonable code from
- # it without aliasing all the variables.
- # this could be fixed in Python 3 where we have the nonlocal
- # keyword or if we switch to bytecode generation
- overridden_closure_vars = (
- func_frame.identifiers.undeclared &
- func_frame.identifiers.declared &
- (func_frame.identifiers.declared_locally |
- func_frame.identifiers.declared_parameter)
- )
- if overridden_closure_vars:
- self.fail('It\'s not possible to set and access variables '
- 'derived from an outer scope! (affects: %s)' %
- ', '.join(sorted(overridden_closure_vars)), node.lineno)
-
- # remove variables from a closure from the frame's undeclared
- # identifiers.
- func_frame.identifiers.undeclared -= (
- func_frame.identifiers.undeclared &
- func_frame.identifiers.declared
- )
-
- # no special variables for this scope, abort early
- if not find_special:
- return func_frame
-
- func_frame.accesses_kwargs = False
- func_frame.accesses_varargs = False
- func_frame.accesses_caller = False
- func_frame.arguments = args = ['l_' + x.name for x in node.args]
-
- undeclared = find_undeclared(children, ('caller', 'kwargs', 'varargs'))
+ def enter_frame(self, frame):
+ undefs = []
+ for target, (action, param) in iteritems(frame.symbols.loads):
+ if action == VAR_LOAD_PARAMETER:
+ pass
+ elif action == VAR_LOAD_RESOLVE:
+ self.writeline('%s = %s(%r)' %
+ (target, self.get_resolve_func(), param))
+ elif action == VAR_LOAD_ALIAS:
+ self.writeline('%s = %s' % (target, param))
+ elif action == VAR_LOAD_UNDEFINED:
+ undefs.append(target)
+ else:
+ raise NotImplementedError('unknown load instruction')
+ if undefs:
+ self.writeline('%s = missing' % ' = '.join(undefs))
+
+ def leave_frame(self, frame, with_python_scope=False):
+ if not with_python_scope:
+ undefs = []
+ for target, _ in iteritems(frame.symbols.loads):
+ undefs.append(target)
+ if undefs:
+ self.writeline('%s = missing' % ' = '.join(undefs))
+
+ def func(self, name):
+ if self.environment.is_async:
+ return 'async def %s' % name
+ return 'def %s' % name
+
+ def macro_body(self, node, frame):
+ """Dump the function def of a macro or call block."""
+ frame = frame.inner()
+ frame.symbols.analyze_node(node)
+ macro_ref = MacroRef(node)
+
+ explicit_caller = None
+ skip_special_params = set()
+ args = []
+ for idx, arg in enumerate(node.args):
+ if arg.name == 'caller':
+ explicit_caller = idx
+ if arg.name in ('kwargs', 'varargs'):
+ skip_special_params.add(arg.name)
+ args.append(frame.symbols.ref(arg.name))
+
+ undeclared = find_undeclared(node.body, ('caller', 'kwargs', 'varargs'))
if 'caller' in undeclared:
- func_frame.accesses_caller = True
- func_frame.identifiers.add_special('caller')
- args.append('l_caller')
- if 'kwargs' in undeclared:
- func_frame.accesses_kwargs = True
- func_frame.identifiers.add_special('kwargs')
- args.append('l_kwargs')
- if 'varargs' in undeclared:
- func_frame.accesses_varargs = True
- func_frame.identifiers.add_special('varargs')
- args.append('l_varargs')
- return func_frame
-
- def macro_body(self, node, frame, children=None):
- """Dump the function def of a macro or call block."""
- frame = self.function_scoping(node, frame, children)
+ # In older Jinja2 versions there was a bug that allowed caller
+ # to retain the special behavior even if it was mentioned in
+ # the argument list. However thankfully this was only really
+ # working if it was the last argument. So we are explicitly
+ # checking this now and error out if it is anywhere else in
+ # the argument list.
+ if explicit_caller is not None:
+ try:
+ node.defaults[explicit_caller - len(node.args)]
+ except IndexError:
+ self.fail('When defining macros or call blocks the '
+ 'special "caller" argument must be omitted '
+ 'or be given a default.', node.lineno)
+ else:
+ args.append(frame.symbols.declare_parameter('caller'))
+ macro_ref.accesses_caller = True
+ if 'kwargs' in undeclared and not 'kwargs' in skip_special_params:
+ args.append(frame.symbols.declare_parameter('kwargs'))
+ macro_ref.accesses_kwargs = True
+ if 'varargs' in undeclared and not 'varargs' in skip_special_params:
+ args.append(frame.symbols.declare_parameter('varargs'))
+ macro_ref.accesses_varargs = True
+
# macros are delayed, they never require output checks
frame.require_output_check = False
- args = frame.arguments
- # XXX: this is an ugly fix for the loop nesting bug
- # (tests.test_old_bugs.test_loop_call_bug). This works around
- # a identifier nesting problem we have in general. It's just more
- # likely to happen in loops which is why we work around it. The
- # real solution would be "nonlocal" all the identifiers that are
- # leaking into a new python frame and might be used both unassigned
- # and assigned.
- if 'loop' in frame.identifiers.declared:
- args = args + ['l_loop=l_loop']
- self.writeline('def macro(%s):' % ', '.join(args), node)
+ frame.symbols.analyze_node(node)
+ self.writeline('%s(%s):' % (self.func('macro'), ', '.join(args)), node)
self.indent()
+
self.buffer(frame)
- self.pull_locals(frame)
+ self.enter_frame(frame)
+
+ self.push_parameter_definitions(frame)
+ for idx, arg in enumerate(node.args):
+ ref = frame.symbols.ref(arg.name)
+ self.writeline('if %s is missing:' % ref)
+ self.indent()
+ try:
+ default = node.defaults[idx - len(node.args)]
+ except IndexError:
+ self.writeline('%s = undefined(%r, name=%r)' % (
+ ref,
+ 'parameter %r was not provided' % arg.name,
+ arg.name))
+ else:
+ self.writeline('%s = ' % ref)
+ self.visit(default, frame)
+ self.mark_parameter_stored(ref)
+ self.outdent()
+ self.pop_parameter_definitions()
+
self.blockvisit(node.body, frame)
- self.return_buffer_contents(frame)
+ self.return_buffer_contents(frame, force_unescaped=True)
+ self.leave_frame(frame, with_python_scope=True)
self.outdent()
- return frame
- def macro_def(self, node, frame):
+ return frame, macro_ref
+
+ def macro_def(self, macro_ref, frame):
"""Dump the macro definition for the def created by macro_body."""
- arg_tuple = ', '.join(repr(x.name) for x in node.args)
- name = getattr(node, 'name', None)
- if len(node.args) == 1:
+ arg_tuple = ', '.join(repr(x.name) for x in macro_ref.node.args)
+ name = getattr(macro_ref.node, 'name', None)
+ if len(macro_ref.node.args) == 1:
arg_tuple += ','
- self.write('Macro(environment, macro, %r, (%s), (' %
- (name, arg_tuple))
- for arg in node.defaults:
- self.visit(arg, frame)
- self.write(', ')
- self.write('), %r, %r, %r)' % (
- bool(frame.accesses_kwargs),
- bool(frame.accesses_varargs),
- bool(frame.accesses_caller)
- ))
+ self.write('Macro(environment, macro, %r, (%s), %r, %r, %r, '
+ 'context.eval_ctx.autoescape)' %
+ (name, arg_tuple, macro_ref.accesses_kwargs,
+ macro_ref.accesses_varargs, macro_ref.accesses_caller))
def position(self, node):
"""Return a human readable position for the node."""
@@ -756,6 +597,99 @@ class CodeGenerator(NodeVisitor):
rv += ' in ' + repr(self.name)
return rv
+ def dump_local_context(self, frame):
+ return '{%s}' % ', '.join(
+ '%r: %s' % (name, target) for name, target
+ in iteritems(frame.symbols.dump_stores()))
+
+ def write_commons(self):
+ """Writes a common preamble that is used by root and block functions.
+ Primarily this sets up common local helpers and enforces a generator
+ through a dead branch.
+ """
+ self.writeline('resolve = context.resolve_or_missing')
+ self.writeline('undefined = environment.undefined')
+ self.writeline('if 0: yield None')
+
+ def push_parameter_definitions(self, frame):
+ """Pushes all parameter targets from the given frame into a local
+ stack that permits tracking of yet to be assigned parameters. In
+ particular this enables the optimization from `visit_Name` to skip
+ undefined expressions for parameters in macros as macros can reference
+ otherwise unbound parameters.
+ """
+ self._param_def_block.append(frame.symbols.dump_param_targets())
+
+ def pop_parameter_definitions(self):
+ """Pops the current parameter definitions set."""
+ self._param_def_block.pop()
+
+ def mark_parameter_stored(self, target):
+ """Marks a parameter in the current parameter definitions as stored.
+ This will skip the enforced undefined checks.
+ """
+ if self._param_def_block:
+ self._param_def_block[-1].discard(target)
+
+ def push_context_reference(self, target):
+ self._context_reference_stack.append(target)
+
+ def pop_context_reference(self):
+ self._context_reference_stack.pop()
+
+ def get_context_ref(self):
+ return self._context_reference_stack[-1]
+
+ def get_resolve_func(self):
+ target = self._context_reference_stack[-1]
+ if target == 'context':
+ return 'resolve'
+ return '%s.resolve' % target
+
+ def derive_context(self, frame):
+ return '%s.derived(%s)' % (
+ self.get_context_ref(),
+ self.dump_local_context(frame),
+ )
+
+ def parameter_is_undeclared(self, target):
+ """Checks if a given target is an undeclared parameter."""
+ if not self._param_def_block:
+ return False
+ return target in self._param_def_block[-1]
+
+ def push_assign_tracking(self):
+ """Pushes a new layer for assignment tracking."""
+ self._assign_stack.append(set())
+
+ def pop_assign_tracking(self, frame):
+ """Pops the topmost level for assignment tracking and updates the
+ context variables if necessary.
+ """
+ vars = self._assign_stack.pop()
+ if not frame.toplevel or not vars:
+ return
+ public_names = [x for x in vars if x[:1] != '_']
+ if len(vars) == 1:
+ name = next(iter(vars))
+ ref = frame.symbols.ref(name)
+ self.writeline('context.vars[%r] = %s' % (name, ref))
+ else:
+ self.writeline('context.vars.update({')
+ for idx, name in enumerate(vars):
+ if idx:
+ self.write(', ')
+ ref = frame.symbols.ref(name)
+ self.write('%r: %s' % (name, ref))
+ self.write('})')
+ if public_names:
+ if len(public_names) == 1:
+ self.writeline('context.exported_vars.add(%r)' %
+ public_names[0])
+ else:
+ self.writeline('context.exported_vars.update((%s))' %
+ ', '.join(imap(repr, public_names)))
+
# -- Statement Visitors
def visit_Template(self, node, frame=None):
@@ -763,10 +697,12 @@ class CodeGenerator(NodeVisitor):
eval_ctx = EvalContext(self.environment, self.name)
from jinja2.runtime import __all__ as exported
- self.writeline('from __future__ import division')
+ self.writeline('from __future__ import %s' % ', '.join(code_features))
self.writeline('from jinja2.runtime import ' + ', '.join(exported))
- if not unoptimize_before_dead_code:
- self.writeline('dummy = lambda *x: None')
+
+ if self.environment.is_async:
+ self.writeline('from jinja2.asyncsupport import auto_await, '
+ 'auto_aiter, make_async_loop_context')
# if we want a deferred initialization we cannot move the
# environment into a local name
@@ -798,22 +734,25 @@ class CodeGenerator(NodeVisitor):
self.writeline('name = %r' % self.name)
# generate the root render function.
- self.writeline('def root(context%s):' % envenv, extra=1)
+ self.writeline('%s(context, missing=missing%s):' %
+ (self.func('root'), envenv), extra=1)
+ self.indent()
+ self.write_commons()
# process the root
frame = Frame(eval_ctx)
- frame.inspect(node.body)
+ if 'self' in find_undeclared(node.body, ('self',)):
+ ref = frame.symbols.declare_parameter('self')
+ self.writeline('%s = TemplateReference(context)' % ref)
+ frame.symbols.analyze_node(node)
frame.toplevel = frame.rootlevel = True
frame.require_output_check = have_extends and not self.has_known_extends
- self.indent()
if have_extends:
self.writeline('parent_template = None')
- if 'self' in find_undeclared(node.body, ('self',)):
- frame.identifiers.add_special('self')
- self.writeline('l_self = TemplateReference(context)')
- self.pull_locals(frame)
+ self.enter_frame(frame)
self.pull_dependencies(node.body)
self.blockvisit(node.body, frame)
+ self.leave_frame(frame, with_python_scope=True)
self.outdent()
# make sure that the parent root is called.
@@ -822,31 +761,43 @@ class CodeGenerator(NodeVisitor):
self.indent()
self.writeline('if parent_template is not None:')
self.indent()
- self.writeline('for event in parent_template.'
- 'root_render_func(context):')
- self.indent()
- self.writeline('yield event')
- self.outdent(2 + (not self.has_known_extends))
+ if supports_yield_from and not self.environment.is_async:
+ self.writeline('yield from parent_template.'
+ 'root_render_func(context)')
+ else:
+ self.writeline('%sfor event in parent_template.'
+ 'root_render_func(context):' %
+ (self.environment.is_async and 'async ' or ''))
+ self.indent()
+ self.writeline('yield event')
+ self.outdent()
+ self.outdent(1 + (not self.has_known_extends))
# at this point we now have the blocks collected and can visit them too.
for name, block in iteritems(self.blocks):
- block_frame = Frame(eval_ctx)
- block_frame.inspect(block.body)
- block_frame.block = name
- self.writeline('def block_%s(context%s):' % (name, envenv),
+ self.writeline('%s(context, missing=missing%s):' %
+ (self.func('block_' + name), envenv),
block, 1)
self.indent()
+ self.write_commons()
+ # It's important that we do not make this frame a child of the
+ # toplevel template. This would cause a variety of
+ # interesting issues with identifier tracking.
+ block_frame = Frame(eval_ctx)
undeclared = find_undeclared(block.body, ('self', 'super'))
if 'self' in undeclared:
- block_frame.identifiers.add_special('self')
- self.writeline('l_self = TemplateReference(context)')
+ ref = block_frame.symbols.declare_parameter('self')
+ self.writeline('%s = TemplateReference(context)' % ref)
if 'super' in undeclared:
- block_frame.identifiers.add_special('super')
- self.writeline('l_super = context.super(%r, '
- 'block_%s)' % (name, name))
- self.pull_locals(block_frame)
+ ref = block_frame.symbols.declare_parameter('super')
+ self.writeline('%s = context.super(%r, '
+ 'block_%s)' % (ref, name, name))
+ block_frame.symbols.analyze_node(block)
+ block_frame.block = name
+ self.enter_frame(block_frame)
self.pull_dependencies(block.body)
self.blockvisit(block.body, block_frame)
+ self.leave_frame(block_frame, with_python_scope=True)
self.outdent()
self.writeline('blocks = {%s}' % ', '.join('%r: block_%s' % (x, x)
@@ -859,7 +810,7 @@ class CodeGenerator(NodeVisitor):
def visit_Block(self, node, frame):
"""Call a block and register it for the template."""
- level = 1
+ level = 0
if frame.toplevel:
# if we know that we are a child template, there is no need to
# check if we are one
@@ -869,11 +820,24 @@ class CodeGenerator(NodeVisitor):
self.writeline('if parent_template is None:')
self.indent()
level += 1
- context = node.scoped and 'context.derived(locals())' or 'context'
- self.writeline('for event in context.blocks[%r][0](%s):' % (
- node.name, context), node)
- self.indent()
- self.simple_write('event', frame)
+
+ if node.scoped:
+ context = self.derive_context(frame)
+ else:
+ context = self.get_context_ref()
+
+ if supports_yield_from and not self.environment.is_async and \
+ frame.buffer is None:
+ self.writeline('yield from context.blocks[%r][0](%s)' % (
+ node.name, context), node)
+ else:
+ loop = self.environment.is_async and 'async for' or 'for'
+ self.writeline('%s event in context.blocks[%r][0](%s):' % (
+ loop, node.name, context), node)
+ self.indent()
+ self.simple_write('event', frame)
+ self.outdent()
+
self.outdent(level)
def visit_Extends(self, node, frame):
@@ -925,8 +889,6 @@ class CodeGenerator(NodeVisitor):
def visit_Include(self, node, frame):
"""Handles includes."""
- if node.with_context:
- self.unoptimize_scope(frame)
if node.ignore_missing:
self.writeline('try:')
self.indent()
@@ -952,48 +914,69 @@ class CodeGenerator(NodeVisitor):
self.writeline('else:')
self.indent()
+ skip_event_yield = False
if node.with_context:
- self.writeline('for event in template.root_render_func('
- 'template.new_context(context.parent, True, '
- 'locals())):')
+ loop = self.environment.is_async and 'async for' or 'for'
+ self.writeline('%s event in template.root_render_func('
+ 'template.new_context(context.get_all(), True, '
+ '%s)):' % (loop, self.dump_local_context(frame)))
+ elif self.environment.is_async:
+ self.writeline('for event in (await '
+ 'template._get_default_module_async())'
+ '._body_stream:')
else:
- self.writeline('for event in template.module._body_stream:')
+ if supports_yield_from:
+ self.writeline('yield from template._get_default_module()'
+ '._body_stream')
+ skip_event_yield = True
+ else:
+ self.writeline('for event in template._get_default_module()'
+ '._body_stream:')
- self.indent()
- self.simple_write('event', frame)
- self.outdent()
+ if not skip_event_yield:
+ self.indent()
+ self.simple_write('event', frame)
+ self.outdent()
if node.ignore_missing:
self.outdent()
def visit_Import(self, node, frame):
"""Visit regular imports."""
- if node.with_context:
- self.unoptimize_scope(frame)
- self.writeline('l_%s = ' % node.target, node)
+ self.writeline('%s = ' % frame.symbols.ref(node.target), node)
if frame.toplevel:
self.write('context.vars[%r] = ' % node.target)
+ if self.environment.is_async:
+ self.write('await ')
self.write('environment.get_template(')
self.visit(node.template, frame)
self.write(', %r).' % self.name)
if node.with_context:
- self.write('make_module(context.parent, True, locals())')
+ self.write('make_module%s(context.get_all(), True, %s)'
+ % (self.environment.is_async and '_async' or '',
+ self.dump_local_context(frame)))
+ elif self.environment.is_async:
+ self.write('_get_default_module_async()')
else:
- self.write('module')
+ self.write('_get_default_module()')
if frame.toplevel and not node.target.startswith('_'):
self.writeline('context.exported_vars.discard(%r)' % node.target)
- frame.assigned_names.add(node.target)
def visit_FromImport(self, node, frame):
"""Visit named imports."""
self.newline(node)
- self.write('included_template = environment.get_template(')
+ self.write('included_template = %senvironment.get_template('
+ % (self.environment.is_async and 'await ' or ''))
self.visit(node.template, frame)
self.write(', %r).' % self.name)
if node.with_context:
- self.write('make_module(context.parent, True)')
+ self.write('make_module%s(context.get_all(), True, %s)'
+ % (self.environment.is_async and '_async' or '',
+ self.dump_local_context(frame)))
+ elif self.environment.is_async:
+ self.write('_get_default_module_async()')
else:
- self.write('module')
+ self.write('_get_default_module()')
var_names = []
discarded_names = []
@@ -1002,15 +985,16 @@ class CodeGenerator(NodeVisitor):
name, alias = name
else:
alias = name
- self.writeline('l_%s = getattr(included_template, '
- '%r, missing)' % (alias, name))
- self.writeline('if l_%s is missing:' % alias)
+ self.writeline('%s = getattr(included_template, '
+ '%r, missing)' % (frame.symbols.ref(alias), name))
+ self.writeline('if %s is missing:' % frame.symbols.ref(alias))
self.indent()
- self.writeline('l_%s = environment.undefined(%r %% '
+ self.writeline('%s = undefined(%r %% '
'included_template.__name__, '
'name=%r)' %
- (alias, 'the template %%r (imported on %s) does '
- 'not export the requested name %s' % (
+ (frame.symbols.ref(alias),
+ 'the template %%r (imported on %s) does '
+ 'not export the requested name %s' % (
self.position(node),
repr(name)
), name))
@@ -1019,15 +1003,15 @@ class CodeGenerator(NodeVisitor):
var_names.append(alias)
if not alias.startswith('_'):
discarded_names.append(alias)
- frame.assigned_names.add(alias)
if var_names:
if len(var_names) == 1:
name = var_names[0]
- self.writeline('context.vars[%r] = l_%s' % (name, name))
+ self.writeline('context.vars[%r] = %s' %
+ (name, frame.symbols.ref(name)))
else:
self.writeline('context.vars.update({%s})' % ', '.join(
- '%r: l_%s' % (name, name) for name in var_names
+ '%r: %s' % (name, frame.symbols.ref(name)) for name in var_names
))
if discarded_names:
if len(discarded_names) == 1:
@@ -1038,15 +1022,9 @@ class CodeGenerator(NodeVisitor):
'update((%s))' % ', '.join(imap(repr, discarded_names)))
def visit_For(self, node, frame):
- # when calculating the nodes for the inner frame we have to exclude
- # the iterator contents from it
- children = node.iter_child_nodes(exclude=('iter',))
- if node.recursive:
- loop_frame = self.function_scoping(node, frame, children,
- find_special=False)
- else:
- loop_frame = frame.inner()
- loop_frame.inspect(children)
+ loop_frame = frame.inner()
+ test_frame = frame.inner()
+ else_frame = frame.inner()
# try to figure out if we have an extended loop. An extended loop
# is necessary if the loop is in recursive mode if the special loop
@@ -1055,111 +1033,121 @@ class CodeGenerator(NodeVisitor):
find_undeclared(node.iter_child_nodes(
only=('body',)), ('loop',))
+ loop_ref = None
+ if extended_loop:
+ loop_ref = loop_frame.symbols.declare_parameter('loop')
+
+ loop_frame.symbols.analyze_node(node, for_branch='body')
+ if node.else_:
+ else_frame.symbols.analyze_node(node, for_branch='else')
+
+ if node.test:
+ loop_filter_func = self.temporary_identifier()
+ test_frame.symbols.analyze_node(node, for_branch='test')
+ self.writeline('%s(fiter):' % self.func(loop_filter_func), node.test)
+ self.indent()
+ self.enter_frame(test_frame)
+ self.writeline(self.environment.is_async and 'async for ' or 'for ')
+ self.visit(node.target, loop_frame)
+ self.write(' in ')
+ self.write(self.environment.is_async and 'auto_aiter(fiter)' or 'fiter')
+ self.write(':')
+ self.indent()
+ self.writeline('if ', node.test)
+ self.visit(node.test, test_frame)
+ self.write(':')
+ self.indent()
+ self.writeline('yield ')
+ self.visit(node.target, loop_frame)
+ self.outdent(3)
+ self.leave_frame(test_frame, with_python_scope=True)
+
# if we don't have an recursive loop we have to find the shadowed
# variables at that point. Because loops can be nested but the loop
# variable is a special one we have to enforce aliasing for it.
- if not node.recursive:
- aliases = self.push_scope(loop_frame, ('loop',))
-
- # otherwise we set up a buffer and add a function def
- else:
- self.writeline('def loop(reciter, loop_render_func, depth=0):', node)
+ if node.recursive:
+ self.writeline('%s(reciter, loop_render_func, depth=0):' %
+ self.func('loop'), node)
self.indent()
self.buffer(loop_frame)
- aliases = {}
+
+ # Use the same buffer for the else frame
+ else_frame.buffer = loop_frame.buffer
# make sure the loop variable is a special one and raise a template
# assertion error if a loop tries to write to loop
if extended_loop:
- self.writeline('l_loop = missing')
- loop_frame.identifiers.add_special('loop')
+ self.writeline('%s = missing' % loop_ref)
+
for name in node.find_all(nodes.Name):
if name.ctx == 'store' and name.name == 'loop':
self.fail('Can\'t assign to special loop variable '
'in for-loop target', name.lineno)
- self.pull_locals(loop_frame)
if node.else_:
iteration_indicator = self.temporary_identifier()
self.writeline('%s = 1' % iteration_indicator)
- # Create a fake parent loop if the else or test section of a
- # loop is accessing the special loop variable and no parent loop
- # exists.
- if 'loop' not in aliases and 'loop' in find_undeclared(
- node.iter_child_nodes(only=('else_', 'test')), ('loop',)):
- self.writeline("l_loop = environment.undefined(%r, name='loop')" %
- ("'loop' is undefined. the filter section of a loop as well "
- "as the else block don't have access to the special 'loop'"
- " variable of the current loop. Because there is no parent "
- "loop it's undefined. Happened in loop on %s" %
- self.position(node)))
-
- self.writeline('for ', node)
+ self.writeline(self.environment.is_async and 'async for ' or 'for ', node)
self.visit(node.target, loop_frame)
- self.write(extended_loop and ', l_loop in LoopContext(' or ' in ')
-
- # if we have an extened loop and a node test, we filter in the
- # "outer frame".
- if extended_loop and node.test is not None:
- self.write('(')
- self.visit(node.target, loop_frame)
- self.write(' for ')
- self.visit(node.target, loop_frame)
- self.write(' in ')
- if node.recursive:
- self.write('reciter')
+ if extended_loop:
+ if self.environment.is_async:
+ self.write(', %s in await make_async_loop_context(' % loop_ref)
else:
- self.visit(node.iter, loop_frame)
- self.write(' if (')
- test_frame = loop_frame.copy()
- self.visit(node.test, test_frame)
- self.write('))')
+ self.write(', %s in LoopContext(' % loop_ref)
+ else:
+ self.write(' in ')
- elif node.recursive:
+ if node.test:
+ self.write('%s(' % loop_filter_func)
+ if node.recursive:
self.write('reciter')
else:
- self.visit(node.iter, loop_frame)
+ if self.environment.is_async and not extended_loop:
+ self.write('auto_aiter(')
+ self.visit(node.iter, frame)
+ if self.environment.is_async and not extended_loop:
+ self.write(')')
+ if node.test:
+ self.write(')')
if node.recursive:
- self.write(', loop_render_func, depth):')
+ self.write(', undefined, loop_render_func, depth):')
else:
- self.write(extended_loop and '):' or ':')
-
- # tests in not extended loops become a continue
- if not extended_loop and node.test is not None:
- self.indent()
- self.writeline('if not ')
- self.visit(node.test, loop_frame)
- self.write(':')
- self.indent()
- self.writeline('continue')
- self.outdent(2)
+ self.write(extended_loop and ', undefined):' or ':')
self.indent()
+ self.enter_frame(loop_frame)
+
self.blockvisit(node.body, loop_frame)
if node.else_:
self.writeline('%s = 0' % iteration_indicator)
self.outdent()
+ self.leave_frame(loop_frame, with_python_scope=node.recursive
+ and not node.else_)
if node.else_:
self.writeline('if %s:' % iteration_indicator)
self.indent()
- self.blockvisit(node.else_, loop_frame)
+ self.enter_frame(else_frame)
+ self.blockvisit(node.else_, else_frame)
+ self.leave_frame(else_frame)
self.outdent()
- # reset the aliases if there are any.
- if not node.recursive:
- self.pop_scope(aliases, loop_frame)
-
# if the node was recursive we have to return the buffer contents
# and start the iteration code
if node.recursive:
self.return_buffer_contents(loop_frame)
self.outdent()
self.start_write(frame, node)
+ if self.environment.is_async:
+ self.write('await ')
self.write('loop(')
+ if self.environment.is_async:
+ self.write('auto_aiter(')
self.visit(node.iter, frame)
+ if self.environment.is_async:
+ self.write(')')
self.write(', loop)')
self.end_write(frame)
@@ -1171,6 +1159,13 @@ class CodeGenerator(NodeVisitor):
self.indent()
self.blockvisit(node.body, if_frame)
self.outdent()
+ for elif_ in node.elif_:
+ self.writeline('elif ', elif_)
+ self.visit(elif_.test, if_frame)
+ self.write(':')
+ self.indent()
+ self.blockvisit(elif_.body, if_frame)
+ self.outdent()
if node.else_:
self.writeline('else:')
self.indent()
@@ -1178,36 +1173,46 @@ class CodeGenerator(NodeVisitor):
self.outdent()
def visit_Macro(self, node, frame):
- macro_frame = self.macro_body(node, frame)
+ macro_frame, macro_ref = self.macro_body(node, frame)
self.newline()
if frame.toplevel:
if not node.name.startswith('_'):
self.write('context.exported_vars.add(%r)' % node.name)
+ ref = frame.symbols.ref(node.name)
self.writeline('context.vars[%r] = ' % node.name)
- self.write('l_%s = ' % node.name)
- self.macro_def(node, macro_frame)
- frame.assigned_names.add(node.name)
+ self.write('%s = ' % frame.symbols.ref(node.name))
+ self.macro_def(macro_ref, macro_frame)
def visit_CallBlock(self, node, frame):
- children = node.iter_child_nodes(exclude=('call',))
- call_frame = self.macro_body(node, frame, children)
+ call_frame, macro_ref = self.macro_body(node, frame)
self.writeline('caller = ')
- self.macro_def(node, call_frame)
+ self.macro_def(macro_ref, call_frame)
self.start_write(frame, node)
- self.visit_Call(node.call, call_frame, forward_caller=True)
+ self.visit_Call(node.call, frame, forward_caller=True)
self.end_write(frame)
def visit_FilterBlock(self, node, frame):
filter_frame = frame.inner()
- filter_frame.inspect(node.iter_child_nodes())
- aliases = self.push_scope(filter_frame)
- self.pull_locals(filter_frame)
+ filter_frame.symbols.analyze_node(node)
+ self.enter_frame(filter_frame)
self.buffer(filter_frame)
self.blockvisit(node.body, filter_frame)
self.start_write(frame, node)
self.visit_Filter(node.filter, filter_frame)
self.end_write(frame)
- self.pop_scope(aliases, filter_frame)
+ self.leave_frame(filter_frame)
+
+ def visit_With(self, node, frame):
+ with_frame = frame.inner()
+ with_frame.symbols.analyze_node(node)
+ self.enter_frame(with_frame)
+ for idx, (target, expr) in enumerate(izip(node.targets, node.values)):
+ self.newline()
+ self.visit(target, with_frame)
+ self.write(' = ')
+ self.visit(expr, frame)
+ self.blockvisit(node.body, with_frame)
+ self.leave_frame(with_frame)
def visit_ExprStmt(self, node, frame):
self.newline(node)
@@ -1286,7 +1291,7 @@ class CodeGenerator(NodeVisitor):
if frame.buffer is None:
self.writeline('yield ' + val)
else:
- self.writeline(val + ', ')
+ self.writeline(val + ',')
else:
if frame.buffer is None:
self.writeline('yield ', item)
@@ -1294,8 +1299,8 @@ class CodeGenerator(NodeVisitor):
self.newline(item)
close = 1
if frame.eval_ctx.volatile:
- self.write('(context.eval_ctx.autoescape and'
- ' escape or to_string)(')
+ self.write('(escape if context.eval_ctx.autoescape'
+ ' else to_string)(')
elif frame.eval_ctx.autoescape:
self.write('escape(')
else:
@@ -1309,7 +1314,7 @@ class CodeGenerator(NodeVisitor):
self.visit(item, frame)
self.write(')' * close)
if frame.buffer is not None:
- self.write(', ')
+ self.write(',')
if frame.buffer is not None:
# close the open parentheses
self.outdent()
@@ -1332,8 +1337,8 @@ class CodeGenerator(NodeVisitor):
self.newline(argument)
close = 0
if frame.eval_ctx.volatile:
- self.write('(context.eval_ctx.autoescape and'
- ' escape or to_string)(')
+ self.write('(escape if context.eval_ctx.autoescape else'
+ ' to_string)(')
close += 1
elif frame.eval_ctx.autoescape:
self.write('escape(')
@@ -1358,73 +1363,72 @@ class CodeGenerator(NodeVisitor):
if outdent_later:
self.outdent()
- def make_assignment_frame(self, frame):
- # toplevel assignments however go into the local namespace and
- # the current template's context. We create a copy of the frame
- # here and add a set so that the Name visitor can add the assigned
- # names here.
- if not frame.toplevel:
- return frame
- assignment_frame = frame.copy()
- assignment_frame.toplevel_assignments = set()
- return assignment_frame
-
- def export_assigned_vars(self, frame, assignment_frame):
- if not frame.toplevel:
- return
- public_names = [x for x in assignment_frame.toplevel_assignments
- if not x.startswith('_')]
- if len(assignment_frame.toplevel_assignments) == 1:
- name = next(iter(assignment_frame.toplevel_assignments))
- self.writeline('context.vars[%r] = l_%s' % (name, name))
- else:
- self.writeline('context.vars.update({')
- for idx, name in enumerate(assignment_frame.toplevel_assignments):
- if idx:
- self.write(', ')
- self.write('%r: l_%s' % (name, name))
- self.write('})')
- if public_names:
- if len(public_names) == 1:
- self.writeline('context.exported_vars.add(%r)' %
- public_names[0])
- else:
- self.writeline('context.exported_vars.update((%s))' %
- ', '.join(imap(repr, public_names)))
-
def visit_Assign(self, node, frame):
+ self.push_assign_tracking()
self.newline(node)
- assignment_frame = self.make_assignment_frame(frame)
- self.visit(node.target, assignment_frame)
+ self.visit(node.target, frame)
self.write(' = ')
self.visit(node.node, frame)
- self.export_assigned_vars(frame, assignment_frame)
+ self.pop_assign_tracking(frame)
def visit_AssignBlock(self, node, frame):
+ self.push_assign_tracking()
block_frame = frame.inner()
- block_frame.inspect(node.body)
- aliases = self.push_scope(block_frame)
- self.pull_locals(block_frame)
+ # This is a special case. Since a set block always captures we
+ # will disable output checks. This way one can use set blocks
+ # toplevel even in extended templates.
+ block_frame.require_output_check = False
+ block_frame.symbols.analyze_node(node)
+ self.enter_frame(block_frame)
self.buffer(block_frame)
self.blockvisit(node.body, block_frame)
- self.pop_scope(aliases, block_frame)
-
- assignment_frame = self.make_assignment_frame(frame)
self.newline(node)
- self.visit(node.target, assignment_frame)
- self.write(' = concat(%s)' % block_frame.buffer)
- self.export_assigned_vars(frame, assignment_frame)
+ self.visit(node.target, frame)
+ self.write(' = (Markup if context.eval_ctx.autoescape '
+ 'else identity)(')
+ if node.filter is not None:
+ self.visit_Filter(node.filter, block_frame)
+ else:
+ self.write('concat(%s)' % block_frame.buffer)
+ self.write(')')
+ self.pop_assign_tracking(frame)
+ self.leave_frame(block_frame)
# -- Expression Visitors
def visit_Name(self, node, frame):
if node.ctx == 'store' and frame.toplevel:
- frame.toplevel_assignments.add(node.name)
- self.write('l_' + node.name)
- frame.assigned_names.add(node.name)
+ if self._assign_stack:
+ self._assign_stack[-1].add(node.name)
+ ref = frame.symbols.ref(node.name)
+
+ # If we are looking up a variable we might have to deal with the
+ # case where it's undefined. We can skip that case if the load
+ # instruction indicates a parameter which are always defined.
+ if node.ctx == 'load':
+ load = frame.symbols.find_load(ref)
+ if not (load is not None and load[0] == VAR_LOAD_PARAMETER and \
+ not self.parameter_is_undeclared(ref)):
+ self.write('(undefined(name=%r) if %s is missing else %s)' %
+ (node.name, ref, ref))
+ return
+
+ self.write(ref)
+
+ def visit_NSRef(self, node, frame):
+ # NSRefs can only be used to store values; since they use the normal
+ # `foo.bar` notation they will be parsed as a normal attribute access
+ # when used anywhere but in a `set` context
+ ref = frame.symbols.ref(node.name)
+ self.writeline('if not isinstance(%s, Namespace):' % ref)
+ self.indent()
+ self.writeline('raise TemplateRuntimeError(%r)' %
+ 'cannot assign attribute on non-namespace object')
+ self.outdent()
+ self.writeline('%s[%r]' % (ref, node.attr))
def visit_Const(self, node, frame):
- val = node.value
+ val = node.as_const(frame.eval_ctx)
if isinstance(val, float):
self.write(str(val))
else:
@@ -1434,7 +1438,7 @@ class CodeGenerator(NodeVisitor):
try:
self.write(repr(node.as_const(frame.eval_ctx)))
except nodes.Impossible:
- self.write('(context.eval_ctx.autoescape and Markup or identity)(%r)'
+ self.write('(Markup if context.eval_ctx.autoescape else identity)(%r)'
% node.data)
def visit_Tuple(self, node, frame):
@@ -1465,6 +1469,7 @@ class CodeGenerator(NodeVisitor):
self.write('}')
def binop(operator, interceptable=True):
+ @optimizeconst
def visitor(self, node, frame):
if self.environment.sandboxed and \
operator in self.environment.intercepted_binops:
@@ -1481,6 +1486,7 @@ class CodeGenerator(NodeVisitor):
return visitor
def uaop(operator, interceptable=True):
+ @optimizeconst
def visitor(self, node, frame):
if self.environment.sandboxed and \
operator in self.environment.intercepted_unops:
@@ -1506,6 +1512,7 @@ class CodeGenerator(NodeVisitor):
visit_Not = uaop('not ', interceptable=False)
del binop, uaop
+ @optimizeconst
def visit_Concat(self, node, frame):
if frame.eval_ctx.volatile:
func_name = '(context.eval_ctx.volatile and' \
@@ -1520,6 +1527,7 @@ class CodeGenerator(NodeVisitor):
self.write(', ')
self.write('))')
+ @optimizeconst
def visit_Compare(self, node, frame):
self.visit(node.expr, frame)
for op in node.ops:
@@ -1529,11 +1537,13 @@ class CodeGenerator(NodeVisitor):
self.write(' %s ' % operators[node.op])
self.visit(node.expr, frame)
+ @optimizeconst
def visit_Getattr(self, node, frame):
self.write('environment.getattr(')
self.visit(node.node, frame)
self.write(', %r)' % node.attr)
+ @optimizeconst
def visit_Getitem(self, node, frame):
# slices bypass the environment getitem method.
if isinstance(node.arg, nodes.Slice):
@@ -1558,7 +1568,10 @@ class CodeGenerator(NodeVisitor):
self.write(':')
self.visit(node.step, frame)
+ @optimizeconst
def visit_Filter(self, node, frame):
+ if self.environment.is_async:
+ self.write('await auto_await(')
self.write(self.filters[node.name] + '(')
func = self.environment.filters.get(node.name)
if func is None:
@@ -1584,7 +1597,10 @@ class CodeGenerator(NodeVisitor):
self.write('concat(%s)' % frame.buffer)
self.signature(node, frame)
self.write(')')
+ if self.environment.is_async:
+ self.write(')')
+ @optimizeconst
def visit_Test(self, node, frame):
self.write(self.tests[node.name] + '(')
if node.name not in self.environment.tests:
@@ -1593,11 +1609,12 @@ class CodeGenerator(NodeVisitor):
self.signature(node, frame)
self.write(')')
+ @optimizeconst
def visit_CondExpr(self, node, frame):
def write_expr2():
if node.expr2 is not None:
return self.visit(node.expr2, frame)
- self.write('environment.undefined(%r)' % ('the inline if-'
+ self.write('undefined(%r)' % ('the inline if-'
'expression on %s evaluated to false and '
'no else section was defined.' % self.position(node)))
@@ -1609,7 +1626,10 @@ class CodeGenerator(NodeVisitor):
write_expr2()
self.write(')')
+ @optimizeconst
def visit_Call(self, node, frame, forward_caller=False):
+ if self.environment.is_async:
+ self.write('await auto_await(')
if self.environment.sandboxed:
self.write('environment.call(context, ')
else:
@@ -1618,6 +1638,8 @@ class CodeGenerator(NodeVisitor):
extra_kwargs = forward_caller and {'caller': 'caller'} or None
self.signature(node, frame, extra_kwargs)
self.write(')')
+ if self.environment.is_async:
+ self.write(')')
def visit_Keyword(self, node, frame):
self.write(node.key + '=')
@@ -1658,11 +1680,24 @@ class CodeGenerator(NodeVisitor):
def visit_Scope(self, node, frame):
scope_frame = frame.inner()
- scope_frame.inspect(node.iter_child_nodes())
- aliases = self.push_scope(scope_frame)
- self.pull_locals(scope_frame)
+ scope_frame.symbols.analyze_node(node)
+ self.enter_frame(scope_frame)
+ self.blockvisit(node.body, scope_frame)
+ self.leave_frame(scope_frame)
+
+ def visit_OverlayScope(self, node, frame):
+ ctx = self.temporary_identifier()
+ self.writeline('%s = %s' % (ctx, self.derive_context(frame)))
+ self.writeline('%s.vars = ' % ctx)
+ self.visit(node.context, frame)
+ self.push_context_reference(ctx)
+
+ scope_frame = frame.inner(isolated=True)
+ scope_frame.symbols.analyze_node(node)
+ self.enter_frame(scope_frame)
self.blockvisit(node.body, scope_frame)
- self.pop_scope(aliases, scope_frame)
+ self.leave_frame(scope_frame)
+ self.pop_context_reference()
def visit_EvalContextModifier(self, node, frame):
for keyword in node.options:
@@ -1677,10 +1712,10 @@ class CodeGenerator(NodeVisitor):
def visit_ScopedEvalContextModifier(self, node, frame):
old_ctx_name = self.temporary_identifier()
- safed_ctx = frame.eval_ctx.save()
+ saved_ctx = frame.eval_ctx.save()
self.writeline('%s = context.eval_ctx.save()' % old_ctx_name)
self.visit_EvalContextModifier(node, frame)
for child in node.body:
self.visit(child, frame)
- frame.eval_ctx.revert(safed_ctx)
+ frame.eval_ctx.revert(saved_ctx)
self.writeline('context.eval_ctx.revert(%s)' % old_ctx_name)
diff --git a/third_party/jinja2/constants.py b/third_party/jinja2/constants.py
index cab203cc77..11efd1ed15 100644
--- a/third_party/jinja2/constants.py
+++ b/third_party/jinja2/constants.py
@@ -5,7 +5,7 @@
Various constants.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
diff --git a/third_party/jinja2/debug.py b/third_party/jinja2/debug.py
index 3252748369..b61139f0cd 100644
--- a/third_party/jinja2/debug.py
+++ b/third_party/jinja2/debug.py
@@ -7,7 +7,7 @@
ugly stuff with the Python traceback system in order to achieve tracebacks
with correct line numbers, locals and contents.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import sys
@@ -195,21 +195,43 @@ def translate_exception(exc_info, initial_skip=0):
return ProcessedTraceback(exc_info[0], exc_info[1], frames)
+def get_jinja_locals(real_locals):
+ ctx = real_locals.get('context')
+ if ctx:
+ locals = ctx.get_all().copy()
+ else:
+ locals = {}
+
+ local_overrides = {}
+
+ for name, value in iteritems(real_locals):
+ if not name.startswith('l_') or value is missing:
+ continue
+ try:
+ _, depth, name = name.split('_', 2)
+ depth = int(depth)
+ except ValueError:
+ continue
+ cur_depth = local_overrides.get(name, (-1,))[0]
+ if cur_depth < depth:
+ local_overrides[name] = (depth, value)
+
+ for name, (_, value) in iteritems(local_overrides):
+ if value is missing:
+ locals.pop(name, None)
+ else:
+ locals[name] = value
+
+ return locals
+
+
def fake_exc_info(exc_info, filename, lineno):
"""Helper for `translate_exception`."""
exc_type, exc_value, tb = exc_info
# figure the real context out
if tb is not None:
- real_locals = tb.tb_frame.f_locals.copy()
- ctx = real_locals.get('context')
- if ctx:
- locals = ctx.get_all()
- else:
- locals = {}
- for name, value in iteritems(real_locals):
- if name.startswith('l_') and value is not missing:
- locals[name[2:]] = value
+ locals = get_jinja_locals(tb.tb_frame.f_locals)
# if there is a local called __jinja_exception__, we get
# rid of it to not break the debug functionality.
diff --git a/third_party/jinja2/defaults.py b/third_party/jinja2/defaults.py
index 3717a7223f..7c93dec0ae 100644
--- a/third_party/jinja2/defaults.py
+++ b/third_party/jinja2/defaults.py
@@ -5,11 +5,11 @@
Jinja default filters and tags.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
from jinja2._compat import range_type
-from jinja2.utils import generate_lorem_ipsum, Cycler, Joiner
+from jinja2.utils import generate_lorem_ipsum, Cycler, Joiner, Namespace
# defaults for the parser / lexer
@@ -35,7 +35,20 @@ DEFAULT_NAMESPACE = {
'dict': dict,
'lipsum': generate_lorem_ipsum,
'cycler': Cycler,
- 'joiner': Joiner
+ 'joiner': Joiner,
+ 'namespace': Namespace
+}
+
+
+# default policies
+DEFAULT_POLICIES = {
+ 'compiler.ascii_str': True,
+ 'urlize.rel': 'noopener',
+ 'urlize.target': None,
+ 'truncate.leeway': 5,
+ 'json.dumps_function': None,
+ 'json.dumps_kwargs': {'sort_keys': True},
+ 'ext.i18n.trimmed': False,
}
diff --git a/third_party/jinja2/environment.py b/third_party/jinja2/environment.py
index 8b2572bb8c..549d9afab4 100644
--- a/third_party/jinja2/environment.py
+++ b/third_party/jinja2/environment.py
@@ -5,32 +5,32 @@
Provides a class that holds runtime and parsing time options.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import os
import sys
+import weakref
+from functools import reduce, partial
from jinja2 import nodes
from jinja2.defaults import BLOCK_START_STRING, \
BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \
COMMENT_START_STRING, COMMENT_END_STRING, LINE_STATEMENT_PREFIX, \
LINE_COMMENT_PREFIX, TRIM_BLOCKS, NEWLINE_SEQUENCE, \
DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE, \
- KEEP_TRAILING_NEWLINE, LSTRIP_BLOCKS
+ DEFAULT_POLICIES, KEEP_TRAILING_NEWLINE, LSTRIP_BLOCKS
from jinja2.lexer import get_lexer, TokenStream
from jinja2.parser import Parser
from jinja2.nodes import EvalContext
-from jinja2.optimizer import optimize
from jinja2.compiler import generate, CodeGenerator
from jinja2.runtime import Undefined, new_context, Context
from jinja2.exceptions import TemplateSyntaxError, TemplateNotFound, \
TemplatesNotFound, TemplateRuntimeError
from jinja2.utils import import_string, LRUCache, Markup, missing, \
- concat, consume, internalcode
+ concat, consume, internalcode, have_async_gen
from jinja2._compat import imap, ifilter, string_types, iteritems, \
text_type, reraise, implements_iterator, implements_to_string, \
- get_next, encode_filename, PY2, PYPY
-from functools import reduce
+ encode_filename, PY2, PYPY
# for direct template usage we have up to ten living environments
@@ -87,6 +87,16 @@ def load_extensions(environment, extensions):
return result
+def fail_for_missing_callable(string, name):
+ msg = string % name
+ if isinstance(name, Undefined):
+ try:
+ name._fail_with_undefined_error()
+ except Exception as e:
+ msg = '%s (%s; did you forget to quote the callable name?)' % (msg, e)
+ raise TemplateRuntimeError(msg)
+
+
def _environment_sanity_check(environment):
"""Perform a sanity check on the environment."""
assert issubclass(environment.undefined, Undefined), 'undefined must ' \
@@ -167,7 +177,7 @@ class Environment(object):
look at :ref:`the extensions documentation <jinja-extensions>`.
`optimized`
- should the optimizer be enabled? Default is `True`.
+ should the optimizer be enabled? Default is ``True``.
`undefined`
:class:`Undefined` or a subclass of it that is used to represent
@@ -176,14 +186,14 @@ class Environment(object):
`finalize`
A callable that can be used to process the result of a variable
expression before it is output. For example one can convert
- `None` implicitly into an empty string here.
+ ``None`` implicitly into an empty string here.
`autoescape`
- If set to true the XML/HTML autoescaping feature is enabled by
+ If set to ``True`` the XML/HTML autoescaping feature is enabled by
default. For more details about autoescaping see
:class:`~jinja2.utils.Markup`. As of Jinja 2.4 this can also
be a callable that is passed the template name and has to
- return `True` or `False` depending on autoescape should be
+ return ``True`` or ``False`` depending on autoescape should be
enabled by default.
.. versionchanged:: 2.4
@@ -205,7 +215,7 @@ class Environment(object):
`auto_reload`
Some loaders load templates from locations where the template
sources may change (ie: file system or database). If
- `auto_reload` is set to `True` (default) every time a template is
+ ``auto_reload`` is set to ``True`` (default) every time a template is
requested the loader checks if the source changed and if yes, it
will reload the template. For higher performance it's possible to
disable that.
@@ -216,6 +226,11 @@ class Environment(object):
have to be parsed if they were not changed.
See :ref:`bytecode-cache` for more information.
+
+ `enable_async`
+ If set to true this enables async template execution which allows
+ you to take advantage of newer Python features. This requires
+ Python 3.6 or later.
"""
#: if this environment is sandboxed. Modifying this variable won't make
@@ -267,7 +282,8 @@ class Environment(object):
loader=None,
cache_size=400,
auto_reload=True,
- bytecode_cache=None):
+ bytecode_cache=None,
+ enable_async=False):
# !!Important notice!!
# The constructor accepts quite a few arguments that should be
# passed by keyword rather than position. However it's important to
@@ -310,9 +326,15 @@ class Environment(object):
self.bytecode_cache = bytecode_cache
self.auto_reload = auto_reload
+ # configurable policies
+ self.policies = DEFAULT_POLICIES.copy()
+
# load extensions
self.extensions = load_extensions(self, extensions)
+ self.enable_async = enable_async
+ self.is_async = self.enable_async and have_async_gen
+
_environment_sanity_check(self)
def add_extension(self, extension):
@@ -387,7 +409,7 @@ class Environment(object):
"""Get an item or attribute of an object but prefer the item."""
try:
return obj[argument]
- except (TypeError, LookupError):
+ except (AttributeError, TypeError, LookupError):
if isinstance(argument, string_types):
try:
attr = str(argument)
@@ -417,11 +439,16 @@ class Environment(object):
context=None, eval_ctx=None):
"""Invokes a filter on a value the same way the compiler does it.
+ Note that on Python 3 this might return a coroutine in case the
+ filter is running from an environment in async mode and the filter
+ supports async execution. It's your responsibility to await this
+ if needed.
+
.. versionadded:: 2.7
"""
func = self.filters.get(name)
if func is None:
- raise TemplateRuntimeError('no filter named %r' % name)
+ fail_for_missing_callable('no filter named %r', name)
args = [value] + list(args or ())
if getattr(func, 'contextfilter', False):
if context is None:
@@ -446,7 +473,7 @@ class Environment(object):
"""
func = self.tests.get(name)
if func is None:
- raise TemplateRuntimeError('no test named %r' % name)
+ fail_for_missing_callable('no test named %r', name)
return func(value, *(args or ()), **(kwargs or {}))
@internalcode
@@ -512,7 +539,8 @@ class Environment(object):
.. versionadded:: 2.5
"""
- return generate(source, self, name, filename, defer_init=defer_init)
+ return generate(source, self, name, filename, defer_init=defer_init,
+ optimized=self.optimized)
def _compile(self, source, filename):
"""Internal hook that can be overridden to hook a different compile
@@ -549,8 +577,6 @@ class Environment(object):
if isinstance(source, string_types):
source_hint = source
source = self._parse(source, name, filename)
- if self.optimized:
- source = optimize(source, self)
source = self._generate(source, name, filename,
defer_init=defer_init)
if raw:
@@ -769,15 +795,7 @@ class Environment(object):
def _load_template(self, name, globals):
if self.loader is None:
raise TypeError('no loader for this environment specified')
- try:
- # use abs path for cache key
- cache_key = self.loader.get_source(self, name)[1]
- except RuntimeError:
- # if loader does not implement get_source()
- cache_key = None
- # if template is not file, use name for cache key
- if cache_key is None:
- cache_key = name
+ cache_key = (weakref.ref(self.loader), name)
if self.cache is not None:
template = self.cache.get(cache_key)
if template is not None and (not self.auto_reload or
@@ -791,7 +809,7 @@ class Environment(object):
@internalcode
def get_template(self, name, parent=None, globals=None):
"""Load a template from the loader. If a loader is configured this
- method ask the loader for the template and returns a :class:`Template`.
+ method asks the loader for the template and returns a :class:`Template`.
If the `parent` parameter is not `None`, :meth:`join_path` is called
to get the real template name before loading.
@@ -915,14 +933,15 @@ class Template(object):
optimized=True,
undefined=Undefined,
finalize=None,
- autoescape=False):
+ autoescape=False,
+ enable_async=False):
env = get_spontaneous_environment(
block_start_string, block_end_string, variable_start_string,
variable_end_string, comment_start_string, comment_end_string,
line_statement_prefix, line_comment_prefix, trim_blocks,
lstrip_blocks, newline_sequence, keep_trailing_newline,
frozenset(extensions), optimized, undefined, finalize, autoescape,
- None, 0, False, None)
+ None, 0, False, None, enable_async)
return env.from_string(source, template_class=cls)
@classmethod
@@ -988,6 +1007,19 @@ class Template(object):
exc_info = sys.exc_info()
return self.environment.handle_exception(exc_info, True)
+ def render_async(self, *args, **kwargs):
+ """This works similar to :meth:`render` but returns a coroutine
+ that when awaited returns the entire rendered template string. This
+ requires the async feature to be enabled.
+
+ Example usage::
+
+ await template.render_async(knights='that say nih; asynchronously')
+ """
+ # see asyncsupport for the actual implementation
+ raise NotImplementedError('This feature is not available for this '
+ 'version of Python')
+
def stream(self, *args, **kwargs):
"""Works exactly like :meth:`generate` but returns a
:class:`TemplateStream`.
@@ -1012,6 +1044,14 @@ class Template(object):
return
yield self.environment.handle_exception(exc_info, True)
+ def generate_async(self, *args, **kwargs):
+ """An async version of :meth:`generate`. Works very similarly but
+ returns an async iterator instead.
+ """
+ # see asyncsupport for the actual implementation
+ raise NotImplementedError('This feature is not available for this '
+ 'version of Python')
+
def new_context(self, vars=None, shared=False, locals=None):
"""Create a new :class:`Context` for this template. The vars
provided will be passed to the template. Per default the globals
@@ -1032,6 +1072,23 @@ class Template(object):
"""
return TemplateModule(self, self.new_context(vars, shared, locals))
+ def make_module_async(self, vars=None, shared=False, locals=None):
+ """As template module creation can invoke template code for
+ asynchronous exections this method must be used instead of the
+ normal :meth:`make_module` one. Likewise the module attribute
+ becomes unavailable in async mode.
+ """
+ # see asyncsupport for the actual implementation
+ raise NotImplementedError('This feature is not available for this '
+ 'version of Python')
+
+ @internalcode
+ def _get_default_module(self):
+ if self._module is not None:
+ return self._module
+ self._module = rv = self.make_module()
+ return rv
+
@property
def module(self):
"""The template as module. This is used for imports in the
@@ -1043,11 +1100,10 @@ class Template(object):
'23'
>>> t.module.foo() == u'42'
True
+
+ This attribute is not available if async mode is enabled.
"""
- if self._module is not None:
- return self._module
- self._module = rv = self.make_module()
- return rv
+ return self._get_default_module()
def get_corresponding_lineno(self, lineno):
"""Return the source line number of a line number in the
@@ -1086,8 +1142,15 @@ class TemplateModule(object):
converting it into an unicode- or bytestrings renders the contents.
"""
- def __init__(self, template, context):
- self._body_stream = list(template.root_render_func(context))
+ def __init__(self, template, context, body_stream=None):
+ if body_stream is None:
+ if context.environment.is_async:
+ raise RuntimeError('Async mode requires a body stream '
+ 'to be passed to a template module. Use '
+ 'the async methods of the API you are '
+ 'using.')
+ body_stream = list(template.root_render_func(context))
+ self._body_stream = body_stream
self.__dict__.update(context.get_exported())
self.__name__ = template.name
@@ -1171,35 +1234,35 @@ class TemplateStream(object):
def disable_buffering(self):
"""Disable the output buffering."""
- self._next = get_next(self._gen)
+ self._next = partial(next, self._gen)
self.buffered = False
+ def _buffered_generator(self, size):
+ buf = []
+ c_size = 0
+ push = buf.append
+
+ while 1:
+ try:
+ while c_size < size:
+ c = next(self._gen)
+ push(c)
+ if c:
+ c_size += 1
+ except StopIteration:
+ if not c_size:
+ return
+ yield concat(buf)
+ del buf[:]
+ c_size = 0
+
def enable_buffering(self, size=5):
"""Enable buffering. Buffer `size` items before yielding them."""
if size <= 1:
raise ValueError('buffer size too small')
- def generator(next):
- buf = []
- c_size = 0
- push = buf.append
-
- while 1:
- try:
- while c_size < size:
- c = next()
- push(c)
- if c:
- c_size += 1
- except StopIteration:
- if not c_size:
- return
- yield concat(buf)
- del buf[:]
- c_size = 0
-
self.buffered = True
- self._next = get_next(generator(get_next(self._gen)))
+ self._next = partial(next, self._buffered_generator(size))
def __iter__(self):
return self
diff --git a/third_party/jinja2/exceptions.py b/third_party/jinja2/exceptions.py
index c9df6dc7c2..c018a33e32 100644
--- a/third_party/jinja2/exceptions.py
+++ b/third_party/jinja2/exceptions.py
@@ -5,7 +5,7 @@
Jinja exceptions.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
from jinja2._compat import imap, text_type, PY2, implements_to_string
diff --git a/third_party/jinja2/ext.py b/third_party/jinja2/ext.py
index 562ab506ff..0734a84f73 100644
--- a/third_party/jinja2/ext.py
+++ b/third_party/jinja2/ext.py
@@ -7,9 +7,11 @@
tags work. By default two example extensions exist: an i18n and a cache
extension.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD.
"""
+import re
+
from jinja2 import nodes
from jinja2.defaults import BLOCK_START_STRING, \
BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \
@@ -87,7 +89,7 @@ class Extension(with_metaclass(ExtensionRegistry, object)):
def filter_stream(self, stream):
"""It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
to filter tokens returned. This method has to return an iterable of
- :class:`~jinja2.lexer.Token`\s, but it doesn't have to return a
+ :class:`~jinja2.lexer.Token`\\s, but it doesn't have to return a
:class:`~jinja2.lexer.TokenStream`.
In the `ext` folder of the Jinja2 source distribution there is a file
@@ -223,6 +225,7 @@ class InternationalizationExtension(Extension):
plural_expr = None
plural_expr_assignment = None
variables = {}
+ trimmed = None
while parser.stream.current.type != 'block_end':
if variables:
parser.stream.expect('comma')
@@ -241,6 +244,9 @@ class InternationalizationExtension(Extension):
if parser.stream.current.type == 'assign':
next(parser.stream)
variables[name.value] = var = parser.parse_expression()
+ elif trimmed is None and name.value in ('trimmed', 'notrimmed'):
+ trimmed = name.value == 'trimmed'
+ continue
else:
variables[name.value] = var = nodes.Name(name.value, 'load')
@@ -256,7 +262,7 @@ class InternationalizationExtension(Extension):
parser.stream.expect('block_end')
- plural = plural_names = None
+ plural = None
have_plural = False
referenced = set()
@@ -297,6 +303,13 @@ class InternationalizationExtension(Extension):
elif plural_expr is None:
parser.fail('pluralize without variables', lineno)
+ if trimmed is None:
+ trimmed = self.environment.policies['ext.i18n.trimmed']
+ if trimmed:
+ singular = self._trim_whitespace(singular)
+ if plural:
+ plural = self._trim_whitespace(plural)
+
node = self._make_node(singular, plural, variables, plural_expr,
bool(referenced),
num_called_num and have_plural)
@@ -306,6 +319,9 @@ class InternationalizationExtension(Extension):
else:
return node
+ def _trim_whitespace(self, string, _ws_re=re.compile(r'\s*\n\s*')):
+ return _ws_re.sub(' ', string.strip())
+
def _parse_block(self, parser, allow_pluralize):
"""Parse until the next block tag with a given name."""
referenced = []
@@ -411,38 +427,11 @@ class LoopControlExtension(Extension):
class WithExtension(Extension):
- """Adds support for a django-like with block."""
- tags = set(['with'])
-
- def parse(self, parser):
- node = nodes.Scope(lineno=next(parser.stream).lineno)
- assignments = []
- while parser.stream.current.type != 'block_end':
- lineno = parser.stream.current.lineno
- if assignments:
- parser.stream.expect('comma')
- target = parser.parse_assign_target()
- parser.stream.expect('assign')
- expr = parser.parse_expression()
- assignments.append(nodes.Assign(target, expr, lineno=lineno))
- node.body = assignments + \
- list(parser.parse_statements(('name:endwith',),
- drop_needle=True))
- return node
+ pass
class AutoEscapeExtension(Extension):
- """Changes auto escape rules for a scope."""
- tags = set(['autoescape'])
-
- def parse(self, parser):
- node = nodes.ScopedEvalContextModifier(lineno=next(parser.stream).lineno)
- node.options = [
- nodes.Keyword('autoescape', parser.parse_expression())
- ]
- node.body = parser.parse_statements(('name:endautoescape',),
- drop_needle=True)
- return nodes.Scope([node])
+ pass
def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS,
@@ -610,6 +599,8 @@ def babel_extract(fileobj, keywords, comment_tags, options):
auto_reload=False
)
+ if getbool(options, 'trimmed'):
+ environment.policies['ext.i18n.trimmed'] = True
if getbool(options, 'newstyle_gettext'):
environment.newstyle_gettext = True
diff --git a/third_party/jinja2/filters.py b/third_party/jinja2/filters.py
index e5c7a1ab43..267ddddaa0 100644
--- a/third_party/jinja2/filters.py
+++ b/third_party/jinja2/filters.py
@@ -5,23 +5,25 @@
Bundled jinja filters.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import re
import math
+import random
+import warnings
-from random import choice
-from operator import itemgetter
-from itertools import groupby
+from itertools import groupby, chain
+from collections import namedtuple
from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \
- unicode_urlencode
+ unicode_urlencode, htmlsafe_json_dumps
from jinja2.runtime import Undefined
from jinja2.exceptions import FilterArgumentError
-from jinja2._compat import imap, string_types, text_type, iteritems
+from jinja2._compat import imap, string_types, text_type, iteritems, PY2
-_word_re = re.compile(r'\w+(?u)')
+_word_re = re.compile(r'\w+', re.UNICODE)
+_word_beginning_split_re = re.compile(r'([-\s\(\{\[\<]+)', re.UNICODE)
def contextfilter(f):
@@ -44,29 +46,41 @@ def evalcontextfilter(f):
def environmentfilter(f):
- """Decorator for marking evironment dependent filters. The current
+ """Decorator for marking environment dependent filters. The current
:class:`Environment` is passed to the filter as first argument.
"""
f.environmentfilter = True
return f
-def make_attrgetter(environment, attribute):
+def ignore_case(value):
+ """For use as a postprocessor for :func:`make_attrgetter`. Converts strings
+ to lowercase and returns other types as-is."""
+ return value.lower() if isinstance(value, string_types) else value
+
+
+def make_attrgetter(environment, attribute, postprocess=None):
"""Returns a callable that looks up the given attribute from a
passed object with the rules of the environment. Dots are allowed
to access attributes of attributes. Integer parts in paths are
looked up as integers.
"""
- if not isinstance(attribute, string_types) \
- or ('.' not in attribute and not attribute.isdigit()):
- return lambda x: environment.getitem(x, attribute)
- attribute = attribute.split('.')
+ if attribute is None:
+ attribute = []
+ elif isinstance(attribute, string_types):
+ attribute = [int(x) if x.isdigit() else x for x in attribute.split('.')]
+ else:
+ attribute = [attribute]
+
def attrgetter(item):
for part in attribute:
- if part.isdigit():
- part = int(part)
item = environment.getitem(item, part)
+
+ if postprocess is not None:
+ item = postprocess(item)
+
return item
+
return attrgetter
@@ -183,15 +197,13 @@ def do_title(s):
"""Return a titlecased version of the value. I.e. words will start with
uppercase letters, all remaining characters are lowercase.
"""
- rv = []
- for item in re.compile(r'([-\s]+)(?u)').split(soft_unicode(s)):
- if not item:
- continue
- rv.append(item[0].upper() + item[1:].lower())
- return ''.join(rv)
+ return ''.join(
+ [item[0].upper() + item[1:].lower()
+ for item in _word_beginning_split_re.split(soft_unicode(s))
+ if item])
-def do_dictsort(value, case_sensitive=False, by='key'):
+def do_dictsort(value, case_sensitive=False, by='key', reverse=False):
"""Sort a dict and yield (key, value) pairs. Because python dicts are
unsorted you may want to use this function to order them by either
key or value:
@@ -201,6 +213,9 @@ def do_dictsort(value, case_sensitive=False, by='key'):
{% for item in mydict|dictsort %}
sort the dict by key, case insensitive
+ {% for item in mydict|dictsort(reverse=true) %}
+ sort the dict by key, case insensitive, reverse order
+
{% for item in mydict|dictsort(true) %}
sort the dict by key, case sensitive
@@ -212,20 +227,25 @@ def do_dictsort(value, case_sensitive=False, by='key'):
elif by == 'value':
pos = 1
else:
- raise FilterArgumentError('You can only sort by either '
- '"key" or "value"')
+ raise FilterArgumentError(
+ 'You can only sort by either "key" or "value"'
+ )
+
def sort_func(item):
value = item[pos]
- if isinstance(value, string_types) and not case_sensitive:
- value = value.lower()
+
+ if not case_sensitive:
+ value = ignore_case(value)
+
return value
- return sorted(value.items(), key=sort_func)
+ return sorted(value.items(), key=sort_func, reverse=reverse)
@environmentfilter
-def do_sort(environment, value, reverse=False, case_sensitive=False,
- attribute=None):
+def do_sort(
+ environment, value, reverse=False, case_sensitive=False, attribute=None
+):
"""Sort an iterable. Per default it sorts ascending, if you pass it
true as first argument it will reverse the sorting.
@@ -251,18 +271,85 @@ def do_sort(environment, value, reverse=False, case_sensitive=False,
.. versionchanged:: 2.6
The `attribute` parameter was added.
"""
- if not case_sensitive:
- def sort_func(item):
- if isinstance(item, string_types):
- item = item.lower()
- return item
- else:
- sort_func = None
- if attribute is not None:
- getter = make_attrgetter(environment, attribute)
- def sort_func(item, processor=sort_func or (lambda x: x)):
- return processor(getter(item))
- return sorted(value, key=sort_func, reverse=reverse)
+ key_func = make_attrgetter(
+ environment, attribute,
+ postprocess=ignore_case if not case_sensitive else None
+ )
+ return sorted(value, key=key_func, reverse=reverse)
+
+
+@environmentfilter
+def do_unique(environment, value, case_sensitive=False, attribute=None):
+ """Returns a list of unique items from the the given iterable.
+
+ .. sourcecode:: jinja
+
+ {{ ['foo', 'bar', 'foobar', 'FooBar']|unique }}
+ -> ['foo', 'bar', 'foobar']
+
+ The unique items are yielded in the same order as their first occurrence in
+ the iterable passed to the filter.
+
+ :param case_sensitive: Treat upper and lower case strings as distinct.
+ :param attribute: Filter objects with unique values for this attribute.
+ """
+ getter = make_attrgetter(
+ environment, attribute,
+ postprocess=ignore_case if not case_sensitive else None
+ )
+ seen = set()
+
+ for item in value:
+ key = getter(item)
+
+ if key not in seen:
+ seen.add(key)
+ yield item
+
+
+def _min_or_max(environment, value, func, case_sensitive, attribute):
+ it = iter(value)
+
+ try:
+ first = next(it)
+ except StopIteration:
+ return environment.undefined('No aggregated item, sequence was empty.')
+
+ key_func = make_attrgetter(
+ environment, attribute,
+ ignore_case if not case_sensitive else None
+ )
+ return func(chain([first], it), key=key_func)
+
+
+@environmentfilter
+def do_min(environment, value, case_sensitive=False, attribute=None):
+ """Return the smallest item from the sequence.
+
+ .. sourcecode:: jinja
+
+ {{ [1, 2, 3]|min }}
+ -> 1
+
+ :param case_sensitive: Treat upper and lower case strings as distinct.
+ :param attribute: Get the object with the max value of this attribute.
+ """
+ return _min_or_max(environment, value, min, case_sensitive, attribute)
+
+
+@environmentfilter
+def do_max(environment, value, case_sensitive=False, attribute=None):
+ """Return the largest item from the sequence.
+
+ .. sourcecode:: jinja
+
+ {{ [1, 2, 3]|max }}
+ -> 3
+
+ :param case_sensitive: Treat upper and lower case strings as distinct.
+ :param attribute: Get the object with the max value of this attribute.
+ """
+ return _min_or_max(environment, value, max, case_sensitive, attribute)
def do_default(value, default_value=u'', boolean=False):
@@ -360,13 +447,13 @@ def do_last(environment, seq):
return environment.undefined('No last item, sequence was empty.')
-@environmentfilter
-def do_random(environment, seq):
+@contextfilter
+def do_random(context, seq):
"""Return a random item from the sequence."""
try:
- return choice(seq)
+ return random.choice(seq)
except IndexError:
- return environment.undefined('No random item, sequence was empty.')
+ return context.environment.undefined('No random item, sequence was empty.')
def do_filesizeformat(value, binary=False):
@@ -410,7 +497,7 @@ def do_pprint(value, verbose=False):
@evalcontextfilter
def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False,
- target=None):
+ target=None, rel=None):
"""Converts URLs in plain text into clickable links.
If you pass the filter an additional integer it will shorten the urls
@@ -432,55 +519,95 @@ def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False,
.. versionchanged:: 2.8+
The *target* parameter was added.
"""
- rv = urlize(value, trim_url_limit, nofollow, target)
+ policies = eval_ctx.environment.policies
+ rel = set((rel or '').split() or [])
+ if nofollow:
+ rel.add('nofollow')
+ rel.update((policies['urlize.rel'] or '').split())
+ if target is None:
+ target = policies['urlize.target']
+ rel = ' '.join(sorted(rel)) or None
+ rv = urlize(value, trim_url_limit, rel=rel, target=target)
if eval_ctx.autoescape:
rv = Markup(rv)
return rv
-def do_indent(s, width=4, indentfirst=False):
- """Return a copy of the passed string, each line indented by
- 4 spaces. The first line is not indented. If you want to
- change the number of spaces or indent the first line too
- you can pass additional parameters to the filter:
+def do_indent(
+ s, width=4, first=False, blank=False, indentfirst=None
+):
+ """Return a copy of the string with each line indented by 4 spaces. The
+ first line and blank lines are not indented by default.
- .. sourcecode:: jinja
+ :param width: Number of spaces to indent by.
+ :param first: Don't skip indenting the first line.
+ :param blank: Don't skip indenting empty lines.
- {{ mytext|indent(2, true) }}
- indent by two spaces and indent the first line too.
+ .. versionchanged:: 2.10
+ Blank lines are not indented by default.
+
+ Rename the ``indentfirst`` argument to ``first``.
"""
+ if indentfirst is not None:
+ warnings.warn(DeprecationWarning(
+ 'The "indentfirst" argument is renamed to "first".'
+ ), stacklevel=2)
+ first = indentfirst
+
+ s += u'\n' # this quirk is necessary for splitlines method
indention = u' ' * width
- rv = (u'\n' + indention).join(s.splitlines())
- if indentfirst:
+
+ if blank:
+ rv = (u'\n' + indention).join(s.splitlines())
+ else:
+ lines = s.splitlines()
+ rv = lines.pop(0)
+
+ if lines:
+ rv += u'\n' + u'\n'.join(
+ indention + line if line else line for line in lines
+ )
+
+ if first:
rv = indention + rv
+
return rv
-def do_truncate(s, length=255, killwords=False, end='...'):
+@environmentfilter
+def do_truncate(env, s, length=255, killwords=False, end='...', leeway=None):
"""Return a truncated copy of the string. The length is specified
with the first parameter which defaults to ``255``. If the second
parameter is ``true`` the filter will cut the text at length. Otherwise
it will discard the last word. If the text was in fact
truncated it will append an ellipsis sign (``"..."``). If you want a
different ellipsis sign than ``"..."`` you can specify it using the
- third parameter.
+ third parameter. Strings that only exceed the length by the tolerance
+ margin given in the fourth parameter will not be truncated.
.. sourcecode:: jinja
- {{ "foo bar baz"|truncate(9) }}
- -> "foo ..."
- {{ "foo bar baz"|truncate(9, True) }}
+ {{ "foo bar baz qux"|truncate(9) }}
+ -> "foo..."
+ {{ "foo bar baz qux"|truncate(9, True) }}
-> "foo ba..."
+ {{ "foo bar baz qux"|truncate(11) }}
+ -> "foo bar baz qux"
+ {{ "foo bar baz qux"|truncate(11, False, '...', 0) }}
+ -> "foo bar..."
+ The default leeway on newer Jinja2 versions is 5 and was 0 before but
+ can be reconfigured globally.
"""
- if len(s) <= length:
+ if leeway is None:
+ leeway = env.policies['truncate.leeway']
+ assert length >= len(end), 'expected length >= %s, got %s' % (len(end), length)
+ assert leeway >= 0, 'expected leeway >= 0, got %s' % leeway
+ if len(s) <= length + leeway:
return s
- elif killwords:
+ if killwords:
return s[:length - len(end)] + end
-
result = s[:length - len(end)].rsplit(' ', 1)[0]
- if len(result) < length:
- result += ' '
return result + end
@@ -518,9 +645,12 @@ def do_int(value, default=0, base=10):
can also override the default base (10) in the second
parameter, which handles input with prefixes such as
0b, 0o and 0x for bases 2, 8 and 16 respectively.
+ The base is ignored for decimal numbers and non-string values.
"""
try:
- return int(value, base)
+ if isinstance(value, string_types):
+ return int(value, base)
+ return int(value)
except (TypeError, ValueError):
# this quirk is necessary so that "42.23"|int gives 42.
try:
@@ -669,6 +799,15 @@ def do_round(value, precision=0, method='common'):
return func(value * (10 ** precision)) / (10 ** precision)
+# Use a regular tuple repr here. This is what we did in the past and we
+# really want to hide this custom type as much as possible. In particular
+# we do not want to accidentally expose an auto generated repr in case
+# people start to print this out in comments or something similar for
+# debugging.
+_GroupTuple = namedtuple('_GroupTuple', ['grouper', 'list'])
+_GroupTuple.__repr__ = tuple.__repr__
+_GroupTuple.__str__ = tuple.__str__
+
@environmentfilter
def do_groupby(environment, value, attribute):
"""Group a sequence of objects by a common attribute.
@@ -709,17 +848,8 @@ def do_groupby(environment, value, attribute):
attribute of another attribute.
"""
expr = make_attrgetter(environment, attribute)
- return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)))
-
-
-class _GroupTuple(tuple):
- __slots__ = ()
- grouper = property(itemgetter(0))
- list = property(itemgetter(1))
-
- def __new__(cls, xxx_todo_changeme):
- (key, value) = xxx_todo_changeme
- return tuple.__new__(cls, (key, list(value)))
+ return [_GroupTuple(key, list(values)) for key, values
+ in groupby(sorted(value, key=expr), expr)]
@environmentfilter
@@ -827,24 +957,7 @@ def do_map(*args, **kwargs):
.. versionadded:: 2.7
"""
- context = args[0]
- seq = args[1]
-
- if len(args) == 2 and 'attribute' in kwargs:
- attribute = kwargs.pop('attribute')
- if kwargs:
- raise FilterArgumentError('Unexpected keyword argument %r' %
- next(iter(kwargs)))
- func = make_attrgetter(context.environment, attribute)
- else:
- try:
- name = args[2]
- args = args[3:]
- except LookupError:
- raise FilterArgumentError('map requires a filter argument')
- func = lambda item: context.environment.call_filter(
- name, item, args, kwargs, context=context)
-
+ seq, func = prepare_map(args, kwargs)
if seq:
for item in seq:
yield func(item)
@@ -852,8 +965,10 @@ def do_map(*args, **kwargs):
@contextfilter
def do_select(*args, **kwargs):
- """Filters a sequence of objects by applying a test to the object and only
- selecting the ones with the test succeeding.
+ """Filters a sequence of objects by applying a test to each object,
+ and only selecting the objects with the test succeeding.
+
+ If no test is specified, each object will be evaluated as a boolean.
Example usage:
@@ -861,16 +976,21 @@ def do_select(*args, **kwargs):
{{ numbers|select("odd") }}
{{ numbers|select("odd") }}
+ {{ numbers|select("divisibleby", 3) }}
+ {{ numbers|select("lessthan", 42) }}
+ {{ strings|select("equalto", "mystring") }}
.. versionadded:: 2.7
"""
- return _select_or_reject(args, kwargs, lambda x: x, False)
+ return select_or_reject(args, kwargs, lambda x: x, False)
@contextfilter
def do_reject(*args, **kwargs):
- """Filters a sequence of objects by applying a test to the object and
- rejecting the ones with the test succeeding.
+ """Filters a sequence of objects by applying a test to each object,
+ and rejecting the objects with the test succeeding.
+
+ If no test is specified, each object will be evaluated as a boolean.
Example usage:
@@ -880,13 +1000,17 @@ def do_reject(*args, **kwargs):
.. versionadded:: 2.7
"""
- return _select_or_reject(args, kwargs, lambda x: not x, False)
+ return select_or_reject(args, kwargs, lambda x: not x, False)
@contextfilter
def do_selectattr(*args, **kwargs):
- """Filters a sequence of objects by applying a test to an attribute of an
- object and only selecting the ones with the test succeeding.
+ """Filters a sequence of objects by applying a test to the specified
+ attribute of each object, and only selecting the objects with the
+ test succeeding.
+
+ If no test is specified, the attribute's value will be evaluated as
+ a boolean.
Example usage:
@@ -897,13 +1021,17 @@ def do_selectattr(*args, **kwargs):
.. versionadded:: 2.7
"""
- return _select_or_reject(args, kwargs, lambda x: x, True)
+ return select_or_reject(args, kwargs, lambda x: x, True)
@contextfilter
def do_rejectattr(*args, **kwargs):
- """Filters a sequence of objects by applying a test to an attribute of an
- object or the attribute and rejecting the ones with the test succeeding.
+ """Filters a sequence of objects by applying a test to the specified
+ attribute of each object, and rejecting the objects with the test
+ succeeding.
+
+ If no test is specified, the attribute's value will be evaluated as
+ a boolean.
.. sourcecode:: jinja
@@ -912,10 +1040,67 @@ def do_rejectattr(*args, **kwargs):
.. versionadded:: 2.7
"""
- return _select_or_reject(args, kwargs, lambda x: not x, True)
+ return select_or_reject(args, kwargs, lambda x: not x, True)
+
+
+@evalcontextfilter
+def do_tojson(eval_ctx, value, indent=None):
+ """Dumps a structure to JSON so that it's safe to use in ``<script>``
+ tags. It accepts the same arguments and returns a JSON string. Note that
+ this is available in templates through the ``|tojson`` filter which will
+ also mark the result as safe. Due to how this function escapes certain
+ characters this is safe even if used outside of ``<script>`` tags.
+
+ The following characters are escaped in strings:
+
+ - ``<``
+ - ``>``
+ - ``&``
+ - ``'``
+
+ This makes it safe to embed such strings in any place in HTML with the
+ notable exception of double quoted attributes. In that case single
+ quote your attributes or HTML escape it in addition.
+
+ The indent parameter can be used to enable pretty printing. Set it to
+ the number of spaces that the structures should be indented with.
+
+ Note that this filter is for use in HTML contexts only.
+
+ .. versionadded:: 2.9
+ """
+ policies = eval_ctx.environment.policies
+ dumper = policies['json.dumps_function']
+ options = policies['json.dumps_kwargs']
+ if indent is not None:
+ options = dict(options)
+ options['indent'] = indent
+ return htmlsafe_json_dumps(value, dumper=dumper, **options)
+
+
+def prepare_map(args, kwargs):
+ context = args[0]
+ seq = args[1]
+
+ if len(args) == 2 and 'attribute' in kwargs:
+ attribute = kwargs.pop('attribute')
+ if kwargs:
+ raise FilterArgumentError('Unexpected keyword argument %r' %
+ next(iter(kwargs)))
+ func = make_attrgetter(context.environment, attribute)
+ else:
+ try:
+ name = args[2]
+ args = args[3:]
+ except LookupError:
+ raise FilterArgumentError('map requires a filter argument')
+ func = lambda item: context.environment.call_filter(
+ name, item, args, kwargs, context=context)
+ return seq, func
-def _select_or_reject(args, kwargs, modfunc, lookup_attr):
+
+def prepare_select_or_reject(args, kwargs, modfunc, lookup_attr):
context = args[0]
seq = args[1]
if lookup_attr:
@@ -937,9 +1122,14 @@ def _select_or_reject(args, kwargs, modfunc, lookup_attr):
except LookupError:
func = bool
+ return seq, lambda item: modfunc(func(transfunc(item)))
+
+
+def select_or_reject(args, kwargs, modfunc, lookup_attr):
+ seq, func = prepare_select_or_reject(args, kwargs, modfunc, lookup_attr)
if seq:
for item in seq:
- if modfunc(func(transfunc(item))):
+ if func(item):
yield item
@@ -969,6 +1159,8 @@ FILTERS = {
'list': do_list,
'lower': do_lower,
'map': do_map,
+ 'min': do_min,
+ 'max': do_max,
'pprint': do_pprint,
'random': do_random,
'reject': do_reject,
@@ -987,10 +1179,12 @@ FILTERS = {
'title': do_title,
'trim': do_trim,
'truncate': do_truncate,
+ 'unique': do_unique,
'upper': do_upper,
'urlencode': do_urlencode,
'urlize': do_urlize,
'wordcount': do_wordcount,
'wordwrap': do_wordwrap,
'xmlattr': do_xmlattr,
+ 'tojson': do_tojson,
}
diff --git a/third_party/jinja2/get_jinja2.sh b/third_party/jinja2/get_jinja2.sh
index 0018349ebc..bc6c4c3068 100755
--- a/third_party/jinja2/get_jinja2.sh
+++ b/third_party/jinja2/get_jinja2.sh
@@ -7,8 +7,8 @@
# Download page:
# https://pypi.python.org/pypi/Jinja2
PACKAGE='Jinja2'
-VERSION='2.8'
-SRC_URL='https://pypi.python.org/packages/f2/2f/0b98b06a345a761bec91a079ccae392d282690c2d8272e708f4d10829e22/Jinja2-2.8.tar.gz'
+VERSION='2.10'
+SRC_URL='https://pypi.python.org/packages/56/e6/332789f295cf22308386cf5bbd1f4e00ed11484299c5d7383378cf48ba47/Jinja2-2.10.tar.gz'
PACKAGE_DIR='jinja2'
CHROMIUM_FILES="README.chromium OWNERS get_jinja2.sh"
@@ -120,3 +120,17 @@ mv "$INSTALL_DIR" "$OLD_DIR"
mv "$PACKAGE_DIR" "$INSTALL_DIR"
cd "$INSTALL_DIR"
rm -fr "$OLD_DIR"
+
+# Generating jinja2.gni
+cat > jinja2.gni <<EOF
+# DO NOT EDIT
+# This is generated from get_jinja2.sh.
+jinja2_sources = [
+EOF
+
+for i in $(LC_COLLATE=C ls *.py)
+do
+ echo " \"//third_party/jinja2/${i}\"," >> jinja2.gni
+done
+
+echo "]" >> jinja2.gni
diff --git a/third_party/jinja2/idtracking.py b/third_party/jinja2/idtracking.py
new file mode 100644
index 0000000000..491bfe0836
--- /dev/null
+++ b/third_party/jinja2/idtracking.py
@@ -0,0 +1,286 @@
+from jinja2.visitor import NodeVisitor
+from jinja2._compat import iteritems
+
+
+VAR_LOAD_PARAMETER = 'param'
+VAR_LOAD_RESOLVE = 'resolve'
+VAR_LOAD_ALIAS = 'alias'
+VAR_LOAD_UNDEFINED = 'undefined'
+
+
+def find_symbols(nodes, parent_symbols=None):
+ sym = Symbols(parent=parent_symbols)
+ visitor = FrameSymbolVisitor(sym)
+ for node in nodes:
+ visitor.visit(node)
+ return sym
+
+
+def symbols_for_node(node, parent_symbols=None):
+ sym = Symbols(parent=parent_symbols)
+ sym.analyze_node(node)
+ return sym
+
+
+class Symbols(object):
+
+ def __init__(self, parent=None, level=None):
+ if level is None:
+ if parent is None:
+ level = 0
+ else:
+ level = parent.level + 1
+ self.level = level
+ self.parent = parent
+ self.refs = {}
+ self.loads = {}
+ self.stores = set()
+
+ def analyze_node(self, node, **kwargs):
+ visitor = RootVisitor(self)
+ visitor.visit(node, **kwargs)
+
+ def _define_ref(self, name, load=None):
+ ident = 'l_%d_%s' % (self.level, name)
+ self.refs[name] = ident
+ if load is not None:
+ self.loads[ident] = load
+ return ident
+
+ def find_load(self, target):
+ if target in self.loads:
+ return self.loads[target]
+ if self.parent is not None:
+ return self.parent.find_load(target)
+
+ def find_ref(self, name):
+ if name in self.refs:
+ return self.refs[name]
+ if self.parent is not None:
+ return self.parent.find_ref(name)
+
+ def ref(self, name):
+ rv = self.find_ref(name)
+ if rv is None:
+ raise AssertionError('Tried to resolve a name to a reference that '
+ 'was unknown to the frame (%r)' % name)
+ return rv
+
+ def copy(self):
+ rv = object.__new__(self.__class__)
+ rv.__dict__.update(self.__dict__)
+ rv.refs = self.refs.copy()
+ rv.loads = self.loads.copy()
+ rv.stores = self.stores.copy()
+ return rv
+
+ def store(self, name):
+ self.stores.add(name)
+
+ # If we have not see the name referenced yet, we need to figure
+ # out what to set it to.
+ if name not in self.refs:
+ # If there is a parent scope we check if the name has a
+ # reference there. If it does it means we might have to alias
+ # to a variable there.
+ if self.parent is not None:
+ outer_ref = self.parent.find_ref(name)
+ if outer_ref is not None:
+ self._define_ref(name, load=(VAR_LOAD_ALIAS, outer_ref))
+ return
+
+ # Otherwise we can just set it to undefined.
+ self._define_ref(name, load=(VAR_LOAD_UNDEFINED, None))
+
+ def declare_parameter(self, name):
+ self.stores.add(name)
+ return self._define_ref(name, load=(VAR_LOAD_PARAMETER, None))
+
+ def load(self, name):
+ target = self.find_ref(name)
+ if target is None:
+ self._define_ref(name, load=(VAR_LOAD_RESOLVE, name))
+
+ def branch_update(self, branch_symbols):
+ stores = {}
+ for branch in branch_symbols:
+ for target in branch.stores:
+ if target in self.stores:
+ continue
+ stores[target] = stores.get(target, 0) + 1
+
+ for sym in branch_symbols:
+ self.refs.update(sym.refs)
+ self.loads.update(sym.loads)
+ self.stores.update(sym.stores)
+
+ for name, branch_count in iteritems(stores):
+ if branch_count == len(branch_symbols):
+ continue
+ target = self.find_ref(name)
+ assert target is not None, 'should not happen'
+
+ if self.parent is not None:
+ outer_target = self.parent.find_ref(name)
+ if outer_target is not None:
+ self.loads[target] = (VAR_LOAD_ALIAS, outer_target)
+ continue
+ self.loads[target] = (VAR_LOAD_RESOLVE, name)
+
+ def dump_stores(self):
+ rv = {}
+ node = self
+ while node is not None:
+ for name in node.stores:
+ if name not in rv:
+ rv[name] = self.find_ref(name)
+ node = node.parent
+ return rv
+
+ def dump_param_targets(self):
+ rv = set()
+ node = self
+ while node is not None:
+ for target, (instr, _) in iteritems(self.loads):
+ if instr == VAR_LOAD_PARAMETER:
+ rv.add(target)
+ node = node.parent
+ return rv
+
+
+class RootVisitor(NodeVisitor):
+
+ def __init__(self, symbols):
+ self.sym_visitor = FrameSymbolVisitor(symbols)
+
+ def _simple_visit(self, node, **kwargs):
+ for child in node.iter_child_nodes():
+ self.sym_visitor.visit(child)
+
+ visit_Template = visit_Block = visit_Macro = visit_FilterBlock = \
+ visit_Scope = visit_If = visit_ScopedEvalContextModifier = \
+ _simple_visit
+
+ def visit_AssignBlock(self, node, **kwargs):
+ for child in node.body:
+ self.sym_visitor.visit(child)
+
+ def visit_CallBlock(self, node, **kwargs):
+ for child in node.iter_child_nodes(exclude=('call',)):
+ self.sym_visitor.visit(child)
+
+ def visit_OverlayScope(self, node, **kwargs):
+ for child in node.body:
+ self.sym_visitor.visit(child)
+
+ def visit_For(self, node, for_branch='body', **kwargs):
+ if for_branch == 'body':
+ self.sym_visitor.visit(node.target, store_as_param=True)
+ branch = node.body
+ elif for_branch == 'else':
+ branch = node.else_
+ elif for_branch == 'test':
+ self.sym_visitor.visit(node.target, store_as_param=True)
+ if node.test is not None:
+ self.sym_visitor.visit(node.test)
+ return
+ else:
+ raise RuntimeError('Unknown for branch')
+ for item in branch or ():
+ self.sym_visitor.visit(item)
+
+ def visit_With(self, node, **kwargs):
+ for target in node.targets:
+ self.sym_visitor.visit(target)
+ for child in node.body:
+ self.sym_visitor.visit(child)
+
+ def generic_visit(self, node, *args, **kwargs):
+ raise NotImplementedError('Cannot find symbols for %r' %
+ node.__class__.__name__)
+
+
+class FrameSymbolVisitor(NodeVisitor):
+ """A visitor for `Frame.inspect`."""
+
+ def __init__(self, symbols):
+ self.symbols = symbols
+
+ def visit_Name(self, node, store_as_param=False, **kwargs):
+ """All assignments to names go through this function."""
+ if store_as_param or node.ctx == 'param':
+ self.symbols.declare_parameter(node.name)
+ elif node.ctx == 'store':
+ self.symbols.store(node.name)
+ elif node.ctx == 'load':
+ self.symbols.load(node.name)
+
+ def visit_NSRef(self, node, **kwargs):
+ self.symbols.load(node.name)
+
+ def visit_If(self, node, **kwargs):
+ self.visit(node.test, **kwargs)
+
+ original_symbols = self.symbols
+
+ def inner_visit(nodes):
+ self.symbols = rv = original_symbols.copy()
+ for subnode in nodes:
+ self.visit(subnode, **kwargs)
+ self.symbols = original_symbols
+ return rv
+
+ body_symbols = inner_visit(node.body)
+ elif_symbols = inner_visit(node.elif_)
+ else_symbols = inner_visit(node.else_ or ())
+
+ self.symbols.branch_update([body_symbols, elif_symbols, else_symbols])
+
+ def visit_Macro(self, node, **kwargs):
+ self.symbols.store(node.name)
+
+ def visit_Import(self, node, **kwargs):
+ self.generic_visit(node, **kwargs)
+ self.symbols.store(node.target)
+
+ def visit_FromImport(self, node, **kwargs):
+ self.generic_visit(node, **kwargs)
+ for name in node.names:
+ if isinstance(name, tuple):
+ self.symbols.store(name[1])
+ else:
+ self.symbols.store(name)
+
+ def visit_Assign(self, node, **kwargs):
+ """Visit assignments in the correct order."""
+ self.visit(node.node, **kwargs)
+ self.visit(node.target, **kwargs)
+
+ def visit_For(self, node, **kwargs):
+ """Visiting stops at for blocks. However the block sequence
+ is visited as part of the outer scope.
+ """
+ self.visit(node.iter, **kwargs)
+
+ def visit_CallBlock(self, node, **kwargs):
+ self.visit(node.call, **kwargs)
+
+ def visit_FilterBlock(self, node, **kwargs):
+ self.visit(node.filter, **kwargs)
+
+ def visit_With(self, node, **kwargs):
+ for target in node.values:
+ self.visit(target)
+
+ def visit_AssignBlock(self, node, **kwargs):
+ """Stop visiting at block assigns."""
+ self.visit(node.target, **kwargs)
+
+ def visit_Scope(self, node, **kwargs):
+ """Stop visiting at scopes."""
+
+ def visit_Block(self, node, **kwargs):
+ """Stop visiting at blocks."""
+
+ def visit_OverlayScope(self, node, **kwargs):
+ """Do not visit into overlay scopes."""
diff --git a/third_party/jinja2/jinja2.gni b/third_party/jinja2/jinja2.gni
new file mode 100644
index 0000000000..cef0d48a88
--- /dev/null
+++ b/third_party/jinja2/jinja2.gni
@@ -0,0 +1,31 @@
+# DO NOT EDIT
+# This is generated from get_jinja2.sh.
+jinja2_sources = [
+ "//third_party/jinja2/__init__.py",
+ "//third_party/jinja2/_compat.py",
+ "//third_party/jinja2/_identifier.py",
+ "//third_party/jinja2/asyncfilters.py",
+ "//third_party/jinja2/asyncsupport.py",
+ "//third_party/jinja2/bccache.py",
+ "//third_party/jinja2/compiler.py",
+ "//third_party/jinja2/constants.py",
+ "//third_party/jinja2/debug.py",
+ "//third_party/jinja2/defaults.py",
+ "//third_party/jinja2/environment.py",
+ "//third_party/jinja2/exceptions.py",
+ "//third_party/jinja2/ext.py",
+ "//third_party/jinja2/filters.py",
+ "//third_party/jinja2/idtracking.py",
+ "//third_party/jinja2/lexer.py",
+ "//third_party/jinja2/loaders.py",
+ "//third_party/jinja2/meta.py",
+ "//third_party/jinja2/nativetypes.py",
+ "//third_party/jinja2/nodes.py",
+ "//third_party/jinja2/optimizer.py",
+ "//third_party/jinja2/parser.py",
+ "//third_party/jinja2/runtime.py",
+ "//third_party/jinja2/sandbox.py",
+ "//third_party/jinja2/tests.py",
+ "//third_party/jinja2/utils.py",
+ "//third_party/jinja2/visitor.py",
+]
diff --git a/third_party/jinja2/lexer.py b/third_party/jinja2/lexer.py
index c8dac214ed..6fd135dd5b 100644
--- a/third_party/jinja2/lexer.py
+++ b/third_party/jinja2/lexer.py
@@ -11,18 +11,16 @@
operators we don't allow in templates. On the other hand it separates
template code and python code in expressions.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import re
-
-from operator import itemgetter
from collections import deque
+from operator import itemgetter
+
+from jinja2._compat import implements_iterator, intern, iteritems, text_type
from jinja2.exceptions import TemplateSyntaxError
from jinja2.utils import LRUCache
-from jinja2._compat import iteritems, implements_iterator, text_type, \
- intern, PY2
-
# cache for the lexers. Exists in order to be able to have multiple
# environments with the same lexer
@@ -34,16 +32,25 @@ string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
integer_re = re.compile(r'\d+')
-# we use the unicode identifier rule if this python version is able
-# to handle unicode identifiers, otherwise the standard ASCII one.
try:
+ # check if this Python supports Unicode identifiers
compile('föö', '<unknown>', 'eval')
except SyntaxError:
- name_re = re.compile(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b')
+ # no Unicode support, use ASCII identifiers
+ name_re = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*')
+ check_ident = False
else:
- from jinja2 import _stringdefs
- name_re = re.compile(r'[%s][%s]*' % (_stringdefs.xid_start,
- _stringdefs.xid_continue))
+ # Unicode support, build a pattern to match valid characters, and set flag
+ # to use str.isidentifier to validate during lexing
+ from jinja2 import _identifier
+ name_re = re.compile(r'[\w{0}]+'.format(_identifier.pattern))
+ check_ident = True
+ # remove the pattern from memory after building the regex
+ import sys
+ del sys.modules['jinja2._identifier']
+ import jinja2
+ del jinja2._identifier
+ del _identifier
float_re = re.compile(r'(?<!\.)\d+\.\d+')
newline_re = re.compile(r'(\r\n|\r|\n)')
@@ -288,7 +295,7 @@ class TokenStreamIterator(object):
@implements_iterator
class TokenStream(object):
- """A token stream is an iterable that yields :class:`Token`\s. The
+ """A token stream is an iterable that yields :class:`Token`\\s. The
parser however does not iterate over it but calls :meth:`next` to go
one token ahead. The current active token is stored as :attr:`current`.
"""
@@ -340,7 +347,10 @@ class TokenStream(object):
return self.next_if(expr) is not None
def __next__(self):
- """Go one token ahead and return the old one"""
+ """Go one token ahead and return the old one.
+
+ Use the built-in :func:`next` instead of calling this directly.
+ """
rv = self.current
if self._pushed:
self.current = self._pushed.popleft()
@@ -500,7 +510,7 @@ class Lexer(object):
],
# blocks
TOKEN_BLOCK_BEGIN: [
- (c('(?:\-%s\s*|%s)%s' % (
+ (c(r'(?:\-%s\s*|%s)%s' % (
e(environment.block_end_string),
e(environment.block_end_string),
block_suffix_re
@@ -508,14 +518,14 @@ class Lexer(object):
] + tag_rules,
# variables
TOKEN_VARIABLE_BEGIN: [
- (c('\-%s\s*|%s' % (
+ (c(r'\-%s\s*|%s' % (
e(environment.variable_end_string),
e(environment.variable_end_string)
)), TOKEN_VARIABLE_END, '#pop')
] + tag_rules,
# raw block
TOKEN_RAW_BEGIN: [
- (c('(.*?)((?:\s*%s\-|%s)\s*endraw\s*(?:\-%s\s*|%s%s))' % (
+ (c(r'(.*?)((?:\s*%s\-|%s)\s*endraw\s*(?:\-%s\s*|%s%s))' % (
e(environment.block_start_string),
block_prefix_re,
e(environment.block_end_string),
@@ -565,6 +575,10 @@ class Lexer(object):
token = value
elif token == 'name':
value = str(value)
+ if check_ident and not value.isidentifier():
+ raise TemplateSyntaxError(
+ 'Invalid character in identifier',
+ lineno, name, filename)
elif token == 'string':
# try to unescape string
try:
@@ -574,15 +588,6 @@ class Lexer(object):
except Exception as e:
msg = str(e).split(':')[-1].strip()
raise TemplateSyntaxError(msg, lineno, name, filename)
- # if we can express it as bytestring (ascii only)
- # we do that for support of semi broken APIs
- # as datetime.datetime.strftime. On python 3 this
- # call becomes a noop thanks to 2to3
- if PY2:
- try:
- value = value.encode('ascii')
- except UnicodeError:
- pass
elif token == 'integer':
value = int(value)
elif token == 'float':
diff --git a/third_party/jinja2/loaders.py b/third_party/jinja2/loaders.py
index 44aa3925a2..4c79793760 100644
--- a/third_party/jinja2/loaders.py
+++ b/third_party/jinja2/loaders.py
@@ -5,7 +5,7 @@
Jinja loader classes.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import os
@@ -351,7 +351,7 @@ class PrefixLoader(BaseLoader):
try:
return loader.get_source(environment, name)
except TemplateNotFound:
- # re-raise the exception with the correct fileame here.
+ # re-raise the exception with the correct filename here.
# (the one that includes the prefix)
raise TemplateNotFound(template)
@@ -361,7 +361,7 @@ class PrefixLoader(BaseLoader):
try:
return loader.load(environment, local_name, globals)
except TemplateNotFound:
- # re-raise the exception with the correct fileame here.
+ # re-raise the exception with the correct filename here.
# (the one that includes the prefix)
raise TemplateNotFound(name)
diff --git a/third_party/jinja2/meta.py b/third_party/jinja2/meta.py
index 3dbab7c22d..7421914f77 100644
--- a/third_party/jinja2/meta.py
+++ b/third_party/jinja2/meta.py
@@ -6,12 +6,12 @@
This module implements various functions that exposes information about
templates that might be interesting for various kinds of applications.
- :copyright: (c) 2010 by the Jinja Team, see AUTHORS for more details.
+ :copyright: (c) 2017 by the Jinja Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from jinja2 import nodes
from jinja2.compiler import CodeGenerator
-from jinja2._compat import string_types
+from jinja2._compat import string_types, iteritems
class TrackingCodeGenerator(CodeGenerator):
@@ -25,9 +25,12 @@ class TrackingCodeGenerator(CodeGenerator):
def write(self, x):
"""Don't write."""
- def pull_locals(self, frame):
+ def enter_frame(self, frame):
"""Remember all undeclared identifiers."""
- self.undeclared_identifiers.update(frame.identifiers.undeclared)
+ CodeGenerator.enter_frame(self, frame)
+ for _, (action, param) in iteritems(frame.symbols.loads):
+ if action == 'resolve':
+ self.undeclared_identifiers.add(param)
def find_undeclared_variables(ast):
diff --git a/third_party/jinja2/nativetypes.py b/third_party/jinja2/nativetypes.py
new file mode 100644
index 0000000000..fe17e4138d
--- /dev/null
+++ b/third_party/jinja2/nativetypes.py
@@ -0,0 +1,220 @@
+import sys
+from ast import literal_eval
+from itertools import islice, chain
+from jinja2 import nodes
+from jinja2._compat import text_type
+from jinja2.compiler import CodeGenerator, has_safe_repr
+from jinja2.environment import Environment, Template
+from jinja2.utils import concat, escape
+
+
+def native_concat(nodes):
+ """Return a native Python type from the list of compiled nodes. If the
+ result is a single node, its value is returned. Otherwise, the nodes are
+ concatenated as strings. If the result can be parsed with
+ :func:`ast.literal_eval`, the parsed value is returned. Otherwise, the
+ string is returned.
+ """
+ head = list(islice(nodes, 2))
+
+ if not head:
+ return None
+
+ if len(head) == 1:
+ out = head[0]
+ else:
+ out = u''.join([text_type(v) for v in chain(head, nodes)])
+
+ try:
+ return literal_eval(out)
+ except (ValueError, SyntaxError, MemoryError):
+ return out
+
+
+class NativeCodeGenerator(CodeGenerator):
+ """A code generator which avoids injecting ``to_string()`` calls around the
+ internal code Jinja uses to render templates.
+ """
+
+ def visit_Output(self, node, frame):
+ """Same as :meth:`CodeGenerator.visit_Output`, but do not call
+ ``to_string`` on output nodes in generated code.
+ """
+ if self.has_known_extends and frame.require_output_check:
+ return
+
+ finalize = self.environment.finalize
+ finalize_context = getattr(finalize, 'contextfunction', False)
+ finalize_eval = getattr(finalize, 'evalcontextfunction', False)
+ finalize_env = getattr(finalize, 'environmentfunction', False)
+
+ if finalize is not None:
+ if finalize_context or finalize_eval:
+ const_finalize = None
+ elif finalize_env:
+ def const_finalize(x):
+ return finalize(self.environment, x)
+ else:
+ const_finalize = finalize
+ else:
+ def const_finalize(x):
+ return x
+
+ # If we are inside a frame that requires output checking, we do so.
+ outdent_later = False
+
+ if frame.require_output_check:
+ self.writeline('if parent_template is None:')
+ self.indent()
+ outdent_later = True
+
+ # Try to evaluate as many chunks as possible into a static string at
+ # compile time.
+ body = []
+
+ for child in node.nodes:
+ try:
+ if const_finalize is None:
+ raise nodes.Impossible()
+
+ const = child.as_const(frame.eval_ctx)
+ if not has_safe_repr(const):
+ raise nodes.Impossible()
+ except nodes.Impossible:
+ body.append(child)
+ continue
+
+ # the frame can't be volatile here, because otherwise the as_const
+ # function would raise an Impossible exception at that point
+ try:
+ if frame.eval_ctx.autoescape:
+ if hasattr(const, '__html__'):
+ const = const.__html__()
+ else:
+ const = escape(const)
+
+ const = const_finalize(const)
+ except Exception:
+ # if something goes wrong here we evaluate the node at runtime
+ # for easier debugging
+ body.append(child)
+ continue
+
+ if body and isinstance(body[-1], list):
+ body[-1].append(const)
+ else:
+ body.append([const])
+
+ # if we have less than 3 nodes or a buffer we yield or extend/append
+ if len(body) < 3 or frame.buffer is not None:
+ if frame.buffer is not None:
+ # for one item we append, for more we extend
+ if len(body) == 1:
+ self.writeline('%s.append(' % frame.buffer)
+ else:
+ self.writeline('%s.extend((' % frame.buffer)
+
+ self.indent()
+
+ for item in body:
+ if isinstance(item, list):
+ val = repr(native_concat(item))
+
+ if frame.buffer is None:
+ self.writeline('yield ' + val)
+ else:
+ self.writeline(val + ',')
+ else:
+ if frame.buffer is None:
+ self.writeline('yield ', item)
+ else:
+ self.newline(item)
+
+ close = 0
+
+ if finalize is not None:
+ self.write('environment.finalize(')
+
+ if finalize_context:
+ self.write('context, ')
+
+ close += 1
+
+ self.visit(item, frame)
+
+ if close > 0:
+ self.write(')' * close)
+
+ if frame.buffer is not None:
+ self.write(',')
+
+ if frame.buffer is not None:
+ # close the open parentheses
+ self.outdent()
+ self.writeline(len(body) == 1 and ')' or '))')
+
+ # otherwise we create a format string as this is faster in that case
+ else:
+ format = []
+ arguments = []
+
+ for item in body:
+ if isinstance(item, list):
+ format.append(native_concat(item).replace('%', '%%'))
+ else:
+ format.append('%s')
+ arguments.append(item)
+
+ self.writeline('yield ')
+ self.write(repr(concat(format)) + ' % (')
+ self.indent()
+
+ for argument in arguments:
+ self.newline(argument)
+ close = 0
+
+ if finalize is not None:
+ self.write('environment.finalize(')
+
+ if finalize_context:
+ self.write('context, ')
+ elif finalize_eval:
+ self.write('context.eval_ctx, ')
+ elif finalize_env:
+ self.write('environment, ')
+
+ close += 1
+
+ self.visit(argument, frame)
+ self.write(')' * close + ', ')
+
+ self.outdent()
+ self.writeline(')')
+
+ if outdent_later:
+ self.outdent()
+
+
+class NativeTemplate(Template):
+ def render(self, *args, **kwargs):
+ """Render the template to produce a native Python type. If the result
+ is a single node, its value is returned. Otherwise, the nodes are
+ concatenated as strings. If the result can be parsed with
+ :func:`ast.literal_eval`, the parsed value is returned. Otherwise, the
+ string is returned.
+ """
+ vars = dict(*args, **kwargs)
+
+ try:
+ return native_concat(self.root_render_func(self.new_context(vars)))
+ except Exception:
+ exc_info = sys.exc_info()
+
+ return self.environment.handle_exception(exc_info, True)
+
+
+class NativeEnvironment(Environment):
+ """An environment that renders templates to native Python types."""
+
+ code_generator_class = NativeCodeGenerator
+ template_class = NativeTemplate
diff --git a/third_party/jinja2/nodes.py b/third_party/jinja2/nodes.py
index d32046ce5c..4d9a01ad8b 100644
--- a/third_party/jinja2/nodes.py
+++ b/third_party/jinja2/nodes.py
@@ -9,7 +9,7 @@
`get_nodes` used by the parser and translator in order to normalize
python and jinja nodes.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import types
@@ -17,7 +17,7 @@ import operator
from collections import deque
from jinja2.utils import Markup
-from jinja2._compat import izip, with_metaclass, text_type
+from jinja2._compat import izip, with_metaclass, text_type, PY2
#: the types we support for context functions
@@ -242,6 +242,35 @@ class Node(with_metaclass(NodeType, object)):
arg in self.fields)
)
+ def dump(self):
+ def _dump(node):
+ if not isinstance(node, Node):
+ buf.append(repr(node))
+ return
+
+ buf.append('nodes.%s(' % node.__class__.__name__)
+ if not node.fields:
+ buf.append(')')
+ return
+ for idx, field in enumerate(node.fields):
+ if idx:
+ buf.append(', ')
+ value = getattr(node, field)
+ if isinstance(value, list):
+ buf.append('[')
+ for idx, item in enumerate(value):
+ if idx:
+ buf.append(', ')
+ _dump(item)
+ buf.append(']')
+ else:
+ _dump(value)
+ buf.append(')')
+ buf = []
+ _dump(self)
+ return ''.join(buf)
+
+
class Stmt(Node):
"""Base node for all statements."""
@@ -285,7 +314,7 @@ class For(Stmt):
class If(Stmt):
"""If `test` is true, `body` is rendered, else `else_`."""
- fields = ('test', 'body', 'else_')
+ fields = ('test', 'body', 'elif_', 'else_')
class Macro(Stmt):
@@ -308,6 +337,15 @@ class FilterBlock(Stmt):
fields = ('body', 'filter')
+class With(Stmt):
+ """Specific node for with statements. In older versions of Jinja the
+ with statement was implemented on the base of the `Scope` node instead.
+
+ .. versionadded:: 2.9.3
+ """
+ fields = ('targets', 'values', 'body')
+
+
class Block(Stmt):
"""A node that represents a block."""
fields = ('name', 'body', 'scoped')
@@ -349,7 +387,7 @@ class Assign(Stmt):
class AssignBlock(Stmt):
"""Assigns a block to a target."""
- fields = ('target', 'body')
+ fields = ('target', 'filter', 'body')
class Expr(Node):
@@ -427,6 +465,18 @@ class Name(Expr):
'True', 'False', 'None')
+class NSRef(Expr):
+ """Reference to a namespace value assignment"""
+ fields = ('name', 'attr')
+
+ def can_assign(self):
+ # We don't need any special checks here; NSRef assignments have a
+ # runtime check to ensure the target is a namespace object which will
+ # have been checked already as it is created using a normal assignment
+ # which goes through a `Name` node.
+ return True
+
+
class Literal(Expr):
"""Baseclass for literals."""
abstract = True
@@ -441,7 +491,14 @@ class Const(Literal):
fields = ('value',)
def as_const(self, eval_ctx=None):
- return self.value
+ rv = self.value
+ if PY2 and type(rv) is text_type and \
+ self.environment.policies['compiler.ascii_str']:
+ try:
+ rv = rv.encode('ascii')
+ except UnicodeError:
+ pass
+ return rv
@classmethod
def from_untrusted(cls, value, lineno=None, environment=None):
@@ -542,6 +599,25 @@ class CondExpr(Expr):
return self.expr2.as_const(eval_ctx)
+def args_as_const(node, eval_ctx):
+ args = [x.as_const(eval_ctx) for x in node.args]
+ kwargs = dict(x.as_const(eval_ctx) for x in node.kwargs)
+
+ if node.dyn_args is not None:
+ try:
+ args.extend(node.dyn_args.as_const(eval_ctx))
+ except Exception:
+ raise Impossible()
+
+ if node.dyn_kwargs is not None:
+ try:
+ kwargs.update(node.dyn_kwargs.as_const(eval_ctx))
+ except Exception:
+ raise Impossible()
+
+ return args, kwargs
+
+
class Filter(Expr):
"""This node applies a filter on an expression. `name` is the name of
the filter, the rest of the fields are the same as for :class:`Call`.
@@ -549,39 +625,43 @@ class Filter(Expr):
If the `node` of a filter is `None` the contents of the last buffer are
filtered. Buffers are created by macros and filter blocks.
"""
+
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
def as_const(self, eval_ctx=None):
eval_ctx = get_eval_context(self, eval_ctx)
+
if eval_ctx.volatile or self.node is None:
raise Impossible()
+
# we have to be careful here because we call filter_ below.
# if this variable would be called filter, 2to3 would wrap the
# call in a list beause it is assuming we are talking about the
# builtin filter function here which no longer returns a list in
# python 3. because of that, do not rename filter_ to filter!
filter_ = self.environment.filters.get(self.name)
+
if filter_ is None or getattr(filter_, 'contextfilter', False):
raise Impossible()
- obj = self.node.as_const(eval_ctx)
- args = [x.as_const(eval_ctx) for x in self.args]
+
+ # We cannot constant handle async filters, so we need to make sure
+ # to not go down this path.
+ if (
+ eval_ctx.environment.is_async
+ and getattr(filter_, 'asyncfiltervariant', False)
+ ):
+ raise Impossible()
+
+ args, kwargs = args_as_const(self, eval_ctx)
+ args.insert(0, self.node.as_const(eval_ctx))
+
if getattr(filter_, 'evalcontextfilter', False):
args.insert(0, eval_ctx)
elif getattr(filter_, 'environmentfilter', False):
args.insert(0, self.environment)
- kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
- if self.dyn_args is not None:
- try:
- args.extend(self.dyn_args.as_const(eval_ctx))
- except Exception:
- raise Impossible()
- if self.dyn_kwargs is not None:
- try:
- kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
- except Exception:
- raise Impossible()
+
try:
- return filter_(obj, *args, **kwargs)
+ return filter_(*args, **kwargs)
except Exception:
raise Impossible()
@@ -590,8 +670,24 @@ class Test(Expr):
"""Applies a test on an expression. `name` is the name of the test, the
rest of the fields are the same as for :class:`Call`.
"""
+
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
+ def as_const(self, eval_ctx=None):
+ test = self.environment.tests.get(self.name)
+
+ if test is None:
+ raise Impossible()
+
+ eval_ctx = get_eval_context(self, eval_ctx)
+ args, kwargs = args_as_const(self, eval_ctx)
+ args.insert(0, self.node.as_const(eval_ctx))
+
+ try:
+ return test(*args, **kwargs)
+ except Exception:
+ raise Impossible()
+
class Call(Expr):
"""Calls an expression. `args` is a list of arguments, `kwargs` a list
@@ -602,38 +698,6 @@ class Call(Expr):
"""
fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- if eval_ctx.volatile:
- raise Impossible()
- obj = self.node.as_const(eval_ctx)
-
- # don't evaluate context functions
- args = [x.as_const(eval_ctx) for x in self.args]
- if isinstance(obj, _context_function_types):
- if getattr(obj, 'contextfunction', False):
- raise Impossible()
- elif getattr(obj, 'evalcontextfunction', False):
- args.insert(0, eval_ctx)
- elif getattr(obj, 'environmentfunction', False):
- args.insert(0, self.environment)
-
- kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
- if self.dyn_args is not None:
- try:
- args.extend(self.dyn_args.as_const(eval_ctx))
- except Exception:
- raise Impossible()
- if self.dyn_kwargs is not None:
- try:
- kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
- except Exception:
- raise Impossible()
- try:
- return obj(*args, **kwargs)
- except Exception:
- raise Impossible()
-
class Getitem(Expr):
"""Get an attribute or item from an expression and prefer the item."""
@@ -701,7 +765,7 @@ class Concat(Expr):
class Compare(Expr):
"""Compares an expression with some other expressions. `ops` must be a
- list of :class:`Operand`\s.
+ list of :class:`Operand`\\s.
"""
fields = ('expr', 'ops')
@@ -894,6 +958,22 @@ class Scope(Stmt):
fields = ('body',)
+class OverlayScope(Stmt):
+ """An overlay scope for extensions. This is a largely unoptimized scope
+ that however can be used to introduce completely arbitrary variables into
+ a sub scope from a dictionary or dictionary like object. The `context`
+ field has to evaluate to a dictionary object.
+
+ Example usage::
+
+ OverlayScope(context=self.call_method('get_context'),
+ body=[...])
+
+ .. versionadded:: 2.10
+ """
+ fields = ('context', 'body')
+
+
class EvalContextModifier(Stmt):
"""Modifies the eval context. For each option that should be modified,
a :class:`Keyword` has to be added to the :attr:`options` list.
diff --git a/third_party/jinja2/optimizer.py b/third_party/jinja2/optimizer.py
index 00eab115e1..65ab3ceb71 100644
--- a/third_party/jinja2/optimizer.py
+++ b/third_party/jinja2/optimizer.py
@@ -13,7 +13,7 @@
The solution would be a second syntax tree that has the scoping rules stored.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD.
"""
from jinja2 import nodes
@@ -32,30 +32,11 @@ class Optimizer(NodeTransformer):
def __init__(self, environment):
self.environment = environment
- def visit_If(self, node):
- """Eliminate dead code."""
- # do not optimize ifs that have a block inside so that it doesn't
- # break super().
- if node.find(nodes.Block) is not None:
- return self.generic_visit(node)
- try:
- val = self.visit(node.test).as_const()
- except nodes.Impossible:
- return self.generic_visit(node)
- if val:
- body = node.body
- else:
- body = node.else_
- result = []
- for node in body:
- result.extend(self.visit_list(node))
- return result
-
- def fold(self, node):
+ def fold(self, node, eval_ctx=None):
"""Do constant folding."""
node = self.generic_visit(node)
try:
- return nodes.Const.from_untrusted(node.as_const(),
+ return nodes.Const.from_untrusted(node.as_const(eval_ctx),
lineno=node.lineno,
environment=self.environment)
except nodes.Impossible:
diff --git a/third_party/jinja2/parser.py b/third_party/jinja2/parser.py
index d24da180ea..ed00d9708e 100644
--- a/third_party/jinja2/parser.py
+++ b/third_party/jinja2/parser.py
@@ -5,7 +5,7 @@
Implements the template parser.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
from jinja2 import nodes
@@ -16,9 +16,18 @@ from jinja2._compat import imap
_statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print',
'macro', 'include', 'from', 'import',
- 'set'])
+ 'set', 'with', 'autoescape'])
_compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq'])
+_math_nodes = {
+ 'add': nodes.Add,
+ 'sub': nodes.Sub,
+ 'mul': nodes.Mul,
+ 'div': nodes.Div,
+ 'floordiv': nodes.FloorDiv,
+ 'mod': nodes.Mod,
+}
+
class Parser(object):
"""This is the central parsing class Jinja2 uses. It's passed to
@@ -167,13 +176,14 @@ class Parser(object):
def parse_set(self):
"""Parse an assign statement."""
lineno = next(self.stream).lineno
- target = self.parse_assign_target()
+ target = self.parse_assign_target(with_namespace=True)
if self.stream.skip_if('assign'):
expr = self.parse_tuple()
return nodes.Assign(target, expr, lineno=lineno)
+ filter_node = self.parse_filter(None)
body = self.parse_statements(('name:endset',),
drop_needle=True)
- return nodes.AssignBlock(target, body, lineno=lineno)
+ return nodes.AssignBlock(target, filter_node, body, lineno=lineno)
def parse_for(self):
"""Parse a for loop."""
@@ -201,20 +211,47 @@ class Parser(object):
node.test = self.parse_tuple(with_condexpr=False)
node.body = self.parse_statements(('name:elif', 'name:else',
'name:endif'))
+ node.elif_ = []
+ node.else_ = []
token = next(self.stream)
if token.test('name:elif'):
- new_node = nodes.If(lineno=self.stream.current.lineno)
- node.else_ = [new_node]
- node = new_node
+ node = nodes.If(lineno=self.stream.current.lineno)
+ result.elif_.append(node)
continue
elif token.test('name:else'):
- node.else_ = self.parse_statements(('name:endif',),
- drop_needle=True)
- else:
- node.else_ = []
+ result.else_ = self.parse_statements(('name:endif',),
+ drop_needle=True)
break
return result
+ def parse_with(self):
+ node = nodes.With(lineno=next(self.stream).lineno)
+ targets = []
+ values = []
+ while self.stream.current.type != 'block_end':
+ lineno = self.stream.current.lineno
+ if targets:
+ self.stream.expect('comma')
+ target = self.parse_assign_target()
+ target.set_ctx('param')
+ targets.append(target)
+ self.stream.expect('assign')
+ values.append(self.parse_expression())
+ node.targets = targets
+ node.values = values
+ node.body = self.parse_statements(('name:endwith',),
+ drop_needle=True)
+ return node
+
+ def parse_autoescape(self):
+ node = nodes.ScopedEvalContextModifier(lineno=next(self.stream).lineno)
+ node.options = [
+ nodes.Keyword('autoescape', self.parse_expression())
+ ]
+ node.body = self.parse_statements(('name:endautoescape',),
+ drop_needle=True)
+ return nodes.Scope([node])
+
def parse_block(self):
node = nodes.Block(lineno=next(self.stream).lineno)
node.name = self.stream.expect('name').value
@@ -297,10 +334,9 @@ class Parser(object):
if parse_context() or self.stream.current.type != 'comma':
break
else:
- break
+ self.stream.expect('name')
if not hasattr(node, 'with_context'):
node.with_context = False
- self.stream.skip_if('comma')
return node
def parse_signature(self, node):
@@ -358,15 +394,21 @@ class Parser(object):
return node
def parse_assign_target(self, with_tuple=True, name_only=False,
- extra_end_rules=None):
+ extra_end_rules=None, with_namespace=False):
"""Parse an assignment target. As Jinja2 allows assignments to
tuples, this function can parse all allowed assignment targets. Per
default assignments to tuples are parsed, that can be disable however
by setting `with_tuple` to `False`. If only assignments to names are
wanted `name_only` can be set to `True`. The `extra_end_rules`
- parameter is forwarded to the tuple parsing function.
+ parameter is forwarded to the tuple parsing function. If
+ `with_namespace` is enabled, a namespace assignment may be parsed.
"""
- if name_only:
+ if with_namespace and self.stream.look().type == 'dot':
+ token = self.stream.expect('name')
+ next(self.stream) # dot
+ attr = self.stream.expect('name')
+ target = nodes.NSRef(token.value, attr.value, lineno=token.lineno)
+ elif name_only:
token = self.stream.expect('name')
target = nodes.Name(token.value, 'store', lineno=token.lineno)
else:
@@ -429,19 +471,19 @@ class Parser(object):
def parse_compare(self):
lineno = self.stream.current.lineno
- expr = self.parse_add()
+ expr = self.parse_math1()
ops = []
while 1:
token_type = self.stream.current.type
if token_type in _compare_operators:
next(self.stream)
- ops.append(nodes.Operand(token_type, self.parse_add()))
+ ops.append(nodes.Operand(token_type, self.parse_math1()))
elif self.stream.skip_if('name:in'):
- ops.append(nodes.Operand('in', self.parse_add()))
+ ops.append(nodes.Operand('in', self.parse_math1()))
elif (self.stream.current.test('name:not') and
self.stream.look().test('name:in')):
self.stream.skip(2)
- ops.append(nodes.Operand('notin', self.parse_add()))
+ ops.append(nodes.Operand('notin', self.parse_math1()))
else:
break
lineno = self.stream.current.lineno
@@ -449,73 +491,35 @@ class Parser(object):
return expr
return nodes.Compare(expr, ops, lineno=lineno)
- def parse_add(self):
- lineno = self.stream.current.lineno
- left = self.parse_sub()
- while self.stream.current.type == 'add':
- next(self.stream)
- right = self.parse_sub()
- left = nodes.Add(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_sub(self):
+ def parse_math1(self):
lineno = self.stream.current.lineno
left = self.parse_concat()
- while self.stream.current.type == 'sub':
+ while self.stream.current.type in ('add', 'sub'):
+ cls = _math_nodes[self.stream.current.type]
next(self.stream)
right = self.parse_concat()
- left = nodes.Sub(left, right, lineno=lineno)
+ left = cls(left, right, lineno=lineno)
lineno = self.stream.current.lineno
return left
def parse_concat(self):
lineno = self.stream.current.lineno
- args = [self.parse_mul()]
+ args = [self.parse_math2()]
while self.stream.current.type == 'tilde':
next(self.stream)
- args.append(self.parse_mul())
+ args.append(self.parse_math2())
if len(args) == 1:
return args[0]
return nodes.Concat(args, lineno=lineno)
- def parse_mul(self):
- lineno = self.stream.current.lineno
- left = self.parse_div()
- while self.stream.current.type == 'mul':
- next(self.stream)
- right = self.parse_div()
- left = nodes.Mul(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_div(self):
- lineno = self.stream.current.lineno
- left = self.parse_floordiv()
- while self.stream.current.type == 'div':
- next(self.stream)
- right = self.parse_floordiv()
- left = nodes.Div(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_floordiv(self):
- lineno = self.stream.current.lineno
- left = self.parse_mod()
- while self.stream.current.type == 'floordiv':
- next(self.stream)
- right = self.parse_mod()
- left = nodes.FloorDiv(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_mod(self):
+ def parse_math2(self):
lineno = self.stream.current.lineno
left = self.parse_pow()
- while self.stream.current.type == 'mod':
+ while self.stream.current.type in ('mul', 'div', 'floordiv', 'mod'):
+ cls = _math_nodes[self.stream.current.type]
next(self.stream)
right = self.parse_pow()
- left = nodes.Mod(left, right, lineno=lineno)
+ left = cls(left, right, lineno=lineno)
lineno = self.stream.current.lineno
return left
@@ -835,7 +839,7 @@ class Parser(object):
'name:and')):
if self.stream.current.test('name:is'):
self.fail('You cannot chain multiple tests with is')
- args = [self.parse_expression()]
+ args = [self.parse_primary()]
else:
args = []
node = nodes.Test(node, name, args, kwargs, dyn_args,
diff --git a/third_party/jinja2/runtime.py b/third_party/jinja2/runtime.py
index 685a12da06..f9d7a6806c 100644
--- a/third_party/jinja2/runtime.py
+++ b/third_party/jinja2/runtime.py
@@ -5,26 +5,29 @@
Runtime helpers.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD.
"""
import sys
from itertools import chain
+from types import MethodType
+
from jinja2.nodes import EvalContext, _context_function_types
from jinja2.utils import Markup, soft_unicode, escape, missing, concat, \
- internalcode, object_type_repr
+ internalcode, object_type_repr, evalcontextfunction, Namespace
from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
TemplateNotFound
from jinja2._compat import imap, text_type, iteritems, \
- implements_iterator, implements_to_string, string_types, PY2
+ implements_iterator, implements_to_string, string_types, PY2, \
+ with_metaclass
# these variables are exported to the template runtime
__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
'TemplateRuntimeError', 'missing', 'concat', 'escape',
'markup_join', 'unicode_join', 'to_string', 'identity',
- 'TemplateNotFound', 'make_logging_undefined']
+ 'TemplateNotFound', 'Namespace']
#: the name of the function that is used to convert something into
#: a string. We can just use the text type here.
@@ -33,6 +36,7 @@ to_string = text_type
#: the identity function. Useful for certain things in the environment
identity = lambda x: x
+_first_iteration = object()
_last_iteration = object()
@@ -67,8 +71,8 @@ def new_context(environment, template_name, blocks, vars=None,
if shared:
parent = dict(parent)
for key, value in iteritems(locals):
- if key[:2] == 'l_' and value is not missing:
- parent[key[2:]] = value
+ if value is not missing:
+ parent[key] = value
return environment.context_class(environment, parent, template_name,
blocks)
@@ -90,7 +94,43 @@ class TemplateReference(object):
)
-class Context(object):
+def _get_func(x):
+ return getattr(x, '__func__', x)
+
+
+class ContextMeta(type):
+
+ def __new__(cls, name, bases, d):
+ rv = type.__new__(cls, name, bases, d)
+ if bases == ():
+ return rv
+
+ resolve = _get_func(rv.resolve)
+ default_resolve = _get_func(Context.resolve)
+ resolve_or_missing = _get_func(rv.resolve_or_missing)
+ default_resolve_or_missing = _get_func(Context.resolve_or_missing)
+
+ # If we have a changed resolve but no changed default or missing
+ # resolve we invert the call logic.
+ if resolve is not default_resolve and \
+ resolve_or_missing is default_resolve_or_missing:
+ rv._legacy_resolve_mode = True
+ elif resolve is default_resolve and \
+ resolve_or_missing is default_resolve_or_missing:
+ rv._fast_resolve_mode = True
+
+ return rv
+
+
+def resolve_or_missing(context, key, missing=missing):
+ if key in context.vars:
+ return context.vars[key]
+ if key in context.parent:
+ return context.parent[key]
+ return missing
+
+
+class Context(with_metaclass(ContextMeta)):
"""The template context holds the variables of a template. It stores the
values passed to the template and also the names the template exports.
Creating instances is neither supported nor useful as it's created
@@ -100,7 +140,7 @@ class Context(object):
The context is immutable. Modifications on :attr:`parent` **must not**
happen and modifications on :attr:`vars` are allowed from generated
template code only. Template filters and global functions marked as
- :func:`contextfunction`\s get the active context passed as first argument
+ :func:`contextfunction`\\s get the active context passed as first argument
and are allowed to access the context read-only.
The template context supports read only dict operations (`get`,
@@ -109,8 +149,10 @@ class Context(object):
method that doesn't fail with a `KeyError` but returns an
:class:`Undefined` object for missing variables.
"""
- __slots__ = ('parent', 'vars', 'environment', 'eval_ctx', 'exported_vars',
- 'name', 'blocks', '__weakref__')
+ # XXX: we want to eventually make this be a deprecation warning and
+ # remove it.
+ _legacy_resolve_mode = False
+ _fast_resolve_mode = False
def __init__(self, environment, parent, name, blocks):
self.parent = parent
@@ -125,6 +167,11 @@ class Context(object):
# from the template.
self.blocks = dict((k, [v]) for k, v in iteritems(blocks))
+ # In case we detect the fast resolve mode we can set up an alias
+ # here that bypasses the legacy code logic.
+ if self._fast_resolve_mode:
+ self.resolve_or_missing = MethodType(resolve_or_missing, self)
+
def super(self, name, current):
"""Render a parent block."""
try:
@@ -150,20 +197,38 @@ class Context(object):
"""Looks up a variable like `__getitem__` or `get` but returns an
:class:`Undefined` object with the name of the name looked up.
"""
- if key in self.vars:
- return self.vars[key]
- if key in self.parent:
- return self.parent[key]
- return self.environment.undefined(name=key)
+ if self._legacy_resolve_mode:
+ rv = resolve_or_missing(self, key)
+ else:
+ rv = self.resolve_or_missing(key)
+ if rv is missing:
+ return self.environment.undefined(name=key)
+ return rv
+
+ def resolve_or_missing(self, key):
+ """Resolves a variable like :meth:`resolve` but returns the
+ special `missing` value if it cannot be found.
+ """
+ if self._legacy_resolve_mode:
+ rv = self.resolve(key)
+ if isinstance(rv, Undefined):
+ rv = missing
+ return rv
+ return resolve_or_missing(self, key)
def get_exported(self):
"""Get a new dict with the exported variables."""
return dict((k, self.vars[k]) for k in self.exported_vars)
def get_all(self):
- """Return a copy of the complete context as dict including the
- exported variables.
+ """Return the complete context as dict including the exported
+ variables. For optimizations reasons this might not return an
+ actual copy so be careful with using it.
"""
+ if not self.vars:
+ return self.parent
+ if not self.parent:
+ return self.vars
return dict(self.parent, **self.vars)
@internalcode
@@ -177,13 +242,14 @@ class Context(object):
__traceback_hide__ = True # noqa
# Allow callable classes to take a context
- fn = __obj.__call__
- for fn_type in ('contextfunction',
- 'evalcontextfunction',
- 'environmentfunction'):
- if hasattr(fn, fn_type):
- __obj = fn
- break
+ if hasattr(__obj, '__call__'):
+ fn = __obj.__call__
+ for fn_type in ('contextfunction',
+ 'evalcontextfunction',
+ 'environmentfunction'):
+ if hasattr(fn, fn_type):
+ __obj = fn
+ break
if isinstance(__obj, _context_function_types):
if getattr(__obj, 'contextfunction', 0):
@@ -200,10 +266,12 @@ class Context(object):
'StopIteration exception')
def derived(self, locals=None):
- """Internal helper function to create a derived context."""
+ """Internal helper function to create a derived context. This is
+ used in situations where the system needs a new context in the same
+ template that is independent.
+ """
context = new_context(self.environment, self.name, {},
- self.parent, True, None, locals)
- context.vars.update(self.vars)
+ self.get_all(), True, None, locals)
context.eval_ctx = self.eval_ctx
context.blocks.update((k, list(v)) for k, v in iteritems(self.blocks))
return context
@@ -232,8 +300,8 @@ class Context(object):
"""Lookup a variable or raise `KeyError` if the variable is
undefined.
"""
- item = self.resolve(key)
- if isinstance(item, Undefined):
+ item = self.resolve_or_missing(key)
+ if item is missing:
raise KeyError(key)
return item
@@ -280,24 +348,20 @@ class BlockReference(object):
return rv
-class LoopContext(object):
+class LoopContextBase(object):
"""A loop context for dynamic iteration."""
- def __init__(self, iterable, recurse=None, depth0=0):
- self._iterator = iter(iterable)
+ _before = _first_iteration
+ _current = _first_iteration
+ _after = _last_iteration
+ _length = None
+
+ def __init__(self, undefined, recurse=None, depth0=0):
+ self._undefined = undefined
self._recurse = recurse
- self._after = self._safe_next()
self.index0 = -1
self.depth0 = depth0
-
- # try to get the length of the iterable early. This must be done
- # here because there are some broken iterators around where there
- # __len__ is the number of iterations left (i'm looking at your
- # listreverseiterator!).
- try:
- self._length = len(iterable)
- except (TypeError, AttributeError):
- self._length = None
+ self._last_checked_value = missing
def cycle(self, *args):
"""Cycles among the arguments with the current loop index."""
@@ -305,6 +369,13 @@ class LoopContext(object):
raise TypeError('no items for cycling given')
return args[self.index0 % len(args)]
+ def changed(self, *value):
+ """Checks whether the value has changed since the last call."""
+ if self._last_checked_value != value:
+ self._last_checked_value = value
+ return True
+ return False
+
first = property(lambda x: x.index0 == 0)
last = property(lambda x: x._after is _last_iteration)
index = property(lambda x: x.index0 + 1)
@@ -312,17 +383,20 @@ class LoopContext(object):
revindex0 = property(lambda x: x.length - x.index)
depth = property(lambda x: x.depth0 + 1)
- def __len__(self):
- return self.length
+ @property
+ def previtem(self):
+ if self._before is _first_iteration:
+ return self._undefined('there is no previous item')
+ return self._before
- def __iter__(self):
- return LoopContextIterator(self)
+ @property
+ def nextitem(self):
+ if self._after is _last_iteration:
+ return self._undefined('there is no next item')
+ return self._after
- def _safe_next(self):
- try:
- return next(self._iterator)
- except StopIteration:
- return _last_iteration
+ def __len__(self):
+ return self.length
@internalcode
def loop(self, iterable):
@@ -336,6 +410,30 @@ class LoopContext(object):
__call__ = loop
del loop
+ def __repr__(self):
+ return '<%s %r/%r>' % (
+ self.__class__.__name__,
+ self.index,
+ self.length
+ )
+
+
+class LoopContext(LoopContextBase):
+
+ def __init__(self, iterable, undefined, recurse=None, depth0=0):
+ LoopContextBase.__init__(self, undefined, recurse, depth0)
+ self._iterator = iter(iterable)
+
+ # try to get the length of the iterable early. This must be done
+ # here because there are some broken iterators around where there
+ # __len__ is the number of iterations left (i'm looking at your
+ # listreverseiterator!).
+ try:
+ self._length = len(iterable)
+ except (TypeError, AttributeError):
+ self._length = None
+ self._after = self._safe_next()
+
@property
def length(self):
if self._length is None:
@@ -349,12 +447,14 @@ class LoopContext(object):
self._length = len(iterable) + iterations_done
return self._length
- def __repr__(self):
- return '<%s %r/%r>' % (
- self.__class__.__name__,
- self.index,
- self.length
- )
+ def __iter__(self):
+ return LoopContextIterator(self)
+
+ def _safe_next(self):
+ try:
+ return next(self._iterator)
+ except StopIteration:
+ return _last_iteration
@implements_iterator
@@ -373,32 +473,65 @@ class LoopContextIterator(object):
ctx.index0 += 1
if ctx._after is _last_iteration:
raise StopIteration()
- next_elem = ctx._after
+ ctx._before = ctx._current
+ ctx._current = ctx._after
ctx._after = ctx._safe_next()
- return next_elem, ctx
+ return ctx._current, ctx
class Macro(object):
"""Wraps a macro function."""
- def __init__(self, environment, func, name, arguments, defaults,
- catch_kwargs, catch_varargs, caller):
+ def __init__(self, environment, func, name, arguments,
+ catch_kwargs, catch_varargs, caller,
+ default_autoescape=None):
self._environment = environment
self._func = func
self._argument_count = len(arguments)
self.name = name
self.arguments = arguments
- self.defaults = defaults
self.catch_kwargs = catch_kwargs
self.catch_varargs = catch_varargs
self.caller = caller
+ self.explicit_caller = 'caller' in arguments
+ if default_autoescape is None:
+ default_autoescape = environment.autoescape
+ self._default_autoescape = default_autoescape
@internalcode
+ @evalcontextfunction
def __call__(self, *args, **kwargs):
+ # This requires a bit of explanation, In the past we used to
+ # decide largely based on compile-time information if a macro is
+ # safe or unsafe. While there was a volatile mode it was largely
+ # unused for deciding on escaping. This turns out to be
+ # problemtic for macros because if a macro is safe or not not so
+ # much depends on the escape mode when it was defined but when it
+ # was used.
+ #
+ # Because however we export macros from the module system and
+ # there are historic callers that do not pass an eval context (and
+ # will continue to not pass one), we need to perform an instance
+ # check here.
+ #
+ # This is considered safe because an eval context is not a valid
+ # argument to callables otherwise anwyays. Worst case here is
+ # that if no eval context is passed we fall back to the compile
+ # time autoescape flag.
+ if args and isinstance(args[0], EvalContext):
+ autoescape = args[0].autoescape
+ args = args[1:]
+ else:
+ autoescape = self._default_autoescape
+
# try to consume the positional arguments
arguments = list(args[:self._argument_count])
off = len(arguments)
+ # For information why this is necessary refer to the handling
+ # of caller in the `macro_body` handler in the compiler.
+ found_caller = False
+
# if the number of arguments consumed is not the number of
# arguments expected we start filling in keyword arguments
# and defaults.
@@ -407,25 +540,30 @@ class Macro(object):
try:
value = kwargs.pop(name)
except KeyError:
- try:
- value = self.defaults[idx - self._argument_count + off]
- except IndexError:
- value = self._environment.undefined(
- 'parameter %r was not provided' % name, name=name)
+ value = missing
+ if name == 'caller':
+ found_caller = True
arguments.append(value)
+ else:
+ found_caller = self.explicit_caller
# it's important that the order of these arguments does not change
# if not also changed in the compiler's `function_scoping` method.
# the order is caller, keyword arguments, positional arguments!
- if self.caller:
+ if self.caller and not found_caller:
caller = kwargs.pop('caller', None)
if caller is None:
caller = self._environment.undefined('No caller defined',
name='caller')
arguments.append(caller)
+
if self.catch_kwargs:
arguments.append(kwargs)
elif kwargs:
+ if 'caller' in kwargs:
+ raise TypeError('macro %r was invoked with two values for '
+ 'the special caller argument. This is '
+ 'most likely a bug.' % self.name)
raise TypeError('macro %r takes no keyword argument %r' %
(self.name, next(iter(kwargs))))
if self.catch_varargs:
@@ -433,7 +571,15 @@ class Macro(object):
elif len(args) > self._argument_count:
raise TypeError('macro %r takes not more than %d argument(s)' %
(self.name, len(self.arguments)))
- return self._func(*arguments)
+
+ return self._invoke(arguments, autoescape)
+
+ def _invoke(self, arguments, autoescape):
+ """This method is being swapped out by the async implementation."""
+ rv = self._func(*arguments)
+ if autoescape:
+ rv = Markup(rv)
+ return rv
def __repr__(self):
return '<%s %s>' % (
@@ -498,8 +644,8 @@ class Undefined(object):
__truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \
__mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
__getitem__ = __lt__ = __le__ = __gt__ = __ge__ = __int__ = \
- __float__ = __complex__ = __pow__ = __rpow__ = \
- _fail_with_undefined_error
+ __float__ = __complex__ = __pow__ = __rpow__ = __sub__ = \
+ __rsub__ = _fail_with_undefined_error
def __eq__(self, other):
return type(self) is type(other)
diff --git a/third_party/jinja2/sandbox.py b/third_party/jinja2/sandbox.py
index 7e40ab3085..93fb9d45f3 100644
--- a/third_party/jinja2/sandbox.py
+++ b/third_party/jinja2/sandbox.py
@@ -9,14 +9,19 @@
The behavior can be changed by subclassing the environment.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD.
"""
import types
import operator
+from collections import Mapping
from jinja2.environment import Environment
from jinja2.exceptions import SecurityError
from jinja2._compat import string_types, PY2
+from jinja2.utils import Markup
+
+from markupsafe import EscapeFormatter
+from string import Formatter
#: maximum number of items a range may produce
@@ -38,6 +43,12 @@ UNSAFE_METHOD_ATTRIBUTES = set(['im_class', 'im_func', 'im_self'])
#: unsafe generator attirbutes.
UNSAFE_GENERATOR_ATTRIBUTES = set(['gi_frame', 'gi_code'])
+#: unsafe attributes on coroutines
+UNSAFE_COROUTINE_ATTRIBUTES = set(['cr_frame', 'cr_code'])
+
+#: unsafe attributes on async generators
+UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = set(['ag_code', 'ag_frame'])
+
import warnings
# make sure we don't warn in python 2.6 about stuff we don't care about
@@ -68,13 +79,11 @@ except ImportError:
pass
#: register Python 2.6 abstract base classes
-try:
- from collections import MutableSet, MutableMapping, MutableSequence
- _mutable_set_types += (MutableSet,)
- _mutable_mapping_types += (MutableMapping,)
- _mutable_sequence_types += (MutableSequence,)
-except ImportError:
- pass
+from collections import MutableSet, MutableMapping, MutableSequence
+_mutable_set_types += (MutableSet,)
+_mutable_mapping_types += (MutableMapping,)
+_mutable_sequence_types += (MutableSequence,)
+
_mutable_spec = (
(_mutable_set_types, frozenset([
@@ -94,6 +103,47 @@ _mutable_spec = (
)
+class _MagicFormatMapping(Mapping):
+ """This class implements a dummy wrapper to fix a bug in the Python
+ standard library for string formatting.
+
+ See https://bugs.python.org/issue13598 for information about why
+ this is necessary.
+ """
+
+ def __init__(self, args, kwargs):
+ self._args = args
+ self._kwargs = kwargs
+ self._last_index = 0
+
+ def __getitem__(self, key):
+ if key == '':
+ idx = self._last_index
+ self._last_index += 1
+ try:
+ return self._args[idx]
+ except LookupError:
+ pass
+ key = str(idx)
+ return self._kwargs[key]
+
+ def __iter__(self):
+ return iter(self._kwargs)
+
+ def __len__(self):
+ return len(self._kwargs)
+
+
+def inspect_format_method(callable):
+ if not isinstance(callable, (types.MethodType,
+ types.BuiltinMethodType)) or \
+ callable.__name__ != 'format':
+ return None
+ obj = callable.__self__
+ if isinstance(obj, string_types):
+ return obj
+
+
def safe_range(*args):
"""A range that can't generate ranges with a length of more than
MAX_RANGE items.
@@ -145,6 +195,12 @@ def is_internal_attribute(obj, attr):
elif isinstance(obj, types.GeneratorType):
if attr in UNSAFE_GENERATOR_ATTRIBUTES:
return True
+ elif hasattr(types, 'CoroutineType') and isinstance(obj, types.CoroutineType):
+ if attr in UNSAFE_COROUTINE_ATTRIBUTES:
+ return True
+ elif hasattr(types, 'AsyncGeneratorType') and isinstance(obj, types.AsyncGeneratorType):
+ if attr in UNSAFE_ASYNC_GENERATOR_ATTRIBUTES:
+ return True
return attr.startswith('__')
@@ -183,8 +239,8 @@ class SandboxedEnvironment(Environment):
attributes or functions are safe to access.
If the template tries to access insecure code a :exc:`SecurityError` is
- raised. However also other exceptions may occour during the rendering so
- the caller has to ensure that all exceptions are catched.
+ raised. However also other exceptions may occur during the rendering so
+ the caller has to ensure that all exceptions are caught.
"""
sandboxed = True
@@ -346,8 +402,24 @@ class SandboxedEnvironment(Environment):
obj.__class__.__name__
), name=attribute, obj=obj, exc=SecurityError)
+ def format_string(self, s, args, kwargs):
+ """If a format call is detected, then this is routed through this
+ method so that our safety sandbox can be used for it.
+ """
+ if isinstance(s, Markup):
+ formatter = SandboxedEscapeFormatter(self, s.escape)
+ else:
+ formatter = SandboxedFormatter(self)
+ kwargs = _MagicFormatMapping(args, kwargs)
+ rv = formatter.vformat(s, args, kwargs)
+ return type(s)(rv)
+
def call(__self, __context, __obj, *args, **kwargs):
"""Call an object from sandboxed code."""
+ fmt = inspect_format_method(__obj)
+ if fmt is not None:
+ return __self.format_string(fmt, args, kwargs)
+
# the double prefixes are to avoid double keyword argument
# errors when proxying the call.
if not __self.is_safe_callable(__obj):
@@ -365,3 +437,39 @@ class ImmutableSandboxedEnvironment(SandboxedEnvironment):
if not SandboxedEnvironment.is_safe_attribute(self, obj, attr, value):
return False
return not modifies_known_mutable(obj, attr)
+
+
+# This really is not a public API apparenlty.
+try:
+ from _string import formatter_field_name_split
+except ImportError:
+ def formatter_field_name_split(field_name):
+ return field_name._formatter_field_name_split()
+
+
+class SandboxedFormatterMixin(object):
+
+ def __init__(self, env):
+ self._env = env
+
+ def get_field(self, field_name, args, kwargs):
+ first, rest = formatter_field_name_split(field_name)
+ obj = self.get_value(first, args, kwargs)
+ for is_attr, i in rest:
+ if is_attr:
+ obj = self._env.getattr(obj, i)
+ else:
+ obj = self._env.getitem(obj, i)
+ return obj, first
+
+class SandboxedFormatter(SandboxedFormatterMixin, Formatter):
+
+ def __init__(self, env):
+ SandboxedFormatterMixin.__init__(self, env)
+ Formatter.__init__(self)
+
+class SandboxedEscapeFormatter(SandboxedFormatterMixin, EscapeFormatter):
+
+ def __init__(self, env, escape):
+ SandboxedFormatterMixin.__init__(self, env)
+ EscapeFormatter.__init__(self, escape)
diff --git a/third_party/jinja2/tests.py b/third_party/jinja2/tests.py
index bb32349df0..0adc3d4dbc 100644
--- a/third_party/jinja2/tests.py
+++ b/third_party/jinja2/tests.py
@@ -5,9 +5,10 @@
Jinja test functions. Used with the "is" operator.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
+import operator
import re
from collections import Mapping
from jinja2.runtime import Undefined
@@ -103,28 +104,6 @@ def test_sequence(value):
return True
-def test_equalto(value, other):
- """Check if an object has the same value as another object:
-
- .. sourcecode:: jinja
-
- {% if foo.expression is equalto 42 %}
- the foo attribute evaluates to the constant 42
- {% endif %}
-
- This appears to be a useless test as it does exactly the same as the
- ``==`` operator, but it can be useful when used together with the
- `selectattr` function:
-
- .. sourcecode:: jinja
-
- {{ users|selectattr("email", "equalto", "foo@bar.invalid") }}
-
- .. versionadded:: 2.8
- """
- return value == other
-
-
def test_sameas(value, other):
"""Check if an object points to the same memory address than another
object:
@@ -152,6 +131,14 @@ def test_escaped(value):
return hasattr(value, '__html__')
+def test_in(value, seq):
+ """Check if value is in seq.
+
+ .. versionadded:: 2.10
+ """
+ return value in seq
+
+
TESTS = {
'odd': test_odd,
'even': test_even,
@@ -168,6 +155,21 @@ TESTS = {
'iterable': test_iterable,
'callable': test_callable,
'sameas': test_sameas,
- 'equalto': test_equalto,
- 'escaped': test_escaped
+ 'escaped': test_escaped,
+ 'in': test_in,
+ '==': operator.eq,
+ 'eq': operator.eq,
+ 'equalto': operator.eq,
+ '!=': operator.ne,
+ 'ne': operator.ne,
+ '>': operator.gt,
+ 'gt': operator.gt,
+ 'greaterthan': operator.gt,
+ 'ge': operator.ge,
+ '>=': operator.ge,
+ '<': operator.lt,
+ 'lt': operator.lt,
+ 'lessthan': operator.lt,
+ '<=': operator.le,
+ 'le': operator.le,
}
diff --git a/third_party/jinja2/utils.py b/third_party/jinja2/utils.py
index cdd4cd3af0..502a311c08 100644
--- a/third_party/jinja2/utils.py
+++ b/third_party/jinja2/utils.py
@@ -5,10 +5,11 @@
Utility functions.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import re
+import json
import errno
from collections import deque
from threading import Lock
@@ -37,6 +38,8 @@ internal_code = set()
concat = u''.join
+_slash_escape = '\\/' not in json.dumps('/')
+
def contextfunction(f):
"""This decorator can be used to mark a function or method context callable.
@@ -109,7 +112,7 @@ def clear_caches():
"""Jinja2 keeps internal caches for environments and lexers. These are
used so that Jinja2 doesn't have to recreate environments and lexers all
the time. Normally you don't have to care about that but if you are
- messuring memory consumption you may want to clean the caches.
+ measuring memory consumption you may want to clean the caches.
"""
from jinja2.environment import _spontaneous_environments
from jinja2.lexer import _lexer_cache
@@ -183,7 +186,7 @@ def pformat(obj, verbose=False):
return pformat(obj)
-def urlize(text, trim_url_limit=None, nofollow=False, target=None):
+def urlize(text, trim_url_limit=None, rel=None, target=None):
"""Converts any URLs in text into clickable links. Works on http://,
https:// and www. links. Links can have trailing punctuation (periods,
commas, close-parens) and leading punctuation (opening parens) and
@@ -201,11 +204,9 @@ def urlize(text, trim_url_limit=None, nofollow=False, target=None):
and (x[:limit] + (len(x) >=limit and '...'
or '')) or x
words = _word_split_re.split(text_type(escape(text)))
- nofollow_attr = nofollow and ' rel="nofollow"' or ''
- if target is not None and isinstance(target, string_types):
- target_attr = ' target="%s"' % target
- else:
- target_attr = ''
+ rel_attr = rel and ' rel="%s"' % text_type(escape(rel)) or ''
+ target_attr = target and ' target="%s"' % escape(target) or ''
+
for i, word in enumerate(words):
match = _punctuation_re.match(word)
if match:
@@ -221,11 +222,11 @@ def urlize(text, trim_url_limit=None, nofollow=False, target=None):
middle.endswith('.com')
)):
middle = '<a href="http://%s"%s%s>%s</a>' % (middle,
- nofollow_attr, target_attr, trim_url(middle))
+ rel_attr, target_attr, trim_url(middle))
if middle.startswith('http://') or \
middle.startswith('https://'):
middle = '<a href="%s"%s%s>%s</a>' % (middle,
- nofollow_attr, target_attr, trim_url(middle))
+ rel_attr, target_attr, trim_url(middle))
if '@' in middle and not middle.startswith('www.') and \
not ':' in middle and _simple_email_re.match(middle):
middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
@@ -295,7 +296,7 @@ def unicode_urlencode(obj, charset='utf-8', for_qs=False):
obj = text_type(obj)
if isinstance(obj, text_type):
obj = obj.encode(charset)
- safe = for_qs and b'' or b'/'
+ safe = not for_qs and b'/' or b''
rv = text_type(url_quote(obj, safe))
if for_qs:
rv = rv.replace('%20', '+')
@@ -487,6 +488,88 @@ except ImportError:
pass
+def select_autoescape(enabled_extensions=('html', 'htm', 'xml'),
+ disabled_extensions=(),
+ default_for_string=True,
+ default=False):
+ """Intelligently sets the initial value of autoescaping based on the
+ filename of the template. This is the recommended way to configure
+ autoescaping if you do not want to write a custom function yourself.
+
+ If you want to enable it for all templates created from strings or
+ for all templates with `.html` and `.xml` extensions::
+
+ from jinja2 import Environment, select_autoescape
+ env = Environment(autoescape=select_autoescape(
+ enabled_extensions=('html', 'xml'),
+ default_for_string=True,
+ ))
+
+ Example configuration to turn it on at all times except if the template
+ ends with `.txt`::
+
+ from jinja2 import Environment, select_autoescape
+ env = Environment(autoescape=select_autoescape(
+ disabled_extensions=('txt',),
+ default_for_string=True,
+ default=True,
+ ))
+
+ The `enabled_extensions` is an iterable of all the extensions that
+ autoescaping should be enabled for. Likewise `disabled_extensions` is
+ a list of all templates it should be disabled for. If a template is
+ loaded from a string then the default from `default_for_string` is used.
+ If nothing matches then the initial value of autoescaping is set to the
+ value of `default`.
+
+ For security reasons this function operates case insensitive.
+
+ .. versionadded:: 2.9
+ """
+ enabled_patterns = tuple('.' + x.lstrip('.').lower()
+ for x in enabled_extensions)
+ disabled_patterns = tuple('.' + x.lstrip('.').lower()
+ for x in disabled_extensions)
+ def autoescape(template_name):
+ if template_name is None:
+ return default_for_string
+ template_name = template_name.lower()
+ if template_name.endswith(enabled_patterns):
+ return True
+ if template_name.endswith(disabled_patterns):
+ return False
+ return default
+ return autoescape
+
+
+def htmlsafe_json_dumps(obj, dumper=None, **kwargs):
+ """Works exactly like :func:`dumps` but is safe for use in ``<script>``
+ tags. It accepts the same arguments and returns a JSON string. Note that
+ this is available in templates through the ``|tojson`` filter which will
+ also mark the result as safe. Due to how this function escapes certain
+ characters this is safe even if used outside of ``<script>`` tags.
+
+ The following characters are escaped in strings:
+
+ - ``<``
+ - ``>``
+ - ``&``
+ - ``'``
+
+ This makes it safe to embed such strings in any place in HTML with the
+ notable exception of double quoted attributes. In that case single
+ quote your attributes or HTML escape it in addition.
+ """
+ if dumper is None:
+ dumper = json.dumps
+ rv = dumper(obj, **kwargs) \
+ .replace(u'<', u'\\u003c') \
+ .replace(u'>', u'\\u003e') \
+ .replace(u'&', u'\\u0026') \
+ .replace(u"'", u'\\u0027')
+ return Markup(rv)
+
+
@implements_iterator
class Cycler(object):
"""A cycle helper for templates."""
@@ -506,12 +589,14 @@ class Cycler(object):
"""Returns the current item."""
return self.items[self.pos]
- def __next__(self):
+ def next(self):
"""Goes one item ahead and returns it."""
rv = self.current
self.pos = (self.pos + 1) % len(self.items)
return rv
+ __next__ = next
+
class Joiner(object):
"""A joining helper for templates."""
@@ -527,5 +612,36 @@ class Joiner(object):
return self.sep
+class Namespace(object):
+ """A namespace object that can hold arbitrary attributes. It may be
+ initialized from a dictionary or with keyword argments."""
+
+ def __init__(*args, **kwargs):
+ self, args = args[0], args[1:]
+ self.__attrs = dict(*args, **kwargs)
+
+ def __getattribute__(self, name):
+ if name == '_Namespace__attrs':
+ return object.__getattribute__(self, name)
+ try:
+ return self.__attrs[name]
+ except KeyError:
+ raise AttributeError(name)
+
+ def __setitem__(self, name, value):
+ self.__attrs[name] = value
+
+ def __repr__(self):
+ return '<Namespace %r>' % self.__attrs
+
+
+# does this python version support async for in and async generators?
+try:
+ exec('async def _():\n async for _ in ():\n yield _')
+ have_async_gen = True
+except SyntaxError:
+ have_async_gen = False
+
+
# Imported here because that's where it was in the past
from markupsafe import Markup, escape, soft_unicode
diff --git a/third_party/jinja2/visitor.py b/third_party/jinja2/visitor.py
index 413e7c309d..ba526dfac9 100644
--- a/third_party/jinja2/visitor.py
+++ b/third_party/jinja2/visitor.py
@@ -5,7 +5,7 @@
This module implements a visitor for the nodes.
- :copyright: (c) 2010 by the Jinja Team.
+ :copyright: (c) 2017 by the Jinja Team.
:license: BSD.
"""
from jinja2.nodes import Node
diff --git a/ui/gfx/geometry/insets_f.h b/ui/gfx/geometry/insets_f.h
index 30c2ff2b79..3230287957 100644
--- a/ui/gfx/geometry/insets_f.h
+++ b/ui/gfx/geometry/insets_f.h
@@ -75,6 +75,11 @@ class GFX_EXPORT InsetsF {
return InsetsF(-top_, -left_, -bottom_, -right_);
}
+ InsetsF Scale(float scale) const {
+ return InsetsF(scale * top(), scale * left(), scale * bottom(),
+ scale * right());
+ }
+
// Returns a string representation of the insets.
std::string ToString() const;
diff --git a/ui/gfx/geometry/mojo/BUILD.gn b/ui/gfx/geometry/mojo/BUILD.gn
index f6cda9d3be..2d0e1efbcf 100644
--- a/ui/gfx/geometry/mojo/BUILD.gn
+++ b/ui/gfx/geometry/mojo/BUILD.gn
@@ -10,6 +10,8 @@ mojom("mojo") {
sources = [
"geometry.mojom",
]
+
+ check_includes_blink = false
}
mojom("test_interfaces") {
diff --git a/ui/gfx/geometry/mojo/DEPS b/ui/gfx/geometry/mojo/DEPS
deleted file mode 100644
index 3ad6543823..0000000000
--- a/ui/gfx/geometry/mojo/DEPS
+++ /dev/null
@@ -1,4 +0,0 @@
-include_rules = [
- "+mojo/public",
- "+ui/gfx/geometry",
-]
diff --git a/ui/gfx/geometry/mojo/geometry.mojom b/ui/gfx/geometry/mojo/geometry.mojom
index 97143866da..9b8342f7cc 100644
--- a/ui/gfx/geometry/mojo/geometry.mojom
+++ b/ui/gfx/geometry/mojo/geometry.mojom
@@ -4,11 +4,18 @@
module gfx.mojom;
+// Don't make backwards-incompatible changes to this definition!
+// It's used in PageState serialization, so backwards incompatible changes
+// would cause stored PageState objects to be un-parseable.
struct Point {
int32 x;
int32 y;
};
+// Don't make backwards-incompatible changes to this definition!
+// It's used in PageState serialization, so backwards incompatible changes
+// would cause stored PageState objects to be un-parseable. Please contact the
+// page state serialization owners before making such a change.
struct PointF {
float x;
float y;
@@ -61,3 +68,8 @@ struct Vector2dF {
float x;
float y;
};
+
+struct ScrollOffset {
+ float x;
+ float y;
+};
diff --git a/ui/gfx/geometry/mojo/geometry.typemap b/ui/gfx/geometry/mojo/geometry.typemap
index 686ea05cfb..52775304c6 100644
--- a/ui/gfx/geometry/mojo/geometry.typemap
+++ b/ui/gfx/geometry/mojo/geometry.typemap
@@ -10,6 +10,7 @@ public_headers = [
"//ui/gfx/geometry/rect.h",
"//ui/gfx/geometry/rect_f.h",
"//ui/gfx/geometry/safe_integer_conversions.h",
+ "//ui/gfx/geometry/scroll_offset.h",
"//ui/gfx/geometry/insets.h",
"//ui/gfx/geometry/vector2d.h",
"//ui/gfx/geometry/vector2d_f.h",
@@ -29,4 +30,5 @@ type_mappings = [
"gfx.mojom.InsetsF=gfx::InsetsF",
"gfx.mojom.Vector2d=gfx::Vector2d",
"gfx.mojom.Vector2dF=gfx::Vector2dF",
+ "gfx.mojom.ScrollOffset=gfx::ScrollOffset",
]
diff --git a/ui/gfx/geometry/mojo/geometry_struct_traits.h b/ui/gfx/geometry/mojo/geometry_struct_traits.h
index a31d7385b6..2fcd37cce7 100644
--- a/ui/gfx/geometry/mojo/geometry_struct_traits.h
+++ b/ui/gfx/geometry/mojo/geometry_struct_traits.h
@@ -12,6 +12,7 @@
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_f.h"
+#include "ui/gfx/geometry/scroll_offset.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/size_f.h"
#include "ui/gfx/geometry/vector2d.h"
@@ -141,6 +142,18 @@ struct StructTraits<gfx::mojom::Vector2dFDataView, gfx::Vector2dF> {
}
};
+template <>
+struct StructTraits<gfx::mojom::ScrollOffsetDataView, gfx::ScrollOffset> {
+ static float x(const gfx::ScrollOffset& v) { return v.x(); }
+ static float y(const gfx::ScrollOffset& v) { return v.y(); }
+ static bool Read(gfx::mojom::ScrollOffsetDataView data,
+ gfx::ScrollOffset* out) {
+ out->set_x(data.x());
+ out->set_y(data.y());
+ return true;
+ }
+};
+
} // namespace mojo
#endif // UI_GFX_GEOMETRY_MOJO_GEOMETRY_STRUCT_TRAITS_H_
diff --git a/ui/gfx/geometry/mojo/geometry_struct_traits_unittest.cc b/ui/gfx/geometry/mojo/geometry_struct_traits_unittest.cc
index d59579443a..2be935bd46 100644
--- a/ui/gfx/geometry/mojo/geometry_struct_traits_unittest.cc
+++ b/ui/gfx/geometry/mojo/geometry_struct_traits_unittest.cc
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <utility>
+
#include "base/message_loop/message_loop.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -19,54 +21,52 @@ class GeometryStructTraitsTest : public testing::Test,
protected:
mojom::GeometryTraitsTestServicePtr GetTraitsTestProxy() {
- return traits_test_bindings_.CreateInterfacePtrAndBind(this);
+ mojom::GeometryTraitsTestServicePtr proxy;
+ traits_test_bindings_.AddBinding(this, mojo::MakeRequest(&proxy));
+ return proxy;
}
private:
// GeometryTraitsTestService:
- void EchoPoint(const Point& p, const EchoPointCallback& callback) override {
- callback.Run(p);
+ void EchoPoint(const Point& p, EchoPointCallback callback) override {
+ std::move(callback).Run(p);
}
- void EchoPointF(const PointF& p,
- const EchoPointFCallback& callback) override {
- callback.Run(p);
+ void EchoPointF(const PointF& p, EchoPointFCallback callback) override {
+ std::move(callback).Run(p);
}
- void EchoSize(const Size& s, const EchoSizeCallback& callback) override {
- callback.Run(s);
+ void EchoSize(const Size& s, EchoSizeCallback callback) override {
+ std::move(callback).Run(s);
}
- void EchoSizeF(const SizeF& s, const EchoSizeFCallback& callback) override {
- callback.Run(s);
+ void EchoSizeF(const SizeF& s, EchoSizeFCallback callback) override {
+ std::move(callback).Run(s);
}
- void EchoRect(const Rect& r, const EchoRectCallback& callback) override {
- callback.Run(r);
+ void EchoRect(const Rect& r, EchoRectCallback callback) override {
+ std::move(callback).Run(r);
}
- void EchoRectF(const RectF& r, const EchoRectFCallback& callback) override {
- callback.Run(r);
+ void EchoRectF(const RectF& r, EchoRectFCallback callback) override {
+ std::move(callback).Run(r);
}
- void EchoInsets(const Insets& i,
- const EchoInsetsCallback& callback) override {
- callback.Run(i);
+ void EchoInsets(const Insets& i, EchoInsetsCallback callback) override {
+ std::move(callback).Run(i);
}
- void EchoInsetsF(const InsetsF& i,
- const EchoInsetsFCallback& callback) override {
- callback.Run(i);
+ void EchoInsetsF(const InsetsF& i, EchoInsetsFCallback callback) override {
+ std::move(callback).Run(i);
}
- void EchoVector2d(const Vector2d& v,
- const EchoVector2dCallback& callback) override {
- callback.Run(v);
+ void EchoVector2d(const Vector2d& v, EchoVector2dCallback callback) override {
+ std::move(callback).Run(v);
}
void EchoVector2dF(const Vector2dF& v,
- const EchoVector2dFCallback& callback) override {
- callback.Run(v);
+ EchoVector2dFCallback callback) override {
+ std::move(callback).Run(v);
}
base::MessageLoop loop_;
diff --git a/ui/gfx/geometry/point.h b/ui/gfx/geometry/point.h
index bb248d5432..b1ba5065de 100644
--- a/ui/gfx/geometry/point.h
+++ b/ui/gfx/geometry/point.h
@@ -9,7 +9,7 @@
#include <string>
#include <tuple>
-#include "base/numerics/saturated_arithmetic.h"
+#include "base/numerics/clamped_math.h"
#include "build/build_config.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/gfx_export.h"
@@ -56,18 +56,18 @@ class GFX_EXPORT Point {
}
void Offset(int delta_x, int delta_y) {
- x_ = base::SaturatedAddition(x_, delta_x);
- y_ = base::SaturatedAddition(y_, delta_y);
+ x_ = base::ClampAdd(x_, delta_x);
+ y_ = base::ClampAdd(y_, delta_y);
}
void operator+=(const Vector2d& vector) {
- x_ = base::SaturatedAddition(x_, vector.x());
- y_ = base::SaturatedAddition(y_, vector.y());
+ x_ = base::ClampAdd(x_, vector.x());
+ y_ = base::ClampAdd(y_, vector.y());
}
void operator-=(const Vector2d& vector) {
- x_ = base::SaturatedSubtraction(x_, vector.x());
- y_ = base::SaturatedSubtraction(y_, vector.y());
+ x_ = base::ClampSub(x_, vector.x());
+ y_ = base::ClampSub(y_, vector.y());
}
void SetToMin(const Point& other);
@@ -116,8 +116,8 @@ inline Point operator-(const Point& lhs, const Vector2d& rhs) {
}
inline Vector2d operator-(const Point& lhs, const Point& rhs) {
- return Vector2d(base::SaturatedSubtraction(lhs.x(), rhs.x()),
- base::SaturatedSubtraction(lhs.y(), rhs.y()));
+ return Vector2d(base::ClampSub(lhs.x(), rhs.x()),
+ base::ClampSub(lhs.y(), rhs.y()));
}
inline Point PointAtOffsetFromOrigin(const Vector2d& offset_from_origin) {
diff --git a/ui/gfx/geometry/rect.cc b/ui/gfx/geometry/rect.cc
index b5ceda5829..63c0bbf04f 100644
--- a/ui/gfx/geometry/rect.cc
+++ b/ui/gfx/geometry/rect.cc
@@ -15,7 +15,7 @@
#endif
#include "base/logging.h"
-#include "base/numerics/saturated_arithmetic.h"
+#include "base/numerics/clamped_math.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "ui/gfx/geometry/insets.h"
@@ -69,8 +69,8 @@ static void SaturatedClampRange(int min, int max, int* origin, int* span) {
return;
}
- int effective_span = base::SaturatedSubtraction(max, min);
- int span_loss = base::SaturatedSubtraction(max, min + effective_span);
+ int effective_span = base::ClampSub(max, min);
+ int span_loss = base::ClampSub(max, min + effective_span);
// If the desired width is within the limits of ints, we can just
// use the simple computations to represent the range precisely.
@@ -83,12 +83,12 @@ static void SaturatedClampRange(int min, int max, int* origin, int* span) {
// Now we have to approximate. If one of min or max is close enough
// to zero we choose to represent that one precisely. The other side is
// probably practically "infinite", so we move it.
- if (base::SaturatedAbsolute(max) < std::numeric_limits<int>::max() / 2) {
+ constexpr unsigned kMaxDimension = std::numeric_limits<int>::max() / 2;
+ if (base::SafeUnsignedAbs(max) < kMaxDimension) {
// Maintain origin + span == max.
*span = effective_span;
*origin = max - effective_span;
- } else if (base::SaturatedAbsolute(min) <
- std::numeric_limits<int>::max() / 2) {
+ } else if (base::SafeUnsignedAbs(min) < kMaxDimension) {
// Maintain origin == min.
*span = effective_span;
*origin = min;
@@ -116,10 +116,8 @@ void Rect::Inset(int left, int top, int right, int bottom) {
origin_ += Vector2d(left, top);
// left+right might overflow/underflow, but width() - (left+right) might
// overflow as well.
- set_width(base::SaturatedSubtraction(width(),
- base::SaturatedAddition(left, right)));
- set_height(base::SaturatedSubtraction(height(),
- base::SaturatedAddition(top, bottom)));
+ set_width(base::ClampSub(width(), base::ClampAdd(left, right)));
+ set_height(base::ClampSub(height(), base::ClampAdd(top, bottom)));
}
void Rect::Offset(int horizontal, int vertical) {
diff --git a/ui/gfx/geometry/rect.h b/ui/gfx/geometry/rect.h
index 1858d44d2c..3109912b6a 100644
--- a/ui/gfx/geometry/rect.h
+++ b/ui/gfx/geometry/rect.h
@@ -61,12 +61,14 @@ class GFX_EXPORT Rect {
#endif
constexpr int x() const { return origin_.x(); }
+ // Sets the X position while preserving the width.
void set_x(int x) {
origin_.set_x(x);
size_.set_width(GetClampedValue(x, width()));
}
constexpr int y() const { return origin_.y(); }
+ // Sets the Y position while preserving the height.
void set_y(int y) {
origin_.set_y(y);
size_.set_height(GetClampedValue(y, height()));
@@ -227,7 +229,7 @@ class GFX_EXPORT Rect {
// Clamp the size to avoid integer overflow in bottom() and right().
// This returns the width given an origin and a width.
- // TODO(enne): this should probably use base::SaturatedAddition, but that
+ // TODO(enne): this should probably use base::ClampAdd, but that
// function is not a constexpr.
static constexpr int GetClampedValue(int origin, int size) {
return AddWouldOverflow(origin, size)
@@ -340,6 +342,41 @@ inline Rect ScaleToEnclosedRect(const Rect& rect, float scale) {
return ScaleToEnclosedRect(rect, scale, scale);
}
+// Scales |rect| by scaling its four corner points. If the corner points lie on
+// non-integral coordinate after scaling, their values are rounded to the
+// nearest integer.
+// This is helpful during layout when relative positions of multiple gfx::Rect
+// in a given coordinate space needs to be same after scaling as it was before
+// scaling. ie. this gives a lossless relative positioning of rects.
+inline Rect ScaleToRoundedRect(const Rect& rect, float x_scale, float y_scale) {
+ if (x_scale == 1.f && y_scale == 1.f)
+ return rect;
+
+ DCHECK(
+ base::IsValueInRangeForNumericType<int>(std::round(rect.x() * x_scale)));
+ DCHECK(
+ base::IsValueInRangeForNumericType<int>(std::round(rect.y() * y_scale)));
+ DCHECK(base::IsValueInRangeForNumericType<int>(
+ std::round(rect.right() * x_scale)));
+ DCHECK(base::IsValueInRangeForNumericType<int>(
+ std::round(rect.bottom() * y_scale)));
+
+ int x = static_cast<int>(std::round(rect.x() * x_scale));
+ int y = static_cast<int>(std::round(rect.y() * y_scale));
+ int r = rect.width() == 0
+ ? x
+ : static_cast<int>(std::round(rect.right() * x_scale));
+ int b = rect.height() == 0
+ ? y
+ : static_cast<int>(std::round(rect.bottom() * y_scale));
+
+ return Rect(x, y, r - x, b - y);
+}
+
+inline Rect ScaleToRoundedRect(const Rect& rect, float scale) {
+ return ScaleToRoundedRect(rect, scale, scale);
+}
+
// This is declared here for use in gtest-based unit tests but is defined in
// the //ui/gfx:test_support target. Depend on that to use this in your unit
// test. This should not be used in production code - call ToString() instead.
diff --git a/ui/gfx/geometry/rect_f.cc b/ui/gfx/geometry/rect_f.cc
index a08e384776..bb474ba63d 100644
--- a/ui/gfx/geometry/rect_f.cc
+++ b/ui/gfx/geometry/rect_f.cc
@@ -5,12 +5,7 @@
#include "ui/gfx/geometry/rect_f.h"
#include <algorithm>
-
-#if defined(OS_IOS)
-#include <CoreGraphics/CoreGraphics.h>
-#elif defined(OS_MACOSX)
-#include <ApplicationServices/ApplicationServices.h>
-#endif
+#include <limits>
#include "base/logging.h"
#include "base/strings/stringprintf.h"
@@ -18,6 +13,12 @@
#include "ui/gfx/geometry/insets_f.h"
#include "ui/gfx/geometry/safe_integer_conversions.h"
+#if defined(OS_IOS)
+#include <CoreGraphics/CoreGraphics.h>
+#elif defined(OS_MACOSX)
+#include <ApplicationServices/ApplicationServices.h>
+#endif
+
namespace gfx {
static void AdjustAlongAxis(float dst_origin,
@@ -47,8 +48,8 @@ void RectF::Inset(const InsetsF& insets) {
void RectF::Inset(float left, float top, float right, float bottom) {
origin_ += Vector2dF(left, top);
- set_width(std::max(width() - left - right, static_cast<float>(0)));
- set_height(std::max(height() - top - bottom, static_cast<float>(0)));
+ set_width(std::max(width() - left - right, 0.0f));
+ set_height(std::max(height() - top - bottom, 0.0f));
}
void RectF::Offset(float horizontal, float vertical) {
@@ -71,30 +72,27 @@ InsetsF RectF::InsetsFrom(const RectF& inner) const {
}
bool RectF::operator<(const RectF& other) const {
- if (origin_ == other.origin_) {
- if (width() == other.width()) {
- return height() < other.height();
- } else {
- return width() < other.width();
- }
- } else {
+ if (origin_ != other.origin_)
return origin_ < other.origin_;
- }
+
+ if (width() == other.width())
+ return height() < other.height();
+ return width() < other.width();
}
bool RectF::Contains(float point_x, float point_y) const {
- return (point_x >= x()) && (point_x < right()) && (point_y >= y()) &&
- (point_y < bottom());
+ return point_x >= x() && point_x < right() && point_y >= y() &&
+ point_y < bottom();
}
bool RectF::Contains(const RectF& rect) const {
- return (rect.x() >= x() && rect.right() <= right() && rect.y() >= y() &&
- rect.bottom() <= bottom());
+ return rect.x() >= x() && rect.right() <= right() && rect.y() >= y() &&
+ rect.bottom() <= bottom();
}
bool RectF::Intersects(const RectF& rect) const {
- return !(IsEmpty() || rect.IsEmpty() || rect.x() >= right() ||
- rect.right() <= x() || rect.y() >= bottom() || rect.bottom() <= y());
+ return !IsEmpty() && !rect.IsEmpty() && rect.x() < right() &&
+ rect.right() > x() && rect.y() < bottom() && rect.bottom() > y();
}
void RectF::Intersect(const RectF& rect) {
@@ -108,8 +106,10 @@ void RectF::Intersect(const RectF& rect) {
float rr = std::min(right(), rect.right());
float rb = std::min(bottom(), rect.bottom());
- if (rx >= rr || ry >= rb)
- rx = ry = rr = rb = 0; // non-intersecting
+ if (rx >= rr || ry >= rb) {
+ SetRect(0, 0, 0, 0);
+ return;
+ }
SetRect(rx, ry, rr - rx, rb - ry);
}
@@ -133,7 +133,7 @@ void RectF::Union(const RectF& rect) {
void RectF::Subtract(const RectF& rect) {
if (!Intersects(rect))
return;
- if (rect.Contains(*static_cast<const RectF*>(this))) {
+ if (rect.Contains(*this)) {
SetRect(0, 0, 0, 0);
return;
}
@@ -212,7 +212,7 @@ float RectF::ManhattanInternalDistance(const RectF& rect) const {
RectF c(*this);
c.Union(rect);
- static const float kEpsilon = std::numeric_limits<float>::epsilon();
+ static constexpr float kEpsilon = std::numeric_limits<float>::epsilon();
float x = std::max(0.f, c.width() - width() - rect.width() + kEpsilon);
float y = std::max(0.f, c.height() - height() - rect.height() + kEpsilon);
return x + y;
diff --git a/ui/gfx/geometry/scroll_offset.cc b/ui/gfx/geometry/scroll_offset.cc
new file mode 100644
index 0000000000..063b050aca
--- /dev/null
+++ b/ui/gfx/geometry/scroll_offset.cc
@@ -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.
+
+#include "ui/gfx/geometry/scroll_offset.h"
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+std::string ScrollOffset::ToString() const {
+ return base::StringPrintf("[%f %f]", x_, y_);
+}
+
+} // namespace gfx
diff --git a/ui/gfx/geometry/scroll_offset.h b/ui/gfx/geometry/scroll_offset.h
new file mode 100644
index 0000000000..d7aa2f97d8
--- /dev/null
+++ b/ui/gfx/geometry/scroll_offset.h
@@ -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.
+
+#ifndef UI_GFX_GEOMETRY_SCROLL_OFFSET_H_
+#define UI_GFX_GEOMETRY_SCROLL_OFFSET_H_
+
+#include <iosfwd>
+#include <string>
+
+#include "ui/gfx/geometry/safe_integer_conversions.h"
+#include "ui/gfx/geometry/vector2d.h"
+#include "ui/gfx/gfx_export.h"
+
+namespace gfx {
+
+// TODO(szager): Reconcile terminology with blink. An 'offset' here corresponds
+// to a 'position' in blink. In blink, 'offset' means something else. See
+// third_party/WebKit/Source/core/layout/README.md for more information.
+
+class GFX_EXPORT ScrollOffset {
+ public:
+ ScrollOffset() : x_(0), y_(0) {}
+ ScrollOffset(float x, float y) : x_(x), y_(y) {}
+ explicit ScrollOffset(const Vector2dF& v) : x_(v.x()), y_(v.y()) {}
+ explicit ScrollOffset(const Vector2d& v) : x_(v.x()), y_(v.y()) {}
+
+ float x() const { return x_; }
+ void set_x(float x) { x_ = x; }
+
+ float y() const { return y_; }
+ void set_y(float y) { y_ = y; }
+
+ // True if both components are 0.
+ bool IsZero() const {
+ return x_ == 0 && y_ == 0;
+ }
+
+ // Add the components of the |other| ScrollOffset to the current ScrollOffset.
+ void Add(const ScrollOffset& other) {
+ x_ += other.x_;
+ y_ += other.y_;
+ }
+
+ // Subtract the components of the |other| ScrollOffset from the current
+ // ScrollOffset.
+ void Subtract(const ScrollOffset& other) {
+ x_ -= other.x_;
+ y_ -= other.y_;
+ }
+
+ Vector2dF DeltaFrom(const ScrollOffset& v) const {
+ return Vector2dF(x_ - v.x(), y_ - v.y());
+ }
+
+ void operator+=(const ScrollOffset& other) { Add(other); }
+ void operator-=(const ScrollOffset& other) { Subtract(other); }
+
+ void SetToMin(const ScrollOffset& other) {
+ x_ = x_ <= other.x_ ? x_ : other.x_;
+ y_ = y_ <= other.y_ ? y_ : other.y_;
+ }
+
+ void SetToMax(const ScrollOffset& other) {
+ x_ = x_ >= other.x_ ? x_ : other.x_;
+ y_ = y_ >= other.y_ ? y_ : other.y_;
+ }
+
+ void Scale(float scale) { Scale(scale, scale); }
+ void Scale(float x_scale, float y_scale) {
+ x_ *= x_scale;
+ y_ *= y_scale;
+ }
+
+ std::string ToString() const;
+
+ private:
+ float x_;
+ float y_;
+};
+
+inline bool operator==(const ScrollOffset& lhs, const ScrollOffset& rhs) {
+ return lhs.x() == rhs.x() && lhs.y() == rhs.y();
+}
+
+inline bool operator!=(const ScrollOffset& lhs, const ScrollOffset& rhs) {
+ return lhs.x() != rhs.x() || lhs.y() != rhs.y();
+}
+
+inline ScrollOffset operator-(const ScrollOffset& v) {
+ return ScrollOffset(-v.x(), -v.y());
+}
+
+inline ScrollOffset operator+(const ScrollOffset& lhs,
+ const ScrollOffset& rhs) {
+ ScrollOffset result = lhs;
+ result.Add(rhs);
+ return result;
+}
+
+inline ScrollOffset operator-(const ScrollOffset& lhs,
+ const ScrollOffset& rhs) {
+ ScrollOffset result = lhs;
+ result.Add(-rhs);
+ return result;
+}
+
+inline Vector2d ScrollOffsetToFlooredVector2d(const ScrollOffset& v) {
+ return Vector2d(ToFlooredInt(v.x()), ToFlooredInt(v.y()));
+}
+
+inline Vector2dF ScrollOffsetToVector2dF(const ScrollOffset& v) {
+ return Vector2dF(v.x(), v.y());
+}
+
+inline ScrollOffset ScrollOffsetWithDelta(const ScrollOffset& offset,
+ const Vector2dF& delta) {
+ return ScrollOffset(offset.x() + delta.x(),
+ offset.y() + delta.y());
+}
+
+// This is declared here for use in gtest-based unit tests but is defined in
+// the //ui/gfx:test_support target. Depend on that to use this in your unit
+// test. This should not be used in production code - call ToString() instead.
+void PrintTo(const ScrollOffset& scroll_offset, ::std::ostream* os);
+
+} // namespace gfx
+
+#endif // UI_GFX_GEOMETRY_SCROLL_OFFSET_H_
diff --git a/ui/gfx/geometry/size.cc b/ui/gfx/geometry/size.cc
index 69486723a1..781b84d7f5 100644
--- a/ui/gfx/geometry/size.cc
+++ b/ui/gfx/geometry/size.cc
@@ -12,8 +12,8 @@
#include <ApplicationServices/ApplicationServices.h>
#endif
+#include "base/numerics/clamped_math.h"
#include "base/numerics/safe_math.h"
-#include "base/numerics/saturated_arithmetic.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "ui/gfx/geometry/safe_integer_conversions.h"
@@ -58,8 +58,8 @@ base::CheckedNumeric<int> Size::GetCheckedArea() const {
}
void Size::Enlarge(int grow_width, int grow_height) {
- SetSize(base::SaturatedAddition(width(), grow_width),
- base::SaturatedAddition(height(), grow_height));
+ SetSize(base::ClampAdd(width(), grow_width),
+ base::ClampAdd(height(), grow_height));
}
void Size::SetToMin(const Size& other) {
diff --git a/ui/gfx/geometry/vector2d.cc b/ui/gfx/geometry/vector2d.cc
index 2b4875c39c..0ce3b20baa 100644
--- a/ui/gfx/geometry/vector2d.cc
+++ b/ui/gfx/geometry/vector2d.cc
@@ -6,7 +6,7 @@
#include <cmath>
-#include "base/numerics/saturated_arithmetic.h"
+#include "base/numerics/clamped_math.h"
#include "base/strings/stringprintf.h"
namespace gfx {
@@ -16,13 +16,13 @@ bool Vector2d::IsZero() const {
}
void Vector2d::Add(const Vector2d& other) {
- x_ = base::SaturatedAddition(other.x_, x_);
- y_ = base::SaturatedAddition(other.y_, y_);
+ x_ = base::ClampAdd(other.x_, x_);
+ y_ = base::ClampAdd(other.y_, y_);
}
void Vector2d::Subtract(const Vector2d& other) {
- x_ = base::SaturatedSubtraction(x_, other.x_);
- y_ = base::SaturatedSubtraction(y_, other.y_);
+ x_ = base::ClampSub(x_, other.x_);
+ y_ = base::ClampSub(y_, other.y_);
}
int64_t Vector2d::LengthSquared() const {
diff --git a/ui/gfx/geometry/vector2d.h b/ui/gfx/geometry/vector2d.h
index 4b45667adf..d9964532f5 100644
--- a/ui/gfx/geometry/vector2d.h
+++ b/ui/gfx/geometry/vector2d.h
@@ -39,6 +39,9 @@ class GFX_EXPORT Vector2d {
// Subtract the components of the |other| vector from the current vector.
void Subtract(const Vector2d& other);
+ constexpr bool operator==(const Vector2d& other) const {
+ return x_ == other.x_ && y_ == other.y_;
+ }
void operator+=(const Vector2d& other) { Add(other); }
void operator-=(const Vector2d& other) { Subtract(other); }
@@ -70,11 +73,7 @@ class GFX_EXPORT Vector2d {
int y_;
};
-inline bool operator==(const Vector2d& lhs, const Vector2d& rhs) {
- return lhs.x() == rhs.x() && lhs.y() == rhs.y();
-}
-
-inline Vector2d operator-(const Vector2d& v) {
+inline constexpr Vector2d operator-(const Vector2d& v) {
return Vector2d(-v.x(), -v.y());
}
diff --git a/ui/gfx/geometry/vector2d_f.h b/ui/gfx/geometry/vector2d_f.h
index 92f7f87753..7e40ea881f 100644
--- a/ui/gfx/geometry/vector2d_f.h
+++ b/ui/gfx/geometry/vector2d_f.h
@@ -67,15 +67,15 @@ class GFX_EXPORT Vector2dF {
float y_;
};
-inline bool operator==(const Vector2dF& lhs, const Vector2dF& rhs) {
+inline constexpr bool operator==(const Vector2dF& lhs, const Vector2dF& rhs) {
return lhs.x() == rhs.x() && lhs.y() == rhs.y();
}
-inline bool operator!=(const Vector2dF& lhs, const Vector2dF& rhs) {
+inline constexpr bool operator!=(const Vector2dF& lhs, const Vector2dF& rhs) {
return !(lhs == rhs);
}
-inline Vector2dF operator-(const Vector2dF& v) {
+inline constexpr Vector2dF operator-(const Vector2dF& v) {
return Vector2dF(-v.x(), -v.y());
}
diff --git a/ui/gfx/range/BUILD.gn b/ui/gfx/range/BUILD.gn
index 0a8d8b2338..2a2568a287 100644
--- a/ui/gfx/range/BUILD.gn
+++ b/ui/gfx/range/BUILD.gn
@@ -2,7 +2,9 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-component("range") {
+import("//build/config/jumbo.gni")
+
+jumbo_component("range") {
sources = [
"gfx_range_export.h",
"range.cc",
diff --git a/ui/gfx/range/mojo/DEPS b/ui/gfx/range/mojo/DEPS
deleted file mode 100644
index 418fc69e2e..0000000000
--- a/ui/gfx/range/mojo/DEPS
+++ /dev/null
@@ -1,4 +0,0 @@
-include_rules = [
- "+mojo/public",
- "+ui/gfx/range",
-]
diff --git a/ui/gfx/range/mojo/range_struct_traits_unittest.cc b/ui/gfx/range/mojo/range_struct_traits_unittest.cc
index 70b32f3ff7..31705ca900 100644
--- a/ui/gfx/range/mojo/range_struct_traits_unittest.cc
+++ b/ui/gfx/range/mojo/range_struct_traits_unittest.cc
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <utility>
+
#include "base/message_loop/message_loop.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -18,18 +20,19 @@ class RangeStructTraitsTest : public testing::Test,
protected:
mojom::RangeTraitsTestServicePtr GetTraitsTestProxy() {
- return traits_test_bindings_.CreateInterfacePtrAndBind(this);
+ mojom::RangeTraitsTestServicePtr proxy;
+ traits_test_bindings_.AddBinding(this, mojo::MakeRequest(&proxy));
+ return proxy;
}
private:
// RangeTraitsTestService:
- void EchoRange(const Range& p, const EchoRangeCallback& callback) override {
- callback.Run(p);
+ void EchoRange(const Range& p, EchoRangeCallback callback) override {
+ std::move(callback).Run(p);
}
- void EchoRangeF(const RangeF& p,
- const EchoRangeFCallback& callback) override {
- callback.Run(p);
+ void EchoRangeF(const RangeF& p, EchoRangeFCallback callback) override {
+ std::move(callback).Run(p);
}
base::MessageLoop loop_;
diff --git a/ui/gfx/range/range.h b/ui/gfx/range/range.h
index e785eb6c70..5ea9e3c690 100644
--- a/ui/gfx/range/range.h
+++ b/ui/gfx/range/range.h
@@ -24,8 +24,7 @@ typedef struct _NSRange NSRange;
#endif // defined(OS_MACOSX)
#if defined(OS_WIN)
-#include <windows.h>
-#include <richedit.h>
+typedef struct _charrange CHARRANGE;
#endif
namespace gfx {
@@ -52,7 +51,7 @@ class GFX_RANGE_EXPORT Range {
#elif defined(OS_WIN)
// The |total_length| paramater should be used if the CHARRANGE is set to
// {0,-1} to indicate the whole range.
- Range(const CHARRANGE& range, LONG total_length = -1);
+ Range(const CHARRANGE& range, long total_length = -1);
#endif
// Returns a range that is invalid, which is {UINT32_MAX,UINT32_MAX}.